diff options
author | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@ppc970.osdl.org> | 2005-04-16 18:20:36 -0400 |
commit | 1da177e4c3f41524e886b7f1b8a0c1fc7321cac2 (patch) | |
tree | 0bba044c4ce775e45a88a51686b5d9f90697ea9d /drivers/video/stifb.c |
Linux-2.6.12-rc2v2.6.12-rc2
Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.
Let it rip!
Diffstat (limited to 'drivers/video/stifb.c')
-rw-r--r-- | drivers/video/stifb.c | 1495 |
1 files changed, 1495 insertions, 0 deletions
diff --git a/drivers/video/stifb.c b/drivers/video/stifb.c new file mode 100644 index 000000000000..9e52794768e6 --- /dev/null +++ b/drivers/video/stifb.c | |||
@@ -0,0 +1,1495 @@ | |||
1 | /* | ||
2 | * linux/drivers/video/stifb.c - | ||
3 | * Low level Frame buffer driver for HP workstations with | ||
4 | * STI (standard text interface) video firmware. | ||
5 | * | ||
6 | * Copyright (C) 2001-2004 Helge Deller <deller@gmx.de> | ||
7 | * Portions Copyright (C) 2001 Thomas Bogendoerfer <tsbogend@alpha.franken.de> | ||
8 | * | ||
9 | * Based on: | ||
10 | * - linux/drivers/video/artistfb.c -- Artist frame buffer driver | ||
11 | * Copyright (C) 2000 Philipp Rumpf <prumpf@tux.org> | ||
12 | * - based on skeletonfb, which was | ||
13 | * Created 28 Dec 1997 by Geert Uytterhoeven | ||
14 | * - HP Xhp cfb-based X11 window driver for XFree86 | ||
15 | * (c)Copyright 1992 Hewlett-Packard Co. | ||
16 | * | ||
17 | * | ||
18 | * The following graphics display devices (NGLE family) are supported by this driver: | ||
19 | * | ||
20 | * HPA4070A known as "HCRX", a 1280x1024 color device with 8 planes | ||
21 | * HPA4071A known as "HCRX24", a 1280x1024 color device with 24 planes, | ||
22 | * optionally available with a hardware accelerator as HPA4071A_Z | ||
23 | * HPA1659A known as "CRX", a 1280x1024 color device with 8 planes | ||
24 | * HPA1439A known as "CRX24", a 1280x1024 color device with 24 planes, | ||
25 | * optionally available with a hardware accelerator. | ||
26 | * HPA1924A known as "GRX", a 1280x1024 grayscale device with 8 planes | ||
27 | * HPA2269A known as "Dual CRX", a 1280x1024 color device with 8 planes, | ||
28 | * implements support for two displays on a single graphics card. | ||
29 | * HP710C internal graphics support optionally available on the HP9000s710 SPU, | ||
30 | * supports 1280x1024 color displays with 8 planes. | ||
31 | * HP710G same as HP710C, 1280x1024 grayscale only | ||
32 | * HP710L same as HP710C, 1024x768 color only | ||
33 | * HP712 internal graphics support on HP9000s712 SPU, supports 640x480, | ||
34 | * 1024x768 or 1280x1024 color displays on 8 planes (Artist) | ||
35 | * | ||
36 | * This file is subject to the terms and conditions of the GNU General Public | ||
37 | * License. See the file COPYING in the main directory of this archive | ||
38 | * for more details. | ||
39 | */ | ||
40 | |||
41 | /* TODO: | ||
42 | * - 1bpp mode is completely untested | ||
43 | * - add support for h/w acceleration | ||
44 | * - add hardware cursor | ||
45 | * - automatically disable double buffering (e.g. on RDI precisionbook laptop) | ||
46 | */ | ||
47 | |||
48 | |||
49 | /* on supported graphic devices you may: | ||
50 | * #define FALLBACK_TO_1BPP to fall back to 1 bpp, or | ||
51 | * #undef FALLBACK_TO_1BPP to reject support for unsupported cards */ | ||
52 | #undef FALLBACK_TO_1BPP | ||
53 | |||
54 | #undef DEBUG_STIFB_REGS /* debug sti register accesses */ | ||
55 | |||
56 | |||
57 | #include <linux/config.h> | ||
58 | #include <linux/module.h> | ||
59 | #include <linux/kernel.h> | ||
60 | #include <linux/errno.h> | ||
61 | #include <linux/string.h> | ||
62 | #include <linux/mm.h> | ||
63 | #include <linux/slab.h> | ||
64 | #include <linux/delay.h> | ||
65 | #include <linux/fb.h> | ||
66 | #include <linux/init.h> | ||
67 | #include <linux/ioport.h> | ||
68 | #include <linux/pci.h> | ||
69 | |||
70 | #include <asm/grfioctl.h> /* for HP-UX compatibility */ | ||
71 | #include <asm/uaccess.h> | ||
72 | |||
73 | #include "sticore.h" | ||
74 | |||
75 | /* REGION_BASE(fb_info, index) returns the virtual address for region <index> */ | ||
76 | #ifdef __LP64__ | ||
77 | #define REGION_BASE(fb_info, index) \ | ||
78 | (fb_info->sti->glob_cfg->region_ptrs[index] | 0xffffffff00000000) | ||
79 | #else | ||
80 | #define REGION_BASE(fb_info, index) \ | ||
81 | fb_info->sti->glob_cfg->region_ptrs[index] | ||
82 | #endif | ||
83 | |||
84 | #define NGLEDEVDEPROM_CRT_REGION 1 | ||
85 | |||
86 | typedef struct { | ||
87 | __s32 video_config_reg; | ||
88 | __s32 misc_video_start; | ||
89 | __s32 horiz_timing_fmt; | ||
90 | __s32 serr_timing_fmt; | ||
91 | __s32 vert_timing_fmt; | ||
92 | __s32 horiz_state; | ||
93 | __s32 vert_state; | ||
94 | __s32 vtg_state_elements; | ||
95 | __s32 pipeline_delay; | ||
96 | __s32 misc_video_end; | ||
97 | } video_setup_t; | ||
98 | |||
99 | typedef struct { | ||
100 | __s16 sizeof_ngle_data; | ||
101 | __s16 x_size_visible; /* visible screen dim in pixels */ | ||
102 | __s16 y_size_visible; | ||
103 | __s16 pad2[15]; | ||
104 | __s16 cursor_pipeline_delay; | ||
105 | __s16 video_interleaves; | ||
106 | __s32 pad3[11]; | ||
107 | } ngle_rom_t; | ||
108 | |||
109 | struct stifb_info { | ||
110 | struct fb_info info; | ||
111 | unsigned int id; | ||
112 | ngle_rom_t ngle_rom; | ||
113 | struct sti_struct *sti; | ||
114 | int deviceSpecificConfig; | ||
115 | u32 pseudo_palette[256]; | ||
116 | }; | ||
117 | |||
118 | static int __initdata stifb_bpp_pref[MAX_STI_ROMS]; | ||
119 | |||
120 | /* ------------------- chipset specific functions -------------------------- */ | ||
121 | |||
122 | /* offsets to graphic-chip internal registers */ | ||
123 | |||
124 | #define REG_1 0x000118 | ||
125 | #define REG_2 0x000480 | ||
126 | #define REG_3 0x0004a0 | ||
127 | #define REG_4 0x000600 | ||
128 | #define REG_6 0x000800 | ||
129 | #define REG_8 0x000820 | ||
130 | #define REG_9 0x000a04 | ||
131 | #define REG_10 0x018000 | ||
132 | #define REG_11 0x018004 | ||
133 | #define REG_12 0x01800c | ||
134 | #define REG_13 0x018018 | ||
135 | #define REG_14 0x01801c | ||
136 | #define REG_15 0x200000 | ||
137 | #define REG_15b0 0x200000 | ||
138 | #define REG_16b1 0x200005 | ||
139 | #define REG_16b3 0x200007 | ||
140 | #define REG_21 0x200218 | ||
141 | #define REG_22 0x0005a0 | ||
142 | #define REG_23 0x0005c0 | ||
143 | #define REG_26 0x200118 | ||
144 | #define REG_27 0x200308 | ||
145 | #define REG_32 0x21003c | ||
146 | #define REG_33 0x210040 | ||
147 | #define REG_34 0x200008 | ||
148 | #define REG_35 0x018010 | ||
149 | #define REG_38 0x210020 | ||
150 | #define REG_39 0x210120 | ||
151 | #define REG_40 0x210130 | ||
152 | #define REG_42 0x210028 | ||
153 | #define REG_43 0x21002c | ||
154 | #define REG_44 0x210030 | ||
155 | #define REG_45 0x210034 | ||
156 | |||
157 | #define READ_BYTE(fb,reg) gsc_readb((fb)->info.fix.mmio_start + (reg)) | ||
158 | #define READ_WORD(fb,reg) gsc_readl((fb)->info.fix.mmio_start + (reg)) | ||
159 | |||
160 | |||
161 | #ifndef DEBUG_STIFB_REGS | ||
162 | # define DEBUG_OFF() | ||
163 | # define DEBUG_ON() | ||
164 | # define WRITE_BYTE(value,fb,reg) gsc_writeb((value),(fb)->info.fix.mmio_start + (reg)) | ||
165 | # define WRITE_WORD(value,fb,reg) gsc_writel((value),(fb)->info.fix.mmio_start + (reg)) | ||
166 | #else | ||
167 | static int debug_on = 1; | ||
168 | # define DEBUG_OFF() debug_on=0 | ||
169 | # define DEBUG_ON() debug_on=1 | ||
170 | # define WRITE_BYTE(value,fb,reg) do { if (debug_on) \ | ||
171 | printk(KERN_DEBUG "%30s: WRITE_BYTE(0x%06x) = 0x%02x (old=0x%02x)\n", \ | ||
172 | __FUNCTION__, reg, value, READ_BYTE(fb,reg)); \ | ||
173 | gsc_writeb((value),(fb)->info.fix.mmio_start + (reg)); } while (0) | ||
174 | # define WRITE_WORD(value,fb,reg) do { if (debug_on) \ | ||
175 | printk(KERN_DEBUG "%30s: WRITE_WORD(0x%06x) = 0x%08x (old=0x%08x)\n", \ | ||
176 | __FUNCTION__, reg, value, READ_WORD(fb,reg)); \ | ||
177 | gsc_writel((value),(fb)->info.fix.mmio_start + (reg)); } while (0) | ||
178 | #endif /* DEBUG_STIFB_REGS */ | ||
179 | |||
180 | |||
181 | #define ENABLE 1 /* for enabling/disabling screen */ | ||
182 | #define DISABLE 0 | ||
183 | |||
184 | #define NGLE_LOCK(fb_info) do { } while (0) | ||
185 | #define NGLE_UNLOCK(fb_info) do { } while (0) | ||
186 | |||
187 | static void | ||
188 | SETUP_HW(struct stifb_info *fb) | ||
189 | { | ||
190 | char stat; | ||
191 | |||
192 | do { | ||
193 | stat = READ_BYTE(fb, REG_15b0); | ||
194 | if (!stat) | ||
195 | stat = READ_BYTE(fb, REG_15b0); | ||
196 | } while (stat); | ||
197 | } | ||
198 | |||
199 | |||
200 | static void | ||
201 | SETUP_FB(struct stifb_info *fb) | ||
202 | { | ||
203 | unsigned int reg10_value = 0; | ||
204 | |||
205 | SETUP_HW(fb); | ||
206 | switch (fb->id) | ||
207 | { | ||
208 | case CRT_ID_VISUALIZE_EG: | ||
209 | case S9000_ID_ARTIST: | ||
210 | case S9000_ID_A1659A: | ||
211 | reg10_value = 0x13601000; | ||
212 | break; | ||
213 | case S9000_ID_A1439A: | ||
214 | if (fb->info.var.bits_per_pixel == 32) | ||
215 | reg10_value = 0xBBA0A000; | ||
216 | else | ||
217 | reg10_value = 0x13601000; | ||
218 | break; | ||
219 | case S9000_ID_HCRX: | ||
220 | if (fb->info.var.bits_per_pixel == 32) | ||
221 | reg10_value = 0xBBA0A000; | ||
222 | else | ||
223 | reg10_value = 0x13602000; | ||
224 | break; | ||
225 | case S9000_ID_TIMBER: | ||
226 | case CRX24_OVERLAY_PLANES: | ||
227 | reg10_value = 0x13602000; | ||
228 | break; | ||
229 | } | ||
230 | if (reg10_value) | ||
231 | WRITE_WORD(reg10_value, fb, REG_10); | ||
232 | WRITE_WORD(0x83000300, fb, REG_14); | ||
233 | SETUP_HW(fb); | ||
234 | WRITE_BYTE(1, fb, REG_16b1); | ||
235 | } | ||
236 | |||
237 | static void | ||
238 | START_IMAGE_COLORMAP_ACCESS(struct stifb_info *fb) | ||
239 | { | ||
240 | SETUP_HW(fb); | ||
241 | WRITE_WORD(0xBBE0F000, fb, REG_10); | ||
242 | WRITE_WORD(0x03000300, fb, REG_14); | ||
243 | WRITE_WORD(~0, fb, REG_13); | ||
244 | } | ||
245 | |||
246 | static void | ||
247 | WRITE_IMAGE_COLOR(struct stifb_info *fb, int index, int color) | ||
248 | { | ||
249 | SETUP_HW(fb); | ||
250 | WRITE_WORD(((0x100+index)<<2), fb, REG_3); | ||
251 | WRITE_WORD(color, fb, REG_4); | ||
252 | } | ||
253 | |||
254 | static void | ||
255 | FINISH_IMAGE_COLORMAP_ACCESS(struct stifb_info *fb) | ||
256 | { | ||
257 | WRITE_WORD(0x400, fb, REG_2); | ||
258 | if (fb->info.var.bits_per_pixel == 32) { | ||
259 | WRITE_WORD(0x83000100, fb, REG_1); | ||
260 | } else { | ||
261 | if (fb->id == S9000_ID_ARTIST || fb->id == CRT_ID_VISUALIZE_EG) | ||
262 | WRITE_WORD(0x80000100, fb, REG_26); | ||
263 | else | ||
264 | WRITE_WORD(0x80000100, fb, REG_1); | ||
265 | } | ||
266 | SETUP_FB(fb); | ||
267 | } | ||
268 | |||
269 | static void | ||
270 | SETUP_RAMDAC(struct stifb_info *fb) | ||
271 | { | ||
272 | SETUP_HW(fb); | ||
273 | WRITE_WORD(0x04000000, fb, 0x1020); | ||
274 | WRITE_WORD(0xff000000, fb, 0x1028); | ||
275 | } | ||
276 | |||
277 | static void | ||
278 | CRX24_SETUP_RAMDAC(struct stifb_info *fb) | ||
279 | { | ||
280 | SETUP_HW(fb); | ||
281 | WRITE_WORD(0x04000000, fb, 0x1000); | ||
282 | WRITE_WORD(0x02000000, fb, 0x1004); | ||
283 | WRITE_WORD(0xff000000, fb, 0x1008); | ||
284 | WRITE_WORD(0x05000000, fb, 0x1000); | ||
285 | WRITE_WORD(0x02000000, fb, 0x1004); | ||
286 | WRITE_WORD(0x03000000, fb, 0x1008); | ||
287 | } | ||
288 | |||
289 | #if 0 | ||
290 | static void | ||
291 | HCRX_SETUP_RAMDAC(struct stifb_info *fb) | ||
292 | { | ||
293 | WRITE_WORD(0xffffffff, fb, REG_32); | ||
294 | } | ||
295 | #endif | ||
296 | |||
297 | static void | ||
298 | CRX24_SET_OVLY_MASK(struct stifb_info *fb) | ||
299 | { | ||
300 | SETUP_HW(fb); | ||
301 | WRITE_WORD(0x13a02000, fb, REG_11); | ||
302 | WRITE_WORD(0x03000300, fb, REG_14); | ||
303 | WRITE_WORD(0x000017f0, fb, REG_3); | ||
304 | WRITE_WORD(0xffffffff, fb, REG_13); | ||
305 | WRITE_WORD(0xffffffff, fb, REG_22); | ||
306 | WRITE_WORD(0x00000000, fb, REG_23); | ||
307 | } | ||
308 | |||
309 | static void | ||
310 | ENABLE_DISABLE_DISPLAY(struct stifb_info *fb, int enable) | ||
311 | { | ||
312 | unsigned int value = enable ? 0x43000000 : 0x03000000; | ||
313 | SETUP_HW(fb); | ||
314 | WRITE_WORD(0x06000000, fb, 0x1030); | ||
315 | WRITE_WORD(value, fb, 0x1038); | ||
316 | } | ||
317 | |||
318 | static void | ||
319 | CRX24_ENABLE_DISABLE_DISPLAY(struct stifb_info *fb, int enable) | ||
320 | { | ||
321 | unsigned int value = enable ? 0x10000000 : 0x30000000; | ||
322 | SETUP_HW(fb); | ||
323 | WRITE_WORD(0x01000000, fb, 0x1000); | ||
324 | WRITE_WORD(0x02000000, fb, 0x1004); | ||
325 | WRITE_WORD(value, fb, 0x1008); | ||
326 | } | ||
327 | |||
328 | static void | ||
329 | ARTIST_ENABLE_DISABLE_DISPLAY(struct stifb_info *fb, int enable) | ||
330 | { | ||
331 | u32 DregsMiscVideo = REG_21; | ||
332 | u32 DregsMiscCtl = REG_27; | ||
333 | |||
334 | SETUP_HW(fb); | ||
335 | if (enable) { | ||
336 | WRITE_WORD(READ_WORD(fb, DregsMiscVideo) | 0x0A000000, fb, DregsMiscVideo); | ||
337 | WRITE_WORD(READ_WORD(fb, DregsMiscCtl) | 0x00800000, fb, DregsMiscCtl); | ||
338 | } else { | ||
339 | WRITE_WORD(READ_WORD(fb, DregsMiscVideo) & ~0x0A000000, fb, DregsMiscVideo); | ||
340 | WRITE_WORD(READ_WORD(fb, DregsMiscCtl) & ~0x00800000, fb, DregsMiscCtl); | ||
341 | } | ||
342 | } | ||
343 | |||
344 | #define GET_ROMTABLE_INDEX(fb) \ | ||
345 | (READ_BYTE(fb, REG_16b3) - 1) | ||
346 | |||
347 | #define HYPER_CONFIG_PLANES_24 0x00000100 | ||
348 | |||
349 | #define IS_24_DEVICE(fb) \ | ||
350 | (fb->deviceSpecificConfig & HYPER_CONFIG_PLANES_24) | ||
351 | |||
352 | #define IS_888_DEVICE(fb) \ | ||
353 | (!(IS_24_DEVICE(fb))) | ||
354 | |||
355 | #define GET_FIFO_SLOTS(fb, cnt, numslots) \ | ||
356 | { while (cnt < numslots) \ | ||
357 | cnt = READ_WORD(fb, REG_34); \ | ||
358 | cnt -= numslots; \ | ||
359 | } | ||
360 | |||
361 | #define IndexedDcd 0 /* Pixel data is indexed (pseudo) color */ | ||
362 | #define Otc04 2 /* Pixels in each longword transfer (4) */ | ||
363 | #define Otc32 5 /* Pixels in each longword transfer (32) */ | ||
364 | #define Ots08 3 /* Each pixel is size (8)d transfer (1) */ | ||
365 | #define OtsIndirect 6 /* Each bit goes through FG/BG color(8) */ | ||
366 | #define AddrLong 5 /* FB address is Long aligned (pixel) */ | ||
367 | #define BINovly 0x2 /* 8 bit overlay */ | ||
368 | #define BINapp0I 0x0 /* Application Buffer 0, Indexed */ | ||
369 | #define BINapp1I 0x1 /* Application Buffer 1, Indexed */ | ||
370 | #define BINapp0F8 0xa /* Application Buffer 0, Fractional 8-8-8 */ | ||
371 | #define BINattr 0xd /* Attribute Bitmap */ | ||
372 | #define RopSrc 0x3 | ||
373 | #define BitmapExtent08 3 /* Each write hits ( 8) bits in depth */ | ||
374 | #define BitmapExtent32 5 /* Each write hits (32) bits in depth */ | ||
375 | #define DataDynamic 0 /* Data register reloaded by direct access */ | ||
376 | #define MaskDynamic 1 /* Mask register reloaded by direct access */ | ||
377 | #define MaskOtc 0 /* Mask contains Object Count valid bits */ | ||
378 | |||
379 | #define MaskAddrOffset(offset) (offset) | ||
380 | #define StaticReg(en) (en) | ||
381 | #define BGx(en) (en) | ||
382 | #define FGx(en) (en) | ||
383 | |||
384 | #define BAJustPoint(offset) (offset) | ||
385 | #define BAIndexBase(base) (base) | ||
386 | #define BA(F,C,S,A,J,B,I) \ | ||
387 | (((F)<<31)|((C)<<27)|((S)<<24)|((A)<<21)|((J)<<16)|((B)<<12)|(I)) | ||
388 | |||
389 | #define IBOvals(R,M,X,S,D,L,B,F) \ | ||
390 | (((R)<<8)|((M)<<16)|((X)<<24)|((S)<<29)|((D)<<28)|((L)<<31)|((B)<<1)|(F)) | ||
391 | |||
392 | #define NGLE_QUICK_SET_IMAGE_BITMAP_OP(fb, val) \ | ||
393 | WRITE_WORD(val, fb, REG_14) | ||
394 | |||
395 | #define NGLE_QUICK_SET_DST_BM_ACCESS(fb, val) \ | ||
396 | WRITE_WORD(val, fb, REG_11) | ||
397 | |||
398 | #define NGLE_QUICK_SET_CTL_PLN_REG(fb, val) \ | ||
399 | WRITE_WORD(val, fb, REG_12) | ||
400 | |||
401 | #define NGLE_REALLY_SET_IMAGE_PLANEMASK(fb, plnmsk32) \ | ||
402 | WRITE_WORD(plnmsk32, fb, REG_13) | ||
403 | |||
404 | #define NGLE_REALLY_SET_IMAGE_FG_COLOR(fb, fg32) \ | ||
405 | WRITE_WORD(fg32, fb, REG_35) | ||
406 | |||
407 | #define NGLE_SET_TRANSFERDATA(fb, val) \ | ||
408 | WRITE_WORD(val, fb, REG_8) | ||
409 | |||
410 | #define NGLE_SET_DSTXY(fb, val) \ | ||
411 | WRITE_WORD(val, fb, REG_6) | ||
412 | |||
413 | #define NGLE_LONG_FB_ADDRESS(fbaddrbase, x, y) ( \ | ||
414 | (u32) (fbaddrbase) + \ | ||
415 | ( (unsigned int) ( (y) << 13 ) | \ | ||
416 | (unsigned int) ( (x) << 2 ) ) \ | ||
417 | ) | ||
418 | |||
419 | #define NGLE_BINC_SET_DSTADDR(fb, addr) \ | ||
420 | WRITE_WORD(addr, fb, REG_3) | ||
421 | |||
422 | #define NGLE_BINC_SET_SRCADDR(fb, addr) \ | ||
423 | WRITE_WORD(addr, fb, REG_2) | ||
424 | |||
425 | #define NGLE_BINC_SET_DSTMASK(fb, mask) \ | ||
426 | WRITE_WORD(mask, fb, REG_22) | ||
427 | |||
428 | #define NGLE_BINC_WRITE32(fb, data32) \ | ||
429 | WRITE_WORD(data32, fb, REG_23) | ||
430 | |||
431 | #define START_COLORMAPLOAD(fb, cmapBltCtlData32) \ | ||
432 | WRITE_WORD((cmapBltCtlData32), fb, REG_38) | ||
433 | |||
434 | #define SET_LENXY_START_RECFILL(fb, lenxy) \ | ||
435 | WRITE_WORD(lenxy, fb, REG_9) | ||
436 | |||
437 | static void | ||
438 | HYPER_ENABLE_DISABLE_DISPLAY(struct stifb_info *fb, int enable) | ||
439 | { | ||
440 | u32 DregsHypMiscVideo = REG_33; | ||
441 | unsigned int value; | ||
442 | SETUP_HW(fb); | ||
443 | value = READ_WORD(fb, DregsHypMiscVideo); | ||
444 | if (enable) | ||
445 | value |= 0x0A000000; | ||
446 | else | ||
447 | value &= ~0x0A000000; | ||
448 | WRITE_WORD(value, fb, DregsHypMiscVideo); | ||
449 | } | ||
450 | |||
451 | |||
452 | /* BufferNumbers used by SETUP_ATTR_ACCESS() */ | ||
453 | #define BUFF0_CMAP0 0x00001e02 | ||
454 | #define BUFF1_CMAP0 0x02001e02 | ||
455 | #define BUFF1_CMAP3 0x0c001e02 | ||
456 | #define ARTIST_CMAP0 0x00000102 | ||
457 | #define HYPER_CMAP8 0x00000100 | ||
458 | #define HYPER_CMAP24 0x00000800 | ||
459 | |||
460 | static void | ||
461 | SETUP_ATTR_ACCESS(struct stifb_info *fb, unsigned BufferNumber) | ||
462 | { | ||
463 | SETUP_HW(fb); | ||
464 | WRITE_WORD(0x2EA0D000, fb, REG_11); | ||
465 | WRITE_WORD(0x23000302, fb, REG_14); | ||
466 | WRITE_WORD(BufferNumber, fb, REG_12); | ||
467 | WRITE_WORD(0xffffffff, fb, REG_8); | ||
468 | } | ||
469 | |||
470 | static void | ||
471 | SET_ATTR_SIZE(struct stifb_info *fb, int width, int height) | ||
472 | { | ||
473 | /* REG_6 seems to have special values when run on a | ||
474 | RDI precisionbook parisc laptop (INTERNAL_EG_DX1024 or | ||
475 | INTERNAL_EG_X1024). The values are: | ||
476 | 0x2f0: internal (LCD) & external display enabled | ||
477 | 0x2a0: external display only | ||
478 | 0x000: zero on standard artist graphic cards | ||
479 | */ | ||
480 | WRITE_WORD(0x00000000, fb, REG_6); | ||
481 | WRITE_WORD((width<<16) | height, fb, REG_9); | ||
482 | WRITE_WORD(0x05000000, fb, REG_6); | ||
483 | WRITE_WORD(0x00040001, fb, REG_9); | ||
484 | } | ||
485 | |||
486 | static void | ||
487 | FINISH_ATTR_ACCESS(struct stifb_info *fb) | ||
488 | { | ||
489 | SETUP_HW(fb); | ||
490 | WRITE_WORD(0x00000000, fb, REG_12); | ||
491 | } | ||
492 | |||
493 | static void | ||
494 | elkSetupPlanes(struct stifb_info *fb) | ||
495 | { | ||
496 | SETUP_RAMDAC(fb); | ||
497 | SETUP_FB(fb); | ||
498 | } | ||
499 | |||
500 | static void | ||
501 | ngleSetupAttrPlanes(struct stifb_info *fb, int BufferNumber) | ||
502 | { | ||
503 | SETUP_ATTR_ACCESS(fb, BufferNumber); | ||
504 | SET_ATTR_SIZE(fb, fb->info.var.xres, fb->info.var.yres); | ||
505 | FINISH_ATTR_ACCESS(fb); | ||
506 | SETUP_FB(fb); | ||
507 | } | ||
508 | |||
509 | |||
510 | static void | ||
511 | rattlerSetupPlanes(struct stifb_info *fb) | ||
512 | { | ||
513 | CRX24_SETUP_RAMDAC(fb); | ||
514 | |||
515 | /* replacement for: SETUP_FB(fb, CRX24_OVERLAY_PLANES); */ | ||
516 | WRITE_WORD(0x83000300, fb, REG_14); | ||
517 | SETUP_HW(fb); | ||
518 | WRITE_BYTE(1, fb, REG_16b1); | ||
519 | |||
520 | fb_memset(fb->info.fix.smem_start, 0xff, | ||
521 | fb->info.var.yres*fb->info.fix.line_length); | ||
522 | |||
523 | CRX24_SET_OVLY_MASK(fb); | ||
524 | SETUP_FB(fb); | ||
525 | } | ||
526 | |||
527 | |||
528 | #define HYPER_CMAP_TYPE 0 | ||
529 | #define NGLE_CMAP_INDEXED0_TYPE 0 | ||
530 | #define NGLE_CMAP_OVERLAY_TYPE 3 | ||
531 | |||
532 | /* typedef of LUT (Colormap) BLT Control Register */ | ||
533 | typedef union /* Note assumption that fields are packed left-to-right */ | ||
534 | { u32 all; | ||
535 | struct | ||
536 | { | ||
537 | unsigned enable : 1; | ||
538 | unsigned waitBlank : 1; | ||
539 | unsigned reserved1 : 4; | ||
540 | unsigned lutOffset : 10; /* Within destination LUT */ | ||
541 | unsigned lutType : 2; /* Cursor, image, overlay */ | ||
542 | unsigned reserved2 : 4; | ||
543 | unsigned length : 10; | ||
544 | } fields; | ||
545 | } NgleLutBltCtl; | ||
546 | |||
547 | |||
548 | #if 0 | ||
549 | static NgleLutBltCtl | ||
550 | setNgleLutBltCtl(struct stifb_info *fb, int offsetWithinLut, int length) | ||
551 | { | ||
552 | NgleLutBltCtl lutBltCtl; | ||
553 | |||
554 | /* set enable, zero reserved fields */ | ||
555 | lutBltCtl.all = 0x80000000; | ||
556 | lutBltCtl.fields.length = length; | ||
557 | |||
558 | switch (fb->id) | ||
559 | { | ||
560 | case S9000_ID_A1439A: /* CRX24 */ | ||
561 | if (fb->var.bits_per_pixel == 8) { | ||
562 | lutBltCtl.fields.lutType = NGLE_CMAP_OVERLAY_TYPE; | ||
563 | lutBltCtl.fields.lutOffset = 0; | ||
564 | } else { | ||
565 | lutBltCtl.fields.lutType = NGLE_CMAP_INDEXED0_TYPE; | ||
566 | lutBltCtl.fields.lutOffset = 0 * 256; | ||
567 | } | ||
568 | break; | ||
569 | |||
570 | case S9000_ID_ARTIST: | ||
571 | lutBltCtl.fields.lutType = NGLE_CMAP_INDEXED0_TYPE; | ||
572 | lutBltCtl.fields.lutOffset = 0 * 256; | ||
573 | break; | ||
574 | |||
575 | default: | ||
576 | lutBltCtl.fields.lutType = NGLE_CMAP_INDEXED0_TYPE; | ||
577 | lutBltCtl.fields.lutOffset = 0; | ||
578 | break; | ||
579 | } | ||
580 | |||
581 | /* Offset points to start of LUT. Adjust for within LUT */ | ||
582 | lutBltCtl.fields.lutOffset += offsetWithinLut; | ||
583 | |||
584 | return lutBltCtl; | ||
585 | } | ||
586 | #endif | ||
587 | |||
588 | static NgleLutBltCtl | ||
589 | setHyperLutBltCtl(struct stifb_info *fb, int offsetWithinLut, int length) | ||
590 | { | ||
591 | NgleLutBltCtl lutBltCtl; | ||
592 | |||
593 | /* set enable, zero reserved fields */ | ||
594 | lutBltCtl.all = 0x80000000; | ||
595 | |||
596 | lutBltCtl.fields.length = length; | ||
597 | lutBltCtl.fields.lutType = HYPER_CMAP_TYPE; | ||
598 | |||
599 | /* Expect lutIndex to be 0 or 1 for image cmaps, 2 or 3 for overlay cmaps */ | ||
600 | if (fb->info.var.bits_per_pixel == 8) | ||
601 | lutBltCtl.fields.lutOffset = 2 * 256; | ||
602 | else | ||
603 | lutBltCtl.fields.lutOffset = 0 * 256; | ||
604 | |||
605 | /* Offset points to start of LUT. Adjust for within LUT */ | ||
606 | lutBltCtl.fields.lutOffset += offsetWithinLut; | ||
607 | |||
608 | return lutBltCtl; | ||
609 | } | ||
610 | |||
611 | |||
612 | static void hyperUndoITE(struct stifb_info *fb) | ||
613 | { | ||
614 | int nFreeFifoSlots = 0; | ||
615 | u32 fbAddr; | ||
616 | |||
617 | NGLE_LOCK(fb); | ||
618 | |||
619 | GET_FIFO_SLOTS(fb, nFreeFifoSlots, 1); | ||
620 | WRITE_WORD(0xffffffff, fb, REG_32); | ||
621 | |||
622 | /* Write overlay transparency mask so only entry 255 is transparent */ | ||
623 | |||
624 | /* Hardware setup for full-depth write to "magic" location */ | ||
625 | GET_FIFO_SLOTS(fb, nFreeFifoSlots, 7); | ||
626 | NGLE_QUICK_SET_DST_BM_ACCESS(fb, | ||
627 | BA(IndexedDcd, Otc04, Ots08, AddrLong, | ||
628 | BAJustPoint(0), BINovly, BAIndexBase(0))); | ||
629 | NGLE_QUICK_SET_IMAGE_BITMAP_OP(fb, | ||
630 | IBOvals(RopSrc, MaskAddrOffset(0), | ||
631 | BitmapExtent08, StaticReg(0), | ||
632 | DataDynamic, MaskOtc, BGx(0), FGx(0))); | ||
633 | |||
634 | /* Now prepare to write to the "magic" location */ | ||
635 | fbAddr = NGLE_LONG_FB_ADDRESS(0, 1532, 0); | ||
636 | NGLE_BINC_SET_DSTADDR(fb, fbAddr); | ||
637 | NGLE_REALLY_SET_IMAGE_PLANEMASK(fb, 0xffffff); | ||
638 | NGLE_BINC_SET_DSTMASK(fb, 0xffffffff); | ||
639 | |||
640 | /* Finally, write a zero to clear the mask */ | ||
641 | NGLE_BINC_WRITE32(fb, 0); | ||
642 | |||
643 | NGLE_UNLOCK(fb); | ||
644 | } | ||
645 | |||
646 | static void | ||
647 | ngleDepth8_ClearImagePlanes(struct stifb_info *fb) | ||
648 | { | ||
649 | /* FIXME! */ | ||
650 | } | ||
651 | |||
652 | static void | ||
653 | ngleDepth24_ClearImagePlanes(struct stifb_info *fb) | ||
654 | { | ||
655 | /* FIXME! */ | ||
656 | } | ||
657 | |||
658 | static void | ||
659 | ngleResetAttrPlanes(struct stifb_info *fb, unsigned int ctlPlaneReg) | ||
660 | { | ||
661 | int nFreeFifoSlots = 0; | ||
662 | u32 packed_dst; | ||
663 | u32 packed_len; | ||
664 | |||
665 | NGLE_LOCK(fb); | ||
666 | |||
667 | GET_FIFO_SLOTS(fb, nFreeFifoSlots, 4); | ||
668 | NGLE_QUICK_SET_DST_BM_ACCESS(fb, | ||
669 | BA(IndexedDcd, Otc32, OtsIndirect, | ||
670 | AddrLong, BAJustPoint(0), | ||
671 | BINattr, BAIndexBase(0))); | ||
672 | NGLE_QUICK_SET_CTL_PLN_REG(fb, ctlPlaneReg); | ||
673 | NGLE_SET_TRANSFERDATA(fb, 0xffffffff); | ||
674 | |||
675 | NGLE_QUICK_SET_IMAGE_BITMAP_OP(fb, | ||
676 | IBOvals(RopSrc, MaskAddrOffset(0), | ||
677 | BitmapExtent08, StaticReg(1), | ||
678 | DataDynamic, MaskOtc, | ||
679 | BGx(0), FGx(0))); | ||
680 | packed_dst = 0; | ||
681 | packed_len = (fb->info.var.xres << 16) | fb->info.var.yres; | ||
682 | GET_FIFO_SLOTS(fb, nFreeFifoSlots, 2); | ||
683 | NGLE_SET_DSTXY(fb, packed_dst); | ||
684 | SET_LENXY_START_RECFILL(fb, packed_len); | ||
685 | |||
686 | /* | ||
687 | * In order to work around an ELK hardware problem (Buffy doesn't | ||
688 | * always flush it's buffers when writing to the attribute | ||
689 | * planes), at least 4 pixels must be written to the attribute | ||
690 | * planes starting at (X == 1280) and (Y != to the last Y written | ||
691 | * by BIF): | ||
692 | */ | ||
693 | |||
694 | if (fb->id == S9000_ID_A1659A) { /* ELK_DEVICE_ID */ | ||
695 | /* It's safe to use scanline zero: */ | ||
696 | packed_dst = (1280 << 16); | ||
697 | GET_FIFO_SLOTS(fb, nFreeFifoSlots, 2); | ||
698 | NGLE_SET_DSTXY(fb, packed_dst); | ||
699 | packed_len = (4 << 16) | 1; | ||
700 | SET_LENXY_START_RECFILL(fb, packed_len); | ||
701 | } /* ELK Hardware Kludge */ | ||
702 | |||
703 | /**** Finally, set the Control Plane Register back to zero: ****/ | ||
704 | GET_FIFO_SLOTS(fb, nFreeFifoSlots, 1); | ||
705 | NGLE_QUICK_SET_CTL_PLN_REG(fb, 0); | ||
706 | |||
707 | NGLE_UNLOCK(fb); | ||
708 | } | ||
709 | |||
710 | static void | ||
711 | ngleClearOverlayPlanes(struct stifb_info *fb, int mask, int data) | ||
712 | { | ||
713 | int nFreeFifoSlots = 0; | ||
714 | u32 packed_dst; | ||
715 | u32 packed_len; | ||
716 | |||
717 | NGLE_LOCK(fb); | ||
718 | |||
719 | /* Hardware setup */ | ||
720 | GET_FIFO_SLOTS(fb, nFreeFifoSlots, 8); | ||
721 | NGLE_QUICK_SET_DST_BM_ACCESS(fb, | ||
722 | BA(IndexedDcd, Otc04, Ots08, AddrLong, | ||
723 | BAJustPoint(0), BINovly, BAIndexBase(0))); | ||
724 | |||
725 | NGLE_SET_TRANSFERDATA(fb, 0xffffffff); /* Write foreground color */ | ||
726 | |||
727 | NGLE_REALLY_SET_IMAGE_FG_COLOR(fb, data); | ||
728 | NGLE_REALLY_SET_IMAGE_PLANEMASK(fb, mask); | ||
729 | |||
730 | packed_dst = 0; | ||
731 | packed_len = (fb->info.var.xres << 16) | fb->info.var.yres; | ||
732 | NGLE_SET_DSTXY(fb, packed_dst); | ||
733 | |||
734 | /* Write zeroes to overlay planes */ | ||
735 | NGLE_QUICK_SET_IMAGE_BITMAP_OP(fb, | ||
736 | IBOvals(RopSrc, MaskAddrOffset(0), | ||
737 | BitmapExtent08, StaticReg(0), | ||
738 | DataDynamic, MaskOtc, BGx(0), FGx(0))); | ||
739 | |||
740 | SET_LENXY_START_RECFILL(fb, packed_len); | ||
741 | |||
742 | NGLE_UNLOCK(fb); | ||
743 | } | ||
744 | |||
745 | static void | ||
746 | hyperResetPlanes(struct stifb_info *fb, int enable) | ||
747 | { | ||
748 | unsigned int controlPlaneReg; | ||
749 | |||
750 | NGLE_LOCK(fb); | ||
751 | |||
752 | if (IS_24_DEVICE(fb)) | ||
753 | if (fb->info.var.bits_per_pixel == 32) | ||
754 | controlPlaneReg = 0x04000F00; | ||
755 | else | ||
756 | controlPlaneReg = 0x00000F00; /* 0x00000800 should be enought, but lets clear all 4 bits */ | ||
757 | else | ||
758 | controlPlaneReg = 0x00000F00; /* 0x00000100 should be enought, but lets clear all 4 bits */ | ||
759 | |||
760 | switch (enable) { | ||
761 | case ENABLE: | ||
762 | /* clear screen */ | ||
763 | if (IS_24_DEVICE(fb)) | ||
764 | ngleDepth24_ClearImagePlanes(fb); | ||
765 | else | ||
766 | ngleDepth8_ClearImagePlanes(fb); | ||
767 | |||
768 | /* Paint attribute planes for default case. | ||
769 | * On Hyperdrive, this means all windows using overlay cmap 0. */ | ||
770 | ngleResetAttrPlanes(fb, controlPlaneReg); | ||
771 | |||
772 | /* clear overlay planes */ | ||
773 | ngleClearOverlayPlanes(fb, 0xff, 255); | ||
774 | |||
775 | /************************************************** | ||
776 | ** Also need to counteract ITE settings | ||
777 | **************************************************/ | ||
778 | hyperUndoITE(fb); | ||
779 | break; | ||
780 | |||
781 | case DISABLE: | ||
782 | /* clear screen */ | ||
783 | if (IS_24_DEVICE(fb)) | ||
784 | ngleDepth24_ClearImagePlanes(fb); | ||
785 | else | ||
786 | ngleDepth8_ClearImagePlanes(fb); | ||
787 | ngleResetAttrPlanes(fb, controlPlaneReg); | ||
788 | ngleClearOverlayPlanes(fb, 0xff, 0); | ||
789 | break; | ||
790 | |||
791 | case -1: /* RESET */ | ||
792 | hyperUndoITE(fb); | ||
793 | ngleResetAttrPlanes(fb, controlPlaneReg); | ||
794 | break; | ||
795 | } | ||
796 | |||
797 | NGLE_UNLOCK(fb); | ||
798 | } | ||
799 | |||
800 | /* Return pointer to in-memory structure holding ELK device-dependent ROM values. */ | ||
801 | |||
802 | static void | ||
803 | ngleGetDeviceRomData(struct stifb_info *fb) | ||
804 | { | ||
805 | #if 0 | ||
806 | XXX: FIXME: !!! | ||
807 | int *pBytePerLongDevDepData;/* data byte == LSB */ | ||
808 | int *pRomTable; | ||
809 | NgleDevRomData *pPackedDevRomData; | ||
810 | int sizePackedDevRomData = sizeof(*pPackedDevRomData); | ||
811 | char *pCard8; | ||
812 | int i; | ||
813 | char *mapOrigin = NULL; | ||
814 | |||
815 | int romTableIdx; | ||
816 | |||
817 | pPackedDevRomData = fb->ngle_rom; | ||
818 | |||
819 | SETUP_HW(fb); | ||
820 | if (fb->id == S9000_ID_ARTIST) { | ||
821 | pPackedDevRomData->cursor_pipeline_delay = 4; | ||
822 | pPackedDevRomData->video_interleaves = 4; | ||
823 | } else { | ||
824 | /* Get pointer to unpacked byte/long data in ROM */ | ||
825 | pBytePerLongDevDepData = fb->sti->regions[NGLEDEVDEPROM_CRT_REGION]; | ||
826 | |||
827 | /* Tomcat supports several resolutions: 1280x1024, 1024x768, 640x480 */ | ||
828 | if (fb->id == S9000_ID_TOMCAT) | ||
829 | { | ||
830 | /* jump to the correct ROM table */ | ||
831 | GET_ROMTABLE_INDEX(romTableIdx); | ||
832 | while (romTableIdx > 0) | ||
833 | { | ||
834 | pCard8 = (Card8 *) pPackedDevRomData; | ||
835 | pRomTable = pBytePerLongDevDepData; | ||
836 | /* Pack every fourth byte from ROM into structure */ | ||
837 | for (i = 0; i < sizePackedDevRomData; i++) | ||
838 | { | ||
839 | *pCard8++ = (Card8) (*pRomTable++); | ||
840 | } | ||
841 | |||
842 | pBytePerLongDevDepData = (Card32 *) | ||
843 | ((Card8 *) pBytePerLongDevDepData + | ||
844 | pPackedDevRomData->sizeof_ngle_data); | ||
845 | |||
846 | romTableIdx--; | ||
847 | } | ||
848 | } | ||
849 | |||
850 | pCard8 = (Card8 *) pPackedDevRomData; | ||
851 | |||
852 | /* Pack every fourth byte from ROM into structure */ | ||
853 | for (i = 0; i < sizePackedDevRomData; i++) | ||
854 | { | ||
855 | *pCard8++ = (Card8) (*pBytePerLongDevDepData++); | ||
856 | } | ||
857 | } | ||
858 | |||
859 | SETUP_FB(fb); | ||
860 | #endif | ||
861 | } | ||
862 | |||
863 | |||
864 | #define HYPERBOWL_MODE_FOR_8_OVER_88_LUT0_NO_TRANSPARENCIES 4 | ||
865 | #define HYPERBOWL_MODE01_8_24_LUT0_TRANSPARENT_LUT1_OPAQUE 8 | ||
866 | #define HYPERBOWL_MODE01_8_24_LUT0_OPAQUE_LUT1_OPAQUE 10 | ||
867 | #define HYPERBOWL_MODE2_8_24 15 | ||
868 | |||
869 | /* HCRX specific boot-time initialization */ | ||
870 | static void __init | ||
871 | SETUP_HCRX(struct stifb_info *fb) | ||
872 | { | ||
873 | int hyperbowl; | ||
874 | int nFreeFifoSlots = 0; | ||
875 | |||
876 | if (fb->id != S9000_ID_HCRX) | ||
877 | return; | ||
878 | |||
879 | /* Initialize Hyperbowl registers */ | ||
880 | GET_FIFO_SLOTS(fb, nFreeFifoSlots, 7); | ||
881 | |||
882 | if (IS_24_DEVICE(fb)) { | ||
883 | hyperbowl = (fb->info.var.bits_per_pixel == 32) ? | ||
884 | HYPERBOWL_MODE01_8_24_LUT0_TRANSPARENT_LUT1_OPAQUE : | ||
885 | HYPERBOWL_MODE01_8_24_LUT0_OPAQUE_LUT1_OPAQUE; | ||
886 | |||
887 | /* First write to Hyperbowl must happen twice (bug) */ | ||
888 | WRITE_WORD(hyperbowl, fb, REG_40); | ||
889 | WRITE_WORD(hyperbowl, fb, REG_40); | ||
890 | |||
891 | WRITE_WORD(HYPERBOWL_MODE2_8_24, fb, REG_39); | ||
892 | |||
893 | WRITE_WORD(0x014c0148, fb, REG_42); /* Set lut 0 to be the direct color */ | ||
894 | WRITE_WORD(0x404c4048, fb, REG_43); | ||
895 | WRITE_WORD(0x034c0348, fb, REG_44); | ||
896 | WRITE_WORD(0x444c4448, fb, REG_45); | ||
897 | } else { | ||
898 | hyperbowl = HYPERBOWL_MODE_FOR_8_OVER_88_LUT0_NO_TRANSPARENCIES; | ||
899 | |||
900 | /* First write to Hyperbowl must happen twice (bug) */ | ||
901 | WRITE_WORD(hyperbowl, fb, REG_40); | ||
902 | WRITE_WORD(hyperbowl, fb, REG_40); | ||
903 | |||
904 | WRITE_WORD(0x00000000, fb, REG_42); | ||
905 | WRITE_WORD(0x00000000, fb, REG_43); | ||
906 | WRITE_WORD(0x00000000, fb, REG_44); | ||
907 | WRITE_WORD(0x444c4048, fb, REG_45); | ||
908 | } | ||
909 | } | ||
910 | |||
911 | |||
912 | /* ------------------- driver specific functions --------------------------- */ | ||
913 | |||
914 | #define TMPBUFLEN 2048 | ||
915 | |||
916 | static ssize_t | ||
917 | stifb_read(struct file *file, char *buf, size_t count, loff_t *ppos) | ||
918 | { | ||
919 | unsigned long p = *ppos; | ||
920 | struct inode *inode = file->f_dentry->d_inode; | ||
921 | int fbidx = iminor(inode); | ||
922 | struct fb_info *info = registered_fb[fbidx]; | ||
923 | char tmpbuf[TMPBUFLEN]; | ||
924 | |||
925 | if (!info || ! info->screen_base) | ||
926 | return -ENODEV; | ||
927 | |||
928 | if (p >= info->fix.smem_len) | ||
929 | return 0; | ||
930 | if (count >= info->fix.smem_len) | ||
931 | count = info->fix.smem_len; | ||
932 | if (count + p > info->fix.smem_len) | ||
933 | count = info->fix.smem_len - p; | ||
934 | if (count > sizeof(tmpbuf)) | ||
935 | count = sizeof(tmpbuf); | ||
936 | if (count) { | ||
937 | char *base_addr; | ||
938 | |||
939 | base_addr = info->screen_base; | ||
940 | memcpy_fromio(&tmpbuf, base_addr+p, count); | ||
941 | count -= copy_to_user(buf, &tmpbuf, count); | ||
942 | if (!count) | ||
943 | return -EFAULT; | ||
944 | *ppos += count; | ||
945 | } | ||
946 | return count; | ||
947 | } | ||
948 | |||
949 | static ssize_t | ||
950 | stifb_write(struct file *file, const char *buf, size_t count, loff_t *ppos) | ||
951 | { | ||
952 | struct inode *inode = file->f_dentry->d_inode; | ||
953 | int fbidx = iminor(inode); | ||
954 | struct fb_info *info = registered_fb[fbidx]; | ||
955 | unsigned long p = *ppos; | ||
956 | size_t c; | ||
957 | int err; | ||
958 | char tmpbuf[TMPBUFLEN]; | ||
959 | |||
960 | if (!info || !info->screen_base) | ||
961 | return -ENODEV; | ||
962 | |||
963 | if (p > info->fix.smem_len) | ||
964 | return -ENOSPC; | ||
965 | if (count >= info->fix.smem_len) | ||
966 | count = info->fix.smem_len; | ||
967 | err = 0; | ||
968 | if (count + p > info->fix.smem_len) { | ||
969 | count = info->fix.smem_len - p; | ||
970 | err = -ENOSPC; | ||
971 | } | ||
972 | |||
973 | p += (unsigned long)info->screen_base; | ||
974 | c = count; | ||
975 | while (c) { | ||
976 | int len = c > sizeof(tmpbuf) ? sizeof(tmpbuf) : c; | ||
977 | err = -EFAULT; | ||
978 | if (copy_from_user(&tmpbuf, buf, len)) | ||
979 | break; | ||
980 | memcpy_toio(p, &tmpbuf, len); | ||
981 | c -= len; | ||
982 | p += len; | ||
983 | buf += len; | ||
984 | *ppos += len; | ||
985 | } | ||
986 | if (count-c) | ||
987 | return (count-c); | ||
988 | return err; | ||
989 | } | ||
990 | |||
991 | static int | ||
992 | stifb_setcolreg(u_int regno, u_int red, u_int green, | ||
993 | u_int blue, u_int transp, struct fb_info *info) | ||
994 | { | ||
995 | struct stifb_info *fb = (struct stifb_info *) info; | ||
996 | u32 color; | ||
997 | |||
998 | if (regno >= 256) /* no. of hw registers */ | ||
999 | return 1; | ||
1000 | |||
1001 | red >>= 8; | ||
1002 | green >>= 8; | ||
1003 | blue >>= 8; | ||
1004 | |||
1005 | DEBUG_OFF(); | ||
1006 | |||
1007 | START_IMAGE_COLORMAP_ACCESS(fb); | ||
1008 | |||
1009 | if (fb->info.var.grayscale) { | ||
1010 | /* gray = 0.30*R + 0.59*G + 0.11*B */ | ||
1011 | color = ((red * 77) + | ||
1012 | (green * 151) + | ||
1013 | (blue * 28)) >> 8; | ||
1014 | } else { | ||
1015 | color = ((red << 16) | | ||
1016 | (green << 8) | | ||
1017 | (blue)); | ||
1018 | } | ||
1019 | |||
1020 | if (info->var.bits_per_pixel == 32) { | ||
1021 | ((u32 *)(info->pseudo_palette))[regno] = | ||
1022 | (red << info->var.red.offset) | | ||
1023 | (green << info->var.green.offset) | | ||
1024 | (blue << info->var.blue.offset); | ||
1025 | } else { | ||
1026 | ((u32 *)(info->pseudo_palette))[regno] = regno; | ||
1027 | } | ||
1028 | |||
1029 | WRITE_IMAGE_COLOR(fb, regno, color); | ||
1030 | |||
1031 | if (fb->id == S9000_ID_HCRX) { | ||
1032 | NgleLutBltCtl lutBltCtl; | ||
1033 | |||
1034 | lutBltCtl = setHyperLutBltCtl(fb, | ||
1035 | 0, /* Offset w/i LUT */ | ||
1036 | 256); /* Load entire LUT */ | ||
1037 | NGLE_BINC_SET_SRCADDR(fb, | ||
1038 | NGLE_LONG_FB_ADDRESS(0, 0x100, 0)); | ||
1039 | /* 0x100 is same as used in WRITE_IMAGE_COLOR() */ | ||
1040 | START_COLORMAPLOAD(fb, lutBltCtl.all); | ||
1041 | SETUP_FB(fb); | ||
1042 | } else { | ||
1043 | /* cleanup colormap hardware */ | ||
1044 | FINISH_IMAGE_COLORMAP_ACCESS(fb); | ||
1045 | } | ||
1046 | |||
1047 | DEBUG_ON(); | ||
1048 | |||
1049 | return 0; | ||
1050 | } | ||
1051 | |||
1052 | static int | ||
1053 | stifb_blank(int blank_mode, struct fb_info *info) | ||
1054 | { | ||
1055 | struct stifb_info *fb = (struct stifb_info *) info; | ||
1056 | int enable = (blank_mode == 0) ? ENABLE : DISABLE; | ||
1057 | |||
1058 | switch (fb->id) { | ||
1059 | case S9000_ID_A1439A: | ||
1060 | CRX24_ENABLE_DISABLE_DISPLAY(fb, enable); | ||
1061 | break; | ||
1062 | case CRT_ID_VISUALIZE_EG: | ||
1063 | case S9000_ID_ARTIST: | ||
1064 | ARTIST_ENABLE_DISABLE_DISPLAY(fb, enable); | ||
1065 | break; | ||
1066 | case S9000_ID_HCRX: | ||
1067 | HYPER_ENABLE_DISABLE_DISPLAY(fb, enable); | ||
1068 | break; | ||
1069 | case S9000_ID_A1659A:; /* fall through */ | ||
1070 | case S9000_ID_TIMBER:; | ||
1071 | case CRX24_OVERLAY_PLANES:; | ||
1072 | default: | ||
1073 | ENABLE_DISABLE_DISPLAY(fb, enable); | ||
1074 | break; | ||
1075 | } | ||
1076 | |||
1077 | SETUP_FB(fb); | ||
1078 | return 0; | ||
1079 | } | ||
1080 | |||
1081 | static void __init | ||
1082 | stifb_init_display(struct stifb_info *fb) | ||
1083 | { | ||
1084 | int id = fb->id; | ||
1085 | |||
1086 | SETUP_FB(fb); | ||
1087 | |||
1088 | /* HCRX specific initialization */ | ||
1089 | SETUP_HCRX(fb); | ||
1090 | |||
1091 | /* | ||
1092 | if (id == S9000_ID_HCRX) | ||
1093 | hyperInitSprite(fb); | ||
1094 | else | ||
1095 | ngleInitSprite(fb); | ||
1096 | */ | ||
1097 | |||
1098 | /* Initialize the image planes. */ | ||
1099 | switch (id) { | ||
1100 | case S9000_ID_HCRX: | ||
1101 | hyperResetPlanes(fb, ENABLE); | ||
1102 | break; | ||
1103 | case S9000_ID_A1439A: | ||
1104 | rattlerSetupPlanes(fb); | ||
1105 | break; | ||
1106 | case S9000_ID_A1659A: | ||
1107 | case S9000_ID_ARTIST: | ||
1108 | case CRT_ID_VISUALIZE_EG: | ||
1109 | elkSetupPlanes(fb); | ||
1110 | break; | ||
1111 | } | ||
1112 | |||
1113 | /* Clear attribute planes on non HCRX devices. */ | ||
1114 | switch (id) { | ||
1115 | case S9000_ID_A1659A: | ||
1116 | case S9000_ID_A1439A: | ||
1117 | if (fb->info.var.bits_per_pixel == 32) | ||
1118 | ngleSetupAttrPlanes(fb, BUFF1_CMAP3); | ||
1119 | else { | ||
1120 | ngleSetupAttrPlanes(fb, BUFF1_CMAP0); | ||
1121 | } | ||
1122 | if (id == S9000_ID_A1439A) | ||
1123 | ngleClearOverlayPlanes(fb, 0xff, 0); | ||
1124 | break; | ||
1125 | case S9000_ID_ARTIST: | ||
1126 | case CRT_ID_VISUALIZE_EG: | ||
1127 | if (fb->info.var.bits_per_pixel == 32) | ||
1128 | ngleSetupAttrPlanes(fb, BUFF1_CMAP3); | ||
1129 | else { | ||
1130 | ngleSetupAttrPlanes(fb, ARTIST_CMAP0); | ||
1131 | } | ||
1132 | break; | ||
1133 | } | ||
1134 | stifb_blank(0, (struct fb_info *)fb); /* 0=enable screen */ | ||
1135 | |||
1136 | SETUP_FB(fb); | ||
1137 | } | ||
1138 | |||
1139 | /* ------------ Interfaces to hardware functions ------------ */ | ||
1140 | |||
1141 | static struct fb_ops stifb_ops = { | ||
1142 | .owner = THIS_MODULE, | ||
1143 | .fb_read = stifb_read, | ||
1144 | .fb_write = stifb_write, | ||
1145 | .fb_setcolreg = stifb_setcolreg, | ||
1146 | .fb_blank = stifb_blank, | ||
1147 | .fb_fillrect = cfb_fillrect, | ||
1148 | .fb_copyarea = cfb_copyarea, | ||
1149 | .fb_imageblit = cfb_imageblit, | ||
1150 | .fb_cursor = soft_cursor, | ||
1151 | }; | ||
1152 | |||
1153 | |||
1154 | /* | ||
1155 | * Initialization | ||
1156 | */ | ||
1157 | |||
1158 | int __init | ||
1159 | stifb_init_fb(struct sti_struct *sti, int bpp_pref) | ||
1160 | { | ||
1161 | struct fb_fix_screeninfo *fix; | ||
1162 | struct fb_var_screeninfo *var; | ||
1163 | struct stifb_info *fb; | ||
1164 | struct fb_info *info; | ||
1165 | unsigned long sti_rom_address; | ||
1166 | char *dev_name; | ||
1167 | int bpp, xres, yres; | ||
1168 | |||
1169 | fb = kmalloc(sizeof(*fb), GFP_ATOMIC); | ||
1170 | if (!fb) { | ||
1171 | printk(KERN_ERR "stifb: Could not allocate stifb structure\n"); | ||
1172 | return -ENODEV; | ||
1173 | } | ||
1174 | |||
1175 | info = &fb->info; | ||
1176 | |||
1177 | /* set struct to a known state */ | ||
1178 | memset(fb, 0, sizeof(*fb)); | ||
1179 | fix = &info->fix; | ||
1180 | var = &info->var; | ||
1181 | |||
1182 | fb->sti = sti; | ||
1183 | /* store upper 32bits of the graphics id */ | ||
1184 | fb->id = fb->sti->graphics_id[0]; | ||
1185 | |||
1186 | /* only supported cards are allowed */ | ||
1187 | switch (fb->id) { | ||
1188 | case CRT_ID_VISUALIZE_EG: | ||
1189 | /* look for a double buffering device like e.g. the | ||
1190 | "INTERNAL_EG_DX1024" in the RDI precisionbook laptop | ||
1191 | which won't work. The same device in non-double | ||
1192 | buffering mode returns "INTERNAL_EG_X1024". */ | ||
1193 | if (strstr(sti->outptr.dev_name, "EG_DX")) { | ||
1194 | printk(KERN_WARNING | ||
1195 | "stifb: ignoring '%s'. Disable double buffering in IPL menu.\n", | ||
1196 | sti->outptr.dev_name); | ||
1197 | goto out_err0; | ||
1198 | } | ||
1199 | /* fall though */ | ||
1200 | case S9000_ID_ARTIST: | ||
1201 | case S9000_ID_HCRX: | ||
1202 | case S9000_ID_TIMBER: | ||
1203 | case S9000_ID_A1659A: | ||
1204 | case S9000_ID_A1439A: | ||
1205 | break; | ||
1206 | default: | ||
1207 | printk(KERN_WARNING "stifb: '%s' (id: 0x%08x) not supported.\n", | ||
1208 | sti->outptr.dev_name, fb->id); | ||
1209 | goto out_err0; | ||
1210 | } | ||
1211 | |||
1212 | /* default to 8 bpp on most graphic chips */ | ||
1213 | bpp = 8; | ||
1214 | xres = sti_onscreen_x(fb->sti); | ||
1215 | yres = sti_onscreen_y(fb->sti); | ||
1216 | |||
1217 | ngleGetDeviceRomData(fb); | ||
1218 | |||
1219 | /* get (virtual) io region base addr */ | ||
1220 | fix->mmio_start = REGION_BASE(fb,2); | ||
1221 | fix->mmio_len = 0x400000; | ||
1222 | |||
1223 | /* Reject any device not in the NGLE family */ | ||
1224 | switch (fb->id) { | ||
1225 | case S9000_ID_A1659A: /* CRX/A1659A */ | ||
1226 | break; | ||
1227 | case S9000_ID_ELM: /* GRX, grayscale but else same as A1659A */ | ||
1228 | var->grayscale = 1; | ||
1229 | fb->id = S9000_ID_A1659A; | ||
1230 | break; | ||
1231 | case S9000_ID_TIMBER: /* HP9000/710 Any (may be a grayscale device) */ | ||
1232 | dev_name = fb->sti->outptr.dev_name; | ||
1233 | if (strstr(dev_name, "GRAYSCALE") || | ||
1234 | strstr(dev_name, "Grayscale") || | ||
1235 | strstr(dev_name, "grayscale")) | ||
1236 | var->grayscale = 1; | ||
1237 | break; | ||
1238 | case S9000_ID_TOMCAT: /* Dual CRX, behaves else like a CRX */ | ||
1239 | /* FIXME: TomCat supports two heads: | ||
1240 | * fb.iobase = REGION_BASE(fb_info,3); | ||
1241 | * fb.screen_base = (void*) REGION_BASE(fb_info,2); | ||
1242 | * for now we only support the left one ! */ | ||
1243 | xres = fb->ngle_rom.x_size_visible; | ||
1244 | yres = fb->ngle_rom.y_size_visible; | ||
1245 | fb->id = S9000_ID_A1659A; | ||
1246 | break; | ||
1247 | case S9000_ID_A1439A: /* CRX24/A1439A */ | ||
1248 | bpp = 32; | ||
1249 | break; | ||
1250 | case S9000_ID_HCRX: /* Hyperdrive/HCRX */ | ||
1251 | memset(&fb->ngle_rom, 0, sizeof(fb->ngle_rom)); | ||
1252 | if ((fb->sti->regions_phys[0] & 0xfc000000) == | ||
1253 | (fb->sti->regions_phys[2] & 0xfc000000)) | ||
1254 | sti_rom_address = fb->sti->regions_phys[0]; | ||
1255 | else | ||
1256 | sti_rom_address = fb->sti->regions_phys[1]; | ||
1257 | #ifdef __LP64__ | ||
1258 | sti_rom_address |= 0xffffffff00000000; | ||
1259 | #endif | ||
1260 | fb->deviceSpecificConfig = gsc_readl(sti_rom_address); | ||
1261 | if (IS_24_DEVICE(fb)) { | ||
1262 | if (bpp_pref == 8 || bpp_pref == 32) | ||
1263 | bpp = bpp_pref; | ||
1264 | else | ||
1265 | bpp = 32; | ||
1266 | } else | ||
1267 | bpp = 8; | ||
1268 | READ_WORD(fb, REG_15); | ||
1269 | SETUP_HW(fb); | ||
1270 | break; | ||
1271 | case CRT_ID_VISUALIZE_EG: | ||
1272 | case S9000_ID_ARTIST: /* Artist */ | ||
1273 | break; | ||
1274 | default: | ||
1275 | #ifdef FALLBACK_TO_1BPP | ||
1276 | printk(KERN_WARNING | ||
1277 | "stifb: Unsupported graphics card (id=0x%08x) " | ||
1278 | "- now trying 1bpp mode instead\n", | ||
1279 | fb->id); | ||
1280 | bpp = 1; /* default to 1 bpp */ | ||
1281 | break; | ||
1282 | #else | ||
1283 | printk(KERN_WARNING | ||
1284 | "stifb: Unsupported graphics card (id=0x%08x) " | ||
1285 | "- skipping.\n", | ||
1286 | fb->id); | ||
1287 | goto out_err0; | ||
1288 | #endif | ||
1289 | } | ||
1290 | |||
1291 | |||
1292 | /* get framebuffer physical and virtual base addr & len (64bit ready) */ | ||
1293 | fix->smem_start = F_EXTEND(fb->sti->regions_phys[1]); | ||
1294 | fix->smem_len = fb->sti->regions[1].region_desc.length * 4096; | ||
1295 | |||
1296 | fix->line_length = (fb->sti->glob_cfg->total_x * bpp) / 8; | ||
1297 | if (!fix->line_length) | ||
1298 | fix->line_length = 2048; /* default */ | ||
1299 | |||
1300 | /* limit fbsize to max visible screen size */ | ||
1301 | if (fix->smem_len > yres*fix->line_length) | ||
1302 | fix->smem_len = yres*fix->line_length; | ||
1303 | |||
1304 | fix->accel = FB_ACCEL_NONE; | ||
1305 | |||
1306 | switch (bpp) { | ||
1307 | case 1: | ||
1308 | fix->type = FB_TYPE_PLANES; /* well, sort of */ | ||
1309 | fix->visual = FB_VISUAL_MONO10; | ||
1310 | var->red.length = var->green.length = var->blue.length = 1; | ||
1311 | break; | ||
1312 | case 8: | ||
1313 | fix->type = FB_TYPE_PACKED_PIXELS; | ||
1314 | fix->visual = FB_VISUAL_PSEUDOCOLOR; | ||
1315 | var->red.length = var->green.length = var->blue.length = 8; | ||
1316 | break; | ||
1317 | case 32: | ||
1318 | fix->type = FB_TYPE_PACKED_PIXELS; | ||
1319 | fix->visual = FB_VISUAL_TRUECOLOR; | ||
1320 | var->red.length = var->green.length = var->blue.length = var->transp.length = 8; | ||
1321 | var->blue.offset = 0; | ||
1322 | var->green.offset = 8; | ||
1323 | var->red.offset = 16; | ||
1324 | var->transp.offset = 24; | ||
1325 | break; | ||
1326 | default: | ||
1327 | break; | ||
1328 | } | ||
1329 | |||
1330 | var->xres = var->xres_virtual = xres; | ||
1331 | var->yres = var->yres_virtual = yres; | ||
1332 | var->bits_per_pixel = bpp; | ||
1333 | |||
1334 | strcpy(fix->id, "stifb"); | ||
1335 | info->fbops = &stifb_ops; | ||
1336 | info->screen_base = (void*) REGION_BASE(fb,1); | ||
1337 | info->flags = FBINFO_DEFAULT; | ||
1338 | info->pseudo_palette = &fb->pseudo_palette; | ||
1339 | |||
1340 | /* This has to been done !!! */ | ||
1341 | fb_alloc_cmap(&info->cmap, 256, 0); | ||
1342 | stifb_init_display(fb); | ||
1343 | |||
1344 | if (!request_mem_region(fix->smem_start, fix->smem_len, "stifb fb")) { | ||
1345 | printk(KERN_ERR "stifb: cannot reserve fb region 0x%04lx-0x%04lx\n", | ||
1346 | fix->smem_start, fix->smem_start+fix->smem_len); | ||
1347 | goto out_err1; | ||
1348 | } | ||
1349 | |||
1350 | if (!request_mem_region(fix->mmio_start, fix->mmio_len, "stifb mmio")) { | ||
1351 | printk(KERN_ERR "stifb: cannot reserve sti mmio region 0x%04lx-0x%04lx\n", | ||
1352 | fix->mmio_start, fix->mmio_start+fix->mmio_len); | ||
1353 | goto out_err2; | ||
1354 | } | ||
1355 | |||
1356 | if (register_framebuffer(&fb->info) < 0) | ||
1357 | goto out_err3; | ||
1358 | |||
1359 | sti->info = info; /* save for unregister_framebuffer() */ | ||
1360 | |||
1361 | printk(KERN_INFO | ||
1362 | "fb%d: %s %dx%d-%d frame buffer device, %s, id: %04x, mmio: 0x%04lx\n", | ||
1363 | fb->info.node, | ||
1364 | fix->id, | ||
1365 | var->xres, | ||
1366 | var->yres, | ||
1367 | var->bits_per_pixel, | ||
1368 | sti->outptr.dev_name, | ||
1369 | fb->id, | ||
1370 | fix->mmio_start); | ||
1371 | |||
1372 | return 0; | ||
1373 | |||
1374 | |||
1375 | out_err3: | ||
1376 | release_mem_region(fix->mmio_start, fix->mmio_len); | ||
1377 | out_err2: | ||
1378 | release_mem_region(fix->smem_start, fix->smem_len); | ||
1379 | out_err1: | ||
1380 | fb_dealloc_cmap(&info->cmap); | ||
1381 | out_err0: | ||
1382 | kfree(fb); | ||
1383 | return -ENXIO; | ||
1384 | } | ||
1385 | |||
1386 | static int stifb_disabled __initdata; | ||
1387 | |||
1388 | int __init | ||
1389 | stifb_setup(char *options); | ||
1390 | |||
1391 | int __init | ||
1392 | stifb_init(void) | ||
1393 | { | ||
1394 | struct sti_struct *sti; | ||
1395 | struct sti_struct *def_sti; | ||
1396 | int i; | ||
1397 | |||
1398 | #ifndef MODULE | ||
1399 | char *option = NULL; | ||
1400 | |||
1401 | if (fb_get_options("stifb", &option)) | ||
1402 | return -ENODEV; | ||
1403 | stifb_setup(option); | ||
1404 | #endif | ||
1405 | if (stifb_disabled) { | ||
1406 | printk(KERN_INFO "stifb: disabled by \"stifb=off\" kernel parameter\n"); | ||
1407 | return -ENXIO; | ||
1408 | } | ||
1409 | |||
1410 | def_sti = sti_get_rom(0); | ||
1411 | if (def_sti) { | ||
1412 | for (i = 1; i <= MAX_STI_ROMS; i++) { | ||
1413 | sti = sti_get_rom(i); | ||
1414 | if (!sti) | ||
1415 | break; | ||
1416 | if (sti == def_sti) { | ||
1417 | stifb_init_fb(sti, stifb_bpp_pref[i - 1]); | ||
1418 | break; | ||
1419 | } | ||
1420 | } | ||
1421 | } | ||
1422 | |||
1423 | for (i = 1; i <= MAX_STI_ROMS; i++) { | ||
1424 | sti = sti_get_rom(i); | ||
1425 | if (!sti) | ||
1426 | break; | ||
1427 | if (sti == def_sti) | ||
1428 | continue; | ||
1429 | stifb_init_fb(sti, stifb_bpp_pref[i - 1]); | ||
1430 | } | ||
1431 | return 0; | ||
1432 | } | ||
1433 | |||
1434 | /* | ||
1435 | * Cleanup | ||
1436 | */ | ||
1437 | |||
1438 | static void __exit | ||
1439 | stifb_cleanup(void) | ||
1440 | { | ||
1441 | struct sti_struct *sti; | ||
1442 | int i; | ||
1443 | |||
1444 | for (i = 1; i <= MAX_STI_ROMS; i++) { | ||
1445 | sti = sti_get_rom(i); | ||
1446 | if (!sti) | ||
1447 | break; | ||
1448 | if (sti->info) { | ||
1449 | struct fb_info *info = sti->info; | ||
1450 | unregister_framebuffer(sti->info); | ||
1451 | release_mem_region(info->fix.mmio_start, info->fix.mmio_len); | ||
1452 | release_mem_region(info->fix.smem_start, info->fix.smem_len); | ||
1453 | fb_dealloc_cmap(&info->cmap); | ||
1454 | kfree(info); | ||
1455 | } | ||
1456 | sti->info = NULL; | ||
1457 | } | ||
1458 | } | ||
1459 | |||
1460 | int __init | ||
1461 | stifb_setup(char *options) | ||
1462 | { | ||
1463 | int i; | ||
1464 | |||
1465 | if (!options || !*options) | ||
1466 | return 0; | ||
1467 | |||
1468 | if (strncmp(options, "off", 3) == 0) { | ||
1469 | stifb_disabled = 1; | ||
1470 | options += 3; | ||
1471 | } | ||
1472 | |||
1473 | if (strncmp(options, "bpp", 3) == 0) { | ||
1474 | options += 3; | ||
1475 | for (i = 0; i < MAX_STI_ROMS; i++) { | ||
1476 | if (*options++ != ':') | ||
1477 | break; | ||
1478 | stifb_bpp_pref[i] = simple_strtoul(options, &options, 10); | ||
1479 | } | ||
1480 | } | ||
1481 | return 0; | ||
1482 | } | ||
1483 | |||
1484 | __setup("stifb=", stifb_setup); | ||
1485 | |||
1486 | module_init(stifb_init); | ||
1487 | module_exit(stifb_cleanup); | ||
1488 | |||
1489 | MODULE_AUTHOR("Helge Deller <deller@gmx.de>, Thomas Bogendoerfer <tsbogend@alpha.franken.de>"); | ||
1490 | MODULE_DESCRIPTION("Framebuffer driver for HP's NGLE series graphics cards in HP PARISC machines"); | ||
1491 | MODULE_LICENSE("GPL v2"); | ||
1492 | |||
1493 | MODULE_PARM(bpp, "i"); | ||
1494 | MODULE_PARM_DESC(mem, "Bits per pixel (default: 8)"); | ||
1495 | |||