Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
@@ -0,0 +1,52 @@
# SPDX-License-Identifier: GPL-2.0-only
config RADIO_SI470X
tristate "Silicon Labs Si470x FM Radio Receiver support"
depends on VIDEO_DEV
help
This is a driver for devices with the Silicon Labs SI470x
chip (either via USB or I2C buses).
Say Y here if you want to connect this type of radio to your
computer's USB port or if it is used by some other driver
via I2C bus.
To compile this driver as a module, choose M here: the
module will be called radio-si470x-common.
config USB_SI470X
tristate "Silicon Labs Si470x FM Radio Receiver support with USB"
depends on USB && RADIO_SI470X
help
This is a driver for USB devices with the Silicon Labs SI470x
chip. Currently these devices are known to work:
- 10c4:818a: Silicon Labs USB FM Radio Reference Design
- 06e1:a155: ADS/Tech FM Radio Receiver (formerly Instant FM Music)
- 1b80:d700: KWorld USB FM Radio SnapMusic Mobile 700 (FM700)
- 10c5:819a: Sanei Electric FM USB Radio (aka DealExtreme.com PCear)
Sound is provided by the ALSA USB Audio/MIDI driver. Therefore
if you don't want to use the device solely for RDS receiving,
it is recommended to also select SND_USB_AUDIO.
Please have a look at the documentation, especially on how
to redirect the audio stream from the radio to your sound device:
Documentation/admin-guide/media/si470x.rst
Say Y here if you want to connect this type of radio to your
computer's USB port.
To compile this driver as a module, choose M here: the
module will be called radio-usb-si470x.
config I2C_SI470X
tristate "Silicon Labs Si470x FM Radio Receiver support with I2C"
depends on I2C && RADIO_SI470X
help
This is a driver for I2C devices with the Silicon Labs SI470x
chip.
Say Y here if you want to connect this type of radio to your
computer's I2C port.
To compile this driver as a module, choose M here: the
module will be called radio-i2c-si470x.
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for radios with Silicon Labs Si470x FM Radio Receivers
#
obj-$(CONFIG_RADIO_SI470X) += radio-si470x-common.o
obj-$(CONFIG_USB_SI470X) += radio-si470x-usb.o
obj-$(CONFIG_I2C_SI470X) += radio-si470x-i2c.o
@@ -0,0 +1,794 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* drivers/media/radio/si470x/radio-si470x-common.c
*
* Driver for radios with Silicon Labs Si470x FM Radio Receivers
*
* Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>
* Copyright (c) 2012 Hans de Goede <hdegoede@redhat.com>
*/
/*
* History:
* 2008-01-12 Tobias Lorenz <tobias.lorenz@gmx.net>
* Version 1.0.0
* - First working version
* 2008-01-13 Tobias Lorenz <tobias.lorenz@gmx.net>
* Version 1.0.1
* - Improved error handling, every function now returns errno
* - Improved multi user access (start/mute/stop)
* - Channel doesn't get lost anymore after start/mute/stop
* - RDS support added (polling mode via interrupt EP 1)
* - marked default module parameters with *value*
* - switched from bit structs to bit masks
* - header file cleaned and integrated
* 2008-01-14 Tobias Lorenz <tobias.lorenz@gmx.net>
* Version 1.0.2
* - hex values are now lower case
* - commented USB ID for ADS/Tech moved on todo list
* - blacklisted si470x in hid-quirks.c
* - rds buffer handling functions integrated into *_work, *_read
* - rds_command in si470x_poll exchanged against simple retval
* - check for firmware version 15
* - code order and prototypes still remain the same
* - spacing and bottom of band codes remain the same
* 2008-01-16 Tobias Lorenz <tobias.lorenz@gmx.net>
* Version 1.0.3
* - code reordered to avoid function prototypes
* - switch/case defaults are now more user-friendly
* - unified comment style
* - applied all checkpatch.pl v1.12 suggestions
* except the warning about the too long lines with bit comments
* - renamed FMRADIO to RADIO to cut line length (checkpatch.pl)
* 2008-01-22 Tobias Lorenz <tobias.lorenz@gmx.net>
* Version 1.0.4
* - avoid poss. locking when doing copy_to_user which may sleep
* - RDS is automatically activated on read now
* - code cleaned of unnecessary rds_commands
* - USB Vendor/Product ID for ADS/Tech FM Radio Receiver verified
* (thanks to Guillaume RAMOUSSE)
* 2008-01-27 Tobias Lorenz <tobias.lorenz@gmx.net>
* Version 1.0.5
* - number of seek_retries changed to tune_timeout
* - fixed problem with incomplete tune operations by own buffers
* - optimization of variables and printf types
* - improved error logging
* 2008-01-31 Tobias Lorenz <tobias.lorenz@gmx.net>
* Oliver Neukum <oliver@neukum.org>
* Version 1.0.6
* - fixed coverity checker warnings in *_usb_driver_disconnect
* - probe()/open() race by correct ordering in probe()
* - DMA coherency rules by separate allocation of all buffers
* - use of endianness macros
* - abuse of spinlock, replaced by mutex
* - racy handling of timer in disconnect,
* replaced by delayed_work
* - racy interruptible_sleep_on(),
* replaced with wait_event_interruptible()
* - handle signals in read()
* 2008-02-08 Tobias Lorenz <tobias.lorenz@gmx.net>
* Oliver Neukum <oliver@neukum.org>
* Version 1.0.7
* - usb autosuspend support
* - unplugging fixed
* 2008-05-07 Tobias Lorenz <tobias.lorenz@gmx.net>
* Version 1.0.8
* - hardware frequency seek support
* - afc indication
* - more safety checks, let si470x_get_freq return errno
* - vidioc behavior corrected according to v4l2 spec
* 2008-10-20 Alexey Klimov <klimov.linux@gmail.com>
* - add support for KWorld USB FM Radio FM700
* - blacklisted KWorld radio in hid-core.c and hid-ids.h
* 2008-12-03 Mark Lord <mlord@pobox.com>
* - add support for DealExtreme USB Radio
* 2009-01-31 Bob Ross <pigiron@gmx.com>
* - correction of stereo detection/setting
* - correction of signal strength indicator scaling
* 2009-01-31 Rick Bronson <rick@efn.org>
* Tobias Lorenz <tobias.lorenz@gmx.net>
* - add LED status output
* - get HW/SW version from scratchpad
* 2009-06-16 Edouard Lafargue <edouard@lafargue.name>
* Version 1.0.10
* - add support for interrupt mode for RDS endpoint,
* instead of polling.
* Improves RDS reception significantly
*/
/* kernel includes */
#include "radio-si470x.h"
/**************************************************************************
* Module Parameters
**************************************************************************/
/* Spacing (kHz) */
/* 0: 200 kHz (USA, Australia) */
/* 1: 100 kHz (Europe, Japan) */
/* 2: 50 kHz */
static unsigned short space = 2;
module_param(space, ushort, 0444);
MODULE_PARM_DESC(space, "Spacing: 0=200kHz 1=100kHz *2=50kHz*");
/* De-emphasis */
/* 0: 75 us (USA) */
/* 1: 50 us (Europe, Australia, Japan) */
static unsigned short de = 1;
module_param(de, ushort, 0444);
MODULE_PARM_DESC(de, "De-emphasis: 0=75us *1=50us*");
/* Tune timeout */
static unsigned int tune_timeout = 3000;
module_param(tune_timeout, uint, 0644);
MODULE_PARM_DESC(tune_timeout, "Tune timeout: *3000*");
/* Seek timeout */
static unsigned int seek_timeout = 5000;
module_param(seek_timeout, uint, 0644);
MODULE_PARM_DESC(seek_timeout, "Seek timeout: *5000*");
static const struct v4l2_frequency_band bands[] = {
{
.type = V4L2_TUNER_RADIO,
.index = 0,
.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
V4L2_TUNER_CAP_FREQ_BANDS |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP,
.rangelow = 87500 * 16,
.rangehigh = 108000 * 16,
.modulation = V4L2_BAND_MODULATION_FM,
},
{
.type = V4L2_TUNER_RADIO,
.index = 1,
.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
V4L2_TUNER_CAP_FREQ_BANDS |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP,
.rangelow = 76000 * 16,
.rangehigh = 108000 * 16,
.modulation = V4L2_BAND_MODULATION_FM,
},
{
.type = V4L2_TUNER_RADIO,
.index = 2,
.capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
V4L2_TUNER_CAP_FREQ_BANDS |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP,
.rangelow = 76000 * 16,
.rangehigh = 90000 * 16,
.modulation = V4L2_BAND_MODULATION_FM,
},
};
/**************************************************************************
* Generic Functions
**************************************************************************/
/*
* si470x_set_band - set the band
*/
static int si470x_set_band(struct si470x_device *radio, int band)
{
if (radio->band == band)
return 0;
radio->band = band;
radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_BAND;
radio->registers[SYSCONFIG2] |= radio->band << 6;
return radio->set_register(radio, SYSCONFIG2);
}
/*
* si470x_set_chan - set the channel
*/
static int si470x_set_chan(struct si470x_device *radio, unsigned short chan)
{
int retval;
unsigned long time_left;
bool timed_out = false;
retval = radio->get_register(radio, POWERCFG);
if (retval)
return retval;
if ((radio->registers[POWERCFG] & (POWERCFG_ENABLE|POWERCFG_DMUTE))
!= (POWERCFG_ENABLE|POWERCFG_DMUTE)) {
return 0;
}
/* start tuning */
radio->registers[CHANNEL] &= ~CHANNEL_CHAN;
radio->registers[CHANNEL] |= CHANNEL_TUNE | chan;
retval = radio->set_register(radio, CHANNEL);
if (retval < 0)
goto done;
/* wait till tune operation has completed */
reinit_completion(&radio->completion);
time_left = wait_for_completion_timeout(&radio->completion,
msecs_to_jiffies(tune_timeout));
if (time_left == 0)
timed_out = true;
if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
dev_warn(&radio->videodev.dev, "tune does not complete\n");
if (timed_out)
dev_warn(&radio->videodev.dev,
"tune timed out after %u ms\n", tune_timeout);
/* stop tuning */
radio->registers[CHANNEL] &= ~CHANNEL_TUNE;
retval = radio->set_register(radio, CHANNEL);
done:
return retval;
}
/*
* si470x_get_step - get channel spacing
*/
static unsigned int si470x_get_step(struct si470x_device *radio)
{
/* Spacing (kHz) */
switch ((radio->registers[SYSCONFIG2] & SYSCONFIG2_SPACE) >> 4) {
/* 0: 200 kHz (USA, Australia) */
case 0:
return 200 * 16;
/* 1: 100 kHz (Europe, Japan) */
case 1:
return 100 * 16;
/* 2: 50 kHz */
default:
return 50 * 16;
}
}
/*
* si470x_get_freq - get the frequency
*/
static int si470x_get_freq(struct si470x_device *radio, unsigned int *freq)
{
int chan, retval;
/* read channel */
retval = radio->get_register(radio, READCHAN);
chan = radio->registers[READCHAN] & READCHAN_READCHAN;
/* Frequency (MHz) = Spacing (kHz) x Channel + Bottom of Band (MHz) */
*freq = chan * si470x_get_step(radio) + bands[radio->band].rangelow;
return retval;
}
/*
* si470x_set_freq - set the frequency
*/
int si470x_set_freq(struct si470x_device *radio, unsigned int freq)
{
unsigned short chan;
freq = clamp(freq, bands[radio->band].rangelow,
bands[radio->band].rangehigh);
/* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */
chan = (freq - bands[radio->band].rangelow) / si470x_get_step(radio);
return si470x_set_chan(radio, chan);
}
EXPORT_SYMBOL_GPL(si470x_set_freq);
/*
* si470x_set_seek - set seek
*/
static int si470x_set_seek(struct si470x_device *radio,
const struct v4l2_hw_freq_seek *seek)
{
int band, retval;
unsigned int freq;
bool timed_out = false;
unsigned long time_left;
/* set band */
if (seek->rangelow || seek->rangehigh) {
for (band = 0; band < ARRAY_SIZE(bands); band++) {
if (bands[band].rangelow == seek->rangelow &&
bands[band].rangehigh == seek->rangehigh)
break;
}
if (band == ARRAY_SIZE(bands))
return -EINVAL; /* No matching band found */
} else
band = 1; /* If nothing is specified seek 76 - 108 Mhz */
if (radio->band != band) {
retval = si470x_get_freq(radio, &freq);
if (retval)
return retval;
retval = si470x_set_band(radio, band);
if (retval)
return retval;
retval = si470x_set_freq(radio, freq);
if (retval)
return retval;
}
/* start seeking */
radio->registers[POWERCFG] |= POWERCFG_SEEK;
if (seek->wrap_around)
radio->registers[POWERCFG] &= ~POWERCFG_SKMODE;
else
radio->registers[POWERCFG] |= POWERCFG_SKMODE;
if (seek->seek_upward)
radio->registers[POWERCFG] |= POWERCFG_SEEKUP;
else
radio->registers[POWERCFG] &= ~POWERCFG_SEEKUP;
retval = radio->set_register(radio, POWERCFG);
if (retval < 0)
return retval;
/* wait till tune operation has completed */
reinit_completion(&radio->completion);
time_left = wait_for_completion_timeout(&radio->completion,
msecs_to_jiffies(seek_timeout));
if (time_left == 0)
timed_out = true;
if ((radio->registers[STATUSRSSI] & STATUSRSSI_STC) == 0)
dev_warn(&radio->videodev.dev, "seek does not complete\n");
if (radio->registers[STATUSRSSI] & STATUSRSSI_SF)
dev_warn(&radio->videodev.dev,
"seek failed / band limit reached\n");
/* stop seeking */
radio->registers[POWERCFG] &= ~POWERCFG_SEEK;
retval = radio->set_register(radio, POWERCFG);
/* try again, if timed out */
if (retval == 0 && timed_out)
return -ENODATA;
return retval;
}
/*
* si470x_start - switch on radio
*/
int si470x_start(struct si470x_device *radio)
{
int retval;
/* powercfg */
radio->registers[POWERCFG] =
POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM;
retval = radio->set_register(radio, POWERCFG);
if (retval < 0)
goto done;
/* sysconfig 1 */
radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDSIEN | SYSCONFIG1_STCIEN |
SYSCONFIG1_RDS;
radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_GPIO2;
radio->registers[SYSCONFIG1] |= SYSCONFIG1_GPIO2_INT;
if (de)
radio->registers[SYSCONFIG1] |= SYSCONFIG1_DE;
retval = radio->set_register(radio, SYSCONFIG1);
if (retval < 0)
goto done;
/* sysconfig 2 */
radio->registers[SYSCONFIG2] =
(0x1f << 8) | /* SEEKTH */
((radio->band << 6) & SYSCONFIG2_BAND) |/* BAND */
((space << 4) & SYSCONFIG2_SPACE) | /* SPACE */
15; /* VOLUME (max) */
retval = radio->set_register(radio, SYSCONFIG2);
if (retval < 0)
goto done;
/* reset last channel */
retval = si470x_set_chan(radio,
radio->registers[CHANNEL] & CHANNEL_CHAN);
done:
return retval;
}
EXPORT_SYMBOL_GPL(si470x_start);
/*
* si470x_stop - switch off radio
*/
int si470x_stop(struct si470x_device *radio)
{
int retval;
/* sysconfig 1 */
radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
retval = radio->set_register(radio, SYSCONFIG1);
if (retval < 0)
goto done;
/* powercfg */
radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
/* POWERCFG_ENABLE has to automatically go low */
radio->registers[POWERCFG] |= POWERCFG_ENABLE | POWERCFG_DISABLE;
retval = radio->set_register(radio, POWERCFG);
done:
return retval;
}
EXPORT_SYMBOL_GPL(si470x_stop);
/*
* si470x_rds_on - switch on rds reception
*/
static int si470x_rds_on(struct si470x_device *radio)
{
int retval;
/* sysconfig 1 */
radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS;
retval = radio->set_register(radio, SYSCONFIG1);
if (retval < 0)
radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS;
return retval;
}
/**************************************************************************
* File Operations Interface
**************************************************************************/
/*
* si470x_fops_read - read RDS data
*/
static ssize_t si470x_fops_read(struct file *file, char __user *buf,
size_t count, loff_t *ppos)
{
struct si470x_device *radio = video_drvdata(file);
int retval = 0;
unsigned int block_count = 0;
/* switch on rds reception */
if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
si470x_rds_on(radio);
/* block if no new data available */
while (radio->wr_index == radio->rd_index) {
if (file->f_flags & O_NONBLOCK) {
retval = -EWOULDBLOCK;
goto done;
}
if (wait_event_interruptible(radio->read_queue,
radio->wr_index != radio->rd_index) < 0) {
retval = -EINTR;
goto done;
}
}
/* calculate block count from byte count */
count /= 3;
/* copy RDS block out of internal buffer and to user buffer */
while (block_count < count) {
if (radio->rd_index == radio->wr_index)
break;
/* always transfer rds complete blocks */
if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3))
/* retval = -EFAULT; */
break;
/* increment and wrap read pointer */
radio->rd_index += 3;
if (radio->rd_index >= radio->buf_size)
radio->rd_index = 0;
/* increment counters */
block_count++;
buf += 3;
retval += 3;
}
done:
return retval;
}
/*
* si470x_fops_poll - poll RDS data
*/
static __poll_t si470x_fops_poll(struct file *file,
struct poll_table_struct *pts)
{
struct si470x_device *radio = video_drvdata(file);
__poll_t req_events = poll_requested_events(pts);
__poll_t retval = v4l2_ctrl_poll(file, pts);
if (req_events & (EPOLLIN | EPOLLRDNORM)) {
/* switch on rds reception */
if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
si470x_rds_on(radio);
poll_wait(file, &radio->read_queue, pts);
if (radio->rd_index != radio->wr_index)
retval |= EPOLLIN | EPOLLRDNORM;
}
return retval;
}
static int si470x_fops_open(struct file *file)
{
struct si470x_device *radio = video_drvdata(file);
return radio->fops_open(file);
}
/*
* si470x_fops_release - file release
*/
static int si470x_fops_release(struct file *file)
{
struct si470x_device *radio = video_drvdata(file);
return radio->fops_release(file);
}
/*
* si470x_fops - file operations interface
*/
static const struct v4l2_file_operations si470x_fops = {
.owner = THIS_MODULE,
.read = si470x_fops_read,
.poll = si470x_fops_poll,
.unlocked_ioctl = video_ioctl2,
.open = si470x_fops_open,
.release = si470x_fops_release,
};
/**************************************************************************
* Video4Linux Interface
**************************************************************************/
static int si470x_s_ctrl(struct v4l2_ctrl *ctrl)
{
struct si470x_device *radio =
container_of(ctrl->handler, struct si470x_device, hdl);
switch (ctrl->id) {
case V4L2_CID_AUDIO_VOLUME:
radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME;
radio->registers[SYSCONFIG2] |= ctrl->val;
return radio->set_register(radio, SYSCONFIG2);
case V4L2_CID_AUDIO_MUTE:
if (ctrl->val)
radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
else
radio->registers[POWERCFG] |= POWERCFG_DMUTE;
return radio->set_register(radio, POWERCFG);
default:
return -EINVAL;
}
}
/*
* si470x_vidioc_g_tuner - get tuner attributes
*/
static int si470x_vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *tuner)
{
struct si470x_device *radio = video_drvdata(file);
int retval = 0;
if (tuner->index != 0)
return -EINVAL;
if (!radio->status_rssi_auto_update) {
retval = radio->get_register(radio, STATUSRSSI);
if (retval < 0)
return retval;
}
/* driver constants */
strscpy(tuner->name, "FM", sizeof(tuner->name));
tuner->type = V4L2_TUNER_RADIO;
tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO |
V4L2_TUNER_CAP_RDS | V4L2_TUNER_CAP_RDS_BLOCK_IO |
V4L2_TUNER_CAP_HWSEEK_BOUNDED |
V4L2_TUNER_CAP_HWSEEK_WRAP;
tuner->rangelow = 76 * FREQ_MUL;
tuner->rangehigh = 108 * FREQ_MUL;
/* stereo indicator == stereo (instead of mono) */
if ((radio->registers[STATUSRSSI] & STATUSRSSI_ST) == 0)
tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
else
tuner->rxsubchans = V4L2_TUNER_SUB_STEREO;
/* If there is a reliable method of detecting an RDS channel,
then this code should check for that before setting this
RDS subchannel. */
tuner->rxsubchans |= V4L2_TUNER_SUB_RDS;
/* mono/stereo selector */
if ((radio->registers[POWERCFG] & POWERCFG_MONO) == 0)
tuner->audmode = V4L2_TUNER_MODE_STEREO;
else
tuner->audmode = V4L2_TUNER_MODE_MONO;
/* min is worst, max is best; signal:0..0xffff; rssi: 0..0xff */
/* measured in units of dbµV in 1 db increments (max at ~75 dbµV) */
tuner->signal = (radio->registers[STATUSRSSI] & STATUSRSSI_RSSI);
/* the ideal factor is 0xffff/75 = 873,8 */
tuner->signal = (tuner->signal * 873) + (8 * tuner->signal / 10);
if (tuner->signal > 0xffff)
tuner->signal = 0xffff;
/* automatic frequency control: -1: freq to low, 1 freq to high */
/* AFCRL does only indicate that freq. differs, not if too low/high */
tuner->afc = (radio->registers[STATUSRSSI] & STATUSRSSI_AFCRL) ? 1 : 0;
return retval;
}
/*
* si470x_vidioc_s_tuner - set tuner attributes
*/
static int si470x_vidioc_s_tuner(struct file *file, void *priv,
const struct v4l2_tuner *tuner)
{
struct si470x_device *radio = video_drvdata(file);
if (tuner->index != 0)
return -EINVAL;
/* mono/stereo selector */
switch (tuner->audmode) {
case V4L2_TUNER_MODE_MONO:
radio->registers[POWERCFG] |= POWERCFG_MONO; /* force mono */
break;
case V4L2_TUNER_MODE_STEREO:
default:
radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */
break;
}
return radio->set_register(radio, POWERCFG);
}
/*
* si470x_vidioc_g_frequency - get tuner or modulator radio frequency
*/
static int si470x_vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *freq)
{
struct si470x_device *radio = video_drvdata(file);
if (freq->tuner != 0)
return -EINVAL;
freq->type = V4L2_TUNER_RADIO;
return si470x_get_freq(radio, &freq->frequency);
}
/*
* si470x_vidioc_s_frequency - set tuner or modulator radio frequency
*/
static int si470x_vidioc_s_frequency(struct file *file, void *priv,
const struct v4l2_frequency *freq)
{
struct si470x_device *radio = video_drvdata(file);
int retval;
if (freq->tuner != 0)
return -EINVAL;
if (freq->frequency < bands[radio->band].rangelow ||
freq->frequency > bands[radio->band].rangehigh) {
/* Switch to band 1 which covers everything we support */
retval = si470x_set_band(radio, 1);
if (retval)
return retval;
}
return si470x_set_freq(radio, freq->frequency);
}
/*
* si470x_vidioc_s_hw_freq_seek - set hardware frequency seek
*/
static int si470x_vidioc_s_hw_freq_seek(struct file *file, void *priv,
const struct v4l2_hw_freq_seek *seek)
{
struct si470x_device *radio = video_drvdata(file);
if (seek->tuner != 0)
return -EINVAL;
if (file->f_flags & O_NONBLOCK)
return -EWOULDBLOCK;
return si470x_set_seek(radio, seek);
}
/*
* si470x_vidioc_enum_freq_bands - enumerate supported bands
*/
static int si470x_vidioc_enum_freq_bands(struct file *file, void *priv,
struct v4l2_frequency_band *band)
{
if (band->tuner != 0)
return -EINVAL;
if (band->index >= ARRAY_SIZE(bands))
return -EINVAL;
*band = bands[band->index];
return 0;
}
const struct v4l2_ctrl_ops si470x_ctrl_ops = {
.s_ctrl = si470x_s_ctrl,
};
EXPORT_SYMBOL_GPL(si470x_ctrl_ops);
static int si470x_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *capability)
{
struct si470x_device *radio = video_drvdata(file);
return radio->vidioc_querycap(file, priv, capability);
};
/*
* si470x_ioctl_ops - video device ioctl operations
*/
static const struct v4l2_ioctl_ops si470x_ioctl_ops = {
.vidioc_querycap = si470x_vidioc_querycap,
.vidioc_g_tuner = si470x_vidioc_g_tuner,
.vidioc_s_tuner = si470x_vidioc_s_tuner,
.vidioc_g_frequency = si470x_vidioc_g_frequency,
.vidioc_s_frequency = si470x_vidioc_s_frequency,
.vidioc_s_hw_freq_seek = si470x_vidioc_s_hw_freq_seek,
.vidioc_enum_freq_bands = si470x_vidioc_enum_freq_bands,
.vidioc_subscribe_event = v4l2_ctrl_subscribe_event,
.vidioc_unsubscribe_event = v4l2_event_unsubscribe,
};
/*
* si470x_viddev_template - video device interface
*/
const struct video_device si470x_viddev_template = {
.fops = &si470x_fops,
.name = DRIVER_NAME,
.release = video_device_release_empty,
.ioctl_ops = &si470x_ioctl_ops,
};
EXPORT_SYMBOL_GPL(si470x_viddev_template);
MODULE_DESCRIPTION("Core radio driver for Si470x FM Radio Receivers");
MODULE_LICENSE("GPL");
@@ -0,0 +1,545 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* drivers/media/radio/si470x/radio-si470x-i2c.c
*
* I2C driver for radios with Silicon Labs Si470x FM Radio Receivers
*
* Copyright (c) 2009 Samsung Electronics Co.Ltd
* Author: Joonyoung Shim <jy0922.shim@samsung.com>
*/
/* driver definitions */
#define DRIVER_AUTHOR "Joonyoung Shim <jy0922.shim@samsung.com>"
#define DRIVER_CARD "Silicon Labs Si470x FM Radio"
#define DRIVER_DESC "I2C radio driver for Si470x FM Radio Receivers"
#define DRIVER_VERSION "1.0.2"
/* kernel includes */
#include <linux/i2c.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include "radio-si470x.h"
/* I2C Device ID List */
static const struct i2c_device_id si470x_i2c_id[] = {
/* Generic Entry */
{ .name = "si470x" },
/* Terminating entry */
{ }
};
MODULE_DEVICE_TABLE(i2c, si470x_i2c_id);
/**************************************************************************
* Module Parameters
**************************************************************************/
/* Radio Nr */
static int radio_nr = -1;
module_param(radio_nr, int, 0444);
MODULE_PARM_DESC(radio_nr, "Radio Nr");
/* RDS buffer blocks */
static unsigned int rds_buf = 100;
module_param(rds_buf, uint, 0444);
MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
/* RDS maximum block errors */
static unsigned short max_rds_errors = 1;
/* 0 means 0 errors requiring correction */
/* 1 means 1-2 errors requiring correction (used by original USBRadio.exe) */
/* 2 means 3-5 errors requiring correction */
/* 3 means 6+ errors or errors in checkword, correction not possible */
module_param(max_rds_errors, ushort, 0644);
MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
/**************************************************************************
* I2C Definitions
**************************************************************************/
/* Write starts with the upper byte of register 0x02 */
#define WRITE_REG_NUM 8
#define WRITE_INDEX(i) (i + 0x02)
/* Read starts with the upper byte of register 0x0a */
#define READ_REG_NUM RADIO_REGISTER_NUM
#define READ_INDEX(i) ((i + RADIO_REGISTER_NUM - 0x0a) % READ_REG_NUM)
/**************************************************************************
* General Driver Functions - REGISTERs
**************************************************************************/
/*
* si470x_get_register - read register
*/
static int si470x_get_register(struct si470x_device *radio, int regnr)
{
__be16 buf[READ_REG_NUM];
struct i2c_msg msgs[1] = {
{
.addr = radio->client->addr,
.flags = I2C_M_RD,
.len = sizeof(u16) * READ_REG_NUM,
.buf = (void *)buf
},
};
if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
return -EIO;
radio->registers[regnr] = __be16_to_cpu(buf[READ_INDEX(regnr)]);
return 0;
}
/*
* si470x_set_register - write register
*/
static int si470x_set_register(struct si470x_device *radio, int regnr)
{
int i;
__be16 buf[WRITE_REG_NUM];
struct i2c_msg msgs[1] = {
{
.addr = radio->client->addr,
.len = sizeof(u16) * WRITE_REG_NUM,
.buf = (void *)buf
},
};
for (i = 0; i < WRITE_REG_NUM; i++)
buf[i] = __cpu_to_be16(radio->registers[WRITE_INDEX(i)]);
if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
return -EIO;
return 0;
}
/**************************************************************************
* General Driver Functions - ENTIRE REGISTERS
**************************************************************************/
/*
* si470x_get_all_registers - read entire registers
*/
static int si470x_get_all_registers(struct si470x_device *radio)
{
int i;
__be16 buf[READ_REG_NUM];
struct i2c_msg msgs[1] = {
{
.addr = radio->client->addr,
.flags = I2C_M_RD,
.len = sizeof(u16) * READ_REG_NUM,
.buf = (void *)buf
},
};
if (i2c_transfer(radio->client->adapter, msgs, 1) != 1)
return -EIO;
for (i = 0; i < READ_REG_NUM; i++)
radio->registers[i] = __be16_to_cpu(buf[READ_INDEX(i)]);
return 0;
}
/**************************************************************************
* File Operations Interface
**************************************************************************/
/*
* si470x_fops_open - file open
*/
static int si470x_fops_open(struct file *file)
{
struct si470x_device *radio = video_drvdata(file);
int retval = v4l2_fh_open(file);
if (retval)
return retval;
if (v4l2_fh_is_singular_file(file)) {
/* start radio */
retval = si470x_start(radio);
if (retval < 0)
goto done;
/* enable RDS / STC interrupt */
radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDSIEN;
radio->registers[SYSCONFIG1] |= SYSCONFIG1_STCIEN;
radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_GPIO2;
radio->registers[SYSCONFIG1] |= 0x1 << 2;
retval = si470x_set_register(radio, SYSCONFIG1);
}
done:
if (retval)
v4l2_fh_release(file);
return retval;
}
/*
* si470x_fops_release - file release
*/
static int si470x_fops_release(struct file *file)
{
struct si470x_device *radio = video_drvdata(file);
if (v4l2_fh_is_singular_file(file))
/* stop radio */
si470x_stop(radio);
return v4l2_fh_release(file);
}
/**************************************************************************
* Video4Linux Interface
**************************************************************************/
/*
* si470x_vidioc_querycap - query device capabilities
*/
static int si470x_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *capability)
{
strscpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
strscpy(capability->card, DRIVER_CARD, sizeof(capability->card));
return 0;
}
/**************************************************************************
* I2C Interface
**************************************************************************/
/*
* si470x_i2c_interrupt - interrupt handler
*/
static irqreturn_t si470x_i2c_interrupt(int irq, void *dev_id)
{
struct si470x_device *radio = dev_id;
unsigned char regnr;
unsigned char blocknum;
unsigned short bler; /* rds block errors */
unsigned short rds;
unsigned char tmpbuf[3];
int retval = 0;
/* check Seek/Tune Complete */
retval = si470x_get_register(radio, STATUSRSSI);
if (retval < 0)
goto end;
if (radio->registers[STATUSRSSI] & STATUSRSSI_STC)
complete(&radio->completion);
/* safety checks */
if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0)
goto end;
/* Update RDS registers */
for (regnr = 1; regnr < RDS_REGISTER_NUM; regnr++) {
retval = si470x_get_register(radio, STATUSRSSI + regnr);
if (retval < 0)
goto end;
}
/* get rds blocks */
if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0)
/* No RDS group ready, better luck next time */
goto end;
for (blocknum = 0; blocknum < 4; blocknum++) {
switch (blocknum) {
default:
bler = (radio->registers[STATUSRSSI] &
STATUSRSSI_BLERA) >> 9;
rds = radio->registers[RDSA];
break;
case 1:
bler = (radio->registers[READCHAN] &
READCHAN_BLERB) >> 14;
rds = radio->registers[RDSB];
break;
case 2:
bler = (radio->registers[READCHAN] &
READCHAN_BLERC) >> 12;
rds = radio->registers[RDSC];
break;
case 3:
bler = (radio->registers[READCHAN] &
READCHAN_BLERD) >> 10;
rds = radio->registers[RDSD];
break;
}
/* Fill the V4L2 RDS buffer */
put_unaligned_le16(rds, &tmpbuf);
tmpbuf[2] = blocknum; /* offset name */
tmpbuf[2] |= blocknum << 3; /* received offset */
if (bler > max_rds_errors)
tmpbuf[2] |= 0x80; /* uncorrectable errors */
else if (bler > 0)
tmpbuf[2] |= 0x40; /* corrected error(s) */
/* copy RDS block to internal buffer */
memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3);
radio->wr_index += 3;
/* wrap write pointer */
if (radio->wr_index >= radio->buf_size)
radio->wr_index = 0;
/* check for overflow */
if (radio->wr_index == radio->rd_index) {
/* increment and wrap read pointer */
radio->rd_index += 3;
if (radio->rd_index >= radio->buf_size)
radio->rd_index = 0;
}
}
if (radio->wr_index != radio->rd_index)
wake_up_interruptible(&radio->read_queue);
end:
return IRQ_HANDLED;
}
/*
* si470x_i2c_probe - probe for the device
*/
static int si470x_i2c_probe(struct i2c_client *client)
{
struct si470x_device *radio;
int retval = 0;
/* private data allocation and initialization */
radio = devm_kzalloc(&client->dev, sizeof(*radio), GFP_KERNEL);
if (!radio) {
retval = -ENOMEM;
goto err_initial;
}
radio->client = client;
radio->band = 1; /* Default to 76 - 108 MHz */
mutex_init(&radio->lock);
init_completion(&radio->completion);
radio->get_register = si470x_get_register;
radio->set_register = si470x_set_register;
radio->fops_open = si470x_fops_open;
radio->fops_release = si470x_fops_release;
radio->vidioc_querycap = si470x_vidioc_querycap;
retval = v4l2_device_register(&client->dev, &radio->v4l2_dev);
if (retval < 0) {
dev_err(&client->dev, "couldn't register v4l2_device\n");
goto err_initial;
}
v4l2_ctrl_handler_init(&radio->hdl, 2);
v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops,
V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 15);
if (radio->hdl.error) {
retval = radio->hdl.error;
dev_err(&client->dev, "couldn't register control\n");
goto err_all;
}
/* video device initialization */
radio->videodev = si470x_viddev_template;
radio->videodev.ctrl_handler = &radio->hdl;
radio->videodev.lock = &radio->lock;
radio->videodev.v4l2_dev = &radio->v4l2_dev;
radio->videodev.release = video_device_release_empty;
radio->videodev.device_caps =
V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE | V4L2_CAP_TUNER |
V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE;
video_set_drvdata(&radio->videodev, radio);
radio->gpio_reset = devm_gpiod_get_optional(&client->dev, "reset",
GPIOD_OUT_LOW);
if (IS_ERR(radio->gpio_reset)) {
retval = PTR_ERR(radio->gpio_reset);
dev_err(&client->dev, "Failed to request gpio: %d\n", retval);
goto err_all;
}
if (radio->gpio_reset)
gpiod_set_value(radio->gpio_reset, 1);
/* power up : need 110ms */
radio->registers[POWERCFG] = POWERCFG_ENABLE;
if (si470x_set_register(radio, POWERCFG) < 0) {
retval = -EIO;
goto err_all;
}
msleep(110);
/* get device and chip versions */
if (si470x_get_all_registers(radio) < 0) {
retval = -EIO;
goto err_all;
}
dev_info(&client->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
radio->registers[DEVICEID], radio->registers[SI_CHIPID]);
if ((radio->registers[SI_CHIPID] & SI_CHIPID_FIRMWARE) < RADIO_FW_VERSION) {
dev_warn(&client->dev,
"This driver is known to work with firmware version %u, but the device has firmware version %u.\n"
"If you have some trouble using this driver, please report to V4L ML at linux-media@vger.kernel.org\n",
RADIO_FW_VERSION,
radio->registers[SI_CHIPID] & SI_CHIPID_FIRMWARE);
}
/* set initial frequency */
si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */
/* rds buffer allocation */
radio->buf_size = rds_buf * 3;
radio->buffer = devm_kmalloc(&client->dev, radio->buf_size, GFP_KERNEL);
if (!radio->buffer) {
retval = -EIO;
goto err_all;
}
/* rds buffer configuration */
radio->wr_index = 0;
radio->rd_index = 0;
init_waitqueue_head(&radio->read_queue);
retval = devm_request_threaded_irq(&client->dev, client->irq, NULL,
si470x_i2c_interrupt,
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
DRIVER_NAME, radio);
if (retval) {
dev_err(&client->dev, "Failed to register interrupt\n");
goto err_all;
}
/* register video device */
retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO,
radio_nr);
if (retval) {
dev_warn(&client->dev, "Could not register video device\n");
goto err_all;
}
i2c_set_clientdata(client, radio);
return 0;
err_all:
v4l2_ctrl_handler_free(&radio->hdl);
v4l2_device_unregister(&radio->v4l2_dev);
err_initial:
return retval;
}
/*
* si470x_i2c_remove - remove the device
*/
static void si470x_i2c_remove(struct i2c_client *client)
{
struct si470x_device *radio = i2c_get_clientdata(client);
video_unregister_device(&radio->videodev);
if (radio->gpio_reset)
gpiod_set_value(radio->gpio_reset, 0);
v4l2_ctrl_handler_free(&radio->hdl);
v4l2_device_unregister(&radio->v4l2_dev);
}
#ifdef CONFIG_PM_SLEEP
/*
* si470x_i2c_suspend - suspend the device
*/
static int si470x_i2c_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct si470x_device *radio = i2c_get_clientdata(client);
/* power down */
radio->registers[POWERCFG] |= POWERCFG_DISABLE;
if (si470x_set_register(radio, POWERCFG) < 0)
return -EIO;
return 0;
}
/*
* si470x_i2c_resume - resume the device
*/
static int si470x_i2c_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct si470x_device *radio = i2c_get_clientdata(client);
/* power up : need 110ms */
radio->registers[POWERCFG] |= POWERCFG_ENABLE;
if (si470x_set_register(radio, POWERCFG) < 0)
return -EIO;
msleep(110);
return 0;
}
static SIMPLE_DEV_PM_OPS(si470x_i2c_pm, si470x_i2c_suspend, si470x_i2c_resume);
#endif
#if IS_ENABLED(CONFIG_OF)
static const struct of_device_id si470x_of_match[] = {
{ .compatible = "silabs,si470x" },
{ },
};
MODULE_DEVICE_TABLE(of, si470x_of_match);
#endif
/*
* si470x_i2c_driver - i2c driver interface
*/
static struct i2c_driver si470x_i2c_driver = {
.driver = {
.name = "si470x",
.of_match_table = of_match_ptr(si470x_of_match),
#ifdef CONFIG_PM_SLEEP
.pm = &si470x_i2c_pm,
#endif
},
.probe = si470x_i2c_probe,
.remove = si470x_i2c_remove,
.id_table = si470x_i2c_id,
};
module_i2c_driver(si470x_i2c_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION(DRIVER_VERSION);
@@ -0,0 +1,849 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* drivers/media/radio/si470x/radio-si470x-usb.c
*
* USB driver for radios with Silicon Labs Si470x FM Radio Receivers
*
* Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>
*/
/*
* ToDo:
* - add firmware download/update support
*/
/* driver definitions */
#define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>"
#define DRIVER_CARD "Silicon Labs Si470x FM Radio"
#define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers"
#define DRIVER_VERSION "1.0.10"
/* kernel includes */
#include <linux/usb.h>
#include <linux/hid.h>
#include <linux/slab.h>
#include "radio-si470x.h"
/* USB Device ID List */
static const struct usb_device_id si470x_usb_driver_id_table[] = {
/* Silicon Labs USB FM Radio Reference Design */
{ USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) },
/* ADS/Tech FM Radio Receiver (formerly Instant FM Music) */
{ USB_DEVICE_AND_INTERFACE_INFO(0x06e1, 0xa155, USB_CLASS_HID, 0, 0) },
/* KWorld USB FM Radio SnapMusic Mobile 700 (FM700) */
{ USB_DEVICE_AND_INTERFACE_INFO(0x1b80, 0xd700, USB_CLASS_HID, 0, 0) },
/* Sanei Electric, Inc. FM USB Radio (sold as DealExtreme.com PCear) */
{ USB_DEVICE_AND_INTERFACE_INFO(0x10c5, 0x819a, USB_CLASS_HID, 0, 0) },
/* Axentia ALERT FM USB Receiver */
{ USB_DEVICE_AND_INTERFACE_INFO(0x12cf, 0x7111, USB_CLASS_HID, 0, 0) },
/* Terminating entry */
{ }
};
MODULE_DEVICE_TABLE(usb, si470x_usb_driver_id_table);
/**************************************************************************
* Module Parameters
**************************************************************************/
/* Radio Nr */
static int radio_nr = -1;
module_param(radio_nr, int, 0444);
MODULE_PARM_DESC(radio_nr, "Radio Nr");
/* USB timeout */
static unsigned int usb_timeout = 500;
module_param(usb_timeout, uint, 0644);
MODULE_PARM_DESC(usb_timeout, "USB timeout (ms): *500*");
/* RDS buffer blocks */
static unsigned int rds_buf = 100;
module_param(rds_buf, uint, 0444);
MODULE_PARM_DESC(rds_buf, "RDS buffer entries: *100*");
/* RDS maximum block errors */
static unsigned short max_rds_errors = 1;
/* 0 means 0 errors requiring correction */
/* 1 means 1-2 errors requiring correction (used by original USBRadio.exe) */
/* 2 means 3-5 errors requiring correction */
/* 3 means 6+ errors or errors in checkword, correction not possible */
module_param(max_rds_errors, ushort, 0644);
MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*");
/**************************************************************************
* USB HID Reports
**************************************************************************/
/* Reports 1-16 give direct read/write access to the 16 Si470x registers */
/* with the (REPORT_ID - 1) corresponding to the register address across USB */
/* endpoint 0 using GET_REPORT and SET_REPORT */
#define REGISTER_REPORT_SIZE (RADIO_REGISTER_SIZE + 1)
#define REGISTER_REPORT(reg) ((reg) + 1)
/* Report 17 gives direct read/write access to the entire Si470x register */
/* map across endpoint 0 using GET_REPORT and SET_REPORT */
#define ENTIRE_REPORT_SIZE (RADIO_REGISTER_NUM * RADIO_REGISTER_SIZE + 1)
#define ENTIRE_REPORT 17
/* Report 18 is used to send the lowest 6 Si470x registers up the HID */
/* interrupt endpoint 1 to Windows every 20 milliseconds for status */
#define RDS_REPORT_SIZE (RDS_REGISTER_NUM * RADIO_REGISTER_SIZE + 1)
#define RDS_REPORT 18
/* Report 19: LED state */
#define LED_REPORT_SIZE 3
#define LED_REPORT 19
/* Report 19: stream */
#define STREAM_REPORT_SIZE 3
#define STREAM_REPORT 19
/* Report 20: scratch */
#define SCRATCH_PAGE_SIZE 63
#define SCRATCH_REPORT_SIZE (SCRATCH_PAGE_SIZE + 1)
#define SCRATCH_REPORT 20
/* Reports 19-22: flash upgrade of the C8051F321 */
#define WRITE_REPORT_SIZE 4
#define WRITE_REPORT 19
#define FLASH_REPORT_SIZE 64
#define FLASH_REPORT 20
#define CRC_REPORT_SIZE 3
#define CRC_REPORT 21
#define RESPONSE_REPORT_SIZE 2
#define RESPONSE_REPORT 22
/* Report 23: currently unused, but can accept 60 byte reports on the HID */
/* interrupt out endpoint 2 every 1 millisecond */
#define UNUSED_REPORT 23
#define MAX_REPORT_SIZE 64
/**************************************************************************
* Software/Hardware Versions from Scratch Page
**************************************************************************/
#define RADIO_HW_VERSION 1
/**************************************************************************
* LED State Definitions
**************************************************************************/
#define LED_COMMAND 0x35
#define NO_CHANGE_LED 0x00
#define ALL_COLOR_LED 0x01 /* streaming state */
#define BLINK_GREEN_LED 0x02 /* connect state */
#define BLINK_RED_LED 0x04
#define BLINK_ORANGE_LED 0x10 /* disconnect state */
#define SOLID_GREEN_LED 0x20 /* tuning/seeking state */
#define SOLID_RED_LED 0x40 /* bootload state */
#define SOLID_ORANGE_LED 0x80
/**************************************************************************
* Stream State Definitions
**************************************************************************/
#define STREAM_COMMAND 0x36
#define STREAM_VIDPID 0x00
#define STREAM_AUDIO 0xff
/**************************************************************************
* Bootloader / Flash Commands
**************************************************************************/
/* unique id sent to bootloader and required to put into a bootload state */
#define UNIQUE_BL_ID 0x34
/* mask for the flash data */
#define FLASH_DATA_MASK 0x55
/* bootloader commands */
#define GET_SW_VERSION_COMMAND 0x00
#define SET_PAGE_COMMAND 0x01
#define ERASE_PAGE_COMMAND 0x02
#define WRITE_PAGE_COMMAND 0x03
#define CRC_ON_PAGE_COMMAND 0x04
#define READ_FLASH_BYTE_COMMAND 0x05
#define RESET_DEVICE_COMMAND 0x06
#define GET_HW_VERSION_COMMAND 0x07
#define BLANK 0xff
/* bootloader command responses */
#define COMMAND_OK 0x01
#define COMMAND_FAILED 0x02
#define COMMAND_PENDING 0x03
/**************************************************************************
* General Driver Functions - REGISTER_REPORTs
**************************************************************************/
/*
* si470x_get_report - receive a HID report
*/
static int si470x_get_report(struct si470x_device *radio, void *buf, int size)
{
unsigned char *report = buf;
int retval;
retval = usb_control_msg(radio->usbdev,
usb_rcvctrlpipe(radio->usbdev, 0),
HID_REQ_GET_REPORT,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
report[0], 2,
buf, size, usb_timeout);
if (retval < 0)
dev_warn(&radio->intf->dev,
"si470x_get_report: usb_control_msg returned %d\n",
retval);
return retval;
}
/*
* si470x_set_report - send a HID report
*/
static int si470x_set_report(struct si470x_device *radio, void *buf, int size)
{
unsigned char *report = buf;
int retval;
retval = usb_control_msg(radio->usbdev,
usb_sndctrlpipe(radio->usbdev, 0),
HID_REQ_SET_REPORT,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT,
report[0], 2,
buf, size, usb_timeout);
if (retval < 0)
dev_warn(&radio->intf->dev,
"si470x_set_report: usb_control_msg returned %d\n",
retval);
return retval;
}
/*
* si470x_get_register - read register
*/
static int si470x_get_register(struct si470x_device *radio, int regnr)
{
int retval;
radio->usb_buf[0] = REGISTER_REPORT(regnr);
retval = si470x_get_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE);
if (retval >= 0)
radio->registers[regnr] = get_unaligned_be16(&radio->usb_buf[1]);
return (retval < 0) ? -EINVAL : 0;
}
/*
* si470x_set_register - write register
*/
static int si470x_set_register(struct si470x_device *radio, int regnr)
{
int retval;
radio->usb_buf[0] = REGISTER_REPORT(regnr);
put_unaligned_be16(radio->registers[regnr], &radio->usb_buf[1]);
retval = si470x_set_report(radio, radio->usb_buf, REGISTER_REPORT_SIZE);
return (retval < 0) ? -EINVAL : 0;
}
/**************************************************************************
* General Driver Functions - ENTIRE_REPORT
**************************************************************************/
/*
* si470x_get_all_registers - read entire registers
*/
static int si470x_get_all_registers(struct si470x_device *radio)
{
int retval;
unsigned char regnr;
radio->usb_buf[0] = ENTIRE_REPORT;
retval = si470x_get_report(radio, radio->usb_buf, ENTIRE_REPORT_SIZE);
if (retval >= 0)
for (regnr = 0; regnr < RADIO_REGISTER_NUM; regnr++)
radio->registers[regnr] = get_unaligned_be16(
&radio->usb_buf[regnr * RADIO_REGISTER_SIZE + 1]);
return (retval < 0) ? -EINVAL : 0;
}
/**************************************************************************
* General Driver Functions - LED_REPORT
**************************************************************************/
/*
* si470x_set_led_state - sets the led state
*/
static int si470x_set_led_state(struct si470x_device *radio,
unsigned char led_state)
{
int retval;
radio->usb_buf[0] = LED_REPORT;
radio->usb_buf[1] = LED_COMMAND;
radio->usb_buf[2] = led_state;
retval = si470x_set_report(radio, radio->usb_buf, LED_REPORT_SIZE);
return (retval < 0) ? -EINVAL : 0;
}
/**************************************************************************
* General Driver Functions - SCRATCH_REPORT
**************************************************************************/
/*
* si470x_get_scratch_versions - gets the scratch page and version infos
*/
static int si470x_get_scratch_page_versions(struct si470x_device *radio)
{
int retval;
radio->usb_buf[0] = SCRATCH_REPORT;
retval = si470x_get_report(radio, radio->usb_buf, SCRATCH_REPORT_SIZE);
if (retval < 0)
dev_warn(&radio->intf->dev, "si470x_get_scratch: si470x_get_report returned %d\n",
retval);
else {
radio->software_version = radio->usb_buf[1];
radio->hardware_version = radio->usb_buf[2];
}
return (retval < 0) ? -EINVAL : 0;
}
/**************************************************************************
* RDS Driver Functions
**************************************************************************/
/*
* si470x_int_in_callback - rds callback and processing function
*
* TODO: do we need to use mutex locks in some sections?
*/
static void si470x_int_in_callback(struct urb *urb)
{
struct si470x_device *radio = urb->context;
int retval;
unsigned char regnr;
unsigned char blocknum;
unsigned short bler; /* rds block errors */
unsigned short rds;
unsigned char tmpbuf[3];
if (urb->status) {
if (urb->status == -ENOENT ||
urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN) {
return;
} else {
dev_warn(&radio->intf->dev,
"non-zero urb status (%d)\n", urb->status);
goto resubmit; /* Maybe we can recover. */
}
}
/* Sometimes the device returns len 0 packets */
if (urb->actual_length != RDS_REPORT_SIZE)
goto resubmit;
radio->registers[STATUSRSSI] =
get_unaligned_be16(&radio->int_in_buffer[1]);
if (radio->registers[STATUSRSSI] & STATUSRSSI_STC)
complete(&radio->completion);
if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS)) {
/* Update RDS registers with URB data */
for (regnr = 1; regnr < RDS_REGISTER_NUM; regnr++)
radio->registers[STATUSRSSI + regnr] =
get_unaligned_be16(&radio->int_in_buffer[
regnr * RADIO_REGISTER_SIZE + 1]);
/* get rds blocks */
if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) {
/* No RDS group ready, better luck next time */
goto resubmit;
}
if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSS) == 0) {
/* RDS decoder not synchronized */
goto resubmit;
}
for (blocknum = 0; blocknum < 4; blocknum++) {
switch (blocknum) {
default:
bler = (radio->registers[STATUSRSSI] &
STATUSRSSI_BLERA) >> 9;
rds = radio->registers[RDSA];
break;
case 1:
bler = (radio->registers[READCHAN] &
READCHAN_BLERB) >> 14;
rds = radio->registers[RDSB];
break;
case 2:
bler = (radio->registers[READCHAN] &
READCHAN_BLERC) >> 12;
rds = radio->registers[RDSC];
break;
case 3:
bler = (radio->registers[READCHAN] &
READCHAN_BLERD) >> 10;
rds = radio->registers[RDSD];
break;
}
/* Fill the V4L2 RDS buffer */
put_unaligned_le16(rds, &tmpbuf);
tmpbuf[2] = blocknum; /* offset name */
tmpbuf[2] |= blocknum << 3; /* received offset */
if (bler > max_rds_errors)
tmpbuf[2] |= 0x80; /* uncorrectable errors */
else if (bler > 0)
tmpbuf[2] |= 0x40; /* corrected error(s) */
/* copy RDS block to internal buffer */
memcpy(&radio->buffer[radio->wr_index], &tmpbuf, 3);
radio->wr_index += 3;
/* wrap write pointer */
if (radio->wr_index >= radio->buf_size)
radio->wr_index = 0;
/* check for overflow */
if (radio->wr_index == radio->rd_index) {
/* increment and wrap read pointer */
radio->rd_index += 3;
if (radio->rd_index >= radio->buf_size)
radio->rd_index = 0;
}
}
if (radio->wr_index != radio->rd_index)
wake_up_interruptible(&radio->read_queue);
}
resubmit:
/* Resubmit if we're still running. */
if (radio->int_in_running && radio->usbdev) {
retval = usb_submit_urb(radio->int_in_urb, GFP_ATOMIC);
if (retval) {
dev_warn(&radio->intf->dev,
"resubmitting urb failed (%d)", retval);
radio->int_in_running = 0;
}
}
radio->status_rssi_auto_update = radio->int_in_running;
}
static int si470x_fops_open(struct file *file)
{
return v4l2_fh_open(file);
}
static int si470x_fops_release(struct file *file)
{
return v4l2_fh_release(file);
}
static void si470x_usb_release(struct v4l2_device *v4l2_dev)
{
struct si470x_device *radio =
container_of(v4l2_dev, struct si470x_device, v4l2_dev);
usb_free_urb(radio->int_in_urb);
v4l2_ctrl_handler_free(&radio->hdl);
v4l2_device_unregister(&radio->v4l2_dev);
kfree(radio->int_in_buffer);
kfree(radio->buffer);
kfree(radio->usb_buf);
kfree(radio);
}
/**************************************************************************
* Video4Linux Interface
**************************************************************************/
/*
* si470x_vidioc_querycap - query device capabilities
*/
static int si470x_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *capability)
{
struct si470x_device *radio = video_drvdata(file);
strscpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
strscpy(capability->card, DRIVER_CARD, sizeof(capability->card));
usb_make_path(radio->usbdev, capability->bus_info,
sizeof(capability->bus_info));
return 0;
}
static int si470x_start_usb(struct si470x_device *radio)
{
int retval;
/* initialize interrupt urb */
usb_fill_int_urb(radio->int_in_urb, radio->usbdev,
usb_rcvintpipe(radio->usbdev,
radio->int_in_endpoint->bEndpointAddress),
radio->int_in_buffer,
le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize),
si470x_int_in_callback,
radio,
radio->int_in_endpoint->bInterval);
radio->int_in_running = 1;
mb();
retval = usb_submit_urb(radio->int_in_urb, GFP_KERNEL);
if (retval) {
dev_info(&radio->intf->dev,
"submitting int urb failed (%d)\n", retval);
radio->int_in_running = 0;
}
radio->status_rssi_auto_update = radio->int_in_running;
/* start radio */
retval = si470x_start(radio);
if (retval < 0)
return retval;
v4l2_ctrl_handler_setup(&radio->hdl);
return retval;
}
/**************************************************************************
* USB Interface
**************************************************************************/
/*
* si470x_usb_driver_probe - probe for the device
*/
static int si470x_usb_driver_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct si470x_device *radio;
struct usb_host_interface *iface_desc;
int int_end_size, retval;
unsigned char version_warning = 0;
/* private data allocation and initialization */
radio = kzalloc_obj(struct si470x_device);
if (!radio) {
retval = -ENOMEM;
goto err_initial;
}
radio->usb_buf = kmalloc(MAX_REPORT_SIZE, GFP_KERNEL);
if (radio->usb_buf == NULL) {
retval = -ENOMEM;
goto err_radio;
}
radio->usbdev = interface_to_usbdev(intf);
radio->intf = intf;
radio->band = 1; /* Default to 76 - 108 MHz */
mutex_init(&radio->lock);
init_completion(&radio->completion);
radio->get_register = si470x_get_register;
radio->set_register = si470x_set_register;
radio->fops_open = si470x_fops_open;
radio->fops_release = si470x_fops_release;
radio->vidioc_querycap = si470x_vidioc_querycap;
iface_desc = intf->cur_altsetting;
/* Set up interrupt endpoint information. */
retval = usb_find_int_in_endpoint(iface_desc, &radio->int_in_endpoint);
if (retval) {
dev_info(&intf->dev, "could not find interrupt in endpoint\n");
retval = -EIO;
goto err_usbbuf;
}
int_end_size = le16_to_cpu(radio->int_in_endpoint->wMaxPacketSize);
radio->int_in_buffer = kmalloc(int_end_size, GFP_KERNEL);
if (!radio->int_in_buffer) {
dev_info(&intf->dev, "could not allocate int_in_buffer");
retval = -ENOMEM;
goto err_usbbuf;
}
radio->int_in_urb = usb_alloc_urb(0, GFP_KERNEL);
if (!radio->int_in_urb) {
retval = -ENOMEM;
goto err_intbuffer;
}
radio->v4l2_dev.release = si470x_usb_release;
/*
* The si470x SiLabs reference design uses the same USB IDs as
* 'Thanko's Raremono' si4734 based receiver. So check here which we
* have: attempt to read the device ID from the si470x: the lower 12
* bits should be 0x0242 for the si470x.
*
* We use this check to determine which device we are dealing with.
*/
if (id->idVendor == 0x10c4 && id->idProduct == 0x818a) {
retval = usb_control_msg(radio->usbdev,
usb_rcvctrlpipe(radio->usbdev, 0),
HID_REQ_GET_REPORT,
USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN,
1, 2,
radio->usb_buf, 3, 500);
if (retval != 3 ||
(get_unaligned_be16(&radio->usb_buf[1]) & 0xfff) != 0x0242) {
dev_info(&intf->dev, "this is not a si470x device.\n");
retval = -ENODEV;
goto err_urb;
}
}
retval = v4l2_device_register(&intf->dev, &radio->v4l2_dev);
if (retval < 0) {
dev_err(&intf->dev, "couldn't register v4l2_device\n");
goto err_urb;
}
v4l2_ctrl_handler_init(&radio->hdl, 2);
v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops,
V4L2_CID_AUDIO_MUTE, 0, 1, 1, 1);
v4l2_ctrl_new_std(&radio->hdl, &si470x_ctrl_ops,
V4L2_CID_AUDIO_VOLUME, 0, 15, 1, 15);
if (radio->hdl.error) {
retval = radio->hdl.error;
dev_err(&intf->dev, "couldn't register control\n");
goto err_dev;
}
radio->videodev = si470x_viddev_template;
radio->videodev.ctrl_handler = &radio->hdl;
radio->videodev.lock = &radio->lock;
radio->videodev.v4l2_dev = &radio->v4l2_dev;
radio->videodev.release = video_device_release_empty;
radio->videodev.device_caps =
V4L2_CAP_HW_FREQ_SEEK | V4L2_CAP_READWRITE | V4L2_CAP_TUNER |
V4L2_CAP_RADIO | V4L2_CAP_RDS_CAPTURE;
video_set_drvdata(&radio->videodev, radio);
/* get device and chip versions */
if (si470x_get_all_registers(radio) < 0) {
retval = -EIO;
goto err_ctrl;
}
dev_info(&intf->dev, "DeviceID=0x%4.4hx ChipID=0x%4.4hx\n",
radio->registers[DEVICEID], radio->registers[SI_CHIPID]);
if ((radio->registers[SI_CHIPID] & SI_CHIPID_FIRMWARE) < RADIO_FW_VERSION) {
dev_warn(&intf->dev,
"This driver is known to work with firmware version %u, but the device has firmware version %u.\n",
RADIO_FW_VERSION,
radio->registers[SI_CHIPID] & SI_CHIPID_FIRMWARE);
version_warning = 1;
}
/* get software and hardware versions */
if (si470x_get_scratch_page_versions(radio) < 0) {
retval = -EIO;
goto err_ctrl;
}
dev_info(&intf->dev, "software version %d, hardware version %d\n",
radio->software_version, radio->hardware_version);
if (radio->hardware_version < RADIO_HW_VERSION) {
dev_warn(&intf->dev,
"This driver is known to work with hardware version %u, but the device has hardware version %u.\n",
RADIO_HW_VERSION,
radio->hardware_version);
version_warning = 1;
}
/* give out version warning */
if (version_warning == 1) {
dev_warn(&intf->dev,
"If you have some trouble using this driver, please report to V4L ML at linux-media@vger.kernel.org\n");
}
/* set led to connect state */
si470x_set_led_state(radio, BLINK_GREEN_LED);
/* rds buffer allocation */
radio->buf_size = rds_buf * 3;
radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL);
if (!radio->buffer) {
retval = -EIO;
goto err_ctrl;
}
/* rds buffer configuration */
radio->wr_index = 0;
radio->rd_index = 0;
init_waitqueue_head(&radio->read_queue);
usb_set_intfdata(intf, radio);
/* start radio */
retval = si470x_start_usb(radio);
if (retval < 0 && !radio->int_in_running)
goto err_buf;
else if (retval < 0) /* in case of radio->int_in_running == 1 */
goto err_all;
/* set initial frequency */
si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */
/* register video device */
retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO,
radio_nr);
if (retval) {
dev_err(&intf->dev, "Could not register video device\n");
goto err_all;
}
return 0;
err_all:
usb_kill_urb(radio->int_in_urb);
err_buf:
kfree(radio->buffer);
err_ctrl:
v4l2_ctrl_handler_free(&radio->hdl);
err_dev:
v4l2_device_unregister(&radio->v4l2_dev);
err_urb:
usb_free_urb(radio->int_in_urb);
err_intbuffer:
kfree(radio->int_in_buffer);
err_usbbuf:
kfree(radio->usb_buf);
err_radio:
kfree(radio);
err_initial:
return retval;
}
/*
* si470x_usb_driver_suspend - suspend the device
*/
static int si470x_usb_driver_suspend(struct usb_interface *intf,
pm_message_t message)
{
struct si470x_device *radio = usb_get_intfdata(intf);
dev_info(&intf->dev, "suspending now...\n");
/* shutdown interrupt handler */
if (radio->int_in_running) {
radio->int_in_running = 0;
if (radio->int_in_urb)
usb_kill_urb(radio->int_in_urb);
}
/* cancel read processes */
wake_up_interruptible(&radio->read_queue);
/* stop radio */
si470x_stop(radio);
return 0;
}
/*
* si470x_usb_driver_resume - resume the device
*/
static int si470x_usb_driver_resume(struct usb_interface *intf)
{
struct si470x_device *radio = usb_get_intfdata(intf);
int ret;
dev_info(&intf->dev, "resuming now...\n");
/* start radio */
ret = si470x_start_usb(radio);
if (ret == 0)
v4l2_ctrl_handler_setup(&radio->hdl);
return ret;
}
/*
* si470x_usb_driver_disconnect - disconnect the device
*/
static void si470x_usb_driver_disconnect(struct usb_interface *intf)
{
struct si470x_device *radio = usb_get_intfdata(intf);
mutex_lock(&radio->lock);
v4l2_device_disconnect(&radio->v4l2_dev);
video_unregister_device(&radio->videodev);
usb_kill_urb(radio->int_in_urb);
usb_set_intfdata(intf, NULL);
mutex_unlock(&radio->lock);
v4l2_device_put(&radio->v4l2_dev);
}
/*
* si470x_usb_driver - usb driver interface
*
* A note on suspend/resume: this driver had only empty suspend/resume
* functions, and when I tried to test suspend/resume it always disconnected
* instead of resuming (using my ADS InstantFM stick). So I've decided to
* remove these callbacks until someone else with better hardware can
* implement and test this.
*/
static struct usb_driver si470x_usb_driver = {
.name = DRIVER_NAME,
.probe = si470x_usb_driver_probe,
.disconnect = si470x_usb_driver_disconnect,
.suspend = si470x_usb_driver_suspend,
.resume = si470x_usb_driver_resume,
.reset_resume = si470x_usb_driver_resume,
.id_table = si470x_usb_driver_id_table,
};
module_usb_driver(si470x_usb_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION(DRIVER_VERSION);
@@ -0,0 +1,220 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* drivers/media/radio/si470x/radio-si470x.h
*
* Driver for radios with Silicon Labs Si470x FM Radio Receivers
*
* Copyright (c) 2009 Tobias Lorenz <tobias.lorenz@gmx.net>
*/
/* driver definitions */
#define DRIVER_NAME "radio-si470x"
/* kernel includes */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/videodev2.h>
#include <linux/mutex.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 <linux/unaligned.h>
/**************************************************************************
* Register Definitions
**************************************************************************/
#define RADIO_REGISTER_SIZE 2 /* 16 register bit width */
#define RADIO_REGISTER_NUM 16 /* DEVICEID ... RDSD */
#define RDS_REGISTER_NUM 6 /* STATUSRSSI ... RDSD */
#define DEVICEID 0 /* Device ID */
#define DEVICEID_PN 0xf000 /* bits 15..12: Part Number */
#define DEVICEID_MFGID 0x0fff /* bits 11..00: Manufacturer ID */
#define SI_CHIPID 1 /* Chip ID */
#define SI_CHIPID_REV 0xfc00 /* bits 15..10: Chip Version */
#define SI_CHIPID_DEV 0x0200 /* bits 09..09: Device */
#define SI_CHIPID_FIRMWARE 0x01ff /* bits 08..00: Firmware Version */
#define POWERCFG 2 /* Power Configuration */
#define POWERCFG_DSMUTE 0x8000 /* bits 15..15: Softmute Disable */
#define POWERCFG_DMUTE 0x4000 /* bits 14..14: Mute Disable */
#define POWERCFG_MONO 0x2000 /* bits 13..13: Mono Select */
#define POWERCFG_RDSM 0x0800 /* bits 11..11: RDS Mode (Si4701 only) */
#define POWERCFG_SKMODE 0x0400 /* bits 10..10: Seek Mode */
#define POWERCFG_SEEKUP 0x0200 /* bits 09..09: Seek Direction */
#define POWERCFG_SEEK 0x0100 /* bits 08..08: Seek */
#define POWERCFG_DISABLE 0x0040 /* bits 06..06: Powerup Disable */
#define POWERCFG_ENABLE 0x0001 /* bits 00..00: Powerup Enable */
#define CHANNEL 3 /* Channel */
#define CHANNEL_TUNE 0x8000 /* bits 15..15: Tune */
#define CHANNEL_CHAN 0x03ff /* bits 09..00: Channel Select */
#define SYSCONFIG1 4 /* System Configuration 1 */
#define SYSCONFIG1_RDSIEN 0x8000 /* bits 15..15: RDS Interrupt Enable (Si4701 only) */
#define SYSCONFIG1_STCIEN 0x4000 /* bits 14..14: Seek/Tune Complete Interrupt Enable */
#define SYSCONFIG1_RDS 0x1000 /* bits 12..12: RDS Enable (Si4701 only) */
#define SYSCONFIG1_DE 0x0800 /* bits 11..11: De-emphasis (0=75us 1=50us) */
#define SYSCONFIG1_AGCD 0x0400 /* bits 10..10: AGC Disable */
#define SYSCONFIG1_BLNDADJ 0x00c0 /* bits 07..06: Stereo/Mono Blend Level Adjustment */
#define SYSCONFIG1_GPIO3 0x0030 /* bits 05..04: General Purpose I/O 3 */
#define SYSCONFIG1_GPIO2 0x000c /* bits 03..02: General Purpose I/O 2 */
#define SYSCONFIG1_GPIO2_DIS 0x0000 /* Disable GPIO 2 interrupt */
#define SYSCONFIG1_GPIO2_INT 0x0004 /* Enable STC/RDS interrupt */
#define SYSCONFIG1_GPIO1 0x0003 /* bits 01..00: General Purpose I/O 1 */
#define SYSCONFIG2 5 /* System Configuration 2 */
#define SYSCONFIG2_SEEKTH 0xff00 /* bits 15..08: RSSI Seek Threshold */
#define SYSCONFIG2_BAND 0x00c0 /* bits 07..06: Band Select */
#define SYSCONFIG2_SPACE 0x0030 /* bits 05..04: Channel Spacing */
#define SYSCONFIG2_VOLUME 0x000f /* bits 03..00: Volume */
#define SYSCONFIG3 6 /* System Configuration 3 */
#define SYSCONFIG3_SMUTER 0xc000 /* bits 15..14: Softmute Attack/Recover Rate */
#define SYSCONFIG3_SMUTEA 0x3000 /* bits 13..12: Softmute Attenuation */
#define SYSCONFIG3_SKSNR 0x00f0 /* bits 07..04: Seek SNR Threshold */
#define SYSCONFIG3_SKCNT 0x000f /* bits 03..00: Seek FM Impulse Detection Threshold */
#define TEST1 7 /* Test 1 */
#define TEST1_AHIZEN 0x4000 /* bits 14..14: Audio High-Z Enable */
#define TEST2 8 /* Test 2 */
/* TEST2 only contains reserved bits */
#define BOOTCONFIG 9 /* Boot Configuration */
/* BOOTCONFIG only contains reserved bits */
#define STATUSRSSI 10 /* Status RSSI */
#define STATUSRSSI_RDSR 0x8000 /* bits 15..15: RDS Ready (Si4701 only) */
#define STATUSRSSI_STC 0x4000 /* bits 14..14: Seek/Tune Complete */
#define STATUSRSSI_SF 0x2000 /* bits 13..13: Seek Fail/Band Limit */
#define STATUSRSSI_AFCRL 0x1000 /* bits 12..12: AFC Rail */
#define STATUSRSSI_RDSS 0x0800 /* bits 11..11: RDS Synchronized (Si4701 only) */
#define STATUSRSSI_BLERA 0x0600 /* bits 10..09: RDS Block A Errors (Si4701 only) */
#define STATUSRSSI_ST 0x0100 /* bits 08..08: Stereo Indicator */
#define STATUSRSSI_RSSI 0x00ff /* bits 07..00: RSSI (Received Signal Strength Indicator) */
#define READCHAN 11 /* Read Channel */
#define READCHAN_BLERB 0xc000 /* bits 15..14: RDS Block D Errors (Si4701 only) */
#define READCHAN_BLERC 0x3000 /* bits 13..12: RDS Block C Errors (Si4701 only) */
#define READCHAN_BLERD 0x0c00 /* bits 11..10: RDS Block B Errors (Si4701 only) */
#define READCHAN_READCHAN 0x03ff /* bits 09..00: Read Channel */
#define RDSA 12 /* RDSA */
#define RDSA_RDSA 0xffff /* bits 15..00: RDS Block A Data (Si4701 only) */
#define RDSB 13 /* RDSB */
#define RDSB_RDSB 0xffff /* bits 15..00: RDS Block B Data (Si4701 only) */
#define RDSC 14 /* RDSC */
#define RDSC_RDSC 0xffff /* bits 15..00: RDS Block C Data (Si4701 only) */
#define RDSD 15 /* RDSD */
#define RDSD_RDSD 0xffff /* bits 15..00: RDS Block D Data (Si4701 only) */
/**************************************************************************
* General Driver Definitions
**************************************************************************/
/*
* si470x_device - private data
*/
struct si470x_device {
struct v4l2_device v4l2_dev;
struct video_device videodev;
struct v4l2_ctrl_handler hdl;
int band;
/* Silabs internal registers (0..15) */
unsigned short registers[RADIO_REGISTER_NUM];
/* RDS receive buffer */
wait_queue_head_t read_queue;
struct mutex lock; /* buffer locking */
unsigned char *buffer; /* size is always multiple of three */
unsigned int buf_size;
unsigned int rd_index;
unsigned int wr_index;
struct completion completion;
bool status_rssi_auto_update; /* Does RSSI get updated automatic? */
/* si470x ops */
int (*get_register)(struct si470x_device *radio, int regnr);
int (*set_register)(struct si470x_device *radio, int regnr);
int (*fops_open)(struct file *file);
int (*fops_release)(struct file *file);
int (*vidioc_querycap)(struct file *file, void *priv,
struct v4l2_capability *capability);
#if IS_ENABLED(CONFIG_USB_SI470X)
/* reference to USB and video device */
struct usb_device *usbdev;
struct usb_interface *intf;
char *usb_buf;
/* Interrupt endpoint handling */
char *int_in_buffer;
struct usb_endpoint_descriptor *int_in_endpoint;
struct urb *int_in_urb;
int int_in_running;
/* scratch page */
unsigned char software_version;
unsigned char hardware_version;
#endif
#if IS_ENABLED(CONFIG_I2C_SI470X)
struct i2c_client *client;
struct gpio_desc *gpio_reset;
#endif
};
/**************************************************************************
* Firmware Versions
**************************************************************************/
#define RADIO_FW_VERSION 12
/**************************************************************************
* Frequency Multiplicator
**************************************************************************/
/*
* The frequency is set in units of 62.5 Hz when using V4L2_TUNER_CAP_LOW,
* 62.5 kHz otherwise.
* The tuner is able to have a channel spacing of 50, 100 or 200 kHz.
* tuner->capability is therefore set to V4L2_TUNER_CAP_LOW
* The FREQ_MUL is then: 1 MHz / 62.5 Hz = 16000
*/
#define FREQ_MUL (1000000 / 62.5)
/**************************************************************************
* Common Functions
**************************************************************************/
extern const struct video_device si470x_viddev_template;
extern const struct v4l2_ctrl_ops si470x_ctrl_ops;
int si470x_disconnect_check(struct si470x_device *radio);
int si470x_set_freq(struct si470x_device *radio, unsigned int freq);
int si470x_start(struct si470x_device *radio);
int si470x_stop(struct si470x_device *radio);