1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Copyright (c) 2000-2001 Vojtech Pavlik |
4 | * |
5 | * Based on the work of: |
6 | * Hamish Macdonald |
7 | */ |
8 | |
9 | /* |
10 | * Amiga keyboard driver for Linux/m68k |
11 | */ |
12 | |
13 | #include <linux/module.h> |
14 | #include <linux/init.h> |
15 | #include <linux/input.h> |
16 | #include <linux/delay.h> |
17 | #include <linux/interrupt.h> |
18 | #include <linux/keyboard.h> |
19 | #include <linux/platform_device.h> |
20 | |
21 | #include <asm/amigaints.h> |
22 | #include <asm/amigahw.h> |
23 | #include <asm/irq.h> |
24 | |
25 | MODULE_AUTHOR("Vojtech Pavlik <vojtech@ucw.cz>" ); |
26 | MODULE_DESCRIPTION("Amiga keyboard driver" ); |
27 | MODULE_LICENSE("GPL" ); |
28 | |
29 | #ifdef CONFIG_HW_CONSOLE |
30 | static unsigned char amikbd_keycode[0x78] __initdata = { |
31 | [0] = KEY_GRAVE, |
32 | [1] = KEY_1, |
33 | [2] = KEY_2, |
34 | [3] = KEY_3, |
35 | [4] = KEY_4, |
36 | [5] = KEY_5, |
37 | [6] = KEY_6, |
38 | [7] = KEY_7, |
39 | [8] = KEY_8, |
40 | [9] = KEY_9, |
41 | [10] = KEY_0, |
42 | [11] = KEY_MINUS, |
43 | [12] = KEY_EQUAL, |
44 | [13] = KEY_BACKSLASH, |
45 | [15] = KEY_KP0, |
46 | [16] = KEY_Q, |
47 | [17] = KEY_W, |
48 | [18] = KEY_E, |
49 | [19] = KEY_R, |
50 | [20] = KEY_T, |
51 | [21] = KEY_Y, |
52 | [22] = KEY_U, |
53 | [23] = KEY_I, |
54 | [24] = KEY_O, |
55 | [25] = KEY_P, |
56 | [26] = KEY_LEFTBRACE, |
57 | [27] = KEY_RIGHTBRACE, |
58 | [29] = KEY_KP1, |
59 | [30] = KEY_KP2, |
60 | [31] = KEY_KP3, |
61 | [32] = KEY_A, |
62 | [33] = KEY_S, |
63 | [34] = KEY_D, |
64 | [35] = KEY_F, |
65 | [36] = KEY_G, |
66 | [37] = KEY_H, |
67 | [38] = KEY_J, |
68 | [39] = KEY_K, |
69 | [40] = KEY_L, |
70 | [41] = KEY_SEMICOLON, |
71 | [42] = KEY_APOSTROPHE, |
72 | [43] = KEY_BACKSLASH, |
73 | [45] = KEY_KP4, |
74 | [46] = KEY_KP5, |
75 | [47] = KEY_KP6, |
76 | [48] = KEY_102ND, |
77 | [49] = KEY_Z, |
78 | [50] = KEY_X, |
79 | [51] = KEY_C, |
80 | [52] = KEY_V, |
81 | [53] = KEY_B, |
82 | [54] = KEY_N, |
83 | [55] = KEY_M, |
84 | [56] = KEY_COMMA, |
85 | [57] = KEY_DOT, |
86 | [58] = KEY_SLASH, |
87 | [60] = KEY_KPDOT, |
88 | [61] = KEY_KP7, |
89 | [62] = KEY_KP8, |
90 | [63] = KEY_KP9, |
91 | [64] = KEY_SPACE, |
92 | [65] = KEY_BACKSPACE, |
93 | [66] = KEY_TAB, |
94 | [67] = KEY_KPENTER, |
95 | [68] = KEY_ENTER, |
96 | [69] = KEY_ESC, |
97 | [70] = KEY_DELETE, |
98 | [74] = KEY_KPMINUS, |
99 | [76] = KEY_UP, |
100 | [77] = KEY_DOWN, |
101 | [78] = KEY_RIGHT, |
102 | [79] = KEY_LEFT, |
103 | [80] = KEY_F1, |
104 | [81] = KEY_F2, |
105 | [82] = KEY_F3, |
106 | [83] = KEY_F4, |
107 | [84] = KEY_F5, |
108 | [85] = KEY_F6, |
109 | [86] = KEY_F7, |
110 | [87] = KEY_F8, |
111 | [88] = KEY_F9, |
112 | [89] = KEY_F10, |
113 | [90] = KEY_KPLEFTPAREN, |
114 | [91] = KEY_KPRIGHTPAREN, |
115 | [92] = KEY_KPSLASH, |
116 | [93] = KEY_KPASTERISK, |
117 | [94] = KEY_KPPLUS, |
118 | [95] = KEY_HELP, |
119 | [96] = KEY_LEFTSHIFT, |
120 | [97] = KEY_RIGHTSHIFT, |
121 | [98] = KEY_CAPSLOCK, |
122 | [99] = KEY_LEFTCTRL, |
123 | [100] = KEY_LEFTALT, |
124 | [101] = KEY_RIGHTALT, |
125 | [102] = KEY_LEFTMETA, |
126 | [103] = KEY_RIGHTMETA |
127 | }; |
128 | |
129 | static void __init amikbd_init_console_keymaps(void) |
130 | { |
131 | /* We can spare 512 bytes on stack for temp_map in init path. */ |
132 | unsigned short temp_map[NR_KEYS]; |
133 | int i, j; |
134 | |
135 | for (i = 0; i < MAX_NR_KEYMAPS; i++) { |
136 | if (!key_maps[i]) |
137 | continue; |
138 | memset(temp_map, 0, sizeof(temp_map)); |
139 | for (j = 0; j < 0x78; j++) { |
140 | if (!amikbd_keycode[j]) |
141 | continue; |
142 | temp_map[j] = key_maps[i][amikbd_keycode[j]]; |
143 | } |
144 | for (j = 0; j < NR_KEYS; j++) { |
145 | if (!temp_map[j]) |
146 | temp_map[j] = 0xf200; |
147 | } |
148 | memcpy(key_maps[i], temp_map, sizeof(temp_map)); |
149 | } |
150 | } |
151 | #else /* !CONFIG_HW_CONSOLE */ |
152 | static inline void amikbd_init_console_keymaps(void) {} |
153 | #endif /* !CONFIG_HW_CONSOLE */ |
154 | |
155 | static const char *amikbd_messages[8] = { |
156 | [0] = KERN_ALERT "amikbd: Ctrl-Amiga-Amiga reset warning!!\n" , |
157 | [1] = KERN_WARNING "amikbd: keyboard lost sync\n" , |
158 | [2] = KERN_WARNING "amikbd: keyboard buffer overflow\n" , |
159 | [3] = KERN_WARNING "amikbd: keyboard controller failure\n" , |
160 | [4] = KERN_ERR "amikbd: keyboard selftest failure\n" , |
161 | [5] = KERN_INFO "amikbd: initiate power-up key stream\n" , |
162 | [6] = KERN_INFO "amikbd: terminate power-up key stream\n" , |
163 | [7] = KERN_WARNING "amikbd: keyboard interrupt\n" |
164 | }; |
165 | |
166 | static irqreturn_t amikbd_interrupt(int irq, void *data) |
167 | { |
168 | struct input_dev *dev = data; |
169 | unsigned char scancode, down; |
170 | |
171 | scancode = ~ciaa.sdr; /* get and invert scancode (keyboard is active low) */ |
172 | ciaa.cra |= 0x40; /* switch SP pin to output for handshake */ |
173 | udelay(85); /* wait until 85 us have expired */ |
174 | ciaa.cra &= ~0x40; /* switch CIA serial port to input mode */ |
175 | |
176 | down = !(scancode & 1); /* lowest bit is release bit */ |
177 | scancode >>= 1; |
178 | |
179 | if (scancode < 0x78) { /* scancodes < 0x78 are keys */ |
180 | if (scancode == 98) { /* CapsLock is a toggle switch key on Amiga */ |
181 | input_report_key(dev, code: scancode, value: 1); |
182 | input_report_key(dev, code: scancode, value: 0); |
183 | } else { |
184 | input_report_key(dev, code: scancode, value: down); |
185 | } |
186 | |
187 | input_sync(dev); |
188 | } else /* scancodes >= 0x78 are error codes */ |
189 | printk(amikbd_messages[scancode - 0x78]); |
190 | |
191 | return IRQ_HANDLED; |
192 | } |
193 | |
194 | static int __init amikbd_probe(struct platform_device *pdev) |
195 | { |
196 | struct input_dev *dev; |
197 | int i, err; |
198 | |
199 | dev = devm_input_allocate_device(&pdev->dev); |
200 | if (!dev) { |
201 | dev_err(&pdev->dev, "Not enough memory for input device\n" ); |
202 | return -ENOMEM; |
203 | } |
204 | |
205 | dev->name = pdev->name; |
206 | dev->phys = "amikbd/input0" ; |
207 | dev->id.bustype = BUS_AMIGA; |
208 | dev->id.vendor = 0x0001; |
209 | dev->id.product = 0x0001; |
210 | dev->id.version = 0x0100; |
211 | |
212 | dev->evbit[0] = BIT_MASK(EV_KEY) | BIT_MASK(EV_REP); |
213 | |
214 | for (i = 0; i < 0x78; i++) |
215 | set_bit(nr: i, addr: dev->keybit); |
216 | |
217 | amikbd_init_console_keymaps(); |
218 | |
219 | ciaa.cra &= ~0x41; /* serial data in, turn off TA */ |
220 | err = devm_request_irq(dev: &pdev->dev, irq: IRQ_AMIGA_CIAA_SP, handler: amikbd_interrupt, |
221 | irqflags: 0, devname: "amikbd" , dev_id: dev); |
222 | if (err) |
223 | return err; |
224 | |
225 | err = input_register_device(dev); |
226 | if (err) |
227 | return err; |
228 | |
229 | platform_set_drvdata(pdev, data: dev); |
230 | |
231 | return 0; |
232 | } |
233 | |
234 | static struct platform_driver amikbd_driver = { |
235 | .driver = { |
236 | .name = "amiga-keyboard" , |
237 | }, |
238 | }; |
239 | |
240 | module_platform_driver_probe(amikbd_driver, amikbd_probe); |
241 | |
242 | MODULE_ALIAS("platform:amiga-keyboard" ); |
243 | |