On Tue February 26 2013 07:38:54 Andrey Smirnov wrote: > From: Andrey Smirnov <andreysm@charmander.(none)> > > This commit adds a driver that exposes all the radio related > functionality of the Si476x series of chips via the V4L2 subsystem. > > Signed-off-by: Andrey Smirnov <andrew.smirnov@xxxxxxxxx> > --- > Documentation/video4linux/si476x.txt | 187 ++++ > drivers/media/radio/Kconfig | 17 + > drivers/media/radio/Makefile | 1 + > drivers/media/radio/radio-si476x.c | 1581 ++++++++++++++++++++++++++++++++++ > include/media/si476x.h | 426 +++++++++ > 5 files changed, 2212 insertions(+) > create mode 100644 Documentation/video4linux/si476x.txt > create mode 100644 drivers/media/radio/radio-si476x.c > create mode 100644 include/media/si476x.h > <snip> > diff --git a/drivers/media/radio/radio-si476x.c b/drivers/media/radio/radio-si476x.c > new file mode 100644 > index 0000000..568cd82 > --- /dev/null > +++ b/drivers/media/radio/radio-si476x.c > @@ -0,0 +1,1581 @@ > +/* > + * 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@xxxxxxxxx> > + * > + * 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); > + > +static const char * const deemphasis[] = { > + "75 us", > + "50 us", > +}; Obsolete array, can be removed. > + > +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", Question: what does it mean if this is disabled? That none of the antennas are working? That would imply that you get no reception at all. > + [SI476X_IDX_PHDIV_PRIMARY_COMBINING] = "Primary W/Secondary", Just write in full: "Primary with Secondary" > + [SI476X_IDX_PHDIV_PRIMARY_ANTENNA] = "Primary(Primary Antenna)", This becomes: "Primary Antenna" > + [SI476X_IDX_PHDIV_SECONDARY_ANTENNA] = "Primary(Seconadary Antenna)", "Secondary Antenna" > + [SI476X_IDX_PHDIV_SECONDARY_COMBINING] = "Secondary W/Primary", "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", Use lower case "of" and "to". Don't blame me, blame the style guides w.r.t. titles :-) > + .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 detrmine if two Typo: 'determine' > + * chips working in diversity mode have established a link > + * between each other and if the system as awhole uses "a whole" > + * 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, > + }, > +}; <snip> Regards, Hans -- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html