1/*	$NetBSD: nouveau_display.c,v 1.6 2024/04/16 14:34:02 riastradh Exp $	*/
2
3/*
4 * Copyright (C) 2008 Maarten Maathuis.
5 * All Rights Reserved.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining
8 * a copy of this software and associated documentation files (the
9 * "Software"), to deal in the Software without restriction, including
10 * without limitation the rights to use, copy, modify, merge, publish,
11 * distribute, sublicense, and/or sell copies of the Software, and to
12 * permit persons to whom the Software is furnished to do so, subject to
13 * the following conditions:
14 *
15 * The above copyright notice and this permission notice (including the
16 * next paragraph) shall be included in all copies or substantial
17 * portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
23 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
24 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
25 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
26 *
27 */
28
29#include <sys/cdefs.h>
30__KERNEL_RCSID(0, "$NetBSD: nouveau_display.c,v 1.6 2024/04/16 14:34:02 riastradh Exp $");
31
32#include <acpi/video.h>
33
34#include <drm/drm_atomic.h>
35#include <drm/drm_atomic_helper.h>
36#include <drm/drm_crtc_helper.h>
37#include <drm/drm_fb_helper.h>
38#include <drm/drm_fourcc.h>
39#include <drm/drm_probe_helper.h>
40#include <drm/drm_vblank.h>
41
42#include "nouveau_fbcon.h"
43#include "nouveau_crtc.h"
44#include "nouveau_gem.h"
45#include "nouveau_connector.h"
46#include "nv50_display.h"
47
48#include <nvif/class.h>
49#include <nvif/cl0046.h>
50#include <nvif/event.h>
51
52#ifdef __NetBSD__
53/* Used only for runtime power management, not in NetBSD for now.  */
54#undef	CONFIG_ACPI
55#endif
56
57static int
58nouveau_display_vblank_handler(struct nvif_notify *notify)
59{
60	struct nouveau_crtc *nv_crtc =
61		container_of(notify, typeof(*nv_crtc), vblank);
62	drm_crtc_handle_vblank(&nv_crtc->base);
63	return NVIF_NOTIFY_KEEP;
64}
65
66int
67nouveau_display_vblank_enable(struct drm_device *dev, unsigned int pipe)
68{
69	struct drm_crtc *crtc;
70	struct nouveau_crtc *nv_crtc;
71
72	crtc = drm_crtc_from_index(dev, pipe);
73	if (!crtc)
74		return -EINVAL;
75
76	nv_crtc = nouveau_crtc(crtc);
77	nvif_notify_get(&nv_crtc->vblank);
78
79	return 0;
80}
81
82void
83nouveau_display_vblank_disable(struct drm_device *dev, unsigned int pipe)
84{
85	struct drm_crtc *crtc;
86	struct nouveau_crtc *nv_crtc;
87
88	crtc = drm_crtc_from_index(dev, pipe);
89	if (!crtc)
90		return;
91
92	nv_crtc = nouveau_crtc(crtc);
93	nvif_notify_put(&nv_crtc->vblank);
94}
95
96static inline int
97calc(int blanks, int blanke, int total, int line)
98{
99	if (blanke >= blanks) {
100		if (line >= blanks)
101			line -= total;
102	} else {
103		if (line >= blanks)
104			line -= total;
105		line -= blanke + 1;
106	}
107	return line;
108}
109
110static bool
111nouveau_display_scanoutpos_head(struct drm_crtc *crtc, int *vpos, int *hpos,
112				ktime_t *stime, ktime_t *etime)
113{
114	struct {
115		struct nv04_disp_mthd_v0 base;
116		struct nv04_disp_scanoutpos_v0 scan;
117	} args = {
118		.base.method = NV04_DISP_SCANOUTPOS,
119		.base.head = nouveau_crtc(crtc)->index,
120	};
121	struct nouveau_display *disp = nouveau_display(crtc->dev);
122	struct drm_vblank_crtc *vblank = &crtc->dev->vblank[drm_crtc_index(crtc)];
123	int retry = 20;
124	bool ret = false;
125
126	do {
127		ret = nvif_mthd(&disp->disp.object, 0, &args, sizeof(args));
128		if (ret != 0)
129			return false;
130
131		if (args.scan.vline) {
132			ret = true;
133			break;
134		}
135
136		if (retry) ndelay(vblank->linedur_ns);
137	} while (retry--);
138
139	*hpos = args.scan.hline;
140	*vpos = calc(args.scan.vblanks, args.scan.vblanke,
141		     args.scan.vtotal, args.scan.vline);
142	if (stime) *stime = ns_to_ktime(args.scan.time[0]);
143	if (etime) *etime = ns_to_ktime(args.scan.time[1]);
144
145	return ret;
146}
147
148bool
149nouveau_display_scanoutpos(struct drm_device *dev, unsigned int pipe,
150			   bool in_vblank_irq, int *vpos, int *hpos,
151			   ktime_t *stime, ktime_t *etime,
152			   const struct drm_display_mode *mode)
153{
154	struct drm_crtc *crtc;
155
156	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
157		if (nouveau_crtc(crtc)->index == pipe) {
158			return nouveau_display_scanoutpos_head(crtc, vpos, hpos,
159							       stime, etime);
160		}
161	}
162
163	return false;
164}
165
166static void
167nouveau_display_vblank_fini(struct drm_device *dev)
168{
169	struct drm_crtc *crtc;
170
171	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
172		struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
173		nvif_notify_fini(&nv_crtc->vblank);
174	}
175}
176
177static int
178nouveau_display_vblank_init(struct drm_device *dev)
179{
180	struct nouveau_display *disp = nouveau_display(dev);
181	struct drm_crtc *crtc;
182	int ret;
183
184	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
185		struct nouveau_crtc *nv_crtc = nouveau_crtc(crtc);
186		ret = nvif_notify_init(&disp->disp.object,
187				       nouveau_display_vblank_handler, false,
188				       NV04_DISP_NTFY_VBLANK,
189				       &(struct nvif_notify_head_req_v0) {
190					.head = nv_crtc->index,
191				       },
192				       sizeof(struct nvif_notify_head_req_v0),
193				       sizeof(struct nvif_notify_head_rep_v0),
194				       &nv_crtc->vblank);
195		if (ret) {
196			nouveau_display_vblank_fini(dev);
197			return ret;
198		}
199	}
200
201	ret = drm_vblank_init(dev, dev->mode_config.num_crtc);
202	if (ret) {
203		nouveau_display_vblank_fini(dev);
204		return ret;
205	}
206
207	return 0;
208}
209
210static void
211nouveau_user_framebuffer_destroy(struct drm_framebuffer *drm_fb)
212{
213	struct nouveau_framebuffer *fb = nouveau_framebuffer(drm_fb);
214
215	if (fb->nvbo)
216		drm_gem_object_put_unlocked(&fb->nvbo->bo.base);
217
218	drm_framebuffer_cleanup(drm_fb);
219	kfree(fb);
220}
221
222static int
223nouveau_user_framebuffer_create_handle(struct drm_framebuffer *drm_fb,
224				       struct drm_file *file_priv,
225				       unsigned int *handle)
226{
227	struct nouveau_framebuffer *fb = nouveau_framebuffer(drm_fb);
228
229	return drm_gem_handle_create(file_priv, &fb->nvbo->bo.base, handle);
230}
231
232static const struct drm_framebuffer_funcs nouveau_framebuffer_funcs = {
233	.destroy = nouveau_user_framebuffer_destroy,
234	.create_handle = nouveau_user_framebuffer_create_handle,
235};
236
237int
238nouveau_framebuffer_new(struct drm_device *dev,
239			const struct drm_mode_fb_cmd2 *mode_cmd,
240			struct nouveau_bo *nvbo,
241			struct nouveau_framebuffer **pfb)
242{
243	struct nouveau_drm *drm = nouveau_drm(dev);
244	struct nouveau_framebuffer *fb;
245	int ret;
246
247        /* YUV overlays have special requirements pre-NV50 */
248	if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA &&
249
250	    (mode_cmd->pixel_format == DRM_FORMAT_YUYV ||
251	     mode_cmd->pixel_format == DRM_FORMAT_UYVY ||
252	     mode_cmd->pixel_format == DRM_FORMAT_NV12 ||
253	     mode_cmd->pixel_format == DRM_FORMAT_NV21) &&
254	    (mode_cmd->pitches[0] & 0x3f || /* align 64 */
255	     mode_cmd->pitches[0] >= 0x10000 || /* at most 64k pitch */
256	     (mode_cmd->pitches[1] && /* pitches for planes must match */
257	      mode_cmd->pitches[0] != mode_cmd->pitches[1]))) {
258		struct drm_format_name_buf format_name;
259		DRM_DEBUG_KMS("Unsuitable framebuffer: format: %s; pitches: 0x%x\n 0x%x\n",
260			      drm_get_format_name(mode_cmd->pixel_format,
261						  &format_name),
262			      mode_cmd->pitches[0],
263			      mode_cmd->pitches[1]);
264		return -EINVAL;
265	}
266
267	if (!(fb = *pfb = kzalloc(sizeof(*fb), GFP_KERNEL)))
268		return -ENOMEM;
269
270	drm_helper_mode_fill_fb_struct(dev, &fb->base, mode_cmd);
271	fb->nvbo = nvbo;
272
273	ret = drm_framebuffer_init(dev, &fb->base, &nouveau_framebuffer_funcs);
274	if (ret)
275		kfree(fb);
276	return ret;
277}
278
279struct drm_framebuffer *
280nouveau_user_framebuffer_create(struct drm_device *dev,
281				struct drm_file *file_priv,
282				const struct drm_mode_fb_cmd2 *mode_cmd)
283{
284	struct nouveau_framebuffer *fb;
285	struct nouveau_bo *nvbo;
286	struct drm_gem_object *gem;
287	int ret;
288
289	gem = drm_gem_object_lookup(file_priv, mode_cmd->handles[0]);
290	if (!gem)
291		return ERR_PTR(-ENOENT);
292	nvbo = nouveau_gem_object(gem);
293
294	ret = nouveau_framebuffer_new(dev, mode_cmd, nvbo, &fb);
295	if (ret == 0)
296		return &fb->base;
297
298	drm_gem_object_put_unlocked(gem);
299	return ERR_PTR(ret);
300}
301
302static const struct drm_mode_config_funcs nouveau_mode_config_funcs = {
303	.fb_create = nouveau_user_framebuffer_create,
304	.output_poll_changed = nouveau_fbcon_output_poll_changed,
305};
306
307
308struct nouveau_drm_prop_enum_list {
309	u8 gen_mask;
310	int type;
311	const char *name;
312};
313
314static struct nouveau_drm_prop_enum_list underscan[] = {
315	{ 6, UNDERSCAN_AUTO, "auto" },
316	{ 6, UNDERSCAN_OFF, "off" },
317	{ 6, UNDERSCAN_ON, "on" },
318	{}
319};
320
321static struct nouveau_drm_prop_enum_list dither_mode[] = {
322	{ 7, DITHERING_MODE_AUTO, "auto" },
323	{ 7, DITHERING_MODE_OFF, "off" },
324	{ 1, DITHERING_MODE_ON, "on" },
325	{ 6, DITHERING_MODE_STATIC2X2, "static 2x2" },
326	{ 6, DITHERING_MODE_DYNAMIC2X2, "dynamic 2x2" },
327	{ 4, DITHERING_MODE_TEMPORAL, "temporal" },
328	{}
329};
330
331static struct nouveau_drm_prop_enum_list dither_depth[] = {
332	{ 6, DITHERING_DEPTH_AUTO, "auto" },
333	{ 6, DITHERING_DEPTH_6BPC, "6 bpc" },
334	{ 6, DITHERING_DEPTH_8BPC, "8 bpc" },
335	{}
336};
337
338#define PROP_ENUM(p,gen,n,list) do {                                           \
339	struct nouveau_drm_prop_enum_list *l = (list);                         \
340	int c = 0;                                                             \
341	while (l->gen_mask) {                                                  \
342		if (l->gen_mask & (1 << (gen)))                                \
343			c++;                                                   \
344		l++;                                                           \
345	}                                                                      \
346	if (c) {                                                               \
347		p = drm_property_create(dev, DRM_MODE_PROP_ENUM, n, c);        \
348		l = (list);                                                    \
349		while (p && l->gen_mask) {                                     \
350			if (l->gen_mask & (1 << (gen))) {                      \
351				drm_property_add_enum(p, l->type, l->name);    \
352			}                                                      \
353			l++;                                                   \
354		}                                                              \
355	}                                                                      \
356} while(0)
357
358static void
359nouveau_display_hpd_work(struct work_struct *work)
360{
361	struct nouveau_drm *drm = container_of(work, typeof(*drm), hpd_work);
362
363	pm_runtime_get_sync(drm->dev->dev);
364
365	drm_helper_hpd_irq_event(drm->dev);
366
367	pm_runtime_mark_last_busy(drm->dev->dev);
368	pm_runtime_put_sync(drm->dev->dev);
369}
370
371#ifdef CONFIG_ACPI
372
373static int
374nouveau_display_acpi_ntfy(struct notifier_block *nb, unsigned long val,
375			  void *data)
376{
377	struct nouveau_drm *drm = container_of(nb, typeof(*drm), acpi_nb);
378	struct acpi_bus_event *info = data;
379	int ret;
380
381	if (!strcmp(info->device_class, ACPI_VIDEO_CLASS)) {
382		if (info->type == ACPI_VIDEO_NOTIFY_PROBE) {
383			ret = pm_runtime_get(drm->dev->dev);
384			if (ret == 1 || ret == -EACCES) {
385				/* If the GPU is already awake, or in a state
386				 * where we can't wake it up, it can handle
387				 * it's own hotplug events.
388				 */
389				pm_runtime_put_autosuspend(drm->dev->dev);
390			} else if (ret == 0) {
391				/* This may be the only indication we receive
392				 * of a connector hotplug on a runtime
393				 * suspended GPU, schedule hpd_work to check.
394				 */
395				NV_DEBUG(drm, "ACPI requested connector reprobe\n");
396				schedule_work(&drm->hpd_work);
397				pm_runtime_put_noidle(drm->dev->dev);
398			} else {
399				NV_WARN(drm, "Dropped ACPI reprobe event due to RPM error: %d\n",
400					ret);
401			}
402
403			/* acpi-video should not generate keypresses for this */
404			return NOTIFY_BAD;
405		}
406	}
407
408	return NOTIFY_DONE;
409}
410#endif
411
412int
413nouveau_display_init(struct drm_device *dev, bool resume, bool runtime)
414{
415	struct nouveau_display *disp = nouveau_display(dev);
416	struct drm_connector *connector;
417	struct drm_connector_list_iter conn_iter;
418	int ret;
419
420	/*
421	 * Enable hotplug interrupts (done as early as possible, since we need
422	 * them for MST)
423	 */
424	drm_connector_list_iter_begin(dev, &conn_iter);
425	nouveau_for_each_non_mst_connector_iter(connector, &conn_iter) {
426		struct nouveau_connector *conn = nouveau_connector(connector);
427		nvif_notify_get(&conn->hpd);
428	}
429	drm_connector_list_iter_end(&conn_iter);
430
431	ret = disp->init(dev, resume, runtime);
432	if (ret)
433		return ret;
434
435	/* enable connector detection and polling for connectors without HPD
436	 * support
437	 */
438	drm_kms_helper_poll_enable(dev);
439
440	return ret;
441}
442
443void
444nouveau_display_fini(struct drm_device *dev, bool suspend, bool runtime)
445{
446	struct nouveau_display *disp = nouveau_display(dev);
447	struct nouveau_drm *drm = nouveau_drm(dev);
448	struct drm_connector *connector;
449	struct drm_connector_list_iter conn_iter;
450
451	if (!suspend) {
452		if (drm_drv_uses_atomic_modeset(dev))
453			drm_atomic_helper_shutdown(dev);
454		else
455			drm_helper_force_disable_all(dev);
456	}
457
458	/* disable hotplug interrupts */
459	drm_connector_list_iter_begin(dev, &conn_iter);
460	nouveau_for_each_non_mst_connector_iter(connector, &conn_iter) {
461		struct nouveau_connector *conn = nouveau_connector(connector);
462		nvif_notify_put(&conn->hpd);
463	}
464	drm_connector_list_iter_end(&conn_iter);
465
466	if (!runtime)
467		cancel_work_sync(&drm->hpd_work);
468
469	drm_kms_helper_poll_disable(dev);
470	disp->fini(dev, suspend);
471}
472
473static void
474nouveau_display_create_properties(struct drm_device *dev)
475{
476	struct nouveau_display *disp = nouveau_display(dev);
477	int gen;
478
479	if (disp->disp.object.oclass < NV50_DISP)
480		gen = 0;
481	else
482	if (disp->disp.object.oclass < GF110_DISP)
483		gen = 1;
484	else
485		gen = 2;
486
487	PROP_ENUM(disp->dithering_mode, gen, "dithering mode", dither_mode);
488	PROP_ENUM(disp->dithering_depth, gen, "dithering depth", dither_depth);
489	PROP_ENUM(disp->underscan_property, gen, "underscan", underscan);
490
491	disp->underscan_hborder_property =
492		drm_property_create_range(dev, 0, "underscan hborder", 0, 128);
493
494	disp->underscan_vborder_property =
495		drm_property_create_range(dev, 0, "underscan vborder", 0, 128);
496
497	if (gen < 1)
498		return;
499
500	/* -90..+90 */
501	disp->vibrant_hue_property =
502		drm_property_create_range(dev, 0, "vibrant hue", 0, 180);
503
504	/* -100..+100 */
505	disp->color_vibrance_property =
506		drm_property_create_range(dev, 0, "color vibrance", 0, 200);
507}
508
509int
510nouveau_display_create(struct drm_device *dev)
511{
512	struct nouveau_drm *drm = nouveau_drm(dev);
513	struct nvkm_device *device = nvxx_device(&drm->client.device);
514	struct nouveau_display *disp;
515	int ret;
516
517	disp = drm->display = kzalloc(sizeof(*disp), GFP_KERNEL);
518	if (!disp)
519		return -ENOMEM;
520
521	drm_mode_config_init(dev);
522	drm_mode_create_scaling_mode_property(dev);
523	drm_mode_create_dvi_i_properties(dev);
524
525	dev->mode_config.funcs = &nouveau_mode_config_funcs;
526	dev->mode_config.fb_base = device->func->resource_addr(device, 1);
527
528	dev->mode_config.min_width = 0;
529	dev->mode_config.min_height = 0;
530	if (drm->client.device.info.family < NV_DEVICE_INFO_V0_CELSIUS) {
531		dev->mode_config.max_width = 2048;
532		dev->mode_config.max_height = 2048;
533	} else
534	if (drm->client.device.info.family < NV_DEVICE_INFO_V0_TESLA) {
535		dev->mode_config.max_width = 4096;
536		dev->mode_config.max_height = 4096;
537	} else
538	if (drm->client.device.info.family < NV_DEVICE_INFO_V0_FERMI) {
539		dev->mode_config.max_width = 8192;
540		dev->mode_config.max_height = 8192;
541	} else {
542		dev->mode_config.max_width = 16384;
543		dev->mode_config.max_height = 16384;
544	}
545
546	dev->mode_config.preferred_depth = 24;
547	dev->mode_config.prefer_shadow = 1;
548
549	if (drm->client.device.info.chipset < 0x11)
550		dev->mode_config.async_page_flip = false;
551	else
552		dev->mode_config.async_page_flip = true;
553
554	drm_kms_helper_poll_init(dev);
555	drm_kms_helper_poll_disable(dev);
556
557	if (nouveau_modeset != 2 && drm->vbios.dcb.entries) {
558		ret = nvif_disp_ctor(&drm->client.device, 0, &disp->disp);
559		if (ret == 0) {
560			nouveau_display_create_properties(dev);
561			if (disp->disp.object.oclass < NV50_DISP)
562				ret = nv04_display_create(dev);
563			else
564				ret = nv50_display_create(dev);
565		}
566	} else {
567		ret = 0;
568	}
569
570	if (ret)
571		goto disp_create_err;
572
573	drm_mode_config_reset(dev);
574
575	if (dev->mode_config.num_crtc) {
576		ret = nouveau_display_vblank_init(dev);
577		if (ret)
578			goto vblank_err;
579	}
580
581	INIT_WORK(&drm->hpd_work, nouveau_display_hpd_work);
582#ifdef CONFIG_ACPI
583	drm->acpi_nb.notifier_call = nouveau_display_acpi_ntfy;
584	register_acpi_notifier(&drm->acpi_nb);
585#endif
586
587	return 0;
588
589vblank_err:
590	disp->dtor(dev);
591disp_create_err:
592	drm_kms_helper_poll_fini(dev);
593	drm_mode_config_cleanup(dev);
594	return ret;
595}
596
597void
598nouveau_display_destroy(struct drm_device *dev)
599{
600	struct nouveau_display *disp = nouveau_display(dev);
601
602#ifdef CONFIG_ACPI
603	unregister_acpi_notifier(&nouveau_drm(dev)->acpi_nb);
604#endif
605	nouveau_display_vblank_fini(dev);
606
607	drm_kms_helper_poll_fini(dev);
608	drm_mode_config_cleanup(dev);
609
610	if (disp->dtor)
611		disp->dtor(dev);
612
613	nvif_disp_dtor(&disp->disp);
614
615	nouveau_drm(dev)->display = NULL;
616	kfree(disp);
617}
618
619int
620nouveau_display_suspend(struct drm_device *dev, bool runtime)
621{
622	struct nouveau_display *disp = nouveau_display(dev);
623
624	if (drm_drv_uses_atomic_modeset(dev)) {
625		if (!runtime) {
626			disp->suspend = drm_atomic_helper_suspend(dev);
627			if (IS_ERR(disp->suspend)) {
628				int ret = PTR_ERR(disp->suspend);
629				disp->suspend = NULL;
630				return ret;
631			}
632		}
633	}
634
635	nouveau_display_fini(dev, true, runtime);
636	return 0;
637}
638
639void
640nouveau_display_resume(struct drm_device *dev, bool runtime)
641{
642	struct nouveau_display *disp = nouveau_display(dev);
643
644	nouveau_display_init(dev, true, runtime);
645
646	if (drm_drv_uses_atomic_modeset(dev)) {
647		if (disp->suspend) {
648			drm_atomic_helper_resume(dev, disp->suspend);
649			disp->suspend = NULL;
650		}
651		return;
652	}
653}
654
655int
656nouveau_display_dumb_create(struct drm_file *file_priv, struct drm_device *dev,
657			    struct drm_mode_create_dumb *args)
658{
659	struct nouveau_cli *cli = nouveau_cli(file_priv);
660	struct nouveau_bo *bo;
661	uint32_t domain;
662	int ret;
663
664	args->pitch = roundup(args->width * (args->bpp / 8), 256);
665	args->size = args->pitch * args->height;
666	args->size = roundup(args->size, PAGE_SIZE);
667
668	/* Use VRAM if there is any ; otherwise fallback to system memory */
669	if (nouveau_drm(dev)->client.device.info.ram_size != 0)
670		domain = NOUVEAU_GEM_DOMAIN_VRAM;
671	else
672		domain = NOUVEAU_GEM_DOMAIN_GART;
673
674	ret = nouveau_gem_new(cli, args->size, 0, domain, 0, 0, &bo);
675	if (ret)
676		return ret;
677
678	ret = drm_gem_handle_create(file_priv, &bo->bo.base, &args->handle);
679	drm_gem_object_put_unlocked(&bo->bo.base);
680	return ret;
681}
682
683int
684nouveau_display_dumb_map_offset(struct drm_file *file_priv,
685				struct drm_device *dev,
686				uint32_t handle, uint64_t *poffset)
687{
688	struct drm_gem_object *gem;
689
690	gem = drm_gem_object_lookup(file_priv, handle);
691	if (gem) {
692		struct nouveau_bo *bo = nouveau_gem_object(gem);
693		*poffset = drm_vma_node_offset_addr(&bo->bo.base.vma_node);
694		drm_gem_object_put_unlocked(gem);
695		return 0;
696	}
697
698	return -ENOENT;
699}
700