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