diff options
Diffstat (limited to 'drivers/video/fbdev/hpfb.c')
| -rw-r--r-- | drivers/video/fbdev/hpfb.c | 429 | 
1 files changed, 429 insertions, 0 deletions
diff --git a/drivers/video/fbdev/hpfb.c b/drivers/video/fbdev/hpfb.c new file mode 100644 index 00000000000..a1b7e5fa9b0 --- /dev/null +++ b/drivers/video/fbdev/hpfb.c @@ -0,0 +1,429 @@ +/* + *	HP300 Topcat framebuffer support (derived from macfb of all things) + *	Phil Blundell <philb@gnu.org> 1998 + *	DIO-II, colour map and Catseye support by + *	Kars de Jong <jongk@linux-m68k.org>, May 2004. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/string.h> +#include <linux/mm.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/fb.h> +#include <linux/dio.h> + +#include <asm/io.h> +#include <asm/uaccess.h> + +static struct fb_info fb_info = { +	.fix = { +		.id		= "HP300 ", +		.type		= FB_TYPE_PACKED_PIXELS, +		.visual		= FB_VISUAL_PSEUDOCOLOR, +		.accel		= FB_ACCEL_NONE, +	} +}; + +static unsigned long fb_regs; +static unsigned char fb_bitmask; + +#define TC_NBLANK	0x4080 +#define TC_WEN		0x4088 +#define TC_REN		0x408c +#define TC_FBEN		0x4090 +#define TC_PRR		0x40ea + +/* These defines match the X window system */ +#define RR_CLEAR	0x0 +#define RR_COPY		0x3 +#define RR_NOOP		0x5 +#define RR_XOR		0x6 +#define RR_INVERT	0xa +#define RR_COPYINVERTED 0xc +#define RR_SET		0xf + +/* blitter regs */ +#define BUSY		0x4044 +#define WMRR		0x40ef +#define SOURCE_X	0x40f2 +#define SOURCE_Y	0x40f6 +#define DEST_X		0x40fa +#define DEST_Y		0x40fe +#define WHEIGHT		0x4106 +#define WWIDTH		0x4102 +#define WMOVE		0x409c + +static struct fb_var_screeninfo hpfb_defined = { +	.red		= { +		.length = 8, +	}, +	.green		= { +		.length = 8, +	}, +	.blue		= { +		.length = 8, +	}, +	.activate	= FB_ACTIVATE_NOW, +	.height		= -1, +	.width		= -1, +	.vmode		= FB_VMODE_NONINTERLACED, +}; + +static int hpfb_setcolreg(unsigned regno, unsigned red, unsigned green, +			  unsigned blue, unsigned transp, +			  struct fb_info *info) +{ +	/* use MSBs */ +	unsigned char _red  =red>>8; +	unsigned char _green=green>>8; +	unsigned char _blue =blue>>8; +	unsigned char _regno=regno; + +	/* +	 *  Set a single color register. The values supplied are +	 *  already rounded down to the hardware's capabilities +	 *  (according to the entries in the `var' structure). Return +	 *  != 0 for invalid regno. +	 */ + +	if (regno >= info->cmap.len) +		return 1; +	 +	while (in_be16(fb_regs + 0x6002) & 0x4) udelay(1); + +	out_be16(fb_regs + 0x60ba, 0xff); + +	out_be16(fb_regs + 0x60b2, _red); +	out_be16(fb_regs + 0x60b4, _green); +	out_be16(fb_regs + 0x60b6, _blue); +	out_be16(fb_regs + 0x60b8, ~_regno); +	out_be16(fb_regs + 0x60f0, 0xff); + +	udelay(100); + +	while (in_be16(fb_regs + 0x6002) & 0x4) udelay(1); +	out_be16(fb_regs + 0x60b2, 0); +	out_be16(fb_regs + 0x60b4, 0); +	out_be16(fb_regs + 0x60b6, 0); +	out_be16(fb_regs + 0x60b8, 0); + +	return 0; +} + +/* 0 unblank, 1 blank, 2 no vsync, 3 no hsync, 4 off */ + +static int hpfb_blank(int blank, struct fb_info *info) +{ +	out_8(fb_regs + TC_NBLANK, (blank ? 0x00 : fb_bitmask)); + +	return 0; +} + +static void topcat_blit(int x0, int y0, int x1, int y1, int w, int h, int rr) +{ +	if (rr >= 0) { +		while (in_8(fb_regs + BUSY) & fb_bitmask) +			; +	} +	out_8(fb_regs + TC_FBEN, fb_bitmask); +	if (rr >= 0) { +		out_8(fb_regs + TC_WEN, fb_bitmask); +		out_8(fb_regs + WMRR, rr); +	} +	out_be16(fb_regs + SOURCE_X, x0); +	out_be16(fb_regs + SOURCE_Y, y0); +	out_be16(fb_regs + DEST_X, x1); +	out_be16(fb_regs + DEST_Y, y1); +	out_be16(fb_regs + WWIDTH, w); +	out_be16(fb_regs + WHEIGHT, h); +	out_8(fb_regs + WMOVE, fb_bitmask); +} + +static void hpfb_copyarea(struct fb_info *info, const struct fb_copyarea *area)  +{ +	topcat_blit(area->sx, area->sy, area->dx, area->dy, area->width, area->height, RR_COPY); +} + +static void hpfb_fillrect(struct fb_info *p, const struct fb_fillrect *region) +{ +	u8 clr; + +	clr = region->color & 0xff; + +	while (in_8(fb_regs + BUSY) & fb_bitmask) +		; + +	/* Foreground */ +	out_8(fb_regs + TC_WEN, fb_bitmask & clr); +	out_8(fb_regs + WMRR, (region->rop == ROP_COPY ? RR_SET : RR_INVERT)); + +	/* Background */ +	out_8(fb_regs + TC_WEN, fb_bitmask & ~clr); +	out_8(fb_regs + WMRR, (region->rop == ROP_COPY ? RR_CLEAR : RR_NOOP)); + +	topcat_blit(region->dx, region->dy, region->dx, region->dy, region->width, region->height, -1); +} + +static int hpfb_sync(struct fb_info *info) +{ +	/* +	 * Since we also access the framebuffer directly, we have to wait +	 * until the block mover is finished +	 */ +	while (in_8(fb_regs + BUSY) & fb_bitmask) +		; + +	out_8(fb_regs + TC_WEN, fb_bitmask); +	out_8(fb_regs + TC_PRR, RR_COPY); +	out_8(fb_regs + TC_FBEN, fb_bitmask); + +	return 0; +} + +static struct fb_ops hpfb_ops = { +	.owner		= THIS_MODULE, +	.fb_setcolreg	= hpfb_setcolreg, +	.fb_blank	= hpfb_blank, +	.fb_fillrect	= hpfb_fillrect, +	.fb_copyarea	= hpfb_copyarea, +	.fb_imageblit	= cfb_imageblit, +	.fb_sync	= hpfb_sync, +}; + +/* Common to all HP framebuffers */ +#define HPFB_FBWMSB	0x05	/* Frame buffer width 		*/ +#define HPFB_FBWLSB	0x07 +#define HPFB_FBHMSB	0x09	/* Frame buffer height		*/ +#define HPFB_FBHLSB	0x0b +#define HPFB_DWMSB	0x0d	/* Display width		*/ +#define HPFB_DWLSB	0x0f +#define HPFB_DHMSB	0x11	/* Display height		*/ +#define HPFB_DHLSB	0x13 +#define HPFB_NUMPLANES	0x5b	/* Number of colour planes	*/ +#define HPFB_FBOMSB	0x5d	/* Frame buffer offset		*/ +#define HPFB_FBOLSB	0x5f + +static int hpfb_init_one(unsigned long phys_base, unsigned long virt_base) +{ +	unsigned long fboff, fb_width, fb_height, fb_start; +	int ret; + +	fb_regs = virt_base; +	fboff = (in_8(fb_regs + HPFB_FBOMSB) << 8) | in_8(fb_regs + HPFB_FBOLSB); + +	fb_info.fix.smem_start = (in_8(fb_regs + fboff) << 16); + +	if (phys_base >= DIOII_BASE) { +		fb_info.fix.smem_start += phys_base; +	} + +	if (DIO_SECID(fb_regs) != DIO_ID2_TOPCAT) { +		/* This is the magic incantation the HP X server uses to make Catseye boards work. */ +		while (in_be16(fb_regs+0x4800) & 1) +			; +		out_be16(fb_regs+0x4800, 0);	/* Catseye status */ +		out_be16(fb_regs+0x4510, 0);	/* VB */ +		out_be16(fb_regs+0x4512, 0);	/* TCNTRL */ +		out_be16(fb_regs+0x4514, 0);	/* ACNTRL */ +		out_be16(fb_regs+0x4516, 0);	/* PNCNTRL */ +		out_be16(fb_regs+0x4206, 0x90);	/* RUG Command/Status */ +		out_be16(fb_regs+0x60a2, 0);	/* Overlay Mask */ +		out_be16(fb_regs+0x60bc, 0);	/* Ram Select */ +	} + +	/* +	 *	Fill in the available video resolution +	 */ +	fb_width = (in_8(fb_regs + HPFB_FBWMSB) << 8) | in_8(fb_regs + HPFB_FBWLSB); +	fb_info.fix.line_length = fb_width; +	fb_height = (in_8(fb_regs + HPFB_FBHMSB) << 8) | in_8(fb_regs + HPFB_FBHLSB); +	fb_info.fix.smem_len = fb_width * fb_height; +	fb_start = (unsigned long)ioremap_writethrough(fb_info.fix.smem_start, +						       fb_info.fix.smem_len); +	hpfb_defined.xres = (in_8(fb_regs + HPFB_DWMSB) << 8) | in_8(fb_regs + HPFB_DWLSB); +	hpfb_defined.yres = (in_8(fb_regs + HPFB_DHMSB) << 8) | in_8(fb_regs + HPFB_DHLSB); +	hpfb_defined.xres_virtual = hpfb_defined.xres; +	hpfb_defined.yres_virtual = hpfb_defined.yres; +	hpfb_defined.bits_per_pixel = in_8(fb_regs + HPFB_NUMPLANES); + +	printk(KERN_INFO "hpfb: framebuffer at 0x%lx, mapped to 0x%lx, size %dk\n", +	       fb_info.fix.smem_start, fb_start, fb_info.fix.smem_len/1024); +	printk(KERN_INFO "hpfb: mode is %dx%dx%d, linelength=%d\n", +	       hpfb_defined.xres, hpfb_defined.yres, hpfb_defined.bits_per_pixel, fb_info.fix.line_length); + +	/* +	 *	Give the hardware a bit of a prod and work out how many bits per +	 *	pixel are supported. +	 */ +	out_8(fb_regs + TC_WEN, 0xff); +	out_8(fb_regs + TC_PRR, RR_COPY); +	out_8(fb_regs + TC_FBEN, 0xff); +	out_8(fb_start, 0xff); +	fb_bitmask = in_8(fb_start); +	out_8(fb_start, 0); + +	/* +	 *	Enable reading/writing of all the planes. +	 */ +	out_8(fb_regs + TC_WEN, fb_bitmask); +	out_8(fb_regs + TC_PRR, RR_COPY); +	out_8(fb_regs + TC_REN, fb_bitmask); +	out_8(fb_regs + TC_FBEN, fb_bitmask); + +	/* +	 *	Clear the screen. +	 */ +	topcat_blit(0, 0, 0, 0, fb_width, fb_height, RR_CLEAR); + +	/* +	 *	Let there be consoles.. +	 */ +	if (DIO_SECID(fb_regs) == DIO_ID2_TOPCAT) +		strcat(fb_info.fix.id, "Topcat"); +	else +		strcat(fb_info.fix.id, "Catseye"); +	fb_info.fbops = &hpfb_ops; +	fb_info.flags = FBINFO_DEFAULT; +	fb_info.var   = hpfb_defined; +	fb_info.screen_base = (char *)fb_start; + +	ret = fb_alloc_cmap(&fb_info.cmap, 1 << hpfb_defined.bits_per_pixel, 0); +	if (ret < 0) +		goto unmap_screen_base; + +	ret = register_framebuffer(&fb_info); +	if (ret < 0) +		goto dealloc_cmap; + +	fb_info(&fb_info, "%s frame buffer device\n", fb_info.fix.id); + +	return 0; + +dealloc_cmap: +	fb_dealloc_cmap(&fb_info.cmap); + +unmap_screen_base: +	if (fb_info.screen_base) { +		iounmap(fb_info.screen_base); +		fb_info.screen_base = NULL; +	} + +	return ret; +} + +/*  + * Check that the secondary ID indicates that we have some hope of working with this + * framebuffer.  The catseye boards are pretty much like topcats and we can muddle through. + */ + +#define topcat_sid_ok(x)  (((x) == DIO_ID2_LRCATSEYE) || ((x) == DIO_ID2_HRCCATSEYE)    \ +			   || ((x) == DIO_ID2_HRMCATSEYE) || ((x) == DIO_ID2_TOPCAT)) + +/*  + * Initialise the framebuffer + */ +static int hpfb_dio_probe(struct dio_dev *d, const struct dio_device_id *ent) +{ +	unsigned long paddr, vaddr; + +	paddr = d->resource.start; +	if (!request_mem_region(d->resource.start, resource_size(&d->resource), d->name)) +                return -EBUSY; + +	if (d->scode >= DIOII_SCBASE) { +		vaddr = (unsigned long)ioremap(paddr, resource_size(&d->resource)); +	} else { +		vaddr = paddr + DIO_VIRADDRBASE; +	} +	printk(KERN_INFO "Topcat found at DIO select code %d " +	       "(secondary id %02x)\n", d->scode, (d->id >> 8) & 0xff); +	if (hpfb_init_one(paddr, vaddr)) { +		if (d->scode >= DIOII_SCBASE) +			iounmap((void *)vaddr); +		return -ENOMEM; +	} +	return 0; +} + +static void hpfb_remove_one(struct dio_dev *d) +{ +	unregister_framebuffer(&fb_info); +	if (d->scode >= DIOII_SCBASE) +		iounmap((void *)fb_regs); +	release_mem_region(d->resource.start, resource_size(&d->resource)); +	fb_dealloc_cmap(&fb_info.cmap); +	if (fb_info.screen_base) +		iounmap(fb_info.screen_base); +} + +static struct dio_device_id hpfb_dio_tbl[] = { +    { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_LRCATSEYE) }, +    { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_HRCCATSEYE) }, +    { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_HRMCATSEYE) }, +    { DIO_ENCODE_ID(DIO_ID_FBUFFER, DIO_ID2_TOPCAT) }, +    { 0 } +}; + +static struct dio_driver hpfb_driver = { +    .name      = "hpfb", +    .id_table  = hpfb_dio_tbl, +    .probe     = hpfb_dio_probe, +    .remove    = hpfb_remove_one, +}; + +int __init hpfb_init(void) +{ +	unsigned int sid; +	mm_segment_t fs; +	unsigned char i; +	int err; + +	/* Topcats can be on the internal IO bus or real DIO devices. +	 * The internal variant sits at 0x560000; it has primary +	 * and secondary ID registers just like the DIO version. +	 * So we merge the two detection routines. +	 * +	 * Perhaps this #define should be in a global header file: +	 * I believe it's common to all internal fbs, not just topcat. +	 */ +#define INTFBVADDR 0xf0560000 +#define INTFBPADDR 0x560000 + +	if (!MACH_IS_HP300) +		return -ENODEV; + +	if (fb_get_options("hpfb", NULL)) +		return -ENODEV; + +	err = dio_register_driver(&hpfb_driver); +	if (err) +		return err; + +	fs = get_fs(); +	set_fs(KERNEL_DS); +	err = get_user(i, (unsigned char *)INTFBVADDR + DIO_IDOFF); +	set_fs(fs); + +	if (!err && (i == DIO_ID_FBUFFER) && topcat_sid_ok(sid = DIO_SECID(INTFBVADDR))) { +		if (!request_mem_region(INTFBPADDR, DIO_DEVSIZE, "Internal Topcat")) +			return -EBUSY; +		printk(KERN_INFO "Internal Topcat found (secondary id %02x)\n", sid); +		if (hpfb_init_one(INTFBPADDR, INTFBVADDR)) { +			return -ENOMEM; +		} +	} +	return 0; +} + +void __exit hpfb_cleanup_module(void) +{ +	dio_unregister_driver(&hpfb_driver); +} + +module_init(hpfb_init); +module_exit(hpfb_cleanup_module); + +MODULE_LICENSE("GPL");  | 
