1 | // SPDX-License-Identifier: GPL-2.0 |
2 | // |
3 | // Driver for the Winmate FM07 front-panel keys |
4 | // |
5 | // Author: Daniel Beer <daniel.beer@tirotech.co.nz> |
6 | |
7 | #include <linux/init.h> |
8 | #include <linux/module.h> |
9 | #include <linux/input.h> |
10 | #include <linux/ioport.h> |
11 | #include <linux/platform_device.h> |
12 | #include <linux/dmi.h> |
13 | #include <linux/io.h> |
14 | |
15 | #define DRV_NAME "winmate-fm07keys" |
16 | |
17 | #define PORT_CMD 0x6c |
18 | #define PORT_DATA 0x68 |
19 | |
20 | #define EC_ADDR_KEYS 0x3b |
21 | #define EC_CMD_READ 0x80 |
22 | |
23 | #define BASE_KEY KEY_F13 |
24 | #define NUM_KEYS 5 |
25 | |
26 | /* Typically we're done in fewer than 10 iterations */ |
27 | #define LOOP_TIMEOUT 1000 |
28 | |
29 | static void fm07keys_poll(struct input_dev *input) |
30 | { |
31 | uint8_t k; |
32 | int i; |
33 | |
34 | /* Flush output buffer */ |
35 | i = 0; |
36 | while (inb(PORT_CMD) & 0x01) { |
37 | if (++i >= LOOP_TIMEOUT) |
38 | goto timeout; |
39 | inb(PORT_DATA); |
40 | } |
41 | |
42 | /* Send request and wait for write completion */ |
43 | outb(EC_CMD_READ, PORT_CMD); |
44 | i = 0; |
45 | while (inb(PORT_CMD) & 0x02) |
46 | if (++i >= LOOP_TIMEOUT) |
47 | goto timeout; |
48 | |
49 | outb(EC_ADDR_KEYS, PORT_DATA); |
50 | i = 0; |
51 | while (inb(PORT_CMD) & 0x02) |
52 | if (++i >= LOOP_TIMEOUT) |
53 | goto timeout; |
54 | |
55 | /* Wait for data ready */ |
56 | i = 0; |
57 | while (!(inb(PORT_CMD) & 0x01)) |
58 | if (++i >= LOOP_TIMEOUT) |
59 | goto timeout; |
60 | k = inb(PORT_DATA); |
61 | |
62 | /* Notify of new key states */ |
63 | for (i = 0; i < NUM_KEYS; i++) { |
64 | input_report_key(dev: input, BASE_KEY + i, value: (~k) & 1); |
65 | k >>= 1; |
66 | } |
67 | |
68 | input_sync(dev: input); |
69 | return; |
70 | |
71 | timeout: |
72 | dev_warn_ratelimited(&input->dev, "timeout polling IO memory\n" ); |
73 | } |
74 | |
75 | static int fm07keys_probe(struct platform_device *pdev) |
76 | { |
77 | struct device *dev = &pdev->dev; |
78 | struct input_dev *input; |
79 | int ret; |
80 | int i; |
81 | |
82 | input = devm_input_allocate_device(dev); |
83 | if (!input) { |
84 | dev_err(dev, "no memory for input device\n" ); |
85 | return -ENOMEM; |
86 | } |
87 | |
88 | if (!devm_request_region(dev, PORT_CMD, 1, "Winmate FM07 EC" )) |
89 | return -EBUSY; |
90 | if (!devm_request_region(dev, PORT_DATA, 1, "Winmate FM07 EC" )) |
91 | return -EBUSY; |
92 | |
93 | input->name = "Winmate FM07 front-panel keys" ; |
94 | input->phys = DRV_NAME "/input0" ; |
95 | |
96 | input->id.bustype = BUS_HOST; |
97 | input->id.vendor = 0x0001; |
98 | input->id.product = 0x0001; |
99 | input->id.version = 0x0100; |
100 | |
101 | __set_bit(EV_KEY, input->evbit); |
102 | |
103 | for (i = 0; i < NUM_KEYS; i++) |
104 | __set_bit(BASE_KEY + i, input->keybit); |
105 | |
106 | ret = input_setup_polling(dev: input, poll_fn: fm07keys_poll); |
107 | if (ret) { |
108 | dev_err(dev, "unable to set up polling, err=%d\n" , ret); |
109 | return ret; |
110 | } |
111 | |
112 | /* These are silicone buttons. They can't be pressed in rapid |
113 | * succession too quickly, and 50 Hz seems to be an adequate |
114 | * sampling rate without missing any events when tested. |
115 | */ |
116 | input_set_poll_interval(dev: input, interval: 20); |
117 | |
118 | ret = input_register_device(input); |
119 | if (ret) { |
120 | dev_err(dev, "unable to register polled device, err=%d\n" , |
121 | ret); |
122 | return ret; |
123 | } |
124 | |
125 | input_sync(dev: input); |
126 | return 0; |
127 | } |
128 | |
129 | static struct platform_driver fm07keys_driver = { |
130 | .probe = fm07keys_probe, |
131 | .driver = { |
132 | .name = DRV_NAME |
133 | }, |
134 | }; |
135 | |
136 | static struct platform_device *dev; |
137 | |
138 | static const struct dmi_system_id fm07keys_dmi_table[] __initconst = { |
139 | { |
140 | /* FM07 and FM07P */ |
141 | .matches = { |
142 | DMI_MATCH(DMI_SYS_VENDOR, "Winmate Inc." ), |
143 | DMI_MATCH(DMI_PRODUCT_NAME, "IP30" ), |
144 | }, |
145 | }, |
146 | { } |
147 | }; |
148 | |
149 | MODULE_DEVICE_TABLE(dmi, fm07keys_dmi_table); |
150 | |
151 | static int __init fm07keys_init(void) |
152 | { |
153 | int ret; |
154 | |
155 | if (!dmi_check_system(list: fm07keys_dmi_table)) |
156 | return -ENODEV; |
157 | |
158 | ret = platform_driver_register(&fm07keys_driver); |
159 | if (ret) { |
160 | pr_err("fm07keys: failed to register driver, err=%d\n" , ret); |
161 | return ret; |
162 | } |
163 | |
164 | dev = platform_device_register_simple(DRV_NAME, PLATFORM_DEVID_NONE, NULL, num: 0); |
165 | if (IS_ERR(ptr: dev)) { |
166 | ret = PTR_ERR(ptr: dev); |
167 | pr_err("fm07keys: failed to allocate device, err = %d\n" , ret); |
168 | goto fail_register; |
169 | } |
170 | |
171 | return 0; |
172 | |
173 | fail_register: |
174 | platform_driver_unregister(&fm07keys_driver); |
175 | return ret; |
176 | } |
177 | |
178 | static void __exit fm07keys_exit(void) |
179 | { |
180 | platform_driver_unregister(&fm07keys_driver); |
181 | platform_device_unregister(dev); |
182 | } |
183 | |
184 | module_init(fm07keys_init); |
185 | module_exit(fm07keys_exit); |
186 | |
187 | MODULE_AUTHOR("Daniel Beer <daniel.beer@tirotech.co.nz>" ); |
188 | MODULE_DESCRIPTION("Winmate FM07 front-panel keys driver" ); |
189 | MODULE_LICENSE("GPL" ); |
190 | |