18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * TechnoTrend USB IR Receiver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Copyright (C) 2012 Sean Young <sean@mess.org>
68c2ecf20Sopenharmony_ci */
78c2ecf20Sopenharmony_ci
88c2ecf20Sopenharmony_ci#include <linux/module.h>
98c2ecf20Sopenharmony_ci#include <linux/usb.h>
108c2ecf20Sopenharmony_ci#include <linux/usb/input.h>
118c2ecf20Sopenharmony_ci#include <linux/slab.h>
128c2ecf20Sopenharmony_ci#include <linux/leds.h>
138c2ecf20Sopenharmony_ci#include <media/rc-core.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci#define DRIVER_NAME	"ttusbir"
168c2ecf20Sopenharmony_ci#define DRIVER_DESC	"TechnoTrend USB IR Receiver"
178c2ecf20Sopenharmony_ci/*
188c2ecf20Sopenharmony_ci * The Windows driver uses 8 URBS, the original lirc drivers has a
198c2ecf20Sopenharmony_ci * configurable amount (2 default, 4 max). This device generates about 125
208c2ecf20Sopenharmony_ci * messages per second (!), whether IR is idle or not.
218c2ecf20Sopenharmony_ci */
228c2ecf20Sopenharmony_ci#define NUM_URBS	4
238c2ecf20Sopenharmony_ci#define US_PER_BYTE	62
248c2ecf20Sopenharmony_ci#define US_PER_BIT	(US_PER_BYTE / 8)
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistruct ttusbir {
278c2ecf20Sopenharmony_ci	struct rc_dev *rc;
288c2ecf20Sopenharmony_ci	struct device *dev;
298c2ecf20Sopenharmony_ci	struct usb_device *udev;
308c2ecf20Sopenharmony_ci
318c2ecf20Sopenharmony_ci	struct urb *urb[NUM_URBS];
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	struct led_classdev led;
348c2ecf20Sopenharmony_ci	struct urb *bulk_urb;
358c2ecf20Sopenharmony_ci	uint8_t bulk_buffer[5];
368c2ecf20Sopenharmony_ci	int bulk_out_endp, iso_in_endp;
378c2ecf20Sopenharmony_ci	bool led_on, is_led_on;
388c2ecf20Sopenharmony_ci	atomic_t led_complete;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	char phys[64];
418c2ecf20Sopenharmony_ci};
428c2ecf20Sopenharmony_ci
438c2ecf20Sopenharmony_cistatic enum led_brightness ttusbir_brightness_get(struct led_classdev *led_dev)
448c2ecf20Sopenharmony_ci{
458c2ecf20Sopenharmony_ci	struct ttusbir *tt = container_of(led_dev, struct ttusbir, led);
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	return tt->led_on ? LED_FULL : LED_OFF;
488c2ecf20Sopenharmony_ci}
498c2ecf20Sopenharmony_ci
508c2ecf20Sopenharmony_cistatic void ttusbir_set_led(struct ttusbir *tt)
518c2ecf20Sopenharmony_ci{
528c2ecf20Sopenharmony_ci	int ret;
538c2ecf20Sopenharmony_ci
548c2ecf20Sopenharmony_ci	smp_mb();
558c2ecf20Sopenharmony_ci
568c2ecf20Sopenharmony_ci	if (tt->led_on != tt->is_led_on && tt->udev &&
578c2ecf20Sopenharmony_ci				atomic_add_unless(&tt->led_complete, 1, 1)) {
588c2ecf20Sopenharmony_ci		tt->bulk_buffer[4] = tt->is_led_on = tt->led_on;
598c2ecf20Sopenharmony_ci		ret = usb_submit_urb(tt->bulk_urb, GFP_ATOMIC);
608c2ecf20Sopenharmony_ci		if (ret) {
618c2ecf20Sopenharmony_ci			dev_warn(tt->dev, "failed to submit bulk urb: %d\n",
628c2ecf20Sopenharmony_ci									ret);
638c2ecf20Sopenharmony_ci			atomic_dec(&tt->led_complete);
648c2ecf20Sopenharmony_ci		}
658c2ecf20Sopenharmony_ci	}
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cistatic void ttusbir_brightness_set(struct led_classdev *led_dev, enum
698c2ecf20Sopenharmony_ci						led_brightness brightness)
708c2ecf20Sopenharmony_ci{
718c2ecf20Sopenharmony_ci	struct ttusbir *tt = container_of(led_dev, struct ttusbir, led);
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_ci	tt->led_on = brightness != LED_OFF;
748c2ecf20Sopenharmony_ci
758c2ecf20Sopenharmony_ci	ttusbir_set_led(tt);
768c2ecf20Sopenharmony_ci}
778c2ecf20Sopenharmony_ci
788c2ecf20Sopenharmony_ci/*
798c2ecf20Sopenharmony_ci * The urb cannot be reused until the urb completes
808c2ecf20Sopenharmony_ci */
818c2ecf20Sopenharmony_cistatic void ttusbir_bulk_complete(struct urb *urb)
828c2ecf20Sopenharmony_ci{
838c2ecf20Sopenharmony_ci	struct ttusbir *tt = urb->context;
848c2ecf20Sopenharmony_ci
858c2ecf20Sopenharmony_ci	atomic_dec(&tt->led_complete);
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci	switch (urb->status) {
888c2ecf20Sopenharmony_ci	case 0:
898c2ecf20Sopenharmony_ci		break;
908c2ecf20Sopenharmony_ci	case -ECONNRESET:
918c2ecf20Sopenharmony_ci	case -ENOENT:
928c2ecf20Sopenharmony_ci	case -ESHUTDOWN:
938c2ecf20Sopenharmony_ci		usb_unlink_urb(urb);
948c2ecf20Sopenharmony_ci		return;
958c2ecf20Sopenharmony_ci	case -EPIPE:
968c2ecf20Sopenharmony_ci	default:
978c2ecf20Sopenharmony_ci		dev_dbg(tt->dev, "Error: urb status = %d\n", urb->status);
988c2ecf20Sopenharmony_ci		break;
998c2ecf20Sopenharmony_ci	}
1008c2ecf20Sopenharmony_ci
1018c2ecf20Sopenharmony_ci	ttusbir_set_led(tt);
1028c2ecf20Sopenharmony_ci}
1038c2ecf20Sopenharmony_ci
1048c2ecf20Sopenharmony_ci/*
1058c2ecf20Sopenharmony_ci * The data is one bit per sample, a set bit signifying silence and samples
1068c2ecf20Sopenharmony_ci * being MSB first. Bit 0 can contain garbage so take it to be whatever
1078c2ecf20Sopenharmony_ci * bit 1 is, so we don't have unexpected edges.
1088c2ecf20Sopenharmony_ci */
1098c2ecf20Sopenharmony_cistatic void ttusbir_process_ir_data(struct ttusbir *tt, uint8_t *buf)
1108c2ecf20Sopenharmony_ci{
1118c2ecf20Sopenharmony_ci	struct ir_raw_event rawir = {};
1128c2ecf20Sopenharmony_ci	unsigned i, v, b;
1138c2ecf20Sopenharmony_ci	bool event = false;
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ci	for (i = 0; i < 128; i++) {
1168c2ecf20Sopenharmony_ci		v = buf[i] & 0xfe;
1178c2ecf20Sopenharmony_ci		switch (v) {
1188c2ecf20Sopenharmony_ci		case 0xfe:
1198c2ecf20Sopenharmony_ci			rawir.pulse = false;
1208c2ecf20Sopenharmony_ci			rawir.duration = US_PER_BYTE;
1218c2ecf20Sopenharmony_ci			if (ir_raw_event_store_with_filter(tt->rc, &rawir))
1228c2ecf20Sopenharmony_ci				event = true;
1238c2ecf20Sopenharmony_ci			break;
1248c2ecf20Sopenharmony_ci		case 0:
1258c2ecf20Sopenharmony_ci			rawir.pulse = true;
1268c2ecf20Sopenharmony_ci			rawir.duration = US_PER_BYTE;
1278c2ecf20Sopenharmony_ci			if (ir_raw_event_store_with_filter(tt->rc, &rawir))
1288c2ecf20Sopenharmony_ci				event = true;
1298c2ecf20Sopenharmony_ci			break;
1308c2ecf20Sopenharmony_ci		default:
1318c2ecf20Sopenharmony_ci			/* one edge per byte */
1328c2ecf20Sopenharmony_ci			if (v & 2) {
1338c2ecf20Sopenharmony_ci				b = ffz(v | 1);
1348c2ecf20Sopenharmony_ci				rawir.pulse = true;
1358c2ecf20Sopenharmony_ci			} else {
1368c2ecf20Sopenharmony_ci				b = ffs(v) - 1;
1378c2ecf20Sopenharmony_ci				rawir.pulse = false;
1388c2ecf20Sopenharmony_ci			}
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci			rawir.duration = US_PER_BIT * (8 - b);
1418c2ecf20Sopenharmony_ci			if (ir_raw_event_store_with_filter(tt->rc, &rawir))
1428c2ecf20Sopenharmony_ci				event = true;
1438c2ecf20Sopenharmony_ci
1448c2ecf20Sopenharmony_ci			rawir.pulse = !rawir.pulse;
1458c2ecf20Sopenharmony_ci			rawir.duration = US_PER_BIT * b;
1468c2ecf20Sopenharmony_ci			if (ir_raw_event_store_with_filter(tt->rc, &rawir))
1478c2ecf20Sopenharmony_ci				event = true;
1488c2ecf20Sopenharmony_ci			break;
1498c2ecf20Sopenharmony_ci		}
1508c2ecf20Sopenharmony_ci	}
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	/* don't wakeup when there's nothing to do */
1538c2ecf20Sopenharmony_ci	if (event)
1548c2ecf20Sopenharmony_ci		ir_raw_event_handle(tt->rc);
1558c2ecf20Sopenharmony_ci}
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_cistatic void ttusbir_urb_complete(struct urb *urb)
1588c2ecf20Sopenharmony_ci{
1598c2ecf20Sopenharmony_ci	struct ttusbir *tt = urb->context;
1608c2ecf20Sopenharmony_ci	int rc;
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	switch (urb->status) {
1638c2ecf20Sopenharmony_ci	case 0:
1648c2ecf20Sopenharmony_ci		ttusbir_process_ir_data(tt, urb->transfer_buffer);
1658c2ecf20Sopenharmony_ci		break;
1668c2ecf20Sopenharmony_ci	case -ECONNRESET:
1678c2ecf20Sopenharmony_ci	case -ENOENT:
1688c2ecf20Sopenharmony_ci	case -ESHUTDOWN:
1698c2ecf20Sopenharmony_ci		usb_unlink_urb(urb);
1708c2ecf20Sopenharmony_ci		return;
1718c2ecf20Sopenharmony_ci	case -EPIPE:
1728c2ecf20Sopenharmony_ci	default:
1738c2ecf20Sopenharmony_ci		dev_dbg(tt->dev, "Error: urb status = %d\n", urb->status);
1748c2ecf20Sopenharmony_ci		break;
1758c2ecf20Sopenharmony_ci	}
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_ci	rc = usb_submit_urb(urb, GFP_ATOMIC);
1788c2ecf20Sopenharmony_ci	if (rc && rc != -ENODEV)
1798c2ecf20Sopenharmony_ci		dev_warn(tt->dev, "failed to resubmit urb: %d\n", rc);
1808c2ecf20Sopenharmony_ci}
1818c2ecf20Sopenharmony_ci
1828c2ecf20Sopenharmony_cistatic int ttusbir_probe(struct usb_interface *intf,
1838c2ecf20Sopenharmony_ci			 const struct usb_device_id *id)
1848c2ecf20Sopenharmony_ci{
1858c2ecf20Sopenharmony_ci	struct ttusbir *tt;
1868c2ecf20Sopenharmony_ci	struct usb_interface_descriptor *idesc;
1878c2ecf20Sopenharmony_ci	struct usb_endpoint_descriptor *desc;
1888c2ecf20Sopenharmony_ci	struct rc_dev *rc;
1898c2ecf20Sopenharmony_ci	int i, j, ret;
1908c2ecf20Sopenharmony_ci	int altsetting = -1;
1918c2ecf20Sopenharmony_ci
1928c2ecf20Sopenharmony_ci	tt = kzalloc(sizeof(*tt), GFP_KERNEL);
1938c2ecf20Sopenharmony_ci	rc = rc_allocate_device(RC_DRIVER_IR_RAW);
1948c2ecf20Sopenharmony_ci	if (!tt || !rc) {
1958c2ecf20Sopenharmony_ci		ret = -ENOMEM;
1968c2ecf20Sopenharmony_ci		goto out;
1978c2ecf20Sopenharmony_ci	}
1988c2ecf20Sopenharmony_ci
1998c2ecf20Sopenharmony_ci	/* find the correct alt setting */
2008c2ecf20Sopenharmony_ci	for (i = 0; i < intf->num_altsetting && altsetting == -1; i++) {
2018c2ecf20Sopenharmony_ci		int max_packet, bulk_out_endp = -1, iso_in_endp = -1;
2028c2ecf20Sopenharmony_ci
2038c2ecf20Sopenharmony_ci		idesc = &intf->altsetting[i].desc;
2048c2ecf20Sopenharmony_ci
2058c2ecf20Sopenharmony_ci		for (j = 0; j < idesc->bNumEndpoints; j++) {
2068c2ecf20Sopenharmony_ci			desc = &intf->altsetting[i].endpoint[j].desc;
2078c2ecf20Sopenharmony_ci			max_packet = le16_to_cpu(desc->wMaxPacketSize);
2088c2ecf20Sopenharmony_ci			if (usb_endpoint_dir_in(desc) &&
2098c2ecf20Sopenharmony_ci					usb_endpoint_xfer_isoc(desc) &&
2108c2ecf20Sopenharmony_ci					max_packet == 0x10)
2118c2ecf20Sopenharmony_ci				iso_in_endp = j;
2128c2ecf20Sopenharmony_ci			else if (usb_endpoint_dir_out(desc) &&
2138c2ecf20Sopenharmony_ci					usb_endpoint_xfer_bulk(desc) &&
2148c2ecf20Sopenharmony_ci					max_packet == 0x20)
2158c2ecf20Sopenharmony_ci				bulk_out_endp = j;
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci			if (bulk_out_endp != -1 && iso_in_endp != -1) {
2188c2ecf20Sopenharmony_ci				tt->bulk_out_endp = bulk_out_endp;
2198c2ecf20Sopenharmony_ci				tt->iso_in_endp = iso_in_endp;
2208c2ecf20Sopenharmony_ci				altsetting = i;
2218c2ecf20Sopenharmony_ci				break;
2228c2ecf20Sopenharmony_ci			}
2238c2ecf20Sopenharmony_ci		}
2248c2ecf20Sopenharmony_ci	}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci	if (altsetting == -1) {
2278c2ecf20Sopenharmony_ci		dev_err(&intf->dev, "cannot find expected altsetting\n");
2288c2ecf20Sopenharmony_ci		ret = -ENODEV;
2298c2ecf20Sopenharmony_ci		goto out;
2308c2ecf20Sopenharmony_ci	}
2318c2ecf20Sopenharmony_ci
2328c2ecf20Sopenharmony_ci	tt->dev = &intf->dev;
2338c2ecf20Sopenharmony_ci	tt->udev = interface_to_usbdev(intf);
2348c2ecf20Sopenharmony_ci	tt->rc = rc;
2358c2ecf20Sopenharmony_ci
2368c2ecf20Sopenharmony_ci	ret = usb_set_interface(tt->udev, 0, altsetting);
2378c2ecf20Sopenharmony_ci	if (ret)
2388c2ecf20Sopenharmony_ci		goto out;
2398c2ecf20Sopenharmony_ci
2408c2ecf20Sopenharmony_ci	for (i = 0; i < NUM_URBS; i++) {
2418c2ecf20Sopenharmony_ci		struct urb *urb = usb_alloc_urb(8, GFP_KERNEL);
2428c2ecf20Sopenharmony_ci		void *buffer;
2438c2ecf20Sopenharmony_ci
2448c2ecf20Sopenharmony_ci		if (!urb) {
2458c2ecf20Sopenharmony_ci			ret = -ENOMEM;
2468c2ecf20Sopenharmony_ci			goto out;
2478c2ecf20Sopenharmony_ci		}
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci		urb->dev = tt->udev;
2508c2ecf20Sopenharmony_ci		urb->context = tt;
2518c2ecf20Sopenharmony_ci		urb->pipe = usb_rcvisocpipe(tt->udev, tt->iso_in_endp);
2528c2ecf20Sopenharmony_ci		urb->interval = 1;
2538c2ecf20Sopenharmony_ci		buffer = usb_alloc_coherent(tt->udev, 128, GFP_KERNEL,
2548c2ecf20Sopenharmony_ci						&urb->transfer_dma);
2558c2ecf20Sopenharmony_ci		if (!buffer) {
2568c2ecf20Sopenharmony_ci			usb_free_urb(urb);
2578c2ecf20Sopenharmony_ci			ret = -ENOMEM;
2588c2ecf20Sopenharmony_ci			goto out;
2598c2ecf20Sopenharmony_ci		}
2608c2ecf20Sopenharmony_ci		urb->transfer_flags = URB_NO_TRANSFER_DMA_MAP | URB_ISO_ASAP;
2618c2ecf20Sopenharmony_ci		urb->transfer_buffer = buffer;
2628c2ecf20Sopenharmony_ci		urb->complete = ttusbir_urb_complete;
2638c2ecf20Sopenharmony_ci		urb->number_of_packets = 8;
2648c2ecf20Sopenharmony_ci		urb->transfer_buffer_length = 128;
2658c2ecf20Sopenharmony_ci
2668c2ecf20Sopenharmony_ci		for (j = 0; j < 8; j++) {
2678c2ecf20Sopenharmony_ci			urb->iso_frame_desc[j].offset = j * 16;
2688c2ecf20Sopenharmony_ci			urb->iso_frame_desc[j].length = 16;
2698c2ecf20Sopenharmony_ci		}
2708c2ecf20Sopenharmony_ci
2718c2ecf20Sopenharmony_ci		tt->urb[i] = urb;
2728c2ecf20Sopenharmony_ci	}
2738c2ecf20Sopenharmony_ci
2748c2ecf20Sopenharmony_ci	tt->bulk_urb = usb_alloc_urb(0, GFP_KERNEL);
2758c2ecf20Sopenharmony_ci	if (!tt->bulk_urb) {
2768c2ecf20Sopenharmony_ci		ret = -ENOMEM;
2778c2ecf20Sopenharmony_ci		goto out;
2788c2ecf20Sopenharmony_ci	}
2798c2ecf20Sopenharmony_ci
2808c2ecf20Sopenharmony_ci	tt->bulk_buffer[0] = 0xaa;
2818c2ecf20Sopenharmony_ci	tt->bulk_buffer[1] = 0x01;
2828c2ecf20Sopenharmony_ci	tt->bulk_buffer[2] = 0x05;
2838c2ecf20Sopenharmony_ci	tt->bulk_buffer[3] = 0x01;
2848c2ecf20Sopenharmony_ci
2858c2ecf20Sopenharmony_ci	usb_fill_bulk_urb(tt->bulk_urb, tt->udev, usb_sndbulkpipe(tt->udev,
2868c2ecf20Sopenharmony_ci		tt->bulk_out_endp), tt->bulk_buffer, sizeof(tt->bulk_buffer),
2878c2ecf20Sopenharmony_ci						ttusbir_bulk_complete, tt);
2888c2ecf20Sopenharmony_ci
2898c2ecf20Sopenharmony_ci	tt->led.name = "ttusbir:green:power";
2908c2ecf20Sopenharmony_ci	tt->led.default_trigger = "rc-feedback";
2918c2ecf20Sopenharmony_ci	tt->led.brightness_set = ttusbir_brightness_set;
2928c2ecf20Sopenharmony_ci	tt->led.brightness_get = ttusbir_brightness_get;
2938c2ecf20Sopenharmony_ci	tt->is_led_on = tt->led_on = true;
2948c2ecf20Sopenharmony_ci	atomic_set(&tt->led_complete, 0);
2958c2ecf20Sopenharmony_ci	ret = led_classdev_register(&intf->dev, &tt->led);
2968c2ecf20Sopenharmony_ci	if (ret)
2978c2ecf20Sopenharmony_ci		goto out;
2988c2ecf20Sopenharmony_ci
2998c2ecf20Sopenharmony_ci	usb_make_path(tt->udev, tt->phys, sizeof(tt->phys));
3008c2ecf20Sopenharmony_ci
3018c2ecf20Sopenharmony_ci	rc->device_name = DRIVER_DESC;
3028c2ecf20Sopenharmony_ci	rc->input_phys = tt->phys;
3038c2ecf20Sopenharmony_ci	usb_to_input_id(tt->udev, &rc->input_id);
3048c2ecf20Sopenharmony_ci	rc->dev.parent = &intf->dev;
3058c2ecf20Sopenharmony_ci	rc->allowed_protocols = RC_PROTO_BIT_ALL_IR_DECODER;
3068c2ecf20Sopenharmony_ci	rc->priv = tt;
3078c2ecf20Sopenharmony_ci	rc->driver_name = DRIVER_NAME;
3088c2ecf20Sopenharmony_ci	rc->map_name = RC_MAP_TT_1500;
3098c2ecf20Sopenharmony_ci	rc->min_timeout = 1;
3108c2ecf20Sopenharmony_ci	rc->timeout = IR_DEFAULT_TIMEOUT;
3118c2ecf20Sopenharmony_ci	rc->max_timeout = 10 * IR_DEFAULT_TIMEOUT;
3128c2ecf20Sopenharmony_ci
3138c2ecf20Sopenharmony_ci	/*
3148c2ecf20Sopenharmony_ci	 * The precision is US_PER_BIT, but since every 8th bit can be
3158c2ecf20Sopenharmony_ci	 * overwritten with garbage the accuracy is at best 2 * US_PER_BIT.
3168c2ecf20Sopenharmony_ci	 */
3178c2ecf20Sopenharmony_ci	rc->rx_resolution = 2 * US_PER_BIT;
3188c2ecf20Sopenharmony_ci
3198c2ecf20Sopenharmony_ci	ret = rc_register_device(rc);
3208c2ecf20Sopenharmony_ci	if (ret) {
3218c2ecf20Sopenharmony_ci		dev_err(&intf->dev, "failed to register rc device %d\n", ret);
3228c2ecf20Sopenharmony_ci		goto out2;
3238c2ecf20Sopenharmony_ci	}
3248c2ecf20Sopenharmony_ci
3258c2ecf20Sopenharmony_ci	usb_set_intfdata(intf, tt);
3268c2ecf20Sopenharmony_ci
3278c2ecf20Sopenharmony_ci	for (i = 0; i < NUM_URBS; i++) {
3288c2ecf20Sopenharmony_ci		ret = usb_submit_urb(tt->urb[i], GFP_KERNEL);
3298c2ecf20Sopenharmony_ci		if (ret) {
3308c2ecf20Sopenharmony_ci			dev_err(tt->dev, "failed to submit urb %d\n", ret);
3318c2ecf20Sopenharmony_ci			goto out3;
3328c2ecf20Sopenharmony_ci		}
3338c2ecf20Sopenharmony_ci	}
3348c2ecf20Sopenharmony_ci
3358c2ecf20Sopenharmony_ci	return 0;
3368c2ecf20Sopenharmony_ciout3:
3378c2ecf20Sopenharmony_ci	rc_unregister_device(rc);
3388c2ecf20Sopenharmony_ci	rc = NULL;
3398c2ecf20Sopenharmony_ciout2:
3408c2ecf20Sopenharmony_ci	led_classdev_unregister(&tt->led);
3418c2ecf20Sopenharmony_ciout:
3428c2ecf20Sopenharmony_ci	if (tt) {
3438c2ecf20Sopenharmony_ci		for (i = 0; i < NUM_URBS && tt->urb[i]; i++) {
3448c2ecf20Sopenharmony_ci			struct urb *urb = tt->urb[i];
3458c2ecf20Sopenharmony_ci
3468c2ecf20Sopenharmony_ci			usb_kill_urb(urb);
3478c2ecf20Sopenharmony_ci			usb_free_coherent(tt->udev, 128, urb->transfer_buffer,
3488c2ecf20Sopenharmony_ci							urb->transfer_dma);
3498c2ecf20Sopenharmony_ci			usb_free_urb(urb);
3508c2ecf20Sopenharmony_ci		}
3518c2ecf20Sopenharmony_ci		usb_kill_urb(tt->bulk_urb);
3528c2ecf20Sopenharmony_ci		usb_free_urb(tt->bulk_urb);
3538c2ecf20Sopenharmony_ci		kfree(tt);
3548c2ecf20Sopenharmony_ci	}
3558c2ecf20Sopenharmony_ci	rc_free_device(rc);
3568c2ecf20Sopenharmony_ci
3578c2ecf20Sopenharmony_ci	return ret;
3588c2ecf20Sopenharmony_ci}
3598c2ecf20Sopenharmony_ci
3608c2ecf20Sopenharmony_cistatic void ttusbir_disconnect(struct usb_interface *intf)
3618c2ecf20Sopenharmony_ci{
3628c2ecf20Sopenharmony_ci	struct ttusbir *tt = usb_get_intfdata(intf);
3638c2ecf20Sopenharmony_ci	struct usb_device *udev = tt->udev;
3648c2ecf20Sopenharmony_ci	int i;
3658c2ecf20Sopenharmony_ci
3668c2ecf20Sopenharmony_ci	tt->udev = NULL;
3678c2ecf20Sopenharmony_ci
3688c2ecf20Sopenharmony_ci	rc_unregister_device(tt->rc);
3698c2ecf20Sopenharmony_ci	led_classdev_unregister(&tt->led);
3708c2ecf20Sopenharmony_ci	for (i = 0; i < NUM_URBS; i++) {
3718c2ecf20Sopenharmony_ci		usb_kill_urb(tt->urb[i]);
3728c2ecf20Sopenharmony_ci		usb_free_coherent(udev, 128, tt->urb[i]->transfer_buffer,
3738c2ecf20Sopenharmony_ci						tt->urb[i]->transfer_dma);
3748c2ecf20Sopenharmony_ci		usb_free_urb(tt->urb[i]);
3758c2ecf20Sopenharmony_ci	}
3768c2ecf20Sopenharmony_ci	usb_kill_urb(tt->bulk_urb);
3778c2ecf20Sopenharmony_ci	usb_free_urb(tt->bulk_urb);
3788c2ecf20Sopenharmony_ci	usb_set_intfdata(intf, NULL);
3798c2ecf20Sopenharmony_ci	kfree(tt);
3808c2ecf20Sopenharmony_ci}
3818c2ecf20Sopenharmony_ci
3828c2ecf20Sopenharmony_cistatic int ttusbir_suspend(struct usb_interface *intf, pm_message_t message)
3838c2ecf20Sopenharmony_ci{
3848c2ecf20Sopenharmony_ci	struct ttusbir *tt = usb_get_intfdata(intf);
3858c2ecf20Sopenharmony_ci	int i;
3868c2ecf20Sopenharmony_ci
3878c2ecf20Sopenharmony_ci	for (i = 0; i < NUM_URBS; i++)
3888c2ecf20Sopenharmony_ci		usb_kill_urb(tt->urb[i]);
3898c2ecf20Sopenharmony_ci
3908c2ecf20Sopenharmony_ci	led_classdev_suspend(&tt->led);
3918c2ecf20Sopenharmony_ci	usb_kill_urb(tt->bulk_urb);
3928c2ecf20Sopenharmony_ci
3938c2ecf20Sopenharmony_ci	return 0;
3948c2ecf20Sopenharmony_ci}
3958c2ecf20Sopenharmony_ci
3968c2ecf20Sopenharmony_cistatic int ttusbir_resume(struct usb_interface *intf)
3978c2ecf20Sopenharmony_ci{
3988c2ecf20Sopenharmony_ci	struct ttusbir *tt = usb_get_intfdata(intf);
3998c2ecf20Sopenharmony_ci	int i, rc;
4008c2ecf20Sopenharmony_ci
4018c2ecf20Sopenharmony_ci	tt->is_led_on = true;
4028c2ecf20Sopenharmony_ci	led_classdev_resume(&tt->led);
4038c2ecf20Sopenharmony_ci
4048c2ecf20Sopenharmony_ci	for (i = 0; i < NUM_URBS; i++) {
4058c2ecf20Sopenharmony_ci		rc = usb_submit_urb(tt->urb[i], GFP_KERNEL);
4068c2ecf20Sopenharmony_ci		if (rc) {
4078c2ecf20Sopenharmony_ci			dev_warn(tt->dev, "failed to submit urb: %d\n", rc);
4088c2ecf20Sopenharmony_ci			break;
4098c2ecf20Sopenharmony_ci		}
4108c2ecf20Sopenharmony_ci	}
4118c2ecf20Sopenharmony_ci
4128c2ecf20Sopenharmony_ci	return rc;
4138c2ecf20Sopenharmony_ci}
4148c2ecf20Sopenharmony_ci
4158c2ecf20Sopenharmony_cistatic const struct usb_device_id ttusbir_table[] = {
4168c2ecf20Sopenharmony_ci	{ USB_DEVICE(0x0b48, 0x2003) },
4178c2ecf20Sopenharmony_ci	{ }
4188c2ecf20Sopenharmony_ci};
4198c2ecf20Sopenharmony_ci
4208c2ecf20Sopenharmony_cistatic struct usb_driver ttusbir_driver = {
4218c2ecf20Sopenharmony_ci	.name = DRIVER_NAME,
4228c2ecf20Sopenharmony_ci	.id_table = ttusbir_table,
4238c2ecf20Sopenharmony_ci	.probe = ttusbir_probe,
4248c2ecf20Sopenharmony_ci	.suspend = ttusbir_suspend,
4258c2ecf20Sopenharmony_ci	.resume = ttusbir_resume,
4268c2ecf20Sopenharmony_ci	.reset_resume = ttusbir_resume,
4278c2ecf20Sopenharmony_ci	.disconnect = ttusbir_disconnect,
4288c2ecf20Sopenharmony_ci};
4298c2ecf20Sopenharmony_ci
4308c2ecf20Sopenharmony_cimodule_usb_driver(ttusbir_driver);
4318c2ecf20Sopenharmony_ci
4328c2ecf20Sopenharmony_ciMODULE_DESCRIPTION(DRIVER_DESC);
4338c2ecf20Sopenharmony_ciMODULE_AUTHOR("Sean Young <sean@mess.org>");
4348c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
4358c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(usb, ttusbir_table);
4368c2ecf20Sopenharmony_ci
437