[PATCH v2] HID: Add support for pressure sensitive buttons

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



This driver needs drvdata for each input device, but this has already
been used for storing a pointer to the hid device. The drvdata of the
hid device could be used if there was one for each input device, but
this is not so (HID_QUIRK_MULTI_INPUT with up to four ports). So I've
reused the private data of the forced feedback to store the data.

Signed-off-by: Sean Young <sean@xxxxxxxx>
---
v2:
 - Explaining what the device is being instructed to do more exactly
 - Fix comment style
 - Add documentation for sysfs attributes in Documentation/ABI

Note:
 - With the documented fields, do we need the available_* sysfs attributes?
 - Is using the same hid report from force feedback and sysfs racey?

 Documentation/ABI/testing/sysfs-driver-hid-sjoy |   25 +++
 drivers/hid/Kconfig                             |    3 +-
 drivers/hid/hid-sjoy.c                          |  241 ++++++++++++++++++++++-
 drivers/input/ff-memless.c                      |    8 +
 include/linux/input.h                           |    1 +
 5 files changed, 273 insertions(+), 5 deletions(-)
 create mode 100644 Documentation/ABI/testing/sysfs-driver-hid-sjoy

diff --git a/Documentation/ABI/testing/sysfs-driver-hid-sjoy b/Documentation/ABI/testing/sysfs-driver-hid-sjoy
new file mode 100644
index 0000000..63845c7
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-driver-hid-sjoy
@@ -0,0 +1,25 @@
+What:		/sys/bus/hid/drivers/hid-sjoy/<dev>/controller_mode
+What:		/sys/bus/hid/drivers/hid-sjoy/<dev>/available_controller_modes
+Date		November 2011
+KernelVersion:	3.3
+Contact:	Sean Young <sean@xxxxxxxx>
+Description:	available_controller_modes lists the available modes:
+		auto	 : analog sticks can be enabled or disabled on
+			   the controler itself with the analog button (default)
+		digital	 : disable analog sticks, analog button does not work
+		analog	 : enable analog sticks, analog button does not work
+		pressure : enable analog sticks and two buttons as pressure
+			   sensitive buttons, analog button does not work
+
+What:		/sys/bus/hid/drivers/hid-sjoy/<dev>/pressure_button_0
+What:		/sys/bus/hid/drivers/hid-sjoy/<dev>/pressure_button_1
+What:		/sys/bus/hid/drivers/hid-sjoy/<dev>/available_pressure_buttons
+Date		November 2011
+KernelVersion:	3.3
+Contact:	Sean Young <sean@xxxxxxxx>
+Description:	Only two buttons can be configured to read pressure
+		sensitivity. The available buttons are listed in
+		available_pressure_buttons: triangle circle cross square l1
+		r1 l2 r2. Note that the two buttons must be different.
+
+		The default buttons are cross and square.
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 4d07288..92b1e9c 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -549,8 +549,7 @@ config HID_SMARTJOYPLUS
 	Support for SmartJoy PLUS PS2/USB adapter, Super Dual Box,
 	Super Joy Box 3 Pro, Super Dual Box Pro, and Super Joy Box 5 Pro.
 
-	Note that DDR (Dance Dance Revolution) mode is not supported, nor
-	is pressure sensitive buttons on the pro models.
+	Note that DDR (Dance Dance Revolution) mode is not supported.
 
 config SMARTJOYPLUS_FF
 	bool "SmartJoy PLUS PS2/USB adapter force feedback support"
diff --git a/drivers/hid/hid-sjoy.c b/drivers/hid/hid-sjoy.c
index 670da91..cd9ef2a 100644
--- a/drivers/hid/hid-sjoy.c
+++ b/drivers/hid/hid-sjoy.c
@@ -35,10 +35,31 @@
 #ifdef CONFIG_SMARTJOYPLUS_FF
 #include "usbhid/usbhid.h"
 
+enum mode {
+	MODE_AUTO,
+	MODE_ANALOG,
+	MODE_DIGITAL,
+	MODE_PRESSURE
+};
+
+static const char * const mode_names[] = {
+	"auto", "analog", "digital", "pressure"
+};
+
+static const char * const button_names[] = {
+	"triangle", "circle", "cross", "square", "l1", "r1", "l2", "r2"
+};
+
 struct sjoyff_device {
 	struct hid_report *report;
+	enum mode mode;
+	int buttons[2];
 };
 
+/*
+ * The dual shock controller has two vibration feedback motors, one weak
+ * and one strong. The weak one only can only be turned on or off.
+ */
 static int hid_sjoyff_play(struct input_dev *dev, void *data,
 			 struct ff_effect *effect)
 {
@@ -53,14 +74,186 @@ static int hid_sjoyff_play(struct input_dev *dev, void *data,
 	left = left * 0xff / 0xffff;
 	right = (right != 0); /* on/off only */
 
+	sjoyff->report->field[0]->value[0] = 1;
 	sjoyff->report->field[0]->value[1] = right;
 	sjoyff->report->field[0]->value[2] = left;
-	dev_dbg(&dev->dev, "running with 0x%02x 0x%02x\n", left, right);
+	dev_dbg(&dev->dev, "port %u running with 0x%02x 0x%02x\n",
+					sjoyff->report->id, left, right);
 	usbhid_submit_report(hid, sjoyff->report, USB_DIR_OUT);
 
 	return 0;
 }
 
+/*
+ * When the controller is in pressure sensitive mode, two buttons can
+ * be configured to provide pressure sensitivity. The low four bits
+ * of the second byte specify the first button and the high four bits
+ * the second button. If the third byte is 0, the buttons will no
+ * longer report digital presses, when it is 1 they will continue to
+ * report digital presses as normal as well as pressure sensitivity.
+ */
+static void hid_sjoy_set_buttons(struct device *dev)
+{
+	struct input_dev *idev = to_input_dev(dev);
+	struct sjoyff_device *sjoy = input_ff_get_data(idev);
+	struct hid_device *hid = input_get_drvdata(idev);
+	struct hid_report *report = sjoy->report;
+
+	report->field[0]->value[0] = 6;
+	report->field[0]->value[1] = sjoy->buttons[0] | (sjoy->buttons[1] << 4);
+	report->field[0]->value[2] = 1;
+	report->field[0]->value[3] = 0;
+
+	usbhid_submit_report(hid, report, USB_DIR_OUT);
+}
+
+/*
+ * The controller can be set into different modes. In auto mode, the
+ * analog sticks can be turned on or off using the analog button on the
+ * controller. In analog mode, the analog sticks are enabled and cannot
+ * be turned off with the analog button; with digital mode, the analog
+ * sticks are turned off and cannot be enabled; in pressure mode, the
+ * analog sticks are enabled, and two buttons will report pressure
+ * sensitivity.
+ */
+static void hid_sjoy_set_mode(struct device *dev, enum mode mode)
+{
+	struct input_dev *idev = to_input_dev(dev);
+	struct sjoyff_device *sjoy = input_ff_get_data(idev);
+	struct hid_device *hid = input_get_drvdata(idev);
+	struct hid_report *report = sjoy->report;
+
+	sjoy->mode = mode;
+
+	report->field[0]->value[0] = 3;
+
+	switch (mode) {
+	case MODE_AUTO:
+		report->field[0]->value[1] = 0;
+		report->field[0]->value[2] = 2;
+		report->field[0]->value[3] = 0;
+		break;
+	case MODE_ANALOG:
+		report->field[0]->value[1] = 1;
+		report->field[0]->value[2] = 3;
+		report->field[0]->value[3] = 0;
+		break;
+	case MODE_DIGITAL:
+		report->field[0]->value[1] = 0;
+		report->field[0]->value[2] = 3;
+		report->field[0]->value[3] = 0;
+		break;
+	case MODE_PRESSURE:
+		report->field[0]->value[1] = 1;
+		report->field[0]->value[2] = 3;
+		report->field[0]->value[3] = 1;
+		break;
+	}
+
+	dev_dbg(&hid->dev, "switching port %u mode to %s\n", report->id,
+							mode_names[mode]);
+	usbhid_submit_report(hid, report, USB_DIR_OUT);
+}
+
+static ssize_t store_mode(struct device *dev, struct device_attribute *attr,
+						const char *buf, size_t count)
+{
+	int i, rc = -EINVAL;
+
+	for (i = 0; i < ARRAY_SIZE(mode_names); i++) {
+		if (sysfs_streq(mode_names[i], buf)) {
+			hid_sjoy_set_mode(dev, i);
+			rc = count;
+			break;
+		}
+	}
+
+	return rc;
+}
+
+#define store_button(button)	\
+static ssize_t store_button_##button(struct device *dev,		\
+		struct device_attribute *attr, const char *buf, size_t count) \
+{									      \
+	struct input_dev *idev = to_input_dev(dev);			      \
+	struct sjoyff_device *sjoy = input_ff_get_data(idev);		      \
+	struct hid_device *hid = input_get_drvdata(idev);		      \
+	int i, rc = -EINVAL;						      \
+									      \
+	for (i = 0; i < ARRAY_SIZE(button_names); i++) {		      \
+		if (sysfs_streq(button_names[i], buf)) {		      \
+			if (sjoy->buttons[!button] == i)		      \
+				break;					      \
+									      \
+			dev_dbg(&hid->dev, "port %u pressure button %d: %s\n",\
+				sjoy->report->id, button, button_names[i]);   \
+			sjoy->buttons[button] = i;			      \
+			hid_sjoy_set_buttons(dev);			      \
+			rc = count;					      \
+			break;						      \
+		}							      \
+	}								      \
+									      \
+	return rc;							      \
+}									      \
+									      \
+static ssize_t show_button_##button(struct device *dev,			      \
+				struct device_attribute *attr, char *buf)     \
+{									      \
+	struct input_dev *idev = to_input_dev(dev);			      \
+	struct sjoyff_device *sjoy = input_ff_get_data(idev);		      \
+									      \
+	return snprintf(buf, PAGE_SIZE, "%s\n",				      \
+				button_names[sjoy->buttons[button]]);	      \
+}
+
+store_button(0);
+store_button(1);
+
+static ssize_t show_mode(struct device *dev, struct device_attribute *attr,
+								char *buf)
+{
+	struct input_dev *idev = to_input_dev(dev);
+	struct sjoyff_device *sjoy = input_ff_get_data(idev);
+
+	return snprintf(buf, PAGE_SIZE, "%s\n", mode_names[sjoy->mode]);
+}
+
+static ssize_t show_modes(struct device *dev, struct device_attribute *attr,
+								char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%s\n", "auto analog digital pressure");
+}
+
+static ssize_t show_buttons(struct device *dev, struct device_attribute *attr,
+								char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%s\n",
+		"triangle circle cross square l1 r1 l2 r2");
+}
+
+static DEVICE_ATTR(pressure_button_0, S_IRUGO | S_IWUSR | S_IWGRP,
+						show_button_0, store_button_0);
+static DEVICE_ATTR(pressure_button_1, S_IRUGO | S_IWUSR | S_IWGRP,
+						show_button_1, store_button_1);
+static DEVICE_ATTR(available_pressure_buttons, S_IRUGO, show_buttons, NULL);
+static DEVICE_ATTR(controller_mode, S_IRUGO | S_IWUSR | S_IWGRP, show_mode,
+							store_mode);
+static DEVICE_ATTR(available_controller_modes, S_IRUGO, show_modes, NULL);
+
+static struct attribute *sysfs_attrs[] = {
+	&dev_attr_controller_mode.attr,
+	&dev_attr_available_controller_modes.attr,
+	&dev_attr_pressure_button_0.attr,
+	&dev_attr_pressure_button_1.attr,
+	&dev_attr_available_pressure_buttons.attr,
+	NULL
+};
+
+static struct attribute_group sjoy_attribute_group = {
+	.attrs = sysfs_attrs
+};
+
 static int sjoyff_init(struct hid_device *hid)
 {
 	struct sjoyff_device *sjoyff;
@@ -115,17 +308,57 @@ static int sjoyff_init(struct hid_device *hid)
 		sjoyff->report->field[0]->value[1] = 0x00;
 		sjoyff->report->field[0]->value[2] = 0x00;
 		usbhid_submit_report(hid, sjoyff->report, USB_DIR_OUT);
+
+		/*
+		 * Only the pro models support changing mode, and the
+		 * same devices also have the noget quirk.
+		 */
+		if (!(hid->quirks & HID_QUIRK_NOGET))
+			continue;
+
+		if (report->field[0]->report_count < 4) {
+			hid_err(hid, "not enough values in the field\n");
+			continue;
+		}
+
+		hid_sjoy_set_mode(&dev->dev, MODE_AUTO);
+
+		sjoyff->buttons[0] = 2; /* cross */
+		sjoyff->buttons[1] = 3; /* square */
+
+		hid_sjoy_set_buttons(&dev->dev);
+
+		error = sysfs_create_group(&dev->dev.kobj,
+							&sjoy_attribute_group);
+		if (error)
+			hid_warn(hid, "failed to create sysfs attributes: %d\n",
+									error);
 	}
 
 	hid_info(hid, "Force feedback for SmartJoy PLUS PS2/USB adapter\n");
 
 	return 0;
 }
+
+static void sjoy_remove(struct hid_device *hid)
+{
+	struct hid_input *hidinput;
+	struct input_dev *dev;
+
+	list_for_each_entry(hidinput, &hid->inputs, list) {
+		dev = hidinput->input;
+		sysfs_remove_group(&dev->dev.kobj, &sjoy_attribute_group);
+	}
+
+	hid_hw_stop(hid);
+}
 #else
 static inline int sjoyff_init(struct hid_device *hid)
 {
 	return 0;
 }
+
+#define sjoy_remove NULL
 #endif
 
 static int sjoy_probe(struct hid_device *hdev, const struct hid_device_id *id)
@@ -154,7 +387,8 @@ err:
 }
 
 static const struct hid_device_id sjoy_devices[] = {
-	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_JOY_BOX_3_PRO) },
+	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_JOY_BOX_3_PRO),
+		.driver_data = HID_QUIRK_NOGET },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD, USB_DEVICE_ID_SUPER_DUAL_BOX_PRO),
 		.driver_data = HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET |
 			       HID_QUIRK_SKIP_OUTPUT_REPORTS },
@@ -163,7 +397,7 @@ static const struct hid_device_id sjoy_devices[] = {
 			       HID_QUIRK_SKIP_OUTPUT_REPORTS },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_SMARTJOY_PLUS) },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP, USB_DEVICE_ID_DUAL_USB_JOYPAD),
-		.driver_data = HID_QUIRK_MULTI_INPUT | HID_QUIRK_NOGET |
+		.driver_data = HID_QUIRK_MULTI_INPUT |
 			       HID_QUIRK_SKIP_OUTPUT_REPORTS },
 	{ }
 };
@@ -173,6 +407,7 @@ static struct hid_driver sjoy_driver = {
 	.name = "smartjoyplus",
 	.id_table = sjoy_devices,
 	.probe = sjoy_probe,
+	.remove = sjoy_remove
 };
 
 static int __init sjoy_init(void)
diff --git a/drivers/input/ff-memless.c b/drivers/input/ff-memless.c
index 117a59a..42c83a5 100644
--- a/drivers/input/ff-memless.c
+++ b/drivers/input/ff-memless.c
@@ -544,3 +544,11 @@ int input_ff_create_memless(struct input_dev *dev, void *data,
 	return 0;
 }
 EXPORT_SYMBOL_GPL(input_ff_create_memless);
+
+void *input_ff_get_data(struct input_dev *input)
+{
+	struct ml_device *ml = input->ff->private;
+
+	return ml->private;
+}
+EXPORT_SYMBOL_GPL(input_ff_get_data);
diff --git a/include/linux/input.h b/include/linux/input.h
index 771d6d8..d7a1ce2 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -1617,6 +1617,7 @@ int input_ff_erase(struct input_dev *dev, int effect_id, struct file *file);
 
 int input_ff_create_memless(struct input_dev *dev, void *data,
 		int (*play_effect)(struct input_dev *, void *, struct ff_effect *));
+void *input_ff_get_data(struct input_dev *dev);
 
 #endif
 #endif
-- 
1.7.7.1

--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux