xref: /openbmc/linux/drivers/video/fbdev/68328fb.c (revision 62e7ca52)
1 /*
2  *  linux/drivers/video/68328fb.c -- Low level implementation of the
3  *                                   mc68x328 LCD frame buffer device
4  *
5  *	Copyright (C) 2003 Georges Menie
6  *
7  *  This driver assumes an already configured controller (e.g. from config.c)
8  *  Keep the code clean of board specific initialization.
9  *
10  *  This code has not been tested with colors, colormap management functions
11  *  are minimal (no colormap data written to the 68328 registers...)
12  *
13  *  initial version of this driver:
14  *    Copyright (C) 1998,1999 Kenneth Albanowski <kjahds@kjahds.com>,
15  *                            The Silver Hammer Group, Ltd.
16  *
17  *  this version is based on :
18  *
19  *  linux/drivers/video/vfb.c -- Virtual frame buffer device
20  *
21  *      Copyright (C) 2002 James Simmons
22  *
23  *	Copyright (C) 1997 Geert Uytterhoeven
24  *
25  *  This file is subject to the terms and conditions of the GNU General Public
26  *  License. See the file COPYING in the main directory of this archive for
27  *  more details.
28  */
29 
30 #include <linux/module.h>
31 #include <linux/kernel.h>
32 #include <linux/errno.h>
33 #include <linux/string.h>
34 #include <linux/mm.h>
35 #include <linux/vmalloc.h>
36 #include <linux/delay.h>
37 #include <linux/interrupt.h>
38 #include <asm/uaccess.h>
39 #include <linux/fb.h>
40 #include <linux/init.h>
41 
42 #if defined(CONFIG_M68VZ328)
43 #include <asm/MC68VZ328.h>
44 #elif defined(CONFIG_M68EZ328)
45 #include <asm/MC68EZ328.h>
46 #elif defined(CONFIG_M68328)
47 #include <asm/MC68328.h>
48 #else
49 #error wrong architecture for the MC68x328 frame buffer device
50 #endif
51 
52 #if defined(CONFIG_FB_68328_INVERT)
53 #define MC68X328FB_MONO_VISUAL FB_VISUAL_MONO01
54 #else
55 #define MC68X328FB_MONO_VISUAL FB_VISUAL_MONO10
56 #endif
57 
58 static u_long videomemory;
59 static u_long videomemorysize;
60 
61 static struct fb_info fb_info;
62 static u32 mc68x328fb_pseudo_palette[16];
63 
64 static struct fb_var_screeninfo mc68x328fb_default __initdata = {
65 	.red =		{ 0, 8, 0 },
66       	.green =	{ 0, 8, 0 },
67       	.blue =		{ 0, 8, 0 },
68       	.activate =	FB_ACTIVATE_TEST,
69       	.height =	-1,
70       	.width =	-1,
71       	.pixclock =	20000,
72       	.left_margin =	64,
73       	.right_margin =	64,
74       	.upper_margin =	32,
75       	.lower_margin =	32,
76       	.hsync_len =	64,
77       	.vsync_len =	2,
78       	.vmode =	FB_VMODE_NONINTERLACED,
79 };
80 
81 static struct fb_fix_screeninfo mc68x328fb_fix __initdata = {
82 	.id =		"68328fb",
83 	.type =		FB_TYPE_PACKED_PIXELS,
84 	.xpanstep =	1,
85 	.ypanstep =	1,
86 	.ywrapstep =	1,
87 	.accel =	FB_ACCEL_NONE,
88 };
89 
90     /*
91      *  Interface used by the world
92      */
93 int mc68x328fb_init(void);
94 int mc68x328fb_setup(char *);
95 
96 static int mc68x328fb_check_var(struct fb_var_screeninfo *var,
97 			 struct fb_info *info);
98 static int mc68x328fb_set_par(struct fb_info *info);
99 static int mc68x328fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
100 			 u_int transp, struct fb_info *info);
101 static int mc68x328fb_pan_display(struct fb_var_screeninfo *var,
102 			   struct fb_info *info);
103 static int mc68x328fb_mmap(struct fb_info *info, struct vm_area_struct *vma);
104 
105 static struct fb_ops mc68x328fb_ops = {
106 	.fb_check_var	= mc68x328fb_check_var,
107 	.fb_set_par	= mc68x328fb_set_par,
108 	.fb_setcolreg	= mc68x328fb_setcolreg,
109 	.fb_pan_display	= mc68x328fb_pan_display,
110 	.fb_fillrect	= cfb_fillrect,
111 	.fb_copyarea	= cfb_copyarea,
112 	.fb_imageblit	= cfb_imageblit,
113 	.fb_mmap	= mc68x328fb_mmap,
114 };
115 
116     /*
117      *  Internal routines
118      */
119 
120 static u_long get_line_length(int xres_virtual, int bpp)
121 {
122 	u_long length;
123 
124 	length = xres_virtual * bpp;
125 	length = (length + 31) & ~31;
126 	length >>= 3;
127 	return (length);
128 }
129 
130     /*
131      *  Setting the video mode has been split into two parts.
132      *  First part, xxxfb_check_var, must not write anything
133      *  to hardware, it should only verify and adjust var.
134      *  This means it doesn't alter par but it does use hardware
135      *  data from it to check this var.
136      */
137 
138 static int mc68x328fb_check_var(struct fb_var_screeninfo *var,
139 			 struct fb_info *info)
140 {
141 	u_long line_length;
142 
143 	/*
144 	 *  FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal!
145 	 *  as FB_VMODE_SMOOTH_XPAN is only used internally
146 	 */
147 
148 	if (var->vmode & FB_VMODE_CONUPDATE) {
149 		var->vmode |= FB_VMODE_YWRAP;
150 		var->xoffset = info->var.xoffset;
151 		var->yoffset = info->var.yoffset;
152 	}
153 
154 	/*
155 	 *  Some very basic checks
156 	 */
157 	if (!var->xres)
158 		var->xres = 1;
159 	if (!var->yres)
160 		var->yres = 1;
161 	if (var->xres > var->xres_virtual)
162 		var->xres_virtual = var->xres;
163 	if (var->yres > var->yres_virtual)
164 		var->yres_virtual = var->yres;
165 	if (var->bits_per_pixel <= 1)
166 		var->bits_per_pixel = 1;
167 	else if (var->bits_per_pixel <= 8)
168 		var->bits_per_pixel = 8;
169 	else if (var->bits_per_pixel <= 16)
170 		var->bits_per_pixel = 16;
171 	else if (var->bits_per_pixel <= 24)
172 		var->bits_per_pixel = 24;
173 	else if (var->bits_per_pixel <= 32)
174 		var->bits_per_pixel = 32;
175 	else
176 		return -EINVAL;
177 
178 	if (var->xres_virtual < var->xoffset + var->xres)
179 		var->xres_virtual = var->xoffset + var->xres;
180 	if (var->yres_virtual < var->yoffset + var->yres)
181 		var->yres_virtual = var->yoffset + var->yres;
182 
183 	/*
184 	 *  Memory limit
185 	 */
186 	line_length =
187 	    get_line_length(var->xres_virtual, var->bits_per_pixel);
188 	if (line_length * var->yres_virtual > videomemorysize)
189 		return -ENOMEM;
190 
191 	/*
192 	 * Now that we checked it we alter var. The reason being is that the video
193 	 * mode passed in might not work but slight changes to it might make it
194 	 * work. This way we let the user know what is acceptable.
195 	 */
196 	switch (var->bits_per_pixel) {
197 	case 1:
198 		var->red.offset = 0;
199 		var->red.length = 1;
200 		var->green.offset = 0;
201 		var->green.length = 1;
202 		var->blue.offset = 0;
203 		var->blue.length = 1;
204 		var->transp.offset = 0;
205 		var->transp.length = 0;
206 		break;
207 	case 8:
208 		var->red.offset = 0;
209 		var->red.length = 8;
210 		var->green.offset = 0;
211 		var->green.length = 8;
212 		var->blue.offset = 0;
213 		var->blue.length = 8;
214 		var->transp.offset = 0;
215 		var->transp.length = 0;
216 		break;
217 	case 16:		/* RGBA 5551 */
218 		if (var->transp.length) {
219 			var->red.offset = 0;
220 			var->red.length = 5;
221 			var->green.offset = 5;
222 			var->green.length = 5;
223 			var->blue.offset = 10;
224 			var->blue.length = 5;
225 			var->transp.offset = 15;
226 			var->transp.length = 1;
227 		} else {	/* RGB 565 */
228 			var->red.offset = 0;
229 			var->red.length = 5;
230 			var->green.offset = 5;
231 			var->green.length = 6;
232 			var->blue.offset = 11;
233 			var->blue.length = 5;
234 			var->transp.offset = 0;
235 			var->transp.length = 0;
236 		}
237 		break;
238 	case 24:		/* RGB 888 */
239 		var->red.offset = 0;
240 		var->red.length = 8;
241 		var->green.offset = 8;
242 		var->green.length = 8;
243 		var->blue.offset = 16;
244 		var->blue.length = 8;
245 		var->transp.offset = 0;
246 		var->transp.length = 0;
247 		break;
248 	case 32:		/* RGBA 8888 */
249 		var->red.offset = 0;
250 		var->red.length = 8;
251 		var->green.offset = 8;
252 		var->green.length = 8;
253 		var->blue.offset = 16;
254 		var->blue.length = 8;
255 		var->transp.offset = 24;
256 		var->transp.length = 8;
257 		break;
258 	}
259 	var->red.msb_right = 0;
260 	var->green.msb_right = 0;
261 	var->blue.msb_right = 0;
262 	var->transp.msb_right = 0;
263 
264 	return 0;
265 }
266 
267 /* This routine actually sets the video mode. It's in here where we
268  * the hardware state info->par and fix which can be affected by the
269  * change in par. For this driver it doesn't do much.
270  */
271 static int mc68x328fb_set_par(struct fb_info *info)
272 {
273 	info->fix.line_length = get_line_length(info->var.xres_virtual,
274 						info->var.bits_per_pixel);
275 	return 0;
276 }
277 
278     /*
279      *  Set a single color register. The values supplied are already
280      *  rounded down to the hardware's capabilities (according to the
281      *  entries in the var structure). Return != 0 for invalid regno.
282      */
283 
284 static int mc68x328fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
285 			 u_int transp, struct fb_info *info)
286 {
287 	if (regno >= 256)	/* no. of hw registers */
288 		return 1;
289 	/*
290 	 * Program hardware... do anything you want with transp
291 	 */
292 
293 	/* grayscale works only partially under directcolor */
294 	if (info->var.grayscale) {
295 		/* grayscale = 0.30*R + 0.59*G + 0.11*B */
296 		red = green = blue =
297 		    (red * 77 + green * 151 + blue * 28) >> 8;
298 	}
299 
300 	/* Directcolor:
301 	 *   var->{color}.offset contains start of bitfield
302 	 *   var->{color}.length contains length of bitfield
303 	 *   {hardwarespecific} contains width of RAMDAC
304 	 *   cmap[X] is programmed to (X << red.offset) | (X << green.offset) | (X << blue.offset)
305 	 *   RAMDAC[X] is programmed to (red, green, blue)
306 	 *
307 	 * Pseudocolor:
308 	 *    uses offset = 0 && length = RAMDAC register width.
309 	 *    var->{color}.offset is 0
310 	 *    var->{color}.length contains width of DAC
311 	 *    cmap is not used
312 	 *    RAMDAC[X] is programmed to (red, green, blue)
313 	 * Truecolor:
314 	 *    does not use DAC. Usually 3 are present.
315 	 *    var->{color}.offset contains start of bitfield
316 	 *    var->{color}.length contains length of bitfield
317 	 *    cmap is programmed to (red << red.offset) | (green << green.offset) |
318 	 *                      (blue << blue.offset) | (transp << transp.offset)
319 	 *    RAMDAC does not exist
320 	 */
321 #define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16)
322 	switch (info->fix.visual) {
323 	case FB_VISUAL_TRUECOLOR:
324 	case FB_VISUAL_PSEUDOCOLOR:
325 		red = CNVT_TOHW(red, info->var.red.length);
326 		green = CNVT_TOHW(green, info->var.green.length);
327 		blue = CNVT_TOHW(blue, info->var.blue.length);
328 		transp = CNVT_TOHW(transp, info->var.transp.length);
329 		break;
330 	case FB_VISUAL_DIRECTCOLOR:
331 		red = CNVT_TOHW(red, 8);	/* expect 8 bit DAC */
332 		green = CNVT_TOHW(green, 8);
333 		blue = CNVT_TOHW(blue, 8);
334 		/* hey, there is bug in transp handling... */
335 		transp = CNVT_TOHW(transp, 8);
336 		break;
337 	}
338 #undef CNVT_TOHW
339 	/* Truecolor has hardware independent palette */
340 	if (info->fix.visual == FB_VISUAL_TRUECOLOR) {
341 		u32 v;
342 
343 		if (regno >= 16)
344 			return 1;
345 
346 		v = (red << info->var.red.offset) |
347 		    (green << info->var.green.offset) |
348 		    (blue << info->var.blue.offset) |
349 		    (transp << info->var.transp.offset);
350 		switch (info->var.bits_per_pixel) {
351 		case 8:
352 			break;
353 		case 16:
354 			((u32 *) (info->pseudo_palette))[regno] = v;
355 			break;
356 		case 24:
357 		case 32:
358 			((u32 *) (info->pseudo_palette))[regno] = v;
359 			break;
360 		}
361 		return 0;
362 	}
363 	return 0;
364 }
365 
366     /*
367      *  Pan or Wrap the Display
368      *
369      *  This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag
370      */
371 
372 static int mc68x328fb_pan_display(struct fb_var_screeninfo *var,
373 			   struct fb_info *info)
374 {
375 	if (var->vmode & FB_VMODE_YWRAP) {
376 		if (var->yoffset < 0
377 		    || var->yoffset >= info->var.yres_virtual
378 		    || var->xoffset)
379 			return -EINVAL;
380 	} else {
381 		if (var->xoffset + info->var.xres > info->var.xres_virtual ||
382 		    var->yoffset + info->var.yres > info->var.yres_virtual)
383 			return -EINVAL;
384 	}
385 	info->var.xoffset = var->xoffset;
386 	info->var.yoffset = var->yoffset;
387 	if (var->vmode & FB_VMODE_YWRAP)
388 		info->var.vmode |= FB_VMODE_YWRAP;
389 	else
390 		info->var.vmode &= ~FB_VMODE_YWRAP;
391 	return 0;
392 }
393 
394     /*
395      *  Most drivers don't need their own mmap function
396      */
397 
398 static int mc68x328fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
399 {
400 #ifndef MMU
401 	/* this is uClinux (no MMU) specific code */
402 
403 	vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
404 	vma->vm_start = videomemory;
405 
406 	return 0;
407 #else
408 	return -EINVAL;
409 #endif
410 }
411 
412 int __init mc68x328fb_setup(char *options)
413 {
414 #if 0
415 	char *this_opt;
416 #endif
417 
418 	if (!options || !*options)
419 		return 1;
420 #if 0
421 	while ((this_opt = strsep(&options, ",")) != NULL) {
422 		if (!*this_opt)
423 			continue;
424 		if (!strncmp(this_opt, "disable", 7))
425 			mc68x328fb_enable = 0;
426 	}
427 #endif
428 	return 1;
429 }
430 
431     /*
432      *  Initialisation
433      */
434 
435 int __init mc68x328fb_init(void)
436 {
437 #ifndef MODULE
438 	char *option = NULL;
439 
440 	if (fb_get_options("68328fb", &option))
441 		return -ENODEV;
442 	mc68x328fb_setup(option);
443 #endif
444 	/*
445 	 *  initialize the default mode from the LCD controller registers
446 	 */
447 	mc68x328fb_default.xres = LXMAX;
448 	mc68x328fb_default.yres = LYMAX+1;
449 	mc68x328fb_default.xres_virtual = mc68x328fb_default.xres;
450 	mc68x328fb_default.yres_virtual = mc68x328fb_default.yres;
451 	mc68x328fb_default.bits_per_pixel = 1 + (LPICF & 0x01);
452 	videomemory = LSSA;
453 	videomemorysize = (mc68x328fb_default.xres_virtual+7) / 8 *
454 		mc68x328fb_default.yres_virtual * mc68x328fb_default.bits_per_pixel;
455 
456 	fb_info.screen_base = (void *)videomemory;
457 	fb_info.fbops = &mc68x328fb_ops;
458 	fb_info.var = mc68x328fb_default;
459 	fb_info.fix = mc68x328fb_fix;
460 	fb_info.fix.smem_start = videomemory;
461 	fb_info.fix.smem_len = videomemorysize;
462 	fb_info.fix.line_length =
463 		get_line_length(mc68x328fb_default.xres_virtual, mc68x328fb_default.bits_per_pixel);
464 	fb_info.fix.visual = (mc68x328fb_default.bits_per_pixel) == 1 ?
465 		MC68X328FB_MONO_VISUAL : FB_VISUAL_PSEUDOCOLOR;
466 	if (fb_info.var.bits_per_pixel == 1) {
467 		fb_info.var.red.length = fb_info.var.green.length = fb_info.var.blue.length = 1;
468 		fb_info.var.red.offset = fb_info.var.green.offset = fb_info.var.blue.offset = 0;
469 	}
470 	fb_info.pseudo_palette = &mc68x328fb_pseudo_palette;
471 	fb_info.flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN;
472 
473 	if (fb_alloc_cmap(&fb_info.cmap, 256, 0))
474 		return -ENOMEM;
475 
476 	if (register_framebuffer(&fb_info) < 0) {
477 		fb_dealloc_cmap(&fb_info.cmap);
478 		return -EINVAL;
479 	}
480 
481 	fb_info(&fb_info, "%s frame buffer device\n", fb_info.fix.id);
482 	fb_info(&fb_info, "%dx%dx%d at 0x%08lx\n",
483 		mc68x328fb_default.xres_virtual,
484 		mc68x328fb_default.yres_virtual,
485 		1 << mc68x328fb_default.bits_per_pixel, videomemory);
486 
487 	return 0;
488 }
489 
490 module_init(mc68x328fb_init);
491 
492 #ifdef MODULE
493 
494 static void __exit mc68x328fb_cleanup(void)
495 {
496 	unregister_framebuffer(&fb_info);
497 	fb_dealloc_cmap(&fb_info.cmap);
498 }
499 
500 module_exit(mc68x328fb_cleanup);
501 
502 MODULE_LICENSE("GPL");
503 #endif				/* MODULE */
504