1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2011 Texas Instruments Incorporated - https://www.ti.com/ |
4 | * Author: Rob Clark <rob@ti.com> |
5 | */ |
6 | |
7 | #include <linux/fb.h> |
8 | |
9 | #include <drm/drm_drv.h> |
10 | #include <drm/drm_crtc_helper.h> |
11 | #include <drm/drm_fb_helper.h> |
12 | #include <drm/drm_file.h> |
13 | #include <drm/drm_fourcc.h> |
14 | #include <drm/drm_framebuffer.h> |
15 | #include <drm/drm_gem_framebuffer_helper.h> |
16 | #include <drm/drm_util.h> |
17 | |
18 | #include "omap_drv.h" |
19 | #include "omap_fbdev.h" |
20 | |
21 | MODULE_PARM_DESC(ywrap, "Enable ywrap scrolling (omap44xx and later, default 'y')" ); |
22 | static bool ywrap_enabled = true; |
23 | module_param_named(ywrap, ywrap_enabled, bool, 0644); |
24 | |
25 | /* |
26 | * fbdev funcs, to implement legacy fbdev interface on top of drm driver |
27 | */ |
28 | |
29 | #define to_omap_fbdev(x) container_of(x, struct omap_fbdev, base) |
30 | |
31 | struct omap_fbdev { |
32 | struct drm_fb_helper base; |
33 | bool ywrap_enabled; |
34 | |
35 | /* for deferred dmm roll when getting called in atomic ctx */ |
36 | struct work_struct work; |
37 | }; |
38 | |
39 | static struct drm_fb_helper *get_fb(struct fb_info *fbi); |
40 | |
41 | static void pan_worker(struct work_struct *work) |
42 | { |
43 | struct omap_fbdev *fbdev = container_of(work, struct omap_fbdev, work); |
44 | struct drm_fb_helper *helper = &fbdev->base; |
45 | struct fb_info *fbi = helper->info; |
46 | struct drm_gem_object *bo = drm_gem_fb_get_obj(fb: helper->fb, plane: 0); |
47 | int npages; |
48 | |
49 | /* DMM roll shifts in 4K pages: */ |
50 | npages = fbi->fix.line_length >> PAGE_SHIFT; |
51 | omap_gem_roll(obj: bo, roll: fbi->var.yoffset * npages); |
52 | } |
53 | |
54 | static int omap_fbdev_pan_display(struct fb_var_screeninfo *var, |
55 | struct fb_info *fbi) |
56 | { |
57 | struct drm_fb_helper *helper = get_fb(fbi); |
58 | struct omap_fbdev *fbdev = to_omap_fbdev(helper); |
59 | |
60 | if (!helper) |
61 | goto fallback; |
62 | |
63 | if (!fbdev->ywrap_enabled) |
64 | goto fallback; |
65 | |
66 | if (drm_can_sleep()) { |
67 | pan_worker(work: &fbdev->work); |
68 | } else { |
69 | struct omap_drm_private *priv = helper->dev->dev_private; |
70 | queue_work(wq: priv->wq, work: &fbdev->work); |
71 | } |
72 | |
73 | return 0; |
74 | |
75 | fallback: |
76 | return drm_fb_helper_pan_display(var, info: fbi); |
77 | } |
78 | |
79 | static int omap_fbdev_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) |
80 | { |
81 | struct drm_fb_helper *helper = info->par; |
82 | struct drm_framebuffer *fb = helper->fb; |
83 | struct drm_gem_object *bo = drm_gem_fb_get_obj(fb, plane: 0); |
84 | |
85 | return drm_gem_mmap_obj(obj: bo, obj_size: omap_gem_mmap_size(obj: bo), vma); |
86 | } |
87 | |
88 | static void omap_fbdev_fb_destroy(struct fb_info *info) |
89 | { |
90 | struct drm_fb_helper *helper = info->par; |
91 | struct drm_framebuffer *fb = helper->fb; |
92 | struct drm_gem_object *bo = drm_gem_fb_get_obj(fb, plane: 0); |
93 | struct omap_fbdev *fbdev = to_omap_fbdev(helper); |
94 | |
95 | DBG(); |
96 | |
97 | drm_fb_helper_fini(helper); |
98 | |
99 | omap_gem_unpin(obj: bo); |
100 | drm_framebuffer_remove(fb); |
101 | |
102 | drm_client_release(client: &helper->client); |
103 | drm_fb_helper_unprepare(fb_helper: helper); |
104 | kfree(objp: fbdev); |
105 | } |
106 | |
107 | static const struct fb_ops omap_fb_ops = { |
108 | .owner = THIS_MODULE, |
109 | __FB_DEFAULT_DMAMEM_OPS_RDWR, |
110 | .fb_check_var = drm_fb_helper_check_var, |
111 | .fb_set_par = drm_fb_helper_set_par, |
112 | .fb_setcmap = drm_fb_helper_setcmap, |
113 | .fb_blank = drm_fb_helper_blank, |
114 | .fb_pan_display = omap_fbdev_pan_display, |
115 | __FB_DEFAULT_DMAMEM_OPS_DRAW, |
116 | .fb_ioctl = drm_fb_helper_ioctl, |
117 | .fb_mmap = omap_fbdev_fb_mmap, |
118 | .fb_destroy = omap_fbdev_fb_destroy, |
119 | }; |
120 | |
121 | static int omap_fbdev_create(struct drm_fb_helper *helper, |
122 | struct drm_fb_helper_surface_size *sizes) |
123 | { |
124 | struct omap_fbdev *fbdev = to_omap_fbdev(helper); |
125 | struct drm_device *dev = helper->dev; |
126 | struct omap_drm_private *priv = dev->dev_private; |
127 | struct drm_framebuffer *fb = NULL; |
128 | union omap_gem_size gsize; |
129 | struct fb_info *fbi = NULL; |
130 | struct drm_mode_fb_cmd2 mode_cmd = {0}; |
131 | struct drm_gem_object *bo; |
132 | dma_addr_t dma_addr; |
133 | int ret; |
134 | |
135 | sizes->surface_bpp = 32; |
136 | sizes->surface_depth = 24; |
137 | |
138 | DBG("create fbdev: %dx%d@%d (%dx%d)" , sizes->surface_width, |
139 | sizes->surface_height, sizes->surface_bpp, |
140 | sizes->fb_width, sizes->fb_height); |
141 | |
142 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(bpp: sizes->surface_bpp, |
143 | depth: sizes->surface_depth); |
144 | |
145 | mode_cmd.width = sizes->surface_width; |
146 | mode_cmd.height = sizes->surface_height; |
147 | |
148 | mode_cmd.pitches[0] = |
149 | DIV_ROUND_UP(mode_cmd.width * sizes->surface_bpp, 8); |
150 | |
151 | fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled; |
152 | if (fbdev->ywrap_enabled) { |
153 | /* need to align pitch to page size if using DMM scrolling */ |
154 | mode_cmd.pitches[0] = PAGE_ALIGN(mode_cmd.pitches[0]); |
155 | } |
156 | |
157 | /* allocate backing bo */ |
158 | gsize = (union omap_gem_size){ |
159 | .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height), |
160 | }; |
161 | DBG("allocating %d bytes for fb %d" , gsize.bytes, dev->primary->index); |
162 | bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC); |
163 | if (!bo) { |
164 | dev_err(dev->dev, "failed to allocate buffer object\n" ); |
165 | ret = -ENOMEM; |
166 | goto fail; |
167 | } |
168 | |
169 | fb = omap_framebuffer_init(dev, mode_cmd: &mode_cmd, bos: &bo); |
170 | if (IS_ERR(ptr: fb)) { |
171 | dev_err(dev->dev, "failed to allocate fb\n" ); |
172 | /* note: if fb creation failed, we can't rely on fb destroy |
173 | * to unref the bo: |
174 | */ |
175 | drm_gem_object_put(obj: bo); |
176 | ret = PTR_ERR(ptr: fb); |
177 | goto fail; |
178 | } |
179 | |
180 | /* note: this keeps the bo pinned.. which is perhaps not ideal, |
181 | * but is needed as long as we use fb_mmap() to mmap to userspace |
182 | * (since this happens using fix.smem_start). Possibly we could |
183 | * implement our own mmap using GEM mmap support to avoid this |
184 | * (non-tiled buffer doesn't need to be pinned for fbcon to write |
185 | * to it). Then we just need to be sure that we are able to re- |
186 | * pin it in case of an opps. |
187 | */ |
188 | ret = omap_gem_pin(obj: bo, dma_addr: &dma_addr); |
189 | if (ret) { |
190 | dev_err(dev->dev, "could not pin framebuffer\n" ); |
191 | ret = -ENOMEM; |
192 | goto fail; |
193 | } |
194 | |
195 | fbi = drm_fb_helper_alloc_info(fb_helper: helper); |
196 | if (IS_ERR(ptr: fbi)) { |
197 | dev_err(dev->dev, "failed to allocate fb info\n" ); |
198 | ret = PTR_ERR(ptr: fbi); |
199 | goto fail; |
200 | } |
201 | |
202 | DBG("fbi=%p, dev=%p" , fbi, dev); |
203 | |
204 | helper->fb = fb; |
205 | |
206 | fbi->fbops = &omap_fb_ops; |
207 | |
208 | drm_fb_helper_fill_info(info: fbi, fb_helper: helper, sizes); |
209 | |
210 | fbi->flags |= FBINFO_VIRTFB; |
211 | fbi->screen_buffer = omap_gem_vaddr(obj: bo); |
212 | fbi->screen_size = bo->size; |
213 | fbi->fix.smem_start = dma_addr; |
214 | fbi->fix.smem_len = bo->size; |
215 | |
216 | /* if we have DMM, then we can use it for scrolling by just |
217 | * shuffling pages around in DMM rather than doing sw blit. |
218 | */ |
219 | if (fbdev->ywrap_enabled) { |
220 | DRM_INFO("Enabling DMM ywrap scrolling\n" ); |
221 | fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST; |
222 | fbi->fix.ywrapstep = 1; |
223 | } |
224 | |
225 | |
226 | DBG("par=%p, %dx%d" , fbi->par, fbi->var.xres, fbi->var.yres); |
227 | DBG("allocated %dx%d fb" , fb->width, fb->height); |
228 | |
229 | return 0; |
230 | |
231 | fail: |
232 | |
233 | if (ret) { |
234 | if (fb) |
235 | drm_framebuffer_remove(fb); |
236 | } |
237 | |
238 | return ret; |
239 | } |
240 | |
241 | static const struct drm_fb_helper_funcs omap_fb_helper_funcs = { |
242 | .fb_probe = omap_fbdev_create, |
243 | }; |
244 | |
245 | static struct drm_fb_helper *get_fb(struct fb_info *fbi) |
246 | { |
247 | if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) { |
248 | /* these are not the fb's you're looking for */ |
249 | return NULL; |
250 | } |
251 | return fbi->par; |
252 | } |
253 | |
254 | /* |
255 | * struct drm_client |
256 | */ |
257 | |
258 | static void omap_fbdev_client_unregister(struct drm_client_dev *client) |
259 | { |
260 | struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); |
261 | |
262 | if (fb_helper->info) { |
263 | drm_fb_helper_unregister_info(fb_helper); |
264 | } else { |
265 | drm_client_release(client: &fb_helper->client); |
266 | drm_fb_helper_unprepare(fb_helper); |
267 | kfree(objp: fb_helper); |
268 | } |
269 | } |
270 | |
271 | static int omap_fbdev_client_restore(struct drm_client_dev *client) |
272 | { |
273 | drm_fb_helper_lastclose(dev: client->dev); |
274 | |
275 | return 0; |
276 | } |
277 | |
278 | static int omap_fbdev_client_hotplug(struct drm_client_dev *client) |
279 | { |
280 | struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); |
281 | struct drm_device *dev = client->dev; |
282 | int ret; |
283 | |
284 | if (dev->fb_helper) |
285 | return drm_fb_helper_hotplug_event(fb_helper: dev->fb_helper); |
286 | |
287 | ret = drm_fb_helper_init(dev, helper: fb_helper); |
288 | if (ret) |
289 | goto err_drm_err; |
290 | |
291 | ret = drm_fb_helper_initial_config(fb_helper); |
292 | if (ret) |
293 | goto err_drm_fb_helper_fini; |
294 | |
295 | return 0; |
296 | |
297 | err_drm_fb_helper_fini: |
298 | drm_fb_helper_fini(helper: fb_helper); |
299 | err_drm_err: |
300 | drm_err(dev, "Failed to setup fbdev emulation (ret=%d)\n" , ret); |
301 | return ret; |
302 | } |
303 | |
304 | static const struct drm_client_funcs omap_fbdev_client_funcs = { |
305 | .owner = THIS_MODULE, |
306 | .unregister = omap_fbdev_client_unregister, |
307 | .restore = omap_fbdev_client_restore, |
308 | .hotplug = omap_fbdev_client_hotplug, |
309 | }; |
310 | |
311 | void omap_fbdev_setup(struct drm_device *dev) |
312 | { |
313 | struct omap_fbdev *fbdev; |
314 | struct drm_fb_helper *helper; |
315 | int ret; |
316 | |
317 | drm_WARN(dev, !dev->registered, "Device has not been registered.\n" ); |
318 | drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n" ); |
319 | |
320 | fbdev = kzalloc(size: sizeof(*fbdev), GFP_KERNEL); |
321 | if (!fbdev) |
322 | return; |
323 | helper = &fbdev->base; |
324 | |
325 | drm_fb_helper_prepare(dev, helper, preferred_bpp: 32, funcs: &omap_fb_helper_funcs); |
326 | |
327 | ret = drm_client_init(dev, client: &helper->client, name: "fbdev" , funcs: &omap_fbdev_client_funcs); |
328 | if (ret) |
329 | goto err_drm_client_init; |
330 | |
331 | INIT_WORK(&fbdev->work, pan_worker); |
332 | |
333 | drm_client_register(client: &helper->client); |
334 | |
335 | return; |
336 | |
337 | err_drm_client_init: |
338 | drm_fb_helper_unprepare(fb_helper: helper); |
339 | kfree(objp: fbdev); |
340 | } |
341 | |