2013-04-18 16:59:06

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH v9 00/12] Driver for Si476x series of chips

Driver for Si476x series of chips

This is a eight version of the patchset originaly posted here:
https://lkml.org/lkml/2012/9/13/590

Second version of the patch was posted here:
https://lkml.org/lkml/2012/10/5/598

Third version of the patch was posted here:
https://lkml.org/lkml/2012/10/23/510

Fourth version of the patch was posted here:
https://lkml.org/lkml/2013/2/18/572

Fifth version of the patch was posted here:
https://lkml.org/lkml/2013/2/26/45

Sixth version of the patch was posted here:
https://lkml.org/lkml/2013/2/26/257

Seventh version of the patch was posted here:
https://lkml.org/lkml/2013/2/27/22

Eighth version of the patch was posted here:
https://lkml.org/lkml/2013/3/26/891

To save everyone's time I'll repost the original description of it:

This patchset contains a driver for a Silicon Laboratories 476x series
of radio tuners. The driver itself is implemented as an MFD devices
comprised of three parts:
1. Core device that provides all the other devices with basic
functionality and locking scheme.
2. Radio device that translates between V4L2 subsystem requests into
Core device commands.
3. Codec device that does similar to the earlier described task, but
for ALSA SoC subsystem.

v9 of this driver has following changes:
- MFD part of the driver no longer depends on the header file added
by the radio driver(media/si476x.h) which should potential
restore the bisectability of the patches

Mauro, I am not sure if you reverted changes in patches 5 - 7, so I am
including them just in case.

Hans, some of the patches you gave your ACK to were changed, but since
the only thing changed is the location of the original code(it was
rearranged into different files) I did not remove your ACKs from the
new commits. I hope you don't mind, but if you do, let me know and
I'll post an updated version of the patchset so it would be clear that
it is not ready to be merged.

Please note, taht patch #12 is the modified version of
https://patchwork-mail.kernel.org/patch/2420751/
It _was not_ ACKEd by anyone.

Samuel, I couldn't just move media/si476x.h to mfd patches because it
would also break bisectability since media/si476x.h depends on patch
#8 in this patchset(whcih is the change that should go through 'media' tree)
But I rearranged definitions and there shouldn't be any dependencies on
media patches in MFD part.

Andrey Smirnov (10):
mfd: Add commands abstraction layer for SI476X MFD
mfd: Add the main bulk of core driver for SI476x code
mfd: Add chip properties handling code for SI476X MFD
mfd: Add header files and Kbuild plumbing for SI476x MFD core
v4l2: Fix the type of V4L2_CID_TUNE_PREEMPHASIS in the documentation
v4l2: Add standard controls for FM receivers
v4l2: Add documentation for the FM RX controls
v4l2: Add private controls base for SI476X
v4l2: Add a V4L2 driver for SI476X MFD
radio-si476x: Fix incorrect pointer checking

Hans Verkuil (1):
si476x: Fix some config dependencies and a compile warnings

Mauro Carvalho Chehab (1):
radio-si476x: vidioc_s* now uses a const parameter

Documentation/DocBook/media/v4l/compat.xml | 3 +
Documentation/DocBook/media/v4l/controls.xml | 74 +-
.../DocBook/media/v4l/vidioc-g-ext-ctrls.xml | 9 +
Documentation/video4linux/si476x.txt | 187 +++
drivers/media/radio/Kconfig | 17 +
drivers/media/radio/Makefile | 1 +
drivers/media/radio/radio-si476x.c | 1575 ++++++++++++++++++++
drivers/media/v4l2-core/v4l2-ctrls.c | 14 +-
drivers/mfd/Kconfig | 13 +
drivers/mfd/Makefile | 4 +
drivers/mfd/si476x-cmd.c | 1553 +++++++++++++++++++
drivers/mfd/si476x-i2c.c | 886 +++++++++++
drivers/mfd/si476x-prop.c | 242 +++
include/linux/mfd/si476x-core.h | 533 +++++++
include/linux/mfd/si476x-platform.h | 267 ++++
include/linux/mfd/si476x-reports.h | 163 ++
include/media/si476x.h | 37 +
include/uapi/linux/v4l2-controls.h | 17 +
18 files changed, 5591 insertions(+), 4 deletions(-)
create mode 100644 Documentation/video4linux/si476x.txt
create mode 100644 drivers/media/radio/radio-si476x.c
create mode 100644 drivers/mfd/si476x-cmd.c
create mode 100644 drivers/mfd/si476x-i2c.c
create mode 100644 drivers/mfd/si476x-prop.c
create mode 100644 include/linux/mfd/si476x-core.h
create mode 100644 include/linux/mfd/si476x-platform.h
create mode 100644 include/linux/mfd/si476x-reports.h
create mode 100644 include/media/si476x.h

--
1.7.10.4


2013-04-18 16:59:18

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH 07/12] v4l2: Add documentation for the FM RX controls

Add appropriate documentation for all the newly added standard
controls.

Based on the patch by Manjunatha Halli [1]

[1] http://lists-archives.com/linux-kernel/27641303-media-update-docs-for-v4l2-fm-new-features.html

Acked-by: Hans Verkuil <[email protected]>
Signed-off-by: Andrey Smirnov <[email protected]>
---
Documentation/DocBook/media/v4l/compat.xml | 3 +
Documentation/DocBook/media/v4l/controls.xml | 72 ++++++++++++++++++++
.../DocBook/media/v4l/vidioc-g-ext-ctrls.xml | 9 +++
3 files changed, 84 insertions(+)

diff --git a/Documentation/DocBook/media/v4l/compat.xml b/Documentation/DocBook/media/v4l/compat.xml
index 104a1a2..f418bc3 100644
--- a/Documentation/DocBook/media/v4l/compat.xml
+++ b/Documentation/DocBook/media/v4l/compat.xml
@@ -2310,6 +2310,9 @@ more information.</para>
<listitem>
<para>Added FM Modulator (FM TX) Extended Control Class: <constant>V4L2_CTRL_CLASS_FM_TX</constant> and their Control IDs.</para>
</listitem>
+<listitem>
+ <para>Added FM Receiver (FM RX) Extended Control Class: <constant>V4L2_CTRL_CLASS_FM_RX</constant> and their Control IDs.</para>
+ </listitem>
<listitem>
<para>Added Remote Controller chapter, describing the default Remote Controller mapping for media devices.</para>
</listitem>
diff --git a/Documentation/DocBook/media/v4l/controls.xml b/Documentation/DocBook/media/v4l/controls.xml
index 1ad20cc..6aa647a 100644
--- a/Documentation/DocBook/media/v4l/controls.xml
+++ b/Documentation/DocBook/media/v4l/controls.xml
@@ -4687,4 +4687,76 @@ interface and may change in the future.</para>
</table>

</section>
+
+ <section id="fm-rx-controls">
+ <title>FM Receiver Control Reference</title>
+
+ <para>The FM Receiver (FM_RX) class includes controls for common features of
+ FM Reception capable devices.</para>
+
+ <table pgwide="1" frame="none" id="fm-rx-control-id">
+ <title>FM_RX Control IDs</title>
+
+ <tgroup cols="4">
+ <colspec colname="c1" colwidth="1*" />
+ <colspec colname="c2" colwidth="6*" />
+ <colspec colname="c3" colwidth="2*" />
+ <colspec colname="c4" colwidth="6*" />
+ <spanspec namest="c1" nameend="c2" spanname="id" />
+ <spanspec namest="c2" nameend="c4" spanname="descr" />
+ <thead>
+ <row>
+ <entry spanname="id" align="left">ID</entry>
+ <entry align="left">Type</entry>
+ </row><row rowsep="1"><entry spanname="descr" align="left">Description</entry>
+ </row>
+ </thead>
+ <tbody valign="top">
+ <row><entry></entry></row>
+ <row>
+ <entry spanname="id"><constant>V4L2_CID_FM_RX_CLASS</constant>&nbsp;</entry>
+ <entry>class</entry>
+ </row><row><entry spanname="descr">The FM_RX class
+descriptor. Calling &VIDIOC-QUERYCTRL; for this control will return a
+description of this control class.</entry>
+ </row>
+ <row>
+ <entry spanname="id"><constant>V4L2_CID_RDS_RECEPTION</constant>&nbsp;</entry>
+ <entry>boolean</entry>
+ </row><row><entry spanname="descr">Enables/disables RDS
+ reception by the radio tuner</entry>
+ </row>
+ <row>
+ <entry spanname="id"><constant>V4L2_CID_TUNE_DEEMPHASIS</constant>&nbsp;</entry>
+ <entry>enum v4l2_deemphasis</entry>
+ </row>
+ <row id="v4l2-deemphasis"><entry spanname="descr">Configures the de-emphasis value for reception.
+A de-emphasis filter is applied to the broadcast to accentuate the high audio frequencies.
+Depending on the region, a time constant of either 50 or 75 useconds is used. The enum&nbsp;v4l2_deemphasis
+defines possible values for de-emphasis. Here they are:</entry>
+ </row><row>
+ <entrytbl spanname="descr" cols="2">
+ <tbody valign="top">
+ <row>
+ <entry><constant>V4L2_DEEMPHASIS_DISABLED</constant>&nbsp;</entry>
+ <entry>No de-emphasis is applied.</entry>
+ </row>
+ <row>
+ <entry><constant>V4L2_DEEMPHASIS_50_uS</constant>&nbsp;</entry>
+ <entry>A de-emphasis of 50 uS is used.</entry>
+ </row>
+ <row>
+ <entry><constant>V4L2_DEEMPHASIS_75_uS</constant>&nbsp;</entry>
+ <entry>A de-emphasis of 75 uS is used.</entry>
+ </row>
+ </tbody>
+ </entrytbl>
+
+ </row>
+ <row><entry></entry></row>
+ </tbody>
+ </tgroup>
+ </table>
+
+ </section>
</section>
diff --git a/Documentation/DocBook/media/v4l/vidioc-g-ext-ctrls.xml b/Documentation/DocBook/media/v4l/vidioc-g-ext-ctrls.xml
index 4e16112..b3bb957 100644
--- a/Documentation/DocBook/media/v4l/vidioc-g-ext-ctrls.xml
+++ b/Documentation/DocBook/media/v4l/vidioc-g-ext-ctrls.xml
@@ -319,6 +319,15 @@ These controls are described in <xref
processing controls. These controls are described in <xref
linkend="image-process-controls" />.</entry>
</row>
+
+ <row>
+ <entry><constant>V4L2_CTRL_CLASS_FM_RX</constant></entry>
+ <entry>0xa10000</entry>
+ <entry>The class containing FM Receiver (FM RX) controls.
+These controls are described in <xref
+ linkend="fm-rx-controls" />.</entry>
+ </row>
+
</tbody>
</tgroup>
</table>
--
1.7.10.4

2013-04-18 16:59:28

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH 09/12] v4l2: Add a V4L2 driver for SI476X MFD

From: Andrey Smirnov <andreysm@charmander.(none)>

This commit adds a driver that exposes all the radio related
functionality of the Si476x series of chips via the V4L2 subsystem.

Acked-by: Hans Verkuil <[email protected]>
Signed-off-by: Andrey Smirnov <[email protected]>
---
Documentation/video4linux/si476x.txt | 187 ++++
drivers/media/radio/Kconfig | 17 +
drivers/media/radio/Makefile | 1 +
drivers/media/radio/radio-si476x.c | 1599 ++++++++++++++++++++++++++++++++++
include/media/si476x.h | 37 +
5 files changed, 1841 insertions(+)
create mode 100644 Documentation/video4linux/si476x.txt
create mode 100644 drivers/media/radio/radio-si476x.c
create mode 100644 include/media/si476x.h

diff --git a/Documentation/video4linux/si476x.txt b/Documentation/video4linux/si476x.txt
new file mode 100644
index 0000000..d1a08db
--- /dev/null
+++ b/Documentation/video4linux/si476x.txt
@@ -0,0 +1,187 @@
+SI476x Driver Readme
+------------------------------------------------
+ Copyright (C) 2013 Andrey Smirnov <[email protected]>
+
+TODO for the driver
+------------------------------
+
+- According to the SiLabs' datasheet it is possible to update the
+ firmware of the radio chip in the run-time, thus bringing it to the
+ most recent version. Unfortunately I couldn't find any mentioning of
+ the said firmware update for the old chips that I tested the driver
+ against, so for chips like that the driver only exposes the old
+ functionality.
+
+
+Parameters exposed over debugfs
+-------------------------------
+SI476x allow user to get multiple characteristics that can be very
+useful for EoL testing/RF performance estimation, parameters that have
+very little to do with V4L2 subsystem. Such parameters are exposed via
+debugfs and can be accessed via regular file I/O operations.
+
+The drivers exposes following files:
+
+* /sys/kernel/debug/<device-name>/acf
+ This file contains ACF(Automatically Controlled Features) status
+ information. The contents of the file is binary data of the
+ following layout:
+
+ Offset | Name | Description
+ ====================================================================
+ 0x00 | blend_int | Flag, set when stereo separation has
+ | | crossed below the blend threshold
+ --------------------------------------------------------------------
+ 0x01 | hblend_int | Flag, set when HiBlend cutoff
+ | | frequency is lower than threshold
+ --------------------------------------------------------------------
+ 0x02 | hicut_int | Flag, set when HiCut cutoff
+ | | frequency is lower than threshold
+ --------------------------------------------------------------------
+ 0x03 | chbw_int | Flag, set when channel filter
+ | | bandwidth is less than threshold
+ --------------------------------------------------------------------
+ 0x04 | softmute_int | Flag indicating that softmute
+ | | attenuation has increased above
+ | | softmute threshold
+ --------------------------------------------------------------------
+ 0x05 | smute | 0 - Audio is not soft muted
+ | | 1 - Audio is soft muted
+ --------------------------------------------------------------------
+ 0x06 | smattn | Soft mute attenuation level in dB
+ --------------------------------------------------------------------
+ 0x07 | chbw | Channel filter bandwidth in kHz
+ --------------------------------------------------------------------
+ 0x08 | hicut | HiCut cutoff frequency in units of
+ | | 100Hz
+ --------------------------------------------------------------------
+ 0x09 | hiblend | HiBlend cutoff frequency in units
+ | | of 100 Hz
+ --------------------------------------------------------------------
+ 0x10 | pilot | 0 - Stereo pilot is not present
+ | | 1 - Stereo pilot is present
+ --------------------------------------------------------------------
+ 0x11 | stblend | Stereo blend in %
+ --------------------------------------------------------------------
+
+
+* /sys/kernel/debug/<device-name>/rds_blckcnt
+ This file contains statistics about RDS receptions. It's binary data
+ has the following layout:
+
+ Offset | Name | Description
+ ====================================================================
+ 0x00 | expected | Number of expected RDS blocks
+ --------------------------------------------------------------------
+ 0x02 | received | Number of received RDS blocks
+ --------------------------------------------------------------------
+ 0x04 | uncorrectable | Number of uncorrectable RDS blocks
+ --------------------------------------------------------------------
+
+* /sys/kernel/debug/<device-name>/agc
+ This file contains information about parameters pertaining to
+ AGC(Automatic Gain Control)
+
+ The layout is:
+ Offset | Name | Description
+ ====================================================================
+ 0x00 | mxhi | 0 - FM Mixer PD high threshold is
+ | | not tripped
+ | | 1 - FM Mixer PD high threshold is
+ | | tripped
+ --------------------------------------------------------------------
+ 0x01 | mxlo | ditto for FM Mixer PD low
+ --------------------------------------------------------------------
+ 0x02 | lnahi | ditto for FM LNA PD high
+ --------------------------------------------------------------------
+ 0x03 | lnalo | ditto for FM LNA PD low
+ --------------------------------------------------------------------
+ 0x04 | fmagc1 | FMAGC1 attenuator resistance
+ | | (see datasheet for more detail)
+ --------------------------------------------------------------------
+ 0x05 | fmagc2 | ditto for FMAGC2
+ --------------------------------------------------------------------
+ 0x06 | pgagain | PGA gain in dB
+ --------------------------------------------------------------------
+ 0x07 | fmwblang | FM/WB LNA Gain in dB
+ --------------------------------------------------------------------
+
+* /sys/kernel/debug/<device-name>/rsq
+ This file contains information about parameters pertaining to
+ RSQ(Received Signal Quality)
+
+ The layout is:
+ Offset | Name | Description
+ ====================================================================
+ 0x00 | multhint | 0 - multipath value has not crossed
+ | | the Multipath high threshold
+ | | 1 - multipath value has crossed
+ | | the Multipath high threshold
+ --------------------------------------------------------------------
+ 0x01 | multlint | ditto for Multipath low threshold
+ --------------------------------------------------------------------
+ 0x02 | snrhint | 0 - received signal's SNR has not
+ | | crossed high threshold
+ | | 1 - received signal's SNR has
+ | | crossed high threshold
+ --------------------------------------------------------------------
+ 0x03 | snrlint | ditto for low threshold
+ --------------------------------------------------------------------
+ 0x04 | rssihint | ditto for RSSI high threshold
+ --------------------------------------------------------------------
+ 0x05 | rssilint | ditto for RSSI low threshold
+ --------------------------------------------------------------------
+ 0x06 | bltf | Flag indicating if seek command
+ | | reached/wrapped seek band limit
+ --------------------------------------------------------------------
+ 0x07 | snr_ready | Indicates that SNR metrics is ready
+ --------------------------------------------------------------------
+ 0x08 | rssiready | ditto for RSSI metrics
+ --------------------------------------------------------------------
+ 0x09 | injside | 0 - Low-side injection is being used
+ | | 1 - High-side injection is used
+ --------------------------------------------------------------------
+ 0x10 | afcrl | Flag indicating if AFC rails
+ --------------------------------------------------------------------
+ 0x11 | valid | Flag indicating if channel is valid
+ --------------------------------------------------------------------
+ 0x12 | readfreq | Current tuned frequency
+ --------------------------------------------------------------------
+ 0x14 | freqoff | Singed frequency offset in units of
+ | | 2ppm
+ --------------------------------------------------------------------
+ 0x15 | rssi | Signed value of RSSI in dBuV
+ --------------------------------------------------------------------
+ 0x16 | snr | Signed RF SNR in dB
+ --------------------------------------------------------------------
+ 0x17 | issi | Signed Image Strength Signal
+ | | indicator
+ --------------------------------------------------------------------
+ 0x18 | lassi | Signed Low side adjacent Channel
+ | | Strength indicator
+ --------------------------------------------------------------------
+ 0x19 | hassi | ditto fpr High side
+ --------------------------------------------------------------------
+ 0x20 | mult | Multipath indicator
+ --------------------------------------------------------------------
+ 0x21 | dev | Frequency deviation
+ --------------------------------------------------------------------
+ 0x24 | assi | Adjascent channel SSI
+ --------------------------------------------------------------------
+ 0x25 | usn | Ultrasonic noise indicator
+ --------------------------------------------------------------------
+ 0x26 | pilotdev | Pilot deviation in units of 100 Hz
+ --------------------------------------------------------------------
+ 0x27 | rdsdev | ditto for RDS
+ --------------------------------------------------------------------
+ 0x28 | assidev | ditto for ASSI
+ --------------------------------------------------------------------
+ 0x29 | strongdev | Frequency deviation
+ --------------------------------------------------------------------
+ 0x30 | rdspi | RDS PI code
+ --------------------------------------------------------------------
+
+* /sys/kernel/debug/<device-name>/rsq_primary
+ This file contains information about parameters pertaining to
+ RSQ(Received Signal Quality) for primary tuner only. Layout is as
+ the one above.
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
index ead9928..170460d 100644
--- a/drivers/media/radio/Kconfig
+++ b/drivers/media/radio/Kconfig
@@ -18,6 +18,23 @@ config RADIO_SI470X

source "drivers/media/radio/si470x/Kconfig"

+config RADIO_SI476X
+ tristate "Silicon Laboratories Si476x I2C FM Radio"
+ depends on I2C && VIDEO_V4L2
+ select MFD_CORE
+ select MFD_SI476X_CORE
+ select SND_SOC_SI476X
+ ---help---
+ Choose Y here if you have this FM radio chip.
+
+ In order to control your radio card, you will need to use programs
+ that are compatible with the Video For Linux 2 API. Information on
+ this API and pointers to "v4l2" 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-si476x.
+
config USB_MR800
tristate "AverMedia MR 800 USB FM radio support"
depends on USB && VIDEO_V4L2
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
index 303eaeb..0dcdb32 100644
--- a/drivers/media/radio/Makefile
+++ b/drivers/media/radio/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_RADIO_GEMTEK) += radio-gemtek.o
obj-$(CONFIG_RADIO_TRUST) += radio-trust.o
obj-$(CONFIG_I2C_SI4713) += si4713-i2c.o
obj-$(CONFIG_RADIO_SI4713) += radio-si4713.o
+obj-$(CONFIG_RADIO_SI476X) += radio-si476x.o
obj-$(CONFIG_RADIO_MIROPCM20) += radio-miropcm20.o
obj-$(CONFIG_USB_DSBR) += dsbr100.o
obj-$(CONFIG_RADIO_SI470X) += si470x/
diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c
new file mode 100644
index 0000000..0895a0c
--- /dev/null
+++ b/drivers/media/radio/radio-si476x.c
@@ -0,0 +1,1599 @@
+/*
+ * drivers/media/radio/radio-si476x.c -- V4L2 driver for SI476X chips
+ *
+ * Copyright (C) 2012 Innovative Converged Devices(ICD)
+ * Copyright (C) 2013 Andrey Smirnov
+ *
+ * Author: Andrey Smirnov <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/atomic.h>
+#include <linux/videodev2.h>
+#include <linux/mutex.h>
+#include <linux/debugfs.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-ctrls.h>
+#include <media/v4l2-event.h>
+#include <media/v4l2-device.h>
+
+#include <media/si476x.h>
+#include <linux/mfd/si476x-core.h>
+
+#define FM_FREQ_RANGE_LOW 64000000
+#define FM_FREQ_RANGE_HIGH 108000000
+
+#define AM_FREQ_RANGE_LOW 520000
+#define AM_FREQ_RANGE_HIGH 30000000
+
+#define PWRLINEFLTR (1 << 8)
+
+#define FREQ_MUL (10000000 / 625)
+
+#define SI476X_PHDIV_STATUS_LINK_LOCKED(status) (0b10000000 & (status))
+
+#define DRIVER_NAME "si476x-radio"
+#define DRIVER_CARD "SI476x AM/FM Receiver"
+
+enum si476x_freq_bands {
+ SI476X_BAND_FM,
+ SI476X_BAND_AM,
+};
+
+static const struct v4l2_frequency_band si476x_bands[] = {
+ [SI476X_BAND_FM] = {
+ .type = V4L2_TUNER_RADIO,
+ .index = SI476X_BAND_FM,
+ .capability = V4L2_TUNER_CAP_LOW
+ | V4L2_TUNER_CAP_STEREO
+ | V4L2_TUNER_CAP_RDS
+ | V4L2_TUNER_CAP_RDS_BLOCK_IO
+ | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 64 * FREQ_MUL,
+ .rangehigh = 108 * FREQ_MUL,
+ .modulation = V4L2_BAND_MODULATION_FM,
+ },
+ [SI476X_BAND_AM] = {
+ .type = V4L2_TUNER_RADIO,
+ .index = SI476X_BAND_AM,
+ .capability = V4L2_TUNER_CAP_LOW
+ | V4L2_TUNER_CAP_FREQ_BANDS,
+ .rangelow = 0.52 * FREQ_MUL,
+ .rangehigh = 30 * FREQ_MUL,
+ .modulation = V4L2_BAND_MODULATION_AM,
+ },
+};
+
+static inline bool si476x_radio_freq_is_inside_of_the_band(u32 freq, int band)
+{
+ return freq >= si476x_bands[band].rangelow &&
+ freq <= si476x_bands[band].rangehigh;
+}
+
+static inline bool si476x_radio_range_is_inside_of_the_band(u32 low, u32 high,
+ int band)
+{
+ return low >= si476x_bands[band].rangelow &&
+ high <= si476x_bands[band].rangehigh;
+}
+
+static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl);
+static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl);
+
+enum phase_diversity_modes_idx {
+ SI476X_IDX_PHDIV_DISABLED,
+ SI476X_IDX_PHDIV_PRIMARY_COMBINING,
+ SI476X_IDX_PHDIV_PRIMARY_ANTENNA,
+ SI476X_IDX_PHDIV_SECONDARY_ANTENNA,
+ SI476X_IDX_PHDIV_SECONDARY_COMBINING,
+};
+
+static const char * const phase_diversity_modes[] = {
+ [SI476X_IDX_PHDIV_DISABLED] = "Disabled",
+ [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = "Primary with Secondary",
+ [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = "Primary Antenna",
+ [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = "Secondary Antenna",
+ [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = "Secondary with Primary",
+};
+
+static inline enum phase_diversity_modes_idx
+si476x_phase_diversity_mode_to_idx(enum si476x_phase_diversity_mode mode)
+{
+ switch (mode) {
+ default: /* FALLTHROUGH */
+ case SI476X_PHDIV_DISABLED:
+ return SI476X_IDX_PHDIV_DISABLED;
+ case SI476X_PHDIV_PRIMARY_COMBINING:
+ return SI476X_IDX_PHDIV_PRIMARY_COMBINING;
+ case SI476X_PHDIV_PRIMARY_ANTENNA:
+ return SI476X_IDX_PHDIV_PRIMARY_ANTENNA;
+ case SI476X_PHDIV_SECONDARY_ANTENNA:
+ return SI476X_IDX_PHDIV_SECONDARY_ANTENNA;
+ case SI476X_PHDIV_SECONDARY_COMBINING:
+ return SI476X_IDX_PHDIV_SECONDARY_COMBINING;
+ }
+}
+
+static inline enum si476x_phase_diversity_mode
+si476x_phase_diversity_idx_to_mode(enum phase_diversity_modes_idx idx)
+{
+ static const int idx_to_value[] = {
+ [SI476X_IDX_PHDIV_DISABLED] = SI476X_PHDIV_DISABLED,
+ [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = SI476X_PHDIV_PRIMARY_COMBINING,
+ [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = SI476X_PHDIV_PRIMARY_ANTENNA,
+ [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = SI476X_PHDIV_SECONDARY_ANTENNA,
+ [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = SI476X_PHDIV_SECONDARY_COMBINING,
+ };
+
+ return idx_to_value[idx];
+}
+
+static const struct v4l2_ctrl_ops si476x_ctrl_ops = {
+ .g_volatile_ctrl = si476x_radio_g_volatile_ctrl,
+ .s_ctrl = si476x_radio_s_ctrl,
+};
+
+
+enum si476x_ctrl_idx {
+ SI476X_IDX_RSSI_THRESHOLD,
+ SI476X_IDX_SNR_THRESHOLD,
+ SI476X_IDX_MAX_TUNE_ERROR,
+ SI476X_IDX_HARMONICS_COUNT,
+ SI476X_IDX_DIVERSITY_MODE,
+ SI476X_IDX_INTERCHIP_LINK,
+};
+static struct v4l2_ctrl_config si476x_ctrls[] = {
+
+ /**
+ * SI476X during its station seeking(or tuning) process uses several
+ * parameters to detrmine if "the station" is valid:
+ *
+ * - Signal's SNR(in dBuV) must be lower than
+ * #V4L2_CID_SI476X_SNR_THRESHOLD
+ * - Signal's RSSI(in dBuV) must be greater than
+ * #V4L2_CID_SI476X_RSSI_THRESHOLD
+ * - Signal's frequency deviation(in units of 2ppm) must not be
+ * more than #V4L2_CID_SI476X_MAX_TUNE_ERROR
+ */
+ [SI476X_IDX_RSSI_THRESHOLD] = {
+ .ops = &si476x_ctrl_ops,
+ .id = V4L2_CID_SI476X_RSSI_THRESHOLD,
+ .name = "Valid RSSI Threshold",
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .min = -128,
+ .max = 127,
+ .step = 1,
+ },
+ [SI476X_IDX_SNR_THRESHOLD] = {
+ .ops = &si476x_ctrl_ops,
+ .id = V4L2_CID_SI476X_SNR_THRESHOLD,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Valid SNR Threshold",
+ .min = -128,
+ .max = 127,
+ .step = 1,
+ },
+ [SI476X_IDX_MAX_TUNE_ERROR] = {
+ .ops = &si476x_ctrl_ops,
+ .id = V4L2_CID_SI476X_MAX_TUNE_ERROR,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+ .name = "Max Tune Errors",
+ .min = 0,
+ .max = 126 * 2,
+ .step = 2,
+ },
+
+ /**
+ * #V4L2_CID_SI476X_HARMONICS_COUNT -- number of harmonics
+ * built-in power-line noise supression filter is to reject
+ * during AM-mode operation.
+ */
+ [SI476X_IDX_HARMONICS_COUNT] = {
+ .ops = &si476x_ctrl_ops,
+ .id = V4L2_CID_SI476X_HARMONICS_COUNT,
+ .type = V4L2_CTRL_TYPE_INTEGER,
+
+ .name = "Count of Harmonics to Reject",
+ .min = 0,
+ .max = 20,
+ .step = 1,
+ },
+
+ /**
+ * #V4L2_CID_SI476X_DIVERSITY_MODE -- configuration which
+ * two tuners working in diversity mode are to work in.
+ *
+ * - #SI476X_IDX_PHDIV_DISABLED diversity mode disabled
+ * - #SI476X_IDX_PHDIV_PRIMARY_COMBINING diversity mode is
+ * on, primary tuner's antenna is the main one.
+ * - #SI476X_IDX_PHDIV_PRIMARY_ANTENNA diversity mode is
+ * off, primary tuner's antenna is the main one.
+ * - #SI476X_IDX_PHDIV_SECONDARY_ANTENNA diversity mode is
+ * off, secondary tuner's antenna is the main one.
+ * - #SI476X_IDX_PHDIV_SECONDARY_COMBINING diversity mode is
+ * on, secondary tuner's antenna is the main one.
+ */
+ [SI476X_IDX_DIVERSITY_MODE] = {
+ .ops = &si476x_ctrl_ops,
+ .id = V4L2_CID_SI476X_DIVERSITY_MODE,
+ .type = V4L2_CTRL_TYPE_MENU,
+ .name = "Phase Diversity Mode",
+ .qmenu = phase_diversity_modes,
+ .min = 0,
+ .max = ARRAY_SIZE(phase_diversity_modes) - 1,
+ },
+
+ /**
+ * #V4L2_CID_SI476X_INTERCHIP_LINK -- inter-chip link in
+ * diversity mode indicator. Allows user to determine if two
+ * chips working in diversity mode have established a link
+ * between each other and if the system as a whole uses
+ * signals from both antennas to receive FM radio.
+ */
+ [SI476X_IDX_INTERCHIP_LINK] = {
+ .ops = &si476x_ctrl_ops,
+ .id = V4L2_CID_SI476X_INTERCHIP_LINK,
+ .type = V4L2_CTRL_TYPE_BOOLEAN,
+ .flags = V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_VOLATILE,
+ .name = "Inter-Chip Link",
+ .min = 0,
+ .max = 1,
+ .step = 1,
+ },
+};
+
+struct si476x_radio;
+
+/**
+ * struct si476x_radio_ops - vtable of tuner functions
+ *
+ * This table holds pointers to functions implementing particular
+ * operations depending on the mode in which the tuner chip was
+ * configured to start in. If the function is not supported
+ * corresponding element is set to #NULL.
+ *
+ * @tune_freq: Tune chip to a specific frequency
+ * @seek_start: Star station seeking
+ * @rsq_status: Get Recieved Signal Quality(RSQ) status
+ * @rds_blckcnt: Get recived RDS blocks count
+ * @phase_diversity: Change phase diversity mode of the tuner
+ * @phase_div_status: Get phase diversity mode status
+ * @acf_status: Get the status of Automatically Controlled
+ * Features(ACF)
+ * @agc_status: Get Automatic Gain Control(AGC) status
+ */
+struct si476x_radio_ops {
+ int (*tune_freq)(struct si476x_core *, struct si476x_tune_freq_args *);
+ int (*seek_start)(struct si476x_core *, bool, bool);
+ int (*rsq_status)(struct si476x_core *, struct si476x_rsq_status_args *,
+ struct si476x_rsq_status_report *);
+ int (*rds_blckcnt)(struct si476x_core *, bool,
+ struct si476x_rds_blockcount_report *);
+
+ int (*phase_diversity)(struct si476x_core *,
+ enum si476x_phase_diversity_mode);
+ int (*phase_div_status)(struct si476x_core *);
+ int (*acf_status)(struct si476x_core *,
+ struct si476x_acf_status_report *);
+ int (*agc_status)(struct si476x_core *,
+ struct si476x_agc_status_report *);
+};
+
+/**
+ * struct si476x_radio - radio device
+ *
+ * @core: Pointer to underlying core device
+ * @videodev: Pointer to video device created by V4L2 subsystem
+ * @ops: Vtable of functions. See struct si476x_radio_ops for details
+ * @kref: Reference counter
+ * @core_lock: An r/w semaphore to brebvent the deletion of underlying
+ * core structure is the radio device is being used
+ */
+struct si476x_radio {
+ struct v4l2_device v4l2dev;
+ struct video_device videodev;
+ struct v4l2_ctrl_handler ctrl_handler;
+
+ struct si476x_core *core;
+ /* This field should not be accesses unless core lock is held */
+ const struct si476x_radio_ops *ops;
+
+ struct dentry *debugfs;
+ u32 audmode;
+};
+
+static inline struct si476x_radio *
+v4l2_dev_to_radio(struct v4l2_device *d)
+{
+ return container_of(d, struct si476x_radio, v4l2dev);
+}
+
+static inline struct si476x_radio *
+v4l2_ctrl_handler_to_radio(struct v4l2_ctrl_handler *d)
+{
+ return container_of(d, struct si476x_radio, ctrl_handler);
+}
+
+/*
+ * si476x_vidioc_querycap - query device capabilities
+ */
+static int si476x_radio_querycap(struct file *file, void *priv,
+ struct v4l2_capability *capability)
+{
+ struct si476x_radio *radio = video_drvdata(file);
+
+ strlcpy(capability->driver, radio->v4l2dev.name,
+ sizeof(capability->driver));
+ strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
+ snprintf(capability->bus_info, sizeof(capability->bus_info),
+ "platform:%s", radio->v4l2dev.name);
+
+ capability->device_caps = V4L2_CAP_TUNER
+ | V4L2_CAP_RADIO
+ | V4L2_CAP_HW_FREQ_SEEK;
+
+ si476x_core_lock(radio->core);
+ if (!si476x_core_is_a_secondary_tuner(radio->core))
+ capability->device_caps |= V4L2_CAP_RDS_CAPTURE
+ | V4L2_CAP_READWRITE;
+ si476x_core_unlock(radio->core);
+
+ capability->capabilities = capability->device_caps
+ | V4L2_CAP_DEVICE_CAPS;
+ return 0;
+}
+
+static int si476x_radio_enum_freq_bands(struct file *file, void *priv,
+ struct v4l2_frequency_band *band)
+{
+ int err;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ if (band->tuner != 0)
+ return -EINVAL;
+
+ switch (radio->core->chip_id) {
+ /* AM/FM tuners -- all bands are supported */
+ case SI476X_CHIP_SI4761:
+ case SI476X_CHIP_SI4764:
+ if (band->index < ARRAY_SIZE(si476x_bands)) {
+ *band = si476x_bands[band->index];
+ err = 0;
+ } else {
+ err = -EINVAL;
+ }
+ break;
+ /* FM companion tuner chips -- only FM bands are
+ * supported */
+ case SI476X_CHIP_SI4768:
+ if (band->index == SI476X_BAND_FM) {
+ *band = si476x_bands[band->index];
+ err = 0;
+ } else {
+ err = -EINVAL;
+ }
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static int si476x_radio_g_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ int err;
+ struct si476x_rsq_status_report report;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ struct si476x_rsq_status_args args = {
+ .primary = false,
+ .rsqack = false,
+ .attune = false,
+ .cancel = false,
+ .stcack = false,
+ };
+
+ if (tuner->index != 0)
+ return -EINVAL;
+
+ tuner->type = V4L2_TUNER_RADIO;
+ tuner->capability = V4L2_TUNER_CAP_LOW /* Measure frequencies
+ * in multiples of
+ * 62.5 Hz */
+ | V4L2_TUNER_CAP_STEREO
+ | V4L2_TUNER_CAP_HWSEEK_BOUNDED
+ | V4L2_TUNER_CAP_HWSEEK_WRAP
+ | V4L2_TUNER_CAP_HWSEEK_PROG_LIM;
+
+ si476x_core_lock(radio->core);
+
+ if (si476x_core_is_a_secondary_tuner(radio->core)) {
+ strlcpy(tuner->name, "FM (secondary)", sizeof(tuner->name));
+ tuner->rxsubchans = 0;
+ tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow;
+ } else if (si476x_core_has_am(radio->core)) {
+ if (si476x_core_is_a_primary_tuner(radio->core))
+ strlcpy(tuner->name, "AM/FM (primary)",
+ sizeof(tuner->name));
+ else
+ strlcpy(tuner->name, "AM/FM", sizeof(tuner->name));
+
+ tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO
+ | V4L2_TUNER_SUB_RDS;
+ tuner->capability |= V4L2_TUNER_CAP_RDS
+ | V4L2_TUNER_CAP_RDS_BLOCK_IO
+ | V4L2_TUNER_CAP_FREQ_BANDS;
+
+ tuner->rangelow = si476x_bands[SI476X_BAND_AM].rangelow;
+ } else {
+ strlcpy(tuner->name, "FM", sizeof(tuner->name));
+ tuner->rxsubchans = V4L2_TUNER_SUB_RDS;
+ tuner->capability |= V4L2_TUNER_CAP_RDS
+ | V4L2_TUNER_CAP_RDS_BLOCK_IO
+ | V4L2_TUNER_CAP_FREQ_BANDS;
+ tuner->rangelow = si476x_bands[SI476X_BAND_FM].rangelow;
+ }
+
+ tuner->audmode = radio->audmode;
+
+ tuner->afc = 1;
+ tuner->rangehigh = si476x_bands[SI476X_BAND_FM].rangehigh;
+
+ err = radio->ops->rsq_status(radio->core,
+ &args, &report);
+ if (err < 0) {
+ tuner->signal = 0;
+ } else {
+ /*
+ * tuner->signal value range: 0x0000 .. 0xFFFF,
+ * report.rssi: -128 .. 127
+ */
+ tuner->signal = (report.rssi + 128) * 257;
+ }
+ si476x_core_unlock(radio->core);
+
+ return err;
+}
+
+static int si476x_radio_s_tuner(struct file *file, void *priv,
+ struct v4l2_tuner *tuner)
+{
+ struct si476x_radio *radio = video_drvdata(file);
+
+ if (tuner->index != 0)
+ return -EINVAL;
+
+ if (tuner->audmode == V4L2_TUNER_MODE_MONO ||
+ tuner->audmode == V4L2_TUNER_MODE_STEREO)
+ radio->audmode = tuner->audmode;
+ else
+ radio->audmode = V4L2_TUNER_MODE_STEREO;
+
+ return 0;
+}
+
+static int si476x_radio_init_vtable(struct si476x_radio *radio,
+ enum si476x_func func)
+{
+ static const struct si476x_radio_ops fm_ops = {
+ .tune_freq = si476x_core_cmd_fm_tune_freq,
+ .seek_start = si476x_core_cmd_fm_seek_start,
+ .rsq_status = si476x_core_cmd_fm_rsq_status,
+ .rds_blckcnt = si476x_core_cmd_fm_rds_blockcount,
+ .phase_diversity = si476x_core_cmd_fm_phase_diversity,
+ .phase_div_status = si476x_core_cmd_fm_phase_div_status,
+ .acf_status = si476x_core_cmd_fm_acf_status,
+ .agc_status = si476x_core_cmd_agc_status,
+ };
+
+ static const struct si476x_radio_ops am_ops = {
+ .tune_freq = si476x_core_cmd_am_tune_freq,
+ .seek_start = si476x_core_cmd_am_seek_start,
+ .rsq_status = si476x_core_cmd_am_rsq_status,
+ .rds_blckcnt = NULL,
+ .phase_diversity = NULL,
+ .phase_div_status = NULL,
+ .acf_status = si476x_core_cmd_am_acf_status,
+ .agc_status = NULL,
+ };
+
+ switch (func) {
+ case SI476X_FUNC_FM_RECEIVER:
+ radio->ops = &fm_ops;
+ return 0;
+
+ case SI476X_FUNC_AM_RECEIVER:
+ radio->ops = &am_ops;
+ return 0;
+ default:
+ WARN(1, "Unexpected tuner function value\n");
+ return -EINVAL;
+ }
+}
+
+static int si476x_radio_pretune(struct si476x_radio *radio,
+ enum si476x_func func)
+{
+ int retval;
+
+ struct si476x_tune_freq_args args = {
+ .zifsr = false,
+ .hd = false,
+ .injside = SI476X_INJSIDE_AUTO,
+ .tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE,
+ .smoothmetrics = SI476X_SM_INITIALIZE_AUDIO,
+ .antcap = 0,
+ };
+
+ switch (func) {
+ case SI476X_FUNC_FM_RECEIVER:
+ args.freq = v4l2_to_si476x(radio->core,
+ 92 * FREQ_MUL);
+ retval = radio->ops->tune_freq(radio->core, &args);
+ break;
+ case SI476X_FUNC_AM_RECEIVER:
+ args.freq = v4l2_to_si476x(radio->core,
+ 0.6 * FREQ_MUL);
+ retval = radio->ops->tune_freq(radio->core, &args);
+ break;
+ default:
+ WARN(1, "Unexpected tuner function value\n");
+ retval = -EINVAL;
+ }
+
+ return retval;
+}
+static int si476x_radio_do_post_powerup_init(struct si476x_radio *radio,
+ enum si476x_func func)
+{
+ int err;
+
+ /* regcache_mark_dirty(radio->core->regmap); */
+ err = regcache_sync_region(radio->core->regmap,
+ SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE,
+ SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT);
+ if (err < 0)
+ return err;
+
+ err = regcache_sync_region(radio->core->regmap,
+ SI476X_PROP_AUDIO_DEEMPHASIS,
+ SI476X_PROP_AUDIO_PWR_LINE_FILTER);
+ if (err < 0)
+ return err;
+
+ err = regcache_sync_region(radio->core->regmap,
+ SI476X_PROP_INT_CTL_ENABLE,
+ SI476X_PROP_INT_CTL_ENABLE);
+ if (err < 0)
+ return err;
+
+ /*
+ * Is there any point in restoring SNR and the like
+ * when switching between AM/FM?
+ */
+ err = regcache_sync_region(radio->core->regmap,
+ SI476X_PROP_VALID_MAX_TUNE_ERROR,
+ SI476X_PROP_VALID_MAX_TUNE_ERROR);
+ if (err < 0)
+ return err;
+
+ err = regcache_sync_region(radio->core->regmap,
+ SI476X_PROP_VALID_SNR_THRESHOLD,
+ SI476X_PROP_VALID_RSSI_THRESHOLD);
+ if (err < 0)
+ return err;
+
+ if (func == SI476X_FUNC_FM_RECEIVER) {
+ if (si476x_core_has_diversity(radio->core)) {
+ err = si476x_core_cmd_fm_phase_diversity(radio->core,
+ radio->core->diversity_mode);
+ if (err < 0)
+ return err;
+ }
+
+ err = regcache_sync_region(radio->core->regmap,
+ SI476X_PROP_FM_RDS_INTERRUPT_SOURCE,
+ SI476X_PROP_FM_RDS_CONFIG);
+ if (err < 0)
+ return err;
+ }
+
+ return si476x_radio_init_vtable(radio, func);
+
+}
+
+static int si476x_radio_change_func(struct si476x_radio *radio,
+ enum si476x_func func)
+{
+ int err;
+ bool soft;
+ /*
+ * Since power/up down is a very time consuming operation,
+ * try to avoid doing it if the requested mode matches the one
+ * the tuner is in
+ */
+ if (func == radio->core->power_up_parameters.func)
+ return 0;
+
+ soft = true;
+ err = si476x_core_stop(radio->core, soft);
+ if (err < 0) {
+ /*
+ * OK, if the chip does not want to play nice let's
+ * try to reset it in more brutal way
+ */
+ soft = false;
+ err = si476x_core_stop(radio->core, soft);
+ if (err < 0)
+ return err;
+ }
+ /*
+ Set the desired radio tuner function
+ */
+ radio->core->power_up_parameters.func = func;
+
+ err = si476x_core_start(radio->core, soft);
+ if (err < 0)
+ return err;
+
+ /*
+ * No need to do the rest of manipulations for the bootlader
+ * mode
+ */
+ if (func != SI476X_FUNC_FM_RECEIVER &&
+ func != SI476X_FUNC_AM_RECEIVER)
+ return err;
+
+ return si476x_radio_do_post_powerup_init(radio, func);
+}
+
+static int si476x_radio_g_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ int err;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ if (f->tuner != 0 ||
+ f->type != V4L2_TUNER_RADIO)
+ return -EINVAL;
+
+ si476x_core_lock(radio->core);
+
+ if (radio->ops->rsq_status) {
+ struct si476x_rsq_status_report report;
+ struct si476x_rsq_status_args args = {
+ .primary = false,
+ .rsqack = false,
+ .attune = true,
+ .cancel = false,
+ .stcack = false,
+ };
+
+ err = radio->ops->rsq_status(radio->core, &args, &report);
+ if (!err)
+ f->frequency = si476x_to_v4l2(radio->core,
+ report.readfreq);
+ } else {
+ err = -EINVAL;
+ }
+
+ si476x_core_unlock(radio->core);
+
+ return err;
+}
+
+static int si476x_radio_s_frequency(struct file *file, void *priv,
+ struct v4l2_frequency *f)
+{
+ int err;
+ struct si476x_tune_freq_args args;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh +
+ si476x_bands[SI476X_BAND_FM].rangelow) / 2;
+ const int band = (f->frequency > midrange) ?
+ SI476X_BAND_FM : SI476X_BAND_AM;
+ const enum si476x_func func = (band == SI476X_BAND_AM) ?
+ SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER;
+
+ if (f->tuner != 0 ||
+ f->type != V4L2_TUNER_RADIO)
+ return -EINVAL;
+
+ si476x_core_lock(radio->core);
+
+ f->frequency = clamp(f->frequency,
+ si476x_bands[band].rangelow,
+ si476x_bands[band].rangehigh);
+
+ if (si476x_radio_freq_is_inside_of_the_band(f->frequency,
+ SI476X_BAND_AM) &&
+ (!si476x_core_has_am(radio->core) ||
+ si476x_core_is_a_secondary_tuner(radio->core))) {
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ err = si476x_radio_change_func(radio, func);
+ if (err < 0)
+ goto unlock;
+
+ args.zifsr = false;
+ args.hd = false;
+ args.injside = SI476X_INJSIDE_AUTO;
+ args.freq = v4l2_to_si476x(radio->core,
+ f->frequency);
+ args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE;
+ args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO;
+ args.antcap = 0;
+
+ err = radio->ops->tune_freq(radio->core, &args);
+
+unlock:
+ si476x_core_unlock(radio->core);
+ return err;
+}
+
+static int si476x_radio_s_hw_freq_seek(struct file *file, void *priv,
+ const struct v4l2_hw_freq_seek *seek)
+{
+ int err;
+ enum si476x_func func;
+ u32 rangelow, rangehigh;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ if (file->f_flags & O_NONBLOCK)
+ return -EAGAIN;
+
+ if (seek->tuner != 0 ||
+ seek->type != V4L2_TUNER_RADIO)
+ return -EINVAL;
+
+ si476x_core_lock(radio->core);
+
+ if (!seek->rangelow) {
+ err = regmap_read(radio->core->regmap,
+ SI476X_PROP_SEEK_BAND_BOTTOM,
+ &rangelow);
+ if (!err)
+ rangelow = si476x_to_v4l2(radio->core, rangelow);
+ else
+ goto unlock;
+ }
+ if (!seek->rangehigh) {
+ err = regmap_read(radio->core->regmap,
+ SI476X_PROP_SEEK_BAND_TOP,
+ &rangehigh);
+ if (!err)
+ rangehigh = si476x_to_v4l2(radio->core, rangehigh);
+ else
+ goto unlock;
+ }
+
+ if (rangelow > rangehigh) {
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ if (si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh,
+ SI476X_BAND_FM)) {
+ func = SI476X_FUNC_FM_RECEIVER;
+
+ } else if (si476x_core_has_am(radio->core) &&
+ si476x_radio_range_is_inside_of_the_band(rangelow, rangehigh,
+ SI476X_BAND_AM)) {
+ func = SI476X_FUNC_AM_RECEIVER;
+ } else {
+ err = -EINVAL;
+ goto unlock;
+ }
+
+ err = si476x_radio_change_func(radio, func);
+ if (err < 0)
+ goto unlock;
+
+ if (seek->rangehigh) {
+ err = regmap_write(radio->core->regmap,
+ SI476X_PROP_SEEK_BAND_TOP,
+ v4l2_to_si476x(radio->core,
+ seek->rangehigh));
+ if (err)
+ goto unlock;
+ }
+ if (seek->rangelow) {
+ err = regmap_write(radio->core->regmap,
+ SI476X_PROP_SEEK_BAND_BOTTOM,
+ v4l2_to_si476x(radio->core,
+ seek->rangelow));
+ if (err)
+ goto unlock;
+ }
+ if (seek->spacing) {
+ err = regmap_write(radio->core->regmap,
+ SI476X_PROP_SEEK_FREQUENCY_SPACING,
+ v4l2_to_si476x(radio->core,
+ seek->spacing));
+ if (err)
+ goto unlock;
+ }
+
+ err = radio->ops->seek_start(radio->core,
+ seek->seek_upward,
+ seek->wrap_around);
+unlock:
+ si476x_core_unlock(radio->core);
+
+
+
+ return err;
+}
+
+static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
+{
+ int retval;
+ struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler);
+
+ si476x_core_lock(radio->core);
+
+ switch (ctrl->id) {
+ case V4L2_CID_SI476X_INTERCHIP_LINK:
+ if (si476x_core_has_diversity(radio->core)) {
+ if (radio->ops->phase_diversity) {
+ retval = radio->ops->phase_div_status(radio->core);
+ if (retval < 0)
+ break;
+
+ ctrl->val = !!SI476X_PHDIV_STATUS_LINK_LOCKED(retval);
+ retval = 0;
+ break;
+ } else {
+ retval = -ENOTTY;
+ break;
+ }
+ }
+ retval = -EINVAL;
+ break;
+ default:
+ retval = -EINVAL;
+ break;
+ }
+ si476x_core_unlock(radio->core);
+ return retval;
+
+}
+
+static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl)
+{
+ int retval;
+ enum si476x_phase_diversity_mode mode;
+ struct si476x_radio *radio = v4l2_ctrl_handler_to_radio(ctrl->handler);
+
+ si476x_core_lock(radio->core);
+
+ switch (ctrl->id) {
+ case V4L2_CID_SI476X_HARMONICS_COUNT:
+ retval = regmap_update_bits(radio->core->regmap,
+ SI476X_PROP_AUDIO_PWR_LINE_FILTER,
+ SI476X_PROP_PWR_HARMONICS_MASK,
+ ctrl->val);
+ break;
+ case V4L2_CID_POWER_LINE_FREQUENCY:
+ switch (ctrl->val) {
+ case V4L2_CID_POWER_LINE_FREQUENCY_DISABLED:
+ retval = regmap_update_bits(radio->core->regmap,
+ SI476X_PROP_AUDIO_PWR_LINE_FILTER,
+ SI476X_PROP_PWR_ENABLE_MASK,
+ 0);
+ break;
+ case V4L2_CID_POWER_LINE_FREQUENCY_50HZ:
+ retval = regmap_update_bits(radio->core->regmap,
+ SI476X_PROP_AUDIO_PWR_LINE_FILTER,
+ SI476X_PROP_PWR_GRID_MASK,
+ SI476X_PROP_PWR_GRID_50HZ);
+ break;
+ case V4L2_CID_POWER_LINE_FREQUENCY_60HZ:
+ retval = regmap_update_bits(radio->core->regmap,
+ SI476X_PROP_AUDIO_PWR_LINE_FILTER,
+ SI476X_PROP_PWR_GRID_MASK,
+ SI476X_PROP_PWR_GRID_60HZ);
+ break;
+ default:
+ retval = -EINVAL;
+ break;
+ }
+ break;
+ case V4L2_CID_SI476X_RSSI_THRESHOLD:
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_VALID_RSSI_THRESHOLD,
+ ctrl->val);
+ break;
+ case V4L2_CID_SI476X_SNR_THRESHOLD:
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_VALID_SNR_THRESHOLD,
+ ctrl->val);
+ break;
+ case V4L2_CID_SI476X_MAX_TUNE_ERROR:
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_VALID_MAX_TUNE_ERROR,
+ ctrl->val);
+ break;
+ case V4L2_CID_RDS_RECEPTION:
+ /*
+ * It looks like RDS related properties are
+ * inaccesable when tuner is in AM mode, so cache the
+ * changes
+ */
+ if (si476x_core_is_in_am_receiver_mode(radio->core))
+ regcache_cache_only(radio->core->regmap, true);
+
+ if (ctrl->val) {
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT,
+ radio->core->rds_fifo_depth);
+ if (retval < 0)
+ break;
+
+ if (radio->core->client->irq) {
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_FM_RDS_INTERRUPT_SOURCE,
+ SI476X_RDSRECV);
+ if (retval < 0)
+ break;
+ }
+
+ /* Drain RDS FIFO before enabling RDS processing */
+ retval = si476x_core_cmd_fm_rds_status(radio->core,
+ false,
+ true,
+ true,
+ NULL);
+ if (retval < 0)
+ break;
+
+ retval = regmap_update_bits(radio->core->regmap,
+ SI476X_PROP_FM_RDS_CONFIG,
+ SI476X_PROP_RDSEN_MASK,
+ SI476X_PROP_RDSEN);
+ } else {
+ retval = regmap_update_bits(radio->core->regmap,
+ SI476X_PROP_FM_RDS_CONFIG,
+ SI476X_PROP_RDSEN_MASK,
+ !SI476X_PROP_RDSEN);
+ }
+
+ if (si476x_core_is_in_am_receiver_mode(radio->core))
+ regcache_cache_only(radio->core->regmap, false);
+ break;
+ case V4L2_CID_TUNE_DEEMPHASIS:
+ retval = regmap_write(radio->core->regmap,
+ SI476X_PROP_AUDIO_DEEMPHASIS,
+ ctrl->val);
+ break;
+
+ case V4L2_CID_SI476X_DIVERSITY_MODE:
+ mode = si476x_phase_diversity_idx_to_mode(ctrl->val);
+
+ if (mode == radio->core->diversity_mode) {
+ retval = 0;
+ break;
+ }
+
+ if (si476x_core_is_in_am_receiver_mode(radio->core)) {
+ /*
+ * Diversity cannot be configured while tuner
+ * is in AM mode so save the changes and carry on.
+ */
+ radio->core->diversity_mode = mode;
+ retval = 0;
+ } else {
+ retval = radio->ops->phase_diversity(radio->core, mode);
+ if (!retval)
+ radio->core->diversity_mode = mode;
+ }
+ break;
+
+ default:
+ retval = -EINVAL;
+ break;
+ }
+
+ si476x_core_unlock(radio->core);
+
+ return retval;
+}
+
+static int si476x_radio_g_chip_ident(struct file *file, void *fh,
+ struct v4l2_dbg_chip_ident *chip)
+{
+ if (chip->match.type == V4L2_CHIP_MATCH_HOST &&
+ v4l2_chip_match_host(&chip->match))
+ return 0;
+ return -EINVAL;
+}
+
+
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+static int si476x_radio_g_register(struct file *file, void *fh,
+ struct v4l2_dbg_register *reg)
+{
+ int err;
+ unsigned int value;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ si476x_core_lock(radio->core);
+ reg->size = 2;
+ err = regmap_read(radio->core->regmap,
+ (unsigned int)reg->reg, &value);
+ reg->val = value;
+ si476x_core_unlock(radio->core);
+
+ return err;
+}
+static int si476x_radio_s_register(struct file *file, void *fh,
+ struct v4l2_dbg_register *reg)
+{
+
+ int err;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ si476x_core_lock(radio->core);
+ err = regmap_write(radio->core->regmap,
+ (unsigned int)reg->reg,
+ (unsigned int)reg->val);
+ si476x_core_unlock(radio->core);
+
+ return err;
+}
+#endif
+
+static int si476x_radio_fops_open(struct file *file)
+{
+ struct si476x_radio *radio = video_drvdata(file);
+ int err;
+
+ err = v4l2_fh_open(file);
+ if (err)
+ return err;
+
+ if (v4l2_fh_is_singular_file(file)) {
+ si476x_core_lock(radio->core);
+ err = si476x_core_set_power_state(radio->core,
+ SI476X_POWER_UP_FULL);
+ if (err < 0)
+ goto done;
+
+ err = si476x_radio_do_post_powerup_init(radio,
+ radio->core->power_up_parameters.func);
+ if (err < 0)
+ goto power_down;
+
+ err = si476x_radio_pretune(radio,
+ radio->core->power_up_parameters.func);
+ if (err < 0)
+ goto power_down;
+
+ si476x_core_unlock(radio->core);
+ /*Must be done after si476x_core_unlock to prevent a deadlock*/
+ v4l2_ctrl_handler_setup(&radio->ctrl_handler);
+ }
+
+ return err;
+
+power_down:
+ si476x_core_set_power_state(radio->core,
+ SI476X_POWER_DOWN);
+done:
+ si476x_core_unlock(radio->core);
+ v4l2_fh_release(file);
+
+ return err;
+}
+
+static int si476x_radio_fops_release(struct file *file)
+{
+ int err;
+ struct si476x_radio *radio = video_drvdata(file);
+
+ if (v4l2_fh_is_singular_file(file) &&
+ atomic_read(&radio->core->is_alive))
+ si476x_core_set_power_state(radio->core,
+ SI476X_POWER_DOWN);
+
+ err = v4l2_fh_release(file);
+
+ return err;
+}
+
+static ssize_t si476x_radio_fops_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ ssize_t rval;
+ size_t fifo_len;
+ unsigned int copied;
+
+ struct si476x_radio *radio = video_drvdata(file);
+
+ /* block if no new data available */
+ if (kfifo_is_empty(&radio->core->rds_fifo)) {
+ if (file->f_flags & O_NONBLOCK)
+ return -EWOULDBLOCK;
+
+ rval = wait_event_interruptible(radio->core->rds_read_queue,
+ (!kfifo_is_empty(&radio->core->rds_fifo) ||
+ !atomic_read(&radio->core->is_alive)));
+ if (rval < 0)
+ return -EINTR;
+
+ if (!atomic_read(&radio->core->is_alive))
+ return -ENODEV;
+ }
+
+ fifo_len = kfifo_len(&radio->core->rds_fifo);
+
+ if (kfifo_to_user(&radio->core->rds_fifo, buf,
+ min(fifo_len, count),
+ &copied) != 0) {
+ dev_warn(&radio->videodev.dev,
+ "Error during FIFO to userspace copy\n");
+ rval = -EIO;
+ } else {
+ rval = (ssize_t)copied;
+ }
+
+ return rval;
+}
+
+static unsigned int si476x_radio_fops_poll(struct file *file,
+ struct poll_table_struct *pts)
+{
+ struct si476x_radio *radio = video_drvdata(file);
+ unsigned long req_events = poll_requested_events(pts);
+ unsigned int err = v4l2_ctrl_poll(file, pts);
+
+ if (req_events & (POLLIN | POLLRDNORM)) {
+ if (atomic_read(&radio->core->is_alive))
+ poll_wait(file, &radio->core->rds_read_queue, pts);
+
+ if (!atomic_read(&radio->core->is_alive))
+ err = POLLHUP;
+
+ if (!kfifo_is_empty(&radio->core->rds_fifo))
+ err = POLLIN | POLLRDNORM;
+ }
+
+ return err;
+}
+
+static const struct v4l2_file_operations si476x_fops = {
+ .owner = THIS_MODULE,
+ .read = si476x_radio_fops_read,
+ .poll = si476x_radio_fops_poll,
+ .unlocked_ioctl = video_ioctl2,
+ .open = si476x_radio_fops_open,
+ .release = si476x_radio_fops_release,
+};
+
+
+static const struct v4l2_ioctl_ops si4761_ioctl_ops = {
+ .vidioc_querycap = si476x_radio_querycap,
+ .vidioc_g_tuner = si476x_radio_g_tuner,
+ .vidioc_s_tuner = si476x_radio_s_tuner,
+
+ .vidioc_g_frequency = si476x_radio_g_frequency,
+ .vidioc_s_frequency = si476x_radio_s_frequency,
+ .vidioc_s_hw_freq_seek = si476x_radio_s_hw_freq_seek,
+ .vidioc_enum_freq_bands = si476x_radio_enum_freq_bands,
+
+ .vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
+ .vidioc_unsubscribe_event = v4l2_event_unsubscribe,
+
+ .vidioc_g_chip_ident = si476x_radio_g_chip_ident,
+#ifdef CONFIG_VIDEO_ADV_DEBUG
+ .vidioc_g_register = si476x_radio_g_register,
+ .vidioc_s_register = si476x_radio_s_register,
+#endif
+};
+
+
+static const struct video_device si476x_viddev_template = {
+ .fops = &si476x_fops,
+ .name = DRIVER_NAME,
+ .release = video_device_release_empty,
+};
+
+
+
+static ssize_t si476x_radio_read_acf_blob(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err;
+ struct si476x_radio *radio = file->private_data;
+ struct si476x_acf_status_report report;
+
+ si476x_core_lock(radio->core);
+ if (radio->ops->acf_status)
+ err = radio->ops->acf_status(radio->core, &report);
+ else
+ err = -ENOENT;
+ si476x_core_unlock(radio->core);
+
+ if (err < 0)
+ return err;
+
+ return simple_read_from_buffer(user_buf, count, ppos, &report,
+ sizeof(report));
+}
+
+static const struct file_operations radio_acf_fops = {
+ .open = simple_open,
+ .llseek = default_llseek,
+ .read = si476x_radio_read_acf_blob,
+};
+
+static ssize_t si476x_radio_read_rds_blckcnt_blob(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err;
+ struct si476x_radio *radio = file->private_data;
+ struct si476x_rds_blockcount_report report;
+
+ si476x_core_lock(radio->core);
+ if (radio->ops->rds_blckcnt)
+ err = radio->ops->rds_blckcnt(radio->core, true,
+ &report);
+ else
+ err = -ENOENT;
+ si476x_core_unlock(radio->core);
+
+ if (err < 0)
+ return err;
+
+ return simple_read_from_buffer(user_buf, count, ppos, &report,
+ sizeof(report));
+}
+
+static const struct file_operations radio_rds_blckcnt_fops = {
+ .open = simple_open,
+ .llseek = default_llseek,
+ .read = si476x_radio_read_rds_blckcnt_blob,
+};
+
+static ssize_t si476x_radio_read_agc_blob(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err;
+ struct si476x_radio *radio = file->private_data;
+ struct si476x_agc_status_report report;
+
+ si476x_core_lock(radio->core);
+ if (radio->ops->rds_blckcnt)
+ err = radio->ops->agc_status(radio->core, &report);
+ else
+ err = -ENOENT;
+ si476x_core_unlock(radio->core);
+
+ if (err < 0)
+ return err;
+
+ return simple_read_from_buffer(user_buf, count, ppos, &report,
+ sizeof(report));
+}
+
+static const struct file_operations radio_agc_fops = {
+ .open = simple_open,
+ .llseek = default_llseek,
+ .read = si476x_radio_read_agc_blob,
+};
+
+static ssize_t si476x_radio_read_rsq_blob(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err;
+ struct si476x_radio *radio = file->private_data;
+ struct si476x_rsq_status_report report;
+ struct si476x_rsq_status_args args = {
+ .primary = false,
+ .rsqack = false,
+ .attune = false,
+ .cancel = false,
+ .stcack = false,
+ };
+
+ si476x_core_lock(radio->core);
+ if (radio->ops->rds_blckcnt)
+ err = radio->ops->rsq_status(radio->core, &args, &report);
+ else
+ err = -ENOENT;
+ si476x_core_unlock(radio->core);
+
+ if (err < 0)
+ return err;
+
+ return simple_read_from_buffer(user_buf, count, ppos, &report,
+ sizeof(report));
+}
+
+static const struct file_operations radio_rsq_fops = {
+ .open = simple_open,
+ .llseek = default_llseek,
+ .read = si476x_radio_read_rsq_blob,
+};
+
+static ssize_t si476x_radio_read_rsq_primary_blob(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ int err;
+ struct si476x_radio *radio = file->private_data;
+ struct si476x_rsq_status_report report;
+ struct si476x_rsq_status_args args = {
+ .primary = true,
+ .rsqack = false,
+ .attune = false,
+ .cancel = false,
+ .stcack = false,
+ };
+
+ si476x_core_lock(radio->core);
+ if (radio->ops->rds_blckcnt)
+ err = radio->ops->rsq_status(radio->core, &args, &report);
+ else
+ err = -ENOENT;
+ si476x_core_unlock(radio->core);
+
+ if (err < 0)
+ return err;
+
+ return simple_read_from_buffer(user_buf, count, ppos, &report,
+ sizeof(report));
+}
+
+static const struct file_operations radio_rsq_primary_fops = {
+ .open = simple_open,
+ .llseek = default_llseek,
+ .read = si476x_radio_read_rsq_primary_blob,
+};
+
+
+static int si476x_radio_init_debugfs(struct si476x_radio *radio)
+{
+ struct dentry *dentry;
+ int ret;
+
+ dentry = debugfs_create_dir(dev_name(radio->v4l2dev.dev), NULL);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto exit;
+ }
+ radio->debugfs = dentry;
+
+ dentry = debugfs_create_file("acf", S_IRUGO,
+ radio->debugfs, radio, &radio_acf_fops);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto cleanup;
+ }
+
+ dentry = debugfs_create_file("rds_blckcnt", S_IRUGO,
+ radio->debugfs, radio,
+ &radio_rds_blckcnt_fops);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto cleanup;
+ }
+
+ dentry = debugfs_create_file("agc", S_IRUGO,
+ radio->debugfs, radio, &radio_agc_fops);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto cleanup;
+ }
+
+ dentry = debugfs_create_file("rsq", S_IRUGO,
+ radio->debugfs, radio, &radio_rsq_fops);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto cleanup;
+ }
+
+ dentry = debugfs_create_file("rsq_primary", S_IRUGO,
+ radio->debugfs, radio,
+ &radio_rsq_primary_fops);
+ if (IS_ERR(dentry)) {
+ ret = PTR_ERR(dentry);
+ goto cleanup;
+ }
+
+ return 0;
+cleanup:
+ debugfs_remove_recursive(radio->debugfs);
+exit:
+ return ret;
+}
+
+
+static int si476x_radio_add_new_custom(struct si476x_radio *radio,
+ enum si476x_ctrl_idx idx)
+{
+ int rval;
+ struct v4l2_ctrl *ctrl;
+
+ ctrl = v4l2_ctrl_new_custom(&radio->ctrl_handler,
+ &si476x_ctrls[idx],
+ NULL);
+ rval = radio->ctrl_handler.error;
+ if (ctrl == NULL && rval)
+ dev_err(radio->v4l2dev.dev,
+ "Could not initialize '%s' control %d\n",
+ si476x_ctrls[idx].name, rval);
+
+ return rval;
+}
+
+static int si476x_radio_probe(struct platform_device *pdev)
+{
+ int rval;
+ struct si476x_radio *radio;
+ struct v4l2_ctrl *ctrl;
+
+ static atomic_t instance = ATOMIC_INIT(0);
+
+ radio = devm_kzalloc(&pdev->dev, sizeof(*radio), GFP_KERNEL);
+ if (!radio)
+ return -ENOMEM;
+
+ radio->core = i2c_mfd_cell_to_core(&pdev->dev);
+
+ v4l2_device_set_name(&radio->v4l2dev, DRIVER_NAME, &instance);
+
+ rval = v4l2_device_register(&pdev->dev, &radio->v4l2dev);
+ if (rval) {
+ dev_err(&pdev->dev, "Cannot register v4l2_device.\n");
+ return rval;
+ }
+
+ memcpy(&radio->videodev, &si476x_viddev_template,
+ sizeof(struct video_device));
+
+ radio->videodev.v4l2_dev = &radio->v4l2dev;
+ radio->videodev.ioctl_ops = &si4761_ioctl_ops;
+
+ video_set_drvdata(&radio->videodev, radio);
+ platform_set_drvdata(pdev, radio);
+
+ set_bit(V4L2_FL_USE_FH_PRIO, &radio->videodev.flags);
+
+ radio->v4l2dev.ctrl_handler = &radio->ctrl_handler;
+ v4l2_ctrl_handler_init(&radio->ctrl_handler,
+ 1 + ARRAY_SIZE(si476x_ctrls));
+
+ if (si476x_core_has_am(radio->core)) {
+ ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler,
+ &si476x_ctrl_ops,
+ V4L2_CID_POWER_LINE_FREQUENCY,
+ V4L2_CID_POWER_LINE_FREQUENCY_60HZ,
+ 0, 0);
+ rval = radio->ctrl_handler.error;
+ if (ctrl == NULL && rval) {
+ dev_err(&pdev->dev, "Could not initialize V4L2_CID_POWER_LINE_FREQUENCY control %d\n",
+ rval);
+ goto exit;
+ }
+
+ rval = si476x_radio_add_new_custom(radio,
+ SI476X_IDX_HARMONICS_COUNT);
+ if (rval < 0)
+ goto exit;
+ }
+
+ rval = si476x_radio_add_new_custom(radio, SI476X_IDX_RSSI_THRESHOLD);
+ if (rval < 0)
+ goto exit;
+
+ rval = si476x_radio_add_new_custom(radio, SI476X_IDX_SNR_THRESHOLD);
+ if (rval < 0)
+ goto exit;
+
+ rval = si476x_radio_add_new_custom(radio, SI476X_IDX_MAX_TUNE_ERROR);
+ if (rval < 0)
+ goto exit;
+
+ ctrl = v4l2_ctrl_new_std_menu(&radio->ctrl_handler,
+ &si476x_ctrl_ops,
+ V4L2_CID_TUNE_DEEMPHASIS,
+ V4L2_DEEMPHASIS_75_uS, 0, 0);
+ rval = radio->ctrl_handler.error;
+ if (ctrl == NULL && rval) {
+ dev_err(&pdev->dev, "Could not initialize V4L2_CID_TUNE_DEEMPHASIS control %d\n",
+ rval);
+ goto exit;
+ }
+
+ ctrl = v4l2_ctrl_new_std(&radio->ctrl_handler, &si476x_ctrl_ops,
+ V4L2_CID_RDS_RECEPTION,
+ 0, 1, 1, 1);
+ rval = radio->ctrl_handler.error;
+ if (ctrl == NULL && rval) {
+ dev_err(&pdev->dev, "Could not initialize V4L2_CID_RDS_RECEPTION control %d\n",
+ rval);
+ goto exit;
+ }
+
+ if (si476x_core_has_diversity(radio->core)) {
+ si476x_ctrls[SI476X_IDX_DIVERSITY_MODE].def =
+ si476x_phase_diversity_mode_to_idx(radio->core->diversity_mode);
+ si476x_radio_add_new_custom(radio, SI476X_IDX_DIVERSITY_MODE);
+ if (rval < 0)
+ goto exit;
+
+ si476x_radio_add_new_custom(radio, SI476X_IDX_INTERCHIP_LINK);
+ if (rval < 0)
+ goto exit;
+ }
+
+ /* register video device */
+ rval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, -1);
+ if (rval < 0) {
+ dev_err(&pdev->dev, "Could not register video device\n");
+ goto exit;
+ }
+
+ rval = si476x_radio_init_debugfs(radio);
+ if (rval < 0) {
+ dev_err(&pdev->dev, "Could not creat debugfs interface\n");
+ goto exit;
+ }
+
+ return 0;
+exit:
+ v4l2_ctrl_handler_free(radio->videodev.ctrl_handler);
+ return rval;
+}
+
+static int si476x_radio_remove(struct platform_device *pdev)
+{
+ struct si476x_radio *radio = platform_get_drvdata(pdev);
+
+ v4l2_ctrl_handler_free(radio->videodev.ctrl_handler);
+ video_unregister_device(&radio->videodev);
+ v4l2_device_unregister(&radio->v4l2dev);
+ debugfs_remove_recursive(radio->debugfs);
+
+ return 0;
+}
+
+MODULE_ALIAS("platform:si476x-radio");
+
+static struct platform_driver si476x_radio_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+ },
+ .probe = si476x_radio_probe,
+ .remove = si476x_radio_remove,
+};
+module_platform_driver(si476x_radio_driver);
+
+MODULE_AUTHOR("Andrey Smirnov <[email protected]>");
+MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell");
+MODULE_LICENSE("GPL");
diff --git a/include/media/si476x.h b/include/media/si476x.h
new file mode 100644
index 0000000..e02e241
--- /dev/null
+++ b/include/media/si476x.h
@@ -0,0 +1,37 @@
+/*
+ * include/media/si476x.h -- Common definitions for si476x driver
+ *
+ * Copyright (C) 2012 Innovative Converged Devices(ICD)
+ * Copyright (C) 2013 Andrey Smirnov
+ *
+ * Author: Andrey Smirnov <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+
+#ifndef SI476X_H
+#define SI476X_H
+
+#include <linux/types.h>
+#include <linux/videodev2.h>
+
+#include <linux/mfd/si476x-reports.h>
+
+enum si476x_ctrl_id {
+ V4L2_CID_SI476X_RSSI_THRESHOLD = (V4L2_CID_USER_SI476X_BASE + 1),
+ V4L2_CID_SI476X_SNR_THRESHOLD = (V4L2_CID_USER_SI476X_BASE + 2),
+ V4L2_CID_SI476X_MAX_TUNE_ERROR = (V4L2_CID_USER_SI476X_BASE + 3),
+ V4L2_CID_SI476X_HARMONICS_COUNT = (V4L2_CID_USER_SI476X_BASE + 4),
+ V4L2_CID_SI476X_DIVERSITY_MODE = (V4L2_CID_USER_SI476X_BASE + 5),
+ V4L2_CID_SI476X_INTERCHIP_LINK = (V4L2_CID_USER_SI476X_BASE + 6),
+};
+
+#endif /* SI476X_H*/
--
1.7.10.4

2013-04-18 16:59:26

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH 10/12] radio-si476x: vidioc_s* now uses a const parameter

From: Mauro Carvalho Chehab <[email protected]>

vidioc_s_tuner, vidioc_s_frequency and vidioc_s_register now
uses a constant argument. So, the driver reports warnings:

drivers/media/radio/radio-si476x.c:1196:2: warning: initialization from incompatible pointer type [enabled by default]
drivers/media/radio/radio-si476x.c:1196:2: warning: (near initialization for 'si4761_ioctl_ops.vidioc_s_tuner') [enabled by default]
drivers/media/radio/radio-si476x.c:1199:2: warning: initialization from incompatible pointer type [enabled by default]
drivers/media/radio/radio-si476x.c:1199:2: warning: (near initialization for 'si4761_ioctl_ops.vidioc_s_frequency') [enabled by default]
drivers/media/radio/radio-si476x.c:1209:2: warning: initialization from incompatible pointer type [enabled by default]
drivers/media/radio/radio-si476x.c:1209:2: warning: (near initialization for 'si4761_ioctl_ops.vidioc_s_register') [enabled by default]

This is due to a (soft) merge conflict, as both this driver and the
const patches were applied for the same Kernel version.

Acked-by: Hans Verkuil <[email protected]>
Acked-by: Andrey Smirnov <[email protected]>
Signed-off-by: Mauro Carvalho Chehab <[email protected]>
---
drivers/media/radio/radio-si476x.c | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c
index 0895a0c..9430c6a 100644
--- a/drivers/media/radio/radio-si476x.c
+++ b/drivers/media/radio/radio-si476x.c
@@ -472,7 +472,7 @@ static int si476x_radio_g_tuner(struct file *file, void *priv,
}

static int si476x_radio_s_tuner(struct file *file, void *priv,
- struct v4l2_tuner *tuner)
+ const struct v4l2_tuner *tuner)
{
struct si476x_radio *radio = video_drvdata(file);

@@ -699,15 +699,16 @@ static int si476x_radio_g_frequency(struct file *file, void *priv,
}

static int si476x_radio_s_frequency(struct file *file, void *priv,
- struct v4l2_frequency *f)
+ const struct v4l2_frequency *f)
{
int err;
+ u32 freq = f->frequency;
struct si476x_tune_freq_args args;
struct si476x_radio *radio = video_drvdata(file);

const u32 midrange = (si476x_bands[SI476X_BAND_AM].rangehigh +
si476x_bands[SI476X_BAND_FM].rangelow) / 2;
- const int band = (f->frequency > midrange) ?
+ const int band = (freq > midrange) ?
SI476X_BAND_FM : SI476X_BAND_AM;
const enum si476x_func func = (band == SI476X_BAND_AM) ?
SI476X_FUNC_AM_RECEIVER : SI476X_FUNC_FM_RECEIVER;
@@ -718,11 +719,11 @@ static int si476x_radio_s_frequency(struct file *file, void *priv,

si476x_core_lock(radio->core);

- f->frequency = clamp(f->frequency,
- si476x_bands[band].rangelow,
- si476x_bands[band].rangehigh);
+ freq = clamp(freq,
+ si476x_bands[band].rangelow,
+ si476x_bands[band].rangehigh);

- if (si476x_radio_freq_is_inside_of_the_band(f->frequency,
+ if (si476x_radio_freq_is_inside_of_the_band(freq,
SI476X_BAND_AM) &&
(!si476x_core_has_am(radio->core) ||
si476x_core_is_a_secondary_tuner(radio->core))) {
@@ -737,8 +738,7 @@ static int si476x_radio_s_frequency(struct file *file, void *priv,
args.zifsr = false;
args.hd = false;
args.injside = SI476X_INJSIDE_AUTO;
- args.freq = v4l2_to_si476x(radio->core,
- f->frequency);
+ args.freq = v4l2_to_si476x(radio->core, freq);
args.tunemode = SI476X_TM_VALIDATED_NORMAL_TUNE;
args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO;
args.antcap = 0;
@@ -1046,7 +1046,7 @@ static int si476x_radio_g_register(struct file *file, void *fh,
return err;
}
static int si476x_radio_s_register(struct file *file, void *fh,
- struct v4l2_dbg_register *reg)
+ const struct v4l2_dbg_register *reg)
{

int err;
--
1.7.10.4

2013-04-18 16:59:52

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH 12/12] radio-si476x: Fix incorrect pointer checking

Fix incorrect pointer checking and make some minor code improvements:

* Remove unnecessary elements from function pointer table(vtable),
that includes all the elements that are FM-only, this allows for not
checking of the fucntion pointer and calling of the function
directly(THe check if the tuner is in FM mode has to be done anyway)

* Fix incorrect function pointer checking where the code would check one
pointer to be non-NULL, but would use other pointer, which would not
be checked.

* Remove code duplication in "si476x_radio_read_rsq_blob" and
"si476x_radio_read_rsq_primary_blob".

* Add some BUG_ON statements for function pointers that should never be NULL

Signed-off-by: Andrey Smirnov <[email protected]>
Signed-off-by: Dan Carpenter <[email protected]>
---
drivers/media/radio/radio-si476x.c | 90 +++++++++++++-----------------------
1 file changed, 33 insertions(+), 57 deletions(-)

diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c
index 9430c6a..378c7f0 100644
--- a/drivers/media/radio/radio-si476x.c
+++ b/drivers/media/radio/radio-si476x.c
@@ -270,8 +270,6 @@ struct si476x_radio;
* @seek_start: Star station seeking
* @rsq_status: Get Recieved Signal Quality(RSQ) status
* @rds_blckcnt: Get recived RDS blocks count
- * @phase_diversity: Change phase diversity mode of the tuner
- * @phase_div_status: Get phase diversity mode status
* @acf_status: Get the status of Automatically Controlled
* Features(ACF)
* @agc_status: Get Automatic Gain Control(AGC) status
@@ -281,16 +279,8 @@ struct si476x_radio_ops {
int (*seek_start)(struct si476x_core *, bool, bool);
int (*rsq_status)(struct si476x_core *, struct si476x_rsq_status_args *,
struct si476x_rsq_status_report *);
- int (*rds_blckcnt)(struct si476x_core *, bool,
- struct si476x_rds_blockcount_report *);
-
- int (*phase_diversity)(struct si476x_core *,
- enum si476x_phase_diversity_mode);
- int (*phase_div_status)(struct si476x_core *);
int (*acf_status)(struct si476x_core *,
struct si476x_acf_status_report *);
- int (*agc_status)(struct si476x_core *,
- struct si476x_agc_status_report *);
};

/**
@@ -495,22 +485,14 @@ static int si476x_radio_init_vtable(struct si476x_radio *radio,
.tune_freq = si476x_core_cmd_fm_tune_freq,
.seek_start = si476x_core_cmd_fm_seek_start,
.rsq_status = si476x_core_cmd_fm_rsq_status,
- .rds_blckcnt = si476x_core_cmd_fm_rds_blockcount,
- .phase_diversity = si476x_core_cmd_fm_phase_diversity,
- .phase_div_status = si476x_core_cmd_fm_phase_div_status,
.acf_status = si476x_core_cmd_fm_acf_status,
- .agc_status = si476x_core_cmd_agc_status,
};

static const struct si476x_radio_ops am_ops = {
.tune_freq = si476x_core_cmd_am_tune_freq,
.seek_start = si476x_core_cmd_am_seek_start,
.rsq_status = si476x_core_cmd_am_rsq_status,
- .rds_blckcnt = NULL,
- .phase_diversity = NULL,
- .phase_div_status = NULL,
.acf_status = si476x_core_cmd_am_acf_status,
- .agc_status = NULL,
};

switch (func) {
@@ -545,11 +527,15 @@ static int si476x_radio_pretune(struct si476x_radio *radio,
case SI476X_FUNC_FM_RECEIVER:
args.freq = v4l2_to_si476x(radio->core,
92 * FREQ_MUL);
+
+ BUG_ON(!radio->ops->tune_freq);
retval = radio->ops->tune_freq(radio->core, &args);
break;
case SI476X_FUNC_AM_RECEIVER:
args.freq = v4l2_to_si476x(radio->core,
0.6 * FREQ_MUL);
+
+ BUG_ON(!radio->ops->tune_freq);
retval = radio->ops->tune_freq(radio->core, &args);
break;
default:
@@ -599,7 +585,7 @@ static int si476x_radio_do_post_powerup_init(struct si476x_radio *radio,
if (err < 0)
return err;

- if (func == SI476X_FUNC_FM_RECEIVER) {
+ if (!si476x_core_is_in_am_receiver_mode(radio->core)) {
if (si476x_core_has_diversity(radio->core)) {
err = si476x_core_cmd_fm_phase_diversity(radio->core,
radio->core->diversity_mode);
@@ -743,6 +729,7 @@ static int si476x_radio_s_frequency(struct file *file, void *priv,
args.smoothmetrics = SI476X_SM_INITIALIZE_AUDIO;
args.antcap = 0;

+ BUG_ON(!radio->ops->tune_freq);
err = radio->ops->tune_freq(radio->core, &args);

unlock:
@@ -833,6 +820,7 @@ static int si476x_radio_s_hw_freq_seek(struct file *file, void *priv,
goto unlock;
}

+ BUG_ON(!radio->ops->seek_start);
err = radio->ops->seek_start(radio->core,
seek->seek_upward,
seek->wrap_around);
@@ -854,8 +842,8 @@ static int si476x_radio_g_volatile_ctrl(struct v4l2_ctrl *ctrl)
switch (ctrl->id) {
case V4L2_CID_SI476X_INTERCHIP_LINK:
if (si476x_core_has_diversity(radio->core)) {
- if (radio->ops->phase_diversity) {
- retval = radio->ops->phase_div_status(radio->core);
+ if (!si476x_core_is_in_am_receiver_mode(radio->core)) {
+ retval = si476x_core_cmd_fm_phase_div_status(radio->core);
if (retval < 0)
break;

@@ -1002,7 +990,7 @@ static int si476x_radio_s_ctrl(struct v4l2_ctrl *ctrl)
radio->core->diversity_mode = mode;
retval = 0;
} else {
- retval = radio->ops->phase_diversity(radio->core, mode);
+ retval = si476x_core_cmd_fm_phase_diversity(radio->core, mode);
if (!retval)
radio->core->diversity_mode = mode;
}
@@ -1256,11 +1244,11 @@ static ssize_t si476x_radio_read_rds_blckcnt_blob(struct file *file,
struct si476x_rds_blockcount_report report;

si476x_core_lock(radio->core);
- if (radio->ops->rds_blckcnt)
- err = radio->ops->rds_blckcnt(radio->core, true,
- &report);
- else
+ if (si476x_core_is_in_am_receiver_mode(radio->core))
err = -ENOENT;
+ else
+ err = si476x_core_cmd_fm_rds_blockcount(radio->core, true,
+ &report);
si476x_core_unlock(radio->core);

if (err < 0)
@@ -1285,10 +1273,10 @@ static ssize_t si476x_radio_read_agc_blob(struct file *file,
struct si476x_agc_status_report report;

si476x_core_lock(radio->core);
- if (radio->ops->rds_blckcnt)
- err = radio->ops->agc_status(radio->core, &report);
- else
+ if (si476x_core_is_in_am_receiver_mode(radio->core))
err = -ENOENT;
+ else
+ err = si476x_core_cmd_agc_status(radio->core, &report);
si476x_core_unlock(radio->core);

if (err < 0)
@@ -1304,15 +1292,17 @@ static const struct file_operations radio_agc_fops = {
.read = si476x_radio_read_agc_blob,
};

-static ssize_t si476x_radio_read_rsq_blob(struct file *file,
- char __user *user_buf,
- size_t count, loff_t *ppos)
+
+static ssize_t __si476x_radio_read_rsq_blob(bool is_primary,
+ struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
{
int err;
struct si476x_radio *radio = file->private_data;
struct si476x_rsq_status_report report;
struct si476x_rsq_status_args args = {
- .primary = false,
+ .primary = is_primary,
.rsqack = false,
.attune = false,
.cancel = false,
@@ -1320,7 +1310,7 @@ static ssize_t si476x_radio_read_rsq_blob(struct file *file,
};

si476x_core_lock(radio->core);
- if (radio->ops->rds_blckcnt)
+ if (radio->ops->rsq_status)
err = radio->ops->rsq_status(radio->core, &args, &report);
else
err = -ENOENT;
@@ -1331,6 +1321,14 @@ static ssize_t si476x_radio_read_rsq_blob(struct file *file,

return simple_read_from_buffer(user_buf, count, ppos, &report,
sizeof(report));
+
+}
+
+static ssize_t si476x_radio_read_rsq_blob(struct file *file,
+ char __user *user_buf,
+ size_t count, loff_t *ppos)
+{
+ return __si476x_radio_read_rsq_blob(false, file, user_buf, count, ppos);
}

static const struct file_operations radio_rsq_fops = {
@@ -1343,29 +1341,7 @@ static ssize_t si476x_radio_read_rsq_primary_blob(struct file *file,
char __user *user_buf,
size_t count, loff_t *ppos)
{
- int err;
- struct si476x_radio *radio = file->private_data;
- struct si476x_rsq_status_report report;
- struct si476x_rsq_status_args args = {
- .primary = true,
- .rsqack = false,
- .attune = false,
- .cancel = false,
- .stcack = false,
- };
-
- si476x_core_lock(radio->core);
- if (radio->ops->rds_blckcnt)
- err = radio->ops->rsq_status(radio->core, &args, &report);
- else
- err = -ENOENT;
- si476x_core_unlock(radio->core);
-
- if (err < 0)
- return err;
-
- return simple_read_from_buffer(user_buf, count, ppos, &report,
- sizeof(report));
+ return __si476x_radio_read_rsq_blob(true, file, user_buf, count, ppos);
}

static const struct file_operations radio_rsq_primary_fops = {
--
1.7.10.4

2013-04-18 17:01:15

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH 11/12] si476x: Fix some config dependencies and a compile warnings

From: Hans Verkuil <[email protected]>

radio-si476x depends on SND and SND_SOC, the mfd driver should select
REGMAP_I2C.

Also fix a small compile warning in a debug message:

drivers/mfd/si476x-i2c.c: In function ‘si476x_core_drain_rds_fifo’:
drivers/mfd/si476x-i2c.c:391:4: warning: field width specifier ‘*’ expects argument of type ‘int’, but argument 4 has type ‘long unsigned int’ [-Wformat]

Acked-by: Andrey Smirnov <[email protected]>
Signed-off-by: Hans Verkuil <[email protected]>
---
drivers/media/radio/Kconfig | 2 +-
drivers/mfd/Kconfig | 1 +
drivers/mfd/si476x-i2c.c | 2 +-
3 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
index 170460d..181a25f 100644
--- a/drivers/media/radio/Kconfig
+++ b/drivers/media/radio/Kconfig
@@ -20,7 +20,7 @@ source "drivers/media/radio/si470x/Kconfig"

config RADIO_SI476X
tristate "Silicon Laboratories Si476x I2C FM Radio"
- depends on I2C && VIDEO_V4L2
+ depends on I2C && VIDEO_V4L2 && SND && SND_SOC
select MFD_CORE
select MFD_SI476X_CORE
select SND_SOC_SI476X
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 3cd8f21..606e549 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -974,6 +974,7 @@ config MFD_SI476X_CORE
tristate "Support for Silicon Laboratories 4761/64/68 AM/FM radio."
depends on I2C
select MFD_CORE
+ select REGMAP_I2C
help
This is the core driver for the SI476x series of AM/FM
radio. This MFD driver connects the radio-si476x V4L2 module
diff --git a/drivers/mfd/si476x-i2c.c b/drivers/mfd/si476x-i2c.c
index 118c6b1..f5bc8e4 100644
--- a/drivers/mfd/si476x-i2c.c
+++ b/drivers/mfd/si476x-i2c.c
@@ -389,7 +389,7 @@ static void si476x_core_drain_rds_fifo(struct work_struct *work)
kfifo_in(&core->rds_fifo, report.rds,
sizeof(report.rds));
dev_dbg(&core->client->dev, "RDS data:\n %*ph\n",
- sizeof(report.rds), report.rds);
+ (int)sizeof(report.rds), report.rds);
}
dev_dbg(&core->client->dev, "Drrrrained!\n");
wake_up_interruptible(&core->rds_read_queue);
--
1.7.10.4

2013-04-18 17:01:54

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH 08/12] v4l2: Add private controls base for SI476X

Add a base to be used for allocation of all the SI476X specific
controls in the corresponding driver.

Acked-by: Hans Verkuil <[email protected]>
Signed-off-by: Andrey Smirnov <[email protected]>
---
include/uapi/linux/v4l2-controls.h | 4 ++++
1 file changed, 4 insertions(+)

diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index 3e985be..22e5170 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -147,6 +147,10 @@ enum v4l2_colorfx {
* of controls. We reserve 16 controls for this driver. */
#define V4L2_CID_USER_MEYE_BASE (V4L2_CID_USER_BASE + 0x1000)

+/* The base for the si476x driver controls. See include/media/si476x.h for the list
+ * of controls. Total of 16 controls is reserved for that driver */
+#define V4L2_CID_USER_SI476X_BASE (V4L2_CID_USER_BASE + 0x1010)
+
/* MPEG-class control IDs */

#define V4L2_CID_MPEG_BASE (V4L2_CTRL_CLASS_MPEG | 0x900)
--
1.7.10.4

2013-04-18 16:59:16

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH 05/12] v4l2: Fix the type of V4L2_CID_TUNE_PREEMPHASIS in the documentation

Change the type of V4L2_CID_TUNE_PREEMPHASIS from 'integer' to 'enum
v4l2_preemphasis'

Acked-by: Hans Verkuil <[email protected]>
Signed-off-by: Andrey Smirnov <[email protected]>
---
Documentation/DocBook/media/v4l/controls.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/Documentation/DocBook/media/v4l/controls.xml b/Documentation/DocBook/media/v4l/controls.xml
index 9e8f854..1ad20cc 100644
--- a/Documentation/DocBook/media/v4l/controls.xml
+++ b/Documentation/DocBook/media/v4l/controls.xml
@@ -3848,7 +3848,7 @@ in Hz. The range and step are driver-specific.</entry>
</row>
<row>
<entry spanname="id"><constant>V4L2_CID_TUNE_PREEMPHASIS</constant>&nbsp;</entry>
- <entry>integer</entry>
+ <entry>enum v4l2_preemphasis</entry>
</row>
<row id="v4l2-preemphasis"><entry spanname="descr">Configures the pre-emphasis value for broadcasting.
A pre-emphasis filter is applied to the broadcast to accentuate the high audio frequencies.
--
1.7.10.4

2013-04-18 16:59:14

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH 02/12] mfd: Add the main bulk of core driver for SI476x code

From: Andrey Smirnov <andreysm@charmander.(none)>

This patch adds main part(out of three) of the I2C driver for the
"core" of MFD device.

Acked-by: Hans Verkuil <[email protected]>
Signed-off-by: Andrey Smirnov <[email protected]>
---
drivers/mfd/si476x-i2c.c | 886 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 886 insertions(+)
create mode 100644 drivers/mfd/si476x-i2c.c

diff --git a/drivers/mfd/si476x-i2c.c b/drivers/mfd/si476x-i2c.c
new file mode 100644
index 0000000..118c6b1
--- /dev/null
+++ b/drivers/mfd/si476x-i2c.c
@@ -0,0 +1,886 @@
+/*
+ * drivers/mfd/si476x-i2c.c -- Core device driver for si476x MFD
+ * device
+ *
+ * Copyright (C) 2012 Innovative Converged Devices(ICD)
+ * Copyright (C) 2013 Andrey Smirnov
+ *
+ * Author: Andrey Smirnov <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+#include <linux/module.h>
+
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/gpio.h>
+#include <linux/regulator/consumer.h>
+#include <linux/i2c.h>
+#include <linux/err.h>
+
+#include <linux/mfd/si476x-core.h>
+
+#define SI476X_MAX_IO_ERRORS 10
+#define SI476X_DRIVER_RDS_FIFO_DEPTH 128
+
+/**
+ * si476x_core_config_pinmux() - pin function configuration function
+ *
+ * @core: Core device structure
+ *
+ * Configure the functions of the pins of the radio chip.
+ *
+ * The function returns zero in case of succes or negative error code
+ * otherwise.
+ */
+static int si476x_core_config_pinmux(struct si476x_core *core)
+{
+ int err;
+ dev_dbg(&core->client->dev, "Configuring pinmux\n");
+ err = si476x_core_cmd_dig_audio_pin_cfg(core,
+ core->pinmux.dclk,
+ core->pinmux.dfs,
+ core->pinmux.dout,
+ core->pinmux.xout);
+ if (err < 0) {
+ dev_err(&core->client->dev,
+ "Failed to configure digital audio pins(err = %d)\n",
+ err);
+ return err;
+ }
+
+ err = si476x_core_cmd_zif_pin_cfg(core,
+ core->pinmux.iqclk,
+ core->pinmux.iqfs,
+ core->pinmux.iout,
+ core->pinmux.qout);
+ if (err < 0) {
+ dev_err(&core->client->dev,
+ "Failed to configure ZIF pins(err = %d)\n",
+ err);
+ return err;
+ }
+
+ err = si476x_core_cmd_ic_link_gpo_ctl_pin_cfg(core,
+ core->pinmux.icin,
+ core->pinmux.icip,
+ core->pinmux.icon,
+ core->pinmux.icop);
+ if (err < 0) {
+ dev_err(&core->client->dev,
+ "Failed to configure IC-Link/GPO pins(err = %d)\n",
+ err);
+ return err;
+ }
+
+ err = si476x_core_cmd_ana_audio_pin_cfg(core,
+ core->pinmux.lrout);
+ if (err < 0) {
+ dev_err(&core->client->dev,
+ "Failed to configure analog audio pins(err = %d)\n",
+ err);
+ return err;
+ }
+
+ err = si476x_core_cmd_intb_pin_cfg(core,
+ core->pinmux.intb,
+ core->pinmux.a1);
+ if (err < 0) {
+ dev_err(&core->client->dev,
+ "Failed to configure interrupt pins(err = %d)\n",
+ err);
+ return err;
+ }
+
+ return 0;
+}
+
+static inline void si476x_core_schedule_polling_work(struct si476x_core *core)
+{
+ schedule_delayed_work(&core->status_monitor,
+ usecs_to_jiffies(SI476X_STATUS_POLL_US));
+}
+
+/**
+ * si476x_core_start() - early chip startup function
+ * @core: Core device structure
+ * @soft: When set, this flag forces "soft" startup, where "soft"
+ * power down is the one done by sending appropriate command instead
+ * of using reset pin of the tuner
+ *
+ * Perform required startup sequence to correctly power
+ * up the chip and perform initial configuration. It does the
+ * following sequence of actions:
+ * 1. Claims and enables the power supplies VD and VIO1 required
+ * for I2C interface of the chip operation.
+ * 2. Waits for 100us, pulls the reset line up, enables irq,
+ * waits for another 100us as it is specified by the
+ * datasheet.
+ * 3. Sends 'POWER_UP' command to the device with all provided
+ * information about power-up parameters.
+ * 4. Configures, pin multiplexor, disables digital audio and
+ * configures interrupt sources.
+ *
+ * The function returns zero in case of succes or negative error code
+ * otherwise.
+ */
+int si476x_core_start(struct si476x_core *core, bool soft)
+{
+ struct i2c_client *client = core->client;
+ int err;
+
+ if (!soft) {
+ if (gpio_is_valid(core->gpio_reset))
+ gpio_set_value_cansleep(core->gpio_reset, 1);
+
+ if (client->irq)
+ enable_irq(client->irq);
+
+ udelay(100);
+
+ if (!client->irq) {
+ atomic_set(&core->is_alive, 1);
+ si476x_core_schedule_polling_work(core);
+ }
+ } else {
+ if (client->irq)
+ enable_irq(client->irq);
+ else {
+ atomic_set(&core->is_alive, 1);
+ si476x_core_schedule_polling_work(core);
+ }
+ }
+
+ err = si476x_core_cmd_power_up(core,
+ &core->power_up_parameters);
+
+ if (err < 0) {
+ dev_err(&core->client->dev,
+ "Power up failure(err = %d)\n",
+ err);
+ goto disable_irq;
+ }
+
+ if (client->irq)
+ atomic_set(&core->is_alive, 1);
+
+ err = si476x_core_config_pinmux(core);
+ if (err < 0) {
+ dev_err(&core->client->dev,
+ "Failed to configure pinmux(err = %d)\n",
+ err);
+ goto disable_irq;
+ }
+
+ if (client->irq) {
+ err = regmap_write(core->regmap,
+ SI476X_PROP_INT_CTL_ENABLE,
+ SI476X_RDSIEN |
+ SI476X_STCIEN |
+ SI476X_CTSIEN);
+ if (err < 0) {
+ dev_err(&core->client->dev,
+ "Failed to configure interrupt sources"
+ "(err = %d)\n", err);
+ goto disable_irq;
+ }
+ }
+
+ return 0;
+
+disable_irq:
+ if (err == -ENODEV)
+ atomic_set(&core->is_alive, 0);
+
+ if (client->irq)
+ disable_irq(client->irq);
+ else
+ cancel_delayed_work_sync(&core->status_monitor);
+
+ if (gpio_is_valid(core->gpio_reset))
+ gpio_set_value_cansleep(core->gpio_reset, 0);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(si476x_core_start);
+
+/**
+ * si476x_core_stop() - chip power-down function
+ * @core: Core device structure
+ * @soft: When set, function sends a POWER_DOWN command instead of
+ * bringing reset line low
+ *
+ * Power down the chip by performing following actions:
+ * 1. Disable IRQ or stop the polling worker
+ * 2. Send the POWER_DOWN command if the power down is soft or bring
+ * reset line low if not.
+ *
+ * The function returns zero in case of succes or negative error code
+ * otherwise.
+ */
+int si476x_core_stop(struct si476x_core *core, bool soft)
+{
+ int err = 0;
+ atomic_set(&core->is_alive, 0);
+
+ if (soft) {
+ /* TODO: This probably shoud be a configurable option,
+ * so it is possible to have the chips keep their
+ * oscillators running
+ */
+ struct si476x_power_down_args args = {
+ .xosc = false,
+ };
+ err = si476x_core_cmd_power_down(core, &args);
+ }
+
+ /* We couldn't disable those before
+ * 'si476x_core_cmd_power_down' since we expect to get CTS
+ * interrupt */
+ if (core->client->irq)
+ disable_irq(core->client->irq);
+ else
+ cancel_delayed_work_sync(&core->status_monitor);
+
+ if (!soft) {
+ if (gpio_is_valid(core->gpio_reset))
+ gpio_set_value_cansleep(core->gpio_reset, 0);
+ }
+ return err;
+}
+EXPORT_SYMBOL_GPL(si476x_core_stop);
+
+/**
+ * si476x_core_set_power_state() - set the level at which the power is
+ * supplied for the chip.
+ * @core: Core device structure
+ * @next_state: enum si476x_power_state describing power state to
+ * switch to.
+ *
+ * Switch on all the required power supplies
+ *
+ * This function returns 0 in case of suvccess and negative error code
+ * otherwise.
+ */
+int si476x_core_set_power_state(struct si476x_core *core,
+ enum si476x_power_state next_state)
+{
+ /*
+ It is not clear form the datasheet if it is possible to
+ work with device if not all power domains are operational.
+ So for now the power-up policy is "power-up all the things!"
+ */
+ int err = 0;
+
+ if (core->power_state == SI476X_POWER_INCONSISTENT) {
+ dev_err(&core->client->dev,
+ "The device in inconsistent power state\n");
+ return -EINVAL;
+ }
+
+ if (next_state != core->power_state) {
+ switch (next_state) {
+ case SI476X_POWER_UP_FULL:
+ err = regulator_bulk_enable(ARRAY_SIZE(core->supplies),
+ core->supplies);
+ if (err < 0) {
+ core->power_state = SI476X_POWER_INCONSISTENT;
+ break;
+ }
+ /*
+ * Startup timing diagram recommends to have a
+ * 100 us delay between enabling of the power
+ * supplies and turning the tuner on.
+ */
+ udelay(100);
+
+ err = si476x_core_start(core, false);
+ if (err < 0)
+ goto disable_regulators;
+
+ core->power_state = next_state;
+ break;
+
+ case SI476X_POWER_DOWN:
+ core->power_state = next_state;
+ err = si476x_core_stop(core, false);
+ if (err < 0)
+ core->power_state = SI476X_POWER_INCONSISTENT;
+disable_regulators:
+ err = regulator_bulk_disable(ARRAY_SIZE(core->supplies),
+ core->supplies);
+ if (err < 0)
+ core->power_state = SI476X_POWER_INCONSISTENT;
+ break;
+ default:
+ BUG();
+ }
+ }
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(si476x_core_set_power_state);
+
+/**
+ * si476x_core_report_drainer_stop() - mark the completion of the RDS
+ * buffer drain porcess by the worker.
+ *
+ * @core: Core device structure
+ */
+static inline void si476x_core_report_drainer_stop(struct si476x_core *core)
+{
+ mutex_lock(&core->rds_drainer_status_lock);
+ core->rds_drainer_is_working = false;
+ mutex_unlock(&core->rds_drainer_status_lock);
+}
+
+/**
+ * si476x_core_start_rds_drainer_once() - start RDS drainer worker if
+ * ther is none working, do nothing otherwise
+ *
+ * @core: Datastructure corresponding to the chip.
+ */
+static inline void si476x_core_start_rds_drainer_once(struct si476x_core *core)
+{
+ mutex_lock(&core->rds_drainer_status_lock);
+ if (!core->rds_drainer_is_working) {
+ core->rds_drainer_is_working = true;
+ schedule_work(&core->rds_fifo_drainer);
+ }
+ mutex_unlock(&core->rds_drainer_status_lock);
+}
+/**
+ * si476x_drain_rds_fifo() - RDS buffer drainer.
+ * @work: struct work_struct being ppassed to the function by the
+ * kernel.
+ *
+ * Drain the contents of the RDS FIFO of
+ */
+static void si476x_core_drain_rds_fifo(struct work_struct *work)
+{
+ int err;
+
+ struct si476x_core *core = container_of(work, struct si476x_core,
+ rds_fifo_drainer);
+
+ struct si476x_rds_status_report report;
+
+ si476x_core_lock(core);
+ err = si476x_core_cmd_fm_rds_status(core, true, false, false, &report);
+ if (!err) {
+ int i = report.rdsfifoused;
+ dev_dbg(&core->client->dev,
+ "%d elements in RDS FIFO. Draining.\n", i);
+ for (; i > 0; --i) {
+ err = si476x_core_cmd_fm_rds_status(core, false, false,
+ (i == 1), &report);
+ if (err < 0)
+ goto unlock;
+
+ kfifo_in(&core->rds_fifo, report.rds,
+ sizeof(report.rds));
+ dev_dbg(&core->client->dev, "RDS data:\n %*ph\n",
+ sizeof(report.rds), report.rds);
+ }
+ dev_dbg(&core->client->dev, "Drrrrained!\n");
+ wake_up_interruptible(&core->rds_read_queue);
+ }
+
+unlock:
+ si476x_core_unlock(core);
+ si476x_core_report_drainer_stop(core);
+}
+
+/**
+ * si476x_core_pronounce_dead()
+ *
+ * @core: Core device structure
+ *
+ * Mark the device as being dead and wake up all potentially waiting
+ * threads of execution.
+ *
+ */
+static void si476x_core_pronounce_dead(struct si476x_core *core)
+{
+ dev_info(&core->client->dev, "Core device is dead.\n");
+
+ atomic_set(&core->is_alive, 0);
+
+ /* Wake up al possible waiting processes */
+ wake_up_interruptible(&core->rds_read_queue);
+
+ atomic_set(&core->cts, 1);
+ wake_up(&core->command);
+
+ atomic_set(&core->stc, 1);
+ wake_up(&core->tuning);
+}
+
+/**
+ * si476x_core_i2c_xfer()
+ *
+ * @core: Core device structure
+ * @type: Transfer type
+ * @buf: Transfer buffer for/with data
+ * @count: Transfer buffer size
+ *
+ * Perfrom and I2C transfer(either read or write) and keep a counter
+ * of I/O errors. If the error counter rises above the threshold
+ * pronounce device dead.
+ *
+ * The function returns zero on succes or negative error code on
+ * failure.
+ */
+int si476x_core_i2c_xfer(struct si476x_core *core,
+ enum si476x_i2c_type type,
+ char *buf, int count)
+{
+ static int io_errors_count;
+ int err;
+ if (type == SI476X_I2C_SEND)
+ err = i2c_master_send(core->client, buf, count);
+ else
+ err = i2c_master_recv(core->client, buf, count);
+
+ if (err < 0) {
+ if (io_errors_count++ > SI476X_MAX_IO_ERRORS)
+ si476x_core_pronounce_dead(core);
+ } else {
+ io_errors_count = 0;
+ }
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(si476x_core_i2c_xfer);
+
+/**
+ * si476x_get_status()
+ * @core: Core device structure
+ *
+ * Get the status byte of the core device by berforming one byte I2C
+ * read.
+ *
+ * The function returns a status value or a negative error code on
+ * error.
+ */
+static int si476x_core_get_status(struct si476x_core *core)
+{
+ u8 response;
+ int err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV,
+ &response, sizeof(response));
+
+ return (err < 0) ? err : response;
+}
+
+/**
+ * si476x_get_and_signal_status() - IRQ dispatcher
+ * @core: Core device structure
+ *
+ * Dispatch the arrived interrupt request based on the value of the
+ * status byte reported by the tuner.
+ *
+ */
+static void si476x_core_get_and_signal_status(struct si476x_core *core)
+{
+ int status = si476x_core_get_status(core);
+ if (status < 0) {
+ dev_err(&core->client->dev, "Failed to get status\n");
+ return;
+ }
+
+ if (status & SI476X_CTS) {
+ /* Unfortunately completions could not be used for
+ * signalling CTS since this flag cannot be cleared
+ * in status byte, and therefore once it becomes true
+ * multiple calls to 'complete' would cause the
+ * commands following the current one to be completed
+ * before they actually are */
+ dev_dbg(&core->client->dev, "[interrupt] CTSINT\n");
+ atomic_set(&core->cts, 1);
+ wake_up(&core->command);
+ }
+
+ if (status & SI476X_FM_RDS_INT) {
+ dev_dbg(&core->client->dev, "[interrupt] RDSINT\n");
+ si476x_core_start_rds_drainer_once(core);
+ }
+
+ if (status & SI476X_STC_INT) {
+ dev_dbg(&core->client->dev, "[interrupt] STCINT\n");
+ atomic_set(&core->stc, 1);
+ wake_up(&core->tuning);
+ }
+}
+
+static void si476x_core_poll_loop(struct work_struct *work)
+{
+ struct si476x_core *core = SI476X_WORK_TO_CORE(work);
+
+ si476x_core_get_and_signal_status(core);
+
+ if (atomic_read(&core->is_alive))
+ si476x_core_schedule_polling_work(core);
+}
+
+static irqreturn_t si476x_core_interrupt(int irq, void *dev)
+{
+ struct si476x_core *core = dev;
+
+ si476x_core_get_and_signal_status(core);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * si476x_firmware_version_to_revision()
+ * @core: Core device structure
+ * @major: Firmware major number
+ * @minor1: Firmware first minor number
+ * @minor2: Firmware second minor number
+ *
+ * Convert a chip's firmware version number into an offset that later
+ * will be used to as offset in "vtable" of tuner functions
+ *
+ * This function returns a positive offset in case of success and a -1
+ * in case of failure.
+ */
+static int si476x_core_fwver_to_revision(struct si476x_core *core,
+ int func, int major,
+ int minor1, int minor2)
+{
+ switch (func) {
+ case SI476X_FUNC_FM_RECEIVER:
+ switch (major) {
+ case 5:
+ return SI476X_REVISION_A10;
+ case 8:
+ return SI476X_REVISION_A20;
+ case 10:
+ return SI476X_REVISION_A30;
+ default:
+ goto unknown_revision;
+ }
+ case SI476X_FUNC_AM_RECEIVER:
+ switch (major) {
+ case 5:
+ return SI476X_REVISION_A10;
+ case 7:
+ return SI476X_REVISION_A20;
+ case 9:
+ return SI476X_REVISION_A30;
+ default:
+ goto unknown_revision;
+ }
+ case SI476X_FUNC_WB_RECEIVER:
+ switch (major) {
+ case 3:
+ return SI476X_REVISION_A10;
+ case 5:
+ return SI476X_REVISION_A20;
+ case 7:
+ return SI476X_REVISION_A30;
+ default:
+ goto unknown_revision;
+ }
+ case SI476X_FUNC_BOOTLOADER:
+ default: /* FALLTHROUG */
+ BUG();
+ return -1;
+ }
+
+unknown_revision:
+ dev_err(&core->client->dev,
+ "Unsupported version of the firmware: %d.%d.%d, "
+ "reverting to A10 comptible functions\n",
+ major, minor1, minor2);
+
+ return SI476X_REVISION_A10;
+}
+
+/**
+ * si476x_get_revision_info()
+ * @core: Core device structure
+ *
+ * Get the firmware version number of the device. It is done in
+ * following three steps:
+ * 1. Power-up the device
+ * 2. Send the 'FUNC_INFO' command
+ * 3. Powering the device down.
+ *
+ * The function return zero on success and a negative error code on
+ * failure.
+ */
+static int si476x_core_get_revision_info(struct si476x_core *core)
+{
+ int rval;
+ struct si476x_func_info info;
+
+ si476x_core_lock(core);
+ rval = si476x_core_set_power_state(core, SI476X_POWER_UP_FULL);
+ if (rval < 0)
+ goto exit;
+
+ rval = si476x_core_cmd_func_info(core, &info);
+ if (rval < 0)
+ goto power_down;
+
+ core->revision = si476x_core_fwver_to_revision(core, info.func,
+ info.firmware.major,
+ info.firmware.minor[0],
+ info.firmware.minor[1]);
+power_down:
+ si476x_core_set_power_state(core, SI476X_POWER_DOWN);
+exit:
+ si476x_core_unlock(core);
+
+ return rval;
+}
+
+bool si476x_core_has_am(struct si476x_core *core)
+{
+ return core->chip_id == SI476X_CHIP_SI4761 ||
+ core->chip_id == SI476X_CHIP_SI4764;
+}
+EXPORT_SYMBOL_GPL(si476x_core_has_am);
+
+bool si476x_core_has_diversity(struct si476x_core *core)
+{
+ return core->chip_id == SI476X_CHIP_SI4764;
+}
+EXPORT_SYMBOL_GPL(si476x_core_has_diversity);
+
+bool si476x_core_is_a_secondary_tuner(struct si476x_core *core)
+{
+ return si476x_core_has_diversity(core) &&
+ (core->diversity_mode == SI476X_PHDIV_SECONDARY_ANTENNA ||
+ core->diversity_mode == SI476X_PHDIV_SECONDARY_COMBINING);
+}
+EXPORT_SYMBOL_GPL(si476x_core_is_a_secondary_tuner);
+
+bool si476x_core_is_a_primary_tuner(struct si476x_core *core)
+{
+ return si476x_core_has_diversity(core) &&
+ (core->diversity_mode == SI476X_PHDIV_PRIMARY_ANTENNA ||
+ core->diversity_mode == SI476X_PHDIV_PRIMARY_COMBINING);
+}
+EXPORT_SYMBOL_GPL(si476x_core_is_a_primary_tuner);
+
+bool si476x_core_is_in_am_receiver_mode(struct si476x_core *core)
+{
+ return si476x_core_has_am(core) &&
+ (core->power_up_parameters.func == SI476X_FUNC_AM_RECEIVER);
+}
+EXPORT_SYMBOL_GPL(si476x_core_is_in_am_receiver_mode);
+
+bool si476x_core_is_powered_up(struct si476x_core *core)
+{
+ return core->power_state == SI476X_POWER_UP_FULL;
+}
+EXPORT_SYMBOL_GPL(si476x_core_is_powered_up);
+
+static int si476x_core_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int rval;
+ struct si476x_core *core;
+ struct si476x_platform_data *pdata;
+ struct mfd_cell *cell;
+ int cell_num;
+
+ core = devm_kzalloc(&client->dev, sizeof(*core), GFP_KERNEL);
+ if (!core) {
+ dev_err(&client->dev,
+ "failed to allocate 'struct si476x_core'\n");
+ return -ENOMEM;
+ }
+ core->client = client;
+
+ core->regmap = devm_regmap_init_si476x(core);
+ if (IS_ERR(core->regmap)) {
+ rval = PTR_ERR(core->regmap);
+ dev_err(&client->dev,
+ "Failed to allocate register map: %d\n",
+ rval);
+ return rval;
+ }
+
+ i2c_set_clientdata(client, core);
+
+ atomic_set(&core->is_alive, 0);
+ core->power_state = SI476X_POWER_DOWN;
+
+ pdata = client->dev.platform_data;
+ if (pdata) {
+ memcpy(&core->power_up_parameters,
+ &pdata->power_up_parameters,
+ sizeof(core->power_up_parameters));
+
+ core->gpio_reset = -1;
+ if (gpio_is_valid(pdata->gpio_reset)) {
+ rval = gpio_request(pdata->gpio_reset, "si476x reset");
+ if (rval) {
+ dev_err(&client->dev,
+ "Failed to request gpio: %d\n", rval);
+ return rval;
+ }
+ core->gpio_reset = pdata->gpio_reset;
+ gpio_direction_output(core->gpio_reset, 0);
+ }
+
+ core->diversity_mode = pdata->diversity_mode;
+ memcpy(&core->pinmux, &pdata->pinmux,
+ sizeof(struct si476x_pinmux));
+ } else {
+ dev_err(&client->dev, "No platform data provided\n");
+ return -EINVAL;
+ }
+
+ core->supplies[0].supply = "vd";
+ core->supplies[1].supply = "va";
+ core->supplies[2].supply = "vio1";
+ core->supplies[3].supply = "vio2";
+
+ rval = devm_regulator_bulk_get(&client->dev,
+ ARRAY_SIZE(core->supplies),
+ core->supplies);
+ if (rval) {
+ dev_err(&client->dev, "Failet to gett all of the regulators\n");
+ goto free_gpio;
+ }
+
+ mutex_init(&core->cmd_lock);
+ init_waitqueue_head(&core->command);
+ init_waitqueue_head(&core->tuning);
+
+ rval = kfifo_alloc(&core->rds_fifo,
+ SI476X_DRIVER_RDS_FIFO_DEPTH *
+ sizeof(struct v4l2_rds_data),
+ GFP_KERNEL);
+ if (rval) {
+ dev_err(&client->dev, "Could not alloate the FIFO\n");
+ goto free_gpio;
+ }
+ mutex_init(&core->rds_drainer_status_lock);
+ init_waitqueue_head(&core->rds_read_queue);
+ INIT_WORK(&core->rds_fifo_drainer, si476x_core_drain_rds_fifo);
+
+ if (client->irq) {
+ rval = devm_request_threaded_irq(&client->dev,
+ client->irq, NULL,
+ si476x_core_interrupt,
+ IRQF_TRIGGER_FALLING,
+ client->name, core);
+ if (rval < 0) {
+ dev_err(&client->dev, "Could not request IRQ %d\n",
+ client->irq);
+ goto free_kfifo;
+ }
+ disable_irq(client->irq);
+ dev_dbg(&client->dev, "IRQ requested.\n");
+
+ core->rds_fifo_depth = 20;
+ } else {
+ INIT_DELAYED_WORK(&core->status_monitor,
+ si476x_core_poll_loop);
+ dev_info(&client->dev,
+ "No IRQ number specified, will use polling\n");
+
+ core->rds_fifo_depth = 5;
+ }
+
+ core->chip_id = id->driver_data;
+
+ rval = si476x_core_get_revision_info(core);
+ if (rval < 0) {
+ rval = -ENODEV;
+ goto free_kfifo;
+ }
+
+ cell_num = 0;
+
+ cell = &core->cells[SI476X_RADIO_CELL];
+ cell->name = "si476x-radio";
+ cell_num++;
+
+#ifdef CONFIG_SND_SOC_SI476X
+ if ((core->chip_id == SI476X_CHIP_SI4761 ||
+ core->chip_id == SI476X_CHIP_SI4764) &&
+ core->pinmux.dclk == SI476X_DCLK_DAUDIO &&
+ core->pinmux.dfs == SI476X_DFS_DAUDIO &&
+ core->pinmux.dout == SI476X_DOUT_I2S_OUTPUT &&
+ core->pinmux.xout == SI476X_XOUT_TRISTATE) {
+ cell = &core->cells[SI476X_CODEC_CELL];
+ cell->name = "si476x-codec";
+ cell_num++;
+ }
+#endif
+ rval = mfd_add_devices(&client->dev,
+ (client->adapter->nr << 8) + client->addr,
+ core->cells, cell_num,
+ NULL, 0, NULL);
+ if (!rval)
+ return 0;
+
+free_kfifo:
+ kfifo_free(&core->rds_fifo);
+
+free_gpio:
+ if (gpio_is_valid(core->gpio_reset))
+ gpio_free(core->gpio_reset);
+
+ return rval;
+}
+
+static int si476x_core_remove(struct i2c_client *client)
+{
+ struct si476x_core *core = i2c_get_clientdata(client);
+
+ si476x_core_pronounce_dead(core);
+ mfd_remove_devices(&client->dev);
+
+ if (client->irq)
+ disable_irq(client->irq);
+ else
+ cancel_delayed_work_sync(&core->status_monitor);
+
+ kfifo_free(&core->rds_fifo);
+
+ if (gpio_is_valid(core->gpio_reset))
+ gpio_free(core->gpio_reset);
+
+ return 0;
+}
+
+
+static const struct i2c_device_id si476x_id[] = {
+ { "si4761", SI476X_CHIP_SI4761 },
+ { "si4764", SI476X_CHIP_SI4764 },
+ { "si4768", SI476X_CHIP_SI4768 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, si476x_id);
+
+static struct i2c_driver si476x_core_driver = {
+ .driver = {
+ .name = "si476x-core",
+ .owner = THIS_MODULE,
+ },
+ .probe = si476x_core_probe,
+ .remove = si476x_core_remove,
+ .id_table = si476x_id,
+};
+module_i2c_driver(si476x_core_driver);
+
+
+MODULE_AUTHOR("Andrey Smirnov <[email protected]>");
+MODULE_DESCRIPTION("Si4761/64/68 AM/FM MFD core device driver");
+MODULE_LICENSE("GPL");
--
1.7.10.4

2013-04-18 17:02:48

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH 06/12] v4l2: Add standard controls for FM receivers

This commit introduces new class of standard controls
V4L2_CTRL_CLASS_FM_RX. This class is intended to all controls
pertaining to FM receiver chips. Also, two controls belonging to said
class are added as a part of this commit: V4L2_CID_TUNE_DEEMPHASIS and
V4L2_CID_RDS_RECEPTION.

This patch is based on the code found in the patch by Manjunatha Halli [1]

[1] http://lists-archives.com/linux-kernel/27641307-new-control-class-and-features-for-fm-rx.html

Acked-by: Hans Verkuil <[email protected]>
Signed-off-by: Andrey Smirnov <[email protected]>
---
drivers/media/v4l2-core/v4l2-ctrls.c | 14 +++++++++++---
include/uapi/linux/v4l2-controls.h | 13 +++++++++++++
2 files changed, 24 insertions(+), 3 deletions(-)

diff --git a/drivers/media/v4l2-core/v4l2-ctrls.c b/drivers/media/v4l2-core/v4l2-ctrls.c
index 6b28b58..8b89fb8 100644
--- a/drivers/media/v4l2-core/v4l2-ctrls.c
+++ b/drivers/media/v4l2-core/v4l2-ctrls.c
@@ -297,8 +297,8 @@ const char * const *v4l2_ctrl_get_menu(u32 id)
"Text",
NULL
};
- static const char * const tune_preemphasis[] = {
- "No Preemphasis",
+ static const char * const tune_emphasis[] = {
+ "None",
"50 Microseconds",
"75 Microseconds",
NULL,
@@ -508,7 +508,9 @@ const char * const *v4l2_ctrl_get_menu(u32 id)
case V4L2_CID_SCENE_MODE:
return scene_mode;
case V4L2_CID_TUNE_PREEMPHASIS:
- return tune_preemphasis;
+ return tune_emphasis;
+ case V4L2_CID_TUNE_DEEMPHASIS:
+ return tune_emphasis;
case V4L2_CID_FLASH_LED_MODE:
return flash_led_mode;
case V4L2_CID_FLASH_STROBE_SOURCE:
@@ -799,6 +801,9 @@ const char *v4l2_ctrl_get_name(u32 id)
case V4L2_CID_DV_RX_POWER_PRESENT: return "Power Present";
case V4L2_CID_DV_RX_RGB_RANGE: return "Rx RGB Quantization Range";

+ case V4L2_CID_FM_RX_CLASS: return "FM Radio Receiver Controls";
+ case V4L2_CID_TUNE_DEEMPHASIS: return "De-Emphasis";
+ case V4L2_CID_RDS_RECEPTION: return "RDS Reception";
default:
return NULL;
}
@@ -846,6 +851,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
case V4L2_CID_MPEG_VIDEO_MPEG4_QPEL:
case V4L2_CID_WIDE_DYNAMIC_RANGE:
case V4L2_CID_IMAGE_STABILIZATION:
+ case V4L2_CID_RDS_RECEPTION:
*type = V4L2_CTRL_TYPE_BOOLEAN;
*min = 0;
*max = *step = 1;
@@ -904,6 +910,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
case V4L2_CID_DV_TX_RGB_RANGE:
case V4L2_CID_DV_RX_RGB_RANGE:
case V4L2_CID_TEST_PATTERN:
+ case V4L2_CID_TUNE_DEEMPHASIS:
*type = V4L2_CTRL_TYPE_MENU;
break;
case V4L2_CID_LINK_FREQ:
@@ -926,6 +933,7 @@ void v4l2_ctrl_fill(u32 id, const char **name, enum v4l2_ctrl_type *type,
case V4L2_CID_IMAGE_SOURCE_CLASS:
case V4L2_CID_IMAGE_PROC_CLASS:
case V4L2_CID_DV_CLASS:
+ case V4L2_CID_FM_RX_CLASS:
*type = V4L2_CTRL_TYPE_CTRL_CLASS;
/* You can neither read not write these */
*flags |= V4L2_CTRL_FLAG_READ_ONLY | V4L2_CTRL_FLAG_WRITE_ONLY;
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index dcd6374..3e985be 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -59,6 +59,7 @@
#define V4L2_CTRL_CLASS_IMAGE_SOURCE 0x009e0000 /* Image source controls */
#define V4L2_CTRL_CLASS_IMAGE_PROC 0x009f0000 /* Image processing controls */
#define V4L2_CTRL_CLASS_DV 0x00a00000 /* Digital Video controls */
+#define V4L2_CTRL_CLASS_FM_RX 0x00a10000 /* Digital Video controls */

/* User-class control IDs */

@@ -825,4 +826,16 @@ enum v4l2_dv_rgb_range {
#define V4L2_CID_DV_RX_POWER_PRESENT (V4L2_CID_DV_CLASS_BASE + 100)
#define V4L2_CID_DV_RX_RGB_RANGE (V4L2_CID_DV_CLASS_BASE + 101)

+#define V4L2_CID_FM_RX_CLASS_BASE (V4L2_CTRL_CLASS_FM_RX | 0x900)
+#define V4L2_CID_FM_RX_CLASS (V4L2_CTRL_CLASS_FM_RX | 1)
+
+#define V4L2_CID_TUNE_DEEMPHASIS (V4L2_CID_FM_RX_CLASS_BASE + 1)
+enum v4l2_deemphasis {
+ V4L2_DEEMPHASIS_DISABLED = V4L2_PREEMPHASIS_DISABLED,
+ V4L2_DEEMPHASIS_50_uS = V4L2_PREEMPHASIS_50_uS,
+ V4L2_DEEMPHASIS_75_uS = V4L2_PREEMPHASIS_75_uS,
+};
+
+#define V4L2_CID_RDS_RECEPTION (V4L2_CID_FM_RX_CLASS_BASE + 2)
+
#endif
--
1.7.10.4

2013-04-18 16:59:11

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH 03/12] mfd: Add chip properties handling code for SI476X MFD

From: Andrey Smirnov <andreysm@charmander.(none)>

This patch adds code related to manipulation of the properties of
SI476X chips.

Acked-by: Hans Verkuil <[email protected]>
Signed-off-by: Andrey Smirnov <[email protected]>
---
drivers/mfd/si476x-prop.c | 241 +++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 241 insertions(+)
create mode 100644 drivers/mfd/si476x-prop.c

diff --git a/drivers/mfd/si476x-prop.c b/drivers/mfd/si476x-prop.c
new file mode 100644
index 0000000..cfeffa6
--- /dev/null
+++ b/drivers/mfd/si476x-prop.c
@@ -0,0 +1,241 @@
+/*
+ * drivers/mfd/si476x-prop.c -- Subroutines to access
+ * properties of si476x chips
+ *
+ * Copyright (C) 2012 Innovative Converged Devices(ICD)
+ * Copyright (C) 2013 Andrey Smirnov
+ *
+ * Author: Andrey Smirnov <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ */
+#include <linux/module.h>
+
+#include <linux/mfd/si476x-core.h>
+
+struct si476x_property_range {
+ u16 low, high;
+};
+
+static bool si476x_core_element_is_in_array(u16 element,
+ const u16 array[],
+ size_t size)
+{
+ int i;
+
+ for (i = 0; i < size; i++)
+ if (element == array[i])
+ return true;
+
+ return false;
+}
+
+static bool si476x_core_element_is_in_range(u16 element,
+ const struct si476x_property_range range[],
+ size_t size)
+{
+ int i;
+
+ for (i = 0; i < size; i++)
+ if (element <= range[i].high && element >= range[i].low)
+ return true;
+
+ return false;
+}
+
+static bool si476x_core_is_valid_property_a10(struct si476x_core *core,
+ u16 property)
+{
+ static const u16 valid_properties[] = {
+ 0x0000,
+ 0x0500, 0x0501,
+ 0x0600,
+ 0x0709, 0x070C, 0x070D, 0x70E, 0x710,
+ 0x0718,
+ 0x1207, 0x1208,
+ 0x2007,
+ 0x2300,
+ };
+
+ static const struct si476x_property_range valid_ranges[] = {
+ { 0x0200, 0x0203 },
+ { 0x0300, 0x0303 },
+ { 0x0400, 0x0404 },
+ { 0x0700, 0x0707 },
+ { 0x1100, 0x1102 },
+ { 0x1200, 0x1204 },
+ { 0x1300, 0x1306 },
+ { 0x2000, 0x2005 },
+ { 0x2100, 0x2104 },
+ { 0x2106, 0x2106 },
+ { 0x2200, 0x220E },
+ { 0x3100, 0x3104 },
+ { 0x3207, 0x320F },
+ { 0x3300, 0x3304 },
+ { 0x3500, 0x3517 },
+ { 0x3600, 0x3617 },
+ { 0x3700, 0x3717 },
+ { 0x4000, 0x4003 },
+ };
+
+ return si476x_core_element_is_in_range(property, valid_ranges,
+ ARRAY_SIZE(valid_ranges)) ||
+ si476x_core_element_is_in_array(property, valid_properties,
+ ARRAY_SIZE(valid_properties));
+}
+
+static bool si476x_core_is_valid_property_a20(struct si476x_core *core,
+ u16 property)
+{
+ static const u16 valid_properties[] = {
+ 0x071B,
+ 0x1006,
+ 0x2210,
+ 0x3401,
+ };
+
+ static const struct si476x_property_range valid_ranges[] = {
+ { 0x2215, 0x2219 },
+ };
+
+ return si476x_core_is_valid_property_a10(core, property) ||
+ si476x_core_element_is_in_range(property, valid_ranges,
+ ARRAY_SIZE(valid_ranges)) ||
+ si476x_core_element_is_in_array(property, valid_properties,
+ ARRAY_SIZE(valid_properties));
+}
+
+static bool si476x_core_is_valid_property_a30(struct si476x_core *core,
+ u16 property)
+{
+ static const u16 valid_properties[] = {
+ 0x071C, 0x071D,
+ 0x1007, 0x1008,
+ 0x220F, 0x2214,
+ 0x2301,
+ 0x3105, 0x3106,
+ 0x3402,
+ };
+
+ static const struct si476x_property_range valid_ranges[] = {
+ { 0x0405, 0x0411 },
+ { 0x2008, 0x200B },
+ { 0x2220, 0x2223 },
+ { 0x3100, 0x3106 },
+ };
+
+ return si476x_core_is_valid_property_a20(core, property) ||
+ si476x_core_element_is_in_range(property, valid_ranges,
+ ARRAY_SIZE(valid_ranges)) ||
+ si476x_core_element_is_in_array(property, valid_properties,
+ ARRAY_SIZE(valid_properties));
+}
+
+typedef bool (*valid_property_pred_t) (struct si476x_core *, u16);
+
+static bool si476x_core_is_valid_property(struct si476x_core *core,
+ u16 property)
+{
+ static const valid_property_pred_t is_valid_property[] = {
+ [SI476X_REVISION_A10] = si476x_core_is_valid_property_a10,
+ [SI476X_REVISION_A20] = si476x_core_is_valid_property_a20,
+ [SI476X_REVISION_A30] = si476x_core_is_valid_property_a30,
+ };
+
+ BUG_ON(core->revision > SI476X_REVISION_A30 ||
+ core->revision == -1);
+ return is_valid_property[core->revision](core, property);
+}
+
+
+static bool si476x_core_is_readonly_property(struct si476x_core *core,
+ u16 property)
+{
+ BUG_ON(core->revision > SI476X_REVISION_A30 ||
+ core->revision == -1);
+
+ switch (core->revision) {
+ case SI476X_REVISION_A10:
+ return (property == 0x3200);
+ case SI476X_REVISION_A20:
+ return (property == 0x1006 ||
+ property == 0x2210 ||
+ property == 0x3200);
+ case SI476X_REVISION_A30:
+ return false;
+ }
+
+ return false;
+}
+
+static bool si476x_core_regmap_readable_register(struct device *dev,
+ unsigned int reg)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct si476x_core *core = i2c_get_clientdata(client);
+
+ return si476x_core_is_valid_property(core, (u16) reg);
+
+}
+
+static bool si476x_core_regmap_writable_register(struct device *dev,
+ unsigned int reg)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct si476x_core *core = i2c_get_clientdata(client);
+
+ return si476x_core_is_valid_property(core, (u16) reg) &&
+ !si476x_core_is_readonly_property(core, (u16) reg);
+}
+
+
+static int si476x_core_regmap_write(void *context, unsigned int reg,
+ unsigned int val)
+{
+ return si476x_core_cmd_set_property(context, reg, val);
+}
+
+static int si476x_core_regmap_read(void *context, unsigned int reg,
+ unsigned *val)
+{
+ struct si476x_core *core = context;
+ int err;
+
+ err = si476x_core_cmd_get_property(core, reg);
+ if (err < 0)
+ return err;
+
+ *val = err;
+
+ return 0;
+}
+
+
+static const struct regmap_config si476x_regmap_config = {
+ .reg_bits = 16,
+ .val_bits = 16,
+
+ .max_register = 0x4003,
+
+ .writeable_reg = si476x_core_regmap_writable_register,
+ .readable_reg = si476x_core_regmap_readable_register,
+
+ .reg_read = si476x_core_regmap_read,
+ .reg_write = si476x_core_regmap_write,
+
+ .cache_type = REGCACHE_RBTREE,
+};
+
+struct regmap *devm_regmap_init_si476x(struct si476x_core *core)
+{
+ return devm_regmap_init(&core->client->dev, NULL,
+ core, &si476x_regmap_config);
+}
+EXPORT_SYMBOL_GPL(devm_regmap_init_si476x);
--
1.7.10.4

2013-04-18 17:03:51

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH 04/12] mfd: Add header files and Kbuild plumbing for SI476x MFD core

From: Andrey Smirnov <andreysm@charmander.(none)>

This patch adds all necessary header files and Kbuild plumbing for the
core driver for Silicon Laboratories Si476x series of AM/FM tuner
chips.

The driver as a whole is implemented as an MFD device and this patch
adds a core portion of it that provides all the necessary
functionality to the two other drivers that represent radio and audio
codec subsystems of the chip.

Acked-by: Hans Verkuil <[email protected]>
Acked-by: Sam Ravnborg <[email protected]>
Signed-off-by: Andrey Smirnov <[email protected]>
---
drivers/mfd/Kconfig | 12 +
drivers/mfd/Makefile | 4 +
include/linux/mfd/si476x-core.h | 533 +++++++++++++++++++++++++++++++++++
include/linux/mfd/si476x-platform.h | 267 ++++++++++++++++++
include/linux/mfd/si476x-reports.h | 163 +++++++++++
5 files changed, 979 insertions(+)
create mode 100644 include/linux/mfd/si476x-core.h
create mode 100644 include/linux/mfd/si476x-platform.h
create mode 100644 include/linux/mfd/si476x-reports.h

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index 1c0abd4..3cd8f21 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -970,6 +970,18 @@ config MFD_WL1273_CORE
driver connects the radio-wl1273 V4L2 module and the wl1273
audio codec.

+config MFD_SI476X_CORE
+ tristate "Support for Silicon Laboratories 4761/64/68 AM/FM radio."
+ depends on I2C
+ select MFD_CORE
+ help
+ This is the core driver for the SI476x series of AM/FM
+ radio. This MFD driver connects the radio-si476x V4L2 module
+ and the si476x audio codec.
+
+ To compile this driver as a module, choose M here: the
+ module will be called si476x-core.
+
config MFD_OMAP_USB_HOST
bool "Support OMAP USBHS core and TLL driver"
depends on USB_EHCI_HCD_OMAP || USB_OHCI_HCD_OMAP3
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 8b977f8..ca87ae8 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -131,6 +131,10 @@ obj-$(CONFIG_MFD_JZ4740_ADC) += jz4740-adc.o
obj-$(CONFIG_MFD_TPS6586X) += tps6586x.o
obj-$(CONFIG_MFD_VX855) += vx855.o
obj-$(CONFIG_MFD_WL1273_CORE) += wl1273-core.o
+
+si476x-core-y := si476x-cmd.o si476x-prop.o si476x-i2c.o
+obj-$(CONFIG_MFD_SI476X_CORE) += si476x-core.o
+
obj-$(CONFIG_MFD_CS5535) += cs5535-mfd.o
obj-$(CONFIG_MFD_OMAP_USB_HOST) += omap-usb-host.o omap-usb-tll.o
obj-$(CONFIG_MFD_PM8921_CORE) += pm8921-core.o
diff --git a/include/linux/mfd/si476x-core.h b/include/linux/mfd/si476x-core.h
new file mode 100644
index 0000000..ede3022
--- /dev/null
+++ b/include/linux/mfd/si476x-core.h
@@ -0,0 +1,533 @@
+/*
+ * include/media/si476x-core.h -- Common definitions for si476x core
+ * device
+ *
+ * Copyright (C) 2012 Innovative Converged Devices(ICD)
+ * Copyright (C) 2013 Andrey Smirnov
+ *
+ * Author: Andrey Smirnov <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+
+#ifndef SI476X_CORE_H
+#define SI476X_CORE_H
+
+#include <linux/kfifo.h>
+#include <linux/atomic.h>
+#include <linux/i2c.h>
+#include <linux/regmap.h>
+#include <linux/mutex.h>
+#include <linux/mfd/core.h>
+#include <linux/videodev2.h>
+#include <linux/regulator/consumer.h>
+
+#include <linux/mfd/si476x-platform.h>
+#include <linux/mfd/si476x-reports.h>
+
+/* Command Timeouts */
+#define SI476X_DEFAULT_TIMEOUT 100000
+#define SI476X_TIMEOUT_TUNE 700000
+#define SI476X_TIMEOUT_POWER_UP 330000
+#define SI476X_STATUS_POLL_US 0
+
+/* -------------------- si476x-i2c.c ----------------------- */
+
+enum si476x_freq_supported_chips {
+ SI476X_CHIP_SI4761 = 1,
+ SI476X_CHIP_SI4764,
+ SI476X_CHIP_SI4768,
+};
+
+enum si476x_part_revisions {
+ SI476X_REVISION_A10 = 0,
+ SI476X_REVISION_A20 = 1,
+ SI476X_REVISION_A30 = 2,
+};
+
+enum si476x_mfd_cells {
+ SI476X_RADIO_CELL = 0,
+ SI476X_CODEC_CELL,
+ SI476X_MFD_CELLS,
+};
+
+/**
+ * enum si476x_power_state - possible power state of the si476x
+ * device.
+ *
+ * @SI476X_POWER_DOWN: In this state all regulators are turned off
+ * and the reset line is pulled low. The device is completely
+ * inactive.
+ * @SI476X_POWER_UP_FULL: In this state all the power regualtors are
+ * turned on, reset line pulled high, IRQ line is enabled(polling is
+ * active for polling use scenario) and device is turned on with
+ * POWER_UP command. The device is ready to be used.
+ * @SI476X_POWER_INCONSISTENT: This state indicates that previous
+ * power down was inconsistent, meaning some of the regulators were
+ * not turned down and thus use of the device, without power-cycling
+ * is impossible.
+ */
+enum si476x_power_state {
+ SI476X_POWER_DOWN = 0,
+ SI476X_POWER_UP_FULL = 1,
+ SI476X_POWER_INCONSISTENT = 2,
+};
+
+/**
+ * struct si476x_core - internal data structure representing the
+ * underlying "core" device which all the MFD cell-devices use.
+ *
+ * @client: Actual I2C client used to transfer commands to the chip.
+ * @chip_id: Last digit of the chip model(E.g. "1" for SI4761)
+ * @cells: MFD cell devices created by this driver.
+ * @cmd_lock: Mutex used to serialize all the requests to the core
+ * device. This filed should not be used directly. Instead
+ * si476x_core_lock()/si476x_core_unlock() should be used to get
+ * exclusive access to the "core" device.
+ * @users: Active users counter(Used by the radio cell)
+ * @rds_read_queue: Wait queue used to wait for RDS data.
+ * @rds_fifo: FIFO in which all the RDS data received from the chip is
+ * placed.
+ * @rds_fifo_drainer: Worker that drains on-chip RDS FIFO.
+ * @rds_drainer_is_working: Flag used for launching only one instance
+ * of the @rds_fifo_drainer.
+ * @rds_drainer_status_lock: Lock used to guard access to the
+ * @rds_drainer_is_working variable.
+ * @command: Wait queue for wainting on the command comapletion.
+ * @cts: Clear To Send flag set upon receiving first status with CTS
+ * set.
+ * @tuning: Wait queue used for wainting for tune/seek comand
+ * completion.
+ * @stc: Similar to @cts, but for the STC bit of the status value.
+ * @power_up_parameters: Parameters used as argument for POWER_UP
+ * command when the device is started.
+ * @state: Current power state of the device.
+ * @supplues: Structure containing handles to all power supplies used
+ * by the device (NULL ones are ignored).
+ * @gpio_reset: GPIO pin connectet to the RSTB pin of the chip.
+ * @pinmux: Chip's configurable pins configuration.
+ * @diversity_mode: Chips role when functioning in diversity mode.
+ * @status_monitor: Polling worker used in polling use case scenarion
+ * (when IRQ is not avalible).
+ * @revision: Chip's running firmware revision number(Used for correct
+ * command set support).
+ */
+
+struct si476x_core {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ int chip_id;
+ struct mfd_cell cells[SI476X_MFD_CELLS];
+
+ struct mutex cmd_lock; /* for serializing fm radio operations */
+ atomic_t users;
+
+ wait_queue_head_t rds_read_queue;
+ struct kfifo rds_fifo;
+ struct work_struct rds_fifo_drainer;
+ bool rds_drainer_is_working;
+ struct mutex rds_drainer_status_lock;
+
+ wait_queue_head_t command;
+ atomic_t cts;
+
+ wait_queue_head_t tuning;
+ atomic_t stc;
+
+ struct si476x_power_up_args power_up_parameters;
+
+ enum si476x_power_state power_state;
+
+ struct regulator_bulk_data supplies[4];
+
+ int gpio_reset;
+
+ struct si476x_pinmux pinmux;
+ enum si476x_phase_diversity_mode diversity_mode;
+
+ atomic_t is_alive;
+
+ struct delayed_work status_monitor;
+#define SI476X_WORK_TO_CORE(w) container_of(to_delayed_work(w), \
+ struct si476x_core, \
+ status_monitor)
+
+ int revision;
+
+ int rds_fifo_depth;
+};
+
+static inline struct si476x_core *i2c_mfd_cell_to_core(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev->parent);
+ return i2c_get_clientdata(client);
+}
+
+
+/**
+ * si476x_core_lock() - lock the core device to get an exclusive access
+ * to it.
+ */
+static inline void si476x_core_lock(struct si476x_core *core)
+{
+ mutex_lock(&core->cmd_lock);
+}
+
+/**
+ * si476x_core_unlock() - unlock the core device to relinquish an
+ * exclusive access to it.
+ */
+static inline void si476x_core_unlock(struct si476x_core *core)
+{
+ mutex_unlock(&core->cmd_lock);
+}
+
+/* *_TUNE_FREQ family of commands accept frequency in multiples of
+ 10kHz */
+static inline u16 hz_to_si476x(struct si476x_core *core, int freq)
+{
+ u16 result;
+
+ switch (core->power_up_parameters.func) {
+ default:
+ case SI476X_FUNC_FM_RECEIVER:
+ result = freq / 10000;
+ break;
+ case SI476X_FUNC_AM_RECEIVER:
+ result = freq / 1000;
+ break;
+ }
+
+ return result;
+}
+
+static inline int si476x_to_hz(struct si476x_core *core, u16 freq)
+{
+ int result;
+
+ switch (core->power_up_parameters.func) {
+ default:
+ case SI476X_FUNC_FM_RECEIVER:
+ result = freq * 10000;
+ break;
+ case SI476X_FUNC_AM_RECEIVER:
+ result = freq * 1000;
+ break;
+ }
+
+ return result;
+}
+
+/* Since the V4L2_TUNER_CAP_LOW flag is supplied, V4L2 subsystem
+ * mesures frequency in 62.5 Hz units */
+
+static inline int hz_to_v4l2(int freq)
+{
+ return (freq * 10) / 625;
+}
+
+static inline int v4l2_to_hz(int freq)
+{
+ return (freq * 625) / 10;
+}
+
+static inline u16 v4l2_to_si476x(struct si476x_core *core, int freq)
+{
+ return hz_to_si476x(core, v4l2_to_hz(freq));
+}
+
+static inline int si476x_to_v4l2(struct si476x_core *core, u16 freq)
+{
+ return hz_to_v4l2(si476x_to_hz(core, freq));
+}
+
+
+
+/**
+ * struct si476x_func_info - structure containing result of the
+ * FUNC_INFO command.
+ *
+ * @firmware.major: Firmware major number.
+ * @firmware.minor[...]: Firmware minor numbers.
+ * @patch_id:
+ * @func: Mode tuner is working in.
+ */
+struct si476x_func_info {
+ struct {
+ u8 major, minor[2];
+ } firmware;
+ u16 patch_id;
+ enum si476x_func func;
+};
+
+/**
+ * struct si476x_power_down_args - structure used to pass parameters
+ * to POWER_DOWN command
+ *
+ * @xosc: true - Power down, but leav oscillator running.
+ * false - Full power down.
+ */
+struct si476x_power_down_args {
+ bool xosc;
+};
+
+/**
+ * enum si476x_tunemode - enum representing possible tune modes for
+ * the chip.
+ * @SI476X_TM_VALIDATED_NORMAL_TUNE: Unconditionally stay on the new
+ * channel after tune, tune status is valid.
+ * @SI476X_TM_INVALIDATED_FAST_TUNE: Unconditionally stay in the new
+ * channel after tune, tune status invalid.
+ * @SI476X_TM_VALIDATED_AF_TUNE: Jump back to previous channel if
+ * metric thresholds are not met.
+ * @SI476X_TM_VALIDATED_AF_CHECK: Unconditionally jump back to the
+ * previous channel.
+ */
+enum si476x_tunemode {
+ SI476X_TM_VALIDATED_NORMAL_TUNE = 0,
+ SI476X_TM_INVALIDATED_FAST_TUNE = 1,
+ SI476X_TM_VALIDATED_AF_TUNE = 2,
+ SI476X_TM_VALIDATED_AF_CHECK = 3,
+};
+
+/**
+ * enum si476x_smoothmetrics - enum containing the possible setting fo
+ * audio transitioning of the chip
+ * @SI476X_SM_INITIALIZE_AUDIO: Initialize audio state to match this
+ * new channel
+ * @SI476X_SM_TRANSITION_AUDIO: Transition audio state from previous
+ * channel values to the new values
+ */
+enum si476x_smoothmetrics {
+ SI476X_SM_INITIALIZE_AUDIO = 0,
+ SI476X_SM_TRANSITION_AUDIO = 1,
+};
+
+/**
+ * struct si476x_rds_status_report - the structure representing the
+ * response to 'FM_RD_STATUS' command
+ * @rdstpptyint: Traffic program flag(TP) and/or program type(PTY)
+ * code has changed.
+ * @rdspiint: Program indentifiaction(PI) code has changed.
+ * @rdssyncint: RDS synchronization has changed.
+ * @rdsfifoint: RDS was received and the RDS FIFO has at least
+ * 'FM_RDS_INTERRUPT_FIFO_COUNT' elements in it.
+ * @tpptyvalid: TP flag and PTY code are valid falg.
+ * @pivalid: PI code is valid flag.
+ * @rdssync: RDS is currently synchronized.
+ * @rdsfifolost: On or more RDS groups have been lost/discarded flag.
+ * @tp: Current channel's TP flag.
+ * @pty: Current channel's PTY code.
+ * @pi: Current channel's PI code.
+ * @rdsfifoused: Number of blocks remaining in the RDS FIFO (0 if
+ * empty).
+ */
+struct si476x_rds_status_report {
+ bool rdstpptyint, rdspiint, rdssyncint, rdsfifoint;
+ bool tpptyvalid, pivalid, rdssync, rdsfifolost;
+ bool tp;
+
+ u8 pty;
+ u16 pi;
+
+ u8 rdsfifoused;
+ u8 ble[4];
+
+ struct v4l2_rds_data rds[4];
+};
+
+struct si476x_rsq_status_args {
+ bool primary;
+ bool rsqack;
+ bool attune;
+ bool cancel;
+ bool stcack;
+};
+
+enum si476x_injside {
+ SI476X_INJSIDE_AUTO = 0,
+ SI476X_INJSIDE_LOW = 1,
+ SI476X_INJSIDE_HIGH = 2,
+};
+
+struct si476x_tune_freq_args {
+ bool zifsr;
+ bool hd;
+ enum si476x_injside injside;
+ int freq;
+ enum si476x_tunemode tunemode;
+ enum si476x_smoothmetrics smoothmetrics;
+ int antcap;
+};
+
+int si476x_core_stop(struct si476x_core *, bool);
+int si476x_core_start(struct si476x_core *, bool);
+int si476x_core_set_power_state(struct si476x_core *, enum si476x_power_state);
+bool si476x_core_has_am(struct si476x_core *);
+bool si476x_core_has_diversity(struct si476x_core *);
+bool si476x_core_is_a_secondary_tuner(struct si476x_core *);
+bool si476x_core_is_a_primary_tuner(struct si476x_core *);
+bool si476x_core_is_in_am_receiver_mode(struct si476x_core *core);
+bool si476x_core_is_powered_up(struct si476x_core *core);
+
+enum si476x_i2c_type {
+ SI476X_I2C_SEND,
+ SI476X_I2C_RECV
+};
+
+int si476x_core_i2c_xfer(struct si476x_core *,
+ enum si476x_i2c_type,
+ char *, int);
+
+
+/* -------------------- si476x-cmd.c ----------------------- */
+
+int si476x_core_cmd_func_info(struct si476x_core *, struct si476x_func_info *);
+int si476x_core_cmd_set_property(struct si476x_core *, u16, u16);
+int si476x_core_cmd_get_property(struct si476x_core *, u16);
+int si476x_core_cmd_dig_audio_pin_cfg(struct si476x_core *,
+ enum si476x_dclk_config,
+ enum si476x_dfs_config,
+ enum si476x_dout_config,
+ enum si476x_xout_config);
+int si476x_core_cmd_zif_pin_cfg(struct si476x_core *,
+ enum si476x_iqclk_config,
+ enum si476x_iqfs_config,
+ enum si476x_iout_config,
+ enum si476x_qout_config);
+int si476x_core_cmd_ic_link_gpo_ctl_pin_cfg(struct si476x_core *,
+ enum si476x_icin_config,
+ enum si476x_icip_config,
+ enum si476x_icon_config,
+ enum si476x_icop_config);
+int si476x_core_cmd_ana_audio_pin_cfg(struct si476x_core *,
+ enum si476x_lrout_config);
+int si476x_core_cmd_intb_pin_cfg(struct si476x_core *, enum si476x_intb_config,
+ enum si476x_a1_config);
+int si476x_core_cmd_fm_seek_start(struct si476x_core *, bool, bool);
+int si476x_core_cmd_am_seek_start(struct si476x_core *, bool, bool);
+int si476x_core_cmd_fm_rds_status(struct si476x_core *, bool, bool, bool,
+ struct si476x_rds_status_report *);
+int si476x_core_cmd_fm_rds_blockcount(struct si476x_core *, bool,
+ struct si476x_rds_blockcount_report *);
+int si476x_core_cmd_fm_tune_freq(struct si476x_core *,
+ struct si476x_tune_freq_args *);
+int si476x_core_cmd_am_tune_freq(struct si476x_core *,
+ struct si476x_tune_freq_args *);
+int si476x_core_cmd_am_rsq_status(struct si476x_core *,
+ struct si476x_rsq_status_args *,
+ struct si476x_rsq_status_report *);
+int si476x_core_cmd_fm_rsq_status(struct si476x_core *,
+ struct si476x_rsq_status_args *,
+ struct si476x_rsq_status_report *);
+int si476x_core_cmd_power_up(struct si476x_core *,
+ struct si476x_power_up_args *);
+int si476x_core_cmd_power_down(struct si476x_core *,
+ struct si476x_power_down_args *);
+int si476x_core_cmd_fm_phase_div_status(struct si476x_core *);
+int si476x_core_cmd_fm_phase_diversity(struct si476x_core *,
+ enum si476x_phase_diversity_mode);
+
+int si476x_core_cmd_fm_acf_status(struct si476x_core *,
+ struct si476x_acf_status_report *);
+int si476x_core_cmd_am_acf_status(struct si476x_core *,
+ struct si476x_acf_status_report *);
+int si476x_core_cmd_agc_status(struct si476x_core *,
+ struct si476x_agc_status_report *);
+
+enum si476x_power_grid_type {
+ SI476X_POWER_GRID_50HZ = 0,
+ SI476X_POWER_GRID_60HZ,
+};
+
+/* Properties */
+
+enum si476x_interrupt_flags {
+ SI476X_STCIEN = (1 << 0),
+ SI476X_ACFIEN = (1 << 1),
+ SI476X_RDSIEN = (1 << 2),
+ SI476X_RSQIEN = (1 << 3),
+
+ SI476X_ERRIEN = (1 << 6),
+ SI476X_CTSIEN = (1 << 7),
+
+ SI476X_STCREP = (1 << 8),
+ SI476X_ACFREP = (1 << 9),
+ SI476X_RDSREP = (1 << 10),
+ SI476X_RSQREP = (1 << 11),
+};
+
+enum si476x_rdsint_sources {
+ SI476X_RDSTPPTY = (1 << 4),
+ SI476X_RDSPI = (1 << 3),
+ SI476X_RDSSYNC = (1 << 1),
+ SI476X_RDSRECV = (1 << 0),
+};
+
+enum si476x_status_response_bits {
+ SI476X_CTS = (1 << 7),
+ SI476X_ERR = (1 << 6),
+ /* Status response for WB receiver */
+ SI476X_WB_ASQ_INT = (1 << 4),
+ SI476X_RSQ_INT = (1 << 3),
+ /* Status response for FM receiver */
+ SI476X_FM_RDS_INT = (1 << 2),
+ SI476X_ACF_INT = (1 << 1),
+ SI476X_STC_INT = (1 << 0),
+};
+
+/* -------------------- si476x-prop.c ----------------------- */
+
+enum si476x_common_receiver_properties {
+ SI476X_PROP_INT_CTL_ENABLE = 0x0000,
+ SI476X_PROP_DIGITAL_IO_INPUT_SAMPLE_RATE = 0x0200,
+ SI476X_PROP_DIGITAL_IO_INPUT_FORMAT = 0x0201,
+ SI476X_PROP_DIGITAL_IO_OUTPUT_SAMPLE_RATE = 0x0202,
+ SI476X_PROP_DIGITAL_IO_OUTPUT_FORMAT = 0x0203,
+
+ SI476X_PROP_SEEK_BAND_BOTTOM = 0x1100,
+ SI476X_PROP_SEEK_BAND_TOP = 0x1101,
+ SI476X_PROP_SEEK_FREQUENCY_SPACING = 0x1102,
+
+ SI476X_PROP_VALID_MAX_TUNE_ERROR = 0x2000,
+ SI476X_PROP_VALID_SNR_THRESHOLD = 0x2003,
+ SI476X_PROP_VALID_RSSI_THRESHOLD = 0x2004,
+};
+
+enum si476x_am_receiver_properties {
+ SI476X_PROP_AUDIO_PWR_LINE_FILTER = 0x0303,
+};
+
+enum si476x_fm_receiver_properties {
+ SI476X_PROP_AUDIO_DEEMPHASIS = 0x0302,
+
+ SI476X_PROP_FM_RDS_INTERRUPT_SOURCE = 0x4000,
+ SI476X_PROP_FM_RDS_INTERRUPT_FIFO_COUNT = 0x4001,
+ SI476X_PROP_FM_RDS_CONFIG = 0x4002,
+};
+
+enum si476x_prop_audio_pwr_line_filter_bits {
+ SI476X_PROP_PWR_HARMONICS_MASK = 0b0000000000011111,
+ SI476X_PROP_PWR_GRID_MASK = 0b0000000100000000,
+ SI476X_PROP_PWR_ENABLE_MASK = 0b0000001000000000,
+ SI476X_PROP_PWR_GRID_50HZ = 0b0000000000000000,
+ SI476X_PROP_PWR_GRID_60HZ = 0b0000000100000000,
+};
+
+enum si476x_prop_fm_rds_config_bits {
+ SI476X_PROP_RDSEN_MASK = 0x1,
+ SI476X_PROP_RDSEN = 0x1,
+};
+
+
+struct regmap *devm_regmap_init_si476x(struct si476x_core *);
+
+#endif /* SI476X_CORE_H */
diff --git a/include/linux/mfd/si476x-platform.h b/include/linux/mfd/si476x-platform.h
new file mode 100644
index 0000000..88bb93b
--- /dev/null
+++ b/include/linux/mfd/si476x-platform.h
@@ -0,0 +1,267 @@
+/*
+ * include/media/si476x-platform.h -- Platform data specific definitions
+ *
+ * Copyright (C) 2013 Andrey Smirnov
+ *
+ * Author: Andrey Smirnov <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+
+#ifndef __SI476X_PLATFORM_H__
+#define __SI476X_PLATFORM_H__
+
+/* It is possible to select one of the four adresses using pins A0
+ * and A1 on SI476x */
+#define SI476X_I2C_ADDR_1 0x60
+#define SI476X_I2C_ADDR_2 0x61
+#define SI476X_I2C_ADDR_3 0x62
+#define SI476X_I2C_ADDR_4 0x63
+
+enum si476x_iqclk_config {
+ SI476X_IQCLK_NOOP = 0,
+ SI476X_IQCLK_TRISTATE = 1,
+ SI476X_IQCLK_IQ = 21,
+};
+enum si476x_iqfs_config {
+ SI476X_IQFS_NOOP = 0,
+ SI476X_IQFS_TRISTATE = 1,
+ SI476X_IQFS_IQ = 21,
+};
+enum si476x_iout_config {
+ SI476X_IOUT_NOOP = 0,
+ SI476X_IOUT_TRISTATE = 1,
+ SI476X_IOUT_OUTPUT = 22,
+};
+enum si476x_qout_config {
+ SI476X_QOUT_NOOP = 0,
+ SI476X_QOUT_TRISTATE = 1,
+ SI476X_QOUT_OUTPUT = 22,
+};
+
+enum si476x_dclk_config {
+ SI476X_DCLK_NOOP = 0,
+ SI476X_DCLK_TRISTATE = 1,
+ SI476X_DCLK_DAUDIO = 10,
+};
+
+enum si476x_dfs_config {
+ SI476X_DFS_NOOP = 0,
+ SI476X_DFS_TRISTATE = 1,
+ SI476X_DFS_DAUDIO = 10,
+};
+
+enum si476x_dout_config {
+ SI476X_DOUT_NOOP = 0,
+ SI476X_DOUT_TRISTATE = 1,
+ SI476X_DOUT_I2S_OUTPUT = 12,
+ SI476X_DOUT_I2S_INPUT = 13,
+};
+
+enum si476x_xout_config {
+ SI476X_XOUT_NOOP = 0,
+ SI476X_XOUT_TRISTATE = 1,
+ SI476X_XOUT_I2S_INPUT = 13,
+ SI476X_XOUT_MODE_SELECT = 23,
+};
+
+enum si476x_icin_config {
+ SI476X_ICIN_NOOP = 0,
+ SI476X_ICIN_TRISTATE = 1,
+ SI476X_ICIN_GPO1_HIGH = 2,
+ SI476X_ICIN_GPO1_LOW = 3,
+ SI476X_ICIN_IC_LINK = 30,
+};
+
+enum si476x_icip_config {
+ SI476X_ICIP_NOOP = 0,
+ SI476X_ICIP_TRISTATE = 1,
+ SI476X_ICIP_GPO2_HIGH = 2,
+ SI476X_ICIP_GPO2_LOW = 3,
+ SI476X_ICIP_IC_LINK = 30,
+};
+
+enum si476x_icon_config {
+ SI476X_ICON_NOOP = 0,
+ SI476X_ICON_TRISTATE = 1,
+ SI476X_ICON_I2S = 10,
+ SI476X_ICON_IC_LINK = 30,
+};
+
+enum si476x_icop_config {
+ SI476X_ICOP_NOOP = 0,
+ SI476X_ICOP_TRISTATE = 1,
+ SI476X_ICOP_I2S = 10,
+ SI476X_ICOP_IC_LINK = 30,
+};
+
+
+enum si476x_lrout_config {
+ SI476X_LROUT_NOOP = 0,
+ SI476X_LROUT_TRISTATE = 1,
+ SI476X_LROUT_AUDIO = 2,
+ SI476X_LROUT_MPX = 3,
+};
+
+
+enum si476x_intb_config {
+ SI476X_INTB_NOOP = 0,
+ SI476X_INTB_TRISTATE = 1,
+ SI476X_INTB_DAUDIO = 10,
+ SI476X_INTB_IRQ = 40,
+};
+
+enum si476x_a1_config {
+ SI476X_A1_NOOP = 0,
+ SI476X_A1_TRISTATE = 1,
+ SI476X_A1_IRQ = 40,
+};
+
+
+struct si476x_pinmux {
+ enum si476x_dclk_config dclk;
+ enum si476x_dfs_config dfs;
+ enum si476x_dout_config dout;
+ enum si476x_xout_config xout;
+
+ enum si476x_iqclk_config iqclk;
+ enum si476x_iqfs_config iqfs;
+ enum si476x_iout_config iout;
+ enum si476x_qout_config qout;
+
+ enum si476x_icin_config icin;
+ enum si476x_icip_config icip;
+ enum si476x_icon_config icon;
+ enum si476x_icop_config icop;
+
+ enum si476x_lrout_config lrout;
+
+ enum si476x_intb_config intb;
+ enum si476x_a1_config a1;
+};
+
+enum si476x_ibias6x {
+ SI476X_IBIAS6X_OTHER = 0,
+ SI476X_IBIAS6X_RCVR1_NON_4MHZ_CLK = 1,
+};
+
+enum si476x_xstart {
+ SI476X_XSTART_MULTIPLE_TUNER = 0x11,
+ SI476X_XSTART_NORMAL = 0x77,
+};
+
+enum si476x_freq {
+ SI476X_FREQ_4_MHZ = 0,
+ SI476X_FREQ_37P209375_MHZ = 1,
+ SI476X_FREQ_36P4_MHZ = 2,
+ SI476X_FREQ_37P8_MHZ = 3,
+};
+
+enum si476x_xmode {
+ SI476X_XMODE_CRYSTAL_RCVR1 = 1,
+ SI476X_XMODE_EXT_CLOCK = 2,
+ SI476X_XMODE_CRYSTAL_RCVR2_3 = 3,
+};
+
+enum si476x_xbiashc {
+ SI476X_XBIASHC_SINGLE_RECEIVER = 0,
+ SI476X_XBIASHC_MULTIPLE_RECEIVER = 1,
+};
+
+enum si476x_xbias {
+ SI476X_XBIAS_RCVR2_3 = 0,
+ SI476X_XBIAS_4MHZ_RCVR1 = 3,
+ SI476X_XBIAS_RCVR1 = 7,
+};
+
+enum si476x_func {
+ SI476X_FUNC_BOOTLOADER = 0,
+ SI476X_FUNC_FM_RECEIVER = 1,
+ SI476X_FUNC_AM_RECEIVER = 2,
+ SI476X_FUNC_WB_RECEIVER = 3,
+};
+
+
+/**
+ * @xcload: Selects the amount of additional on-chip capacitance to
+ * be connected between XTAL1 and gnd and between XTAL2 and
+ * GND. One half of the capacitance value shown here is the
+ * additional load capacitance presented to the xtal. The
+ * minimum step size is 0.277 pF. Recommended value is 0x28
+ * but it will be layout dependent. Range is 0–0x3F i.e.
+ * (0–16.33 pF)
+ * @ctsien: enable CTSINT(interrupt request when CTS condition
+ * arises) when set
+ * @intsel: when set A1 pin becomes the interrupt pin; otherwise,
+ * INTB is the interrupt pin
+ * @func: selects the boot function of the device. I.e.
+ * SI476X_BOOTLOADER - Boot loader
+ * SI476X_FM_RECEIVER - FM receiver
+ * SI476X_AM_RECEIVER - AM receiver
+ * SI476X_WB_RECEIVER - Weatherband receiver
+ * @freq: oscillator's crystal frequency:
+ * SI476X_XTAL_37P209375_MHZ - 37.209375 Mhz
+ * SI476X_XTAL_36P4_MHZ - 36.4 Mhz
+ * SI476X_XTAL_37P8_MHZ - 37.8 Mhz
+ */
+struct si476x_power_up_args {
+ enum si476x_ibias6x ibias6x;
+ enum si476x_xstart xstart;
+ u8 xcload;
+ bool fastboot;
+ enum si476x_xbiashc xbiashc;
+ enum si476x_xbias xbias;
+ enum si476x_func func;
+ enum si476x_freq freq;
+ enum si476x_xmode xmode;
+};
+
+
+/**
+ * enum si476x_phase_diversity_mode - possbile phase diversity modes
+ * for SI4764/5/6/7 chips.
+ *
+ * @SI476X_PHDIV_DISABLED: Phase diversity feature is
+ * disabled.
+ * @SI476X_PHDIV_PRIMARY_COMBINING: Tuner works as a primary tuner
+ * in combination with a
+ * secondary one.
+ * @SI476X_PHDIV_PRIMARY_ANTENNA: Tuner works as a primary tuner
+ * using only its own antenna.
+ * @SI476X_PHDIV_SECONDARY_ANTENNA: Tuner works as a primary tuner
+ * usning seconary tuner's antenna.
+ * @SI476X_PHDIV_SECONDARY_COMBINING: Tuner works as a secondary
+ * tuner in combination with the
+ * primary one.
+ */
+enum si476x_phase_diversity_mode {
+ SI476X_PHDIV_DISABLED = 0,
+ SI476X_PHDIV_PRIMARY_COMBINING = 1,
+ SI476X_PHDIV_PRIMARY_ANTENNA = 2,
+ SI476X_PHDIV_SECONDARY_ANTENNA = 3,
+ SI476X_PHDIV_SECONDARY_COMBINING = 5,
+};
+
+
+/*
+ * Platform dependent definition
+ */
+struct si476x_platform_data {
+ int gpio_reset; /* < 0 if not used */
+
+ struct si476x_power_up_args power_up_parameters;
+ enum si476x_phase_diversity_mode diversity_mode;
+
+ struct si476x_pinmux pinmux;
+};
+
+
+#endif /* __SI476X_PLATFORM_H__ */
diff --git a/include/linux/mfd/si476x-reports.h b/include/linux/mfd/si476x-reports.h
new file mode 100644
index 0000000..e0b9455
--- /dev/null
+++ b/include/linux/mfd/si476x-reports.h
@@ -0,0 +1,163 @@
+/*
+ * include/media/si476x-platform.h -- Definitions of the data formats
+ * returned by debugfs hooks
+ *
+ * Copyright (C) 2013 Andrey Smirnov
+ *
+ * Author: Andrey Smirnov <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+
+#ifndef __SI476X_REPORTS_H__
+#define __SI476X_REPORTS_H__
+
+/**
+ * struct si476x_rsq_status - structure containing received signal
+ * quality
+ * @multhint: Multipath Detect High.
+ * true - Indicatedes that the value is below
+ * FM_RSQ_MULTIPATH_HIGH_THRESHOLD
+ * false - Indicatedes that the value is above
+ * FM_RSQ_MULTIPATH_HIGH_THRESHOLD
+ * @multlint: Multipath Detect Low.
+ * true - Indicatedes that the value is below
+ * FM_RSQ_MULTIPATH_LOW_THRESHOLD
+ * false - Indicatedes that the value is above
+ * FM_RSQ_MULTIPATH_LOW_THRESHOLD
+ * @snrhint: SNR Detect High.
+ * true - Indicatedes that the value is below
+ * FM_RSQ_SNR_HIGH_THRESHOLD
+ * false - Indicatedes that the value is above
+ * FM_RSQ_SNR_HIGH_THRESHOLD
+ * @snrlint: SNR Detect Low.
+ * true - Indicatedes that the value is below
+ * FM_RSQ_SNR_LOW_THRESHOLD
+ * false - Indicatedes that the value is above
+ * FM_RSQ_SNR_LOW_THRESHOLD
+ * @rssihint: RSSI Detect High.
+ * true - Indicatedes that the value is below
+ * FM_RSQ_RSSI_HIGH_THRESHOLD
+ * false - Indicatedes that the value is above
+ * FM_RSQ_RSSI_HIGH_THRESHOLD
+ * @rssilint: RSSI Detect Low.
+ * true - Indicatedes that the value is below
+ * FM_RSQ_RSSI_LOW_THRESHOLD
+ * false - Indicatedes that the value is above
+ * FM_RSQ_RSSI_LOW_THRESHOLD
+ * @bltf: Band Limit.
+ * Set if seek command hits the band limit or wrapped to
+ * the original frequency.
+ * @snr_ready: SNR measurement in progress.
+ * @rssiready: RSSI measurement in progress.
+ * @afcrl: Set if FREQOFF >= MAX_TUNE_ERROR
+ * @valid: Set if the channel is valid
+ * rssi < FM_VALID_RSSI_THRESHOLD
+ * snr < FM_VALID_SNR_THRESHOLD
+ * tune_error < FM_VALID_MAX_TUNE_ERROR
+ * @readfreq: Current tuned frequency.
+ * @freqoff: Signed frequency offset.
+ * @rssi: Received Signal Strength Indicator(dBuV).
+ * @snr: RF SNR Indicator(dB).
+ * @lassi:
+ * @hassi: Low/High side Adjacent(100 kHz) Channel Strength Indicator
+ * @mult: Multipath indicator
+ * @dev: Who knows? But values may vary.
+ * @readantcap: Antenna tuning capacity value.
+ * @assi: Adjacent Channel(+/- 200kHz) Strength Indicator
+ * @usn: Ultrasonic Noise Inticator in -DBFS
+ */
+struct si476x_rsq_status_report {
+ __u8 multhint, multlint;
+ __u8 snrhint, snrlint;
+ __u8 rssihint, rssilint;
+ __u8 bltf;
+ __u8 snr_ready;
+ __u8 rssiready;
+ __u8 injside;
+ __u8 afcrl;
+ __u8 valid;
+
+ __u16 readfreq;
+ __s8 freqoff;
+ __s8 rssi;
+ __s8 snr;
+ __s8 issi;
+ __s8 lassi, hassi;
+ __s8 mult;
+ __u8 dev;
+ __u16 readantcap;
+ __s8 assi;
+ __s8 usn;
+
+ __u8 pilotdev;
+ __u8 rdsdev;
+ __u8 assidev;
+ __u8 strongdev;
+ __u16 rdspi;
+} __packed;
+
+/**
+ * si476x_acf_status_report - ACF report results
+ *
+ * @blend_int: If set, indicates that stereo separation has crossed
+ * below the blend threshold as set by FM_ACF_BLEND_THRESHOLD
+ * @hblend_int: If set, indicates that HiBlend cutoff frequency is
+ * lower than threshold as set by FM_ACF_HBLEND_THRESHOLD
+ * @hicut_int: If set, indicates that HiCut cutoff frequency is lower
+ * than the threshold set by ACF_
+
+ */
+struct si476x_acf_status_report {
+ __u8 blend_int;
+ __u8 hblend_int;
+ __u8 hicut_int;
+ __u8 chbw_int;
+ __u8 softmute_int;
+ __u8 smute;
+ __u8 smattn;
+ __u8 chbw;
+ __u8 hicut;
+ __u8 hiblend;
+ __u8 pilot;
+ __u8 stblend;
+} __packed;
+
+enum si476x_fmagc {
+ SI476X_FMAGC_10K_OHM = 0,
+ SI476X_FMAGC_800_OHM = 1,
+ SI476X_FMAGC_400_OHM = 2,
+ SI476X_FMAGC_200_OHM = 4,
+ SI476X_FMAGC_100_OHM = 8,
+ SI476X_FMAGC_50_OHM = 16,
+ SI476X_FMAGC_25_OHM = 32,
+ SI476X_FMAGC_12P5_OHM = 64,
+ SI476X_FMAGC_6P25_OHM = 128,
+};
+
+struct si476x_agc_status_report {
+ __u8 mxhi;
+ __u8 mxlo;
+ __u8 lnahi;
+ __u8 lnalo;
+ __u8 fmagc1;
+ __u8 fmagc2;
+ __u8 pgagain;
+ __u8 fmwblang;
+} __packed;
+
+struct si476x_rds_blockcount_report {
+ __u16 expected;
+ __u16 received;
+ __u16 uncorrectable;
+} __packed;
+
+#endif /* __SI476X_REPORTS_H__ */
--
1.7.10.4

2013-04-18 17:04:20

by Andrey Smirnov

[permalink] [raw]
Subject: [PATCH 01/12] mfd: Add commands abstraction layer for SI476X MFD

From: Andrey Smirnov <andreysm@charmander.(none)>

This patch adds all the functions used for exchanging commands with
the chip.

Acked-by: Hans Verkuil <[email protected]>
Signed-off-by: Andrey Smirnov <[email protected]>
---
drivers/mfd/si476x-cmd.c | 1553 ++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 1553 insertions(+)
create mode 100644 drivers/mfd/si476x-cmd.c

diff --git a/drivers/mfd/si476x-cmd.c b/drivers/mfd/si476x-cmd.c
new file mode 100644
index 0000000..de48b4e
--- /dev/null
+++ b/drivers/mfd/si476x-cmd.c
@@ -0,0 +1,1553 @@
+/*
+ * drivers/mfd/si476x-cmd.c -- Subroutines implementing command
+ * protocol of si476x series of chips
+ *
+ * Copyright (C) 2012 Innovative Converged Devices(ICD)
+ * Copyright (C) 2013 Andrey Smirnov
+ *
+ * Author: Andrey Smirnov <[email protected]>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/atomic.h>
+#include <linux/i2c.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/videodev2.h>
+
+#include <linux/mfd/si476x-core.h>
+
+#define msb(x) ((u8)((u16) x >> 8))
+#define lsb(x) ((u8)((u16) x & 0x00FF))
+
+
+
+#define CMD_POWER_UP 0x01
+#define CMD_POWER_UP_A10_NRESP 1
+#define CMD_POWER_UP_A10_NARGS 5
+
+#define CMD_POWER_UP_A20_NRESP 1
+#define CMD_POWER_UP_A20_NARGS 5
+
+#define POWER_UP_DELAY_MS 110
+
+#define CMD_POWER_DOWN 0x11
+#define CMD_POWER_DOWN_A10_NRESP 1
+
+#define CMD_POWER_DOWN_A20_NRESP 1
+#define CMD_POWER_DOWN_A20_NARGS 1
+
+#define CMD_FUNC_INFO 0x12
+#define CMD_FUNC_INFO_NRESP 7
+
+#define CMD_SET_PROPERTY 0x13
+#define CMD_SET_PROPERTY_NARGS 5
+#define CMD_SET_PROPERTY_NRESP 1
+
+#define CMD_GET_PROPERTY 0x14
+#define CMD_GET_PROPERTY_NARGS 3
+#define CMD_GET_PROPERTY_NRESP 4
+
+#define CMD_AGC_STATUS 0x17
+#define CMD_AGC_STATUS_NRESP_A10 2
+#define CMD_AGC_STATUS_NRESP_A20 6
+
+#define PIN_CFG_BYTE(x) (0x7F & (x))
+#define CMD_DIG_AUDIO_PIN_CFG 0x18
+#define CMD_DIG_AUDIO_PIN_CFG_NARGS 4
+#define CMD_DIG_AUDIO_PIN_CFG_NRESP 5
+
+#define CMD_ZIF_PIN_CFG 0x19
+#define CMD_ZIF_PIN_CFG_NARGS 4
+#define CMD_ZIF_PIN_CFG_NRESP 5
+
+#define CMD_IC_LINK_GPO_CTL_PIN_CFG 0x1A
+#define CMD_IC_LINK_GPO_CTL_PIN_CFG_NARGS 4
+#define CMD_IC_LINK_GPO_CTL_PIN_CFG_NRESP 5
+
+#define CMD_ANA_AUDIO_PIN_CFG 0x1B
+#define CMD_ANA_AUDIO_PIN_CFG_NARGS 1
+#define CMD_ANA_AUDIO_PIN_CFG_NRESP 2
+
+#define CMD_INTB_PIN_CFG 0x1C
+#define CMD_INTB_PIN_CFG_NARGS 2
+#define CMD_INTB_PIN_CFG_A10_NRESP 6
+#define CMD_INTB_PIN_CFG_A20_NRESP 3
+
+#define CMD_FM_TUNE_FREQ 0x30
+#define CMD_FM_TUNE_FREQ_A10_NARGS 5
+#define CMD_FM_TUNE_FREQ_A20_NARGS 3
+#define CMD_FM_TUNE_FREQ_NRESP 1
+
+#define CMD_FM_RSQ_STATUS 0x32
+
+#define CMD_FM_RSQ_STATUS_A10_NARGS 1
+#define CMD_FM_RSQ_STATUS_A10_NRESP 17
+#define CMD_FM_RSQ_STATUS_A30_NARGS 1
+#define CMD_FM_RSQ_STATUS_A30_NRESP 23
+
+
+#define CMD_FM_SEEK_START 0x31
+#define CMD_FM_SEEK_START_NARGS 1
+#define CMD_FM_SEEK_START_NRESP 1
+
+#define CMD_FM_RDS_STATUS 0x36
+#define CMD_FM_RDS_STATUS_NARGS 1
+#define CMD_FM_RDS_STATUS_NRESP 16
+
+#define CMD_FM_RDS_BLOCKCOUNT 0x37
+#define CMD_FM_RDS_BLOCKCOUNT_NARGS 1
+#define CMD_FM_RDS_BLOCKCOUNT_NRESP 8
+
+#define CMD_FM_PHASE_DIVERSITY 0x38
+#define CMD_FM_PHASE_DIVERSITY_NARGS 1
+#define CMD_FM_PHASE_DIVERSITY_NRESP 1
+
+#define CMD_FM_PHASE_DIV_STATUS 0x39
+#define CMD_FM_PHASE_DIV_STATUS_NRESP 2
+
+#define CMD_AM_TUNE_FREQ 0x40
+#define CMD_AM_TUNE_FREQ_NARGS 3
+#define CMD_AM_TUNE_FREQ_NRESP 1
+
+#define CMD_AM_RSQ_STATUS 0x42
+#define CMD_AM_RSQ_STATUS_NARGS 1
+#define CMD_AM_RSQ_STATUS_NRESP 13
+
+#define CMD_AM_SEEK_START 0x41
+#define CMD_AM_SEEK_START_NARGS 1
+#define CMD_AM_SEEK_START_NRESP 1
+
+
+#define CMD_AM_ACF_STATUS 0x45
+#define CMD_AM_ACF_STATUS_NRESP 6
+#define CMD_AM_ACF_STATUS_NARGS 1
+
+#define CMD_FM_ACF_STATUS 0x35
+#define CMD_FM_ACF_STATUS_NRESP 8
+#define CMD_FM_ACF_STATUS_NARGS 1
+
+#define CMD_MAX_ARGS_COUNT (10)
+
+
+enum si476x_acf_status_report_bits {
+ SI476X_ACF_BLEND_INT = (1 << 4),
+ SI476X_ACF_HIBLEND_INT = (1 << 3),
+ SI476X_ACF_HICUT_INT = (1 << 2),
+ SI476X_ACF_CHBW_INT = (1 << 1),
+ SI476X_ACF_SOFTMUTE_INT = (1 << 0),
+
+ SI476X_ACF_SMUTE = (1 << 0),
+ SI476X_ACF_SMATTN = 0b11111,
+ SI476X_ACF_PILOT = (1 << 7),
+ SI476X_ACF_STBLEND = ~SI476X_ACF_PILOT,
+};
+
+enum si476x_agc_status_report_bits {
+ SI476X_AGC_MXHI = (1 << 5),
+ SI476X_AGC_MXLO = (1 << 4),
+ SI476X_AGC_LNAHI = (1 << 3),
+ SI476X_AGC_LNALO = (1 << 2),
+};
+
+enum si476x_errors {
+ SI476X_ERR_BAD_COMMAND = 0x10,
+ SI476X_ERR_BAD_ARG1 = 0x11,
+ SI476X_ERR_BAD_ARG2 = 0x12,
+ SI476X_ERR_BAD_ARG3 = 0x13,
+ SI476X_ERR_BAD_ARG4 = 0x14,
+ SI476X_ERR_BUSY = 0x18,
+ SI476X_ERR_BAD_INTERNAL_MEMORY = 0x20,
+ SI476X_ERR_BAD_PATCH = 0x30,
+ SI476X_ERR_BAD_BOOT_MODE = 0x31,
+ SI476X_ERR_BAD_PROPERTY = 0x40,
+};
+
+static int si476x_core_parse_and_nag_about_error(struct si476x_core *core)
+{
+ int err;
+ char *cause;
+ u8 buffer[2];
+
+ if (core->revision != SI476X_REVISION_A10) {
+ err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV,
+ buffer, sizeof(buffer));
+ if (err == sizeof(buffer)) {
+ switch (buffer[1]) {
+ case SI476X_ERR_BAD_COMMAND:
+ cause = "Bad command";
+ err = -EINVAL;
+ break;
+ case SI476X_ERR_BAD_ARG1:
+ cause = "Bad argument #1";
+ err = -EINVAL;
+ break;
+ case SI476X_ERR_BAD_ARG2:
+ cause = "Bad argument #2";
+ err = -EINVAL;
+ break;
+ case SI476X_ERR_BAD_ARG3:
+ cause = "Bad argument #3";
+ err = -EINVAL;
+ break;
+ case SI476X_ERR_BAD_ARG4:
+ cause = "Bad argument #4";
+ err = -EINVAL;
+ break;
+ case SI476X_ERR_BUSY:
+ cause = "Chip is busy";
+ err = -EBUSY;
+ break;
+ case SI476X_ERR_BAD_INTERNAL_MEMORY:
+ cause = "Bad internal memory";
+ err = -EIO;
+ break;
+ case SI476X_ERR_BAD_PATCH:
+ cause = "Bad patch";
+ err = -EINVAL;
+ break;
+ case SI476X_ERR_BAD_BOOT_MODE:
+ cause = "Bad boot mode";
+ err = -EINVAL;
+ break;
+ case SI476X_ERR_BAD_PROPERTY:
+ cause = "Bad property";
+ err = -EINVAL;
+ break;
+ default:
+ cause = "Unknown";
+ err = -EIO;
+ }
+
+ dev_err(&core->client->dev,
+ "[Chip error status]: %s\n", cause);
+ } else {
+ dev_err(&core->client->dev,
+ "Failed to fetch error code\n");
+ err = (err >= 0) ? -EIO : err;
+ }
+ } else {
+ err = -EIO;
+ }
+
+ return err;
+}
+
+/**
+ * si476x_core_send_command() - sends a command to si476x and waits its
+ * response
+ * @core: si476x_device structure for the device we are
+ * communicating with
+ * @command: command id
+ * @args: command arguments we are sending
+ * @argn: actual size of @args
+ * @response: buffer to place the expected response from the device
+ * @respn: actual size of @response
+ * @usecs: amount of time to wait before reading the response (in
+ * usecs)
+ *
+ * Function returns 0 on succsess and negative error code on
+ * failure
+ */
+static int si476x_core_send_command(struct si476x_core *core,
+ const u8 command,
+ const u8 args[],
+ const int argn,
+ u8 resp[],
+ const int respn,
+ const int usecs)
+{
+ struct i2c_client *client = core->client;
+ int err;
+ u8 data[CMD_MAX_ARGS_COUNT + 1];
+
+ if (argn > CMD_MAX_ARGS_COUNT) {
+ err = -ENOMEM;
+ goto exit;
+ }
+
+ if (!client->adapter) {
+ err = -ENODEV;
+ goto exit;
+ }
+
+ /* First send the command and its arguments */
+ data[0] = command;
+ memcpy(&data[1], args, argn);
+ dev_dbg(&client->dev, "Command:\n %*ph\n", argn + 1, data);
+
+ err = si476x_core_i2c_xfer(core, SI476X_I2C_SEND,
+ (char *) data, argn + 1);
+ if (err != argn + 1) {
+ dev_err(&core->client->dev,
+ "Error while sending command 0x%02x\n",
+ command);
+ err = (err >= 0) ? -EIO : err;
+ goto exit;
+ }
+ /* Set CTS to zero only after the command is send to avoid
+ * possible racing conditions when working in polling mode */
+ atomic_set(&core->cts, 0);
+
+ /* if (unlikely(command == CMD_POWER_DOWN) */
+ if (!wait_event_timeout(core->command,
+ atomic_read(&core->cts),
+ usecs_to_jiffies(usecs) + 1))
+ dev_warn(&core->client->dev,
+ "(%s) [CMD 0x%02x] Answer timeout.\n",
+ __func__, command);
+
+ /*
+ When working in polling mode, for some reason the tuner will
+ report CTS bit as being set in the first status byte read,
+ but all the consequtive ones will return zeros until the
+ tuner is actually completed the POWER_UP command. To
+ workaround that we wait for second CTS to be reported
+ */
+ if (unlikely(!core->client->irq && command == CMD_POWER_UP)) {
+ if (!wait_event_timeout(core->command,
+ atomic_read(&core->cts),
+ usecs_to_jiffies(usecs) + 1))
+ dev_warn(&core->client->dev,
+ "(%s) Power up took too much time.\n",
+ __func__);
+ }
+
+ /* Then get the response */
+ err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV, resp, respn);
+ if (err != respn) {
+ dev_err(&core->client->dev,
+ "Error while reading response for command 0x%02x\n",
+ command);
+ err = (err >= 0) ? -EIO : err;
+ goto exit;
+ }
+ dev_dbg(&client->dev, "Response:\n %*ph\n", respn, resp);
+
+ err = 0;
+
+ if (resp[0] & SI476X_ERR) {
+ dev_err(&core->client->dev,
+ "[CMD 0x%02x] Chip set error flag\n", command);
+ err = si476x_core_parse_and_nag_about_error(core);
+ goto exit;
+ }
+
+ if (!(resp[0] & SI476X_CTS))
+ err = -EBUSY;
+exit:
+ return err;
+}
+
+static int si476x_cmd_clear_stc(struct si476x_core *core)
+{
+ int err;
+ struct si476x_rsq_status_args args = {
+ .primary = false,
+ .rsqack = false,
+ .attune = false,
+ .cancel = false,
+ .stcack = true,
+ };
+
+ switch (core->power_up_parameters.func) {
+ case SI476X_FUNC_FM_RECEIVER:
+ err = si476x_core_cmd_fm_rsq_status(core, &args, NULL);
+ break;
+ case SI476X_FUNC_AM_RECEIVER:
+ err = si476x_core_cmd_am_rsq_status(core, &args, NULL);
+ break;
+ default:
+ err = -EINVAL;
+ }
+
+ return err;
+}
+
+static int si476x_cmd_tune_seek_freq(struct si476x_core *core,
+ uint8_t cmd,
+ const uint8_t args[], size_t argn,
+ uint8_t *resp, size_t respn)
+{
+ int err;
+
+
+ atomic_set(&core->stc, 0);
+ err = si476x_core_send_command(core, cmd, args, argn, resp, respn,
+ SI476X_TIMEOUT_TUNE);
+ if (!err) {
+ wait_event_killable(core->tuning,
+ atomic_read(&core->stc));
+ si476x_cmd_clear_stc(core);
+ }
+
+ return err;
+}
+
+/**
+ * si476x_cmd_func_info() - send 'FUNC_INFO' command to the device
+ * @core: device to send the command to
+ * @info: struct si476x_func_info to fill all the information
+ * returned by the command
+ *
+ * The command requests the firmware and patch version for currently
+ * loaded firmware (dependent on the function of the device FM/AM/WB)
+ *
+ * Function returns 0 on succsess and negative error code on
+ * failure
+ */
+int si476x_core_cmd_func_info(struct si476x_core *core,
+ struct si476x_func_info *info)
+{
+ int err;
+ u8 resp[CMD_FUNC_INFO_NRESP];
+
+ err = si476x_core_send_command(core, CMD_FUNC_INFO,
+ NULL, 0,
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+
+ info->firmware.major = resp[1];
+ info->firmware.minor[0] = resp[2];
+ info->firmware.minor[1] = resp[3];
+
+ info->patch_id = ((u16) resp[4] << 8) | resp[5];
+ info->func = resp[6];
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_func_info);
+
+/**
+ * si476x_cmd_set_property() - send 'SET_PROPERTY' command to the device
+ * @core: device to send the command to
+ * @property: property address
+ * @value: property value
+ *
+ * Function returns 0 on succsess and negative error code on
+ * failure
+ */
+int si476x_core_cmd_set_property(struct si476x_core *core,
+ u16 property, u16 value)
+{
+ u8 resp[CMD_SET_PROPERTY_NRESP];
+ const u8 args[CMD_SET_PROPERTY_NARGS] = {
+ 0x00,
+ msb(property),
+ lsb(property),
+ msb(value),
+ lsb(value),
+ };
+
+ return si476x_core_send_command(core, CMD_SET_PROPERTY,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_set_property);
+
+/**
+ * si476x_cmd_get_property() - send 'GET_PROPERTY' command to the device
+ * @core: device to send the command to
+ * @property: property address
+ *
+ * Function return the value of property as u16 on success or a
+ * negative error on failure
+ */
+int si476x_core_cmd_get_property(struct si476x_core *core, u16 property)
+{
+ int err;
+ u8 resp[CMD_GET_PROPERTY_NRESP];
+ const u8 args[CMD_GET_PROPERTY_NARGS] = {
+ 0x00,
+ msb(property),
+ lsb(property),
+ };
+
+ err = si476x_core_send_command(core, CMD_GET_PROPERTY,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+ if (err < 0)
+ return err;
+ else
+ return be16_to_cpup((__be16 *)(resp + 2));
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_get_property);
+
+/**
+ * si476x_cmd_dig_audio_pin_cfg() - send 'DIG_AUDIO_PIN_CFG' command to
+ * the device
+ * @core: device to send the command to
+ * @dclk: DCLK pin function configuration:
+ * #SI476X_DCLK_NOOP - do not modify the behaviour
+ * #SI476X_DCLK_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * #SI476X_DCLK_DAUDIO - set the pin to be a part of digital
+ * audio interface
+ * @dfs: DFS pin function configuration:
+ * #SI476X_DFS_NOOP - do not modify the behaviour
+ * #SI476X_DFS_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_DFS_DAUDIO - set the pin to be a part of digital
+ * audio interface
+ * @dout - DOUT pin function configuration:
+ * SI476X_DOUT_NOOP - do not modify the behaviour
+ * SI476X_DOUT_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_DOUT_I2S_OUTPUT - set this pin to be digital out on I2S
+ * port 1
+ * SI476X_DOUT_I2S_INPUT - set this pin to be digital in on I2S
+ * port 1
+ * @xout - XOUT pin function configuration:
+ * SI476X_XOUT_NOOP - do not modify the behaviour
+ * SI476X_XOUT_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_XOUT_I2S_INPUT - set this pin to be digital in on I2S
+ * port 1
+ * SI476X_XOUT_MODE_SELECT - set this pin to be the input that
+ * selects the mode of the I2S audio
+ * combiner (analog or HD)
+ * [SI4761/63/65/67 Only]
+ *
+ * Function returns 0 on success and negative error code on failure
+ */
+int si476x_core_cmd_dig_audio_pin_cfg(struct si476x_core *core,
+ enum si476x_dclk_config dclk,
+ enum si476x_dfs_config dfs,
+ enum si476x_dout_config dout,
+ enum si476x_xout_config xout)
+{
+ u8 resp[CMD_DIG_AUDIO_PIN_CFG_NRESP];
+ const u8 args[CMD_DIG_AUDIO_PIN_CFG_NARGS] = {
+ PIN_CFG_BYTE(dclk),
+ PIN_CFG_BYTE(dfs),
+ PIN_CFG_BYTE(dout),
+ PIN_CFG_BYTE(xout),
+ };
+
+ return si476x_core_send_command(core, CMD_DIG_AUDIO_PIN_CFG,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_dig_audio_pin_cfg);
+
+/**
+ * si476x_cmd_zif_pin_cfg - send 'ZIF_PIN_CFG_COMMAND'
+ * @core - device to send the command to
+ * @iqclk - IQCL pin function configuration:
+ * SI476X_IQCLK_NOOP - do not modify the behaviour
+ * SI476X_IQCLK_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_IQCLK_IQ - set pin to be a part of I/Q interace
+ * in master mode
+ * @iqfs - IQFS pin function configuration:
+ * SI476X_IQFS_NOOP - do not modify the behaviour
+ * SI476X_IQFS_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_IQFS_IQ - set pin to be a part of I/Q interace
+ * in master mode
+ * @iout - IOUT pin function configuration:
+ * SI476X_IOUT_NOOP - do not modify the behaviour
+ * SI476X_IOUT_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_IOUT_OUTPUT - set pin to be I out
+ * @qout - QOUT pin function configuration:
+ * SI476X_QOUT_NOOP - do not modify the behaviour
+ * SI476X_QOUT_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_QOUT_OUTPUT - set pin to be Q out
+ *
+ * Function returns 0 on success and negative error code on failure
+ */
+int si476x_core_cmd_zif_pin_cfg(struct si476x_core *core,
+ enum si476x_iqclk_config iqclk,
+ enum si476x_iqfs_config iqfs,
+ enum si476x_iout_config iout,
+ enum si476x_qout_config qout)
+{
+ u8 resp[CMD_ZIF_PIN_CFG_NRESP];
+ const u8 args[CMD_ZIF_PIN_CFG_NARGS] = {
+ PIN_CFG_BYTE(iqclk),
+ PIN_CFG_BYTE(iqfs),
+ PIN_CFG_BYTE(iout),
+ PIN_CFG_BYTE(qout),
+ };
+
+ return si476x_core_send_command(core, CMD_ZIF_PIN_CFG,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_zif_pin_cfg);
+
+/**
+ * si476x_cmd_ic_link_gpo_ctl_pin_cfg - send
+ * 'IC_LINK_GPIO_CTL_PIN_CFG' comand to the device
+ * @core - device to send the command to
+ * @icin - ICIN pin function configuration:
+ * SI476X_ICIN_NOOP - do not modify the behaviour
+ * SI476X_ICIN_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_ICIN_GPO1_HIGH - set pin to be an output, drive it high
+ * SI476X_ICIN_GPO1_LOW - set pin to be an output, drive it low
+ * SI476X_ICIN_IC_LINK - set the pin to be a part of Inter-Chip link
+ * @icip - ICIP pin function configuration:
+ * SI476X_ICIP_NOOP - do not modify the behaviour
+ * SI476X_ICIP_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_ICIP_GPO1_HIGH - set pin to be an output, drive it high
+ * SI476X_ICIP_GPO1_LOW - set pin to be an output, drive it low
+ * SI476X_ICIP_IC_LINK - set the pin to be a part of Inter-Chip link
+ * @icon - ICON pin function configuration:
+ * SI476X_ICON_NOOP - do not modify the behaviour
+ * SI476X_ICON_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_ICON_I2S - set the pin to be a part of audio
+ * interface in slave mode (DCLK)
+ * SI476X_ICON_IC_LINK - set the pin to be a part of Inter-Chip link
+ * @icop - ICOP pin function configuration:
+ * SI476X_ICOP_NOOP - do not modify the behaviour
+ * SI476X_ICOP_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_ICOP_I2S - set the pin to be a part of audio
+ * interface in slave mode (DOUT)
+ * [Si4761/63/65/67 Only]
+ * SI476X_ICOP_IC_LINK - set the pin to be a part of Inter-Chip link
+ *
+ * Function returns 0 on success and negative error code on failure
+ */
+int si476x_core_cmd_ic_link_gpo_ctl_pin_cfg(struct si476x_core *core,
+ enum si476x_icin_config icin,
+ enum si476x_icip_config icip,
+ enum si476x_icon_config icon,
+ enum si476x_icop_config icop)
+{
+ u8 resp[CMD_IC_LINK_GPO_CTL_PIN_CFG_NRESP];
+ const u8 args[CMD_IC_LINK_GPO_CTL_PIN_CFG_NARGS] = {
+ PIN_CFG_BYTE(icin),
+ PIN_CFG_BYTE(icip),
+ PIN_CFG_BYTE(icon),
+ PIN_CFG_BYTE(icop),
+ };
+
+ return si476x_core_send_command(core, CMD_IC_LINK_GPO_CTL_PIN_CFG,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_ic_link_gpo_ctl_pin_cfg);
+
+/**
+ * si476x_cmd_ana_audio_pin_cfg - send 'ANA_AUDIO_PIN_CFG' to the
+ * device
+ * @core - device to send the command to
+ * @lrout - LROUT pin function configuration:
+ * SI476X_LROUT_NOOP - do not modify the behaviour
+ * SI476X_LROUT_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_LROUT_AUDIO - set pin to be audio output
+ * SI476X_LROUT_MPX - set pin to be MPX output
+ *
+ * Function returns 0 on success and negative error code on failure
+ */
+int si476x_core_cmd_ana_audio_pin_cfg(struct si476x_core *core,
+ enum si476x_lrout_config lrout)
+{
+ u8 resp[CMD_ANA_AUDIO_PIN_CFG_NRESP];
+ const u8 args[CMD_ANA_AUDIO_PIN_CFG_NARGS] = {
+ PIN_CFG_BYTE(lrout),
+ };
+
+ return si476x_core_send_command(core, CMD_ANA_AUDIO_PIN_CFG,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_ana_audio_pin_cfg);
+
+
+/**
+ * si476x_cmd_intb_pin_cfg - send 'INTB_PIN_CFG' command to the device
+ * @core - device to send the command to
+ * @intb - INTB pin function configuration:
+ * SI476X_INTB_NOOP - do not modify the behaviour
+ * SI476X_INTB_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_INTB_DAUDIO - set pin to be a part of digital
+ * audio interface in slave mode
+ * SI476X_INTB_IRQ - set pin to be an interrupt request line
+ * @a1 - A1 pin function configuration:
+ * SI476X_A1_NOOP - do not modify the behaviour
+ * SI476X_A1_TRISTATE - put the pin in tristate condition,
+ * enable 1MOhm pulldown
+ * SI476X_A1_IRQ - set pin to be an interrupt request line
+ *
+ * Function returns 0 on success and negative error code on failure
+ */
+static int si476x_core_cmd_intb_pin_cfg_a10(struct si476x_core *core,
+ enum si476x_intb_config intb,
+ enum si476x_a1_config a1)
+{
+ u8 resp[CMD_INTB_PIN_CFG_A10_NRESP];
+ const u8 args[CMD_INTB_PIN_CFG_NARGS] = {
+ PIN_CFG_BYTE(intb),
+ PIN_CFG_BYTE(a1),
+ };
+
+ return si476x_core_send_command(core, CMD_INTB_PIN_CFG,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+}
+
+static int si476x_core_cmd_intb_pin_cfg_a20(struct si476x_core *core,
+ enum si476x_intb_config intb,
+ enum si476x_a1_config a1)
+{
+ u8 resp[CMD_INTB_PIN_CFG_A20_NRESP];
+ const u8 args[CMD_INTB_PIN_CFG_NARGS] = {
+ PIN_CFG_BYTE(intb),
+ PIN_CFG_BYTE(a1),
+ };
+
+ return si476x_core_send_command(core, CMD_INTB_PIN_CFG,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+}
+
+
+
+/**
+ * si476x_cmd_am_rsq_status - send 'AM_RSQ_STATUS' command to the
+ * device
+ * @core - device to send the command to
+ * @rsqack - if set command clears RSQINT, SNRINT, SNRLINT, RSSIHINT,
+ * RSSSILINT, BLENDINT, MULTHINT and MULTLINT
+ * @attune - when set the values in the status report are the values
+ * that were calculated at tune
+ * @cancel - abort ongoing seek/tune opertation
+ * @stcack - clear the STCINT bin in status register
+ * @report - all signal quality information retured by the command
+ * (if NULL then the output of the command is ignored)
+ *
+ * Function returns 0 on success and negative error code on failure
+ */
+int si476x_core_cmd_am_rsq_status(struct si476x_core *core,
+ struct si476x_rsq_status_args *rsqargs,
+ struct si476x_rsq_status_report *report)
+{
+ int err;
+ u8 resp[CMD_AM_RSQ_STATUS_NRESP];
+ const u8 args[CMD_AM_RSQ_STATUS_NARGS] = {
+ rsqargs->rsqack << 3 | rsqargs->attune << 2 |
+ rsqargs->cancel << 1 | rsqargs->stcack,
+ };
+
+ err = si476x_core_send_command(core, CMD_AM_RSQ_STATUS,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+ /*
+ * Besides getting received signal quality information this
+ * command can be used to just acknowledge different interrupt
+ * flags in those cases it is useless to copy and parse
+ * received data so user can pass NULL, and thus avoid
+ * unnecessary copying.
+ */
+ if (!report)
+ return err;
+
+ report->snrhint = 0b00001000 & resp[1];
+ report->snrlint = 0b00000100 & resp[1];
+ report->rssihint = 0b00000010 & resp[1];
+ report->rssilint = 0b00000001 & resp[1];
+
+ report->bltf = 0b10000000 & resp[2];
+ report->snr_ready = 0b00100000 & resp[2];
+ report->rssiready = 0b00001000 & resp[2];
+ report->afcrl = 0b00000010 & resp[2];
+ report->valid = 0b00000001 & resp[2];
+
+ report->readfreq = be16_to_cpup((__be16 *)(resp + 3));
+ report->freqoff = resp[5];
+ report->rssi = resp[6];
+ report->snr = resp[7];
+ report->lassi = resp[9];
+ report->hassi = resp[10];
+ report->mult = resp[11];
+ report->dev = resp[12];
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_am_rsq_status);
+
+int si476x_core_cmd_fm_acf_status(struct si476x_core *core,
+ struct si476x_acf_status_report *report)
+{
+ int err;
+ u8 resp[CMD_FM_ACF_STATUS_NRESP];
+ const u8 args[CMD_FM_ACF_STATUS_NARGS] = {
+ 0x0,
+ };
+
+ if (!report)
+ return -EINVAL;
+
+ err = si476x_core_send_command(core, CMD_FM_ACF_STATUS,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+ if (err < 0)
+ return err;
+
+ report->blend_int = resp[1] & SI476X_ACF_BLEND_INT;
+ report->hblend_int = resp[1] & SI476X_ACF_HIBLEND_INT;
+ report->hicut_int = resp[1] & SI476X_ACF_HICUT_INT;
+ report->chbw_int = resp[1] & SI476X_ACF_CHBW_INT;
+ report->softmute_int = resp[1] & SI476X_ACF_SOFTMUTE_INT;
+ report->smute = resp[2] & SI476X_ACF_SMUTE;
+ report->smattn = resp[3] & SI476X_ACF_SMATTN;
+ report->chbw = resp[4];
+ report->hicut = resp[5];
+ report->hiblend = resp[6];
+ report->pilot = resp[7] & SI476X_ACF_PILOT;
+ report->stblend = resp[7] & SI476X_ACF_STBLEND;
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_acf_status);
+
+int si476x_core_cmd_am_acf_status(struct si476x_core *core,
+ struct si476x_acf_status_report *report)
+{
+ int err;
+ u8 resp[CMD_AM_ACF_STATUS_NRESP];
+ const u8 args[CMD_AM_ACF_STATUS_NARGS] = {
+ 0x0,
+ };
+
+ if (!report)
+ return -EINVAL;
+
+ err = si476x_core_send_command(core, CMD_AM_ACF_STATUS,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+ if (err < 0)
+ return err;
+
+ report->blend_int = resp[1] & SI476X_ACF_BLEND_INT;
+ report->hblend_int = resp[1] & SI476X_ACF_HIBLEND_INT;
+ report->hicut_int = resp[1] & SI476X_ACF_HICUT_INT;
+ report->chbw_int = resp[1] & SI476X_ACF_CHBW_INT;
+ report->softmute_int = resp[1] & SI476X_ACF_SOFTMUTE_INT;
+ report->smute = resp[2] & SI476X_ACF_SMUTE;
+ report->smattn = resp[3] & SI476X_ACF_SMATTN;
+ report->chbw = resp[4];
+ report->hicut = resp[5];
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_am_acf_status);
+
+
+/**
+ * si476x_cmd_fm_seek_start - send 'FM_SEEK_START' command to the
+ * device
+ * @core - device to send the command to
+ * @seekup - if set the direction of the search is 'up'
+ * @wrap - if set seek wraps when hitting band limit
+ *
+ * This function begins search for a valid station. The station is
+ * considered valid when 'FM_VALID_SNR_THRESHOLD' and
+ * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria
+ * are met.
+} *
+ * Function returns 0 on success and negative error code on failure
+ */
+int si476x_core_cmd_fm_seek_start(struct si476x_core *core,
+ bool seekup, bool wrap)
+{
+ u8 resp[CMD_FM_SEEK_START_NRESP];
+ const u8 args[CMD_FM_SEEK_START_NARGS] = {
+ seekup << 3 | wrap << 2,
+ };
+
+ return si476x_cmd_tune_seek_freq(core, CMD_FM_SEEK_START,
+ args, sizeof(args),
+ resp, sizeof(resp));
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_seek_start);
+
+/**
+ * si476x_cmd_fm_rds_status - send 'FM_RDS_STATUS' command to the
+ * device
+ * @core - device to send the command to
+ * @status_only - if set the data is not removed from RDSFIFO,
+ * RDSFIFOUSED is not decremented and data in all the
+ * rest RDS data contains the last valid info received
+ * @mtfifo if set the command clears RDS receive FIFO
+ * @intack if set the command clards the RDSINT bit.
+ *
+ * Function returns 0 on success and negative error code on failure
+ */
+int si476x_core_cmd_fm_rds_status(struct si476x_core *core,
+ bool status_only,
+ bool mtfifo,
+ bool intack,
+ struct si476x_rds_status_report *report)
+{
+ int err;
+ u8 resp[CMD_FM_RDS_STATUS_NRESP];
+ const u8 args[CMD_FM_RDS_STATUS_NARGS] = {
+ status_only << 2 | mtfifo << 1 | intack,
+ };
+
+ err = si476x_core_send_command(core, CMD_FM_RDS_STATUS,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+ /*
+ * Besides getting RDS status information this command can be
+ * used to just acknowledge different interrupt flags in those
+ * cases it is useless to copy and parse received data so user
+ * can pass NULL, and thus avoid unnecessary copying.
+ */
+ if (err < 0 || report == NULL)
+ return err;
+
+ report->rdstpptyint = 0b00010000 & resp[1];
+ report->rdspiint = 0b00001000 & resp[1];
+ report->rdssyncint = 0b00000010 & resp[1];
+ report->rdsfifoint = 0b00000001 & resp[1];
+
+ report->tpptyvalid = 0b00010000 & resp[2];
+ report->pivalid = 0b00001000 & resp[2];
+ report->rdssync = 0b00000010 & resp[2];
+ report->rdsfifolost = 0b00000001 & resp[2];
+
+ report->tp = 0b00100000 & resp[3];
+ report->pty = 0b00011111 & resp[3];
+
+ report->pi = be16_to_cpup((__be16 *)(resp + 4));
+ report->rdsfifoused = resp[6];
+
+ report->ble[V4L2_RDS_BLOCK_A] = 0b11000000 & resp[7];
+ report->ble[V4L2_RDS_BLOCK_B] = 0b00110000 & resp[7];
+ report->ble[V4L2_RDS_BLOCK_C] = 0b00001100 & resp[7];
+ report->ble[V4L2_RDS_BLOCK_D] = 0b00000011 & resp[7];
+
+ report->rds[V4L2_RDS_BLOCK_A].block = V4L2_RDS_BLOCK_A;
+ report->rds[V4L2_RDS_BLOCK_A].msb = resp[8];
+ report->rds[V4L2_RDS_BLOCK_A].lsb = resp[9];
+
+ report->rds[V4L2_RDS_BLOCK_B].block = V4L2_RDS_BLOCK_B;
+ report->rds[V4L2_RDS_BLOCK_B].msb = resp[10];
+ report->rds[V4L2_RDS_BLOCK_B].lsb = resp[11];
+
+ report->rds[V4L2_RDS_BLOCK_C].block = V4L2_RDS_BLOCK_C;
+ report->rds[V4L2_RDS_BLOCK_C].msb = resp[12];
+ report->rds[V4L2_RDS_BLOCK_C].lsb = resp[13];
+
+ report->rds[V4L2_RDS_BLOCK_D].block = V4L2_RDS_BLOCK_D;
+ report->rds[V4L2_RDS_BLOCK_D].msb = resp[14];
+ report->rds[V4L2_RDS_BLOCK_D].lsb = resp[15];
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rds_status);
+
+int si476x_core_cmd_fm_rds_blockcount(struct si476x_core *core,
+ bool clear,
+ struct si476x_rds_blockcount_report *report)
+{
+ int err;
+ u8 resp[CMD_FM_RDS_BLOCKCOUNT_NRESP];
+ const u8 args[CMD_FM_RDS_BLOCKCOUNT_NARGS] = {
+ clear,
+ };
+
+ if (!report)
+ return -EINVAL;
+
+ err = si476x_core_send_command(core, CMD_FM_RDS_BLOCKCOUNT,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+
+ if (!err) {
+ report->expected = be16_to_cpup((__be16 *)(resp + 2));
+ report->received = be16_to_cpup((__be16 *)(resp + 4));
+ report->uncorrectable = be16_to_cpup((__be16 *)(resp + 6));
+ }
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rds_blockcount);
+
+int si476x_core_cmd_fm_phase_diversity(struct si476x_core *core,
+ enum si476x_phase_diversity_mode mode)
+{
+ u8 resp[CMD_FM_PHASE_DIVERSITY_NRESP];
+ const u8 args[CMD_FM_PHASE_DIVERSITY_NARGS] = {
+ mode & 0b111,
+ };
+
+ return si476x_core_send_command(core, CMD_FM_PHASE_DIVERSITY,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_phase_diversity);
+/**
+ * si476x_core_cmd_fm_phase_div_status() - get the phase diversity
+ * status
+ *
+ * @core: si476x device
+ *
+ * NOTE caller must hold core lock
+ *
+ * Function returns the value of the status bit in case of success and
+ * negative error code in case of failre.
+ */
+int si476x_core_cmd_fm_phase_div_status(struct si476x_core *core)
+{
+ int err;
+ u8 resp[CMD_FM_PHASE_DIV_STATUS_NRESP];
+
+ err = si476x_core_send_command(core, CMD_FM_PHASE_DIV_STATUS,
+ NULL, 0,
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+
+ return (err < 0) ? err : resp[1];
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_phase_div_status);
+
+
+/**
+ * si476x_cmd_am_seek_start - send 'FM_SEEK_START' command to the
+ * device
+ * @core - device to send the command to
+ * @seekup - if set the direction of the search is 'up'
+ * @wrap - if set seek wraps when hitting band limit
+ *
+ * This function begins search for a valid station. The station is
+ * considered valid when 'FM_VALID_SNR_THRESHOLD' and
+ * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria
+ * are met.
+ *
+ * Function returns 0 on success and negative error code on failure
+ */
+int si476x_core_cmd_am_seek_start(struct si476x_core *core,
+ bool seekup, bool wrap)
+{
+ u8 resp[CMD_AM_SEEK_START_NRESP];
+ const u8 args[CMD_AM_SEEK_START_NARGS] = {
+ seekup << 3 | wrap << 2,
+ };
+
+ return si476x_cmd_tune_seek_freq(core, CMD_AM_SEEK_START,
+ args, sizeof(args),
+ resp, sizeof(resp));
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_am_seek_start);
+
+
+
+static int si476x_core_cmd_power_up_a10(struct si476x_core *core,
+ struct si476x_power_up_args *puargs)
+{
+ u8 resp[CMD_POWER_UP_A10_NRESP];
+ const bool intsel = (core->pinmux.a1 == SI476X_A1_IRQ);
+ const bool ctsen = (core->client->irq != 0);
+ const u8 args[CMD_POWER_UP_A10_NARGS] = {
+ 0xF7, /* Reserved, always 0xF7 */
+ 0x3F & puargs->xcload, /* First two bits are reserved to be
+ * zeros */
+ ctsen << 7 | intsel << 6 | 0x07, /* Last five bits
+ * are reserved to
+ * be written as 0x7 */
+ puargs->func << 4 | puargs->freq,
+ 0x11, /* Reserved, always 0x11 */
+ };
+
+ return si476x_core_send_command(core, CMD_POWER_UP,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_TIMEOUT_POWER_UP);
+}
+
+static int si476x_core_cmd_power_up_a20(struct si476x_core *core,
+ struct si476x_power_up_args *puargs)
+{
+ u8 resp[CMD_POWER_UP_A20_NRESP];
+ const bool intsel = (core->pinmux.a1 == SI476X_A1_IRQ);
+ const bool ctsen = (core->client->irq != 0);
+ const u8 args[CMD_POWER_UP_A20_NARGS] = {
+ puargs->ibias6x << 7 | puargs->xstart,
+ 0x3F & puargs->xcload, /* First two bits are reserved to be
+ * zeros */
+ ctsen << 7 | intsel << 6 | puargs->fastboot << 5 |
+ puargs->xbiashc << 3 | puargs->xbias,
+ puargs->func << 4 | puargs->freq,
+ 0x10 | puargs->xmode,
+ };
+
+ return si476x_core_send_command(core, CMD_POWER_UP,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_TIMEOUT_POWER_UP);
+}
+
+static int si476x_core_cmd_power_down_a10(struct si476x_core *core,
+ struct si476x_power_down_args *pdargs)
+{
+ u8 resp[CMD_POWER_DOWN_A10_NRESP];
+
+ return si476x_core_send_command(core, CMD_POWER_DOWN,
+ NULL, 0,
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+}
+
+static int si476x_core_cmd_power_down_a20(struct si476x_core *core,
+ struct si476x_power_down_args *pdargs)
+{
+ u8 resp[CMD_POWER_DOWN_A20_NRESP];
+ const u8 args[CMD_POWER_DOWN_A20_NARGS] = {
+ pdargs->xosc,
+ };
+ return si476x_core_send_command(core, CMD_POWER_DOWN,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+}
+
+static int si476x_core_cmd_am_tune_freq_a10(struct si476x_core *core,
+ struct si476x_tune_freq_args *tuneargs)
+{
+
+ const int am_freq = tuneargs->freq;
+ u8 resp[CMD_AM_TUNE_FREQ_NRESP];
+ const u8 args[CMD_AM_TUNE_FREQ_NARGS] = {
+ (tuneargs->hd << 6),
+ msb(am_freq),
+ lsb(am_freq),
+ };
+
+ return si476x_cmd_tune_seek_freq(core, CMD_AM_TUNE_FREQ, args,
+ sizeof(args),
+ resp, sizeof(resp));
+}
+
+static int si476x_core_cmd_am_tune_freq_a20(struct si476x_core *core,
+ struct si476x_tune_freq_args *tuneargs)
+{
+ const int am_freq = tuneargs->freq;
+ u8 resp[CMD_AM_TUNE_FREQ_NRESP];
+ const u8 args[CMD_AM_TUNE_FREQ_NARGS] = {
+ (tuneargs->zifsr << 6) | (tuneargs->injside & 0b11),
+ msb(am_freq),
+ lsb(am_freq),
+ };
+
+ return si476x_cmd_tune_seek_freq(core, CMD_AM_TUNE_FREQ,
+ args, sizeof(args),
+ resp, sizeof(resp));
+}
+
+static int si476x_core_cmd_fm_rsq_status_a10(struct si476x_core *core,
+ struct si476x_rsq_status_args *rsqargs,
+ struct si476x_rsq_status_report *report)
+{
+ int err;
+ u8 resp[CMD_FM_RSQ_STATUS_A10_NRESP];
+ const u8 args[CMD_FM_RSQ_STATUS_A10_NARGS] = {
+ rsqargs->rsqack << 3 | rsqargs->attune << 2 |
+ rsqargs->cancel << 1 | rsqargs->stcack,
+ };
+
+ err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+ /*
+ * Besides getting received signal quality information this
+ * command can be used to just acknowledge different interrupt
+ * flags in those cases it is useless to copy and parse
+ * received data so user can pass NULL, and thus avoid
+ * unnecessary copying.
+ */
+ if (err < 0 || report == NULL)
+ return err;
+
+ report->multhint = 0b10000000 & resp[1];
+ report->multlint = 0b01000000 & resp[1];
+ report->snrhint = 0b00001000 & resp[1];
+ report->snrlint = 0b00000100 & resp[1];
+ report->rssihint = 0b00000010 & resp[1];
+ report->rssilint = 0b00000001 & resp[1];
+
+ report->bltf = 0b10000000 & resp[2];
+ report->snr_ready = 0b00100000 & resp[2];
+ report->rssiready = 0b00001000 & resp[2];
+ report->afcrl = 0b00000010 & resp[2];
+ report->valid = 0b00000001 & resp[2];
+
+ report->readfreq = be16_to_cpup((__be16 *)(resp + 3));
+ report->freqoff = resp[5];
+ report->rssi = resp[6];
+ report->snr = resp[7];
+ report->lassi = resp[9];
+ report->hassi = resp[10];
+ report->mult = resp[11];
+ report->dev = resp[12];
+ report->readantcap = be16_to_cpup((__be16 *)(resp + 13));
+ report->assi = resp[15];
+ report->usn = resp[16];
+
+ return err;
+}
+
+static int si476x_core_cmd_fm_rsq_status_a20(struct si476x_core *core,
+ struct si476x_rsq_status_args *rsqargs,
+ struct si476x_rsq_status_report *report)
+{
+ int err;
+ u8 resp[CMD_FM_RSQ_STATUS_A10_NRESP];
+ const u8 args[CMD_FM_RSQ_STATUS_A30_NARGS] = {
+ rsqargs->primary << 4 | rsqargs->rsqack << 3 |
+ rsqargs->attune << 2 | rsqargs->cancel << 1 |
+ rsqargs->stcack,
+ };
+
+ err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+ /*
+ * Besides getting received signal quality information this
+ * command can be used to just acknowledge different interrupt
+ * flags in those cases it is useless to copy and parse
+ * received data so user can pass NULL, and thus avoid
+ * unnecessary copying.
+ */
+ if (err < 0 || report == NULL)
+ return err;
+
+ report->multhint = 0b10000000 & resp[1];
+ report->multlint = 0b01000000 & resp[1];
+ report->snrhint = 0b00001000 & resp[1];
+ report->snrlint = 0b00000100 & resp[1];
+ report->rssihint = 0b00000010 & resp[1];
+ report->rssilint = 0b00000001 & resp[1];
+
+ report->bltf = 0b10000000 & resp[2];
+ report->snr_ready = 0b00100000 & resp[2];
+ report->rssiready = 0b00001000 & resp[2];
+ report->afcrl = 0b00000010 & resp[2];
+ report->valid = 0b00000001 & resp[2];
+
+ report->readfreq = be16_to_cpup((__be16 *)(resp + 3));
+ report->freqoff = resp[5];
+ report->rssi = resp[6];
+ report->snr = resp[7];
+ report->lassi = resp[9];
+ report->hassi = resp[10];
+ report->mult = resp[11];
+ report->dev = resp[12];
+ report->readantcap = be16_to_cpup((__be16 *)(resp + 13));
+ report->assi = resp[15];
+ report->usn = resp[16];
+
+ return err;
+}
+
+
+static int si476x_core_cmd_fm_rsq_status_a30(struct si476x_core *core,
+ struct si476x_rsq_status_args *rsqargs,
+ struct si476x_rsq_status_report *report)
+{
+ int err;
+ u8 resp[CMD_FM_RSQ_STATUS_A30_NRESP];
+ const u8 args[CMD_FM_RSQ_STATUS_A30_NARGS] = {
+ rsqargs->primary << 4 | rsqargs->rsqack << 3 |
+ rsqargs->attune << 2 | rsqargs->cancel << 1 |
+ rsqargs->stcack,
+ };
+
+ err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS,
+ args, ARRAY_SIZE(args),
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+ /*
+ * Besides getting received signal quality information this
+ * command can be used to just acknowledge different interrupt
+ * flags in those cases it is useless to copy and parse
+ * received data so user can pass NULL, and thus avoid
+ * unnecessary copying.
+ */
+ if (err < 0 || report == NULL)
+ return err;
+
+ report->multhint = 0b10000000 & resp[1];
+ report->multlint = 0b01000000 & resp[1];
+ report->snrhint = 0b00001000 & resp[1];
+ report->snrlint = 0b00000100 & resp[1];
+ report->rssihint = 0b00000010 & resp[1];
+ report->rssilint = 0b00000001 & resp[1];
+
+ report->bltf = 0b10000000 & resp[2];
+ report->snr_ready = 0b00100000 & resp[2];
+ report->rssiready = 0b00001000 & resp[2];
+ report->injside = 0b00000100 & resp[2];
+ report->afcrl = 0b00000010 & resp[2];
+ report->valid = 0b00000001 & resp[2];
+
+ report->readfreq = be16_to_cpup((__be16 *)(resp + 3));
+ report->freqoff = resp[5];
+ report->rssi = resp[6];
+ report->snr = resp[7];
+ report->issi = resp[8];
+ report->lassi = resp[9];
+ report->hassi = resp[10];
+ report->mult = resp[11];
+ report->dev = resp[12];
+ report->readantcap = be16_to_cpup((__be16 *)(resp + 13));
+ report->assi = resp[15];
+ report->usn = resp[16];
+
+ report->pilotdev = resp[17];
+ report->rdsdev = resp[18];
+ report->assidev = resp[19];
+ report->strongdev = resp[20];
+ report->rdspi = be16_to_cpup((__be16 *)(resp + 21));
+
+ return err;
+}
+
+static int si476x_core_cmd_fm_tune_freq_a10(struct si476x_core *core,
+ struct si476x_tune_freq_args *tuneargs)
+{
+ u8 resp[CMD_FM_TUNE_FREQ_NRESP];
+ const u8 args[CMD_FM_TUNE_FREQ_A10_NARGS] = {
+ (tuneargs->hd << 6) | (tuneargs->tunemode << 4)
+ | (tuneargs->smoothmetrics << 2),
+ msb(tuneargs->freq),
+ lsb(tuneargs->freq),
+ msb(tuneargs->antcap),
+ lsb(tuneargs->antcap)
+ };
+
+ return si476x_cmd_tune_seek_freq(core, CMD_FM_TUNE_FREQ,
+ args, sizeof(args),
+ resp, sizeof(resp));
+}
+
+static int si476x_core_cmd_fm_tune_freq_a20(struct si476x_core *core,
+ struct si476x_tune_freq_args *tuneargs)
+{
+ u8 resp[CMD_FM_TUNE_FREQ_NRESP];
+ const u8 args[CMD_FM_TUNE_FREQ_A20_NARGS] = {
+ (tuneargs->hd << 6) | (tuneargs->tunemode << 4)
+ | (tuneargs->smoothmetrics << 2) | (tuneargs->injside),
+ msb(tuneargs->freq),
+ lsb(tuneargs->freq),
+ };
+
+ return si476x_cmd_tune_seek_freq(core, CMD_FM_TUNE_FREQ,
+ args, sizeof(args),
+ resp, sizeof(resp));
+}
+
+static int si476x_core_cmd_agc_status_a20(struct si476x_core *core,
+ struct si476x_agc_status_report *report)
+{
+ int err;
+ u8 resp[CMD_AGC_STATUS_NRESP_A20];
+
+ if (!report)
+ return -EINVAL;
+
+ err = si476x_core_send_command(core, CMD_AGC_STATUS,
+ NULL, 0,
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+ if (err < 0)
+ return err;
+
+ report->mxhi = resp[1] & SI476X_AGC_MXHI;
+ report->mxlo = resp[1] & SI476X_AGC_MXLO;
+ report->lnahi = resp[1] & SI476X_AGC_LNAHI;
+ report->lnalo = resp[1] & SI476X_AGC_LNALO;
+ report->fmagc1 = resp[2];
+ report->fmagc2 = resp[3];
+ report->pgagain = resp[4];
+ report->fmwblang = resp[5];
+
+ return err;
+}
+
+static int si476x_core_cmd_agc_status_a10(struct si476x_core *core,
+ struct si476x_agc_status_report *report)
+{
+ int err;
+ u8 resp[CMD_AGC_STATUS_NRESP_A10];
+
+ if (!report)
+ return -EINVAL;
+
+ err = si476x_core_send_command(core, CMD_AGC_STATUS,
+ NULL, 0,
+ resp, ARRAY_SIZE(resp),
+ SI476X_DEFAULT_TIMEOUT);
+ if (err < 0)
+ return err;
+
+ report->mxhi = resp[1] & SI476X_AGC_MXHI;
+ report->mxlo = resp[1] & SI476X_AGC_MXLO;
+ report->lnahi = resp[1] & SI476X_AGC_LNAHI;
+ report->lnalo = resp[1] & SI476X_AGC_LNALO;
+
+ return err;
+}
+
+typedef int (*tune_freq_func_t) (struct si476x_core *core,
+ struct si476x_tune_freq_args *tuneargs);
+
+static struct {
+ int (*power_up) (struct si476x_core *,
+ struct si476x_power_up_args *);
+ int (*power_down) (struct si476x_core *,
+ struct si476x_power_down_args *);
+
+ tune_freq_func_t fm_tune_freq;
+ tune_freq_func_t am_tune_freq;
+
+ int (*fm_rsq_status)(struct si476x_core *,
+ struct si476x_rsq_status_args *,
+ struct si476x_rsq_status_report *);
+
+ int (*agc_status)(struct si476x_core *,
+ struct si476x_agc_status_report *);
+ int (*intb_pin_cfg)(struct si476x_core *core,
+ enum si476x_intb_config intb,
+ enum si476x_a1_config a1);
+} si476x_cmds_vtable[] = {
+ [SI476X_REVISION_A10] = {
+ .power_up = si476x_core_cmd_power_up_a10,
+ .power_down = si476x_core_cmd_power_down_a10,
+ .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a10,
+ .am_tune_freq = si476x_core_cmd_am_tune_freq_a10,
+ .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a10,
+ .agc_status = si476x_core_cmd_agc_status_a10,
+ .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a10,
+ },
+ [SI476X_REVISION_A20] = {
+ .power_up = si476x_core_cmd_power_up_a20,
+ .power_down = si476x_core_cmd_power_down_a20,
+ .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a20,
+ .am_tune_freq = si476x_core_cmd_am_tune_freq_a20,
+ .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a20,
+ .agc_status = si476x_core_cmd_agc_status_a20,
+ .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a20,
+ },
+ [SI476X_REVISION_A30] = {
+ .power_up = si476x_core_cmd_power_up_a20,
+ .power_down = si476x_core_cmd_power_down_a20,
+ .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a20,
+ .am_tune_freq = si476x_core_cmd_am_tune_freq_a20,
+ .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a30,
+ .agc_status = si476x_core_cmd_agc_status_a20,
+ .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a20,
+ },
+};
+
+int si476x_core_cmd_power_up(struct si476x_core *core,
+ struct si476x_power_up_args *args)
+{
+ BUG_ON(core->revision > SI476X_REVISION_A30 ||
+ core->revision == -1);
+ return si476x_cmds_vtable[core->revision].power_up(core, args);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_power_up);
+
+int si476x_core_cmd_power_down(struct si476x_core *core,
+ struct si476x_power_down_args *args)
+{
+ BUG_ON(core->revision > SI476X_REVISION_A30 ||
+ core->revision == -1);
+ return si476x_cmds_vtable[core->revision].power_down(core, args);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_power_down);
+
+int si476x_core_cmd_fm_tune_freq(struct si476x_core *core,
+ struct si476x_tune_freq_args *args)
+{
+ BUG_ON(core->revision > SI476X_REVISION_A30 ||
+ core->revision == -1);
+ return si476x_cmds_vtable[core->revision].fm_tune_freq(core, args);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_tune_freq);
+
+int si476x_core_cmd_am_tune_freq(struct si476x_core *core,
+ struct si476x_tune_freq_args *args)
+{
+ BUG_ON(core->revision > SI476X_REVISION_A30 ||
+ core->revision == -1);
+ return si476x_cmds_vtable[core->revision].am_tune_freq(core, args);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_am_tune_freq);
+
+int si476x_core_cmd_fm_rsq_status(struct si476x_core *core,
+ struct si476x_rsq_status_args *args,
+ struct si476x_rsq_status_report *report)
+
+{
+ BUG_ON(core->revision > SI476X_REVISION_A30 ||
+ core->revision == -1);
+ return si476x_cmds_vtable[core->revision].fm_rsq_status(core, args,
+ report);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rsq_status);
+
+int si476x_core_cmd_agc_status(struct si476x_core *core,
+ struct si476x_agc_status_report *report)
+
+{
+ BUG_ON(core->revision > SI476X_REVISION_A30 ||
+ core->revision == -1);
+ return si476x_cmds_vtable[core->revision].agc_status(core, report);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_agc_status);
+
+int si476x_core_cmd_intb_pin_cfg(struct si476x_core *core,
+ enum si476x_intb_config intb,
+ enum si476x_a1_config a1)
+{
+ BUG_ON(core->revision > SI476X_REVISION_A30 ||
+ core->revision == -1);
+
+ return si476x_cmds_vtable[core->revision].intb_pin_cfg(core, intb, a1);
+}
+EXPORT_SYMBOL_GPL(si476x_core_cmd_intb_pin_cfg);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Andrey Smirnov <[email protected]>");
+MODULE_DESCRIPTION("API for command exchange for si476x");
--
1.7.10.4

2013-04-18 17:28:10

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [PATCH v9 00/12] Driver for Si476x series of chips

Em Thu, 18 Apr 2013 09:58:26 -0700
Andrey Smirnov <[email protected]> escreveu:

> Driver for Si476x series of chips
>
> This is a eight version of the patchset originaly posted here:
> https://lkml.org/lkml/2012/9/13/590
>
> Second version of the patch was posted here:
> https://lkml.org/lkml/2012/10/5/598
>
> Third version of the patch was posted here:
> https://lkml.org/lkml/2012/10/23/510
>
> Fourth version of the patch was posted here:
> https://lkml.org/lkml/2013/2/18/572
>
> Fifth version of the patch was posted here:
> https://lkml.org/lkml/2013/2/26/45
>
> Sixth version of the patch was posted here:
> https://lkml.org/lkml/2013/2/26/257
>
> Seventh version of the patch was posted here:
> https://lkml.org/lkml/2013/2/27/22
>
> Eighth version of the patch was posted here:
> https://lkml.org/lkml/2013/3/26/891
>
> To save everyone's time I'll repost the original description of it:
>
> This patchset contains a driver for a Silicon Laboratories 476x series
> of radio tuners. The driver itself is implemented as an MFD devices
> comprised of three parts:
> 1. Core device that provides all the other devices with basic
> functionality and locking scheme.
> 2. Radio device that translates between V4L2 subsystem requests into
> Core device commands.
> 3. Codec device that does similar to the earlier described task, but
> for ALSA SoC subsystem.
>
> v9 of this driver has following changes:
> - MFD part of the driver no longer depends on the header file added
> by the radio driver(media/si476x.h) which should potential
> restore the bisectability of the patches
>
> Mauro, I am not sure if you reverted changes in patches 5 - 7, so I am
> including them just in case.

No, I didn't revert all patches. I just reverted two patches: the
last one, and the one that Samuel asked me.

Please rebase the remaining drivers/media patch(es) on the top of my tree,
or on the top of linux-next.

Regards,
Mauro

>
> Hans, some of the patches you gave your ACK to were changed, but since
> the only thing changed is the location of the original code(it was
> rearranged into different files) I did not remove your ACKs from the
> new commits. I hope you don't mind, but if you do, let me know and
> I'll post an updated version of the patchset so it would be clear that
> it is not ready to be merged.
>
> Please note, taht patch #12 is the modified version of
> https://patchwork-mail.kernel.org/patch/2420751/
> It _was not_ ACKEd by anyone.
>
> Samuel, I couldn't just move media/si476x.h to mfd patches because it
> would also break bisectability since media/si476x.h depends on patch
> #8 in this patchset(whcih is the change that should go through 'media' tree)
> But I rearranged definitions and there shouldn't be any dependencies on
> media patches in MFD part.
>
> Andrey Smirnov (10):
> mfd: Add commands abstraction layer for SI476X MFD
> mfd: Add the main bulk of core driver for SI476x code
> mfd: Add chip properties handling code for SI476X MFD
> mfd: Add header files and Kbuild plumbing for SI476x MFD core
> v4l2: Fix the type of V4L2_CID_TUNE_PREEMPHASIS in the documentation
> v4l2: Add standard controls for FM receivers
> v4l2: Add documentation for the FM RX controls
> v4l2: Add private controls base for SI476X
> v4l2: Add a V4L2 driver for SI476X MFD
> radio-si476x: Fix incorrect pointer checking
>
> Hans Verkuil (1):
> si476x: Fix some config dependencies and a compile warnings
>
> Mauro Carvalho Chehab (1):
> radio-si476x: vidioc_s* now uses a const parameter
>
> Documentation/DocBook/media/v4l/compat.xml | 3 +
> Documentation/DocBook/media/v4l/controls.xml | 74 +-
> .../DocBook/media/v4l/vidioc-g-ext-ctrls.xml | 9 +
> Documentation/video4linux/si476x.txt | 187 +++
> drivers/media/radio/Kconfig | 17 +
> drivers/media/radio/Makefile | 1 +
> drivers/media/radio/radio-si476x.c | 1575 ++++++++++++++++++++
> drivers/media/v4l2-core/v4l2-ctrls.c | 14 +-
> drivers/mfd/Kconfig | 13 +
> drivers/mfd/Makefile | 4 +
> drivers/mfd/si476x-cmd.c | 1553 +++++++++++++++++++
> drivers/mfd/si476x-i2c.c | 886 +++++++++++
> drivers/mfd/si476x-prop.c | 242 +++
> include/linux/mfd/si476x-core.h | 533 +++++++
> include/linux/mfd/si476x-platform.h | 267 ++++
> include/linux/mfd/si476x-reports.h | 163 ++
> include/media/si476x.h | 37 +
> include/uapi/linux/v4l2-controls.h | 17 +
> 18 files changed, 5591 insertions(+), 4 deletions(-)
> create mode 100644 Documentation/video4linux/si476x.txt
> create mode 100644 drivers/media/radio/radio-si476x.c
> create mode 100644 drivers/mfd/si476x-cmd.c
> create mode 100644 drivers/mfd/si476x-i2c.c
> create mode 100644 drivers/mfd/si476x-prop.c
> create mode 100644 include/linux/mfd/si476x-core.h
> create mode 100644 include/linux/mfd/si476x-platform.h
> create mode 100644 include/linux/mfd/si476x-reports.h
> create mode 100644 include/media/si476x.h
>


--

Cheers,
Mauro

2013-04-18 17:45:55

by Samuel Ortiz

[permalink] [raw]
Subject: Re: [PATCH v9 00/12] Driver for Si476x series of chips

On Thu, Apr 18, 2013 at 02:28:00PM -0300, Mauro Carvalho Chehab wrote:
> Em Thu, 18 Apr 2013 09:58:26 -0700
> Andrey Smirnov <[email protected]> escreveu:
>
> > Driver for Si476x series of chips
> >
> > This is a eight version of the patchset originaly posted here:
> > https://lkml.org/lkml/2012/9/13/590
> >
> > Second version of the patch was posted here:
> > https://lkml.org/lkml/2012/10/5/598
> >
> > Third version of the patch was posted here:
> > https://lkml.org/lkml/2012/10/23/510
> >
> > Fourth version of the patch was posted here:
> > https://lkml.org/lkml/2013/2/18/572
> >
> > Fifth version of the patch was posted here:
> > https://lkml.org/lkml/2013/2/26/45
> >
> > Sixth version of the patch was posted here:
> > https://lkml.org/lkml/2013/2/26/257
> >
> > Seventh version of the patch was posted here:
> > https://lkml.org/lkml/2013/2/27/22
> >
> > Eighth version of the patch was posted here:
> > https://lkml.org/lkml/2013/3/26/891
> >
> > To save everyone's time I'll repost the original description of it:
> >
> > This patchset contains a driver for a Silicon Laboratories 476x series
> > of radio tuners. The driver itself is implemented as an MFD devices
> > comprised of three parts:
> > 1. Core device that provides all the other devices with basic
> > functionality and locking scheme.
> > 2. Radio device that translates between V4L2 subsystem requests into
> > Core device commands.
> > 3. Codec device that does similar to the earlier described task, but
> > for ALSA SoC subsystem.
> >
> > v9 of this driver has following changes:
> > - MFD part of the driver no longer depends on the header file added
> > by the radio driver(media/si476x.h) which should potential
> > restore the bisectability of the patches
> >
> > Mauro, I am not sure if you reverted changes in patches 5 - 7, so I am
> > including them just in case.
>
> No, I didn't revert all patches. I just reverted two patches: the
> last one, and the one that Samuel asked me.
Sorry I didn't have time to check your email from yesterday, but I was
actually hoping you would revert the whole patchset, then pull from my
mfd-next/topic/si476x branch to fetch the MFD bits and then apply the
v4l2/media ones (From patchset v9) on top of that.
Does that make sense to you ?

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

2013-04-18 17:48:32

by Dan Carpenter

[permalink] [raw]
Subject: Re: [PATCH 12/12] radio-si476x: Fix incorrect pointer checking

On Thu, Apr 18, 2013 at 09:58:38AM -0700, Andrey Smirnov wrote:
> Fix incorrect pointer checking and make some minor code improvements:
>
> * Remove unnecessary elements from function pointer table(vtable),
> that includes all the elements that are FM-only, this allows for not
> checking of the fucntion pointer and calling of the function
> directly(THe check if the tuner is in FM mode has to be done anyway)
>
> * Fix incorrect function pointer checking where the code would check one
> pointer to be non-NULL, but would use other pointer, which would not
> be checked.
>
> * Remove code duplication in "si476x_radio_read_rsq_blob" and
> "si476x_radio_read_rsq_primary_blob".
>
> * Add some BUG_ON statements for function pointers that should never be NULL
>
> Signed-off-by: Andrey Smirnov <[email protected]>
> Signed-off-by: Dan Carpenter <[email protected]>

This should be a Reported-by for me, probably.

regards,
dan carpenter

2013-04-18 17:56:41

by Samuel Ortiz

[permalink] [raw]
Subject: Re: [PATCH 11/12] si476x: Fix some config dependencies and a compile warnings

On Thu, Apr 18, 2013 at 09:58:37AM -0700, Andrey Smirnov wrote:
> From: Hans Verkuil <[email protected]>
>
> radio-si476x depends on SND and SND_SOC, the mfd driver should select
> REGMAP_I2C.
>
> Also fix a small compile warning in a debug message:
>
> drivers/mfd/si476x-i2c.c: In function ‘si476x_core_drain_rds_fifo’:
> drivers/mfd/si476x-i2c.c:391:4: warning: field width specifier ‘*’ expects argument of type ‘int’, but argument 4 has type ‘long unsigned int’ [-Wformat]
>
> Acked-by: Andrey Smirnov <[email protected]>
> Signed-off-by: Hans Verkuil <[email protected]>
> ---
> drivers/media/radio/Kconfig | 2 +-
> drivers/mfd/Kconfig | 1 +
> drivers/mfd/si476x-i2c.c | 2 +-
You should have merged the MFD bits from this patch into one of the first 4
patches, that are MFD related. Or at least separated those 2 changes into 2
patches...

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

2013-04-18 17:58:01

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [PATCH v9 00/12] Driver for Si476x series of chips

Em Thu, 18 Apr 2013 19:45:47 +0200
Samuel Ortiz <[email protected]> escreveu:

> On Thu, Apr 18, 2013 at 02:28:00PM -0300, Mauro Carvalho Chehab wrote:
> > Em Thu, 18 Apr 2013 09:58:26 -0700
> > Andrey Smirnov <[email protected]> escreveu:
> >
> > > Driver for Si476x series of chips
> > >
> > > This is a eight version of the patchset originaly posted here:
> > > https://lkml.org/lkml/2012/9/13/590
> > >
> > > Second version of the patch was posted here:
> > > https://lkml.org/lkml/2012/10/5/598
> > >
> > > Third version of the patch was posted here:
> > > https://lkml.org/lkml/2012/10/23/510
> > >
> > > Fourth version of the patch was posted here:
> > > https://lkml.org/lkml/2013/2/18/572
> > >
> > > Fifth version of the patch was posted here:
> > > https://lkml.org/lkml/2013/2/26/45
> > >
> > > Sixth version of the patch was posted here:
> > > https://lkml.org/lkml/2013/2/26/257
> > >
> > > Seventh version of the patch was posted here:
> > > https://lkml.org/lkml/2013/2/27/22
> > >
> > > Eighth version of the patch was posted here:
> > > https://lkml.org/lkml/2013/3/26/891
> > >
> > > To save everyone's time I'll repost the original description of it:
> > >
> > > This patchset contains a driver for a Silicon Laboratories 476x series
> > > of radio tuners. The driver itself is implemented as an MFD devices
> > > comprised of three parts:
> > > 1. Core device that provides all the other devices with basic
> > > functionality and locking scheme.
> > > 2. Radio device that translates between V4L2 subsystem requests into
> > > Core device commands.
> > > 3. Codec device that does similar to the earlier described task, but
> > > for ALSA SoC subsystem.
> > >
> > > v9 of this driver has following changes:
> > > - MFD part of the driver no longer depends on the header file added
> > > by the radio driver(media/si476x.h) which should potential
> > > restore the bisectability of the patches
> > >
> > > Mauro, I am not sure if you reverted changes in patches 5 - 7, so I am
> > > including them just in case.
> >
> > No, I didn't revert all patches. I just reverted two patches: the
> > last one, and the one that Samuel asked me.
> Sorry I didn't have time to check your email from yesterday, but I was
> actually hoping you would revert the whole patchset, then pull from my
> mfd-next/topic/si476x branch to fetch the MFD bits and then apply the
> v4l2/media ones (From patchset v9) on top of that.
> Does that make sense to you ?

I don't rebase my tree, as this would cause troubles for everybody that
relies on it.

Reverting the entire patchset is hard, as there are lots of patches after
them, and some patches touch at V4L2 core. Even reverting those
two patches hit conflicts, that I needed to manage, in order to avoid
compilation breakages.

So, I really prefer to confine the patch reversion to the absolute
minimum.

--

Cheers,
Mauro

2013-04-18 18:17:31

by Samuel Ortiz

[permalink] [raw]
Subject: Re: [PATCH v9 00/12] Driver for Si476x series of chips

On Thu, Apr 18, 2013 at 02:57:53PM -0300, Mauro Carvalho Chehab wrote:
> Em Thu, 18 Apr 2013 19:45:47 +0200
> Samuel Ortiz <[email protected]> escreveu:
>
> > On Thu, Apr 18, 2013 at 02:28:00PM -0300, Mauro Carvalho Chehab wrote:
> > > Em Thu, 18 Apr 2013 09:58:26 -0700
> > > Andrey Smirnov <[email protected]> escreveu:
> > >
> > > > Driver for Si476x series of chips
> > > >
> > > > This is a eight version of the patchset originaly posted here:
> > > > https://lkml.org/lkml/2012/9/13/590
> > > >
> > > > Second version of the patch was posted here:
> > > > https://lkml.org/lkml/2012/10/5/598
> > > >
> > > > Third version of the patch was posted here:
> > > > https://lkml.org/lkml/2012/10/23/510
> > > >
> > > > Fourth version of the patch was posted here:
> > > > https://lkml.org/lkml/2013/2/18/572
> > > >
> > > > Fifth version of the patch was posted here:
> > > > https://lkml.org/lkml/2013/2/26/45
> > > >
> > > > Sixth version of the patch was posted here:
> > > > https://lkml.org/lkml/2013/2/26/257
> > > >
> > > > Seventh version of the patch was posted here:
> > > > https://lkml.org/lkml/2013/2/27/22
> > > >
> > > > Eighth version of the patch was posted here:
> > > > https://lkml.org/lkml/2013/3/26/891
> > > >
> > > > To save everyone's time I'll repost the original description of it:
> > > >
> > > > This patchset contains a driver for a Silicon Laboratories 476x series
> > > > of radio tuners. The driver itself is implemented as an MFD devices
> > > > comprised of three parts:
> > > > 1. Core device that provides all the other devices with basic
> > > > functionality and locking scheme.
> > > > 2. Radio device that translates between V4L2 subsystem requests into
> > > > Core device commands.
> > > > 3. Codec device that does similar to the earlier described task, but
> > > > for ALSA SoC subsystem.
> > > >
> > > > v9 of this driver has following changes:
> > > > - MFD part of the driver no longer depends on the header file added
> > > > by the radio driver(media/si476x.h) which should potential
> > > > restore the bisectability of the patches
> > > >
> > > > Mauro, I am not sure if you reverted changes in patches 5 - 7, so I am
> > > > including them just in case.
> > >
> > > No, I didn't revert all patches. I just reverted two patches: the
> > > last one, and the one that Samuel asked me.
> > Sorry I didn't have time to check your email from yesterday, but I was
> > actually hoping you would revert the whole patchset, then pull from my
> > mfd-next/topic/si476x branch to fetch the MFD bits and then apply the
> > v4l2/media ones (From patchset v9) on top of that.
> > Does that make sense to you ?
>
> I don't rebase my tree, as this would cause troubles for everybody that
> relies on it.
>
> Reverting the entire patchset is hard, as there are lots of patches after
> them, and some patches touch at V4L2 core. Even reverting those
> two patches hit conflicts, that I needed to manage, in order to avoid
> compilation breakages.
>
> So, I really prefer to confine the patch reversion to the absolute
> minimum.
In that case we're left with only one solution: Leave your tree as it is (with
both patches reverted) and push the mfd/Kconfig and mfd/Makefile changes as a
3.10 fix. radio/radio-si476x.c should not build without the MFD Kconfig symbol
so we should be safe. The radio/radio-si476x.c Kconfig dependency is not
correct btw, I'll send you a patch for that.

This is an ugly solution, but the only one I can think about. I would have
appreciated some sync before you merged this jumbo patch, especially since the
bulk of it is an MFD driver.

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

2013-04-18 18:23:26

by Samuel Ortiz

[permalink] [raw]
Subject: Re: [PATCH v9 00/12] Driver for Si476x series of chips

On Thu, Apr 18, 2013 at 08:17:18PM +0200, Samuel Ortiz wrote:
> On Thu, Apr 18, 2013 at 02:57:53PM -0300, Mauro Carvalho Chehab wrote:
> > Em Thu, 18 Apr 2013 19:45:47 +0200
> > Samuel Ortiz <[email protected]> escreveu:
> >
> > > On Thu, Apr 18, 2013 at 02:28:00PM -0300, Mauro Carvalho Chehab wrote:
> > > > Em Thu, 18 Apr 2013 09:58:26 -0700
> > > > Andrey Smirnov <[email protected]> escreveu:
> > > >
> > > > > Driver for Si476x series of chips
> > > > >
> > > > > This is a eight version of the patchset originaly posted here:
> > > > > https://lkml.org/lkml/2012/9/13/590
> > > > >
> > > > > Second version of the patch was posted here:
> > > > > https://lkml.org/lkml/2012/10/5/598
> > > > >
> > > > > Third version of the patch was posted here:
> > > > > https://lkml.org/lkml/2012/10/23/510
> > > > >
> > > > > Fourth version of the patch was posted here:
> > > > > https://lkml.org/lkml/2013/2/18/572
> > > > >
> > > > > Fifth version of the patch was posted here:
> > > > > https://lkml.org/lkml/2013/2/26/45
> > > > >
> > > > > Sixth version of the patch was posted here:
> > > > > https://lkml.org/lkml/2013/2/26/257
> > > > >
> > > > > Seventh version of the patch was posted here:
> > > > > https://lkml.org/lkml/2013/2/27/22
> > > > >
> > > > > Eighth version of the patch was posted here:
> > > > > https://lkml.org/lkml/2013/3/26/891
> > > > >
> > > > > To save everyone's time I'll repost the original description of it:
> > > > >
> > > > > This patchset contains a driver for a Silicon Laboratories 476x series
> > > > > of radio tuners. The driver itself is implemented as an MFD devices
> > > > > comprised of three parts:
> > > > > 1. Core device that provides all the other devices with basic
> > > > > functionality and locking scheme.
> > > > > 2. Radio device that translates between V4L2 subsystem requests into
> > > > > Core device commands.
> > > > > 3. Codec device that does similar to the earlier described task, but
> > > > > for ALSA SoC subsystem.
> > > > >
> > > > > v9 of this driver has following changes:
> > > > > - MFD part of the driver no longer depends on the header file added
> > > > > by the radio driver(media/si476x.h) which should potential
> > > > > restore the bisectability of the patches
> > > > >
> > > > > Mauro, I am not sure if you reverted changes in patches 5 - 7, so I am
> > > > > including them just in case.
> > > >
> > > > No, I didn't revert all patches. I just reverted two patches: the
> > > > last one, and the one that Samuel asked me.
> > > Sorry I didn't have time to check your email from yesterday, but I was
> > > actually hoping you would revert the whole patchset, then pull from my
> > > mfd-next/topic/si476x branch to fetch the MFD bits and then apply the
> > > v4l2/media ones (From patchset v9) on top of that.
> > > Does that make sense to you ?
> >
> > I don't rebase my tree, as this would cause troubles for everybody that
> > relies on it.
> >
> > Reverting the entire patchset is hard, as there are lots of patches after
> > them, and some patches touch at V4L2 core. Even reverting those
> > two patches hit conflicts, that I needed to manage, in order to avoid
> > compilation breakages.
> >
> > So, I really prefer to confine the patch reversion to the absolute
> > minimum.
> In that case we're left with only one solution: Leave your tree as it is (with
> both patches reverted) and push the mfd/Kconfig and mfd/Makefile changes as a
> 3.10 fix. radio/radio-si476x.c should not build without the MFD Kconfig symbol
> so we should be safe.
You reverted that one, I think this was not needed as it would not build
without the MFD symbol. Any other reason why you reverted it ?

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

2013-04-18 20:27:56

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [PATCH v9 00/12] Driver for Si476x series of chips

Em Thu, 18 Apr 2013 20:23:05 +0200
Samuel Ortiz <[email protected]> escreveu:

> On Thu, Apr 18, 2013 at 08:17:18PM +0200, Samuel Ortiz wrote:
> > On Thu, Apr 18, 2013 at 02:57:53PM -0300, Mauro Carvalho Chehab wrote:
> > > Em Thu, 18 Apr 2013 19:45:47 +0200
> > > Samuel Ortiz <[email protected]> escreveu:
> > >
> > > > On Thu, Apr 18, 2013 at 02:28:00PM -0300, Mauro Carvalho Chehab wrote:
> > > > > Em Thu, 18 Apr 2013 09:58:26 -0700
> > > > > Andrey Smirnov <[email protected]> escreveu:
> > > > >
> > > > > > Driver for Si476x series of chips
> > > > > >
> > > > > > This is a eight version of the patchset originaly posted here:
> > > > > > https://lkml.org/lkml/2012/9/13/590
> > > > > >
> > > > > > Second version of the patch was posted here:
> > > > > > https://lkml.org/lkml/2012/10/5/598
> > > > > >
> > > > > > Third version of the patch was posted here:
> > > > > > https://lkml.org/lkml/2012/10/23/510
> > > > > >
> > > > > > Fourth version of the patch was posted here:
> > > > > > https://lkml.org/lkml/2013/2/18/572
> > > > > >
> > > > > > Fifth version of the patch was posted here:
> > > > > > https://lkml.org/lkml/2013/2/26/45
> > > > > >
> > > > > > Sixth version of the patch was posted here:
> > > > > > https://lkml.org/lkml/2013/2/26/257
> > > > > >
> > > > > > Seventh version of the patch was posted here:
> > > > > > https://lkml.org/lkml/2013/2/27/22
> > > > > >
> > > > > > Eighth version of the patch was posted here:
> > > > > > https://lkml.org/lkml/2013/3/26/891
> > > > > >
> > > > > > To save everyone's time I'll repost the original description of it:
> > > > > >
> > > > > > This patchset contains a driver for a Silicon Laboratories 476x series
> > > > > > of radio tuners. The driver itself is implemented as an MFD devices
> > > > > > comprised of three parts:
> > > > > > 1. Core device that provides all the other devices with basic
> > > > > > functionality and locking scheme.
> > > > > > 2. Radio device that translates between V4L2 subsystem requests into
> > > > > > Core device commands.
> > > > > > 3. Codec device that does similar to the earlier described task, but
> > > > > > for ALSA SoC subsystem.
> > > > > >
> > > > > > v9 of this driver has following changes:
> > > > > > - MFD part of the driver no longer depends on the header file added
> > > > > > by the radio driver(media/si476x.h) which should potential
> > > > > > restore the bisectability of the patches
> > > > > >
> > > > > > Mauro, I am not sure if you reverted changes in patches 5 - 7, so I am
> > > > > > including them just in case.
> > > > >
> > > > > No, I didn't revert all patches. I just reverted two patches: the
> > > > > last one, and the one that Samuel asked me.
> > > > Sorry I didn't have time to check your email from yesterday, but I was
> > > > actually hoping you would revert the whole patchset, then pull from my
> > > > mfd-next/topic/si476x branch to fetch the MFD bits and then apply the
> > > > v4l2/media ones (From patchset v9) on top of that.
> > > > Does that make sense to you ?
> > >
> > > I don't rebase my tree, as this would cause troubles for everybody that
> > > relies on it.
> > >
> > > Reverting the entire patchset is hard, as there are lots of patches after
> > > them, and some patches touch at V4L2 core. Even reverting those
> > > two patches hit conflicts, that I needed to manage, in order to avoid
> > > compilation breakages.
> > >
> > > So, I really prefer to confine the patch reversion to the absolute
> > > minimum.
> > In that case we're left with only one solution: Leave your tree as it is (with
> > both patches reverted) and push the mfd/Kconfig and mfd/Makefile changes as a
> > 3.10 fix. radio/radio-si476x.c should not build without the MFD Kconfig symbol
> > so we should be safe.
> You reverted that one, I think this was not needed as it would not build
> without the MFD symbol. Any other reason why you reverted it ?

I reverted this one because you requested to revert changeset
3f8ec5df11aa2ad7402cfb3368532a96b63426a4:
http://git.linuxtv.org/media_tree.git/commit/33a31edd4a4b7d26b962b32decfd8ea2377eaa0d

And I reverted this one because Andrey requested to revert changeset
30bac9110455402fa8888740c6819dd3daa2666f:
http://git.linuxtv.org/media_tree.git/commit/82cd0b278fddc1c0bc7e187ff82fd0e273520233

Just reverting 3f8ec5df11aa2ad7402cfb3368532a96b63426a4 would break compilation.

After thinking a little bit, I think that the best is to apply this driver
via your tree.

So, if you're ok, i'll be applying the enclosed patch on my tree. It
basically reverts all changes caused by all si476x at drivers/mfd.
The preparation patches at V4L2 core will remain there on my tree.

So, the only thing left, from V4L2 side, is this single patch:
http://git.linuxtv.org/media_tree.git/commitdiff/30bac9110455402fa8888740c6819dd3daa2666f

As it seems that most of the left-overs are mfd, feel free
to apply the V4L2 driver via your tree with my ack:

Acked-by: Mauro Carvalho Chehab <[email protected]>

It should be noticed that there was one kABI change on my tree affects
this patch: the arguments of one of the media callbacks used there was
constifyed. So, this patch will be needed, after both trees gets merged
upstream, in order to fix a few warnings:

http://git.linuxtv.org/media_tree.git/commitdiff/38a46c2128ade2a0c6ee4438297180b09a01c309

Regards,
Mauro

-

[media] mfd/si476x: Remove the left-overs from this driver

This driver was nacked by mfd maintainer, as it causes
merge conflicts there. Remove the remaining stuff from
it, in order to allow it to be merged via his tree.

Signed-off-by: Mauro Carvalho Chehab <[email protected]>

diff --git a/drivers/mfd/si476x-cmd.c b/drivers/mfd/si476x-cmd.c
deleted file mode 100644
index 71ac2e8..0000000
--- a/drivers/mfd/si476x-cmd.c
+++ /dev/null
@@ -1,1554 +0,0 @@
-/*
- * drivers/mfd/si476x-cmd.c -- Subroutines implementing command
- * protocol of si476x series of chips
- *
- * Copyright (C) 2012 Innovative Converged Devices(ICD)
- * Copyright (C) 2013 Andrey Smirnov
- *
- * Author: Andrey Smirnov <[email protected]>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- */
-
-#include <linux/module.h>
-#include <linux/completion.h>
-#include <linux/delay.h>
-#include <linux/atomic.h>
-#include <linux/i2c.h>
-#include <linux/device.h>
-#include <linux/gpio.h>
-#include <linux/videodev2.h>
-
-#include <media/si476x.h>
-#include <linux/mfd/si476x-core.h>
-
-#define msb(x) ((u8)((u16) x >> 8))
-#define lsb(x) ((u8)((u16) x & 0x00FF))
-
-
-
-#define CMD_POWER_UP 0x01
-#define CMD_POWER_UP_A10_NRESP 1
-#define CMD_POWER_UP_A10_NARGS 5
-
-#define CMD_POWER_UP_A20_NRESP 1
-#define CMD_POWER_UP_A20_NARGS 5
-
-#define POWER_UP_DELAY_MS 110
-
-#define CMD_POWER_DOWN 0x11
-#define CMD_POWER_DOWN_A10_NRESP 1
-
-#define CMD_POWER_DOWN_A20_NRESP 1
-#define CMD_POWER_DOWN_A20_NARGS 1
-
-#define CMD_FUNC_INFO 0x12
-#define CMD_FUNC_INFO_NRESP 7
-
-#define CMD_SET_PROPERTY 0x13
-#define CMD_SET_PROPERTY_NARGS 5
-#define CMD_SET_PROPERTY_NRESP 1
-
-#define CMD_GET_PROPERTY 0x14
-#define CMD_GET_PROPERTY_NARGS 3
-#define CMD_GET_PROPERTY_NRESP 4
-
-#define CMD_AGC_STATUS 0x17
-#define CMD_AGC_STATUS_NRESP_A10 2
-#define CMD_AGC_STATUS_NRESP_A20 6
-
-#define PIN_CFG_BYTE(x) (0x7F & (x))
-#define CMD_DIG_AUDIO_PIN_CFG 0x18
-#define CMD_DIG_AUDIO_PIN_CFG_NARGS 4
-#define CMD_DIG_AUDIO_PIN_CFG_NRESP 5
-
-#define CMD_ZIF_PIN_CFG 0x19
-#define CMD_ZIF_PIN_CFG_NARGS 4
-#define CMD_ZIF_PIN_CFG_NRESP 5
-
-#define CMD_IC_LINK_GPO_CTL_PIN_CFG 0x1A
-#define CMD_IC_LINK_GPO_CTL_PIN_CFG_NARGS 4
-#define CMD_IC_LINK_GPO_CTL_PIN_CFG_NRESP 5
-
-#define CMD_ANA_AUDIO_PIN_CFG 0x1B
-#define CMD_ANA_AUDIO_PIN_CFG_NARGS 1
-#define CMD_ANA_AUDIO_PIN_CFG_NRESP 2
-
-#define CMD_INTB_PIN_CFG 0x1C
-#define CMD_INTB_PIN_CFG_NARGS 2
-#define CMD_INTB_PIN_CFG_A10_NRESP 6
-#define CMD_INTB_PIN_CFG_A20_NRESP 3
-
-#define CMD_FM_TUNE_FREQ 0x30
-#define CMD_FM_TUNE_FREQ_A10_NARGS 5
-#define CMD_FM_TUNE_FREQ_A20_NARGS 3
-#define CMD_FM_TUNE_FREQ_NRESP 1
-
-#define CMD_FM_RSQ_STATUS 0x32
-
-#define CMD_FM_RSQ_STATUS_A10_NARGS 1
-#define CMD_FM_RSQ_STATUS_A10_NRESP 17
-#define CMD_FM_RSQ_STATUS_A30_NARGS 1
-#define CMD_FM_RSQ_STATUS_A30_NRESP 23
-
-
-#define CMD_FM_SEEK_START 0x31
-#define CMD_FM_SEEK_START_NARGS 1
-#define CMD_FM_SEEK_START_NRESP 1
-
-#define CMD_FM_RDS_STATUS 0x36
-#define CMD_FM_RDS_STATUS_NARGS 1
-#define CMD_FM_RDS_STATUS_NRESP 16
-
-#define CMD_FM_RDS_BLOCKCOUNT 0x37
-#define CMD_FM_RDS_BLOCKCOUNT_NARGS 1
-#define CMD_FM_RDS_BLOCKCOUNT_NRESP 8
-
-#define CMD_FM_PHASE_DIVERSITY 0x38
-#define CMD_FM_PHASE_DIVERSITY_NARGS 1
-#define CMD_FM_PHASE_DIVERSITY_NRESP 1
-
-#define CMD_FM_PHASE_DIV_STATUS 0x39
-#define CMD_FM_PHASE_DIV_STATUS_NRESP 2
-
-#define CMD_AM_TUNE_FREQ 0x40
-#define CMD_AM_TUNE_FREQ_NARGS 3
-#define CMD_AM_TUNE_FREQ_NRESP 1
-
-#define CMD_AM_RSQ_STATUS 0x42
-#define CMD_AM_RSQ_STATUS_NARGS 1
-#define CMD_AM_RSQ_STATUS_NRESP 13
-
-#define CMD_AM_SEEK_START 0x41
-#define CMD_AM_SEEK_START_NARGS 1
-#define CMD_AM_SEEK_START_NRESP 1
-
-
-#define CMD_AM_ACF_STATUS 0x45
-#define CMD_AM_ACF_STATUS_NRESP 6
-#define CMD_AM_ACF_STATUS_NARGS 1
-
-#define CMD_FM_ACF_STATUS 0x35
-#define CMD_FM_ACF_STATUS_NRESP 8
-#define CMD_FM_ACF_STATUS_NARGS 1
-
-#define CMD_MAX_ARGS_COUNT (10)
-
-
-enum si476x_acf_status_report_bits {
- SI476X_ACF_BLEND_INT = (1 << 4),
- SI476X_ACF_HIBLEND_INT = (1 << 3),
- SI476X_ACF_HICUT_INT = (1 << 2),
- SI476X_ACF_CHBW_INT = (1 << 1),
- SI476X_ACF_SOFTMUTE_INT = (1 << 0),
-
- SI476X_ACF_SMUTE = (1 << 0),
- SI476X_ACF_SMATTN = 0b11111,
- SI476X_ACF_PILOT = (1 << 7),
- SI476X_ACF_STBLEND = ~SI476X_ACF_PILOT,
-};
-
-enum si476x_agc_status_report_bits {
- SI476X_AGC_MXHI = (1 << 5),
- SI476X_AGC_MXLO = (1 << 4),
- SI476X_AGC_LNAHI = (1 << 3),
- SI476X_AGC_LNALO = (1 << 2),
-};
-
-enum si476x_errors {
- SI476X_ERR_BAD_COMMAND = 0x10,
- SI476X_ERR_BAD_ARG1 = 0x11,
- SI476X_ERR_BAD_ARG2 = 0x12,
- SI476X_ERR_BAD_ARG3 = 0x13,
- SI476X_ERR_BAD_ARG4 = 0x14,
- SI476X_ERR_BUSY = 0x18,
- SI476X_ERR_BAD_INTERNAL_MEMORY = 0x20,
- SI476X_ERR_BAD_PATCH = 0x30,
- SI476X_ERR_BAD_BOOT_MODE = 0x31,
- SI476X_ERR_BAD_PROPERTY = 0x40,
-};
-
-static int si476x_core_parse_and_nag_about_error(struct si476x_core *core)
-{
- int err;
- char *cause;
- u8 buffer[2];
-
- if (core->revision != SI476X_REVISION_A10) {
- err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV,
- buffer, sizeof(buffer));
- if (err == sizeof(buffer)) {
- switch (buffer[1]) {
- case SI476X_ERR_BAD_COMMAND:
- cause = "Bad command";
- err = -EINVAL;
- break;
- case SI476X_ERR_BAD_ARG1:
- cause = "Bad argument #1";
- err = -EINVAL;
- break;
- case SI476X_ERR_BAD_ARG2:
- cause = "Bad argument #2";
- err = -EINVAL;
- break;
- case SI476X_ERR_BAD_ARG3:
- cause = "Bad argument #3";
- err = -EINVAL;
- break;
- case SI476X_ERR_BAD_ARG4:
- cause = "Bad argument #4";
- err = -EINVAL;
- break;
- case SI476X_ERR_BUSY:
- cause = "Chip is busy";
- err = -EBUSY;
- break;
- case SI476X_ERR_BAD_INTERNAL_MEMORY:
- cause = "Bad internal memory";
- err = -EIO;
- break;
- case SI476X_ERR_BAD_PATCH:
- cause = "Bad patch";
- err = -EINVAL;
- break;
- case SI476X_ERR_BAD_BOOT_MODE:
- cause = "Bad boot mode";
- err = -EINVAL;
- break;
- case SI476X_ERR_BAD_PROPERTY:
- cause = "Bad property";
- err = -EINVAL;
- break;
- default:
- cause = "Unknown";
- err = -EIO;
- }
-
- dev_err(&core->client->dev,
- "[Chip error status]: %s\n", cause);
- } else {
- dev_err(&core->client->dev,
- "Failed to fetch error code\n");
- err = (err >= 0) ? -EIO : err;
- }
- } else {
- err = -EIO;
- }
-
- return err;
-}
-
-/**
- * si476x_core_send_command() - sends a command to si476x and waits its
- * response
- * @core: si476x_device structure for the device we are
- * communicating with
- * @command: command id
- * @args: command arguments we are sending
- * @argn: actual size of @args
- * @response: buffer to place the expected response from the device
- * @respn: actual size of @response
- * @usecs: amount of time to wait before reading the response (in
- * usecs)
- *
- * Function returns 0 on succsess and negative error code on
- * failure
- */
-static int si476x_core_send_command(struct si476x_core *core,
- const u8 command,
- const u8 args[],
- const int argn,
- u8 resp[],
- const int respn,
- const int usecs)
-{
- struct i2c_client *client = core->client;
- int err;
- u8 data[CMD_MAX_ARGS_COUNT + 1];
-
- if (argn > CMD_MAX_ARGS_COUNT) {
- err = -ENOMEM;
- goto exit;
- }
-
- if (!client->adapter) {
- err = -ENODEV;
- goto exit;
- }
-
- /* First send the command and its arguments */
- data[0] = command;
- memcpy(&data[1], args, argn);
- dev_dbg(&client->dev, "Command:\n %*ph\n", argn + 1, data);
-
- err = si476x_core_i2c_xfer(core, SI476X_I2C_SEND,
- (char *) data, argn + 1);
- if (err != argn + 1) {
- dev_err(&core->client->dev,
- "Error while sending command 0x%02x\n",
- command);
- err = (err >= 0) ? -EIO : err;
- goto exit;
- }
- /* Set CTS to zero only after the command is send to avoid
- * possible racing conditions when working in polling mode */
- atomic_set(&core->cts, 0);
-
- /* if (unlikely(command == CMD_POWER_DOWN) */
- if (!wait_event_timeout(core->command,
- atomic_read(&core->cts),
- usecs_to_jiffies(usecs) + 1))
- dev_warn(&core->client->dev,
- "(%s) [CMD 0x%02x] Answer timeout.\n",
- __func__, command);
-
- /*
- When working in polling mode, for some reason the tuner will
- report CTS bit as being set in the first status byte read,
- but all the consequtive ones will return zeros until the
- tuner is actually completed the POWER_UP command. To
- workaround that we wait for second CTS to be reported
- */
- if (unlikely(!core->client->irq && command == CMD_POWER_UP)) {
- if (!wait_event_timeout(core->command,
- atomic_read(&core->cts),
- usecs_to_jiffies(usecs) + 1))
- dev_warn(&core->client->dev,
- "(%s) Power up took too much time.\n",
- __func__);
- }
-
- /* Then get the response */
- err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV, resp, respn);
- if (err != respn) {
- dev_err(&core->client->dev,
- "Error while reading response for command 0x%02x\n",
- command);
- err = (err >= 0) ? -EIO : err;
- goto exit;
- }
- dev_dbg(&client->dev, "Response:\n %*ph\n", respn, resp);
-
- err = 0;
-
- if (resp[0] & SI476X_ERR) {
- dev_err(&core->client->dev,
- "[CMD 0x%02x] Chip set error flag\n", command);
- err = si476x_core_parse_and_nag_about_error(core);
- goto exit;
- }
-
- if (!(resp[0] & SI476X_CTS))
- err = -EBUSY;
-exit:
- return err;
-}
-
-static int si476x_cmd_clear_stc(struct si476x_core *core)
-{
- int err;
- struct si476x_rsq_status_args args = {
- .primary = false,
- .rsqack = false,
- .attune = false,
- .cancel = false,
- .stcack = true,
- };
-
- switch (core->power_up_parameters.func) {
- case SI476X_FUNC_FM_RECEIVER:
- err = si476x_core_cmd_fm_rsq_status(core, &args, NULL);
- break;
- case SI476X_FUNC_AM_RECEIVER:
- err = si476x_core_cmd_am_rsq_status(core, &args, NULL);
- break;
- default:
- err = -EINVAL;
- }
-
- return err;
-}
-
-static int si476x_cmd_tune_seek_freq(struct si476x_core *core,
- uint8_t cmd,
- const uint8_t args[], size_t argn,
- uint8_t *resp, size_t respn)
-{
- int err;
-
-
- atomic_set(&core->stc, 0);
- err = si476x_core_send_command(core, cmd, args, argn, resp, respn,
- SI476X_TIMEOUT_TUNE);
- if (!err) {
- wait_event_killable(core->tuning,
- atomic_read(&core->stc));
- si476x_cmd_clear_stc(core);
- }
-
- return err;
-}
-
-/**
- * si476x_cmd_func_info() - send 'FUNC_INFO' command to the device
- * @core: device to send the command to
- * @info: struct si476x_func_info to fill all the information
- * returned by the command
- *
- * The command requests the firmware and patch version for currently
- * loaded firmware (dependent on the function of the device FM/AM/WB)
- *
- * Function returns 0 on succsess and negative error code on
- * failure
- */
-int si476x_core_cmd_func_info(struct si476x_core *core,
- struct si476x_func_info *info)
-{
- int err;
- u8 resp[CMD_FUNC_INFO_NRESP];
-
- err = si476x_core_send_command(core, CMD_FUNC_INFO,
- NULL, 0,
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-
- info->firmware.major = resp[1];
- info->firmware.minor[0] = resp[2];
- info->firmware.minor[1] = resp[3];
-
- info->patch_id = ((u16) resp[4] << 8) | resp[5];
- info->func = resp[6];
-
- return err;
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_func_info);
-
-/**
- * si476x_cmd_set_property() - send 'SET_PROPERTY' command to the device
- * @core: device to send the command to
- * @property: property address
- * @value: property value
- *
- * Function returns 0 on succsess and negative error code on
- * failure
- */
-int si476x_core_cmd_set_property(struct si476x_core *core,
- u16 property, u16 value)
-{
- u8 resp[CMD_SET_PROPERTY_NRESP];
- const u8 args[CMD_SET_PROPERTY_NARGS] = {
- 0x00,
- msb(property),
- lsb(property),
- msb(value),
- lsb(value),
- };
-
- return si476x_core_send_command(core, CMD_SET_PROPERTY,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_set_property);
-
-/**
- * si476x_cmd_get_property() - send 'GET_PROPERTY' command to the device
- * @core: device to send the command to
- * @property: property address
- *
- * Function return the value of property as u16 on success or a
- * negative error on failure
- */
-int si476x_core_cmd_get_property(struct si476x_core *core, u16 property)
-{
- int err;
- u8 resp[CMD_GET_PROPERTY_NRESP];
- const u8 args[CMD_GET_PROPERTY_NARGS] = {
- 0x00,
- msb(property),
- lsb(property),
- };
-
- err = si476x_core_send_command(core, CMD_GET_PROPERTY,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
- if (err < 0)
- return err;
- else
- return be16_to_cpup((__be16 *)(resp + 2));
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_get_property);
-
-/**
- * si476x_cmd_dig_audio_pin_cfg() - send 'DIG_AUDIO_PIN_CFG' command to
- * the device
- * @core: device to send the command to
- * @dclk: DCLK pin function configuration:
- * #SI476X_DCLK_NOOP - do not modify the behaviour
- * #SI476X_DCLK_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * #SI476X_DCLK_DAUDIO - set the pin to be a part of digital
- * audio interface
- * @dfs: DFS pin function configuration:
- * #SI476X_DFS_NOOP - do not modify the behaviour
- * #SI476X_DFS_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_DFS_DAUDIO - set the pin to be a part of digital
- * audio interface
- * @dout - DOUT pin function configuration:
- * SI476X_DOUT_NOOP - do not modify the behaviour
- * SI476X_DOUT_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_DOUT_I2S_OUTPUT - set this pin to be digital out on I2S
- * port 1
- * SI476X_DOUT_I2S_INPUT - set this pin to be digital in on I2S
- * port 1
- * @xout - XOUT pin function configuration:
- * SI476X_XOUT_NOOP - do not modify the behaviour
- * SI476X_XOUT_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_XOUT_I2S_INPUT - set this pin to be digital in on I2S
- * port 1
- * SI476X_XOUT_MODE_SELECT - set this pin to be the input that
- * selects the mode of the I2S audio
- * combiner (analog or HD)
- * [SI4761/63/65/67 Only]
- *
- * Function returns 0 on success and negative error code on failure
- */
-int si476x_core_cmd_dig_audio_pin_cfg(struct si476x_core *core,
- enum si476x_dclk_config dclk,
- enum si476x_dfs_config dfs,
- enum si476x_dout_config dout,
- enum si476x_xout_config xout)
-{
- u8 resp[CMD_DIG_AUDIO_PIN_CFG_NRESP];
- const u8 args[CMD_DIG_AUDIO_PIN_CFG_NARGS] = {
- PIN_CFG_BYTE(dclk),
- PIN_CFG_BYTE(dfs),
- PIN_CFG_BYTE(dout),
- PIN_CFG_BYTE(xout),
- };
-
- return si476x_core_send_command(core, CMD_DIG_AUDIO_PIN_CFG,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_dig_audio_pin_cfg);
-
-/**
- * si476x_cmd_zif_pin_cfg - send 'ZIF_PIN_CFG_COMMAND'
- * @core - device to send the command to
- * @iqclk - IQCL pin function configuration:
- * SI476X_IQCLK_NOOP - do not modify the behaviour
- * SI476X_IQCLK_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_IQCLK_IQ - set pin to be a part of I/Q interace
- * in master mode
- * @iqfs - IQFS pin function configuration:
- * SI476X_IQFS_NOOP - do not modify the behaviour
- * SI476X_IQFS_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_IQFS_IQ - set pin to be a part of I/Q interace
- * in master mode
- * @iout - IOUT pin function configuration:
- * SI476X_IOUT_NOOP - do not modify the behaviour
- * SI476X_IOUT_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_IOUT_OUTPUT - set pin to be I out
- * @qout - QOUT pin function configuration:
- * SI476X_QOUT_NOOP - do not modify the behaviour
- * SI476X_QOUT_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_QOUT_OUTPUT - set pin to be Q out
- *
- * Function returns 0 on success and negative error code on failure
- */
-int si476x_core_cmd_zif_pin_cfg(struct si476x_core *core,
- enum si476x_iqclk_config iqclk,
- enum si476x_iqfs_config iqfs,
- enum si476x_iout_config iout,
- enum si476x_qout_config qout)
-{
- u8 resp[CMD_ZIF_PIN_CFG_NRESP];
- const u8 args[CMD_ZIF_PIN_CFG_NARGS] = {
- PIN_CFG_BYTE(iqclk),
- PIN_CFG_BYTE(iqfs),
- PIN_CFG_BYTE(iout),
- PIN_CFG_BYTE(qout),
- };
-
- return si476x_core_send_command(core, CMD_ZIF_PIN_CFG,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_zif_pin_cfg);
-
-/**
- * si476x_cmd_ic_link_gpo_ctl_pin_cfg - send
- * 'IC_LINK_GPIO_CTL_PIN_CFG' comand to the device
- * @core - device to send the command to
- * @icin - ICIN pin function configuration:
- * SI476X_ICIN_NOOP - do not modify the behaviour
- * SI476X_ICIN_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_ICIN_GPO1_HIGH - set pin to be an output, drive it high
- * SI476X_ICIN_GPO1_LOW - set pin to be an output, drive it low
- * SI476X_ICIN_IC_LINK - set the pin to be a part of Inter-Chip link
- * @icip - ICIP pin function configuration:
- * SI476X_ICIP_NOOP - do not modify the behaviour
- * SI476X_ICIP_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_ICIP_GPO1_HIGH - set pin to be an output, drive it high
- * SI476X_ICIP_GPO1_LOW - set pin to be an output, drive it low
- * SI476X_ICIP_IC_LINK - set the pin to be a part of Inter-Chip link
- * @icon - ICON pin function configuration:
- * SI476X_ICON_NOOP - do not modify the behaviour
- * SI476X_ICON_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_ICON_I2S - set the pin to be a part of audio
- * interface in slave mode (DCLK)
- * SI476X_ICON_IC_LINK - set the pin to be a part of Inter-Chip link
- * @icop - ICOP pin function configuration:
- * SI476X_ICOP_NOOP - do not modify the behaviour
- * SI476X_ICOP_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_ICOP_I2S - set the pin to be a part of audio
- * interface in slave mode (DOUT)
- * [Si4761/63/65/67 Only]
- * SI476X_ICOP_IC_LINK - set the pin to be a part of Inter-Chip link
- *
- * Function returns 0 on success and negative error code on failure
- */
-int si476x_core_cmd_ic_link_gpo_ctl_pin_cfg(struct si476x_core *core,
- enum si476x_icin_config icin,
- enum si476x_icip_config icip,
- enum si476x_icon_config icon,
- enum si476x_icop_config icop)
-{
- u8 resp[CMD_IC_LINK_GPO_CTL_PIN_CFG_NRESP];
- const u8 args[CMD_IC_LINK_GPO_CTL_PIN_CFG_NARGS] = {
- PIN_CFG_BYTE(icin),
- PIN_CFG_BYTE(icip),
- PIN_CFG_BYTE(icon),
- PIN_CFG_BYTE(icop),
- };
-
- return si476x_core_send_command(core, CMD_IC_LINK_GPO_CTL_PIN_CFG,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_ic_link_gpo_ctl_pin_cfg);
-
-/**
- * si476x_cmd_ana_audio_pin_cfg - send 'ANA_AUDIO_PIN_CFG' to the
- * device
- * @core - device to send the command to
- * @lrout - LROUT pin function configuration:
- * SI476X_LROUT_NOOP - do not modify the behaviour
- * SI476X_LROUT_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_LROUT_AUDIO - set pin to be audio output
- * SI476X_LROUT_MPX - set pin to be MPX output
- *
- * Function returns 0 on success and negative error code on failure
- */
-int si476x_core_cmd_ana_audio_pin_cfg(struct si476x_core *core,
- enum si476x_lrout_config lrout)
-{
- u8 resp[CMD_ANA_AUDIO_PIN_CFG_NRESP];
- const u8 args[CMD_ANA_AUDIO_PIN_CFG_NARGS] = {
- PIN_CFG_BYTE(lrout),
- };
-
- return si476x_core_send_command(core, CMD_ANA_AUDIO_PIN_CFG,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_ana_audio_pin_cfg);
-
-
-/**
- * si476x_cmd_intb_pin_cfg - send 'INTB_PIN_CFG' command to the device
- * @core - device to send the command to
- * @intb - INTB pin function configuration:
- * SI476X_INTB_NOOP - do not modify the behaviour
- * SI476X_INTB_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_INTB_DAUDIO - set pin to be a part of digital
- * audio interface in slave mode
- * SI476X_INTB_IRQ - set pin to be an interrupt request line
- * @a1 - A1 pin function configuration:
- * SI476X_A1_NOOP - do not modify the behaviour
- * SI476X_A1_TRISTATE - put the pin in tristate condition,
- * enable 1MOhm pulldown
- * SI476X_A1_IRQ - set pin to be an interrupt request line
- *
- * Function returns 0 on success and negative error code on failure
- */
-static int si476x_core_cmd_intb_pin_cfg_a10(struct si476x_core *core,
- enum si476x_intb_config intb,
- enum si476x_a1_config a1)
-{
- u8 resp[CMD_INTB_PIN_CFG_A10_NRESP];
- const u8 args[CMD_INTB_PIN_CFG_NARGS] = {
- PIN_CFG_BYTE(intb),
- PIN_CFG_BYTE(a1),
- };
-
- return si476x_core_send_command(core, CMD_INTB_PIN_CFG,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-}
-
-static int si476x_core_cmd_intb_pin_cfg_a20(struct si476x_core *core,
- enum si476x_intb_config intb,
- enum si476x_a1_config a1)
-{
- u8 resp[CMD_INTB_PIN_CFG_A20_NRESP];
- const u8 args[CMD_INTB_PIN_CFG_NARGS] = {
- PIN_CFG_BYTE(intb),
- PIN_CFG_BYTE(a1),
- };
-
- return si476x_core_send_command(core, CMD_INTB_PIN_CFG,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-}
-
-
-
-/**
- * si476x_cmd_am_rsq_status - send 'AM_RSQ_STATUS' command to the
- * device
- * @core - device to send the command to
- * @rsqack - if set command clears RSQINT, SNRINT, SNRLINT, RSSIHINT,
- * RSSSILINT, BLENDINT, MULTHINT and MULTLINT
- * @attune - when set the values in the status report are the values
- * that were calculated at tune
- * @cancel - abort ongoing seek/tune opertation
- * @stcack - clear the STCINT bin in status register
- * @report - all signal quality information retured by the command
- * (if NULL then the output of the command is ignored)
- *
- * Function returns 0 on success and negative error code on failure
- */
-int si476x_core_cmd_am_rsq_status(struct si476x_core *core,
- struct si476x_rsq_status_args *rsqargs,
- struct si476x_rsq_status_report *report)
-{
- int err;
- u8 resp[CMD_AM_RSQ_STATUS_NRESP];
- const u8 args[CMD_AM_RSQ_STATUS_NARGS] = {
- rsqargs->rsqack << 3 | rsqargs->attune << 2 |
- rsqargs->cancel << 1 | rsqargs->stcack,
- };
-
- err = si476x_core_send_command(core, CMD_AM_RSQ_STATUS,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
- /*
- * Besides getting received signal quality information this
- * command can be used to just acknowledge different interrupt
- * flags in those cases it is useless to copy and parse
- * received data so user can pass NULL, and thus avoid
- * unnecessary copying.
- */
- if (!report)
- return err;
-
- report->snrhint = 0b00001000 & resp[1];
- report->snrlint = 0b00000100 & resp[1];
- report->rssihint = 0b00000010 & resp[1];
- report->rssilint = 0b00000001 & resp[1];
-
- report->bltf = 0b10000000 & resp[2];
- report->snr_ready = 0b00100000 & resp[2];
- report->rssiready = 0b00001000 & resp[2];
- report->afcrl = 0b00000010 & resp[2];
- report->valid = 0b00000001 & resp[2];
-
- report->readfreq = be16_to_cpup((__be16 *)(resp + 3));
- report->freqoff = resp[5];
- report->rssi = resp[6];
- report->snr = resp[7];
- report->lassi = resp[9];
- report->hassi = resp[10];
- report->mult = resp[11];
- report->dev = resp[12];
-
- return err;
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_am_rsq_status);
-
-int si476x_core_cmd_fm_acf_status(struct si476x_core *core,
- struct si476x_acf_status_report *report)
-{
- int err;
- u8 resp[CMD_FM_ACF_STATUS_NRESP];
- const u8 args[CMD_FM_ACF_STATUS_NARGS] = {
- 0x0,
- };
-
- if (!report)
- return -EINVAL;
-
- err = si476x_core_send_command(core, CMD_FM_ACF_STATUS,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
- if (err < 0)
- return err;
-
- report->blend_int = resp[1] & SI476X_ACF_BLEND_INT;
- report->hblend_int = resp[1] & SI476X_ACF_HIBLEND_INT;
- report->hicut_int = resp[1] & SI476X_ACF_HICUT_INT;
- report->chbw_int = resp[1] & SI476X_ACF_CHBW_INT;
- report->softmute_int = resp[1] & SI476X_ACF_SOFTMUTE_INT;
- report->smute = resp[2] & SI476X_ACF_SMUTE;
- report->smattn = resp[3] & SI476X_ACF_SMATTN;
- report->chbw = resp[4];
- report->hicut = resp[5];
- report->hiblend = resp[6];
- report->pilot = resp[7] & SI476X_ACF_PILOT;
- report->stblend = resp[7] & SI476X_ACF_STBLEND;
-
- return err;
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_acf_status);
-
-int si476x_core_cmd_am_acf_status(struct si476x_core *core,
- struct si476x_acf_status_report *report)
-{
- int err;
- u8 resp[CMD_AM_ACF_STATUS_NRESP];
- const u8 args[CMD_AM_ACF_STATUS_NARGS] = {
- 0x0,
- };
-
- if (!report)
- return -EINVAL;
-
- err = si476x_core_send_command(core, CMD_AM_ACF_STATUS,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
- if (err < 0)
- return err;
-
- report->blend_int = resp[1] & SI476X_ACF_BLEND_INT;
- report->hblend_int = resp[1] & SI476X_ACF_HIBLEND_INT;
- report->hicut_int = resp[1] & SI476X_ACF_HICUT_INT;
- report->chbw_int = resp[1] & SI476X_ACF_CHBW_INT;
- report->softmute_int = resp[1] & SI476X_ACF_SOFTMUTE_INT;
- report->smute = resp[2] & SI476X_ACF_SMUTE;
- report->smattn = resp[3] & SI476X_ACF_SMATTN;
- report->chbw = resp[4];
- report->hicut = resp[5];
-
- return err;
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_am_acf_status);
-
-
-/**
- * si476x_cmd_fm_seek_start - send 'FM_SEEK_START' command to the
- * device
- * @core - device to send the command to
- * @seekup - if set the direction of the search is 'up'
- * @wrap - if set seek wraps when hitting band limit
- *
- * This function begins search for a valid station. The station is
- * considered valid when 'FM_VALID_SNR_THRESHOLD' and
- * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria
- * are met.
-} *
- * Function returns 0 on success and negative error code on failure
- */
-int si476x_core_cmd_fm_seek_start(struct si476x_core *core,
- bool seekup, bool wrap)
-{
- u8 resp[CMD_FM_SEEK_START_NRESP];
- const u8 args[CMD_FM_SEEK_START_NARGS] = {
- seekup << 3 | wrap << 2,
- };
-
- return si476x_cmd_tune_seek_freq(core, CMD_FM_SEEK_START,
- args, sizeof(args),
- resp, sizeof(resp));
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_seek_start);
-
-/**
- * si476x_cmd_fm_rds_status - send 'FM_RDS_STATUS' command to the
- * device
- * @core - device to send the command to
- * @status_only - if set the data is not removed from RDSFIFO,
- * RDSFIFOUSED is not decremented and data in all the
- * rest RDS data contains the last valid info received
- * @mtfifo if set the command clears RDS receive FIFO
- * @intack if set the command clards the RDSINT bit.
- *
- * Function returns 0 on success and negative error code on failure
- */
-int si476x_core_cmd_fm_rds_status(struct si476x_core *core,
- bool status_only,
- bool mtfifo,
- bool intack,
- struct si476x_rds_status_report *report)
-{
- int err;
- u8 resp[CMD_FM_RDS_STATUS_NRESP];
- const u8 args[CMD_FM_RDS_STATUS_NARGS] = {
- status_only << 2 | mtfifo << 1 | intack,
- };
-
- err = si476x_core_send_command(core, CMD_FM_RDS_STATUS,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
- /*
- * Besides getting RDS status information this command can be
- * used to just acknowledge different interrupt flags in those
- * cases it is useless to copy and parse received data so user
- * can pass NULL, and thus avoid unnecessary copying.
- */
- if (err < 0 || report == NULL)
- return err;
-
- report->rdstpptyint = 0b00010000 & resp[1];
- report->rdspiint = 0b00001000 & resp[1];
- report->rdssyncint = 0b00000010 & resp[1];
- report->rdsfifoint = 0b00000001 & resp[1];
-
- report->tpptyvalid = 0b00010000 & resp[2];
- report->pivalid = 0b00001000 & resp[2];
- report->rdssync = 0b00000010 & resp[2];
- report->rdsfifolost = 0b00000001 & resp[2];
-
- report->tp = 0b00100000 & resp[3];
- report->pty = 0b00011111 & resp[3];
-
- report->pi = be16_to_cpup((__be16 *)(resp + 4));
- report->rdsfifoused = resp[6];
-
- report->ble[V4L2_RDS_BLOCK_A] = 0b11000000 & resp[7];
- report->ble[V4L2_RDS_BLOCK_B] = 0b00110000 & resp[7];
- report->ble[V4L2_RDS_BLOCK_C] = 0b00001100 & resp[7];
- report->ble[V4L2_RDS_BLOCK_D] = 0b00000011 & resp[7];
-
- report->rds[V4L2_RDS_BLOCK_A].block = V4L2_RDS_BLOCK_A;
- report->rds[V4L2_RDS_BLOCK_A].msb = resp[8];
- report->rds[V4L2_RDS_BLOCK_A].lsb = resp[9];
-
- report->rds[V4L2_RDS_BLOCK_B].block = V4L2_RDS_BLOCK_B;
- report->rds[V4L2_RDS_BLOCK_B].msb = resp[10];
- report->rds[V4L2_RDS_BLOCK_B].lsb = resp[11];
-
- report->rds[V4L2_RDS_BLOCK_C].block = V4L2_RDS_BLOCK_C;
- report->rds[V4L2_RDS_BLOCK_C].msb = resp[12];
- report->rds[V4L2_RDS_BLOCK_C].lsb = resp[13];
-
- report->rds[V4L2_RDS_BLOCK_D].block = V4L2_RDS_BLOCK_D;
- report->rds[V4L2_RDS_BLOCK_D].msb = resp[14];
- report->rds[V4L2_RDS_BLOCK_D].lsb = resp[15];
-
- return err;
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rds_status);
-
-int si476x_core_cmd_fm_rds_blockcount(struct si476x_core *core,
- bool clear,
- struct si476x_rds_blockcount_report *report)
-{
- int err;
- u8 resp[CMD_FM_RDS_BLOCKCOUNT_NRESP];
- const u8 args[CMD_FM_RDS_BLOCKCOUNT_NARGS] = {
- clear,
- };
-
- if (!report)
- return -EINVAL;
-
- err = si476x_core_send_command(core, CMD_FM_RDS_BLOCKCOUNT,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-
- if (!err) {
- report->expected = be16_to_cpup((__be16 *)(resp + 2));
- report->received = be16_to_cpup((__be16 *)(resp + 4));
- report->uncorrectable = be16_to_cpup((__be16 *)(resp + 6));
- }
-
- return err;
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rds_blockcount);
-
-int si476x_core_cmd_fm_phase_diversity(struct si476x_core *core,
- enum si476x_phase_diversity_mode mode)
-{
- u8 resp[CMD_FM_PHASE_DIVERSITY_NRESP];
- const u8 args[CMD_FM_PHASE_DIVERSITY_NARGS] = {
- mode & 0b111,
- };
-
- return si476x_core_send_command(core, CMD_FM_PHASE_DIVERSITY,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_phase_diversity);
-/**
- * si476x_core_cmd_fm_phase_div_status() - get the phase diversity
- * status
- *
- * @core: si476x device
- *
- * NOTE caller must hold core lock
- *
- * Function returns the value of the status bit in case of success and
- * negative error code in case of failre.
- */
-int si476x_core_cmd_fm_phase_div_status(struct si476x_core *core)
-{
- int err;
- u8 resp[CMD_FM_PHASE_DIV_STATUS_NRESP];
-
- err = si476x_core_send_command(core, CMD_FM_PHASE_DIV_STATUS,
- NULL, 0,
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-
- return (err < 0) ? err : resp[1];
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_phase_div_status);
-
-
-/**
- * si476x_cmd_am_seek_start - send 'FM_SEEK_START' command to the
- * device
- * @core - device to send the command to
- * @seekup - if set the direction of the search is 'up'
- * @wrap - if set seek wraps when hitting band limit
- *
- * This function begins search for a valid station. The station is
- * considered valid when 'FM_VALID_SNR_THRESHOLD' and
- * 'FM_VALID_RSSI_THRESHOLD' and 'FM_VALID_MAX_TUNE_ERROR' criteria
- * are met.
- *
- * Function returns 0 on success and negative error code on failure
- */
-int si476x_core_cmd_am_seek_start(struct si476x_core *core,
- bool seekup, bool wrap)
-{
- u8 resp[CMD_AM_SEEK_START_NRESP];
- const u8 args[CMD_AM_SEEK_START_NARGS] = {
- seekup << 3 | wrap << 2,
- };
-
- return si476x_cmd_tune_seek_freq(core, CMD_AM_SEEK_START,
- args, sizeof(args),
- resp, sizeof(resp));
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_am_seek_start);
-
-
-
-static int si476x_core_cmd_power_up_a10(struct si476x_core *core,
- struct si476x_power_up_args *puargs)
-{
- u8 resp[CMD_POWER_UP_A10_NRESP];
- const bool intsel = (core->pinmux.a1 == SI476X_A1_IRQ);
- const bool ctsen = (core->client->irq != 0);
- const u8 args[CMD_POWER_UP_A10_NARGS] = {
- 0xF7, /* Reserved, always 0xF7 */
- 0x3F & puargs->xcload, /* First two bits are reserved to be
- * zeros */
- ctsen << 7 | intsel << 6 | 0x07, /* Last five bits
- * are reserved to
- * be written as 0x7 */
- puargs->func << 4 | puargs->freq,
- 0x11, /* Reserved, always 0x11 */
- };
-
- return si476x_core_send_command(core, CMD_POWER_UP,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_TIMEOUT_POWER_UP);
-}
-
-static int si476x_core_cmd_power_up_a20(struct si476x_core *core,
- struct si476x_power_up_args *puargs)
-{
- u8 resp[CMD_POWER_UP_A20_NRESP];
- const bool intsel = (core->pinmux.a1 == SI476X_A1_IRQ);
- const bool ctsen = (core->client->irq != 0);
- const u8 args[CMD_POWER_UP_A20_NARGS] = {
- puargs->ibias6x << 7 | puargs->xstart,
- 0x3F & puargs->xcload, /* First two bits are reserved to be
- * zeros */
- ctsen << 7 | intsel << 6 | puargs->fastboot << 5 |
- puargs->xbiashc << 3 | puargs->xbias,
- puargs->func << 4 | puargs->freq,
- 0x10 | puargs->xmode,
- };
-
- return si476x_core_send_command(core, CMD_POWER_UP,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_TIMEOUT_POWER_UP);
-}
-
-static int si476x_core_cmd_power_down_a10(struct si476x_core *core,
- struct si476x_power_down_args *pdargs)
-{
- u8 resp[CMD_POWER_DOWN_A10_NRESP];
-
- return si476x_core_send_command(core, CMD_POWER_DOWN,
- NULL, 0,
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-}
-
-static int si476x_core_cmd_power_down_a20(struct si476x_core *core,
- struct si476x_power_down_args *pdargs)
-{
- u8 resp[CMD_POWER_DOWN_A20_NRESP];
- const u8 args[CMD_POWER_DOWN_A20_NARGS] = {
- pdargs->xosc,
- };
- return si476x_core_send_command(core, CMD_POWER_DOWN,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
-}
-
-static int si476x_core_cmd_am_tune_freq_a10(struct si476x_core *core,
- struct si476x_tune_freq_args *tuneargs)
-{
-
- const int am_freq = tuneargs->freq;
- u8 resp[CMD_AM_TUNE_FREQ_NRESP];
- const u8 args[CMD_AM_TUNE_FREQ_NARGS] = {
- (tuneargs->hd << 6),
- msb(am_freq),
- lsb(am_freq),
- };
-
- return si476x_cmd_tune_seek_freq(core, CMD_AM_TUNE_FREQ, args,
- sizeof(args),
- resp, sizeof(resp));
-}
-
-static int si476x_core_cmd_am_tune_freq_a20(struct si476x_core *core,
- struct si476x_tune_freq_args *tuneargs)
-{
- const int am_freq = tuneargs->freq;
- u8 resp[CMD_AM_TUNE_FREQ_NRESP];
- const u8 args[CMD_AM_TUNE_FREQ_NARGS] = {
- (tuneargs->zifsr << 6) | (tuneargs->injside & 0b11),
- msb(am_freq),
- lsb(am_freq),
- };
-
- return si476x_cmd_tune_seek_freq(core, CMD_AM_TUNE_FREQ,
- args, sizeof(args),
- resp, sizeof(resp));
-}
-
-static int si476x_core_cmd_fm_rsq_status_a10(struct si476x_core *core,
- struct si476x_rsq_status_args *rsqargs,
- struct si476x_rsq_status_report *report)
-{
- int err;
- u8 resp[CMD_FM_RSQ_STATUS_A10_NRESP];
- const u8 args[CMD_FM_RSQ_STATUS_A10_NARGS] = {
- rsqargs->rsqack << 3 | rsqargs->attune << 2 |
- rsqargs->cancel << 1 | rsqargs->stcack,
- };
-
- err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
- /*
- * Besides getting received signal quality information this
- * command can be used to just acknowledge different interrupt
- * flags in those cases it is useless to copy and parse
- * received data so user can pass NULL, and thus avoid
- * unnecessary copying.
- */
- if (err < 0 || report == NULL)
- return err;
-
- report->multhint = 0b10000000 & resp[1];
- report->multlint = 0b01000000 & resp[1];
- report->snrhint = 0b00001000 & resp[1];
- report->snrlint = 0b00000100 & resp[1];
- report->rssihint = 0b00000010 & resp[1];
- report->rssilint = 0b00000001 & resp[1];
-
- report->bltf = 0b10000000 & resp[2];
- report->snr_ready = 0b00100000 & resp[2];
- report->rssiready = 0b00001000 & resp[2];
- report->afcrl = 0b00000010 & resp[2];
- report->valid = 0b00000001 & resp[2];
-
- report->readfreq = be16_to_cpup((__be16 *)(resp + 3));
- report->freqoff = resp[5];
- report->rssi = resp[6];
- report->snr = resp[7];
- report->lassi = resp[9];
- report->hassi = resp[10];
- report->mult = resp[11];
- report->dev = resp[12];
- report->readantcap = be16_to_cpup((__be16 *)(resp + 13));
- report->assi = resp[15];
- report->usn = resp[16];
-
- return err;
-}
-
-static int si476x_core_cmd_fm_rsq_status_a20(struct si476x_core *core,
- struct si476x_rsq_status_args *rsqargs,
- struct si476x_rsq_status_report *report)
-{
- int err;
- u8 resp[CMD_FM_RSQ_STATUS_A10_NRESP];
- const u8 args[CMD_FM_RSQ_STATUS_A30_NARGS] = {
- rsqargs->primary << 4 | rsqargs->rsqack << 3 |
- rsqargs->attune << 2 | rsqargs->cancel << 1 |
- rsqargs->stcack,
- };
-
- err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
- /*
- * Besides getting received signal quality information this
- * command can be used to just acknowledge different interrupt
- * flags in those cases it is useless to copy and parse
- * received data so user can pass NULL, and thus avoid
- * unnecessary copying.
- */
- if (err < 0 || report == NULL)
- return err;
-
- report->multhint = 0b10000000 & resp[1];
- report->multlint = 0b01000000 & resp[1];
- report->snrhint = 0b00001000 & resp[1];
- report->snrlint = 0b00000100 & resp[1];
- report->rssihint = 0b00000010 & resp[1];
- report->rssilint = 0b00000001 & resp[1];
-
- report->bltf = 0b10000000 & resp[2];
- report->snr_ready = 0b00100000 & resp[2];
- report->rssiready = 0b00001000 & resp[2];
- report->afcrl = 0b00000010 & resp[2];
- report->valid = 0b00000001 & resp[2];
-
- report->readfreq = be16_to_cpup((__be16 *)(resp + 3));
- report->freqoff = resp[5];
- report->rssi = resp[6];
- report->snr = resp[7];
- report->lassi = resp[9];
- report->hassi = resp[10];
- report->mult = resp[11];
- report->dev = resp[12];
- report->readantcap = be16_to_cpup((__be16 *)(resp + 13));
- report->assi = resp[15];
- report->usn = resp[16];
-
- return err;
-}
-
-
-static int si476x_core_cmd_fm_rsq_status_a30(struct si476x_core *core,
- struct si476x_rsq_status_args *rsqargs,
- struct si476x_rsq_status_report *report)
-{
- int err;
- u8 resp[CMD_FM_RSQ_STATUS_A30_NRESP];
- const u8 args[CMD_FM_RSQ_STATUS_A30_NARGS] = {
- rsqargs->primary << 4 | rsqargs->rsqack << 3 |
- rsqargs->attune << 2 | rsqargs->cancel << 1 |
- rsqargs->stcack,
- };
-
- err = si476x_core_send_command(core, CMD_FM_RSQ_STATUS,
- args, ARRAY_SIZE(args),
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
- /*
- * Besides getting received signal quality information this
- * command can be used to just acknowledge different interrupt
- * flags in those cases it is useless to copy and parse
- * received data so user can pass NULL, and thus avoid
- * unnecessary copying.
- */
- if (err < 0 || report == NULL)
- return err;
-
- report->multhint = 0b10000000 & resp[1];
- report->multlint = 0b01000000 & resp[1];
- report->snrhint = 0b00001000 & resp[1];
- report->snrlint = 0b00000100 & resp[1];
- report->rssihint = 0b00000010 & resp[1];
- report->rssilint = 0b00000001 & resp[1];
-
- report->bltf = 0b10000000 & resp[2];
- report->snr_ready = 0b00100000 & resp[2];
- report->rssiready = 0b00001000 & resp[2];
- report->injside = 0b00000100 & resp[2];
- report->afcrl = 0b00000010 & resp[2];
- report->valid = 0b00000001 & resp[2];
-
- report->readfreq = be16_to_cpup((__be16 *)(resp + 3));
- report->freqoff = resp[5];
- report->rssi = resp[6];
- report->snr = resp[7];
- report->issi = resp[8];
- report->lassi = resp[9];
- report->hassi = resp[10];
- report->mult = resp[11];
- report->dev = resp[12];
- report->readantcap = be16_to_cpup((__be16 *)(resp + 13));
- report->assi = resp[15];
- report->usn = resp[16];
-
- report->pilotdev = resp[17];
- report->rdsdev = resp[18];
- report->assidev = resp[19];
- report->strongdev = resp[20];
- report->rdspi = be16_to_cpup((__be16 *)(resp + 21));
-
- return err;
-}
-
-static int si476x_core_cmd_fm_tune_freq_a10(struct si476x_core *core,
- struct si476x_tune_freq_args *tuneargs)
-{
- u8 resp[CMD_FM_TUNE_FREQ_NRESP];
- const u8 args[CMD_FM_TUNE_FREQ_A10_NARGS] = {
- (tuneargs->hd << 6) | (tuneargs->tunemode << 4)
- | (tuneargs->smoothmetrics << 2),
- msb(tuneargs->freq),
- lsb(tuneargs->freq),
- msb(tuneargs->antcap),
- lsb(tuneargs->antcap)
- };
-
- return si476x_cmd_tune_seek_freq(core, CMD_FM_TUNE_FREQ,
- args, sizeof(args),
- resp, sizeof(resp));
-}
-
-static int si476x_core_cmd_fm_tune_freq_a20(struct si476x_core *core,
- struct si476x_tune_freq_args *tuneargs)
-{
- u8 resp[CMD_FM_TUNE_FREQ_NRESP];
- const u8 args[CMD_FM_TUNE_FREQ_A20_NARGS] = {
- (tuneargs->hd << 6) | (tuneargs->tunemode << 4)
- | (tuneargs->smoothmetrics << 2) | (tuneargs->injside),
- msb(tuneargs->freq),
- lsb(tuneargs->freq),
- };
-
- return si476x_cmd_tune_seek_freq(core, CMD_FM_TUNE_FREQ,
- args, sizeof(args),
- resp, sizeof(resp));
-}
-
-static int si476x_core_cmd_agc_status_a20(struct si476x_core *core,
- struct si476x_agc_status_report *report)
-{
- int err;
- u8 resp[CMD_AGC_STATUS_NRESP_A20];
-
- if (!report)
- return -EINVAL;
-
- err = si476x_core_send_command(core, CMD_AGC_STATUS,
- NULL, 0,
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
- if (err < 0)
- return err;
-
- report->mxhi = resp[1] & SI476X_AGC_MXHI;
- report->mxlo = resp[1] & SI476X_AGC_MXLO;
- report->lnahi = resp[1] & SI476X_AGC_LNAHI;
- report->lnalo = resp[1] & SI476X_AGC_LNALO;
- report->fmagc1 = resp[2];
- report->fmagc2 = resp[3];
- report->pgagain = resp[4];
- report->fmwblang = resp[5];
-
- return err;
-}
-
-static int si476x_core_cmd_agc_status_a10(struct si476x_core *core,
- struct si476x_agc_status_report *report)
-{
- int err;
- u8 resp[CMD_AGC_STATUS_NRESP_A10];
-
- if (!report)
- return -EINVAL;
-
- err = si476x_core_send_command(core, CMD_AGC_STATUS,
- NULL, 0,
- resp, ARRAY_SIZE(resp),
- SI476X_DEFAULT_TIMEOUT);
- if (err < 0)
- return err;
-
- report->mxhi = resp[1] & SI476X_AGC_MXHI;
- report->mxlo = resp[1] & SI476X_AGC_MXLO;
- report->lnahi = resp[1] & SI476X_AGC_LNAHI;
- report->lnalo = resp[1] & SI476X_AGC_LNALO;
-
- return err;
-}
-
-typedef int (*tune_freq_func_t) (struct si476x_core *core,
- struct si476x_tune_freq_args *tuneargs);
-
-static struct {
- int (*power_up) (struct si476x_core *,
- struct si476x_power_up_args *);
- int (*power_down) (struct si476x_core *,
- struct si476x_power_down_args *);
-
- tune_freq_func_t fm_tune_freq;
- tune_freq_func_t am_tune_freq;
-
- int (*fm_rsq_status)(struct si476x_core *,
- struct si476x_rsq_status_args *,
- struct si476x_rsq_status_report *);
-
- int (*agc_status)(struct si476x_core *,
- struct si476x_agc_status_report *);
- int (*intb_pin_cfg)(struct si476x_core *core,
- enum si476x_intb_config intb,
- enum si476x_a1_config a1);
-} si476x_cmds_vtable[] = {
- [SI476X_REVISION_A10] = {
- .power_up = si476x_core_cmd_power_up_a10,
- .power_down = si476x_core_cmd_power_down_a10,
- .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a10,
- .am_tune_freq = si476x_core_cmd_am_tune_freq_a10,
- .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a10,
- .agc_status = si476x_core_cmd_agc_status_a10,
- .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a10,
- },
- [SI476X_REVISION_A20] = {
- .power_up = si476x_core_cmd_power_up_a20,
- .power_down = si476x_core_cmd_power_down_a20,
- .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a20,
- .am_tune_freq = si476x_core_cmd_am_tune_freq_a20,
- .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a20,
- .agc_status = si476x_core_cmd_agc_status_a20,
- .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a20,
- },
- [SI476X_REVISION_A30] = {
- .power_up = si476x_core_cmd_power_up_a20,
- .power_down = si476x_core_cmd_power_down_a20,
- .fm_tune_freq = si476x_core_cmd_fm_tune_freq_a20,
- .am_tune_freq = si476x_core_cmd_am_tune_freq_a20,
- .fm_rsq_status = si476x_core_cmd_fm_rsq_status_a30,
- .agc_status = si476x_core_cmd_agc_status_a20,
- .intb_pin_cfg = si476x_core_cmd_intb_pin_cfg_a20,
- },
-};
-
-int si476x_core_cmd_power_up(struct si476x_core *core,
- struct si476x_power_up_args *args)
-{
- BUG_ON(core->revision > SI476X_REVISION_A30 ||
- core->revision == -1);
- return si476x_cmds_vtable[core->revision].power_up(core, args);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_power_up);
-
-int si476x_core_cmd_power_down(struct si476x_core *core,
- struct si476x_power_down_args *args)
-{
- BUG_ON(core->revision > SI476X_REVISION_A30 ||
- core->revision == -1);
- return si476x_cmds_vtable[core->revision].power_down(core, args);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_power_down);
-
-int si476x_core_cmd_fm_tune_freq(struct si476x_core *core,
- struct si476x_tune_freq_args *args)
-{
- BUG_ON(core->revision > SI476X_REVISION_A30 ||
- core->revision == -1);
- return si476x_cmds_vtable[core->revision].fm_tune_freq(core, args);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_tune_freq);
-
-int si476x_core_cmd_am_tune_freq(struct si476x_core *core,
- struct si476x_tune_freq_args *args)
-{
- BUG_ON(core->revision > SI476X_REVISION_A30 ||
- core->revision == -1);
- return si476x_cmds_vtable[core->revision].am_tune_freq(core, args);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_am_tune_freq);
-
-int si476x_core_cmd_fm_rsq_status(struct si476x_core *core,
- struct si476x_rsq_status_args *args,
- struct si476x_rsq_status_report *report)
-
-{
- BUG_ON(core->revision > SI476X_REVISION_A30 ||
- core->revision == -1);
- return si476x_cmds_vtable[core->revision].fm_rsq_status(core, args,
- report);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_fm_rsq_status);
-
-int si476x_core_cmd_agc_status(struct si476x_core *core,
- struct si476x_agc_status_report *report)
-
-{
- BUG_ON(core->revision > SI476X_REVISION_A30 ||
- core->revision == -1);
- return si476x_cmds_vtable[core->revision].agc_status(core, report);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_agc_status);
-
-int si476x_core_cmd_intb_pin_cfg(struct si476x_core *core,
- enum si476x_intb_config intb,
- enum si476x_a1_config a1)
-{
- BUG_ON(core->revision > SI476X_REVISION_A30 ||
- core->revision == -1);
-
- return si476x_cmds_vtable[core->revision].intb_pin_cfg(core, intb, a1);
-}
-EXPORT_SYMBOL_GPL(si476x_core_cmd_intb_pin_cfg);
-
-MODULE_LICENSE("GPL");
-MODULE_AUTHOR("Andrey Smirnov <[email protected]>");
-MODULE_DESCRIPTION("API for command exchange for si476x");
diff --git a/drivers/mfd/si476x-i2c.c b/drivers/mfd/si476x-i2c.c
deleted file mode 100644
index f5bc8e4..0000000
--- a/drivers/mfd/si476x-i2c.c
+++ /dev/null
@@ -1,886 +0,0 @@
-/*
- * drivers/mfd/si476x-i2c.c -- Core device driver for si476x MFD
- * device
- *
- * Copyright (C) 2012 Innovative Converged Devices(ICD)
- * Copyright (C) 2013 Andrey Smirnov
- *
- * Author: Andrey Smirnov <[email protected]>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- *
- */
-#include <linux/module.h>
-
-#include <linux/slab.h>
-#include <linux/interrupt.h>
-#include <linux/delay.h>
-#include <linux/gpio.h>
-#include <linux/regulator/consumer.h>
-#include <linux/i2c.h>
-#include <linux/err.h>
-
-#include <linux/mfd/si476x-core.h>
-
-#define SI476X_MAX_IO_ERRORS 10
-#define SI476X_DRIVER_RDS_FIFO_DEPTH 128
-
-/**
- * si476x_core_config_pinmux() - pin function configuration function
- *
- * @core: Core device structure
- *
- * Configure the functions of the pins of the radio chip.
- *
- * The function returns zero in case of succes or negative error code
- * otherwise.
- */
-static int si476x_core_config_pinmux(struct si476x_core *core)
-{
- int err;
- dev_dbg(&core->client->dev, "Configuring pinmux\n");
- err = si476x_core_cmd_dig_audio_pin_cfg(core,
- core->pinmux.dclk,
- core->pinmux.dfs,
- core->pinmux.dout,
- core->pinmux.xout);
- if (err < 0) {
- dev_err(&core->client->dev,
- "Failed to configure digital audio pins(err = %d)\n",
- err);
- return err;
- }
-
- err = si476x_core_cmd_zif_pin_cfg(core,
- core->pinmux.iqclk,
- core->pinmux.iqfs,
- core->pinmux.iout,
- core->pinmux.qout);
- if (err < 0) {
- dev_err(&core->client->dev,
- "Failed to configure ZIF pins(err = %d)\n",
- err);
- return err;
- }
-
- err = si476x_core_cmd_ic_link_gpo_ctl_pin_cfg(core,
- core->pinmux.icin,
- core->pinmux.icip,
- core->pinmux.icon,
- core->pinmux.icop);
- if (err < 0) {
- dev_err(&core->client->dev,
- "Failed to configure IC-Link/GPO pins(err = %d)\n",
- err);
- return err;
- }
-
- err = si476x_core_cmd_ana_audio_pin_cfg(core,
- core->pinmux.lrout);
- if (err < 0) {
- dev_err(&core->client->dev,
- "Failed to configure analog audio pins(err = %d)\n",
- err);
- return err;
- }
-
- err = si476x_core_cmd_intb_pin_cfg(core,
- core->pinmux.intb,
- core->pinmux.a1);
- if (err < 0) {
- dev_err(&core->client->dev,
- "Failed to configure interrupt pins(err = %d)\n",
- err);
- return err;
- }
-
- return 0;
-}
-
-static inline void si476x_core_schedule_polling_work(struct si476x_core *core)
-{
- schedule_delayed_work(&core->status_monitor,
- usecs_to_jiffies(SI476X_STATUS_POLL_US));
-}
-
-/**
- * si476x_core_start() - early chip startup function
- * @core: Core device structure
- * @soft: When set, this flag forces "soft" startup, where "soft"
- * power down is the one done by sending appropriate command instead
- * of using reset pin of the tuner
- *
- * Perform required startup sequence to correctly power
- * up the chip and perform initial configuration. It does the
- * following sequence of actions:
- * 1. Claims and enables the power supplies VD and VIO1 required
- * for I2C interface of the chip operation.
- * 2. Waits for 100us, pulls the reset line up, enables irq,
- * waits for another 100us as it is specified by the
- * datasheet.
- * 3. Sends 'POWER_UP' command to the device with all provided
- * information about power-up parameters.
- * 4. Configures, pin multiplexor, disables digital audio and
- * configures interrupt sources.
- *
- * The function returns zero in case of succes or negative error code
- * otherwise.
- */
-int si476x_core_start(struct si476x_core *core, bool soft)
-{
- struct i2c_client *client = core->client;
- int err;
-
- if (!soft) {
- if (gpio_is_valid(core->gpio_reset))
- gpio_set_value_cansleep(core->gpio_reset, 1);
-
- if (client->irq)
- enable_irq(client->irq);
-
- udelay(100);
-
- if (!client->irq) {
- atomic_set(&core->is_alive, 1);
- si476x_core_schedule_polling_work(core);
- }
- } else {
- if (client->irq)
- enable_irq(client->irq);
- else {
- atomic_set(&core->is_alive, 1);
- si476x_core_schedule_polling_work(core);
- }
- }
-
- err = si476x_core_cmd_power_up(core,
- &core->power_up_parameters);
-
- if (err < 0) {
- dev_err(&core->client->dev,
- "Power up failure(err = %d)\n",
- err);
- goto disable_irq;
- }
-
- if (client->irq)
- atomic_set(&core->is_alive, 1);
-
- err = si476x_core_config_pinmux(core);
- if (err < 0) {
- dev_err(&core->client->dev,
- "Failed to configure pinmux(err = %d)\n",
- err);
- goto disable_irq;
- }
-
- if (client->irq) {
- err = regmap_write(core->regmap,
- SI476X_PROP_INT_CTL_ENABLE,
- SI476X_RDSIEN |
- SI476X_STCIEN |
- SI476X_CTSIEN);
- if (err < 0) {
- dev_err(&core->client->dev,
- "Failed to configure interrupt sources"
- "(err = %d)\n", err);
- goto disable_irq;
- }
- }
-
- return 0;
-
-disable_irq:
- if (err == -ENODEV)
- atomic_set(&core->is_alive, 0);
-
- if (client->irq)
- disable_irq(client->irq);
- else
- cancel_delayed_work_sync(&core->status_monitor);
-
- if (gpio_is_valid(core->gpio_reset))
- gpio_set_value_cansleep(core->gpio_reset, 0);
-
- return err;
-}
-EXPORT_SYMBOL_GPL(si476x_core_start);
-
-/**
- * si476x_core_stop() - chip power-down function
- * @core: Core device structure
- * @soft: When set, function sends a POWER_DOWN command instead of
- * bringing reset line low
- *
- * Power down the chip by performing following actions:
- * 1. Disable IRQ or stop the polling worker
- * 2. Send the POWER_DOWN command if the power down is soft or bring
- * reset line low if not.
- *
- * The function returns zero in case of succes or negative error code
- * otherwise.
- */
-int si476x_core_stop(struct si476x_core *core, bool soft)
-{
- int err = 0;
- atomic_set(&core->is_alive, 0);
-
- if (soft) {
- /* TODO: This probably shoud be a configurable option,
- * so it is possible to have the chips keep their
- * oscillators running
- */
- struct si476x_power_down_args args = {
- .xosc = false,
- };
- err = si476x_core_cmd_power_down(core, &args);
- }
-
- /* We couldn't disable those before
- * 'si476x_core_cmd_power_down' since we expect to get CTS
- * interrupt */
- if (core->client->irq)
- disable_irq(core->client->irq);
- else
- cancel_delayed_work_sync(&core->status_monitor);
-
- if (!soft) {
- if (gpio_is_valid(core->gpio_reset))
- gpio_set_value_cansleep(core->gpio_reset, 0);
- }
- return err;
-}
-EXPORT_SYMBOL_GPL(si476x_core_stop);
-
-/**
- * si476x_core_set_power_state() - set the level at which the power is
- * supplied for the chip.
- * @core: Core device structure
- * @next_state: enum si476x_power_state describing power state to
- * switch to.
- *
- * Switch on all the required power supplies
- *
- * This function returns 0 in case of suvccess and negative error code
- * otherwise.
- */
-int si476x_core_set_power_state(struct si476x_core *core,
- enum si476x_power_state next_state)
-{
- /*
- It is not clear form the datasheet if it is possible to
- work with device if not all power domains are operational.
- So for now the power-up policy is "power-up all the things!"
- */
- int err = 0;
-
- if (core->power_state == SI476X_POWER_INCONSISTENT) {
- dev_err(&core->client->dev,
- "The device in inconsistent power state\n");
- return -EINVAL;
- }
-
- if (next_state != core->power_state) {
- switch (next_state) {
- case SI476X_POWER_UP_FULL:
- err = regulator_bulk_enable(ARRAY_SIZE(core->supplies),
- core->supplies);
- if (err < 0) {
- core->power_state = SI476X_POWER_INCONSISTENT;
- break;
- }
- /*
- * Startup timing diagram recommends to have a
- * 100 us delay between enabling of the power
- * supplies and turning the tuner on.
- */
- udelay(100);
-
- err = si476x_core_start(core, false);
- if (err < 0)
- goto disable_regulators;
-
- core->power_state = next_state;
- break;
-
- case SI476X_POWER_DOWN:
- core->power_state = next_state;
- err = si476x_core_stop(core, false);
- if (err < 0)
- core->power_state = SI476X_POWER_INCONSISTENT;
-disable_regulators:
- err = regulator_bulk_disable(ARRAY_SIZE(core->supplies),
- core->supplies);
- if (err < 0)
- core->power_state = SI476X_POWER_INCONSISTENT;
- break;
- default:
- BUG();
- }
- }
-
- return err;
-}
-EXPORT_SYMBOL_GPL(si476x_core_set_power_state);
-
-/**
- * si476x_core_report_drainer_stop() - mark the completion of the RDS
- * buffer drain porcess by the worker.
- *
- * @core: Core device structure
- */
-static inline void si476x_core_report_drainer_stop(struct si476x_core *core)
-{
- mutex_lock(&core->rds_drainer_status_lock);
- core->rds_drainer_is_working = false;
- mutex_unlock(&core->rds_drainer_status_lock);
-}
-
-/**
- * si476x_core_start_rds_drainer_once() - start RDS drainer worker if
- * ther is none working, do nothing otherwise
- *
- * @core: Datastructure corresponding to the chip.
- */
-static inline void si476x_core_start_rds_drainer_once(struct si476x_core *core)
-{
- mutex_lock(&core->rds_drainer_status_lock);
- if (!core->rds_drainer_is_working) {
- core->rds_drainer_is_working = true;
- schedule_work(&core->rds_fifo_drainer);
- }
- mutex_unlock(&core->rds_drainer_status_lock);
-}
-/**
- * si476x_drain_rds_fifo() - RDS buffer drainer.
- * @work: struct work_struct being ppassed to the function by the
- * kernel.
- *
- * Drain the contents of the RDS FIFO of
- */
-static void si476x_core_drain_rds_fifo(struct work_struct *work)
-{
- int err;
-
- struct si476x_core *core = container_of(work, struct si476x_core,
- rds_fifo_drainer);
-
- struct si476x_rds_status_report report;
-
- si476x_core_lock(core);
- err = si476x_core_cmd_fm_rds_status(core, true, false, false, &report);
- if (!err) {
- int i = report.rdsfifoused;
- dev_dbg(&core->client->dev,
- "%d elements in RDS FIFO. Draining.\n", i);
- for (; i > 0; --i) {
- err = si476x_core_cmd_fm_rds_status(core, false, false,
- (i == 1), &report);
- if (err < 0)
- goto unlock;
-
- kfifo_in(&core->rds_fifo, report.rds,
- sizeof(report.rds));
- dev_dbg(&core->client->dev, "RDS data:\n %*ph\n",
- (int)sizeof(report.rds), report.rds);
- }
- dev_dbg(&core->client->dev, "Drrrrained!\n");
- wake_up_interruptible(&core->rds_read_queue);
- }
-
-unlock:
- si476x_core_unlock(core);
- si476x_core_report_drainer_stop(core);
-}
-
-/**
- * si476x_core_pronounce_dead()
- *
- * @core: Core device structure
- *
- * Mark the device as being dead and wake up all potentially waiting
- * threads of execution.
- *
- */
-static void si476x_core_pronounce_dead(struct si476x_core *core)
-{
- dev_info(&core->client->dev, "Core device is dead.\n");
-
- atomic_set(&core->is_alive, 0);
-
- /* Wake up al possible waiting processes */
- wake_up_interruptible(&core->rds_read_queue);
-
- atomic_set(&core->cts, 1);
- wake_up(&core->command);
-
- atomic_set(&core->stc, 1);
- wake_up(&core->tuning);
-}
-
-/**
- * si476x_core_i2c_xfer()
- *
- * @core: Core device structure
- * @type: Transfer type
- * @buf: Transfer buffer for/with data
- * @count: Transfer buffer size
- *
- * Perfrom and I2C transfer(either read or write) and keep a counter
- * of I/O errors. If the error counter rises above the threshold
- * pronounce device dead.
- *
- * The function returns zero on succes or negative error code on
- * failure.
- */
-int si476x_core_i2c_xfer(struct si476x_core *core,
- enum si476x_i2c_type type,
- char *buf, int count)
-{
- static int io_errors_count;
- int err;
- if (type == SI476X_I2C_SEND)
- err = i2c_master_send(core->client, buf, count);
- else
- err = i2c_master_recv(core->client, buf, count);
-
- if (err < 0) {
- if (io_errors_count++ > SI476X_MAX_IO_ERRORS)
- si476x_core_pronounce_dead(core);
- } else {
- io_errors_count = 0;
- }
-
- return err;
-}
-EXPORT_SYMBOL_GPL(si476x_core_i2c_xfer);
-
-/**
- * si476x_get_status()
- * @core: Core device structure
- *
- * Get the status byte of the core device by berforming one byte I2C
- * read.
- *
- * The function returns a status value or a negative error code on
- * error.
- */
-static int si476x_core_get_status(struct si476x_core *core)
-{
- u8 response;
- int err = si476x_core_i2c_xfer(core, SI476X_I2C_RECV,
- &response, sizeof(response));
-
- return (err < 0) ? err : response;
-}
-
-/**
- * si476x_get_and_signal_status() - IRQ dispatcher
- * @core: Core device structure
- *
- * Dispatch the arrived interrupt request based on the value of the
- * status byte reported by the tuner.
- *
- */
-static void si476x_core_get_and_signal_status(struct si476x_core *core)
-{
- int status = si476x_core_get_status(core);
- if (status < 0) {
- dev_err(&core->client->dev, "Failed to get status\n");
- return;
- }
-
- if (status & SI476X_CTS) {
- /* Unfortunately completions could not be used for
- * signalling CTS since this flag cannot be cleared
- * in status byte, and therefore once it becomes true
- * multiple calls to 'complete' would cause the
- * commands following the current one to be completed
- * before they actually are */
- dev_dbg(&core->client->dev, "[interrupt] CTSINT\n");
- atomic_set(&core->cts, 1);
- wake_up(&core->command);
- }
-
- if (status & SI476X_FM_RDS_INT) {
- dev_dbg(&core->client->dev, "[interrupt] RDSINT\n");
- si476x_core_start_rds_drainer_once(core);
- }
-
- if (status & SI476X_STC_INT) {
- dev_dbg(&core->client->dev, "[interrupt] STCINT\n");
- atomic_set(&core->stc, 1);
- wake_up(&core->tuning);
- }
-}
-
-static void si476x_core_poll_loop(struct work_struct *work)
-{
- struct si476x_core *core = SI476X_WORK_TO_CORE(work);
-
- si476x_core_get_and_signal_status(core);
-
- if (atomic_read(&core->is_alive))
- si476x_core_schedule_polling_work(core);
-}
-
-static irqreturn_t si476x_core_interrupt(int irq, void *dev)
-{
- struct si476x_core *core = dev;
-
- si476x_core_get_and_signal_status(core);
-
- return IRQ_HANDLED;
-}
-
-/**
- * si476x_firmware_version_to_revision()
- * @core: Core device structure
- * @major: Firmware major number
- * @minor1: Firmware first minor number
- * @minor2: Firmware second minor number
- *
- * Convert a chip's firmware version number into an offset that later
- * will be used to as offset in "vtable" of tuner functions
- *
- * This function returns a positive offset in case of success and a -1
- * in case of failure.
- */
-static int si476x_core_fwver_to_revision(struct si476x_core *core,
- int func, int major,
- int minor1, int minor2)
-{
- switch (func) {
- case SI476X_FUNC_FM_RECEIVER:
- switch (major) {
- case 5:
- return SI476X_REVISION_A10;
- case 8:
- return SI476X_REVISION_A20;
- case 10:
- return SI476X_REVISION_A30;
- default:
- goto unknown_revision;
- }
- case SI476X_FUNC_AM_RECEIVER:
- switch (major) {
- case 5:
- return SI476X_REVISION_A10;
- case 7:
- return SI476X_REVISION_A20;
- case 9:
- return SI476X_REVISION_A30;
- default:
- goto unknown_revision;
- }
- case SI476X_FUNC_WB_RECEIVER:
- switch (major) {
- case 3:
- return SI476X_REVISION_A10;
- case 5:
- return SI476X_REVISION_A20;
- case 7:
- return SI476X_REVISION_A30;
- default:
- goto unknown_revision;
- }
- case SI476X_FUNC_BOOTLOADER:
- default: /* FALLTHROUG */
- BUG();
- return -1;
- }
-
-unknown_revision:
- dev_err(&core->client->dev,
- "Unsupported version of the firmware: %d.%d.%d, "
- "reverting to A10 comptible functions\n",
- major, minor1, minor2);
-
- return SI476X_REVISION_A10;
-}
-
-/**
- * si476x_get_revision_info()
- * @core: Core device structure
- *
- * Get the firmware version number of the device. It is done in
- * following three steps:
- * 1. Power-up the device
- * 2. Send the 'FUNC_INFO' command
- * 3. Powering the device down.
- *
- * The function return zero on success and a negative error code on
- * failure.
- */
-static int si476x_core_get_revision_info(struct si476x_core *core)
-{
- int rval;
- struct si476x_func_info info;
-
- si476x_core_lock(core);
- rval = si476x_core_set_power_state(core, SI476X_POWER_UP_FULL);
- if (rval < 0)
- goto exit;
-
- rval = si476x_core_cmd_func_info(core, &info);
- if (rval < 0)
- goto power_down;
-
- core->revision = si476x_core_fwver_to_revision(core, info.func,
- info.firmware.major,
- info.firmware.minor[0],
- info.firmware.minor[1]);
-power_down:
- si476x_core_set_power_state(core, SI476X_POWER_DOWN);
-exit:
- si476x_core_unlock(core);
-
- return rval;
-}
-
-bool si476x_core_has_am(struct si476x_core *core)
-{
- return core->chip_id == SI476X_CHIP_SI4761 ||
- core->chip_id == SI476X_CHIP_SI4764;
-}
-EXPORT_SYMBOL_GPL(si476x_core_has_am);
-
-bool si476x_core_has_diversity(struct si476x_core *core)
-{
- return core->chip_id == SI476X_CHIP_SI4764;
-}
-EXPORT_SYMBOL_GPL(si476x_core_has_diversity);
-
-bool si476x_core_is_a_secondary_tuner(struct si476x_core *core)
-{
- return si476x_core_has_diversity(core) &&
- (core->diversity_mode == SI476X_PHDIV_SECONDARY_ANTENNA ||
- core->diversity_mode == SI476X_PHDIV_SECONDARY_COMBINING);
-}
-EXPORT_SYMBOL_GPL(si476x_core_is_a_secondary_tuner);
-
-bool si476x_core_is_a_primary_tuner(struct si476x_core *core)
-{
- return si476x_core_has_diversity(core) &&
- (core->diversity_mode == SI476X_PHDIV_PRIMARY_ANTENNA ||
- core->diversity_mode == SI476X_PHDIV_PRIMARY_COMBINING);
-}
-EXPORT_SYMBOL_GPL(si476x_core_is_a_primary_tuner);
-
-bool si476x_core_is_in_am_receiver_mode(struct si476x_core *core)
-{
- return si476x_core_has_am(core) &&
- (core->power_up_parameters.func == SI476X_FUNC_AM_RECEIVER);
-}
-EXPORT_SYMBOL_GPL(si476x_core_is_in_am_receiver_mode);
-
-bool si476x_core_is_powered_up(struct si476x_core *core)
-{
- return core->power_state == SI476X_POWER_UP_FULL;
-}
-EXPORT_SYMBOL_GPL(si476x_core_is_powered_up);
-
-static int si476x_core_probe(struct i2c_client *client,
- const struct i2c_device_id *id)
-{
- int rval;
- struct si476x_core *core;
- struct si476x_platform_data *pdata;
- struct mfd_cell *cell;
- int cell_num;
-
- core = devm_kzalloc(&client->dev, sizeof(*core), GFP_KERNEL);
- if (!core) {
- dev_err(&client->dev,
- "failed to allocate 'struct si476x_core'\n");
- return -ENOMEM;
- }
- core->client = client;
-
- core->regmap = devm_regmap_init_si476x(core);
- if (IS_ERR(core->regmap)) {
- rval = PTR_ERR(core->regmap);
- dev_err(&client->dev,
- "Failed to allocate register map: %d\n",
- rval);
- return rval;
- }
-
- i2c_set_clientdata(client, core);
-
- atomic_set(&core->is_alive, 0);
- core->power_state = SI476X_POWER_DOWN;
-
- pdata = client->dev.platform_data;
- if (pdata) {
- memcpy(&core->power_up_parameters,
- &pdata->power_up_parameters,
- sizeof(core->power_up_parameters));
-
- core->gpio_reset = -1;
- if (gpio_is_valid(pdata->gpio_reset)) {
- rval = gpio_request(pdata->gpio_reset, "si476x reset");
- if (rval) {
- dev_err(&client->dev,
- "Failed to request gpio: %d\n", rval);
- return rval;
- }
- core->gpio_reset = pdata->gpio_reset;
- gpio_direction_output(core->gpio_reset, 0);
- }
-
- core->diversity_mode = pdata->diversity_mode;
- memcpy(&core->pinmux, &pdata->pinmux,
- sizeof(struct si476x_pinmux));
- } else {
- dev_err(&client->dev, "No platform data provided\n");
- return -EINVAL;
- }
-
- core->supplies[0].supply = "vd";
- core->supplies[1].supply = "va";
- core->supplies[2].supply = "vio1";
- core->supplies[3].supply = "vio2";
-
- rval = devm_regulator_bulk_get(&client->dev,
- ARRAY_SIZE(core->supplies),
- core->supplies);
- if (rval) {
- dev_err(&client->dev, "Failet to gett all of the regulators\n");
- goto free_gpio;
- }
-
- mutex_init(&core->cmd_lock);
- init_waitqueue_head(&core->command);
- init_waitqueue_head(&core->tuning);
-
- rval = kfifo_alloc(&core->rds_fifo,
- SI476X_DRIVER_RDS_FIFO_DEPTH *
- sizeof(struct v4l2_rds_data),
- GFP_KERNEL);
- if (rval) {
- dev_err(&client->dev, "Could not alloate the FIFO\n");
- goto free_gpio;
- }
- mutex_init(&core->rds_drainer_status_lock);
- init_waitqueue_head(&core->rds_read_queue);
- INIT_WORK(&core->rds_fifo_drainer, si476x_core_drain_rds_fifo);
-
- if (client->irq) {
- rval = devm_request_threaded_irq(&client->dev,
- client->irq, NULL,
- si476x_core_interrupt,
- IRQF_TRIGGER_FALLING,
- client->name, core);
- if (rval < 0) {
- dev_err(&client->dev, "Could not request IRQ %d\n",
- client->irq);
- goto free_kfifo;
- }
- disable_irq(client->irq);
- dev_dbg(&client->dev, "IRQ requested.\n");
-
- core->rds_fifo_depth = 20;
- } else {
- INIT_DELAYED_WORK(&core->status_monitor,
- si476x_core_poll_loop);
- dev_info(&client->dev,
- "No IRQ number specified, will use polling\n");
-
- core->rds_fifo_depth = 5;
- }
-
- core->chip_id = id->driver_data;
-
- rval = si476x_core_get_revision_info(core);
- if (rval < 0) {
- rval = -ENODEV;
- goto free_kfifo;
- }
-
- cell_num = 0;
-
- cell = &core->cells[SI476X_RADIO_CELL];
- cell->name = "si476x-radio";
- cell_num++;
-
-#ifdef CONFIG_SND_SOC_SI476X
- if ((core->chip_id == SI476X_CHIP_SI4761 ||
- core->chip_id == SI476X_CHIP_SI4764) &&
- core->pinmux.dclk == SI476X_DCLK_DAUDIO &&
- core->pinmux.dfs == SI476X_DFS_DAUDIO &&
- core->pinmux.dout == SI476X_DOUT_I2S_OUTPUT &&
- core->pinmux.xout == SI476X_XOUT_TRISTATE) {
- cell = &core->cells[SI476X_CODEC_CELL];
- cell->name = "si476x-codec";
- cell_num++;
- }
-#endif
- rval = mfd_add_devices(&client->dev,
- (client->adapter->nr << 8) + client->addr,
- core->cells, cell_num,
- NULL, 0, NULL);
- if (!rval)
- return 0;
-
-free_kfifo:
- kfifo_free(&core->rds_fifo);
-
-free_gpio:
- if (gpio_is_valid(core->gpio_reset))
- gpio_free(core->gpio_reset);
-
- return rval;
-}
-
-static int si476x_core_remove(struct i2c_client *client)
-{
- struct si476x_core *core = i2c_get_clientdata(client);
-
- si476x_core_pronounce_dead(core);
- mfd_remove_devices(&client->dev);
-
- if (client->irq)
- disable_irq(client->irq);
- else
- cancel_delayed_work_sync(&core->status_monitor);
-
- kfifo_free(&core->rds_fifo);
-
- if (gpio_is_valid(core->gpio_reset))
- gpio_free(core->gpio_reset);
-
- return 0;
-}
-
-
-static const struct i2c_device_id si476x_id[] = {
- { "si4761", SI476X_CHIP_SI4761 },
- { "si4764", SI476X_CHIP_SI4764 },
- { "si4768", SI476X_CHIP_SI4768 },
- { },
-};
-MODULE_DEVICE_TABLE(i2c, si476x_id);
-
-static struct i2c_driver si476x_core_driver = {
- .driver = {
- .name = "si476x-core",
- .owner = THIS_MODULE,
- },
- .probe = si476x_core_probe,
- .remove = si476x_core_remove,
- .id_table = si476x_id,
-};
-module_i2c_driver(si476x_core_driver);
-
-
-MODULE_AUTHOR("Andrey Smirnov <[email protected]>");
-MODULE_DESCRIPTION("Si4761/64/68 AM/FM MFD core device driver");
-MODULE_LICENSE("GPL");
diff --git a/drivers/mfd/si476x-prop.c b/drivers/mfd/si476x-prop.c
deleted file mode 100644
index d1f548a..0000000
--- a/drivers/mfd/si476x-prop.c
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * drivers/mfd/si476x-prop.c -- Subroutines to access
- * properties of si476x chips
- *
- * Copyright (C) 2012 Innovative Converged Devices(ICD)
- * Copyright (C) 2013 Andrey Smirnov
- *
- * Author: Andrey Smirnov <[email protected]>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; version 2 of the License.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * General Public License for more details.
- */
-#include <linux/module.h>
-
-#include <media/si476x.h>
-#include <linux/mfd/si476x-core.h>
-
-struct si476x_property_range {
- u16 low, high;
-};
-
-static bool si476x_core_element_is_in_array(u16 element,
- const u16 array[],
- size_t size)
-{
- int i;
-
- for (i = 0; i < size; i++)
- if (element == array[i])
- return true;
-
- return false;
-}
-
-static bool si476x_core_element_is_in_range(u16 element,
- const struct si476x_property_range range[],
- size_t size)
-{
- int i;
-
- for (i = 0; i < size; i++)
- if (element <= range[i].high && element >= range[i].low)
- return true;
-
- return false;
-}
-
-static bool si476x_core_is_valid_property_a10(struct si476x_core *core,
- u16 property)
-{
- static const u16 valid_properties[] = {
- 0x0000,
- 0x0500, 0x0501,
- 0x0600,
- 0x0709, 0x070C, 0x070D, 0x70E, 0x710,
- 0x0718,
- 0x1207, 0x1208,
- 0x2007,
- 0x2300,
- };
-
- static const struct si476x_property_range valid_ranges[] = {
- { 0x0200, 0x0203 },
- { 0x0300, 0x0303 },
- { 0x0400, 0x0404 },
- { 0x0700, 0x0707 },
- { 0x1100, 0x1102 },
- { 0x1200, 0x1204 },
- { 0x1300, 0x1306 },
- { 0x2000, 0x2005 },
- { 0x2100, 0x2104 },
- { 0x2106, 0x2106 },
- { 0x2200, 0x220E },
- { 0x3100, 0x3104 },
- { 0x3207, 0x320F },
- { 0x3300, 0x3304 },
- { 0x3500, 0x3517 },
- { 0x3600, 0x3617 },
- { 0x3700, 0x3717 },
- { 0x4000, 0x4003 },
- };
-
- return si476x_core_element_is_in_range(property, valid_ranges,
- ARRAY_SIZE(valid_ranges)) ||
- si476x_core_element_is_in_array(property, valid_properties,
- ARRAY_SIZE(valid_properties));
-}
-
-static bool si476x_core_is_valid_property_a20(struct si476x_core *core,
- u16 property)
-{
- static const u16 valid_properties[] = {
- 0x071B,
- 0x1006,
- 0x2210,
- 0x3401,
- };
-
- static const struct si476x_property_range valid_ranges[] = {
- { 0x2215, 0x2219 },
- };
-
- return si476x_core_is_valid_property_a10(core, property) ||
- si476x_core_element_is_in_range(property, valid_ranges,
- ARRAY_SIZE(valid_ranges)) ||
- si476x_core_element_is_in_array(property, valid_properties,
- ARRAY_SIZE(valid_properties));
-}
-
-static bool si476x_core_is_valid_property_a30(struct si476x_core *core,
- u16 property)
-{
- static const u16 valid_properties[] = {
- 0x071C, 0x071D,
- 0x1007, 0x1008,
- 0x220F, 0x2214,
- 0x2301,
- 0x3105, 0x3106,
- 0x3402,
- };
-
- static const struct si476x_property_range valid_ranges[] = {
- { 0x0405, 0x0411 },
- { 0x2008, 0x200B },
- { 0x2220, 0x2223 },
- { 0x3100, 0x3106 },
- };
-
- return si476x_core_is_valid_property_a20(core, property) ||
- si476x_core_element_is_in_range(property, valid_ranges,
- ARRAY_SIZE(valid_ranges)) ||
- si476x_core_element_is_in_array(property, valid_properties,
- ARRAY_SIZE(valid_properties));
-}
-
-typedef bool (*valid_property_pred_t) (struct si476x_core *, u16);
-
-static bool si476x_core_is_valid_property(struct si476x_core *core,
- u16 property)
-{
- static const valid_property_pred_t is_valid_property[] = {
- [SI476X_REVISION_A10] = si476x_core_is_valid_property_a10,
- [SI476X_REVISION_A20] = si476x_core_is_valid_property_a20,
- [SI476X_REVISION_A30] = si476x_core_is_valid_property_a30,
- };
-
- BUG_ON(core->revision > SI476X_REVISION_A30 ||
- core->revision == -1);
- return is_valid_property[core->revision](core, property);
-}
-
-
-static bool si476x_core_is_readonly_property(struct si476x_core *core,
- u16 property)
-{
- BUG_ON(core->revision > SI476X_REVISION_A30 ||
- core->revision == -1);
-
- switch (core->revision) {
- case SI476X_REVISION_A10:
- return (property == 0x3200);
- case SI476X_REVISION_A20:
- return (property == 0x1006 ||
- property == 0x2210 ||
- property == 0x3200);
- case SI476X_REVISION_A30:
- return false;
- }
-
- return false;
-}
-
-static bool si476x_core_regmap_readable_register(struct device *dev,
- unsigned int reg)
-{
- struct i2c_client *client = to_i2c_client(dev);
- struct si476x_core *core = i2c_get_clientdata(client);
-
- return si476x_core_is_valid_property(core, (u16) reg);
-
-}
-
-static bool si476x_core_regmap_writable_register(struct device *dev,
- unsigned int reg)
-{
- struct i2c_client *client = to_i2c_client(dev);
- struct si476x_core *core = i2c_get_clientdata(client);
-
- return si476x_core_is_valid_property(core, (u16) reg) &&
- !si476x_core_is_readonly_property(core, (u16) reg);
-}
-
-
-static int si476x_core_regmap_write(void *context, unsigned int reg,
- unsigned int val)
-{
- return si476x_core_cmd_set_property(context, reg, val);
-}
-
-static int si476x_core_regmap_read(void *context, unsigned int reg,
- unsigned *val)
-{
- struct si476x_core *core = context;
- int err;
-
- err = si476x_core_cmd_get_property(core, reg);
- if (err < 0)
- return err;
-
- *val = err;
-
- return 0;
-}
-
-
-static const struct regmap_config si476x_regmap_config = {
- .reg_bits = 16,
- .val_bits = 16,
-
- .max_register = 0x4003,
-
- .writeable_reg = si476x_core_regmap_writable_register,
- .readable_reg = si476x_core_regmap_readable_register,
-
- .reg_read = si476x_core_regmap_read,
- .reg_write = si476x_core_regmap_write,
-
- .cache_type = REGCACHE_RBTREE,
-};
-
-struct regmap *devm_regmap_init_si476x(struct si476x_core *core)
-{
- return devm_regmap_init(&core->client->dev, NULL,
- core, &si476x_regmap_config);
-}
-EXPORT_SYMBOL_GPL(devm_regmap_init_si476x);

2013-04-18 22:26:33

by Samuel Ortiz

[permalink] [raw]
Subject: Re: [PATCH 09/12] v4l2: Add a V4L2 driver for SI476X MFD

Hi Andrey,

On Thu, Apr 18, 2013 at 09:58:35AM -0700, Andrey Smirnov wrote:
> diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
> index ead9928..170460d 100644
> --- a/drivers/media/radio/Kconfig
> +++ b/drivers/media/radio/Kconfig
> @@ -18,6 +18,23 @@ config RADIO_SI470X
>
> source "drivers/media/radio/si470x/Kconfig"
>
> +config RADIO_SI476X
> + tristate "Silicon Laboratories Si476x I2C FM Radio"
> + depends on I2C && VIDEO_V4L2
> + select MFD_CORE
> + select MFD_SI476X_CORE
This is wrong. You want depends on MFD_SI476X_CORE, you should not select a
symbol that has other dependencies. It also would allow us to carry the
v4l2/media parts of your patchset independently from the MFD ones as the radio
driver will not be buildable on a tree where the MFD part is not present.
We'll try to figure something out with Mauro.

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

2013-04-19 21:31:59

by Samuel Ortiz

[permalink] [raw]
Subject: Re: [PATCH v9 00/12] Driver for Si476x series of chips

Hi Andrey,

On Thu, Apr 18, 2013 at 09:58:26AM -0700, Andrey Smirnov wrote:
> Driver for Si476x series of chips
>
> This is a eight version of the patchset originaly posted here:
> https://lkml.org/lkml/2012/9/13/590
>
> Second version of the patch was posted here:
> https://lkml.org/lkml/2012/10/5/598
>
> Third version of the patch was posted here:
> https://lkml.org/lkml/2012/10/23/510
>
> Fourth version of the patch was posted here:
> https://lkml.org/lkml/2013/2/18/572
>
> Fifth version of the patch was posted here:
> https://lkml.org/lkml/2013/2/26/45
>
> Sixth version of the patch was posted here:
> https://lkml.org/lkml/2013/2/26/257
>
> Seventh version of the patch was posted here:
> https://lkml.org/lkml/2013/2/27/22
>
> Eighth version of the patch was posted here:
> https://lkml.org/lkml/2013/3/26/891
>
> To save everyone's time I'll repost the original description of it:
>
> This patchset contains a driver for a Silicon Laboratories 476x series
> of radio tuners. The driver itself is implemented as an MFD devices
> comprised of three parts:
> 1. Core device that provides all the other devices with basic
> functionality and locking scheme.
> 2. Radio device that translates between V4L2 subsystem requests into
> Core device commands.
> 3. Codec device that does similar to the earlier described task, but
> for ALSA SoC subsystem.
>
> v9 of this driver has following changes:
> - MFD part of the driver no longer depends on the header file added
> by the radio driver(media/si476x.h) which should potential
> restore the bisectability of the patches
I applied all the MFD patches from this patchset (All 4 first ones), plus a
follow up one for fixing the i2c related warning.
I also squashed the REGMAP_I2C dependency into patch #4.
It's all in mfd-next now, I'd appreciate if you could double check it's all
fine.

Mauro will take the rest, we made sure there won't be any merge conflict
between our trees.

Cheers,
Samuel.

--
Intel Open Source Technology Centre
http://oss.intel.com/

2013-04-19 21:48:51

by Andrey Smirnov

[permalink] [raw]
Subject: Re: [PATCH v9 00/12] Driver for Si476x series of chips

On Fri, Apr 19, 2013 at 2:31 PM, Samuel Ortiz <[email protected]> wrote:
> Hi Andrey,
>
> On Thu, Apr 18, 2013 at 09:58:26AM -0700, Andrey Smirnov wrote:
>> Driver for Si476x series of chips
>>
>> This is a eight version of the patchset originaly posted here:
>> https://lkml.org/lkml/2012/9/13/590
>>
>> Second version of the patch was posted here:
>> https://lkml.org/lkml/2012/10/5/598
>>
>> Third version of the patch was posted here:
>> https://lkml.org/lkml/2012/10/23/510
>>
>> Fourth version of the patch was posted here:
>> https://lkml.org/lkml/2013/2/18/572
>>
>> Fifth version of the patch was posted here:
>> https://lkml.org/lkml/2013/2/26/45
>>
>> Sixth version of the patch was posted here:
>> https://lkml.org/lkml/2013/2/26/257
>>
>> Seventh version of the patch was posted here:
>> https://lkml.org/lkml/2013/2/27/22
>>
>> Eighth version of the patch was posted here:
>> https://lkml.org/lkml/2013/3/26/891
>>
>> To save everyone's time I'll repost the original description of it:
>>
>> This patchset contains a driver for a Silicon Laboratories 476x series
>> of radio tuners. The driver itself is implemented as an MFD devices
>> comprised of three parts:
>> 1. Core device that provides all the other devices with basic
>> functionality and locking scheme.
>> 2. Radio device that translates between V4L2 subsystem requests into
>> Core device commands.
>> 3. Codec device that does similar to the earlier described task, but
>> for ALSA SoC subsystem.
>>
>> v9 of this driver has following changes:
>> - MFD part of the driver no longer depends on the header file added
>> by the radio driver(media/si476x.h) which should potential
>> restore the bisectability of the patches
> I applied all the MFD patches from this patchset (All 4 first ones), plus a
> follow up one for fixing the i2c related warning.
> I also squashed the REGMAP_I2C dependency into patch #4.
> It's all in mfd-next now, I'd appreciate if you could double check it's all
> fine.
>
> Mauro will take the rest, we made sure there won't be any merge conflict
> between our trees.
>

Thanks, I will try to test it today or tomorrow(20/04) at the latest.

> Cheers,
> Samuel.
>
> --
> Intel Open Source Technology Centre
> http://oss.intel.com/

2013-04-21 05:53:15

by Andrey Smirnov

[permalink] [raw]
Subject: Re: [PATCH v9 00/12] Driver for Si476x series of chips

> I applied all the MFD patches from this patchset (All 4 first ones), plus a
> follow up one for fixing the i2c related warning.
> I also squashed the REGMAP_I2C dependency into patch #4.
> It's all in mfd-next now, I'd appreciate if you could double check it's all
> fine.

I checked out latest
git://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-next.git and
applied patches 5 - 10, 12. There doesn't seem to be any problems, so
I think MFD part of the driver is good to go.

>
> Mauro will take the rest, we made sure there won't be any merge conflict
> between our trees.

Mauro, I am not sure if you need me to rebase any of the patches(it
doesn't seem like you had a chance to make any further changes related
to this driver in media tree), but if you do, ping me and I'll get on
it.

>
> Cheers,
> Samuel.
>
> --
> Intel Open Source Technology Centre
> http://oss.intel.com/

2013-04-21 09:33:16

by Mauro Carvalho Chehab

[permalink] [raw]
Subject: Re: [PATCH v9 00/12] Driver for Si476x series of chips

Em 21-04-2013 02:53, Andrey Smirnov escreveu:
>> I applied all the MFD patches from this patchset (All 4 first ones), plus a
>> follow up one for fixing the i2c related warning.
>> I also squashed the REGMAP_I2C dependency into patch #4.
>> It's all in mfd-next now, I'd appreciate if you could double check it's all
>> fine.
>
> I checked out latest
> git://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-next.git and
> applied patches 5 - 10, 12. There doesn't seem to be any problems, so
> I think MFD part of the driver is good to go.
>
>>
>> Mauro will take the rest, we made sure there won't be any merge conflict
>> between our trees.
>
> Mauro, I am not sure if you need me to rebase any of the patches(it
> doesn't seem like you had a chance to make any further changes related
> to this driver in media tree), but if you do, ping me and I'll get on
> it.

No, I don't need. The V4L parts are on my experimental tree:

http://git.linuxtv.org/mchehab/experimental.git/shortlog/refs/heads/si476x

I'll just merge it at the main tree or as a topic branch later today
or (more likely) tomorrow.

Please ping me today if you find anything wrong there.

Regards,
Mauro

2013-04-22 02:01:56

by Andrey Smirnov

[permalink] [raw]
Subject: Re: [PATCH v9 00/12] Driver for Si476x series of chips

On Sun, Apr 21, 2013 at 2:33 AM, Mauro Carvalho Chehab
<[email protected]> wrote:
> Em 21-04-2013 02:53, Andrey Smirnov escreveu:
>
>>> I applied all the MFD patches from this patchset (All 4 first ones), plus
>>> a
>>> follow up one for fixing the i2c related warning.
>>> I also squashed the REGMAP_I2C dependency into patch #4.
>>> It's all in mfd-next now, I'd appreciate if you could double check it's
>>> all
>>> fine.
>>
>>
>> I checked out latest
>> git://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-next.git and
>> applied patches 5 - 10, 12. There doesn't seem to be any problems, so
>> I think MFD part of the driver is good to go.
>>
>>>
>>> Mauro will take the rest, we made sure there won't be any merge conflict
>>> between our trees.
>>
>>
>> Mauro, I am not sure if you need me to rebase any of the patches(it
>> doesn't seem like you had a chance to make any further changes related
>> to this driver in media tree), but if you do, ping me and I'll get on
>> it.
>
>
> No, I don't need. The V4L parts are on my experimental tree:
>
> http://git.linuxtv.org/mchehab/experimental.git/shortlog/refs/heads/si476x
>
> I'll just merge it at the main tree or as a topic branch later today
> or (more likely) tomorrow.
>
> Please ping me today if you find anything wrong there.

It looks like everything is there(it's only one patch without mfd
part, after all) except patch #12
https://patchwork.kernel.org/patch/2461631/ of this patchset which is
a modified version of this guy
https://patchwork-mail.kernel.org/patch/2420751/

Other than that I think everything is fine.

>
> Regards,
> Mauro