1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * |
4 | * Hardware accelerated Matrox Millennium I, II, Mystique, G100, G200, G400 and G450. |
5 | * |
6 | * (c) 1998-2002 Petr Vandrovec <vandrove@vc.cvut.cz> |
7 | * |
8 | * Portions Copyright (c) 2001 Matrox Graphics Inc. |
9 | * |
10 | * Version: 1.65 2002/08/14 |
11 | * |
12 | */ |
13 | |
14 | #include "matroxfb_maven.h" |
15 | #include "matroxfb_crtc2.h" |
16 | #include "matroxfb_misc.h" |
17 | #include "matroxfb_DAC1064.h" |
18 | #include <linux/matroxfb.h> |
19 | #include <linux/slab.h> |
20 | #include <linux/uaccess.h> |
21 | |
22 | /* **************************************************** */ |
23 | |
24 | static int mem = 8192; |
25 | |
26 | module_param(mem, int, 0); |
27 | MODULE_PARM_DESC(mem, "Memory size reserved for dualhead (default=8MB)" ); |
28 | |
29 | /* **************************************************** */ |
30 | |
31 | static int matroxfb_dh_setcolreg(unsigned regno, unsigned red, unsigned green, |
32 | unsigned blue, unsigned transp, struct fb_info* info) { |
33 | u_int32_t col; |
34 | #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
35 | |
36 | if (regno >= 16) |
37 | return 1; |
38 | if (m2info->fbcon.var.grayscale) { |
39 | /* gray = 0.30*R + 0.59*G + 0.11*B */ |
40 | red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; |
41 | } |
42 | red = CNVT_TOHW(red, m2info->fbcon.var.red.length); |
43 | green = CNVT_TOHW(green, m2info->fbcon.var.green.length); |
44 | blue = CNVT_TOHW(blue, m2info->fbcon.var.blue.length); |
45 | transp = CNVT_TOHW(transp, m2info->fbcon.var.transp.length); |
46 | |
47 | col = (red << m2info->fbcon.var.red.offset) | |
48 | (green << m2info->fbcon.var.green.offset) | |
49 | (blue << m2info->fbcon.var.blue.offset) | |
50 | (transp << m2info->fbcon.var.transp.offset); |
51 | |
52 | switch (m2info->fbcon.var.bits_per_pixel) { |
53 | case 16: |
54 | m2info->cmap[regno] = col | (col << 16); |
55 | break; |
56 | case 32: |
57 | m2info->cmap[regno] = col; |
58 | break; |
59 | } |
60 | return 0; |
61 | #undef m2info |
62 | } |
63 | |
64 | static void matroxfb_dh_restore(struct matroxfb_dh_fb_info* m2info, |
65 | struct my_timming* mt, |
66 | int mode, |
67 | unsigned int pos) { |
68 | u_int32_t tmp; |
69 | u_int32_t datactl; |
70 | struct matrox_fb_info *minfo = m2info->primary_dev; |
71 | |
72 | switch (mode) { |
73 | case 15: |
74 | tmp = 0x00200000; |
75 | break; |
76 | case 16: |
77 | tmp = 0x00400000; |
78 | break; |
79 | /* case 32: */ |
80 | default: |
81 | tmp = 0x00800000; |
82 | break; |
83 | } |
84 | tmp |= 0x00000001; /* enable CRTC2 */ |
85 | datactl = 0; |
86 | if (minfo->outputs[1].src == MATROXFB_SRC_CRTC2) { |
87 | if (minfo->devflags.g450dac) { |
88 | tmp |= 0x00000006; /* source from secondary pixel PLL */ |
89 | /* no vidrst when in monitor mode */ |
90 | if (minfo->outputs[1].mode != MATROXFB_OUTPUT_MODE_MONITOR) { |
91 | tmp |= 0xC0001000; /* Enable H/V vidrst */ |
92 | } |
93 | } else { |
94 | tmp |= 0x00000002; /* source from VDOCLK */ |
95 | tmp |= 0xC0000000; /* enable vvidrst & hvidrst */ |
96 | /* MGA TVO is our clock source */ |
97 | } |
98 | } else if (minfo->outputs[0].src == MATROXFB_SRC_CRTC2) { |
99 | tmp |= 0x00000004; /* source from pixclock */ |
100 | /* PIXPLL is our clock source */ |
101 | } |
102 | if (minfo->outputs[0].src == MATROXFB_SRC_CRTC2) { |
103 | tmp |= 0x00100000; /* connect CRTC2 to DAC */ |
104 | } |
105 | if (mt->interlaced) { |
106 | tmp |= 0x02000000; /* interlaced, second field is bigger, as G450 apparently ignores it */ |
107 | mt->VDisplay >>= 1; |
108 | mt->VSyncStart >>= 1; |
109 | mt->VSyncEnd >>= 1; |
110 | mt->VTotal >>= 1; |
111 | } |
112 | if ((mt->HTotal & 7) == 2) { |
113 | datactl |= 0x00000010; |
114 | mt->HTotal &= ~7; |
115 | } |
116 | tmp |= 0x10000000; /* 0x10000000 is VIDRST polarity */ |
117 | mga_outl(0x3C14, ((mt->HDisplay - 8) << 16) | (mt->HTotal - 8)); |
118 | mga_outl(0x3C18, ((mt->HSyncEnd - 8) << 16) | (mt->HSyncStart - 8)); |
119 | mga_outl(0x3C1C, ((mt->VDisplay - 1) << 16) | (mt->VTotal - 1)); |
120 | mga_outl(0x3C20, ((mt->VSyncEnd - 1) << 16) | (mt->VSyncStart - 1)); |
121 | mga_outl(0x3C24, ((mt->VSyncStart) << 16) | (mt->HSyncStart)); /* preload */ |
122 | { |
123 | u_int32_t linelen = m2info->fbcon.var.xres_virtual * (m2info->fbcon.var.bits_per_pixel >> 3); |
124 | if (tmp & 0x02000000) { |
125 | /* field #0 is smaller, so... */ |
126 | mga_outl(0x3C2C, pos); /* field #1 vmemory start */ |
127 | mga_outl(0x3C28, pos + linelen); /* field #0 vmemory start */ |
128 | linelen <<= 1; |
129 | m2info->interlaced = 1; |
130 | } else { |
131 | mga_outl(0x3C28, pos); /* vmemory start */ |
132 | m2info->interlaced = 0; |
133 | } |
134 | mga_outl(0x3C40, linelen); |
135 | } |
136 | mga_outl(0x3C4C, datactl); /* data control */ |
137 | if (tmp & 0x02000000) { |
138 | int i; |
139 | |
140 | mga_outl(0x3C10, tmp & ~0x02000000); |
141 | for (i = 0; i < 2; i++) { |
142 | unsigned int nl; |
143 | unsigned int lastl = 0; |
144 | |
145 | while ((nl = mga_inl(0x3C48) & 0xFFF) >= lastl) { |
146 | lastl = nl; |
147 | } |
148 | } |
149 | } |
150 | mga_outl(0x3C10, tmp); |
151 | minfo->hw.crtc2.ctl = tmp; |
152 | |
153 | tmp = mt->VDisplay << 16; /* line compare */ |
154 | if (mt->sync & FB_SYNC_HOR_HIGH_ACT) |
155 | tmp |= 0x00000100; |
156 | if (mt->sync & FB_SYNC_VERT_HIGH_ACT) |
157 | tmp |= 0x00000200; |
158 | mga_outl(0x3C44, tmp); |
159 | } |
160 | |
161 | static void matroxfb_dh_disable(struct matroxfb_dh_fb_info* m2info) { |
162 | struct matrox_fb_info *minfo = m2info->primary_dev; |
163 | |
164 | mga_outl(0x3C10, 0x00000004); /* disable CRTC2, CRTC1->DAC1, PLL as clock source */ |
165 | minfo->hw.crtc2.ctl = 0x00000004; |
166 | } |
167 | |
168 | static void matroxfb_dh_pan_var(struct matroxfb_dh_fb_info* m2info, |
169 | struct fb_var_screeninfo* var) { |
170 | unsigned int pos; |
171 | unsigned int linelen; |
172 | unsigned int pixelsize; |
173 | struct matrox_fb_info *minfo = m2info->primary_dev; |
174 | |
175 | m2info->fbcon.var.xoffset = var->xoffset; |
176 | m2info->fbcon.var.yoffset = var->yoffset; |
177 | pixelsize = m2info->fbcon.var.bits_per_pixel >> 3; |
178 | linelen = m2info->fbcon.var.xres_virtual * pixelsize; |
179 | pos = m2info->fbcon.var.yoffset * linelen + m2info->fbcon.var.xoffset * pixelsize; |
180 | pos += m2info->video.offbase; |
181 | if (m2info->interlaced) { |
182 | mga_outl(0x3C2C, pos); |
183 | mga_outl(0x3C28, pos + linelen); |
184 | } else { |
185 | mga_outl(0x3C28, pos); |
186 | } |
187 | } |
188 | |
189 | static int matroxfb_dh_decode_var(struct matroxfb_dh_fb_info* m2info, |
190 | struct fb_var_screeninfo* var, |
191 | int *visual, |
192 | int *video_cmap_len, |
193 | int *mode) { |
194 | unsigned int mask; |
195 | unsigned int memlen; |
196 | unsigned int vramlen; |
197 | |
198 | switch (var->bits_per_pixel) { |
199 | case 16: mask = 0x1F; |
200 | break; |
201 | case 32: mask = 0x0F; |
202 | break; |
203 | default: return -EINVAL; |
204 | } |
205 | vramlen = m2info->video.len_usable; |
206 | if (var->yres_virtual < var->yres) |
207 | var->yres_virtual = var->yres; |
208 | if (var->xres_virtual < var->xres) |
209 | var->xres_virtual = var->xres; |
210 | var->xres_virtual = (var->xres_virtual + mask) & ~mask; |
211 | if (var->yres_virtual > 32767) |
212 | return -EINVAL; |
213 | memlen = var->xres_virtual * var->yres_virtual * (var->bits_per_pixel >> 3); |
214 | if (memlen > vramlen) |
215 | return -EINVAL; |
216 | if (var->xoffset + var->xres > var->xres_virtual) |
217 | var->xoffset = var->xres_virtual - var->xres; |
218 | if (var->yoffset + var->yres > var->yres_virtual) |
219 | var->yoffset = var->yres_virtual - var->yres; |
220 | |
221 | var->xres &= ~7; |
222 | var->left_margin &= ~7; |
223 | var->right_margin &= ~7; |
224 | var->hsync_len &= ~7; |
225 | |
226 | *mode = var->bits_per_pixel; |
227 | if (var->bits_per_pixel == 16) { |
228 | if (var->green.length == 5) { |
229 | var->red.offset = 10; |
230 | var->red.length = 5; |
231 | var->green.offset = 5; |
232 | var->green.length = 5; |
233 | var->blue.offset = 0; |
234 | var->blue.length = 5; |
235 | var->transp.offset = 15; |
236 | var->transp.length = 1; |
237 | *mode = 15; |
238 | } else { |
239 | var->red.offset = 11; |
240 | var->red.length = 5; |
241 | var->green.offset = 5; |
242 | var->green.length = 6; |
243 | var->blue.offset = 0; |
244 | var->blue.length = 5; |
245 | var->transp.offset = 0; |
246 | var->transp.length = 0; |
247 | } |
248 | } else { |
249 | var->red.offset = 16; |
250 | var->red.length = 8; |
251 | var->green.offset = 8; |
252 | var->green.length = 8; |
253 | var->blue.offset = 0; |
254 | var->blue.length = 8; |
255 | var->transp.offset = 24; |
256 | var->transp.length = 8; |
257 | } |
258 | *visual = FB_VISUAL_TRUECOLOR; |
259 | *video_cmap_len = 16; |
260 | return 0; |
261 | } |
262 | |
263 | static int matroxfb_dh_open(struct fb_info* info, int user) { |
264 | #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
265 | struct matrox_fb_info *minfo = m2info->primary_dev; |
266 | |
267 | if (minfo) { |
268 | int err; |
269 | |
270 | if (minfo->dead) { |
271 | return -ENXIO; |
272 | } |
273 | err = minfo->fbops.fb_open(&minfo->fbcon, user); |
274 | if (err) { |
275 | return err; |
276 | } |
277 | } |
278 | return 0; |
279 | #undef m2info |
280 | } |
281 | |
282 | static int matroxfb_dh_release(struct fb_info* info, int user) { |
283 | #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
284 | int err = 0; |
285 | struct matrox_fb_info *minfo = m2info->primary_dev; |
286 | |
287 | if (minfo) { |
288 | err = minfo->fbops.fb_release(&minfo->fbcon, user); |
289 | } |
290 | return err; |
291 | #undef m2info |
292 | } |
293 | |
294 | /* |
295 | * This function is called before the register_framebuffer so |
296 | * no locking is needed. |
297 | */ |
298 | static void matroxfb_dh_init_fix(struct matroxfb_dh_fb_info *m2info) |
299 | { |
300 | struct fb_fix_screeninfo *fix = &m2info->fbcon.fix; |
301 | |
302 | strcpy(p: fix->id, q: "MATROX DH" ); |
303 | |
304 | fix->smem_start = m2info->video.base; |
305 | fix->smem_len = m2info->video.len_usable; |
306 | fix->ypanstep = 1; |
307 | fix->ywrapstep = 0; |
308 | fix->xpanstep = 8; /* TBD */ |
309 | fix->mmio_start = m2info->mmio.base; |
310 | fix->mmio_len = m2info->mmio.len; |
311 | fix->accel = 0; /* no accel... */ |
312 | } |
313 | |
314 | static int matroxfb_dh_check_var(struct fb_var_screeninfo* var, struct fb_info* info) { |
315 | #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
316 | int visual; |
317 | int cmap_len; |
318 | int mode; |
319 | |
320 | return matroxfb_dh_decode_var(m2info, var, visual: &visual, video_cmap_len: &cmap_len, mode: &mode); |
321 | #undef m2info |
322 | } |
323 | |
324 | static int matroxfb_dh_set_par(struct fb_info* info) { |
325 | #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
326 | int visual; |
327 | int cmap_len; |
328 | int mode; |
329 | int err; |
330 | struct fb_var_screeninfo* var = &info->var; |
331 | struct matrox_fb_info *minfo = m2info->primary_dev; |
332 | |
333 | if ((err = matroxfb_dh_decode_var(m2info, var, visual: &visual, video_cmap_len: &cmap_len, mode: &mode)) != 0) |
334 | return err; |
335 | /* cmap */ |
336 | { |
337 | m2info->fbcon.screen_base = vaddr_va(m2info->video.vbase); |
338 | m2info->fbcon.fix.visual = visual; |
339 | m2info->fbcon.fix.type = FB_TYPE_PACKED_PIXELS; |
340 | m2info->fbcon.fix.type_aux = 0; |
341 | m2info->fbcon.fix.line_length = (var->xres_virtual * var->bits_per_pixel) >> 3; |
342 | } |
343 | { |
344 | struct my_timming mt; |
345 | unsigned int pos; |
346 | int out; |
347 | int cnt; |
348 | |
349 | matroxfb_var2my(fvsi: &m2info->fbcon.var, mt: &mt); |
350 | mt.crtc = MATROXFB_SRC_CRTC2; |
351 | /* CRTC2 delay */ |
352 | mt.delay = 34; |
353 | |
354 | pos = (m2info->fbcon.var.yoffset * m2info->fbcon.var.xres_virtual + m2info->fbcon.var.xoffset) * m2info->fbcon.var.bits_per_pixel >> 3; |
355 | pos += m2info->video.offbase; |
356 | cnt = 0; |
357 | down_read(sem: &minfo->altout.lock); |
358 | for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { |
359 | if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2) { |
360 | cnt++; |
361 | if (minfo->outputs[out].output->compute) { |
362 | minfo->outputs[out].output->compute(minfo->outputs[out].data, &mt); |
363 | } |
364 | } |
365 | } |
366 | minfo->crtc2.pixclock = mt.pixclock; |
367 | minfo->crtc2.mnp = mt.mnp; |
368 | up_read(sem: &minfo->altout.lock); |
369 | if (cnt) { |
370 | matroxfb_dh_restore(m2info, mt: &mt, mode, pos); |
371 | } else { |
372 | matroxfb_dh_disable(m2info); |
373 | } |
374 | DAC1064_global_init(minfo); |
375 | DAC1064_global_restore(minfo); |
376 | down_read(sem: &minfo->altout.lock); |
377 | for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { |
378 | if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2 && |
379 | minfo->outputs[out].output->program) { |
380 | minfo->outputs[out].output->program(minfo->outputs[out].data); |
381 | } |
382 | } |
383 | for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { |
384 | if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2 && |
385 | minfo->outputs[out].output->start) { |
386 | minfo->outputs[out].output->start(minfo->outputs[out].data); |
387 | } |
388 | } |
389 | up_read(sem: &minfo->altout.lock); |
390 | } |
391 | m2info->initialized = 1; |
392 | return 0; |
393 | #undef m2info |
394 | } |
395 | |
396 | static int matroxfb_dh_pan_display(struct fb_var_screeninfo* var, struct fb_info* info) { |
397 | #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
398 | matroxfb_dh_pan_var(m2info, var); |
399 | return 0; |
400 | #undef m2info |
401 | } |
402 | |
403 | static int matroxfb_dh_get_vblank(const struct matroxfb_dh_fb_info* m2info, struct fb_vblank* vblank) { |
404 | struct matrox_fb_info *minfo = m2info->primary_dev; |
405 | |
406 | matroxfb_enable_irq(minfo, reenable: 0); |
407 | memset(vblank, 0, sizeof(*vblank)); |
408 | vblank->flags = FB_VBLANK_HAVE_VCOUNT | FB_VBLANK_HAVE_VBLANK; |
409 | /* mask out reserved bits + field number (odd/even) */ |
410 | vblank->vcount = mga_inl(0x3C48) & 0x000007FF; |
411 | /* compatibility stuff */ |
412 | if (vblank->vcount >= m2info->fbcon.var.yres) |
413 | vblank->flags |= FB_VBLANK_VBLANKING; |
414 | if (test_bit(0, &minfo->irq_flags)) { |
415 | vblank->flags |= FB_VBLANK_HAVE_COUNT; |
416 | /* Only one writer, aligned int value... |
417 | it should work without lock and without atomic_t */ |
418 | vblank->count = minfo->crtc2.vsync.cnt; |
419 | } |
420 | return 0; |
421 | } |
422 | |
423 | static int matroxfb_dh_ioctl(struct fb_info *info, |
424 | unsigned int cmd, |
425 | unsigned long arg) |
426 | { |
427 | #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
428 | struct matrox_fb_info *minfo = m2info->primary_dev; |
429 | |
430 | DBG(__func__) |
431 | |
432 | switch (cmd) { |
433 | case FBIOGET_VBLANK: |
434 | { |
435 | struct fb_vblank vblank; |
436 | int err; |
437 | |
438 | err = matroxfb_dh_get_vblank(m2info, vblank: &vblank); |
439 | if (err) |
440 | return err; |
441 | if (copy_to_user(to: (void __user *)arg, from: &vblank, n: sizeof(vblank))) |
442 | return -EFAULT; |
443 | return 0; |
444 | } |
445 | case FBIO_WAITFORVSYNC: |
446 | { |
447 | u_int32_t crt; |
448 | |
449 | if (get_user(crt, (u_int32_t __user *)arg)) |
450 | return -EFAULT; |
451 | |
452 | if (crt != 0) |
453 | return -ENODEV; |
454 | return matroxfb_wait_for_sync(minfo, crtc: 1); |
455 | } |
456 | case MATROXFB_SET_OUTPUT_MODE: |
457 | case MATROXFB_GET_OUTPUT_MODE: |
458 | case MATROXFB_GET_ALL_OUTPUTS: |
459 | { |
460 | return minfo->fbcon.fbops->fb_ioctl(&minfo->fbcon, cmd, arg); |
461 | } |
462 | case MATROXFB_SET_OUTPUT_CONNECTION: |
463 | { |
464 | u_int32_t tmp; |
465 | int out; |
466 | int changes; |
467 | |
468 | if (get_user(tmp, (u_int32_t __user *)arg)) |
469 | return -EFAULT; |
470 | for (out = 0; out < 32; out++) { |
471 | if (tmp & (1 << out)) { |
472 | if (out >= MATROXFB_MAX_OUTPUTS) |
473 | return -ENXIO; |
474 | if (!minfo->outputs[out].output) |
475 | return -ENXIO; |
476 | switch (minfo->outputs[out].src) { |
477 | case MATROXFB_SRC_NONE: |
478 | case MATROXFB_SRC_CRTC2: |
479 | break; |
480 | default: |
481 | return -EBUSY; |
482 | } |
483 | } |
484 | } |
485 | if (minfo->devflags.panellink) { |
486 | if (tmp & MATROXFB_OUTPUT_CONN_DFP) |
487 | return -EINVAL; |
488 | if ((minfo->outputs[2].src == MATROXFB_SRC_CRTC1) && tmp) |
489 | return -EBUSY; |
490 | } |
491 | changes = 0; |
492 | for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { |
493 | if (tmp & (1 << out)) { |
494 | if (minfo->outputs[out].src != MATROXFB_SRC_CRTC2) { |
495 | changes = 1; |
496 | minfo->outputs[out].src = MATROXFB_SRC_CRTC2; |
497 | } |
498 | } else if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2) { |
499 | changes = 1; |
500 | minfo->outputs[out].src = MATROXFB_SRC_NONE; |
501 | } |
502 | } |
503 | if (!changes) |
504 | return 0; |
505 | matroxfb_dh_set_par(info); |
506 | return 0; |
507 | } |
508 | case MATROXFB_GET_OUTPUT_CONNECTION: |
509 | { |
510 | u_int32_t conn = 0; |
511 | int out; |
512 | |
513 | for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { |
514 | if (minfo->outputs[out].src == MATROXFB_SRC_CRTC2) { |
515 | conn |= 1 << out; |
516 | } |
517 | } |
518 | if (put_user(conn, (u_int32_t __user *)arg)) |
519 | return -EFAULT; |
520 | return 0; |
521 | } |
522 | case MATROXFB_GET_AVAILABLE_OUTPUTS: |
523 | { |
524 | u_int32_t tmp = 0; |
525 | int out; |
526 | |
527 | for (out = 0; out < MATROXFB_MAX_OUTPUTS; out++) { |
528 | if (minfo->outputs[out].output) { |
529 | switch (minfo->outputs[out].src) { |
530 | case MATROXFB_SRC_NONE: |
531 | case MATROXFB_SRC_CRTC2: |
532 | tmp |= 1 << out; |
533 | break; |
534 | } |
535 | } |
536 | } |
537 | if (minfo->devflags.panellink) { |
538 | tmp &= ~MATROXFB_OUTPUT_CONN_DFP; |
539 | if (minfo->outputs[2].src == MATROXFB_SRC_CRTC1) { |
540 | tmp = 0; |
541 | } |
542 | } |
543 | if (put_user(tmp, (u_int32_t __user *)arg)) |
544 | return -EFAULT; |
545 | return 0; |
546 | } |
547 | } |
548 | return -ENOTTY; |
549 | #undef m2info |
550 | } |
551 | |
552 | static int matroxfb_dh_blank(int blank, struct fb_info* info) { |
553 | #define m2info (container_of(info, struct matroxfb_dh_fb_info, fbcon)) |
554 | switch (blank) { |
555 | case 1: |
556 | case 2: |
557 | case 3: |
558 | case 4: |
559 | default:; |
560 | } |
561 | /* do something... */ |
562 | return 0; |
563 | #undef m2info |
564 | } |
565 | |
566 | static const struct fb_ops matroxfb_dh_ops = { |
567 | .owner = THIS_MODULE, |
568 | .fb_open = matroxfb_dh_open, |
569 | .fb_release = matroxfb_dh_release, |
570 | FB_DEFAULT_IOMEM_OPS, |
571 | .fb_check_var = matroxfb_dh_check_var, |
572 | .fb_set_par = matroxfb_dh_set_par, |
573 | .fb_setcolreg = matroxfb_dh_setcolreg, |
574 | .fb_pan_display =matroxfb_dh_pan_display, |
575 | .fb_blank = matroxfb_dh_blank, |
576 | .fb_ioctl = matroxfb_dh_ioctl, |
577 | }; |
578 | |
579 | static struct fb_var_screeninfo matroxfb_dh_defined = { |
580 | 640,480,640,480,/* W,H, virtual W,H */ |
581 | 0,0, /* offset */ |
582 | 32, /* depth */ |
583 | 0, /* gray */ |
584 | {0,0,0}, /* R */ |
585 | {0,0,0}, /* G */ |
586 | {0,0,0}, /* B */ |
587 | {0,0,0}, /* alpha */ |
588 | 0, /* nonstd */ |
589 | FB_ACTIVATE_NOW, |
590 | -1,-1, /* display size */ |
591 | 0, /* accel flags */ |
592 | 39721L,48L,16L,33L,10L, |
593 | 96L,2,0, /* no sync info */ |
594 | FB_VMODE_NONINTERLACED, |
595 | }; |
596 | |
597 | static int matroxfb_dh_regit(const struct matrox_fb_info *minfo, |
598 | struct matroxfb_dh_fb_info *m2info) |
599 | { |
600 | #define minfo (m2info->primary_dev) |
601 | void* oldcrtc2; |
602 | |
603 | m2info->fbcon.fbops = &matroxfb_dh_ops; |
604 | m2info->fbcon.flags = FBINFO_HWACCEL_XPAN | |
605 | FBINFO_HWACCEL_YPAN; |
606 | m2info->fbcon.pseudo_palette = m2info->cmap; |
607 | fb_alloc_cmap(cmap: &m2info->fbcon.cmap, len: 256, transp: 1); |
608 | |
609 | if (mem < 64) |
610 | mem *= 1024; |
611 | if (mem < 64*1024) |
612 | mem *= 1024; |
613 | mem &= ~0x00000FFF; /* PAGE_MASK? */ |
614 | if (minfo->video.len_usable + mem <= minfo->video.len) |
615 | m2info->video.offbase = minfo->video.len - mem; |
616 | else if (minfo->video.len < mem) { |
617 | return -ENOMEM; |
618 | } else { /* check yres on first head... */ |
619 | m2info->video.borrowed = mem; |
620 | minfo->video.len_usable -= mem; |
621 | m2info->video.offbase = minfo->video.len_usable; |
622 | } |
623 | m2info->video.base = minfo->video.base + m2info->video.offbase; |
624 | m2info->video.len = m2info->video.len_usable = m2info->video.len_maximum = mem; |
625 | m2info->video.vbase.vaddr = vaddr_va(minfo->video.vbase) + m2info->video.offbase; |
626 | m2info->mmio.base = minfo->mmio.base; |
627 | m2info->mmio.vbase = minfo->mmio.vbase; |
628 | m2info->mmio.len = minfo->mmio.len; |
629 | |
630 | matroxfb_dh_init_fix(m2info); |
631 | if (register_framebuffer(fb_info: &m2info->fbcon)) { |
632 | return -ENXIO; |
633 | } |
634 | if (!m2info->initialized) |
635 | fb_set_var(info: &m2info->fbcon, var: &matroxfb_dh_defined); |
636 | down_write(sem: &minfo->crtc2.lock); |
637 | oldcrtc2 = minfo->crtc2.info; |
638 | minfo->crtc2.info = m2info; |
639 | up_write(sem: &minfo->crtc2.lock); |
640 | if (oldcrtc2) { |
641 | printk(KERN_ERR "matroxfb_crtc2: Internal consistency check failed: crtc2 already present: %p\n" , |
642 | oldcrtc2); |
643 | } |
644 | return 0; |
645 | #undef minfo |
646 | } |
647 | |
648 | /* ************************** */ |
649 | |
650 | static int matroxfb_dh_registerfb(struct matroxfb_dh_fb_info* m2info) { |
651 | #define minfo (m2info->primary_dev) |
652 | if (matroxfb_dh_regit(minfo, m2info)) { |
653 | printk(KERN_ERR "matroxfb_crtc2: secondary head failed to register\n" ); |
654 | return -1; |
655 | } |
656 | printk(KERN_INFO "matroxfb_crtc2: secondary head of fb%u was registered as fb%u\n" , |
657 | minfo->fbcon.node, m2info->fbcon.node); |
658 | m2info->fbcon_registered = 1; |
659 | return 0; |
660 | #undef minfo |
661 | } |
662 | |
663 | static void matroxfb_dh_deregisterfb(struct matroxfb_dh_fb_info* m2info) { |
664 | #define minfo (m2info->primary_dev) |
665 | if (m2info->fbcon_registered) { |
666 | int id; |
667 | struct matroxfb_dh_fb_info* crtc2; |
668 | |
669 | down_write(sem: &minfo->crtc2.lock); |
670 | crtc2 = minfo->crtc2.info; |
671 | if (crtc2 == m2info) |
672 | minfo->crtc2.info = NULL; |
673 | up_write(sem: &minfo->crtc2.lock); |
674 | if (crtc2 != m2info) { |
675 | printk(KERN_ERR "matroxfb_crtc2: Internal consistency check failed: crtc2 mismatch at unload: %p != %p\n" , |
676 | crtc2, m2info); |
677 | printk(KERN_ERR "matroxfb_crtc2: Expect kernel crash after module unload.\n" ); |
678 | return; |
679 | } |
680 | id = m2info->fbcon.node; |
681 | unregister_framebuffer(fb_info: &m2info->fbcon); |
682 | /* return memory back to primary head */ |
683 | minfo->video.len_usable += m2info->video.borrowed; |
684 | printk(KERN_INFO "matroxfb_crtc2: fb%u unregistered\n" , id); |
685 | m2info->fbcon_registered = 0; |
686 | } |
687 | #undef minfo |
688 | } |
689 | |
690 | static void* matroxfb_crtc2_probe(struct matrox_fb_info* minfo) { |
691 | struct matroxfb_dh_fb_info* m2info; |
692 | |
693 | /* hardware is CRTC2 incapable... */ |
694 | if (!minfo->devflags.crtc2) |
695 | return NULL; |
696 | m2info = kzalloc(size: sizeof(*m2info), GFP_KERNEL); |
697 | if (!m2info) |
698 | return NULL; |
699 | |
700 | m2info->primary_dev = minfo; |
701 | if (matroxfb_dh_registerfb(m2info)) { |
702 | kfree(objp: m2info); |
703 | printk(KERN_ERR "matroxfb_crtc2: CRTC2 framebuffer failed to register\n" ); |
704 | return NULL; |
705 | } |
706 | return m2info; |
707 | } |
708 | |
709 | static void matroxfb_crtc2_remove(struct matrox_fb_info* minfo, void* crtc2) { |
710 | matroxfb_dh_deregisterfb(m2info: crtc2); |
711 | kfree(objp: crtc2); |
712 | } |
713 | |
714 | static struct matroxfb_driver crtc2 = { |
715 | .name = "Matrox G400 CRTC2" , |
716 | .probe = matroxfb_crtc2_probe, |
717 | .remove = matroxfb_crtc2_remove }; |
718 | |
719 | static int matroxfb_crtc2_init(void) { |
720 | if (fb_get_options(name: "matrox_crtc2fb" , NULL)) |
721 | return -ENODEV; |
722 | |
723 | matroxfb_register_driver(drv: &crtc2); |
724 | return 0; |
725 | } |
726 | |
727 | static void matroxfb_crtc2_exit(void) { |
728 | matroxfb_unregister_driver(drv: &crtc2); |
729 | } |
730 | |
731 | MODULE_AUTHOR("(c) 1999-2002 Petr Vandrovec <vandrove@vc.cvut.cz>" ); |
732 | MODULE_DESCRIPTION("Matrox G400 CRTC2 driver" ); |
733 | MODULE_LICENSE("GPL" ); |
734 | module_init(matroxfb_crtc2_init); |
735 | module_exit(matroxfb_crtc2_exit); |
736 | /* we do not have __setup() yet */ |
737 | |