Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 1 | /* drivers/input/keyreset.c |
| 2 | * |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 3 | * Copyright (C) 2014 Google, Inc. |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 4 | * |
| 5 | * This software is licensed under the terms of the GNU General Public |
| 6 | * License version 2, as published by the Free Software Foundation, and |
| 7 | * may be copied, distributed, and modified under those terms. |
| 8 | * |
| 9 | * This program is distributed in the hope that it will be useful, |
| 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | * GNU General Public License for more details. |
| 13 | * |
| 14 | */ |
| 15 | |
| 16 | #include <linux/input.h> |
| 17 | #include <linux/keyreset.h> |
| 18 | #include <linux/module.h> |
| 19 | #include <linux/platform_device.h> |
| 20 | #include <linux/reboot.h> |
| 21 | #include <linux/sched.h> |
| 22 | #include <linux/slab.h> |
| 23 | #include <linux/syscalls.h> |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 24 | #include <linux/keycombo.h> |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 25 | |
| 26 | struct keyreset_state { |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 27 | int restart_requested; |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 28 | int (*reset_fn)(void); |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 29 | struct platform_device *pdev_child; |
Daniel Rosenberg | 12daace | 2014-06-27 16:39:35 -0700 | [diff] [blame] | 30 | struct work_struct restart_work; |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 31 | }; |
| 32 | |
Daniel Rosenberg | 12daace | 2014-06-27 16:39:35 -0700 | [diff] [blame] | 33 | static void do_restart(struct work_struct *unused) |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 34 | { |
Eric Ernst | 8d6d148 | 2016-09-02 16:12:06 -0700 | [diff] [blame] | 35 | orderly_reboot(); |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 36 | } |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 37 | |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 38 | static void do_reset_fn(void *priv) |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 39 | { |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 40 | struct keyreset_state *state = priv; |
| 41 | if (state->restart_requested) |
| 42 | panic("keyboard reset failed, %d", state->restart_requested); |
| 43 | if (state->reset_fn) { |
| 44 | state->restart_requested = state->reset_fn(); |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 45 | } else { |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 46 | pr_info("keyboard reset\n"); |
Daniel Rosenberg | 12daace | 2014-06-27 16:39:35 -0700 | [diff] [blame] | 47 | schedule_work(&state->restart_work); |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 48 | state->restart_requested = 1; |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 49 | } |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 50 | } |
| 51 | |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 52 | static int keyreset_probe(struct platform_device *pdev) |
| 53 | { |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 54 | int ret = -ENOMEM; |
| 55 | struct keycombo_platform_data *pdata_child; |
| 56 | struct keyreset_platform_data *pdata = pdev->dev.platform_data; |
| 57 | int up_size = 0, down_size = 0, size; |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 58 | int key, *keyp; |
| 59 | struct keyreset_state *state; |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 60 | |
| 61 | if (!pdata) |
| 62 | return -EINVAL; |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 63 | state = devm_kzalloc(&pdev->dev, sizeof(*state), GFP_KERNEL); |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 64 | if (!state) |
| 65 | return -ENOMEM; |
| 66 | |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 67 | state->pdev_child = platform_device_alloc(KEYCOMBO_NAME, |
| 68 | PLATFORM_DEVID_AUTO); |
| 69 | if (!state->pdev_child) |
| 70 | return -ENOMEM; |
| 71 | state->pdev_child->dev.parent = &pdev->dev; |
Daniel Rosenberg | 12daace | 2014-06-27 16:39:35 -0700 | [diff] [blame] | 72 | INIT_WORK(&state->restart_work, do_restart); |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 73 | |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 74 | keyp = pdata->keys_down; |
| 75 | while ((key = *keyp++)) { |
| 76 | if (key >= KEY_MAX) |
| 77 | continue; |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 78 | down_size++; |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 79 | } |
| 80 | if (pdata->keys_up) { |
| 81 | keyp = pdata->keys_up; |
| 82 | while ((key = *keyp++)) { |
| 83 | if (key >= KEY_MAX) |
| 84 | continue; |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 85 | up_size++; |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 86 | } |
| 87 | } |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 88 | size = sizeof(struct keycombo_platform_data) |
| 89 | + sizeof(int) * (down_size + 1); |
| 90 | pdata_child = devm_kzalloc(&pdev->dev, size, GFP_KERNEL); |
| 91 | if (!pdata_child) |
| 92 | goto error; |
| 93 | memcpy(pdata_child->keys_down, pdata->keys_down, |
| 94 | sizeof(int) * down_size); |
| 95 | if (up_size > 0) { |
| 96 | pdata_child->keys_up = devm_kzalloc(&pdev->dev, up_size + 1, |
| 97 | GFP_KERNEL); |
| 98 | if (!pdata_child->keys_up) |
| 99 | goto error; |
| 100 | memcpy(pdata_child->keys_up, pdata->keys_up, |
| 101 | sizeof(int) * up_size); |
| 102 | if (!pdata_child->keys_up) |
| 103 | goto error; |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 104 | } |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 105 | state->reset_fn = pdata->reset_fn; |
| 106 | pdata_child->key_down_fn = do_reset_fn; |
| 107 | pdata_child->priv = state; |
| 108 | pdata_child->key_down_delay = pdata->key_down_delay; |
| 109 | ret = platform_device_add_data(state->pdev_child, pdata_child, size); |
| 110 | if (ret) |
| 111 | goto error; |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 112 | platform_set_drvdata(pdev, state); |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 113 | return platform_device_add(state->pdev_child); |
| 114 | error: |
| 115 | platform_device_put(state->pdev_child); |
| 116 | return ret; |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 117 | } |
| 118 | |
| 119 | int keyreset_remove(struct platform_device *pdev) |
| 120 | { |
| 121 | struct keyreset_state *state = platform_get_drvdata(pdev); |
Daniel Rosenberg | 215234c | 2014-05-07 14:17:47 -0700 | [diff] [blame] | 122 | platform_device_put(state->pdev_child); |
Arve Hjønnevåg | 6ca5fa0 | 2008-11-21 21:47:23 -0800 | [diff] [blame] | 123 | return 0; |
| 124 | } |
| 125 | |
| 126 | |
| 127 | struct platform_driver keyreset_driver = { |
| 128 | .driver.name = KEYRESET_NAME, |
| 129 | .probe = keyreset_probe, |
| 130 | .remove = keyreset_remove, |
| 131 | }; |
| 132 | |
| 133 | static int __init keyreset_init(void) |
| 134 | { |
| 135 | return platform_driver_register(&keyreset_driver); |
| 136 | } |
| 137 | |
| 138 | static void __exit keyreset_exit(void) |
| 139 | { |
| 140 | return platform_driver_unregister(&keyreset_driver); |
| 141 | } |
| 142 | |
| 143 | module_init(keyreset_init); |
| 144 | module_exit(keyreset_exit); |