1 | /* |
2 | * linux/drivers/video/mdacon.c -- Low level MDA based console driver |
3 | * |
4 | * (c) 1998 Andrew Apted <ajapted@netspace.net.au> |
5 | * |
6 | * including portions (c) 1995-1998 Patrick Caulfield. |
7 | * |
8 | * slight improvements (c) 2000 Edward Betts <edward@debian.org> |
9 | * |
10 | * This file is based on the VGA console driver (vgacon.c): |
11 | * |
12 | * Created 28 Sep 1997 by Geert Uytterhoeven |
13 | * |
14 | * Rewritten by Martin Mares <mj@ucw.cz>, July 1998 |
15 | * |
16 | * and on the old console.c, vga.c and vesa_blank.c drivers: |
17 | * |
18 | * Copyright (C) 1991, 1992 Linus Torvalds |
19 | * 1995 Jay Estabrook |
20 | * |
21 | * This file is subject to the terms and conditions of the GNU General Public |
22 | * License. See the file COPYING in the main directory of this archive for |
23 | * more details. |
24 | * |
25 | * Changelog: |
26 | * Paul G. (03/2001) Fix mdacon= boot prompt to use __setup(). |
27 | */ |
28 | |
29 | #include <linux/types.h> |
30 | #include <linux/fs.h> |
31 | #include <linux/kernel.h> |
32 | #include <linux/module.h> |
33 | #include <linux/console.h> |
34 | #include <linux/string.h> |
35 | #include <linux/kd.h> |
36 | #include <linux/vt_kern.h> |
37 | #include <linux/vt_buffer.h> |
38 | #include <linux/selection.h> |
39 | #include <linux/spinlock.h> |
40 | #include <linux/ioport.h> |
41 | #include <linux/delay.h> |
42 | #include <linux/init.h> |
43 | |
44 | #include <asm/io.h> |
45 | #include <asm/vga.h> |
46 | |
47 | static DEFINE_SPINLOCK(mda_lock); |
48 | |
49 | /* description of the hardware layout */ |
50 | |
51 | static u16 *mda_vram_base; /* Base of video memory */ |
52 | static unsigned long mda_vram_len; /* Size of video memory */ |
53 | static unsigned int mda_num_columns; /* Number of text columns */ |
54 | static unsigned int mda_num_lines; /* Number of text lines */ |
55 | |
56 | static unsigned int mda_index_port; /* Register select port */ |
57 | static unsigned int mda_value_port; /* Register value port */ |
58 | static unsigned int mda_mode_port; /* Mode control port */ |
59 | static unsigned int mda_status_port; /* Status and Config port */ |
60 | static unsigned int mda_gfx_port; /* Graphics control port */ |
61 | |
62 | /* current hardware state */ |
63 | |
64 | static int mda_cursor_loc=-1; |
65 | static int mda_cursor_size_from=-1; |
66 | static int mda_cursor_size_to=-1; |
67 | |
68 | static enum { TYPE_MDA, TYPE_HERC, TYPE_HERCPLUS, TYPE_HERCCOLOR } mda_type; |
69 | static char *mda_type_name; |
70 | |
71 | /* console information */ |
72 | |
73 | static int mda_first_vc = 13; |
74 | static int mda_last_vc = 16; |
75 | |
76 | static struct vc_data *mda_display_fg = NULL; |
77 | |
78 | module_param(mda_first_vc, int, 0); |
79 | MODULE_PARM_DESC(mda_first_vc, "First virtual console. Default: 13" ); |
80 | module_param(mda_last_vc, int, 0); |
81 | MODULE_PARM_DESC(mda_last_vc, "Last virtual console. Default: 16" ); |
82 | |
83 | /* MDA register values |
84 | */ |
85 | |
86 | #define MDA_CURSOR_BLINKING 0x00 |
87 | #define MDA_CURSOR_OFF 0x20 |
88 | #define MDA_CURSOR_SLOWBLINK 0x60 |
89 | |
90 | #define MDA_MODE_GRAPHICS 0x02 |
91 | #define MDA_MODE_VIDEO_EN 0x08 |
92 | #define MDA_MODE_BLINK_EN 0x20 |
93 | #define MDA_MODE_GFX_PAGE1 0x80 |
94 | |
95 | #define MDA_STATUS_HSYNC 0x01 |
96 | #define MDA_STATUS_VSYNC 0x80 |
97 | #define MDA_STATUS_VIDEO 0x08 |
98 | |
99 | #define MDA_CONFIG_COL132 0x08 |
100 | #define MDA_GFX_MODE_EN 0x01 |
101 | #define MDA_GFX_PAGE_EN 0x02 |
102 | |
103 | |
104 | /* |
105 | * MDA could easily be classified as "pre-dinosaur hardware". |
106 | */ |
107 | |
108 | static void write_mda_b(unsigned int val, unsigned char reg) |
109 | { |
110 | unsigned long flags; |
111 | |
112 | spin_lock_irqsave(&mda_lock, flags); |
113 | |
114 | outb_p(value: reg, port: mda_index_port); |
115 | outb_p(value: val, port: mda_value_port); |
116 | |
117 | spin_unlock_irqrestore(lock: &mda_lock, flags); |
118 | } |
119 | |
120 | static void write_mda_w(unsigned int val, unsigned char reg) |
121 | { |
122 | unsigned long flags; |
123 | |
124 | spin_lock_irqsave(&mda_lock, flags); |
125 | |
126 | outb_p(value: reg, port: mda_index_port); outb_p(value: val >> 8, port: mda_value_port); |
127 | outb_p(value: reg+1, port: mda_index_port); outb_p(value: val & 0xff, port: mda_value_port); |
128 | |
129 | spin_unlock_irqrestore(lock: &mda_lock, flags); |
130 | } |
131 | |
132 | #ifdef TEST_MDA_B |
133 | static int test_mda_b(unsigned char val, unsigned char reg) |
134 | { |
135 | unsigned long flags; |
136 | |
137 | spin_lock_irqsave(&mda_lock, flags); |
138 | |
139 | outb_p(reg, mda_index_port); |
140 | outb (val, mda_value_port); |
141 | |
142 | udelay(20); val = (inb_p(mda_value_port) == val); |
143 | |
144 | spin_unlock_irqrestore(&mda_lock, flags); |
145 | return val; |
146 | } |
147 | #endif |
148 | |
149 | static inline void mda_set_cursor(unsigned int location) |
150 | { |
151 | if (mda_cursor_loc == location) |
152 | return; |
153 | |
154 | write_mda_w(val: location >> 1, reg: 0x0e); |
155 | |
156 | mda_cursor_loc = location; |
157 | } |
158 | |
159 | static inline void mda_set_cursor_size(int from, int to) |
160 | { |
161 | if (mda_cursor_size_from==from && mda_cursor_size_to==to) |
162 | return; |
163 | |
164 | if (from > to) { |
165 | write_mda_b(MDA_CURSOR_OFF, reg: 0x0a); /* disable cursor */ |
166 | } else { |
167 | write_mda_b(val: from, reg: 0x0a); /* cursor start */ |
168 | write_mda_b(val: to, reg: 0x0b); /* cursor end */ |
169 | } |
170 | |
171 | mda_cursor_size_from = from; |
172 | mda_cursor_size_to = to; |
173 | } |
174 | |
175 | |
176 | #ifndef MODULE |
177 | static int __init mdacon_setup(char *str) |
178 | { |
179 | /* command line format: mdacon=<first>,<last> */ |
180 | |
181 | int ints[3]; |
182 | |
183 | str = get_options(str, ARRAY_SIZE(ints), ints); |
184 | |
185 | if (ints[0] < 2) |
186 | return 0; |
187 | |
188 | if (ints[1] < 1 || ints[1] > MAX_NR_CONSOLES || |
189 | ints[2] < 1 || ints[2] > MAX_NR_CONSOLES) |
190 | return 0; |
191 | |
192 | mda_first_vc = ints[1]; |
193 | mda_last_vc = ints[2]; |
194 | return 1; |
195 | } |
196 | |
197 | __setup("mdacon=" , mdacon_setup); |
198 | #endif |
199 | |
200 | static int mda_detect(void) |
201 | { |
202 | int count=0; |
203 | u16 *p, p_save; |
204 | u16 *q, q_save; |
205 | |
206 | /* do a memory check */ |
207 | |
208 | p = mda_vram_base; |
209 | q = mda_vram_base + 0x01000 / 2; |
210 | |
211 | p_save = scr_readw(p); |
212 | q_save = scr_readw(q); |
213 | |
214 | scr_writew(0xAA55, p); |
215 | if (scr_readw(p) == 0xAA55) |
216 | count++; |
217 | |
218 | scr_writew(0x55AA, p); |
219 | if (scr_readw(p) == 0x55AA) |
220 | count++; |
221 | |
222 | scr_writew(p_save, p); |
223 | |
224 | if (count != 2) { |
225 | return 0; |
226 | } |
227 | |
228 | /* check if we have 4K or 8K */ |
229 | |
230 | scr_writew(0xA55A, q); |
231 | scr_writew(0x0000, p); |
232 | if (scr_readw(q) == 0xA55A) |
233 | count++; |
234 | |
235 | scr_writew(0x5AA5, q); |
236 | scr_writew(0x0000, p); |
237 | if (scr_readw(q) == 0x5AA5) |
238 | count++; |
239 | |
240 | scr_writew(p_save, p); |
241 | scr_writew(q_save, q); |
242 | |
243 | if (count == 4) { |
244 | mda_vram_len = 0x02000; |
245 | } |
246 | |
247 | /* Ok, there is definitely a card registering at the correct |
248 | * memory location, so now we do an I/O port test. |
249 | */ |
250 | |
251 | #ifdef TEST_MDA_B |
252 | /* Edward: These two mess `tests' mess up my cursor on bootup */ |
253 | |
254 | /* cursor low register */ |
255 | if (!test_mda_b(0x66, 0x0f)) |
256 | return 0; |
257 | |
258 | /* cursor low register */ |
259 | if (!test_mda_b(0x99, 0x0f)) |
260 | return 0; |
261 | #endif |
262 | |
263 | /* See if the card is a Hercules, by checking whether the vsync |
264 | * bit of the status register is changing. This test lasts for |
265 | * approximately 1/10th of a second. |
266 | */ |
267 | |
268 | p_save = q_save = inb_p(port: mda_status_port) & MDA_STATUS_VSYNC; |
269 | |
270 | for (count = 0; count < 50000 && p_save == q_save; count++) { |
271 | q_save = inb(port: mda_status_port) & MDA_STATUS_VSYNC; |
272 | udelay(2); |
273 | } |
274 | |
275 | if (p_save != q_save) { |
276 | switch (inb_p(port: mda_status_port) & 0x70) { |
277 | case 0x10: |
278 | mda_type = TYPE_HERCPLUS; |
279 | mda_type_name = "HerculesPlus" ; |
280 | break; |
281 | case 0x50: |
282 | mda_type = TYPE_HERCCOLOR; |
283 | mda_type_name = "HerculesColor" ; |
284 | break; |
285 | default: |
286 | mda_type = TYPE_HERC; |
287 | mda_type_name = "Hercules" ; |
288 | break; |
289 | } |
290 | } |
291 | |
292 | return 1; |
293 | } |
294 | |
295 | static void mda_initialize(void) |
296 | { |
297 | write_mda_b(val: 97, reg: 0x00); /* horizontal total */ |
298 | write_mda_b(val: 80, reg: 0x01); /* horizontal displayed */ |
299 | write_mda_b(val: 82, reg: 0x02); /* horizontal sync pos */ |
300 | write_mda_b(val: 15, reg: 0x03); /* horizontal sync width */ |
301 | |
302 | write_mda_b(val: 25, reg: 0x04); /* vertical total */ |
303 | write_mda_b(val: 6, reg: 0x05); /* vertical total adjust */ |
304 | write_mda_b(val: 25, reg: 0x06); /* vertical displayed */ |
305 | write_mda_b(val: 25, reg: 0x07); /* vertical sync pos */ |
306 | |
307 | write_mda_b(val: 2, reg: 0x08); /* interlace mode */ |
308 | write_mda_b(val: 13, reg: 0x09); /* maximum scanline */ |
309 | write_mda_b(val: 12, reg: 0x0a); /* cursor start */ |
310 | write_mda_b(val: 13, reg: 0x0b); /* cursor end */ |
311 | |
312 | write_mda_w(val: 0x0000, reg: 0x0c); /* start address */ |
313 | write_mda_w(val: 0x0000, reg: 0x0e); /* cursor location */ |
314 | |
315 | outb_p(MDA_MODE_VIDEO_EN | MDA_MODE_BLINK_EN, port: mda_mode_port); |
316 | outb_p(value: 0x00, port: mda_status_port); |
317 | outb_p(value: 0x00, port: mda_gfx_port); |
318 | } |
319 | |
320 | static const char *mdacon_startup(void) |
321 | { |
322 | mda_num_columns = 80; |
323 | mda_num_lines = 25; |
324 | |
325 | mda_vram_len = 0x01000; |
326 | mda_vram_base = (u16 *)VGA_MAP_MEM(0xb0000, mda_vram_len); |
327 | |
328 | mda_index_port = 0x3b4; |
329 | mda_value_port = 0x3b5; |
330 | mda_mode_port = 0x3b8; |
331 | mda_status_port = 0x3ba; |
332 | mda_gfx_port = 0x3bf; |
333 | |
334 | mda_type = TYPE_MDA; |
335 | mda_type_name = "MDA" ; |
336 | |
337 | if (! mda_detect()) { |
338 | printk("mdacon: MDA card not detected.\n" ); |
339 | return NULL; |
340 | } |
341 | |
342 | if (mda_type != TYPE_MDA) { |
343 | mda_initialize(); |
344 | } |
345 | |
346 | /* cursor looks ugly during boot-up, so turn it off */ |
347 | mda_set_cursor(location: mda_vram_len - 1); |
348 | |
349 | printk("mdacon: %s with %ldK of memory detected.\n" , |
350 | mda_type_name, mda_vram_len/1024); |
351 | |
352 | return "MDA-2" ; |
353 | } |
354 | |
355 | static void mdacon_init(struct vc_data *c, bool init) |
356 | { |
357 | c->vc_complement_mask = 0x0800; /* reverse video */ |
358 | c->vc_display_fg = &mda_display_fg; |
359 | |
360 | if (init) { |
361 | c->vc_cols = mda_num_columns; |
362 | c->vc_rows = mda_num_lines; |
363 | } else |
364 | vc_resize(vc: c, cols: mda_num_columns, lines: mda_num_lines); |
365 | |
366 | /* make the first MDA console visible */ |
367 | |
368 | if (mda_display_fg == NULL) |
369 | mda_display_fg = c; |
370 | } |
371 | |
372 | static void mdacon_deinit(struct vc_data *c) |
373 | { |
374 | /* con_set_default_unimap(c->vc_num); */ |
375 | |
376 | if (mda_display_fg == c) |
377 | mda_display_fg = NULL; |
378 | } |
379 | |
380 | static inline u16 mda_convert_attr(u16 ch) |
381 | { |
382 | u16 attr = 0x0700; |
383 | |
384 | /* Underline and reverse-video are mutually exclusive on MDA. |
385 | * Since reverse-video is used for cursors and selected areas, |
386 | * it takes precedence. |
387 | */ |
388 | |
389 | if (ch & 0x0800) attr = 0x7000; /* reverse */ |
390 | else if (ch & 0x0400) attr = 0x0100; /* underline */ |
391 | |
392 | return ((ch & 0x0200) << 2) | /* intensity */ |
393 | (ch & 0x8000) | /* blink */ |
394 | (ch & 0x00ff) | attr; |
395 | } |
396 | |
397 | static u8 mdacon_build_attr(struct vc_data *c, u8 color, |
398 | enum vc_intensity intensity, |
399 | bool blink, bool underline, bool reverse, |
400 | bool italic) |
401 | { |
402 | /* The attribute is just a bit vector: |
403 | * |
404 | * Bit 0..1 : intensity (0..2) |
405 | * Bit 2 : underline |
406 | * Bit 3 : reverse |
407 | * Bit 7 : blink |
408 | */ |
409 | |
410 | return (intensity & VCI_MASK) | |
411 | (underline << 2) | |
412 | (reverse << 3) | |
413 | (italic << 4) | |
414 | (blink << 7); |
415 | } |
416 | |
417 | static void mdacon_invert_region(struct vc_data *c, u16 *p, int count) |
418 | { |
419 | for (; count > 0; count--) { |
420 | scr_writew(scr_readw(p) ^ 0x0800, p); |
421 | p++; |
422 | } |
423 | } |
424 | |
425 | static inline u16 *mda_addr(unsigned int x, unsigned int y) |
426 | { |
427 | return mda_vram_base + y * mda_num_columns + x; |
428 | } |
429 | |
430 | static void mdacon_putcs(struct vc_data *c, const u16 *s, unsigned int count, |
431 | unsigned int y, unsigned int x) |
432 | { |
433 | u16 *dest = mda_addr(x, y); |
434 | |
435 | for (; count > 0; count--) { |
436 | scr_writew(mda_convert_attr(scr_readw(s++)), dest++); |
437 | } |
438 | } |
439 | |
440 | static void mdacon_clear(struct vc_data *c, unsigned int y, unsigned int x, |
441 | unsigned int width) |
442 | { |
443 | u16 *dest = mda_addr(x, y); |
444 | u16 eattr = mda_convert_attr(ch: c->vc_video_erase_char); |
445 | |
446 | scr_memsetw(s: dest, c: eattr, count: width * 2); |
447 | } |
448 | |
449 | static bool mdacon_switch(struct vc_data *c) |
450 | { |
451 | return true; /* redrawing needed */ |
452 | } |
453 | |
454 | static bool mdacon_blank(struct vc_data *c, enum vesa_blank_mode blank, |
455 | bool mode_switch) |
456 | { |
457 | if (mda_type == TYPE_MDA) { |
458 | if (blank) |
459 | scr_memsetw(s: mda_vram_base, |
460 | c: mda_convert_attr(ch: c->vc_video_erase_char), |
461 | count: c->vc_screenbuf_size); |
462 | /* Tell console.c that it has to restore the screen itself */ |
463 | return true; |
464 | } else { |
465 | if (blank) |
466 | outb_p(value: 0x00, port: mda_mode_port); /* disable video */ |
467 | else |
468 | outb_p(MDA_MODE_VIDEO_EN | MDA_MODE_BLINK_EN, |
469 | port: mda_mode_port); |
470 | return false; |
471 | } |
472 | } |
473 | |
474 | static void mdacon_cursor(struct vc_data *c, bool enable) |
475 | { |
476 | if (!enable) { |
477 | mda_set_cursor(location: mda_vram_len - 1); |
478 | return; |
479 | } |
480 | |
481 | mda_set_cursor(location: c->state.y * mda_num_columns * 2 + c->state.x * 2); |
482 | |
483 | switch (CUR_SIZE(c->vc_cursor_type)) { |
484 | |
485 | case CUR_LOWER_THIRD: mda_set_cursor_size(from: 10, to: 13); break; |
486 | case CUR_LOWER_HALF: mda_set_cursor_size(from: 7, to: 13); break; |
487 | case CUR_TWO_THIRDS: mda_set_cursor_size(from: 4, to: 13); break; |
488 | case CUR_BLOCK: mda_set_cursor_size(from: 1, to: 13); break; |
489 | case CUR_NONE: mda_set_cursor_size(from: 14, to: 13); break; |
490 | default: mda_set_cursor_size(from: 12, to: 13); break; |
491 | } |
492 | } |
493 | |
494 | static bool mdacon_scroll(struct vc_data *c, unsigned int t, unsigned int b, |
495 | enum con_scroll dir, unsigned int lines) |
496 | { |
497 | u16 eattr = mda_convert_attr(ch: c->vc_video_erase_char); |
498 | |
499 | if (!lines) |
500 | return false; |
501 | |
502 | if (lines > c->vc_rows) /* maximum realistic size */ |
503 | lines = c->vc_rows; |
504 | |
505 | switch (dir) { |
506 | |
507 | case SM_UP: |
508 | scr_memmovew(d: mda_addr(x: 0, y: t), s: mda_addr(x: 0, y: t + lines), |
509 | count: (b-t-lines)*mda_num_columns*2); |
510 | scr_memsetw(s: mda_addr(x: 0, y: b - lines), c: eattr, |
511 | count: lines*mda_num_columns*2); |
512 | break; |
513 | |
514 | case SM_DOWN: |
515 | scr_memmovew(d: mda_addr(x: 0, y: t + lines), s: mda_addr(x: 0, y: t), |
516 | count: (b-t-lines)*mda_num_columns*2); |
517 | scr_memsetw(s: mda_addr(x: 0, y: t), c: eattr, count: lines*mda_num_columns*2); |
518 | break; |
519 | } |
520 | |
521 | return false; |
522 | } |
523 | |
524 | |
525 | /* |
526 | * The console `switch' structure for the MDA based console |
527 | */ |
528 | |
529 | static const struct consw mda_con = { |
530 | .owner = THIS_MODULE, |
531 | .con_startup = mdacon_startup, |
532 | .con_init = mdacon_init, |
533 | .con_deinit = mdacon_deinit, |
534 | .con_clear = mdacon_clear, |
535 | .con_putcs = mdacon_putcs, |
536 | .con_cursor = mdacon_cursor, |
537 | .con_scroll = mdacon_scroll, |
538 | .con_switch = mdacon_switch, |
539 | .con_blank = mdacon_blank, |
540 | .con_build_attr = mdacon_build_attr, |
541 | .con_invert_region = mdacon_invert_region, |
542 | }; |
543 | |
544 | int __init mda_console_init(void) |
545 | { |
546 | int err; |
547 | |
548 | if (mda_first_vc > mda_last_vc) |
549 | return 1; |
550 | console_lock(); |
551 | err = do_take_over_console(sw: &mda_con, first: mda_first_vc-1, last: mda_last_vc-1, deflt: 0); |
552 | console_unlock(); |
553 | return err; |
554 | } |
555 | |
556 | static void __exit mda_console_exit(void) |
557 | { |
558 | give_up_console(sw: &mda_con); |
559 | } |
560 | |
561 | module_init(mda_console_init); |
562 | module_exit(mda_console_exit); |
563 | |
564 | MODULE_LICENSE("GPL" ); |
565 | |
566 | |