diff --git a/drivers/char/drm/Kconfig b/drivers/char/drm/Kconfig index 56ace9d..10c42b7 100644 --- a/drivers/char/drm/Kconfig +++ b/drivers/char/drm/Kconfig @@ -16,6 +16,13 @@ config DRM details. You should also select and configure AGP (/dev/agpgart) support. +config DRM_BLADE + tristate "Trident CyberBlade/i1" + depends on DRM && PCI + help + Choose this option if you have a Trident CyberBlade/i1 device. If M + is selected, the module will be called blade. + config DRM_TDFX tristate "3dfx Banshee/Voodoo3+" depends on DRM && PCI diff --git a/drivers/char/drm/Makefile b/drivers/char/drm/Makefile index 9d180c4..a212175 100644 --- a/drivers/char/drm/Makefile +++ b/drivers/char/drm/Makefile @@ -8,6 +8,7 @@ drm-objs := drm_auth.o drm_bufs.o drm drm_agpsupport.o drm_scatter.o ati_pcigart.o drm_pci.o \ drm_sysfs.o +blade-objs := blade_drv.o tdfx-objs := tdfx_drv.o r128-objs := r128_drv.o r128_cce.o r128_state.o r128_irq.o mga-objs := mga_drv.o mga_dma.o mga_state.o mga_warp.o mga_irq.o @@ -30,6 +31,7 @@ endif obj-$(CONFIG_DRM) += drm.o obj-$(CONFIG_DRM_TDFX) += tdfx.o +obj-$(CONFIG_DRM_BLADE) += blade.o obj-$(CONFIG_DRM_R128) += r128.o obj-$(CONFIG_DRM_RADEON)+= radeon.o obj-$(CONFIG_DRM_MGA) += mga.o diff --git a/drivers/char/drm/blade_drv.c b/drivers/char/drm/blade_drv.c index e76bb3b..687e9f4 100644 --- a/drivers/char/drm/blade_drv.c +++ b/drivers/char/drm/blade_drv.c @@ -1,28 +1,465 @@ +/* + * Copyright 2006 Jesse Barnes + * All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * PRECISION INSIGHT AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, + * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + * Authors: + * Jesse Barnes + * + * TODO: + * Upload/Download To/From VRAM ioctl + * Update cursor pointers in mouse driver notifier callback? + * (will only work when pointer isn't transformed, needs extra hacks + * for when pointer pixmap is not supported by hw) + * VBLANK support + */ + #include +#include +#include +#include #include "drm_pciids.h" #include "drmP.h" -#define DRIVER_AUTHOR "Jesse Barnes" +#include "blade_drm.h" + +#define DRIVER_AUTHOR "Jesse Barnes" + +#define DRIVER_NAME "blade" +#define DRIVER_DESC "Trident CyberBlade" +#define DRIVER_DATE "20060303" + +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +#ifdef BLADE_DEBUG +static void blade_dump_dma_req(drm_blade_dma_req_t *req) +{ + printk(KERN_ERR "req->sys_start: 0x%p\n", req->sys_start); + printk(KERN_ERR " sys_pitch: %u\n", req->sys_pitch); + printk(KERN_ERR " fb_start: %u\n", req->fb_start); + printk(KERN_ERR " fb_pitch: %u\n", req->fb_pitch); + printk(KERN_ERR " height: %u\n", req->height); + printk(KERN_ERR " width: %u\n", req->width); + printk(KERN_ERR " direction: %u\n", req->direction); +} +#endif + +/* + * Bus mastering (DMA) registers + */ +#define GBM_STATUS 0x2204 +#define GBM_STATUS_ERR (1 << 0) +#define GBM_STATUS_IDLE (1 << 1) +#define GBM_CTL 0x2300 +#define GBM_SG (1 << 0) +#define GBM_READ (1 << 1) +#define GBM_WRITE (1 << 2) +#define GBM_SYSTEM_ADDR 0x2310 +#define GBM_SRC_HEIGHT 0x2314 +#define GBM_SRC_WIDTH 0x2316 +#define GBM_FB_PITCH_ADDR 0x2318 +#define GBM_SYSTEM_PITCH 0x231C + +typedef struct drm_blade_private { + void __iomem *iobase; +} drm_blade_private_t; + +struct blade_dma_req_priv { + struct page **pages; /* pointer to struct page of each page in req */ + unsigned long num_pages; /* number of pages in req */ + drm_blade_dma_req_t *user_req; +}; + +static int dma_write(drm_blade_dma_req_t *req) +{ + return req->direction == BLADE_DMA_FB_TO_SYS ? 1 : 0; +} + +static unsigned long dma_pages(drm_blade_dma_req_t *req) +{ + unsigned long start_addr = (unsigned long)req->sys_start; + unsigned long bytes = req->height * req->sys_pitch; + unsigned long first_pfn = start_addr >> PAGE_SHIFT; + unsigned long last_pfn = (start_addr + bytes) >> PAGE_SHIFT; + + return last_pfn == first_pfn ? 1 : last_pfn - first_pfn; +} + +static void blade_io_write(struct drm_device *dev, u32 reg, u32 val) +{ + drm_blade_private_t *dev_priv = dev->dev_private; + + printk(KERN_ERR "writing 0x%08x to 0x%08lx\n", val, + (unsigned long)dev_priv->iobase + reg); + return writel(val, dev_priv->iobase + reg); + //return outl(val, 0xf000 + reg); +} + +static unsigned int blade_io_read(struct drm_device *dev, u32 reg) +{ + drm_blade_private_t *dev_priv = dev->dev_private; + + printk(KERN_ERR "reading from 0x%08lx\n", + (unsigned long)dev_priv->iobase + reg); + return readl(dev_priv->iobase + reg); + //return inl(0xf000 + reg); +} + +static void blade_unlock_pages(struct blade_dma_req_priv *req) +{ + struct page *page; + int i; + + for (i = 0; i < req->num_pages; i++) { + if ((page = req->pages[i])) { + if (!PageReserved(page) && dma_write(req->user_req)) + SetPageDirty(page); + page_cache_release(page); + } + } + vfree(req->pages); +} + +static int blade_lock_pages(struct blade_dma_req_priv *req) +{ + int ret; + + req->num_pages = dma_pages(req->user_req); + + req->pages = vmalloc(sizeof(struct page *) * req->num_pages); + if (!req->pages) + return DRM_ERR(ENOMEM); + memset(req->pages, 0, sizeof(struct page *) * req->num_pages); + + down_read(¤t->mm->mmap_sem); + ret = get_user_pages(current, current->mm, + (unsigned long)req->user_req->sys_start, + req->num_pages, dma_write(req->user_req), 0, + req->pages, NULL); + up_read(¤t->mm->mmap_sem); + + if (ret != req->num_pages) { + blade_unlock_pages(req); + if (ret < 0) + return ret; + return DRM_ERR(EINVAL); + } + + return 0; +} + +/** + * blade_do_dma - perform a DMA operation + * + * This routine + * - locks down user pages involved in the DMA + * - maps them into a scatterlist + * - creates a sg table for the cyberblade device and + * - initiates the DMA using the hw sg table + * + * If an error occurs or the DMA completes, the pages are unpinned and the + * various tables are freed up. + * + * The lockdown & sg mapping should probably be moved to a separate routine. + */ +static int blade_do_dma(struct drm_device *dev, struct blade_dma_req_priv *req) +{ + struct scatterlist *sg; + u32 *blade_sg_list; + dma_addr_t blade_sg_list_bus; + u32 start_off; /* Offset into first page of first byte */ + unsigned long status; + int i, ret = 0; + + start_off = (u32)req->user_req->sys_start % PAGE_SIZE; + + if ((ret = blade_lock_pages(req))) { + ret = EIO; + goto out; + } + + /* Device only supports <= 1024 page transfers */ + if (req->num_pages > 1024) { + ret = EINVAL; + goto out_unlock_pages; + } + + /* The actual hardware sg list page */ + blade_sg_list = page_address(alloc_page(GFP_KERNEL)); + if (!blade_sg_list) { + ret = ENOMEM; + goto out_unlock_pages; + } + + sg = kzalloc(sizeof(struct scatterlist) * req->num_pages, GFP_KERNEL); + if (!sg) { + ret = ENOMEM; + goto out_free_blade; + } + + for (i = 0; i < req->num_pages; i++) + sg_init_one(&sg[i], page_address(req->pages[i]), PAGE_SIZE); + + ret = dma_map_sg(&dev->pdev->dev, sg, req->num_pages, + dma_write(req->user_req) ? DMA_FROM_DEVICE : + DMA_TO_DEVICE); + if (ret != req->num_pages) { + ret = EIO; + goto out_free_sg; + } + + /* Build and map the device's sg table */ + for (i = 0; i < req->num_pages; i++) + blade_sg_list[i] = sg_dma_address(&sg[i]); + + blade_sg_list[i - 1] |= 1; /* Last sg entry marker */ + blade_sg_list_bus = dma_map_single(&dev->pdev->dev, blade_sg_list, + PAGE_SIZE, DMA_TO_DEVICE); + if (!blade_sg_list_bus) { + ret = EIO; + goto out_unmap_sg; + } + + start_off = 0; -#define DRIVER_NAME "blade_drm" -#define DRIVER_DESC "Trident CyberBlade" -#define DRIVER_DATE "20060303" - -#define DRIVER_MAJOR 1 -#define DRIVER_MINOR 0 -#define DRIVER_PATCHLEVEL 0 + /* Everything is all set. Do the DMA. */ + blade_io_write(dev, GBM_SYSTEM_ADDR, blade_sg_list_bus | start_off); + printk(KERN_ERR "GBM_SYSTEM_ADDR: 0x%08x\n", + blade_io_read(dev, GBM_SYSTEM_ADDR)); + blade_io_write(dev, GBM_SYSTEM_PITCH, req->user_req->sys_pitch); + blade_io_write(dev, GBM_SRC_HEIGHT, req->user_req->height); + blade_io_write(dev, GBM_SRC_WIDTH, req->user_req->width); + blade_io_write(dev, GBM_FB_PITCH_ADDR, ((req->user_req->fb_pitch) << 22 + | req->user_req->fb_start)); +#if 0 + blade_io_write(dev, GBM_CTL, (dma_write(req->user_req) ? GBM_WRITE : + GBM_READ) | GBM_SG); + if ((status = blade_io_read(dev, GBM_STATUS)) & GBM_STATUS_ERR) { + printk(KERN_ERR "Blade DMA error: 0x%08lx\n", + (unsigned long)status); + ret = EIO; + goto out_unmap_sg; + } + + /* wait for DMA to finish */ + while(!(blade_io_read(dev, GBM_STATUS) & GBM_STATUS_IDLE)) { + printk(KERN_ERR "Waiting for Blade DMA\n"); + if (blade_io_read(dev, GBM_STATUS) & GBM_STATUS_ERR) { + printk(KERN_ERR "Blade DMA error 2\n"); + ret = EIO; + goto out_unmap_blade; + } + } +#endif + ret = 0; + +out_unmap_blade: + /* Unmap, unlock, and free, then return */ + dma_unmap_single(&dev->pdev->dev, blade_sg_list_bus, PAGE_SIZE, + DMA_TO_DEVICE); +out_unmap_sg: + dma_unmap_sg(&dev->pdev->dev, sg, req->num_pages, + dma_write(req->user_req) ? DMA_FROM_DEVICE : + DMA_TO_DEVICE); +out_free_sg: + kfree(sg); +out_free_blade: + free_page((unsigned long)blade_sg_list); +out_unlock_pages: + blade_unlock_pages(req); +out: + return DRM_ERR(ret); +} + +/** + * blade_dma - perform a synchronous DMA request + * + * Performs the DMA as requested by the user. This is currently a synchronous + * operation. To make it asynchronous (i.e. start the DMA and return) we'll + * need: + * - a queue to maintain the list of DMA requests + * - fire DMA requests in the queue at interrupt time + * - notify the user of completion somehow (by waking up the process + * with a completion, an fd, or a signal) + */ +static int blade_dma(DRM_IOCTL_ARGS) +{ + struct blade_dma_req_priv dma_req_priv; + drm_blade_dma_req_t user_req; + int err; + u32 val; + DRM_DEVICE; + + DRM_COPY_FROM_USER_IOCTL(user_req, (drm_blade_dma_req_t *)data, + sizeof(user_req)); + + if (!(user_req.direction == BLADE_DMA_FB_TO_SYS || + user_req.direction == BLADE_DMA_SYS_TO_FB)) + return DRM_ERR(EINVAL); + + dma_req_priv.user_req = &user_req; + + printk(KERN_ERR "blade_io_read(GBM_STATUS): 0x%08x\n", + blade_io_read(dev, GBM_STATUS)); + printk(KERN_ERR "inl(GBM_STATUS): 0x%08x\n", inl(GBM_STATUS)); + err = blade_do_dma(dev, &dma_req_priv); + + if (DRM_ERR(EINTR) == err) + err = DRM_ERR(EAGAIN); + + return err; +} + +/** + * blade_irq_handler - handle interrupt events + * + * We need to check for several interrupt events here: + * - VSYNC + * - DMA completion + * - command completion + * - others? + */ +static irqreturn_t blade_irq_handler(DRM_IRQ_ARGS) +{ + /* + * If the interrupt is Capture (CR9B) or GE (2120) then we assume it's + * not a vblank interrupt + */ +// if (!CR9B && !2120 status) + /* must be vblank */ + return IRQ_HANDLED; +} + +static void blade_irq_preinstall(struct drm_device *dev) +{ + /* Enable VBLANK interrupts */ +// write 1 << 5 to port 0x3b4 or 0x3b5 +} + +static void blade_irq_postinstall(struct drm_device *dev) +{ + /* Nothing to do */ +} + +static void blade_irq_uninstall(struct drm_device *dev) +{ + /* Disable interrupts here */ +} + +/** + * blade_vblank_wait - sleep until a vblank event occurs + * + * Wait for the next vblank to occur, sleeping between checks. + */ +static int blade_vblank_wait(struct drm_device *dev, unsigned int *sequence) +{ + unsigned int cur_vblank; + int ret = 0; + + /* + * Wait up to .5s for the next blank to occur + */ + DRM_WAIT_ON(ret, dev->vbl_queue, DRM_HZ >> 1, + (((cur_vblank = atomic_read(&dev->vbl_received)) + - *sequence) <= (1 << 23))); + + *sequence = cur_vblank; + + return ret; +} + +int blade_load(drm_device_t *dev, unsigned long unused) +{ + drm_blade_private_t *dev_priv; + u16 cmd; + + dma_set_mask(&dev->pdev->dev, DMA_32BIT_MASK); + + dev_priv = drm_calloc(1, sizeof(drm_blade_private_t), DRM_MEM_DRIVER); + if (dev_priv == NULL) + return DRM_ERR(ENOMEM); + + /* Map the 128k register window */ + dev_priv->iobase = ioremap(pci_resource_start(dev->pdev, 1), + pci_resource_len(dev->pdev, 1)); + if (!dev_priv->iobase) { + drm_free(dev_priv, sizeof(drm_blade_private_t), + DRM_MEM_DRIVER); + return DRM_ERR(EIO); + } + + /* Enable DMA */ + pci_read_config_word(dev->pdev, PCI_COMMAND, &cmd); + cmd |= (PCI_COMMAND_MASTER | PCI_COMMAND_MEMORY | PCI_COMMAND_IO); + pci_write_config_word(dev->pdev, PCI_COMMAND, cmd); + + dev->dev_private = (void *)dev_priv; + + return 0; +} + +int blade_unload(drm_device_t *dev) +{ + drm_blade_private_t *dev_priv = dev->dev_private; + u16 cmd; + + /* Disable DMA */ + pci_read_config_word(dev->pdev, PCI_COMMAND, &cmd); + cmd &= (u16)~PCI_COMMAND_MASTER; + pci_write_config_word(dev->pdev, PCI_COMMAND, cmd); + + iounmap(dev_priv->iobase); + + drm_free(dev_priv, sizeof(drm_blade_private_t), DRM_MEM_DRIVER); + + return 0; +} static struct pci_device_id blade_pciids[] = { blade_PCI_IDS }; +drm_ioctl_desc_t blade_ioctls[] = { + [DRM_IOCTL_NR(DRM_BLADE_DMA)] = {blade_dma, DRM_AUTH} +}; + +static int blade_max_ioctl = DRM_ARRAY_SIZE(blade_ioctls); + static struct drm_driver blade_driver = { .driver_features = (DRIVER_USE_AGP | DRIVER_USE_MTRR | DRIVER_PCI_DMA | DRIVER_SG | DRIVER_HAVE_DMA | - DRIVER_HAVE_IRQ | DRIVER_FB_DMA), /* IRQ_VBL? */ + DRIVER_HAVE_IRQ | DRIVER_FB_DMA | DRIVER_IRQ_VBL), + .load = blade_load, + .unload = blade_unload, .reclaim_buffers = drm_core_reclaim_buffers, .get_map_ofs = drm_core_get_map_ofs, .get_reg_ofs = drm_core_get_reg_ofs, + .vblank_wait = blade_vblank_wait, + .irq_preinstall = blade_irq_preinstall, + .irq_postinstall = blade_irq_postinstall, + .irq_uninstall = blade_irq_uninstall, + .irq_handler = blade_irq_handler, + .ioctls = blade_ioctls, .fops = { .owner = THIS_MODULE, .open = drm_open, @@ -47,6 +484,7 @@ static struct drm_driver blade_driver = static int __init blade_init(void) { + blade_driver.num_ioctls = blade_max_ioctl; return drm_init(&blade_driver); } @@ -60,4 +498,4 @@ module_exit(blade_exit); MODULE_AUTHOR(DRIVER_AUTHOR); MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_LICENSE("GPL"); +MODULE_LICENSE("GPL and additional rights"); diff --git a/drivers/char/drm/drm_pciids.h b/drivers/char/drm/drm_pciids.h index 2c17e88..0441d52 100644 --- a/drivers/char/drm/drm_pciids.h +++ b/drivers/char/drm/drm_pciids.h @@ -245,3 +245,5 @@ {0x8086, 0x27a2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, \ {0, 0, 0} +#define blade_PCI_IDS \ + {0x1023, 0x8500, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}