mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH] platform/x86: ideapad-laptop: Report camera switch as SW_CAMERA_LENS_COVER
@ 2026-09-19  8:41 Marco Giunta
  2026-09-20  6:42 ` Huang Wei
  2026-09-20 20:28 ` [PATCH v2] " Marco Giunta
  0 siblings, 2 replies; 20+ messages in thread
From: Marco Giunta @ 2026-09-19  8:41 UTC (permalink / raw)
  To: Ilpo Järvinen, Mark Pearson, Hans de Goede
  Cc: Ike Panhc, Derek J . Clark, platform-driver-x86, linux-kernel,
	Marco Giunta

On certain 2025 Lenovo laptops, such as the Yoga Pro 7 14ASP10 and Legion
Pro 7 16AFR10H, a switch on the side disables the camera. This is handled
by the firmware, which also sends event 0x0d (camera disabled) or 0x0c
(camera enabled) through the WMI event GUID handled with
ideapad_wmi_context_fn_keys. These events are currently reported as
KEY_UNKNOWN.

Other Lenovo laptops expose a similar camera switch through a separate WMI
GUID, which lenovo-wmi-camera reports as SW_CAMERA_LENS_COVER since
commit d98bf6a6ed61 ("platform/x86: lenovo-wmi-camera: Use
SW_CAMERA_LENS_COVER instead of KEY_CAMERA_ACESS"). That driver does not
bind on these laptops, so do the same here: as the switch state cannot be
read from the firmware, register a separate input device with the switch
on the first event, and report the state carried by each event.

Signed-off-by: Marco Giunta <marco_giunta@outlook.it>
---
Notes:

* I considered a couple of alternatives to the approach in this patch:

  1.  Simply map 0x0c and 0x0d to KEY_CAMERA_ACCESS_ENABLE/DISABLE:

      { KE_KEY,	0x0c | IDEAPAD_WMI_KEY, { KEY_CAMERA_ACCESS_ENABLE } },
      { KE_KEY,	0x0d | IDEAPAD_WMI_KEY, { KEY_CAMERA_ACCESS_DISABLE } },

      similar to the touchpad off/on hkey events (66/67) this driver emits
      after the firmware has toggled the touchpad state. However, HUTRR72
      describes these hkey events as asserting a camera access state for
      the host to apply, rather than reporting one the firmware has
      already applied. Furthermore, lenovo-wmi-camera moved away from
      these hkey events in favor of SW_CAMERA_LENS_COVER in the quoted
      commit, which is arguably the closest relevant precedent.
    
  2.  Ignore these events like the firmware-handled FnLock events:

				{ KE_IGNORE,	0x0c | IDEAPAD_WMI_KEY },
				{ KE_IGNORE,	0x0d | IDEAPAD_WMI_KEY },

      This fixes the KEY_UNKNOWN issue, but userspace gets no information.
    
  Overall, in the end I copied the approach of the lenovo-wmi-camera
  driver. Kindly let me know if there are other/better solutions.
    
* Unlike the touchpad, whose state this driver reads with VPCCMD_R_TOUCHPAD
  at probe, on resume and on each event, the camera switch state is only
  available in EC-private fields, and the two tested laptops even use
  different ones. Hence the lenovo-wmi-camera approach: the switch appears
  on the first event, and a change made while suspended is not reported,
  leaving the state wrong until the switch is toggled again.

* For reference, on KDE Plasma 6.7.5, using the Legion's touchpad toggle
  button shows an OSD, but nothing on a SW_CAMERA_LENS_COVER change or if
  sending KEY_CAMERA_ACCESS_ENABLE/DISABLE events. I am not sure whether
  this is simply a case of "not implemented yet" or if I misunderstood how
  these events are supposed to be used to communicate with userspace.
  Please feel free to correct me here.
  
Best regards,
Marco
---
 drivers/platform/x86/lenovo/ideapad-laptop.c | 56 ++++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/drivers/platform/x86/lenovo/ideapad-laptop.c b/drivers/platform/x86/lenovo/ideapad-laptop.c
index 207368f5d489..c354b5d71399 100644
--- a/drivers/platform/x86/lenovo/ideapad-laptop.c
+++ b/drivers/platform/x86/lenovo/ideapad-laptop.c
@@ -178,6 +178,7 @@ struct ideapad_private {
 	struct ideapad_rfk_priv rfk_priv[IDEAPAD_RFKILL_DEV_NUM];
 	struct platform_device *platform_device;
 	struct input_dev *inputdev;
+	struct input_dev *camera_switch_idev;
 	struct backlight_device *blightdev;
 	struct ideapad_dytc_priv *dytc;
 	struct dentry *debug;
@@ -1410,10 +1411,43 @@ static int ideapad_input_init(struct ideapad_private *priv)
 	return err;
 }
 
+static int ideapad_camera_switch_init(struct ideapad_private *priv, bool covered)
+{
+	struct input_dev *idev;
+	int err;
+
+	idev = input_allocate_device();
+	if (!idev)
+		return -ENOMEM;
+
+	idev->name = "Ideapad Camera Switch";
+	idev->phys = "ideapad/input1";
+	idev->id.bustype = BUS_HOST;
+	idev->dev.parent = &priv->platform_device->dev;
+
+	input_set_capability(idev, EV_SW, SW_CAMERA_LENS_COVER);
+	input_report_switch(idev, SW_CAMERA_LENS_COVER, covered);
+	input_sync(idev);
+
+	err = input_register_device(idev);
+	if (err) {
+		input_free_device(idev);
+		return err;
+	}
+
+	priv->camera_switch_idev = idev;
+	return 0;
+}
+
 static void ideapad_input_exit(struct ideapad_private *priv)
 {
 	input_unregister_device(priv->inputdev);
 	priv->inputdev = NULL;
+
+	if (priv->camera_switch_idev) {
+		input_unregister_device(priv->camera_switch_idev);
+		priv->camera_switch_idev = NULL;
+	}
 }
 
 static void ideapad_input_report(struct ideapad_private *priv,
@@ -1422,6 +1456,19 @@ static void ideapad_input_report(struct ideapad_private *priv,
 	sparse_keymap_report_event(priv->inputdev, scancode, 1, true);
 }
 
+static void ideapad_camera_switch_report(struct ideapad_private *priv, bool covered)
+{
+	if (!priv->camera_switch_idev) {
+		if (ideapad_camera_switch_init(priv, covered))
+			dev_warn(&priv->platform_device->dev,
+				 "Failed to register camera switch input device\n");
+		return;
+	}
+
+	input_report_switch(priv->camera_switch_idev, SW_CAMERA_LENS_COVER, covered);
+	input_sync(priv->camera_switch_idev);
+}
+
 static void ideapad_input_novokey(struct ideapad_private *priv)
 {
 	unsigned long long_pressed;
@@ -2312,6 +2359,15 @@ static void ideapad_wmi_notify(struct wmi_device *wdev, union acpi_object *data)
 			break;
 		}
 
+		/*
+		 * Camera switch, handled by the firmware:
+		 * 0x0c camera enabled, 0x0d camera disabled
+		 */
+		if (data->integer.value == 0x0c || data->integer.value == 0x0d) {
+			ideapad_camera_switch_report(priv, data->integer.value == 0x0d);
+			break;
+		}
+
 		/* 0x02 FnLock, 0x03 Esc */
 		if (data->integer.value == 0x02 || data->integer.value == 0x03)
 			ideapad_fn_lock_led_notify(priv, data->integer.value == 0x02);

base-commit: f475845eaf3d749114a63270bf2efea459e14dd2
-- 
2.55.0


^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2026-09-23 13:11 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-19  8:41 [PATCH] platform/x86: ideapad-laptop: Report camera switch as SW_CAMERA_LENS_COVER Marco Giunta
2026-09-20  6:42 ` Huang Wei
2026-09-20  9:58   ` Marco Giunta
2026-09-20 10:29     ` Huang Wei
2026-09-20 10:58       ` Marco Giunta
2026-09-20 20:28 ` [PATCH v2] " Marco Giunta
2026-09-21  2:36   ` Huang Wei
2026-09-21 14:50   ` Rong Zhang
2026-09-21 15:03     ` Rong Zhang
2026-09-21 19:00     ` Marco Giunta
2026-09-22 11:08       ` Huang Wei
2026-09-22 12:18       ` Rong Zhang
2026-09-22 15:52         ` Mark Pearson
2026-09-22 15:58           ` Rong Zhang
2026-09-22 16:42             ` Rong Zhang
2026-09-22 17:32               ` Mark Pearson
2026-09-22 18:47                 ` Rong Zhang
2026-09-23 11:17                   ` Marco Giunta
2026-09-23 12:39                     ` Rong Zhang
2026-09-23 13:11                       ` Marco Giunta

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®