forked from Minki/linux
d585c1e14a
FM (frequency modulated) signal for SDR is generated by varying the phase, where phase variation is proportional to input signal. It is seen that, the larger phase increments leads to discontinuities in the signal recovered after demodulation. Reducing the extent of phase variation with respect to input signal, equivalent to reducing the modulation index. Tested using FM receiver flow graph in gnuradio-companion. Cc: Antti Palosaari <crope@iki.fi> Signed-off-by: Prashant Laddha <prladdha@cisco.com> Signed-off-by: Hans Verkuil <hans.verkuil@cisco.com> Signed-off-by: Mauro Carvalho Chehab <mchehab@osg.samsung.com>
488 lines
13 KiB
C
488 lines
13 KiB
C
/*
|
|
* vivid-sdr-cap.c - software defined radio 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/kthread.h>
|
|
#include <linux/freezer.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 <linux/fixp-arith.h>
|
|
|
|
#include "vivid-core.h"
|
|
#include "vivid-ctrls.h"
|
|
#include "vivid-sdr-cap.h"
|
|
|
|
static const struct v4l2_frequency_band bands_adc[] = {
|
|
{
|
|
.tuner = 0,
|
|
.type = V4L2_TUNER_ADC,
|
|
.index = 0,
|
|
.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
|
|
.rangelow = 300000,
|
|
.rangehigh = 300000,
|
|
},
|
|
{
|
|
.tuner = 0,
|
|
.type = V4L2_TUNER_ADC,
|
|
.index = 1,
|
|
.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
|
|
.rangelow = 900001,
|
|
.rangehigh = 2800000,
|
|
},
|
|
{
|
|
.tuner = 0,
|
|
.type = V4L2_TUNER_ADC,
|
|
.index = 2,
|
|
.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
|
|
.rangelow = 3200000,
|
|
.rangehigh = 3200000,
|
|
},
|
|
};
|
|
|
|
/* ADC band midpoints */
|
|
#define BAND_ADC_0 ((bands_adc[0].rangehigh + bands_adc[1].rangelow) / 2)
|
|
#define BAND_ADC_1 ((bands_adc[1].rangehigh + bands_adc[2].rangelow) / 2)
|
|
|
|
static const struct v4l2_frequency_band bands_fm[] = {
|
|
{
|
|
.tuner = 1,
|
|
.type = V4L2_TUNER_RF,
|
|
.index = 0,
|
|
.capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS,
|
|
.rangelow = 50000000,
|
|
.rangehigh = 2000000000,
|
|
},
|
|
};
|
|
|
|
static void vivid_thread_sdr_cap_tick(struct vivid_dev *dev)
|
|
{
|
|
struct vivid_buffer *sdr_cap_buf = NULL;
|
|
|
|
dprintk(dev, 1, "SDR Capture Thread Tick\n");
|
|
|
|
/* Drop a certain percentage of buffers. */
|
|
if (dev->perc_dropped_buffers &&
|
|
prandom_u32_max(100) < dev->perc_dropped_buffers)
|
|
return;
|
|
|
|
spin_lock(&dev->slock);
|
|
if (!list_empty(&dev->sdr_cap_active)) {
|
|
sdr_cap_buf = list_entry(dev->sdr_cap_active.next,
|
|
struct vivid_buffer, list);
|
|
list_del(&sdr_cap_buf->list);
|
|
}
|
|
spin_unlock(&dev->slock);
|
|
|
|
if (sdr_cap_buf) {
|
|
sdr_cap_buf->vb.v4l2_buf.sequence = dev->sdr_cap_seq_count;
|
|
vivid_sdr_cap_process(dev, sdr_cap_buf);
|
|
v4l2_get_timestamp(&sdr_cap_buf->vb.v4l2_buf.timestamp);
|
|
sdr_cap_buf->vb.v4l2_buf.timestamp.tv_sec += dev->time_wrap_offset;
|
|
vb2_buffer_done(&sdr_cap_buf->vb, dev->dqbuf_error ?
|
|
VB2_BUF_STATE_ERROR : VB2_BUF_STATE_DONE);
|
|
dev->dqbuf_error = false;
|
|
}
|
|
}
|
|
|
|
static int vivid_thread_sdr_cap(void *data)
|
|
{
|
|
struct vivid_dev *dev = data;
|
|
u64 samples_since_start;
|
|
u64 buffers_since_start;
|
|
u64 next_jiffies_since_start;
|
|
unsigned long jiffies_since_start;
|
|
unsigned long cur_jiffies;
|
|
unsigned wait_jiffies;
|
|
|
|
dprintk(dev, 1, "SDR Capture Thread Start\n");
|
|
|
|
set_freezable();
|
|
|
|
/* Resets frame counters */
|
|
dev->sdr_cap_seq_offset = 0;
|
|
if (dev->seq_wrap)
|
|
dev->sdr_cap_seq_offset = 0xffffff80U;
|
|
dev->jiffies_sdr_cap = jiffies;
|
|
dev->sdr_cap_seq_resync = false;
|
|
|
|
for (;;) {
|
|
try_to_freeze();
|
|
if (kthread_should_stop())
|
|
break;
|
|
|
|
mutex_lock(&dev->mutex);
|
|
cur_jiffies = jiffies;
|
|
if (dev->sdr_cap_seq_resync) {
|
|
dev->jiffies_sdr_cap = cur_jiffies;
|
|
dev->sdr_cap_seq_offset = dev->sdr_cap_seq_count + 1;
|
|
dev->sdr_cap_seq_count = 0;
|
|
dev->sdr_cap_seq_resync = false;
|
|
}
|
|
/* Calculate the number of jiffies since we started streaming */
|
|
jiffies_since_start = cur_jiffies - dev->jiffies_sdr_cap;
|
|
/* Get the number of buffers streamed since the start */
|
|
buffers_since_start = (u64)jiffies_since_start * dev->sdr_adc_freq +
|
|
(HZ * SDR_CAP_SAMPLES_PER_BUF) / 2;
|
|
do_div(buffers_since_start, HZ * SDR_CAP_SAMPLES_PER_BUF);
|
|
|
|
/*
|
|
* After more than 0xf0000000 (rounded down to a multiple of
|
|
* 'jiffies-per-day' to ease jiffies_to_msecs calculation)
|
|
* jiffies have passed since we started streaming reset the
|
|
* counters and keep track of the sequence offset.
|
|
*/
|
|
if (jiffies_since_start > JIFFIES_RESYNC) {
|
|
dev->jiffies_sdr_cap = cur_jiffies;
|
|
dev->sdr_cap_seq_offset = buffers_since_start;
|
|
buffers_since_start = 0;
|
|
}
|
|
dev->sdr_cap_seq_count = buffers_since_start + dev->sdr_cap_seq_offset;
|
|
|
|
vivid_thread_sdr_cap_tick(dev);
|
|
mutex_unlock(&dev->mutex);
|
|
|
|
/*
|
|
* Calculate the number of samples streamed since we started,
|
|
* not including the current buffer.
|
|
*/
|
|
samples_since_start = buffers_since_start * SDR_CAP_SAMPLES_PER_BUF;
|
|
|
|
/* And the number of jiffies since we started */
|
|
jiffies_since_start = jiffies - dev->jiffies_sdr_cap;
|
|
|
|
/* Increase by the number of samples in one buffer */
|
|
samples_since_start += SDR_CAP_SAMPLES_PER_BUF;
|
|
/*
|
|
* Calculate when that next buffer is supposed to start
|
|
* in jiffies since we started streaming.
|
|
*/
|
|
next_jiffies_since_start = samples_since_start * HZ +
|
|
dev->sdr_adc_freq / 2;
|
|
do_div(next_jiffies_since_start, dev->sdr_adc_freq);
|
|
/* If it is in the past, then just schedule asap */
|
|
if (next_jiffies_since_start < jiffies_since_start)
|
|
next_jiffies_since_start = jiffies_since_start;
|
|
|
|
wait_jiffies = next_jiffies_since_start - jiffies_since_start;
|
|
schedule_timeout_interruptible(wait_jiffies ? wait_jiffies : 1);
|
|
}
|
|
dprintk(dev, 1, "SDR Capture Thread End\n");
|
|
return 0;
|
|
}
|
|
|
|
static int sdr_cap_queue_setup(struct vb2_queue *vq, const struct v4l2_format *fmt,
|
|
unsigned *nbuffers, unsigned *nplanes,
|
|
unsigned sizes[], void *alloc_ctxs[])
|
|
{
|
|
/* 2 = max 16-bit sample returned */
|
|
sizes[0] = SDR_CAP_SAMPLES_PER_BUF * 2;
|
|
*nplanes = 1;
|
|
return 0;
|
|
}
|
|
|
|
static int sdr_cap_buf_prepare(struct vb2_buffer *vb)
|
|
{
|
|
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
|
|
unsigned size = SDR_CAP_SAMPLES_PER_BUF * 2;
|
|
|
|
dprintk(dev, 1, "%s\n", __func__);
|
|
|
|
if (dev->buf_prepare_error) {
|
|
/*
|
|
* Error injection: test what happens if buf_prepare() returns
|
|
* an error.
|
|
*/
|
|
dev->buf_prepare_error = false;
|
|
return -EINVAL;
|
|
}
|
|
if (vb2_plane_size(vb, 0) < size) {
|
|
dprintk(dev, 1, "%s data will not fit into plane (%lu < %u)\n",
|
|
__func__, vb2_plane_size(vb, 0), size);
|
|
return -EINVAL;
|
|
}
|
|
vb2_set_plane_payload(vb, 0, size);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sdr_cap_buf_queue(struct vb2_buffer *vb)
|
|
{
|
|
struct vivid_dev *dev = vb2_get_drv_priv(vb->vb2_queue);
|
|
struct vivid_buffer *buf = container_of(vb, struct vivid_buffer, vb);
|
|
|
|
dprintk(dev, 1, "%s\n", __func__);
|
|
|
|
spin_lock(&dev->slock);
|
|
list_add_tail(&buf->list, &dev->sdr_cap_active);
|
|
spin_unlock(&dev->slock);
|
|
}
|
|
|
|
static int sdr_cap_start_streaming(struct vb2_queue *vq, unsigned count)
|
|
{
|
|
struct vivid_dev *dev = vb2_get_drv_priv(vq);
|
|
int err = 0;
|
|
|
|
dprintk(dev, 1, "%s\n", __func__);
|
|
dev->sdr_cap_seq_count = 0;
|
|
if (dev->start_streaming_error) {
|
|
dev->start_streaming_error = false;
|
|
err = -EINVAL;
|
|
} else if (dev->kthread_sdr_cap == NULL) {
|
|
dev->kthread_sdr_cap = kthread_run(vivid_thread_sdr_cap, dev,
|
|
"%s-sdr-cap", dev->v4l2_dev.name);
|
|
|
|
if (IS_ERR(dev->kthread_sdr_cap)) {
|
|
v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n");
|
|
err = PTR_ERR(dev->kthread_sdr_cap);
|
|
dev->kthread_sdr_cap = NULL;
|
|
}
|
|
}
|
|
if (err) {
|
|
struct vivid_buffer *buf, *tmp;
|
|
|
|
list_for_each_entry_safe(buf, tmp, &dev->sdr_cap_active, list) {
|
|
list_del(&buf->list);
|
|
vb2_buffer_done(&buf->vb, VB2_BUF_STATE_QUEUED);
|
|
}
|
|
}
|
|
return err;
|
|
}
|
|
|
|
/* abort streaming and wait for last buffer */
|
|
static void sdr_cap_stop_streaming(struct vb2_queue *vq)
|
|
{
|
|
struct vivid_dev *dev = vb2_get_drv_priv(vq);
|
|
|
|
if (dev->kthread_sdr_cap == NULL)
|
|
return;
|
|
|
|
while (!list_empty(&dev->sdr_cap_active)) {
|
|
struct vivid_buffer *buf;
|
|
|
|
buf = list_entry(dev->sdr_cap_active.next, struct vivid_buffer, list);
|
|
list_del(&buf->list);
|
|
vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR);
|
|
}
|
|
|
|
/* shutdown control thread */
|
|
mutex_unlock(&dev->mutex);
|
|
kthread_stop(dev->kthread_sdr_cap);
|
|
dev->kthread_sdr_cap = NULL;
|
|
mutex_lock(&dev->mutex);
|
|
}
|
|
|
|
const struct vb2_ops vivid_sdr_cap_qops = {
|
|
.queue_setup = sdr_cap_queue_setup,
|
|
.buf_prepare = sdr_cap_buf_prepare,
|
|
.buf_queue = sdr_cap_buf_queue,
|
|
.start_streaming = sdr_cap_start_streaming,
|
|
.stop_streaming = sdr_cap_stop_streaming,
|
|
.wait_prepare = vb2_ops_wait_prepare,
|
|
.wait_finish = vb2_ops_wait_finish,
|
|
};
|
|
|
|
int vivid_sdr_enum_freq_bands(struct file *file, void *fh, struct v4l2_frequency_band *band)
|
|
{
|
|
switch (band->tuner) {
|
|
case 0:
|
|
if (band->index >= ARRAY_SIZE(bands_adc))
|
|
return -EINVAL;
|
|
*band = bands_adc[band->index];
|
|
return 0;
|
|
case 1:
|
|
if (band->index >= ARRAY_SIZE(bands_fm))
|
|
return -EINVAL;
|
|
*band = bands_fm[band->index];
|
|
return 0;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
int vivid_sdr_g_frequency(struct file *file, void *fh, struct v4l2_frequency *vf)
|
|
{
|
|
struct vivid_dev *dev = video_drvdata(file);
|
|
|
|
switch (vf->tuner) {
|
|
case 0:
|
|
vf->frequency = dev->sdr_adc_freq;
|
|
vf->type = V4L2_TUNER_ADC;
|
|
return 0;
|
|
case 1:
|
|
vf->frequency = dev->sdr_fm_freq;
|
|
vf->type = V4L2_TUNER_RF;
|
|
return 0;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
int vivid_sdr_s_frequency(struct file *file, void *fh, const struct v4l2_frequency *vf)
|
|
{
|
|
struct vivid_dev *dev = video_drvdata(file);
|
|
unsigned freq = vf->frequency;
|
|
unsigned band;
|
|
|
|
switch (vf->tuner) {
|
|
case 0:
|
|
if (vf->type != V4L2_TUNER_ADC)
|
|
return -EINVAL;
|
|
if (freq < BAND_ADC_0)
|
|
band = 0;
|
|
else if (freq < BAND_ADC_1)
|
|
band = 1;
|
|
else
|
|
band = 2;
|
|
|
|
freq = clamp_t(unsigned, freq,
|
|
bands_adc[band].rangelow,
|
|
bands_adc[band].rangehigh);
|
|
|
|
if (vb2_is_streaming(&dev->vb_sdr_cap_q) &&
|
|
freq != dev->sdr_adc_freq) {
|
|
/* resync the thread's timings */
|
|
dev->sdr_cap_seq_resync = true;
|
|
}
|
|
dev->sdr_adc_freq = freq;
|
|
return 0;
|
|
case 1:
|
|
if (vf->type != V4L2_TUNER_RF)
|
|
return -EINVAL;
|
|
dev->sdr_fm_freq = clamp_t(unsigned, freq,
|
|
bands_fm[0].rangelow,
|
|
bands_fm[0].rangehigh);
|
|
return 0;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
int vivid_sdr_g_tuner(struct file *file, void *fh, struct v4l2_tuner *vt)
|
|
{
|
|
switch (vt->index) {
|
|
case 0:
|
|
strlcpy(vt->name, "ADC", sizeof(vt->name));
|
|
vt->type = V4L2_TUNER_ADC;
|
|
vt->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
|
|
vt->rangelow = bands_adc[0].rangelow;
|
|
vt->rangehigh = bands_adc[2].rangehigh;
|
|
return 0;
|
|
case 1:
|
|
strlcpy(vt->name, "RF", sizeof(vt->name));
|
|
vt->type = V4L2_TUNER_RF;
|
|
vt->capability = V4L2_TUNER_CAP_1HZ | V4L2_TUNER_CAP_FREQ_BANDS;
|
|
vt->rangelow = bands_fm[0].rangelow;
|
|
vt->rangehigh = bands_fm[0].rangehigh;
|
|
return 0;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
int vivid_sdr_s_tuner(struct file *file, void *fh, const struct v4l2_tuner *vt)
|
|
{
|
|
if (vt->index > 1)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
int vidioc_enum_fmt_sdr_cap(struct file *file, void *fh, struct v4l2_fmtdesc *f)
|
|
{
|
|
if (f->index)
|
|
return -EINVAL;
|
|
f->pixelformat = V4L2_SDR_FMT_CU8;
|
|
strlcpy(f->description, "IQ U8", sizeof(f->description));
|
|
return 0;
|
|
}
|
|
|
|
int vidioc_g_fmt_sdr_cap(struct file *file, void *fh, struct v4l2_format *f)
|
|
{
|
|
f->fmt.sdr.pixelformat = V4L2_SDR_FMT_CU8;
|
|
f->fmt.sdr.buffersize = SDR_CAP_SAMPLES_PER_BUF * 2;
|
|
memset(f->fmt.sdr.reserved, 0, sizeof(f->fmt.sdr.reserved));
|
|
return 0;
|
|
}
|
|
|
|
#define FIXP_N (15)
|
|
#define FIXP_FRAC (1 << FIXP_N)
|
|
#define FIXP_2PI ((int)(2 * 3.141592653589 * FIXP_FRAC))
|
|
|
|
void vivid_sdr_cap_process(struct vivid_dev *dev, struct vivid_buffer *buf)
|
|
{
|
|
u8 *vbuf = vb2_plane_vaddr(&buf->vb, 0);
|
|
unsigned long i;
|
|
unsigned long plane_size = vb2_plane_size(&buf->vb, 0);
|
|
s32 src_phase_step;
|
|
s32 mod_phase_step;
|
|
s32 fixp_i;
|
|
s32 fixp_q;
|
|
|
|
/*
|
|
* TODO: Generated beep tone goes very crackly when sample rate is
|
|
* increased to ~1Msps or more. That is because of huge rounding error
|
|
* of phase angle caused by used cosine implementation.
|
|
*/
|
|
|
|
/* calculate phase step */
|
|
#define BEEP_FREQ 1000 /* 1kHz beep */
|
|
src_phase_step = DIV_ROUND_CLOSEST(FIXP_2PI * BEEP_FREQ,
|
|
dev->sdr_adc_freq);
|
|
|
|
for (i = 0; i < plane_size; i += 2) {
|
|
mod_phase_step = fixp_cos32_rad(dev->sdr_fixp_src_phase,
|
|
FIXP_2PI) >> (31 - FIXP_N);
|
|
|
|
dev->sdr_fixp_src_phase += src_phase_step;
|
|
dev->sdr_fixp_mod_phase += mod_phase_step / 4;
|
|
|
|
/*
|
|
* Transfer phases to [0 / 2xPI] in order to avoid variable
|
|
* overflow and make it suitable for cosine implementation
|
|
* used, which does not support negative angles.
|
|
*/
|
|
while (dev->sdr_fixp_mod_phase < FIXP_2PI)
|
|
dev->sdr_fixp_mod_phase += FIXP_2PI;
|
|
while (dev->sdr_fixp_mod_phase > FIXP_2PI)
|
|
dev->sdr_fixp_mod_phase -= FIXP_2PI;
|
|
|
|
while (dev->sdr_fixp_src_phase > FIXP_2PI)
|
|
dev->sdr_fixp_src_phase -= FIXP_2PI;
|
|
|
|
fixp_i = fixp_cos32_rad(dev->sdr_fixp_mod_phase, FIXP_2PI);
|
|
fixp_q = fixp_sin32_rad(dev->sdr_fixp_mod_phase, FIXP_2PI);
|
|
|
|
/* Normalize fraction values represented with 32 bit precision
|
|
* to fixed point representation with FIXP_N bits */
|
|
fixp_i >>= (31 - FIXP_N);
|
|
fixp_q >>= (31 - FIXP_N);
|
|
|
|
/* convert 'fixp float' to u8 */
|
|
/* u8 = X * 127.5f + 127.5f; where X is float [-1.0 / +1.0] */
|
|
fixp_i = fixp_i * 1275 + FIXP_FRAC * 1275;
|
|
fixp_q = fixp_q * 1275 + FIXP_FRAC * 1275;
|
|
*vbuf++ = DIV_ROUND_CLOSEST(fixp_i, FIXP_FRAC * 10);
|
|
*vbuf++ = DIV_ROUND_CLOSEST(fixp_q, FIXP_FRAC * 10);
|
|
}
|
|
}
|