summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Thielen <jtln@google.com>2023-07-06 18:25:46 +0000
committerJosh Thielen <jtln@google.com>2023-07-10 18:39:41 +0000
commit47177b0378e6c916a305d987177973c62c089d5f (patch)
tree8e140ec3c4882e5c84a7f91c7c4ac5eabd5c43ba
parent582ea070fbbc7e8c16e62b0dc1957cedecf6c22a (diff)
downloadrotary-encoders-47177b0378e6c916a305d987177973c62c089d5f.tar.gz
Add PETC input filter driver
This driver: * Finds input (source) devices that have the power key set as a capability (excluding touch devices) * Creates clones of the matching input devices * Filters input events on the source device and forwards the events on the clone device * Detects when the system is in false interactive (MCU display state is On, while AP display state is Off) * Injects a wakeup key event on the clone device when an input event occurs while the system is in false interactive * When in false interactive, queues source events until the display is on or a timeout occurs Test: Manual. * Verified crown button press in ambient transitions Android to interactive. * Verified crown button press in false interactive and interactive launches app list. * Verified successful removal of device with rmmod. Bug: b/285402950 Change-Id: I3adc8b3015866220c21fac7a58c4e830c9ecbbdb Signed-off-by: Josh Thielen <jtln@google.com>
-rw-r--r--petc_input_filter/Kbuild3
-rw-r--r--petc_input_filter/Kconfig13
-rw-r--r--petc_input_filter/Makefile18
-rw-r--r--petc_input_filter/petc_input_filter.c672
-rw-r--r--petc_input_filter/petc_input_filter.h21
5 files changed, 727 insertions, 0 deletions
diff --git a/petc_input_filter/Kbuild b/petc_input_filter/Kbuild
new file mode 100644
index 0000000..cb7590a
--- /dev/null
+++ b/petc_input_filter/Kbuild
@@ -0,0 +1,3 @@
+obj-$(CONFIG_INPUT_PETC_INPUT_FILTER) += petc_input_filter.o
+
+petc-input-filter-y += petc_input_filter.o \ No newline at end of file
diff --git a/petc_input_filter/Kconfig b/petc_input_filter/Kconfig
new file mode 100644
index 0000000..8aca385
--- /dev/null
+++ b/petc_input_filter/Kconfig
@@ -0,0 +1,13 @@
+#
+# PETC input filter driver configuration
+#
+
+config INPUT_PETC_INPUT_FILTER
+ tristate "PETC input filter driver"
+ depends on INPUT
+ help
+ Say Y to enable support for the PETC input filter.
+ If unsure, say N.
+
+ To compile this driver as a module, choose M here: the
+ module will be called petc-if.
diff --git a/petc_input_filter/Makefile b/petc_input_filter/Makefile
new file mode 100644
index 0000000..517aab7
--- /dev/null
+++ b/petc_input_filter/Makefile
@@ -0,0 +1,18 @@
+EXTRA_INCLUDE := -I$(KERNEL_SRC)/../google-modules/nanohub/
+EXTRA_SYMBOLS := $(OUT_DIR)/../google-modules/nanohub/Module.symvers
+
+default: all
+
+KBUILD_OPTIONS := CONFIG_INPUT_PETC_INPUT_FILTER=m \
+ ccflags-y=$(EXTRA_INCLUDE) \
+ KBUILD_EXTRA_SYMBOLS="$(EXTRA_SYMBOLS)"
+
+all:
+ $(MAKE) -C $(KERNEL_SRC) M=$(M) modules $(KBUILD_OPTIONS)
+
+modules_install:
+ $(MAKE) INSTALL_MOD_STRIP=1 M=$(M) -C $(KERNEL_SRC) modules_install
+
+clean:
+ rm -f *.o *.ko *.mod.c *.mod.o *~ .*.cmd Module.symvers
+ rm -rf .tmp_versions
diff --git a/petc_input_filter/petc_input_filter.c b/petc_input_filter/petc_input_filter.c
new file mode 100644
index 0000000..c192866
--- /dev/null
+++ b/petc_input_filter/petc_input_filter.c
@@ -0,0 +1,672 @@
+/*
+ * Copyright (C) 2023 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+#include <linux/errno.h>
+#include <linux/input.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/major.h>
+#include <linux/mm.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/poll.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/soc/qcom/panel_event_notifier.h>
+#include <linux/workqueue.h>
+#include <linux/version.h>
+
+#include "petc_input_filter.h"
+#include "nanohub_exports.h"
+
+#define WAKEUP_WAIT_MAX msecs_to_jiffies(500)
+
+struct petc_input_event {
+ struct list_head node;
+ unsigned int type;
+ unsigned int code;
+ int value;
+};
+
+struct petc_if_dev_data {
+ struct list_head node;
+ struct input_handle handle;
+ struct work_struct register_work;
+ struct work_struct immediate_work;
+ struct delayed_work delayed_work;
+ struct list_head event_list;
+ spinlock_t event_list_slock;
+ struct input_dev *clone_dev;
+ struct petc_if_drv_data *drv_data;
+ bool clone_registered;
+};
+
+struct petc_if_drv_data {
+ struct platform_device *pdev;
+ struct drm_panel *active_panel;
+ struct work_struct remove_devices_work;
+ struct list_head device_list;
+ struct list_head removed_device_list;
+ spinlock_t device_list_slock;
+ void *notifier_cookie;
+ bool ap_screen_on;
+};
+
+static bool petc_if_is_mcu_screen_on(void)
+{
+ int ret = nanohub_query_display_state();
+ pr_debug("[PETC_IF] read MCU display state = %d\n", ret);
+ return (ret == MCU_DISPLAY_ON || ret == MCU_DISPLAY_HIGH_BRIGHTNESS);
+}
+
+static bool petc_if_is_ap_screen_on(struct petc_if_drv_data *drv_data)
+{
+ return drv_data->ap_screen_on;
+}
+
+static bool petc_if_is_false_interactive(struct petc_if_dev_data *dev_data)
+{
+ return !petc_if_is_ap_screen_on(dev_data->drv_data) &&
+ petc_if_is_mcu_screen_on();
+}
+
+static void petc_if_add_device(struct petc_if_drv_data *drv_data,
+ struct petc_if_dev_data *dev_data)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&drv_data->device_list_slock, flags);
+ list_add_tail(&dev_data->node, &drv_data->device_list);
+ spin_unlock_irqrestore(&drv_data->device_list_slock, flags);
+}
+
+static void petc_if_queue_event(struct petc_if_dev_data *dev_data,
+ unsigned int type, unsigned int code, int value)
+{
+ struct petc_input_event *event;
+ unsigned long flags;
+
+ event = kmalloc(sizeof(struct petc_input_event), GFP_ATOMIC);
+ if (!event) {
+ pr_err("[PETC_IF] Out of memory to queue event\n");
+ return;
+ }
+
+ event->type = type;
+ event->code = code;
+ event->value = value;
+
+ spin_lock_irqsave(&dev_data->event_list_slock, flags);
+ list_add_tail(&event->node, &dev_data->event_list);
+ spin_unlock_irqrestore(&dev_data->event_list_slock, flags);
+}
+
+struct petc_input_event *
+petc_if_get_queued_event(struct petc_if_dev_data *dev_data)
+{
+ struct petc_input_event *event = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev_data->event_list_slock, flags);
+ if (!list_empty(&dev_data->event_list)) {
+ event = list_first_entry(&dev_data->event_list,
+ struct petc_input_event, node);
+ list_del(&event->node);
+ }
+ spin_unlock_irqrestore(&dev_data->event_list_slock, flags);
+
+ return event;
+}
+
+static void petc_if_process_queued_events(struct petc_if_dev_data *dev_data)
+{
+ struct petc_input_event *event;
+
+ while ((event = petc_if_get_queued_event(dev_data))) {
+ pr_debug(
+ "[PETC_IF] dispatched queued input event: type=%d code=%d value=%d\n",
+ event->type, event->code, event->value);
+ input_event(dev_data->clone_dev, event->type, event->code,
+ event->value);
+ kfree(event);
+ }
+}
+
+static void petc_if_free_queued_events(struct petc_if_dev_data *dev_data)
+{
+ struct petc_input_event *event, *next;
+ unsigned long flags;
+
+ spin_lock_irqsave(&dev_data->event_list_slock, flags);
+ list_for_each_entry_safe(event, next, &dev_data->event_list, node) {
+ kfree(event);
+ }
+ spin_unlock_irqrestore(&dev_data->event_list_slock, flags);
+}
+
+static void petc_if_delayed_work_func(struct work_struct *work)
+{
+ struct petc_if_dev_data *dev_data =
+ container_of(work, struct petc_if_dev_data, delayed_work.work);
+
+ pr_debug("[PETC_IF] handling delayed work\n");
+
+ if (!dev_data->clone_registered) {
+ pr_warn("[PETC_IF] ignoring work. clone not registered\n");
+ return;
+ }
+
+ petc_if_process_queued_events(dev_data);
+}
+
+static void petc_if_send_wakeup_key(struct petc_if_dev_data *dev_data)
+{
+ input_report_key(dev_data->clone_dev, KEY_WAKEUP, 1);
+ input_sync(dev_data->clone_dev);
+ input_report_key(dev_data->clone_dev, KEY_WAKEUP, 0);
+ input_sync(dev_data->clone_dev);
+}
+
+static void petc_if_immediate_work_func(struct work_struct *work)
+{
+ struct petc_if_dev_data *dev_data =
+ container_of(work, struct petc_if_dev_data, immediate_work);
+
+ pr_debug("[PETC_IF] handling immediate work\n");
+
+ if (!dev_data->clone_registered) {
+ pr_warn("[PETC_IF] ignoring work. clone not registered\n");
+ return;
+ }
+
+ if (petc_if_is_false_interactive(dev_data)) {
+ pr_debug("[PETC_IF] injecting wakekey\n");
+ petc_if_send_wakeup_key(dev_data);
+
+ pr_debug("[PETC_IF] delayed work scheduled\n");
+ schedule_delayed_work(&dev_data->delayed_work, WAKEUP_WAIT_MAX);
+ } else {
+ petc_if_process_queued_events(dev_data);
+ }
+}
+
+static bool petc_if_handle_input(struct petc_if_dev_data *dev_data,
+ unsigned int type, unsigned int code,
+ int value)
+{
+ if (!work_pending(&dev_data->immediate_work) &&
+ !delayed_work_pending(&dev_data->delayed_work)) {
+ if (petc_if_is_ap_screen_on(dev_data->drv_data)) {
+ pr_debug(
+ "[PETC_IF] forwarding event (AP screen on): type=%d code=%d value=%d\n",
+ type, code, value);
+ input_event(dev_data->clone_dev, type, code, value);
+ } else {
+ pr_debug(
+ "[PETC_IF] queued event (AP screen off): type=%d code=%d value=%d\n",
+ type, code, value);
+ petc_if_queue_event(dev_data, type, code, value);
+ schedule_work(&dev_data->immediate_work);
+ pr_debug("[PETC_IF] immediate work scheduled\n");
+ }
+ } else {
+ pr_debug(
+ "[PETC_IF] queued event (pending work): type=%d code=%d value=%d\n",
+ type, code, value);
+ petc_if_queue_event(dev_data, type, code, value);
+ if (!delayed_work_pending(&dev_data->delayed_work)) {
+ pr_debug("[PETC_IF] immediate work scheduled\n");
+ schedule_work(&dev_data->immediate_work);
+ }
+ }
+ return true;
+}
+
+static void petc_if_register_work_func(struct work_struct *work)
+{
+ struct petc_if_dev_data *dev_data =
+ container_of(work, struct petc_if_dev_data, register_work);
+ int error;
+
+ pr_debug("[PETC_IF] handling register work\n");
+
+ if (dev_data->clone_registered) {
+ pr_debug("[PETC_IF] clone already registered\n");
+ return;
+ }
+
+ error = input_register_device(dev_data->clone_dev);
+ if (error) {
+ pr_err("[PETC_IF] Failed to register clone_dev, error %d\n",
+ error);
+ input_unregister_handle(&dev_data->handle);
+ input_free_device(dev_data->clone_dev);
+ kfree(dev_data);
+ return;
+ }
+
+ petc_if_add_device(dev_data->drv_data, dev_data);
+
+ dev_data->clone_registered = true;
+}
+
+static void petc_if_remove_device(struct petc_if_dev_data *dev_data)
+{
+ cancel_work_sync(&dev_data->immediate_work);
+ cancel_delayed_work_sync(&dev_data->delayed_work);
+ petc_if_free_queued_events(dev_data);
+ input_unregister_device(dev_data->clone_dev);
+ kfree(dev_data);
+}
+
+struct petc_if_dev_data *
+petc_if_get_next_removed_device(struct petc_if_drv_data *drv_data)
+{
+ struct petc_if_dev_data *dev_data = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&drv_data->device_list_slock, flags);
+ if (!list_empty(&drv_data->removed_device_list)) {
+ dev_data = list_first_entry(&drv_data->removed_device_list,
+ struct petc_if_dev_data, node);
+ list_del(&dev_data->node);
+ }
+ spin_unlock_irqrestore(&drv_data->device_list_slock, flags);
+
+ return dev_data;
+}
+
+static void petc_if_remove_devices_work_func(struct work_struct *work)
+{
+ struct petc_if_dev_data *dev_data;
+ struct petc_if_drv_data *drv_data =
+ container_of(work, struct petc_if_drv_data, remove_devices_work);
+
+ pr_debug("[PETC_IF] handling remove devices work\n");
+
+ while ((dev_data = petc_if_get_next_removed_device(drv_data))) {
+ pr_debug("[PETC_IF] removing device %s\n", dev_data->clone_dev->name);
+ petc_if_remove_device(dev_data);
+ }
+}
+
+static bool petc_if_filter(struct input_handle *handle, unsigned int type,
+ unsigned int code, int value)
+{
+ struct petc_if_dev_data *dev_data = handle->private;
+
+ if (!dev_data->clone_registered) {
+ pr_debug(
+ "[PETC_IF] input received but clone not yet registered\n");
+ return false;
+ }
+
+ input_set_timestamp(dev_data->clone_dev,
+ handle->dev->timestamp[INPUT_CLK_MONO]);
+ return petc_if_handle_input(dev_data, type, code, value);
+}
+
+static bool petc_if_match(struct input_handler *handler, struct input_dev *dev)
+{
+ if (!strcmp(dev->name, PETC_DEV_NAME)) {
+ /* Don't match on the clone */
+ return false;
+ }
+
+ if (test_bit(EV_ABS, dev->evbit)) {
+ /* Don't match on a touch device */
+ return false;
+ }
+
+ return true;
+}
+
+static int petc_if_dev_connect(struct input_handler *handler,
+ struct input_dev *dev,
+ const struct input_device_id *id)
+{
+ struct petc_if_dev_data *dev_data;
+ struct input_dev *clone_dev;
+ int error;
+
+ pr_debug("[PETC_IF] device connected\n");
+
+ dev_data = kzalloc(sizeof(struct petc_if_dev_data), GFP_KERNEL);
+ if (!dev_data)
+ return -ENOMEM;
+
+ clone_dev = input_allocate_device();
+ if (clone_dev == NULL) {
+ pr_err("[PETC_IF] Failed to allocate clone_dev\n");
+ error = -ENOMEM;
+ goto err_free;
+ }
+
+ clone_dev->name = PETC_DEV_NAME;
+
+ memcpy(clone_dev->evbit, dev->evbit, sizeof(dev->evbit));
+ memcpy(clone_dev->keybit, dev->keybit, sizeof(dev->keybit));
+ memcpy(clone_dev->relbit, dev->relbit, sizeof(dev->relbit));
+ memcpy(clone_dev->absbit, dev->absbit, sizeof(dev->absbit));
+ memcpy(clone_dev->mscbit, dev->mscbit, sizeof(dev->mscbit));
+ memcpy(clone_dev->ledbit, dev->ledbit, sizeof(dev->ledbit));
+ memcpy(clone_dev->sndbit, dev->sndbit, sizeof(dev->sndbit));
+ memcpy(clone_dev->ffbit, dev->ffbit, sizeof(dev->ffbit));
+ memcpy(clone_dev->swbit, dev->swbit, sizeof(dev->swbit));
+ memcpy(clone_dev->propbit, dev->propbit, sizeof(dev->propbit));
+
+ input_set_capability(clone_dev, EV_KEY, KEY_WAKEUP);
+
+ dev_data->handle.dev = dev;
+ dev_data->handle.handler = handler;
+ dev_data->handle.name = PETC_HANDLE_NAME;
+ dev_data->handle.private = dev_data;
+ dev_data->clone_dev = clone_dev;
+ dev_data->drv_data = handler->private;
+
+ spin_lock_init(&dev_data->event_list_slock);
+ INIT_LIST_HEAD(&dev_data->event_list);
+ INIT_WORK(&dev_data->immediate_work, petc_if_immediate_work_func);
+ INIT_WORK(&dev_data->register_work, petc_if_register_work_func);
+ INIT_DELAYED_WORK(&dev_data->delayed_work, petc_if_delayed_work_func);
+
+ error = input_register_handle(&dev_data->handle);
+ if (error) {
+ pr_err("[PETC_IF] Failed to register input handle, error %d\n",
+ error);
+ goto err_free_device;
+ }
+
+ error = input_open_device(&dev_data->handle);
+ if (error) {
+ pr_err("[PETC_IF] Failed to open input device, error %d\n",
+ error);
+ goto err_unregister_handle;
+ }
+
+ /* The device can't be registered here because connect() is called
+ * while input_mutex is held, so queue it for registration.
+ */
+
+ pr_debug("[PETC_IF] work scheduled to register clone device\n");
+ schedule_work(&dev_data->register_work);
+
+ return 0;
+
+err_unregister_handle:
+ input_unregister_handle(&dev_data->handle);
+err_free_device:
+ input_free_device(clone_dev);
+err_free:
+ kfree(dev_data);
+ return error;
+}
+
+static void petc_if_dev_disconnect(struct input_handle *handle)
+{
+ unsigned long flags;
+ struct petc_if_dev_data *dev_data = handle->private;
+ struct petc_if_drv_data *drv_data = dev_data->drv_data;
+
+ pr_debug("[PETC_IF] device disconnected\n");
+
+ input_close_device(handle);
+ input_unregister_handle(handle);
+
+ /* The device can't be removed here because disconnect() is called
+ * while input_mutex is held, so queue it for removal.
+ */
+
+ spin_lock_irqsave(&drv_data->device_list_slock, flags);
+ list_del(&dev_data->node);
+ list_add_tail(&dev_data->node, &drv_data->removed_device_list);
+ spin_unlock_irqrestore(&drv_data->device_list_slock, flags);
+
+ schedule_work(&drv_data->remove_devices_work);
+}
+
+static const struct input_device_id petc_if_device_ids[] = {
+ {
+ .flags = INPUT_DEVICE_ID_MATCH_EVBIT |
+ INPUT_DEVICE_ID_MATCH_KEYBIT,
+ .evbit = { [BIT_WORD(EV_KEY)] = BIT_MASK(EV_KEY) },
+ .keybit = { [BIT_WORD(KEY_POWER)] = BIT_MASK(KEY_POWER) },
+ }, /* match on devices with a power key */
+ {}, /* Terminating entry */
+};
+
+MODULE_DEVICE_TABLE(input, petc_if_device_ids);
+
+static struct input_handler petc_if_handler = {
+ .filter = petc_if_filter,
+ .match = petc_if_match,
+ .connect = petc_if_dev_connect,
+ .disconnect = petc_if_dev_disconnect,
+ .name = PETC_DEV_NAME,
+ .id_table = petc_if_device_ids,
+};
+
+void petc_if_display_suspend(struct petc_if_drv_data *drv_data)
+{
+ pr_debug("[PETC_IF] display suspend\n");
+ drv_data->ap_screen_on = false;
+}
+
+void petc_if_display_resume(struct petc_if_drv_data *drv_data)
+{
+ struct petc_if_dev_data *dev_data;
+ unsigned long flags;
+
+ pr_debug("[PETC_IF] display resume\n");
+ drv_data->ap_screen_on = true;
+
+ /* Immediately dispatch queued events on clone devices */
+
+ spin_lock_irqsave(&drv_data->device_list_slock, flags);
+
+ list_for_each_entry(dev_data, &drv_data->device_list, node) {
+ if (cancel_delayed_work(&dev_data->delayed_work)) {
+ pr_debug(
+ "[PETC_IF] delayed work scheduled immediately\n");
+ schedule_work(&dev_data->immediate_work);
+ }
+ }
+
+ spin_unlock_irqrestore(&drv_data->device_list_slock, flags);
+}
+
+static void petc_if_drm_panel_notifier_callback(
+ enum panel_event_notifier_tag tag,
+ struct panel_event_notification *notification, void *data)
+{
+ struct petc_if_drv_data *drv_data = data;
+
+ if (!notification) {
+ return;
+ }
+
+ switch (notification->notif_type) {
+ case DRM_PANEL_EVENT_UNBLANK:
+ if (!notification->notif_data.early_trigger) {
+ petc_if_display_resume(drv_data);
+ }
+ break;
+
+ case DRM_PANEL_EVENT_BLANK:
+ case DRM_PANEL_EVENT_BLANK_LP:
+ if (notification->notif_data.early_trigger) {
+ petc_if_display_suspend(drv_data);
+ }
+ break;
+ default:
+ break;
+ }
+
+ return;
+}
+
+static int petc_if_register_panel_notifier(struct petc_if_drv_data *drv_data)
+{
+ struct device_node *np = drv_data->pdev->dev.of_node;
+ struct device_node *pnode;
+ struct drm_panel *panel;
+ void *cookie = NULL;
+ int i, count, ret;
+
+ count = of_count_phandle_with_args(np, "display-panels", NULL);
+ if (count <= 0) {
+ pr_err("[PETC_IF] failed to find display-panels, count=%d\n",
+ count);
+ return -ENODEV;
+ }
+
+ for (i = 0; i < count; i++) {
+ pnode = of_parse_phandle(np, "display-panels", i);
+ if (!pnode) {
+ pr_err("[PETC_IF] failed to parse display-panels[%d]\n",
+ i);
+ return -ENODEV;
+ }
+
+ panel = of_drm_find_panel(pnode);
+ of_node_put(pnode);
+ if (!IS_ERR(panel)) {
+ drv_data->active_panel = panel;
+ break;
+ }
+ }
+
+ if (!drv_data->active_panel) {
+ pr_err("[PETC_IF] failed to find active panel\n");
+ return -ENODEV;
+ }
+
+#if defined(CONFIG_DRM_PANEL)
+#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 10, 0)
+#error "Registration with drm_panel_notifier_register not implemented"
+#else
+#if IS_ENABLED(CONFIG_QCOM_PANEL_EVENT_NOTIFIER)
+ cookie = panel_event_notifier_register(
+ PANEL_EVENT_NOTIFICATION_PRIMARY,
+ PANEL_EVENT_NOTIFIER_CLIENT_INPUT_FILTER, drv_data->active_panel,
+ &petc_if_drm_panel_notifier_callback, drv_data);
+ if (IS_ERR(cookie)) {
+ ret = PTR_ERR(cookie);
+ pr_err("[PETC_IF] failed to register panel event notifier, ret=%d\n",
+ ret);
+ return ret;
+ }
+#endif
+#endif
+#elif defined(_MSM_DRM_NOTIFY_H_)
+#error "Registration with msm_drm_register_client not implemented"
+#elif defined(CONFIG_FB)
+#error "Registration with fb_register_client not implemented"
+#elif defined(CONFIG_HAS_EARLYSUSPEND)
+#error "Registration with register_early_suspend not implemented"
+#else
+#error "Unknown display panel notification registration method"
+#endif
+
+ pr_debug("[PETC_IF] register panel notifier successful\n");
+ drv_data->notifier_cookie = cookie;
+ return 0;
+}
+
+static void petc_if_unregister_panel_notifier(struct petc_if_drv_data *drv_data)
+{
+ if (drv_data->notifier_cookie)
+ panel_event_notifier_unregister(drv_data->notifier_cookie);
+}
+
+static int petc_if_probe(struct platform_device *pdev)
+{
+ struct petc_if_drv_data *drv_data;
+ int ret;
+
+ pr_debug("[PETC_IF] loading driver\n");
+
+ drv_data = kzalloc(sizeof(struct petc_if_drv_data), GFP_KERNEL);
+ if (!drv_data)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, drv_data);
+ drv_data->pdev = pdev;
+
+ spin_lock_init(&drv_data->device_list_slock);
+ INIT_LIST_HEAD(&drv_data->device_list);
+ INIT_LIST_HEAD(&drv_data->removed_device_list);
+ INIT_WORK(&drv_data->remove_devices_work,
+ petc_if_remove_devices_work_func);
+
+ ret = petc_if_register_panel_notifier(drv_data);
+ if (ret) {
+ pr_err("[PETC_IF] failed to register panel notifier, errno:%d\n",
+ ret);
+ goto err_free;
+ }
+
+ petc_if_handler.private = drv_data;
+
+ ret = input_register_handler(&petc_if_handler);
+ if (ret) {
+ pr_err("[PETC_IF] failed to register input handler, errno:%d\n",
+ ret);
+ petc_if_handler.private = NULL;
+ goto err_unregister_notifier;
+ }
+
+ return 0;
+
+err_unregister_notifier:
+ petc_if_unregister_panel_notifier(drv_data);
+err_free:
+ kfree(drv_data);
+ return ret;
+}
+
+static int petc_if_remove(struct platform_device *pdev)
+{
+ struct petc_if_drv_data *drv_data = platform_get_drvdata(pdev);
+ petc_if_handler.private = NULL;
+
+ pr_debug("[PETC_IF] unloading driver\n");
+
+ input_unregister_handler(&petc_if_handler);
+ petc_if_unregister_panel_notifier(drv_data);
+ flush_work(&drv_data->remove_devices_work);
+ kfree(drv_data);
+
+ return 0;
+}
+
+static const struct of_device_id petc_if_dt_match[] = {
+ { .compatible = "google,petc_if" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, petc_if_match);
+
+static struct platform_driver petc_if_driver = {
+ .driver = {
+ .name = PETC_DEV_NAME,
+ .of_match_table = of_match_ptr(petc_if_dt_match),
+ },
+ .probe = petc_if_probe,
+ .remove = petc_if_remove};
+
+module_platform_driver(petc_if_driver);
+
+MODULE_AUTHOR("Google");
+MODULE_DESCRIPTION("Google PETC input filter driver");
+MODULE_LICENSE("GPL");
diff --git a/petc_input_filter/petc_input_filter.h b/petc_input_filter/petc_input_filter.h
new file mode 100644
index 0000000..56a4694
--- /dev/null
+++ b/petc_input_filter/petc_input_filter.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2023 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef _PETC_INPUT_FILTER_H_
+#define _PETC_INPUT_FILTER_H_
+
+#define PETC_DEV_NAME "petc"
+#define PETC_HANDLE_NAME "petc-if"
+
+#endif