File: /usr/src/linux/drivers/video/sa1100fb.c
1 /*
2 * linux/drivers/video/sa1100fb.c
3 *
4 * Copyright (C) 1999 Eric A. Thomas
5 * Based on acornfb.c Copyright (C) Russell King.
6 *
7 * This file is subject to the terms and conditions of the GNU General Public
8 * License. See the file COPYING in the main directory of this archive for
9 * more details.
10 *
11 * StrongARM 1100 LCD Controller Frame Buffer Driver
12 *
13 * Please direct your questions and comments on this driver to the following
14 * email address:
15 *
16 * linux-arm-kernel@lists.arm.linux.org.uk
17 *
18 * Clean patches should be sent to the ARM Linux Patch System. Please see the
19 * following web page for more information:
20 *
21 * http://www.arm.linux.org.uk/developer/patches/info.shtml
22 *
23 * Thank you.
24 *
25 * Known problems:
26 * - With the Neponset plugged into an Assabet, LCD powerdown
27 * doesn't work (LCD stays powered up). Therefore we shouldn't
28 * blank the screen.
29 * - We don't limit the CPU clock rate nor the mode selection
30 * according to the available SDRAM bandwidth.
31 *
32 *
33 * Code Status:
34 * 1999/04/01:
35 * - Driver appears to be working for Brutus 320x200x8bpp mode. Other
36 * resolutions are working, but only the 8bpp mode is supported.
37 * Changes need to be made to the palette encode and decode routines
38 * to support 4 and 16 bpp modes.
39 * Driver is not designed to be a module. The FrameBuffer is statically
40 * allocated since dynamic allocation of a 300k buffer cannot be
41 * guaranteed.
42 *
43 * 1999/06/17:
44 * - FrameBuffer memory is now allocated at run-time when the
45 * driver is initialized.
46 *
47 * 2000/04/10: Nicolas Pitre <nico@cam.org>
48 * - Big cleanup for dynamic selection of machine type at run time.
49 *
50 * 2000/07/19: Jamey Hicks <jamey@crl.dec.com>
51 * - Support for Bitsy aka Compaq iPAQ H3600 added.
52 *
53 * 2000/08/07: Tak-Shing Chan <tchan.rd@idthk.com>
54 * Jeff Sutherland <jsutherland@accelent.com>
55 * - Resolved an issue caused by a change made to the Assabet's PLD
56 * earlier this year which broke the framebuffer driver for newer
57 * Phase 4 Assabets. Some other parameters were changed to optimize
58 * for the Sharp display.
59 *
60 * 2000/08/09: Kunihiko IMAI <imai@vasara.co.jp>
61 * - XP860 support added
62 *
63 * 2000/08/19: Mark Huang <mhuang@livetoy.com>
64 * - Allows standard options to be passed on the kernel command line
65 * for most common passive displays.
66 *
67 * 2000/08/29:
68 * - s/save_flags_cli/local_irq_save/
69 * - remove unneeded extra save_flags_cli in sa1100fb_enable_lcd_controller
70 *
71 * 2000/10/10: Erik Mouw <J.A.K.Mouw@its.tudelft.nl>
72 * - Updated LART stuff. Fixed some minor bugs.
73 *
74 * 2000/10/30: Murphy Chen <murphy@mail.dialogue.com.tw>
75 * - Pangolin support added
76 *
77 * 2000/10/31: Roman Jordan <jor@hoeft-wessel.de>
78 * - Huw Webpanel support added
79 *
80 * 2000/11/23: Eric Peng <ericpeng@coventive.com>
81 * - Freebird add
82 *
83 * 2001/02/07: Jamey Hicks <jamey.hicks@compaq.com>
84 * Cliff Brake <cbrake@accelent.com>
85 * - Added PM callback
86 *
87 * 2001/05/26: <rmk@arm.linux.org.uk>
88 * - Fix 16bpp so that (a) we use the right colours rather than some
89 * totally random colour depending on what was in page 0, and (b)
90 * we don't de-reference a NULL pointer.
91 * - remove duplicated implementation of consistent_alloc()
92 * - convert dma address types to dma_addr_t
93 * - remove unused 'montype' stuff
94 * - remove redundant zero inits of init_var after the initial
95 * memzero.
96 * - remove allow_modeset (acornfb idea does not belong here)
97 *
98 * 2001/05/28: <rmk@arm.linux.org.uk>
99 * - massive cleanup - move machine dependent data into structures
100 * - I've left various #warnings in - if you see one, and know
101 * the hardware concerned, please get in contact with me.
102 *
103 * 2001/05/31: <rmk@arm.linux.org.uk>
104 * - Fix LCCR1 HSW value, fix all machine type specifications to
105 * keep values in line. (Please check your machine type specs)
106 *
107 * 2001/06/10: <rmk@arm.linux.org.uk>
108 * - Fiddle with the LCD controller from task context only; mainly
109 * so that we can run with interrupts on, and sleep.
110 * - Convert #warnings into #errors. No pain, no gain. ;)
111 *
112 * 2001/06/14: <rmk@arm.linux.org.uk>
113 * - Make the palette BPS value for 12bpp come out correctly.
114 * - Take notice of "greyscale" on any colour depth.
115 * - Make truecolor visuals use the RGB channel encoding information.
116 *
117 * 2001/07/02: <rmk@arm.linux.org.uk>
118 * - Fix colourmap problems.
119 *
120 * 2001/07/13: <abraham@2d3d.co.za>
121 * - Added support for the ICP LCD-Kit01 on LART. This LCD is
122 * manufactured by Prime View, model no V16C6448AB
123 *
124 * 2001/07/23: <rmk@arm.linux.org.uk>
125 * - Hand merge version from handhelds.org CVS tree. See patch
126 * notes for 595/1 for more information.
127 * - Drop 12bpp (it's 16bpp with different colour register mappings).
128 * - This hardware can not do direct colour. Therefore we don't
129 * support it.
130 *
131 * 2001/07/27: <rmk@arm.linux.org.uk>
132 * - Halve YRES on dual scan LCDs.
133 */
134
135 #include <linux/config.h>
136 #include <linux/module.h>
137 #include <linux/kernel.h>
138 #include <linux/sched.h>
139 #include <linux/errno.h>
140 #include <linux/string.h>
141 #include <linux/interrupt.h>
142 #include <linux/slab.h>
143 #include <linux/fb.h>
144 #include <linux/delay.h>
145 #include <linux/pm.h>
146 #include <linux/init.h>
147 #include <linux/cpufreq.h>
148
149 #include <asm/hardware.h>
150 #include <asm/io.h>
151 #include <asm/irq.h>
152 #include <asm/mach-types.h>
153 #include <asm/uaccess.h>
154
155 #include <video/fbcon.h>
156 #include <video/fbcon-mfb.h>
157 #include <video/fbcon-cfb4.h>
158 #include <video/fbcon-cfb8.h>
159 #include <video/fbcon-cfb16.h>
160
161 /*
162 * enable this if your panel appears to have broken
163 */
164 #undef CHECK_COMPAT
165
166 /*
167 * debugging?
168 */
169 #define DEBUG 0
170 /*
171 * Complain if VAR is out of range.
172 */
173 #define DEBUG_VAR 1
174
175 #undef ASSABET_PAL_VIDEO
176
177 #include "sa1100fb.h"
178
179 void (*sa1100fb_blank_helper)(int blank);
180 EXPORT_SYMBOL(sa1100fb_blank_helper);
181
182
183 #ifdef CHECK_COMPAT
184 static void
185 sa1100fb_check_shadow(struct sa1100fb_lcd_reg *new_regs,
186 struct fb_var_screeninfo *var, u_int pcd)
187 {
188 struct sa1100fb_lcd_reg shadow;
189 int different = 0;
190
191 /*
192 * These machines are good machines!
193 */
194 if (machine_is_assabet() || machine_is_bitsy())
195 return;
196
197 /*
198 * The following ones are bad, bad, bad.
199 * Please make yours good!
200 */
201 if (machine_is_pangolin()) {
202 DPRINTK("Configuring Pangolin LCD\n");
203 shadow.lccr0 =
204 LCCR0_LEN + LCCR0_Color + LCCR0_LDM +
205 LCCR0_BAM + LCCR0_ERM + LCCR0_Act +
206 LCCR0_LtlEnd + LCCR0_DMADel(0);
207 shadow.lccr1 =
208 LCCR1_DisWdth(var->xres) + LCCR1_HorSnchWdth(64) +
209 LCCR1_BegLnDel(160) + LCCR1_EndLnDel(24);
210 shadow.lccr2 =
211 LCCR2_DisHght(var->yres) + LCCR2_VrtSnchWdth(7) +
212 LCCR2_BegFrmDel(7) + LCCR2_EndFrmDel(1);
213 shadow.lccr3 =
214 LCCR3_PixClkDiv(pcd) + LCCR3_HorSnchH +
215 LCCR3_VrtSnchH + LCCR3_PixFlEdg + LCCR3_OutEnH;
216
217 DPRINTK("pcd = %x, PixCldDiv(pcd)=%x\n",
218 pcd, LCCR3_PixClkDiv(pcd));
219 }
220 if (machine_is_freebird()) {
221 DPRINTK("Configuring Freebird LCD\n");
222 #if 1
223 shadow.lccr0 = 0x00000038;
224 shadow.lccr1 = 0x010108e0;
225 shadow.lccr2 = 0x0000053f;
226 shadow.lccr3 = 0x00000c20;
227 #else
228 shadow.lccr0 =
229 LCCR0_LEN + LCCR0_Color + LCCR0_Sngl +
230 LCCR0_LDM + LCCR0_BAM + LCCR0_ERM + LCCR0_Pas +
231 LCCR0_LtlEnd + LCCR0_DMADel(0);
232 /* Check ,Chester */
233 shadow.lccr1 =
234 LCCR1_DisWdth(var->xres) + LCCR1_HorSnchWdth(5) +
235 LCCR1_BegLnDel(61) + LCCR1_EndLnDel(9);
236 /* Check ,Chester */
237 shadow.lccr2 =
238 LCCR2_DisHght(var->yres) + LCCR2_VrtSnchWdth(1) +
239 LCCR2_BegFrmDel(3) + LCCR2_EndFrmDel(0);
240 /* Check ,Chester */
241 shadow.lccr3 =
242 LCCR3_OutEnH + LCCR3_PixFlEdg + LCCR3_VrtSnchH +
243 LCCR3_HorSnchH + LCCR3_ACBsCntOff +
244 LCCR3_ACBsDiv(2) + LCCR3_PixClkDiv(pcd);
245 #endif
246 }
247 if (machine_is_brutus()) {
248 DPRINTK("Configuring Brutus LCD\n");
249 shadow.lccr0 =
250 LCCR0_LEN + LCCR0_Color + LCCR0_Sngl + LCCR0_Pas +
251 LCCR0_LtlEnd + LCCR0_LDM + LCCR0_BAM + LCCR0_ERM +
252 LCCR0_DMADel(0);
253 shadow.lccr1 =
254 LCCR1_DisWdth(var->xres) + LCCR1_HorSnchWdth(3) +
255 LCCR1_BegLnDel(41) + LCCR1_EndLnDel(101);
256 shadow.lccr2 =
257 LCCR2_DisHght(var->yres) + LCCR2_VrtSnchWdth(1) +
258 LCCR2_BegFrmDel(0) + LCCR2_EndFrmDel(0);
259 shadow.lccr3 =
260 LCCR3_OutEnH + LCCR3_PixRsEdg + LCCR3_VrtSnchH +
261 LCCR3_HorSnchH + LCCR3_ACBsCntOff +
262 LCCR3_ACBsDiv(2) + LCCR3_PixClkDiv(44);
263 }
264 if (machine_is_huw_webpanel()) {
265 DPRINTK("Configuring HuW LCD\n");
266 shadow.lccr0 = LCCR0_LEN + LCCR0_Dual + LCCR0_LDM;
267 shadow.lccr1 = LCCR1_DisWdth(var->xres) +
268 LCCR1_HorSnchWdth(3) +
269 LCCR1_BegLnDel(41) + LCCR1_EndLnDel(101);
270 shadow.lccr2 = 239 + LCCR2_VrtSnchWdth(1);
271 shadow.lccr3 = 8 + LCCR3_OutEnH +
272 LCCR3_PixRsEdg + LCCR3_VrtSnchH +
273 LCCR3_HorSnchH + LCCR3_ACBsCntOff + LCCR3_ACBsDiv(2);
274 }
275 #ifdef CONFIG_SA1100_CERF
276 if (machine_is_cerf()) {
277 DPRINTK("Configuring Cerf LCD\n");
278 #if defined (CONFIG_CERF_LCD_72_A)
279 shadow.lccr0 =
280 LCCR0_LEN + LCCR0_Color + LCCR0_Dual +
281 LCCR0_LDM + LCCR0_BAM + LCCR0_ERM + LCCR0_Pas +
282 LCCR0_LtlEnd + LCCR0_DMADel(0);
283 shadow.lccr1 =
284 LCCR1_DisWdth(var->xres) + LCCR1_HorSnchWdth(5) +
285 LCCR1_BegLnDel(61) + LCCR1_EndLnDel(9);
286 shadow.lccr2 =
287 LCCR2_DisHght(var->yres / 2) + LCCR2_VrtSnchWdth(1) +
288 LCCR2_BegFrmDel(3) + LCCR2_EndFrmDel(0);
289 shadow.lccr3 =
290 LCCR3_OutEnH + LCCR3_PixRsEdg + LCCR3_VrtSnchH +
291 LCCR3_HorSnchH + LCCR3_ACBsCntOff +
292 LCCR3_ACBsDiv(2) + LCCR3_PixClkDiv(38);
293 #elif defined (CONFIG_CERF_LCD_57_A)
294 shadow.lccr0 =
295 LCCR0_LEN + LCCR0_Color + LCCR0_Sngl +
296 LCCR0_LDM + LCCR0_BAM + LCCR0_ERM + LCCR0_Pas +
297 LCCR0_LtlEnd + LCCR0_DMADel(0);
298 shadow.lccr1 =
299 LCCR1_DisWdth(var->xres) + LCCR1_HorSnchWdth(5) +
300 LCCR1_BegLnDel(61) + LCCR1_EndLnDel(9);
301 shadow.lccr2 =
302 LCCR2_DisHght(var->yres) + LCCR2_VrtSnchWdth(1) +
303 LCCR2_BegFrmDel(3) + LCCR2_EndFrmDel(0);
304 shadow.lccr3 =
305 LCCR3_OutEnH + LCCR3_PixRsEdg + LCCR3_VrtSnchH +
306 LCCR3_HorSnchH + LCCR3_ACBsCntOff +
307 LCCR3_ACBsDiv(2) + LCCR3_PixClkDiv(38);
308 #elif defined (CONFIG_CERF_LCD_38_A)
309 shadow.lccr0 =
310 LCCR0_LEN + LCCR0_Color + LCCR0_Sngl +
311 LCCR0_LDM + LCCR0_BAM + LCCR0_ERM + LCCR0_Pas +
312 LCCR0_LtlEnd + LCCR0_DMADel(0);
313 shadow.lccr1 =
314 LCCR1_DisWdth(var->xres) + LCCR1_HorSnchWdth(5) +
315 LCCR1_BegLnDel(61) + LCCR1_EndLnDel(9);
316 shadow.lccr2 =
317 LCCR2_DisHght(var->yres) + LCCR2_VrtSnchWdth(1) +
318 LCCR2_BegFrmDel(3) + LCCR2_EndFrmDel(0);
319 shadow.lccr3 =
320 LCCR3_OutEnH + LCCR3_PixRsEdg + LCCR3_VrtSnchH +
321 LCCR3_HorSnchH + LCCR3_ACBsCntOff +
322 LCCR3_ACBsDiv(2) + LCCR3_PixClkDiv(38);
323 #else
324 #error "Must have a CerfBoard LCD form factor selected"
325 #endif
326 }
327 #endif
328 if (machine_is_lart()) {
329 DPRINTK("Configuring LART LCD\n");
330 #if defined LART_GREY_LCD
331 shadow.lccr0 =
332 LCCR0_LEN + LCCR0_Mono + LCCR0_Sngl + LCCR0_Pas +
333 LCCR0_LtlEnd + LCCR0_LDM + LCCR0_BAM + LCCR0_ERM +
334 LCCR0_DMADel(0);
335 shadow.lccr1 =
336 LCCR1_DisWdth(var->xres) + LCCR1_HorSnchWdth(1) +
337 LCCR1_BegLnDel(4) + LCCR1_EndLnDel(2);
338 shadow.lccr2 =
339 LCCR2_DisHght(var->yres) + LCCR2_VrtSnchWdth(1) +
340 LCCR2_BegFrmDel(0) + LCCR2_EndFrmDel(0);
341 shadow.lccr3 =
342 LCCR3_PixClkDiv(34) + LCCR3_ACBsDiv(512) +
343 LCCR3_ACBsCntOff + LCCR3_HorSnchH + LCCR3_VrtSnchH;
344 #endif
345 #if defined LART_COLOR_LCD
346 shadow.lccr0 =
347 LCCR0_LEN + LCCR0_Color + LCCR0_Sngl + LCCR0_Act +
348 LCCR0_LtlEnd + LCCR0_LDM + LCCR0_BAM + LCCR0_ERM +
349 LCCR0_DMADel(0);
350 shadow.lccr1 =
351 LCCR1_DisWdth(var->xres) + LCCR1_HorSnchWdth(2) +
352 LCCR1_BegLnDel(69) + LCCR1_EndLnDel(8);
353 shadow.lccr2 =
354 LCCR2_DisHght(var->yres) + LCCR2_VrtSnchWdth(3) +
355 LCCR2_BegFrmDel(14) + LCCR2_EndFrmDel(4);
356 shadow.lccr3 =
357 LCCR3_PixClkDiv(34) + LCCR3_ACBsDiv(512) +
358 LCCR3_ACBsCntOff + LCCR3_HorSnchL + LCCR3_VrtSnchL +
359 LCCR3_PixFlEdg;
360 #endif
361 #if defined LART_VIDEO_OUT
362 shadow.lccr0 =
363 LCCR0_LEN + LCCR0_Color + LCCR0_Sngl + LCCR0_Act +
364 LCCR0_LtlEnd + LCCR0_LDM + LCCR0_BAM + LCCR0_ERM +
365 LCCR0_DMADel(0);
366 shadow.lccr1 =
367 LCCR1_DisWdth(640) + LCCR1_HorSnchWdth(95) +
368 LCCR1_BegLnDel(40) + LCCR1_EndLnDel(24);
369 shadow.lccr2 =
370 LCCR2_DisHght(480) + LCCR2_VrtSnchWdth(2) +
371 LCCR2_BegFrmDel(32) + LCCR2_EndFrmDel(11);
372 shadow.lccr3 =
373 LCCR3_PixClkDiv(8) + LCCR3_ACBsDiv(512) +
374 LCCR3_ACBsCntOff + LCCR3_HorSnchH + LCCR3_VrtSnchH +
375 LCCR3_PixFlEdg + LCCR3_OutEnL;
376 #endif
377 }
378 if (machine_is_graphicsclient()) {
379 DPRINTK("Configuring GraphicsClient LCD\n");
380 shadow.lccr0 =
381 LCCR0_LEN + LCCR0_Color + LCCR0_Sngl + LCCR0_Act;
382 shadow.lccr1 =
383 LCCR1_DisWdth(var->xres) + LCCR1_HorSnchWdth(9) +
384 LCCR1_EndLnDel(54) + LCCR1_BegLnDel(54);
385 shadow.lccr2 =
386 LCCR2_DisHght(var->yres) + LCCR2_VrtSnchWdth(9) +
387 LCCR2_EndFrmDel(32) + LCCR2_BegFrmDel(24);
388 shadow.lccr3 =
389 LCCR3_PixClkDiv(10) + LCCR3_ACBsDiv(2) +
390 LCCR3_ACBsCntOff + LCCR3_HorSnchL + LCCR3_VrtSnchL;
391 }
392 if (machine_is_omnimeter()) {
393 DPRINTK("Configuring OMNI LCD\n");
394 shadow.lccr0 = LCCR0_LEN | LCCR0_CMS | LCCR0_DPD;
395 shadow.lccr1 =
396 LCCR1_BegLnDel(10) + LCCR1_EndLnDel(10) +
397 LCCR1_HorSnchWdth(1) + LCCR1_DisWdth(var->xres);
398 shadow.lccr2 = LCCR2_DisHght(var->yres);
399 shadow.lccr3 =
400 LCCR3_ACBsDiv(0xFF) + LCCR3_PixClkDiv(44);
401 //jca (GetPCD(25) << LCD3_V_PCD);
402 }
403 if (machine_is_xp860()) {
404 DPRINTK("Configuring XP860 LCD\n");
405 shadow.lccr0 =
406 LCCR0_LEN + LCCR0_Color + LCCR0_Sngl + LCCR0_Act +
407 LCCR0_LtlEnd + LCCR0_LDM + LCCR0_ERM + LCCR0_DMADel(0);
408 shadow.lccr1 =
409 LCCR1_DisWdth(var->xres) +
410 LCCR1_HorSnchWdth(var->hsync_len) +
411 LCCR1_BegLnDel(var->left_margin) +
412 LCCR1_EndLnDel(var->right_margin);
413 shadow.lccr2 =
414 LCCR2_DisHght(var->yres) +
415 LCCR2_VrtSnchWdth(var->vsync_len) +
416 LCCR2_BegFrmDel(var->upper_margin) +
417 LCCR2_EndFrmDel(var->lower_margin);
418 shadow.lccr3 =
419 LCCR3_PixClkDiv(6) + LCCR3_HorSnchL + LCCR3_VrtSnchL;
420 }
421
422 /*
423 * Ok, since we're calculating these values, we want to know
424 * if the calculation is correct. If you see any of these
425 * messages _PLEASE_ report the incident to me for diagnosis,
426 * including details about what was happening when the
427 * messages appeared. --rmk, 30 March 2001
428 */
429 if (shadow.lccr0 != new_regs->lccr0) {
430 printk(KERN_ERR "LCCR1 mismatch: 0x%08x != 0x%08x\n",
431 shadow.lccr1, new_regs->lccr1);
432 different = 1;
433 }
434 if (shadow.lccr1 != new_regs->lccr1) {
435 printk(KERN_ERR "LCCR1 mismatch: 0x%08x != 0x%08x\n",
436 shadow.lccr1, new_regs->lccr1);
437 different = 1;
438 }
439 if (shadow.lccr2 != new_regs->lccr2) {
440 printk(KERN_ERR "LCCR2 mismatch: 0x%08x != 0x%08x\n",
441 shadow.lccr2, new_regs->lccr2);
442 different = 1;
443 }
444 if (shadow.lccr3 != new_regs->lccr3) {
445 printk(KERN_ERR "LCCR3 mismatch: 0x%08x != 0x%08x\n",
446 shadow.lccr3, new_regs->lccr3);
447 different = 1;
448 }
449 if (different) {
450 printk(KERN_ERR "var: xres=%d hslen=%d lm=%d rm=%d\n",
451 var->xres, var->hsync_len,
452 var->left_margin, var->right_margin);
453 printk(KERN_ERR "var: yres=%d vslen=%d um=%d bm=%d\n",
454 var->yres, var->vsync_len,
455 var->upper_margin, var->lower_margin);
456
457 printk(KERN_ERR "Please report this to Russell King "
458 "<rmk@arm.linux.org.uk>\n");
459 }
460
461 DPRINTK("olccr0 = 0x%08x\n", shadow.lccr0);
462 DPRINTK("olccr1 = 0x%08x\n", shadow.lccr1);
463 DPRINTK("olccr2 = 0x%08x\n", shadow.lccr2);
464 DPRINTK("olccr3 = 0x%08x\n", shadow.lccr3);
465 }
466 #else
467 #define sa1100fb_check_shadow(regs,var,pcd)
468 #endif
469
470
471
472 /*
473 * IMHO this looks wrong. In 8BPP, length should be 8.
474 */
475 static struct sa1100fb_rgb rgb_8 = {
476 red: { offset: 0, length: 4, },
477 green: { offset: 0, length: 4, },
478 blue: { offset: 0, length: 4, },
479 transp: { offset: 0, length: 0, },
480 };
481
482 static struct sa1100fb_rgb def_rgb_16 = {
483 red: { offset: 11, length: 5, },
484 green: { offset: 5, length: 6, },
485 blue: { offset: 0, length: 5, },
486 transp: { offset: 0, length: 0, },
487 };
488
489 #ifdef CONFIG_SA1100_ASSABET
490 static struct sa1100fb_mach_info assabet_info __initdata = {
491 #ifdef ASSABET_PAL_VIDEO
492 pixclock: 67797, bpp: 16,
493 xres: 640, yres: 512,
494
495 hsync_len: 64, vsync_len: 6,
496 left_margin: 125, upper_margin: 70,
497 right_margin: 115, lower_margin: 36,
498
499 sync: 0,
500
501 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Act,
502 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(512),
503 #else
504 pixclock: 171521, bpp: 16,
505 xres: 320, yres: 240,
506
507 hsync_len: 5, vsync_len: 1,
508 left_margin: 61, upper_margin: 3,
509 right_margin: 9, lower_margin: 0,
510
511 sync: FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
512
513 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Act,
514 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2),
515 #endif
516 };
517 #endif
518
519 #ifdef CONFIG_SA1100_BITSY
520 static struct sa1100fb_mach_info bitsy_info __initdata = {
521 #ifdef CONFIG_IPAQ_H3100
522 pixclock: 0, bpp: 4,
523 xres: 320, yres: 240,
524
525 hsync_len: 26, vsync_len: 41,
526 left_margin: 4, upper_margin: 0,
527 right_margin: 4, lower_margin: 0,
528
529 sync: FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
530 cmap_greyscale: 1, cmap_static: 1,
531 cmap_inverse: 1,
532
533 lccr0: LCCR0_Mono | LCCR0_4PixMono | LCCR0_Sngl | LCCR0_Pas,
534 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2),
535 #error FIXME
536 /*
537 * Sorry, this should have read:
538 * FIXME: please get rid of the PCD and PixClkDiv definitions
539 * in favour of pixclock. --rmk
540 */
541 #else
542 pixclock: 5722222, bpp: 16,
543 xres: 320, yres: 240,
544
545 hsync_len: 3, vsync_len: 3,
546 left_margin: 12, upper_margin: 10,
547 right_margin: 17, lower_margin: 1,
548
549 sync: 0,
550
551 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Act,
552 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2),
553 #endif
554 };
555
556 static struct sa1100fb_rgb bitsy_rgb_16 = {
557 red: { offset: 12, length: 4, },
558 green: { offset: 7, length: 4, },
559 blue: { offset: 1, length: 4, },
560 transp: { offset: 0, length: 0, },
561 };
562 #endif
563
564 #ifdef CONFIG_SA1100_BRUTUS
565 static struct sa1100fb_mach_info brutus_info __initdata = {
566 pixclock: 0, bpp: 8,
567 xres: 320, yres: 240,
568
569 hsync_len: 3, vsync_len: 1,
570 left_margin: 41, upper_margin: 0,
571 right_margin: 101, lower_margin: 0,
572
573 sync: FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
574
575 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Pas,
576 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2) |
577 LCCR3_PixClkDiv(44),
578 };
579 #endif
580
581 #ifdef CONFIG_SA1100_CERF
582 static struct sa1100fb_mach_info cerf_info __initdata = {
583 pixclock: 171521, bpp: 8,
584 #if defined(CONFIG_CERF_LCD_72_A)
585 xres: 640, yres: 480,
586 lccr0: LCCR0_Color | LCCR0_Dual | LCCR0_Pas,
587 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2) |
588 LCCR3_PixClkDiv(38),
589 #elif defined(CONFIG_CERF_LCD_57_A)
590 xres: 320, yres: 240,
591 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Pas,
592 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2) |
593 LCCR3_PixClkDiv(38),
594 #elif defined(CONFIG_CERF_LCD_38_A)
595 xres: 240, yres: 320,
596 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Pas,
597 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2) |
598 LCCR3_PixClkDiv(38),
599 #else
600 #error "Must have a CerfBoard LCD form factor selected"
601 #endif
602
603 hsync_len: 5, vsync_len: 1,
604 left_margin: 61, upper_margin: 3,
605 right_margin: 9, lower_margin: 0,
606
607 sync: FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
608
609 };
610 #endif
611
612 #ifdef CONFIG_SA1100_FREEBIRD
613 #warning Please check this carefully
614 static struct sa1100fb_mach_info freebird_info __initdata = {
615 pixclock: 171521, bpp: 16,
616 xres: 240, yres: 320,
617
618 hsync_len: 3, vsync_len: 2,
619 left_margin: 2, upper_margin: 0,
620 right_margin: 2, lower_margin: 0,
621
622 sync: FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
623
624 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Pas,
625 lccr3: LCCR3_OutEnH | LCCR3_PixFlEdg | LCCR3_ACBsDiv(2),
626 };
627
628 static struct sa1100fb_rgb freebird_rgb_16 = {
629 red: { offset: 8, length: 4, },
630 green: { offset: 4, length: 4, },
631 blue: { offset: 0, length: 4, },
632 transp: { offset: 12, length: 4, },
633 };
634 #endif
635
636 #ifdef CONFIG_SA1100_GRAPHICSCLIENT
637 static struct sa1100fb_mach_info graphicsclient_info __initdata = {
638 pixclock: 0, bpp: 8,
639 xres: 640, yres: 480,
640
641 hsync_len: 9, vsync_len: 9,
642 left_margin: 54, upper_margin: 24,
643 right_margin: 54, lower_margin: 32,
644
645 sync: 0,
646
647 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Act,
648 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2) |
649 LCCR3_PixClkDiv(10),
650 };
651 #endif
652
653 #ifdef CONFIG_SA1100_HUW_WEBPANEL
654 static struct sa1100fb_mach_info huw_webpanel_info __initdata = {
655 pixclock: 0, bpp: 8,
656 xres: 640, yres: 480,
657
658 hsync_len: 3, vsync_len: 1,
659 left_margin: 41, upper_margin: 0,
660 right_margin: 101, lower_margin: 0,
661
662 sync: FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
663
664 lccr0: LCCR0_Color | LCCR0_Dual | LCCR0_Pas,
665 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(2) | 8,
666 #error FIXME
667 /*
668 * FIXME: please get rid of the '| 8' in preference to an
669 * LCCR3_PixClkDiv() version. --rmk
670 */
671 };
672 #endif
673
674 #ifdef LART_GREY_LCD
675 static struct sa1100fb_mach_info lart_grey_info __initdata = {
676 pixclock: 150000, bpp: 4,
677 xres: 320, yres: 240,
678
679 hsync_len: 1, vsync_len: 1,
680 left_margin: 4, upper_margin: 0,
681 right_margin: 2, lower_margin: 0,
682
683 cmap_greyscale: 1,
684 sync: FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
685
686 lccr0: LCCR0_Mono | LCCR0_Sngl | LCCR0_Pas | LCCR0_4PixMono,
687 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(512),
688 };
689 #endif
690 #ifdef LART_COLOR_LCD
691 static struct sa1100fb_mach_info lart_color_info __initdata = {
692 pixclock: 150000, bpp: 16,
693 xres: 320, yres: 240,
694
695 hsync_len: 2, vsync_len: 3,
696 left_margin: 69, upper_margin: 14,
697 right_margin: 8, lower_margin: 4,
698
699 sync: 0,
700
701 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Act,
702 lccr3: LCCR3_OutEnH | LCCR3_PixFlEdg | LCCR3_ACBsDiv(512),
703 };
704 #endif
705 #ifdef LART_VIDEO_OUT
706 static struct sa1100fb_mach_info lart_video_info __initdata = {
707 pixclock: 39721, bpp: 16,
708 xres: 640, yres: 480,
709
710 hsync_len: 95, vsync_len: 2,
711 left_margin: 40, upper_margin: 32,
712 right_margin: 24, lower_margin: 11,
713
714 sync: FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
715
716 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Act,
717 lccr3: LCCR3_OutEnL | LCCR3_PixFlEdg | LCCR3_ACBsDiv(512),
718 };
719 #endif
720
721 #ifdef LART_KIT01_LCD
722 static struct sa1100fb_mach_info lart_kit01_info __initdata =
723 {
724 pixclock: 63291, bpp: 16,
725 xres: 640, yres: 480,
726
727 hsync_len: 64, vsync_len: 3,
728 left_margin: 122, upper_margin: 45,
729 right_margin: 10, lower_margin: 10,
730
731 sync: 0,
732
733 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Act,
734 lccr3: LCCR3_OutEnH | LCCR3_PixFlEdg
735 };
736 #endif
737
738 #ifdef CONFIG_SA1100_OMNIMETER
739 static struct sa1100fb_mach_info omnimeter_info __initdata = {
740 pixclock: 0, bpp: 4,
741 xres: 480, yres: 320,
742
743 hsync_len: 1, vsync_len: 1,
744 left_margin: 10, upper_margin: 0,
745 right_margin: 10, lower_margin: 0,
746
747 cmap_greyscale: 1,
748 sync: FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
749
750 lccr0: LCCR0_Mono | LCCR0_Sngl | LCCR0_Pas | LCCR0_8PixMono,
751 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_ACBsDiv(255) |
752 LCCR3_PixClkDiv(44),
753 #error FIXME: fix pixclock, ACBsDiv
754 /*
755 * FIXME: I think ACBsDiv is wrong above - should it be 512 (disabled)?
756 * - rmk
757 */
758 };
759 #endif
760
761 #ifdef CONFIG_SA1100_PANGOLIN
762 static struct sa1100fb_mach_info pangolin_info __initdata = {
763 pixclock: 341521, bpp: 16,
764 xres: 800, yres: 600,
765
766 hsync_len: 64, vsync_len: 7,
767 left_margin: 160, upper_margin: 7,
768 right_margin: 24, lower_margin: 1,
769
770 sync: FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT,
771
772 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Act,
773 lccr3: LCCR3_OutEnH | LCCR3_PixFlEdg,
774 };
775 #endif
776
777 #ifdef CONFIG_SA1100_XP860
778 static struct sa1100fb_mach_info xp860_info __initdata = {
779 pixclock: 0, bpp: 8,
780 xres: 1024, yres: 768,
781
782 hsync_len: 3, vsync_len: 3,
783 left_margin: 3, upper_margin: 2,
784 right_margin: 2, lower_margin: 1,
785
786 sync: 0,
787
788 lccr0: LCCR0_Color | LCCR0_Sngl | LCCR0_Act,
789 lccr3: LCCR3_OutEnH | LCCR3_PixRsEdg | LCCR3_PixClkDiv(6),
790 };
791 #endif
792
793 static struct sa1100fb_mach_info * __init
794 sa1100fb_get_machine_info(struct sa1100fb_info *fbi)
795 {
796 struct sa1100fb_mach_info *inf = NULL;
797
798 /*
799 * R G B T
800 * default {11,5}, { 5,6}, { 0,5}, { 0,0}
801 * bitsy {12,4}, { 7,4}, { 1,4}, { 0,0}
802 * freebird { 8,4}, { 4,4}, { 0,4}, {12,4}
803 */
804 #ifdef CONFIG_SA1100_ASSABET
805 if (machine_is_assabet()) {
806 inf = &assabet_info;
807 }
808 #endif
809 #ifdef CONFIG_SA1100_BITSY
810 if (machine_is_bitsy()) {
811 inf = &bitsy_info;
812 fbi->rgb[RGB_16] = &bitsy_rgb_16;
813 }
814 #endif
815 #ifdef CONFIG_SA1100_BRUTUS
816 if (machine_is_brutus()) {
817 inf = &brutus_info;
818 }
819 #endif
820 #ifdef CONFIG_SA1100_CERF
821 if (machine_is_cerf()) {
822 inf = &cerf_info;
823 }
824 #endif
825 #ifdef CONFIG_SA1100_FREEBIRD
826 if (machine_is_freebird()) {
827 inf = &freebird_info;
828 fbi->rgb[RGB_16] = &freebird_rgb16;
829 }
830 #endif
831 #ifdef CONFIG_SA1100_GRAPHICSCLIENT
832 if (machine_is_graphicsclient()) {
833 inf = &graphicsclient_info;
834 }
835 #endif
836 #ifdef CONFIG_SA1100_HUW_WEBPANEL
837 if (machine_is_huw_webpanel()) {
838 inf = &huw_webpanel_info;
839 }
840 #endif
841 #ifdef CONFIG_SA1100_LART
842 if (machine_is_lart()) {
843 #ifdef LART_GREY_LCD
844 inf = &lart_grey_info;
845 #endif
846 #ifdef LART_COLOR_LCD
847 inf = &lart_color_info;
848 #endif
849 #ifdef LART_VIDEO_OUT
850 inf = &lart_video_info;
851 #endif
852 #ifdef LART_KIT01_LCD
853 inf = &lart_kit01_info;
854 #endif
855 }
856 #endif
857 #ifdef CONFIG_SA1100_OMNIMETER
858 if (machine_is_omnimeter()) {
859 inf = &omnimeter_info;
860 }
861 #endif
862 #ifdef CONFIG_SA1100_PANGOLIN
863 if (machine_is_pangolin()) {
864 inf = &pangolin_info;
865 }
866 #endif
867 #ifdef CONFIG_SA1100_XP860
868 if (machine_is_xp860()) {
869 inf = &xp860_info;
870 }
871 #endif
872 return inf;
873 }
874
875 static int sa1100fb_activate_var(struct fb_var_screeninfo *var, struct sa1100fb_info *);
876 static void set_ctrlr_state(struct sa1100fb_info *fbi, u_int state);
877
878 static inline void sa1100fb_schedule_task(struct sa1100fb_info *fbi, u_int state)
879 {
880 unsigned long flags;
881
882 local_irq_save(flags);
883 /*
884 * We need to handle two requests being made at the same time.
885 * There are two important cases:
886 * 1. When we are changing VT (C_REENABLE) while unblanking (C_ENABLE)
887 * We must perform the unblanking, which will do our REENABLE for us.
888 * 2. When we are blanking, but immediately unblank before we have
889 * blanked. We do the "REENABLE" thing here as well, just to be sure.
890 */
891 if (fbi->task_state == C_ENABLE && state == C_REENABLE)
892 state = (u_int) -1;
893 if (fbi->task_state == C_DISABLE && state == C_ENABLE)
894 state = C_REENABLE;
895
896 if (state != (u_int)-1) {
897 fbi->task_state = state;
898 schedule_task(&fbi->task);
899 }
900 local_irq_restore(flags);
901 }
902
903 /*
904 * Get the VAR structure pointer for the specified console
905 */
906 static inline struct fb_var_screeninfo *get_con_var(struct fb_info *info, int con)
907 {
908 struct sa1100fb_info *fbi = (struct sa1100fb_info *)info;
909 return (con == fbi->currcon || con == -1) ? &fbi->fb.var : &fb_display[con].var;
910 }
911
912 /*
913 * Get the DISPLAY structure pointer for the specified console
914 */
915 static inline struct display *get_con_display(struct fb_info *info, int con)
916 {
917 struct sa1100fb_info *fbi = (struct sa1100fb_info *)info;
918 return (con < 0) ? fbi->fb.disp : &fb_display[con];
919 }
920
921 /*
922 * Get the CMAP pointer for the specified console
923 */
924 static inline struct fb_cmap *get_con_cmap(struct fb_info *info, int con)
925 {
926 struct sa1100fb_info *fbi = (struct sa1100fb_info *)info;
927 return (con == fbi->currcon || con == -1) ? &fbi->fb.cmap : &fb_display[con].cmap;
928 }
929
930 static inline u_int
931 chan_to_field(u_int chan, struct fb_bitfield *bf)
932 {
933 chan &= 0xffff;
934 chan >>= 16 - bf->length;
935 return chan << bf->offset;
936 }
937
938 /*
939 * Convert bits-per-pixel to a hardware palette PBS value.
940 */
941 static inline u_int
942 palette_pbs(struct fb_var_screeninfo *var)
943 {
944 int ret = 0;
945 switch (var->bits_per_pixel) {
946 #ifdef FBCON_HAS_CFB4
947 case 4: ret = 0 << 12; break;
948 #endif
949 #ifdef FBCON_HAS_CFB8
950 case 8: ret = 1 << 12; break;
951 #endif
952 #ifdef FBCON_HAS_CFB16
953 case 16: ret = 2 << 12; break;
954 #endif
955 }
956 return ret;
957 }
958
959 static int
960 sa1100fb_setpalettereg(u_int regno, u_int red, u_int green, u_int blue,
961 u_int trans, struct fb_info *info)
962 {
963 struct sa1100fb_info *fbi = (struct sa1100fb_info *)info;
964 u_int val, ret = 1;
965
966 if (regno < fbi->palette_size) {
967 val = ((red >> 4) & 0xf00);
968 val |= ((green >> 8) & 0x0f0);
969 val |= ((blue >> 12) & 0x00f);
970
971 if (regno == 0)
972 val |= palette_pbs(&fbi->fb.var);
973
974 fbi->palette_cpu[regno] = val;
975 ret = 0;
976 }
977 return ret;
978 }
979
980 static int
981 sa1100fb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
982 u_int trans, struct fb_info *info)
983 {
984 struct sa1100fb_info *fbi = (struct sa1100fb_info *)info;
985 struct display *disp = get_con_display(info, fbi->currcon);
986 u_int val;
987 int ret = 1;
988
989 /*
990 * If inverse mode was selected, invert all the colours
991 * rather than the register number. The register number
992 * is what you poke into the framebuffer to produce the
993 * colour you requested.
994 */
995 if (disp->inverse) {
996 red = 0xffff - red;
997 green = 0xffff - green;
998 blue = 0xffff - blue;
999 }
1000
1001 /*
1002 * If greyscale is true, then we convert the RGB value
1003 * to greyscale no mater what visual we are using.
1004 */
1005 if (fbi->fb.var.grayscale)
1006 red = green = blue = (19595 * red + 38470 * green +
1007 7471 * blue) >> 16;
1008
1009 switch (fbi->fb.disp->visual) {
1010 case FB_VISUAL_TRUECOLOR:
1011 /*
1012 * 12 or 16-bit True Colour. We encode the RGB value
1013 * according to the RGB bitfield information.
1014 */
1015 if (regno < 16) {
1016 u16 *pal = fbi->fb.pseudo_palette;
1017
1018 val = chan_to_field(red, &fbi->fb.var.red);
1019 val |= chan_to_field(green, &fbi->fb.var.green);
1020 val |= chan_to_field(blue, &fbi->fb.var.blue);
1021
1022 pal[regno] = val;
1023 ret = 0;
1024 }
1025 break;
1026
1027 case FB_VISUAL_STATIC_PSEUDOCOLOR:
1028 case FB_VISUAL_PSEUDOCOLOR:
1029 ret = sa1100fb_setpalettereg(regno, red, green, blue, trans, info);
1030 break;
1031 }
1032
1033 return ret;
1034 }
1035
1036 /*
1037 * sa1100fb_display_dma_period()
1038 * Calculate the minimum period (in picoseconds) between two DMA
1039 * requests for the LCD controller.
1040 */
1041 static unsigned int
1042 sa1100fb_display_dma_period(struct fb_var_screeninfo *var)
1043 {
1044 unsigned int mem_bits_per_pixel;
1045
1046 mem_bits_per_pixel = var->bits_per_pixel;
1047 if (mem_bits_per_pixel == 12)
1048 mem_bits_per_pixel = 16;
1049
1050 /*
1051 * Period = pixclock * bits_per_byte * bytes_per_transfer
1052 * / memory_bits_per_pixel;
1053 */
1054 return var->pixclock * 8 * 16 / mem_bits_per_pixel;
1055 }
1056
1057 /*
1058 * sa1100fb_decode_var():
1059 * Get the video params out of 'var'. If a value doesn't fit, round it up,
1060 * if it's too big, return -EINVAL.
1061 *
1062 * Suggestion: Round up in the following order: bits_per_pixel, xres,
1063 * yres, xres_virtual, yres_virtual, xoffset, yoffset, grayscale,
1064 * bitfields, horizontal timing, vertical timing.
1065 */
1066 static int
1067 sa1100fb_validate_var(struct fb_var_screeninfo *var,
1068 struct sa1100fb_info *fbi)
1069 {
1070 int ret = -EINVAL;
1071
1072 if (var->xres < MIN_XRES)
1073 var->xres = MIN_XRES;
1074 if (var->yres < MIN_YRES)
1075 var->yres = MIN_YRES;
1076 if (var->xres > fbi->max_xres)
1077 var->xres = fbi->max_xres;
1078 if (var->yres > fbi->max_yres)
1079 var->yres = fbi->max_yres;
1080 var->xres_virtual =
1081 var->xres_virtual < var->xres ? var->xres : var->xres_virtual;
1082 var->yres_virtual =
1083 var->yres_virtual < var->yres ? var->yres : var->yres_virtual;
1084
1085 DPRINTK("var->bits_per_pixel=%d\n", var->bits_per_pixel);
1086 switch (var->bits_per_pixel) {
1087 #ifdef FBCON_HAS_CFB4
1088 case 4: ret = 0; break;
1089 #endif
1090 #ifdef FBCON_HAS_CFB8
1091 case 8: ret = 0; break;
1092 #endif
1093 #ifdef FBCON_HAS_CFB16
1094 case 16: ret = 0; break;
1095 #endif
1096 default:
1097 break;
1098 }
1099
1100 printk(KERN_DEBUG "dma period = %d ps, clock = %d kHz",
1101 sa1100fb_display_dma_period(var),
1102 cpufreq_get(smp_processor_id()));
1103
1104 return ret;
1105 }
1106
1107 static inline void sa1100fb_set_truecolor(u_int is_true_color)
1108 {
1109 DPRINTK("true_color = %d\n", is_true_color);
1110 #ifdef CONFIG_SA1100_ASSABET
1111 if (machine_is_assabet()) {
1112 #if 1
1113 // phase 4 or newer Assabet's
1114 if (is_true_color)
1115 BCR_set(BCR_LCD_12RGB);
1116 else
1117 BCR_clear(BCR_LCD_12RGB);
1118 #else
1119 // older Assabet's
1120 if (is_true_color)
1121 BCR_clear(BCR_LCD_12RGB);
1122 else
1123 BCR_set(BCR_LCD_12RGB);
1124 #endif
1125 }
1126 #endif
1127 }
1128
1129 static void
1130 sa1100fb_hw_set_var(struct fb_var_screeninfo *var, struct sa1100fb_info *fbi)
1131 {
1132 u_long palette_mem_size;
1133
1134 fbi->palette_size = var->bits_per_pixel == 8 ? 256 : 16;
1135
1136 palette_mem_size = fbi->palette_size * sizeof(u16);
1137
1138 DPRINTK("palette_mem_size = 0x%08lx\n", (u_long) palette_mem_size);
1139
1140 fbi->palette_cpu = (u16 *)(fbi->map_cpu + PAGE_SIZE - palette_mem_size);
1141 fbi->palette_dma = fbi->map_dma + PAGE_SIZE - palette_mem_size;
1142
1143 fb_set_cmap(&fbi->fb.cmap, 1, sa1100fb_setcolreg, &fbi->fb);
1144
1145 /* Set board control register to handle new color depth */
1146 sa1100fb_set_truecolor(var->bits_per_pixel >= 16);
1147
1148 #ifdef CONFIG_SA1100_OMNIMETER
1149 #error Do we have to do this here? We already do it at init time.
1150 if (machine_is_omnimeter())
1151 SetLCDContrast(DefaultLCDContrast);
1152 #endif
1153
1154 sa1100fb_activate_var(var, fbi);
1155
1156 fbi->palette_cpu[0] = (fbi->palette_cpu[0] &
1157 0xcfff) | palette_pbs(var);
1158
1159 }
1160
1161 /*
1162 * sa1100fb_set_var():
1163 * Set the user defined part of the display for the specified console
1164 */
1165 static int
1166 sa1100fb_set_var(struct fb_var_screeninfo *var, int con, struct fb_info *info)
1167 {
1168 struct sa1100fb_info *fbi = (struct sa1100fb_info *)info;
1169 struct fb_var_screeninfo *dvar = get_con_var(&fbi->fb, con);
1170 struct display *display = get_con_display(&fbi->fb, con);
1171 int err, chgvar = 0, rgbidx;
1172
1173 DPRINTK("set_var\n");
1174
1175 /*
1176 * Decode var contents into a par structure, adjusting any
1177 * out of range values.
1178 */
1179 err = sa1100fb_validate_var(var, fbi);
1180 if (err)
1181 return err;
1182
1183 if (var->activate & FB_ACTIVATE_TEST)
1184 return 0;
1185
1186 if ((var->activate & FB_ACTIVATE_MASK) != FB_ACTIVATE_NOW)
1187 return -EINVAL;
1188
1189 if (dvar->xres != var->xres)
1190 chgvar = 1;
1191 if (dvar->yres != var->yres)
1192 chgvar = 1;
1193 if (dvar->xres_virtual != var->xres_virtual)
1194 chgvar = 1;
1195 if (dvar->yres_virtual != var->yres_virtual)
1196 chgvar = 1;
1197 if (dvar->bits_per_pixel != var->bits_per_pixel)
1198 chgvar = 1;
1199 if (con < 0)
1200 chgvar = 0;
1201
1202 switch (var->bits_per_pixel) {
1203 #ifdef FBCON_HAS_CFB4
1204 case 4:
1205 if (fbi->cmap_static)
1206 display->visual = FB_VISUAL_STATIC_PSEUDOCOLOR;
1207 else
1208 display->visual = FB_VISUAL_PSEUDOCOLOR;
1209 display->line_length = var->xres / 2;
1210 display->dispsw = &fbcon_cfb4;
1211 rgbidx = RGB_8;
1212 break;
1213 #endif
1214 #ifdef FBCON_HAS_CFB8
1215 case 8:
1216 if (fbi->cmap_static)
1217 display->visual = FB_VISUAL_STATIC_PSEUDOCOLOR;
1218 else
1219 display->visual = FB_VISUAL_PSEUDOCOLOR;
1220 display->line_length = var->xres;
1221 display->dispsw = &fbcon_cfb8;
1222 rgbidx = RGB_8;
1223 break;
1224 #endif
1225 #ifdef FBCON_HAS_CFB16
1226 case 16:
1227 display->visual = FB_VISUAL_TRUECOLOR;
1228 display->line_length = var->xres * 2;
1229 display->dispsw = &fbcon_cfb16;
1230 display->dispsw_data = fbi->fb.pseudo_palette;
1231 rgbidx = RGB_16;
1232 break;
1233 #endif
1234 default:
1235 rgbidx = 0;
1236 display->dispsw = &fbcon_dummy;
1237 break;
1238 }
1239
1240 display->screen_base = fbi->screen_cpu;
1241 display->next_line = display->line_length;
1242 display->type = fbi->fb.fix.type;
1243 display->type_aux = fbi->fb.fix.type_aux;
1244 display->ypanstep = fbi->fb.fix.ypanstep;
1245 display->ywrapstep = fbi->fb.fix.ywrapstep;
1246 display->can_soft_blank = 1;
1247 display->inverse = fbi->cmap_inverse;
1248
1249 *dvar = *var;
1250 dvar->activate &= ~FB_ACTIVATE_ALL;
1251
1252 /*
1253 * Copy the RGB parameters for this display
1254 * from the machine specific parameters.
1255 */
1256 dvar->red = fbi->rgb[rgbidx]->red;
1257 dvar->green = fbi->rgb[rgbidx]->green;
1258 dvar->blue = fbi->rgb[rgbidx]->blue;
1259 dvar->transp = fbi->rgb[rgbidx]->transp;
1260
1261 DPRINTK("RGBT length = %d:%d:%d:%d\n",
1262 dvar->red.length, dvar->green.length, dvar->blue.length,
1263 dvar->transp.length);
1264
1265 DPRINTK("RGBT offset = %d:%d:%d:%d\n",
1266 dvar->red.offset, dvar->green.offset, dvar->blue.offset,
1267 dvar->transp.offset);
1268
1269 /*
1270 * Update the old var. The fbcon drivers still use this.
1271 * Once they are using fbi->fb.var, this can be dropped.
1272 */
1273 display->var = *dvar;
1274
1275 /*
1276 * If we are setting all the virtual consoles, also set the
1277 * defaults used to create new consoles.
1278 */
1279 if (var->activate & FB_ACTIVATE_ALL)
1280 fbi->fb.disp->var = *dvar;
1281
1282 /*
1283 * If the console has changed and the console has defined
1284 * a changevar function, call that function.
1285 */
1286 if (chgvar && info && fbi->fb.changevar)
1287 fbi->fb.changevar(con);
1288
1289 /* If the current console is selected, activate the new var. */
1290 if (con != fbi->currcon)
1291 return 0;
1292
1293 sa1100fb_hw_set_var(dvar, fbi);
1294
1295 return 0;
1296 }
1297
1298 static int
1299 __do_set_cmap(struct fb_cmap *cmap, int kspc, int con,
1300 struct fb_info *info)
1301 {
1302 struct sa1100fb_info *fbi = (struct sa1100fb_info *)info;
1303 struct fb_cmap *dcmap = get_con_cmap(info, con);
1304 int err = 0;
1305
1306 if (con == -1)
1307 con = fbi->currcon;
1308
1309 /* no colormap allocated? (we always have "this" colour map allocated) */
1310 if (con >= 0)
1311 err = fb_alloc_cmap(&fb_display[con].cmap, fbi->palette_size, 0);
1312
1313 if (!err && con == fbi->currcon)
1314 err = fb_set_cmap(cmap, kspc, sa1100fb_setcolreg, info);
1315
1316 if (!err)
1317 fb_copy_cmap(cmap, dcmap, kspc ? 0 : 1);
1318
1319 return err;
1320 }
1321
1322 static int
1323 sa1100fb_set_cmap(struct fb_cmap *cmap, int kspc, int con,
1324 struct fb_info *info)
1325 {
1326 struct display *disp = get_con_display(info, con);
1327
1328 if (disp->visual == FB_VISUAL_TRUECOLOR ||
1329 disp->visual == FB_VISUAL_STATIC_PSEUDOCOLOR)
1330 return -EINVAL;
1331
1332 return __do_set_cmap(cmap, kspc, con, info);
1333 }
1334
1335 static int
1336 sa1100fb_get_fix(struct fb_fix_screeninfo *fix, int con, struct fb_info *info)
1337 {
1338 struct display *display = get_con_display(info, con);
1339
1340 *fix = info->fix;
1341
1342 fix->line_length = display->line_length;
1343 fix->visual = display->visual;
1344 return 0;
1345 }
1346
1347 static int
1348 sa1100fb_get_var(struct fb_var_screeninfo *var, int con, struct fb_info *info)
1349 {
1350 *var = *get_con_var(info, con);
1351 return 0;
1352 }
1353
1354 static int
1355 sa1100fb_get_cmap(struct fb_cmap *cmap, int kspc, int con, struct fb_info *info)
1356 {
1357 struct fb_cmap *dcmap = get_con_cmap(info, con);
1358 fb_copy_cmap(dcmap, cmap, kspc ? 0 : 2);
1359 return 0;
1360 }
1361
1362 static struct fb_ops sa1100fb_ops = {
1363 owner: THIS_MODULE,
1364 fb_get_fix: sa1100fb_get_fix,
1365 fb_get_var: sa1100fb_get_var,
1366 fb_set_var: sa1100fb_set_var,
1367 fb_get_cmap: sa1100fb_get_cmap,
1368 fb_set_cmap: sa1100fb_set_cmap,
1369 };
1370
1371 /*
1372 * sa1100fb_switch():
1373 * Change to the specified console. Palette and video mode
1374 * are changed to the console's stored parameters.
1375 *
1376 * Uh oh, this can be called from a tasklet (IRQ)
1377 */
1378 static int sa1100fb_switch(int con, struct fb_info *info)
1379 {
1380 struct sa1100fb_info *fbi = (struct sa1100fb_info *)info;
1381 struct display *disp;
1382 struct fb_cmap *cmap;
1383
1384 DPRINTK("con=%d info->modename=%s\n", con, fbi->fb.modename);
1385
1386 if (con == fbi->currcon)
1387 return 0;
1388
1389 if (fbi->currcon >= 0) {
1390 disp = fb_display + fbi->currcon;
1391
1392 /*
1393 * Save the old colormap and video mode.
1394 */
1395 disp->var = fbi->fb.var;
1396
1397 if (disp->cmap.len)
1398 fb_copy_cmap(&fbi->fb.cmap, &disp->cmap, 0);
1399 }
1400
1401 fbi->currcon = con;
1402 disp = fb_display + con;
1403
1404 /*
1405 * Make sure that our colourmap contains 256 entries.
1406 */
1407 fb_alloc_cmap(&fbi->fb.cmap, 256, 0);
1408
1409 if (disp->cmap.len)
1410 cmap = &disp->cmap;
1411 else
1412 cmap = fb_default_cmap(1 << disp->var.bits_per_pixel);
1413
1414 fb_copy_cmap(cmap, &fbi->fb.cmap, 0);
1415
1416 fbi->fb.var = disp->var;
1417 fbi->fb.var.activate = FB_ACTIVATE_NOW;
1418
1419 sa1100fb_set_var(&fbi->fb.var, con, info);
1420 return 0;
1421 }
1422
1423 /*
1424 * Formal definition of the VESA spec:
1425 * On
1426 * This refers to the state of the display when it is in full operation
1427 * Stand-By
1428 * This defines an optional operating state of minimal power reduction with
1429 * the shortest recovery time
1430 * Suspend
1431 * This refers to a level of power management in which substantial power
1432 * reduction is achieved by the display. The display can have a longer
1433 * recovery time from this state than from the Stand-by state
1434 * Off
1435 * This indicates that the display is consuming the lowest level of power
1436 * and is non-operational. Recovery from this state may optionally require
1437 * the user to manually power on the monitor
1438 *
1439 * Now, the fbdev driver adds an additional state, (blank), where they
1440 * turn off the video (maybe by colormap tricks), but don't mess with the
1441 * video itself: think of it semantically between on and Stand-By.
1442 *
1443 * So here's what we should do in our fbdev blank routine:
1444 *
1445 * VESA_NO_BLANKING (mode 0) Video on, front/back light on
1446 * VESA_VSYNC_SUSPEND (mode 1) Video on, front/back light off
1447 * VESA_HSYNC_SUSPEND (mode 2) Video on, front/back light off
1448 * VESA_POWERDOWN (mode 3) Video off, front/back light off
1449 *
1450 * This will match the matrox implementation.
1451 */
1452 /*
1453 * sa1100fb_blank():
1454 * Blank the display by setting all palette values to zero. Note, the
1455 * 12 and 16 bpp modes don't really use the palette, so this will not
1456 * blank the display in all modes.
1457 */
1458 static void sa1100fb_blank(int blank, struct fb_info *info)
1459 {
1460 struct sa1100fb_info *fbi = (struct sa1100fb_info *)info;
1461 int i;
1462
1463 DPRINTK("sa1100fb_blank: blank=%d info->modename=%s\n", blank,
1464 fbi->fb.modename);
1465
1466 switch (blank) {
1467 case VESA_POWERDOWN:
1468 case VESA_VSYNC_SUSPEND:
1469 case VESA_HSYNC_SUSPEND:
1470 if (fbi->fb.disp->visual == FB_VISUAL_PSEUDOCOLOR ||
1471 fbi->fb.disp->visual == FB_VISUAL_STATIC_PSEUDOCOLOR)
1472 for (i = 0; i < fbi->palette_size; i++)
1473 sa1100fb_setpalettereg(i, 0, 0, 0, 0, info);
1474 sa1100fb_schedule_task(fbi, C_DISABLE);
1475 if (sa1100fb_blank_helper)
1476 sa1100fb_blank_helper(blank);
1477 break;
1478
1479 case VESA_NO_BLANKING:
1480 if (sa1100fb_blank_helper)
1481 sa1100fb_blank_helper(blank);
1482 if (fbi->fb.disp->visual == FB_VISUAL_PSEUDOCOLOR ||
1483 fbi->fb.disp->visual == FB_VISUAL_STATIC_PSEUDOCOLOR)
1484 fb_set_cmap(&fbi->fb.cmap, 1, sa1100fb_setcolreg, info);
1485 sa1100fb_schedule_task(fbi, C_ENABLE);
1486 }
1487 }
1488
1489 static int sa1100fb_updatevar(int con, struct fb_info *info)
1490 {
1491 DPRINTK("entered\n");
1492 return 0;
1493 }
1494
1495 /*
1496 * Calculate the PCD value from the clock rate (in picoseconds).
1497 * We take account of the PPCR clock setting.
1498 */
1499 static inline int get_pcd(unsigned int pixclock)
1500 {
1501 unsigned int pcd;
1502
1503 if (pixclock) {
1504 pcd = get_cclk_frequency() * pixclock;
1505 pcd /= 10000000;
1506 pcd += 1; /* make up for integer math truncations */
1507 } else {
1508 /*
1509 * People seem to be missing this message. Make it big.
1510 * Make it stand out. Make sure people see it.
1511 */
1512 printk(KERN_WARNING "******************************************************\n");
1513 printk(KERN_WARNING "** ZERO PIXEL CLOCK DETECTED **\n");
1514 printk(KERN_WARNING "** You are using a zero pixclock. This means that **\n");
1515 printk(KERN_WARNING "** clock scaling will not be able to adjust your **\n");
1516 printk(KERN_WARNING "** your timing parameters appropriately, and the **\n");
1517 printk(KERN_WARNING "** bandwidth calculations will fail to work. This **\n");
1518 printk(KERN_WARNING "** will shortly become an error condition, which **\n");
1519 printk(KERN_WARNING "** will prevent your LCD display working. Please **\n");
1520 printk(KERN_WARNING "** send your patches in as soon as possible to shut **\n");
1521 printk(KERN_WARNING "** this message up. **\n");
1522 printk(KERN_WARNING "******************************************************\n");
1523 pcd = 0;
1524 }
1525 return pcd;
1526 }
1527
1528 /*
1529 * sa1100fb_activate_var():
1530 * Configures LCD Controller based on entries in var parameter. Settings are
1531 * only written to the controller if changes were made.
1532 */
1533 static int sa1100fb_activate_var(struct fb_var_screeninfo *var, struct sa1100fb_info *fbi)
1534 {
1535 struct sa1100fb_lcd_reg new_regs;
1536 u_int half_screen_size, yres, pcd = get_pcd(var->pixclock);
1537 u_long flags;
1538
1539 DPRINTK("Configuring SA1100 LCD\n");
1540
1541 DPRINTK("var: xres=%d hslen=%d lm=%d rm=%d\n",
1542 var->xres, var->hsync_len,
1543 var->left_margin, var->right_margin);
1544 DPRINTK("var: yres=%d vslen=%d um=%d bm=%d\n",
1545 var->yres, var->vsync_len,
1546 var->upper_margin, var->lower_margin);
1547
1548 #if DEBUG_VAR
1549 if (var->xres < 16 || var->xres > 1024)
1550 printk(KERN_ERR "%s: invalid xres %d\n",
1551 fbi->fb.fix.id, var->xres);
1552 if (var->hsync_len < 1 || var->hsync_len > 64)
1553 printk(KERN_ERR "%s: invalid hsync_len %d\n",
1554 fbi->fb.fix.id, var->hsync_len);
1555 if (var->left_margin < 1 || var->left_margin > 255)
1556 printk(KERN_ERR "%s: invalid left_margin %d\n",
1557 fbi->fb.fix.id, var->left_margin);
1558 if (var->right_margin < 1 || var->right_margin > 255)
1559 printk(KERN_ERR "%s: invalid right_margin %d\n",
1560 fbi->fb.fix.id, var->right_margin);
1561 if (var->yres < 1 || var->yres > 1024)
1562 printk(KERN_ERR "%s: invalid yres %d\n",
1563 fbi->fb.fix.id, var->yres);
1564 if (var->vsync_len < 1 || var->vsync_len > 64)
1565 printk(KERN_ERR "%s: invalid vsync_len %d\n",
1566 fbi->fb.fix.id, var->vsync_len);
1567 if (var->upper_margin < 0 || var->upper_margin > 255)
1568 printk(KERN_ERR "%s: invalid upper_margin %d\n",
1569 fbi->fb.fix.id, var->upper_margin);
1570 if (var->lower_margin < 0 || var->lower_margin > 255)
1571 printk(KERN_ERR "%s: invalid lower_margin %d\n",
1572 fbi->fb.fix.id, var->lower_margin);
1573 #endif
1574
1575 new_regs.lccr0 = fbi->lccr0 |
1576 LCCR0_LEN | LCCR0_LDM | LCCR0_BAM |
1577 LCCR0_ERM | LCCR0_LtlEnd | LCCR0_DMADel(0);
1578
1579 new_regs.lccr1 =
1580 LCCR1_DisWdth(var->xres) +
1581 LCCR1_HorSnchWdth(var->hsync_len) +
1582 LCCR1_BegLnDel(var->left_margin) +
1583 LCCR1_EndLnDel(var->right_margin);
1584
1585 /*
1586 * If we have a dual scan LCD, then we need to halve
1587 * the YRES parameter.
1588 */
1589 yres = var->yres;
1590 if (fbi->lccr0 & LCCR0_Dual)
1591 yres /= 2;
1592
1593 new_regs.lccr2 =
1594 LCCR2_DisHght(yres) +
1595 LCCR2_VrtSnchWdth(var->vsync_len) +
1596 LCCR2_BegFrmDel(var->upper_margin) +
1597 LCCR2_EndFrmDel(var->lower_margin);
1598
1599 new_regs.lccr3 = fbi->lccr3 |
1600 (var->sync & FB_SYNC_HOR_HIGH_ACT ? LCCR3_HorSnchH : LCCR3_HorSnchL) |
1601 (var->sync & FB_SYNC_VERT_HIGH_ACT ? LCCR3_VrtSnchH : LCCR3_VrtSnchL) |
1602 LCCR3_ACBsCntOff;
1603
1604 if (pcd)
1605 new_regs.lccr3 |= LCCR3_PixClkDiv(pcd);
1606
1607 sa1100fb_check_shadow(&new_regs, var, pcd);
1608
1609 DPRINTK("nlccr0 = 0x%08x\n", new_regs.lccr0);
1610 DPRINTK("nlccr1 = 0x%08x\n", new_regs.lccr1);
1611 DPRINTK("nlccr2 = 0x%08x\n", new_regs.lccr2);
1612 DPRINTK("nlccr3 = 0x%08x\n", new_regs.lccr3);
1613
1614 half_screen_size = var->bits_per_pixel;
1615 half_screen_size = half_screen_size * var->xres * var->yres / 16;
1616
1617 /* Update shadow copy atomically */
1618 local_irq_save(flags);
1619 fbi->dbar1 = fbi->palette_dma;
1620 fbi->dbar2 = fbi->screen_dma + half_screen_size;
1621
1622 fbi->reg_lccr0 = new_regs.lccr0;
1623 fbi->reg_lccr1 = new_regs.lccr1;
1624 fbi->reg_lccr2 = new_regs.lccr2;
1625 fbi->reg_lccr3 = new_regs.lccr3;
1626 local_irq_restore(flags);
1627
1628 /*
1629 * Only update the registers if the controller is enabled
1630 * and something has changed.
1631 */
1632 if ((LCCR0 != fbi->reg_lccr0) || (LCCR1 != fbi->reg_lccr1) ||
1633 (LCCR2 != fbi->reg_lccr2) || (LCCR3 != fbi->reg_lccr3) ||
1634 (DBAR1 != (Address) fbi->dbar1) || (DBAR2 != (Address) fbi->dbar2))
1635 sa1100fb_schedule_task(fbi, C_REENABLE);
1636
1637 return 0;
1638 }
1639
1640 /*
1641 * NOTE! The following functions are purely helpers for set_ctrlr_state.
1642 * Do not call them directly; set_ctrlr_state does the correct serialisation
1643 * to ensure that things happen in the right way 100% of time time.
1644 * -- rmk
1645 */
1646
1647 /*
1648 * FIXME: move LCD power stuff into sa1100fb_power_up_lcd()
1649 * Also, I'm expecting that the backlight stuff should
1650 * be handled differently.
1651 */
1652 static void sa1100fb_backlight_on(struct sa1100fb_info *fbi)
1653 {
1654 DPRINTK("backlight on\n");
1655
1656 #ifdef CONFIG_SA1100_FREEBIRD
1657 #error FIXME
1658 if (machine_is_freebird()) {
1659 BCR_set(BCR_FREEBIRD_LCD_PWR | BCR_FREEBIRD_LCD_DISP);
1660 }
1661 #endif
1662 #ifdef CONFIG_SA1100_FREEBIRD
1663 if (machine_is_freebird()) {
1664 /* Turn on backlight ,Chester */
1665 BCR_set(BCR_FREEBIRD_LCD_BACKLIGHT);
1666 }
1667 #endif
1668 #ifdef CONFIG_SA1100_HUW_WEBPANEL
1669 #error FIXME
1670 if (machine_is_huw_webpanel()) {
1671 BCR_set(BCR_CCFL_POW + BCR_PWM_BACKLIGHT);
1672 set_current_state(TASK_UNINTERRUPTIBLE);
1673 schedule_task(200 * HZ / 1000);
1674 BCR_set(BCR_TFT_ENA);
1675 }
1676 #endif
1677 #ifdef CONFIG_SA1100_OMNIMETER
1678 if (machine_is_omnimeter())
1679 LEDBacklightOn();
1680 #endif
1681 #ifdef CONFIG_SA1100_BITSY
1682 /* what rmk said --dneuer */
1683 #endif
1684 }
1685
1686 /*
1687 * FIXME: move LCD power stuf into sa1100fb_power_down_lcd()
1688 * Also, I'm expecting that the backlight stuff should
1689 * be handled differently.
1690 */
1691 static void sa1100fb_backlight_off(struct sa1100fb_info *fbi)
1692 {
1693 DPRINTK("backlight off\n");
1694
1695 #ifdef CONFIG_SA1100_FREEBIRD
1696 #error FIXME
1697 if (machine_is_freebird()) {
1698 BCR_clear(BCR_FREEBIRD_LCD_PWR | BCR_FREEBIRD_LCD_DISP
1699 /*| BCR_FREEBIRD_LCD_BACKLIGHT */ );
1700 }
1701 #endif
1702 #ifdef CONFIG_SA1100_OMNIMETER
1703 if (machine_is_omnimeter())
1704 LEDBacklightOff();
1705 #endif
1706 #ifdef CONFIG_SA1100_BITSY
1707 /* what rmk said --dneuer */
1708 #endif
1709 }
1710
1711 static void sa1100fb_power_up_lcd(struct sa1100fb_info *fbi)
1712 {
1713 DPRINTK("LCD power on\n");
1714
1715 #if defined(CONFIG_SA1100_ASSABET) && !defined(ASSABET_PAL_VIDEO)
1716 if (machine_is_assabet())
1717 BCR_set(BCR_LCD_ON);
1718 #endif
1719 #ifdef CONFIG_SA1100_HUW_WEBPANEL
1720 if (machine_is_huw_webpanel())
1721 BCR_clear(BCR_TFT_NPWR);
1722 #endif
1723 #ifdef CONFIG_SA1100_OMNIMETER
1724 if (machine_is_omnimeter())
1725 LCDPowerOn();
1726 #endif
1727 #ifdef CONFIG_SA1100_BITSY
1728 if (machine_is_bitsy()) {
1729 set_bitsy_egpio(EGPIO_BITSY_LCD_ON |
1730 EGPIO_BITSY_LCD_PCI |
1731 EGPIO_BITSY_LCD_5V_ON |
1732 EGPIO_BITSY_LVDD_ON);
1733 }
1734 #endif
1735 }
1736
1737 static void sa1100fb_power_down_lcd(struct sa1100fb_info *fbi)
1738 {
1739 DPRINTK("LCD power off\n");
1740
1741 #if defined(CONFIG_SA1100_ASSABET) && !defined(ASSABET_PAL_VIDEO)
1742 if (machine_is_assabet())
1743 BCR_clear(BCR_LCD_ON);
1744 #endif
1745 #ifdef CONFIG_SA1100_HUW_WEBPANEL
1746 // dont forget to set the control lines to zero (?)
1747 if (machine_is_huw_webpanel())
1748 BCR_set(BCR_TFT_NPWR);
1749 #endif
1750 #ifdef CONFIG_SA1100_BITSY
1751 if (machine_is_bitsy()) {
1752 clr_bitsy_egpio(EGPIO_BITSY_LCD_ON |
1753 EGPIO_BITSY_LCD_PCI |
1754 EGPIO_BITSY_LCD_5V_ON |
1755 EGPIO_BITSY_LVDD_ON);
1756 }
1757 #endif
1758 }
1759
1760 static void sa1100fb_setup_gpio(struct sa1100fb_info *fbi)
1761 {
1762 u_int mask = 0;
1763
1764 /*
1765 * Enable GPIO<9:2> for LCD use if:
1766 * 1. Active display, or
1767 * 2. Color Dual Passive display
1768 *
1769 * see table 11.8 on page 11-27 in the SA1100 manual
1770 * -- Erik.
1771 *
1772 * SA1110 spec update nr. 25 says we can and should
1773 * clear LDD15 to 12 for 4 or 8bpp modes with active
1774 * panels.
1775 */
1776 if ((fbi->reg_lccr0 & LCCR0_CMS) == LCCR0_Color &&
1777 (fbi->reg_lccr0 & (LCCR0_Dual|LCCR0_Act)) != 0) {
1778 mask = GPIO_LDD11 | GPIO_LDD10 | GPIO_LDD9 | GPIO_LDD8;
1779
1780 if (fbi->fb.var.bits_per_pixel > 8 ||
1781 (fbi->reg_lccr0 & (LCCR0_Dual|LCCR0_Act)) == LCCR0_Dual)
1782 mask |= GPIO_LDD15 | GPIO_LDD14 | GPIO_LDD13 | GPIO_LDD12;
1783
1784 }
1785
1786 #ifdef CONFIG_SA1100_FREEBIRD
1787 #error Please contact <rmk@arm.linux.org.uk> about this
1788 if (machine_is_freebird()) {
1789 /* Color single passive */
1790 mask |= GPIO_LDD15 | GPIO_LDD14 | GPIO_LDD13 | GPIO_LDD12 |
1791 GPIO_LDD11 | GPIO_LDD10 | GPIO_LDD9 | GPIO_LDD8;
1792 }
1793 #endif
1794 #ifdef CONFIG_SA1100_CERF
1795 #error Please contact <rmk@arm.linux.org.uk> about this
1796 if (machine_is_cerf()) {
1797 /* GPIO15 is used as a bypass for 3.8" displays */
1798 mask |= GPIO_GPIO15;
1799
1800 /* FIXME: why is this? The Cerf's display doesn't seem
1801 * to be dual scan or active. I just leave it here,
1802 * but in my opinion this is definitively wrong.
1803 * -- Erik <J.A.K.Mouw@its.tudelft.nl>
1804 */
1805
1806 /* REPLY: Umm.. Well to be honest, the 5.7" LCD which
1807 * this was used for does not use these pins, but
1808 * apparently all hell breaks loose if they are not
1809 * set on the Cerf, so we decided to leave them in ;)
1810 * -- Daniel Chemko <dchemko@intrinsyc.com>
1811 */
1812 /* color {dual/single} passive */
1813 mask |= GPIO_LDD15 | GPIO_LDD14 | GPIO_LDD13 | GPIO_LDD12 |
1814 GPIO_LDD11 | GPIO_LDD10 | GPIO_LDD9 | GPIO_LDD8;
1815 }
1816 #endif
1817
1818 if (mask) {
1819 GPDR |= mask;
1820 GAFR |= mask;
1821 }
1822 }
1823
1824 static void sa1100fb_enable_controller(struct sa1100fb_info *fbi)
1825 {
1826 DPRINTK("Enabling LCD controller\n");
1827
1828 /*
1829 * Make sure the mode bits are present in the first palette entry
1830 */
1831 fbi->palette_cpu[0] &= 0xcfff;
1832 fbi->palette_cpu[0] |= palette_pbs(&fbi->fb.var);
1833
1834 /* Sequence from 11.7.10 */
1835 LCCR3 = fbi->reg_lccr3;
1836 LCCR2 = fbi->reg_lccr2;
1837 LCCR1 = fbi->reg_lccr1;
1838 LCCR0 = fbi->reg_lccr0 & ~LCCR0_LEN;
1839 DBAR1 = (Address) fbi->dbar1;
1840 DBAR2 = (Address) fbi->dbar2;
1841 LCCR0 |= LCCR0_LEN;
1842
1843 #ifdef CONFIG_SA1100_GRAPHICSCLIENT
1844 #error Where is GPIO24 set as an output? Can we fit this in somewhere else?
1845 if (machine_is_graphicsclient()) {
1846 // From ADS doc again...same as disable
1847 set_current_state(TASK_UNINTERRUPTIBLE);
1848 schedule_timeout(20 * HZ / 1000);
1849 GPSR |= GPIO_GPIO24;
1850 }
1851 #endif
1852
1853 DPRINTK("DBAR1 = %p\n", DBAR1);
1854 DPRINTK("DBAR2 = %p\n", DBAR2);
1855 DPRINTK("LCCR0 = 0x%08x\n", LCCR0);
1856 DPRINTK("LCCR1 = 0x%08x\n", LCCR1);
1857 DPRINTK("LCCR2 = 0x%08x\n", LCCR2);
1858 DPRINTK("LCCR3 = 0x%08x\n", LCCR3);
1859 }
1860
1861 static void sa1100fb_disable_controller(struct sa1100fb_info *fbi)
1862 {
1863 DECLARE_WAITQUEUE(wait, current);
1864
1865 DPRINTK("Disabling LCD controller\n");
1866
1867 #ifdef CONFIG_SA1100_GRAPHICSCLIENT
1868 #error Where is GPIO24 set as an output? Can we fit this in somewhere else?
1869 if (machine_is_graphicsclient()) {
1870 /*
1871 * From ADS internal document:
1872 * GPIO24 should be LOW at least 10msec prior to disabling
1873 * the LCD interface.
1874 *
1875 * We'll wait 20msec.
1876 */
1877 GPCR |= GPIO_GPIO24;
1878 set_current_state(TASK_UNINTERRUPTIBLE);
1879 schedule_timeout(20 * HZ / 1000);
1880 }
1881 #endif
1882 #ifdef CONFIG_SA1100_HUW_WEBPANEL
1883 #error Move me into sa1100fb_power_up_lcd and/or sa1100fb_backlight_on
1884 if (machine_is_huw_webpanel()) {
1885 // dont forget to set the control lines to zero (?)
1886 DPRINTK("ShutDown HuW LCD controller\n");
1887 BCR_clear(BCR_TFT_ENA + BCR_CCFL_POW + BCR_PWM_BACKLIGHT);
1888 }
1889 #endif
1890
1891 add_wait_queue(&fbi->ctrlr_wait, &wait);
1892 set_current_state(TASK_UNINTERRUPTIBLE);
1893
1894 LCSR = 0xffffffff; /* Clear LCD Status Register */
1895 LCCR0 &= ~LCCR0_LDM; /* Enable LCD Disable Done Interrupt */
1896 enable_irq(IRQ_LCD); /* Enable LCD IRQ */
1897 LCCR0 &= ~LCCR0_LEN; /* Disable LCD Controller */
1898
1899 schedule_timeout(20 * HZ / 1000);
1900 current->state = TASK_RUNNING;
1901 remove_wait_queue(&fbi->ctrlr_wait, &wait);
1902 }
1903
1904 /*
1905 * sa1100fb_handle_irq: Handle 'LCD DONE' interrupts.
1906 */
1907 static void sa1100fb_handle_irq(int irq, void *dev_id, struct pt_regs *regs)
1908 {
1909 struct sa1100fb_info *fbi = dev_id;
1910 unsigned int lcsr = LCSR;
1911
1912 if (lcsr & LCSR_LDD) {
1913 LCCR0 |= LCCR0_LDM;
1914 wake_up(&fbi->ctrlr_wait);
1915 }
1916
1917 LCSR = lcsr;
1918 }
1919
1920 /*
1921 * This function must be called from task context only, since it will
1922 * sleep when disabling the LCD controller, or if we get two contending
1923 * processes trying to alter state.
1924 */
1925 static void set_ctrlr_state(struct sa1100fb_info *fbi, u_int state)
1926 {
1927 u_int old_state;
1928
1929 down(&fbi->ctrlr_sem);
1930
1931 old_state = fbi->state;
1932
1933 switch (state) {
1934 case C_DISABLE_CLKCHANGE:
1935 /*
1936 * Disable controller for clock change. If the
1937 * controller is already disabled, then do nothing.
1938 */
1939 if (old_state != C_DISABLE) {
1940 fbi->state = state;
1941 sa1100fb_disable_controller(fbi);
1942 }
1943 break;
1944
1945 case C_DISABLE:
1946 /*
1947 * Disable controller
1948 */
1949 if (old_state != C_DISABLE) {
1950 fbi->state = state;
1951
1952 sa1100fb_backlight_off(fbi);
1953 if (old_state != C_DISABLE_CLKCHANGE)
1954 sa1100fb_disable_controller(fbi);
1955 sa1100fb_power_down_lcd(fbi);
1956 }
1957 break;
1958
1959 case C_ENABLE_CLKCHANGE:
1960 /*
1961 * Enable the controller after clock change. Only
1962 * do this if we were disabled for the clock change.
1963 */
1964 if (old_state == C_DISABLE_CLKCHANGE) {
1965 fbi->state = C_ENABLE;
1966 sa1100fb_enable_controller(fbi);
1967 }
1968 break;
1969
1970 case C_REENABLE:
1971 /*
1972 * Re-enable the controller only if it was already
1973 * enabled. This is so we reprogram the control
1974 * registers.
1975 */
1976 if (old_state == C_ENABLE) {
1977 sa1100fb_disable_controller(fbi);
1978 sa1100fb_setup_gpio(fbi);
1979 sa1100fb_enable_controller(fbi);
1980 }
1981 break;
1982
1983 case C_ENABLE:
1984 /*
1985 * Power up the LCD screen, enable controller, and
1986 * turn on the backlight.
1987 */
1988 if (old_state != C_ENABLE) {
1989 fbi->state = C_ENABLE;
1990 sa1100fb_setup_gpio(fbi);
1991 sa1100fb_power_up_lcd(fbi);
1992 sa1100fb_enable_controller(fbi);
1993 sa1100fb_backlight_on(fbi);
1994 }
1995 break;
1996 }
1997 up(&fbi->ctrlr_sem);
1998 }
1999
2000 /*
2001 * Our LCD controller task (which is called when we blank or unblank)
2002 * via keventd.
2003 */
2004 static void sa1100fb_task(void *dummy)
2005 {
2006 struct sa1100fb_info *fbi = dummy;
2007 u_int state = xchg(&fbi->task_state, -1);
2008
2009 set_ctrlr_state(fbi, state);
2010 }
2011
2012 #ifdef CONFIG_CPU_FREQ
2013 /*
2014 * Calculate the minimum DMA period over all displays that we own.
2015 * This, together with the SDRAM bandwidth defines the slowest CPU
2016 * frequency that can be selected.
2017 */
2018 static unsigned int sa1100fb_min_dma_period(struct sa1100fb_info *fbi)
2019 {
2020 unsigned int min_period = (unsigned int)-1;
2021 int i;
2022
2023 for (i = 0; i < MAX_NR_CONSOLES; i++) {
2024 unsigned int period;
2025
2026 /*
2027 * Do we own this display?
2028 */
2029 if (fb_display[i].fb_info != &fbi->fb)
2030 continue;
2031
2032 /*
2033 * Ok, calculate its DMA period
2034 */
2035 period = sa1100fb_display_dma_period(get_con_var(fbi, i));
2036 if (period < min_period)
2037 min_period = period;
2038 }
2039
2040 return min_period;
2041 }
2042
2043 /*
2044 * CPU clock speed change handler. We need to adjust the LCD timing
2045 * parameters when the CPU clock is adjusted by the power management
2046 * subsystem.
2047 */
2048 static int
2049 sa1100fb_clkchg_notifier(struct notifier_block *nb, unsigned long val,
2050 void *data)
2051 {
2052 struct sa1100fb_info *fbi = TO_INF(nb, clockchg);
2053 struct cpufreq_minmax *mm = data;
2054 u_int pcd;
2055
2056 switch (val) {
2057 case CPUFREQ_MINMAX:
2058 printk(KERN_DEBUG "min dma period: %d ps, old clock %d kHz, "
2059 "new clock %d kHz\n", sa1100fb_min_dma_period(fbi),
2060 mm->cur_freq, mm->new_freq);
2061 /* todo: fill in min/max values */
2062 break;
2063
2064 case CPUFREQ_PRECHANGE:
2065 set_ctrlr_state(fbi, C_DISABLE_CLKCHANGE);
2066 break;
2067
2068 case CPUFREQ_POSTCHANGE:
2069 pcd = get_pcd(fbi->fb.var.pixclock);
2070 fbi->reg_lccr3 = (fbi->reg_lccr3 & ~0xff) | LCCR3_PixClkDiv(pcd);
2071 set_ctrlr_state(fbi, C_ENABLE_CLKCHANGE);
2072 break;
2073 }
2074 return 0;
2075 }
2076 #endif
2077
2078 #ifdef CONFIG_PM
2079 /*
2080 * Power management hook. Note that we won't be called from IRQ context,
2081 * unlike the blank functions above, so we may sleep.
2082 */
2083 static int
2084 sa1100fb_pm_callback(struct pm_dev *pm_dev, pm_request_t req, void *data)
2085 {
2086 struct sa1100fb_info *fbi = pm_dev->data;
2087
2088 DPRINTK("pm_callback: %d\n", req);
2089
2090 if (req == PM_SUSPEND || req == PM_RESUME) {
2091 int state = (int)data;
2092
2093 if (state == 0) {
2094 /* Enter D0. */
2095 set_ctrlr_state(fbi, C_ENABLE);
2096 } else {
2097 /* Enter D1-D3. Disable the LCD controller. */
2098 set_ctrlr_state(fbi, C_DISABLE);
2099 }
2100 }
2101 DPRINTK("done\n");
2102 return 0;
2103 }
2104 #endif
2105
2106 /*
2107 * sa1100fb_map_video_memory():
2108 * Allocates the DRAM memory for the frame buffer. This buffer is
2109 * remapped into a non-cached, non-buffered, memory region to
2110 * allow palette and pixel writes to occur without flushing the
2111 * cache. Once this area is remapped, all virtual memory
2112 * access to the video memory should occur at the new region.
2113 */
2114 static int __init sa1100fb_map_video_memory(struct sa1100fb_info *fbi)
2115 {
2116 /*
2117 * We reserve one page for the palette, plus the size
2118 * of the framebuffer.
2119 */
2120 fbi->map_size = PAGE_ALIGN(fbi->fb.fix.smem_len + PAGE_SIZE);
2121 fbi->map_cpu = consistent_alloc(GFP_KERNEL, fbi->map_size,
2122 &fbi->map_dma);
2123
2124 if (fbi->map_cpu) {
2125 fbi->screen_cpu = fbi->map_cpu + PAGE_SIZE;
2126 fbi->screen_dma = fbi->map_dma + PAGE_SIZE;
2127 fbi->fb.fix.smem_start = fbi->screen_dma;
2128 }
2129
2130 return fbi->map_cpu ? 0 : -ENOMEM;
2131 }
2132
2133 /* Fake monspecs to fill in fbinfo structure */
2134 static struct fb_monspecs monspecs __initdata = {
2135 30000, 70000, 50, 65, 0 /* Generic */
2136 };
2137
2138
2139 static struct sa1100fb_info * __init sa1100fb_init_fbinfo(void)
2140 {
2141 struct sa1100fb_mach_info *inf;
2142 struct sa1100fb_info *fbi;
2143
2144 fbi = kmalloc(sizeof(struct sa1100fb_info) + sizeof(struct display) +
2145 sizeof(u16) * 16, GFP_KERNEL);
2146 if (!fbi)
2147 return NULL;
2148
2149 memset(fbi, 0, sizeof(struct sa1100fb_info) + sizeof(struct display));
2150
2151 fbi->currcon = -1;
2152
2153 strcpy(fbi->fb.fix.id, SA1100_NAME);
2154
2155 fbi->fb.fix.type = FB_TYPE_PACKED_PIXELS;
2156 fbi->fb.fix.type_aux = 0;
2157 fbi->fb.fix.xpanstep = 0;
2158 fbi->fb.fix.ypanstep = 0;
2159 fbi->fb.fix.ywrapstep = 0;
2160 fbi->fb.fix.accel = FB_ACCEL_NONE;
2161
2162 fbi->fb.var.nonstd = 0;
2163 fbi->fb.var.activate = FB_ACTIVATE_NOW;
2164 fbi->fb.var.height = -1;
2165 fbi->fb.var.width = -1;
2166 fbi->fb.var.accel_flags = 0;
2167 fbi->fb.var.vmode = FB_VMODE_NONINTERLACED;
2168
2169 strcpy(fbi->fb.modename, SA1100_NAME);
2170 strcpy(fbi->fb.fontname, "Acorn8x8");
2171
2172 fbi->fb.fbops = &sa1100fb_ops;
2173 fbi->fb.changevar = NULL;
2174 fbi->fb.switch_con = sa1100fb_switch;
2175 fbi->fb.updatevar = sa1100fb_updatevar;
2176 fbi->fb.blank = sa1100fb_blank;
2177 fbi->fb.flags = FBINFO_FLAG_DEFAULT;
2178 fbi->fb.node = -1;
2179 fbi->fb.monspecs = monspecs;
2180 fbi->fb.disp = (struct display *)(fbi + 1);
2181 fbi->fb.pseudo_palette = (void *)(fbi->fb.disp + 1);
2182
2183 fbi->rgb[RGB_8] = &rgb_8;
2184 fbi->rgb[RGB_16] = &def_rgb_16;
2185
2186 inf = sa1100fb_get_machine_info(fbi);
2187
2188 fbi->max_xres = inf->xres;
2189 fbi->fb.var.xres = inf->xres;
2190 fbi->fb.var.xres_virtual = inf->xres;
2191 fbi->max_yres = inf->yres;
2192 fbi->fb.var.yres = inf->yres;
2193 fbi->fb.var.yres_virtual = inf->yres;
2194 fbi->max_bpp = inf->bpp;
2195 fbi->fb.var.bits_per_pixel = inf->bpp;
2196 fbi->fb.var.pixclock = inf->pixclock;
2197 fbi->fb.var.hsync_len = inf->hsync_len;
2198 fbi->fb.var.left_margin = inf->left_margin;
2199 fbi->fb.var.right_margin = inf->right_margin;
2200 fbi->fb.var.vsync_len = inf->vsync_len;
2201 fbi->fb.var.upper_margin = inf->upper_margin;
2202 fbi->fb.var.lower_margin = inf->lower_margin;
2203 fbi->fb.var.sync = inf->sync;
2204 fbi->fb.var.grayscale = inf->cmap_greyscale;
2205 fbi->cmap_inverse = inf->cmap_inverse;
2206 fbi->cmap_static = inf->cmap_static;
2207 fbi->lccr0 = inf->lccr0;
2208 fbi->lccr3 = inf->lccr3;
2209 fbi->state = C_DISABLE;
2210 fbi->task_state = (u_char)-1;
2211 fbi->fb.fix.smem_len = fbi->max_xres * fbi->max_yres *
2212 fbi->max_bpp / 8;
2213
2214 init_waitqueue_head(&fbi->ctrlr_wait);
2215 INIT_TQUEUE(&fbi->task, sa1100fb_task, fbi);
2216 init_MUTEX(&fbi->ctrlr_sem);
2217
2218 return fbi;
2219 }
2220
2221 int __init sa1100fb_init(void)
2222 {
2223 struct sa1100fb_info *fbi;
2224 int ret;
2225
2226 fbi = sa1100fb_init_fbinfo();
2227 ret = -ENOMEM;
2228 if (!fbi)
2229 goto failed;
2230
2231 /* Initialize video memory */
2232 ret = sa1100fb_map_video_memory(fbi);
2233 if (ret)
2234 goto failed;
2235
2236 ret = request_irq(IRQ_LCD, sa1100fb_handle_irq, SA_INTERRUPT,
2237 fbi->fb.fix.id, fbi);
2238 if (ret) {
2239 printk(KERN_ERR "sa1100fb: failed in request_irq: %d\n", ret);
2240 goto failed;
2241 }
2242
2243 #if defined(CONFIG_SA1100_ASSABET) && defined(ASSABET_PAL_VIDEO)
2244 if (machine_is_assabet())
2245 BCR_clear(BCR_LCD_ON);
2246 #endif
2247
2248 #ifdef CONFIG_SA1100_FREEBIRD
2249 #error Please move this into sa1100fb_power_up_lcd
2250 if (machine_is_freebird()) {
2251 BCR_set(BCR_FREEBIRD_LCD_DISP);
2252 mdelay(20);
2253 BCR_set(BCR_FREEBIRD_LCD_PWR);
2254 mdelay(20);
2255 }
2256 #endif
2257
2258 sa1100fb_set_var(&fbi->fb.var, -1, &fbi->fb);
2259
2260 ret = register_framebuffer(&fbi->fb);
2261 if (ret < 0)
2262 goto failed;
2263
2264 #ifdef CONFIG_PM
2265 /*
2266 * Note that the console registers this as well, but we want to
2267 * power down the display prior to sleeping.
2268 */
2269 fbi->pm = pm_register(PM_SYS_DEV, PM_SYS_VGA, sa1100fb_pm_callback);
2270 if (fbi->pm)
2271 fbi->pm->data = fbi;
2272 #endif
2273 #ifdef CONFIG_CPU_FREQ
2274 fbi->clockchg.notifier_call = sa1100fb_clkchg_notifier;
2275 cpufreq_register_notifier(&fbi->clockchg);
2276 #endif
2277
2278 /*
2279 * Ok, now enable the LCD controller
2280 */
2281 set_ctrlr_state(fbi, C_ENABLE);
2282
2283 /* This driver cannot be unloaded at the moment */
2284 MOD_INC_USE_COUNT;
2285
2286 return 0;
2287
2288 failed:
2289 if (fbi)
2290 kfree(fbi);
2291 return ret;
2292 }
2293
2294 int __init sa1100fb_setup(char *options)
2295 {
2296 #if 0
2297 char *this_opt;
2298
2299 if (!options || !*options)
2300 return 0;
2301
2302 for (this_opt = strtok(options, ","); this_opt;
2303 this_opt = strtok(NULL, ",")) {
2304
2305 if (!strncmp(this_opt, "bpp:", 4))
2306 current_par.max_bpp =
2307 simple_strtoul(this_opt + 4, NULL, 0);
2308
2309 if (!strncmp(this_opt, "lccr0:", 6))
2310 lcd_shadow.lccr0 =
2311 simple_strtoul(this_opt + 6, NULL, 0);
2312 if (!strncmp(this_opt, "lccr1:", 6)) {
2313 lcd_shadow.lccr1 =
2314 simple_strtoul(this_opt + 6, NULL, 0);
2315 current_par.max_xres =
2316 (lcd_shadow.lccr1 & 0x3ff) + 16;
2317 }
2318 if (!strncmp(this_opt, "lccr2:", 6)) {
2319 lcd_shadow.lccr2 =
2320 simple_strtoul(this_opt + 6, NULL, 0);
2321 current_par.max_yres =
2322 (lcd_shadow.
2323 lccr0 & LCCR0_SDS) ? ((lcd_shadow.
2324 lccr2 & 0x3ff) +
2325 1) *
2326 2 : ((lcd_shadow.lccr2 & 0x3ff) + 1);
2327 }
2328 if (!strncmp(this_opt, "lccr3:", 6))
2329 lcd_shadow.lccr3 =
2330 simple_strtoul(this_opt + 6, NULL, 0);
2331 }
2332 #endif
2333 return 0;
2334 }
2335
2336 MODULE_LICENSE("GPL");
2337