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