Commit | Line | Data |
---|---|---|
8ab59da2 TZ |
1 | // SPDX-License-Identifier: MIT |
2 | ||
3 | #include <linux/moduleparam.h> | |
478f6213 | 4 | #include <linux/vmalloc.h> |
8ab59da2 TZ |
5 | |
6 | #include <drm/drm_crtc_helper.h> | |
7 | #include <drm/drm_drv.h> | |
8 | #include <drm/drm_fb_helper.h> | |
9 | #include <drm/drm_framebuffer.h> | |
8fbc9af5 | 10 | #include <drm/drm_gem.h> |
8ab59da2 TZ |
11 | #include <drm/drm_print.h> |
12 | ||
13 | #include <drm/drm_fbdev_generic.h> | |
14 | ||
8ab59da2 | 15 | /* @user: 1=userspace, 0=fbcon */ |
b9c93f4e | 16 | static int drm_fbdev_generic_fb_open(struct fb_info *info, int user) |
8ab59da2 TZ |
17 | { |
18 | struct drm_fb_helper *fb_helper = info->par; | |
19 | ||
20 | /* No need to take a ref for fbcon because it unbinds on unregister */ | |
21 | if (user && !try_module_get(fb_helper->dev->driver->fops->owner)) | |
22 | return -ENODEV; | |
23 | ||
24 | return 0; | |
25 | } | |
26 | ||
b9c93f4e | 27 | static int drm_fbdev_generic_fb_release(struct fb_info *info, int user) |
8ab59da2 TZ |
28 | { |
29 | struct drm_fb_helper *fb_helper = info->par; | |
30 | ||
31 | if (user) | |
32 | module_put(fb_helper->dev->driver->fops->owner); | |
33 | ||
34 | return 0; | |
35 | } | |
36 | ||
744d35d3 TZ |
37 | FB_GEN_DEFAULT_DEFERRED_SYSMEM_OPS(drm_fbdev_generic, |
38 | drm_fb_helper_damage_range, | |
39 | drm_fb_helper_damage_area); | |
c6baad68 | 40 | |
b9c93f4e | 41 | static void drm_fbdev_generic_fb_destroy(struct fb_info *info) |
8ab59da2 | 42 | { |
7f5fe873 | 43 | struct drm_fb_helper *fb_helper = info->par; |
a5b44c4a | 44 | void *shadow = info->screen_buffer; |
8ab59da2 TZ |
45 | |
46 | if (!fb_helper->dev) | |
47 | return; | |
48 | ||
a5b44c4a | 49 | fb_deferred_io_cleanup(info); |
8ab59da2 | 50 | drm_fb_helper_fini(fb_helper); |
a5b44c4a | 51 | vfree(shadow); |
8ab59da2 | 52 | drm_client_framebuffer_delete(fb_helper->buffer); |
3fb1f62f | 53 | |
a5b44c4a | 54 | drm_client_release(&fb_helper->client); |
3fb1f62f | 55 | drm_fb_helper_unprepare(fb_helper); |
8ab59da2 TZ |
56 | kfree(fb_helper); |
57 | } | |
58 | ||
b9c93f4e | 59 | static const struct fb_ops drm_fbdev_generic_fb_ops = { |
8ab59da2 | 60 | .owner = THIS_MODULE, |
b9c93f4e TZ |
61 | .fb_open = drm_fbdev_generic_fb_open, |
62 | .fb_release = drm_fbdev_generic_fb_release, | |
c6baad68 | 63 | FB_DEFAULT_DEFERRED_OPS(drm_fbdev_generic), |
a5b44c4a | 64 | DRM_FB_HELPER_DEFAULT_OPS, |
b9c93f4e | 65 | .fb_destroy = drm_fbdev_generic_fb_destroy, |
8ab59da2 TZ |
66 | }; |
67 | ||
8ab59da2 TZ |
68 | /* |
69 | * This function uses the client API to create a framebuffer backed by a dumb buffer. | |
70 | */ | |
b9c93f4e TZ |
71 | static int drm_fbdev_generic_helper_fb_probe(struct drm_fb_helper *fb_helper, |
72 | struct drm_fb_helper_surface_size *sizes) | |
8ab59da2 TZ |
73 | { |
74 | struct drm_client_dev *client = &fb_helper->client; | |
75 | struct drm_device *dev = fb_helper->dev; | |
76 | struct drm_client_buffer *buffer; | |
6ca80b9e | 77 | struct fb_info *info; |
8fbc9af5 | 78 | size_t screen_size; |
61b5d007 | 79 | void *screen_buffer; |
8ab59da2 | 80 | u32 format; |
8ab59da2 TZ |
81 | int ret; |
82 | ||
83 | drm_dbg_kms(dev, "surface width(%d), height(%d) and bpp(%d)\n", | |
84 | sizes->surface_width, sizes->surface_height, | |
85 | sizes->surface_bpp); | |
86 | ||
87 | format = drm_mode_legacy_fb_format(sizes->surface_bpp, sizes->surface_depth); | |
88 | buffer = drm_client_framebuffer_create(client, sizes->surface_width, | |
89 | sizes->surface_height, format); | |
90 | if (IS_ERR(buffer)) | |
91 | return PTR_ERR(buffer); | |
92 | ||
93 | fb_helper->buffer = buffer; | |
94 | fb_helper->fb = buffer->fb; | |
61b5d007 | 95 | |
8fbc9af5 | 96 | screen_size = buffer->gem->size; |
61b5d007 TZ |
97 | screen_buffer = vzalloc(screen_size); |
98 | if (!screen_buffer) { | |
99 | ret = -ENOMEM; | |
100 | goto err_drm_client_framebuffer_delete; | |
101 | } | |
8ab59da2 | 102 | |
6ca80b9e | 103 | info = drm_fb_helper_alloc_info(fb_helper); |
61b5d007 TZ |
104 | if (IS_ERR(info)) { |
105 | ret = PTR_ERR(info); | |
106 | goto err_vfree; | |
107 | } | |
108 | ||
109 | drm_fb_helper_fill_info(info, fb_helper, sizes); | |
8ab59da2 | 110 | |
b9c93f4e | 111 | info->fbops = &drm_fbdev_generic_fb_ops; |
8ab59da2 | 112 | |
61b5d007 | 113 | /* screen */ |
a5b44c4a | 114 | info->flags |= FBINFO_VIRTFB | FBINFO_READS_FAST; |
61b5d007 | 115 | info->screen_buffer = screen_buffer; |
a5b44c4a | 116 | info->fix.smem_start = page_to_phys(vmalloc_to_page(info->screen_buffer)); |
61b5d007 | 117 | info->fix.smem_len = screen_size; |
d6591da5 | 118 | |
61b5d007 | 119 | /* deferred I/O */ |
a5b44c4a TZ |
120 | fb_helper->fbdefio.delay = HZ / 20; |
121 | fb_helper->fbdefio.deferred_io = drm_fb_helper_deferred_io; | |
122 | ||
123 | info->fbdefio = &fb_helper->fbdefio; | |
124 | ret = fb_deferred_io_init(info); | |
125 | if (ret) | |
61b5d007 | 126 | goto err_drm_fb_helper_release_info; |
8ab59da2 TZ |
127 | |
128 | return 0; | |
61b5d007 TZ |
129 | |
130 | err_drm_fb_helper_release_info: | |
131 | drm_fb_helper_release_info(fb_helper); | |
132 | err_vfree: | |
133 | vfree(screen_buffer); | |
134 | err_drm_client_framebuffer_delete: | |
135 | fb_helper->fb = NULL; | |
136 | fb_helper->buffer = NULL; | |
137 | drm_client_framebuffer_delete(buffer); | |
138 | return ret; | |
8ab59da2 TZ |
139 | } |
140 | ||
b9c93f4e TZ |
141 | static void drm_fbdev_generic_damage_blit_real(struct drm_fb_helper *fb_helper, |
142 | struct drm_clip_rect *clip, | |
143 | struct iosys_map *dst) | |
8ab59da2 TZ |
144 | { |
145 | struct drm_framebuffer *fb = fb_helper->fb; | |
146 | size_t offset = clip->y1 * fb->pitches[0]; | |
147 | size_t len = clip->x2 - clip->x1; | |
148 | unsigned int y; | |
149 | void *src; | |
150 | ||
151 | switch (drm_format_info_bpp(fb->format, 0)) { | |
152 | case 1: | |
153 | offset += clip->x1 / 8; | |
154 | len = DIV_ROUND_UP(len + clip->x1 % 8, 8); | |
155 | break; | |
156 | case 2: | |
157 | offset += clip->x1 / 4; | |
158 | len = DIV_ROUND_UP(len + clip->x1 % 4, 4); | |
159 | break; | |
160 | case 4: | |
161 | offset += clip->x1 / 2; | |
162 | len = DIV_ROUND_UP(len + clip->x1 % 2, 2); | |
163 | break; | |
164 | default: | |
165 | offset += clip->x1 * fb->format->cpp[0]; | |
166 | len *= fb->format->cpp[0]; | |
167 | break; | |
168 | } | |
169 | ||
170 | src = fb_helper->info->screen_buffer + offset; | |
171 | iosys_map_incr(dst, offset); /* go to first pixel within clip rect */ | |
172 | ||
173 | for (y = clip->y1; y < clip->y2; y++) { | |
174 | iosys_map_memcpy_to(dst, 0, src, len); | |
175 | iosys_map_incr(dst, fb->pitches[0]); | |
176 | src += fb->pitches[0]; | |
177 | } | |
178 | } | |
179 | ||
b9c93f4e TZ |
180 | static int drm_fbdev_generic_damage_blit(struct drm_fb_helper *fb_helper, |
181 | struct drm_clip_rect *clip) | |
8ab59da2 TZ |
182 | { |
183 | struct drm_client_buffer *buffer = fb_helper->buffer; | |
184 | struct iosys_map map, dst; | |
185 | int ret; | |
186 | ||
187 | /* | |
188 | * We have to pin the client buffer to its current location while | |
189 | * flushing the shadow buffer. In the general case, concurrent | |
190 | * modesetting operations could try to move the buffer and would | |
191 | * fail. The modeset has to be serialized by acquiring the reservation | |
192 | * object of the underlying BO here. | |
193 | * | |
194 | * For fbdev emulation, we only have to protect against fbdev modeset | |
195 | * operations. Nothing else will involve the client buffer's BO. So it | |
196 | * is sufficient to acquire struct drm_fb_helper.lock here. | |
197 | */ | |
198 | mutex_lock(&fb_helper->lock); | |
199 | ||
200 | ret = drm_client_buffer_vmap(buffer, &map); | |
201 | if (ret) | |
202 | goto out; | |
203 | ||
204 | dst = map; | |
b9c93f4e | 205 | drm_fbdev_generic_damage_blit_real(fb_helper, clip, &dst); |
8ab59da2 TZ |
206 | |
207 | drm_client_buffer_vunmap(buffer); | |
208 | ||
209 | out: | |
210 | mutex_unlock(&fb_helper->lock); | |
211 | ||
212 | return ret; | |
213 | } | |
214 | ||
b9c93f4e TZ |
215 | static int drm_fbdev_generic_helper_fb_dirty(struct drm_fb_helper *helper, |
216 | struct drm_clip_rect *clip) | |
8ab59da2 TZ |
217 | { |
218 | struct drm_device *dev = helper->dev; | |
219 | int ret; | |
220 | ||
8ab59da2 TZ |
221 | /* Call damage handlers only if necessary */ |
222 | if (!(clip->x1 < clip->x2 && clip->y1 < clip->y2)) | |
223 | return 0; | |
224 | ||
b9c93f4e | 225 | ret = drm_fbdev_generic_damage_blit(helper, clip); |
a5b44c4a TZ |
226 | if (drm_WARN_ONCE(dev, ret, "Damage blitter failed: ret=%d\n", ret)) |
227 | return ret; | |
8ab59da2 TZ |
228 | |
229 | if (helper->fb->funcs->dirty) { | |
230 | ret = helper->fb->funcs->dirty(helper->fb, NULL, 0, 0, clip, 1); | |
231 | if (drm_WARN_ONCE(dev, ret, "Dirty helper failed: ret=%d\n", ret)) | |
232 | return ret; | |
233 | } | |
234 | ||
235 | return 0; | |
236 | } | |
237 | ||
b9c93f4e TZ |
238 | static const struct drm_fb_helper_funcs drm_fbdev_generic_helper_funcs = { |
239 | .fb_probe = drm_fbdev_generic_helper_fb_probe, | |
240 | .fb_dirty = drm_fbdev_generic_helper_fb_dirty, | |
8ab59da2 TZ |
241 | }; |
242 | ||
b9c93f4e | 243 | static void drm_fbdev_generic_client_unregister(struct drm_client_dev *client) |
8ab59da2 TZ |
244 | { |
245 | struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); | |
246 | ||
032116bb | 247 | if (fb_helper->info) { |
8ab59da2 | 248 | drm_fb_helper_unregister_info(fb_helper); |
032116bb TZ |
249 | } else { |
250 | drm_client_release(&fb_helper->client); | |
251 | drm_fb_helper_unprepare(fb_helper); | |
252 | kfree(fb_helper); | |
253 | } | |
8ab59da2 TZ |
254 | } |
255 | ||
b9c93f4e | 256 | static int drm_fbdev_generic_client_restore(struct drm_client_dev *client) |
8ab59da2 TZ |
257 | { |
258 | drm_fb_helper_lastclose(client->dev); | |
259 | ||
260 | return 0; | |
261 | } | |
262 | ||
b9c93f4e | 263 | static int drm_fbdev_generic_client_hotplug(struct drm_client_dev *client) |
8ab59da2 TZ |
264 | { |
265 | struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); | |
266 | struct drm_device *dev = client->dev; | |
267 | int ret; | |
268 | ||
8ab59da2 TZ |
269 | if (dev->fb_helper) |
270 | return drm_fb_helper_hotplug_event(dev->fb_helper); | |
271 | ||
8ab59da2 TZ |
272 | ret = drm_fb_helper_init(dev, fb_helper); |
273 | if (ret) | |
643231b2 | 274 | goto err_drm_err; |
8ab59da2 TZ |
275 | |
276 | if (!drm_drv_uses_atomic_modeset(dev)) | |
277 | drm_helper_disable_unused_functions(dev); | |
278 | ||
6c80a93b | 279 | ret = drm_fb_helper_initial_config(fb_helper); |
8ab59da2 | 280 | if (ret) |
643231b2 | 281 | goto err_drm_fb_helper_fini; |
8ab59da2 TZ |
282 | |
283 | return 0; | |
284 | ||
643231b2 TZ |
285 | err_drm_fb_helper_fini: |
286 | drm_fb_helper_fini(fb_helper); | |
287 | err_drm_err: | |
8ab59da2 | 288 | drm_err(dev, "fbdev: Failed to setup generic emulation (ret=%d)\n", ret); |
8ab59da2 TZ |
289 | return ret; |
290 | } | |
291 | ||
b9c93f4e | 292 | static const struct drm_client_funcs drm_fbdev_generic_client_funcs = { |
8ab59da2 | 293 | .owner = THIS_MODULE, |
b9c93f4e TZ |
294 | .unregister = drm_fbdev_generic_client_unregister, |
295 | .restore = drm_fbdev_generic_client_restore, | |
296 | .hotplug = drm_fbdev_generic_client_hotplug, | |
8ab59da2 TZ |
297 | }; |
298 | ||
299 | /** | |
300 | * drm_fbdev_generic_setup() - Setup generic fbdev emulation | |
301 | * @dev: DRM device | |
302 | * @preferred_bpp: Preferred bits per pixel for the device. | |
8ab59da2 TZ |
303 | * |
304 | * This function sets up generic fbdev emulation for drivers that supports | |
305 | * dumb buffers with a virtual address and that can be mmap'ed. | |
306 | * drm_fbdev_generic_setup() shall be called after the DRM driver registered | |
307 | * the new DRM device with drm_dev_register(). | |
308 | * | |
309 | * Restore, hotplug events and teardown are all taken care of. Drivers that do | |
310 | * suspend/resume need to call drm_fb_helper_set_suspend_unlocked() themselves. | |
311 | * Simple drivers might use drm_mode_config_helper_suspend(). | |
312 | * | |
a5b44c4a TZ |
313 | * In order to provide fixed mmap-able memory ranges, generic fbdev emulation |
314 | * uses a shadow buffer in system memory. The implementation blits the shadow | |
315 | * fbdev buffer onto the real buffer in regular intervals. | |
8ab59da2 TZ |
316 | * |
317 | * This function is safe to call even when there are no connectors present. | |
318 | * Setup will be retried on the next hotplug event. | |
319 | * | |
320 | * The fbdev is destroyed by drm_dev_unregister(). | |
321 | */ | |
b9c93f4e | 322 | void drm_fbdev_generic_setup(struct drm_device *dev, unsigned int preferred_bpp) |
8ab59da2 TZ |
323 | { |
324 | struct drm_fb_helper *fb_helper; | |
325 | int ret; | |
326 | ||
327 | drm_WARN(dev, !dev->registered, "Device has not been registered.\n"); | |
328 | drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n"); | |
329 | ||
330 | fb_helper = kzalloc(sizeof(*fb_helper), GFP_KERNEL); | |
331 | if (!fb_helper) | |
332 | return; | |
b9c93f4e | 333 | drm_fb_helper_prepare(dev, fb_helper, preferred_bpp, &drm_fbdev_generic_helper_funcs); |
8ab59da2 | 334 | |
b9c93f4e | 335 | ret = drm_client_init(dev, &fb_helper->client, "fbdev", &drm_fbdev_generic_client_funcs); |
8ab59da2 | 336 | if (ret) { |
8ab59da2 | 337 | drm_err(dev, "Failed to register client: %d\n", ret); |
f73ab51b | 338 | goto err_drm_client_init; |
8ab59da2 TZ |
339 | } |
340 | ||
8ab59da2 | 341 | drm_client_register(&fb_helper->client); |
f73ab51b TZ |
342 | |
343 | return; | |
344 | ||
345 | err_drm_client_init: | |
346 | drm_fb_helper_unprepare(fb_helper); | |
347 | kfree(fb_helper); | |
348 | return; | |
8ab59da2 TZ |
349 | } |
350 | EXPORT_SYMBOL(drm_fbdev_generic_setup); |