aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorAlexey Klimov <klimov.linux@gmail.com>2008-10-01 08:40:59 -0400
committerMauro Carvalho Chehab <mchehab@redhat.com>2008-10-12 07:37:13 -0400
commit2aa72f3b63e4b524e9e4b1438f6c0d50a214d836 (patch)
tree41272dfa85a82e888c60a75e6bd0d49bed8f8d5e /drivers
parent695ebd125ade17101861c9eb99f74e6cc9a516ed (diff)
V4L/DVB (9101): radio-mr800: Add driver for AverMedia MR 800 USB FM radio devices
This patch creates a new usb-radio driver, radio-mr800.c, that supports the AverMedia MR 800 USB FM radio devices. This device plugs into both the USB and an analog audio input, so this thing only deals with initialization and frequency setting, the audio data has to be handled by a sound driver. Signed-off-by: Alexey Klimov <klimov.linux@gmail.com> Signed-off-by: Douglas Schilling Landgraf <dougsland@linuxtv.org> Signed-off-by: Mauro Carvalho Chehab <mchehab@redhat.com>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/media/radio/Kconfig12
-rw-r--r--drivers/media/radio/Makefile1
-rw-r--r--drivers/media/radio/radio-mr800.c628
3 files changed, 641 insertions, 0 deletions
diff --git a/drivers/media/radio/Kconfig b/drivers/media/radio/Kconfig
index 1b41b3f77cf..e51d707e58d 100644
--- a/drivers/media/radio/Kconfig
+++ b/drivers/media/radio/Kconfig
@@ -361,4 +361,16 @@ config USB_SI470X
361 To compile this driver as a module, choose M here: the 361 To compile this driver as a module, choose M here: the
362 module will be called radio-silabs. 362 module will be called radio-silabs.
363 363
364config USB_MR800
365 tristate "AverMedia MR 800 USB FM radio support"
366 depends on USB && VIDEO_V4L2
367 ---help---
368 Say Y here if you want to connect this type of radio to your
369 computer's USB port. Note that the audio is not digital, and
370 you must connect the line out connector to a sound card or a
371 set of speakers.
372
373 To compile this driver as a module, choose M here: the
374 module will be called radio-mr800.
375
364endif # RADIO_ADAPTERS 376endif # RADIO_ADAPTERS
diff --git a/drivers/media/radio/Makefile b/drivers/media/radio/Makefile
index 7ca71ab96b4..240ec63cdaf 100644
--- a/drivers/media/radio/Makefile
+++ b/drivers/media/radio/Makefile
@@ -18,5 +18,6 @@ obj-$(CONFIG_RADIO_TRUST) += radio-trust.o
18obj-$(CONFIG_RADIO_MAESTRO) += radio-maestro.o 18obj-$(CONFIG_RADIO_MAESTRO) += radio-maestro.o
19obj-$(CONFIG_USB_DSBR) += dsbr100.o 19obj-$(CONFIG_USB_DSBR) += dsbr100.o
20obj-$(CONFIG_USB_SI470X) += radio-si470x.o 20obj-$(CONFIG_USB_SI470X) += radio-si470x.o
21obj-$(CONFIG_USB_MR800) += radio-mr800.o
21 22
22EXTRA_CFLAGS += -Isound 23EXTRA_CFLAGS += -Isound
diff --git a/drivers/media/radio/radio-mr800.c b/drivers/media/radio/radio-mr800.c
new file mode 100644
index 00000000000..a33717c4800
--- /dev/null
+++ b/drivers/media/radio/radio-mr800.c
@@ -0,0 +1,628 @@
1/*
2 * A driver for the AverMedia MR 800 USB FM radio. This device plugs
3 * into both the USB and an analog audio input, so this thing
4 * only deals with initialization and frequency setting, the
5 * audio data has to be handled by a sound driver.
6 *
7 * Copyright (c) 2008 Alexey Klimov <klimov.linux@gmail.com>
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 */
23
24/*
25 * Big thanks to authors of dsbr100.c and radio-si470x.c
26 *
27 * When work was looked pretty good, i discover this:
28 * http://av-usbradio.sourceforge.net/index.php
29 * http://sourceforge.net/projects/av-usbradio/
30 * Latest release of theirs project was in 2005.
31 * Probably, this driver could be improved trough using their
32 * achievements (specifications given).
33 * So, we have smth to begin with.
34 *
35 * History:
36 * Version 0.01: First working version.
37 * It's required to blacklist AverMedia USB Radio
38 * in usbhid/hid-quirks.c
39 *
40 * Many things to do:
41 * - Correct power managment of device (suspend & resume)
42 * - Make x86 independance (little-endian and big-endian stuff)
43 * - Add code for scanning and smooth tuning
44 * - Checked and add stereo&mono stuff
45 * - Add code for sensitivity value
46 * - Correct mistakes
47 * - In Japan another FREQ_MIN and FREQ_MAX
48 */
49
50/* kernel includes */
51#include <linux/kernel.h>
52#include <linux/module.h>
53#include <linux/init.h>
54#include <linux/slab.h>
55#include <linux/input.h>
56#include <linux/videodev2.h>
57#include <media/v4l2-common.h>
58#include <media/v4l2-ioctl.h>
59#include <linux/usb.h>
60#include <linux/version.h> /* for KERNEL_VERSION MACRO */
61
62/* driver and module definitions */
63#define DRIVER_AUTHOR "Alexey Klimov <klimov.linux@gmail.com>"
64#define DRIVER_DESC "AverMedia MR 800 USB FM radio driver"
65#define DRIVER_VERSION "0.01"
66#define RADIO_VERSION KERNEL_VERSION(0, 0, 1)
67
68MODULE_AUTHOR(DRIVER_AUTHOR);
69MODULE_DESCRIPTION(DRIVER_DESC);
70MODULE_LICENSE("GPL");
71
72#define USB_AMRADIO_VENDOR 0x07ca
73#define USB_AMRADIO_PRODUCT 0xb800
74
75/* Probably USB_TIMEOUT should be modified in module parameter */
76#define BUFFER_LENGTH 8
77#define USB_TIMEOUT 500
78
79/* Frequency limits in MHz -- these are European values. For Japanese
80devices, that would be 76 and 91. */
81#define FREQ_MIN 87.5
82#define FREQ_MAX 108.0
83#define FREQ_MUL 16000
84
85/* module parameter */
86static int radio_nr = -1;
87module_param(radio_nr, int, 0);
88MODULE_PARM_DESC(radio_nr, "Radio Nr");
89
90static struct v4l2_queryctrl radio_qctrl[] = {
91 {
92 .id = V4L2_CID_AUDIO_MUTE,
93 .name = "Mute",
94 .minimum = 0,
95 .maximum = 1,
96 .step = 1,
97 .default_value = 1,
98 .type = V4L2_CTRL_TYPE_BOOLEAN,
99 },
100/* HINT: the disabled controls are only here to satify kradio and such apps */
101 { .id = V4L2_CID_AUDIO_VOLUME,
102 .flags = V4L2_CTRL_FLAG_DISABLED,
103 },
104 {
105 .id = V4L2_CID_AUDIO_BALANCE,
106 .flags = V4L2_CTRL_FLAG_DISABLED,
107 },
108 {
109 .id = V4L2_CID_AUDIO_BASS,
110 .flags = V4L2_CTRL_FLAG_DISABLED,
111 },
112 {
113 .id = V4L2_CID_AUDIO_TREBLE,
114 .flags = V4L2_CTRL_FLAG_DISABLED,
115 },
116 {
117 .id = V4L2_CID_AUDIO_LOUDNESS,
118 .flags = V4L2_CTRL_FLAG_DISABLED,
119 },
120};
121
122static int usb_amradio_probe(struct usb_interface *intf,
123 const struct usb_device_id *id);
124static void usb_amradio_disconnect(struct usb_interface *intf);
125static int usb_amradio_open(struct inode *inode, struct file *file);
126static int usb_amradio_close(struct inode *inode, struct file *file);
127static int usb_amradio_suspend(struct usb_interface *intf,
128 pm_message_t message);
129static int usb_amradio_resume(struct usb_interface *intf);
130
131/* Data for one (physical) device */
132struct amradio_device {
133 /* reference to USB and video device */
134 struct usb_device *usbdev;
135 struct video_device *videodev;
136
137 unsigned char *buffer;
138 struct mutex lock; /* buffer locking */
139 int curfreq;
140 int stereo;
141 int users;
142 int removed;
143 int muted;
144};
145
146/* USB Device ID List */
147static struct usb_device_id usb_amradio_device_table[] = {
148 {USB_DEVICE_AND_INTERFACE_INFO(USB_AMRADIO_VENDOR, USB_AMRADIO_PRODUCT,
149 USB_CLASS_HID, 0, 0) },
150 { } /* Terminating entry */
151};
152
153MODULE_DEVICE_TABLE(usb, usb_amradio_device_table);
154
155/* USB subsystem interface */
156static struct usb_driver usb_amradio_driver = {
157 .name = "radio-mr800",
158 .probe = usb_amradio_probe,
159 .disconnect = usb_amradio_disconnect,
160 .suspend = usb_amradio_suspend,
161 .resume = usb_amradio_resume,
162 .reset_resume = usb_amradio_resume,
163 .id_table = usb_amradio_device_table,
164 .supports_autosuspend = 1,
165};
166
167/* switch on radio. Send 8 bytes to device. */
168static int amradio_start(struct amradio_device *radio)
169{
170 int retval;
171 int size;
172
173 mutex_lock(&radio->lock);
174
175 radio->buffer[0] = 0x00;
176 radio->buffer[1] = 0x55;
177 radio->buffer[2] = 0xaa;
178 radio->buffer[3] = 0x00;
179 radio->buffer[4] = 0xab;
180 radio->buffer[5] = 0x00;
181 radio->buffer[6] = 0x00;
182 radio->buffer[7] = 0x00;
183
184 retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2),
185 (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT);
186
187 if (retval) {
188 mutex_unlock(&radio->lock);
189 return retval;
190 }
191
192 mutex_unlock(&radio->lock);
193
194 radio->muted = 0;
195
196 return retval;
197}
198
199/* switch off radio */
200static int amradio_stop(struct amradio_device *radio)
201{
202 int retval;
203 int size;
204
205 mutex_lock(&radio->lock);
206
207 radio->buffer[0] = 0x00;
208 radio->buffer[1] = 0x55;
209 radio->buffer[2] = 0xaa;
210 radio->buffer[3] = 0x00;
211 radio->buffer[4] = 0xab;
212 radio->buffer[5] = 0x01;
213 radio->buffer[6] = 0x00;
214 radio->buffer[7] = 0x00;
215
216 retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2),
217 (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT);
218
219 if (retval) {
220 mutex_unlock(&radio->lock);
221 return retval;
222 }
223
224 mutex_unlock(&radio->lock);
225
226 radio->muted = 1;
227
228 return retval;
229}
230
231/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */
232static int amradio_setfreq(struct amradio_device *radio, int freq)
233{
234 int retval;
235 int size;
236 unsigned short freq_send = 0x13 + (freq >> 3) / 25;
237
238 mutex_lock(&radio->lock);
239
240 radio->buffer[0] = 0x00;
241 radio->buffer[1] = 0x55;
242 radio->buffer[2] = 0xaa;
243 radio->buffer[3] = 0x03;
244 radio->buffer[4] = 0xa4;
245 radio->buffer[5] = 0x00;
246 radio->buffer[6] = 0x00;
247 radio->buffer[7] = 0x08;
248
249 retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2),
250 (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT);
251
252 if (retval) {
253 mutex_unlock(&radio->lock);
254 return retval;
255 }
256
257 /* frequency is calculated from freq_send and placed in first 2 bytes */
258 radio->buffer[0] = (freq_send >> 8) & 0xff;
259 radio->buffer[1] = freq_send & 0xff;
260 radio->buffer[2] = 0x01;
261 radio->buffer[3] = 0x00;
262 radio->buffer[4] = 0x00;
263 /* 5 and 6 bytes of buffer already = 0x00 */
264 radio->buffer[7] = 0x00;
265
266 retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2),
267 (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT);
268
269 if (retval) {
270 mutex_unlock(&radio->lock);
271 return retval;
272 }
273
274 mutex_unlock(&radio->lock);
275
276 radio->stereo = 0;
277
278 return retval;
279}
280
281/* USB subsystem interface begins here */
282
283/* handle unplugging of the device, release data structures
284if nothing keeps us from doing it. If something is still
285keeping us busy, the release callback of v4l will take care
286of releasing it. */
287static void usb_amradio_disconnect(struct usb_interface *intf)
288{
289 struct amradio_device *radio = usb_get_intfdata(intf);
290
291 usb_set_intfdata(intf, NULL);
292
293 if (radio) {
294 video_unregister_device(radio->videodev);
295 radio->videodev = NULL;
296 if (radio->users) {
297 kfree(radio->buffer);
298 kfree(radio);
299 } else {
300 radio->removed = 1;
301 }
302 }
303}
304
305/* vidioc_querycap - query device capabilities */
306static int vidioc_querycap(struct file *file, void *priv,
307 struct v4l2_capability *v)
308{
309 strlcpy(v->driver, "radio-mr800", sizeof(v->driver));
310 strlcpy(v->card, "AverMedia MR 800 USB FM Radio", sizeof(v->card));
311 sprintf(v->bus_info, "USB");
312 v->version = RADIO_VERSION;
313 v->capabilities = V4L2_CAP_TUNER;
314 return 0;
315}
316
317/* vidioc_g_tuner - get tuner attributes */
318static int vidioc_g_tuner(struct file *file, void *priv,
319 struct v4l2_tuner *v)
320{
321 struct amradio_device *radio = video_get_drvdata(video_devdata(file));
322
323 if (v->index > 0)
324 return -EINVAL;
325
326/* TODO: Add function which look is signal stereo or not
327 * amradio_getstat(radio);
328 */
329 radio->stereo = -1;
330 strcpy(v->name, "FM");
331 v->type = V4L2_TUNER_RADIO;
332 v->rangelow = FREQ_MIN * FREQ_MUL;
333 v->rangehigh = FREQ_MAX * FREQ_MUL;
334 v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
335 v->capability = V4L2_TUNER_CAP_LOW;
336 if (radio->stereo)
337 v->audmode = V4L2_TUNER_MODE_STEREO;
338 else
339 v->audmode = V4L2_TUNER_MODE_MONO;
340 v->signal = 0xffff; /* Can't get the signal strength, sad.. */
341 v->afc = 0; /* Don't know what is this */
342 return 0;
343}
344
345/* vidioc_s_tuner - set tuner attributes */
346static int vidioc_s_tuner(struct file *file, void *priv,
347 struct v4l2_tuner *v)
348{
349 if (v->index > 0)
350 return -EINVAL;
351 return 0;
352}
353
354/* vidioc_s_frequency - set tuner radio frequency */
355static int vidioc_s_frequency(struct file *file, void *priv,
356 struct v4l2_frequency *f)
357{
358 struct amradio_device *radio = video_get_drvdata(video_devdata(file));
359
360 radio->curfreq = f->frequency;
361 if (amradio_setfreq(radio, radio->curfreq) < 0)
362 warn("Set frequency failed");
363 return 0;
364}
365
366/* vidioc_g_frequency - get tuner radio frequency */
367static int vidioc_g_frequency(struct file *file, void *priv,
368 struct v4l2_frequency *f)
369{
370 struct amradio_device *radio = video_get_drvdata(video_devdata(file));
371
372 f->type = V4L2_TUNER_RADIO;
373 f->frequency = radio->curfreq;
374 return 0;
375}
376
377/* vidioc_queryctrl - enumerate control items */
378static int vidioc_queryctrl(struct file *file, void *priv,
379 struct v4l2_queryctrl *qc)
380{
381 int i;
382
383 for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
384 if (qc->id && qc->id == radio_qctrl[i].id) {
385 memcpy(qc, &(radio_qctrl[i]),
386 sizeof(*qc));
387 return 0;
388 }
389 }
390 return -EINVAL;
391}
392
393/* vidioc_g_ctrl - get the value of a control */
394static int vidioc_g_ctrl(struct file *file, void *priv,
395 struct v4l2_control *ctrl)
396{
397 struct amradio_device *radio = video_get_drvdata(video_devdata(file));
398
399 switch (ctrl->id) {
400 case V4L2_CID_AUDIO_MUTE:
401 ctrl->value = radio->muted;
402 return 0;
403 }
404 return -EINVAL;
405}
406
407/* vidioc_s_ctrl - set the value of a control */
408static int vidioc_s_ctrl(struct file *file, void *priv,
409 struct v4l2_control *ctrl)
410{
411 struct amradio_device *radio = video_get_drvdata(video_devdata(file));
412
413 switch (ctrl->id) {
414 case V4L2_CID_AUDIO_MUTE:
415 if (ctrl->value) {
416 if (amradio_stop(radio) < 0) {
417 warn("amradio_stop() failed");
418 return -1;
419 }
420 } else {
421 if (amradio_start(radio) < 0) {
422 warn("amradio_start() failed");
423 return -1;
424 }
425 }
426 return 0;
427 }
428 return -EINVAL;
429}
430
431/* vidioc_g_audio - get audio attributes */
432static int vidioc_g_audio(struct file *file, void *priv,
433 struct v4l2_audio *a)
434{
435 if (a->index > 1)
436 return -EINVAL;
437
438 strcpy(a->name, "Radio");
439 a->capability = V4L2_AUDCAP_STEREO;
440 return 0;
441}
442
443/* vidioc_s_audio - set audio attributes */
444static int vidioc_s_audio(struct file *file, void *priv,
445 struct v4l2_audio *a)
446{
447 if (a->index != 0)
448 return -EINVAL;
449 return 0;
450}
451
452/* vidioc_g_input - get input */
453static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
454{
455 *i = 0;
456 return 0;
457}
458
459/* vidioc_s_input - set input */
460static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
461{
462 if (i != 0)
463 return -EINVAL;
464 return 0;
465}
466
467/* open device - amradio_start() and amradio_setfreq() */
468static int usb_amradio_open(struct inode *inode, struct file *file)
469{
470 struct amradio_device *radio = video_get_drvdata(video_devdata(file));
471
472 radio->users = 1;
473 radio->muted = 1;
474
475 if (amradio_start(radio) < 0) {
476 warn("Radio did not start up properly");
477 radio->users = 0;
478 return -EIO;
479 }
480 if (amradio_setfreq(radio, radio->curfreq) < 0)
481 warn("Set frequency failed");
482 return 0;
483}
484
485/*close device - free driver structures */
486static int usb_amradio_close(struct inode *inode, struct file *file)
487{
488 struct amradio_device *radio = video_get_drvdata(video_devdata(file));
489
490 if (!radio)
491 return -ENODEV;
492 radio->users = 0;
493 if (radio->removed) {
494 kfree(radio->buffer);
495 kfree(radio);
496 }
497 return 0;
498}
499
500/* Suspend device - stop device. Need to be checked and fixed */
501static int usb_amradio_suspend(struct usb_interface *intf, pm_message_t message)
502{
503 struct amradio_device *radio = usb_get_intfdata(intf);
504
505 if (amradio_stop(radio) < 0)
506 warn("amradio_stop() failed");
507
508 info("radio-mr800: Going into suspend..");
509
510 return 0;
511}
512
513/* Resume device - start device. Need to be checked and fixed */
514static int usb_amradio_resume(struct usb_interface *intf)
515{
516 struct amradio_device *radio = usb_get_intfdata(intf);
517
518 if (amradio_start(radio) < 0)
519 warn("amradio_start() failed");
520
521 info("radio-mr800: Coming out of suspend..");
522
523 return 0;
524}
525
526/* File system interface */
527static const struct file_operations usb_amradio_fops = {
528 .owner = THIS_MODULE,
529 .open = usb_amradio_open,
530 .release = usb_amradio_close,
531 .ioctl = video_ioctl2,
532#ifdef CONFIG_COMPAT
533 .compat_ioctl = v4l_compat_ioctl32,
534#endif
535 .llseek = no_llseek,
536};
537
538static const struct v4l2_ioctl_ops usb_amradio_ioctl_ops = {
539 .vidioc_querycap = vidioc_querycap,
540 .vidioc_g_tuner = vidioc_g_tuner,
541 .vidioc_s_tuner = vidioc_s_tuner,
542 .vidioc_g_frequency = vidioc_g_frequency,
543 .vidioc_s_frequency = vidioc_s_frequency,
544 .vidioc_queryctrl = vidioc_queryctrl,
545 .vidioc_g_ctrl = vidioc_g_ctrl,
546 .vidioc_s_ctrl = vidioc_s_ctrl,
547 .vidioc_g_audio = vidioc_g_audio,
548 .vidioc_s_audio = vidioc_s_audio,
549 .vidioc_g_input = vidioc_g_input,
550 .vidioc_s_input = vidioc_s_input,
551};
552
553/* V4L2 interface */
554static struct video_device amradio_videodev_template = {
555 .name = "AverMedia MR 800 USB FM Radio",
556 .fops = &usb_amradio_fops,
557 .ioctl_ops = &usb_amradio_ioctl_ops,
558 .release = video_device_release,
559};
560
561/* check if the device is present and register with v4l and
562usb if it is */
563static int usb_amradio_probe(struct usb_interface *intf,
564 const struct usb_device_id *id)
565{
566 struct amradio_device *radio;
567
568 radio = kmalloc(sizeof(struct amradio_device), GFP_KERNEL);
569
570 if (!(radio))
571 return -ENOMEM;
572
573 radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL);
574
575 if (!(radio->buffer)) {
576 kfree(radio);
577 return -ENOMEM;
578 }
579
580 radio->videodev = video_device_alloc();
581
582 if (!(radio->videodev)) {
583 kfree(radio->buffer);
584 kfree(radio);
585 return -ENOMEM;
586 }
587
588 memcpy(radio->videodev, &amradio_videodev_template,
589 sizeof(amradio_videodev_template));
590
591 radio->removed = 0;
592 radio->users = 0;
593 radio->usbdev = interface_to_usbdev(intf);
594 radio->curfreq = 95.16 * FREQ_MUL;
595
596 mutex_init(&radio->lock);
597
598 video_set_drvdata(radio->videodev, radio);
599 if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr)) {
600 warn("Could not register video device");
601 video_device_release(radio->videodev);
602 kfree(radio->buffer);
603 kfree(radio);
604 return -EIO;
605 }
606
607 usb_set_intfdata(intf, radio);
608 return 0;
609}
610
611static int __init amradio_init(void)
612{
613 int retval = usb_register(&usb_amradio_driver);
614
615 info(DRIVER_VERSION " " DRIVER_DESC);
616 if (retval)
617 err("usb_register failed. Error number %d", retval);
618 return retval;
619}
620
621static void __exit amradio_exit(void)
622{
623 usb_deregister(&usb_amradio_driver);
624}
625
626module_init(amradio_init);
627module_exit(amradio_exit);
628