diff options
Diffstat (limited to 'drivers/media/radio/radio-mr800.c')
-rw-r--r-- | drivers/media/radio/radio-mr800.c | 221 |
1 files changed, 152 insertions, 69 deletions
diff --git a/drivers/media/radio/radio-mr800.c b/drivers/media/radio/radio-mr800.c index fdfc7bf86b9..ded25bfb366 100644 --- a/drivers/media/radio/radio-mr800.c +++ b/drivers/media/radio/radio-mr800.c @@ -22,7 +22,7 @@ */ /* - * Big thanks to authors of dsbr100.c and radio-si470x.c + * Big thanks to authors and contributors of dsbr100.c and radio-si470x.c * * When work was looked pretty good, i discover this: * http://av-usbradio.sourceforge.net/index.php @@ -30,18 +30,23 @@ * Latest release of theirs project was in 2005. * Probably, this driver could be improved trough using their * achievements (specifications given). - * So, we have smth to begin with. + * Also, Faidon Liambotis <paravoid@debian.org> wrote nice driver for this radio + * in 2007. He allowed to use his driver to improve current mr800 radio driver. + * http://kerneltrap.org/mailarchive/linux-usb-devel/2007/10/11/342492 * - * History: * Version 0.01: First working version. * It's required to blacklist AverMedia USB Radio * in usbhid/hid-quirks.c + * Version 0.10: A lot of cleanups and fixes: unpluging the device, + * few mutex locks were added, codinstyle issues, etc. + * Added stereo support. Thanks to + * Douglas Schilling Landgraf <dougsland@gmail.com> and + * David Ellingsworth <david@identd.dyndns.org> + * for discussion, help and support. * * Many things to do: * - Correct power managment of device (suspend & resume) - * - Make x86 independance (little-endian and big-endian stuff) * - Add code for scanning and smooth tuning - * - Checked and add stereo&mono stuff * - Add code for sensitivity value * - Correct mistakes * - In Japan another FREQ_MIN and FREQ_MAX @@ -62,8 +67,8 @@ /* driver and module definitions */ #define DRIVER_AUTHOR "Alexey Klimov <klimov.linux@gmail.com>" #define DRIVER_DESC "AverMedia MR 800 USB FM radio driver" -#define DRIVER_VERSION "0.01" -#define RADIO_VERSION KERNEL_VERSION(0, 0, 1) +#define DRIVER_VERSION "0.10" +#define RADIO_VERSION KERNEL_VERSION(0, 1, 0) MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); @@ -87,6 +92,22 @@ devices, that would be 76 and 91. */ #define FREQ_MAX 108.0 #define FREQ_MUL 16000 +/* + * Commands that device should understand + * List isnt full and will be updated with implementation of new functions + */ +#define AMRADIO_SET_FREQ 0xa4 +#define AMRADIO_SET_MUTE 0xab +#define AMRADIO_SET_MONO 0xae + +/* Comfortable defines for amradio_set_mute */ +#define AMRADIO_START 0x00 +#define AMRADIO_STOP 0x01 + +/* Comfortable defines for amradio_set_stereo */ +#define WANT_STEREO 0x00 +#define WANT_MONO 0x01 + /* module parameter */ static int radio_nr = -1; module_param(radio_nr, int, 0); @@ -169,43 +190,48 @@ static struct usb_driver usb_amradio_driver = { .supports_autosuspend = 0, }; -/* switch on radio. Send 8 bytes to device. */ -static int amradio_start(struct amradio_device *radio) +/* switch on/off the radio. Send 8 bytes to device */ +static int amradio_set_mute(struct amradio_device *radio, char argument) { int retval; int size; + /* safety check */ + if (radio->removed) + return -EIO; + mutex_lock(&radio->lock); radio->buffer[0] = 0x00; radio->buffer[1] = 0x55; radio->buffer[2] = 0xaa; radio->buffer[3] = 0x00; - radio->buffer[4] = 0xab; - radio->buffer[5] = 0x00; + radio->buffer[4] = AMRADIO_SET_MUTE; + radio->buffer[5] = argument; radio->buffer[6] = 0x00; radio->buffer[7] = 0x00; retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); - if (retval) { + if (retval < 0 || size != BUFFER_LENGTH) { mutex_unlock(&radio->lock); return retval; } - radio->muted = 0; + radio->muted = argument; mutex_unlock(&radio->lock); return retval; } -/* switch off radio */ -static int amradio_stop(struct amradio_device *radio) +/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ +static int amradio_setfreq(struct amradio_device *radio, int freq) { int retval; int size; + unsigned short freq_send = 0x10 + (freq >> 3) / 25; /* safety check */ if (radio->removed) @@ -216,33 +242,46 @@ static int amradio_stop(struct amradio_device *radio) radio->buffer[0] = 0x00; radio->buffer[1] = 0x55; radio->buffer[2] = 0xaa; - radio->buffer[3] = 0x00; - radio->buffer[4] = 0xab; - radio->buffer[5] = 0x01; + radio->buffer[3] = 0x03; + radio->buffer[4] = AMRADIO_SET_FREQ; + radio->buffer[5] = 0x00; radio->buffer[6] = 0x00; - radio->buffer[7] = 0x00; + radio->buffer[7] = 0x08; retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); - if (retval) { + if (retval < 0 || size != BUFFER_LENGTH) { mutex_unlock(&radio->lock); return retval; } - radio->muted = 1; + /* frequency is calculated from freq_send and placed in first 2 bytes */ + radio->buffer[0] = (freq_send >> 8) & 0xff; + radio->buffer[1] = freq_send & 0xff; + radio->buffer[2] = 0x01; + radio->buffer[3] = 0x00; + radio->buffer[4] = 0x00; + /* 5 and 6 bytes of buffer already = 0x00 */ + radio->buffer[7] = 0x00; + + retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), + (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); + + if (retval < 0 || size != BUFFER_LENGTH) { + mutex_unlock(&radio->lock); + return retval; + } mutex_unlock(&radio->lock); return retval; } -/* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ -static int amradio_setfreq(struct amradio_device *radio, int freq) +static int amradio_set_stereo(struct amradio_device *radio, char argument) { int retval; int size; - unsigned short freq_send = 0x13 + (freq >> 3) / 25; /* safety check */ if (radio->removed) @@ -253,50 +292,33 @@ static int amradio_setfreq(struct amradio_device *radio, int freq) radio->buffer[0] = 0x00; radio->buffer[1] = 0x55; radio->buffer[2] = 0xaa; - radio->buffer[3] = 0x03; - radio->buffer[4] = 0xa4; - radio->buffer[5] = 0x00; - radio->buffer[6] = 0x00; - radio->buffer[7] = 0x08; - - retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), - (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); - - if (retval) { - mutex_unlock(&radio->lock); - return retval; - } - - /* frequency is calculated from freq_send and placed in first 2 bytes */ - radio->buffer[0] = (freq_send >> 8) & 0xff; - radio->buffer[1] = freq_send & 0xff; - radio->buffer[2] = 0x01; radio->buffer[3] = 0x00; - radio->buffer[4] = 0x00; - /* 5 and 6 bytes of buffer already = 0x00 */ + radio->buffer[4] = AMRADIO_SET_MONO; + radio->buffer[5] = argument; + radio->buffer[6] = 0x00; radio->buffer[7] = 0x00; retval = usb_bulk_msg(radio->usbdev, usb_sndintpipe(radio->usbdev, 2), (void *) (radio->buffer), BUFFER_LENGTH, &size, USB_TIMEOUT); - if (retval) { + if (retval < 0 || size != BUFFER_LENGTH) { + radio->stereo = -1; mutex_unlock(&radio->lock); return retval; } - radio->stereo = 0; + radio->stereo = 1; mutex_unlock(&radio->lock); return retval; } -/* USB subsystem interface begins here */ - -/* handle unplugging of the device, release data structures -if nothing keeps us from doing it. If something is still -keeping us busy, the release callback of v4l will take care -of releasing it. */ +/* Handle unplugging the device. + * We call video_unregister_device in any case. + * The last function called in this procedure is + * usb_amradio_device_release. + */ static void usb_amradio_disconnect(struct usb_interface *intf) { struct amradio_device *radio = usb_get_intfdata(intf); @@ -313,9 +335,11 @@ static void usb_amradio_disconnect(struct usb_interface *intf) static int vidioc_querycap(struct file *file, void *priv, struct v4l2_capability *v) { + struct amradio_device *radio = video_drvdata(file); + strlcpy(v->driver, "radio-mr800", sizeof(v->driver)); strlcpy(v->card, "AverMedia MR 800 USB FM Radio", sizeof(v->card)); - sprintf(v->bus_info, "USB"); + usb_make_path(radio->usbdev, v->bus_info, sizeof(v->bus_info)); v->version = RADIO_VERSION; v->capabilities = V4L2_CAP_TUNER; return 0; @@ -326,6 +350,7 @@ static int vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *v) { struct amradio_device *radio = video_get_drvdata(video_devdata(file)); + int retval; /* safety check */ if (radio->removed) @@ -337,7 +362,16 @@ static int vidioc_g_tuner(struct file *file, void *priv, /* TODO: Add function which look is signal stereo or not * amradio_getstat(radio); */ - radio->stereo = -1; + +/* we call amradio_set_stereo to set radio->stereo + * Honestly, amradio_getstat should cover this in future and + * amradio_set_stereo shouldn't be here + */ + retval = amradio_set_stereo(radio, WANT_STEREO); + if (retval < 0) + amradio_dev_warn(&radio->videodev->dev, + "set stereo failed\n"); + strcpy(v->name, "FM"); v->type = V4L2_TUNER_RADIO; v->rangelow = FREQ_MIN * FREQ_MUL; @@ -358,6 +392,7 @@ static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *v) { struct amradio_device *radio = video_get_drvdata(video_devdata(file)); + int retval; /* safety check */ if (radio->removed) @@ -365,6 +400,25 @@ static int vidioc_s_tuner(struct file *file, void *priv, if (v->index > 0) return -EINVAL; + + /* mono/stereo selector */ + switch (v->audmode) { + case V4L2_TUNER_MODE_MONO: + retval = amradio_set_stereo(radio, WANT_MONO); + if (retval < 0) + amradio_dev_warn(&radio->videodev->dev, + "set mono failed\n"); + break; + case V4L2_TUNER_MODE_STEREO: + retval = amradio_set_stereo(radio, WANT_STEREO); + if (retval < 0) + amradio_dev_warn(&radio->videodev->dev, + "set stereo failed\n"); + break; + default: + return -EINVAL; + } + return 0; } @@ -373,13 +427,18 @@ static int vidioc_s_frequency(struct file *file, void *priv, struct v4l2_frequency *f) { struct amradio_device *radio = video_get_drvdata(video_devdata(file)); + int retval; /* safety check */ if (radio->removed) return -EIO; + mutex_lock(&radio->lock); radio->curfreq = f->frequency; - if (amradio_setfreq(radio, radio->curfreq) < 0) + mutex_unlock(&radio->lock); + + retval = amradio_setfreq(radio, radio->curfreq); + if (retval < 0) amradio_dev_warn(&radio->videodev->dev, "set frequency failed\n"); return 0; @@ -438,6 +497,7 @@ static int vidioc_s_ctrl(struct file *file, void *priv, struct v4l2_control *ctrl) { struct amradio_device *radio = video_get_drvdata(video_devdata(file)); + int retval; /* safety check */ if (radio->removed) @@ -446,13 +506,15 @@ static int vidioc_s_ctrl(struct file *file, void *priv, switch (ctrl->id) { case V4L2_CID_AUDIO_MUTE: if (ctrl->value) { - if (amradio_stop(radio) < 0) { + retval = amradio_set_mute(radio, AMRADIO_STOP); + if (retval < 0) { amradio_dev_warn(&radio->videodev->dev, "amradio_stop failed\n"); return -1; } } else { - if (amradio_start(radio) < 0) { + retval = amradio_set_mute(radio, AMRADIO_START); + if (retval < 0) { amradio_dev_warn(&radio->videodev->dev, "amradio_start failed\n"); return -1; @@ -503,20 +565,29 @@ static int vidioc_s_input(struct file *filp, void *priv, unsigned int i) static int usb_amradio_open(struct file *file) { struct amradio_device *radio = video_get_drvdata(video_devdata(file)); + int retval; lock_kernel(); radio->users = 1; radio->muted = 1; - if (amradio_start(radio) < 0) { + retval = amradio_set_mute(radio, AMRADIO_START); + if (retval < 0) { amradio_dev_warn(&radio->videodev->dev, "radio did not start up properly\n"); radio->users = 0; unlock_kernel(); return -EIO; } - if (amradio_setfreq(radio, radio->curfreq) < 0) + + retval = amradio_set_stereo(radio, WANT_STEREO); + if (retval < 0) + amradio_dev_warn(&radio->videodev->dev, + "set stereo failed\n"); + + retval = amradio_setfreq(radio, radio->curfreq); + if (retval < 0) amradio_dev_warn(&radio->videodev->dev, "set frequency failed\n"); @@ -533,10 +604,12 @@ static int usb_amradio_close(struct file *file) if (!radio) return -ENODEV; + mutex_lock(&radio->lock); radio->users = 0; + mutex_unlock(&radio->lock); if (!radio->removed) { - retval = amradio_stop(radio); + retval = amradio_set_mute(radio, AMRADIO_STOP); if (retval < 0) amradio_dev_warn(&radio->videodev->dev, "amradio_stop failed\n"); @@ -549,8 +622,10 @@ static int usb_amradio_close(struct file *file) static int usb_amradio_suspend(struct usb_interface *intf, pm_message_t message) { struct amradio_device *radio = usb_get_intfdata(intf); + int retval; - if (amradio_stop(radio) < 0) + retval = amradio_set_mute(radio, AMRADIO_STOP); + if (retval < 0) dev_warn(&intf->dev, "amradio_stop failed\n"); dev_info(&intf->dev, "going into suspend..\n"); @@ -562,8 +637,10 @@ static int usb_amradio_suspend(struct usb_interface *intf, pm_message_t message) static int usb_amradio_resume(struct usb_interface *intf) { struct amradio_device *radio = usb_get_intfdata(intf); + int retval; - if (amradio_start(radio) < 0) + retval = amradio_set_mute(radio, AMRADIO_START); + if (retval < 0) dev_warn(&intf->dev, "amradio_start failed\n"); dev_info(&intf->dev, "coming out of suspend..\n"); @@ -614,28 +691,32 @@ static struct video_device amradio_videodev_template = { .release = usb_amradio_device_release, }; -/* check if the device is present and register with v4l and -usb if it is */ +/* check if the device is present and register with v4l and usb if it is */ static int usb_amradio_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct amradio_device *radio; + int retval; radio = kmalloc(sizeof(struct amradio_device), GFP_KERNEL); - if (!(radio)) + if (!radio) { + dev_err(&intf->dev, "kmalloc for amradio_device failed\n"); return -ENOMEM; + } radio->buffer = kmalloc(BUFFER_LENGTH, GFP_KERNEL); - if (!(radio->buffer)) { + if (!radio->buffer) { + dev_err(&intf->dev, "kmalloc for radio->buffer failed\n"); kfree(radio); return -ENOMEM; } radio->videodev = video_device_alloc(); - if (!(radio->videodev)) { + if (!radio->videodev) { + dev_err(&intf->dev, "video_device_alloc failed\n"); kfree(radio->buffer); kfree(radio); return -ENOMEM; @@ -648,12 +729,14 @@ static int usb_amradio_probe(struct usb_interface *intf, radio->users = 0; radio->usbdev = interface_to_usbdev(intf); radio->curfreq = 95.16 * FREQ_MUL; + radio->stereo = -1; mutex_init(&radio->lock); video_set_drvdata(radio->videodev, radio); - if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr)) { - dev_warn(&intf->dev, "could not register video device\n"); + retval = video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr); + if (retval < 0) { + dev_err(&intf->dev, "could not register video device\n"); video_device_release(radio->videodev); kfree(radio->buffer); kfree(radio); |