2498 lines
79 KiB
Diff
2498 lines
79 KiB
Diff
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Andrey Smirnov <andrew.smirnov@gmail.com>
|
||
|
Date: Sat, 19 Feb 2022 16:08:36 -0800
|
||
|
Subject: [PATCH] mfd: Add MFD core driver for Steam Deck
|
||
|
|
||
|
Add MFD core driver for Steam Deck. Doesn't really do much so far
|
||
|
besides instantiating a number of MFD cells that implement all the
|
||
|
interesting functionality.
|
||
|
|
||
|
(cherry picked from commit 5f534c2d6ebdefccb9c024eb0f013bc1c0c622d9)
|
||
|
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
|
||
|
Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
|
||
|
---
|
||
|
drivers/mfd/Kconfig | 11 ++++
|
||
|
drivers/mfd/Makefile | 2 +
|
||
|
drivers/mfd/steamdeck.c | 127 ++++++++++++++++++++++++++++++++++++++++
|
||
|
3 files changed, 140 insertions(+)
|
||
|
create mode 100644 drivers/mfd/steamdeck.c
|
||
|
|
||
|
diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
|
||
|
index 8b93856de432..af335d9150e9 100644
|
||
|
--- a/drivers/mfd/Kconfig
|
||
|
+++ b/drivers/mfd/Kconfig
|
||
|
@@ -2260,5 +2260,16 @@ config MFD_RSMU_SPI
|
||
|
Additional drivers must be enabled in order to use the functionality
|
||
|
of the device.
|
||
|
|
||
|
+config MFD_STEAMDECK
|
||
|
+ tristate "Valve Steam Deck"
|
||
|
+ select MFD_CORE
|
||
|
+ depends on ACPI
|
||
|
+ depends on X86_64 || COMPILE_TEST
|
||
|
+ help
|
||
|
+ This driver registers various MFD cells that expose aspects
|
||
|
+ of Steam Deck specific ACPI functionality.
|
||
|
+
|
||
|
+ Say N here, unless you are running on Steam Deck hardware.
|
||
|
+
|
||
|
endmenu
|
||
|
endif
|
||
|
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
|
||
|
index 7ed3ef4a698c..d01254ef0106 100644
|
||
|
--- a/drivers/mfd/Makefile
|
||
|
+++ b/drivers/mfd/Makefile
|
||
|
@@ -280,3 +280,5 @@ rsmu-i2c-objs := rsmu_core.o rsmu_i2c.o
|
||
|
rsmu-spi-objs := rsmu_core.o rsmu_spi.o
|
||
|
obj-$(CONFIG_MFD_RSMU_I2C) += rsmu-i2c.o
|
||
|
obj-$(CONFIG_MFD_RSMU_SPI) += rsmu-spi.o
|
||
|
+
|
||
|
+obj-$(CONFIG_MFD_STEAMDECK) += steamdeck.o
|
||
|
diff --git a/drivers/mfd/steamdeck.c b/drivers/mfd/steamdeck.c
|
||
|
new file mode 100644
|
||
|
index 000000000000..0e504b3c2796
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/mfd/steamdeck.c
|
||
|
@@ -0,0 +1,127 @@
|
||
|
+// SPDX-License-Identifier: GPL-2.0+
|
||
|
+
|
||
|
+/*
|
||
|
+ * Steam Deck EC MFD core driver
|
||
|
+ *
|
||
|
+ * Copyright (C) 2021-2022 Valve Corporation
|
||
|
+ *
|
||
|
+ */
|
||
|
+
|
||
|
+#include <linux/acpi.h>
|
||
|
+#include <linux/platform_device.h>
|
||
|
+#include <linux/mfd/core.h>
|
||
|
+
|
||
|
+#define STEAMDECK_STA_OK \
|
||
|
+ (ACPI_STA_DEVICE_ENABLED | \
|
||
|
+ ACPI_STA_DEVICE_PRESENT | \
|
||
|
+ ACPI_STA_DEVICE_FUNCTIONING)
|
||
|
+
|
||
|
+struct steamdeck {
|
||
|
+ struct acpi_device *adev;
|
||
|
+ struct device *dev;
|
||
|
+};
|
||
|
+
|
||
|
+#define STEAMDECK_ATTR_RO(_name, _method) \
|
||
|
+ static ssize_t _name##_show(struct device *dev, \
|
||
|
+ struct device_attribute *attr, \
|
||
|
+ char *buf) \
|
||
|
+ { \
|
||
|
+ struct steamdeck *sd = dev_get_drvdata(dev); \
|
||
|
+ unsigned long long val; \
|
||
|
+ \
|
||
|
+ if (ACPI_FAILURE(acpi_evaluate_integer( \
|
||
|
+ sd->adev->handle, \
|
||
|
+ _method, NULL, &val))) \
|
||
|
+ return -EIO; \
|
||
|
+ \
|
||
|
+ return sysfs_emit(buf, "%llu\n", val); \
|
||
|
+ } \
|
||
|
+ static DEVICE_ATTR_RO(_name)
|
||
|
+
|
||
|
+STEAMDECK_ATTR_RO(firmware_version, "PDFW");
|
||
|
+STEAMDECK_ATTR_RO(board_id, "BOID");
|
||
|
+
|
||
|
+static struct attribute *steamdeck_attrs[] = {
|
||
|
+ &dev_attr_firmware_version.attr,
|
||
|
+ &dev_attr_board_id.attr,
|
||
|
+ NULL
|
||
|
+};
|
||
|
+
|
||
|
+ATTRIBUTE_GROUPS(steamdeck);
|
||
|
+
|
||
|
+static const struct mfd_cell steamdeck_cells[] = {
|
||
|
+ { .name = "steamdeck-hwmon" },
|
||
|
+ { .name = "steamdeck-leds" },
|
||
|
+ { .name = "steamdeck-extcon" },
|
||
|
+};
|
||
|
+
|
||
|
+static void steamdeck_remove_sysfs_groups(void *data)
|
||
|
+{
|
||
|
+ struct steamdeck *sd = data;
|
||
|
+
|
||
|
+ sysfs_remove_groups(&sd->dev->kobj, steamdeck_groups);
|
||
|
+}
|
||
|
+
|
||
|
+static int steamdeck_probe(struct platform_device *pdev)
|
||
|
+{
|
||
|
+ struct device *dev = &pdev->dev;
|
||
|
+ unsigned long long sta;
|
||
|
+ struct steamdeck *sd;
|
||
|
+ acpi_status status;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL);
|
||
|
+ if (!sd)
|
||
|
+ return -ENOMEM;
|
||
|
+ sd->adev = ACPI_COMPANION(dev);
|
||
|
+ sd->dev = dev;
|
||
|
+ platform_set_drvdata(pdev, sd);
|
||
|
+
|
||
|
+ status = acpi_evaluate_integer(sd->adev->handle, "_STA",
|
||
|
+ NULL, &sta);
|
||
|
+ if (ACPI_FAILURE(status)) {
|
||
|
+ dev_err(dev, "Status check failed (0x%x)\n", status);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ if ((sta & STEAMDECK_STA_OK) != STEAMDECK_STA_OK) {
|
||
|
+ dev_err(dev, "Device is not ready\n");
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = sysfs_create_groups(&dev->kobj, steamdeck_groups);
|
||
|
+ if (ret) {
|
||
|
+ dev_err(dev, "Failed to create sysfs group\n");
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = devm_add_action_or_reset(dev, steamdeck_remove_sysfs_groups,
|
||
|
+ sd);
|
||
|
+ if (ret) {
|
||
|
+ dev_err(dev, "Failed to register devres action\n");
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ return devm_mfd_add_devices(dev, PLATFORM_DEVID_NONE,
|
||
|
+ steamdeck_cells, ARRAY_SIZE(steamdeck_cells),
|
||
|
+ NULL, 0, NULL);
|
||
|
+}
|
||
|
+
|
||
|
+static const struct acpi_device_id steamdeck_device_ids[] = {
|
||
|
+ { "VLV0100", 0 },
|
||
|
+ { "", 0 },
|
||
|
+};
|
||
|
+MODULE_DEVICE_TABLE(acpi, steamdeck_device_ids);
|
||
|
+
|
||
|
+static struct platform_driver steamdeck_driver = {
|
||
|
+ .probe = steamdeck_probe,
|
||
|
+ .driver = {
|
||
|
+ .name = "steamdeck",
|
||
|
+ .acpi_match_table = steamdeck_device_ids,
|
||
|
+ },
|
||
|
+};
|
||
|
+module_platform_driver(steamdeck_driver);
|
||
|
+
|
||
|
+MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
|
||
|
+MODULE_DESCRIPTION("Steam Deck EC MFD core driver");
|
||
|
+MODULE_LICENSE("GPL");
|
||
|
|
||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Andrey Smirnov <andrew.smirnov@gmail.com>
|
||
|
Date: Sat, 19 Feb 2022 16:09:45 -0800
|
||
|
Subject: [PATCH] hwmon: Add driver for Steam Deck's EC sensors
|
||
|
|
||
|
Add driver for sensors exposed by EC firmware on Steam Deck hardware.
|
||
|
|
||
|
(cherry picked from commit 6917aac77bee6185ae3920b936cdbe7876118c0b)
|
||
|
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
|
||
|
Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
|
||
|
---
|
||
|
drivers/hwmon/Kconfig | 11 ++
|
||
|
drivers/hwmon/Makefile | 1 +
|
||
|
drivers/hwmon/steamdeck-hwmon.c | 224 ++++++++++++++++++++++++++++++++
|
||
|
3 files changed, 236 insertions(+)
|
||
|
create mode 100644 drivers/hwmon/steamdeck-hwmon.c
|
||
|
|
||
|
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
|
||
|
index 7ac3daaf59ce..d784c78417cf 100644
|
||
|
--- a/drivers/hwmon/Kconfig
|
||
|
+++ b/drivers/hwmon/Kconfig
|
||
|
@@ -1900,6 +1900,17 @@ config SENSORS_SCH5636
|
||
|
This driver can also be built as a module. If so, the module
|
||
|
will be called sch5636.
|
||
|
|
||
|
+config SENSORS_STEAMDECK
|
||
|
+ tristate "Steam Deck EC sensors"
|
||
|
+ depends on MFD_STEAMDECK
|
||
|
+ help
|
||
|
+ If you say yes here you get support for the hardware
|
||
|
+ monitoring features exposed by EC firmware on Steam Deck
|
||
|
+ devices
|
||
|
+
|
||
|
+ This driver can also be built as a module. If so, the module
|
||
|
+ will be called steamdeck-hwmon.
|
||
|
+
|
||
|
config SENSORS_STTS751
|
||
|
tristate "ST Microelectronics STTS751"
|
||
|
depends on I2C
|
||
|
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
|
||
|
index 11d076cad8a2..d03c1e1d339f 100644
|
||
|
--- a/drivers/hwmon/Makefile
|
||
|
+++ b/drivers/hwmon/Makefile
|
||
|
@@ -191,6 +191,7 @@ obj-$(CONFIG_SENSORS_SMSC47B397)+= smsc47b397.o
|
||
|
obj-$(CONFIG_SENSORS_SMSC47M1) += smsc47m1.o
|
||
|
obj-$(CONFIG_SENSORS_SMSC47M192)+= smsc47m192.o
|
||
|
obj-$(CONFIG_SENSORS_SPARX5) += sparx5-temp.o
|
||
|
+obj-$(CONFIG_SENSORS_STEAMDECK) += steamdeck-hwmon.o
|
||
|
obj-$(CONFIG_SENSORS_STTS751) += stts751.o
|
||
|
obj-$(CONFIG_SENSORS_SY7636A) += sy7636a-hwmon.o
|
||
|
obj-$(CONFIG_SENSORS_AMC6821) += amc6821.o
|
||
|
diff --git a/drivers/hwmon/steamdeck-hwmon.c b/drivers/hwmon/steamdeck-hwmon.c
|
||
|
new file mode 100644
|
||
|
index 000000000000..fab9e9460bd4
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/hwmon/steamdeck-hwmon.c
|
||
|
@@ -0,0 +1,224 @@
|
||
|
+// SPDX-License-Identifier: GPL-2.0+
|
||
|
+/*
|
||
|
+ * Steam Deck EC sensors driver
|
||
|
+ *
|
||
|
+ * Copyright (C) 2021-2022 Valve Corporation
|
||
|
+ */
|
||
|
+
|
||
|
+#include <linux/acpi.h>
|
||
|
+#include <linux/hwmon.h>
|
||
|
+#include <linux/platform_device.h>
|
||
|
+
|
||
|
+#define STEAMDECK_HWMON_NAME "steamdeck-hwmon"
|
||
|
+
|
||
|
+struct steamdeck_hwmon {
|
||
|
+ struct acpi_device *adev;
|
||
|
+};
|
||
|
+
|
||
|
+static long
|
||
|
+steamdeck_hwmon_get(struct steamdeck_hwmon *sd, const char *method)
|
||
|
+{
|
||
|
+ unsigned long long val;
|
||
|
+ if (ACPI_FAILURE(acpi_evaluate_integer(sd->adev->handle,
|
||
|
+ (char *)method, NULL, &val)))
|
||
|
+ return -EIO;
|
||
|
+
|
||
|
+ return val;
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+steamdeck_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
|
||
|
+ u32 attr, int channel, long *out)
|
||
|
+{
|
||
|
+ struct steamdeck_hwmon *sd = dev_get_drvdata(dev);
|
||
|
+
|
||
|
+ switch (type) {
|
||
|
+ case hwmon_curr:
|
||
|
+ if (attr != hwmon_curr_input)
|
||
|
+ return -EOPNOTSUPP;
|
||
|
+
|
||
|
+ *out = steamdeck_hwmon_get(sd, "PDAM");
|
||
|
+ if (*out < 0)
|
||
|
+ return *out;
|
||
|
+ break;
|
||
|
+ case hwmon_in:
|
||
|
+ if (attr != hwmon_in_input)
|
||
|
+ return -EOPNOTSUPP;
|
||
|
+
|
||
|
+ *out = steamdeck_hwmon_get(sd, "PDVL");
|
||
|
+ if (*out < 0)
|
||
|
+ return *out;
|
||
|
+ break;
|
||
|
+ case hwmon_temp:
|
||
|
+ if (attr != hwmon_temp_input)
|
||
|
+ return -EOPNOTSUPP;
|
||
|
+
|
||
|
+ *out = steamdeck_hwmon_get(sd, "BATT");
|
||
|
+ if (*out < 0)
|
||
|
+ return *out;
|
||
|
+ /*
|
||
|
+ * Assuming BATT returns deg C we need to mutiply it
|
||
|
+ * by 1000 to convert to mC
|
||
|
+ */
|
||
|
+ *out *= 1000;
|
||
|
+ break;
|
||
|
+ case hwmon_fan:
|
||
|
+ switch (attr) {
|
||
|
+ case hwmon_fan_input:
|
||
|
+ *out = steamdeck_hwmon_get(sd, "FANR");
|
||
|
+ if (*out < 0)
|
||
|
+ return *out;
|
||
|
+ break;
|
||
|
+ case hwmon_fan_target:
|
||
|
+ *out = steamdeck_hwmon_get(sd, "FSSR");
|
||
|
+ if (*out < 0)
|
||
|
+ return *out;
|
||
|
+ break;
|
||
|
+ case hwmon_fan_fault:
|
||
|
+ *out = steamdeck_hwmon_get(sd, "FANC");
|
||
|
+ if (*out < 0)
|
||
|
+ return *out;
|
||
|
+ /*
|
||
|
+ * FANC (Fan check):
|
||
|
+ * 0: Abnormal
|
||
|
+ * 1: Normal
|
||
|
+ */
|
||
|
+ *out = !*out;
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ return -EOPNOTSUPP;
|
||
|
+ }
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ return -EOPNOTSUPP;
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+steamdeck_hwmon_read_string(struct device *dev, enum hwmon_sensor_types type,
|
||
|
+ u32 attr, int channel, const char **str)
|
||
|
+{
|
||
|
+ switch (type) {
|
||
|
+ /*
|
||
|
+ * These two aren't, strictly speaking, measured. EC
|
||
|
+ * firmware just reports what PD negotiation resulted
|
||
|
+ * in.
|
||
|
+ */
|
||
|
+ case hwmon_curr:
|
||
|
+ *str = "PD Contract Current";
|
||
|
+ break;
|
||
|
+ case hwmon_in:
|
||
|
+ *str = "PD Contract Voltage";
|
||
|
+ break;
|
||
|
+ case hwmon_temp:
|
||
|
+ *str = "Battery Temp";
|
||
|
+ break;
|
||
|
+ case hwmon_fan:
|
||
|
+ *str = "System Fan";
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ return -EOPNOTSUPP;
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int
|
||
|
+steamdeck_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
|
||
|
+ u32 attr, int channel, long val)
|
||
|
+{
|
||
|
+ struct steamdeck_hwmon *sd = dev_get_drvdata(dev);
|
||
|
+
|
||
|
+ if (type != hwmon_fan ||
|
||
|
+ attr != hwmon_fan_target)
|
||
|
+ return -EOPNOTSUPP;
|
||
|
+
|
||
|
+ val = clamp_val(val, 0, 7300);
|
||
|
+
|
||
|
+ if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle,
|
||
|
+ "FANS", val)))
|
||
|
+ return -EIO;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static umode_t
|
||
|
+steamdeck_hwmon_is_visible(const void *data, enum hwmon_sensor_types type,
|
||
|
+ u32 attr, int channel)
|
||
|
+{
|
||
|
+ if (type == hwmon_fan &&
|
||
|
+ attr == hwmon_fan_target)
|
||
|
+ return 0644;
|
||
|
+
|
||
|
+ return 0444;
|
||
|
+}
|
||
|
+
|
||
|
+static const struct hwmon_channel_info *steamdeck_hwmon_info[] = {
|
||
|
+ HWMON_CHANNEL_INFO(in,
|
||
|
+ HWMON_I_INPUT | HWMON_I_LABEL),
|
||
|
+ HWMON_CHANNEL_INFO(curr,
|
||
|
+ HWMON_C_INPUT | HWMON_C_LABEL),
|
||
|
+ HWMON_CHANNEL_INFO(temp,
|
||
|
+ HWMON_T_INPUT | HWMON_T_LABEL),
|
||
|
+ HWMON_CHANNEL_INFO(fan,
|
||
|
+ HWMON_F_INPUT | HWMON_F_LABEL |
|
||
|
+ HWMON_F_TARGET | HWMON_F_FAULT),
|
||
|
+ NULL
|
||
|
+};
|
||
|
+
|
||
|
+static const struct hwmon_ops steamdeck_hwmon_ops = {
|
||
|
+ .is_visible = steamdeck_hwmon_is_visible,
|
||
|
+ .read = steamdeck_hwmon_read,
|
||
|
+ .read_string = steamdeck_hwmon_read_string,
|
||
|
+ .write = steamdeck_hwmon_write,
|
||
|
+};
|
||
|
+
|
||
|
+static const struct hwmon_chip_info steamdeck_hwmon_chip_info = {
|
||
|
+ .ops = &steamdeck_hwmon_ops,
|
||
|
+ .info = steamdeck_hwmon_info,
|
||
|
+};
|
||
|
+
|
||
|
+static int steamdeck_hwmon_probe(struct platform_device *pdev)
|
||
|
+{
|
||
|
+ struct device *dev = &pdev->dev;
|
||
|
+ struct steamdeck_hwmon *sd;
|
||
|
+ struct device *hwmon;
|
||
|
+
|
||
|
+ sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL);
|
||
|
+ if (!sd)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ sd->adev = ACPI_COMPANION(dev->parent);
|
||
|
+ hwmon = devm_hwmon_device_register_with_info(dev,
|
||
|
+ "steamdeck_hwmon",
|
||
|
+ sd,
|
||
|
+ &steamdeck_hwmon_chip_info,
|
||
|
+ NULL);
|
||
|
+ if (IS_ERR(hwmon)) {
|
||
|
+ dev_err(dev, "Failed to register HWMON device");
|
||
|
+ return PTR_ERR(hwmon);
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static const struct platform_device_id steamdeck_hwmon_id_table[] = {
|
||
|
+ { .name = STEAMDECK_HWMON_NAME },
|
||
|
+ {}
|
||
|
+};
|
||
|
+MODULE_DEVICE_TABLE(platform, steamdeck_hwmon_id_table);
|
||
|
+
|
||
|
+static struct platform_driver steamdeck_hwmon_driver = {
|
||
|
+ .probe = steamdeck_hwmon_probe,
|
||
|
+ .driver = {
|
||
|
+ .name = STEAMDECK_HWMON_NAME,
|
||
|
+ },
|
||
|
+ .id_table = steamdeck_hwmon_id_table,
|
||
|
+};
|
||
|
+module_platform_driver(steamdeck_hwmon_driver);
|
||
|
+
|
||
|
+MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
|
||
|
+MODULE_DESCRIPTION("Steam Deck EC sensors driver");
|
||
|
+MODULE_LICENSE("GPL");
|
||
|
|
||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Andrey Smirnov <andrew.smirnov@gmail.com>
|
||
|
Date: Sun, 27 Feb 2022 12:58:05 -0800
|
||
|
Subject: [PATCH] leds: steamdeck: Add support for Steam Deck LED
|
||
|
|
||
|
(cherry picked from commit 85a86d19aa7022ff0555023d53aef78323a42d0c)
|
||
|
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
|
||
|
Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
|
||
|
---
|
||
|
drivers/leds/Kconfig | 7 ++++
|
||
|
drivers/leds/Makefile | 1 +
|
||
|
drivers/leds/leds-steamdeck.c | 74 +++++++++++++++++++++++++++++++++++
|
||
|
3 files changed, 82 insertions(+)
|
||
|
create mode 100644 drivers/leds/leds-steamdeck.c
|
||
|
|
||
|
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
|
||
|
index 499d0f215a8b..d1d761695cd6 100644
|
||
|
--- a/drivers/leds/Kconfig
|
||
|
+++ b/drivers/leds/Kconfig
|
||
|
@@ -864,6 +864,13 @@ config LEDS_ACER_A500
|
||
|
This option enables support for the Power Button LED of
|
||
|
Acer Iconia Tab A500.
|
||
|
|
||
|
+config LEDS_STEAMDECK
|
||
|
+ tristate "LED support for Steam Deck"
|
||
|
+ depends on LEDS_CLASS && MFD_STEAMDECK
|
||
|
+ help
|
||
|
+ This option enabled support for the status LED (next to the
|
||
|
+ power button) on Steam Deck
|
||
|
+
|
||
|
source "drivers/leds/blink/Kconfig"
|
||
|
|
||
|
comment "Flash and Torch LED drivers"
|
||
|
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
|
||
|
index 4fd2f92cd198..130a1c175dde 100644
|
||
|
--- a/drivers/leds/Makefile
|
||
|
+++ b/drivers/leds/Makefile
|
||
|
@@ -75,6 +75,7 @@
|
||
|
obj-$(CONFIG_LEDS_PWM) += leds-pwm.o
|
||
|
obj-$(CONFIG_LEDS_REGULATOR) += leds-regulator.o
|
||
|
obj-$(CONFIG_LEDS_SC27XX_BLTC) += leds-sc27xx-bltc.o
|
||
|
+obj-$(CONFIG_LEDS_STEAMDECK) += leds-steamdeck.o
|
||
|
obj-$(CONFIG_LEDS_SUNFIRE) += leds-sunfire.o
|
||
|
obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
|
||
|
obj-$(CONFIG_LEDS_TCA6507) += leds-tca6507.o
|
||
|
diff --git a/drivers/leds/leds-steamdeck.c b/drivers/leds/leds-steamdeck.c
|
||
|
new file mode 100644
|
||
|
index 000000000000..686500b8de73
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/leds/leds-steamdeck.c
|
||
|
@@ -0,0 +1,74 @@
|
||
|
+// SPDX-License-Identifier: GPL-2.0+
|
||
|
+
|
||
|
+/*
|
||
|
+ * Steam Deck EC MFD LED cell driver
|
||
|
+ *
|
||
|
+ * Copyright (C) 2021-2022 Valve Corporation
|
||
|
+ *
|
||
|
+ */
|
||
|
+
|
||
|
+#include <linux/acpi.h>
|
||
|
+#include <linux/leds.h>
|
||
|
+#include <linux/platform_device.h>
|
||
|
+
|
||
|
+struct steamdeck_led {
|
||
|
+ struct acpi_device *adev;
|
||
|
+ struct led_classdev cdev;
|
||
|
+};
|
||
|
+
|
||
|
+static int steamdeck_leds_brightness_set(struct led_classdev *cdev,
|
||
|
+ enum led_brightness value)
|
||
|
+{
|
||
|
+ struct steamdeck_led *sd = container_of(cdev, struct steamdeck_led,
|
||
|
+ cdev);
|
||
|
+
|
||
|
+ if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle,
|
||
|
+ "CHBV", value)))
|
||
|
+ return -EIO;
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static int steamdeck_leds_probe(struct platform_device *pdev)
|
||
|
+{
|
||
|
+ struct device *dev = &pdev->dev;
|
||
|
+ struct steamdeck_led *sd;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL);
|
||
|
+ if (!sd)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ sd->adev = ACPI_COMPANION(dev->parent);
|
||
|
+
|
||
|
+ sd->cdev.name = "status:white";
|
||
|
+ sd->cdev.brightness_set_blocking = steamdeck_leds_brightness_set;
|
||
|
+ sd->cdev.max_brightness = 100;
|
||
|
+
|
||
|
+ ret = devm_led_classdev_register(dev, &sd->cdev);
|
||
|
+ if (ret) {
|
||
|
+ dev_err(dev, "Failed to register LEDs device: %d\n", ret);
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static const struct platform_device_id steamdeck_leds_id_table[] = {
|
||
|
+ { .name = "steamdeck-leds" },
|
||
|
+ {}
|
||
|
+};
|
||
|
+MODULE_DEVICE_TABLE(platform, steamdeck_leds_id_table);
|
||
|
+
|
||
|
+static struct platform_driver steamdeck_leds_driver = {
|
||
|
+ .probe = steamdeck_leds_probe,
|
||
|
+ .driver = {
|
||
|
+ .name = "steamdeck-leds",
|
||
|
+ },
|
||
|
+ .id_table = steamdeck_leds_id_table,
|
||
|
+};
|
||
|
+module_platform_driver(steamdeck_leds_driver);
|
||
|
+
|
||
|
+MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
|
||
|
+MODULE_DESCRIPTION("Steam Deck LEDs driver");
|
||
|
+MODULE_LICENSE("GPL");
|
||
|
|
||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Andrey Smirnov <andrew.smirnov@gmail.com>
|
||
|
Date: Sun, 27 Feb 2022 14:46:08 -0800
|
||
|
Subject: [PATCH] extcon: Add driver for Steam Deck
|
||
|
|
||
|
(cherry picked from commit f9f2eddae582ae39d5f89c1218448fc259b90aa8)
|
||
|
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
|
||
|
Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
|
||
|
---
|
||
|
drivers/extcon/Kconfig | 7 ++
|
||
|
drivers/extcon/Makefile | 1 +
|
||
|
drivers/extcon/extcon-steamdeck.c | 180 ++++++++++++++++++++++++++++++
|
||
|
3 files changed, 188 insertions(+)
|
||
|
create mode 100644 drivers/extcon/extcon-steamdeck.c
|
||
|
|
||
|
diff --git a/drivers/extcon/Kconfig b/drivers/extcon/Kconfig
|
||
|
index 290186e44e6b..4d444a9e2c1f 100644
|
||
|
--- a/drivers/extcon/Kconfig
|
||
|
+++ b/drivers/extcon/Kconfig
|
||
|
@@ -189,4 +189,11 @@ config EXTCON_USBC_TUSB320
|
||
|
Say Y here to enable support for USB Type C cable detection extcon
|
||
|
support using a TUSB320.
|
||
|
|
||
|
+config EXTCON_STEAMDECK
|
||
|
+ tristate "Steam Deck extcon support"
|
||
|
+ depends on MFD_STEAMDECK
|
||
|
+ help
|
||
|
+ Say Y here to enable support of USB Type C cable detection extcon
|
||
|
+ support on Steam Deck devices
|
||
|
+
|
||
|
endif
|
||
|
diff --git a/drivers/extcon/Makefile b/drivers/extcon/Makefile
|
||
|
index 1b390d934ca9..1c7e217f29e4 100644
|
||
|
--- a/drivers/extcon/Makefile
|
||
|
+++ b/drivers/extcon/Makefile
|
||
|
@@ -25,3 +25,4 @@ obj-$(CONFIG_EXTCON_SM5502) += extcon-sm5502.o
|
||
|
obj-$(CONFIG_EXTCON_USB_GPIO) += extcon-usb-gpio.o
|
||
|
obj-$(CONFIG_EXTCON_USBC_CROS_EC) += extcon-usbc-cros-ec.o
|
||
|
obj-$(CONFIG_EXTCON_USBC_TUSB320) += extcon-usbc-tusb320.o
|
||
|
+obj-$(CONFIG_EXTCON_STEAMDECK) += extcon-steamdeck.o
|
||
|
diff --git a/drivers/extcon/extcon-steamdeck.c b/drivers/extcon/extcon-steamdeck.c
|
||
|
new file mode 100644
|
||
|
index 000000000000..74f190adc8ea
|
||
|
--- /dev/null
|
||
|
+++ b/drivers/extcon/extcon-steamdeck.c
|
||
|
@@ -0,0 +1,180 @@
|
||
|
+
|
||
|
+#include <linux/acpi.h>
|
||
|
+#include <linux/platform_device.h>
|
||
|
+#include <linux/extcon-provider.h>
|
||
|
+
|
||
|
+#define ACPI_STEAMDECK_NOTIFY_STATUS 0x80
|
||
|
+
|
||
|
+/* 0 - port connected, 1 -port disconnected */
|
||
|
+#define ACPI_STEAMDECK_PORT_CONNECT BIT(0)
|
||
|
+/* 0 - Upstream Facing Port, 1 - Downdstream Facing Port */
|
||
|
+#define ACPI_STEAMDECK_CUR_DATA_ROLE BIT(3)
|
||
|
+/*
|
||
|
+ * Debouncing delay to allow negotiation process to settle. 2s value
|
||
|
+ * was arrived at via trial and error.
|
||
|
+ */
|
||
|
+#define STEAMDECK_ROLE_SWITCH_DELAY (msecs_to_jiffies(2000))
|
||
|
+
|
||
|
+struct steamdeck_extcon {
|
||
|
+ struct acpi_device *adev;
|
||
|
+ struct delayed_work role_work;
|
||
|
+ struct extcon_dev *edev;
|
||
|
+ struct device *dev;
|
||
|
+};
|
||
|
+
|
||
|
+static int steamdeck_read_pdcs(struct steamdeck_extcon *sd, unsigned long long *pdcs)
|
||
|
+{
|
||
|
+ acpi_status status;
|
||
|
+
|
||
|
+ status = acpi_evaluate_integer(sd->adev->handle, "PDCS", NULL, pdcs);
|
||
|
+ if (ACPI_FAILURE(status)) {
|
||
|
+ dev_err(sd->dev, "PDCS evaluation failed: %s\n",
|
||
|
+ acpi_format_exception(status));
|
||
|
+ return -EIO;
|
||
|
+ }
|
||
|
+
|
||
|
+ return 0;
|
||
|
+}
|
||
|
+
|
||
|
+static void steamdeck_usb_role_work(struct work_struct *work)
|
||
|
+{
|
||
|
+ struct steamdeck_extcon *sd =
|
||
|
+ container_of(work, struct steamdeck_extcon, role_work.work);
|
||
|
+ unsigned long long pdcs;
|
||
|
+ bool usb_host;
|
||
|
+
|
||
|
+ if (steamdeck_read_pdcs(sd, &pdcs))
|
||
|
+ return;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * We only care about these two
|
||
|
+ */
|
||
|
+ pdcs &= ACPI_STEAMDECK_PORT_CONNECT | ACPI_STEAMDECK_CUR_DATA_ROLE;
|
||
|
+
|
||
|
+ /*
|
||
|
+ * For "connect" events our role is determined by a bit in
|
||
|
+ * PDCS, for "disconnect" we switch to being a gadget
|
||
|
+ * unconditionally. The thinking for the latter is we don't
|
||
|
+ * want to start acting as a USB host until we get
|
||
|
+ * confirmation from the firmware that we are a USB host
|
||
|
+ */
|
||
|
+ usb_host = (pdcs & ACPI_STEAMDECK_PORT_CONNECT) ?
|
||
|
+ pdcs & ACPI_STEAMDECK_CUR_DATA_ROLE : false;
|
||
|
+
|
||
|
+ dev_dbg(sd->dev, "USB role is %s\n", usb_host ? "host" : "device");
|
||
|
+ WARN_ON(extcon_set_state_sync(sd->edev, EXTCON_USB_HOST,
|
||
|
+ usb_host));
|
||
|
+
|
||
|
+}
|
||
|
+
|
||
|
+static void steamdeck_notify(acpi_handle handle, u32 event, void *context)
|
||
|
+{
|
||
|
+ struct device *dev = context;
|
||
|
+ struct steamdeck_extcon *sd = dev_get_drvdata(dev);
|
||
|
+ unsigned long long pdcs;
|
||
|
+ unsigned long delay;
|
||
|
+
|
||
|
+ switch (event) {
|
||
|
+ case ACPI_STEAMDECK_NOTIFY_STATUS:
|
||
|
+ if (steamdeck_read_pdcs(sd, &pdcs))
|
||
|
+ return;
|
||
|
+ /*
|
||
|
+ * We process "disconnect" events immediately and
|
||
|
+ * "connect" events with a delay to give the HW time
|
||
|
+ * to settle. For example attaching USB hub (at least
|
||
|
+ * for HW used for testing) will generate intermediary
|
||
|
+ * event with "host" bit not set, followed by the one
|
||
|
+ * that does have it set.
|
||
|
+ */
|
||
|
+ delay = (pdcs & ACPI_STEAMDECK_PORT_CONNECT) ?
|
||
|
+ STEAMDECK_ROLE_SWITCH_DELAY : 0;
|
||
|
+
|
||
|
+ queue_delayed_work(system_long_wq, &sd->role_work, delay);
|
||
|
+ break;
|
||
|
+ default:
|
||
|
+ dev_warn(dev, "Unsupported event [0x%x]\n", event);
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
+static void steamdeck_remove_notify_handler(void *data)
|
||
|
+{
|
||
|
+ struct steamdeck_extcon *sd = data;
|
||
|
+
|
||
|
+ acpi_remove_notify_handler(sd->adev->handle, ACPI_DEVICE_NOTIFY,
|
||
|
+ steamdeck_notify);
|
||
|
+ cancel_delayed_work_sync(&sd->role_work);
|
||
|
+}
|
||
|
+
|
||
|
+static const unsigned int steamdeck_extcon_cable[] = {
|
||
|
+ EXTCON_USB,
|
||
|
+ EXTCON_USB_HOST,
|
||
|
+ EXTCON_CHG_USB_SDP,
|
||
|
+ EXTCON_CHG_USB_CDP,
|
||
|
+ EXTCON_CHG_USB_DCP,
|
||
|
+ EXTCON_CHG_USB_ACA,
|
||
|
+ EXTCON_NONE,
|
||
|
+};
|
||
|
+
|
||
|
+static int steamdeck_extcon_probe(struct platform_device *pdev)
|
||
|
+{
|
||
|
+ struct device *dev = &pdev->dev;
|
||
|
+ struct steamdeck_extcon *sd;
|
||
|
+ acpi_status status;
|
||
|
+ int ret;
|
||
|
+
|
||
|
+ sd = devm_kzalloc(dev, sizeof(*sd), GFP_KERNEL);
|
||
|
+ if (!sd)
|
||
|
+ return -ENOMEM;
|
||
|
+
|
||
|
+ INIT_DELAYED_WORK(&sd->role_work, steamdeck_usb_role_work);
|
||
|
+ platform_set_drvdata(pdev, sd);
|
||
|
+ sd->adev = ACPI_COMPANION(dev->parent);
|
||
|
+ sd->dev = dev;
|
||
|
+ sd->edev = devm_extcon_dev_allocate(dev, steamdeck_extcon_cable);
|
||
|
+ if (IS_ERR(sd->edev))
|
||
|
+ return PTR_ERR(sd->edev);
|
||
|
+
|
||
|
+ ret = devm_extcon_dev_register(dev, sd->edev);
|
||
|
+ if (ret < 0) {
|
||
|
+ dev_err(dev, "Failed to register extcon device: %d\n", ret);
|
||
|
+ return ret;
|
||
|
+ }
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Set initial role value
|
||
|
+ */
|
||
|
+ queue_delayed_work(system_long_wq, &sd->role_work, 0);
|
||
|
+ flush_delayed_work(&sd->role_work);
|
||
|
+
|
||
|
+ status = acpi_install_notify_handler(sd->adev->handle,
|
||
|
+ ACPI_DEVICE_NOTIFY,
|
||
|
+ steamdeck_notify,
|
||
|
+ dev);
|
||
|
+ if (ACPI_FAILURE(status)) {
|
||
|
+ dev_err(dev, "Error installing ACPI notify handler\n");
|
||
|
+ return -EIO;
|
||
|
+ }
|
||
|
+
|
||
|
+ ret = devm_add_action_or_reset(dev, steamdeck_remove_notify_handler,
|
||
|
+ sd);
|
||
|
+ return ret;
|
||
|
+}
|
||
|
+
|
||
|
+static const struct platform_device_id steamdeck_extcon_id_table[] = {
|
||
|
+ { .name = "steamdeck-extcon" },
|
||
|
+ {}
|
||
|
+};
|
||
|
+MODULE_DEVICE_TABLE(platform, steamdeck_extcon_id_table);
|
||
|
+
|
||
|
+static struct platform_driver steamdeck_extcon_driver = {
|
||
|
+ .probe = steamdeck_extcon_probe,
|
||
|
+ .driver = {
|
||
|
+ .name = "steamdeck-extcon",
|
||
|
+ },
|
||
|
+ .id_table = steamdeck_extcon_id_table,
|
||
|
+};
|
||
|
+module_platform_driver(steamdeck_extcon_driver);
|
||
|
+
|
||
|
+MODULE_AUTHOR("Andrey Smirnov <andrew.smirnov@gmail.com>");
|
||
|
+MODULE_DESCRIPTION("Steam Deck extcon driver");
|
||
|
+MODULE_LICENSE("GPL");
|
||
|
|
||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Andrey Smirnov <andrew.smirnov@gmail.com>
|
||
|
Date: Sat, 15 Jul 2023 12:58:54 -0700
|
||
|
Subject: [PATCH] hwmon: steamdeck-hwmon: Add support for max battery
|
||
|
level/rate
|
||
|
|
||
|
Add support for max battery level/charge rate attributes.
|
||
|
|
||
|
Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
|
||
|
(cherry picked from commit 50af83e8fd75dc52221edd3fb6fd7a7f70c4d8a4)
|
||
|
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
|
||
|
Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
|
||
|
---
|
||
|
drivers/hwmon/steamdeck-hwmon.c | 72 ++++++++++++++++++++++++++++++++-
|
||
|
1 file changed, 71 insertions(+), 1 deletion(-)
|
||
|
|
||
|
diff --git a/drivers/hwmon/steamdeck-hwmon.c b/drivers/hwmon/steamdeck-hwmon.c
|
||
|
index fab9e9460bd4..9d0a5471b181 100644
|
||
|
--- a/drivers/hwmon/steamdeck-hwmon.c
|
||
|
+++ b/drivers/hwmon/steamdeck-hwmon.c
|
||
|
@@ -180,6 +180,76 @@ static const struct hwmon_chip_info steamdeck_hwmon_chip_info = {
|
||
|
.info = steamdeck_hwmon_info,
|
||
|
};
|
||
|
|
||
|
+
|
||
|
+static ssize_t
|
||
|
+steamdeck_hwmon_simple_store(struct device *dev, const char *buf, size_t count,
|
||
|
+ const char *method,
|
||
|
+ unsigned long upper_limit)
|
||
|
+{
|
||
|
+ struct steamdeck_hwmon *sd = dev_get_drvdata(dev);
|
||
|
+ unsigned long value;
|
||
|
+
|
||
|
+ if (kstrtoul(buf, 10, &value) || value >= upper_limit)
|
||
|
+ return -EINVAL;
|
||
|
+
|
||
|
+ if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle,
|
||
|
+ (char *)method, value)))
|
||
|
+ return -EIO;
|
||
|
+
|
||
|
+ return count;
|
||
|
+}
|
||
|
+
|
||
|
+static ssize_t
|
||
|
+steamdeck_hwmon_simple_show(struct device *dev, char *buf,
|
||
|
+ const char *method)
|
||
|
+{
|
||
|
+ struct steamdeck_hwmon *sd = dev_get_drvdata(dev);
|
||
|
+ unsigned long value;
|
||
|
+
|
||
|
+ value = steamdeck_hwmon_get(sd, method);
|
||
|
+ if (value < 0)
|
||
|
+ return value;
|
||
|
+
|
||
|
+ return sprintf(buf, "%ld\n", value);
|
||
|
+}
|
||
|
+
|
||
|
+#define STEAMDECK_HWMON_ATTR_RW(_name, _set_method, _get_method, \
|
||
|
+ _upper_limit) \
|
||
|
+ static ssize_t _name##_show(struct device *dev, \
|
||
|
+ struct device_attribute *attr, \
|
||
|
+ char *buf) \
|
||
|
+ { \
|
||
|
+ return steamdeck_hwmon_simple_show(dev, buf, \
|
||
|
+ _get_method); \
|
||
|
+ } \
|
||
|
+ static ssize_t _name##_store(struct device *dev, \
|
||
|
+ struct device_attribute *attr, \
|
||
|
+ const char *buf, size_t count) \
|
||
|
+ { \
|
||
|
+ return steamdeck_hwmon_simple_store(dev, buf, count, \
|
||
|
+ _set_method, \
|
||
|
+ _upper_limit); \
|
||
|
+ } \
|
||
|
+ static DEVICE_ATTR_RW(_name)
|
||
|
+
|
||
|
+STEAMDECK_HWMON_ATTR_RW(max_battery_charge_level, "FCBL", "SFBL", 101);
|
||
|
+STEAMDECK_HWMON_ATTR_RW(max_battery_charge_rate, "CHGR", "GCHR", 101);
|
||
|
+
|
||
|
+static struct attribute *steamdeck_hwmon_attributes[] = {
|
||
|
+ &dev_attr_max_battery_charge_level.attr,
|
||
|
+ &dev_attr_max_battery_charge_rate.attr,
|
||
|
+ NULL
|
||
|
+};
|
||
|
+
|
||
|
+static const struct attribute_group steamdeck_hwmon_group = {
|
||
|
+ .attrs = steamdeck_hwmon_attributes,
|
||
|
+};
|
||
|
+
|
||
|
+static const struct attribute_group *steamdeck_hwmon_groups[] = {
|
||
|
+ &steamdeck_hwmon_group,
|
||
|
+ NULL
|
||
|
+};
|
||
|
+
|
||
|
static int steamdeck_hwmon_probe(struct platform_device *pdev)
|
||
|
{
|
||
|
struct device *dev = &pdev->dev;
|
||
|
@@ -195,7 +265,7 @@ static int steamdeck_hwmon_probe(struct platform_device *pdev)
|
||
|
"steamdeck_hwmon",
|
||
|
sd,
|
||
|
&steamdeck_hwmon_chip_info,
|
||
|
- NULL);
|
||
|
+ steamdeck_hwmon_groups);
|
||
|
if (IS_ERR(hwmon)) {
|
||
|
dev_err(dev, "Failed to register HWMON device");
|
||
|
return PTR_ERR(hwmon);
|
||
|
|
||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Andrey Smirnov <andrew.smirnov@gmail.com>
|
||
|
Date: Sun, 24 Sep 2023 15:02:33 -0700
|
||
|
Subject: [PATCH] mfd: steamdeck: Expose controller board power in sysfs
|
||
|
|
||
|
As of version 118 Deck's BIOS implements "SCBP" method that allows
|
||
|
gating power of the controller board (VBUS). Add a basic WO method to
|
||
|
our root MFD device to allow toggling that.
|
||
|
|
||
|
Signed-off-by: Andrey Smirnov <andrew.smirnov@gmail.com>
|
||
|
(cherry picked from commit f97f32718acc10cbb51fef925842392e80904d74)
|
||
|
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
|
||
|
Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
|
||
|
---
|
||
|
drivers/mfd/steamdeck.c | 20 ++++++++++++++++++++
|
||
|
1 file changed, 20 insertions(+)
|
||
|
|
||
|
diff --git a/drivers/mfd/steamdeck.c b/drivers/mfd/steamdeck.c
|
||
|
index 0e504b3c2796..a60fa7db9141 100644
|
||
|
--- a/drivers/mfd/steamdeck.c
|
||
|
+++ b/drivers/mfd/steamdeck.c
|
||
|
@@ -41,9 +41,29 @@ struct steamdeck {
|
||
|
STEAMDECK_ATTR_RO(firmware_version, "PDFW");
|
||
|
STEAMDECK_ATTR_RO(board_id, "BOID");
|
||
|
|
||
|
+static ssize_t controller_board_power_store(struct device *dev,
|
||
|
+ struct device_attribute *attr,
|
||
|
+ const char *buf, size_t count)
|
||
|
+{
|
||
|
+ struct steamdeck *sd = dev_get_drvdata(dev);
|
||
|
+ bool enabled;
|
||
|
+ ssize_t ret = kstrtobool(buf, &enabled);
|
||
|
+
|
||
|
+ if (ret)
|
||
|
+ return ret;
|
||
|
+
|
||
|
+ if (ACPI_FAILURE(acpi_execute_simple_method(sd->adev->handle,
|
||
|
+ "SCBP", enabled)))
|
||
|
+ return -EIO;
|
||
|
+
|
||
|
+ return count;
|
||
|
+}
|
||
|
+static DEVICE_ATTR_WO(controller_board_power);
|
||
|
+
|
||
|
static struct attribute *steamdeck_attrs[] = {
|
||
|
&dev_attr_firmware_version.attr,
|
||
|
&dev_attr_board_id.attr,
|
||
|
+ &dev_attr_controller_board_power.attr,
|
||
|
NULL
|
||
|
};
|
||
|
|
||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Vicki Pfau <vi@endrift.com>
|
||
|
Date: Thu, 30 Jun 2022 18:42:10 -0700
|
||
|
Subject: [PATCH 01/10] USB: gadget: f_hid: Add Get-Feature report
|
||
|
|
||
|
While the HID gadget implementation has been sufficient for devices that only
|
||
|
use INTERRUPT transfers, the USB HID standard includes provisions for Set- and
|
||
|
Get-Feature report CONTROL transfers that go over endpoint 0. These were
|
||
|
previously impossible with the existing implementation, and would either send
|
||
|
an empty reply, or stall out.
|
||
|
|
||
|
As the feature is a standard part of USB HID, it stands to reason that devices
|
||
|
would use it, and that the HID gadget should support it. This patch adds
|
||
|
support for (polled) device-to-host Get-Feature reports through a new ioctl
|
||
|
interface to the hidg class dev nodes.
|
||
|
|
||
|
Signed-off-by: Vicki Pfau <vi@endrift.com>
|
||
|
(cherry picked from commit 8437fa3861c7198a3e286f393c8637c4fc08d2bc)
|
||
|
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
|
||
|
---
|
||
|
drivers/usb/gadget/function/f_hid.c | 121 ++++++++++++++++++++++++++--
|
||
|
include/uapi/linux/usb/g_hid.h | 38 +++++++++
|
||
|
include/uapi/linux/usb/gadgetfs.h | 2 +-
|
||
|
3 files changed, 154 insertions(+), 7 deletions(-)
|
||
|
create mode 100644 include/uapi/linux/usb/g_hid.h
|
||
|
|
||
|
diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c
|
||
|
index ea85e2c701a15..6fec92b5a0bd9 100644
|
||
|
--- a/drivers/usb/gadget/function/f_hid.c
|
||
|
+++ b/drivers/usb/gadget/function/f_hid.c
|
||
|
@@ -16,6 +16,7 @@
|
||
|
#include <linux/wait.h>
|
||
|
#include <linux/sched.h>
|
||
|
#include <linux/usb/g_hid.h>
|
||
|
+#include <uapi/linux/usb/g_hid.h>
|
||
|
|
||
|
#include "u_f.h"
|
||
|
#include "u_hid.h"
|
||
|
@@ -75,6 +76,13 @@ struct f_hidg {
|
||
|
wait_queue_head_t write_queue;
|
||
|
struct usb_request *req;
|
||
|
|
||
|
+ /* get report */
|
||
|
+ struct usb_request *get_req;
|
||
|
+ struct usb_hidg_report get_report;
|
||
|
+ spinlock_t get_spinlock;
|
||
|
+ bool get_pending;
|
||
|
+ wait_queue_head_t get_queue;
|
||
|
+
|
||
|
struct device dev;
|
||
|
struct cdev cdev;
|
||
|
struct usb_function func;
|
||
|
@@ -523,6 +531,64 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer,
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
+
|
||
|
+static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *buffer)
|
||
|
+{
|
||
|
+ struct f_hidg *hidg = file->private_data;
|
||
|
+ struct usb_composite_dev *cdev = hidg->func.config->cdev;
|
||
|
+
|
||
|
+ int status = 0;
|
||
|
+ unsigned long flags;
|
||
|
+
|
||
|
+ spin_lock_irqsave(&hidg->get_spinlock, flags);
|
||
|
+
|
||
|
+#define GET_REPORT_COND (!hidg->get_pending)
|
||
|
+
|
||
|
+ while (!GET_REPORT_COND) {
|
||
|
+ spin_unlock_irqrestore(&hidg->get_spinlock, flags);
|
||
|
+
|
||
|
+ if (file->f_flags & O_NONBLOCK)
|
||
|
+ return -EAGAIN;
|
||
|
+
|
||
|
+ if (wait_event_interruptible_exclusive(hidg->get_queue,
|
||
|
+ GET_REPORT_COND))
|
||
|
+ return -ERESTARTSYS;
|
||
|
+
|
||
|
+ spin_lock_irqsave(&hidg->get_spinlock, flags);
|
||
|
+ if (!hidg->get_pending) {
|
||
|
+ spin_unlock_irqrestore(&hidg->get_spinlock, flags);
|
||
|
+ return -EINVAL;
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ hidg->get_pending = true;
|
||
|
+ spin_unlock_irqrestore(&hidg->get_spinlock, flags);
|
||
|
+
|
||
|
+ status = copy_from_user(&hidg->get_report, buffer,
|
||
|
+ sizeof(struct usb_hidg_report));
|
||
|
+ if (status != 0) {
|
||
|
+ ERROR(cdev, "copy_from_user error\n");
|
||
|
+ status = -EINVAL;
|
||
|
+ }
|
||
|
+
|
||
|
+ spin_lock_irqsave(&hidg->get_spinlock, flags);
|
||
|
+ hidg->get_pending = false;
|
||
|
+ spin_unlock_irqrestore(&hidg->get_spinlock, flags);
|
||
|
+
|
||
|
+ wake_up(&hidg->get_queue);
|
||
|
+ return status;
|
||
|
+}
|
||
|
+
|
||
|
+static long f_hidg_ioctl(struct file *file, unsigned int code, unsigned long arg)
|
||
|
+{
|
||
|
+ switch (code) {
|
||
|
+ case GADGET_HID_WRITE_GET_REPORT:
|
||
|
+ return f_hidg_get_report(file, (struct usb_hidg_report __user *)arg);
|
||
|
+ default:
|
||
|
+ return -ENOTTY;
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
|
||
|
{
|
||
|
struct f_hidg *hidg = file->private_data;
|
||
|
@@ -548,6 +614,7 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
|
||
|
#undef WRITE_COND
|
||
|
#undef READ_COND_SSREPORT
|
||
|
#undef READ_COND_INTOUT
|
||
|
+#undef GET_REPORT_COND
|
||
|
|
||
|
static int f_hidg_release(struct inode *inode, struct file *fd)
|
||
|
{
|
||
|
@@ -640,6 +707,10 @@ static void hidg_ssreport_complete(struct usb_ep *ep, struct usb_request *req)
|
||
|
wake_up(&hidg->read_queue);
|
||
|
}
|
||
|
|
||
|
+static void hidg_get_report_complete(struct usb_ep *ep, struct usb_request *req)
|
||
|
+{
|
||
|
+}
|
||
|
+
|
||
|
static int hidg_setup(struct usb_function *f,
|
||
|
const struct usb_ctrlrequest *ctrl)
|
||
|
{
|
||
|
@@ -647,6 +718,8 @@ static int hidg_setup(struct usb_function *f,
|
||
|
struct usb_composite_dev *cdev = f->config->cdev;
|
||
|
struct usb_request *req = cdev->req;
|
||
|
int status = 0;
|
||
|
+ unsigned long flags;
|
||
|
+ bool do_wake = false;
|
||
|
__u16 value, length;
|
||
|
|
||
|
value = __le16_to_cpu(ctrl->wValue);
|
||
|
@@ -659,14 +732,29 @@ static int hidg_setup(struct usb_function *f,
|
||
|
switch ((ctrl->bRequestType << 8) | ctrl->bRequest) {
|
||
|
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
|
||
|
| HID_REQ_GET_REPORT):
|
||
|
- VDBG(cdev, "get_report\n");
|
||
|
+ VDBG(cdev, "get_report | wLength=%d\n", ctrl->wLength);
|
||
|
|
||
|
- /* send an empty report */
|
||
|
- length = min_t(unsigned, length, hidg->report_length);
|
||
|
- memset(req->buf, 0x0, length);
|
||
|
+ req = hidg->get_req;
|
||
|
+ req->zero = 0;
|
||
|
+ req->length = min_t(unsigned, length, hidg->report_length);
|
||
|
+ status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
|
||
|
+ if (status < 0) {
|
||
|
+ ERROR(cdev, "usb_ep_queue error on get_report %d\n",
|
||
|
+ status);
|
||
|
|
||
|
- goto respond;
|
||
|
- break;
|
||
|
+ spin_lock_irqsave(&hidg->get_spinlock, flags);
|
||
|
+ if (hidg->get_pending) {
|
||
|
+ hidg->get_pending = false;
|
||
|
+ do_wake = true;
|
||
|
+ }
|
||
|
+ spin_unlock_irqrestore(&hidg->get_spinlock, flags);
|
||
|
+
|
||
|
+ if (do_wake) {
|
||
|
+ wake_up(&hidg->get_queue);
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ return status;
|
||
|
|
||
|
case ((USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
|
||
|
| HID_REQ_GET_PROTOCOL):
|
||
|
@@ -800,6 +888,14 @@ static void hidg_disable(struct usb_function *f)
|
||
|
|
||
|
hidg->req = NULL;
|
||
|
spin_unlock_irqrestore(&hidg->write_spinlock, flags);
|
||
|
+
|
||
|
+ spin_lock_irqsave(&hidg->get_spinlock, flags);
|
||
|
+ if (!hidg->get_pending) {
|
||
|
+ usb_ep_free_request(f->config->cdev->gadget->ep0, hidg->get_req);
|
||
|
+ hidg->get_pending = true;
|
||
|
+ }
|
||
|
+ hidg->get_req = NULL;
|
||
|
+ spin_unlock_irqrestore(&hidg->get_spinlock, flags);
|
||
|
}
|
||
|
|
||
|
static int hidg_set_alt(struct usb_function *f, unsigned intf, unsigned alt)
|
||
|
@@ -908,6 +1004,7 @@ static const struct file_operations f_hidg_fops = {
|
||
|
.write = f_hidg_write,
|
||
|
.read = f_hidg_read,
|
||
|
.poll = f_hidg_poll,
|
||
|
+ .unlocked_ioctl = f_hidg_ioctl,
|
||
|
.llseek = noop_llseek,
|
||
|
};
|
||
|
|
||
|
@@ -918,6 +1015,14 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
|
||
|
struct usb_string *us;
|
||
|
int status;
|
||
|
|
||
|
+ hidg->get_req = usb_ep_alloc_request(c->cdev->gadget->ep0, GFP_ATOMIC);
|
||
|
+ if (!hidg->get_req)
|
||
|
+ return -ENOMEM;
|
||
|
+ hidg->get_req->buf = hidg->get_report.data;
|
||
|
+ hidg->get_req->zero = 0;
|
||
|
+ hidg->get_req->complete = hidg_get_report_complete;
|
||
|
+ hidg->get_req->context = hidg;
|
||
|
+
|
||
|
/* maybe allocate device-global string IDs, and patch descriptors */
|
||
|
us = usb_gstrings_attach(c->cdev, ct_func_strings,
|
||
|
ARRAY_SIZE(ct_func_string_defs));
|
||
|
@@ -1003,8 +1108,10 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
|
||
|
hidg->write_pending = 1;
|
||
|
hidg->req = NULL;
|
||
|
spin_lock_init(&hidg->read_spinlock);
|
||
|
+ spin_lock_init(&hidg->get_spinlock);
|
||
|
init_waitqueue_head(&hidg->write_queue);
|
||
|
init_waitqueue_head(&hidg->read_queue);
|
||
|
+ init_waitqueue_head(&hidg->get_queue);
|
||
|
INIT_LIST_HEAD(&hidg->completed_out_req);
|
||
|
|
||
|
/* create char device */
|
||
|
@@ -1021,6 +1128,8 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
|
||
|
if (hidg->req != NULL)
|
||
|
free_ep_req(hidg->in_ep, hidg->req);
|
||
|
|
||
|
+ usb_ep_free_request(c->cdev->gadget->ep0, hidg->get_req);
|
||
|
+
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
diff --git a/include/uapi/linux/usb/g_hid.h b/include/uapi/linux/usb/g_hid.h
|
||
|
new file mode 100644
|
||
|
index 0000000000000..c6068b4863543
|
||
|
--- /dev/null
|
||
|
+++ b/include/uapi/linux/usb/g_hid.h
|
||
|
@@ -0,0 +1,38 @@
|
||
|
+/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
|
||
|
+/*
|
||
|
+ * g_hid.h -- Header file for USB HID gadget driver
|
||
|
+ *
|
||
|
+ * Copyright (C) 2022 Valve Software
|
||
|
+ *
|
||
|
+ * This program is free software; you can redistribute it and/or modify
|
||
|
+ * it under the terms of the GNU General Public License as published by
|
||
|
+ * the Free Software Foundation; either version 2 of the License, or
|
||
|
+ * (at your option) any later version.
|
||
|
+ *
|
||
|
+ * This program is distributed in the hope that it will be useful,
|
||
|
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
+ * GNU General Public License for more details.
|
||
|
+ *
|
||
|
+ * You should have received a copy of the GNU General Public License
|
||
|
+ * along with this program; if not, write to the Free Software
|
||
|
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
+ */
|
||
|
+
|
||
|
+#ifndef __UAPI_LINUX_USB_G_HID_H
|
||
|
+#define __UAPI_LINUX_USB_G_HID_H
|
||
|
+
|
||
|
+#include <linux/types.h>
|
||
|
+
|
||
|
+struct usb_hidg_report {
|
||
|
+ __u16 length;
|
||
|
+ __u8 data[512];
|
||
|
+};
|
||
|
+
|
||
|
+/* The 'g' code is also used by gadgetfs and hid gadget ioctl requests.
|
||
|
+ * Don't add any colliding codes to either driver, and keep
|
||
|
+ * them in unique ranges (size 0x20 for now).
|
||
|
+ */
|
||
|
+#define GADGET_HID_WRITE_GET_REPORT _IOW('g', 0x42, struct usb_hidg_report)
|
||
|
+
|
||
|
+#endif /* __UAPI_LINUX_USB_G_HID_H */
|
||
|
diff --git a/include/uapi/linux/usb/gadgetfs.h b/include/uapi/linux/usb/gadgetfs.h
|
||
|
index 835473910a498..9754822b2a409 100644
|
||
|
--- a/include/uapi/linux/usb/gadgetfs.h
|
||
|
+++ b/include/uapi/linux/usb/gadgetfs.h
|
||
|
@@ -62,7 +62,7 @@ struct usb_gadgetfs_event {
|
||
|
};
|
||
|
|
||
|
|
||
|
-/* The 'g' code is also used by printer gadget ioctl requests.
|
||
|
+/* The 'g' code is also used by printer and hid gadget ioctl requests.
|
||
|
* Don't add any colliding codes to either driver, and keep
|
||
|
* them in unique ranges (size 0x20 for now).
|
||
|
*/
|
||
|
--
|
||
|
2.41.0
|
||
|
|
||
|
|
||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Vicki Pfau <vi@endrift.com>
|
||
|
Date: Thu, 30 Jun 2022 18:43:10 -0700
|
||
|
Subject: [PATCH 02/10] USB: gadget: f_hid: Add Set-Feature report
|
||
|
|
||
|
While the HID gadget implementation has been sufficient for devices that only
|
||
|
use INTERRUPT transfers, the USB HID standard includes provisions for Set- and
|
||
|
Get-Feature report CONTROL transfers that go over endpoint 0. These were
|
||
|
previously impossible with the existing implementation, and would either send
|
||
|
an empty reply, or stall out.
|
||
|
|
||
|
As the feature is a standard part of USB HID, it stands to reason that devices
|
||
|
would use it, and that the HID gadget should support it. This patch adds
|
||
|
support for host-to-device Set-Feature reports through a new ioctl
|
||
|
interface to the hidg class dev nodes.
|
||
|
|
||
|
Signed-off-by: Vicki Pfau <vi@endrift.com>
|
||
|
(cherry picked from commit 3d82be0ec3aa3b947d9c927d7b06c433de15be8b)
|
||
|
Signed-off-by: Cristian Ciocaltea <cristian.ciocaltea@collabora.com>
|
||
|
---
|
||
|
drivers/usb/gadget/function/f_hid.c | 110 ++++++++++++++++++++++++++--
|
||
|
include/uapi/linux/usb/g_hid.h | 24 +-----
|
||
|
2 files changed, 106 insertions(+), 28 deletions(-)
|
||
|
|
||
|
diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c
|
||
|
index 6fec92b5a0bd9..172cba91aded1 100644
|
||
|
--- a/drivers/usb/gadget/function/f_hid.c
|
||
|
+++ b/drivers/usb/gadget/function/f_hid.c
|
||
|
@@ -76,6 +76,11 @@ struct f_hidg {
|
||
|
wait_queue_head_t write_queue;
|
||
|
struct usb_request *req;
|
||
|
|
||
|
+ /* set report */
|
||
|
+ struct list_head completed_set_req;
|
||
|
+ spinlock_t set_spinlock;
|
||
|
+ wait_queue_head_t set_queue;
|
||
|
+
|
||
|
/* get report */
|
||
|
struct usb_request *get_req;
|
||
|
struct usb_hidg_report get_report;
|
||
|
@@ -531,6 +536,54 @@ static ssize_t f_hidg_write(struct file *file, const char __user *buffer,
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
+static int f_hidg_set_report(struct file *file, struct usb_hidg_report __user *buffer)
|
||
|
+{
|
||
|
+ struct f_hidg *hidg = file->private_data;
|
||
|
+ struct f_hidg_req_list *list;
|
||
|
+ struct usb_request *req;
|
||
|
+ unsigned long flags;
|
||
|
+ unsigned short length;
|
||
|
+ int status;
|
||
|
+
|
||
|
+ spin_lock_irqsave(&hidg->set_spinlock, flags);
|
||
|
+
|
||
|
+#define SET_REPORT_COND (!list_empty(&hidg->completed_set_req))
|
||
|
+
|
||
|
+ /* wait for at least one buffer to complete */
|
||
|
+ while (!SET_REPORT_COND) {
|
||
|
+ spin_unlock_irqrestore(&hidg->set_spinlock, flags);
|
||
|
+ if (file->f_flags & O_NONBLOCK)
|
||
|
+ return -EAGAIN;
|
||
|
+
|
||
|
+ if (wait_event_interruptible(hidg->set_queue, SET_REPORT_COND))
|
||
|
+ return -ERESTARTSYS;
|
||
|
+
|
||
|
+ spin_lock_irqsave(&hidg->set_spinlock, flags);
|
||
|
+ }
|
||
|
+
|
||
|
+ /* pick the first one */
|
||
|
+ list = list_first_entry(&hidg->completed_set_req,
|
||
|
+ struct f_hidg_req_list, list);
|
||
|
+
|
||
|
+ /*
|
||
|
+ * Remove this from list to protect it from being free()
|
||
|
+ * while host disables our function
|
||
|
+ */
|
||
|
+ list_del(&list->list);
|
||
|
+
|
||
|
+ req = list->req;
|
||
|
+ spin_unlock_irqrestore(&hidg->set_spinlock, flags);
|
||
|
+
|
||
|
+ /* copy to user outside spinlock */
|
||
|
+ length = min_t(unsigned short, sizeof(buffer->data), req->actual);
|
||
|
+ status = copy_to_user(&buffer->length, &length, sizeof(buffer->length));
|
||
|
+ if (!status) {
|
||
|
+ status = copy_to_user(&buffer->data, req->buf, length);
|
||
|
+ }
|
||
|
+ kfree(list);
|
||
|
+ free_ep_req(hidg->func.config->cdev->gadget->ep0, req);
|
||
|
+ return status;
|
||
|
+}
|
||
|
|
||
|
static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *buffer)
|
||
|
{
|
||
|
@@ -582,6 +635,8 @@ static int f_hidg_get_report(struct file *file, struct usb_hidg_report __user *b
|
||
|
static long f_hidg_ioctl(struct file *file, unsigned int code, unsigned long arg)
|
||
|
{
|
||
|
switch (code) {
|
||
|
+ case GADGET_HID_READ_SET_REPORT:
|
||
|
+ return f_hidg_set_report(file, (struct usb_hidg_report __user *)arg);
|
||
|
case GADGET_HID_WRITE_GET_REPORT:
|
||
|
return f_hidg_get_report(file, (struct usb_hidg_report __user *)arg);
|
||
|
default:
|
||
|
@@ -596,6 +651,7 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
|
||
|
|
||
|
poll_wait(file, &hidg->read_queue, wait);
|
||
|
poll_wait(file, &hidg->write_queue, wait);
|
||
|
+ poll_wait(file, &hidg->set_queue, wait);
|
||
|
|
||
|
if (WRITE_COND)
|
||
|
ret |= EPOLLOUT | EPOLLWRNORM;
|
||
|
@@ -608,12 +664,16 @@ static __poll_t f_hidg_poll(struct file *file, poll_table *wait)
|
||
|
ret |= EPOLLIN | EPOLLRDNORM;
|
||
|
}
|
||
|
|
||
|
+ if (SET_REPORT_COND)
|
||
|
+ ret |= EPOLLPRI;
|
||
|
+
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
#undef WRITE_COND
|
||
|
#undef READ_COND_SSREPORT
|
||
|
#undef READ_COND_INTOUT
|
||
|
+#undef SET_REPORT_COND
|
||
|
#undef GET_REPORT_COND
|
||
|
|
||
|
static int f_hidg_release(struct inode *inode, struct file *fd)
|
||
|
@@ -658,11 +718,19 @@ static void hidg_intout_complete(struct usb_ep *ep, struct usb_request *req)
|
||
|
|
||
|
req_list->req = req;
|
||
|
|
||
|
- spin_lock_irqsave(&hidg->read_spinlock, flags);
|
||
|
- list_add_tail(&req_list->list, &hidg->completed_out_req);
|
||
|
- spin_unlock_irqrestore(&hidg->read_spinlock, flags);
|
||
|
+ if (ep == cdev->gadget->ep0) {
|
||
|
+ spin_lock_irqsave(&hidg->set_spinlock, flags);
|
||
|
+ list_add_tail(&req_list->list, &hidg->completed_set_req);
|
||
|
+ spin_unlock_irqrestore(&hidg->set_spinlock, flags);
|
||
|
|
||
|
- wake_up(&hidg->read_queue);
|
||
|
+ wake_up(&hidg->set_queue);
|
||
|
+ } else {
|
||
|
+ spin_lock_irqsave(&hidg->read_spinlock, flags);
|
||
|
+ list_add_tail(&req_list->list, &hidg->completed_out_req);
|
||
|
+ spin_unlock_irqrestore(&hidg->read_spinlock, flags);
|
||
|
+
|
||
|
+ wake_up(&hidg->read_queue);
|
||
|
+ }
|
||
|
break;
|
||
|
default:
|
||
|
ERROR(cdev, "Set report failed %d\n", req->status);
|
||
|
@@ -775,12 +843,27 @@ static int hidg_setup(struct usb_function *f,
|
||
|
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
|
||
|
| HID_REQ_SET_REPORT):
|
||
|
VDBG(cdev, "set_report | wLength=%d\n", ctrl->wLength);
|
||
|
- if (hidg->use_out_ep)
|
||
|
+ if (!hidg->use_out_ep) {
|
||
|
+ req->complete = hidg_ssreport_complete;
|
||
|
+ req->context = hidg;
|
||
|
+ goto respond;
|
||
|
+ }
|
||
|
+ if (!length)
|
||
|
goto stall;
|
||
|
- req->complete = hidg_ssreport_complete;
|
||
|
+ req = alloc_ep_req(cdev->gadget->ep0, GFP_ATOMIC);
|
||
|
+ if (!req)
|
||
|
+ return -ENOMEM;
|
||
|
+ req->complete = hidg_intout_complete;
|
||
|
req->context = hidg;
|
||
|
- goto respond;
|
||
|
- break;
|
||
|
+ req->zero = 0;
|
||
|
+ req->length = length;
|
||
|
+ status = usb_ep_queue(cdev->gadget->ep0, req, GFP_ATOMIC);
|
||
|
+ if (status < 0) {
|
||
|
+ ERROR(cdev, "usb_ep_queue error on set_report %d\n", status);
|
||
|
+ free_ep_req(cdev->gadget->ep0, req);
|
||
|
+ }
|
||
|
+
|
||
|
+ return status;
|
||
|
|
||
|
case ((USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE) << 8
|
||
|
| HID_REQ_SET_PROTOCOL):
|
||
|
@@ -880,6 +963,14 @@ static void hidg_disable(struct usb_function *f)
|
||
|
spin_unlock_irqrestore(&hidg->read_spinlock, flags);
|
||
|
}
|
||
|
|
||
|
+ spin_lock_irqsave(&hidg->set_spinlock, flags);
|
||
|
+ list_for_each_entry_safe(list, next, &hidg->completed_set_req, list) {
|
||
|
+ free_ep_req(f->config->cdev->gadget->ep0, list->req);
|
||
|
+ list_del(&list->list);
|
||
|
+ kfree(list);
|
||
|
+ }
|
||
|
+ spin_unlock_irqrestore(&hidg->set_spinlock, flags);
|
||
|
+
|
||
|
spin_lock_irqsave(&hidg->write_spinlock, flags);
|
||
|
if (!hidg->write_pending) {
|
||
|
free_ep_req(hidg->in_ep, hidg->req);
|
||
|
@@ -1108,11 +1199,14 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f)
|
||
|
hidg->write_pending = 1;
|
||
|
hidg->req = NULL;
|
||
|
spin_lock_init(&hidg->read_spinlock);
|
||
|
+ spin_lock_init(&hidg->set_spinlock);
|
||
|
spin_lock_init(&hidg->get_spinlock);
|
||
|
init_waitqueue_head(&hidg->write_queue);
|
||
|
init_waitqueue_head(&hidg->read_queue);
|
||
|
+ init_waitqueue_head(&hidg->set_queue);
|
||
|
init_waitqueue_head(&hidg->get_queue);
|
||
|
INIT_LIST_HEAD(&hidg->completed_out_req);
|
||
|
+ INIT_LIST_HEAD(&hidg->completed_set_req);
|
||
|
|
||
|
/* create char device */
|
||
|
cdev_init(&hidg->cdev, &f_hidg_fops);
|
||
|
diff --git a/include/uapi/linux/usb/g_hid.h b/include/uapi/linux/usb/g_hid.h
|
||
|
index c6068b4863543..54814c2c68d60 100644
|
||
|
--- a/include/uapi/linux/usb/g_hid.h
|
||
|
+++ b/include/uapi/linux/usb/g_hid.h
|
||
|
@@ -1,38 +1,22 @@
|
||
|
/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
|
||
|
-/*
|
||
|
- * g_hid.h -- Header file for USB HID gadget driver
|
||
|
- *
|
||
|
- * Copyright (C) 2022 Valve Software
|
||
|
- *
|
||
|
- * This program is free software; you can redistribute it and/or modify
|
||
|
- * it under the terms of the GNU General Public License as published by
|
||
|
- * the Free Software Foundation; either version 2 of the License, or
|
||
|
- * (at your option) any later version.
|
||
|
- *
|
||
|
- * This program is distributed in the hope that it will be useful,
|
||
|
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
- * GNU General Public License for more details.
|
||
|
- *
|
||
|
- * You should have received a copy of the GNU General Public License
|
||
|
- * along with this program; if not, write to the Free Software
|
||
|
- * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
- */
|
||
|
|
||
|
#ifndef __UAPI_LINUX_USB_G_HID_H
|
||
|
#define __UAPI_LINUX_USB_G_HID_H
|
||
|
|
||
|
#include <linux/types.h>
|
||
|
|
||
|
+#define HIDG_REPORT_SIZE_MAX 64
|
||
|
+
|
||
|
struct usb_hidg_report {
|
||
|
__u16 length;
|
||
|
- __u8 data[512];
|
||
|
+ __u8 data[HIDG_REPORT_SIZE_MAX];
|
||
|
};
|
||
|
|
||
|
/* The 'g' code is also used by gadgetfs and hid gadget ioctl requests.
|
||
|
* Don't add any colliding codes to either driver, and keep
|
||
|
* them in unique ranges (size 0x20 for now).
|
||
|
*/
|
||
|
+#define GADGET_HID_READ_SET_REPORT _IOR('g', 0x41, struct usb_hidg_report)
|
||
|
#define GADGET_HID_WRITE_GET_REPORT _IOW('g', 0x42, struct usb_hidg_report)
|
||
|
|
||
|
#endif /* __UAPI_LINUX_USB_G_HID_H */
|
||
|
--
|
||
|
2.41.0
|
||
|
|
||
|
|
||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Vicki Pfau <vi@endrift.com>
|
||
|
Date: Tue, 29 Nov 2022 18:32:58 -0800
|
||
|
Subject: [PATCH 03/10] HID: hid-steam: Update list of identifiers from SDL
|
||
|
|
||
|
SDL includes a list of settings (registers), reports (cmds), and various other
|
||
|
identifiers that were provided by Valve. This commit imports a significant
|
||
|
chunk of that list as well as updating the guessed names and replacing a
|
||
|
handful of magic constants. It also replaces bitmask definitions that used hex
|
||
|
with the BIT macro.
|
||
|
|
||
|
Signed-off-by: Vicki Pfau <vi@endrift.com>
|
||
|
---
|
||
|
drivers/hid/hid-steam.c | 156 +++++++++++++++++++++++++++++++---------
|
||
|
1 file changed, 121 insertions(+), 35 deletions(-)
|
||
|
|
||
|
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
|
||
|
index b110818fc9458..39a9bf3b7f77d 100644
|
||
|
--- a/drivers/hid/hid-steam.c
|
||
|
+++ b/drivers/hid/hid-steam.c
|
||
|
@@ -71,7 +71,7 @@ static LIST_HEAD(steam_devices);
|
||
|
|
||
|
/*
|
||
|
* Commands that can be sent in a feature report.
|
||
|
- * Thanks to Valve for some valuable hints.
|
||
|
+ * Thanks to Valve and SDL for some valuable hints.
|
||
|
*/
|
||
|
#define STEAM_CMD_SET_MAPPINGS 0x80
|
||
|
#define STEAM_CMD_CLEAR_MAPPINGS 0x81
|
||
|
@@ -80,27 +80,98 @@ static LIST_HEAD(steam_devices);
|
||
|
#define STEAM_CMD_GET_ATTRIB_LABEL 0x84
|
||
|
#define STEAM_CMD_DEFAULT_MAPPINGS 0x85
|
||
|
#define STEAM_CMD_FACTORY_RESET 0x86
|
||
|
-#define STEAM_CMD_WRITE_REGISTER 0x87
|
||
|
+#define STEAM_CMD_SET_REGISTER 0x87
|
||
|
#define STEAM_CMD_CLEAR_REGISTER 0x88
|
||
|
-#define STEAM_CMD_READ_REGISTER 0x89
|
||
|
+#define STEAM_CMD_GET_REGISTER 0x89
|
||
|
#define STEAM_CMD_GET_REGISTER_LABEL 0x8a
|
||
|
#define STEAM_CMD_GET_REGISTER_MAX 0x8b
|
||
|
#define STEAM_CMD_GET_REGISTER_DEFAULT 0x8c
|
||
|
#define STEAM_CMD_SET_MODE 0x8d
|
||
|
-#define STEAM_CMD_DEFAULT_MOUSE 0x8e
|
||
|
-#define STEAM_CMD_FORCEFEEDBAK 0x8f
|
||
|
-#define STEAM_CMD_REQUEST_COMM_STATUS 0xb4
|
||
|
-#define STEAM_CMD_GET_SERIAL 0xae
|
||
|
+#define STEAM_CMD_DEFAULT_REGISTER 0x8e
|
||
|
+#define STEAM_CMD_HAPTIC_PULSE 0x8f
|
||
|
+#define STEAM_CMD_TURN_OFF_CONTROLLER 0x9f
|
||
|
+#define STEAM_CMD_GET_DEVICE_IFNO 0xa1
|
||
|
+#define STEAM_CMD_CALIBRATE_TRACKPADS 0xa7
|
||
|
+#define STEAM_CMD_SET_SERIAL 0xa9
|
||
|
+#define STEAM_CMD_GET_TRACKPAD_CALIB 0xaa
|
||
|
+#define STEAM_CMD_GET_TRACKPAD_FACTORY_CALIB 0xab
|
||
|
+#define STEAM_CMD_GET_TRACKPAD_RAW_DATA 0xac
|
||
|
+#define STEAM_CMD_ENABLE_PAIRING 0xad
|
||
|
+#define STEAM_CMD_GET_STRING_ATTRIB 0xae
|
||
|
+#define STEAM_CMD_RADIO_ERASE_RECORDS 0xaf
|
||
|
+#define STEAM_CMD_RADIO_WRITE_RECORD 0xb0
|
||
|
+#define STEAM_CMD_SET_DONGLE_SETTING 0xb1
|
||
|
+#define STEAM_CMD_DONGLE_DISCONNECT_DEV 0xb2
|
||
|
+#define STEAM_CMD_DONGLE_COMMIT_DEV 0xb3
|
||
|
+#define STEAM_CMD_DONGLE_GET_STATE 0xb4
|
||
|
+#define STEAM_CMD_CALIBRATE_GYRO 0xb5
|
||
|
+#define STEAM_CMD_PLAY_AUDIO 0xb6
|
||
|
+#define STEAM_CMD_AUDIO_UPDATE_START 0xb7
|
||
|
+#define STEAM_CMD_AUDIO_UPDATE_DATA 0xb8
|
||
|
+#define STEAM_CMD_AUDIO_UPDATE_COMPLETE 0xb9
|
||
|
+#define STEAM_CMD_GET_CHIPID 0xba
|
||
|
+#define STEAM_CMD_CALIBRATE_JOYSTICK 0xbf
|
||
|
+#define STEAM_CMD_CALIBRATE_TRIGGERS 0xc0
|
||
|
+#define STEAM_CMD_SET_AUDIO_MAPPING 0xc1
|
||
|
+#define STEAM_CMD_CHECK_GYRO_FW_LOAD 0xc2
|
||
|
+#define STEAM_CMD_CALIBRATE_ANALOG 0xc3
|
||
|
+#define STEAM_CMD_DONGLE_GET_CONN_SLOTS 0xc4
|
||
|
+#define STEAM_CMD_HAPTIC_CMD 0xea
|
||
|
#define STEAM_CMD_HAPTIC_RUMBLE 0xeb
|
||
|
|
||
|
/* Some useful register ids */
|
||
|
-#define STEAM_REG_LPAD_MODE 0x07
|
||
|
-#define STEAM_REG_RPAD_MODE 0x08
|
||
|
-#define STEAM_REG_RPAD_MARGIN 0x18
|
||
|
-#define STEAM_REG_LED 0x2d
|
||
|
-#define STEAM_REG_GYRO_MODE 0x30
|
||
|
-#define STEAM_REG_LPAD_CLICK_PRESSURE 0x34
|
||
|
-#define STEAM_REG_RPAD_CLICK_PRESSURE 0x35
|
||
|
+#define STEAM_REG_MOUSE_SENSITIVITY 0x00
|
||
|
+#define STEAM_REG_MOUSE_ACCELERATION 0x01
|
||
|
+#define STEAM_REG_TRACKBALL_ROTATION_ANGLE 0x02
|
||
|
+#define STEAM_REG_HAPTIC_INTENSITY 0x03
|
||
|
+#define STEAM_REG_LEFT_GAMEPAD_STICK_ENABLED 0x04
|
||
|
+#define STEAM_REG_RIGHT_GAMEPAD_STICK_ENABLED 0x05
|
||
|
+#define STEAM_REG_USB_DEBUG_MODE 0x06
|
||
|
+#define STEAM_REG_LEFT_TRACKPAD_MODE 0x07
|
||
|
+#define STEAM_REG_RIGHT_TRACKPAD_MODE 0x08
|
||
|
+#define STEAM_REG_MOUSE_POINTER_ENABLED 0x09
|
||
|
+#define STEAM_REG_DPAD_DEADZONE 0x0a
|
||
|
+#define STEAM_REG_MINIMUM_MOMENTUM_VEL 0x0b
|
||
|
+#define STEAM_REG_MOMENTUM_DECAY_AMOUNT 0x0c
|
||
|
+#define STEAM_REG_PAD_REL_MODE_TICKS_PER_PIXEL 0x0d
|
||
|
+#define STEAM_REG_HAPTIC_INCREMENT 0x0e
|
||
|
+#define STEAM_REG_DPAD_ANGLE_SIN 0x0f
|
||
|
+#define STEAM_REG_DPAD_ANGLE_COS 0x10
|
||
|
+#define STEAM_REG_MOMENTUM_VERTICAL_DIVISOR 0x11
|
||
|
+#define STEAM_REG_MOMENTUM_MAXIMUM_VELOCITY 0x12
|
||
|
+#define STEAM_REG_TRACKPAD_Z_ON 0x13
|
||
|
+#define STEAM_REG_TRACKPAD_Z_OFF 0x14
|
||
|
+#define STEAM_REG_SENSITIVY_SCALE_AMOUNT 0x15
|
||
|
+#define STEAM_REG_LEFT_TRACKPAD_SECONDARY_MODE 0x16
|
||
|
+#define STEAM_REG_RIGHT_TRACKPAD_SECONDARY_MODE 0x17
|
||
|
+#define STEAM_REG_SMOOTH_ABSOLUTE_MOUSE 0x18
|
||
|
+#define STEAM_REG_STEAMBUTTON_POWEROFF_TIME 0x19
|
||
|
+#define STEAM_REG_TRACKPAD_OUTER_RADIUS 0x1b
|
||
|
+#define STEAM_REG_TRACKPAD_Z_ON_LEFT 0x1c
|
||
|
+#define STEAM_REG_TRACKPAD_Z_OFF_LEFT 0x1d
|
||
|
+#define STEAM_REG_TRACKPAD_OUTER_SPIN_VEL 0x1e
|
||
|
+#define STEAM_REG_TRACKPAD_OUTER_SPIN_RADIUS 0x1f
|
||
|
+#define STEAM_REG_TRACKPAD_OUTER_SPIN_HORIZONTAL_ONLY 0x20
|
||
|
+#define STEAM_REG_TRACKPAD_RELATIVE_MODE_DEADZONE 0x21
|
||
|
+#define STEAM_REG_TRACKPAD_RELATIVE_MODE_MAX_VEL 0x22
|
||
|
+#define STEAM_REG_TRACKPAD_RELATIVE_MODE_INVERT_Y 0x23
|
||
|
+#define STEAM_REG_TRACKPAD_DOUBLE_TAP_BEEP_ENABLED 0x24
|
||
|
+#define STEAM_REG_TRACKPAD_DOUBLE_TAP_BEEP_PERIOD 0x25
|
||
|
+#define STEAM_REG_TRACKPAD_DOUBLE_TAP_BEEP_COUNT 0x26
|
||
|
+#define STEAM_REG_TRACKPAD_OUTER_RADIUS_RELEASE_ON_TRANSITION 0x27
|
||
|
+#define STEAM_REG_RADIAL_MODE_ANGLE 0x28
|
||
|
+#define STEAM_REG_HAPTIC_INTENSITY_MOUSE_MODE 0x29
|
||
|
+#define STEAM_REG_LEFT_DPAD_REQUIRES_CLICK 0x2a
|
||
|
+#define STEAM_REG_RIGHT_DPAD_REQUIRES_CLICK 0x2b
|
||
|
+#define STEAM_REG_LED_BASELINE_BRIGHTNESS 0x2c
|
||
|
+#define STEAM_REG_LED_USER_BRIGHTNESS 0x2d
|
||
|
+#define STEAM_REG_ENABLE_RAW_JOYSTICK 0x2e
|
||
|
+#define STEAM_REG_ENABLE_FAST_SCAN 0x2f
|
||
|
+#define STEAM_REG_GYRO_MODE 0x30
|
||
|
+#define STEAM_REG_WIRELESS_PACKET_VERSION 0x31
|
||
|
+#define STEAM_REG_SLEEP_INACTIVITY_TIMEOUT 0x32
|
||
|
+#define STEAM_REG_LEFT_TRACKPAD_CLICK_PRESSURE 0x34
|
||
|
+#define STEAM_REG_RIGHT_TRACKPAD_CLICK_PRESSURE 0x35
|
||
|
|
||
|
/* Raw event identifiers */
|
||
|
#define STEAM_EV_INPUT_DATA 0x01
|
||
|
@@ -108,13 +179,28 @@ static LIST_HEAD(steam_devices);
|
||
|
#define STEAM_EV_BATTERY 0x04
|
||
|
#define STEAM_EV_DECK_INPUT_DATA 0x09
|
||
|
|
||
|
+/* String attribute idenitifiers */
|
||
|
+#define STEAM_ATTRIB_STR_BOARD_SERIAL 0x00
|
||
|
+#define STEAM_ATTRIB_STR_UNIT_SERIAL 0x01
|
||
|
+
|
||
|
/* Values for GYRO_MODE (bitmask) */
|
||
|
-#define STEAM_GYRO_MODE_OFF 0x0000
|
||
|
-#define STEAM_GYRO_MODE_STEERING 0x0001
|
||
|
-#define STEAM_GYRO_MODE_TILT 0x0002
|
||
|
-#define STEAM_GYRO_MODE_SEND_ORIENTATION 0x0004
|
||
|
-#define STEAM_GYRO_MODE_SEND_RAW_ACCEL 0x0008
|
||
|
-#define STEAM_GYRO_MODE_SEND_RAW_GYRO 0x0010
|
||
|
+#define STEAM_GYRO_MODE_OFF 0
|
||
|
+#define STEAM_GYRO_MODE_STEERING BIT(0)
|
||
|
+#define STEAM_GYRO_MODE_TILT BIT(1)
|
||
|
+#define STEAM_GYRO_MODE_SEND_ORIENTATION BIT(2)
|
||
|
+#define STEAM_GYRO_MODE_SEND_RAW_ACCEL BIT(3)
|
||
|
+#define STEAM_GYRO_MODE_SEND_RAW_GYRO BIT(4)
|
||
|
+
|
||
|
+/* Trackpad modes */
|
||
|
+#define STEAM_TRACKPAD_ABSOLUTE_MOUSE 0x00
|
||
|
+#define STEAM_TRACKPAD_RELATIVE_MOUSE 0x01
|
||
|
+#define STEAM_TRACKPAD_DPAD_FOUR_WAY_DISCRETE 0x02
|
||
|
+#define STEAM_TRACKPAD_DPAD_FOUR_WAY_OVERLAP 0x03
|
||
|
+#define STEAM_TRACKPAD_DPAD_EIGHT_WAY 0x04
|
||
|
+#define STEAM_TRACKPAD_RADIAL_MODE 0x05
|
||
|
+#define STEAM_TRACKPAD_ABSOLUTE_DPAD 0x06
|
||
|
+#define STEAM_TRACKPAD_NONE 0x07
|
||
|
+#define STEAM_TRACKPAD_GESTURE_KEYBOARD 0x08
|
||
|
|
||
|
/* Other random constants */
|
||
|
#define STEAM_SERIAL_LEN 10
|
||
|
@@ -232,7 +318,7 @@ static int steam_write_registers(struct steam_device *steam,
|
||
|
/* Send: 0x87 len (reg valLo valHi)* */
|
||
|
u8 reg;
|
||
|
u16 val;
|
||
|
- u8 cmd[64] = {STEAM_CMD_WRITE_REGISTER, 0x00};
|
||
|
+ u8 cmd[64] = {STEAM_CMD_SET_REGISTER, 0x00};
|
||
|
int ret;
|
||
|
va_list args;
|
||
|
|
||
|
@@ -268,7 +354,7 @@ static int steam_get_serial(struct steam_device *steam)
|
||
|
* Recv: 0xae 0x15 0x01 serialnumber (10 chars)
|
||
|
*/
|
||
|
int ret;
|
||
|
- u8 cmd[] = {STEAM_CMD_GET_SERIAL, 0x15, 0x01};
|
||
|
+ u8 cmd[] = {STEAM_CMD_GET_STRING_ATTRIB, 0x15, STEAM_ATTRIB_STR_UNIT_SERIAL};
|
||
|
u8 reply[3 + STEAM_SERIAL_LEN + 1];
|
||
|
|
||
|
ret = steam_send_report(steam, cmd, sizeof(cmd));
|
||
|
@@ -277,7 +363,7 @@ static int steam_get_serial(struct steam_device *steam)
|
||
|
ret = steam_recv_report(steam, reply, sizeof(reply));
|
||
|
if (ret < 0)
|
||
|
return ret;
|
||
|
- if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != 0x01)
|
||
|
+ if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL)
|
||
|
return -EIO;
|
||
|
reply[3 + STEAM_SERIAL_LEN] = 0;
|
||
|
strscpy(steam->serial_no, reply + 3, sizeof(steam->serial_no));
|
||
|
@@ -291,7 +377,7 @@ static int steam_get_serial(struct steam_device *steam)
|
||
|
*/
|
||
|
static inline int steam_request_conn_status(struct steam_device *steam)
|
||
|
{
|
||
|
- return steam_send_report_byte(steam, STEAM_CMD_REQUEST_COMM_STATUS);
|
||
|
+ return steam_send_report_byte(steam, STEAM_CMD_DONGLE_GET_STATE);
|
||
|
}
|
||
|
|
||
|
static inline int steam_haptic_rumble(struct steam_device *steam,
|
||
|
@@ -339,9 +425,9 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
|
||
|
/* enable esc, enter, cursors */
|
||
|
steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MAPPINGS);
|
||
|
/* enable mouse */
|
||
|
- steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MOUSE);
|
||
|
+ steam_send_report_byte(steam, STEAM_CMD_DEFAULT_REGISTER);
|
||
|
steam_write_registers(steam,
|
||
|
- STEAM_REG_RPAD_MARGIN, 0x01, /* enable margin */
|
||
|
+ STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x01, /* enable smooth */
|
||
|
0);
|
||
|
|
||
|
cancel_delayed_work_sync(&steam->heartbeat);
|
||
|
@@ -351,11 +437,11 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
|
||
|
|
||
|
if (steam->quirks & STEAM_QUIRK_DECK) {
|
||
|
steam_write_registers(steam,
|
||
|
- STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
|
||
|
- STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */
|
||
|
- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
|
||
|
- STEAM_REG_LPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
|
||
|
- STEAM_REG_RPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
|
||
|
+ STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x00, /* disable smooth */
|
||
|
+ STEAM_REG_LEFT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
|
||
|
+ STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
|
||
|
+ STEAM_REG_LEFT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
|
||
|
+ STEAM_REG_RIGHT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
|
||
|
0);
|
||
|
/*
|
||
|
* The Steam Deck has a watchdog that automatically enables
|
||
|
@@ -365,9 +451,9 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
|
||
|
schedule_delayed_work(&steam->heartbeat, 5 * HZ);
|
||
|
} else {
|
||
|
steam_write_registers(steam,
|
||
|
- STEAM_REG_RPAD_MARGIN, 0x00, /* disable margin */
|
||
|
- STEAM_REG_LPAD_MODE, 0x07, /* disable mouse */
|
||
|
- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
|
||
|
+ STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x00, /* disable smooth */
|
||
|
+ STEAM_REG_LEFT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
|
||
|
+ STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
|
||
|
0);
|
||
|
}
|
||
|
}
|
||
|
@@ -747,7 +833,7 @@ static void steam_lizard_mode_heartbeat(struct work_struct *work)
|
||
|
if (!steam->client_opened && steam->client_hdev) {
|
||
|
steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
|
||
|
steam_write_registers(steam,
|
||
|
- STEAM_REG_RPAD_MODE, 0x07, /* disable mouse */
|
||
|
+ STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
|
||
|
0);
|
||
|
schedule_delayed_work(&steam->heartbeat, 5 * HZ);
|
||
|
}
|
||
|
--
|
||
|
2.41.0
|
||
|
|
||
|
|
||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Vicki Pfau <vi@endrift.com>
|
||
|
Date: Wed, 16 Nov 2022 19:54:26 -0800
|
||
|
Subject: [PATCH 04/10] HID: hid-steam: Add gamepad-only mode switched to by
|
||
|
holding options
|
||
|
|
||
|
Signed-off-by: Vicki Pfau <vi@endrift.com>
|
||
|
---
|
||
|
drivers/hid/hid-steam.c | 72 +++++++++++++++++++++++++++++++++++++++++
|
||
|
1 file changed, 72 insertions(+)
|
||
|
|
||
|
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
|
||
|
index 39a9bf3b7f77d..0620046b142ef 100644
|
||
|
--- a/drivers/hid/hid-steam.c
|
||
|
+++ b/drivers/hid/hid-steam.c
|
||
|
@@ -202,6 +202,11 @@ static LIST_HEAD(steam_devices);
|
||
|
#define STEAM_TRACKPAD_NONE 0x07
|
||
|
#define STEAM_TRACKPAD_GESTURE_KEYBOARD 0x08
|
||
|
|
||
|
+/* Pad identifiers for the deck */
|
||
|
+#define STEAM_PAD_LEFT 0
|
||
|
+#define STEAM_PAD_RIGHT 1
|
||
|
+#define STEAM_PAD_BOTH 2
|
||
|
+
|
||
|
/* Other random constants */
|
||
|
#define STEAM_SERIAL_LEN 10
|
||
|
|
||
|
@@ -221,6 +226,9 @@ struct steam_device {
|
||
|
u8 battery_charge;
|
||
|
u16 voltage;
|
||
|
struct delayed_work heartbeat;
|
||
|
+ struct delayed_work mode_switch;
|
||
|
+ bool did_mode_switch;
|
||
|
+ bool gamepad_mode;
|
||
|
struct work_struct rumble_work;
|
||
|
u16 rumble_left;
|
||
|
u16 rumble_right;
|
||
|
@@ -380,6 +388,33 @@ static inline int steam_request_conn_status(struct steam_device *steam)
|
||
|
return steam_send_report_byte(steam, STEAM_CMD_DONGLE_GET_STATE);
|
||
|
}
|
||
|
|
||
|
+/*
|
||
|
+ * Send a haptic pulse to the trackpads
|
||
|
+ * Duration and interval are measured in microseconds, count is the number
|
||
|
+ * of pulses to send for duration time with interval microseconds between them
|
||
|
+ * and gain is measured in decibels, ranging from -24 to +6
|
||
|
+ */
|
||
|
+static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad,
|
||
|
+ u16 duration, u16 interval, u16 count, u8 gain)
|
||
|
+{
|
||
|
+ u8 report[10] = {STEAM_CMD_HAPTIC_PULSE, 8};
|
||
|
+
|
||
|
+ /* Left and right are swapped on this report for legacy reasons */
|
||
|
+ if (pad < STEAM_PAD_BOTH)
|
||
|
+ pad ^= 1;
|
||
|
+
|
||
|
+ report[2] = pad;
|
||
|
+ report[3] = duration & 0xFF;
|
||
|
+ report[4] = duration >> 8;
|
||
|
+ report[5] = interval & 0xFF;
|
||
|
+ report[6] = interval >> 8;
|
||
|
+ report[7] = count & 0xFF;
|
||
|
+ report[8] = count >> 8;
|
||
|
+ report[9] = gain;
|
||
|
+
|
||
|
+ return steam_send_report(steam, report, sizeof(report));
|
||
|
+}
|
||
|
+
|
||
|
static inline int steam_haptic_rumble(struct steam_device *steam,
|
||
|
u16 intensity, u16 left_speed, u16 right_speed,
|
||
|
u8 left_gain, u8 right_gain)
|
||
|
@@ -421,6 +456,9 @@ static int steam_play_effect(struct input_dev *dev, void *data,
|
||
|
|
||
|
static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
|
||
|
{
|
||
|
+ if (steam->gamepad_mode)
|
||
|
+ enable = false;
|
||
|
+
|
||
|
if (enable) {
|
||
|
/* enable esc, enter, cursors */
|
||
|
steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MAPPINGS);
|
||
|
@@ -805,6 +843,29 @@ static void steam_work_connect_cb(struct work_struct *work)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
+static void steam_mode_switch_cb(struct work_struct *work)
|
||
|
+{
|
||
|
+ struct steam_device *steam = container_of(to_delayed_work(work),
|
||
|
+ struct steam_device, mode_switch);
|
||
|
+ steam->gamepad_mode = !steam->gamepad_mode;
|
||
|
+ if (!lizard_mode)
|
||
|
+ return;
|
||
|
+
|
||
|
+ mutex_lock(&steam->mutex);
|
||
|
+ if (steam->gamepad_mode)
|
||
|
+ steam_set_lizard_mode(steam, false);
|
||
|
+ else if (!steam->client_opened)
|
||
|
+ steam_set_lizard_mode(steam, lizard_mode);
|
||
|
+ mutex_unlock(&steam->mutex);
|
||
|
+
|
||
|
+ steam_haptic_pulse(steam, STEAM_PAD_RIGHT, 0x190, 0, 1, 0);
|
||
|
+ if (steam->gamepad_mode) {
|
||
|
+ steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x14D, 0x14D, 0x2D, 0);
|
||
|
+ } else {
|
||
|
+ steam_haptic_pulse(steam, STEAM_PAD_LEFT, 0x1F4, 0x1F4, 0x1E, 0);
|
||
|
+ }
|
||
|
+}
|
||
|
+
|
||
|
static bool steam_is_valve_interface(struct hid_device *hdev)
|
||
|
{
|
||
|
struct hid_report_enum *rep_enum;
|
||
|
@@ -977,6 +1038,7 @@ static int steam_probe(struct hid_device *hdev,
|
||
|
mutex_init(&steam->mutex);
|
||
|
steam->quirks = id->driver_data;
|
||
|
INIT_WORK(&steam->work_connect, steam_work_connect_cb);
|
||
|
+ INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb);
|
||
|
INIT_LIST_HEAD(&steam->list);
|
||
|
INIT_DEFERRABLE_WORK(&steam->heartbeat, steam_lizard_mode_heartbeat);
|
||
|
INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb);
|
||
|
@@ -1036,6 +1098,7 @@ static int steam_probe(struct hid_device *hdev,
|
||
|
client_hdev_fail:
|
||
|
cancel_work_sync(&steam->work_connect);
|
||
|
cancel_delayed_work_sync(&steam->heartbeat);
|
||
|
+ cancel_delayed_work_sync(&steam->mode_switch);
|
||
|
cancel_work_sync(&steam->rumble_work);
|
||
|
steam_alloc_fail:
|
||
|
hid_err(hdev, "%s: failed with error %d\n",
|
||
|
@@ -1059,6 +1122,7 @@ static void steam_remove(struct hid_device *hdev)
|
||
|
cancel_delayed_work_sync(&steam->heartbeat);
|
||
|
mutex_unlock(&steam->mutex);
|
||
|
cancel_work_sync(&steam->work_connect);
|
||
|
+ cancel_delayed_work_sync(&steam->mode_switch);
|
||
|
if (steam->quirks & STEAM_QUIRK_WIRELESS) {
|
||
|
hid_info(hdev, "Steam wireless receiver disconnected");
|
||
|
}
|
||
|
@@ -1393,6 +1457,14 @@ static void steam_do_deck_input_event(struct steam_device *steam,
|
||
|
input_event(input, EV_KEY, BTN_BASE, !!(b14 & BIT(2)));
|
||
|
|
||
|
input_sync(input);
|
||
|
+
|
||
|
+ if (!(b9 & BIT(6)) && steam->did_mode_switch) {
|
||
|
+ steam->did_mode_switch = false;
|
||
|
+ cancel_delayed_work_sync(&steam->mode_switch);
|
||
|
+ } else if (!steam->client_opened && (b9 & BIT(6)) && !steam->did_mode_switch) {
|
||
|
+ steam->did_mode_switch = true;
|
||
|
+ schedule_delayed_work(&steam->mode_switch, 45 * HZ / 100);
|
||
|
+ }
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
--
|
||
|
2.41.0
|
||
|
|
||
|
|
||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Vicki Pfau <vi@endrift.com>
|
||
|
Date: Mon, 8 May 2023 20:24:56 -0700
|
||
|
Subject: [PATCH 05/10] HID: hid-steam: Clean up locking
|
||
|
|
||
|
This cleans up the locking logic so that the spinlock is consistently used for
|
||
|
access to a small handful of struct variables, and the mutex is exclusively and
|
||
|
consistently used for ensuring that mutliple threads aren't trying to
|
||
|
send/receive reports at the same time. Previously, only some report
|
||
|
transactions were guarded by this mutex, potentially breaking atomicity. The
|
||
|
mutex has been renamed to reflect this usage.
|
||
|
|
||
|
Signed-off-by: Vicki Pfau <vi@endrift.com>
|
||
|
---
|
||
|
drivers/hid/hid-steam.c | 148 ++++++++++++++++++++++++----------------
|
||
|
1 file changed, 90 insertions(+), 58 deletions(-)
|
||
|
|
||
|
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
|
||
|
index 0620046b142ef..845ca71b8bd3a 100644
|
||
|
--- a/drivers/hid/hid-steam.c
|
||
|
+++ b/drivers/hid/hid-steam.c
|
||
|
@@ -214,7 +214,7 @@ struct steam_device {
|
||
|
struct list_head list;
|
||
|
spinlock_t lock;
|
||
|
struct hid_device *hdev, *client_hdev;
|
||
|
- struct mutex mutex;
|
||
|
+ struct mutex report_mutex;
|
||
|
bool client_opened;
|
||
|
struct input_dev __rcu *input;
|
||
|
unsigned long quirks;
|
||
|
@@ -361,21 +361,26 @@ static int steam_get_serial(struct steam_device *steam)
|
||
|
* Send: 0xae 0x15 0x01
|
||
|
* Recv: 0xae 0x15 0x01 serialnumber (10 chars)
|
||
|
*/
|
||
|
- int ret;
|
||
|
+ int ret = 0;
|
||
|
u8 cmd[] = {STEAM_CMD_GET_STRING_ATTRIB, 0x15, STEAM_ATTRIB_STR_UNIT_SERIAL};
|
||
|
u8 reply[3 + STEAM_SERIAL_LEN + 1];
|
||
|
|
||
|
+ mutex_lock(&steam->report_mutex);
|
||
|
ret = steam_send_report(steam, cmd, sizeof(cmd));
|
||
|
if (ret < 0)
|
||
|
- return ret;
|
||
|
+ goto out;
|
||
|
ret = steam_recv_report(steam, reply, sizeof(reply));
|
||
|
if (ret < 0)
|
||
|
- return ret;
|
||
|
- if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL)
|
||
|
- return -EIO;
|
||
|
+ goto out;
|
||
|
+ if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL) {
|
||
|
+ ret = -EIO;
|
||
|
+ goto out;
|
||
|
+ }
|
||
|
reply[3 + STEAM_SERIAL_LEN] = 0;
|
||
|
strscpy(steam->serial_no, reply + 3, sizeof(steam->serial_no));
|
||
|
- return 0;
|
||
|
+out:
|
||
|
+ mutex_unlock(&steam->report_mutex);
|
||
|
+ return ret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
@@ -385,7 +390,11 @@ static int steam_get_serial(struct steam_device *steam)
|
||
|
*/
|
||
|
static inline int steam_request_conn_status(struct steam_device *steam)
|
||
|
{
|
||
|
- return steam_send_report_byte(steam, STEAM_CMD_DONGLE_GET_STATE);
|
||
|
+ int ret;
|
||
|
+ mutex_lock(&steam->report_mutex);
|
||
|
+ ret = steam_send_report_byte(steam, STEAM_CMD_DONGLE_GET_STATE);
|
||
|
+ mutex_unlock(&steam->report_mutex);
|
||
|
+ return ret;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
@@ -397,6 +406,7 @@ static inline int steam_request_conn_status(struct steam_device *steam)
|
||
|
static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad,
|
||
|
u16 duration, u16 interval, u16 count, u8 gain)
|
||
|
{
|
||
|
+ int ret;
|
||
|
u8 report[10] = {STEAM_CMD_HAPTIC_PULSE, 8};
|
||
|
|
||
|
/* Left and right are swapped on this report for legacy reasons */
|
||
|
@@ -412,13 +422,17 @@ static inline int steam_haptic_pulse(struct steam_device *steam, u8 pad,
|
||
|
report[8] = count >> 8;
|
||
|
report[9] = gain;
|
||
|
|
||
|
- return steam_send_report(steam, report, sizeof(report));
|
||
|
+ mutex_lock(&steam->report_mutex);
|
||
|
+ ret = steam_send_report(steam, report, sizeof(report));
|
||
|
+ mutex_unlock(&steam->report_mutex);
|
||
|
+ return ret;
|
||
|
}
|
||
|
|
||
|
static inline int steam_haptic_rumble(struct steam_device *steam,
|
||
|
u16 intensity, u16 left_speed, u16 right_speed,
|
||
|
u8 left_gain, u8 right_gain)
|
||
|
{
|
||
|
+ int ret;
|
||
|
u8 report[11] = {STEAM_CMD_HAPTIC_RUMBLE, 9};
|
||
|
|
||
|
report[3] = intensity & 0xFF;
|
||
|
@@ -430,7 +444,10 @@ static inline int steam_haptic_rumble(struct steam_device *steam,
|
||
|
report[9] = left_gain;
|
||
|
report[10] = right_gain;
|
||
|
|
||
|
- return steam_send_report(steam, report, sizeof(report));
|
||
|
+ mutex_lock(&steam->report_mutex);
|
||
|
+ ret = steam_send_report(steam, report, sizeof(report));
|
||
|
+ mutex_unlock(&steam->report_mutex);
|
||
|
+ return ret;
|
||
|
}
|
||
|
|
||
|
static void steam_haptic_rumble_cb(struct work_struct *work)
|
||
|
@@ -460,6 +477,7 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
|
||
|
enable = false;
|
||
|
|
||
|
if (enable) {
|
||
|
+ mutex_lock(&steam->report_mutex);
|
||
|
/* enable esc, enter, cursors */
|
||
|
steam_send_report_byte(steam, STEAM_CMD_DEFAULT_MAPPINGS);
|
||
|
/* enable mouse */
|
||
|
@@ -467,9 +485,11 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
|
||
|
steam_write_registers(steam,
|
||
|
STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x01, /* enable smooth */
|
||
|
0);
|
||
|
+ mutex_unlock(&steam->report_mutex);
|
||
|
|
||
|
cancel_delayed_work_sync(&steam->heartbeat);
|
||
|
} else {
|
||
|
+ mutex_lock(&steam->report_mutex);
|
||
|
/* disable esc, enter, cursor */
|
||
|
steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
|
||
|
|
||
|
@@ -481,18 +501,19 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
|
||
|
STEAM_REG_LEFT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
|
||
|
STEAM_REG_RIGHT_TRACKPAD_CLICK_PRESSURE, 0xFFFF, /* disable clicky pad */
|
||
|
0);
|
||
|
+ mutex_unlock(&steam->report_mutex);
|
||
|
/*
|
||
|
* The Steam Deck has a watchdog that automatically enables
|
||
|
* lizard mode if it doesn't see any traffic for too long
|
||
|
*/
|
||
|
- if (!work_busy(&steam->heartbeat.work))
|
||
|
- schedule_delayed_work(&steam->heartbeat, 5 * HZ);
|
||
|
+ schedule_delayed_work(&steam->heartbeat, 5 * HZ);
|
||
|
} else {
|
||
|
steam_write_registers(steam,
|
||
|
STEAM_REG_SMOOTH_ABSOLUTE_MOUSE, 0x00, /* disable smooth */
|
||
|
STEAM_REG_LEFT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
|
||
|
STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
|
||
|
0);
|
||
|
+ mutex_unlock(&steam->report_mutex);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
@@ -500,22 +521,29 @@ static void steam_set_lizard_mode(struct steam_device *steam, bool enable)
|
||
|
static int steam_input_open(struct input_dev *dev)
|
||
|
{
|
||
|
struct steam_device *steam = input_get_drvdata(dev);
|
||
|
+ unsigned long flags;
|
||
|
+ bool set_lizard_mode;
|
||
|
|
||
|
- mutex_lock(&steam->mutex);
|
||
|
- if (!steam->client_opened && lizard_mode)
|
||
|
+ spin_lock_irqsave(&steam->lock, flags);
|
||
|
+ set_lizard_mode = !steam->client_opened && lizard_mode;
|
||
|
+ spin_unlock_irqrestore(&steam->lock, flags);
|
||
|
+ if (set_lizard_mode)
|
||
|
steam_set_lizard_mode(steam, false);
|
||
|
- mutex_unlock(&steam->mutex);
|
||
|
+
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void steam_input_close(struct input_dev *dev)
|
||
|
{
|
||
|
struct steam_device *steam = input_get_drvdata(dev);
|
||
|
+ unsigned long flags;
|
||
|
+ bool set_lizard_mode;
|
||
|
|
||
|
- mutex_lock(&steam->mutex);
|
||
|
- if (!steam->client_opened && lizard_mode)
|
||
|
+ spin_lock_irqsave(&steam->lock, flags);
|
||
|
+ set_lizard_mode = !steam->client_opened && lizard_mode;
|
||
|
+ spin_unlock_irqrestore(&steam->lock, flags);
|
||
|
+ if (set_lizard_mode)
|
||
|
steam_set_lizard_mode(steam, true);
|
||
|
- mutex_unlock(&steam->mutex);
|
||
|
}
|
||
|
|
||
|
static enum power_supply_property steam_battery_props[] = {
|
||
|
@@ -760,6 +788,7 @@ static int steam_register(struct steam_device *steam)
|
||
|
{
|
||
|
int ret;
|
||
|
bool client_opened;
|
||
|
+ unsigned long flags;
|
||
|
|
||
|
/*
|
||
|
* This function can be called several times in a row with the
|
||
|
@@ -772,11 +801,9 @@ static int steam_register(struct steam_device *steam)
|
||
|
* Unlikely, but getting the serial could fail, and it is not so
|
||
|
* important, so make up a serial number and go on.
|
||
|
*/
|
||
|
- mutex_lock(&steam->mutex);
|
||
|
if (steam_get_serial(steam) < 0)
|
||
|
strscpy(steam->serial_no, "XXXXXXXXXX",
|
||
|
sizeof(steam->serial_no));
|
||
|
- mutex_unlock(&steam->mutex);
|
||
|
|
||
|
hid_info(steam->hdev, "Steam Controller '%s' connected",
|
||
|
steam->serial_no);
|
||
|
@@ -791,11 +818,11 @@ static int steam_register(struct steam_device *steam)
|
||
|
mutex_unlock(&steam_devices_lock);
|
||
|
}
|
||
|
|
||
|
- mutex_lock(&steam->mutex);
|
||
|
+ spin_lock_irqsave(&steam->lock, flags);
|
||
|
client_opened = steam->client_opened;
|
||
|
+ spin_unlock_irqrestore(&steam->lock, flags);
|
||
|
if (!client_opened)
|
||
|
steam_set_lizard_mode(steam, lizard_mode);
|
||
|
- mutex_unlock(&steam->mutex);
|
||
|
|
||
|
if (!client_opened)
|
||
|
ret = steam_input_register(steam);
|
||
|
@@ -847,16 +874,21 @@ static void steam_mode_switch_cb(struct work_struct *work)
|
||
|
{
|
||
|
struct steam_device *steam = container_of(to_delayed_work(work),
|
||
|
struct steam_device, mode_switch);
|
||
|
+ unsigned long flags;
|
||
|
+ bool client_opened;
|
||
|
steam->gamepad_mode = !steam->gamepad_mode;
|
||
|
if (!lizard_mode)
|
||
|
return;
|
||
|
|
||
|
- mutex_lock(&steam->mutex);
|
||
|
if (steam->gamepad_mode)
|
||
|
steam_set_lizard_mode(steam, false);
|
||
|
- else if (!steam->client_opened)
|
||
|
- steam_set_lizard_mode(steam, lizard_mode);
|
||
|
- mutex_unlock(&steam->mutex);
|
||
|
+ else {
|
||
|
+ spin_lock_irqsave(&steam->lock, flags);
|
||
|
+ client_opened = steam->client_opened;
|
||
|
+ spin_unlock_irqrestore(&steam->lock, flags);
|
||
|
+ if (!client_opened)
|
||
|
+ steam_set_lizard_mode(steam, lizard_mode);
|
||
|
+ }
|
||
|
|
||
|
steam_haptic_pulse(steam, STEAM_PAD_RIGHT, 0x190, 0, 1, 0);
|
||
|
if (steam->gamepad_mode) {
|
||
|
@@ -889,16 +921,21 @@ static void steam_lizard_mode_heartbeat(struct work_struct *work)
|
||
|
{
|
||
|
struct steam_device *steam = container_of(work, struct steam_device,
|
||
|
heartbeat.work);
|
||
|
+ bool client_opened;
|
||
|
+ unsigned long flags;
|
||
|
|
||
|
- mutex_lock(&steam->mutex);
|
||
|
- if (!steam->client_opened && steam->client_hdev) {
|
||
|
+ spin_lock_irqsave(&steam->lock, flags);
|
||
|
+ client_opened = steam->client_opened;
|
||
|
+ spin_unlock_irqrestore(&steam->lock, flags);
|
||
|
+ if (!client_opened) {
|
||
|
+ mutex_lock(&steam->report_mutex);
|
||
|
steam_send_report_byte(steam, STEAM_CMD_CLEAR_MAPPINGS);
|
||
|
steam_write_registers(steam,
|
||
|
STEAM_REG_RIGHT_TRACKPAD_MODE, STEAM_TRACKPAD_NONE, /* disable mouse */
|
||
|
0);
|
||
|
+ mutex_unlock(&steam->report_mutex);
|
||
|
schedule_delayed_work(&steam->heartbeat, 5 * HZ);
|
||
|
}
|
||
|
- mutex_unlock(&steam->mutex);
|
||
|
}
|
||
|
|
||
|
static int steam_client_ll_parse(struct hid_device *hdev)
|
||
|
@@ -921,10 +958,11 @@ static void steam_client_ll_stop(struct hid_device *hdev)
|
||
|
static int steam_client_ll_open(struct hid_device *hdev)
|
||
|
{
|
||
|
struct steam_device *steam = hdev->driver_data;
|
||
|
+ unsigned long flags;
|
||
|
|
||
|
- mutex_lock(&steam->mutex);
|
||
|
+ spin_lock_irqsave(&steam->lock, flags);
|
||
|
steam->client_opened = true;
|
||
|
- mutex_unlock(&steam->mutex);
|
||
|
+ spin_unlock_irqrestore(&steam->lock, flags);
|
||
|
|
||
|
steam_input_unregister(steam);
|
||
|
|
||
|
@@ -939,14 +977,12 @@ static void steam_client_ll_close(struct hid_device *hdev)
|
||
|
bool connected;
|
||
|
|
||
|
spin_lock_irqsave(&steam->lock, flags);
|
||
|
- connected = steam->connected;
|
||
|
+ steam->client_opened = false;
|
||
|
+ connected = steam->connected && !steam->client_opened;
|
||
|
spin_unlock_irqrestore(&steam->lock, flags);
|
||
|
|
||
|
- mutex_lock(&steam->mutex);
|
||
|
- steam->client_opened = false;
|
||
|
if (connected)
|
||
|
steam_set_lizard_mode(steam, lizard_mode);
|
||
|
- mutex_unlock(&steam->mutex);
|
||
|
|
||
|
if (connected)
|
||
|
steam_input_register(steam);
|
||
|
@@ -1035,7 +1071,7 @@ static int steam_probe(struct hid_device *hdev,
|
||
|
steam->hdev = hdev;
|
||
|
hid_set_drvdata(hdev, steam);
|
||
|
spin_lock_init(&steam->lock);
|
||
|
- mutex_init(&steam->mutex);
|
||
|
+ mutex_init(&steam->report_mutex);
|
||
|
steam->quirks = id->driver_data;
|
||
|
INIT_WORK(&steam->work_connect, steam_work_connect_cb);
|
||
|
INIT_DELAYED_WORK(&steam->mode_switch, steam_mode_switch_cb);
|
||
|
@@ -1043,13 +1079,6 @@ static int steam_probe(struct hid_device *hdev,
|
||
|
INIT_DEFERRABLE_WORK(&steam->heartbeat, steam_lizard_mode_heartbeat);
|
||
|
INIT_WORK(&steam->rumble_work, steam_haptic_rumble_cb);
|
||
|
|
||
|
- steam->client_hdev = steam_create_client_hid(hdev);
|
||
|
- if (IS_ERR(steam->client_hdev)) {
|
||
|
- ret = PTR_ERR(steam->client_hdev);
|
||
|
- goto client_hdev_fail;
|
||
|
- }
|
||
|
- steam->client_hdev->driver_data = steam;
|
||
|
-
|
||
|
/*
|
||
|
* With the real steam controller interface, do not connect hidraw.
|
||
|
* Instead, create the client_hid and connect that.
|
||
|
@@ -1058,10 +1087,6 @@ static int steam_probe(struct hid_device *hdev,
|
||
|
if (ret)
|
||
|
goto hid_hw_start_fail;
|
||
|
|
||
|
- ret = hid_add_device(steam->client_hdev);
|
||
|
- if (ret)
|
||
|
- goto client_hdev_add_fail;
|
||
|
-
|
||
|
ret = hid_hw_open(hdev);
|
||
|
if (ret) {
|
||
|
hid_err(hdev,
|
||
|
@@ -1087,15 +1112,26 @@ static int steam_probe(struct hid_device *hdev,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
+ steam->client_hdev = steam_create_client_hid(hdev);
|
||
|
+ if (IS_ERR(steam->client_hdev)) {
|
||
|
+ ret = PTR_ERR(steam->client_hdev);
|
||
|
+ goto client_hdev_fail;
|
||
|
+ }
|
||
|
+ steam->client_hdev->driver_data = steam;
|
||
|
+
|
||
|
+ ret = hid_add_device(steam->client_hdev);
|
||
|
+ if (ret)
|
||
|
+ goto client_hdev_add_fail;
|
||
|
+
|
||
|
return 0;
|
||
|
|
||
|
-input_register_fail:
|
||
|
-hid_hw_open_fail:
|
||
|
client_hdev_add_fail:
|
||
|
hid_hw_stop(hdev);
|
||
|
-hid_hw_start_fail:
|
||
|
- hid_destroy_device(steam->client_hdev);
|
||
|
client_hdev_fail:
|
||
|
+ hid_destroy_device(steam->client_hdev);
|
||
|
+input_register_fail:
|
||
|
+hid_hw_open_fail:
|
||
|
+hid_hw_start_fail:
|
||
|
cancel_work_sync(&steam->work_connect);
|
||
|
cancel_delayed_work_sync(&steam->heartbeat);
|
||
|
cancel_delayed_work_sync(&steam->mode_switch);
|
||
|
@@ -1115,14 +1151,12 @@ static void steam_remove(struct hid_device *hdev)
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
+ cancel_delayed_work_sync(&steam->heartbeat);
|
||
|
+ cancel_delayed_work_sync(&steam->mode_switch);
|
||
|
+ cancel_work_sync(&steam->work_connect);
|
||
|
hid_destroy_device(steam->client_hdev);
|
||
|
- mutex_lock(&steam->mutex);
|
||
|
steam->client_hdev = NULL;
|
||
|
steam->client_opened = false;
|
||
|
- cancel_delayed_work_sync(&steam->heartbeat);
|
||
|
- mutex_unlock(&steam->mutex);
|
||
|
- cancel_work_sync(&steam->work_connect);
|
||
|
- cancel_delayed_work_sync(&steam->mode_switch);
|
||
|
if (steam->quirks & STEAM_QUIRK_WIRELESS) {
|
||
|
hid_info(hdev, "Steam wireless receiver disconnected");
|
||
|
}
|
||
|
@@ -1597,10 +1631,8 @@ static int steam_param_set_lizard_mode(const char *val,
|
||
|
|
||
|
mutex_lock(&steam_devices_lock);
|
||
|
list_for_each_entry(steam, &steam_devices, list) {
|
||
|
- mutex_lock(&steam->mutex);
|
||
|
if (!steam->client_opened)
|
||
|
steam_set_lizard_mode(steam, lizard_mode);
|
||
|
- mutex_unlock(&steam->mutex);
|
||
|
}
|
||
|
mutex_unlock(&steam_devices_lock);
|
||
|
return 0;
|
||
|
--
|
||
|
2.41.0
|
||
|
|
||
|
|
||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Vicki Pfau <vi@endrift.com>
|
||
|
Date: Wed, 10 May 2023 17:27:12 -0700
|
||
|
Subject: [PATCH 06/10] HID: hid-steam: Make client_opened a counter
|
||
|
|
||
|
The client_opened variable was used to track if the hidraw was opened by any
|
||
|
clients to silence keyboard/mouse events while opened. However, there was no
|
||
|
counting of how many clients were opened, so opening two at the same time and
|
||
|
then closing one would fool the driver into thinking it had no remaining opened
|
||
|
clients.
|
||
|
|
||
|
Signed-off-by: Vicki Pfau <vi@endrift.com>
|
||
|
---
|
||
|
drivers/hid/hid-steam.c | 10 +++++-----
|
||
|
1 file changed, 5 insertions(+), 5 deletions(-)
|
||
|
|
||
|
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
|
||
|
index 845ca71b8bd3a..0c2fe51b29bc1 100644
|
||
|
--- a/drivers/hid/hid-steam.c
|
||
|
+++ b/drivers/hid/hid-steam.c
|
||
|
@@ -215,7 +215,7 @@ struct steam_device {
|
||
|
spinlock_t lock;
|
||
|
struct hid_device *hdev, *client_hdev;
|
||
|
struct mutex report_mutex;
|
||
|
- bool client_opened;
|
||
|
+ unsigned long client_opened;
|
||
|
struct input_dev __rcu *input;
|
||
|
unsigned long quirks;
|
||
|
struct work_struct work_connect;
|
||
|
@@ -787,7 +787,7 @@ static void steam_battery_unregister(struct steam_device *steam)
|
||
|
static int steam_register(struct steam_device *steam)
|
||
|
{
|
||
|
int ret;
|
||
|
- bool client_opened;
|
||
|
+ unsigned long client_opened;
|
||
|
unsigned long flags;
|
||
|
|
||
|
/*
|
||
|
@@ -961,7 +961,7 @@ static int steam_client_ll_open(struct hid_device *hdev)
|
||
|
unsigned long flags;
|
||
|
|
||
|
spin_lock_irqsave(&steam->lock, flags);
|
||
|
- steam->client_opened = true;
|
||
|
+ steam->client_opened++;
|
||
|
spin_unlock_irqrestore(&steam->lock, flags);
|
||
|
|
||
|
steam_input_unregister(steam);
|
||
|
@@ -977,7 +977,7 @@ static void steam_client_ll_close(struct hid_device *hdev)
|
||
|
bool connected;
|
||
|
|
||
|
spin_lock_irqsave(&steam->lock, flags);
|
||
|
- steam->client_opened = false;
|
||
|
+ steam->client_opened--;
|
||
|
connected = steam->connected && !steam->client_opened;
|
||
|
spin_unlock_irqrestore(&steam->lock, flags);
|
||
|
|
||
|
@@ -1156,7 +1156,7 @@ static void steam_remove(struct hid_device *hdev)
|
||
|
cancel_work_sync(&steam->work_connect);
|
||
|
hid_destroy_device(steam->client_hdev);
|
||
|
steam->client_hdev = NULL;
|
||
|
- steam->client_opened = false;
|
||
|
+ steam->client_opened = 0;
|
||
|
if (steam->quirks & STEAM_QUIRK_WIRELESS) {
|
||
|
hid_info(hdev, "Steam wireless receiver disconnected");
|
||
|
}
|
||
|
--
|
||
|
2.41.0
|
||
|
|
||
|
|
||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Vicki Pfau <vi@endrift.com>
|
||
|
Date: Thu, 18 May 2023 18:00:35 -0700
|
||
|
Subject: [PATCH 07/10] HID: hid-steam: Better handling of serial number length
|
||
|
|
||
|
The second byte of the GET_STRING_ATTRIB report is a length, so we should set
|
||
|
the size of the buffer to be the size we're actually requesting, and only
|
||
|
reject the reply if the length out is nonsensical.
|
||
|
|
||
|
Signed-off-by: Vicki Pfau <vi@endrift.com>
|
||
|
---
|
||
|
drivers/hid/hid-steam.c | 10 +++++-----
|
||
|
1 file changed, 5 insertions(+), 5 deletions(-)
|
||
|
|
||
|
diff --git a/drivers/hid/hid-steam.c b/drivers/hid/hid-steam.c
|
||
|
index 0c2fe51b29bc1..92e3e1052fa42 100644
|
||
|
--- a/drivers/hid/hid-steam.c
|
||
|
+++ b/drivers/hid/hid-steam.c
|
||
|
@@ -208,7 +208,7 @@ static LIST_HEAD(steam_devices);
|
||
|
#define STEAM_PAD_BOTH 2
|
||
|
|
||
|
/* Other random constants */
|
||
|
-#define STEAM_SERIAL_LEN 10
|
||
|
+#define STEAM_SERIAL_LEN 0x15
|
||
|
|
||
|
struct steam_device {
|
||
|
struct list_head list;
|
||
|
@@ -359,10 +359,10 @@ static int steam_get_serial(struct steam_device *steam)
|
||
|
{
|
||
|
/*
|
||
|
* Send: 0xae 0x15 0x01
|
||
|
- * Recv: 0xae 0x15 0x01 serialnumber (10 chars)
|
||
|
+ * Recv: 0xae 0x15 0x01 serialnumber
|
||
|
*/
|
||
|
int ret = 0;
|
||
|
- u8 cmd[] = {STEAM_CMD_GET_STRING_ATTRIB, 0x15, STEAM_ATTRIB_STR_UNIT_SERIAL};
|
||
|
+ u8 cmd[] = {STEAM_CMD_GET_STRING_ATTRIB, sizeof(steam->serial_no), STEAM_ATTRIB_STR_UNIT_SERIAL};
|
||
|
u8 reply[3 + STEAM_SERIAL_LEN + 1];
|
||
|
|
||
|
mutex_lock(&steam->report_mutex);
|
||
|
@@ -372,12 +372,12 @@ static int steam_get_serial(struct steam_device *steam)
|
||
|
ret = steam_recv_report(steam, reply, sizeof(reply));
|
||
|
if (ret < 0)
|
||
|
goto out;
|
||
|
- if (reply[0] != 0xae || reply[1] != 0x15 || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL) {
|
||
|
+ if (reply[0] != 0xae || reply[1] < 1 || reply[1] > sizeof(steam->serial_no) || reply[2] != STEAM_ATTRIB_STR_UNIT_SERIAL) {
|
||
|
ret = -EIO;
|
||
|
goto out;
|
||
|
}
|
||
|
reply[3 + STEAM_SERIAL_LEN] = 0;
|
||
|
- strscpy(steam->serial_no, reply + 3, sizeof(steam->serial_no));
|
||
|
+ strscpy(steam->serial_no, reply + 3, reply[1]);
|
||
|
out:
|
||
|
mutex_unlock(&steam->report_mutex);
|
||
|
return ret;
|
||
|
--
|
||
|
2.41.0
|