* [PATCH 01/10] dt-bindings: mailbox: Add Apple t8122 ASC mailbox
2026-06-30 12:54 [PATCH 00/10] Add support for Apple Silicon DockChannel internal keyboards Michael Reeves via B4 Relay
@ 2026-06-30 12:54 ` Michael Reeves via B4 Relay
2026-06-30 17:05 ` Conor Dooley
2026-06-30 12:54 ` [PATCH 02/10] dt-bindings: mailbox: apple: Add DockChannel mailbox Michael Reeves via B4 Relay
` (8 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Michael Reeves via B4 Relay @ 2026-06-30 12:54 UTC (permalink / raw)
To: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires
Cc: asahi, linux-arm-kernel, linux-kernel, devicetree, iommu,
linux-input, Michael Reeves
From: Michael Reeves <michael.reeves077@gmail.com>
The ASC mailbox on t8122 is compatible with the v4 mailbox block used
by other Apple Silicon SoCs.
Document the t8122 compatible in the v4 mailbox section so M3 device
trees can describe their coprocessor mailboxes.
Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
---
Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml | 1 +
1 file changed, 1 insertion(+)
diff --git a/Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml b/Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml
index 28985cc62c25..946c909c6922 100644
--- a/Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml
+++ b/Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml
@@ -30,6 +30,7 @@ properties:
- enum:
- apple,t8103-asc-mailbox
- apple,t8112-asc-mailbox
+ - apple,t8122-asc-mailbox
- apple,t6000-asc-mailbox
- apple,t6020-asc-mailbox
- const: apple,asc-mailbox-v4
--
2.51.2
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH 01/10] dt-bindings: mailbox: Add Apple t8122 ASC mailbox
2026-06-30 12:54 ` [PATCH 01/10] dt-bindings: mailbox: Add Apple t8122 ASC mailbox Michael Reeves via B4 Relay
@ 2026-06-30 17:05 ` Conor Dooley
0 siblings, 0 replies; 22+ messages in thread
From: Conor Dooley @ 2026-06-30 17:05 UTC (permalink / raw)
To: michael.reeves077
Cc: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires, asahi, linux-arm-kernel,
linux-kernel, devicetree, iommu, linux-input
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 02/10] dt-bindings: mailbox: apple: Add DockChannel mailbox
2026-06-30 12:54 [PATCH 00/10] Add support for Apple Silicon DockChannel internal keyboards Michael Reeves via B4 Relay
2026-06-30 12:54 ` [PATCH 01/10] dt-bindings: mailbox: Add Apple t8122 ASC mailbox Michael Reeves via B4 Relay
@ 2026-06-30 12:54 ` Michael Reeves via B4 Relay
2026-06-30 17:07 ` Conor Dooley
2026-06-30 12:54 ` [PATCH 03/10] dt-bindings: iommu: apple,dart: Add t8122 compatible Michael Reeves via B4 Relay
` (7 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Michael Reeves via B4 Relay @ 2026-06-30 12:54 UTC (permalink / raw)
To: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires
Cc: asahi, linux-arm-kernel, linux-kernel, devicetree, iommu,
linux-input, Michael Reeves
From: Michael Reeves <michael.reeves077@gmail.com>
DockChannel is a FIFO and interrupt block used by Apple coprocessors
to exchange byte-stream traffic with the AP.
Describe it as a mailbox provider so HID and future serial clients can
use the common mailbox API.
Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
---
.../bindings/mailbox/apple,dockchannel.yaml | 75 ++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 76 insertions(+)
diff --git a/Documentation/devicetree/bindings/mailbox/apple,dockchannel.yaml b/Documentation/devicetree/bindings/mailbox/apple,dockchannel.yaml
new file mode 100644
index 000000000000..4f326d8f3d5a
--- /dev/null
+++ b/Documentation/devicetree/bindings/mailbox/apple,dockchannel.yaml
@@ -0,0 +1,75 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mailbox/apple,dockchannel.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Apple DockChannel FIFO Mailbox
+
+maintainers:
+ - Michael Reeves <michael.reeves077@gmail.com>
+
+description:
+ DockChannel is a hardware FIFO and interrupt block used on Apple SoCs for
+ low-latency byte stream communication with co-processors.
+
+properties:
+ compatible:
+ oneOf:
+ - items:
+ - const: apple,t8112-dockchannel
+ - items:
+ - enum:
+ - apple,t6020-dockchannel
+ - apple,t8122-dockchannel
+ - const: apple,t8112-dockchannel
+
+ reg:
+ items:
+ - description: Interrupt control registers
+ - description: FIFO configuration registers
+ - description: FIFO data registers
+
+ reg-names:
+ items:
+ - const: irq
+ - const: config
+ - const: data
+
+ interrupts:
+ maxItems: 1
+
+ "#mbox-cells":
+ const: 0
+
+ nonposted-mmio: true
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - interrupts
+ - "#mbox-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/apple-aic.h>
+ #include <dt-bindings/interrupt-controller/irq.h>
+
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ mailbox@24eb14000 {
+ compatible = "apple,t8112-dockchannel";
+ reg = <0x2 0x4eb14000 0x0 0x4000>,
+ <0x2 0x4eb30000 0x0 0x4000>,
+ <0x2 0x4eb34000 0x0 0x4000>;
+ reg-names = "irq", "config", "data";
+ interrupt-parent = <&aic>;
+ interrupts = <AIC_IRQ 850 IRQ_TYPE_LEVEL_HIGH>;
+ #mbox-cells = <0>;
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 15011f5752a9..741974f0f326 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2594,6 +2594,7 @@ F: Documentation/devicetree/bindings/interrupt-controller/apple,*
F: Documentation/devicetree/bindings/iommu/apple,dart.yaml
F: Documentation/devicetree/bindings/iommu/apple,sart.yaml
F: Documentation/devicetree/bindings/leds/backlight/apple,dwi-bl.yaml
+F: Documentation/devicetree/bindings/mailbox/apple,dockchannel.yaml
F: Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml
F: Documentation/devicetree/bindings/mfd/apple,smc.yaml
F: Documentation/devicetree/bindings/net/bluetooth/brcm,bcm4377-bluetooth.yaml
--
2.51.2
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH 02/10] dt-bindings: mailbox: apple: Add DockChannel mailbox
2026-06-30 12:54 ` [PATCH 02/10] dt-bindings: mailbox: apple: Add DockChannel mailbox Michael Reeves via B4 Relay
@ 2026-06-30 17:07 ` Conor Dooley
2026-07-01 14:35 ` Michael Reeves
0 siblings, 1 reply; 22+ messages in thread
From: Conor Dooley @ 2026-06-30 17:07 UTC (permalink / raw)
To: michael.reeves077
Cc: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires, asahi, linux-arm-kernel,
linux-kernel, devicetree, iommu, linux-input
[-- Attachment #1: Type: text/plain, Size: 1125 bytes --]
On Tue, Jun 30, 2026 at 10:54:30PM +1000, Michael Reeves via B4 Relay wrote:
> From: Michael Reeves <michael.reeves077@gmail.com>
>
> DockChannel is a FIFO and interrupt block used by Apple coprocessors
> to exchange byte-stream traffic with the AP.
>
> Describe it as a mailbox provider so HID and future serial clients can
> use the common mailbox API.
>
> Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
> ---
> .../bindings/mailbox/apple,dockchannel.yaml | 75 ++++++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 76 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/mailbox/apple,dockchannel.yaml b/Documentation/devicetree/bindings/mailbox/apple,dockchannel.yaml
> new file mode 100644
> index 000000000000..4f326d8f3d5a
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/mailbox/apple,dockchannel.yaml
Please name this file matching the t8112 compatible (since that's your
fallback).
Otherwise, this looks good.
Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: changes-requested
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 02/10] dt-bindings: mailbox: apple: Add DockChannel mailbox
2026-06-30 17:07 ` Conor Dooley
@ 2026-07-01 14:35 ` Michael Reeves
0 siblings, 0 replies; 22+ messages in thread
From: Michael Reeves @ 2026-07-01 14:35 UTC (permalink / raw)
To: Conor Dooley
Cc: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires, asahi, linux-arm-kernel,
linux-kernel, devicetree, iommu, linux-input
On Wed, Jul 1, 2026 at 3:07 AM Conor Dooley <conor@kernel.org> wrote:
[...]
> > +++ b/Documentation/devicetree/bindings/mailbox/apple,dockchannel.yaml
>
> Please name this file matching the t8112 compatible (since that's your
> fallback).
> Otherwise, this looks good.
> Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
>
> pw-bot: changes-requested
Thank you, will change the name in v2.
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 03/10] dt-bindings: iommu: apple,dart: Add t8122 compatible
2026-06-30 12:54 [PATCH 00/10] Add support for Apple Silicon DockChannel internal keyboards Michael Reeves via B4 Relay
2026-06-30 12:54 ` [PATCH 01/10] dt-bindings: mailbox: Add Apple t8122 ASC mailbox Michael Reeves via B4 Relay
2026-06-30 12:54 ` [PATCH 02/10] dt-bindings: mailbox: apple: Add DockChannel mailbox Michael Reeves via B4 Relay
@ 2026-06-30 12:54 ` Michael Reeves via B4 Relay
2026-06-30 17:07 ` Conor Dooley
2026-06-30 12:54 ` [PATCH 04/10] dt-bindings: input: apple: Add DockChannel HID transport Michael Reeves via B4 Relay
` (6 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Michael Reeves via B4 Relay @ 2026-06-30 12:54 UTC (permalink / raw)
To: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires
Cc: asahi, linux-arm-kernel, linux-kernel, devicetree, iommu,
linux-input, Michael Reeves
From: Michael Reeves <michael.reeves077@gmail.com>
The MTP block on t8122 has its own DART instance for coprocessor DMA.
Document the t8122 compatible with the existing t8110 fallback because
the programming model is shared.
Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
---
Documentation/devicetree/bindings/iommu/apple,dart.yaml | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Documentation/devicetree/bindings/iommu/apple,dart.yaml b/Documentation/devicetree/bindings/iommu/apple,dart.yaml
index 47ec7fa52c3a..e179199dbd3b 100644
--- a/Documentation/devicetree/bindings/iommu/apple,dart.yaml
+++ b/Documentation/devicetree/bindings/iommu/apple,dart.yaml
@@ -29,7 +29,9 @@ properties:
- apple,t8110-dart
- apple,t6000-dart
- items:
- - const: apple,t6020-dart
+ - enum:
+ - apple,t6020-dart
+ - apple,t8122-dart
- const: apple,t8110-dart
reg:
--
2.51.2
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH 03/10] dt-bindings: iommu: apple,dart: Add t8122 compatible
2026-06-30 12:54 ` [PATCH 03/10] dt-bindings: iommu: apple,dart: Add t8122 compatible Michael Reeves via B4 Relay
@ 2026-06-30 17:07 ` Conor Dooley
0 siblings, 0 replies; 22+ messages in thread
From: Conor Dooley @ 2026-06-30 17:07 UTC (permalink / raw)
To: michael.reeves077
Cc: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires, asahi, linux-arm-kernel,
linux-kernel, devicetree, iommu, linux-input
[-- Attachment #1: Type: text/plain, Size: 75 bytes --]
Acked-by: Conor Dooley <conor.dooley@microchip.com>
pw-bot: not-applicable
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 04/10] dt-bindings: input: apple: Add DockChannel HID transport
2026-06-30 12:54 [PATCH 00/10] Add support for Apple Silicon DockChannel internal keyboards Michael Reeves via B4 Relay
` (2 preceding siblings ...)
2026-06-30 12:54 ` [PATCH 03/10] dt-bindings: iommu: apple,dart: Add t8122 compatible Michael Reeves via B4 Relay
@ 2026-06-30 12:54 ` Michael Reeves via B4 Relay
2026-06-30 17:08 ` Conor Dooley
2026-06-30 12:54 ` [PATCH 05/10] mailbox: apple: Add DockChannel FIFO controller Michael Reeves via B4 Relay
` (5 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Michael Reeves via B4 Relay @ 2026-06-30 12:54 UTC (permalink / raw)
To: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires
Cc: asahi, linux-arm-kernel, linux-kernel, devicetree, iommu,
linux-input, Michael Reeves
From: Michael Reeves <michael.reeves077@gmail.com>
Apple internal keyboards and trackpads behind MTP are exposed through a
DockChannel HID transport.
Add the client binding tying together the RTKit ASC mailbox,
DockChannel mailbox, and MTP DART. The keyboard child can provide the
HID country code used by hid-apple to distinguish layout variants.
Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
---
.../bindings/input/apple,dockchannel-hid.yaml | 91 ++++++++++++++++++++++
MAINTAINERS | 1 +
2 files changed, 92 insertions(+)
diff --git a/Documentation/devicetree/bindings/input/apple,dockchannel-hid.yaml b/Documentation/devicetree/bindings/input/apple,dockchannel-hid.yaml
new file mode 100644
index 000000000000..dbba4fc38971
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/apple,dockchannel-hid.yaml
@@ -0,0 +1,91 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/apple,dockchannel-hid.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Apple DockChannel HID Transport
+
+maintainers:
+ - Michael Reeves <michael.reeves077@gmail.com>
+
+description:
+ HID transport for keyboard and trackpad devices connected via the
+ DockChannel FIFO mailbox on Apple Silicon SoCs. The endpoint includes a
+ small RTKit coprocessor called MTP which must be booted before the HID
+ transport becomes available.
+
+properties:
+ compatible:
+ oneOf:
+ - items:
+ - const: apple,t8112-dockchannel-hid
+ - items:
+ - enum:
+ - apple,t6020-dockchannel-hid
+ - apple,t8122-dockchannel-hid
+ - const: apple,t8112-dockchannel-hid
+
+ reg:
+ items:
+ - description: Coprocessor ASC registers
+ - description: Coprocessor SRAM/mailbox registers
+
+ reg-names:
+ items:
+ - const: coproc-asc
+ - const: coproc-sram
+
+ mboxes:
+ items:
+ - description: ASC mailbox used for RTKit control
+ - description: DockChannel FIFO mailbox used for HID packets
+
+ mbox-names:
+ items:
+ - const: asc
+ - const: dockchannel
+
+ iommus:
+ maxItems: 1
+
+ keyboard:
+ type: object
+ properties:
+ hid-country-code:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description:
+ HID country code for the keyboard layout variant.
+
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - mboxes
+ - mbox-names
+ - iommus
+
+additionalProperties: false
+
+examples:
+ - |
+ soc {
+ #address-cells = <2>;
+ #size-cells = <2>;
+
+ hid@24e400000 {
+ compatible = "apple,t8112-dockchannel-hid";
+ reg = <0x2 0x4e400000 0x0 0x4000>,
+ <0x2 0x4ec00000 0x0 0x100000>;
+ reg-names = "coproc-asc", "coproc-sram";
+ mboxes = <&mtp_mbox>, <&mtp_dockchannel>;
+ mbox-names = "asc", "dockchannel";
+ iommus = <&mtp_dart 1>;
+
+ keyboard {
+ hid-country-code = <0>;
+ };
+ };
+ };
diff --git a/MAINTAINERS b/MAINTAINERS
index 741974f0f326..1f3c2cdb6e19 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2589,6 +2589,7 @@ F: Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml
F: Documentation/devicetree/bindings/gpu/apple,agx.yaml
F: Documentation/devicetree/bindings/hwmon/apple,smc-hwmon.yaml
F: Documentation/devicetree/bindings/i2c/apple,i2c.yaml
+F: Documentation/devicetree/bindings/input/apple,dockchannel-hid.yaml
F: Documentation/devicetree/bindings/input/touchscreen/apple,z2-multitouch.yaml
F: Documentation/devicetree/bindings/interrupt-controller/apple,*
F: Documentation/devicetree/bindings/iommu/apple,dart.yaml
--
2.51.2
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH 04/10] dt-bindings: input: apple: Add DockChannel HID transport
2026-06-30 12:54 ` [PATCH 04/10] dt-bindings: input: apple: Add DockChannel HID transport Michael Reeves via B4 Relay
@ 2026-06-30 17:08 ` Conor Dooley
2026-07-01 14:36 ` Michael Reeves
0 siblings, 1 reply; 22+ messages in thread
From: Conor Dooley @ 2026-06-30 17:08 UTC (permalink / raw)
To: michael.reeves077
Cc: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires, asahi, linux-arm-kernel,
linux-kernel, devicetree, iommu, linux-input
[-- Attachment #1: Type: text/plain, Size: 1208 bytes --]
On Tue, Jun 30, 2026 at 10:54:32PM +1000, Michael Reeves via B4 Relay wrote:
> From: Michael Reeves <michael.reeves077@gmail.com>
>
> Apple internal keyboards and trackpads behind MTP are exposed through a
> DockChannel HID transport.
>
> Add the client binding tying together the RTKit ASC mailbox,
> DockChannel mailbox, and MTP DART. The keyboard child can provide the
> HID country code used by hid-apple to distinguish layout variants.
>
> Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
> ---
> .../bindings/input/apple,dockchannel-hid.yaml | 91 ++++++++++++++++++++++
> MAINTAINERS | 1 +
> 2 files changed, 92 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/input/apple,dockchannel-hid.yaml b/Documentation/devicetree/bindings/input/apple,dockchannel-hid.yaml
> new file mode 100644
> index 000000000000..dbba4fc38971
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/input/apple,dockchannel-hid.yaml
Same thing here about the filename. Looks good otherwise, so please
change that.
pw-bot: changes-requested
\x02
Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
Thanks,
Conor.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 04/10] dt-bindings: input: apple: Add DockChannel HID transport
2026-06-30 17:08 ` Conor Dooley
@ 2026-07-01 14:36 ` Michael Reeves
0 siblings, 0 replies; 22+ messages in thread
From: Michael Reeves @ 2026-07-01 14:36 UTC (permalink / raw)
To: Conor Dooley
Cc: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires, asahi, linux-arm-kernel,
linux-kernel, devicetree, iommu, linux-input
On Wed, Jul 1, 2026 at 3:08 AM Conor Dooley <conor@kernel.org> wrote:
[...]
> > +++ b/Documentation/devicetree/bindings/input/apple,dockchannel-hid.yaml
>
> Same thing here about the filename. Looks good otherwise, so please
> change that.
> pw-bot: changes-requested
>
> Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
>
> Thanks,
> Conor.
Thanks again, will also adjust this name in v2.
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 05/10] mailbox: apple: Add DockChannel FIFO controller
2026-06-30 12:54 [PATCH 00/10] Add support for Apple Silicon DockChannel internal keyboards Michael Reeves via B4 Relay
` (3 preceding siblings ...)
2026-06-30 12:54 ` [PATCH 04/10] dt-bindings: input: apple: Add DockChannel HID transport Michael Reeves via B4 Relay
@ 2026-06-30 12:54 ` Michael Reeves via B4 Relay
2026-06-30 12:54 ` [PATCH 06/10] soc: apple: rtkit: Add tracekit endpoint Michael Reeves via B4 Relay
` (4 subsequent siblings)
9 siblings, 0 replies; 22+ messages in thread
From: Michael Reeves via B4 Relay @ 2026-06-30 12:54 UTC (permalink / raw)
To: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires
Cc: asahi, linux-arm-kernel, linux-kernel, devicetree, iommu,
linux-input, Michael Reeves
From: Michael Reeves <michael.reeves077@gmail.com>
DockChannel is a hardware FIFO used by Apple coprocessors for
low-latency byte-stream communication with the AP.
Add a mailbox controller that preallocates RX storage, tracks IRQ
enable state in software, and reports TX completion from the TX-empty
interrupt.
Reject messages larger than the FIFO and return -EBUSY while the
previous message is still pending. This keeps the provider usable for
future small-message clients such as serial transports without a TX
worker.
Co-developed-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
---
MAINTAINERS | 2 +
drivers/mailbox/Kconfig | 12 +
drivers/mailbox/Makefile | 2 +
drivers/mailbox/apple-dockchannel.c | 380 ++++++++++++++++++++++++++++++
include/linux/mailbox/apple-dockchannel.h | 29 +++
5 files changed, 425 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index 1f3c2cdb6e19..ed68452c0ad6 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2628,6 +2628,7 @@ F: drivers/input/touchscreen/apple_z2.c
F: drivers/iommu/apple-dart.c
F: drivers/iommu/io-pgtable-dart.c
F: drivers/irqchip/irq-apple-aic.c
+F: drivers/mailbox/apple-dockchannel.c
F: drivers/mfd/macsmc.c
F: drivers/nvme/host/apple.c
F: drivers/nvmem/apple-efuses.c
@@ -2646,6 +2647,7 @@ F: drivers/video/backlight/apple_dwi_bl.c
F: drivers/watchdog/apple_wdt.c
F: include/dt-bindings/interrupt-controller/apple-aic.h
F: include/dt-bindings/pinctrl/apple.h
+F: include/linux/mailbox/apple-dockchannel.h
F: include/linux/mfd/macsmc.h
F: include/linux/soc/apple/*
F: include/uapi/drm/asahi_drm.h
diff --git a/drivers/mailbox/Kconfig b/drivers/mailbox/Kconfig
index 3062ee352f78..f1af76d19f1e 100644
--- a/drivers/mailbox/Kconfig
+++ b/drivers/mailbox/Kconfig
@@ -36,6 +36,18 @@ config ARM_MHU_V3
that provides different means of transports: supported extensions
will be discovered and possibly managed at probe-time.
+config APPLE_DOCKCHANNEL
+ tristate "Apple DockChannel FIFO mailbox"
+ depends on ARCH_APPLE || COMPILE_TEST
+ depends on HAS_IOMEM
+ depends on OF
+ help
+ DockChannel is a hardware FIFO used on Apple Silicon SoCs for
+ communication between the application processor and co-processors.
+ This driver exposes DockChannel FIFOs through the mailbox framework.
+
+ Say Y here if you have an M2 or later Apple MacBook.
+
config AST2700_MBOX
tristate "ASPEED AST2700 IPC driver"
depends on ARCH_ASPEED || COMPILE_TEST
diff --git a/drivers/mailbox/Makefile b/drivers/mailbox/Makefile
index 944d8ea39f34..4f3405064269 100644
--- a/drivers/mailbox/Makefile
+++ b/drivers/mailbox/Makefile
@@ -11,6 +11,8 @@ obj-$(CONFIG_ARM_MHU_V2) += arm_mhuv2.o
obj-$(CONFIG_ARM_MHU_V3) += arm_mhuv3.o
+obj-$(CONFIG_APPLE_DOCKCHANNEL) += apple-dockchannel.o
+
obj-$(CONFIG_AST2700_MBOX) += ast2700-mailbox.o
obj-$(CONFIG_CV1800_MBOX) += cv1800-mailbox.o
diff --git a/drivers/mailbox/apple-dockchannel.c b/drivers/mailbox/apple-dockchannel.c
new file mode 100644
index 000000000000..bae183db1307
--- /dev/null
+++ b/drivers/mailbox/apple-dockchannel.c
@@ -0,0 +1,380 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple DockChannel mailbox controller
+ *
+ * Copyright The Asahi Linux Contributors
+ *
+ * DockChannel is a byte FIFO used by Apple co-processors. This driver exposes a
+ * single FIFO pair as a Linux mailbox channel and moves payload bytes with PIO.
+ * There is no DMA involved, so relaxed MMIO accessors are sufficient for the
+ * FIFO accesses themselves.
+ */
+
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/mailbox/apple-dockchannel.h>
+#include <linux/mailbox_controller.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/spinlock.h>
+#include <linux/unaligned.h>
+
+#define APPLE_DOCKCHANNEL_FIFO_SIZE 0x800
+
+#define IRQ_MASK 0x0
+#define IRQ_FLAG 0x4
+
+#define IRQ_TX BIT(2)
+#define IRQ_RX BIT(3)
+
+#define CONFIG_TX_THRESH 0x0
+#define CONFIG_RX_THRESH 0x4
+
+#define DATA_TX8 0x4
+#define DATA_TX32 0x10
+#define DATA_TX_FREE 0x14
+#define DATA_RX8 0x1c
+#define DATA_RX32 0x28
+#define DATA_RX_COUNT 0x2c
+
+struct apple_dockchannel {
+ struct device *dev;
+ struct mbox_controller controller;
+ struct mbox_chan chan;
+
+ void __iomem *irq_base;
+ void __iomem *config_base;
+ void __iomem *data_base;
+ int irq;
+
+ spinlock_t lock; /* protects IRQ mask and TX state */
+ u32 irq_mask;
+
+ const u8 *tx_buf;
+ size_t tx_len;
+ size_t tx_pos;
+ bool tx_active;
+
+ u8 rx_buf[APPLE_DOCKCHANNEL_FIFO_SIZE];
+};
+
+static void apple_dockchannel_irq_update(struct apple_dockchannel *dc,
+ u32 bits, bool enable)
+{
+ if (enable)
+ dc->irq_mask |= bits;
+ else
+ dc->irq_mask &= ~bits;
+ writel_relaxed(dc->irq_mask, dc->irq_base + IRQ_MASK);
+}
+
+static void apple_dockchannel_irq_enable(struct apple_dockchannel *dc, u32 bits)
+{
+ /*
+ * IRQ_FLAG is write-to-clear. Clear stale latched flags before
+ * unmasking so the next interrupt reflects current FIFO state.
+ */
+ writel_relaxed(bits, dc->irq_base + IRQ_FLAG);
+ apple_dockchannel_irq_update(dc, bits, true);
+}
+
+static void apple_dockchannel_irq_disable(struct apple_dockchannel *dc, u32 bits)
+{
+ apple_dockchannel_irq_update(dc, bits, false);
+}
+
+static bool apple_dockchannel_tx_empty(struct apple_dockchannel *dc)
+{
+ return readl_relaxed(dc->data_base + DATA_TX_FREE) ==
+ APPLE_DOCKCHANNEL_FIFO_SIZE;
+}
+
+static void apple_dockchannel_write_pending(struct apple_dockchannel *dc)
+{
+ size_t left = dc->tx_len - dc->tx_pos;
+ const u8 *p = dc->tx_buf + dc->tx_pos;
+
+ while (left) {
+ size_t avail;
+ size_t block;
+
+ avail = readl_relaxed(dc->data_base + DATA_TX_FREE);
+ if (!avail)
+ break;
+
+ block = min(left, avail);
+
+ while (block >= sizeof(u32)) {
+ writel_relaxed(get_unaligned_le32(p),
+ dc->data_base + DATA_TX32);
+ p += sizeof(u32);
+ left -= sizeof(u32);
+ block -= sizeof(u32);
+ }
+
+ while (block) {
+ writeb_relaxed(*p++, dc->data_base + DATA_TX8);
+ left--;
+ block--;
+ }
+ }
+
+ dc->tx_pos = dc->tx_len - left;
+}
+
+static void apple_dockchannel_read(struct apple_dockchannel *dc, void *buf,
+ size_t count)
+{
+ u8 *p = buf;
+ size_t left = count;
+
+ while (left >= sizeof(u32)) {
+ put_unaligned_le32(readl_relaxed(dc->data_base + DATA_RX32), p);
+ p += sizeof(u32);
+ left -= sizeof(u32);
+ }
+
+ while (left) {
+ /*
+ * The byte FIFO register returns the byte in bits [15:8] on
+ * these instances.
+ */
+ *p++ = readl_relaxed(dc->data_base + DATA_RX8) >> 8;
+ left--;
+ }
+}
+
+static int apple_dockchannel_send_data(struct mbox_chan *chan, void *data)
+{
+ struct apple_dockchannel *dc = chan->con_priv;
+ struct apple_dockchannel_msg *msg = data;
+ unsigned long flags;
+
+ if (!msg || !msg->data || !msg->len)
+ return -EINVAL;
+
+ if (msg->len > APPLE_DOCKCHANNEL_FIFO_SIZE)
+ return -EMSGSIZE;
+
+ spin_lock_irqsave(&dc->lock, flags);
+
+ if (dc->tx_active || !apple_dockchannel_tx_empty(dc)) {
+ spin_unlock_irqrestore(&dc->lock, flags);
+ return -EBUSY;
+ }
+
+ dc->tx_buf = msg->data;
+ dc->tx_len = msg->len;
+ dc->tx_pos = 0;
+ dc->tx_active = true;
+
+ apple_dockchannel_write_pending(dc);
+ writel_relaxed(APPLE_DOCKCHANNEL_FIFO_SIZE,
+ dc->config_base + CONFIG_TX_THRESH);
+ apple_dockchannel_irq_enable(dc, IRQ_TX);
+
+ spin_unlock_irqrestore(&dc->lock, flags);
+
+ return 0;
+}
+
+static int apple_dockchannel_startup(struct mbox_chan *chan)
+{
+ struct apple_dockchannel *dc = chan->con_priv;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dc->lock, flags);
+ /*
+ * The mailbox framework has no per-client RX threshold. Use byte
+ * granularity because UART-style DockChannel clients require it.
+ */
+ writel_relaxed(1, dc->config_base + CONFIG_RX_THRESH);
+ apple_dockchannel_irq_enable(dc, IRQ_RX);
+ spin_unlock_irqrestore(&dc->lock, flags);
+
+ enable_irq(dc->irq);
+
+ return 0;
+}
+
+static void apple_dockchannel_shutdown(struct mbox_chan *chan)
+{
+ struct apple_dockchannel *dc = chan->con_priv;
+ unsigned long flags;
+
+ disable_irq(dc->irq);
+
+ spin_lock_irqsave(&dc->lock, flags);
+ apple_dockchannel_irq_disable(dc, IRQ_TX | IRQ_RX);
+ dc->tx_active = false;
+ spin_unlock_irqrestore(&dc->lock, flags);
+}
+
+static const struct mbox_chan_ops apple_dockchannel_mbox_ops = {
+ .send_data = apple_dockchannel_send_data,
+ .startup = apple_dockchannel_startup,
+ .shutdown = apple_dockchannel_shutdown,
+};
+
+static irqreturn_t apple_dockchannel_irq(int irq, void *data)
+{
+ struct apple_dockchannel *dc = data;
+ u32 flags;
+ u32 pending;
+ bool tx_done = false;
+
+ flags = readl_relaxed(dc->irq_base + IRQ_FLAG);
+
+ spin_lock(&dc->lock);
+
+ pending = flags & dc->irq_mask & (IRQ_TX | IRQ_RX);
+ if (!pending)
+ goto out_unlock_none;
+
+ if (pending & IRQ_TX) {
+ if (apple_dockchannel_tx_empty(dc)) {
+ apple_dockchannel_irq_disable(dc, IRQ_TX);
+ tx_done = dc->tx_active;
+ dc->tx_active = false;
+ } else {
+ pending &= ~IRQ_TX;
+ }
+ }
+
+ writel_relaxed(pending, dc->irq_base + IRQ_FLAG);
+
+ spin_unlock(&dc->lock);
+
+ if (tx_done)
+ mbox_chan_txdone(&dc->chan, 0);
+
+ if (pending & IRQ_RX)
+ return IRQ_WAKE_THREAD;
+
+ if (pending)
+ return IRQ_HANDLED;
+
+ return IRQ_NONE;
+
+out_unlock_none:
+ spin_unlock(&dc->lock);
+
+ if (flags & (IRQ_TX | IRQ_RX))
+ writel_relaxed(flags & (IRQ_TX | IRQ_RX),
+ dc->irq_base + IRQ_FLAG);
+
+ return IRQ_NONE;
+}
+
+static irqreturn_t apple_dockchannel_irq_thread(int irq, void *data)
+{
+ struct apple_dockchannel *dc = data;
+
+ for (;;) {
+ struct apple_dockchannel_msg msg;
+ size_t avail;
+
+ avail = readl_relaxed(dc->data_base + DATA_RX_COUNT);
+ if (!avail)
+ break;
+
+ avail = min_t(size_t, avail, APPLE_DOCKCHANNEL_FIFO_SIZE);
+
+ apple_dockchannel_read(dc, dc->rx_buf, avail);
+
+ msg.data = dc->rx_buf;
+ msg.len = avail;
+ mbox_chan_received_data(&dc->chan, &msg);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static struct mbox_chan *
+apple_dockchannel_of_xlate(struct mbox_controller *mbox,
+ const struct of_phandle_args *spec)
+{
+ if (spec->args_count != 0)
+ return ERR_PTR(-EINVAL);
+
+ return &mbox->chans[0];
+}
+
+static int apple_dockchannel_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct apple_dockchannel *dc;
+ int ret;
+
+ dc = devm_kzalloc(dev, sizeof(*dc), GFP_KERNEL);
+ if (!dc)
+ return -ENOMEM;
+
+ dc->dev = dev;
+ spin_lock_init(&dc->lock);
+ platform_set_drvdata(pdev, dc);
+
+ dc->irq_base = devm_platform_ioremap_resource_byname(pdev, "irq");
+ if (IS_ERR(dc->irq_base))
+ return PTR_ERR(dc->irq_base);
+
+ dc->config_base = devm_platform_ioremap_resource_byname(pdev, "config");
+ if (IS_ERR(dc->config_base))
+ return PTR_ERR(dc->config_base);
+
+ dc->data_base = devm_platform_ioremap_resource_byname(pdev, "data");
+ if (IS_ERR(dc->data_base))
+ return PTR_ERR(dc->data_base);
+
+ writel_relaxed(0, dc->irq_base + IRQ_MASK);
+ writel_relaxed(~0, dc->irq_base + IRQ_FLAG);
+
+ dc->irq = platform_get_irq(pdev, 0);
+ if (dc->irq < 0)
+ return dc->irq;
+
+ ret = devm_request_threaded_irq(dev, dc->irq, apple_dockchannel_irq,
+ apple_dockchannel_irq_thread, IRQF_ONESHOT,
+ dev_name(dev), dc);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to request IRQ\n");
+
+ disable_irq(dc->irq);
+
+ dc->chan.con_priv = dc;
+ dc->controller.dev = dev;
+ dc->controller.ops = &apple_dockchannel_mbox_ops;
+ dc->controller.chans = &dc->chan;
+ dc->controller.num_chans = 1;
+ dc->controller.txdone_irq = true;
+ dc->controller.of_xlate = apple_dockchannel_of_xlate;
+
+ ret = devm_mbox_controller_register(dev, &dc->controller);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to register mailbox\n");
+
+ return 0;
+}
+
+static const struct of_device_id apple_dockchannel_of_match[] = {
+ { .compatible = "apple,t8122-dockchannel" },
+ { .compatible = "apple,t8112-dockchannel" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, apple_dockchannel_of_match);
+
+static struct platform_driver apple_dockchannel_driver = {
+ .driver = {
+ .name = "apple-dockchannel",
+ .of_match_table = apple_dockchannel_of_match,
+ },
+ .probe = apple_dockchannel_probe,
+};
+module_platform_driver(apple_dockchannel_driver);
+
+MODULE_DESCRIPTION("Apple DockChannel mailbox controller");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_AUTHOR("Michael Reeves <michael.reeves077@gmail.com>");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/include/linux/mailbox/apple-dockchannel.h b/include/linux/mailbox/apple-dockchannel.h
new file mode 100644
index 000000000000..04d2fc44f12f
--- /dev/null
+++ b/include/linux/mailbox/apple-dockchannel.h
@@ -0,0 +1,29 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
+/*
+ * Apple DockChannel mailbox message format.
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#ifndef _LINUX_MAILBOX_APPLE_DOCKCHANNEL_H_
+#define _LINUX_MAILBOX_APPLE_DOCKCHANNEL_H_
+
+#include <linux/types.h>
+
+/**
+ * struct apple_dockchannel_msg - DockChannel mailbox payload
+ * @data: Pointer to the byte stream payload
+ * @len: Number of payload bytes
+ *
+ * For TX, @data must remain valid until mbox_send_message() completes or the
+ * client receives tx_done in non-blocking mode.
+ *
+ * For RX, @data is owned by the controller and is valid only for the duration
+ * of the rx_callback.
+ */
+struct apple_dockchannel_msg {
+ void *data;
+ size_t len;
+};
+
+#endif /* _LINUX_MAILBOX_APPLE_DOCKCHANNEL_H_ */
--
2.51.2
^ permalink raw reply related [flat|nested] 22+ messages in thread* [PATCH 06/10] soc: apple: rtkit: Add tracekit endpoint
2026-06-30 12:54 [PATCH 00/10] Add support for Apple Silicon DockChannel internal keyboards Michael Reeves via B4 Relay
` (4 preceding siblings ...)
2026-06-30 12:54 ` [PATCH 05/10] mailbox: apple: Add DockChannel FIFO controller Michael Reeves via B4 Relay
@ 2026-06-30 12:54 ` Michael Reeves via B4 Relay
2026-06-30 12:54 ` [PATCH 07/10] HID: apple: Add support for DockChannel HID keyboards Michael Reeves via B4 Relay
` (3 subsequent siblings)
9 siblings, 0 replies; 22+ messages in thread
From: Michael Reeves via B4 Relay @ 2026-06-30 12:54 UTC (permalink / raw)
To: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires
Cc: asahi, linux-arm-kernel, linux-kernel, devicetree, iommu,
linux-input, Michael Reeves, Sasha Finkelstein
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
The TraceKit endpoint is a system endpoint used by MTP, AOP, and
potentially other Apple RTKit coprocessors.
Start it automatically when it appears in the endpoint map, like the
other known system endpoints, to avoid warnings about an unknown
endpoint.
Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
---
drivers/soc/apple/rtkit.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c
index a3fdac8f6f06..1059b4bd8732 100644
--- a/drivers/soc/apple/rtkit.c
+++ b/drivers/soc/apple/rtkit.c
@@ -22,6 +22,7 @@ enum {
APPLE_RTKIT_EP_DEBUG = 3,
APPLE_RTKIT_EP_IOREPORT = 4,
APPLE_RTKIT_EP_OSLOG = 8,
+ APPLE_RTKIT_EP_TRACEKIT = 0xa,
};
#define APPLE_RTKIT_MGMT_TYPE GENMASK_ULL(59, 52)
@@ -191,6 +192,7 @@ static void apple_rtkit_management_rx_epmap(struct apple_rtkit *rtk, u64 msg)
case APPLE_RTKIT_EP_DEBUG:
case APPLE_RTKIT_EP_IOREPORT:
case APPLE_RTKIT_EP_OSLOG:
+ case APPLE_RTKIT_EP_TRACEKIT:
dev_dbg(rtk->dev,
"RTKit: Starting system endpoint 0x%02x\n", ep);
apple_rtkit_start_ep(rtk, ep);
--
2.51.2
^ permalink raw reply related [flat|nested] 22+ messages in thread* [PATCH 07/10] HID: apple: Add support for DockChannel HID keyboards
2026-06-30 12:54 [PATCH 00/10] Add support for Apple Silicon DockChannel internal keyboards Michael Reeves via B4 Relay
` (5 preceding siblings ...)
2026-06-30 12:54 ` [PATCH 06/10] soc: apple: rtkit: Add tracekit endpoint Michael Reeves via B4 Relay
@ 2026-06-30 12:54 ` Michael Reeves via B4 Relay
2026-06-30 13:41 ` Sasha Finkelstein
2026-06-30 12:54 ` [PATCH 08/10] HID: apple: Add DockChannel HID transport driver Michael Reeves via B4 Relay
` (2 subsequent siblings)
9 siblings, 1 reply; 22+ messages in thread
From: Michael Reeves via B4 Relay @ 2026-06-30 12:54 UTC (permalink / raw)
To: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires
Cc: asahi, linux-arm-kernel, linux-kernel, devicetree, iommu,
linux-input, Michael Reeves
From: Michael Reeves <michael.reeves077@gmail.com>
DockChannel keyboards are registered as host-bus Apple HID devices
instead of USB or Bluetooth devices.
Match them in hid-apple, use the modern Magic Keyboard function-key
table, and fix up the oversized report-size descriptor pattern before
parsing.
Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
---
drivers/hid/hid-apple.c | 139 ++++++++++++++++++++++++++++++++----------------
include/linux/hid.h | 1 +
2 files changed, 94 insertions(+), 46 deletions(-)
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index bf7dd0fbf249..47c6ec09d5fa 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -390,6 +390,12 @@ static bool apple_is_omoton_kb066(struct hid_device *hdev)
strcmp(hdev->name, "Bluetooth Keyboard") == 0;
}
+static bool apple_is_dockchannel_keyboard(struct hid_device *hdev)
+{
+ return hdev->bus == BUS_HOST &&
+ hdev->group == HID_GROUP_APPLE_DOCKCHANNEL;
+}
+
static inline void apple_setup_key_translation(struct input_dev *input,
const struct apple_key_translation *table)
{
@@ -477,53 +483,57 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
asc->fn_on = !!value;
if (real_fnmode) {
- switch (hid->product) {
- case USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI:
- case USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO:
- case USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS:
- case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI:
- case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO:
- case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS:
- case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI:
- case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO:
- case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS:
- table = magic_keyboard_alu_fn_keys;
- break;
- case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015:
- case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015:
- table = magic_keyboard_2015_fn_keys;
- break;
- case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021:
- case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021:
- case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021:
- case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024:
- case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2024:
- case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2024:
+ if (apple_is_dockchannel_keyboard(hid)) {
table = magic_keyboard_2021_and_2024_fn_keys;
- break;
- case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132:
- case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213:
- case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680:
- case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680_ALT:
- table = macbookpro_no_esc_fn_keys;
- break;
- case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F:
- case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K:
- case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223:
- table = macbookpro_dedicated_esc_fn_keys;
- break;
- case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K:
- case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K:
- table = apple_fn_keys;
- break;
- default:
- if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
- hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
- table = macbookair_fn_keys;
- else if (hid->product < 0x21d || hid->product >= 0x300)
- table = powerbook_fn_keys;
- else
+ } else {
+ switch (hid->product) {
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_ANSI:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_ISO:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_JIS:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ANSI:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_ISO:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2009_JIS:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ANSI:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_ISO:
+ case USB_DEVICE_ID_APPLE_ALU_WIRELESS_2011_JIS:
+ table = magic_keyboard_alu_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2015:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2015:
+ table = magic_keyboard_2015_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2021:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2021:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_2024:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_FINGERPRINT_2024:
+ case USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2024:
+ table = magic_keyboard_2021_and_2024_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J132:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J213:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J680_ALT:
+ table = macbookpro_no_esc_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J152F:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J214K:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J223:
+ table = macbookpro_dedicated_esc_fn_keys;
+ break;
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J140K:
+ case USB_DEVICE_ID_APPLE_WELLSPRINGT2_J230K:
table = apple_fn_keys;
+ break;
+ default:
+ if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
+ hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
+ table = macbookair_fn_keys;
+ else if (hid->product < 0x21d || hid->product >= 0x300)
+ table = powerbook_fn_keys;
+ else
+ table = apple_fn_keys;
+ }
}
trans = apple_find_translation(table, code);
@@ -659,6 +669,7 @@ static void apple_battery_timer_tick(struct timer_list *t)
/*
* MacBook JIS keyboard has wrong logical maximum
* Magic Keyboard JIS has wrong logical maximum
+ * Internal DockChannel keyboards can advertise oversized report sizes
*/
static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
unsigned int *rsize)
@@ -699,6 +710,27 @@ static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
rdesc[3] = 0x06;
}
+ if (apple_is_dockchannel_keyboard(hdev) && *rsize >= 5) {
+ int i;
+
+ for (i = 0; i <= *rsize - 5; i++) {
+ if (rdesc[i] == 0x76 && rdesc[i + 1] == 0x00 &&
+ rdesc[i + 2] == 0x40 && rdesc[i + 3] == 0x95) {
+ u8 count = rdesc[i + 4];
+
+ if (count > 0 && count < 32) {
+ hid_info(hdev,
+ "fixing up DockChannel report size\n");
+ rdesc[i] = 0x75;
+ rdesc[i + 1] = 0x08;
+ rdesc[i + 2] = 0x96;
+ rdesc[i + 3] = 0x00;
+ rdesc[i + 4] = count * 8;
+ }
+ }
+ }
+ }
+
return rdesc;
}
@@ -763,7 +795,7 @@ static int apple_input_configured(struct hid_device *hdev,
struct apple_sc *asc = hid_get_drvdata(hdev);
if (((asc->quirks & APPLE_HAS_FN) && !asc->fn_found) || apple_is_omoton_kb066(hdev)) {
- hid_info(hdev, "Fn key not found (Apple Wireless Keyboard clone?), disabling Fn key handling\n");
+ hid_info(hdev, "Disabling function quirk for device without function key\n");
asc->quirks &= ~APPLE_HAS_FN;
}
@@ -1003,6 +1035,17 @@ static void apple_remove(struct hid_device *hdev)
hid_hw_stop(hdev);
}
+static bool apple_match(struct hid_device *hdev, bool ignore_special_driver)
+{
+ if (ignore_special_driver)
+ return false;
+
+ if (hdev->group == HID_GROUP_APPLE_DOCKCHANNEL)
+ return apple_is_dockchannel_keyboard(hdev);
+
+ return true;
+}
+
static const struct hid_device_id apple_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MIGHTYMOUSE),
.driver_data = APPLE_MIGHTYMOUSE | APPLE_INVERT_HWHEEL },
@@ -1224,6 +1267,9 @@ static const struct hid_device_id apple_devices[] = {
.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT),
.driver_data = APPLE_MAGIC_BACKLIGHT },
+ { HID_DEVICE(BUS_HOST, HID_GROUP_APPLE_DOCKCHANNEL,
+ HID_ANY_ID, HID_ANY_ID),
+ .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
{ }
};
@@ -1232,6 +1278,7 @@ MODULE_DEVICE_TABLE(hid, apple_devices);
static struct hid_driver apple_driver = {
.name = "apple",
.id_table = apple_devices,
+ .match = apple_match,
.report_fixup = apple_report_fixup,
.probe = apple_probe,
.remove = apple_remove,
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 47dc0bc89fa4..0d40deec6295 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -441,6 +441,7 @@ struct hid_item {
#define HID_GROUP_STEAM 0x0103
#define HID_GROUP_LOGITECH_27MHZ_DEVICE 0x0104
#define HID_GROUP_VIVALDI 0x0105
+#define HID_GROUP_APPLE_DOCKCHANNEL 0x0106
/*
* HID protocol status
--
2.51.2
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH 07/10] HID: apple: Add support for DockChannel HID keyboards
2026-06-30 12:54 ` [PATCH 07/10] HID: apple: Add support for DockChannel HID keyboards Michael Reeves via B4 Relay
@ 2026-06-30 13:41 ` Sasha Finkelstein
2026-07-01 14:01 ` Michael Reeves
0 siblings, 1 reply; 22+ messages in thread
From: Sasha Finkelstein @ 2026-06-30 13:41 UTC (permalink / raw)
To: michael.reeves077
Cc: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires, asahi, linux-arm-kernel,
linux-kernel, devicetree, iommu, linux-input
> On Jun 30, 2026, at 14:54, Michael Reeves via B4 Relay <devnull+michael.reeves077.gmail.com@kernel.org> wrote:
>
> @@ -659,6 +669,7 @@ static void apple_battery_timer_tick(struct timer_list *t)
> /*
> * MacBook JIS keyboard has wrong logical maximum
> * Magic Keyboard JIS has wrong logical maximum
> + * Internal DockChannel keyboards can advertise oversized report sizes
> */
> static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> unsigned int *rsize)
> @@ -699,6 +710,27 @@ static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc,
> rdesc[3] = 0x06;
> }
>
> + if (apple_is_dockchannel_keyboard(hdev) && *rsize >= 5) {
> + int i;
> +
> + for (i = 0; i <= *rsize - 5; i++) {
> + if (rdesc[i] == 0x76 && rdesc[i + 1] == 0x00 &&
> + rdesc[i + 2] == 0x40 && rdesc[i + 3] == 0x95) {
> + u8 count = rdesc[i + 4];
> +
> + if (count > 0 && count < 32) {
> + hid_info(hdev,
> + "fixing up DockChannel report size\n");
> + rdesc[i] = 0x75;
> + rdesc[i + 1] = 0x08;
> + rdesc[i + 2] = 0x96;
> + rdesc[i + 3] = 0x00;
> + rdesc[i + 4] = count * 8;
> + }
> + }
> + }
> + }
> +
> return rdesc;
> }
>
It looks like this section is duplicated in the following commit (8).
Is that correct?
^ permalink raw reply [flat|nested] 22+ messages in thread* Re: [PATCH 07/10] HID: apple: Add support for DockChannel HID keyboards
2026-06-30 13:41 ` Sasha Finkelstein
@ 2026-07-01 14:01 ` Michael Reeves
0 siblings, 0 replies; 22+ messages in thread
From: Michael Reeves @ 2026-07-01 14:01 UTC (permalink / raw)
To: Sasha Finkelstein
Cc: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires, asahi, linux-arm-kernel,
linux-kernel, devicetree, iommu, linux-input
On Tue, Jun 30, 2026 at 11:41 PM Sasha Finkelstein <k@chaosmail.tech> wrote:
[...]
> It looks like this section is duplicated in the following commit (8).
> Is that correct?
>
Yes, it is duplicated, thank you for the pick up. I moved the fixup to
the transport layer driver (in the following commit 8), which works
better, but must have forgotten to delete it here.
I will correct this in v2.
Thank you again!
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 08/10] HID: apple: Add DockChannel HID transport driver
2026-06-30 12:54 [PATCH 00/10] Add support for Apple Silicon DockChannel internal keyboards Michael Reeves via B4 Relay
` (6 preceding siblings ...)
2026-06-30 12:54 ` [PATCH 07/10] HID: apple: Add support for DockChannel HID keyboards Michael Reeves via B4 Relay
@ 2026-06-30 12:54 ` Michael Reeves via B4 Relay
2026-06-30 13:21 ` Yureka Lilian
2026-07-02 19:32 ` Julian Braha
2026-06-30 12:54 ` [PATCH 09/10] arm64: dts: apple: Add MTP DockChannel HID nodes Michael Reeves via B4 Relay
2026-06-30 12:54 ` [PATCH 10/10] arm64: dts: apple: Enable DockChannel HID on M2 and M3 laptops Michael Reeves via B4 Relay
9 siblings, 2 replies; 22+ messages in thread
From: Michael Reeves via B4 Relay @ 2026-06-30 12:54 UTC (permalink / raw)
To: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires
Cc: asahi, linux-arm-kernel, linux-kernel, devicetree, iommu,
linux-input, Michael Reeves
From: Michael Reeves <michael.reeves077@gmail.com>
Apple MTP exposes internal keyboard and trackpad interfaces over a HID
transport carried by DockChannel.
Add a transport driver that boots the MTP RTKit coprocessor, exchanges
HID packets through the DockChannel mailbox, and registers child HID
interfaces from devicetree.
Co-developed-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
---
MAINTAINERS | 1 +
drivers/hid/Kconfig | 2 +
drivers/hid/Makefile | 2 +
drivers/hid/dockchannel/Kconfig | 15 +
drivers/hid/dockchannel/Makefile | 3 +
drivers/hid/dockchannel/apple-hid.c | 1130 +++++++++++++++++++++++++++++++++++
6 files changed, 1153 insertions(+)
diff --git a/MAINTAINERS b/MAINTAINERS
index ed68452c0ad6..0063276f0349 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2620,6 +2620,7 @@ F: drivers/clk/clk-apple-nco.c
F: drivers/cpufreq/apple-soc-cpufreq.c
F: drivers/dma/apple-admac.c
F: drivers/gpio/gpio-macsmc.c
+F: drivers/hid/dockchannel/
F: drivers/hwmon/macsmc-hwmon.c
F: drivers/pmdomain/apple/
F: drivers/i2c/busses/i2c-pasemi-core.c
diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index f9bcaeb66385..f27cda601ede 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1488,6 +1488,8 @@ source "drivers/hid/surface-hid/Kconfig"
source "drivers/hid/intel-thc-hid/Kconfig"
+source "drivers/hid/dockchannel/Kconfig"
+
endif # HID
# USB support may be used with HID disabled
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 23e6e3dd0c56..c9b4b1aff247 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -182,3 +182,5 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/
obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/
+
+obj-$(CONFIG_APPLE_DOCKCHANNEL_HID) += dockchannel/
diff --git a/drivers/hid/dockchannel/Kconfig b/drivers/hid/dockchannel/Kconfig
new file mode 100644
index 000000000000..fca09ef74403
--- /dev/null
+++ b/drivers/hid/dockchannel/Kconfig
@@ -0,0 +1,15 @@
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+config APPLE_DOCKCHANNEL_HID
+ tristate "HID over Apple DockChannel"
+ depends on APPLE_DOCKCHANNEL
+ depends on APPLE_RTKIT
+ depends on HID
+ depends on INPUT
+ depends on OF
+ help
+ This provides a HID transport layer over the Apple DockChannel
+ mailbox interface. It is required to support the internal keyboard
+ and trackpad on M2 and later MacBook models.
+
+ Say Y here if you have an M2 or later Apple MacBook.
diff --git a/drivers/hid/dockchannel/Makefile b/drivers/hid/dockchannel/Makefile
new file mode 100644
index 000000000000..d1a82aa57a69
--- /dev/null
+++ b/drivers/hid/dockchannel/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+obj-$(CONFIG_APPLE_DOCKCHANNEL_HID) += apple-hid.o
diff --git a/drivers/hid/dockchannel/apple-hid.c b/drivers/hid/dockchannel/apple-hid.c
new file mode 100644
index 000000000000..162fcfb5ab1c
--- /dev/null
+++ b/drivers/hid/dockchannel/apple-hid.c
@@ -0,0 +1,1130 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple DockChannel HID transport driver
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/ctype.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/hid.h>
+#include <linux/mailbox/apple-dockchannel.h>
+#include <linux/mailbox_client.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/slab.h>
+#include <linux/soc/apple/rtkit.h>
+#include <linux/spinlock.h>
+#include <linux/string.h>
+#include <linux/unaligned.h>
+#include <linux/workqueue.h>
+
+#define APPLE_ASC_CPU_CONTROL 0x44
+#define APPLE_ASC_CPU_CONTROL_RUN BIT(4)
+
+#define COMMAND_TIMEOUT_MS 1000
+#define START_TIMEOUT_MS 2000
+
+#define MAX_INTERFACES 16
+
+#define DCHID_MAX_PAYLOAD 0xffff
+#define DCHID_CHECKSUM_LEN 4
+#define DCHID_RX_BUF_SIZE (sizeof(struct dchid_hdr) + DCHID_MAX_PAYLOAD + \
+ DCHID_CHECKSUM_LEN)
+
+#define DCHID_CHANNEL_CMD 0x11
+#define DCHID_CHANNEL_REPORT 0x12
+#define DCHID_CHECKSUM_SEED 0xffffffff
+
+struct dchid_hdr {
+ u8 hdr_len;
+ u8 channel;
+ __le16 length;
+ u8 seq;
+ u8 iface;
+ __le16 pad;
+} __packed;
+
+#define IFACE_COMM 0
+
+#define FLAGS_GROUP GENMASK(7, 6)
+#define FLAGS_REQ GENMASK(5, 0)
+
+#define REQ_SET_REPORT 0
+#define REQ_GET_REPORT 1
+
+struct dchid_subhdr {
+ u8 flags;
+ u8 unk;
+ __le16 length;
+ __le32 retcode;
+} __packed;
+
+#define EVENT_INIT 0xf0
+#define EVENT_READY 0xf1
+
+struct dchid_init_hdr {
+ u8 type;
+ u8 unk1;
+ u8 unk2;
+ u8 iface;
+ char name[16];
+ u8 more_packets;
+ u8 unkpad;
+} __packed;
+
+#define INIT_HID_DESCRIPTOR 0
+#define INIT_TERMINATOR 2
+#define INIT_PRODUCT_NAME 7
+
+#define CMD_RESET_INTERFACE 0x40
+#define CMD_RESET_INTERFACE_SUB 1
+#define CMD_ENABLE_INTERFACE 0xb4
+
+struct dchid_init_block_hdr {
+ __le16 type;
+ __le16 length;
+} __packed;
+
+#define STM_REPORT_ID 0x10
+#define STM_REPORT_SERIAL 0x11
+
+struct dchid_stm_id {
+ u8 unk;
+ __le16 vendor_id;
+ __le16 product_id;
+ __le16 version_number;
+ u8 unk2;
+ u8 unk3;
+ u8 keyboard_type;
+ u8 serial_length;
+ /* Serial follows, but we grab it with a different report. */
+} __packed;
+
+struct dchid_work {
+ struct work_struct work;
+ struct dchid_iface *iface;
+
+ struct dchid_hdr hdr;
+ u8 data[];
+};
+
+struct dchid_iface {
+ struct dchid_dev *dchid;
+ struct hid_device *hid;
+ struct workqueue_struct *wq;
+
+ bool creating;
+ struct work_struct create_work;
+
+ int index;
+ const char *name;
+ struct fwnode_handle *fwnode;
+
+ u8 tx_seq;
+ bool deferred;
+ bool starting;
+ bool open;
+ struct completion ready;
+
+ void *hid_desc;
+ size_t hid_desc_len;
+
+ /* Lock for command submission state below */
+ spinlock_t out_lock;
+ u32 out_flags;
+ int out_report;
+ u32 retcode;
+ void *resp_buf;
+ size_t resp_size;
+ struct completion out_complete;
+};
+
+struct dchid_dev {
+ struct device *dev;
+ struct mbox_client dc_mbox_client;
+ struct mbox_chan *dc_mbox;
+
+ struct apple_rtkit *rtk;
+ void __iomem *asc_base;
+ void __iomem *sram_base;
+ struct resource sram_res;
+
+ bool id_ready;
+ struct dchid_stm_id device_id;
+ char serial[64];
+
+ u8 *rx_buf;
+ size_t rx_len;
+
+ struct dchid_iface *comm;
+ struct mutex ifaces_lock; /* protects ifaces array */
+ struct dchid_iface *ifaces[MAX_INTERFACES];
+
+ /* Workqueue to asynchronously create HID devices */
+ struct workqueue_struct *new_iface_wq;
+};
+
+static void dchid_destroy_wq(void *data)
+{
+ struct workqueue_struct *wq = data;
+
+ destroy_workqueue(wq);
+}
+
+static void dchid_fwnode_release(void *data)
+{
+ fwnode_handle_put(data);
+}
+
+static void dchid_free_mbox(void *data)
+{
+ mbox_free_channel(data);
+}
+
+static u32 dchid_checksum(const void *data, size_t len)
+{
+ const u8 *p = data;
+ u32 sum = 0;
+ int i;
+
+ while (len >= sizeof(u32)) {
+ sum += get_unaligned_le32(p);
+ p += sizeof(u32);
+ len -= sizeof(u32);
+ }
+
+ if (len) {
+ u32 tmp = 0;
+
+ for (i = 0; i < len; i++)
+ tmp |= p[i] << (i * 8);
+ sum += tmp;
+ }
+
+ return sum;
+}
+
+static struct dchid_iface *
+dchid_get_interface(struct dchid_dev *dchid, int index, const char *name)
+{
+ struct dchid_iface *iface;
+ struct fwnode_handle *fwnode;
+ int ret;
+
+ if (index >= MAX_INTERFACES) {
+ dev_err(dchid->dev, "interface index %d out of range\n", index);
+ return NULL;
+ }
+
+ mutex_lock(&dchid->ifaces_lock);
+ if (dchid->ifaces[index]) {
+ iface = dchid->ifaces[index];
+ mutex_unlock(&dchid->ifaces_lock);
+ return iface;
+ }
+
+ iface = devm_kzalloc(dchid->dev, sizeof(*iface), GFP_KERNEL);
+ if (!iface) {
+ mutex_unlock(&dchid->ifaces_lock);
+ return NULL;
+ }
+
+ iface->index = index;
+ iface->name = devm_kstrdup(dchid->dev, name, GFP_KERNEL);
+ if (!iface->name) {
+ mutex_unlock(&dchid->ifaces_lock);
+ return NULL;
+ }
+
+ iface->dchid = dchid;
+ iface->out_report = -1;
+ init_completion(&iface->out_complete);
+ init_completion(&iface->ready);
+ spin_lock_init(&iface->out_lock);
+
+ iface->wq = alloc_ordered_workqueue("dchid-%s", 0, iface->name);
+ if (!iface->wq) {
+ mutex_unlock(&dchid->ifaces_lock);
+ return NULL;
+ }
+
+ ret = devm_add_action_or_reset(dchid->dev, dchid_destroy_wq, iface->wq);
+ if (ret) {
+ mutex_unlock(&dchid->ifaces_lock);
+ return NULL;
+ }
+
+ if (!strcmp(name, "comm")) {
+ /* Comm is not a HID subdevice */
+ dchid->ifaces[index] = iface;
+ mutex_unlock(&dchid->ifaces_lock);
+ return iface;
+ }
+
+ fwnode = device_get_named_child_node(dchid->dev, name);
+ if (fwnode) {
+ iface->fwnode = fwnode;
+ ret = devm_add_action_or_reset(dchid->dev, dchid_fwnode_release,
+ iface->fwnode);
+ if (ret) {
+ mutex_unlock(&dchid->ifaces_lock);
+ return NULL;
+ }
+ } else {
+ iface->fwnode = dev_fwnode(dchid->dev);
+ }
+
+ dchid->ifaces[index] = iface;
+ mutex_unlock(&dchid->ifaces_lock);
+ return iface;
+}
+
+static int dchid_send(struct dchid_iface *iface, u32 flags, const void *msg,
+ size_t size)
+{
+ struct dchid_dev *dchid = iface->dchid;
+ size_t payload_padded = round_up(size, sizeof(u32));
+ size_t total_len = sizeof(struct dchid_hdr) + sizeof(struct dchid_subhdr) +
+ payload_padded + DCHID_CHECKSUM_LEN;
+ struct apple_dockchannel_msg dc_msg;
+ struct dchid_hdr *hdr;
+ struct dchid_subhdr *sub;
+ u32 *checksum_ptr;
+ u8 *buf;
+ int ret;
+
+ if (total_len > DCHID_RX_BUF_SIZE)
+ return -EINVAL;
+
+ buf = kzalloc(total_len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ hdr = (struct dchid_hdr *)buf;
+ sub = (struct dchid_subhdr *)(buf + sizeof(*hdr));
+ checksum_ptr = (u32 *)(buf + total_len - DCHID_CHECKSUM_LEN);
+
+ hdr->hdr_len = sizeof(*hdr);
+ hdr->channel = DCHID_CHANNEL_CMD;
+ hdr->length = cpu_to_le16(payload_padded + sizeof(*sub));
+ hdr->seq = iface->tx_seq;
+ hdr->iface = iface->index;
+
+ sub->flags = (u8)flags;
+ sub->length = cpu_to_le16(size);
+
+ memcpy(buf + sizeof(*hdr) + sizeof(*sub), msg, size);
+
+ *checksum_ptr = 0xffffffff - dchid_checksum(buf, total_len - DCHID_CHECKSUM_LEN);
+
+ dc_msg.data = buf;
+ dc_msg.len = total_len;
+ ret = mbox_send_message(dchid->dc_mbox, &dc_msg);
+ kfree(buf);
+
+ return ret < 0 ? ret : 0;
+}
+
+static int dchid_cmd(struct dchid_iface *iface, u32 type, u32 req,
+ void *data, size_t size, void *resp_buf, size_t resp_size)
+{
+ unsigned long flags;
+ int ret;
+ int report_id;
+ bool timed_out = false;
+ u32 out_flags;
+
+ if (size < 1)
+ return -EINVAL;
+
+ report_id = *(u8 *)data;
+ out_flags = FIELD_PREP(FLAGS_GROUP, type) | FIELD_PREP(FLAGS_REQ, req);
+
+ spin_lock_irqsave(&iface->out_lock, flags);
+
+ /* Only one command can be in flight per interface */
+ if (WARN_ON(iface->out_report != -1)) {
+ spin_unlock_irqrestore(&iface->out_lock, flags);
+ return -EBUSY;
+ }
+
+ iface->out_report = report_id;
+ iface->out_flags = out_flags;
+ iface->retcode = 0;
+ iface->resp_buf = resp_buf;
+ iface->resp_size = resp_size;
+ reinit_completion(&iface->out_complete);
+
+ spin_unlock_irqrestore(&iface->out_lock, flags);
+
+ ret = dchid_send(iface, out_flags, data, size);
+ if (ret < 0) {
+ spin_lock_irqsave(&iface->out_lock, flags);
+ iface->out_report = -1;
+ iface->resp_buf = NULL;
+ iface->resp_size = 0;
+ spin_unlock_irqrestore(&iface->out_lock, flags);
+ return ret;
+ }
+
+ if (!wait_for_completion_timeout(&iface->out_complete,
+ msecs_to_jiffies(COMMAND_TIMEOUT_MS))) {
+ dev_err(iface->dchid->dev, "command 0x%x to iface %d (%s) timed out\n",
+ report_id, iface->index, iface->name);
+ timed_out = true;
+ }
+
+ spin_lock_irqsave(&iface->out_lock, flags);
+
+ if (timed_out && iface->out_report == report_id) {
+ ret = -ETIMEDOUT;
+ } else if (iface->retcode) {
+ dev_err(iface->dchid->dev,
+ "command 0x%x to iface %d (%s) failed with err 0x%x\n",
+ report_id, iface->index, iface->name, iface->retcode);
+ ret = -EIO;
+ } else {
+ ret = iface->resp_size;
+ }
+
+ iface->tx_seq++;
+ iface->out_report = -1;
+ iface->resp_buf = NULL;
+ iface->resp_size = 0;
+ spin_unlock_irqrestore(&iface->out_lock, flags);
+
+ return ret;
+}
+
+static int dchid_comm_cmd(struct dchid_dev *dchid, void *cmd, size_t size)
+{
+ return dchid_cmd(dchid->comm, HID_FEATURE_REPORT, REQ_SET_REPORT,
+ cmd, size, NULL, 0);
+}
+
+static int dchid_enable_interface(struct dchid_iface *iface)
+{
+ u8 cmd[] = { CMD_ENABLE_INTERFACE, iface->index };
+
+ return dchid_comm_cmd(iface->dchid, cmd, sizeof(cmd));
+}
+
+static int dchid_reset_interface(struct dchid_iface *iface, int state)
+{
+ u8 cmd[] = { CMD_RESET_INTERFACE, CMD_RESET_INTERFACE_SUB, iface->index,
+ (u8)state };
+
+ return dchid_comm_cmd(iface->dchid, cmd, sizeof(cmd));
+}
+
+static int dchid_start_interface(struct dchid_iface *iface)
+{
+ if (iface->starting)
+ return -EINPROGRESS;
+
+ dev_dbg(iface->dchid->dev, "starting interface %s\n", iface->name);
+
+ iface->starting = true;
+ dchid_reset_interface(iface, 0);
+ dchid_reset_interface(iface, 2);
+
+ return 0;
+}
+
+static int dchid_start(struct hid_device *hdev)
+{
+ return 0;
+}
+
+static int dchid_open(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+ int ret;
+
+ if (!completion_done(&iface->ready)) {
+ ret = dchid_start_interface(iface);
+ if (ret < 0)
+ return ret;
+
+ if (!wait_for_completion_timeout(&iface->ready,
+ msecs_to_jiffies(START_TIMEOUT_MS))) {
+ dev_err(iface->dchid->dev, "iface %s start timed out\n",
+ iface->name);
+ return -ETIMEDOUT;
+ }
+ }
+
+ iface->open = true;
+ return 0;
+}
+
+static void dchid_close(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ iface->open = false;
+}
+
+static int dchid_parse(struct hid_device *hdev)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ return hid_parse_report(hdev, iface->hid_desc, iface->hid_desc_len);
+}
+
+/* Note: buf excludes report number. */
+static int dchid_get_report_cmd(struct dchid_iface *iface, u8 reportnum,
+ void *buf, size_t len)
+{
+ int ret;
+
+ ret = dchid_cmd(iface, HID_FEATURE_REPORT, REQ_GET_REPORT, &reportnum, 1,
+ buf, len);
+
+ return ret <= 0 ? ret : ret - 1;
+}
+
+/* Note: buf includes report number. */
+static int dchid_set_report(struct dchid_iface *iface, void *buf, size_t len)
+{
+ return dchid_cmd(iface, HID_OUTPUT_REPORT, REQ_SET_REPORT, buf, len,
+ NULL, 0);
+}
+
+static int dchid_raw_request(struct hid_device *hdev, unsigned char reportnum,
+ __u8 *buf, size_t len, unsigned char rtype,
+ int reqtype)
+{
+ struct dchid_iface *iface = hdev->driver_data;
+
+ switch (reqtype) {
+ case HID_REQ_GET_REPORT:
+ if (len < 1)
+ return -EINVAL;
+
+ buf[0] = reportnum;
+ return dchid_cmd(iface, rtype, REQ_GET_REPORT, &reportnum, 1,
+ buf + 1, len - 1);
+ case HID_REQ_SET_REPORT:
+ return dchid_set_report(iface, buf, len);
+ default:
+ return -EIO;
+ }
+}
+
+static const struct hid_ll_driver dchid_ll = {
+ .start = dchid_start,
+ .open = dchid_open,
+ .close = dchid_close,
+ .parse = dchid_parse,
+ .raw_request = dchid_raw_request,
+};
+
+static void dchid_create_interface_work(struct work_struct *ws)
+{
+ struct dchid_iface *iface = container_of(ws, struct dchid_iface, create_work);
+ struct dchid_dev *dchid = iface->dchid;
+ struct hid_device *hid;
+ char cap_name[16];
+ int ret;
+
+ if (iface->hid) {
+ dev_warn(dchid->dev, "interface %s already created\n", iface->name);
+ goto done;
+ }
+
+ ret = dchid_enable_interface(iface);
+ if (ret < 0) {
+ dev_warn(dchid->dev, "failed to enable %s: %d\n", iface->name, ret);
+ goto done;
+ }
+
+ iface->deferred = false;
+
+ hid = hid_allocate_device();
+ if (IS_ERR(hid))
+ goto done;
+
+ strscpy(cap_name, iface->name, sizeof(cap_name));
+ if (cap_name[0])
+ cap_name[0] = toupper(cap_name[0]);
+ snprintf(hid->name, sizeof(hid->name), "Apple DockChannel %s", cap_name);
+
+ snprintf(hid->phys, sizeof(hid->phys), "%s.%d", dev_name(dchid->dev),
+ iface->index);
+ strscpy(hid->uniq, dchid->serial, sizeof(hid->uniq));
+
+ hid->ll_driver = &dchid_ll;
+ hid->bus = BUS_HOST;
+ hid->vendor = le16_to_cpu(dchid->device_id.vendor_id);
+ hid->product = le16_to_cpu(dchid->device_id.product_id);
+ hid->version = le16_to_cpu(dchid->device_id.version_number);
+ hid->type = HID_TYPE_OTHER;
+ if (!strcmp(iface->name, "keyboard")) {
+ u32 country_code;
+
+ hid->group = HID_GROUP_APPLE_DOCKCHANNEL;
+
+ /*
+ * The device provides no reliable way to get the keyboard
+ * country code, so board devicetrees provide it instead,
+ * filled by the bootloader.
+ */
+ if (!fwnode_property_read_u32(iface->fwnode, "hid-country-code",
+ &country_code))
+ hid->country = country_code;
+ }
+
+ hid->dev.parent = iface->dchid->dev;
+ hid->driver_data = iface;
+ iface->hid = hid;
+
+ ret = hid_add_device(hid);
+ if (ret < 0) {
+ iface->hid = NULL;
+ hid_destroy_device(hid);
+ dev_warn(iface->dchid->dev, "failed to register HID device %s\n",
+ iface->name);
+ }
+
+done:
+ iface->creating = false;
+}
+
+static int dchid_create_interface(struct dchid_iface *iface)
+{
+ if (iface->creating)
+ return -EBUSY;
+
+ iface->creating = true;
+ INIT_WORK(&iface->create_work, dchid_create_interface_work);
+ return queue_work(iface->dchid->new_iface_wq, &iface->create_work);
+}
+
+static void dchid_handle_descriptor(struct dchid_iface *iface, void *hid_desc,
+ size_t desc_len)
+{
+ u8 *rdesc;
+ int i;
+
+ if (iface->hid)
+ return;
+
+ rdesc = devm_kmemdup(iface->dchid->dev, hid_desc, desc_len,
+ GFP_KERNEL);
+ if (!rdesc)
+ return;
+
+ /* Fix up oversized report sizes in DockChannel report descriptors */
+ if (desc_len >= 5) {
+ for (i = 0; i <= (int)desc_len - 5; i++) {
+ if (rdesc[i] == 0x76 && rdesc[i + 1] == 0x00 &&
+ rdesc[i + 2] == 0x40 && rdesc[i + 3] == 0x95) {
+ u8 count = rdesc[i + 4];
+
+ if (count > 0 && count < 32) {
+ dev_info(iface->dchid->dev,
+ "fixing up interface %s (%d) report size\n",
+ iface->name, iface->index);
+ rdesc[i] = 0x75;
+ rdesc[i + 1] = 0x08;
+ rdesc[i + 2] = 0x96;
+ rdesc[i + 3] = 0x00;
+ rdesc[i + 4] = count * 8;
+ }
+ }
+ }
+ }
+
+ iface->hid_desc = rdesc;
+ iface->hid_desc_len = desc_len;
+}
+
+static void dchid_handle_ready(struct dchid_dev *dchid, void *data, size_t length)
+{
+ struct dchid_iface *iface;
+ u8 *pkt = data;
+ u8 index;
+ int i;
+ int ret;
+
+ if (length < 2)
+ return;
+
+ index = pkt[1];
+ if (index >= MAX_INTERFACES)
+ return;
+
+ iface = dchid->ifaces[index];
+ if (!iface)
+ return;
+
+ dev_dbg(dchid->dev, "interface %s is now ready\n", iface->name);
+ complete_all(&iface->ready);
+
+ /* When STM is ready, grab global device info */
+ if (!strcmp(iface->name, "stm")) {
+ ret = dchid_get_report_cmd(iface, STM_REPORT_ID, &dchid->device_id,
+ sizeof(dchid->device_id));
+ if (ret < (int)sizeof(dchid->device_id)) {
+ dev_warn(iface->dchid->dev, "failed to get device ID from STM\n");
+ /* Fake it and keep going. Things might still work. */
+ memset(&dchid->device_id, 0, sizeof(dchid->device_id));
+ }
+
+ ret = dchid_get_report_cmd(iface, STM_REPORT_SERIAL, dchid->serial,
+ sizeof(dchid->serial) - 1);
+ if (ret < 0) {
+ dev_warn(iface->dchid->dev, "failed to get serial from STM\n");
+ dchid->serial[0] = 0;
+ }
+
+ dchid->id_ready = true;
+ for (i = 0; i < MAX_INTERFACES; i++) {
+ if (!dchid->ifaces[i] || !dchid->ifaces[i]->deferred)
+ continue;
+ dchid_create_interface(dchid->ifaces[i]);
+ }
+ }
+}
+
+static void dchid_handle_init(struct dchid_dev *dchid, void *data, size_t length)
+{
+ struct dchid_init_hdr *hdr = data;
+ struct dchid_init_block_hdr *blk;
+ struct dchid_iface *iface;
+ u8 *p = data;
+
+ if (length < sizeof(*hdr))
+ return;
+
+ iface = dchid_get_interface(dchid, hdr->iface, hdr->name);
+ if (!iface)
+ return;
+
+ p += sizeof(*hdr);
+ length -= sizeof(*hdr);
+
+ while (length >= sizeof(*blk)) {
+ u16 blk_len;
+
+ blk = (struct dchid_init_block_hdr *)p;
+ p += sizeof(*blk);
+ length -= sizeof(*blk);
+
+ blk_len = le16_to_cpu(blk->length);
+ if (blk_len > length)
+ break;
+
+ switch (le16_to_cpu(blk->type)) {
+ case INIT_HID_DESCRIPTOR:
+ dchid_handle_descriptor(iface, p, blk_len);
+ break;
+ case INIT_PRODUCT_NAME:
+ if (blk_len > 0 && p[blk_len - 1] != 0)
+ dev_warn(dchid->dev, "unterminated product name for %s\n",
+ iface->name);
+ break;
+ }
+
+ p += blk_len;
+ length -= blk_len;
+
+ if (le16_to_cpu(blk->type) == INIT_TERMINATOR)
+ break;
+ }
+
+ if (hdr->more_packets)
+ return;
+
+ /*
+ * Prefer to enable STM first, since it provides device IDs. Some
+ * firmware versions do not expose STM, so let the keyboard start
+ * without it.
+ */
+ if (iface->dchid->id_ready || !strcmp(iface->name, "stm") ||
+ !strcmp(iface->name, "keyboard"))
+ dchid_create_interface(iface);
+ else
+ iface->deferred = true;
+}
+
+static void dchid_handle_event(struct dchid_dev *dchid, void *data, size_t length)
+{
+ u8 *p = data;
+
+ if (!length)
+ return;
+
+ switch (*p) {
+ case EVENT_INIT:
+ dchid_handle_init(dchid, data, length);
+ break;
+ case EVENT_READY:
+ dchid_handle_ready(dchid, data, length);
+ break;
+ }
+}
+
+static void dchid_handle_report(struct dchid_iface *iface, void *data, size_t length)
+{
+ if (!iface->hid || !iface->open)
+ return;
+
+ hid_input_report(iface->hid, HID_INPUT_REPORT, data, length, 1);
+}
+
+static void dchid_packet_work(struct work_struct *ws)
+{
+ struct dchid_work *work = container_of(ws, struct dchid_work, work);
+ struct dchid_subhdr *shdr = (void *)work->data;
+ struct dchid_dev *dchid = work->iface->dchid;
+ u16 hdr_len = le16_to_cpu(work->hdr.length);
+ u16 sub_len;
+ int type;
+ u8 *payload;
+
+ if (hdr_len < sizeof(*shdr)) {
+ dev_err(dchid->dev, "bad subheader length\n");
+ goto done;
+ }
+
+ sub_len = le16_to_cpu(shdr->length);
+ if (sub_len > hdr_len - sizeof(*shdr)) {
+ dev_err(dchid->dev, "bad subheader length\n");
+ goto done;
+ }
+
+ type = FIELD_GET(FLAGS_GROUP, shdr->flags);
+ payload = work->data + sizeof(*shdr);
+
+ switch (type) {
+ case HID_INPUT_REPORT:
+ if (work->hdr.iface == IFACE_COMM)
+ dchid_handle_event(dchid, payload, sub_len);
+ else
+ dchid_handle_report(work->iface, payload, sub_len);
+ break;
+ }
+
+done:
+ kfree(work);
+}
+
+static void dchid_handle_ack(struct dchid_iface *iface, struct dchid_hdr *hdr,
+ void *data)
+{
+ struct dchid_subhdr *shdr = data;
+ u8 *payload = data + sizeof(*shdr);
+ u16 hdr_len = le16_to_cpu(hdr->length);
+ u16 sub_len = le16_to_cpu(shdr->length);
+ unsigned long flags;
+ bool complete_cmd = false;
+
+ if (hdr_len < sizeof(*shdr) || sub_len > hdr_len - sizeof(*shdr) ||
+ sub_len < 1)
+ return;
+
+ spin_lock_irqsave(&iface->out_lock, flags);
+
+ if (shdr->flags == iface->out_flags && iface->tx_seq == hdr->seq &&
+ iface->out_report == payload[0]) {
+ if (iface->resp_buf && iface->resp_size)
+ memcpy(iface->resp_buf, payload + 1,
+ min_t(size_t, sub_len - 1, iface->resp_size));
+
+ iface->resp_size = sub_len;
+ iface->out_report = -1;
+ iface->retcode = le32_to_cpu(shdr->retcode);
+ complete_cmd = true;
+ }
+
+ spin_unlock_irqrestore(&iface->out_lock, flags);
+
+ if (complete_cmd)
+ complete(&iface->out_complete);
+}
+
+static void dchid_process_packet(struct dchid_dev *dchid, struct dchid_hdr *hdr,
+ u8 *payload, size_t payload_len, u8 *packet,
+ size_t packet_len)
+{
+ struct dchid_work *work;
+
+ if (dchid_checksum(packet, packet_len) != DCHID_CHECKSUM_SEED) {
+ dev_err_ratelimited(dchid->dev, "checksum error\n");
+ return;
+ }
+
+ if (payload_len < sizeof(struct dchid_subhdr))
+ return;
+
+ if (hdr->iface >= MAX_INTERFACES || !dchid->ifaces[hdr->iface])
+ return;
+
+ if (hdr->channel == DCHID_CHANNEL_CMD) {
+ dchid_handle_ack(dchid->ifaces[hdr->iface], hdr, payload);
+ return;
+ }
+
+ if (hdr->channel != DCHID_CHANNEL_REPORT)
+ return;
+
+ work = kzalloc(sizeof(*work) + payload_len, GFP_ATOMIC);
+ if (!work)
+ return;
+
+ work->hdr = *hdr;
+ work->iface = dchid->ifaces[hdr->iface];
+ memcpy(work->data, payload, payload_len);
+ INIT_WORK(&work->work, dchid_packet_work);
+
+ queue_work(work->iface->wq, &work->work);
+}
+
+static void dchid_consume_rx(struct dchid_dev *dchid)
+{
+ while (dchid->rx_len >= sizeof(struct dchid_hdr)) {
+ struct dchid_hdr *hdr = (struct dchid_hdr *)dchid->rx_buf;
+ size_t payload_len;
+ size_t packet_len;
+
+ if (hdr->hdr_len != sizeof(*hdr)) {
+ dev_err_ratelimited(dchid->dev, "bad header length %u\n",
+ hdr->hdr_len);
+ dchid->rx_len = 0;
+ return;
+ }
+
+ payload_len = le16_to_cpu(hdr->length);
+ packet_len = sizeof(*hdr) + payload_len + DCHID_CHECKSUM_LEN;
+ if (packet_len > DCHID_RX_BUF_SIZE) {
+ dev_err_ratelimited(dchid->dev, "oversized packet %zu\n",
+ packet_len);
+ dchid->rx_len = 0;
+ return;
+ }
+
+ if (dchid->rx_len < packet_len)
+ return;
+
+ dchid_process_packet(dchid, hdr, dchid->rx_buf + sizeof(*hdr),
+ payload_len, dchid->rx_buf, packet_len);
+
+ dchid->rx_len -= packet_len;
+ memmove(dchid->rx_buf, dchid->rx_buf + packet_len, dchid->rx_len);
+ }
+}
+
+static void dchid_rx_callback(struct mbox_client *cl, void *mssg)
+{
+ struct dchid_dev *dchid = container_of(cl, struct dchid_dev, dc_mbox_client);
+ struct apple_dockchannel_msg *msg = mssg;
+
+ if (!msg || !msg->data || !msg->len)
+ return;
+
+ if (msg->len > DCHID_RX_BUF_SIZE - dchid->rx_len) {
+ dev_err_ratelimited(dchid->dev, "RX buffer overflow\n");
+ dchid->rx_len = 0;
+ return;
+ }
+
+ memcpy(dchid->rx_buf + dchid->rx_len, msg->data, msg->len);
+ dchid->rx_len += msg->len;
+
+ dchid_consume_rx(dchid);
+}
+
+static int dchid_rtkit_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
+{
+ struct dchid_dev *dchid = cookie;
+ struct resource res = {
+ .start = bfr->iova,
+ .end = bfr->iova + bfr->size - 1,
+ .name = "rtkit_map",
+ };
+
+ if (!bfr->iova) {
+ bfr->buffer = dma_alloc_coherent(dchid->dev, bfr->size,
+ &bfr->iova, GFP_KERNEL);
+ if (!bfr->buffer)
+ return -ENOMEM;
+ return 0;
+ }
+
+ if (!dchid->sram_res.start)
+ return -EFAULT;
+
+ res.flags = dchid->sram_res.flags;
+ if (res.end < res.start || !resource_contains(&dchid->sram_res, &res))
+ return -EFAULT;
+
+ bfr->iomem = dchid->sram_base + (res.start - dchid->sram_res.start);
+ bfr->is_mapped = true;
+
+ return 0;
+}
+
+static void dchid_rtkit_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr)
+{
+ struct dchid_dev *dchid = cookie;
+
+ if (bfr->buffer)
+ dma_free_coherent(dchid->dev, bfr->size, bfr->buffer, bfr->iova);
+}
+
+static const struct apple_rtkit_ops dchid_rtkit_ops = {
+ .shmem_setup = dchid_rtkit_shmem_setup,
+ .shmem_destroy = dchid_rtkit_shmem_destroy,
+};
+
+static int dchid_map_helper_cpu(struct platform_device *pdev, struct dchid_dev *dchid)
+{
+ struct resource *res;
+
+ dchid->asc_base = devm_platform_ioremap_resource_byname(pdev, "coproc-asc");
+ if (IS_ERR(dchid->asc_base))
+ return PTR_ERR(dchid->asc_base);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "coproc-sram");
+ if (!res)
+ return -EINVAL;
+
+ dchid->sram_res = *res;
+
+ dchid->sram_base = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(dchid->sram_base))
+ return PTR_ERR(dchid->sram_base);
+
+ return 0;
+}
+
+static int dchid_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct dchid_dev *dchid;
+ int ret;
+
+ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(44));
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to set DMA mask\n");
+
+ dchid = devm_kzalloc(dev, sizeof(*dchid), GFP_KERNEL);
+ if (!dchid)
+ return -ENOMEM;
+
+ dchid->rx_buf = devm_kmalloc(dev, DCHID_RX_BUF_SIZE, GFP_KERNEL);
+ if (!dchid->rx_buf)
+ return -ENOMEM;
+
+ dchid->dev = dev;
+ mutex_init(&dchid->ifaces_lock);
+ platform_set_drvdata(pdev, dchid);
+
+ ret = dchid_map_helper_cpu(pdev, dchid);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to map helper CPU\n");
+
+ dchid->dc_mbox_client.dev = dev;
+ dchid->dc_mbox_client.tx_block = true;
+ dchid->dc_mbox_client.rx_callback = dchid_rx_callback;
+
+ dchid->dc_mbox = mbox_request_channel_byname(&dchid->dc_mbox_client,
+ "dockchannel");
+ if (IS_ERR(dchid->dc_mbox))
+ return dev_err_probe(dev, PTR_ERR(dchid->dc_mbox),
+ "failed to request DockChannel mailbox\n");
+
+ ret = devm_add_action_or_reset(dev, dchid_free_mbox, dchid->dc_mbox);
+ if (ret)
+ return ret;
+
+ dchid->rtk = devm_apple_rtkit_init(dev, dchid, "asc", 0, &dchid_rtkit_ops);
+ if (IS_ERR(dchid->rtk))
+ return dev_err_probe(dev, PTR_ERR(dchid->rtk), "failed to init RTKit\n");
+
+ writel_relaxed(APPLE_ASC_CPU_CONTROL_RUN,
+ dchid->asc_base + APPLE_ASC_CPU_CONTROL);
+
+ ret = apple_rtkit_wake(dchid->rtk);
+ if (ret)
+ return dev_err_probe(dev, ret, "failed to wake coprocessor\n");
+
+ dchid->new_iface_wq = alloc_ordered_workqueue("dchid-new", 0);
+ if (!dchid->new_iface_wq)
+ return dev_err_probe(dev, -ENOMEM, "failed to allocate workqueue\n");
+
+ ret = devm_add_action_or_reset(dev, dchid_destroy_wq, dchid->new_iface_wq);
+ if (ret)
+ return ret;
+
+ dchid->comm = dchid_get_interface(dchid, IFACE_COMM, "comm");
+ if (!dchid->comm)
+ return dev_err_probe(dev, -EIO, "failed to init comm interface\n");
+
+ return 0;
+}
+
+static void dchid_remove(struct platform_device *pdev)
+{
+ struct dchid_dev *dchid = platform_get_drvdata(pdev);
+ int i;
+
+ if (dchid->dc_mbox) {
+ devm_release_action(&pdev->dev, dchid_free_mbox, dchid->dc_mbox);
+ dchid->dc_mbox = NULL;
+ }
+
+ if (dchid->rtk && apple_rtkit_is_running(dchid->rtk))
+ apple_rtkit_quiesce(dchid->rtk);
+
+ if (dchid->asc_base)
+ writel_relaxed(0, dchid->asc_base + APPLE_ASC_CPU_CONTROL);
+
+ for (i = 0; i < MAX_INTERFACES; i++) {
+ struct dchid_iface *iface = dchid->ifaces[i];
+
+ if (!iface)
+ continue;
+
+ cancel_work_sync(&iface->create_work);
+ flush_workqueue(iface->wq);
+
+ if (iface->hid)
+ hid_destroy_device(iface->hid);
+ }
+
+ if (dchid->new_iface_wq)
+ flush_workqueue(dchid->new_iface_wq);
+}
+
+static const struct of_device_id dchid_of_match[] = {
+ { .compatible = "apple,t8122-dockchannel-hid" },
+ { .compatible = "apple,t8112-dockchannel-hid" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, dchid_of_match);
+
+static struct platform_driver dchid_platform_driver = {
+ .driver = {
+ .name = "dockchannel-hid",
+ .of_match_table = dchid_of_match,
+ },
+ .probe = dchid_probe,
+ .remove = dchid_remove,
+};
+module_platform_driver(dchid_platform_driver);
+
+MODULE_DESCRIPTION("Apple DockChannel HID transport driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_AUTHOR("Michael Reeves <michael.reeves077@gmail.com>");
+MODULE_LICENSE("Dual MIT/GPL");
--
2.51.2
^ permalink raw reply related [flat|nested] 22+ messages in thread* Re: [PATCH 08/10] HID: apple: Add DockChannel HID transport driver
2026-06-30 12:54 ` [PATCH 08/10] HID: apple: Add DockChannel HID transport driver Michael Reeves via B4 Relay
@ 2026-06-30 13:21 ` Yureka Lilian
2026-07-01 14:32 ` Michael Reeves
2026-07-02 19:32 ` Julian Braha
1 sibling, 1 reply; 22+ messages in thread
From: Yureka Lilian @ 2026-06-30 13:21 UTC (permalink / raw)
To: michael.reeves077, Sven Peter, Janne Grunau, Neal Gompa,
Jassi Brar, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hector Martin, Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires
Cc: asahi, linux-arm-kernel, linux-kernel, devicetree, iommu,
linux-input
On 6/30/26 14:54, Michael Reeves via B4 Relay wrote:
> From: Michael Reeves <michael.reeves077@gmail.com>
>
> Apple MTP exposes internal keyboard and trackpad interfaces over a HID
> transport carried by DockChannel.
>
> Add a transport driver that boots the MTP RTKit coprocessor, exchanges
> HID packets through the DockChannel mailbox, and registers child HID
> interfaces from devicetree.
>
> Co-developed-by: Hector Martin <marcan@marcan.st>
> Signed-off-by: Hector Martin <marcan@marcan.st>
> Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
> ---
> MAINTAINERS | 1 +
> drivers/hid/Kconfig | 2 +
> drivers/hid/Makefile | 2 +
> drivers/hid/dockchannel/Kconfig | 15 +
> drivers/hid/dockchannel/Makefile | 3 +
> drivers/hid/dockchannel/apple-hid.c | 1130 +++++++++++++++++++++++++++++++++++
> 6 files changed, 1153 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index ed68452c0ad6..0063276f0349 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2620,6 +2620,7 @@ F: drivers/clk/clk-apple-nco.c
> F: drivers/cpufreq/apple-soc-cpufreq.c
> F: drivers/dma/apple-admac.c
> F: drivers/gpio/gpio-macsmc.c
> +F: drivers/hid/dockchannel/
> F: drivers/hwmon/macsmc-hwmon.c
> F: drivers/pmdomain/apple/
> F: drivers/i2c/busses/i2c-pasemi-core.c
> diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
> index f9bcaeb66385..f27cda601ede 100644
> --- a/drivers/hid/Kconfig
> +++ b/drivers/hid/Kconfig
> @@ -1488,6 +1488,8 @@ source "drivers/hid/surface-hid/Kconfig"
>
> source "drivers/hid/intel-thc-hid/Kconfig"
>
> +source "drivers/hid/dockchannel/Kconfig"
> +
> endif # HID
>
> # USB support may be used with HID disabled
> diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
> index 23e6e3dd0c56..c9b4b1aff247 100644
> --- a/drivers/hid/Makefile
> +++ b/drivers/hid/Makefile
> @@ -182,3 +182,5 @@ obj-$(CONFIG_AMD_SFH_HID) += amd-sfh-hid/
> obj-$(CONFIG_SURFACE_HID_CORE) += surface-hid/
>
> obj-$(CONFIG_INTEL_THC_HID) += intel-thc-hid/
> +
> +obj-$(CONFIG_APPLE_DOCKCHANNEL_HID) += dockchannel/
> diff --git a/drivers/hid/dockchannel/Kconfig b/drivers/hid/dockchannel/Kconfig
> new file mode 100644
> index 000000000000..fca09ef74403
> --- /dev/null
> +++ b/drivers/hid/dockchannel/Kconfig
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR MIT
> +
> +config APPLE_DOCKCHANNEL_HID
> + tristate "HID over Apple DockChannel"
> + depends on APPLE_DOCKCHANNEL
> + depends on APPLE_RTKIT
> + depends on HID
> + depends on INPUT
> + depends on OF
> + help
> + This provides a HID transport layer over the Apple DockChannel
> + mailbox interface. It is required to support the internal keyboard
> + and trackpad on M2 and later MacBook models.
> +
> + Say Y here if you have an M2 or later Apple MacBook.
> diff --git a/drivers/hid/dockchannel/Makefile b/drivers/hid/dockchannel/Makefile
> new file mode 100644
> index 000000000000..d1a82aa57a69
> --- /dev/null
> +++ b/drivers/hid/dockchannel/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR MIT
> +
> +obj-$(CONFIG_APPLE_DOCKCHANNEL_HID) += apple-hid.o
> diff --git a/drivers/hid/dockchannel/apple-hid.c b/drivers/hid/dockchannel/apple-hid.c
> new file mode 100644
> index 000000000000..162fcfb5ab1c
> --- /dev/null
> +++ b/drivers/hid/dockchannel/apple-hid.c
> @@ -0,0 +1,1130 @@
> +// SPDX-License-Identifier: GPL-2.0-only OR MIT
> +/*
> + * Apple DockChannel HID transport driver
> + *
> + * Copyright The Asahi Linux Contributors
> + */
> +
> +#include <linux/bitfield.h>
> +#include <linux/completion.h>
> +#include <linux/ctype.h>
> +#include <linux/delay.h>
> +#include <linux/device.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/hid.h>
> +#include <linux/mailbox/apple-dockchannel.h>
> +#include <linux/mailbox_client.h>
> +#include <linux/module.h>
> +#include <linux/mutex.h>
> +#include <linux/of.h>
> +#include <linux/platform_device.h>
> +#include <linux/property.h>
> +#include <linux/slab.h>
> +#include <linux/soc/apple/rtkit.h>
> +#include <linux/spinlock.h>
> +#include <linux/string.h>
> +#include <linux/unaligned.h>
> +#include <linux/workqueue.h>
> +
> +#define APPLE_ASC_CPU_CONTROL 0x44
> +#define APPLE_ASC_CPU_CONTROL_RUN BIT(4)
> +
> +#define COMMAND_TIMEOUT_MS 1000
> +#define START_TIMEOUT_MS 2000
> +
> +#define MAX_INTERFACES 16
> +
> +#define DCHID_MAX_PAYLOAD 0xffff
> +#define DCHID_CHECKSUM_LEN 4
> +#define DCHID_RX_BUF_SIZE (sizeof(struct dchid_hdr) + DCHID_MAX_PAYLOAD + \
> + DCHID_CHECKSUM_LEN)
> +
> +#define DCHID_CHANNEL_CMD 0x11
> +#define DCHID_CHANNEL_REPORT 0x12
> +#define DCHID_CHECKSUM_SEED 0xffffffff
> +
> +struct dchid_hdr {
> + u8 hdr_len;
> + u8 channel;
> + __le16 length;
> + u8 seq;
> + u8 iface;
> + __le16 pad;
> +} __packed;
> +
> +#define IFACE_COMM 0
> +
> +#define FLAGS_GROUP GENMASK(7, 6)
> +#define FLAGS_REQ GENMASK(5, 0)
> +
> +#define REQ_SET_REPORT 0
> +#define REQ_GET_REPORT 1
> +
> +struct dchid_subhdr {
> + u8 flags;
> + u8 unk;
> + __le16 length;
> + __le32 retcode;
> +} __packed;
> +
> +#define EVENT_INIT 0xf0
> +#define EVENT_READY 0xf1
> +
> +struct dchid_init_hdr {
> + u8 type;
> + u8 unk1;
> + u8 unk2;
> + u8 iface;
> + char name[16];
> + u8 more_packets;
> + u8 unkpad;
> +} __packed;
> +
> +#define INIT_HID_DESCRIPTOR 0
> +#define INIT_TERMINATOR 2
> +#define INIT_PRODUCT_NAME 7
> +
> +#define CMD_RESET_INTERFACE 0x40
> +#define CMD_RESET_INTERFACE_SUB 1
> +#define CMD_ENABLE_INTERFACE 0xb4
> +
> +struct dchid_init_block_hdr {
> + __le16 type;
> + __le16 length;
> +} __packed;
> +
> +#define STM_REPORT_ID 0x10
> +#define STM_REPORT_SERIAL 0x11
> +
> +struct dchid_stm_id {
> + u8 unk;
> + __le16 vendor_id;
> + __le16 product_id;
> + __le16 version_number;
> + u8 unk2;
> + u8 unk3;
> + u8 keyboard_type;
> + u8 serial_length;
> + /* Serial follows, but we grab it with a different report. */
> +} __packed;
> +
> +struct dchid_work {
> + struct work_struct work;
> + struct dchid_iface *iface;
> +
> + struct dchid_hdr hdr;
> + u8 data[];
> +};
> +
> +struct dchid_iface {
> + struct dchid_dev *dchid;
> + struct hid_device *hid;
> + struct workqueue_struct *wq;
> +
> + bool creating;
> + struct work_struct create_work;
> +
> + int index;
> + const char *name;
> + struct fwnode_handle *fwnode;
> +
> + u8 tx_seq;
> + bool deferred;
> + bool starting;
> + bool open;
> + struct completion ready;
> +
> + void *hid_desc;
> + size_t hid_desc_len;
> +
> + /* Lock for command submission state below */
> + spinlock_t out_lock;
> + u32 out_flags;
> + int out_report;
> + u32 retcode;
> + void *resp_buf;
> + size_t resp_size;
> + struct completion out_complete;
> +};
> +
> +struct dchid_dev {
> + struct device *dev;
> + struct mbox_client dc_mbox_client;
> + struct mbox_chan *dc_mbox;
> +
> + struct apple_rtkit *rtk;
> + void __iomem *asc_base;
> + void __iomem *sram_base;
> + struct resource sram_res;
> +
> + bool id_ready;
> + struct dchid_stm_id device_id;
> + char serial[64];
> +
> + u8 *rx_buf;
> + size_t rx_len;
> +
> + struct dchid_iface *comm;
> + struct mutex ifaces_lock; /* protects ifaces array */
> + struct dchid_iface *ifaces[MAX_INTERFACES];
> +
> + /* Workqueue to asynchronously create HID devices */
> + struct workqueue_struct *new_iface_wq;
> +};
> +
> +static void dchid_destroy_wq(void *data)
> +{
> + struct workqueue_struct *wq = data;
> +
> + destroy_workqueue(wq);
> +}
> +
> +static void dchid_fwnode_release(void *data)
> +{
> + fwnode_handle_put(data);
> +}
> +
> +static void dchid_free_mbox(void *data)
> +{
> + mbox_free_channel(data);
> +}
> +
> +static u32 dchid_checksum(const void *data, size_t len)
> +{
> + const u8 *p = data;
> + u32 sum = 0;
> + int i;
> +
> + while (len >= sizeof(u32)) {
> + sum += get_unaligned_le32(p);
> + p += sizeof(u32);
> + len -= sizeof(u32);
> + }
> +
> + if (len) {
> + u32 tmp = 0;
> +
> + for (i = 0; i < len; i++)
> + tmp |= p[i] << (i * 8);
> + sum += tmp;
> + }
> +
> + return sum;
> +}
> +
> +static struct dchid_iface *
> +dchid_get_interface(struct dchid_dev *dchid, int index, const char *name)
> +{
> + struct dchid_iface *iface;
> + struct fwnode_handle *fwnode;
> + int ret;
> +
> + if (index >= MAX_INTERFACES) {
> + dev_err(dchid->dev, "interface index %d out of range\n", index);
> + return NULL;
> + }
> +
> + mutex_lock(&dchid->ifaces_lock);
> + if (dchid->ifaces[index]) {
> + iface = dchid->ifaces[index];
> + mutex_unlock(&dchid->ifaces_lock);
> + return iface;
> + }
> +
> + iface = devm_kzalloc(dchid->dev, sizeof(*iface), GFP_KERNEL);
> + if (!iface) {
> + mutex_unlock(&dchid->ifaces_lock);
> + return NULL;
> + }
> +
> + iface->index = index;
> + iface->name = devm_kstrdup(dchid->dev, name, GFP_KERNEL);
> + if (!iface->name) {
> + mutex_unlock(&dchid->ifaces_lock);
> + return NULL;
> + }
> +
> + iface->dchid = dchid;
> + iface->out_report = -1;
> + init_completion(&iface->out_complete);
> + init_completion(&iface->ready);
> + spin_lock_init(&iface->out_lock);
> +
> + iface->wq = alloc_ordered_workqueue("dchid-%s", 0, iface->name);
> + if (!iface->wq) {
> + mutex_unlock(&dchid->ifaces_lock);
> + return NULL;
> + }
> +
> + ret = devm_add_action_or_reset(dchid->dev, dchid_destroy_wq, iface->wq);
> + if (ret) {
> + mutex_unlock(&dchid->ifaces_lock);
> + return NULL;
> + }
> +
> + if (!strcmp(name, "comm")) {
> + /* Comm is not a HID subdevice */
> + dchid->ifaces[index] = iface;
> + mutex_unlock(&dchid->ifaces_lock);
> + return iface;
> + }
> +
> + fwnode = device_get_named_child_node(dchid->dev, name);
> + if (fwnode) {
> + iface->fwnode = fwnode;
> + ret = devm_add_action_or_reset(dchid->dev, dchid_fwnode_release,
> + iface->fwnode);
> + if (ret) {
> + mutex_unlock(&dchid->ifaces_lock);
> + return NULL;
> + }
> + } else {
> + iface->fwnode = dev_fwnode(dchid->dev);
> + }
> +
> + dchid->ifaces[index] = iface;
> + mutex_unlock(&dchid->ifaces_lock);
> + return iface;
> +}
> +
> +static int dchid_send(struct dchid_iface *iface, u32 flags, const void *msg,
> + size_t size)
> +{
> + struct dchid_dev *dchid = iface->dchid;
> + size_t payload_padded = round_up(size, sizeof(u32));
> + size_t total_len = sizeof(struct dchid_hdr) + sizeof(struct dchid_subhdr) +
> + payload_padded + DCHID_CHECKSUM_LEN;
> + struct apple_dockchannel_msg dc_msg;
> + struct dchid_hdr *hdr;
> + struct dchid_subhdr *sub;
> + u32 *checksum_ptr;
> + u8 *buf;
> + int ret;
> +
> + if (total_len > DCHID_RX_BUF_SIZE)
> + return -EINVAL;
> +
> + buf = kzalloc(total_len, GFP_KERNEL);
> + if (!buf)
> + return -ENOMEM;
> +
> + hdr = (struct dchid_hdr *)buf;
> + sub = (struct dchid_subhdr *)(buf + sizeof(*hdr));
> + checksum_ptr = (u32 *)(buf + total_len - DCHID_CHECKSUM_LEN);
> +
> + hdr->hdr_len = sizeof(*hdr);
> + hdr->channel = DCHID_CHANNEL_CMD;
> + hdr->length = cpu_to_le16(payload_padded + sizeof(*sub));
> + hdr->seq = iface->tx_seq;
> + hdr->iface = iface->index;
> +
> + sub->flags = (u8)flags;
> + sub->length = cpu_to_le16(size);
> +
> + memcpy(buf + sizeof(*hdr) + sizeof(*sub), msg, size);
> +
> + *checksum_ptr = 0xffffffff - dchid_checksum(buf, total_len - DCHID_CHECKSUM_LEN);
> +
> + dc_msg.data = buf;
> + dc_msg.len = total_len;
> + ret = mbox_send_message(dchid->dc_mbox, &dc_msg);
> + kfree(buf);
> +
> + return ret < 0 ? ret : 0;
> +}
> +
> +static int dchid_cmd(struct dchid_iface *iface, u32 type, u32 req,
> + void *data, size_t size, void *resp_buf, size_t resp_size)
> +{
> + unsigned long flags;
> + int ret;
> + int report_id;
> + bool timed_out = false;
> + u32 out_flags;
> +
> + if (size < 1)
> + return -EINVAL;
> +
> + report_id = *(u8 *)data;
> + out_flags = FIELD_PREP(FLAGS_GROUP, type) | FIELD_PREP(FLAGS_REQ, req);
> +
> + spin_lock_irqsave(&iface->out_lock, flags);
> +
> + /* Only one command can be in flight per interface */
> + if (WARN_ON(iface->out_report != -1)) {
> + spin_unlock_irqrestore(&iface->out_lock, flags);
> + return -EBUSY;
> + }
> +
> + iface->out_report = report_id;
> + iface->out_flags = out_flags;
> + iface->retcode = 0;
> + iface->resp_buf = resp_buf;
> + iface->resp_size = resp_size;
> + reinit_completion(&iface->out_complete);
> +
> + spin_unlock_irqrestore(&iface->out_lock, flags);
> +
> + ret = dchid_send(iface, out_flags, data, size);
> + if (ret < 0) {
> + spin_lock_irqsave(&iface->out_lock, flags);
> + iface->out_report = -1;
> + iface->resp_buf = NULL;
> + iface->resp_size = 0;
> + spin_unlock_irqrestore(&iface->out_lock, flags);
> + return ret;
> + }
> +
> + if (!wait_for_completion_timeout(&iface->out_complete,
> + msecs_to_jiffies(COMMAND_TIMEOUT_MS))) {
> + dev_err(iface->dchid->dev, "command 0x%x to iface %d (%s) timed out\n",
> + report_id, iface->index, iface->name);
> + timed_out = true;
> + }
> +
> + spin_lock_irqsave(&iface->out_lock, flags);
> +
> + if (timed_out && iface->out_report == report_id) {
> + ret = -ETIMEDOUT;
> + } else if (iface->retcode) {
> + dev_err(iface->dchid->dev,
> + "command 0x%x to iface %d (%s) failed with err 0x%x\n",
> + report_id, iface->index, iface->name, iface->retcode);
> + ret = -EIO;
> + } else {
> + ret = iface->resp_size;
> + }
> +
> + iface->tx_seq++;
> + iface->out_report = -1;
> + iface->resp_buf = NULL;
> + iface->resp_size = 0;
> + spin_unlock_irqrestore(&iface->out_lock, flags);
> +
> + return ret;
> +}
> +
> +static int dchid_comm_cmd(struct dchid_dev *dchid, void *cmd, size_t size)
> +{
> + return dchid_cmd(dchid->comm, HID_FEATURE_REPORT, REQ_SET_REPORT,
> + cmd, size, NULL, 0);
> +}
> +
> +static int dchid_enable_interface(struct dchid_iface *iface)
> +{
> + u8 cmd[] = { CMD_ENABLE_INTERFACE, iface->index };
> +
> + return dchid_comm_cmd(iface->dchid, cmd, sizeof(cmd));
> +}
> +
> +static int dchid_reset_interface(struct dchid_iface *iface, int state)
> +{
> + u8 cmd[] = { CMD_RESET_INTERFACE, CMD_RESET_INTERFACE_SUB, iface->index,
> + (u8)state };
> +
> + return dchid_comm_cmd(iface->dchid, cmd, sizeof(cmd));
> +}
> +
> +static int dchid_start_interface(struct dchid_iface *iface)
> +{
> + if (iface->starting)
> + return -EINPROGRESS;
> +
> + dev_dbg(iface->dchid->dev, "starting interface %s\n", iface->name);
> +
> + iface->starting = true;
> + dchid_reset_interface(iface, 0);
> + dchid_reset_interface(iface, 2);
> +
> + return 0;
> +}
> +
> +static int dchid_start(struct hid_device *hdev)
> +{
> + return 0;
> +}
> +
> +static int dchid_open(struct hid_device *hdev)
> +{
> + struct dchid_iface *iface = hdev->driver_data;
> + int ret;
> +
> + if (!completion_done(&iface->ready)) {
> + ret = dchid_start_interface(iface);
> + if (ret < 0)
> + return ret;
> +
> + if (!wait_for_completion_timeout(&iface->ready,
> + msecs_to_jiffies(START_TIMEOUT_MS))) {
> + dev_err(iface->dchid->dev, "iface %s start timed out\n",
> + iface->name);
> + return -ETIMEDOUT;
> + }
> + }
> +
> + iface->open = true;
> + return 0;
> +}
> +
> +static void dchid_close(struct hid_device *hdev)
> +{
> + struct dchid_iface *iface = hdev->driver_data;
> +
> + iface->open = false;
> +}
> +
> +static int dchid_parse(struct hid_device *hdev)
> +{
> + struct dchid_iface *iface = hdev->driver_data;
> +
> + return hid_parse_report(hdev, iface->hid_desc, iface->hid_desc_len);
> +}
> +
> +/* Note: buf excludes report number. */
> +static int dchid_get_report_cmd(struct dchid_iface *iface, u8 reportnum,
> + void *buf, size_t len)
> +{
> + int ret;
> +
> + ret = dchid_cmd(iface, HID_FEATURE_REPORT, REQ_GET_REPORT, &reportnum, 1,
> + buf, len);
> +
> + return ret <= 0 ? ret : ret - 1;
> +}
> +
> +/* Note: buf includes report number. */
> +static int dchid_set_report(struct dchid_iface *iface, void *buf, size_t len)
> +{
> + return dchid_cmd(iface, HID_OUTPUT_REPORT, REQ_SET_REPORT, buf, len,
> + NULL, 0);
> +}
> +
> +static int dchid_raw_request(struct hid_device *hdev, unsigned char reportnum,
> + __u8 *buf, size_t len, unsigned char rtype,
> + int reqtype)
> +{
> + struct dchid_iface *iface = hdev->driver_data;
> +
> + switch (reqtype) {
> + case HID_REQ_GET_REPORT:
> + if (len < 1)
> + return -EINVAL;
> +
> + buf[0] = reportnum;
> + return dchid_cmd(iface, rtype, REQ_GET_REPORT, &reportnum, 1,
> + buf + 1, len - 1);
> + case HID_REQ_SET_REPORT:
> + return dchid_set_report(iface, buf, len);
> + default:
> + return -EIO;
> + }
> +}
> +
> +static const struct hid_ll_driver dchid_ll = {
> + .start = dchid_start,
> + .open = dchid_open,
> + .close = dchid_close,
> + .parse = dchid_parse,
> + .raw_request = dchid_raw_request,
> +};
> +
> +static void dchid_create_interface_work(struct work_struct *ws)
> +{
> + struct dchid_iface *iface = container_of(ws, struct dchid_iface, create_work);
> + struct dchid_dev *dchid = iface->dchid;
> + struct hid_device *hid;
> + char cap_name[16];
> + int ret;
> +
> + if (iface->hid) {
> + dev_warn(dchid->dev, "interface %s already created\n", iface->name);
> + goto done;
> + }
> +
> + ret = dchid_enable_interface(iface);
> + if (ret < 0) {
> + dev_warn(dchid->dev, "failed to enable %s: %d\n", iface->name, ret);
> + goto done;
> + }
> +
> + iface->deferred = false;
> +
> + hid = hid_allocate_device();
> + if (IS_ERR(hid))
> + goto done;
> +
> + strscpy(cap_name, iface->name, sizeof(cap_name));
> + if (cap_name[0])
> + cap_name[0] = toupper(cap_name[0]);
> + snprintf(hid->name, sizeof(hid->name), "Apple DockChannel %s", cap_name);
> +
> + snprintf(hid->phys, sizeof(hid->phys), "%s.%d", dev_name(dchid->dev),
> + iface->index);
> + strscpy(hid->uniq, dchid->serial, sizeof(hid->uniq));
If the keyboard appeared before stm, dchip->serial might be uninitialized.
> +
> + hid->ll_driver = &dchid_ll;
> + hid->bus = BUS_HOST;
> + hid->vendor = le16_to_cpu(dchid->device_id.vendor_id);
> + hid->product = le16_to_cpu(dchid->device_id.product_id);
> + hid->version = le16_to_cpu(dchid->device_id.version_number);
> + hid->type = HID_TYPE_OTHER;
> + if (!strcmp(iface->name, "keyboard")) {
> + u32 country_code;
> +
> + hid->group = HID_GROUP_APPLE_DOCKCHANNEL;
> +
> + /*
> + * The device provides no reliable way to get the keyboard
> + * country code, so board devicetrees provide it instead,
> + * filled by the bootloader.
> + */
> + if (!fwnode_property_read_u32(iface->fwnode, "hid-country-code",
> + &country_code))
> + hid->country = country_code;
> + }
> +
> + hid->dev.parent = iface->dchid->dev;
> + hid->driver_data = iface;
> + iface->hid = hid;
> +
> + ret = hid_add_device(hid);
> + if (ret < 0) {
> + iface->hid = NULL;
> + hid_destroy_device(hid);
> + dev_warn(iface->dchid->dev, "failed to register HID device %s\n",
> + iface->name);
> + }
> +
> +done:
> + iface->creating = false;
> +}
> +
> +static int dchid_create_interface(struct dchid_iface *iface)
> +{
> + if (iface->creating)
> + return -EBUSY;
> +
> + iface->creating = true;
> + INIT_WORK(&iface->create_work, dchid_create_interface_work);
> + return queue_work(iface->dchid->new_iface_wq, &iface->create_work);
> +}
> +
> +static void dchid_handle_descriptor(struct dchid_iface *iface, void *hid_desc,
> + size_t desc_len)
> +{
> + u8 *rdesc;
> + int i;
> +
> + if (iface->hid)
> + return;
> +
> + rdesc = devm_kmemdup(iface->dchid->dev, hid_desc, desc_len,
> + GFP_KERNEL);
> + if (!rdesc)
> + return;
> +
> + /* Fix up oversized report sizes in DockChannel report descriptors */
> + if (desc_len >= 5) {
> + for (i = 0; i <= (int)desc_len - 5; i++) {
> + if (rdesc[i] == 0x76 && rdesc[i + 1] == 0x00 &&
> + rdesc[i + 2] == 0x40 && rdesc[i + 3] == 0x95) {
> + u8 count = rdesc[i + 4];
> +
> + if (count > 0 && count < 32) {
> + dev_info(iface->dchid->dev,
> + "fixing up interface %s (%d) report size\n",
> + iface->name, iface->index);
> + rdesc[i] = 0x75;
> + rdesc[i + 1] = 0x08;
> + rdesc[i + 2] = 0x96;
> + rdesc[i + 3] = 0x00;
> + rdesc[i + 4] = count * 8;
> + }
> + }
> + }
> + }
> +
> + iface->hid_desc = rdesc;
> + iface->hid_desc_len = desc_len;
> +}
> +
> +static void dchid_handle_ready(struct dchid_dev *dchid, void *data, size_t length)
> +{
> + struct dchid_iface *iface;
> + u8 *pkt = data;
> + u8 index;
> + int i;
> + int ret;
> +
> + if (length < 2)
> + return;
> +
> + index = pkt[1];
> + if (index >= MAX_INTERFACES)
> + return;
> +
> + iface = dchid->ifaces[index];
> + if (!iface)
> + return;
> +
> + dev_dbg(dchid->dev, "interface %s is now ready\n", iface->name);
> + complete_all(&iface->ready);
> +
> + /* When STM is ready, grab global device info */
> + if (!strcmp(iface->name, "stm")) {
> + ret = dchid_get_report_cmd(iface, STM_REPORT_ID, &dchid->device_id,
> + sizeof(dchid->device_id));
> + if (ret < (int)sizeof(dchid->device_id)) {
> + dev_warn(iface->dchid->dev, "failed to get device ID from STM\n");
> + /* Fake it and keep going. Things might still work. */
> + memset(&dchid->device_id, 0, sizeof(dchid->device_id));
> + }
> +
> + ret = dchid_get_report_cmd(iface, STM_REPORT_SERIAL, dchid->serial,
> + sizeof(dchid->serial) - 1);
> + if (ret < 0) {
> + dev_warn(iface->dchid->dev, "failed to get serial from STM\n");
> + dchid->serial[0] = 0;
> + }
> +
> + dchid->id_ready = true;
> + for (i = 0; i < MAX_INTERFACES; i++) {
> + if (!dchid->ifaces[i] || !dchid->ifaces[i]->deferred)
> + continue;
> + dchid_create_interface(dchid->ifaces[i]);
> + }
> + }
> +}
> +
> +static void dchid_handle_init(struct dchid_dev *dchid, void *data, size_t length)
> +{
> + struct dchid_init_hdr *hdr = data;
> + struct dchid_init_block_hdr *blk;
> + struct dchid_iface *iface;
> + u8 *p = data;
> +
> + if (length < sizeof(*hdr))
> + return;
> +
> + iface = dchid_get_interface(dchid, hdr->iface, hdr->name);
> + if (!iface)
> + return;
> +
> + p += sizeof(*hdr);
> + length -= sizeof(*hdr);
> +
> + while (length >= sizeof(*blk)) {
> + u16 blk_len;
> +
> + blk = (struct dchid_init_block_hdr *)p;
> + p += sizeof(*blk);
> + length -= sizeof(*blk);
> +
> + blk_len = le16_to_cpu(blk->length);
> + if (blk_len > length)
> + break;
> +
> + switch (le16_to_cpu(blk->type)) {
> + case INIT_HID_DESCRIPTOR:
> + dchid_handle_descriptor(iface, p, blk_len);
> + break;
> + case INIT_PRODUCT_NAME:
> + if (blk_len > 0 && p[blk_len - 1] != 0)
> + dev_warn(dchid->dev, "unterminated product name for %s\n",
> + iface->name);
> + break;
> + }
> +
> + p += blk_len;
> + length -= blk_len;
> +
> + if (le16_to_cpu(blk->type) == INIT_TERMINATOR)
> + break;
> + }
> +
> + if (hdr->more_packets)
> + return;
> +
> + /*
> + * Prefer to enable STM first, since it provides device IDs. Some
> + * firmware versions do not expose STM, so let the keyboard start
> + * without it.
> + */
> + if (iface->dchid->id_ready || !strcmp(iface->name, "stm") ||
> + !strcmp(iface->name, "keyboard"))
I specifically asked for a mechanism to let the keyboard probe even on
devices which do not expose/have stm. Thanks for adding that!
However, I think this might need some more sophisticated mechanism to
decide whether the stm is still going to appear after the keyboard, or
not at all. I'm not sure if there is a way to tell this at this point,
or we need to add a timeout for the stm to appearing, which needs to
expire before we create the other interfaces with fake serials.
> + dchid_create_interface(iface);
> + else
> + iface->deferred = true;
> +}
> +
> +static void dchid_handle_event(struct dchid_dev *dchid, void *data, size_t length)
> +{
> + u8 *p = data;
> +
> + if (!length)
> + return;
> +
> + switch (*p) {
> + case EVENT_INIT:
> + dchid_handle_init(dchid, data, length);
> + break;
> + case EVENT_READY:
> + dchid_handle_ready(dchid, data, length);
> + break;
> + }
> +}
> +
> +static void dchid_handle_report(struct dchid_iface *iface, void *data, size_t length)
> +{
> + if (!iface->hid || !iface->open)
> + return;
> +
> + hid_input_report(iface->hid, HID_INPUT_REPORT, data, length, 1);
> +}
> +
> +static void dchid_packet_work(struct work_struct *ws)
> +{
> + struct dchid_work *work = container_of(ws, struct dchid_work, work);
> + struct dchid_subhdr *shdr = (void *)work->data;
> + struct dchid_dev *dchid = work->iface->dchid;
> + u16 hdr_len = le16_to_cpu(work->hdr.length);
> + u16 sub_len;
> + int type;
> + u8 *payload;
> +
> + if (hdr_len < sizeof(*shdr)) {
> + dev_err(dchid->dev, "bad subheader length\n");
> + goto done;
> + }
> +
> + sub_len = le16_to_cpu(shdr->length);
> + if (sub_len > hdr_len - sizeof(*shdr)) {
> + dev_err(dchid->dev, "bad subheader length\n");
> + goto done;
> + }
> +
> + type = FIELD_GET(FLAGS_GROUP, shdr->flags);
> + payload = work->data + sizeof(*shdr);
> +
> + switch (type) {
> + case HID_INPUT_REPORT:
> + if (work->hdr.iface == IFACE_COMM)
> + dchid_handle_event(dchid, payload, sub_len);
> + else
> + dchid_handle_report(work->iface, payload, sub_len);
> + break;
> + }
> +
> +done:
> + kfree(work);
> +}
> +
> +static void dchid_handle_ack(struct dchid_iface *iface, struct dchid_hdr *hdr,
> + void *data)
> +{
> + struct dchid_subhdr *shdr = data;
> + u8 *payload = data + sizeof(*shdr);
> + u16 hdr_len = le16_to_cpu(hdr->length);
> + u16 sub_len = le16_to_cpu(shdr->length);
> + unsigned long flags;
> + bool complete_cmd = false;
> +
> + if (hdr_len < sizeof(*shdr) || sub_len > hdr_len - sizeof(*shdr) ||
> + sub_len < 1)
> + return;
> +
> + spin_lock_irqsave(&iface->out_lock, flags);
> +
> + if (shdr->flags == iface->out_flags && iface->tx_seq == hdr->seq &&
> + iface->out_report == payload[0]) {
> + if (iface->resp_buf && iface->resp_size)
> + memcpy(iface->resp_buf, payload + 1,
> + min_t(size_t, sub_len - 1, iface->resp_size));
> +
> + iface->resp_size = sub_len;
> + iface->out_report = -1;
> + iface->retcode = le32_to_cpu(shdr->retcode);
> + complete_cmd = true;
> + }
> +
> + spin_unlock_irqrestore(&iface->out_lock, flags);
> +
> + if (complete_cmd)
> + complete(&iface->out_complete);
> +}
> +
> +static void dchid_process_packet(struct dchid_dev *dchid, struct dchid_hdr *hdr,
> + u8 *payload, size_t payload_len, u8 *packet,
> + size_t packet_len)
> +{
> + struct dchid_work *work;
> +
> + if (dchid_checksum(packet, packet_len) != DCHID_CHECKSUM_SEED) {
> + dev_err_ratelimited(dchid->dev, "checksum error\n");
> + return;
> + }
> +
> + if (payload_len < sizeof(struct dchid_subhdr))
> + return;
> +
> + if (hdr->iface >= MAX_INTERFACES || !dchid->ifaces[hdr->iface])
> + return;
> +
> + if (hdr->channel == DCHID_CHANNEL_CMD) {
> + dchid_handle_ack(dchid->ifaces[hdr->iface], hdr, payload);
> + return;
> + }
> +
> + if (hdr->channel != DCHID_CHANNEL_REPORT)
> + return;
> +
> + work = kzalloc(sizeof(*work) + payload_len, GFP_ATOMIC);
> + if (!work)
> + return;
> +
> + work->hdr = *hdr;
> + work->iface = dchid->ifaces[hdr->iface];
> + memcpy(work->data, payload, payload_len);
> + INIT_WORK(&work->work, dchid_packet_work);
> +
> + queue_work(work->iface->wq, &work->work);
> +}
> +
> +static void dchid_consume_rx(struct dchid_dev *dchid)
> +{
> + while (dchid->rx_len >= sizeof(struct dchid_hdr)) {
> + struct dchid_hdr *hdr = (struct dchid_hdr *)dchid->rx_buf;
> + size_t payload_len;
> + size_t packet_len;
> +
> + if (hdr->hdr_len != sizeof(*hdr)) {
> + dev_err_ratelimited(dchid->dev, "bad header length %u\n",
> + hdr->hdr_len);
> + dchid->rx_len = 0;
> + return;
> + }
> +
> + payload_len = le16_to_cpu(hdr->length);
> + packet_len = sizeof(*hdr) + payload_len + DCHID_CHECKSUM_LEN;
> + if (packet_len > DCHID_RX_BUF_SIZE) {
> + dev_err_ratelimited(dchid->dev, "oversized packet %zu\n",
> + packet_len);
> + dchid->rx_len = 0;
> + return;
> + }
> +
> + if (dchid->rx_len < packet_len)
> + return;
> +
> + dchid_process_packet(dchid, hdr, dchid->rx_buf + sizeof(*hdr),
> + payload_len, dchid->rx_buf, packet_len);
> +
> + dchid->rx_len -= packet_len;
> + memmove(dchid->rx_buf, dchid->rx_buf + packet_len, dchid->rx_len);
> + }
> +}
> +
> +static void dchid_rx_callback(struct mbox_client *cl, void *mssg)
> +{
> + struct dchid_dev *dchid = container_of(cl, struct dchid_dev, dc_mbox_client);
> + struct apple_dockchannel_msg *msg = mssg;
> +
> + if (!msg || !msg->data || !msg->len)
> + return;
> +
> + if (msg->len > DCHID_RX_BUF_SIZE - dchid->rx_len) {
> + dev_err_ratelimited(dchid->dev, "RX buffer overflow\n");
> + dchid->rx_len = 0;
> + return;
> + }
> +
> + memcpy(dchid->rx_buf + dchid->rx_len, msg->data, msg->len);
> + dchid->rx_len += msg->len;
> +
> + dchid_consume_rx(dchid);
> +}
> +
> +static int dchid_rtkit_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
> +{
> + struct dchid_dev *dchid = cookie;
> + struct resource res = {
> + .start = bfr->iova,
> + .end = bfr->iova + bfr->size - 1,
> + .name = "rtkit_map",
> + };
> +
> + if (!bfr->iova) {
> + bfr->buffer = dma_alloc_coherent(dchid->dev, bfr->size,
> + &bfr->iova, GFP_KERNEL);
> + if (!bfr->buffer)
> + return -ENOMEM;
> + return 0;
> + }
> +
> + if (!dchid->sram_res.start)
> + return -EFAULT;
> +
> + res.flags = dchid->sram_res.flags;
> + if (res.end < res.start || !resource_contains(&dchid->sram_res, &res))
> + return -EFAULT;
> +
> + bfr->iomem = dchid->sram_base + (res.start - dchid->sram_res.start);
> + bfr->is_mapped = true;
> +
> + return 0;
> +}
> +
> +static void dchid_rtkit_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr)
> +{
> + struct dchid_dev *dchid = cookie;
> +
> + if (bfr->buffer)
> + dma_free_coherent(dchid->dev, bfr->size, bfr->buffer, bfr->iova);
> +}
> +
> +static const struct apple_rtkit_ops dchid_rtkit_ops = {
> + .shmem_setup = dchid_rtkit_shmem_setup,
> + .shmem_destroy = dchid_rtkit_shmem_destroy,
> +};
> +
> +static int dchid_map_helper_cpu(struct platform_device *pdev, struct dchid_dev *dchid)
> +{
> + struct resource *res;
> +
> + dchid->asc_base = devm_platform_ioremap_resource_byname(pdev, "coproc-asc");
> + if (IS_ERR(dchid->asc_base))
> + return PTR_ERR(dchid->asc_base);
> +
> + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "coproc-sram");
> + if (!res)
> + return -EINVAL;
> +
> + dchid->sram_res = *res;
> +
> + dchid->sram_base = devm_ioremap_resource(&pdev->dev, res);
> + if (IS_ERR(dchid->sram_base))
> + return PTR_ERR(dchid->sram_base);
> +
> + return 0;
> +}
> +
> +static int dchid_probe(struct platform_device *pdev)
> +{
> + struct device *dev = &pdev->dev;
> + struct dchid_dev *dchid;
> + int ret;
> +
> + ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(44));
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to set DMA mask\n");
> +
> + dchid = devm_kzalloc(dev, sizeof(*dchid), GFP_KERNEL);
> + if (!dchid)
> + return -ENOMEM;
> +
> + dchid->rx_buf = devm_kmalloc(dev, DCHID_RX_BUF_SIZE, GFP_KERNEL);
> + if (!dchid->rx_buf)
> + return -ENOMEM;
> +
> + dchid->dev = dev;
> + mutex_init(&dchid->ifaces_lock);
> + platform_set_drvdata(pdev, dchid);
> +
> + ret = dchid_map_helper_cpu(pdev, dchid);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to map helper CPU\n");
> +
> + dchid->dc_mbox_client.dev = dev;
> + dchid->dc_mbox_client.tx_block = true;
> + dchid->dc_mbox_client.rx_callback = dchid_rx_callback;
> +
> + dchid->dc_mbox = mbox_request_channel_byname(&dchid->dc_mbox_client,
> + "dockchannel");
> + if (IS_ERR(dchid->dc_mbox))
> + return dev_err_probe(dev, PTR_ERR(dchid->dc_mbox),
> + "failed to request DockChannel mailbox\n");
> +
> + ret = devm_add_action_or_reset(dev, dchid_free_mbox, dchid->dc_mbox);
> + if (ret)
> + return ret;
> +
> + dchid->rtk = devm_apple_rtkit_init(dev, dchid, "asc", 0, &dchid_rtkit_ops);
> + if (IS_ERR(dchid->rtk))
> + return dev_err_probe(dev, PTR_ERR(dchid->rtk), "failed to init RTKit\n");
> +
> + writel_relaxed(APPLE_ASC_CPU_CONTROL_RUN,
> + dchid->asc_base + APPLE_ASC_CPU_CONTROL);
> +
> + ret = apple_rtkit_wake(dchid->rtk);
> + if (ret)
> + return dev_err_probe(dev, ret, "failed to wake coprocessor\n");
> +
> + dchid->new_iface_wq = alloc_ordered_workqueue("dchid-new", 0);
> + if (!dchid->new_iface_wq)
> + return dev_err_probe(dev, -ENOMEM, "failed to allocate workqueue\n");
> +
> + ret = devm_add_action_or_reset(dev, dchid_destroy_wq, dchid->new_iface_wq);
> + if (ret)
> + return ret;
> +
> + dchid->comm = dchid_get_interface(dchid, IFACE_COMM, "comm");
> + if (!dchid->comm)
> + return dev_err_probe(dev, -EIO, "failed to init comm interface\n");
> +
> + return 0;
> +}
> +
> +static void dchid_remove(struct platform_device *pdev)
> +{
> + struct dchid_dev *dchid = platform_get_drvdata(pdev);
> + int i;
> +
> + if (dchid->dc_mbox) {
> + devm_release_action(&pdev->dev, dchid_free_mbox, dchid->dc_mbox);
> + dchid->dc_mbox = NULL;
> + }
> +
> + if (dchid->rtk && apple_rtkit_is_running(dchid->rtk))
> + apple_rtkit_quiesce(dchid->rtk);
> +
> + if (dchid->asc_base)
> + writel_relaxed(0, dchid->asc_base + APPLE_ASC_CPU_CONTROL);
> +
> + for (i = 0; i < MAX_INTERFACES; i++) {
> + struct dchid_iface *iface = dchid->ifaces[i];
> +
> + if (!iface)
> + continue;
> +
> + cancel_work_sync(&iface->create_work);
> + flush_workqueue(iface->wq);
> +
> + if (iface->hid)
> + hid_destroy_device(iface->hid);
> + }
> +
> + if (dchid->new_iface_wq)
> + flush_workqueue(dchid->new_iface_wq);
> +}
> +
> +static const struct of_device_id dchid_of_match[] = {
> + { .compatible = "apple,t8122-dockchannel-hid" },
> + { .compatible = "apple,t8112-dockchannel-hid" },
> + {},
> +};
> +MODULE_DEVICE_TABLE(of, dchid_of_match);
> +
> +static struct platform_driver dchid_platform_driver = {
> + .driver = {
> + .name = "dockchannel-hid",
> + .of_match_table = dchid_of_match,
> + },
> + .probe = dchid_probe,
> + .remove = dchid_remove,
> +};
> +module_platform_driver(dchid_platform_driver);
> +
> +MODULE_DESCRIPTION("Apple DockChannel HID transport driver");
> +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
> +MODULE_AUTHOR("Michael Reeves <michael.reeves077@gmail.com>");
> +MODULE_LICENSE("Dual MIT/GPL");
>
^ permalink raw reply [flat|nested] 22+ messages in thread* Re: [PATCH 08/10] HID: apple: Add DockChannel HID transport driver
2026-06-30 13:21 ` Yureka Lilian
@ 2026-07-01 14:32 ` Michael Reeves
0 siblings, 0 replies; 22+ messages in thread
From: Michael Reeves @ 2026-07-01 14:32 UTC (permalink / raw)
To: Yureka Lilian
Cc: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires, asahi, linux-arm-kernel,
linux-kernel, devicetree, iommu, linux-input
Hi Yureka,
Thank you for the review and the feedback.
On Tue, Jun 30, 2026 at 11:21 PM Yureka Lilian <yureka@cyberchaos.dev> wrote:
[...]
> > + strscpy(hid->uniq, dchid->serial, sizeof(hid->uniq));
> If the keyboard appeared before stm, dchip->serial might be uninitialized.
Yes, true, thank you for picking this up!
[...]
> > + /*
> > + * Prefer to enable STM first, since it provides device IDs. Some
> > + * firmware versions do not expose STM, so let the keyboard start
> > + * without it.
> > + */
> > + if (iface->dchid->id_ready || !strcmp(iface->name, "stm") ||
> > + !strcmp(iface->name, "keyboard"))
>
> I specifically asked for a mechanism to let the keyboard probe even on
> devices which do not expose/have stm. Thanks for adding that!
> However, I think this might need some more sophisticated mechanism to
> decide whether the stm is still going to appear after the keyboard, or
> not at all. I'm not sure if there is a way to tell this at this point,
> or we need to add a timeout for the stm to appearing, which needs to
> expire before we create the other interfaces with fake serials.
>
I do not think there is a way to tell at this point, unfortunately.
I think the best way to resolve this would be to implement a delayed work
item that is scheduled during probe. If STM appears and initalises before
the timer (I'm not sure how long it should be right now, I will prototype and
experiment) we cancel the delayed work, get the real serial, and spin up
the keyboard. If the timeout expires without seeing the STM interface, we
assume the platform does not have one, mark id_ready as true, and
proceed to init the keyboard.
I will work on this and experiment to see if it's the best solution or
if there's
any others as well as to find the ideal timeout, and submit as part of v2.
[...]
Thanks again,
Michael
^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH 08/10] HID: apple: Add DockChannel HID transport driver
2026-06-30 12:54 ` [PATCH 08/10] HID: apple: Add DockChannel HID transport driver Michael Reeves via B4 Relay
2026-06-30 13:21 ` Yureka Lilian
@ 2026-07-02 19:32 ` Julian Braha
1 sibling, 0 replies; 22+ messages in thread
From: Julian Braha @ 2026-07-02 19:32 UTC (permalink / raw)
To: michael.reeves077, Sven Peter, Janne Grunau, Neal Gompa,
Jassi Brar, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Hector Martin, Joerg Roedel (AMD), Will Deacon, Robin Murphy,
Dmitry Torokhov, Jiri Kosina, Benjamin Tissoires
Cc: asahi, linux-arm-kernel, linux-kernel, devicetree, iommu,
linux-input
Hi Michael,
On 6/30/26 13:54, Michael Reeves via B4 Relay wrote:
> +source "drivers/hid/dockchannel/Kconfig"
> +
> endif # HID
>
> +++ b/drivers/hid/dockchannel/Kconfig
> @@ -0,0 +1,15 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR MIT
> +
> +config APPLE_DOCKCHANNEL_HID
> + tristate "HID over Apple DockChannel"
> + depends on APPLE_DOCKCHANNEL
> + depends on APPLE_RTKIT
> + depends on HID
APPLE_DOCKCHANNEL_HID has a duplicate dependency on HID,
since you put the import for this file inside of 'if HID..endif',
and then also gave it a 'depends on HID'.
- Julian Braha
^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH 09/10] arm64: dts: apple: Add MTP DockChannel HID nodes
2026-06-30 12:54 [PATCH 00/10] Add support for Apple Silicon DockChannel internal keyboards Michael Reeves via B4 Relay
` (7 preceding siblings ...)
2026-06-30 12:54 ` [PATCH 08/10] HID: apple: Add DockChannel HID transport driver Michael Reeves via B4 Relay
@ 2026-06-30 12:54 ` Michael Reeves via B4 Relay
2026-06-30 12:54 ` [PATCH 10/10] arm64: dts: apple: Enable DockChannel HID on M2 and M3 laptops Michael Reeves via B4 Relay
9 siblings, 0 replies; 22+ messages in thread
From: Michael Reeves via B4 Relay @ 2026-06-30 12:54 UTC (permalink / raw)
To: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires
Cc: asahi, linux-arm-kernel, linux-kernel, devicetree, iommu,
linux-input, Michael Reeves
From: Michael Reeves <michael.reeves077@gmail.com>
Add disabled MTP nodes for the Apple SoCs used by M2 and M3 systems.
Each instance describes the RTKit ASC mailbox, MTP DART, DockChannel
mailbox, and DockChannel HID client. Board files can enable them on
machines that route internal input through MTP.
Co-developed-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
---
arch/arm64/boot/dts/apple/t602x-die0.dtsi | 46 ++++++++++++++++++++++++++++++
arch/arm64/boot/dts/apple/t8112.dtsi | 46 ++++++++++++++++++++++++++++++
arch/arm64/boot/dts/apple/t8122.dtsi | 47 +++++++++++++++++++++++++++++++
3 files changed, 139 insertions(+)
diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
index 8622ddea7b44..edc73682fd22 100644
--- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
@@ -150,6 +150,52 @@ pinctrl_smc: pinctrl@2a2820000 {
<AIC_IRQ 0 857 IRQ_TYPE_LEVEL_HIGH>;
};
+ mtp_mbox: mbox@2a9408000 {
+ compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4";
+ reg = <0x2 0xa9408000 0x0 0x4000>;
+ interrupt-parent = <&aic>;
+ interrupts = <AIC_IRQ 0 693 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 0 694 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 0 695 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 0 696 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "send-empty", "send-not-empty",
+ "recv-empty", "recv-not-empty";
+ #mbox-cells = <0>;
+ status = "disabled";
+ };
+
+ mtp_dart: iommu@2a9808000 {
+ compatible = "apple,t6020-dart", "apple,t8110-dart";
+ reg = <0x2 0xa9808000 0x0 0x4000>;
+ interrupt-parent = <&aic>;
+ interrupts = <AIC_IRQ 0 676 IRQ_TYPE_LEVEL_HIGH>;
+ #iommu-cells = <1>;
+ status = "disabled";
+ };
+
+ mtp_dockchannel: mailbox@2a9b14000 {
+ compatible = "apple,t6020-dockchannel", "apple,t8112-dockchannel";
+ reg = <0x2 0xa9b14000 0x0 0x4000>,
+ <0x2 0xa9b30000 0x0 0x4000>,
+ <0x2 0xa9b34000 0x0 0x4000>;
+ reg-names = "irq", "config", "data";
+ interrupt-parent = <&aic>;
+ interrupts = <AIC_IRQ 0 677 IRQ_TYPE_LEVEL_HIGH>;
+ #mbox-cells = <0>;
+ status = "disabled";
+ };
+
+ mtp_hid: hid@2a9400000 {
+ compatible = "apple,t6020-dockchannel-hid", "apple,t8112-dockchannel-hid";
+ reg = <0x2 0xa9400000 0x0 0x4000>,
+ <0x2 0xa9c00000 0x0 0x100000>;
+ reg-names = "coproc-asc", "coproc-sram";
+ mboxes = <&mtp_mbox>, <&mtp_dockchannel>;
+ mbox-names = "asc", "dockchannel";
+ iommus = <&mtp_dart 1>;
+ status = "disabled";
+ };
+
sio_dart: iommu@39b008000 {
compatible = "apple,t6020-dart", "apple,t8110-dart";
reg = <0x3 0x9b008000 0x0 0x8000>;
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 85c47422d4e8..8411828c0772 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -983,6 +983,52 @@ pinctrl_aop: pinctrl@24a820000 {
<AIC_IRQ 307 IRQ_TYPE_LEVEL_HIGH>;
};
+ mtp_mbox: mbox@24e408000 {
+ compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4";
+ reg = <0x2 0x4e408000 0x0 0x4000>;
+ interrupt-parent = <&aic>;
+ interrupts = <AIC_IRQ 864 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 865 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 866 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 867 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "send-empty", "send-not-empty",
+ "recv-empty", "recv-not-empty";
+ #mbox-cells = <0>;
+ status = "disabled";
+ };
+
+ mtp_dart: iommu@24e808000 {
+ compatible = "apple,t8110-dart";
+ reg = <0x2 0x4e808000 0x0 0x4000>;
+ interrupt-parent = <&aic>;
+ interrupts = <AIC_IRQ 848 IRQ_TYPE_LEVEL_HIGH>;
+ #iommu-cells = <1>;
+ status = "disabled";
+ };
+
+ mtp_dockchannel: mailbox@24eb14000 {
+ compatible = "apple,t8112-dockchannel";
+ reg = <0x2 0x4eb14000 0x0 0x4000>,
+ <0x2 0x4eb30000 0x0 0x4000>,
+ <0x2 0x4eb34000 0x0 0x4000>;
+ reg-names = "irq", "config", "data";
+ interrupt-parent = <&aic>;
+ interrupts = <AIC_IRQ 850 IRQ_TYPE_LEVEL_HIGH>;
+ #mbox-cells = <0>;
+ status = "disabled";
+ };
+
+ mtp_hid: hid@24e400000 {
+ compatible = "apple,t8112-dockchannel-hid";
+ reg = <0x2 0x4e400000 0x0 0x4000>,
+ <0x2 0x4ec00000 0x0 0x100000>;
+ reg-names = "coproc-asc", "coproc-sram";
+ mboxes = <&mtp_mbox>, <&mtp_dockchannel>;
+ mbox-names = "asc", "dockchannel";
+ iommus = <&mtp_dart 1>;
+ status = "disabled";
+ };
+
ans_mbox: mbox@277408000 {
compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4";
reg = <0x2 0x77408000 0x0 0x4000>;
diff --git a/arch/arm64/boot/dts/apple/t8122.dtsi b/arch/arm64/boot/dts/apple/t8122.dtsi
index c6196225e96e..ec1f47d15ec9 100644
--- a/arch/arm64/boot/dts/apple/t8122.dtsi
+++ b/arch/arm64/boot/dts/apple/t8122.dtsi
@@ -438,6 +438,53 @@ pinctrl_aop: pinctrl@2f4824000 {
<AIC_IRQ 351 IRQ_TYPE_LEVEL_HIGH>,
<AIC_IRQ 352 IRQ_TYPE_LEVEL_HIGH>;
};
+
+ mtp_mbox: mbox@2fa408000 {
+ compatible = "apple,t8122-asc-mailbox", "apple,asc-mailbox-v4";
+ reg = <0x2 0xfa408000 0x0 0x4000>;
+ interrupt-parent = <&aic>;
+ interrupts = <AIC_IRQ 838 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 839 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 840 IRQ_TYPE_LEVEL_HIGH>,
+ <AIC_IRQ 841 IRQ_TYPE_LEVEL_HIGH>;
+ interrupt-names = "send-empty", "send-not-empty",
+ "recv-empty", "recv-not-empty";
+ #mbox-cells = <0>;
+ status = "disabled";
+ };
+
+ mtp_dart: iommu@2fa808000 {
+ compatible = "apple,t8122-dart", "apple,t8110-dart";
+ reg = <0x2 0xfa808000 0x0 0x4000>;
+ interrupt-parent = <&aic>;
+ interrupts = <AIC_IRQ 822 IRQ_TYPE_LEVEL_HIGH>;
+ #iommu-cells = <1>;
+ status = "disabled";
+ };
+
+ mtp_dockchannel: mailbox@2fab14000 {
+ compatible = "apple,t8122-dockchannel", "apple,t8112-dockchannel";
+ reg = <0x2 0xfab14000 0x0 0x4000>,
+ <0x2 0xfab30000 0x0 0x4000>,
+ <0x2 0xfab34000 0x0 0x4000>;
+ reg-names = "irq", "config", "data";
+ interrupt-parent = <&aic>;
+ interrupts = <AIC_IRQ 824 IRQ_TYPE_LEVEL_HIGH>;
+ #mbox-cells = <0>;
+ status = "disabled";
+ };
+
+ mtp_hid: hid@2fa400000 {
+ compatible = "apple,t8122-dockchannel-hid",
+ "apple,t8112-dockchannel-hid";
+ reg = <0x2 0xfa400000 0x0 0x4000>,
+ <0x2 0xfac00000 0x0 0x100000>;
+ reg-names = "coproc-asc", "coproc-sram";
+ mboxes = <&mtp_mbox>, <&mtp_dockchannel>;
+ mbox-names = "asc", "dockchannel";
+ iommus = <&mtp_dart 1>;
+ status = "disabled";
+ };
};
};
--
2.51.2
^ permalink raw reply related [flat|nested] 22+ messages in thread* [PATCH 10/10] arm64: dts: apple: Enable DockChannel HID on M2 and M3 laptops
2026-06-30 12:54 [PATCH 00/10] Add support for Apple Silicon DockChannel internal keyboards Michael Reeves via B4 Relay
` (8 preceding siblings ...)
2026-06-30 12:54 ` [PATCH 09/10] arm64: dts: apple: Add MTP DockChannel HID nodes Michael Reeves via B4 Relay
@ 2026-06-30 12:54 ` Michael Reeves via B4 Relay
9 siblings, 0 replies; 22+ messages in thread
From: Michael Reeves via B4 Relay @ 2026-06-30 12:54 UTC (permalink / raw)
To: Sven Peter, Janne Grunau, Neal Gompa, Jassi Brar, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Hector Martin,
Joerg Roedel (AMD), Will Deacon, Robin Murphy, Dmitry Torokhov,
Jiri Kosina, Benjamin Tissoires
Cc: asahi, linux-arm-kernel, linux-kernel, devicetree, iommu,
linux-input, Michael Reeves
From: Michael Reeves <michael.reeves077@gmail.com>
Enable the MTP mailbox, DART, DockChannel mailbox, and HID transport on
the M2 and M3 laptop device trees using this internal input path.
Add a keyboard alias and keyboard child node for each machine so the
transport can expose the internal keyboard.
Co-developed-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Michael Reeves <michael.reeves077@gmail.com>
---
arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi | 25 +++++++++++++++++++++++++
arch/arm64/boot/dts/apple/t8112-j413.dts | 20 ++++++++++++++++++++
arch/arm64/boot/dts/apple/t8112-j415.dts | 20 ++++++++++++++++++++
arch/arm64/boot/dts/apple/t8112-j493.dts | 22 +++++++++++++++++++++-
arch/arm64/boot/dts/apple/t8122-j504.dts | 22 ++++++++++++++++++++++
arch/arm64/boot/dts/apple/t8122-j613.dts | 23 +++++++++++++++++++++++
arch/arm64/boot/dts/apple/t8122-j615.dts | 23 +++++++++++++++++++++++
7 files changed, 154 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
index 0e806d8ddf81..46ed5ea86242 100644
--- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
@@ -16,6 +16,12 @@
#include "t600x-j314-j316.dtsi"
+/ {
+ aliases {
+ keyboard = &keyboard;
+ };
+};
+
&framebuffer0 {
power-domains = <&ps_disp0_cpu0>, <&ps_dptx_phy_ps>;
};
@@ -43,3 +49,22 @@ &wifi0 {
&bluetooth0 {
compatible = "pci14e4,5f72";
};
+
+&mtp_mbox {
+ status = "okay";
+};
+
+&mtp_dart {
+ status = "okay";
+};
+
+&mtp_dockchannel {
+ status = "okay";
+};
+
+&mtp_hid {
+ status = "okay";
+
+ keyboard: keyboard {
+ };
+};
diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index 1a08a41f369b..1256e7cd9876 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -20,6 +20,7 @@ / {
aliases {
bluetooth0 = &bluetooth0;
+ keyboard = &keyboard;
wifi0 = &wifi0;
};
@@ -91,3 +92,22 @@ &i2c4 {
&fpwm1 {
status = "okay";
};
+
+&mtp_mbox {
+ status = "okay";
+};
+
+&mtp_dart {
+ status = "okay";
+};
+
+&mtp_dockchannel {
+ status = "okay";
+};
+
+&mtp_hid {
+ status = "okay";
+
+ keyboard: keyboard {
+ };
+};
diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts
index e37c56d9fb4d..1db3500e991f 100644
--- a/arch/arm64/boot/dts/apple/t8112-j415.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j415.dts
@@ -20,6 +20,7 @@ / {
aliases {
bluetooth0 = &bluetooth0;
+ keyboard = &keyboard;
wifi0 = &wifi0;
};
@@ -91,3 +92,22 @@ &i2c4 {
&fpwm1 {
status = "okay";
};
+
+&mtp_mbox {
+ status = "okay";
+};
+
+&mtp_dart {
+ status = "okay";
+};
+
+&mtp_dockchannel {
+ status = "okay";
+};
+
+&mtp_hid {
+ status = "okay";
+
+ keyboard: keyboard {
+ };
+};
diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index ec116da3e4dd..5deb2dd9802a 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0+ OR MIT
/*
- * Apple MacBook Pro (13-inch, M1, 2022)
+ * Apple MacBook Pro (13-inch, M2, 2022)
*
* target-type: J493
*
@@ -24,6 +24,7 @@ / {
*/
aliases {
bluetooth0 = &bluetooth0;
+ keyboard = &keyboard;
touchbar0 = &touchbar0;
wifi0 = &wifi0;
};
@@ -146,3 +147,22 @@ touchbar0: touchbar@0 {
touchscreen-inverted-y;
};
};
+
+&mtp_mbox {
+ status = "okay";
+};
+
+&mtp_dart {
+ status = "okay";
+};
+
+&mtp_dockchannel {
+ status = "okay";
+};
+
+&mtp_hid {
+ status = "okay";
+
+ keyboard: keyboard {
+ };
+};
diff --git a/arch/arm64/boot/dts/apple/t8122-j504.dts b/arch/arm64/boot/dts/apple/t8122-j504.dts
index 464491b55b01..0d93ac72fff9 100644
--- a/arch/arm64/boot/dts/apple/t8122-j504.dts
+++ b/arch/arm64/boot/dts/apple/t8122-j504.dts
@@ -18,6 +18,10 @@ / {
compatible = "apple,j504", "apple,t8122", "apple,arm-platform";
model = "Apple MacBook Pro (14-inch, M3, 2023)";
+ aliases {
+ keyboard = &keyboard;
+ };
+
led-controller {
compatible = "pwm-leds";
led-0 {
@@ -35,3 +39,21 @@ &fpwm1 {
status = "okay";
};
+&mtp_mbox {
+ status = "okay";
+};
+
+&mtp_dart {
+ status = "okay";
+};
+
+&mtp_dockchannel {
+ status = "okay";
+};
+
+&mtp_hid {
+ status = "okay";
+
+ keyboard: keyboard {
+ };
+};
diff --git a/arch/arm64/boot/dts/apple/t8122-j613.dts b/arch/arm64/boot/dts/apple/t8122-j613.dts
index 51894ea705e7..e77b1ad869eb 100644
--- a/arch/arm64/boot/dts/apple/t8122-j613.dts
+++ b/arch/arm64/boot/dts/apple/t8122-j613.dts
@@ -17,6 +17,10 @@ / {
compatible = "apple,j613", "apple,t8122", "apple,arm-platform";
model = "Apple MacBook Air (13-inch, M3, 2024)";
+ aliases {
+ keyboard = &keyboard;
+ };
+
led-controller {
compatible = "pwm-leds";
led-0 {
@@ -33,3 +37,22 @@ led-0 {
&fpwm1 {
status = "okay";
};
+
+&mtp_mbox {
+ status = "okay";
+};
+
+&mtp_dart {
+ status = "okay";
+};
+
+&mtp_dockchannel {
+ status = "okay";
+};
+
+&mtp_hid {
+ status = "okay";
+
+ keyboard: keyboard {
+ };
+};
diff --git a/arch/arm64/boot/dts/apple/t8122-j615.dts b/arch/arm64/boot/dts/apple/t8122-j615.dts
index 2a1970c1bc90..5da0021d40f8 100644
--- a/arch/arm64/boot/dts/apple/t8122-j615.dts
+++ b/arch/arm64/boot/dts/apple/t8122-j615.dts
@@ -17,6 +17,10 @@ / {
compatible = "apple,j615", "apple,t8122", "apple,arm-platform";
model = "Apple MacBook Air (15-inch, M3, 2024)";
+ aliases {
+ keyboard = &keyboard;
+ };
+
led-controller {
compatible = "pwm-leds";
led-0 {
@@ -33,3 +37,22 @@ led-0 {
&fpwm1 {
status = "okay";
};
+
+&mtp_mbox {
+ status = "okay";
+};
+
+&mtp_dart {
+ status = "okay";
+};
+
+&mtp_dockchannel {
+ status = "okay";
+};
+
+&mtp_hid {
+ status = "okay";
+
+ keyboard: keyboard {
+ };
+};
--
2.51.2
^ permalink raw reply related [flat|nested] 22+ messages in thread