blob: 7ec410123fff29722e865650a6627a6cfcdef769 [file] [log] [blame]
/*
* 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)
#define PETC_PREFIX "petc_"
struct petc_input_event {
struct list_head node;
ktime_t timestamp;
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;
char *clone_name;
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;
struct input_dev *key_dev;
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_send_key(ktime_t timestamp,
struct petc_if_dev_data *dev_data,
uint32_t keycode)
{
struct input_dev *key_dev = dev_data->drv_data->key_dev;
input_set_timestamp(key_dev, timestamp);
input_report_key(key_dev, keycode, 1);
input_sync(key_dev);
input_set_timestamp(key_dev, timestamp);
input_report_key(key_dev, keycode, 0);
input_sync(key_dev);
}
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,
ktime_t timestamp, 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->timestamp = timestamp;
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;
}
struct petc_input_event *
petc_if_peek_first_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);
}
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_set_timestamp(dev_data->clone_dev, event->timestamp);
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_immediate_work_func(struct work_struct *work)
{
struct petc_input_event *event;
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");
event = petc_if_peek_first_queued_event(dev_data);
if (event == NULL) {
pr_warn("[PETC_IF] event queue unexpectedly empty\n");
return;
}
if (event->type == EV_REL && event->code == REL_WHEEL) {
if (event->value > 0) {
petc_if_send_key(event->timestamp, dev_data,
KEY_DOWN);
} else if (event->value < 0) {
petc_if_send_key(event->timestamp, dev_data,
KEY_UP);
}
petc_if_get_queued_event(dev_data);
} else {
petc_if_send_key(event->timestamp, dev_data,
KEY_WAKEUP);
}
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,
ktime_t timestamp, 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_set_timestamp(dev_data->clone_dev, timestamp);
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, timestamp, 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, timestamp, 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->clone_name);
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->clone_name);
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;
}
return petc_if_handle_input(dev_data,
handle->dev->timestamp[INPUT_CLK_MONO],
type, code, value);
}
static bool petc_if_match(struct input_handler *handler, struct input_dev *dev)
{
struct petc_if_drv_data *drv_data =
(struct petc_if_drv_data *)handler->private;
if (dev == drv_data->key_dev) {
/* Don't match on petc key dev */
return false;
}
if (dev->id.bustype == BUS_VIRTUAL) {
/* Don't match on the clone */
return false;
}
if (dev->id.bustype != 0) {
/* TODO: b/299198389
*
* We were matching a PnP device and filtering when we shouldn't have.
* This is a temporary workaround since the devices we do want are
* bustype 0. This should be cleaned up with device tree entries to be
* more robust and cleaner.
*/
return false;
}
if (test_bit(EV_ABS, dev->evbit)) {
/* Don't match on a touch device */
return false;
}
return true;
}
static int petc_if_open_clone(struct input_dev *dev) {
struct input_handle *handle =
(struct input_handle *) input_get_drvdata(dev);
int error;
pr_info("[PETC_IF]: Opening clone %s\n", dev->name);
error = input_open_device(handle);
if (error) {
pr_err("[PETC_IF] Failed to open input device, error %d\n",
error);
return error;
}
return 0;
}
static void petc_if_close_clone(struct input_dev *dev) {
struct input_handle *handle =
(struct input_handle *) input_get_drvdata(dev);
pr_info("[PETC_IF]: Closing clone %s\n", dev->name);
input_close_device(handle);
}
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;
size_t clone_name_size;
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_name_size = strlen(dev->name) + sizeof(PETC_PREFIX);
dev_data->clone_name = kzalloc(clone_name_size, GFP_KERNEL);
if (!dev_data->clone_name) {
pr_err("[PETC_IF] Failed to allocate clone_name\n");
error = -ENOMEM;
goto err_free_dev_data;
}
strncpy(dev_data->clone_name, PETC_PREFIX, clone_name_size);
strlcat(dev_data->clone_name, dev->name, clone_name_size);
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_name;
}
clone_dev->name = dev_data->clone_name;
clone_dev->id.bustype = BUS_VIRTUAL;
clone_dev->uniq = dev->uniq;
clone_dev->phys = dev->phys;
clone_dev->open = petc_if_open_clone;
clone_dev->close = petc_if_close_clone;
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));
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;
}
input_set_drvdata(clone_dev, &dev_data->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_free_device:
input_free_device(clone_dev);
err_free_clone_name:
kfree(dev_data->clone_name);
err_free_dev_data:
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_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 */
{
.flags = INPUT_DEVICE_ID_MATCH_EVBIT |
INPUT_DEVICE_ID_MATCH_RELBIT,
.evbit = { [BIT_WORD(EV_REL)] = BIT_MASK(EV_REL) },
.relbit = { [BIT_WORD(REL_WHEEL)] = BIT_MASK(REL_WHEEL) },
}, /* match on scroll wheels */
{}, /* 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_create_key_dev(struct petc_if_drv_data *drv_data)
{
struct input_dev *key_dev;
int error;
key_dev = input_allocate_device();
if (key_dev == NULL) {
pr_err("[PETC_IF] Failed to allocate key_dev\n");
return -ENOMEM;
}
key_dev->name = "petc";
key_dev->phys = "input/petc";
input_set_capability(key_dev, EV_KEY, KEY_WAKEUP);
input_set_capability(key_dev, EV_KEY, KEY_UP);
input_set_capability(key_dev, EV_KEY, KEY_DOWN);
error = input_register_device(key_dev);
if (error) {
pr_err("[PETC_IF] Failed to register key_dev, error %d\n",
error);
input_free_device(key_dev);
return error;
}
drv_data->key_dev = key_dev;
return 0;
}
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;
}
ret = petc_if_create_key_dev(drv_data);
if (ret) {
pr_err("[PETC_IF] failed to create key dev, errno:%d\n", ret);
goto err_unregister_notifier;
}
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_free_key_dev;
}
return 0;
err_free_key_dev:
input_unregister_device(drv_data->key_dev);
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);
input_unregister_device(drv_data->key_dev);
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");