summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilippe Langlais <philippe.langlais@stericsson.com>2012-06-04 19:45:33 +0800
committerPhilippe Langlais <philippe.langlais@stericsson.com>2012-06-04 19:45:33 +0800
commit416f7ed72d4a115d473df16bab92b46c5d120ef1 (patch)
tree18a9b5e14677643b0ca89544780b43d7670ea2ef
parent6c6c532c86bc2f335eed1537e6144266a114a4e9 (diff)
parent23d54cde0b436343684f76141daa3b0dd6710f57 (diff)
Merge topic branch 'cg2900-fm' into integration-linux-ux500
-rw-r--r--Documentation/DocBook/cg2900_fm_radio.tmpl2025
-rwxr-xr-xdrivers/media/radio/CG2900/Makefile12
-rw-r--r--drivers/media/radio/CG2900/cg2900_fm_api.c3389
-rw-r--r--drivers/media/radio/CG2900/cg2900_fm_api.h1077
-rw-r--r--drivers/media/radio/CG2900/cg2900_fm_driver.c5017
-rw-r--r--drivers/media/radio/CG2900/cg2900_fm_driver.h1856
-rw-r--r--drivers/media/radio/CG2900/radio-cg2900.c3024
-rw-r--r--drivers/media/radio/Kconfig16
-rw-r--r--drivers/media/radio/Makefile1
-rw-r--r--include/linux/videodev2.h58
10 files changed, 16475 insertions, 0 deletions
diff --git a/Documentation/DocBook/cg2900_fm_radio.tmpl b/Documentation/DocBook/cg2900_fm_radio.tmpl
new file mode 100644
index 00000000000..2cdbf146ff4
--- /dev/null
+++ b/Documentation/DocBook/cg2900_fm_radio.tmpl
@@ -0,0 +1,2025 @@
+<!DOCTYPE book PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" []>
+
+<book id="STE-CG2900-fm-driver-template">
+ <bookinfo>
+ <title>V4L FM Radio Driver for CG2900</title>
+ <authorgroup>
+ <author>
+ <firstname>Hemant</firstname>
+ <surname>Gupta</surname>
+ <affiliation>
+ <address>
+ <email>hemant.gupta@stericsson.com</email>
+ </address>
+ </affiliation>
+ </author>
+ </authorgroup>
+ <copyright>
+ <year>2010</year>
+ <holder>ST-Ericsson</holder>
+ </copyright>
+ <subjectset>
+ <subject>
+ <subjectterm>Connectivity</subjectterm>
+ </subject>
+ </subjectset>
+ <legalnotice>
+ <!-- Do NOT remove the legal notice below -->
+ <para>
+ This documentation 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.
+ </para>
+ <para>
+ 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.
+ </para>
+ <para>
+ 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
+ </para>
+ <para>
+ For more details see the file COPYING in the source
+ distribution of Linux.
+ </para>
+ </legalnotice>
+ </bookinfo>
+ <toc></toc>
+ <chapter id="intro">
+ <title>Introduction</title>
+ <!-- Do NOT change the chapter id or title! -->
+ <para>
+ This documentation describes the functions provided by the CG2900 FM Driver.
+ </para>
+ </chapter>
+ <chapter id="gettingstarted">
+ <title>Getting Started</title>
+ <!-- Do NOT change the chapter id or title! -->
+ <para>
+ There are no special compilation flags needed to build the CG2900
+ FM Driver.
+ </para>
+ <para>
+ There must be coeffecient and firmware files that match the used chip version inside the firmware folder.
+ The files:
+ <itemizedlist>
+ <listitem><para>cg2900_fm_bt_src_coeff_info.fw.org</para></listitem>
+ <listitem><para>cg2900_fm_ext_src_coeff_info.fw.org</para></listitem>
+ <listitem><para>cg2900_fm_fm_coeff_info.fw.org</para></listitem>
+ <listitem><para>cg2900_fm_fm_prog_info.fw.org</para></listitem>
+ </itemizedlist>
+ handle the mapping between chip version and correct firmware files (firmware and coeffecient files).
+ The necessary firmware and coeffecient files should be placed with the extension <constant>.fw.org</constant>.
+ Note that there is a limitation in the Kernel firmware system regarding name length of a file.
+ </para>
+ <section id="basic-tutorial">
+ <title>Basic Tutorial</title>
+ <para>
+ To enable the CG2900 FM Driver using KConfig go to <constant>Device Drivers -> Multimedia devices </constant>
+ and enable the following:
+ <itemizedlist>
+ <listitem><para>Video For Linux</para></listitem>
+ <listitem><para>Enable Video For Linux API 1 compatible Layer</para></listitem>
+ <listitem><para>Radio Adapters</para></listitem>
+ <listitem><para>Radio Adapter -> ST-Ericsson CG2900 FM Radio support</para></listitem>
+ </itemizedlist>
+ Select the driver as built in kernel object.
+ </para>
+ </section>
+ </chapter>
+ <chapter id="concepts">
+ <title>Concepts</title>
+ <!-- Do NOT change the chapter id or title! -->
+ <para>
+ The CG2900 FM driver acts as an interface between Video4Linux and CG2900-Protocol Driver. It configures the FM chip in FM Rx or FM Tx mode. It also sends the unformatted RDS data to the application for decoding while in FM rx mode and sends the formatted RDS data to FM Chip while in Tx mode.
+ </para>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>FM Driver Working</term>
+ <listitem>
+ <para>
+ In order to send and receive data on an H:4 channel, the FM Driver opens the channel by registering with the CG2900 Protocol driver. After this the FM driver encapsulates the user operation into specific HCI comamnds and sends that data to the CG2900 Connectivity Controller and waits till the response for the previous command is received. FM Driver in this way maintains the flow control.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </chapter>
+ <chapter id="Tasks">
+ <title>Tasks</title>
+ <!-- Do NOT change the chapter id or title! -->
+ <section id="Switching-On-FM">
+ <title>Switch On FM</title>
+ <para>
+ FM specific tasks
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Switching On FM</term>
+ <listitem>
+ <para>
+ For switching on FM the character device /dev/radio0 should be opened from user space. This sets the FM Radio in Idle mode. For configuring the FM Radio in Rx or Tx mode the IOCTL's VIDIOC_S_TUNER and VIDIOC_S_MODULATOR respectively.
+ <programlisting>
+ int fd;
+ fd = open("/dev/radio0", O_RDONLY);
+ if(fd &lt; 0) {
+ printf("open:error!!!\n");
+ goto err;
+ }
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="Switching-Off-FM">
+ <title>Switch Off FM</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Switching Off FM</term>
+ <listitem>
+ <para>
+ For switching OFF FM the character device /dev/radio0 should be closed from user space.
+ <programlisting>
+ if(fd &gt;= 0)
+ close(fd);
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="Rx-Mode">
+ <title>Switching To FM Rx Mode</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Switching To FM Rx Mode</term>
+ <listitem>
+ <para>
+ For switching on FM Rx mode the IOCTL VIDIOC_S_TUNER should be called with appropriate parameters.
+ <programlisting>
+ memset(&amp;tuner, 0, sizeof(tuner));
+ tuner.index = 0;
+ tuner.rxsubchans |= V4L2_TUNER_SUB_STEREO;
+ if (ioctl(fd, VIDIOC_S_TUNER, &amp;tuner) &lt; 0) {
+ printf("VIDIOC_S_TUNER:error!!\n");
+ return;
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="Tx-Mode">
+ <title>Switching To FM Tx Mode</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Switching To FM Tx Mode</term>
+ <listitem>
+ <para>
+ For switching on FM Tx mode the IOCTL VIDIOC_S_MODULATOR should be
+ called with appropriate parameters.
+ <programlisting>
+ memset(&amp;modulator, 0, sizeof(modulator));
+ modulator.index = 0;
+ modulator.txsubchans |= V4L2_TUNER_SUB_STEREO;
+ if (ioctl(fd, VIDIOC_S_MODULATOR, &amp;modulator) &lt; 0) {
+ printf("VIDIOC_S_MODULATOR:error!!\n");
+ return;
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="FM-Standby">
+ <title>Standby</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Making the FM Radio go in Standby Mode</term>
+ <listitem>
+ <para>
+ For making the FM Radio go in Standby mode, the IOCTL VIDIOC_S_CTRL should be used. The id of the v4l2_control structure should be set to V4L2_CID_CG2900_RADIO_CHIP_STATE and the value of v4l2_control structure should be set as V4L2_CG2900_RADIO_STANDBY.
+ <programlisting>
+ struct v4l2_control sctrl;
+ int ret;
+ sctrl.id = V4L2_CID_CG2900_RADIO_CHIP_STATE;
+ sctrl.value = V4L2_CG2900_RADIO_STANDBY;
+ ret = ioctl(fd, VIDIOC_S_CTRL, &amp;sctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_CTRL:error!!\n");
+ }
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="Powerup-from-standby">
+ <title>Powering Up FM From Standby Mode</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Powering Up FM Radio from Standby Mode</term>
+ <listitem>
+ <para>
+ For powering up FM radio again from standby mode, the IOCTL VIDIOC_S_CTRL should be used. The id of the v4l2_control structure should be set to V4L2_CID_CG2900_RADIO_CHIP_STATE and the value of v4l2_control structure should be set as V4L2_CG2900_RADIO_POWERUP.
+ <programlisting>
+ struct v4l2_control sctrl;
+ int ret;
+ sctrl.id = V4L2_CID_CG2900_RADIO_CHIP_STATE;
+ sctrl.value = V4L2_CG2900_RADIO_POWERUP;
+ ret = ioctl(fd, VIDIOC_S_CTRL, &amp;sctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_CTRL:error!!\n");
+ }
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="tune-frequency">
+ <title>Tune Channel</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Tune to a particular station</term>
+ <listitem>
+ <para>
+ for tuning to a particular station, the IOCTL VIDIOC_S_FREQUENCY should be used. The frequency of the v4l2_frequency structure should be converted to V4L2 format.
+ <programlisting>
+ struct v4l2_frequency freq;
+ int ret;
+ /* Convert frequency in Hz to V4L2 Format */
+ freq.frequency = (frequency * 2)/ 125;
+ ret = ioctl(fd, VIDIOC_S_FREQUENCY, &amp;freq);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_FREQUENCY:error!!\n");
+ }
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="get-frequency">
+ <title>Get Tuned Channel</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Get the Currently tuned Station Frequncy</term>
+ <listitem>
+ <para>
+ for tuning to a particular station, the IOCTL VIDIOC_G_FREQUENCY should be used. The frequency returned in the v4l2_frequency structure would be in V4L2 format.
+ <programlisting>
+ struct v4l2_frequency freq;
+ int ret;
+ ret = ioctl(fd, VIDIOC_G_FREQUENCY, &amp;freq);
+ if (ret &lt; 0) {
+ printf("VIDIOC_G_FREQUENCY:error!!\n");
+ *frequency = 0;
+ return;
+ }
+ /* Convert frequency to Hz from V4L2 Format */
+ *frequency = (freq.frequency * 125)/2;
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="get-signal-strength">
+ <title>Retreive Signal Strength</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Retreive Signal Strength</term>
+ <listitem>
+ <para>
+ For retreiving the Signal strength of the currently tuned channel in FM Rx mode, IOCTL VIDIOC_G_TUNER should be called. The current signal strength would be represented by the parameter signal of the v4l2_tuner strucure.
+ <programlisting>
+ void get_signal_strength(int *rssi)
+ {
+ struct v4l2_tuner tuner;
+ int ret;
+ memset(&amp;tuner, 0, sizeof(tuner));
+ tuner.index = 0;
+ ret = ioctl(fd, VIDIOC_G_TUNER, &amp;tuner);
+ if (ret &lt; 0) {
+ printf("VIDIOC_G_TUNER:error!!\n");
+ *rssi = 0;
+ return;
+ }
+ *rssi = tuner.signal;
+ }
+ </programlisting>
+Note: Currently the retrieved signal strength is in decimals and not in "dBuV", proper external conversion required.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="band-scan">
+ <title>Band Scan</title>
+ <para>
+ <!-- Driver loading Parameters:Not Applicable -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Band Scan</term>
+ <listitem>
+ <para>
+ For doing a band scan, ie search for all available stations in the entire FM band, IOCTL VIDIOC_S_CTRL should be used with parameter id of the v4l2_control structure should be set to V4L2_CID_CG2900_RADIO_BANDSCAN and the value of v4l2_control structure should be set as V4L2_CG2900_RADIO_BANDSCAN_START. If the IOCTL returns successfully, a common thread (which should have been created at the start of the application and is already polling to FM driver for multiple other interrupts including events related to Block Scan and Search Frequency operation) which polls with an infinite timeout iteratively until the end of the user-space application, when poll in one iteration is complete. When poll is complete, thread will make an IOCTL call with VIDIOC_G_EXT_CTRLS with parameter id set to V4L2_CID_CG2900_RADIO_GET_INTERRUPT and the data structure FmInterrupt.controls-&gt;string associated with V4L2_CID_CG2900_RADIO_GET_INTERRUPT shall contain the interrupt retrieved from FMD. Interrupts received in this manner from FMD can be any of the following.
+ <itemizedlist>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_UNKNOWN</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_SEARCH_COMPLETED</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_BAND_SCAN_COMPLETED</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_BLOCK_SCAN_COMPLETED</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_SCAN_CANCELLED</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_MONO_STEREO_TRANSITION</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_DEVICE_RESET</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_RDS_RECEIVED</para></listitem>
+ </itemizedlist>
+
+For Band Scan it shall be V4L2_CG2900_RADIO_INTERRUPT_BAND_SCAN_COMPLETE, an appropriate handler should be then called from thethread to retrieve the found stations along with RSSI using the IOCTL VIDIOC_G_EXT_CTRLS, this IOCTL should be used with parameters as described in example code. Note that the common thread for capturing synchronous as well asynchronous events used here is FmInterruptMonitoringThread. It shall be used in other sections i.e. Block Scan, Cancel Scan, Search Frequency and Mono Stereo Transition.
+ <programlisting>
+ void Band_Scan()
+ {
+ struct v4l2_control sctrl
+ int ret;
+ sctrl.id = V4L2_CID_CG2900_RADIO_BANDSCAN;
+ sctrl.value = V4L2_CG2900_RADIO_BANDSCAN_START;
+ ret = ioctl(fd, VIDIOC_S_CTRL, &amp;sctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_CTRL:error!!\n");
+ }
+ pthread_create(&amp;fmScanThread, NULL, FmScanThread, NULL);
+ }
+
+ static void *FmInterruptMonitoringThread(void *param)
+ {
+ struct v4l2_ext_controls FmInterrupt;
+ struct pollfd pollFd;
+ long * p = NULL;
+ int index, ret, count = 0;
+ int interrupt, interrupt_reason;
+ int err;
+
+ while(closeApp) {
+ pollFd.fd = fd;
+ pollFd.events = POLLRDNORM;
+ /* wait infinitely for interrupt */
+ timeout = -1;
+ ret = poll(&amp;pollFd, 1, timeout);
+ if(!closeApp)
+ break;
+ if(ret) {
+ if(pollFd.revents &amp; POLLRDNORM)
+ {
+ /* Get the interrupt */
+ FmInterrupt.count = 0;
+ FmInterrupt.ctrl_class = V4L2_CTRL_CLASS_USER;
+ FmInterrupt.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ if(!FmInterrupt.controls)
+ goto error;
+ FmInterrupt.controls-&gt;id = V4L2_CID_CG2900_RADIO_GET_INTERRUPT;
+ FmInterrupt.controls-&gt;size = 2;
+ FmInterrupt.controls-&gt;string = (int *)malloc(sizeof(int) * FmInterrupt.controls-&gt;size);
+ interrupt_buffer_pointer = FmInterrupt.controls-&gt;string;
+ if (ioctl(fd, VIDIOC_G_EXT_CTRLS, &amp;FmInterrupt) &lt; 0) {
+ printf("VIDIOC_G_EXT_CTRLS:error!!\n");
+ ret_val = -1;
+ goto error_free_ext_control_string;
+ }
+
+ if(!ret_val) {
+ interrupt = *interrupt_buffer_pointer;
+ interrupt_reason = *(interrupt_buffer_pointer + 1);
+ printf("Interrupt = %d, , Result = %d\n", interrupt, interrupt_reason);
+ if(interrupt_reason == 0) {
+ switch(interrupt)
+ {
+ case V4L2_CG2900_RADIO_INTERRUPT_BAND_SCAN_COMPLETED:
+ /* Band Scan Completed */
+ HandleBandScanCompletion();
+ otherOperationInProgress = 0;
+ break;
+
+ }
+ }
+ }
+error_free_ext_control_string:
+ free(FmInterrupt.controls-&gt;string);
+error_free_ext_control_control:
+ free(FmInterrupt.controls);
+error:
+ otherOperationInProgress = 0;
+ } else {
+ printf ("FmInterruptMonitoringThread : poll returned = %d\n", ret);
+ }
+ }
+ }
+ return 0;
+ }
+
+ static void HandleBandScanCompletion()
+ {
+ struct v4l2_ext_controls scanResult;
+ long * band_scan_pointer = NULL;
+ int err;
+ int index, ret, count = 0;
+
+ /* Get the Number Of Channels */
+ scanResult.count = 0;
+ scanResult.ctrl_class = V4L2_CTRL_CLASS_USER;
+ scanResult.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ if(!scanResult.controls)
+ goto done;
+ scanResult.controls-&gt;id = V4L2_CID_CG2900_RADIO_BANDSCAN_GET_RESULTS;
+ scanResult.controls-&gt;size = 0;
+ scanResult.controls-&gt;string = NULL;
+ err = ioctl(fd, VIDIOC_G_EXT_CTRLS, &amp; scanResult);
+
+ if (err &lt; 0 &amp; &amp; errno != ENOSPC) {
+ printf("VIDIOC_G_EXT_CTRLS:error!!\n");
+ goto error_free_ext_control_control;
+ }
+
+ if(scanResult.controls-&gt;size &gt; 0 )
+ {
+ scanResult.controls-&gt;string = (long *)malloc(sizeof(long) * 2 * scanResult.controls-&gt;size );
+ band_scan_pointer = scanResult.controls-&gt;string;
+ printf("\n\n\n==================================\n");
+ printf("\nNumber of Channels Found = %d \n", scanResult.controls-&gt;size);
+ printf("\n==================================\n");
+ if (ioctl(fd, VIDIOC_G_EXT_CTRLS, &amp;scanResult) &lt; 0) {
+ printf("VIDIOC_G_EXT_CTRLS:error!!\n");
+ goto error_free_ext_control_string;
+ }
+ printf("\n================================\n");
+ printf("\nSNo. Frequency(MHz) RSSI\n");
+ printf("\n================================\n");
+ for (index = 0, count = 0; index &lt; scanResult.controls-&gt;size; index ++, count +=2) {
+ printf("%d %d.%d %d\n", index + 1,
+ MEGAHRTZ((*(band_scan_pointer +count + 0) * 125) / 2),
+ *(band_scan_pointer + count + 1));
+ }
+ printf("\n================================\n");
+ error_free_ext_control_string:
+ free(band_scan_pointer);
+ }
+ else if(scanResult.controls-&gt;size == 0)
+ {
+ printf("\nNo channels found during scanning!!\n");
+ }
+ error_free_ext_control_control:
+ free(scanResult.controls);
+ done:
+ otherOperationInProgress = 0;
+ }
+
+ </programlisting>
+ Note: Currently the retrieved signal strength is in decimals and not in "dBuV", proper external conversion required.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="block-scan">
+ <title>Block Scan</title>
+ <para>
+ <!-- Driver loading Parameters:Not Applicable -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Block Scan</term>
+ <listitem>
+ <para>
+ The Block Scan functionality will take two inputs, start and stop frequency (V4L2 compliance) and enables the host to scan all channels with in that range for RSSI values. The measured channels will be stored in a list in order of channel number and the block scan feature to identify "empty" channels for transmission. And for doing a block scan, IOCTL VIDIOC_S_EXT_CTRL with parameter id of the v4l2_control structure should be set to V4L2_CID_FM_RADIO_BLOCKSCAN_START. If the IOCTL returns successfully, a common thread (which should have been created at the start of the application and is already polling to FM driver for multiple other interrupts including events related to Band Scan and Search Frequency operation) which polls with an infinite timeout iteratively until the end of the user-space application, when poll in one iteration is complete. When poll is complete, thread will make an IOCTL call with VIDIOC_G_EXT_CTRLS with parameter id set to V4L2_CID_CG2900_RADIO_GET_INTERRUPT and the data structure FmInterrupt.controls-&gt;string associated with V4L2_CID_CG2900_RADIO_GET_INTERRUPT shall contain the interrupt retrieved from FMD. Interrupts received in this manner from FMD can be any of the following.
+ <itemizedlist>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_UNKNOWN</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_SEARCH_COMPLETED</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_BAND_SCAN_COMPLETED</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_BLOCK_SCAN_COMPLETED</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_SCAN_CANCELLED</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_MONO_STEREO_TRANSITION</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_DEVICE_RESET</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_INTERRUPT_RDS_RECEIVED</para></listitem>
+ </itemizedlist>
+
+For Block Scan it shall be V4L2_CG2900_RADIO_INTERRUPT_BLOCK_SCAN_COMPLETED, an appropriate handler should be then called from thethread to retrieve the found stations along with RSSI using the IOCTL VIDIOC_G_EXT_CTRLS, this IOCTL should be used with parameters as described in example code. Note that the common thread for capturing synchronous as well asynchronous events used here is FmInterruptMonitoringThread. It shall be used in other sections i.e. Band Scan, Cancel Scan, Search Frequency and Mono Stereo Transition. When poll is complete, the found stations along with RSSI should be retrieved using the IOCTL VIDIOC_G_EXT_CTRLS should be used with parameters as described in example code.
+ <programlisting>
+ void Block_Scan()
+ {
+ struct v4l2_ext_controls ext_ctrl;
+ long *p = NULL;
+ int index;
+ int ret_val;
+ if(1 == mode) {
+ otherOperationInProgress = 1;
+ ext_ctrl.ctrl_class = V4L2_CTRL_CLASS_USER;
+ ext_ctrl.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ ext_ctrl.count = 0;
+ ext_ctrl.controls-&gt;id = V4L2_CID_CG2900_RADIO_BLOCKSCAN_START;
+ ext_ctrl.controls-&gt;size = 2;
+ ext_ctrl.controls-&gt;string = (long *)malloc(sizeof(long) * ext_ctrl.controls-&gt;size);
+ p = ext_ctrl.controls-&gt;string;
+ *p = (StartFreq * 2)/ 125;
+ *(p + 1) = (EndFreq * 2)/ 125;;
+ if (ioctl(fd, VIDIOC_S_EXT_CTRLS, &amp;ext_ctrl) &lt; 0)
+ printf("APP_BlockScanStart:VIDIOC_S_EXT_CTRLS:error!!\n");
+ free(ext_ctrl.controls-&gt;string);
+ free(ext_ctrl.controls);
+ pthread_create(&amp;fmBlockScanThread, NULL, FmBlockScanThread, NULL);
+ }
+
+ static void *FmInterruptMonitoringThread(void *param)
+ {
+ struct v4l2_ext_controls FmInterrupt;
+ struct pollfd pollFd;
+ long * p = NULL;
+ int index, ret, count = 0;
+ int interrupt, interrupt_reason;
+ int err;
+
+ while(closeApp) {
+ pollFd.fd = fd;
+ pollFd.events = POLLRDNORM;
+ /* wait infinitely for interrupt */
+ timeout = -1;
+ ret = poll(&amp;pollFd, 1, timeout);
+ if(!closeApp)
+ break;
+ if(ret) {
+ if(pollFd.revents &amp; POLLRDNORM)
+ {
+ /* Get the interrupt */
+ FmInterrupt.count = 0;
+ FmInterrupt.ctrl_class = V4L2_CTRL_CLASS_USER;
+ FmInterrupt.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ if(!FmInterrupt.controls)
+ goto error;
+ FmInterrupt.controls-&gt;id = V4L2_CID_CG2900_RADIO_GET_INTERRUPT;
+ FmInterrupt.controls-&gt;size = 2;
+ FmInterrupt.controls-&gt;string = (int *)malloc(sizeof(int) * FmInterrupt.controls-&gt;size);
+ interrupt_buffer_pointer = FmInterrupt.controls-&gt;string;
+ if (ioctl(fd, VIDIOC_G_EXT_CTRLS, &amp;FmInterrupt) &lt; 0) {
+ printf("VIDIOC_G_EXT_CTRLS:error!!\n");
+ ret_val = -1;
+ goto error_free_ext_control_string;
+ }
+
+ if(!ret_val) {
+ interrupt = *interrupt_buffer_pointer;
+ interrupt_reason = *(interrupt_buffer_pointer + 1);
+ printf("Interrupt = %d, , Result = %d\n", interrupt, interrupt_reason);
+ if(interrupt_reason == 0) {
+ switch(interrupt)
+ {
+ case V4L2_CG2900_RADIO_INTERRUPT_BLOCK_SCAN_COMPLETED:
+ /* Block Scan Completed */
+ HandleBlockScanCompletion();
+ otherOperationInProgress = 0;
+ break;
+ }
+ }
+ }
+error_free_ext_control_string:
+ free(FmInterrupt.controls-&gt;string);
+error_free_ext_control_control:
+ free(FmInterrupt.controls);
+error:
+ otherOperationInProgress = 0;
+ } else {
+ printf ("FmInterruptMonitoringThread : poll returned = %d\n", ret);
+ }
+ }
+ }
+ return 0;
+ }
+
+ static void HandleBlockScanCompletion()
+ {
+ struct v4l2_ext_controls blockscanResult;
+ long * block_scan_pointer = NULL;
+ int index, ret;
+ int err;
+ int current_grid = -1;
+ FILE *fp;
+ long start_freq = StartFreq;
+ long next_freq_offset = 0;
+
+ fp = fopen("/sys/module/radio_cg2900/parameters/grid", "r");
+ if(fp != NULL)
+ {
+ /* Retrieve the currently set grid to determine the next channel is 50 Khz, 100 Khz or 200 Khz apart */
+ fscanf(fp, "%d", &amp;current_grid);
+ fclose(fp);
+ }
+
+ if(current_grid == 0) {
+ next_freq_offset = 50000;
+ } else if (current_grid == 1) {
+ next_freq_offset = 100000;
+ } else if (current_grid == 2) {
+ next_freq_offset = 200000;
+ }
+
+ /* Get the Number Of Channels */
+ blockscanResult.count = 0;
+ blockscanResult.ctrl_class = V4L2_CTRL_CLASS_USER;
+ blockscanResult.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ if(!blockscanResult.controls)
+ goto done;
+ blockscanResult.controls-&gt;id = V4L2_CID_CG2900_RADIO_BLOCKSCAN_GET_RESULTS;
+ blockscanResult.controls-&gt;size = 0;
+
+ blockscanResult.controls-&gt;string = NULL;
+ err = ioctl(fd, VIDIOC_G_EXT_CTRLS, &amp;blockscanResult);
+
+ if (err &lt; 0 &amp;&amp; errno != ENOSPC) {
+ printf("VIDIOC_G_EXT_CTRLS:error!!\n");
+ goto error_free_ext_control_control;
+ }
+
+ if(blockscanResult.controls-&gt;size &gt; 0)
+ {
+ blockscanResult.controls-&gt;string = (long *)malloc(sizeof(long) * blockscanResult.controls-&gt;size );
+ block_scan_pointer = blockscanResult.controls-&gt;string;
+ if (ioctl(fd, VIDIOC_G_EXT_CTRLS, &amp;blockscanResult) &lt; 0) {
+ printf("VIDIOC_G_EXT_CTRLS:error!!\n");
+ goto error_free_ext_control_string;
+ }
+ printf("\n================================\n");
+ printf("\nMHz. RSSI\n");
+ printf("\n================================\n");
+ for (index = 0; index &lt; blockscanResult.controls-&gt;size; index ++) {
+ printf("%d.%d %d\n", MEGAHRTZ(start_freq), *(block_scan_pointer + index));
+ start_freq += next_freq_offset;
+ }
+ printf("\n================================\n");
+ error_free_ext_control_string:
+ free(block_scan_pointer);
+ }
+ else if(blockscanResult.controls-&gt;size == 0)
+ {
+ printf("\nNo channels found during Block Scan!!\n");
+ }
+ error_free_ext_control_control:
+ free(blockscanResult.controls);
+ done:
+ otherOperationInProgress = 0;
+ }
+
+
+ </programlisting>
+ Note: Currently the retrieved signal strength is in decimals and not in "dBuV", proper external conversion required.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="cancel-scan-seek">
+ <title>Cancel Scan</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Cancel Scan/Seek</term>
+ <listitem>
+ <para>
+ This is used for stopping an active Band Scan, Seek operation and Block Scan. IOCTL VIDIOC_S_CTRL should be used with parameter id of the v4l2_control structure. For Eg incase of Band scan the parameter id should be set to V4L2_CID_CG2900_RADIO_BANDSCAN and the value of v4l2_control structure should be set as V4L2_CG2900_RADIO_BANDSCAN_STOP. The example thread shown in following code snippet FmInterruptMonitoringThread shall have already been started as mentioned in Band Scan and Block sections, it shall receive an asynchronous event V4L2_CG2900_RADIO_INTERRUPT_SCAN_CANCELLED.
+ <programlisting>
+ struct v4l2_control sctrl;
+ int ret;
+ sctrl.id = V4L2_CID_CG2900_RADIO_BANDSCAN;
+ sctrl.value = V4L2_CG2900_RADIO_BANDSCAN_STOP;
+ ret = ioctl(fd, VIDIOC_S_CTRL, &amp;sctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_CTRL:error!!\n");
+ }
+
+ static void *FmInterruptMonitoringThread(void *param)
+ {
+ struct v4l2_ext_controls FmInterrupt;
+ struct pollfd pollFd;
+ long * p = NULL;
+ int index, ret, count = 0;
+ int interrupt, interrupt_reason;
+ int err;
+
+ while(closeApp) {
+ pollFd.fd = fd;
+ pollFd.events = POLLRDNORM;
+ /* wait infinitely for interrupt */
+ timeout = -1;
+ ret = poll(&amp;pollFd, 1, timeout);
+ if(!closeApp)
+ break;
+ if(ret) {
+ if(pollFd.revents &amp; POLLRDNORM)
+ {
+ /* Get the interrupt */
+ FmInterrupt.count = 0;
+ FmInterrupt.ctrl_class = V4L2_CTRL_CLASS_USER;
+ FmInterrupt.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ if(!FmInterrupt.controls)
+ goto error;
+ FmInterrupt.controls-&gt;id = V4L2_CID_CG2900_RADIO_GET_INTERRUPT;
+ FmInterrupt.controls-&gt;size = 2;
+ FmInterrupt.controls-&gt;string = (int *)malloc(sizeof(int) * FmInterrupt.controls-&gt;size);
+ interrupt_buffer_pointer = FmInterrupt.controls-&gt;string;
+ if (ioctl(fd, VIDIOC_G_EXT_CTRLS, &amp;FmInterrupt) &lt; 0) {
+ printf("VIDIOC_G_EXT_CTRLS:error!!\n");
+ ret_val = -1;
+ goto error_free_ext_control_string;
+ }
+
+ if(!ret_val) {
+ interrupt = *interrupt_buffer_pointer;
+ interrupt_reason = *(interrupt_buffer_pointer + 1);
+ printf("Interrupt = %d, , Result = %d\n", interrupt, interrupt_reason);
+ if(interrupt_reason == 0) {
+ switch(interrupt)
+ {
+ case V4L2_CG2900_RADIO_INTERRUPT_SCAN_CANCELLED:
+ /* Scan/Search/Block Scan Cancelled */
+ printf(" Scan cancelled by user\n");
+ otherOperationInProgress = 0;
+ break;
+ }
+ }
+ }
+error_free_ext_control_string:
+ free(FmInterrupt.controls-&gt;string);
+error_free_ext_control_control:
+ free(FmInterrupt.controls);
+error:
+ otherOperationInProgress = 0;
+ } else {
+ printf ("FmInterruptMonitoringThread : poll returned = %d\n", ret);
+ }
+ }
+ }
+ return 0;
+ }
+
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="rds-receive">
+ <title>RDS Receive</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>RDS Receive</term>
+ <listitem>
+ <para>
+ For enabling/disabling RDS for FM Rx, IOCTL VIDIOC_S_TUNER should be used with parameter rxsubchans of the v4l2_tuner structure set to V4L2_TUNER_SUB_RDS if rds needs to be enabled and the same value must not be set in case rds is to be disabled. Once RDS data is available, the appplication would be signalled waiting on poll. If the Interrupt retrieved using V4L2_CID_CG2900_RADIO_GET_INTERRUPT is V4L2_CG2900_RADIO_INTERRUPT_RDS_RECEIVED, RDS data can be retrieved using the read() functionality from the CG2900 FM driver. The RDS data received from FM Driver should be parsed in user space to retrive RDS information i.e Radio Text, Program Service Name, Program Identification, Program Type, Alternate Frequency, etc.
+ <programlisting>
+ void rds_rx_set(bool enable_rds)
+ {
+ struct v4l2_tuner tuner;
+ int ret;
+ memset(&amp;tuner, 0, sizeof(tuner));
+ tuner.index = 0;
+ if(enable_rds)
+ tuner.rxsubchans |= V4L2_TUNER_SUB_RDS;
+ else
+ tuner.rxsubchans &amp; = ~V4L2_TUNER_SUB_RDS;
+ ret = ioctl(fd, VIDIOC_S_TUNER, &amp;tuner);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_TUNER:error!!\n");
+ }
+ }
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="af-update_switch">
+ <title>AF Update and Switching</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>AF Update &amp; Switching</term>
+ <listitem>
+ <para>Alternate Frequency (AF) Handling needs to be done in user space.</para>
+ <para>The application should use the AF RDS group data to compose a list of AFs when tuned to a new channel.</para>
+ <para>When the reception of the currently tuned frequency falls below a set threshold, it can decide to switch to one of the alternative frequencies for this channel.
+ </para>
+ <para>The application can perform an AF Update, which returns the RSSI value for all or some of the channel's AFs. Thus allowing the hardware to switch to the AF with the highest RSSI. The AF Update could be designed to stop as soon as it finds an AF with an acceptable RSSI level. In the event that all the AF RSSI values are lower than the base channel, the AF Switch would not be necessary.
+ </para>
+ <para>To know the RSSI of the alternative frequencies (V4L2 compliance), the application can use the IOCTL VIDIOC_S_CTRL with parameter id set to V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_START, and the parameter value be set as the frequency in Hz for a channel from the AF List. If this call returns successfully, the RSSI of the frequency can then be retrieved. Using IOCTL VIDIOC_G_CTRL, with the parameter id set to V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_GET_RESULT, and the output parameter value will contain the RSSI of the AF frequency.
+ </para>
+ <para>If it is still deemed necessary to switch channels, the next step is then to switch to an alternative frequency in the AF list. This can be done using the IOCTL VIDIOC_S_EXT_CTRLS, with:
+ </para>
+ <itemizedlist>
+ <listitem><para>Parameter id set to V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START</para></listitem>
+ <listitem><para>Parameter size set to 2</para></listitem>
+ <listitem><para>Parameters filled as below (string field of the parameter) </para></listitem>
+ <listitem><para>Control class parameter set to V4L2_CTRL_CLASS_USER</para></listitem>
+ <listitem><para>The AF switch frequency in Hz</para></listitem>
+ <listitem><para>Expected PI code </para></listitem>
+ </itemizedlist>
+ <para>The application can check if the AF switch succeeded or not using the IOCTL VIDIOC_G_CTRL, with parameter id set to V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_GET_RESULT, and the output parameter value will contain the AF switch conclusion.
+ </para>
+ <para> The example code below illustrates both the aforementioned functionalities.</para>
+ <para>
+ <programlisting>
+ void PerformAFUpdate(long AF_Frequency, int *AF_Rssi)
+ {
+ struct v4l2_control sctrl, gctrl;
+ int ret;
+ sctrl.id = V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_START;
+ sctrl.value = AF_Frequency;
+ ret = ioctl(fd, VIDIOC_S_CTRL, &amp; sctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_CTRL:error!!\n");
+ }
+ gctrl.id = V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_GET_RESULT;
+ ret = ioctl(fd, VIDIOC_G_CTRL, &amp; gctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_G_CTRL:error!!\n");
+ }
+ *AF_Rssi = gctrl.value;
+ }
+ void PerformAFSwitch(long AF_BestFrequency, int AF_ExpectedPI, int *AF_SwitchConclusion)
+ {
+ struct v4l2_control gctrl;
+ struct v4l2_ext_controls ext_ctrl;
+ int ret;
+ int conclusion;
+ long freq;
+ long *p = NULL;
+ ext_ctrl.ctrl_class = V4L2_CTRL_CLASS_USER;
+ ext_ctrl.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ ext_ctrl.count = 0;
+ ext_ctrl.controls->id = V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START;
+ ext_ctrl.controls->size = 2;
+ ext_ctrl.controls->string = (long *)malloc(sizeof(long) * ext_ctrl.controls->size);
+ p = ext_ctrl.controls->string;
+ *p = (AF_BestFrequency * 2)/ 125;
+ *(p+1) = (long)AF_ExpectedPI;
+ ret = ioctl(fd, VIDIOC_S_EXT_CTRLS, &amp; ext_ctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_EXT_CTRLS:error!!\n");
+ }
+ free(ext_ctrl.controls->string);
+ free(ext_ctrl.controls);
+ gctrl.id = V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_GET_RESULT;
+ ret = ioctl(fd, VIDIOC_G_CTRL, &amp; gctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_G_CTRL:error!!\n");
+ }
+ *AF_SwitchConclusion = gctrl.value;
+ }
+ </programlisting>
+ Note: For V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_GET_RESULT returned values are:
+ <itemizedlist>
+ <listitem><para> -1 AF Switch failed, the AF-RSSI was too low.</para></listitem>
+ <listitem><para> -2 AF Switch failed, the AF-PI Doesn't correlate.</para></listitem>
+ <listitem><para> -3 AF Switch failed, the AF-RDS SYNC Lost.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="rds-transmit">
+ <title>RDS Transmit</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>RDS Transmit</term>
+ <listitem>
+ <para>
+ For enabling/disabling RDS for FM Tx, IOCTL VIDIOC_S_MODULATOR should be used with parameter txsubchans of the v4l2_modulator structure set to V4L2_TUNER_SUB_RDS if rds needs to be enabled and the same value must not be set in case rds is to be disabled. For Trasmitting RDS Data like PI, PTY, PSN, RT, VIDIOC_S_EXT_CTRLS IOCTL should be used with the id set to V4L2_CID_RDS_TX_PI, V4L2_CID_RDS_TX_PTY, V4L2_CID_RDS_TX_PS_NAME and V4L2_CID_RDS_TX_RADIO_TEXT respectively. Below example shows how to transmit various RDS functionalities.
+ <programlisting>
+ void rds_tx_set(bool enable_rds)
+ {
+ struct v4l2_modulator modulator;
+ int ret;
+ memset(&amp;modulator, 0, sizeof(modulator));
+ modulator.index = 0;
+ if(enable_rds)
+ modulator.txsubchans |= V4L2_TUNER_SUB_RDS;
+ else
+ modulator.txsubchans &amp; = ~V4L2_TUNER_SUB_RDS;
+ ret = ioctl(fd, VIDIOC_S_MODULATOR, &amp; modulator);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_MODULATOR:error!!\n");
+ }
+ }
+ void rds_tx_PI(void *value)
+ {
+ struct v4l2_ext_controls ext_ctrl;
+ int ret;
+ unsigned short *pi_code = (unsigned short *)value;
+ ext_ctrl.ctrl_class = V4L2_CTRL_CLASS_FM_TX;
+ ext_ctrl.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ ext_ctrl.count = 0;
+ ext_ctrl.controls->id = V4L2_CID_RDS_TX_PI;
+ ext_ctrl.controls->size = 0;
+ ext_ctrl.controls->string = NULL;
+ ext_ctrl.controls->value = *pi_code;
+ ret = ioctl(fd, VIDIOC_S_EXT_CTRLS, &amp; ext_ctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_EXT_CTRLS:error!!\n");
+ }
+ free(ext_ctrl.controls);
+ }
+ void rds_tx_PTY(void *value)
+ {
+ struct v4l2_ext_controls ext_ctrl;
+ int ret;
+ unsigned short *pty_code = (unsigned short *)value;
+ ext_ctrl.ctrl_class = V4L2_CTRL_CLASS_FM_TX;
+ ext_ctrl.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ ext_ctrl.count = 0;
+ ext_ctrl.controls->id = V4L2_CID_RDS_TX_PTY;
+ ext_ctrl.controls->size = 0;
+ ext_ctrl.controls->string = NULL;
+ ext_ctrl.controls->value = *pty_code;
+ ret = ioctl(fd, VIDIOC_S_EXT_CTRLS, &amp; ext_ctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_EXT_CTRLS:error!!\n");
+ }
+ free(ext_ctrl.controls);
+ }
+ void rds_tx_PSN(void *value)
+ {
+ struct v4l2_ext_controls ext_ctrl;
+ int ret;
+ char *psn = (char *)value;
+ ext_ctrl.ctrl_class = V4L2_CTRL_CLASS_FM_TX;
+ ext_ctrl.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ ext_ctrl.count = 0;
+ ext_ctrl.controls->id = V4L2_CID_RDS_TX_PS_NAME;
+ ext_ctrl.controls->size = strlen(psn);
+ ext_ctrl.controls->value = 0;
+ ext_ctrl.controls->string = (char *)malloc(ext_ctrl.controls->size);
+ memcpy(ext_ctrl.controls->string, psn, ext_ctrl.controls->size);
+ ret = ioctl(fd, VIDIOC_S_EXT_CTRLS, &amp; ext_ctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_EXT_CTRLS:error!!\n");
+ }
+ free(ext_ctrl.controls->string);
+ free(ext_ctrl.controls);
+ }
+ void rds_tx_RT(void *value)
+ {
+ struct v4l2_ext_controls ext_ctrl;
+ int ret;
+ char *radio_text = (char *)value;
+ ext_ctrl.ctrl_class = V4L2_CTRL_CLASS_FM_TX;
+ ext_ctrl.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ ext_ctrl.count = 0;
+ ext_ctrl.controls->id = V4L2_CID_RDS_TX_RADIO_TEXT;
+ ext_ctrl.controls->size = strlen(radio_text);
+ ext_ctrl.controls->value = 0;
+ ext_ctrl.controls->string = (char *)malloc(ext_ctrl.controls->size);
+ memcpy(ext_ctrl.controls->string, radio_text, ext_ctrl.controls->size);
+ ret = ioctl(fd, VIDIOC_S_EXT_CTRLS, &amp; ext_ctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_EXT_CTRLS:error!!\n");
+ }
+ free(ext_ctrl.controls->string);
+ free(ext_ctrl.controls);
+ }
+ </programlisting>
+ Note: RDS default parameters
+ <itemizedlist>
+ <listitem><para>Programme Identification code[PI]: Default value -> 0x1234</para></listitem>
+ <listitem><para>Programme Type[PTY]: Default value -> OTHER_MUSIC</para></listitem>
+ <listitem><para>Music/Speech switch[M/S]: Default value -> Music</para></listitem>
+ <listitem><para>Programme Service name[PS]: Default value -> FM Xmit</para></listitem>
+ <listitem><para>Radio text[RT]: Default value -> Default Radio Text</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="internal-test-tone-generator">
+ <title>Test Tone Generation</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>1. Enabling-Disabling test tone</term>
+ <listitem>
+ <para>
+ 1. For setting the test tone status, application should use IOCTL VIDIOC_S_CTRL with its parameter id set to V4L2_CID_CG2900_RADIO_TEST_TONE_GENERATOR_SET_STATUS and parameter value set to any of the following :
+ <itemizedlist>
+ <listitem><para>Turn off test tone - use V4L2_CG2900_RADIO_TEST_TONE_GEN_OFF</para></listitem>
+ <listitem><para>Turn on test tone - use V4L2_CG2900_RADIO_TEST_TONE_GEN_ON_WO_SRC</para></listitem>
+ <listitem><para>Turn on with sample rate correction - use V4L2_CG2900_RADIO_TEST_TONE_GEN_ON_W_SRC</para></listitem>
+ </itemizedlist>
+ <programlisting>
+ void SetTestToneStatus(int state)
+ {
+ struct v4l2_control sctrl;
+ int ret;
+ sctrl.id = V4L2_CID_CG2900_RADIO_TEST_TONE_GENERATOR_SET_STATUS;
+ sctrl.value = state;
+ ret = ioctl(fd, VIDIOC_S_CTRL, &amp; sctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_CTRL:error!!\n");
+ }
+ }
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>2. Test Tone Connect</term>
+ <listitem>
+ <para>
+ 2. The internal test tone can be used following modes: FMR (receiver mode) and FMT (transmitter mode). Each of the waves, or the sum of them, can be used as audio source for the receiver audio outputs, or for the transmitter audio input. This can be done by means of command "Test Tone Connect". For the receiver, all available audio outputs will be connected to the tone generator. After switching FM to another mode, the command "Test Tone Connect" must be executed again to set up a connection in the new mode.
+ </para>
+
+ <para>
+ The IOCTL VIDIOC_S_EXT_CTRLS is used to perform Test Tone Connect operation, with following parameters:
+ </para>
+ <itemizedlist>
+ <listitem><para>Parameter id set to V4L2_CID_CG2900_RADIO_TEST_TONE_CONNECT</para></listitem>
+ <listitem><para>Parameter size set to 2</para></listitem>
+ <listitem><para>Control class parameter set to V4L2_CTRL_CLASS_USER</para></listitem>
+ <listitem><para>Parameters value filled as below in code snippet(string field of the parameter) </para></listitem>
+ <listitem><para>First byte of Parameter value shall contain connect parameter for left audio output </para></listitem>
+ <listitem><para>Second byte of Parameter value shall contain connect parameter forright audio output </para></listitem>
+ </itemizedlist>
+
+ <para>
+ Value of either of Parameter values (for left or right audio outputs)can assume values:
+ </para>
+ <itemizedlist>
+ <listitem><para>V4L2_CG2900_RADIO_TEST_TONE_NORMAL_AUDIO - Normal Audio</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_TEST_TONE_ZERO - Zero</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_TEST_TONE_TONE_1 - Tone_1</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_TEST_TONE_TONE_2 - Tone_2</para></listitem>
+ <listitem><para>V4L2_CG2900_RADIO_TEST_TONE_TONE_SUM - Tone_Sum</para></listitem>
+ </itemizedlist>
+
+ <programlisting>
+ void TestToneConnect(u8 left_audio_mode, u8 right_audio_mode)
+ {
+ u8 *test_tone_connect_ptr = NULL;
+ ext_ctrl.ctrl_class = V4L2_CTRL_CLASS_USER;
+ ext_ctrl.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ ext_ctrl.count = 0;
+ ext_ctrl.controls-&gt;id = V4L2_CID_CG2900_RADIO_TEST_TONE_CONNECT;
+ ext_ctrl.controls-&gt;size = 2;
+ ext_ctrl.controls-&gt;string = (u8 *)malloc(sizeof(u8) * ext_ctrl.controls-&gt;size);
+ test_tone_connect_ptr = ext_ctrl.controls-&gt;string;
+ *(test_tone_connect_ptr) = left_audio_mode;
+ *(test_tone_connect_ptr + 1) = right_audio_mode;
+
+ ret = ioctl(fd, VIDIOC_S_EXT_CTRLS, &amp; ext_ctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_EXT_CTRL:error!!\n");
+ }
+ }
+ </programlisting>
+
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>3. Test Tone Set Parameters</term>
+ <listitem>
+ <para>
+ 3. The tone generator is capable of generating two different sine waves with adjustable offset, amplitude, phase offset and frequency. These properties can be changed with command "Test Tone Set Params"
+ </para>
+
+ <para>
+ The IOCTL VIDIOC_S_EXT_CTRLS is used to perform Test Tone Set Parameters operation, with following parameters:
+ </para>
+ <itemizedlist>
+ <listitem><para>Parameter id set to V4L2_CID_CG2900_RADIO_TEST_TONE_SET_PARAMS</para></listitem>
+ <listitem><para>Parameter size set to 6</para></listitem>
+ <listitem><para>Control class parameter set to V4L2_CTRL_CLASS_USER</para></listitem>
+ <listitem><para>Parameters value filled as below in code snippet(string field of the parameter) </para></listitem>
+ <listitem><para>First word of Parameter value shall contain tone_gen (0: tone_1, 1:tone_2)</para></listitem>
+ <listitem><para>Second word of Parameter value shall contain frequency ([0x0000..0x7FFF], (default = 0x064D))</para></listitem>
+ <listitem><para>Third word of Parameter value shall contain volume ([0x0000..0x7FFF], (default = 0x0CCD))</para></listitem>
+ <listitem><para>Fourth word of Parameter value shall contain phase offset([0x8000..0x7FFF], (default = 0x0000))</para></listitem>
+ <listitem><para>Fifth word of Parameter value shall contain DC to add to tone([0x8000..0x7FFF], (default = 0x0000))</para></listitem>
+ <listitem><para>Sixth word of Parameter value shall contain waveform type(0=sine shaped, 1=Pulse shaped)</para></listitem>
+ </itemizedlist>
+
+ <programlisting>
+ void Sample_TestToneSetParams()
+ {
+ u8 *test_tone_connect_ptr = NULL;
+ u8 tone_gen =0, waveform = 1; /* Tone_Gen = Tone_1, waveform type = pulse shaped */
+ u16 frequency = 0x064D, volume = 0x0CCD, phase_offset = 0x0000, dc=0x0000;
+
+ ext_ctrl.ctrl_class = V4L2_CTRL_CLASS_USER;
+ ext_ctrl.controls = (struct v4l2_ext_control *) malloc(sizeof(struct v4l2_ext_control));
+ ext_ctrl.count = 0;
+ ext_ctrl.controls-&gt;id = V4L2_CID_CG2900_RADIO_TEST_TONE_CONNECT;
+ ext_ctrl.controls-&gt;size = 6;
+ ext_ctrl.controls-&gt;string = (u16 *)malloc(sizeof(u16) * ext_ctrl.controls-&gt;size);
+ test_tone_connect_ptr = ext_ctrl.controls-&gt;string;
+ *(test_tone_connect_ptr) = tone_gen;
+ *(test_tone_connect_ptr + 1) = frequency;
+ *(test_tone_connect_ptr + 2) = volume;
+ *(test_tone_connect_ptr + 3) = phase_offset;
+ *(test_tone_connect_ptr + 4) = dc;
+ *(test_tone_connect_ptr + 5) = waveform;
+
+ ret = ioctl(fd, VIDIOC_S_EXT_CTRLS, &amp; ext_ctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_EXT_CTRL:error!!\n");
+ }
+ }
+ </programlisting>
+
+ </listitem>
+
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="de-emphasis-filter">
+ <title>Test Tone Generation</title>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>Set De-Emphasis Filter</term>
+ <listitem>
+ <para>
+ To apply de-emphasis filter to the FM received signal to compansate for
+ preemphasis that has been applied to the signal by the FM transmitter. IOCTL VIDIOC_S_CTRL is used in with parameter id set to V4L2_CID_CG2900_RADIO_TUNE_DEEMPHASIS and parameter value can take following values:
+ <itemizedlist>
+ <listitem><para>Disable de-emphasis - use V4L2_CG2900_RADIO_DEEMPHASIS_DISABLED</para></listitem>
+ <listitem><para>De-emphasis with 50 micro seconds - use V4L2_CG2900_RADIO_DEEMPHASIS_50_uS</para></listitem>
+ <listitem><para>De-emphasis filter with 75 micro seconds - use V4L2_CG2900_RADIO_DEEMPHASIS_75_uS</para></listitem>
+ </itemizedlist>
+
+ <programlisting>
+ void SetDeemphasisLevel(int deemphasis_level)
+ {
+ struct v4l2_control sctrl;
+ int ret;
+ sctrl.id = V4L2_CID_CG2900_RADIO_TUNE_DEEMPHASIS ;
+ sctrl.value = deemphasis_level;
+ ret = ioctl(fd, VIDIOC_S_CTRL, &amp; sctrl);
+ if (ret &lt; 0) {
+ printf("VIDIOC_S_CTRL:error!!\n");
+ }
+ }
+ </programlisting>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+
+ </chapter>
+ <chapter id="driver-configuration">
+ <title>Driver Configuration and Interaction</title>
+ <!-- Do NOT change the chapter id or title! -->
+ <para>
+ For debug purposes the variable cg2900_fm_debug_level in the file cg2900_fm_driver.c can be changed to set how much debug printouts
+ that shall be generated.
+ <itemizedlist>
+ <listitem><para>1 = Error logs</para></listitem>
+ <listitem><para>2 = Info logs, e.g. function entries</para></listitem>
+ <listitem><para>3 = Debug logs</para></listitem>
+ <listitem><para>4 = HCI logs, i.e. contents of the transferred data</para></listitem>
+ </itemizedlist>
+ </para>
+ <section id="driver-implemented-operations">
+ <title>Implemented operations in driver</title>
+ <para>
+ </para>
+ <para>
+ <table>
+ <title> Supported device driver operations when using character device </title>
+ <tgroup cols="2"><tbody>
+ <row><entry> open </entry> <entry> Opening a character device will Initialize the FM Chip and download the firmware files.</entry> </row>
+ <row><entry> close </entry> <entry> Closes a character device will deinitialize the FM Chip.</entry> </row>
+ <row><entry> poll </entry> <entry> Polling a character device will check if there is requested data is available or not.</entry> </row>
+ <row><entry> read </entry> <entry> Reading from a character device reads RDS data from the Chip</entry> </row>
+ </tbody></tgroup>
+ </table>
+ </para>
+ </section>
+ <section id="driver-loading">
+ <title>Driver loading parameters</title>
+ <para>
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term>radio_nr</term>
+ <listitem>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>Parameter type</term>
+ <listitem><synopsis><type>int</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Default value</term>
+ <listitem><para>0</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Runtime readable/modifiable</term>
+ <listitem><para>Readable</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The parameter radio_nr in radio-cg2900.c can be set to register a particular minor number with Video4Linux. Currently this parameter is set to 0 by default, signifying that the "\dev\radio0" is the character device assigned to CG2900 FM Driver in Video4Linux.
+ If the Platform has more than 1 radio drivers, the radio_nr parameter should be changed in file radio-cg2900.c.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Checking the Radio Number</term>
+ <listitem>
+ <para>
+ cat sys/module/radio_cg2900/parameters/radio_nr
+ </para>
+ <para>
+ The above command gets the radio number registered with
+ Video4Linux. This is used for opening the FM Radio
+ character device from user space.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>grid</term>
+ <listitem>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>Parameter type</term>
+ <listitem><synopsis><type>int</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Default value</term>
+ <listitem><para>1</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Runtime readable/modifiable</term>
+ <listitem><para>Readable</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The parameter grid in radio-cg2900.c defines the spacing to be used in Khz while switching on FM Radio.
+ <itemizedlist>
+ <listitem><para>0: 50 kHz (China)</para></listitem>
+ <listitem><para>1: 100 kHz (Europe, Japan)</para></listitem>
+ <listitem><para>2: 200 kHz (USA)</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Changing the Grid</term>
+ <listitem>
+ <para>
+ echo 1 &gt; /sys/module/radio_cg2900/parameters/grid.
+ </para>
+ <para>
+ The above command sets the radio band spacing between
+ two adjacent radio channels, in this case sets to 100KHz
+ suitable for Europe. The change is applicable before
+ switching on FM Radio, otherwise the change takes effect
+ from next FM switch on.
+ </para>
+ <para>
+ Note: The Grid parameter cannot be changed during FM radio is operational.
+ </para>
+ <para>
+ The user must change the grid value and restart the FM radio when moving into a different radio region.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Checking the current Grid Value</term>
+ <listitem>
+ <para>
+ cat sys/module/radio_cg2900/parameters/grid.
+ </para>
+ <para>
+ The above command gets the radio band spacing
+ between two adjacent radio channels currently set.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>band</term>
+ <listitem>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>Parameter type</term>
+ <listitem><synopsis><type>int</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Default value</term>
+ <listitem><para>0</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Runtime readable/modifiable</term>
+ <listitem><para>Readable</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The parameter band in radio-cg2900.c defines the band to be used while switching on FM Radio.
+ <itemizedlist>
+ <listitem><para>0: 87.5 - 108 MHz (USA, Europe)</para></listitem>
+ <listitem><para>1: 76 - 90 MHz (Japan)</para></listitem>
+ <listitem><para>2: 70 - 108 MHz (China wide band)</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Changing the Band</term>
+ <listitem>
+ <para>
+ echo 0 &gt; /sys/module/radio_cg2900/parameters/band.
+ </para>
+ <para>
+ The above command sets the FM band to be used.
+ In this case, it sets the FM band 87.5 - 100 MHz.
+ The change is applicable before switching on FM Radio,
+ otherwise the change takes effect from next FM switch on.
+ </para>
+ <para>
+ Note: The band parameter cannot be changed during FM radio is operational.
+ </para>
+ <para>
+ The user must change the band value and restart the FM radio when moving into a different radio region.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Checking the current Band Value</term>
+ <listitem>
+ <para>
+ cat sys/module/radio_cg2900/parameters/band.
+ </para>
+ <para>
+ The above command gets the current radio band set.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>cg2900_fm_debug_level</term>
+ <listitem>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>Parameter type</term>
+ <listitem><synopsis><type>int</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Default value</term>
+ <listitem><para>1</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Runtime readable/modifiable</term>
+ <listitem><para>Readable</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The parameter CG2900_fm_debug_level in platformosapi.c defines the debug level that is currently used.
+ The higher the debug level the more print-outs are received in the terminal window.
+ The following values are supported:
+ <itemizedlist>
+ <listitem><para>1 = Error logs</para></listitem>
+ <listitem><para>2 = Info logs, e.g. function entries</para></listitem>
+ <listitem><para>3 = Debug logs</para></listitem>
+ <listitem><para>4 = HCI logs, i.e. contents of the transferred data</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Changing the Log Level</term>
+ <listitem>
+ <para>
+ echo 3 &gt; /sys/module/radio_cg2900/parameters/cg2900_fm_debug_level.
+ </para>
+ <para>
+ The above command sets the Logging level of FM Driver.
+ In this case, it set will print all the debug messages
+ except the HCI commands exchanged with FM Chip.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Checking the current Log Level</term>
+ <listitem>
+ <para>
+ cat sys/module/radio_cg2900/parameters/cg2900_fm_debug_level.
+ </para>
+ <para>
+ The above command gets the current debug log level set.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ <para>
+ </para>
+ </section>
+ <section id="driver-ioctl">
+ <title>Driver IO Control</title>
+ <para>
+ Describes the FM driver IO control parameters
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><constant>VIDIOC_QUERYCAP</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Get</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>v4l2_capability</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>VIDIOC_QUERYCAP</constant> IOCTL is used to query the capabilities supported by FM Driver. IF the FM Driver supports FM Rx it should set the capabilities field bit should be bitwise OR'd with V4L2_CAP_TUNER, otherwise if it supports FM Tx, the capabilities field bit should be bitwise OR'd with V4L2_CAP_MODULATOR.
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If IOCTL is able to retrive the Capabilities successfully without errors the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate error.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><constant>VIDIOC_G_TUNER</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Get</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>v4l2_tuner</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>VIDIOC_G_TUNER</constant> IOCTL gets the FM Radio Tuner properties supported by FM Radio. It is also used to retrieve RDS status, mono/stereo status and Signal strength of the tuned channel. These values are valid when FM is configured using IOCTL VIDIOC_S_TUNER, i.e in FM Rx mode.
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If IOCTL is able to retrive the tuner properties successfully without errors the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate error.</para></listitem>
+ </itemizedlist>
+ Note: Currently the retrieved signal strength is in decimals and not in "dBuV", proper external conversion required.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><constant>VIDIOC_S_TUNER</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Set</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>v4l2_tuner</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>VIDIOC_S_TUNER</constant> IOCTL configures the FM radio in Rx mode. Only 1 FM Tuner is supported by FM Driver.
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If IOCTL is able to set the tuner properties successfully without errors the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate error.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><constant>VIDIOC_G_MODULATOR</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Get</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>v4l2_tuner</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>VIDIOC_G_MODULATOR</constant> IOCTL gets the FM Radio Modulator properties supported by FM Radio. It is also used to retrieve RDS status and mono/stereo status. These values are valid when FM is configured using IOCTL VIDIOC_S_MODULATOR, i.e in FM Tx mode.
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If IOCTL is able to retrive the tuner properties successfully without errors the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate error.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><constant>VIDIOC_S_MODULATOR</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Set</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>v4l2_modulator</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>VIDIOC_S_MODULATOR</constant> IOCTL configures the FM radio in Tx mode. Only 1 FM Modulator is supported by FM Driver.
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If IOCTL is able to set the modulator properties successfully without errors the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate error.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><constant>VIDIOC_S_FREQUENCY</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Set</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>v4l2_frequency</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>VIDIOC_S_FREQUENCY</constant> IOCTL sets the frequency on FM radio in Rx or Tx mode. The frequency parameter passed is in V4L2 format.
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If IOCTL is able to set the frequency successfully without errors the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate error.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><constant>VIDIOC_G_FREQUENCY</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Set</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>v4l2_modulator</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>VIDIOC_G_FREQUENCY</constant> IOCTL retrives the currently set frequency on FM Radio in Rx or Tx mode.
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If IOCTL is able to get the frequency successfully without errors the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate error.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><constant>VIDIOC_S_HW_FREQ_SEEK</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Set</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>v4l2_hw_freq_seek</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>VIDIOC_S_HW_FREQ_SEEK</constant> IOCTL starts the seek operation when FM Radio is configured in Rx mode. The direction parameter indicates the direction of seeking from the current station. At present the FM Driver ignores the wrap_Around parameter and unconditional wrap around is supported. If the operation is started successfully, the application should use poll() to identify when the seek is over.
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If IOCTL is able to start the seek successfully without errors the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate error.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><constant>VIDIOC_G_CTRL</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Get</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>v4l2_control</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>VIDIOC_G_CTRL</constant> IOCTL to retrive value of a paticular control. The following controls are supported by FM Driver:
+ <itemizedlist>
+ <listitem><para>
+ V4L2_CID_AUDIO_VOLUME
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_AUDIO_MUTE
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_AUDIO_BALANCE
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_CG2900_RADIO_RSSI_THRESHOLD
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_CG2900_RADIO_SELECT_ANTENNA
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_GET_RESULT
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_GET_RESULT
+ </para></listitem>
+ </itemizedlist>
+ Generic returned values are:
+ <itemizedlist>
+ <listitem><para>If IOCTL is able to retrive the value of the control successfully without errors the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate error.</para></listitem>
+ </itemizedlist>
+ Note: For V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_GET_RESULT returned values are:
+ <itemizedlist>
+ <listitem><para> -1 AF Switch failed, the AF-RSSI was too low.</para></listitem>
+ <listitem><para> -2 AF Switch failed, the AF-PI Doesn't correlate.</para></listitem>
+ <listitem><para> -3 AF Switch failed, the AF-RDS SYNC Lost.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><constant>VIDIOC_S_CTRL</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Set</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>v4l2_control</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>VIDIOC_S_CTRL</constant> IOCTL to set value of a paticular control. The following controls are supported by FM Driver:
+ <itemizedlist>
+ <listitem><para>
+ V4L2_CID_CG2900_RADIO_CHIP_STATE
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_CG2900_RADIO_BANDSCAN
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_CG2900_RADIO_BLOCKSCAN_START
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_CG2900_RADIO_SELECT_ANTENNA
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_CG2900_RADIO_RSSI_THRESHOLD
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_START
+ </para></listitem>
+ </itemizedlist>
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If IOCTL is able to set the value of the control successfully without errors the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate error.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><constant>VIDIOC_G_EXT_CTRLS</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Get</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>v4l2_ext_controls</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>VIDIOC_G_EXT_CTRLS</constant> IOCTL to retrive value of a paticular control. This is used when a control class is defined or when the value to be retrieved is more than 1 parameter(s). Only V4L2_CTRL_CLASS_FM_TX class is supported for this IOCTL in FM Driver. The following controls are supported by FM Driver:
+ <itemizedlist>
+ <listitem><para>
+ V4L2_CID_RDS_TX_DEVIATION
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_PILOT_TONE_ENABLED
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_PILOT_TONE_DEVIATION
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_TUNE_PREEMPHASIS
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_TUNE_POWER_LEVEL
+ </para></listitem>
+ </itemizedlist>
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If IOCTL is able to retrive the value(s) of the control successfully without errors the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate error.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><constant>VIDIOC_S_EXT_CTRLS</constant></term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>Direction</term>
+ <listitem><para>Set</para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Parameter</term>
+ <listitem><synopsis><type>v4l2_ext_controls</type></synopsis></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>
+ The <constant>VIDIOC_S_CTRL</constant> IOCTL to set value of a paticular control when the parameters to be set are more than 1 parameter or when a control class is defined. At present only the V4L2_CTRL_CLASS_FM_TX and V4L2_CTRL_CLASS_USER control classes are supported by FM Driver. The following controls are supported by FM Driver:
+ <itemizedlist>
+ <listitem><para>
+ V4L2_CID_RDS_TX_DEVIATION
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_RDS_TX_PI
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_RDS_TX_PTY
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_RDS_TX_PS_NAME
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_RDS_TX_RADIO_TEXT
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_PILOT_TONE_ENABLED
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_PILOT_TONE_DEVIATION
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_TUNE_PREEMPHASIS
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_TUNE_POWER_LEVEL
+ </para></listitem>
+ <listitem><para>
+ V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START
+ </para></listitem>
+ </itemizedlist>
+ Returned values are:
+ <itemizedlist>
+ <listitem><para>If IOCTL is able to set the value of the control successfully without errors the IOCTL function will return 0.</para></listitem>
+ <listitem><para>A negative value will indicate error.</para></listitem>
+ </itemizedlist>
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ <section id="driver-sysfs">
+ <title>Driver Interaction with Sysfs</title>
+ <para>
+ Not Applicable
+ </para>
+ </section>
+ <section id="driver-proc">
+ <title>Driver Interaction using /proc filesystem</title>
+ <para>
+ Not Applicable
+ </para>
+ </section>
+ <section id="driver-other">
+ <title>Other means for Driver Interaction</title>
+ <para>
+ Not Applicable
+ </para>
+ </section>
+ <section id="driver-node">
+ <title>Driver Node File</title>
+ <variablelist>
+ <varlistentry>
+ <term>FM Radio Device</term>
+ <listitem>
+ <variablelist>
+ <varlistentry>
+ <term>File</term>
+ <listitem><para><filename>/dev/radio0</filename></para></listitem>
+ </varlistentry>
+ <varlistentry>
+ <term>Description</term>
+ <listitem>
+ <para>The radio device for FM Radio.</para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </section>
+ </chapter>
+ <chapter id="bugs">
+ <title>Known Bugs And Limitations</title>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term>No known issues.</term>
+ <listitem>
+ <para>
+ <!-- Do NOT change the chapter id or title! -->
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </para>
+ </chapter>
+ <chapter id="internal-functions">
+ <title>Internal Functions Provided</title>
+ <para>
+ List of internal functions used in FM Driver.
+ </para>
+ <!-- Do NOT change the chapter id or title! -->
+ <section id="radio-cg2900.c">
+ <title>radio-cg2900.c</title>
+!Idrivers/media/radio/CG2900/radio-cg2900.c
+ </section>
+ <section id="cg2900_fm_api.h">
+ <title>cg2900_fm_api.h</title>
+!Idrivers/media/radio/CG2900/cg2900_fm_api.h
+ </section>
+ <section id="cg2900_fm_api.c">
+ <title>cg2900_fm_api.c</title>
+!Idrivers/media/radio/CG2900/cg2900_fm_api.c
+ </section>
+ <section id="cg2900_fm_driver.h">
+ <title>cg2900_fm_driver.h</title>
+!Idrivers/media/radio/CG2900/cg2900_fm_driver.h
+ </section>
+ <section id="cg2900_fm_driver.c">
+ <title>cg2900_fm_driver.c</title>
+!Idrivers/media/radio/CG2900/cg2900_fm_driver.c
+ </section>
+ </chapter>
+</book>
diff --git a/drivers/media/radio/CG2900/Makefile b/drivers/media/radio/CG2900/Makefile
new file mode 100755
index 00000000000..60b12dd9c35
--- /dev/null
+++ b/drivers/media/radio/CG2900/Makefile
@@ -0,0 +1,12 @@
+#
+# Makefile for the CG2900 FM Radio Driver
+#
+
+radio_cg2900-objs := radio-cg2900.o cg2900_fm_api.o cg2900_fm_driver.o
+
+obj-$(CONFIG_RADIO_CG2900) += radio_cg2900.o
+
+ccflags-y := \
+ -Idrivers/staging/cg2900/include \
+
+
diff --git a/drivers/media/radio/CG2900/cg2900_fm_api.c b/drivers/media/radio/CG2900/cg2900_fm_api.c
new file mode 100644
index 00000000000..f73f3ef39ee
--- /dev/null
+++ b/drivers/media/radio/CG2900/cg2900_fm_api.c
@@ -0,0 +1,3389 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Linux FM Host API's for ST-Ericsson FM Chip.
+ *
+ * Author: Hemant Gupta <hemant.gupta@stericsson.com> for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/firmware.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/version.h>
+#include <linux/kthread.h>
+#include <linux/kernel.h>
+#include <linux/mutex.h>
+#include "cg2900_fm_driver.h"
+
+#define CG2910_FM_LUT_INFO_FILE "FM_FW_CG2910_1_0_P1_4_lut_info.fw"
+#define CG2910_FM_PROG_INFO_FILE "FM_FW_CG2910_1_0_P1_4_prog_info.fw"
+#define CG2910_LUT_IDX 0
+#define CG2910_PROG_IDX 1
+#define CG2910_MAX_FILES_DL 2
+
+#define CG2900_FM_BT_SRC_COEFF_INFO_FILE "cg2900_fm_bt_src_coeff_info.fw"
+#define CG2900_FM_EXT_SRC_COEFF_INFO_FILE "cg2900_fm_ext_src_coeff_info.fw"
+#define CG2900_FM_FM_COEFF_INFO_FILE "cg2900_fm_fm_coeff_info.fw"
+#define CG2900_FM_FM_PROG_INFO_FILE "cg2900_fm_fm_prog_info.fw"
+#define CG2900_FM_LINE_BUFFER_LENGTH 128
+#define CG2900_FM_FILENAME_MAX 128
+#define FW_FILE_PARAM_LEN 3
+/* RDS Tx PTY set to Other music */
+#define OTHER_MUSIC 15
+#define DEFAULT_AUDIO_DEVIATION 0x1AA9
+#define DEFAULT_NOTIFICATION_HOLD_OFF_TIME 0x000A
+
+/* Specific chip version data */
+#define CG2900_PG1_REV 0x0101
+#define CG2900_PG2_REV 0x0200
+#define CG2900_PG1_SPECIAL_REV 0x0700
+#define CG2905_PG1_1_REV 0x1805
+#define CG2910_PG1_REV 0x1004
+#define CG2910_PG2_REV 0x1008
+
+static bool fm_rds_status;
+static bool fm_prev_rds_status;
+static u16 program_identification_code;
+static u16 default_program_identification_code = 0x1234;
+static u16 program_type_code;
+static u16 default_program_type_code = OTHER_MUSIC;
+static char program_service[MAX_PSN_SIZE];
+static char default_program_service[MAX_PSN_SIZE] = "FM-Xmit ";
+static char radio_text[MAX_RT_SIZE];
+static char default_radio_text[MAX_RT_SIZE] = "Default Radio Text "
+ "Default Radio Text Default Radio Text Default";
+static bool a_b_flag;
+u8 fm_event;
+static struct mutex rds_mutex;
+struct cg2900_fm_rds_buf fm_rds_buf[MAX_RDS_BUFFER][MAX_RDS_GROUPS];
+struct cg2900_fm_rds_info fm_rds_info;
+static enum cg2900_fm_state fm_state;
+static enum cg2900_fm_mode fm_mode;
+static struct cg2900_version_info version_info;
+
+/**
+ * cg2900_fm_get_one_line_of_text()- Get One line of text from a file.
+ *
+ * Replacement function for stdio function fgets.This function extracts one
+ * line of text from input file.
+ *
+ * @wr_buffer: Buffer to copy text to.
+ * @max_nbr_of_bytes: Max number of bytes to read, i.e. size of rd_buffer.
+ * @rd_buffer: Data to parse.
+ * @bytes_copied: Number of bytes copied to wr_buffer.
+ *
+ * Returns:
+ * Pointer to next data to read.
+ */
+static char *cg2900_fm_get_one_line_of_text(
+ char *wr_buffer,
+ int max_nbr_of_bytes,
+ char *rd_buffer,
+ int *bytes_copied
+ )
+{
+ char *curr_wr = wr_buffer;
+ char *curr_rd = rd_buffer;
+ char in_byte;
+
+ *bytes_copied = 0;
+
+ do {
+ *curr_wr = *curr_rd;
+ in_byte = *curr_wr;
+ curr_wr++;
+ curr_rd++;
+ (*bytes_copied)++;
+ } while ((*bytes_copied <= max_nbr_of_bytes) && (in_byte != '\0')
+ && (in_byte != '\n'));
+ *curr_wr = '\0';
+ return curr_rd;
+}
+
+/**
+ * cg2900_fm_get_file_to_load() - Parse info file and find correct target file.
+ *
+ * @fw: Firmware structure containing file data.
+ * @file_name: (out) Pointer to name of requested file.
+ *
+ * Returns:
+ * True, if target file was found,
+ * False, otherwise.
+ */
+static bool cg2900_fm_get_file_to_load(
+ const struct firmware *fw,
+ char **file_name
+ )
+{
+ char *line_buffer;
+ char *curr_file_buffer;
+ int bytes_left_to_parse = fw->size;
+ int bytes_read = 0;
+ bool file_found = false;
+
+ curr_file_buffer = (char *)&(fw->data[0]);
+
+ line_buffer = kmalloc(CG2900_FM_LINE_BUFFER_LENGTH,
+ GFP_KERNEL);
+
+ if (line_buffer == NULL) {
+ FM_ERR_REPORT("Failed to allocate:"
+ "file_name 0x%X, line_buffer 0x%X",
+ (unsigned int)file_name,
+ (unsigned int)line_buffer);
+ goto error;
+ }
+
+ while (!file_found) {
+ /* Get one line of text from the file to parse */
+ curr_file_buffer =
+ cg2900_fm_get_one_line_of_text(line_buffer,
+ min
+ (CG2900_FM_LINE_BUFFER_LENGTH,
+ (int)(fw->size -
+ bytes_read)),
+ curr_file_buffer,
+ &bytes_read);
+
+ bytes_left_to_parse -= bytes_read;
+ if (bytes_left_to_parse <= 0) {
+ /* End of file => Leave while loop */
+ FM_ERR_REPORT("Reached end of file."
+ "No file found!");
+ break;
+ }
+
+ /*
+ * Check if the line of text is a comment
+ * or not, comments begin with '#'
+ */
+ if (*line_buffer != '#') {
+ u32 hci_rev = 0;
+ u32 lmp_sub = 0;
+
+ FM_DEBUG_REPORT("Found a valid line <%s>",
+ line_buffer);
+
+ /*
+ * Check if we can find the correct
+ * HCI revision and LMP subversion
+ * as well as a file name in the text line
+ * Store the filename if the actual file can
+ * be found in the file system
+ */
+ if (sscanf(line_buffer, "%x%x%s",
+ (unsigned int *)&hci_rev,
+ (unsigned int *)&lmp_sub,
+ *file_name) == FW_FILE_PARAM_LEN
+ && hci_rev == version_info.revision
+ && lmp_sub == version_info.sub_version) {
+ FM_INFO_REPORT("File name = %s "
+ "HCI Revision"
+ "= 0x%04X LMP "
+ "Subversion = 0x%04X",
+ *file_name,
+ (unsigned int)hci_rev,
+ (unsigned int)lmp_sub);
+
+ /*
+ * Name has already been stored above.
+ * Nothing more to do
+ */
+ file_found = true;
+ } else {
+ /*Zero the name buffer so it is clear to next read*/
+ memset(*file_name, 0x00,
+ CG2900_FM_FILENAME_MAX);
+ }
+ }
+ }
+ kfree(line_buffer);
+error:
+ return file_found;
+}
+
+
+/**
+ * cg2910_fm_load_firmware() - Loads the FM lut and
+ * Program firmware files for CG2910
+ *
+ * @device: Pointer to char device requesting the operation.
+ *
+ * Returns:
+ * 0, if firmware download is successful
+ * -ENOENT, file not found.
+ * -ENOMEM, out of memory
+ */
+static int cg2910_fm_load_firmware(
+ struct device *device
+ )
+{
+ int err;
+ bool file_found;
+ int result = 0;
+ const struct firmware *fm_fw_info[2];
+ const struct firmware *fm_firmware[2];
+ char *fm_fw_file_name = NULL;
+ int loopi = 0;
+
+ FM_INFO_REPORT("+cg2910_fm_load_firmware");
+ fm_fw_info[CG2910_LUT_IDX] = NULL;
+ fm_fw_info[CG2910_PROG_IDX] = NULL;
+ fm_firmware[CG2910_LUT_IDX] = NULL;
+ fm_firmware[CG2910_PROG_IDX] = NULL;
+
+ /* Open fm_fw_info lut file. */
+ err = request_firmware(&fm_fw_info[CG2910_LUT_IDX],
+ CG2910_FM_LUT_INFO_FILE, device);
+ if (err) {
+ FM_ERR_REPORT("cg2910_fm_load_firmware: "
+ "Couldn't get fm_fw_info lut file");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Open fm_fw_info prog file. */
+ err = request_firmware(&fm_fw_info[CG2910_PROG_IDX],
+ CG2910_FM_PROG_INFO_FILE, device);
+ if (err) {
+ FM_ERR_REPORT("cg2910_fm_load_firmware: "
+ "Couldn't get fm_fw_info prog file");
+ result = -ENOENT;
+ goto error;
+ }
+
+ fm_fw_file_name = kmalloc(CG2900_FM_FILENAME_MAX,
+ GFP_KERNEL);
+ if (fm_fw_file_name == NULL) {
+ FM_ERR_REPORT("cg2910_fm_load_firmware: "
+ "Couldn't allocate memory for "
+ "fm_fw_file_name");
+ result = -ENOMEM;
+ goto error;
+ }
+
+ /* Put a loop for downloading lut and prog */
+ for (loopi = 0; loopi < CG2910_MAX_FILES_DL; loopi++) {
+ /*
+ * Now we have the fm_fw_info file. See if we can
+ * find the right fm_fw_file_name file as well
+ */
+ file_found = cg2900_fm_get_file_to_load(fm_fw_info[loopi],
+ &fm_fw_file_name);
+
+ if (!file_found) {
+ FM_ERR_REPORT("cg2910_fm_load_firmware: "
+ "Couldn't find fm_fw_file_name file!! "
+ "Major error!!!");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /*
+ * OK. Now it is time to download the firmware
+ * First download lut file & then prog
+ */
+ err = request_firmware(&fm_firmware[loopi],
+ fm_fw_file_name, device);
+ if (err < 0) {
+ FM_ERR_REPORT("cg2910_fm_load_firmware: "
+ "Couldn't get fm_firmware"
+ " file, err = %d", err);
+ result = -ENOENT;
+ goto error;
+ }
+
+ FM_INFO_REPORT("cg2910_fm_load_firmware: "
+ "Downloading %s of %d bytes",
+ fm_fw_file_name, fm_firmware[loopi]->size);
+ if (fmd_send_fm_firmware((u8 *) fm_firmware[loopi]->data,
+ fm_firmware[loopi]->size)) {
+ FM_ERR_REPORT("cg2910_fm_load_firmware: Error in "
+ "downloading %s", fm_fw_file_name);
+ result = -ENOENT;
+ goto error;
+ }
+ }
+
+error:
+ /* Release fm_fw_info lut and prog file */
+ if (fm_fw_info[CG2910_LUT_IDX])
+ release_firmware(fm_fw_info[CG2910_LUT_IDX]);
+ if (fm_fw_info[CG2910_PROG_IDX])
+ release_firmware(fm_fw_info[CG2910_PROG_IDX]);
+
+ if (fm_firmware[CG2910_LUT_IDX])
+ release_firmware(fm_firmware[CG2910_LUT_IDX]);
+ if (fm_firmware[CG2910_PROG_IDX])
+ release_firmware(fm_firmware[CG2910_PROG_IDX]);
+
+ /* Free Allocated memory */
+ kfree(fm_fw_file_name);
+ FM_DEBUG_REPORT("-cg2910_fm_load_firmware: returning %d",
+ result);
+ return result;
+}
+
+/**
+ * cg2900_fm_load_firmware() - Loads the FM Coeffecients and F/W file(s)
+ * for CG2900
+ * @device: Pointer to char device requesting the operation.
+ *
+ * Returns:
+ * 0, if firmware download is successful
+ * -ENOENT, file not found.
+ * -ENOMEM, out of memory
+ */
+static int cg2900_fm_load_firmware(
+ struct device *device
+ )
+{
+ int err;
+ bool file_found;
+ int result = 0;
+ const struct firmware *bt_src_coeff_info;
+ const struct firmware *ext_src_coeff_info;
+ const struct firmware *fm_coeff_info;
+ const struct firmware *fm_prog_info;
+ char *bt_src_coeff_file_name = NULL;
+ char *ext_src_coeff_file_name = NULL;
+ char *fm_coeff_file_name = NULL;
+ char *fm_prog_file_name = NULL;
+
+ FM_INFO_REPORT("+cg2900_fm_load_firmware");
+
+ /* Open bt_src_coeff info file. */
+ err = request_firmware(&bt_src_coeff_info,
+ CG2900_FM_BT_SRC_COEFF_INFO_FILE, device);
+ if (err) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get bt_src_coeff info file");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /*
+ * Now we have the bt_src_coeff info file.
+ * See if we can find the right bt_src_coeff file as well
+ */
+ bt_src_coeff_file_name = kmalloc(CG2900_FM_FILENAME_MAX,
+ GFP_KERNEL);
+ if (bt_src_coeff_file_name == NULL) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't allocate memory for "
+ "bt_src_coeff_file_name");
+ release_firmware(bt_src_coeff_info);
+ result = -ENOMEM;
+ goto error;
+ }
+ file_found = cg2900_fm_get_file_to_load(bt_src_coeff_info,
+ &bt_src_coeff_file_name);
+
+ /* Now we are finished with the bt_src_coeff info file */
+ release_firmware(bt_src_coeff_info);
+
+ if (!file_found) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't find bt_src_coeff file!! "
+ "Major error!!!");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Open ext_src_coeff info file. */
+ err = request_firmware(&ext_src_coeff_info,
+ CG2900_FM_EXT_SRC_COEFF_INFO_FILE, device);
+ if (err) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get ext_src_coeff_info info file");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /*
+ * Now we have the ext_src_coeff info file. See if we can
+ * find the right ext_src_coeff file as well
+ */
+ ext_src_coeff_file_name = kmalloc(CG2900_FM_FILENAME_MAX,
+ GFP_KERNEL);
+ if (ext_src_coeff_file_name == NULL) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't allocate memory for "
+ "ext_src_coeff_file_name");
+ release_firmware(ext_src_coeff_info);
+ result = -ENOMEM;
+ goto error;
+ }
+ file_found = cg2900_fm_get_file_to_load(ext_src_coeff_info,
+ &ext_src_coeff_file_name);
+
+ /* Now we are finished with the ext_src_coeff info file */
+ release_firmware(ext_src_coeff_info);
+
+ if (!file_found) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't find ext_src_coeff_info "
+ "file!!! Major error!");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Open fm_coeff info file. */
+ err = request_firmware(&fm_coeff_info,
+ CG2900_FM_FM_COEFF_INFO_FILE, device);
+ if (err) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get fm_coeff info file");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /*
+ * Now we have the fm_coeff_info info file.
+ * See if we can find the right fm_coeff_info file as well
+ */
+ fm_coeff_file_name = kmalloc(CG2900_FM_FILENAME_MAX,
+ GFP_KERNEL);
+ if (fm_coeff_file_name == NULL) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't allocate memory for "
+ "fm_coeff_file_name");
+ release_firmware(fm_coeff_info);
+ result = -ENOMEM;
+ goto error;
+ }
+ file_found = cg2900_fm_get_file_to_load(fm_coeff_info,
+ &fm_coeff_file_name);
+
+ /* Now we are finished with the fm_coeff info file */
+ release_firmware(fm_coeff_info);
+
+ if (!file_found) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't find fm_coeff file!!! "
+ "Major error!");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Open fm_prog info file. */
+ err = request_firmware(&fm_prog_info,
+ CG2900_FM_FM_PROG_INFO_FILE, device);
+ if (err) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get fm_prog_info info file");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /*
+ * Now we have the fm_prog info file.
+ * See if we can find the right fm_prog file as well
+ */
+ fm_prog_file_name = kmalloc(CG2900_FM_FILENAME_MAX,
+ GFP_KERNEL);
+ if (fm_prog_file_name == NULL) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't allocate memory for "
+ "fm_prog_file_name");
+ release_firmware(fm_prog_info);
+ result = -ENOMEM;
+ goto error;
+ }
+ file_found = cg2900_fm_get_file_to_load(fm_prog_info,
+ &fm_prog_file_name);
+
+ /* Now we are finished with fm_prog patch info file */
+ release_firmware(fm_prog_info);
+
+ if (!file_found) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't find fm_prog_info file!!! "
+ "Major error!");
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* OK. Now it is time to download the firmware */
+ err = request_firmware(&bt_src_coeff_info,
+ bt_src_coeff_file_name, device);
+ if (err < 0) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get bt_src_coeff file, err = %d", err);
+ result = -ENOENT;
+ goto error;
+ }
+
+ FM_INFO_REPORT("cg2900_fm_load_firmware: Downloading %s of %d bytes",
+ bt_src_coeff_file_name, bt_src_coeff_info->size);
+ if (fmd_send_fm_firmware((u8 *) bt_src_coeff_info->data,
+ bt_src_coeff_info->size)) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: Error in "
+ "downloading %s", bt_src_coeff_file_name);
+ release_firmware(bt_src_coeff_info);
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Now we are finished with the bt_src_coeff info file */
+ release_firmware(bt_src_coeff_info);
+ err = request_firmware(&ext_src_coeff_info,
+ ext_src_coeff_file_name, device);
+ if (err < 0) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get ext_src_coeff file, err = %d", err);
+ result = -ENOENT;
+ goto error;
+ }
+
+ FM_INFO_REPORT("cg2900_fm_load_firmware: Downloading %s of %d bytes",
+ ext_src_coeff_file_name, ext_src_coeff_info->size);
+ if (fmd_send_fm_firmware((u8 *) ext_src_coeff_info->data,
+ ext_src_coeff_info->size)) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: Error in "
+ "downloading %s", ext_src_coeff_file_name);
+ release_firmware(ext_src_coeff_info);
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Now we are finished with the bt_src_coeff info file */
+ release_firmware(ext_src_coeff_info);
+
+ err = request_firmware(&fm_coeff_info, fm_coeff_file_name, device);
+ if (err < 0) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get fm_coeff file, err = %d", err);
+ result = -ENOENT;
+ goto error;
+ }
+
+ FM_INFO_REPORT("cg2900_fm_load_firmware: Downloading %s of %d bytes",
+ fm_coeff_file_name, fm_coeff_info->size);
+ if (fmd_send_fm_firmware((u8 *) fm_coeff_info->data,
+ fm_coeff_info->size)) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: Error in "
+ "downloading %s", fm_coeff_file_name);
+ release_firmware(fm_coeff_info);
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Now we are finished with the bt_src_coeff info file */
+ release_firmware(fm_coeff_info);
+
+ err = request_firmware(&fm_prog_info, fm_prog_file_name, device);
+ if (err < 0) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: "
+ "Couldn't get fm_prog file, err = %d", err);
+ result = -ENOENT;
+ goto error;
+ }
+
+ FM_INFO_REPORT("cg2900_fm_load_firmware: Downloading %s of %d bytes",
+ fm_prog_file_name, fm_prog_info->size);
+ if (fmd_send_fm_firmware((u8 *) fm_prog_info->data,
+ fm_prog_info->size)) {
+ FM_ERR_REPORT("cg2900_fm_load_firmware: Error in "
+ "downloading %s", fm_prog_file_name);
+ release_firmware(fm_prog_info);
+ result = -ENOENT;
+ goto error;
+ }
+
+ /* Now we are finished with the bt_src_coeff info file */
+ release_firmware(fm_prog_info);
+
+error:
+ /* Free Allocated memory */
+ if (bt_src_coeff_file_name != NULL)
+ kfree(bt_src_coeff_file_name);
+ if (ext_src_coeff_file_name != NULL)
+ kfree(ext_src_coeff_file_name);
+ if (fm_coeff_file_name != NULL)
+ kfree(fm_coeff_file_name);
+ if (fm_prog_file_name != NULL)
+ kfree(fm_prog_file_name);
+ FM_DEBUG_REPORT("-cg2900_fm_load_firmware: returning %d",
+ result);
+ return result;
+}
+
+/**
+ * cg2900_fm_transmit_rds_groups()- Transmits the RDS Groups.
+ *
+ * Stores the RDS Groups in Chip's buffer and each group is
+ * transmitted every 87.6 ms.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise
+ */
+static int cg2900_fm_transmit_rds_groups(void)
+{
+ int result = 0;
+ u16 group_position = 0;
+ u8 block1[2];
+ u8 block2[2];
+ u8 block3[2];
+ u8 block4[2];
+ int index1 = 0;
+ int index2 = 0;
+ int group_0B_count = 0;
+ int group_2A_count = 0;
+
+ FM_INFO_REPORT("cg2900_fm_transmit_rds_groups");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_transmit_rds_groups: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ while (group_position < 20 && result == 0) {
+ if (group_position < 4) {
+ /* Transmit PSN in Group 0B */
+ block1[0] = program_identification_code;
+ block1[1] = program_identification_code >> 8;
+ /* M/S bit set to Music */
+ if (group_0B_count % 4 == 0) {
+ /* Manipulate DI bit */
+ block2[0] =
+ (0x08 | ((program_type_code & 0x07)
+ << 5))
+ + group_0B_count;
+ } else {
+ block2[0] =
+ (0x0C | ((program_type_code & 0x07)
+ << 5))
+ + group_0B_count;
+ }
+ block2[1] =
+ 0x08 | ((program_type_code & 0x18) >> 3);
+ block3[0] = program_identification_code;
+ block3[1] = program_identification_code >> 8;
+ block4[0] = program_service[index1 + 1];
+ block4[1] = program_service[index1 + 0];
+ index1 += 2;
+ group_0B_count++;
+ } else {
+ /* Transmit RT in Group 2A */
+ block1[0] = program_identification_code;
+ block1[1] = program_identification_code >> 8;
+ if (a_b_flag)
+ block2[0] = (0x10 |
+ ((program_type_code & 0x07)
+ << 5)) + group_2A_count;
+ else
+ block2[0] = (0x00 |
+ ((program_type_code & 0x07)
+ << 5)) + group_2A_count;
+ block2[1] = 0x20 | ((program_type_code & 0x18)
+ >> 3);
+ block3[0] = radio_text[index2 + 1];
+ block3[1] = radio_text[index2 + 0];
+ block4[0] = radio_text[index2 + 3];
+ block4[1] = radio_text[index2 + 2];
+ index2 += 4;
+ group_2A_count++;
+ }
+ FM_DEBUG_REPORT("%02x%02x "
+ "%02x%02x "
+ "%02x%02x "
+ "%02x%02x ",
+ block1[1], block1[0],
+ block2[1], block2[0],
+ block3[1], block3[0],
+ block4[1], block4[0]);
+ result = fmd_tx_set_group(
+ group_position,
+ block1,
+ block2,
+ block3,
+ block4);
+ group_position++;
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_transmit_rds_groups: "
+ "fmd_tx_set_group failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ break;
+ }
+ }
+ a_b_flag = !a_b_flag;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_transmit_rds_groups: returning %d",
+ result);
+ return result;
+}
+
+/**
+ * cg2900_fm_check_rds_status()- Checks whether RDS was On previously
+ *
+ * This method is called on receiving interrupt for Seek Completion,
+ * Scan completion and Block Scan completion. It will check whether RDS
+ * was forcefully disabled before the above operations started and if the
+ * previous RDS state was true, then RDS will be enabled back
+ */
+static void cg2900_fm_check_rds_status(void)
+{
+ FM_INFO_REPORT("cg2900_fm_check_rds_status");
+ if (fm_prev_rds_status) {
+ /* Restart RDS if it was active previously */
+ cg2900_fm_rds_on();
+ fm_prev_rds_status = false;
+ }
+}
+
+/**
+ * cg2900_fm_driver_callback()- Callback function indicating the event.
+ *
+ * This callback function is called on receiving irpt_CommandSucceeded,
+ * irpt_CommandFailed, irpt_bufferFull, etc from FM chip.
+ * @event: event for which the callback function was caled
+ * from FM Driver.
+ * @event_successful: Signifying whether the event is called from FM Driver
+ * on receiving irpt_OperationSucceeded or irpt_OperationFailed.
+ */
+static void cg2900_fm_driver_callback(
+ u8 event,
+ bool event_successful
+ )
+{
+ struct sk_buff *skb;
+
+ FM_INFO_REPORT("cg2900_fm_driver_callback: "
+ "event = %02x, event_successful = %x",
+ event, event_successful);
+
+ switch (event) {
+ case FMD_EVENT_GEN_POWERUP:
+ FM_DEBUG_REPORT("FMD_EVENT_GEN_POWERUP");
+ break;
+ case FMD_EVENT_ANTENNA_STATUS_CHANGED:
+ FM_DEBUG_REPORT("FMD_EVENT_ANTENNA_STATUS_CHANGED");
+ break;
+ case FMD_EVENT_FREQUENCY_CHANGED:
+ FM_DEBUG_REPORT("FMD_EVENT_FREQUENCY_CHANGED ");
+ break;
+ case FMD_EVENT_SEEK_STOPPED:
+ FM_DEBUG_REPORT("FMD_EVENT_SEEK_STOPPED");
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA,
+ GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_fm_driver_callback: "
+ "Unable to Allocate Memory");
+ return;
+ }
+ skb->data[0] = CG2900_EVENT_SCAN_CANCELLED;
+ skb->data[1] = event_successful;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+ wake_up_poll_queue();
+ break;
+ case FMD_EVENT_SEEK_COMPLETED:
+ FM_DEBUG_REPORT("FMD_EVENT_SEEK_COMPLETED");
+ cg2900_fm_check_rds_status();
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA,
+ GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_fm_driver_callback: "
+ "Unable to Allocate Memory");
+ return;
+ }
+ skb->data[0] = CG2900_EVENT_SEARCH_CHANNEL_FOUND;
+ skb->data[1] = event_successful;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+ wake_up_poll_queue();
+ break;
+ case FMD_EVENT_SCAN_BAND_COMPLETED:
+ FM_DEBUG_REPORT("FMD_EVENT_SCAN_BAND_COMPLETED");
+ cg2900_fm_check_rds_status();
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA,
+ GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_fm_driver_callback: "
+ "Unable to Allocate Memory");
+ return;
+ }
+ skb->data[0] = CG2900_EVENT_SCAN_CHANNELS_FOUND;
+ skb->data[1] = event_successful;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+ wake_up_poll_queue();
+ break;
+ case FMD_EVENT_BLOCK_SCAN_COMPLETED:
+ FM_DEBUG_REPORT("FMD_EVENT_BLOCK_SCAN_COMPLETED");
+ cg2900_fm_check_rds_status();
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA,
+ GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_fm_driver_callback: "
+ "Unable to Allocate Memory");
+ return;
+ }
+ skb->data[0] = CG2900_EVENT_BLOCK_SCAN_CHANNELS_FOUND;
+ skb->data[1] = event_successful;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+ wake_up_poll_queue();
+ break;
+ case FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE:
+ FM_DEBUG_REPORT("FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE");
+ break;
+ case FMD_EVENT_RDSGROUP_RCVD:
+ FM_DEBUG_REPORT("FMD_EVENT_RDSGROUP_RCVD");
+ /*
+ * Release the rds semaphore, poll queue
+ * will be woken-up in rds callback
+ */
+ fmd_set_rds_sem();
+ break;
+ case FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE:
+ FM_ERR_REPORT(
+ "FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE");
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA,
+ GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_fm_driver_callback: "
+ "Unable to Allocate Memory");
+ return;
+ }
+ skb->data[0] = CG2900_EVENT_MONO_STEREO_TRANSITION;
+ skb->data[1] = event_successful;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+ wake_up_poll_queue();
+ break;
+ default:
+ FM_INFO_REPORT("cg2900_fm_driver_callback: "
+ "Unknown event = %x", event);
+ break;
+ }
+}
+
+/**
+ * cg2900_fm_rds_callback()- Function to retrieve the RDS groups.
+ *
+ * This is called when the chip has received enough RDS groups
+ * so an interrupt irpt_BufferFull is generated to read the groups.
+ */
+static void cg2900_fm_rds_callback(void)
+{
+ u8 index = 0;
+ u16 rds_local_buf_count;
+ int result;
+ struct sk_buff *skb;
+
+ FM_INFO_REPORT("cg2900_fm_rds_callback");
+
+ /*
+ * Wait till interrupt is RDS Buffer
+ * full interrupt is received
+ */
+ fmd_get_rds_sem();
+
+ if (!fm_rds_status)
+ return;
+
+ /* RDS Data available, Read the Groups */
+ mutex_lock(&rds_mutex);
+ result = fmd_int_bufferfull(&rds_local_buf_count);
+
+ if (0 != result)
+ goto error;
+
+ while (index < rds_local_buf_count) {
+ /*
+ * Status are in reverse order because of Endianness
+ * of status byte received from chip
+ */
+ result = fmd_rx_get_low_level_rds_groups(
+ index,
+ &fm_rds_buf[fm_rds_info.rds_head][index].block1,
+ &fm_rds_buf[fm_rds_info.rds_head][index].block2,
+ &fm_rds_buf[fm_rds_info.rds_head][index].block3,
+ &fm_rds_buf[fm_rds_info.rds_head][index].block4,
+ &fm_rds_buf[fm_rds_info.rds_head][index].status2,
+ &fm_rds_buf[fm_rds_info.rds_head][index].status1,
+ &fm_rds_buf[fm_rds_info.rds_head][index].status4,
+ &fm_rds_buf[fm_rds_info.rds_head][index].status3);
+ FM_INFO_REPORT("%04x %04x %04x %04x %02x %02x %02x %02x",
+ fm_rds_buf[fm_rds_info.rds_head][index].block1,
+ fm_rds_buf[fm_rds_info.rds_head][index].block2,
+ fm_rds_buf[fm_rds_info.rds_head][index].block3,
+ fm_rds_buf[fm_rds_info.rds_head][index].block4,
+ fm_rds_buf[fm_rds_info.rds_head][index].status1,
+ fm_rds_buf[fm_rds_info.rds_head][index].status2,
+ fm_rds_buf[fm_rds_info.rds_head][index].status3,
+ fm_rds_buf[fm_rds_info.rds_head][index].status4);
+
+ if (0 != result)
+
+ goto error;
+
+ if (!fm_rds_status)
+ return;
+
+ index++;
+ }
+ fm_rds_info.rds_head++;
+ if (fm_rds_info.rds_head == MAX_RDS_BUFFER)
+ fm_rds_info.rds_head = 0;
+
+ /* Queue the RDS event */
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA,
+ GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_fm_rds_callback: "
+ "Unable to Allocate Memory");
+ goto error;
+ }
+ skb->data[0] = CG2900_EVENT_RDS_EVENT;
+ skb->data[1] = true;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+
+ /* Wake up the poll queue */
+ wake_up_poll_queue();
+error:
+ mutex_unlock(&rds_mutex);
+}
+
+int cg2900_fm_init(void)
+{
+ int result = 0;
+
+ FM_INFO_REPORT("cg2900_fm_init");
+
+ if (CG2900_FM_STATE_DEINITIALIZED != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_init: Already Initialized");
+ result = -EINVAL;
+ goto error;
+ }
+
+ mutex_init(&rds_mutex);
+
+ memset(&fm_rds_info, 0, sizeof(struct cg2900_fm_rds_info));
+ memset(&version_info, 0, sizeof(struct cg2900_version_info));
+ memset(
+ fm_rds_buf,
+ 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+
+ /* Initalize the Driver */
+ if (fmd_init() != 0) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Register the callback */
+ if (fmd_register_callback(
+ (fmd_radio_cb) cg2900_fm_driver_callback) != 0) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* initialize global variables */
+ fm_event = CG2900_EVENT_NO_EVENT;
+ fm_state = CG2900_FM_STATE_INITIALIZED;
+ fm_mode = CG2900_FM_IDLE_MODE;
+ fm_prev_rds_status = false;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_init: returning %d",
+ result);
+ return result;
+
+}
+
+int cg2900_fm_deinit(void)
+{
+ int result = 0;
+
+ FM_INFO_REPORT("cg2900_fm_deinit");
+
+ if (CG2900_FM_STATE_INITIALIZED != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_deinit: Already de-Initialized");
+ result = -EINVAL;
+ goto error;
+ }
+ fmd_exit();
+ mutex_destroy(&rds_mutex);
+ fm_state = CG2900_FM_STATE_DEINITIALIZED;
+ fm_mode = CG2900_FM_IDLE_MODE;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_deinit: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_switch_on(
+ struct device *device
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_switch_on");
+
+ if (CG2900_FM_STATE_INITIALIZED != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Enable FM IP */
+ FM_DEBUG_REPORT("cg2900_fm_switch_on: " "Sending FM IP Enable");
+
+ if (fmd_send_fm_ip_enable()) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "Error in fmd_send_fm_ip_enable");
+ result = -EINVAL;
+ goto error;
+ }
+
+ if (version_info.revision == CG2910_PG1_REV
+ || version_info.revision == CG2910_PG2_REV
+ || version_info.revision == CG2905_PG1_1_REV) {
+ /* Now Download CG2910 lut and program Firmware files */
+ if (cg2910_fm_load_firmware(device) != 0) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "Error in downloading firmware for CG2910/05");
+ result = -EINVAL;
+ goto error;
+ }
+ } else if (version_info.revision == CG2900_PG1_REV
+ || version_info.revision == CG2900_PG2_REV
+ || version_info.revision == CG2900_PG1_SPECIAL_REV) {
+ /* Now Download the Coefficient Files and FM Firmware */
+ if (cg2900_fm_load_firmware(device) != 0) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "Error in downloading firmware for CG2900");
+ result = -EINVAL;
+ goto error;
+ }
+ } else {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "Unsupported Chip revision");
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Power up FM */
+ result = fmd_power_up();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "fmd_power_up failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Switch Mode To Idle */
+ result = fmd_set_mode(FMD_MODE_IDLE);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "fmd_set_mode failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ fm_state = CG2900_FM_STATE_SWITCHED_ON;
+ fm_mode = CG2900_FM_IDLE_MODE;
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_switch_on: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_switch_off(void)
+{
+ int result = 0;
+
+ FM_INFO_REPORT("cg2900_fm_switch_off");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state &&
+ CG2900_FM_STATE_STAND_BY != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_switch_off: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Stop the RDS Thread if it is running */
+ if (fm_rds_status) {
+ fm_rds_status = false;
+ fmd_stop_rds_thread();
+ }
+ if (CG2900_FM_STATE_STAND_BY == fm_state) {
+ /* Power up FM */
+ result = fmd_power_up();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_switch_off: "
+ "fmd_power_up failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ } else
+ fm_state = CG2900_FM_STATE_SWITCHED_ON;
+ }
+ if (fmd_send_fm_ip_disable()) {
+ FM_ERR_REPORT("cg2900_fm_switch_off: "
+ "Problem in fmd_send_fm_ip_"
+ "disable");
+ result = -EINVAL;
+ goto error;
+ }
+ if (0 == result) {
+ fm_state = CG2900_FM_STATE_INITIALIZED;
+ fm_mode = CG2900_FM_IDLE_MODE;
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_switch_off: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_standby(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_standby");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_standby: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ result = fmd_goto_standby();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_standby: "
+ "FMLGotoStandby failed, "
+ "err = %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ fm_state = CG2900_FM_STATE_STAND_BY;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_standby: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_power_up_from_standby(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_power_up_from_standby");
+
+ if (CG2900_FM_STATE_STAND_BY != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_power_up_from_standby: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Power up FM */
+ result = fmd_power_up();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_power_up_from_standby: "
+ "fmd_power_up failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ } else {
+ fm_state = CG2900_FM_STATE_SWITCHED_ON;
+ if (CG2900_FM_TX_MODE == fm_mode) {
+ /* Enable the PA */
+ result = fmd_tx_set_pa(true);
+ if (0 != result) {
+ FM_ERR_REPORT
+ ("cg2900_fm_power_up_from_standby:"
+ " fmd_tx_set_pa " "failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ }
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_power_up_from_standby: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_rx_default_settings(
+ u32 freq,
+ u8 band,
+ u8 grid,
+ bool enable_rds,
+ bool enable_stereo
+ )
+{
+ int result;
+ u8 vol_in_percentage;
+
+ FM_INFO_REPORT("cg2900_fm_set_rx_default_settings: freq = %d Hz, "
+ "band = %d, grid = %d, RDS = %d, Stereo Mode = %d",
+ freq, band, grid, enable_rds, enable_stereo);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state &&
+ CG2900_FM_STATE_STAND_BY != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_STATE_STAND_BY == fm_state) {
+ /* Power up FM */
+ result = fmd_power_up();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_power_up failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ } else
+ fm_state = CG2900_FM_STATE_SWITCHED_ON;
+ }
+ fm_mode = CG2900_FM_RX_MODE;
+
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: "
+ "Sending Set mode to Rx");
+ result = fmd_set_mode(FMD_MODE_RX);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_set_mode failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Grid */
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: "
+ "Sending fmd_rx_set_grid ");
+ result = fmd_rx_set_grid(grid);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_rx_set_grid failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Band */
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: "
+ "Sending Set fmd_set_freq_range");
+ result = fmd_set_freq_range(band);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_set_freq_range failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Frequency */
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: "
+ "Sending Set fmd_rx_set_frequency");
+ result = fmd_rx_set_frequency(
+ freq / FREQUENCY_CONVERTOR_KHZ_HZ);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_rx_set_frequency failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: "
+ "SetFrequency interrupt received, "
+ "Sending Set fmd_rx_set_stereo_mode");
+
+ if (enable_stereo) {
+ /* Set the Stereo Blending mode */
+ result = fmd_rx_set_stereo_mode(
+ FMD_STEREOMODE_BLENDING);
+ } else {
+ /* Set the Mono mode */
+ result = fmd_rx_set_stereo_mode(
+ FMD_STEREOMODE_MONO);
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_rx_set_stereo_mode "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ if (enable_stereo) {
+ /* Set the Stereo Blending RSSI control */
+ result = fmd_rx_set_stereo_ctrl_blending_rssi(
+ STEREO_BLENDING_MIN_RSSI,
+ STEREO_BLENDING_MAX_RSSI);
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_rx_set_stereo_ctrl_blending_rssi "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set RDS Group rejection Off */
+ result = fmd_rx_set_rds_group_rejection(
+ FMD_RDS_GROUP_REJECTION_OFF);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "fmd_rx_set_rds_group_rejection "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Remove all Interrupt from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: "
+ "Sending Set rds");
+
+ if (enable_rds) {
+ /* Enable RDS */
+ a_b_flag = false;
+ result = cg2900_fm_rds_on();
+ } else {
+ /* Disable RDS */
+ result = cg2900_fm_rds_off();
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rx_default_settings: "
+ "cg2900_fm_rds_on "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Currently, not supported for CG2905/10 */
+ if (version_info.revision == CG2900_PG1_REV
+ || version_info.revision == CG2900_PG2_REV
+ || version_info.revision == CG2900_PG1_SPECIAL_REV) {
+ /* Set the Analog Out Volume to Max */
+ vol_in_percentage = (u8)
+ (((u16) (MAX_ANALOG_VOLUME) * 100)
+ / MAX_ANALOG_VOLUME);
+ result = fmd_set_volume(vol_in_percentage);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "FMRSetVolume failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_rx_default_settings: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_tx_default_settings(
+ u32 freq,
+ u8 band,
+ u8 grid,
+ bool enable_rds,
+ bool enable_stereo
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_set_tx_default_settings: freq = %d Hz, "
+ "band = %d, grid = %d, RDS = %d, Stereo Mode = %d",
+ freq, band, grid, enable_rds, enable_stereo);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state &&
+ CG2900_FM_STATE_STAND_BY != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_STATE_STAND_BY == fm_state) {
+ /* Power up FM */
+ result = fmd_power_up();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_power_up failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ } else
+ fm_state = CG2900_FM_STATE_SWITCHED_ON;
+ }
+ fm_mode = CG2900_FM_TX_MODE;
+ if (fm_rds_status) {
+ fm_rds_status = false;
+ fmd_stop_rds_thread();
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Give 50 ms delay to exit the RDS thread */
+ schedule_timeout_interruptible(msecs_to_jiffies(50));
+ }
+ /* Remove all Interrupt from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+
+ /* Switch To Tx mode */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending Set mode to Tx");
+ result = fmd_set_mode(FMD_MODE_TX);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_set_mode failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Sets the Limiter Values */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending fmd_limiter_setcontrol");
+ result = fmd_limiter_setcontrol(
+ DEFAULT_AUDIO_DEVIATION,
+ DEFAULT_NOTIFICATION_HOLD_OFF_TIME);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_limiter_setcontrol failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Grid */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending fmd_tx_set_grid ");
+ result = fmd_tx_set_grid(grid);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_tx_set_grid failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Band */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending fmd_tx_set_freq_range");
+ result = fmd_tx_set_freq_range(band);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_tx_set_freq_range failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Band */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending fmd_tx_set_preemphasis");
+ result = fmd_tx_set_preemphasis(FMD_EMPHASIS_75US);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "fmd_tx_set_preemphasis failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Set the Frequency */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending Set fmd_tx_set_frequency");
+ result = fmd_tx_set_frequency(
+ freq / FREQUENCY_CONVERTOR_KHZ_HZ);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_switch_on: "
+ "fmd_tx_set_frequency failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "SetFrequency interrupt received, "
+ "Sending Set fmd_tx_enable_stereo_mode");
+
+ /* Set the Stereo mode */
+ result = fmd_tx_enable_stereo_mode(enable_stereo);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_tx_enable_stereo_mode "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending Set fmd_tx_set_pa");
+
+ /* Enable the PA */
+ result = fmd_tx_set_pa(true);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_tx_set_pa "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "set PA interrupt received, "
+ "Sending Set fmd_tx_set_signal_strength");
+
+ /* Set the Signal Strength to Max */
+ result = fmd_tx_set_signal_strength(
+ MAX_POWER_LEVEL);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "fmd_tx_set_signal_strength "
+ "failed %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Enable Tx RDS */
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: "
+ "Sending Set cg2900_fm_tx_rds");
+ result = cg2900_fm_tx_rds(enable_rds);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_tx_default_settings: "
+ "cg2900_fm_tx_rds "
+ "failed %x", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_tx_default_settings: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_grid(
+ u8 grid
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_set_grid: Grid = %d", grid);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_grid: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_set_grid(grid);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_grid: "
+ "fmd_rx_set_grid failed");
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_grid: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_band(
+ u8 band
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_set_band: Band = %d", band);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_band: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_set_freq_range(band);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_band: "
+ "fmd_set_freq_range failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_band: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_search_up_freq(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_search_up_freq");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_search_up_freq: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (fm_rds_status) {
+ /* Stop RDS if it is active */
+ result = cg2900_fm_rds_off();
+ fm_prev_rds_status = true;
+ } else {
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ }
+ result = fmd_rx_seek(CG2900_DIR_UP);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_search_up_freq: "
+ "Error Code %d", (unsigned int)result);
+ cg2900_fm_check_rds_status();
+ result = -EINVAL;
+ goto error;
+ }
+ result = 0;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_search_up_freq: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_search_down_freq(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_search_down_freq");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_search_down_freq: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (fm_rds_status) {
+ /* Stop RDS if it is active */
+ result = cg2900_fm_rds_off();
+ fm_prev_rds_status = true;
+ } else {
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ }
+ result = fmd_rx_seek(CG2900_DIR_DOWN);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_search_down_freq: "
+ "Error Code %d", (unsigned int)result);
+ cg2900_fm_check_rds_status();
+ result = -EINVAL;
+ goto error;
+ }
+ result = 0;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_search_down_freq: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_start_band_scan(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_start_band_scan");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_start_band_scan: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (fm_rds_status) {
+ /* Stop RDS if it is active */
+ result = cg2900_fm_rds_off();
+ fm_prev_rds_status = true;
+ } else {
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ }
+ result = fmd_rx_scan_band(DEFAULT_CHANNELS_TO_SCAN);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_start_band_scan: "
+ "Error Code %d", (unsigned int)result);
+ cg2900_fm_check_rds_status();
+ result = -EINVAL;
+ goto error;
+ }
+ result = 0;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_start_band_scan: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_stop_scan(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_stop_scan");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_stop_scan: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_stop_seeking();
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_stop_scan: "
+ "Error Code %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ result = 0;
+ if (fm_prev_rds_status) {
+ /* Restart RDS if it was active earlier */
+ cg2900_fm_rds_on();
+ fm_prev_rds_status = false;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_stop_scan: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_scan_result(
+ u16 *num_of_scanfreq,
+ u32 *scan_freq,
+ u32 *scan_freq_rssi_level
+ )
+{
+ int result;
+ u32 cnt;
+ u32 index;
+ u32 minfreq;
+ u32 maxfreq;
+ u16 channels[3];
+ u16 rssi[3];
+ u8 freq_range;
+ u8 max_channels = 0;
+
+ FM_INFO_REPORT("cg2900_fm_get_scan_result");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_scan_result: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_get_freq_range(&freq_range);
+
+ if (0 != result) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_get_freq_range_properties(
+ freq_range,
+ &minfreq,
+ &maxfreq);
+
+ if (0 != result) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_rx_get_max_channels_to_scan(&max_channels);
+
+ if (0 != result) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* In 1 iteration we can retreive max 3 channels */
+ cnt = (max_channels / 3) + 1;
+ while ((cnt--) && (result == 0)) {
+ /*
+ * Get all channels, including empty ones.
+ * In 1 iteration at max 3 channels can be found.
+ */
+ result = fmd_rx_get_scan_band_info(cnt * 3,
+ num_of_scanfreq,
+ channels, rssi);
+ if (0 == result) {
+ index = cnt * 3;
+ /* Convert Freq to Hz from channel number */
+ scan_freq[index] = (minfreq +
+ channels[0] *
+ CHANNEL_FREQ_CONVERTER_MHZ) *
+ FREQUENCY_CONVERTOR_KHZ_HZ;
+ scan_freq_rssi_level[index] = rssi[0];
+ /* Convert Freq to Hz from channel number */
+ scan_freq[index + 1] = (minfreq +
+ channels[1] *
+ CHANNEL_FREQ_CONVERTER_MHZ) *
+ FREQUENCY_CONVERTOR_KHZ_HZ;
+ scan_freq_rssi_level[index + 1] = rssi[1];
+ /* Check if we donot overwrite the array */
+ if (cnt < (max_channels / 3)) {
+ /* Convert Freq to Hz from channel number */
+ scan_freq[index + 2] = (minfreq +
+ channels[2] *
+ CHANNEL_FREQ_CONVERTER_MHZ) *
+ FREQUENCY_CONVERTOR_KHZ_HZ;
+ scan_freq_rssi_level[index + 2]
+ = rssi[2];
+ }
+ }
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_scan_result: returning %d",
+ result);
+ return result;
+
+}
+
+int cg2900_fm_start_block_scan(
+ u32 start_freq,
+ u32 end_freq
+ )
+{
+ int result;
+ u8 antenna;
+
+ FM_INFO_REPORT("cg2900_fm_start_block_scan");
+
+ FM_DEBUG_REPORT("cg2900_fm_start_block_scan: Start Freq = %d, "
+ "End Freq = %d", start_freq, end_freq);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_start_block_scan: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (fm_rds_status) {
+ /* Stop RDS if it is active */
+ result = cg2900_fm_rds_off();
+ fm_prev_rds_status = true;
+ } else {
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ }
+ result = fmd_get_antenna(
+ &antenna);
+ result = fmd_block_scan(
+ start_freq/FREQUENCY_CONVERTOR_KHZ_HZ,
+ end_freq/FREQUENCY_CONVERTOR_KHZ_HZ,
+ antenna);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_start_block_scan: "
+ "Error Code %d", (unsigned int)result);
+ cg2900_fm_check_rds_status();
+ result = -EINVAL;
+ goto error;
+ }
+ result = 0;
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_start_block_scan: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_block_scan_result(
+ u16 *num_of_scanchan,
+ u16 *scan_freq_rssi_level
+ )
+{
+ int result = 0;
+ u32 cnt;
+ u32 index;
+ u16 rssi[6];
+
+ FM_INFO_REPORT("cg2900_fm_get_block_scan_result");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_block_scan_result: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ cnt = 33;
+ while ((cnt--) && (result == 0)) {
+ /* Get all channels, including empty ones */
+ result = fmd_get_block_scan_result(
+ cnt * 6,
+ num_of_scanchan,
+ rssi);
+ if (0 == result) {
+ index = cnt * 6;
+ scan_freq_rssi_level[index]
+ = rssi[0];
+ scan_freq_rssi_level[index + 1]
+ = rssi[1];
+ scan_freq_rssi_level[index + 2]
+ = rssi[2];
+ scan_freq_rssi_level[index + 3]
+ = rssi[3];
+ scan_freq_rssi_level[index + 4]
+ = rssi[4];
+ scan_freq_rssi_level[index + 5]
+ = rssi[5];
+ }
+ }
+ if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_block_scan_result:"
+ " Sending Set fmd_tx_set_pa");
+
+ /* Enable the PA */
+ result = fmd_tx_set_pa(true);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_block_scan_result:"
+ " fmd_tx_set_pa "
+ "failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_block_scan_result: returning %d",
+ result);
+ return result;
+
+}
+
+int cg2900_fm_tx_rds(
+ bool enable_rds
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_rds: enable_rds = %d", enable_rds);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_rds: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (enable_rds) {
+ /* Set the Tx Buffer Size */
+ result = fmd_tx_buffer_set_size(
+ MAX_RDS_GROUPS - 2);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_rds: "
+ "fmd_tx_buffer_set_size "
+ "failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ } else {
+ result = fmd_tx_set_rds(true);
+ }
+
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_rds: "
+ "fmd_tx_set_rds "
+ "failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ program_identification_code =
+ default_program_identification_code;
+ program_type_code = default_program_type_code;
+ memcpy(program_service,
+ default_program_service,
+ MAX_PSN_SIZE);
+ memcpy(radio_text,
+ default_radio_text, MAX_RT_SIZE);
+ radio_text[strlen(radio_text)] = 0x0D;
+ cg2900_fm_transmit_rds_groups();
+ result = 0;
+ } else {
+ result = fmd_tx_set_rds(false);
+
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_rds: "
+ "fmd_tx_set_rds "
+ "failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_rds: returning %d",
+ result);
+
+ return result;
+}
+
+int cg2900_fm_tx_set_pi_code(
+ u16 pi_code
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_pi_code: PI = %04x", pi_code);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_pi_code: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ program_identification_code = pi_code;
+ result = cg2900_fm_transmit_rds_groups();
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_pi_code: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_pty_code(
+ u16 pty_code
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_pty_code: PTY = %04x", pty_code);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_pty_code: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ program_type_code = pty_code;
+ result = cg2900_fm_transmit_rds_groups();
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_pty_code: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_program_station_name(
+ char *psn,
+ u8 len
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_program_station_name: PSN = %s",
+ psn);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_program_station_name: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (len < (MAX_PSN_SIZE - 1)) {
+ int count = len;
+ while (count < (MAX_PSN_SIZE - 1))
+ psn[count++] = ' ';
+ }
+ memcpy(program_service, psn, MAX_PSN_SIZE);
+ result = cg2900_fm_transmit_rds_groups();
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_program_station_name: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_radio_text(
+ char *rt,
+ u8 len
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_radio_text: RT = %s", rt);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_radio_text: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ rt[len] = 0x0D;
+ memcpy(radio_text, rt, len + 1);
+
+ result = cg2900_fm_transmit_rds_groups();
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_radio_text: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_get_rds_deviation(
+ u16 *deviation
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_get_rds_deviation");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_rds_deviation: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_get_rds_deviation(deviation);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_rds_deviation: "
+ "fmd_tx_get_rds_deviation failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_get_rds_deviation: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_rds_deviation(
+ u16 deviation
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_rds_deviation: deviation = %d",
+ deviation);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_rds_deviation: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_set_rds_deviation(deviation);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_rds_deviation: "
+ "fmd_tx_set_rds_deviation failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_rds_deviation: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_get_pilot_tone_status(
+ bool *enable
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_get_pilot_tone_status");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_pilot_tone_status: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_get_stereo_mode(enable);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_pilot_tone_status: "
+ "fmd_tx_get_stereo_mode failed %d",
+ result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_get_pilot_tone_status: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_pilot_tone_status(
+ bool enable
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_pilot_tone_status: enable = %d",
+ enable);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_pilot_tone_status: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_enable_stereo_mode(enable);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_pilot_tone_status: "
+ "fmd_tx_enable_stereo_mode failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_pilot_tone_status: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_get_pilot_deviation(
+ u16 *deviation
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_get_pilot_deviation");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_pilot_deviation: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_get_pilot_deviation(deviation);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_pilot_deviation: "
+ "fmd_tx_get_pilot_deviation failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_get_pilot_deviation: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_pilot_deviation(
+ u16 deviation
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_pilot_deviation: deviation = %d",
+ deviation);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_pilot_deviation: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_set_pilot_deviation(deviation);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_pilot_deviation: "
+ "fmd_tx_set_pilot_deviation failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_pilot_deviation: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_get_preemphasis(
+ u8 *preemphasis
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_get_preemphasis");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_preemphasis: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_get_preemphasis(preemphasis);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_preemphasis: "
+ "fmd_tx_get_preemphasis failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_get_preemphasis: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_preemphasis(
+ u8 preemphasis
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_preemphasis: preemphasis = %d",
+ preemphasis);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_preemphasis: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_set_preemphasis(preemphasis);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_preemphasis: "
+ "fmd_tx_set_preemphasis failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_preemphasis: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_rx_set_deemphasis(
+ u8 deemphasis
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_rx_set_deemphasis: deemphasis = %02x",
+ deemphasis);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_rx_set_deemphasis: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_set_deemphasis(deemphasis);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_rx_set_deemphasis: "
+ "fmd_rx_set_deemphasis failed %d",
+ result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_rx_set_deemphasis: returning %d", result);
+ return result;
+}
+
+int cg2900_fm_tx_get_power_level(
+ u16 *power_level
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_get_power_level");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_power_level: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_get_signal_strength(power_level);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_get_power_level: "
+ "fmd_tx_get_signal_strength failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_get_power_level: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_tx_set_power_level(
+ u16 power_level
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_tx_set_power_level: power_level = %d",
+ power_level);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_power_level: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_tx_set_signal_strength(power_level);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_tx_set_power_level: "
+ "fmd_tx_set_preemphasis failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_tx_set_power_level: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_audio_balance(
+ s8 balance
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_set_audio_balance, balance = %d", balance);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_audio_balance: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_set_balance(balance);
+ if (0 != result) {
+ FM_ERR_REPORT("FMRSetAudioBalance : "
+ "Failed in fmd_set_balance, err = %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_audio_balance: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_volume(
+ u8 vol_level
+ )
+{
+ int result;
+ u8 vol_in_percentage;
+
+ FM_INFO_REPORT("cg2900_fm_set_volume: Volume Level = %d", vol_level);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_volume: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ vol_in_percentage =
+ (u8) (((u16) (vol_level) * 100) / MAX_ANALOG_VOLUME);
+ result = fmd_set_volume(vol_in_percentage);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_increase_volume: "
+ "FMRSetVolume failed, err = %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_volume: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_volume(
+ u8 *vol_level
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_get_volume");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_volume: "
+ "Invalid state of FM Driver = %d", fm_state);
+ *vol_level = 0;
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_get_volume(vol_level);
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_volume: returning %d, VolLevel = %d",
+ result, *vol_level);
+ return result;
+}
+
+int cg2900_fm_rds_off(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_rds_off");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_rds_off: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ result = fmd_rx_set_rds(FMD_SWITCH_OFF_RDS);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_rds_off: fmd_rx_set_rds failed, "
+ "err = %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ if (fm_rds_status) {
+ /* Stop the RDS Thread */
+ FM_DEBUG_REPORT("cg2900_fm_rds_off: "
+ "Stopping RDS Thread");
+ fmd_stop_rds_thread();
+ fm_rds_status = false;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_rds_off: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_rds_on(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_rds_on");
+ if (fm_rds_status) {
+ result = 0;
+ FM_DEBUG_REPORT("cg2900_fm_rds_on: rds is on "
+ "return result = %d", result);
+ return result;
+ }
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_rds_on: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ FM_DEBUG_REPORT("cg2900_fm_rds_on:"
+ " Sending fmd_rx_buffer_set_size");
+ result = fmd_rx_buffer_set_size(MAX_RDS_GROUPS);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_rds_on: fmd_rx_buffer_set_size"
+ "failed, err = %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ FM_DEBUG_REPORT("cg2900_fm_rds_on: Sending "
+ "fmd_rx_buffer_set_threshold");
+ result = fmd_rx_buffer_set_threshold(MAX_RDS_GROUPS - 1);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_rds_on: fmd_rx_buffer_set_threshold "
+ "failed, err = %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ FM_DEBUG_REPORT("cg2900_fm_rds_on: Sending fmd_rx_set_rds");
+ result = fmd_rx_set_rds(FMD_SWITCH_ON_RDS_ENHANCED_MODE);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_rds_on: fmd_rx_set_rds failed, "
+ "err = %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Start the RDS Thread to read the RDS Buffers */
+ fm_rds_status = true;
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ fmd_start_rds_thread(cg2900_fm_rds_callback);
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_rds_on: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_rds_status(
+ bool *rds_status
+ )
+{
+ int result = 0;
+
+ FM_INFO_REPORT("cg2900_fm_get_rds_status");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_rds_status: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_RX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_rds_status: "
+ "fmd_rx_get_rds");
+ result = fmd_rx_get_rds(rds_status);
+ } else if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_rds_status: "
+ "fmd_tx_get_rds");
+ result = fmd_tx_get_rds(rds_status);
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_rds_status: "
+ "fmd_get_rds failed, Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_rds_status: returning %d, "
+ "rds_status = %d", result,
+ *rds_status);
+ return result;
+}
+
+int cg2900_fm_mute(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_mute");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_mute: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+
+ /* Mute Analog DAC */
+ result = fmd_set_mute(true);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_mute: "
+ "fmd_set_mute failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Mute Ext Src */
+ result = fmd_ext_set_mute(true);
+ if (0 != result) {
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_mute: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_unmute(void)
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_unmute");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_unmute: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Unmute Analog DAC */
+ result = fmd_set_mute(false);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_mute: "
+ "fmd_set_mute failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Unmute Ext Src */
+ result = fmd_ext_set_mute(false);
+ if (0 != result) {
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_unmute: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_frequency(
+ u32 *freq
+ )
+{
+ int result = 0;
+ u32 currentFreq = 0;
+
+ FM_INFO_REPORT("cg2900_fm_get_frequency");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_frequency: "
+ "Invalid state of FM Driver = %d", fm_state);
+ *freq = 0;
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_RX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_frequency: "
+ "fmd_rx_get_frequency");
+ result = fmd_rx_get_frequency(
+ (u32 *) &currentFreq);
+ } else if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_frequency: "
+ "fmd_tx_get_frequency");
+ result = fmd_tx_get_frequency(
+ (u32 *) &currentFreq);
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_frequency: "
+ "fmd_rx_get_frequency failed %d",
+ (unsigned int)result);
+ *freq = 0;
+ result = -EINVAL;
+ goto error;
+ }
+ /* Convert To Hz */
+ *freq = currentFreq * FREQUENCY_CONVERTOR_KHZ_HZ;
+ FM_DEBUG_REPORT("cg2900_fm_get_frequency: "
+ "Current Frequency = %d Hz", *freq);
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_frequency: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_frequency(
+ u32 new_freq
+ )
+{
+ int result = 0;
+
+ FM_INFO_REPORT("cg2900_fm_set_frequency, new_freq = %d",
+ (int)new_freq);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_frequency: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ /* Check if RDS needs to be disabled before Setting Frequency */
+ if (fm_rds_status) {
+ /* Stop RDS if it is active */
+ result = cg2900_fm_rds_off();
+ fm_prev_rds_status = true;
+ } else {
+ memset(&fm_rds_info, 0,
+ sizeof(struct cg2900_fm_rds_info));
+ memset(fm_rds_buf, 0,
+ sizeof(struct cg2900_fm_rds_buf) *
+ MAX_RDS_BUFFER * MAX_RDS_GROUPS);
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ }
+
+ if (CG2900_FM_RX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_set_frequency: "
+ "fmd_rx_set_frequency");
+ result = fmd_rx_set_frequency(
+ new_freq / FREQUENCY_CONVERTOR_KHZ_HZ);
+ } else if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_set_frequency: "
+ "fmd_tx_set_frequency");
+ result = fmd_tx_set_frequency(
+ new_freq / FREQUENCY_CONVERTOR_KHZ_HZ);
+ }
+ if (fm_prev_rds_status) {
+ /* Restart RDS if it was active earlier */
+ cg2900_fm_rds_on();
+ fm_prev_rds_status = false;
+ }
+ if (result != 0) {
+ FM_ERR_REPORT("cg2900_fm_set_frequency: "
+ "fmd_rx_set_frequency failed %x",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+ if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_set_frequency:"
+ " Sending Set" "fmd_tx_set_pa");
+
+ /* Enable the PA */
+ result = fmd_tx_set_pa(true);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_frequency:"
+ " fmd_tx_set_pa "
+ "failed %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ result = 0;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_frequency: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_signal_strength(
+ u16 *signal_strength
+ )
+{
+ int result = 0;
+
+ FM_INFO_REPORT("cg2900_fm_get_signal_strength");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_signal_strength: "
+ "Invalid state of FM Driver = %d", fm_state);
+ *signal_strength = 0;
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_RX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_signal_strength: "
+ "fmd_rx_get_signal_strength");
+ result = fmd_rx_get_signal_strength(
+ signal_strength);
+ } else if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_signal_strength: "
+ "fmd_tx_get_signal_strength");
+ result = fmd_tx_get_signal_strength(
+ signal_strength);
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_signal_strength: "
+ "Error Code %d", (unsigned int)result);
+ *signal_strength = 0;
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_signal_strength: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_af_update_get_result(
+ u16 *af_update_rssi
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_af_update_get_result");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_af_update_get_result: "
+ "Invalid state of FM Driver = %d", fm_state);
+ *af_update_rssi = 0;
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_get_af_update_result(af_update_rssi);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_af_update_get_result: "
+ "Error Code %d", (unsigned int)result);
+ *af_update_rssi = 0;
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_af_update_get_result: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_af_update_start(
+ u32 af_freq
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_af_update_start");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_af_update_start: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_af_update_start(
+ af_freq / FREQUENCY_CONVERTOR_KHZ_HZ);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_af_update_start: "
+ "Error Code %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_af_update_start: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_af_switch_get_result(
+ u16 *af_switch_conclusion
+ )
+{
+ int result;
+ u16 af_rssi;
+ u16 af_pi;
+
+ FM_INFO_REPORT("cg2900_fm_af_switch_get_result");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_af_switch_get_result: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_get_af_switch_results(
+ af_switch_conclusion,
+ &af_rssi, &af_pi);
+
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_af_switch_get_result: "
+ "Error Code %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+ FM_DEBUG_REPORT("cg2900_fm_af_switch_get_result: "
+ "AF Switch conclusion = %d "
+ "AF Switch RSSI level = %d "
+ "AF Switch PI code = %d ",
+ *af_switch_conclusion, af_rssi, af_pi);
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_af_switch_get_result: returning %d",
+ result);
+ return result;
+
+}
+
+int cg2900_fm_af_switch_start(
+ u32 af_switch_freq,
+ u16 af_switch_pi
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_af_switch_start");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_af_switch_start: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_rx_af_switch_start(
+ af_switch_freq / FREQUENCY_CONVERTOR_KHZ_HZ,
+ af_switch_pi);
+
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_af_switch_start: "
+ "Error Code %d", (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_af_switch_start: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_mode(
+ u8 *cur_mode
+ )
+{
+ int result = 0;
+ bool stereo_mode;
+
+ FM_INFO_REPORT("cg2900_fm_get_mode");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_mode: "
+ "Invalid state of FM Driver = %d", fm_state);
+ *cur_mode = CG2900_MODE_MONO;
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_RX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_mode: "
+ "fmd_rx_get_stereo_mode");
+ result = fmd_rx_get_stereo_mode(cur_mode);
+ FM_DEBUG_REPORT("cg2900_fm_get_mode: cur_mode = %x", *cur_mode);
+ } else if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_get_mode: "
+ "fmd_tx_get_stereo_mode");
+ result = fmd_tx_get_stereo_mode(&stereo_mode);
+ if (stereo_mode)
+ *cur_mode = CG2900_MODE_STEREO;
+ else
+ *cur_mode = CG2900_MODE_MONO;
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_mode: "
+ "fmd_get_stereo_mode failed, "
+ "Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_mode: returning %d, mode = %d",
+ result, *cur_mode);
+ return result;
+}
+
+int cg2900_fm_set_mode(
+ u8 mode
+ )
+{
+ int result = 0;
+ bool enable_stereo_mode = false;
+
+ FM_INFO_REPORT("cg2900_fm_set_mode: mode = %d", mode);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_set_mode: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ if (CG2900_FM_RX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_set_mode: "
+ "fmd_rx_set_stereo_mode");
+ result = fmd_rx_set_stereo_mode(mode);
+ } else if (CG2900_FM_TX_MODE == fm_mode) {
+ FM_DEBUG_REPORT("cg2900_fm_set_mode: "
+ "fmd_tx_set_stereo_mode");
+ if (mode == CG2900_MODE_STEREO)
+ enable_stereo_mode = true;
+ result =
+ fmd_tx_enable_stereo_mode(
+ enable_stereo_mode);
+ }
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_mode: "
+ "fmd_rx_set_stereo_mode failed, "
+ "Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_mode: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_select_antenna(
+ u8 antenna
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_select_antenna: Antenna = %d", antenna);
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_select_antenna: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_set_antenna(antenna);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_select_antenna: "
+ "fmd_set_antenna failed, Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_select_antenna: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_antenna(
+ u8 *antenna
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_get_antenna");
+
+ if (CG2900_FM_STATE_SWITCHED_ON != fm_state) {
+ FM_ERR_REPORT("cg2900_fm_get_antenna: "
+ "Invalid state of FM Driver = %d", fm_state);
+ result = -EINVAL;
+ goto error;
+ }
+ result = fmd_get_antenna(antenna);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_antenna: "
+ "fmd_get_antenna failed, Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_antenna: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_get_rssi_threshold(
+ u16 *rssi_thresold
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_get_rssi_threshold");
+
+ result = fmd_rx_get_stop_level(rssi_thresold);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_get_rssi_threshold: "
+ "fmd_rx_get_stop_level failed, Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_get_rssi_threshold: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_set_rssi_threshold(
+ u16 rssi_thresold
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_set_rssi_threshold: "
+ "RssiThresold = %d", rssi_thresold);
+
+ result = fmd_rx_set_stop_level(rssi_thresold);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_rssi_threshold: "
+ "fmd_rx_set_stop_level failed, Error Code %d",
+ (unsigned int)result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_rssi_threshold: returning %d",
+ result);
+ return result;
+}
+
+void cg2900_fm_set_chip_version(
+ u16 revision,
+ u16 sub_version
+ )
+{
+ version_info.revision = revision;
+ version_info.sub_version = sub_version;
+}
+
+int cg2900_fm_set_test_tone_generator(
+ u8 test_tone_status
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_set_test_tone_generator: "
+ "test_tone_status = %02x", test_tone_status);
+
+ result = fmd_set_test_tone_generator_status(test_tone_status);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_set_test_tone_generator: "
+ "fmd_set_test_tone_generator_status failed"
+ ", Error Code %d", result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_set_test_tone_generator: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_test_tone_connect(
+ u8 left_audio_mode,
+ u8 right_audio_mode
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_test_tone_connect: "
+ "left_audio_mode = %02x right_audio_mode = %02x",
+ left_audio_mode, right_audio_mode);
+
+ result = fmd_test_tone_connect(left_audio_mode, right_audio_mode);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_test_tone_connect: "
+ "fmd_set_test_tone_connect failed, Error Code %d",
+ result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_test_tone_connect: returning %d",
+ result);
+ return result;
+}
+
+int cg2900_fm_test_tone_set_params(
+ u8 tone_gen,
+ u16 frequency,
+ u16 volume,
+ u16 phase_offset,
+ u16 dc,
+ u8 waveform
+ )
+{
+ int result;
+
+ FM_INFO_REPORT("cg2900_fm_test_tone_set_params: "
+ "tone_gen = %02x frequency = %04x "
+ "volume = %04x phase_offset = %04x "
+ "dc offset = %04x waveform = %02x",
+ tone_gen, frequency,
+ volume, phase_offset,
+ dc, waveform);
+
+ result = fmd_test_tone_set_params(
+ tone_gen,
+ frequency,
+ volume,
+ phase_offset,
+ dc,
+ waveform);
+ if (0 != result) {
+ FM_ERR_REPORT("cg2900_fm_test_tone_set_params: "
+ "fmd_test_tone_set_params failed, Error Code %d",
+ result);
+ result = -EINVAL;
+ goto error;
+ }
+
+error:
+ FM_DEBUG_REPORT("cg2900_fm_test_tone_set_params: returning %d",
+ result);
+ return result;
+}
+
+MODULE_AUTHOR("Hemant Gupta");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/radio/CG2900/cg2900_fm_api.h b/drivers/media/radio/CG2900/cg2900_fm_api.h
new file mode 100644
index 00000000000..ec9e6e86f77
--- /dev/null
+++ b/drivers/media/radio/CG2900/cg2900_fm_api.h
@@ -0,0 +1,1077 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Linux FM Host API's for ST-Ericsson FM Chip.
+ *
+ * Author: Hemant Gupta <hemant.gupta@stericsson.com> for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#ifndef CG2900_FM_API_H
+#define CG2900_FM_API_H
+
+#include <linux/device.h>
+#include <linux/skbuff.h>
+
+/* Callback function to receive RDS Data. */
+typedef void (*cg2900_fm_rds_cb)(void);
+
+extern struct sk_buff_head fm_interrupt_queue;
+
+/**
+ * struct cg2900_fm_rds_buf - RDS Group Receiving Structure
+ *
+ * @block1: RDS Block A
+ * @block2: RDS Block B
+ * @block3: RDS Block C
+ * @block4: RDS Block D
+ * @status1: Status of received RDS Block A
+ * @status2: Status of received RDS Block B
+ * @status3: Status of received RDS Block C
+ * @status4: Status of received RDS Block D
+ *
+ * Structure for receiving the RDS Group from FM Chip.
+ */
+struct cg2900_fm_rds_buf {
+ u16 block1;
+ u16 block2;
+ u16 block3;
+ u16 block4;
+ u8 status1;
+ u8 status2;
+ u8 status3;
+ u8 status4;
+};
+
+/**
+ * struct cg2900_fm_rds_info - RDS Information Structure
+ *
+ * @rds_head: RDS Queue Head for storing next valid data.
+ * @rds_tail: RDS Queue Tail for retreiving next valid data.
+ * @rds_group_sent: Number of RDS Groups sent to Application.
+ * @rds_block_sent: Number of RDS Blocks sent to Application.
+ *
+ * Structure for storing the RDS data queue information.
+ */
+struct cg2900_fm_rds_info {
+ u8 rds_head;
+ u8 rds_tail;
+ u8 rds_group_sent;
+ u8 rds_block_sent;
+};
+
+/**
+ * struct cg2900_version_info - Chip HCI Version Info
+ *
+ * @revision: Revision of the controller, e.g. to indicate that it is
+ * a CG2900 controller.
+ * @sub_version: Subversion of the controller, e.g. to indicate a certain
+ * tape-out of the controller.
+ *
+ * Structure for storing the HCI Version Information of the Controller.
+ */
+struct cg2900_version_info {
+ u16 revision;
+ u16 sub_version;
+};
+
+/**
+ * enum cg2900_fm_state - States of FM Driver.
+ *
+ * @CG2900_FM_STATE_DEINITIALIZED: FM driver is not initialized.
+ * @CG2900_FM_STATE_INITIALIZED: FM driver is initialized.
+ * @CG2900_FM_STATE_SWITCHED_ON: FM driver is switched on and in active state.
+ * @CG2900_FM_STATE_STAND_BY: FM Radio is switched on but not in active state.
+ *
+ * Various states of FM Driver.
+ */
+enum cg2900_fm_state {
+ CG2900_FM_STATE_DEINITIALIZED,
+ CG2900_FM_STATE_INITIALIZED,
+ CG2900_FM_STATE_SWITCHED_ON,
+ CG2900_FM_STATE_STAND_BY
+};
+
+/**
+ * enum cg2900_fm_mode - FM Driver Command state .
+ *
+ * @CG2900_FM_IDLE_MODE: FM Radio is in Idle Mode.
+ * @CG2900_FM_RX_MODE: FM Radio is configured in Rx mode.
+ * @CG2900_FM_TX_MODE: FM Radio is configured in Tx mode.
+ *
+ * Various Modes of the FM Radio.
+ */
+enum cg2900_fm_mode {
+ CG2900_FM_IDLE_MODE,
+ CG2900_FM_RX_MODE,
+ CG2900_FM_TX_MODE
+};
+
+/**
+ * enum cg2900_fm_band - Various Frequency band supported.
+ *
+ * @CG2900_FM_BAND_US_EU: European / US Band.
+ * @CG2900_FM_BAND_JAPAN: Japan Band.
+ * @CG2900_FM_BAND_CHINA: China Band.
+ * @CG2900_FM_BAND_CUSTOM: Custom Band.
+ *
+ * Various Frequency band supported.
+ */
+enum cg2900_fm_band {
+ CG2900_FM_BAND_US_EU,
+ CG2900_FM_BAND_JAPAN,
+ CG2900_FM_BAND_CHINA,
+ CG2900_FM_BAND_CUSTOM
+};
+
+/**
+ * enum cg2900_fm_grid - Various Frequency grids supported.
+ *
+ * @CG2900_FM_GRID_50: 50 kHz spacing.
+ * @CG2900_FM_GRID_100: 100 kHz spacing.
+ * @CG2900_FM_GRID_200: 200 kHz spacing.
+ *
+ * Various Frequency grids supported.
+ */
+enum cg2900_fm_grid {
+ CG2900_FM_GRID_50,
+ CG2900_FM_GRID_100,
+ CG2900_FM_GRID_200
+};
+
+/**
+ * enum cg2900_fm_event - Various Events reported by FM API layer.
+ *
+ * @CG2900_EVENT_NO_EVENT: No Event.
+ * @CG2900_EVENT_SEARCH_CHANNEL_FOUND: Seek operation is completed.
+ * @CG2900_EVENT_SCAN_CHANNELS_FOUND: Band Scan is completed.
+ * @CG2900_EVENT_BLOCK_SCAN_CHANNELS_FOUND: Block Scan is completed.
+ * @CG2900_EVENT_SCAN_CANCELLED: Scan/Seek is cancelled.
+ * @CG2900_EVENT_MONO_STEREO_TRANSITION: Mono/Stereo Transition has taken place.
+ * @CG2900_EVENT_DEVICE_RESET: CG2900 has been reset by some other IP.
+ * @CG2900_EVENT_RDS_EVENT: RDS data interrupt has been received from chip.
+ *
+ * Various Events reported by FM API layer.
+ */
+enum cg2900_fm_event {
+ CG2900_EVENT_NO_EVENT,
+ CG2900_EVENT_SEARCH_CHANNEL_FOUND,
+ CG2900_EVENT_SCAN_CHANNELS_FOUND,
+ CG2900_EVENT_BLOCK_SCAN_CHANNELS_FOUND,
+ CG2900_EVENT_SCAN_CANCELLED,
+ CG2900_EVENT_MONO_STEREO_TRANSITION,
+ CG2900_EVENT_DEVICE_RESET,
+ CG2900_EVENT_RDS_EVENT
+};
+
+/**
+ * enum cg2900_fm_direction - Directions used while seek.
+ *
+ * @CG2900_DIR_DOWN: Search in downwards direction.
+ * @CG2900_DIR_UP: Search in upwards direction.
+ *
+ * Directions used while seek.
+ */
+enum cg2900_fm_direction {
+ CG2900_DIR_DOWN,
+ CG2900_DIR_UP
+};
+
+/**
+ * enum cg2900_fm_stereo_mode - Stereo Modes.
+ *
+ * @CG2900_MODE_MONO: Mono Mode.
+ * @CG2900_MODE_STEREO: Stereo Mode.
+ *
+ * Stereo Modes.
+ */
+enum cg2900_fm_stereo_mode {
+ CG2900_MODE_MONO,
+ CG2900_MODE_STEREO
+};
+
+#define CG2900_FM_DEFAULT_RSSI_THRESHOLD 100
+#define MAX_RDS_BUFFER 10
+#define MAX_RDS_GROUPS 22
+#define MIN_ANALOG_VOLUME 0
+#define MAX_ANALOG_VOLUME 20
+#define NUM_OF_RDS_BLOCKS 4
+#define RDS_BLOCK_MASK 0x1C
+#define RDS_ERROR_STATUS_MASK 0x03
+#define RDS_UPTO_TWO_BITS_CORRECTED 0x01
+#define RDS_UPTO_FIVE_BITS_CORRECTED 0x02
+#define MAX_RT_SIZE 65
+#define MAX_PSN_SIZE 9
+#define DEFAULT_CHANNELS_TO_SCAN 32
+#define MAX_CHANNELS_TO_SCAN 99
+#define MAX_CHANNELS_FOR_BLOCK_SCAN 198
+#define SKB_FM_INTERRUPT_DATA 2
+
+extern u8 fm_event;
+extern struct cg2900_fm_rds_buf fm_rds_buf[MAX_RDS_BUFFER][MAX_RDS_GROUPS];
+extern struct cg2900_fm_rds_info fm_rds_info;
+
+/**
+ * cg2900_fm_init()- Initializes FM Radio.
+ *
+ * Initializes the Variables and structures required for FM Driver.
+ * It also registers the callback to receive the events for command
+ * completion, etc
+ *
+ * Returns:
+ * 0, if Initialization successful
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_init(void);
+
+/**
+ * cg2900_fm_deinit()- De-initializes FM Radio.
+ *
+ * De-initializes the Variables and structures required for FM Driver.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_deinit(void);
+
+/**
+ * cg2900_fm_switch_on()- Start up procedure of the FM radio.
+ *
+ * @device: Character device requesting the operation.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_switch_on(
+ struct device *device
+ );
+
+/**
+ * cg2900_fm_switch_off()- Switches off FM radio
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_switch_off(void);
+
+/**
+ * cg2900_fm_standby()- Makes the FM Radio Go in Standby mode.
+ *
+ * The FM Radio memorizes the the last state, i.e. Volume, last
+ * tuned station, etc that helps in resuming quickly to previous state.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_standby(void);
+
+/**
+ * cg2900_fm_power_up_from_standby()- Power Up FM Radio from Standby mode.
+ *
+ * It retruns the FM radio to the same state as it was before
+ * going to Standby.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_power_up_from_standby(void);
+
+/**
+ * cg2900_fm_set_rx_default_settings()- Loads FM Rx Default Settings.
+ *
+ * @freq: Frequency in Hz to be set on the FM Radio.
+ * @band: Band To be Set.
+ * (0: US/EU, 1: Japan, 2: China, 3: Custom)
+ * @grid: Grid specifying Spacing.
+ * (0: 50 KHz, 1: 100 KHz, 2: 200 Khz)
+ * @enable_rds: Flag indicating enable or disable rds transmission.
+ * @enable_stereo: Flag indicating enable or disable stereo mode.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_rx_default_settings(
+ u32 freq,
+ u8 band,
+ u8 grid,
+ bool enable_rds,
+ bool enable_stereo
+ );
+
+/**
+ * cg2900_fm_set_tx_default_settings()- Loads FM Tx Default Settings.
+ *
+ * @freq: Frequency in Hz to be set on the FM Radio.
+ * @band: Band To be Set.
+ * (0: US/EU, 1: Japan, 2: China, 3: Custom)
+ * @grid: Grid specifying Spacing.
+ * (0: 50 KHz, 1: 100 KHz, 2: 200 Khz)
+ * @enable_rds: Flag indicating enable or disable rds transmission.
+ * @enable_stereo: Flag indicating enable or disable stereo mode.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_tx_default_settings(
+ u32 freq,
+ u8 band,
+ u8 grid,
+ bool enable_rds,
+ bool enable_stereo
+ );
+
+/**
+ * cg2900_fm_set_grid()- Sets the Grid on the FM Radio.
+ *
+ * @grid: Grid specifying Spacing.
+ * (0: 50 KHz,1: 100 KHz,2: 200 Khz)
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_grid(
+ u8 grid
+ );
+
+/**
+ * cg2900_fm_set_band()- Sets the Band on the FM Radio.
+ *
+ * @band: Band specifying Region.
+ * (0: US_EU,1: Japan,2: China,3: Custom)
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_band(
+ u8 band
+ );
+
+/**
+ * cg2900_fm_search_up_freq()- seek Up.
+ *
+ * Searches the next available station in Upward Direction
+ * starting from the Current freq.
+ *
+ * If the operation is started successfully, the chip will generate the
+ * irpt_OperationSucced. interrupt when the operation is completed
+ * and will tune to the next available frequency.
+ * If no station is found, the chip is still tuned to the original station
+ * before starting the search
+ * Till the interrupt is received, no more API's should be called
+ * except cg2900_fm_stop_scan
+ *
+ * Returns:
+ * 0, if operation started successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_search_up_freq(void);
+
+/**
+ * cg2900_fm_search_down_freq()- seek Down.
+ *
+ * Searches the next available station in Downward Direction
+ * starting from the Current freq.
+ *
+ * If the operation is started successfully, the chip will generate
+ * the irpt_OperationSucced. interrupt when the operation is completed.
+ * and will tune to the next available frequency. If no station is found,
+ * the chip is still tuned to the original station before starting the search.
+ * Till the interrupt is received, no more API's should be called
+ * except cg2900_fm_stop_scan.
+ *
+ * Returns:
+ * 0, if operation started successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_search_down_freq(void);
+
+/**
+ * cg2900_fm_start_band_scan()- Band Scan.
+ *
+ * Searches for available Stations in the entire Band starting from
+ * current freq.
+ * If the operation is started successfully, the chip will generate
+ * the irpt_OperationSucced. interrupt when the operation is completed.
+ * After completion the chip will still be tuned the original station before
+ * starting the Scan. on reception of interrupt, the host should call the AP
+ * cg2900_fm_get_scan_result() to retrieve the Stations and corresponding
+ * RSSI of stations found in the Band.
+ * Till the interrupt is received, no more API's should be called
+ * except cg2900_fm_stop_scan, cg2900_fm_switch_off, cg2900_fm_standby and
+ * cg2900_fm_get_frequency.
+ *
+ * Returns:
+ * 0, if operation started successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_start_band_scan(void);
+
+/**
+ * cg2900_fm_stop_scan()- Stops an active ongoing seek or Band Scan.
+ *
+ * If the operation is started successfully, the chip will generate the
+ * irpt_OperationSucced interrupt when the operation is completed.
+ * Till the interrupt is received, no more API's should be called.
+ *
+ * Returns:
+ * 0, if operation started successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_stop_scan(void);
+
+/**
+ * cg2900_fm_get_scan_result()- Retreives Band Scan Result
+ *
+ * Retrieves the Scan Band Results of the stations found and
+ * the corressponding RSSI values of the stations.
+ * @num_of_scanfreq: (out) Number of Stations found
+ * during Scanning.
+ * @scan_freq: (out) Frequency of Stations in Hz
+ * found during Scanning.
+ * @scan_freq_rssi_level: (out) RSSI level of Stations
+ * found during Scanning.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_scan_result(
+ u16 *num_of_scanfreq,
+ u32 *scan_freq,
+ u32 *scan_freq_rssi_level
+ );
+
+/**
+ * cg2900_fm_start_block_scan()- Block Scan.
+ *
+ * Searches for RSSI level of all the channels between the start and stop
+ * channels. If the operation is started successfully, the chip will generate
+ * the irpt_OperationSucced interrupt when the operation is completed.
+ * After completion the chip will still be tuned the original station before
+ * starting the Scan. On reception of interrupt, the host should call the AP
+ * cg2900_fm_get_block_scan_result() to retrieve the RSSI of channels.
+ * Till the interrupt is received, no more API's should be called from Host
+ * except cg2900_fm_stop_scan, cg2900_fm_switch_off, cg2900_fm_standby and
+ * cg2900_fm_get_frequency.
+ * @start_freq: Start channel block scan Frequency.
+ * @end_freq: End channel block scan Frequency
+ *
+ * Returns:
+ * 0, if operation started successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_start_block_scan(
+ u32 start_freq,
+ u32 end_freq
+ );
+
+/**
+ * cg2900_fm_get_scan_result()- Retreives Band Scan Result
+ *
+ * Retrieves the Scan Band Results of the stations found and
+ * the corressponding RSSI values of the stations.
+ * @num_of_scanchan: (out) Number of Stations found
+ * during Scanning.
+ * @scan_freq_rssi_level: (out) RSSI level of Stations
+ * found during Scanning.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_block_scan_result(
+ u16 *num_of_scanchan,
+ u16 *scan_freq_rssi_level
+ );
+
+/**
+ * cg2900_fm_tx_get_rds_deviation()- Gets RDS Deviation.
+ *
+ * Retrieves the RDS Deviation level set for FM Tx.
+ * @deviation: (out) Rds Deviation.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_get_rds_deviation(
+ u16 *deviation
+ );
+
+/**
+ * cg2900_fm_tx_set_rds_deviation()- Sets RDS Deviation.
+ *
+ * Sets the RDS Deviation level on FM Tx.
+ * @deviation: Rds Deviation to set on FM Tx.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_rds_deviation(
+ u16 deviation
+ );
+
+/**
+ * cg2900_fm_tx_set_pi_code()- Sets PI code for RDS Transmission.
+ *
+ * Sets the Program Identification code to be transmitted.
+ * @pi_code: PI code to be transmitted.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_pi_code(
+ u16 pi_code
+ );
+
+/**
+ * cg2900_fm_tx_set_pty_code()- Sets PTY code for RDS Transmission.
+ *
+ * Sets the Program Type code to be transmitted.
+ * @pty_code: PTY code to be transmitted.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_pty_code(
+ u16 pty_code
+ );
+
+/**
+ * cg2900_fm_tx_set_program_station_name()- Sets PSN for RDS Transmission.
+ *
+ * Sets the Program Station Name to be transmitted.
+ * @psn: Program Station Name to be transmitted.
+ * @len: Length of Program Station Name to be transmitted.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_program_station_name(
+ char *psn,
+ u8 len
+ );
+
+/**
+ * cg2900_fm_tx_set_radio_text()- Sets RT for RDS Transmission.
+ *
+ * Sets the radio text to be transmitted.
+ * @rt: Radio Text to be transmitted.
+ * @len: Length of Radio Text to be transmitted.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_radio_text(
+ char *rt,
+ u8 len
+ );
+
+/**
+ * cg2900_fm_tx_get_rds_deviation()- Gets Pilot Tone status
+ *
+ * Gets the current status of pilot tone for FM Tx.
+ * @enable: (out) Flag indicating Pilot Tone is enabled or disabled.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_get_pilot_tone_status(
+ bool *enable
+ );
+
+/**
+ * cg2900_fm_tx_set_pilot_tone_status()- Enables/Disables Pilot Tone.
+ *
+ * Enables or disables the pilot tone for FM Tx.
+ * @enable: Flag indicating enabling or disabling Pilot Tone.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_pilot_tone_status(
+ bool enable
+ );
+
+/**
+ * cg2900_fm_tx_get_pilot_deviation()- Gets Pilot Deviation.
+ *
+ * Retrieves the Pilot Tone Deviation level set for FM Tx.
+ * @deviation: (out) Pilot Tone Deviation.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_get_pilot_deviation(
+ u16 *deviation
+ );
+
+/**
+ * cg2900_fm_tx_set_pilot_deviation()- Sets Pilot Deviation.
+ *
+ * Sets the Pilot Tone Deviation level on FM Tx.
+ * @deviation: Pilot Tone Deviation to set.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_pilot_deviation(
+ u16 deviation
+ );
+
+/**
+ * cg2900_fm_tx_get_preemphasis()- Gets Pre-emhasis level.
+ *
+ * Retrieves the Preemphasis level set for FM Tx.
+ * @preemphasis: (out) Preemphasis level.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_get_preemphasis(
+ u8 *preemphasis
+ );
+
+/**
+ * cg2900_fm_tx_set_preemphasis()- Sets Pre-emhasis level.
+ *
+ * Sets the Preemphasis level on FM Tx.
+ * @preemphasis: Preemphasis level.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_preemphasis(
+ u8 preemphasis
+ );
+
+/**
+ * cg2900_fm_tx_get_power_level()- Gets Power level.
+ *
+ * Retrieves the Power level set for FM Tx.
+ * @power_level: (out) Power level.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_get_power_level(
+ u16 *power_level
+ );
+
+/**
+ * cg2900_fm_tx_set_power_level()- Sets Power level.
+ *
+ * Sets the Power level for FM Tx.
+ * @power_level: Power level.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_set_power_level(
+ u16 power_level
+ );
+
+/**
+ * cg2900_fm_tx_rds()- Enable or disable Tx RDS.
+ *
+ * Enable or disable RDS transmission.
+ * @enable_rds: Flag indicating enabling or disabling RDS.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_tx_rds(
+ bool enable_rds
+ );
+
+/**
+ * cg2900_fm_set_audio_balance()- Sets Audio Balance.
+ *
+ * @balance: Audio Balnce to be Set in Percentage.
+ * (-100: Right Mute.... 0: Both on.... 100: Left Mute)
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_audio_balance(
+ s8 balance
+ );
+
+/**
+ * cg2900_fm_set_volume()- Sets the Analog Out Gain of FM Chip.
+ *
+ * @vol_level: Volume Level to be set on Tuner (0-20).
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_volume(
+ u8 vol_level
+ );
+
+/**
+ * cg2900_fm_get_volume()- Gets the currently set Analog Out Gain of FM Chip.
+ *
+ * @vol_level: (out)Volume Level set on Tuner (0-20).
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_volume(
+ u8 *vol_level
+ );
+
+/**
+ * cg2900_fm_rds_off()- Disables the RDS decoding algorithm in FM chip
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_rds_off(void);
+
+/**
+ * cg2900_fm_rds_on()- Enables the RDS decoding algorithm in FM chip
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_rds_on(void);
+
+/**
+ * cg2900_fm_get_rds_status()- Retrieves the status whether RDS is enabled or not
+ *
+ * @rds_status: (out) Status of RDS
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_rds_status(
+ bool *rds_status
+ );
+
+/**
+ * cg2900_fm_mute()- Mutes the Audio output from FM Chip
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_mute(void);
+
+/**
+ * cg2900_fm_unmute()- Unmutes the Audio output from FM Chip
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_unmute(void);
+
+/**
+ * cg2900_fm_get_frequency()- Gets the Curently tuned Frequency on FM Radio
+ *
+ * @freq: (out) Frequency in Hz set on the FM Radio.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_frequency(
+ u32 *freq
+ );
+
+/**
+ * cg2900_fm_set_frequency()- Sets the frequency on FM Radio
+ *
+ * @new_freq: Frequency in Hz to be set on the FM Radio.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_frequency(
+ u32 new_freq
+ );
+
+/**
+ * cg2900_fm_get_signal_strength()- Gets the RSSI level.
+ *
+ * @signal_strength: (out) RSSI level of the currently
+ * tuned frequency.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_signal_strength(
+ u16 *signal_strength
+ );
+
+/**
+ * cg2900_fm_get_af_updat()- Retrives results of AF Update
+ *
+ * @af_update_rssi: (out) RSSI level of the Alternative frequency.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_af_update_get_result(
+ u16 *af_update_rssi
+ );
+
+
+/**
+ * cg2900_fm_af_update_start()- PErforms AF Update.
+ *
+ * @af_freq: AF frequency in Hz whose RSSI is to be retrived.
+ * tuned frequency.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+
+int cg2900_fm_af_update_start(
+ u32 af_freq
+ );
+
+/**
+ * cg2900_fm_af_switch_get_result()- Retrives the AF switch result.
+ *
+ * @af_switch_conclusion: (out) Conclusion of the AF Switch.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_af_switch_get_result(
+ u16 *af_switch_conclusion
+ );
+
+/**
+ * cg2900_fm_af_switch_start()- PErforms AF switch.
+ *
+ * @af_switch_freq: Alternate Frequency in Hz to be switched.
+ * @af_switch_pi: picode of the Alternative frequency.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_af_switch_start(
+ u32 af_switch_freq,
+ u16 af_switch_pi
+ );
+
+/**
+ * cg2900_fm_get_mode()- Gets the mode of the Radio tuner.
+ *
+ * @cur_mode: (out) Current mode set on FM Radio
+ * (0: Stereo, 1: Mono, 2: Blending, 3: Switching).
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_mode(
+ u8 *cur_mode
+ );
+
+/**
+ * cg2900_fm_set_mode()- Sets the mode on the Radio tuner.
+ *
+ * @mode: mode to be set on FM Radio
+ * (0: Stereo, 1: Mono, 2: Blending, 3: Switching.)
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_mode(
+ u8 mode
+ );
+
+/**
+ * cg2900_fm_select_antenna()- Selects the Antenna of the Radio tuner.
+ *
+ * @antenna: (0: Embedded, 1: Wired.)
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_select_antenna(
+ u8 antenna
+ );
+
+/**
+ * cg2900_fm_get_antenna()- Retreives the currently selected antenna.
+ *
+ * @antenna: out (0: Embedded, 1: Wired.)
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_antenna(
+ u8 *antenna
+ );
+
+/**
+ * cg2900_fm_get_rssi_threshold()- Gets the rssi threshold currently
+ *
+ * set on FM radio.
+ * @rssi_thresold: (out) Current rssi threshold set.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_get_rssi_threshold(
+ u16 *rssi_thresold
+ );
+
+/**
+ * cg2900_fm_set_rssi_threshold()- Sets the rssi threshold to be used during
+ *
+ * Band Scan and seek Stations
+ * @rssi_thresold: rssi threshold to be set.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_rssi_threshold(
+ u16 rssi_thresold
+ );
+
+/**
+ * cg2900_handle_device_reset()- Handle The reset of Device
+ */
+void cg2900_handle_device_reset(void);
+
+/**
+ * wake_up_poll_queue()- Wakes up the Task waiting on Poll Queue.
+ * This function is called when Scan Band or seek has completed.
+ */
+void wake_up_poll_queue(void);
+
+/**
+ * void cg2900_fm_set_chip_version()- Sets the Version of the Controller.
+ *
+ * This function is used to update the Chip Version information at time
+ * of intitialization of FM driver.
+ * @revision: Revision of the controller, e.g. to indicate that it is
+ * a CG2900 controller.
+ * @sub_version: Subversion of the controller, e.g. to indicate a certain
+ * tape-out of the controller.
+ */
+void cg2900_fm_set_chip_version(
+ u16 revision,
+ u16 sub_version
+ );
+
+/**
+ * cg2900_fm_rx_set_deemphasis()- Sets de-emhasis level.
+ *
+ * Sets the Deemphasis level on FM Rx.
+ * @deemphasis: Deemphasis level.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_rx_set_deemphasis(
+ u8 deemphasis
+ );
+
+/**
+ * cg2900_fm_set_test_tone_generator()- Sets the Test Tone Generator.
+ *
+ * This function is used to enable/disable the Internal Tone Generator of
+ * CG2900.
+ * @test_tone_status: Status of tone generator.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_set_test_tone_generator(
+ u8 test_tone_status
+ );
+
+
+/**
+ * cg2900_fm_test_tone_connect()- Connect Audio outputs/inputs.
+ *
+ * This function connects the audio outputs/inputs of the Internal Tone
+ * Generator of CG2900.
+ * @left_audio_mode: Left Audio Output Mode.
+ * @right_audio_mode: Right Audio Output Mode.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_test_tone_connect(
+ u8 left_audio_mode,
+ u8 right_audio_mode
+ );
+
+/**
+ * cg2900_fm_test_tone_set_params()- Sets the Test Tone Parameters.
+ *
+ * This function is used to set the parameters of the Internal Tone Generator of
+ * CG2900.
+ * @tone_gen: Tone to be configured (Tone 1 or Tone 2)
+ * @frequency: Frequency of the tone.
+ * @volume: Volume of the tone.
+ * @phase_offset: Phase offset of the tone.
+ * @dc: DC to add to tone.
+ * @waveform: Waveform to generate, sine or pulse.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int cg2900_fm_test_tone_set_params(
+ u8 tone_gen,
+ u16 frequency,
+ u16 volume,
+ u16 phase_offset,
+ u16 dc,
+ u8 waveform
+ );
+
+#endif /* CG2900_FM_API_H */
diff --git a/drivers/media/radio/CG2900/cg2900_fm_driver.c b/drivers/media/radio/CG2900/cg2900_fm_driver.c
new file mode 100644
index 00000000000..627d0f2d674
--- /dev/null
+++ b/drivers/media/radio/CG2900/cg2900_fm_driver.c
@@ -0,0 +1,5017 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Linux FM Driver for CG2900 FM Chip
+ *
+ * Author: Hemant Gupta <hemant.gupta@stericsson.com> for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include <linux/device.h>
+#include <linux/time.h>
+#include <linux/module.h>
+#include <linux/skbuff.h>
+#include <asm-generic/errno-base.h>
+#include "cg2900.h"
+#include "cg2900_fm_driver.h"
+
+/*
+ * Macro for printing the HCI Packet received from Protocol Driver
+ * to FM Driver.
+ */
+#define CG2900_HEX_READ_PACKET_DUMP \
+ if (cg2900_fm_debug_level == FM_HCI_PACKET_LOGS) \
+ fmd_hexdump('<', skb->data, skb->len);
+
+/* Macro for printing the HCI Packet sent to Protocol Driver from FM Driver */
+#define CG2900_HEX_WRITE_PACKET_DUMP \
+ if (cg2900_fm_debug_level == FM_HCI_PACKET_LOGS) \
+ fmd_hexdump('>', send_buffer, num_bytes);
+
+/* Converts the given value to ASCII format*/
+#define ASCVAL(x)(((x) <= 9) ? (x) + '0' : (x) - 10 + 'a')
+
+/* The receive Packet's 1st byte indicates the packet length */
+#define FM_GET_PKT_LEN(__data) __data[0]
+
+/* The receive Packet's following bytes are the actual data. */
+#define FM_GET_RSP_PKT_ADDR(__data) (&__data[1])
+
+/*
+ * LSB consists of shifting the command Id by 3 bits
+ * to left and ORING with the number of parameters of the
+ * command.
+ */
+#define FM_CMD_GET_LSB(__cmd_id, __num_param) \
+ ((u8)(((__cmd_id << 3) & 0x00FF) | __num_param))
+
+/* MSB consists of shifting the command Id by 5 bits to right. */
+#define FM_CMD_GET_MSB(__cmd_id) \
+ ((u8)(__cmd_id >> 5))
+
+/*
+ * Command id is mapped as shifting the MSB 5 bits to left and
+ * ORING with LSB shifted 3 bits to right.
+ */
+#define FM_GET_CMD_ID(__data) \
+ ((u16)((__data[2] << 5) | __data[1] >> 3))
+
+/*
+ * Number of parameters in the response packet are the last 3 bits
+ * of the 1st byte of the received packet.
+ */
+#define FM_GET_NUM_PARAMS(__data) \
+ ((u16)((__data[1] & 0x07)))
+
+/* Function Id is mapped to the 1st byte of the received packet */
+#define FM_GET_FUNCTION_ID(__data) __data[0]
+
+/*
+ * Block Id of the FM Firmware downloaded is mapped to the
+ * 2nd byte of the received packet.
+ */
+#define FM_GET_BLOCK_ID(__data) __data[1]
+
+/* Status of the received packet is mapped to the 4th byte. */
+#define FM_GET_STATUS(__data) __data[3]
+
+/*
+ * For PG1 of CG2900, the FM Interrupt is mapped
+ * to the 3rd and 4th byte of the received packet.
+ */
+#define FM_GET_PGI_INTERRUPT(__data) \
+ ((u16)(__data[3] << 8 | __data[2]))
+
+/*
+ * For PG2 of CG2900, the FM Interrupt is mapped
+ * to the 5th and 6th byte of the received packet.
+ */
+#define FM_GET_PG2_INTERRUPT(__data) \
+ ((u16)(__data[5] << 8 | __data[4]))
+
+#define FM_GET_NUM_RDS_GRPS(__data) __data[0]
+
+/* Response buffer starts from the 4th byte if the response buffer */
+#define FM_GET_RSP_BUFFER_ADDR(__data) (&__data[3])
+
+/* FM Function buffer starts from the 5th byte if the response buffer */
+#define FM_GET_FUNCTION_ADDR(__data) (&__data[4])
+
+/*
+ * Maximum time for chip to respond including the Command
+ * Competion interrupts for some commands. This time has been
+ * adjusted to cater to increased communication time with chip
+ * when debug level is set to 4.
+ */
+#define MAX_RESPONSE_TIME_IN_MS 5000
+
+/* Byte per word */
+#define WORD_LENGTH 2
+
+/* Byte Offset Counter */
+#define COUNTER 1
+
+/* Binary shift offset for one byte */
+#define SHIFT_OFFSET 8
+
+/*
+ * enum fmd_gocmd_t - FM Driver Command state.
+ *
+ * @FMD_STATE_NONE: FM Driver in Idle state
+ * @FMD_STATE_MODE: FM Driver in setmode state
+ * @FMD_STATE_FREQUENCY: FM Driver in Set frequency state.
+ * @FMD_STATE_PA: FM Driver in SetPA state.
+ * @FMD_STATE_PA_LEVEL: FM Driver in Setpalevl state.
+ * @FMD_STATE_ANTENNA: FM Driver in Setantenna state
+ * @FMD_STATE_MUTE: FM Driver in Setmute state
+ * @FMD_STATE_SEEK: FM Driver in seek mode
+ * @FMD_STATE_SEEK_STOP: FM Driver in seek stop level state.
+ * @FMD_STATE_SCAN_BAND: FM Driver in Scanband mode
+ * @FMD_STATE_TX_SET_CTRL: FM Driver in RDS control state
+ * @FMD_STATE_TX_SET_THRSHLD: FM Driver in RDS threshld state
+ * @FMD_STATE_GEN_POWERUP: FM Driver in Power UP state.
+ * @FMD_STATE_SELECT_REF_CLK: FM Driver in Select Reference clock state.
+ * @FMD_STATE_SET_REF_CLK_PLL: FM Driver in Set Reference Freq state.
+ * @FMD_STATE_BLOCK_SCAN: FM Driver in Block Scan state.
+ * @FMD_STATE_AF_UPDATE: FM Driver in AF Update State.
+ * @FMD_STATE_AF_SWITCH: FM Driver in AF Switch State.
+ * @FMD_STATE_LAST: Last State of FM Driver
+ *
+ * Various states of the FM driver.
+ */
+enum fmd_gocmd {
+ FMD_STATE_NONE,
+ FMD_STATE_MODE,
+ FMD_STATE_FREQUENCY,
+ FMD_STATE_PA,
+ FMD_STATE_PA_LEVEL,
+ FMD_STATE_ANTENNA,
+ FMD_STATE_MUTE,
+ FMD_STATE_SEEK,
+ FMD_STATE_SEEK_STOP,
+ FMD_STATE_SCAN_BAND,
+ FMD_STATE_TX_SET_CTRL,
+ FMD_STATE_TX_SET_THRSHLD,
+ FMD_STATE_GEN_POWERUP,
+ FMD_STATE_SELECT_REF_CLK,
+ FMD_STATE_SET_REF_CLK_PLL,
+ FMD_STATE_BLOCK_SCAN,
+ FMD_STATE_AF_UPDATE,
+ FMD_STATE_AF_SWITCH,
+ FMD_STATE_LAST
+};
+
+/**
+ * struct fmd_rdsgroup_t - Rds group structure.
+ *
+ * @block: Array for RDS Block(s) received.
+ * @status: Array of Status of corresponding RDS block(s).
+ *
+ * It stores the value and status of a particular RDS group
+ * received.
+ */
+struct fmd_rds_group {
+ u16 block[NUM_OF_RDS_BLOCKS];
+ u8 status[NUM_OF_RDS_BLOCKS];
+};
+
+/**
+ * struct fmd_states_info - Main FM state info structure.
+ *
+ * @fmd_initialized: Flag indicating FM Driver is initialized or not
+ * @rx_freq_range: Receiver freq range
+ * @rx_volume: Receiver volume level
+ * @rx_antenna: Receiver Antenna
+ * @rx_seek_stop_level: RDS seek stop Level
+ * @rx_rds_on: Receiver RDS ON
+ * @rx_stereo_mode: Receiver Stereo mode
+ * @max_channels_to_scan: Maximum Number of channels to Scan.
+ * @tx_freq_range: Transmitter freq Range
+ * @tx_preemphasis: Transmitter Pre emphiasis level
+ * @tx_stereo_mode: Transmitter stero mode
+ * @tx_rds_on: Enable RDS
+ * @tx_pilot_dev: PIlot freq deviation
+ * @tx_rds_dev: RDS deviation
+ * @tx_strength: TX Signal Stregnth
+ * @irq_index: Index where last interrupt is added to Interrupt queue
+ * @interrupt_available_for_processing: Flag indicating if interrupt is
+ * available for processing or not.
+ * @interrupt_queue: Circular Queue to store the received interrupt from chip.
+ * @gocmd: Command which is in progress.
+ * @mode: Current Mode of FM Radio.
+ * @rds_group: Array of RDS group Buffer
+ * @callback: Callback registered by upper layers.
+ */
+struct fmd_states_info {
+ bool fmd_initialized;
+ u8 rx_freq_range;
+ u8 rx_volume;
+ u8 rx_antenna;
+ u16 rx_seek_stop_level;
+ bool rx_rds_on;
+ u8 rx_stereo_mode;
+ u8 tx_freq_range;
+ u8 tx_preemphasis;
+ bool tx_stereo_mode;
+ u8 max_channels_to_scan;
+ bool tx_rds_on;
+ u16 tx_pilot_dev;
+ u16 tx_rds_dev;
+ u16 tx_strength;
+ u8 irq_index;
+ bool interrupt_available_for_processing;
+ u16 interrupt_queue[MAX_COUNT_OF_IRQS];
+ enum fmd_gocmd gocmd;
+ enum fmd_mode mode;
+ struct fmd_rds_group rds_group[MAX_RDS_GROUPS];
+ fmd_radio_cb callback;
+};
+
+/**
+ * struct fmd_data - Main structure for FM data exchange.
+ *
+ * @cmd_id: Command Id of the command being exchanged.
+ * @num_parameters: Number of parameters
+ * @parameters: FM data parameters.
+ */
+struct fmd_data {
+ u32 cmd_id;
+ u16 num_parameters;
+ u8 parameters[MAX_RESP_SIZE];
+};
+
+static struct fmd_states_info fmd_state_info;
+static struct fmd_data fmd_data;
+static struct semaphore cmd_sem;
+static struct semaphore rds_sem;
+static struct semaphore interrupt_sem;
+static struct task_struct *rds_thread_task;
+static struct task_struct *irq_thread_task;
+static struct device *cg2900_fm_dev;
+static struct mutex write_mutex;
+static struct mutex send_cmd_mutex;
+static spinlock_t fmd_spinlock;
+static spinlock_t fmd_spinlock_read;
+
+/* Debug Level
+ * 1: Only Error Logs
+ * 2: Info Logs
+ * 3: Debug Logs
+ * 4: HCI Logs
+ */
+unsigned short cg2900_fm_debug_level = FM_ERROR_LOGS;
+EXPORT_SYMBOL(cg2900_fm_debug_level);
+
+static cg2900_fm_rds_cb cb_rds_func;
+static bool rds_thread_required;
+static bool irq_thread_required;
+
+static char event_name[FMD_EVENT_LAST_ELEMENT][MAX_NAME_SIZE] = {
+ "FMD_EVENT_OPERATION_COMPLETED",
+ "FMD_EVENT_ANTENNA_STATUS_CHANGED",
+ "FMD_EVENT_FREQUENCY_CHANGED",
+ "FMD_EVENT_SEEK_COMPLETED",
+ "FMD_EVENT_SCAN_BAND_COMPLETED",
+ "FMD_EVENT_BLOCK_SCAN_COMPLETED",
+ "FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE",
+ "FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE",
+ "FMD_EVENT_SEEK_STOPPED",
+ "FMD_EVENT_GEN_POWERUP",
+ "FMD_EVENT_RDSGROUP_RCVD",
+};
+
+static char interrupt_name[MAX_COUNT_OF_IRQS][MAX_NAME_SIZE] = {
+ "IRPT_OPERATION_SUCCEEDED",
+ "IRPT_OPERATION_FAILED",
+ "IRPT_NOT_DEFINED",
+ "IRPT_RX_BUFFER_FULL_OR_TX_BUFFER_EMPTY",
+ "IRPT_RX_SIGNAL_QUALITY_LOW_OR_TX_MUTE_STATUS_CHANGED",
+ "IRPT_MONO_STEREO_TRANSITION",
+ "IRPT_RX_RDS_SYNC_FOUND_OR_TX_INPUT_OVERDRIVE",
+ "IRPT_RDS_SYNC_LOST",
+ "IRPT_PI_CODE_CHANGED",
+ "IRPT_REQUESTED_BLOCK_AVAILABLE",
+ "IRPT_NOT_DEFINED",
+ "IRPT_NOT_DEFINED",
+ "IRPT_NOT_DEFINED",
+ "IRPT_NOT_DEFINED",
+ "IRPT_WARM_BOOT_READY",
+ "IRPT_COLD_BOOT_READY",
+};
+
+
+static void fmd_hexdump(
+ char prompt,
+ u8 *buffer,
+ int num_bytes
+ );
+static u8 fmd_get_event(
+ enum fmd_gocmd gocmd
+ );
+static void fmd_event_name(
+ u8 event,
+ char *event_name
+ );
+static char *fmd_get_fm_function_name(
+ u8 fm_function
+ );
+static void fmd_interrupt_name(
+ u16 interrupt,
+ char *interrupt_name
+ );
+static void fmd_add_interrupt_to_queue(
+ u16 interrupt
+ );
+static void fmd_process_interrupt(
+ u16 interrupt
+ );
+static void fmd_callback(
+ u8 event,
+ bool event_successful
+ );
+static int fmd_rx_frequency_to_channel(
+ u32 freq,
+ u16 *channel
+ );
+static int fmd_rx_channel_to_frequency(
+ u16 channel_number,
+ u32 *frequency
+ );
+static int fmd_tx_frequency_to_channel(
+ u32 freq,
+ u16 *channel
+ );
+static int fmd_tx_channel_to_frequency(
+ u16 channel_number,
+ u32 *frequency
+ );
+static bool fmd_go_cmd_busy(void);
+static int fmd_send_cmd_and_read_resp(
+ const u16 cmd_id,
+ const u16 num_parameters,
+ const u16 *parameters,
+ u16 *resp_num_parameters,
+ u16 *resp_parameters
+ );
+static int fmd_send_cmd(
+ const u16 cmd_id,
+ const u16 num_parameters,
+ const u16 *parameters
+ );
+static int fmd_read_resp(
+ u16 *cmd_id,
+ u16 *num_parameters,
+ u16 *parameters
+ );
+static void fmd_process_fm_function(
+ u8 *packet_buffer
+ );
+static int fmd_write_file_block(
+ u32 file_block_id,
+ u8 *file_block,
+ u16 file_block_length
+ );
+static void fmd_receive_data(
+ u16 packet_length,
+ u8 *packet_buffer
+ );
+static int fmd_rds_thread(
+ void *data
+ );
+static void fmd_start_irq_thread(void);
+static void fmd_stop_irq_thread(void);
+static int fmd_irq_thread(
+ void *data
+ );
+static int fmd_send_packet(
+ u16 num_bytes,
+ u8 *send_buffer
+ );
+static int fmd_get_cmd_sem(void);
+static void fmd_set_cmd_sem(void);
+static void fmd_get_interrupt_sem(void);
+static void fmd_set_interrupt_sem(void);
+static bool fmd_driver_init(void);
+static void fmd_driver_exit(void);
+
+/* structure declared in time.h */
+struct timespec time_spec;
+
+
+/**
+ * fmd_hexdump() - Displays the HCI Data Bytes exchanged with FM Chip.
+ *
+ * @prompt: Prompt signifying the direction '<' for Rx '>' for Tx
+ * @buffer: Buffer to be displayed.
+ * @num_bytes: Number of bytes of the buffer.
+ */
+ static void fmd_hexdump(
+ char prompt,
+ u8 *buffer,
+ int num_bytes
+ )
+{
+ int i;
+ u8 tmp_val;
+ struct timespec time;
+ static u8 pkt_write[MAX_BUFFER_SIZE], *pkt_ptr;
+
+ getnstimeofday(&time);
+ sprintf(pkt_write, "\n[%08x:%08x] [%04x] %c",
+ (unsigned int)time.tv_sec,
+ (unsigned int)time.tv_nsec,
+ num_bytes, prompt);
+
+ pkt_ptr = pkt_write + strlen(pkt_write);
+ if (buffer == NULL)
+ return;
+
+ /* Copy the buffer only if the input buffer is not NULL */
+ for (i = 0; i < num_bytes; i++) {
+ *pkt_ptr++ = ' ';
+ tmp_val = buffer[i] >> 4;
+ *pkt_ptr++ = ASCVAL(tmp_val);
+ tmp_val = buffer[i] & 0x0F;
+ *pkt_ptr++ = ASCVAL(tmp_val);
+ if (i > 20) {
+ /* Print only 20 bytes at max */
+ break;
+ }
+ }
+ *pkt_ptr++ = '\0';
+ FM_HEX_REPORT("%s", pkt_write);
+}
+
+/**
+ * fmd_get_event() - Returns the Event based on FM Driver State.
+ *
+ * @gocmd: Pending FM Command
+ *
+ * Returns: Corresponding Event
+ */
+static u8 fmd_get_event(
+ enum fmd_gocmd gocmd
+ )
+{
+ u8 event = FMD_EVENT_OPERATION_COMPLETED;
+ switch (gocmd) {
+ case FMD_STATE_ANTENNA:
+ event = FMD_EVENT_ANTENNA_STATUS_CHANGED;
+ break;
+ case FMD_STATE_FREQUENCY:
+ event = FMD_EVENT_FREQUENCY_CHANGED;
+ break;
+ case FMD_STATE_SEEK:
+ event = FMD_EVENT_SEEK_COMPLETED;
+ break;
+ case FMD_STATE_SCAN_BAND:
+ event = FMD_EVENT_SCAN_BAND_COMPLETED;
+ break;
+ case FMD_STATE_BLOCK_SCAN:
+ event = FMD_EVENT_BLOCK_SCAN_COMPLETED;
+ break;
+ case FMD_STATE_AF_UPDATE:
+ /* Drop Down */
+ case FMD_STATE_AF_SWITCH:
+ event = FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE;
+ break;
+ case FMD_STATE_SEEK_STOP:
+ event = FMD_EVENT_SEEK_STOPPED;
+ break;
+ default:
+ event = FMD_EVENT_OPERATION_COMPLETED;
+ break;
+ }
+ return event;
+}
+
+/**
+ * fmd_event_name() - Converts the event to a displayable string.
+ *
+ * @event: Event that has occurred.
+ * @eventname: (out) Buffer to store event name.
+ */
+static void fmd_event_name(
+ u8 event,
+ char *eventname
+ )
+{
+ if (eventname == NULL) {
+ FM_ERR_REPORT("fmd_event_name: Output Buffer is NULL");
+ return;
+ }
+ if (event < FMD_EVENT_LAST_ELEMENT)
+ strcpy(eventname, event_name[event]);
+ else
+ strcpy(eventname, "FMD_EVENT_UNKNOWN");
+}
+
+/**
+ * fmd_get_fm_function_name() - Returns the FM Fucntion name.
+ *
+ * @fm_function: Function whose name is to be retrieved.
+ *
+ * Returns FM Function Name.
+ */
+static char *fmd_get_fm_function_name(
+ u8 fm_function
+ )
+{
+ switch (fm_function) {
+ case FM_FUNCTION_ENABLE:
+ return "FM_FUNCTION_ENABLE";
+ break;
+ case FM_FUNCTION_DISABLE:
+ return "FM_FUNCTION_DISABLE";
+ break;
+ case FM_FUNCTION_RESET:
+ return "FM_FUNCTION_RESET";
+ break;
+ case FM_FUNCTION_WRITE_COMMAND:
+ return "FM_FUNCTION_WRITE_COMMAND";
+ break;
+ case FM_FUNCTION_SET_INT_MASK_ALL:
+ return "FM_FUNCTION_SET_INT_MASK_ALL";
+ break;
+ case FM_FUNCTION_GET_INT_MASK_ALL:
+ return "FM_FUNCTION_GET_INT_MASK_ALL";
+ break;
+ case FM_FUNCTION_SET_INT_MASK:
+ return "FM_FUNCTION_SET_INT_MASK";
+ break;
+ case FM_FUNCTION_GET_INT_MASK:
+ return "FM_FUNCTION_GET_INT_MASK";
+ break;
+ case FM_FUNCTION_FIRMWARE_DOWNLOAD:
+ return "FM_FUNCTION_FIRMWARE_DOWNLOAD";
+ break;
+ default:
+ return "FM_FUNCTION_UNKNOWN";
+ break;
+ }
+}
+
+/**
+ * fmd_interrupt_name() - Converts the interrupt to a displayable string.
+ *
+ * @interrupt: interrupt received from FM Chip
+ * @interruptname: (out) Buffer to store interrupt name.
+ */
+static void fmd_interrupt_name(
+ u16 interrupt,
+ char *interruptname
+ )
+{
+ int index;
+
+ if (interruptname == NULL) {
+ FM_ERR_REPORT("fmd_interrupt_name: Output Buffer is NULL!!!");
+ return;
+ }
+ /* Convert Interrupt to Bit */
+ for (index = 0; index < MAX_COUNT_OF_IRQS; index++) {
+ if (interrupt & (1 << index)) {
+ /* Match found, break the loop */
+ break;
+ }
+ }
+ if (index < MAX_COUNT_OF_IRQS)
+ strcpy(interruptname, interrupt_name[index]);
+ else
+ strcpy(interruptname, "IRPT_UNKNOWN");
+}
+
+/**
+ * fmd_add_interrupt_to_queue() - Add interrupt to IRQ Queue.
+ *
+ * @interrupt: interrupt received from FM Chip
+ */
+static void fmd_add_interrupt_to_queue(
+ u16 interrupt
+ )
+{
+ FM_DEBUG_REPORT("fmd_add_interrupt_to_queue : "
+ "Interrupt Received = %04x", (u16) interrupt);
+
+ /* Reset the index if it reaches the array limit */
+ if (fmd_state_info.irq_index > MAX_COUNT_OF_IRQS - 1) {
+ spin_lock(&fmd_spinlock);
+ fmd_state_info.irq_index = 0;
+ spin_unlock(&fmd_spinlock);
+ }
+
+ spin_lock(&fmd_spinlock);
+ fmd_state_info.interrupt_queue[fmd_state_info.irq_index] = interrupt;
+ fmd_state_info.irq_index++;
+ spin_unlock(&fmd_spinlock);
+ if (!fmd_state_info.interrupt_available_for_processing) {
+ spin_lock(&fmd_spinlock);
+ fmd_state_info.interrupt_available_for_processing = true;
+ spin_unlock(&fmd_spinlock);
+ fmd_set_interrupt_sem();
+ }
+}
+
+/**
+ * fmd_process_interrupt() - Processes the Interrupt.
+ *
+ * This function processes the interrupt received from FM Chip
+ * and calls the corresponding callback registered by upper layers with
+ * proper parameters.
+ * @interrupt: interrupt received from FM Chip
+ */
+static void fmd_process_interrupt(
+ u16 interrupt
+ )
+{
+ char irpt_name[MAX_NAME_SIZE];
+
+ fmd_interrupt_name(interrupt, irpt_name);
+ FM_DEBUG_REPORT("%s", irpt_name);
+ if ((interrupt & IRPT_OPERATION_SUCCEEDED) |
+ (interrupt & IRPT_OPERATION_FAILED)) {
+ bool event_status = (interrupt & IRPT_OPERATION_SUCCEEDED);
+ u8 event = fmd_get_event(fmd_state_info.gocmd);
+
+ switch (fmd_state_info.gocmd) {
+ case FMD_STATE_MODE:
+ /* Mode has been changed. */
+ case FMD_STATE_MUTE:
+ /* FM radio is Muter or Unmuted */
+ case FMD_STATE_PA:
+ /* Power Amplifier has been enabled/disabled */
+ case FMD_STATE_PA_LEVEL:
+ /* Power Amplifier Level has been changed. */
+ case FMD_STATE_SELECT_REF_CLK:
+ /* Reference Clock has been selected. */
+ case FMD_STATE_SET_REF_CLK_PLL:
+ /* Reference Clock frequency has been changed. */
+ case FMD_STATE_TX_SET_CTRL:
+ /* Tx Control has been set. */
+ case FMD_STATE_TX_SET_THRSHLD:
+ /* Tx Threashold has been set. */
+ /* Set State to None and set the waiting semaphore. */
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ fmd_set_cmd_sem();
+ break;
+ case FMD_STATE_ANTENNA:
+ /* Antenna status has been changed. */
+ case FMD_STATE_SEEK_STOP:
+ /* Band scan, seek or block scan has completed. */
+ case FMD_STATE_AF_UPDATE:
+ /* AF Update has completed. */
+ case FMD_STATE_AF_SWITCH:
+ /* AF Switch has completed. */
+ case FMD_STATE_FREQUENCY:
+ /* Frequency has been changed. */
+ /*
+ * Set State to None, set the waiting semaphore,
+ * and inform upper layer.
+ */
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ fmd_set_cmd_sem();
+ fmd_callback(
+ event,
+ event_status);
+ break;
+ case FMD_STATE_SEEK:
+ /* Seek has completed. */
+ case FMD_STATE_SCAN_BAND:
+ /* Band scan has completed. */
+ case FMD_STATE_BLOCK_SCAN:
+ /* Block scan has completed. */
+ /*
+ * Set State to None. No need to set the
+ * semaphore since this is an asyncronous event.
+ */
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ /* Inform Upper layer. */
+ fmd_callback(event, event_status);
+ break;
+ default:
+ /* Do Nothing */
+ FM_ERR_REPORT("Default %s case of "\
+ "interrupt processing", event_status ? \
+ "Success" : "Failed");
+ break;
+ }
+ }
+
+ if (interrupt & IRPT_RX_BUFFERFULL_TX_BUFFEREMPTY) {
+ /*
+ * RDS Buffer Full or RDS Buffer Empty
+ * interrupt received from chip, indicating
+ * that RDS data is available if chip
+ * is in Rx mode or RDS data can be send
+ * to chip in case of Tx mode. Inform the
+ * upper layers about this interrupt.
+ */
+ fmd_callback(
+ FMD_EVENT_RDSGROUP_RCVD,
+ true);
+ }
+
+ if (interrupt & IRPT_RX_MONO_STEREO_TRANSITION) {
+ /*
+ * Mono Stereo Transition interrupt
+ * received from chip, inform the
+ * upper layers about it.
+ */
+ fmd_callback(
+ FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE,
+ true);
+ }
+
+ if ((interrupt & IRPT_COLD_BOOT_READY) |
+ (interrupt & IRPT_WARM_BOOT_READY)) {
+ switch (fmd_state_info.gocmd) {
+ case FMD_STATE_GEN_POWERUP:
+ /*
+ * Cold Boot/ Warm Boot Interrupt received from
+ * chip, indicating transition from
+ * power off/standby state to active state.
+ * Inform the upper layers about it.
+ */
+ fmd_callback(
+ FMD_EVENT_GEN_POWERUP,
+ true);
+ /* Set State to None and set the waiting semaphore. */
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ fmd_set_cmd_sem();
+ break;
+ default:
+ /* Do Nothing */
+ break;
+ }
+ }
+}
+
+/**
+ * fmd_callback() - Callback function for upper layers.
+ *
+ * Callback function that calls the registered callback of upper
+ * layers with proper parameters.
+ * @event: event for which the callback function was called
+ * from FM Driver.
+ * @event_successful: Signifying whether the event is called from FM
+ * Driver on receiving irpt_Operation_Succeeded or irpt_Operation_Failed.
+ */
+static void fmd_callback(
+ u8 event,
+ bool event_successful
+ )
+{
+ char event_name_string[MAX_NAME_SIZE];
+
+ fmd_event_name(event, event_name_string);
+
+ FM_DEBUG_REPORT("%s %x, %d", event_name_string,
+ (unsigned int)event , (unsigned int)event_successful);
+
+ if (fmd_state_info.callback)
+ fmd_state_info.callback(
+ event,
+ event_successful);
+}
+
+/**
+ * fmd_rx_frequency_to_channel() - Converts Rx frequency to channel number.
+ *
+ * Converts the Frequency in kHz to corresponding Channel number.
+ * This is used for FM Rx.
+ * @freq: Frequency in kHz.
+ * @channel: Channel Number corresponding to the given Frequency.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ *
+ */
+static int fmd_rx_frequency_to_channel(
+ u32 freq,
+ u16 *channel
+ )
+{
+ u8 range;
+ int result;
+ u32 min_freq;
+ u32 max_freq;
+
+ if (channel == NULL) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_get_freq_range(
+ &range);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_get_freq_range_properties(
+ range,
+ &min_freq,
+ &max_freq);
+
+ if (result != 0)
+ goto error;
+
+ if (freq > max_freq)
+ freq = max_freq;
+ else if (freq < min_freq)
+ freq = min_freq;
+
+ /*
+ * Frequency in kHz needs to be divided with 50 kHz to get
+ * channel number for all FM Bands
+ */
+ *channel = (u16)((freq - min_freq) / CHANNEL_FREQ_CONVERTER_MHZ);
+ result = 0;
+error:
+ return result;
+}
+
+/**
+ * fmd_rx_channel_to_frequency() - Converts Rx Channel number to frequency.
+ *
+ * Converts the Channel Number to corresponding Frequency in kHz.
+ * This is used for FM Rx.
+ * @channel_number: Channel Number to be converted.
+ * @frequency: Frequency corresponding to the corresponding channel in kHz.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ *
+ */
+static int fmd_rx_channel_to_frequency(
+ u16 channel_number,
+ u32 *frequency
+ )
+{
+ u8 range;
+ int result;
+ u32 min_freq;
+ u32 max_freq;
+
+ if (frequency == NULL) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_get_freq_range(
+ &range);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_get_freq_range_properties(
+ range,
+ &min_freq,
+ &max_freq);
+
+ if (result != 0)
+ goto error;
+
+ /*
+ * Channel Number needs to be multiplied with 50 kHz to get
+ * frequency in kHz for all FM Bands
+ */
+ *frequency = min_freq + (channel_number * CHANNEL_FREQ_CONVERTER_MHZ);
+
+error:
+ return result;
+}
+
+/**
+ * fmd_tx_frequency_to_channel() - Converts Tx frequency to channel number.
+ *
+ * Converts the Frequency in kHz to corresponding Channel number.
+ * This is used for FM Tx.
+ * @freq: Frequency in kHz.
+ * @channel: (out)Channel Number corresponding to the given Frequency.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ */
+static int fmd_tx_frequency_to_channel(
+ u32 freq,
+ u16 *channel
+ )
+{
+ u8 range;
+ int result;
+ u32 min_freq;
+ u32 max_freq;
+
+ if (channel == NULL) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_tx_get_freq_range(
+ &range);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_get_freq_range_properties(
+ range,
+ &min_freq,
+ &max_freq);
+
+ if (result != 0)
+ goto error;
+
+ if (freq > max_freq)
+ freq = max_freq;
+ else if (freq < min_freq)
+ freq = min_freq;
+
+ /*
+ * Frequency in kHz needs to be divided with 50 kHz to get
+ * channel number for all FM Bands
+ */
+ *channel = (u16)((freq - min_freq) / CHANNEL_FREQ_CONVERTER_MHZ);
+ result = 0;
+error:
+ return result;
+}
+
+/**
+ * fmd_tx_channel_to_frequency() - Converts Tx Channel number to frequency.
+ *
+ * Converts the Channel Number to corresponding Frequency in kHz.
+ * This is used for FM Tx.
+ * @channel_number: Channel Number to be converted.
+ * @frequency: Frequency corresponding to the corresponding channel
+ * in kHz.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ */
+static int fmd_tx_channel_to_frequency(
+ u16 channel_number,
+ u32 *frequency
+ )
+{
+ u8 range;
+ int result;
+ u32 min_freq;
+ u32 max_freq;
+
+ if (frequency == NULL) {
+ result = -EINVAL;
+ goto error;
+ }
+
+ result = fmd_tx_get_freq_range(
+ &range);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_get_freq_range_properties(
+ range,
+ &min_freq,
+ &max_freq);
+
+ if (result != 0)
+ goto error;
+
+ /*
+ * Channel Number needs to be multiplied with 50 kHz to get
+ * frequency in kHz for all FM Bands
+ */
+ *frequency = min_freq + (channel_number * CHANNEL_FREQ_CONVERTER_MHZ);
+
+ if (*frequency > max_freq)
+ *frequency = max_freq;
+ else if (*frequency < min_freq)
+ *frequency = min_freq;
+
+ result = 0;
+error:
+ return result;
+}
+
+/**
+ * fmd_go_cmd_busy() - Function to check if FM Driver is busy or idle
+ *
+ * Returns:
+ * false if FM Driver is Idle
+ * true otherwise
+ */
+static bool fmd_go_cmd_busy(void)
+{
+ return (fmd_state_info.gocmd != FMD_STATE_NONE);
+}
+
+/**
+ * fmd_read_cb() - Handle Received Data
+ *
+ * This function handles data received from connectivity protocol driver.
+ * @dev: Device receiving data.
+ * @skb: Buffer with data coming form device.
+ */
+static void fmd_read_cb(
+ struct cg2900_user_data *dev,
+ struct sk_buff *skb
+ )
+{
+ FM_INFO_REPORT("fmd_read_cb");
+
+ if (skb->data == NULL || skb->len == 0)
+ return;
+
+ spin_lock(&fmd_spinlock_read);
+ CG2900_HEX_READ_PACKET_DUMP;
+ /*
+ * The first byte is length of bytes following bytes
+ * Rest of the bytes are the actual data
+ */
+ fmd_receive_data(
+ FM_GET_PKT_LEN(skb->data),
+ FM_GET_RSP_PKT_ADDR(skb->data));
+
+ kfree_skb(skb);
+ spin_unlock(&fmd_spinlock_read);
+}
+
+/**
+ * fmd_receive_data() - Processes the FM data received from device.
+ *
+ * @packet_length: Length of received Data Packet
+ * @packet_buffer: Received Data buffer.
+ */
+static void fmd_receive_data(
+ u16 packet_length,
+ u8 *packet_buffer
+ )
+{
+ if (packet_buffer == NULL) {
+ FM_ERR_REPORT("fmd_receive_data: Buffer = NULL");
+ return;
+ }
+
+ if (packet_length == FM_PG1_INTERRUPT_EVENT_LEN &&
+ packet_buffer[0] == FM_CATENA_OPCODE &&
+ packet_buffer[1] == FM_EVENT_ID) {
+ /* PG 1.0 interrupt Handling */
+ u16 interrupt = FM_GET_PGI_INTERRUPT(packet_buffer);
+ FM_DEBUG_REPORT("interrupt = %04x",
+ (unsigned int)interrupt);
+ fmd_add_interrupt_to_queue(interrupt);
+ } else if (packet_length == FM_PG2_INTERRUPT_EVENT_LEN &&
+ packet_buffer[0] == FM_SUCCESS_STATUS &&
+ packet_buffer[1] == FM_CATENA_OPCODE &&
+ packet_buffer[2] == FM_EVENT &&
+ packet_buffer[3] == FM_EVENT_ID) {
+ /* PG 2.0 interrupt Handling */
+ u16 interrupt = FM_GET_PG2_INTERRUPT(packet_buffer);
+ FM_DEBUG_REPORT("interrupt = %04x",
+ (unsigned int)interrupt);
+ fmd_add_interrupt_to_queue(interrupt);
+ } else if (packet_buffer[0] == FM_SUCCESS_STATUS &&
+ packet_buffer[1] == FM_CATENA_OPCODE &&
+ packet_buffer[2] == FM_WRITE) {
+ /* Command Complete or RDS Data Handling */
+ u8 fm_status = FM_GET_STATUS(packet_buffer);;
+ switch (fm_status) {
+ case FM_CMD_STATUS_CMD_SUCCESS:
+ fmd_process_fm_function(
+ FM_GET_FUNCTION_ADDR(packet_buffer));
+ break;
+ case FM_CMD_STATUS_HCI_ERR_HW_FAILURE:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_HCI_ERR_HW_FAILURE");
+ break;
+ case FM_CMD_STATUS_HCI_ERR_INVALID_PARAMETERS:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_HCI_ERR_INVALID_PARAMETERS");
+ break;
+ case FM_CMD_STATUS_IP_UNINIT:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_IP_UNINIT");
+ break;
+ case FM_CMD_STATUS_HCI_ERR_UNSPECIFIED_ERROR:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_HCI_ERR_UNSPECIFIED_ERROR");
+ break;
+ case FM_CMD_STATUS_HCI_ERR_CMD_DISALLOWED:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_HCI_ERR_CMD_DISALLOWED");
+ break;
+ case FM_CMD_STATUS_WRONG_SEQ_NUM:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_WRONG_SEQ_NUM");
+ break;
+ case FM_CMD_STATUS_UNKNOWN_FILE_TYPE:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_UNKNOWN_FILE_TYPE");
+ break;
+ case FM_CMD_STATUS_FILE_VERSION_MISMATCH:
+ FM_DEBUG_REPORT(
+ "FM_CMD_STATUS_FILE_VERSION_MISMATCH");
+ break;
+ default:
+ FM_DEBUG_REPORT(
+ "Unknown Status = %02x", fm_status);
+ break;
+ }
+ }
+}
+
+/**
+ * fmd_reset_cb() - Reset callback fuction.
+ *
+ * @dev: CPD device reseting.
+ */
+static void fmd_reset_cb(struct cg2900_user_data *dev)
+{
+ FM_INFO_REPORT("fmd_reset_cb: Device Reset");
+ spin_lock(&fmd_spinlock_read);
+ cg2900_handle_device_reset();
+ spin_unlock(&fmd_spinlock_read);
+}
+
+/**
+ * fmd_rds_thread() - Thread for receiving RDS data from Chip.
+ *
+ * @data: Data beng passed as parameter on starting the thread.
+ */
+static int fmd_rds_thread(
+ void *data
+ )
+{
+ FM_INFO_REPORT("fmd_rds_thread Created Successfully");
+ while (rds_thread_required) {
+ if (cb_rds_func)
+ cb_rds_func();
+ /* Give 100 ms for context switching */
+ schedule_timeout_interruptible(msecs_to_jiffies(100));
+ }
+ /* Always signal the rds_sem semaphore before exiting */
+ fmd_set_rds_sem();
+ FM_DEBUG_REPORT("fmd_rds_thread Exiting!!!");
+ return 0;
+}
+
+/**
+ * fmd_start_irq_thread() - Function for starting Interrupt Thread.
+ */
+static void fmd_start_irq_thread(void)
+{
+ FM_INFO_REPORT("fmd_start_irq_thread");
+ irq_thread_task = kthread_create(fmd_irq_thread, NULL, "irq_thread");
+ if (IS_ERR(irq_thread_task)) {
+ FM_ERR_REPORT("fmd_start_irq_thread: "
+ "Unable to Create irq_thread");
+ irq_thread_task = NULL;
+ return;
+ }
+ wake_up_process(irq_thread_task);
+}
+
+/**
+ * fmd_stop_irq_thread() - Function for stopping Interrupt Thread.
+ */
+static void fmd_stop_irq_thread(void)
+{
+ FM_INFO_REPORT("fmd_stop_irq_thread");
+ kthread_stop(irq_thread_task);
+ irq_thread_task = NULL;
+ FM_DEBUG_REPORT("-fmd_stop_irq_thread");
+}
+
+/**
+ * fmd_irq_thread() - Thread for processing Interrupts received from Chip.
+ *
+ * @data: Data being passed as parameter on starting the thread.
+ */
+
+static int fmd_irq_thread(
+ void *data
+ )
+{
+ int index;
+
+ FM_INFO_REPORT("fmd_irq_thread Created Successfully");
+
+ while (irq_thread_required) {
+ if (!fmd_state_info.interrupt_available_for_processing) {
+ FM_DEBUG_REPORT("fmd_irq_thread: Waiting on irq sem "
+ "interrupt_available_for_processing = %d "
+ "fmd_state_info.fmd_initialized = %d",
+ fmd_state_info.interrupt_available_for_processing,
+ fmd_state_info.fmd_initialized);
+ fmd_get_interrupt_sem();
+ FM_DEBUG_REPORT("fmd_irq_thread: Waiting on irq sem "
+ "interrupt_available_for_processing = %d "
+ "fmd_state_info.fmd_initialized = %d",
+ fmd_state_info.interrupt_available_for_processing,
+ fmd_state_info.fmd_initialized);
+ }
+ index = 0;
+
+ if (fmd_state_info.interrupt_available_for_processing) {
+ while (index < MAX_COUNT_OF_IRQS) {
+ if (fmd_state_info.interrupt_queue[index]
+ != IRPT_INVALID) {
+ FM_DEBUG_REPORT("fmd_process_interrupt "
+ "Interrupt = %04x",
+ fmd_state_info.
+ interrupt_queue[index]);
+ fmd_process_interrupt(
+ fmd_state_info.interrupt_queue[index]);
+ fmd_state_info.interrupt_queue[index]
+ = IRPT_INVALID;
+ }
+ index++;
+ }
+ }
+ fmd_state_info.interrupt_available_for_processing = false;
+ schedule_timeout_interruptible(msecs_to_jiffies(100));
+ }
+ FM_DEBUG_REPORT("fmd_irq_thread Exiting!!!");
+ return 0;
+}
+
+/**
+ * fmd_send_packet() - Sends the FM HCI Packet to the CG2900 Protocol Driver.
+ *
+ * @num_bytes: Number of bytes of Data to be sent including
+ * Channel Identifier (08)
+ * @send_buffer: Buffer containing the Data to be sent to Chip.
+ *
+ * Returns:
+ * 0, If packet was sent successfully to
+ * CG2900 Protocol Driver, otherwise the corresponding error.
+ * -EINVAL If parameters are not valid.
+ * -EIO If there is an Input/Output Error.
+ */
+static int fmd_send_packet(
+ u16 num_bytes,
+ u8 *send_buffer
+ )
+{
+ int err;
+ struct sk_buff *skb;
+ struct cg2900_user_data *pf_data;
+
+ FM_INFO_REPORT("fmd_send_packet");
+
+ if (send_buffer == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (!cg2900_fm_dev) {
+ FM_ERR_REPORT("fmd_send_packet: No FM device registered");
+ err = -EIO;
+ goto error;
+ }
+
+ pf_data = dev_get_platdata(cg2900_fm_dev);
+ if (!pf_data->opened) {
+ FM_ERR_REPORT("fmd_send_packet: FM channel is not opened");
+ err = -EIO;
+ goto error;
+ }
+
+ mutex_lock(&write_mutex);
+ CG2900_HEX_WRITE_PACKET_DUMP;
+
+ skb = pf_data->alloc_skb(num_bytes, GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("fmd_send_packet:Couldn't " \
+ "allocate sk_buff with length %d", num_bytes);
+ err = -EIO;
+ goto error;
+ }
+
+ /*
+ * Copy the buffer removing the FM Header as this
+ * would be done by Protocol Driver
+ */
+ memcpy(skb_put(skb, num_bytes), send_buffer, num_bytes);
+
+ err = pf_data->write(pf_data, skb);
+ if (err) {
+ FM_ERR_REPORT("fmd_send_packet: "
+ "Failed to send(%d) bytes using "
+ "cg2900_write, err = %d",
+ num_bytes, err);
+ kfree(skb);
+ err = -EIO;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ mutex_unlock(&write_mutex);
+ FM_DEBUG_REPORT("fmd_send_packet returning %d", err);
+ return err;
+}
+
+/**
+ * fmd_get_cmd_sem() - Block on Command Semaphore.
+ *
+ * This is required to ensure Flow Control in FM Driver.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ETIME if timeout occurs.
+ */
+static int fmd_get_cmd_sem(void)
+{
+ int ret_val;
+
+ FM_INFO_REPORT("fmd_get_cmd_sem");
+
+ ret_val = down_timeout(&cmd_sem,
+ msecs_to_jiffies(MAX_RESPONSE_TIME_IN_MS));
+
+ if (ret_val)
+ FM_ERR_REPORT("fmd_get_cmd_sem: down_timeout "
+ "returned error = %d", ret_val);
+
+ return ret_val;
+}
+
+/**
+ * fmd_set_cmd_sem() - Unblock on Command Semaphore.
+ *
+ * This is required to ensure Flow Control in FM Driver.
+ */
+static void fmd_set_cmd_sem(void)
+{
+ FM_DEBUG_REPORT("fmd_set_cmd_sem");
+
+ up(&cmd_sem);
+}
+
+/**
+ * fmd_get_interrupt_sem() - Block on Interrupt Semaphore.
+ *
+ * Till Interrupt is received, Interrupt Task is blocked.
+ */
+static void fmd_get_interrupt_sem(void)
+{
+ int ret_val;
+
+ FM_DEBUG_REPORT("fmd_get_interrupt_sem");
+
+ ret_val = down_killable(&interrupt_sem);
+
+ if (ret_val)
+ FM_ERR_REPORT("fmd_get_interrupt_sem: down_killable "
+ "returned error = %d", ret_val);
+}
+
+/**
+ * fmd_set_interrupt_sem() - Unblock on Interrupt Semaphore.
+ *
+ * on receiving Interrupt, Interrupt Task is un-blocked.
+ */
+static void fmd_set_interrupt_sem(void)
+{
+ FM_DEBUG_REPORT("fmd_set_interrupt_sem");
+ up(&interrupt_sem);
+}
+
+/**
+ * fmd_driver_init()- Initializes the Mutex, Semaphore, etc for FM Driver.
+ *
+ * It also registers FM Driver with the Protocol Driver.
+ *
+ * Returns:
+ * true if initialization is successful
+ * false if initiialization fails.
+ */
+static bool fmd_driver_init(void)
+{
+ bool ret_val;
+ struct cg2900_rev_data rev_data;
+ struct cg2900_user_data *pf_data;
+ int err;
+
+ FM_INFO_REPORT("fmd_driver_init");
+
+ if (!cg2900_fm_dev) {
+ FM_ERR_REPORT("No device registered");
+ ret_val = false;
+ goto error;
+ }
+
+ /* Initialize the semaphores */
+ sema_init(&cmd_sem, 0);
+ sema_init(&rds_sem, 0);
+ sema_init(&interrupt_sem, 0);
+ cb_rds_func = NULL;
+ rds_thread_required = false;
+ irq_thread_required = true;
+
+ pf_data = dev_get_platdata(cg2900_fm_dev);
+
+ /* Create Mutex For Reading and Writing */
+ spin_lock_init(&fmd_spinlock_read);
+ mutex_init(&write_mutex);
+ mutex_init(&send_cmd_mutex);
+ spin_lock_init(&fmd_spinlock);
+ fmd_start_irq_thread();
+
+ /* Open the FM channel */
+ err = pf_data->open(pf_data);
+ if (err) {
+ FM_ERR_REPORT("fmd_driver_init: "
+ "Couldn't open FM channel. Either chip is not connected"
+ " or Protocol Driver is not initialized");
+ ret_val = false;
+ goto error;
+ }
+
+ if (!pf_data->get_local_revision(pf_data, &rev_data)) {
+ FM_DEBUG_REPORT("No revision data available");
+ ret_val = false;
+ goto error;
+ }
+
+ FM_DEBUG_REPORT("Read revision data revision %04x "
+ "sub_version %04x",
+ rev_data.revision, rev_data.sub_version);
+ cg2900_fm_set_chip_version(rev_data.revision, rev_data.sub_version);
+ ret_val = true;
+
+error:
+ FM_DEBUG_REPORT("fmd_driver_init: Returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * fmd_driver_exit() - Deinitializes the mutex, semaphores, etc.
+ *
+ * It also deregisters FM Driver with the Protocol Driver.
+ *
+ */
+static void fmd_driver_exit(void)
+{
+ struct cg2900_user_data *pf_data;
+
+ FM_INFO_REPORT("fmd_driver_exit");
+ irq_thread_required = false;
+ mutex_destroy(&write_mutex);
+ mutex_destroy(&send_cmd_mutex);
+ fmd_stop_irq_thread();
+ /* Close the FM channel */
+ pf_data = dev_get_platdata(cg2900_fm_dev);
+ if (pf_data->opened)
+ pf_data->close(pf_data);
+}
+
+/**
+ * fmd_send_cmd_and_read_resp() - Send command and read response.
+ *
+ * This function sends the HCI Command to Protocol Driver and
+ * Reads back the Response Packet.
+ * @cmd_id: Command Id to be sent to FM Chip.
+ * @num_parameters: Number of parameters of the command sent.
+ * @parameters: Buffer containing the Buffer to be sent.
+ * @resp_num_parameters: (out) Number of paramters of the response packet.
+ * @resp_parameters: (out) Buffer of the response packet.
+ *
+ * Returns:
+ * 0: If the command is sent successfully and the
+ * response received is also correct.
+ * -EINVAL: If the received response is not correct.
+ * -EIO: If there is an input/output error.
+ * -EINVAL: If parameters are not valid.
+ */
+static int fmd_send_cmd_and_read_resp(
+ const u16 cmd_id,
+ const u16 num_parameters,
+ const u16 *parameters,
+ u16 *resp_num_parameters,
+ u16 *resp_parameters
+ )
+{
+ int result;
+ u16 read_cmd_id = CMD_ID_NONE;
+
+ FM_INFO_REPORT("fmd_send_cmd_and_read_resp");
+
+ mutex_lock(&send_cmd_mutex);
+ result = fmd_send_cmd(
+ cmd_id,
+ num_parameters,
+ parameters);
+
+ if (result != 0)
+ goto error;
+
+ result = fmd_read_resp(
+ &read_cmd_id,
+ resp_num_parameters,
+ resp_parameters);
+
+ if (result != 0)
+ goto error;
+
+ /*
+ * Check that the response belongs to the sent command
+ */
+ if (read_cmd_id != cmd_id)
+ result = -EINVAL;
+
+error:
+ mutex_unlock(&send_cmd_mutex);
+ FM_DEBUG_REPORT("fmd_send_cmd_and_read_resp: "
+ "returning %d", result);
+ return result;
+}
+
+/**
+ * fmd_send_cmd() - This function sends the HCI Command
+ * to Protocol Driver.
+ *
+ * @cmd_id: Command Id to be sent to FM Chip.
+ * @num_parameters: Number of parameters of the command sent.
+ * @parameters: Buffer containing the Buffer to be sent.
+ *
+ * Returns:
+ * 0: If the command is sent successfully to Lower Layers.
+ * -EIO: If there is an input/output error.
+ * -EINVAL: If parameters are not valid.
+ */
+static int fmd_send_cmd(
+ const u16 cmd_id ,
+ const u16 num_parameters,
+ const u16 *parameters
+ )
+{
+ /*
+ * Total Length includes 6 bytes HCI Header
+ * and remaining bytes depending on number of paramters.
+ */
+ u16 total_length = num_parameters * sizeof(u16) + FM_HCI_CMD_HEADER_LEN;
+ /*
+ * Parameter Length includes 5 bytes HCI Header
+ * and remaining bytes depending on number of paramters.
+ */
+ u16 param_length = num_parameters * sizeof(u16) + FM_HCI_CMD_PARAM_LEN;
+ u8 *fm_data = kmalloc(total_length, GFP_KERNEL);
+ int err = -EINVAL;
+
+ FM_INFO_REPORT("fmd_send_cmd");
+
+ if (fm_data == NULL) {
+ err = -EIO;
+ goto error;
+ }
+
+ if (num_parameters && parameters == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ /* HCI encapsulation */
+ fm_data[0] = param_length;
+ fm_data[1] = FM_CATENA_OPCODE;
+ fm_data[2] = FM_WRITE;
+ fm_data[3] = FM_FUNCTION_WRITE_COMMAND;
+ fm_data[4] = FM_CMD_GET_LSB(cmd_id, num_parameters);
+ fm_data[5] = FM_CMD_GET_MSB(cmd_id);
+
+ memcpy(
+ (fm_data + FM_HCI_CMD_HEADER_LEN),
+ (void *)parameters,
+ num_parameters * sizeof(u16));
+
+ /* Send the Packet */
+ err = fmd_send_packet(total_length , fm_data);
+
+error:
+ kfree(fm_data);
+ FM_DEBUG_REPORT("fmd_send_cmd: "
+ "returning %d", err);
+ return err;
+}
+
+/**
+ * fmd_read_resp() - This function reads the response packet of the previous
+ * command sent to FM Chip and copies it to the buffer provided as parameter.
+ *
+ * @cmd_id: (out) Command Id received from FM Chip.
+ * @num_parameters: (out) Number of paramters of the response packet.
+ * @parameters: (out) Buffer of the response packet.
+ *
+ * Returns:
+ * 0: If the response buffer is copied successfully.
+ * -EINVAL: If parameters are not valid.
+ * -ETIME: Otherwise
+ */
+static int fmd_read_resp(
+ u16 *cmd_id,
+ u16 *num_parameters,
+ u16 *parameters
+ )
+{
+ int err;
+ int param_offset = 0;
+ int byte_offset = 0;
+ FM_INFO_REPORT("fmd_read_resp");
+
+ /* Wait till response of the command is received */
+ if (fmd_get_cmd_sem()) {
+ err = -ETIME;
+ goto error;
+ }
+
+ /* Check if the parameters are valid */
+ if (cmd_id == NULL || (fmd_data.num_parameters &&
+ (num_parameters == NULL || parameters == NULL))) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ /* Fill the arguments */
+ *cmd_id = fmd_data.cmd_id;
+ if (fmd_data.num_parameters) {
+ *num_parameters = fmd_data.num_parameters;
+ while (param_offset <
+ (*num_parameters * sizeof(u16)) / WORD_LENGTH) {
+ parameters[param_offset] =
+ (u16)(fmd_data.parameters[byte_offset])
+ & 0x00ff;
+ parameters[param_offset] |=
+ ((u16)(fmd_data.parameters[byte_offset + COUNTER])
+ & 0x00ff) << SHIFT_OFFSET;
+ byte_offset = byte_offset + WORD_LENGTH;
+ param_offset++;
+ }
+ }
+
+ err = 0;
+
+error:
+ FM_DEBUG_REPORT("fmd_read_resp: "
+ "returning %d", err);
+ return err;
+}
+
+/**
+ * fmd_process_fm_function() - Process FM Function.
+ *
+ * This function processes the Response buffer received
+ * from lower layers for the FM function and performs the necessary action to
+ * parse the same.
+ * @packet_buffer: Received Buffer.
+ */
+static void fmd_process_fm_function(
+ u8 *packet_buffer
+ )
+{
+ u8 fm_function_id;
+ u8 block_id;
+ int count = 0;
+
+ if (packet_buffer == NULL)
+ return;
+
+ fm_function_id = FM_GET_FUNCTION_ID(packet_buffer);
+ switch (fm_function_id) {
+ case FM_FUNCTION_ENABLE:
+ case FM_FUNCTION_DISABLE:
+ case FM_FUNCTION_RESET:
+ FM_DEBUG_REPORT(
+ "fmd_process_fm_function: "
+ "command success received for %s",
+ fmd_get_fm_function_name(fm_function_id));
+ /* Release the semaphore since response is received */
+ fmd_set_cmd_sem();
+ break;
+ case FM_FUNCTION_WRITE_COMMAND:
+ FM_DEBUG_REPORT(
+ "fmd_process_fm_function: "
+ "command success received for %s",
+ fmd_get_fm_function_name(fm_function_id));
+
+ fmd_data.cmd_id = FM_GET_CMD_ID(packet_buffer);
+ fmd_data.num_parameters =
+ FM_GET_NUM_PARAMS(packet_buffer);
+
+ FM_DEBUG_REPORT(
+ "fmd_process_fm_function: "
+ "Cmd Id = 0x%04x, Num Of Parms = %02x",
+ fmd_data.cmd_id, fmd_data.num_parameters);
+
+ if (fmd_data.num_parameters) {
+ while (count <
+ (fmd_data.num_parameters * sizeof(u16))) {
+ fmd_data.parameters[count] =
+ *(FM_GET_RSP_BUFFER_ADDR(packet_buffer) + count);
+ count++;
+ }
+ }
+
+ /* Release the semaphore since response is received */
+ fmd_set_cmd_sem();
+ break;
+ case FM_FUNCTION_FIRMWARE_DOWNLOAD:
+ block_id = FM_GET_BLOCK_ID(packet_buffer);
+ FM_DEBUG_REPORT(
+ "fmd_process_fm_function: "
+ "command success received for %s"
+ "block id = %02x",
+ fmd_get_fm_function_name(fm_function_id),
+ block_id);
+ /* Release the semaphore since response is received */
+ fmd_set_cmd_sem();
+ break;
+ default:
+ FM_ERR_REPORT(
+ "fmd_process_fm_function: "
+ "default case: command success received for %s",
+ fmd_get_fm_function_name(fm_function_id));
+ break;
+ }
+}
+
+/**
+ * fmd_write_file_block() - download firmware.
+ *
+ * This Function adds the header for downloading
+ * the firmware and coeffecient files and sends it to Protocol Driver.
+ * @file_block_id: Block ID of the F/W to be transmitted to FM Chip
+ * @file_block: Buffer containing the bytes to be sent.
+ * @file_block_length: Size of the Firmware buffer.
+ *
+ * Returns:
+ * 0: If there is no error.
+ * -EINVAL: If parameters are not valid.
+ * -ETIME: Otherwise
+ */
+static int fmd_write_file_block(
+ u32 file_block_id,
+ u8 *file_block,
+ u16 file_block_length
+ )
+{
+ int err;
+
+ FM_INFO_REPORT("fmd_write_file_block");
+ if (file_block == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ mutex_lock(&send_cmd_mutex);
+ file_block[0] = file_block_length + FM_HCI_WRITE_FILE_BLK_PARAM_LEN;
+ file_block[1] = FM_CATENA_OPCODE;
+ file_block[2] = FM_WRITE;
+ file_block[3] = FM_FUNCTION_FIRMWARE_DOWNLOAD;
+ file_block[4] = file_block_id;
+ /* Send the Packet */
+ err = fmd_send_packet(
+ file_block_length +
+ FM_HCI_WRITE_FILE_BLK_HEADER_LEN,
+ file_block);
+
+ /* wait till response comes */
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+
+error:
+ mutex_unlock(&send_cmd_mutex);
+ FM_DEBUG_REPORT("fmd_write_file_block: "
+ "returning %d", err);
+ return err;
+}
+
+int fmd_init(void)
+{
+ int err;
+
+ if (!fmd_driver_init()) {
+ err = -EIO;
+ goto error;
+ }
+
+ memset(&fmd_state_info, 0, sizeof(fmd_state_info));
+ fmd_state_info.fmd_initialized = true;
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ fmd_state_info.mode = FMD_MODE_IDLE;
+ fmd_state_info.callback = NULL;
+ fmd_state_info.rx_freq_range = FMD_FREQRANGE_EUROAMERICA;
+ fmd_state_info.rx_stereo_mode = FMD_STEREOMODE_BLENDING;
+ fmd_state_info.rx_volume = MAX_ANALOG_VOLUME;
+ fmd_state_info.rx_antenna = FMD_ANTENNA_EMBEDDED;
+ fmd_state_info.rx_rds_on = false;
+ fmd_state_info.rx_seek_stop_level = DEFAULT_RSSI_THRESHOLD;
+ fmd_state_info.tx_freq_range = FMD_FREQRANGE_EUROAMERICA;
+ fmd_state_info.tx_preemphasis = FMD_EMPHASIS_75US;
+ fmd_state_info.tx_pilot_dev = DEFAULT_PILOT_DEVIATION;
+ fmd_state_info.tx_rds_dev = DEFAULT_RDS_DEVIATION;
+ fmd_state_info.tx_strength = MAX_POWER_LEVEL;
+ fmd_state_info.max_channels_to_scan = DEFAULT_CHANNELS_TO_SCAN;
+ fmd_state_info.tx_stereo_mode = true;
+ fmd_state_info.irq_index = 0;
+ spin_lock_init(&fmd_spinlock);
+ err = 0;
+
+error:
+ FM_DEBUG_REPORT("fmd_init returning = %d", err);
+ return err;
+}
+
+void fmd_exit(void)
+{
+ fmd_set_interrupt_sem();
+ fmd_driver_exit();
+ memset(&fmd_state_info, 0, sizeof(fmd_state_info));
+}
+
+int fmd_register_callback(
+ fmd_radio_cb callback
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ fmd_state_info.callback = callback;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_version(
+ u16 *version
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_GET_VERSION_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (version == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_GET_VERSION,
+ CMD_GET_VERSION_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ memcpy(version,
+ response_data,
+ sizeof(u16) * CMD_GET_VERSION_RSP_PARAM_LEN);
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_mode(
+ u8 mode
+ )
+{
+ int err;
+ u16 parameters[CMD_GOTO_MODE_PARAM_LEN];
+ int io_result;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (mode > FMD_MODE_TX) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = mode;
+
+ fmd_state_info.gocmd = FMD_STATE_MODE;
+ FM_ERR_REPORT("Sending Set Mode");
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_GOTO_MODE,
+ CMD_GOTO_MODE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem()) {
+ err = -ETIME;
+ goto error;
+ }
+ fmd_state_info.mode = mode;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_freq_range_properties(
+ u8 range,
+ u32 *min_freq,
+ u32 *max_freq
+ )
+{
+ int err;
+
+ if (min_freq == NULL || max_freq == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ switch (range) {
+ case FMD_FREQRANGE_EUROAMERICA:
+ *min_freq = FMD_EU_US_MIN_FREQ_IN_KHZ;
+ *max_freq = FMD_EU_US_MAX_FREQ_IN_KHZ;
+ break;
+ case FMD_FREQRANGE_JAPAN:
+ *min_freq = FMD_JAPAN_MIN_FREQ_IN_KHZ;
+ *max_freq = FMD_JAPAN_MAX_FREQ_IN_KHZ;
+ break;
+ case FMD_FREQRANGE_CHINA:
+ *min_freq = FMD_CHINA_MIN_FREQ_IN_KHZ;
+ *max_freq = FMD_CHINA_MAX_FREQ_IN_KHZ;
+ break;
+ default:
+ *min_freq = FMD_EU_US_MIN_FREQ_IN_KHZ;
+ *max_freq = FMD_EU_US_MAX_FREQ_IN_KHZ;
+ break;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_antenna(
+ u8 antenna
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_ANTENNA_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (antenna > FMD_ANTENNA_WIRED) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = antenna;
+
+ fmd_state_info.gocmd = FMD_STATE_ANTENNA;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SET_ANTENNA,
+ CMD_SET_ANTENNA_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.rx_antenna = antenna;
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_antenna(
+ u8 *antenna
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ *antenna = fmd_state_info.rx_antenna;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_freq_range(
+ u8 range
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TN_SET_BAND_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = range;
+ parameters[1] = FMD_MIN_CHANNEL_NUMBER;
+ parameters[2] = FMD_MAX_CHANNEL_NUMBER;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_TN_SET_BAND,
+ CMD_TN_SET_BAND_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ fmd_state_info.rx_freq_range = range;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_freq_range(
+ u8 *range
+ )
+{
+ int err;
+
+ if (range == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ *range = fmd_state_info.rx_freq_range;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_grid(
+ u8 grid
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TN_SET_GRID_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (grid > FMD_GRID_200KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = grid;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_TN_SET_GRID,
+ CMD_TN_SET_GRID_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_frequency(
+ u32 freq
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (freq > FMD_EU_US_MAX_FREQ_IN_KHZ ||
+ freq < FMD_CHINA_MIN_FREQ_IN_KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_rx_frequency_to_channel(
+ freq,
+ &parameters[0]);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_FREQUENCY;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_TUNE_SET_CHANNEL,
+ CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_frequency(
+ u32 *freq
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_SP_TUNE_GET_CHANNEL_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (freq == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_TUNE_GET_CHANNEL,
+ CMD_SP_TUNE_GET_CHANNEL_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ io_result = fmd_rx_channel_to_frequency(
+ response_data[0], /* 1st byte is the Frequency */
+ freq);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_stereo_mode(
+ u8 mode
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_STEREO_SET_MODE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (mode > FMD_STEREOMODE_BLENDING) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = mode;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_STEREO_SET_MODE,
+ CMD_RP_STEREO_SET_MODE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.rx_stereo_mode = mode;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_stereo_ctrl_blending_rssi(
+ u16 min_rssi,
+ u16 max_rssi)
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_STEREO_SET_CONTROL_BLENDING_RSSI_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = min_rssi;
+ parameters[1] = max_rssi;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_STEREO_SET_CONTROL_BLENDING_RSSI,
+ CMD_RP_STEREO_SET_CONTROL_BLENDING_RSSI_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_stereo_mode(
+ u8 *mode
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_RP_GET_STATE_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (mode == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_GET_STATE,
+ CMD_RP_GET_STATE_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /* 2nd element of response is stereo signal */
+ *mode = response_data[1];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_signal_strength(
+ u16 *strength
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_RP_GET_RSSI_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (strength == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_GET_RSSI,
+ CMD_RP_GET_RSSI_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ *strength = response_data[0]; /* 1st byte is the signal strength */
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_stop_level(
+ u16 stoplevel
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ fmd_state_info.rx_seek_stop_level = stoplevel;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_stop_level(
+ u16 *stop_level
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (stop_level == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *stop_level = fmd_state_info.rx_seek_stop_level;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_seek(
+ bool upwards
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_SEARCH_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (upwards)
+ parameters[0] = 0x0000;
+ else
+ parameters[0] = 0x0001;
+ parameters[1] = fmd_state_info.rx_seek_stop_level;
+ parameters[2] = DEFAULT_PEAK_NOISE_VALUE;
+ parameters[3] = DEFAULT_AVERAGE_NOISE_MAX_VALUE;
+ fmd_state_info.gocmd = FMD_STATE_SEEK;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_SEARCH_START,
+ CMD_SP_SEARCH_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_scan_band(
+ u8 max_channels_to_scan
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_SCAN_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (max_channels_to_scan > MAX_CHANNELS_TO_SCAN) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = max_channels_to_scan;
+ parameters[1] = fmd_state_info.rx_seek_stop_level;
+ parameters[2] = DEFAULT_PEAK_NOISE_VALUE;
+ parameters[3] = DEFAULT_AVERAGE_NOISE_MAX_VALUE;
+
+ fmd_state_info.gocmd = FMD_STATE_SCAN_BAND;
+ fmd_state_info.max_channels_to_scan = max_channels_to_scan;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_SCAN_START,
+ CMD_SP_SCAN_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_max_channels_to_scan(
+ u8 *max_channels_to_scan
+ )
+{
+ int err;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (max_channels_to_scan == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *max_channels_to_scan = fmd_state_info.max_channels_to_scan;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_scan_band_info(
+ u32 index,
+ u16 *num_channels,
+ u16 *channels,
+ u16 *rssi
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_SCAN_GET_RESULT_PARAM_LEN];
+ u16 response_count;
+ u16 response_data[CMD_SP_SCAN_GET_RESULT_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (num_channels == NULL || rssi == NULL || channels == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = index;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_SCAN_GET_RESULT,
+ CMD_SP_SCAN_GET_RESULT_PARAM_LEN,
+ parameters,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /* 1st byte indicates number of channels found */
+ *num_channels = response_data[0];
+ /* 2nd byte indicates 1st channel number */
+ channels[0] = response_data[1];
+ /* 3rd byte indicates RSSI of corresponding channel */
+ rssi[0] = response_data[2];
+ /* 4th byte indicates 2nd channel number */
+ channels[1] = response_data[3];
+ /* 5th byte indicates RSSI of corresponding channel */
+ rssi[1] = response_data[4];
+ /* 6th byte indicates 3rd channel number */
+ channels[2] = response_data[5];
+ /* 7th byte indicates RSSI of corresponding channel */
+ rssi[2] = response_data[6];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_block_scan(
+ u32 start_freq,
+ u32 stop_freq,
+ u8 antenna
+ )
+{
+ u16 start_channel;
+ u16 stop_channel;
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_BLOCK_SCAN_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (antenna > FMD_ANTENNA_WIRED) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (start_freq > FMD_EU_US_MAX_FREQ_IN_KHZ ||
+ start_freq < FMD_CHINA_MIN_FREQ_IN_KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (stop_freq > FMD_EU_US_MAX_FREQ_IN_KHZ ||
+ stop_freq < FMD_CHINA_MIN_FREQ_IN_KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ /* Convert the start frequency to corresponsing channel */
+ switch (fmd_state_info.mode) {
+ case FMD_MODE_RX:
+ io_result = fmd_rx_frequency_to_channel(
+ start_freq,
+ &start_channel);
+ break;
+ case FMD_MODE_TX:
+ io_result = fmd_tx_frequency_to_channel(
+ start_freq,
+ &start_channel);
+ break;
+ default:
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /* Convert the end frequency to corresponsing channel */
+ switch (fmd_state_info.mode) {
+ case FMD_MODE_RX:
+ io_result = fmd_rx_frequency_to_channel(
+ stop_freq,
+ &stop_channel);
+ break;
+ case FMD_MODE_TX:
+ io_result = fmd_tx_frequency_to_channel(
+ stop_freq,
+ &stop_channel);
+ break;
+ default:
+ err = -EINVAL;
+ goto error;
+ }
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ parameters[0] = start_channel;
+ parameters[1] = stop_channel;
+ parameters[2] = antenna;
+
+ fmd_state_info.gocmd = FMD_STATE_BLOCK_SCAN;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_BLOCK_SCAN_START,
+ CMD_SP_BLOCK_SCAN_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_block_scan_result(
+ u32 index,
+ u16 *num_channels,
+ u16 *rssi
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_BLOCK_SCAN_GET_RESULT_PARAM_LEN];
+ u16 response_count;
+ u16 response_data[CMD_SP_BLOCK_SCAN_GET_RESULT_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (num_channels == NULL || rssi == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = index;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_BLOCK_SCAN_GET_RESULT,
+ CMD_SP_BLOCK_SCAN_GET_RESULT_PARAM_LEN,
+ parameters,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /*
+ * Response packet has 1st byte as the number
+ * of channels, and the remaining 6 bytes as
+ * rssi values of the channels.
+ */
+ *num_channels = response_data[0];
+ rssi[0] = response_data[1];
+ rssi[1] = response_data[2];
+ rssi[2] = response_data[3];
+ rssi[3] = response_data[4];
+ rssi[4] = response_data[5];
+ rssi[5] = response_data[6];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_stop_seeking(void)
+{
+ int err;
+ int io_result;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (!(fmd_state_info.gocmd == FMD_STATE_SEEK ||
+ fmd_state_info.gocmd == FMD_STATE_SCAN_BAND ||
+ fmd_state_info.gocmd == FMD_STATE_BLOCK_SCAN)) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_SEEK_STOP;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_STOP,
+ CMD_SP_STOP_PARAM_LEN,
+ NULL,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_af_update_start(
+ u32 freq
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_AF_UPDATE_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_rx_frequency_to_channel(
+ freq,
+ &parameters[0]);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_AF_UPDATE;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_AF_UPDATE_START,
+ CMD_SP_AF_UPDATE_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_af_update_result(
+ u16 *af_level
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_SP_AF_UPDATE_GET_RESULT_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (af_level == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_AF_UPDATE_GET_RESULT,
+ CMD_SP_AF_UPDATE_GET_RESULT_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ /*
+ * 1st byte of response packet is the
+ * RSSI of the AF Frequency.
+ */
+ *af_level = response_data[0];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_af_switch_start(
+ u32 freq,
+ u16 picode
+ )
+{
+
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_AF_SWITCH_START_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_rx_frequency_to_channel(
+ freq,
+ &parameters[0]);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ parameters[1] = picode;
+ parameters[2] = 0xFFFF; /* PI Mask */
+ parameters[3] = fmd_state_info.rx_seek_stop_level;
+ parameters[4] = 0x0000; /* Unmute when AF's PI matches expected PI */
+
+ fmd_state_info.gocmd = FMD_STATE_AF_SWITCH;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_AF_SWITCH_START,
+ CMD_SP_AF_SWITCH_START_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_af_switch_results(
+ u16 *afs_conclusion,
+ u16 *afs_level,
+ u16 *afs_pi
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_SP_AF_SWITCH_GET_RESULT_RWSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (afs_conclusion == NULL ||
+ afs_level == NULL ||
+ afs_pi == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_SP_AF_SWITCH_GET_RESULT,
+ CMD_SP_AF_SWITCH_GET_RESULT_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ *afs_conclusion = response_data[0];
+ *afs_level = response_data[1];
+ *afs_pi = response_data[2];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_rds(
+ bool *on
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *on = fmd_state_info.rx_rds_on;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_buffer_set_size(
+ u8 size
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_BUFFER_SET_SIZE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (size > MAX_RDS_GROUPS) {
+ err = -EIO;
+ goto error;
+ }
+
+ parameters[0] = size;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_BUFFER_SET_SIZE,
+ CMD_DP_BUFFER_SET_SIZE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_buffer_set_threshold(
+ u8 threshold
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_BUFFER_SET_THRESHOLD_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (threshold > MAX_RDS_GROUPS) {
+ err = -EIO;
+ goto error;
+ }
+
+ parameters[0] = threshold;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_BUFFER_SET_THRESHOLD,
+ CMD_DP_BUFFER_SET_THRESHOLD_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_rds(
+ u8 on_off_state
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_SET_CONTROL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ switch (on_off_state) {
+ case FMD_SWITCH_ON_RDS_SIMULATOR:
+ parameters[0] = 0xFFFF;
+ break;
+ case FMD_SWITCH_OFF_RDS:
+ default:
+ parameters[0] = 0x0000;
+ fmd_state_info.rx_rds_on = false;
+ break;
+ case FMD_SWITCH_ON_RDS:
+ parameters[0] = 0x0001;
+ fmd_state_info.rx_rds_on = true;
+ break;
+ case FMD_SWITCH_ON_RDS_ENHANCED_MODE:
+ parameters[0] = 0x0002;
+ fmd_state_info.rx_rds_on = true;
+ break;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_SET_CONTROL,
+ CMD_DP_SET_CONTROL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_rds_group_rejection(
+ u8 on_off_state
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_SET_GROUP_REJECTION_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on_off_state == FMD_RDS_GROUP_REJECTION_ON)
+ parameters[0] = 0x0001;
+ else if (on_off_state == FMD_RDS_GROUP_REJECTION_OFF)
+ parameters[0] = 0x0000;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_SET_GROUP_REJECTION,
+ CMD_DP_SET_GROUP_REJECTION_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_get_low_level_rds_groups(
+ u8 index,
+ u16 *block1,
+ u16 *block2,
+ u16 *block3,
+ u16 *block4,
+ u8 *status1,
+ u8 *status2,
+ u8 *status3,
+ u8 *status4
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (block1 == NULL ||
+ block2 == NULL ||
+ block3 == NULL ||
+ block4 == NULL ||
+ status1 == NULL ||
+ status2 == NULL ||
+ status3 == NULL ||
+ status4 == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *block1 = fmd_state_info.rds_group[index].block[0];
+ *block2 = fmd_state_info.rds_group[index].block[1];
+ *block3 = fmd_state_info.rds_group[index].block[2];
+ *block4 = fmd_state_info.rds_group[index].block[3];
+ *status1 = fmd_state_info.rds_group[index].status[0];
+ *status2 = fmd_state_info.rds_group[index].status[1];
+ *status3 = fmd_state_info.rds_group[index].status[2];
+ *status4 = fmd_state_info.rds_group[index].status[3];
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_rx_set_deemphasis(
+ u8 deemphasis
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_SET_DEEMPHASIS_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ switch (deemphasis) {
+ case FMD_EMPHASIS_50US:
+ parameters[0] = FMD_EMPHASIS_50US;
+ break;
+
+ case FMD_EMPHASIS_75US:
+ parameters[0] = FMD_EMPHASIS_75US;
+ break;
+
+ case FMD_EMPHASIS_NONE:
+ default:
+ parameters[0] = FMD_EMPHASIS_NONE;
+ break;
+
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_RP_SET_DEEMPHASIS,
+ CMD_RP_SET_DEEMPHASIS_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_pa(
+ bool on
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_PA_SET_MODE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on)
+ parameters[0] = 0x0001;
+ else
+ parameters[0] = 0x0000;
+
+ fmd_state_info.gocmd = FMD_STATE_PA;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_PA_SET_MODE,
+ CMD_PA_SET_MODE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_signal_strength(
+ u16 strength
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_PA_SET_CONTROL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if ((strength > MAX_POWER_LEVEL)
+ || (strength < MIN_POWER_LEVEL)) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = strength;
+
+ fmd_state_info.gocmd = FMD_STATE_PA_LEVEL;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_PA_SET_CONTROL,
+ CMD_PA_SET_CONTROL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_strength = strength;
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_signal_strength(
+ u16 *strength
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (strength == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *strength = fmd_state_info.tx_strength;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_freq_range(
+ u8 range
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TN_SET_BAND_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (range > FMD_FREQRANGE_CHINA) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = range;
+ parameters[1] = FMD_MIN_CHANNEL_NUMBER;
+ parameters[2] = FMD_MAX_CHANNEL_NUMBER;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_TN_SET_BAND,
+ CMD_TN_SET_BAND_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_freq_range = range;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_freq_range(
+ u8 *range
+ )
+{
+ int err;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (range == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *range = fmd_state_info.tx_freq_range;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_grid(
+ u8 grid
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TN_SET_GRID_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (grid > FMD_GRID_200KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = grid;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_TN_SET_GRID,
+ CMD_TN_SET_GRID_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_preemphasis(
+ u8 preemphasis
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_SET_PREEMPHASIS_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ switch (preemphasis) {
+ case FMD_EMPHASIS_50US:
+ parameters[0] = FMD_EMPHASIS_50US;
+ break;
+ case FMD_EMPHASIS_75US:
+ default:
+ parameters[0] = FMD_EMPHASIS_75US;
+ break;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_SET_PREEMPHASIS,
+ CMD_RP_SET_PREEMPHASIS_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_preemphasis = preemphasis;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_preemphasis(
+ u8 *preemphasis
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (preemphasis == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *preemphasis = fmd_state_info.tx_preemphasis;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_frequency(
+ u32 freq
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (freq > FMD_EU_US_MAX_FREQ_IN_KHZ ||
+ freq < FMD_CHINA_MIN_FREQ_IN_KHZ) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_tx_frequency_to_channel(
+ freq,
+ &parameters[0]);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_FREQUENCY;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_SP_TUNE_SET_CHANNEL,
+ CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_frequency(
+ u32 *freq
+ )
+{
+ int err;
+ int io_result;
+ u16 response_count;
+ u16 response_data[CMD_SP_TUNE_GET_CHANNEL_RSP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (freq == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_SP_TUNE_GET_CHANNEL,
+ CMD_SP_TUNE_GET_CHANNEL_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ io_result = fmd_tx_channel_to_frequency(
+ response_data[0], /* 1st byte is the Frequency */
+ freq);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_enable_stereo_mode(
+ bool enable_stereo_mode
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_STEREO_SET_MODE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = enable_stereo_mode;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_STEREO_SET_MODE,
+ CMD_RP_STEREO_SET_MODE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_stereo_mode = enable_stereo_mode;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_stereo_mode(
+ bool *stereo_mode
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (stereo_mode == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *stereo_mode = fmd_state_info.tx_stereo_mode;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_pilot_deviation(
+ u16 deviation
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_SET_PILOT_DEVIATION_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (deviation > MAX_PILOT_DEVIATION) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = deviation;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_SET_PILOT_DEVIATION,
+ CMD_RP_SET_PILOT_DEVIATION_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_pilot_dev = deviation;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_pilot_deviation(
+ u16 *deviation
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (deviation == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *deviation = fmd_state_info.tx_pilot_dev;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_rds_deviation(
+ u16 deviation
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_RP_SET_RDS_DEVIATION_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (deviation > MAX_RDS_DEVIATION) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = deviation;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_SET_RDS_DEVIATION,
+ CMD_RP_SET_RDS_DEVIATION_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_rds_dev = deviation;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_get_rds_deviation(
+ u16 *deviation
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (deviation == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *deviation = fmd_state_info.tx_rds_dev;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_rds(
+ bool on
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_SET_CONTROL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on)
+ parameters[0] = 0x0001;
+ else
+ parameters[0] = 0x0000;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_DP_SET_CONTROL,
+ CMD_DP_SET_CONTROL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.tx_rds_on = on;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_set_group(
+ u16 position,
+ u8 *block1,
+ u8 *block2,
+ u8 *block3,
+ u8 *block4
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_BUFFER_SET_GROUP_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (block1 == NULL ||
+ block2 == NULL ||
+ block3 == NULL ||
+ block4 == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = position;
+ memcpy(&parameters[1], block1, sizeof(u16));
+ memcpy(&parameters[2], block2, sizeof(u16));
+ memcpy(&parameters[3], block3, sizeof(u16));
+ memcpy(&parameters[4], block4, sizeof(u16));
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_DP_BUFFER_SET_GROUP,
+ CMD_DP_BUFFER_SET_GROUP_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_tx_buffer_set_size(
+ u16 buffer_size
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_DP_BUFFER_SET_SIZE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = buffer_size;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_DP_BUFFER_SET_SIZE,
+ CMD_DP_BUFFER_SET_SIZE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+
+}
+
+int fmd_tx_get_rds(
+ bool *on
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (on == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *on = fmd_state_info.tx_rds_on;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_balance(
+ s8 balance
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_BALANCE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ /* Convert balance from percentage to chip number */
+ parameters[0] = (((s16)balance) * FMD_MAX_BALANCE) / 100;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_AUP_SET_BALANCE,
+ CMD_SET_BALANCE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_volume(
+ u8 volume
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_VOLUME_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ /* Convert volume from percentage to chip number */
+ parameters[0] = (((u16)volume) * FMD_MAX_VOLUME) / 100;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_AUP_SET_VOLUME,
+ CMD_SET_VOLUME_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ fmd_state_info.rx_volume = volume;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_get_volume(
+ u8 *volume
+ )
+{
+ int err;
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (volume == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ *volume = fmd_state_info.rx_volume;
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_mute(
+ bool mute_on
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_MUTE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (!mute_on)
+ parameters[0] = 0x0000;
+ else
+ parameters[0] = 0x0001;
+ parameters[1] = 0x0001;
+
+ fmd_state_info.gocmd = FMD_STATE_MUTE;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_AUP_SET_MUTE,
+ CMD_SET_MUTE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_ext_set_mute(
+ bool mute_on
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_EXT_SET_MUTE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (!mute_on)
+ parameters[0] = 0x0000;
+ else
+ parameters[0] = 0x0001;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_AUP_EXT_SET_MUTE,
+ CMD_EXT_SET_MUTE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_power_up(void)
+{
+ int err;
+ int io_result;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ fmd_state_info.gocmd = FMD_STATE_GEN_POWERUP;
+ FM_ERR_REPORT("Sending Gen Power Up");
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_POWERUP,
+ CMD_POWERUP_PARAM_LEN,
+ NULL,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_goto_standby(void)
+{
+ int err;
+ int io_result;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_GOTO_STANDBY,
+ CMD_GOTO_STANDBY_PARAM_LEN,
+ NULL,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_goto_power_down(void)
+{
+ int err;
+ int io_result;
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_GOTO_POWERDOWN,
+ CMD_GOTO_POWERDOWN_PARAM_LEN,
+ NULL,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_select_ref_clk(
+ u16 ref_clk
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SELECT_REFERENCE_CLOCK_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = ref_clk;
+
+ fmd_state_info.gocmd = FMD_STATE_SELECT_REF_CLK;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_SELECT_REFERENCE_CLOCK,
+ CMD_SELECT_REFERENCE_CLOCK_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_set_ref_clk_pll(
+ u16 freq
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_SET_REFERENCE_CLOCK_PLL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ parameters[0] = freq;
+
+ fmd_state_info.gocmd = FMD_STATE_SET_REF_CLK_PLL;
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_GEN_SET_REFERENCE_CLOCK_PLL,
+ CMD_SET_REFERENCE_CLOCK_PLL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ fmd_state_info.gocmd = FMD_STATE_NONE;
+ err = io_result;
+ goto error;
+ }
+
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+ else
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_send_fm_ip_enable(void)
+{
+ int err;
+ u8 fm_ip_enable_cmd[CMD_IP_ENABLE_CMD_LEN];
+
+ mutex_lock(&send_cmd_mutex);
+ fm_ip_enable_cmd[0] = CMD_IP_ENABLE_PARAM_LEN;
+ fm_ip_enable_cmd[1] = FM_CATENA_OPCODE;
+ fm_ip_enable_cmd[2] = FM_WRITE ;
+ fm_ip_enable_cmd[3] = FM_FUNCTION_ENABLE;
+
+ /* Send the Packet */
+ err = fmd_send_packet(
+ CMD_IP_ENABLE_CMD_LEN,
+ fm_ip_enable_cmd);
+
+ /* Check the ErrorCode */
+ if (err != 0)
+ goto error;
+
+ /* wait till response comes */
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+
+error:
+ mutex_unlock(&send_cmd_mutex);
+ return err;
+}
+
+int fmd_send_fm_ip_disable(void)
+{
+ int err;
+ u8 fm_ip_disable_cmd[CMD_IP_DISABLE_CMD_LEN];
+
+ mutex_lock(&send_cmd_mutex);
+ fm_ip_disable_cmd[0] = CMD_IP_DISABLE_PARAM_LEN;
+ fm_ip_disable_cmd[1] = FM_CATENA_OPCODE;
+ fm_ip_disable_cmd[2] = FM_WRITE ;
+ fm_ip_disable_cmd[3] = FM_FUNCTION_DISABLE;
+
+ /* Send the Packet */
+ err = fmd_send_packet(
+ CMD_IP_DISABLE_CMD_LEN,
+ fm_ip_disable_cmd);
+
+ /* Check the ErrorCode */
+ if (err != 0)
+ goto error;
+
+ /* wait till response comes */
+ if (fmd_get_cmd_sem())
+ err = -ETIME;
+
+error:
+ mutex_unlock(&send_cmd_mutex);
+ return err;
+}
+
+int fmd_send_fm_firmware(
+ u8 *fw_buffer,
+ u16 fw_size
+ )
+{
+ int err = -EINVAL;
+ u16 bytes_to_write = ST_WRITE_FILE_BLK_SIZE -
+ FM_HCI_WRITE_FILE_BLK_PARAM_LEN;
+ u16 bytes_remaining = fw_size;
+ u8 fm_firmware_data[ST_WRITE_FILE_BLK_SIZE + FM_HCI_CMD_HEADER_LEN];
+ u32 block_id = 0;
+
+ if (fw_buffer == NULL) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ while (bytes_remaining > 0) {
+ if (bytes_remaining <
+ (ST_WRITE_FILE_BLK_SIZE -
+ FM_HCI_WRITE_FILE_BLK_PARAM_LEN))
+ bytes_to_write = bytes_remaining;
+
+ /*
+ * Five bytes of HCI Header for FM Firmware
+ * so shift the firmware data by 5 bytes
+ */
+ memcpy(
+ fm_firmware_data + FM_HCI_WRITE_FILE_BLK_HEADER_LEN,
+ fw_buffer, bytes_to_write);
+ err = fmd_write_file_block(
+ block_id,
+ fm_firmware_data,
+ bytes_to_write);
+ if (err) {
+ FM_DEBUG_REPORT("fmd_send_fm_firmware: "
+ "Failed to download %d Block "
+ "error = %d", (unsigned int)block_id, err);
+ goto error;
+ }
+ /*
+ * Increment the Block Id by 1, since one
+ * block is successfully transmitted
+ * to the chip.
+ */
+ block_id++;
+ /*
+ * Increment the next firmware buffer equal
+ * to the number of bytes transmitted.
+ */
+ fw_buffer += bytes_to_write;
+ /*
+ * Decrement the number of bytes remaining
+ * equal to number of bytes transmitted successfully.
+ */
+ bytes_remaining -= bytes_to_write;
+
+ if (block_id == ST_MAX_NUMBER_OF_FILE_BLOCKS)
+ block_id = 0;
+ }
+
+error:
+ return err;
+}
+
+int fmd_int_bufferfull(
+ u16 *number_of_rds_groups
+ )
+{
+ u16 response_count;
+ u16 response_data[CMD_DP_BUFFER_GET_GROUP_COUNT_PARAM_LEN];
+ u16 index = 0;
+ u16 rds_group_count;
+ u8 result = -ENOEXEC;
+ struct fmd_rds_group rds_group;
+
+ if (!fmd_state_info.rx_rds_on)
+ goto error;
+
+ /* get group count*/
+ result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_BUFFER_GET_GROUP_COUNT,
+ CMD_DP_BUFFER_GET_GROUP_COUNT_PARAM_LEN,
+ NULL,
+ &response_count,
+ response_data);
+
+ if (result != 0)
+ goto error;
+
+ /* read RDS groups */
+ rds_group_count = FM_GET_NUM_RDS_GRPS(response_data);
+ if (rds_group_count > MAX_RDS_GROUPS)
+ rds_group_count = MAX_RDS_GROUPS;
+
+ *number_of_rds_groups = rds_group_count;
+
+ if (rds_group_count) {
+ FM_DEBUG_REPORT("rds_group_count = %d", rds_group_count);
+ while (rds_group_count-- && fmd_state_info.rx_rds_on) {
+ result = fmd_send_cmd_and_read_resp(
+ CMD_FMR_DP_BUFFER_GET_GROUP,
+ CMD_DP_BUFFER_GET_GROUP_PARAM_LEN,
+ NULL,
+ &response_count,
+ (u16 *)&rds_group);
+
+ if (result != 0)
+ goto error;
+
+ if (fmd_state_info.rx_rds_on)
+ fmd_state_info.rds_group[index++] = rds_group;
+ }
+ }
+error:
+ return result;
+}
+
+void fmd_start_rds_thread(
+ cg2900_fm_rds_cb cb_func
+ )
+{
+ FM_INFO_REPORT("fmd_start_rds_thread");
+ cb_rds_func = cb_func;
+ rds_thread_required = true;
+ rds_thread_task = kthread_create(fmd_rds_thread, NULL, "rds_thread");
+ if (IS_ERR(rds_thread_task)) {
+ FM_ERR_REPORT("fmd_start_rds_thread: "
+ "Unable to Create rds_thread");
+ rds_thread_task = NULL;
+ rds_thread_required = false;
+ return;
+ }
+ wake_up_process(rds_thread_task);
+}
+
+void fmd_stop_rds_thread(void)
+{
+ FM_INFO_REPORT("fmd_stop_rds_thread");
+ /* In case thread is waiting, set the rds sem */
+ fmd_set_rds_sem();
+ /* Re-initialize RDS Semaphore to zero */
+ sema_init(&rds_sem, 0);
+ cb_rds_func = NULL;
+ rds_thread_required = false;
+ /* Wait for RDS thread to exit gracefully */
+ fmd_get_rds_sem();
+
+ if (rds_thread_task)
+ rds_thread_task = NULL;
+}
+
+void fmd_get_rds_sem(void)
+{
+ int ret_val;
+
+ FM_DEBUG_REPORT("fmd_get_rds_sem");
+ ret_val = down_killable(&rds_sem);
+
+ if (ret_val)
+ FM_ERR_REPORT("fmd_get_rds_sem: down_killable "
+ "returned error = %d", ret_val);
+}
+
+void fmd_set_rds_sem(void)
+{
+ FM_DEBUG_REPORT("fmd_set_rds_sem");
+ up(&rds_sem);
+}
+
+int fmd_set_dev(struct device *dev)
+{
+ struct cg2900_user_data *pf_data;
+
+ FM_DEBUG_REPORT("fmd_set_dev");
+
+ if (dev && cg2900_fm_dev) {
+ FM_ERR_REPORT("Only one FM device supported");
+ return -EACCES;
+ }
+
+ cg2900_fm_dev = dev;
+
+ if (!dev)
+ return 0;
+
+ pf_data = dev_get_platdata(dev);
+ pf_data->dev = dev;
+ pf_data->read_cb = fmd_read_cb;
+ pf_data->reset_cb = fmd_reset_cb;
+
+ return 0;
+}
+
+int fmd_set_test_tone_generator_status(
+ u8 test_tone_status
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TST_TONE_ENABLE_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (test_tone_status > FMD_TST_TONE_ON_WO_SRC) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = test_tone_status;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_TST_TONE_ENABLE,
+ CMD_TST_TONE_ENABLE_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_test_tone_connect(
+ u8 left_audio_mode,
+ u8 right_audio_mode
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TST_TONE_CONNECT_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (left_audio_mode > FMD_TST_TONE_AUDIO_TONE_SUM ||
+ right_audio_mode > FMD_TST_TONE_AUDIO_TONE_SUM) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = left_audio_mode;
+ parameters[1] = right_audio_mode;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_TST_TONE_CONNECT,
+ CMD_TST_TONE_CONNECT_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_test_tone_set_params(
+ u8 tone_gen,
+ u16 frequency,
+ u16 volume,
+ u16 phase_offset,
+ u16 dc,
+ u8 waveform
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_TST_TONE_SET_PARAMS_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (tone_gen > FMD_TST_TONE_2 ||
+ waveform > FMD_TST_TONE_PULSE ||
+ frequency > 0x7FFF ||
+ volume > 0x7FFF) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = tone_gen;
+ parameters[1] = frequency;
+ parameters[2] = volume;
+ parameters[3] = phase_offset;
+ parameters[4] = dc;
+ parameters[5] = waveform;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_TST_TONE_SET_PARAMS,
+ CMD_TST_TONE_SET_PARAMS_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+int fmd_limiter_setcontrol(
+ u16 audio_deviation,
+ u16 notification_hold_off_time
+ )
+{
+ int err;
+ int io_result;
+ u16 parameters[CMD_FMT_RP_LIMITER_SETCONTROL_PARAM_LEN];
+
+ if (fmd_go_cmd_busy()) {
+ err = -EBUSY;
+ goto error;
+ }
+
+ if (!fmd_state_info.fmd_initialized) {
+ err = -ENOEXEC;
+ goto error;
+ }
+
+ if (audio_deviation < MIN_AUDIO_DEVIATION ||
+ audio_deviation > MAX_AUDIO_DEVIATION ||
+ notification_hold_off_time > 0x7FFF) {
+ err = -EINVAL;
+ goto error;
+ }
+
+ parameters[0] = audio_deviation;
+ parameters[1] = notification_hold_off_time;
+
+ io_result = fmd_send_cmd_and_read_resp(
+ CMD_FMT_RP_LIMITER_SETCONTROL,
+ CMD_FMT_RP_LIMITER_SETCONTROL_PARAM_LEN,
+ parameters,
+ NULL,
+ NULL);
+
+ if (io_result != 0) {
+ err = io_result;
+ goto error;
+ }
+
+ err = 0;
+
+error:
+ return err;
+}
+
+MODULE_AUTHOR("Hemant Gupta");
+MODULE_LICENSE("GPL v2");
+
+module_param(cg2900_fm_debug_level, ushort, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(cg2900_fm_debug_level, "cg2900_fm_debug_level: "
+ " *1: Only Error Logs* "
+ " 2: Info Logs "
+ " 3: Debug Logs "
+ " 4: HCI Logs");
+
diff --git a/drivers/media/radio/CG2900/cg2900_fm_driver.h b/drivers/media/radio/CG2900/cg2900_fm_driver.h
new file mode 100644
index 00000000000..62703fa4690
--- /dev/null
+++ b/drivers/media/radio/CG2900/cg2900_fm_driver.h
@@ -0,0 +1,1856 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Linux FM Driver for CG2900 FM Chip
+ *
+ * Author: Hemant Gupta <hemant.gupta@stericsson.com> for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#ifndef _FMDRIVER_H_
+#define _FMDRIVER_H_
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/uaccess.h>
+#include <linux/semaphore.h>
+#include <linux/version.h>
+#include <linux/kthread.h>
+#include <linux/kernel.h>
+#include <linux/timer.h>
+#include <linux/mutex.h>
+#include "cg2900_fm_api.h"
+
+/* structure declared in cg2900_fm_driver.c */
+extern struct timespec time_spec;
+
+/* module_param declared in cg2900_fm_driver.c */
+extern unsigned short cg2900_fm_debug_level;
+
+/**
+ * enum fmd_debug_levels - FM Driver Debug Levels.
+ *
+ * @FM_NO_LOGS: No Logs are displayed.
+ * @FM_ERROR_LOGS: Only Error Logs are displayed.
+ * @FM_INFO_LOGS: Function Entry logs are displayed.
+ * @FM_DEBUG_LOGS: Full debugging support.
+ * @FM_HCI_PACKET_LOGS: HCI Packet Sent/received to/by
+ * FM Driver are displayed.
+ *
+ * Various debug levels for FM Driver.
+ */
+enum fmd_debug_levels {
+ FM_NO_LOGS,
+ FM_ERROR_LOGS,
+ FM_INFO_LOGS,
+ FM_DEBUG_LOGS,
+ FM_HCI_PACKET_LOGS
+};
+
+#define FM_HEX_REPORT(fmt, arg...) \
+ if (cg2900_fm_debug_level == FM_HCI_PACKET_LOGS) { \
+ printk(KERN_INFO fmt "\r\n" , ## arg); \
+ }
+
+#define FM_DEBUG_REPORT(fmt, arg...) \
+ if (cg2900_fm_debug_level > FM_INFO_LOGS && \
+ cg2900_fm_debug_level < FM_HCI_PACKET_LOGS) { \
+ getnstimeofday(&time_spec); \
+ printk(KERN_INFO "\n[%08x:%08x] " \
+ "CG2900_FM_Driver: " fmt "\r\n" , \
+ (unsigned int)time_spec.tv_sec, \
+ (unsigned int)time_spec.tv_nsec, ## arg); \
+ }
+
+#define FM_INFO_REPORT(fmt, arg...) \
+ if (cg2900_fm_debug_level > FM_ERROR_LOGS && \
+ cg2900_fm_debug_level < FM_HCI_PACKET_LOGS) { \
+ getnstimeofday(&time_spec); \
+ printk(KERN_INFO "\n[%08x:%08x] " \
+ "CG2900_FM_Driver: " fmt "\r\n" , \
+ (unsigned int)time_spec.tv_sec, \
+ (unsigned int)time_spec.tv_nsec, ## arg); \
+ }
+
+#define FM_ERR_REPORT(fmt, arg...) \
+ if (cg2900_fm_debug_level >= FM_ERROR_LOGS) { \
+ getnstimeofday(&time_spec); \
+ printk(KERN_ERR "\n[%08x:%08x] " \
+ "CG2900_FM_Driver: " fmt "\r\n" , \
+ (unsigned int)time_spec.tv_sec, \
+ (unsigned int)time_spec.tv_nsec, ## arg); \
+ }
+
+#define MAX_COUNT_OF_IRQS 16
+#define MAX_BUFFER_SIZE 512
+#define MAX_NAME_SIZE 100
+/* Maximum size of parsable data in bytes, received from CG2900 FM IP */
+#define MAX_RESP_SIZE 20
+/* Minimum Power level for CG2900. The value is in units of dBuV */
+#define MIN_POWER_LEVEL 88
+/* Maximum Power level for CG2900. The value is in units of dBuV */
+#define MAX_POWER_LEVEL 123
+/* Minimum RDS Deviation value for CG2900. The value is in units of 10 Hz */
+#define MIN_RDS_DEVIATION 0
+/* Default RDS Deviation value for CG2900. The value is in units of 10 Hz */
+#define DEFAULT_RDS_DEVIATION 200
+/* Maximum RDS Deviation value for CG2900. The value is in units of 10 Hz */
+#define MAX_RDS_DEVIATION 750
+#define FMD_EU_US_MIN_FREQ_IN_KHZ 87500
+#define FMD_EU_US_MAX_FREQ_IN_KHZ 108000
+#define FMD_JAPAN_MIN_FREQ_IN_KHZ 76000
+#define FMD_JAPAN_MAX_FREQ_IN_KHZ 90000
+#define FMD_CHINA_MIN_FREQ_IN_KHZ 70000
+#define FMD_CHINA_MAX_FREQ_IN_KHZ 108000
+#define FMD_MIN_CHANNEL_NUMBER 0
+#define FMD_MAX_CHANNEL_NUMBER 760
+/*
+ * Maximum supported balance for CG2900. This is just a hexadecimal number
+ * with no units.
+ */
+#define FMD_MAX_BALANCE 0x7FFF
+/*
+ * Maximum supported volume for CG2900. This is just a hexadecimal number
+ * with no units.
+ */
+#define FMD_MAX_VOLUME 0x7FFF
+/* Minimum Program Identification value as per RDS specification */
+#define MIN_PI_VALUE 0x0000
+/* Maximum Program Identification value as per RDS specification */
+#define MAX_PI_VALUE 0xFFFF
+/* Minimum Program Type code value as per RDS specification */
+#define MIN_PTY_VALUE 0
+/* Maximum Program Type code value as per RDS specification */
+#define MAX_PTY_VALUE 31
+/* Minimum Pilot Deviation value for CG2900. The value is in units of 10 Hz */
+#define MIN_PILOT_DEVIATION 0
+/* Default Pilot Deviation value for CG2900. The value is in units of 10 Hz */
+#define DEFAULT_PILOT_DEVIATION 675
+/* Maximum Pilot Deviation value for CG2900. The value is in units of 10 Hz */
+#define MAX_PILOT_DEVIATION 1000
+/*
+ * Default RSSI Threshold for a channel to be considered valid for CG2900.
+ * This is just a hexadecimal number with no units.
+ */
+#define DEFAULT_RSSI_THRESHOLD 0x0100
+/*
+ * Default Peak Noise level for a channel to be considered valid for CG2900.
+ * This is just a hexadecimal number with no units.
+ */
+#define DEFAULT_PEAK_NOISE_VALUE 0x0035
+/* Defines the RF level (at the antenna pin) at which the stereo blending
+ * function will stop limiting the channel separation */
+#define STEREO_BLENDING_MIN_RSSI 0x0005
+/* Defines the RF level (at the antenna pin) at which the stereo blending
+ * function will start limiting the channel separation */
+#define STEREO_BLENDING_MAX_RSSI 0x0100
+/*
+ * Default Average Noise level for a channel to be considered valid for CG2900.
+ * This is just a hexadecimal number with no units.
+ */
+#define DEFAULT_AVERAGE_NOISE_MAX_VALUE 0x0030
+/*
+ * Minimum Audio Deviation Level, as per CG2900 FM User Manual.
+ * This is units of 10 Hz.
+ */
+#define MIN_AUDIO_DEVIATION 0x157C
+/*
+ * Maximum Audio Deviation Level, as per CG2900 FM UserManual.
+ * This is units of 10 Hz.
+ */
+#define MAX_AUDIO_DEVIATION 0x3840
+#define FREQUENCY_CONVERTOR_KHZ_HZ 1000
+#define CHANNEL_FREQ_CONVERTER_MHZ 50
+/* Interrupt(s) for CG2900 */
+#define IRPT_INVALID 0x0000
+#define IRPT_OPERATION_SUCCEEDED 0x0001
+#define IRPT_OPERATION_FAILED 0x0002
+#define IRPT_RX_BUFFERFULL_TX_BUFFEREMPTY 0x0008
+#define IRPT_RX_SIGNAL_QUALITYLOW_MUTE_STATUS_CHANGED 0x0010
+#define IRPT_RX_MONO_STEREO_TRANSITION 0x0020
+#define IRPT_TX_OVERMODULATION 0x0030
+#define IRPT_RX_RDS_SYNCFOUND_TX_OVERDRIVE 0x0040
+#define IRPT_RDS_SYNC_LOST 0x0080
+#define IRPT_PI_CODE_CHANGED 0x0100
+#define IRPT_REQUESTED_BLOCK_AVAILABLE 0x0200
+#define IRPT_BUFFER_CLEARED 0x2000
+#define IRPT_WARM_BOOT_READY 0x4000
+#define IRPT_COLD_BOOT_READY 0x8000
+/* FM Commands Id */
+#define CMD_ID_NONE 0x0000
+#define CMD_AUP_EXT_SET_MUTE 0x01E2
+#define CMD_AUP_SET_BALANCE 0x0042
+#define CMD_AUP_SET_MUTE 0x0062
+#define CMD_AUP_SET_VOLUME 0x0022
+#define CMD_FMR_DP_BUFFER_GET_GROUP 0x0303
+#define CMD_FMR_DP_BUFFER_GET_GROUP_COUNT 0x0323
+#define CMD_FMR_DP_BUFFER_SET_SIZE 0x0343
+#define CMD_FMR_DP_BUFFER_SET_THRESHOLD 0x06C3
+#define CMD_FMR_DP_SET_CONTROL 0x02A3
+#define CMD_FMR_DP_SET_GROUP_REJECTION 0x0543
+#define CMD_FMR_RP_GET_RSSI 0x0083
+#define CMD_FMR_RP_GET_STATE 0x0063
+#define CMD_FMR_RP_STEREO_SET_MODE 0x0123
+#define CMD_FMR_RP_STEREO_SET_CONTROL_BLENDING_RSSI 0x0143
+#define CMD_FMR_SET_ANTENNA 0x0663
+#define CMD_FMR_SP_AF_SWITCH_GET_RESULT 0x0603
+#define CMD_FMR_SP_AF_SWITCH_START 0x04A3
+#define CMD_FMR_SP_AF_UPDATE_GET_RESULT 0x0483
+#define CMD_FMR_SP_AF_UPDATE_START 0x0463
+#define CMD_FMR_SP_BLOCK_SCAN_GET_RESULT 0x06A3
+#define CMD_FMR_SP_BLOCK_SCAN_START 0x0683
+#define CMD_FMR_SP_SCAN_GET_RESULT 0x0423
+#define CMD_FMR_SP_SCAN_START 0x0403
+#define CMD_FMR_SP_SEARCH_START 0x03E3
+#define CMD_FMR_SP_STOP 0x0383
+#define CMD_FMR_SP_TUNE_GET_CHANNEL 0x03A3
+#define CMD_FMR_SP_TUNE_SET_CHANNEL 0x03C3
+#define CMD_FMR_TN_SET_BAND 0x0023
+#define CMD_FMR_TN_SET_GRID 0x0043
+#define CMD_FMR_RP_SET_DEEMPHASIS 0x00C3
+#define CMD_FMT_DP_BUFFER_GET_POSITION 0x0204
+#define CMD_FMT_DP_BUFFER_SET_GROUP 0x0244
+#define CMD_FMT_DP_BUFFER_SET_SIZE 0x0224
+#define CMD_FMT_DP_BUFFER_SET_THRESHOLD 0x0284
+#define CMD_FMT_DP_SET_CONTROL 0x0264
+#define CMD_FMT_PA_SET_CONTROL 0x01A4
+#define CMD_FMT_PA_SET_MODE 0x01E4
+#define CMD_FMT_RP_SET_PILOT_DEVIATION 0x02A4
+#define CMD_FMT_RP_SET_PREEMPHASIS 0x00C4
+#define CMD_FMT_RP_SET_RDS_DEVIATION 0x0344
+#define CMD_FMT_RP_STEREO_SET_MODE 0x0164
+#define CMD_FMT_SP_TUNE_GET_CHANNEL 0x0184
+#define CMD_FMT_SP_TUNE_SET_CHANNEL 0x0064
+#define CMD_FMT_TN_SET_BAND 0x0024
+#define CMD_FMT_TN_SET_GRID 0x0044
+#define CMD_GEN_GET_MODE 0x0021
+#define CMD_GEN_GET_REGISTER_VALUE 0x00E1
+#define CMD_GEN_GET_VERSION 0x00C1
+#define CMD_GEN_GOTO_MODE 0x0041
+#define CMD_GEN_GOTO_POWERDOWN 0x0081
+#define CMD_GEN_GOTO_STANDBY 0x0061
+#define CMD_GEN_POWERUP 0x0141
+#define CMD_GEN_SELECT_REFERENCE_CLOCK 0x0201
+#define CMD_GEN_SET_REFERENCE_CLOCK 0x0161
+#define CMD_GEN_SET_REFERENCE_CLOCK_PLL 0x01A1
+#define CMD_GEN_SET_REGISTER_VALUE 0x0101
+#define CMD_TST_TONE_ENABLE 0x0027
+#define CMD_TST_TONE_CONNECT 0x0047
+#define CMD_TST_TONE_SET_PARAMS 0x0067
+#define CMD_FMT_RP_LIMITER_SETCONTROL 0x01C4
+
+/* FM Command Id Parameter Length */
+#define CMD_GET_VERSION_PARAM_LEN 0
+#define CMD_GET_VERSION_RSP_PARAM_LEN 7
+#define CMD_GOTO_MODE_PARAM_LEN 1
+#define CMD_SET_ANTENNA_PARAM_LEN 1
+#define CMD_TN_SET_BAND_PARAM_LEN 3
+#define CMD_TN_SET_GRID_PARAM_LEN 1
+#define CMD_SP_TUNE_SET_CHANNEL_PARAM_LEN 1
+#define CMD_SP_TUNE_GET_CHANNEL_PARAM_LEN 0
+#define CMD_SP_TUNE_GET_CHANNEL_RSP_PARAM_LEN 1
+#define CMD_RP_STEREO_SET_MODE_PARAM_LEN 1
+#define CMD_RP_STEREO_SET_CONTROL_BLENDING_RSSI_PARAM_LEN 2
+#define CMD_RP_GET_RSSI_PARAM_LEN 0
+#define CMD_RP_GET_RSSI_RSP_PARAM_LEN 1
+#define CMD_RP_GET_STATE_PARAM_LEN 0
+#define CMD_RP_GET_STATE_RSP_PARAM_LEN 2
+#define CMD_SP_SEARCH_START_PARAM_LEN 4
+#define CMD_SP_SCAN_START_PARAM_LEN 4
+#define CMD_SP_SCAN_GET_RESULT_PARAM_LEN 1
+#define CMD_SP_SCAN_GET_RESULT_RSP_PARAM_LEN 7
+#define CMD_SP_BLOCK_SCAN_START_PARAM_LEN 3
+#define CMD_SP_BLOCK_SCAN_GET_RESULT_PARAM_LEN 1
+#define CMD_SP_BLOCK_SCAN_GET_RESULT_RSP_PARAM_LEN 7
+#define CMD_SP_STOP_PARAM_LEN 0
+#define CMD_SP_AF_UPDATE_START_PARAM_LEN 1
+#define CMD_SP_AF_UPDATE_GET_RESULT_PARAM_LEN 0
+#define CMD_SP_AF_UPDATE_GET_RESULT_RSP_PARAM_LEN 1
+#define CMD_SP_AF_SWITCH_START_PARAM_LEN 5
+#define CMD_SP_AF_SWITCH_GET_RESULT_PARAM_LEN 0
+#define CMD_SP_AF_SWITCH_GET_RESULT_RWSP_PARAM_LEN 3
+#define CMD_DP_BUFFER_SET_SIZE_PARAM_LEN 1
+#define CMD_DP_BUFFER_SET_THRESHOLD_PARAM_LEN 1
+#define CMD_DP_SET_CONTROL_PARAM_LEN 1
+#define CMD_DP_SET_GROUP_REJECTION_PARAM_LEN 1
+#define CMD_PA_SET_MODE_PARAM_LEN 1
+#define CMD_PA_SET_CONTROL_PARAM_LEN 1
+#define CMD_RP_SET_PREEMPHASIS_PARAM_LEN 1
+#define CMD_RP_SET_DEEMPHASIS_PARAM_LEN 1
+#define CMD_RP_SET_PILOT_DEVIATION_PARAM_LEN 1
+#define CMD_RP_SET_RDS_DEVIATION_PARAM_LEN 1
+#define CMD_DP_BUFFER_SET_GROUP_PARAM_LEN 5
+#define CMD_SET_BALANCE_PARAM_LEN 1
+#define CMD_SET_VOLUME_PARAM_LEN 1
+#define CMD_SET_MUTE_PARAM_LEN 2
+#define CMD_EXT_SET_MUTE_PARAM_LEN 1
+#define CMD_POWERUP_PARAM_LEN 0
+#define CMD_GOTO_STANDBY_PARAM_LEN 0
+#define CMD_GOTO_POWERDOWN_PARAM_LEN 0
+#define CMD_SELECT_REFERENCE_CLOCK_PARAM_LEN 1
+#define CMD_SET_REFERENCE_CLOCK_PLL_PARAM_LEN 1
+#define CMD_DP_BUFFER_GET_GROUP_COUNT_PARAM_LEN 0
+#define CMD_DP_BUFFER_GET_GROUP_PARAM_LEN 0
+#define CMD_IP_ENABLE_CMD_LEN 4
+#define CMD_IP_ENABLE_PARAM_LEN 3
+#define CMD_IP_DISABLE_CMD_LEN 4
+#define CMD_IP_DISABLE_PARAM_LEN 3
+#define CMD_TST_TONE_ENABLE_PARAM_LEN 1
+#define CMD_TST_TONE_CONNECT_PARAM_LEN 2
+#define CMD_TST_TONE_SET_PARAMS_PARAM_LEN 6
+#define CMD_FMT_RP_LIMITER_SETCONTROL_PARAM_LEN 2
+
+/* FM HCI Command and event specific */
+#define FM_WRITE 0x00
+#define FM_READ 0x01
+#define FM_CATENA_OPCODE 0xFE
+#define HCI_CMD_FM 0xFD50
+#define HCI_CMD_VS_WRITE_FILE_BLOCK 0xFC2E
+#define FM_EVENT_ID 0x15
+#define FM_SUCCESS_STATUS 0x00
+#define FM_EVENT 0x01
+#define HCI_COMMAND_COMPLETE_EVENT 0x0E
+#define HCI_VS_DBG_EVENT 0xFF
+#define ST_WRITE_FILE_BLK_SIZE 254
+#define ST_MAX_NUMBER_OF_FILE_BLOCKS 256
+#define FM_PG1_INTERRUPT_EVENT_LEN 0x04
+#define FM_PG2_INTERRUPT_EVENT_LEN 0x06
+#define FM_HCI_CMD_HEADER_LEN 6
+#define FM_HCI_CMD_PARAM_LEN 5
+#define FM_HCI_WRITE_FILE_BLK_HEADER_LEN 5
+#define FM_HCI_WRITE_FILE_BLK_PARAM_LEN 4
+#define HCI_PACKET_INDICATOR_CMD 0x01
+#define HCI_PACKET_INDICATOR_EVENT 0x04
+#define HCI_PACKET_INDICATOR_FM_CMD_EVT 0x08
+/* FM Functions specific to CG2900 */
+#define FM_FUNCTION_ENABLE 0x00
+#define FM_FUNCTION_DISABLE 0x01
+#define FM_FUNCTION_RESET 0x02
+#define FM_FUNCTION_WRITE_COMMAND 0x10
+#define FM_FUNCTION_SET_INT_MASK_ALL 0x20
+#define FM_FUNCTION_GET_INT_MASK_ALL 0x21
+#define FM_FUNCTION_SET_INT_MASK 0x22
+#define FM_FUNCTION_GET_INT_MASK 0x23
+#define FM_FUNCTION_FIRMWARE_DOWNLOAD 0x30
+/* Command succeeded */
+#define FM_CMD_STATUS_CMD_SUCCESS 0x00
+/* HCI_ERR_HW_FAILURE when no response from the IP */
+#define FM_CMD_STATUS_HCI_ERR_HW_FAILURE 0x03
+/* HCI_ERR_INVALID_PARAMETERS. */
+#define FM_CMD_STATUS_HCI_ERR_INVALID_PARAMETERS 0x12
+/* When the host tries to send a command to an IP that hasn't been
+ * initialized.
+ */
+#define FM_CMD_STATUS_IP_UNINIT 0x15
+/* HCI_ERR_UNSPECIFIED_ERROR: any other error */
+#define FM_CMD_STATUS_HCI_ERR_UNSPECIFIED_ERROR 0x1F
+/* HCI_ERR_CMD_DISALLOWED when the host asks for an unauthorized operation
+ * (FM state transition for instance)
+ */
+#define FM_CMD_STATUS_HCI_ERR_CMD_DISALLOWED 0x0C
+/* Wrong sequence number for FM FW download command */
+#define FM_CMD_STATUS_WRONG_SEQ_NUM 0xF1
+/* Unknown file type for FM FW download command */
+#define FM_CMD_STATUS_UNKNOWN_FILE_TYPE 0xF2
+/* File version mismatch for FM FW download command */
+#define FM_CMD_STATUS_FILE_VERSION_MISMATCH 0xF3
+
+
+/**
+ * enum fmd_event - Events received.
+ *
+ * @FMD_EVENT_OPERATION_COMPLETED: Previous operation has been completed.
+ * @FMD_EVENT_ANTENNA_STATUS_CHANGED: Antenna has been changed.
+ * @FMD_EVENT_FREQUENCY_CHANGED: Frequency has been changed.
+ * @FMD_EVENT_SEEK_COMPLETED: Seek operation has completed.
+ * @FMD_EVENT_SCAN_BAND_COMPLETED: Band Scan completed.
+ * @FMD_EVENT_BLOCK_SCAN_COMPLETED: Block Scan completed.
+ * @FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE: Af Update or AF Switch is complete.
+ * @FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE: Mono stereo transition is
+ * completed.
+ * @FMD_EVENT_SEEK_STOPPED: Previous Seek/Band Scan/ Block Scan operation is
+ * stopped.
+ * @FMD_EVENT_GEN_POWERUP: FM IP Powerup has been powered up.
+ * @FMD_EVENT_RDSGROUP_RCVD: RDS Groups Full interrupt.
+ * @FMD_EVENT_LAST_ELEMENT: Last event, used for keeping count of
+ * number of events.
+ *
+ * Various events received from FM driver for Upper Layer(s) processing.
+ */
+enum fmd_event {
+ FMD_EVENT_OPERATION_COMPLETED,
+ FMD_EVENT_ANTENNA_STATUS_CHANGED,
+ FMD_EVENT_FREQUENCY_CHANGED,
+ FMD_EVENT_SEEK_COMPLETED,
+ FMD_EVENT_SCAN_BAND_COMPLETED,
+ FMD_EVENT_BLOCK_SCAN_COMPLETED,
+ FMD_EVENT_AF_UPDATE_SWITCH_COMPLETE,
+ FMD_EVENT_MONO_STEREO_TRANSITION_COMPLETE,
+ FMD_EVENT_SEEK_STOPPED,
+ FMD_EVENT_GEN_POWERUP,
+ FMD_EVENT_RDSGROUP_RCVD,
+ FMD_EVENT_LAST_ELEMENT
+};
+
+/**
+ * enum fmd_mode - FM Driver Modes.
+ *
+ * @FMD_MODE_IDLE: FM Driver in Idle mode.
+ * @FMD_MODE_RX: FM Driver in Rx mode.
+ * @FMD_MODE_TX: FM Driver in Tx mode.
+ *
+ * Various Modes of FM Radio.
+ */
+enum fmd_mode {
+ FMD_MODE_IDLE,
+ FMD_MODE_RX,
+ FMD_MODE_TX
+};
+
+/**
+ * enum fmd_antenna - Antenna selection.
+ *
+ * @FMD_ANTENNA_EMBEDDED: Embedded Antenna.
+ * @FMD_ANTENNA_WIRED: Wired Antenna.
+ *
+ * Antenna to be used for FM Radio.
+ */
+enum fmd_antenna {
+ FMD_ANTENNA_EMBEDDED,
+ FMD_ANTENNA_WIRED
+};
+
+/**
+ * enum fmd_grid - Grid used on FM Radio.
+ *
+ * @FMD_GRID_50KHZ: 50 kHz grid spacing.
+ * @FMD_GRID_100KHZ: 100 kHz grid spacing.
+ * @FMD_GRID_200KHZ: 200 kHz grid spacing.
+ *
+ * Spacing used on FM Radio.
+ */
+enum fmd_grid {
+ FMD_GRID_50KHZ,
+ FMD_GRID_100KHZ,
+ FMD_GRID_200KHZ
+};
+
+/**
+ * enum fmd_emphasis - De-emphasis/Pre-emphasis level.
+ *
+ * @FMD_EMPHASIS_NONE: De-emphasis Disabled.
+ * @FMD_EMPHASIS_50US: 50 us de-emphasis/pre-emphasis level.
+ * @FMD_EMPHASIS_75US: 75 us de-emphasis/pre-emphasis level.
+ *
+ * De-emphasis/Pre-emphasis level used on FM Radio.
+ */
+enum fmd_emphasis {
+ FMD_EMPHASIS_NONE = 0,
+ FMD_EMPHASIS_50US = 1,
+ FMD_EMPHASIS_75US = 2
+};
+
+/**
+ * enum fmd_freq_range - Frequency range.
+ *
+ * @FMD_FREQRANGE_EUROAMERICA: EU/US Range (87.5 - 108 MHz).
+ * @FMD_FREQRANGE_JAPAN: Japan Range (76 - 90 MHz).
+ * @FMD_FREQRANGE_CHINA: China Range (70 - 108 MHz).
+ *
+ * Various Frequency range(s) supported by FM Radio.
+ */
+enum fmd_freq_range {
+ FMD_FREQRANGE_EUROAMERICA,
+ FMD_FREQRANGE_JAPAN,
+ FMD_FREQRANGE_CHINA
+};
+
+/**
+ * enum fmd_stereo_mode - FM Driver Stereo Modes.
+ *
+ * @FMD_STEREOMODE_OFF: Streo Blending Off.
+ * @FMD_STEREOMODE_MONO: Mono Mode.
+ * @FMD_STEREOMODE_BLENDING: Blending Mode.
+ *
+ * Various Stereo Modes of FM Radio.
+ */
+enum fmd_stereo_mode {
+ FMD_STEREOMODE_OFF,
+ FMD_STEREOMODE_MONO,
+ FMD_STEREOMODE_BLENDING
+};
+
+/**
+ * enum fmd_pilot_tone - Pilot Tone Selection
+ *
+ * @FMD_PILOT_TONE_DISABLED: Pilot Tone to be disabled.
+ * @FMD_PILOT_TONE_ENABLED: Pilot Tone to be enabled.
+ *
+ * Pilot Tone to be enabled or disabled.
+ */
+enum fmd_pilot_tone {
+ FMD_PILOT_TONE_DISABLED,
+ FMD_PILOT_TONE_ENABLED
+};
+
+/**
+ * enum fmd_output - Output of Sample Rate Converter.
+ *
+ * @FMD_OUTPUT_DISABLED: Sample Rate converter in disabled.
+ * @FMD_OUTPUT_I2S: I2S Output from Sample rate converter.
+ * @FMD_OUTPUT_PARALLEL: Parallel output from sample rate converter.
+ *
+ * Sample Rate Converter's output to be set on Connectivity Controller.
+ */
+enum fmd_output {
+ FMD_OUTPUT_DISABLED,
+ FMD_OUTPUT_I2S,
+ FMD_OUTPUT_PARALLEL
+};
+
+/**
+ * enum fmd_input - Audio Input to Sample Rate Converter.
+ *
+ * @FMD_INPUT_ANALOG: Selects the ADC's as audio source
+ * @FMD_INPUT_DIGITAL: Selects Digital Input as audio source.
+ *
+ * Audio Input source for Sample Rate Converter.
+ */
+enum fmd_input {
+ FMD_INPUT_ANALOG,
+ FMD_INPUT_DIGITAL
+};
+
+/**
+ * enum fmd_rds_mode - RDS Mode to be selected for FM Rx.
+ *
+ * @FMD_SWITCH_OFF_RDS: RDS Decoding disabled in FM Chip.
+ * @FMD_SWITCH_ON_RDS: RDS Decoding enabled in FM Chip.
+ * @FMD_SWITCH_ON_RDS_ENHANCED_MODE: Enhanced RDS Mode.
+ * @FMD_SWITCH_ON_RDS_SIMULATOR: RDS Simulator switched on in FM Chip.
+ *
+ * RDS Mode to be selected for FM Rx.
+ */
+enum fmd_rds_mode {
+ FMD_SWITCH_OFF_RDS,
+ FMD_SWITCH_ON_RDS,
+ FMD_SWITCH_ON_RDS_ENHANCED_MODE,
+ FMD_SWITCH_ON_RDS_SIMULATOR
+};
+
+/**
+ * enum fmd_rds_group_rejection_mode - RDS Group Rejection
+ * to be selected for FM Rx.
+ *
+ * @FMD_RDS_GROUP_REJECTION_ON: Group rejection is enabled in FM Chip.
+ * @FMD_RDS_GROUP_REJECTION_OFF: Group rejection is disabled in FM Chip.
+ *
+ * RDS Group rejection to be selected for FM Rx.
+ */
+enum fmd_rds_group_rejection_mode {
+ FMD_RDS_GROUP_REJECTION_ON,
+ FMD_RDS_GROUP_REJECTION_OFF
+};
+
+/**
+ * enum fmd_tst_tone_status - Test Tone Generator Status.
+ *
+ * @FMD_TST_TONE_OFF: Test Tone Generator is off.
+ * @FMD_TST_TONE_ON_W_SRC: Test Tone Gen. is on with Sample Rate Conversion.
+ * @FMD_TST_TONE_ON_WO_SRC: Test Tone Gen. is on without Sample Rate Conversion.
+ *
+ * Test Tone Generator status to be set.
+ */
+enum fmd_tst_tone_status {
+ FMD_TST_TONE_OFF,
+ FMD_TST_TONE_ON_W_SRC,
+ FMD_TST_TONE_ON_WO_SRC
+};
+
+/**
+ * enum fmd_tst_tone_audio_mode - Test Tone Generator Audio Output/Input Mode.
+ *
+ * @FMD_TST_TONE_AUDIO_NORMAL: Normal Audio.
+ * @FMD_TST_TONE_AUDIO_ZERO: Zero.
+ * @FMD_TST_TONE_AUDIO_TONE_1: Tone 1.
+ * @FMD_TST_TONE_AUDIO_TONE_2: Tone 2.
+ * @FMD_TST_TONE_AUDIO_TONE_SUM: Sum of Tone 1 and Tone 2.
+ *
+ * Test Tone Generator Audio Output/Input Modes.
+ */
+enum fmd_tst_tone_audio_mode {
+ FMD_TST_TONE_AUDIO_NORMAL,
+ FMD_TST_TONE_AUDIO_ZERO,
+ FMD_TST_TONE_AUDIO_TONE_1,
+ FMD_TST_TONE_AUDIO_TONE_2,
+ FMD_TST_TONE_AUDIO_TONE_SUM
+};
+
+/**
+ * enum fmd_tst_tone - Test Tone of Internal Tone Generator.
+ *
+ * @FMD_TST_TONE_1: Test Tone 1
+ * @FMD_TST_TONE_2: Test Tone 2
+ *
+ * Test Tone.
+ */
+enum fmd_tst_tone {
+ FMD_TST_TONE_1,
+ FMD_TST_TONE_2
+};
+
+/**
+ * enum fmd_tst_tone_waveform - Test Tone Waveform of Internal Tone Generator.
+ *
+ * @FMD_TST_TONE_SINE: Sine wave
+ * @FMD_TST_TONE_PULSE: Pulse wave
+ *
+ * Test Tone waveform.
+ */
+enum fmd_tst_tone_waveform {
+ FMD_TST_TONE_SINE,
+ FMD_TST_TONE_PULSE
+};
+
+/* Callback function to receive radio events. */
+typedef void(*fmd_radio_cb)(
+ u8 event,
+ bool event_successful
+ );
+
+/**
+ * fmd_init() - Initialize the FM Driver internal structures.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EIO, if there is an error.
+ */
+int fmd_init(void);
+
+/**
+ * fmd_exit() - De-initialize the FM Driver.
+ */
+void fmd_exit(void);
+
+/**
+ * fmd_register_callback() - Function to register callback function.
+ *
+ * This function registers the callback function provided by upper layers.
+ * @callback: Fmradio call back Function pointer
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_register_callback(
+ fmd_radio_cb callback
+ );
+
+/**
+ * fmd_get_version() - Retrieves the FM HW and FW version.
+ *
+ * @version: (out) Version Array
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameters are not valid.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_get_version(
+ u16 *version
+ );
+
+/**
+ * fmd_set_mode() - Starts a transition to the given mode.
+ *
+ * @mode: Transition mode
+ *
+ * Returns:
+ * 0, if set mode done successfully.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_mode(
+ u8 mode
+ );
+
+/**
+ * fmd_get_freq_range_properties() - Retrieves Freq Range Properties.
+ *
+ * @range: range of freq
+ * @min_freq: (out) Minimum Frequency of the Band in kHz.
+ * @max_freq: (out) Maximum Frequency of the Band in kHz
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ */
+int fmd_get_freq_range_properties(
+ u8 range,
+ u32 *min_freq,
+ u32 *max_freq
+ );
+
+/**
+ * fmd_set_antenna() - Selects the antenna to be used in receive mode.
+ *
+ * embedded - Selects the embedded antenna, wired- Selects the wired antenna.
+ * @antenna: Antenna Type
+ *
+ * Returns:
+ * 0, if set antenna done successfully.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_antenna(
+ u8 antenna
+ );
+
+/**
+ * fmd_get_antenna() - Retrieves the currently used antenna type.
+ *
+ * @antenna: (out) Antenna Selected on FM Radio.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_get_antenna(
+ u8 *antenna
+ );
+
+/**
+ * fmd_set_freq_range() - Sets the FM band.
+ *
+ * @range: freq range
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_freq_range(
+ u8 range
+ );
+
+/**
+ * fmd_get_freq_range() - Gets the FM band currently in use.
+ *
+ * @range: (out) Frequency Range set on FM Radio.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ */
+int fmd_get_freq_range(
+ u8 *range
+ );
+
+/**
+ * fmd_rx_set_grid() - Sets the tuning grid.
+ *
+ * @grid: Tuning grid size
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_set_grid(
+ u8 grid
+ );
+
+/**
+ * fmd_rx_set_frequency() - Sets the FM Channel.
+ *
+ * @freq: Frequency to Set in Khz
+ *
+ * Returns:
+ * 0, if set frequency done successfully.
+ * -EINVAL, if parameters are invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_set_frequency(
+ u32 freq
+ );
+
+/**
+ * fmd_rx_get_frequency() - Gets the currently used FM Channel.
+ *
+ * @freq: (out) Current Frequency set on FM Radio.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameters are invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_get_frequency(
+ u32 *freq
+ );
+
+/**
+ * fmd_rx_set_stereo_mode() - Sets the stereomode functionality.
+ *
+ * @mode: FMD_STEREOMODE_MONO, FMD_STEREOMODE_STEREO and
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_set_stereo_mode(
+ u8 mode
+ );
+
+/**
+ * fmd_rx_set_stereo_ctrl_blending_rssi() - Sets the stereo blending control setting.
+ *
+ * @min_rssi: Defines the RF level (at the antenna pin) at which the stereo blending
+ * function will stop limiting the channel separation
+ * @max_rssi: Defines the RF level (at the antenna pin) at which the stereo blending
+ * function will start limiting the channel separation.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_set_stereo_ctrl_blending_rssi(
+ u16 min_rssi,
+ u16 max_rssi
+ );
+
+/**
+ * fmd_rx_get_stereo_mode() - Gets the currently used FM mode.
+ *
+ * FMD_STEREOMODE_MONO, FMD_STEREOMODE_STEREO and
+ * FMD_STEREOMODE_AUTO.
+ * @mode: (out) Mode set on FM Radio, stereo or mono.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_rx_get_stereo_mode(
+ u8 *mode
+ );
+
+/**
+ * fmd_rx_get_signal_strength() - Gets the RSSI level of current frequency.
+ *
+ * @strength: (out) RSSI level of current channel.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_get_signal_strength(
+ u16 *strength
+ );
+
+/**
+ * fmd_rx_set_stop_level() - Sets the FM Rx Seek stop level.
+ *
+ * @stoplevel: seek stop level
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_rx_set_stop_level(
+ u16 stoplevel
+ );
+
+/**
+ * fmd_rx_get_stop_level() - Gets the current FM Rx Seek stop level.
+ *
+ * @stoplevel: (out) RSSI Threshold set on FM Radio.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_rx_get_stop_level(
+ u16 *stoplevel
+ );
+
+/**
+ * fmd_rx_seek() - Perform FM Seek.
+ *
+ * Starts searching relative to the actual channel with
+ * a specific direction, stop.
+ * level and optional noise levels
+ * @upwards: scan up
+ *
+ * Returns:
+ * 0, if seek started successfully.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_seek(
+ bool upwards
+ );
+
+/**
+ * fmd_rx_stop_seeking() - Stops a currently active seek or scan band.
+ *
+ * Returns:
+ * 0, if stop seek done successfully.
+ * -ENOEXEC, if preconditions are violated.
+ * -ENOEXEC, if FM Driver is
+ * not currently in Seek or Scan State..
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_stop_seeking(void);
+
+/**
+ * fmd_rx_af_update_start() - Perform AF update.
+ *
+ * This is used to switch to a shortly tune to a AF freq,
+ * measure its RSSI and tune back to the original frequency.
+ * @freq: Alternative frequncy in KHz to be set for AF updation.
+ *
+ * Returns:
+ * -EBUSY, if FM Driver is not in idle state.
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ */
+int fmd_rx_af_update_start(
+ u32 freq
+ );
+
+/**
+ * fmd_rx_get_af_update_result() - Retrive result of AF update.
+ *
+ * Retrive the RSSI level of the Alternative frequency.
+ * @af_level: RSSI level of the Alternative frequency.
+ *
+ * Returns:
+ * -EBUSY, if FM Driver is not in idle state.
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ */
+int fmd_rx_get_af_update_result(
+ u16 *af_level
+ );
+
+/**
+ * fmd_af_switch_start() -Performs AF switch.
+ *
+ * @freq: Frequency to Set in Khz.
+ * @picode:programable id,unique for each station.
+ *
+ * Returns:
+ * -EBUSY, if FM Driver is not in idle state.
+ * 0, if no error and if AF switch started successfully.
+ * -ENOEXEC, if preconditions are violated.
+ */
+int fmd_rx_af_switch_start(
+ u32 freq,
+ u16 picode
+ );
+
+/**
+ * fmd_rx_get_af_switch_results() -Retrieves the results of AF Switch.
+ *
+ * @afs_conclusion: Conclusion of AF switch.
+ * @afs_level: RSSI level of the Alternative frequnecy.
+ * @afs_pi: PI code of the alternative channel (if found).
+ *
+ * Returns:
+ * -EBUSY, if FM Driver is not in idle state.
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ */
+int fmd_rx_get_af_switch_results(
+ u16 *afs_conclusion,
+ u16 *afs_level,
+ u16 *afs_pi
+ );
+
+/**
+ * fmd_rx_scan_band() - Starts Band Scan.
+ *
+ * Starts scanning the active band for the strongest
+ * channels above a threshold.
+ * @max_channels_to_scan: Maximum number of channels to scan.
+ *
+ * Returns:
+ * 0, if scan band started successfully.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_scan_band(
+ u8 max_channels_to_scan
+ );
+
+/**
+ * fmd_rx_get_max_channels_to_scan() - Retreives the maximum channels.
+ *
+ * Retrieves the maximum number of channels that can be found during
+ * band scann.
+ * @max_channels_to_scan: (out) Maximum number of channels to scan.
+ *
+ * Returns:
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if parameter is invalid.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_get_max_channels_to_scan(
+ u8 *max_channels_to_scan
+ );
+
+/**
+ * fmd_rx_get_scan_band_info() - Retrieves Channels found during scanning.
+ *
+ * Retrieves the scanned active band
+ * for the strongest channels above a threshold.
+ * @index: (out) Index value to retrieve the channels.
+ * @numchannels: (out) Number of channels found during Band Scan.
+ * @channels: (out) Channels found during band scan.
+ * @rssi: (out) Rssi of channels found during Band scan.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_get_scan_band_info(
+ u32 index,
+ u16 *numchannels,
+ u16 *channels,
+ u16 *rssi
+ );
+
+/**
+ * fmd_block_scan() - Starts Block Scan.
+ *
+ * Starts block scan for retriving the RSSI level of channels
+ * in the given block.
+ * @start_freq: Starting frequency of the block from where scanning has
+ * to be started.
+ * @stop_freq: End frequency of the block to be scanned.
+ * @antenna: Antenna to be used during scanning.
+ *
+ * Returns:
+ * 0, if scan band started successfully.
+ * -EINVAL, if parameters are invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_block_scan(
+ u32 start_freq,
+ u32 stop_freq,
+ u8 antenna
+ );
+
+/**
+ * fmd_get_block_scan_result() - Retrieves RSSI Level of channels.
+ *
+ * Retrieves the RSSI level of the channels in the block.
+ * @index: (out) Index value to retrieve the channels.
+ * @numchannels: (out) Number of channels found during Band Scan.
+ * @rssi: (out) Rssi of channels found during Band scan.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_get_block_scan_result(
+ u32 index,
+ u16 *numchannels,
+ u16 *rssi
+ );
+
+/**
+ * fmd_rx_get_rds() - Gets the current status of RDS transmission.
+ *
+ * @on: (out) RDS status
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_rx_get_rds(
+ bool *on
+ );
+
+/**
+ * fmd_rx_buffer_set_size() - Sets the number of groups that the data buffer.
+ * can contain and clears the buffer.
+ *
+ * @size: buffer size
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_buffer_set_size(
+ u8 size
+ );
+
+/**
+ * fmd_rx_buffer_set_threshold() - RDS Buffer Threshold level in FM Chip.
+ *
+ * Sets the group number at which the RDS buffer full interrupt must be
+ * generated. The interrupt will be set after reception of the group.
+ * @threshold: threshold level.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_buffer_set_threshold(
+ u8 threshold
+ );
+
+/**
+ * fmd_rx_set_rds() - Enables or disables demodulation of RDS data.
+ *
+ * @on_off_state : Rx Set ON/OFF control
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_rx_set_rds(
+ u8 on_off_state
+ );
+
+/**
+ * fmd_rx_set_rds_group_rejection() - Enables or disables group rejection
+ * in case groups with erroneous blocks are received.
+ *
+ * @on_off_state : Rx Group Rejection ON /OFF control
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+
+int fmd_rx_set_rds_group_rejection(
+ u8 on_off_state
+ );
+
+/**
+ * fmd_rx_get_low_level_rds_groups() - Gets Low level RDS group data.
+ *
+ * @index: RDS group index
+ * @block1: (out) RDS Block 1
+ * @block2: (out) RDS Block 2
+ * @block3: (out) RDS Block 3
+ * @block4: (out) RDS Block 4
+ * @status1: (out) RDS data status 1
+ * @status2: (out) RDS data status 2
+ * @status3: (out) RDS data status 3
+ * @status4: (out) RDS data status 4
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_rx_get_low_level_rds_groups(
+ u8 index,
+ u16 *block1,
+ u16 *block2,
+ u16 *block3,
+ u16 *block4,
+ u8 *status1,
+ u8 *status2,
+ u8 *status3,
+ u8 *status4
+ );
+
+/**
+ * fmd_tx_set_pa() - Enables or disables the Power Amplifier.
+ *
+ * @on: Power Amplifier current state to set
+ *
+ * Returns:
+ * 0, if set Power Amplifier done successfully.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_pa(
+ bool on
+ );
+
+/**
+ * fmd_tx_set_signal_strength() - Sets the RF-level of the output FM signal.
+ *
+ * @strength: Signal strength to be set for FM Tx in dBuV.
+ *
+ * Returns:
+ * 0, if set RSSI Level done successfully.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_signal_strength(
+ u16 strength
+ );
+
+/**
+ * fmd_tx_get_signal_strength() - Retrieves current RSSI of FM Tx.
+ *
+ * @strength: (out) Strength of signal being transmitted in dBuV.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_signal_strength(
+ u16 *strength
+ );
+
+/**
+ * fmd_tx_set_freq_range() - Sets the FM band and specifies the custom band.
+ *
+ * @range: Freq range to set on FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_freq_range(
+ u8 range
+ );
+
+/**
+ * fmd_tx_get_freq_range() - Gets the FM band currently in use.
+ *
+ * @range: (out) Frequency Range set on Fm Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ */
+int fmd_tx_get_freq_range(
+ u8 *range
+ );
+
+/**
+ * fmd_tx_set_grid() - Sets the tuning grid size.
+ *
+ * @grid: FM Grid (50 Khz, 100 Khz, 200 Khz) to be set for FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_grid(
+ u8 grid
+ );
+
+/**
+ * fmd_tx_get_grid() - Gets the current tuning grid size.
+ *
+ * @grid: (out) FM Grid (50 Khz, 100 Khz, 200 Khz) currently set on FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_grid(
+ u8 *grid
+ );
+
+/**
+ * fmd_tx_set_preemphasis() - Sets the Preemphasis characteristic of the Tx.
+ *
+ * @preemphasis: Pre-emphasis level to be set for FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_preemphasis(
+ u8 preemphasis
+ );
+
+/**
+ * fmd_tx_get_preemphasis() - Gets the currently used Preemphasis char of th FM Tx.
+ *
+ * @preemphasis: (out) Preemphasis Level used for FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_preemphasis(
+ u8 *preemphasis
+ );
+
+/**
+ * fmd_tx_set_frequency() - Sets the FM Channel for Tx.
+ *
+ * @freq: Freq to be set for transmission.
+ *
+ * Returns:
+ * 0, if set frequency done successfully.
+ * -EINVAL, if parameters are invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_frequency(
+ u32 freq
+ );
+
+/**
+ * fmd_rx_get_frequency() - Gets the currently used Channel for Tx.
+ *
+ * @freq: (out) Frequency set on FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameters are invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_get_frequency(
+ u32 *freq
+ );
+
+/**
+ * fmd_tx_enable_stereo_mode() - Sets Stereo mode state for TX.
+ *
+ * @enable_stereo_mode: Flag indicating enabling or disabling Stereo mode.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_enable_stereo_mode(
+ bool enable_stereo_mode
+ );
+
+/**
+ * fmd_tx_get_stereo_mode() - Gets the currently used FM Tx stereo mode.
+ *
+ * @stereo_mode: (out) Stereo Mode state set on FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -EINVAL, if parameter is invalid.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_stereo_mode(
+ bool *stereo_mode
+ );
+
+/**
+ * fmd_tx_set_pilot_deviation() - Sets pilot deviation in HZ
+ *
+ * @deviation: Pilot deviation in HZ to set on FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_pilot_deviation(
+ u16 deviation
+ );
+
+/**
+ * fmd_tx_get_pilot_deviation() - Retrieves the current pilot deviation.
+ *
+ * @deviation: (out) Pilot deviation set on FM Tx.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_pilot_deviation(
+ u16 *deviation
+ );
+
+/**
+ * fmd_tx_set_rds_deviation() - Sets Rds deviation in HZ.
+ *
+ * @deviation: RDS deviation in HZ.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_rds_deviation(
+ u16 deviation
+ );
+
+/**
+ * fmd_tx_get_rds_deviation() - Retrieves the current Rds deviation.
+ *
+ * @deviation: (out) RDS deviation currently set.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_rds_deviation(
+ u16 *deviation
+ );
+
+/**
+ * fmd_tx_set_rds() - Enables or disables RDS transmission for Tx.
+ *
+ * @on: Boolean - RDS ON
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_rds(
+ bool on
+ );
+
+/**
+ * fmd_rx_get_rds() - Gets the current status of RDS transmission for FM Tx.
+ *
+ * @on: (out) Rds enabled or disabled.
+ *
+ *Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_tx_get_rds(
+ bool *on
+ );
+
+/**
+ * fmd_tx_set_group() - Programs a grp on a certain position in the RDS buffer.
+ *
+ * @position: RDS group position
+ * @block1: Data to be transmitted in Block 1
+ * @block2: Data to be transmitted in Block 2
+ * @block3: Data to be transmitted in Block 3
+ * @block4: Data to be transmitted in Block 4
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if parameters are invalid.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_set_group(
+ u16 position,
+ u8 *block1,
+ u8 *block2,
+ u8 *block3,
+ u8 *block4
+ );
+
+/**
+ * fmd_tx_buffer_set_size() - Controls the size of the RDS buffer in groups.
+ *
+ * @buffer_size: RDS buffer size.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_tx_buffer_set_size(
+ u16 buffer_size
+ );
+
+/**
+ * fmd_set_volume() - Sets the receive audio volume.
+ *
+ * @volume: Audio volume level
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_volume(
+ u8 volume
+ );
+
+/**
+ * fmd_get_volume() - Retrives the current audio volume.
+ *
+ * @volume: Analog Volume level.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if parameter is invalid.
+ * -EBUSY, if FM Driver is not in idle state.
+ */
+int fmd_get_volume(
+ u8 *volume
+ );
+
+/**
+ * fmd_set_balance() - Controls the receiver audio balance.
+ *
+ * @balance: Audio balance level
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_balance(
+ s8 balance
+ );
+
+/**
+ * fmd_set_mute() - Enables or disables muting of the analog audio(DAC).
+ *
+ * @mute_on: bool of mute on
+ *
+ * Returns:
+ * 0, if mute done successfully.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_mute(
+ bool mute_on
+ );
+
+/**
+ * fmd_ext_set_mute() - Enables or disables muting of the audio channel.
+ *
+ * @mute_on: bool to Mute
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_ext_set_mute(
+ bool mute_on
+ );
+
+/**
+ * fmd_power_up() - Puts the system in Powerup state.
+ *
+ * Returns:
+ * 0, if power up command sent successfully to chip.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_power_up(void);
+
+/**
+ * fmd_goto_standby() - Puts the system in standby mode.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_goto_standby(void);
+
+/**
+ * fmd_goto_power_down() - Puts the system in Powerdown mode.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_goto_power_down(void);
+
+/**
+ * fmd_select_ref_clk() - Selects the FM reference clock.
+ *
+ * @ref_clk: Ref Clock.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_select_ref_clk(
+ u16 ref_clk
+ );
+
+/**
+ * fmd_set_ref_clk_pll() - Sets the freq of Referece Clock.
+ *
+ * Sets frequency and offset correction properties of the external
+ * reference clock of the PLL
+ * @freq: PLL Frequency/ 2 in kHz.
+ *
+ * Returns:
+ * 0, if no error.
+ * -ENOEXEC, if preconditions are violated.
+ * -EBUSY, if FM Driver is not in idle state.
+ * -EINVAL, if wrong response received from chip.
+ */
+int fmd_set_ref_clk_pll(
+ u16 freq
+ );
+
+/**
+ * fmd_send_fm_ip_enable()- Enables the FM IP.
+ *
+ * Returns:
+ * 0: If there is no error.
+ * -ETIME: Otherwise
+ */
+int fmd_send_fm_ip_enable(void);
+
+/**
+ * fmd_send_fm_ip_disable()- Disables the FM IP.
+ *
+ * Returns:
+ * 0, If there is no error.
+ * -ETIME: Otherwise
+ */
+int fmd_send_fm_ip_disable(void);
+
+/**
+ * fmd_send_fm_firmware() - Send the FM Firmware File to Device.
+ *
+ * @fw_buffer: Firmware to be downloaded.
+ * @fw_size: Size of firmware to be downloaded.
+ *
+ * Returns:
+ * 0, If there is no error.
+ * -ETIME: Otherwise
+ */
+int fmd_send_fm_firmware(
+ u8 *fw_buffer,
+ u16 fw_size
+ );
+
+/**
+ * fmd_int_bufferfull() - RDS Groups availabe for reading by Host.
+ *
+ * Gets the number of groups that are available in the
+ * buffer. This function is called in RX mode to read RDS groups.
+ * @number_of_rds_groups: Number of RDS groups ready to
+ * be read from the Host.
+ *
+ * Returns:
+ * 0, If there is no error.
+ * corresponding error Otherwise
+ */
+int fmd_int_bufferfull(
+ u16 *number_of_rds_groups
+ );
+
+/**
+ * fmd_start_rds_thread() - Starts the RDS Thread for receiving RDS Data.
+ *
+ * This is started by Application when it wants to receive RDS Data.
+ * @cb_func: Callback function for receiving RDS Data
+ */
+void fmd_start_rds_thread(
+ cg2900_fm_rds_cb cb_func
+ );
+/**
+ * fmd_stop_rds_thread() - Stops the RDS Thread when Application does not
+ * want to receive RDS.
+ */
+void fmd_stop_rds_thread(void);
+
+/**
+ * fmd_get_rds_sem() - Block on RDS Semaphore.
+ * Till irpt_BufferFull is received, RDS Task is blocked.
+ */
+void fmd_get_rds_sem(void);
+
+/**
+ * fmd_set_rds_sem() - Unblock on RDS Semaphore.
+ * on receiving irpt_BufferFull, RDS Task is un-blocked.
+ */
+void fmd_set_rds_sem(void);
+
+/**
+ * fmd_set_dev() - Set FM device.
+ *
+ * @dev: FM Device
+ *
+ * Returns:
+ * 0, If there is no error.
+ * corresponding error Otherwise
+ */
+int fmd_set_dev(
+ struct device *dev
+ );
+
+/**
+ * fmd_set_test_tone_generator_status()- Sets the Test Tone Generator.
+ *
+ * This function is used to enable/disable the Internal Tone Generator of
+ * CG2900.
+ * @test_tone_status: Status of tone generator.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int fmd_set_test_tone_generator_status(
+ u8 test_tone_status
+ );
+
+/**
+ * fmd_test_tone_connect()- Connect Audio outputs/inputs.
+ *
+ * This function connects the audio outputs/inputs of the
+ * Internal Tone Generator of CG2900.
+ * @left_audio_mode: Left Audio Output Mode.
+ * @right_audio_mode: Right Audio Output Mode.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int fmd_test_tone_connect(
+ u8 left_audio_mode,
+ u8 right_audio_mode
+ );
+
+/**
+ * fmd_test_tone_set_params()- Sets the Test Tone Parameters.
+ *
+ * This function is used to set the parameters of
+ * the Internal Tone Generator of CG2900.
+ * @tone_gen: Tone to be configured (Tone 1 or Tone 2)
+ * @frequency: Frequency of the tone.
+ * @volume: Volume of the tone.
+ * @phase_offset: Phase offset of the tone.
+ * @dc: DC to add to tone.
+ * @waveform: Waveform to generate, sine or pulse.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int fmd_test_tone_set_params(
+ u8 tone_gen,
+ u16 frequency,
+ u16 volume,
+ u16 phase_offset,
+ u16 dc,
+ u8 waveform
+ );
+
+/**
+ * fmd_rx_set_deemphasis()- Connect Audio outputs/inputs.
+ *
+ * This function sets the de-emphasis filter to the
+ * specified de-empahsis level.
+ * @deemphasis: De-emphasis level to set.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int fmd_rx_set_deemphasis(
+ u8 deemphasis
+ );
+
+/**
+ * fmd_limiter_setcontrol()- Sets the Limiter Controls.
+ *
+ * This function sets the limiter control.
+ * @audio_deviation: Limiting level of Audio Deviation.
+ * @notification_hold_off_time: Minimum time between
+ * two limiting interrupts.
+ *
+ * Returns:
+ * 0, if operation completed successfully.
+ * -EINVAL, otherwise.
+ */
+int fmd_limiter_setcontrol(
+ u16 audio_deviation,
+ u16 notification_hold_off_time
+ );
+
+#endif /* _FMDRIVER_H_ */
diff --git a/drivers/media/radio/CG2900/radio-cg2900.c b/drivers/media/radio/CG2900/radio-cg2900.c
new file mode 100644
index 00000000000..9ccb4e6b85d
--- /dev/null
+++ b/drivers/media/radio/CG2900/radio-cg2900.c
@@ -0,0 +1,3024 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2010
+ *
+ * Linux Wrapper for V4l2 FM Driver for CG2900.
+ *
+ * Author: Hemant Gupta <hemant.gupta@stericsson.com> for ST-Ericsson.
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#include<linux/init.h>
+#include<linux/videodev2.h>
+#include<media/v4l2-ioctl.h>
+#include<media/v4l2-common.h>
+#include<linux/module.h>
+#include <linux/platform_device.h>
+#include<linux/string.h>
+#include<linux/wait.h>
+#include"cg2900.h"
+#include"cg2900_fm_driver.h"
+
+#define RADIO_CG2900_VERSION KERNEL_VERSION(1, 1, 0)
+#define BANNER "ST-Ericsson FM Radio Card driver v1.1.0"
+
+#define FMR_HZ_TO_MHZ_CONVERTER 1000000
+#define FMR_EU_US_LOW_FREQ_IN_MHZ 87.5
+#define FMR_EU_US_HIGH_FREQ_IN_MHZ 108
+#define FMR_JAPAN_LOW_FREQ_IN_MHZ 76
+#define FMR_JAPAN_HIGH_FREQ_IN_MHZ 90
+#define FMR_CHINA_LOW_FREQ_IN_MHZ 70
+#define FMR_CHINA_HIGH_FREQ_IN_MHZ 108
+#define FMR_MAX_BLOCK_SCAN_CHANNELS 198
+#define FMR_CHINA_GRID_IN_HZ 50000
+#define FMR_EUROPE_GRID_IN_HZ 100000
+#define FMR_USA_GRID_IN_HZ 200000
+#define FMR_AF_SWITCH_DATA_SIZE 2
+#define FMR_BLOCK_SCAN_DATA_SIZE 2
+#define FMR_GET_INTERRUPT_DATA_SIZE 2
+#define FMR_TEST_TONE_CONNECT_DATA_SIZE 2
+#define FMR_TEST_TONE_SET_PARAMS_DATA_SIZE 6
+
+/* freq in Hz to V4l2 freq (units of 62.5Hz) */
+#define HZ_TO_V4L2(X) (2*(X)/125)
+/* V4l2 freq (units of 62.5Hz) to freq in Hz */
+#define V4L2_TO_HZ(X) (((X)*125)/(2))
+
+static int cg2900_open(
+ struct file *file
+ );
+static int cg2900_release(
+ struct file *file
+ );
+static ssize_t cg2900_read(
+ struct file *file,
+ char __user *data,
+ size_t count,
+ loff_t *pos
+ );
+static unsigned int cg2900_poll(
+ struct file *file,
+ struct poll_table_struct *wait
+ );
+static int vidioc_querycap(
+ struct file *file,
+ void *priv,
+ struct v4l2_capability *query_caps
+ );
+static int vidioc_get_tuner(
+ struct file *file,
+ void *priv,
+ struct v4l2_tuner *tuner
+ );
+static int vidioc_set_tuner(
+ struct file *file,
+ void *priv,
+ struct v4l2_tuner *tuner
+ );
+static int vidioc_get_modulator(
+ struct file *file,
+ void *priv,
+ struct v4l2_modulator *modulator
+ );
+static int vidioc_set_modulator(
+ struct file *file,
+ void *priv,
+ struct v4l2_modulator *modulator
+ );
+static int vidioc_get_frequency(
+ struct file *file,
+ void *priv,
+ struct v4l2_frequency *freq
+ );
+static int vidioc_set_frequency(
+ struct file *file,
+ void *priv,
+ struct v4l2_frequency *freq
+ );
+static int vidioc_query_ctrl(
+ struct file *file,
+ void *priv,
+ struct v4l2_queryctrl *query_ctrl
+ );
+static int vidioc_get_ctrl(
+ struct file *file,
+ void *priv,
+ struct v4l2_control *ctrl
+ );
+static int vidioc_set_ctrl(
+ struct file *file,
+ void *priv,
+ struct v4l2_control *ctrl
+ );
+static int vidioc_get_ext_ctrls(
+ struct file *file,
+ void *priv,
+ struct v4l2_ext_controls *ext_ctrl
+ );
+static int vidioc_set_ext_ctrls(
+ struct file *file,
+ void *priv,
+ struct v4l2_ext_controls *ext_ctrl
+ );
+static int vidioc_set_hw_freq_seek(
+ struct file *file,
+ void *priv,
+ struct v4l2_hw_freq_seek *freq_seek
+ );
+static int vidioc_get_audio(
+ struct file *file,
+ void *priv,
+ struct v4l2_audio *audio
+ );
+static int vidioc_set_audio(
+ struct file *file,
+ void *priv,
+ struct v4l2_audio *audio
+ );
+static int vidioc_get_input(
+ struct file *filp,
+ void *priv,
+ unsigned int *input
+ );
+static int vidioc_set_input(
+ struct file *filp,
+ void *priv,
+ unsigned int input
+ );
+static void cg2900_convert_err_to_v4l2(
+ char status_byte,
+ char *out_byte
+ );
+static int cg2900_map_event_to_v4l2(
+ u8 fm_event
+ );
+
+static u32 freq_low;
+static u32 freq_high;
+
+/* Module Parameters */
+static int radio_nr = -1;
+static int grid;
+static int band;
+
+/* cg2900_poll_queue - Main Wait Queue for polling (Scan/Seek) */
+static wait_queue_head_t cg2900_poll_queue;
+
+struct sk_buff_head fm_interrupt_queue;
+
+/**
+ * enum fm_seek_status - Seek status of FM Radio.
+ *
+ * @FMR_SEEK_NONE: No seek in progress.
+ * @FMR_SEEK_IN_PROGRESS: Seek is in progress.
+ *
+ * Seek status of FM Radio.
+ */
+enum fm_seek_status {
+ FMR_SEEK_NONE,
+ FMR_SEEK_IN_PROGRESS
+};
+
+/**
+ * enum fm_power_state - Power states of FM Radio.
+ *
+ * @FMR_SWITCH_OFF: FM Radio is switched off.
+ * @FMR_SWITCH_ON: FM Radio is switched on.
+ * @FMR_STANDBY: FM Radio in standby state.
+ *
+ * Power states of FM Radio.
+ */
+enum fm_power_state {
+ FMR_SWITCH_OFF,
+ FMR_SWITCH_ON,
+ FMR_STANDBY
+};
+
+/**
+ * struct cg2900_device - Stores FM Device Info.
+ *
+ * @state: state of FM Radio
+ * @muted: FM Radio Mute/Unmute status
+ * @seekstatus: seek status
+ * @rx_rds_enabled: Rds enable/disable status for FM Rx
+ * @tx_rds_enabled: Rds enable/disable status for FM Tx
+ * @rx_stereo_status: Stereo Mode status for FM Rx
+ * @tx_stereo_status: Stereo Mode status for FM Tx
+ * @volume: Analog Volume Gain of FM Radio
+ * @rssi_threshold: rssi Thresold set on FM Radio
+ * @frequency: Frequency tuned on FM Radio in V4L2 Format
+ * @audiopath: Audio Balance
+ * @wait_on_read_queue: Flag for waiting on read queue.
+ * @fm_mode: Enum for storing the current FM Mode.
+ *
+ * FM Driver Information Structure.
+ */
+struct cg2900_device {
+ u8 state;
+ u8 muted;
+ u8 seekstatus;
+ bool rx_rds_enabled;
+ bool tx_rds_enabled;
+ bool rx_stereo_status;
+ bool tx_stereo_status;
+ int volume;
+ u16 rssi_threshold;
+ u32 frequency;
+ u32 audiopath;
+ bool wait_on_read_queue;
+ enum cg2900_fm_mode fm_mode;
+};
+
+/* Global Structure to store the maintain FM Driver device info */
+static struct cg2900_device cg2900_device;
+
+/* V4l2 File Operation Structure */
+static const struct v4l2_file_operations cg2900_fops = {
+ .owner = THIS_MODULE,
+ .open = cg2900_open,
+ .release = cg2900_release,
+ .read = cg2900_read,
+ .poll = cg2900_poll,
+ .ioctl = video_ioctl2,
+};
+
+/* V4L2 IOCTL Operation Structure */
+static const struct v4l2_ioctl_ops cg2900_ioctl_ops = {
+ .vidioc_querycap = vidioc_querycap,
+ .vidioc_g_tuner = vidioc_get_tuner,
+ .vidioc_s_tuner = vidioc_set_tuner,
+ .vidioc_g_modulator = vidioc_get_modulator,
+ .vidioc_s_modulator = vidioc_set_modulator,
+ .vidioc_g_frequency = vidioc_get_frequency,
+ .vidioc_s_frequency = vidioc_set_frequency,
+ .vidioc_queryctrl = vidioc_query_ctrl,
+ .vidioc_g_ctrl = vidioc_get_ctrl,
+ .vidioc_s_ctrl = vidioc_set_ctrl,
+ .vidioc_g_ext_ctrls = vidioc_get_ext_ctrls,
+ .vidioc_s_ext_ctrls = vidioc_set_ext_ctrls,
+ .vidioc_s_hw_freq_seek = vidioc_set_hw_freq_seek,
+ .vidioc_g_audio = vidioc_get_audio,
+ .vidioc_s_audio = vidioc_set_audio,
+ .vidioc_g_input = vidioc_get_input,
+ .vidioc_s_input = vidioc_set_input,
+};
+
+/* V4L2 Video Device Structure */
+static struct video_device cg2900_video_device = {
+ .name = "STE CG2900 FM Rx/Tx Radio",
+ .fops = &cg2900_fops,
+ .ioctl_ops = &cg2900_ioctl_ops,
+ .release = video_device_release_empty,
+};
+
+static u16 no_of_scan_freq;
+static u16 no_of_block_scan_freq;
+static u32 scanfreq_rssi_level[MAX_CHANNELS_TO_SCAN];
+static u16 block_scan_rssi_level[MAX_CHANNELS_FOR_BLOCK_SCAN];
+static u32 scanfreq[MAX_CHANNELS_TO_SCAN];
+static struct mutex fm_mutex;
+static spinlock_t fm_spinlock;
+static int users;
+
+/**
+ * vidioc_querycap()- Query FM Driver Capabilities.
+ *
+ * This function is used to query the capabilities of the
+ * FM Driver. This function is called when the application issues the IOCTL
+ * VIDIOC_QUERYCAP.
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @query_caps: v4l2_capability structure.
+ *
+ * Returns: 0
+ */
+static int vidioc_querycap(
+ struct file *file,
+ void *priv,
+ struct v4l2_capability *query_caps
+ )
+{
+ FM_INFO_REPORT("vidioc_querycap");
+ memset(
+ query_caps,
+ 0,
+ sizeof(*query_caps)
+ );
+ strlcpy(
+ query_caps->driver,
+ "CG2900 Driver",
+ sizeof(query_caps->driver)
+ );
+ strlcpy(
+ query_caps->card,
+ "CG2900 FM Radio",
+ sizeof(query_caps->card)
+ );
+ strcpy(
+ query_caps->bus_info,
+ "platform"
+ );
+ query_caps->version = RADIO_CG2900_VERSION;
+ query_caps->capabilities =
+ V4L2_CAP_TUNER |
+ V4L2_CAP_MODULATOR |
+ V4L2_CAP_RADIO |
+ V4L2_CAP_READWRITE |
+ V4L2_CAP_RDS_CAPTURE |
+ V4L2_CAP_HW_FREQ_SEEK |
+ V4L2_CAP_RDS_OUTPUT;
+ FM_DEBUG_REPORT("vidioc_querycap returning 0");
+ return 0;
+}
+
+/**
+ * vidioc_get_tuner()- Get FM Tuner Features.
+ *
+ * This function is used to get the tuner features.
+ * This function is called when the application issues the IOCTL
+ * VIDIOC_G_TUNER
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @tuner: v4l2_tuner structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_tuner(
+ struct file *file,
+ void *priv,
+ struct v4l2_tuner *tuner
+ )
+{
+ int status = 0;
+ u8 mode;
+ bool rds_enabled;
+ u16 rssi;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_get_tuner");
+
+ if (tuner->index > 0) {
+ FM_ERR_REPORT("vidioc_get_tuner: Only 1 tuner supported");
+ goto error;
+ }
+
+ memset(tuner, 0, sizeof(*tuner));
+ strcpy(tuner->name, "CG2900 FM Receiver");
+ tuner->type = V4L2_TUNER_RADIO;
+ tuner->rangelow = HZ_TO_V4L2(freq_low);
+ tuner->rangehigh = HZ_TO_V4L2(freq_high);
+ tuner->capability =
+ V4L2_TUNER_CAP_LOW /* Frequency steps = 1/16 kHz */
+ | V4L2_TUNER_CAP_STEREO /* Can receive stereo */
+ | V4L2_TUNER_CAP_RDS; /* Supports RDS Capture */
+
+ if (cg2900_device.fm_mode == CG2900_FM_RX_MODE) {
+
+ status = cg2900_fm_get_mode(&mode);
+
+ FM_DEBUG_REPORT("vidioc_get_tuner: mode = %x, ", mode);
+
+ if (0 != status) {
+ /* Get mode API failed, set mode to mono */
+ tuner->audmode = V4L2_TUNER_MODE_MONO;
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
+ goto error;
+ }
+
+ switch (mode) {
+ case CG2900_MODE_STEREO:
+ tuner->audmode = V4L2_TUNER_MODE_STEREO;
+ tuner->rxsubchans = V4L2_TUNER_SUB_STEREO;
+ break;
+ case CG2900_MODE_MONO:
+ default:
+ tuner->audmode = V4L2_TUNER_MODE_MONO;
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
+ break;
+ }
+
+ status = cg2900_fm_get_rds_status(&rds_enabled);
+
+ if (0 != status) {
+ tuner->rxsubchans &= ~V4L2_TUNER_SUB_RDS;
+ goto error;
+ }
+
+ if (rds_enabled)
+ tuner->rxsubchans |= V4L2_TUNER_SUB_RDS;
+ else
+ tuner->rxsubchans &= ~V4L2_TUNER_SUB_RDS;
+ } else {
+ tuner->audmode = V4L2_TUNER_MODE_MONO;
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
+ }
+
+ if (cg2900_device.fm_mode == CG2900_FM_RX_MODE) {
+ status = cg2900_fm_get_signal_strength(&rssi);
+
+ if (0 != status) {
+ tuner->signal = 0;
+ goto error;
+ }
+ tuner->signal = rssi;
+ } else {
+ tuner->signal = 0;
+ }
+
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_get_tuner: returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_set_tuner()- Set FM Tuner Features.
+ *
+ * This function is used to set the tuner features.
+ * It also sets the default FM Rx settings.
+ * This function is called when the application issues the IOCTL
+ * VIDIOC_S_TUNER
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @tuner: v4l2_tuner structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_tuner(
+ struct file *file,
+ void *priv,
+ struct v4l2_tuner *tuner
+ )
+{
+ bool rds_status = false;
+ bool stereo_status = false;
+ int status = 0;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_set_tuner");
+ if (tuner->index != 0) {
+ FM_ERR_REPORT("vidioc_set_tuner: Only 1 tuner supported");
+ goto error;
+ }
+
+ if (cg2900_device.fm_mode != CG2900_FM_RX_MODE) {
+ /*
+ * FM Rx mode should be configured
+ * as earlier mode was not FM Rx
+ */
+ if (CG2900_FM_BAND_US_EU == band) {
+ freq_low = FMR_EU_US_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_EU_US_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ } else if (CG2900_FM_BAND_JAPAN == band) {
+ freq_low = FMR_JAPAN_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_JAPAN_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ } else if (CG2900_FM_BAND_CHINA == band) {
+ freq_low = FMR_CHINA_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_CHINA_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ }
+ cg2900_device.fm_mode = CG2900_FM_RX_MODE;
+ cg2900_device.rx_rds_enabled =
+ (tuner->rxsubchans & V4L2_TUNER_SUB_RDS) ?
+ true : false;
+ if (tuner->rxsubchans & V4L2_TUNER_SUB_STEREO)
+ stereo_status = true;
+ else if (tuner->rxsubchans & V4L2_TUNER_SUB_MONO)
+ stereo_status = false;
+ cg2900_device.rx_stereo_status = stereo_status;
+ status = cg2900_fm_set_rx_default_settings(freq_low,
+ band,
+ grid,
+ cg2900_device.rx_rds_enabled,
+ cg2900_device.rx_stereo_status);
+
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_tuner: "
+ "cg2900_fm_set_rx_default_settings returned "
+ " %d", status);
+ goto error;
+ }
+ status = cg2900_fm_set_rssi_threshold(
+ cg2900_device.rssi_threshold);
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_tuner: "
+ "cg2900_fm_set_rssi_threshold returned "
+ " %d", status);
+ goto error;
+ }
+ } else {
+ /*
+ * Mode was FM Rx only, change the RDS settings or stereo mode
+ * if they are changed by application
+ */
+ rds_status = (tuner->rxsubchans & V4L2_TUNER_SUB_RDS) ?
+ true : false;
+ if (tuner->rxsubchans & V4L2_TUNER_SUB_STEREO)
+ stereo_status = true;
+ else if (tuner->rxsubchans & V4L2_TUNER_SUB_MONO)
+ stereo_status = false;
+ if (stereo_status != cg2900_device.rx_stereo_status) {
+ cg2900_device.rx_stereo_status = stereo_status;
+ if (stereo_status)
+ status =
+ cg2900_fm_set_mode(
+ FMD_STEREOMODE_BLENDING);
+ else
+ status = cg2900_fm_set_mode(
+ FMD_STEREOMODE_MONO);
+
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_tuner: "
+ "cg2900_fm_set_mode returned "
+ " %d", status);
+ goto error;
+ }
+ }
+ if (rds_status != cg2900_device.rx_rds_enabled) {
+ cg2900_device.rx_rds_enabled = rds_status;
+ if (rds_status)
+ status = cg2900_fm_rds_on();
+ else
+ status = cg2900_fm_rds_off();
+
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_tuner: "
+ "cg2900_fm_rds returned "
+ " %d", status);
+ goto error;
+ }
+ }
+ }
+
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_set_tuner: returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_get_modulator()- Get FM Modulator Features.
+ *
+ * This function is used to get the modulator features.
+ * This function is called when the application issues the IOCTL
+ * VIDIOC_G_MODULATOR
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @modulator: v4l2_modulator structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_modulator(
+ struct file *file,
+ void *priv,
+ struct v4l2_modulator *modulator
+ )
+{
+ int status = 0;
+ bool rds_enabled;
+ u8 mode;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_get_modulator");
+
+ if (modulator->index > 0) {
+ FM_ERR_REPORT("vidioc_get_modulator: Only 1 "
+ "modulator supported");
+ goto error;
+ }
+
+ memset(modulator, 0, sizeof(*modulator));
+ strcpy(modulator->name, "CG2900 FM Transmitter");
+ modulator->rangelow = freq_low;
+ modulator->rangehigh = freq_high;
+ modulator->capability = V4L2_TUNER_CAP_NORM /* Freq steps = 1/16 kHz */
+ | V4L2_TUNER_CAP_STEREO /* Can receive stereo */
+ | V4L2_TUNER_CAP_RDS; /* Supports RDS Capture */
+
+ if (cg2900_device.fm_mode == CG2900_FM_TX_MODE) {
+ status = cg2900_fm_get_mode(&mode);
+ FM_DEBUG_REPORT("vidioc_get_modulator: mode = %x", mode);
+ if (0 != status) {
+ /* Get mode API failed, set mode to mono */
+ modulator->txsubchans = V4L2_TUNER_SUB_MONO;
+ goto error;
+ }
+ switch (mode) {
+ /* Stereo */
+ case CG2900_MODE_STEREO:
+ modulator->txsubchans = V4L2_TUNER_SUB_STEREO;
+ break;
+ /* Mono */
+ case CG2900_MODE_MONO:
+ modulator->txsubchans = V4L2_TUNER_SUB_MONO;
+ break;
+ /* Switching or Blending, set mode as Stereo */
+ default:
+ modulator->txsubchans = V4L2_TUNER_SUB_STEREO;
+ }
+ status = cg2900_fm_get_rds_status(&rds_enabled);
+ if (0 != status) {
+ modulator->txsubchans &= ~V4L2_TUNER_SUB_RDS;
+ goto error;
+ }
+ if (rds_enabled)
+ modulator->txsubchans |= V4L2_TUNER_SUB_RDS;
+ else
+ modulator->txsubchans &= ~V4L2_TUNER_SUB_RDS;
+ } else
+ modulator->txsubchans = V4L2_TUNER_SUB_MONO;
+
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_get_modulator: returning %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_set_modulator()- Set FM Modulator Features.
+ *
+ * This function is used to set the Modulaotr features.
+ * It also sets the default FM Tx settings.
+ * This function is called when the application issues the IOCTL
+ * VIDIOC_S_MODULATOR
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @modulator: v4l2_modulator structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_modulator(
+ struct file *file,
+ void *priv,
+ struct v4l2_modulator *modulator
+ )
+{
+ bool rds_status = false;
+ bool stereo_status = false;
+ int status = 0;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_set_modulator");
+ if (modulator->index != 0) {
+ FM_ERR_REPORT("vidioc_set_modulator: Only 1 "
+ "modulator supported");
+ goto error;
+ }
+
+ if (cg2900_device.fm_mode != CG2900_FM_TX_MODE) {
+ /*
+ * FM Tx mode should be configured as
+ * earlier mode was not FM Tx
+ */
+ if (band == CG2900_FM_BAND_US_EU) {
+ freq_low = FMR_EU_US_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_EU_US_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ } else if (band == CG2900_FM_BAND_JAPAN) {
+ freq_low = FMR_JAPAN_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_JAPAN_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ } else if (band == CG2900_FM_BAND_CHINA) {
+ freq_low = FMR_CHINA_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_CHINA_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ }
+ cg2900_device.fm_mode = CG2900_FM_TX_MODE;
+ cg2900_device.rx_rds_enabled = false;
+ cg2900_device.tx_rds_enabled =
+ (modulator->txsubchans & V4L2_TUNER_SUB_RDS) ?
+ true : false;
+ if (modulator->txsubchans & V4L2_TUNER_SUB_STEREO)
+ stereo_status = true;
+ else if (modulator->txsubchans & V4L2_TUNER_SUB_MONO)
+ stereo_status = false;
+ cg2900_device.tx_stereo_status = stereo_status;
+
+ status = cg2900_fm_set_tx_default_settings(freq_low,
+ band,
+ grid,
+ cg2900_device.tx_rds_enabled,
+ cg2900_device.
+ tx_stereo_status);
+
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_modulator: "
+ "cg2900_fm_set_tx_default_settings returned "
+ " %d", status);
+ goto error;
+ }
+ } else {
+ /*
+ * Mode was FM Tx only, change the RDS settings or stereo mode
+ * if they are changed by application
+ */
+ rds_status = (modulator->txsubchans & V4L2_TUNER_SUB_RDS) ?
+ true : false;
+ if (modulator->txsubchans & V4L2_TUNER_SUB_STEREO)
+ stereo_status = true;
+ else if (modulator->txsubchans & V4L2_TUNER_SUB_MONO)
+ stereo_status = false;
+ if (stereo_status != cg2900_device.tx_stereo_status) {
+ cg2900_device.tx_stereo_status = stereo_status;
+ status = cg2900_fm_set_mode(stereo_status);
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_modulator: "
+ "cg2900_fm_set_mode returned "
+ " %d", status);
+ goto error;
+ }
+ }
+ if (rds_status != cg2900_device.tx_rds_enabled) {
+ cg2900_device.tx_rds_enabled = rds_status;
+ status = cg2900_fm_tx_rds(rds_status);
+ if (0 != status) {
+ FM_ERR_REPORT("vidioc_set_modulator: "
+ "cg2900_fm_tx_rds returned "
+ " %d", status);
+ goto error;
+ }
+ }
+ }
+
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_set_modulator: returning %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_get_frequency()- Get the Current FM Frequnecy.
+ *
+ * This function is used to get the currently tuned
+ * frequency on FM Radio. This function is called when the application
+ * issues the IOCTL VIDIOC_G_FREQUENCY
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @freq: v4l2_frequency structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_frequency(
+ struct file *file,
+ void *priv,
+ struct v4l2_frequency *freq
+ )
+{
+ int status;
+ u32 frequency;
+ int ret_val = -EINVAL;
+ struct sk_buff *skb;
+
+ FM_INFO_REPORT("vidioc_get_frequency: Status = %d",
+ cg2900_device.seekstatus);
+
+ status = cg2900_fm_get_frequency(&frequency);
+
+ if (0 != status) {
+ freq->frequency = cg2900_device.frequency;
+ goto error;
+ }
+
+ if (cg2900_device.seekstatus == FMR_SEEK_IN_PROGRESS) {
+ if (skb_queue_empty(&fm_interrupt_queue)) {
+ /* No Interrupt, bad case */
+ FM_ERR_REPORT("vidioc_get_frequency: "
+ "No Interrupt to read");
+ fm_event = CG2900_EVENT_NO_EVENT;
+ goto error;
+ }
+ spin_lock(&fm_spinlock);
+ skb = skb_dequeue(&fm_interrupt_queue);
+ spin_unlock(&fm_spinlock);
+ if (!skb) {
+ /* No Interrupt, bad case */
+ FM_ERR_REPORT("vidioc_get_frequency: "
+ "No Interrupt to read");
+ fm_event = CG2900_EVENT_NO_EVENT;
+ goto error;
+ }
+ fm_event = (u8)skb->data[0];
+ FM_DEBUG_REPORT("vidioc_get_frequency: Interrupt = %x",
+ fm_event);
+ /* Check if seek is finished or not */
+ if (CG2900_EVENT_SEARCH_CHANNEL_FOUND == fm_event) {
+ /* seek is finished */
+ spin_lock(&fm_spinlock);
+ cg2900_device.frequency = HZ_TO_V4L2(frequency);
+ freq->frequency = cg2900_device.frequency;
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ fm_event = CG2900_EVENT_NO_EVENT;
+ kfree_skb(skb);
+ spin_unlock(&fm_spinlock);
+ } else {
+ /* Some other interrupt, queue it back */
+ spin_lock(&fm_spinlock);
+ skb_queue_head(&fm_interrupt_queue, skb);
+ spin_unlock(&fm_spinlock);
+ }
+ } else {
+ spin_lock(&fm_spinlock);
+ cg2900_device.frequency = HZ_TO_V4L2(frequency);
+ freq->frequency = cg2900_device.frequency;
+ spin_unlock(&fm_spinlock);
+ }
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_get_frequency: returning = %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_set_frequency()- Set the FM Frequnecy.
+ *
+ * This function is used to set the frequency
+ * on FM Radio. This function is called when the application
+ * issues the IOCTL VIDIOC_S_FREQUENCY
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @freq: v4l2_frequency structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_frequency(
+ struct file *file,
+ void *priv,
+ struct v4l2_frequency *freq
+ )
+{
+ u32 frequency = freq->frequency;
+ u32 freq_low, freq_high;
+ int status;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_set_frequency: Frequency = "
+ "%d ", V4L2_TO_HZ(frequency));
+
+ /* Check which band is set currently */
+ switch (band) {
+ case CG2900_FM_BAND_US_EU:
+ freq_low = FMR_EU_US_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_EU_US_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ break;
+
+ case CG2900_FM_BAND_CHINA:
+ freq_low = FMR_CHINA_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_CHINA_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ break;
+
+ case CG2900_FM_BAND_JAPAN:
+ freq_low = FMR_JAPAN_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_JAPAN_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ break;
+
+ default:
+ /* Set to US_MAX and CHINA_MIN band */
+ freq_low = FMR_CHINA_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ freq_high = FMR_EU_US_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ }
+
+ /* Check if the frequency set is out of current band */
+ if ((V4L2_TO_HZ(frequency) < freq_low) ||
+ (V4L2_TO_HZ(frequency) > freq_high))
+ goto error;
+
+ spin_lock(&fm_spinlock);
+ fm_event = CG2900_EVENT_NO_EVENT;
+ no_of_scan_freq = 0;
+ spin_unlock(&fm_spinlock);
+
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ cg2900_device.frequency = frequency;
+ status = cg2900_fm_set_frequency(V4L2_TO_HZ(frequency));
+
+ if (0 != status)
+ goto error;
+
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_set_frequency: returning = %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_query_ctrl()- Query the FM Driver control features.
+ *
+ * This function is used to query the control features on FM Radio.
+ * This function is called when the application
+ * issues the IOCTL VIDIOC_QUERYCTRL
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @query_ctrl: v4l2_queryctrl structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_query_ctrl(
+ struct file *file,
+ void *priv,
+ struct v4l2_queryctrl *query_ctrl
+ )
+{
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_query_ctrl");
+ /* Check which control is requested */
+ switch (query_ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ FM_DEBUG_REPORT("vidioc_query_ctrl: V4L2_CID_AUDIO_MUTE");
+ query_ctrl->type = V4L2_CTRL_TYPE_BOOLEAN;
+ query_ctrl->minimum = 0;
+ query_ctrl->maximum = 1;
+ query_ctrl->step = 1;
+ query_ctrl->default_value = 0;
+ query_ctrl->flags = 0;
+ strncpy(query_ctrl->name, "CG2900 Mute", 32);
+ ret_val = 0;
+ break;
+
+ case V4L2_CID_AUDIO_VOLUME:
+ FM_DEBUG_REPORT("vidioc_query_ctrl: V4L2_CID_AUDIO_VOLUME");
+
+ strncpy(query_ctrl->name, "CG2900 Volume", 32);
+ query_ctrl->minimum = MIN_ANALOG_VOLUME;
+ query_ctrl->maximum = MAX_ANALOG_VOLUME;
+ query_ctrl->step = 1;
+ query_ctrl->default_value = MAX_ANALOG_VOLUME;
+ query_ctrl->flags = 0;
+ query_ctrl->type = V4L2_CTRL_TYPE_INTEGER;
+ ret_val = 0;
+ break;
+
+ case V4L2_CID_AUDIO_BALANCE:
+ FM_DEBUG_REPORT("vidioc_query_ctrl: V4L2_CID_AUDIO_BALANCE ");
+ strncpy(query_ctrl->name, "CG2900 Audio Balance", 32);
+ query_ctrl->type = V4L2_CTRL_TYPE_INTEGER;
+ query_ctrl->minimum = 0x0000;
+ query_ctrl->maximum = 0xFFFF;
+ query_ctrl->step = 0x0001;
+ query_ctrl->default_value = 0x0000;
+ query_ctrl->flags = 0;
+ ret_val = 0;
+ break;
+
+ case V4L2_CID_AUDIO_BASS:
+ FM_DEBUG_REPORT("vidioc_query_ctrl: "
+ "V4L2_CID_AUDIO_BASS (unsupported)");
+ break;
+
+ case V4L2_CID_AUDIO_TREBLE:
+ FM_DEBUG_REPORT("vidioc_query_ctrl: "
+ "V4L2_CID_AUDIO_TREBLE (unsupported)");
+ break;
+
+ default:
+ FM_DEBUG_REPORT("vidioc_query_ctrl: "
+ "--> unsupported id = %x", query_ctrl->id);
+ break;
+ }
+
+ FM_DEBUG_REPORT("vidioc_query_ctrl: returning = %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_get_ctrl()- Get the value of a particular Control.
+ *
+ * This function is used to get the value of a
+ * particular control from the FM Driver. This function is called
+ * when the application issues the IOCTL VIDIOC_G_CTRL
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @ctrl: v4l2_control structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_ctrl(
+ struct file *file,
+ void *priv,
+ struct v4l2_control *ctrl
+ )
+{
+ int status;
+ u8 value;
+ u16 rssi;
+ u8 antenna;
+ u16 conclusion;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_get_ctrl");
+
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_VOLUME:
+ status = cg2900_fm_get_volume(&value);
+ if (0 == status) {
+ ctrl->value = value;
+ cg2900_device.volume = value;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_AUDIO_MUTE:
+ ctrl->value = cg2900_device.muted;
+ ret_val = 0;
+ break;
+ case V4L2_CID_AUDIO_BALANCE:
+ ctrl->value = cg2900_device.audiopath;
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_RSSI_THRESHOLD:
+ ctrl->value = cg2900_device.rssi_threshold;
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_SELECT_ANTENNA:
+ status = cg2900_fm_get_antenna(&antenna);
+ FM_DEBUG_REPORT("vidioc_get_ctrl: Antenna = %x", antenna);
+ if (0 == status) {
+ ctrl->value = antenna;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_GET_RESULT:
+ status = cg2900_fm_af_update_get_result(&rssi);
+ FM_DEBUG_REPORT("vidioc_get_ctrl: AF RSSI Level = %x", rssi);
+ if (0 == status) {
+ ctrl->value = rssi;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_GET_RESULT:
+ status = cg2900_fm_af_switch_get_result(&conclusion);
+ FM_DEBUG_REPORT("vidioc_get_ctrl: AF Switch conclusion = %x",
+ conclusion);
+ if (0 != status)
+ break;
+ if (conclusion == 0) {
+ ctrl->value = conclusion;
+ FM_DEBUG_REPORT("vidioc_get_ctrl: "
+ "AF Switch conclusion = %d",
+ ctrl->value);
+ ret_val = 0;
+ } else {
+ /*
+ * Convert positive error code returned by chip
+ * into negative error codes to be in line with linux.
+ */
+ ctrl->value = -conclusion;
+ FM_ERR_REPORT("vidioc_get_ctrl: "
+ "AF-Switch failed with value %d", ctrl->value);
+ ret_val = 0;
+ }
+ break;
+ default:
+ FM_DEBUG_REPORT("vidioc_get_ctrl: "
+ "unsupported (id = %x)", (int)ctrl->id);
+ ret_val = -EINVAL;
+ }
+ FM_DEBUG_REPORT("vidioc_get_ctrl: returning = %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_set_ctrl()- Set the value of a particular Control.
+ *
+ * This function is used to set the value of a
+ * particular control from the FM Driver. This function is called when the
+ * application issues the IOCTL VIDIOC_S_CTRL
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @ctrl: v4l2_control structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -ERANGE when the parameter is out of range.
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_ctrl(
+ struct file *file,
+ void *priv,
+ struct v4l2_control *ctrl
+ )
+{
+ int status;
+ int ret_val = -EINVAL;
+ FM_INFO_REPORT("vidioc_set_ctrl");
+ /* Check which control is requested */
+ switch (ctrl->id) {
+ case V4L2_CID_AUDIO_MUTE:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_AUDIO_MUTE, "
+ "value = %d", ctrl->value);
+ if (ctrl->value > 1 && ctrl->value < 0) {
+ ret_val = -ERANGE;
+ break;
+ }
+
+ if (ctrl->value) {
+ FM_DEBUG_REPORT("vidioc_set_ctrl: Ctrl_Id = "
+ "V4L2_CID_AUDIO_MUTE, "
+ "Muting the Radio");
+ status = cg2900_fm_mute();
+ } else {
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "Ctrl_Id = V4L2_CID_AUDIO_MUTE, "
+ "UnMuting the Radio");
+ status = cg2900_fm_unmute();
+ }
+ if (0 == status) {
+ cg2900_device.muted = ctrl->value;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_AUDIO_VOLUME:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_AUDIO_VOLUME, "
+ "value = %d", ctrl->value);
+ if (ctrl->value > MAX_ANALOG_VOLUME &&
+ ctrl->value < MIN_ANALOG_VOLUME) {
+ ret_val = -ERANGE;
+ break;
+ }
+ status = cg2900_fm_set_volume(ctrl->value);
+ if (0 == status) {
+ cg2900_device.volume = ctrl->value;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_AUDIO_BALANCE:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_AUDIO_BALANCE, "
+ "value = %d", ctrl->value);
+ status = cg2900_fm_set_audio_balance(ctrl->value);
+ if (0 == status) {
+ cg2900_device.audiopath = ctrl->value;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_CG2900_RADIO_CHIP_STATE:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_CHIP_STATE, "
+ "value = %d", ctrl->value);
+ if (V4L2_CG2900_RADIO_STANDBY == ctrl->value)
+ status = cg2900_fm_standby();
+ else if (V4L2_CG2900_RADIO_POWERUP == ctrl->value)
+ status = cg2900_fm_power_up_from_standby();
+ else
+ break;
+ if (0 != status)
+ break;
+ if (V4L2_CG2900_RADIO_STANDBY == ctrl->value)
+ cg2900_device.state = FMR_STANDBY;
+ else if (V4L2_CG2900_RADIO_POWERUP == ctrl->value)
+ cg2900_device.state = FMR_SWITCH_ON;
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_SELECT_ANTENNA:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_SELECT_ANTENNA, "
+ "value = %d", ctrl->value);
+ status = cg2900_fm_select_antenna(ctrl->value);
+ if (0 == status)
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_BANDSCAN:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_BANDSCAN, "
+ "value = %d", ctrl->value);
+ if (V4L2_CG2900_RADIO_BANDSCAN_START == ctrl->value) {
+ cg2900_device.seekstatus = FMR_SEEK_IN_PROGRESS;
+ no_of_scan_freq = 0;
+ status = cg2900_fm_start_band_scan();
+ } else if (V4L2_CG2900_RADIO_BANDSCAN_STOP == ctrl->value) {
+ status = cg2900_fm_stop_scan();
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ } else
+ break;
+ if (0 == status)
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_RSSI_THRESHOLD:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_RSSI_THRESHOLD "
+ "= %d", ctrl->value);
+ status = cg2900_fm_set_rssi_threshold(ctrl->value);
+ if (0 == status) {
+ cg2900_device.rssi_threshold = ctrl->value;
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_START:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_START "
+ "freq = %d Hz", ctrl->value);
+ status = cg2900_fm_af_update_start(ctrl->value);
+ if (0 == status)
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_TEST_TONE_GENERATOR_SET_STATUS:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_TEST_TONE_GENERATOR_SET_STATUS "
+ "state = %d ", ctrl->value);
+ if (ctrl->value < V4L2_CG2900_RADIO_TEST_TONE_GEN_OFF ||
+ ctrl->value >
+ V4L2_CG2900_RADIO_TEST_TONE_GENERATOR_ON_WO_SRC) {
+ FM_ERR_REPORT("Invalid parameter = %d", ctrl->value);
+ break;
+ }
+ status = cg2900_fm_set_test_tone_generator(ctrl->value);
+ if (0 == status)
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_TUNE_DEEMPHASIS:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "V4L2_CID_CG2900_RADIO_TUNE_DEEMPHASIS, "
+ "Value = %d", ctrl->value);
+
+ if ((V4L2_CG2900_RADIO_DEEMPHASIS_DISABLED >
+ ctrl->value) ||
+ (V4L2_CG2900_RADIO_DEEMPHASIS_75_uS <
+ ctrl->value)) {
+ FM_ERR_REPORT("Unsupported deemphasis = %d",
+ ctrl->value);
+ break;
+ }
+
+ switch (ctrl->value) {
+ case V4L2_CG2900_RADIO_DEEMPHASIS_50_uS:
+ ctrl->value = FMD_EMPHASIS_50US;
+ break;
+ case V4L2_CG2900_RADIO_DEEMPHASIS_75_uS:
+ ctrl->value = FMD_EMPHASIS_75US;
+ break;
+ case V4L2_CG2900_RADIO_DEEMPHASIS_DISABLED:
+ /* Drop Down */
+ default:
+ ctrl->value = FMD_EMPHASIS_NONE;
+ break;
+ }
+ status = cg2900_fm_rx_set_deemphasis(ctrl->value);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ default:
+ FM_DEBUG_REPORT("vidioc_set_ctrl: "
+ "unsupported (id = %x)", ctrl->id);
+ }
+ FM_DEBUG_REPORT("vidioc_set_ctrl: returning = %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_get_ext_ctrls()- Get the values of a particular control.
+ *
+ * This function is used to get the value of a
+ * particular control from the FM Driver. This is used when the data to
+ * be received is more than 1 paramter. This function is called when the
+ * application issues the IOCTL VIDIOC_G_EXT_CTRLS
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @ext_ctrl: v4l2_ext_controls structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -ENOSPC: when there is no space to copy the data into the buffer provided
+ * by application.
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_ext_ctrls(
+ struct file *file,
+ void *priv,
+ struct v4l2_ext_controls *ext_ctrl
+ )
+{
+ u32 *dest_buffer;
+ int index = 0;
+ int count = 0;
+ int ret_val = -EINVAL;
+ int status;
+ struct sk_buff *skb;
+ u8 mode;
+ s8 interrupt_success;
+ int *fm_interrupt_buffer;
+
+ FM_INFO_REPORT("vidioc_get_ext_ctrls: Id = %04x,"
+ "ext_ctrl->ctrl_class = %04x",
+ ext_ctrl->controls->id,
+ ext_ctrl->ctrl_class);
+
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_FM_TX &&
+ ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_get_ext_ctrls: Unsupported "
+ "ctrl_class = %04x", ext_ctrl->ctrl_class);
+ goto error;
+ }
+
+ switch (ext_ctrl->controls->id) {
+ case V4L2_CID_CG2900_RADIO_BANDSCAN_GET_RESULTS:
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_BANDSCAN_GET_RESULTS "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ if (cg2900_device.seekstatus ==
+ FMR_SEEK_IN_PROGRESS) {
+ spin_lock(&fm_spinlock);
+ skb = skb_dequeue(&fm_interrupt_queue);
+ spin_unlock(&fm_spinlock);
+ if (!skb) {
+ /* No Interrupt, bad case */
+ FM_ERR_REPORT("No Interrupt to read");
+ fm_event = CG2900_EVENT_NO_EVENT;
+ break;
+ }
+ fm_event = (u8)skb->data[0];
+ FM_DEBUG_REPORT(
+ "V4L2_CID_CG2900_RADIO"
+ "_BANDSCAN_GET_RESULTS: "
+ "fm_event = %x", fm_event);
+ if (fm_event ==
+ CG2900_EVENT_SCAN_CHANNELS_FOUND) {
+ /* Check to get Scan Result */
+ status =
+ cg2900_fm_get_scan_result
+ (&no_of_scan_freq, scanfreq,
+ scanfreq_rssi_level);
+ if (0 != status) {
+ FM_ERR_REPORT
+ ("vidioc_get_ext_ctrls: "
+ "cg2900_fm_get_scan_"
+ "result: returned %d",
+ status);
+ kfree_skb(skb);
+ break;
+ }
+ kfree_skb(skb);
+ } else {
+ /* Some other interrupt, Queue it back */
+ spin_lock(&fm_spinlock);
+ skb_queue_head(&fm_interrupt_queue, skb);
+ spin_unlock(&fm_spinlock);
+ }
+ }
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "SeekStatus = %x, GlobalEvent = %x, "
+ "numchannels = %x",
+ cg2900_device.seekstatus,
+ fm_event, no_of_scan_freq);
+
+ if (ext_ctrl->controls->size == 0 &&
+ ext_ctrl->controls->string == NULL) {
+ if (cg2900_device.seekstatus ==
+ FMR_SEEK_IN_PROGRESS &&
+ CG2900_EVENT_SCAN_CHANNELS_FOUND
+ == fm_event) {
+ spin_lock(&fm_spinlock);
+ ext_ctrl->controls->size =
+ no_of_scan_freq;
+ cg2900_device.seekstatus
+ = FMR_SEEK_NONE;
+ fm_event =
+ CG2900_EVENT_NO_EVENT;
+ spin_unlock(&fm_spinlock);
+ return -ENOSPC;
+ }
+ } else if (ext_ctrl->controls->string != NULL) {
+ dest_buffer =
+ (u32 *) ext_ctrl->controls->string;
+ while (index < no_of_scan_freq) {
+ *(dest_buffer + count + 0) =
+ HZ_TO_V4L2(scanfreq[index]);
+ *(dest_buffer + count + 1) =
+ scanfreq_rssi_level[index];
+ count += 2;
+ index++;
+ }
+ ret_val = 0;
+ }
+ break;
+ case V4L2_CID_CG2900_RADIO_BLOCKSCAN_GET_RESULTS:
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_BLOCKSCAN"
+ "_GET_RESULTS "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ if (cg2900_device.seekstatus == FMR_SEEK_IN_PROGRESS) {
+ spin_lock(&fm_spinlock);
+ skb = skb_dequeue(&fm_interrupt_queue);
+ spin_unlock(&fm_spinlock);
+ if (!skb) {
+ /* No Interrupt, bad case */
+ FM_ERR_REPORT("No Interrupt to read");
+ fm_event = CG2900_EVENT_NO_EVENT;
+ break;
+ }
+ fm_event = (u8)skb->data[0];
+ FM_DEBUG_REPORT(
+ "V4L2_CID_CG2900_RADIO_BLOCKSCAN"
+ "GET_RESULTS: "
+ "fm_event = %x", fm_event);
+ if (fm_event ==
+ CG2900_EVENT_BLOCK_SCAN_CHANNELS_FOUND) {
+ /* Check for BlockScan Result */
+ status =
+ cg2900_fm_get_block_scan_result
+ (&no_of_block_scan_freq,
+ block_scan_rssi_level);
+ if (0 != status) {
+ FM_ERR_REPORT
+ ("vidioc_get_ext_ctrls: "
+ "cg2900_fm_get_block_scan_"
+ "result: returned %d",
+ status);
+ kfree_skb(skb);
+ break;
+ }
+ kfree_skb(skb);
+ } else {
+ /* Some other interrupt,
+ Queue it back */
+ spin_lock(&fm_spinlock);
+ skb_queue_head(&fm_interrupt_queue, skb);
+ spin_unlock(&fm_spinlock);
+ }
+ }
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "SeekStatus = %x, GlobalEvent = %x, "
+ "numchannels = %x",
+ cg2900_device.seekstatus,
+ fm_event, no_of_block_scan_freq);
+ if (ext_ctrl->controls->size == 0 &&
+ ext_ctrl->controls->string == NULL) {
+ if (cg2900_device.seekstatus ==
+ FMR_SEEK_IN_PROGRESS &&
+ CG2900_EVENT_BLOCK_SCAN_CHANNELS_FOUND
+ == fm_event) {
+ spin_lock(&fm_spinlock);
+ ext_ctrl->controls->size =
+ no_of_block_scan_freq;
+ cg2900_device.seekstatus
+ = FMR_SEEK_NONE;
+ fm_event =
+ CG2900_EVENT_NO_EVENT;
+ spin_unlock(&fm_spinlock);
+ return -ENOSPC;
+ }
+ } else if (ext_ctrl->controls->size >=
+ no_of_block_scan_freq &&
+ ext_ctrl->controls->string != NULL) {
+ dest_buffer =
+ (u32 *) ext_ctrl->controls->string;
+ while (index < no_of_block_scan_freq) {
+ *(dest_buffer + index) =
+ block_scan_rssi_level
+ [index];
+ index++;
+ }
+ ret_val = 0;
+ return ret_val;
+ }
+ break;
+ case V4L2_CID_RDS_TX_DEVIATION:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_RDS_TX_DEVIATION");
+ if (V4L2_CTRL_CLASS_FM_TX != ext_ctrl->ctrl_class) {
+ FM_ERR_REPORT("Invalid Ctrl Class = %x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ status = cg2900_fm_tx_get_rds_deviation((u16 *) &
+ ext_ctrl->
+ controls->value);
+ if (status == 0)
+ ret_val = 0;
+ break;
+ case V4L2_CID_PILOT_TONE_ENABLED:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_PILOT_TONE_ENABLED");
+ if (V4L2_CTRL_CLASS_FM_TX != ext_ctrl->ctrl_class) {
+ FM_ERR_REPORT("Invalid Ctrl Class = %x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ status = cg2900_fm_tx_get_pilot_tone_status(
+ (bool *)&ext_ctrl->controls->value);
+ if (status == 0)
+ ret_val = 0;
+ break;
+ case V4L2_CID_PILOT_TONE_DEVIATION:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_PILOT_TONE_DEVIATION");
+ if (V4L2_CTRL_CLASS_FM_TX != ext_ctrl->ctrl_class) {
+ FM_ERR_REPORT("Invalid Ctrl Class = %x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ status = cg2900_fm_tx_get_pilot_deviation(
+ (u16 *)&ext_ctrl->controls->value);
+ if (status == 0)
+ ret_val = 0;
+ break;
+ case V4L2_CID_TUNE_PREEMPHASIS:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_TUNE_PREEMPHASIS");
+ if (V4L2_CTRL_CLASS_FM_TX != ext_ctrl->ctrl_class) {
+ FM_ERR_REPORT("Invalid Ctrl Class = %x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ status = cg2900_fm_tx_get_preemphasis(
+ (u8 *)&ext_ctrl->controls->value);
+ if (status == 0)
+ ret_val = 0;
+ break;
+ case V4L2_CID_TUNE_POWER_LEVEL:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_TUNE_POWER_LEVEL");
+ if (V4L2_CTRL_CLASS_FM_TX != ext_ctrl->ctrl_class) {
+ FM_ERR_REPORT("Invalid Ctrl Class = %x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ status = cg2900_fm_tx_get_power_level(
+ (u16 *)&ext_ctrl->controls->value);
+ if (status == 0)
+ ret_val = 0;
+ break;
+ case V4L2_CID_CG2900_RADIO_GET_INTERRUPT:
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_GET_INTERRUPT "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+ if (ext_ctrl->controls->size != FMR_GET_INTERRUPT_DATA_SIZE ||
+ ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("vidioc_get_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_GET_INTERRUPT "
+ "Invalid parameters, ext_ctrl->controls->size = %x "
+ "ext_ctrl->controls->string = %08x",
+ ext_ctrl->controls->size,
+ (unsigned int)ext_ctrl->controls->string);
+ ret_val = -ENOSPC;
+ break;
+ }
+ spin_lock(&fm_spinlock);
+ skb = skb_dequeue(&fm_interrupt_queue);
+ spin_unlock(&fm_spinlock);
+ if (!skb) {
+ /* No Interrupt, bad case */
+ FM_ERR_REPORT("V4L2_CID_CG2900_RADIO_GET_INTERRUPT: "
+ "No Interrupt to read");
+ fm_event = CG2900_EVENT_NO_EVENT;
+ break;
+ }
+ fm_event = (u8)skb->data[0];
+ interrupt_success = (s8)skb->data[1];
+ FM_DEBUG_REPORT("vidioc_get_ctrl: Interrupt = %x "
+ "interrupt_success = %x",
+ fm_event, interrupt_success);
+ fm_interrupt_buffer =
+ (int *) ext_ctrl->controls->string;
+ /* Interrupt that has occurred */
+ *fm_interrupt_buffer = cg2900_map_event_to_v4l2(fm_event);
+
+ /* Interrupt success or failed */
+ if (interrupt_success) {
+ /* Interrupt Success, return 0 */
+ *(fm_interrupt_buffer + 1) = 0;
+ } else {
+ spin_lock(&fm_spinlock);
+ no_of_scan_freq = 0;
+ no_of_block_scan_freq = 0;
+ spin_unlock(&fm_spinlock);
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ /* Clear the Interrupt flag */
+ fm_event = CG2900_EVENT_NO_EVENT;
+ kfree_skb(skb);
+ /* Interrupt Success, return negative error */
+ *(fm_interrupt_buffer + 1) = -1;
+ FM_ERR_REPORT("vidioc_get_ext_ctrls: Interrupt = %d "
+ "failed with reason = %d",
+ (*fm_interrupt_buffer),
+ (*(fm_interrupt_buffer + 1)));
+ /*
+ * Update return value, so that application
+ * can read the event failure reason.
+ */
+ ret_val = 0;
+ break;
+ }
+
+ if (CG2900_EVENT_MONO_STEREO_TRANSITION
+ == fm_event) {
+ /*
+ * In case of Mono/Stereo Interrupt,
+ * get the current value from chip
+ */
+ status = cg2900_fm_get_mode(&mode);
+ cg2900_device.rx_stereo_status = (bool)mode;
+ /* Clear the Interrupt flag */
+ fm_event = CG2900_EVENT_NO_EVENT;
+ kfree_skb(skb);
+ } else if (CG2900_EVENT_SCAN_CANCELLED ==
+ fm_event) {
+ /* Scan/Search cancelled by User */
+ spin_lock(&fm_spinlock);
+ no_of_scan_freq = 0;
+ no_of_block_scan_freq = 0;
+ spin_unlock(&fm_spinlock);
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ /* Clear the Interrupt flag */
+ fm_event = CG2900_EVENT_NO_EVENT;
+ kfree_skb(skb);
+ } else {
+ /* Queue the interrupt back
+ for later dequeuing */
+ FM_DEBUG_REPORT("V4L2_CID_CG2900"
+ "_RADIO_GET_INTERRUPT: "
+ "Queuing the interrupt"
+ "again to head of list");
+ spin_lock(&fm_spinlock);
+ skb_queue_head(&fm_interrupt_queue, skb);
+ spin_unlock(&fm_spinlock);
+ }
+ ret_val = 0;
+ break;
+ default:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: "
+ "unsupported (id = %x)",
+ ext_ctrl->controls->id);
+ }
+
+error:
+ FM_DEBUG_REPORT("vidioc_get_ext_ctrls: returning = %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_set_ext_ctrls()- Set the values of a particular control.
+ *
+ * This function is used to set the value of a
+ * particular control on the FM Driver. This is used when the data to
+ * be set is more than 1 paramter. This function is called when the
+ * application issues the IOCTL VIDIOC_S_EXT_CTRLS
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @ext_ctrl: v4l2_ext_controls structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -ENOSPC: when there is no space to copy the data into the buffer provided
+ * by application.
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_ext_ctrls(
+ struct file *file,
+ void *priv,
+ struct v4l2_ext_controls *ext_ctrl
+ )
+{
+ int ret_val = -EINVAL;
+ int status;
+
+ FM_INFO_REPORT("vidioc_set_ext_ctrls: Id = %04x, ctrl_class = %04x",
+ ext_ctrl->controls->id, ext_ctrl->ctrl_class);
+
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_FM_TX &&
+ ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: Unsupported "
+ "ctrl_class = %04x", ext_ctrl->ctrl_class);
+ goto error;
+ }
+
+ switch (ext_ctrl->controls->id) {
+ case V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START:
+ {
+ u32 af_switch_freq;
+ u16 af_switch_pi;
+ u32 *af_switch_buf;
+
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+
+ if (ext_ctrl->controls->size !=
+ FMR_AF_SWITCH_DATA_SIZE ||
+ ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+
+ af_switch_buf = (u32 *) ext_ctrl->controls->string;
+ af_switch_freq = V4L2_TO_HZ(*af_switch_buf);
+ af_switch_pi = *(af_switch_buf + 1);
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START: "
+ "AF Switch Freq =%d Hz AF Switch PI = %04x",
+ (int)af_switch_freq, af_switch_pi);
+
+ if (af_switch_freq < (FMR_CHINA_LOW_FREQ_IN_MHZ
+ * FMR_HZ_TO_MHZ_CONVERTER) ||
+ af_switch_freq > (FMR_CHINA_HIGH_FREQ_IN_MHZ
+ * FMR_HZ_TO_MHZ_CONVERTER)) {
+ FM_ERR_REPORT("Invalid Freq = %04x",
+ af_switch_freq);
+ break;
+ }
+
+ status = cg2900_fm_af_switch_start(
+ af_switch_freq,
+ af_switch_pi);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_RDS_TX_DEVIATION:
+ {
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_RDS_TX_DEVIATION, "
+ "Value = %d",
+ ext_ctrl->controls->value);
+
+ if (ext_ctrl->controls->value <= MIN_RDS_DEVIATION &&
+ ext_ctrl->controls->value > MAX_RDS_DEVIATION) {
+ FM_ERR_REPORT("Invalid RDS Deviation = %02x",
+ ext_ctrl->controls->value);
+ break;
+ }
+
+ status = cg2900_fm_tx_set_rds_deviation(
+ ext_ctrl->controls->value);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_RDS_TX_PI:
+ {
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_RDS_TX_PI, PI = %04x",
+ ext_ctrl->controls->value);
+
+ if (ext_ctrl->controls->value <= MIN_PI_VALUE &&
+ ext_ctrl->controls->value > MAX_PI_VALUE) {
+ FM_ERR_REPORT("Invalid PI = %04x",
+ ext_ctrl->controls->value);
+ break;
+ }
+
+ status = cg2900_fm_tx_set_pi_code(
+ ext_ctrl->controls->value);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_RDS_TX_PTY:
+ {
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_RDS_TX_PTY, PTY = %d",
+ ext_ctrl->controls->value);
+
+ if (ext_ctrl->controls->value < MIN_PTY_VALUE &&
+ ext_ctrl->controls->value > MAX_PTY_VALUE) {
+ FM_ERR_REPORT("Invalid PTY = %02x",
+ ext_ctrl->controls->value);
+ break;
+ }
+
+ status = cg2900_fm_tx_set_pty_code(
+ ext_ctrl->controls->value);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_RDS_TX_PS_NAME:
+ {
+ if (ext_ctrl->controls->size > MAX_PSN_SIZE
+ || ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("Invalid PSN");
+ break;
+ }
+
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_RDS_TX_PS_NAME, "
+ "PSN = %s, Len = %x",
+ ext_ctrl->controls->string,
+ ext_ctrl->controls->size);
+
+ status = cg2900_fm_tx_set_program_station_name(
+ ext_ctrl->controls->string,
+ ext_ctrl->controls->size);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_RDS_TX_RADIO_TEXT:
+ {
+ if (ext_ctrl->controls->size >= MAX_RT_SIZE
+ || ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("Invalid RT");
+ break;
+ }
+
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_RDS_TX_RADIO_TEXT, "
+ "RT = %s, Len = %x",
+ ext_ctrl->controls->string,
+ ext_ctrl->controls->size);
+
+ status = cg2900_fm_tx_set_radio_text(
+ ext_ctrl->controls->string,
+ ext_ctrl->controls->size);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_PILOT_TONE_ENABLED:
+ {
+ bool enable;
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_PILOT_TONE_ENABLED, "
+ "Value = %d",
+ ext_ctrl->controls->value);
+
+ if (FMD_PILOT_TONE_ENABLED ==
+ ext_ctrl->controls->value)
+ enable = true;
+ else if (FMD_PILOT_TONE_DISABLED ==
+ ext_ctrl->controls->value)
+ enable = false;
+ else {
+ FM_ERR_REPORT("Unsupported Value = %d",
+ ext_ctrl->controls->value);
+ break;
+ }
+ status = cg2900_fm_tx_set_pilot_tone_status(enable);
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_PILOT_TONE_DEVIATION:
+ {
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_PILOT_TONE_DEVIATION, "
+ "Value = %d",
+ ext_ctrl->controls->value);
+
+ if (ext_ctrl->controls->value <= MIN_PILOT_DEVIATION &&
+ ext_ctrl->controls->value > MAX_PILOT_DEVIATION) {
+ FM_ERR_REPORT("Invalid Pilot Deviation = %02x",
+ ext_ctrl->controls->value);
+ break;
+ }
+
+ status = cg2900_fm_tx_set_pilot_deviation(
+ ext_ctrl->controls->value);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_TUNE_PREEMPHASIS:
+ {
+ u8 preemphasis;
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_TUNE_PREEMPHASIS, "
+ "Value = %d",
+ ext_ctrl->controls->value);
+
+ if ((V4L2_PREEMPHASIS_50_uS >
+ ext_ctrl->controls->value) ||
+ (V4L2_PREEMPHASIS_75_uS <
+ ext_ctrl->controls->value)) {
+ FM_ERR_REPORT("Unsupported Preemphasis = %d",
+ ext_ctrl->controls->value);
+ break;
+ }
+
+ if (V4L2_PREEMPHASIS_50_uS ==
+ ext_ctrl->controls->value) {
+ preemphasis = FMD_EMPHASIS_50US;
+ } else if (V4L2_PREEMPHASIS_75_uS ==
+ ext_ctrl->controls->value) {
+ preemphasis = FMD_EMPHASIS_75US;
+ }
+
+ status = cg2900_fm_tx_set_preemphasis(preemphasis);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_TUNE_POWER_LEVEL:
+ {
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_TUNE_POWER_LEVEL, "
+ "Value = %d",
+ ext_ctrl->controls->value);
+ if (ext_ctrl->controls->value < MIN_POWER_LEVEL &&
+ ext_ctrl->controls->value > MAX_POWER_LEVEL) {
+ FM_ERR_REPORT("Invalid Power Level = %02x",
+ ext_ctrl->controls->value);
+ break;
+ }
+
+ status = cg2900_fm_tx_set_power_level(
+ ext_ctrl->controls->value);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_CG2900_RADIO_BLOCKSCAN_START:
+ {
+ u32 start_freq;
+ u32 end_freq;
+ u32 *block_scan_buf;
+ u32 current_grid;
+ u32 low_freq;
+ u32 high_freq;
+ u32 result_freq;
+ u8 no_of_block_scan_channels;
+
+ /* V4L2 Initial check */
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_BLOCKSCAN_START "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+
+ if (ext_ctrl->controls->size !=
+ FMR_BLOCK_SCAN_DATA_SIZE ||
+ ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_BLOCKSCAN_START "
+ "Invalid Parameters");
+ break;
+ }
+
+ /* Check for current grid */
+ if (grid == CG2900_FM_GRID_50)
+ current_grid = FMR_CHINA_GRID_IN_HZ;
+ else if (grid == CG2900_FM_GRID_100)
+ current_grid = FMR_EUROPE_GRID_IN_HZ;
+ else
+ current_grid = FMR_USA_GRID_IN_HZ;
+
+ /* Check for current band */
+ if (band == CG2900_FM_BAND_US_EU) {
+ low_freq = FMR_EU_US_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ high_freq = FMR_EU_US_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+
+ } else if (band == CG2900_FM_BAND_JAPAN) {
+ low_freq = FMR_JAPAN_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ high_freq = FMR_JAPAN_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+
+ } else {
+ low_freq = FMR_CHINA_LOW_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ high_freq = FMR_CHINA_HIGH_FREQ_IN_MHZ *
+ FMR_HZ_TO_MHZ_CONVERTER;
+ }
+
+ /* V4L2 Extended control */
+
+ block_scan_buf = (u32 *)ext_ctrl->controls->string;
+ start_freq = V4L2_TO_HZ(*block_scan_buf);
+ end_freq = V4L2_TO_HZ(*(block_scan_buf + 1));
+
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_"
+ "BLOCKSCAN_START: "
+ "Start Freq = %d Hz "
+ "End Freq = %d Hz",
+ (int)start_freq,
+ (int)end_freq);
+
+ result_freq = end_freq - start_freq;
+ no_of_block_scan_channels =
+ (u8)(result_freq / current_grid);
+
+ /* Frequency Check */
+ if (end_freq < start_freq) {
+ FM_ERR_REPORT("Start Freq (%d Hz) "
+ " > End Freq (%d Hz)",
+ (int)start_freq,
+ (int)end_freq);
+ break;
+ }
+
+ if ((start_freq < low_freq) ||
+ (start_freq > high_freq)) {
+ FM_ERR_REPORT("Out of Band Freq: "
+ "Start Freq = %d Hz",
+ (int)start_freq);
+ break;
+ }
+
+ if ((end_freq < low_freq) ||
+ (end_freq > high_freq)) {
+ FM_ERR_REPORT("Out of Band Freq: "
+ "End Freq = %d Hz",
+ (int)end_freq);
+ break;
+ }
+
+ /* Maximum allowed block scan range */
+ if (FMR_MAX_BLOCK_SCAN_CHANNELS <
+ no_of_block_scan_channels) {
+ FM_ERR_REPORT("No of channels (%x)"
+ "exceeds Max Block Scan (%x)",
+ no_of_block_scan_channels,
+ FMR_MAX_BLOCK_SCAN_CHANNELS);
+ break;
+ }
+
+ status = cg2900_fm_start_block_scan(
+ start_freq,
+ end_freq);
+ if (0 == status) {
+ cg2900_device.seekstatus =
+ FMR_SEEK_IN_PROGRESS;
+ ret_val = 0;
+ }
+ break;
+ }
+ case V4L2_CID_CG2900_RADIO_TEST_TONE_CONNECT:
+ {
+ u8 left_audio_mode;
+ u8 right_audio_mode;
+ u8 *test_tone_connect_buf;
+
+ /* V4L2 Initial check */
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_"
+ "TEST_TONE_CONNECT "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+
+ if (ext_ctrl->controls->size !=
+ FMR_TEST_TONE_CONNECT_DATA_SIZE ||
+ ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_TEST"
+ "_TONE_CONNECT "
+ "Invalid Parameters");
+ break;
+ }
+
+ /* V4L2 Extended control */
+ test_tone_connect_buf =
+ (u8 *)ext_ctrl->controls->string;
+ left_audio_mode = *test_tone_connect_buf;
+ right_audio_mode = *(test_tone_connect_buf + 1);
+
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_TEST_TONE_CONNECT"
+ "left_audio_mode Freq = %02x"
+ "right_audio_modeFreq = %02x",
+ left_audio_mode,
+ right_audio_mode);
+
+ /* Range Check */
+ if (left_audio_mode > \
+ V4L2_CG2900_RADIO_TEST_TONE_TONE_SUM) {
+ FM_ERR_REPORT("Invalid Value of "
+ "left_audio_mode (%02x) ",
+ left_audio_mode);
+ break;
+ }
+
+ if (right_audio_mode > \
+ V4L2_CG2900_RADIO_TEST_TONE_TONE_SUM) {
+ FM_ERR_REPORT("Invalid Value of "
+ "right_audio_mode (%02x) ",
+ left_audio_mode);
+ break;
+ }
+
+ status = cg2900_fm_test_tone_connect(
+ left_audio_mode,
+ right_audio_mode);
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ case V4L2_CID_CG2900_RADIO_TEST_TONE_SET_PARAMS:
+ {
+ u8 tone_gen;
+ u16 frequency;
+ u16 volume;
+ u16 phase_offset;
+ u16 dc;
+ u8 waveform;
+ u16 *test_tone_set_params_buf;
+
+ /* V4L2 Initial check */
+ if (ext_ctrl->ctrl_class != V4L2_CTRL_CLASS_USER) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_TEST_TONE_SET_PARAMS "
+ "Unsupported ctrl_class = %04x",
+ ext_ctrl->ctrl_class);
+ break;
+ }
+
+ if (ext_ctrl->controls->size !=
+ FMR_TEST_TONE_SET_PARAMS_DATA_SIZE ||
+ ext_ctrl->controls->string == NULL) {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "FMR_TEST_TONE_SET_PARAMS_DATA_SIZE "
+ "Invalid Parameters");
+ break;
+ }
+
+ /* V4L2 Extended control */
+ test_tone_set_params_buf = \
+ (u16 *)ext_ctrl->controls->string;
+
+ tone_gen = (u8)(*test_tone_set_params_buf);
+ frequency = *(test_tone_set_params_buf + 1);
+ volume = *(test_tone_set_params_buf + 2);
+ phase_offset = *(test_tone_set_params_buf + 3);
+ dc = *(test_tone_set_params_buf + 4);
+ waveform = (u8)(*(test_tone_set_params_buf + 5));
+
+ FM_DEBUG_REPORT("vidioc_set_ext_ctrls: "
+ "V4L2_CID_CG2900_RADIO_TEST_TONE_SET_PARAMS"
+ "tone_gen = %02x frequency = %04x"
+ "volume = %04x phase_offset = %04x"
+ "dc = %04x waveform = %02x",
+ tone_gen, frequency,
+ volume, phase_offset,
+ dc, waveform);
+
+ /* Range Check */
+ if (tone_gen > FMD_TST_TONE_2) {
+ FM_ERR_REPORT("Invalid Value of "
+ "tone_gen (%02x) ",
+ tone_gen);
+ break;
+ }
+
+ if (waveform > FMD_TST_TONE_PULSE) {
+ FM_ERR_REPORT("Invalid Value of "
+ "waveform (%02x) ",
+ waveform);
+ break;
+ }
+
+ if (frequency > 0x7FFF) {
+ FM_ERR_REPORT("Invalid Value of "
+ "frequency (%04x) ",
+ frequency);
+ break;
+ }
+
+ if (volume > 0x7FFF) {
+ FM_ERR_REPORT("Invalid Value of "
+ "volume (%04x) ",
+ volume);
+ break;
+ }
+
+ status = cg2900_fm_test_tone_set_params(
+ tone_gen,
+ frequency,
+ volume,
+ phase_offset,
+ dc,
+ waveform);
+
+ if (0 == status)
+ ret_val = 0;
+ break;
+ }
+ default:
+ {
+ FM_ERR_REPORT("vidioc_set_ext_ctrls: "
+ "Unsupported Id = %04x",
+ ext_ctrl->controls->id);
+ }
+ }
+error:
+ return ret_val;
+}
+
+/**
+ * vidioc_set_hw_freq_seek()- seek Up/Down Frequency.
+ *
+ * This function is used to start seek
+ * on the FM Radio. Direction if seek is as inicated by the parameter
+ * inside the v4l2_hw_freq_seek structure. This function is called when the
+ * application issues the IOCTL VIDIOC_S_HW_FREQ_SEEK
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @freq_seek: v4l2_hw_freq_seek structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_hw_freq_seek(
+ struct file *file,
+ void *priv,
+ struct v4l2_hw_freq_seek *freq_seek
+ )
+{
+ int status;
+ int ret_val = -EINVAL;
+
+ FM_INFO_REPORT("vidioc_set_hw_freq_seek");
+
+ FM_DEBUG_REPORT("vidioc_set_hw_freq_seek: Status = %x, "
+ "Upwards = %x, Wrap Around = %x",
+ cg2900_device.seekstatus,
+ freq_seek->seek_upward, freq_seek->wrap_around);
+
+ if (cg2900_device.seekstatus == FMR_SEEK_IN_PROGRESS) {
+ FM_ERR_REPORT("vidioc_set_hw_freq_seek: "
+ "VIDIOC_S_HW_FREQ_SEEK, "
+ "freq_seek in progress");
+ goto error;
+ }
+
+ spin_lock(&fm_spinlock);
+ fm_event = CG2900_EVENT_NO_EVENT;
+ no_of_scan_freq = 0;
+ spin_unlock(&fm_spinlock);
+
+ if (CG2900_DIR_UP == freq_seek->seek_upward)
+ status = cg2900_fm_search_up_freq();
+ else if (CG2900_DIR_DOWN == freq_seek->seek_upward)
+ status = cg2900_fm_search_down_freq();
+ else
+ goto error;
+
+ if (0 != status)
+ goto error;
+
+ cg2900_device.seekstatus = FMR_SEEK_IN_PROGRESS;
+ ret_val = 0;
+
+error:
+ FM_DEBUG_REPORT("vidioc_set_hw_freq_seek: returning = %d",
+ ret_val);
+ return ret_val;
+}
+
+/**
+ * vidioc_get_audio()- Get Audio features of FM Driver.
+ *
+ * This function is used to get the audio features of FM Driver.
+ * This function is imlemented as a dumy function.
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @audio: (out) v4l2_audio structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_audio(
+ struct file *file,
+ void *priv,
+ struct v4l2_audio *audio
+ )
+{
+ FM_INFO_REPORT("vidioc_get_audio");
+ strcpy(audio->name, "");
+ audio->capability = 0;
+ audio->mode = 0;
+ return 0;
+}
+
+/**
+ * vidioc_set_audio()- Set Audio features of FM Driver.
+ *
+ * This function is used to set the audio features of FM Driver.
+ * This function is imlemented as a dumy function.
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @audio: v4l2_audio structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_audio(
+ struct file *file,
+ void *priv,
+ struct v4l2_audio *audio
+ )
+{
+ FM_INFO_REPORT("vidioc_set_audio");
+ if (audio->index != 0)
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * vidioc_get_input()- Get the Input Value
+ *
+ * This function is used to get the Input.
+ * This function is imlemented as a dumy function.
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @input: (out) Value to be stored.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_get_input(
+ struct file *file,
+ void *priv,
+ unsigned int *input
+ )
+{
+ FM_INFO_REPORT("vidioc_get_input");
+ *input = 0;
+ return 0;
+}
+
+/**
+ * vidioc_set_input()- Set the input value.
+ *
+ * This function is used to set input.
+ * This function is imlemented as a dumy function.
+ *
+ * @file: File structure.
+ * @priv: Previous data of file structure.
+ * @input: Value to set
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int vidioc_set_input(
+ struct file *file,
+ void *priv,
+ unsigned int input
+ )
+{
+ FM_INFO_REPORT("vidioc_set_input");
+ if (input != 0)
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * cg2900_convert_err_to_v4l2()- Convert Error Bits to V4L2 RDS format.
+ *
+ * This function converts the error bits in RDS Block
+ * as received from Chip into V4L2 RDS data specification.
+ *
+ * @status_byte: The status byte as received in RDS Group for
+ * particular RDS Block
+ * @out_byte: byte to store the modified byte with the err bits
+ * alligned as per V4L2 RDS Specifications.
+ */
+static void cg2900_convert_err_to_v4l2(
+ char status_byte,
+ char *out_byte
+ )
+{
+ if ((status_byte & RDS_ERROR_STATUS_MASK) == RDS_ERROR_STATUS_MASK) {
+ /* Uncorrectable Block */
+ *out_byte = (*out_byte | V4L2_RDS_BLOCK_ERROR);
+ } else if (((status_byte & RDS_UPTO_TWO_BITS_CORRECTED)
+ == RDS_UPTO_TWO_BITS_CORRECTED) ||
+ ((status_byte & RDS_UPTO_FIVE_BITS_CORRECTED)
+ == RDS_UPTO_FIVE_BITS_CORRECTED)) {
+ /* Corrected Bits in Block */
+ *out_byte = (*out_byte | V4L2_RDS_BLOCK_CORRECTED);
+ }
+}
+
+/**
+ * cg2900_map_event_to_v4l2()- Maps cg2900 event to v4l2 events .
+ *
+ * This function maps cg2900 events to corresponding v4l2 events.
+ *
+ * @event: This contains the cg2900 event to be converted.
+ *
+ * Returns: Corresponding V4L2 events.
+ */
+static int cg2900_map_event_to_v4l2(
+ u8 event
+ )
+{
+ switch (event) {
+ case CG2900_EVENT_MONO_STEREO_TRANSITION:
+ return V4L2_CG2900_RADIO_INTERRUPT_MONO_STEREO_TRANSITION;
+ case CG2900_EVENT_SEARCH_CHANNEL_FOUND:
+ return V4L2_CG2900_RADIO_INTERRUPT_SEARCH_COMPLETED;
+ case CG2900_EVENT_SCAN_CHANNELS_FOUND:
+ return V4L2_CG2900_RADIO_INTERRUPT_BAND_SCAN_COMPLETED;
+ case CG2900_EVENT_BLOCK_SCAN_CHANNELS_FOUND:
+ return V4L2_CG2900_RADIO_INTERRUPT_BLOCK_SCAN_COMPLETED;
+ case CG2900_EVENT_SCAN_CANCELLED:
+ return V4L2_CG2900_RADIO_INTERRUPT_SCAN_CANCELLED;
+ case CG2900_EVENT_DEVICE_RESET:
+ return V4L2_CG2900_RADIO_INTERRUPT_DEVICE_RESET;
+ case CG2900_EVENT_RDS_EVENT:
+ return V4L2_CG2900_RADIO_INTERRUPT_RDS_RECEIVED;
+ default:
+ return V4L2_CG2900_RADIO_INTERRUPT_UNKNOWN;
+ }
+}
+
+/**
+ * cg2900_open()- This function nitializes and switches on FM.
+ *
+ * This is called when the application opens the character device.
+ *
+ * @file: File structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int cg2900_open(
+ struct file *file
+ )
+{
+ int status;
+ int ret_val = -EINVAL;
+ struct video_device *vdev = video_devdata(file);
+
+ mutex_lock(&fm_mutex);
+ users++;
+ FM_INFO_REPORT("cg2900_open: users = %d", users);
+
+ if (users > 1) {
+ FM_INFO_REPORT("cg2900_open: FM already switched on!!!");
+ ret_val = 0;
+ /*
+ * No need to perform the initialization and switch on FM
+ * since it is already done during the first open call to
+ * this driver.
+ */
+ goto done;
+ }
+
+ status = cg2900_fm_init();
+ if (0 != status)
+ goto init_error;
+
+ FM_DEBUG_REPORT("cg2900_open: Switching on FM");
+ status = cg2900_fm_switch_on(&(vdev->dev));
+ if (0 != status)
+ goto switch_on_error;
+
+ cg2900_device.state = FMR_SWITCH_ON;
+ cg2900_device.frequency = HZ_TO_V4L2(freq_low);
+ cg2900_device.rx_rds_enabled = false;
+ cg2900_device.muted = false;
+ cg2900_device.audiopath = 0;
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ cg2900_device.rssi_threshold = CG2900_FM_DEFAULT_RSSI_THRESHOLD;
+ fm_event = CG2900_EVENT_NO_EVENT;
+ no_of_scan_freq = 0;
+ cg2900_device.fm_mode = CG2900_FM_IDLE_MODE;
+ ret_val = 0;
+ goto done;
+
+switch_on_error:
+ cg2900_fm_deinit();
+init_error:
+ users--;
+done:
+ mutex_unlock(&fm_mutex);
+ FM_DEBUG_REPORT("cg2900_open: returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * cg2900_release()- This function switches off FM.
+ *
+ * This function switches off FM and releases the resources.
+ * This is called when the application closes the character
+ * device.
+ *
+ * @file: File structure.
+ *
+ * Returns:
+ * 0 when no error
+ * -EINVAL: otherwise
+ */
+static int cg2900_release(
+ struct file *file
+ )
+{
+ int status;
+ int ret_val = -EINVAL;
+
+ mutex_lock(&fm_mutex);
+
+ FM_INFO_REPORT("cg2900_release");
+ if (users <= 0) {
+ FM_ERR_REPORT("cg2900_release: No users registered "
+ "with FM Driver");
+ goto done;
+ }
+
+ users--;
+ FM_INFO_REPORT("cg2900_release: users = %d", users);
+
+ if (0 == users) {
+ FM_DEBUG_REPORT("cg2900_release: Switching Off FM");
+ status = cg2900_fm_switch_off();
+ status = cg2900_fm_deinit();
+ if (0 != status)
+ goto done;
+
+ cg2900_device.state = FMR_SWITCH_OFF;
+ cg2900_device.frequency = 0;
+ cg2900_device.rx_rds_enabled = false;
+ cg2900_device.muted = false;
+ cg2900_device.seekstatus = FMR_SEEK_NONE;
+ fm_event = CG2900_EVENT_NO_EVENT;
+ no_of_scan_freq = 0;
+ }
+ ret_val = 0;
+
+done:
+ mutex_unlock(&fm_mutex);
+ FM_DEBUG_REPORT("cg2900_release: returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * cg2900_read()- This function is invoked when the application
+ * calls read() to receive RDS Data.
+ *
+ * @file: File structure.
+ * @data: buffer provided by application for receving the data.
+ * @count: Number of bytes that application wants to read from driver
+ * @pos: offset
+ *
+ * Returns:
+ * Number of bytes copied to the user buffer
+ * -EFAULT: If there is problem in copying data to buffer supplied
+ * by application
+ * -EIO: If the number of bytes to be read are not a multiple of
+ * struct v4l2_rds_data.
+ * -EAGAIN: More than 22 blocks requested to be read or read
+ * was called in non blocking mode and no data was available for reading.
+ * -EINTR: If read was interrupted by a signal before data was avaialble.
+ * 0 when no data available for reading.
+ */
+static ssize_t cg2900_read(
+ struct file *file,
+ char __user *data,
+ size_t count, loff_t *pos
+ )
+{
+ int current_rds_grp;
+ int index = 0;
+ int blocks_to_read;
+ struct v4l2_rds_data rdsbuf[MAX_RDS_GROUPS * NUM_OF_RDS_BLOCKS];
+ struct v4l2_rds_data *rdslocalbuf = rdsbuf;
+ struct sk_buff *skb;
+
+ FM_INFO_REPORT("cg2900_read");
+
+ blocks_to_read = (count / sizeof(struct v4l2_rds_data));
+
+ if (!cg2900_device.rx_rds_enabled) {
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ FM_INFO_REPORT("cg2900_read: returning 0");
+ return 0;
+ }
+
+ if (count % sizeof(struct v4l2_rds_data) != 0) {
+ FM_ERR_REPORT("cg2900_read: Invalid Number of bytes %x "
+ "requested to read", count);
+ return -EIO;
+ }
+
+ if (blocks_to_read > MAX_RDS_GROUPS * NUM_OF_RDS_BLOCKS) {
+ FM_ERR_REPORT("cg2900_read: Too many blocks(%d) "
+ "requested to be read", blocks_to_read);
+ return -EAGAIN;
+ }
+
+ current_rds_grp = fm_rds_info.rds_group_sent;
+
+ if ((fm_rds_info.rds_head == fm_rds_info.rds_tail) ||
+ (fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block1 == 0x0000)) {
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ FM_INFO_REPORT("cg2900_read: returning 0");
+ return 0;
+ }
+
+ spin_lock(&fm_spinlock);
+ while (index < blocks_to_read) {
+ /* Check which Block needs to be transferred next */
+ switch (fm_rds_info.rds_block_sent % NUM_OF_RDS_BLOCKS) {
+ case 0:
+ (rdslocalbuf + index)->lsb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block1;
+ (rdslocalbuf + index)->msb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block1 >> 8;
+ (rdslocalbuf + index)->block =
+ (fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status1
+ & RDS_BLOCK_MASK) >> 2;
+ cg2900_convert_err_to_v4l2(
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status1,
+ &(rdslocalbuf + index)->block);
+ break;
+ case 1:
+ (rdslocalbuf + index)->lsb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block2;
+ (rdslocalbuf + index)->msb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block2 >> 8;
+ (rdslocalbuf + index)->block =
+ (fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status2
+ & RDS_BLOCK_MASK) >> 2;
+ cg2900_convert_err_to_v4l2(
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status2,
+ &(rdslocalbuf + index)->block);
+ break;
+ case 2:
+ (rdslocalbuf + index)->lsb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block3;
+ (rdslocalbuf + index)->msb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block3 >> 8;
+ (rdslocalbuf + index)->block =
+ (fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status3
+ & RDS_BLOCK_MASK) >> 2;
+ cg2900_convert_err_to_v4l2(
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status3,
+ &(rdslocalbuf + index)->block);
+ break;
+ case 3:
+ (rdslocalbuf + index)->lsb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block4;
+ (rdslocalbuf + index)->msb =
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].block4 >> 8;
+ (rdslocalbuf + index)->block =
+ (fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status4
+ & RDS_BLOCK_MASK) >> 2;
+ cg2900_convert_err_to_v4l2(
+ fm_rds_buf[fm_rds_info.rds_tail]
+ [current_rds_grp].status4,
+ &(rdslocalbuf + index)->block);
+ current_rds_grp++;
+ if (current_rds_grp == MAX_RDS_GROUPS) {
+ fm_rds_info.rds_tail++;
+ current_rds_grp = 0;
+ /* Dequeue Rds Interrupt here */
+ skb = skb_dequeue(&fm_interrupt_queue);
+ if (!skb) {
+ /* No Interrupt, bad case */
+ FM_ERR_REPORT("cg2900_read: "
+ "skb is NULL. Major error");
+ spin_unlock(&fm_spinlock);
+ return 0;
+ }
+ fm_event = (u8)skb->data[0];
+ if (fm_event != CG2900_EVENT_RDS_EVENT) {
+ /* RDS interrupt not found */
+ FM_ERR_REPORT("cg2900_read:"
+ "RDS interrupt not found"
+ "for de-queuing."
+ "fm_event = %x", fm_event);
+ /* Queue the event back */
+ skb_queue_head(&fm_interrupt_queue,
+ skb);
+ spin_unlock(&fm_spinlock);
+ return 0;
+ }
+ kfree_skb(skb);
+ }
+ break;
+ default:
+ FM_ERR_REPORT("Invalid RDS Group!!!");
+ spin_unlock(&fm_spinlock);
+ return 0;
+ }
+ index++;
+ fm_rds_info.rds_block_sent++;
+ if (fm_rds_info.rds_block_sent == NUM_OF_RDS_BLOCKS)
+ fm_rds_info.rds_block_sent = 0;
+
+ if (!cg2900_device.rx_rds_enabled) {
+ /* Remove all Interrupts from the queue */
+ skb_queue_purge(&fm_interrupt_queue);
+ FM_INFO_REPORT("cg2900_read: returning 0");
+ spin_unlock(&fm_spinlock);
+ return 0;
+ }
+ }
+ /* Update the RDS Group Count Sent to Application */
+ fm_rds_info.rds_group_sent = current_rds_grp;
+ if (fm_rds_info.rds_tail == MAX_RDS_BUFFER)
+ fm_rds_info.rds_tail = 0;
+
+ spin_unlock(&fm_spinlock);
+ if (copy_to_user(data, rdslocalbuf, count)) {
+ FM_ERR_REPORT("cg2900_read: Error "
+ "in copying, returning");
+ return -EFAULT;
+ }
+ return count;
+}
+
+/**
+ * cg2900_poll()- Check if the operation is complete or not.
+ *
+ * This function is invoked by application on calling poll() and is used to
+ * wait till the any FM interrupt is received from the chip.
+ * The application decides to read the corresponding data depending on FM
+ * interrupt.
+ *
+ * @file: File structure.
+ * @wait: poll table
+ *
+ * Returns:
+ * POLLRDNORM|POLLIN whenever FM interrupt has occurred.
+ * 0 whenever the call times out.
+ */
+static unsigned int cg2900_poll(
+ struct file *file,
+ struct poll_table_struct *wait
+ )
+{
+ int ret_val = 0;
+
+ FM_INFO_REPORT("cg2900_poll");
+
+ /* Check if we have some data in queue already */
+ if (skb_queue_empty(&fm_interrupt_queue)) {
+ FM_DEBUG_REPORT("cg2900_poll: Interrupt Queue Empty, waiting");
+ /* No Interrupt, wait for it to occur */
+ poll_wait(file, &cg2900_poll_queue, wait);
+ }
+ /* Check if we now have interrupt to read in queue */
+ if (skb_queue_empty(&fm_interrupt_queue))
+ goto done;
+
+ ret_val = POLLIN | POLLRDNORM;
+
+done:
+ FM_DEBUG_REPORT("poll_wait returning %d", ret_val);
+ return ret_val;
+}
+
+/**
+ * radio_cg2900_probe()- This function registers FM Driver with V4L2 Driver.
+ *
+ * This function is called whenever the driver is probed by the device system,
+ * i.e. when a CG2900 controller has connected. It registers the FM Driver with
+ * Video4Linux as a character device.
+ *
+ * @pdev: Platform device.
+ *
+ * Returns:
+ * 0 on success
+ * -EINVAL on error
+ */
+static int __devinit radio_cg2900_probe(
+ struct platform_device *pdev
+ )
+{
+ int err;
+
+ FM_INFO_REPORT(BANNER);
+
+ err = fmd_set_dev(&pdev->dev);
+ if (err) {
+ FM_ERR_REPORT("Could not set device %s", pdev->name);
+ return err;
+ }
+
+ radio_nr = 0;
+ grid = CG2900_FM_GRID_100;
+ band = CG2900_FM_BAND_US_EU;
+ FM_INFO_REPORT("radio_cg2900_probe: radio_nr= %d.", radio_nr);
+
+ /* Initialize the parameters */
+ if (video_register_device(
+ &cg2900_video_device,
+ VFL_TYPE_RADIO,
+ radio_nr) == -1) {
+ FM_ERR_REPORT("radio_cg2900_probe: video_register_device err");
+ return -EINVAL;
+ }
+ mutex_init(&fm_mutex);
+ spin_lock_init(&fm_spinlock);
+ init_waitqueue_head(&cg2900_poll_queue);
+ skb_queue_head_init(&fm_interrupt_queue);
+ users = 0;
+ return 0;
+}
+
+/**
+ * radio_cg2900_remove()- This function removes the FM Driver.
+ *
+ * This function is called whenever the driver is removed by the device system,
+ * i.e. when a CG2900 controller has disconnected. It unregisters the FM Driver
+ * from Video4Linux.
+ *
+ * @pdev: Platform device.
+ *
+ * Returns: 0 on success
+ */
+static int __devexit radio_cg2900_remove(
+ struct platform_device *pdev
+ )
+{
+ FM_INFO_REPORT("radio_cg2900_remove");
+ /* Wake up the poll queue since we are now exiting */
+ wake_up_poll_queue();
+ /* Give some time for application to exit the poll thread */
+ schedule_timeout_interruptible(msecs_to_jiffies(500));
+
+ /* Try to Switch Off FM in case it is still switched on */
+ cg2900_fm_switch_off();
+ cg2900_fm_deinit();
+ skb_queue_purge(&fm_interrupt_queue);
+ mutex_destroy(&fm_mutex);
+ video_unregister_device(&cg2900_video_device);
+ fmd_set_dev(NULL);
+ return 0;
+}
+
+static struct platform_driver radio_cg2900_driver = {
+ .driver = {
+ .name = "cg2900-fm",
+ .owner = THIS_MODULE,
+ },
+ .probe = radio_cg2900_probe,
+ .remove = __devexit_p(radio_cg2900_remove),
+};
+
+/**
+ * radio_cg2900_init() - Initialize module.
+ *
+ * Registers platform driver.
+ */
+static int __init radio_cg2900_init(void)
+{
+ FM_INFO_REPORT("radio_cg2900_init");
+ return platform_driver_register(&radio_cg2900_driver);
+}
+
+/**
+ * radio_cg2900_exit() - Remove module.
+ *
+ * Unregisters platform driver.
+ */
+static void __exit radio_cg2900_exit(void)
+{
+ FM_INFO_REPORT("radio_cg2900_exit");
+ platform_driver_unregister(&radio_cg2900_driver);
+}
+
+void wake_up_poll_queue(void)
+{
+ FM_INFO_REPORT("wake_up_poll_queue");
+ wake_up_interruptible(&cg2900_poll_queue);
+}
+
+void cg2900_handle_device_reset(void)
+{
+ struct sk_buff *skb;
+ FM_INFO_REPORT("cg2900_handle_device_reset");
+ skb = alloc_skb(SKB_FM_INTERRUPT_DATA, GFP_KERNEL);
+ if (!skb) {
+ FM_ERR_REPORT("cg2900_handle_device_reset: "
+ "Unable to Allocate Memory");
+ return;
+ }
+ skb->data[0] = CG2900_EVENT_DEVICE_RESET;
+ skb->data[1] = true;
+ skb_queue_tail(&fm_interrupt_queue, skb);
+ wake_up_poll_queue();
+}
+
+module_init(radio_cg2900_init);
+module_exit(radio_cg2900_exit);
+MODULE_AUTHOR("Hemant Gupta");
+MODULE_LICENSE("GPL v2");
+
+module_param(radio_nr, int, S_IRUGO);
+
+module_param(grid, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(grid, "Grid:"
+ "0=50 kHz"
+ "*1=100 kHz*"
+ "2=200 kHz");
+
+module_param(band, int, S_IRUGO | S_IWUSR | S_IWGRP);
+MODULE_PARM_DESC(band, "Band:"
+ "*0=87.5-108 MHz*"
+ "1=76-90 MHz"
+ "2=70-108 MHz");
+
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
index 8db2d7f4b52..3fadf6aff35 100644
--- a/drivers/media/radio/Kconfig
+++ b/drivers/media/radio/Kconfig
@@ -161,6 +161,22 @@ config RADIO_WL1273
To compile this driver as a module, choose M here: the
module will be called radio-wl1273.
+config RADIO_CG2900
+ tristate "ST-Ericsson CG2900 FM Radio support"
+ depends on CG2900 && VIDEO_V4L2
+ ---help---
+ Choose Y here if you have one of these FM radio cards. This is a BT,
+ FM and GPS combo chip controlled via HCI.
+
+ 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-CG2900.
+
+
# TI's ST based wl128x FM radio
source "drivers/media/radio/wl128x/Kconfig"
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
index ca8c7d134b9..8435dabcb3c 100644
--- a/drivers/media/radio/Makefile
+++ b/drivers/media/radio/Makefile
@@ -28,5 +28,6 @@ obj-$(CONFIG_RADIO_TEF6862) += tef6862.o
obj-$(CONFIG_RADIO_TIMBERDALE) += radio-timb.o
obj-$(CONFIG_RADIO_WL1273) += radio-wl1273.o
obj-$(CONFIG_RADIO_WL128X) += wl128x/
+obj-$(CONFIG_RADIO_CG2900) += CG2900/
ccflags-y += -Isound
diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h
index c9c9a4680cc..42332ae92fd 100644
--- a/include/linux/videodev2.h
+++ b/include/linux/videodev2.h
@@ -1654,6 +1654,64 @@ enum v4l2_mpeg_mfc51_video_force_frame_type {
#define V4L2_CID_MPEG_MFC51_VIDEO_H264_ADAPTIVE_RC_STATIC (V4L2_CID_MPEG_MFC51_BASE+53)
#define V4L2_CID_MPEG_MFC51_VIDEO_H264_NUM_REF_PIC_FOR_P (V4L2_CID_MPEG_MFC51_BASE+54)
+/* Private Base control IDs specific to the CG2900 FM driver as defined by V4L2 */
+#define V4L2_CID_CG2900_RADIO_PRIVATE_BASE (V4L2_CID_PRIVATE_BASE | 0x1000)
+#define V4L2_CID_CG2900_RADIO_BANDSCAN (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+1)
+enum v4l2_cg2900_radio_bandscan {
+ V4L2_CG2900_RADIO_BANDSCAN_START = 0,
+ V4L2_CG2900_RADIO_BANDSCAN_STOP = 1,
+};
+#define V4L2_CID_CG2900_RADIO_BANDSCAN_GET_RESULTS (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+2)
+#define V4L2_CID_CG2900_RADIO_BLOCKSCAN_START (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+3)
+#define V4L2_CID_CG2900_RADIO_BLOCKSCAN_GET_RESULTS (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+4)
+#define V4L2_CID_CG2900_RADIO_CHIP_STATE (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+5)
+enum v4l2_cg2900_radio_chip_state {
+ V4L2_CG2900_RADIO_STANDBY = 0,
+ V4L2_CG2900_RADIO_POWERUP = 1,
+};
+#define V4L2_CID_CG2900_RADIO_RSSI_THRESHOLD (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+6)
+#define V4L2_CID_CG2900_RADIO_SELECT_ANTENNA (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+7)
+enum v4l2_cg2900_radio_select_antenna {
+ V4L2_CG2900_RADIO_EMBEDDED_ANTENNA = 0,
+ V4L2_CG2900_RADIO_WIRED_ANTENNA = 1,
+};
+#define V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_START (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+8)
+#define V4L2_CID_CG2900_RADIO_RDS_AF_UPDATE_GET_RESULT (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+9)
+#define V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_START (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+10)
+#define V4L2_CID_CG2900_RADIO_RDS_AF_SWITCH_GET_RESULT (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+11)
+#define V4L2_CID_CG2900_RADIO_TEST_TONE_GENERATOR_SET_STATUS (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+12)
+enum v4l2_cg2900_radio_test_tone_generator_set_status {
+ V4L2_CG2900_RADIO_TEST_TONE_GEN_OFF = 0,
+ V4L2_CG2900_RADIO_TEST_TONE_GEN_ON_W_SRC = 1,
+ V4L2_CG2900_RADIO_TEST_TONE_GENERATOR_ON_WO_SRC = 2,
+};
+#define V4L2_CID_CG2900_RADIO_TEST_TONE_CONNECT (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+13)
+enum v4l2_cg2900_radio_test_tone_generator {
+ V4L2_CG2900_RADIO_TEST_TONE_NORMAL_AUDIO = 0,
+ V4L2_CG2900_RADIO_TEST_TONE_ZERO = 1,
+ V4L2_CG2900_RADIO_TEST_TONE_TONE_1 = 2,
+ V4L2_CG2900_RADIO_TEST_TONE_TONE_2 = 3,
+ V4L2_CG2900_RADIO_TEST_TONE_TONE_SUM = 4,
+};
+#define V4L2_CID_CG2900_RADIO_TEST_TONE_SET_PARAMS (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+14)
+#define V4L2_CID_CG2900_RADIO_TUNE_DEEMPHASIS (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+15)
+enum v4l2_cg2900_radio_deemphasis {
+ V4L2_CG2900_RADIO_DEEMPHASIS_DISABLED = 0,
+ V4L2_CG2900_RADIO_DEEMPHASIS_50_uS = 1,
+ V4L2_CG2900_RADIO_DEEMPHASIS_75_uS = 2,
+};
+#define V4L2_CID_CG2900_RADIO_GET_INTERRUPT (V4L2_CID_CG2900_RADIO_PRIVATE_BASE+16)
+enum v4l2_cg2900_radio_interrupt {
+ V4L2_CG2900_RADIO_INTERRUPT_UNKNOWN = 0,
+ V4L2_CG2900_RADIO_INTERRUPT_SEARCH_COMPLETED = 1,
+ V4L2_CG2900_RADIO_INTERRUPT_BAND_SCAN_COMPLETED = 2,
+ V4L2_CG2900_RADIO_INTERRUPT_BLOCK_SCAN_COMPLETED = 3,
+ V4L2_CG2900_RADIO_INTERRUPT_SCAN_CANCELLED = 4,
+ V4L2_CG2900_RADIO_INTERRUPT_MONO_STEREO_TRANSITION = 5,
+ V4L2_CG2900_RADIO_INTERRUPT_DEVICE_RESET = 6,
+ V4L2_CG2900_RADIO_INTERRUPT_RDS_RECEIVED = 7
+};
+
/* Camera class control IDs */
#define V4L2_CID_CAMERA_CLASS_BASE (V4L2_CTRL_CLASS_CAMERA | 0x900)
#define V4L2_CID_CAMERA_CLASS (V4L2_CTRL_CLASS_CAMERA | 1)