[PATCH 2/3] platform/x86: panasonic-laptop: Add driver for CF-33 A1/A2 buttons (TBTN)

Hilgad Montelo <[email protected]>
Newsgroups org.kernel.vger.platform-driver-x86,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
On the Panasonic Toughbook CF-33 the bezel A1/A2 buttons are wired to
a separate ACPI device, MAT003C (ACPI path \_SB.TBTN), rather than the
main Hotkey device (MAT0019/HKEY) this driver already talks to.
MAT003C was present in the ACPI namespace but had no driver bound to
it, so A1/A2 produced no signal through any channel: no evdev events,
no ACPI notify (nothing logged), no WMI device, and no correlated ACPI
GPE interrupt activity.

Found by dumping and disassembling the platform's ACPI tables
(acpidump -b + iasl -d) and searching for EC _Qxx query handlers that
push scancodes into a hotkey queue. TBTN turned out to implement its
own HINF/HIND/SQTY/SINF method quartet, structurally a clone of HKEY's:
_Qxx handlers call TBTN.HIND(code) then Notify(TBTN, 0x80); HINF()
dequeues one scancode from a small EC-side FIFO. Confirmed scancodes:
0x38/0x39 = A1 press/release, 0x42/0x43 = A2 press/release. Unlike
HKEY, TBTN's codes never set the high bit -- press and release are
distinct scancodes rather than one code plus an up/down flag.

TBTN.SQTY returns 1 (it has no brightness/battery data, just a
button-availability flag), so it cannot be probed via the existing
acpi_pcc_hotkey_probe(), which requires num_sifr > SINF_DC_CUR_BRIGHT
(assumes every device has the full brightness/eco-mode/battery SINF
block). Add a second, minimal platform_driver bound to MAT003C
instead, with its own small input device reporting KEY_PROG2 (A1) and
KEY_PROG3 (A2) -- both already carry standard XKB keysym mappings
(XF86Launch2/XF86Launch3), so no udev/hwdb work is needed for desktop
environments to bind them to actions.

Verified on a CF-33 Mk1: evtest shows clean KEY_PROG2/KEY_PROG3
press/release pairs with real physical timing; confirmed bindable as
GNOME custom shortcuts and launching applications correctly.

Signed-off-by: Hilgad Montelo <[email protected]>
---
 drivers/platform/x86/panasonic-laptop.c | 168 +++++++++++++++++++++++-
 1 file changed, 167 insertions(+), 1 deletion(-)

diff --git a/drivers/platform/x86/panasonic-laptop.c b/drivers/platform/x86/panasonic-laptop.c
index 730d7cb..0afd6b8 100644
--- a/drivers/platform/x86/panasonic-laptop.c
+++ b/drivers/platform/x86/panasonic-laptop.c
@@ -197,6 +197,42 @@ static const struct acpi_device_id pcc_device_ids[] = {
 };
 MODULE_DEVICE_TABLE(acpi, pcc_device_ids);
 
+/*
+ * On the CF-33 the bezel A1/A2 buttons are wired to a separate ACPI device
+ * (MAT003C, ACPI path \_SB.TBTN) rather than the main Hotkey (MAT0019/HKEY)
+ * device the rest of this driver talks to. TBTN implements its own
+ * HINF/HIND/SQTY/SINF method quartet, structurally a clone of HKEY's, but
+ * SQTY only reports a single SIFR element (a button-availability flag) --
+ * it has none of HKEY's brightness/battery/backlight state, so it can't be
+ * probed via acpi_pcc_hotkey_probe(), which requires the full SINF block.
+ * Register a second, minimal platform_driver for it instead.
+ */
+#define METHOD_TBTN_QUERY	"HINF"
+#define TBTN_NOTIFY		0x80
+
+static const struct acpi_device_id tbtn_device_ids[] = {
+	{ "MAT003C", 0},
+	{ "", 0},
+};
+MODULE_DEVICE_TABLE(acpi, tbtn_device_ids);
+
+struct tbtn_acpi {
+	acpi_handle		handle;
+	struct input_dev	*input_dev;
+};
+
+static int tbtn_probe(struct platform_device *pdev);
+static void tbtn_remove(struct platform_device *pdev);
+
+static struct platform_driver acpi_tbtn_driver = {
+	.probe = tbtn_probe,
+	.remove = tbtn_remove,
+	.driver = {
+		.name = "Panasonic Tablet Buttons",
+		.acpi_match_table = tbtn_device_ids,
+	},
+};
+
 #ifdef CONFIG_PM_SLEEP
 static int acpi_pcc_hotkey_resume(struct device *dev);
 #endif
@@ -951,6 +987,112 @@ static void acpi_pcc_hotkey_notify(acpi_handle handle, u32 event, void *data)
 	}
 }
 
+/*
+ * TBTN's HINF dequeues one raw scancode from a small EC-side FIFO (0 if
+ * empty) and re-Notify()s itself if more than one entry was pending, so a
+ * single evaluate-and-report per notification is sufficient here -- unlike
+ * HKEY, TBTN's codes never set the high bit, since press and release are
+ * distinct scancodes rather than one code plus an up/down flag.
+ */
+static void tbtn_report_key(struct tbtn_acpi *tbtn, unsigned int code)
+{
+	static const struct {
+		unsigned int code;
+		unsigned int keycode;
+		bool down;
+	} keymap[] = {
+		{ 0x38, KEY_PROG2, true },  /* A1 press */
+		{ 0x39, KEY_PROG2, false }, /* A1 release */
+		{ 0x42, KEY_PROG3, true },  /* A2 press */
+		{ 0x43, KEY_PROG3, false }, /* A2 release */
+	};
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(keymap); i++) {
+		if (keymap[i].code != code)
+			continue;
+		input_report_key(tbtn->input_dev, keymap[i].keycode, keymap[i].down);
+		input_sync(tbtn->input_dev);
+		return;
+	}
+
+	pr_info("Unknown TBTN hotkey event: 0x%02x\n", code);
+}
+
+static void tbtn_notify(acpi_handle handle, u32 event, void *data)
+{
+	struct tbtn_acpi *tbtn = data;
+	unsigned long long result;
+	acpi_status status;
+
+	if (event != TBTN_NOTIFY)
+		return;
+
+	status = acpi_evaluate_integer(tbtn->handle, METHOD_TBTN_QUERY,
+				       NULL, &result);
+	if (ACPI_FAILURE(status)) {
+		pr_err("TBTN: error getting hotkey status\n");
+		return;
+	}
+
+	if (result)
+		tbtn_report_key(tbtn, result);
+}
+
+static int tbtn_probe(struct platform_device *pdev)
+{
+	struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
+	struct tbtn_acpi *tbtn;
+	struct input_dev *input_dev;
+	int error;
+
+	if (!device)
+		return -ENODEV;
+
+	tbtn = devm_kzalloc(&pdev->dev, sizeof(*tbtn), GFP_KERNEL);
+	if (!tbtn)
+		return -ENOMEM;
+
+	tbtn->handle = device->handle;
+	device->driver_data = tbtn;
+
+	input_dev = devm_input_allocate_device(&pdev->dev);
+	if (!input_dev)
+		return -ENOMEM;
+
+	input_dev->name = "Panasonic Tablet Buttons";
+	input_dev->phys = "panasonic/tbtn0";
+	input_dev->id.bustype = BUS_HOST;
+	input_dev->id.vendor = 0x0001;
+	input_dev->id.product = 0x0002;
+	input_dev->id.version = 0x0100;
+	input_set_capability(input_dev, EV_KEY, KEY_PROG2);
+	input_set_capability(input_dev, EV_KEY, KEY_PROG3);
+
+	error = input_register_device(input_dev);
+	if (error) {
+		pr_err("TBTN: unable to register input device\n");
+		return error;
+	}
+
+	tbtn->input_dev = input_dev;
+
+	error = acpi_dev_install_notify_handler(device, ACPI_DEVICE_NOTIFY,
+						tbtn_notify, tbtn);
+	if (error)
+		return error;
+
+	return 0;
+}
+
+static void tbtn_remove(struct platform_device *pdev)
+{
+	struct acpi_device *device = ACPI_COMPANION(&pdev->dev);
+
+	acpi_dev_remove_notify_handler(device, ACPI_DEVICE_NOTIFY, tbtn_notify);
+	device->driver_data = NULL;
+}
+
 static void pcc_optd_notify(acpi_handle handle, u32 event, void *data)
 {
 	if (event != ACPI_NOTIFY_EJECT_REQUEST)
@@ -1211,4 +1353,28 @@ static void acpi_pcc_hotkey_remove(struct platform_device *pdev)
 	kfree(pcc);
 }
 
-module_platform_driver(acpi_pcc_driver);
+static int __init panasonic_module_init(void)
+{
+	int error;
+
+	error = platform_driver_register(&acpi_pcc_driver);
+	if (error)
+		return error;
+
+	error = platform_driver_register(&acpi_tbtn_driver);
+	if (error) {
+		platform_driver_unregister(&acpi_pcc_driver);
+		return error;
+	}
+
+	return 0;
+}
+
+static void __exit panasonic_module_exit(void)
+{
+	platform_driver_unregister(&acpi_tbtn_driver);
+	platform_driver_unregister(&acpi_pcc_driver);
+}
+
+module_init(panasonic_module_init);
+module_exit(panasonic_module_exit);
-- 
2.53.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.