diff mbox series

video: fbdev: add HP Visualize FX driver

Message ID 20211031204952.25678-2-svens@stackframe.org (mailing list archive)
State New, archived
Headers show
Series video: fbdev: add HP Visualize FX driver | expand

Commit Message

Sven Schnelle Oct. 31, 2021, 8:49 p.m. UTC
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@stackframe.org>
---
 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

Comments

Helge Deller Oct. 31, 2021, 9:55 p.m. UTC | #1
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@stackframe.org>

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@gmx.de>

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@stackframe.org>
> + */
> +
> +#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@stackframe.org>");
> +MODULE_DESCRIPTION("Framebuffer driver for HP Visualize FX cards");
> +MODULE_LICENSE("GPL");
>
Rolf Eike Beer Nov. 1, 2021, 4:10 p.m. UTC | #2
Am Sonntag, 31. Oktober 2021, 21:49:52 CET schrieb Sven Schnelle:
> 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.

Besides the DRM things here are a few more things I spotted.

> +struct visfx_par {

What is "par"? Maybe a little more descriptive name would help, _param or 
something?

> +	u32 pseudo_palette[256];
> +	unsigned long debug_reg;
> +	void __iomem *reg_base;
> +	unsigned long reg_size;
> +	int open_count;
> +};

I would move the more often used members to the top, that should make accesses 
of the often used members slightly more efficient as there is no offset 
needed.

> +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';

No -EINVAL without '='?

> +	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))

Spaces around +

> +			return -EINVAL;
> +		visfx_writel(info, par->debug_reg, data);
> +	}
> +	return count;
> +}
> +
> +static DEVICE_ATTR(reg, 0600, visfx_sysfs_show_reg, visfx_sysfs_store_reg);

Sysfs API is public API, so it needs to be documented and such. Also I bet 
there are helpers to do this better. But I think this should be debugfs 
attributes, not sysfs ones.

> +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;

Having variables named width and _width is just asking for future trouble 
IMHO. Maybe just call the local one "w" or something.

> +	u32 tmp;

You can move that into a more local scope to make it more obvious it doesn't 
carry and state between loop iterations.

> +	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);

A little comment about these numbers would be good, even if it is just 
"recorded from HP-UX driver, no idea what it does".

> +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;

Something went wrong with the indentation here.

> +static int visfx_release(struct fb_info *info, int user)
> +{
> +	struct visfx_par *par = info->par;
> +
> +	if (user)
> +		par->open_count--;

Check for underflow, just out of paranoia?

> +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);

sizeof(*par)

> +	if (!info)
> +		return -ENOMEM;
> +
> +	par = info->par;
> +
> +	ret = pci_enable_device(pdev);

If you don't have a really good reason to do so you should used 
pcim_enable_device(), which will do half of the following error cleanups and 
the remove handling for you.

> +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_pci_driver(&visfx_driver);

Greetings,

Eike
diff mbox series

Patch

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
+	  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@stackframe.org>
+ */
+
+#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@stackframe.org>");
+MODULE_DESCRIPTION("Framebuffer driver for HP Visualize FX cards");
+MODULE_LICENSE("GPL");