Skip to content

Instantly share code, notes, and snippets.

@izumogeiger
Created August 19, 2013 11:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save izumogeiger/6268289 to your computer and use it in GitHub Desktop.
Save izumogeiger/6268289 to your computer and use it in GitHub Desktop.
# 先ほど決めたパッケージ名です
PACKAGE_NAME="radio_rdpc101"
# 先ほど決めたパッケージ版数
PACKAGE_VERSION="0.0.1"
# 中間ファイルの削除コマンド。指定しない場合は、make cleanとみなされます。
CLEAN="make clean"
# ビルドするモジュールの生成コマンド。MAKE[0]がデフォルトのビルドコマンドとなります。
MAKE[0]="cd ${dkms_tree}/radio_rdpc101-0.0.1;make"
# ビルドされたモジュールが作成されるディレクトリまでの相対パス。
# 以下[]内の番号が一致するものが一揃いで動作します。
BUILT_MODULE_LOCATION[0]="./"
# ビルドだれたモジュールの名前です。拡張子(.ko)は除きます。
BUILT_MODULE_NAME[0]="radio_rdpc101"
# ビルドしたモジュールをインストールするディレクトリを指定します。
DEST_MODULE_LOCATION[0]="/updates/dkms"
AUTOINSTALL="yes"
obj-m := radio_rdpc101.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
rm *.ko
rm *.o
rm *.mod.*
rm *.order
rm *.symvers
/*
* drivers/media/radio/radio-rdpc101.c
*
* Driver for SUNTAC USB AM/FM radio receiver
*
* derived from radio-si470x.c
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/* driver definitions */
#define DRIVER_AUTHOR "your own"
#define DRIVER_NAME "radio-rdpc101"
#define DRIVER_KERNEL_VERSION KERNEL_VERSION(0, 0, 1)
#define DRIVER_CARD "SUNTAC RDPC-101 USB radio receiver"
#define DRIVER_DESC "USB radio driver for SUNTAC RDPC-101"
#define DRIVER_VERSION "0.0.1"
/* kernel includes */
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/input.h>
#include <linux/usb.h>
#include <linux/hid.h>
#include <linux/version.h>
#include <linux/videodev2.h>
#include <linux/mutex.h>
#include <media/v4l2-common.h>
#include <media/v4l2-ioctl.h>
#include <asm/unaligned.h>
/* USB Device ID List */
static struct usb_device_id rdpc101_usb_driver_id_table[] = {
/* Silicon Labs USB FM Radio Reference Design */
{ USB_DEVICE_AND_INTERFACE_INFO(0x10c4, 0x818a, USB_CLASS_HID, 0, 0) },
/* Terminating entry */
{ }
};
MODULE_DEVICE_TABLE(usb, rdpc101_usb_driver_id_table);
/**************************************************************************
* Module Parameters
**************************************************************************/
/* Radio Nr */
static int radio_nr = -1;
#define FREQ_STEP_FM_KHZ (100) /* 100[kHz] */
#define FREQ_STEP_AM_KHZ (9) /* 9[kHz] */
/*
* FM: 76.0 - 90.0 [MHz]
* AM: 522 - 1629 [kHz]
*/
#define FREQ_LOW_FM_KHZ (76 * 1000)
#define FREQ_HIGH_FM_KHZ (90 * 1000)
#define FREQ_LOW_AM_KHZ (522)
#define FREQ_HIGH_AM_KHZ (1629)
/* USB timeout */
static unsigned int usb_timeout = 500;
#if 0
/* Tune timeout */
static unsigned int tune_timeout = 3000;
/* Seek timeout */
static unsigned int seek_timeout = 5000;
#endif
/**************************************************************************
* 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 CHIPID 1 /* Chip ID */
#define CHIPID_REV 0xfc00 /* bits 15..10: Chip Version */
#define CHIPID_DEV 0x0200 /* bits 09..09: Device */
#define 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_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 0x0080 /* 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) */
/**************************************************************************
* USB HID Reports
**************************************************************************/
/*
* frequency setting
*/
#define REPORT_ID_SET_FREQ (0x02)
#define REPORT_SIZE_SET_FREQ (3)
/*
* AM/FM tuner mode switching
*/
#define REPORT_ID_TUNER_MODE (0x0A)
#define REPORT_SIZE_TUNER_MODE (3)
/* Report 19: LED state */
#define LED_REPORT_SIZE 3
#define LED_REPORT 19
/**************************************************************************
* Software/Hardware Versions
**************************************************************************/
#define RADIO_SW_VERSION_NOT_BOOTLOADABLE 6
#define RADIO_SW_VERSION 7
#define RADIO_SW_VERSION_CURRENT 15
#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
/**************************************************************************
* General Driver Definitions
**************************************************************************/
enum {
TUNER_MODE_NONE = 0,
TUNER_MODE_FM = 1,
TUNER_MODE_AM = 2
};
/*
* rdpc101_device - private data
*/
struct rdpc101_device {
/* reference to USB and video device */
struct usb_device *usbdev;
struct usb_interface *intf;
struct video_device *videodev;
/* driver management */
unsigned int users;
unsigned char disconnected;
struct mutex disconnect_lock;
/* for control */
int tuner_mode; /* TUNER_MODE_xxx */
int freq_khz; /* frequency as kHz */
};
/*
* 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)
#define FREQ_MUL (1000000 * 10 / 625)
#define FREQ_KHZ_TO_V4L(val) ((val) * 16)
#define FREQ_V4L_TO_KHZ(val) ((val) / 16)
/**************************************************************************
* General Driver Functions
**************************************************************************/
/*
* rdpc101_get_report - receive a HID report
*/
static int rdpc101_get_report(struct rdpc101_device *radio, void *buf, int size)
{
unsigned char *report = (unsigned char *) 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)
printk(KERN_WARNING DRIVER_NAME
": rdpc101_get_report: usb_control_msg returned %d\n",
retval);
return retval;
}
/*
* rdpc101_set_report - send a HID report
*/
static int rdpc101_set_report(struct rdpc101_device *radio, void *buf, int size)
{
unsigned char *report = (unsigned char *) 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)
printk(KERN_WARNING DRIVER_NAME
": rdpc101_set_report: usb_control_msg returned %d\n",
retval);
return retval;
}
/**************************************************************************
* control functions
**************************************************************************/
/*
* rdpc101_set_tuner_mode
*/
static int rdpc101_set_tuner_mode( struct rdpc101_device* radio, int mode )
{
unsigned char buf[REPORT_SIZE_TUNER_MODE];
int retval;
int err = 0;
if( mode == radio->tuner_mode ){
goto l_exit;
}
switch( mode ){
case TUNER_MODE_AM:
case TUNER_MODE_FM:
break;
default:
err = -EINVAL;
goto l_exit;
}
buf[0] = REPORT_ID_TUNER_MODE;
buf[1] = (mode == TUNER_MODE_AM) ? 0x80 : 0x02;
buf[2] = 0x02;
retval = rdpc101_set_report( radio, (void*)&buf, sizeof(buf) );
if( retval < 0 ){
err = -EINVAL;
goto l_exit;
}
radio->tuner_mode = mode;
l_exit:;
return err;
}
/*
* rdpc101_get_freq - get the frequency
*/
static int rdpc101_get_freq(struct rdpc101_device *radio, unsigned int *freq)
{
*freq = FREQ_KHZ_TO_V4L( radio->freq_khz );
return 0;
}
/*
* rdpc101_set_freq - set the frequency
*/
static int rdpc101_set_freq(struct rdpc101_device *radio, unsigned int freq)
{
int freq_khz;
int freq_step;
int freq_low;
int freq_high;
int temp;
unsigned char buf[REPORT_SIZE_SET_FREQ];
int retval;
int err = 0;
freq_khz = FREQ_V4L_TO_KHZ( freq );
if( radio->tuner_mode == TUNER_MODE_AM ){
freq_step = FREQ_STEP_AM_KHZ;
freq_low = FREQ_LOW_AM_KHZ;
freq_high = FREQ_HIGH_AM_KHZ;
} else{
freq_step = FREQ_STEP_FM_KHZ;
freq_low = FREQ_LOW_FM_KHZ;
freq_high = FREQ_HIGH_FM_KHZ;
}
if( (freq_khz < freq_low) || (freq_high < freq_khz) ){
err = -EINVAL;
goto l_exit;
}
/* round frequency by step value */
temp = freq_khz % freq_step;
freq_khz -= temp;
radio->freq_khz = freq_khz;
buf[0] = REPORT_ID_SET_FREQ;
if( radio->tuner_mode == TUNER_MODE_FM ){
freq_khz /= 10;
}
buf[1] = (freq_khz >> 8) & 0xFF;
buf[2] = freq_khz & 0xFF;
printk("rdpc101: freq set value = %d\n",freq_khz);
retval = rdpc101_set_report( radio, (void*)&buf, sizeof(buf) );
if( retval < 0 ){
err = -EINVAL;
}
l_exit:;
return err;
}
/**************************************************************************
* General Driver Functions - LED_REPORT
**************************************************************************/
/*
* rdpc101_set_led_state - sets the led state
*/
static int rdpc101_set_led_state(struct rdpc101_device *radio,
unsigned char led_state)
{
unsigned char buf[LED_REPORT_SIZE];
int retval;
buf[0] = LED_REPORT;
buf[1] = LED_COMMAND;
buf[2] = led_state;
retval = rdpc101_set_report(radio, (void *) &buf, sizeof(buf));
return (retval < 0) ? -EINVAL : 0;
}
/**************************************************************************
* File Operations Interface
**************************************************************************/
/*
* rdpc101_fops_open - file open
*/
static int rdpc101_fops_open(struct file *file)
{
struct rdpc101_device *radio = video_drvdata(file);
int retval;
radio->users++;
retval = usb_autopm_get_interface(radio->intf);
if (retval < 0) {
radio->users--;
retval = -EIO;
goto done;
}
done:
return retval;
}
/*
* rdpc101_fops_release - file release
*/
static int rdpc101_fops_release(struct file *file)
{
struct rdpc101_device *radio = video_drvdata(file);
int retval = 0;
/* safety check */
if (!radio) {
retval = -ENODEV;
goto done;
}
mutex_lock(&radio->disconnect_lock);
radio->users--;
if (radio->users == 0) {
if (radio->disconnected) {
video_unregister_device(radio->videodev);
kfree(radio);
goto unlock;
}
usb_autopm_put_interface(radio->intf);
}
unlock:
mutex_unlock(&radio->disconnect_lock);
done:
return retval;
}
/*
* rdpc101_fops - file operations interface
*/
static const struct v4l2_file_operations rdpc101_fops = {
.owner = THIS_MODULE,
.ioctl = video_ioctl2,
.open = rdpc101_fops_open,
.release = rdpc101_fops_release,
};
/**************************************************************************
* Video4Linux Interface
**************************************************************************/
/*
* rdpc101_v4l2_queryctrl - query control
*/
static struct v4l2_queryctrl rdpc101_v4l2_queryctrl[] = {
{
.id = V4L2_CID_AUDIO_VOLUME,
.type = V4L2_CTRL_TYPE_INTEGER,
.name = "Volume",
.minimum = 0,
.maximum = 15,
.step = 1,
.default_value = 15,
},
{
.id = V4L2_CID_AUDIO_MUTE,
.type = V4L2_CTRL_TYPE_BOOLEAN,
.name = "Mute",
.minimum = 0,
.maximum = 1,
.step = 1,
.default_value = 1,
},
};
/*
* rdpc101_vidioc_querycap - query device capabilities
*/
static int rdpc101_vidioc_querycap(struct file *file, void *priv,
struct v4l2_capability *capability)
{
strlcpy(capability->driver, DRIVER_NAME, sizeof(capability->driver));
strlcpy(capability->card, DRIVER_CARD, sizeof(capability->card));
sprintf(capability->bus_info, "USB");
capability->version = DRIVER_KERNEL_VERSION;
#if 1
capability->capabilities = 0 |
V4L2_CAP_TUNER | V4L2_CAP_RADIO;
#else
capability->capabilities = V4L2_CAP_HW_FREQ_SEEK |
V4L2_CAP_TUNER | V4L2_CAP_RADIO;
#endif
return 0;
}
/*
* rdpc101_vidioc_queryctrl - enumerate control items
*/
static int rdpc101_vidioc_queryctrl(struct file *file, void *priv,
struct v4l2_queryctrl *qc)
{
unsigned char i = 0;
int retval = -EINVAL;
/* abort if qc->id is below V4L2_CID_BASE */
if (qc->id < V4L2_CID_BASE)
goto done;
/* search video control */
for (i = 0; i < ARRAY_SIZE(rdpc101_v4l2_queryctrl); i++) {
if (qc->id == rdpc101_v4l2_queryctrl[i].id) {
memcpy(qc, &(rdpc101_v4l2_queryctrl[i]), sizeof(*qc));
retval = 0; /* found */
break;
}
}
/* disable unsupported base controls */
/* to satisfy kradio and such apps */
if ((retval == -EINVAL) && (qc->id < V4L2_CID_LASTP1)) {
qc->flags = V4L2_CTRL_FLAG_DISABLED;
retval = 0;
}
done:
if (retval < 0)
printk(KERN_WARNING DRIVER_NAME
": query controls failed with %d\n", retval);
return retval;
}
/*
* rdpc101_vidioc_g_ctrl - get the value of a control
*/
static int rdpc101_vidioc_g_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct rdpc101_device *radio = video_drvdata(file);
int retval = 0;
/* safety checks */
if (radio->disconnected) {
retval = -EIO;
goto done;
}
switch (ctrl->id) {
case V4L2_CID_AUDIO_VOLUME:
#if 0
ctrl->value = radio->registers[SYSCONFIG2] &
SYSCONFIG2_VOLUME;
#endif
break;
case V4L2_CID_AUDIO_MUTE:
#if 0
ctrl->value = ((radio->registers[POWERCFG] &
POWERCFG_DMUTE) == 0) ? 1 : 0;
#endif
break;
default:
retval = -EINVAL;
}
done:
if (retval < 0)
printk(KERN_WARNING DRIVER_NAME
": get control failed with %d\n", retval);
return retval;
}
/*
* rdpc101_vidioc_s_ctrl - set the value of a control
*/
static int rdpc101_vidioc_s_ctrl(struct file *file, void *priv,
struct v4l2_control *ctrl)
{
struct rdpc101_device *radio = video_drvdata(file);
int retval = 0;
/* safety checks */
if (radio->disconnected) {
retval = -EIO;
goto done;
}
switch (ctrl->id) {
case V4L2_CID_AUDIO_VOLUME:
#if 0
radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME;
radio->registers[SYSCONFIG2] |= ctrl->value;
retval = rdpc101_set_register(radio, SYSCONFIG2);
#endif
break;
case V4L2_CID_AUDIO_MUTE:
#if 0
if (ctrl->value == 1)
radio->registers[POWERCFG] &= ~POWERCFG_DMUTE;
else
radio->registers[POWERCFG] |= POWERCFG_DMUTE;
retval = rdpc101_set_register(radio, POWERCFG);
#endif
break;
default:
retval = -EINVAL;
}
done:
if (retval < 0)
printk(KERN_WARNING DRIVER_NAME
": set control failed with %d\n", retval);
return retval;
}
/*
* rdpc101_vidioc_g_audio - get audio attributes
*/
static int rdpc101_vidioc_g_audio(struct file *file, void *priv,
struct v4l2_audio *audio)
{
/* driver constants */
audio->index = 0;
strcpy(audio->name, "Radio");
audio->capability = V4L2_AUDCAP_STEREO;
audio->mode = 0;
return 0;
}
/*
* rdpc101_vidioc_g_tuner - get tuner attributes
*/
static int rdpc101_vidioc_g_tuner(struct file *file, void *priv,
struct v4l2_tuner *tuner)
{
struct rdpc101_device *radio = video_drvdata(file);
int retval = 0;
/* safety checks */
if (radio->disconnected) {
retval = -EIO;
goto done;
}
if (tuner->index != 0) {
retval = -EINVAL;
goto done;
}
/* driver constants */
strcpy(tuner->name, "FM");
tuner->type = V4L2_TUNER_RADIO;
tuner->capability = V4L2_TUNER_CAP_LOW | V4L2_TUNER_CAP_STEREO;
/* range limits */
tuner->rangelow = FREQ_KHZ_TO_V4L( FREQ_LOW_FM_KHZ );
tuner->rangehigh = FREQ_KHZ_TO_V4L( FREQ_HIGH_FM_KHZ );
#if 1
tuner->rxsubchans = V4L2_TUNER_SUB_MONO;
#else
/* 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_MONO | V4L2_TUNER_SUB_STEREO;
#endif
#if 1
tuner->audmode = V4L2_TUNER_MODE_MONO;
#else
/* mono/stereo selector */
if ((radio->registers[POWERCFG] & POWERCFG_MONO) == 0)
tuner->audmode = V4L2_TUNER_MODE_STEREO;
else
tuner->audmode = V4L2_TUNER_MODE_MONO;
#endif
#if 1
tuner->signal = 0;
tuner->afc = 0;
#else
/* 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);
/* 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;
#endif
done:
if (retval < 0)
printk(KERN_WARNING DRIVER_NAME
": get tuner failed with %d\n", retval);
return retval;
}
/*
* rdpc101_vidioc_s_tuner - set tuner attributes
*/
static int rdpc101_vidioc_s_tuner(struct file *file, void *priv,
struct v4l2_tuner *tuner)
{
struct rdpc101_device *radio = video_drvdata(file);
int retval = -EINVAL;
/* safety checks */
if (radio->disconnected) {
retval = -EIO;
goto done;
}
if (tuner->index != 0)
goto done;
/* mono/stereo selector */
#if 0
switch (tuner->audmode) {
case V4L2_TUNER_MODE_MONO:
radio->registers[POWERCFG] |= POWERCFG_MONO; /* force mono */
break;
case V4L2_TUNER_MODE_STEREO:
radio->registers[POWERCFG] &= ~POWERCFG_MONO; /* try stereo */
break;
default:
goto done;
}
retval = rdpc101_set_register(radio, POWERCFG);
#endif
done:
if (retval < 0)
printk(KERN_WARNING DRIVER_NAME
": set tuner failed with %d\n", retval);
return retval;
}
/*
* rdpc101_vidioc_g_frequency - get tuner or modulator radio frequency
*/
static int rdpc101_vidioc_g_frequency(struct file *file, void *priv,
struct v4l2_frequency *freq)
{
struct rdpc101_device *radio = video_drvdata(file);
int retval = 0;
/* safety checks */
if (radio->disconnected) {
retval = -EIO;
goto done;
}
if (freq->tuner != 0) {
retval = -EINVAL;
goto done;
}
freq->type = V4L2_TUNER_RADIO;
retval = rdpc101_get_freq(radio, &freq->frequency);
done:
if (retval < 0)
printk(KERN_WARNING DRIVER_NAME
": get frequency failed with %d\n", retval);
return retval;
}
/*
* rdpc101_vidioc_s_frequency - set tuner or modulator radio frequency
*/
static int rdpc101_vidioc_s_frequency(struct file *file, void *priv,
struct v4l2_frequency *freq)
{
struct rdpc101_device *radio = video_drvdata(file);
int retval = 0;
/* safety checks */
if (radio->disconnected) {
retval = -EIO;
goto done;
}
if (freq->tuner != 0) {
retval = -EINVAL;
goto done;
}
/*
* judge tuner mode by frequency value.
* < 10000 ... assume as AM frequency
* >= 10000 ... assume as FM frequency
*/
if( freq->frequency < 10000 ){
retval = rdpc101_set_tuner_mode( radio, TUNER_MODE_AM );
freq->frequency *= 16;
} else{
retval = rdpc101_set_tuner_mode( radio, TUNER_MODE_FM );
}
if( retval < 0 ){
goto done;
}
retval = rdpc101_set_freq(radio, freq->frequency);
done:
if (retval < 0)
printk(KERN_WARNING DRIVER_NAME
": set frequency failed with %d\n", retval);
return retval;
}
/*
* rdpc101_vidioc_s_hw_freq_seek - set hardware frequency seek
*/
static int rdpc101_vidioc_s_hw_freq_seek(struct file *file, void *priv,
struct v4l2_hw_freq_seek *seek)
{
struct rdpc101_device *radio = video_drvdata(file);
int retval = 0;
/* safety checks */
if (radio->disconnected) {
retval = -EIO;
goto done;
}
if (seek->tuner != 0) {
retval = -EINVAL;
goto done;
}
#if 0
retval = rdpc101_set_seek(radio, seek->wrap_around, seek->seek_upward);
#endif
done:
if (retval < 0)
printk(KERN_WARNING DRIVER_NAME
": set hardware frequency seek failed with %d\n",
retval);
return retval;
}
/*
* rdpc101_ioctl_ops - video device ioctl operations
*/
static const struct v4l2_ioctl_ops rdpc101_ioctl_ops = {
.vidioc_querycap = rdpc101_vidioc_querycap,
.vidioc_queryctrl = rdpc101_vidioc_queryctrl,
.vidioc_g_ctrl = rdpc101_vidioc_g_ctrl,
.vidioc_s_ctrl = rdpc101_vidioc_s_ctrl,
.vidioc_g_audio = rdpc101_vidioc_g_audio,
.vidioc_g_tuner = rdpc101_vidioc_g_tuner,
.vidioc_s_tuner = rdpc101_vidioc_s_tuner,
.vidioc_g_frequency = rdpc101_vidioc_g_frequency,
.vidioc_s_frequency = rdpc101_vidioc_s_frequency,
.vidioc_s_hw_freq_seek = rdpc101_vidioc_s_hw_freq_seek,
};
/*
* rdpc101_viddev_template - video device interface
*/
static struct video_device rdpc101_viddev_template = {
.fops = &rdpc101_fops,
.name = DRIVER_NAME,
.release = video_device_release,
.ioctl_ops = &rdpc101_ioctl_ops,
};
/**************************************************************************
* USB Interface
**************************************************************************/
/*
* rdpc101_usb_driver_probe - probe for the device
*/
static int rdpc101_usb_driver_probe(struct usb_interface *intf,
const struct usb_device_id *id)
{
struct rdpc101_device *radio;
int retval = 0;
/* private data allocation and initialization */
radio = kzalloc(sizeof(struct rdpc101_device), GFP_KERNEL);
if (!radio) {
retval = -ENOMEM;
goto err_initial;
}
radio->users = 0;
radio->disconnected = 0;
radio->usbdev = interface_to_usbdev(intf);
radio->intf = intf;
mutex_init(&radio->disconnect_lock);
radio->tuner_mode = TUNER_MODE_NONE;
radio->freq_khz = 0;
/* video device allocation and initialization */
radio->videodev = video_device_alloc();
if (!radio->videodev) {
retval = -ENOMEM;
goto err_radio;
}
memcpy(radio->videodev, &rdpc101_viddev_template,
sizeof(rdpc101_viddev_template));
video_set_drvdata(radio->videodev, radio);
/* set tuner mode */
rdpc101_set_tuner_mode( radio, TUNER_MODE_FM );
/* set initial frequency */
rdpc101_set_freq( radio, 80 * FREQ_MUL ); /* set to 80.0[MHz] (TOKYO FM) */
#if 0 /* Does it has no means ? */
/* set led to connect state */
rdpc101_set_led_state(radio, BLINK_GREEN_LED);
#endif
/* register video device */
retval = video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr);
if (retval) {
printk(KERN_WARNING DRIVER_NAME
": Could not register video device\n");
goto err_all;
}
usb_set_intfdata(intf, radio);
return 0;
err_all:
video_device_release(radio->videodev);
err_radio:
kfree(radio);
err_initial:
return retval;
}
/*
* rdpc101_usb_driver_suspend - suspend the device
*/
static int rdpc101_usb_driver_suspend(struct usb_interface *intf,
pm_message_t message)
{
printk(KERN_INFO DRIVER_NAME ": suspending now...\n");
return 0;
}
/*
* rdpc101_usb_driver_resume - resume the device
*/
static int rdpc101_usb_driver_resume(struct usb_interface *intf)
{
printk(KERN_INFO DRIVER_NAME ": resuming now...\n");
return 0;
}
/*
* rdpc101_usb_driver_disconnect - disconnect the device
*/
static void rdpc101_usb_driver_disconnect(struct usb_interface *intf)
{
struct rdpc101_device *radio = usb_get_intfdata(intf);
mutex_lock(&radio->disconnect_lock);
radio->disconnected = 1;
usb_set_intfdata(intf, NULL);
if (radio->users == 0) {
/* set led to disconnect state */
rdpc101_set_led_state(radio, BLINK_ORANGE_LED);
video_unregister_device(radio->videodev);
kfree(radio);
}
mutex_unlock(&radio->disconnect_lock);
}
/*
* rdpc101_usb_driver - usb driver interface
*/
static struct usb_driver rdpc101_usb_driver = {
.name = DRIVER_NAME,
.probe = rdpc101_usb_driver_probe,
.disconnect = rdpc101_usb_driver_disconnect,
.suspend = rdpc101_usb_driver_suspend,
.resume = rdpc101_usb_driver_resume,
.id_table = rdpc101_usb_driver_id_table,
.supports_autosuspend = 1,
};
/**************************************************************************
* Module Interface
**************************************************************************/
/*
* rdpc101_module_init - module init
*/
static int __init rdpc101_module_init(void)
{
printk(KERN_INFO DRIVER_DESC ", Version " DRIVER_VERSION "\n");
return usb_register(&rdpc101_usb_driver);
}
/*
* rdpc101_module_exit - module exit
*/
static void __exit rdpc101_module_exit(void)
{
usb_deregister(&rdpc101_usb_driver);
}
module_init(rdpc101_module_init);
module_exit(rdpc101_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR(DRIVER_AUTHOR);
MODULE_DESCRIPTION(DRIVER_DESC);
MODULE_VERSION(DRIVER_VERSION);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment