diff options
-rw-r--r-- | linux/drivers/media/radio/radio-si470x.c | 1034 |
1 files changed, 513 insertions, 521 deletions
diff --git a/linux/drivers/media/radio/radio-si470x.c b/linux/drivers/media/radio/radio-si470x.c index f34900cd4..c5a764b7b 100644 --- a/linux/drivers/media/radio/radio-si470x.c +++ b/linux/drivers/media/radio/radio-si470x.c @@ -5,7 +5,7 @@ * - Silicon Labs USB FM Radio Reference Design * - ADS/Tech FM Radio Receiver (formerly Instant FM Music) (RDX-155-EF) * - * Copyright (c) 2007 Tobias Lorenz <tobias.lorenz@gmx.net> + * Copyright (c) 2008 Tobias Lorenz <tobias.lorenz@gmx.net> * * 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 @@ -29,7 +29,7 @@ * Version 1.0.0 * - First working version * 2008-01-13 Tobias Lorenz <tobias.lorenz@gmx.net> - * Version 1.0.1 + * 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 @@ -47,21 +47,34 @@ * - 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) * * ToDo: - * - check USB Vendor/Product ID for ADS/Tech FM Radio Receiver - * (formerly Instant FM Music) (RDX-155-EF) is 06e1:a155 * - add seeking support * - add firmware download/update support - * - add possibility to switch off RDS * - RDS support: interrupt mode, instead of polling - * - add LED status output + * - add LED status output (check if that's not already done in firmware) */ + /* driver definitions */ #define DRIVER_AUTHOR "Tobias Lorenz <tobias.lorenz@gmx.net>" #define DRIVER_NAME "radio-si470x" -#define DRIVER_VERSION KERNEL_VERSION(1, 0, 2) +#define DRIVER_VERSION KERNEL_VERSION(1, 0, 4) #define DRIVER_CARD "Silicon Labs Si470x FM Radio Receiver" #define DRIVER_DESC "USB radio driver for Si470x FM Radio Receivers" @@ -81,6 +94,23 @@ #include <media/rds.h> +/* USB Device ID List */ +static 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) }, + /* 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, 0); @@ -136,6 +166,7 @@ MODULE_PARM_DESC(max_rds_errors, "RDS maximum block errors: *1*"); /* RDS poll frequency */ static int rds_poll_time = 40; /* 40 is used by the original USBRadio.exe */ +/* 50 is used by radio-cadet */ /* 75 should be okay */ /* 80 is the usual RDS receive interval */ module_param(rds_poll_time, int, 0); @@ -146,8 +177,8 @@ MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*"); /************************************************************************** * Register Definitions **************************************************************************/ -#define FMRADIO_REGISTER_SIZE 2 /* 16 register bit width */ -#define FMRADIO_REGISTER_NUM 16 /* DEVICEID ... RDSD */ +#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 */ @@ -237,23 +268,23 @@ MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*"); /************************************************************************** - * USB HID reports + * 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 (FMRADIO_REGISTER_SIZE + 1) +#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 (FMRADIO_REGISTER_NUM * FMRADIO_REGISTER_SIZE + 1) +#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 * FMRADIO_REGISTER_SIZE + 1) +#define RDS_REPORT_SIZE (RDS_REGISTER_NUM * RADIO_REGISTER_SIZE + 1) #define RDS_REPORT 18 /* Report 19: LED state */ @@ -282,12 +313,12 @@ MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*"); /************************************************************************** - * software/hardware versions + * Software/Hardware Versions **************************************************************************/ -#define FMRADIO_SW_VERSION_NOT_BOOTLOADABLE 6 -#define FMRADIO_SW_VERSION 7 -#define FMRADIO_SW_VERSION_CURRENT 15 -#define FMRADIO_HW_VERSION 1 +#define RADIO_SW_VERSION_NOT_BOOTLOADABLE 6 +#define RADIO_SW_VERSION 7 +#define RADIO_SW_VERSION_CURRENT 15 +#define RADIO_HW_VERSION 1 #define SCRATCH_PAGE_SW_VERSION 1 #define SCRATCH_PAGE_HW_VERSION 2 @@ -295,7 +326,7 @@ MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*"); /************************************************************************** - * LED State definitions + * LED State Definitions **************************************************************************/ #define LED_COMMAND 0x35 @@ -311,7 +342,7 @@ MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*"); /************************************************************************** - * Stream State definitions + * Stream State Definitions **************************************************************************/ #define STREAM_COMMAND 0x36 #define STREAM_VIDPID 0x00 @@ -320,16 +351,16 @@ MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*"); /************************************************************************** - * bootloader / flash commands + * Bootloader / Flash Commands **************************************************************************/ -/* Unique ID sent to bootloader and required to put into a bootload state */ +/* unique id sent to bootloader and required to put into a bootload state */ #define UNIQUE_BL_ID 0x34 -/* Mask for the flash data */ +/* mask for the flash data */ #define FLASH_DATA_MASK 0x55 -/* Bootloader commands */ +/* bootloader commands */ #define GET_SW_VERSION_COMMAND 0x00 #define SET_PAGE_COMMAND 0x01 #define ERASE_PAGE_COMMAND 0x02 @@ -340,12 +371,12 @@ MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*"); #define GET_HW_VERSION_COMMAND 0x07 #define BLANK 0xff -/* Bootloader command responses */ +/* bootloader command responses */ #define COMMAND_OK 0x01 #define COMMAND_FAILED 0x02 #define COMMAND_PENDING 0x03 -/* Buffer sizes */ +/* buffer sizes */ #define COMMAND_BUFFER_SIZE 4 #define RESPONSE_BUFFER_SIZE 2 #define FLASH_BUFFER_SIZE 64 @@ -354,10 +385,12 @@ MODULE_PARM_DESC(rds_poll_time, "RDS poll time (ms): *40*"); /************************************************************************** - * Driver private definitions + * General Driver Definitions **************************************************************************/ -/* private data */ +/* + * si470x_device - private data + */ struct si470x_device { /* reference to USB and video device */ struct usb_device *usbdev; @@ -370,193 +403,33 @@ struct si470x_device { unsigned char buf[64]; /* Silabs internal registers (0..15) */ - unsigned short registers[FMRADIO_REGISTER_NUM]; + unsigned short registers[RADIO_REGISTER_NUM]; /* RDS receive buffer */ struct work_struct work; + wait_queue_head_t read_queue; struct timer_list timer; - spinlock_t lock; - unsigned char *buffer; + spinlock_t lock; /* buffer locking */ + unsigned char *buffer; /* size is always multiple of three */ unsigned int buf_size; unsigned int rd_index; unsigned int wr_index; - unsigned int block_count; - unsigned char last_blocknum; - wait_queue_head_t read_queue; - int data_available_for_read; }; -/* register acccess functions */ -static int si470x_get_report(struct si470x_device *radio, int size); -static int si470x_set_report(struct si470x_device *radio, int size); -static int si470x_get_register(struct si470x_device *radio, int regnr); -static int si470x_set_register(struct si470x_device *radio, int regnr); -static int si470x_get_all_registers(struct si470x_device *radio); -static int si470x_get_rds_registers(struct si470x_device *radio); - -/* high-level functions */ -static int si470x_start(struct si470x_device *radio); -static int si470x_stop(struct si470x_device *radio); -static int si470x_set_chan(struct si470x_device *radio, int chan); -static int si470x_get_freq(struct si470x_device *radio); -static int si470x_set_freq(struct si470x_device *radio, int freq); - -/* RDS functions */ -static void si470x_rds(struct si470x_device *radio); -static void si470x_timer(unsigned long data); -static void si470x_work(struct work_struct *work); - - -/************************************************************************** - * USB interface definitions - **************************************************************************/ - -/* USB device ID list (Vendor, Product, Class, SubClass, Protocol) */ -static 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) }, - /* Terminating entry */ - { } -}; -MODULE_DEVICE_TABLE (usb, si470x_usb_driver_id_table); - -/* USB driver functions */ -static int si470x_usb_driver_probe(struct usb_interface *intf, - const struct usb_device_id *id); -static void si470x_usb_driver_disconnect(struct usb_interface *intf); - -/* USB driver interface */ -static struct usb_driver si470x_usb_driver = { - .name = DRIVER_NAME, - .probe = si470x_usb_driver_probe, - .disconnect = si470x_usb_driver_disconnect, - .id_table = si470x_usb_driver_id_table, -}; - - - -/************************************************************************** - * Video4Linux interface definitions - **************************************************************************/ - -/* 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 */ +/* + * 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) -/* File operations functions */ -static ssize_t si470x_fops_read(struct file *file, char __user *buf, - size_t count, loff_t *ppos); -static unsigned int si470x_fops_poll(struct file *file, - struct poll_table_struct *pts); -static int si470x_fops_open(struct inode *inode, struct file *file); -static int si470x_fops_release(struct inode *inode, struct file *file); - -/* File operations interface */ -static const struct file_operations si470x_fops = { - .owner = THIS_MODULE, - .llseek = no_llseek, - .read = si470x_fops_read, - .poll = si470x_fops_poll, - .ioctl = video_ioctl2, - .compat_ioctl = v4l_compat_ioctl32, - .open = si470x_fops_open, - .release = si470x_fops_release, -}; - -/* Video device functions */ -static int si470x_vidioc_querycap(struct file *file, void *priv, - struct v4l2_capability *capability); -static int si470x_vidioc_g_input(struct file *file, void *priv, - unsigned int *i); -static int si470x_vidioc_s_input(struct file *file, void *priv, - unsigned int i); -static int si470x_vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc); -static int si470x_vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl); -static int si470x_vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl); -static int si470x_vidioc_g_audio(struct file *file, void *priv, - struct v4l2_audio *audio); -static int si470x_vidioc_s_audio(struct file *file, void *priv, - struct v4l2_audio *audio); -static int si470x_vidioc_g_tuner(struct file *file, void *priv, - struct v4l2_tuner *tuner); -static int si470x_vidioc_s_tuner(struct file *file, void *priv, - struct v4l2_tuner *tuner); -static int si470x_vidioc_g_frequency(struct file *file, void *priv, - struct v4l2_frequency *freq); -static int si470x_vidioc_s_frequency(struct file *file, void *priv, - struct v4l2_frequency *freq); - -/* Video device interface */ -static struct video_device si470x_viddev_template = { - .fops = &si470x_fops, - .name = DRIVER_NAME, - .type = VID_TYPE_TUNER, - .release = video_device_release, - .vidioc_querycap = si470x_vidioc_querycap, - .vidioc_g_input = si470x_vidioc_g_input, - .vidioc_s_input = si470x_vidioc_s_input, - .vidioc_queryctrl = si470x_vidioc_queryctrl, - .vidioc_g_ctrl = si470x_vidioc_g_ctrl, - .vidioc_s_ctrl = si470x_vidioc_s_ctrl, - .vidioc_g_audio = si470x_vidioc_g_audio, - .vidioc_s_audio = si470x_vidioc_s_audio, - .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, - .owner = THIS_MODULE, -}; - -/* Query control */ -static struct v4l2_queryctrl radio_queryctrl[] = { -/* HINT: the disabled controls are only here to satify kradio and such apps */ - { - .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_BALANCE, - .flags = V4L2_CTRL_FLAG_DISABLED, - }, - { - .id = V4L2_CID_AUDIO_BASS, - .flags = V4L2_CTRL_FLAG_DISABLED, - }, - { - .id = V4L2_CID_AUDIO_TREBLE, - .flags = V4L2_CTRL_FLAG_DISABLED, - }, - { - .id = V4L2_CID_AUDIO_MUTE, - .type = V4L2_CTRL_TYPE_BOOLEAN, - .name = "Mute", - .minimum = 0, - .maximum = 1, - .step = 1, - .default_value = 1, - }, - { - .id = V4L2_CID_AUDIO_LOUDNESS, - .flags = V4L2_CTRL_FLAG_DISABLED, - }, -}; - /************************************************************************** - * Driver private functions + * General Driver Functions **************************************************************************/ /* @@ -634,10 +507,10 @@ static int si470x_get_all_registers(struct si470x_device *radio) retval = si470x_get_report(radio, ENTIRE_REPORT_SIZE); if (retval >= 0) - for (regnr = 0; regnr < FMRADIO_REGISTER_NUM; regnr++) + for (regnr = 0; regnr < RADIO_REGISTER_NUM; regnr++) radio->registers[regnr] = - (radio->buf[regnr * FMRADIO_REGISTER_SIZE + 1] << 8) | - radio->buf[regnr * FMRADIO_REGISTER_SIZE + 2]; + (radio->buf[regnr * RADIO_REGISTER_SIZE + 1] << 8) | + radio->buf[regnr * RADIO_REGISTER_SIZE + 2]; return (retval < 0) ? -EINVAL : 0; } @@ -658,65 +531,13 @@ static int si470x_get_rds_registers(struct si470x_device *radio) usb_rcvctrlpipe(radio->usbdev, 1), radio->buf, RDS_REPORT_SIZE, &size, usb_timeout); - if (retval >= 0) { - for (regnr = 0; regnr < RDS_REGISTER_NUM; regnr++) { + if (retval >= 0) + for (regnr = 0; regnr < RDS_REGISTER_NUM; regnr++) radio->registers[STATUSRSSI + regnr] = - (radio->buf[regnr * FMRADIO_REGISTER_SIZE + 1] << 8) | - radio->buf[regnr * FMRADIO_REGISTER_SIZE + 2]; - } - } + (radio->buf[regnr * RADIO_REGISTER_SIZE + 1] << 8) | + radio->buf[regnr * RADIO_REGISTER_SIZE + 2]; - return (retval < 0) ? - EINVAL : 0; -} - - -/* - * si470x_start - switch on radio - */ -static int si470x_start(struct si470x_device *radio) -{ - int retval; - - /* powercfg */ - radio->registers[POWERCFG] = - POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM; - retval = si470x_set_register(radio, POWERCFG); - if (retval < 0) - return retval; - - /* sysconfig 1 */ - radio->registers[SYSCONFIG1] = - SYSCONFIG1_DE | SYSCONFIG1_RDS; - retval = si470x_set_register(radio, SYSCONFIG1); - if (retval < 0) - return retval; - - /* sysconfig 2 */ - radio->registers[SYSCONFIG2] = - (0x3f << 8) | /* SEEKTH */ - (band << 6) | /* BAND */ - (space << 4) | /* SPACE */ - 15; /* VOLUME (max) */ - retval = si470x_set_register(radio, SYSCONFIG2); - if (retval < 0) - return retval; - - /* reset last channel */ - return si470x_set_chan(radio, - radio->registers[CHANNEL] & CHANNEL_CHAN); -} - - -/* - * si470x_stop - switch off radio - */ -static int si470x_stop(struct si470x_device *radio) -{ - /* powercfg */ - radio->registers[POWERCFG] &= ~POWERCFG_DMUTE; - /* POWERCFG_ENABLE has to automatically go low */ - radio->registers[POWERCFG] |= POWERCFG_ENABLE | POWERCFG_DISABLE; - return si470x_set_register(radio, POWERCFG); + return (retval < 0) ? -EINVAL : 0; } @@ -762,22 +583,22 @@ static int si470x_get_freq(struct si470x_device *radio) /* Spacing (kHz) */ switch (space) { - /* 0: 200 kHz (USA, Australia) */ - default: spacing = 0.200 * FREQ_MUL; break; - /* 1: 100 kHz (Europe, Japan) */ - case 1 : spacing = 0.100 * FREQ_MUL; break; - /* 2: 50 kHz */ - case 2 : spacing = 0.050 * FREQ_MUL; break; + /* 0: 200 kHz (USA, Australia) */ + case 0 : spacing = 0.200 * FREQ_MUL; break; + /* 1: 100 kHz (Europe, Japan) */ + case 1 : spacing = 0.100 * FREQ_MUL; break; + /* 2: 50 kHz */ + default: spacing = 0.050 * FREQ_MUL; break; }; /* Bottom of Band (MHz) */ switch (band) { - /* 0: 87.5 - 108 MHz (USA, Europe) */ - default: band_bottom = 87.5 * FREQ_MUL; break; - /* 1: 76 - 108 MHz (Japan wide band) */ - case 1 : band_bottom = 76 * FREQ_MUL; break; - /* 2: 76 - 90 MHz (Japan) */ - case 2 : band_bottom = 76 * FREQ_MUL; break; + /* 0: 87.5 - 108 MHz (USA, Europe) */ + case 0 : band_bottom = 87.5 * FREQ_MUL; break; + /* 1: 76 - 108 MHz (Japan wide band) */ + default: band_bottom = 76 * FREQ_MUL; break; + /* 2: 76 - 90 MHz (Japan) */ + case 2 : band_bottom = 76 * FREQ_MUL; break; }; /* read channel */ @@ -802,22 +623,22 @@ static int si470x_set_freq(struct si470x_device *radio, int freq) /* Spacing (kHz) */ switch (space) { - /* 0: 200 kHz (USA, Australia) */ - default: spacing = 0.200 * FREQ_MUL; break; - /* 1: 100 kHz (Europe, Japan) */ - case 1 : spacing = 0.100 * FREQ_MUL; break; - /* 2: 50 kHz */ - case 2 : spacing = 0.050 * FREQ_MUL; break; + /* 0: 200 kHz (USA, Australia) */ + case 0 : spacing = 0.200 * FREQ_MUL; break; + /* 1: 100 kHz (Europe, Japan) */ + case 1 : spacing = 0.100 * FREQ_MUL; break; + /* 2: 50 kHz */ + default: spacing = 0.050 * FREQ_MUL; break; }; /* Bottom of Band (MHz) */ switch (band) { - /* 0: 87.5 - 108 MHz (USA, Europe) */ - default: band_bottom = 87.5 * FREQ_MUL; break; - /* 1: 76 - 108 MHz (Japan wide band) */ - case 1 : band_bottom = 76 * FREQ_MUL; break; - /* 2: 76 - 90 MHz (Japan) */ - case 2 : band_bottom = 76 * FREQ_MUL; break; + /* 0: 87.5 - 108 MHz (USA, Europe) */ + case 0 : band_bottom = 87.5 * FREQ_MUL; break; + /* 1: 76 - 108 MHz (Japan wide band) */ + default: band_bottom = 76 * FREQ_MUL; break; + /* 2: 76 - 90 MHz (Japan) */ + case 2 : band_bottom = 76 * FREQ_MUL; break; }; /* Chan = [ Freq (Mhz) - Bottom of Band (MHz) ] / Spacing (kHz) */ @@ -827,21 +648,91 @@ static int si470x_set_freq(struct si470x_device *radio, int freq) } +/* + * si470x_start - switch on radio + */ +static int si470x_start(struct si470x_device *radio) +{ + int retval; + + /* powercfg */ + radio->registers[POWERCFG] = + POWERCFG_DMUTE | POWERCFG_ENABLE | POWERCFG_RDSM; + retval = si470x_set_register(radio, POWERCFG); + if (retval < 0) + return retval; + + /* sysconfig 1 */ + radio->registers[SYSCONFIG1] = SYSCONFIG1_DE; + retval = si470x_set_register(radio, SYSCONFIG1); + if (retval < 0) + return retval; + + /* sysconfig 2 */ + radio->registers[SYSCONFIG2] = + (0x3f << 8) | /* SEEKTH */ + (band << 6) | /* BAND */ + (space << 4) | /* SPACE */ + 15; /* VOLUME (max) */ + retval = si470x_set_register(radio, SYSCONFIG2); + if (retval < 0) + return retval; + + /* reset last channel */ + return si470x_set_chan(radio, + radio->registers[CHANNEL] & CHANNEL_CHAN); +} + + +/* + * si470x_stop - switch off radio + */ +static int si470x_stop(struct si470x_device *radio) +{ + int retval; + + /* sysconfig 1 */ + radio->registers[SYSCONFIG1] &= ~SYSCONFIG1_RDS; + retval = si470x_set_register(radio, SYSCONFIG1); + if (retval < 0) + return retval; + + /* powercfg */ + radio->registers[POWERCFG] &= ~POWERCFG_DMUTE; + /* POWERCFG_ENABLE has to automatically go low */ + radio->registers[POWERCFG] |= POWERCFG_ENABLE | POWERCFG_DISABLE; + return si470x_set_register(radio, POWERCFG); +} + + +/* + * si470x_rds_on - switch on rds reception + */ +static int si470x_rds_on(struct si470x_device *radio) +{ + /* sysconfig 1 */ + radio->registers[SYSCONFIG1] |= SYSCONFIG1_RDS; + return si470x_set_register(radio, SYSCONFIG1); +} + + + +/************************************************************************** + * RDS Driver Functions + **************************************************************************/ /* * si470x_rds - rds processing function */ static void si470x_rds(struct si470x_device *radio) { - unsigned long flags; unsigned char tmpbuf[3]; unsigned char blocknum; - unsigned char bler; /* RDS block errors */ + unsigned char bler; /* rds block errors */ unsigned short rds; unsigned int i; - if (radio->users == 0) - return; + /* get rds blocks */ if (si470x_get_rds_registers(radio) < 0) return; if ((radio->registers[STATUSRSSI] & STATUSRSSI_RDSR) == 0) { @@ -853,8 +744,11 @@ static void si470x_rds(struct si470x_device *radio) return; } - for (blocknum = 0; blocknum < 4; blocknum++) { - switch (blocknum) { + /* copy four RDS blocks to internal buffer */ + if (spin_trylock(&radio->lock)) { + /* process each rds block */ + for (blocknum = 0; blocknum < 4; blocknum++) { + switch (blocknum) { default: bler = (radio->registers[STATUSRSSI] & STATUSRSSI_BLERA) >> 9; @@ -875,41 +769,42 @@ static void si470x_rds(struct si470x_device *radio) READCHAN_BLERD) >> 10; rds = radio->registers[RDSD]; break; - }; - - /* Fill the V4L2 RDS buffer */ - tmpbuf[0] = rds & 0x00ff; /* LSB */ - tmpbuf[1] = (rds & 0xff00) >> 8;/* MSB */ - 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) */ - - spin_lock_irqsave(&radio->lock, flags); - - /* copy RDS block to internal buffer */ - for (i = 0; i < 3; i++) { - radio->buffer[radio->wr_index] = tmpbuf[i]; - radio->wr_index++; + }; + + /* Fill the V4L2 RDS buffer */ + tmpbuf[0] = rds & 0x00ff; /* LSB */ + tmpbuf[1] = (rds & 0xff00) >> 8;/* MSB */ + 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 */ + for (i = 0; i < 3; i++) { + radio->buffer[radio->wr_index] = tmpbuf[i]; + radio->wr_index++; + } + + /* 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->buf_size) - radio->wr_index = 0; - - if (radio->wr_index == radio->rd_index) { - radio->rd_index += 3; - if (radio->rd_index >= radio->buf_size) - radio->rd_index = 0; - } else - radio->block_count++; - - spin_unlock_irqrestore(&radio->lock, flags); + spin_unlock(&radio->lock); } - radio->data_available_for_read = 1; - wake_up_interruptible(&radio->read_queue); + /* wake up read queue */ + if (radio->wr_index != radio->rd_index) + wake_up_interruptible(&radio->read_queue); } @@ -925,14 +820,14 @@ static void si470x_timer(unsigned long data) /* - * si470x_timer - rds work function + * si470x_work - rds work function */ static void si470x_work(struct work_struct *work) { struct si470x_device *radio = container_of(work, struct si470x_device, work); - if (radio->users == 0) + if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) return; si470x_rds(radio); @@ -942,107 +837,7 @@ static void si470x_work(struct work_struct *work) /************************************************************************** - * USB interface functions - **************************************************************************/ - -/* - * 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; - - if (!(radio = kmalloc(sizeof(struct si470x_device), GFP_KERNEL))) - return -ENOMEM; - if (!(radio->videodev = video_device_alloc())) { - kfree(radio); - return -ENOMEM; - } - memcpy(radio->videodev, &si470x_viddev_template, - sizeof(si470x_viddev_template)); - radio->users = 0; - radio->usbdev = interface_to_usbdev(intf); - video_set_drvdata(radio->videodev, radio); - if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr)) { - printk(KERN_WARNING DRIVER_NAME - ": Could not register video device\n"); - video_device_release(radio->videodev); - kfree(radio); - return -EIO; - } - usb_set_intfdata(intf, radio); - - /* show some infos about the specific device */ - if (si470x_get_all_registers(radio) < 0) { - video_device_release(radio->videodev); - kfree(radio); - return -EIO; - } - printk(KERN_INFO DRIVER_NAME ": DeviceID=0x%4.4x ChipID=0x%4.4x\n", - radio->registers[DEVICEID], radio->registers[CHIPID]); - - /* check if firmware is current */ - if ((radio->registers[CHIPID] & CHIPID_FIRMWARE) - < FMRADIO_SW_VERSION_CURRENT) { - printk(KERN_WARNING DRIVER_NAME - ": This driver is known to work with chip version %d, " - "but the device has firmware %d. If you have some " - "trouble using this driver, please report to V4L ML " - "at video4linux-list@redhat.com\n", - radio->registers[CHIPID] & CHIPID_FIRMWARE, - FMRADIO_SW_VERSION_CURRENT); - } - - /* set initial frequency */ - si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */ - - /* rds initialization */ - radio->buf_size = rds_buf * 3; - if (NULL == (radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL))) { - video_device_release(radio->videodev); - kfree(radio); - return -ENOMEM; - } - radio->block_count = 0; - radio->wr_index = 0; - radio->rd_index = 0; - radio->last_blocknum = 0xff; - init_waitqueue_head(&radio->read_queue); - radio->data_available_for_read = 0; - - /* prepare polling via eventd */ - INIT_WORK(&radio->work, si470x_work); - init_timer(&radio->timer); - radio->timer.function = si470x_timer; - radio->timer.data = (unsigned long) radio; - - return 0; -} - - -/* - * 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); - - del_timer_sync(&radio->timer); - flush_scheduled_work(); - - usb_set_intfdata(intf, NULL); - if (radio) { - video_unregister_device(radio->videodev); - kfree(radio->buffer); - kfree(radio); - } -} - - - -/************************************************************************** - * Video4Linux interface functions + * File Operations Interface **************************************************************************/ /* @@ -1052,53 +847,52 @@ static ssize_t si470x_fops_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) { struct si470x_device *radio = video_get_drvdata(video_devdata(file)); - struct rds_command cmd; - unsigned long flags; - unsigned int i; - unsigned int rd_blocks; - - cmd.block_count = count / 3; /* each RDS block needs 3 bytes */ - cmd.result = 0; - cmd.buffer = buf; - cmd.instance = file; - - /* copy RDS block out of internal buffer */ - while (!radio->data_available_for_read) { - if (wait_event_interruptible(radio->read_queue, - radio->data_available_for_read) < 0) - return -EINTR; - } + int retval = 0; + unsigned int block_count = 0; - spin_lock_irqsave(&radio->lock, flags); - rd_blocks = cmd.block_count; - if (rd_blocks > radio->block_count) - rd_blocks = radio->block_count; + /* switch on rds reception */ + if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) { + si470x_rds_on(radio); + schedule_work(&radio->work); + } - if (!rd_blocks) { - spin_unlock_irqrestore(&radio->lock, flags); - return cmd.result; + /* block if no new data available */ + while (radio->wr_index == radio->rd_index) { + if (file->f_flags & O_NONBLOCK) + return -EWOULDBLOCK; + interruptible_sleep_on(&radio->read_queue); } - for (i = 0; i < rd_blocks; i++) { - /* copy RDS block to user buffer */ - if (radio->rd_index == radio->wr_index) - break; + /* calculate block count from byte count */ + count /= 3; - if (copy_to_user(buf, &radio->buffer[radio->rd_index], 3)) - break; + /* copy RDS block out of internal buffer and to user buffer */ + if (spin_trylock(&radio->lock)) { + while (block_count < count) { + if (radio->rd_index == radio->wr_index) + break; - radio->rd_index += 3; - if (radio->rd_index >= radio->buf_size) - radio->rd_index = 0; - radio->block_count--; + /* 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; - buf += 3; - cmd.result += 3; + /* increment counters */ + block_count++; + buf += 3; + retval += 3; + } + + spin_unlock(&radio->lock); } - radio->data_available_for_read = (radio->block_count > 0); - spin_unlock_irqrestore(&radio->lock, flags); - return cmd.result; + return retval; } @@ -1109,14 +903,19 @@ static unsigned int si470x_fops_poll(struct file *file, struct poll_table_struct *pts) { struct si470x_device *radio = video_get_drvdata(video_devdata(file)); - int retval; - retval = 0; - if (radio->data_available_for_read) - retval = POLLIN | POLLRDNORM; + /* switch on rds reception */ + if ((radio->registers[SYSCONFIG1] & SYSCONFIG1_RDS) == 0) { + si470x_rds_on(radio); + schedule_work(&radio->work); + } + poll_wait(file, &radio->read_queue, pts); - return retval; + if (radio->rd_index != radio->wr_index) + return POLLIN | POLLRDNORM; + + return 0; } @@ -1125,17 +924,11 @@ static unsigned int si470x_fops_poll(struct file *file, */ static int si470x_fops_open(struct inode *inode, struct file *file) { - int retval; struct si470x_device *radio = video_get_drvdata(video_devdata(file)); radio->users++; - if (radio->users == 1) { - retval = si470x_start(radio); - if (retval < 0) - return retval; - - schedule_work(&radio->work); - } + if (radio->users == 1) + return si470x_start(radio); return 0; } @@ -1146,7 +939,6 @@ static int si470x_fops_open(struct inode *inode, struct file *file) */ static int si470x_fops_release(struct inode *inode, struct file *file) { - int retval; struct si470x_device *radio = video_get_drvdata(video_devdata(file)); if (!radio) @@ -1154,12 +946,14 @@ static int si470x_fops_release(struct inode *inode, struct file *file) radio->users--; if (radio->users == 0) { - radio->data_available_for_read = 1; /* ? */ - wake_up_interruptible(&radio->read_queue); /* ? */ + /* stop rds reception */ + del_timer_sync(&radio->timer); + flush_scheduled_work(); - retval = si470x_stop(radio); - if (retval < 0) - return retval; + /* cancel read processes */ + wake_up_interruptible(&radio->read_queue); + + return si470x_stop(radio); } return 0; @@ -1167,6 +961,68 @@ static int si470x_fops_release(struct inode *inode, struct file *file) /* + * si470x_fops - file operations interface + */ +static const struct file_operations si470x_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .read = si470x_fops_read, + .poll = si470x_fops_poll, + .ioctl = video_ioctl2, + .compat_ioctl = v4l_compat_ioctl32, + .open = si470x_fops_open, + .release = si470x_fops_release, +}; + + + +/************************************************************************** + * Video4Linux Interface + **************************************************************************/ + +/* + * si470x_v4l2_queryctrl - query control + */ +static struct v4l2_queryctrl si470x_v4l2_queryctrl[] = { +/* HINT: the disabled controls are only here to satify kradio and such apps */ + { + .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_BALANCE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BASS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_TREBLE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_MUTE, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Mute", + .minimum = 0, + .maximum = 1, + .step = 1, + .default_value = 1, + }, + { + .id = V4L2_CID_AUDIO_LOUDNESS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, +}; + + +/* * si470x_vidioc_querycap - query device capabilities */ static int si470x_vidioc_querycap(struct file *file, void *priv, @@ -1214,9 +1070,9 @@ static int si470x_vidioc_queryctrl(struct file *file, void *priv, { int i; - for (i = 0; i < ARRAY_SIZE(radio_queryctrl); i++) { - if (qc->id && qc->id == radio_queryctrl[i].id) { - memcpy(qc, &(radio_queryctrl[i]), sizeof(*qc)); + for (i = 0; i < ARRAY_SIZE(si470x_v4l2_queryctrl); i++) { + if (qc->id && qc->id == si470x_v4l2_queryctrl[i].id) { + memcpy(qc, &(si470x_v4l2_queryctrl[i]), sizeof(*qc)); return 0; } } @@ -1234,14 +1090,14 @@ static int si470x_vidioc_g_ctrl(struct file *file, void *priv, struct si470x_device *radio = video_get_drvdata(video_devdata(file)); switch (ctrl->id) { - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = radio->registers[SYSCONFIG2] & - SYSCONFIG2_VOLUME; - break; - case V4L2_CID_AUDIO_MUTE: - ctrl->value = ((radio->registers[POWERCFG] & - POWERCFG_DMUTE) == 0) ? 1 : 0; - break; + case V4L2_CID_AUDIO_VOLUME: + ctrl->value = radio->registers[SYSCONFIG2] & + SYSCONFIG2_VOLUME; + break; + case V4L2_CID_AUDIO_MUTE: + ctrl->value = ((radio->registers[POWERCFG] & + POWERCFG_DMUTE) == 0) ? 1 : 0; + break; } return 0; @@ -1257,16 +1113,16 @@ static int si470x_vidioc_s_ctrl(struct file *file, void *priv, struct si470x_device *radio = video_get_drvdata(video_devdata(file)); switch (ctrl->id) { - case V4L2_CID_AUDIO_VOLUME: - radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME; - radio->registers[SYSCONFIG2] |= ctrl->value; - return si470x_set_register(radio, SYSCONFIG2); - case V4L2_CID_AUDIO_MUTE: - if (ctrl->value == 1) - radio->registers[POWERCFG] &= ~POWERCFG_DMUTE; - else - radio->registers[POWERCFG] |= POWERCFG_DMUTE; - return si470x_set_register(radio, POWERCFG); + case V4L2_CID_AUDIO_VOLUME: + radio->registers[SYSCONFIG2] &= ~SYSCONFIG2_VOLUME; + radio->registers[SYSCONFIG2] |= ctrl->value; + return si470x_set_register(radio, SYSCONFIG2); + case V4L2_CID_AUDIO_MUTE: + if (ctrl->value == 1) + radio->registers[POWERCFG] &= ~POWERCFG_DMUTE; + else + radio->registers[POWERCFG] |= POWERCFG_DMUTE; + return si470x_set_register(radio, POWERCFG); } return -EINVAL; @@ -1321,22 +1177,22 @@ static int si470x_vidioc_g_tuner(struct file *file, void *priv, strcpy(tuner->name, "FM"); tuner->type = V4L2_TUNER_RADIO; - switch(band) { - /* 0: 87.5 - 108 MHz (USA, Europe, default) */ - default: - tuner->rangelow = 87.5 * FREQ_MUL; - tuner->rangehigh = 108 * FREQ_MUL; - break; - /* 1: 76 - 108 MHz (Japan wide band) */ - case 1 : - tuner->rangelow = 76 * FREQ_MUL; - tuner->rangehigh = 108 * FREQ_MUL; - break; - /* 2: 76 - 90 MHz (Japan) */ - case 2 : - tuner->rangelow = 76 * FREQ_MUL; - tuner->rangehigh = 90 * FREQ_MUL; - break; + switch (band) { + /* 0: 87.5 - 108 MHz (USA, Europe, default) */ + default: + tuner->rangelow = 87.5 * FREQ_MUL; + tuner->rangehigh = 108 * FREQ_MUL; + break; + /* 1: 76 - 108 MHz (Japan wide band) */ + case 1 : + tuner->rangelow = 76 * FREQ_MUL; + tuner->rangehigh = 108 * FREQ_MUL; + break; + /* 2: 76 - 90 MHz (Japan) */ + case 2 : + tuner->rangelow = 76 * FREQ_MUL; + tuner->rangehigh = 90 * FREQ_MUL; + break; }; tuner->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; tuner->capability = V4L2_TUNER_CAP_LOW; @@ -1408,9 +1264,145 @@ static int si470x_vidioc_s_frequency(struct file *file, void *priv, } +/* + * si470x_viddev_tamples - video device interface + */ +static struct video_device si470x_viddev_template = { + .fops = &si470x_fops, + .name = DRIVER_NAME, + .type = VID_TYPE_TUNER, + .release = video_device_release, + .vidioc_querycap = si470x_vidioc_querycap, + .vidioc_g_input = si470x_vidioc_g_input, + .vidioc_s_input = si470x_vidioc_s_input, + .vidioc_queryctrl = si470x_vidioc_queryctrl, + .vidioc_g_ctrl = si470x_vidioc_g_ctrl, + .vidioc_s_ctrl = si470x_vidioc_s_ctrl, + .vidioc_g_audio = si470x_vidioc_g_audio, + .vidioc_s_audio = si470x_vidioc_s_audio, + .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, + .owner = THIS_MODULE, +}; + + + +/************************************************************************** + * 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; + + /* memory and interface allocations */ + radio = kmalloc(sizeof(struct si470x_device), GFP_KERNEL); + if (!radio) + return -ENOMEM; + radio->videodev = video_device_alloc(); + if (!radio->videodev) { + kfree(radio); + return -ENOMEM; + } + memcpy(radio->videodev, &si470x_viddev_template, + sizeof(si470x_viddev_template)); + radio->users = 0; + radio->usbdev = interface_to_usbdev(intf); + video_set_drvdata(radio->videodev, radio); + if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr)) { + printk(KERN_WARNING DRIVER_NAME + ": Could not register video device\n"); + video_device_release(radio->videodev); + kfree(radio); + return -EIO; + } + usb_set_intfdata(intf, radio); + + /* show some infos about the specific device */ + if (si470x_get_all_registers(radio) < 0) { + video_device_release(radio->videodev); + kfree(radio); + return -EIO; + } + printk(KERN_INFO DRIVER_NAME ": DeviceID=0x%4.4x ChipID=0x%4.4x\n", + radio->registers[DEVICEID], radio->registers[CHIPID]); + + /* check if firmware is current */ + if ((radio->registers[CHIPID] & CHIPID_FIRMWARE) + < RADIO_SW_VERSION_CURRENT) + printk(KERN_WARNING DRIVER_NAME + ": This driver is known to work with chip version %d, " + "but the device has firmware %d.\n" + DRIVER_NAME + "If you have some trouble using this driver, please " + "report to V4L ML at video4linux-list@redhat.com\n", + radio->registers[CHIPID] & CHIPID_FIRMWARE, + RADIO_SW_VERSION_CURRENT); + + /* set initial frequency */ + si470x_set_freq(radio, 87.5 * FREQ_MUL); /* available in all regions */ + + /* rds initialization */ + radio->buf_size = rds_buf * 3; + radio->buffer = kmalloc(radio->buf_size, GFP_KERNEL); + if (!radio->buffer) { + video_device_release(radio->videodev); + kfree(radio); + return -ENOMEM; + } + radio->wr_index = 0; + radio->rd_index = 0; + init_waitqueue_head(&radio->read_queue); + + /* prepare polling via eventd */ + INIT_WORK(&radio->work, si470x_work); + init_timer(&radio->timer); + radio->timer.function = si470x_timer; + radio->timer.data = (unsigned long) radio; + + return 0; +} + + +/* + * 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); + + del_timer_sync(&radio->timer); + flush_scheduled_work(); + + usb_set_intfdata(intf, NULL); + if (radio) { + video_unregister_device(radio->videodev); + kfree(radio->buffer); + kfree(radio); + } +} + + +/* + * si470x_usb_driver - usb driver interface + */ +static struct usb_driver si470x_usb_driver = { + .name = DRIVER_NAME, + .probe = si470x_usb_driver_probe, + .disconnect = si470x_usb_driver_disconnect, + .id_table = si470x_usb_driver_id_table, +}; + + /************************************************************************** - * Module interface definitions and functions + * Module Interface **************************************************************************/ /* @@ -1438,4 +1430,4 @@ module_exit(si470x_module_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_VERSION("1.0.2"); +MODULE_VERSION("1.0.4"); |