| /* |
| * 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"); |