From patchwork Fri Feb 10 03:44:26 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Aditya Garg X-Patchwork-Id: 55210 Return-Path: Delivered-To: ouuuleilei@gmail.com Received: by 2002:adf:eb09:0:0:0:0:0 with SMTP id s9csp736897wrn; Thu, 9 Feb 2023 19:56:57 -0800 (PST) X-Google-Smtp-Source: AK7set/E7gKlKrhRUwmZaFxrmpOWj1rzs1H2LD/IXMhYsaTgTRUjFYn+7ED0x1Hn9kz3v0wkEoNF X-Received: by 2002:a17:907:20ae:b0:87b:59d9:5a03 with SMTP id pw14-20020a17090720ae00b0087b59d95a03mr14510779ejb.36.1676001417792; Thu, 09 Feb 2023 19:56:57 -0800 (PST) ARC-Seal: i=2; a=rsa-sha256; t=1676001417; cv=pass; d=google.com; s=arc-20160816; b=KKlHE+J3kQ2dyqxcaj5G34pDKw43Dwnc9uNISZTZrtppSLKj1yekkXl47/HcZSEmgp HOkwaq0sgw3Xemr6UL3Foxai6FU5DJodBAHu8si0w3g1oSVLcSWFVvVeZzBvSbFePC5x 60wLdj2ocsKQgjzN6tMVYn5FWHC9bFQm9rHTc7qsWWTCkJhBt329VsP4gS5ihbhzJ7qR kbkagsN0WqAwD7EW+NNgWnU9QHTnCb2MhOgomm2pUz6dtJGSBsC7CC5TKPXHLA+8Cjgz 0j6EdbNXTsBEDnGpoX7sp/oKJw22ZhSYw60zXM8foVr4A7RODKC+wfxhG65Gtnijd01z 5f/g== ARC-Message-Signature: i=2; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:mime-version:content-transfer-encoding :content-id:content-language:accept-language:in-reply-to:references :message-id:date:thread-index:thread-topic:subject:cc:to:from :dkim-signature; bh=HpVyLms6/HzgQmXROEJbE/HvIMbEFWoSNJ1DDcly0oE=; b=KGNt6nKTi4hS2PFF+ehACP7A7XwDKzJqs5rGBcNvRBQtSRmewszjYlHgrQiqDy0ttG 06udfUEqXWJQEmGfDDfk9K4IXe8NVvbsC+8BjUk3ddPJaPWj224sHCM1o1ITze6S08ln f2tebNFk+7qxecsXOwbjc+lb5vRHFph9fGTybM0KZ18OQwEtDcE+qvrXmSCFXNbg2ysO mCB9jIbUYDSpMmG0pddJnVxN2uBehRlNa/NrwEEENiwO9wO9yCUZGLWbNTsBVr8h7Tc4 Zi2Fvb4DQ59ursp4HslcrZ3OH2cMvDuX/+Y7sWKgXzh1TxRNIhaSm4iXyl7VVKAsviA8 2WFg== ARC-Authentication-Results: i=2; mx.google.com; dkim=pass header.i=@live.com header.s=selector1 header.b=PoxazQSe; arc=pass (i=1); spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=live.com Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id fu29-20020a170907b01d00b0088e5d80685fsi3652083ejc.429.2023.02.09.19.56.32; Thu, 09 Feb 2023 19:56:57 -0800 (PST) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; dkim=pass header.i=@live.com header.s=selector1 header.b=PoxazQSe; arc=pass (i=1); spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=pass (p=NONE sp=QUARANTINE dis=NONE) header.from=live.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230481AbjBJDor (ORCPT + 99 others); Thu, 9 Feb 2023 22:44:47 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:40898 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229869AbjBJDop (ORCPT ); Thu, 9 Feb 2023 22:44:45 -0500 Received: from NAM12-MW2-obe.outbound.protection.outlook.com (mail-mw2nam12olkn2080.outbound.protection.outlook.com [40.92.23.80]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 8368074311; Thu, 9 Feb 2023 19:44:31 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; s=arcselector9901; d=microsoft.com; cv=none; b=i9FpmwJ1xQQcP+waZPZT9DVYFqtru+MGDSAvk1XlvaoR4Y0Bd+yDHPTNt1AZkcAXYxMJyP1AynNSZhP3U5jXhT+5wuGQAxMEiNeKUeWdjPqOiLVOoumYxBNsyANxOBKSHHQj4SiEki0lEBk4L5Lype3I6pJHf7suv2BEnmeW/SrTJ8BDAEzRfI+SzlhbF6Sdtlaz6aRe8fLADJZedmfTr+iGb0cwCqkjGF9eNJydq4+dORNl2AfVYpahaR/c0WZBF4Ch80osvin16poA3yKvOCYAtaqTxSKnTjHzH9l1wL+8ptv0Tq0X3us8T7zmV9LrvU81z1ypCpp6jwSGc013qw== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=microsoft.com; s=arcselector9901; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-AntiSpam-MessageData-ChunkCount:X-MS-Exchange-AntiSpam-MessageData-0:X-MS-Exchange-AntiSpam-MessageData-1; bh=HpVyLms6/HzgQmXROEJbE/HvIMbEFWoSNJ1DDcly0oE=; b=do0nunavctCP4Mk7Ivgpp9yzeAE0lXkuWL0uZM7UDIf+j6FP8q2gKNWeLuonzDj0SwhO4sC6/8+XmsVP8/zHEZcSouegTEbDCNIfiJJ7U0wnoKL8Dr2JjWy8DjzU3K80fxSPMcA2oq7V/RB97jBK23eLZllcSCj6JGtTqeQ6PS3eT3u/Ux3xwKcV1gRa5uVV3KiIsKzJ71wYQmCcbNqicvfD/DKc5CIMi01NJB9N64TF0soJmVfH24mpDn4bizfV+YNOP/90n36VHZAIKsGZW4wRuXIubpPlOGUSUQA6slQxjb+rrJK06CNDeIm5rBB6TTvBwzoHYDvF2O/pzLii+g== ARC-Authentication-Results: i=1; mx.microsoft.com 1; spf=none; dmarc=none; dkim=none; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=live.com; s=selector1; h=From:Date:Subject:Message-ID:Content-Type:MIME-Version:X-MS-Exchange-SenderADCheck; bh=HpVyLms6/HzgQmXROEJbE/HvIMbEFWoSNJ1DDcly0oE=; b=PoxazQSe5Ra+yzh0M54LpupLhvAvf7UaG4pBor6svSMCXE4tn80PigyHgZ3vlh34pRRl4LFfUFsDw63C0S+62hmJeWZcPeFas9lm6MTEPgShbCcnZ7cYcczLCHrS8xoqMcDrXZ+6Yn1E7JiSdWKn81PyXyZ1mUJB4DAyH4b1t5chANyGkU+XjtRY/3LEB/sprLGnGKKbGFKT6X7pbhsABJy4Zr4syBbMtuskeqYR2gfBq4CfO/MkDKJme/iMdnYKD6pYnjCi0QXUWSsZLgFlavrclT08qPjxLhXJfl2Y20gRQKTdrdEzpqycbG+u+Jbbzty5PQTRkDAupXpuBgCrzw== Received: from BM1PR01MB0931.INDPRD01.PROD.OUTLOOK.COM (2603:1096:b00:2::9) by MAZPR01MB5554.INDPRD01.PROD.OUTLOOK.COM (2603:1096:a01:61::6) with Microsoft SMTP Server (version=TLS1_2, cipher=TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384) id 15.20.6086.19; Fri, 10 Feb 2023 03:44:26 +0000 Received: from BM1PR01MB0931.INDPRD01.PROD.OUTLOOK.COM ([fe80::9dc7:9298:c988:474d]) by BM1PR01MB0931.INDPRD01.PROD.OUTLOOK.COM ([fe80::9dc7:9298:c988:474d%11]) with mapi id 15.20.6111.005; Fri, 10 Feb 2023 03:44:26 +0000 From: Aditya Garg To: Jiri Kosina , "jkosina@suse.cz" , "benjamin.tissoires@redhat.com" , Andy Shevchenko , "andy.shevchenko@gmail.com" CC: LKML , "linux-input@vger.kernel.org" , "ronald@innovation.ch" , "kekrby@gmail.com" , Orlando Chamberlain Subject: [PATCH 2/3] HID: apple-touchbar: Add driver for the Touch Bar on MacBook Pros Thread-Topic: [PATCH 2/3] HID: apple-touchbar: Add driver for the Touch Bar on MacBook Pros Thread-Index: AQHZPQH444pcUE9Pn0ybBKOwkJjnOQ== Date: Fri, 10 Feb 2023 03:44:26 +0000 Message-ID: <868AA58D-2399-4E4A-A6C6-73F88DB13992@live.com> References: <40274C3D-4F4F-479C-944C-EEBDC78F959C@live.com> In-Reply-To: <40274C3D-4F4F-479C-944C-EEBDC78F959C@live.com> Accept-Language: en-US Content-Language: en-US X-MS-Has-Attach: X-MS-TNEF-Correlator: x-ms-exchange-messagesentrepresentingtype: 1 x-tmn: [H1dRgnGmS0FAf3TqD/55ScCs8Ckto2D4] x-ms-publictraffictype: Email x-ms-traffictypediagnostic: BM1PR01MB0931:EE_|MAZPR01MB5554:EE_ x-ms-office365-filtering-correlation-id: 5bea2c93-d060-4217-aece-08db0b191b5a x-microsoft-antispam: BCL:0; x-microsoft-antispam-message-info: iYHLjxKb9zcvbyJH2yVGuN9IpSblVP7yYPvaPg2QjJ4YfROmMzCcNeIFTHjSHt/I9W/40sZ/ey9N0+tRYcCV5ItPweiYEbLcy4t2kSQ85ix3V02fO44bbj+LTq3kRZvOd65m7dkDHWUwuk0pRRoJ/+1JTqxCE0QhHxDrW5ZBxtw9N6PCLabdR88hdGTiFZGd6an4K9530o5KFrtbCyWoMj0mjZIIPLWuX/l2hPSe7wY85IfnIWofeAhOa2HGxF+9b5JpC1N2osxQfvzT5I+0xzLA00rSKFI0oqLGApwTo5B/vAM/of+EHQAbzHJx0HE4Kdpttp9oZ4LPAXLT0Ta1Zepc6esQ2MuijMoI91M5lFsAtulf24+mJ995GnimqVgX9N5aJpDiSgoaHnwyrnSq4id9xX9tV5ezgn3WCp2T3zlA0TydjKN60q3N0iroblbJIrSiKxAhcERCfEAKd3p+H4vyGcqzoqYa66RUfpfdpWm6rUdGtL5pH1b5MtOTKyIZnIi0BSuRASbZjqctixooTVlE4qjaVmrI4BU0cRb0CAy+gExrO/KxoLv0z6soaumMLaUBjFx5gtsULq81GF5sUBiMt41jlcuBkA3Gm1846EM= x-ms-exchange-antispam-messagedata-chunkcount: 1 x-ms-exchange-antispam-messagedata-0: =?utf-8?q?VmEpcBs2/i7UgyUzNLQ9XKHD2MtA?= =?utf-8?q?brwnJjJdVqZQd6aE6/QGrXkjbJk8niDPbxNwki/X0bvuE9ykUCN5sawnz1d/vCFCN?= =?utf-8?q?u6gIbla44UuDxcrq6EmzaEc7dftdzQGyHFz/oZwdNTbqQNG82NKksWxkCBGKOGu+b?= =?utf-8?q?7LR7rTgsJ6IJT+SJXoSFJ9uxkWTAs6eVhFEFa+RyPSH1XgvlJF+afVlidPdDckfYk?= =?utf-8?q?HgDb++o08fAW8NelAkSWSSnbTaUuvhGkojC4M30zaIfFnzymFtdKMQky3AnKQgQpu?= =?utf-8?q?Q0gZXHzW9h52zjUPQX0MJVlM2oNqzmCfy4NGjv3Iq7r878cjd8FWVSYYV9IbTubES?= =?utf-8?q?C4V5VnA3sQvEVPv1gMBbIobhYGMDZWiDevjaiAhCt473X7oj+52eFApDEO2IUbcGB?= =?utf-8?q?OCVTTWqqgiPVIYS+XkU2/9rZM2exTRyBHekbDPEE6R00IKumrP/bxTIhlnkvXxyEF?= =?utf-8?q?wD92dsnOwSH9/J7tGyCHSeA0SqS1ewSdgRMhXN710gSHmoTtbabsoEYms2NdZ9PFO?= =?utf-8?q?C/P34qnOR31g/XwnhC9tD8CVmjV+lHup8wAFJ3bn2puC9fpzsCXzrSSopNbRxt5z8?= =?utf-8?q?gjMh/erFrnUw3ePdnzHO7ADn0TZHpYp8fo58S2Y0M7fwQhz9bqX3PUfJA/37W+nhU?= =?utf-8?q?VlYMOKVrW8Q8i1noSK4SENLEDlplOAvBOb+a0dhhtTSbhXAt0Kt2S+DxK8GVDNhOh?= =?utf-8?q?/OMKCog6zs1713dqyP+W+Upn/kjoMLhvC9hfhx0OUkyTBLkuKcgiHoYnNg3Ob41p9?= =?utf-8?q?uDu08f7sxYrOAQ25+k/RJWgaxVRA6HyV+sCaxupqYMqjhnjhQdub33aaary2iqADn?= =?utf-8?q?WWP40ltrlj8gR1Nzsv74CLEtPv44f7shlcLHVzfXbI4JEpg5hFMIcO7uPiWwRp683?= =?utf-8?q?WlPUc4YLT+1uso2mC1zgTJSBiF1F1NN4KrGVPuPbspBYQRXAf0DYKz/ydJQoCS9Cn?= =?utf-8?q?wEVr+h4szKCtzXw/3eR9QwqBOl05I5vPYDDhZvTWOzXBbPydqOzCAriPprGxuIKdI?= =?utf-8?q?vxf+3E0ARgq7jOs5alvRb7jEZyepJ6iBE3Hkh0OT7mPzPO9Z4R9//d84tj8/IXcNJ?= =?utf-8?q?sYaIb/8q9fnYL7jKwyTdHecvoVOZ60Z2Ja7HRk8UadncS5ufbAK2Ho+etFmc573JM?= =?utf-8?q?nLGhbJFuUU5x54wcDzRNh2PsgsjF8qx+3aAey0kehsbXJgyCr1LzreBWWUI44VugB?= =?utf-8?q?7XAUjqU8Tzz0oNSXA9mBpkVamgVyn7cmxzGming=3D=3D?= Content-ID: <30A691B92BE79F4F91459B2DFBF374FC@INDPRD01.PROD.OUTLOOK.COM> MIME-Version: 1.0 X-OriginatorOrg: sct-15-20-4755-11-msonline-outlook-42ed3.templateTenant X-MS-Exchange-CrossTenant-AuthAs: Internal X-MS-Exchange-CrossTenant-AuthSource: BM1PR01MB0931.INDPRD01.PROD.OUTLOOK.COM X-MS-Exchange-CrossTenant-RMS-PersistedConsumerOrg: 00000000-0000-0000-0000-000000000000 X-MS-Exchange-CrossTenant-Network-Message-Id: 5bea2c93-d060-4217-aece-08db0b191b5a X-MS-Exchange-CrossTenant-originalarrivaltime: 10 Feb 2023 03:44:26.3760 (UTC) X-MS-Exchange-CrossTenant-fromentityheader: Hosted X-MS-Exchange-CrossTenant-id: 84df9e7f-e9f6-40af-b435-aaaaaaaaaaaa X-MS-Exchange-CrossTenant-rms-persistedconsumerorg: 00000000-0000-0000-0000-000000000000 X-MS-Exchange-Transport-CrossTenantHeadersStamped: MAZPR01MB5554 X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,FREEMAIL_ENVFROM_END_DIGIT, FREEMAIL_FROM,RCVD_IN_DNSWL_NONE,RCVD_IN_MSPIKE_H2,SPF_HELO_PASS, SPF_PASS autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1757414862956340315?= X-GMAIL-MSGID: =?utf-8?q?1757414862956340315?= From: Ronald Tschalär This driver enables basic touch bar functionality: enabling it, switching between modes on FN key press, and dimming and turning the display off/on when idle/active. Signed-off-by: Ronald Tschalär [Kerem Karabay: use USB product IDs from hid-ids.h] [Kerem Karabay: use hid_hw_raw_request except when setting the touchbar mode on T1 Macs] [Kerem Karabay: update Kconfig description] Signed-off-by: Kerem Karabay [Orlando Chamberlain: add usage check to not bind to keyboard backlight interface] Signed-off-by: Orlando Chamberlain [Aditya Garg: check if apple-touchbar is enabled in the special driver list] [Aditya Garg: fix suspend on T2 Macs] Signed-off-by: Aditya Garg --- drivers/hid/Kconfig | 11 + drivers/hid/Makefile | 1 + drivers/hid/apple-touchbar.c | 1500 ++++++++++++++++++++++++++++++++++ drivers/hid/hid-quirks.c | 6 +- 4 files changed, 1516 insertions(+), 2 deletions(-) create mode 100644 drivers/hid/apple-touchbar.c -- 2.37.2 diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index e69afa5f4..4ec669267 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -134,6 +134,7 @@ config HID_APPLE_IBRIDGE tristate "Apple iBridge" depends on USB_HID depends on (X86 && ACPI) || COMPILE_TEST + imply HID_APPLE_TOUCHBAR imply HID_SENSOR_HUB imply HID_SENSOR_ALS help @@ -145,6 +146,16 @@ config HID_APPLE_IBRIDGE To compile this driver as a module, choose M here: the module will be called apple-ibridge. +config HID_APPLE_TOUCHBAR + tristate "Apple Touch Bar" + depends on USB_HID + help + Say Y here if you want support for the Touch Bar on x86 + MacBook Pros. + + To compile this driver as a module, choose M here: the + module will be called apple-touchbar. + config HID_APPLEIR tristate "Apple infrared receiver" depends on (USB_HID) diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index b61373cd8..c792e42fe 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_HID_ALPS) += hid-alps.o obj-$(CONFIG_HID_ACRUX) += hid-axff.o obj-$(CONFIG_HID_APPLE) += hid-apple.o obj-$(CONFIG_HID_APPLE_IBRIDGE) += apple-ibridge.o +obj-$(CONFIG_HID_APPLE_TOUCHBAR) += apple-touchbar.o obj-$(CONFIG_HID_APPLEIR) += hid-appleir.o obj-$(CONFIG_HID_CREATIVE_SB0540) += hid-creative-sb0540.o obj-$(CONFIG_HID_ASUS) += hid-asus.o diff --git a/drivers/hid/apple-touchbar.c b/drivers/hid/apple-touchbar.c new file mode 100644 index 000000000..ff6a8493b --- /dev/null +++ b/drivers/hid/apple-touchbar.c @@ -0,0 +1,1500 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Apple Touch Bar Driver + * + * Copyright (c) 2017-2018 Ronald Tschalär + */ + +/* + * Recent MacBookPro models (MacBookPro 13,[23] and later) have a touch bar, + * which is exposed via several USB interfaces. MacOS supports a fancy mode + * where arbitrary buttons can be defined; this driver currently only + * supports the simple mode that consists of 3 predefined layouts + * (escape-only, esc + special keys, and esc + function keys). + * + * The first USB HID interface supports two reports, an input report that + * is used to report the key presses, and an output report which can be + * used to set the touch bar "mode": touch bar off (in which case no touches + * are reported at all), escape key only, escape + 12 function keys, and + * escape + several special keys (including brightness, audio volume, + * etc). The second interface supports several, complex reports, most of + * which are unknown at this time, but one of which has been determined to + * allow for controlling of the touch bar's brightness: off (though touches + * are still reported), dimmed, and full brightness. This driver makes + * use of these two reports. + */ + +#define dev_fmt(fmt) "tb: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "hid-ids.h" +#include "apple-ibridge.h" + +#define HID_UP_APPLE 0xff120000 +#define HID_USAGE_MODE (HID_UP_CUSTOM | 0x0004) +#define HID_USAGE_APPLE_APP (HID_UP_APPLE | 0x0001) +#define HID_USAGE_DISP (HID_UP_APPLE | 0x0021) +#define HID_USAGE_DISP_AUX1 (HID_UP_APPLE | 0x0020) + +#define APPLETB_MAX_TB_KEYS 13 /* ESC, F1-F12 */ + +#define APPLETB_CMD_MODE_ESC 0 +#define APPLETB_CMD_MODE_FN 1 +#define APPLETB_CMD_MODE_SPCL 2 +#define APPLETB_CMD_MODE_OFF 3 +#define APPLETB_CMD_MODE_UPD 254 +#define APPLETB_CMD_MODE_NONE 255 + +#define APPLETB_CMD_DISP_ON 1 +#define APPLETB_CMD_DISP_DIM 2 +#define APPLETB_CMD_DISP_OFF 4 +#define APPLETB_CMD_DISP_UPD 254 +#define APPLETB_CMD_DISP_NONE 255 + +#define APPLETB_FN_MODE_FKEYS 0 +#define APPLETB_FN_MODE_NORM 1 +#define APPLETB_FN_MODE_INV 2 +#define APPLETB_FN_MODE_SPCL 3 +#define APPLETB_FN_MODE_ESC 4 +#define APPLETB_FN_MODE_MAX APPLETB_FN_MODE_ESC + +#define APPLETB_DEVID_KEYBOARD 1 +#define APPLETB_DEVID_TOUCHPAD 2 + +#define APPLETB_MAX_DIM_TIME 30 + +#define APPLETB_FEATURE_IS_T1 BIT(0) + +static int appletb_tb_def_idle_timeout = 5 * 60; +module_param_named(idle_timeout, appletb_tb_def_idle_timeout, int, 0444); +MODULE_PARM_DESC(idle_timeout, "Default touch bar idle timeout:\n" + " [>0] - turn touch bar display off after no keyboard, trackpad, or touch bar input has been received for this many seconds;\n" + " the display will be turned back on as soon as new input is received\n" + " 0 - turn touch bar display off (input does not turn it on again)\n" + " -1 - turn touch bar display on (does not turn off automatically)\n" + " -2 - disable touch bar completely"); + +static int appletb_tb_def_dim_timeout = -2; +module_param_named(dim_timeout, appletb_tb_def_dim_timeout, int, 0444); +MODULE_PARM_DESC(dim_timeout, "Default touch bar dim timeout:\n" + " >0 - dim touch bar display after no keyboard, trackpad, or touch bar input has been received for this many seconds\n" + " the display will be returned to full brightness as soon as new input is received\n" + " 0 - dim touch bar display (input does not return it to full brightness)\n" + " -1 - disable timeout (touch bar never dimmed)\n" + " [-2] - calculate timeout based on idle-timeout"); + +static int appletb_tb_def_fn_mode = APPLETB_FN_MODE_NORM; +module_param_named(fnmode, appletb_tb_def_fn_mode, int, 0444); +MODULE_PARM_DESC(fnmode, "Default Fn key mode:\n" + " 0 - function-keys only\n" + " [1] - fn key switches from special to function-keys\n" + " 2 - inverse of 1\n" + " 3 - special keys only\n" + " 4 - escape key only"); + +static ssize_t idle_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t idle_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size); +static DEVICE_ATTR_RW(idle_timeout); + +static ssize_t dim_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t dim_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size); +static DEVICE_ATTR_RW(dim_timeout); + +static ssize_t fnmode_show(struct device *dev, struct device_attribute *attr, + char *buf); +static ssize_t fnmode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size); +static DEVICE_ATTR_RW(fnmode); + +static struct attribute *appletb_attrs[] = { + &dev_attr_idle_timeout.attr, + &dev_attr_dim_timeout.attr, + &dev_attr_fnmode.attr, + NULL, +}; + +static const struct attribute_group appletb_attr_group = { + .attrs = appletb_attrs, +}; + +struct appletb_device { + bool active; + struct device *log_dev; + + struct hid_field *mode_field; + struct hid_field *disp_field; + struct hid_field *disp_field_aux1; + struct appletb_iface_info { + struct hid_device *hdev; + struct usb_interface *usb_iface; + bool suspended; + } mode_iface, disp_iface; + + struct input_handler inp_handler; + struct input_handle kbd_handle; + struct input_handle tpd_handle; + + bool last_tb_keys_pressed[APPLETB_MAX_TB_KEYS]; + bool last_tb_keys_translated[APPLETB_MAX_TB_KEYS]; + bool last_fn_pressed; + + ktime_t last_event_time; + + unsigned char cur_tb_mode; + unsigned char pnd_tb_mode; + unsigned char cur_tb_disp; + unsigned char pnd_tb_disp; + bool tb_autopm_off; + bool restore_autopm; + struct delayed_work tb_work; + /* protects most of the above */ + spinlock_t tb_lock; + + int dim_timeout; + int idle_timeout; + bool dim_to_is_calc; + int fn_mode; + + bool is_t1; +}; + +struct appletb_key_translation { + u16 from; + u16 to; +}; + +static const struct appletb_key_translation appletb_fn_codes[] = { + { KEY_F1, KEY_BRIGHTNESSDOWN }, + { KEY_F2, KEY_BRIGHTNESSUP }, + { KEY_F3, KEY_SCALE }, /* not used */ + { KEY_F4, KEY_DASHBOARD }, /* not used */ + { KEY_F5, KEY_KBDILLUMDOWN }, + { KEY_F6, KEY_KBDILLUMUP }, + { KEY_F7, KEY_PREVIOUSSONG }, + { KEY_F8, KEY_PLAYPAUSE }, + { KEY_F9, KEY_NEXTSONG }, + { KEY_F10, KEY_MUTE }, + { KEY_F11, KEY_VOLUMEDOWN }, + { KEY_F12, KEY_VOLUMEUP }, +}; + +static struct appletb_device *appletb_dev; + +static bool appletb_disable_autopm(struct hid_device *hdev) +{ + int rc; + + rc = hid_hw_power(hdev, PM_HINT_FULLON); + + if (rc == 0) + return true; + + hid_err(hdev, + "Failed to disable auto-pm on touch bar device (%d)\n", rc); + return false; +} + +/* + * While the mode functionality is listed as a valid hid report in the usb + * interface descriptor, on a T1 it's not sent that way. Instead it's sent with + * different request-type and without a leading report-id in the data. Hence + * we need to send it as a custom usb control message rather via any of the + * standard hid_hw_*request() functions. The device might return EPIPE for a + * while after setting the display mode on T1 models, so retrying should be + * done on those models. + */ +static int appletb_set_tb_mode(struct appletb_device *tb_dev, + unsigned char mode) +{ + struct hid_report *report; + void *buf; + bool autopm_off = false; + int rc; + + if (!tb_dev->mode_iface.hdev) + return -ENOTCONN; + + report = tb_dev->mode_field->report; + + if (tb_dev->is_t1) { + buf = kmemdup(&mode, 1, GFP_KERNEL); + } else { + char data[] = { report->id, mode }; + + buf = kmemdup(data, sizeof(data), GFP_KERNEL); + } + if (!buf) + return -ENOMEM; + + autopm_off = appletb_disable_autopm(tb_dev->mode_iface.hdev); + + if (tb_dev->is_t1) { + int tries = 0; + struct usb_device *dev = interface_to_usbdev(tb_dev->mode_iface.usb_iface); + __u8 ifnum = tb_dev->mode_iface.usb_iface->cur_altsetting->desc.bInterfaceNumber; + + do { + rc = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), HID_REQ_SET_REPORT, + USB_DIR_OUT | USB_RECIP_INTERFACE | USB_TYPE_VENDOR, + (report->type + 1) << 8 | report->id, + ifnum, buf, 1, 2000); + + if (rc != -EPIPE) + break; + + usleep_range(1000 << tries, 3000 << tries); + } while (++tries < 5); + } else { + rc = hid_hw_raw_request(tb_dev->mode_iface.hdev, report->id, + (__u8 *) buf, 2, report->type, + HID_REQ_SET_REPORT); + } + + if (rc < 0) + dev_err(tb_dev->log_dev, + "Failed to set touch bar mode to %u (%d)\n", mode, rc); + + if (autopm_off) + hid_hw_power(tb_dev->mode_iface.hdev, PM_HINT_NORMAL); + + kfree(buf); + + return rc; +} + +static int appletb_set_tb_disp(struct appletb_device *tb_dev, + unsigned char disp) +{ + struct hid_report *report; + int rc; + + if (!tb_dev->disp_iface.hdev) + return -ENOTCONN; + + report = tb_dev->disp_field->report; + + rc = hid_set_field(tb_dev->disp_field_aux1, 0, 1); + if (rc) { + dev_err(tb_dev->log_dev, + "Failed to set display report field (%d)\n", rc); + return rc; + } + + rc = hid_set_field(tb_dev->disp_field, 0, disp); + if (rc) { + dev_err(tb_dev->log_dev, + "Failed to set display report field (%d)\n", rc); + return rc; + } + + /* + * Keep the USB interface powered on while the touch bar display is on + * for better responsiveness. + */ + if (disp != APPLETB_CMD_DISP_OFF && !tb_dev->tb_autopm_off) + tb_dev->tb_autopm_off = + appletb_disable_autopm(report->device); + + hid_hw_request(tb_dev->disp_iface.hdev, report, HID_REQ_SET_REPORT); + + if (disp == APPLETB_CMD_DISP_OFF && tb_dev->tb_autopm_off) { + hid_hw_power(tb_dev->disp_iface.hdev, PM_HINT_NORMAL); + tb_dev->tb_autopm_off = false; + } + + return rc; +} + +static bool appletb_any_tb_key_pressed(struct appletb_device *tb_dev) +{ + return !!memchr_inv(tb_dev->last_tb_keys_pressed, 0, + sizeof(tb_dev->last_tb_keys_pressed)); +} + +static void appletb_schedule_tb_update(struct appletb_device *tb_dev, s64 secs) +{ + schedule_delayed_work(&tb_dev->tb_work, msecs_to_jiffies(secs * 1000)); +} + +static void appletb_set_tb_worker(struct work_struct *work) +{ + struct appletb_device *tb_dev = + container_of(work, struct appletb_device, tb_work.work); + s64 time_left = 0, min_timeout, time_to_off; + unsigned char pending_mode; + unsigned char pending_disp; + unsigned char current_disp; + bool restore_autopm; + bool any_tb_key_pressed, need_reschedule; + int rc1 = 1, rc2 = 1; + unsigned long flags; + + spin_lock_irqsave(&tb_dev->tb_lock, flags); + + /* handle explicit mode-change request */ + pending_mode = tb_dev->pnd_tb_mode; + pending_disp = tb_dev->pnd_tb_disp; + restore_autopm = tb_dev->restore_autopm; + + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); + + if (pending_mode != APPLETB_CMD_MODE_NONE) + rc1 = appletb_set_tb_mode(tb_dev, pending_mode); + if (pending_mode != APPLETB_CMD_MODE_NONE && + pending_disp != APPLETB_CMD_DISP_NONE) + msleep(25); + if (pending_disp != APPLETB_CMD_DISP_NONE) + rc2 = appletb_set_tb_disp(tb_dev, pending_disp); + + if (restore_autopm && tb_dev->tb_autopm_off) + appletb_disable_autopm(tb_dev->disp_field->report->device); + + spin_lock_irqsave(&tb_dev->tb_lock, flags); + + need_reschedule = false; + + if (rc1 == 0) { + tb_dev->cur_tb_mode = pending_mode; + + if (tb_dev->pnd_tb_mode == pending_mode) + tb_dev->pnd_tb_mode = APPLETB_CMD_MODE_NONE; + else + need_reschedule = true; + } + + if (rc2 == 0) { + tb_dev->cur_tb_disp = pending_disp; + + if (tb_dev->pnd_tb_disp == pending_disp) + tb_dev->pnd_tb_disp = APPLETB_CMD_DISP_NONE; + else + need_reschedule = true; + } + current_disp = tb_dev->cur_tb_disp; + + tb_dev->restore_autopm = false; + + /* calculate time left to next timeout */ + if (tb_dev->idle_timeout == -2 || tb_dev->idle_timeout == 0) + min_timeout = -1; + else if (tb_dev->idle_timeout == -1) + min_timeout = tb_dev->dim_timeout; + else if (tb_dev->dim_timeout <= 0) + min_timeout = tb_dev->idle_timeout; + else + min_timeout = min(tb_dev->dim_timeout, tb_dev->idle_timeout); + + if (min_timeout > 0) { + s64 idle_time = + (ktime_ms_delta(ktime_get(), tb_dev->last_event_time) + + 500) / 1000; + + time_left = max(min_timeout - idle_time, 0LL); + if (tb_dev->idle_timeout <= 0) + time_to_off = -1; + else if (idle_time >= tb_dev->idle_timeout) + time_to_off = 0; + else + time_to_off = tb_dev->idle_timeout - idle_time; + } else { + /* not used - just to appease the compiler */ + time_to_off = 0; + } + + any_tb_key_pressed = appletb_any_tb_key_pressed(tb_dev); + + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); + + dev_dbg(tb_dev->log_dev, "timeout calc: idle_timeout=%d dim_timeout=%d min_timeout=%lld time_left=%lld need_reschedule=%d any_tb_key_pressed=%d\n", + tb_dev->idle_timeout, tb_dev->dim_timeout, min_timeout, + time_left, need_reschedule, any_tb_key_pressed); + + /* a new command arrived while we were busy - handle it */ + if (need_reschedule) { + appletb_schedule_tb_update(tb_dev, 0); + return; + } + + /* if no idle/dim timeout, we're done */ + if (min_timeout <= 0) + return; + + /* manage idle/dim timeout */ + if (time_left > 0) { + /* we fired too soon or had a mode-change - re-schedule */ + appletb_schedule_tb_update(tb_dev, time_left); + } else if (any_tb_key_pressed) { + /* keys are still pressed - re-schedule */ + appletb_schedule_tb_update(tb_dev, min_timeout); + } else { + /* dim or idle timeout reached */ + int next_disp = (time_to_off == 0) ? APPLETB_CMD_DISP_OFF : + APPLETB_CMD_DISP_DIM; + if (next_disp != current_disp && + appletb_set_tb_disp(tb_dev, next_disp) == 0) { + spin_lock_irqsave(&tb_dev->tb_lock, flags); + tb_dev->cur_tb_disp = next_disp; + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); + } + + if (time_to_off > 0) + appletb_schedule_tb_update(tb_dev, time_to_off); + } +} + +static u16 appletb_fn_to_special(u16 code) +{ + int idx; + + for (idx = 0; idx < ARRAY_SIZE(appletb_fn_codes); idx++) { + if (appletb_fn_codes[idx].from == code) + return appletb_fn_codes[idx].to; + } + + return 0; +} + +static unsigned char appletb_get_cur_tb_mode(struct appletb_device *tb_dev) +{ + return tb_dev->pnd_tb_mode != APPLETB_CMD_MODE_NONE ? + tb_dev->pnd_tb_mode : tb_dev->cur_tb_mode; +} + +static unsigned char appletb_get_cur_tb_disp(struct appletb_device *tb_dev) +{ + return tb_dev->pnd_tb_disp != APPLETB_CMD_DISP_NONE ? + tb_dev->pnd_tb_disp : tb_dev->cur_tb_disp; +} + +static unsigned char appletb_get_fn_tb_mode(struct appletb_device *tb_dev) +{ + switch (tb_dev->fn_mode) { + case APPLETB_FN_MODE_ESC: + return APPLETB_CMD_MODE_ESC; + + case APPLETB_FN_MODE_FKEYS: + return APPLETB_CMD_MODE_FN; + + case APPLETB_FN_MODE_SPCL: + return APPLETB_CMD_MODE_SPCL; + + case APPLETB_FN_MODE_INV: + return (tb_dev->last_fn_pressed) ? APPLETB_CMD_MODE_SPCL : + APPLETB_CMD_MODE_FN; + + case APPLETB_FN_MODE_NORM: + default: + return (tb_dev->last_fn_pressed) ? APPLETB_CMD_MODE_FN : + APPLETB_CMD_MODE_SPCL; + } +} + +/* + * Switch touch bar mode and display when mode or display not the desired ones. + */ +static void appletb_update_touchbar_no_lock(struct appletb_device *tb_dev, + bool force) +{ + unsigned char want_mode; + unsigned char want_disp; + bool need_update = false; + + /* + * Calculate the new modes: + * idle_timeout: + * -2 mode/disp off + * -1 mode on, disp on/dim + * 0 mode on, disp off + * >0 mode on, disp off after idle_timeout seconds + * dim_timeout (only valid if idle_timeout > 0 || idle_timeout == -1): + * -1 disp never dimmed + * 0 disp always dimmed + * >0 disp dim after dim_timeout seconds + */ + if (tb_dev->idle_timeout == -2) { + want_mode = APPLETB_CMD_MODE_OFF; + want_disp = APPLETB_CMD_DISP_OFF; + } else { + want_mode = appletb_get_fn_tb_mode(tb_dev); + want_disp = tb_dev->idle_timeout == 0 ? APPLETB_CMD_DISP_OFF : + tb_dev->dim_timeout == 0 ? APPLETB_CMD_DISP_DIM : + APPLETB_CMD_DISP_ON; + } + + /* + * See if we need to update the touch bar, taking into account that we + * generally don't want to switch modes while a touch bar key is + * pressed. + */ + if (appletb_get_cur_tb_mode(tb_dev) != want_mode && + !appletb_any_tb_key_pressed(tb_dev)) { + tb_dev->pnd_tb_mode = want_mode; + need_update = true; + } + + if (appletb_get_cur_tb_disp(tb_dev) != want_disp && + (!appletb_any_tb_key_pressed(tb_dev) || + want_disp != APPLETB_CMD_DISP_OFF)) { + tb_dev->pnd_tb_disp = want_disp; + need_update = true; + } + + if (force) + need_update = true; + + /* schedule the update if desired */ + dev_dbg_ratelimited(tb_dev->log_dev, + "update: need_update=%d, want_mode=%d, cur-mode=%d, want_disp=%d, cur-disp=%d\n", + need_update, want_mode, tb_dev->cur_tb_mode, + want_disp, tb_dev->cur_tb_disp); + + if (need_update) { + cancel_delayed_work(&tb_dev->tb_work); + appletb_schedule_tb_update(tb_dev, 0); + } +} + +static void appletb_update_touchbar(struct appletb_device *tb_dev, bool force) +{ + unsigned long flags; + + spin_lock_irqsave(&tb_dev->tb_lock, flags); + + if (tb_dev->active) + appletb_update_touchbar_no_lock(tb_dev, force); + + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); +} + +static void appletb_set_idle_timeout(struct appletb_device *tb_dev, int new) +{ + tb_dev->idle_timeout = new; + + if (tb_dev->dim_to_is_calc && tb_dev->idle_timeout > 0) + tb_dev->dim_timeout = new - min(APPLETB_MAX_DIM_TIME, new / 3); + else if (tb_dev->dim_to_is_calc) + tb_dev->dim_timeout = -1; +} + +static ssize_t idle_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct appletb_device *tb_dev = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", tb_dev->idle_timeout); +} + +static ssize_t idle_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct appletb_device *tb_dev = dev_get_drvdata(dev); + long new; + int rc; + + rc = kstrtol(buf, 0, &new); + if (rc || new > INT_MAX || new < -2) + return -EINVAL; + + appletb_set_idle_timeout(tb_dev, new); + appletb_update_touchbar(tb_dev, true); + + return size; +} + +static void appletb_set_dim_timeout(struct appletb_device *tb_dev, int new) +{ + if (new == -2) { + tb_dev->dim_to_is_calc = true; + appletb_set_idle_timeout(tb_dev, tb_dev->idle_timeout); + } else { + tb_dev->dim_to_is_calc = false; + tb_dev->dim_timeout = new; + } +} + +static ssize_t dim_timeout_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct appletb_device *tb_dev = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", + tb_dev->dim_to_is_calc ? -2 : tb_dev->dim_timeout); +} + +static ssize_t dim_timeout_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t size) +{ + struct appletb_device *tb_dev = dev_get_drvdata(dev); + long new; + int rc; + + rc = kstrtol(buf, 0, &new); + if (rc || new > INT_MAX || new < -2) + return -EINVAL; + + appletb_set_dim_timeout(tb_dev, new); + appletb_update_touchbar(tb_dev, true); + + return size; +} + +static ssize_t fnmode_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct appletb_device *tb_dev = dev_get_drvdata(dev); + + return snprintf(buf, PAGE_SIZE, "%d\n", tb_dev->fn_mode); +} + +static ssize_t fnmode_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t size) +{ + struct appletb_device *tb_dev = dev_get_drvdata(dev); + long new; + int rc; + + rc = kstrtol(buf, 0, &new); + if (rc || new > APPLETB_FN_MODE_MAX || new < 0) + return -EINVAL; + + tb_dev->fn_mode = new; + appletb_update_touchbar(tb_dev, false); + + return size; +} + +static int appletb_tb_key_to_slot(unsigned int code) +{ + switch (code) { + case KEY_ESC: + return 0; + case KEY_F1: + case KEY_F2: + case KEY_F3: + case KEY_F4: + case KEY_F5: + case KEY_F6: + case KEY_F7: + case KEY_F8: + case KEY_F9: + case KEY_F10: + return code - KEY_F1 + 1; + case KEY_F11: + case KEY_F12: + return code - KEY_F11 + 11; + default: + return -1; + } +} + +static int appletb_hid_event(struct hid_device *hdev, struct hid_field *field, + struct hid_usage *usage, __s32 value) +{ + struct appletb_device *tb_dev = hid_get_drvdata(hdev); + unsigned int new_code = 0; + unsigned long flags; + bool send_dummy = false; + bool send_trnsl = false; + int slot; + int rc = 0; + + if ((usage->hid & HID_USAGE_PAGE) != HID_UP_KEYBOARD || + usage->type != EV_KEY) + return 0; + + /* + * Skip non-touch-bar keys. + * + * Either the touch bar itself or usbhid generate a slew of key-down + * events for all the meta keys. None of which we're at all interested + * in. + */ + slot = appletb_tb_key_to_slot(usage->code); + if (slot < 0) + return 0; + + spin_lock_irqsave(&tb_dev->tb_lock, flags); + + if (!tb_dev->active) { + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); + return 0; + } + + new_code = appletb_fn_to_special(usage->code); + + if (value != 2) + tb_dev->last_tb_keys_pressed[slot] = value; + + tb_dev->last_event_time = ktime_get(); + + appletb_update_touchbar_no_lock(tb_dev, false); + + /* + * We want to suppress touch bar keys while the touch bar is off, but + * we do want to wake up the screen if it's asleep, so generate a dummy + * event in that case. + */ + if (tb_dev->cur_tb_mode == APPLETB_CMD_MODE_OFF || + tb_dev->cur_tb_disp == APPLETB_CMD_DISP_OFF) { + send_dummy = true; + rc = 1; + /* translate special keys */ + } else if (new_code && + ((value > 0 && + appletb_get_cur_tb_mode(tb_dev) == APPLETB_CMD_MODE_SPCL) + || + (value == 0 && tb_dev->last_tb_keys_translated[slot]))) { + tb_dev->last_tb_keys_translated[slot] = true; + send_trnsl = true; + rc = 1; + /* everything else handled normally */ + } else { + tb_dev->last_tb_keys_translated[slot] = false; + } + + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); + + /* + * Need to send these input events outside of the lock, as otherwise + * we can run into the following deadlock: + * Task 1 Task 2 + * appletb_hid_event() input_event() + * acquire tb_lock acquire dev->event_lock + * input_event() appletb_inp_event() + * acquire dev->event_lock acquire tb_lock + */ + if (send_dummy) { + input_event(field->hidinput->input, EV_KEY, KEY_UNKNOWN, 1); + input_event(field->hidinput->input, EV_KEY, KEY_UNKNOWN, 0); + } else if (send_trnsl) { + input_event(field->hidinput->input, usage->type, new_code, + value); + } + + return rc; +} + +static void appletb_inp_event(struct input_handle *handle, unsigned int type, + unsigned int code, int value) +{ + struct appletb_device *tb_dev = handle->private; + unsigned long flags; + + spin_lock_irqsave(&tb_dev->tb_lock, flags); + + if (!tb_dev->active) { + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); + return; + } + + if (type == EV_KEY && code == KEY_FN && value != 2) + tb_dev->last_fn_pressed = value; + + tb_dev->last_event_time = ktime_get(); + + appletb_update_touchbar_no_lock(tb_dev, false); + + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); +} + +/* Find and save the usb-device associated with the touch bar input device */ +static struct usb_interface *appletb_get_usb_iface(struct hid_device *hdev) +{ + struct device *dev = &hdev->dev; + + while (dev && !(dev->type && dev->type->name && + !strcmp(dev->type->name, "usb_interface"))) + dev = dev->parent; + + return dev ? to_usb_interface(dev) : NULL; +} + +static int appletb_inp_connect(struct input_handler *handler, + struct input_dev *dev, + const struct input_device_id *id) +{ + struct appletb_device *tb_dev = handler->private; + struct input_handle *handle; + int rc; + + if (id->driver_info == APPLETB_DEVID_KEYBOARD) { + handle = &tb_dev->kbd_handle; + handle->name = "tbkbd"; + } else if (id->driver_info == APPLETB_DEVID_TOUCHPAD) { + handle = &tb_dev->tpd_handle; + handle->name = "tbtpad"; + } else { + dev_err(tb_dev->log_dev, "Unknown device id (%lu)\n", + id->driver_info); + return -ENOENT; + } + + if (handle->dev) { + dev_err(tb_dev->log_dev, + "Duplicate connect to %s input device\n", handle->name); + return -EEXIST; + } + + handle->open = 0; + handle->dev = input_get_device(dev); + handle->handler = handler; + handle->private = tb_dev; + + rc = input_register_handle(handle); + if (rc) + goto err_free_dev; + + rc = input_open_device(handle); + if (rc) + goto err_unregister_handle; + + dev_dbg(tb_dev->log_dev, "Connected to %s input device\n", + handle == &tb_dev->kbd_handle ? "keyboard" : "touchpad"); + + return 0; + + err_unregister_handle: + input_unregister_handle(handle); + err_free_dev: + input_put_device(handle->dev); + handle->dev = NULL; + return rc; +} + +static void appletb_inp_disconnect(struct input_handle *handle) +{ + struct appletb_device *tb_dev = handle->private; + + input_close_device(handle); + input_unregister_handle(handle); + + dev_dbg(tb_dev->log_dev, "Disconnected from %s input device\n", + handle == &tb_dev->kbd_handle ? "keyboard" : "touchpad"); + + input_put_device(handle->dev); + handle->dev = NULL; +} + +static int appletb_input_configured(struct hid_device *hdev, + struct hid_input *hidinput) +{ + int idx; + struct input_dev *input = hidinput->input; + + /* + * Clear various input capabilities that are blindly set by the hid + * driver (usbkbd.c) + */ + memset(input->evbit, 0, sizeof(input->evbit)); + memset(input->keybit, 0, sizeof(input->keybit)); + memset(input->ledbit, 0, sizeof(input->ledbit)); + + /* set our actual capabilities */ + __set_bit(EV_KEY, input->evbit); + __set_bit(EV_REP, input->evbit); + __set_bit(EV_MSC, input->evbit); /* hid-input generates MSC_SCAN */ + + for (idx = 0; idx < ARRAY_SIZE(appletb_fn_codes); idx++) { + input_set_capability(input, EV_KEY, appletb_fn_codes[idx].from); + input_set_capability(input, EV_KEY, appletb_fn_codes[idx].to); + } + + input_set_capability(input, EV_KEY, KEY_ESC); + input_set_capability(input, EV_KEY, KEY_UNKNOWN); + + return 0; +} + +static struct appletb_iface_info * +appletb_get_iface_info(struct appletb_device *tb_dev, struct hid_device *hdev) +{ + if (hdev == tb_dev->mode_iface.hdev) + return &tb_dev->mode_iface; + if (hdev == tb_dev->disp_iface.hdev) + return &tb_dev->disp_iface; + return NULL; +} + +/** + * appletb_find_report_field() - Find the field in the report with the given + * usage. + * @report: the report to search + * @field_usage: the usage of the field to search for + * + * Returns: the hid field if found, or NULL if none found. + */ +static struct hid_field *appletb_find_report_field(struct hid_report *report, + unsigned int field_usage) +{ + int f, u; + + for (f = 0; f < report->maxfield; f++) { + struct hid_field *field = report->field[f]; + + if (field->logical == field_usage) + return field; + + for (u = 0; u < field->maxusage; u++) { + if (field->usage[u].hid == field_usage) + return field; + } + } + + return NULL; +} + +/** + * appletb_find_hid_field() - Search all the reports of the device for the + * field with the given usage. + * @hdev: the device whose reports to search + * @application: the usage of application collection that the field must + * belong to + * @field_usage: the usage of the field to search for + * + * Returns: the hid field if found, or NULL if none found. + */ +static struct hid_field *appletb_find_hid_field(struct hid_device *hdev, + unsigned int application, + unsigned int field_usage) +{ + static const int report_types[] = { HID_INPUT_REPORT, HID_OUTPUT_REPORT, + HID_FEATURE_REPORT }; + struct hid_report *report; + struct hid_field *field; + int t; + + for (t = 0; t < ARRAY_SIZE(report_types); t++) { + struct list_head *report_list = + &hdev->report_enum[report_types[t]].report_list; + list_for_each_entry(report, report_list, list) { + if (report->application != application) + continue; + + field = appletb_find_report_field(report, field_usage); + if (field) + return field; + } + } + + return NULL; +} + +static int appletb_extract_report_and_iface_info(struct appletb_device *tb_dev, + struct hid_device *hdev, + const struct hid_device_id *id) +{ + struct appletb_iface_info *iface_info; + struct usb_interface *usb_iface; + struct hid_field *field; + + field = appletb_find_hid_field(hdev, HID_GD_KEYBOARD, HID_USAGE_MODE); + if (field) { + iface_info = &tb_dev->mode_iface; + tb_dev->mode_field = field; + tb_dev->is_t1 = !!(id->driver_data & APPLETB_FEATURE_IS_T1); + } else { + field = appletb_find_hid_field(hdev, HID_USAGE_APPLE_APP, + HID_USAGE_DISP); + if (!field) + return 0; + + iface_info = &tb_dev->disp_iface; + tb_dev->disp_field = field; + tb_dev->disp_field_aux1 = + appletb_find_hid_field(hdev, HID_USAGE_APPLE_APP, + HID_USAGE_DISP_AUX1); + + if (!tb_dev->disp_field_aux1 || + tb_dev->disp_field_aux1->report != + tb_dev->disp_field->report) { + dev_err(tb_dev->log_dev, + "Unexpected report structure for report %u in device %s\n", + tb_dev->disp_field->report->id, + dev_name(&hdev->dev)); + return -ENODEV; + } + } + + usb_iface = appletb_get_usb_iface(hdev); + if (!usb_iface) { + dev_err(tb_dev->log_dev, + "Failed to find usb interface for hid device %s\n", + dev_name(&hdev->dev)); + return -ENODEV; + } + + iface_info->hdev = hdev; + iface_info->usb_iface = usb_get_intf(usb_iface); + iface_info->suspended = false; + + return 1; +} + +static void appletb_clear_iface_info(struct appletb_device *tb_dev, + struct hid_device *hdev) +{ + struct appletb_iface_info *iface_info; + + iface_info = appletb_get_iface_info(tb_dev, hdev); + if (iface_info) { + usb_put_intf(iface_info->usb_iface); + iface_info->usb_iface = NULL; + iface_info->hdev = NULL; + } +} + +static bool appletb_test_and_mark_active(struct appletb_device *tb_dev) +{ + unsigned long flags; + bool activated = false; + + spin_lock_irqsave(&tb_dev->tb_lock, flags); + + if (tb_dev->mode_iface.hdev && tb_dev->disp_iface.hdev && + !tb_dev->active) { + tb_dev->active = true; + activated = true; + } + + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); + + return activated; +} + +static bool appletb_test_and_mark_inactive(struct appletb_device *tb_dev, + struct hid_device *hdev) +{ + unsigned long flags; + bool deactivated = false; + + spin_lock_irqsave(&tb_dev->tb_lock, flags); + + if (tb_dev->mode_iface.hdev && tb_dev->disp_iface.hdev && + tb_dev->active && + (hdev == tb_dev->mode_iface.hdev || + hdev == tb_dev->disp_iface.hdev)) { + tb_dev->active = false; + deactivated = true; + } + + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); + + return deactivated; +} + +static const struct input_device_id appletb_input_devices[] = { + { + .flags = INPUT_DEVICE_ID_MATCH_BUS | + INPUT_DEVICE_ID_MATCH_KEYBIT, + .bustype = BUS_SPI, + .keybit = { [BIT_WORD(KEY_FN)] = BIT_MASK(KEY_FN) }, + .driver_info = APPLETB_DEVID_KEYBOARD, + }, /* Builtin SPI keyboard device */ + { + .flags = INPUT_DEVICE_ID_MATCH_BUS | + INPUT_DEVICE_ID_MATCH_KEYBIT, + .bustype = BUS_SPI, + .keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) }, + .driver_info = APPLETB_DEVID_TOUCHPAD, + }, /* Builtin SPI touchpad device */ + { + .flags = INPUT_DEVICE_ID_MATCH_BUS | + INPUT_DEVICE_ID_MATCH_VENDOR | + INPUT_DEVICE_ID_MATCH_KEYBIT, + .bustype = BUS_USB, + .vendor = 0x05ac /* USB_VENDOR_ID_APPLE */, + .keybit = { [BIT_WORD(KEY_FN)] = BIT_MASK(KEY_FN) }, + .driver_info = APPLETB_DEVID_KEYBOARD, + }, /* Builtin USB keyboard device */ + { + .flags = INPUT_DEVICE_ID_MATCH_BUS | + INPUT_DEVICE_ID_MATCH_VENDOR | + INPUT_DEVICE_ID_MATCH_KEYBIT, + .bustype = BUS_USB, + .vendor = 0x05ac /* USB_VENDOR_ID_APPLE */, + .keybit = { [BIT_WORD(BTN_TOUCH)] = BIT_MASK(BTN_TOUCH) }, + .driver_info = APPLETB_DEVID_TOUCHPAD, + }, /* Builtin USB touchpad device */ + { }, /* Terminating zero entry */ +}; + +static bool appletb_match_internal_device(struct input_handler *handler, + struct input_dev *inp_dev) +{ + struct device *dev = &inp_dev->dev; + + if (inp_dev->id.bustype == BUS_SPI) + return true; + + /* in kernel: dev && !is_usb_device(dev) */ + while (dev && !(dev->type && dev->type->name && + !strcmp(dev->type->name, "usb_device"))) + dev = dev->parent; + + /* + * Apple labels all their internal keyboards and trackpads as such, + * instead of maintaining an ever expanding list of product-id's we + * just look at the device's product name. + */ + if (dev) + return !!strstr(to_usb_device(dev)->product, "Internal Keyboard"); + + return false; +} + +static int appletb_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + struct appletb_device *tb_dev = appletb_dev; + unsigned long flags; + int rc; + + /* initialize the report info */ + rc = hid_parse(hdev); + if (rc) { + dev_err(tb_dev->log_dev, "hid parse failed (%d)\n", rc); + goto error; + } + + /* Ensure this usb endpoint is for the touchbar backlight, not keyboard + * backlight. + */ + if ((hdev->product == USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) && + !(hdev->collection && hdev->collection[0].usage == + HID_USAGE_APPLE_APP)) { + return -ENODEV; + } + + spin_lock_irqsave(&tb_dev->tb_lock, flags); + + if (!tb_dev->log_dev) + tb_dev->log_dev = &hdev->dev; + + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); + + hid_set_drvdata(hdev, tb_dev); + + rc = appletb_extract_report_and_iface_info(tb_dev, hdev, id); + if (rc < 0) + goto error; + + rc = hid_hw_start(hdev, HID_CONNECT_DRIVER | HID_CONNECT_HIDINPUT); + if (rc) { + dev_err(tb_dev->log_dev, "hw start failed (%d)\n", rc); + goto clear_iface_info; + } + + rc = hid_hw_open(hdev); + if (rc) { + dev_err(tb_dev->log_dev, "hw open failed (%d)\n", rc); + goto stop_hid; + } + + /* do setup if we have both interfaces */ + if (appletb_test_and_mark_active(tb_dev)) { + /* initialize the touch bar */ + if (appletb_tb_def_fn_mode >= 0 && + appletb_tb_def_fn_mode <= APPLETB_FN_MODE_MAX) + tb_dev->fn_mode = appletb_tb_def_fn_mode; + else + tb_dev->fn_mode = APPLETB_FN_MODE_NORM; + appletb_set_idle_timeout(tb_dev, appletb_tb_def_idle_timeout); + appletb_set_dim_timeout(tb_dev, appletb_tb_def_dim_timeout); + tb_dev->last_event_time = ktime_get(); + + tb_dev->pnd_tb_mode = APPLETB_CMD_MODE_UPD; + tb_dev->pnd_tb_disp = APPLETB_CMD_DISP_UPD; + + appletb_update_touchbar(tb_dev, false); + + /* set up the input handler */ + tb_dev->inp_handler.event = appletb_inp_event; + tb_dev->inp_handler.connect = appletb_inp_connect; + tb_dev->inp_handler.disconnect = appletb_inp_disconnect; + tb_dev->inp_handler.name = "appletb"; + tb_dev->inp_handler.id_table = appletb_input_devices; + tb_dev->inp_handler.match = appletb_match_internal_device; + tb_dev->inp_handler.private = tb_dev; + + rc = input_register_handler(&tb_dev->inp_handler); + if (rc) { + dev_err(tb_dev->log_dev, + "Unable to register keyboard handler (%d)\n", + rc); + goto mark_inactive; + } + + /* initialize sysfs attributes */ + rc = sysfs_create_group(&tb_dev->mode_iface.hdev->dev.kobj, + &appletb_attr_group); + if (rc) { + dev_err(tb_dev->log_dev, + "Failed to create sysfs attributes (%d)\n", rc); + goto unreg_handler; + } + + dev_dbg(tb_dev->log_dev, "Touchbar activated\n"); + } + + return 0; + +unreg_handler: + input_unregister_handler(&tb_dev->inp_handler); +mark_inactive: + appletb_test_and_mark_inactive(tb_dev, hdev); + cancel_delayed_work_sync(&tb_dev->tb_work); + hid_hw_close(hdev); +stop_hid: + hid_hw_stop(hdev); +clear_iface_info: + appletb_clear_iface_info(tb_dev, hdev); +error: + return rc; +} + +static void appletb_remove(struct hid_device *hdev) +{ + struct appletb_device *tb_dev = hid_get_drvdata(hdev); + unsigned long flags; + + if (appletb_test_and_mark_inactive(tb_dev, hdev)) { + sysfs_remove_group(&tb_dev->mode_iface.hdev->dev.kobj, + &appletb_attr_group); + + input_unregister_handler(&tb_dev->inp_handler); + + cancel_delayed_work_sync(&tb_dev->tb_work); + appletb_set_tb_mode(tb_dev, APPLETB_CMD_MODE_OFF); + appletb_set_tb_disp(tb_dev, APPLETB_CMD_DISP_ON); + + if (tb_dev->tb_autopm_off) + hid_hw_power(tb_dev->disp_iface.hdev, PM_HINT_NORMAL); + + dev_info(tb_dev->log_dev, "Touchbar deactivated\n"); + } + + hid_hw_close(hdev); + hid_hw_stop(hdev); + appletb_clear_iface_info(tb_dev, hdev); + + spin_lock_irqsave(&tb_dev->tb_lock, flags); + + if (tb_dev->log_dev == &hdev->dev) { + if (tb_dev->mode_iface.hdev) + tb_dev->log_dev = &tb_dev->mode_iface.hdev->dev; + else if (tb_dev->disp_iface.hdev) + tb_dev->log_dev = &tb_dev->disp_iface.hdev->dev; + else + tb_dev->log_dev = NULL; + } + + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); +} + +#ifdef CONFIG_PM +static int appletb_suspend(struct hid_device *hdev, pm_message_t message) +{ + struct appletb_device *tb_dev = hid_get_drvdata(hdev); + struct appletb_iface_info *iface_info; + unsigned long flags; + bool all_suspended = false; + + if (message.event != PM_EVENT_SUSPEND && + message.event != PM_EVENT_FREEZE) + return 0; + + if (tb_dev->is_t1) { + + /* + * Wait for both interfaces to be suspended and no more async work + * in progress. + */ + + spin_lock_irqsave(&tb_dev->tb_lock, flags); + + if (!tb_dev->mode_iface.suspended && !tb_dev->disp_iface.suspended) { + tb_dev->active = false; + cancel_delayed_work(&tb_dev->tb_work); + } + + iface_info = appletb_get_iface_info(tb_dev, hdev); + if (iface_info) + iface_info->suspended = true; + + if ((!tb_dev->mode_iface.hdev || tb_dev->mode_iface.suspended) && + (!tb_dev->disp_iface.hdev || tb_dev->disp_iface.suspended)) + all_suspended = true; + + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); + + flush_delayed_work(&tb_dev->tb_work); + + if (!all_suspended) + return 0; + + /* + * The touch bar device itself remembers the last state when suspended + * in some cases, but in others (e.g. when mode != off and disp == off) + * it resumes with a different state; furthermore it may be only + * partially responsive in that state. By turning both mode and disp + * off we ensure it is in a good state when resuming (and this happens + * to be the same state after booting/resuming-from-hibernate, so less + * special casing between the two). + */ + if (message.event == PM_EVENT_SUSPEND) { + appletb_set_tb_mode(tb_dev, APPLETB_CMD_MODE_OFF); + appletb_set_tb_disp(tb_dev, APPLETB_CMD_DISP_OFF); + } + + spin_lock_irqsave(&tb_dev->tb_lock, flags); + + tb_dev->cur_tb_mode = APPLETB_CMD_MODE_OFF; + tb_dev->cur_tb_disp = APPLETB_CMD_DISP_OFF; + + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); + + dev_info(tb_dev->log_dev, "Touchbar suspended.\n"); + } else { + dev_info(tb_dev->log_dev, "T2 Mac detected, not handling suspend.\n"); + } + + return 0; +} + +static int appletb_reset_resume(struct hid_device *hdev) +{ + struct appletb_device *tb_dev = hid_get_drvdata(hdev); + struct appletb_iface_info *iface_info; + unsigned long flags; + + spin_lock_irqsave(&tb_dev->tb_lock, flags); + + iface_info = appletb_get_iface_info(tb_dev, hdev); + if (iface_info) + iface_info->suspended = false; + + if ((tb_dev->mode_iface.hdev && !tb_dev->mode_iface.suspended) && + (tb_dev->disp_iface.hdev && !tb_dev->disp_iface.suspended)) { + /* + * Restore touch bar state. Note that autopm state is not + * preserved, so need explicitly restore that here. + */ + tb_dev->active = true; + tb_dev->restore_autopm = true; + tb_dev->last_event_time = ktime_get(); + + appletb_update_touchbar_no_lock(tb_dev, true); + + dev_info(tb_dev->log_dev, "Touchbar resumed.\n"); + } + + spin_unlock_irqrestore(&tb_dev->tb_lock, flags); + + return 0; +} +#endif + +static struct appletb_device *appletb_alloc_device(void) +{ + struct appletb_device *tb_dev; + + tb_dev = kzalloc(sizeof(*tb_dev), GFP_KERNEL); + if (!tb_dev) + return NULL; + + spin_lock_init(&tb_dev->tb_lock); + INIT_DELAYED_WORK(&tb_dev->tb_work, appletb_set_tb_worker); + + return tb_dev; +} + +static void appletb_free_device(struct appletb_device *tb_dev) +{ + cancel_delayed_work_sync(&tb_dev->tb_work); + kfree(tb_dev); +} + +static const struct hid_device_id appletb_hid_ids[] = { + /* MacBook Pro's 2016, 2017, with T1 chip */ + { HID_USB_DEVICE(USB_VENDOR_ID_LINUX_FOUNDATION, + USB_DEVICE_ID_IBRIDGE_TB), + .driver_data = APPLETB_FEATURE_IS_T1 }, + /* MacBook Pro's 2018, 2019, with T2 chip: iBridge DFR brightness */ + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, + USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) }, + /* MacBook Pro's 2018, 2019, with T2 chip: iBridge Display */ + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, + USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) }, + { }, +}; + +MODULE_DEVICE_TABLE(hid, appletb_hid_ids); + +static struct hid_driver appletb_hid_driver = { + .name = "apple-touchbar", + .id_table = appletb_hid_ids, + .probe = appletb_probe, + .remove = appletb_remove, + .event = appletb_hid_event, + .input_configured = appletb_input_configured, +#ifdef CONFIG_PM + .suspend = appletb_suspend, + .reset_resume = appletb_reset_resume, +#endif +}; + +static int __init appletb_init(void) +{ + struct appletb_device *tb_dev; + int rc; + + tb_dev = appletb_alloc_device(); + if (!tb_dev) + return -ENOMEM; + + appletb_dev = tb_dev; + + rc = hid_register_driver(&appletb_hid_driver); + if (rc) + goto error; + + return 0; + +error: + appletb_free_device(tb_dev); + return rc; +} + +static void __exit appletb_exit(void) +{ + hid_unregister_driver(&appletb_hid_driver); + appletb_free_device(appletb_dev); +} + +module_init(appletb_init); +module_exit(appletb_exit); + +MODULE_AUTHOR("Ronald Tschalär"); +MODULE_DESCRIPTION("MacBookPro Touch Bar driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-quirks.c b/drivers/hid/hid-quirks.c index c03535c4b..e620190b5 100644 --- a/drivers/hid/hid-quirks.c +++ b/drivers/hid/hid-quirks.c @@ -316,12 +316,14 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_GEYSER1_TP_ONLY) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021) }, - { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) }, - { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) }, #endif #if IS_ENABLED(CONFIG_HID_APPLE_IBRIDGE) { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IBRIDGE) }, #endif +#if IS_ENABLED(CONFIG_HID_APPLE_TOUCHBAR) + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT) }, + { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY) }, +#endif #if IS_ENABLED(CONFIG_HID_APPLEIR) { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL) }, { HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_IRCONTROL2) },