diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 18244ecf301614..cf4b58ef0566b3 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -146,6 +146,16 @@ 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_HAPTIC + tristate "Apple MTP Haptic Actuator support" + depends on HID + help + Say Y here if you want support for allowing a "Host controlled" touchpad mode + where userspace has control of manually triggering haptic click and deep-click + effects on the apple haptic trackpad. This is independent of the autonomous + clicking that triggers without userspace input by default. + + config HID_APPLEIR tristate "Apple infrared receiver" depends on (USB_HID) @@ -739,6 +749,7 @@ config LOGIWHEELS_FF config HID_MAGICMOUSE tristate "Apple Magic Mouse/Trackpad multi-touch support" + depends on (HID && HID_APPLE_HAPTIC) || HID_APPLE_HAPTIC=n help Support for the Apple Magic Mouse/Trackpad multi-touch. diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index d2579e299b5e51..b9e16999a9196d 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_HAPTIC) += hid-apple-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/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; } diff --git a/drivers/hid/hid-apple-haptic.c b/drivers/hid/hid-apple-haptic.c new file mode 100644 index 00000000000000..4ca37bb1542c4e --- /dev/null +++ b/drivers/hid/hid-apple-haptic.c @@ -0,0 +1,118 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "asm-generic/errno-base.h" +#include "hid-ids.h" +#include "linux/gfp_types.h" +#include "linux/hid.h" +#include + +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 = "apple-trackpad-haptics", + .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/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c index a770ceccabde6b..4b82b934e1400a 100644 --- a/drivers/hid/hid-magicmouse.c +++ b/drivers/hid/hid-magicmouse.c @@ -17,6 +17,13 @@ #include #include #include +#include "hid-haptic.h" +#include "asm-generic/errno-base.h" +#include "linux/container_of.h" +#include "linux/gfp_types.h" +#include "linux/input.h" +#include "linux/workqueue_types.h" +#include #include "hid-ids.h" @@ -138,6 +145,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 +190,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 +1061,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 +1069,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 +1170,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,11 +1331,174 @@ 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; + + if ((effect->u.haptic.hid_usage != (HID_HP_WAVEFORMPRESS & HID_USAGE)) && + (effect->u.haptic.hid_usage != (HID_HP_WAVEFORMRELEASE & HID_USAGE)) && + (effect->u.haptic.hid_usage != (APPLE_HP_WAVEFORMDEEPCLICK & HID_USAGE))) { + 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; + } + + // msc->haptics->input_dev = dev; + 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_probe(struct hid_device *hdev, const struct hid_device_id *id) { struct magicmouse_sc *msc; struct hid_report *report; + struct device *taptic_dev; int ret; if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE && @@ -1356,6 +1562,36 @@ static int magicmouse_probe(struct hid_device *hdev, goto err_stop_hw; } + // Check if actuator is allocated + taptic_dev = device_find_child(hdev->dev.parent, NULL, match_actuator); + if (taptic_dev) + msc->taptic_hdev = to_hid_device(taptic_dev); + + if (IS_ENABLED(CONFIG_HID_APPLE_HAPTIC) && msc->taptic_hdev) { + hid_info(hdev, "Successfully bound trackpad drivers to the actuator\n"); + + msc->haptics = devm_kzalloc(&hdev->dev, sizeof(*msc->haptics), GFP_KERNEL); + + if (!msc->haptics) { + dev_warn(&hdev->dev, "Cannot allocate haptics for %s\n", hdev->name); + put_device(taptic_dev); + msc->taptic_hdev = NULL; + + ret = -ENOMEM; + goto err_stop_hw; + } + + msc->haptics->hdev = hdev; + + if (apple_taptic_init_mtp(msc, msc->haptics)) { + put_device(taptic_dev); + msc->taptic_hdev = NULL; + + devm_kfree(&hdev->dev, msc->haptics); + msc->haptics = NULL; + } + } + switch (id->product) { case USB_DEVICE_ID_APPLE_MAGICMOUSE: report = hid_register_report(hdev, HID_INPUT_REPORT, MOUSE_REPORT_ID, 0); @@ -1416,6 +1652,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 +1673,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, diff --git a/include/linux/soc/apple/actuator.h b/include/linux/soc/apple/actuator.h new file mode 100644 index 00000000000000..9acaa49b232678 --- /dev/null +++ b/include/linux/soc/apple/actuator.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ + +#ifndef __LINUX_ACTUATOR_H +#define __LINUX_ACTUATOR_H +#include + +#define APPLE_HP_WAVEFORMDEEPCLICK 0x000e2001 + +#if IS_ENABLED(CONFIG_HID_APPLE_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 /* __LINUX_ACTUATOR_H */