190 lines
		
	
	
	
		
			5.7 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			190 lines
		
	
	
	
		
			5.7 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
|   | /*
 | ||
|  |  * 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; | ||
|  | } |