From 09308562d4afb1abc66366608fa1cb9de783272f Mon Sep 17 00:00:00 2001 From: Rahul Rameshbabu Date: Thu, 8 Jun 2023 07:34:50 -0700 Subject: HID: nvidia-shield: Initial driver implementation with Thunderstrike support Supports the Thunderstrike (SHIELD 2017) controller. Implements support for the Thunderstrike HOSTCMD firmware interface. Adds sysfs attributes about a SHIELD device and introduces haptics support for controllers. Signed-off-by: Rahul Rameshbabu Signed-off-by: Jiri Kosina --- MAINTAINERS | 6 + drivers/hid/Kconfig | 18 ++ drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 3 + drivers/hid/hid-nvidia-shield.c | 587 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 615 insertions(+) create mode 100644 drivers/hid/hid-nvidia-shield.c diff --git a/MAINTAINERS b/MAINTAINERS index 0dab9737ec16..4b0c1cfc8a8d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9155,6 +9155,12 @@ L: linux-input@vger.kernel.org S: Maintained F: drivers/hid/hid-pxrc.c +HID NVIDIA SHIELD DRIVER +M: Rahul Rameshbabu +L: linux-input@vger.kernel.org +S: Maintained +F: drivers/hid/hid-nvidia-shield.c + HID PLAYSTATION DRIVER M: Roderick Colenbrander L: linux-input@vger.kernel.org diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 4ce012f83253..3b068c4d81f0 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -788,6 +788,24 @@ config HID_NTRIG help Support for N-Trig touch screen. +config HID_NVIDIA_SHIELD + tristate "NVIDIA SHIELD devices" + depends on USB_HID + depends on BT_HIDP + help + Support for NVIDIA SHIELD accessories. + + Supported devices: + - Thunderstrike (NVIDIA SHIELD Controller 2017) + +config NVIDIA_SHIELD_FF + bool "NVIDIA SHIELD force feedback support" + depends on HID_NVIDIA_SHIELD + select INPUT_FF_MEMLESS + help + Say Y here if you would like to enable force feedback support for + NVIDIA SHIELD accessories with haptics capabilities. + config HID_ORTEK tristate "Ortek PKB-1700/WKB-2000/Skycable wireless keyboard and mouse trackpad" help diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 5d37cacbde33..7a9e160158f7 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -87,6 +87,7 @@ obj-$(CONFIG_HID_MULTITOUCH) += hid-multitouch.o obj-$(CONFIG_HID_NINTENDO) += hid-nintendo.o obj-$(CONFIG_HID_NTI) += hid-nti.o obj-$(CONFIG_HID_NTRIG) += hid-ntrig.o +obj-$(CONFIG_HID_NVIDIA_SHIELD) += hid-nvidia-shield.o obj-$(CONFIG_HID_ORTEK) += hid-ortek.o obj-$(CONFIG_HID_PRODIKEYS) += hid-prodikeys.o obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 5d29abac2300..766ba121ac02 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -1005,6 +1005,9 @@ #define USB_DEVICE_ID_NTRIG_TOUCH_SCREEN_18 0x0014 #define USB_DEVICE_ID_NTRIG_DUOSENSE 0x1500 +#define USB_VENDOR_ID_NVIDIA 0x0955 +#define USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER 0x7214 + #define USB_VENDOR_ID_ONTRAK 0x0a07 #define USB_DEVICE_ID_ONTRAK_ADU100 0x0064 diff --git a/drivers/hid/hid-nvidia-shield.c b/drivers/hid/hid-nvidia-shield.c new file mode 100644 index 000000000000..4182ad3a7b93 --- /dev/null +++ b/drivers/hid/hid-nvidia-shield.c @@ -0,0 +1,587 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * + * HID driver for NVIDIA SHIELD peripherals. + */ + +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" + +#define NOT_INIT_STR "NOT INITIALIZED" + +enum { + SHIELD_FW_VERSION_INITIALIZED = 0, + SHIELD_BOARD_INFO_INITIALIZED, +}; + +enum { + THUNDERSTRIKE_FW_VERSION_UPDATE = 0, + THUNDERSTRIKE_BOARD_INFO_UPDATE, + THUNDERSTRIKE_HAPTICS_UPDATE, +}; + +enum { + THUNDERSTRIKE_HOSTCMD_REPORT_SIZE = 33, + THUNDERSTRIKE_HOSTCMD_REQ_REPORT_ID = 0x4, + THUNDERSTRIKE_HOSTCMD_RESP_REPORT_ID = 0x3, +}; + +enum { + THUNDERSTRIKE_HOSTCMD_ID_FW_VERSION = 1, + THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO = 16, + THUNDERSTRIKE_HOSTCMD_ID_USB_INIT = 53, + THUNDERSTRIKE_HOSTCMD_ID_HAPTICS = 57, + THUNDERSTRIKE_HOSTCMD_ID_BLUETOOTH_INIT = 58, +}; + +struct thunderstrike_hostcmd_board_info { + __le16 revision; + __le16 serial[7]; +}; + +struct thunderstrike_hostcmd_haptics { + u8 motor_left; + u8 motor_right; +}; + +struct thunderstrike_hostcmd_resp_report { + u8 report_id; /* THUNDERSTRIKE_HOSTCMD_RESP_REPORT_ID */ + u8 cmd_id; + u8 reserved_at_10; + + union { + struct thunderstrike_hostcmd_board_info board_info; + struct thunderstrike_hostcmd_haptics motors; + __le16 fw_version; + u8 payload[30]; + }; +} __packed; +static_assert(sizeof(struct thunderstrike_hostcmd_resp_report) == + THUNDERSTRIKE_HOSTCMD_REPORT_SIZE); + +struct thunderstrike_hostcmd_req_report { + u8 report_id; /* THUNDERSTRIKE_HOSTCMD_REQ_REPORT_ID */ + u8 cmd_id; + u8 reserved_at_10; + + struct { + u8 update; + struct thunderstrike_hostcmd_haptics motors; + } haptics; + u8 reserved_at_30[27]; +} __packed; +static_assert(sizeof(struct thunderstrike_hostcmd_req_report) == + THUNDERSTRIKE_HOSTCMD_REPORT_SIZE); + +/* Common struct for shield accessories. */ +struct shield_device { + struct hid_device *hdev; + + unsigned long initialized_flags; + const char *codename; + u16 fw_version; + struct { + u16 revision; + char serial_number[15]; + } board_info; +}; + +struct thunderstrike { + struct shield_device base; + + /* Sub-devices */ + struct input_dev *haptics_dev; + + /* Resources */ + void *req_report_dmabuf; + unsigned long update_flags; + struct thunderstrike_hostcmd_haptics haptics_val; + spinlock_t haptics_update_lock; + struct work_struct hostcmd_req_work; +}; + +static inline void thunderstrike_hostcmd_req_report_init( + struct thunderstrike_hostcmd_req_report *report, u8 cmd_id) +{ + memset(report, 0, sizeof(*report)); + report->report_id = THUNDERSTRIKE_HOSTCMD_REQ_REPORT_ID; + report->cmd_id = cmd_id; +} + +static inline void shield_strrev(char *dest, size_t len, u16 rev) +{ + dest[0] = ('A' - 1) + (rev >> 8); + snprintf(&dest[1], len - 1, "%02X", 0xff & rev); +} + +static struct input_dev *shield_allocate_input_dev(struct hid_device *hdev, + const char *name_suffix) +{ + struct input_dev *idev; + + idev = input_allocate_device(); + if (!idev) + goto err_device; + + idev->id.bustype = hdev->bus; + idev->id.vendor = hdev->vendor; + idev->id.product = hdev->product; + idev->id.version = hdev->version; + idev->uniq = hdev->uniq; + idev->name = devm_kasprintf(&idev->dev, GFP_KERNEL, "%s %s", hdev->name, + name_suffix); + if (!idev->name) + goto err_name; + + input_set_drvdata(idev, hdev); + + return idev; + +err_name: + input_free_device(idev); +err_device: + return ERR_PTR(-ENOMEM); +} + +static struct input_dev *shield_haptics_create( + struct shield_device *dev, + int (*play_effect)(struct input_dev *, void *, struct ff_effect *)) +{ + struct input_dev *haptics; + int ret; + + if (!IS_ENABLED(CONFIG_NVIDIA_SHIELD_FF)) + return NULL; + + haptics = shield_allocate_input_dev(dev->hdev, "Haptics"); + if (IS_ERR(haptics)) + return haptics; + + input_set_capability(haptics, EV_FF, FF_RUMBLE); + input_ff_create_memless(haptics, NULL, play_effect); + + ret = input_register_device(haptics); + if (ret) + goto err; + + return haptics; + +err: + input_free_device(haptics); + return ERR_PTR(ret); +} + +static inline void thunderstrike_send_hostcmd_request(struct thunderstrike *ts) +{ + struct thunderstrike_hostcmd_req_report *report = ts->req_report_dmabuf; + struct shield_device *shield_dev = &ts->base; + int ret; + + ret = hid_hw_raw_request(shield_dev->hdev, report->report_id, + ts->req_report_dmabuf, + THUNDERSTRIKE_HOSTCMD_REPORT_SIZE, + HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); + + if (ret < 0) { + hid_err(shield_dev->hdev, + "Failed to output Thunderstrike HOSTCMD request HID report due to %pe\n", + ERR_PTR(ret)); + } +} + +static void thunderstrike_hostcmd_req_work_handler(struct work_struct *work) +{ + struct thunderstrike *ts = + container_of(work, struct thunderstrike, hostcmd_req_work); + struct thunderstrike_hostcmd_req_report *report; + unsigned long flags; + + report = ts->req_report_dmabuf; + + if (test_and_clear_bit(THUNDERSTRIKE_FW_VERSION_UPDATE, &ts->update_flags)) { + thunderstrike_hostcmd_req_report_init( + report, THUNDERSTRIKE_HOSTCMD_ID_FW_VERSION); + thunderstrike_send_hostcmd_request(ts); + } + + if (test_and_clear_bit(THUNDERSTRIKE_BOARD_INFO_UPDATE, &ts->update_flags)) { + thunderstrike_hostcmd_req_report_init( + report, THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO); + thunderstrike_send_hostcmd_request(ts); + } + + if (test_and_clear_bit(THUNDERSTRIKE_HAPTICS_UPDATE, &ts->update_flags)) { + thunderstrike_hostcmd_req_report_init( + report, THUNDERSTRIKE_HOSTCMD_ID_HAPTICS); + + report->haptics.update = 1; + spin_lock_irqsave(&ts->haptics_update_lock, flags); + report->haptics.motors = ts->haptics_val; + spin_unlock_irqrestore(&ts->haptics_update_lock, flags); + + thunderstrike_send_hostcmd_request(ts); + } +} + +static inline void thunderstrike_request_firmware_version(struct thunderstrike *ts) +{ + set_bit(THUNDERSTRIKE_FW_VERSION_UPDATE, &ts->update_flags); + schedule_work(&ts->hostcmd_req_work); +} + +static inline void thunderstrike_request_board_info(struct thunderstrike *ts) +{ + set_bit(THUNDERSTRIKE_BOARD_INFO_UPDATE, &ts->update_flags); + schedule_work(&ts->hostcmd_req_work); +} + +static inline int +thunderstrike_update_haptics(struct thunderstrike *ts, + struct thunderstrike_hostcmd_haptics *motors) +{ + unsigned long flags; + + spin_lock_irqsave(&ts->haptics_update_lock, flags); + ts->haptics_val = *motors; + spin_unlock_irqrestore(&ts->haptics_update_lock, flags); + + set_bit(THUNDERSTRIKE_HAPTICS_UPDATE, &ts->update_flags); + schedule_work(&ts->hostcmd_req_work); + + return 0; +} + +static int thunderstrike_play_effect(struct input_dev *idev, void *data, + struct ff_effect *effect) +{ + struct hid_device *hdev = input_get_drvdata(idev); + struct thunderstrike_hostcmd_haptics motors; + struct shield_device *shield_dev; + struct thunderstrike *ts; + + if (effect->type != FF_RUMBLE) + return 0; + + shield_dev = hid_get_drvdata(hdev); + ts = container_of(shield_dev, struct thunderstrike, base); + + /* Thunderstrike motor values range from 0 to 32 inclusively */ + motors.motor_left = effect->u.rumble.strong_magnitude / 2047; + motors.motor_right = effect->u.rumble.weak_magnitude / 2047; + + hid_dbg(hdev, "Thunderstrike FF_RUMBLE request, left: %u right: %u\n", + motors.motor_left, motors.motor_right); + + return thunderstrike_update_haptics(ts, &motors); +} + +static void +thunderstrike_parse_fw_version_payload(struct shield_device *shield_dev, + __le16 fw_version) +{ + shield_dev->fw_version = le16_to_cpu(fw_version); + + set_bit(SHIELD_FW_VERSION_INITIALIZED, &shield_dev->initialized_flags); + + hid_dbg(shield_dev->hdev, "Thunderstrike firmware version 0x%04X\n", + shield_dev->fw_version); +} + +static void +thunderstrike_parse_board_info_payload(struct shield_device *shield_dev, + struct thunderstrike_hostcmd_board_info *board_info) +{ + char board_revision_str[4]; + int i; + + shield_dev->board_info.revision = le16_to_cpu(board_info->revision); + for (i = 0; i < 7; ++i) { + u16 val = le16_to_cpu(board_info->serial[i]); + + shield_dev->board_info.serial_number[2 * i] = val & 0xFF; + shield_dev->board_info.serial_number[2 * i + 1] = val >> 8; + } + shield_dev->board_info.serial_number[14] = '\0'; + + set_bit(SHIELD_BOARD_INFO_INITIALIZED, &shield_dev->initialized_flags); + + shield_strrev(board_revision_str, 4, shield_dev->board_info.revision); + hid_dbg(shield_dev->hdev, + "Thunderstrike BOARD_REVISION_%s (0x%04X) S/N: %s\n", + board_revision_str, shield_dev->board_info.revision, + shield_dev->board_info.serial_number); +} + +static inline void +thunderstrike_parse_haptics_payload(struct shield_device *shield_dev, + struct thunderstrike_hostcmd_haptics *haptics) +{ + hid_dbg(shield_dev->hdev, + "Thunderstrike haptics HOSTCMD response, left: %u right: %u\n", + haptics->motor_left, haptics->motor_right); +} + +static int thunderstrike_parse_report(struct shield_device *shield_dev, + struct hid_report *report, u8 *data, + int size) +{ + struct thunderstrike_hostcmd_resp_report *hostcmd_resp_report; + struct thunderstrike *ts = + container_of(shield_dev, struct thunderstrike, base); + struct hid_device *hdev = shield_dev->hdev; + + switch (report->id) { + case THUNDERSTRIKE_HOSTCMD_RESP_REPORT_ID: + if (size != THUNDERSTRIKE_HOSTCMD_REPORT_SIZE) { + hid_err(hdev, + "Encountered Thunderstrike HOSTCMD HID report with unexpected size %d\n", + size); + return -EINVAL; + } + + hostcmd_resp_report = + (struct thunderstrike_hostcmd_resp_report *)data; + + switch (hostcmd_resp_report->cmd_id) { + case THUNDERSTRIKE_HOSTCMD_ID_FW_VERSION: + thunderstrike_parse_fw_version_payload( + shield_dev, hostcmd_resp_report->fw_version); + break; + case THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO: + thunderstrike_parse_board_info_payload( + shield_dev, &hostcmd_resp_report->board_info); + break; + case THUNDERSTRIKE_HOSTCMD_ID_HAPTICS: + thunderstrike_parse_haptics_payload( + shield_dev, &hostcmd_resp_report->motors); + break; + + case THUNDERSTRIKE_HOSTCMD_ID_USB_INIT: + case THUNDERSTRIKE_HOSTCMD_ID_BLUETOOTH_INIT: + /* May block HOSTCMD requests till received initially */ + thunderstrike_request_firmware_version(ts); + thunderstrike_request_board_info(ts); + /* Only HOSTCMD that can be triggered without a request */ + return 0; + default: + hid_warn(hdev, + "Unhandled Thunderstrike HOSTCMD id %d\n", + hostcmd_resp_report->cmd_id); + return -ENOENT; + } + + break; + default: + return 0; + } + + return 0; +} + +static struct shield_device *thunderstrike_create(struct hid_device *hdev) +{ + struct shield_device *shield_dev; + struct thunderstrike *ts; + + ts = devm_kzalloc(&hdev->dev, sizeof(*ts), GFP_KERNEL); + if (!ts) + return ERR_PTR(-ENOMEM); + + ts->req_report_dmabuf = devm_kzalloc( + &hdev->dev, THUNDERSTRIKE_HOSTCMD_REPORT_SIZE, GFP_KERNEL); + if (!ts->req_report_dmabuf) + return ERR_PTR(-ENOMEM); + + shield_dev = &ts->base; + shield_dev->hdev = hdev; + shield_dev->codename = "Thunderstrike"; + + spin_lock_init(&ts->haptics_update_lock); + INIT_WORK(&ts->hostcmd_req_work, thunderstrike_hostcmd_req_work_handler); + + hid_set_drvdata(hdev, shield_dev); + + ts->haptics_dev = shield_haptics_create(shield_dev, thunderstrike_play_effect); + if (IS_ERR(ts->haptics_dev)) + return ERR_CAST(ts->haptics_dev); + + hid_info(hdev, "Registered Thunderstrike controller\n"); + return shield_dev; +} + +static ssize_t firmware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct shield_device *shield_dev; + int ret; + + shield_dev = hid_get_drvdata(hdev); + + if (test_bit(SHIELD_FW_VERSION_INITIALIZED, &shield_dev->initialized_flags)) + ret = sysfs_emit(buf, "0x%04X\n", shield_dev->fw_version); + else + ret = sysfs_emit(buf, NOT_INIT_STR "\n"); + + return ret; +} + +static DEVICE_ATTR_RO(firmware_version); + +static ssize_t hardware_version_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct shield_device *shield_dev; + char board_revision_str[4]; + int ret; + + shield_dev = hid_get_drvdata(hdev); + + if (test_bit(SHIELD_BOARD_INFO_INITIALIZED, &shield_dev->initialized_flags)) { + shield_strrev(board_revision_str, 4, shield_dev->board_info.revision); + ret = sysfs_emit(buf, "%s BOARD_REVISION_%s (0x%04X)\n", + shield_dev->codename, board_revision_str, + shield_dev->board_info.revision); + } else + ret = sysfs_emit(buf, NOT_INIT_STR "\n"); + + return ret; +} + +static DEVICE_ATTR_RO(hardware_version); + +static ssize_t serial_number_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct hid_device *hdev = to_hid_device(dev); + struct shield_device *shield_dev; + int ret; + + shield_dev = hid_get_drvdata(hdev); + + if (test_bit(SHIELD_BOARD_INFO_INITIALIZED, &shield_dev->initialized_flags)) + ret = sysfs_emit(buf, "%s\n", shield_dev->board_info.serial_number); + else + ret = sysfs_emit(buf, NOT_INIT_STR "\n"); + + return ret; +} + +static DEVICE_ATTR_RO(serial_number); + +static struct attribute *shield_device_attrs[] = { + &dev_attr_firmware_version.attr, + &dev_attr_hardware_version.attr, + &dev_attr_serial_number.attr, + NULL, +}; +ATTRIBUTE_GROUPS(shield_device); + +static int shield_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct shield_device *dev = hid_get_drvdata(hdev); + + return thunderstrike_parse_report(dev, report, data, size); +} + +static int shield_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct shield_device *shield_dev = NULL; + struct thunderstrike *ts; + int ret; + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "Parse failed\n"); + return ret; + } + + switch (id->product) { + case USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER: + shield_dev = thunderstrike_create(hdev); + break; + } + + if (unlikely(!shield_dev)) { + hid_err(hdev, "Failed to identify SHIELD device\n"); + return -ENODEV; + } + if (IS_ERR(shield_dev)) { + hid_err(hdev, "Failed to create SHIELD device\n"); + return PTR_ERR(shield_dev); + } + + ts = container_of(shield_dev, struct thunderstrike, base); + + ret = hid_hw_start(hdev, HID_CONNECT_HIDINPUT); + if (ret) { + hid_err(hdev, "Failed to start HID device\n"); + goto err_haptics; + } + + ret = hid_hw_open(hdev); + if (ret) { + hid_err(hdev, "Failed to open HID device\n"); + goto err_stop; + } + + thunderstrike_request_firmware_version(ts); + thunderstrike_request_board_info(ts); + + return ret; + +err_stop: + hid_hw_stop(hdev); +err_haptics: + if (ts->haptics_dev) + input_unregister_device(ts->haptics_dev); + return ret; +} + +static void shield_remove(struct hid_device *hdev) +{ + struct shield_device *dev = hid_get_drvdata(hdev); + struct thunderstrike *ts; + + ts = container_of(dev, struct thunderstrike, base); + + hid_hw_close(hdev); + if (ts->haptics_dev) + input_unregister_device(ts->haptics_dev); + cancel_work_sync(&ts->hostcmd_req_work); + hid_hw_stop(hdev); +} + +static const struct hid_device_id shield_devices[] = { + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NVIDIA, + USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER) }, + { HID_USB_DEVICE(USB_VENDOR_ID_NVIDIA, + USB_DEVICE_ID_NVIDIA_THUNDERSTRIKE_CONTROLLER) }, + { } +}; +MODULE_DEVICE_TABLE(hid, shield_devices); + +static struct hid_driver shield_driver = { + .name = "shield", + .id_table = shield_devices, + .probe = shield_probe, + .remove = shield_remove, + .raw_event = shield_raw_event, + .driver = { + .dev_groups = shield_device_groups, + }, +}; +module_hid_driver(shield_driver); + +MODULE_AUTHOR("Rahul Rameshbabu "); +MODULE_DESCRIPTION("HID Driver for NVIDIA SHIELD peripherals."); +MODULE_LICENSE("GPL"); -- cgit v1.2.3 From 13d02c69e4788f700377677a6cc4247a2869b17d Mon Sep 17 00:00:00 2001 From: Rahul Rameshbabu Date: Mon, 29 May 2023 15:20:50 -0700 Subject: HID: nvidia-shield: Add mappings for consumer HID USAGE buttons Map Android Home, Back, Search, VolumeUp, VolumeDown, and PlayPause buttons to the appropriate input event codes. Signed-off-by: Rahul Rameshbabu Signed-off-by: Jiri Kosina --- drivers/hid/hid-nvidia-shield.c | 55 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-nvidia-shield.c b/drivers/hid/hid-nvidia-shield.c index 4182ad3a7b93..d7521c9c8c1e 100644 --- a/drivers/hid/hid-nvidia-shield.c +++ b/drivers/hid/hid-nvidia-shield.c @@ -15,6 +15,16 @@ #include "hid-ids.h" #define NOT_INIT_STR "NOT INITIALIZED" +#define android_map_key(c) hid_map_usage(hi, usage, bit, max, EV_KEY, (c)) + +enum { + HID_USAGE_ANDROID_PLAYPAUSE_BTN = 0xcd, /* Double-tap volume slider */ + HID_USAGE_ANDROID_VOLUMEUP_BTN = 0xe9, + HID_USAGE_ANDROID_VOLUMEDOWN_BTN = 0xea, + HID_USAGE_ANDROID_SEARCH_BTN = 0x221, /* NVIDIA btn on Thunderstrike */ + HID_USAGE_ANDROID_HOME_BTN = 0x223, + HID_USAGE_ANDROID_BACK_BTN = 0x224, +}; enum { SHIELD_FW_VERSION_INITIALIZED = 0, @@ -416,6 +426,40 @@ static struct shield_device *thunderstrike_create(struct hid_device *hdev) return shield_dev; } +static int android_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, + struct hid_usage *usage, unsigned long **bit, + int *max) +{ + if ((usage->hid & HID_USAGE_PAGE) != HID_UP_CONSUMER) + return 0; + + switch (usage->hid & HID_USAGE) { + case HID_USAGE_ANDROID_PLAYPAUSE_BTN: + android_map_key(KEY_PLAYPAUSE); + break; + case HID_USAGE_ANDROID_VOLUMEUP_BTN: + android_map_key(KEY_VOLUMEUP); + break; + case HID_USAGE_ANDROID_VOLUMEDOWN_BTN: + android_map_key(KEY_VOLUMEDOWN); + break; + case HID_USAGE_ANDROID_SEARCH_BTN: + android_map_key(BTN_Z); + break; + case HID_USAGE_ANDROID_HOME_BTN: + android_map_key(BTN_MODE); + break; + case HID_USAGE_ANDROID_BACK_BTN: + android_map_key(BTN_SELECT); + break; + default: + return 0; + } + + return 1; +} + static ssize_t firmware_version_show(struct device *dev, struct device_attribute *attr, char *buf) { @@ -571,11 +615,12 @@ static const struct hid_device_id shield_devices[] = { MODULE_DEVICE_TABLE(hid, shield_devices); static struct hid_driver shield_driver = { - .name = "shield", - .id_table = shield_devices, - .probe = shield_probe, - .remove = shield_remove, - .raw_event = shield_raw_event, + .name = "shield", + .id_table = shield_devices, + .input_mapping = android_input_mapping, + .probe = shield_probe, + .remove = shield_remove, + .raw_event = shield_raw_event, .driver = { .dev_groups = shield_device_groups, }, -- cgit v1.2.3 From f88af60e74a5b31be1551ffb9af216c8a800501c Mon Sep 17 00:00:00 2001 From: Rahul Rameshbabu Date: Mon, 29 May 2023 15:20:51 -0700 Subject: HID: nvidia-shield: Support LED functionality for Thunderstrike Expose the 2017 SHIELD controller (Thunderstrike) LED through the kernel LED API. Signed-off-by: Rahul Rameshbabu Signed-off-by: Jiri Kosina --- drivers/hid/hid-nvidia-shield.c | 116 ++++++++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-nvidia-shield.c b/drivers/hid/hid-nvidia-shield.c index d7521c9c8c1e..85700cec5eac 100644 --- a/drivers/hid/hid-nvidia-shield.c +++ b/drivers/hid/hid-nvidia-shield.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,7 @@ enum { THUNDERSTRIKE_FW_VERSION_UPDATE = 0, THUNDERSTRIKE_BOARD_INFO_UPDATE, THUNDERSTRIKE_HAPTICS_UPDATE, + THUNDERSTRIKE_LED_UPDATE, }; enum { @@ -45,12 +47,19 @@ enum { enum { THUNDERSTRIKE_HOSTCMD_ID_FW_VERSION = 1, + THUNDERSTRIKE_HOSTCMD_ID_LED = 6, THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO = 16, THUNDERSTRIKE_HOSTCMD_ID_USB_INIT = 53, THUNDERSTRIKE_HOSTCMD_ID_HAPTICS = 57, THUNDERSTRIKE_HOSTCMD_ID_BLUETOOTH_INIT = 58, }; +enum thunderstrike_led_state { + THUNDERSTRIKE_LED_OFF = 1, + THUNDERSTRIKE_LED_ON = 8, +} __packed; +static_assert(sizeof(enum thunderstrike_led_state) == 1); + struct thunderstrike_hostcmd_board_info { __le16 revision; __le16 serial[7]; @@ -70,6 +79,7 @@ struct thunderstrike_hostcmd_resp_report { struct thunderstrike_hostcmd_board_info board_info; struct thunderstrike_hostcmd_haptics motors; __le16 fw_version; + enum thunderstrike_led_state led_state; u8 payload[30]; }; } __packed; @@ -81,10 +91,16 @@ struct thunderstrike_hostcmd_req_report { u8 cmd_id; u8 reserved_at_10; - struct { - u8 update; - struct thunderstrike_hostcmd_haptics motors; - } haptics; + union { + struct { + u8 update; + enum thunderstrike_led_state state; + } led; + struct { + u8 update; + struct thunderstrike_hostcmd_haptics motors; + } haptics; + }; u8 reserved_at_30[27]; } __packed; static_assert(sizeof(struct thunderstrike_hostcmd_req_report) == @@ -108,12 +124,15 @@ struct thunderstrike { /* Sub-devices */ struct input_dev *haptics_dev; + struct led_classdev led_dev; /* Resources */ void *req_report_dmabuf; unsigned long update_flags; struct thunderstrike_hostcmd_haptics haptics_val; spinlock_t haptics_update_lock; + u8 led_state : 1; + enum thunderstrike_led_state led_value; struct work_struct hostcmd_req_work; }; @@ -221,6 +240,13 @@ static void thunderstrike_hostcmd_req_work_handler(struct work_struct *work) thunderstrike_send_hostcmd_request(ts); } + if (test_and_clear_bit(THUNDERSTRIKE_LED_UPDATE, &ts->update_flags)) { + thunderstrike_hostcmd_req_report_init(report, THUNDERSTRIKE_HOSTCMD_ID_LED); + report->led.update = 1; + report->led.state = ts->led_value; + thunderstrike_send_hostcmd_request(ts); + } + if (test_and_clear_bit(THUNDERSTRIKE_BOARD_INFO_UPDATE, &ts->update_flags)) { thunderstrike_hostcmd_req_report_init( report, THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO); @@ -292,6 +318,40 @@ static int thunderstrike_play_effect(struct input_dev *idev, void *data, return thunderstrike_update_haptics(ts, &motors); } +static enum led_brightness +thunderstrike_led_get_brightness(struct led_classdev *led) +{ + struct hid_device *hdev = to_hid_device(led->dev->parent); + struct shield_device *shield_dev = hid_get_drvdata(hdev); + struct thunderstrike *ts; + + ts = container_of(shield_dev, struct thunderstrike, base); + + return ts->led_state; +} + +static void thunderstrike_led_set_brightness(struct led_classdev *led, + enum led_brightness value) +{ + struct hid_device *hdev = to_hid_device(led->dev->parent); + struct shield_device *shield_dev = hid_get_drvdata(hdev); + struct thunderstrike *ts; + + ts = container_of(shield_dev, struct thunderstrike, base); + + switch (value) { + case LED_OFF: + ts->led_value = THUNDERSTRIKE_LED_OFF; + break; + default: + ts->led_value = THUNDERSTRIKE_LED_ON; + break; + } + + set_bit(THUNDERSTRIKE_LED_UPDATE, &ts->update_flags); + schedule_work(&ts->hostcmd_req_work); +} + static void thunderstrike_parse_fw_version_payload(struct shield_device *shield_dev, __le16 fw_version) @@ -338,6 +398,24 @@ thunderstrike_parse_haptics_payload(struct shield_device *shield_dev, haptics->motor_left, haptics->motor_right); } +static void +thunderstrike_parse_led_payload(struct shield_device *shield_dev, + enum thunderstrike_led_state led_state) +{ + struct thunderstrike *ts = container_of(shield_dev, struct thunderstrike, base); + + switch (led_state) { + case THUNDERSTRIKE_LED_OFF: + ts->led_state = 0; + break; + case THUNDERSTRIKE_LED_ON: + ts->led_state = 1; + break; + } + + hid_dbg(shield_dev->hdev, "Thunderstrike led HOSTCMD response, 0x%02X\n", led_state); +} + static int thunderstrike_parse_report(struct shield_device *shield_dev, struct hid_report *report, u8 *data, int size) @@ -364,6 +442,9 @@ static int thunderstrike_parse_report(struct shield_device *shield_dev, thunderstrike_parse_fw_version_payload( shield_dev, hostcmd_resp_report->fw_version); break; + case THUNDERSTRIKE_HOSTCMD_ID_LED: + thunderstrike_parse_led_payload(shield_dev, hostcmd_resp_report->led_state); + break; case THUNDERSTRIKE_HOSTCMD_ID_BOARD_INFO: thunderstrike_parse_board_info_payload( shield_dev, &hostcmd_resp_report->board_info); @@ -395,10 +476,24 @@ static int thunderstrike_parse_report(struct shield_device *shield_dev, return 0; } +static inline int thunderstrike_led_create(struct thunderstrike *ts) +{ + struct led_classdev *led = &ts->led_dev; + + led->name = "thunderstrike:blue:led"; + led->max_brightness = 1; + led->flags = LED_CORE_SUSPENDRESUME; + led->brightness_get = &thunderstrike_led_get_brightness; + led->brightness_set = &thunderstrike_led_set_brightness; + + return led_classdev_register(&ts->base.hdev->dev, led); +} + static struct shield_device *thunderstrike_create(struct hid_device *hdev) { struct shield_device *shield_dev; struct thunderstrike *ts; + int ret; ts = devm_kzalloc(&hdev->dev, sizeof(*ts), GFP_KERNEL); if (!ts) @@ -418,12 +513,22 @@ static struct shield_device *thunderstrike_create(struct hid_device *hdev) hid_set_drvdata(hdev, shield_dev); + ret = thunderstrike_led_create(ts); + if (ret) { + hid_err(hdev, "Failed to create Thunderstrike LED instance\n"); + return ERR_PTR(ret); + } + ts->haptics_dev = shield_haptics_create(shield_dev, thunderstrike_play_effect); if (IS_ERR(ts->haptics_dev)) - return ERR_CAST(ts->haptics_dev); + goto err; hid_info(hdev, "Registered Thunderstrike controller\n"); return shield_dev; + +err: + led_classdev_unregister(&ts->led_dev); + return ERR_CAST(ts->haptics_dev); } static int android_input_mapping(struct hid_device *hdev, struct hid_input *hi, @@ -599,6 +704,7 @@ static void shield_remove(struct hid_device *hdev) ts = container_of(dev, struct thunderstrike, base); hid_hw_close(hdev); + led_classdev_unregister(&ts->led_dev); if (ts->haptics_dev) input_unregister_device(ts->haptics_dev); cancel_work_sync(&ts->hostcmd_req_work); -- cgit v1.2.3