xref: /openbmc/linux/drivers/media/radio/dsbr100.c (revision 54b7b0de)
17fb65297SMauro Carvalho Chehab /* A driver for the D-Link DSB-R100 USB radio.  The R100 plugs
27fb65297SMauro Carvalho Chehab  into both the USB and an analog audio input, so this thing
37fb65297SMauro Carvalho Chehab  only deals with initialisation and frequency setting, the
47fb65297SMauro Carvalho Chehab  audio data has to be handled by a sound driver.
57fb65297SMauro Carvalho Chehab 
67fb65297SMauro Carvalho Chehab  Major issue: I can't find out where the device reports the signal
77fb65297SMauro Carvalho Chehab  strength, and indeed the windows software appearantly just looks
87fb65297SMauro Carvalho Chehab  at the stereo indicator as well.  So, scanning will only find
97fb65297SMauro Carvalho Chehab  stereo stations.  Sad, but I can't help it.
107fb65297SMauro Carvalho Chehab 
117fb65297SMauro Carvalho Chehab  Also, the windows program sends oodles of messages over to the
127fb65297SMauro Carvalho Chehab  device, and I couldn't figure out their meaning.  My suspicion
137fb65297SMauro Carvalho Chehab  is that they don't have any:-)
147fb65297SMauro Carvalho Chehab 
157fb65297SMauro Carvalho Chehab  You might find some interesting stuff about this module at
167fb65297SMauro Carvalho Chehab  http://unimut.fsk.uni-heidelberg.de/unimut/demi/dsbr
177fb65297SMauro Carvalho Chehab 
187fb65297SMauro Carvalho Chehab  Copyright (c) 2000 Markus Demleitner <msdemlei@cl.uni-heidelberg.de>
197fb65297SMauro Carvalho Chehab 
207fb65297SMauro Carvalho Chehab  This program is free software; you can redistribute it and/or modify
217fb65297SMauro Carvalho Chehab  it under the terms of the GNU General Public License as published by
227fb65297SMauro Carvalho Chehab  the Free Software Foundation; either version 2 of the License, or
237fb65297SMauro Carvalho Chehab  (at your option) any later version.
247fb65297SMauro Carvalho Chehab 
257fb65297SMauro Carvalho Chehab  This program is distributed in the hope that it will be useful,
267fb65297SMauro Carvalho Chehab  but WITHOUT ANY WARRANTY; without even the implied warranty of
277fb65297SMauro Carvalho Chehab  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
287fb65297SMauro Carvalho Chehab  GNU General Public License for more details.
297fb65297SMauro Carvalho Chehab 
307fb65297SMauro Carvalho Chehab  You should have received a copy of the GNU General Public License
317fb65297SMauro Carvalho Chehab  along with this program; if not, write to the Free Software
327fb65297SMauro Carvalho Chehab  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
337fb65297SMauro Carvalho Chehab 
347fb65297SMauro Carvalho Chehab  History:
357fb65297SMauro Carvalho Chehab 
36863c86ddSOliver Neukum  Version 0.43:
37863c86ddSOliver Neukum 	Oliver Neukum: avoided DMA coherency issue
38863c86ddSOliver Neukum 
397002a4f3SDouglas Landgraf  Version 0.42:
407002a4f3SDouglas Landgraf 	Converted dsbr100 to use video_ioctl2
417002a4f3SDouglas Landgraf 	by Douglas Landgraf <dougsland@gmail.com>
427002a4f3SDouglas Landgraf 
435aff308cSAlan Cox  Version 0.41-ac1:
445aff308cSAlan Cox 	Alan Cox: Some cleanups and fixes
455aff308cSAlan Cox 
465aff308cSAlan Cox  Version 0.41:
475aff308cSAlan Cox 	Converted to V4L2 API by Mauro Carvalho Chehab <mchehab@infradead.org>
485aff308cSAlan Cox 
497fb65297SMauro Carvalho Chehab  Version 0.40:
507fb65297SMauro Carvalho Chehab 	Markus: Updates for 2.6.x kernels, code layout changes, name sanitizing
517fb65297SMauro Carvalho Chehab 
527fb65297SMauro Carvalho Chehab  Version 0.30:
537fb65297SMauro Carvalho Chehab 	Markus: Updates for 2.5.x kernel and more ISO compliant source
547fb65297SMauro Carvalho Chehab 
557fb65297SMauro Carvalho Chehab  Version 0.25:
567fb65297SMauro Carvalho Chehab 	PSL and Markus: Cleanup, radio now doesn't stop on device close
577fb65297SMauro Carvalho Chehab 
587fb65297SMauro Carvalho Chehab  Version 0.24:
597fb65297SMauro Carvalho Chehab 	Markus: Hope I got these silly VIDEO_TUNER_LOW issues finally
607fb65297SMauro Carvalho Chehab 	right.  Some minor cleanup, improved standalone compilation
617fb65297SMauro Carvalho Chehab 
627fb65297SMauro Carvalho Chehab  Version 0.23:
637fb65297SMauro Carvalho Chehab 	Markus: Sign extension bug fixed by declaring transfer_buffer unsigned
647fb65297SMauro Carvalho Chehab 
657fb65297SMauro Carvalho Chehab  Version 0.22:
667fb65297SMauro Carvalho Chehab 	Markus: Some (brown bag) cleanup in what VIDIOCSTUNER returns,
677fb65297SMauro Carvalho Chehab 	thanks to Mike Cox for pointing the problem out.
687fb65297SMauro Carvalho Chehab 
697fb65297SMauro Carvalho Chehab  Version 0.21:
707fb65297SMauro Carvalho Chehab 	Markus: Minor cleanup, warnings if something goes wrong, lame attempt
717fb65297SMauro Carvalho Chehab 	to adhere to Documentation/CodingStyle
727fb65297SMauro Carvalho Chehab 
737fb65297SMauro Carvalho Chehab  Version 0.2:
747fb65297SMauro Carvalho Chehab 	Brad Hards <bradh@dynamite.com.au>: Fixes to make it work as non-module
757fb65297SMauro Carvalho Chehab 	Markus: Copyright clarification
767fb65297SMauro Carvalho Chehab 
777fb65297SMauro Carvalho Chehab  Version 0.01: Markus: initial release
787fb65297SMauro Carvalho Chehab 
797fb65297SMauro Carvalho Chehab */
807fb65297SMauro Carvalho Chehab 
817fb65297SMauro Carvalho Chehab #include <linux/kernel.h>
827fb65297SMauro Carvalho Chehab #include <linux/module.h>
837fb65297SMauro Carvalho Chehab #include <linux/init.h>
847fb65297SMauro Carvalho Chehab #include <linux/slab.h>
857fb65297SMauro Carvalho Chehab #include <linux/input.h>
865aff308cSAlan Cox #include <linux/videodev2.h>
877fb65297SMauro Carvalho Chehab #include <media/v4l2-common.h>
8835ea11ffSHans Verkuil #include <media/v4l2-ioctl.h>
897fb65297SMauro Carvalho Chehab #include <linux/usb.h>
907fb65297SMauro Carvalho Chehab 
917fb65297SMauro Carvalho Chehab /*
927fb65297SMauro Carvalho Chehab  * Version Information
937fb65297SMauro Carvalho Chehab  */
945aff308cSAlan Cox #include <linux/version.h>	/* for KERNEL_VERSION MACRO	*/
955aff308cSAlan Cox 
9654b7b0deSAlexey Klimov #define DRIVER_VERSION "v0.43"
9754b7b0deSAlexey Klimov #define RADIO_VERSION KERNEL_VERSION(0, 4, 3)
985aff308cSAlan Cox 
995aff308cSAlan Cox static struct v4l2_queryctrl radio_qctrl[] = {
1005aff308cSAlan Cox 	{
1015aff308cSAlan Cox 		.id            = V4L2_CID_AUDIO_MUTE,
1025aff308cSAlan Cox 		.name          = "Mute",
1035aff308cSAlan Cox 		.minimum       = 0,
1045aff308cSAlan Cox 		.maximum       = 1,
1055aff308cSAlan Cox 		.default_value = 1,
1065aff308cSAlan Cox 		.type          = V4L2_CTRL_TYPE_BOOLEAN,
10754b7b0deSAlexey Klimov 	},
10854b7b0deSAlexey Klimov /* HINT: the disabled controls are only here to satify kradio and such apps */
10954b7b0deSAlexey Klimov 	{       .id             = V4L2_CID_AUDIO_VOLUME,
11054b7b0deSAlexey Klimov 		.flags          = V4L2_CTRL_FLAG_DISABLED,
11154b7b0deSAlexey Klimov 	},
11254b7b0deSAlexey Klimov 	{
11354b7b0deSAlexey Klimov 		.id             = V4L2_CID_AUDIO_BALANCE,
11454b7b0deSAlexey Klimov 		.flags          = V4L2_CTRL_FLAG_DISABLED,
11554b7b0deSAlexey Klimov 	},
11654b7b0deSAlexey Klimov 	{
11754b7b0deSAlexey Klimov 		.id             = V4L2_CID_AUDIO_BASS,
11854b7b0deSAlexey Klimov 		.flags          = V4L2_CTRL_FLAG_DISABLED,
11954b7b0deSAlexey Klimov 	},
12054b7b0deSAlexey Klimov 	{
12154b7b0deSAlexey Klimov 		.id             = V4L2_CID_AUDIO_TREBLE,
12254b7b0deSAlexey Klimov 		.flags          = V4L2_CTRL_FLAG_DISABLED,
12354b7b0deSAlexey Klimov 	},
12454b7b0deSAlexey Klimov 	{
12554b7b0deSAlexey Klimov 		.id             = V4L2_CID_AUDIO_LOUDNESS,
12654b7b0deSAlexey Klimov 		.flags          = V4L2_CTRL_FLAG_DISABLED,
12754b7b0deSAlexey Klimov 	},
1285aff308cSAlan Cox };
1295aff308cSAlan Cox 
1307fb65297SMauro Carvalho Chehab #define DRIVER_AUTHOR "Markus Demleitner <msdemlei@tucana.harvard.edu>"
1317fb65297SMauro Carvalho Chehab #define DRIVER_DESC "D-Link DSB-R100 USB FM radio driver"
1327fb65297SMauro Carvalho Chehab 
1337fb65297SMauro Carvalho Chehab #define DSB100_VENDOR 0x04b4
1347fb65297SMauro Carvalho Chehab #define DSB100_PRODUCT 0x1002
1357fb65297SMauro Carvalho Chehab 
1367fb65297SMauro Carvalho Chehab /* Commands the device appears to understand */
1377fb65297SMauro Carvalho Chehab #define DSB100_TUNE 1
1387fb65297SMauro Carvalho Chehab #define DSB100_ONOFF 2
1397fb65297SMauro Carvalho Chehab 
1407fb65297SMauro Carvalho Chehab #define TB_LEN 16
1417fb65297SMauro Carvalho Chehab 
1427fb65297SMauro Carvalho Chehab /* Frequency limits in MHz -- these are European values.  For Japanese
1437fb65297SMauro Carvalho Chehab devices, that would be 76 and 91.  */
1447fb65297SMauro Carvalho Chehab #define FREQ_MIN  87.5
1457fb65297SMauro Carvalho Chehab #define FREQ_MAX 108.0
1467fb65297SMauro Carvalho Chehab #define FREQ_MUL 16000
1477fb65297SMauro Carvalho Chehab 
1487fb65297SMauro Carvalho Chehab 
1497fb65297SMauro Carvalho Chehab static int usb_dsbr100_probe(struct usb_interface *intf,
1507fb65297SMauro Carvalho Chehab 			     const struct usb_device_id *id);
1517fb65297SMauro Carvalho Chehab static void usb_dsbr100_disconnect(struct usb_interface *intf);
1527fb65297SMauro Carvalho Chehab static int usb_dsbr100_open(struct inode *inode, struct file *file);
1537fb65297SMauro Carvalho Chehab static int usb_dsbr100_close(struct inode *inode, struct file *file);
15404e0ffbbSAlexey Klimov static int usb_dsbr100_suspend(struct usb_interface *intf,
15504e0ffbbSAlexey Klimov 						pm_message_t message);
15604e0ffbbSAlexey Klimov static int usb_dsbr100_resume(struct usb_interface *intf);
1577fb65297SMauro Carvalho Chehab 
1587fb65297SMauro Carvalho Chehab static int radio_nr = -1;
1597fb65297SMauro Carvalho Chehab module_param(radio_nr, int, 0);
1607fb65297SMauro Carvalho Chehab 
1617fb65297SMauro Carvalho Chehab /* Data for one (physical) device */
1625aff308cSAlan Cox struct dsbr100_device {
1637fb65297SMauro Carvalho Chehab 	struct usb_device *usbdev;
1647fb65297SMauro Carvalho Chehab 	struct video_device *videodev;
165863c86ddSOliver Neukum 	u8 *transfer_buffer;
1667fb65297SMauro Carvalho Chehab 	int curfreq;
1677fb65297SMauro Carvalho Chehab 	int stereo;
1687fb65297SMauro Carvalho Chehab 	int users;
1697fb65297SMauro Carvalho Chehab 	int removed;
1705aff308cSAlan Cox 	int muted;
1715aff308cSAlan Cox };
1727fb65297SMauro Carvalho Chehab 
1737fb65297SMauro Carvalho Chehab 
1747fb65297SMauro Carvalho Chehab static struct usb_device_id usb_dsbr100_device_table [] = {
1757fb65297SMauro Carvalho Chehab 	{ USB_DEVICE(DSB100_VENDOR, DSB100_PRODUCT) },
1767fb65297SMauro Carvalho Chehab 	{ }						/* Terminating entry */
1777fb65297SMauro Carvalho Chehab };
1787fb65297SMauro Carvalho Chehab 
1797fb65297SMauro Carvalho Chehab MODULE_DEVICE_TABLE (usb, usb_dsbr100_device_table);
1807fb65297SMauro Carvalho Chehab 
1817fb65297SMauro Carvalho Chehab /* USB subsystem interface */
1827fb65297SMauro Carvalho Chehab static struct usb_driver usb_dsbr100_driver = {
1837fb65297SMauro Carvalho Chehab 	.name			= "dsbr100",
1847fb65297SMauro Carvalho Chehab 	.probe			= usb_dsbr100_probe,
1857fb65297SMauro Carvalho Chehab 	.disconnect		= usb_dsbr100_disconnect,
1867fb65297SMauro Carvalho Chehab 	.id_table		= usb_dsbr100_device_table,
18704e0ffbbSAlexey Klimov 	.suspend		= usb_dsbr100_suspend,
18804e0ffbbSAlexey Klimov 	.resume			= usb_dsbr100_resume,
18904e0ffbbSAlexey Klimov 	.reset_resume		= usb_dsbr100_resume,
19004e0ffbbSAlexey Klimov 	.supports_autosuspend	= 0,
1917fb65297SMauro Carvalho Chehab };
1927fb65297SMauro Carvalho Chehab 
1937fb65297SMauro Carvalho Chehab /* Low-level device interface begins here */
1947fb65297SMauro Carvalho Chehab 
1957fb65297SMauro Carvalho Chehab /* switch on radio */
1965aff308cSAlan Cox static int dsbr100_start(struct dsbr100_device *radio)
1977fb65297SMauro Carvalho Chehab {
1987fb65297SMauro Carvalho Chehab 	if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
1997fb65297SMauro Carvalho Chehab 			USB_REQ_GET_STATUS,
2007fb65297SMauro Carvalho Chehab 			USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
2017fb65297SMauro Carvalho Chehab 			0x00, 0xC7, radio->transfer_buffer, 8, 300) < 0 ||
2027fb65297SMauro Carvalho Chehab 	usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
2037fb65297SMauro Carvalho Chehab 			DSB100_ONOFF,
2047fb65297SMauro Carvalho Chehab 			USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
2057fb65297SMauro Carvalho Chehab 			0x01, 0x00, radio->transfer_buffer, 8, 300) < 0)
2067fb65297SMauro Carvalho Chehab 		return -1;
2075aff308cSAlan Cox 	radio->muted=0;
2087fb65297SMauro Carvalho Chehab 	return (radio->transfer_buffer)[0];
2097fb65297SMauro Carvalho Chehab }
2107fb65297SMauro Carvalho Chehab 
2117fb65297SMauro Carvalho Chehab 
2127fb65297SMauro Carvalho Chehab /* switch off radio */
2135aff308cSAlan Cox static int dsbr100_stop(struct dsbr100_device *radio)
2147fb65297SMauro Carvalho Chehab {
2157fb65297SMauro Carvalho Chehab 	if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
2167fb65297SMauro Carvalho Chehab 			USB_REQ_GET_STATUS,
2177fb65297SMauro Carvalho Chehab 			USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
2187fb65297SMauro Carvalho Chehab 			0x16, 0x1C, radio->transfer_buffer, 8, 300) < 0 ||
2197fb65297SMauro Carvalho Chehab 	usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
2207fb65297SMauro Carvalho Chehab 			DSB100_ONOFF,
2217fb65297SMauro Carvalho Chehab 			USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
2227fb65297SMauro Carvalho Chehab 			0x00, 0x00, radio->transfer_buffer, 8, 300) < 0)
2237fb65297SMauro Carvalho Chehab 		return -1;
2245aff308cSAlan Cox 	radio->muted=1;
2257fb65297SMauro Carvalho Chehab 	return (radio->transfer_buffer)[0];
2267fb65297SMauro Carvalho Chehab }
2277fb65297SMauro Carvalho Chehab 
2287fb65297SMauro Carvalho Chehab /* set a frequency, freq is defined by v4l's TUNER_LOW, i.e. 1/16th kHz */
2295aff308cSAlan Cox static int dsbr100_setfreq(struct dsbr100_device *radio, int freq)
2307fb65297SMauro Carvalho Chehab {
2317fb65297SMauro Carvalho Chehab 	freq = (freq / 16 * 80) / 1000 + 856;
2327fb65297SMauro Carvalho Chehab 	if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
2337fb65297SMauro Carvalho Chehab 			DSB100_TUNE,
2347fb65297SMauro Carvalho Chehab 			USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
2357fb65297SMauro Carvalho Chehab 			(freq >> 8) & 0x00ff, freq & 0xff,
2367fb65297SMauro Carvalho Chehab 			radio->transfer_buffer, 8, 300) < 0 ||
2377fb65297SMauro Carvalho Chehab 	   usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
2387fb65297SMauro Carvalho Chehab 			USB_REQ_GET_STATUS,
2397fb65297SMauro Carvalho Chehab 			USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
2407fb65297SMauro Carvalho Chehab 			0x96, 0xB7, radio->transfer_buffer, 8, 300) < 0 ||
2417fb65297SMauro Carvalho Chehab 	usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
2427fb65297SMauro Carvalho Chehab 			USB_REQ_GET_STATUS,
2437fb65297SMauro Carvalho Chehab 			USB_TYPE_VENDOR | USB_RECIP_DEVICE |  USB_DIR_IN,
2447fb65297SMauro Carvalho Chehab 			0x00, 0x24, radio->transfer_buffer, 8, 300) < 0) {
2457fb65297SMauro Carvalho Chehab 		radio->stereo = -1;
2467fb65297SMauro Carvalho Chehab 		return -1;
2477fb65297SMauro Carvalho Chehab 	}
2487fb65297SMauro Carvalho Chehab 	radio->stereo = !((radio->transfer_buffer)[0] & 0x01);
2497fb65297SMauro Carvalho Chehab 	return (radio->transfer_buffer)[0];
2507fb65297SMauro Carvalho Chehab }
2517fb65297SMauro Carvalho Chehab 
2527fb65297SMauro Carvalho Chehab /* return the device status.  This is, in effect, just whether it
2537fb65297SMauro Carvalho Chehab sees a stereo signal or not.  Pity. */
2545aff308cSAlan Cox static void dsbr100_getstat(struct dsbr100_device *radio)
2557fb65297SMauro Carvalho Chehab {
2567fb65297SMauro Carvalho Chehab 	if (usb_control_msg(radio->usbdev, usb_rcvctrlpipe(radio->usbdev, 0),
2577fb65297SMauro Carvalho Chehab 		USB_REQ_GET_STATUS,
2587fb65297SMauro Carvalho Chehab 		USB_TYPE_VENDOR | USB_RECIP_DEVICE | USB_DIR_IN,
2597fb65297SMauro Carvalho Chehab 		0x00 , 0x24, radio->transfer_buffer, 8, 300) < 0)
2607fb65297SMauro Carvalho Chehab 		radio->stereo = -1;
2617fb65297SMauro Carvalho Chehab 	else
2627fb65297SMauro Carvalho Chehab 		radio->stereo = !(radio->transfer_buffer[0] & 0x01);
2637fb65297SMauro Carvalho Chehab }
2647fb65297SMauro Carvalho Chehab 
2657fb65297SMauro Carvalho Chehab 
2667fb65297SMauro Carvalho Chehab /* USB subsystem interface begins here */
2677fb65297SMauro Carvalho Chehab 
2687fb65297SMauro Carvalho Chehab /* handle unplugging of the device, release data structures
2697fb65297SMauro Carvalho Chehab if nothing keeps us from doing it.  If something is still
2707fb65297SMauro Carvalho Chehab keeping us busy, the release callback of v4l will take care
271863c86ddSOliver Neukum of releasing it. */
2727fb65297SMauro Carvalho Chehab static void usb_dsbr100_disconnect(struct usb_interface *intf)
2737fb65297SMauro Carvalho Chehab {
2745aff308cSAlan Cox 	struct dsbr100_device *radio = usb_get_intfdata(intf);
2757fb65297SMauro Carvalho Chehab 
2767fb65297SMauro Carvalho Chehab 	usb_set_intfdata (intf, NULL);
2777fb65297SMauro Carvalho Chehab 	if (radio) {
2787fb65297SMauro Carvalho Chehab 		video_unregister_device(radio->videodev);
2797fb65297SMauro Carvalho Chehab 		radio->videodev = NULL;
2807fb65297SMauro Carvalho Chehab 		if (radio->users) {
281863c86ddSOliver Neukum 			kfree(radio->transfer_buffer);
2827fb65297SMauro Carvalho Chehab 			kfree(radio);
2837fb65297SMauro Carvalho Chehab 		} else {
2847fb65297SMauro Carvalho Chehab 			radio->removed = 1;
2857fb65297SMauro Carvalho Chehab 		}
2867fb65297SMauro Carvalho Chehab 	}
2877fb65297SMauro Carvalho Chehab }
2887fb65297SMauro Carvalho Chehab 
2897fb65297SMauro Carvalho Chehab 
2907002a4f3SDouglas Landgraf static int vidioc_querycap(struct file *file, void *priv,
2917002a4f3SDouglas Landgraf 					struct v4l2_capability *v)
2927fb65297SMauro Carvalho Chehab {
2935aff308cSAlan Cox 	strlcpy(v->driver, "dsbr100", sizeof(v->driver));
2945aff308cSAlan Cox 	strlcpy(v->card, "D-Link R-100 USB FM Radio", sizeof(v->card));
295448441cfSAlexey Klimov 	sprintf(v->bus_info, "USB");
2965aff308cSAlan Cox 	v->version = RADIO_VERSION;
2975aff308cSAlan Cox 	v->capabilities = V4L2_CAP_TUNER;
2987fb65297SMauro Carvalho Chehab 	return 0;
2997fb65297SMauro Carvalho Chehab }
3007002a4f3SDouglas Landgraf 
3017002a4f3SDouglas Landgraf static int vidioc_g_tuner(struct file *file, void *priv,
3027002a4f3SDouglas Landgraf 				struct v4l2_tuner *v)
3035aff308cSAlan Cox {
304c170ecf4SHans Verkuil 	struct dsbr100_device *radio = video_drvdata(file);
3055aff308cSAlan Cox 
3065aff308cSAlan Cox 	if (v->index > 0)
3075aff308cSAlan Cox 		return -EINVAL;
3087fb65297SMauro Carvalho Chehab 
3097fb65297SMauro Carvalho Chehab 	dsbr100_getstat(radio);
3105aff308cSAlan Cox 	strcpy(v->name, "FM");
3115aff308cSAlan Cox 	v->type = V4L2_TUNER_RADIO;
3127fb65297SMauro Carvalho Chehab 	v->rangelow = FREQ_MIN * FREQ_MUL;
3137fb65297SMauro Carvalho Chehab 	v->rangehigh = FREQ_MAX * FREQ_MUL;
3145aff308cSAlan Cox 	v->rxsubchans = V4L2_TUNER_SUB_MONO | V4L2_TUNER_SUB_STEREO;
3155aff308cSAlan Cox 	v->capability = V4L2_TUNER_CAP_LOW;
3165aff308cSAlan Cox 	if(radio->stereo)
3175aff308cSAlan Cox 		v->audmode = V4L2_TUNER_MODE_STEREO;
3185aff308cSAlan Cox 	else
3195aff308cSAlan Cox 		v->audmode = V4L2_TUNER_MODE_MONO;
3207002a4f3SDouglas Landgraf 	v->signal = 0xffff;     /* We can't get the signal strength */
3217fb65297SMauro Carvalho Chehab 	return 0;
3227fb65297SMauro Carvalho Chehab }
3237fb65297SMauro Carvalho Chehab 
3247002a4f3SDouglas Landgraf static int vidioc_s_tuner(struct file *file, void *priv,
3257002a4f3SDouglas Landgraf 				struct v4l2_tuner *v)
3267002a4f3SDouglas Landgraf {
3275aff308cSAlan Cox 	if (v->index > 0)
3287fb65297SMauro Carvalho Chehab 		return -EINVAL;
3295aff308cSAlan Cox 
3307fb65297SMauro Carvalho Chehab 	return 0;
3317fb65297SMauro Carvalho Chehab }
3327002a4f3SDouglas Landgraf 
3337002a4f3SDouglas Landgraf static int vidioc_s_frequency(struct file *file, void *priv,
3347002a4f3SDouglas Landgraf 				struct v4l2_frequency *f)
3355aff308cSAlan Cox {
336c170ecf4SHans Verkuil 	struct dsbr100_device *radio = video_drvdata(file);
3377fb65297SMauro Carvalho Chehab 
3385aff308cSAlan Cox 	radio->curfreq = f->frequency;
3397fb65297SMauro Carvalho Chehab 	if (dsbr100_setfreq(radio, radio->curfreq) == -1)
340aa82661bSGreg Kroah-Hartman 		dev_warn(&radio->usbdev->dev, "Set frequency failed\n");
3417fb65297SMauro Carvalho Chehab 	return 0;
3427fb65297SMauro Carvalho Chehab }
3437002a4f3SDouglas Landgraf 
3447002a4f3SDouglas Landgraf static int vidioc_g_frequency(struct file *file, void *priv,
3457002a4f3SDouglas Landgraf 				struct v4l2_frequency *f)
3465aff308cSAlan Cox {
347c170ecf4SHans Verkuil 	struct dsbr100_device *radio = video_drvdata(file);
3487fb65297SMauro Carvalho Chehab 
3495aff308cSAlan Cox 	f->type = V4L2_TUNER_RADIO;
3505aff308cSAlan Cox 	f->frequency = radio->curfreq;
3517fb65297SMauro Carvalho Chehab 	return 0;
3527fb65297SMauro Carvalho Chehab }
3537002a4f3SDouglas Landgraf 
3547002a4f3SDouglas Landgraf static int vidioc_queryctrl(struct file *file, void *priv,
3557002a4f3SDouglas Landgraf 				struct v4l2_queryctrl *qc)
3565aff308cSAlan Cox {
3575aff308cSAlan Cox 	int i;
3587fb65297SMauro Carvalho Chehab 
3595aff308cSAlan Cox 	for (i = 0; i < ARRAY_SIZE(radio_qctrl); i++) {
3605aff308cSAlan Cox 		if (qc->id && qc->id == radio_qctrl[i].id) {
361223377e7SAlexey Klimov 			memcpy(qc, &(radio_qctrl[i]), sizeof(*qc));
3625aff308cSAlan Cox 			return 0;
3635aff308cSAlan Cox 		}
3645aff308cSAlan Cox 	}
3657fb65297SMauro Carvalho Chehab 	return -EINVAL;
3665aff308cSAlan Cox }
3677002a4f3SDouglas Landgraf 
3687002a4f3SDouglas Landgraf static int vidioc_g_ctrl(struct file *file, void *priv,
3697002a4f3SDouglas Landgraf 				struct v4l2_control *ctrl)
3705aff308cSAlan Cox {
371c170ecf4SHans Verkuil 	struct dsbr100_device *radio = video_drvdata(file);
3725aff308cSAlan Cox 
3735aff308cSAlan Cox 	switch (ctrl->id) {
3745aff308cSAlan Cox 	case V4L2_CID_AUDIO_MUTE:
3755aff308cSAlan Cox 		ctrl->value = radio->muted;
3765aff308cSAlan Cox 		return 0;
3775aff308cSAlan Cox 	}
3785aff308cSAlan Cox 	return -EINVAL;
3795aff308cSAlan Cox }
3807002a4f3SDouglas Landgraf 
3817002a4f3SDouglas Landgraf static int vidioc_s_ctrl(struct file *file, void *priv,
3827002a4f3SDouglas Landgraf 				struct v4l2_control *ctrl)
3835aff308cSAlan Cox {
384c170ecf4SHans Verkuil 	struct dsbr100_device *radio = video_drvdata(file);
3855aff308cSAlan Cox 
3865aff308cSAlan Cox 	switch (ctrl->id) {
3875aff308cSAlan Cox 	case V4L2_CID_AUDIO_MUTE:
3885aff308cSAlan Cox 		if (ctrl->value) {
38990b698ddSAlexey Klimov 			if (dsbr100_stop(radio) == -1) {
390aa82661bSGreg Kroah-Hartman 				dev_warn(&radio->usbdev->dev,
391aa82661bSGreg Kroah-Hartman 					 "Radio did not respond properly\n");
39290b698ddSAlexey Klimov 				return -EBUSY;
39390b698ddSAlexey Klimov 			}
3945aff308cSAlan Cox 		} else {
39590b698ddSAlexey Klimov 			if (dsbr100_start(radio) == -1) {
396aa82661bSGreg Kroah-Hartman 				dev_warn(&radio->usbdev->dev,
397aa82661bSGreg Kroah-Hartman 					 "Radio did not respond properly\n");
39890b698ddSAlexey Klimov 				return -EBUSY;
39990b698ddSAlexey Klimov 			}
4005aff308cSAlan Cox 		}
4017fb65297SMauro Carvalho Chehab 		return 0;
4027fb65297SMauro Carvalho Chehab 	}
4035aff308cSAlan Cox 	return -EINVAL;
4045aff308cSAlan Cox }
4057002a4f3SDouglas Landgraf 
4067002a4f3SDouglas Landgraf static int vidioc_g_audio(struct file *file, void *priv,
4077002a4f3SDouglas Landgraf 				struct v4l2_audio *a)
4087002a4f3SDouglas Landgraf {
4097002a4f3SDouglas Landgraf 	if (a->index > 1)
4107002a4f3SDouglas Landgraf 		return -EINVAL;
4117002a4f3SDouglas Landgraf 
4127002a4f3SDouglas Landgraf 	strcpy(a->name, "Radio");
4137002a4f3SDouglas Landgraf 	a->capability = V4L2_AUDCAP_STEREO;
4147002a4f3SDouglas Landgraf 	return 0;
4157fb65297SMauro Carvalho Chehab }
4167fb65297SMauro Carvalho Chehab 
4177002a4f3SDouglas Landgraf static int vidioc_g_input(struct file *filp, void *priv, unsigned int *i)
4187fb65297SMauro Carvalho Chehab {
4197002a4f3SDouglas Landgraf 	*i = 0;
4207002a4f3SDouglas Landgraf 	return 0;
4217002a4f3SDouglas Landgraf }
4227002a4f3SDouglas Landgraf 
4237002a4f3SDouglas Landgraf static int vidioc_s_input(struct file *filp, void *priv, unsigned int i)
4247002a4f3SDouglas Landgraf {
4257002a4f3SDouglas Landgraf 	if (i != 0)
4267002a4f3SDouglas Landgraf 		return -EINVAL;
4277002a4f3SDouglas Landgraf 	return 0;
4287002a4f3SDouglas Landgraf }
4297002a4f3SDouglas Landgraf 
4307002a4f3SDouglas Landgraf static int vidioc_s_audio(struct file *file, void *priv,
4317002a4f3SDouglas Landgraf 					struct v4l2_audio *a)
4327002a4f3SDouglas Landgraf {
4337002a4f3SDouglas Landgraf 	if (a->index != 0)
4347002a4f3SDouglas Landgraf 		return -EINVAL;
4357002a4f3SDouglas Landgraf 	return 0;
4367fb65297SMauro Carvalho Chehab }
4377fb65297SMauro Carvalho Chehab 
4387fb65297SMauro Carvalho Chehab static int usb_dsbr100_open(struct inode *inode, struct file *file)
4397fb65297SMauro Carvalho Chehab {
440c170ecf4SHans Verkuil 	struct dsbr100_device *radio = video_drvdata(file);
441b9f35737SAlexey Klimov 	int retval;
4427fb65297SMauro Carvalho Chehab 
443d56dc612SHans Verkuil 	lock_kernel();
4447fb65297SMauro Carvalho Chehab 	radio->users = 1;
4455aff308cSAlan Cox 	radio->muted = 1;
4465aff308cSAlan Cox 
4477fb65297SMauro Carvalho Chehab 	if (dsbr100_start(radio) < 0) {
448aa82661bSGreg Kroah-Hartman 		dev_warn(&radio->usbdev->dev,
449aa82661bSGreg Kroah-Hartman 			 "Radio did not start up properly\n");
4507fb65297SMauro Carvalho Chehab 		radio->users = 0;
451d56dc612SHans Verkuil 		unlock_kernel();
4527fb65297SMauro Carvalho Chehab 		return -EIO;
4537fb65297SMauro Carvalho Chehab 	}
454b9f35737SAlexey Klimov 
455b9f35737SAlexey Klimov 	retval = dsbr100_setfreq(radio, radio->curfreq);
456b9f35737SAlexey Klimov 
457b9f35737SAlexey Klimov 	if (retval == -1)
458b9f35737SAlexey Klimov 		printk(KERN_WARNING KBUILD_MODNAME ": Set frequency failed\n");
459b9f35737SAlexey Klimov 
460d56dc612SHans Verkuil 	unlock_kernel();
4617fb65297SMauro Carvalho Chehab 	return 0;
4627fb65297SMauro Carvalho Chehab }
4637fb65297SMauro Carvalho Chehab 
4647fb65297SMauro Carvalho Chehab static int usb_dsbr100_close(struct inode *inode, struct file *file)
4657fb65297SMauro Carvalho Chehab {
466c170ecf4SHans Verkuil 	struct dsbr100_device *radio = video_drvdata(file);
4677fb65297SMauro Carvalho Chehab 
4687fb65297SMauro Carvalho Chehab 	if (!radio)
4697fb65297SMauro Carvalho Chehab 		return -ENODEV;
4707fb65297SMauro Carvalho Chehab 	radio->users = 0;
4717fb65297SMauro Carvalho Chehab 	if (radio->removed) {
472863c86ddSOliver Neukum 		kfree(radio->transfer_buffer);
4737fb65297SMauro Carvalho Chehab 		kfree(radio);
4747fb65297SMauro Carvalho Chehab 	}
4757fb65297SMauro Carvalho Chehab 	return 0;
4767fb65297SMauro Carvalho Chehab }
4777fb65297SMauro Carvalho Chehab 
47804e0ffbbSAlexey Klimov /* Suspend device - stop device. */
47904e0ffbbSAlexey Klimov static int usb_dsbr100_suspend(struct usb_interface *intf, pm_message_t message)
48004e0ffbbSAlexey Klimov {
48104e0ffbbSAlexey Klimov 	struct dsbr100_device *radio = usb_get_intfdata(intf);
48204e0ffbbSAlexey Klimov 	int retval;
48304e0ffbbSAlexey Klimov 
48404e0ffbbSAlexey Klimov 	retval = dsbr100_stop(radio);
48504e0ffbbSAlexey Klimov 	if (retval == -1)
48604e0ffbbSAlexey Klimov 		dev_warn(&intf->dev, "dsbr100_stop failed\n");
48704e0ffbbSAlexey Klimov 
48804e0ffbbSAlexey Klimov 	dev_info(&intf->dev, "going into suspend..\n");
48904e0ffbbSAlexey Klimov 
49004e0ffbbSAlexey Klimov 	return 0;
49104e0ffbbSAlexey Klimov }
49204e0ffbbSAlexey Klimov 
49304e0ffbbSAlexey Klimov /* Resume device - start device. */
49404e0ffbbSAlexey Klimov static int usb_dsbr100_resume(struct usb_interface *intf)
49504e0ffbbSAlexey Klimov {
49604e0ffbbSAlexey Klimov 	struct dsbr100_device *radio = usb_get_intfdata(intf);
49704e0ffbbSAlexey Klimov 	int retval;
49804e0ffbbSAlexey Klimov 
49904e0ffbbSAlexey Klimov 	retval = dsbr100_start(radio);
50004e0ffbbSAlexey Klimov 	if (retval == -1)
50104e0ffbbSAlexey Klimov 		dev_warn(&intf->dev, "dsbr100_start failed\n");
50204e0ffbbSAlexey Klimov 
50304e0ffbbSAlexey Klimov 	dev_info(&intf->dev, "coming out of suspend..\n");
50404e0ffbbSAlexey Klimov 
50504e0ffbbSAlexey Klimov 	return 0;
50604e0ffbbSAlexey Klimov }
50704e0ffbbSAlexey Klimov 
5087002a4f3SDouglas Landgraf /* File system interface */
5097002a4f3SDouglas Landgraf static const struct file_operations usb_dsbr100_fops = {
5107002a4f3SDouglas Landgraf 	.owner		= THIS_MODULE,
5117002a4f3SDouglas Landgraf 	.open		= usb_dsbr100_open,
5127002a4f3SDouglas Landgraf 	.release	= usb_dsbr100_close,
5137002a4f3SDouglas Landgraf 	.ioctl		= video_ioctl2,
514078ff795SDouglas Schilling Landgraf #ifdef CONFIG_COMPAT
5157002a4f3SDouglas Landgraf 	.compat_ioctl	= v4l_compat_ioctl32,
516078ff795SDouglas Schilling Landgraf #endif
5177002a4f3SDouglas Landgraf 	.llseek		= no_llseek,
5187002a4f3SDouglas Landgraf };
5197002a4f3SDouglas Landgraf 
520a399810cSHans Verkuil static const struct v4l2_ioctl_ops usb_dsbr100_ioctl_ops = {
5217002a4f3SDouglas Landgraf 	.vidioc_querycap    = vidioc_querycap,
5227002a4f3SDouglas Landgraf 	.vidioc_g_tuner     = vidioc_g_tuner,
5237002a4f3SDouglas Landgraf 	.vidioc_s_tuner     = vidioc_s_tuner,
5247002a4f3SDouglas Landgraf 	.vidioc_g_frequency = vidioc_g_frequency,
5257002a4f3SDouglas Landgraf 	.vidioc_s_frequency = vidioc_s_frequency,
5267002a4f3SDouglas Landgraf 	.vidioc_queryctrl   = vidioc_queryctrl,
5277002a4f3SDouglas Landgraf 	.vidioc_g_ctrl      = vidioc_g_ctrl,
5287002a4f3SDouglas Landgraf 	.vidioc_s_ctrl      = vidioc_s_ctrl,
5297002a4f3SDouglas Landgraf 	.vidioc_g_audio     = vidioc_g_audio,
5307002a4f3SDouglas Landgraf 	.vidioc_s_audio     = vidioc_s_audio,
5317002a4f3SDouglas Landgraf 	.vidioc_g_input     = vidioc_g_input,
5327002a4f3SDouglas Landgraf 	.vidioc_s_input     = vidioc_s_input,
5337002a4f3SDouglas Landgraf };
5347002a4f3SDouglas Landgraf 
535a399810cSHans Verkuil /* V4L2 interface */
536a399810cSHans Verkuil static struct video_device dsbr100_videodev_template = {
537a399810cSHans Verkuil 	.name		= "D-Link DSB-R 100",
538a399810cSHans Verkuil 	.fops		= &usb_dsbr100_fops,
539a399810cSHans Verkuil 	.ioctl_ops 	= &usb_dsbr100_ioctl_ops,
540a399810cSHans Verkuil 	.release	= video_device_release,
541a399810cSHans Verkuil };
542a399810cSHans Verkuil 
5437002a4f3SDouglas Landgraf /* check if the device is present and register with v4l and
5447002a4f3SDouglas Landgraf usb if it is */
5457002a4f3SDouglas Landgraf static int usb_dsbr100_probe(struct usb_interface *intf,
5467002a4f3SDouglas Landgraf 				const struct usb_device_id *id)
5477002a4f3SDouglas Landgraf {
5487002a4f3SDouglas Landgraf 	struct dsbr100_device *radio;
5497002a4f3SDouglas Landgraf 
550223377e7SAlexey Klimov 	radio = kmalloc(sizeof(struct dsbr100_device), GFP_KERNEL);
551223377e7SAlexey Klimov 
552223377e7SAlexey Klimov 	if (!radio)
5537002a4f3SDouglas Landgraf 		return -ENOMEM;
554223377e7SAlexey Klimov 
555223377e7SAlexey Klimov 	radio->transfer_buffer = kmalloc(TB_LEN, GFP_KERNEL);
556223377e7SAlexey Klimov 
557223377e7SAlexey Klimov 	if (!(radio->transfer_buffer)) {
558863c86ddSOliver Neukum 		kfree(radio);
559863c86ddSOliver Neukum 		return -ENOMEM;
560863c86ddSOliver Neukum 	}
561223377e7SAlexey Klimov 	radio->videodev = video_device_alloc();
562223377e7SAlexey Klimov 
563223377e7SAlexey Klimov 	if (!(radio->videodev)) {
564863c86ddSOliver Neukum 		kfree(radio->transfer_buffer);
5657002a4f3SDouglas Landgraf 		kfree(radio);
5667002a4f3SDouglas Landgraf 		return -ENOMEM;
5677002a4f3SDouglas Landgraf 	}
5687002a4f3SDouglas Landgraf 	memcpy(radio->videodev, &dsbr100_videodev_template,
5697002a4f3SDouglas Landgraf 		sizeof(dsbr100_videodev_template));
5707002a4f3SDouglas Landgraf 	radio->removed = 0;
5717002a4f3SDouglas Landgraf 	radio->users = 0;
5727002a4f3SDouglas Landgraf 	radio->usbdev = interface_to_usbdev(intf);
5737002a4f3SDouglas Landgraf 	radio->curfreq = FREQ_MIN * FREQ_MUL;
5747002a4f3SDouglas Landgraf 	video_set_drvdata(radio->videodev, radio);
575cba99ae8SHans Verkuil 	if (video_register_device(radio->videodev, VFL_TYPE_RADIO, radio_nr) < 0) {
576aa82661bSGreg Kroah-Hartman 		dev_warn(&intf->dev, "Could not register video device\n");
5777002a4f3SDouglas Landgraf 		video_device_release(radio->videodev);
578863c86ddSOliver Neukum 		kfree(radio->transfer_buffer);
5797002a4f3SDouglas Landgraf 		kfree(radio);
5807002a4f3SDouglas Landgraf 		return -EIO;
5817002a4f3SDouglas Landgraf 	}
5827002a4f3SDouglas Landgraf 	usb_set_intfdata(intf, radio);
5837002a4f3SDouglas Landgraf 	return 0;
5847002a4f3SDouglas Landgraf }
5857002a4f3SDouglas Landgraf 
5867fb65297SMauro Carvalho Chehab static int __init dsbr100_init(void)
5877fb65297SMauro Carvalho Chehab {
5887fb65297SMauro Carvalho Chehab 	int retval = usb_register(&usb_dsbr100_driver);
589a482f327SGreg Kroah-Hartman 	printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION ":"
590a482f327SGreg Kroah-Hartman 	       DRIVER_DESC "\n");
5917fb65297SMauro Carvalho Chehab 	return retval;
5927fb65297SMauro Carvalho Chehab }
5937fb65297SMauro Carvalho Chehab 
5947fb65297SMauro Carvalho Chehab static void __exit dsbr100_exit(void)
5957fb65297SMauro Carvalho Chehab {
5967fb65297SMauro Carvalho Chehab 	usb_deregister(&usb_dsbr100_driver);
5977fb65297SMauro Carvalho Chehab }
5987fb65297SMauro Carvalho Chehab 
5997fb65297SMauro Carvalho Chehab module_init (dsbr100_init);
6007fb65297SMauro Carvalho Chehab module_exit (dsbr100_exit);
6017fb65297SMauro Carvalho Chehab 
6027fb65297SMauro Carvalho Chehab MODULE_AUTHOR( DRIVER_AUTHOR );
6037fb65297SMauro Carvalho Chehab MODULE_DESCRIPTION( DRIVER_DESC );
6047fb65297SMauro Carvalho Chehab MODULE_LICENSE("GPL");
605