From 1affd03d0d8f3d5b91bbeb04107e5248f9def5fd Mon Sep 17 00:00:00 2001 From: areofyl Date: Sun, 31 May 2026 16:53:52 -0700 Subject: [PATCH] macsmc-input: poll MSLD for lid state, fix spurious s2idle wake SMC lid events (0x7203) show up in the SMC firmware syslog but never get delivered to Linux via RTKit notifications, so lid open/close is invisible to userspace. Switch to polling the MSLD key every second with a delayed_work and reporting SW_LID when it changes. 2-poll debounce because MSLD bounces during DP disconnect events. Also fixes spurious wake: the SMC fires a fake BTN_TOUCHID press+release within ~1ms of entering s2idle. Skip the first 2 button events after pm_prepare, real presses after that go through normally. Other fixes: - Add remove callback (old driver had none, leaked notifier + work) - pm_complete cancels and reschedules lid_work so polling survives suspend/resume cycles - Expose MSLD as sysfs attribute (msld_state) for debugging - Init pending_lid_state from MSLD at probe Tested on M1 MacBook Air (J313), fairydust 6.18.10. Signed-off-by: areofyl --- drivers/input/misc/macsmc-input.c | 116 ++++++++++++++++++++++++++++-- 1 file changed, 109 insertions(+), 7 deletions(-) diff --git a/drivers/input/misc/macsmc-input.c b/drivers/input/misc/macsmc-input.c index 2cead3b7f45fed..8992ef52f32ced 100644 --- a/drivers/input/misc/macsmc-input.c +++ b/drivers/input/misc/macsmc-input.c @@ -28,6 +28,12 @@ struct macsmc_input { struct input_dev *input; struct notifier_block nb; bool wakeup_mode; + int ignore_btn_count; + struct delayed_work lid_work; + u8 pending_lid_state; + u8 debounce_lid_state; + int debounce_count; + bool have_lid; }; #define SMC_EV_BTN 0x7201 @@ -46,11 +52,21 @@ static void macsmc_input_event_button(struct macsmc_input *smcin, unsigned long switch (button) { case BTN_POWER: case BTN_TOUCHID: + /* + * The SMC fires a spurious BTN_TOUCHID press+release ~1ms + * after entering s2idle. Skip those first 2 events, then + * let real presses through normally. + */ + if (smcin->wakeup_mode && smcin->ignore_btn_count > 0) { + smcin->ignore_btn_count--; + return; + } + pm_wakeup_dev_event(smcin->dev, 0, (smcin->wakeup_mode && state)); /* * Suppress KEY_POWER reports when suspended to avoid powering down * immediately after waking from s2idle. - * */ + */ if (smcin->wakeup_mode) return; @@ -79,14 +95,75 @@ static void macsmc_input_event_button(struct macsmc_input *smcin, unsigned long } } +static void macsmc_input_lid_work(struct work_struct *work) +{ + struct macsmc_input *smcin = container_of(work, struct macsmc_input, lid_work.work); + u8 val; + int ret; + + ret = apple_smc_read_u8(smcin->smc, SMC_KEY(MSLD), &val); + if (ret) { + dev_warn_once(smcin->dev, "MSLD read failed: %d\n", ret); + goto resched; + } + + /* + * Debounce: DP disconnect can cause MSLD to bounce briefly. + * Require 2 consecutive polls with the same changed value + * before reporting a lid state change. + */ + if (val != smcin->debounce_lid_state) { + smcin->debounce_lid_state = val; + smcin->debounce_count = 1; + } else { + smcin->debounce_count++; + } + + if (smcin->debounce_lid_state != smcin->pending_lid_state && + smcin->debounce_count >= 2) { + dev_info(smcin->dev, "lid state changed: %d -> %d\n", + smcin->pending_lid_state, smcin->debounce_lid_state); + smcin->pending_lid_state = smcin->debounce_lid_state; + input_report_switch(smcin->input, SW_LID, smcin->pending_lid_state); + input_sync(smcin->input); + } + +resched: + schedule_delayed_work(&smcin->lid_work, msecs_to_jiffies(1000)); +} + static void macsmc_input_event_lid(struct macsmc_input *smcin, unsigned long event) { - u8 lid_state = !!((event >> 8) & 0xff); + /* + * SMC lid events (0x7203) are not reliably delivered via RTKit + * notifications on all machines. Lid state is polled via the + * MSLD key in lid_work instead. + */ +} + +static ssize_t msld_state_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct macsmc_input *smcin = dev_get_drvdata(dev); + u8 val; + int ret; - pm_wakeup_dev_event(smcin->dev, 0, (smcin->wakeup_mode && !lid_state)); - input_report_switch(smcin->input, SW_LID, lid_state); - input_sync(smcin->input); + if (!smcin->have_lid) + return sysfs_emit(buf, "unsupported\n"); + + ret = apple_smc_read_u8(smcin->smc, SMC_KEY(MSLD), &val); + if (ret) + return sysfs_emit(buf, "error %d\n", ret); + + return sysfs_emit(buf, "%d\n", val); } +static DEVICE_ATTR_RO(msld_state); + +static struct attribute *macsmc_input_attrs[] = { + &dev_attr_msld_state.attr, + NULL, +}; +ATTRIBUTE_GROUPS(macsmc_input); static int macsmc_input_event(struct notifier_block *nb, unsigned long event, void *data) { @@ -125,7 +202,9 @@ static int macsmc_input_probe(struct platform_device *pdev) smcin->dev = &pdev->dev; smcin->smc = smc; + smcin->have_lid = have_lid; platform_set_drvdata(pdev, smcin); + INIT_DELAYED_WORK(&smcin->lid_work, macsmc_input_lid_work); smcin->input = devm_input_allocate_device(&pdev->dev); if (!smcin->input) @@ -143,10 +222,14 @@ static int macsmc_input_probe(struct platform_device *pdev) u8 val; error = apple_smc_read_u8(smc, SMC_KEY(MSLD), &val); - if (error < 0) + if (error < 0) { dev_warn(&pdev->dev, "Failed to read initial lid state\n"); - else + } else { + smcin->pending_lid_state = val; + smcin->debounce_lid_state = val; + smcin->debounce_count = 2; input_report_switch(smcin->input, SW_LID, val); + } } if (have_power) { @@ -172,14 +255,27 @@ static int macsmc_input_probe(struct platform_device *pdev) device_init_wakeup(&pdev->dev, true); + if (have_lid) + schedule_delayed_work(&smcin->lid_work, msecs_to_jiffies(1000)); + return 0; } +static void macsmc_input_remove(struct platform_device *pdev) +{ + struct macsmc_input *smcin = platform_get_drvdata(pdev); + struct apple_smc *smc = smcin->smc; + + cancel_delayed_work_sync(&smcin->lid_work); + blocking_notifier_chain_unregister(&smc->event_handlers, &smcin->nb); +} + static int macsmc_input_pm_prepare(struct device *dev) { struct macsmc_input *smcin = dev_get_drvdata(dev); smcin->wakeup_mode = true; + smcin->ignore_btn_count = 2; return 0; } @@ -187,7 +283,11 @@ static void macsmc_input_pm_complete(struct device *dev) { struct macsmc_input *smcin = dev_get_drvdata(dev); + cancel_delayed_work_sync(&smcin->lid_work); smcin->wakeup_mode = false; + smcin->ignore_btn_count = 0; + if (smcin->have_lid) + schedule_delayed_work(&smcin->lid_work, msecs_to_jiffies(1000)); } static const struct dev_pm_ops macsmc_input_pm_ops = { @@ -199,8 +299,10 @@ static struct platform_driver macsmc_input_driver = { .driver = { .name = "macsmc-input", .pm = &macsmc_input_pm_ops, + .dev_groups = macsmc_input_groups, }, .probe = macsmc_input_probe, + .remove = macsmc_input_remove, }; module_platform_driver(macsmc_input_driver);