1/*
2 *  linux/drivers/video/vfb.c -- Virtual frame buffer device
3 *
4 *	Copyright (C) 1997 Geert Uytterhoeven
5 *
6 *  This file is subject to the terms and conditions of the GNU General Public
7 *  License. See the file COPYING in the main directory of this archive for
8 *  more details.
9 */
10
11#include <linux/module.h>
12#include <linux/kernel.h>
13#include <linux/errno.h>
14#include <linux/string.h>
15#include <linux/mm.h>
16#include <linux/tty.h>
17#include <linux/slab.h>
18#include <linux/vmalloc.h>
19#include <linux/delay.h>
20#include <linux/interrupt.h>
21#include <asm/uaccess.h>
22#include <linux/fb.h>
23#include <linux/init.h>
24
25#include <video/fbcon.h>
26#include <video/fbcon-mfb.h>
27#include <video/fbcon-cfb2.h>
28#include <video/fbcon-cfb4.h>
29#include <video/fbcon-cfb8.h>
30#include <video/fbcon-cfb16.h>
31#include <video/fbcon-cfb24.h>
32#include <video/fbcon-cfb32.h>
33
34
35    /*
36     *  RAM we reserve for the frame buffer. This defines the maximum screen
37     *  size
38     *
39     *  The default can be overridden if the driver is compiled as a module
40     */
41
42#define VIDEOMEMSIZE	(1*1024*1024)	/* 1 MB */
43
44static u_long videomemory, videomemorysize = VIDEOMEMSIZE;
45MODULE_PARM(videomemorysize, "l");
46static int currcon = 0;
47static struct display disp;
48static struct fb_info fb_info;
49static struct { u_char red, green, blue, pad; } palette[256];
50static union {
51#ifdef FBCON_HAS_CFB16
52    u16 cfb16[16];
53#endif
54#ifdef FBCON_HAS_CFB24
55    u32 cfb24[16];
56#endif
57#ifdef FBCON_HAS_CFB32
58    u32 cfb32[16];
59#endif
60} fbcon_cmap;
61static char vfb_name[16] = "Virtual FB";
62
63static struct fb_var_screeninfo vfb_default = {
64    /* 640x480, 8 bpp */
65    640, 480, 640, 480, 0, 0, 8, 0,
66    {0, 8, 0}, {0, 8, 0}, {0, 8, 0}, {0, 0, 0},
67    0, 0, -1, -1, 0, 20000, 64, 64, 32, 32, 64, 2,
68    0, FB_VMODE_NONINTERLACED
69};
70
71static int vfb_enable = 0;	/* disabled by default */
72
73
74    /*
75     *  Interface used by the world
76     */
77
78int vfb_setup(char*);
79
80static int vfb_get_fix(struct fb_fix_screeninfo *fix, int con,
81		       struct fb_info *info);
82static int vfb_get_var(struct fb_var_screeninfo *var, int con,
83		       struct fb_info *info);
84static int vfb_set_var(struct fb_var_screeninfo *var, int con,
85		       struct fb_info *info);
86static int vfb_pan_display(struct fb_var_screeninfo *var, int con,
87			   struct fb_info *info);
88static int vfb_get_cmap(struct fb_cmap *cmap, int kspc, int con,
89			struct fb_info *info);
90static int vfb_set_cmap(struct fb_cmap *cmap, int kspc, int con,
91			struct fb_info *info);
92
93
94    /*
95     *  Interface to the low level console driver
96     */
97
98int vfb_init(void);
99static int vfbcon_switch(int con, struct fb_info *info);
100static int vfbcon_updatevar(int con, struct fb_info *info);
101static void vfbcon_blank(int blank, struct fb_info *info);
102
103
104    /*
105     *  Internal routines
106     */
107
108static u_long get_line_length(int xres_virtual, int bpp);
109static void vfb_encode_fix(struct fb_fix_screeninfo *fix,
110			   struct fb_var_screeninfo *var);
111static void set_color_bitfields(struct fb_var_screeninfo *var);
112static int vfb_getcolreg(u_int regno, u_int *red, u_int *green, u_int *blue,
113                         u_int *transp, struct fb_info *info);
114static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
115                         u_int transp, struct fb_info *info);
116static void do_install_cmap(int con, struct fb_info *info);
117
118
119static struct fb_ops vfb_ops = {
120	owner:		THIS_MODULE,
121	fb_get_fix:	vfb_get_fix,
122	fb_get_var:	vfb_get_var,
123	fb_set_var:	vfb_set_var,
124	fb_get_cmap:	vfb_get_cmap,
125	fb_set_cmap:	vfb_set_cmap,
126	fb_pan_display:	vfb_pan_display,
127};
128
129    /*
130     *  Get the Fixed Part of the Display
131     */
132
133static int vfb_get_fix(struct fb_fix_screeninfo *fix, int con,
134		       struct fb_info *info)
135{
136    struct fb_var_screeninfo *var;
137
138    if (con == -1)
139	var = &vfb_default;
140    else
141	var = &fb_display[con].var;
142    vfb_encode_fix(fix, var);
143    return 0;
144}
145
146
147    /*
148     *  Get the User Defined Part of the Display
149     */
150
151static int vfb_get_var(struct fb_var_screeninfo *var, int con,
152		       struct fb_info *info)
153{
154    if (con == -1)
155	*var = vfb_default;
156    else
157	*var = fb_display[con].var;
158    set_color_bitfields(var);
159    return 0;
160}
161
162
163    /*
164     *  Set the User Defined Part of the Display
165     */
166
167static int vfb_set_var(struct fb_var_screeninfo *var, int con,
168		       struct fb_info *info)
169{
170    int err, activate = var->activate;
171    int oldxres, oldyres, oldvxres, oldvyres, oldbpp;
172    u_long line_length;
173
174    struct display *display;
175    if (con >= 0)
176	display = &fb_display[con];
177    else
178	display = &disp;	/* used during initialization */
179
180    /*
181     *  FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal!
182     *  as FB_VMODE_SMOOTH_XPAN is only used internally
183     */
184
185    if (var->vmode & FB_VMODE_CONUPDATE) {
186	var->vmode |= FB_VMODE_YWRAP;
187	var->xoffset = display->var.xoffset;
188	var->yoffset = display->var.yoffset;
189    }
190
191    /*
192     *  Some very basic checks
193     */
194    if (!var->xres)
195	var->xres = 1;
196    if (!var->yres)
197	var->yres = 1;
198    if (var->xres > var->xres_virtual)
199	var->xres_virtual = var->xres;
200    if (var->yres > var->yres_virtual)
201	var->yres_virtual = var->yres;
202    if (var->bits_per_pixel <= 1)
203	var->bits_per_pixel = 1;
204    else if (var->bits_per_pixel <= 8)
205	var->bits_per_pixel = 8;
206    else if (var->bits_per_pixel <= 16)
207	var->bits_per_pixel = 16;
208    else
209	return -EINVAL;
210
211    /*
212     *  Memory limit
213     */
214    line_length = get_line_length(var->xres_virtual, var->bits_per_pixel);
215    if (line_length*var->yres_virtual > videomemorysize)
216	return -ENOMEM;
217
218    set_color_bitfields(var);
219
220    if ((activate & FB_ACTIVATE_MASK) == FB_ACTIVATE_NOW) {
221	oldxres = display->var.xres;
222	oldyres = display->var.yres;
223	oldvxres = display->var.xres_virtual;
224	oldvyres = display->var.yres_virtual;
225	oldbpp = display->var.bits_per_pixel;
226	display->var = *var;
227	if (oldxres != var->xres || oldyres != var->yres ||
228	    oldvxres != var->xres_virtual || oldvyres != var->yres_virtual ||
229	    oldbpp != var->bits_per_pixel) {
230	    struct fb_fix_screeninfo fix;
231
232	    vfb_encode_fix(&fix, var);
233	    display->screen_base = (char *)videomemory;
234	    display->visual = fix.visual;
235	    display->type = fix.type;
236	    display->type_aux = fix.type_aux;
237	    display->ypanstep = fix.ypanstep;
238	    display->ywrapstep = fix.ywrapstep;
239	    display->line_length = fix.line_length;
240	    display->can_soft_blank = 1;
241	    display->inverse = 0;
242	    switch (var->bits_per_pixel) {
243#ifdef FBCON_HAS_MFB
244		case 1:
245		    display->dispsw = &fbcon_mfb;
246		    break;
247#endif
248#ifdef FBCON_HAS_CFB2
249		case 2:
250		    display->dispsw = &fbcon_cfb2;
251		    break;
252#endif
253#ifdef FBCON_HAS_CFB4
254		case 4:
255		    display->dispsw = &fbcon_cfb4;
256		    break;
257#endif
258#ifdef FBCON_HAS_CFB8
259		case 8:
260		    display->dispsw = &fbcon_cfb8;
261		    break;
262#endif
263#ifdef FBCON_HAS_CFB16
264		case 16:
265		    display->dispsw = &fbcon_cfb16;
266		    display->dispsw_data = fbcon_cmap.cfb16;
267		    break;
268#endif
269#ifdef FBCON_HAS_CFB24
270		case 24:
271		    display->dispsw = &fbcon_cfb24;
272		    display->dispsw_data = fbcon_cmap.cfb24;
273		    break;
274#endif
275#ifdef FBCON_HAS_CFB32
276		case 32:
277		    display->dispsw = &fbcon_cfb32;
278		    display->dispsw_data = fbcon_cmap.cfb32;
279		    break;
280#endif
281		default:
282		    display->dispsw = &fbcon_dummy;
283		    break;
284	    }
285	    if (fb_info.changevar)
286		(*fb_info.changevar)(con);
287	}
288	if (oldbpp != var->bits_per_pixel) {
289	    if ((err = fb_alloc_cmap(&display->cmap, 0, 0)))
290		return err;
291	    do_install_cmap(con, info);
292	}
293    }
294    return 0;
295}
296
297
298    /*
299     *  Pan or Wrap the Display
300     *
301     *  This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
302     */
303
304static int vfb_pan_display(struct fb_var_screeninfo *var, int con,
305			   struct fb_info *info)
306{
307    if (var->vmode & FB_VMODE_YWRAP) {
308	if (var->yoffset < 0 ||
309	    var->yoffset >= fb_display[con].var.yres_virtual ||
310	    var->xoffset)
311	    return -EINVAL;
312    } else {
313	if (var->xoffset+fb_display[con].var.xres >
314	    fb_display[con].var.xres_virtual ||
315	    var->yoffset+fb_display[con].var.yres >
316	    fb_display[con].var.yres_virtual)
317	    return -EINVAL;
318    }
319    fb_display[con].var.xoffset = var->xoffset;
320    fb_display[con].var.yoffset = var->yoffset;
321    if (var->vmode & FB_VMODE_YWRAP)
322	fb_display[con].var.vmode |= FB_VMODE_YWRAP;
323    else
324	fb_display[con].var.vmode &= ~FB_VMODE_YWRAP;
325    return 0;
326}
327
328    /*
329     *  Get the Colormap
330     */
331
332static int vfb_get_cmap(struct fb_cmap *cmap, int kspc, int con,
333			struct fb_info *info)
334{
335    if (con == currcon) /* current console? */
336	return fb_get_cmap(cmap, kspc, vfb_getcolreg, info);
337    else if (fb_display[con].cmap.len) /* non default colormap? */
338	fb_copy_cmap(&fb_display[con].cmap, cmap, kspc ? 0 : 2);
339    else
340	fb_copy_cmap(fb_default_cmap(1<<fb_display[con].var.bits_per_pixel),
341		     cmap, kspc ? 0 : 2);
342    return 0;
343}
344
345    /*
346     *  Set the Colormap
347     */
348
349static int vfb_set_cmap(struct fb_cmap *cmap, int kspc, int con,
350			struct fb_info *info)
351{
352    int err;
353
354    if (!fb_display[con].cmap.len) {	/* no colormap allocated? */
355	if ((err = fb_alloc_cmap(&fb_display[con].cmap,
356			      1<<fb_display[con].var.bits_per_pixel, 0)))
357	    return err;
358    }
359    if (con == currcon)			/* current console? */
360	return fb_set_cmap(cmap, kspc, vfb_setcolreg, info);
361    else
362	fb_copy_cmap(cmap, &fb_display[con].cmap, kspc ? 0 : 1);
363    return 0;
364}
365
366
367int __init vfb_setup(char *options)
368{
369    char *this_opt;
370
371    fb_info.fontname[0] = '\0';
372
373    vfb_enable = 1;
374
375    if (!options || !*options)
376	return 0;
377
378    while ((this_opt = strsep(&options, ",")) != NULL) {
379	if (!strncmp(this_opt, "font:", 5))
380	    strcpy(fb_info.fontname, this_opt+5);
381    }
382    return 0;
383}
384
385
386    /*
387     *  Initialisation
388     */
389
390int __init vfb_init(void)
391{
392    if (!vfb_enable)
393	return -ENXIO;
394
395    if (!(videomemory = (u_long)vmalloc(videomemorysize)))
396	return -ENOMEM;
397
398    strcpy(fb_info.modename, vfb_name);
399    fb_info.changevar = NULL;
400    fb_info.node = -1;
401    fb_info.fbops = &vfb_ops;
402    fb_info.disp = &disp;
403    fb_info.switch_con = &vfbcon_switch;
404    fb_info.updatevar = &vfbcon_updatevar;
405    fb_info.blank = &vfbcon_blank;
406    fb_info.flags = FBINFO_FLAG_DEFAULT;
407
408    vfb_set_var(&vfb_default, -1, &fb_info);
409
410    if (register_framebuffer(&fb_info) < 0) {
411	vfree((void *)videomemory);
412	return -EINVAL;
413    }
414
415    printk(KERN_INFO "fb%d: Virtual frame buffer device, using %ldK of video memory\n",
416	   GET_FB_IDX(fb_info.node), videomemorysize>>10);
417    return 0;
418}
419
420
421static int vfbcon_switch(int con, struct fb_info *info)
422{
423    /* Do we have to save the colormap? */
424    if (fb_display[currcon].cmap.len)
425	fb_get_cmap(&fb_display[currcon].cmap, 1, vfb_getcolreg, info);
426
427    currcon = con;
428    /* Install new colormap */
429    do_install_cmap(con, info);
430    return 0;
431}
432
433    /*
434     *  Update the `var' structure (called by fbcon.c)
435     */
436
437static int vfbcon_updatevar(int con, struct fb_info *info)
438{
439    /* Nothing */
440    return 0;
441}
442
443    /*
444     *  Blank the display.
445     */
446
447static void vfbcon_blank(int blank, struct fb_info *info)
448{
449    /* Nothing */
450}
451
452static u_long get_line_length(int xres_virtual, int bpp)
453{
454    u_long length;
455
456    length = xres_virtual*bpp;
457    length = (length+31)&-32;
458    length >>= 3;
459    return(length);
460}
461
462static void vfb_encode_fix(struct fb_fix_screeninfo *fix,
463			   struct fb_var_screeninfo *var)
464{
465    memset(fix, 0, sizeof(struct fb_fix_screeninfo));
466    strcpy(fix->id, vfb_name);
467    fix->smem_start = videomemory;
468    fix->smem_len = videomemorysize;
469    fix->type = FB_TYPE_PACKED_PIXELS;
470    fix->type_aux = 0;
471    switch (var->bits_per_pixel) {
472	case 1:
473	    fix->visual = FB_VISUAL_MONO01;
474	    break;
475	case 2:
476	case 4:
477	case 8:
478	    fix->visual = FB_VISUAL_PSEUDOCOLOR;
479	    break;
480	case 16:
481	case 24:
482	case 32:
483	    fix->visual = FB_VISUAL_TRUECOLOR;
484	    break;
485    }
486    fix->ywrapstep = 1;
487    fix->xpanstep = 1;
488    fix->ypanstep = 1;
489    fix->line_length = get_line_length(var->xres_virtual, var->bits_per_pixel);
490}
491
492static void set_color_bitfields(struct fb_var_screeninfo *var)
493{
494    switch (var->bits_per_pixel) {
495	case 1:
496	case 8:
497	    var->red.offset = 0;
498	    var->red.length = 8;
499	    var->green.offset = 0;
500	    var->green.length = 8;
501	    var->blue.offset = 0;
502	    var->blue.length = 8;
503	    var->transp.offset = 0;
504	    var->transp.length = 0;
505	    break;
506	case 16:	/* RGB 565 */
507	    var->red.offset = 0;
508	    var->red.length = 5;
509	    var->green.offset = 5;
510	    var->green.length = 6;
511	    var->blue.offset = 11;
512	    var->blue.length = 5;
513	    var->transp.offset = 0;
514	    var->transp.length = 0;
515	    break;
516	case 24:	/* RGB 888 */
517	    var->red.offset = 0;
518	    var->red.length = 8;
519	    var->green.offset = 8;
520	    var->green.length = 8;
521	    var->blue.offset = 16;
522	    var->blue.length = 8;
523	    var->transp.offset = 0;
524	    var->transp.length = 0;
525	    break;
526	case 32:	/* RGBA 8888 */
527	    var->red.offset = 0;
528	    var->red.length = 8;
529	    var->green.offset = 8;
530	    var->green.length = 8;
531	    var->blue.offset = 16;
532	    var->blue.length = 8;
533	    var->transp.offset = 24;
534	    var->transp.length = 8;
535	    break;
536    }
537    var->red.msb_right = 0;
538    var->green.msb_right = 0;
539    var->blue.msb_right = 0;
540    var->transp.msb_right = 0;
541}
542
543
544    /*
545     *  Read a single color register and split it into
546     *  colors/transparent. Return != 0 for invalid regno.
547     */
548
549static int vfb_getcolreg(u_int regno, u_int *red, u_int *green, u_int *blue,
550                         u_int *transp, struct fb_info *info)
551{
552    if (regno > 255)
553	return 1;
554    *red = (palette[regno].red<<8) | palette[regno].red;
555    *green = (palette[regno].green<<8) | palette[regno].green;
556    *blue = (palette[regno].blue<<8) | palette[regno].blue;
557    *transp = 0;
558    return 0;
559}
560
561
562    /*
563     *  Set a single color register. The values supplied are already
564     *  rounded down to the hardware's capabilities (according to the
565     *  entries in the var structure). Return != 0 for invalid regno.
566     */
567
568static int vfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
569                         u_int transp, struct fb_info *info)
570{
571    if (regno > 255)
572	return 1;
573    red >>= 8;
574    green >>= 8;
575    blue >>= 8;
576    palette[regno].red = red;
577    palette[regno].green = green;
578    palette[regno].blue = blue;
579    return 0;
580}
581
582
583static void do_install_cmap(int con, struct fb_info *info)
584{
585    if (con != currcon)
586	return;
587    if (fb_display[con].cmap.len)
588	fb_set_cmap(&fb_display[con].cmap, 1, vfb_setcolreg, info);
589    else
590	fb_set_cmap(fb_default_cmap(1<<fb_display[con].var.bits_per_pixel), 1,
591		    vfb_setcolreg, info);
592}
593
594
595#ifdef MODULE
596MODULE_LICENSE("GPL");
597
598int init_module(void)
599{
600    return vfb_init();
601}
602
603void cleanup_module(void)
604{
605    unregister_framebuffer(&fb_info);
606    vfree((void *)videomemory);
607}
608
609#endif /* MODULE */
610