ps3fb.c 30.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/*
 *  linux/drivers/video/ps3fb.c -- PS3 GPU frame buffer device
 *
 *	Copyright (C) 2006 Sony Computer Entertainment Inc.
 *	Copyright 2006, 2007 Sony Corporation
 *
 *  This file is based on :
 *
 *  linux/drivers/video/vfb.c -- Virtual frame buffer device
 *
 *	Copyright (C) 2002 James Simmons
 *
 *	Copyright (C) 1997 Geert Uytterhoeven
 *
 *  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/interrupt.h>
#include <linux/console.h>
#include <linux/ioctl.h>
G
Geert Uytterhoeven 已提交
28 29
#include <linux/kthread.h>
#include <linux/freezer.h>
30
#include <linux/uaccess.h>
31 32 33 34 35 36 37 38 39
#include <linux/fb.h>
#include <linux/init.h>

#include <asm/abs_addr.h>
#include <asm/lv1call.h>
#include <asm/ps3av.h>
#include <asm/ps3fb.h>
#include <asm/ps3.h>

40 41 42

#define DEVICE_NAME		"ps3fb"

43 44 45 46 47 48 49 50 51 52 53 54
#define L1GPU_CONTEXT_ATTRIBUTE_DISPLAY_SYNC	0x101
#define L1GPU_CONTEXT_ATTRIBUTE_DISPLAY_FLIP	0x102
#define L1GPU_CONTEXT_ATTRIBUTE_FB_SETUP	0x600
#define L1GPU_CONTEXT_ATTRIBUTE_FB_BLIT		0x601
#define L1GPU_CONTEXT_ATTRIBUTE_FB_BLIT_SYNC	0x602

#define L1GPU_FB_BLIT_WAIT_FOR_COMPLETION	(1ULL << 32)

#define L1GPU_DISPLAY_SYNC_HSYNC		1
#define L1GPU_DISPLAY_SYNC_VSYNC		2

#define DDR_SIZE				(0)	/* used no ddr */
55
#define GPU_CMD_BUF_SIZE			(64 * 1024)
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
#define GPU_IOIF				(0x0d000000UL)

#define PS3FB_FULL_MODE_BIT			0x80

#define GPU_INTR_STATUS_VSYNC_0			0	/* vsync on head A */
#define GPU_INTR_STATUS_VSYNC_1			1	/* vsync on head B */
#define GPU_INTR_STATUS_FLIP_0			3	/* flip head A */
#define GPU_INTR_STATUS_FLIP_1			4	/* flip head B */
#define GPU_INTR_STATUS_QUEUE_0			5	/* queue head A */
#define GPU_INTR_STATUS_QUEUE_1			6	/* queue head B */

#define GPU_DRIVER_INFO_VERSION			0x211

/* gpu internals */
struct display_head {
	u64 be_time_stamp;
	u32 status;
	u32 offset;
	u32 res1;
	u32 res2;
	u32 field;
	u32 reserved1;

	u64 res3;
	u32 raster;

	u64 vblank_count;
	u32 field_vsync;
	u32 reserved2;
};

struct gpu_irq {
	u32 irq_outlet;
	u32 status;
	u32 mask;
	u32 video_cause;
	u32 graph_cause;
	u32 user_cause;

	u32 res1;
	u64 res2;

	u32 reserved[4];
};

struct gpu_driver_info {
	u32 version_driver;
	u32 version_gpu;
	u32 memory_size;
	u32 hardware_channel;

	u32 nvcore_frequency;
	u32 memory_frequency;

	u32 reserved[1063];
	struct display_head display_head[8];
	struct gpu_irq irq;
};

struct ps3fb_priv {
	unsigned int irq_no;

	u64 context_handle, memory_handle;
	void *xdr_ea;
120
	size_t xdr_size;
121 122 123 124 125 126 127 128
	struct gpu_driver_info *dinfo;

	u64 vblank_count;	/* frame count */
	wait_queue_head_t wait_vsync;

	atomic_t ext_flip;	/* on/off flip with vsync */
	atomic_t f_count;	/* fb_open count */
	int is_blanked;
G
Geert Uytterhoeven 已提交
129 130
	int is_kicked;
	struct task_struct *task;
131 132 133
};
static struct ps3fb_priv ps3fb;

134 135 136 137 138 139 140
struct ps3fb_par {
	u32 pseudo_palette[16];
	int mode_id, new_mode_id;
	int res_index;
	unsigned int num_frames;	/* num of frame buffers */
};

141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
struct ps3fb_res_table {
	u32 xres;
	u32 yres;
	u32 xoff;
	u32 yoff;
	u32 type;
};
#define PS3FB_RES_FULL 1
static const struct ps3fb_res_table ps3fb_res[] = {
	/* res_x,y   margin_x,y  full */
	{  720,  480,  72,  48 , 0},
	{  720,  576,  72,  58 , 0},
	{ 1280,  720,  78,  38 , 0},
	{ 1920, 1080, 116,  58 , 0},
	/* full mode */
	{  720,  480,   0,   0 , PS3FB_RES_FULL},
	{  720,  576,   0,   0 , PS3FB_RES_FULL},
	{ 1280,  720,   0,   0 , PS3FB_RES_FULL},
	{ 1920, 1080,   0,   0 , PS3FB_RES_FULL},
	/* vesa: normally full mode */
	{ 1280,  768,   0,   0 , 0},
	{ 1280, 1024,   0,   0 , 0},
	{ 1920, 1200,   0,   0 , 0},
	{    0,    0,   0,   0 , 0} };

/* default resolution */
167
#define GPU_RES_INDEX	0		/* 720 x 480 */
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288

static const struct fb_videomode ps3fb_modedb[] = {
    /* 60 Hz broadcast modes (modes "1" to "5") */
    {
        /* 480i */
        "480i", 60, 576, 384, 74074, 130, 89, 78, 57, 63, 6,
        FB_SYNC_BROADCAST, FB_VMODE_INTERLACED
    },    {
        /* 480p */
        "480p", 60, 576, 384, 37037, 130, 89, 78, 57, 63, 6,
        FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED
    },    {
        /* 720p */
        "720p", 60, 1124, 644, 13481, 298, 148, 57, 44, 80, 5,
        FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED
    },    {
        /* 1080i */
        "1080i", 60, 1688, 964, 13481, 264, 160, 94, 62, 88, 5,
        FB_SYNC_BROADCAST, FB_VMODE_INTERLACED
    },    {
        /* 1080p */
        "1080p", 60, 1688, 964, 6741, 264, 160, 94, 62, 88, 5,
        FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED
    },

    /* 50 Hz broadcast modes (modes "6" to "10") */
    {
        /* 576i */
        "576i", 50, 576, 460, 74074, 142, 83, 97, 63, 63, 5,
        FB_SYNC_BROADCAST, FB_VMODE_INTERLACED
    },    {
        /* 576p */
        "576p", 50, 576, 460, 37037, 142, 83, 97, 63, 63, 5,
        FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED
    },    {
        /* 720p */
        "720p", 50, 1124, 644, 13468, 298, 478, 57, 44, 80, 5,
        FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED
    },    {
        /* 1080 */
        "1080i", 50, 1688, 964, 13468, 264, 600, 94, 62, 88, 5,
        FB_SYNC_BROADCAST, FB_VMODE_INTERLACED
    },    {
        /* 1080p */
        "1080p", 50, 1688, 964, 6734, 264, 600, 94, 62, 88, 5,
        FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED
    },

    /* VESA modes (modes "11" to "13") */
    {
	/* WXGA */
	"wxga", 60, 1280, 768, 12924, 160, 24, 29, 3, 136, 6,
	0, FB_VMODE_NONINTERLACED,
	FB_MODE_IS_VESA
    }, {
	/* SXGA */
	"sxga", 60, 1280, 1024, 9259, 248, 48, 38, 1, 112, 3,
	FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED,
	FB_MODE_IS_VESA
    }, {
	/* WUXGA */
	"wuxga", 60, 1920, 1200, 6494, 80, 48, 26, 3, 32, 6,
	FB_SYNC_HOR_HIGH_ACT, FB_VMODE_NONINTERLACED,
	FB_MODE_IS_VESA
    },

    /* 60 Hz broadcast modes (full resolution versions of modes "1" to "5") */
    {
	/* 480if */
	"480if", 60, 720, 480, 74074, 58, 17, 30, 9, 63, 6,
	FB_SYNC_BROADCAST, FB_VMODE_INTERLACED
    }, {
	/* 480pf */
	"480pf", 60, 720, 480, 37037, 58, 17, 30, 9, 63, 6,
	FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED
    }, {
	/* 720pf */
	"720pf", 60, 1280, 720, 13481, 220, 70, 19, 6, 80, 5,
	FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED
    }, {
	/* 1080if */
	"1080if", 60, 1920, 1080, 13481, 148, 44, 36, 4, 88, 5,
	FB_SYNC_BROADCAST, FB_VMODE_INTERLACED
    }, {
	/* 1080pf */
	"1080pf", 60, 1920, 1080, 6741, 148, 44, 36, 4, 88, 5,
	FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED
    },

    /* 50 Hz broadcast modes (full resolution versions of modes "6" to "10") */
    {
	/* 576if */
	"576if", 50, 720, 576, 74074, 70, 11, 39, 5, 63, 5,
	FB_SYNC_BROADCAST, FB_VMODE_INTERLACED
    }, {
	/* 576pf */
	"576pf", 50, 720, 576, 37037, 70, 11, 39, 5, 63, 5,
	FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED
    }, {
	/* 720pf */
	"720pf", 50, 1280, 720, 13468, 220, 400, 19, 6, 80, 5,
	FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED
    }, {
	/* 1080if */
	"1080f", 50, 1920, 1080, 13468, 148, 484, 36, 4, 88, 5,
	FB_SYNC_BROADCAST, FB_VMODE_INTERLACED
    }, {
	/* 1080pf */
	"1080pf", 50, 1920, 1080, 6734, 148, 484, 36, 4, 88, 5,
	FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED
    }
};


#define HEAD_A
#define HEAD_B

#define X_OFF(i)	(ps3fb_res[i].xoff)	/* left/right margin (pixel) */
#define Y_OFF(i)	(ps3fb_res[i].yoff)	/* top/bottom margin (pixel) */
#define WIDTH(i)	(ps3fb_res[i].xres)	/* width of FB */
#define HEIGHT(i)	(ps3fb_res[i].yres)	/* height of FB */
289 290 291 292 293 294 295 296 297 298 299 300 301 302
#define BPP		4			/* number of bytes per pixel */

/* Start of the virtual frame buffer (relative to fullscreen ) */
#define VP_OFF(i)	((WIDTH(i) * Y_OFF(i) + X_OFF(i)) * BPP)

/*
 * Start of the virtual frame buffer (relative to start of video memory)
 * This is PAGE_SIZE aligned for easier mmap()
 */
#define VFB_OFF(i)	PAGE_ALIGN(VP_OFF(i))

/* Start of the fullscreen frame buffer (relative to start of video memory) */
#define FB_OFF(i)	(-VP_OFF(i) & ~PAGE_MASK)

303

304
static int ps3fb_mode;
305
module_param(ps3fb_mode, int, 0);
306

307
static char *mode_option __devinitdata;
308

309
static int ps3fb_get_res_table(u32 xres, u32 yres, int mode)
310 311 312 313 314
{
	int full_mode;
	unsigned int i;
	u32 x, y, f;

315
	full_mode = (mode & PS3FB_FULL_MODE_BIT) ? PS3FB_RES_FULL : 0;
316 317 318 319 320 321
	for (i = 0;; i++) {
		x = ps3fb_res[i].xres;
		y = ps3fb_res[i].yres;
		f = ps3fb_res[i].type;

		if (!x) {
322
			pr_debug("ERROR: ps3fb_get_res_table()\n");
323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362
			return -1;
		}

		if (full_mode == PS3FB_RES_FULL && f != PS3FB_RES_FULL)
			continue;

		if (x == xres && (yres == 0 || y == yres))
			break;

		x = x - 2 * ps3fb_res[i].xoff;
		y = y - 2 * ps3fb_res[i].yoff;
		if (x == xres && (yres == 0 || y == yres))
			break;
	}
	return i;
}

static unsigned int ps3fb_find_mode(const struct fb_var_screeninfo *var,
				    u32 *line_length)
{
	unsigned int i, mode;

	for (i = 0; i < ARRAY_SIZE(ps3fb_modedb); i++)
		if (var->xres == ps3fb_modedb[i].xres &&
		    var->yres == ps3fb_modedb[i].yres &&
		    var->pixclock == ps3fb_modedb[i].pixclock &&
		    var->hsync_len == ps3fb_modedb[i].hsync_len &&
		    var->vsync_len == ps3fb_modedb[i].vsync_len &&
		    var->left_margin == ps3fb_modedb[i].left_margin &&
		    var->right_margin == ps3fb_modedb[i].right_margin &&
		    var->upper_margin == ps3fb_modedb[i].upper_margin &&
		    var->lower_margin == ps3fb_modedb[i].lower_margin &&
		    var->sync == ps3fb_modedb[i].sync &&
		    (var->vmode & FB_VMODE_MASK) == ps3fb_modedb[i].vmode) {
			/* Cropped broadcast modes use the full line_length */
			*line_length =
			    ps3fb_modedb[i < 10 ? i + 13 : i].xres * 4;
			/* Full broadcast modes have the full mode bit set */
			mode = i > 12 ? (i - 12) | PS3FB_FULL_MODE_BIT : i + 1;

363
			pr_debug("ps3fb_find_mode: mode %u\n", mode);
364 365 366
			return mode;
		}

367
	pr_debug("ps3fb_find_mode: mode not found\n");
368 369 370
	return 0;
}

371
static const struct fb_videomode *ps3fb_default_mode(int id)
372
{
373
	u32 mode = id & PS3AV_MODE_MASK;
374 375 376 377 378
	u32 flags;

	if (mode < 1 || mode > 13)
		return NULL;

379
	flags = id & ~PS3AV_MODE_MASK;
380 381 382 383 384 385 386 387 388

	if (mode <= 10 && flags & PS3FB_FULL_MODE_BIT) {
		/* Full broadcast mode */
		return &ps3fb_modedb[mode + 12];
	}

	return &ps3fb_modedb[mode - 1];
}

389
static int ps3fb_sync(struct fb_info *info, u32 frame)
390
{
391 392
	struct ps3fb_par *par = info->par;
	int i, status, error = 0;
393 394 395
	u32 xres, yres;
	u64 fb_ioif, offset;

396 397 398
	acquire_console_sem();

	i = par->res_index;
399 400 401
	xres = ps3fb_res[i].xres;
	yres = ps3fb_res[i].yres;

402
	if (frame > par->num_frames - 1) {
403 404
		dev_dbg(info->device, "%s: invalid frame number (%u)\n",
			__func__, frame);
405 406
		error = -EINVAL;
		goto out;
407 408 409 410 411 412 413 414 415 416 417
	}
	offset = xres * yres * BPP * frame;

	fb_ioif = GPU_IOIF + FB_OFF(i) + offset;
	status = lv1_gpu_context_attribute(ps3fb.context_handle,
					   L1GPU_CONTEXT_ATTRIBUTE_FB_BLIT,
					   offset, fb_ioif,
					   L1GPU_FB_BLIT_WAIT_FOR_COMPLETION |
					   (xres << 16) | yres,
					   xres * BPP);	/* line_length */
	if (status)
418 419 420
		dev_err(info->device,
			"%s: lv1_gpu_context_attribute FB_BLIT failed: %d\n",
			__func__, status);
421 422 423 424 425
#ifdef HEAD_A
	status = lv1_gpu_context_attribute(ps3fb.context_handle,
					   L1GPU_CONTEXT_ATTRIBUTE_DISPLAY_FLIP,
					   0, offset, 0, 0);
	if (status)
426 427 428
		dev_err(info->device,
			"%s: lv1_gpu_context_attribute FLIP failed: %d\n",
			__func__, status);
429 430 431 432 433 434
#endif
#ifdef HEAD_B
	status = lv1_gpu_context_attribute(ps3fb.context_handle,
					   L1GPU_CONTEXT_ATTRIBUTE_DISPLAY_FLIP,
					   1, offset, 0, 0);
	if (status)
435 436 437
		dev_err(info->device,
			"%s: lv1_gpu_context_attribute FLIP failed: %d\n",
			__func__, status);
438
#endif
439 440 441 442

out:
	release_console_sem();
	return error;
443 444 445 446 447 448 449 450 451 452 453 454 455 456
}


static int ps3fb_open(struct fb_info *info, int user)
{
	atomic_inc(&ps3fb.f_count);
	return 0;
}

static int ps3fb_release(struct fb_info *info, int user)
{
	if (atomic_dec_and_test(&ps3fb.f_count)) {
		if (atomic_read(&ps3fb.ext_flip)) {
			atomic_set(&ps3fb.ext_flip, 0);
457
			ps3fb_sync(info, 0);	/* single buffer */
458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476
		}
	}
	return 0;
}

    /*
     *  Setting the video mode has been split into two parts.
     *  First part, xxxfb_check_var, must not write anything
     *  to hardware, it should only verify and adjust var.
     *  This means it doesn't alter par but it does use hardware
     *  data from it to check this var.
     */

static int ps3fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info)
{
	u32 line_length;
	int mode;
	int i;

477 478 479 480
	dev_dbg(info->device, "var->xres:%u info->var.xres:%u\n", var->xres,
		info->var.xres);
	dev_dbg(info->device, "var->yres:%u info->var.yres:%u\n", var->yres,
		info->var.yres);
481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500

	/* FIXME For now we do exact matches only */
	mode = ps3fb_find_mode(var, &line_length);
	if (!mode)
		return -EINVAL;

	/*
	 *  FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal!
	 *  as FB_VMODE_SMOOTH_XPAN is only used internally
	 */

	if (var->vmode & FB_VMODE_CONUPDATE) {
		var->vmode |= FB_VMODE_YWRAP;
		var->xoffset = info->var.xoffset;
		var->yoffset = info->var.yoffset;
	}

	/* Virtual screen and panning are not supported */
	if (var->xres_virtual > var->xres || var->yres_virtual > var->yres ||
	    var->xoffset || var->yoffset) {
501 502
		dev_dbg(info->device,
			"Virtual screen and panning are not supported\n");
503 504 505 506 507 508 509 510 511 512 513 514 515 516
		return -EINVAL;
	}

	var->xres_virtual = var->xres;
	var->yres_virtual = var->yres;

	/* We support ARGB8888 only */
	if (var->bits_per_pixel > 32 || var->grayscale ||
	    var->red.offset > 16 || var->green.offset > 8 ||
	    var->blue.offset > 0 || var->transp.offset > 24 ||
	    var->red.length > 8 || var->green.length > 8 ||
	    var->blue.length > 8 || var->transp.length > 8 ||
	    var->red.msb_right || var->green.msb_right ||
	    var->blue.msb_right || var->transp.msb_right || var->nonstd) {
517
		dev_dbg(info->device, "We support ARGB8888 only\n");
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536
		return -EINVAL;
	}

	var->bits_per_pixel = 32;
	var->red.offset = 16;
	var->green.offset = 8;
	var->blue.offset = 0;
	var->transp.offset = 24;
	var->red.length = 8;
	var->green.length = 8;
	var->blue.length = 8;
	var->transp.length = 8;
	var->red.msb_right = 0;
	var->green.msb_right = 0;
	var->blue.msb_right = 0;
	var->transp.msb_right = 0;

	/* Rotation is not supported */
	if (var->rotate) {
537
		dev_dbg(info->device, "Rotation is not supported\n");
538 539 540 541
		return -EINVAL;
	}

	/* Memory limit */
542
	i = ps3fb_get_res_table(var->xres, var->yres, mode);
543 544
	if (ps3fb_res[i].xres*ps3fb_res[i].yres*BPP >
	    ps3fb.xdr_size - VFB_OFF(i)) {
545
		dev_dbg(info->device, "Not enough memory\n");
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560
		return -ENOMEM;
	}

	var->height = -1;
	var->width = -1;

	return 0;
}

    /*
     * This routine actually sets the video mode.
     */

static int ps3fb_set_par(struct fb_info *info)
{
561
	struct ps3fb_par *par = info->par;
562 563 564 565
	unsigned int mode;
	int i;
	unsigned long offset;

566
	dev_dbg(info->device, "xres:%d xv:%d yres:%d yv:%d clock:%d\n",
567 568 569 570 571 572 573
		info->var.xres, info->var.xres_virtual,
		info->var.yres, info->var.yres_virtual, info->var.pixclock);

	mode = ps3fb_find_mode(&info->var, &info->fix.line_length);
	if (!mode)
		return -EINVAL;

574
	i = ps3fb_get_res_table(info->var.xres, info->var.yres, mode);
575
	par->res_index = i;
576

577 578 579
	offset = VFB_OFF(i);
	info->fix.smem_start = virt_to_abs(ps3fb.xdr_ea) + offset;
	info->fix.smem_len = ps3fb.xdr_size - offset;
580
	info->screen_base = (char __iomem *)ps3fb.xdr_ea + offset;
581
	memset(ps3fb.xdr_ea, 0, ps3fb.xdr_size);
582

583 584
	par->num_frames = info->fix.smem_len/
			  (ps3fb_res[i].xres*ps3fb_res[i].yres*BPP);
585 586

	/* Keep the special bits we cannot set using fb_var_screeninfo */
587
	par->new_mode_id = (par->new_mode_id & ~PS3AV_MODE_MASK) | mode;
588

589 590 591 592 593 594 595
	if (par->new_mode_id != par->mode_id) {
		if (ps3av_set_video_mode(par->new_mode_id)) {
			par->new_mode_id = par->mode_id;
			return -EINVAL;
		}
		par->mode_id = par->new_mode_id;
	}
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 633 634 635

	return 0;
}

    /*
     *  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.
     */

static int ps3fb_setcolreg(unsigned int regno, unsigned int red,
			   unsigned int green, unsigned int blue,
			   unsigned int transp, struct fb_info *info)
{
	if (regno >= 16)
		return 1;

	red >>= 8;
	green >>= 8;
	blue >>= 8;
	transp >>= 8;

	((u32 *)info->pseudo_palette)[regno] = transp << 24 | red << 16 |
					       green << 8 | blue;
	return 0;
}

    /*
     *  As we have a virtual frame buffer, we need our own mmap function
     */

static int ps3fb_mmap(struct fb_info *info, struct vm_area_struct *vma)
{
	unsigned long size, offset;

	size = vma->vm_end - vma->vm_start;
	offset = vma->vm_pgoff << PAGE_SHIFT;
	if (offset + size > info->fix.smem_len)
		return -EINVAL;

636
	offset += info->fix.smem_start;
637 638 639 640
	if (remap_pfn_range(vma, vma->vm_start, offset >> PAGE_SHIFT,
			    size, vma->vm_page_prot))
		return -EAGAIN;

641 642
	dev_dbg(info->device, "ps3fb: mmap framebuffer P(%lx)->V(%lx)\n",
		offset, vma->vm_start);
643 644 645 646 647 648 649 650 651 652 653
	return 0;
}

    /*
     * Blank the display
     */

static int ps3fb_blank(int blank, struct fb_info *info)
{
	int retval;

654
	dev_dbg(info->device, "%s: blank:%d\n", __func__, blank);
655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680
	switch (blank) {
	case FB_BLANK_POWERDOWN:
	case FB_BLANK_HSYNC_SUSPEND:
	case FB_BLANK_VSYNC_SUSPEND:
	case FB_BLANK_NORMAL:
		retval = ps3av_video_mute(1);	/* mute on */
		if (!retval)
			ps3fb.is_blanked = 1;
		break;

	default:		/* unblank */
		retval = ps3av_video_mute(0);	/* mute off */
		if (!retval)
			ps3fb.is_blanked = 0;
		break;
	}
	return retval;
}

static int ps3fb_get_vblank(struct fb_vblank *vblank)
{
	memset(vblank, 0, sizeof(&vblank));
	vblank->flags = FB_VBLANK_HAVE_VSYNC;
	return 0;
}

681
static int ps3fb_wait_for_vsync(u32 crtc)
682 683 684 685 686 687 688 689 690 691 692 693 694 695
{
	int ret;
	u64 count;

	count = ps3fb.vblank_count;
	ret = wait_event_interruptible_timeout(ps3fb.wait_vsync,
					       count != ps3fb.vblank_count,
					       HZ / 10);
	if (!ret)
		return -ETIMEDOUT;

	return 0;
}

696
static void ps3fb_flip_ctl(int on, void *data)
697
{
698
	struct ps3fb_priv *priv = data;
G
Geert Uytterhoeven 已提交
699
	if (on)
700
		atomic_dec_if_positive(&priv->ext_flip);
G
Geert Uytterhoeven 已提交
701
	else
702
		atomic_inc(&priv->ext_flip);
703 704 705 706 707 708 709 710 711 712 713
}


    /*
     * ioctl
     */

static int ps3fb_ioctl(struct fb_info *info, unsigned int cmd,
		       unsigned long arg)
{
	void __user *argp = (void __user *)arg;
714
	u32 val;
715 716 717 718 719 720
	int retval = -EFAULT;

	switch (cmd) {
	case FBIOGET_VBLANK:
		{
			struct fb_vblank vblank;
721
			dev_dbg(info->device, "FBIOGET_VBLANK:\n");
722 723 724 725 726 727 728 729 730 731 732 733
			retval = ps3fb_get_vblank(&vblank);
			if (retval)
				break;

			if (copy_to_user(argp, &vblank, sizeof(vblank)))
				retval = -EFAULT;
			break;
		}

	case FBIO_WAITFORVSYNC:
		{
			u32 crt;
734
			dev_dbg(info->device, "FBIO_WAITFORVSYNC:\n");
735 736 737 738 739 740 741 742 743
			if (get_user(crt, (u32 __user *) arg))
				break;

			retval = ps3fb_wait_for_vsync(crt);
			break;
		}

	case PS3FB_IOCTL_SETMODE:
		{
744
			struct ps3fb_par *par = info->par;
745 746 747 748 749 750
			const struct fb_videomode *mode;
			struct fb_var_screeninfo var;

			if (copy_from_user(&val, argp, sizeof(val)))
				break;

751
			if (!(val & PS3AV_MODE_MASK)) {
752
				u32 id = ps3av_get_auto_mode();
753 754 755
				if (id > 0)
					val = (val & ~PS3AV_MODE_MASK) | id;
			}
756
			dev_dbg(info->device, "PS3FB_IOCTL_SETMODE:%x\n", val);
757
			retval = -EINVAL;
758
			mode = ps3fb_default_mode(val);
759 760 761 762 763 764 765
			if (mode) {
				var = info->var;
				fb_videomode_to_var(&var, mode);
				acquire_console_sem();
				info->flags |= FBINFO_MISC_USEREVENT;
				/* Force, in case only special bits changed */
				var.activate |= FB_ACTIVATE_FORCE;
766
				par->new_mode_id = val;
767 768 769 770 771 772 773 774 775
				retval = fb_set_var(info, &var);
				info->flags &= ~FBINFO_MISC_USEREVENT;
				release_console_sem();
			}
			break;
		}

	case PS3FB_IOCTL_GETMODE:
		val = ps3av_get_mode();
776
		dev_dbg(info->device, "PS3FB_IOCTL_GETMODE:%x\n", val);
777 778 779 780 781 782
		if (!copy_to_user(argp, &val, sizeof(val)))
			retval = 0;
		break;

	case PS3FB_IOCTL_SCREENINFO:
		{
783
			struct ps3fb_par *par = info->par;
784
			struct ps3fb_ioctl_res res;
785
			int i = par->res_index;
786
			dev_dbg(info->device, "PS3FB_IOCTL_SCREENINFO:\n");
787 788 789 790
			res.xres = ps3fb_res[i].xres;
			res.yres = ps3fb_res[i].yres;
			res.xoff = ps3fb_res[i].xoff;
			res.yoff = ps3fb_res[i].yoff;
791
			res.num_frames = par->num_frames;
792 793 794 795 796 797
			if (!copy_to_user(argp, &res, sizeof(res)))
				retval = 0;
			break;
		}

	case PS3FB_IOCTL_ON:
798
		dev_dbg(info->device, "PS3FB_IOCTL_ON:\n");
799 800 801 802 803
		atomic_inc(&ps3fb.ext_flip);
		retval = 0;
		break;

	case PS3FB_IOCTL_OFF:
804
		dev_dbg(info->device, "PS3FB_IOCTL_OFF:\n");
G
Geert Uytterhoeven 已提交
805
		atomic_dec_if_positive(&ps3fb.ext_flip);
806 807 808 809 810 811 812
		retval = 0;
		break;

	case PS3FB_IOCTL_FSEL:
		if (copy_from_user(&val, argp, sizeof(val)))
			break;

813 814
		dev_dbg(info->device, "PS3FB_IOCTL_FSEL:%d\n", val);
		retval = ps3fb_sync(info, val);
815 816 817 818 819 820 821 822 823 824 825
		break;

	default:
		retval = -ENOIOCTLCMD;
		break;
	}
	return retval;
}

static int ps3fbd(void *arg)
{
826 827
	struct fb_info *info = arg;

828
	set_freezable();
G
Geert Uytterhoeven 已提交
829 830 831 832 833
	while (!kthread_should_stop()) {
		try_to_freeze();
		set_current_state(TASK_INTERRUPTIBLE);
		if (ps3fb.is_kicked) {
			ps3fb.is_kicked = 0;
834
			ps3fb_sync(info, 0);	/* single buffer */
G
Geert Uytterhoeven 已提交
835 836
		}
		schedule();
837 838 839 840 841 842
	}
	return 0;
}

static irqreturn_t ps3fb_vsync_interrupt(int irq, void *ptr)
{
843
	struct device *dev = ptr;
844 845 846 847 848 849
	u64 v1;
	int status;
	struct display_head *head = &ps3fb.dinfo->display_head[1];

	status = lv1_gpu_context_intr(ps3fb.context_handle, &v1);
	if (status) {
850 851
		dev_err(dev, "%s: lv1_gpu_context_intr failed: %d\n", __func__,
			status);
852 853 854 855 856 857
		return IRQ_NONE;
	}

	if (v1 & (1 << GPU_INTR_STATUS_VSYNC_1)) {
		/* VSYNC */
		ps3fb.vblank_count = head->vblank_count;
G
Geert Uytterhoeven 已提交
858 859 860 861 862
		if (ps3fb.task && !ps3fb.is_blanked &&
		    !atomic_read(&ps3fb.ext_flip)) {
			ps3fb.is_kicked = 1;
			wake_up_process(ps3fb.task);
		}
863 864 865 866 867 868 869
		wake_up_interruptible(&ps3fb.wait_vsync);
	}

	return IRQ_HANDLED;
}


870
static int ps3fb_vsync_settings(struct gpu_driver_info *dinfo,
871
				struct device *dev)
872 873 874
{
	int error;

875 876 877 878 879
	dev_dbg(dev, "version_driver:%x\n", dinfo->version_driver);
	dev_dbg(dev, "irq outlet:%x\n", dinfo->irq.irq_outlet);
	dev_dbg(dev,
		"version_gpu: %x memory_size: %x ch: %x core_freq: %d "
		"mem_freq:%d\n",
880 881 882 883
		dinfo->version_gpu, dinfo->memory_size, dinfo->hardware_channel,
		dinfo->nvcore_frequency/1000000, dinfo->memory_frequency/1000000);

	if (dinfo->version_driver != GPU_DRIVER_INFO_VERSION) {
884 885
		dev_err(dev, "%s: version_driver err:%x\n", __func__,
			dinfo->version_driver);
886 887 888
		return -EINVAL;
	}

889 890
	error = ps3_irq_plug_setup(PS3_BINDING_CPU_ANY, dinfo->irq.irq_outlet,
				   &ps3fb.irq_no);
891
	if (error) {
892
		dev_err(dev, "%s: ps3_alloc_irq failed %d\n", __func__, error);
893 894 895 896
		return error;
	}

	error = request_irq(ps3fb.irq_no, ps3fb_vsync_interrupt, IRQF_DISABLED,
897
			    DEVICE_NAME, dev);
898
	if (error) {
899
		dev_err(dev, "%s: request_irq failed %d\n", __func__, error);
900
		ps3_irq_plug_destroy(ps3fb.irq_no);
901 902 903 904 905 906 907 908
		return error;
	}

	dinfo->irq.mask = (1 << GPU_INTR_STATUS_VSYNC_1) |
			  (1 << GPU_INTR_STATUS_FLIP_1);
	return 0;
}

909
static int ps3fb_xdr_settings(u64 xdr_lpar, struct device *dev)
910 911 912 913 914 915
{
	int status;

	status = lv1_gpu_context_iomap(ps3fb.context_handle, GPU_IOIF,
				       xdr_lpar, ps3fb_videomemory.size, 0);
	if (status) {
916 917
		dev_err(dev, "%s: lv1_gpu_context_iomap failed: %d\n",
			__func__, status);
918 919
		return -ENXIO;
	}
920 921
	dev_dbg(dev,
		"video:%p xdr_ea:%p ioif:%lx lpar:%lx phys:%lx size:%lx\n",
922 923 924 925 926
		ps3fb_videomemory.address, ps3fb.xdr_ea, GPU_IOIF, xdr_lpar,
		virt_to_abs(ps3fb.xdr_ea), ps3fb_videomemory.size);

	status = lv1_gpu_context_attribute(ps3fb.context_handle,
					   L1GPU_CONTEXT_ATTRIBUTE_FB_SETUP,
927 928 929
					   xdr_lpar + ps3fb.xdr_size,
					   GPU_CMD_BUF_SIZE,
					   GPU_IOIF + ps3fb.xdr_size, 0);
930
	if (status) {
931 932 933
		dev_err(dev,
			"%s: lv1_gpu_context_attribute FB_SETUP failed: %d\n",
			__func__, status);
934 935 936 937 938 939 940 941
		return -ENXIO;
	}
	return 0;
}

static struct fb_ops ps3fb_ops = {
	.fb_open	= ps3fb_open,
	.fb_release	= ps3fb_release,
942 943
	.fb_read        = fb_sys_read,
	.fb_write       = fb_sys_write,
944 945 946
	.fb_check_var	= ps3fb_check_var,
	.fb_set_par	= ps3fb_set_par,
	.fb_setcolreg	= ps3fb_setcolreg,
947 948 949
	.fb_fillrect	= sys_fillrect,
	.fb_copyarea	= sys_copyarea,
	.fb_imageblit	= sys_imageblit,
950 951 952 953 954 955 956
	.fb_mmap	= ps3fb_mmap,
	.fb_blank	= ps3fb_blank,
	.fb_ioctl	= ps3fb_ioctl,
	.fb_compat_ioctl = ps3fb_ioctl
};

static struct fb_fix_screeninfo ps3fb_fix __initdata = {
957
	.id =		DEVICE_NAME,
958 959 960 961 962
	.type =		FB_TYPE_PACKED_PIXELS,
	.visual =	FB_VISUAL_TRUECOLOR,
	.accel =	FB_ACCEL_NONE,
};

963
static int ps3fb_set_sync(struct device *dev)
964 965 966 967 968 969 970 971
{
	int status;

#ifdef HEAD_A
	status = lv1_gpu_context_attribute(0x0,
					   L1GPU_CONTEXT_ATTRIBUTE_DISPLAY_SYNC,
					   0, L1GPU_DISPLAY_SYNC_VSYNC, 0, 0);
	if (status) {
972 973 974 975
		dev_err(dev,
			"%s: lv1_gpu_context_attribute DISPLAY_SYNC failed: "
			"%d\n",
			__func__, status);
976 977 978 979 980 981 982 983 984
		return -1;
	}
#endif
#ifdef HEAD_B
	status = lv1_gpu_context_attribute(0x0,
					   L1GPU_CONTEXT_ATTRIBUTE_DISPLAY_SYNC,
					   1, L1GPU_DISPLAY_SYNC_VSYNC, 0, 0);

	if (status) {
985 986 987 988
		dev_err(dev,
			"%s: lv1_gpu_context_attribute DISPLAY_SYNC failed: "
			"%d\n",
			__func__, status);
989 990 991 992 993 994 995
		return -1;
	}
#endif
	return 0;
}

static int __devinit ps3fb_probe(struct ps3_system_bus_device *dev)
996 997
{
	struct fb_info *info;
998
	struct ps3fb_par *par;
999
	int retval = -ENOMEM;
1000
	u32 xres, yres;
1001 1002 1003 1004 1005 1006
	u64 ddr_lpar = 0;
	u64 lpar_dma_control = 0;
	u64 lpar_driver_info = 0;
	u64 lpar_reports = 0;
	u64 lpar_reports_size = 0;
	u64 xdr_lpar;
1007
	int status, res_index;
1008
	unsigned long offset;
G
Geert Uytterhoeven 已提交
1009
	struct task_struct *task;
1010

1011 1012
	status = ps3_open_hv_device(dev);
	if (status) {
1013 1014
		dev_err(&dev->core, "%s: ps3_open_hv_device failed\n",
			__func__);
1015 1016 1017 1018 1019
		goto err;
	}

	if (!ps3fb_mode)
		ps3fb_mode = ps3av_get_mode();
1020
	dev_dbg(&dev->core, "ps3av_mode:%d\n", ps3fb_mode);
1021 1022 1023

	if (ps3fb_mode > 0 &&
	    !ps3av_video_mode2res(ps3fb_mode, &xres, &yres)) {
1024 1025
		res_index = ps3fb_get_res_table(xres, yres, ps3fb_mode);
		dev_dbg(&dev->core, "res_index:%d\n", res_index);
1026
	} else
1027
		res_index = GPU_RES_INDEX;
1028 1029 1030 1031 1032

	atomic_set(&ps3fb.f_count, -1);	/* fbcon opens ps3fb */
	atomic_set(&ps3fb.ext_flip, 0);	/* for flip with vsync */
	init_waitqueue_head(&ps3fb.wait_vsync);

1033
	ps3fb_set_sync(&dev->core);
1034

1035 1036 1037 1038
	/* get gpu context handle */
	status = lv1_gpu_memory_allocate(DDR_SIZE, 0, 0, 0, 0,
					 &ps3fb.memory_handle, &ddr_lpar);
	if (status) {
1039 1040
		dev_err(&dev->core, "%s: lv1_gpu_memory_allocate failed: %d\n",
			__func__, status);
1041 1042
		goto err;
	}
1043
	dev_dbg(&dev->core, "ddr:lpar:0x%lx\n", ddr_lpar);
1044 1045 1046 1047 1048 1049

	status = lv1_gpu_context_allocate(ps3fb.memory_handle, 0,
					  &ps3fb.context_handle,
					  &lpar_dma_control, &lpar_driver_info,
					  &lpar_reports, &lpar_reports_size);
	if (status) {
1050 1051 1052
		dev_err(&dev->core,
			"%s: lv1_gpu_context_attribute failed: %d\n", __func__,
			status);
1053 1054 1055 1056 1057 1058
		goto err_gpu_memory_free;
	}

	/* vsync interrupt */
	ps3fb.dinfo = ioremap(lpar_driver_info, 128 * 1024);
	if (!ps3fb.dinfo) {
1059
		dev_err(&dev->core, "%s: ioremap failed\n", __func__);
1060 1061 1062
		goto err_gpu_context_free;
	}

1063
	retval = ps3fb_vsync_settings(ps3fb.dinfo, &dev->core);
1064 1065 1066
	if (retval)
		goto err_iounmap_dinfo;

1067
	/* XDR frame buffer */
1068 1069
	ps3fb.xdr_ea = ps3fb_videomemory.address;
	xdr_lpar = ps3_mm_phys_to_lpar(__pa(ps3fb.xdr_ea));
1070 1071 1072 1073 1074 1075 1076

	/* Clear memory to prevent kernel info leakage into userspace */
	memset(ps3fb.xdr_ea, 0, ps3fb_videomemory.size);

	/* The GPU command buffer is at the end of video memory */
	ps3fb.xdr_size = ps3fb_videomemory.size - GPU_CMD_BUF_SIZE;

1077
	retval = ps3fb_xdr_settings(xdr_lpar, &dev->core);
1078 1079 1080
	if (retval)
		goto err_free_irq;

1081
	info = framebuffer_alloc(sizeof(struct ps3fb_par), &dev->core);
1082 1083 1084
	if (!info)
		goto err_free_irq;

1085 1086 1087 1088 1089 1090 1091
	par = info->par;
	par->mode_id = ~ps3fb_mode;	/* != ps3fb_mode, to trigger change */
	par->new_mode_id = ps3fb_mode;
	par->res_index = res_index;
	par->num_frames = 1;

	offset = VFB_OFF(res_index);
1092 1093 1094 1095
	info->screen_base = (char __iomem *)ps3fb.xdr_ea + offset;
	info->fbops = &ps3fb_ops;

	info->fix = ps3fb_fix;
1096 1097
	info->fix.smem_start = virt_to_abs(ps3fb.xdr_ea) + offset;
	info->fix.smem_len = ps3fb.xdr_size - offset;
1098
	info->pseudo_palette = par->pseudo_palette;
1099
	info->flags = FBINFO_DEFAULT | FBINFO_READS_FAST;
1100 1101 1102 1103 1104 1105

	retval = fb_alloc_cmap(&info->cmap, 256, 0);
	if (retval < 0)
		goto err_framebuffer_release;

	if (!fb_find_mode(&info->var, info, mode_option, ps3fb_modedb,
1106 1107
			  ARRAY_SIZE(ps3fb_modedb),
			  ps3fb_default_mode(par->new_mode_id), 32)) {
1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118
		retval = -EINVAL;
		goto err_fb_dealloc;
	}

	fb_videomode_to_modelist(ps3fb_modedb, ARRAY_SIZE(ps3fb_modedb),
				 &info->modelist);

	retval = register_framebuffer(info);
	if (retval < 0)
		goto err_fb_dealloc;

1119
	dev->core.driver_data = info;
1120

1121 1122
	dev_info(info->device, "%s %s, using %lu KiB of video memory\n",
		 dev_driver_string(info->dev), info->dev->bus_id,
1123
		 ps3fb.xdr_size >> 10);
1124

1125
	task = kthread_run(ps3fbd, info, DEVICE_NAME);
G
Geert Uytterhoeven 已提交
1126 1127 1128 1129 1130 1131
	if (IS_ERR(task)) {
		retval = PTR_ERR(task);
		goto err_unregister_framebuffer;
	}

	ps3fb.task = task;
1132
	ps3av_register_flip_ctl(ps3fb_flip_ctl, &ps3fb);
G
Geert Uytterhoeven 已提交
1133

1134 1135
	return 0;

G
Geert Uytterhoeven 已提交
1136 1137
err_unregister_framebuffer:
	unregister_framebuffer(info);
1138 1139 1140 1141 1142
err_fb_dealloc:
	fb_dealloc_cmap(&info->cmap);
err_framebuffer_release:
	framebuffer_release(info);
err_free_irq:
1143
	free_irq(ps3fb.irq_no, dev);
1144
	ps3_irq_plug_destroy(ps3fb.irq_no);
1145 1146 1147 1148 1149 1150 1151 1152 1153 1154
err_iounmap_dinfo:
	iounmap((u8 __iomem *)ps3fb.dinfo);
err_gpu_context_free:
	lv1_gpu_context_free(ps3fb.context_handle);
err_gpu_memory_free:
	lv1_gpu_memory_free(ps3fb.memory_handle);
err:
	return retval;
}

1155
static int ps3fb_shutdown(struct ps3_system_bus_device *dev)
1156
{
1157 1158 1159
	int status;
	struct fb_info *info = dev->core.driver_data;

1160
	dev_dbg(&dev->core, " -> %s:%d\n", __func__, __LINE__);
1161 1162

	ps3fb_flip_ctl(0, &ps3fb);	/* flip off */
1163 1164
	ps3fb.dinfo->irq.mask = 0;

1165 1166 1167 1168 1169
	if (info) {
		unregister_framebuffer(info);
		fb_dealloc_cmap(&info->cmap);
		framebuffer_release(info);
	}
1170

1171
	ps3av_register_flip_ctl(NULL, NULL);
G
Geert Uytterhoeven 已提交
1172 1173 1174 1175 1176
	if (ps3fb.task) {
		struct task_struct *task = ps3fb.task;
		ps3fb.task = NULL;
		kthread_stop(task);
	}
1177
	if (ps3fb.irq_no) {
1178
		free_irq(ps3fb.irq_no, dev);
1179
		ps3_irq_plug_destroy(ps3fb.irq_no);
1180 1181 1182 1183 1184
	}
	iounmap((u8 __iomem *)ps3fb.dinfo);

	status = lv1_gpu_context_free(ps3fb.context_handle);
	if (status)
1185 1186
		dev_dbg(&dev->core, "lv1_gpu_context_free failed: %d\n",
			status);
1187 1188 1189

	status = lv1_gpu_memory_free(ps3fb.memory_handle);
	if (status)
1190 1191
		dev_dbg(&dev->core, "lv1_gpu_memory_free failed: %d\n",
			status);
1192

1193
	ps3_close_hv_device(dev);
1194
	dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__);
1195 1196 1197 1198

	return 0;
}

1199 1200 1201 1202 1203 1204 1205
static struct ps3_system_bus_driver ps3fb_driver = {
	.match_id	= PS3_MATCH_ID_GRAPHICS,
	.core.name	= DEVICE_NAME,
	.core.owner	= THIS_MODULE,
	.probe		= ps3fb_probe,
	.remove		= ps3fb_shutdown,
	.shutdown	= ps3fb_shutdown,
1206 1207
};

1208
static int __init ps3fb_setup(void)
1209
{
1210
	char *options;
1211

1212
#ifdef MODULE
1213 1214 1215
	return 0;
#endif

1216 1217
	if (fb_get_options(DEVICE_NAME, &options))
		return -ENXIO;
1218

1219 1220
	if (!options || !*options)
		return 0;
1221

1222 1223
	while (1) {
		char *this_opt = strsep(&options, ",");
1224

1225 1226 1227 1228 1229 1230 1231 1232
		if (!this_opt)
			break;
		if (!*this_opt)
			continue;
		if (!strncmp(this_opt, "mode:", 5))
			ps3fb_mode = simple_strtoul(this_opt + 5, NULL, 0);
		else
			mode_option = this_opt;
1233
	}
1234 1235
	return 0;
}
1236

1237 1238 1239 1240
static int __init ps3fb_init(void)
{
	if (!ps3fb_videomemory.address ||  ps3fb_setup())
		return -ENXIO;
1241

1242
	return ps3_system_bus_driver_register(&ps3fb_driver);
1243 1244 1245 1246
}

static void __exit ps3fb_exit(void)
{
1247
	pr_debug(" -> %s:%d\n", __func__, __LINE__);
1248
	ps3_system_bus_driver_unregister(&ps3fb_driver);
1249
	pr_debug(" <- %s:%d\n", __func__, __LINE__);
1250 1251
}

1252
module_init(ps3fb_init);
1253 1254 1255
module_exit(ps3fb_exit);

MODULE_LICENSE("GPL");
1256 1257 1258
MODULE_DESCRIPTION("PS3 GPU Frame Buffer Driver");
MODULE_AUTHOR("Sony Computer Entertainment Inc.");
MODULE_ALIAS(PS3_MODULE_ALIAS_GRAPHICS);