forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
308 lines
7.9 KiB
308 lines
7.9 KiB
// SPDX-License-Identifier: GPL-2.0+ |
|
// Driver for Xbox DVD Movie Playback Kit |
|
// Copyright (c) 2018 by Benjamin Valentin <[email protected]> |
|
|
|
/* |
|
* Xbox DVD Movie Playback Kit USB IR dongle support |
|
* |
|
* The driver was derived from the ati_remote driver 2.2.1 |
|
* and used information from lirc_xbox.c |
|
* |
|
* Copyright (c) 2011, 2012 Anssi Hannula <[email protected]> |
|
* Copyright (c) 2004 Torrey Hoffman <[email protected]> |
|
* Copyright (c) 2002 Vladimir Dergachev |
|
* Copyright (c) 2003-2004 Paul Miller <[email protected]> |
|
*/ |
|
|
|
#include <linux/slab.h> |
|
#include <linux/module.h> |
|
#include <linux/usb/input.h> |
|
#include <media/rc-core.h> |
|
|
|
/* |
|
* Module and Version Information |
|
*/ |
|
#define DRIVER_VERSION "1.0.0" |
|
#define DRIVER_AUTHOR "Benjamin Valentin <[email protected]>" |
|
#define DRIVER_DESC "Xbox DVD USB Remote Control" |
|
|
|
#define NAME_BUFSIZE 80 /* size of product name, path buffers */ |
|
#define DATA_BUFSIZE 8 /* size of URB data buffers */ |
|
|
|
/* |
|
* USB vendor ids for XBOX DVD Dongles |
|
*/ |
|
#define VENDOR_GAMESTER 0x040b |
|
#define VENDOR_MICROSOFT 0x045e |
|
|
|
static const struct usb_device_id xbox_remote_table[] = { |
|
/* Gamester Xbox DVD Movie Playback Kit IR */ |
|
{ |
|
USB_DEVICE(VENDOR_GAMESTER, 0x6521), |
|
}, |
|
/* Microsoft Xbox DVD Movie Playback Kit IR */ |
|
{ |
|
USB_DEVICE(VENDOR_MICROSOFT, 0x0284), |
|
}, |
|
{} /* Terminating entry */ |
|
}; |
|
|
|
MODULE_DEVICE_TABLE(usb, xbox_remote_table); |
|
|
|
struct xbox_remote { |
|
struct rc_dev *rdev; |
|
struct usb_device *udev; |
|
struct usb_interface *interface; |
|
|
|
struct urb *irq_urb; |
|
unsigned char inbuf[DATA_BUFSIZE] __aligned(sizeof(u16)); |
|
|
|
char rc_name[NAME_BUFSIZE]; |
|
char rc_phys[NAME_BUFSIZE]; |
|
}; |
|
|
|
static int xbox_remote_rc_open(struct rc_dev *rdev) |
|
{ |
|
struct xbox_remote *xbox_remote = rdev->priv; |
|
|
|
/* On first open, submit the read urb which was set up previously. */ |
|
xbox_remote->irq_urb->dev = xbox_remote->udev; |
|
if (usb_submit_urb(xbox_remote->irq_urb, GFP_KERNEL)) { |
|
dev_err(&xbox_remote->interface->dev, |
|
"%s: usb_submit_urb failed!\n", __func__); |
|
return -EIO; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static void xbox_remote_rc_close(struct rc_dev *rdev) |
|
{ |
|
struct xbox_remote *xbox_remote = rdev->priv; |
|
|
|
usb_kill_urb(xbox_remote->irq_urb); |
|
} |
|
|
|
/* |
|
* xbox_remote_report_input |
|
*/ |
|
static void xbox_remote_input_report(struct urb *urb) |
|
{ |
|
struct xbox_remote *xbox_remote = urb->context; |
|
unsigned char *data = xbox_remote->inbuf; |
|
|
|
/* |
|
* data[0] = 0x00 |
|
* data[1] = length - always 0x06 |
|
* data[2] = the key code |
|
* data[3] = high part of key code |
|
* data[4] = last_press_ms (low) |
|
* data[5] = last_press_ms (high) |
|
*/ |
|
|
|
/* Deal with strange looking inputs */ |
|
if (urb->actual_length != 6 || urb->actual_length != data[1]) { |
|
dev_warn(&urb->dev->dev, "Weird data, len=%d: %*ph\n", |
|
urb->actual_length, urb->actual_length, data); |
|
return; |
|
} |
|
|
|
rc_keydown(xbox_remote->rdev, RC_PROTO_XBOX_DVD, |
|
le16_to_cpup((__le16 *)(data + 2)), 0); |
|
} |
|
|
|
/* |
|
* xbox_remote_irq_in |
|
*/ |
|
static void xbox_remote_irq_in(struct urb *urb) |
|
{ |
|
struct xbox_remote *xbox_remote = urb->context; |
|
int retval; |
|
|
|
switch (urb->status) { |
|
case 0: /* success */ |
|
xbox_remote_input_report(urb); |
|
break; |
|
case -ECONNRESET: /* unlink */ |
|
case -ENOENT: |
|
case -ESHUTDOWN: |
|
dev_dbg(&xbox_remote->interface->dev, |
|
"%s: urb error status, unlink?\n", |
|
__func__); |
|
return; |
|
default: /* error */ |
|
dev_dbg(&xbox_remote->interface->dev, |
|
"%s: Nonzero urb status %d\n", |
|
__func__, urb->status); |
|
} |
|
|
|
retval = usb_submit_urb(urb, GFP_ATOMIC); |
|
if (retval) |
|
dev_err(&xbox_remote->interface->dev, |
|
"%s: usb_submit_urb()=%d\n", |
|
__func__, retval); |
|
} |
|
|
|
static void xbox_remote_rc_init(struct xbox_remote *xbox_remote) |
|
{ |
|
struct rc_dev *rdev = xbox_remote->rdev; |
|
|
|
rdev->priv = xbox_remote; |
|
rdev->allowed_protocols = RC_PROTO_BIT_XBOX_DVD; |
|
rdev->driver_name = "xbox_remote"; |
|
|
|
rdev->open = xbox_remote_rc_open; |
|
rdev->close = xbox_remote_rc_close; |
|
|
|
rdev->device_name = xbox_remote->rc_name; |
|
rdev->input_phys = xbox_remote->rc_phys; |
|
|
|
rdev->timeout = MS_TO_US(10); |
|
|
|
usb_to_input_id(xbox_remote->udev, &rdev->input_id); |
|
rdev->dev.parent = &xbox_remote->interface->dev; |
|
} |
|
|
|
static int xbox_remote_initialize(struct xbox_remote *xbox_remote, |
|
struct usb_endpoint_descriptor *endpoint_in) |
|
{ |
|
struct usb_device *udev = xbox_remote->udev; |
|
int pipe, maxp; |
|
|
|
/* Set up irq_urb */ |
|
pipe = usb_rcvintpipe(udev, endpoint_in->bEndpointAddress); |
|
maxp = usb_maxpacket(udev, pipe, usb_pipeout(pipe)); |
|
maxp = (maxp > DATA_BUFSIZE) ? DATA_BUFSIZE : maxp; |
|
|
|
usb_fill_int_urb(xbox_remote->irq_urb, udev, pipe, xbox_remote->inbuf, |
|
maxp, xbox_remote_irq_in, xbox_remote, |
|
endpoint_in->bInterval); |
|
|
|
return 0; |
|
} |
|
|
|
/* |
|
* xbox_remote_probe |
|
*/ |
|
static int xbox_remote_probe(struct usb_interface *interface, |
|
const struct usb_device_id *id) |
|
{ |
|
struct usb_device *udev = interface_to_usbdev(interface); |
|
struct usb_host_interface *iface_host = interface->cur_altsetting; |
|
struct usb_endpoint_descriptor *endpoint_in; |
|
struct xbox_remote *xbox_remote; |
|
struct rc_dev *rc_dev; |
|
int err = -ENOMEM; |
|
|
|
// why is there also a device with no endpoints? |
|
if (iface_host->desc.bNumEndpoints == 0) |
|
return -ENODEV; |
|
|
|
if (iface_host->desc.bNumEndpoints != 1) { |
|
pr_err("%s: Unexpected desc.bNumEndpoints: %d\n", |
|
__func__, iface_host->desc.bNumEndpoints); |
|
return -ENODEV; |
|
} |
|
|
|
endpoint_in = &iface_host->endpoint[0].desc; |
|
|
|
if (!usb_endpoint_is_int_in(endpoint_in)) { |
|
pr_err("%s: Unexpected endpoint_in\n", __func__); |
|
return -ENODEV; |
|
} |
|
if (le16_to_cpu(endpoint_in->wMaxPacketSize) == 0) { |
|
pr_err("%s: endpoint_in message size==0?\n", __func__); |
|
return -ENODEV; |
|
} |
|
|
|
xbox_remote = kzalloc(sizeof(*xbox_remote), GFP_KERNEL); |
|
rc_dev = rc_allocate_device(RC_DRIVER_SCANCODE); |
|
if (!xbox_remote || !rc_dev) |
|
goto exit_free_dev_rdev; |
|
|
|
/* Allocate URB buffer */ |
|
xbox_remote->irq_urb = usb_alloc_urb(0, GFP_KERNEL); |
|
if (!xbox_remote->irq_urb) |
|
goto exit_free_buffers; |
|
|
|
xbox_remote->udev = udev; |
|
xbox_remote->rdev = rc_dev; |
|
xbox_remote->interface = interface; |
|
|
|
usb_make_path(udev, xbox_remote->rc_phys, sizeof(xbox_remote->rc_phys)); |
|
|
|
strlcat(xbox_remote->rc_phys, "/input0", sizeof(xbox_remote->rc_phys)); |
|
|
|
snprintf(xbox_remote->rc_name, sizeof(xbox_remote->rc_name), "%s%s%s", |
|
udev->manufacturer ?: "", |
|
udev->manufacturer && udev->product ? " " : "", |
|
udev->product ?: ""); |
|
|
|
if (!strlen(xbox_remote->rc_name)) |
|
snprintf(xbox_remote->rc_name, sizeof(xbox_remote->rc_name), |
|
DRIVER_DESC "(%04x,%04x)", |
|
le16_to_cpu(xbox_remote->udev->descriptor.idVendor), |
|
le16_to_cpu(xbox_remote->udev->descriptor.idProduct)); |
|
|
|
rc_dev->map_name = RC_MAP_XBOX_DVD; /* default map */ |
|
|
|
xbox_remote_rc_init(xbox_remote); |
|
|
|
/* Device Hardware Initialization */ |
|
err = xbox_remote_initialize(xbox_remote, endpoint_in); |
|
if (err) |
|
goto exit_kill_urbs; |
|
|
|
/* Set up and register rc device */ |
|
err = rc_register_device(xbox_remote->rdev); |
|
if (err) |
|
goto exit_kill_urbs; |
|
|
|
usb_set_intfdata(interface, xbox_remote); |
|
|
|
return 0; |
|
|
|
exit_kill_urbs: |
|
usb_kill_urb(xbox_remote->irq_urb); |
|
exit_free_buffers: |
|
usb_free_urb(xbox_remote->irq_urb); |
|
exit_free_dev_rdev: |
|
rc_free_device(rc_dev); |
|
kfree(xbox_remote); |
|
|
|
return err; |
|
} |
|
|
|
/* |
|
* xbox_remote_disconnect |
|
*/ |
|
static void xbox_remote_disconnect(struct usb_interface *interface) |
|
{ |
|
struct xbox_remote *xbox_remote; |
|
|
|
xbox_remote = usb_get_intfdata(interface); |
|
usb_set_intfdata(interface, NULL); |
|
if (!xbox_remote) { |
|
dev_warn(&interface->dev, "%s - null device?\n", __func__); |
|
return; |
|
} |
|
|
|
usb_kill_urb(xbox_remote->irq_urb); |
|
rc_unregister_device(xbox_remote->rdev); |
|
usb_free_urb(xbox_remote->irq_urb); |
|
kfree(xbox_remote); |
|
} |
|
|
|
/* usb specific object to register with the usb subsystem */ |
|
static struct usb_driver xbox_remote_driver = { |
|
.name = "xbox_remote", |
|
.probe = xbox_remote_probe, |
|
.disconnect = xbox_remote_disconnect, |
|
.id_table = xbox_remote_table, |
|
}; |
|
|
|
module_usb_driver(xbox_remote_driver); |
|
|
|
MODULE_AUTHOR(DRIVER_AUTHOR); |
|
MODULE_DESCRIPTION(DRIVER_DESC); |
|
MODULE_LICENSE("GPL");
|
|
|