| /* drivers/input/keyreset.c |
| * |
| * Copyright (C) 2014 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/input.h> |
| #include <linux/keyreset.h> |
| #include <linux/module.h> |
| #include <linux/platform_device.h> |
| #include <linux/reboot.h> |
| #include <linux/sched.h> |
| #include <linux/slab.h> |
| #include <linux/syscalls.h> |
| #include <linux/keycombo.h> |
| |
| struct keyreset_state { |
| int restart_requested; |
| int (*reset_fn)(void); |
| struct platform_device *pdev_child; |
| struct work_struct restart_work; |
| }; |
| |
| static void do_restart(struct work_struct *unused) |
| { |
| orderly_reboot(); |
| } |
| |
| static void do_reset_fn(void *priv) |
| { |
| struct keyreset_state *state = priv; |
| if (state->restart_requested) |
| panic("keyboard reset failed, %d", state->restart_requested); |
| if (state->reset_fn) { |
| state->restart_requested = state->reset_fn(); |
| } else { |
| pr_info("keyboard reset\n"); |
| schedule_work(&state->restart_work); |
| state->restart_requested = 1; |
| } |
| } |
| |
| static int keyreset_probe(struct platform_device *pdev) |
| { |
| int ret = -ENOMEM; |
| struct keycombo_platform_data *pdata_child; |
| struct keyreset_platform_data *pdata = pdev->dev.platform_data; |
| int up_size = 0, down_size = 0, size; |
| int key, *keyp; |
| struct keyreset_state *state; |
| |
| if (!pdata) |
| return -EINVAL; |
| state = devm_kzalloc(&pdev->dev, sizeof(*state), GFP_KERNEL); |
| if (!state) |
| return -ENOMEM; |
| |
| state->pdev_child = platform_device_alloc(KEYCOMBO_NAME, |
| PLATFORM_DEVID_AUTO); |
| if (!state->pdev_child) |
| return -ENOMEM; |
| state->pdev_child->dev.parent = &pdev->dev; |
| INIT_WORK(&state->restart_work, do_restart); |
| |
| keyp = pdata->keys_down; |
| while ((key = *keyp++)) { |
| if (key >= KEY_MAX) |
| continue; |
| down_size++; |
| } |
| if (pdata->keys_up) { |
| keyp = pdata->keys_up; |
| while ((key = *keyp++)) { |
| if (key >= KEY_MAX) |
| continue; |
| up_size++; |
| } |
| } |
| size = sizeof(struct keycombo_platform_data) |
| + sizeof(int) * (down_size + 1); |
| pdata_child = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); |
| if (!pdata_child) |
| goto error; |
| memcpy(pdata_child->keys_down, pdata->keys_down, |
| sizeof(int) * down_size); |
| if (up_size > 0) { |
| pdata_child->keys_up = devm_kzalloc(&pdev->dev, up_size + 1, |
| GFP_KERNEL); |
| if (!pdata_child->keys_up) |
| goto error; |
| memcpy(pdata_child->keys_up, pdata->keys_up, |
| sizeof(int) * up_size); |
| if (!pdata_child->keys_up) |
| goto error; |
| } |
| state->reset_fn = pdata->reset_fn; |
| pdata_child->key_down_fn = do_reset_fn; |
| pdata_child->priv = state; |
| pdata_child->key_down_delay = pdata->key_down_delay; |
| ret = platform_device_add_data(state->pdev_child, pdata_child, size); |
| if (ret) |
| goto error; |
| platform_set_drvdata(pdev, state); |
| return platform_device_add(state->pdev_child); |
| error: |
| platform_device_put(state->pdev_child); |
| return ret; |
| } |
| |
| int keyreset_remove(struct platform_device *pdev) |
| { |
| struct keyreset_state *state = platform_get_drvdata(pdev); |
| platform_device_put(state->pdev_child); |
| return 0; |
| } |
| |
| |
| struct platform_driver keyreset_driver = { |
| .driver.name = KEYRESET_NAME, |
| .probe = keyreset_probe, |
| .remove = keyreset_remove, |
| }; |
| |
| static int __init keyreset_init(void) |
| { |
| return platform_driver_register(&keyreset_driver); |
| } |
| |
| static void __exit keyreset_exit(void) |
| { |
| return platform_driver_unregister(&keyreset_driver); |
| } |
| |
| module_init(keyreset_init); |
| module_exit(keyreset_exit); |