1 /* SPDX-License-Identifier: GPL-2.0-only */
2 /*
3 * i8042-loongsonio.h
4 *
5 * Copyright (C) 2020 Loongson Technology Corporation Limited
6 * Author: Jianmin Lv <lvjianmin@loongson.cn>
7 * Huacai Chen <chenhuacai@loongson.cn>
8 */
9
10 #ifndef _I8042_LOONGSONIO_H
11 #define _I8042_LOONGSONIO_H
12
13 /*
14 * Names.
15 */
16
17 #define I8042_KBD_PHYS_DESC "isa0060/serio0"
18 #define I8042_AUX_PHYS_DESC "isa0060/serio1"
19 #define I8042_MUX_PHYS_DESC "isa0060/serio%d"
20
21 /*
22 * IRQs.
23 */
24 #define I8042_MAP_IRQ(x) (x)
25
26 #define I8042_KBD_IRQ i8042_kbd_irq
27 #define I8042_AUX_IRQ i8042_aux_irq
28
29 static int i8042_kbd_irq;
30 static int i8042_aux_irq;
31
32 /*
33 * Register numbers.
34 */
35
36 #define I8042_COMMAND_REG i8042_command_reg
37 #define I8042_STATUS_REG i8042_command_reg
38 #define I8042_DATA_REG i8042_data_reg
39
40 static int i8042_command_reg = 0x64;
41 static int i8042_data_reg = 0x60;
42
43
i8042_read_data(void)44 static inline int i8042_read_data(void)
45 {
46 return inb(I8042_DATA_REG);
47 }
48
i8042_read_status(void)49 static inline int i8042_read_status(void)
50 {
51 return inb(I8042_STATUS_REG);
52 }
53
i8042_write_data(int val)54 static inline void i8042_write_data(int val)
55 {
56 outb(val, I8042_DATA_REG);
57 }
58
i8042_write_command(int val)59 static inline void i8042_write_command(int val)
60 {
61 outb(val, I8042_COMMAND_REG);
62 }
63
64 #ifdef CONFIG_PNP
65 #include <linux/pnp.h>
66
67 static bool i8042_pnp_kbd_registered;
68 static unsigned int i8042_pnp_kbd_devices;
69 static bool i8042_pnp_aux_registered;
70 static unsigned int i8042_pnp_aux_devices;
71
72 static int i8042_pnp_command_reg;
73 static int i8042_pnp_data_reg;
74 static int i8042_pnp_kbd_irq;
75 static int i8042_pnp_aux_irq;
76
77 static char i8042_pnp_kbd_name[32];
78 static char i8042_pnp_aux_name[32];
79
i8042_pnp_id_to_string(struct pnp_id *id, char *dst, int dst_size)80 static void i8042_pnp_id_to_string(struct pnp_id *id, char *dst, int dst_size)
81 {
82 strlcpy(dst, "PNP:", dst_size);
83
84 while (id) {
85 strlcat(dst, " ", dst_size);
86 strlcat(dst, id->id, dst_size);
87 id = id->next;
88 }
89 }
90
i8042_pnp_kbd_probe(struct pnp_dev *dev, const struct pnp_device_id *did)91 static int i8042_pnp_kbd_probe(struct pnp_dev *dev,
92 const struct pnp_device_id *did)
93 {
94 if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1)
95 i8042_pnp_data_reg = pnp_port_start(dev, 0);
96
97 if (pnp_port_valid(dev, 1) && pnp_port_len(dev, 1) == 1)
98 i8042_pnp_command_reg = pnp_port_start(dev, 1);
99
100 if (pnp_irq_valid(dev, 0))
101 i8042_pnp_kbd_irq = pnp_irq(dev, 0);
102
103 strlcpy(i8042_pnp_kbd_name, did->id, sizeof(i8042_pnp_kbd_name));
104 if (strlen(pnp_dev_name(dev))) {
105 strlcat(i8042_pnp_kbd_name, ":", sizeof(i8042_pnp_kbd_name));
106 strlcat(i8042_pnp_kbd_name, pnp_dev_name(dev),
107 sizeof(i8042_pnp_kbd_name));
108 }
109 i8042_pnp_id_to_string(dev->id, i8042_kbd_firmware_id,
110 sizeof(i8042_kbd_firmware_id));
111
112 /* Keyboard ports are always supposed to be wakeup-enabled */
113 device_set_wakeup_enable(&dev->dev, true);
114
115 i8042_pnp_kbd_devices++;
116 return 0;
117 }
118
i8042_pnp_aux_probe(struct pnp_dev *dev, const struct pnp_device_id *did)119 static int i8042_pnp_aux_probe(struct pnp_dev *dev,
120 const struct pnp_device_id *did)
121 {
122 if (pnp_port_valid(dev, 0) && pnp_port_len(dev, 0) == 1)
123 i8042_pnp_data_reg = pnp_port_start(dev, 0);
124
125 if (pnp_port_valid(dev, 1) && pnp_port_len(dev, 1) == 1)
126 i8042_pnp_command_reg = pnp_port_start(dev, 1);
127
128 if (pnp_irq_valid(dev, 0))
129 i8042_pnp_aux_irq = pnp_irq(dev, 0);
130
131 strlcpy(i8042_pnp_aux_name, did->id, sizeof(i8042_pnp_aux_name));
132 if (strlen(pnp_dev_name(dev))) {
133 strlcat(i8042_pnp_aux_name, ":", sizeof(i8042_pnp_aux_name));
134 strlcat(i8042_pnp_aux_name, pnp_dev_name(dev),
135 sizeof(i8042_pnp_aux_name));
136 }
137 i8042_pnp_id_to_string(dev->id, i8042_aux_firmware_id,
138 sizeof(i8042_aux_firmware_id));
139
140 i8042_pnp_aux_devices++;
141 return 0;
142 }
143
144 static const struct pnp_device_id pnp_kbd_devids[] = {
145 { .id = "PNP0300", .driver_data = 0 },
146 { .id = "PNP0301", .driver_data = 0 },
147 { .id = "PNP0302", .driver_data = 0 },
148 { .id = "PNP0303", .driver_data = 0 },
149 { .id = "PNP0304", .driver_data = 0 },
150 { .id = "PNP0305", .driver_data = 0 },
151 { .id = "PNP0306", .driver_data = 0 },
152 { .id = "PNP0309", .driver_data = 0 },
153 { .id = "PNP030a", .driver_data = 0 },
154 { .id = "PNP030b", .driver_data = 0 },
155 { .id = "PNP0320", .driver_data = 0 },
156 { .id = "PNP0343", .driver_data = 0 },
157 { .id = "PNP0344", .driver_data = 0 },
158 { .id = "PNP0345", .driver_data = 0 },
159 { .id = "CPQA0D7", .driver_data = 0 },
160 { .id = "", },
161 };
162 MODULE_DEVICE_TABLE(pnp, pnp_kbd_devids);
163
164 static struct pnp_driver i8042_pnp_kbd_driver = {
165 .name = "i8042 kbd",
166 .id_table = pnp_kbd_devids,
167 .probe = i8042_pnp_kbd_probe,
168 .driver = {
169 .probe_type = PROBE_FORCE_SYNCHRONOUS,
170 .suppress_bind_attrs = true,
171 },
172 };
173
174 static const struct pnp_device_id pnp_aux_devids[] = {
175 { .id = "AUI0200", .driver_data = 0 },
176 { .id = "FJC6000", .driver_data = 0 },
177 { .id = "FJC6001", .driver_data = 0 },
178 { .id = "PNP0f03", .driver_data = 0 },
179 { .id = "PNP0f0b", .driver_data = 0 },
180 { .id = "PNP0f0e", .driver_data = 0 },
181 { .id = "PNP0f12", .driver_data = 0 },
182 { .id = "PNP0f13", .driver_data = 0 },
183 { .id = "PNP0f19", .driver_data = 0 },
184 { .id = "PNP0f1c", .driver_data = 0 },
185 { .id = "SYN0801", .driver_data = 0 },
186 { .id = "", },
187 };
188 MODULE_DEVICE_TABLE(pnp, pnp_aux_devids);
189
190 static struct pnp_driver i8042_pnp_aux_driver = {
191 .name = "i8042 aux",
192 .id_table = pnp_aux_devids,
193 .probe = i8042_pnp_aux_probe,
194 .driver = {
195 .probe_type = PROBE_FORCE_SYNCHRONOUS,
196 .suppress_bind_attrs = true,
197 },
198 };
199
i8042_pnp_exit(void)200 static void i8042_pnp_exit(void)
201 {
202 if (i8042_pnp_kbd_registered) {
203 i8042_pnp_kbd_registered = false;
204 pnp_unregister_driver(&i8042_pnp_kbd_driver);
205 }
206
207 if (i8042_pnp_aux_registered) {
208 i8042_pnp_aux_registered = false;
209 pnp_unregister_driver(&i8042_pnp_aux_driver);
210 }
211 }
212 #ifdef CONFIG_ACPI
213 #include <linux/acpi.h>
214 #endif
i8042_pnp_init(void)215 static int __init i8042_pnp_init(void)
216 {
217 char kbd_irq_str[4] = { 0 }, aux_irq_str[4] = { 0 };
218 bool pnp_data_busted = false;
219 int err;
220
221 if (i8042_nopnp) {
222 pr_info("PNP detection disabled\n");
223 return 0;
224 }
225
226 err = pnp_register_driver(&i8042_pnp_kbd_driver);
227 if (!err)
228 i8042_pnp_kbd_registered = true;
229
230 err = pnp_register_driver(&i8042_pnp_aux_driver);
231 if (!err)
232 i8042_pnp_aux_registered = true;
233
234 if (!i8042_pnp_kbd_devices && !i8042_pnp_aux_devices) {
235 i8042_pnp_exit();
236 pr_info("PNP: No PS/2 controller found.\n");
237 #ifdef CONFIG_ACPI
238 if (acpi_disabled == 0)
239 return -ENODEV;
240 #endif
241 pr_info("Probing ports directly.\n");
242 return 0;
243 }
244
245 if (i8042_pnp_kbd_devices)
246 snprintf(kbd_irq_str, sizeof(kbd_irq_str),
247 "%d", i8042_pnp_kbd_irq);
248 if (i8042_pnp_aux_devices)
249 snprintf(aux_irq_str, sizeof(aux_irq_str),
250 "%d", i8042_pnp_aux_irq);
251
252 pr_info("PNP: PS/2 Controller [%s%s%s] at %#x,%#x irq %s%s%s\n",
253 i8042_pnp_kbd_name,
254 (i8042_pnp_kbd_devices && i8042_pnp_aux_devices) ? "," : "",
255 i8042_pnp_aux_name,
256 i8042_pnp_data_reg, i8042_pnp_command_reg,
257 kbd_irq_str,
258 (i8042_pnp_kbd_devices && i8042_pnp_aux_devices) ? "," : "",
259 aux_irq_str);
260
261 if (((i8042_pnp_data_reg & ~0xf) == (i8042_data_reg & ~0xf) &&
262 i8042_pnp_data_reg != i8042_data_reg) ||
263 !i8042_pnp_data_reg) {
264 pr_warn("PNP: PS/2 controller has invalid data port %#x; using default %#x\n",
265 i8042_pnp_data_reg, i8042_data_reg);
266 i8042_pnp_data_reg = i8042_data_reg;
267 pnp_data_busted = true;
268 }
269
270 if (((i8042_pnp_command_reg & ~0xf) == (i8042_command_reg & ~0xf) &&
271 i8042_pnp_command_reg != i8042_command_reg) ||
272 !i8042_pnp_command_reg) {
273 pr_warn("PNP: PS/2 controller has invalid command port %#x; using default %#x\n",
274 i8042_pnp_command_reg, i8042_command_reg);
275 i8042_pnp_command_reg = i8042_command_reg;
276 pnp_data_busted = true;
277 }
278
279 if (!i8042_nokbd && !i8042_pnp_kbd_irq) {
280 pr_warn("PNP: PS/2 controller doesn't have KBD irq; using default %d\n",
281 i8042_kbd_irq);
282 i8042_pnp_kbd_irq = i8042_kbd_irq;
283 pnp_data_busted = true;
284 }
285
286 if (!i8042_noaux && !i8042_pnp_aux_irq) {
287 if (!pnp_data_busted && i8042_pnp_kbd_irq) {
288 pr_warn("PNP: PS/2 appears to have AUX port disabled, "
289 "if this is incorrect please boot with i8042.nopnp\n");
290 i8042_noaux = true;
291 } else {
292 pr_warn("PNP: PS/2 controller doesn't have AUX irq; using default %d\n",
293 i8042_aux_irq);
294 i8042_pnp_aux_irq = i8042_aux_irq;
295 }
296 }
297
298 i8042_data_reg = i8042_pnp_data_reg;
299 i8042_command_reg = i8042_pnp_command_reg;
300 i8042_kbd_irq = i8042_pnp_kbd_irq;
301 i8042_aux_irq = i8042_pnp_aux_irq;
302
303 return 0;
304 }
305
306 #else /* !CONFIG_PNP */
i8042_pnp_init(void)307 static inline int i8042_pnp_init(void) { return 0; }
i8042_pnp_exit(void)308 static inline void i8042_pnp_exit(void) { }
309 #endif /* CONFIG_PNP */
310
i8042_platform_init(void)311 static int __init i8042_platform_init(void)
312 {
313 int retval;
314
315 i8042_kbd_irq = I8042_MAP_IRQ(1);
316 i8042_aux_irq = I8042_MAP_IRQ(12);
317
318 retval = i8042_pnp_init();
319 if (retval)
320 return retval;
321
322 return retval;
323 }
324
i8042_platform_exit(void)325 static inline void i8042_platform_exit(void)
326 {
327 i8042_pnp_exit();
328 }
329
330 #endif /* _I8042_LOONGSONIO_H */
331