From patchwork Tue May 11 20:16:01 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Emmanuel Gil Peyrot X-Patchwork-Id: 434517 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 733DEC433B4 for ; Tue, 11 May 2021 20:16:15 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 3E6566190A for ; Tue, 11 May 2021 20:16:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229871AbhEKURV (ORCPT ); Tue, 11 May 2021 16:17:21 -0400 Received: from 82-65-109-163.subs.proxad.net ([82.65.109.163]:37932 "EHLO luna.linkmauve.fr" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229710AbhEKURU (ORCPT ); Tue, 11 May 2021 16:17:20 -0400 Received: by luna.linkmauve.fr (Postfix, from userid 1000) id 1AB4CF40639; Tue, 11 May 2021 22:16:10 +0200 (CEST) From: Emmanuel Gil Peyrot To: linux-input@vger.kernel.org Cc: Ash Logan , =?utf-8?q?Jonathan_Neusch=C3=A4fer?= , =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= , Jiri Kosina , Benjamin Tissoires , linux-kernel@vger.kernel.org, Emmanuel Gil Peyrot Subject: [PATCH v2 1/4] HID: wiiu-drc: Add a driver for this gamepad Date: Tue, 11 May 2021 22:16:01 +0200 Message-Id: <20210511201604.23204-2-linkmauve@linkmauve.fr> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210511201604.23204-1-linkmauve@linkmauve.fr> References: <20210511201604.23204-1-linkmauve@linkmauve.fr> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org From: Ash Logan This driver is for the DRC (gamepad) of the Wii U when wirelessly connected to the DRH of the console, an internal chip exposing it as a USB device. This first patch exposes the buttons and sticks of this device, so that it can act as a plain game controller. The report format has been described by the libdrc folks at: https://libdrc.org/docs/re/sc-input.html Signed-off-by: Ash Logan Signed-off-by: Emmanuel Gil Peyrot --- drivers/hid/Kconfig | 7 + drivers/hid/Makefile | 1 + drivers/hid/hid-ids.h | 1 + drivers/hid/hid-quirks.c | 3 + drivers/hid/hid-wiiu-drc.c | 281 +++++++++++++++++++++++++++++++++++++ 5 files changed, 293 insertions(+) create mode 100644 drivers/hid/hid-wiiu-drc.c diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 4bf263c2d61a..7681d4614c0a 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1105,6 +1105,13 @@ config HID_WACOM To compile this driver as a module, choose M here: the module will be called wacom. +config HID_WIIU_DRC + tristate "Nintendo Wii U gamepad (DRC) over internal DRH" + depends on HID + help + Support for the Wii U gamepad, when connected with the Wii U’s + internal DRH chip. + config HID_WIIMOTE tristate "Nintendo Wii / Wii U peripherals" depends on HID diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 193431ec4db8..8fcaaeae4d65 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -134,6 +134,7 @@ wacom-objs := wacom_wac.o wacom_sys.o obj-$(CONFIG_HID_WACOM) += wacom.o obj-$(CONFIG_HID_WALTOP) += hid-waltop.o obj-$(CONFIG_HID_WIIMOTE) += hid-wiimote.o +obj-$(CONFIG_HID_WIIU_DRC) += hid-wiiu-drc.o obj-$(CONFIG_HID_SENSOR_HUB) += hid-sensor-hub.o obj-$(CONFIG_HID_SENSOR_CUSTOM_SENSOR) += hid-sensor-custom.o diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 84b8da3e7d09..fbac0dd021f1 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -916,6 +916,7 @@ #define USB_VENDOR_ID_NINTENDO 0x057e #define USB_DEVICE_ID_NINTENDO_WIIMOTE 0x0306 #define USB_DEVICE_ID_NINTENDO_WIIMOTE2 0x0330 +#define USB_DEVICE_ID_NINTENDO_WIIU_DRH 0x0341 #define USB_VENDOR_ID_NOVATEK 0x0603 #define USB_DEVICE_ID_NOVATEK_PCT 0x0600 diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index 3dd6f15f2a67..af400177537e 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -513,6 +513,9 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_WIIMOTE) }, { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_WIIMOTE2) }, #endif +#if IS_ENABLED(CONFIG_HID_WIIU_DRC) + { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_WIIU_DRH) }, +#endif #if IS_ENABLED(CONFIG_HID_NTI) { HID_USB_DEVICE(USB_VENDOR_ID_NTI, USB_DEVICE_ID_USB_SUN) }, #endif diff --git a/drivers/hid/hid-wiiu-drc.c b/drivers/hid/hid-wiiu-drc.c new file mode 100644 index 000000000000..875ccfab7bbf --- /dev/null +++ b/drivers/hid/hid-wiiu-drc.c @@ -0,0 +1,281 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * HID driver for Nintendo Wii U gamepad (DRC), connected via console-internal DRH + * + * Copyright (C) 2021 Emmanuel Gil Peyrot + * Copyright (C) 2019 Ash Logan + * Copyright (C) 2013 Mema Hacking + * + * Based on the excellent work at http://libdrc.org/docs/re/sc-input.html and + * https://bitbucket.org/memahaxx/libdrc/src/master/src/input-receiver.cpp . + * libdrc code is licensed under BSD 2-Clause. + * Driver based on hid-udraw-ps3.c. + */ + +#include +#include +#include +#include +#include +#include "hid-ids.h" + +#define DEVICE_NAME "Nintendo Wii U gamepad (DRC)" + +/* Button and stick constants */ +#define VOLUME_MIN 0 +#define VOLUME_MAX 255 +#define NUM_STICK_AXES 4 +#define STICK_MIN 900 +#define STICK_MAX 3200 + +#define BUTTON_SYNC BIT(0) +#define BUTTON_HOME BIT(1) +#define BUTTON_MINUS BIT(2) +#define BUTTON_PLUS BIT(3) +#define BUTTON_R BIT(4) +#define BUTTON_L BIT(5) +#define BUTTON_ZR BIT(6) +#define BUTTON_ZL BIT(7) +#define BUTTON_DOWN BIT(8) +#define BUTTON_UP BIT(9) +#define BUTTON_RIGHT BIT(10) +#define BUTTON_LEFT BIT(11) +#define BUTTON_Y BIT(12) +#define BUTTON_X BIT(13) +#define BUTTON_B BIT(14) +#define BUTTON_A BIT(15) + +#define BUTTON_TV BIT(21) +#define BUTTON_R3 BIT(22) +#define BUTTON_L3 BIT(23) + +#define BUTTON_POWER BIT(25) + +/* + * The device is setup with multiple input devices: + * - A joypad with the buttons and sticks. + */ + +struct drc { + struct input_dev *joy_input_dev; + struct hid_device *hdev; +}; + +/* + * The format of this report has been reversed by the libdrc project, the + * documentation can be found here: + * https://libdrc.org/docs/re/sc-input.html + * + * We receive this report from USB, but it is actually formed on the DRC, the + * DRH only retransmits it over USB. + */ +static int drc_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int len) +{ + struct drc *drc = hid_get_drvdata(hdev); + int i; + u32 buttons; + + if (len != 128) + return -EINVAL; + + buttons = (data[4] << 24) | (data[80] << 16) | (data[2] << 8) | data[3]; + /* joypad */ + input_report_key(drc->joy_input_dev, BTN_DPAD_RIGHT, buttons & BUTTON_RIGHT); + input_report_key(drc->joy_input_dev, BTN_DPAD_DOWN, buttons & BUTTON_DOWN); + input_report_key(drc->joy_input_dev, BTN_DPAD_LEFT, buttons & BUTTON_LEFT); + input_report_key(drc->joy_input_dev, BTN_DPAD_UP, buttons & BUTTON_UP); + + input_report_key(drc->joy_input_dev, BTN_EAST, buttons & BUTTON_A); + input_report_key(drc->joy_input_dev, BTN_SOUTH, buttons & BUTTON_B); + input_report_key(drc->joy_input_dev, BTN_NORTH, buttons & BUTTON_X); + input_report_key(drc->joy_input_dev, BTN_WEST, buttons & BUTTON_Y); + + input_report_key(drc->joy_input_dev, BTN_TL, buttons & BUTTON_L); + input_report_key(drc->joy_input_dev, BTN_TL2, buttons & BUTTON_ZL); + input_report_key(drc->joy_input_dev, BTN_TR, buttons & BUTTON_R); + input_report_key(drc->joy_input_dev, BTN_TR2, buttons & BUTTON_ZR); + + input_report_key(drc->joy_input_dev, BTN_Z, buttons & BUTTON_TV); + input_report_key(drc->joy_input_dev, BTN_THUMBL, buttons & BUTTON_L3); + input_report_key(drc->joy_input_dev, BTN_THUMBR, buttons & BUTTON_R3); + + input_report_key(drc->joy_input_dev, BTN_SELECT, buttons & BUTTON_MINUS); + input_report_key(drc->joy_input_dev, BTN_START, buttons & BUTTON_PLUS); + input_report_key(drc->joy_input_dev, BTN_MODE, buttons & BUTTON_HOME); + + input_report_key(drc->joy_input_dev, BTN_DEAD, buttons & BUTTON_POWER); + + for (i = 0; i < NUM_STICK_AXES; i++) { + s16 val = (data[7 + 2*i] << 8) | data[6 + 2*i]; + + val = clamp(val, STICK_MIN, STICK_MAX); + + switch (i) { + case 0: + input_report_abs(drc->joy_input_dev, ABS_X, val); + break; + case 1: + input_report_abs(drc->joy_input_dev, ABS_Y, val); + break; + case 2: + input_report_abs(drc->joy_input_dev, ABS_RX, val); + break; + case 3: + input_report_abs(drc->joy_input_dev, ABS_RY, val); + break; + default: + break; + } + } + + input_report_abs(drc->joy_input_dev, ABS_VOLUME, data[14]); + input_sync(drc->joy_input_dev); + + /* let hidraw and hiddev handle the report */ + return 0; +} + +static int drc_open(struct input_dev *dev) +{ + struct drc *drc = input_get_drvdata(dev); + + return hid_hw_open(drc->hdev); +} + +static void drc_close(struct input_dev *dev) +{ + struct drc *drc = input_get_drvdata(dev); + + hid_hw_close(drc->hdev); +} + +static struct input_dev *allocate_and_setup(struct hid_device *hdev, + const char *name) +{ + struct input_dev *input_dev; + + input_dev = devm_input_allocate_device(&hdev->dev); + if (!input_dev) + return NULL; + + input_dev->name = name; + input_dev->phys = hdev->phys; + input_dev->dev.parent = &hdev->dev; + input_dev->open = drc_open; + input_dev->close = drc_close; + input_dev->uniq = hdev->uniq; + input_dev->id.bustype = hdev->bus; + input_dev->id.vendor = hdev->vendor; + input_dev->id.product = hdev->product; + input_dev->id.version = hdev->version; + input_set_drvdata(input_dev, hid_get_drvdata(hdev)); + + return input_dev; +} + +static bool drc_setup_joypad(struct drc *drc, + struct hid_device *hdev) +{ + struct input_dev *input_dev; + + input_dev = allocate_and_setup(hdev, DEVICE_NAME " buttons and sticks"); + if (!input_dev) + return false; + + drc->joy_input_dev = input_dev; + + input_set_capability(input_dev, EV_KEY, BTN_DPAD_RIGHT); + input_set_capability(input_dev, EV_KEY, BTN_DPAD_DOWN); + input_set_capability(input_dev, EV_KEY, BTN_DPAD_LEFT); + input_set_capability(input_dev, EV_KEY, BTN_DPAD_UP); + input_set_capability(input_dev, EV_KEY, BTN_EAST); + input_set_capability(input_dev, EV_KEY, BTN_SOUTH); + input_set_capability(input_dev, EV_KEY, BTN_NORTH); + input_set_capability(input_dev, EV_KEY, BTN_WEST); + input_set_capability(input_dev, EV_KEY, BTN_TL); + input_set_capability(input_dev, EV_KEY, BTN_TL2); + input_set_capability(input_dev, EV_KEY, BTN_TR); + input_set_capability(input_dev, EV_KEY, BTN_TR2); + input_set_capability(input_dev, EV_KEY, BTN_THUMBL); + input_set_capability(input_dev, EV_KEY, BTN_THUMBR); + input_set_capability(input_dev, EV_KEY, BTN_SELECT); + input_set_capability(input_dev, EV_KEY, BTN_START); + input_set_capability(input_dev, EV_KEY, BTN_MODE); + + /* + * These two buttons are actually TV Control and Power. + * + * TV Control draws a line at the bottom of the DRC’s screen saying to + * go into System Settings (on the original proprietary OS), while + * Power will shutdown the DRC when held for four seconds, but those + * two are still normal buttons otherwise. + */ + input_set_capability(input_dev, EV_KEY, BTN_Z); + input_set_capability(input_dev, EV_KEY, BTN_DEAD); + + input_set_abs_params(input_dev, ABS_X, STICK_MIN, STICK_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_Y, STICK_MIN, STICK_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_RX, STICK_MIN, STICK_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_RY, STICK_MIN, STICK_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_VOLUME, VOLUME_MIN, VOLUME_MAX, 0, 0); + + return true; +} + +static int drc_probe(struct hid_device *hdev, const struct hid_device_id *id) +{ + struct drc *drc; + int ret; + + drc = devm_kzalloc(&hdev->dev, sizeof(struct drc), GFP_KERNEL); + if (!drc) + return -ENOMEM; + + drc->hdev = hdev; + + hid_set_drvdata(hdev, drc); + + ret = hid_parse(hdev); + if (ret) { + hid_err(hdev, "parse failed\n"); + return ret; + } + + if (!drc_setup_joypad(drc, hdev)) { + hid_err(hdev, "could not allocate interface\n"); + return -ENOMEM; + } + + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW | HID_CONNECT_DRIVER); + if (ret) { + hid_err(hdev, "hw start failed\n"); + return ret; + } + + ret = input_register_device(drc->joy_input_dev); + if (ret) { + hid_err(hdev, "failed to register interface\n"); + return ret; + } + + return 0; +} + +static const struct hid_device_id drc_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_NINTENDO, USB_DEVICE_ID_NINTENDO_WIIU_DRH) }, + { } +}; +MODULE_DEVICE_TABLE(hid, drc_devices); + +static struct hid_driver drc_driver = { + .name = "hid-wiiu-drc", + .id_table = drc_devices, + .raw_event = drc_raw_event, + .probe = drc_probe, +}; +module_hid_driver(drc_driver); + +MODULE_AUTHOR("Emmanuel Gil Peyrot "); +MODULE_DESCRIPTION("Nintendo Wii U gamepad (DRC) driver"); +MODULE_LICENSE("GPL"); From patchwork Tue May 11 20:16:02 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Emmanuel Gil Peyrot X-Patchwork-Id: 435942 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 0941AC43460 for ; Tue, 11 May 2021 20:16:16 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id CE96C6190A for ; Tue, 11 May 2021 20:16:15 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229952AbhEKURV (ORCPT ); Tue, 11 May 2021 16:17:21 -0400 Received: from 82-65-109-163.subs.proxad.net ([82.65.109.163]:37942 "EHLO luna.linkmauve.fr" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229809AbhEKURV (ORCPT ); Tue, 11 May 2021 16:17:21 -0400 Received: by luna.linkmauve.fr (Postfix, from userid 1000) id BDD21F4063B; Tue, 11 May 2021 22:16:11 +0200 (CEST) From: Emmanuel Gil Peyrot To: linux-input@vger.kernel.org Cc: Emmanuel Gil Peyrot , Ash Logan , =?utf-8?q?Jonathan_Neusch=C3=A4fer?= , =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= , Jiri Kosina , Benjamin Tissoires , linux-kernel@vger.kernel.org Subject: [PATCH v2 2/4] HID: wiiu-drc: Implement touch reports Date: Tue, 11 May 2021 22:16:02 +0200 Message-Id: <20210511201604.23204-3-linkmauve@linkmauve.fr> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210511201604.23204-1-linkmauve@linkmauve.fr> References: <20210511201604.23204-1-linkmauve@linkmauve.fr> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org There is an inaccessible border on each side, 100 units on the left and right sides, and 200 units at the top and bottom. The Y axis is inverted too, these are the two main quirks of this touch panel. I’ve been testing with weston-simple-touch mostly, but it also with the rest of Weston and it aligns perfectly without the need of any additional calibration. Signed-off-by: Ash Logan Signed-off-by: Emmanuel Gil Peyrot --- drivers/hid/hid-wiiu-drc.c | 86 +++++++++++++++++++++++++++++++++++--- 1 file changed, 81 insertions(+), 5 deletions(-) diff --git a/drivers/hid/hid-wiiu-drc.c b/drivers/hid/hid-wiiu-drc.c index 875ccfab7bbf..33c02d3494aa 100644 --- a/drivers/hid/hid-wiiu-drc.c +++ b/drivers/hid/hid-wiiu-drc.c @@ -51,13 +51,27 @@ #define BUTTON_POWER BIT(25) +/* Touch constants */ +/* Resolution in pixels */ +#define RES_X 854 +#define RES_Y 480 +/* Display/touch size in mm */ +#define WIDTH 138 +#define HEIGHT 79 +#define NUM_TOUCH_POINTS 10 +#define MAX_TOUCH_RES (1 << 12) +#define TOUCH_BORDER_X 100 +#define TOUCH_BORDER_Y 200 + /* * The device is setup with multiple input devices: * - A joypad with the buttons and sticks. + * - The touch area which works as a touchscreen. */ struct drc { struct input_dev *joy_input_dev; + struct input_dev *touch_input_dev; struct hid_device *hdev; }; @@ -73,7 +87,7 @@ static int drc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int len) { struct drc *drc = hid_get_drvdata(hdev); - int i; + int i, x, y, pressure, base; u32 buttons; if (len != 128) @@ -132,6 +146,42 @@ static int drc_raw_event(struct hid_device *hdev, struct hid_report *report, input_report_abs(drc->joy_input_dev, ABS_VOLUME, data[14]); input_sync(drc->joy_input_dev); + /* touch */ + /* + * Average touch points for improved accuracy. Sadly these are always + * reported extremely close from each other… Even when the user + * pressed two (or more) different points, all ten values will be + * approximately in the middle of the pressure points. + */ + x = y = 0; + for (i = 0; i < NUM_TOUCH_POINTS; i++) { + base = 36 + 4 * i; + + x += ((data[base + 1] & 0xF) << 8) | data[base]; + y += ((data[base + 3] & 0xF) << 8) | data[base + 2]; + } + x /= NUM_TOUCH_POINTS; + y /= NUM_TOUCH_POINTS; + + /* Pressure reporting isn’t properly understood, so we don’t report it yet. */ + pressure = 0; + pressure |= ((data[37] >> 4) & 7) << 0; + pressure |= ((data[39] >> 4) & 7) << 3; + pressure |= ((data[41] >> 4) & 7) << 6; + pressure |= ((data[43] >> 4) & 7) << 9; + + if (pressure != 0) { + input_report_key(drc->touch_input_dev, BTN_TOUCH, 1); + input_report_key(drc->touch_input_dev, BTN_TOOL_FINGER, 1); + + input_report_abs(drc->touch_input_dev, ABS_X, x); + input_report_abs(drc->touch_input_dev, ABS_Y, MAX_TOUCH_RES - y); + } else { + input_report_key(drc->touch_input_dev, BTN_TOUCH, 0); + input_report_key(drc->touch_input_dev, BTN_TOOL_FINGER, 0); + } + input_sync(drc->touch_input_dev); + /* let hidraw and hiddev handle the report */ return 0; } @@ -223,6 +273,30 @@ static bool drc_setup_joypad(struct drc *drc, return true; } +static bool drc_setup_touch(struct drc *drc, + struct hid_device *hdev) +{ + struct input_dev *input_dev; + + input_dev = allocate_and_setup(hdev, DEVICE_NAME " touchscreen"); + if (!input_dev) + return false; + + drc->touch_input_dev = input_dev; + + set_bit(INPUT_PROP_DIRECT, input_dev->propbit); + + input_set_abs_params(input_dev, ABS_X, TOUCH_BORDER_X, MAX_TOUCH_RES - TOUCH_BORDER_X, 20, 0); + input_abs_set_res(input_dev, ABS_X, RES_X / WIDTH); + input_set_abs_params(input_dev, ABS_Y, TOUCH_BORDER_Y, MAX_TOUCH_RES - TOUCH_BORDER_Y, 20, 0); + input_abs_set_res(input_dev, ABS_Y, RES_Y / HEIGHT); + + input_set_capability(input_dev, EV_KEY, BTN_TOUCH); + input_set_capability(input_dev, EV_KEY, BTN_TOOL_FINGER); + + return true; +} + static int drc_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct drc *drc; @@ -242,8 +316,9 @@ static int drc_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } - if (!drc_setup_joypad(drc, hdev)) { - hid_err(hdev, "could not allocate interface\n"); + if (!drc_setup_joypad(drc, hdev) || + !drc_setup_touch(drc, hdev)) { + hid_err(hdev, "could not allocate interfaces\n"); return -ENOMEM; } @@ -253,9 +328,10 @@ static int drc_probe(struct hid_device *hdev, const struct hid_device_id *id) return ret; } - ret = input_register_device(drc->joy_input_dev); + ret = input_register_device(drc->joy_input_dev) || + input_register_device(drc->touch_input_dev); if (ret) { - hid_err(hdev, "failed to register interface\n"); + hid_err(hdev, "failed to register interfaces\n"); return ret; } From patchwork Tue May 11 20:16:03 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Emmanuel Gil Peyrot X-Patchwork-Id: 434516 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id A31B3C43461 for ; Tue, 11 May 2021 20:16:18 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id 65B2561879 for ; Tue, 11 May 2021 20:16:18 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230002AbhEKURX (ORCPT ); Tue, 11 May 2021 16:17:23 -0400 Received: from 82-65-109-163.subs.proxad.net ([82.65.109.163]:37954 "EHLO luna.linkmauve.fr" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229917AbhEKURW (ORCPT ); Tue, 11 May 2021 16:17:22 -0400 Received: by luna.linkmauve.fr (Postfix, from userid 1000) id 7E6F1F4063C; Tue, 11 May 2021 22:16:13 +0200 (CEST) From: Emmanuel Gil Peyrot To: linux-input@vger.kernel.org Cc: Emmanuel Gil Peyrot , Ash Logan , =?utf-8?q?Jonathan_Neusch=C3=A4fer?= , =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= , Jiri Kosina , Benjamin Tissoires , linux-kernel@vger.kernel.org Subject: [PATCH v2 3/4] HID: wiiu-drc: Add accelerometer, gyroscope and magnetometer readings Date: Tue, 11 May 2021 22:16:03 +0200 Message-Id: <20210511201604.23204-4-linkmauve@linkmauve.fr> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210511201604.23204-1-linkmauve@linkmauve.fr> References: <20210511201604.23204-1-linkmauve@linkmauve.fr> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org These are mostly untested so far, because I have no idea which userland to test against, but evtest at least seems to give sensible values. The magnetometer doesn’t have dedicated INPUT_PROP_ACCELEROMETER buttons, so I used three clearly invalid absolute values, in the hope that someone will fix that eventually. Another solution might be to go for the iio subsystem instead, but then it wouldn’t be tied to the HID any longer and I would feel uneasy about that. Especially because multiple such gamepads could be connected to a single computer. Signed-off-by: Ash Logan Signed-off-by: Emmanuel Gil Peyrot --- drivers/hid/hid-wiiu-drc.c | 77 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/drivers/hid/hid-wiiu-drc.c b/drivers/hid/hid-wiiu-drc.c index 33c02d3494aa..a631d405119e 100644 --- a/drivers/hid/hid-wiiu-drc.c +++ b/drivers/hid/hid-wiiu-drc.c @@ -63,15 +63,25 @@ #define TOUCH_BORDER_X 100 #define TOUCH_BORDER_Y 200 +/* Accelerometer, gyroscope and magnetometer constants */ +#define ACCEL_MIN -(1 << 15) +#define ACCEL_MAX ((1 << 15) - 1) +#define GYRO_MIN -(1 << 23) +#define GYRO_MAX ((1 << 23) - 1) +#define MAGNET_MIN -(1 << 15) +#define MAGNET_MAX ((1 << 15) - 1) + /* * The device is setup with multiple input devices: * - A joypad with the buttons and sticks. * - The touch area which works as a touchscreen. + * - An accelerometer + gyroscope + magnetometer device. */ struct drc { struct input_dev *joy_input_dev; struct input_dev *touch_input_dev; + struct input_dev *accel_input_dev; struct hid_device *hdev; }; @@ -87,7 +97,7 @@ static int drc_raw_event(struct hid_device *hdev, struct hid_report *report, u8 *data, int len) { struct drc *drc = hid_get_drvdata(hdev); - int i, x, y, pressure, base; + int i, x, y, z, pressure, base; u32 buttons; if (len != 128) @@ -182,6 +192,31 @@ static int drc_raw_event(struct hid_device *hdev, struct hid_report *report, } input_sync(drc->touch_input_dev); + /* accelerometer */ + x = (data[16] << 8) | data[15]; + y = (data[18] << 8) | data[17]; + z = (data[20] << 8) | data[19]; + input_report_abs(drc->accel_input_dev, ABS_X, (int16_t)x); + input_report_abs(drc->accel_input_dev, ABS_Y, (int16_t)y); + input_report_abs(drc->accel_input_dev, ABS_Z, (int16_t)z); + + /* gyroscope */ + x = (data[23] << 24) | (data[22] << 16) | (data[21] << 8); + y = (data[26] << 24) | (data[25] << 16) | (data[24] << 8); + z = (data[29] << 24) | (data[28] << 16) | (data[27] << 8); + input_report_abs(drc->accel_input_dev, ABS_RX, x >> 8); + input_report_abs(drc->accel_input_dev, ABS_RY, y >> 8); + input_report_abs(drc->accel_input_dev, ABS_RZ, z >> 8); + + /* magnetometer */ + x = (data[31] << 8) | data[30]; + y = (data[33] << 8) | data[32]; + z = (data[35] << 8) | data[34]; + input_report_abs(drc->accel_input_dev, ABS_THROTTLE, (int16_t)x); + input_report_abs(drc->accel_input_dev, ABS_RUDDER, (int16_t)y); + input_report_abs(drc->accel_input_dev, ABS_WHEEL, (int16_t)z); + input_sync(drc->accel_input_dev); + /* let hidraw and hiddev handle the report */ return 0; } @@ -297,6 +332,40 @@ static bool drc_setup_touch(struct drc *drc, return true; } +static bool drc_setup_accel(struct drc *drc, + struct hid_device *hdev) +{ + struct input_dev *input_dev; + + input_dev = allocate_and_setup(hdev, DEVICE_NAME " accelerometer, gyroscope and magnetometer"); + if (!input_dev) + return false; + + drc->accel_input_dev = input_dev; + + set_bit(INPUT_PROP_ACCELEROMETER, input_dev->propbit); + + /* 1G accel is reported as about -7900 */ + input_set_abs_params(input_dev, ABS_X, ACCEL_MIN, ACCEL_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_Y, ACCEL_MIN, ACCEL_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_Z, ACCEL_MIN, ACCEL_MAX, 0, 0); + + /* gyroscope */ + input_set_abs_params(input_dev, ABS_RX, GYRO_MIN, GYRO_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_RY, GYRO_MIN, GYRO_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_RZ, GYRO_MIN, GYRO_MAX, 0, 0); + + /* magnetometer */ + /* TODO: Figure out which ABS_* would make more sense to expose, or + * maybe go for the iio subsystem? + */ + input_set_abs_params(input_dev, ABS_THROTTLE, MAGNET_MIN, MAGNET_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_RUDDER, MAGNET_MIN, MAGNET_MAX, 0, 0); + input_set_abs_params(input_dev, ABS_WHEEL, MAGNET_MIN, MAGNET_MAX, 0, 0); + + return true; +} + static int drc_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct drc *drc; @@ -317,7 +386,8 @@ static int drc_probe(struct hid_device *hdev, const struct hid_device_id *id) } if (!drc_setup_joypad(drc, hdev) || - !drc_setup_touch(drc, hdev)) { + !drc_setup_touch(drc, hdev) || + !drc_setup_accel(drc, hdev)) { hid_err(hdev, "could not allocate interfaces\n"); return -ENOMEM; } @@ -329,7 +399,8 @@ static int drc_probe(struct hid_device *hdev, const struct hid_device_id *id) } ret = input_register_device(drc->joy_input_dev) || - input_register_device(drc->touch_input_dev); + input_register_device(drc->touch_input_dev) || + input_register_device(drc->accel_input_dev); if (ret) { hid_err(hdev, "failed to register interfaces\n"); return ret; From patchwork Tue May 11 20:16:04 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Emmanuel Gil Peyrot X-Patchwork-Id: 434515 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-16.8 required=3.0 tests=BAYES_00, HEADER_FROM_DIFFERENT_DOMAINS,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI, SPF_HELO_NONE, SPF_PASS, URIBL_BLOCKED, USER_AGENT_GIT autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id EFC61C433B4 for ; Tue, 11 May 2021 20:16:24 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by mail.kernel.org (Postfix) with ESMTP id C12416187E for ; Tue, 11 May 2021 20:16:24 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229548AbhEKUR3 (ORCPT ); Tue, 11 May 2021 16:17:29 -0400 Received: from 82-65-109-163.subs.proxad.net ([82.65.109.163]:37966 "EHLO luna.linkmauve.fr" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229984AbhEKURX (ORCPT ); Tue, 11 May 2021 16:17:23 -0400 Received: by luna.linkmauve.fr (Postfix, from userid 1000) id D7217F4063D; Tue, 11 May 2021 22:16:14 +0200 (CEST) From: Emmanuel Gil Peyrot To: linux-input@vger.kernel.org Cc: Emmanuel Gil Peyrot , Ash Logan , =?utf-8?q?Jonathan_Neusch=C3=A4fer?= , =?utf-8?q?Barnab=C3=A1s_P=C5=91cze?= , Jiri Kosina , Benjamin Tissoires , linux-kernel@vger.kernel.org Subject: [PATCH v2 4/4] HID: wiiu-drc: Add battery reporting Date: Tue, 11 May 2021 22:16:04 +0200 Message-Id: <20210511201604.23204-5-linkmauve@linkmauve.fr> X-Mailer: git-send-email 2.31.1 In-Reply-To: <20210511201604.23204-1-linkmauve@linkmauve.fr> References: <20210511201604.23204-1-linkmauve@linkmauve.fr> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org On my DRC the values only go between 142 (battery LED blinking red before shutdown) and 178 (charge LED stopping), it seems to be the same on other units according to other testers. This might be the raw voltage value as reported by an ADC, so adding a linear interpolation between two common battery voltage values. A spinlock is used to avoid the battery level and status from being reported unsynchronised. Signed-off-by: Emmanuel Gil Peyrot --- drivers/hid/hid-wiiu-drc.c | 123 +++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) diff --git a/drivers/hid/hid-wiiu-drc.c b/drivers/hid/hid-wiiu-drc.c index a631d405119e..c1c883641b67 100644 --- a/drivers/hid/hid-wiiu-drc.c +++ b/drivers/hid/hid-wiiu-drc.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include "hid-ids.h" #define DEVICE_NAME "Nintendo Wii U gamepad (DRC)" @@ -71,6 +73,13 @@ #define MAGNET_MIN -(1 << 15) #define MAGNET_MAX ((1 << 15) - 1) +/* ADC constants for the battery */ +#define BATTERY_CHARGING_BIT BIT(6) +#define BATTERY_MIN 142 +#define BATTERY_MAX 178 +#define VOLTAGE_MIN 3270000 +#define VOLTAGE_MAX 4100000 + /* * The device is setup with multiple input devices: * - A joypad with the buttons and sticks. @@ -83,6 +92,12 @@ struct drc { struct input_dev *touch_input_dev; struct input_dev *accel_input_dev; struct hid_device *hdev; + + struct power_supply *battery; + struct power_supply_desc battery_desc; + spinlock_t battery_lock; + u8 battery_energy; + int battery_status; }; /* @@ -99,6 +114,7 @@ static int drc_raw_event(struct hid_device *hdev, struct hid_report *report, struct drc *drc = hid_get_drvdata(hdev); int i, x, y, z, pressure, base; u32 buttons; + unsigned long flags; if (len != 128) return -EINVAL; @@ -217,6 +233,17 @@ static int drc_raw_event(struct hid_device *hdev, struct hid_report *report, input_report_abs(drc->accel_input_dev, ABS_WHEEL, (int16_t)z); input_sync(drc->accel_input_dev); + /* battery */ + spin_lock_irqsave(&drc->battery_lock, flags); + drc->battery_energy = data[5]; + if (drc->battery_energy == BATTERY_MAX) + drc->battery_status = POWER_SUPPLY_STATUS_FULL; + else if (data[4] & BATTERY_CHARGING_BIT) + drc->battery_status = POWER_SUPPLY_STATUS_CHARGING; + else + drc->battery_status = POWER_SUPPLY_STATUS_DISCHARGING; + spin_unlock_irqrestore(&drc->battery_lock, flags); + /* let hidraw and hiddev handle the report */ return 0; } @@ -366,6 +393,96 @@ static bool drc_setup_accel(struct drc *drc, return true; } +static enum power_supply_property drc_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_SCOPE, +}; + +static int drc_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct drc *drc = power_supply_get_drvdata(psy); + unsigned long flags; + int ret = 0; + u8 battery_energy; + int battery_status; + + spin_lock_irqsave(&drc->battery_lock, flags); + battery_energy = drc->battery_energy; + battery_status = drc->battery_status; + spin_unlock_irqrestore(&drc->battery_lock, flags); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = battery_status; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = 1; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = VOLTAGE_MAX; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + val->intval = VOLTAGE_MIN; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = fixp_linear_interpolate(BATTERY_MIN, VOLTAGE_MIN, + BATTERY_MAX, VOLTAGE_MAX, + battery_energy); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = fixp_linear_interpolate(BATTERY_MIN, 0, + BATTERY_MAX, 100, + battery_energy); + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_DEVICE; + break; + default: + ret = -EINVAL; + break; + } + return ret; +} + +static int drc_setup_battery(struct drc *drc, + struct hid_device *hdev) +{ + struct power_supply_config psy_cfg = { .drv_data = drc, }; + static atomic_t drc_num = ATOMIC_INIT(0); + int ret; + + spin_lock_init(&drc->battery_lock); + + drc->battery_desc.properties = drc_battery_props; + drc->battery_desc.num_properties = ARRAY_SIZE(drc_battery_props); + drc->battery_desc.get_property = drc_battery_get_property; + drc->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY; + drc->battery_desc.use_for_apm = 0; + + drc->battery_desc.name = devm_kasprintf(&hdev->dev, GFP_KERNEL, + "wiiu-drc-%i-battery", atomic_fetch_inc(&drc_num)); + if (!drc->battery_desc.name) + return -ENOMEM; + + drc->battery = devm_power_supply_register(&hdev->dev, &drc->battery_desc, &psy_cfg); + if (IS_ERR(drc->battery)) { + ret = PTR_ERR(drc->battery); + hid_err(hdev, "Unable to register battery device\n"); + return ret; + } + + power_supply_powers(drc->battery, &hdev->dev); + + return 0; +} + static int drc_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct drc *drc; @@ -392,6 +509,12 @@ static int drc_probe(struct hid_device *hdev, const struct hid_device_id *id) return -ENOMEM; } + ret = drc_setup_battery(drc, hdev); + if (ret) { + hid_err(hdev, "could not allocate battery interface\n"); + return ret; + } + ret = hid_hw_start(hdev, HID_CONNECT_HIDRAW | HID_CONNECT_DRIVER); if (ret) { hid_err(hdev, "hw start failed\n");