pm3fb.c 26.8 KB
Newer Older
L
Linus Torvalds 已提交
1 2
/*
 *  linux/drivers/video/pm3fb.c -- 3DLabs Permedia3 frame buffer device
3 4 5 6 7 8
 *
 *  Copyright (C) 2001 Romain Dolbeau <romain@dolbeau.org>.
 *
 *  Ported to 2.6 kernel on 1 May 2007 by Krzysztof Helt <krzysztof.h1@wp.pl>
 *	based on pm2fb.c
 *
L
Linus Torvalds 已提交
9
 *  Based on code written by:
10 11 12
 *	   Sven Luther, <luther@dpt-info.u-strasbg.fr>
 *	   Alan Hourihane, <alanh@fairlite.demon.co.uk>
 *	   Russell King, <rmk@arm.linux.org.uk>
L
Linus Torvalds 已提交
13 14 15
 *  Based on linux/drivers/video/skeletonfb.c:
 *	Copyright (C) 1997 Geert Uytterhoeven
 *  Based on linux/driver/video/pm2fb.c:
16 17
 *	Copyright (C) 1998-1999 Ilario Nardinocchi (nardinoc@CS.UniBO.IT)
 *	Copyright (C) 1999 Jakub Jelinek (jakub@redhat.com)
L
Linus Torvalds 已提交
18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
 *
 *  This file is subject to the terms and conditions of the GNU General Public
 *  License. See the file COPYING in the main directory of this archive for
 *  more details.
 *
 */

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/fb.h>
#include <linux/init.h>
#include <linux/pci.h>

36
#include <video/pm3fb.h>
L
Linus Torvalds 已提交
37

38 39
#if !defined(CONFIG_PCI)
#error "Only generic PCI cards supported."
L
Linus Torvalds 已提交
40 41
#endif

42 43 44 45 46
#undef PM3FB_MASTER_DEBUG
#ifdef PM3FB_MASTER_DEBUG
#define DPRINTK(a,b...)	printk(KERN_DEBUG "pm3fb: %s: " a, __FUNCTION__ , ## b)
#else
#define DPRINTK(a,b...)
L
Linus Torvalds 已提交
47 48
#endif

49 50 51 52
/*
 * Driver data
 */
static char *mode_option __devinitdata;
L
Linus Torvalds 已提交
53

54 55 56 57
/*
 *  If your driver supports multiple boards, you should make the
 *  below data types arrays, or allocate them dynamically (using kmalloc()).
 */
L
Linus Torvalds 已提交
58

59 60 61 62 63 64 65 66 67 68 69 70
/*
 * This structure defines the hardware state of the graphics card. Normally
 * you place this in a header file in linux/include/video. This file usually
 * also includes register information. That allows other driver subsystems
 * and userland applications the ability to use the same header file to
 * avoid duplicate work and easy porting of software.
 */
struct pm3_par {
	unsigned char	__iomem *v_regs;/* virtual address of p_regs */
	u32		video;		/* video flags before blanking */
	u32		base;		/* screen base (xoffset+yoffset) in 128 bits unit */
	u32 		palette[16];
L
Linus Torvalds 已提交
71 72
};

73 74 75 76 77 78 79 80 81 82 83 84 85
/*
 * Here we define the default structs fb_fix_screeninfo and fb_var_screeninfo
 * if we don't use modedb. If we do use modedb see pm3fb_init how to use it
 * to get a fb_var_screeninfo. Otherwise define a default var as well.
 */
static struct fb_fix_screeninfo pm3fb_fix __devinitdata = {
	.id =		"Permedia3",
	.type =		FB_TYPE_PACKED_PIXELS,
	.visual =	FB_VISUAL_PSEUDOCOLOR,
	.xpanstep =	1,
	.ypanstep =	1,
	.ywrapstep =	0,
	.accel =	FB_ACCEL_NONE,
L
Linus Torvalds 已提交
86 87
};

88 89 90
/*
 * Utility functions
 */
L
Linus Torvalds 已提交
91

92 93 94 95
static inline u32 PM3_READ_REG(struct pm3_par *par, s32 off)
{
	return fb_readl(par->v_regs + off);
}
L
Linus Torvalds 已提交
96

97 98 99 100
static inline void PM3_WRITE_REG(struct pm3_par *par, s32 off, u32 v)
{
	fb_writel(v, par->v_regs + off);
}
L
Linus Torvalds 已提交
101

102 103 104
static inline void PM3_WAIT(struct pm3_par *par, u32 n)
{
	while (PM3_READ_REG(par, PM3InFIFOSpace) < n);
L
Linus Torvalds 已提交
105 106
}

107
static inline void PM3_SLOW_WRITE_REG(struct pm3_par *par, s32 off, u32 v)
L
Linus Torvalds 已提交
108
{
109 110 111 112 113
	if (par->v_regs) {
		mb();
		PM3_WAIT(par, 1);
		wmb();
		PM3_WRITE_REG(par, off, v);
L
Linus Torvalds 已提交
114 115 116
	}
}

117
static inline void PM3_SET_INDEX(struct pm3_par *par, unsigned index)
L
Linus Torvalds 已提交
118
{
119 120
	PM3_SLOW_WRITE_REG(par, PM3RD_IndexHigh, (index >> 8) & 0xff);
	PM3_SLOW_WRITE_REG(par, PM3RD_IndexLow, index & 0xff);
L
Linus Torvalds 已提交
121 122
}

123
static inline void PM3_WRITE_DAC_REG(struct pm3_par *par, unsigned r, u8 v)
L
Linus Torvalds 已提交
124
{
125 126 127
	PM3_SET_INDEX(par, r);
	wmb();
	PM3_WRITE_REG(par, PM3RD_IndexedData, v);
L
Linus Torvalds 已提交
128 129
}

130 131
static inline void pm3fb_set_color(struct pm3_par *par, unsigned char regno,
			unsigned char r, unsigned char g, unsigned char b)
L
Linus Torvalds 已提交
132
{
133 134 135 136 137 138 139 140 141 142 143 144 145 146
	PM3_SLOW_WRITE_REG(par, PM3RD_PaletteWriteAddress, regno);
	PM3_SLOW_WRITE_REG(par, PM3RD_PaletteData, r);
	PM3_SLOW_WRITE_REG(par, PM3RD_PaletteData, g);
	PM3_SLOW_WRITE_REG(par, PM3RD_PaletteData, b);
}

static void pm3fb_clear_colormap(struct pm3_par *par,
			unsigned char r, unsigned char g, unsigned char b)
{
	int i;

	for (i = 0; i < 256 ; i++) /* fill color map with white */
		pm3fb_set_color(par, i, r, g, b);

L
Linus Torvalds 已提交
147 148 149
}

/* Calculating various clock parameter */
150 151 152 153
static void pm3fb_calculate_clock(unsigned long reqclock,
				unsigned char *prescale,
				unsigned char *feedback,
				unsigned char *postscale)
L
Linus Torvalds 已提交
154 155 156 157
{
	int f, pre, post;
	unsigned long freq;
	long freqerr = 1000;
158
	long currerr;
L
Linus Torvalds 已提交
159 160 161 162

	for (f = 1; f < 256; f++) {
		for (pre = 1; pre < 256; pre++) {
			for (post = 0; post < 5; post++) {
163 164 165 166 167 168
				freq = ((2*PM3_REF_CLOCK * f) >> post) / pre;
				currerr = (reqclock > freq)
					? reqclock - freq
					: freq - reqclock;
				if (currerr < freqerr) {
					freqerr = currerr;
L
Linus Torvalds 已提交
169 170 171 172 173 174 175 176 177
					*feedback = f;
					*prescale = pre;
					*postscale = post;
				}
			}
		}
	}
}

178
static inline int pm3fb_shift_bpp(unsigned long depth, int v)
L
Linus Torvalds 已提交
179 180 181 182 183 184 185 186 187 188 189
{
	switch (depth) {
	case 8:
		return (v >> 4);
	case 12:
	case 15:
	case 16:
		return (v >> 3);
	case 32:
		return (v >> 2);
	}
190 191
	DPRINTK("Unsupported depth %ld\n", depth);
	return 0;
L
Linus Torvalds 已提交
192 193 194
}

/* write the mode to registers */
195
static void pm3fb_write_mode(struct fb_info *info)
L
Linus Torvalds 已提交
196
{
197
	struct pm3_par *par = info->par;
L
Linus Torvalds 已提交
198
	char tempsync = 0x00, tempmisc = 0x00;
199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
	const u32 hsstart = info->var.right_margin;
	const u32 hsend = hsstart + info->var.hsync_len;
	const u32 hbend = hsend + info->var.left_margin;
	const u32 xres = (info->var.xres + 31) & ~31;
	const u32 htotal = xres + hbend;
	const u32 vsstart = info->var.lower_margin;
	const u32 vsend = vsstart + info->var.vsync_len;
	const u32 vbend = vsend + info->var.upper_margin;
	const u32 vtotal = info->var.yres + vbend;
	const u32 width = (info->var.xres_virtual + 7) & ~7;

	PM3_SLOW_WRITE_REG(par, PM3MemBypassWriteMask, 0xffffffff);
	PM3_SLOW_WRITE_REG(par, PM3Aperture0, 0x00000000);
	PM3_SLOW_WRITE_REG(par, PM3Aperture1, 0x00000000);
	PM3_SLOW_WRITE_REG(par, PM3FIFODis, 0x00000007);

	PM3_SLOW_WRITE_REG(par, PM3HTotal,
			   pm3fb_shift_bpp(info->var.bits_per_pixel,
					  htotal - 1));
	PM3_SLOW_WRITE_REG(par, PM3HsEnd,
			   pm3fb_shift_bpp(info->var.bits_per_pixel,
					  hsend));
	PM3_SLOW_WRITE_REG(par, PM3HsStart,
			   pm3fb_shift_bpp(info->var.bits_per_pixel,
L
Linus Torvalds 已提交
223
					  hsstart));
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
	PM3_SLOW_WRITE_REG(par, PM3HbEnd,
			   pm3fb_shift_bpp(info->var.bits_per_pixel,
					  hbend));
	PM3_SLOW_WRITE_REG(par, PM3HgEnd,
			   pm3fb_shift_bpp(info->var.bits_per_pixel,
					  hbend));
	PM3_SLOW_WRITE_REG(par, PM3ScreenStride,
			   pm3fb_shift_bpp(info->var.bits_per_pixel,
					  width));
	PM3_SLOW_WRITE_REG(par, PM3VTotal, vtotal - 1);
	PM3_SLOW_WRITE_REG(par, PM3VsEnd, vsend - 1);
	PM3_SLOW_WRITE_REG(par, PM3VsStart, vsstart - 1);
	PM3_SLOW_WRITE_REG(par, PM3VbEnd, vbend);

	switch (info->var.bits_per_pixel) {
L
Linus Torvalds 已提交
239
	case 8:
240
		PM3_SLOW_WRITE_REG(par, PM3ByAperture1Mode,
L
Linus Torvalds 已提交
241
				   PM3ByApertureMode_PIXELSIZE_8BIT);
242
		PM3_SLOW_WRITE_REG(par, PM3ByAperture2Mode,
L
Linus Torvalds 已提交
243 244 245 246 247 248 249
				   PM3ByApertureMode_PIXELSIZE_8BIT);
		break;

	case 12:
	case 15:
	case 16:
#ifndef __BIG_ENDIAN
250
		PM3_SLOW_WRITE_REG(par, PM3ByAperture1Mode,
L
Linus Torvalds 已提交
251
				   PM3ByApertureMode_PIXELSIZE_16BIT);
252
		PM3_SLOW_WRITE_REG(par, PM3ByAperture2Mode,
L
Linus Torvalds 已提交
253 254
				   PM3ByApertureMode_PIXELSIZE_16BIT);
#else
255
		PM3_SLOW_WRITE_REG(par, PM3ByAperture1Mode,
L
Linus Torvalds 已提交
256 257
				   PM3ByApertureMode_PIXELSIZE_16BIT |
				   PM3ByApertureMode_BYTESWAP_BADC);
258
		PM3_SLOW_WRITE_REG(par, PM3ByAperture2Mode,
L
Linus Torvalds 已提交
259 260 261 262 263 264 265
				   PM3ByApertureMode_PIXELSIZE_16BIT |
				   PM3ByApertureMode_BYTESWAP_BADC);
#endif /* ! __BIG_ENDIAN */
		break;

	case 32:
#ifndef __BIG_ENDIAN
266
		PM3_SLOW_WRITE_REG(par, PM3ByAperture1Mode,
L
Linus Torvalds 已提交
267
				   PM3ByApertureMode_PIXELSIZE_32BIT);
268
		PM3_SLOW_WRITE_REG(par, PM3ByAperture2Mode,
L
Linus Torvalds 已提交
269 270
				   PM3ByApertureMode_PIXELSIZE_32BIT);
#else
271
		PM3_SLOW_WRITE_REG(par, PM3ByAperture1Mode,
L
Linus Torvalds 已提交
272 273
				   PM3ByApertureMode_PIXELSIZE_32BIT |
				   PM3ByApertureMode_BYTESWAP_DCBA);
274
		PM3_SLOW_WRITE_REG(par, PM3ByAperture2Mode,
L
Linus Torvalds 已提交
275 276 277 278 279 280
				   PM3ByApertureMode_PIXELSIZE_32BIT |
				   PM3ByApertureMode_BYTESWAP_DCBA);
#endif /* ! __BIG_ENDIAN */
		break;

	default:
281 282
		DPRINTK("Unsupported depth %d\n",
			info->var.bits_per_pixel);
L
Linus Torvalds 已提交
283 284 285 286 287 288 289 290 291 292
		break;
	}

	/*
	 * Oxygen VX1 - it appears that setting PM3VideoControl and
	 * then PM3RD_SyncControl to the same SYNC settings undoes
	 * any net change - they seem to xor together.  Only set the
	 * sync options in PM3RD_SyncControl.  --rmk
	 */
	{
293
		unsigned int video = par->video;
L
Linus Torvalds 已提交
294 295 296 297 298

		video &= ~(PM3VideoControl_HSYNC_MASK |
			   PM3VideoControl_VSYNC_MASK);
		video |= PM3VideoControl_HSYNC_ACTIVE_HIGH |
			 PM3VideoControl_VSYNC_ACTIVE_HIGH;
299
		PM3_SLOW_WRITE_REG(par, PM3VideoControl, video);
L
Linus Torvalds 已提交
300
	}
301 302 303 304 305
	PM3_SLOW_WRITE_REG(par, PM3VClkCtl,
			   (PM3_READ_REG(par, PM3VClkCtl) & 0xFFFFFFFC));
	PM3_SLOW_WRITE_REG(par, PM3ScreenBase, par->base);
	PM3_SLOW_WRITE_REG(par, PM3ChipConfig,
			   (PM3_READ_REG(par, PM3ChipConfig) & 0xFFFFFFFD));
L
Linus Torvalds 已提交
306 307

	{
308 309 310 311 312 313 314 315 316 317 318 319 320
		unsigned char uninitialized_var(m);	/* ClkPreScale */
		unsigned char uninitialized_var(n);	/* ClkFeedBackScale */
		unsigned char uninitialized_var(p);	/* ClkPostScale */
		unsigned long pixclock = PICOS2KHZ(info->var.pixclock);

		(void)pm3fb_calculate_clock(pixclock, &m, &n, &p);

		DPRINTK("Pixclock: %ld, Pre: %d, Feedback: %d, Post: %d\n",
			pixclock, (int) m, (int) n, (int) p);

		PM3_WRITE_DAC_REG(par, PM3RD_DClk0PreScale, m);
		PM3_WRITE_DAC_REG(par, PM3RD_DClk0FeedbackScale, n);
		PM3_WRITE_DAC_REG(par, PM3RD_DClk0PostScale, p);
L
Linus Torvalds 已提交
321 322
	}
	/*
323
	   PM3_WRITE_DAC_REG(par, PM3RD_IndexControl, 0x00);
L
Linus Torvalds 已提交
324 325
	 */
	/*
326
	   PM3_SLOW_WRITE_REG(par, PM3RD_IndexControl, 0x00);
L
Linus Torvalds 已提交
327
	 */
328
	if ((par->video & PM3VideoControl_HSYNC_MASK) ==
L
Linus Torvalds 已提交
329 330
	    PM3VideoControl_HSYNC_ACTIVE_HIGH)
		tempsync |= PM3RD_SyncControl_HSYNC_ACTIVE_HIGH;
331
	if ((par->video & PM3VideoControl_VSYNC_MASK) ==
L
Linus Torvalds 已提交
332 333 334
	    PM3VideoControl_VSYNC_ACTIVE_HIGH)
		tempsync |= PM3RD_SyncControl_VSYNC_ACTIVE_HIGH;

335 336 337 338 339 340
	PM3_WRITE_DAC_REG(par, PM3RD_SyncControl, tempsync);
	DPRINTK("PM3RD_SyncControl: %d\n", tempsync);

	PM3_WRITE_DAC_REG(par, PM3RD_DACControl, 0x00);

	switch (info->var.bits_per_pixel) {
L
Linus Torvalds 已提交
341
	case 8:
342
		PM3_WRITE_DAC_REG(par, PM3RD_PixelSize,
L
Linus Torvalds 已提交
343
				  PM3RD_PixelSize_8_BIT_PIXELS);
344
		PM3_WRITE_DAC_REG(par, PM3RD_ColorFormat,
L
Linus Torvalds 已提交
345 346 347 348 349
				  PM3RD_ColorFormat_CI8_COLOR |
				  PM3RD_ColorFormat_COLOR_ORDER_BLUE_LOW);
		tempmisc |= PM3RD_MiscControl_HIGHCOLOR_RES_ENABLE;
		break;
	case 12:
350
		PM3_WRITE_DAC_REG(par, PM3RD_PixelSize,
L
Linus Torvalds 已提交
351
				  PM3RD_PixelSize_16_BIT_PIXELS);
352
		PM3_WRITE_DAC_REG(par, PM3RD_ColorFormat,
L
Linus Torvalds 已提交
353 354 355 356 357
				  PM3RD_ColorFormat_4444_COLOR |
				  PM3RD_ColorFormat_COLOR_ORDER_BLUE_LOW |
				  PM3RD_ColorFormat_LINEAR_COLOR_EXT_ENABLE);
		tempmisc |= PM3RD_MiscControl_DIRECTCOLOR_ENABLE |
			PM3RD_MiscControl_HIGHCOLOR_RES_ENABLE;
358
		break;
L
Linus Torvalds 已提交
359
	case 15:
360
		PM3_WRITE_DAC_REG(par, PM3RD_PixelSize,
L
Linus Torvalds 已提交
361
				  PM3RD_PixelSize_16_BIT_PIXELS);
362
		PM3_WRITE_DAC_REG(par, PM3RD_ColorFormat,
L
Linus Torvalds 已提交
363 364 365 366 367
				  PM3RD_ColorFormat_5551_FRONT_COLOR |
				  PM3RD_ColorFormat_COLOR_ORDER_BLUE_LOW |
				  PM3RD_ColorFormat_LINEAR_COLOR_EXT_ENABLE);
		tempmisc |= PM3RD_MiscControl_DIRECTCOLOR_ENABLE |
			PM3RD_MiscControl_HIGHCOLOR_RES_ENABLE;
368
		break;
L
Linus Torvalds 已提交
369
	case 16:
370
		PM3_WRITE_DAC_REG(par, PM3RD_PixelSize,
L
Linus Torvalds 已提交
371
				  PM3RD_PixelSize_16_BIT_PIXELS);
372
		PM3_WRITE_DAC_REG(par, PM3RD_ColorFormat,
L
Linus Torvalds 已提交
373 374 375 376 377 378 379
				  PM3RD_ColorFormat_565_FRONT_COLOR |
				  PM3RD_ColorFormat_COLOR_ORDER_BLUE_LOW |
				  PM3RD_ColorFormat_LINEAR_COLOR_EXT_ENABLE);
		tempmisc |= PM3RD_MiscControl_DIRECTCOLOR_ENABLE |
			PM3RD_MiscControl_HIGHCOLOR_RES_ENABLE;
		break;
	case 32:
380
		PM3_WRITE_DAC_REG(par, PM3RD_PixelSize,
L
Linus Torvalds 已提交
381
				  PM3RD_PixelSize_32_BIT_PIXELS);
382
		PM3_WRITE_DAC_REG(par, PM3RD_ColorFormat,
L
Linus Torvalds 已提交
383 384 385 386 387 388
				  PM3RD_ColorFormat_8888_COLOR |
				  PM3RD_ColorFormat_COLOR_ORDER_BLUE_LOW);
		tempmisc |= PM3RD_MiscControl_DIRECTCOLOR_ENABLE |
			PM3RD_MiscControl_HIGHCOLOR_RES_ENABLE;
		break;
	}
389
	PM3_WRITE_DAC_REG(par, PM3RD_MiscControl, tempmisc);
L
Linus Torvalds 已提交
390 391
}

392 393 394 395 396
/*
 * hardware independent functions
 */
int pm3fb_init(void);
int pm3fb_setup(char*);
L
Linus Torvalds 已提交
397

398 399 400
static int pm3fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
	u32 lpitch;
L
Linus Torvalds 已提交
401

402 403 404 405 406 407
	var->transp.offset = 0;
	var->transp.length = 0;
	switch(var->bits_per_pixel) {
	case 8:
		var->red.length = var->green.length = var->blue.length = 8;
		var->red.offset = var->green.offset = var->blue.offset = 0;
L
Linus Torvalds 已提交
408
		break;
409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426
	case 12:
		var->red.offset   = 8;
		var->red.length   = 4;
		var->green.offset = 4;
		var->green.length = 4;
		var->blue.offset  = 0;
		var->blue.length  = 4;
		var->transp.offset = 12;
		var->transp.length = 4;
	case 15:
		var->red.offset   = 10;
		var->red.length   = 5;
		var->green.offset = 5;
		var->green.length = 5;
		var->blue.offset  = 0;
		var->blue.length  = 5;
		var->transp.offset = 15;
		var->transp.length = 1;
L
Linus Torvalds 已提交
427
		break;
428 429 430 431 432 433 434
	case 16:
		var->red.offset   = 11;
		var->red.length   = 5;
		var->green.offset = 5;
		var->green.length = 6;
		var->blue.offset  = 0;
		var->blue.length  = 5;
L
Linus Torvalds 已提交
435
		break;
436 437 438 439 440 441 442
	case 32:
		var->transp.offset = 24;
		var->transp.length = 8;
		var->red.offset	  = 16;
		var->green.offset = 8;
		var->blue.offset  = 0;
		var->red.length = var->green.length = var->blue.length = 8;
L
Linus Torvalds 已提交
443 444
		break;
	default:
445 446
		DPRINTK("depth not supported: %u\n", var->bits_per_pixel);
		return -EINVAL;
L
Linus Torvalds 已提交
447
	}
448
	var->height = var->width = -1;
L
Linus Torvalds 已提交
449

450 451 452 453
	if (var->xres != var->xres_virtual) {
		DPRINTK("virtual x resolution != physical x resolution not supported\n");
		return -EINVAL;
	}
L
Linus Torvalds 已提交
454

455 456 457
	if (var->yres > var->yres_virtual) {
		DPRINTK("virtual y resolution < physical y resolution not possible\n");
		return -EINVAL;
L
Linus Torvalds 已提交
458 459
	}

460 461 462
	if (var->xoffset) {
		DPRINTK("xoffset not supported\n");
		return -EINVAL;
L
Linus Torvalds 已提交
463 464
	}

465 466 467
	if ((var->vmode & FB_VMODE_MASK) == FB_VMODE_INTERLACED) {
		DPRINTK("interlace not supported\n");
		return -EINVAL;
L
Linus Torvalds 已提交
468 469
	}

470 471
	var->xres = (var->xres + 31) & ~31; /* could sometimes be 8 */
	lpitch = var->xres * ((var->bits_per_pixel + 7)>>3);
L
Linus Torvalds 已提交
472

473 474 475 476
	if (var->xres < 200 || var->xres > 2048) {
		DPRINTK("width not supported: %u\n", var->xres);
		return -EINVAL;
	}
L
Linus Torvalds 已提交
477

478 479 480 481
	if (var->yres < 200 || var->yres > 4095) {
		DPRINTK("height not supported: %u\n", var->yres);
		return -EINVAL;
	}
L
Linus Torvalds 已提交
482

483 484 485 486 487 488 489 490 491
	if (lpitch * var->yres_virtual > info->fix.smem_len) {
		DPRINTK("no memory for screen (%ux%ux%u)\n",
			var->xres, var->yres_virtual, var->bits_per_pixel);
		return -EINVAL;
	}

	if (PICOS2KHZ(var->pixclock) > PM3_MAX_PIXCLOCK) {
		DPRINTK("pixclock too high (%ldKHz)\n", PICOS2KHZ(var->pixclock));
		return -EINVAL;
L
Linus Torvalds 已提交
492 493
	}

494
	var->accel_flags = 0;	/* Can't mmap if this is on */
L
Linus Torvalds 已提交
495

496 497 498 499
	DPRINTK("Checking graphics mode at %dx%d depth %d\n",
		var->xres, var->yres, var->bits_per_pixel);
	return 0;
}
L
Linus Torvalds 已提交
500

501 502 503 504 505
static int pm3fb_set_par(struct fb_info *info)
{
	struct pm3_par *par = info->par;
	const u32 xres = (info->var.xres + 31) & ~31;
	const int depth = (info->var.bits_per_pixel + 7) & ~7;
L
Linus Torvalds 已提交
506

507 508 509 510
	par->base = pm3fb_shift_bpp(info->var.bits_per_pixel,
					(info->var.yoffset * xres)
					+ info->var.xoffset);
	par->video = 0;
L
Linus Torvalds 已提交
511

512 513 514 515
	if (info->var.sync & FB_SYNC_HOR_HIGH_ACT)
		par->video |= PM3VideoControl_HSYNC_ACTIVE_HIGH;
	else
		par->video |= PM3VideoControl_HSYNC_ACTIVE_LOW;
L
Linus Torvalds 已提交
516

517 518 519 520
	if (info->var.sync & FB_SYNC_VERT_HIGH_ACT)
		par->video |= PM3VideoControl_VSYNC_ACTIVE_HIGH;
	else
		par->video |= PM3VideoControl_VSYNC_ACTIVE_LOW;
L
Linus Torvalds 已提交
521

522 523 524 525
	if ((info->var.vmode & FB_VMODE_MASK) == FB_VMODE_DOUBLE)
		par->video |= PM3VideoControl_LINE_DOUBLE_ON;
	else
		par->video |= PM3VideoControl_LINE_DOUBLE_OFF;
L
Linus Torvalds 已提交
526

527 528 529 530 531
	if (info->var.activate == FB_ACTIVATE_NOW)
		par->video |= PM3VideoControl_ENABLE;
	else {
		par->video |= PM3VideoControl_DISABLE;
		DPRINTK("PM3Video disabled\n");
L
Linus Torvalds 已提交
532
	}
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
	switch (depth) {
	case 8:
		par->video |= PM3VideoControl_PIXELSIZE_8BIT;
		break;
	case 12:
	case 15:
	case 16:
		par->video |= PM3VideoControl_PIXELSIZE_16BIT;
		break;
	case 32:
		par->video |= PM3VideoControl_PIXELSIZE_32BIT;
		break;
	default:
		DPRINTK("Unsupported depth\n");
		break;
L
Linus Torvalds 已提交
548 549
	}

550 551 552 553
	info->fix.visual =
		(depth == 8) ? FB_VISUAL_PSEUDOCOLOR : FB_VISUAL_TRUECOLOR;
	info->fix.line_length = ((info->var.xres_virtual + 7)  & ~7)
					* depth / 8;
L
Linus Torvalds 已提交
554

555 556 557 558 559 560
/*	pm3fb_clear_memory(info, 0);*/
	pm3fb_clear_colormap(par, 0, 0, 0);
	PM3_WRITE_DAC_REG(par, PM3RD_CursorMode,
			  PM3RD_CursorMode_CURSOR_DISABLE);
	pm3fb_write_mode(info);
	return 0;
L
Linus Torvalds 已提交
561 562
}

563 564 565
static int pm3fb_setcolreg(unsigned regno, unsigned red, unsigned green,
			   unsigned blue, unsigned transp,
			   struct fb_info *info)
L
Linus Torvalds 已提交
566
{
567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595
	struct pm3_par *par = info->par;

	if (regno >= 256)  /* no. of hw registers */
	   return -EINVAL;

	/* grayscale works only partially under directcolor */
	if (info->var.grayscale) {
	   /* grayscale = 0.30*R + 0.59*G + 0.11*B */
	   red = green = blue = (red * 77 + green * 151 + blue * 28) >> 8;
	}

	/* Directcolor:
	 *   var->{color}.offset contains start of bitfield
	 *   var->{color}.length contains length of bitfield
	 *   {hardwarespecific} contains width of DAC
	 *   pseudo_palette[X] is programmed to (X << red.offset) |
	 *				      (X << green.offset) |
	 *				      (X << blue.offset)
	 *   RAMDAC[X] is programmed to (red, green, blue)
	 *   color depth = SUM(var->{color}.length)
	 *
	 * Pseudocolor:
	 *	var->{color}.offset is 0
	 *	var->{color}.length contains width of DAC or the number of unique
	 *			colors available (color depth)
	 *	pseudo_palette is not used
	 *	RAMDAC[X] is programmed to (red, green, blue)
	 *	color depth = var->{color}.length
	 */
L
Linus Torvalds 已提交
596

597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632
	/*
	 * This is the point where the color is converted to something that
	 * is acceptable by the hardware.
	 */
#define CNVT_TOHW(val,width) ((((val)<<(width))+0x7FFF-(val))>>16)
	red = CNVT_TOHW(red, info->var.red.length);
	green = CNVT_TOHW(green, info->var.green.length);
	blue = CNVT_TOHW(blue, info->var.blue.length);
	transp = CNVT_TOHW(transp, info->var.transp.length);
#undef CNVT_TOHW

	if (info->fix.visual == FB_VISUAL_TRUECOLOR ||
	info->fix.visual == FB_VISUAL_DIRECTCOLOR) {
		u32 v;

		if (regno >= 16)
			return -EINVAL;

		v = (red << info->var.red.offset) |
			(green << info->var.green.offset) |
			(blue << info->var.blue.offset) |
			(transp << info->var.transp.offset);

		switch (info->var.bits_per_pixel) {
		case 8:
			break;
		case 16:
		case 24:
		case 32:
			((u32*)(info->pseudo_palette))[regno] = v;
			break;
		}
		return 0;
	}
	else if (info->fix.visual == FB_VISUAL_PSEUDOCOLOR)
		pm3fb_set_color(par, regno, red, green, blue);
L
Linus Torvalds 已提交
633

634
	return 0;
L
Linus Torvalds 已提交
635 636
}

637 638
static int pm3fb_pan_display(struct fb_var_screeninfo *var,
				 struct fb_info *info)
L
Linus Torvalds 已提交
639
{
640 641
	struct pm3_par *par = info->par;
	const u32 xres = (var->xres + 31) & ~31;
L
Linus Torvalds 已提交
642

643 644 645 646 647 648
	par->base = pm3fb_shift_bpp(var->bits_per_pixel,
					(var->yoffset * xres)
					+ var->xoffset);
	PM3_SLOW_WRITE_REG(par, PM3ScreenBase, par->base);
	return 0;
}
L
Linus Torvalds 已提交
649

650 651 652 653
static int pm3fb_blank(int blank_mode, struct fb_info *info)
{
	struct pm3_par *par = info->par;
	u32 video = par->video;
L
Linus Torvalds 已提交
654

655 656 657 658 659 660 661 662 663 664
	/*
	 * Oxygen VX1 - it appears that setting PM3VideoControl and
	 * then PM3RD_SyncControl to the same SYNC settings undoes
	 * any net change - they seem to xor together.  Only set the
	 * sync options in PM3RD_SyncControl.  --rmk
	 */
	video &= ~(PM3VideoControl_HSYNC_MASK |
		   PM3VideoControl_VSYNC_MASK);
	video |= PM3VideoControl_HSYNC_ACTIVE_HIGH |
		 PM3VideoControl_VSYNC_ACTIVE_HIGH;
L
Linus Torvalds 已提交
665

666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688
	switch (blank_mode) {
	case FB_BLANK_UNBLANK:
		video = video | PM3VideoControl_ENABLE;
		break;
	case FB_BLANK_NORMAL:	/* FIXME */
		video = video & ~(PM3VideoControl_ENABLE);
		break;
	case FB_BLANK_HSYNC_SUSPEND:
		video = video & ~(PM3VideoControl_HSYNC_MASK |
				  PM3VideoControl_BLANK_ACTIVE_LOW);
		break;
	case FB_BLANK_VSYNC_SUSPEND:
		video = video & ~(PM3VideoControl_VSYNC_MASK |
				  PM3VideoControl_BLANK_ACTIVE_LOW);
		break;
	case FB_BLANK_POWERDOWN:
		video = video & ~(PM3VideoControl_HSYNC_MASK |
				  PM3VideoControl_VSYNC_MASK |
				  PM3VideoControl_BLANK_ACTIVE_LOW);
		break;
	default:
		DPRINTK("Unsupported blanking %d\n", blank_mode);
		return 1;
L
Linus Torvalds 已提交
689 690
	}

691
	PM3_SLOW_WRITE_REG(par,PM3VideoControl, video);
L
Linus Torvalds 已提交
692

693
	return 0;
L
Linus Torvalds 已提交
694 695
}

696 697 698
	/*
	 *  Frame buffer operations
	 */
L
Linus Torvalds 已提交
699

700 701 702 703 704 705 706 707 708 709 710
static struct fb_ops pm3fb_ops = {
	.owner		= THIS_MODULE,
	.fb_check_var	= pm3fb_check_var,
	.fb_set_par	= pm3fb_set_par,
	.fb_setcolreg	= pm3fb_setcolreg,
	.fb_pan_display	= pm3fb_pan_display,
	.fb_fillrect	= cfb_fillrect,		/* Needed !!! */
	.fb_copyarea	= cfb_copyarea,		/* Needed !!! */
	.fb_imageblit	= cfb_imageblit,	/* Needed !!! */
	.fb_blank	= pm3fb_blank,
};
L
Linus Torvalds 已提交
711

712
/* ------------------------------------------------------------------------- */
L
Linus Torvalds 已提交
713

714 715 716
	/*
	 *  Initialization
	 */
L
Linus Torvalds 已提交
717

718 719 720
/* mmio register are already mapped when this function is called */
/* the pm3fb_fix.smem_start is also set */
static unsigned long pm3fb_size_memory(struct pm3_par *par)
L
Linus Torvalds 已提交
721
{
722 723
	unsigned long	memsize = 0, tempBypass, i, temp1, temp2;
	unsigned char	__iomem *screen_mem;
L
Linus Torvalds 已提交
724

725 726 727 728 729 730
	pm3fb_fix.smem_len = 64 * 1024 * 1024; /* request full aperture size */
	/* Linear frame buffer - request region and map it. */
	if (!request_mem_region(pm3fb_fix.smem_start, pm3fb_fix.smem_len,
				 "pm3fb smem")) {
		printk(KERN_WARNING "pm3fb: Can't reserve smem.\n");
		return 0;
L
Linus Torvalds 已提交
731
	}
732 733 734 735 736 737
	screen_mem =
		ioremap_nocache(pm3fb_fix.smem_start, pm3fb_fix.smem_len);
	if (!screen_mem) {
		printk(KERN_WARNING "pm3fb: Can't ioremap smem area.\n");
		release_mem_region(pm3fb_fix.smem_start, pm3fb_fix.smem_len);
		return 0;
L
Linus Torvalds 已提交
738 739
	}

740 741
	/* TODO: card-specific stuff, *before* accessing *any* FB memory */
	/* For Appian Jeronimo 2000 board second head */
L
Linus Torvalds 已提交
742

743
	tempBypass = PM3_READ_REG(par, PM3MemBypassWriteMask);
L
Linus Torvalds 已提交
744

745
	DPRINTK("PM3MemBypassWriteMask was: 0x%08lx\n", tempBypass);
L
Linus Torvalds 已提交
746

747
	PM3_SLOW_WRITE_REG(par, PM3MemBypassWriteMask, 0xFFFFFFFF);
L
Linus Torvalds 已提交
748

749 750 751 752 753 754
	/* pm3 split up memory, replicates, and do a lot of nasty stuff IMHO ;-) */
	for (i = 0; i < 32; i++) {
		fb_writel(i * 0x00345678,
			  (screen_mem + (i * 1048576)));
		mb();
		temp1 = fb_readl((screen_mem + (i * 1048576)));
L
Linus Torvalds 已提交
755

756 757 758
		/* Let's check for wrapover, write will fail at 16MB boundary */
		if (temp1 == (i * 0x00345678))
			memsize = i;
L
Linus Torvalds 已提交
759
		else
760
			break;
L
Linus Torvalds 已提交
761 762
	}

763
	DPRINTK("First detect pass already got %ld MB\n", memsize + 1);
L
Linus Torvalds 已提交
764

765 766 767 768 769 770
	if (memsize + 1 == i) {
		for (i = 0; i < 32; i++) {
			/* Clear first 32MB ; 0 is 0, no need to byteswap */
			writel(0x0000000,
			       (screen_mem + (i * 1048576)));
			mb();
L
Linus Torvalds 已提交
771 772
		}

773 774 775 776 777 778 779 780 781 782 783 784 785
		for (i = 32; i < 64; i++) {
			fb_writel(i * 0x00345678,
				  (screen_mem + (i * 1048576)));
			mb();
			temp1 =
			    fb_readl((screen_mem + (i * 1048576)));
			temp2 =
			    fb_readl((screen_mem + ((i - 32) * 1048576)));
			/* different value, different RAM... */
			if ((temp1 == (i * 0x00345678)) && (temp2 == 0))
				memsize = i;
			else
				break;
L
Linus Torvalds 已提交
786 787
		}
	}
788
	DPRINTK("Second detect pass got %ld MB\n", memsize + 1);
L
Linus Torvalds 已提交
789

790
	PM3_SLOW_WRITE_REG(par, PM3MemBypassWriteMask, tempBypass);
L
Linus Torvalds 已提交
791

792 793 794
	iounmap(screen_mem);
	release_mem_region(pm3fb_fix.smem_start, pm3fb_fix.smem_len);
	memsize = 1048576 * (memsize + 1);
L
Linus Torvalds 已提交
795

796
	DPRINTK("Returning 0x%08lx bytes\n", memsize);
L
Linus Torvalds 已提交
797

798
	return memsize;
L
Linus Torvalds 已提交
799 800
}

801 802
static int __devinit pm3fb_probe(struct pci_dev *dev,
				  const struct pci_device_id *ent)
L
Linus Torvalds 已提交
803
{
804 805 806 807
	struct fb_info *info;
	struct pm3_par *par;
	struct device* device = &dev->dev; /* for pci drivers */
	int err, retval = -ENXIO;
L
Linus Torvalds 已提交
808

809 810 811 812
	err = pci_enable_device(dev);
	if (err) {
		printk(KERN_WARNING "pm3fb: Can't enable PCI dev: %d\n", err);
		return err;
L
Linus Torvalds 已提交
813
	}
814 815 816 817
	/*
	 * Dynamically allocate info and par
	 */
	info = framebuffer_alloc(sizeof(struct pm3_par), device);
L
Linus Torvalds 已提交
818

819 820 821
	if (!info)
		return -ENOMEM;
	par = info->par;
L
Linus Torvalds 已提交
822

823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855
	/*
	 * Here we set the screen_base to the virtual memory address
	 * for the framebuffer.
	 */
	pm3fb_fix.mmio_start = pci_resource_start(dev, 0);
	pm3fb_fix.mmio_len = PM3_REGS_SIZE;

	/* Registers - request region and map it. */
	if (!request_mem_region(pm3fb_fix.mmio_start, pm3fb_fix.mmio_len,
				 "pm3fb regbase")) {
		printk(KERN_WARNING "pm3fb: Can't reserve regbase.\n");
		goto err_exit_neither;
	}
	par->v_regs =
		ioremap_nocache(pm3fb_fix.mmio_start, pm3fb_fix.mmio_len);
	if (!par->v_regs) {
		printk(KERN_WARNING "pm3fb: Can't remap %s register area.\n",
			pm3fb_fix.id);
		release_mem_region(pm3fb_fix.mmio_start, pm3fb_fix.mmio_len);
		goto err_exit_neither;
	}

#if defined(__BIG_ENDIAN)
	pm3fb_fix.mmio_start += PM3_REGS_SIZE;
	DPRINTK("Adjusting register base for big-endian.\n");
#endif
	/* Linear frame buffer - request region and map it. */
	pm3fb_fix.smem_start = pci_resource_start(dev, 1);
	pm3fb_fix.smem_len = pm3fb_size_memory(par);
	if (!pm3fb_fix.smem_len)
	{
		printk(KERN_WARNING "pm3fb: Can't find memory on board.\n");
		goto err_exit_mmio;
L
Linus Torvalds 已提交
856
	}
857 858 859 860
	if (!request_mem_region(pm3fb_fix.smem_start, pm3fb_fix.smem_len,
				 "pm3fb smem")) {
		printk(KERN_WARNING "pm3fb: Can't reserve smem.\n");
		goto err_exit_mmio;
L
Linus Torvalds 已提交
861
	}
862 863 864 865 866 867
	info->screen_base =
		ioremap_nocache(pm3fb_fix.smem_start, pm3fb_fix.smem_len);
	if (!info->screen_base) {
		printk(KERN_WARNING "pm3fb: Can't ioremap smem area.\n");
		release_mem_region(pm3fb_fix.smem_start, pm3fb_fix.smem_len);
		goto err_exit_mmio;
L
Linus Torvalds 已提交
868
	}
869
	info->screen_size = pm3fb_fix.smem_len;
L
Linus Torvalds 已提交
870

871
	info->fbops = &pm3fb_ops;
L
Linus Torvalds 已提交
872

873
	par->video = PM3_READ_REG(par, PM3VideoControl);
L
Linus Torvalds 已提交
874

875 876 877
	info->fix = pm3fb_fix;
	info->pseudo_palette = par->palette;
	info->flags = FBINFO_DEFAULT;/* | FBINFO_HWACCEL_YPAN;*/
L
Linus Torvalds 已提交
878

879 880 881 882 883 884
	/*
	 * This should give a reasonable default video mode. The following is
	 * done when we can set a video mode.
	 */
	if (!mode_option)
		mode_option = "640x480@60";
L
Linus Torvalds 已提交
885

886
	retval = fb_find_mode(&info->var, info, mode_option, NULL, 0, NULL, 8);
L
Linus Torvalds 已提交
887

888 889 890
	if (!retval || retval == 4) {
		retval = -EINVAL;
		goto err_exit_both;
L
Linus Torvalds 已提交
891 892
	}

893 894 895 896
	/* This has to been done !!! */
	if (fb_alloc_cmap(&info->cmap, 256, 0) < 0) {
		retval = -ENOMEM;
		goto err_exit_both;
L
Linus Torvalds 已提交
897 898
	}

899 900 901 902
	/*
	 * For drivers that can...
	 */
	pm3fb_check_var(&info->var, info);
L
Linus Torvalds 已提交
903

904 905 906
	if (register_framebuffer(info) < 0) {
		retval = -EINVAL;
		goto err_exit_all;
L
Linus Torvalds 已提交
907
	}
908 909 910 911
	printk(KERN_INFO "fb%d: %s frame buffer device\n", info->node,
	   info->fix.id);
	pci_set_drvdata(dev, info); /* or dev_set_drvdata(device, info) */
	return 0;
L
Linus Torvalds 已提交
912

913 914 915 916 917 918 919 920 921 922 923
 err_exit_all:
	fb_dealloc_cmap(&info->cmap);
 err_exit_both:
	iounmap(info->screen_base);
	release_mem_region(pm3fb_fix.smem_start, pm3fb_fix.smem_len);
 err_exit_mmio:
	iounmap(par->v_regs);
	release_mem_region(pm3fb_fix.mmio_start, pm3fb_fix.mmio_len);
 err_exit_neither:
	framebuffer_release(info);
	return retval;
L
Linus Torvalds 已提交
924 925
}

926 927 928 929
	/*
	 *  Cleanup
	 */
static void __devexit pm3fb_remove(struct pci_dev *dev)
L
Linus Torvalds 已提交
930
{
931
	struct fb_info *info = pci_get_drvdata(dev);
L
Linus Torvalds 已提交
932

933 934 935
	if (info) {
		struct fb_fix_screeninfo *fix = &info->fix;
		struct pm3_par *par = info->par;
L
Linus Torvalds 已提交
936

937 938
		unregister_framebuffer(info);
		fb_dealloc_cmap(&info->cmap);
L
Linus Torvalds 已提交
939

940 941 942 943
		iounmap(info->screen_base);
		release_mem_region(fix->smem_start, fix->smem_len);
		iounmap(par->v_regs);
		release_mem_region(fix->mmio_start, fix->mmio_len);
L
Linus Torvalds 已提交
944

945 946
		pci_set_drvdata(dev, NULL);
		framebuffer_release(info);
L
Linus Torvalds 已提交
947 948 949
	}
}

950 951 952 953 954 955
static struct pci_device_id pm3fb_id_table[] = {
	{ PCI_VENDOR_ID_3DLABS, 0x0a,
	  PCI_ANY_ID, PCI_ANY_ID, PCI_BASE_CLASS_DISPLAY << 16,
	  0xff0000, 0 },
	{ 0, }
};
L
Linus Torvalds 已提交
956

957 958 959 960 961 962 963
/* For PCI drivers */
static struct pci_driver pm3fb_driver = {
	.name =		"pm3fb",
	.id_table =	pm3fb_id_table,
	.probe =	pm3fb_probe,
	.remove =	__devexit_p(pm3fb_remove),
};
L
Linus Torvalds 已提交
964

965
MODULE_DEVICE_TABLE(pci, pm3fb_id_table);
L
Linus Torvalds 已提交
966

967
int __init pm3fb_init(void)
L
Linus Torvalds 已提交
968 969
{
	/*
970
	 *  For kernel boot options (in 'video=pm3fb:<options>' format)
L
Linus Torvalds 已提交
971
	 */
972 973
#ifndef MODULE
	char *option = NULL;
L
Linus Torvalds 已提交
974

975 976 977
	if (fb_get_options("pm3fb", &option))
		return -ENODEV;
	pm3fb_setup(option);
L
Linus Torvalds 已提交
978 979
#endif

980
	return pci_register_driver(&pm3fb_driver);
L
Linus Torvalds 已提交
981 982
}

983
static void __exit pm3fb_exit(void)
L
Linus Torvalds 已提交
984
{
985
	pci_unregister_driver(&pm3fb_driver);
L
Linus Torvalds 已提交
986 987
}

988
#ifndef MODULE
989 990 991
	/*
	 *  Setup
	 */
L
Linus Torvalds 已提交
992

993 994 995 996 997
/*
 * Only necessary if your driver takes special options,
 * otherwise we fall back on the generic fb_setup().
 */
int __init pm3fb_setup(char *options)
L
Linus Torvalds 已提交
998
{
999
	/* Parse user speficied options (`video=pm3fb:') */
A
Alan 已提交
1000
	return 0;
L
Linus Torvalds 已提交
1001 1002
}
#endif /* MODULE */
1003 1004 1005 1006 1007

module_init(pm3fb_init);
module_exit(pm3fb_exit);

MODULE_LICENSE("GPL");