162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) Intel Corp. 2007.
462306a36Sopenharmony_ci * All Rights Reserved.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to
762306a36Sopenharmony_ci * develop this driver.
862306a36Sopenharmony_ci *
962306a36Sopenharmony_ci * This file is part of the Vermilion Range fb driver.
1062306a36Sopenharmony_ci *
1162306a36Sopenharmony_ci * Authors:
1262306a36Sopenharmony_ci *   Thomas Hellström <thomas-at-tungstengraphics-dot-com>
1362306a36Sopenharmony_ci *   Michel Dänzer <michel-at-tungstengraphics-dot-com>
1462306a36Sopenharmony_ci *   Alan Hourihane <alanh-at-tungstengraphics-dot-com>
1562306a36Sopenharmony_ci */
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_ci#include <linux/aperture.h>
1862306a36Sopenharmony_ci#include <linux/module.h>
1962306a36Sopenharmony_ci#include <linux/kernel.h>
2062306a36Sopenharmony_ci#include <linux/errno.h>
2162306a36Sopenharmony_ci#include <linux/string.h>
2262306a36Sopenharmony_ci#include <linux/delay.h>
2362306a36Sopenharmony_ci#include <linux/slab.h>
2462306a36Sopenharmony_ci#include <linux/mm.h>
2562306a36Sopenharmony_ci#include <linux/fb.h>
2662306a36Sopenharmony_ci#include <linux/pci.h>
2762306a36Sopenharmony_ci#include <asm/set_memory.h>
2862306a36Sopenharmony_ci#include <asm/tlbflush.h>
2962306a36Sopenharmony_ci#include <linux/mmzone.h>
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci/* #define VERMILION_DEBUG */
3262306a36Sopenharmony_ci
3362306a36Sopenharmony_ci#include "vermilion.h"
3462306a36Sopenharmony_ci
3562306a36Sopenharmony_ci#define MODULE_NAME "vmlfb"
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci#define VML_TOHW(_val, _width) ((((_val) << (_width)) + 0x7FFF - (_val)) >> 16)
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistatic struct mutex vml_mutex;
4062306a36Sopenharmony_cistatic struct list_head global_no_mode;
4162306a36Sopenharmony_cistatic struct list_head global_has_mode;
4262306a36Sopenharmony_cistatic struct fb_ops vmlfb_ops;
4362306a36Sopenharmony_cistatic struct vml_sys *subsys = NULL;
4462306a36Sopenharmony_cistatic char *vml_default_mode = "1024x768@60";
4562306a36Sopenharmony_cistatic const struct fb_videomode defaultmode = {
4662306a36Sopenharmony_ci	NULL, 60, 1024, 768, 12896, 144, 24, 29, 3, 136, 6,
4762306a36Sopenharmony_ci	0, FB_VMODE_NONINTERLACED
4862306a36Sopenharmony_ci};
4962306a36Sopenharmony_ci
5062306a36Sopenharmony_cistatic u32 vml_mem_requested = (10 * 1024 * 1024);
5162306a36Sopenharmony_cistatic u32 vml_mem_contig = (4 * 1024 * 1024);
5262306a36Sopenharmony_cistatic u32 vml_mem_min = (4 * 1024 * 1024);
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistatic u32 vml_clocks[] = {
5562306a36Sopenharmony_ci	6750,
5662306a36Sopenharmony_ci	13500,
5762306a36Sopenharmony_ci	27000,
5862306a36Sopenharmony_ci	29700,
5962306a36Sopenharmony_ci	37125,
6062306a36Sopenharmony_ci	54000,
6162306a36Sopenharmony_ci	59400,
6262306a36Sopenharmony_ci	74250,
6362306a36Sopenharmony_ci	120000,
6462306a36Sopenharmony_ci	148500
6562306a36Sopenharmony_ci};
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_cistatic u32 vml_num_clocks = ARRAY_SIZE(vml_clocks);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci/*
7062306a36Sopenharmony_ci * Allocate a contiguous vram area and make its linear kernel map
7162306a36Sopenharmony_ci * uncached.
7262306a36Sopenharmony_ci */
7362306a36Sopenharmony_ci
7462306a36Sopenharmony_cistatic int vmlfb_alloc_vram_area(struct vram_area *va, unsigned max_order,
7562306a36Sopenharmony_ci				 unsigned min_order)
7662306a36Sopenharmony_ci{
7762306a36Sopenharmony_ci	gfp_t flags;
7862306a36Sopenharmony_ci	unsigned long i;
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	max_order++;
8162306a36Sopenharmony_ci	do {
8262306a36Sopenharmony_ci		/*
8362306a36Sopenharmony_ci		 * Really try hard to get the needed memory.
8462306a36Sopenharmony_ci		 * We need memory below the first 32MB, so we
8562306a36Sopenharmony_ci		 * add the __GFP_DMA flag that guarantees that we are
8662306a36Sopenharmony_ci		 * below the first 16MB.
8762306a36Sopenharmony_ci		 */
8862306a36Sopenharmony_ci
8962306a36Sopenharmony_ci		flags = __GFP_DMA | __GFP_HIGH | __GFP_KSWAPD_RECLAIM;
9062306a36Sopenharmony_ci		va->logical =
9162306a36Sopenharmony_ci			 __get_free_pages(flags, --max_order);
9262306a36Sopenharmony_ci	} while (va->logical == 0 && max_order > min_order);
9362306a36Sopenharmony_ci
9462306a36Sopenharmony_ci	if (!va->logical)
9562306a36Sopenharmony_ci		return -ENOMEM;
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_ci	va->phys = virt_to_phys((void *)va->logical);
9862306a36Sopenharmony_ci	va->size = PAGE_SIZE << max_order;
9962306a36Sopenharmony_ci	va->order = max_order;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	/*
10262306a36Sopenharmony_ci	 * It seems like __get_free_pages only ups the usage count
10362306a36Sopenharmony_ci	 * of the first page. This doesn't work with fault mapping, so
10462306a36Sopenharmony_ci	 * up the usage count once more (XXX: should use split_page or
10562306a36Sopenharmony_ci	 * compound page).
10662306a36Sopenharmony_ci	 */
10762306a36Sopenharmony_ci
10862306a36Sopenharmony_ci	memset((void *)va->logical, 0x00, va->size);
10962306a36Sopenharmony_ci	for (i = va->logical; i < va->logical + va->size; i += PAGE_SIZE) {
11062306a36Sopenharmony_ci		get_page(virt_to_page(i));
11162306a36Sopenharmony_ci	}
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	/*
11462306a36Sopenharmony_ci	 * Change caching policy of the linear kernel map to avoid
11562306a36Sopenharmony_ci	 * mapping type conflicts with user-space mappings.
11662306a36Sopenharmony_ci	 */
11762306a36Sopenharmony_ci	set_pages_uc(virt_to_page(va->logical), va->size >> PAGE_SHIFT);
11862306a36Sopenharmony_ci
11962306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME
12062306a36Sopenharmony_ci	       ": Allocated %ld bytes vram area at 0x%08lx\n",
12162306a36Sopenharmony_ci	       va->size, va->phys);
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ci	return 0;
12462306a36Sopenharmony_ci}
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci/*
12762306a36Sopenharmony_ci * Free a contiguous vram area and reset its linear kernel map
12862306a36Sopenharmony_ci * mapping type.
12962306a36Sopenharmony_ci */
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cistatic void vmlfb_free_vram_area(struct vram_area *va)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	unsigned long j;
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	if (va->logical) {
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci		/*
13862306a36Sopenharmony_ci		 * Reset the linear kernel map caching policy.
13962306a36Sopenharmony_ci		 */
14062306a36Sopenharmony_ci
14162306a36Sopenharmony_ci		set_pages_wb(virt_to_page(va->logical),
14262306a36Sopenharmony_ci				 va->size >> PAGE_SHIFT);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_ci		/*
14562306a36Sopenharmony_ci		 * Decrease the usage count on the pages we've used
14662306a36Sopenharmony_ci		 * to compensate for upping when allocating.
14762306a36Sopenharmony_ci		 */
14862306a36Sopenharmony_ci
14962306a36Sopenharmony_ci		for (j = va->logical; j < va->logical + va->size;
15062306a36Sopenharmony_ci		     j += PAGE_SIZE) {
15162306a36Sopenharmony_ci			(void)put_page_testzero(virt_to_page(j));
15262306a36Sopenharmony_ci		}
15362306a36Sopenharmony_ci
15462306a36Sopenharmony_ci		printk(KERN_DEBUG MODULE_NAME
15562306a36Sopenharmony_ci		       ": Freeing %ld bytes vram area at 0x%08lx\n",
15662306a36Sopenharmony_ci		       va->size, va->phys);
15762306a36Sopenharmony_ci		free_pages(va->logical, va->order);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci		va->logical = 0;
16062306a36Sopenharmony_ci	}
16162306a36Sopenharmony_ci}
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_ci/*
16462306a36Sopenharmony_ci * Free allocated vram.
16562306a36Sopenharmony_ci */
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_cistatic void vmlfb_free_vram(struct vml_info *vinfo)
16862306a36Sopenharmony_ci{
16962306a36Sopenharmony_ci	int i;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	for (i = 0; i < vinfo->num_areas; ++i) {
17262306a36Sopenharmony_ci		vmlfb_free_vram_area(&vinfo->vram[i]);
17362306a36Sopenharmony_ci	}
17462306a36Sopenharmony_ci	vinfo->num_areas = 0;
17562306a36Sopenharmony_ci}
17662306a36Sopenharmony_ci
17762306a36Sopenharmony_ci/*
17862306a36Sopenharmony_ci * Allocate vram. Currently we try to allocate contiguous areas from the
17962306a36Sopenharmony_ci * __GFP_DMA zone and puzzle them together. A better approach would be to
18062306a36Sopenharmony_ci * allocate one contiguous area for scanout and use one-page allocations for
18162306a36Sopenharmony_ci * offscreen areas. This requires user-space and GPU virtual mappings.
18262306a36Sopenharmony_ci */
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_cistatic int vmlfb_alloc_vram(struct vml_info *vinfo,
18562306a36Sopenharmony_ci			    size_t requested,
18662306a36Sopenharmony_ci			    size_t min_total, size_t min_contig)
18762306a36Sopenharmony_ci{
18862306a36Sopenharmony_ci	int i, j;
18962306a36Sopenharmony_ci	int order;
19062306a36Sopenharmony_ci	int contiguous;
19162306a36Sopenharmony_ci	int err;
19262306a36Sopenharmony_ci	struct vram_area *va;
19362306a36Sopenharmony_ci	struct vram_area *va2;
19462306a36Sopenharmony_ci
19562306a36Sopenharmony_ci	vinfo->num_areas = 0;
19662306a36Sopenharmony_ci	for (i = 0; i < VML_VRAM_AREAS; ++i) {
19762306a36Sopenharmony_ci		va = &vinfo->vram[i];
19862306a36Sopenharmony_ci		order = 0;
19962306a36Sopenharmony_ci
20062306a36Sopenharmony_ci		while (requested > (PAGE_SIZE << order) && order <= MAX_ORDER)
20162306a36Sopenharmony_ci			order++;
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci		err = vmlfb_alloc_vram_area(va, order, 0);
20462306a36Sopenharmony_ci
20562306a36Sopenharmony_ci		if (err)
20662306a36Sopenharmony_ci			break;
20762306a36Sopenharmony_ci
20862306a36Sopenharmony_ci		if (i == 0) {
20962306a36Sopenharmony_ci			vinfo->vram_start = va->phys;
21062306a36Sopenharmony_ci			vinfo->vram_logical = (void __iomem *) va->logical;
21162306a36Sopenharmony_ci			vinfo->vram_contig_size = va->size;
21262306a36Sopenharmony_ci			vinfo->num_areas = 1;
21362306a36Sopenharmony_ci		} else {
21462306a36Sopenharmony_ci			contiguous = 0;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci			for (j = 0; j < i; ++j) {
21762306a36Sopenharmony_ci				va2 = &vinfo->vram[j];
21862306a36Sopenharmony_ci				if (va->phys + va->size == va2->phys ||
21962306a36Sopenharmony_ci				    va2->phys + va2->size == va->phys) {
22062306a36Sopenharmony_ci					contiguous = 1;
22162306a36Sopenharmony_ci					break;
22262306a36Sopenharmony_ci				}
22362306a36Sopenharmony_ci			}
22462306a36Sopenharmony_ci
22562306a36Sopenharmony_ci			if (contiguous) {
22662306a36Sopenharmony_ci				vinfo->num_areas++;
22762306a36Sopenharmony_ci				if (va->phys < vinfo->vram_start) {
22862306a36Sopenharmony_ci					vinfo->vram_start = va->phys;
22962306a36Sopenharmony_ci					vinfo->vram_logical =
23062306a36Sopenharmony_ci						(void __iomem *)va->logical;
23162306a36Sopenharmony_ci				}
23262306a36Sopenharmony_ci				vinfo->vram_contig_size += va->size;
23362306a36Sopenharmony_ci			} else {
23462306a36Sopenharmony_ci				vmlfb_free_vram_area(va);
23562306a36Sopenharmony_ci				break;
23662306a36Sopenharmony_ci			}
23762306a36Sopenharmony_ci		}
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci		if (requested < va->size)
24062306a36Sopenharmony_ci			break;
24162306a36Sopenharmony_ci		else
24262306a36Sopenharmony_ci			requested -= va->size;
24362306a36Sopenharmony_ci	}
24462306a36Sopenharmony_ci
24562306a36Sopenharmony_ci	if (vinfo->vram_contig_size > min_total &&
24662306a36Sopenharmony_ci	    vinfo->vram_contig_size > min_contig) {
24762306a36Sopenharmony_ci
24862306a36Sopenharmony_ci		printk(KERN_DEBUG MODULE_NAME
24962306a36Sopenharmony_ci		       ": Contiguous vram: %ld bytes at physical 0x%08lx.\n",
25062306a36Sopenharmony_ci		       (unsigned long)vinfo->vram_contig_size,
25162306a36Sopenharmony_ci		       (unsigned long)vinfo->vram_start);
25262306a36Sopenharmony_ci
25362306a36Sopenharmony_ci		return 0;
25462306a36Sopenharmony_ci	}
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	printk(KERN_ERR MODULE_NAME
25762306a36Sopenharmony_ci	       ": Could not allocate requested minimal amount of vram.\n");
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	vmlfb_free_vram(vinfo);
26062306a36Sopenharmony_ci
26162306a36Sopenharmony_ci	return -ENOMEM;
26262306a36Sopenharmony_ci}
26362306a36Sopenharmony_ci
26462306a36Sopenharmony_ci/*
26562306a36Sopenharmony_ci * Find the GPU to use with our display controller.
26662306a36Sopenharmony_ci */
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_cistatic int vmlfb_get_gpu(struct vml_par *par)
26962306a36Sopenharmony_ci{
27062306a36Sopenharmony_ci	mutex_lock(&vml_mutex);
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	par->gpu = pci_get_device(PCI_VENDOR_ID_INTEL, VML_DEVICE_GPU, NULL);
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_ci	if (!par->gpu) {
27562306a36Sopenharmony_ci		mutex_unlock(&vml_mutex);
27662306a36Sopenharmony_ci		return -ENODEV;
27762306a36Sopenharmony_ci	}
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	mutex_unlock(&vml_mutex);
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	if (pci_enable_device(par->gpu) < 0) {
28262306a36Sopenharmony_ci		pci_dev_put(par->gpu);
28362306a36Sopenharmony_ci		return -ENODEV;
28462306a36Sopenharmony_ci	}
28562306a36Sopenharmony_ci
28662306a36Sopenharmony_ci	return 0;
28762306a36Sopenharmony_ci}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci/*
29062306a36Sopenharmony_ci * Find a contiguous vram area that contains a given offset from vram start.
29162306a36Sopenharmony_ci */
29262306a36Sopenharmony_cistatic int vmlfb_vram_offset(struct vml_info *vinfo, unsigned long offset)
29362306a36Sopenharmony_ci{
29462306a36Sopenharmony_ci	unsigned long aoffset;
29562306a36Sopenharmony_ci	unsigned i;
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	for (i = 0; i < vinfo->num_areas; ++i) {
29862306a36Sopenharmony_ci		aoffset = offset - (vinfo->vram[i].phys - vinfo->vram_start);
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci		if (aoffset < vinfo->vram[i].size) {
30162306a36Sopenharmony_ci			return 0;
30262306a36Sopenharmony_ci		}
30362306a36Sopenharmony_ci	}
30462306a36Sopenharmony_ci
30562306a36Sopenharmony_ci	return -EINVAL;
30662306a36Sopenharmony_ci}
30762306a36Sopenharmony_ci
30862306a36Sopenharmony_ci/*
30962306a36Sopenharmony_ci * Remap the MMIO register spaces of the VDC and the GPU.
31062306a36Sopenharmony_ci */
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_cistatic int vmlfb_enable_mmio(struct vml_par *par)
31362306a36Sopenharmony_ci{
31462306a36Sopenharmony_ci	int err;
31562306a36Sopenharmony_ci
31662306a36Sopenharmony_ci	par->vdc_mem_base = pci_resource_start(par->vdc, 0);
31762306a36Sopenharmony_ci	par->vdc_mem_size = pci_resource_len(par->vdc, 0);
31862306a36Sopenharmony_ci	if (!request_mem_region(par->vdc_mem_base, par->vdc_mem_size, "vmlfb")) {
31962306a36Sopenharmony_ci		printk(KERN_ERR MODULE_NAME
32062306a36Sopenharmony_ci		       ": Could not claim display controller MMIO.\n");
32162306a36Sopenharmony_ci		return -EBUSY;
32262306a36Sopenharmony_ci	}
32362306a36Sopenharmony_ci	par->vdc_mem = ioremap(par->vdc_mem_base, par->vdc_mem_size);
32462306a36Sopenharmony_ci	if (par->vdc_mem == NULL) {
32562306a36Sopenharmony_ci		printk(KERN_ERR MODULE_NAME
32662306a36Sopenharmony_ci		       ": Could not map display controller MMIO.\n");
32762306a36Sopenharmony_ci		err = -ENOMEM;
32862306a36Sopenharmony_ci		goto out_err_0;
32962306a36Sopenharmony_ci	}
33062306a36Sopenharmony_ci
33162306a36Sopenharmony_ci	par->gpu_mem_base = pci_resource_start(par->gpu, 0);
33262306a36Sopenharmony_ci	par->gpu_mem_size = pci_resource_len(par->gpu, 0);
33362306a36Sopenharmony_ci	if (!request_mem_region(par->gpu_mem_base, par->gpu_mem_size, "vmlfb")) {
33462306a36Sopenharmony_ci		printk(KERN_ERR MODULE_NAME ": Could not claim GPU MMIO.\n");
33562306a36Sopenharmony_ci		err = -EBUSY;
33662306a36Sopenharmony_ci		goto out_err_1;
33762306a36Sopenharmony_ci	}
33862306a36Sopenharmony_ci	par->gpu_mem = ioremap(par->gpu_mem_base, par->gpu_mem_size);
33962306a36Sopenharmony_ci	if (par->gpu_mem == NULL) {
34062306a36Sopenharmony_ci		printk(KERN_ERR MODULE_NAME ": Could not map GPU MMIO.\n");
34162306a36Sopenharmony_ci		err = -ENOMEM;
34262306a36Sopenharmony_ci		goto out_err_2;
34362306a36Sopenharmony_ci	}
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ci	return 0;
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_ciout_err_2:
34862306a36Sopenharmony_ci	release_mem_region(par->gpu_mem_base, par->gpu_mem_size);
34962306a36Sopenharmony_ciout_err_1:
35062306a36Sopenharmony_ci	iounmap(par->vdc_mem);
35162306a36Sopenharmony_ciout_err_0:
35262306a36Sopenharmony_ci	release_mem_region(par->vdc_mem_base, par->vdc_mem_size);
35362306a36Sopenharmony_ci	return err;
35462306a36Sopenharmony_ci}
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ci/*
35762306a36Sopenharmony_ci * Unmap the VDC and GPU register spaces.
35862306a36Sopenharmony_ci */
35962306a36Sopenharmony_ci
36062306a36Sopenharmony_cistatic void vmlfb_disable_mmio(struct vml_par *par)
36162306a36Sopenharmony_ci{
36262306a36Sopenharmony_ci	iounmap(par->gpu_mem);
36362306a36Sopenharmony_ci	release_mem_region(par->gpu_mem_base, par->gpu_mem_size);
36462306a36Sopenharmony_ci	iounmap(par->vdc_mem);
36562306a36Sopenharmony_ci	release_mem_region(par->vdc_mem_base, par->vdc_mem_size);
36662306a36Sopenharmony_ci}
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci/*
36962306a36Sopenharmony_ci * Release and uninit the VDC and GPU.
37062306a36Sopenharmony_ci */
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_cistatic void vmlfb_release_devices(struct vml_par *par)
37362306a36Sopenharmony_ci{
37462306a36Sopenharmony_ci	if (atomic_dec_and_test(&par->refcount)) {
37562306a36Sopenharmony_ci		pci_disable_device(par->gpu);
37662306a36Sopenharmony_ci		pci_disable_device(par->vdc);
37762306a36Sopenharmony_ci	}
37862306a36Sopenharmony_ci}
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci/*
38162306a36Sopenharmony_ci * Free up allocated resources for a device.
38262306a36Sopenharmony_ci */
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_cistatic void vml_pci_remove(struct pci_dev *dev)
38562306a36Sopenharmony_ci{
38662306a36Sopenharmony_ci	struct fb_info *info;
38762306a36Sopenharmony_ci	struct vml_info *vinfo;
38862306a36Sopenharmony_ci	struct vml_par *par;
38962306a36Sopenharmony_ci
39062306a36Sopenharmony_ci	info = pci_get_drvdata(dev);
39162306a36Sopenharmony_ci	if (info) {
39262306a36Sopenharmony_ci		vinfo = container_of(info, struct vml_info, info);
39362306a36Sopenharmony_ci		par = vinfo->par;
39462306a36Sopenharmony_ci		mutex_lock(&vml_mutex);
39562306a36Sopenharmony_ci		unregister_framebuffer(info);
39662306a36Sopenharmony_ci		fb_dealloc_cmap(&info->cmap);
39762306a36Sopenharmony_ci		vmlfb_free_vram(vinfo);
39862306a36Sopenharmony_ci		vmlfb_disable_mmio(par);
39962306a36Sopenharmony_ci		vmlfb_release_devices(par);
40062306a36Sopenharmony_ci		kfree(vinfo);
40162306a36Sopenharmony_ci		kfree(par);
40262306a36Sopenharmony_ci		mutex_unlock(&vml_mutex);
40362306a36Sopenharmony_ci	}
40462306a36Sopenharmony_ci}
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_cistatic void vmlfb_set_pref_pixel_format(struct fb_var_screeninfo *var)
40762306a36Sopenharmony_ci{
40862306a36Sopenharmony_ci	switch (var->bits_per_pixel) {
40962306a36Sopenharmony_ci	case 16:
41062306a36Sopenharmony_ci		var->blue.offset = 0;
41162306a36Sopenharmony_ci		var->blue.length = 5;
41262306a36Sopenharmony_ci		var->green.offset = 5;
41362306a36Sopenharmony_ci		var->green.length = 5;
41462306a36Sopenharmony_ci		var->red.offset = 10;
41562306a36Sopenharmony_ci		var->red.length = 5;
41662306a36Sopenharmony_ci		var->transp.offset = 15;
41762306a36Sopenharmony_ci		var->transp.length = 1;
41862306a36Sopenharmony_ci		break;
41962306a36Sopenharmony_ci	case 32:
42062306a36Sopenharmony_ci		var->blue.offset = 0;
42162306a36Sopenharmony_ci		var->blue.length = 8;
42262306a36Sopenharmony_ci		var->green.offset = 8;
42362306a36Sopenharmony_ci		var->green.length = 8;
42462306a36Sopenharmony_ci		var->red.offset = 16;
42562306a36Sopenharmony_ci		var->red.length = 8;
42662306a36Sopenharmony_ci		var->transp.offset = 24;
42762306a36Sopenharmony_ci		var->transp.length = 0;
42862306a36Sopenharmony_ci		break;
42962306a36Sopenharmony_ci	default:
43062306a36Sopenharmony_ci		break;
43162306a36Sopenharmony_ci	}
43262306a36Sopenharmony_ci
43362306a36Sopenharmony_ci	var->blue.msb_right = var->green.msb_right =
43462306a36Sopenharmony_ci	    var->red.msb_right = var->transp.msb_right = 0;
43562306a36Sopenharmony_ci}
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci/*
43862306a36Sopenharmony_ci * Device initialization.
43962306a36Sopenharmony_ci * We initialize one vml_par struct per device and one vml_info
44062306a36Sopenharmony_ci * struct per pipe. Currently we have only one pipe.
44162306a36Sopenharmony_ci */
44262306a36Sopenharmony_ci
44362306a36Sopenharmony_cistatic int vml_pci_probe(struct pci_dev *dev, const struct pci_device_id *id)
44462306a36Sopenharmony_ci{
44562306a36Sopenharmony_ci	struct vml_info *vinfo;
44662306a36Sopenharmony_ci	struct fb_info *info;
44762306a36Sopenharmony_ci	struct vml_par *par;
44862306a36Sopenharmony_ci	int err;
44962306a36Sopenharmony_ci
45062306a36Sopenharmony_ci	err = aperture_remove_conflicting_pci_devices(dev, "vmlfb");
45162306a36Sopenharmony_ci	if (err)
45262306a36Sopenharmony_ci		return err;
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ci	par = kzalloc(sizeof(*par), GFP_KERNEL);
45562306a36Sopenharmony_ci	if (par == NULL)
45662306a36Sopenharmony_ci		return -ENOMEM;
45762306a36Sopenharmony_ci
45862306a36Sopenharmony_ci	vinfo = kzalloc(sizeof(*vinfo), GFP_KERNEL);
45962306a36Sopenharmony_ci	if (vinfo == NULL) {
46062306a36Sopenharmony_ci		err = -ENOMEM;
46162306a36Sopenharmony_ci		goto out_err_0;
46262306a36Sopenharmony_ci	}
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	vinfo->par = par;
46562306a36Sopenharmony_ci	par->vdc = dev;
46662306a36Sopenharmony_ci	atomic_set(&par->refcount, 1);
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	switch (id->device) {
46962306a36Sopenharmony_ci	case VML_DEVICE_VDC:
47062306a36Sopenharmony_ci		if ((err = vmlfb_get_gpu(par)))
47162306a36Sopenharmony_ci			goto out_err_1;
47262306a36Sopenharmony_ci		pci_set_drvdata(dev, &vinfo->info);
47362306a36Sopenharmony_ci		break;
47462306a36Sopenharmony_ci	default:
47562306a36Sopenharmony_ci		err = -ENODEV;
47662306a36Sopenharmony_ci		goto out_err_1;
47762306a36Sopenharmony_ci	}
47862306a36Sopenharmony_ci
47962306a36Sopenharmony_ci	info = &vinfo->info;
48062306a36Sopenharmony_ci	info->flags = FBINFO_PARTIAL_PAN_OK;
48162306a36Sopenharmony_ci
48262306a36Sopenharmony_ci	err = vmlfb_enable_mmio(par);
48362306a36Sopenharmony_ci	if (err)
48462306a36Sopenharmony_ci		goto out_err_2;
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	err = vmlfb_alloc_vram(vinfo, vml_mem_requested,
48762306a36Sopenharmony_ci			       vml_mem_contig, vml_mem_min);
48862306a36Sopenharmony_ci	if (err)
48962306a36Sopenharmony_ci		goto out_err_3;
49062306a36Sopenharmony_ci
49162306a36Sopenharmony_ci	strcpy(info->fix.id, "Vermilion Range");
49262306a36Sopenharmony_ci	info->fix.mmio_start = 0;
49362306a36Sopenharmony_ci	info->fix.mmio_len = 0;
49462306a36Sopenharmony_ci	info->fix.smem_start = vinfo->vram_start;
49562306a36Sopenharmony_ci	info->fix.smem_len = vinfo->vram_contig_size;
49662306a36Sopenharmony_ci	info->fix.type = FB_TYPE_PACKED_PIXELS;
49762306a36Sopenharmony_ci	info->fix.visual = FB_VISUAL_TRUECOLOR;
49862306a36Sopenharmony_ci	info->fix.ypanstep = 1;
49962306a36Sopenharmony_ci	info->fix.xpanstep = 1;
50062306a36Sopenharmony_ci	info->fix.ywrapstep = 0;
50162306a36Sopenharmony_ci	info->fix.accel = FB_ACCEL_NONE;
50262306a36Sopenharmony_ci	info->screen_base = vinfo->vram_logical;
50362306a36Sopenharmony_ci	info->pseudo_palette = vinfo->pseudo_palette;
50462306a36Sopenharmony_ci	info->par = par;
50562306a36Sopenharmony_ci	info->fbops = &vmlfb_ops;
50662306a36Sopenharmony_ci	info->device = &dev->dev;
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	INIT_LIST_HEAD(&vinfo->head);
50962306a36Sopenharmony_ci	vinfo->pipe_disabled = 1;
51062306a36Sopenharmony_ci	vinfo->cur_blank_mode = FB_BLANK_UNBLANK;
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_ci	info->var.grayscale = 0;
51362306a36Sopenharmony_ci	info->var.bits_per_pixel = 16;
51462306a36Sopenharmony_ci	vmlfb_set_pref_pixel_format(&info->var);
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci	if (!fb_find_mode
51762306a36Sopenharmony_ci	    (&info->var, info, vml_default_mode, NULL, 0, &defaultmode, 16)) {
51862306a36Sopenharmony_ci		printk(KERN_ERR MODULE_NAME ": Could not find initial mode\n");
51962306a36Sopenharmony_ci	}
52062306a36Sopenharmony_ci
52162306a36Sopenharmony_ci	if (fb_alloc_cmap(&info->cmap, 256, 1) < 0) {
52262306a36Sopenharmony_ci		err = -ENOMEM;
52362306a36Sopenharmony_ci		goto out_err_4;
52462306a36Sopenharmony_ci	}
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	err = register_framebuffer(info);
52762306a36Sopenharmony_ci	if (err) {
52862306a36Sopenharmony_ci		printk(KERN_ERR MODULE_NAME ": Register framebuffer error.\n");
52962306a36Sopenharmony_ci		goto out_err_5;
53062306a36Sopenharmony_ci	}
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	printk("Initialized vmlfb\n");
53362306a36Sopenharmony_ci
53462306a36Sopenharmony_ci	return 0;
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ciout_err_5:
53762306a36Sopenharmony_ci	fb_dealloc_cmap(&info->cmap);
53862306a36Sopenharmony_ciout_err_4:
53962306a36Sopenharmony_ci	vmlfb_free_vram(vinfo);
54062306a36Sopenharmony_ciout_err_3:
54162306a36Sopenharmony_ci	vmlfb_disable_mmio(par);
54262306a36Sopenharmony_ciout_err_2:
54362306a36Sopenharmony_ci	vmlfb_release_devices(par);
54462306a36Sopenharmony_ciout_err_1:
54562306a36Sopenharmony_ci	kfree(vinfo);
54662306a36Sopenharmony_ciout_err_0:
54762306a36Sopenharmony_ci	kfree(par);
54862306a36Sopenharmony_ci	return err;
54962306a36Sopenharmony_ci}
55062306a36Sopenharmony_ci
55162306a36Sopenharmony_cistatic int vmlfb_open(struct fb_info *info, int user)
55262306a36Sopenharmony_ci{
55362306a36Sopenharmony_ci	/*
55462306a36Sopenharmony_ci	 * Save registers here?
55562306a36Sopenharmony_ci	 */
55662306a36Sopenharmony_ci	return 0;
55762306a36Sopenharmony_ci}
55862306a36Sopenharmony_ci
55962306a36Sopenharmony_cistatic int vmlfb_release(struct fb_info *info, int user)
56062306a36Sopenharmony_ci{
56162306a36Sopenharmony_ci	/*
56262306a36Sopenharmony_ci	 * Restore registers here.
56362306a36Sopenharmony_ci	 */
56462306a36Sopenharmony_ci
56562306a36Sopenharmony_ci	return 0;
56662306a36Sopenharmony_ci}
56762306a36Sopenharmony_ci
56862306a36Sopenharmony_cistatic int vml_nearest_clock(int clock)
56962306a36Sopenharmony_ci{
57062306a36Sopenharmony_ci
57162306a36Sopenharmony_ci	int i;
57262306a36Sopenharmony_ci	int cur_index;
57362306a36Sopenharmony_ci	int cur_diff;
57462306a36Sopenharmony_ci	int diff;
57562306a36Sopenharmony_ci
57662306a36Sopenharmony_ci	cur_index = 0;
57762306a36Sopenharmony_ci	cur_diff = clock - vml_clocks[0];
57862306a36Sopenharmony_ci	cur_diff = (cur_diff < 0) ? -cur_diff : cur_diff;
57962306a36Sopenharmony_ci	for (i = 1; i < vml_num_clocks; ++i) {
58062306a36Sopenharmony_ci		diff = clock - vml_clocks[i];
58162306a36Sopenharmony_ci		diff = (diff < 0) ? -diff : diff;
58262306a36Sopenharmony_ci		if (diff < cur_diff) {
58362306a36Sopenharmony_ci			cur_index = i;
58462306a36Sopenharmony_ci			cur_diff = diff;
58562306a36Sopenharmony_ci		}
58662306a36Sopenharmony_ci	}
58762306a36Sopenharmony_ci	return vml_clocks[cur_index];
58862306a36Sopenharmony_ci}
58962306a36Sopenharmony_ci
59062306a36Sopenharmony_cistatic int vmlfb_check_var_locked(struct fb_var_screeninfo *var,
59162306a36Sopenharmony_ci				  struct vml_info *vinfo)
59262306a36Sopenharmony_ci{
59362306a36Sopenharmony_ci	u32 pitch;
59462306a36Sopenharmony_ci	u64 mem;
59562306a36Sopenharmony_ci	int nearest_clock;
59662306a36Sopenharmony_ci	int clock;
59762306a36Sopenharmony_ci	int clock_diff;
59862306a36Sopenharmony_ci	struct fb_var_screeninfo v;
59962306a36Sopenharmony_ci
60062306a36Sopenharmony_ci	v = *var;
60162306a36Sopenharmony_ci	clock = PICOS2KHZ(var->pixclock);
60262306a36Sopenharmony_ci
60362306a36Sopenharmony_ci	if (subsys && subsys->nearest_clock) {
60462306a36Sopenharmony_ci		nearest_clock = subsys->nearest_clock(subsys, clock);
60562306a36Sopenharmony_ci	} else {
60662306a36Sopenharmony_ci		nearest_clock = vml_nearest_clock(clock);
60762306a36Sopenharmony_ci	}
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_ci	/*
61062306a36Sopenharmony_ci	 * Accept a 20% diff.
61162306a36Sopenharmony_ci	 */
61262306a36Sopenharmony_ci
61362306a36Sopenharmony_ci	clock_diff = nearest_clock - clock;
61462306a36Sopenharmony_ci	clock_diff = (clock_diff < 0) ? -clock_diff : clock_diff;
61562306a36Sopenharmony_ci	if (clock_diff > clock / 5) {
61662306a36Sopenharmony_ci#if 0
61762306a36Sopenharmony_ci		printk(KERN_DEBUG MODULE_NAME ": Diff failure. %d %d\n",clock_diff,clock);
61862306a36Sopenharmony_ci#endif
61962306a36Sopenharmony_ci		return -EINVAL;
62062306a36Sopenharmony_ci	}
62162306a36Sopenharmony_ci
62262306a36Sopenharmony_ci	v.pixclock = KHZ2PICOS(nearest_clock);
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	if (var->xres > VML_MAX_XRES || var->yres > VML_MAX_YRES) {
62562306a36Sopenharmony_ci		printk(KERN_DEBUG MODULE_NAME ": Resolution failure.\n");
62662306a36Sopenharmony_ci		return -EINVAL;
62762306a36Sopenharmony_ci	}
62862306a36Sopenharmony_ci	if (var->xres_virtual > VML_MAX_XRES_VIRTUAL) {
62962306a36Sopenharmony_ci		printk(KERN_DEBUG MODULE_NAME
63062306a36Sopenharmony_ci		       ": Virtual resolution failure.\n");
63162306a36Sopenharmony_ci		return -EINVAL;
63262306a36Sopenharmony_ci	}
63362306a36Sopenharmony_ci	switch (v.bits_per_pixel) {
63462306a36Sopenharmony_ci	case 0 ... 16:
63562306a36Sopenharmony_ci		v.bits_per_pixel = 16;
63662306a36Sopenharmony_ci		break;
63762306a36Sopenharmony_ci	case 17 ... 32:
63862306a36Sopenharmony_ci		v.bits_per_pixel = 32;
63962306a36Sopenharmony_ci		break;
64062306a36Sopenharmony_ci	default:
64162306a36Sopenharmony_ci		printk(KERN_DEBUG MODULE_NAME ": Invalid bpp: %d.\n",
64262306a36Sopenharmony_ci		       var->bits_per_pixel);
64362306a36Sopenharmony_ci		return -EINVAL;
64462306a36Sopenharmony_ci	}
64562306a36Sopenharmony_ci
64662306a36Sopenharmony_ci	pitch = ALIGN((var->xres * var->bits_per_pixel) >> 3, 0x40);
64762306a36Sopenharmony_ci	mem = (u64)pitch * var->yres_virtual;
64862306a36Sopenharmony_ci	if (mem > vinfo->vram_contig_size) {
64962306a36Sopenharmony_ci		return -ENOMEM;
65062306a36Sopenharmony_ci	}
65162306a36Sopenharmony_ci
65262306a36Sopenharmony_ci	switch (v.bits_per_pixel) {
65362306a36Sopenharmony_ci	case 16:
65462306a36Sopenharmony_ci		if (var->blue.offset != 0 ||
65562306a36Sopenharmony_ci		    var->blue.length != 5 ||
65662306a36Sopenharmony_ci		    var->green.offset != 5 ||
65762306a36Sopenharmony_ci		    var->green.length != 5 ||
65862306a36Sopenharmony_ci		    var->red.offset != 10 ||
65962306a36Sopenharmony_ci		    var->red.length != 5 ||
66062306a36Sopenharmony_ci		    var->transp.offset != 15 || var->transp.length != 1) {
66162306a36Sopenharmony_ci			vmlfb_set_pref_pixel_format(&v);
66262306a36Sopenharmony_ci		}
66362306a36Sopenharmony_ci		break;
66462306a36Sopenharmony_ci	case 32:
66562306a36Sopenharmony_ci		if (var->blue.offset != 0 ||
66662306a36Sopenharmony_ci		    var->blue.length != 8 ||
66762306a36Sopenharmony_ci		    var->green.offset != 8 ||
66862306a36Sopenharmony_ci		    var->green.length != 8 ||
66962306a36Sopenharmony_ci		    var->red.offset != 16 ||
67062306a36Sopenharmony_ci		    var->red.length != 8 ||
67162306a36Sopenharmony_ci		    (var->transp.length != 0 && var->transp.length != 8) ||
67262306a36Sopenharmony_ci		    (var->transp.length == 8 && var->transp.offset != 24)) {
67362306a36Sopenharmony_ci			vmlfb_set_pref_pixel_format(&v);
67462306a36Sopenharmony_ci		}
67562306a36Sopenharmony_ci		break;
67662306a36Sopenharmony_ci	default:
67762306a36Sopenharmony_ci		return -EINVAL;
67862306a36Sopenharmony_ci	}
67962306a36Sopenharmony_ci
68062306a36Sopenharmony_ci	*var = v;
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	return 0;
68362306a36Sopenharmony_ci}
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_cistatic int vmlfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
68662306a36Sopenharmony_ci{
68762306a36Sopenharmony_ci	struct vml_info *vinfo = container_of(info, struct vml_info, info);
68862306a36Sopenharmony_ci	int ret;
68962306a36Sopenharmony_ci
69062306a36Sopenharmony_ci	mutex_lock(&vml_mutex);
69162306a36Sopenharmony_ci	ret = vmlfb_check_var_locked(var, vinfo);
69262306a36Sopenharmony_ci	mutex_unlock(&vml_mutex);
69362306a36Sopenharmony_ci
69462306a36Sopenharmony_ci	return ret;
69562306a36Sopenharmony_ci}
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_cistatic void vml_wait_vblank(struct vml_info *vinfo)
69862306a36Sopenharmony_ci{
69962306a36Sopenharmony_ci	/* Wait for vblank. For now, just wait for a 50Hz cycle (20ms)) */
70062306a36Sopenharmony_ci	mdelay(20);
70162306a36Sopenharmony_ci}
70262306a36Sopenharmony_ci
70362306a36Sopenharmony_cistatic void vmlfb_disable_pipe(struct vml_info *vinfo)
70462306a36Sopenharmony_ci{
70562306a36Sopenharmony_ci	struct vml_par *par = vinfo->par;
70662306a36Sopenharmony_ci
70762306a36Sopenharmony_ci	/* Disable the MDVO pad */
70862306a36Sopenharmony_ci	VML_WRITE32(par, VML_RCOMPSTAT, 0);
70962306a36Sopenharmony_ci	while (!(VML_READ32(par, VML_RCOMPSTAT) & VML_MDVO_VDC_I_RCOMP)) ;
71062306a36Sopenharmony_ci
71162306a36Sopenharmony_ci	/* Disable display planes */
71262306a36Sopenharmony_ci	VML_WRITE32(par, VML_DSPCCNTR,
71362306a36Sopenharmony_ci		    VML_READ32(par, VML_DSPCCNTR) & ~VML_GFX_ENABLE);
71462306a36Sopenharmony_ci	(void)VML_READ32(par, VML_DSPCCNTR);
71562306a36Sopenharmony_ci	/* Wait for vblank for the disable to take effect */
71662306a36Sopenharmony_ci	vml_wait_vblank(vinfo);
71762306a36Sopenharmony_ci
71862306a36Sopenharmony_ci	/* Next, disable display pipes */
71962306a36Sopenharmony_ci	VML_WRITE32(par, VML_PIPEACONF, 0);
72062306a36Sopenharmony_ci	(void)VML_READ32(par, VML_PIPEACONF);
72162306a36Sopenharmony_ci
72262306a36Sopenharmony_ci	vinfo->pipe_disabled = 1;
72362306a36Sopenharmony_ci}
72462306a36Sopenharmony_ci
72562306a36Sopenharmony_ci#ifdef VERMILION_DEBUG
72662306a36Sopenharmony_cistatic void vml_dump_regs(struct vml_info *vinfo)
72762306a36Sopenharmony_ci{
72862306a36Sopenharmony_ci	struct vml_par *par = vinfo->par;
72962306a36Sopenharmony_ci
73062306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": Modesetting register dump:\n");
73162306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tHTOTAL_A         : 0x%08x\n",
73262306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_HTOTAL_A));
73362306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tHBLANK_A         : 0x%08x\n",
73462306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_HBLANK_A));
73562306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tHSYNC_A          : 0x%08x\n",
73662306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_HSYNC_A));
73762306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tVTOTAL_A         : 0x%08x\n",
73862306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_VTOTAL_A));
73962306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tVBLANK_A         : 0x%08x\n",
74062306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_VBLANK_A));
74162306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tVSYNC_A          : 0x%08x\n",
74262306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_VSYNC_A));
74362306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tDSPCSTRIDE       : 0x%08x\n",
74462306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_DSPCSTRIDE));
74562306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tDSPCSIZE         : 0x%08x\n",
74662306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_DSPCSIZE));
74762306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tDSPCPOS          : 0x%08x\n",
74862306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_DSPCPOS));
74962306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tDSPARB           : 0x%08x\n",
75062306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_DSPARB));
75162306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tDSPCADDR         : 0x%08x\n",
75262306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_DSPCADDR));
75362306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tBCLRPAT_A        : 0x%08x\n",
75462306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_BCLRPAT_A));
75562306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tCANVSCLR_A       : 0x%08x\n",
75662306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_CANVSCLR_A));
75762306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tPIPEASRC         : 0x%08x\n",
75862306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_PIPEASRC));
75962306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tPIPEACONF        : 0x%08x\n",
76062306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_PIPEACONF));
76162306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tDSPCCNTR         : 0x%08x\n",
76262306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_DSPCCNTR));
76362306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": \tRCOMPSTAT        : 0x%08x\n",
76462306a36Sopenharmony_ci	       (unsigned)VML_READ32(par, VML_RCOMPSTAT));
76562306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": End of modesetting register dump.\n");
76662306a36Sopenharmony_ci}
76762306a36Sopenharmony_ci#endif
76862306a36Sopenharmony_ci
76962306a36Sopenharmony_cistatic int vmlfb_set_par_locked(struct vml_info *vinfo)
77062306a36Sopenharmony_ci{
77162306a36Sopenharmony_ci	struct vml_par *par = vinfo->par;
77262306a36Sopenharmony_ci	struct fb_info *info = &vinfo->info;
77362306a36Sopenharmony_ci	struct fb_var_screeninfo *var = &info->var;
77462306a36Sopenharmony_ci	u32 htotal, hactive, hblank_start, hblank_end, hsync_start, hsync_end;
77562306a36Sopenharmony_ci	u32 vtotal, vactive, vblank_start, vblank_end, vsync_start, vsync_end;
77662306a36Sopenharmony_ci	u32 dspcntr;
77762306a36Sopenharmony_ci	int clock;
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_ci	vinfo->bytes_per_pixel = var->bits_per_pixel >> 3;
78062306a36Sopenharmony_ci	vinfo->stride = ALIGN(var->xres_virtual * vinfo->bytes_per_pixel, 0x40);
78162306a36Sopenharmony_ci	info->fix.line_length = vinfo->stride;
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci	if (!subsys)
78462306a36Sopenharmony_ci		return 0;
78562306a36Sopenharmony_ci
78662306a36Sopenharmony_ci	htotal =
78762306a36Sopenharmony_ci	    var->xres + var->right_margin + var->hsync_len + var->left_margin;
78862306a36Sopenharmony_ci	hactive = var->xres;
78962306a36Sopenharmony_ci	hblank_start = var->xres;
79062306a36Sopenharmony_ci	hblank_end = htotal;
79162306a36Sopenharmony_ci	hsync_start = hactive + var->right_margin;
79262306a36Sopenharmony_ci	hsync_end = hsync_start + var->hsync_len;
79362306a36Sopenharmony_ci
79462306a36Sopenharmony_ci	vtotal =
79562306a36Sopenharmony_ci	    var->yres + var->lower_margin + var->vsync_len + var->upper_margin;
79662306a36Sopenharmony_ci	vactive = var->yres;
79762306a36Sopenharmony_ci	vblank_start = var->yres;
79862306a36Sopenharmony_ci	vblank_end = vtotal;
79962306a36Sopenharmony_ci	vsync_start = vactive + var->lower_margin;
80062306a36Sopenharmony_ci	vsync_end = vsync_start + var->vsync_len;
80162306a36Sopenharmony_ci
80262306a36Sopenharmony_ci	dspcntr = VML_GFX_ENABLE | VML_GFX_GAMMABYPASS;
80362306a36Sopenharmony_ci	clock = PICOS2KHZ(var->pixclock);
80462306a36Sopenharmony_ci
80562306a36Sopenharmony_ci	if (subsys->nearest_clock) {
80662306a36Sopenharmony_ci		clock = subsys->nearest_clock(subsys, clock);
80762306a36Sopenharmony_ci	} else {
80862306a36Sopenharmony_ci		clock = vml_nearest_clock(clock);
80962306a36Sopenharmony_ci	}
81062306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME
81162306a36Sopenharmony_ci	       ": Set mode Hfreq : %d kHz, Vfreq : %d Hz.\n", clock / htotal,
81262306a36Sopenharmony_ci	       ((clock / htotal) * 1000) / vtotal);
81362306a36Sopenharmony_ci
81462306a36Sopenharmony_ci	switch (var->bits_per_pixel) {
81562306a36Sopenharmony_ci	case 16:
81662306a36Sopenharmony_ci		dspcntr |= VML_GFX_ARGB1555;
81762306a36Sopenharmony_ci		break;
81862306a36Sopenharmony_ci	case 32:
81962306a36Sopenharmony_ci		if (var->transp.length == 8)
82062306a36Sopenharmony_ci			dspcntr |= VML_GFX_ARGB8888 | VML_GFX_ALPHAMULT;
82162306a36Sopenharmony_ci		else
82262306a36Sopenharmony_ci			dspcntr |= VML_GFX_RGB0888;
82362306a36Sopenharmony_ci		break;
82462306a36Sopenharmony_ci	default:
82562306a36Sopenharmony_ci		return -EINVAL;
82662306a36Sopenharmony_ci	}
82762306a36Sopenharmony_ci
82862306a36Sopenharmony_ci	vmlfb_disable_pipe(vinfo);
82962306a36Sopenharmony_ci	mb();
83062306a36Sopenharmony_ci
83162306a36Sopenharmony_ci	if (subsys->set_clock)
83262306a36Sopenharmony_ci		subsys->set_clock(subsys, clock);
83362306a36Sopenharmony_ci	else
83462306a36Sopenharmony_ci		return -EINVAL;
83562306a36Sopenharmony_ci
83662306a36Sopenharmony_ci	VML_WRITE32(par, VML_HTOTAL_A, ((htotal - 1) << 16) | (hactive - 1));
83762306a36Sopenharmony_ci	VML_WRITE32(par, VML_HBLANK_A,
83862306a36Sopenharmony_ci		    ((hblank_end - 1) << 16) | (hblank_start - 1));
83962306a36Sopenharmony_ci	VML_WRITE32(par, VML_HSYNC_A,
84062306a36Sopenharmony_ci		    ((hsync_end - 1) << 16) | (hsync_start - 1));
84162306a36Sopenharmony_ci	VML_WRITE32(par, VML_VTOTAL_A, ((vtotal - 1) << 16) | (vactive - 1));
84262306a36Sopenharmony_ci	VML_WRITE32(par, VML_VBLANK_A,
84362306a36Sopenharmony_ci		    ((vblank_end - 1) << 16) | (vblank_start - 1));
84462306a36Sopenharmony_ci	VML_WRITE32(par, VML_VSYNC_A,
84562306a36Sopenharmony_ci		    ((vsync_end - 1) << 16) | (vsync_start - 1));
84662306a36Sopenharmony_ci	VML_WRITE32(par, VML_DSPCSTRIDE, vinfo->stride);
84762306a36Sopenharmony_ci	VML_WRITE32(par, VML_DSPCSIZE,
84862306a36Sopenharmony_ci		    ((var->yres - 1) << 16) | (var->xres - 1));
84962306a36Sopenharmony_ci	VML_WRITE32(par, VML_DSPCPOS, 0x00000000);
85062306a36Sopenharmony_ci	VML_WRITE32(par, VML_DSPARB, VML_FIFO_DEFAULT);
85162306a36Sopenharmony_ci	VML_WRITE32(par, VML_BCLRPAT_A, 0x00000000);
85262306a36Sopenharmony_ci	VML_WRITE32(par, VML_CANVSCLR_A, 0x00000000);
85362306a36Sopenharmony_ci	VML_WRITE32(par, VML_PIPEASRC,
85462306a36Sopenharmony_ci		    ((var->xres - 1) << 16) | (var->yres - 1));
85562306a36Sopenharmony_ci
85662306a36Sopenharmony_ci	wmb();
85762306a36Sopenharmony_ci	VML_WRITE32(par, VML_PIPEACONF, VML_PIPE_ENABLE);
85862306a36Sopenharmony_ci	wmb();
85962306a36Sopenharmony_ci	VML_WRITE32(par, VML_DSPCCNTR, dspcntr);
86062306a36Sopenharmony_ci	wmb();
86162306a36Sopenharmony_ci	VML_WRITE32(par, VML_DSPCADDR, (u32) vinfo->vram_start +
86262306a36Sopenharmony_ci		    var->yoffset * vinfo->stride +
86362306a36Sopenharmony_ci		    var->xoffset * vinfo->bytes_per_pixel);
86462306a36Sopenharmony_ci
86562306a36Sopenharmony_ci	VML_WRITE32(par, VML_RCOMPSTAT, VML_MDVO_PAD_ENABLE);
86662306a36Sopenharmony_ci
86762306a36Sopenharmony_ci	while (!(VML_READ32(par, VML_RCOMPSTAT) &
86862306a36Sopenharmony_ci		 (VML_MDVO_VDC_I_RCOMP | VML_MDVO_PAD_ENABLE))) ;
86962306a36Sopenharmony_ci
87062306a36Sopenharmony_ci	vinfo->pipe_disabled = 0;
87162306a36Sopenharmony_ci#ifdef VERMILION_DEBUG
87262306a36Sopenharmony_ci	vml_dump_regs(vinfo);
87362306a36Sopenharmony_ci#endif
87462306a36Sopenharmony_ci
87562306a36Sopenharmony_ci	return 0;
87662306a36Sopenharmony_ci}
87762306a36Sopenharmony_ci
87862306a36Sopenharmony_cistatic int vmlfb_set_par(struct fb_info *info)
87962306a36Sopenharmony_ci{
88062306a36Sopenharmony_ci	struct vml_info *vinfo = container_of(info, struct vml_info, info);
88162306a36Sopenharmony_ci	int ret;
88262306a36Sopenharmony_ci
88362306a36Sopenharmony_ci	mutex_lock(&vml_mutex);
88462306a36Sopenharmony_ci	list_move(&vinfo->head, (subsys) ? &global_has_mode : &global_no_mode);
88562306a36Sopenharmony_ci	ret = vmlfb_set_par_locked(vinfo);
88662306a36Sopenharmony_ci
88762306a36Sopenharmony_ci	mutex_unlock(&vml_mutex);
88862306a36Sopenharmony_ci	return ret;
88962306a36Sopenharmony_ci}
89062306a36Sopenharmony_ci
89162306a36Sopenharmony_cistatic int vmlfb_blank_locked(struct vml_info *vinfo)
89262306a36Sopenharmony_ci{
89362306a36Sopenharmony_ci	struct vml_par *par = vinfo->par;
89462306a36Sopenharmony_ci	u32 cur = VML_READ32(par, VML_PIPEACONF);
89562306a36Sopenharmony_ci
89662306a36Sopenharmony_ci	switch (vinfo->cur_blank_mode) {
89762306a36Sopenharmony_ci	case FB_BLANK_UNBLANK:
89862306a36Sopenharmony_ci		if (vinfo->pipe_disabled) {
89962306a36Sopenharmony_ci			vmlfb_set_par_locked(vinfo);
90062306a36Sopenharmony_ci		}
90162306a36Sopenharmony_ci		VML_WRITE32(par, VML_PIPEACONF, cur & ~VML_PIPE_FORCE_BORDER);
90262306a36Sopenharmony_ci		(void)VML_READ32(par, VML_PIPEACONF);
90362306a36Sopenharmony_ci		break;
90462306a36Sopenharmony_ci	case FB_BLANK_NORMAL:
90562306a36Sopenharmony_ci		if (vinfo->pipe_disabled) {
90662306a36Sopenharmony_ci			vmlfb_set_par_locked(vinfo);
90762306a36Sopenharmony_ci		}
90862306a36Sopenharmony_ci		VML_WRITE32(par, VML_PIPEACONF, cur | VML_PIPE_FORCE_BORDER);
90962306a36Sopenharmony_ci		(void)VML_READ32(par, VML_PIPEACONF);
91062306a36Sopenharmony_ci		break;
91162306a36Sopenharmony_ci	case FB_BLANK_VSYNC_SUSPEND:
91262306a36Sopenharmony_ci	case FB_BLANK_HSYNC_SUSPEND:
91362306a36Sopenharmony_ci		if (!vinfo->pipe_disabled) {
91462306a36Sopenharmony_ci			vmlfb_disable_pipe(vinfo);
91562306a36Sopenharmony_ci		}
91662306a36Sopenharmony_ci		break;
91762306a36Sopenharmony_ci	case FB_BLANK_POWERDOWN:
91862306a36Sopenharmony_ci		if (!vinfo->pipe_disabled) {
91962306a36Sopenharmony_ci			vmlfb_disable_pipe(vinfo);
92062306a36Sopenharmony_ci		}
92162306a36Sopenharmony_ci		break;
92262306a36Sopenharmony_ci	default:
92362306a36Sopenharmony_ci		return -EINVAL;
92462306a36Sopenharmony_ci	}
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_ci	return 0;
92762306a36Sopenharmony_ci}
92862306a36Sopenharmony_ci
92962306a36Sopenharmony_cistatic int vmlfb_blank(int blank_mode, struct fb_info *info)
93062306a36Sopenharmony_ci{
93162306a36Sopenharmony_ci	struct vml_info *vinfo = container_of(info, struct vml_info, info);
93262306a36Sopenharmony_ci	int ret;
93362306a36Sopenharmony_ci
93462306a36Sopenharmony_ci	mutex_lock(&vml_mutex);
93562306a36Sopenharmony_ci	vinfo->cur_blank_mode = blank_mode;
93662306a36Sopenharmony_ci	ret = vmlfb_blank_locked(vinfo);
93762306a36Sopenharmony_ci	mutex_unlock(&vml_mutex);
93862306a36Sopenharmony_ci	return ret;
93962306a36Sopenharmony_ci}
94062306a36Sopenharmony_ci
94162306a36Sopenharmony_cistatic int vmlfb_pan_display(struct fb_var_screeninfo *var,
94262306a36Sopenharmony_ci			     struct fb_info *info)
94362306a36Sopenharmony_ci{
94462306a36Sopenharmony_ci	struct vml_info *vinfo = container_of(info, struct vml_info, info);
94562306a36Sopenharmony_ci	struct vml_par *par = vinfo->par;
94662306a36Sopenharmony_ci
94762306a36Sopenharmony_ci	mutex_lock(&vml_mutex);
94862306a36Sopenharmony_ci	VML_WRITE32(par, VML_DSPCADDR, (u32) vinfo->vram_start +
94962306a36Sopenharmony_ci		    var->yoffset * vinfo->stride +
95062306a36Sopenharmony_ci		    var->xoffset * vinfo->bytes_per_pixel);
95162306a36Sopenharmony_ci	(void)VML_READ32(par, VML_DSPCADDR);
95262306a36Sopenharmony_ci	mutex_unlock(&vml_mutex);
95362306a36Sopenharmony_ci
95462306a36Sopenharmony_ci	return 0;
95562306a36Sopenharmony_ci}
95662306a36Sopenharmony_ci
95762306a36Sopenharmony_cistatic int vmlfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue,
95862306a36Sopenharmony_ci			   u_int transp, struct fb_info *info)
95962306a36Sopenharmony_ci{
96062306a36Sopenharmony_ci	u32 v;
96162306a36Sopenharmony_ci
96262306a36Sopenharmony_ci	if (regno >= 16)
96362306a36Sopenharmony_ci		return -EINVAL;
96462306a36Sopenharmony_ci
96562306a36Sopenharmony_ci	if (info->var.grayscale) {
96662306a36Sopenharmony_ci		red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8;
96762306a36Sopenharmony_ci	}
96862306a36Sopenharmony_ci
96962306a36Sopenharmony_ci	if (info->fix.visual != FB_VISUAL_TRUECOLOR)
97062306a36Sopenharmony_ci		return -EINVAL;
97162306a36Sopenharmony_ci
97262306a36Sopenharmony_ci	red = VML_TOHW(red, info->var.red.length);
97362306a36Sopenharmony_ci	blue = VML_TOHW(blue, info->var.blue.length);
97462306a36Sopenharmony_ci	green = VML_TOHW(green, info->var.green.length);
97562306a36Sopenharmony_ci	transp = VML_TOHW(transp, info->var.transp.length);
97662306a36Sopenharmony_ci
97762306a36Sopenharmony_ci	v = (red << info->var.red.offset) |
97862306a36Sopenharmony_ci	    (green << info->var.green.offset) |
97962306a36Sopenharmony_ci	    (blue << info->var.blue.offset) |
98062306a36Sopenharmony_ci	    (transp << info->var.transp.offset);
98162306a36Sopenharmony_ci
98262306a36Sopenharmony_ci	switch (info->var.bits_per_pixel) {
98362306a36Sopenharmony_ci	case 16:
98462306a36Sopenharmony_ci		((u32 *) info->pseudo_palette)[regno] = v;
98562306a36Sopenharmony_ci		break;
98662306a36Sopenharmony_ci	case 24:
98762306a36Sopenharmony_ci	case 32:
98862306a36Sopenharmony_ci		((u32 *) info->pseudo_palette)[regno] = v;
98962306a36Sopenharmony_ci		break;
99062306a36Sopenharmony_ci	}
99162306a36Sopenharmony_ci	return 0;
99262306a36Sopenharmony_ci}
99362306a36Sopenharmony_ci
99462306a36Sopenharmony_cistatic int vmlfb_mmap(struct fb_info *info, struct vm_area_struct *vma)
99562306a36Sopenharmony_ci{
99662306a36Sopenharmony_ci	struct vml_info *vinfo = container_of(info, struct vml_info, info);
99762306a36Sopenharmony_ci	unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
99862306a36Sopenharmony_ci	int ret;
99962306a36Sopenharmony_ci	unsigned long prot;
100062306a36Sopenharmony_ci
100162306a36Sopenharmony_ci	ret = vmlfb_vram_offset(vinfo, offset);
100262306a36Sopenharmony_ci	if (ret)
100362306a36Sopenharmony_ci		return -EINVAL;
100462306a36Sopenharmony_ci
100562306a36Sopenharmony_ci	prot = pgprot_val(vma->vm_page_prot) & ~_PAGE_CACHE_MASK;
100662306a36Sopenharmony_ci	pgprot_val(vma->vm_page_prot) =
100762306a36Sopenharmony_ci		prot | cachemode2protval(_PAGE_CACHE_MODE_UC_MINUS);
100862306a36Sopenharmony_ci
100962306a36Sopenharmony_ci	return vm_iomap_memory(vma, vinfo->vram_start,
101062306a36Sopenharmony_ci			vinfo->vram_contig_size);
101162306a36Sopenharmony_ci}
101262306a36Sopenharmony_ci
101362306a36Sopenharmony_cistatic int vmlfb_sync(struct fb_info *info)
101462306a36Sopenharmony_ci{
101562306a36Sopenharmony_ci	return 0;
101662306a36Sopenharmony_ci}
101762306a36Sopenharmony_ci
101862306a36Sopenharmony_cistatic int vmlfb_cursor(struct fb_info *info, struct fb_cursor *cursor)
101962306a36Sopenharmony_ci{
102062306a36Sopenharmony_ci	return -EINVAL;	/* just to force soft_cursor() call */
102162306a36Sopenharmony_ci}
102262306a36Sopenharmony_ci
102362306a36Sopenharmony_cistatic struct fb_ops vmlfb_ops = {
102462306a36Sopenharmony_ci	.owner = THIS_MODULE,
102562306a36Sopenharmony_ci	.fb_open = vmlfb_open,
102662306a36Sopenharmony_ci	.fb_release = vmlfb_release,
102762306a36Sopenharmony_ci	.fb_check_var = vmlfb_check_var,
102862306a36Sopenharmony_ci	.fb_set_par = vmlfb_set_par,
102962306a36Sopenharmony_ci	.fb_blank = vmlfb_blank,
103062306a36Sopenharmony_ci	.fb_pan_display = vmlfb_pan_display,
103162306a36Sopenharmony_ci	.fb_fillrect = cfb_fillrect,
103262306a36Sopenharmony_ci	.fb_copyarea = cfb_copyarea,
103362306a36Sopenharmony_ci	.fb_imageblit = cfb_imageblit,
103462306a36Sopenharmony_ci	.fb_cursor = vmlfb_cursor,
103562306a36Sopenharmony_ci	.fb_sync = vmlfb_sync,
103662306a36Sopenharmony_ci	.fb_mmap = vmlfb_mmap,
103762306a36Sopenharmony_ci	.fb_setcolreg = vmlfb_setcolreg
103862306a36Sopenharmony_ci};
103962306a36Sopenharmony_ci
104062306a36Sopenharmony_cistatic const struct pci_device_id vml_ids[] = {
104162306a36Sopenharmony_ci	{PCI_DEVICE(PCI_VENDOR_ID_INTEL, VML_DEVICE_VDC)},
104262306a36Sopenharmony_ci	{0}
104362306a36Sopenharmony_ci};
104462306a36Sopenharmony_ci
104562306a36Sopenharmony_cistatic struct pci_driver vmlfb_pci_driver = {
104662306a36Sopenharmony_ci	.name = "vmlfb",
104762306a36Sopenharmony_ci	.id_table = vml_ids,
104862306a36Sopenharmony_ci	.probe = vml_pci_probe,
104962306a36Sopenharmony_ci	.remove = vml_pci_remove,
105062306a36Sopenharmony_ci};
105162306a36Sopenharmony_ci
105262306a36Sopenharmony_cistatic void __exit vmlfb_cleanup(void)
105362306a36Sopenharmony_ci{
105462306a36Sopenharmony_ci	pci_unregister_driver(&vmlfb_pci_driver);
105562306a36Sopenharmony_ci}
105662306a36Sopenharmony_ci
105762306a36Sopenharmony_cistatic int __init vmlfb_init(void)
105862306a36Sopenharmony_ci{
105962306a36Sopenharmony_ci
106062306a36Sopenharmony_ci#ifndef MODULE
106162306a36Sopenharmony_ci	char *option = NULL;
106262306a36Sopenharmony_ci#endif
106362306a36Sopenharmony_ci
106462306a36Sopenharmony_ci	if (fb_modesetting_disabled("vmlfb"))
106562306a36Sopenharmony_ci		return -ENODEV;
106662306a36Sopenharmony_ci
106762306a36Sopenharmony_ci#ifndef MODULE
106862306a36Sopenharmony_ci	if (fb_get_options(MODULE_NAME, &option))
106962306a36Sopenharmony_ci		return -ENODEV;
107062306a36Sopenharmony_ci#endif
107162306a36Sopenharmony_ci
107262306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": initializing\n");
107362306a36Sopenharmony_ci	mutex_init(&vml_mutex);
107462306a36Sopenharmony_ci	INIT_LIST_HEAD(&global_no_mode);
107562306a36Sopenharmony_ci	INIT_LIST_HEAD(&global_has_mode);
107662306a36Sopenharmony_ci
107762306a36Sopenharmony_ci	return pci_register_driver(&vmlfb_pci_driver);
107862306a36Sopenharmony_ci}
107962306a36Sopenharmony_ci
108062306a36Sopenharmony_ciint vmlfb_register_subsys(struct vml_sys *sys)
108162306a36Sopenharmony_ci{
108262306a36Sopenharmony_ci	struct vml_info *entry;
108362306a36Sopenharmony_ci	struct list_head *list;
108462306a36Sopenharmony_ci	u32 save_activate;
108562306a36Sopenharmony_ci
108662306a36Sopenharmony_ci	mutex_lock(&vml_mutex);
108762306a36Sopenharmony_ci	if (subsys != NULL) {
108862306a36Sopenharmony_ci		subsys->restore(subsys);
108962306a36Sopenharmony_ci	}
109062306a36Sopenharmony_ci	subsys = sys;
109162306a36Sopenharmony_ci	subsys->save(subsys);
109262306a36Sopenharmony_ci
109362306a36Sopenharmony_ci	/*
109462306a36Sopenharmony_ci	 * We need to restart list traversal for each item, since we
109562306a36Sopenharmony_ci	 * release the list mutex in the loop.
109662306a36Sopenharmony_ci	 */
109762306a36Sopenharmony_ci
109862306a36Sopenharmony_ci	list = global_no_mode.next;
109962306a36Sopenharmony_ci	while (list != &global_no_mode) {
110062306a36Sopenharmony_ci		list_del_init(list);
110162306a36Sopenharmony_ci		entry = list_entry(list, struct vml_info, head);
110262306a36Sopenharmony_ci
110362306a36Sopenharmony_ci		/*
110462306a36Sopenharmony_ci		 * First, try the current mode which might not be
110562306a36Sopenharmony_ci		 * completely validated with respect to the pixel clock.
110662306a36Sopenharmony_ci		 */
110762306a36Sopenharmony_ci
110862306a36Sopenharmony_ci		if (!vmlfb_check_var_locked(&entry->info.var, entry)) {
110962306a36Sopenharmony_ci			vmlfb_set_par_locked(entry);
111062306a36Sopenharmony_ci			list_add_tail(list, &global_has_mode);
111162306a36Sopenharmony_ci		} else {
111262306a36Sopenharmony_ci
111362306a36Sopenharmony_ci			/*
111462306a36Sopenharmony_ci			 * Didn't work. Try to find another mode,
111562306a36Sopenharmony_ci			 * that matches this subsys.
111662306a36Sopenharmony_ci			 */
111762306a36Sopenharmony_ci
111862306a36Sopenharmony_ci			mutex_unlock(&vml_mutex);
111962306a36Sopenharmony_ci			save_activate = entry->info.var.activate;
112062306a36Sopenharmony_ci			entry->info.var.bits_per_pixel = 16;
112162306a36Sopenharmony_ci			vmlfb_set_pref_pixel_format(&entry->info.var);
112262306a36Sopenharmony_ci			if (fb_find_mode(&entry->info.var,
112362306a36Sopenharmony_ci					 &entry->info,
112462306a36Sopenharmony_ci					 vml_default_mode, NULL, 0, NULL, 16)) {
112562306a36Sopenharmony_ci				entry->info.var.activate |=
112662306a36Sopenharmony_ci				    FB_ACTIVATE_FORCE | FB_ACTIVATE_NOW;
112762306a36Sopenharmony_ci				fb_set_var(&entry->info, &entry->info.var);
112862306a36Sopenharmony_ci			} else {
112962306a36Sopenharmony_ci				printk(KERN_ERR MODULE_NAME
113062306a36Sopenharmony_ci				       ": Sorry. no mode found for this subsys.\n");
113162306a36Sopenharmony_ci			}
113262306a36Sopenharmony_ci			entry->info.var.activate = save_activate;
113362306a36Sopenharmony_ci			mutex_lock(&vml_mutex);
113462306a36Sopenharmony_ci		}
113562306a36Sopenharmony_ci		vmlfb_blank_locked(entry);
113662306a36Sopenharmony_ci		list = global_no_mode.next;
113762306a36Sopenharmony_ci	}
113862306a36Sopenharmony_ci	mutex_unlock(&vml_mutex);
113962306a36Sopenharmony_ci
114062306a36Sopenharmony_ci	printk(KERN_DEBUG MODULE_NAME ": Registered %s subsystem.\n",
114162306a36Sopenharmony_ci				subsys->name ? subsys->name : "unknown");
114262306a36Sopenharmony_ci	return 0;
114362306a36Sopenharmony_ci}
114462306a36Sopenharmony_ci
114562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(vmlfb_register_subsys);
114662306a36Sopenharmony_ci
114762306a36Sopenharmony_civoid vmlfb_unregister_subsys(struct vml_sys *sys)
114862306a36Sopenharmony_ci{
114962306a36Sopenharmony_ci	struct vml_info *entry, *next;
115062306a36Sopenharmony_ci
115162306a36Sopenharmony_ci	mutex_lock(&vml_mutex);
115262306a36Sopenharmony_ci	if (subsys != sys) {
115362306a36Sopenharmony_ci		mutex_unlock(&vml_mutex);
115462306a36Sopenharmony_ci		return;
115562306a36Sopenharmony_ci	}
115662306a36Sopenharmony_ci	subsys->restore(subsys);
115762306a36Sopenharmony_ci	subsys = NULL;
115862306a36Sopenharmony_ci	list_for_each_entry_safe(entry, next, &global_has_mode, head) {
115962306a36Sopenharmony_ci		printk(KERN_DEBUG MODULE_NAME ": subsys disable pipe\n");
116062306a36Sopenharmony_ci		vmlfb_disable_pipe(entry);
116162306a36Sopenharmony_ci		list_move_tail(&entry->head, &global_no_mode);
116262306a36Sopenharmony_ci	}
116362306a36Sopenharmony_ci	mutex_unlock(&vml_mutex);
116462306a36Sopenharmony_ci}
116562306a36Sopenharmony_ci
116662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(vmlfb_unregister_subsys);
116762306a36Sopenharmony_ci
116862306a36Sopenharmony_cimodule_init(vmlfb_init);
116962306a36Sopenharmony_cimodule_exit(vmlfb_cleanup);
117062306a36Sopenharmony_ci
117162306a36Sopenharmony_ciMODULE_AUTHOR("Tungsten Graphics");
117262306a36Sopenharmony_ciMODULE_DESCRIPTION("Initialization of the Vermilion display devices");
117362306a36Sopenharmony_ciMODULE_VERSION("1.0.0");
117462306a36Sopenharmony_ciMODULE_LICENSE("GPL");
1175