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