162306a36Sopenharmony_ci/* 262306a36Sopenharmony_ci * linux/drivers/video/ps3fb.c -- PS3 GPU frame buffer device 362306a36Sopenharmony_ci * 462306a36Sopenharmony_ci * Copyright (C) 2006 Sony Computer Entertainment Inc. 562306a36Sopenharmony_ci * Copyright 2006, 2007 Sony Corporation 662306a36Sopenharmony_ci * 762306a36Sopenharmony_ci * This file is based on : 862306a36Sopenharmony_ci * 962306a36Sopenharmony_ci * linux/drivers/video/vfb.c -- Virtual frame buffer device 1062306a36Sopenharmony_ci * 1162306a36Sopenharmony_ci * Copyright (C) 2002 James Simmons 1262306a36Sopenharmony_ci * 1362306a36Sopenharmony_ci * Copyright (C) 1997 Geert Uytterhoeven 1462306a36Sopenharmony_ci * 1562306a36Sopenharmony_ci * This file is subject to the terms and conditions of the GNU General Public 1662306a36Sopenharmony_ci * License. See the file COPYING in the main directory of this archive for 1762306a36Sopenharmony_ci * more details. 1862306a36Sopenharmony_ci */ 1962306a36Sopenharmony_ci 2062306a36Sopenharmony_ci#include <linux/module.h> 2162306a36Sopenharmony_ci#include <linux/kernel.h> 2262306a36Sopenharmony_ci#include <linux/errno.h> 2362306a36Sopenharmony_ci#include <linux/string.h> 2462306a36Sopenharmony_ci#include <linux/mm.h> 2562306a36Sopenharmony_ci#include <linux/interrupt.h> 2662306a36Sopenharmony_ci#include <linux/console.h> 2762306a36Sopenharmony_ci#include <linux/ioctl.h> 2862306a36Sopenharmony_ci#include <linux/kthread.h> 2962306a36Sopenharmony_ci#include <linux/freezer.h> 3062306a36Sopenharmony_ci#include <linux/uaccess.h> 3162306a36Sopenharmony_ci#include <linux/fb.h> 3262306a36Sopenharmony_ci#include <linux/fbcon.h> 3362306a36Sopenharmony_ci#include <linux/init.h> 3462306a36Sopenharmony_ci 3562306a36Sopenharmony_ci#include <asm/cell-regs.h> 3662306a36Sopenharmony_ci#include <asm/lv1call.h> 3762306a36Sopenharmony_ci#include <asm/ps3av.h> 3862306a36Sopenharmony_ci#include <asm/ps3fb.h> 3962306a36Sopenharmony_ci#include <asm/ps3.h> 4062306a36Sopenharmony_ci#include <asm/ps3gpu.h> 4162306a36Sopenharmony_ci 4262306a36Sopenharmony_ci 4362306a36Sopenharmony_ci#define DEVICE_NAME "ps3fb" 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci#define GPU_CMD_BUF_SIZE (2 * 1024 * 1024) 4662306a36Sopenharmony_ci#define GPU_FB_START (64 * 1024) 4762306a36Sopenharmony_ci#define GPU_IOIF (0x0d000000UL) 4862306a36Sopenharmony_ci#define GPU_ALIGN_UP(x) ALIGN((x), 64) 4962306a36Sopenharmony_ci#define GPU_MAX_LINE_LENGTH (65536 - 64) 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci#define GPU_INTR_STATUS_VSYNC_0 0 /* vsync on head A */ 5262306a36Sopenharmony_ci#define GPU_INTR_STATUS_VSYNC_1 1 /* vsync on head B */ 5362306a36Sopenharmony_ci#define GPU_INTR_STATUS_FLIP_0 3 /* flip head A */ 5462306a36Sopenharmony_ci#define GPU_INTR_STATUS_FLIP_1 4 /* flip head B */ 5562306a36Sopenharmony_ci#define GPU_INTR_STATUS_QUEUE_0 5 /* queue head A */ 5662306a36Sopenharmony_ci#define GPU_INTR_STATUS_QUEUE_1 6 /* queue head B */ 5762306a36Sopenharmony_ci 5862306a36Sopenharmony_ci#define GPU_DRIVER_INFO_VERSION 0x211 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci/* gpu internals */ 6162306a36Sopenharmony_cistruct display_head { 6262306a36Sopenharmony_ci u64 be_time_stamp; 6362306a36Sopenharmony_ci u32 status; 6462306a36Sopenharmony_ci u32 offset; 6562306a36Sopenharmony_ci u32 res1; 6662306a36Sopenharmony_ci u32 res2; 6762306a36Sopenharmony_ci u32 field; 6862306a36Sopenharmony_ci u32 reserved1; 6962306a36Sopenharmony_ci 7062306a36Sopenharmony_ci u64 res3; 7162306a36Sopenharmony_ci u32 raster; 7262306a36Sopenharmony_ci 7362306a36Sopenharmony_ci u64 vblank_count; 7462306a36Sopenharmony_ci u32 field_vsync; 7562306a36Sopenharmony_ci u32 reserved2; 7662306a36Sopenharmony_ci}; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_cistruct gpu_irq { 7962306a36Sopenharmony_ci u32 irq_outlet; 8062306a36Sopenharmony_ci u32 status; 8162306a36Sopenharmony_ci u32 mask; 8262306a36Sopenharmony_ci u32 video_cause; 8362306a36Sopenharmony_ci u32 graph_cause; 8462306a36Sopenharmony_ci u32 user_cause; 8562306a36Sopenharmony_ci 8662306a36Sopenharmony_ci u32 res1; 8762306a36Sopenharmony_ci u64 res2; 8862306a36Sopenharmony_ci 8962306a36Sopenharmony_ci u32 reserved[4]; 9062306a36Sopenharmony_ci}; 9162306a36Sopenharmony_ci 9262306a36Sopenharmony_cistruct gpu_driver_info { 9362306a36Sopenharmony_ci u32 version_driver; 9462306a36Sopenharmony_ci u32 version_gpu; 9562306a36Sopenharmony_ci u32 memory_size; 9662306a36Sopenharmony_ci u32 hardware_channel; 9762306a36Sopenharmony_ci 9862306a36Sopenharmony_ci u32 nvcore_frequency; 9962306a36Sopenharmony_ci u32 memory_frequency; 10062306a36Sopenharmony_ci 10162306a36Sopenharmony_ci u32 reserved[1063]; 10262306a36Sopenharmony_ci struct display_head display_head[8]; 10362306a36Sopenharmony_ci struct gpu_irq irq; 10462306a36Sopenharmony_ci}; 10562306a36Sopenharmony_ci 10662306a36Sopenharmony_cistruct ps3fb_priv { 10762306a36Sopenharmony_ci unsigned int irq_no; 10862306a36Sopenharmony_ci 10962306a36Sopenharmony_ci u64 context_handle, memory_handle; 11062306a36Sopenharmony_ci struct gpu_driver_info *dinfo; 11162306a36Sopenharmony_ci 11262306a36Sopenharmony_ci u64 vblank_count; /* frame count */ 11362306a36Sopenharmony_ci wait_queue_head_t wait_vsync; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci atomic_t ext_flip; /* on/off flip with vsync */ 11662306a36Sopenharmony_ci atomic_t f_count; /* fb_open count */ 11762306a36Sopenharmony_ci int is_blanked; 11862306a36Sopenharmony_ci int is_kicked; 11962306a36Sopenharmony_ci struct task_struct *task; 12062306a36Sopenharmony_ci}; 12162306a36Sopenharmony_cistatic struct ps3fb_priv ps3fb; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_cistruct ps3fb_par { 12462306a36Sopenharmony_ci u32 pseudo_palette[16]; 12562306a36Sopenharmony_ci int mode_id, new_mode_id; 12662306a36Sopenharmony_ci unsigned int num_frames; /* num of frame buffers */ 12762306a36Sopenharmony_ci unsigned int width; 12862306a36Sopenharmony_ci unsigned int height; 12962306a36Sopenharmony_ci unsigned int ddr_line_length; 13062306a36Sopenharmony_ci unsigned int ddr_frame_size; 13162306a36Sopenharmony_ci unsigned int xdr_frame_size; 13262306a36Sopenharmony_ci unsigned int full_offset; /* start of fullscreen DDR fb */ 13362306a36Sopenharmony_ci unsigned int fb_offset; /* start of actual DDR fb */ 13462306a36Sopenharmony_ci unsigned int pan_offset; 13562306a36Sopenharmony_ci}; 13662306a36Sopenharmony_ci 13762306a36Sopenharmony_ci 13862306a36Sopenharmony_ci#define FIRST_NATIVE_MODE_INDEX 10 13962306a36Sopenharmony_ci 14062306a36Sopenharmony_cistatic const struct fb_videomode ps3fb_modedb[] = { 14162306a36Sopenharmony_ci /* 60 Hz broadcast modes (modes "1" to "5") */ 14262306a36Sopenharmony_ci { 14362306a36Sopenharmony_ci /* 480i */ 14462306a36Sopenharmony_ci "480i", 60, 576, 384, 74074, 130, 89, 78, 57, 63, 6, 14562306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_INTERLACED 14662306a36Sopenharmony_ci }, { 14762306a36Sopenharmony_ci /* 480p */ 14862306a36Sopenharmony_ci "480p", 60, 576, 384, 37037, 130, 89, 78, 57, 63, 6, 14962306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED 15062306a36Sopenharmony_ci }, { 15162306a36Sopenharmony_ci /* 720p */ 15262306a36Sopenharmony_ci "720p", 60, 1124, 644, 13481, 298, 148, 57, 44, 80, 5, 15362306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED 15462306a36Sopenharmony_ci }, { 15562306a36Sopenharmony_ci /* 1080i */ 15662306a36Sopenharmony_ci "1080i", 60, 1688, 964, 13481, 264, 160, 94, 62, 88, 5, 15762306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_INTERLACED 15862306a36Sopenharmony_ci }, { 15962306a36Sopenharmony_ci /* 1080p */ 16062306a36Sopenharmony_ci "1080p", 60, 1688, 964, 6741, 264, 160, 94, 62, 88, 5, 16162306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED 16262306a36Sopenharmony_ci }, 16362306a36Sopenharmony_ci 16462306a36Sopenharmony_ci /* 50 Hz broadcast modes (modes "6" to "10") */ 16562306a36Sopenharmony_ci { 16662306a36Sopenharmony_ci /* 576i */ 16762306a36Sopenharmony_ci "576i", 50, 576, 460, 74074, 142, 83, 97, 63, 63, 5, 16862306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_INTERLACED 16962306a36Sopenharmony_ci }, { 17062306a36Sopenharmony_ci /* 576p */ 17162306a36Sopenharmony_ci "576p", 50, 576, 460, 37037, 142, 83, 97, 63, 63, 5, 17262306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED 17362306a36Sopenharmony_ci }, { 17462306a36Sopenharmony_ci /* 720p */ 17562306a36Sopenharmony_ci "720p", 50, 1124, 644, 13468, 298, 478, 57, 44, 80, 5, 17662306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED 17762306a36Sopenharmony_ci }, { 17862306a36Sopenharmony_ci /* 1080i */ 17962306a36Sopenharmony_ci "1080i", 50, 1688, 964, 13468, 264, 600, 94, 62, 88, 5, 18062306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_INTERLACED 18162306a36Sopenharmony_ci }, { 18262306a36Sopenharmony_ci /* 1080p */ 18362306a36Sopenharmony_ci "1080p", 50, 1688, 964, 6734, 264, 600, 94, 62, 88, 5, 18462306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED 18562306a36Sopenharmony_ci }, 18662306a36Sopenharmony_ci 18762306a36Sopenharmony_ci [FIRST_NATIVE_MODE_INDEX] = 18862306a36Sopenharmony_ci /* 60 Hz broadcast modes (full resolution versions of modes "1" to "5") */ 18962306a36Sopenharmony_ci { 19062306a36Sopenharmony_ci /* 480if */ 19162306a36Sopenharmony_ci "480if", 60, 720, 480, 74074, 58, 17, 30, 9, 63, 6, 19262306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_INTERLACED 19362306a36Sopenharmony_ci }, { 19462306a36Sopenharmony_ci /* 480pf */ 19562306a36Sopenharmony_ci "480pf", 60, 720, 480, 37037, 58, 17, 30, 9, 63, 6, 19662306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED 19762306a36Sopenharmony_ci }, { 19862306a36Sopenharmony_ci /* 720pf */ 19962306a36Sopenharmony_ci "720pf", 60, 1280, 720, 13481, 220, 70, 19, 6, 80, 5, 20062306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED 20162306a36Sopenharmony_ci }, { 20262306a36Sopenharmony_ci /* 1080if */ 20362306a36Sopenharmony_ci "1080if", 60, 1920, 1080, 13481, 148, 44, 36, 4, 88, 5, 20462306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_INTERLACED 20562306a36Sopenharmony_ci }, { 20662306a36Sopenharmony_ci /* 1080pf */ 20762306a36Sopenharmony_ci "1080pf", 60, 1920, 1080, 6741, 148, 44, 36, 4, 88, 5, 20862306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED 20962306a36Sopenharmony_ci }, 21062306a36Sopenharmony_ci 21162306a36Sopenharmony_ci /* 50 Hz broadcast modes (full resolution versions of modes "6" to "10") */ 21262306a36Sopenharmony_ci { 21362306a36Sopenharmony_ci /* 576if */ 21462306a36Sopenharmony_ci "576if", 50, 720, 576, 74074, 70, 11, 39, 5, 63, 5, 21562306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_INTERLACED 21662306a36Sopenharmony_ci }, { 21762306a36Sopenharmony_ci /* 576pf */ 21862306a36Sopenharmony_ci "576pf", 50, 720, 576, 37037, 70, 11, 39, 5, 63, 5, 21962306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED 22062306a36Sopenharmony_ci }, { 22162306a36Sopenharmony_ci /* 720pf */ 22262306a36Sopenharmony_ci "720pf", 50, 1280, 720, 13468, 220, 400, 19, 6, 80, 5, 22362306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED 22462306a36Sopenharmony_ci }, { 22562306a36Sopenharmony_ci /* 1080if */ 22662306a36Sopenharmony_ci "1080if", 50, 1920, 1080, 13468, 148, 484, 36, 4, 88, 5, 22762306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_INTERLACED 22862306a36Sopenharmony_ci }, { 22962306a36Sopenharmony_ci /* 1080pf */ 23062306a36Sopenharmony_ci "1080pf", 50, 1920, 1080, 6734, 148, 484, 36, 4, 88, 5, 23162306a36Sopenharmony_ci FB_SYNC_BROADCAST, FB_VMODE_NONINTERLACED 23262306a36Sopenharmony_ci }, 23362306a36Sopenharmony_ci 23462306a36Sopenharmony_ci /* VESA modes (modes "11" to "13") */ 23562306a36Sopenharmony_ci { 23662306a36Sopenharmony_ci /* WXGA */ 23762306a36Sopenharmony_ci "wxga", 60, 1280, 768, 12924, 160, 24, 29, 3, 136, 6, 23862306a36Sopenharmony_ci 0, FB_VMODE_NONINTERLACED, 23962306a36Sopenharmony_ci FB_MODE_IS_VESA 24062306a36Sopenharmony_ci }, { 24162306a36Sopenharmony_ci /* SXGA */ 24262306a36Sopenharmony_ci "sxga", 60, 1280, 1024, 9259, 248, 48, 38, 1, 112, 3, 24362306a36Sopenharmony_ci FB_SYNC_HOR_HIGH_ACT | FB_SYNC_VERT_HIGH_ACT, FB_VMODE_NONINTERLACED, 24462306a36Sopenharmony_ci FB_MODE_IS_VESA 24562306a36Sopenharmony_ci }, { 24662306a36Sopenharmony_ci /* WUXGA */ 24762306a36Sopenharmony_ci "wuxga", 60, 1920, 1200, 6494, 80, 48, 26, 3, 32, 6, 24862306a36Sopenharmony_ci FB_SYNC_HOR_HIGH_ACT, FB_VMODE_NONINTERLACED, 24962306a36Sopenharmony_ci FB_MODE_IS_VESA 25062306a36Sopenharmony_ci } 25162306a36Sopenharmony_ci}; 25262306a36Sopenharmony_ci 25362306a36Sopenharmony_ci 25462306a36Sopenharmony_ci#define HEAD_A 25562306a36Sopenharmony_ci#define HEAD_B 25662306a36Sopenharmony_ci 25762306a36Sopenharmony_ci#define BPP 4 /* number of bytes per pixel */ 25862306a36Sopenharmony_ci 25962306a36Sopenharmony_ci 26062306a36Sopenharmony_cistatic int ps3fb_mode; 26162306a36Sopenharmony_cimodule_param(ps3fb_mode, int, 0); 26262306a36Sopenharmony_ci 26362306a36Sopenharmony_cistatic char *mode_option; 26462306a36Sopenharmony_ci 26562306a36Sopenharmony_cistatic int ps3fb_cmp_mode(const struct fb_videomode *vmode, 26662306a36Sopenharmony_ci const struct fb_var_screeninfo *var) 26762306a36Sopenharmony_ci{ 26862306a36Sopenharmony_ci long xres, yres, left_margin, right_margin, upper_margin, lower_margin; 26962306a36Sopenharmony_ci long dx, dy; 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci /* maximum values */ 27262306a36Sopenharmony_ci if (var->xres > vmode->xres || var->yres > vmode->yres || 27362306a36Sopenharmony_ci var->pixclock > vmode->pixclock || 27462306a36Sopenharmony_ci var->hsync_len > vmode->hsync_len || 27562306a36Sopenharmony_ci var->vsync_len > vmode->vsync_len) 27662306a36Sopenharmony_ci return -1; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci /* progressive/interlaced must match */ 27962306a36Sopenharmony_ci if ((var->vmode & FB_VMODE_MASK) != vmode->vmode) 28062306a36Sopenharmony_ci return -1; 28162306a36Sopenharmony_ci 28262306a36Sopenharmony_ci /* minimum resolution */ 28362306a36Sopenharmony_ci xres = max(var->xres, 1U); 28462306a36Sopenharmony_ci yres = max(var->yres, 1U); 28562306a36Sopenharmony_ci 28662306a36Sopenharmony_ci /* minimum margins */ 28762306a36Sopenharmony_ci left_margin = max(var->left_margin, vmode->left_margin); 28862306a36Sopenharmony_ci right_margin = max(var->right_margin, vmode->right_margin); 28962306a36Sopenharmony_ci upper_margin = max(var->upper_margin, vmode->upper_margin); 29062306a36Sopenharmony_ci lower_margin = max(var->lower_margin, vmode->lower_margin); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci /* resolution + margins may not exceed native parameters */ 29362306a36Sopenharmony_ci dx = ((long)vmode->left_margin + (long)vmode->xres + 29462306a36Sopenharmony_ci (long)vmode->right_margin) - 29562306a36Sopenharmony_ci (left_margin + xres + right_margin); 29662306a36Sopenharmony_ci if (dx < 0) 29762306a36Sopenharmony_ci return -1; 29862306a36Sopenharmony_ci 29962306a36Sopenharmony_ci dy = ((long)vmode->upper_margin + (long)vmode->yres + 30062306a36Sopenharmony_ci (long)vmode->lower_margin) - 30162306a36Sopenharmony_ci (upper_margin + yres + lower_margin); 30262306a36Sopenharmony_ci if (dy < 0) 30362306a36Sopenharmony_ci return -1; 30462306a36Sopenharmony_ci 30562306a36Sopenharmony_ci /* exact match */ 30662306a36Sopenharmony_ci if (!dx && !dy) 30762306a36Sopenharmony_ci return 0; 30862306a36Sopenharmony_ci 30962306a36Sopenharmony_ci /* resolution difference */ 31062306a36Sopenharmony_ci return (vmode->xres - xres) * (vmode->yres - yres); 31162306a36Sopenharmony_ci} 31262306a36Sopenharmony_ci 31362306a36Sopenharmony_cistatic const struct fb_videomode *ps3fb_native_vmode(enum ps3av_mode_num id) 31462306a36Sopenharmony_ci{ 31562306a36Sopenharmony_ci return &ps3fb_modedb[FIRST_NATIVE_MODE_INDEX + id - 1]; 31662306a36Sopenharmony_ci} 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_cistatic const struct fb_videomode *ps3fb_vmode(int id) 31962306a36Sopenharmony_ci{ 32062306a36Sopenharmony_ci u32 mode = id & PS3AV_MODE_MASK; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci if (mode < PS3AV_MODE_480I || mode > PS3AV_MODE_WUXGA) 32362306a36Sopenharmony_ci return NULL; 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci if (mode <= PS3AV_MODE_1080P50 && !(id & PS3AV_MODE_FULL)) { 32662306a36Sopenharmony_ci /* Non-fullscreen broadcast mode */ 32762306a36Sopenharmony_ci return &ps3fb_modedb[mode - 1]; 32862306a36Sopenharmony_ci } 32962306a36Sopenharmony_ci 33062306a36Sopenharmony_ci return ps3fb_native_vmode(mode); 33162306a36Sopenharmony_ci} 33262306a36Sopenharmony_ci 33362306a36Sopenharmony_cistatic unsigned int ps3fb_find_mode(struct fb_var_screeninfo *var, 33462306a36Sopenharmony_ci u32 *ddr_line_length, u32 *xdr_line_length) 33562306a36Sopenharmony_ci{ 33662306a36Sopenharmony_ci unsigned int id, best_id; 33762306a36Sopenharmony_ci int diff, best_diff; 33862306a36Sopenharmony_ci const struct fb_videomode *vmode; 33962306a36Sopenharmony_ci long gap; 34062306a36Sopenharmony_ci 34162306a36Sopenharmony_ci best_id = 0; 34262306a36Sopenharmony_ci best_diff = INT_MAX; 34362306a36Sopenharmony_ci pr_debug("%s: wanted %u [%u] %u x %u [%u] %u\n", __func__, 34462306a36Sopenharmony_ci var->left_margin, var->xres, var->right_margin, 34562306a36Sopenharmony_ci var->upper_margin, var->yres, var->lower_margin); 34662306a36Sopenharmony_ci for (id = PS3AV_MODE_480I; id <= PS3AV_MODE_WUXGA; id++) { 34762306a36Sopenharmony_ci vmode = ps3fb_native_vmode(id); 34862306a36Sopenharmony_ci diff = ps3fb_cmp_mode(vmode, var); 34962306a36Sopenharmony_ci pr_debug("%s: mode %u: %u [%u] %u x %u [%u] %u: diff = %d\n", 35062306a36Sopenharmony_ci __func__, id, vmode->left_margin, vmode->xres, 35162306a36Sopenharmony_ci vmode->right_margin, vmode->upper_margin, 35262306a36Sopenharmony_ci vmode->yres, vmode->lower_margin, diff); 35362306a36Sopenharmony_ci if (diff < 0) 35462306a36Sopenharmony_ci continue; 35562306a36Sopenharmony_ci if (diff < best_diff) { 35662306a36Sopenharmony_ci best_id = id; 35762306a36Sopenharmony_ci if (!diff) 35862306a36Sopenharmony_ci break; 35962306a36Sopenharmony_ci best_diff = diff; 36062306a36Sopenharmony_ci } 36162306a36Sopenharmony_ci } 36262306a36Sopenharmony_ci 36362306a36Sopenharmony_ci if (!best_id) { 36462306a36Sopenharmony_ci pr_debug("%s: no suitable mode found\n", __func__); 36562306a36Sopenharmony_ci return 0; 36662306a36Sopenharmony_ci } 36762306a36Sopenharmony_ci 36862306a36Sopenharmony_ci id = best_id; 36962306a36Sopenharmony_ci vmode = ps3fb_native_vmode(id); 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci *ddr_line_length = vmode->xres * BPP; 37262306a36Sopenharmony_ci 37362306a36Sopenharmony_ci /* minimum resolution */ 37462306a36Sopenharmony_ci if (!var->xres) 37562306a36Sopenharmony_ci var->xres = 1; 37662306a36Sopenharmony_ci if (!var->yres) 37762306a36Sopenharmony_ci var->yres = 1; 37862306a36Sopenharmony_ci 37962306a36Sopenharmony_ci /* minimum virtual resolution */ 38062306a36Sopenharmony_ci if (var->xres_virtual < var->xres) 38162306a36Sopenharmony_ci var->xres_virtual = var->xres; 38262306a36Sopenharmony_ci if (var->yres_virtual < var->yres) 38362306a36Sopenharmony_ci var->yres_virtual = var->yres; 38462306a36Sopenharmony_ci 38562306a36Sopenharmony_ci /* minimum margins */ 38662306a36Sopenharmony_ci if (var->left_margin < vmode->left_margin) 38762306a36Sopenharmony_ci var->left_margin = vmode->left_margin; 38862306a36Sopenharmony_ci if (var->right_margin < vmode->right_margin) 38962306a36Sopenharmony_ci var->right_margin = vmode->right_margin; 39062306a36Sopenharmony_ci if (var->upper_margin < vmode->upper_margin) 39162306a36Sopenharmony_ci var->upper_margin = vmode->upper_margin; 39262306a36Sopenharmony_ci if (var->lower_margin < vmode->lower_margin) 39362306a36Sopenharmony_ci var->lower_margin = vmode->lower_margin; 39462306a36Sopenharmony_ci 39562306a36Sopenharmony_ci /* extra margins */ 39662306a36Sopenharmony_ci gap = ((long)vmode->left_margin + (long)vmode->xres + 39762306a36Sopenharmony_ci (long)vmode->right_margin) - 39862306a36Sopenharmony_ci ((long)var->left_margin + (long)var->xres + 39962306a36Sopenharmony_ci (long)var->right_margin); 40062306a36Sopenharmony_ci if (gap > 0) { 40162306a36Sopenharmony_ci var->left_margin += gap/2; 40262306a36Sopenharmony_ci var->right_margin += (gap+1)/2; 40362306a36Sopenharmony_ci pr_debug("%s: rounded up H to %u [%u] %u\n", __func__, 40462306a36Sopenharmony_ci var->left_margin, var->xres, var->right_margin); 40562306a36Sopenharmony_ci } 40662306a36Sopenharmony_ci 40762306a36Sopenharmony_ci gap = ((long)vmode->upper_margin + (long)vmode->yres + 40862306a36Sopenharmony_ci (long)vmode->lower_margin) - 40962306a36Sopenharmony_ci ((long)var->upper_margin + (long)var->yres + 41062306a36Sopenharmony_ci (long)var->lower_margin); 41162306a36Sopenharmony_ci if (gap > 0) { 41262306a36Sopenharmony_ci var->upper_margin += gap/2; 41362306a36Sopenharmony_ci var->lower_margin += (gap+1)/2; 41462306a36Sopenharmony_ci pr_debug("%s: rounded up V to %u [%u] %u\n", __func__, 41562306a36Sopenharmony_ci var->upper_margin, var->yres, var->lower_margin); 41662306a36Sopenharmony_ci } 41762306a36Sopenharmony_ci 41862306a36Sopenharmony_ci /* fixed fields */ 41962306a36Sopenharmony_ci var->pixclock = vmode->pixclock; 42062306a36Sopenharmony_ci var->hsync_len = vmode->hsync_len; 42162306a36Sopenharmony_ci var->vsync_len = vmode->vsync_len; 42262306a36Sopenharmony_ci var->sync = vmode->sync; 42362306a36Sopenharmony_ci 42462306a36Sopenharmony_ci if (ps3_compare_firmware_version(1, 9, 0) >= 0) { 42562306a36Sopenharmony_ci *xdr_line_length = GPU_ALIGN_UP(var->xres_virtual * BPP); 42662306a36Sopenharmony_ci if (*xdr_line_length > GPU_MAX_LINE_LENGTH) 42762306a36Sopenharmony_ci *xdr_line_length = GPU_MAX_LINE_LENGTH; 42862306a36Sopenharmony_ci } else 42962306a36Sopenharmony_ci *xdr_line_length = *ddr_line_length; 43062306a36Sopenharmony_ci 43162306a36Sopenharmony_ci if (vmode->sync & FB_SYNC_BROADCAST) { 43262306a36Sopenharmony_ci /* Full broadcast modes have the full mode bit set */ 43362306a36Sopenharmony_ci if (vmode->xres == var->xres && vmode->yres == var->yres) 43462306a36Sopenharmony_ci id |= PS3AV_MODE_FULL; 43562306a36Sopenharmony_ci } 43662306a36Sopenharmony_ci 43762306a36Sopenharmony_ci pr_debug("%s: mode %u\n", __func__, id); 43862306a36Sopenharmony_ci return id; 43962306a36Sopenharmony_ci} 44062306a36Sopenharmony_ci 44162306a36Sopenharmony_cistatic void ps3fb_sync_image(struct device *dev, u64 frame_offset, 44262306a36Sopenharmony_ci u64 dst_offset, u64 src_offset, u32 width, 44362306a36Sopenharmony_ci u32 height, u32 dst_line_length, 44462306a36Sopenharmony_ci u32 src_line_length) 44562306a36Sopenharmony_ci{ 44662306a36Sopenharmony_ci int status; 44762306a36Sopenharmony_ci u64 line_length; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci line_length = dst_line_length; 45062306a36Sopenharmony_ci if (src_line_length != dst_line_length) 45162306a36Sopenharmony_ci line_length |= (u64)src_line_length << 32; 45262306a36Sopenharmony_ci 45362306a36Sopenharmony_ci src_offset += GPU_FB_START; 45462306a36Sopenharmony_ci 45562306a36Sopenharmony_ci mutex_lock(&ps3_gpu_mutex); 45662306a36Sopenharmony_ci status = lv1_gpu_fb_blit(ps3fb.context_handle, dst_offset, 45762306a36Sopenharmony_ci GPU_IOIF + src_offset, 45862306a36Sopenharmony_ci L1GPU_FB_BLIT_WAIT_FOR_COMPLETION | 45962306a36Sopenharmony_ci (width << 16) | height, 46062306a36Sopenharmony_ci line_length); 46162306a36Sopenharmony_ci mutex_unlock(&ps3_gpu_mutex); 46262306a36Sopenharmony_ci 46362306a36Sopenharmony_ci if (status) 46462306a36Sopenharmony_ci dev_err(dev, "%s: lv1_gpu_fb_blit failed: %d\n", __func__, 46562306a36Sopenharmony_ci status); 46662306a36Sopenharmony_ci#ifdef HEAD_A 46762306a36Sopenharmony_ci status = lv1_gpu_display_flip(ps3fb.context_handle, 0, frame_offset); 46862306a36Sopenharmony_ci if (status) 46962306a36Sopenharmony_ci dev_err(dev, "%s: lv1_gpu_display_flip failed: %d\n", __func__, 47062306a36Sopenharmony_ci status); 47162306a36Sopenharmony_ci#endif 47262306a36Sopenharmony_ci#ifdef HEAD_B 47362306a36Sopenharmony_ci status = lv1_gpu_display_flip(ps3fb.context_handle, 1, frame_offset); 47462306a36Sopenharmony_ci if (status) 47562306a36Sopenharmony_ci dev_err(dev, "%s: lv1_gpu_display_flip failed: %d\n", __func__, 47662306a36Sopenharmony_ci status); 47762306a36Sopenharmony_ci#endif 47862306a36Sopenharmony_ci} 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_cistatic int ps3fb_sync(struct fb_info *info, u32 frame) 48162306a36Sopenharmony_ci{ 48262306a36Sopenharmony_ci struct ps3fb_par *par = info->par; 48362306a36Sopenharmony_ci int error = 0; 48462306a36Sopenharmony_ci u64 ddr_base, xdr_base; 48562306a36Sopenharmony_ci 48662306a36Sopenharmony_ci if (frame > par->num_frames - 1) { 48762306a36Sopenharmony_ci dev_dbg(info->device, "%s: invalid frame number (%u)\n", 48862306a36Sopenharmony_ci __func__, frame); 48962306a36Sopenharmony_ci error = -EINVAL; 49062306a36Sopenharmony_ci goto out; 49162306a36Sopenharmony_ci } 49262306a36Sopenharmony_ci 49362306a36Sopenharmony_ci xdr_base = frame * par->xdr_frame_size; 49462306a36Sopenharmony_ci ddr_base = frame * par->ddr_frame_size; 49562306a36Sopenharmony_ci 49662306a36Sopenharmony_ci ps3fb_sync_image(info->device, ddr_base + par->full_offset, 49762306a36Sopenharmony_ci ddr_base + par->fb_offset, xdr_base + par->pan_offset, 49862306a36Sopenharmony_ci par->width, par->height, par->ddr_line_length, 49962306a36Sopenharmony_ci info->fix.line_length); 50062306a36Sopenharmony_ci 50162306a36Sopenharmony_ciout: 50262306a36Sopenharmony_ci return error; 50362306a36Sopenharmony_ci} 50462306a36Sopenharmony_ci 50562306a36Sopenharmony_cistatic int ps3fb_open(struct fb_info *info, int user) 50662306a36Sopenharmony_ci{ 50762306a36Sopenharmony_ci atomic_inc(&ps3fb.f_count); 50862306a36Sopenharmony_ci return 0; 50962306a36Sopenharmony_ci} 51062306a36Sopenharmony_ci 51162306a36Sopenharmony_cistatic int ps3fb_release(struct fb_info *info, int user) 51262306a36Sopenharmony_ci{ 51362306a36Sopenharmony_ci if (atomic_dec_and_test(&ps3fb.f_count)) { 51462306a36Sopenharmony_ci if (atomic_read(&ps3fb.ext_flip)) { 51562306a36Sopenharmony_ci atomic_set(&ps3fb.ext_flip, 0); 51662306a36Sopenharmony_ci if (console_trylock()) { 51762306a36Sopenharmony_ci ps3fb_sync(info, 0); /* single buffer */ 51862306a36Sopenharmony_ci console_unlock(); 51962306a36Sopenharmony_ci } 52062306a36Sopenharmony_ci } 52162306a36Sopenharmony_ci } 52262306a36Sopenharmony_ci return 0; 52362306a36Sopenharmony_ci} 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_ci /* 52662306a36Sopenharmony_ci * Setting the video mode has been split into two parts. 52762306a36Sopenharmony_ci * First part, xxxfb_check_var, must not write anything 52862306a36Sopenharmony_ci * to hardware, it should only verify and adjust var. 52962306a36Sopenharmony_ci * This means it doesn't alter par but it does use hardware 53062306a36Sopenharmony_ci * data from it to check this var. 53162306a36Sopenharmony_ci */ 53262306a36Sopenharmony_ci 53362306a36Sopenharmony_cistatic int ps3fb_check_var(struct fb_var_screeninfo *var, struct fb_info *info) 53462306a36Sopenharmony_ci{ 53562306a36Sopenharmony_ci u32 xdr_line_length, ddr_line_length; 53662306a36Sopenharmony_ci int mode; 53762306a36Sopenharmony_ci 53862306a36Sopenharmony_ci mode = ps3fb_find_mode(var, &ddr_line_length, &xdr_line_length); 53962306a36Sopenharmony_ci if (!mode) 54062306a36Sopenharmony_ci return -EINVAL; 54162306a36Sopenharmony_ci 54262306a36Sopenharmony_ci /* Virtual screen */ 54362306a36Sopenharmony_ci if (var->xres_virtual > xdr_line_length / BPP) { 54462306a36Sopenharmony_ci dev_dbg(info->device, 54562306a36Sopenharmony_ci "Horizontal virtual screen size too large\n"); 54662306a36Sopenharmony_ci return -EINVAL; 54762306a36Sopenharmony_ci } 54862306a36Sopenharmony_ci 54962306a36Sopenharmony_ci if (var->xoffset + var->xres > var->xres_virtual || 55062306a36Sopenharmony_ci var->yoffset + var->yres > var->yres_virtual) { 55162306a36Sopenharmony_ci dev_dbg(info->device, "panning out-of-range\n"); 55262306a36Sopenharmony_ci return -EINVAL; 55362306a36Sopenharmony_ci } 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci /* We support ARGB8888 only */ 55662306a36Sopenharmony_ci if (var->bits_per_pixel > 32 || var->grayscale || 55762306a36Sopenharmony_ci var->red.offset > 16 || var->green.offset > 8 || 55862306a36Sopenharmony_ci var->blue.offset > 0 || var->transp.offset > 24 || 55962306a36Sopenharmony_ci var->red.length > 8 || var->green.length > 8 || 56062306a36Sopenharmony_ci var->blue.length > 8 || var->transp.length > 8 || 56162306a36Sopenharmony_ci var->red.msb_right || var->green.msb_right || 56262306a36Sopenharmony_ci var->blue.msb_right || var->transp.msb_right || var->nonstd) { 56362306a36Sopenharmony_ci dev_dbg(info->device, "We support ARGB8888 only\n"); 56462306a36Sopenharmony_ci return -EINVAL; 56562306a36Sopenharmony_ci } 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci var->bits_per_pixel = 32; 56862306a36Sopenharmony_ci var->red.offset = 16; 56962306a36Sopenharmony_ci var->green.offset = 8; 57062306a36Sopenharmony_ci var->blue.offset = 0; 57162306a36Sopenharmony_ci var->transp.offset = 24; 57262306a36Sopenharmony_ci var->red.length = 8; 57362306a36Sopenharmony_ci var->green.length = 8; 57462306a36Sopenharmony_ci var->blue.length = 8; 57562306a36Sopenharmony_ci var->transp.length = 8; 57662306a36Sopenharmony_ci var->red.msb_right = 0; 57762306a36Sopenharmony_ci var->green.msb_right = 0; 57862306a36Sopenharmony_ci var->blue.msb_right = 0; 57962306a36Sopenharmony_ci var->transp.msb_right = 0; 58062306a36Sopenharmony_ci 58162306a36Sopenharmony_ci /* Rotation is not supported */ 58262306a36Sopenharmony_ci if (var->rotate) { 58362306a36Sopenharmony_ci dev_dbg(info->device, "Rotation is not supported\n"); 58462306a36Sopenharmony_ci return -EINVAL; 58562306a36Sopenharmony_ci } 58662306a36Sopenharmony_ci 58762306a36Sopenharmony_ci /* Memory limit */ 58862306a36Sopenharmony_ci if (var->yres_virtual * xdr_line_length > info->fix.smem_len) { 58962306a36Sopenharmony_ci dev_dbg(info->device, "Not enough memory\n"); 59062306a36Sopenharmony_ci return -ENOMEM; 59162306a36Sopenharmony_ci } 59262306a36Sopenharmony_ci 59362306a36Sopenharmony_ci var->height = -1; 59462306a36Sopenharmony_ci var->width = -1; 59562306a36Sopenharmony_ci 59662306a36Sopenharmony_ci return 0; 59762306a36Sopenharmony_ci} 59862306a36Sopenharmony_ci 59962306a36Sopenharmony_ci /* 60062306a36Sopenharmony_ci * This routine actually sets the video mode. 60162306a36Sopenharmony_ci */ 60262306a36Sopenharmony_ci 60362306a36Sopenharmony_cistatic int ps3fb_set_par(struct fb_info *info) 60462306a36Sopenharmony_ci{ 60562306a36Sopenharmony_ci struct ps3fb_par *par = info->par; 60662306a36Sopenharmony_ci unsigned int mode, ddr_line_length, xdr_line_length, lines, maxlines; 60762306a36Sopenharmony_ci unsigned int ddr_xoff, ddr_yoff, offset; 60862306a36Sopenharmony_ci const struct fb_videomode *vmode; 60962306a36Sopenharmony_ci u64 dst; 61062306a36Sopenharmony_ci 61162306a36Sopenharmony_ci mode = ps3fb_find_mode(&info->var, &ddr_line_length, &xdr_line_length); 61262306a36Sopenharmony_ci if (!mode) 61362306a36Sopenharmony_ci return -EINVAL; 61462306a36Sopenharmony_ci 61562306a36Sopenharmony_ci vmode = ps3fb_native_vmode(mode & PS3AV_MODE_MASK); 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci info->fix.xpanstep = info->var.xres_virtual > info->var.xres ? 1 : 0; 61862306a36Sopenharmony_ci info->fix.ypanstep = info->var.yres_virtual > info->var.yres ? 1 : 0; 61962306a36Sopenharmony_ci info->fix.line_length = xdr_line_length; 62062306a36Sopenharmony_ci 62162306a36Sopenharmony_ci par->ddr_line_length = ddr_line_length; 62262306a36Sopenharmony_ci par->ddr_frame_size = vmode->yres * ddr_line_length; 62362306a36Sopenharmony_ci par->xdr_frame_size = info->var.yres_virtual * xdr_line_length; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci par->num_frames = info->fix.smem_len / 62662306a36Sopenharmony_ci max(par->ddr_frame_size, par->xdr_frame_size); 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci /* Keep the special bits we cannot set using fb_var_screeninfo */ 62962306a36Sopenharmony_ci par->new_mode_id = (par->new_mode_id & ~PS3AV_MODE_MASK) | mode; 63062306a36Sopenharmony_ci 63162306a36Sopenharmony_ci par->width = info->var.xres; 63262306a36Sopenharmony_ci par->height = info->var.yres; 63362306a36Sopenharmony_ci 63462306a36Sopenharmony_ci /* Start of the virtual frame buffer (relative to fullscreen) */ 63562306a36Sopenharmony_ci ddr_xoff = info->var.left_margin - vmode->left_margin; 63662306a36Sopenharmony_ci ddr_yoff = info->var.upper_margin - vmode->upper_margin; 63762306a36Sopenharmony_ci offset = ddr_yoff * ddr_line_length + ddr_xoff * BPP; 63862306a36Sopenharmony_ci 63962306a36Sopenharmony_ci par->fb_offset = GPU_ALIGN_UP(offset); 64062306a36Sopenharmony_ci par->full_offset = par->fb_offset - offset; 64162306a36Sopenharmony_ci par->pan_offset = info->var.yoffset * xdr_line_length + 64262306a36Sopenharmony_ci info->var.xoffset * BPP; 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_ci if (par->new_mode_id != par->mode_id) { 64562306a36Sopenharmony_ci if (ps3av_set_video_mode(par->new_mode_id)) { 64662306a36Sopenharmony_ci par->new_mode_id = par->mode_id; 64762306a36Sopenharmony_ci return -EINVAL; 64862306a36Sopenharmony_ci } 64962306a36Sopenharmony_ci par->mode_id = par->new_mode_id; 65062306a36Sopenharmony_ci } 65162306a36Sopenharmony_ci 65262306a36Sopenharmony_ci /* Clear XDR frame buffer memory */ 65362306a36Sopenharmony_ci memset(info->screen_buffer, 0, info->fix.smem_len); 65462306a36Sopenharmony_ci 65562306a36Sopenharmony_ci /* Clear DDR frame buffer memory */ 65662306a36Sopenharmony_ci lines = vmode->yres * par->num_frames; 65762306a36Sopenharmony_ci if (par->full_offset) 65862306a36Sopenharmony_ci lines++; 65962306a36Sopenharmony_ci maxlines = info->fix.smem_len / ddr_line_length; 66062306a36Sopenharmony_ci for (dst = 0; lines; dst += maxlines * ddr_line_length) { 66162306a36Sopenharmony_ci unsigned int l = min(lines, maxlines); 66262306a36Sopenharmony_ci ps3fb_sync_image(info->device, 0, dst, 0, vmode->xres, l, 66362306a36Sopenharmony_ci ddr_line_length, ddr_line_length); 66462306a36Sopenharmony_ci lines -= l; 66562306a36Sopenharmony_ci } 66662306a36Sopenharmony_ci 66762306a36Sopenharmony_ci return 0; 66862306a36Sopenharmony_ci} 66962306a36Sopenharmony_ci 67062306a36Sopenharmony_ci /* 67162306a36Sopenharmony_ci * Set a single color register. The values supplied are already 67262306a36Sopenharmony_ci * rounded down to the hardware's capabilities (according to the 67362306a36Sopenharmony_ci * entries in the var structure). Return != 0 for invalid regno. 67462306a36Sopenharmony_ci */ 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_cistatic int ps3fb_setcolreg(unsigned int regno, unsigned int red, 67762306a36Sopenharmony_ci unsigned int green, unsigned int blue, 67862306a36Sopenharmony_ci unsigned int transp, struct fb_info *info) 67962306a36Sopenharmony_ci{ 68062306a36Sopenharmony_ci if (regno >= 16) 68162306a36Sopenharmony_ci return 1; 68262306a36Sopenharmony_ci 68362306a36Sopenharmony_ci red >>= 8; 68462306a36Sopenharmony_ci green >>= 8; 68562306a36Sopenharmony_ci blue >>= 8; 68662306a36Sopenharmony_ci transp >>= 8; 68762306a36Sopenharmony_ci 68862306a36Sopenharmony_ci ((u32 *)info->pseudo_palette)[regno] = transp << 24 | red << 16 | 68962306a36Sopenharmony_ci green << 8 | blue; 69062306a36Sopenharmony_ci return 0; 69162306a36Sopenharmony_ci} 69262306a36Sopenharmony_ci 69362306a36Sopenharmony_cistatic int ps3fb_pan_display(struct fb_var_screeninfo *var, 69462306a36Sopenharmony_ci struct fb_info *info) 69562306a36Sopenharmony_ci{ 69662306a36Sopenharmony_ci struct ps3fb_par *par = info->par; 69762306a36Sopenharmony_ci 69862306a36Sopenharmony_ci par->pan_offset = var->yoffset * info->fix.line_length + 69962306a36Sopenharmony_ci var->xoffset * BPP; 70062306a36Sopenharmony_ci return 0; 70162306a36Sopenharmony_ci} 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_ci /* 70462306a36Sopenharmony_ci * As we have a virtual frame buffer, we need our own mmap function 70562306a36Sopenharmony_ci */ 70662306a36Sopenharmony_ci 70762306a36Sopenharmony_cistatic int ps3fb_mmap(struct fb_info *info, struct vm_area_struct *vma) 70862306a36Sopenharmony_ci{ 70962306a36Sopenharmony_ci int r; 71062306a36Sopenharmony_ci 71162306a36Sopenharmony_ci r = vm_iomap_memory(vma, info->fix.smem_start, info->fix.smem_len); 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_ci dev_dbg(info->device, "ps3fb: mmap framebuffer P(%lx)->V(%lx)\n", 71462306a36Sopenharmony_ci info->fix.smem_start + (vma->vm_pgoff << PAGE_SHIFT), 71562306a36Sopenharmony_ci vma->vm_start); 71662306a36Sopenharmony_ci 71762306a36Sopenharmony_ci return r; 71862306a36Sopenharmony_ci} 71962306a36Sopenharmony_ci 72062306a36Sopenharmony_ci /* 72162306a36Sopenharmony_ci * Blank the display 72262306a36Sopenharmony_ci */ 72362306a36Sopenharmony_ci 72462306a36Sopenharmony_cistatic int ps3fb_blank(int blank, struct fb_info *info) 72562306a36Sopenharmony_ci{ 72662306a36Sopenharmony_ci int retval; 72762306a36Sopenharmony_ci 72862306a36Sopenharmony_ci dev_dbg(info->device, "%s: blank:%d\n", __func__, blank); 72962306a36Sopenharmony_ci switch (blank) { 73062306a36Sopenharmony_ci case FB_BLANK_POWERDOWN: 73162306a36Sopenharmony_ci case FB_BLANK_HSYNC_SUSPEND: 73262306a36Sopenharmony_ci case FB_BLANK_VSYNC_SUSPEND: 73362306a36Sopenharmony_ci case FB_BLANK_NORMAL: 73462306a36Sopenharmony_ci retval = ps3av_video_mute(1); /* mute on */ 73562306a36Sopenharmony_ci if (!retval) 73662306a36Sopenharmony_ci ps3fb.is_blanked = 1; 73762306a36Sopenharmony_ci break; 73862306a36Sopenharmony_ci 73962306a36Sopenharmony_ci default: /* unblank */ 74062306a36Sopenharmony_ci retval = ps3av_video_mute(0); /* mute off */ 74162306a36Sopenharmony_ci if (!retval) 74262306a36Sopenharmony_ci ps3fb.is_blanked = 0; 74362306a36Sopenharmony_ci break; 74462306a36Sopenharmony_ci } 74562306a36Sopenharmony_ci return retval; 74662306a36Sopenharmony_ci} 74762306a36Sopenharmony_ci 74862306a36Sopenharmony_cistatic int ps3fb_get_vblank(struct fb_vblank *vblank) 74962306a36Sopenharmony_ci{ 75062306a36Sopenharmony_ci memset(vblank, 0, sizeof(*vblank)); 75162306a36Sopenharmony_ci vblank->flags = FB_VBLANK_HAVE_VSYNC; 75262306a36Sopenharmony_ci return 0; 75362306a36Sopenharmony_ci} 75462306a36Sopenharmony_ci 75562306a36Sopenharmony_cistatic int ps3fb_wait_for_vsync(u32 crtc) 75662306a36Sopenharmony_ci{ 75762306a36Sopenharmony_ci int ret; 75862306a36Sopenharmony_ci u64 count; 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci count = ps3fb.vblank_count; 76162306a36Sopenharmony_ci ret = wait_event_interruptible_timeout(ps3fb.wait_vsync, 76262306a36Sopenharmony_ci count != ps3fb.vblank_count, 76362306a36Sopenharmony_ci HZ / 10); 76462306a36Sopenharmony_ci if (!ret) 76562306a36Sopenharmony_ci return -ETIMEDOUT; 76662306a36Sopenharmony_ci 76762306a36Sopenharmony_ci return 0; 76862306a36Sopenharmony_ci} 76962306a36Sopenharmony_ci 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_ci /* 77262306a36Sopenharmony_ci * ioctl 77362306a36Sopenharmony_ci */ 77462306a36Sopenharmony_ci 77562306a36Sopenharmony_cistatic int ps3fb_ioctl(struct fb_info *info, unsigned int cmd, 77662306a36Sopenharmony_ci unsigned long arg) 77762306a36Sopenharmony_ci{ 77862306a36Sopenharmony_ci void __user *argp = (void __user *)arg; 77962306a36Sopenharmony_ci u32 val; 78062306a36Sopenharmony_ci int retval = -EFAULT; 78162306a36Sopenharmony_ci 78262306a36Sopenharmony_ci switch (cmd) { 78362306a36Sopenharmony_ci case FBIOGET_VBLANK: 78462306a36Sopenharmony_ci { 78562306a36Sopenharmony_ci struct fb_vblank vblank; 78662306a36Sopenharmony_ci dev_dbg(info->device, "FBIOGET_VBLANK:\n"); 78762306a36Sopenharmony_ci retval = ps3fb_get_vblank(&vblank); 78862306a36Sopenharmony_ci if (retval) 78962306a36Sopenharmony_ci break; 79062306a36Sopenharmony_ci 79162306a36Sopenharmony_ci if (copy_to_user(argp, &vblank, sizeof(vblank))) 79262306a36Sopenharmony_ci retval = -EFAULT; 79362306a36Sopenharmony_ci break; 79462306a36Sopenharmony_ci } 79562306a36Sopenharmony_ci 79662306a36Sopenharmony_ci case FBIO_WAITFORVSYNC: 79762306a36Sopenharmony_ci { 79862306a36Sopenharmony_ci u32 crt; 79962306a36Sopenharmony_ci dev_dbg(info->device, "FBIO_WAITFORVSYNC:\n"); 80062306a36Sopenharmony_ci if (get_user(crt, (u32 __user *) arg)) 80162306a36Sopenharmony_ci break; 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci retval = ps3fb_wait_for_vsync(crt); 80462306a36Sopenharmony_ci break; 80562306a36Sopenharmony_ci } 80662306a36Sopenharmony_ci 80762306a36Sopenharmony_ci case PS3FB_IOCTL_SETMODE: 80862306a36Sopenharmony_ci { 80962306a36Sopenharmony_ci struct ps3fb_par *par = info->par; 81062306a36Sopenharmony_ci const struct fb_videomode *vmode; 81162306a36Sopenharmony_ci struct fb_var_screeninfo var; 81262306a36Sopenharmony_ci 81362306a36Sopenharmony_ci if (copy_from_user(&val, argp, sizeof(val))) 81462306a36Sopenharmony_ci break; 81562306a36Sopenharmony_ci 81662306a36Sopenharmony_ci if (!(val & PS3AV_MODE_MASK)) { 81762306a36Sopenharmony_ci u32 id = ps3av_get_auto_mode(); 81862306a36Sopenharmony_ci if (id > 0) 81962306a36Sopenharmony_ci val = (val & ~PS3AV_MODE_MASK) | id; 82062306a36Sopenharmony_ci } 82162306a36Sopenharmony_ci dev_dbg(info->device, "PS3FB_IOCTL_SETMODE:%x\n", val); 82262306a36Sopenharmony_ci retval = -EINVAL; 82362306a36Sopenharmony_ci vmode = ps3fb_vmode(val); 82462306a36Sopenharmony_ci if (vmode) { 82562306a36Sopenharmony_ci var = info->var; 82662306a36Sopenharmony_ci fb_videomode_to_var(&var, vmode); 82762306a36Sopenharmony_ci console_lock(); 82862306a36Sopenharmony_ci /* Force, in case only special bits changed */ 82962306a36Sopenharmony_ci var.activate |= FB_ACTIVATE_FORCE; 83062306a36Sopenharmony_ci par->new_mode_id = val; 83162306a36Sopenharmony_ci retval = fb_set_var(info, &var); 83262306a36Sopenharmony_ci if (!retval) 83362306a36Sopenharmony_ci fbcon_update_vcs(info, var.activate & FB_ACTIVATE_ALL); 83462306a36Sopenharmony_ci console_unlock(); 83562306a36Sopenharmony_ci } 83662306a36Sopenharmony_ci break; 83762306a36Sopenharmony_ci } 83862306a36Sopenharmony_ci 83962306a36Sopenharmony_ci case PS3FB_IOCTL_GETMODE: 84062306a36Sopenharmony_ci val = ps3av_get_mode(); 84162306a36Sopenharmony_ci dev_dbg(info->device, "PS3FB_IOCTL_GETMODE:%x\n", val); 84262306a36Sopenharmony_ci if (!copy_to_user(argp, &val, sizeof(val))) 84362306a36Sopenharmony_ci retval = 0; 84462306a36Sopenharmony_ci break; 84562306a36Sopenharmony_ci 84662306a36Sopenharmony_ci case PS3FB_IOCTL_SCREENINFO: 84762306a36Sopenharmony_ci { 84862306a36Sopenharmony_ci struct ps3fb_par *par = info->par; 84962306a36Sopenharmony_ci struct ps3fb_ioctl_res res; 85062306a36Sopenharmony_ci dev_dbg(info->device, "PS3FB_IOCTL_SCREENINFO:\n"); 85162306a36Sopenharmony_ci res.xres = info->fix.line_length / BPP; 85262306a36Sopenharmony_ci res.yres = info->var.yres_virtual; 85362306a36Sopenharmony_ci res.xoff = (res.xres - info->var.xres) / 2; 85462306a36Sopenharmony_ci res.yoff = (res.yres - info->var.yres) / 2; 85562306a36Sopenharmony_ci res.num_frames = par->num_frames; 85662306a36Sopenharmony_ci if (!copy_to_user(argp, &res, sizeof(res))) 85762306a36Sopenharmony_ci retval = 0; 85862306a36Sopenharmony_ci break; 85962306a36Sopenharmony_ci } 86062306a36Sopenharmony_ci 86162306a36Sopenharmony_ci case PS3FB_IOCTL_ON: 86262306a36Sopenharmony_ci dev_dbg(info->device, "PS3FB_IOCTL_ON:\n"); 86362306a36Sopenharmony_ci atomic_inc(&ps3fb.ext_flip); 86462306a36Sopenharmony_ci retval = 0; 86562306a36Sopenharmony_ci break; 86662306a36Sopenharmony_ci 86762306a36Sopenharmony_ci case PS3FB_IOCTL_OFF: 86862306a36Sopenharmony_ci dev_dbg(info->device, "PS3FB_IOCTL_OFF:\n"); 86962306a36Sopenharmony_ci atomic_dec_if_positive(&ps3fb.ext_flip); 87062306a36Sopenharmony_ci retval = 0; 87162306a36Sopenharmony_ci break; 87262306a36Sopenharmony_ci 87362306a36Sopenharmony_ci case PS3FB_IOCTL_FSEL: 87462306a36Sopenharmony_ci if (copy_from_user(&val, argp, sizeof(val))) 87562306a36Sopenharmony_ci break; 87662306a36Sopenharmony_ci 87762306a36Sopenharmony_ci dev_dbg(info->device, "PS3FB_IOCTL_FSEL:%d\n", val); 87862306a36Sopenharmony_ci console_lock(); 87962306a36Sopenharmony_ci retval = ps3fb_sync(info, val); 88062306a36Sopenharmony_ci console_unlock(); 88162306a36Sopenharmony_ci break; 88262306a36Sopenharmony_ci 88362306a36Sopenharmony_ci default: 88462306a36Sopenharmony_ci retval = -ENOIOCTLCMD; 88562306a36Sopenharmony_ci break; 88662306a36Sopenharmony_ci } 88762306a36Sopenharmony_ci return retval; 88862306a36Sopenharmony_ci} 88962306a36Sopenharmony_ci 89062306a36Sopenharmony_cistatic int ps3fbd(void *arg) 89162306a36Sopenharmony_ci{ 89262306a36Sopenharmony_ci struct fb_info *info = arg; 89362306a36Sopenharmony_ci 89462306a36Sopenharmony_ci set_freezable(); 89562306a36Sopenharmony_ci while (!kthread_should_stop()) { 89662306a36Sopenharmony_ci try_to_freeze(); 89762306a36Sopenharmony_ci set_current_state(TASK_INTERRUPTIBLE); 89862306a36Sopenharmony_ci if (ps3fb.is_kicked) { 89962306a36Sopenharmony_ci ps3fb.is_kicked = 0; 90062306a36Sopenharmony_ci console_lock(); 90162306a36Sopenharmony_ci ps3fb_sync(info, 0); /* single buffer */ 90262306a36Sopenharmony_ci console_unlock(); 90362306a36Sopenharmony_ci } 90462306a36Sopenharmony_ci schedule(); 90562306a36Sopenharmony_ci } 90662306a36Sopenharmony_ci return 0; 90762306a36Sopenharmony_ci} 90862306a36Sopenharmony_ci 90962306a36Sopenharmony_cistatic irqreturn_t ps3fb_vsync_interrupt(int irq, void *ptr) 91062306a36Sopenharmony_ci{ 91162306a36Sopenharmony_ci struct device *dev = ptr; 91262306a36Sopenharmony_ci u64 v1; 91362306a36Sopenharmony_ci int status; 91462306a36Sopenharmony_ci struct display_head *head = &ps3fb.dinfo->display_head[1]; 91562306a36Sopenharmony_ci 91662306a36Sopenharmony_ci status = lv1_gpu_context_intr(ps3fb.context_handle, &v1); 91762306a36Sopenharmony_ci if (status) { 91862306a36Sopenharmony_ci dev_err(dev, "%s: lv1_gpu_context_intr failed: %d\n", __func__, 91962306a36Sopenharmony_ci status); 92062306a36Sopenharmony_ci return IRQ_NONE; 92162306a36Sopenharmony_ci } 92262306a36Sopenharmony_ci 92362306a36Sopenharmony_ci if (v1 & (1 << GPU_INTR_STATUS_VSYNC_1)) { 92462306a36Sopenharmony_ci /* VSYNC */ 92562306a36Sopenharmony_ci ps3fb.vblank_count = head->vblank_count; 92662306a36Sopenharmony_ci if (ps3fb.task && !ps3fb.is_blanked && 92762306a36Sopenharmony_ci !atomic_read(&ps3fb.ext_flip)) { 92862306a36Sopenharmony_ci ps3fb.is_kicked = 1; 92962306a36Sopenharmony_ci wake_up_process(ps3fb.task); 93062306a36Sopenharmony_ci } 93162306a36Sopenharmony_ci wake_up_interruptible(&ps3fb.wait_vsync); 93262306a36Sopenharmony_ci } 93362306a36Sopenharmony_ci 93462306a36Sopenharmony_ci return IRQ_HANDLED; 93562306a36Sopenharmony_ci} 93662306a36Sopenharmony_ci 93762306a36Sopenharmony_ci 93862306a36Sopenharmony_cistatic const struct fb_ops ps3fb_ops = { 93962306a36Sopenharmony_ci .owner = THIS_MODULE, 94062306a36Sopenharmony_ci .fb_open = ps3fb_open, 94162306a36Sopenharmony_ci .fb_release = ps3fb_release, 94262306a36Sopenharmony_ci .fb_read = fb_sys_read, 94362306a36Sopenharmony_ci .fb_write = fb_sys_write, 94462306a36Sopenharmony_ci .fb_check_var = ps3fb_check_var, 94562306a36Sopenharmony_ci .fb_set_par = ps3fb_set_par, 94662306a36Sopenharmony_ci .fb_setcolreg = ps3fb_setcolreg, 94762306a36Sopenharmony_ci .fb_pan_display = ps3fb_pan_display, 94862306a36Sopenharmony_ci .fb_fillrect = sys_fillrect, 94962306a36Sopenharmony_ci .fb_copyarea = sys_copyarea, 95062306a36Sopenharmony_ci .fb_imageblit = sys_imageblit, 95162306a36Sopenharmony_ci .fb_mmap = ps3fb_mmap, 95262306a36Sopenharmony_ci .fb_blank = ps3fb_blank, 95362306a36Sopenharmony_ci .fb_ioctl = ps3fb_ioctl, 95462306a36Sopenharmony_ci .fb_compat_ioctl = ps3fb_ioctl 95562306a36Sopenharmony_ci}; 95662306a36Sopenharmony_ci 95762306a36Sopenharmony_cistatic const struct fb_fix_screeninfo ps3fb_fix = { 95862306a36Sopenharmony_ci .id = DEVICE_NAME, 95962306a36Sopenharmony_ci .type = FB_TYPE_PACKED_PIXELS, 96062306a36Sopenharmony_ci .visual = FB_VISUAL_TRUECOLOR, 96162306a36Sopenharmony_ci .accel = FB_ACCEL_NONE, 96262306a36Sopenharmony_ci}; 96362306a36Sopenharmony_ci 96462306a36Sopenharmony_cistatic int ps3fb_probe(struct ps3_system_bus_device *dev) 96562306a36Sopenharmony_ci{ 96662306a36Sopenharmony_ci struct fb_info *info; 96762306a36Sopenharmony_ci struct ps3fb_par *par; 96862306a36Sopenharmony_ci int retval; 96962306a36Sopenharmony_ci u64 ddr_lpar = 0; 97062306a36Sopenharmony_ci u64 lpar_dma_control = 0; 97162306a36Sopenharmony_ci u64 lpar_driver_info = 0; 97262306a36Sopenharmony_ci u64 lpar_reports = 0; 97362306a36Sopenharmony_ci u64 lpar_reports_size = 0; 97462306a36Sopenharmony_ci u64 xdr_lpar; 97562306a36Sopenharmony_ci struct gpu_driver_info *dinfo; 97662306a36Sopenharmony_ci void *fb_start; 97762306a36Sopenharmony_ci int status; 97862306a36Sopenharmony_ci struct task_struct *task; 97962306a36Sopenharmony_ci unsigned long max_ps3fb_size; 98062306a36Sopenharmony_ci 98162306a36Sopenharmony_ci if (ps3fb_videomemory.size < GPU_CMD_BUF_SIZE) { 98262306a36Sopenharmony_ci dev_err(&dev->core, "%s: Not enough video memory\n", __func__); 98362306a36Sopenharmony_ci return -ENOMEM; 98462306a36Sopenharmony_ci } 98562306a36Sopenharmony_ci 98662306a36Sopenharmony_ci retval = ps3_open_hv_device(dev); 98762306a36Sopenharmony_ci if (retval) { 98862306a36Sopenharmony_ci dev_err(&dev->core, "%s: ps3_open_hv_device failed\n", 98962306a36Sopenharmony_ci __func__); 99062306a36Sopenharmony_ci goto err; 99162306a36Sopenharmony_ci } 99262306a36Sopenharmony_ci 99362306a36Sopenharmony_ci if (!ps3fb_mode) 99462306a36Sopenharmony_ci ps3fb_mode = ps3av_get_mode(); 99562306a36Sopenharmony_ci dev_dbg(&dev->core, "ps3fb_mode: %d\n", ps3fb_mode); 99662306a36Sopenharmony_ci 99762306a36Sopenharmony_ci atomic_set(&ps3fb.f_count, -1); /* fbcon opens ps3fb */ 99862306a36Sopenharmony_ci atomic_set(&ps3fb.ext_flip, 0); /* for flip with vsync */ 99962306a36Sopenharmony_ci init_waitqueue_head(&ps3fb.wait_vsync); 100062306a36Sopenharmony_ci 100162306a36Sopenharmony_ci#ifdef HEAD_A 100262306a36Sopenharmony_ci status = lv1_gpu_display_sync(0x0, 0, L1GPU_DISPLAY_SYNC_VSYNC); 100362306a36Sopenharmony_ci if (status) { 100462306a36Sopenharmony_ci dev_err(&dev->core, "%s: lv1_gpu_display_sync failed: %d\n", 100562306a36Sopenharmony_ci __func__, status); 100662306a36Sopenharmony_ci retval = -ENODEV; 100762306a36Sopenharmony_ci goto err_close_device; 100862306a36Sopenharmony_ci } 100962306a36Sopenharmony_ci#endif 101062306a36Sopenharmony_ci#ifdef HEAD_B 101162306a36Sopenharmony_ci status = lv1_gpu_display_sync(0x0, 1, L1GPU_DISPLAY_SYNC_VSYNC); 101262306a36Sopenharmony_ci if (status) { 101362306a36Sopenharmony_ci dev_err(&dev->core, "%s: lv1_gpu_display_sync failed: %d\n", 101462306a36Sopenharmony_ci __func__, status); 101562306a36Sopenharmony_ci retval = -ENODEV; 101662306a36Sopenharmony_ci goto err_close_device; 101762306a36Sopenharmony_ci } 101862306a36Sopenharmony_ci#endif 101962306a36Sopenharmony_ci 102062306a36Sopenharmony_ci max_ps3fb_size = ALIGN(GPU_IOIF, 256*1024*1024) - GPU_IOIF; 102162306a36Sopenharmony_ci if (ps3fb_videomemory.size > max_ps3fb_size) { 102262306a36Sopenharmony_ci dev_info(&dev->core, "Limiting ps3fb mem size to %lu bytes\n", 102362306a36Sopenharmony_ci max_ps3fb_size); 102462306a36Sopenharmony_ci ps3fb_videomemory.size = max_ps3fb_size; 102562306a36Sopenharmony_ci } 102662306a36Sopenharmony_ci 102762306a36Sopenharmony_ci /* get gpu context handle */ 102862306a36Sopenharmony_ci status = lv1_gpu_memory_allocate(ps3fb_videomemory.size, 0, 0, 0, 0, 102962306a36Sopenharmony_ci &ps3fb.memory_handle, &ddr_lpar); 103062306a36Sopenharmony_ci if (status) { 103162306a36Sopenharmony_ci dev_err(&dev->core, "%s: lv1_gpu_memory_allocate failed: %d\n", 103262306a36Sopenharmony_ci __func__, status); 103362306a36Sopenharmony_ci retval = -ENOMEM; 103462306a36Sopenharmony_ci goto err_close_device; 103562306a36Sopenharmony_ci } 103662306a36Sopenharmony_ci dev_dbg(&dev->core, "ddr:lpar:0x%llx\n", ddr_lpar); 103762306a36Sopenharmony_ci 103862306a36Sopenharmony_ci status = lv1_gpu_context_allocate(ps3fb.memory_handle, 0, 103962306a36Sopenharmony_ci &ps3fb.context_handle, 104062306a36Sopenharmony_ci &lpar_dma_control, &lpar_driver_info, 104162306a36Sopenharmony_ci &lpar_reports, &lpar_reports_size); 104262306a36Sopenharmony_ci if (status) { 104362306a36Sopenharmony_ci dev_err(&dev->core, 104462306a36Sopenharmony_ci "%s: lv1_gpu_context_allocate failed: %d\n", __func__, 104562306a36Sopenharmony_ci status); 104662306a36Sopenharmony_ci retval = -ENOMEM; 104762306a36Sopenharmony_ci goto err_gpu_memory_free; 104862306a36Sopenharmony_ci } 104962306a36Sopenharmony_ci 105062306a36Sopenharmony_ci /* vsync interrupt */ 105162306a36Sopenharmony_ci dinfo = (void __force *)ioremap(lpar_driver_info, 128 * 1024); 105262306a36Sopenharmony_ci if (!dinfo) { 105362306a36Sopenharmony_ci dev_err(&dev->core, "%s: ioremap failed\n", __func__); 105462306a36Sopenharmony_ci retval = -ENOMEM; 105562306a36Sopenharmony_ci goto err_gpu_context_free; 105662306a36Sopenharmony_ci } 105762306a36Sopenharmony_ci 105862306a36Sopenharmony_ci ps3fb.dinfo = dinfo; 105962306a36Sopenharmony_ci dev_dbg(&dev->core, "version_driver:%x\n", dinfo->version_driver); 106062306a36Sopenharmony_ci dev_dbg(&dev->core, "irq outlet:%x\n", dinfo->irq.irq_outlet); 106162306a36Sopenharmony_ci dev_dbg(&dev->core, "version_gpu: %x memory_size: %x ch: %x " 106262306a36Sopenharmony_ci "core_freq: %d mem_freq:%d\n", dinfo->version_gpu, 106362306a36Sopenharmony_ci dinfo->memory_size, dinfo->hardware_channel, 106462306a36Sopenharmony_ci dinfo->nvcore_frequency/1000000, 106562306a36Sopenharmony_ci dinfo->memory_frequency/1000000); 106662306a36Sopenharmony_ci 106762306a36Sopenharmony_ci if (dinfo->version_driver != GPU_DRIVER_INFO_VERSION) { 106862306a36Sopenharmony_ci dev_err(&dev->core, "%s: version_driver err:%x\n", __func__, 106962306a36Sopenharmony_ci dinfo->version_driver); 107062306a36Sopenharmony_ci retval = -EINVAL; 107162306a36Sopenharmony_ci goto err_iounmap_dinfo; 107262306a36Sopenharmony_ci } 107362306a36Sopenharmony_ci 107462306a36Sopenharmony_ci retval = ps3_irq_plug_setup(PS3_BINDING_CPU_ANY, dinfo->irq.irq_outlet, 107562306a36Sopenharmony_ci &ps3fb.irq_no); 107662306a36Sopenharmony_ci if (retval) { 107762306a36Sopenharmony_ci dev_err(&dev->core, "%s: ps3_alloc_irq failed %d\n", __func__, 107862306a36Sopenharmony_ci retval); 107962306a36Sopenharmony_ci goto err_iounmap_dinfo; 108062306a36Sopenharmony_ci } 108162306a36Sopenharmony_ci 108262306a36Sopenharmony_ci retval = request_irq(ps3fb.irq_no, ps3fb_vsync_interrupt, 108362306a36Sopenharmony_ci 0, DEVICE_NAME, &dev->core); 108462306a36Sopenharmony_ci if (retval) { 108562306a36Sopenharmony_ci dev_err(&dev->core, "%s: request_irq failed %d\n", __func__, 108662306a36Sopenharmony_ci retval); 108762306a36Sopenharmony_ci goto err_destroy_plug; 108862306a36Sopenharmony_ci } 108962306a36Sopenharmony_ci 109062306a36Sopenharmony_ci dinfo->irq.mask = (1 << GPU_INTR_STATUS_VSYNC_1) | 109162306a36Sopenharmony_ci (1 << GPU_INTR_STATUS_FLIP_1); 109262306a36Sopenharmony_ci 109362306a36Sopenharmony_ci /* Clear memory to prevent kernel info leakage into userspace */ 109462306a36Sopenharmony_ci memset(ps3fb_videomemory.address, 0, ps3fb_videomemory.size); 109562306a36Sopenharmony_ci 109662306a36Sopenharmony_ci xdr_lpar = ps3_mm_phys_to_lpar(__pa(ps3fb_videomemory.address)); 109762306a36Sopenharmony_ci 109862306a36Sopenharmony_ci status = lv1_gpu_context_iomap(ps3fb.context_handle, GPU_IOIF, 109962306a36Sopenharmony_ci xdr_lpar, ps3fb_videomemory.size, 110062306a36Sopenharmony_ci CBE_IOPTE_PP_W | CBE_IOPTE_PP_R | 110162306a36Sopenharmony_ci CBE_IOPTE_M); 110262306a36Sopenharmony_ci if (status) { 110362306a36Sopenharmony_ci dev_err(&dev->core, "%s: lv1_gpu_context_iomap failed: %d\n", 110462306a36Sopenharmony_ci __func__, status); 110562306a36Sopenharmony_ci retval = -ENXIO; 110662306a36Sopenharmony_ci goto err_free_irq; 110762306a36Sopenharmony_ci } 110862306a36Sopenharmony_ci 110962306a36Sopenharmony_ci dev_dbg(&dev->core, "video:%p ioif:%lx lpar:%llx size:%lx\n", 111062306a36Sopenharmony_ci ps3fb_videomemory.address, GPU_IOIF, xdr_lpar, 111162306a36Sopenharmony_ci ps3fb_videomemory.size); 111262306a36Sopenharmony_ci 111362306a36Sopenharmony_ci status = lv1_gpu_fb_setup(ps3fb.context_handle, xdr_lpar, 111462306a36Sopenharmony_ci GPU_CMD_BUF_SIZE, GPU_IOIF); 111562306a36Sopenharmony_ci if (status) { 111662306a36Sopenharmony_ci dev_err(&dev->core, "%s: lv1_gpu_fb_setup failed: %d\n", 111762306a36Sopenharmony_ci __func__, status); 111862306a36Sopenharmony_ci retval = -ENXIO; 111962306a36Sopenharmony_ci goto err_context_unmap; 112062306a36Sopenharmony_ci } 112162306a36Sopenharmony_ci 112262306a36Sopenharmony_ci info = framebuffer_alloc(sizeof(struct ps3fb_par), &dev->core); 112362306a36Sopenharmony_ci if (!info) { 112462306a36Sopenharmony_ci retval = -ENOMEM; 112562306a36Sopenharmony_ci goto err_context_fb_close; 112662306a36Sopenharmony_ci } 112762306a36Sopenharmony_ci 112862306a36Sopenharmony_ci par = info->par; 112962306a36Sopenharmony_ci par->mode_id = ~ps3fb_mode; /* != ps3fb_mode, to trigger change */ 113062306a36Sopenharmony_ci par->new_mode_id = ps3fb_mode; 113162306a36Sopenharmony_ci par->num_frames = 1; 113262306a36Sopenharmony_ci 113362306a36Sopenharmony_ci info->fbops = &ps3fb_ops; 113462306a36Sopenharmony_ci info->fix = ps3fb_fix; 113562306a36Sopenharmony_ci 113662306a36Sopenharmony_ci /* 113762306a36Sopenharmony_ci * The GPU command buffer is at the start of video memory 113862306a36Sopenharmony_ci * As we don't use the full command buffer, we can put the actual 113962306a36Sopenharmony_ci * frame buffer at offset GPU_FB_START and save some precious XDR 114062306a36Sopenharmony_ci * memory 114162306a36Sopenharmony_ci */ 114262306a36Sopenharmony_ci fb_start = ps3fb_videomemory.address + GPU_FB_START; 114362306a36Sopenharmony_ci info->screen_buffer = fb_start; 114462306a36Sopenharmony_ci info->fix.smem_start = __pa(fb_start); 114562306a36Sopenharmony_ci info->fix.smem_len = ps3fb_videomemory.size - GPU_FB_START; 114662306a36Sopenharmony_ci 114762306a36Sopenharmony_ci info->pseudo_palette = par->pseudo_palette; 114862306a36Sopenharmony_ci info->flags = FBINFO_READS_FAST | 114962306a36Sopenharmony_ci FBINFO_HWACCEL_XPAN | FBINFO_HWACCEL_YPAN; 115062306a36Sopenharmony_ci 115162306a36Sopenharmony_ci retval = fb_alloc_cmap(&info->cmap, 256, 0); 115262306a36Sopenharmony_ci if (retval < 0) 115362306a36Sopenharmony_ci goto err_framebuffer_release; 115462306a36Sopenharmony_ci 115562306a36Sopenharmony_ci if (!fb_find_mode(&info->var, info, mode_option, ps3fb_modedb, 115662306a36Sopenharmony_ci ARRAY_SIZE(ps3fb_modedb), 115762306a36Sopenharmony_ci ps3fb_vmode(par->new_mode_id), 32)) { 115862306a36Sopenharmony_ci retval = -EINVAL; 115962306a36Sopenharmony_ci goto err_fb_dealloc; 116062306a36Sopenharmony_ci } 116162306a36Sopenharmony_ci 116262306a36Sopenharmony_ci fb_videomode_to_modelist(ps3fb_modedb, ARRAY_SIZE(ps3fb_modedb), 116362306a36Sopenharmony_ci &info->modelist); 116462306a36Sopenharmony_ci 116562306a36Sopenharmony_ci retval = register_framebuffer(info); 116662306a36Sopenharmony_ci if (retval < 0) 116762306a36Sopenharmony_ci goto err_fb_dealloc; 116862306a36Sopenharmony_ci 116962306a36Sopenharmony_ci ps3_system_bus_set_drvdata(dev, info); 117062306a36Sopenharmony_ci 117162306a36Sopenharmony_ci fb_info(info, "using %u KiB of video memory\n", info->fix.smem_len >> 10); 117262306a36Sopenharmony_ci 117362306a36Sopenharmony_ci task = kthread_run(ps3fbd, info, DEVICE_NAME); 117462306a36Sopenharmony_ci if (IS_ERR(task)) { 117562306a36Sopenharmony_ci retval = PTR_ERR(task); 117662306a36Sopenharmony_ci goto err_unregister_framebuffer; 117762306a36Sopenharmony_ci } 117862306a36Sopenharmony_ci 117962306a36Sopenharmony_ci ps3fb.task = task; 118062306a36Sopenharmony_ci 118162306a36Sopenharmony_ci return 0; 118262306a36Sopenharmony_ci 118362306a36Sopenharmony_cierr_unregister_framebuffer: 118462306a36Sopenharmony_ci unregister_framebuffer(info); 118562306a36Sopenharmony_cierr_fb_dealloc: 118662306a36Sopenharmony_ci fb_dealloc_cmap(&info->cmap); 118762306a36Sopenharmony_cierr_framebuffer_release: 118862306a36Sopenharmony_ci framebuffer_release(info); 118962306a36Sopenharmony_cierr_context_fb_close: 119062306a36Sopenharmony_ci lv1_gpu_fb_close(ps3fb.context_handle); 119162306a36Sopenharmony_cierr_context_unmap: 119262306a36Sopenharmony_ci lv1_gpu_context_iomap(ps3fb.context_handle, GPU_IOIF, xdr_lpar, 119362306a36Sopenharmony_ci ps3fb_videomemory.size, CBE_IOPTE_M); 119462306a36Sopenharmony_cierr_free_irq: 119562306a36Sopenharmony_ci free_irq(ps3fb.irq_no, &dev->core); 119662306a36Sopenharmony_cierr_destroy_plug: 119762306a36Sopenharmony_ci ps3_irq_plug_destroy(ps3fb.irq_no); 119862306a36Sopenharmony_cierr_iounmap_dinfo: 119962306a36Sopenharmony_ci iounmap((u8 __force __iomem *)ps3fb.dinfo); 120062306a36Sopenharmony_cierr_gpu_context_free: 120162306a36Sopenharmony_ci lv1_gpu_context_free(ps3fb.context_handle); 120262306a36Sopenharmony_cierr_gpu_memory_free: 120362306a36Sopenharmony_ci lv1_gpu_memory_free(ps3fb.memory_handle); 120462306a36Sopenharmony_cierr_close_device: 120562306a36Sopenharmony_ci ps3_close_hv_device(dev); 120662306a36Sopenharmony_cierr: 120762306a36Sopenharmony_ci return retval; 120862306a36Sopenharmony_ci} 120962306a36Sopenharmony_ci 121062306a36Sopenharmony_cistatic void ps3fb_shutdown(struct ps3_system_bus_device *dev) 121162306a36Sopenharmony_ci{ 121262306a36Sopenharmony_ci struct fb_info *info = ps3_system_bus_get_drvdata(dev); 121362306a36Sopenharmony_ci u64 xdr_lpar = ps3_mm_phys_to_lpar(__pa(ps3fb_videomemory.address)); 121462306a36Sopenharmony_ci 121562306a36Sopenharmony_ci dev_dbg(&dev->core, " -> %s:%d\n", __func__, __LINE__); 121662306a36Sopenharmony_ci 121762306a36Sopenharmony_ci atomic_inc(&ps3fb.ext_flip); /* flip off */ 121862306a36Sopenharmony_ci ps3fb.dinfo->irq.mask = 0; 121962306a36Sopenharmony_ci 122062306a36Sopenharmony_ci if (ps3fb.task) { 122162306a36Sopenharmony_ci struct task_struct *task = ps3fb.task; 122262306a36Sopenharmony_ci ps3fb.task = NULL; 122362306a36Sopenharmony_ci kthread_stop(task); 122462306a36Sopenharmony_ci } 122562306a36Sopenharmony_ci if (ps3fb.irq_no) { 122662306a36Sopenharmony_ci free_irq(ps3fb.irq_no, &dev->core); 122762306a36Sopenharmony_ci ps3_irq_plug_destroy(ps3fb.irq_no); 122862306a36Sopenharmony_ci } 122962306a36Sopenharmony_ci if (info) { 123062306a36Sopenharmony_ci unregister_framebuffer(info); 123162306a36Sopenharmony_ci fb_dealloc_cmap(&info->cmap); 123262306a36Sopenharmony_ci framebuffer_release(info); 123362306a36Sopenharmony_ci ps3_system_bus_set_drvdata(dev, NULL); 123462306a36Sopenharmony_ci } 123562306a36Sopenharmony_ci iounmap((u8 __force __iomem *)ps3fb.dinfo); 123662306a36Sopenharmony_ci lv1_gpu_fb_close(ps3fb.context_handle); 123762306a36Sopenharmony_ci lv1_gpu_context_iomap(ps3fb.context_handle, GPU_IOIF, xdr_lpar, 123862306a36Sopenharmony_ci ps3fb_videomemory.size, CBE_IOPTE_M); 123962306a36Sopenharmony_ci lv1_gpu_context_free(ps3fb.context_handle); 124062306a36Sopenharmony_ci lv1_gpu_memory_free(ps3fb.memory_handle); 124162306a36Sopenharmony_ci ps3_close_hv_device(dev); 124262306a36Sopenharmony_ci dev_dbg(&dev->core, " <- %s:%d\n", __func__, __LINE__); 124362306a36Sopenharmony_ci} 124462306a36Sopenharmony_ci 124562306a36Sopenharmony_cistatic struct ps3_system_bus_driver ps3fb_driver = { 124662306a36Sopenharmony_ci .match_id = PS3_MATCH_ID_GPU, 124762306a36Sopenharmony_ci .match_sub_id = PS3_MATCH_SUB_ID_GPU_FB, 124862306a36Sopenharmony_ci .core.name = DEVICE_NAME, 124962306a36Sopenharmony_ci .core.owner = THIS_MODULE, 125062306a36Sopenharmony_ci .probe = ps3fb_probe, 125162306a36Sopenharmony_ci .remove = ps3fb_shutdown, 125262306a36Sopenharmony_ci .shutdown = ps3fb_shutdown, 125362306a36Sopenharmony_ci}; 125462306a36Sopenharmony_ci 125562306a36Sopenharmony_cistatic int __init ps3fb_setup(void) 125662306a36Sopenharmony_ci{ 125762306a36Sopenharmony_ci char *options; 125862306a36Sopenharmony_ci 125962306a36Sopenharmony_ci#ifdef MODULE 126062306a36Sopenharmony_ci return 0; 126162306a36Sopenharmony_ci#endif 126262306a36Sopenharmony_ci 126362306a36Sopenharmony_ci if (fb_get_options(DEVICE_NAME, &options)) 126462306a36Sopenharmony_ci return -ENXIO; 126562306a36Sopenharmony_ci 126662306a36Sopenharmony_ci if (!options || !*options) 126762306a36Sopenharmony_ci return 0; 126862306a36Sopenharmony_ci 126962306a36Sopenharmony_ci while (1) { 127062306a36Sopenharmony_ci char *this_opt = strsep(&options, ","); 127162306a36Sopenharmony_ci 127262306a36Sopenharmony_ci if (!this_opt) 127362306a36Sopenharmony_ci break; 127462306a36Sopenharmony_ci if (!*this_opt) 127562306a36Sopenharmony_ci continue; 127662306a36Sopenharmony_ci if (!strncmp(this_opt, "mode:", 5)) 127762306a36Sopenharmony_ci ps3fb_mode = simple_strtoul(this_opt + 5, NULL, 0); 127862306a36Sopenharmony_ci else 127962306a36Sopenharmony_ci mode_option = this_opt; 128062306a36Sopenharmony_ci } 128162306a36Sopenharmony_ci return 0; 128262306a36Sopenharmony_ci} 128362306a36Sopenharmony_ci 128462306a36Sopenharmony_cistatic int __init ps3fb_init(void) 128562306a36Sopenharmony_ci{ 128662306a36Sopenharmony_ci if (!ps3fb_videomemory.address || ps3fb_setup()) 128762306a36Sopenharmony_ci return -ENXIO; 128862306a36Sopenharmony_ci 128962306a36Sopenharmony_ci return ps3_system_bus_driver_register(&ps3fb_driver); 129062306a36Sopenharmony_ci} 129162306a36Sopenharmony_ci 129262306a36Sopenharmony_cistatic void __exit ps3fb_exit(void) 129362306a36Sopenharmony_ci{ 129462306a36Sopenharmony_ci pr_debug(" -> %s:%d\n", __func__, __LINE__); 129562306a36Sopenharmony_ci ps3_system_bus_driver_unregister(&ps3fb_driver); 129662306a36Sopenharmony_ci pr_debug(" <- %s:%d\n", __func__, __LINE__); 129762306a36Sopenharmony_ci} 129862306a36Sopenharmony_ci 129962306a36Sopenharmony_cimodule_init(ps3fb_init); 130062306a36Sopenharmony_cimodule_exit(ps3fb_exit); 130162306a36Sopenharmony_ci 130262306a36Sopenharmony_ciMODULE_LICENSE("GPL"); 130362306a36Sopenharmony_ciMODULE_DESCRIPTION("PS3 GPU Frame Buffer Driver"); 130462306a36Sopenharmony_ciMODULE_AUTHOR("Sony Computer Entertainment Inc."); 130562306a36Sopenharmony_ciMODULE_ALIAS(PS3_MODULE_ALIAS_GPU_FB); 1306