12aa72f3bSAlexey Klimov /* 22aa72f3bSAlexey Klimov * A driver for the AverMedia MR 800 USB FM radio. This device plugs 32aa72f3bSAlexey Klimov * into both the USB and an analog audio input, so this thing 42aa72f3bSAlexey Klimov * only deals with initialization and frequency setting, the 52aa72f3bSAlexey Klimov * audio data has to be handled by a sound driver. 62aa72f3bSAlexey Klimov * 72aa72f3bSAlexey Klimov * Copyright (c) 2008 Alexey Klimov <klimov.linux@gmail.com> 82aa72f3bSAlexey Klimov * 92aa72f3bSAlexey Klimov * This program is free software; you can redistribute it and/or modify 102aa72f3bSAlexey Klimov * it under the terms of the GNU General Public License as published by 112aa72f3bSAlexey Klimov * the Free Software Foundation; either version 2 of the License, or 122aa72f3bSAlexey Klimov * (at your option) any later version. 132aa72f3bSAlexey Klimov * 142aa72f3bSAlexey Klimov * This program is distributed in the hope that it will be useful, 152aa72f3bSAlexey Klimov * but WITHOUT ANY WARRANTY; without even the implied warranty of 162aa72f3bSAlexey Klimov * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 172aa72f3bSAlexey Klimov * GNU General Public License for more details. 182aa72f3bSAlexey Klimov * 192aa72f3bSAlexey Klimov * You should have received a copy of the GNU General Public License 202aa72f3bSAlexey Klimov * along with this program; if not, write to the Free Software 212aa72f3bSAlexey Klimov * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 222aa72f3bSAlexey Klimov */ 232aa72f3bSAlexey Klimov 242aa72f3bSAlexey Klimov /* 252aa72f3bSAlexey Klimov * Big thanks to authors of dsbr100.c and radio-si470x.c 262aa72f3bSAlexey Klimov * 272aa72f3bSAlexey Klimov * When work was looked pretty good, i discover this: 282aa72f3bSAlexey Klimov * http://av-usbradio.sourceforge.net/index.php 292aa72f3bSAlexey Klimov * http://sourceforge.net/projects/av-usbradio/ 302aa72f3bSAlexey Klimov * Latest release of theirs project was in 2005. 312aa72f3bSAlexey Klimov * Probably, this driver could be improved trough using their 322aa72f3bSAlexey Klimov * achievements (specifications given). 332aa72f3bSAlexey Klimov * So, we have smth to begin with. 342aa72f3bSAlexey Klimov * 352aa72f3bSAlexey Klimov * History: 362aa72f3bSAlexey Klimov * Version 0.01: First working version. 372aa72f3bSAlexey Klimov * It's required to blacklist AverMedia USB Radio 382aa72f3bSAlexey Klimov * in usbhid/hid-quirks.c 392aa72f3bSAlexey Klimov * 402aa72f3bSAlexey Klimov * Many things to do: 412aa72f3bSAlexey Klimov * - Correct power managment of device (suspend & resume) 422aa72f3bSAlexey Klimov * - Make x86 independance (little-endian and big-endian stuff) 432aa72f3bSAlexey Klimov * - Add code for scanning and smooth tuning 442aa72f3bSAlexey Klimov * - Checked and add stereo&mono stuff 452aa72f3bSAlexey Klimov * - Add code for sensitivity value 462aa72f3bSAlexey Klimov * - Correct mistakes 472aa72f3bSAlexey Klimov * - In Japan another FREQ_MIN and FREQ_MAX 482aa72f3bSAlexey Klimov */ 492aa72f3bSAlexey Klimov 502aa72f3bSAlexey Klimov /* kernel includes */ 512aa72f3bSAlexey Klimov #include <linux/kernel.h> 522aa72f3bSAlexey Klimov #include <linux/module.h> 532aa72f3bSAlexey Klimov #include <linux/init.h> 542aa72f3bSAlexey Klimov #include <linux/slab.h> 552aa72f3bSAlexey Klimov #include <linux/input.h> 562aa72f3bSAlexey Klimov #include <linux/videodev2.h> 572aa72f3bSAlexey Klimov #include <media/v4l2-common.h> 582aa72f3bSAlexey Klimov #include <media/v4l2-ioctl.h> 592aa72f3bSAlexey Klimov #include <linux/usb.h> 602aa72f3bSAlexey Klimov #include <linux/version.h> /* for KERNEL_VERSION MACRO */ 612aa72f3bSAlexey Klimov 622aa72f3bSAlexey Klimov /* driver and module definitions */ 632aa72f3bSAlexey Klimov #define DRIVER_AUTHOR "Alexey Klimov <klimov.linux@gmail.com>" 642aa72f3bSAlexey Klimov #define DRIVER_DESC "AverMedia MR 800 USB FM radio driver" 652aa72f3bSAlexey Klimov #define DRIVER_VERSION "0.01" 662aa72f3bSAlexey Klimov #define RADIO_VERSION KERNEL_VERSION(0, 0, 1) 672aa72f3bSAlexey Klimov 682aa72f3bSAlexey Klimov MODULE_AUTHOR(DRIVER_AUTHOR); 692aa72f3bSAlexey Klimov MODULE_DESCRIPTION(DRIVER_DESC); 702aa72f3bSAlexey Klimov MODULE_LICENSE("GPL"); 712aa72f3bSAlexey Klimov 722aa72f3bSAlexey Klimov #define USB_AMRADIO_VENDOR 0x07ca 732aa72f3bSAlexey Klimov #define USB_AMRADIO_PRODUCT 0xb800 742aa72f3bSAlexey Klimov 75e60b022eSAlexey Klimov /* dev_warn macro with driver name */ 76e60b022eSAlexey Klimov #define MR800_DRIVER_NAME "radio-mr800" 77e60b022eSAlexey Klimov #define amradio_dev_warn(dev, fmt, arg...) \ 78e60b022eSAlexey Klimov dev_warn(dev, MR800_DRIVER_NAME " - " fmt, ##arg) 79e60b022eSAlexey Klimov 802aa72f3bSAlexey Klimov /* Probably USB_TIMEOUT should be modified in module parameter */ 812aa72f3bSAlexey Klimov #define BUFFER_LENGTH 8 822aa72f3bSAlexey Klimov #define USB_TIMEOUT 500 832aa72f3bSAlexey Klimov 842aa72f3bSAlexey Klimov /* Frequency limits in MHz -- these are European values. For Japanese 852aa72f3bSAlexey Klimov devices, that would be 76 and 91. */ 862aa72f3bSAlexey Klimov #define FREQ_MIN 87.5 872aa72f3bSAlexey Klimov #define FREQ_MAX 108.0 882aa72f3bSAlexey Klimov #define FREQ_MUL 16000 892aa72f3bSAlexey Klimov 902aa72f3bSAlexey Klimov /* module parameter */ 912aa72f3bSAlexey Klimov static int radio_nr = -1; 922aa72f3bSAlexey Klimov module_param(radio_nr, int, 0); 932aa72f3bSAlexey Klimov MODULE_PARM_DESC(radio_nr, "Radio Nr"); 942aa72f3bSAlexey Klimov 952aa72f3bSAlexey Klimov static struct v4l2_queryctrl radio_qctrl[] = { 962aa72f3bSAlexey Klimov { 972aa72f3bSAlexey Klimov .id = V4L2_CID_AUDIO_MUTE, 982aa72f3bSAlexey Klimov .name = "Mute", 992aa72f3bSAlexey Klimov .minimum = 0, 1002aa72f3bSAlexey Klimov .maximum = 1, 1012aa72f3bSAlexey Klimov .step = 1, 1022aa72f3bSAlexey Klimov .default_value = 1, 1032aa72f3bSAlexey Klimov .type = V4L2_CTRL_TYPE_BOOLEAN, 1042aa72f3bSAlexey Klimov }, 1052aa72f3bSAlexey Klimov /* HINT: the disabled controls are only here to satify kradio and such apps */ 1062aa72f3bSAlexey Klimov { .id = V4L2_CID_AUDIO_VOLUME, 1072aa72f3bSAlexey Klimov .flags = V4L2_CTRL_FLAG_DISABLED, 1082aa72f3bSAlexey Klimov }, 1092aa72f3bSAlexey Klimov { 1102aa72f3bSAlexey Klimov .id = V4L2_CID_AUDIO_BALANCE, 1112aa72f3bSAlexey Klimov .flags = V4L2_CTRL_FLAG_DISABLED, 1122aa72f3bSAlexey Klimov }, 1132aa72f3bSAlexey Klimov { 1142aa72f3bSAlexey Klimov .id = V4L2_CID_AUDIO_BASS, 1152aa72f3bSAlexey Klimov .flags = V4L2_CTRL_FLAG_DISABLED, 1162aa72f3bSAlexey Klimov }, 1172aa72f3bSAlexey Klimov { 1182aa72f3bSAlexey Klimov .id = V4L2_CID_AUDIO_TREBLE, 1192aa72f3bSAlexey Klimov .flags = V4L2_CTRL_FLAG_DISABLED, 1202aa72f3bSAlexey Klimov }, 1212aa72f3bSAlexey Klimov { 1222aa72f3bSAlexey Klimov .id = V4L2_CID_AUDIO_LOUDNESS, 1232aa72f3bSAlexey Klimov .flags = V4L2_CTRL_FLAG_DISABLED, 1242aa72f3bSAlexey Klimov }, 1252aa72f3bSAlexey Klimov }; 1262aa72f3bSAlexey Klimov 1272aa72f3bSAlexey Klimov static int usb_amradio_probe(struct usb_interface *intf, 1282aa72f3bSAlexey Klimov const struct usb_device_id *id); 1292aa72f3bSAlexey Klimov static void usb_amradio_disconnect(struct usb_interface *intf); 130bec43661SHans Verkuil static int usb_amradio_open(struct file *file); 131bec43661SHans Verkuil static int usb_amradio_close(struct file *file); 1322aa72f3bSAlexey Klimov static int usb_amradio_suspend(struct usb_interface *intf, 1332aa72f3bSAlexey Klimov pm_message_t message); 1342aa72f3bSAlexey Klimov static int usb_amradio_resume(struct usb_interface *intf); 1352aa72f3bSAlexey Klimov 1362aa72f3bSAlexey Klimov /* Data for one (physical) device */ 1372aa72f3bSAlexey Klimov struct amradio_device { 1382aa72f3bSAlexey Klimov /* reference to USB and video device */ 1392aa72f3bSAlexey Klimov struct usb_device *usbdev; 1402aa72f3bSAlexey Klimov struct video_device *videodev; 1412aa72f3bSAlexey Klimov 1422aa72f3bSAlexey Klimov unsigned char *buffer; 1432aa72f3bSAlexey Klimov struct mutex lock; /* buffer locking */ 1442aa72f3bSAlexey Klimov int curfreq; 1452aa72f3bSAlexey Klimov int stereo; 1462aa72f3bSAlexey Klimov int users; 1472aa72f3bSAlexey Klimov int removed; 1482aa72f3bSAlexey Klimov int muted; 1492aa72f3bSAlexey Klimov }; 1502aa72f3bSAlexey Klimov 1512aa72f3bSAlexey Klimov /* USB Device ID List */ 1522aa72f3bSAlexey Klimov static struct usb_device_id usb_amradio_device_table[] = { 1532aa72f3bSAlexey Klimov {USB_DEVICE_AND_INTERFACE_INFO(USB_AMRADIO_VENDOR, USB_AMRADIO_PRODUCT, 1542aa72f3bSAlexey Klimov USB_CLASS_HID, 0, 0) }, 1552aa72f3bSAlexey Klimov { } /* Terminating entry */ 1562aa72f3bSAlexey Klimov }; 1572aa72f3bSAlexey Klimov 1582aa72f3bSAlexey Klimov MODULE_DEVICE_TABLE(usb, usb_amradio_device_table); 1592aa72f3bSAlexey Klimov 1602aa72f3bSAlexey Klimov /* USB subsystem interface */ 1612aa72f3bSAlexey Klimov static struct usb_driver usb_amradio_driver = { 162e60b022eSAlexey Klimov .name = MR800_DRIVER_NAME, 1632aa72f3bSAlexey Klimov .probe = usb_amradio_probe, 1642aa72f3bSAlexey Klimov .disconnect = usb_amradio_disconnect, 1652aa72f3bSAlexey Klimov .suspend = usb_amradio_suspend, 1662aa72f3bSAlexey Klimov .resume = usb_amradio_resume, 1672aa72f3bSAlexey Klimov .reset_resume = usb_amradio_resume, 1682aa72f3bSAlexey Klimov .id_table = usb_amradio_device_table, 169f2ce9179SAlexey Klimov .supports_autosuspend = 0, 1702aa72f3bSAlexey Klimov }; 1712aa72f3bSAlexey Klimov 1722aa72f3bSAlexey Klimov /* switch on radio. Send 8 bytes to device. */ 1732aa72f3bSAlexey Klimov static int amradio_start(struct amradio_device *radio) 1742aa72f3bSAlexey Klimov { 1752aa72f3bSAlexey Klimov int retval; 1762aa72f3bSAlexey Klimov int size; 1772aa72f3bSAlexey Klimov 1782aa72f3bSAlexey Klimov mutex_lock(&radio->lock); 1792aa72f3bSAlexey Klimov 1802aa72f3bSAlexey Klimov radio->buffer[0] = 0x00; 1812aa72f3bSAlexey Klimov radio->buffer[1] = 0x55; 1822aa72f3bSAlexey Klimov radio->buffer[2] = 0xaa; 1832aa72f3bSAlexey Klimov radio->buffer[3] = 0x00; 1842aa72f3bSAlexey Klimov radio->buffer[4] = 0xab; 1852aa72f3bSAlexey Klimov radio->buffer[5] = 0x00; 1862aa72f3bSAlexey Klimov radio->buffer[6] = 0x00; 1872aa72f3bSAlexey Klimov radio->buffer[7] = 0x00; 1882aa72f3bSAlexey Klimov 1892aa72f3bSAlexey Klimov retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), 1902aa72f3bSAlexey Klimov (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); 1912aa72f3bSAlexey Klimov 1922aa72f3bSAlexey Klimov if (retval) { 1932aa72f3bSAlexey Klimov mutex_unlock(&radio->lock); 1942aa72f3bSAlexey Klimov return retval; 1952aa72f3bSAlexey Klimov } 1962aa72f3bSAlexey Klimov 1972aa72f3bSAlexey Klimov radio->muted = 0; 1982aa72f3bSAlexey Klimov 1997f03a585SAlexey Klimov mutex_unlock(&radio->lock); 2007f03a585SAlexey Klimov 2012aa72f3bSAlexey Klimov return retval; 2022aa72f3bSAlexey Klimov } 2032aa72f3bSAlexey Klimov 2042aa72f3bSAlexey Klimov /* switch off radio */ 2052aa72f3bSAlexey Klimov static int amradio_stop(struct amradio_device *radio) 2062aa72f3bSAlexey Klimov { 2072aa72f3bSAlexey Klimov int retval; 2082aa72f3bSAlexey Klimov int size; 2092aa72f3bSAlexey Klimov 2103480130aSAlexey Klimov /* safety check */ 2113480130aSAlexey Klimov if (radio->removed) 2123480130aSAlexey Klimov return -EIO; 2133480130aSAlexey Klimov 2142aa72f3bSAlexey Klimov mutex_lock(&radio->lock); 2152aa72f3bSAlexey Klimov 2162aa72f3bSAlexey Klimov radio->buffer[0] = 0x00; 2172aa72f3bSAlexey Klimov radio->buffer[1] = 0x55; 2182aa72f3bSAlexey Klimov radio->buffer[2] = 0xaa; 2192aa72f3bSAlexey Klimov radio->buffer[3] = 0x00; 2202aa72f3bSAlexey Klimov radio->buffer[4] = 0xab; 2212aa72f3bSAlexey Klimov radio->buffer[5] = 0x01; 2222aa72f3bSAlexey Klimov radio->buffer[6] = 0x00; 2232aa72f3bSAlexey Klimov radio->buffer[7] = 0x00; 2242aa72f3bSAlexey Klimov 2252aa72f3bSAlexey Klimov retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), 2262aa72f3bSAlexey Klimov (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); 2272aa72f3bSAlexey Klimov 2282aa72f3bSAlexey Klimov if (retval) { 2292aa72f3bSAlexey Klimov mutex_unlock(&radio->lock); 2302aa72f3bSAlexey Klimov return retval; 2312aa72f3bSAlexey Klimov } 2322aa72f3bSAlexey Klimov 2332aa72f3bSAlexey Klimov radio->muted = 1; 2342aa72f3bSAlexey Klimov 2357f03a585SAlexey Klimov mutex_unlock(&radio->lock); 2367f03a585SAlexey Klimov 2372aa72f3bSAlexey Klimov return retval; 2382aa72f3bSAlexey Klimov } 2392aa72f3bSAlexey Klimov 2402aa72f3bSAlexey Klimov /* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ 2412aa72f3bSAlexey Klimov static int amradio_setfreq(struct amradio_device *radio, int freq) 2422aa72f3bSAlexey Klimov { 2432aa72f3bSAlexey Klimov int retval; 2442aa72f3bSAlexey Klimov int size; 2452aa72f3bSAlexey Klimov unsigned short freq_send = 0x13 + (freq >> 3) / 25; 2462aa72f3bSAlexey Klimov 2473480130aSAlexey Klimov /* safety check */ 2483480130aSAlexey Klimov if (radio->removed) 2493480130aSAlexey Klimov return -EIO; 2503480130aSAlexey Klimov 2512aa72f3bSAlexey Klimov mutex_lock(&radio->lock); 2522aa72f3bSAlexey Klimov 2532aa72f3bSAlexey Klimov radio->buffer[0] = 0x00; 2542aa72f3bSAlexey Klimov radio->buffer[1] = 0x55; 2552aa72f3bSAlexey Klimov radio->buffer[2] = 0xaa; 2562aa72f3bSAlexey Klimov radio->buffer[3] = 0x03; 2572aa72f3bSAlexey Klimov radio->buffer[4] = 0xa4; 2582aa72f3bSAlexey Klimov radio->buffer[5] = 0x00; 2592aa72f3bSAlexey Klimov radio->buffer[6] = 0x00; 2602aa72f3bSAlexey Klimov radio->buffer[7] = 0x08; 2612aa72f3bSAlexey Klimov 2622aa72f3bSAlexey Klimov retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), 2632aa72f3bSAlexey Klimov (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); 2642aa72f3bSAlexey Klimov 2652aa72f3bSAlexey Klimov if (retval) { 2662aa72f3bSAlexey Klimov mutex_unlock(&radio->lock); 2672aa72f3bSAlexey Klimov return retval; 2682aa72f3bSAlexey Klimov } 2692aa72f3bSAlexey Klimov 2702aa72f3bSAlexey Klimov /* frequency is calculated from freq_send and placed in first 2 bytes */ 2712aa72f3bSAlexey Klimov radio->buffer[0] = (freq_send >> 8) & 0xff; 2722aa72f3bSAlexey Klimov radio->buffer[1] = freq_send & 0xff; 2732aa72f3bSAlexey Klimov radio->buffer[2] = 0x01; 2742aa72f3bSAlexey Klimov radio->buffer[3] = 0x00; 2752aa72f3bSAlexey Klimov radio->buffer[4] = 0x00; 2762aa72f3bSAlexey Klimov /* 5 and 6 bytes of buffer already = 0x00 */ 2772aa72f3bSAlexey Klimov radio->buffer[7] = 0x00; 2782aa72f3bSAlexey Klimov 2792aa72f3bSAlexey Klimov retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), 2802aa72f3bSAlexey Klimov (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); 2812aa72f3bSAlexey Klimov 2822aa72f3bSAlexey Klimov if (retval) { 2832aa72f3bSAlexey Klimov mutex_unlock(&radio->lock); 2842aa72f3bSAlexey Klimov return retval; 2852aa72f3bSAlexey Klimov } 2862aa72f3bSAlexey Klimov 2872aa72f3bSAlexey Klimov radio->stereo = 0; 2882aa72f3bSAlexey Klimov 2897f03a585SAlexey Klimov mutex_unlock(&radio->lock); 2907f03a585SAlexey Klimov 2912aa72f3bSAlexey Klimov return retval; 2922aa72f3bSAlexey Klimov } 2932aa72f3bSAlexey Klimov 2942aa72f3bSAlexey Klimov /* USB subsystem interface begins here */ 2952aa72f3bSAlexey Klimov 2962aa72f3bSAlexey Klimov /* handle unplugging of the device, release data structures 2972aa72f3bSAlexey Klimov if nothing keeps us from doing it. If something is still 2982aa72f3bSAlexey Klimov keeping us busy, the release callback of v4l will take care 2992aa72f3bSAlexey Klimov of releasing it. */ 3002aa72f3bSAlexey Klimov static void usb_amradio_disconnect(struct usb_interface *intf) 3012aa72f3bSAlexey Klimov { 3022aa72f3bSAlexey Klimov struct amradio_device *radio = usb_get_intfdata(intf); 3032aa72f3bSAlexey Klimov 304f4e9043eSAlexey Klimov mutex_lock(&radio->lock); 3053480130aSAlexey Klimov radio->removed = 1; 306f4e9043eSAlexey Klimov mutex_unlock(&radio->lock); 3072aa72f3bSAlexey Klimov 308f4e9043eSAlexey Klimov usb_set_intfdata(intf, NULL); 3092aa72f3bSAlexey Klimov video_unregister_device(radio->videodev); 3102aa72f3bSAlexey Klimov } 3112aa72f3bSAlexey Klimov 3122aa72f3bSAlexey Klimov /* vidioc_querycap - query device capabilities */ 3132aa72f3bSAlexey Klimov static int vidioc_querycap(struct file *file, void *priv, 3142aa72f3bSAlexey Klimov struct v4l2_capability *v) 3152aa72f3bSAlexey Klimov { 316c7181cfaSAlexey Klimov struct amradio_device *radio = video_drvdata(file); 317c7181cfaSAlexey Klimov 3182aa72f3bSAlexey Klimov strlcpy(v->driver, "radio-mr800", sizeof(v->driver)); 3192aa72f3bSAlexey Klimov strlcpy(v->card, "AverMedia MR 800 USB FM Radio", sizeof(v->card)); 320c7181cfaSAlexey Klimov usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); 3212aa72f3bSAlexey Klimov v->version = RADIO_VERSION; 3222aa72f3bSAlexey Klimov v->capabilities = V4L2_CAP_TUNER; 3232aa72f3bSAlexey Klimov return 0; 3242aa72f3bSAlexey Klimov } 3252aa72f3bSAlexey Klimov 3262aa72f3bSAlexey Klimov /* vidioc_g_tuner - get tuner attributes */ 3272aa72f3bSAlexey Klimov static int vidioc_g_tuner(struct file *file, void *priv, 3282aa72f3bSAlexey Klimov struct v4l2_tuner *v) 3292aa72f3bSAlexey Klimov { 3302aa72f3bSAlexey Klimov struct amradio_device *radio = video_get_drvdata(video_devdata(file)); 3312aa72f3bSAlexey Klimov 3323480130aSAlexey Klimov /* safety check */ 3333480130aSAlexey Klimov if (radio->removed) 3343480130aSAlexey Klimov return -EIO; 3353480130aSAlexey Klimov 3362aa72f3bSAlexey Klimov if (v->index > 0) 3372aa72f3bSAlexey Klimov return -EINVAL; 3382aa72f3bSAlexey Klimov 3392aa72f3bSAlexey Klimov /* TODO: Add function which look is signal stereo or not 3402aa72f3bSAlexey Klimov * amradio_getstat(radio); 3412aa72f3bSAlexey Klimov */ 3422aa72f3bSAlexey Klimov radio->stereo = -1; 3432aa72f3bSAlexey Klimov strcpy(v->name, "FM"); 3442aa72f3bSAlexey Klimov v->type = V4L2_TUNER_RADIO; 3452aa72f3bSAlexey Klimov v->rangelow = FREQ_MIN * FREQ_MUL; 3462aa72f3bSAlexey Klimov v->rangehigh = FREQ_MAX * FREQ_MUL; 3472aa72f3bSAlexey Klimov v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO; 3482aa72f3bSAlexey Klimov v->capability = V4L2_TUNER_CAP_LOW; 3492aa72f3bSAlexey Klimov if (radio->stereo) 3502aa72f3bSAlexey Klimov v->audmode = V4L2_TUNER_MODE_STEREO; 3512aa72f3bSAlexey Klimov else 3522aa72f3bSAlexey Klimov v->audmode = V4L2_TUNER_MODE_MONO; 3532aa72f3bSAlexey Klimov v->signal = 0xffff; /* Can't get the signal strength, sad.. */ 3542aa72f3bSAlexey Klimov v->afc = 0; /* Don't know what is this */ 3552aa72f3bSAlexey Klimov return 0; 3562aa72f3bSAlexey Klimov } 3572aa72f3bSAlexey Klimov 3582aa72f3bSAlexey Klimov /* vidioc_s_tuner - set tuner attributes */ 3592aa72f3bSAlexey Klimov static int vidioc_s_tuner(struct file *file, void *priv, 3602aa72f3bSAlexey Klimov struct v4l2_tuner *v) 3612aa72f3bSAlexey Klimov { 3623480130aSAlexey Klimov struct amradio_device *radio = video_get_drvdata(video_devdata(file)); 3633480130aSAlexey Klimov 3643480130aSAlexey Klimov /* safety check */ 3653480130aSAlexey Klimov if (radio->removed) 3663480130aSAlexey Klimov return -EIO; 3673480130aSAlexey Klimov 3682aa72f3bSAlexey Klimov if (v->index > 0) 3692aa72f3bSAlexey Klimov return -EINVAL; 3702aa72f3bSAlexey Klimov return 0; 3712aa72f3bSAlexey Klimov } 3722aa72f3bSAlexey Klimov 3732aa72f3bSAlexey Klimov /* vidioc_s_frequency - set tuner radio frequency */ 3742aa72f3bSAlexey Klimov static int vidioc_s_frequency(struct file *file, void *priv, 3752aa72f3bSAlexey Klimov struct v4l2_frequency *f) 3762aa72f3bSAlexey Klimov { 3772aa72f3bSAlexey Klimov struct amradio_device *radio = video_get_drvdata(video_devdata(file)); 3782aa72f3bSAlexey Klimov 3793480130aSAlexey Klimov /* safety check */ 3803480130aSAlexey Klimov if (radio->removed) 3813480130aSAlexey Klimov return -EIO; 3823480130aSAlexey Klimov 3832aa72f3bSAlexey Klimov radio->curfreq = f->frequency; 3842aa72f3bSAlexey Klimov if (amradio_setfreq(radio, radio->curfreq) < 0) 385e60b022eSAlexey Klimov amradio_dev_warn(&radio->videodev->dev, 386e60b022eSAlexey Klimov "set frequency failed\n"); 3872aa72f3bSAlexey Klimov return 0; 3882aa72f3bSAlexey Klimov } 3892aa72f3bSAlexey Klimov 3902aa72f3bSAlexey Klimov /* vidioc_g_frequency - get tuner radio frequency */ 3912aa72f3bSAlexey Klimov static int vidioc_g_frequency(struct file *file, void *priv, 3922aa72f3bSAlexey Klimov struct v4l2_frequency *f) 3932aa72f3bSAlexey Klimov { 3942aa72f3bSAlexey Klimov struct amradio_device *radio = video_get_drvdata(video_devdata(file)); 3952aa72f3bSAlexey Klimov 3963480130aSAlexey Klimov /* safety check */ 3973480130aSAlexey Klimov if (radio->removed) 3983480130aSAlexey Klimov return -EIO; 3993480130aSAlexey Klimov 4002aa72f3bSAlexey Klimov f->type = V4L2_TUNER_RADIO; 4012aa72f3bSAlexey Klimov f->frequency = radio->curfreq; 4022aa72f3bSAlexey Klimov return 0; 4032aa72f3bSAlexey Klimov } 4042aa72f3bSAlexey Klimov 4052aa72f3bSAlexey Klimov /* vidioc_queryctrl - enumerate control items */ 4062aa72f3bSAlexey Klimov static int vidioc_queryctrl(struct file *file, void *priv, 4072aa72f3bSAlexey Klimov struct v4l2_queryctrl *qc) 4082aa72f3bSAlexey Klimov { 4092aa72f3bSAlexey Klimov int i; 4102aa72f3bSAlexey Klimov 4112aa72f3bSAlexey Klimov for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) { 4122aa72f3bSAlexey Klimov if (qc->id && qc->id == radio_qctrl[i].id) { 413e60b022eSAlexey Klimov memcpy(qc, &(radio_qctrl[i]), sizeof(*qc)); 4142aa72f3bSAlexey Klimov return 0; 4152aa72f3bSAlexey Klimov } 4162aa72f3bSAlexey Klimov } 4172aa72f3bSAlexey Klimov return -EINVAL; 4182aa72f3bSAlexey Klimov } 4192aa72f3bSAlexey Klimov 4202aa72f3bSAlexey Klimov /* vidioc_g_ctrl - get the value of a control */ 4212aa72f3bSAlexey Klimov static int vidioc_g_ctrl(struct file *file, void *priv, 4222aa72f3bSAlexey Klimov struct v4l2_control *ctrl) 4232aa72f3bSAlexey Klimov { 4242aa72f3bSAlexey Klimov struct amradio_device *radio = video_get_drvdata(video_devdata(file)); 4252aa72f3bSAlexey Klimov 4263480130aSAlexey Klimov /* safety check */ 4273480130aSAlexey Klimov if (radio->removed) 4283480130aSAlexey Klimov return -EIO; 4293480130aSAlexey Klimov 4302aa72f3bSAlexey Klimov switch (ctrl->id) { 4312aa72f3bSAlexey Klimov case V4L2_CID_AUDIO_MUTE: 4322aa72f3bSAlexey Klimov ctrl->value = radio->muted; 4332aa72f3bSAlexey Klimov return 0; 4342aa72f3bSAlexey Klimov } 4352aa72f3bSAlexey Klimov return -EINVAL; 4362aa72f3bSAlexey Klimov } 4372aa72f3bSAlexey Klimov 4382aa72f3bSAlexey Klimov /* vidioc_s_ctrl - set the value of a control */ 4392aa72f3bSAlexey Klimov static int vidioc_s_ctrl(struct file *file, void *priv, 4402aa72f3bSAlexey Klimov struct v4l2_control *ctrl) 4412aa72f3bSAlexey Klimov { 4422aa72f3bSAlexey Klimov struct amradio_device *radio = video_get_drvdata(video_devdata(file)); 4432aa72f3bSAlexey Klimov 4443480130aSAlexey Klimov /* safety check */ 4453480130aSAlexey Klimov if (radio->removed) 4463480130aSAlexey Klimov return -EIO; 4473480130aSAlexey Klimov 4482aa72f3bSAlexey Klimov switch (ctrl->id) { 4492aa72f3bSAlexey Klimov case V4L2_CID_AUDIO_MUTE: 4502aa72f3bSAlexey Klimov if (ctrl->value) { 4512aa72f3bSAlexey Klimov if (amradio_stop(radio) < 0) { 452e60b022eSAlexey Klimov amradio_dev_warn(&radio->videodev->dev, 453e60b022eSAlexey Klimov "amradio_stop failed\n"); 4542aa72f3bSAlexey Klimov return -1; 4552aa72f3bSAlexey Klimov } 4562aa72f3bSAlexey Klimov } else { 4572aa72f3bSAlexey Klimov if (amradio_start(radio) < 0) { 458e60b022eSAlexey Klimov amradio_dev_warn(&radio->videodev->dev, 459e60b022eSAlexey Klimov "amradio_start failed\n"); 4602aa72f3bSAlexey Klimov return -1; 4612aa72f3bSAlexey Klimov } 4622aa72f3bSAlexey Klimov } 4632aa72f3bSAlexey Klimov return 0; 4642aa72f3bSAlexey Klimov } 4652aa72f3bSAlexey Klimov return -EINVAL; 4662aa72f3bSAlexey Klimov } 4672aa72f3bSAlexey Klimov 4682aa72f3bSAlexey Klimov /* vidioc_g_audio - get audio attributes */ 4692aa72f3bSAlexey Klimov static int vidioc_g_audio(struct file *file, void *priv, 4702aa72f3bSAlexey Klimov struct v4l2_audio *a) 4712aa72f3bSAlexey Klimov { 4722aa72f3bSAlexey Klimov if (a->index > 1) 4732aa72f3bSAlexey Klimov return -EINVAL; 4742aa72f3bSAlexey Klimov 4752aa72f3bSAlexey Klimov strcpy(a->name, "Radio"); 4762aa72f3bSAlexey Klimov a->capability = V4L2_AUDCAP_STEREO; 4772aa72f3bSAlexey Klimov return 0; 4782aa72f3bSAlexey Klimov } 4792aa72f3bSAlexey Klimov 4802aa72f3bSAlexey Klimov /* vidioc_s_audio - set audio attributes */ 4812aa72f3bSAlexey Klimov static int vidioc_s_audio(struct file *file, void *priv, 4822aa72f3bSAlexey Klimov struct v4l2_audio *a) 4832aa72f3bSAlexey Klimov { 4842aa72f3bSAlexey Klimov if (a->index != 0) 4852aa72f3bSAlexey Klimov return -EINVAL; 4862aa72f3bSAlexey Klimov return 0; 4872aa72f3bSAlexey Klimov } 4882aa72f3bSAlexey Klimov 4892aa72f3bSAlexey Klimov /* vidioc_g_input - get input */ 4902aa72f3bSAlexey Klimov static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i) 4912aa72f3bSAlexey Klimov { 4922aa72f3bSAlexey Klimov *i = 0; 4932aa72f3bSAlexey Klimov return 0; 4942aa72f3bSAlexey Klimov } 4952aa72f3bSAlexey Klimov 4962aa72f3bSAlexey Klimov /* vidioc_s_input - set input */ 4972aa72f3bSAlexey Klimov static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) 4982aa72f3bSAlexey Klimov { 4992aa72f3bSAlexey Klimov if (i != 0) 5002aa72f3bSAlexey Klimov return -EINVAL; 5012aa72f3bSAlexey Klimov return 0; 5022aa72f3bSAlexey Klimov } 5032aa72f3bSAlexey Klimov 5042aa72f3bSAlexey Klimov /* open device - amradio_start() and amradio_setfreq() */ 505bec43661SHans Verkuil static int usb_amradio_open(struct file *file) 5062aa72f3bSAlexey Klimov { 5072aa72f3bSAlexey Klimov struct amradio_device *radio = video_get_drvdata(video_devdata(file)); 5082aa72f3bSAlexey Klimov 5090fabb783SAlexey Klimov lock_kernel(); 5100fabb783SAlexey Klimov 5112aa72f3bSAlexey Klimov radio->users = 1; 5122aa72f3bSAlexey Klimov radio->muted = 1; 5132aa72f3bSAlexey Klimov 5142aa72f3bSAlexey Klimov if (amradio_start(radio) < 0) { 515e60b022eSAlexey Klimov amradio_dev_warn(&radio->videodev->dev, 516e60b022eSAlexey Klimov "radio did not start up properly\n"); 5172aa72f3bSAlexey Klimov radio->users = 0; 5180fabb783SAlexey Klimov unlock_kernel(); 5192aa72f3bSAlexey Klimov return -EIO; 5202aa72f3bSAlexey Klimov } 5212aa72f3bSAlexey Klimov if (amradio_setfreq(radio, radio->curfreq) < 0) 522e60b022eSAlexey Klimov amradio_dev_warn(&radio->videodev->dev, 523e60b022eSAlexey Klimov "set frequency failed\n"); 5240fabb783SAlexey Klimov 5250fabb783SAlexey Klimov unlock_kernel(); 5262aa72f3bSAlexey Klimov return 0; 5272aa72f3bSAlexey Klimov } 5282aa72f3bSAlexey Klimov 529f4e9043eSAlexey Klimov /*close device */ 530bec43661SHans Verkuil static int usb_amradio_close(struct file *file) 5312aa72f3bSAlexey Klimov { 5322aa72f3bSAlexey Klimov struct amradio_device *radio = video_get_drvdata(video_devdata(file)); 5333480130aSAlexey Klimov int retval; 5342aa72f3bSAlexey Klimov 5352aa72f3bSAlexey Klimov if (!radio) 5362aa72f3bSAlexey Klimov return -ENODEV; 5373480130aSAlexey Klimov 5382aa72f3bSAlexey Klimov radio->users = 0; 5393480130aSAlexey Klimov 540f4e9043eSAlexey Klimov if (!radio->removed) { 5413480130aSAlexey Klimov retval = amradio_stop(radio); 5423480130aSAlexey Klimov if (retval < 0) 5433480130aSAlexey Klimov amradio_dev_warn(&radio->videodev->dev, 5443480130aSAlexey Klimov "amradio_stop failed\n"); 5452aa72f3bSAlexey Klimov } 5463480130aSAlexey Klimov 5472aa72f3bSAlexey Klimov return 0; 5482aa72f3bSAlexey Klimov } 5492aa72f3bSAlexey Klimov 5502aa72f3bSAlexey Klimov /* Suspend device - stop device. Need to be checked and fixed */ 5512aa72f3bSAlexey Klimov static int usb_amradio_suspend(struct usb_interface *intf, pm_message_t message) 5522aa72f3bSAlexey Klimov { 5532aa72f3bSAlexey Klimov struct amradio_device *radio = usb_get_intfdata(intf); 5542aa72f3bSAlexey Klimov 5552aa72f3bSAlexey Klimov if (amradio_stop(radio) < 0) 556e60b022eSAlexey Klimov dev_warn(&intf->dev, "amradio_stop failed\n"); 5572aa72f3bSAlexey Klimov 558e60b022eSAlexey Klimov dev_info(&intf->dev, "going into suspend..\n"); 5592aa72f3bSAlexey Klimov 5602aa72f3bSAlexey Klimov return 0; 5612aa72f3bSAlexey Klimov } 5622aa72f3bSAlexey Klimov 5632aa72f3bSAlexey Klimov /* Resume device - start device. Need to be checked and fixed */ 5642aa72f3bSAlexey Klimov static int usb_amradio_resume(struct usb_interface *intf) 5652aa72f3bSAlexey Klimov { 5662aa72f3bSAlexey Klimov struct amradio_device *radio = usb_get_intfdata(intf); 5672aa72f3bSAlexey Klimov 5682aa72f3bSAlexey Klimov if (amradio_start(radio) < 0) 569e60b022eSAlexey Klimov dev_warn(&intf->dev, "amradio_start failed\n"); 5702aa72f3bSAlexey Klimov 571e60b022eSAlexey Klimov dev_info(&intf->dev, "coming out of suspend..\n"); 5722aa72f3bSAlexey Klimov 5732aa72f3bSAlexey Klimov return 0; 5742aa72f3bSAlexey Klimov } 5752aa72f3bSAlexey Klimov 5762aa72f3bSAlexey Klimov /* File system interface */ 577bec43661SHans Verkuil static const struct v4l2_file_operations usb_amradio_fops = { 5782aa72f3bSAlexey Klimov .owner = THIS_MODULE, 5792aa72f3bSAlexey Klimov .open = usb_amradio_open, 5802aa72f3bSAlexey Klimov .release = usb_amradio_close, 5812aa72f3bSAlexey Klimov .ioctl = video_ioctl2, 5822aa72f3bSAlexey Klimov }; 5832aa72f3bSAlexey Klimov 5842aa72f3bSAlexey Klimov static const struct v4l2_ioctl_ops usb_amradio_ioctl_ops = { 5852aa72f3bSAlexey Klimov .vidioc_querycap = vidioc_querycap, 5862aa72f3bSAlexey Klimov .vidioc_g_tuner = vidioc_g_tuner, 5872aa72f3bSAlexey Klimov .vidioc_s_tuner = vidioc_s_tuner, 5882aa72f3bSAlexey Klimov .vidioc_g_frequency = vidioc_g_frequency, 5892aa72f3bSAlexey Klimov .vidioc_s_frequency = vidioc_s_frequency, 5902aa72f3bSAlexey Klimov .vidioc_queryctrl = vidioc_queryctrl, 5912aa72f3bSAlexey Klimov .vidioc_g_ctrl = vidioc_g_ctrl, 5922aa72f3bSAlexey Klimov .vidioc_s_ctrl = vidioc_s_ctrl, 5932aa72f3bSAlexey Klimov .vidioc_g_audio = vidioc_g_audio, 5942aa72f3bSAlexey Klimov .vidioc_s_audio = vidioc_s_audio, 5952aa72f3bSAlexey Klimov .vidioc_g_input = vidioc_g_input, 5962aa72f3bSAlexey Klimov .vidioc_s_input = vidioc_s_input, 5972aa72f3bSAlexey Klimov }; 5982aa72f3bSAlexey Klimov 599f4e9043eSAlexey Klimov static void usb_amradio_device_release(struct video_device *videodev) 600f4e9043eSAlexey Klimov { 601f4e9043eSAlexey Klimov struct amradio_device *radio = video_get_drvdata(videodev); 602f4e9043eSAlexey Klimov 603f4e9043eSAlexey Klimov /* we call v4l to free radio->videodev */ 604f4e9043eSAlexey Klimov video_device_release(videodev); 605f4e9043eSAlexey Klimov 606f4e9043eSAlexey Klimov /* free rest memory */ 607f4e9043eSAlexey Klimov kfree(radio->buffer); 608f4e9043eSAlexey Klimov kfree(radio); 609f4e9043eSAlexey Klimov } 610f4e9043eSAlexey Klimov 6112aa72f3bSAlexey Klimov /* V4L2 interface */ 6122aa72f3bSAlexey Klimov static struct video_device amradio_videodev_template = { 6132aa72f3bSAlexey Klimov .name = "AverMedia MR 800 USB FM Radio", 6142aa72f3bSAlexey Klimov .fops = &usb_amradio_fops, 6152aa72f3bSAlexey Klimov .ioctl_ops = &usb_amradio_ioctl_ops, 616f4e9043eSAlexey Klimov .release = usb_amradio_device_release, 6172aa72f3bSAlexey Klimov }; 6182aa72f3bSAlexey Klimov 6192aa72f3bSAlexey Klimov /* check if the device is present and register with v4l and 6202aa72f3bSAlexey Klimov usb if it is */ 6212aa72f3bSAlexey Klimov static int usb_amradio_probe(struct usb_interface *intf, 6222aa72f3bSAlexey Klimov const struct usb_device_id *id) 6232aa72f3bSAlexey Klimov { 6242aa72f3bSAlexey Klimov struct amradio_device *radio; 6252aa72f3bSAlexey Klimov 6262aa72f3bSAlexey Klimov radio = kmalloc(sizeof(struct amradio_device), GFP_KERNEL); 6272aa72f3bSAlexey Klimov 6282aa72f3bSAlexey Klimov if (!(radio)) 6292aa72f3bSAlexey Klimov return -ENOMEM; 6302aa72f3bSAlexey Klimov 6312aa72f3bSAlexey Klimov radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); 6322aa72f3bSAlexey Klimov 6332aa72f3bSAlexey Klimov if (!(radio->buffer)) { 6342aa72f3bSAlexey Klimov kfree(radio); 6352aa72f3bSAlexey Klimov return -ENOMEM; 6362aa72f3bSAlexey Klimov } 6372aa72f3bSAlexey Klimov 6382aa72f3bSAlexey Klimov radio->videodev = video_device_alloc(); 6392aa72f3bSAlexey Klimov 6402aa72f3bSAlexey Klimov if (!(radio->videodev)) { 6412aa72f3bSAlexey Klimov kfree(radio->buffer); 6422aa72f3bSAlexey Klimov kfree(radio); 6432aa72f3bSAlexey Klimov return -ENOMEM; 6442aa72f3bSAlexey Klimov } 6452aa72f3bSAlexey Klimov 6462aa72f3bSAlexey Klimov memcpy(radio->videodev, &amradio_videodev_template, 6472aa72f3bSAlexey Klimov sizeof(amradio_videodev_template)); 6482aa72f3bSAlexey Klimov 6492aa72f3bSAlexey Klimov radio->removed = 0; 6502aa72f3bSAlexey Klimov radio->users = 0; 6512aa72f3bSAlexey Klimov radio->usbdev = interface_to_usbdev(intf); 6522aa72f3bSAlexey Klimov radio->curfreq = 95.16 * FREQ_MUL; 6532aa72f3bSAlexey Klimov 6542aa72f3bSAlexey Klimov mutex_init(&radio->lock); 6552aa72f3bSAlexey Klimov 6562aa72f3bSAlexey Klimov video_set_drvdata(radio->videodev, radio); 6572aa72f3bSAlexey Klimov if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr)) { 658e60b022eSAlexey Klimov dev_warn(&intf->dev, "could not register video device\n"); 6592aa72f3bSAlexey Klimov video_device_release(radio->videodev); 6602aa72f3bSAlexey Klimov kfree(radio->buffer); 6612aa72f3bSAlexey Klimov kfree(radio); 6622aa72f3bSAlexey Klimov return -EIO; 6632aa72f3bSAlexey Klimov } 6642aa72f3bSAlexey Klimov 6652aa72f3bSAlexey Klimov usb_set_intfdata(intf, radio); 6662aa72f3bSAlexey Klimov return 0; 6672aa72f3bSAlexey Klimov } 6682aa72f3bSAlexey Klimov 6692aa72f3bSAlexey Klimov static int __init amradio_init(void) 6702aa72f3bSAlexey Klimov { 6712aa72f3bSAlexey Klimov int retval = usb_register(&usb_amradio_driver); 6722aa72f3bSAlexey Klimov 673e60b022eSAlexey Klimov pr_info(KBUILD_MODNAME 674e60b022eSAlexey Klimov ": version " DRIVER_VERSION " " DRIVER_DESC "\n"); 675e60b022eSAlexey Klimov 6762aa72f3bSAlexey Klimov if (retval) 677e60b022eSAlexey Klimov pr_err(KBUILD_MODNAME 678e60b022eSAlexey Klimov ": usb_register failed. Error number %d\n", retval); 679e60b022eSAlexey Klimov 6802aa72f3bSAlexey Klimov return retval; 6812aa72f3bSAlexey Klimov } 6822aa72f3bSAlexey Klimov 6832aa72f3bSAlexey Klimov static void __exit amradio_exit(void) 6842aa72f3bSAlexey Klimov { 6852aa72f3bSAlexey Klimov usb_deregister(&usb_amradio_driver); 6862aa72f3bSAlexey Klimov } 6872aa72f3bSAlexey Klimov 6882aa72f3bSAlexey Klimov module_init(amradio_init); 6892aa72f3bSAlexey Klimov module_exit(amradio_exit); 6902aa72f3bSAlexey Klimov 691