Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f04d5140 authored by Colin Leitner's avatar Colin Leitner Committed by Jiri Kosina
Browse files

HID: driver for PS2/3 Buzz controllers



This patch adds support for PS2/3 Buzz controllers into hid-sony

It has been tested on Debian 7 with kernel version 3.10.0-rc2. Unfortunately
I can't test the patch with a regular six-axis controller myself.

Signed-off-by: default avatarColin Leitner <colin.leitner@gmail.com>
Cc: Jiri Kosina <jkosina@suse.cz>
Cc: linux-input@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent f755407d
Loading
Loading
Loading
Loading
+7 −2
Original line number Original line Diff line number Diff line
@@ -594,10 +594,15 @@ config HID_SAMSUNG
	Support for Samsung InfraRed remote control or keyboards.
	Support for Samsung InfraRed remote control or keyboards.


config HID_SONY
config HID_SONY
	tristate "Sony PS3 controller"
	tristate "Sony PS2/3 accessories"
	depends on USB_HID
	depends on USB_HID
	select NEW_LEDS
	select LEDS_CLASS
	---help---
	---help---
	Support for Sony PS3 6-axis controllers.
	Support for
	  
	  * Sony PS3 6-axis controllers
	  * Buzz controllers


	Support for the Sony PS3 BD Remote is provided by HID_PS3REMOTE.
	Support for the Sony PS3 BD Remote is provided by HID_PS3REMOTE.


+2 −0
Original line number Original line Diff line number Diff line
@@ -1680,6 +1680,8 @@ static const struct hid_device_id hid_have_special_driver[] = {
	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SKYCABLE, USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SKYCABLE, USB_DEVICE_ID_SKYCABLE_WIRELESS_PRESENTER) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_BUZZ_CONTROLLER) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER) },
	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_BDREMOTE) },
	{ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_BDREMOTE) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_PS3_CONTROLLER) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER) },
+2 −0
Original line number Original line Diff line number Diff line
@@ -734,6 +734,8 @@
#define USB_DEVICE_ID_SONY_PS3_BDREMOTE		0x0306
#define USB_DEVICE_ID_SONY_PS3_BDREMOTE		0x0306
#define USB_DEVICE_ID_SONY_PS3_CONTROLLER	0x0268
#define USB_DEVICE_ID_SONY_PS3_CONTROLLER	0x0268
#define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER	0x042f
#define USB_DEVICE_ID_SONY_NAVIGATION_CONTROLLER	0x042f
#define USB_DEVICE_ID_SONY_BUZZ_CONTROLLER		0x0002
#define USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER	0x1000


#define USB_VENDOR_ID_SOUNDGRAPH	0x15c2
#define USB_VENDOR_ID_SOUNDGRAPH	0x15c2
#define USB_DEVICE_ID_SOUNDGRAPH_IMON_FIRST	0x0034
#define USB_DEVICE_ID_SOUNDGRAPH_IMON_FIRST	0x0034
+301 −9
Original line number Original line Diff line number Diff line
@@ -6,6 +6,7 @@
 *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
 *  Copyright (c) 2005 Michael Haboustak <mike-@cinci.rr.com> for Concept2, Inc
 *  Copyright (c) 2008 Jiri Slaby
 *  Copyright (c) 2008 Jiri Slaby
 *  Copyright (c) 2006-2008 Jiri Kosina
 *  Copyright (c) 2006-2008 Jiri Kosina
 *  Copyright (c) 2013 Colin Leitner <colin.leitner@gmail.com>
 */
 */


/*
/*
@@ -26,6 +27,7 @@
#define VAIO_RDESC_CONSTANT     (1 << 0)
#define VAIO_RDESC_CONSTANT     (1 << 0)
#define SIXAXIS_CONTROLLER_USB  (1 << 1)
#define SIXAXIS_CONTROLLER_USB  (1 << 1)
#define SIXAXIS_CONTROLLER_BT   (1 << 2)
#define SIXAXIS_CONTROLLER_BT   (1 << 2)
#define BUZZ_CONTROLLER         (1 << 3)


static const u8 sixaxis_rdesc_fixup[] = {
static const u8 sixaxis_rdesc_fixup[] = {
	0x95, 0x13, 0x09, 0x01, 0x81, 0x02, 0x95, 0x0C,
	0x95, 0x13, 0x09, 0x01, 0x81, 0x02, 0x95, 0x0C,
@@ -55,8 +57,56 @@ static const u8 sixaxis_rdesc_fixup2[] = {
	0xb1, 0x02, 0xc0, 0xc0,
	0xb1, 0x02, 0xc0, 0xc0,
};
};


static const unsigned int buzz_keymap[] = {
	/* The controller has 4 remote buzzers, each with one LED and 5
	 * buttons.
	 * 
	 * We use the mapping chosen by the controller, which is:
	 *
	 * Key          Offset
	 * -------------------
	 * Buzz              1
	 * Blue              5
	 * Orange            4
	 * Green             3
	 * Yellow            2
	 *
	 * So, for example, the orange button on the third buzzer is mapped to
	 * BTN_TRIGGER_HAPPY14
	 */
	[ 1] = BTN_TRIGGER_HAPPY1,
	[ 2] = BTN_TRIGGER_HAPPY2,
	[ 3] = BTN_TRIGGER_HAPPY3,
	[ 4] = BTN_TRIGGER_HAPPY4,
	[ 5] = BTN_TRIGGER_HAPPY5,
	[ 6] = BTN_TRIGGER_HAPPY6,
	[ 7] = BTN_TRIGGER_HAPPY7,
	[ 8] = BTN_TRIGGER_HAPPY8,
	[ 9] = BTN_TRIGGER_HAPPY9,
	[10] = BTN_TRIGGER_HAPPY10,
	[11] = BTN_TRIGGER_HAPPY11,
	[12] = BTN_TRIGGER_HAPPY12,
	[13] = BTN_TRIGGER_HAPPY13,
	[14] = BTN_TRIGGER_HAPPY14,
	[15] = BTN_TRIGGER_HAPPY15,
	[16] = BTN_TRIGGER_HAPPY16,
	[17] = BTN_TRIGGER_HAPPY17,
	[18] = BTN_TRIGGER_HAPPY18,
	[19] = BTN_TRIGGER_HAPPY19,
	[20] = BTN_TRIGGER_HAPPY20,
};

struct sony_sc {
struct sony_sc {
	unsigned long quirks;
	unsigned long quirks;

	void *extra;
};

struct buzz_extra {
#ifdef CONFIG_LEDS_CLASS
	int led_state;
	struct led_classdev *leds[4];
#endif
};
};


/* Sony Vaio VGX has wrongly mouse pointer declared as constant */
/* Sony Vaio VGX has wrongly mouse pointer declared as constant */
@@ -117,6 +167,38 @@ static int sony_raw_event(struct hid_device *hdev, struct hid_report *report,
	return 0;
	return 0;
}
}


static int sony_mapping(struct hid_device *hdev, struct hid_input *hi,
			struct hid_field *field, struct hid_usage *usage,
			unsigned long **bit, int *max)
{
	struct sony_sc *sc = hid_get_drvdata(hdev);

	if (sc->quirks & BUZZ_CONTROLLER) {
		unsigned int key = usage->hid & HID_USAGE;

		if ((usage->hid & HID_USAGE_PAGE) != HID_UP_BUTTON)
			return -1;

		switch (usage->collection_index) {
		case 1:
			if (key >= ARRAY_SIZE(buzz_keymap))
				return -1;

			key = buzz_keymap[key];
			if (!key)
				return -1;
			break;
		default:
			return -1;
		}

		hid_map_usage_clear(hi, usage, bit, max, EV_KEY, key);
		return 1;
	}

	return -1;
}

/*
/*
 * The Sony Sixaxis does not handle HID Output Reports on the Interrupt EP
 * The Sony Sixaxis does not handle HID Output Reports on the Interrupt EP
 * like it should according to usbhid/hid-core.c::usbhid_output_raw_report()
 * like it should according to usbhid/hid-core.c::usbhid_output_raw_report()
@@ -192,11 +274,201 @@ static int sixaxis_set_operational_bt(struct hid_device *hdev)
	return hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT);
	return hdev->hid_output_raw_report(hdev, buf, sizeof(buf), HID_FEATURE_REPORT);
}
}


#ifdef CONFIG_LEDS_CLASS
static void buzz_set_leds(struct hid_device *hdev, int leds)
{
	struct list_head *report_list =
		&hdev->report_enum[HID_OUTPUT_REPORT].report_list;
	struct hid_report *report = list_entry(report_list->next,
		struct hid_report, list);
	__s32 *value = report->field[0]->value;

	value[0] = 0x00;
	value[1] = (leds & 1) ? 0xff : 0x00;
	value[2] = (leds & 2) ? 0xff : 0x00;
	value[3] = (leds & 4) ? 0xff : 0x00;
	value[4] = (leds & 8) ? 0xff : 0x00;
	value[5] = 0x00;
	value[6] = 0x00;
	hid_hw_request(hdev, report, HID_REQ_SET_REPORT);
}

static void buzz_led_set_brightness(struct led_classdev *led,
				    enum led_brightness value)
{
	struct device *dev = led->dev->parent;
	struct hid_device *hdev = container_of(dev, struct hid_device, dev);
	struct sony_sc *drv_data;
	struct buzz_extra *buzz;

	int n;

	drv_data = hid_get_drvdata(hdev);
	if (!drv_data || !drv_data->extra) {
		hid_err(hdev, "No device data\n");
		return;
	}
	buzz = drv_data->extra;

	for (n = 0; n < 4; n++) {
		if (led == buzz->leds[n]) {
			int on = !! (buzz->led_state & (1 << n));
			if (value == LED_OFF && on) {
				buzz->led_state &= ~(1 << n);
				buzz_set_leds(hdev, buzz->led_state);
			} else if (value != LED_OFF && !on) {
				buzz->led_state |= (1 << n);
				buzz_set_leds(hdev, buzz->led_state);
			}
			break;
		}
	}
}

static enum led_brightness buzz_led_get_brightness(struct led_classdev *led)
{
	struct device *dev = led->dev->parent;
	struct hid_device *hdev = container_of(dev, struct hid_device, dev);
	struct sony_sc *drv_data;
	struct buzz_extra *buzz;

	int n;
	int on = 0;

	drv_data = hid_get_drvdata(hdev);
	if (!drv_data || !drv_data->extra) {
		hid_err(hdev, "No device data\n");
		return LED_OFF;
	}
	buzz = drv_data->extra;

	for (n = 0; n < 4; n++) {
		if (led == buzz->leds[n]) {
			on = !! (buzz->led_state & (1 << n));
			break;
		}
	}

	return on ? LED_FULL : LED_OFF;
}
#endif

static int buzz_init(struct hid_device *hdev)
{
	struct sony_sc *drv_data;
	struct buzz_extra *buzz;
	int ret = 0;

	drv_data = hid_get_drvdata(hdev);
	BUG_ON(!(drv_data->quirks & BUZZ_CONTROLLER));

	buzz = kzalloc(sizeof(*buzz), GFP_KERNEL);
	if (!buzz) {
		hid_err(hdev, "Insufficient memory, cannot allocate driver data\n");
		return -ENOMEM;
	}
	drv_data->extra = buzz;

	/* Clear LEDs as we have no way of reading their initial state. This is
	 * only relevant if the driver is loaded after somebody actively set the
	 * LEDs to on */
	buzz_set_leds(hdev, 0x00);

#ifdef CONFIG_LEDS_CLASS
	{
		int n;
		struct led_classdev *led;
		size_t name_sz;
		char *name;

		name_sz = strlen(dev_name(&hdev->dev)) + strlen("::buzz#") + 1;

		for (n = 0; n < 4; n++) {
			led = kzalloc(sizeof(struct led_classdev) + name_sz, GFP_KERNEL);
			if (!led) {
				hid_err(hdev, "Couldn't allocate memory for LED %d\n", n);
				goto error_leds;
			}

			name = (void *)(&led[1]);
			snprintf(name, name_sz, "%s::buzz%d", dev_name(&hdev->dev), n + 1);
			led->name = name;
			led->brightness = 0;
			led->max_brightness = 1;
			led->brightness_get = buzz_led_get_brightness;
			led->brightness_set = buzz_led_set_brightness;

			if (led_classdev_register(&hdev->dev, led)) {
				hid_err(hdev, "Failed to register LED %d\n", n);
				kfree(led);
				goto error_leds;
			}

			buzz->leds[n] = led;
		}
	}
#endif

	return ret;

#ifdef CONFIG_LEDS_CLASS
error_leds:
	{
		int n;
		struct led_classdev *led;

		for (n = 0; n < 4; n++) {
			led = buzz->leds[n];
			buzz->leds[n] = NULL;
			if (!led)
				continue;
			led_classdev_unregister(led);
			kfree(led);
		}
	}

	kfree(drv_data->extra);
	drv_data->extra = NULL;
	return ret;
#endif
}

static void buzz_remove(struct hid_device *hdev)
{
	struct sony_sc *drv_data;
	struct buzz_extra *buzz;

	drv_data = hid_get_drvdata(hdev);
	BUG_ON(!(drv_data->quirks & BUZZ_CONTROLLER));

	buzz = drv_data->extra;
	
#ifdef CONFIG_LEDS_CLASS
	{
		int n;
		struct led_classdev *led;

		for (n = 0; n < 4; n++) {
			led = buzz->leds[n];
			buzz->leds[n] = NULL;
			if (!led)
				continue;
			led_classdev_unregister(led);
			kfree(led);
		}
	}
#endif

	kfree(drv_data->extra);
	drv_data->extra = NULL;
}

static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
{
{
	int ret;
	int ret;
	unsigned long quirks = id->driver_data;
	unsigned long quirks = id->driver_data;
	struct sony_sc *sc;
	struct sony_sc *sc;
	unsigned int connect_mask = HID_CONNECT_DEFAULT;


	sc = kzalloc(sizeof(*sc), GFP_KERNEL);
	sc = kzalloc(sizeof(*sc), GFP_KERNEL);
	if (sc == NULL) {
	if (sc == NULL) {
@@ -213,8 +485,14 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
		goto err_free;
		goto err_free;
	}
	}


	ret = hid_hw_start(hdev, HID_CONNECT_DEFAULT |
	if (sc->quirks & VAIO_RDESC_CONSTANT)
			HID_CONNECT_HIDDEV_FORCE);
		connect_mask |= HID_CONNECT_HIDDEV_FORCE;
	else if (sc->quirks & SIXAXIS_CONTROLLER_USB)
		connect_mask |= HID_CONNECT_HIDDEV_FORCE;
	else if (sc->quirks & SIXAXIS_CONTROLLER_BT)
		connect_mask |= HID_CONNECT_HIDDEV_FORCE;

	ret = hid_hw_start(hdev, connect_mask);
	if (ret) {
	if (ret) {
		hid_err(hdev, "hw start failed\n");
		hid_err(hdev, "hw start failed\n");
		goto err_free;
		goto err_free;
@@ -226,6 +504,8 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)
	}
	}
	else if (sc->quirks & SIXAXIS_CONTROLLER_BT)
	else if (sc->quirks & SIXAXIS_CONTROLLER_BT)
		ret = sixaxis_set_operational_bt(hdev);
		ret = sixaxis_set_operational_bt(hdev);
	else if (sc->quirks & BUZZ_CONTROLLER)
		ret = buzz_init(hdev);
	else
	else
		ret = 0;
		ret = 0;


@@ -242,8 +522,13 @@ static int sony_probe(struct hid_device *hdev, const struct hid_device_id *id)


static void sony_remove(struct hid_device *hdev)
static void sony_remove(struct hid_device *hdev)
{
{
	struct sony_sc *sc = hid_get_drvdata(hdev);

	if (sc->quirks & BUZZ_CONTROLLER)
		buzz_remove(hdev);

	hid_hw_stop(hdev);
	hid_hw_stop(hdev);
	kfree(hid_get_drvdata(hdev));
	kfree(sc);
}
}


static const struct hid_device_id sony_devices[] = {
static const struct hid_device_id sony_devices[] = {
@@ -257,6 +542,12 @@ static const struct hid_device_id sony_devices[] = {
		.driver_data = VAIO_RDESC_CONSTANT },
		.driver_data = VAIO_RDESC_CONSTANT },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE),
	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_VAIO_VGP_MOUSE),
		.driver_data = VAIO_RDESC_CONSTANT },
		.driver_data = VAIO_RDESC_CONSTANT },
	/* Wired Buzz Controller. Reported as Sony Hub from its USB ID and as
	 * Logitech joystick from the device descriptor. */
	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_BUZZ_CONTROLLER),
		.driver_data = BUZZ_CONTROLLER },
	{ HID_USB_DEVICE(USB_VENDOR_ID_SONY, USB_DEVICE_ID_SONY_WIRELESS_BUZZ_CONTROLLER),
		.driver_data = BUZZ_CONTROLLER },
	{ }
	{ }
};
};
MODULE_DEVICE_TABLE(hid, sony_devices);
MODULE_DEVICE_TABLE(hid, sony_devices);
@@ -264,6 +555,7 @@ MODULE_DEVICE_TABLE(hid, sony_devices);
static struct hid_driver sony_driver = {
static struct hid_driver sony_driver = {
	.name          = "sony",
	.name          = "sony",
	.id_table      = sony_devices,
	.id_table      = sony_devices,
	.input_mapping = sony_mapping,
	.probe         = sony_probe,
	.probe         = sony_probe,
	.remove        = sony_remove,
	.remove        = sony_remove,
	.report_fixup  = sony_report_fixup,
	.report_fixup  = sony_report_fixup,