1 | /* |
2 | * linux/drivers/video/tgafb.c -- DEC 21030 TGA frame buffer device |
3 | * |
4 | * Copyright (C) 1995 Jay Estabrook |
5 | * Copyright (C) 1997 Geert Uytterhoeven |
6 | * Copyright (C) 1999,2000 Martin Lucina, Tom Zerucha |
7 | * Copyright (C) 2002 Richard Henderson |
8 | * Copyright (C) 2006, 2007 Maciej W. Rozycki |
9 | * |
10 | * This file is subject to the terms and conditions of the GNU General Public |
11 | * License. See the file COPYING in the main directory of this archive for |
12 | * more details. |
13 | */ |
14 | |
15 | #include <linux/aperture.h> |
16 | #include <linux/bitrev.h> |
17 | #include <linux/compiler.h> |
18 | #include <linux/delay.h> |
19 | #include <linux/device.h> |
20 | #include <linux/errno.h> |
21 | #include <linux/fb.h> |
22 | #include <linux/init.h> |
23 | #include <linux/ioport.h> |
24 | #include <linux/kernel.h> |
25 | #include <linux/mm.h> |
26 | #include <linux/module.h> |
27 | #include <linux/pci.h> |
28 | #include <linux/selection.h> |
29 | #include <linux/string.h> |
30 | #include <linux/tc.h> |
31 | |
32 | #include <asm/io.h> |
33 | |
34 | #include <video/tgafb.h> |
35 | |
36 | #ifdef CONFIG_TC |
37 | #define TGA_BUS_TC(dev) (dev->bus == &tc_bus_type) |
38 | #else |
39 | #define TGA_BUS_TC(dev) 0 |
40 | #endif |
41 | |
42 | /* |
43 | * Local functions. |
44 | */ |
45 | |
46 | static int tgafb_check_var(struct fb_var_screeninfo *, struct fb_info *); |
47 | static int tgafb_set_par(struct fb_info *); |
48 | static void tgafb_set_pll(struct tga_par *, int); |
49 | static int tgafb_setcolreg(unsigned, unsigned, unsigned, unsigned, |
50 | unsigned, struct fb_info *); |
51 | static int tgafb_blank(int, struct fb_info *); |
52 | static void tgafb_init_fix(struct fb_info *); |
53 | |
54 | static void tgafb_imageblit(struct fb_info *, const struct fb_image *); |
55 | static void tgafb_fillrect(struct fb_info *, const struct fb_fillrect *); |
56 | static void tgafb_copyarea(struct fb_info *, const struct fb_copyarea *); |
57 | static int tgafb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info); |
58 | |
59 | static int tgafb_register(struct device *dev); |
60 | static void tgafb_unregister(struct device *dev); |
61 | |
62 | static const char *mode_option; |
63 | static const char *mode_option_pci = "640x480@60" ; |
64 | static const char *mode_option_tc = "1280x1024@72" ; |
65 | |
66 | |
67 | static struct pci_driver tgafb_pci_driver; |
68 | static struct tc_driver tgafb_tc_driver; |
69 | |
70 | /* |
71 | * Frame buffer operations |
72 | */ |
73 | |
74 | static const struct fb_ops tgafb_ops = { |
75 | .owner = THIS_MODULE, |
76 | __FB_DEFAULT_IOMEM_OPS_RDWR, |
77 | .fb_check_var = tgafb_check_var, |
78 | .fb_set_par = tgafb_set_par, |
79 | .fb_setcolreg = tgafb_setcolreg, |
80 | .fb_blank = tgafb_blank, |
81 | .fb_pan_display = tgafb_pan_display, |
82 | .fb_fillrect = tgafb_fillrect, |
83 | .fb_copyarea = tgafb_copyarea, |
84 | .fb_imageblit = tgafb_imageblit, |
85 | __FB_DEFAULT_IOMEM_OPS_MMAP, |
86 | }; |
87 | |
88 | |
89 | #ifdef CONFIG_PCI |
90 | /* |
91 | * PCI registration operations |
92 | */ |
93 | static int tgafb_pci_register(struct pci_dev *, const struct pci_device_id *); |
94 | static void tgafb_pci_unregister(struct pci_dev *); |
95 | |
96 | static struct pci_device_id const tgafb_pci_table[] = { |
97 | { PCI_DEVICE(PCI_VENDOR_ID_DEC, PCI_DEVICE_ID_DEC_TGA) }, |
98 | { } |
99 | }; |
100 | MODULE_DEVICE_TABLE(pci, tgafb_pci_table); |
101 | |
102 | static struct pci_driver tgafb_pci_driver = { |
103 | .name = "tgafb" , |
104 | .id_table = tgafb_pci_table, |
105 | .probe = tgafb_pci_register, |
106 | .remove = tgafb_pci_unregister, |
107 | }; |
108 | |
109 | static int tgafb_pci_register(struct pci_dev *pdev, |
110 | const struct pci_device_id *ent) |
111 | { |
112 | int ret; |
113 | |
114 | ret = aperture_remove_conflicting_pci_devices(pdev, name: "tgafb" ); |
115 | if (ret) |
116 | return ret; |
117 | |
118 | return tgafb_register(dev: &pdev->dev); |
119 | } |
120 | |
121 | static void tgafb_pci_unregister(struct pci_dev *pdev) |
122 | { |
123 | tgafb_unregister(dev: &pdev->dev); |
124 | } |
125 | #endif /* CONFIG_PCI */ |
126 | |
127 | #ifdef CONFIG_TC |
128 | /* |
129 | * TC registration operations |
130 | */ |
131 | static int tgafb_tc_register(struct device *); |
132 | static int tgafb_tc_unregister(struct device *); |
133 | |
134 | static struct tc_device_id const tgafb_tc_table[] = { |
135 | { "DEC " , "PMAGD-AA" }, |
136 | { "DEC " , "PMAGD " }, |
137 | { } |
138 | }; |
139 | MODULE_DEVICE_TABLE(tc, tgafb_tc_table); |
140 | |
141 | static struct tc_driver tgafb_tc_driver = { |
142 | .id_table = tgafb_tc_table, |
143 | .driver = { |
144 | .name = "tgafb" , |
145 | .bus = &tc_bus_type, |
146 | .probe = tgafb_tc_register, |
147 | .remove = tgafb_tc_unregister, |
148 | }, |
149 | }; |
150 | |
151 | static int tgafb_tc_register(struct device *dev) |
152 | { |
153 | int status = tgafb_register(dev); |
154 | if (!status) |
155 | get_device(dev); |
156 | return status; |
157 | } |
158 | |
159 | static int tgafb_tc_unregister(struct device *dev) |
160 | { |
161 | put_device(dev); |
162 | tgafb_unregister(dev); |
163 | return 0; |
164 | } |
165 | #endif /* CONFIG_TC */ |
166 | |
167 | |
168 | /** |
169 | * tgafb_check_var - Optional function. Validates a var passed in. |
170 | * @var: frame buffer variable screen structure |
171 | * @info: frame buffer structure that represents a single frame buffer |
172 | */ |
173 | static int |
174 | tgafb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) |
175 | { |
176 | struct tga_par *par = (struct tga_par *)info->par; |
177 | |
178 | if (!var->pixclock) |
179 | return -EINVAL; |
180 | |
181 | if (par->tga_type == TGA_TYPE_8PLANE) { |
182 | if (var->bits_per_pixel != 8) |
183 | return -EINVAL; |
184 | } else { |
185 | if (var->bits_per_pixel != 32) |
186 | return -EINVAL; |
187 | } |
188 | var->red.length = var->green.length = var->blue.length = 8; |
189 | if (var->bits_per_pixel == 32) { |
190 | var->red.offset = 16; |
191 | var->green.offset = 8; |
192 | var->blue.offset = 0; |
193 | } |
194 | |
195 | if (var->xres_virtual != var->xres || var->yres_virtual != var->yres) |
196 | return -EINVAL; |
197 | if (var->xres * var->yres * (var->bits_per_pixel >> 3) > info->fix.smem_len) |
198 | return -EINVAL; |
199 | if (var->nonstd) |
200 | return -EINVAL; |
201 | if (1000000000 / var->pixclock > TGA_PLL_MAX_FREQ) |
202 | return -EINVAL; |
203 | if ((var->vmode & FB_VMODE_MASK) != FB_VMODE_NONINTERLACED) |
204 | return -EINVAL; |
205 | |
206 | /* Some of the acceleration routines assume the line width is |
207 | a multiple of 8 bytes. */ |
208 | if (var->xres * (par->tga_type == TGA_TYPE_8PLANE ? 1 : 4) % 8) |
209 | return -EINVAL; |
210 | |
211 | return 0; |
212 | } |
213 | |
214 | /** |
215 | * tgafb_set_par - Optional function. Alters the hardware state. |
216 | * @info: frame buffer structure that represents a single frame buffer |
217 | */ |
218 | static int |
219 | tgafb_set_par(struct fb_info *info) |
220 | { |
221 | static unsigned int const deep_presets[4] = { |
222 | 0x00004000, |
223 | 0x0000440d, |
224 | 0xffffffff, |
225 | 0x0000441d |
226 | }; |
227 | static unsigned int const rasterop_presets[4] = { |
228 | 0x00000003, |
229 | 0x00000303, |
230 | 0xffffffff, |
231 | 0x00000303 |
232 | }; |
233 | static unsigned int const mode_presets[4] = { |
234 | 0x00000000, |
235 | 0x00000300, |
236 | 0xffffffff, |
237 | 0x00000300 |
238 | }; |
239 | static unsigned int const base_addr_presets[4] = { |
240 | 0x00000000, |
241 | 0x00000001, |
242 | 0xffffffff, |
243 | 0x00000001 |
244 | }; |
245 | |
246 | struct tga_par *par = (struct tga_par *) info->par; |
247 | int tga_bus_pci = dev_is_pci(par->dev); |
248 | int tga_bus_tc = TGA_BUS_TC(par->dev); |
249 | u32 htimings, vtimings, pll_freq; |
250 | u8 tga_type; |
251 | int i; |
252 | |
253 | /* Encode video timings. */ |
254 | htimings = (((info->var.xres/4) & TGA_HORIZ_ACT_LSB) |
255 | | (((info->var.xres/4) & 0x600 << 19) & TGA_HORIZ_ACT_MSB)); |
256 | vtimings = (info->var.yres & TGA_VERT_ACTIVE); |
257 | htimings |= ((info->var.right_margin/4) << 9) & TGA_HORIZ_FP; |
258 | vtimings |= (info->var.lower_margin << 11) & TGA_VERT_FP; |
259 | htimings |= ((info->var.hsync_len/4) << 14) & TGA_HORIZ_SYNC; |
260 | vtimings |= (info->var.vsync_len << 16) & TGA_VERT_SYNC; |
261 | htimings |= ((info->var.left_margin/4) << 21) & TGA_HORIZ_BP; |
262 | vtimings |= (info->var.upper_margin << 22) & TGA_VERT_BP; |
263 | |
264 | if (info->var.sync & FB_SYNC_HOR_HIGH_ACT) |
265 | htimings |= TGA_HORIZ_POLARITY; |
266 | if (info->var.sync & FB_SYNC_VERT_HIGH_ACT) |
267 | vtimings |= TGA_VERT_POLARITY; |
268 | |
269 | par->htimings = htimings; |
270 | par->vtimings = vtimings; |
271 | |
272 | par->sync_on_green = !!(info->var.sync & FB_SYNC_ON_GREEN); |
273 | |
274 | /* Store other useful values in par. */ |
275 | par->xres = info->var.xres; |
276 | par->yres = info->var.yres; |
277 | par->pll_freq = pll_freq = 1000000000 / info->var.pixclock; |
278 | par->bits_per_pixel = info->var.bits_per_pixel; |
279 | info->fix.line_length = par->xres * (par->bits_per_pixel >> 3); |
280 | |
281 | tga_type = par->tga_type; |
282 | |
283 | /* First, disable video. */ |
284 | TGA_WRITE_REG(par, TGA_VALID_VIDEO | TGA_VALID_BLANK, TGA_VALID_REG); |
285 | |
286 | /* Write the DEEP register. */ |
287 | while (TGA_READ_REG(par, TGA_CMD_STAT_REG) & 1) /* wait for not busy */ |
288 | continue; |
289 | mb(); |
290 | TGA_WRITE_REG(par, v: deep_presets[tga_type] | |
291 | (par->sync_on_green ? 0x0 : 0x00010000), |
292 | TGA_DEEP_REG); |
293 | while (TGA_READ_REG(par, TGA_CMD_STAT_REG) & 1) /* wait for not busy */ |
294 | continue; |
295 | mb(); |
296 | |
297 | /* Write some more registers. */ |
298 | TGA_WRITE_REG(par, v: rasterop_presets[tga_type], TGA_RASTEROP_REG); |
299 | TGA_WRITE_REG(par, v: mode_presets[tga_type], TGA_MODE_REG); |
300 | TGA_WRITE_REG(par, v: base_addr_presets[tga_type], TGA_BASE_ADDR_REG); |
301 | |
302 | /* Calculate & write the PLL. */ |
303 | tgafb_set_pll(par, pll_freq); |
304 | |
305 | /* Write some more registers. */ |
306 | TGA_WRITE_REG(par, v: 0xffffffff, TGA_PLANEMASK_REG); |
307 | TGA_WRITE_REG(par, v: 0xffffffff, TGA_PIXELMASK_REG); |
308 | |
309 | /* Init video timing regs. */ |
310 | TGA_WRITE_REG(par, v: htimings, TGA_HORIZ_REG); |
311 | TGA_WRITE_REG(par, v: vtimings, TGA_VERT_REG); |
312 | |
313 | /* Initialise RAMDAC. */ |
314 | if (tga_type == TGA_TYPE_8PLANE && tga_bus_pci) { |
315 | |
316 | /* Init BT485 RAMDAC registers. */ |
317 | BT485_WRITE(par, v: 0xa2 | (par->sync_on_green ? 0x8 : 0x0), |
318 | BT485_CMD_0); |
319 | BT485_WRITE(par, v: 0x01, BT485_ADDR_PAL_WRITE); |
320 | BT485_WRITE(par, v: 0x14, BT485_CMD_3); /* cursor 64x64 */ |
321 | BT485_WRITE(par, v: 0x40, BT485_CMD_1); |
322 | BT485_WRITE(par, v: 0x20, BT485_CMD_2); /* cursor off, for now */ |
323 | BT485_WRITE(par, v: 0xff, BT485_PIXEL_MASK); |
324 | |
325 | /* Fill palette registers. */ |
326 | BT485_WRITE(par, v: 0x00, BT485_ADDR_PAL_WRITE); |
327 | TGA_WRITE_REG(par, BT485_DATA_PAL, TGA_RAMDAC_SETUP_REG); |
328 | |
329 | for (i = 0; i < 256 * 3; i += 4) { |
330 | TGA_WRITE_REG(par, v: 0x55 | (BT485_DATA_PAL << 8), |
331 | TGA_RAMDAC_REG); |
332 | TGA_WRITE_REG(par, v: 0x00 | (BT485_DATA_PAL << 8), |
333 | TGA_RAMDAC_REG); |
334 | TGA_WRITE_REG(par, v: 0x00 | (BT485_DATA_PAL << 8), |
335 | TGA_RAMDAC_REG); |
336 | TGA_WRITE_REG(par, v: 0x00 | (BT485_DATA_PAL << 8), |
337 | TGA_RAMDAC_REG); |
338 | } |
339 | |
340 | } else if (tga_type == TGA_TYPE_8PLANE && tga_bus_tc) { |
341 | |
342 | /* Init BT459 RAMDAC registers. */ |
343 | BT459_WRITE(par, BT459_REG_ACC, BT459_CMD_REG_0, v: 0x40); |
344 | BT459_WRITE(par, BT459_REG_ACC, BT459_CMD_REG_1, v: 0x00); |
345 | BT459_WRITE(par, BT459_REG_ACC, BT459_CMD_REG_2, |
346 | v: (par->sync_on_green ? 0xc0 : 0x40)); |
347 | |
348 | BT459_WRITE(par, BT459_REG_ACC, BT459_CUR_CMD_REG, v: 0x00); |
349 | |
350 | /* Fill the palette. */ |
351 | BT459_LOAD_ADDR(par, a: 0x0000); |
352 | TGA_WRITE_REG(par, BT459_PALETTE << 2, TGA_RAMDAC_SETUP_REG); |
353 | |
354 | for (i = 0; i < 256 * 3; i += 4) { |
355 | TGA_WRITE_REG(par, v: 0x55, TGA_RAMDAC_REG); |
356 | TGA_WRITE_REG(par, v: 0x00, TGA_RAMDAC_REG); |
357 | TGA_WRITE_REG(par, v: 0x00, TGA_RAMDAC_REG); |
358 | TGA_WRITE_REG(par, v: 0x00, TGA_RAMDAC_REG); |
359 | } |
360 | |
361 | } else { /* 24-plane or 24plusZ */ |
362 | |
363 | /* Init BT463 RAMDAC registers. */ |
364 | BT463_WRITE(par, BT463_REG_ACC, BT463_CMD_REG_0, v: 0x40); |
365 | BT463_WRITE(par, BT463_REG_ACC, BT463_CMD_REG_1, v: 0x08); |
366 | BT463_WRITE(par, BT463_REG_ACC, BT463_CMD_REG_2, |
367 | v: (par->sync_on_green ? 0xc0 : 0x40)); |
368 | |
369 | BT463_WRITE(par, BT463_REG_ACC, BT463_READ_MASK_0, v: 0xff); |
370 | BT463_WRITE(par, BT463_REG_ACC, BT463_READ_MASK_1, v: 0xff); |
371 | BT463_WRITE(par, BT463_REG_ACC, BT463_READ_MASK_2, v: 0xff); |
372 | BT463_WRITE(par, BT463_REG_ACC, BT463_READ_MASK_3, v: 0x0f); |
373 | |
374 | BT463_WRITE(par, BT463_REG_ACC, BT463_BLINK_MASK_0, v: 0x00); |
375 | BT463_WRITE(par, BT463_REG_ACC, BT463_BLINK_MASK_1, v: 0x00); |
376 | BT463_WRITE(par, BT463_REG_ACC, BT463_BLINK_MASK_2, v: 0x00); |
377 | BT463_WRITE(par, BT463_REG_ACC, BT463_BLINK_MASK_3, v: 0x00); |
378 | |
379 | /* Fill the palette. */ |
380 | BT463_LOAD_ADDR(par, a: 0x0000); |
381 | TGA_WRITE_REG(par, BT463_PALETTE << 2, TGA_RAMDAC_SETUP_REG); |
382 | |
383 | #ifdef CONFIG_HW_CONSOLE |
384 | for (i = 0; i < 16; i++) { |
385 | int j = color_table[i]; |
386 | |
387 | TGA_WRITE_REG(par, v: default_red[j], TGA_RAMDAC_REG); |
388 | TGA_WRITE_REG(par, v: default_grn[j], TGA_RAMDAC_REG); |
389 | TGA_WRITE_REG(par, v: default_blu[j], TGA_RAMDAC_REG); |
390 | } |
391 | for (i = 0; i < 512 * 3; i += 4) { |
392 | #else |
393 | for (i = 0; i < 528 * 3; i += 4) { |
394 | #endif |
395 | TGA_WRITE_REG(par, v: 0x55, TGA_RAMDAC_REG); |
396 | TGA_WRITE_REG(par, v: 0x00, TGA_RAMDAC_REG); |
397 | TGA_WRITE_REG(par, v: 0x00, TGA_RAMDAC_REG); |
398 | TGA_WRITE_REG(par, v: 0x00, TGA_RAMDAC_REG); |
399 | } |
400 | |
401 | /* Fill window type table after start of vertical retrace. */ |
402 | while (!(TGA_READ_REG(par, TGA_INTR_STAT_REG) & 0x01)) |
403 | continue; |
404 | TGA_WRITE_REG(par, v: 0x01, TGA_INTR_STAT_REG); |
405 | mb(); |
406 | while (!(TGA_READ_REG(par, TGA_INTR_STAT_REG) & 0x01)) |
407 | continue; |
408 | TGA_WRITE_REG(par, v: 0x01, TGA_INTR_STAT_REG); |
409 | |
410 | BT463_LOAD_ADDR(par, BT463_WINDOW_TYPE_BASE); |
411 | TGA_WRITE_REG(par, BT463_REG_ACC << 2, TGA_RAMDAC_SETUP_REG); |
412 | |
413 | for (i = 0; i < 16; i++) { |
414 | TGA_WRITE_REG(par, v: 0x00, TGA_RAMDAC_REG); |
415 | TGA_WRITE_REG(par, v: 0x01, TGA_RAMDAC_REG); |
416 | TGA_WRITE_REG(par, v: 0x00, TGA_RAMDAC_REG); |
417 | } |
418 | |
419 | } |
420 | |
421 | /* Finally, enable video scan (and pray for the monitor... :-) */ |
422 | TGA_WRITE_REG(par, TGA_VALID_VIDEO, TGA_VALID_REG); |
423 | |
424 | return 0; |
425 | } |
426 | |
427 | #define DIFFCHECK(X) \ |
428 | do { \ |
429 | if (m <= 0x3f) { \ |
430 | int delta = f - (TGA_PLL_BASE_FREQ * (X)) / (r << shift); \ |
431 | if (delta < 0) \ |
432 | delta = -delta; \ |
433 | if (delta < min_diff) \ |
434 | min_diff = delta, vm = m, va = a, vr = r; \ |
435 | } \ |
436 | } while (0) |
437 | |
438 | static void |
439 | tgafb_set_pll(struct tga_par *par, int f) |
440 | { |
441 | int n, shift, base, min_diff, target; |
442 | int r,a,m,vm = 34, va = 1, vr = 30; |
443 | |
444 | for (r = 0 ; r < 12 ; r++) |
445 | TGA_WRITE_REG(par, v: !r, TGA_CLOCK_REG); |
446 | |
447 | if (f > TGA_PLL_MAX_FREQ) |
448 | f = TGA_PLL_MAX_FREQ; |
449 | |
450 | if (f >= TGA_PLL_MAX_FREQ / 2) |
451 | shift = 0; |
452 | else if (f >= TGA_PLL_MAX_FREQ / 4) |
453 | shift = 1; |
454 | else |
455 | shift = 2; |
456 | |
457 | TGA_WRITE_REG(par, v: shift & 1, TGA_CLOCK_REG); |
458 | TGA_WRITE_REG(par, v: shift >> 1, TGA_CLOCK_REG); |
459 | |
460 | for (r = 0 ; r < 10 ; r++) |
461 | TGA_WRITE_REG(par, v: 0, TGA_CLOCK_REG); |
462 | |
463 | if (f <= 120000) { |
464 | TGA_WRITE_REG(par, v: 0, TGA_CLOCK_REG); |
465 | TGA_WRITE_REG(par, v: 0, TGA_CLOCK_REG); |
466 | } |
467 | else if (f <= 200000) { |
468 | TGA_WRITE_REG(par, v: 1, TGA_CLOCK_REG); |
469 | TGA_WRITE_REG(par, v: 0, TGA_CLOCK_REG); |
470 | } |
471 | else { |
472 | TGA_WRITE_REG(par, v: 0, TGA_CLOCK_REG); |
473 | TGA_WRITE_REG(par, v: 1, TGA_CLOCK_REG); |
474 | } |
475 | |
476 | TGA_WRITE_REG(par, v: 1, TGA_CLOCK_REG); |
477 | TGA_WRITE_REG(par, v: 0, TGA_CLOCK_REG); |
478 | TGA_WRITE_REG(par, v: 0, TGA_CLOCK_REG); |
479 | TGA_WRITE_REG(par, v: 1, TGA_CLOCK_REG); |
480 | TGA_WRITE_REG(par, v: 0, TGA_CLOCK_REG); |
481 | TGA_WRITE_REG(par, v: 1, TGA_CLOCK_REG); |
482 | |
483 | target = (f << shift) / TGA_PLL_BASE_FREQ; |
484 | min_diff = TGA_PLL_MAX_FREQ; |
485 | |
486 | r = 7 / target; |
487 | if (!r) r = 1; |
488 | |
489 | base = target * r; |
490 | while (base < 449) { |
491 | for (n = base < 7 ? 7 : base; n < base + target && n < 449; n++) { |
492 | m = ((n + 3) / 7) - 1; |
493 | a = 0; |
494 | DIFFCHECK((m + 1) * 7); |
495 | m++; |
496 | DIFFCHECK((m + 1) * 7); |
497 | m = (n / 6) - 1; |
498 | if ((a = n % 6)) |
499 | DIFFCHECK(n); |
500 | } |
501 | r++; |
502 | base += target; |
503 | } |
504 | |
505 | vr--; |
506 | |
507 | for (r = 0; r < 8; r++) |
508 | TGA_WRITE_REG(par, v: (vm >> r) & 1, TGA_CLOCK_REG); |
509 | for (r = 0; r < 8 ; r++) |
510 | TGA_WRITE_REG(par, v: (va >> r) & 1, TGA_CLOCK_REG); |
511 | for (r = 0; r < 7 ; r++) |
512 | TGA_WRITE_REG(par, v: (vr >> r) & 1, TGA_CLOCK_REG); |
513 | TGA_WRITE_REG(par, v: ((vr >> 7) & 1)|2, TGA_CLOCK_REG); |
514 | } |
515 | |
516 | |
517 | /** |
518 | * tgafb_setcolreg - Optional function. Sets a color register. |
519 | * @regno: boolean, 0 copy local, 1 get_user() function |
520 | * @red: frame buffer colormap structure |
521 | * @green: The green value which can be up to 16 bits wide |
522 | * @blue: The blue value which can be up to 16 bits wide. |
523 | * @transp: If supported the alpha value which can be up to 16 bits wide. |
524 | * @info: frame buffer info structure |
525 | */ |
526 | static int |
527 | tgafb_setcolreg(unsigned regno, unsigned red, unsigned green, unsigned blue, |
528 | unsigned transp, struct fb_info *info) |
529 | { |
530 | struct tga_par *par = (struct tga_par *) info->par; |
531 | int tga_bus_pci = dev_is_pci(par->dev); |
532 | int tga_bus_tc = TGA_BUS_TC(par->dev); |
533 | |
534 | if (regno > 255) |
535 | return 1; |
536 | red >>= 8; |
537 | green >>= 8; |
538 | blue >>= 8; |
539 | |
540 | if (par->tga_type == TGA_TYPE_8PLANE && tga_bus_pci) { |
541 | BT485_WRITE(par, v: regno, BT485_ADDR_PAL_WRITE); |
542 | TGA_WRITE_REG(par, BT485_DATA_PAL, TGA_RAMDAC_SETUP_REG); |
543 | TGA_WRITE_REG(par, v: red|(BT485_DATA_PAL<<8),TGA_RAMDAC_REG); |
544 | TGA_WRITE_REG(par, v: green|(BT485_DATA_PAL<<8),TGA_RAMDAC_REG); |
545 | TGA_WRITE_REG(par, v: blue|(BT485_DATA_PAL<<8),TGA_RAMDAC_REG); |
546 | } else if (par->tga_type == TGA_TYPE_8PLANE && tga_bus_tc) { |
547 | BT459_LOAD_ADDR(par, a: regno); |
548 | TGA_WRITE_REG(par, BT459_PALETTE << 2, TGA_RAMDAC_SETUP_REG); |
549 | TGA_WRITE_REG(par, v: red, TGA_RAMDAC_REG); |
550 | TGA_WRITE_REG(par, v: green, TGA_RAMDAC_REG); |
551 | TGA_WRITE_REG(par, v: blue, TGA_RAMDAC_REG); |
552 | } else { |
553 | if (regno < 16) { |
554 | u32 value = (regno << 16) | (regno << 8) | regno; |
555 | ((u32 *)info->pseudo_palette)[regno] = value; |
556 | } |
557 | BT463_LOAD_ADDR(par, a: regno); |
558 | TGA_WRITE_REG(par, BT463_PALETTE << 2, TGA_RAMDAC_SETUP_REG); |
559 | TGA_WRITE_REG(par, v: red, TGA_RAMDAC_REG); |
560 | TGA_WRITE_REG(par, v: green, TGA_RAMDAC_REG); |
561 | TGA_WRITE_REG(par, v: blue, TGA_RAMDAC_REG); |
562 | } |
563 | |
564 | return 0; |
565 | } |
566 | |
567 | |
568 | /** |
569 | * tgafb_blank - Optional function. Blanks the display. |
570 | * @blank: the blank mode we want. |
571 | * @info: frame buffer structure that represents a single frame buffer |
572 | */ |
573 | static int |
574 | tgafb_blank(int blank, struct fb_info *info) |
575 | { |
576 | struct tga_par *par = (struct tga_par *) info->par; |
577 | u32 vhcr, vvcr, vvvr; |
578 | unsigned long flags; |
579 | |
580 | local_irq_save(flags); |
581 | |
582 | vhcr = TGA_READ_REG(par, TGA_HORIZ_REG); |
583 | vvcr = TGA_READ_REG(par, TGA_VERT_REG); |
584 | vvvr = TGA_READ_REG(par, TGA_VALID_REG); |
585 | vvvr &= ~(TGA_VALID_VIDEO | TGA_VALID_BLANK); |
586 | |
587 | switch (blank) { |
588 | case FB_BLANK_UNBLANK: /* Unblanking */ |
589 | if (par->vesa_blanked) { |
590 | TGA_WRITE_REG(par, v: vhcr & 0xbfffffff, TGA_HORIZ_REG); |
591 | TGA_WRITE_REG(par, v: vvcr & 0xbfffffff, TGA_VERT_REG); |
592 | par->vesa_blanked = 0; |
593 | } |
594 | TGA_WRITE_REG(par, v: vvvr | TGA_VALID_VIDEO, TGA_VALID_REG); |
595 | break; |
596 | |
597 | case FB_BLANK_NORMAL: /* Normal blanking */ |
598 | TGA_WRITE_REG(par, v: vvvr | TGA_VALID_VIDEO | TGA_VALID_BLANK, |
599 | TGA_VALID_REG); |
600 | break; |
601 | |
602 | case FB_BLANK_VSYNC_SUSPEND: /* VESA blank (vsync off) */ |
603 | TGA_WRITE_REG(par, v: vvcr | 0x40000000, TGA_VERT_REG); |
604 | TGA_WRITE_REG(par, v: vvvr | TGA_VALID_BLANK, TGA_VALID_REG); |
605 | par->vesa_blanked = 1; |
606 | break; |
607 | |
608 | case FB_BLANK_HSYNC_SUSPEND: /* VESA blank (hsync off) */ |
609 | TGA_WRITE_REG(par, v: vhcr | 0x40000000, TGA_HORIZ_REG); |
610 | TGA_WRITE_REG(par, v: vvvr | TGA_VALID_BLANK, TGA_VALID_REG); |
611 | par->vesa_blanked = 1; |
612 | break; |
613 | |
614 | case FB_BLANK_POWERDOWN: /* Poweroff */ |
615 | TGA_WRITE_REG(par, v: vhcr | 0x40000000, TGA_HORIZ_REG); |
616 | TGA_WRITE_REG(par, v: vvcr | 0x40000000, TGA_VERT_REG); |
617 | TGA_WRITE_REG(par, v: vvvr | TGA_VALID_BLANK, TGA_VALID_REG); |
618 | par->vesa_blanked = 1; |
619 | break; |
620 | } |
621 | |
622 | local_irq_restore(flags); |
623 | return 0; |
624 | } |
625 | |
626 | |
627 | /* |
628 | * Acceleration. |
629 | */ |
630 | |
631 | static void |
632 | tgafb_mono_imageblit(struct fb_info *info, const struct fb_image *image) |
633 | { |
634 | struct tga_par *par = (struct tga_par *) info->par; |
635 | u32 fgcolor, bgcolor, dx, dy, width, height, vxres, vyres, pixelmask; |
636 | unsigned long rincr, line_length, shift, pos, is8bpp; |
637 | unsigned long i, j; |
638 | const unsigned char *data; |
639 | void __iomem *regs_base; |
640 | void __iomem *fb_base; |
641 | |
642 | is8bpp = info->var.bits_per_pixel == 8; |
643 | |
644 | dx = image->dx; |
645 | dy = image->dy; |
646 | width = image->width; |
647 | height = image->height; |
648 | vxres = info->var.xres_virtual; |
649 | vyres = info->var.yres_virtual; |
650 | line_length = info->fix.line_length; |
651 | rincr = (width + 7) / 8; |
652 | |
653 | /* A shift below cannot cope with. */ |
654 | if (unlikely(width == 0)) |
655 | return; |
656 | /* Crop the image to the screen. */ |
657 | if (dx > vxres || dy > vyres) |
658 | return; |
659 | if (dx + width > vxres) |
660 | width = vxres - dx; |
661 | if (dy + height > vyres) |
662 | height = vyres - dy; |
663 | |
664 | regs_base = par->tga_regs_base; |
665 | fb_base = par->tga_fb_base; |
666 | |
667 | /* Expand the color values to fill 32-bits. */ |
668 | /* ??? Would be nice to notice colour changes elsewhere, so |
669 | that we can do this only when necessary. */ |
670 | fgcolor = image->fg_color; |
671 | bgcolor = image->bg_color; |
672 | if (is8bpp) { |
673 | fgcolor |= fgcolor << 8; |
674 | fgcolor |= fgcolor << 16; |
675 | bgcolor |= bgcolor << 8; |
676 | bgcolor |= bgcolor << 16; |
677 | } else { |
678 | if (fgcolor < 16) |
679 | fgcolor = ((u32 *)info->pseudo_palette)[fgcolor]; |
680 | if (bgcolor < 16) |
681 | bgcolor = ((u32 *)info->pseudo_palette)[bgcolor]; |
682 | } |
683 | __raw_writel(val: fgcolor, addr: regs_base + TGA_FOREGROUND_REG); |
684 | __raw_writel(val: bgcolor, addr: regs_base + TGA_BACKGROUND_REG); |
685 | |
686 | /* Acquire proper alignment; set up the PIXELMASK register |
687 | so that we only write the proper character cell. */ |
688 | pos = dy * line_length; |
689 | if (is8bpp) { |
690 | pos += dx; |
691 | shift = pos & 3; |
692 | pos &= -4; |
693 | } else { |
694 | pos += dx * 4; |
695 | shift = (pos & 7) >> 2; |
696 | pos &= -8; |
697 | } |
698 | |
699 | data = (const unsigned char *) image->data; |
700 | |
701 | /* Enable opaque stipple mode. */ |
702 | __raw_writel(val: (is8bpp |
703 | ? TGA_MODE_SBM_8BPP | TGA_MODE_OPAQUE_STIPPLE |
704 | : TGA_MODE_SBM_24BPP | TGA_MODE_OPAQUE_STIPPLE), |
705 | addr: regs_base + TGA_MODE_REG); |
706 | |
707 | if (width + shift <= 32) { |
708 | unsigned long bwidth; |
709 | |
710 | /* Handle common case of imaging a single character, in |
711 | a font less than or 32 pixels wide. */ |
712 | |
713 | /* Avoid a shift by 32; width > 0 implied. */ |
714 | pixelmask = (2ul << (width - 1)) - 1; |
715 | pixelmask <<= shift; |
716 | __raw_writel(val: pixelmask, addr: regs_base + TGA_PIXELMASK_REG); |
717 | wmb(); |
718 | |
719 | bwidth = (width + 7) / 8; |
720 | |
721 | for (i = 0; i < height; ++i) { |
722 | u32 mask = 0; |
723 | |
724 | /* The image data is bit big endian; we need |
725 | little endian. */ |
726 | for (j = 0; j < bwidth; ++j) |
727 | mask |= bitrev8(data[j]) << (j * 8); |
728 | |
729 | __raw_writel(val: mask << shift, addr: fb_base + pos); |
730 | |
731 | pos += line_length; |
732 | data += rincr; |
733 | } |
734 | wmb(); |
735 | __raw_writel(val: 0xffffffff, addr: regs_base + TGA_PIXELMASK_REG); |
736 | } else if (shift == 0) { |
737 | unsigned long pos0 = pos; |
738 | const unsigned char *data0 = data; |
739 | unsigned long bincr = (is8bpp ? 8 : 8*4); |
740 | unsigned long bwidth; |
741 | |
742 | /* Handle another common case in which accel_putcs |
743 | generates a large bitmap, which happens to be aligned. |
744 | Allow the tail to be misaligned. This case is |
745 | interesting because we've not got to hold partial |
746 | bytes across the words being written. */ |
747 | |
748 | wmb(); |
749 | |
750 | bwidth = (width / 8) & -4; |
751 | for (i = 0; i < height; ++i) { |
752 | for (j = 0; j < bwidth; j += 4) { |
753 | u32 mask = 0; |
754 | mask |= bitrev8(data[j+0]) << (0 * 8); |
755 | mask |= bitrev8(data[j+1]) << (1 * 8); |
756 | mask |= bitrev8(data[j+2]) << (2 * 8); |
757 | mask |= bitrev8(data[j+3]) << (3 * 8); |
758 | __raw_writel(val: mask, addr: fb_base + pos + j*bincr); |
759 | } |
760 | pos += line_length; |
761 | data += rincr; |
762 | } |
763 | wmb(); |
764 | |
765 | pixelmask = (1ul << (width & 31)) - 1; |
766 | if (pixelmask) { |
767 | __raw_writel(val: pixelmask, addr: regs_base + TGA_PIXELMASK_REG); |
768 | wmb(); |
769 | |
770 | pos = pos0 + bwidth*bincr; |
771 | data = data0 + bwidth; |
772 | bwidth = ((width & 31) + 7) / 8; |
773 | |
774 | for (i = 0; i < height; ++i) { |
775 | u32 mask = 0; |
776 | for (j = 0; j < bwidth; ++j) |
777 | mask |= bitrev8(data[j]) << (j * 8); |
778 | __raw_writel(val: mask, addr: fb_base + pos); |
779 | pos += line_length; |
780 | data += rincr; |
781 | } |
782 | wmb(); |
783 | __raw_writel(val: 0xffffffff, addr: regs_base + TGA_PIXELMASK_REG); |
784 | } |
785 | } else { |
786 | unsigned long pos0 = pos; |
787 | const unsigned char *data0 = data; |
788 | unsigned long bincr = (is8bpp ? 8 : 8*4); |
789 | unsigned long bwidth; |
790 | |
791 | /* Finally, handle the generic case of misaligned start. |
792 | Here we split the write into 16-bit spans. This allows |
793 | us to use only one pixel mask, instead of four as would |
794 | be required by writing 24-bit spans. */ |
795 | |
796 | pixelmask = 0xffff << shift; |
797 | __raw_writel(val: pixelmask, addr: regs_base + TGA_PIXELMASK_REG); |
798 | wmb(); |
799 | |
800 | bwidth = (width / 8) & -2; |
801 | for (i = 0; i < height; ++i) { |
802 | for (j = 0; j < bwidth; j += 2) { |
803 | u32 mask = 0; |
804 | mask |= bitrev8(data[j+0]) << (0 * 8); |
805 | mask |= bitrev8(data[j+1]) << (1 * 8); |
806 | mask <<= shift; |
807 | __raw_writel(val: mask, addr: fb_base + pos + j*bincr); |
808 | } |
809 | pos += line_length; |
810 | data += rincr; |
811 | } |
812 | wmb(); |
813 | |
814 | pixelmask = ((1ul << (width & 15)) - 1) << shift; |
815 | if (pixelmask) { |
816 | __raw_writel(val: pixelmask, addr: regs_base + TGA_PIXELMASK_REG); |
817 | wmb(); |
818 | |
819 | pos = pos0 + bwidth*bincr; |
820 | data = data0 + bwidth; |
821 | bwidth = (width & 15) > 8; |
822 | |
823 | for (i = 0; i < height; ++i) { |
824 | u32 mask = bitrev8(data[0]); |
825 | if (bwidth) |
826 | mask |= bitrev8(data[1]) << 8; |
827 | mask <<= shift; |
828 | __raw_writel(val: mask, addr: fb_base + pos); |
829 | pos += line_length; |
830 | data += rincr; |
831 | } |
832 | wmb(); |
833 | } |
834 | __raw_writel(val: 0xffffffff, addr: regs_base + TGA_PIXELMASK_REG); |
835 | } |
836 | |
837 | /* Disable opaque stipple mode. */ |
838 | __raw_writel(val: (is8bpp |
839 | ? TGA_MODE_SBM_8BPP | TGA_MODE_SIMPLE |
840 | : TGA_MODE_SBM_24BPP | TGA_MODE_SIMPLE), |
841 | addr: regs_base + TGA_MODE_REG); |
842 | } |
843 | |
844 | static void |
845 | tgafb_clut_imageblit(struct fb_info *info, const struct fb_image *image) |
846 | { |
847 | struct tga_par *par = (struct tga_par *) info->par; |
848 | u32 color, dx, dy, width, height, vxres, vyres; |
849 | u32 *palette = ((u32 *)info->pseudo_palette); |
850 | unsigned long pos, line_length, i, j; |
851 | const unsigned char *data; |
852 | void __iomem *fb_base; |
853 | |
854 | dx = image->dx; |
855 | dy = image->dy; |
856 | width = image->width; |
857 | height = image->height; |
858 | vxres = info->var.xres_virtual; |
859 | vyres = info->var.yres_virtual; |
860 | line_length = info->fix.line_length; |
861 | |
862 | /* Crop the image to the screen. */ |
863 | if (dx > vxres || dy > vyres) |
864 | return; |
865 | if (dx + width > vxres) |
866 | width = vxres - dx; |
867 | if (dy + height > vyres) |
868 | height = vyres - dy; |
869 | |
870 | fb_base = par->tga_fb_base; |
871 | |
872 | pos = dy * line_length + (dx * 4); |
873 | data = image->data; |
874 | |
875 | /* Now copy the image, color_expanding via the palette. */ |
876 | for (i = 0; i < height; i++) { |
877 | for (j = 0; j < width; j++) { |
878 | color = palette[*data++]; |
879 | __raw_writel(val: color, addr: fb_base + pos + j*4); |
880 | } |
881 | pos += line_length; |
882 | } |
883 | } |
884 | |
885 | /** |
886 | * tgafb_imageblit - REQUIRED function. Can use generic routines if |
887 | * non acclerated hardware and packed pixel based. |
888 | * Copies a image from system memory to the screen. |
889 | * |
890 | * @info: frame buffer structure that represents a single frame buffer |
891 | * @image: structure defining the image. |
892 | */ |
893 | static void |
894 | tgafb_imageblit(struct fb_info *info, const struct fb_image *image) |
895 | { |
896 | unsigned int is8bpp = info->var.bits_per_pixel == 8; |
897 | |
898 | /* If a mono image, regardless of FB depth, go do it. */ |
899 | if (image->depth == 1) { |
900 | tgafb_mono_imageblit(info, image); |
901 | return; |
902 | } |
903 | |
904 | /* For copies that aren't pixel expansion, there's little we |
905 | can do better than the generic code. */ |
906 | /* ??? There is a DMA write mode; I wonder if that could be |
907 | made to pull the data from the image buffer... */ |
908 | if (image->depth == info->var.bits_per_pixel) { |
909 | cfb_imageblit(info, image); |
910 | return; |
911 | } |
912 | |
913 | /* If 24-plane FB and the image is 8-plane with CLUT, we can do it. */ |
914 | if (!is8bpp && image->depth == 8) { |
915 | tgafb_clut_imageblit(info, image); |
916 | return; |
917 | } |
918 | |
919 | /* Silently return... */ |
920 | } |
921 | |
922 | /** |
923 | * tgafb_fillrect - REQUIRED function. Can use generic routines if |
924 | * non acclerated hardware and packed pixel based. |
925 | * Draws a rectangle on the screen. |
926 | * |
927 | * @info: frame buffer structure that represents a single frame buffer |
928 | * @rect: structure defining the rectagle and operation. |
929 | */ |
930 | static void |
931 | tgafb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) |
932 | { |
933 | struct tga_par *par = (struct tga_par *) info->par; |
934 | int is8bpp = info->var.bits_per_pixel == 8; |
935 | u32 dx, dy, width, height, vxres, vyres, color; |
936 | unsigned long pos, align, line_length, i, j; |
937 | void __iomem *regs_base; |
938 | void __iomem *fb_base; |
939 | |
940 | dx = rect->dx; |
941 | dy = rect->dy; |
942 | width = rect->width; |
943 | height = rect->height; |
944 | vxres = info->var.xres_virtual; |
945 | vyres = info->var.yres_virtual; |
946 | line_length = info->fix.line_length; |
947 | regs_base = par->tga_regs_base; |
948 | fb_base = par->tga_fb_base; |
949 | |
950 | /* Crop the rectangle to the screen. */ |
951 | if (dx > vxres || dy > vyres || !width || !height) |
952 | return; |
953 | if (dx + width > vxres) |
954 | width = vxres - dx; |
955 | if (dy + height > vyres) |
956 | height = vyres - dy; |
957 | |
958 | pos = dy * line_length + dx * (is8bpp ? 1 : 4); |
959 | |
960 | /* ??? We could implement ROP_XOR with opaque fill mode |
961 | and a RasterOp setting of GXxor, but as far as I can |
962 | tell, this mode is not actually used in the kernel. |
963 | Thus I am ignoring it for now. */ |
964 | if (rect->rop != ROP_COPY) { |
965 | cfb_fillrect(info, rect); |
966 | return; |
967 | } |
968 | |
969 | /* Expand the color value to fill 8 pixels. */ |
970 | color = rect->color; |
971 | if (is8bpp) { |
972 | color |= color << 8; |
973 | color |= color << 16; |
974 | __raw_writel(val: color, addr: regs_base + TGA_BLOCK_COLOR0_REG); |
975 | __raw_writel(val: color, addr: regs_base + TGA_BLOCK_COLOR1_REG); |
976 | } else { |
977 | if (color < 16) |
978 | color = ((u32 *)info->pseudo_palette)[color]; |
979 | __raw_writel(val: color, addr: regs_base + TGA_BLOCK_COLOR0_REG); |
980 | __raw_writel(val: color, addr: regs_base + TGA_BLOCK_COLOR1_REG); |
981 | __raw_writel(val: color, addr: regs_base + TGA_BLOCK_COLOR2_REG); |
982 | __raw_writel(val: color, addr: regs_base + TGA_BLOCK_COLOR3_REG); |
983 | __raw_writel(val: color, addr: regs_base + TGA_BLOCK_COLOR4_REG); |
984 | __raw_writel(val: color, addr: regs_base + TGA_BLOCK_COLOR5_REG); |
985 | __raw_writel(val: color, addr: regs_base + TGA_BLOCK_COLOR6_REG); |
986 | __raw_writel(val: color, addr: regs_base + TGA_BLOCK_COLOR7_REG); |
987 | } |
988 | |
989 | /* The DATA register holds the fill mask for block fill mode. |
990 | Since we're not stippling, this is all ones. */ |
991 | __raw_writel(val: 0xffffffff, addr: regs_base + TGA_DATA_REG); |
992 | |
993 | /* Enable block fill mode. */ |
994 | __raw_writel(val: (is8bpp |
995 | ? TGA_MODE_SBM_8BPP | TGA_MODE_BLOCK_FILL |
996 | : TGA_MODE_SBM_24BPP | TGA_MODE_BLOCK_FILL), |
997 | addr: regs_base + TGA_MODE_REG); |
998 | wmb(); |
999 | |
1000 | /* We can fill 2k pixels per operation. Notice blocks that fit |
1001 | the width of the screen so that we can take advantage of this |
1002 | and fill more than one line per write. */ |
1003 | if (width == line_length) { |
1004 | width *= height; |
1005 | height = 1; |
1006 | } |
1007 | |
1008 | /* The write into the frame buffer must be aligned to 4 bytes, |
1009 | but we are allowed to encode the offset within the word in |
1010 | the data word written. */ |
1011 | align = (pos & 3) << 16; |
1012 | pos &= -4; |
1013 | |
1014 | if (width <= 2048) { |
1015 | u32 data; |
1016 | |
1017 | data = (width - 1) | align; |
1018 | |
1019 | for (i = 0; i < height; ++i) { |
1020 | __raw_writel(val: data, addr: fb_base + pos); |
1021 | pos += line_length; |
1022 | } |
1023 | } else { |
1024 | unsigned long Bpp = (is8bpp ? 1 : 4); |
1025 | unsigned long nwidth = width & -2048; |
1026 | u32 fdata, ldata; |
1027 | |
1028 | fdata = (2048 - 1) | align; |
1029 | ldata = ((width & 2047) - 1) | align; |
1030 | |
1031 | for (i = 0; i < height; ++i) { |
1032 | for (j = 0; j < nwidth; j += 2048) |
1033 | __raw_writel(val: fdata, addr: fb_base + pos + j*Bpp); |
1034 | if (j < width) |
1035 | __raw_writel(val: ldata, addr: fb_base + pos + j*Bpp); |
1036 | pos += line_length; |
1037 | } |
1038 | } |
1039 | wmb(); |
1040 | |
1041 | /* Disable block fill mode. */ |
1042 | __raw_writel(val: (is8bpp |
1043 | ? TGA_MODE_SBM_8BPP | TGA_MODE_SIMPLE |
1044 | : TGA_MODE_SBM_24BPP | TGA_MODE_SIMPLE), |
1045 | addr: regs_base + TGA_MODE_REG); |
1046 | } |
1047 | |
1048 | /* |
1049 | * tgafb_copyarea - REQUIRED function. Can use generic routines if |
1050 | * non acclerated hardware and packed pixel based. |
1051 | * Copies on area of the screen to another area. |
1052 | * |
1053 | * @info: frame buffer structure that represents a single frame buffer |
1054 | * @area: structure defining the source and destination. |
1055 | */ |
1056 | |
1057 | /* Handle the special case of copying entire lines, e.g. during scrolling. |
1058 | We can avoid a lot of needless computation in this case. In the 8bpp |
1059 | case we need to use the COPY64 registers instead of mask writes into |
1060 | the frame buffer to achieve maximum performance. */ |
1061 | |
1062 | static inline void |
1063 | copyarea_line_8bpp(struct fb_info *info, u32 dy, u32 sy, |
1064 | u32 height, u32 width) |
1065 | { |
1066 | struct tga_par *par = (struct tga_par *) info->par; |
1067 | void __iomem *tga_regs = par->tga_regs_base; |
1068 | unsigned long dpos, spos, i, n64; |
1069 | |
1070 | /* Set up the MODE and PIXELSHIFT registers. */ |
1071 | __raw_writel(TGA_MODE_SBM_8BPP | TGA_MODE_COPY, addr: tga_regs+TGA_MODE_REG); |
1072 | __raw_writel(val: 0, addr: tga_regs+TGA_PIXELSHIFT_REG); |
1073 | wmb(); |
1074 | |
1075 | n64 = (height * width) / 64; |
1076 | |
1077 | if (sy < dy) { |
1078 | spos = (sy + height) * width; |
1079 | dpos = (dy + height) * width; |
1080 | |
1081 | for (i = 0; i < n64; ++i) { |
1082 | spos -= 64; |
1083 | dpos -= 64; |
1084 | __raw_writel(val: spos, addr: tga_regs+TGA_COPY64_SRC); |
1085 | wmb(); |
1086 | __raw_writel(val: dpos, addr: tga_regs+TGA_COPY64_DST); |
1087 | wmb(); |
1088 | } |
1089 | } else { |
1090 | spos = sy * width; |
1091 | dpos = dy * width; |
1092 | |
1093 | for (i = 0; i < n64; ++i) { |
1094 | __raw_writel(val: spos, addr: tga_regs+TGA_COPY64_SRC); |
1095 | wmb(); |
1096 | __raw_writel(val: dpos, addr: tga_regs+TGA_COPY64_DST); |
1097 | wmb(); |
1098 | spos += 64; |
1099 | dpos += 64; |
1100 | } |
1101 | } |
1102 | |
1103 | /* Reset the MODE register to normal. */ |
1104 | __raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_SIMPLE, addr: tga_regs+TGA_MODE_REG); |
1105 | } |
1106 | |
1107 | static inline void |
1108 | copyarea_line_32bpp(struct fb_info *info, u32 dy, u32 sy, |
1109 | u32 height, u32 width) |
1110 | { |
1111 | struct tga_par *par = (struct tga_par *) info->par; |
1112 | void __iomem *tga_regs = par->tga_regs_base; |
1113 | void __iomem *tga_fb = par->tga_fb_base; |
1114 | void __iomem *src; |
1115 | void __iomem *dst; |
1116 | unsigned long i, n16; |
1117 | |
1118 | /* Set up the MODE and PIXELSHIFT registers. */ |
1119 | __raw_writel(TGA_MODE_SBM_24BPP | TGA_MODE_COPY, addr: tga_regs+TGA_MODE_REG); |
1120 | __raw_writel(val: 0, addr: tga_regs+TGA_PIXELSHIFT_REG); |
1121 | wmb(); |
1122 | |
1123 | n16 = (height * width) / 16; |
1124 | |
1125 | if (sy < dy) { |
1126 | src = tga_fb + (sy + height) * width * 4; |
1127 | dst = tga_fb + (dy + height) * width * 4; |
1128 | |
1129 | for (i = 0; i < n16; ++i) { |
1130 | src -= 64; |
1131 | dst -= 64; |
1132 | __raw_writel(val: 0xffff, addr: src); |
1133 | wmb(); |
1134 | __raw_writel(val: 0xffff, addr: dst); |
1135 | wmb(); |
1136 | } |
1137 | } else { |
1138 | src = tga_fb + sy * width * 4; |
1139 | dst = tga_fb + dy * width * 4; |
1140 | |
1141 | for (i = 0; i < n16; ++i) { |
1142 | __raw_writel(val: 0xffff, addr: src); |
1143 | wmb(); |
1144 | __raw_writel(val: 0xffff, addr: dst); |
1145 | wmb(); |
1146 | src += 64; |
1147 | dst += 64; |
1148 | } |
1149 | } |
1150 | |
1151 | /* Reset the MODE register to normal. */ |
1152 | __raw_writel(TGA_MODE_SBM_24BPP|TGA_MODE_SIMPLE, addr: tga_regs+TGA_MODE_REG); |
1153 | } |
1154 | |
1155 | /* The (almost) general case of backward copy in 8bpp mode. */ |
1156 | static inline void |
1157 | copyarea_8bpp(struct fb_info *info, u32 dx, u32 dy, u32 sx, u32 sy, |
1158 | u32 height, u32 width, u32 line_length, |
1159 | const struct fb_copyarea *area) |
1160 | { |
1161 | struct tga_par *par = (struct tga_par *) info->par; |
1162 | unsigned i, yincr; |
1163 | int depos, sepos, backward, last_step, step; |
1164 | u32 mask_last; |
1165 | unsigned n32; |
1166 | void __iomem *tga_regs; |
1167 | void __iomem *tga_fb; |
1168 | |
1169 | /* Do acceleration only if we are aligned on 8 pixels */ |
1170 | if ((dx | sx | width) & 7) { |
1171 | cfb_copyarea(info, area); |
1172 | return; |
1173 | } |
1174 | |
1175 | yincr = line_length; |
1176 | if (dy > sy) { |
1177 | dy += height - 1; |
1178 | sy += height - 1; |
1179 | yincr = -yincr; |
1180 | } |
1181 | backward = dy == sy && dx > sx && dx < sx + width; |
1182 | |
1183 | /* Compute the offsets and alignments in the frame buffer. |
1184 | More than anything else, these control how we do copies. */ |
1185 | depos = dy * line_length + dx; |
1186 | sepos = sy * line_length + sx; |
1187 | if (backward) { |
1188 | depos += width; |
1189 | sepos += width; |
1190 | } |
1191 | |
1192 | /* Next copy full words at a time. */ |
1193 | n32 = width / 32; |
1194 | last_step = width % 32; |
1195 | |
1196 | /* Finally copy the unaligned head of the span. */ |
1197 | mask_last = (1ul << last_step) - 1; |
1198 | |
1199 | if (!backward) { |
1200 | step = 32; |
1201 | last_step = 32; |
1202 | } else { |
1203 | step = -32; |
1204 | last_step = -last_step; |
1205 | sepos -= 32; |
1206 | depos -= 32; |
1207 | } |
1208 | |
1209 | tga_regs = par->tga_regs_base; |
1210 | tga_fb = par->tga_fb_base; |
1211 | |
1212 | /* Set up the MODE and PIXELSHIFT registers. */ |
1213 | __raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_COPY, addr: tga_regs+TGA_MODE_REG); |
1214 | __raw_writel(val: 0, addr: tga_regs+TGA_PIXELSHIFT_REG); |
1215 | wmb(); |
1216 | |
1217 | for (i = 0; i < height; ++i) { |
1218 | unsigned long j; |
1219 | void __iomem *sfb; |
1220 | void __iomem *dfb; |
1221 | |
1222 | sfb = tga_fb + sepos; |
1223 | dfb = tga_fb + depos; |
1224 | |
1225 | for (j = 0; j < n32; j++) { |
1226 | if (j < 2 && j + 1 < n32 && !backward && |
1227 | !(((unsigned long)sfb | (unsigned long)dfb) & 63)) { |
1228 | do { |
1229 | __raw_writel(val: sfb - tga_fb, addr: tga_regs+TGA_COPY64_SRC); |
1230 | wmb(); |
1231 | __raw_writel(val: dfb - tga_fb, addr: tga_regs+TGA_COPY64_DST); |
1232 | wmb(); |
1233 | sfb += 64; |
1234 | dfb += 64; |
1235 | j += 2; |
1236 | } while (j + 1 < n32); |
1237 | j--; |
1238 | continue; |
1239 | } |
1240 | __raw_writel(val: 0xffffffff, addr: sfb); |
1241 | wmb(); |
1242 | __raw_writel(val: 0xffffffff, addr: dfb); |
1243 | wmb(); |
1244 | sfb += step; |
1245 | dfb += step; |
1246 | } |
1247 | |
1248 | if (mask_last) { |
1249 | sfb += last_step - step; |
1250 | dfb += last_step - step; |
1251 | __raw_writel(val: mask_last, addr: sfb); |
1252 | wmb(); |
1253 | __raw_writel(val: mask_last, addr: dfb); |
1254 | wmb(); |
1255 | } |
1256 | |
1257 | sepos += yincr; |
1258 | depos += yincr; |
1259 | } |
1260 | |
1261 | /* Reset the MODE register to normal. */ |
1262 | __raw_writel(TGA_MODE_SBM_8BPP|TGA_MODE_SIMPLE, addr: tga_regs+TGA_MODE_REG); |
1263 | } |
1264 | |
1265 | static void |
1266 | tgafb_copyarea(struct fb_info *info, const struct fb_copyarea *area) |
1267 | { |
1268 | unsigned long dx, dy, width, height, sx, sy, vxres, vyres; |
1269 | unsigned long line_length, bpp; |
1270 | |
1271 | dx = area->dx; |
1272 | dy = area->dy; |
1273 | width = area->width; |
1274 | height = area->height; |
1275 | sx = area->sx; |
1276 | sy = area->sy; |
1277 | vxres = info->var.xres_virtual; |
1278 | vyres = info->var.yres_virtual; |
1279 | line_length = info->fix.line_length; |
1280 | |
1281 | /* The top left corners must be in the virtual screen. */ |
1282 | if (dx > vxres || sx > vxres || dy > vyres || sy > vyres) |
1283 | return; |
1284 | |
1285 | /* Clip the destination. */ |
1286 | if (dx + width > vxres) |
1287 | width = vxres - dx; |
1288 | if (dy + height > vyres) |
1289 | height = vyres - dy; |
1290 | |
1291 | /* The source must be completely inside the virtual screen. */ |
1292 | if (sx + width > vxres || sy + height > vyres) |
1293 | return; |
1294 | |
1295 | bpp = info->var.bits_per_pixel; |
1296 | |
1297 | /* Detect copies of the entire line. */ |
1298 | if (!(line_length & 63) && width * (bpp >> 3) == line_length) { |
1299 | if (bpp == 8) |
1300 | copyarea_line_8bpp(info, dy, sy, height, width); |
1301 | else |
1302 | copyarea_line_32bpp(info, dy, sy, height, width); |
1303 | } |
1304 | |
1305 | /* ??? The documentation is unclear to me exactly how the pixelshift |
1306 | register works in 32bpp mode. Since I don't have hardware to test, |
1307 | give up for now and fall back on the generic routines. */ |
1308 | else if (bpp == 32) |
1309 | cfb_copyarea(info, area); |
1310 | |
1311 | else |
1312 | copyarea_8bpp(info, dx, dy, sx, sy, height, |
1313 | width, line_length, area); |
1314 | } |
1315 | |
1316 | |
1317 | /* |
1318 | * Initialisation |
1319 | */ |
1320 | |
1321 | static void |
1322 | tgafb_init_fix(struct fb_info *info) |
1323 | { |
1324 | struct tga_par *par = (struct tga_par *)info->par; |
1325 | int tga_bus_pci = dev_is_pci(par->dev); |
1326 | int tga_bus_tc = TGA_BUS_TC(par->dev); |
1327 | u8 tga_type = par->tga_type; |
1328 | const char *tga_type_name = NULL; |
1329 | unsigned memory_size; |
1330 | |
1331 | switch (tga_type) { |
1332 | case TGA_TYPE_8PLANE: |
1333 | if (tga_bus_pci) |
1334 | tga_type_name = "Digital ZLXp-E1" ; |
1335 | if (tga_bus_tc) |
1336 | tga_type_name = "Digital ZLX-E1" ; |
1337 | memory_size = 2097152; |
1338 | break; |
1339 | case TGA_TYPE_24PLANE: |
1340 | if (tga_bus_pci) |
1341 | tga_type_name = "Digital ZLXp-E2" ; |
1342 | if (tga_bus_tc) |
1343 | tga_type_name = "Digital ZLX-E2" ; |
1344 | memory_size = 8388608; |
1345 | break; |
1346 | case TGA_TYPE_24PLUSZ: |
1347 | if (tga_bus_pci) |
1348 | tga_type_name = "Digital ZLXp-E3" ; |
1349 | if (tga_bus_tc) |
1350 | tga_type_name = "Digital ZLX-E3" ; |
1351 | memory_size = 16777216; |
1352 | break; |
1353 | } |
1354 | if (!tga_type_name) { |
1355 | tga_type_name = "Unknown" ; |
1356 | memory_size = 16777216; |
1357 | } |
1358 | |
1359 | strscpy(p: info->fix.id, q: tga_type_name, size: sizeof(info->fix.id)); |
1360 | |
1361 | info->fix.type = FB_TYPE_PACKED_PIXELS; |
1362 | info->fix.type_aux = 0; |
1363 | info->fix.visual = (tga_type == TGA_TYPE_8PLANE |
1364 | ? FB_VISUAL_PSEUDOCOLOR |
1365 | : FB_VISUAL_DIRECTCOLOR); |
1366 | |
1367 | info->fix.smem_start = (size_t) par->tga_fb_base; |
1368 | info->fix.smem_len = memory_size; |
1369 | info->fix.mmio_start = (size_t) par->tga_regs_base; |
1370 | info->fix.mmio_len = 512; |
1371 | |
1372 | info->fix.xpanstep = 0; |
1373 | info->fix.ypanstep = 0; |
1374 | info->fix.ywrapstep = 0; |
1375 | |
1376 | info->fix.accel = FB_ACCEL_DEC_TGA; |
1377 | |
1378 | /* |
1379 | * These are needed by fb_set_logo_truepalette(), so we |
1380 | * set them here for 24-plane cards. |
1381 | */ |
1382 | if (tga_type != TGA_TYPE_8PLANE) { |
1383 | info->var.red.length = 8; |
1384 | info->var.green.length = 8; |
1385 | info->var.blue.length = 8; |
1386 | info->var.red.offset = 16; |
1387 | info->var.green.offset = 8; |
1388 | info->var.blue.offset = 0; |
1389 | } |
1390 | } |
1391 | |
1392 | static int tgafb_pan_display(struct fb_var_screeninfo *var, struct fb_info *info) |
1393 | { |
1394 | /* We just use this to catch switches out of graphics mode. */ |
1395 | tgafb_set_par(info); /* A bit of overkill for BASE_ADDR reset. */ |
1396 | return 0; |
1397 | } |
1398 | |
1399 | static int tgafb_register(struct device *dev) |
1400 | { |
1401 | static const struct fb_videomode modedb_tc = { |
1402 | /* 1280x1024 @ 72 Hz, 76.8 kHz hsync */ |
1403 | "1280x1024@72" , 0, 1280, 1024, 7645, 224, 28, 33, 3, 160, 3, |
1404 | FB_SYNC_ON_GREEN, FB_VMODE_NONINTERLACED |
1405 | }; |
1406 | |
1407 | static unsigned int const fb_offset_presets[4] = { |
1408 | TGA_8PLANE_FB_OFFSET, |
1409 | TGA_24PLANE_FB_OFFSET, |
1410 | 0xffffffff, |
1411 | TGA_24PLUSZ_FB_OFFSET |
1412 | }; |
1413 | |
1414 | const struct fb_videomode *modedb_tga = NULL; |
1415 | resource_size_t bar0_start = 0, bar0_len = 0; |
1416 | const char *mode_option_tga = NULL; |
1417 | int tga_bus_pci = dev_is_pci(dev); |
1418 | int tga_bus_tc = TGA_BUS_TC(dev); |
1419 | unsigned int modedbsize_tga = 0; |
1420 | void __iomem *mem_base; |
1421 | struct fb_info *info; |
1422 | struct tga_par *par; |
1423 | u8 tga_type; |
1424 | int ret = 0; |
1425 | |
1426 | /* Enable device in PCI config. */ |
1427 | if (tga_bus_pci && pci_enable_device(to_pci_dev(dev))) { |
1428 | printk(KERN_ERR "tgafb: Cannot enable PCI device\n" ); |
1429 | return -ENODEV; |
1430 | } |
1431 | |
1432 | /* Allocate the fb and par structures. */ |
1433 | info = framebuffer_alloc(size: sizeof(struct tga_par), dev); |
1434 | if (!info) |
1435 | return -ENOMEM; |
1436 | |
1437 | par = info->par; |
1438 | dev_set_drvdata(dev, data: info); |
1439 | |
1440 | /* Request the mem regions. */ |
1441 | ret = -ENODEV; |
1442 | if (tga_bus_pci) { |
1443 | bar0_start = pci_resource_start(to_pci_dev(dev), 0); |
1444 | bar0_len = pci_resource_len(to_pci_dev(dev), 0); |
1445 | } |
1446 | if (tga_bus_tc) { |
1447 | bar0_start = to_tc_dev(dev)->resource.start; |
1448 | bar0_len = to_tc_dev(dev)->resource.end - bar0_start + 1; |
1449 | } |
1450 | if (!request_mem_region (bar0_start, bar0_len, "tgafb" )) { |
1451 | printk(KERN_ERR "tgafb: cannot reserve FB region\n" ); |
1452 | goto err0; |
1453 | } |
1454 | |
1455 | /* Map the framebuffer. */ |
1456 | mem_base = ioremap(offset: bar0_start, size: bar0_len); |
1457 | if (!mem_base) { |
1458 | printk(KERN_ERR "tgafb: Cannot map MMIO\n" ); |
1459 | goto err1; |
1460 | } |
1461 | |
1462 | /* Grab info about the card. */ |
1463 | tga_type = (readl(addr: mem_base) >> 12) & 0x0f; |
1464 | par->dev = dev; |
1465 | par->tga_mem_base = mem_base; |
1466 | par->tga_fb_base = mem_base + fb_offset_presets[tga_type]; |
1467 | par->tga_regs_base = mem_base + TGA_REGS_OFFSET; |
1468 | par->tga_type = tga_type; |
1469 | if (tga_bus_pci) |
1470 | par->tga_chip_rev = (to_pci_dev(dev))->revision; |
1471 | if (tga_bus_tc) |
1472 | par->tga_chip_rev = TGA_READ_REG(par, TGA_START_REG) & 0xff; |
1473 | |
1474 | /* Setup framebuffer. */ |
1475 | info->flags = FBINFO_HWACCEL_COPYAREA | |
1476 | FBINFO_HWACCEL_IMAGEBLIT | FBINFO_HWACCEL_FILLRECT; |
1477 | info->fbops = &tgafb_ops; |
1478 | info->screen_base = par->tga_fb_base; |
1479 | info->pseudo_palette = par->palette; |
1480 | |
1481 | /* This should give a reasonable default video mode. */ |
1482 | if (tga_bus_pci) { |
1483 | mode_option_tga = mode_option_pci; |
1484 | } |
1485 | if (tga_bus_tc) { |
1486 | mode_option_tga = mode_option_tc; |
1487 | modedb_tga = &modedb_tc; |
1488 | modedbsize_tga = 1; |
1489 | } |
1490 | |
1491 | tgafb_init_fix(info); |
1492 | |
1493 | ret = fb_find_mode(var: &info->var, info, |
1494 | mode_option: mode_option ? mode_option : mode_option_tga, |
1495 | db: modedb_tga, dbsize: modedbsize_tga, NULL, |
1496 | default_bpp: tga_type == TGA_TYPE_8PLANE ? 8 : 32); |
1497 | if (ret == 0 || ret == 4) { |
1498 | printk(KERN_ERR "tgafb: Could not find valid video mode\n" ); |
1499 | ret = -EINVAL; |
1500 | goto err1; |
1501 | } |
1502 | |
1503 | if (fb_alloc_cmap(cmap: &info->cmap, len: 256, transp: 0)) { |
1504 | printk(KERN_ERR "tgafb: Could not allocate color map\n" ); |
1505 | ret = -ENOMEM; |
1506 | goto err1; |
1507 | } |
1508 | |
1509 | tgafb_set_par(info); |
1510 | |
1511 | if (register_framebuffer(fb_info: info) < 0) { |
1512 | printk(KERN_ERR "tgafb: Could not register framebuffer\n" ); |
1513 | ret = -EINVAL; |
1514 | goto err2; |
1515 | } |
1516 | |
1517 | if (tga_bus_pci) { |
1518 | pr_info("tgafb: DC21030 [TGA] detected, rev=0x%02x\n" , |
1519 | par->tga_chip_rev); |
1520 | pr_info("tgafb: at PCI bus %d, device %d, function %d\n" , |
1521 | to_pci_dev(dev)->bus->number, |
1522 | PCI_SLOT(to_pci_dev(dev)->devfn), |
1523 | PCI_FUNC(to_pci_dev(dev)->devfn)); |
1524 | } |
1525 | if (tga_bus_tc) |
1526 | pr_info("tgafb: SFB+ detected, rev=0x%02x\n" , |
1527 | par->tga_chip_rev); |
1528 | fb_info(info, "%s frame buffer device at 0x%lx\n" , |
1529 | info->fix.id, (long)bar0_start); |
1530 | |
1531 | return 0; |
1532 | |
1533 | err2: |
1534 | fb_dealloc_cmap(cmap: &info->cmap); |
1535 | err1: |
1536 | if (mem_base) |
1537 | iounmap(addr: mem_base); |
1538 | release_mem_region(bar0_start, bar0_len); |
1539 | err0: |
1540 | framebuffer_release(info); |
1541 | return ret; |
1542 | } |
1543 | |
1544 | static void tgafb_unregister(struct device *dev) |
1545 | { |
1546 | resource_size_t bar0_start = 0, bar0_len = 0; |
1547 | int tga_bus_pci = dev_is_pci(dev); |
1548 | int tga_bus_tc = TGA_BUS_TC(dev); |
1549 | struct fb_info *info = NULL; |
1550 | struct tga_par *par; |
1551 | |
1552 | info = dev_get_drvdata(dev); |
1553 | if (!info) |
1554 | return; |
1555 | |
1556 | par = info->par; |
1557 | unregister_framebuffer(fb_info: info); |
1558 | fb_dealloc_cmap(cmap: &info->cmap); |
1559 | iounmap(addr: par->tga_mem_base); |
1560 | if (tga_bus_pci) { |
1561 | bar0_start = pci_resource_start(to_pci_dev(dev), 0); |
1562 | bar0_len = pci_resource_len(to_pci_dev(dev), 0); |
1563 | } |
1564 | if (tga_bus_tc) { |
1565 | bar0_start = to_tc_dev(dev)->resource.start; |
1566 | bar0_len = to_tc_dev(dev)->resource.end - bar0_start + 1; |
1567 | } |
1568 | release_mem_region(bar0_start, bar0_len); |
1569 | framebuffer_release(info); |
1570 | } |
1571 | |
1572 | static void tgafb_exit(void) |
1573 | { |
1574 | tc_unregister_driver(tdrv: &tgafb_tc_driver); |
1575 | pci_unregister_driver(dev: &tgafb_pci_driver); |
1576 | } |
1577 | |
1578 | #ifndef MODULE |
1579 | static int tgafb_setup(char *arg) |
1580 | { |
1581 | char *this_opt; |
1582 | |
1583 | if (arg && *arg) { |
1584 | while ((this_opt = strsep(&arg, "," ))) { |
1585 | if (!*this_opt) |
1586 | continue; |
1587 | if (!strncmp(this_opt, "mode:" , 5)) |
1588 | mode_option = this_opt+5; |
1589 | else |
1590 | printk(KERN_ERR |
1591 | "tgafb: unknown parameter %s\n" , |
1592 | this_opt); |
1593 | } |
1594 | } |
1595 | |
1596 | return 0; |
1597 | } |
1598 | #endif /* !MODULE */ |
1599 | |
1600 | static int tgafb_init(void) |
1601 | { |
1602 | int status; |
1603 | #ifndef MODULE |
1604 | char *option = NULL; |
1605 | #endif |
1606 | |
1607 | if (fb_modesetting_disabled(drvname: "tgafb" )) |
1608 | return -ENODEV; |
1609 | |
1610 | #ifndef MODULE |
1611 | if (fb_get_options(name: "tgafb" , option: &option)) |
1612 | return -ENODEV; |
1613 | tgafb_setup(arg: option); |
1614 | #endif |
1615 | status = pci_register_driver(&tgafb_pci_driver); |
1616 | if (!status) |
1617 | status = tc_register_driver(tdrv: &tgafb_tc_driver); |
1618 | return status; |
1619 | } |
1620 | |
1621 | /* |
1622 | * Modularisation |
1623 | */ |
1624 | |
1625 | module_init(tgafb_init); |
1626 | module_exit(tgafb_exit); |
1627 | |
1628 | MODULE_DESCRIPTION("Framebuffer driver for TGA/SFB+ chipset" ); |
1629 | MODULE_LICENSE("GPL" ); |
1630 | |