| /* | 
 |  * Streamzap Remote Control driver | 
 |  * | 
 |  * Copyright (c) 2005 Christoph Bartelmus <lirc@bartelmus.de> | 
 |  * Copyright (c) 2010 Jarod Wilson <jarod@wilsonet.com> | 
 |  * | 
 |  * This driver was based on the work of Greg Wickham and Adrian | 
 |  * Dewhurst. It was substantially rewritten to support correct signal | 
 |  * gaps and now maintains a delay buffer, which is used to present | 
 |  * consistent timing behaviour to user space applications. Without the | 
 |  * delay buffer an ugly hack would be required in lircd, which can | 
 |  * cause sluggish signal decoding in certain situations. | 
 |  * | 
 |  * Ported to in-kernel ir-core interface by Jarod Wilson | 
 |  * | 
 |  * This driver is based on the USB skeleton driver packaged with the | 
 |  * kernel; copyright (C) 2001-2003 Greg Kroah-Hartman (greg@kroah.com) | 
 |  * | 
 |  *  This program is free software; you can redistribute it and/or modify | 
 |  *  it under the terms of the GNU General Public License as published by | 
 |  *  the Free Software Foundation; either version 2 of the License, or | 
 |  *  (at your option) any later version. | 
 |  * | 
 |  *  This program is distributed in the hope that it will be useful, | 
 |  *  but WITHOUT ANY WARRANTY; without even the implied warranty of | 
 |  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
 |  *  GNU General Public License for more details. | 
 |  */ | 
 |  | 
 | #include <linux/device.h> | 
 | #include <linux/module.h> | 
 | #include <linux/slab.h> | 
 | #include <linux/ktime.h> | 
 | #include <linux/usb.h> | 
 | #include <linux/usb/input.h> | 
 | #include <media/rc-core.h> | 
 |  | 
 | #define DRIVER_VERSION	"1.61" | 
 | #define DRIVER_NAME	"streamzap" | 
 | #define DRIVER_DESC	"Streamzap Remote Control driver" | 
 |  | 
 | #define USB_STREAMZAP_VENDOR_ID		0x0e9c | 
 | #define USB_STREAMZAP_PRODUCT_ID	0x0000 | 
 |  | 
 | /* table of devices that work with this driver */ | 
 | static const struct usb_device_id streamzap_table[] = { | 
 | 	/* Streamzap Remote Control */ | 
 | 	{ USB_DEVICE(USB_STREAMZAP_VENDOR_ID, USB_STREAMZAP_PRODUCT_ID) }, | 
 | 	/* Terminating entry */ | 
 | 	{ } | 
 | }; | 
 |  | 
 | MODULE_DEVICE_TABLE(usb, streamzap_table); | 
 |  | 
 | #define SZ_PULSE_MASK 0xf0 | 
 | #define SZ_SPACE_MASK 0x0f | 
 | #define SZ_TIMEOUT    0xff | 
 | #define SZ_RESOLUTION 256 | 
 |  | 
 | /* number of samples buffered */ | 
 | #define SZ_BUF_LEN 128 | 
 |  | 
 | enum StreamzapDecoderState { | 
 | 	PulseSpace, | 
 | 	FullPulse, | 
 | 	FullSpace, | 
 | 	IgnorePulse | 
 | }; | 
 |  | 
 | /* structure to hold our device specific stuff */ | 
 | struct streamzap_ir { | 
 | 	/* ir-core */ | 
 | 	struct rc_dev *rdev; | 
 |  | 
 | 	/* core device info */ | 
 | 	struct device *dev; | 
 |  | 
 | 	/* usb */ | 
 | 	struct usb_device	*usbdev; | 
 | 	struct usb_interface	*interface; | 
 | 	struct usb_endpoint_descriptor *endpoint; | 
 | 	struct urb		*urb_in; | 
 |  | 
 | 	/* buffer & dma */ | 
 | 	unsigned char		*buf_in; | 
 | 	dma_addr_t		dma_in; | 
 | 	unsigned int		buf_in_len; | 
 |  | 
 | 	/* track what state we're in */ | 
 | 	enum StreamzapDecoderState decoder_state; | 
 | 	/* tracks whether we are currently receiving some signal */ | 
 | 	bool			idle; | 
 | 	/* sum of signal lengths received since signal start */ | 
 | 	unsigned long		sum; | 
 | 	/* start time of signal; necessary for gap tracking */ | 
 | 	ktime_t			signal_last; | 
 | 	ktime_t			signal_start; | 
 | 	bool			timeout_enabled; | 
 |  | 
 | 	char			name[128]; | 
 | 	char			phys[64]; | 
 | }; | 
 |  | 
 |  | 
 | /* local function prototypes */ | 
 | static int streamzap_probe(struct usb_interface *interface, | 
 | 			   const struct usb_device_id *id); | 
 | static void streamzap_disconnect(struct usb_interface *interface); | 
 | static void streamzap_callback(struct urb *urb); | 
 | static int streamzap_suspend(struct usb_interface *intf, pm_message_t message); | 
 | static int streamzap_resume(struct usb_interface *intf); | 
 |  | 
 | /* usb specific object needed to register this driver with the usb subsystem */ | 
 | static struct usb_driver streamzap_driver = { | 
 | 	.name =		DRIVER_NAME, | 
 | 	.probe =	streamzap_probe, | 
 | 	.disconnect =	streamzap_disconnect, | 
 | 	.suspend =	streamzap_suspend, | 
 | 	.resume =	streamzap_resume, | 
 | 	.id_table =	streamzap_table, | 
 | }; | 
 |  | 
 | static void sz_push(struct streamzap_ir *sz, struct ir_raw_event rawir) | 
 | { | 
 | 	dev_dbg(sz->dev, "Storing %s with duration %u us\n", | 
 | 		(rawir.pulse ? "pulse" : "space"), rawir.duration); | 
 | 	ir_raw_event_store_with_filter(sz->rdev, &rawir); | 
 | } | 
 |  | 
 | static void sz_push_full_pulse(struct streamzap_ir *sz, | 
 | 			       unsigned char value) | 
 | { | 
 | 	DEFINE_IR_RAW_EVENT(rawir); | 
 |  | 
 | 	if (sz->idle) { | 
 | 		int delta; | 
 |  | 
 | 		sz->signal_last = sz->signal_start; | 
 | 		sz->signal_start = ktime_get_real(); | 
 |  | 
 | 		delta = ktime_us_delta(sz->signal_start, sz->signal_last); | 
 | 		rawir.pulse = false; | 
 | 		if (delta > (15 * USEC_PER_SEC)) { | 
 | 			/* really long time */ | 
 | 			rawir.duration = IR_MAX_DURATION; | 
 | 		} else { | 
 | 			rawir.duration = delta; | 
 | 			rawir.duration -= sz->sum; | 
 | 			rawir.duration = US_TO_NS(rawir.duration); | 
 | 			rawir.duration = (rawir.duration > IR_MAX_DURATION) ? | 
 | 					 IR_MAX_DURATION : rawir.duration; | 
 | 		} | 
 | 		sz_push(sz, rawir); | 
 |  | 
 | 		sz->idle = false; | 
 | 		sz->sum = 0; | 
 | 	} | 
 |  | 
 | 	rawir.pulse = true; | 
 | 	rawir.duration = ((int) value) * SZ_RESOLUTION; | 
 | 	rawir.duration += SZ_RESOLUTION / 2; | 
 | 	sz->sum += rawir.duration; | 
 | 	rawir.duration = US_TO_NS(rawir.duration); | 
 | 	rawir.duration = (rawir.duration > IR_MAX_DURATION) ? | 
 | 			 IR_MAX_DURATION : rawir.duration; | 
 | 	sz_push(sz, rawir); | 
 | } | 
 |  | 
 | static void sz_push_half_pulse(struct streamzap_ir *sz, | 
 | 			       unsigned char value) | 
 | { | 
 | 	sz_push_full_pulse(sz, (value & SZ_PULSE_MASK) >> 4); | 
 | } | 
 |  | 
 | static void sz_push_full_space(struct streamzap_ir *sz, | 
 | 			       unsigned char value) | 
 | { | 
 | 	DEFINE_IR_RAW_EVENT(rawir); | 
 |  | 
 | 	rawir.pulse = false; | 
 | 	rawir.duration = ((int) value) * SZ_RESOLUTION; | 
 | 	rawir.duration += SZ_RESOLUTION / 2; | 
 | 	sz->sum += rawir.duration; | 
 | 	rawir.duration = US_TO_NS(rawir.duration); | 
 | 	sz_push(sz, rawir); | 
 | } | 
 |  | 
 | static void sz_push_half_space(struct streamzap_ir *sz, | 
 | 			       unsigned long value) | 
 | { | 
 | 	sz_push_full_space(sz, value & SZ_SPACE_MASK); | 
 | } | 
 |  | 
 | /* | 
 |  * streamzap_callback - usb IRQ handler callback | 
 |  * | 
 |  * This procedure is invoked on reception of data from | 
 |  * the usb remote. | 
 |  */ | 
 | static void streamzap_callback(struct urb *urb) | 
 | { | 
 | 	struct streamzap_ir *sz; | 
 | 	unsigned int i; | 
 | 	int len; | 
 |  | 
 | 	if (!urb) | 
 | 		return; | 
 |  | 
 | 	sz = urb->context; | 
 | 	len = urb->actual_length; | 
 |  | 
 | 	switch (urb->status) { | 
 | 	case -ECONNRESET: | 
 | 	case -ENOENT: | 
 | 	case -ESHUTDOWN: | 
 | 		/* | 
 | 		 * this urb is terminated, clean up. | 
 | 		 * sz might already be invalid at this point | 
 | 		 */ | 
 | 		dev_err(sz->dev, "urb terminated, status: %d\n", urb->status); | 
 | 		return; | 
 | 	default: | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	dev_dbg(sz->dev, "%s: received urb, len %d\n", __func__, len); | 
 | 	for (i = 0; i < len; i++) { | 
 | 		dev_dbg(sz->dev, "sz->buf_in[%d]: %x\n", | 
 | 			i, (unsigned char)sz->buf_in[i]); | 
 | 		switch (sz->decoder_state) { | 
 | 		case PulseSpace: | 
 | 			if ((sz->buf_in[i] & SZ_PULSE_MASK) == | 
 | 				SZ_PULSE_MASK) { | 
 | 				sz->decoder_state = FullPulse; | 
 | 				continue; | 
 | 			} else if ((sz->buf_in[i] & SZ_SPACE_MASK) | 
 | 					== SZ_SPACE_MASK) { | 
 | 				sz_push_half_pulse(sz, sz->buf_in[i]); | 
 | 				sz->decoder_state = FullSpace; | 
 | 				continue; | 
 | 			} else { | 
 | 				sz_push_half_pulse(sz, sz->buf_in[i]); | 
 | 				sz_push_half_space(sz, sz->buf_in[i]); | 
 | 			} | 
 | 			break; | 
 | 		case FullPulse: | 
 | 			sz_push_full_pulse(sz, sz->buf_in[i]); | 
 | 			sz->decoder_state = IgnorePulse; | 
 | 			break; | 
 | 		case FullSpace: | 
 | 			if (sz->buf_in[i] == SZ_TIMEOUT) { | 
 | 				DEFINE_IR_RAW_EVENT(rawir); | 
 |  | 
 | 				rawir.pulse = false; | 
 | 				rawir.duration = sz->rdev->timeout; | 
 | 				sz->idle = true; | 
 | 				if (sz->timeout_enabled) | 
 | 					sz_push(sz, rawir); | 
 | 				ir_raw_event_handle(sz->rdev); | 
 | 				ir_raw_event_reset(sz->rdev); | 
 | 			} else { | 
 | 				sz_push_full_space(sz, sz->buf_in[i]); | 
 | 			} | 
 | 			sz->decoder_state = PulseSpace; | 
 | 			break; | 
 | 		case IgnorePulse: | 
 | 			if ((sz->buf_in[i] & SZ_SPACE_MASK) == | 
 | 				SZ_SPACE_MASK) { | 
 | 				sz->decoder_state = FullSpace; | 
 | 				continue; | 
 | 			} | 
 | 			sz_push_half_space(sz, sz->buf_in[i]); | 
 | 			sz->decoder_state = PulseSpace; | 
 | 			break; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	ir_raw_event_handle(sz->rdev); | 
 | 	usb_submit_urb(urb, GFP_ATOMIC); | 
 |  | 
 | 	return; | 
 | } | 
 |  | 
 | static struct rc_dev *streamzap_init_rc_dev(struct streamzap_ir *sz) | 
 | { | 
 | 	struct rc_dev *rdev; | 
 | 	struct device *dev = sz->dev; | 
 | 	int ret; | 
 |  | 
 | 	rdev = rc_allocate_device(RC_DRIVER_IR_RAW); | 
 | 	if (!rdev) { | 
 | 		dev_err(dev, "remote dev allocation failed\n"); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	snprintf(sz->name, sizeof(sz->name), "Streamzap PC Remote Infrared Receiver (%04x:%04x)", | 
 | 		 le16_to_cpu(sz->usbdev->descriptor.idVendor), | 
 | 		 le16_to_cpu(sz->usbdev->descriptor.idProduct)); | 
 | 	usb_make_path(sz->usbdev, sz->phys, sizeof(sz->phys)); | 
 | 	strlcat(sz->phys, "/input0", sizeof(sz->phys)); | 
 |  | 
 | 	rdev->device_name = sz->name; | 
 | 	rdev->input_phys = sz->phys; | 
 | 	usb_to_input_id(sz->usbdev, &rdev->input_id); | 
 | 	rdev->dev.parent = dev; | 
 | 	rdev->priv = sz; | 
 | 	rdev->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER; | 
 | 	rdev->driver_name = DRIVER_NAME; | 
 | 	rdev->map_name = RC_MAP_STREAMZAP; | 
 |  | 
 | 	ret = rc_register_device(rdev); | 
 | 	if (ret < 0) { | 
 | 		dev_err(dev, "remote input device register failed\n"); | 
 | 		goto out; | 
 | 	} | 
 |  | 
 | 	return rdev; | 
 |  | 
 | out: | 
 | 	rc_free_device(rdev); | 
 | 	return NULL; | 
 | } | 
 |  | 
 | /* | 
 |  *	streamzap_probe | 
 |  * | 
 |  *	Called by usb-core to associated with a candidate device | 
 |  *	On any failure the return value is the ERROR | 
 |  *	On success return 0 | 
 |  */ | 
 | static int streamzap_probe(struct usb_interface *intf, | 
 | 			   const struct usb_device_id *id) | 
 | { | 
 | 	struct usb_device *usbdev = interface_to_usbdev(intf); | 
 | 	struct usb_host_interface *iface_host; | 
 | 	struct streamzap_ir *sz = NULL; | 
 | 	char buf[63], name[128] = ""; | 
 | 	int retval = -ENOMEM; | 
 | 	int pipe, maxp; | 
 |  | 
 | 	/* Allocate space for device driver specific data */ | 
 | 	sz = kzalloc(sizeof(struct streamzap_ir), GFP_KERNEL); | 
 | 	if (!sz) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	sz->usbdev = usbdev; | 
 | 	sz->interface = intf; | 
 |  | 
 | 	/* Check to ensure endpoint information matches requirements */ | 
 | 	iface_host = intf->cur_altsetting; | 
 |  | 
 | 	if (iface_host->desc.bNumEndpoints != 1) { | 
 | 		dev_err(&intf->dev, "%s: Unexpected desc.bNumEndpoints (%d)\n", | 
 | 			__func__, iface_host->desc.bNumEndpoints); | 
 | 		retval = -ENODEV; | 
 | 		goto free_sz; | 
 | 	} | 
 |  | 
 | 	sz->endpoint = &(iface_host->endpoint[0].desc); | 
 | 	if (!usb_endpoint_dir_in(sz->endpoint)) { | 
 | 		dev_err(&intf->dev, "%s: endpoint doesn't match input device 02%02x\n", | 
 | 			__func__, sz->endpoint->bEndpointAddress); | 
 | 		retval = -ENODEV; | 
 | 		goto free_sz; | 
 | 	} | 
 |  | 
 | 	if (!usb_endpoint_xfer_int(sz->endpoint)) { | 
 | 		dev_err(&intf->dev, "%s: endpoint attributes don't match xfer 02%02x\n", | 
 | 			__func__, sz->endpoint->bmAttributes); | 
 | 		retval = -ENODEV; | 
 | 		goto free_sz; | 
 | 	} | 
 |  | 
 | 	pipe = usb_rcvintpipe(usbdev, sz->endpoint->bEndpointAddress); | 
 | 	maxp = usb_maxpacket(usbdev, pipe, usb_pipeout(pipe)); | 
 |  | 
 | 	if (maxp == 0) { | 
 | 		dev_err(&intf->dev, "%s: endpoint Max Packet Size is 0!?!\n", | 
 | 			__func__); | 
 | 		retval = -ENODEV; | 
 | 		goto free_sz; | 
 | 	} | 
 |  | 
 | 	/* Allocate the USB buffer and IRQ URB */ | 
 | 	sz->buf_in = usb_alloc_coherent(usbdev, maxp, GFP_ATOMIC, &sz->dma_in); | 
 | 	if (!sz->buf_in) | 
 | 		goto free_sz; | 
 |  | 
 | 	sz->urb_in = usb_alloc_urb(0, GFP_KERNEL); | 
 | 	if (!sz->urb_in) | 
 | 		goto free_buf_in; | 
 |  | 
 | 	sz->dev = &intf->dev; | 
 | 	sz->buf_in_len = maxp; | 
 |  | 
 | 	if (usbdev->descriptor.iManufacturer | 
 | 	    && usb_string(usbdev, usbdev->descriptor.iManufacturer, | 
 | 			  buf, sizeof(buf)) > 0) | 
 | 		strlcpy(name, buf, sizeof(name)); | 
 |  | 
 | 	if (usbdev->descriptor.iProduct | 
 | 	    && usb_string(usbdev, usbdev->descriptor.iProduct, | 
 | 			  buf, sizeof(buf)) > 0) | 
 | 		snprintf(name + strlen(name), sizeof(name) - strlen(name), | 
 | 			 " %s", buf); | 
 |  | 
 | 	sz->rdev = streamzap_init_rc_dev(sz); | 
 | 	if (!sz->rdev) | 
 | 		goto rc_dev_fail; | 
 |  | 
 | 	sz->idle = true; | 
 | 	sz->decoder_state = PulseSpace; | 
 | 	/* FIXME: don't yet have a way to set this */ | 
 | 	sz->timeout_enabled = true; | 
 | 	sz->rdev->timeout = ((US_TO_NS(SZ_TIMEOUT * SZ_RESOLUTION) & | 
 | 				IR_MAX_DURATION) | 0x03000000); | 
 | 	#if 0 | 
 | 	/* not yet supported, depends on patches from maxim */ | 
 | 	/* see also: LIRC_GET_REC_RESOLUTION and LIRC_SET_REC_TIMEOUT */ | 
 | 	sz->min_timeout = US_TO_NS(SZ_TIMEOUT * SZ_RESOLUTION); | 
 | 	sz->max_timeout = US_TO_NS(SZ_TIMEOUT * SZ_RESOLUTION); | 
 | 	#endif | 
 |  | 
 | 	sz->signal_start = ktime_get_real(); | 
 |  | 
 | 	/* Complete final initialisations */ | 
 | 	usb_fill_int_urb(sz->urb_in, usbdev, pipe, sz->buf_in, | 
 | 			 maxp, (usb_complete_t)streamzap_callback, | 
 | 			 sz, sz->endpoint->bInterval); | 
 | 	sz->urb_in->transfer_dma = sz->dma_in; | 
 | 	sz->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; | 
 |  | 
 | 	usb_set_intfdata(intf, sz); | 
 |  | 
 | 	if (usb_submit_urb(sz->urb_in, GFP_ATOMIC)) | 
 | 		dev_err(sz->dev, "urb submit failed\n"); | 
 |  | 
 | 	dev_info(sz->dev, "Registered %s on usb%d:%d\n", name, | 
 | 		 usbdev->bus->busnum, usbdev->devnum); | 
 |  | 
 | 	return 0; | 
 |  | 
 | rc_dev_fail: | 
 | 	usb_free_urb(sz->urb_in); | 
 | free_buf_in: | 
 | 	usb_free_coherent(usbdev, maxp, sz->buf_in, sz->dma_in); | 
 | free_sz: | 
 | 	kfree(sz); | 
 |  | 
 | 	return retval; | 
 | } | 
 |  | 
 | /* | 
 |  * streamzap_disconnect | 
 |  * | 
 |  * Called by the usb core when the device is removed from the system. | 
 |  * | 
 |  * This routine guarantees that the driver will not submit any more urbs | 
 |  * by clearing dev->usbdev.  It is also supposed to terminate any currently | 
 |  * active urbs.  Unfortunately, usb_bulk_msg(), used in streamzap_read(), | 
 |  * does not provide any way to do this. | 
 |  */ | 
 | static void streamzap_disconnect(struct usb_interface *interface) | 
 | { | 
 | 	struct streamzap_ir *sz = usb_get_intfdata(interface); | 
 | 	struct usb_device *usbdev = interface_to_usbdev(interface); | 
 |  | 
 | 	usb_set_intfdata(interface, NULL); | 
 |  | 
 | 	if (!sz) | 
 | 		return; | 
 |  | 
 | 	sz->usbdev = NULL; | 
 | 	rc_unregister_device(sz->rdev); | 
 | 	usb_kill_urb(sz->urb_in); | 
 | 	usb_free_urb(sz->urb_in); | 
 | 	usb_free_coherent(usbdev, sz->buf_in_len, sz->buf_in, sz->dma_in); | 
 |  | 
 | 	kfree(sz); | 
 | } | 
 |  | 
 | static int streamzap_suspend(struct usb_interface *intf, pm_message_t message) | 
 | { | 
 | 	struct streamzap_ir *sz = usb_get_intfdata(intf); | 
 |  | 
 | 	usb_kill_urb(sz->urb_in); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int streamzap_resume(struct usb_interface *intf) | 
 | { | 
 | 	struct streamzap_ir *sz = usb_get_intfdata(intf); | 
 |  | 
 | 	if (usb_submit_urb(sz->urb_in, GFP_ATOMIC)) { | 
 | 		dev_err(sz->dev, "Error submitting urb\n"); | 
 | 		return -EIO; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | module_usb_driver(streamzap_driver); | 
 |  | 
 | MODULE_AUTHOR("Jarod Wilson <jarod@wilsonet.com>"); | 
 | MODULE_DESCRIPTION(DRIVER_DESC); | 
 | MODULE_LICENSE("GPL"); |