From 5b508f9e9ce95d7a98edfefc34b30217a428b366 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Sun, 12 Jul 2026 00:48:56 +0200 Subject: [PATCH 01/10] power: supply: macsmc: Support macOS 27 SMC firmware The SMC firmware included in macOS 27 changed the size of BCF0 key from 4 to 1 bytes. This key is used for indicating that battery state is critically low. In addition, B0RM key has changed endianness. Reviewed-by: Sven Peter Reviewed-by: Joshua Peisach Reviewed-by: Janne Grunau Cc: stable@vger.kernel.org Fixes: 0ebf821cf6c7 ("power: supply: Add macsmc-power driver for Apple Silicon") Signed-off-by: Sasha Finkelstein Link: https://patch.msgid.link/20260712-gate-power-v4-1-aa59c6583247@chaosmail.tech Signed-off-by: Sebastian Reichel --- drivers/power/supply/macsmc-power.c | 52 +++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/drivers/power/supply/macsmc-power.c b/drivers/power/supply/macsmc-power.c index 33ca07460f3a31..d14782352f9c60 100644 --- a/drivers/power/supply/macsmc-power.c +++ b/drivers/power/supply/macsmc-power.c @@ -86,6 +86,11 @@ struct macsmc_power { bool has_ch0i; /* Force discharge (Older firmware) */ bool has_ch0c; /* Inhibit charge (Older firmware) */ bool has_chte; /* Inhibit charge (Modern firmware) */ + /* + * Battery critical key is 1 byte and charge key is little endian + * (Modern firmware) + */ + bool fw_ge_27; u8 num_cells; int nominal_voltage_mv; @@ -273,6 +278,20 @@ static int macsmc_battery_get_date(const char *s, int *out) return 0; } +static int macsmc_battery_read_bcf0(struct macsmc_power *power, u32 *val) +{ + u8 tval = 0; + int ret; + + if (power->fw_ge_27) { + ret = apple_smc_read_u8(power->smc, SMC_KEY(BCF0), &tval); + *val = tval; + return ret; + } + + return apple_smc_read_u32(power->smc, SMC_KEY(BCF0), val); +} + static int macsmc_battery_get_capacity_level(struct macsmc_power *power) { bool flag; @@ -280,7 +299,7 @@ static int macsmc_battery_get_capacity_level(struct macsmc_power *power) int ret; /* Check for emergency shutdown condition */ - if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val) >= 0 && val) + if (macsmc_battery_read_bcf0(power, &val) >= 0 && val) return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; /* Check AC status for whether we could boot in this state */ @@ -303,6 +322,12 @@ static int macsmc_battery_get_capacity_level(struct macsmc_power *power) return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; } +static s16 macsmc_swap_b0rm(struct macsmc_power *power, s16 b0rm) +{ + /* B0RM was Big Endian, likely pass through from TI gas gauge */ + return power->fw_ge_27 ? b0rm : (s16)swab16(b0rm); +} + static int macsmc_battery_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -397,8 +422,7 @@ static int macsmc_battery_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_CHARGE_NOW: ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16); - /* B0RM is Big Endian, likely pass through from TI gas gauge */ - val->intval = (s16)swab16(vu16) * 1000; + val->intval = macsmc_swap_b0rm(power, vu16) * 1000; break; case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16); @@ -410,8 +434,7 @@ static int macsmc_battery_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_ENERGY_NOW: ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16); - /* B0RM is Big Endian, likely pass through from TI gas gauge */ - val->intval = (s16)swab16(vu16) * power->nominal_voltage_mv; + val->intval = macsmc_swap_b0rm(power, vu16) * power->nominal_voltage_mv; break; case POWER_SUPPLY_PROP_TEMP: ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16); @@ -577,7 +600,7 @@ static void macsmc_power_critical_work(struct work_struct *wrk) * Check if SMC flagged the battery as empty. * We trigger a graceful shutdown to let the OS save data. */ - if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &bcf0) == 0 && bcf0 != 0) { + if (macsmc_battery_read_bcf0(power, &bcf0) == 0 && bcf0 != 0) { power->orderly_shutdown_triggered = true; dev_crit(power->dev, "Battery critical (empty flag set). Triggering orderly shutdown.\n"); orderly_poweroff(true); @@ -616,6 +639,7 @@ static int macsmc_power_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); struct power_supply_config psy_cfg = {}; + struct apple_smc_key_info info; struct macsmc_power *power; bool has_battery = false; bool has_ac_adapter = false; @@ -714,6 +738,20 @@ static int macsmc_power_probe(struct platform_device *pdev) if (apple_smc_key_exists(smc, SMC_KEY(CH0I))) power->has_ch0i = true; + ret = apple_smc_get_key_info(power->smc, SMC_KEY(BCF0), &info); + if (ret) { + dev_err(&pdev->dev, "Failed to determine BCF0 key size\n"); + return ret; + } + if (info.size == 1) + power->fw_ge_27 = true; + else if (info.size == 4) + power->fw_ge_27 = false; + else { + dev_err(&pdev->dev, "Unexpected BCF0 key size %d\n", info.size); + return -EIO; + } + /* Reset "Optimised Battery Charging" flags to default state */ if (power->has_chte) apple_smc_write_u32(smc, SMC_KEY(CHTE), 0); @@ -766,7 +804,7 @@ static int macsmc_power_probe(struct platform_device *pdev) power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells; /* Enable critical shutdown notifications by reading status once */ - apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val32); + macsmc_battery_read_bcf0(power, &val32); psy_cfg.drv_data = power; power->batt = devm_power_supply_register(dev, &power->batt_desc, &psy_cfg); From 3cbb0f3b91e13edd1a46e09261837ae0384cb3f3 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Tue, 7 Apr 2026 13:33:46 +0200 Subject: [PATCH 02/10] Bluetooth: Add Broadcom channel priority commands Certain Broadcom bluetooth chips (bcm4377/bcm4378/bcm438) need ACL streams carrying audio to be set as "high priority" using a vendor specific command to prevent 10-ish second-long dropouts whenever something does a device scan. This patch sends the command when the socket priority is set to TC_PRIO_INTERACTIVE, as BlueZ does for audio. Signed-off-by: Sasha Finkelstein --- MAINTAINERS | 2 ++ drivers/bluetooth/hci_bcm4377.c | 2 ++ include/net/bluetooth/bluetooth.h | 4 ++++ include/net/bluetooth/hci_core.h | 11 +++++++++++ net/bluetooth/Kconfig | 7 +++++++ net/bluetooth/Makefile | 1 + net/bluetooth/brcm.c | 29 +++++++++++++++++++++++++++++ net/bluetooth/brcm.h | 17 +++++++++++++++++ net/bluetooth/hci_conn.c | 27 +++++++++++++++++++++++++++ net/bluetooth/l2cap_sock.c | 13 +++++++++++++ 10 files changed, 113 insertions(+) create mode 100644 net/bluetooth/brcm.c create mode 100644 net/bluetooth/brcm.h diff --git a/MAINTAINERS b/MAINTAINERS index 13241dc0aecafe..f20aebb9bf2d5a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2595,6 +2595,8 @@ F: include/dt-bindings/pinctrl/apple.h F: include/linux/mfd/macsmc.h F: include/linux/soc/apple/* F: include/uapi/drm/asahi_drm.h +F: net/bluetooth/brcm.c +F: net/bluetooth/brcm.h ARM/ARTPEC MACHINE SUPPORT M: Jesper Nilsson diff --git a/drivers/bluetooth/hci_bcm4377.c b/drivers/bluetooth/hci_bcm4377.c index 925d0a6359453e..5f79920c030681 100644 --- a/drivers/bluetooth/hci_bcm4377.c +++ b/drivers/bluetooth/hci_bcm4377.c @@ -2397,6 +2397,8 @@ static int bcm4377_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (bcm4377->hw->broken_le_ext_adv_report_phy) hci_set_quirk(hdev, HCI_QUIRK_FIXUP_LE_EXT_ADV_REPORT_PHY); + hci_set_brcm_capable(hdev); + pci_set_drvdata(pdev, bcm4377); hci_set_drvdata(hdev, bcm4377); SET_HCIDEV_DEV(hdev, &pdev->dev); diff --git a/include/net/bluetooth/bluetooth.h b/include/net/bluetooth/bluetooth.h index 3faea66b19799a..5d82944370e427 100644 --- a/include/net/bluetooth/bluetooth.h +++ b/include/net/bluetooth/bluetooth.h @@ -458,6 +458,7 @@ struct l2cap_ctrl { }; struct hci_dev; +struct hci_conn; typedef void (*hci_req_complete_t)(struct hci_dev *hdev, u8 status, u16 opcode); typedef void (*hci_req_complete_skb_t)(struct hci_dev *hdev, u8 status, @@ -470,6 +471,9 @@ void hci_req_cmd_complete(struct hci_dev *hdev, u16 opcode, u8 status, int hci_ethtool_ts_info(unsigned int index, int sk_proto, struct kernel_ethtool_ts_info *ts_info); +int hci_conn_setsockopt(struct hci_conn *conn, struct sock *sk, int level, + int optname, sockptr_t optval, unsigned int optlen); + #define HCI_REQ_START BIT(0) #define HCI_REQ_SKB BIT(1) diff --git a/include/net/bluetooth/hci_core.h b/include/net/bluetooth/hci_core.h index 02ba1cba6b236c..6d5053cacceb7c 100644 --- a/include/net/bluetooth/hci_core.h +++ b/include/net/bluetooth/hci_core.h @@ -642,6 +642,10 @@ struct hci_dev { bool aosp_quality_report; #endif +#if IS_ENABLED(CONFIG_BT_BRCMEXT) + bool brcm_capable; +#endif + int (*open)(struct hci_dev *hdev); int (*close)(struct hci_dev *hdev); int (*flush)(struct hci_dev *hdev); @@ -1792,6 +1796,13 @@ static inline void hci_set_aosp_capable(struct hci_dev *hdev) #endif } +static inline void hci_set_brcm_capable(struct hci_dev *hdev) +{ +#if IS_ENABLED(CONFIG_BT_BRCMEXT) + hdev->brcm_capable = true; +#endif +} + static inline void hci_devcd_setup(struct hci_dev *hdev) { #ifdef CONFIG_DEV_COREDUMP diff --git a/net/bluetooth/Kconfig b/net/bluetooth/Kconfig index ee6457d1a5eed3..b611942c7b8ffd 100644 --- a/net/bluetooth/Kconfig +++ b/net/bluetooth/Kconfig @@ -107,6 +107,13 @@ config BT_AOSPEXT This options enables support for the Android Open Source Project defined HCI vendor extensions. +config BT_BRCMEXT + bool "Enable Broadcom extensions" + depends on BT + help + This option enables support for the Broadcom defined HCI + vendor extensions. + config BT_DEBUGFS bool "Export Bluetooth internals in debugfs" depends on BT && DEBUG_FS diff --git a/net/bluetooth/Makefile b/net/bluetooth/Makefile index 41049b280887db..d402645dfb7da0 100644 --- a/net/bluetooth/Makefile +++ b/net/bluetooth/Makefile @@ -23,5 +23,6 @@ bluetooth-$(CONFIG_BT_LE) += iso.o bluetooth-$(CONFIG_BT_LEDS) += leds.o bluetooth-$(CONFIG_BT_MSFTEXT) += msft.o bluetooth-$(CONFIG_BT_AOSPEXT) += aosp.o +bluetooth-$(CONFIG_BT_BRCMEXT) += brcm.o bluetooth-$(CONFIG_BT_DEBUGFS) += hci_debugfs.o bluetooth-$(CONFIG_BT_SELFTEST) += selftest.o diff --git a/net/bluetooth/brcm.c b/net/bluetooth/brcm.c new file mode 100644 index 00000000000000..9aa0a265ab3d6b --- /dev/null +++ b/net/bluetooth/brcm.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2026 The Asahi Linux Contributors + */ + +#include +#include + +#include "brcm.h" + +int brcm_set_high_priority(struct hci_dev *hdev, u16 handle, bool enable) +{ + struct sk_buff *skb; + u8 cmd[3]; + + if (!hdev->brcm_capable) + return 0; + + cmd[0] = handle; + cmd[1] = handle >> 8; + cmd[2] = !!enable; + + skb = hci_cmd_sync(hdev, 0xfc57, sizeof(cmd), cmd, HCI_CMD_TIMEOUT); + if (IS_ERR(skb)) + return PTR_ERR(skb); + + kfree_skb(skb); + return 0; +} diff --git a/net/bluetooth/brcm.h b/net/bluetooth/brcm.h new file mode 100644 index 00000000000000..fdaee63bd1d23c --- /dev/null +++ b/net/bluetooth/brcm.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2026 The Asahi Linux Contributors + */ + +#if IS_ENABLED(CONFIG_BT_BRCMEXT) + +int brcm_set_high_priority(struct hci_dev *hdev, u16 handle, bool enable); + +#else + +static inline int brcm_set_high_priority(struct hci_dev *hdev, u16 handle, bool enable) +{ + return 0; +} + +#endif diff --git a/net/bluetooth/hci_conn.c b/net/bluetooth/hci_conn.c index eba4a548bef52a..f481c944061add 100644 --- a/net/bluetooth/hci_conn.c +++ b/net/bluetooth/hci_conn.c @@ -35,6 +35,7 @@ #include #include +#include "brcm.h" #include "smp.h" #include "eir.h" @@ -3156,6 +3157,32 @@ int hci_conn_set_phy(struct hci_conn *conn, u32 phys) } } +int hci_conn_setsockopt(struct hci_conn *conn, struct sock *sk, int level, + int optname, sockptr_t optval, unsigned int optlen) { + int val; + bool old_high, new_high, changed; + + if (level != SOL_SOCKET) + return 0; + + if (optname != SO_PRIORITY) + return 0; + + if (optlen < sizeof(int)) + return -EINVAL; + + if (copy_from_sockptr(&val, optval, sizeof(val))) + return -EFAULT; + + old_high = sk->sk_priority >= TC_PRIO_INTERACTIVE; + new_high = val >= TC_PRIO_INTERACTIVE; + changed = old_high != new_high; + if (!changed) + return 0; + + return brcm_set_high_priority(conn->hdev, conn->handle, new_high); +} + static int abort_conn_sync(struct hci_dev *hdev, void *data) { struct hci_conn *conn = data; diff --git a/net/bluetooth/l2cap_sock.c b/net/bluetooth/l2cap_sock.c index d8cbe278443779..0c86be41622e2b 100644 --- a/net/bluetooth/l2cap_sock.c +++ b/net/bluetooth/l2cap_sock.c @@ -897,6 +897,16 @@ static int l2cap_sock_setsockopt(struct socket *sock, int level, int optname, BT_DBG("sk %p", sk); + if (level == SOL_SOCKET) { + conn = chan->conn; + if (conn) + err = hci_conn_setsockopt(conn->hcon, sock->sk, level, + optname, optval, optlen); + if (err) + return err; + return sock_setsockopt(sock, level, optname, optval, optlen); + } + if (level == SOL_L2CAP) return l2cap_sock_setsockopt_old(sock, optname, optval, optlen); @@ -1976,6 +1986,9 @@ static struct sock *l2cap_sock_alloc(struct net *net, struct socket *sock, INIT_LIST_HEAD(&l2cap_pi(sk)->rx_busy); + if (sock) + set_bit(SOCK_CUSTOM_SOCKOPT, &sock->flags); + /* The sock takes ownership of the caller's reference on chan. */ l2cap_pi(sk)->chan = chan; From 8e8e147083e614caf006046c3280d5a3f38a56d7 Mon Sep 17 00:00:00 2001 From: Sasha Finkelstein Date: Thu, 21 May 2026 10:30:50 +0200 Subject: [PATCH 03/10] Fail the build on RUST=y and RUST_IS_AVAILABLE=n The current approach of silently disabling all rust drivers if the toolchain is missing results in users that try to compile their own kernels getting a "successful" build and then being confused about where did their drivers go. In comparison, missing openssl results in a build failure, not a disappearance of everything that depends on it. This also means that allyesconfig will depend on rust, but since the rust experiment concluded with "rust is here to stay", i believe that allyesconfig should be building rust drivers too. Signed-off-by: Sasha Finkelstein --- Documentation/rust/quick-start.rst | 6 +++--- init/Kconfig | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Documentation/rust/quick-start.rst b/Documentation/rust/quick-start.rst index a6ec3fa94d3307..764c81d0dd5926 100644 --- a/Documentation/rust/quick-start.rst +++ b/Documentation/rust/quick-start.rst @@ -321,9 +321,9 @@ Configuration ------------- ``Rust support`` (``CONFIG_RUST``) needs to be enabled in the ``General setup`` -menu. The option is only shown if a suitable Rust toolchain is found (see -above), as long as the other requirements are met. In turn, this will make -visible the rest of options that depend on Rust. +menu. In turn, this will make visible the rest of options that depend on Rust. +You can check the value of ``RUST_IS_AVAILABLE`` to determine if your toolchain +is configured correctly. Afterwards, go to:: diff --git a/init/Kconfig b/init/Kconfig index d78a6e616311e8..dbc6d709068f42 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -2193,7 +2193,6 @@ config PROFILING config RUST bool "Rust support" depends on HAVE_RUST - depends on RUST_IS_AVAILABLE select EXTENDED_MODVERSIONS if MODVERSIONS depends on !MODVERSIONS || GENDWARFKSYMS depends on !GCC_PLUGIN_RANDSTRUCT From 72109ac553ef4b82251919e83b4cd78e2e15ebbe Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 9 May 2026 11:58:52 +0200 Subject: [PATCH 04/10] driver-core: Add error message to device_links_missing_supplier WARN() Signed-off-by: Janne Grunau --- drivers/base/core.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/base/core.c b/drivers/base/core.c index 3ee92a5b88c3a0..a9df82e5f6b5d9 100644 --- a/drivers/base/core.c +++ b/drivers/base/core.c @@ -1076,6 +1076,7 @@ static void device_links_missing_supplier(struct device *dev) if (link->supplier->links.status == DL_DEV_DRIVER_BOUND) { WRITE_ONCE(link->status, DL_STATE_AVAILABLE); } else { + dev_err(dev, "devices misses supplier %s\n", dev_name(link->supplier)); WARN_ON(!device_link_test(link, DL_FLAG_SYNC_STATE_ONLY)); WRITE_ONCE(link->status, DL_STATE_DORMANT); } From 2eb5a98950410bbed64bbfac7d468f070c208319 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 30 May 2026 12:16:44 +0200 Subject: [PATCH 05/10] dt-bindings: gpio: apple,smc: Add compatible for 'gp00' keys Apple M3 Pro and Max devices are using 'gp00' keys for GPIO in addition to 'gP00' keys. Add a second compatible to handle this keys with an additional macsmc-gpio instance. Signed-off-by: Janne Grunau --- Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml b/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml index 42b1bc0a10c97a..b4063a9dd1248c 100644 --- a/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml +++ b/Documentation/devicetree/bindings/gpio/apple,smc-gpio.yaml @@ -14,7 +14,9 @@ description: properties: compatible: - const: apple,smc-gpio + enum: + - apple,smc-gpio + - apple,smc-low-gpio gpio-controller: true From 49e8c8826f42e088a935ecccf56c02e56389cdcd Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Fri, 29 May 2026 20:54:16 +0200 Subject: [PATCH 06/10] gpio: gpio-macsmc: Support 'gp00' GPIO keys Add support for SMC GPIO keys with a lower letter 'p' via the "apple,smc-low-gpio" compatible. This adds support for a second macsmc-gpio controller using 'gp00' keys. These keys are used on Apple M3 Pro and Max MacBooks in the controller for keyboard and trackpad and for the built-in DisplayPort to HDMI converter. Signed-off-by: Janne Grunau --- drivers/gpio/gpio-macsmc.c | 45 +++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/drivers/gpio/gpio-macsmc.c b/drivers/gpio/gpio-macsmc.c index b0952d066a9dd0..c3ca445a85ac9d 100644 --- a/drivers/gpio/gpio-macsmc.c +++ b/drivers/gpio/gpio-macsmc.c @@ -75,6 +75,7 @@ struct macsmc_gpio { struct gpio_chip gc; int first_index; + smc_key base_key; }; static int macsmc_gpio_nr(smc_key key) @@ -88,15 +89,15 @@ static int macsmc_gpio_nr(smc_key key) return low | (high << 4); } -static int macsmc_gpio_key(unsigned int offset) +static int macsmc_gpio_key(smc_key base_key, unsigned int offset) { - return _SMC_KEY("gP\0\0") | hex_asc_hi(offset) << 8 | hex_asc_lo(offset); + return base_key | hex_asc_hi(offset) << 8 | hex_asc_lo(offset); } static int macsmc_gpio_find_first_gpio_index(struct macsmc_gpio *smcgp) { struct apple_smc *smc = smcgp->smc; - smc_key key = macsmc_gpio_key(0); + smc_key key = macsmc_gpio_key(smcgp->base_key, 0); smc_key first_key, last_key; int start, count, ret; @@ -143,7 +144,7 @@ static int macsmc_gpio_find_first_gpio_index(struct macsmc_gpio *smcgp) static int macsmc_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) { struct macsmc_gpio *smcgp = gpiochip_get_data(gc); - smc_key key = macsmc_gpio_key(offset); + smc_key key = macsmc_gpio_key(smcgp->base_key, offset); u32 val; int ret; @@ -163,7 +164,7 @@ static int macsmc_gpio_get_direction(struct gpio_chip *gc, unsigned int offset) static int macsmc_gpio_get(struct gpio_chip *gc, unsigned int offset) { struct macsmc_gpio *smcgp = gpiochip_get_data(gc); - smc_key key = macsmc_gpio_key(offset); + smc_key key = macsmc_gpio_key(smcgp->base_key, offset); u32 cmd, val; int ret; @@ -186,7 +187,7 @@ static int macsmc_gpio_get(struct gpio_chip *gc, unsigned int offset) static int macsmc_gpio_set(struct gpio_chip *gc, unsigned int offset, int value) { struct macsmc_gpio *smcgp = gpiochip_get_data(gc); - smc_key key = macsmc_gpio_key(offset); + smc_key key = macsmc_gpio_key(smcgp->base_key, offset); int ret; value |= CMD_OUTPUT; @@ -217,7 +218,7 @@ static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc, if (ret < 0) return ret; - if (key > SMC_KEY(gPff)) + if (key > macsmc_gpio_key(smcgp->base_key, MAX_GPIO - 1)) break; gpio_nr = macsmc_gpio_nr(key); @@ -232,10 +233,15 @@ static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc, return 0; } +struct macsmc_gpio_of_match_data { + smc_key base_key; +}; + static int macsmc_gpio_probe(struct platform_device *pdev) { struct macsmc_gpio *smcgp; struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent); + const struct macsmc_gpio_of_match_data *data = of_device_get_match_data(&pdev->dev); smc_key key; int ret; @@ -245,6 +251,7 @@ static int macsmc_gpio_probe(struct platform_device *pdev) smcgp->dev = &pdev->dev; smcgp->smc = smc; + smcgp->base_key = data ? data->base_key : _SMC_KEY("gP\0\0"); smcgp->first_index = macsmc_gpio_find_first_gpio_index(smcgp); if (smcgp->first_index < 0) @@ -254,12 +261,15 @@ static int macsmc_gpio_probe(struct platform_device *pdev) if (ret < 0) return ret; - if (key > macsmc_gpio_key(MAX_GPIO - 1)) + if (key > macsmc_gpio_key(smcgp->base_key, MAX_GPIO - 1)) return -ENODEV; dev_info(smcgp->dev, "First GPIO key: %p4ch\n", &key); - smcgp->gc.label = "macsmc-pmu-gpio"; + if (device_is_compatible(&pdev->dev, "apple,smc-low-gpio")) + smcgp->gc.label = "macsmc-pmu-low-gpio"; + else + smcgp->gc.label = "macsmc-pmu-gpio"; smcgp->gc.owner = THIS_MODULE; smcgp->gc.get = macsmc_gpio_get; smcgp->gc.set = macsmc_gpio_set; @@ -273,8 +283,23 @@ static int macsmc_gpio_probe(struct platform_device *pdev) return devm_gpiochip_add_data(&pdev->dev, &smcgp->gc, smcgp); } +static const struct macsmc_gpio_of_match_data macsmc_gpio_up_data = { + .base_key = _SMC_KEY("gP\0\0"), +}; + +static const struct macsmc_gpio_of_match_data macsmc_gpio_low_data = { + .base_key = _SMC_KEY("gp\0\0"), +}; + static const struct of_device_id macsmc_gpio_of_table[] = { - { .compatible = "apple,smc-gpio", }, + { + .compatible = "apple,smc-gpio", + .data = &macsmc_gpio_up_data, + }, + { + .compatible = "apple,smc-low-gpio", + .data = &macsmc_gpio_low_data, + }, {} }; MODULE_DEVICE_TABLE(of, macsmc_gpio_of_table); From e2e1930a9595bffafad92cec2b5504525efb9cd4 Mon Sep 17 00:00:00 2001 From: Janne Grunau Date: Sat, 30 May 2026 12:20:39 +0200 Subject: [PATCH 07/10] mfd: macsmc: Add second gpio subdevice for 'gp00' keys Apple M3 Pro and Max devices are using 'gp00' keys for GPIO in addition to 'gP00' keys. These keys are handled by an additional macsmc-gpio instance using the "apple,smc-low-gpio" compatible. Signed-off-by: Janne Grunau --- drivers/mfd/macsmc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/mfd/macsmc.c b/drivers/mfd/macsmc.c index 514cba7dc897ae..31540f67ddbd8a 100644 --- a/drivers/mfd/macsmc.c +++ b/drivers/mfd/macsmc.c @@ -48,6 +48,7 @@ static const struct mfd_cell apple_smc_devs[] = { MFD_CELL_NAME("macsmc-input"), MFD_CELL_NAME("macsmc-power"), MFD_CELL_OF("macsmc-gpio", NULL, NULL, 0, 0, "apple,smc-gpio"), + MFD_CELL_OF("macsmc-low-gpio", NULL, NULL, 0, 0, "apple,smc-low-gpio"), MFD_CELL_OF("macsmc-hwmon", NULL, NULL, 0, 0, "apple,smc-hwmon"), MFD_CELL_OF("macsmc-reboot", NULL, NULL, 0, 0, "apple,smc-reboot"), MFD_CELL_OF("macsmc-rtc", NULL, NULL, 0, 0, "apple,smc-rtc"), From c9ff8adb1523d32a92ed4389d2fd105536c6f7fc Mon Sep 17 00:00:00 2001 From: Yicong Hui Date: Fri, 21 Aug 2026 20:18:49 +0200 Subject: [PATCH 08/10] HID: dockchannel-hid: forward correct report type on SET_REPORT dchid_set_report() always sends HID_OUTPUT_REPORT regardless of the report type requested by the caller. Inline it into its sole caller and forward rtype directly, so SET_REPORT works correctly for feature reports too, not just output reports, and to simplify function calls. Signed-off-by: Yicong Hui --- drivers/hid/dockchannel-hid/dockchannel-hid.c | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/drivers/hid/dockchannel-hid/dockchannel-hid.c b/drivers/hid/dockchannel-hid/dockchannel-hid.c index 9cab638b30c328..68f6625313ea32 100644 --- a/drivers/hid/dockchannel-hid/dockchannel-hid.c +++ b/drivers/hid/dockchannel-hid/dockchannel-hid.c @@ -593,12 +593,6 @@ static int dchid_get_report_cmd(struct dchid_iface *iface, u8 reportnum, void *b 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) @@ -610,7 +604,7 @@ static int dchid_raw_request(struct hid_device *hdev, 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); + return dchid_cmd(iface, rtype, REQ_SET_REPORT, buf, len, NULL, 0); default: return -EIO; } From 33d1f0c57d81e8a01d7d3917d30c691eb5e9a57e Mon Sep 17 00:00:00 2001 From: Yicong Hui Date: Fri, 21 Aug 2026 20:29:06 +0200 Subject: [PATCH 09/10] HID: apple-haptic: add driver for apple MTP haptic touchpad actuator Adds a driver to control the actuator of the MTP touchpad's actuator for M2 and later devices. The specific hardware commands and their fields have been reverse engineered on my own machine (Macbook Pro 2023 M2 Max) using the m1n1 hypervisor. Signed-off-by: Yicong Hui --- drivers/hid/Kconfig | 12 +++ drivers/hid/Makefile | 1 + drivers/hid/hid-apple-mtp-haptic.c | 117 +++++++++++++++++++++++++++++ include/linux/soc/apple/actuator.h | 19 +++++ 4 files changed, 149 insertions(+) create mode 100644 drivers/hid/hid-apple-mtp-haptic.c create mode 100644 include/linux/soc/apple/actuator.h diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 18244ecf301614..8d48546911aa98 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -146,6 +146,17 @@ config HID_APPLE Say Y here if you want support for keyboards of Apple iBooks, PowerBooks, MacBooks, MacBook Pros and Apple Aluminum. +config HID_APPLE_MTP_HAPTIC + tristate "Apple MTP Haptic Actuator support" + depends on HID + help + This driver enables support for userspace controlled haptics on Apple Silicon devices + using the MTP protocol (M2 and newer MacBooks). Say Y here if you have an M2 or + newer MacBook. + + This driver can also be built as a module. If so, it will be called hid-apple-mtp-haptic. + + config HID_APPLEIR tristate "Apple infrared receiver" depends on (USB_HID) @@ -739,6 +750,7 @@ config LOGIWHEELS_FF config HID_MAGICMOUSE tristate "Apple Magic Mouse/Trackpad multi-touch support" + depends on (HID && HID_APPLE_MTP_HAPTIC) || HID_APPLE_MTP_HAPTIC=n help Support for the Apple Magic Mouse/Trackpad multi-touch. diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index d2579e299b5e51..97f6bb600c0aed 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -29,6 +29,7 @@ obj-$(CONFIG_HID_ACCUTOUCH) += hid-accutouch.o obj-$(CONFIG_HID_ALPS) += hid-alps.o obj-$(CONFIG_HID_ACRUX) += hid-axff.o obj-$(CONFIG_HID_APPLE) += hid-apple.o +obj-$(CONFIG_HID_APPLE_MTP_HAPTIC) += hid-apple-mtp-haptic.o obj-$(CONFIG_HID_APPLEIR) += hid-appleir.o obj-$(CONFIG_HID_APPLETB_BL) += hid-appletb-bl.o obj-$(CONFIG_HID_APPLETB_KBD) += hid-appletb-kbd.o diff --git a/drivers/hid/hid-apple-mtp-haptic.c b/drivers/hid/hid-apple-mtp-haptic.c new file mode 100644 index 00000000000000..85606596304351 --- /dev/null +++ b/drivers/hid/hid-apple-mtp-haptic.c @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include +#include + +#include "hid-ids.h" + +int apple_taptic_send(struct hid_device *haptic_hdev, u16 effect_type, u8 strength, u8 softness) +{ + int ret; + u8 *msg; + int msg_length; + u8 event_click[] = {0x53, 0x01, strength, softness, 0x03, 0x02, 0x22, 0x48, 0x49, + 0x01, 0x04, 0x60, 0x17, 0x2D, 0x03, 0x02, 0x18, 0x32, 0x1E}; + u8 event_deep_click[] = {0x53, 0x01, strength, softness, 0x01, 0x07, 0x18, 0x18, 0x4F, + 0x02, 0x02, 0x60, 0x0C, 0x25}; + u8 event_release[] = {0x53, 0x01, strength, softness}; + + switch (effect_type) { + case HID_HP_WAVEFORMPRESS & HID_USAGE: + msg = event_click; + msg_length = sizeof(event_click); + break; + case APPLE_HP_WAVEFORMDEEPCLICK & HID_USAGE: + msg = event_deep_click; + msg_length = sizeof(event_deep_click); + break; + case HID_HP_WAVEFORMRELEASE & HID_USAGE: + msg = event_release; + msg_length = sizeof(event_release); + break; + default: + return -EINVAL; + } + + msg = kmemdup(msg, msg_length, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + ret = hid_hw_raw_request(haptic_hdev, msg[0], msg, msg_length, + HID_OUTPUT_REPORT, HID_REQ_SET_REPORT); + kfree(msg); + + if (ret < 0) + return ret; + return 0; +} +EXPORT_SYMBOL_GPL(apple_taptic_send); + +int apple_taptic_switch_modes(struct hid_device *haptic_hdev, bool enable_host_controlled) +{ + int ret; + u8 *buf; + u8 msg[] = { 0x21, enable_host_controlled }; + + buf = kmemdup(msg, sizeof(msg), GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = hid_hw_raw_request(haptic_hdev, buf[0], buf, sizeof(msg), + HID_FEATURE_REPORT, HID_REQ_SET_REPORT); + kfree(buf); + + if (ret < 0) + return ret; + return 0; +} +EXPORT_SYMBOL_GPL(apple_taptic_switch_modes); + +static int apple_actuator_probe(struct hid_device *haptic_hdev, const struct hid_device_id *id) +{ + int ret; + + ret = hid_parse(haptic_hdev); + if (ret) { + hid_err(haptic_hdev, "apple_haptic hid parse failed\n"); + return ret; + } + + ret = hid_hw_start(haptic_hdev, 0); + if (ret) { + hid_err(haptic_hdev, "apple_haptic hw start failed\n"); + return ret; + } + + return 0; +} + +static bool actuator_match(struct hid_device *hdev, bool ignore_special_drivers) +{ + return (strcmp(hdev->name, "Apple MTP actuator") == 0); +} + +static void actuator_remove(struct hid_device *haptic_hdev) +{ + apple_taptic_switch_modes(haptic_hdev, 0); + hid_hw_stop(haptic_hdev); +} + +static const struct hid_device_id apple_haptic_devices[] = { + { HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, + HID_ANY_ID), .driver_data = 0 }, + { } +}; +MODULE_DEVICE_TABLE(hid, apple_haptic_devices); + +static struct hid_driver apple_actuator_driver = { + .name = "hid-apple-mtp-haptic", + .id_table = apple_haptic_devices, + .probe = apple_actuator_probe, + .match = actuator_match, + .remove = actuator_remove +}; +module_hid_driver(apple_actuator_driver); + +MODULE_DESCRIPTION("Apple MTP Haptic Actuator driver"); +MODULE_LICENSE("GPL"); diff --git a/include/linux/soc/apple/actuator.h b/include/linux/soc/apple/actuator.h new file mode 100644 index 00000000000000..68a24a91d52868 --- /dev/null +++ b/include/linux/soc/apple/actuator.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef __APPLE_MTP_ACTUATOR_H__ +#define __APPLE_MTP_ACTUATOR_H__ +#include + +#define APPLE_HP_WAVEFORMDEEPCLICK 0x000e2001 + +#if IS_ENABLED(CONFIG_HID_APPLE_MTP_HAPTIC) +int apple_taptic_send(struct hid_device *hdev, u16 effect_type, u8 strength, u8 softness); +int apple_taptic_switch_modes(struct hid_device *hdev, bool currently_host_controlled); + +#else +static inline int apple_taptic_send(struct hid_device *hdev, u16 effect_type, + u8 strength, u8 softness) { return -ENODEV; } +static inline int apple_taptic_switch_modes(struct hid_device *hdev, + bool enable_host_controlled) { return -ENODEV; } +#endif +#endif /* __APPLE_MTP_ACTUATOR_H__ */ From 807128b8cd7fe646d3596e91e0d8b9a69008e53b Mon Sep 17 00:00:00 2001 From: Yicong Hui Date: Fri, 21 Aug 2026 21:23:49 +0200 Subject: [PATCH 10/10] HID: apple-haptic: add support for controlling MTP touchpad haptics Integrate the MTP actuator driver with the MTP trackpad driver to expose new functionality that allows userspace to control the haptic effects of the touchpad actuator as a force feedback device, following the convention for haptic touchpad functionality as specified in [1]. Set the INPUT_PROP_PRESSUREPAD property within the MTP trackpad device indicating that this device is a haptic touchpad, and set the FF_HAPTIC capability to indicate that force feedback haptic effects can be uploaded to the trackpad [2]. Userspace can thus choose to upload force feedback effects to the touchpad, which would trigger it to switch into "Host controlled" mode, where the touchpad will no longer click unless signalled to do so by userspace. This patch uses an unconventional means of doing so, by writing a separate loadable module for the haptic actuator. This is because the haptic actuator is considered a separate device on MTP trackpads, whereas haptics for HUTRR63 (And the other kernel drivers) considers the haptic actuator and the touchpad to be the very same device. Therefore, to conform to the standardized interface in [1] this patch creates a separate driver for the separate HID device, and is designed such that commands to the actuator are sent through the touchpad. This changeset is relevant because allowing userspace control of the haptics allows for user control of: 1. Click feedback intensity 2. Sensitivity of when clicks trigger 3. Configuring "Force Touch"-like deep click functionality [1] https://lore.kernel.org/all/20250818-support-forcepads-v3-0-e4f9ab0add84@google.com/T/#m12f9be6691015fb9b2e834248c368e5f90 [2] https://docs.kernel.org/input/event-codes.html#input-prop-pressurepad Signed-off-by: Yicong Hui --- drivers/hid/hid-magicmouse.c | 259 ++++++++++++++++++++++++++++++++++- 1 file changed, 258 insertions(+), 1 deletion(-) diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index a770ceccabde6b..859fe8ad92f229 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -17,6 +17,8 @@ #include #include #include +#include "hid-haptic.h" +#include #include "hid-ids.h" @@ -138,6 +140,14 @@ struct magicmouse_input_ops { int (*setup_input)(struct input_dev *input, struct hid_device *hdev); }; +struct effect_job { + struct work_struct work; + struct hid_device *taptic_hdev; + u16 effect_type; + u8 strength; + u8 softness; +}; + /** * struct magicmouse_sc - Tracks Magic Mouse-specific data. * @input: Input device through which we report events. @@ -175,6 +185,10 @@ struct magicmouse_sc { } touches[MAX_CONTACTS]; int tracking_ids[MAX_CONTACTS]; + struct hid_haptic_device *haptics; + struct hid_device *taptic_hdev; + struct effect_job *haptic_effects; + struct hid_device *hdev; struct delayed_work work; struct timer_list battery_timer; @@ -1042,6 +1056,7 @@ static int magicmouse_setup_input_mtp(struct input_dev *input, struct magicmouse_sc *msc = hid_get_drvdata(hdev); __set_bit(INPUT_PROP_BUTTONPAD, input->propbit); + __set_bit(INPUT_PROP_PRESSUREPAD, input->propbit); __clear_bit(BTN_0, input->keybit); __clear_bit(BTN_RIGHT, input->keybit); __clear_bit(BTN_MIDDLE, input->keybit); @@ -1049,7 +1064,7 @@ static int magicmouse_setup_input_mtp(struct input_dev *input, __clear_bit(REL_X, input->relbit); __clear_bit(REL_Y, input->relbit); - mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK; + mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK | INPUT_MT_TOTAL_FORCE; /* finger touch area */ input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 5000, 0, 0); @@ -1150,6 +1165,29 @@ static int magicmouse_input_mapping(struct hid_device *hdev, return 0; } +static int match_actuator(struct device *dev, const void *data) +{ + if (dev->bus != &hid_bus_type) + return 0; + + struct hid_device *hdev = to_hid_device(dev); + + return hdev && !strcmp(hdev->name, "Apple MTP actuator"); +} + +static int magicmouse_switch_mode(struct input_dev *trackpad_idev, int mode) +{ + struct hid_device *trackpad_hdev = input_get_drvdata(trackpad_idev); + struct magicmouse_sc *msc = hid_get_drvdata(trackpad_hdev); + int ret; + + hid_info(trackpad_hdev, "Switching modes to %i\n", mode); + + ret = apple_taptic_switch_modes(msc->taptic_hdev, mode); + + return ret; +} + static int magicmouse_input_configured(struct hid_device *hdev, struct hid_input *hi) @@ -1288,6 +1326,208 @@ static void magicmouse_battery_timer_tick(struct timer_list *t) } } +static int apple_upload_effects(struct input_dev *trackpad_idev, + struct ff_effect *effect, struct ff_effect *old) +{ + struct hid_device *trackpad_hdev = input_get_drvdata(trackpad_idev); + struct magicmouse_sc *msc = hid_get_drvdata(trackpad_hdev); + struct hid_haptic_device *haptics = msc->haptics; + int ret; + + switch (effect->u.haptic.hid_usage) { + case (HID_HP_WAVEFORMPRESS & HID_USAGE): + case (HID_HP_WAVEFORMRELEASE & HID_USAGE): + case (APPLE_HP_WAVEFORMDEEPCLICK & HID_USAGE): + break; + default: + return -EINVAL; + } + + msc->haptic_effects[effect->id].taptic_hdev = msc->taptic_hdev; + msc->haptic_effects[effect->id].effect_type = (effect->u.haptic.hid_usage) & HID_USAGE; + msc->haptic_effects[effect->id].strength = + (min(100, (effect->u.haptic.intensity)) * 255) / 100; + msc->haptic_effects[effect->id].softness = 0x90; + /* A future extension could add configurability to softness. */ + + if (haptics->mode == HID_HAPTIC_MODE_DEVICE) { + ret = magicmouse_switch_mode(trackpad_idev, HID_HAPTIC_MODE_HOST); + + if (ret) { + dev_err(&msc->hdev->dev, "Error: Unable to switch mouse to host-controlled mode."); + msc->haptic_effects[effect->id].effect_type = 0; + return ret; + } + + haptics->mode = HID_HAPTIC_MODE_HOST; + } + + hid_info(trackpad_hdev, "Successfully uploaded effects!!\n"); + return 0; +} + +static void haptic_playback_worker(struct work_struct *ws) +{ + struct effect_job *job = container_of(ws, struct effect_job, work); + + struct hid_device *taptic_hdev = job->taptic_hdev; + + if (job->effect_type && apple_taptic_send(taptic_hdev, job->effect_type, + job->strength, job->softness)) + pr_err("apple-haptic: unable to send haptic event.\n"); +} + +static int apple_taptic_playback(struct input_dev *trackpad_idev, int effect_id, int value) +{ + struct hid_device *trackpad_hdev = input_get_drvdata(trackpad_idev); + struct magicmouse_sc *msc = hid_get_drvdata(trackpad_hdev); + + if (value) + queue_work(msc->haptics->wq, &msc->haptic_effects[effect_id].work); + + return 0; +} + +static int apple_taptic_erase(struct input_dev *trackpad_idev, int effect_id) +{ + struct hid_device *trackpad_hdev = input_get_drvdata(trackpad_idev); + struct magicmouse_sc *msc = hid_get_drvdata(trackpad_hdev); + int i, ret = 0; + + msc->haptic_effects[effect_id].effect_type = 0; + + for (i = 0; i < FF_MAX_EFFECTS; i++) { + if (msc->haptic_effects[i].effect_type != 0) + return 0; + } + + /* Return to device-controlled mode if there are no effects left */ + if (msc->haptics->mode == HID_HAPTIC_MODE_HOST) { + flush_workqueue(msc->haptics->wq); + ret = magicmouse_switch_mode(trackpad_idev, HID_HAPTIC_MODE_DEVICE); + + if (ret) { + dev_err(&msc->hdev->dev, "Error: Unable to switch mouse back to device-controlled mode."); + return ret; + } + + msc->haptics->mode = HID_HAPTIC_MODE_DEVICE; + } + + return 0; +} + +static void apple_taptic_destroy(struct ff_device *ff) +{ + struct hid_haptic_device *haptic_dev = ff->private; + struct magicmouse_sc *msc = hid_get_drvdata(haptic_dev->hdev); + int ret; + + if (msc->haptics && msc->haptics->mode == HID_HAPTIC_MODE_HOST) { + flush_workqueue(msc->haptics->wq); + ret = magicmouse_switch_mode(msc->input, HID_HAPTIC_MODE_DEVICE); + if (ret) + hid_err(msc->hdev, "Failed to switch back to device-controlled mode.\n"); + + msc->haptics->mode = HID_HAPTIC_MODE_DEVICE; + } + + destroy_workqueue(haptic_dev->wq); + haptic_dev->wq = NULL; + + kfree(msc->haptic_effects); + msc->haptic_effects = NULL; +} + +static int apple_taptic_init_mtp(struct magicmouse_sc *msc, + struct hid_haptic_device *haptic_dev) +{ + struct hid_device *trackpad_hdev = msc->hdev; + struct ff_device *ff; + int ret, i; + + haptic_dev->hdev = trackpad_hdev; + + msc->haptic_effects = kzalloc_objs(struct effect_job, FF_MAX_EFFECTS); + if (!msc->haptic_effects) { + dev_err(&trackpad_hdev->dev, "Cannot allocate haptic effects\n"); + return -ENOMEM; + } + + haptic_dev->wq = create_singlethread_workqueue("Apple trackpad haptics workqueue"); + if (!haptic_dev->wq) { + dev_err(&trackpad_hdev->dev, "Cannot allocate haptic workqueue\n"); + kfree(msc->haptic_effects); + msc->haptic_effects = NULL; + return -ENOMEM; + } + + for (i = 0; i < FF_MAX_EFFECTS; i++) + INIT_WORK(&msc->haptic_effects[i].work, haptic_playback_worker); + + ret = input_ff_create(msc->input, FF_MAX_EFFECTS); + if (ret) { + kfree(msc->haptic_effects); + msc->haptic_effects = NULL; + + destroy_workqueue(haptic_dev->wq); + haptic_dev->wq = NULL; + + dev_err(&trackpad_hdev->dev, "Failed to create force-feedback device.\n"); + return ret; + } + + ff = msc->input->ff; + ff->private = haptic_dev; + ff->upload = apple_upload_effects; + ff->playback = apple_taptic_playback; + ff->erase = apple_taptic_erase; + ff->destroy = apple_taptic_destroy; + + input_set_capability(msc->input, EV_FF, FF_HAPTIC); + + hid_info(trackpad_hdev, "Successfully init'd the haptics\n"); + return 0; +} + +static int magicmouse_init_haptics(struct magicmouse_sc *msc, struct hid_device *trackpad_hdev) +{ +#ifdef CONFIG_HID_APPLE_MTP_HAPTIC + struct device *taptic_dev; + + /* Check if actuator is allocated */ + taptic_dev = device_find_child(trackpad_hdev->dev.parent, NULL, match_actuator); + if (taptic_dev) + msc->taptic_hdev = to_hid_device(taptic_dev); + + if (msc->taptic_hdev) { + hid_info(trackpad_hdev, "Successfully bound trackpad drivers to the actuator\n"); + + msc->haptics = devm_kzalloc(&trackpad_hdev->dev, sizeof(*msc->haptics), GFP_KERNEL); + + if (!msc->haptics) { + dev_warn(&trackpad_hdev->dev, "Cannot allocate haptics for %s\n", trackpad_hdev->name); + put_device(taptic_dev); + msc->taptic_hdev = NULL; + + return -ENOMEM; + } + + msc->haptics->hdev = trackpad_hdev; + + if (apple_taptic_init_mtp(msc, msc->haptics)) { + put_device(taptic_dev); + msc->taptic_hdev = NULL; + + devm_kfree(&trackpad_hdev->dev, msc->haptics); + msc->haptics = NULL; + } + } +#endif + + return 0; +} + static int magicmouse_probe(struct hid_device *hdev, const struct hid_device_id *id) { @@ -1356,6 +1596,11 @@ static int magicmouse_probe(struct hid_device *hdev, goto err_stop_hw; } + ret = magicmouse_init_haptics(msc, hdev); + + if (ret) + goto err_stop_hw; + switch (id->product) { case USB_DEVICE_ID_APPLE_MAGICMOUSE: report = hid_register_report(hdev, HID_INPUT_REPORT, MOUSE_REPORT_ID, 0); @@ -1416,6 +1661,12 @@ static int magicmouse_probe(struct hid_device *hdev, timer_delete_sync(&msc->battery_timer); hid_hw_stop(hdev); + + if (msc->taptic_hdev) { + put_device(&msc->taptic_hdev->dev); + msc->taptic_hdev = NULL; + } + return ret; } @@ -1431,6 +1682,12 @@ static void magicmouse_remove(struct hid_device *hdev) } hid_hw_stop(hdev); + + if (msc && msc->taptic_hdev) { + put_device(&msc->taptic_hdev->dev); + msc->taptic_hdev = NULL; + } + } static const __u8 *magicmouse_report_fixup(struct hid_device *hdev, __u8 *rdesc,