diff options
author | Alan Hourihane <alanh@fairlite.demon.co.uk> | 2007-05-08 00:39:25 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-05-08 11:15:32 -0700 |
commit | dbe7e429fedb3fbc93b496cc1c3eb4fc28333ac0 (patch) | |
tree | 9f88a999af677f65beb7041604c3a5d63bfc58db | |
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>
-rw-r--r-- | drivers/video/Kconfig | 16 | ||||
-rw-r--r-- | drivers/video/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/backlight/Kconfig | 8 | ||||
-rw-r--r-- | drivers/video/backlight/Makefile | 1 | ||||
-rw-r--r-- | drivers/video/backlight/cr_bllcd.c | 287 | ||||
-rw-r--r-- | drivers/video/vermilion/Makefile | 5 | ||||
-rw-r--r-- | drivers/video/vermilion/cr_pll.c | 208 | ||||
-rw-r--r-- | drivers/video/vermilion/vermilion.c | 1195 | ||||
-rw-r--r-- | drivers/video/vermilion/vermilion.h | 260 |
9 files changed, 1981 insertions, 0 deletions
diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 863c5983ee60..74d764e2e1fa 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -882,6 +882,22 @@ config FB_I810_I2C select FB_DDC help +config FB_LE80578 + tristate "Intel LE80578 (Vermilion) support" + depends on FB && PCI && X86 + select FB_MODE_HELPERS + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + help + This driver supports the LE80578 (Vermilion Range) chipset + +config FB_CARILLO_RANCH + tristate "Intel Carillo Ranch support" + depends on FB_LE80578 && FB && PCI && X86 + help + This driver supports the LE80578 (Carillo Ranch) board + config FB_INTEL tristate "Intel 830M/845G/852GM/855GM/865G/915G/945G support (EXPERIMENTAL)" depends on FB && EXPERIMENTAL && PCI && X86 diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 6c7b26e81fc2..a59395d6189d 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -56,6 +56,7 @@ obj-$(CONFIG_FB_IMSTT) += imsttfb.o obj-$(CONFIG_FB_FM2) += fm2fb.o obj-$(CONFIG_FB_CYBLA) += cyblafb.o obj-$(CONFIG_FB_TRIDENT) += tridentfb.o +obj-$(CONFIG_FB_LE80578) += vermilion/ obj-$(CONFIG_FB_S3) += s3fb.o obj-$(CONFIG_FB_STI) += stifb.o obj-$(CONFIG_FB_FFB) += ffb.o sbuslib.o diff --git a/drivers/video/backlight/Kconfig b/drivers/video/backlight/Kconfig index 47d15b5d985a..fbef663fc057 100644 --- a/drivers/video/backlight/Kconfig +++ b/drivers/video/backlight/Kconfig @@ -63,3 +63,11 @@ config BACKLIGHT_PROGEAR help If you have a Frontpath ProGear say Y to enable the backlight driver. + +config BACKLIGHT_CARILLO_RANCH + tristate "Intel Carillo Ranch Backlight Driver" + depends on BACKLIGHT_CLASS_DEVICE && LCD_CLASS_DEVICE && PCI && X86 && FB_LE80578 + default n + help + If you have a Intel LE80578 (Carillo Ranch) say Y to enable the + backlight driver. diff --git a/drivers/video/backlight/Makefile b/drivers/video/backlight/Makefile index 0c3ce46f5094..c6e2266f63e2 100644 --- a/drivers/video/backlight/Makefile +++ b/drivers/video/backlight/Makefile @@ -6,3 +6,4 @@ obj-$(CONFIG_BACKLIGHT_CORGI) += corgi_bl.o obj-$(CONFIG_BACKLIGHT_HP680) += hp680_bl.o obj-$(CONFIG_BACKLIGHT_LOCOMO) += locomolcd.o obj-$(CONFIG_BACKLIGHT_PROGEAR) += progear_bl.o +obj-$(CONFIG_BACKLIGHT_CARILLO_RANCH) += cr_bllcd.o diff --git a/drivers/video/backlight/cr_bllcd.c b/drivers/video/backlight/cr_bllcd.c new file mode 100644 index 000000000000..e9bbc3455c94 --- /dev/null +++ b/drivers/video/backlight/cr_bllcd.c @@ -0,0 +1,287 @@ +/* + * 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 Carillo Ranch video subsystem driver. + * The Carillo Ranch video subsystem 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 Carillo Ranch video subsystem 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 Hellstrom <thomas-at-tungstengraphics-dot-com> + * Alan Hourihane <alanh-at-tungstengraphics-dot-com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/fb.h> +#include <linux/backlight.h> +#include <linux/lcd.h> +#include <linux/pci.h> +#include <asm/uaccess.h> + +/* The LVDS- and panel power controls sits on the + * GPIO port of the ISA bridge. + */ + +#define CRVML_DEVICE_LPC 0x27B8 +#define CRVML_REG_GPIOBAR 0x48 +#define CRVML_REG_GPIOEN 0x4C +#define CRVML_GPIOEN_BIT (1 << 4) +#define CRVML_PANEL_PORT 0x38 +#define CRVML_LVDS_ON 0x00000001 +#define CRVML_PANEL_ON 0x00000002 +#define CRVML_BACKLIGHT_OFF 0x00000004 + +/* The PLL Clock register sits on Host bridge */ +#define CRVML_DEVICE_MCH 0x5001 +#define CRVML_REG_MCHBAR 0x44 +#define CRVML_REG_MCHEN 0x54 +#define CRVML_MCHEN_BIT (1 << 28) +#define CRVML_MCHMAP_SIZE 4096 +#define CRVML_REG_CLOCK 0xc3c +#define CRVML_CLOCK_SHIFT 8 +#define CRVML_CLOCK_MASK 0x00000f00 + +static struct pci_dev *lpc_dev; +static u32 gpio_bar; + +struct cr_panel { + struct backlight_device *cr_backlight_device; + struct lcd_device *cr_lcd_device; +}; + +static int cr_backlight_set_intensity(struct backlight_device *bd) +{ + int intensity = bd->props.brightness; + u32 addr = gpio_bar + CRVML_PANEL_PORT; + u32 cur = inl(addr); + + if (bd->props.power == FB_BLANK_UNBLANK) + intensity = FB_BLANK_UNBLANK; + if (bd->props.fb_blank == FB_BLANK_UNBLANK) + intensity = FB_BLANK_UNBLANK; + if (bd->props.power == FB_BLANK_POWERDOWN) + intensity = FB_BLANK_POWERDOWN; + if (bd->props.fb_blank == FB_BLANK_POWERDOWN) + intensity = FB_BLANK_POWERDOWN; + + if (intensity == FB_BLANK_UNBLANK) { /* FULL ON */ + cur &= ~CRVML_BACKLIGHT_OFF; + outl(cur, addr); + } else if (intensity == FB_BLANK_POWERDOWN) { /* OFF */ + cur |= CRVML_BACKLIGHT_OFF; + outl(cur, addr); + } /* anything else, don't bother */ + + return 0; +} + +static int cr_backlight_get_intensity(struct backlight_device *bd) +{ + u32 addr = gpio_bar + CRVML_PANEL_PORT; + u32 cur = inl(addr); + u8 intensity; + + if (cur & CRVML_BACKLIGHT_OFF) + intensity = FB_BLANK_POWERDOWN; + else + intensity = FB_BLANK_UNBLANK; + + return intensity; +} + +static struct backlight_ops cr_backlight_ops = { + .get_brightness = cr_backlight_get_intensity, + .update_status = cr_backlight_set_intensity, +}; + +static void cr_panel_on(void) +{ + u32 addr = gpio_bar + CRVML_PANEL_PORT; + u32 cur = inl(addr); + + if (!(cur & CRVML_PANEL_ON)) { + /* Make sure LVDS controller is down. */ + if (cur & 0x00000001) { + cur &= ~CRVML_LVDS_ON; + outl(cur, addr); + } + /* Power up Panel */ + schedule_timeout(HZ / 10); + cur |= CRVML_PANEL_ON; + outl(cur, addr); + } + + /* Power up LVDS controller */ + + if (!(cur & CRVML_LVDS_ON)) { + schedule_timeout(HZ / 10); + outl(cur | CRVML_LVDS_ON, addr); + } +} + +static void cr_panel_off(void) +{ + u32 addr = gpio_bar + CRVML_PANEL_PORT; + u32 cur = inl(addr); + + /* Power down LVDS controller first to avoid high currents */ + if (cur & CRVML_LVDS_ON) { + cur &= ~CRVML_LVDS_ON; + outl(cur, addr); + } + if (cur & CRVML_PANEL_ON) { + schedule_timeout(HZ / 10); + outl(cur & ~CRVML_PANEL_ON, addr); + } +} + +static int cr_lcd_set_power(struct lcd_device *ld, int power) +{ + if (power == FB_BLANK_UNBLANK) + cr_panel_on(); + if (power == FB_BLANK_POWERDOWN) + cr_panel_off(); + + return 0; +} + +static struct lcd_ops cr_lcd_ops = { + .set_power = cr_lcd_set_power, +}; + +static int cr_backlight_probe(struct platform_device *pdev) +{ + struct cr_panel *crp; + u8 dev_en; + + crp = kzalloc(sizeof(crp), GFP_KERNEL); + if (crp == NULL) + return -ENOMEM; + + lpc_dev = pci_get_device(PCI_VENDOR_ID_INTEL, + CRVML_DEVICE_LPC, NULL); + if (!lpc_dev) { + printk("INTEL CARILLO RANCH LPC not found.\n"); + return -ENODEV; + } + + pci_read_config_byte(lpc_dev, CRVML_REG_GPIOEN, &dev_en); + if (!(dev_en & CRVML_GPIOEN_BIT)) { + printk(KERN_ERR + "Carillo Ranch GPIO device was not enabled.\n"); + pci_dev_put(lpc_dev); + return -ENODEV; + } + + crp->cr_backlight_device = backlight_device_register("cr-backlight", + &pdev->dev, NULL, + &cr_backlight_ops); + if (IS_ERR(crp->cr_backlight_device)) { + pci_dev_put(lpc_dev); + return PTR_ERR(crp->cr_backlight_device); + } + + crp->cr_lcd_device = lcd_device_register("cr-lcd", + &pdev->dev, + &cr_lcd_ops); + + if (IS_ERR(crp->cr_lcd_device)) { + pci_dev_put(lpc_dev); + return PTR_ERR(crp->cr_backlight_device); + } + + pci_read_config_dword(lpc_dev, CRVML_REG_GPIOBAR, + &gpio_bar); + gpio_bar &= ~0x3F; + + crp->cr_backlight_device->props.power = FB_BLANK_UNBLANK; + crp->cr_backlight_device->props.brightness = 0; + crp->cr_backlight_device->props.max_brightness = 0; + cr_backlight_set_intensity(crp->cr_backlight_device); + + cr_lcd_set_power(crp->cr_lcd_device, FB_BLANK_UNBLANK); + + platform_set_drvdata(pdev, crp); + + return 0; +} + +static int cr_backlight_remove(struct platform_device *pdev) +{ + struct cr_panel *crp = platform_get_drvdata(pdev); + crp->cr_backlight_device->props.power = FB_BLANK_POWERDOWN; + crp->cr_backlight_device->props.brightness = 0; + crp->cr_backlight_device->props.max_brightness = 0; + cr_backlight_set_intensity(crp->cr_backlight_device); + cr_lcd_set_power(crp->cr_lcd_device, FB_BLANK_POWERDOWN); + backlight_device_unregister(crp->cr_backlight_device); + lcd_device_unregister(crp->cr_lcd_device); + pci_dev_put(lpc_dev); + + return 0; +} + +static struct platform_driver cr_backlight_driver = { + .probe = cr_backlight_probe, + .remove = cr_backlight_remove, + .driver = { + .name = "cr_backlight", + }, +}; + +static struct platform_device *crp; + +static int __init cr_backlight_init(void) +{ + int ret = platform_driver_register(&cr_backlight_driver); + + if (!ret) { + crp = platform_device_alloc("cr_backlight", -1); + if (!crp) + return -ENOMEM; + + ret = platform_device_add(crp); + + if (ret) { + platform_device_put(crp); + platform_driver_unregister(&cr_backlight_driver); + } + } + + printk("Carillo Ranch Backlight Driver Initialized.\n"); + + return ret; +} + +static void __exit cr_backlight_exit(void) +{ + platform_device_unregister(crp); + platform_driver_unregister(&cr_backlight_driver); +} + +module_init(cr_backlight_init); +module_exit(cr_backlight_exit); + +MODULE_AUTHOR("Tungsten Graphics Inc."); +MODULE_DESCRIPTION("Carillo Ranch Backlight Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/video/vermilion/Makefile b/drivers/video/vermilion/Makefile new file mode 100644 index 000000000000..cc21a656153d --- /dev/null +++ b/drivers/video/vermilion/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_FB_LE80578) += vmlfb.o +obj-$(CONFIG_FB_CARILLO_RANCH) += crvml.o + +vmlfb-objs := vermilion.o +crvml-objs := cr_pll.o diff --git a/drivers/video/vermilion/cr_pll.c b/drivers/video/vermilion/cr_pll.c new file mode 100644 index 000000000000..ebc6e6e0dd0f --- /dev/null +++ b/drivers/video/vermilion/cr_pll.c @@ -0,0 +1,208 @@ +/* + * 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 Carillo Ranch video subsystem driver. + * The Carillo Ranch video subsystem 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 Carillo Ranch video subsystem 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 Hellstrom <thomas-at-tungstengraphics-dot-com> + * Alan Hourihane <alanh-at-tungstengraphics-dot-com> + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/pci.h> +#include <linux/errno.h> +#include <linux/fb.h> +#include "vermilion.h" + +/* The PLL Clock register sits on Host bridge */ +#define CRVML_DEVICE_MCH 0x5001 +#define CRVML_REG_MCHBAR 0x44 +#define CRVML_REG_MCHEN 0x54 +#define CRVML_MCHEN_BIT (1 << 28) +#define CRVML_MCHMAP_SIZE 4096 +#define CRVML_REG_CLOCK 0xc3c +#define CRVML_CLOCK_SHIFT 8 +#define CRVML_CLOCK_MASK 0x00000f00 + +static struct pci_dev *mch_dev; +static u32 mch_bar; +static void __iomem *mch_regs_base; +static u32 saved_clock; + +static const unsigned crvml_clocks[] = { + 6750, + 13500, + 27000, + 29700, + 37125, + 54000, + 59400, + 74250, + 120000 + /* + * There are more clocks, but they are disabled on the CR board. + */ +}; + +static const u32 crvml_clock_bits[] = { + 0x0a, + 0x09, + 0x08, + 0x07, + 0x06, + 0x05, + 0x04, + 0x03, + 0x0b +}; + +static const unsigned crvml_num_clocks = ARRAY_SIZE(crvml_clocks); + +static int crvml_sys_restore(struct vml_sys *sys) +{ + void __iomem *clock_reg = mch_regs_base + CRVML_REG_CLOCK; + + iowrite32(saved_clock, clock_reg); + ioread32(clock_reg); + + return 0; +} + +static int crvml_sys_save(struct vml_sys *sys) +{ + void __iomem *clock_reg = mch_regs_base + CRVML_REG_CLOCK; + + saved_clock = ioread32(clock_reg); + + return 0; +} + +static int crvml_nearest_index(const struct vml_sys *sys, int clock) +{ + int i; + int cur_index = 0; + int cur_diff; + int diff; + + cur_diff = clock - crvml_clocks[0]; + cur_diff = (cur_diff < 0) ? -cur_diff : cur_diff; + for (i = 1; i < crvml_num_clocks; ++i) { + diff = clock - crvml_clocks[i]; + diff = (diff < 0) ? -diff : diff; + if (diff < cur_diff) { + cur_index = i; + cur_diff = diff; + } + } + return cur_index; +} + +static int crvml_nearest_clock(const struct vml_sys *sys, int clock) +{ + return crvml_clocks[crvml_nearest_index(sys, clock)]; +} + +static int crvml_set_clock(struct vml_sys *sys, int clock) +{ + void __iomem *clock_reg = mch_regs_base + CRVML_REG_CLOCK; + int index; + u32 clock_val; + + index = crvml_nearest_index(sys, clock); + + if (crvml_clocks[index] != clock) + return -EINVAL; + + clock_val = ioread32(clock_reg) & ~CRVML_CLOCK_MASK; + clock_val = crvml_clock_bits[index] << CRVML_CLOCK_SHIFT; + iowrite32(clock_val, clock_reg); + ioread32(clock_reg); + + return 0; +} + +static struct vml_sys cr_pll_ops = { + .name = "Carillo Ranch", + .save = crvml_sys_save, + .restore = crvml_sys_restore, + .set_clock = crvml_set_clock, + .nearest_clock = crvml_nearest_clock, +}; + +static int __init cr_pll_init(void) +{ + int err; + u32 dev_en; + + mch_dev = pci_get_device(PCI_VENDOR_ID_INTEL, + CRVML_DEVICE_MCH, NULL); + if (!mch_dev) { + printk(KERN_ERR + "Could not find Carillo Ranch MCH device.\n"); + return -ENODEV; + } + + pci_read_config_dword(mch_dev, CRVML_REG_MCHEN, &dev_en); + if (!(dev_en & CRVML_MCHEN_BIT)) { + printk(KERN_ERR + "Carillo Ranch MCH device was not enabled.\n"); + pci_dev_put(mch_dev); + return -ENODEV; + } + + pci_read_config_dword(mch_dev, CRVML_REG_MCHBAR, + &mch_bar); + mch_regs_base = + ioremap_nocache(mch_bar, CRVML_MCHMAP_SIZE); + if (!mch_regs_base) { + printk(KERN_ERR + "Carillo Ranch MCH device was not enabled.\n"); + pci_dev_put(mch_dev); + return -ENODEV; + } + + err = vmlfb_register_subsys(&cr_pll_ops); + if (err) { + printk(KERN_ERR + "Carillo Ranch failed to initialize vml_sys.\n"); + pci_dev_put(mch_dev); + return err; + } + + return 0; +} + +static void __exit cr_pll_exit(void) +{ + vmlfb_unregister_subsys(&cr_pll_ops); + + iounmap(mch_regs_base); + pci_dev_put(mch_dev); +} + +module_init(cr_pll_init); +module_exit(cr_pll_exit); + +MODULE_AUTHOR("Tungsten Graphics Inc."); +MODULE_DESCRIPTION("Carillo Ranch PLL Driver"); +MODULE_LICENSE("GPL"); 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 @@ +/* + * 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/mm.h> +#include <linux/fb.h> +#include <linux/pci.h> +#include <asm/cacheflush.h> +#include <asm/tlbflush.h> +#include <linux/mmzone.h> +#include <asm/uaccess.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; + pgprot_t wc_pageprot; + + wc_pageprot = PAGE_KERNEL_NOCACHE; + 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 nopage mapping, so + * up the usage count once more. + */ + + 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. + * The first global_flush_tlb() is really only there to do a global + * wbinvd(). + */ + + global_flush_tlb(); + change_page_attr(virt_to_page(va->logical), va->size >> PAGE_SHIFT, + wc_pageprot); + global_flush_tlb(); + + 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. + */ + + change_page_attr(virt_to_page(va->logical), + va->size >> PAGE_SHIFT, PAGE_KERNEL); + global_flush_tlb(); + + /* + * 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_set_drvdata(par->vdc, NULL); + pci_disable_device(par->gpu); + pci_disable_device(par->vdc); + } +} + +/* + * Free up allocated resources for a device. + */ + +static void __devexit 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 __devinit 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; + break; + } + + 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; + } |