diff options
author | Mauro Carvalho Chehab <mchehab@redhat.com> | 2013-04-25 17:26:43 +0400 |
---|---|---|
committer | Mauro Carvalho Chehab <mchehab@redhat.com> | 2013-04-25 17:26:43 +0400 |
commit | 966a1601877c3d29065ab2dde838cdae16ccc099 (patch) | |
tree | 2f07818cf4f093e78c05a7c004d15ec5c283b1d1 | |
parent | cfc3d6c44470c5509f1e8efb1577b49dbaeeb2da (diff) | |
parent | a9dc73e2952d76ed7961aa12b182c9a95c0be8dd (diff) | |
download | linux-966a1601877c3d29065ab2dde838cdae16ccc099.tar.xz |
Merge branch 'topic/si476x' into patchwork
* topic/si476x:
Revert "[media] mfd: Add chip properties handling code for SI476X MFD"
Revert "[media] mfd: Add the main bulk of core driver for SI476x code"
Revert "[media] mfd: Add commands abstraction layer for SI476X MFD"
[media] v4l2: Add a V4L2 driver for SI476X MFD
-rw-r--r-- | Documentation/video4linux/si476x.txt | 187 | ||||
-rw-r--r-- | drivers/media/radio/Kconfig | 16 | ||||
-rw-r--r-- | drivers/media/radio/Makefile | 1 | ||||
-rw-r--r-- | drivers/media/radio/radio-si476x.c | 1599 | ||||
-rw-r--r-- | drivers/mfd/si476x-cmd.c | 1554 | ||||
-rw-r--r-- | drivers/mfd/si476x-i2c.c | 886 | ||||
-rw-r--r-- | drivers/mfd/si476x-prop.c | 242 | ||||
-rw-r--r-- | include/media/si476x.h | 37 |
8 files changed, 1840 insertions, 2682 deletions
diff --git a/Documentation/video4linux/si476x.txt b/Documentation/video4linux/si476x.txt new file mode 100644 index 000000000000..d1a08db2cbd9 --- /dev/null +++ b/Documentation/video4linux/si476x.txt @@ -0,0 +1,187 @@ +SI476x Driver Readme +------------------------------------------------ + Copyright (C) 2013 Andrey Smirnov <andrew.smirnov@gmail.com> + +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 24e64a09884c..c0beee2fa37c 100644 --- a/drivers/media/radio/Kconfig +++ b/drivers/media/radio/Kconfig @@ -18,6 +18,22 @@ config RADIO_SI470X source "drivers/media/radio/si470x/Kconfig" +config RADIO_SI476X + tristate "Silicon Laboratories Si476x I2C FM Radio" + depends on I2C && VIDEO_V4L2 + depends on 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 303eaebdb85a..0dcdb320cfc7 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 000000000000..9430c6a29937 --- /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 <andrew.smirnov@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; 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, + const 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, + 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 = (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; + + if (f->tuner != 0 || + f->type != V4L2_TUNER_RADIO) + return -EINVAL; + + si476x_core_lock(radio->core); + + freq = clamp(freq, + si476x_bands[band].rangelow, + si476x_bands[band].rangehigh); + + 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))) { + 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, freq); + 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, + const 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 <andrew.smirnov@gmail.com>"); +MODULE_DESCRIPTION("Driver for Si4761/64/68 AM/FM Radio MFD Cell"); +MODULE_LICENSE("GPL"); diff --git a/drivers/mfd/si476x-cmd.c b/drivers/mfd/si476x-cmd.c deleted file mode 100644 index 71ac2e8d16d0..000000000000 --- 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 <andrew.smirnov@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; 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 <andrew.smirnov@gmail.com>"); -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 f5bc8e4bd4bf..000000000000 --- 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 <andrew.smirnov@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; 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 <andrew.smirnov@gmail.com>"); -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 d1f548aac0cf..000000000000 --- 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 <andrew.smirnov@gmail.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; 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); diff --git a/include/media/si476x.h b/include/media/si476x.h new file mode 100644 index 000000000000..e02e241e2d22 --- /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 <andrew.smirnov@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; 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*/ |