1a3550ea6SFederico Simoncelli /*
2a3550ea6SFederico Simoncelli  * Copyright (c) 2013 Lubomir Rintel
3a3550ea6SFederico Simoncelli  * All rights reserved.
4a3550ea6SFederico Simoncelli  *
5a3550ea6SFederico Simoncelli  * Redistribution and use in source and binary forms, with or without
6a3550ea6SFederico Simoncelli  * modification, are permitted provided that the following conditions
7a3550ea6SFederico Simoncelli  * are met:
8a3550ea6SFederico Simoncelli  * 1. Redistributions of source code must retain the above copyright
9a3550ea6SFederico Simoncelli  *    notice, this list of conditions, and the following disclaimer,
10a3550ea6SFederico Simoncelli  *    without modification.
11a3550ea6SFederico Simoncelli  * 2. The name of the author may not be used to endorse or promote products
12a3550ea6SFederico Simoncelli  *    derived from this software without specific prior written permission.
13a3550ea6SFederico Simoncelli  *
14a3550ea6SFederico Simoncelli  * Alternatively, this software may be distributed under the terms of the
15a3550ea6SFederico Simoncelli  * GNU General Public License ("GPL").
165ae1e2b2SLubomir Rintel  *
175ae1e2b2SLubomir Rintel  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
185ae1e2b2SLubomir Rintel  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
195ae1e2b2SLubomir Rintel  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
205ae1e2b2SLubomir Rintel  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
215ae1e2b2SLubomir Rintel  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
225ae1e2b2SLubomir Rintel  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
235ae1e2b2SLubomir Rintel  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
245ae1e2b2SLubomir Rintel  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
255ae1e2b2SLubomir Rintel  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
265ae1e2b2SLubomir Rintel  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
275ae1e2b2SLubomir Rintel  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
285ae1e2b2SLubomir Rintel  */
295ae1e2b2SLubomir Rintel /*
305ae1e2b2SLubomir Rintel  * Fushicai USBTV007 Audio-Video Grabber Driver
315ae1e2b2SLubomir Rintel  *
325ae1e2b2SLubomir Rintel  * Product web site:
335ae1e2b2SLubomir Rintel  * http://www.fushicai.com/products_detail/&productId=d05449ee-b690-42f9-a661-aa7353894bed.html
345ae1e2b2SLubomir Rintel  *
355ae1e2b2SLubomir Rintel  * Following LWN articles were very useful in construction of this driver:
365ae1e2b2SLubomir Rintel  * Video4Linux2 API series: http://lwn.net/Articles/203924/
375ae1e2b2SLubomir Rintel  * videobuf2 API explanation: http://lwn.net/Articles/447435/
385ae1e2b2SLubomir Rintel  * Thanks go to Jonathan Corbet for providing this quality documentation.
395ae1e2b2SLubomir Rintel  * He is awesome.
405ae1e2b2SLubomir Rintel  *
415ae1e2b2SLubomir Rintel  * No physical hardware was harmed running Windows during the
425ae1e2b2SLubomir Rintel  * reverse-engineering activity
43a3550ea6SFederico Simoncelli  */
44a3550ea6SFederico Simoncelli 
45a3550ea6SFederico Simoncelli #include "usbtv.h"
46a3550ea6SFederico Simoncelli 
usbtv_set_regs(struct usbtv * usbtv,const u16 regs[][2],int size)47a3550ea6SFederico Simoncelli int usbtv_set_regs(struct usbtv *usbtv, const u16 regs[][2], int size)
48a3550ea6SFederico Simoncelli {
49a3550ea6SFederico Simoncelli 	int ret;
50*91f13120SOliver Neukum 	int pipe = usb_sndctrlpipe(usbtv->udev, 0);
51a3550ea6SFederico Simoncelli 	int i;
52a3550ea6SFederico Simoncelli 
53a3550ea6SFederico Simoncelli 	for (i = 0; i < size; i++) {
54a3550ea6SFederico Simoncelli 		u16 index = regs[i][0];
55a3550ea6SFederico Simoncelli 		u16 value = regs[i][1];
56a3550ea6SFederico Simoncelli 
57a3550ea6SFederico Simoncelli 		ret = usb_control_msg(usbtv->udev, pipe, USBTV_REQUEST_REG,
58a3550ea6SFederico Simoncelli 			USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE,
59536f561dSJohan Hovold 			value, index, NULL, 0, USB_CTRL_GET_TIMEOUT);
60a3550ea6SFederico Simoncelli 		if (ret < 0)
61a3550ea6SFederico Simoncelli 			return ret;
62a3550ea6SFederico Simoncelli 	}
63a3550ea6SFederico Simoncelli 
64a3550ea6SFederico Simoncelli 	return 0;
65a3550ea6SFederico Simoncelli }
66a3550ea6SFederico Simoncelli 
usbtv_probe(struct usb_interface * intf,const struct usb_device_id * id)67a3550ea6SFederico Simoncelli static int usbtv_probe(struct usb_interface *intf,
68a3550ea6SFederico Simoncelli 	const struct usb_device_id *id)
69a3550ea6SFederico Simoncelli {
70a3550ea6SFederico Simoncelli 	int ret;
71a3550ea6SFederico Simoncelli 	int size;
72a3550ea6SFederico Simoncelli 	struct device *dev = &intf->dev;
73a3550ea6SFederico Simoncelli 	struct usbtv *usbtv;
74b72b7979SFelipe Balbi 	struct usb_host_endpoint *ep;
75a3550ea6SFederico Simoncelli 
76a3550ea6SFederico Simoncelli 	/* Checks that the device is what we think it is. */
77a3550ea6SFederico Simoncelli 	if (intf->num_altsetting != 2)
78a3550ea6SFederico Simoncelli 		return -ENODEV;
79a3550ea6SFederico Simoncelli 	if (intf->altsetting[1].desc.bNumEndpoints != 4)
80a3550ea6SFederico Simoncelli 		return -ENODEV;
81a3550ea6SFederico Simoncelli 
82b72b7979SFelipe Balbi 	ep = &intf->altsetting[1].endpoint[0];
83b72b7979SFelipe Balbi 
84a3550ea6SFederico Simoncelli 	/* Packet size is split into 11 bits of base size and count of
85a3550ea6SFederico Simoncelli 	 * extra multiplies of it.*/
86b72b7979SFelipe Balbi 	size = usb_endpoint_maxp(&ep->desc);
876981d6e5SJaejoong Kim 	size = size * usb_endpoint_maxp_mult(&ep->desc);
88a3550ea6SFederico Simoncelli 
89a3550ea6SFederico Simoncelli 	/* Device structure */
90a3550ea6SFederico Simoncelli 	usbtv = kzalloc(sizeof(struct usbtv), GFP_KERNEL);
91a3550ea6SFederico Simoncelli 	if (usbtv == NULL)
92a3550ea6SFederico Simoncelli 		return -ENOMEM;
93a3550ea6SFederico Simoncelli 	usbtv->dev = dev;
94a3550ea6SFederico Simoncelli 	usbtv->udev = usb_get_dev(interface_to_usbdev(intf));
95a3550ea6SFederico Simoncelli 
96a3550ea6SFederico Simoncelli 	usbtv->iso_size = size;
97a3550ea6SFederico Simoncelli 
98a3550ea6SFederico Simoncelli 	usb_set_intfdata(intf, usbtv);
99a3550ea6SFederico Simoncelli 
100a3550ea6SFederico Simoncelli 	ret = usbtv_video_init(usbtv);
101a3550ea6SFederico Simoncelli 	if (ret < 0)
102a3550ea6SFederico Simoncelli 		goto usbtv_video_fail;
103a3550ea6SFederico Simoncelli 
10463ddf68dSFederico Simoncelli 	ret = usbtv_audio_init(usbtv);
10563ddf68dSFederico Simoncelli 	if (ret < 0)
10663ddf68dSFederico Simoncelli 		goto usbtv_audio_fail;
10763ddf68dSFederico Simoncelli 
108a3550ea6SFederico Simoncelli 	/* for simplicity we exploit the v4l2_device reference counting */
109a3550ea6SFederico Simoncelli 	v4l2_device_get(&usbtv->v4l2_dev);
110a3550ea6SFederico Simoncelli 
11163ddf68dSFederico Simoncelli 	dev_info(dev, "Fushicai USBTV007 Audio-Video Grabber\n");
112a3550ea6SFederico Simoncelli 	return 0;
113a3550ea6SFederico Simoncelli 
11463ddf68dSFederico Simoncelli usbtv_audio_fail:
11550e70445SOliver Neukum 	/* we must not free at this point */
116bf65f8aaSOliver Neukum 	v4l2_device_get(&usbtv->v4l2_dev);
117bf65f8aaSOliver Neukum 	/* this will undo the v4l2_device_get() */
11863ddf68dSFederico Simoncelli 	usbtv_video_free(usbtv);
11963ddf68dSFederico Simoncelli 
120a3550ea6SFederico Simoncelli usbtv_video_fail:
121ebdae650SAlexey Khoroshilov 	usb_set_intfdata(intf, NULL);
122ebdae650SAlexey Khoroshilov 	usb_put_dev(usbtv->udev);
123a3550ea6SFederico Simoncelli 	kfree(usbtv);
124a3550ea6SFederico Simoncelli 
125a3550ea6SFederico Simoncelli 	return ret;
126a3550ea6SFederico Simoncelli }
127a3550ea6SFederico Simoncelli 
usbtv_disconnect(struct usb_interface * intf)128a3550ea6SFederico Simoncelli static void usbtv_disconnect(struct usb_interface *intf)
129a3550ea6SFederico Simoncelli {
130a3550ea6SFederico Simoncelli 	struct usbtv *usbtv = usb_get_intfdata(intf);
131146af9cbSAmber Thrall 
132a3550ea6SFederico Simoncelli 	usb_set_intfdata(intf, NULL);
133a3550ea6SFederico Simoncelli 
134a3550ea6SFederico Simoncelli 	if (!usbtv)
135a3550ea6SFederico Simoncelli 		return;
136a3550ea6SFederico Simoncelli 
13763ddf68dSFederico Simoncelli 	usbtv_audio_free(usbtv);
138a3550ea6SFederico Simoncelli 	usbtv_video_free(usbtv);
139a3550ea6SFederico Simoncelli 
140a3550ea6SFederico Simoncelli 	usb_put_dev(usbtv->udev);
141a3550ea6SFederico Simoncelli 	usbtv->udev = NULL;
142a3550ea6SFederico Simoncelli 
143a3550ea6SFederico Simoncelli 	/* the usbtv structure will be deallocated when v4l2 will be
144a3550ea6SFederico Simoncelli 	   done using it */
145a3550ea6SFederico Simoncelli 	v4l2_device_put(&usbtv->v4l2_dev);
146a3550ea6SFederico Simoncelli }
147a3550ea6SFederico Simoncelli 
1487fb2e072SArvind Yadav static const struct usb_device_id usbtv_id_table[] = {
149a3550ea6SFederico Simoncelli 	{ USB_DEVICE(0x1b71, 0x3002) },
15004226916SIcenowy Zheng 	{ USB_DEVICE(0x1f71, 0x3301) },
1518a695a6cSIan Douglas Scott 	{ USB_DEVICE(0x1f71, 0x3306) },
152a3550ea6SFederico Simoncelli 	{}
153a3550ea6SFederico Simoncelli };
154a3550ea6SFederico Simoncelli MODULE_DEVICE_TABLE(usb, usbtv_id_table);
155a3550ea6SFederico Simoncelli 
15663ddf68dSFederico Simoncelli MODULE_AUTHOR("Lubomir Rintel, Federico Simoncelli");
15763ddf68dSFederico Simoncelli MODULE_DESCRIPTION("Fushicai USBTV007 Audio-Video Grabber Driver");
158a3550ea6SFederico Simoncelli MODULE_LICENSE("Dual BSD/GPL");
159a3550ea6SFederico Simoncelli 
1609b058373SFengguang Wu static struct usb_driver usbtv_usb_driver = {
161a3550ea6SFederico Simoncelli 	.name = "usbtv",
162a3550ea6SFederico Simoncelli 	.id_table = usbtv_id_table,
163a3550ea6SFederico Simoncelli 	.probe = usbtv_probe,
164a3550ea6SFederico Simoncelli 	.disconnect = usbtv_disconnect,
165a3550ea6SFederico Simoncelli };
166a3550ea6SFederico Simoncelli 
167a3550ea6SFederico Simoncelli module_usb_driver(usbtv_usb_driver);
168