xref: /OK3568_Linux_fs/kernel/drivers/gpu/drm/nouveau/nouveau_fbcon.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
1*4882a593Smuzhiyun /*
2*4882a593Smuzhiyun  * Copyright © 2007 David Airlie
3*4882a593Smuzhiyun  *
4*4882a593Smuzhiyun  * Permission is hereby granted, free of charge, to any person obtaining a
5*4882a593Smuzhiyun  * copy of this software and associated documentation files (the "Software"),
6*4882a593Smuzhiyun  * to deal in the Software without restriction, including without limitation
7*4882a593Smuzhiyun  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8*4882a593Smuzhiyun  * and/or sell copies of the Software, and to permit persons to whom the
9*4882a593Smuzhiyun  * Software is furnished to do so, subject to the following conditions:
10*4882a593Smuzhiyun  *
11*4882a593Smuzhiyun  * The above copyright notice and this permission notice (including the next
12*4882a593Smuzhiyun  * paragraph) shall be included in all copies or substantial portions of the
13*4882a593Smuzhiyun  * Software.
14*4882a593Smuzhiyun  *
15*4882a593Smuzhiyun  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16*4882a593Smuzhiyun  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17*4882a593Smuzhiyun  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18*4882a593Smuzhiyun  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19*4882a593Smuzhiyun  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20*4882a593Smuzhiyun  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21*4882a593Smuzhiyun  * DEALINGS IN THE SOFTWARE.
22*4882a593Smuzhiyun  *
23*4882a593Smuzhiyun  * Authors:
24*4882a593Smuzhiyun  *     David Airlie
25*4882a593Smuzhiyun  */
26*4882a593Smuzhiyun 
27*4882a593Smuzhiyun #include <linux/module.h>
28*4882a593Smuzhiyun #include <linux/kernel.h>
29*4882a593Smuzhiyun #include <linux/errno.h>
30*4882a593Smuzhiyun #include <linux/string.h>
31*4882a593Smuzhiyun #include <linux/mm.h>
32*4882a593Smuzhiyun #include <linux/tty.h>
33*4882a593Smuzhiyun #include <linux/sysrq.h>
34*4882a593Smuzhiyun #include <linux/delay.h>
35*4882a593Smuzhiyun #include <linux/init.h>
36*4882a593Smuzhiyun #include <linux/screen_info.h>
37*4882a593Smuzhiyun #include <linux/vga_switcheroo.h>
38*4882a593Smuzhiyun #include <linux/console.h>
39*4882a593Smuzhiyun 
40*4882a593Smuzhiyun #include <drm/drm_crtc.h>
41*4882a593Smuzhiyun #include <drm/drm_crtc_helper.h>
42*4882a593Smuzhiyun #include <drm/drm_fb_helper.h>
43*4882a593Smuzhiyun #include <drm/drm_fourcc.h>
44*4882a593Smuzhiyun #include <drm/drm_atomic.h>
45*4882a593Smuzhiyun 
46*4882a593Smuzhiyun #include "nouveau_drv.h"
47*4882a593Smuzhiyun #include "nouveau_gem.h"
48*4882a593Smuzhiyun #include "nouveau_bo.h"
49*4882a593Smuzhiyun #include "nouveau_fbcon.h"
50*4882a593Smuzhiyun #include "nouveau_chan.h"
51*4882a593Smuzhiyun #include "nouveau_vmm.h"
52*4882a593Smuzhiyun 
53*4882a593Smuzhiyun #include "nouveau_crtc.h"
54*4882a593Smuzhiyun 
55*4882a593Smuzhiyun MODULE_PARM_DESC(nofbaccel, "Disable fbcon acceleration");
56*4882a593Smuzhiyun int nouveau_nofbaccel = 0;
57*4882a593Smuzhiyun module_param_named(nofbaccel, nouveau_nofbaccel, int, 0400);
58*4882a593Smuzhiyun 
59*4882a593Smuzhiyun MODULE_PARM_DESC(fbcon_bpp, "fbcon bits-per-pixel (default: auto)");
60*4882a593Smuzhiyun static int nouveau_fbcon_bpp;
61*4882a593Smuzhiyun module_param_named(fbcon_bpp, nouveau_fbcon_bpp, int, 0400);
62*4882a593Smuzhiyun 
63*4882a593Smuzhiyun static void
nouveau_fbcon_fillrect(struct fb_info * info,const struct fb_fillrect * rect)64*4882a593Smuzhiyun nouveau_fbcon_fillrect(struct fb_info *info, const struct fb_fillrect *rect)
65*4882a593Smuzhiyun {
66*4882a593Smuzhiyun 	struct nouveau_fbdev *fbcon = info->par;
67*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
68*4882a593Smuzhiyun 	struct nvif_device *device = &drm->client.device;
69*4882a593Smuzhiyun 	int ret;
70*4882a593Smuzhiyun 
71*4882a593Smuzhiyun 	if (info->state != FBINFO_STATE_RUNNING)
72*4882a593Smuzhiyun 		return;
73*4882a593Smuzhiyun 
74*4882a593Smuzhiyun 	ret = -ENODEV;
75*4882a593Smuzhiyun 	if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
76*4882a593Smuzhiyun 	    mutex_trylock(&drm->client.mutex)) {
77*4882a593Smuzhiyun 		if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
78*4882a593Smuzhiyun 			ret = nv04_fbcon_fillrect(info, rect);
79*4882a593Smuzhiyun 		else
80*4882a593Smuzhiyun 		if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
81*4882a593Smuzhiyun 			ret = nv50_fbcon_fillrect(info, rect);
82*4882a593Smuzhiyun 		else
83*4882a593Smuzhiyun 			ret = nvc0_fbcon_fillrect(info, rect);
84*4882a593Smuzhiyun 		mutex_unlock(&drm->client.mutex);
85*4882a593Smuzhiyun 	}
86*4882a593Smuzhiyun 
87*4882a593Smuzhiyun 	if (ret == 0)
88*4882a593Smuzhiyun 		return;
89*4882a593Smuzhiyun 
90*4882a593Smuzhiyun 	if (ret != -ENODEV)
91*4882a593Smuzhiyun 		nouveau_fbcon_gpu_lockup(info);
92*4882a593Smuzhiyun 	drm_fb_helper_cfb_fillrect(info, rect);
93*4882a593Smuzhiyun }
94*4882a593Smuzhiyun 
95*4882a593Smuzhiyun static void
nouveau_fbcon_copyarea(struct fb_info * info,const struct fb_copyarea * image)96*4882a593Smuzhiyun nouveau_fbcon_copyarea(struct fb_info *info, const struct fb_copyarea *image)
97*4882a593Smuzhiyun {
98*4882a593Smuzhiyun 	struct nouveau_fbdev *fbcon = info->par;
99*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
100*4882a593Smuzhiyun 	struct nvif_device *device = &drm->client.device;
101*4882a593Smuzhiyun 	int ret;
102*4882a593Smuzhiyun 
103*4882a593Smuzhiyun 	if (info->state != FBINFO_STATE_RUNNING)
104*4882a593Smuzhiyun 		return;
105*4882a593Smuzhiyun 
106*4882a593Smuzhiyun 	ret = -ENODEV;
107*4882a593Smuzhiyun 	if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
108*4882a593Smuzhiyun 	    mutex_trylock(&drm->client.mutex)) {
109*4882a593Smuzhiyun 		if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
110*4882a593Smuzhiyun 			ret = nv04_fbcon_copyarea(info, image);
111*4882a593Smuzhiyun 		else
112*4882a593Smuzhiyun 		if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
113*4882a593Smuzhiyun 			ret = nv50_fbcon_copyarea(info, image);
114*4882a593Smuzhiyun 		else
115*4882a593Smuzhiyun 			ret = nvc0_fbcon_copyarea(info, image);
116*4882a593Smuzhiyun 		mutex_unlock(&drm->client.mutex);
117*4882a593Smuzhiyun 	}
118*4882a593Smuzhiyun 
119*4882a593Smuzhiyun 	if (ret == 0)
120*4882a593Smuzhiyun 		return;
121*4882a593Smuzhiyun 
122*4882a593Smuzhiyun 	if (ret != -ENODEV)
123*4882a593Smuzhiyun 		nouveau_fbcon_gpu_lockup(info);
124*4882a593Smuzhiyun 	drm_fb_helper_cfb_copyarea(info, image);
125*4882a593Smuzhiyun }
126*4882a593Smuzhiyun 
127*4882a593Smuzhiyun static void
nouveau_fbcon_imageblit(struct fb_info * info,const struct fb_image * image)128*4882a593Smuzhiyun nouveau_fbcon_imageblit(struct fb_info *info, const struct fb_image *image)
129*4882a593Smuzhiyun {
130*4882a593Smuzhiyun 	struct nouveau_fbdev *fbcon = info->par;
131*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
132*4882a593Smuzhiyun 	struct nvif_device *device = &drm->client.device;
133*4882a593Smuzhiyun 	int ret;
134*4882a593Smuzhiyun 
135*4882a593Smuzhiyun 	if (info->state != FBINFO_STATE_RUNNING)
136*4882a593Smuzhiyun 		return;
137*4882a593Smuzhiyun 
138*4882a593Smuzhiyun 	ret = -ENODEV;
139*4882a593Smuzhiyun 	if (!in_interrupt() && !(info->flags & FBINFO_HWACCEL_DISABLED) &&
140*4882a593Smuzhiyun 	    mutex_trylock(&drm->client.mutex)) {
141*4882a593Smuzhiyun 		if (device->info.family < NV_DEVICE_INFO_V0_TESLA)
142*4882a593Smuzhiyun 			ret = nv04_fbcon_imageblit(info, image);
143*4882a593Smuzhiyun 		else
144*4882a593Smuzhiyun 		if (device->info.family < NV_DEVICE_INFO_V0_FERMI)
145*4882a593Smuzhiyun 			ret = nv50_fbcon_imageblit(info, image);
146*4882a593Smuzhiyun 		else
147*4882a593Smuzhiyun 			ret = nvc0_fbcon_imageblit(info, image);
148*4882a593Smuzhiyun 		mutex_unlock(&drm->client.mutex);
149*4882a593Smuzhiyun 	}
150*4882a593Smuzhiyun 
151*4882a593Smuzhiyun 	if (ret == 0)
152*4882a593Smuzhiyun 		return;
153*4882a593Smuzhiyun 
154*4882a593Smuzhiyun 	if (ret != -ENODEV)
155*4882a593Smuzhiyun 		nouveau_fbcon_gpu_lockup(info);
156*4882a593Smuzhiyun 	drm_fb_helper_cfb_imageblit(info, image);
157*4882a593Smuzhiyun }
158*4882a593Smuzhiyun 
159*4882a593Smuzhiyun static int
nouveau_fbcon_sync(struct fb_info * info)160*4882a593Smuzhiyun nouveau_fbcon_sync(struct fb_info *info)
161*4882a593Smuzhiyun {
162*4882a593Smuzhiyun 	struct nouveau_fbdev *fbcon = info->par;
163*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
164*4882a593Smuzhiyun 	struct nouveau_channel *chan = drm->channel;
165*4882a593Smuzhiyun 	int ret;
166*4882a593Smuzhiyun 
167*4882a593Smuzhiyun 	if (!chan || !chan->accel_done || in_interrupt() ||
168*4882a593Smuzhiyun 	    info->state != FBINFO_STATE_RUNNING ||
169*4882a593Smuzhiyun 	    info->flags & FBINFO_HWACCEL_DISABLED)
170*4882a593Smuzhiyun 		return 0;
171*4882a593Smuzhiyun 
172*4882a593Smuzhiyun 	if (!mutex_trylock(&drm->client.mutex))
173*4882a593Smuzhiyun 		return 0;
174*4882a593Smuzhiyun 
175*4882a593Smuzhiyun 	ret = nouveau_channel_idle(chan);
176*4882a593Smuzhiyun 	mutex_unlock(&drm->client.mutex);
177*4882a593Smuzhiyun 	if (ret) {
178*4882a593Smuzhiyun 		nouveau_fbcon_gpu_lockup(info);
179*4882a593Smuzhiyun 		return 0;
180*4882a593Smuzhiyun 	}
181*4882a593Smuzhiyun 
182*4882a593Smuzhiyun 	chan->accel_done = false;
183*4882a593Smuzhiyun 	return 0;
184*4882a593Smuzhiyun }
185*4882a593Smuzhiyun 
186*4882a593Smuzhiyun static int
nouveau_fbcon_open(struct fb_info * info,int user)187*4882a593Smuzhiyun nouveau_fbcon_open(struct fb_info *info, int user)
188*4882a593Smuzhiyun {
189*4882a593Smuzhiyun 	struct nouveau_fbdev *fbcon = info->par;
190*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
191*4882a593Smuzhiyun 	int ret = pm_runtime_get_sync(drm->dev->dev);
192*4882a593Smuzhiyun 	if (ret < 0 && ret != -EACCES) {
193*4882a593Smuzhiyun 		pm_runtime_put(drm->dev->dev);
194*4882a593Smuzhiyun 		return ret;
195*4882a593Smuzhiyun 	}
196*4882a593Smuzhiyun 	return 0;
197*4882a593Smuzhiyun }
198*4882a593Smuzhiyun 
199*4882a593Smuzhiyun static int
nouveau_fbcon_release(struct fb_info * info,int user)200*4882a593Smuzhiyun nouveau_fbcon_release(struct fb_info *info, int user)
201*4882a593Smuzhiyun {
202*4882a593Smuzhiyun 	struct nouveau_fbdev *fbcon = info->par;
203*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
204*4882a593Smuzhiyun 	pm_runtime_put(drm->dev->dev);
205*4882a593Smuzhiyun 	return 0;
206*4882a593Smuzhiyun }
207*4882a593Smuzhiyun 
208*4882a593Smuzhiyun static const struct fb_ops nouveau_fbcon_ops = {
209*4882a593Smuzhiyun 	.owner = THIS_MODULE,
210*4882a593Smuzhiyun 	DRM_FB_HELPER_DEFAULT_OPS,
211*4882a593Smuzhiyun 	.fb_open = nouveau_fbcon_open,
212*4882a593Smuzhiyun 	.fb_release = nouveau_fbcon_release,
213*4882a593Smuzhiyun 	.fb_fillrect = nouveau_fbcon_fillrect,
214*4882a593Smuzhiyun 	.fb_copyarea = nouveau_fbcon_copyarea,
215*4882a593Smuzhiyun 	.fb_imageblit = nouveau_fbcon_imageblit,
216*4882a593Smuzhiyun 	.fb_sync = nouveau_fbcon_sync,
217*4882a593Smuzhiyun };
218*4882a593Smuzhiyun 
219*4882a593Smuzhiyun static const struct fb_ops nouveau_fbcon_sw_ops = {
220*4882a593Smuzhiyun 	.owner = THIS_MODULE,
221*4882a593Smuzhiyun 	DRM_FB_HELPER_DEFAULT_OPS,
222*4882a593Smuzhiyun 	.fb_open = nouveau_fbcon_open,
223*4882a593Smuzhiyun 	.fb_release = nouveau_fbcon_release,
224*4882a593Smuzhiyun 	.fb_fillrect = drm_fb_helper_cfb_fillrect,
225*4882a593Smuzhiyun 	.fb_copyarea = drm_fb_helper_cfb_copyarea,
226*4882a593Smuzhiyun 	.fb_imageblit = drm_fb_helper_cfb_imageblit,
227*4882a593Smuzhiyun };
228*4882a593Smuzhiyun 
229*4882a593Smuzhiyun void
nouveau_fbcon_accel_save_disable(struct drm_device * dev)230*4882a593Smuzhiyun nouveau_fbcon_accel_save_disable(struct drm_device *dev)
231*4882a593Smuzhiyun {
232*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(dev);
233*4882a593Smuzhiyun 	if (drm->fbcon && drm->fbcon->helper.fbdev) {
234*4882a593Smuzhiyun 		drm->fbcon->saved_flags = drm->fbcon->helper.fbdev->flags;
235*4882a593Smuzhiyun 		drm->fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
236*4882a593Smuzhiyun 	}
237*4882a593Smuzhiyun }
238*4882a593Smuzhiyun 
239*4882a593Smuzhiyun void
nouveau_fbcon_accel_restore(struct drm_device * dev)240*4882a593Smuzhiyun nouveau_fbcon_accel_restore(struct drm_device *dev)
241*4882a593Smuzhiyun {
242*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(dev);
243*4882a593Smuzhiyun 	if (drm->fbcon && drm->fbcon->helper.fbdev) {
244*4882a593Smuzhiyun 		drm->fbcon->helper.fbdev->flags = drm->fbcon->saved_flags;
245*4882a593Smuzhiyun 	}
246*4882a593Smuzhiyun }
247*4882a593Smuzhiyun 
248*4882a593Smuzhiyun static void
nouveau_fbcon_accel_fini(struct drm_device * dev)249*4882a593Smuzhiyun nouveau_fbcon_accel_fini(struct drm_device *dev)
250*4882a593Smuzhiyun {
251*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(dev);
252*4882a593Smuzhiyun 	struct nouveau_fbdev *fbcon = drm->fbcon;
253*4882a593Smuzhiyun 	if (fbcon && drm->channel) {
254*4882a593Smuzhiyun 		console_lock();
255*4882a593Smuzhiyun 		if (fbcon->helper.fbdev)
256*4882a593Smuzhiyun 			fbcon->helper.fbdev->flags |= FBINFO_HWACCEL_DISABLED;
257*4882a593Smuzhiyun 		console_unlock();
258*4882a593Smuzhiyun 		nouveau_channel_idle(drm->channel);
259*4882a593Smuzhiyun 		nvif_object_dtor(&fbcon->twod);
260*4882a593Smuzhiyun 		nvif_object_dtor(&fbcon->blit);
261*4882a593Smuzhiyun 		nvif_object_dtor(&fbcon->gdi);
262*4882a593Smuzhiyun 		nvif_object_dtor(&fbcon->patt);
263*4882a593Smuzhiyun 		nvif_object_dtor(&fbcon->rop);
264*4882a593Smuzhiyun 		nvif_object_dtor(&fbcon->clip);
265*4882a593Smuzhiyun 		nvif_object_dtor(&fbcon->surf2d);
266*4882a593Smuzhiyun 	}
267*4882a593Smuzhiyun }
268*4882a593Smuzhiyun 
269*4882a593Smuzhiyun static void
nouveau_fbcon_accel_init(struct drm_device * dev)270*4882a593Smuzhiyun nouveau_fbcon_accel_init(struct drm_device *dev)
271*4882a593Smuzhiyun {
272*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(dev);
273*4882a593Smuzhiyun 	struct nouveau_fbdev *fbcon = drm->fbcon;
274*4882a593Smuzhiyun 	struct fb_info *info = fbcon->helper.fbdev;
275*4882a593Smuzhiyun 	int ret;
276*4882a593Smuzhiyun 
277*4882a593Smuzhiyun 	if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA)
278*4882a593Smuzhiyun 		ret = nv04_fbcon_accel_init(info);
279*4882a593Smuzhiyun 	else
280*4882a593Smuzhiyun 	if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI)
281*4882a593Smuzhiyun 		ret = nv50_fbcon_accel_init(info);
282*4882a593Smuzhiyun 	else
283*4882a593Smuzhiyun 		ret = nvc0_fbcon_accel_init(info);
284*4882a593Smuzhiyun 
285*4882a593Smuzhiyun 	if (ret == 0)
286*4882a593Smuzhiyun 		info->fbops = &nouveau_fbcon_ops;
287*4882a593Smuzhiyun }
288*4882a593Smuzhiyun 
289*4882a593Smuzhiyun static void
nouveau_fbcon_zfill(struct drm_device * dev,struct nouveau_fbdev * fbcon)290*4882a593Smuzhiyun nouveau_fbcon_zfill(struct drm_device *dev, struct nouveau_fbdev *fbcon)
291*4882a593Smuzhiyun {
292*4882a593Smuzhiyun 	struct fb_info *info = fbcon->helper.fbdev;
293*4882a593Smuzhiyun 	struct fb_fillrect rect;
294*4882a593Smuzhiyun 
295*4882a593Smuzhiyun 	/* Clear the entire fbcon.  The drm will program every connector
296*4882a593Smuzhiyun 	 * with it's preferred mode.  If the sizes differ, one display will
297*4882a593Smuzhiyun 	 * quite likely have garbage around the console.
298*4882a593Smuzhiyun 	 */
299*4882a593Smuzhiyun 	rect.dx = rect.dy = 0;
300*4882a593Smuzhiyun 	rect.width = info->var.xres_virtual;
301*4882a593Smuzhiyun 	rect.height = info->var.yres_virtual;
302*4882a593Smuzhiyun 	rect.color = 0;
303*4882a593Smuzhiyun 	rect.rop = ROP_COPY;
304*4882a593Smuzhiyun 	info->fbops->fb_fillrect(info, &rect);
305*4882a593Smuzhiyun }
306*4882a593Smuzhiyun 
307*4882a593Smuzhiyun static int
nouveau_fbcon_create(struct drm_fb_helper * helper,struct drm_fb_helper_surface_size * sizes)308*4882a593Smuzhiyun nouveau_fbcon_create(struct drm_fb_helper *helper,
309*4882a593Smuzhiyun 		     struct drm_fb_helper_surface_size *sizes)
310*4882a593Smuzhiyun {
311*4882a593Smuzhiyun 	struct nouveau_fbdev *fbcon =
312*4882a593Smuzhiyun 		container_of(helper, struct nouveau_fbdev, helper);
313*4882a593Smuzhiyun 	struct drm_device *dev = fbcon->helper.dev;
314*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(dev);
315*4882a593Smuzhiyun 	struct nvif_device *device = &drm->client.device;
316*4882a593Smuzhiyun 	struct fb_info *info;
317*4882a593Smuzhiyun 	struct drm_framebuffer *fb;
318*4882a593Smuzhiyun 	struct nouveau_channel *chan;
319*4882a593Smuzhiyun 	struct nouveau_bo *nvbo;
320*4882a593Smuzhiyun 	struct drm_mode_fb_cmd2 mode_cmd = {};
321*4882a593Smuzhiyun 	int ret;
322*4882a593Smuzhiyun 
323*4882a593Smuzhiyun 	mode_cmd.width = sizes->surface_width;
324*4882a593Smuzhiyun 	mode_cmd.height = sizes->surface_height;
325*4882a593Smuzhiyun 
326*4882a593Smuzhiyun 	mode_cmd.pitches[0] = mode_cmd.width * (sizes->surface_bpp >> 3);
327*4882a593Smuzhiyun 	mode_cmd.pitches[0] = roundup(mode_cmd.pitches[0], 256);
328*4882a593Smuzhiyun 
329*4882a593Smuzhiyun 	mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
330*4882a593Smuzhiyun 							  sizes->surface_depth);
331*4882a593Smuzhiyun 
332*4882a593Smuzhiyun 	ret = nouveau_gem_new(&drm->client, mode_cmd.pitches[0] *
333*4882a593Smuzhiyun 			      mode_cmd.height, 0, NOUVEAU_GEM_DOMAIN_VRAM,
334*4882a593Smuzhiyun 			      0, 0x0000, &nvbo);
335*4882a593Smuzhiyun 	if (ret) {
336*4882a593Smuzhiyun 		NV_ERROR(drm, "failed to allocate framebuffer\n");
337*4882a593Smuzhiyun 		goto out;
338*4882a593Smuzhiyun 	}
339*4882a593Smuzhiyun 
340*4882a593Smuzhiyun 	ret = nouveau_framebuffer_new(dev, &mode_cmd, &nvbo->bo.base, &fb);
341*4882a593Smuzhiyun 	if (ret)
342*4882a593Smuzhiyun 		goto out_unref;
343*4882a593Smuzhiyun 
344*4882a593Smuzhiyun 	ret = nouveau_bo_pin(nvbo, NOUVEAU_GEM_DOMAIN_VRAM, false);
345*4882a593Smuzhiyun 	if (ret) {
346*4882a593Smuzhiyun 		NV_ERROR(drm, "failed to pin fb: %d\n", ret);
347*4882a593Smuzhiyun 		goto out_unref;
348*4882a593Smuzhiyun 	}
349*4882a593Smuzhiyun 
350*4882a593Smuzhiyun 	ret = nouveau_bo_map(nvbo);
351*4882a593Smuzhiyun 	if (ret) {
352*4882a593Smuzhiyun 		NV_ERROR(drm, "failed to map fb: %d\n", ret);
353*4882a593Smuzhiyun 		goto out_unpin;
354*4882a593Smuzhiyun 	}
355*4882a593Smuzhiyun 
356*4882a593Smuzhiyun 	chan = nouveau_nofbaccel ? NULL : drm->channel;
357*4882a593Smuzhiyun 	if (chan && device->info.family >= NV_DEVICE_INFO_V0_TESLA) {
358*4882a593Smuzhiyun 		ret = nouveau_vma_new(nvbo, chan->vmm, &fbcon->vma);
359*4882a593Smuzhiyun 		if (ret) {
360*4882a593Smuzhiyun 			NV_ERROR(drm, "failed to map fb into chan: %d\n", ret);
361*4882a593Smuzhiyun 			chan = NULL;
362*4882a593Smuzhiyun 		}
363*4882a593Smuzhiyun 	}
364*4882a593Smuzhiyun 
365*4882a593Smuzhiyun 	info = drm_fb_helper_alloc_fbi(helper);
366*4882a593Smuzhiyun 	if (IS_ERR(info)) {
367*4882a593Smuzhiyun 		ret = PTR_ERR(info);
368*4882a593Smuzhiyun 		goto out_unlock;
369*4882a593Smuzhiyun 	}
370*4882a593Smuzhiyun 
371*4882a593Smuzhiyun 	/* setup helper */
372*4882a593Smuzhiyun 	fbcon->helper.fb = fb;
373*4882a593Smuzhiyun 
374*4882a593Smuzhiyun 	if (!chan)
375*4882a593Smuzhiyun 		info->flags = FBINFO_HWACCEL_DISABLED;
376*4882a593Smuzhiyun 	else
377*4882a593Smuzhiyun 		info->flags = FBINFO_HWACCEL_COPYAREA |
378*4882a593Smuzhiyun 			      FBINFO_HWACCEL_FILLRECT |
379*4882a593Smuzhiyun 			      FBINFO_HWACCEL_IMAGEBLIT;
380*4882a593Smuzhiyun 	info->fbops = &nouveau_fbcon_sw_ops;
381*4882a593Smuzhiyun 	info->fix.smem_start = nvbo->bo.mem.bus.offset;
382*4882a593Smuzhiyun 	info->fix.smem_len = nvbo->bo.mem.num_pages << PAGE_SHIFT;
383*4882a593Smuzhiyun 
384*4882a593Smuzhiyun 	info->screen_base = nvbo_kmap_obj_iovirtual(nvbo);
385*4882a593Smuzhiyun 	info->screen_size = nvbo->bo.mem.num_pages << PAGE_SHIFT;
386*4882a593Smuzhiyun 
387*4882a593Smuzhiyun 	drm_fb_helper_fill_info(info, &fbcon->helper, sizes);
388*4882a593Smuzhiyun 
389*4882a593Smuzhiyun 	/* Use default scratch pixmap (info->pixmap.flags = FB_PIXMAP_SYSTEM) */
390*4882a593Smuzhiyun 
391*4882a593Smuzhiyun 	if (chan)
392*4882a593Smuzhiyun 		nouveau_fbcon_accel_init(dev);
393*4882a593Smuzhiyun 	nouveau_fbcon_zfill(dev, fbcon);
394*4882a593Smuzhiyun 
395*4882a593Smuzhiyun 	/* To allow resizeing without swapping buffers */
396*4882a593Smuzhiyun 	NV_INFO(drm, "allocated %dx%d fb: 0x%llx, bo %p\n",
397*4882a593Smuzhiyun 		fb->width, fb->height, nvbo->offset, nvbo);
398*4882a593Smuzhiyun 
399*4882a593Smuzhiyun 	vga_switcheroo_client_fb_set(dev->pdev, info);
400*4882a593Smuzhiyun 	return 0;
401*4882a593Smuzhiyun 
402*4882a593Smuzhiyun out_unlock:
403*4882a593Smuzhiyun 	if (chan)
404*4882a593Smuzhiyun 		nouveau_vma_del(&fbcon->vma);
405*4882a593Smuzhiyun 	nouveau_bo_unmap(nvbo);
406*4882a593Smuzhiyun out_unpin:
407*4882a593Smuzhiyun 	nouveau_bo_unpin(nvbo);
408*4882a593Smuzhiyun out_unref:
409*4882a593Smuzhiyun 	nouveau_bo_ref(NULL, &nvbo);
410*4882a593Smuzhiyun out:
411*4882a593Smuzhiyun 	return ret;
412*4882a593Smuzhiyun }
413*4882a593Smuzhiyun 
414*4882a593Smuzhiyun static int
nouveau_fbcon_destroy(struct drm_device * dev,struct nouveau_fbdev * fbcon)415*4882a593Smuzhiyun nouveau_fbcon_destroy(struct drm_device *dev, struct nouveau_fbdev *fbcon)
416*4882a593Smuzhiyun {
417*4882a593Smuzhiyun 	struct drm_framebuffer *fb = fbcon->helper.fb;
418*4882a593Smuzhiyun 	struct nouveau_bo *nvbo;
419*4882a593Smuzhiyun 
420*4882a593Smuzhiyun 	drm_fb_helper_unregister_fbi(&fbcon->helper);
421*4882a593Smuzhiyun 	drm_fb_helper_fini(&fbcon->helper);
422*4882a593Smuzhiyun 
423*4882a593Smuzhiyun 	if (fb && fb->obj[0]) {
424*4882a593Smuzhiyun 		nvbo = nouveau_gem_object(fb->obj[0]);
425*4882a593Smuzhiyun 		nouveau_vma_del(&fbcon->vma);
426*4882a593Smuzhiyun 		nouveau_bo_unmap(nvbo);
427*4882a593Smuzhiyun 		nouveau_bo_unpin(nvbo);
428*4882a593Smuzhiyun 		drm_framebuffer_put(fb);
429*4882a593Smuzhiyun 	}
430*4882a593Smuzhiyun 
431*4882a593Smuzhiyun 	return 0;
432*4882a593Smuzhiyun }
433*4882a593Smuzhiyun 
nouveau_fbcon_gpu_lockup(struct fb_info * info)434*4882a593Smuzhiyun void nouveau_fbcon_gpu_lockup(struct fb_info *info)
435*4882a593Smuzhiyun {
436*4882a593Smuzhiyun 	struct nouveau_fbdev *fbcon = info->par;
437*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(fbcon->helper.dev);
438*4882a593Smuzhiyun 
439*4882a593Smuzhiyun 	NV_ERROR(drm, "GPU lockup - switching to software fbcon\n");
440*4882a593Smuzhiyun 	info->flags |= FBINFO_HWACCEL_DISABLED;
441*4882a593Smuzhiyun }
442*4882a593Smuzhiyun 
443*4882a593Smuzhiyun static const struct drm_fb_helper_funcs nouveau_fbcon_helper_funcs = {
444*4882a593Smuzhiyun 	.fb_probe = nouveau_fbcon_create,
445*4882a593Smuzhiyun };
446*4882a593Smuzhiyun 
447*4882a593Smuzhiyun static void
nouveau_fbcon_set_suspend_work(struct work_struct * work)448*4882a593Smuzhiyun nouveau_fbcon_set_suspend_work(struct work_struct *work)
449*4882a593Smuzhiyun {
450*4882a593Smuzhiyun 	struct nouveau_drm *drm = container_of(work, typeof(*drm), fbcon_work);
451*4882a593Smuzhiyun 	int state = READ_ONCE(drm->fbcon_new_state);
452*4882a593Smuzhiyun 
453*4882a593Smuzhiyun 	if (state == FBINFO_STATE_RUNNING)
454*4882a593Smuzhiyun 		pm_runtime_get_sync(drm->dev->dev);
455*4882a593Smuzhiyun 
456*4882a593Smuzhiyun 	console_lock();
457*4882a593Smuzhiyun 	if (state == FBINFO_STATE_RUNNING)
458*4882a593Smuzhiyun 		nouveau_fbcon_accel_restore(drm->dev);
459*4882a593Smuzhiyun 	drm_fb_helper_set_suspend(&drm->fbcon->helper, state);
460*4882a593Smuzhiyun 	if (state != FBINFO_STATE_RUNNING)
461*4882a593Smuzhiyun 		nouveau_fbcon_accel_save_disable(drm->dev);
462*4882a593Smuzhiyun 	console_unlock();
463*4882a593Smuzhiyun 
464*4882a593Smuzhiyun 	if (state == FBINFO_STATE_RUNNING) {
465*4882a593Smuzhiyun 		nouveau_fbcon_hotplug_resume(drm->fbcon);
466*4882a593Smuzhiyun 		pm_runtime_mark_last_busy(drm->dev->dev);
467*4882a593Smuzhiyun 		pm_runtime_put_autosuspend(drm->dev->dev);
468*4882a593Smuzhiyun 	}
469*4882a593Smuzhiyun }
470*4882a593Smuzhiyun 
471*4882a593Smuzhiyun void
nouveau_fbcon_set_suspend(struct drm_device * dev,int state)472*4882a593Smuzhiyun nouveau_fbcon_set_suspend(struct drm_device *dev, int state)
473*4882a593Smuzhiyun {
474*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(dev);
475*4882a593Smuzhiyun 
476*4882a593Smuzhiyun 	if (!drm->fbcon)
477*4882a593Smuzhiyun 		return;
478*4882a593Smuzhiyun 
479*4882a593Smuzhiyun 	drm->fbcon_new_state = state;
480*4882a593Smuzhiyun 	/* Since runtime resume can happen as a result of a sysfs operation,
481*4882a593Smuzhiyun 	 * it's possible we already have the console locked. So handle fbcon
482*4882a593Smuzhiyun 	 * init/deinit from a seperate work thread
483*4882a593Smuzhiyun 	 */
484*4882a593Smuzhiyun 	schedule_work(&drm->fbcon_work);
485*4882a593Smuzhiyun }
486*4882a593Smuzhiyun 
487*4882a593Smuzhiyun void
nouveau_fbcon_output_poll_changed(struct drm_device * dev)488*4882a593Smuzhiyun nouveau_fbcon_output_poll_changed(struct drm_device *dev)
489*4882a593Smuzhiyun {
490*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(dev);
491*4882a593Smuzhiyun 	struct nouveau_fbdev *fbcon = drm->fbcon;
492*4882a593Smuzhiyun 	int ret;
493*4882a593Smuzhiyun 
494*4882a593Smuzhiyun 	if (!fbcon)
495*4882a593Smuzhiyun 		return;
496*4882a593Smuzhiyun 
497*4882a593Smuzhiyun 	mutex_lock(&fbcon->hotplug_lock);
498*4882a593Smuzhiyun 
499*4882a593Smuzhiyun 	ret = pm_runtime_get(dev->dev);
500*4882a593Smuzhiyun 	if (ret == 1 || ret == -EACCES) {
501*4882a593Smuzhiyun 		drm_fb_helper_hotplug_event(&fbcon->helper);
502*4882a593Smuzhiyun 
503*4882a593Smuzhiyun 		pm_runtime_mark_last_busy(dev->dev);
504*4882a593Smuzhiyun 		pm_runtime_put_autosuspend(dev->dev);
505*4882a593Smuzhiyun 	} else if (ret == 0) {
506*4882a593Smuzhiyun 		/* If the GPU was already in the process of suspending before
507*4882a593Smuzhiyun 		 * this event happened, then we can't block here as we'll
508*4882a593Smuzhiyun 		 * deadlock the runtime pmops since they wait for us to
509*4882a593Smuzhiyun 		 * finish. So, just defer this event for when we runtime
510*4882a593Smuzhiyun 		 * resume again. It will be handled by fbcon_work.
511*4882a593Smuzhiyun 		 */
512*4882a593Smuzhiyun 		NV_DEBUG(drm, "fbcon HPD event deferred until runtime resume\n");
513*4882a593Smuzhiyun 		fbcon->hotplug_waiting = true;
514*4882a593Smuzhiyun 		pm_runtime_put_noidle(drm->dev->dev);
515*4882a593Smuzhiyun 	} else {
516*4882a593Smuzhiyun 		DRM_WARN("fbcon HPD event lost due to RPM failure: %d\n",
517*4882a593Smuzhiyun 			 ret);
518*4882a593Smuzhiyun 	}
519*4882a593Smuzhiyun 
520*4882a593Smuzhiyun 	mutex_unlock(&fbcon->hotplug_lock);
521*4882a593Smuzhiyun }
522*4882a593Smuzhiyun 
523*4882a593Smuzhiyun void
nouveau_fbcon_hotplug_resume(struct nouveau_fbdev * fbcon)524*4882a593Smuzhiyun nouveau_fbcon_hotplug_resume(struct nouveau_fbdev *fbcon)
525*4882a593Smuzhiyun {
526*4882a593Smuzhiyun 	struct nouveau_drm *drm;
527*4882a593Smuzhiyun 
528*4882a593Smuzhiyun 	if (!fbcon)
529*4882a593Smuzhiyun 		return;
530*4882a593Smuzhiyun 	drm = nouveau_drm(fbcon->helper.dev);
531*4882a593Smuzhiyun 
532*4882a593Smuzhiyun 	mutex_lock(&fbcon->hotplug_lock);
533*4882a593Smuzhiyun 	if (fbcon->hotplug_waiting) {
534*4882a593Smuzhiyun 		fbcon->hotplug_waiting = false;
535*4882a593Smuzhiyun 
536*4882a593Smuzhiyun 		NV_DEBUG(drm, "Handling deferred fbcon HPD events\n");
537*4882a593Smuzhiyun 		drm_fb_helper_hotplug_event(&fbcon->helper);
538*4882a593Smuzhiyun 	}
539*4882a593Smuzhiyun 	mutex_unlock(&fbcon->hotplug_lock);
540*4882a593Smuzhiyun }
541*4882a593Smuzhiyun 
542*4882a593Smuzhiyun int
nouveau_fbcon_init(struct drm_device * dev)543*4882a593Smuzhiyun nouveau_fbcon_init(struct drm_device *dev)
544*4882a593Smuzhiyun {
545*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(dev);
546*4882a593Smuzhiyun 	struct nouveau_fbdev *fbcon;
547*4882a593Smuzhiyun 	int preferred_bpp = nouveau_fbcon_bpp;
548*4882a593Smuzhiyun 	int ret;
549*4882a593Smuzhiyun 
550*4882a593Smuzhiyun 	if (!dev->mode_config.num_crtc ||
551*4882a593Smuzhiyun 	    (dev->pdev->class >> 8) != PCI_CLASS_DISPLAY_VGA)
552*4882a593Smuzhiyun 		return 0;
553*4882a593Smuzhiyun 
554*4882a593Smuzhiyun 	fbcon = kzalloc(sizeof(struct nouveau_fbdev), GFP_KERNEL);
555*4882a593Smuzhiyun 	if (!fbcon)
556*4882a593Smuzhiyun 		return -ENOMEM;
557*4882a593Smuzhiyun 
558*4882a593Smuzhiyun 	drm->fbcon = fbcon;
559*4882a593Smuzhiyun 	INIT_WORK(&drm->fbcon_work, nouveau_fbcon_set_suspend_work);
560*4882a593Smuzhiyun 	mutex_init(&fbcon->hotplug_lock);
561*4882a593Smuzhiyun 
562*4882a593Smuzhiyun 	drm_fb_helper_prepare(dev, &fbcon->helper, &nouveau_fbcon_helper_funcs);
563*4882a593Smuzhiyun 
564*4882a593Smuzhiyun 	ret = drm_fb_helper_init(dev, &fbcon->helper);
565*4882a593Smuzhiyun 	if (ret)
566*4882a593Smuzhiyun 		goto free;
567*4882a593Smuzhiyun 
568*4882a593Smuzhiyun 	if (preferred_bpp != 8 && preferred_bpp != 16 && preferred_bpp != 32) {
569*4882a593Smuzhiyun 		if (drm->client.device.info.ram_size <= 32 * 1024 * 1024)
570*4882a593Smuzhiyun 			preferred_bpp = 8;
571*4882a593Smuzhiyun 		else
572*4882a593Smuzhiyun 		if (drm->client.device.info.ram_size <= 64 * 1024 * 1024)
573*4882a593Smuzhiyun 			preferred_bpp = 16;
574*4882a593Smuzhiyun 		else
575*4882a593Smuzhiyun 			preferred_bpp = 32;
576*4882a593Smuzhiyun 	}
577*4882a593Smuzhiyun 
578*4882a593Smuzhiyun 	/* disable all the possible outputs/crtcs before entering KMS mode */
579*4882a593Smuzhiyun 	if (!drm_drv_uses_atomic_modeset(dev))
580*4882a593Smuzhiyun 		drm_helper_disable_unused_functions(dev);
581*4882a593Smuzhiyun 
582*4882a593Smuzhiyun 	ret = drm_fb_helper_initial_config(&fbcon->helper, preferred_bpp);
583*4882a593Smuzhiyun 	if (ret)
584*4882a593Smuzhiyun 		goto fini;
585*4882a593Smuzhiyun 
586*4882a593Smuzhiyun 	if (fbcon->helper.fbdev)
587*4882a593Smuzhiyun 		fbcon->helper.fbdev->pixmap.buf_align = 4;
588*4882a593Smuzhiyun 	return 0;
589*4882a593Smuzhiyun 
590*4882a593Smuzhiyun fini:
591*4882a593Smuzhiyun 	drm_fb_helper_fini(&fbcon->helper);
592*4882a593Smuzhiyun free:
593*4882a593Smuzhiyun 	kfree(fbcon);
594*4882a593Smuzhiyun 	drm->fbcon = NULL;
595*4882a593Smuzhiyun 	return ret;
596*4882a593Smuzhiyun }
597*4882a593Smuzhiyun 
598*4882a593Smuzhiyun void
nouveau_fbcon_fini(struct drm_device * dev)599*4882a593Smuzhiyun nouveau_fbcon_fini(struct drm_device *dev)
600*4882a593Smuzhiyun {
601*4882a593Smuzhiyun 	struct nouveau_drm *drm = nouveau_drm(dev);
602*4882a593Smuzhiyun 
603*4882a593Smuzhiyun 	if (!drm->fbcon)
604*4882a593Smuzhiyun 		return;
605*4882a593Smuzhiyun 
606*4882a593Smuzhiyun 	nouveau_fbcon_accel_fini(dev);
607*4882a593Smuzhiyun 	nouveau_fbcon_destroy(dev, drm->fbcon);
608*4882a593Smuzhiyun 	kfree(drm->fbcon);
609*4882a593Smuzhiyun 	drm->fbcon = NULL;
610*4882a593Smuzhiyun }
611