1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Driver for simulating a mouse on GPIO lines. |
4 | * |
5 | * Copyright (C) 2007 Atmel Corporation |
6 | * Copyright (C) 2017 Linus Walleij <linus.walleij@linaro.org> |
7 | */ |
8 | |
9 | #include <linux/module.h> |
10 | #include <linux/platform_device.h> |
11 | #include <linux/input.h> |
12 | #include <linux/gpio/consumer.h> |
13 | #include <linux/property.h> |
14 | #include <linux/of.h> |
15 | |
16 | /** |
17 | * struct gpio_mouse |
18 | * @scan_ms: the scan interval in milliseconds. |
19 | * @up: GPIO line for up value. |
20 | * @down: GPIO line for down value. |
21 | * @left: GPIO line for left value. |
22 | * @right: GPIO line for right value. |
23 | * @bleft: GPIO line for left button. |
24 | * @bmiddle: GPIO line for middle button. |
25 | * @bright: GPIO line for right button. |
26 | * |
27 | * This struct must be added to the platform_device in the board code. |
28 | * It is used by the gpio_mouse driver to setup GPIO lines and to |
29 | * calculate mouse movement. |
30 | */ |
31 | struct gpio_mouse { |
32 | u32 scan_ms; |
33 | struct gpio_desc *up; |
34 | struct gpio_desc *down; |
35 | struct gpio_desc *left; |
36 | struct gpio_desc *right; |
37 | struct gpio_desc *bleft; |
38 | struct gpio_desc *bmiddle; |
39 | struct gpio_desc *bright; |
40 | }; |
41 | |
42 | /* |
43 | * Timer function which is run every scan_ms ms when the device is opened. |
44 | * The dev input variable is set to the input_dev pointer. |
45 | */ |
46 | static void gpio_mouse_scan(struct input_dev *input) |
47 | { |
48 | struct gpio_mouse *gpio = input_get_drvdata(dev: input); |
49 | int x, y; |
50 | |
51 | if (gpio->bleft) |
52 | input_report_key(dev: input, BTN_LEFT, |
53 | value: gpiod_get_value(desc: gpio->bleft)); |
54 | if (gpio->bmiddle) |
55 | input_report_key(dev: input, BTN_MIDDLE, |
56 | value: gpiod_get_value(desc: gpio->bmiddle)); |
57 | if (gpio->bright) |
58 | input_report_key(dev: input, BTN_RIGHT, |
59 | value: gpiod_get_value(desc: gpio->bright)); |
60 | |
61 | x = gpiod_get_value(desc: gpio->right) - gpiod_get_value(desc: gpio->left); |
62 | y = gpiod_get_value(desc: gpio->down) - gpiod_get_value(desc: gpio->up); |
63 | |
64 | input_report_rel(dev: input, REL_X, value: x); |
65 | input_report_rel(dev: input, REL_Y, value: y); |
66 | input_sync(dev: input); |
67 | } |
68 | |
69 | static int gpio_mouse_probe(struct platform_device *pdev) |
70 | { |
71 | struct device *dev = &pdev->dev; |
72 | struct gpio_mouse *gmouse; |
73 | struct input_dev *input; |
74 | int error; |
75 | |
76 | gmouse = devm_kzalloc(dev, size: sizeof(*gmouse), GFP_KERNEL); |
77 | if (!gmouse) |
78 | return -ENOMEM; |
79 | |
80 | /* Assign some default scanning time */ |
81 | error = device_property_read_u32(dev, propname: "scan-interval-ms" , |
82 | val: &gmouse->scan_ms); |
83 | if (error || gmouse->scan_ms == 0) { |
84 | dev_warn(dev, "invalid scan time, set to 50 ms\n" ); |
85 | gmouse->scan_ms = 50; |
86 | } |
87 | |
88 | gmouse->up = devm_gpiod_get(dev, con_id: "up" , flags: GPIOD_IN); |
89 | if (IS_ERR(ptr: gmouse->up)) |
90 | return PTR_ERR(ptr: gmouse->up); |
91 | gmouse->down = devm_gpiod_get(dev, con_id: "down" , flags: GPIOD_IN); |
92 | if (IS_ERR(ptr: gmouse->down)) |
93 | return PTR_ERR(ptr: gmouse->down); |
94 | gmouse->left = devm_gpiod_get(dev, con_id: "left" , flags: GPIOD_IN); |
95 | if (IS_ERR(ptr: gmouse->left)) |
96 | return PTR_ERR(ptr: gmouse->left); |
97 | gmouse->right = devm_gpiod_get(dev, con_id: "right" , flags: GPIOD_IN); |
98 | if (IS_ERR(ptr: gmouse->right)) |
99 | return PTR_ERR(ptr: gmouse->right); |
100 | |
101 | gmouse->bleft = devm_gpiod_get_optional(dev, con_id: "button-left" , flags: GPIOD_IN); |
102 | if (IS_ERR(ptr: gmouse->bleft)) |
103 | return PTR_ERR(ptr: gmouse->bleft); |
104 | gmouse->bmiddle = devm_gpiod_get_optional(dev, con_id: "button-middle" , |
105 | flags: GPIOD_IN); |
106 | if (IS_ERR(ptr: gmouse->bmiddle)) |
107 | return PTR_ERR(ptr: gmouse->bmiddle); |
108 | gmouse->bright = devm_gpiod_get_optional(dev, con_id: "button-right" , |
109 | flags: GPIOD_IN); |
110 | if (IS_ERR(ptr: gmouse->bright)) |
111 | return PTR_ERR(ptr: gmouse->bright); |
112 | |
113 | input = devm_input_allocate_device(dev); |
114 | if (!input) |
115 | return -ENOMEM; |
116 | |
117 | input->name = pdev->name; |
118 | input->id.bustype = BUS_HOST; |
119 | |
120 | input_set_drvdata(dev: input, data: gmouse); |
121 | |
122 | input_set_capability(dev: input, EV_REL, REL_X); |
123 | input_set_capability(dev: input, EV_REL, REL_Y); |
124 | if (gmouse->bleft) |
125 | input_set_capability(dev: input, EV_KEY, BTN_LEFT); |
126 | if (gmouse->bmiddle) |
127 | input_set_capability(dev: input, EV_KEY, BTN_MIDDLE); |
128 | if (gmouse->bright) |
129 | input_set_capability(dev: input, EV_KEY, BTN_RIGHT); |
130 | |
131 | error = input_setup_polling(dev: input, poll_fn: gpio_mouse_scan); |
132 | if (error) |
133 | return error; |
134 | |
135 | input_set_poll_interval(dev: input, interval: gmouse->scan_ms); |
136 | |
137 | error = input_register_device(input); |
138 | if (error) { |
139 | dev_err(dev, "could not register input device\n" ); |
140 | return error; |
141 | } |
142 | |
143 | dev_dbg(dev, "%d ms scan time, buttons: %s%s%s\n" , |
144 | gmouse->scan_ms, |
145 | gmouse->bleft ? "" : "left " , |
146 | gmouse->bmiddle ? "" : "middle " , |
147 | gmouse->bright ? "" : "right" ); |
148 | |
149 | return 0; |
150 | } |
151 | |
152 | static const struct of_device_id gpio_mouse_of_match[] = { |
153 | { .compatible = "gpio-mouse" , }, |
154 | { }, |
155 | }; |
156 | MODULE_DEVICE_TABLE(of, gpio_mouse_of_match); |
157 | |
158 | static struct platform_driver gpio_mouse_device_driver = { |
159 | .probe = gpio_mouse_probe, |
160 | .driver = { |
161 | .name = "gpio_mouse" , |
162 | .of_match_table = gpio_mouse_of_match, |
163 | } |
164 | }; |
165 | module_platform_driver(gpio_mouse_device_driver); |
166 | |
167 | MODULE_AUTHOR("Hans-Christian Egtvedt <egtvedt@samfundet.no>" ); |
168 | MODULE_DESCRIPTION("GPIO mouse driver" ); |
169 | MODULE_LICENSE("GPL" ); |
170 | MODULE_ALIAS("platform:gpio_mouse" ); /* work with hotplug and coldplug */ |
171 | |