On 10/31/21 21:49, Sven Schnelle wrote: > This adds a framebuffer driver for HP's visualize series of > cards. The aim is to support all FX2 - FX10 types but currently only > FX5 is tested as i don't have any other card. > > Currently no mmap of video memory is supported as i haven't figured > out how to access VRAM directly. > > Signed-off-by: Sven Schnelle <svens@xxxxxxxxxxxxxx> That's really cool! > --- > drivers/video/fbdev/Kconfig | 14 + > drivers/video/fbdev/Makefile | 2 +- > drivers/video/fbdev/visualizefx.c | 602 ++++++++++++++++++++++++++++++ > 3 files changed, 617 insertions(+), 1 deletion(-) > create mode 100644 drivers/video/fbdev/visualizefx.c > > diff --git a/drivers/video/fbdev/Kconfig b/drivers/video/fbdev/Kconfig > index 6ed5e608dd04..ee963f755047 100644 > --- a/drivers/video/fbdev/Kconfig > +++ b/drivers/video/fbdev/Kconfig > @@ -566,6 +566,20 @@ config FB_STI > > It is safe to enable this option, so you should probably say "Y". > > +config FB_VISUALIZEFX > + tristate "HP Visualize FX support" > + depends on FB && PCI && PARISC > + select FB_CFB_FILLRECT > + select FB_CFB_COPYAREA > + select FB_CFB_IMAGEBLIT > + select RATIONAL You should add "default y", so that it automatically gets enabled on parisc machines: default y Other than that you may add an: Acked-by: Helge Deller <deller@xxxxxx> Thanks! Helge > + help > + Frame buffer driver for the HP Visualize FX cards. These cards are > + commonly found in PA-RISC workstations. Currently only FX5 has been > + tested. > + > + Say Y if you have such a card. > + > config FB_MAC > bool "Generic Macintosh display support" > depends on (FB = y) && MAC > diff --git a/drivers/video/fbdev/Makefile b/drivers/video/fbdev/Makefile > index 477b9624b703..3ef26907a3a4 100644 > --- a/drivers/video/fbdev/Makefile > +++ b/drivers/video/fbdev/Makefile > @@ -129,6 +129,6 @@ obj-$(CONFIG_FB_MX3) += mx3fb.o > obj-$(CONFIG_FB_DA8XX) += da8xx-fb.o > obj-$(CONFIG_FB_SSD1307) += ssd1307fb.o > obj-$(CONFIG_FB_SIMPLE) += simplefb.o > - > +obj-$(CONFIG_FB_VISUALIZEFX) += visualizefx.o > # the test framebuffer is last > obj-$(CONFIG_FB_VIRTUAL) += vfb.o > diff --git a/drivers/video/fbdev/visualizefx.c b/drivers/video/fbdev/visualizefx.c > new file mode 100644 > index 000000000000..9318e07be1aa > --- /dev/null > +++ b/drivers/video/fbdev/visualizefx.c > @@ -0,0 +1,602 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Framebuffer driver for Visualize FX cards commonly found in PA-RISC machines > + * > + * Copyright (c) 2021 Sven Schnelle <svens@xxxxxxxxxxxxxx> > + */ > + > +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt > + > +#include <linux/module.h> > +#include <linux/kernel.h> > +#include <linux/init.h> > +#include <linux/types.h> > +#include <linux/pci.h> > +#include <linux/fb.h> > +#include <linux/delay.h> > +#include <linux/rational.h> > + > +#define VISFX_VRAM_ENDIANESS_WRITE 0xa4303c > +#define VISFX_VRAM_ENDIANESS_READ 0xaa0408 > +#define VISFX_VRAM_ENDIANESS_BIG 0xe4e4e4e4 > +#define VISFX_VRAM_ENDIANESS_LITTLE 0x1b1b1b1b > + > +#define VISFX_STATUS 0x641400 > + > +#define VISFX_COLOR_MASK 0x800018 > +#define VISFX_COLOR_INDEX 0x800020 > +#define VISFX_COLOR_VALUE 0x800024 > + > +#define VISFX_SYNC_POLARITY 0x800044 > +#define VISFX_SYNC_VISIBLE_SIZE 0x80005c > +#define VISFX_SYNC_HORIZ_CONF 0x800060 > +#define VISFX_SYNC_VERT_CONF 0x800068 > +#define VISFX_SYNC_MASTER_PLL 0x8000a0 > +#define VISFX_SYNC_PLL_STATUS 0x8000b8 > + > +#define VISFX_VRAM_WRITE_MODE 0xa00808 > +#define VISFX_VRAM_MASK 0xa0082c > +#define VISFX_FGCOLOR 0xa0083c > +#define VISFX_BGCOLOR 0xa00844 > +#define VISFX_WRITE_MASK 0xa0084c > +#define VISFX_VRAM_WRITE_DATA_INCRX 0xa60000 > +#define VISFX_VRAM_WRITE_DATA_INCRY 0xa68000 > +#define VISFX_SCREEN_SIZE 0xac1054 > +#define VISFX_VRAM_WRITE_DEST 0xac1000 > + > +#define VISFX_START 0xb3c000 > +#define VISFX_SIZE 0xb3c808 > +#define VISFX_HEIGHT 0xb3c008 > +#define VISFX_DST 0xb3cc00 > + > +#define VISFX_DFP_ENABLE 0x10000 > +#define VISFX_HSYNC_POSITIVE 0x40000 > +#define VISFX_VSYNC_POSITIVE 0x80000 > + > +#define VISFX_SYNC_PLL_BASE 49383 /* 20.25MHz in ps */ > + > +#define VISFX_CURSOR_POS 0x400000 > +#define VISFX_CURSOR_INDEX 0x400004 > +#define VISFX_CURSOR_DATA 0x400008 > +#define VISFX_CURSOR_COLOR 0x400010 > +#define VISFX_CURSOR_ENABLE 0x80000000 > + > +#define VISFX_VRAM_WRITE_MODE_BITMAP 0x02000000 > +#define VISFX_VRAM_WRITE_MODE_COLOR 0x050004c0 > +#define VISFX_VRAM_WRITE_MODE_FILL 0x05000080 > + > +#define VISFX_FB_LENGTH 0x01000000 > +#define VISFX_FB_OFFSET 0x01000000 > +#define NR_PALETTE 256 > + > +struct visfx_par { > + u32 pseudo_palette[256]; > + unsigned long debug_reg; > + void __iomem *reg_base; > + unsigned long reg_size; > + int open_count; > +}; > + > +static u32 visfx_readl(struct fb_info *info, int reg) > +{ > + struct visfx_par *par = info->par; > + > + return le32_to_cpu(readl(par->reg_base + reg)); > +} > + > +static void visfx_writel(struct fb_info *info, int reg, u32 val) > +{ > + struct visfx_par *par = info->par; > + > + return writel(cpu_to_le32(val), par->reg_base + reg); > +} > + > +static void visfx_write_vram(struct fb_info *info, int reg, u32 val) > +{ > + struct visfx_par *par = info->par; > + > + return writel(val, par->reg_base + reg); > +} > + > +static void visfx_bmove_wait(struct fb_info *info) > +{ > + while (visfx_readl(info, VISFX_STATUS)); > +} > + > +static ssize_t visfx_sysfs_show_reg(struct device *dev, > + struct device_attribute *attr, > + char *buf) > +{ > + struct fb_info *info = pci_get_drvdata(container_of(dev, struct pci_dev, dev)); > + struct visfx_par *par = info->par; > + > + return sprintf(buf, "%08x\n", visfx_readl(info, par->debug_reg)); > +} > + > +static ssize_t visfx_sysfs_store_reg(struct device *dev, > + struct device_attribute *attr, > + const char *buf, size_t count) > +{ > + struct fb_info *info = pci_get_drvdata(container_of(dev, struct pci_dev, dev)); > + struct visfx_par *par = info->par; > + unsigned long data; > + char *p; > + > + p = strchr(buf, '='); > + if (p) > + *p = '\0'; > + > + if (kstrtoul(buf, 16, &par->debug_reg)) > + return -EINVAL; > + > + if (par->debug_reg > par->reg_size) > + return -EINVAL; > + > + if (p) { > + if (kstrtoul(p+1, 16, &data)) > + return -EINVAL; > + visfx_writel(info, par->debug_reg, data); > + } > + return count; > +} > + > +static DEVICE_ATTR(reg, 0600, visfx_sysfs_show_reg, visfx_sysfs_store_reg); > + > +static void visfx_set_vram_addr(struct fb_info *info, int x, int y) > +{ > + visfx_writel(info, VISFX_VRAM_WRITE_DEST, (y << 16) | x); > +} > + > +static void visfx_set_bmove_color(struct fb_info *info, int fg, int bg) > +{ > + visfx_writel(info, VISFX_BGCOLOR, 0x01010101 * bg); > + visfx_writel(info, VISFX_FGCOLOR, 0x01010101 * fg); > +} > + > +static void visfx_imageblit_mono(struct fb_info *info, const char *data, int dx, int dy, > + int width, int height, int fg_color, int bg_color) > +{ > + int _width, x, y; > + u32 tmp; > + > + visfx_set_bmove_color(info, fg_color, bg_color); > + visfx_writel(info, VISFX_VRAM_WRITE_MODE, VISFX_VRAM_WRITE_MODE_COLOR); > + visfx_writel(info, VISFX_VRAM_MASK, 0xffffffff); > + > + for (x = 0, _width = width; _width > 0; _width -= 32, x += 4) { > + visfx_set_vram_addr(info, dx + x * 8, dy); > + if (_width >= 32) { > + for (y = 0; y < height; y++) { > + memcpy(&tmp, &data[y * (width / 8) + x], 4); > + visfx_write_vram(info, VISFX_VRAM_WRITE_DATA_INCRY, tmp); > + } > + } else { > + visfx_writel(info, VISFX_VRAM_MASK, GENMASK(31, 31 - _width + 1)); > + for (y = 0; y < height; y++) { > + tmp = 0; > + memcpy(&tmp, &data[y * (width / 8) + x], ((_width-1)/8)+1); > + visfx_write_vram(info, VISFX_VRAM_WRITE_DATA_INCRY, tmp); > + } > + } > + } > +} > + > +static void visfx_setup_unknown(struct fb_info *info) > +{ > + visfx_writel(info, 0xb08044, 0x1b); > + visfx_writel(info, 0xb08048, 0x1b); > + visfx_writel(info, 0x920860, 0xe4); > + visfx_writel(info, 0xa00818, 0); > + visfx_writel(info, 0xa00404, 0); > + visfx_writel(info, 0x921110, 0); > + visfx_writel(info, 0x9211d8, 0); > + visfx_writel(info, 0xa0086c, 0); > + visfx_writel(info, 0x921114, 0); > + visfx_writel(info, 0xac1050, 0); > + visfx_writel(info, 0xa00858, 0xb0); > + > + visfx_writel(info, VISFX_VRAM_WRITE_MODE, VISFX_VRAM_WRITE_MODE_BITMAP); > + visfx_writel(info, VISFX_WRITE_MASK, 0xffffffff); > + visfx_writel(info, VISFX_VRAM_MASK, 0xffffffff); > +#ifdef __BIG_ENDIAN > + visfx_writel(info, VISFX_VRAM_ENDIANESS_READ, VISFX_VRAM_ENDIANESS_BIG); > + visfx_writel(info, VISFX_VRAM_ENDIANESS_WRITE, VISFX_VRAM_ENDIANESS_BIG); > +#else > + visfx_writel(info, VISFX_VRAM_ENDIANESS_READ, VISFX_VRAM_ENDIANESS_LITTLE); > + visfx_writel(info, VISFX_VRAM_ENDIANESS_WRITE, VISFX_VRAM_ENDIANESS_LITTLE); > +#endif > +} > + > +static void visfx_imageblit(struct fb_info *info, const struct fb_image *image) > +{ > + int x, y; > + > + visfx_bmove_wait(info); > + visfx_writel(info, VISFX_WRITE_MASK, 0xffffffff); > + > + switch (image->depth) { > + case 1: > + visfx_imageblit_mono(info, image->data, image->dx, image->dy, > + image->width, image->height, > + image->fg_color, image->bg_color); > + break; > + case 8: > + visfx_writel(info, VISFX_VRAM_WRITE_MODE, VISFX_VRAM_WRITE_MODE_BITMAP); > + > + for (y = 0; y < image->height; y++) { > + u32 data = 0; > + int pos = 0; > + > + visfx_writel(info, VISFX_WRITE_MASK, 0xffffffff); > + visfx_set_vram_addr(info, image->dx, image->dy + y); > + > + for (x = 0; x < image->width; x++) { > + pos = x & 3; > + data |= ((u8 *)image->data)[y * image->height + x] << (pos * 8); > + if (pos == 3) { > + visfx_write_vram(info, VISFX_VRAM_WRITE_DATA_INCRX, data); > + data = 0; > + } > + } > + > + if (x && pos != 3) { > + visfx_write_vram(info, VISFX_WRITE_MASK, (1 << ((pos+1) * 8))-1); > + visfx_write_vram(info, VISFX_VRAM_WRITE_DATA_INCRX, data); > + } > + } > + break; > + > + default: > + break; > + } > +} > + > +static void visfx_fillrect(struct fb_info *info, const struct fb_fillrect *fr) > +{ > + visfx_bmove_wait(info); > + visfx_writel(info, VISFX_WRITE_MASK, 0xffffffff); > + visfx_writel(info, VISFX_VRAM_WRITE_MODE, VISFX_VRAM_WRITE_MODE_FILL); > + visfx_set_bmove_color(info, fr->color, 0); > + visfx_writel(info, VISFX_START, (fr->dx << 16) | fr->dy); > + visfx_writel(info, VISFX_SIZE, (fr->width << 16) | fr->height); > +} > + > +static u32 visfx_cmap_entry(struct fb_cmap *cmap, int color) > +{ > + return (((cmap->blue[color] & 0xff)) | > + ((cmap->green[color] & 0xff) << 8) | > + (cmap->red[color] & 0xff) << 16); > +} > + > +static int visfx_setcmap(struct fb_cmap *cmap, struct fb_info *info) > +{ > + int i; > + > + visfx_writel(info, VISFX_COLOR_INDEX, cmap->start); > + > + for (i = 0; i < cmap->len; i++) > + visfx_writel(info, VISFX_COLOR_VALUE, visfx_cmap_entry(cmap, i)); > + > + visfx_writel(info, VISFX_COLOR_MASK, 0xff); > + visfx_writel(info, 0x80004c, 0xc); > + visfx_writel(info, 0x800000, 0); > + return 0; > +} > + > +static void visfx_get_video_mode(struct fb_info *info) > +{ > + struct fb_var_screeninfo *var = &info->var; > + unsigned long n, d; > + u32 tmp; > + > + tmp = visfx_readl(info, VISFX_SYNC_VISIBLE_SIZE); > + var->xres = (tmp & 0xffff) + 1; > + var->yres = (tmp >> 16) + 1; > + > + tmp = visfx_readl(info, VISFX_SYNC_MASTER_PLL); > + n = (tmp & 0xff) + 1; > + d = ((tmp >> 8) & 0xff) + 1; > + var->pixclock = (VISFX_SYNC_PLL_BASE / d) * n; > + > + tmp = visfx_readl(info, VISFX_SYNC_HORIZ_CONF); > + var->left_margin = ((tmp >> 20) & 0x1ff) + 1; > + var->hsync_len = (((tmp >> 12) & 0xff) + 1) * 4; > + var->right_margin = (tmp & 0x1ff) + 1; > + > + tmp = visfx_readl(info, VISFX_SYNC_VERT_CONF); > + var->upper_margin = ((tmp >> 16) & 0xff) + 1; > + var->vsync_len = ((tmp >> 8) & 0xff) + 1; > + var->lower_margin = (tmp & 0xff) + 1; > + > + tmp = visfx_readl(info, VISFX_SYNC_POLARITY); > + if (tmp & VISFX_HSYNC_POSITIVE) > + var->sync |= FB_SYNC_HOR_HIGH_ACT; > + if (tmp & VISFX_VSYNC_POSITIVE) > + var->sync |= FB_SYNC_VERT_HIGH_ACT; > + > + var->red.length = 8; > + var->green.length = 8; > + var->blue.length = 8; > + var->bits_per_pixel = 8; > + var->grayscale = 0; > + var->xres_virtual = var->xres; > + var->yres_virtual = var->yres; > + info->screen_size = 2048 * var->yres; > +} > + > +static void visfx_set_pll(struct fb_info *info, unsigned long clock) > +{ > + unsigned long n, d, tmp; > + > + rational_best_approximation(clock, VISFX_SYNC_PLL_BASE, 0x3f, 0x3f, &n, &d); > + tmp = (((d * 4) - 1) << 8) | ((n * 4) - 1); > + visfx_writel(info, VISFX_SYNC_MASTER_PLL, 0x520000 | tmp); > + while (visfx_readl(info, VISFX_SYNC_PLL_STATUS) & 0xffffff) > + udelay(10); > + visfx_writel(info, VISFX_SYNC_MASTER_PLL, 0x530000 | tmp); > + while (visfx_readl(info, VISFX_SYNC_PLL_STATUS) & 0xffffff) > + udelay(10); > +} > + > +static int visfx_set_par(struct fb_info *info) > +{ > + u32 xres, yres, hbp, hsw, hfp, vbp, vsw, vfp, tmp; > + struct fb_var_screeninfo *var = &info->var; > + > + > + xres = var->xres; > + yres = var->yres; > + hsw = var->hsync_len / 4 - 1; > + hfp = var->right_margin - 1; > + hbp = var->left_margin - 1; > + vsw = var->vsync_len - 1; > + vfp = var->lower_margin - 1; > + vbp = var->upper_margin - 1; > + > + visfx_set_pll(info, var->pixclock); > + visfx_writel(info, VISFX_SYNC_VISIBLE_SIZE, ((yres - 1) << 16) | (xres - 1)); > + visfx_writel(info, VISFX_SYNC_HORIZ_CONF, (hbp << 20) | (hsw << 12) | (0xc << 8) | hfp); > + visfx_writel(info, VISFX_SYNC_VERT_CONF, (vbp << 16) | (vsw << 8) | vfp); > + visfx_writel(info, VISFX_SCREEN_SIZE, (xres << 16) | yres); > + > + tmp = VISFX_DFP_ENABLE; > + if (var->sync & FB_SYNC_HOR_HIGH_ACT) > + tmp |= VISFX_HSYNC_POSITIVE; > + if (var->sync & FB_SYNC_VERT_HIGH_ACT) > + tmp |= VISFX_VSYNC_POSITIVE; > + visfx_writel(info, VISFX_SYNC_POLARITY, tmp); > + > + visfx_writel(info, VISFX_VRAM_WRITE_MODE, VISFX_VRAM_WRITE_MODE_BITMAP); > + > + visfx_get_video_mode(info); > + return 0; > +} > + > +static int visfx_check_var(struct fb_var_screeninfo *var, struct fb_info *info) > +{ > + if (var->pixclock > VISFX_SYNC_PLL_BASE || > + var->left_margin > 512 || > + var->right_margin > 512 || > + var->hsync_len > 512 || > + var->lower_margin > 256 || > + var->upper_margin > 256 || > + var->vsync_len > 256 || > + var->xres > 2048 || > + var->yres > 2048) > + return -EINVAL; > + return 0; > +} > + > +static void visfx_update_cursor_image_line(struct fb_info *info, > + struct fb_cursor *cursor, int y) > +{ > + unsigned int x, bytecnt; > + u32 data[2] = { 0 }; > + u8 d, m; > + > + bytecnt = cursor->image.width / 8; > + > + for (x = 0; x < bytecnt && x < 8; x++) { > + m = cursor->mask[y * bytecnt + x]; > + d = cursor->image.data[y * bytecnt + x]; > + > + if (cursor->rop == ROP_XOR) > + ((u8 *)data)[x] = d ^ m; > + else > + ((u8 *)data)[x] = d & m; > + } > + > + visfx_writel(info, VISFX_CURSOR_DATA, data[0]); > + visfx_writel(info, VISFX_CURSOR_DATA, data[1]); > +} > + > +static void visfx_update_cursor_image(struct fb_info *info, > + struct fb_cursor *cursor) > +{ > + int y, height = cursor->image.height; > + > + if (height > 128) > + height = 128; > + > + visfx_writel(info, VISFX_CURSOR_INDEX, 0); > + for (y = 0; y < height; y++) > + visfx_update_cursor_image_line(info, cursor, y); > + > + for (; y < 256; y++) > + visfx_writel(info, VISFX_CURSOR_DATA, 0); > +} > + > +static int visfx_cursor(struct fb_info *info, struct fb_cursor *cursor) > +{ > + u32 tmp; > + > + if (cursor->set & (FB_CUR_SETIMAGE|FB_CUR_SETSHAPE)) > + visfx_update_cursor_image(info, cursor); > + > + if (cursor->set & FB_CUR_SETCMAP) { > + tmp = visfx_cmap_entry(&info->cmap, cursor->image.fg_color); > + visfx_writel(info, VISFX_CURSOR_COLOR, tmp); > + } > + > + tmp = (cursor->image.dx << 16) | (cursor->image.dy & 0xffff); > + if (cursor->enable) > + tmp |= VISFX_CURSOR_ENABLE; > + visfx_writel(info, VISFX_CURSOR_POS, tmp); > + return 0; > +} > + > +static int visfx_open(struct fb_info *info, int user) > +{ > + struct visfx_par *par = info->par; > + > + if (user && par->open_count++ == 0) > + visfx_writel(info, VISFX_VRAM_WRITE_MODE, VISFX_VRAM_WRITE_MODE_BITMAP); > + > + return 0; > +} > + > +static int visfx_release(struct fb_info *info, int user) > +{ > + struct visfx_par *par = info->par; > + > + if (user) > + par->open_count--; > + > + return 0; > +} > + > +static const struct fb_ops visfx_ops = { > + .owner = THIS_MODULE, > + .fb_open = visfx_open, > + .fb_release = visfx_release, > + .fb_setcmap = visfx_setcmap, > + .fb_fillrect = visfx_fillrect, > + .fb_imageblit = visfx_imageblit, > + .fb_set_par = visfx_set_par, > + .fb_check_var = visfx_check_var, > + .fb_cursor = visfx_cursor, > +}; > + > +static struct fb_fix_screeninfo visfx_fix = { > + .type = FB_TYPE_PACKED_PIXELS, > + .visual = FB_VISUAL_PSEUDOCOLOR, > + .id = "Visualize FX", > +}; > + > +static int visfx_probe(struct pci_dev *pdev, > + const struct pci_device_id *ent) > +{ > + struct visfx_par *par; > + struct fb_info *info; > + int ret; > + > + info = framebuffer_alloc(sizeof(struct visfx_par), &pdev->dev); > + if (!info) > + return -ENOMEM; > + > + par = info->par; > + > + ret = pci_enable_device(pdev); > + if (ret) > + goto err_out_free; > + > + ret = pci_request_regions(pdev, KBUILD_MODNAME); > + if (ret) > + goto err_out_disable; > + > + par->reg_size = pci_resource_len(pdev, 0); > + par->reg_base = pci_ioremap_bar(pdev, 0); > + par->open_count = 0; > + > + if (!par->reg_base) { > + ret = -ENOMEM; > + goto err_out_release; > + } > + > + pci_set_drvdata(pdev, info); > + > + info->fbops = &visfx_ops; > + info->flags = FBINFO_DEFAULT | FBINFO_HWACCEL_FILLRECT | FBINFO_HWACCEL_IMAGEBLIT; > + info->fix = visfx_fix; > + info->pseudo_palette = par->pseudo_palette; > + info->fix.smem_start = pci_resource_start(pdev, 0) + VISFX_FB_OFFSET; > + info->fix.smem_len = VISFX_FB_LENGTH; > + info->screen_base = par->reg_base + VISFX_FB_OFFSET; > + info->fix.type = FB_TYPE_PACKED_PIXELS; > + info->fix.visual = FB_VISUAL_PSEUDOCOLOR; > + info->fix.line_length = 2048; > + info->fix.accel = FB_ACCEL_NONE; > + > + visfx_setup_unknown(info); > + visfx_get_video_mode(info); > + info->var.accel_flags = info->flags; > + > + ret = device_create_file(&pdev->dev, &dev_attr_reg); > + if (ret) > + goto err_out_iounmap; > + > + ret = fb_alloc_cmap(&info->cmap, NR_PALETTE, 0); > + if (ret) > + goto err_out_remove; > + > + ret = register_framebuffer(info); > + if (ret) > + goto err_out_dealloc_cmap; > + return 0; > + > +err_out_dealloc_cmap: > + fb_dealloc_cmap(&info->cmap); > +err_out_remove: > + device_remove_file(&pdev->dev, &dev_attr_reg); > +err_out_iounmap: > + pci_iounmap(pdev, par->reg_base); > +err_out_release: > + pci_release_regions(pdev); > +err_out_disable: > + pci_disable_device(pdev); > +err_out_free: > + framebuffer_release(info); > + return ret; > +} > + > +static void __exit visfx_remove(struct pci_dev *pdev) > +{ > + struct fb_info *info = pci_get_drvdata(pdev); > + struct visfx_par *par = info->par; > + > + device_remove_file(&pdev->dev, &dev_attr_reg); > + unregister_framebuffer(info); > + pci_iounmap(pdev, par->reg_base); > + framebuffer_release(info); > + pci_release_regions(pdev); > + pci_disable_device(pdev); > +} > + > +static const struct pci_device_id visfx_pci_tbl[] = { > + { PCI_DEVICE(PCI_VENDOR_ID_HP, 0x1008) }, > + { 0 }, > +}; > +MODULE_DEVICE_TABLE(pci, visfx_pci_tbl); > + > +static struct pci_driver visfx_driver = { > + .name = KBUILD_MODNAME, > + .id_table = visfx_pci_tbl, > + .probe = visfx_probe, > + .remove = visfx_remove, > +}; > + > +static int __init visfx_init(void) > +{ > + return pci_register_driver(&visfx_driver); > +} > +module_init(visfx_init); > + > +static void __exit visfx_exit(void) > +{ > + pci_unregister_driver(&visfx_driver); > +} > +module_exit(visfx_exit); > + > +MODULE_AUTHOR("Sven Schnelle <svens@xxxxxxxxxxxxxx>"); > +MODULE_DESCRIPTION("Framebuffer driver for HP Visualize FX cards"); > +MODULE_LICENSE("GPL"); >