Commit | Line | Data |
---|---|---|
dbe7e429 AH |
1 | /* |
2 | * Copyright (c) Intel Corp. 2007. | |
3 | * All Rights Reserved. | |
4 | * | |
5 | * Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to | |
6 | * develop this driver. | |
7 | * | |
8 | * This file is part of the Vermilion Range fb driver. | |
9 | * The Vermilion Range fb driver is free software; | |
10 | * you can redistribute it and/or modify | |
11 | * it under the terms of the GNU General Public License as published by | |
12 | * the Free Software Foundation; either version 2 of the License, or | |
13 | * (at your option) any later version. | |
14 | * | |
15 | * The Vermilion Range fb driver is distributed | |
16 | * in the hope that it will be useful, | |
17 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
19 | * GNU General Public License for more details. | |
20 | * | |
21 | * You should have received a copy of the GNU General Public License | |
22 | * along with this driver; if not, write to the Free Software | |
23 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
24 | * | |
25 | * Authors: | |
db955170 MG |
26 | * Thomas Hellström <thomas-at-tungstengraphics-dot-com> |
27 | * Michel Dänzer <michel-at-tungstengraphics-dot-com> | |
dbe7e429 AH |
28 | * Alan Hourihane <alanh-at-tungstengraphics-dot-com> |
29 | */ | |
30 | ||
31 | #include <linux/module.h> | |
32 | #include <linux/kernel.h> | |
33 | #include <linux/errno.h> | |
34 | #include <linux/string.h> | |
35 | #include <linux/delay.h> | |
5a0e3ad6 | 36 | #include <linux/slab.h> |
dbe7e429 AH |
37 | #include <linux/mm.h> |
38 | #include <linux/fb.h> | |
39 | #include <linux/pci.h> | |
40 | #include <asm/cacheflush.h> | |
41 | #include <asm/tlbflush.h> | |
42 | #include <linux/mmzone.h> | |
dbe7e429 AH |
43 | |
44 | /* #define VERMILION_DEBUG */ | |
45 | ||
46 | #include "vermilion.h" | |
47 | ||
48 | #define MODULE_NAME "vmlfb" | |
49 | ||
50 | #define VML_TOHW(_val, _width) ((((_val) << (_width)) + 0x7FFF - (_val)) >> 16) | |
51 | ||
52 | static struct mutex vml_mutex; | |
53 | static struct list_head global_no_mode; | |
54 | static struct list_head global_has_mode; | |
55 | static struct fb_ops vmlfb_ops; | |
56 | static struct vml_sys *subsys = NULL; | |
57 | static char *vml_default_mode = "1024x768@60"; | |
58 | static struct fb_videomode defaultmode = { | |
59 | NULL, 60, 1024, 768, 12896, 144, 24, 29, 3, 136, 6, | |
60 | 0, FB_VMODE_NONINTERLACED | |
61 | }; | |
62 | ||
63 | static u32 vml_mem_requested = (10 * 1024 * 1024); | |
64 | static u32 vml_mem_contig = (4 * 1024 * 1024); | |
65 | static u32 vml_mem_min = (4 * 1024 * 1024); | |
66 | ||
67 | static u32 vml_clocks[] = { | |
68 | 6750, | |
69 | 13500, | |
70 | 27000, | |
71 | 29700, | |
72 | 37125, | |
73 | 54000, | |
74 | 59400, | |
75 | 74250, | |
76 | 120000, | |
77 | 148500 | |
78 | }; | |
79 | ||
80 | static u32 vml_num_clocks = ARRAY_SIZE(vml_clocks); | |
81 | ||
82 | /* | |
83 | * Allocate a contiguous vram area and make its linear kernel map | |
84 | * uncached. | |
85 | */ | |
86 | ||
87 | static int vmlfb_alloc_vram_area(struct vram_area *va, unsigned max_order, | |
88 | unsigned min_order) | |
89 | { | |
90 | gfp_t flags; | |
91 | unsigned long i; | |
dbe7e429 | 92 | |
dbe7e429 AH |
93 | max_order++; |
94 | do { | |
95 | /* | |
96 | * Really try hard to get the needed memory. | |
97 | * We need memory below the first 32MB, so we | |
98 | * add the __GFP_DMA flag that guarantees that we are | |
99 | * below the first 16MB. | |
100 | */ | |
101 | ||
102 | flags = __GFP_DMA | __GFP_HIGH; | |
103 | va->logical = | |
104 | __get_free_pages(flags, --max_order); | |
105 | } while (va->logical == 0 && max_order > min_order); | |
106 | ||
107 | if (!va->logical) | |
108 | return -ENOMEM; | |
109 | ||
110 | va->phys = virt_to_phys((void *)va->logical); | |
111 | va->size = PAGE_SIZE << max_order; | |
112 | va->order = max_order; | |
113 | ||
114 | /* | |
115 | * It seems like __get_free_pages only ups the usage count | |
3c18ddd1 NP |
116 | * of the first page. This doesn't work with fault mapping, so |
117 | * up the usage count once more (XXX: should use split_page or | |
118 | * compound page). | |
dbe7e429 AH |
119 | */ |
120 | ||
121 | memset((void *)va->logical, 0x00, va->size); | |
122 | for (i = va->logical; i < va->logical + va->size; i += PAGE_SIZE) { | |
123 | get_page(virt_to_page(i)); | |
124 | } | |
125 | ||
126 | /* | |
127 | * Change caching policy of the linear kernel map to avoid | |
128 | * mapping type conflicts with user-space mappings. | |
dbe7e429 | 129 | */ |
6d238cc4 | 130 | set_pages_uc(virt_to_page(va->logical), va->size >> PAGE_SHIFT); |
dbe7e429 AH |
131 | |
132 | printk(KERN_DEBUG MODULE_NAME | |
133 | ": Allocated %ld bytes vram area at 0x%08lx\n", | |
134 | va->size, va->phys); | |
135 | ||
136 | return 0; | |
137 | } | |
138 | ||
139 | /* | |
140 | * Free a contiguous vram area and reset its linear kernel map | |
141 | * mapping type. | |
142 | */ | |
143 | ||
144 | static void vmlfb_free_vram_area(struct vram_area *va) | |
145 | { | |
146 | unsigned long j; | |
147 | ||
148 | if (va->logical) { | |
149 | ||
150 | /* | |
151 | * Reset the linear kernel map caching policy. | |
152 | */ | |
153 | ||
6d238cc4 AV |
154 | set_pages_wb(virt_to_page(va->logical), |
155 | va->size >> PAGE_SHIFT); | |
dbe7e429 AH |
156 | |
157 | /* | |
158 | * Decrease the usage count on the pages we've used | |
159 | * to compensate for upping when allocating. | |
160 | */ | |
161 | ||
162 | for (j = va->logical; j < va->logical + va->size; | |
163 | j += PAGE_SIZE) { | |
164 | (void)put_page_testzero(virt_to_page(j)); | |
165 | } | |
166 | ||
167 | printk(KERN_DEBUG MODULE_NAME | |
168 | ": Freeing %ld bytes vram area at 0x%08lx\n", | |
169 | va->size, va->phys); | |
170 | free_pages(va->logical, va->order); | |
171 | ||
172 | va->logical = 0; | |
173 | } | |
174 | } | |
175 | ||
176 | /* | |
177 | * Free allocated vram. | |
178 | */ | |
179 | ||
180 | static void vmlfb_free_vram(struct vml_info *vinfo) | |
181 | { | |
182 | int i; | |
183 | ||
184 | for (i = 0; i < vinfo->num_areas; ++i) { | |
185 | vmlfb_free_vram_area(&vinfo->vram[i]); | |
186 | } | |
187 | vinfo->num_areas = 0; | |
188 | } | |
189 | ||
190 | /* | |
191 | * Allocate vram. Currently we try to allocate contiguous areas from the | |
192 | * __GFP_DMA zone and puzzle them together. A better approach would be to | |
193 | * allocate one contiguous area for scanout and use one-page allocations for | |
194 | * offscreen areas. This requires user-space and GPU virtual mappings. | |
195 | */ | |
196 | ||
197 | static int vmlfb_alloc_vram(struct vml_info *vinfo, | |
198 | size_t requested, | |
199 | size_t min_total, size_t min_contig) | |
200 | { | |
201 | int i, j; | |
202 | int order; | |
203 | int contiguous; | |
204 | int err; | |
205 | struct vram_area *va; | |
206 | struct vram_area *va2; | |
207 | ||
208 | vinfo->num_areas = 0; | |
209 | for (i = 0; i < VML_VRAM_AREAS; ++i) { | |
210 | va = &vinfo->vram[i]; | |
211 | order = 0; | |
212 | ||
213 | while (requested > (PAGE_SIZE << order) && order < MAX_ORDER) | |
214 | order++; | |
215 | ||
216 | err = vmlfb_alloc_vram_area(va, order, 0); | |
217 | ||
218 | if (err) | |
219 | break; | |
220 | ||
221 | if (i == 0) { | |
222 | vinfo->vram_start = va->phys; | |
223 | vinfo->vram_logical = (void __iomem *) va->logical; | |
224 | vinfo->vram_contig_size = va->size; | |
225 | vinfo->num_areas = 1; | |
226 | } else { | |
227 | contiguous = 0; | |
228 | ||
229 | for (j = 0; j < i; ++j) { | |
230 | va2 = &vinfo->vram[j]; | |
231 | if (va->phys + va->size == va2->phys || | |
232 | va2->phys + va2->size == va->phys) { | |
233 | contiguous = 1; | |
234 | break; | |
235 | } | |
236 | } | |
237 | ||
238 | if (contiguous) { | |
239 | vinfo->num_areas++; | |
240 | if (va->phys < vinfo->vram_start) { | |
241 | vinfo->vram_start = va->phys; | |
242 | vinfo->vram_logical = | |
243 | (void __iomem *)va->logical; | |
244 | } | |
245 | vinfo->vram_contig_size += va->size; | |
246 | } else { | |
247 | vmlfb_free_vram_area(va); | |
248 | break; | |
249 | } | |
250 | } | |
251 | ||
252 | if (requested < va->size) | |
253 | break; | |
254 | else | |
255 | requested -= va->size; | |
256 | } | |
257 | ||
258 | if (vinfo->vram_contig_size > min_total && | |
259 | vinfo->vram_contig_size > min_contig) { | |
260 | ||
261 | printk(KERN_DEBUG MODULE_NAME | |
262 | ": Contiguous vram: %ld bytes at physical 0x%08lx.\n", | |
263 | (unsigned long)vinfo->vram_contig_size, | |
264 | (unsigned long)vinfo->vram_start); | |
265 | ||
266 | return 0; | |
267 | } | |
268 | ||
269 | printk(KERN_ERR MODULE_NAME | |
270 | ": Could not allocate requested minimal amount of vram.\n"); | |
271 | ||
272 | vmlfb_free_vram(vinfo); | |
273 | ||
274 | return -ENOMEM; | |
275 | } | |
276 | ||
277 | /* | |
278 | * Find the GPU to use with our display controller. | |
279 | */ | |
280 | ||
281 | static int vmlfb_get_gpu(struct vml_par *par) | |
282 | { | |
283 | mutex_lock(&vml_mutex); | |
284 | ||
285 | par->gpu = pci_get_device(PCI_VENDOR_ID_INTEL, VML_DEVICE_GPU, NULL); | |
286 | ||
287 | if (!par->gpu) { | |
288 | mutex_unlock(&vml_mutex); | |
289 | return -ENODEV; | |
290 | } | |
291 | ||
292 | mutex_unlock(&vml_mutex); | |
293 | ||
294 | if (pci_enable_device(par->gpu) < 0) | |
295 | return -ENODEV; | |
296 | ||
297 | return 0; | |
298 | } | |
299 | ||
300 | /* | |
301 | * Find a contiguous vram area that contains a given offset from vram start. | |
302 | */ | |
303 | static int vmlfb_vram_offset(struct vml_info *vinfo, unsigned long offset) | |
304 | { | |
305 | unsigned long aoffset; | |
306 | unsigned i; | |
307 | ||
308 | for (i = 0; i < vinfo->num_areas; ++i) { | |
309 | aoffset = offset - (vinfo->vram[i].phys - vinfo->vram_start); | |
310 | ||
311 | if (aoffset < vinfo->vram[i].size) { | |
312 | return 0; | |
313 | } | |
314 | } | |
315 | ||
316 | return -EINVAL; | |
317 | } | |
318 | ||
319 | /* | |
320 | * Remap the MMIO register spaces of the VDC and the GPU. | |
321 | */ | |
322 | ||
323 | static int vmlfb_enable_mmio(struct vml_par *par) | |
324 | { | |
325 | int err; | |
326 | ||
327 | par->vdc_mem_base = pci_resource_start(par->vdc, 0); | |
328 | par->vdc_mem_size = pci_resource_len(par->vdc, 0); | |
329 | if (!request_mem_region(par->vdc_mem_base, par->vdc_mem_size, "vmlfb")) { | |
330 | printk(KERN_ERR MODULE_NAME | |
331 | ": Could not claim display controller MMIO.\n"); | |
332 | return -EBUSY; | |
333 | } | |
334 | par->vdc_mem = ioremap_nocache(par->vdc_mem_base, par->vdc_mem_size); | |
335 | if (par->vdc_mem == NULL) { | |
336 | printk(KERN_ERR MODULE_NAME | |
337 | ": Could not map display controller MMIO.\n"); | |
338 | err = -ENOMEM; | |
339 | goto out_err_0; | |
340 | } | |
341 | ||
342 | par->gpu_mem_base = pci_resource_start(par->gpu, 0); | |
343 | par->gpu_mem_size = pci_resource_len(par->gpu, 0); | |
344 | if (!request_mem_region(par->gpu_mem_base, par->gpu_mem_size, "vmlfb")) { | |
345 | printk(KERN_ERR MODULE_NAME ": Could not claim GPU MMIO.\n"); | |
346 | err = -EBUSY; | |
347 | goto out_err_1; | |
348 | } | |
349 | par->gpu_mem = ioremap_nocache(par->gpu_mem_base, par->gpu_mem_size); | |
350 | if (par->gpu_mem == NULL) { | |
351 | printk(KERN_ERR MODULE_NAME ": Could not map GPU MMIO.\n"); | |
352 | err = -ENOMEM; | |
353 | goto out_err_2; | |
354 | } | |
355 | ||
356 | return 0; | |
357 | ||
358 | out_err_2: | |
359 | release_mem_region(par->gpu_mem_base, par->gpu_mem_size); | |
360 | out_err_1: | |
361 | iounmap(par->vdc_mem); | |
362 | out_err_0: | |
363 | release_mem_region(par->vdc_mem_base, par->vdc_mem_size); | |
364 | return err; | |
365 | } | |
366 | ||
367 | /* | |
368 | * Unmap the VDC and GPU register spaces. | |
369 | */ | |
370 | ||
371 | static void vmlfb_disable_mmio(struct vml_par *par) | |
372 | { | |
373 | iounmap(par->gpu_mem); | |
374 | release_mem_region(par->gpu_mem_base, par->gpu_mem_size); | |
375 | iounmap(par->vdc_mem); | |
376 | release_mem_region(par->vdc_mem_base, par->vdc_mem_size); | |
377 | } | |
378 | ||
379 | /* | |
380 | * Release and uninit the VDC and GPU. | |
381 | */ | |
382 | ||
383 | static void vmlfb_release_devices(struct vml_par *par) | |
384 | { | |
385 | if (atomic_dec_and_test(&par->refcount)) { | |
386 | pci_set_drvdata(par->vdc, NULL); | |
387 | pci_disable_device(par->gpu); | |
388 | pci_disable_device(par->vdc); | |
389 | } | |
390 | } | |
391 | ||
392 | /* | |
393 | * Free up allocated resources for a device. | |
394 | */ | |
395 | ||
48c68c4f | 396 | static void vml_pci_remove(struct pci_dev *dev) |
dbe7e429 AH |
397 | { |
398 | struct fb_info *info; | |
399 | struct vml_info *vinfo; | |
400 | struct vml_par *par; | |
401 | ||
402 | info = pci_get_drvdata(dev); | |
403 | if (info) { | |
404 | vinfo = container_of(info, struct vml_info, info); | |
405 | par = vinfo->par; | |
406 | mutex_lock(&vml_mutex); | |
407 | unregister_framebuffer(info); | |
408 | fb_dealloc_cmap(&info->cmap); | |
409 | vmlfb_free_vram(vinfo); | |
410 | vmlfb_disable_mmio(par); | |
411 | vmlfb_release_devices(par); | |
412 | kfree(vinfo); | |
413 | kfree(par); | |
414 | mutex_unlock(&vml_mutex); | |
415 | } | |
416 | } | |
417 | ||
418 | static void vmlfb_set_pref_pixel_format(struct fb_var_screeninfo *var) | |
419 | { | |
420 | switch (var->bits_per_pixel) { | |
421 | case 16: | |
422 | var->blue.offset = 0; | |
423 | var->blue.length = 5; | |
424 | var->green.offset = 5; | |
425 | var->green.length = 5; | |
426 | var->red.offset = 10; | |
427 | var->red.length = 5; | |
428 | var->transp.offset = 15; | |
429 | var->transp.length = 1; | |
430 | break; | |
431 | case 32: | |
432 | var->blue.offset = 0; | |
433 | var->blue.length = 8; | |
434 | var->green.offset = 8; | |
435 | var->green.length = 8; | |
436 | var->red.offset = 16; | |
437 | var->red.length = 8; | |
438 | var->transp.offset = 24; | |
439 | var->transp.length = 0; | |
440 | break; | |
441 | default: | |
442 | break; | |
443 | } | |
444 | ||
445 | var->blue.msb_right = var->green.msb_right = | |
446 | var->red.msb_right = var->transp.msb_right = 0; | |
447 | } | |
448 | ||
449 | /* | |
450 | * Device initialization. | |
451 | * We initialize one vml_par struct per device and one vml_info | |
452 | * struct per pipe. Currently we have only one pipe. | |
453 | */ | |
454 | ||
48c68c4f | 455 | static int vml_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) |
dbe7e429 AH |
456 | { |
457 | struct vml_info *vinfo; | |
458 | struct fb_info *info; | |
459 | struct vml_par *par; | |
460 | int err = 0; | |
461 | ||
462 | par = kzalloc(sizeof(*par), GFP_KERNEL); | |
463 | if (par == NULL) | |
464 | return -ENOMEM; | |
465 | ||
466 | vinfo = kzalloc(sizeof(*vinfo), GFP_KERNEL); | |
467 | if (vinfo == NULL) { | |
468 | err = -ENOMEM; | |
469 | goto out_err_0; | |
470 | } | |
471 | ||
472 | vinfo->par = par; | |
473 | par->vdc = dev; | |
474 | atomic_set(&par->refcount, 1); | |
475 | ||
476 | switch (id->device) { | |
477 | case VML_DEVICE_VDC: | |
478 | if ((err = vmlfb_get_gpu(par))) | |
479 | goto out_err_1; | |
480 | pci_set_drvdata(dev, &vinfo->info); | |
481 | break; | |
482 | default: | |
483 | err = -ENODEV; | |
484 | goto out_err_1; | |
485 | break; | |
486 | } | |
487 | ||
488 | info = &vinfo->info; | |
489 | info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK; | |
490 | ||
491 | err = vmlfb_enable_mmio(par); | |
492 | if (err) | |
493 | goto out_err_2; | |
494 | ||
495 | err = vmlfb_alloc_vram(vinfo, vml_mem_requested, | |
496 | vml_mem_contig, vml_mem_min); | |
497 | if (err) | |
498 | goto out_err_3; | |
499 | ||
500 | strcpy(info->fix.id, "Vermilion Range"); | |
501 | info->fix.mmio_start = 0; | |
502 | info->fix.mmio_len = 0; | |
503 | info->fix.smem_start = vinfo->vram_start; | |
504 | info->fix.smem_len = vinfo->vram_contig_size; | |
505 | info->fix.type = FB_TYPE_PACKED_PIXELS; | |
506 | info->fix.visual = FB_VISUAL_TRUECOLOR; | |
507 | info->fix.ypanstep = 1; | |
508 | info->fix.xpanstep = 1; | |
509 | info->fix.ywrapstep = 0; | |
510 | info->fix.accel = FB_ACCEL_NONE; | |
511 | info->screen_base = vinfo->vram_logical; | |
512 | info->pseudo_palette = vinfo->pseudo_palette; | |
513 | info->par = par; | |
514 | info->fbops = &vmlfb_ops; | |
515 | info->device = &dev->dev; | |
516 | ||
517 | INIT_LIST_HEAD(&vinfo->head); | |
518 | vinfo->pipe_disabled = 1; | |
519 | vinfo->cur_blank_mode = FB_BLANK_UNBLANK; | |
520 | ||
521 | info->var.grayscale = 0; | |
522 | info->var.bits_per_pixel = 16; | |
523 | vmlfb_set_pref_pixel_format(&info->var); | |
524 | ||
525 | if (!fb_find_mode | |
526 | (&info->var, info, vml_default_mode, NULL, 0, &defaultmode, 16)) { | |
527 | printk(KERN_ERR MODULE_NAME ": Could not find initial mode\n"); | |
528 | } | |
529 | ||
530 | if (fb_alloc_cmap(&info->cmap, 256, 1) < 0) { | |
531 | err = -ENOMEM; | |
532 | goto out_err_4; | |
533 | } | |
534 | ||
535 | err = register_framebuffer(info); | |
536 | if (err) { | |
537 | printk(KERN_ERR MODULE_NAME ": Register framebuffer error.\n"); | |
538 | goto out_err_5; | |
539 | } | |
540 | ||
541 | printk("Initialized vmlfb\n"); | |
542 | ||
543 | return 0; | |
544 | ||
545 | out_err_5: | |
546 | fb_dealloc_cmap(&info->cmap); | |
547 | out_err_4: | |
548 | vmlfb_free_vram(vinfo); | |
549 | out_err_3: | |
550 | vmlfb_disable_mmio(par); | |
551 | out_err_2: | |
552 | vmlfb_release_devices(par); | |
553 | out_err_1: | |
554 | kfree(vinfo); | |
555 | out_err_0: | |
556 | kfree(par); | |
557 | return err; | |
558 | } | |
559 | ||
560 | static int vmlfb_open(struct fb_info *info, int user) | |
561 | { | |
562 | /* | |
563 | * Save registers here? | |
564 | */ | |
565 | return 0; | |
566 | } | |
567 | ||
568 | static int vmlfb_release(struct fb_info *info, int user) | |
569 | { | |
570 | /* | |
571 | * Restore registers here. | |
572 | */ | |
573 | ||
574 | return 0; | |
575 | } | |
576 | ||
577 | static int vml_nearest_clock(int clock) | |
578 | { | |
579 | ||
580 | int i; | |
581 | int cur_index; | |
582 | int cur_diff; | |
583 | int diff; | |
584 | ||
585 | cur_index = 0; | |
586 | cur_diff = clock - vml_clocks[0]; | |
587 | cur_diff = (cur_diff < 0) ? -cur_diff : cur_diff; | |
588 | for (i = 1; i < vml_num_clocks; ++i) { | |
589 | diff = clock - vml_clocks[i]; | |
590 | diff = (diff < 0) ? -diff : diff; | |
591 | if (diff < cur_diff) { | |
592 | cur_index = i; | |
593 | cur_diff = diff; | |
594 | } | |
595 | } | |
596 | return vml_clocks[cur_index]; | |
597 | } | |
598 | ||
599 | static int vmlfb_check_var_locked(struct fb_var_screeninfo *var, | |
600 | struct vml_info *vinfo) | |
601 | { | |
602 | u32 pitch; | |
603 | u64 mem; | |
604 | int nearest_clock; | |
605 | int clock; | |
606 | int clock_diff; | |
607 | struct fb_var_screeninfo v; | |
608 | ||
609 | v = *var; | |
610 | clock = PICOS2KHZ(var->pixclock); | |
611 | ||
612 | if (subsys && subsys->nearest_clock) { | |
613 | nearest_clock = subsys->nearest_clock(subsys, clock); | |
614 | } else { | |
615 | nearest_clock = vml_nearest_clock(clock); | |
616 | } | |
617 | ||
618 | /* | |
619 | * Accept a 20% diff. | |
620 | */ | |
621 | ||
622 | clock_diff = nearest_clock - clock; | |
623 | clock_diff = (clock_diff < 0) ? -clock_diff : clock_diff; | |
624 | if (clock_diff > clock / 5) { | |
625 | #if 0 | |
626 | printk(KERN_DEBUG MODULE_NAME ": Diff failure. %d %d\n",clock_diff,clock); | |
627 | #endif | |
628 | return -EINVAL; | |
629 | } | |
630 | ||
631 | v.pixclock = KHZ2PICOS(nearest_clock); | |
632 | ||
633 | if (var->xres > VML_MAX_XRES || var->yres > VML_MAX_YRES) { | |
634 | printk(KERN_DEBUG MODULE_NAME ": Resolution failure.\n"); | |
635 | return -EINVAL; | |
636 | } | |
637 | if (var->xres_virtual > VML_MAX_XRES_VIRTUAL) { | |
638 | printk(KERN_DEBUG MODULE_NAME | |
639 | ": Virtual resolution failure.\n"); | |
640 | return -EINVAL; | |
641 | } | |
642 | switch (v.bits_per_pixel) { | |
643 | case 0 ... 16: | |
644 | v.bits_per_pixel = 16; | |
645 | break; | |
646 | case 17 ... 32: | |
647 | v.bits_per_pixel = 32; | |
648 | break; | |
649 | default: | |
650 | printk(KERN_DEBUG MODULE_NAME ": Invalid bpp: %d.\n", | |
651 | var->bits_per_pixel); | |
652 | return -EINVAL; | |
653 | } | |
654 | ||
2e975027 | 655 | pitch = ALIGN((var->xres * var->bits_per_pixel) >> 3, 0x40); |
dbe7e429 AH |
656 | mem = pitch * var->yres_virtual; |
657 | if (mem > vinfo->vram_contig_size) { | |
658 | return -ENOMEM; | |
659 | } | |
660 | ||
661 | switch (v.bits_per_pixel) { | |
662 | case 16: | |
663 | if (var->blue.offset != 0 || | |
664 | var->blue.length != 5 || | |
665 | var->green.offset != 5 || | |
666 | var->green.length != 5 || | |
667 | var->red.offset != 10 || | |
668 | var->red.length != 5 || | |
669 | var->transp.offset != 15 || var->transp.length != 1) { | |
670 | vmlfb_set_pref_pixel_format(&v); | |
671 | } | |
672 | break; | |
673 | case 32: | |
674 | if (var->blue.offset != 0 || | |
675 | var->blue.length != 8 || | |
676 | var->green.offset != 8 || | |
677 | var->green.length != 8 || | |
678 | var->red.offset != 16 || | |
679 | var->red.length != 8 || | |
680 | (var->transp.length != 0 && var->transp.length != 8) || | |
681 | (var->transp.length == 8 && var->transp.offset != 24)) { | |
682 | vmlfb_set_pref_pixel_format(&v); | |
683 | } | |
684 | break; | |
685 | default: | |
686 | return -EINVAL; | |
687 | } | |
688 | ||
689 | *var = v; | |
690 | ||
691 | return 0; | |
692 | } | |
693 | ||
694 | static int vmlfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) | |
695 | { | |
696 | struct vml_info *vinfo = container_of(info, struct vml_info, info); | |
697 | int ret; | |
698 | ||
699 | mutex_lock(&vml_mutex); | |
700 | ret = vmlfb_check_var_locked(var, vinfo); | |
701 | mutex_unlock(&vml_mutex); | |
702 | ||
703 | return ret; | |
704 | } | |
705 | ||
706 | static void vml_wait_vblank(struct vml_info *vinfo) | |
707 | { | |
708 | /* Wait for vblank. For now, just wait for a 50Hz cycle (20ms)) */ | |
709 | mdelay(20); | |
710 | } | |
711 | ||
712 | static void vmlfb_disable_pipe(struct vml_info *vinfo) | |
713 | { | |
714 | struct vml_par *par = vinfo->par; | |
715 | ||
716 | /* Disable the MDVO pad */ | |
717 | VML_WRITE32(par, VML_RCOMPSTAT, 0); | |
718 | while (!(VML_READ32(par, VML_RCOMPSTAT) & VML_MDVO_VDC_I_RCOMP)) ; | |
719 | ||
720 | /* Disable display planes */ | |
721 | VML_WRITE32(par, VML_DSPCCNTR, | |
722 | VML_READ32(par, VML_DSPCCNTR) & ~VML_GFX_ENABLE); | |
723 | (void)VML_READ32(par, VML_DSPCCNTR); | |
724 | /* Wait for vblank for the disable to take effect */ | |
725 | vml_wait_vblank(vinfo); | |
726 | ||
727 | /* Next, disable display pipes */ | |
728 | VML_WRITE32(par, VML_PIPEACONF, 0); | |
729 | (void)VML_READ32(par, VML_PIPEACONF); | |
730 | ||
731 | vinfo->pipe_disabled = 1; | |
732 | } | |
733 | ||
734 | #ifdef VERMILION_DEBUG | |
735 | static void vml_dump_regs(struct vml_info *vinfo) | |
736 | { | |
737 | struct vml_par *par = vinfo->par; | |
738 | ||
739 | printk(KERN_DEBUG MODULE_NAME ": Modesetting register dump:\n"); | |
740 | printk(KERN_DEBUG MODULE_NAME ": \tHTOTAL_A : 0x%08x\n", | |
741 | (unsigned)VML_READ32(par, VML_HTOTAL_A)); | |
742 | printk(KERN_DEBUG MODULE_NAME ": \tHBLANK_A : 0x%08x\n", | |
743 | (unsigned)VML_READ32(par, VML_HBLANK_A)); | |
744 | printk(KERN_DEBUG MODULE_NAME ": \tHSYNC_A : 0x%08x\n", | |
745 | (unsigned)VML_READ32(par, VML_HSYNC_A)); | |
746 | printk(KERN_DEBUG MODULE_NAME ": \tVTOTAL_A : 0x%08x\n", | |
747 | (unsigned)VML_READ32(par, VML_VTOTAL_A)); | |
748 | printk(KERN_DEBUG MODULE_NAME ": \tVBLANK_A : 0x%08x\n", | |
749 | (unsigned)VML_READ32(par, VML_VBLANK_A)); | |
750 | printk(KERN_DEBUG MODULE_NAME ": \tVSYNC_A : 0x%08x\n", | |
751 | (unsigned)VML_READ32(par, VML_VSYNC_A)); | |
752 | printk(KERN_DEBUG MODULE_NAME ": \tDSPCSTRIDE : 0x%08x\n", | |
753 | (unsigned)VML_READ32(par, VML_DSPCSTRIDE)); | |
754 | printk(KERN_DEBUG MODULE_NAME ": \tDSPCSIZE : 0x%08x\n", | |
755 | (unsigned)VML_READ32(par, VML_DSPCSIZE)); | |
756 | printk(KERN_DEBUG MODULE_NAME ": \tDSPCPOS : 0x%08x\n", | |
757 | (unsigned)VML_READ32(par, VML_DSPCPOS)); | |
758 | printk(KERN_DEBUG MODULE_NAME ": \tDSPARB : 0x%08x\n", | |
759 | (unsigned)VML_READ32(par, VML_DSPARB)); | |
760 | printk(KERN_DEBUG MODULE_NAME ": \tDSPCADDR : 0x%08x\n", | |
761 | (unsigned)VML_READ32(par, VML_DSPCADDR)); | |
762 | printk(KERN_DEBUG MODULE_NAME ": \tBCLRPAT_A : 0x%08x\n", | |
763 | (unsigned)VML_READ32(par, VML_BCLRPAT_A)); | |
764 | printk(KERN_DEBUG MODULE_NAME ": \tCANVSCLR_A : 0x%08x\n", | |
765 | (unsigned)VML_READ32(par, VML_CANVSCLR_A)); | |
766 | printk(KERN_DEBUG MODULE_NAME ": \tPIPEASRC : 0x%08x\n", | |
767 | (unsigned)VML_READ32(par, VML_PIPEASRC)); | |
768 | printk(KERN_DEBUG MODULE_NAME ": \tPIPEACONF : 0x%08x\n", | |
769 | (unsigned)VML_READ32(par, VML_PIPEACONF)); | |
770 | printk(KERN_DEBUG MODULE_NAME ": \tDSPCCNTR : 0x%08x\n", | |
771 | (unsigned)VML_READ32(par, VML_DSPCCNTR)); | |
772 | printk(KERN_DEBUG MODULE_NAME ": \tRCOMPSTAT : 0x%08x\n", | |
773 | (unsigned)VML_READ32(par, VML_RCOMPSTAT)); | |
774 | printk(KERN_DEBUG MODULE_NAME ": End of modesetting register dump.\n"); | |
775 | } | |
776 | #endif | |
777 | ||
778 | static int vmlfb_set_par_locked(struct vml_info *vinfo) | |
779 | { | |
780 | struct vml_par *par = vinfo->par; | |
781 | struct fb_info *info = &vinfo->info; | |
782 | struct fb_var_screeninfo *var = &info->var; | |
783 | u32 htotal, hactive, hblank_start, hblank_end, hsync_start, hsync_end; | |
784 | u32 vtotal, vactive, vblank_start, vblank_end, vsync_start, vsync_end; | |
785 | u32 dspcntr; | |
786 | int clock; | |
787 | ||
788 | vinfo->bytes_per_pixel = var->bits_per_pixel >> 3; | |
2e975027 | 789 | vinfo->stride = ALIGN(var->xres_virtual * vinfo->bytes_per_pixel, 0x40); |
dbe7e429 AH |
790 | info->fix.line_length = vinfo->stride; |
791 | ||
792 | if (!subsys) | |
793 | return 0; | |
794 | ||
795 | htotal = | |
796 | var->xres + var->right_margin + var->hsync_len + var->left_margin; | |
797 | hactive = var->xres; | |
798 | hblank_start = var->xres; | |
799 | hblank_end = htotal; | |
800 | hsync_start = hactive + var->right_margin; | |
801 | hsync_end = hsync_start + var->hsync_len; | |
802 | ||
803 | vtotal = | |
804 | var->yres + var->lower_margin + var->vsync_len + var->upper_margin; | |
805 | vactive = var->yres; | |
806 | vblank_start = var->yres; | |
807 | vblank_end = vtotal; | |
808 | vsync_start = vactive + var->lower_margin; | |
809 | vsync_end = vsync_start + var->vsync_len; | |
810 | ||
811 | dspcntr = VML_GFX_ENABLE | VML_GFX_GAMMABYPASS; | |
812 | clock = PICOS2KHZ(var->pixclock); | |
813 | ||
814 | if (subsys->nearest_clock) { | |
815 | clock = subsys->nearest_clock(subsys, clock); | |
816 | } else { | |
817 | clock = vml_nearest_clock(clock); | |
818 | } | |
819 | printk(KERN_DEBUG MODULE_NAME | |
820 | ": Set mode Hfreq : %d kHz, Vfreq : %d Hz.\n", clock / htotal, | |
821 | ((clock / htotal) * 1000) / vtotal); | |
822 | ||
823 | switch (var->bits_per_pixel) { | |
824 | case 16: | |
825 | dspcntr |= VML_GFX_ARGB1555; | |
826 | break; | |
827 | case 32: | |
828 | if (var->transp.length == 8) | |
829 | dspcntr |= VML_GFX_ARGB8888 | VML_GFX_ALPHAMULT; | |
830 | else | |
831 | dspcntr |= VML_GFX_RGB0888; | |
832 | break; | |
833 | default: | |
834 | return -EINVAL; | |
835 | } | |
836 | ||
837 | vmlfb_disable_pipe(vinfo); | |
838 | mb(); | |
839 | ||
840 | if (subsys->set_clock) | |
841 | subsys->set_clock(subsys, clock); | |
842 | else | |
843 | return -EINVAL; | |
844 | ||
845 | VML_WRITE32(par, VML_HTOTAL_A, ((htotal - 1) << 16) | (hactive - 1)); | |
846 | VML_WRITE32(par, VML_HBLANK_A, | |
847 | ((hblank_end - 1) << 16) | (hblank_start - 1)); | |
848 | VML_WRITE32(par, VML_HSYNC_A, | |
849 | ((hsync_end - 1) << 16) | (hsync_start - 1)); | |
850 | VML_WRITE32(par, VML_VTOTAL_A, ((vtotal - 1) << 16) | (vactive - 1)); | |
851 | VML_WRITE32(par, VML_VBLANK_A, | |
852 | ((vblank_end - 1) << 16) | (vblank_start - 1)); | |
853 | VML_WRITE32(par, VML_VSYNC_A, | |
854 | ((vsync_end - 1) << 16) | (vsync_start - 1)); | |
855 | VML_WRITE32(par, VML_DSPCSTRIDE, vinfo->stride); | |
856 | VML_WRITE32(par, VML_DSPCSIZE, | |
857 | ((var->yres - 1) << 16) | (var->xres - 1)); | |
858 | VML_WRITE32(par, VML_DSPCPOS, 0x00000000); | |
859 | VML_WRITE32(par, VML_DSPARB, VML_FIFO_DEFAULT); | |
860 | VML_WRITE32(par, VML_BCLRPAT_A, 0x00000000); | |
861 | VML_WRITE32(par, VML_CANVSCLR_A, 0x00000000); | |
862 | VML_WRITE32(par, VML_PIPEASRC, | |
863 | ((var->xres - 1) << 16) | (var->yres - 1)); | |
864 | ||
865 | wmb(); | |
866 | VML_WRITE32(par, VML_PIPEACONF, VML_PIPE_ENABLE); | |
867 | wmb(); | |
868 | VML_WRITE32(par, VML_DSPCCNTR, dspcntr); | |
869 | wmb(); | |
870 | VML_WRITE32(par, VML_DSPCADDR, (u32) vinfo->vram_start + | |
871 | var->yoffset * vinfo->stride + | |
872 | var->xoffset * vinfo->bytes_per_pixel); | |
873 | ||
874 | VML_WRITE32(par, VML_RCOMPSTAT, VML_MDVO_PAD_ENABLE); | |
875 | ||
876 | while (!(VML_READ32(par, VML_RCOMPSTAT) & | |
877 | (VML_MDVO_VDC_I_RCOMP | VML_MDVO_PAD_ENABLE))) ; | |
878 | ||
879 | vinfo->pipe_disabled = 0; | |
880 | #ifdef VERMILION_DEBUG | |
881 | vml_dump_regs(vinfo); | |
882 | #endif | |
883 | ||
884 | return 0; | |
885 | } | |
886 | ||
887 | static int vmlfb_set_par(struct fb_info *info) | |
888 | { | |
889 | struct vml_info *vinfo = container_of(info, struct vml_info, info); | |
890 | int ret; | |
891 | ||
892 | mutex_lock(&vml_mutex); | |
1721af4d | 893 | list_move(&vinfo->head, (subsys) ? &global_has_mode : &global_no_mode); |
dbe7e429 AH |
894 | ret = vmlfb_set_par_locked(vinfo); |
895 | ||
896 | mutex_unlock(&vml_mutex); | |
897 | return ret; | |
898 | } | |
899 | ||
900 | static int vmlfb_blank_locked(struct vml_info *vinfo) | |
901 | { | |
902 | struct vml_par *par = vinfo->par; | |
903 | u32 cur = VML_READ32(par, VML_PIPEACONF); | |
904 | ||
905 | switch (vinfo->cur_blank_mode) { | |
906 | case FB_BLANK_UNBLANK: | |
907 | if (vinfo->pipe_disabled) { | |
908 | vmlfb_set_par_locked(vinfo); | |
909 | } | |
910 | VML_WRITE32(par, VML_PIPEACONF, cur & ~VML_PIPE_FORCE_BORDER); | |
911 | (void)VML_READ32(par, VML_PIPEACONF); | |
912 | break; | |
913 | case FB_BLANK_NORMAL: | |
914 | if (vinfo->pipe_disabled) { | |
915 | vmlfb_set_par_locked(vinfo); | |
916 | } | |
917 | VML_WRITE32(par, VML_PIPEACONF, cur | VML_PIPE_FORCE_BORDER); | |
918 | (void)VML_READ32(par, VML_PIPEACONF); | |
919 | break; | |
920 | case FB_BLANK_VSYNC_SUSPEND: | |
921 | case FB_BLANK_HSYNC_SUSPEND: | |
922 | if (!vinfo->pipe_disabled) { | |
923 | vmlfb_disable_pipe(vinfo); | |
924 | } | |
925 | break; | |
926 | case FB_BLANK_POWERDOWN: | |
927 | if (!vinfo->pipe_disabled) { | |
928 | vmlfb_disable_pipe(vinfo); | |
929 | } | |
930 | break; | |
931 | default: | |
932 | return -EINVAL; | |
933 | } | |
934 | ||
935 | return 0; | |
936 | } | |
937 | ||
938 | static int vmlfb_blank(int blank_mode, struct fb_info *info) | |
939 | { | |
940 | struct vml_info *vinfo = container_of(info, struct vml_info, info); | |
941 | int ret; | |
942 | ||
943 | mutex_lock(&vml_mutex); | |
944 | vinfo->cur_blank_mode = blank_mode; | |
945 | ret = vmlfb_blank_locked(vinfo); | |
946 | mutex_unlock(&vml_mutex); | |
947 | return ret; | |
948 | } | |
949 | ||
950 | static int vmlfb_pan_display(struct fb_var_screeninfo *var, | |
951 | struct fb_info *info) | |
952 | { | |
953 | struct vml_info *vinfo = container_of(info, struct vml_info, info); | |
954 | struct vml_par *par = vinfo->par; | |
955 | ||
956 | mutex_lock(&vml_mutex); | |
957 | VML_WRITE32(par, VML_DSPCADDR, (u32) vinfo->vram_start + | |
958 | var->yoffset * vinfo->stride + | |
959 | var->xoffset * vinfo->bytes_per_pixel); | |
960 | (void)VML_READ32(par, VML_DSPCADDR); | |
961 | mutex_unlock(&vml_mutex); | |
962 | ||
963 | return 0; | |
964 | } | |
965 | ||
966 | static int vmlfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, | |
967 | u_int transp, struct fb_info *info) | |
968 | { | |
969 | u32 v; | |
970 | ||
971 | if (regno >= 16) | |
972 | return -EINVAL; | |
973 | ||
974 | if (info->var.grayscale) { | |
975 | red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; | |
976 | } | |
977 | ||
978 | if (info->fix.visual != FB_VISUAL_TRUECOLOR) | |
979 | return -EINVAL; | |
980 | ||
981 | red = VML_TOHW(red, info->var.red.length); | |
982 | blue = VML_TOHW(blue, info->var.blue.length); | |
983 | green = VML_TOHW(green, info->var.green.length); | |
984 | transp = VML_TOHW(transp, info->var.transp.length); | |
985 | ||
986 | v = (red << info->var.red.offset) | | |
987 | (green << info->var.green.offset) | | |
988 | (blue << info->var.blue.offset) | | |
989 | (transp << info->var.transp.offset); | |
990 | ||
991 | switch (info->var.bits_per_pixel) { | |
992 | case 16: | |
993 | ((u32 *) info->pseudo_palette)[regno] = v; | |
994 | break; | |
995 | case 24: | |
996 | case 32: | |
997 | ((u32 *) info->pseudo_palette)[regno] = v; | |
998 | break; | |
999 | } | |
1000 | return 0; | |
1001 | } | |
1002 | ||
1003 | static int vmlfb_mmap(struct fb_info *info, struct vm_area_struct *vma) | |
1004 | { | |
1005 | struct vml_info *vinfo = container_of(info, struct vml_info, info); | |
dbe7e429 AH |
1006 | unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; |
1007 | int ret; | |
1008 | ||
dbe7e429 AH |
1009 | ret = vmlfb_vram_offset(vinfo, offset); |
1010 | if (ret) | |
1011 | return -EINVAL; | |
51717743 | 1012 | |
dbe7e429 AH |
1013 | pgprot_val(vma->vm_page_prot) |= _PAGE_PCD; |
1014 | pgprot_val(vma->vm_page_prot) &= ~_PAGE_PWT; | |
51717743 TV |
1015 | |
1016 | return vm_iomap_memory(vma, vinfo->vram_start, | |
1017 | vinfo->vram_contig_size); | |
dbe7e429 AH |
1018 | } |
1019 | ||
1020 | static int vmlfb_sync(struct fb_info *info) | |
1021 | { | |
1022 | return 0; | |
1023 | } | |
1024 | ||
1025 | static int vmlfb_cursor(struct fb_info *info, struct fb_cursor *cursor) | |
1026 | { | |
1027 | return -EINVAL; /* just to force soft_cursor() call */ | |
1028 | } | |
1029 | ||
1030 | static struct fb_ops vmlfb_ops = { | |
1031 | .owner = THIS_MODULE, | |
1032 | .fb_open = vmlfb_open, | |
1033 | .fb_release = vmlfb_release, | |
1034 | .fb_check_var = vmlfb_check_var, | |
1035 | .fb_set_par = vmlfb_set_par, | |
1036 | .fb_blank = vmlfb_blank, | |
1037 | .fb_pan_display = vmlfb_pan_display, | |
1038 | .fb_fillrect = cfb_fillrect, | |
1039 | .fb_copyarea = cfb_copyarea, | |
1040 | .fb_imageblit = cfb_imageblit, | |
1041 | .fb_cursor = vmlfb_cursor, | |
1042 | .fb_sync = vmlfb_sync, | |
1043 | .fb_mmap = vmlfb_mmap, | |
1044 | .fb_setcolreg = vmlfb_setcolreg | |
1045 | }; | |
1046 | ||
1047 | static struct pci_device_id vml_ids[] = { | |
1048 | {PCI_DEVICE(PCI_VENDOR_ID_INTEL, VML_DEVICE_VDC)}, | |
1049 | {0} | |
1050 | }; | |
1051 | ||
1052 | static struct pci_driver vmlfb_pci_driver = { | |
1053 | .name = "vmlfb", | |
1054 | .id_table = vml_ids, | |
1055 | .probe = vml_pci_probe, | |
48c68c4f | 1056 | .remove = vml_pci_remove, |
dbe7e429 AH |
1057 | }; |
1058 | ||
1059 | static void __exit vmlfb_cleanup(void) | |
1060 | { | |
1061 | pci_unregister_driver(&vmlfb_pci_driver); | |
1062 | } | |
1063 | ||
1064 | static int __init vmlfb_init(void) | |
1065 | { | |
1066 | ||
1067 | #ifndef MODULE | |
1068 | char *option = NULL; | |
1069 | ||
1070 | if (fb_get_options(MODULE_NAME, &option)) | |
1071 | return -ENODEV; | |
1072 | #endif | |
1073 | ||
1074 | printk(KERN_DEBUG MODULE_NAME ": initializing\n"); | |
1075 | mutex_init(&vml_mutex); | |
1076 | INIT_LIST_HEAD(&global_no_mode); | |
1077 | INIT_LIST_HEAD(&global_has_mode); | |
1078 | ||
1079 | return pci_register_driver(&vmlfb_pci_driver); | |
1080 | } | |
1081 | ||
1082 | int vmlfb_register_subsys(struct vml_sys *sys) | |
1083 | { | |
1084 | struct vml_info *entry; | |
1085 | struct list_head *list; | |
1086 | u32 save_activate; | |
1087 | ||
1088 | mutex_lock(&vml_mutex); | |
1089 | if (subsys != NULL) { | |
1090 | subsys->restore(subsys); | |
1091 | } | |
1092 | subsys = sys; | |
1093 | subsys->save(subsys); | |
1094 | ||
1095 | /* | |
1096 | * We need to restart list traversal for each item, since we | |
1097 | * release the list mutex in the loop. | |
1098 | */ | |
1099 | ||
1100 | list = global_no_mode.next; | |
1101 | while (list != &global_no_mode) { | |
1102 | list_del_init(list); | |
1103 | entry = list_entry(list, struct vml_info, head); | |
1104 | ||
1105 | /* | |
1106 | * First, try the current mode which might not be | |
1107 | * completely validated with respect to the pixel clock. | |
1108 | */ | |
1109 | ||
1110 | if (!vmlfb_check_var_locked(&entry->info.var, entry)) { | |
1111 | vmlfb_set_par_locked(entry); | |
1112 | list_add_tail(list, &global_has_mode); | |
1113 | } else { | |
1114 | ||
1115 | /* | |
1116 | * Didn't work. Try to find another mode, | |
1117 | * that matches this subsys. | |
1118 | */ | |
1119 | ||
1120 | mutex_unlock(&vml_mutex); | |
1121 | save_activate = entry->info.var.activate; | |
1122 | entry->info.var.bits_per_pixel = 16; | |
1123 | vmlfb_set_pref_pixel_format(&entry->info.var); | |
1124 | if (fb_find_mode(&entry->info.var, | |
1125 | &entry->info, | |
1126 | vml_default_mode, NULL, 0, NULL, 16)) { | |
1127 | entry->info.var.activate |= | |
1128 | FB_ACTIVATE_FORCE | FB_ACTIVATE_NOW; | |
1129 | fb_set_var(&entry->info, &entry->info.var); | |
1130 | } else { | |
1131 | printk(KERN_ERR MODULE_NAME | |
1132 | ": Sorry. no mode found for this subsys.\n"); | |
1133 | } | |
1134 | entry->info.var.activate = save_activate; | |
1135 | mutex_lock(&vml_mutex); | |
1136 | } | |
1137 | vmlfb_blank_locked(entry); | |
1138 | list = global_no_mode.next; | |
1139 | } | |
1140 | mutex_unlock(&vml_mutex); | |
1141 | ||
1142 | printk(KERN_DEBUG MODULE_NAME ": Registered %s subsystem.\n", | |
1143 | subsys->name ? subsys->name : "unknown"); | |
1144 | return 0; | |
1145 | } | |
1146 | ||
1147 | EXPORT_SYMBOL_GPL(vmlfb_register_subsys); | |
1148 | ||
1149 | void vmlfb_unregister_subsys(struct vml_sys *sys) | |
1150 | { | |
1151 | struct vml_info *entry, *next; | |
1152 | ||
1153 | mutex_lock(&vml_mutex); | |
1154 | if (subsys != sys) { | |
1155 | mutex_unlock(&vml_mutex); | |
1156 | return; | |
1157 | } | |
1158 | subsys->restore(subsys); | |
1159 | subsys = NULL; | |
1160 | list_for_each_entry_safe(entry, next, &global_has_mode, head) { | |
1161 | printk(KERN_DEBUG MODULE_NAME ": subsys disable pipe\n"); | |
1162 | vmlfb_disable_pipe(entry); | |
2636ff6b | 1163 | list_move_tail(&entry->head, &global_no_mode); |
dbe7e429 AH |
1164 | } |
1165 | mutex_unlock(&vml_mutex); | |
1166 | } | |
1167 | ||
1168 | EXPORT_SYMBOL_GPL(vmlfb_unregister_subsys); | |
1169 | ||
1170 | module_init(vmlfb_init); | |
1171 | module_exit(vmlfb_cleanup); | |
1172 | ||
1173 | MODULE_AUTHOR("Tungsten Graphics"); | |
1174 | MODULE_DESCRIPTION("Initialization of the Vermilion display devices"); | |
1175 | MODULE_VERSION("1.0.0"); | |
1176 | MODULE_LICENSE("GPL"); |