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 00000000000000..bc7972ff6d74bd --- /dev/null +++ b/Documentation/devicetree/bindings/input/apple,dockchannel-hid.yaml @@ -0,0 +1,102 @@ +# 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 + +description: | + Transport layer for HID devices (keyboard, trackpad) connected via the + DockChannel FIFO interface on Apple Silicon SoCs. Contains a minimal + coprocessor called the MTP (Multi-Touch Coprocessor) which assists with + this and must be booted for it to work. + +properties: + compatible: + oneOf: + - items: + - const: apple,t8112-dockchannel-hid + - items: + - const: apple,t6020-dockchannel-hid + - const: apple,t8112-dockchannel-hid + + reg: + items: + - description: Config interface registers + - description: Data interface registers + - description: Coprocessor ASC registers + - description: Coprocessor SRAM/mailbox registers + + reg-names: + items: + - const: config + - const: data + - const: coproc-asc + - const: coproc-sram + + interrupts: + items: + - description: TX interrupt + - description: RX interrupt + + interrupt-names: + items: + - const: tx + - const: rx + + mboxes: + maxItems: 1 + + iommus: + maxItems: 1 + + keyboard: + type: object + description: | + Optional properties for the internal keyboard. + properties: + hid-country-code: + $ref: /schemas/types.yaml#/definitions/uint32 + description: The USB HID country code for the keyboard layout. + additionalProperties: false + +required: + - compatible + - reg + - reg-names + - interrupts + - interrupt-names + - mboxes + - iommus + +additionalProperties: false + +examples: + - | + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + input@8000 { + compatible = "apple,t8112-dockchannel-hid"; + reg = <0x2 0xfab30000 0x0 0x4000>, + <0x2 0xfab34000 0x0 0x4000>, + <0x2 0xfa400000 0x0 0x4000>, + <0x2 0xfa050000 0x0 0x100000>; + reg-names = "config", "data", "coproc-asc", "coproc-sram"; + mboxes = <&mtp_mbox>; + iommus = <&mtp_dart 1>; + interrupts = <2 IRQ_TYPE_LEVEL_HIGH>, <3 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "tx", "rx"; + + keyboard { + hid-country-code = <0>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/soc/apple/apple,dockchannel.yaml b/Documentation/devicetree/bindings/soc/apple/apple,dockchannel.yaml new file mode 100644 index 00000000000000..f345183ca4f2b8 --- /dev/null +++ b/Documentation/devicetree/bindings/soc/apple/apple,dockchannel.yaml @@ -0,0 +1,86 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +# Copyright (c) 2025 The Asahi Linux contributors +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/soc/apple/apple,dockchannel.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple DockChannel FIFO + +maintainers: + - Michael Reeves + +description: | + DockChannel is a hardware FIFO and interrupt controller used on Apple SoCs + for low-latency communication with co-processors. + +properties: + compatible: + oneOf: + - items: + - const: apple,t8112-dockchannel + - items: + - const: apple,t6020-dockchannel + - const: apple,t8112-dockchannel + + reg: + maxItems: 1 + + interrupts: + maxItems: 1 + + interrupt-controller: true + + "#interrupt-cells": + const: 2 + + ranges: true + + nonposted-mmio: true + + "#address-cells": + const: 2 + + "#size-cells": + const: 2 + +patternProperties: + "^input@[0-9a-f]+$": + $ref: /schemas/input/apple,dockchannel-hid.yaml + +required: + - compatible + - reg + - interrupts + - interrupt-controller + - "#interrupt-cells" + - ranges + - "#address-cells" + - "#size-cells" + +additionalProperties: false + +examples: + - | + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + fifo@2a9b14000 { + compatible = "apple,t6020-dockchannel", "apple,t8112-dockchannel"; + reg = <0x2 0xa9b14000 0x0 0x4000>; + + interrupt-parent = <&aic>; + interrupts = ; + + ranges; + #address-cells = <2>; + #size-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index 5b11839cba9de1..a95603c06f5092 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2459,6 +2459,7 @@ F: Documentation/devicetree/bindings/dma/apple,admac.yaml F: Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml F: Documentation/devicetree/bindings/gpu/apple,agx.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 @@ -2476,6 +2477,7 @@ F: Documentation/devicetree/bindings/power/apple* F: Documentation/devicetree/bindings/power/reset/apple,smc-reboot.yaml F: Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml F: Documentation/devicetree/bindings/rtc/apple,smc-rtc.yaml +F: Documentation/devicetree/bindings/soc/apple/* F: Documentation/devicetree/bindings/spi/apple,spi.yaml F: Documentation/devicetree/bindings/spmi/apple,spmi.yaml F: Documentation/devicetree/bindings/usb/apple,dwc3.yaml @@ -2487,6 +2489,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/apple-dockchannel-hid/* F: drivers/hwmon/macsmc-hwmon.c F: drivers/pmdomain/apple/ F: drivers/i2c/busses/i2c-pasemi-core.c diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi index 2e7d2bf08ddc82..19a6ab3cff689b 100644 --- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi +++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi @@ -144,6 +144,65 @@ ; }; + mtp_mbox: mbox@2a9408000 { + compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0xa9408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + 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 = ; + #iommu-cells = <1>; + + status = "disabled"; + }; + + mtp_dockchannel: fifo@2a9b14000 { + compatible = "apple,t6020-dockchannel", "apple,t8112-dockchannel"; + reg = <0x2 0xa9b14000 0x0 0x4000>; + + interrupt-parent = <&aic>; + interrupts = ; + + ranges; + #address-cells = <2>; + #size-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + + status = "disabled"; + + mtp_hid: input@2a9b30000 { + compatible = "apple,t6020-dockchannel-hid", "apple,t8112-dockchannel-hid"; + reg = <0x2 0xa9b30000 0x0 0x4000>, + <0x2 0xa9b34000 0x0 0x4000>, + <0x2 0xa9400000 0x0 0x4000>, + <0x2 0xa9c00000 0x0 0x100000>; + reg-names = "config", "data", "coproc-asc", "coproc-sram"; + + mboxes = <&mtp_mbox>; + iommus = <&mtp_dart 1>; + + interrupt-parent = <&mtp_dockchannel>; + interrupts = <2 IRQ_TYPE_LEVEL_HIGH>, + <3 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "tx", "rx"; + }; + }; + sio_dart: iommu@39b008000 { compatible = "apple,t6020-dart", "apple,t8110-dart"; reg = <0x3 0x9b008000 0x0 0x8000>; diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi index 0e806d8ddf81b1..fceff4224894c8 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 @@ &bluetooth0 { compatible = "pci14e4,5f72"; }; + +&mtp_mbox { + status = "okay"; +}; + +&mtp_dart { + status = "okay"; +}; + +&mtp_dockchannel { + status = "okay"; +}; + +&mtp_hid { + keyboard: keyboard { + /* Filled by bootloader */ + hid-country-code = <0>; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts index 6f69658623bf89..b3755befd82556 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; wifi0 = &wifi0; + keyboard = &keyboard; }; led-controller { @@ -78,3 +79,22 @@ &fpwm1 { status = "okay"; }; + +&mtp_mbox { + status = "okay"; +}; + +&mtp_dart { + status = "okay"; +}; + +&mtp_dockchannel { + status = "okay"; +}; + +&mtp_hid { + keyboard: keyboard { + /* Filled by bootloader */ + hid-country-code = <0>; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts index b54e218e5384ca..cee13511f2b9aa 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; wifi0 = &wifi0; + keyboard = &keyboard; }; led-controller { @@ -78,3 +79,22 @@ &fpwm1 { status = "okay"; }; + +&mtp_mbox { + status = "okay"; +}; + +&mtp_dart { + status = "okay"; +}; + +&mtp_dockchannel { + status = "okay"; +}; + +&mtp_hid { + keyboard: keyboard { + /* Filled by bootloader */ + hid-country-code = <0>; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts index fb8ad7d4c65a8f..781d841a0dd324 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 * @@ -25,6 +25,7 @@ bluetooth0 = &bluetooth0; touchbar0 = &touchbar0; wifi0 = &wifi0; + keyboard = &keyboard; }; led-controller { @@ -133,3 +134,22 @@ touchscreen-inverted-y; }; }; + +&mtp_mbox { + status = "okay"; +}; + +&mtp_dart { + status = "okay"; +}; + +&mtp_dockchannel { + status = "okay"; +}; + +&mtp_hid { + keyboard: keyboard { + /* Filled by bootloader */ + hid-country-code = <0>; + }; +}; diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi index 3f79878b25af1f..917e96a174f235 100644 --- a/arch/arm64/boot/dts/apple/t8112.dtsi +++ b/arch/arm64/boot/dts/apple/t8112.dtsi @@ -976,6 +976,65 @@ ; }; + mtp_mbox: mbox@24e408000 { + compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; + reg = <0x2 0x4e408000 0x0 0x4000>; + interrupt-parent = <&aic>; + interrupts = , + , + , + ; + 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 = ; + #iommu-cells = <1>; + + status = "disabled"; + }; + + mtp_dockchannel: fifo@24eb14000 { + compatible = "apple,t8112-dockchannel"; + reg = <0x2 0x4eb14000 0x0 0x4000>; + + interrupt-parent = <&aic>; + interrupts = ; + + ranges; + #address-cells = <2>; + #size-cells = <2>; + + interrupt-controller; + #interrupt-cells = <2>; + + status = "disabled"; + + mtp_hid: input@24eb30000 { + compatible = "apple,t8112-dockchannel-hid"; + reg = <0x2 0x4eb30000 0x0 0x4000>, + <0x2 0x4eb34000 0x0 0x4000>, + <0x2 0x4e400000 0x0 0x4000>, + <0x2 0x4ec00000 0x0 0x100000>; + reg-names = "config", "data", "coproc-asc", "coproc-sram"; + + mboxes = <&mtp_mbox>; + iommus = <&mtp_dart 1>; + + interrupt-parent = <&mtp_dockchannel>; + interrupts = <2 IRQ_TYPE_LEVEL_HIGH>, + <3 IRQ_TYPE_LEVEL_HIGH>; + interrupt-names = "tx", "rx"; + }; + }; + ans_mbox: mbox@277408000 { compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4"; reg = <0x2 0x77408000 0x0 0x4000>; diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 920a64b66b25b3..68244803f79cc3 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -1442,6 +1442,8 @@ source "drivers/hid/surface-hid/Kconfig" source "drivers/hid/intel-thc-hid/Kconfig" +source "drivers/hid/apple-dockchannel-hid/Kconfig" + endif # HID # USB support may be used with HID disabled diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 361a7daedeb854..4677b9c46a0399 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -176,3 +176,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) += apple-dockchannel-hid/ diff --git a/drivers/hid/apple-dockchannel-hid/Kconfig b/drivers/hid/apple-dockchannel-hid/Kconfig new file mode 100644 index 00000000000000..1eb2e6f9146b46 --- /dev/null +++ b/drivers/hid/apple-dockchannel-hid/Kconfig @@ -0,0 +1,15 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +menu "Apple DockChannel HID support" + depends on APPLE_DOCKCHANNEL + +config APPLE_DOCKCHANNEL_HID + tristate "HID over DockChannel transport layer for Apple Silicon SoCs" + depends on APPLE_DOCKCHANNEL && INPUT && OF && HID + help + This provides a HID transport layer over the Apple DockChannel + interface. This 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 MacBook. + +endmenu diff --git a/drivers/hid/apple-dockchannel-hid/Makefile b/drivers/hid/apple-dockchannel-hid/Makefile new file mode 100644 index 00000000000000..289cc75bc44e60 --- /dev/null +++ b/drivers/hid/apple-dockchannel-hid/Makefile @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: GPL-2.0-only OR MIT +# +# Makefile for DockChannel HID transport drivers +# + +obj-$(CONFIG_APPLE_DOCKCHANNEL_HID) += apple_dockchannel_hid.o diff --git a/drivers/hid/apple-dockchannel-hid/apple_dockchannel_hid.c b/drivers/hid/apple-dockchannel-hid/apple_dockchannel_hid.c new file mode 100644 index 00000000000000..c54ee0783fc6df --- /dev/null +++ b/drivers/hid/apple-dockchannel-hid/apple_dockchannel_hid.c @@ -0,0 +1,1004 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple DockChannel HID transport driver + * + * Copyright The Asahi Linux Contributors + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + +/* Data + checksum */ +#define MAX_PKT_SIZE (0xffff + 4) + +#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 +#define STM_REPORT_KEYBTYPE 0x14 + +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; +} __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 dockchannel *dc; + + 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]; + + struct dchid_iface *comm; + struct mutex ifaces_lock; /* protects ifaces array */ + struct dchid_iface *ifaces[MAX_INTERFACES]; + + 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 u32 dchid_checksum(const void *data, size_t len) +{ + const u8 *p = data; + u32 sum = 0; + int i; + + while (len >= 4) { + sum += get_unaligned_le32(p); + p += 4; + len -= 4; + } + + if (len > 0) { + 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); + + /* Input events don't involve memory reclaim */ + 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")) { + 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; +} + +/* + * dchid_send must send the entire packet as one DockChannel transaction + * to ensure atomicity for the coprocessor hardware. + */ +static int dchid_send(struct dchid_iface *iface, u32 flags, const void *msg, size_t size) +{ + size_t payload_padded = round_up(size, 4); + size_t total_len = sizeof(struct dchid_hdr) + sizeof(struct dchid_subhdr) + + payload_padded + 4; + struct dchid_hdr *hdr; + struct dchid_subhdr *sub; + u32 *checksum_ptr; + u8 *buf; + int ret; + + 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 - 4); + + 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 - 4); + + ret = dockchannel_send(iface->dchid->dc, buf, total_len); + kfree(buf); + + return (ret == total_len) ? 0 : -EIO; +} + +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->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) { + /* Truly timed out; the response handler never ran */ + ret = -ETIMEDOUT; + } else if (iface->retcode) { + /* Response received, but coprocessor reported an error */ + 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 { + /* Command succeeded; return bytes written to resp_buf */ + 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; + + /* + * Removed FW loading logic. + * Just reset the interface to clean state. + */ + dev_dbg(iface->dchid->dev, "Resetting %s\n", iface->name); + 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); +} + +static int dchid_get_report_cmd(struct dchid_iface *iface, u8 reportnum, void *buf, size_t len) +{ + int ret = dchid_cmd(iface, HID_FEATURE_REPORT, REQ_GET_REPORT, &reportnum, 1, buf, len); + + return ret <= 0 ? ret : ret - 1; +} + +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: + 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; + int ret; + char cap_name[16]; + + 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); + + if (!strcmp(iface->name, "keyboard")) { + u32 country_code = 0; + + 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) +{ + if (iface->hid) + return; + + iface->hid_desc = devm_kmemdup(iface->dchid->dev, hid_desc, desc_len, GFP_KERNEL); + if (iface->hid_desc) + 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, 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); + + 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)) + 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) + 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_iface *iface; + struct dchid_init_block_hdr *blk; + + if (length < sizeof(*hdr)) + return; + + iface = dchid_get_interface(dchid, hdr->iface, hdr->name); + if (!iface) + return; + + data += sizeof(*hdr); + length -= sizeof(*hdr); + + while (length >= sizeof(*blk)) { + u16 blk_len; + + blk = data; + data += 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, data, blk_len); + break; + + case INIT_PRODUCT_NAME: { + char *product = data; + + if (blk_len > 0 && product[blk_len - 1] != 0) + dev_warn(dchid->dev, "Unterminated product name for %s\n", + iface->name); + break; + } + } + + data += blk_len; + length -= blk_len; + + if (le16_to_cpu(blk->type) == INIT_TERMINATOR) + break; + } + + if (hdr->more_packets) + return; + + if (iface->dchid->id_ready || !strcmp(iface->name, "stm")) + 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; + + 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; + int type = FIELD_GET(FLAGS_GROUP, shdr->flags); + u8 *payload = work->data + sizeof(*shdr); + u16 sub_len = le16_to_cpu(shdr->length); + u16 hdr_len = le16_to_cpu(work->hdr.length); + + if (sub_len + sizeof(*shdr) > hdr_len) { + dev_err(dchid->dev, "Bad sub header length\n"); + goto done; + } + + 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 = (void *)data; + u8 *payload = data + sizeof(*shdr); + u16 sub_len = le16_to_cpu(shdr->length); + u16 hdr_len = le16_to_cpu(hdr->length); + unsigned long flags; + bool complete_cmd = false; + + if (sub_len + sizeof(*shdr) > hdr_len || 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_handle_packet(void *cookie, size_t avail) +{ + struct dchid_dev *dchid = cookie; + struct dchid_hdr hdr; + struct dchid_work *work = NULL; + u8 *tmp_buf = NULL; + u32 checksum; + u16 payload_len; + size_t total_payload_size; + + if (dockchannel_recv(dchid->dc, &hdr, sizeof(hdr)) != sizeof(hdr)) + return; + + if (hdr.hdr_len != sizeof(hdr)) + goto reschedule; + + payload_len = le16_to_cpu(hdr.length); + total_payload_size = payload_len + 4; // Payload + Checksum + + tmp_buf = kzalloc(total_payload_size, GFP_ATOMIC); + if (!tmp_buf) + goto reschedule; + + if (dockchannel_recv(dchid->dc, tmp_buf, total_payload_size) != total_payload_size) + goto out_free; + + checksum = dchid_checksum(&hdr, sizeof(hdr)); + checksum += dchid_checksum(tmp_buf, total_payload_size); + + if (checksum != DCHID_CHECKSUM_SEED) { + dev_err_ratelimited(dchid->dev, "Checksum error\n"); + goto out_free; + } + + if (hdr.iface >= MAX_INTERFACES || !dchid->ifaces[hdr.iface]) + goto out_free; + + if (hdr.channel == DCHID_CHANNEL_CMD) { + dchid_handle_ack(dchid->ifaces[hdr.iface], &hdr, tmp_buf); + goto out_free; + } + + work = kzalloc(sizeof(*work) + payload_len, GFP_ATOMIC); + if (!work) + goto out_free; + + work->hdr = hdr; + work->iface = dchid->ifaces[hdr.iface]; + memcpy(work->data, tmp_buf, payload_len); + INIT_WORK(&work->work, dchid_packet_work); + + queue_work(work->iface->wq, &work->work); + +out_free: + kfree(tmp_buf); +reschedule: + dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr)); +} + +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; + + /* Map ASC (Co-processor CPU control) */ + dchid->asc_base = devm_platform_ioremap_resource_byname(pdev, "coproc-asc"); + if (IS_ERR(dchid->asc_base)) + return PTR_ERR(dchid->asc_base); + + /* Map SRAM (Shared memory) */ + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "coproc-sram"); + if (!res) + return -EINVAL; + + /* Store resource copy for RTKit shmem setup */ + 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->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->rtk = devm_apple_rtkit_init(dev, dchid, NULL, 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 up coprocessor\n"); + + dchid->dc = dockchannel_init(pdev); + if (IS_ERR(dchid->dc)) + return dev_err_probe(dev, PTR_ERR(dchid->dc), "Failed to init DockChannel\n"); + + /* Create workqueue for dynamic interface addition */ + 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"); + + dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr)); + + return 0; +} + +static void dchid_remove(struct platform_device *pdev) +{ + struct dchid_dev *dchid = platform_get_drvdata(pdev); + int i; + + 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); + + dockchannel_await(dchid->dc, NULL, NULL, 0); + + 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,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 "); +MODULE_AUTHOR("Michael Reeves "); +MODULE_LICENSE("Dual MIT/GPL"); diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c index 57da4f86a9fa7f..4b0129c7037b8c 100644 --- a/drivers/hid/hid-apple.c +++ b/drivers/hid/hid-apple.c @@ -473,53 +473,61 @@ 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 it's on the Host bus, it's a modern internal keyboard. + * For other bus types, check the legacy USB product IDs. + */ + if (hid->bus == BUS_HOST) { 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); @@ -653,6 +661,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 HIDs on MacBook M2+ have wrong report sizes */ static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc, unsigned int *rsize) @@ -695,6 +704,38 @@ static const __u8 *apple_report_fixup(struct hid_device *hdev, __u8 *rdesc, rdesc[3] = 0x06; } + /* + * Fix up DockChannel descriptors that use oversized report sizes (16384 bits). + * The HID parser cannot handle such large sizes, so we redistribute the bits + * into a standard 8-bit report size with a proportionally increased count + * to maintain the same total data length in a byte-aligned format. + */ + if (*rsize >= 5) { + int i; + + for (i = 0; i <= *rsize - 5; i++) { + /* Look for Report Size 0x4000 (16384) followed by Report Count */ + 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 (Count %d)\n", + count); + + /* + * Replace with Report Size 8 and + * Report Count (2048 * count) + */ + rdesc[i] = 0x75; + rdesc[i + 1] = 0x08; + rdesc[i + 2] = 0x96; + rdesc[i + 3] = 0x00; + rdesc[i + 4] = count * 8; + } + } + } + } return rdesc; } @@ -759,7 +800,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; } @@ -1218,6 +1259,8 @@ 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_ANY, USB_VENDOR_ID_APPLE, HID_ANY_ID), + .driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK }, { } }; diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig index ad67368892311b..77ccb50412bb98 100644 --- a/drivers/soc/apple/Kconfig +++ b/drivers/soc/apple/Kconfig @@ -4,6 +4,17 @@ if ARCH_APPLE || COMPILE_TEST menu "Apple SoC drivers" +config APPLE_DOCKCHANNEL + tristate "Apple DockChannel FIFO" + depends on ARCH_APPLE || COMPILE_TEST + help + DockChannel is a hardware FIFO used on Apple Silicon SoCs for + communication between the application processor and various + coprocessors. It is the primary transport for the internal + keyboard and trackpad on M2 and later MacBook models. + + Say Y here if you have an M2 or later Apple MacBook. + config APPLE_MAILBOX tristate "Apple SoC mailboxes" depends on PM diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile index 4d9ab8f3037b71..0b6a9f92bbbbf8 100644 --- a/drivers/soc/apple/Makefile +++ b/drivers/soc/apple/Makefile @@ -1,5 +1,8 @@ # SPDX-License-Identifier: GPL-2.0-only +obj-$(CONFIG_APPLE_DOCKCHANNEL) += apple-dockchannel.o +apple-dockchannel-y = dockchannel.o + obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o apple-mailbox-y = mailbox.o diff --git a/drivers/soc/apple/dockchannel.c b/drivers/soc/apple/dockchannel.c new file mode 100644 index 00000000000000..2d1600c174cbd5 --- /dev/null +++ b/drivers/soc/apple/dockchannel.c @@ -0,0 +1,522 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple DockChannel FIFO driver + * Copyright The Asahi Linux Contributors + * + * This driver uses PIO to transfer data to/from the FIFO. + * Because all data is moved by the CPU and no DMA is involved, + * we do not require the expensive memory barriers provided by + * the non-relaxed MMIO accessors. Internal ordering to the + * same peripheral is maintained. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define DOCKCHANNEL_FIFO_SIZE 0x800 + +#define DOCKCHANNEL_MAX_IRQ 32 + +#define DOCKCHANNEL_TX_TIMEOUT_MS 1000 +#define DOCKCHANNEL_RX_TIMEOUT_MS 1000 + +#define IRQ_MASK 0x0 +#define IRQ_FLAG 0x4 + +#define IRQ_TX BIT(0) +#define IRQ_RX BIT(1) + +#define CONFIG_TX_THRESH 0x0 +#define CONFIG_RX_THRESH 0x4 + +#define DATA_TX8 0x4 +#define DATA_TX16 0x8 +#define DATA_TX24 0xc +#define DATA_TX32 0x10 +#define DATA_TX_FREE 0x14 +#define DATA_RX8 0x1c +#define DATA_RX16 0x20 +#define DATA_RX24 0x24 +#define DATA_RX32 0x28 +#define DATA_RX_COUNT 0x2c + +struct dockchannel { + struct device *dev; + struct mutex lock; /* protects send/recv/await serialization */ + + int tx_irq; + int rx_irq; + + void __iomem *config_base; + void __iomem *data_base; + + bool awaiting; + struct completion tx_comp; + struct completion rx_comp; + + void *cookie; + void (*data_available)(void *cookie, size_t avail); +}; + +struct dockchannel_common { + struct device *dev; + struct irq_domain *domain; + int irq; + + void __iomem *irq_base; + struct raw_spinlock lock; /* protects IRQ mask RMW */ +}; + +static irqreturn_t dockchannel_tx_irq(int irq, void *data) +{ + struct dockchannel *dockchannel = data; + + disable_irq_nosync(irq); + complete(&dockchannel->tx_comp); + + return IRQ_HANDLED; +} + +static irqreturn_t dockchannel_rx_irq(int irq, void *data) +{ + struct dockchannel *dockchannel = data; + + disable_irq_nosync(irq); + + if (dockchannel->awaiting) + return IRQ_WAKE_THREAD; + + complete(&dockchannel->rx_comp); + return IRQ_HANDLED; +} + +static irqreturn_t dockchannel_rx_irq_thread(int irq, void *data) +{ + struct dockchannel *dockchannel = data; + + /* Relaxed is safe here as there is no DMA, as explained at top of file */ + size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT); + + dockchannel->awaiting = false; + if (dockchannel->data_available) + dockchannel->data_available(dockchannel->cookie, avail); + + return IRQ_HANDLED; +} + +/** + * dockchannel_send - Send data via DockChannel + * @dockchannel: The dockchannel instance + * @buf: The buffer to send + * @count: Number of bytes to send + * + * This function blocks until all data is written to the FIFO. It handles + * waiting for space if the FIFO fills up. + * + * Return: Number of bytes sent or negative error code. + */ +int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count) +{ + size_t left = count; + const u8 *p = buf; + + mutex_lock(&dockchannel->lock); + + while (left > 0) { + /* Relaxed is safe here as there is no DMA, as explained at top of file */ + size_t avail = readl_relaxed(dockchannel->data_base + DATA_TX_FREE); + size_t block = min(left, avail); + + if (avail == 0) { + size_t threshold = min_t(size_t, DOCKCHANNEL_FIFO_SIZE / 2, left); + + writel_relaxed(threshold, dockchannel->config_base + CONFIG_TX_THRESH); + reinit_completion(&dockchannel->tx_comp); + enable_irq(dockchannel->tx_irq); + + unsigned long timeout; + + timeout = msecs_to_jiffies(DOCKCHANNEL_TX_TIMEOUT_MS); + if (!wait_for_completion_timeout(&dockchannel->tx_comp, + timeout)) { + /* + * If we timed out, we must ensure the IRQ is disabled. + * However, we must check if the handler ran *just* before + * we called disable_irq, which would leave depth at 2. + */ + disable_irq(dockchannel->tx_irq); + if (try_wait_for_completion(&dockchannel->tx_comp)) { + /* Handler ran, depth is 2. Restore to 1 (disabled). */ + enable_irq(dockchannel->tx_irq); + } else { + /* Genuine timeout. Depth is 1. */ + mutex_unlock(&dockchannel->lock); + return -ETIMEDOUT; + } + } + + continue; + } + + while (block >= 4) { + writel_relaxed(get_unaligned_le32(p), dockchannel->data_base + DATA_TX32); + p += 4; + left -= 4; + block -= 4; + } + while (block > 0) { + writeb_relaxed(*p++, dockchannel->data_base + DATA_TX8); + left--; + block--; + } + } + + mutex_unlock(&dockchannel->lock); + return count; +} +EXPORT_SYMBOL_GPL(dockchannel_send); + +/** + * dockchannel_recv - Receive data via DockChannel + * @dockchannel: The dockchannel instance + * @buf: Buffer to receive data into + * @count: Number of bytes to receive + * + * This function blocks until the requested number of bytes are read. + * + * Return: Number of bytes received or negative error code. + */ +int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count) +{ + size_t left = count; + u8 *p = buf; + + mutex_lock(&dockchannel->lock); + + while (left > 0) { + /* Relaxed is safe here as there is no DMA, as explained at top of file */ + size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT); + size_t block = min(left, avail); + + if (avail == 0) { + size_t threshold = min_t(size_t, DOCKCHANNEL_FIFO_SIZE / 2, left); + + writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH); + reinit_completion(&dockchannel->rx_comp); + enable_irq(dockchannel->rx_irq); + + unsigned long timeout; + + timeout = msecs_to_jiffies(DOCKCHANNEL_RX_TIMEOUT_MS); + if (!wait_for_completion_timeout(&dockchannel->rx_comp, + timeout)) { + disable_irq(dockchannel->rx_irq); + if (try_wait_for_completion(&dockchannel->rx_comp)) { + /* Handler ran, depth 2 -> 1 */ + enable_irq(dockchannel->rx_irq); + } else { + mutex_unlock(&dockchannel->lock); + return -ETIMEDOUT; + } + } + + continue; + } + + while (block >= 4) { + put_unaligned_le32(readl_relaxed(dockchannel->data_base + DATA_RX32), p); + p += 4; + left -= 4; + block -= 4; + } + while (block > 0) { + /* + * From testing, the hardware specifically puts the byte + * in the second byte of a 32-bit word, so the shift is + * required. + */ + *p++ = readl_relaxed(dockchannel->data_base + DATA_RX8) >> 8; + left--; + block--; + } + } + + mutex_unlock(&dockchannel->lock); + return count; +} +EXPORT_SYMBOL_GPL(dockchannel_recv); + +/** + * dockchannel_await - Register a callback for incoming data + * @dockchannel: The dockchannel instance + * @callback: Function to call when data is available + * @cookie: Context pointer passed to the callback + * @count: Threshold of bytes to trigger the callback + * + * This sets up an asynchronous notification. When data is available, + * the callback is invoked from a threaded IRQ context. + * + * Return: Threshold value set, or 0 if disabled. + */ +int dockchannel_await(struct dockchannel *dockchannel, + void (*callback)(void *cookie, size_t avail), + void *cookie, size_t count) +{ + size_t threshold = min_t(size_t, DOCKCHANNEL_FIFO_SIZE, count); + + mutex_lock(&dockchannel->lock); + + if (!count) { + dockchannel->awaiting = false; + disable_irq(dockchannel->rx_irq); + mutex_unlock(&dockchannel->lock); + return 0; + } + + dockchannel->data_available = callback; + dockchannel->cookie = cookie; + dockchannel->awaiting = true; + + /* Relaxed is safe here as there is no DMA, as explained at top of file */ + writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH); + + enable_irq(dockchannel->rx_irq); + + mutex_unlock(&dockchannel->lock); + return threshold; +} +EXPORT_SYMBOL_GPL(dockchannel_await); + +/** + * dockchannel_init - Initialize a dockchannel interface + * @pdev: The platform device of the child (consumer) + * + * Initializes the I/O, IRQs and locks for a dockchannel interface. + * + * Return: Pointer to initialized dockchannel struct or ERR_PTR. + */ +struct dockchannel *dockchannel_init(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dockchannel *dockchannel; + int ret; + + dockchannel = devm_kzalloc(dev, sizeof(*dockchannel), GFP_KERNEL); + if (!dockchannel) + return ERR_PTR(-ENOMEM); + + dockchannel->dev = dev; + mutex_init(&dockchannel->lock); + + dockchannel->config_base = devm_platform_ioremap_resource_byname(pdev, "config"); + if (IS_ERR(dockchannel->config_base)) + return ERR_CAST(dockchannel->config_base); + + dockchannel->data_base = devm_platform_ioremap_resource_byname(pdev, "data"); + if (IS_ERR(dockchannel->data_base)) + return ERR_CAST(dockchannel->data_base); + + init_completion(&dockchannel->tx_comp); + init_completion(&dockchannel->rx_comp); + + dockchannel->tx_irq = platform_get_irq_byname(pdev, "tx"); + if (dockchannel->tx_irq <= 0) + return ERR_PTR(dev_err_probe(dev, dockchannel->tx_irq, "Failed to get TX IRQ")); + + dockchannel->rx_irq = platform_get_irq_byname(pdev, "rx"); + if (dockchannel->rx_irq <= 0) + return ERR_PTR(dev_err_probe(dev, dockchannel->rx_irq, "Failed to get RX IRQ")); + + ret = devm_request_irq(dev, dockchannel->tx_irq, dockchannel_tx_irq, IRQF_NO_AUTOEN, + "apple-dockchannel-tx", dockchannel); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, "Failed to request TX IRQ")); + + ret = devm_request_threaded_irq(dev, dockchannel->rx_irq, dockchannel_rx_irq, + dockchannel_rx_irq_thread, IRQF_NO_AUTOEN, + "apple-dockchannel-rx", dockchannel); + if (ret) + return ERR_PTR(dev_err_probe(dev, ret, "Failed to request RX IRQ")); + + return dockchannel; +} +EXPORT_SYMBOL_GPL(dockchannel_init); + +/* Dockchannel IRQchip */ + +static void dockchannel_irq(struct irq_desc *desc) +{ + unsigned int irq = irq_desc_get_irq(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + struct dockchannel_common *dcc = irq_get_handler_data(irq); + + /* Relaxed is safe here as there is no DMA, as explained at top of file */ + unsigned long flags = readl_relaxed(dcc->irq_base + IRQ_FLAG); + + int bit; + + chained_irq_enter(chip, desc); + + for_each_set_bit(bit, &flags, DOCKCHANNEL_MAX_IRQ) + generic_handle_domain_irq(dcc->domain, bit); + + chained_irq_exit(chip, desc); +} + +static void dockchannel_irq_ack(struct irq_data *data) +{ + struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data); + unsigned int hwirq = data->hwirq; + + /* Relaxed is safe here as there is no DMA, as explained at top of file */ + writel_relaxed(BIT(hwirq), dcc->irq_base + IRQ_FLAG); +} + +static void dockchannel_irq_mask(struct irq_data *data) +{ + struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data); + unsigned int hwirq = data->hwirq; + unsigned long flags; + u32 val; + + raw_spin_lock_irqsave(&dcc->lock, flags); + + /* Relaxed is safe here as there is no DMA, as explained at top of file */ + val = readl_relaxed(dcc->irq_base + IRQ_MASK); + writel_relaxed(val & ~BIT(hwirq), dcc->irq_base + IRQ_MASK); + + raw_spin_unlock_irqrestore(&dcc->lock, flags); +} + +static void dockchannel_irq_unmask(struct irq_data *data) +{ + struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data); + unsigned int hwirq = data->hwirq; + unsigned long flags; + u32 val; + + raw_spin_lock_irqsave(&dcc->lock, flags); + + /* Relaxed is safe here as there is no DMA, as explained at top of file */ + val = readl_relaxed(dcc->irq_base + IRQ_MASK); + writel_relaxed(val | BIT(hwirq), dcc->irq_base + IRQ_MASK); + + raw_spin_unlock_irqrestore(&dcc->lock, flags); +} + +static const struct irq_chip dockchannel_irqchip = { + .name = "dockchannel-irqc", + .irq_ack = dockchannel_irq_ack, + .irq_mask = dockchannel_irq_mask, + .irq_unmask = dockchannel_irq_unmask, +}; + +static int dockchannel_irq_domain_map(struct irq_domain *d, unsigned int virq, + irq_hw_number_t hw) +{ + irq_set_chip_data(virq, d->host_data); + irq_set_chip_and_handler(virq, &dockchannel_irqchip, handle_level_irq); + + return 0; +} + +static const struct irq_domain_ops dockchannel_irq_domain_ops = { + .xlate = irq_domain_xlate_twocell, + .map = dockchannel_irq_domain_map, +}; + +static int dockchannel_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct dockchannel_common *dcc; + + dcc = devm_kzalloc(dev, sizeof(*dcc), GFP_KERNEL); + if (!dcc) + return -ENOMEM; + + dcc->dev = dev; + raw_spin_lock_init(&dcc->lock); + platform_set_drvdata(pdev, dcc); + + dcc->irq_base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(dcc->irq_base)) + return PTR_ERR(dcc->irq_base); + + /* Relaxed is safe here as there is no DMA, as explained at top of file */ + writel_relaxed(0, dcc->irq_base + IRQ_MASK); + writel_relaxed(~0, dcc->irq_base + IRQ_FLAG); + + dcc->domain = irq_domain_add_linear(dev->of_node, DOCKCHANNEL_MAX_IRQ, + &dockchannel_irq_domain_ops, dcc); + if (!dcc->domain) + return -ENOMEM; + + dcc->irq = platform_get_irq(pdev, 0); + if (dcc->irq <= 0) { + irq_domain_remove(dcc->domain); + return dev_err_probe(dev, dcc->irq, "Failed to get IRQ"); + } + + irq_set_handler_data(dcc->irq, dcc); + irq_set_chained_handler(dcc->irq, dockchannel_irq); + + return devm_of_platform_populate(dev); +} + +static void dockchannel_remove(struct platform_device *pdev) +{ + struct dockchannel_common *dcc = platform_get_drvdata(pdev); + int hwirq; + + /* + * Children are automatically removed by devm_of_platform_populate + * mechanism before this function is called. + */ + + irq_set_chained_handler_and_data(dcc->irq, NULL, NULL); + + for (hwirq = 0; hwirq < DOCKCHANNEL_MAX_IRQ; hwirq++) + irq_dispose_mapping(irq_find_mapping(dcc->domain, hwirq)); + + irq_domain_remove(dcc->domain); + + /* Relaxed is safe here as there is no DMA, as explained at top of file */ + writel_relaxed(0, dcc->irq_base + IRQ_MASK); + writel_relaxed(~0, dcc->irq_base + IRQ_FLAG); +} + +static const struct of_device_id dockchannel_of_match[] = { + { .compatible = "apple,t8112-dockchannel" }, + {}, +}; +MODULE_DEVICE_TABLE(of, dockchannel_of_match); + +static struct platform_driver dockchannel_driver = { + .driver = { + .name = "dockchannel", + .of_match_table = dockchannel_of_match, + }, + .probe = dockchannel_probe, + .remove = dockchannel_remove, +}; +module_platform_driver(dockchannel_driver); + +MODULE_AUTHOR("Hector Martin "); +MODULE_AUTHOR("Michael Reeves "); +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_DESCRIPTION("Apple DockChannel driver"); diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c index b8d4da147d23f7..d833f847c7d437 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); diff --git a/include/linux/soc/apple/dockchannel.h b/include/linux/soc/apple/dockchannel.h new file mode 100644 index 00000000000000..63eeae53be6b7d --- /dev/null +++ b/include/linux/soc/apple/dockchannel.h @@ -0,0 +1,53 @@ +/* SPDX-License-Identifier: GPL-2.0-only OR MIT */ +/* + * Apple DockChannel definitions + * Copyright (C) The Asahi Linux Contributors + */ + +#ifndef _LINUX_SOC_APPLE_DOCKCHANNEL_H_ +#define _LINUX_SOC_APPLE_DOCKCHANNEL_H_ + +#include +#include +#include + +struct dockchannel; + +#if IS_ENABLED(CONFIG_APPLE_DOCKCHANNEL) + +struct dockchannel *dockchannel_init(struct platform_device *pdev); + +int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count); +int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count); +int dockchannel_await(struct dockchannel *dockchannel, + void (*callback)(void *cookie, size_t avail), + void *cookie, size_t count); + +#else + +static inline struct dockchannel *dockchannel_init(struct platform_device *pdev) +{ + return ERR_PTR(-ENODEV); +} + +static inline int dockchannel_send(struct dockchannel *dockchannel, + const void *buf, size_t count) +{ + return -ENODEV; +} + +static inline int dockchannel_recv(struct dockchannel *dockchannel, + void *buf, size_t count) +{ + return -ENODEV; +} + +static inline int dockchannel_await(struct dockchannel *dockchannel, + void (*callback)(void *cookie, size_t avail), + void *cookie, size_t count) +{ + return -ENODEV; +} + +#endif +#endif