diff options
author | Rusty Russell <rusty@rustcorp.com.au> | 2008-12-31 23:05:57 +1030 |
---|---|---|
committer | Rusty Russell <rusty@rustcorp.com.au> | 2008-12-31 23:05:57 +1030 |
commit | 2ca1a615835d9f4990f42102ab1f2ef434e7e89c (patch) | |
tree | 726cf3d5f29a6c66c44e4bd68e7ebed2fd83d059 /drivers/media/radio/dsbr100.c | |
parent | e12f0102ac81d660c9f801d0a0e10ccf4537a9de (diff) | |
parent | 6a94cb73064c952255336cc57731904174b2c58f (diff) |
Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
Conflicts:
arch/x86/kernel/io_apic.c
Diffstat (limited to 'drivers/media/radio/dsbr100.c')
-rw-r--r-- | drivers/media/radio/dsbr100.c | 381 |
1 files changed, 290 insertions, 91 deletions
diff --git a/drivers/media/radio/dsbr100.c b/drivers/media/radio/dsbr100.c index a5ca176a7b0..5474a22c1b2 100644 --- a/drivers/media/radio/dsbr100.c +++ b/drivers/media/radio/dsbr100.c @@ -1,5 +1,5 @@ -/* A driver for the D-Link DSB-R100 USB radio. The R100 plugs - into both the USB and an analog audio input, so this thing +/* A driver for the D-Link DSB-R100 USB radio and Gemtek USB Radio 21. + The device plugs into both the USB and an analog audio input, so this thing only deals with initialisation and frequency setting, the audio data has to be handled by a sound driver. @@ -33,6 +33,10 @@ History: + Version 0.44: + Add suspend/resume functions, fix unplug of device, + a lot of cleanups and fixes by Alexey Klimov <klimov.linux@gmail.com> + Version 0.43: Oliver Neukum: avoided DMA coherency issue @@ -93,8 +97,8 @@ */ #include <linux/version.h> /* for KERNEL_VERSION MACRO */ -#define DRIVER_VERSION "v0.41" -#define RADIO_VERSION KERNEL_VERSION(0,4,1) +#define DRIVER_VERSION "v0.44" +#define RADIO_VERSION KERNEL_VERSION(0, 4, 4) static struct v4l2_queryctrl radio_qctrl[] = { { @@ -104,7 +108,27 @@ static struct v4l2_queryctrl radio_qctrl[] = { .maximum = 1, .default_value = 1, .type = V4L2_CTRL_TYPE_BOOLEAN, - } + }, +/* HINT: the disabled controls are only here to satify kradio and such apps */ + { .id = V4L2_CID_AUDIO_VOLUME, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BALANCE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_BASS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_TREBLE, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, + { + .id = V4L2_CID_AUDIO_LOUDNESS, + .flags = V4L2_CTRL_FLAG_DISABLED, + }, }; #define DRIVER_AUTHOR "Markus Demleitner <msdemlei@tucana.harvard.edu>" @@ -125,12 +149,16 @@ devices, that would be 76 and 91. */ #define FREQ_MAX 108.0 #define FREQ_MUL 16000 +#define videodev_to_radio(d) container_of(d, struct dsbr100_device, videodev) static int usb_dsbr100_probe(struct usb_interface *intf, const struct usb_device_id *id); static void usb_dsbr100_disconnect(struct usb_interface *intf); static int usb_dsbr100_open(struct inode *inode, struct file *file); static int usb_dsbr100_close(struct inode *inode, struct file *file); +static int usb_dsbr100_suspend(struct usb_interface *intf, + pm_message_t message); +static int usb_dsbr100_resume(struct usb_interface *intf); static int radio_nr = -1; module_param(radio_nr, int, 0); @@ -138,8 +166,9 @@ module_param(radio_nr, int, 0); /* Data for one (physical) device */ struct dsbr100_device { struct usb_device *usbdev; - struct video_device *videodev; + struct video_device videodev; u8 *transfer_buffer; + struct mutex lock; /* buffer locking */ int curfreq; int stereo; int users; @@ -147,7 +176,6 @@ struct dsbr100_device { int muted; }; - static struct usb_device_id usb_dsbr100_device_table [] = { { USB_DEVICE(DSB100_VENDOR, DSB100_PRODUCT) }, { } /* Terminating entry */ @@ -157,10 +185,14 @@ MODULE_DEVICE_TABLE (usb, usb_dsbr100_device_table); /* USB subsystem interface */ static struct usb_driver usb_dsbr100_driver = { - .name = "dsbr100", - .probe = usb_dsbr100_probe, - .disconnect = usb_dsbr100_disconnect, - .id_table = usb_dsbr100_device_table, + .name = "dsbr100", + .probe = usb_dsbr100_probe, + .disconnect = usb_dsbr100_disconnect, + .id_table = usb_dsbr100_device_table, + .suspend = usb_dsbr100_suspend, + .resume = usb_dsbr100_resume, + .reset_resume = usb_dsbr100_resume, + .supports_autosuspend = 0, }; /* Low-level device interface begins here */ @@ -168,95 +200,190 @@ static struct usb_driver usb_dsbr100_driver = { /* switch on radio */ static int dsbr100_start(struct dsbr100_device *radio) { - if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), - USB_REQ_GET_STATUS, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x00, 0xC7, radio->transfer_buffer, 8, 300) < 0 || - usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), - DSB100_ONOFF, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x01, 0x00, radio->transfer_buffer, 8, 300) < 0) - return -1; - radio->muted=0; + int retval; + int request; + + mutex_lock(&radio->lock); + + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + USB_REQ_GET_STATUS, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x00, 0xC7, radio->transfer_buffer, 8, 300); + + if (retval < 0) { + request = USB_REQ_GET_STATUS; + goto usb_control_msg_failed; + } + + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + DSB100_ONOFF, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x01, 0x00, radio->transfer_buffer, 8, 300); + + if (retval < 0) { + request = DSB100_ONOFF; + goto usb_control_msg_failed; + } + + radio->muted = 0; + mutex_unlock(&radio->lock); return (radio->transfer_buffer)[0]; -} +usb_control_msg_failed: + mutex_unlock(&radio->lock); + dev_err(&radio->usbdev->dev, + "%s - usb_control_msg returned %i, request %i\n", + __func__, retval, request); + return retval; + +} /* switch off radio */ static int dsbr100_stop(struct dsbr100_device *radio) { - if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), - USB_REQ_GET_STATUS, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x16, 0x1C, radio->transfer_buffer, 8, 300) < 0 || - usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), - DSB100_ONOFF, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x00, 0x00, radio->transfer_buffer, 8, 300) < 0) - return -1; - radio->muted=1; + int retval; + int request; + + mutex_lock(&radio->lock); + + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + USB_REQ_GET_STATUS, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x16, 0x1C, radio->transfer_buffer, 8, 300); + + if (retval < 0) { + request = USB_REQ_GET_STATUS; + goto usb_control_msg_failed; + } + + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + DSB100_ONOFF, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x00, 0x00, radio->transfer_buffer, 8, 300); + + if (retval < 0) { + request = DSB100_ONOFF; + goto usb_control_msg_failed; + } + + radio->muted = 1; + mutex_unlock(&radio->lock); return (radio->transfer_buffer)[0]; + +usb_control_msg_failed: + mutex_unlock(&radio->lock); + dev_err(&radio->usbdev->dev, + "%s - usb_control_msg returned %i, request %i\n", + __func__, retval, request); + return retval; + } /* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */ static int dsbr100_setfreq(struct dsbr100_device *radio, int freq) { + int retval; + int request; + freq = (freq / 16 * 80) / 1000 + 856; - if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), - DSB100_TUNE, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - (freq >> 8) & 0x00ff, freq & 0xff, - radio->transfer_buffer, 8, 300) < 0 || - usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), - USB_REQ_GET_STATUS, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x96, 0xB7, radio->transfer_buffer, 8, 300) < 0 || - usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), - USB_REQ_GET_STATUS, - USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x00, 0x24, radio->transfer_buffer, 8, 300) < 0) { - radio->stereo = -1; - return -1; + mutex_lock(&radio->lock); + + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + DSB100_TUNE, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + (freq >> 8) & 0x00ff, freq & 0xff, + radio->transfer_buffer, 8, 300); + + if (retval < 0) { + request = DSB100_TUNE; + goto usb_control_msg_failed; } + + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + USB_REQ_GET_STATUS, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x96, 0xB7, radio->transfer_buffer, 8, 300); + + if (retval < 0) { + request = USB_REQ_GET_STATUS; + goto usb_control_msg_failed; + } + + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), + USB_REQ_GET_STATUS, + USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, + 0x00, 0x24, radio->transfer_buffer, 8, 300); + + if (retval < 0) { + request = USB_REQ_GET_STATUS; + goto usb_control_msg_failed; + } + radio->stereo = !((radio->transfer_buffer)[0] & 0x01); + mutex_unlock(&radio->lock); return (radio->transfer_buffer)[0]; + +usb_control_msg_failed: + radio->stereo = -1; + mutex_unlock(&radio->lock); + dev_err(&radio->usbdev->dev, + "%s - usb_control_msg returned %i, request %i\n", + __func__, retval, request); + return retval; } /* return the device status. This is, in effect, just whether it sees a stereo signal or not. Pity. */ static void dsbr100_getstat(struct dsbr100_device *radio) { - if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0), + int retval; + + mutex_lock(&radio->lock); + + retval = usb_control_msg(radio->usbdev, + usb_rcvctrlpipe(radio->usbdev, 0), USB_REQ_GET_STATUS, USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN, - 0x00 , 0x24, radio->transfer_buffer, 8, 300) < 0) + 0x00 , 0x24, radio->transfer_buffer, 8, 300); + + if (retval < 0) { radio->stereo = -1; - else + dev_err(&radio->usbdev->dev, + "%s - usb_control_msg returned %i, request %i\n", + __func__, retval, USB_REQ_GET_STATUS); + } else { radio->stereo = !(radio->transfer_buffer[0] & 0x01); -} + } + mutex_unlock(&radio->lock); +} /* 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 of the device. + * We call video_unregister_device in any case. + * The last function called in this procedure is + * usb_dsbr100_video_device_release + */ static void usb_dsbr100_disconnect(struct usb_interface *intf) { struct dsbr100_device *radio = usb_get_intfdata(intf); usb_set_intfdata (intf, NULL); - if (radio) { - video_unregister_device(radio->videodev); - radio->videodev = NULL; - if (radio->users) { - kfree(radio->transfer_buffer); - kfree(radio); - } else { - radio->removed = 1; - } - } + + mutex_lock(&radio->lock); + radio->removed = 1; + mutex_unlock(&radio->lock); + + video_unregister_device(&radio->videodev); } @@ -276,6 +403,10 @@ static int vidioc_g_tuner(struct file *file, void *priv, { struct dsbr100_device *radio = video_drvdata(file); + /* safety check */ + if (radio->removed) + return -EIO; + if (v->index > 0) return -EINVAL; @@ -297,6 +428,12 @@ static int vidioc_g_tuner(struct file *file, void *priv, static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *v) { + struct dsbr100_device *radio = video_drvdata(file); + + /* safety check */ + if (radio->removed) + return -EIO; + if (v->index > 0) return -EINVAL; @@ -307,9 +444,15 @@ static int vidioc_s_frequency(struct file *file, void *priv, struct v4l2_frequency *f) { struct dsbr100_device *radio = video_drvdata(file); + int retval; + + /* safety check */ + if (radio->removed) + return -EIO; radio->curfreq = f->frequency; - if (dsbr100_setfreq(radio, radio->curfreq) == -1) + retval = dsbr100_setfreq(radio, radio->curfreq); + if (retval < 0) dev_warn(&radio->usbdev->dev, "Set frequency failed\n"); return 0; } @@ -319,6 +462,10 @@ static int vidioc_g_frequency(struct file *file, void *priv, { struct dsbr100_device *radio = video_drvdata(file); + /* safety check */ + if (radio->removed) + return -EIO; + f->type = V4L2_TUNER_RADIO; f->frequency = radio->curfreq; return 0; @@ -343,6 +490,10 @@ static int vidioc_g_ctrl(struct file *file, void *priv, { struct dsbr100_device *radio = video_drvdata(file); + /* safety check */ + if (radio->removed) + return -EIO; + switch (ctrl->id) { case V4L2_CID_AUDIO_MUTE: ctrl->value = radio->muted; @@ -355,17 +506,24 @@ static int vidioc_s_ctrl(struct file *file, void *priv, struct v4l2_control *ctrl) { struct dsbr100_device *radio = video_drvdata(file); + int retval; + + /* safety check */ + if (radio->removed) + return -EIO; switch (ctrl->id) { case V4L2_CID_AUDIO_MUTE: if (ctrl->value) { - if (dsbr100_stop(radio) == -1) { + retval = dsbr100_stop(radio); + if (retval < 0) { dev_warn(&radio->usbdev->dev, "Radio did not respond properly\n"); return -EBUSY; } } else { - if (dsbr100_start(radio) == -1) { + retval = dsbr100_start(radio); + if (retval < 0) { dev_warn(&radio->usbdev->dev, "Radio did not respond properly\n"); return -EBUSY; @@ -417,7 +575,8 @@ static int usb_dsbr100_open(struct inode *inode, struct file *file) radio->users = 1; radio->muted = 1; - if (dsbr100_start(radio) < 0) { + retval = dsbr100_start(radio); + if (retval < 0) { dev_warn(&radio->usbdev->dev, "Radio did not start up properly\n"); radio->users = 0; @@ -426,9 +585,9 @@ static int usb_dsbr100_open(struct inode *inode, struct file *file) } retval = dsbr100_setfreq(radio, radio->curfreq); - - if (retval == -1) - printk(KERN_WARNING KBUILD_MODNAME ": Set frequency failed\n"); + if (retval < 0) + dev_warn(&radio->usbdev->dev, + "set frequency failed\n"); unlock_kernel(); return 0; @@ -437,17 +596,62 @@ static int usb_dsbr100_open(struct inode *inode, struct file *file) static int usb_dsbr100_close(struct inode *inode, struct file *file) { struct dsbr100_device *radio = video_drvdata(file); + int retval; if (!radio) return -ENODEV; + radio->users = 0; - if (radio->removed) { - kfree(radio->transfer_buffer); - kfree(radio); + if (!radio->removed) { + retval = dsbr100_stop(radio); + if (retval < 0) { + dev_warn(&radio->usbdev->dev, + "dsbr100_stop failed\n"); + } + } return 0; } +/* Suspend device - stop device. */ +static int usb_dsbr100_suspend(struct usb_interface *intf, pm_message_t message) +{ + struct dsbr100_device *radio = usb_get_intfdata(intf); + int retval; + + retval = dsbr100_stop(radio); + if (retval < 0) + dev_warn(&intf->dev, "dsbr100_stop failed\n"); + + dev_info(&intf->dev, "going into suspend..\n"); + + return 0; +} + +/* Resume device - start device. */ +static int usb_dsbr100_resume(struct usb_interface *intf) +{ + struct dsbr100_device *radio = usb_get_intfdata(intf); + int retval; + + retval = dsbr100_start(radio); + if (retval < 0) + dev_warn(&intf->dev, "dsbr100_start failed\n"); + + dev_info(&intf->dev, "coming out of suspend..\n"); + + return 0; +} + +/* free data structures */ +static void usb_dsbr100_video_device_release(struct video_device *videodev) +{ + struct dsbr100_device *radio = videodev_to_radio(videodev); + + kfree(radio->transfer_buffer); + kfree(radio); +} + /* File system interface */ static const struct file_operations usb_dsbr100_fops = { .owner = THIS_MODULE, @@ -476,19 +680,19 @@ static const struct v4l2_ioctl_ops usb_dsbr100_ioctl_ops = { }; /* V4L2 interface */ -static struct video_device dsbr100_videodev_template = { +static struct video_device dsbr100_videodev_data = { .name = "D-Link DSB-R 100", .fops = &usb_dsbr100_fops, .ioctl_ops = &usb_dsbr100_ioctl_ops, - .release = video_device_release, + .release = usb_dsbr100_video_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_dsbr100_probe(struct usb_interface *intf, const struct usb_device_id *id) { struct dsbr100_device *radio; + int retval; radio = kmalloc(sizeof(struct dsbr100_device), GFP_KERNEL); @@ -501,23 +705,18 @@ static int usb_dsbr100_probe(struct usb_interface *intf, kfree(radio); return -ENOMEM; } - radio->videodev = video_device_alloc(); - if (!(radio->videodev)) { - kfree(radio->transfer_buffer); - kfree(radio); - return -ENOMEM; - } - memcpy(radio->videodev, &dsbr100_videodev_template, - sizeof(dsbr100_videodev_template)); + mutex_init(&radio->lock); + radio->videodev = dsbr100_videodev_data; + radio->removed = 0; radio->users = 0; radio->usbdev = interface_to_usbdev(intf); radio->curfreq = FREQ_MIN * FREQ_MUL; - video_set_drvdata(radio->videodev, radio); - if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr) < 0) { - dev_warn(&intf->dev, "Could not register video device\n"); - video_device_release(radio->videodev); + video_set_drvdata(&radio->videodev, radio); + retval = video_register_device(&radio->videodev, VFL_TYPE_RADIO, radio_nr); + if (retval < 0) { + dev_err(&intf->dev, "couldn't register video device\n"); kfree(radio->transfer_buffer); kfree(radio); return -EIO; |