Linux kernel mirror (for testing)
git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel
os
linux
1/*
2 * Copyright (C) 2016 Noralf Trønnes
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 */
9
10#include <drm/drm_atomic.h>
11#include <drm/drm_atomic_helper.h>
12#include <drm/drm_crtc_helper.h>
13#include <drm/drm_gem_framebuffer_helper.h>
14#include <drm/tinydrm/tinydrm.h>
15#include <linux/device.h>
16#include <linux/dma-buf.h>
17
18/**
19 * DOC: overview
20 *
21 * This library provides driver helpers for very simple display hardware.
22 *
23 * It is based on &drm_simple_display_pipe coupled with a &drm_connector which
24 * has only one fixed &drm_display_mode. The framebuffers are backed by the
25 * cma helper and have support for framebuffer flushing (dirty).
26 * fbdev support is also included.
27 *
28 */
29
30/**
31 * DOC: core
32 *
33 * The driver allocates &tinydrm_device, initializes it using
34 * devm_tinydrm_init(), sets up the pipeline using tinydrm_display_pipe_init()
35 * and registers the DRM device using devm_tinydrm_register().
36 */
37
38/**
39 * tinydrm_lastclose - DRM lastclose helper
40 * @drm: DRM device
41 *
42 * This function ensures that fbdev is restored when drm_lastclose() is called
43 * on the last drm_release(). Drivers can use this as their
44 * &drm_driver->lastclose callback.
45 */
46void tinydrm_lastclose(struct drm_device *drm)
47{
48 struct tinydrm_device *tdev = drm->dev_private;
49
50 DRM_DEBUG_KMS("\n");
51 drm_fbdev_cma_restore_mode(tdev->fbdev_cma);
52}
53EXPORT_SYMBOL(tinydrm_lastclose);
54
55/**
56 * tinydrm_gem_cma_prime_import_sg_table - Produce a CMA GEM object from
57 * another driver's scatter/gather table of pinned pages
58 * @drm: DRM device to import into
59 * @attach: DMA-BUF attachment
60 * @sgt: Scatter/gather table of pinned pages
61 *
62 * This function imports a scatter/gather table exported via DMA-BUF by
63 * another driver using drm_gem_cma_prime_import_sg_table(). It sets the
64 * kernel virtual address on the CMA object. Drivers should use this as their
65 * &drm_driver->gem_prime_import_sg_table callback if they need the virtual
66 * address. tinydrm_gem_cma_free_object() should be used in combination with
67 * this function.
68 *
69 * Returns:
70 * A pointer to a newly created GEM object or an ERR_PTR-encoded negative
71 * error code on failure.
72 */
73struct drm_gem_object *
74tinydrm_gem_cma_prime_import_sg_table(struct drm_device *drm,
75 struct dma_buf_attachment *attach,
76 struct sg_table *sgt)
77{
78 struct drm_gem_cma_object *cma_obj;
79 struct drm_gem_object *obj;
80 void *vaddr;
81
82 vaddr = dma_buf_vmap(attach->dmabuf);
83 if (!vaddr) {
84 DRM_ERROR("Failed to vmap PRIME buffer\n");
85 return ERR_PTR(-ENOMEM);
86 }
87
88 obj = drm_gem_cma_prime_import_sg_table(drm, attach, sgt);
89 if (IS_ERR(obj)) {
90 dma_buf_vunmap(attach->dmabuf, vaddr);
91 return obj;
92 }
93
94 cma_obj = to_drm_gem_cma_obj(obj);
95 cma_obj->vaddr = vaddr;
96
97 return obj;
98}
99EXPORT_SYMBOL(tinydrm_gem_cma_prime_import_sg_table);
100
101/**
102 * tinydrm_gem_cma_free_object - Free resources associated with a CMA GEM
103 * object
104 * @gem_obj: GEM object to free
105 *
106 * This function frees the backing memory of the CMA GEM object, cleans up the
107 * GEM object state and frees the memory used to store the object itself using
108 * drm_gem_cma_free_object(). It also handles PRIME buffers which has the kernel
109 * virtual address set by tinydrm_gem_cma_prime_import_sg_table(). Drivers
110 * can use this as their &drm_driver->gem_free_object callback.
111 */
112void tinydrm_gem_cma_free_object(struct drm_gem_object *gem_obj)
113{
114 if (gem_obj->import_attach) {
115 struct drm_gem_cma_object *cma_obj;
116
117 cma_obj = to_drm_gem_cma_obj(gem_obj);
118 dma_buf_vunmap(gem_obj->import_attach->dmabuf, cma_obj->vaddr);
119 cma_obj->vaddr = NULL;
120 }
121
122 drm_gem_cma_free_object(gem_obj);
123}
124EXPORT_SYMBOL_GPL(tinydrm_gem_cma_free_object);
125
126static struct drm_framebuffer *
127tinydrm_fb_create(struct drm_device *drm, struct drm_file *file_priv,
128 const struct drm_mode_fb_cmd2 *mode_cmd)
129{
130 struct tinydrm_device *tdev = drm->dev_private;
131
132 return drm_gem_fb_create_with_funcs(drm, file_priv, mode_cmd,
133 tdev->fb_funcs);
134}
135
136static const struct drm_mode_config_funcs tinydrm_mode_config_funcs = {
137 .fb_create = tinydrm_fb_create,
138 .atomic_check = drm_atomic_helper_check,
139 .atomic_commit = drm_atomic_helper_commit,
140};
141
142static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
143 const struct drm_framebuffer_funcs *fb_funcs,
144 struct drm_driver *driver)
145{
146 struct drm_device *drm;
147
148 mutex_init(&tdev->dirty_lock);
149 tdev->fb_funcs = fb_funcs;
150
151 /*
152 * We don't embed drm_device, because that prevent us from using
153 * devm_kzalloc() to allocate tinydrm_device in the driver since
154 * drm_dev_unref() frees the structure. The devm_ functions provide
155 * for easy error handling.
156 */
157 drm = drm_dev_alloc(driver, parent);
158 if (IS_ERR(drm))
159 return PTR_ERR(drm);
160
161 tdev->drm = drm;
162 drm->dev_private = tdev;
163 drm_mode_config_init(drm);
164 drm->mode_config.funcs = &tinydrm_mode_config_funcs;
165
166 return 0;
167}
168
169static void tinydrm_fini(struct tinydrm_device *tdev)
170{
171 drm_mode_config_cleanup(tdev->drm);
172 mutex_destroy(&tdev->dirty_lock);
173 tdev->drm->dev_private = NULL;
174 drm_dev_unref(tdev->drm);
175}
176
177static void devm_tinydrm_release(void *data)
178{
179 tinydrm_fini(data);
180}
181
182/**
183 * devm_tinydrm_init - Initialize tinydrm device
184 * @parent: Parent device object
185 * @tdev: tinydrm device
186 * @fb_funcs: Framebuffer functions
187 * @driver: DRM driver
188 *
189 * This function initializes @tdev, the underlying DRM device and it's
190 * mode_config. Resources will be automatically freed on driver detach (devres)
191 * using drm_mode_config_cleanup() and drm_dev_unref().
192 *
193 * Returns:
194 * Zero on success, negative error code on failure.
195 */
196int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev,
197 const struct drm_framebuffer_funcs *fb_funcs,
198 struct drm_driver *driver)
199{
200 int ret;
201
202 ret = tinydrm_init(parent, tdev, fb_funcs, driver);
203 if (ret)
204 return ret;
205
206 ret = devm_add_action(parent, devm_tinydrm_release, tdev);
207 if (ret)
208 tinydrm_fini(tdev);
209
210 return ret;
211}
212EXPORT_SYMBOL(devm_tinydrm_init);
213
214static int tinydrm_register(struct tinydrm_device *tdev)
215{
216 struct drm_device *drm = tdev->drm;
217 int bpp = drm->mode_config.preferred_depth;
218 struct drm_fbdev_cma *fbdev;
219 int ret;
220
221 ret = drm_dev_register(tdev->drm, 0);
222 if (ret)
223 return ret;
224
225 fbdev = drm_fbdev_cma_init_with_funcs(drm, bpp ? bpp : 32,
226 drm->mode_config.num_connector,
227 tdev->fb_funcs);
228 if (IS_ERR(fbdev))
229 DRM_ERROR("Failed to initialize fbdev: %ld\n", PTR_ERR(fbdev));
230 else
231 tdev->fbdev_cma = fbdev;
232
233 return 0;
234}
235
236static void tinydrm_unregister(struct tinydrm_device *tdev)
237{
238 struct drm_fbdev_cma *fbdev_cma = tdev->fbdev_cma;
239
240 drm_atomic_helper_shutdown(tdev->drm);
241 /* don't restore fbdev in lastclose, keep pipeline disabled */
242 tdev->fbdev_cma = NULL;
243 drm_dev_unregister(tdev->drm);
244 if (fbdev_cma)
245 drm_fbdev_cma_fini(fbdev_cma);
246}
247
248static void devm_tinydrm_register_release(void *data)
249{
250 tinydrm_unregister(data);
251}
252
253/**
254 * devm_tinydrm_register - Register tinydrm device
255 * @tdev: tinydrm device
256 *
257 * This function registers the underlying DRM device and fbdev.
258 * These resources will be automatically unregistered on driver detach (devres)
259 * and the display pipeline will be disabled.
260 *
261 * Returns:
262 * Zero on success, negative error code on failure.
263 */
264int devm_tinydrm_register(struct tinydrm_device *tdev)
265{
266 struct device *dev = tdev->drm->dev;
267 int ret;
268
269 ret = tinydrm_register(tdev);
270 if (ret)
271 return ret;
272
273 ret = devm_add_action(dev, devm_tinydrm_register_release, tdev);
274 if (ret)
275 tinydrm_unregister(tdev);
276
277 return ret;
278}
279EXPORT_SYMBOL(devm_tinydrm_register);
280
281/**
282 * tinydrm_shutdown - Shutdown tinydrm
283 * @tdev: tinydrm device
284 *
285 * This function makes sure that the display pipeline is disabled.
286 * Used by drivers in their shutdown callback to turn off the display
287 * on machine shutdown and reboot.
288 */
289void tinydrm_shutdown(struct tinydrm_device *tdev)
290{
291 drm_atomic_helper_shutdown(tdev->drm);
292}
293EXPORT_SYMBOL(tinydrm_shutdown);
294
295/**
296 * tinydrm_suspend - Suspend tinydrm
297 * @tdev: tinydrm device
298 *
299 * Used in driver PM operations to suspend tinydrm.
300 * Suspends fbdev and DRM.
301 * Resume with tinydrm_resume().
302 *
303 * Returns:
304 * Zero on success, negative error code on failure.
305 */
306int tinydrm_suspend(struct tinydrm_device *tdev)
307{
308 struct drm_atomic_state *state;
309
310 if (tdev->suspend_state) {
311 DRM_ERROR("Failed to suspend: state already set\n");
312 return -EINVAL;
313 }
314
315 drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 1);
316 state = drm_atomic_helper_suspend(tdev->drm);
317 if (IS_ERR(state)) {
318 drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 0);
319 return PTR_ERR(state);
320 }
321
322 tdev->suspend_state = state;
323
324 return 0;
325}
326EXPORT_SYMBOL(tinydrm_suspend);
327
328/**
329 * tinydrm_resume - Resume tinydrm
330 * @tdev: tinydrm device
331 *
332 * Used in driver PM operations to resume tinydrm.
333 * Suspend with tinydrm_suspend().
334 *
335 * Returns:
336 * Zero on success, negative error code on failure.
337 */
338int tinydrm_resume(struct tinydrm_device *tdev)
339{
340 struct drm_atomic_state *state = tdev->suspend_state;
341 int ret;
342
343 if (!state) {
344 DRM_ERROR("Failed to resume: state is not set\n");
345 return -EINVAL;
346 }
347
348 tdev->suspend_state = NULL;
349
350 ret = drm_atomic_helper_resume(tdev->drm, state);
351 if (ret) {
352 DRM_ERROR("Error resuming state: %d\n", ret);
353 return ret;
354 }
355
356 drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 0);
357
358 return 0;
359}
360EXPORT_SYMBOL(tinydrm_resume);
361
362MODULE_LICENSE("GPL");