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