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