18c2ecf20Sopenharmony_ci/* 28c2ecf20Sopenharmony_ci * Copyright © 2007 David Airlie 38c2ecf20Sopenharmony_ci * 48c2ecf20Sopenharmony_ci * Permission is hereby granted, free of charge, to any person obtaining a 58c2ecf20Sopenharmony_ci * copy of this software and associated documentation files (the "Software"), 68c2ecf20Sopenharmony_ci * to deal in the Software without restriction, including without limitation 78c2ecf20Sopenharmony_ci * the rights to use, copy, modify, merge, publish, distribute, sublicense, 88c2ecf20Sopenharmony_ci * and/or sell copies of the Software, and to permit persons to whom the 98c2ecf20Sopenharmony_ci * Software is furnished to do so, subject to the following conditions: 108c2ecf20Sopenharmony_ci * 118c2ecf20Sopenharmony_ci * The above copyright notice and this permission notice (including the next 128c2ecf20Sopenharmony_ci * paragraph) shall be included in all copies or substantial portions of the 138c2ecf20Sopenharmony_ci * Software. 148c2ecf20Sopenharmony_ci * 158c2ecf20Sopenharmony_ci * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 168c2ecf20Sopenharmony_ci * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 178c2ecf20Sopenharmony_ci * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 188c2ecf20Sopenharmony_ci * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 198c2ecf20Sopenharmony_ci * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 208c2ecf20Sopenharmony_ci * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 218c2ecf20Sopenharmony_ci * DEALINGS IN THE SOFTWARE. 228c2ecf20Sopenharmony_ci * 238c2ecf20Sopenharmony_ci * Authors: 248c2ecf20Sopenharmony_ci * David Airlie 258c2ecf20Sopenharmony_ci */ 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_ci#include <linux/module.h> 288c2ecf20Sopenharmony_ci#include <linux/kernel.h> 298c2ecf20Sopenharmony_ci#include <linux/errno.h> 308c2ecf20Sopenharmony_ci#include <linux/string.h> 318c2ecf20Sopenharmony_ci#include <linux/mm.h> 328c2ecf20Sopenharmony_ci#include <linux/tty.h> 338c2ecf20Sopenharmony_ci#include <linux/sysrq.h> 348c2ecf20Sopenharmony_ci#include <linux/delay.h> 358c2ecf20Sopenharmony_ci#include <linux/init.h> 368c2ecf20Sopenharmony_ci#include <linux/screen_info.h> 378c2ecf20Sopenharmony_ci#include <linux/vga_switcheroo.h> 388c2ecf20Sopenharmony_ci#include <linux/console.h> 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci#include <drm/drm_crtc.h> 418c2ecf20Sopenharmony_ci#include <drm/drm_crtc_helper.h> 428c2ecf20Sopenharmony_ci#include <drm/drm_fb_helper.h> 438c2ecf20Sopenharmony_ci#include <drm/drm_fourcc.h> 448c2ecf20Sopenharmony_ci#include <drm/drm_atomic.h> 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#include "nouveau_drv.h" 478c2ecf20Sopenharmony_ci#include "nouveau_gem.h" 488c2ecf20Sopenharmony_ci#include "nouveau_bo.h" 498c2ecf20Sopenharmony_ci#include "nouveau_fbcon.h" 508c2ecf20Sopenharmony_ci#include "nouveau_chan.h" 518c2ecf20Sopenharmony_ci#include "nouveau_vmm.h" 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci#include "nouveau_crtc.h" 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ciMODULE_PARM_DESC(nofbaccel, "Disable fbcon acceleration"); 568c2ecf20Sopenharmony_ciint nouveau_nofbaccel = 0; 578c2ecf20Sopenharmony_cimodule_param_named(nofbaccel, nouveau_nofbaccel, int, 0400); 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ciMODULE_PARM_DESC(fbcon_bpp, "fbcon bits-per-pixel (default: auto)"); 608c2ecf20Sopenharmony_cistatic int nouveau_fbcon_bpp; 618c2ecf20Sopenharmony_cimodule_param_named(fbcon_bpp, nouveau_fbcon_bpp, int, 0400); 628c2ecf20Sopenharmony_ci 638c2ecf20Sopenharmony_cistatic void 648c2ecf20Sopenharmony_cinouveau_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect) 658c2ecf20Sopenharmony_ci{ 668c2ecf20Sopenharmony_ci struct nouveau_fbdev *fbcon = info->par; 678c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 688c2ecf20Sopenharmony_ci struct nvif_device *device = &drm->client.device; 698c2ecf20Sopenharmony_ci int ret; 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci if (info->state != FBINFO_STATE_RUNNING) 728c2ecf20Sopenharmony_ci return; 738c2ecf20Sopenharmony_ci 748c2ecf20Sopenharmony_ci ret = -ENODEV; 758c2ecf20Sopenharmony_ci if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) && 768c2ecf20Sopenharmony_ci mutex_trylock(&drm->client.mutex)) { 778c2ecf20Sopenharmony_ci if (device->info.family < NV_DEVICE_INFO_V0_TESLA) 788c2ecf20Sopenharmony_ci ret = nv04_fbcon_fillrect(info, rect); 798c2ecf20Sopenharmony_ci else 808c2ecf20Sopenharmony_ci if (device->info.family < NV_DEVICE_INFO_V0_FERMI) 818c2ecf20Sopenharmony_ci ret = nv50_fbcon_fillrect(info, rect); 828c2ecf20Sopenharmony_ci else 838c2ecf20Sopenharmony_ci ret = nvc0_fbcon_fillrect(info, rect); 848c2ecf20Sopenharmony_ci mutex_unlock(&drm->client.mutex); 858c2ecf20Sopenharmony_ci } 868c2ecf20Sopenharmony_ci 878c2ecf20Sopenharmony_ci if (ret == 0) 888c2ecf20Sopenharmony_ci return; 898c2ecf20Sopenharmony_ci 908c2ecf20Sopenharmony_ci if (ret != -ENODEV) 918c2ecf20Sopenharmony_ci nouveau_fbcon_gpu_lockup(info); 928c2ecf20Sopenharmony_ci drm_fb_helper_cfb_fillrect(info, rect); 938c2ecf20Sopenharmony_ci} 948c2ecf20Sopenharmony_ci 958c2ecf20Sopenharmony_cistatic void 968c2ecf20Sopenharmony_cinouveau_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *image) 978c2ecf20Sopenharmony_ci{ 988c2ecf20Sopenharmony_ci struct nouveau_fbdev *fbcon = info->par; 998c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 1008c2ecf20Sopenharmony_ci struct nvif_device *device = &drm->client.device; 1018c2ecf20Sopenharmony_ci int ret; 1028c2ecf20Sopenharmony_ci 1038c2ecf20Sopenharmony_ci if (info->state != FBINFO_STATE_RUNNING) 1048c2ecf20Sopenharmony_ci return; 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci ret = -ENODEV; 1078c2ecf20Sopenharmony_ci if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) && 1088c2ecf20Sopenharmony_ci mutex_trylock(&drm->client.mutex)) { 1098c2ecf20Sopenharmony_ci if (device->info.family < NV_DEVICE_INFO_V0_TESLA) 1108c2ecf20Sopenharmony_ci ret = nv04_fbcon_copyarea(info, image); 1118c2ecf20Sopenharmony_ci else 1128c2ecf20Sopenharmony_ci if (device->info.family < NV_DEVICE_INFO_V0_FERMI) 1138c2ecf20Sopenharmony_ci ret = nv50_fbcon_copyarea(info, image); 1148c2ecf20Sopenharmony_ci else 1158c2ecf20Sopenharmony_ci ret = nvc0_fbcon_copyarea(info, image); 1168c2ecf20Sopenharmony_ci mutex_unlock(&drm->client.mutex); 1178c2ecf20Sopenharmony_ci } 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci if (ret == 0) 1208c2ecf20Sopenharmony_ci return; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci if (ret != -ENODEV) 1238c2ecf20Sopenharmony_ci nouveau_fbcon_gpu_lockup(info); 1248c2ecf20Sopenharmony_ci drm_fb_helper_cfb_copyarea(info, image); 1258c2ecf20Sopenharmony_ci} 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_cistatic void 1288c2ecf20Sopenharmony_cinouveau_fbcon_imageblit(struct fb_info *info, const struct fb_image *image) 1298c2ecf20Sopenharmony_ci{ 1308c2ecf20Sopenharmony_ci struct nouveau_fbdev *fbcon = info->par; 1318c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 1328c2ecf20Sopenharmony_ci struct nvif_device *device = &drm->client.device; 1338c2ecf20Sopenharmony_ci int ret; 1348c2ecf20Sopenharmony_ci 1358c2ecf20Sopenharmony_ci if (info->state != FBINFO_STATE_RUNNING) 1368c2ecf20Sopenharmony_ci return; 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_ci ret = -ENODEV; 1398c2ecf20Sopenharmony_ci if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) && 1408c2ecf20Sopenharmony_ci mutex_trylock(&drm->client.mutex)) { 1418c2ecf20Sopenharmony_ci if (device->info.family < NV_DEVICE_INFO_V0_TESLA) 1428c2ecf20Sopenharmony_ci ret = nv04_fbcon_imageblit(info, image); 1438c2ecf20Sopenharmony_ci else 1448c2ecf20Sopenharmony_ci if (device->info.family < NV_DEVICE_INFO_V0_FERMI) 1458c2ecf20Sopenharmony_ci ret = nv50_fbcon_imageblit(info, image); 1468c2ecf20Sopenharmony_ci else 1478c2ecf20Sopenharmony_ci ret = nvc0_fbcon_imageblit(info, image); 1488c2ecf20Sopenharmony_ci mutex_unlock(&drm->client.mutex); 1498c2ecf20Sopenharmony_ci } 1508c2ecf20Sopenharmony_ci 1518c2ecf20Sopenharmony_ci if (ret == 0) 1528c2ecf20Sopenharmony_ci return; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci if (ret != -ENODEV) 1558c2ecf20Sopenharmony_ci nouveau_fbcon_gpu_lockup(info); 1568c2ecf20Sopenharmony_ci drm_fb_helper_cfb_imageblit(info, image); 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic int 1608c2ecf20Sopenharmony_cinouveau_fbcon_sync(struct fb_info *info) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci struct nouveau_fbdev *fbcon = info->par; 1638c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 1648c2ecf20Sopenharmony_ci struct nouveau_channel *chan = drm->channel; 1658c2ecf20Sopenharmony_ci int ret; 1668c2ecf20Sopenharmony_ci 1678c2ecf20Sopenharmony_ci if (!chan || !chan->accel_done || in_interrupt() || 1688c2ecf20Sopenharmony_ci info->state != FBINFO_STATE_RUNNING || 1698c2ecf20Sopenharmony_ci info->flags & FBINFO_HWACCEL_DISABLED) 1708c2ecf20Sopenharmony_ci return 0; 1718c2ecf20Sopenharmony_ci 1728c2ecf20Sopenharmony_ci if (!mutex_trylock(&drm->client.mutex)) 1738c2ecf20Sopenharmony_ci return 0; 1748c2ecf20Sopenharmony_ci 1758c2ecf20Sopenharmony_ci ret = nouveau_channel_idle(chan); 1768c2ecf20Sopenharmony_ci mutex_unlock(&drm->client.mutex); 1778c2ecf20Sopenharmony_ci if (ret) { 1788c2ecf20Sopenharmony_ci nouveau_fbcon_gpu_lockup(info); 1798c2ecf20Sopenharmony_ci return 0; 1808c2ecf20Sopenharmony_ci } 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci chan->accel_done = false; 1838c2ecf20Sopenharmony_ci return 0; 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic int 1878c2ecf20Sopenharmony_cinouveau_fbcon_open(struct fb_info *info, int user) 1888c2ecf20Sopenharmony_ci{ 1898c2ecf20Sopenharmony_ci struct nouveau_fbdev *fbcon = info->par; 1908c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 1918c2ecf20Sopenharmony_ci int ret = pm_runtime_get_sync(drm->dev->dev); 1928c2ecf20Sopenharmony_ci if (ret < 0 && ret != -EACCES) { 1938c2ecf20Sopenharmony_ci pm_runtime_put(drm->dev->dev); 1948c2ecf20Sopenharmony_ci return ret; 1958c2ecf20Sopenharmony_ci } 1968c2ecf20Sopenharmony_ci return 0; 1978c2ecf20Sopenharmony_ci} 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_cistatic int 2008c2ecf20Sopenharmony_cinouveau_fbcon_release(struct fb_info *info, int user) 2018c2ecf20Sopenharmony_ci{ 2028c2ecf20Sopenharmony_ci struct nouveau_fbdev *fbcon = info->par; 2038c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 2048c2ecf20Sopenharmony_ci pm_runtime_put(drm->dev->dev); 2058c2ecf20Sopenharmony_ci return 0; 2068c2ecf20Sopenharmony_ci} 2078c2ecf20Sopenharmony_ci 2088c2ecf20Sopenharmony_cistatic const struct fb_ops nouveau_fbcon_ops = { 2098c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2108c2ecf20Sopenharmony_ci DRM_FB_HELPER_DEFAULT_OPS, 2118c2ecf20Sopenharmony_ci .fb_open = nouveau_fbcon_open, 2128c2ecf20Sopenharmony_ci .fb_release = nouveau_fbcon_release, 2138c2ecf20Sopenharmony_ci .fb_fillrect = nouveau_fbcon_fillrect, 2148c2ecf20Sopenharmony_ci .fb_copyarea = nouveau_fbcon_copyarea, 2158c2ecf20Sopenharmony_ci .fb_imageblit = nouveau_fbcon_imageblit, 2168c2ecf20Sopenharmony_ci .fb_sync = nouveau_fbcon_sync, 2178c2ecf20Sopenharmony_ci}; 2188c2ecf20Sopenharmony_ci 2198c2ecf20Sopenharmony_cistatic const struct fb_ops nouveau_fbcon_sw_ops = { 2208c2ecf20Sopenharmony_ci .owner = THIS_MODULE, 2218c2ecf20Sopenharmony_ci DRM_FB_HELPER_DEFAULT_OPS, 2228c2ecf20Sopenharmony_ci .fb_open = nouveau_fbcon_open, 2238c2ecf20Sopenharmony_ci .fb_release = nouveau_fbcon_release, 2248c2ecf20Sopenharmony_ci .fb_fillrect = drm_fb_helper_cfb_fillrect, 2258c2ecf20Sopenharmony_ci .fb_copyarea = drm_fb_helper_cfb_copyarea, 2268c2ecf20Sopenharmony_ci .fb_imageblit = drm_fb_helper_cfb_imageblit, 2278c2ecf20Sopenharmony_ci}; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_civoid 2308c2ecf20Sopenharmony_cinouveau_fbcon_accel_save_disable(struct drm_device *dev) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 2338c2ecf20Sopenharmony_ci if (drm->fbcon && drm->fbcon->helper.fbdev) { 2348c2ecf20Sopenharmony_ci drm->fbcon->saved_flags = drm->fbcon->helper.fbdev->flags; 2358c2ecf20Sopenharmony_ci drm->fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED; 2368c2ecf20Sopenharmony_ci } 2378c2ecf20Sopenharmony_ci} 2388c2ecf20Sopenharmony_ci 2398c2ecf20Sopenharmony_civoid 2408c2ecf20Sopenharmony_cinouveau_fbcon_accel_restore(struct drm_device *dev) 2418c2ecf20Sopenharmony_ci{ 2428c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 2438c2ecf20Sopenharmony_ci if (drm->fbcon && drm->fbcon->helper.fbdev) { 2448c2ecf20Sopenharmony_ci drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags; 2458c2ecf20Sopenharmony_ci } 2468c2ecf20Sopenharmony_ci} 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_cistatic void 2498c2ecf20Sopenharmony_cinouveau_fbcon_accel_fini(struct drm_device *dev) 2508c2ecf20Sopenharmony_ci{ 2518c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 2528c2ecf20Sopenharmony_ci struct nouveau_fbdev *fbcon = drm->fbcon; 2538c2ecf20Sopenharmony_ci if (fbcon && drm->channel) { 2548c2ecf20Sopenharmony_ci console_lock(); 2558c2ecf20Sopenharmony_ci if (fbcon->helper.fbdev) 2568c2ecf20Sopenharmony_ci fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED; 2578c2ecf20Sopenharmony_ci console_unlock(); 2588c2ecf20Sopenharmony_ci nouveau_channel_idle(drm->channel); 2598c2ecf20Sopenharmony_ci nvif_object_dtor(&fbcon->twod); 2608c2ecf20Sopenharmony_ci nvif_object_dtor(&fbcon->blit); 2618c2ecf20Sopenharmony_ci nvif_object_dtor(&fbcon->gdi); 2628c2ecf20Sopenharmony_ci nvif_object_dtor(&fbcon->patt); 2638c2ecf20Sopenharmony_ci nvif_object_dtor(&fbcon->rop); 2648c2ecf20Sopenharmony_ci nvif_object_dtor(&fbcon->clip); 2658c2ecf20Sopenharmony_ci nvif_object_dtor(&fbcon->surf2d); 2668c2ecf20Sopenharmony_ci } 2678c2ecf20Sopenharmony_ci} 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_cistatic void 2708c2ecf20Sopenharmony_cinouveau_fbcon_accel_init(struct drm_device *dev) 2718c2ecf20Sopenharmony_ci{ 2728c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 2738c2ecf20Sopenharmony_ci struct nouveau_fbdev *fbcon = drm->fbcon; 2748c2ecf20Sopenharmony_ci struct fb_info *info = fbcon->helper.fbdev; 2758c2ecf20Sopenharmony_ci int ret; 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA) 2788c2ecf20Sopenharmony_ci ret = nv04_fbcon_accel_init(info); 2798c2ecf20Sopenharmony_ci else 2808c2ecf20Sopenharmony_ci if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI) 2818c2ecf20Sopenharmony_ci ret = nv50_fbcon_accel_init(info); 2828c2ecf20Sopenharmony_ci else 2838c2ecf20Sopenharmony_ci ret = nvc0_fbcon_accel_init(info); 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci if (ret == 0) 2868c2ecf20Sopenharmony_ci info->fbops = &nouveau_fbcon_ops; 2878c2ecf20Sopenharmony_ci} 2888c2ecf20Sopenharmony_ci 2898c2ecf20Sopenharmony_cistatic void 2908c2ecf20Sopenharmony_cinouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *fbcon) 2918c2ecf20Sopenharmony_ci{ 2928c2ecf20Sopenharmony_ci struct fb_info *info = fbcon->helper.fbdev; 2938c2ecf20Sopenharmony_ci struct fb_fillrect rect; 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci /* Clear the entire fbcon. The drm will program every connector 2968c2ecf20Sopenharmony_ci * with it's preferred mode. If the sizes differ, one display will 2978c2ecf20Sopenharmony_ci * quite likely have garbage around the console. 2988c2ecf20Sopenharmony_ci */ 2998c2ecf20Sopenharmony_ci rect.dx = rect.dy = 0; 3008c2ecf20Sopenharmony_ci rect.width = info->var.xres_virtual; 3018c2ecf20Sopenharmony_ci rect.height = info->var.yres_virtual; 3028c2ecf20Sopenharmony_ci rect.color = 0; 3038c2ecf20Sopenharmony_ci rect.rop = ROP_COPY; 3048c2ecf20Sopenharmony_ci info->fbops->fb_fillrect(info, &rect); 3058c2ecf20Sopenharmony_ci} 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_cistatic int 3088c2ecf20Sopenharmony_cinouveau_fbcon_create(struct drm_fb_helper *helper, 3098c2ecf20Sopenharmony_ci struct drm_fb_helper_surface_size *sizes) 3108c2ecf20Sopenharmony_ci{ 3118c2ecf20Sopenharmony_ci struct nouveau_fbdev *fbcon = 3128c2ecf20Sopenharmony_ci container_of(helper, struct nouveau_fbdev, helper); 3138c2ecf20Sopenharmony_ci struct drm_device *dev = fbcon->helper.dev; 3148c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 3158c2ecf20Sopenharmony_ci struct nvif_device *device = &drm->client.device; 3168c2ecf20Sopenharmony_ci struct fb_info *info; 3178c2ecf20Sopenharmony_ci struct drm_framebuffer *fb; 3188c2ecf20Sopenharmony_ci struct nouveau_channel *chan; 3198c2ecf20Sopenharmony_ci struct nouveau_bo *nvbo; 3208c2ecf20Sopenharmony_ci struct drm_mode_fb_cmd2 mode_cmd = {}; 3218c2ecf20Sopenharmony_ci int ret; 3228c2ecf20Sopenharmony_ci 3238c2ecf20Sopenharmony_ci mode_cmd.width = sizes->surface_width; 3248c2ecf20Sopenharmony_ci mode_cmd.height = sizes->surface_height; 3258c2ecf20Sopenharmony_ci 3268c2ecf20Sopenharmony_ci mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3); 3278c2ecf20Sopenharmony_ci mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256); 3288c2ecf20Sopenharmony_ci 3298c2ecf20Sopenharmony_ci mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, 3308c2ecf20Sopenharmony_ci sizes->surface_depth); 3318c2ecf20Sopenharmony_ci 3328c2ecf20Sopenharmony_ci ret = nouveau_gem_new(&drm->client, mode_cmd.pitches[0] * 3338c2ecf20Sopenharmony_ci mode_cmd.height, 0, NOUVEAU_GEM_DOMAIN_VRAM, 3348c2ecf20Sopenharmony_ci 0, 0x0000, &nvbo); 3358c2ecf20Sopenharmony_ci if (ret) { 3368c2ecf20Sopenharmony_ci NV_ERROR(drm, "failed to allocate framebuffer\n"); 3378c2ecf20Sopenharmony_ci goto out; 3388c2ecf20Sopenharmony_ci } 3398c2ecf20Sopenharmony_ci 3408c2ecf20Sopenharmony_ci ret = nouveau_framebuffer_new(dev, &mode_cmd, &nvbo->bo.base, &fb); 3418c2ecf20Sopenharmony_ci if (ret) 3428c2ecf20Sopenharmony_ci goto out_unref; 3438c2ecf20Sopenharmony_ci 3448c2ecf20Sopenharmony_ci ret = nouveau_bo_pin(nvbo, NOUVEAU_GEM_DOMAIN_VRAM, false); 3458c2ecf20Sopenharmony_ci if (ret) { 3468c2ecf20Sopenharmony_ci NV_ERROR(drm, "failed to pin fb: %d\n", ret); 3478c2ecf20Sopenharmony_ci goto out_unref; 3488c2ecf20Sopenharmony_ci } 3498c2ecf20Sopenharmony_ci 3508c2ecf20Sopenharmony_ci ret = nouveau_bo_map(nvbo); 3518c2ecf20Sopenharmony_ci if (ret) { 3528c2ecf20Sopenharmony_ci NV_ERROR(drm, "failed to map fb: %d\n", ret); 3538c2ecf20Sopenharmony_ci goto out_unpin; 3548c2ecf20Sopenharmony_ci } 3558c2ecf20Sopenharmony_ci 3568c2ecf20Sopenharmony_ci chan = nouveau_nofbaccel ? NULL : drm->channel; 3578c2ecf20Sopenharmony_ci if (chan && device->info.family >= NV_DEVICE_INFO_V0_TESLA) { 3588c2ecf20Sopenharmony_ci ret = nouveau_vma_new(nvbo, chan->vmm, &fbcon->vma); 3598c2ecf20Sopenharmony_ci if (ret) { 3608c2ecf20Sopenharmony_ci NV_ERROR(drm, "failed to map fb into chan: %d\n", ret); 3618c2ecf20Sopenharmony_ci chan = NULL; 3628c2ecf20Sopenharmony_ci } 3638c2ecf20Sopenharmony_ci } 3648c2ecf20Sopenharmony_ci 3658c2ecf20Sopenharmony_ci info = drm_fb_helper_alloc_fbi(helper); 3668c2ecf20Sopenharmony_ci if (IS_ERR(info)) { 3678c2ecf20Sopenharmony_ci ret = PTR_ERR(info); 3688c2ecf20Sopenharmony_ci goto out_unlock; 3698c2ecf20Sopenharmony_ci } 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci /* setup helper */ 3728c2ecf20Sopenharmony_ci fbcon->helper.fb = fb; 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci if (!chan) 3758c2ecf20Sopenharmony_ci info->flags = FBINFO_HWACCEL_DISABLED; 3768c2ecf20Sopenharmony_ci else 3778c2ecf20Sopenharmony_ci info->flags = FBINFO_HWACCEL_COPYAREA | 3788c2ecf20Sopenharmony_ci FBINFO_HWACCEL_FILLRECT | 3798c2ecf20Sopenharmony_ci FBINFO_HWACCEL_IMAGEBLIT; 3808c2ecf20Sopenharmony_ci info->fbops = &nouveau_fbcon_sw_ops; 3818c2ecf20Sopenharmony_ci info->fix.smem_start = nvbo->bo.mem.bus.offset; 3828c2ecf20Sopenharmony_ci info->fix.smem_len = nvbo->bo.mem.num_pages << PAGE_SHIFT; 3838c2ecf20Sopenharmony_ci 3848c2ecf20Sopenharmony_ci info->screen_base = nvbo_kmap_obj_iovirtual(nvbo); 3858c2ecf20Sopenharmony_ci info->screen_size = nvbo->bo.mem.num_pages << PAGE_SHIFT; 3868c2ecf20Sopenharmony_ci 3878c2ecf20Sopenharmony_ci drm_fb_helper_fill_info(info, &fbcon->helper, sizes); 3888c2ecf20Sopenharmony_ci 3898c2ecf20Sopenharmony_ci /* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */ 3908c2ecf20Sopenharmony_ci 3918c2ecf20Sopenharmony_ci if (chan) 3928c2ecf20Sopenharmony_ci nouveau_fbcon_accel_init(dev); 3938c2ecf20Sopenharmony_ci nouveau_fbcon_zfill(dev, fbcon); 3948c2ecf20Sopenharmony_ci 3958c2ecf20Sopenharmony_ci /* To allow resizeing without swapping buffers */ 3968c2ecf20Sopenharmony_ci NV_INFO(drm, "allocated %dx%d fb: 0x%llx, bo %p\n", 3978c2ecf20Sopenharmony_ci fb->width, fb->height, nvbo->offset, nvbo); 3988c2ecf20Sopenharmony_ci 3998c2ecf20Sopenharmony_ci vga_switcheroo_client_fb_set(dev->pdev, info); 4008c2ecf20Sopenharmony_ci return 0; 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ciout_unlock: 4038c2ecf20Sopenharmony_ci if (chan) 4048c2ecf20Sopenharmony_ci nouveau_vma_del(&fbcon->vma); 4058c2ecf20Sopenharmony_ci nouveau_bo_unmap(nvbo); 4068c2ecf20Sopenharmony_ciout_unpin: 4078c2ecf20Sopenharmony_ci nouveau_bo_unpin(nvbo); 4088c2ecf20Sopenharmony_ciout_unref: 4098c2ecf20Sopenharmony_ci nouveau_bo_ref(NULL, &nvbo); 4108c2ecf20Sopenharmony_ciout: 4118c2ecf20Sopenharmony_ci return ret; 4128c2ecf20Sopenharmony_ci} 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_cistatic int 4158c2ecf20Sopenharmony_cinouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon) 4168c2ecf20Sopenharmony_ci{ 4178c2ecf20Sopenharmony_ci struct drm_framebuffer *fb = fbcon->helper.fb; 4188c2ecf20Sopenharmony_ci struct nouveau_bo *nvbo; 4198c2ecf20Sopenharmony_ci 4208c2ecf20Sopenharmony_ci drm_fb_helper_unregister_fbi(&fbcon->helper); 4218c2ecf20Sopenharmony_ci drm_fb_helper_fini(&fbcon->helper); 4228c2ecf20Sopenharmony_ci 4238c2ecf20Sopenharmony_ci if (fb && fb->obj[0]) { 4248c2ecf20Sopenharmony_ci nvbo = nouveau_gem_object(fb->obj[0]); 4258c2ecf20Sopenharmony_ci nouveau_vma_del(&fbcon->vma); 4268c2ecf20Sopenharmony_ci nouveau_bo_unmap(nvbo); 4278c2ecf20Sopenharmony_ci nouveau_bo_unpin(nvbo); 4288c2ecf20Sopenharmony_ci drm_framebuffer_put(fb); 4298c2ecf20Sopenharmony_ci } 4308c2ecf20Sopenharmony_ci 4318c2ecf20Sopenharmony_ci return 0; 4328c2ecf20Sopenharmony_ci} 4338c2ecf20Sopenharmony_ci 4348c2ecf20Sopenharmony_civoid nouveau_fbcon_gpu_lockup(struct fb_info *info) 4358c2ecf20Sopenharmony_ci{ 4368c2ecf20Sopenharmony_ci struct nouveau_fbdev *fbcon = info->par; 4378c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev); 4388c2ecf20Sopenharmony_ci 4398c2ecf20Sopenharmony_ci NV_ERROR(drm, "GPU lockup - switching to software fbcon\n"); 4408c2ecf20Sopenharmony_ci info->flags |= FBINFO_HWACCEL_DISABLED; 4418c2ecf20Sopenharmony_ci} 4428c2ecf20Sopenharmony_ci 4438c2ecf20Sopenharmony_cistatic const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = { 4448c2ecf20Sopenharmony_ci .fb_probe = nouveau_fbcon_create, 4458c2ecf20Sopenharmony_ci}; 4468c2ecf20Sopenharmony_ci 4478c2ecf20Sopenharmony_cistatic void 4488c2ecf20Sopenharmony_cinouveau_fbcon_set_suspend_work(struct work_struct *work) 4498c2ecf20Sopenharmony_ci{ 4508c2ecf20Sopenharmony_ci struct nouveau_drm *drm = container_of(work, typeof(*drm), fbcon_work); 4518c2ecf20Sopenharmony_ci int state = READ_ONCE(drm->fbcon_new_state); 4528c2ecf20Sopenharmony_ci 4538c2ecf20Sopenharmony_ci if (state == FBINFO_STATE_RUNNING) 4548c2ecf20Sopenharmony_ci pm_runtime_get_sync(drm->dev->dev); 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci console_lock(); 4578c2ecf20Sopenharmony_ci if (state == FBINFO_STATE_RUNNING) 4588c2ecf20Sopenharmony_ci nouveau_fbcon_accel_restore(drm->dev); 4598c2ecf20Sopenharmony_ci drm_fb_helper_set_suspend(&drm->fbcon->helper, state); 4608c2ecf20Sopenharmony_ci if (state != FBINFO_STATE_RUNNING) 4618c2ecf20Sopenharmony_ci nouveau_fbcon_accel_save_disable(drm->dev); 4628c2ecf20Sopenharmony_ci console_unlock(); 4638c2ecf20Sopenharmony_ci 4648c2ecf20Sopenharmony_ci if (state == FBINFO_STATE_RUNNING) { 4658c2ecf20Sopenharmony_ci nouveau_fbcon_hotplug_resume(drm->fbcon); 4668c2ecf20Sopenharmony_ci pm_runtime_mark_last_busy(drm->dev->dev); 4678c2ecf20Sopenharmony_ci pm_runtime_put_autosuspend(drm->dev->dev); 4688c2ecf20Sopenharmony_ci } 4698c2ecf20Sopenharmony_ci} 4708c2ecf20Sopenharmony_ci 4718c2ecf20Sopenharmony_civoid 4728c2ecf20Sopenharmony_cinouveau_fbcon_set_suspend(struct drm_device *dev, int state) 4738c2ecf20Sopenharmony_ci{ 4748c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 4758c2ecf20Sopenharmony_ci 4768c2ecf20Sopenharmony_ci if (!drm->fbcon) 4778c2ecf20Sopenharmony_ci return; 4788c2ecf20Sopenharmony_ci 4798c2ecf20Sopenharmony_ci drm->fbcon_new_state = state; 4808c2ecf20Sopenharmony_ci /* Since runtime resume can happen as a result of a sysfs operation, 4818c2ecf20Sopenharmony_ci * it's possible we already have the console locked. So handle fbcon 4828c2ecf20Sopenharmony_ci * init/deinit from a seperate work thread 4838c2ecf20Sopenharmony_ci */ 4848c2ecf20Sopenharmony_ci schedule_work(&drm->fbcon_work); 4858c2ecf20Sopenharmony_ci} 4868c2ecf20Sopenharmony_ci 4878c2ecf20Sopenharmony_civoid 4888c2ecf20Sopenharmony_cinouveau_fbcon_output_poll_changed(struct drm_device *dev) 4898c2ecf20Sopenharmony_ci{ 4908c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 4918c2ecf20Sopenharmony_ci struct nouveau_fbdev *fbcon = drm->fbcon; 4928c2ecf20Sopenharmony_ci int ret; 4938c2ecf20Sopenharmony_ci 4948c2ecf20Sopenharmony_ci if (!fbcon) 4958c2ecf20Sopenharmony_ci return; 4968c2ecf20Sopenharmony_ci 4978c2ecf20Sopenharmony_ci mutex_lock(&fbcon->hotplug_lock); 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci ret = pm_runtime_get(dev->dev); 5008c2ecf20Sopenharmony_ci if (ret == 1 || ret == -EACCES) { 5018c2ecf20Sopenharmony_ci drm_fb_helper_hotplug_event(&fbcon->helper); 5028c2ecf20Sopenharmony_ci 5038c2ecf20Sopenharmony_ci pm_runtime_mark_last_busy(dev->dev); 5048c2ecf20Sopenharmony_ci pm_runtime_put_autosuspend(dev->dev); 5058c2ecf20Sopenharmony_ci } else if (ret == 0) { 5068c2ecf20Sopenharmony_ci /* If the GPU was already in the process of suspending before 5078c2ecf20Sopenharmony_ci * this event happened, then we can't block here as we'll 5088c2ecf20Sopenharmony_ci * deadlock the runtime pmops since they wait for us to 5098c2ecf20Sopenharmony_ci * finish. So, just defer this event for when we runtime 5108c2ecf20Sopenharmony_ci * resume again. It will be handled by fbcon_work. 5118c2ecf20Sopenharmony_ci */ 5128c2ecf20Sopenharmony_ci NV_DEBUG(drm, "fbcon HPD event deferred until runtime resume\n"); 5138c2ecf20Sopenharmony_ci fbcon->hotplug_waiting = true; 5148c2ecf20Sopenharmony_ci pm_runtime_put_noidle(drm->dev->dev); 5158c2ecf20Sopenharmony_ci } else { 5168c2ecf20Sopenharmony_ci DRM_WARN("fbcon HPD event lost due to RPM failure: %d\n", 5178c2ecf20Sopenharmony_ci ret); 5188c2ecf20Sopenharmony_ci } 5198c2ecf20Sopenharmony_ci 5208c2ecf20Sopenharmony_ci mutex_unlock(&fbcon->hotplug_lock); 5218c2ecf20Sopenharmony_ci} 5228c2ecf20Sopenharmony_ci 5238c2ecf20Sopenharmony_civoid 5248c2ecf20Sopenharmony_cinouveau_fbcon_hotplug_resume(struct nouveau_fbdev *fbcon) 5258c2ecf20Sopenharmony_ci{ 5268c2ecf20Sopenharmony_ci struct nouveau_drm *drm; 5278c2ecf20Sopenharmony_ci 5288c2ecf20Sopenharmony_ci if (!fbcon) 5298c2ecf20Sopenharmony_ci return; 5308c2ecf20Sopenharmony_ci drm = nouveau_drm(fbcon->helper.dev); 5318c2ecf20Sopenharmony_ci 5328c2ecf20Sopenharmony_ci mutex_lock(&fbcon->hotplug_lock); 5338c2ecf20Sopenharmony_ci if (fbcon->hotplug_waiting) { 5348c2ecf20Sopenharmony_ci fbcon->hotplug_waiting = false; 5358c2ecf20Sopenharmony_ci 5368c2ecf20Sopenharmony_ci NV_DEBUG(drm, "Handling deferred fbcon HPD events\n"); 5378c2ecf20Sopenharmony_ci drm_fb_helper_hotplug_event(&fbcon->helper); 5388c2ecf20Sopenharmony_ci } 5398c2ecf20Sopenharmony_ci mutex_unlock(&fbcon->hotplug_lock); 5408c2ecf20Sopenharmony_ci} 5418c2ecf20Sopenharmony_ci 5428c2ecf20Sopenharmony_ciint 5438c2ecf20Sopenharmony_cinouveau_fbcon_init(struct drm_device *dev) 5448c2ecf20Sopenharmony_ci{ 5458c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 5468c2ecf20Sopenharmony_ci struct nouveau_fbdev *fbcon; 5478c2ecf20Sopenharmony_ci int preferred_bpp = nouveau_fbcon_bpp; 5488c2ecf20Sopenharmony_ci int ret; 5498c2ecf20Sopenharmony_ci 5508c2ecf20Sopenharmony_ci if (!dev->mode_config.num_crtc || 5518c2ecf20Sopenharmony_ci (dev->pdev->class >> 8) != PCI_CLASS_DISPLAY_VGA) 5528c2ecf20Sopenharmony_ci return 0; 5538c2ecf20Sopenharmony_ci 5548c2ecf20Sopenharmony_ci fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL); 5558c2ecf20Sopenharmony_ci if (!fbcon) 5568c2ecf20Sopenharmony_ci return -ENOMEM; 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci drm->fbcon = fbcon; 5598c2ecf20Sopenharmony_ci INIT_WORK(&drm->fbcon_work, nouveau_fbcon_set_suspend_work); 5608c2ecf20Sopenharmony_ci mutex_init(&fbcon->hotplug_lock); 5618c2ecf20Sopenharmony_ci 5628c2ecf20Sopenharmony_ci drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs); 5638c2ecf20Sopenharmony_ci 5648c2ecf20Sopenharmony_ci ret = drm_fb_helper_init(dev, &fbcon->helper); 5658c2ecf20Sopenharmony_ci if (ret) 5668c2ecf20Sopenharmony_ci goto free; 5678c2ecf20Sopenharmony_ci 5688c2ecf20Sopenharmony_ci if (preferred_bpp != 8 && preferred_bpp != 16 && preferred_bpp != 32) { 5698c2ecf20Sopenharmony_ci if (drm->client.device.info.ram_size <= 32 * 1024 * 1024) 5708c2ecf20Sopenharmony_ci preferred_bpp = 8; 5718c2ecf20Sopenharmony_ci else 5728c2ecf20Sopenharmony_ci if (drm->client.device.info.ram_size <= 64 * 1024 * 1024) 5738c2ecf20Sopenharmony_ci preferred_bpp = 16; 5748c2ecf20Sopenharmony_ci else 5758c2ecf20Sopenharmony_ci preferred_bpp = 32; 5768c2ecf20Sopenharmony_ci } 5778c2ecf20Sopenharmony_ci 5788c2ecf20Sopenharmony_ci /* disable all the possible outputs/crtcs before entering KMS mode */ 5798c2ecf20Sopenharmony_ci if (!drm_drv_uses_atomic_modeset(dev)) 5808c2ecf20Sopenharmony_ci drm_helper_disable_unused_functions(dev); 5818c2ecf20Sopenharmony_ci 5828c2ecf20Sopenharmony_ci ret = drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp); 5838c2ecf20Sopenharmony_ci if (ret) 5848c2ecf20Sopenharmony_ci goto fini; 5858c2ecf20Sopenharmony_ci 5868c2ecf20Sopenharmony_ci if (fbcon->helper.fbdev) 5878c2ecf20Sopenharmony_ci fbcon->helper.fbdev->pixmap.buf_align = 4; 5888c2ecf20Sopenharmony_ci return 0; 5898c2ecf20Sopenharmony_ci 5908c2ecf20Sopenharmony_cifini: 5918c2ecf20Sopenharmony_ci drm_fb_helper_fini(&fbcon->helper); 5928c2ecf20Sopenharmony_cifree: 5938c2ecf20Sopenharmony_ci kfree(fbcon); 5948c2ecf20Sopenharmony_ci drm->fbcon = NULL; 5958c2ecf20Sopenharmony_ci return ret; 5968c2ecf20Sopenharmony_ci} 5978c2ecf20Sopenharmony_ci 5988c2ecf20Sopenharmony_civoid 5998c2ecf20Sopenharmony_cinouveau_fbcon_fini(struct drm_device *dev) 6008c2ecf20Sopenharmony_ci{ 6018c2ecf20Sopenharmony_ci struct nouveau_drm *drm = nouveau_drm(dev); 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_ci if (!drm->fbcon) 6048c2ecf20Sopenharmony_ci return; 6058c2ecf20Sopenharmony_ci 6068c2ecf20Sopenharmony_ci nouveau_fbcon_accel_fini(dev); 6078c2ecf20Sopenharmony_ci nouveau_fbcon_destroy(dev, drm->fbcon); 6088c2ecf20Sopenharmony_ci kfree(drm->fbcon); 6098c2ecf20Sopenharmony_ci drm->fbcon = NULL; 6108c2ecf20Sopenharmony_ci} 611