Commit 55d58e98 authored by Hans Verkuil's avatar Hans Verkuil Committed by Mauro Carvalho Chehab
Browse files

[media] vivid: add support for radio receivers and transmitters



This adds radio receiver and transmitter support. Part of that is common
to both and so is placed in the radio-common source.

These drivers also support RDS. In order to generate valid RDS data a
simple RDS generator is implemented in rds-gen.

Signed-off-by: default avatarHans Verkuil <hans.verkuil@cisco.com>
Signed-off-by: default avatarMauro Carvalho Chehab <m.chehab@samsung.com>
parent 63881df9
Loading
Loading
Loading
Loading
+189 −0
Original line number Diff line number Diff line
/*
 * vivid-radio-common.c - common radio rx/tx support functions.
 *
 * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 *
 * This program is free software; you may 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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/videodev2.h>

#include "vivid-core.h"
#include "vivid-ctrls.h"
#include "vivid-radio-common.h"
#include "vivid-rds-gen.h"

/*
 * These functions are shared between the vivid receiver and transmitter
 * since both use the same frequency bands.
 */

const struct v4l2_frequency_band vivid_radio_bands[TOT_BANDS] = {
	/* Band FM */
	{
		.type = V4L2_TUNER_RADIO,
		.index = 0,
		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
			      V4L2_TUNER_CAP_FREQ_BANDS,
		.rangelow   = FM_FREQ_RANGE_LOW,
		.rangehigh  = FM_FREQ_RANGE_HIGH,
		.modulation = V4L2_BAND_MODULATION_FM,
	},
	/* Band AM */
	{
		.type = V4L2_TUNER_RADIO,
		.index = 1,
		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
		.rangelow   = AM_FREQ_RANGE_LOW,
		.rangehigh  = AM_FREQ_RANGE_HIGH,
		.modulation = V4L2_BAND_MODULATION_AM,
	},
	/* Band SW */
	{
		.type = V4L2_TUNER_RADIO,
		.index = 2,
		.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_FREQ_BANDS,
		.rangelow   = SW_FREQ_RANGE_LOW,
		.rangehigh  = SW_FREQ_RANGE_HIGH,
		.modulation = V4L2_BAND_MODULATION_AM,
	},
};

/*
 * Initialize the RDS generator. If we can loop, then the RDS generator
 * is set up with the values from the RDS TX controls, otherwise it
 * will fill in standard values using one of two alternates.
 */
void vivid_radio_rds_init(struct vivid_dev *dev)
{
	struct vivid_rds_gen *rds = &dev->rds_gen;
	bool alt = dev->radio_rx_rds_use_alternates;

	/* Do nothing, blocks will be filled by the transmitter */
	if (dev->radio_rds_loop && !dev->radio_tx_rds_controls)
		return;

	if (dev->radio_rds_loop) {
		v4l2_ctrl_lock(dev->radio_tx_rds_pi);
		rds->picode = dev->radio_tx_rds_pi->cur.val;
		rds->pty = dev->radio_tx_rds_pty->cur.val;
		rds->mono_stereo = dev->radio_tx_rds_mono_stereo->cur.val;
		rds->art_head = dev->radio_tx_rds_art_head->cur.val;
		rds->compressed = dev->radio_tx_rds_compressed->cur.val;
		rds->dyn_pty = dev->radio_tx_rds_dyn_pty->cur.val;
		rds->ta = dev->radio_tx_rds_ta->cur.val;
		rds->tp = dev->radio_tx_rds_tp->cur.val;
		rds->ms = dev->radio_tx_rds_ms->cur.val;
		strlcpy(rds->psname,
			dev->radio_tx_rds_psname->p_cur.p_char,
			sizeof(rds->psname));
		strlcpy(rds->radiotext,
			dev->radio_tx_rds_radiotext->p_cur.p_char + alt * 64,
			sizeof(rds->radiotext));
		v4l2_ctrl_unlock(dev->radio_tx_rds_pi);
	} else {
		vivid_rds_gen_fill(rds, dev->radio_rx_freq, alt);
	}
	if (dev->radio_rx_rds_controls) {
		v4l2_ctrl_s_ctrl(dev->radio_rx_rds_pty, rds->pty);
		v4l2_ctrl_s_ctrl(dev->radio_rx_rds_ta, rds->ta);
		v4l2_ctrl_s_ctrl(dev->radio_rx_rds_tp, rds->tp);
		v4l2_ctrl_s_ctrl(dev->radio_rx_rds_ms, rds->ms);
		v4l2_ctrl_s_ctrl_string(dev->radio_rx_rds_psname, rds->psname);
		v4l2_ctrl_s_ctrl_string(dev->radio_rx_rds_radiotext, rds->radiotext);
		if (!dev->radio_rds_loop)
			dev->radio_rx_rds_use_alternates = !dev->radio_rx_rds_use_alternates;
	}
	vivid_rds_generate(rds);
}

/*
 * Calculate the emulated signal quality taking into account the frequency
 * the transmitter is using.
 */
static void vivid_radio_calc_sig_qual(struct vivid_dev *dev)
{
	int mod = 16000;
	int delta = 800;
	int sig_qual, sig_qual_tx = mod;

	/*
	 * For SW and FM there is a channel every 1000 kHz, for AM there is one
	 * every 100 kHz.
	 */
	if (dev->radio_rx_freq <= AM_FREQ_RANGE_HIGH) {
		mod /= 10;
		delta /= 10;
	}
	sig_qual = (dev->radio_rx_freq + delta) % mod - delta;
	if (dev->has_radio_tx)
		sig_qual_tx = dev->radio_rx_freq - dev->radio_tx_freq;
	if (abs(sig_qual_tx) <= abs(sig_qual)) {
		sig_qual = sig_qual_tx;
		/*
		 * Zero the internal rds buffer if we are going to loop
		 * rds blocks.
		 */
		if (!dev->radio_rds_loop && !dev->radio_tx_rds_controls)
			memset(dev->rds_gen.data, 0,
			       sizeof(dev->rds_gen.data));
		dev->radio_rds_loop = dev->radio_rx_freq >= FM_FREQ_RANGE_LOW;
	} else {
		dev->radio_rds_loop = false;
	}
	if (dev->radio_rx_freq <= AM_FREQ_RANGE_HIGH)
		sig_qual *= 10;
	dev->radio_rx_sig_qual = sig_qual;
}

int vivid_radio_g_frequency(struct file *file, const unsigned *pfreq, struct v4l2_frequency *vf)
{
	if (vf->tuner != 0)
		return -EINVAL;
	vf->frequency = *pfreq;
	return 0;
}

int vivid_radio_s_frequency(struct file *file, unsigned *pfreq, const struct v4l2_frequency *vf)
{
	struct vivid_dev *dev = video_drvdata(file);
	unsigned freq;
	unsigned band;

	if (vf->tuner != 0)
		return -EINVAL;

	if (vf->frequency >= (FM_FREQ_RANGE_LOW + SW_FREQ_RANGE_HIGH) / 2)
		band = BAND_FM;
	else if (vf->frequency <= (AM_FREQ_RANGE_HIGH + SW_FREQ_RANGE_LOW) / 2)
		band = BAND_AM;
	else
		band = BAND_SW;

	freq = clamp_t(u32, vf->frequency, vivid_radio_bands[band].rangelow,
					   vivid_radio_bands[band].rangehigh);
	*pfreq = freq;

	/*
	 * For both receiver and transmitter recalculate the signal quality
	 * (since that depends on both frequencies) and re-init the rds
	 * generator.
	 */
	vivid_radio_calc_sig_qual(dev);
	vivid_radio_rds_init(dev);
	return 0;
}
+40 −0
Original line number Diff line number Diff line
/*
 * vivid-radio-common.h - common radio rx/tx support functions.
 *
 * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 *
 * This program is free software; you may 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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef _VIVID_RADIO_COMMON_H_
#define _VIVID_RADIO_COMMON_H_

/* The supported radio frequency ranges in kHz */
#define FM_FREQ_RANGE_LOW       (64000U * 16U)
#define FM_FREQ_RANGE_HIGH      (108000U * 16U)
#define AM_FREQ_RANGE_LOW       (520U * 16U)
#define AM_FREQ_RANGE_HIGH      (1710U * 16U)
#define SW_FREQ_RANGE_LOW       (2300U * 16U)
#define SW_FREQ_RANGE_HIGH      (26100U * 16U)

enum { BAND_FM, BAND_AM, BAND_SW, TOT_BANDS };

extern const struct v4l2_frequency_band vivid_radio_bands[TOT_BANDS];

int vivid_radio_g_frequency(struct file *file, const unsigned *freq, struct v4l2_frequency *vf);
int vivid_radio_s_frequency(struct file *file, unsigned *freq, const struct v4l2_frequency *vf);

void vivid_radio_rds_init(struct vivid_dev *dev);

#endif
+287 −0
Original line number Diff line number Diff line
/*
 * vivid-radio-rx.c - radio receiver support functions.
 *
 * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 *
 * This program is free software; you may 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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/videodev2.h>
#include <linux/v4l2-dv-timings.h>
#include <media/v4l2-common.h>
#include <media/v4l2-event.h>
#include <media/v4l2-dv-timings.h>

#include "vivid-core.h"
#include "vivid-ctrls.h"
#include "vivid-radio-common.h"
#include "vivid-rds-gen.h"
#include "vivid-radio-rx.h"

ssize_t vivid_radio_rx_read(struct file *file, char __user *buf,
			 size_t size, loff_t *offset)
{
	struct vivid_dev *dev = video_drvdata(file);
	struct timespec ts;
	struct v4l2_rds_data *data = dev->rds_gen.data;
	bool use_alternates;
	unsigned blk;
	int perc;
	int i;

	if (dev->radio_rx_rds_controls)
		return -EINVAL;
	if (size < sizeof(*data))
		return 0;
	size = sizeof(*data) * (size / sizeof(*data));

	if (mutex_lock_interruptible(&dev->mutex))
		return -ERESTARTSYS;
	if (dev->radio_rx_rds_owner &&
	    file->private_data != dev->radio_rx_rds_owner) {
		mutex_unlock(&dev->mutex);
		return -EBUSY;
	}
	if (dev->radio_rx_rds_owner == NULL) {
		vivid_radio_rds_init(dev);
		dev->radio_rx_rds_owner = file->private_data;
	}

retry:
	ktime_get_ts(&ts);
	use_alternates = ts.tv_sec % 10 >= 5;
	if (dev->radio_rx_rds_last_block == 0 ||
	    dev->radio_rx_rds_use_alternates != use_alternates) {
		dev->radio_rx_rds_use_alternates = use_alternates;
		/* Re-init the RDS generator */
		vivid_radio_rds_init(dev);
	}
	ts = timespec_sub(ts, dev->radio_rds_init_ts);
	blk = ts.tv_sec * 100 + ts.tv_nsec / 10000000;
	blk = (blk * VIVID_RDS_GEN_BLOCKS) / 500;
	if (blk >= dev->radio_rx_rds_last_block + VIVID_RDS_GEN_BLOCKS)
		dev->radio_rx_rds_last_block = blk - VIVID_RDS_GEN_BLOCKS + 1;

	/*
	 * No data is available if there hasn't been time to get new data,
	 * or if the RDS receiver has been disabled, or if we use the data
	 * from the RDS transmitter and that RDS transmitter has been disabled,
	 * or if the signal quality is too weak.
	 */
	if (blk == dev->radio_rx_rds_last_block || !dev->radio_rx_rds_enabled ||
	    (dev->radio_rds_loop && !(dev->radio_tx_subchans & V4L2_TUNER_SUB_RDS)) ||
	    abs(dev->radio_rx_sig_qual) > 200) {
		mutex_unlock(&dev->mutex);
		if (file->f_flags & O_NONBLOCK)
			return -EWOULDBLOCK;
		if (msleep_interruptible(20) && signal_pending(current))
			return -EINTR;
		if (mutex_lock_interruptible(&dev->mutex))
			return -ERESTARTSYS;
		goto retry;
	}

	/* abs(dev->radio_rx_sig_qual) <= 200, map that to a 0-50% range */
	perc = abs(dev->radio_rx_sig_qual) / 4;

	for (i = 0; i < size && blk > dev->radio_rx_rds_last_block;
			dev->radio_rx_rds_last_block++) {
		unsigned data_blk = dev->radio_rx_rds_last_block % VIVID_RDS_GEN_BLOCKS;
		struct v4l2_rds_data rds = data[data_blk];

		if (data_blk == 0 && dev->radio_rds_loop)
			vivid_radio_rds_init(dev);
		if (perc && prandom_u32_max(100) < perc) {
			switch (prandom_u32_max(4)) {
			case 0:
				rds.block |= V4L2_RDS_BLOCK_CORRECTED;
				break;
			case 1:
				rds.block |= V4L2_RDS_BLOCK_INVALID;
				break;
			case 2:
				rds.block |= V4L2_RDS_BLOCK_ERROR;
				rds.lsb = prandom_u32_max(256);
				rds.msb = prandom_u32_max(256);
				break;
			case 3: /* Skip block altogether */
				if (i)
					continue;
				/*
				 * Must make sure at least one block is
				 * returned, otherwise the application
				 * might think that end-of-file occurred.
				 */
				break;
			}
		}
		if (copy_to_user(buf + i, &rds, sizeof(rds))) {
			i = -EFAULT;
			break;
		}
		i += sizeof(rds);
	}
	mutex_unlock(&dev->mutex);
	return i;
}

unsigned int vivid_radio_rx_poll(struct file *file, struct poll_table_struct *wait)
{
	return POLLIN | POLLRDNORM | v4l2_ctrl_poll(file, wait);
}

int vivid_radio_rx_enum_freq_bands(struct file *file, void *fh, struct v4l2_frequency_band *band)
{
	if (band->tuner != 0)
		return -EINVAL;

	if (band->index >= TOT_BANDS)
		return -EINVAL;

	*band = vivid_radio_bands[band->index];
	return 0;
}

int vivid_radio_rx_s_hw_freq_seek(struct file *file, void *fh, const struct v4l2_hw_freq_seek *a)
{
	struct vivid_dev *dev = video_drvdata(file);
	unsigned low, high;
	unsigned freq;
	unsigned spacing;
	unsigned band;

	if (a->tuner)
		return -EINVAL;
	if (a->wrap_around && dev->radio_rx_hw_seek_mode == VIVID_HW_SEEK_BOUNDED)
		return -EINVAL;

	if (!a->wrap_around && dev->radio_rx_hw_seek_mode == VIVID_HW_SEEK_WRAP)
		return -EINVAL;
	if (!a->rangelow ^ !a->rangehigh)
		return -EINVAL;

	if (file->f_flags & O_NONBLOCK)
		return -EWOULDBLOCK;

	if (a->rangelow) {
		for (band = 0; band < TOT_BANDS; band++)
			if (a->rangelow >= vivid_radio_bands[band].rangelow &&
			    a->rangehigh <= vivid_radio_bands[band].rangehigh)
				break;
		if (band == TOT_BANDS)
			return -EINVAL;
		if (!dev->radio_rx_hw_seek_prog_lim &&
		    (a->rangelow != vivid_radio_bands[band].rangelow ||
		     a->rangehigh != vivid_radio_bands[band].rangehigh))
			return -EINVAL;
		low = a->rangelow;
		high = a->rangehigh;
	} else {
		for (band = 0; band < TOT_BANDS; band++)
			if (dev->radio_rx_freq >= vivid_radio_bands[band].rangelow &&
			    dev->radio_rx_freq <= vivid_radio_bands[band].rangehigh)
				break;
		low = vivid_radio_bands[band].rangelow;
		high = vivid_radio_bands[band].rangehigh;
	}
	spacing = band == BAND_AM ? 1600 : 16000;
	freq = clamp(dev->radio_rx_freq, low, high);

	if (a->seek_upward) {
		freq = spacing * (freq / spacing) + spacing;
		if (freq > high) {
			if (!a->wrap_around)
				return -ENODATA;
			freq = spacing * (low / spacing) + spacing;
			if (freq >= dev->radio_rx_freq)
				return -ENODATA;
		}
	} else {
		freq = spacing * ((freq + spacing - 1) / spacing) - spacing;
		if (freq < low) {
			if (!a->wrap_around)
				return -ENODATA;
			freq = spacing * ((high + spacing - 1) / spacing) - spacing;
			if (freq <= dev->radio_rx_freq)
				return -ENODATA;
		}
	}
	return 0;
}

int vivid_radio_rx_g_tuner(struct file *file, void *fh, struct v4l2_tuner *vt)
{
	struct vivid_dev *dev = video_drvdata(file);
	int delta = 800;
	int sig_qual;

	if (vt->index > 0)
		return -EINVAL;

	strlcpy(vt->name, "AM/FM/SW Receiver", sizeof(vt->name));
	vt->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
			 V4L2_TUNER_CAP_FREQ_BANDS | V4L2_TUNER_CAP_RDS |
			 (dev->radio_rx_rds_controls ?
				V4L2_TUNER_CAP_RDS_CONTROLS :
				V4L2_TUNER_CAP_RDS_BLOCK_IO) |
			 (dev->radio_rx_hw_seek_prog_lim ?
				V4L2_TUNER_CAP_HWSEEK_PROG_LIM : 0);
	switch (dev->radio_rx_hw_seek_mode) {
	case VIVID_HW_SEEK_BOUNDED:
		vt->capability |= V4L2_TUNER_CAP_HWSEEK_BOUNDED;
		break;
	case VIVID_HW_SEEK_WRAP:
		vt->capability |= V4L2_TUNER_CAP_HWSEEK_WRAP;
		break;
	case VIVID_HW_SEEK_BOTH:
		vt->capability |= V4L2_TUNER_CAP_HWSEEK_WRAP |
				  V4L2_TUNER_CAP_HWSEEK_BOUNDED;
		break;
	}
	vt->rangelow = AM_FREQ_RANGE_LOW;
	vt->rangehigh = FM_FREQ_RANGE_HIGH;
	sig_qual = dev->radio_rx_sig_qual;
	vt->signal = abs(sig_qual) > delta ? 0 :
		     0xffff - (abs(sig_qual) * 0xffff) / delta;
	vt->afc = sig_qual > delta ? 0 : sig_qual;
	if (abs(sig_qual) > delta)
		vt->rxsubchans = 0;
	else if (dev->radio_rx_freq < FM_FREQ_RANGE_LOW || vt->signal < 0x8000)
		vt->rxsubchans = V4L2_TUNER_SUB_MONO;
	else if (dev->radio_rds_loop && !(dev->radio_tx_subchans & V4L2_TUNER_SUB_STEREO))
		vt->rxsubchans = V4L2_TUNER_SUB_MONO;
	else
		vt->rxsubchans = V4L2_TUNER_SUB_STEREO;
	if (dev->radio_rx_rds_enabled &&
	    (!dev->radio_rds_loop || (dev->radio_tx_subchans & V4L2_TUNER_SUB_RDS)) &&
	    dev->radio_rx_freq >= FM_FREQ_RANGE_LOW && vt->signal >= 0xc000)
		vt->rxsubchans |= V4L2_TUNER_SUB_RDS;
	if (dev->radio_rx_rds_controls)
		vivid_radio_rds_init(dev);
	vt->audmode = dev->radio_rx_audmode;
	return 0;
}

int vivid_radio_rx_s_tuner(struct file *file, void *fh, const struct v4l2_tuner *vt)
{
	struct vivid_dev *dev = video_drvdata(file);

	if (vt->index)
		return -EINVAL;
	dev->radio_rx_audmode = vt->audmode >= V4L2_TUNER_MODE_STEREO;
	return 0;
}
+31 −0
Original line number Diff line number Diff line
/*
 * vivid-radio-rx.h - radio receiver support functions.
 *
 * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 *
 * This program is free software; you may 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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef _VIVID_RADIO_RX_H_
#define _VIVID_RADIO_RX_H_

ssize_t vivid_radio_rx_read(struct file *, char __user *, size_t, loff_t *);
unsigned int vivid_radio_rx_poll(struct file *file, struct poll_table_struct *wait);

int vivid_radio_rx_enum_freq_bands(struct file *file, void *fh, struct v4l2_frequency_band *band);
int vivid_radio_rx_s_hw_freq_seek(struct file *file, void *fh, const struct v4l2_hw_freq_seek *a);
int vivid_radio_rx_g_tuner(struct file *file, void *fh, struct v4l2_tuner *vt);
int vivid_radio_rx_s_tuner(struct file *file, void *fh, const struct v4l2_tuner *vt);

#endif
+141 −0
Original line number Diff line number Diff line
/*
 * vivid-radio-tx.c - radio transmitter support functions.
 *
 * Copyright 2014 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 *
 * This program is free software; you may 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.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include <linux/errno.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/videodev2.h>
#include <linux/v4l2-dv-timings.h>
#include <media/v4l2-common.h>
#include <media/v4l2-event.h>
#include <media/v4l2-dv-timings.h>

#include "vivid-core.h"
#include "vivid-ctrls.h"
#include "vivid-radio-common.h"
#include "vivid-radio-tx.h"

ssize_t vivid_radio_tx_write(struct file *file, const char __user *buf,
			  size_t size, loff_t *offset)
{
	struct vivid_dev *dev = video_drvdata(file);
	struct v4l2_rds_data *data = dev->rds_gen.data;
	struct timespec ts;
	unsigned blk;
	int i;

	if (dev->radio_tx_rds_controls)
		return -EINVAL;

	if (size < sizeof(*data))
		return -EINVAL;
	size = sizeof(*data) * (size / sizeof(*data));

	if (mutex_lock_interruptible(&dev->mutex))
		return -ERESTARTSYS;
	if (dev->radio_tx_rds_owner &&
	    file->private_data != dev->radio_tx_rds_owner) {
		mutex_unlock(&dev->mutex);
		return -EBUSY;
	}
	dev->radio_tx_rds_owner = file->private_data;

retry:
	ktime_get_ts(&ts);
	ts = timespec_sub(ts, dev->radio_rds_init_ts);
	blk = ts.tv_sec * 100 + ts.tv_nsec / 10000000;
	blk = (blk * VIVID_RDS_GEN_BLOCKS) / 500;
	if (blk - VIVID_RDS_GEN_BLOCKS >= dev->radio_tx_rds_last_block)
		dev->radio_tx_rds_last_block = blk - VIVID_RDS_GEN_BLOCKS + 1;

	/*
	 * No data is available if there hasn't been time to get new data,
	 * or if the RDS receiver has been disabled, or if we use the data
	 * from the RDS transmitter and that RDS transmitter has been disabled,
	 * or if the signal quality is too weak.
	 */
	if (blk == dev->radio_tx_rds_last_block ||
	    !(dev->radio_tx_subchans & V4L2_TUNER_SUB_RDS)) {
		mutex_unlock(&dev->mutex);
		if (file->f_flags & O_NONBLOCK)
			return -EWOULDBLOCK;
		if (msleep_interruptible(20) && signal_pending(current))
			return -EINTR;
		if (mutex_lock_interruptible(&dev->mutex))
			return -ERESTARTSYS;
		goto retry;
	}

	for (i = 0; i < size && blk > dev->radio_tx_rds_last_block;
			dev->radio_tx_rds_last_block++) {
		unsigned data_blk = dev->radio_tx_rds_last_block % VIVID_RDS_GEN_BLOCKS;
		struct v4l2_rds_data rds;

		if (copy_from_user(&rds, buf + i, sizeof(rds))) {
			i = -EFAULT;
			break;
		}
		i += sizeof(rds);
		if (!dev->radio_rds_loop)
			continue;
		if ((rds.block & V4L2_RDS_BLOCK_MSK) == V4L2_RDS_BLOCK_INVALID ||
		    (rds.block & V4L2_RDS_BLOCK_ERROR))
			continue;
		rds.block &= V4L2_RDS_BLOCK_MSK;
		data[data_blk] = rds;
	}
	mutex_unlock(&dev->mutex);
	return i;
}

unsigned int vivid_radio_tx_poll(struct file *file, struct poll_table_struct *wait)
{
	return POLLOUT | POLLWRNORM | v4l2_ctrl_poll(file, wait);
}

int vidioc_g_modulator(struct file *file, void *fh, struct v4l2_modulator *a)
{
	struct vivid_dev *dev = video_drvdata(file);

	if (a->index > 0)
		return -EINVAL;

	strlcpy(a->name, "AM/FM/SW Transmitter", sizeof(a->name));
	a->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
			V4L2_TUNER_CAP_FREQ_BANDS | V4L2_TUNER_CAP_RDS |
			(dev->radio_tx_rds_controls ?
				V4L2_TUNER_CAP_RDS_CONTROLS :
				V4L2_TUNER_CAP_RDS_BLOCK_IO);
	a->rangelow = AM_FREQ_RANGE_LOW;
	a->rangehigh = FM_FREQ_RANGE_HIGH;
	a->txsubchans = dev->radio_tx_subchans;
	return 0;
}

int vidioc_s_modulator(struct file *file, void *fh, const struct v4l2_modulator *a)
{
	struct vivid_dev *dev = video_drvdata(file);

	if (a->index)
		return -EINVAL;
	if (a->txsubchans & ~0x13)
		return -EINVAL;
	dev->radio_tx_subchans = a->txsubchans;
	return 0;
}
Loading