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