| /* |
| * Copyright (c) Intel Corp. 2007. |
| * All Rights Reserved. |
| * |
| * Intel funded Tungsten Graphics (http://www.tungstengraphics.com) to |
| * develop this driver. |
| * |
| * This file is part of the Vermilion Range fb driver. |
| * The Vermilion Range fb driver is free software; |
| * you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * The Vermilion Range fb driver is distributed |
| * in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this driver; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| * Authors: |
| * Thomas Hellström <thomas-at-tungstengraphics-dot-com> |
| * Michel Dänzer <michel-at-tungstengraphics-dot-com> |
| * Alan Hourihane <alanh-at-tungstengraphics-dot-com> |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/kernel.h> |
| #include <linux/errno.h> |
| #include <linux/string.h> |
| #include <linux/delay.h> |
| #include <linux/slab.h> |
| #include <linux/mm.h> |
| #include <linux/fb.h> |
| #include <linux/pci.h> |
| #include <asm/cacheflush.h> |
| #include <asm/tlbflush.h> |
| #include <linux/mmzone.h> |
| |
| /* #define VERMILION_DEBUG */ |
| |
| #include "vermilion.h" |
| |
| #define MODULE_NAME "vmlfb" |
| |
| #define VML_TOHW(_val, _width) ((((_val) << (_width)) + 0x7FFF - (_val)) >> 16) |
| |
| static struct mutex vml_mutex; |
| static struct list_head global_no_mode; |
| static struct list_head global_has_mode; |
| static struct fb_ops vmlfb_ops; |
| static struct vml_sys *subsys = NULL; |
| static char *vml_default_mode = "1024x768@60"; |
| static struct fb_videomode defaultmode = { |
| NULL, 60, 1024, 768, 12896, 144, 24, 29, 3, 136, 6, |
| 0, FB_VMODE_NONINTERLACED |
| }; |
| |
| static u32 vml_mem_requested = (10 * 1024 * 1024); |
| static u32 vml_mem_contig = (4 * 1024 * 1024); |
| static u32 vml_mem_min = (4 * 1024 * 1024); |
| |
| static u32 vml_clocks[] = { |
| 6750, |
| 13500, |
| 27000, |
| 29700, |
| 37125, |
| 54000, |
| 59400, |
| 74250, |
| 120000, |
| 148500 |
| }; |
| |
| static u32 vml_num_clocks = ARRAY_SIZE(vml_clocks); |
| |
| /* |
| * Allocate a contiguous vram area and make its linear kernel map |
| * uncached. |
| */ |
| |
| static int vmlfb_alloc_vram_area(struct vram_area *va, unsigned max_order, |
| unsigned min_order) |
| { |
| gfp_t flags; |
| unsigned long i; |
| |
| max_order++; |
| do { |
| /* |
| * Really try hard to get the needed memory. |
| * We need memory below the first 32MB, so we |
| * add the __GFP_DMA flag that guarantees that we are |
| * below the first 16MB. |
| */ |
| |
| flags = __GFP_DMA | __GFP_HIGH; |
| va->logical = |
| __get_free_pages(flags, --max_order); |
| } while (va->logical == 0 && max_order > min_order); |
| |
| if (!va->logical) |
| return -ENOMEM; |
| |
| va->phys = virt_to_phys((void *)va->logical); |
| va->size = PAGE_SIZE << max_order; |
| va->order = max_order; |
| |
| /* |
| * It seems like __get_free_pages only ups the usage count |
| * of the first page. This doesn't work with fault mapping, so |
| * up the usage count once more (XXX: should use split_page or |
| * compound page). |
| */ |
| |
| memset((void *)va->logical, 0x00, va->size); |
| for (i = va->logical; i < va->logical + va->size; i += PAGE_SIZE) { |
| get_page(virt_to_page(i)); |
| } |
| |
| /* |
| * Change caching policy of the linear kernel map to avoid |
| * mapping type conflicts with user-space mappings. |
| */ |
| set_pages_uc(virt_to_page(va->logical), va->size >> PAGE_SHIFT); |
| |
| printk(KERN_DEBUG MODULE_NAME |
| ": Allocated %ld bytes vram area at 0x%08lx\n", |
| va->size, va->phys); |
| |
| return 0; |
| } |
| |
| /* |
| * Free a contiguous vram area and reset its linear kernel map |
| * mapping type. |
| */ |
| |
| static void vmlfb_free_vram_area(struct vram_area *va) |
| { |
| unsigned long j; |
| |
| if (va->logical) { |
| |
| /* |
| * Reset the linear kernel map caching policy. |
| */ |
| |
| set_pages_wb(virt_to_page(va->logical), |
| va->size >> PAGE_SHIFT); |
| |
| /* |
| * Decrease the usage count on the pages we've used |
| * to compensate for upping when allocating. |
| */ |
| |
| for (j = va->logical; j < va->logical + va->size; |
| j += PAGE_SIZE) { |
| (void)put_page_testzero(virt_to_page(j)); |
| } |
| |
| printk(KERN_DEBUG MODULE_NAME |
| ": Freeing %ld bytes vram area at 0x%08lx\n", |
| va->size, va->phys); |
| free_pages(va->logical, va->order); |
| |
| va->logical = 0; |
| } |
| } |
| |
| /* |
| * Free allocated vram. |
| */ |
| |
| static void vmlfb_free_vram(struct vml_info *vinfo) |
| { |
| int i; |
| |
| for (i = 0; i < vinfo->num_areas; ++i) { |
| vmlfb_free_vram_area(&vinfo->vram[i]); |
| } |
| vinfo->num_areas = 0; |
| } |
| |
| /* |
| * Allocate vram. Currently we try to allocate contiguous areas from the |
| * __GFP_DMA zone and puzzle them together. A better approach would be to |
| * allocate one contiguous area for scanout and use one-page allocations for |
| * offscreen areas. This requires user-space and GPU virtual mappings. |
| */ |
| |
| static int vmlfb_alloc_vram(struct vml_info *vinfo, |
| size_t requested, |
| size_t min_total, size_t min_contig) |
| { |
| int i, j; |
| int order; |
| int contiguous; |
| int err; |
| struct vram_area *va; |
| struct vram_area *va2; |
| |
| vinfo->num_areas = 0; |
| for (i = 0; i < VML_VRAM_AREAS; ++i) { |
| va = &vinfo->vram[i]; |
| order = 0; |
| |
| while (requested > (PAGE_SIZE << order) && order < MAX_ORDER) |
| order++; |
| |
| err = vmlfb_alloc_vram_area(va, order, 0); |
| |
| if (err) |
| break; |
| |
| if (i == 0) { |
| vinfo->vram_start = va->phys; |
| vinfo->vram_logical = (void __iomem *) va->logical; |
| vinfo->vram_contig_size = va->size; |
| vinfo->num_areas = 1; |
| } else { |
| contiguous = 0; |
| |
| for (j = 0; j < i; ++j) { |
| va2 = &vinfo->vram[j]; |
| if (va->phys + va->size == va2->phys || |
| va2->phys + va2->size == va->phys) { |
| contiguous = 1; |
| break; |
| } |
| } |
| |
| if (contiguous) { |
| vinfo->num_areas++; |
| if (va->phys < vinfo->vram_start) { |
| vinfo->vram_start = va->phys; |
| vinfo->vram_logical = |
| (void __iomem *)va->logical; |
| } |
| vinfo->vram_contig_size += va->size; |
| } else { |
| vmlfb_free_vram_area(va); |
| break; |
| } |
| } |
| |
| if (requested < va->size) |
| break; |
| else |
| requested -= va->size; |
| } |
| |
| if (vinfo->vram_contig_size > min_total && |
| vinfo->vram_contig_size > min_contig) { |
| |
| printk(KERN_DEBUG MODULE_NAME |
| ": Contiguous vram: %ld bytes at physical 0x%08lx.\n", |
| (unsigned long)vinfo->vram_contig_size, |
| (unsigned long)vinfo->vram_start); |
| |
| return 0; |
| } |
| |
| printk(KERN_ERR MODULE_NAME |
| ": Could not allocate requested minimal amount of vram.\n"); |
| |
| vmlfb_free_vram(vinfo); |
| |
| return -ENOMEM; |
| } |
| |
| /* |
| * Find the GPU to use with our display controller. |
| */ |
| |
| static int vmlfb_get_gpu(struct vml_par *par) |
| { |
| mutex_lock(&vml_mutex); |
| |
| par->gpu = pci_get_device(PCI_VENDOR_ID_INTEL, VML_DEVICE_GPU, NULL); |
| |
| if (!par->gpu) { |
| mutex_unlock(&vml_mutex); |
| return -ENODEV; |
| } |
| |
| mutex_unlock(&vml_mutex); |
| |
| if (pci_enable_device(par->gpu) < 0) |
| return -ENODEV; |
| |
| return 0; |
| } |
| |
| /* |
| * Find a contiguous vram area that contains a given offset from vram start. |
| */ |
| static int vmlfb_vram_offset(struct vml_info *vinfo, unsigned long offset) |
| { |
| unsigned long aoffset; |
| unsigned i; |
| |
| for (i = 0; i < vinfo->num_areas; ++i) { |
| aoffset = offset - (vinfo->vram[i].phys - vinfo->vram_start); |
| |
| if (aoffset < vinfo->vram[i].size) { |
| return 0; |
| } |
| } |
| |
| return -EINVAL; |
| } |
| |
| /* |
| * Remap the MMIO register spaces of the VDC and the GPU. |
| */ |
| |
| static int vmlfb_enable_mmio(struct vml_par *par) |
| { |
| int err; |
| |
| par->vdc_mem_base = pci_resource_start(par->vdc, 0); |
| par->vdc_mem_size = pci_resource_len(par->vdc, 0); |
| if (!request_mem_region(par->vdc_mem_base, par->vdc_mem_size, "vmlfb")) { |
| printk(KERN_ERR MODULE_NAME |
| ": Could not claim display controller MMIO.\n"); |
| return -EBUSY; |
| } |
| par->vdc_mem = ioremap_nocache(par->vdc_mem_base, par->vdc_mem_size); |
| if (par->vdc_mem == NULL) { |
| printk(KERN_ERR MODULE_NAME |
| ": Could not map display controller MMIO.\n"); |
| err = -ENOMEM; |
| goto out_err_0; |
| } |
| |
| par->gpu_mem_base = pci_resource_start(par->gpu, 0); |
| par->gpu_mem_size = pci_resource_len(par->gpu, 0); |
| if (!request_mem_region(par->gpu_mem_base, par->gpu_mem_size, "vmlfb")) { |
| printk(KERN_ERR MODULE_NAME ": Could not claim GPU MMIO.\n"); |
| err = -EBUSY; |
| goto out_err_1; |
| } |
| par->gpu_mem = ioremap_nocache(par->gpu_mem_base, par->gpu_mem_size); |
| if (par->gpu_mem == NULL) { |
| printk(KERN_ERR MODULE_NAME ": Could not map GPU MMIO.\n"); |
| err = -ENOMEM; |
| goto out_err_2; |
| } |
| |
| return 0; |
| |
| out_err_2: |
| release_mem_region(par->gpu_mem_base, par->gpu_mem_size); |
| out_err_1: |
| iounmap(par->vdc_mem); |
| out_err_0: |
| release_mem_region(par->vdc_mem_base, par->vdc_mem_size); |
| return err; |
| } |
| |
| /* |
| * Unmap the VDC and GPU register spaces. |
| */ |
| |
| static void vmlfb_disable_mmio(struct vml_par *par) |
| { |
| iounmap(par->gpu_mem); |
| release_mem_region(par->gpu_mem_base, par->gpu_mem_size); |
| iounmap(par->vdc_mem); |
| release_mem_region(par->vdc_mem_base, par->vdc_mem_size); |
| } |
| |
| /* |
| * Release and uninit the VDC and GPU. |
| */ |
| |
| static void vmlfb_release_devices(struct vml_par *par) |
| { |
| if (atomic_dec_and_test(&par->refcount)) { |
| pci_disable_device(par->gpu); |
| pci_disable_device(par->vdc); |
| } |
| } |
| |
| /* |
| * Free up allocated resources for a device. |
| */ |
| |
| static void vml_pci_remove(struct pci_dev *dev) |
| { |
| struct fb_info *info; |
| struct vml_info *vinfo; |
| struct vml_par *par; |
| |
| info = pci_get_drvdata(dev); |
| if (info) { |
| vinfo = container_of(info, struct vml_info, info); |
| par = vinfo->par; |
| mutex_lock(&vml_mutex); |
| unregister_framebuffer(info); |
| fb_dealloc_cmap(&info->cmap); |
| vmlfb_free_vram(vinfo); |
| vmlfb_disable_mmio(par); |
| vmlfb_release_devices(par); |
| kfree(vinfo); |
| kfree(par); |
| mutex_unlock(&vml_mutex); |
| } |
| } |
| |
| static void vmlfb_set_pref_pixel_format(struct fb_var_screeninfo *var) |
| { |
| switch (var->bits_per_pixel) { |
| case 16: |
| var->blue.offset = 0; |
| var->blue.length = 5; |
| var->green.offset = 5; |
| var->green.length = 5; |
| var->red.offset = 10; |
| var->red.length = 5; |
| var->transp.offset = 15; |
| var->transp.length = 1; |
| break; |
| case 32: |
| var->blue.offset = 0; |
| var->blue.length = 8; |
| var->green.offset = 8; |
| var->green.length = 8; |
| var->red.offset = 16; |
| var->red.length = 8; |
| var->transp.offset = 24; |
| var->transp.length = 0; |
| break; |
| default: |
| break; |
| } |
| |
| var->blue.msb_right = var->green.msb_right = |
| var->red.msb_right = var->transp.msb_right = 0; |
| } |
| |
| /* |
| * Device initialization. |
| * We initialize one vml_par struct per device and one vml_info |
| * struct per pipe. Currently we have only one pipe. |
| */ |
| |
| static int vml_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) |
| { |
| struct vml_info *vinfo; |
| struct fb_info *info; |
| struct vml_par *par; |
| int err = 0; |
| |
| par = kzalloc(sizeof(*par), GFP_KERNEL); |
| if (par == NULL) |
| return -ENOMEM; |
| |
| vinfo = kzalloc(sizeof(*vinfo), GFP_KERNEL); |
| if (vinfo == NULL) { |
| err = -ENOMEM; |
| goto out_err_0; |
| } |
| |
| vinfo->par = par; |
| par->vdc = dev; |
| atomic_set(&par->refcount, 1); |
| |
| switch (id->device) { |
| case VML_DEVICE_VDC: |
| if ((err = vmlfb_get_gpu(par))) |
| goto out_err_1; |
| pci_set_drvdata(dev, &vinfo->info); |
| break; |
| default: |
| err = -ENODEV; |
| goto out_err_1; |
| } |
| |
| info = &vinfo->info; |
| info->flags = FBINFO_DEFAULT | FBINFO_PARTIAL_PAN_OK; |
| |
| err = vmlfb_enable_mmio(par); |
| if (err) |
| goto out_err_2; |
| |
| err = vmlfb_alloc_vram(vinfo, vml_mem_requested, |
| vml_mem_contig, vml_mem_min); |
| if (err) |
| goto out_err_3; |
| |
| strcpy(info->fix.id, "Vermilion Range"); |
| info->fix.mmio_start = 0; |
| info->fix.mmio_len = 0; |
| info->fix.smem_start = vinfo->vram_start; |
| info->fix.smem_len = vinfo->vram_contig_size; |
| info->fix.type = FB_TYPE_PACKED_PIXELS; |
| info->fix.visual = FB_VISUAL_TRUECOLOR; |
| info->fix.ypanstep = 1; |
| info->fix.xpanstep = 1; |
| info->fix.ywrapstep = 0; |
| info->fix.accel = FB_ACCEL_NONE; |
| info->screen_base = vinfo->vram_logical; |
| info->pseudo_palette = vinfo->pseudo_palette; |
| info->par = par; |
| info->fbops = &vmlfb_ops; |
| info->device = &dev->dev; |
| |
| INIT_LIST_HEAD(&vinfo->head); |
| vinfo->pipe_disabled = 1; |
| vinfo->cur_blank_mode = FB_BLANK_UNBLANK; |
| |
| info->var.grayscale = 0; |
| info->var.bits_per_pixel = 16; |
| vmlfb_set_pref_pixel_format(&info->var); |
| |
| if (!fb_find_mode |
| (&info->var, info, vml_default_mode, NULL, 0, &defaultmode, 16)) { |
| printk(KERN_ERR MODULE_NAME ": Could not find initial mode\n"); |
| } |
| |
| if (fb_alloc_cmap(&info->cmap, 256, 1) < 0) { |
| err = -ENOMEM; |
| goto out_err_4; |
| } |
| |
| err = register_framebuffer(info); |
| if (err) { |
| printk(KERN_ERR MODULE_NAME ": Register framebuffer error.\n"); |
| goto out_err_5; |
| } |
| |
| printk("Initialized vmlfb\n"); |
| |
| return 0; |
| |
| out_err_5: |
| fb_dealloc_cmap(&info->cmap); |
| out_err_4: |
| vmlfb_free_vram(vinfo); |
| out_err_3: |
| vmlfb_disable_mmio(par); |
| out_err_2: |
| vmlfb_release_devices(par); |
| out_err_1: |
| kfree(vinfo); |
| out_err_0: |
| kfree(par); |
| return err; |
| } |
| |
| static int vmlfb_open(struct fb_info *info, int user) |
| { |
| /* |
| * Save registers here? |
| */ |
| return 0; |
| } |
| |
| static int vmlfb_release(struct fb_info *info, int user) |
| { |
| /* |
| * Restore registers here. |
| */ |
| |
| return 0; |
| } |
| |
| static int vml_nearest_clock(int clock) |
| { |
| |
| int i; |
| int cur_index; |
| int cur_diff; |
| int diff; |
| |
| cur_index = 0; |
| cur_diff = clock - vml_clocks[0]; |
| cur_diff = (cur_diff < 0) ? -cur_diff : cur_diff; |
| for (i = 1; i < vml_num_clocks; ++i) { |
| diff = clock - vml_clocks[i]; |
| diff = (diff < 0) ? -diff : diff; |
| if (diff < cur_diff) { |
| cur_index = i; |
| cur_diff = diff; |
| } |
| } |
| return vml_clocks[cur_index]; |
| } |
| |
| static int vmlfb_check_var_locked(struct fb_var_screeninfo *var, |
| struct vml_info *vinfo) |
| { |
| u32 pitch; |
| u64 mem; |
| int nearest_clock; |
| int clock; |
| int clock_diff; |
| struct fb_var_screeninfo v; |
| |
| v = *var; |
| clock = PICOS2KHZ(var->pixclock); |
| |
| if (subsys && subsys->nearest_clock) { |
| nearest_clock = subsys->nearest_clock(subsys, clock); |
| } else { |
| nearest_clock = vml_nearest_clock(clock); |
| } |
| |
| /* |
| * Accept a 20% diff. |
| */ |
| |
| clock_diff = nearest_clock - clock; |
| clock_diff = (clock_diff < 0) ? -clock_diff : clock_diff; |
| if (clock_diff > clock / 5) { |
| #if 0 |
| printk(KERN_DEBUG MODULE_NAME ": Diff failure. %d %d\n",clock_diff,clock); |
| #endif |
| return -EINVAL; |
| } |
| |
| v.pixclock = KHZ2PICOS(nearest_clock); |
| |
| if (var->xres > VML_MAX_XRES || var->yres > VML_MAX_YRES) { |
| printk(KERN_DEBUG MODULE_NAME ": Resolution failure.\n"); |
| return -EINVAL; |
| } |
| if (var->xres_virtual > VML_MAX_XRES_VIRTUAL) { |
| printk(KERN_DEBUG MODULE_NAME |
| ": Virtual resolution failure.\n"); |
| return -EINVAL; |
| } |
| switch (v.bits_per_pixel) { |
| case 0 ... 16: |
| v.bits_per_pixel = 16; |
| break; |
| case 17 ... 32: |
| v.bits_per_pixel = 32; |
| break; |
| default: |
| printk(KERN_DEBUG MODULE_NAME ": Invalid bpp: %d.\n", |
| var->bits_per_pixel); |
| return -EINVAL; |
| } |
| |
| pitch = ALIGN((var->xres * var->bits_per_pixel) >> 3, 0x40); |
| mem = pitch * var->yres_virtual; |
| if (mem > vinfo->vram_contig_size) { |
| return -ENOMEM; |
| } |
| |
| switch (v.bits_per_pixel) { |
| case 16: |
| if (var->blue.offset != 0 || |
| var->blue.length != 5 || |
| var->green.offset != 5 || |
| var->green.length != 5 || |
| var->red.offset != 10 || |
| var->red.length != 5 || |
| var->transp.offset != 15 || var->transp.length != 1) { |
| vmlfb_set_pref_pixel_format(&v); |
| } |
| break; |
| case 32: |
| if (var->blue.offset != 0 || |
| var->blue.length != 8 || |
| var->green.offset != 8 || |
| var->green.length != 8 || |
| var->red.offset != 16 || |
| var->red.length != 8 || |
| (var->transp.length != 0 && var->transp.length != 8) || |
| (var->transp.length == 8 && var->transp.offset != 24)) { |
| vmlfb_set_pref_pixel_format(&v); |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| *var = v; |
| |
| return 0; |
| } |
| |
| static int vmlfb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) |
| { |
| struct vml_info *vinfo = container_of(info, struct vml_info, info); |
| int ret; |
| |
| mutex_lock(&vml_mutex); |
| ret = vmlfb_check_var_locked(var, vinfo); |
| mutex_unlock(&vml_mutex); |
| |
| return ret; |
| } |
| |
| static void vml_wait_vblank(struct vml_info *vinfo) |
| { |
| /* Wait for vblank. For now, just wait for a 50Hz cycle (20ms)) */ |
| mdelay(20); |
| } |
| |
| static void vmlfb_disable_pipe(struct vml_info *vinfo) |
| { |
| struct vml_par *par = vinfo->par; |
| |
| /* Disable the MDVO pad */ |
| VML_WRITE32(par, VML_RCOMPSTAT, 0); |
| while (!(VML_READ32(par, VML_RCOMPSTAT) & VML_MDVO_VDC_I_RCOMP)) ; |
| |
| /* Disable display planes */ |
| VML_WRITE32(par, VML_DSPCCNTR, |
| VML_READ32(par, VML_DSPCCNTR) & ~VML_GFX_ENABLE); |
| (void)VML_READ32(par, VML_DSPCCNTR); |
| /* Wait for vblank for the disable to take effect */ |
| vml_wait_vblank(vinfo); |
| |
| /* Next, disable display pipes */ |
| VML_WRITE32(par, VML_PIPEACONF, 0); |
| (void)VML_READ32(par, VML_PIPEACONF); |
| |
| vinfo->pipe_disabled = 1; |
| } |
| |
| #ifdef VERMILION_DEBUG |
| static void vml_dump_regs(struct vml_info *vinfo) |
| { |
| struct vml_par *par = vinfo->par; |
| |
| printk(KERN_DEBUG MODULE_NAME ": Modesetting register dump:\n"); |
| printk(KERN_DEBUG MODULE_NAME ": \tHTOTAL_A : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_HTOTAL_A)); |
| printk(KERN_DEBUG MODULE_NAME ": \tHBLANK_A : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_HBLANK_A)); |
| printk(KERN_DEBUG MODULE_NAME ": \tHSYNC_A : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_HSYNC_A)); |
| printk(KERN_DEBUG MODULE_NAME ": \tVTOTAL_A : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_VTOTAL_A)); |
| printk(KERN_DEBUG MODULE_NAME ": \tVBLANK_A : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_VBLANK_A)); |
| printk(KERN_DEBUG MODULE_NAME ": \tVSYNC_A : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_VSYNC_A)); |
| printk(KERN_DEBUG MODULE_NAME ": \tDSPCSTRIDE : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_DSPCSTRIDE)); |
| printk(KERN_DEBUG MODULE_NAME ": \tDSPCSIZE : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_DSPCSIZE)); |
| printk(KERN_DEBUG MODULE_NAME ": \tDSPCPOS : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_DSPCPOS)); |
| printk(KERN_DEBUG MODULE_NAME ": \tDSPARB : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_DSPARB)); |
| printk(KERN_DEBUG MODULE_NAME ": \tDSPCADDR : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_DSPCADDR)); |
| printk(KERN_DEBUG MODULE_NAME ": \tBCLRPAT_A : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_BCLRPAT_A)); |
| printk(KERN_DEBUG MODULE_NAME ": \tCANVSCLR_A : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_CANVSCLR_A)); |
| printk(KERN_DEBUG MODULE_NAME ": \tPIPEASRC : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_PIPEASRC)); |
| printk(KERN_DEBUG MODULE_NAME ": \tPIPEACONF : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_PIPEACONF)); |
| printk(KERN_DEBUG MODULE_NAME ": \tDSPCCNTR : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_DSPCCNTR)); |
| printk(KERN_DEBUG MODULE_NAME ": \tRCOMPSTAT : 0x%08x\n", |
| (unsigned)VML_READ32(par, VML_RCOMPSTAT)); |
| printk(KERN_DEBUG MODULE_NAME ": End of modesetting register dump.\n"); |
| } |
| #endif |
| |
| static int vmlfb_set_par_locked(struct vml_info *vinfo) |
| { |
| struct vml_par *par = vinfo->par; |
| struct fb_info *info = &vinfo->info; |
| struct fb_var_screeninfo *var = &info->var; |
| u32 htotal, hactive, hblank_start, hblank_end, hsync_start, hsync_end; |
| u32 vtotal, vactive, vblank_start, vblank_end, vsync_start, vsync_end; |
| u32 dspcntr; |
| int clock; |
| |
| vinfo->bytes_per_pixel = var->bits_per_pixel >> 3; |
| vinfo->stride = ALIGN(var->xres_virtual * vinfo->bytes_per_pixel, 0x40); |
| info->fix.line_length = vinfo->stride; |
| |
| if (!subsys) |
| return 0; |
| |
| htotal = |
| var->xres + var->right_margin + var->hsync_len + var->left_margin; |
| hactive = var->xres; |
| hblank_start = var->xres; |
| hblank_end = htotal; |
| hsync_start = hactive + var->right_margin; |
| hsync_end = hsync_start + var->hsync_len; |
| |
| vtotal = |
| var->yres + var->lower_margin + var->vsync_len + var->upper_margin; |
| vactive = var->yres; |
| vblank_start = var->yres; |
| vblank_end = vtotal; |
| vsync_start = vactive + var->lower_margin; |
| vsync_end = vsync_start + var->vsync_len; |
| |
| dspcntr = VML_GFX_ENABLE | VML_GFX_GAMMABYPASS; |
| clock = PICOS2KHZ(var->pixclock); |
| |
| if (subsys->nearest_clock) { |
| clock = subsys->nearest_clock(subsys, clock); |
| } else { |
| clock = vml_nearest_clock(clock); |
| } |
| printk(KERN_DEBUG MODULE_NAME |
| ": Set mode Hfreq : %d kHz, Vfreq : %d Hz.\n", clock / htotal, |
| ((clock / htotal) * 1000) / vtotal); |
| |
| switch (var->bits_per_pixel) { |
| case 16: |
| dspcntr |= VML_GFX_ARGB1555; |
| break; |
| case 32: |
| if (var->transp.length == 8) |
| dspcntr |= VML_GFX_ARGB8888 | VML_GFX_ALPHAMULT; |
| else |
| dspcntr |= VML_GFX_RGB0888; |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| vmlfb_disable_pipe(vinfo); |
| mb(); |
| |
| if (subsys->set_clock) |
| subsys->set_clock(subsys, clock); |
| else |
| return -EINVAL; |
| |
| VML_WRITE32(par, VML_HTOTAL_A, ((htotal - 1) << 16) | (hactive - 1)); |
| VML_WRITE32(par, VML_HBLANK_A, |
| ((hblank_end - 1) << 16) | (hblank_start - 1)); |
| VML_WRITE32(par, VML_HSYNC_A, |
| ((hsync_end - 1) << 16) | (hsync_start - 1)); |
| VML_WRITE32(par, VML_VTOTAL_A, ((vtotal - 1) << 16) | (vactive - 1)); |
| VML_WRITE32(par, VML_VBLANK_A, |
| ((vblank_end - 1) << 16) | (vblank_start - 1)); |
| VML_WRITE32(par, VML_VSYNC_A, |
| ((vsync_end - 1) << 16) | (vsync_start - 1)); |
| VML_WRITE32(par, VML_DSPCSTRIDE, vinfo->stride); |
| VML_WRITE32(par, VML_DSPCSIZE, |
| ((var->yres - 1) << 16) | (var->xres - 1)); |
| VML_WRITE32(par, VML_DSPCPOS, 0x00000000); |
| VML_WRITE32(par, VML_DSPARB, VML_FIFO_DEFAULT); |
| VML_WRITE32(par, VML_BCLRPAT_A, 0x00000000); |
| VML_WRITE32(par, VML_CANVSCLR_A, 0x00000000); |
| VML_WRITE32(par, VML_PIPEASRC, |
| ((var->xres - 1) << 16) | (var->yres - 1)); |
| |
| wmb(); |
| VML_WRITE32(par, VML_PIPEACONF, VML_PIPE_ENABLE); |
| wmb(); |
| VML_WRITE32(par, VML_DSPCCNTR, dspcntr); |
| wmb(); |
| VML_WRITE32(par, VML_DSPCADDR, (u32) vinfo->vram_start + |
| var->yoffset * vinfo->stride + |
| var->xoffset * vinfo->bytes_per_pixel); |
| |
| VML_WRITE32(par, VML_RCOMPSTAT, VML_MDVO_PAD_ENABLE); |
| |
| while (!(VML_READ32(par, VML_RCOMPSTAT) & |
| (VML_MDVO_VDC_I_RCOMP | VML_MDVO_PAD_ENABLE))) ; |
| |
| vinfo->pipe_disabled = 0; |
| #ifdef VERMILION_DEBUG |
| vml_dump_regs(vinfo); |
| #endif |
| |
| return 0; |
| } |
| |
| static int vmlfb_set_par(struct fb_info *info) |
| { |
| struct vml_info *vinfo = container_of(info, struct vml_info, info); |
| int ret; |
| |
| mutex_lock(&vml_mutex); |
| list_move(&vinfo->head, (subsys) ? &global_has_mode : &global_no_mode); |
| ret = vmlfb_set_par_locked(vinfo); |
| |
| mutex_unlock(&vml_mutex); |
| return ret; |
| } |
| |
| static int vmlfb_blank_locked(struct vml_info *vinfo) |
| { |
| struct vml_par *par = vinfo->par; |
| u32 cur = VML_READ32(par, VML_PIPEACONF); |
| |
| switch (vinfo->cur_blank_mode) { |
| case FB_BLANK_UNBLANK: |
| if (vinfo->pipe_disabled) { |
| vmlfb_set_par_locked(vinfo); |
| } |
| VML_WRITE32(par, VML_PIPEACONF, cur & ~VML_PIPE_FORCE_BORDER); |
| (void)VML_READ32(par, VML_PIPEACONF); |
| break; |
| case FB_BLANK_NORMAL: |
| if (vinfo->pipe_disabled) { |
| vmlfb_set_par_locked(vinfo); |
| } |
| VML_WRITE32(par, VML_PIPEACONF, cur | VML_PIPE_FORCE_BORDER); |
| (void)VML_READ32(par, VML_PIPEACONF); |
| break; |
| case FB_BLANK_VSYNC_SUSPEND: |
| case FB_BLANK_HSYNC_SUSPEND: |
| if (!vinfo->pipe_disabled) { |
| vmlfb_disable_pipe(vinfo); |
| } |
| break; |
| case FB_BLANK_POWERDOWN: |
| if (!vinfo->pipe_disabled) { |
| vmlfb_disable_pipe(vinfo); |
| } |
| break; |
| default: |
| return -EINVAL; |
| } |
| |
| return 0; |
| } |
| |
| static int vmlfb_blank(int blank_mode, struct fb_info *info) |
| { |
| struct vml_info *vinfo = container_of(info, struct vml_info, info); |
| int ret; |
| |
| mutex_lock(&vml_mutex); |
| vinfo->cur_blank_mode = blank_mode; |
| ret = vmlfb_blank_locked(vinfo); |
| mutex_unlock(&vml_mutex); |
| return ret; |
| } |
| |
| static int vmlfb_pan_display(struct fb_var_screeninfo *var, |
| struct fb_info *info) |
| { |
| struct vml_info *vinfo = container_of(info, struct vml_info, info); |
| struct vml_par *par = vinfo->par; |
| |
| mutex_lock(&vml_mutex); |
| VML_WRITE32(par, VML_DSPCADDR, (u32) vinfo->vram_start + |
| var->yoffset * vinfo->stride + |
| var->xoffset * vinfo->bytes_per_pixel); |
| (void)VML_READ32(par, VML_DSPCADDR); |
| mutex_unlock(&vml_mutex); |
| |
| return 0; |
| } |
| |
| static int vmlfb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, |
| u_int transp, struct fb_info *info) |
| { |
| u32 v; |
| |
| if (regno >= 16) |
| return -EINVAL; |
| |
| if (info->var.grayscale) { |
| red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8; |
| } |
| |
| if (info->fix.visual != FB_VISUAL_TRUECOLOR) |
| return -EINVAL; |
| |
| red = VML_TOHW(red, info->var.red.length); |
| blue = VML_TOHW(blue, info->var.blue.length); |
| green = VML_TOHW(green, info->var.green.length); |
| transp = VML_TOHW(transp, info->var.transp.length); |
| |
| v = (red << info->var.red.offset) | |
| (green << info->var.green.offset) | |
| (blue << info->var.blue.offset) | |
| (transp << info->var.transp.offset); |
| |
| switch (info->var.bits_per_pixel) { |
| case 16: |
| ((u32 *) info->pseudo_palette)[regno] = v; |
| break; |
| case 24: |
| case 32: |
| ((u32 *) info->pseudo_palette)[regno] = v; |
| break; |
| } |
| return 0; |
| } |
| |
| static int vmlfb_mmap(struct fb_info *info, struct vm_area_struct *vma) |
| { |
| struct vml_info *vinfo = container_of(info, struct vml_info, info); |
| unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; |
| int ret; |
| unsigned long prot; |
| |
| ret = vmlfb_vram_offset(vinfo, offset); |
| if (ret) |
| return -EINVAL; |
| |
| prot = pgprot_val(vma->vm_page_prot) & ~_PAGE_CACHE_MASK; |
| pgprot_val(vma->vm_page_prot) = |
| prot | cachemode2protval(_PAGE_CACHE_MODE_UC_MINUS); |
| |
| return vm_iomap_memory(vma, vinfo->vram_start, |
| vinfo->vram_contig_size); |
| } |
| |
| static int vmlfb_sync(struct fb_info *info) |
| { |
| return 0; |
| } |
| |
| static int vmlfb_cursor(struct fb_info *info, struct fb_cursor *cursor) |
| { |
| return -EINVAL; /* just to force soft_cursor() call */ |
| } |
| |
| static struct fb_ops vmlfb_ops = { |
| .owner = THIS_MODULE, |
| .fb_open = vmlfb_open, |
| .fb_release = vmlfb_release, |
| .fb_check_var = vmlfb_check_var, |
| .fb_set_par = vmlfb_set_par, |
| .fb_blank = vmlfb_blank, |
| .fb_pan_display = vmlfb_pan_display, |
| .fb_fillrect = cfb_fillrect, |
| .fb_copyarea = cfb_copyarea, |
| .fb_imageblit = cfb_imageblit, |
| .fb_cursor = vmlfb_cursor, |
| .fb_sync = vmlfb_sync, |
| .fb_mmap = vmlfb_mmap, |
| .fb_setcolreg = vmlfb_setcolreg |
| }; |
| |
| static struct pci_device_id vml_ids[] = { |
| {PCI_DEVICE(PCI_VENDOR_ID_INTEL, VML_DEVICE_VDC)}, |
| {0} |
| }; |
| |
| static struct pci_driver vmlfb_pci_driver = { |
| .name = "vmlfb", |
| .id_table = vml_ids, |
| .probe = vml_pci_probe, |
| .remove = vml_pci_remove, |
| }; |
| |
| static void __exit vmlfb_cleanup(void) |
| { |
| pci_unregister_driver(&vmlfb_pci_driver); |
| } |
| |
| static int __init vmlfb_init(void) |
| { |
| |
| #ifndef MODULE |
| char *option = NULL; |
| |
| if (fb_get_options(MODULE_NAME, &option)) |
| return -ENODEV; |
| #endif |
| |
| printk(KERN_DEBUG MODULE_NAME ": initializing\n"); |
| mutex_init(&vml_mutex); |
| INIT_LIST_HEAD(&global_no_mode); |
| INIT_LIST_HEAD(&global_has_mode); |
| |
| return pci_register_driver(&vmlfb_pci_driver); |
| } |
| |
| int vmlfb_register_subsys(struct vml_sys *sys) |
| { |
| struct vml_info *entry; |
| struct list_head *list; |
| u32 save_activate; |
| |
| mutex_lock(&vml_mutex); |
| if (subsys != NULL) { |
| subsys->restore(subsys); |
| } |
| subsys = sys; |
| subsys->save(subsys); |
| |
| /* |
| * We need to restart list traversal for each item, since we |
| * release the list mutex in the loop. |
| */ |
| |
| list = global_no_mode.next; |
| while (list != &global_no_mode) { |
| list_del_init(list); |
| entry = list_entry(list, struct vml_info, head); |
| |
| /* |
| * First, try the current mode which might not be |
| * completely validated with respect to the pixel clock. |
| */ |
| |
| if (!vmlfb_check_var_locked(&entry->info.var, entry)) { |
| vmlfb_set_par_locked(entry); |
| list_add_tail(list, &global_has_mode); |
| } else { |
| |
| /* |
| * Didn't work. Try to find another mode, |
| * that matches this subsys. |
| */ |
| |
| mutex_unlock(&vml_mutex); |
| save_activate = entry->info.var.activate; |
| entry->info.var.bits_per_pixel = 16; |
| vmlfb_set_pref_pixel_format(&entry->info.var); |
| if (fb_find_mode(&entry->info.var, |
| &entry->info, |
| vml_default_mode, NULL, 0, NULL, 16)) { |
| entry->info.var.activate |= |
| FB_ACTIVATE_FORCE | FB_ACTIVATE_NOW; |
| fb_set_var(&entry->info, &entry->info.var); |
| } else { |
| printk(KERN_ERR MODULE_NAME |
| ": Sorry. no mode found for this subsys.\n"); |
| } |
| entry->info.var.activate = save_activate; |
| mutex_lock(&vml_mutex); |
| } |
| vmlfb_blank_locked(entry); |
| list = global_no_mode.next; |
| } |
| mutex_unlock(&vml_mutex); |
| |
| printk(KERN_DEBUG MODULE_NAME ": Registered %s subsystem.\n", |
| subsys->name ? subsys->name : "unknown"); |
| return 0; |
| } |
| |
| EXPORT_SYMBOL_GPL(vmlfb_register_subsys); |
| |
| void vmlfb_unregister_subsys(struct vml_sys *sys) |
| { |
| struct vml_info *entry, *next; |
| |
| mutex_lock(&vml_mutex); |
| if (subsys != sys) { |
| mutex_unlock(&vml_mutex); |
| return; |
| } |
| subsys->restore(subsys); |
| subsys = NULL; |
| list_for_each_entry_safe(entry, next, &global_has_mode, head) { |
| printk(KERN_DEBUG MODULE_NAME ": subsys disable pipe\n"); |
| vmlfb_disable_pipe(entry); |
| list_move_tail(&entry->head, &global_no_mode); |
| } |
| mutex_unlock(&vml_mutex); |
| } |
| |
| EXPORT_SYMBOL_GPL(vmlfb_unregister_subsys); |
| |
| module_init(vmlfb_init); |
| module_exit(vmlfb_cleanup); |
| |
| MODULE_AUTHOR("Tungsten Graphics"); |
| MODULE_DESCRIPTION("Initialization of the Vermilion display devices"); |
| MODULE_VERSION("1.0.0"); |
| MODULE_LICENSE("GPL"); |