1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Parallel port to Keyboard port adapter driver for Linux |
4 | * |
5 | * Copyright (c) 1999-2004 Vojtech Pavlik |
6 | */ |
7 | |
8 | |
9 | /* |
10 | * To connect an AT or XT keyboard to the parallel port, a fairly simple adapter |
11 | * can be made: |
12 | * |
13 | * Parallel port Keyboard port |
14 | * |
15 | * +5V --------------------- +5V (4) |
16 | * |
17 | * ______ |
18 | * +5V -------|______|--. |
19 | * | |
20 | * ACK (10) ------------| |
21 | * |--- KBD CLOCK (5) |
22 | * STROBE (1) ---|<|----' |
23 | * |
24 | * ______ |
25 | * +5V -------|______|--. |
26 | * | |
27 | * BUSY (11) -----------| |
28 | * |--- KBD DATA (1) |
29 | * AUTOFD (14) --|<|----' |
30 | * |
31 | * GND (18-25) ------------- GND (3) |
32 | * |
33 | * The diodes can be fairly any type, and the resistors should be somewhere |
34 | * around 5 kOhm, but the adapter will likely work without the resistors, |
35 | * too. |
36 | * |
37 | * The +5V source can be taken either from USB, from mouse or keyboard ports, |
38 | * or from a joystick port. Unfortunately, the parallel port of a PC doesn't |
39 | * have a +5V pin, and feeding the keyboard from signal pins is out of question |
40 | * with 300 mA power reqirement of a typical AT keyboard. |
41 | */ |
42 | |
43 | #include <linux/module.h> |
44 | #include <linux/parport.h> |
45 | #include <linux/slab.h> |
46 | #include <linux/init.h> |
47 | #include <linux/serio.h> |
48 | |
49 | MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>" ); |
50 | MODULE_DESCRIPTION("Parallel port to Keyboard port adapter driver" ); |
51 | MODULE_LICENSE("GPL" ); |
52 | |
53 | static unsigned int parkbd_pp_no; |
54 | module_param_named(port, parkbd_pp_no, int, 0); |
55 | MODULE_PARM_DESC(port, "Parallel port the adapter is connected to (default is 0)" ); |
56 | |
57 | static unsigned int parkbd_mode = SERIO_8042; |
58 | module_param_named(mode, parkbd_mode, uint, 0); |
59 | MODULE_PARM_DESC(mode, "Mode of operation: XT = 0/AT = 1 (default)" ); |
60 | |
61 | #define PARKBD_CLOCK 0x01 /* Strobe & Ack */ |
62 | #define PARKBD_DATA 0x02 /* AutoFd & Busy */ |
63 | |
64 | static int parkbd_buffer; |
65 | static int parkbd_counter; |
66 | static unsigned long parkbd_last; |
67 | static int parkbd_writing; |
68 | static unsigned long parkbd_start; |
69 | |
70 | static struct pardevice *parkbd_dev; |
71 | static struct serio *parkbd_port; |
72 | |
73 | static int parkbd_readlines(void) |
74 | { |
75 | return (parport_read_status(parkbd_dev->port) >> 6) ^ 2; |
76 | } |
77 | |
78 | static void parkbd_writelines(int data) |
79 | { |
80 | parport_write_control(parkbd_dev->port, (~data & 3) | 0x10); |
81 | } |
82 | |
83 | static int parkbd_write(struct serio *port, unsigned char c) |
84 | { |
85 | unsigned char p; |
86 | |
87 | if (!parkbd_mode) return -1; |
88 | |
89 | p = c ^ (c >> 4); |
90 | p = p ^ (p >> 2); |
91 | p = p ^ (p >> 1); |
92 | |
93 | parkbd_counter = 0; |
94 | parkbd_writing = 1; |
95 | parkbd_buffer = c | (((int) (~p & 1)) << 8) | 0x600; |
96 | |
97 | parkbd_writelines(data: 2); |
98 | |
99 | return 0; |
100 | } |
101 | |
102 | static void parkbd_interrupt(void *dev_id) |
103 | { |
104 | |
105 | if (parkbd_writing) { |
106 | |
107 | if (parkbd_counter && ((parkbd_counter == 11) || time_after(jiffies, parkbd_last + HZ/100))) { |
108 | parkbd_counter = 0; |
109 | parkbd_buffer = 0; |
110 | parkbd_writing = 0; |
111 | parkbd_writelines(data: 3); |
112 | return; |
113 | } |
114 | |
115 | parkbd_writelines(data: ((parkbd_buffer >> parkbd_counter++) & 1) | 2); |
116 | |
117 | if (parkbd_counter == 11) { |
118 | parkbd_counter = 0; |
119 | parkbd_buffer = 0; |
120 | parkbd_writing = 0; |
121 | parkbd_writelines(data: 3); |
122 | } |
123 | |
124 | } else { |
125 | |
126 | if ((parkbd_counter == parkbd_mode + 10) || time_after(jiffies, parkbd_last + HZ/100)) { |
127 | parkbd_counter = 0; |
128 | parkbd_buffer = 0; |
129 | } |
130 | |
131 | parkbd_buffer |= (parkbd_readlines() >> 1) << parkbd_counter++; |
132 | |
133 | if (parkbd_counter == parkbd_mode + 10) |
134 | serio_interrupt(serio: parkbd_port, data: (parkbd_buffer >> (2 - parkbd_mode)) & 0xff, flags: 0); |
135 | } |
136 | |
137 | parkbd_last = jiffies; |
138 | } |
139 | |
140 | static int parkbd_getport(struct parport *pp) |
141 | { |
142 | struct pardev_cb parkbd_parport_cb; |
143 | |
144 | memset(&parkbd_parport_cb, 0, sizeof(parkbd_parport_cb)); |
145 | parkbd_parport_cb.irq_func = parkbd_interrupt; |
146 | parkbd_parport_cb.flags = PARPORT_FLAG_EXCL; |
147 | |
148 | parkbd_dev = parport_register_dev_model(port: pp, name: "parkbd" , |
149 | par_dev_cb: &parkbd_parport_cb, cnt: 0); |
150 | |
151 | if (!parkbd_dev) |
152 | return -ENODEV; |
153 | |
154 | if (parport_claim(dev: parkbd_dev)) { |
155 | parport_unregister_device(dev: parkbd_dev); |
156 | return -EBUSY; |
157 | } |
158 | |
159 | parkbd_start = jiffies; |
160 | |
161 | return 0; |
162 | } |
163 | |
164 | static struct serio *parkbd_allocate_serio(void) |
165 | { |
166 | struct serio *serio; |
167 | |
168 | serio = kzalloc(size: sizeof(struct serio), GFP_KERNEL); |
169 | if (serio) { |
170 | serio->id.type = parkbd_mode; |
171 | serio->write = parkbd_write; |
172 | strscpy(p: serio->name, q: "PARKBD AT/XT keyboard adapter" , size: sizeof(serio->name)); |
173 | snprintf(buf: serio->phys, size: sizeof(serio->phys), fmt: "%s/serio0" , parkbd_dev->port->name); |
174 | } |
175 | |
176 | return serio; |
177 | } |
178 | |
179 | static void parkbd_attach(struct parport *pp) |
180 | { |
181 | if (pp->number != parkbd_pp_no) { |
182 | pr_debug("Not using parport%d.\n" , pp->number); |
183 | return; |
184 | } |
185 | |
186 | if (parkbd_getport(pp)) |
187 | return; |
188 | |
189 | parkbd_port = parkbd_allocate_serio(); |
190 | if (!parkbd_port) { |
191 | parport_release(dev: parkbd_dev); |
192 | parport_unregister_device(dev: parkbd_dev); |
193 | return; |
194 | } |
195 | |
196 | parkbd_writelines(data: 3); |
197 | |
198 | serio_register_port(parkbd_port); |
199 | |
200 | printk(KERN_INFO "serio: PARKBD %s adapter on %s\n" , |
201 | parkbd_mode ? "AT" : "XT" , parkbd_dev->port->name); |
202 | |
203 | return; |
204 | } |
205 | |
206 | static void parkbd_detach(struct parport *port) |
207 | { |
208 | if (!parkbd_port || port->number != parkbd_pp_no) |
209 | return; |
210 | |
211 | parport_release(dev: parkbd_dev); |
212 | serio_unregister_port(serio: parkbd_port); |
213 | parport_unregister_device(dev: parkbd_dev); |
214 | parkbd_port = NULL; |
215 | } |
216 | |
217 | static struct parport_driver parkbd_parport_driver = { |
218 | .name = "parkbd" , |
219 | .match_port = parkbd_attach, |
220 | .detach = parkbd_detach, |
221 | .devmodel = true, |
222 | }; |
223 | module_parport_driver(parkbd_parport_driver); |
224 | |