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