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

Commit 7f4b49fe authored by Simon Wood's avatar Simon Wood Committed by Jiri Kosina
Browse files

HID: hid-logitech-hidpp: Add range sysfs for Logitech G920



The G920 can adjust the amount of 'turn' it permits, this patch adds
a sysfs file 'range' to control this.

Signed-off-by: default avatarSimon Wood <simon@mungewell.org>
Reviewed-by: default avatarBenjamin Tissoires <benjamin.tissoires@redhat.com>
Signed-off-by: default avatarJiri Kosina <jkosina@suse.cz>
parent 7bfd2927
Loading
Loading
Loading
Loading
+139 −1
Original line number Diff line number Diff line
@@ -1295,6 +1295,133 @@ static int k400_connect(struct hid_device *hdev, bool connected)
	return k400_disable_tap_to_click(hidpp);
}

/* ------------------------------------------------------------------------- */
/* Logitech G920 Driving Force Racing Wheel for Xbox One                     */
/* ------------------------------------------------------------------------- */

#define HIDPP_PAGE_G920_FORCE_FEEDBACK			0x8123

/* Using session ID = 1 */
#define CMD_G920_FORCE_GET_APERTURE			0x51
#define CMD_G920_FORCE_SET_APERTURE			0x61

struct g920_private_data {
	u8 force_feature;
	u16 range;
};

#define to_hid_device(pdev) container_of(pdev, struct hid_device, dev)

static ssize_t g920_range_show(struct device *dev, struct device_attribute *attr,
				char *buf)
{
	struct hid_device *hid = to_hid_device(dev);
	struct hidpp_device *hidpp = hid_get_drvdata(hid);
	struct g920_private_data *pdata;

	pdata = hidpp->private_data;
	if (!pdata) {
		hid_err(hid, "Private driver data not found!\n");
		return -EINVAL;
	}

	return scnprintf(buf, PAGE_SIZE, "%u\n", pdata->range);
}

static ssize_t g920_range_store(struct device *dev, struct device_attribute *attr,
				 const char *buf, size_t count)
{
	struct hid_device *hid = to_hid_device(dev);
	struct hidpp_device *hidpp = hid_get_drvdata(hid);
	struct g920_private_data *pdata;
	struct hidpp_report response;
	u8 params[2];
	int ret;
	u16 range = simple_strtoul(buf, NULL, 10);

	pdata = hidpp->private_data;
	if (!pdata) {
		hid_err(hid, "Private driver data not found!\n");
		return -EINVAL;
	}

	if (range < 180)
		range = 180;
	else if (range > 900)
		range = 900;

	params[0] = range >> 8;
	params[1] = range & 0x00FF;

	ret = hidpp_send_fap_command_sync(hidpp, pdata->force_feature,
		CMD_G920_FORCE_SET_APERTURE, params, 2, &response);
	if (ret)
		return ret;

	pdata->range = range;
	return count;
}

static DEVICE_ATTR(range, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH, g920_range_show, g920_range_store);

static int g920_allocate(struct hid_device *hdev)
{
	struct hidpp_device *hidpp = hid_get_drvdata(hdev);
	struct g920_private_data *pdata;

	pdata = devm_kzalloc(&hdev->dev, sizeof(struct g920_private_data),
			GFP_KERNEL);
	if (!pdata)
		return -ENOMEM;

	hidpp->private_data = pdata;

	return 0;
}

static int g920_get_config(struct hidpp_device *hidpp)
{
	struct g920_private_data *pdata = hidpp->private_data;
	struct hidpp_report response;
	u8 feature_type;
	u8 feature_index;
	int ret;

	pdata = hidpp->private_data;
	if (!pdata) {
		hid_err(hidpp->hid_dev, "Private driver data not found!\n");
		return -EINVAL;
	}

	/* Find feature and store for later use */
	ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_G920_FORCE_FEEDBACK,
		&feature_index, &feature_type);
	if (ret)
		return ret;

	pdata->force_feature = feature_index;

	/* Read current Range */
	ret = hidpp_send_fap_command_sync(hidpp, feature_index,
		CMD_G920_FORCE_GET_APERTURE, NULL, 0, &response);
	if (ret > 0) {
		hid_err(hidpp->hid_dev, "%s: received protocol error 0x%02x\n",
			__func__, ret);
		return -EPROTO;
	}
	if (ret)
		return ret;

	pdata->range = get_unaligned_be16(&response.fap.params[0]);

	/* Create sysfs interface */
	ret = device_create_file(&(hidpp->hid_dev->dev), &dev_attr_range);
	if (ret)
		hid_warn(hidpp->hid_dev, "Unable to create sysfs interface for \"range\", errno %d\n", ret);

	return 0;
}

/* -------------------------------------------------------------------------- */
/* Generic HID++ devices                                                      */
/* -------------------------------------------------------------------------- */
@@ -1595,6 +1722,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
		ret = k400_allocate(hdev);
		if (ret)
			goto allocate_fail;
	} else if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
		ret = g920_allocate(hdev);
		if (ret)
			goto allocate_fail;
	}

	INIT_WORK(&hidpp->work, delayed_work_cb);
@@ -1648,6 +1779,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
		ret = wtp_get_config(hidpp);
		if (ret)
			goto hid_hw_open_failed;
	} else if (connected && (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)) {
		ret = g920_get_config(hidpp);
		if (ret)
			goto hid_hw_open_failed;
	}

	/* Block incoming packets */
@@ -1673,6 +1808,7 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
hid_hw_open_failed:
	hid_device_io_stop(hdev);
	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
		device_remove_file(&hdev->dev, &dev_attr_range);
		hid_hw_close(hdev);
		hid_hw_stop(hdev);
	}
@@ -1689,8 +1825,10 @@ static void hidpp_remove(struct hid_device *hdev)
{
	struct hidpp_device *hidpp = hid_get_drvdata(hdev);

	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920)
	if (hidpp->quirks & HIDPP_QUIRK_CLASS_G920) {
		device_remove_file(&hdev->dev, &dev_attr_range);
		hid_hw_close(hdev);
	}
	hid_hw_stop(hdev);
	cancel_work_sync(&hidpp->work);
	mutex_destroy(&hidpp->send_mutex);