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
29static int i8042_kbd_irq;
30static 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
40static int i8042_command_reg = 0x64;
41static int i8042_data_reg = 0x60;
42
43
44static inline int i8042_read_data(void)
45{
46	return inb(I8042_DATA_REG);
47}
48
49static inline int i8042_read_status(void)
50{
51	return inb(I8042_STATUS_REG);
52}
53
54static inline void i8042_write_data(int val)
55{
56	outb(val, I8042_DATA_REG);
57}
58
59static 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
67static bool i8042_pnp_kbd_registered;
68static unsigned int i8042_pnp_kbd_devices;
69static bool i8042_pnp_aux_registered;
70static unsigned int i8042_pnp_aux_devices;
71
72static int i8042_pnp_command_reg;
73static int i8042_pnp_data_reg;
74static int i8042_pnp_kbd_irq;
75static int i8042_pnp_aux_irq;
76
77static char i8042_pnp_kbd_name[32];
78static char i8042_pnp_aux_name[32];
79
80static 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
91static 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
119static 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
144static 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};
162MODULE_DEVICE_TABLE(pnp, pnp_kbd_devids);
163
164static 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
174static 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};
188MODULE_DEVICE_TABLE(pnp, pnp_aux_devids);
189
190static 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
200static 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
215static 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 */
307static inline int i8042_pnp_init(void) { return 0; }
308static inline void i8042_pnp_exit(void) { }
309#endif /* CONFIG_PNP */
310
311static 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
325static inline void i8042_platform_exit(void)
326{
327	i8042_pnp_exit();
328}
329
330#endif /* _I8042_LOONGSONIO_H */
331