Commit | Line | Data |
---|---|---|
cd5351f4 RC |
1 | /* |
2 | * drivers/staging/omapdrm/omap_fbdev.c | |
3 | * | |
4 | * Copyright (C) 2011 Texas Instruments | |
5 | * Author: Rob Clark <rob@ti.com> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify it | |
8 | * under the terms of the GNU General Public License version 2 as published by | |
9 | * the Free Software Foundation. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
14 | * more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License along with | |
17 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include "omap_drv.h" | |
21 | ||
22 | #include "drm_crtc.h" | |
23 | #include "drm_fb_helper.h" | |
24 | ||
510d4d32 RC |
25 | MODULE_PARM_DESC(ywrap, "Enable ywrap scrolling (omap44xx and later, default 'y')"); |
26 | static bool ywrap_enabled = true; | |
27 | module_param_named(ywrap, ywrap_enabled, bool, 0644); | |
28 | ||
cd5351f4 RC |
29 | /* |
30 | * fbdev funcs, to implement legacy fbdev interface on top of drm driver | |
31 | */ | |
32 | ||
33 | #define to_omap_fbdev(x) container_of(x, struct omap_fbdev, base) | |
34 | ||
35 | struct omap_fbdev { | |
36 | struct drm_fb_helper base; | |
37 | struct drm_framebuffer *fb; | |
a6a91827 | 38 | struct drm_gem_object *bo; |
510d4d32 | 39 | bool ywrap_enabled; |
9b55b95a RC |
40 | |
41 | /* for deferred dmm roll when getting called in atomic ctx */ | |
42 | struct work_struct work; | |
cd5351f4 RC |
43 | }; |
44 | ||
45 | static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h); | |
a6a91827 | 46 | static struct drm_fb_helper *get_fb(struct fb_info *fbi); |
cd5351f4 RC |
47 | |
48 | static ssize_t omap_fbdev_write(struct fb_info *fbi, const char __user *buf, | |
49 | size_t count, loff_t *ppos) | |
50 | { | |
51 | ssize_t res; | |
52 | ||
53 | res = fb_sys_write(fbi, buf, count, ppos); | |
54 | omap_fbdev_flush(fbi, 0, 0, fbi->var.xres, fbi->var.yres); | |
55 | ||
56 | return res; | |
57 | } | |
58 | ||
59 | static void omap_fbdev_fillrect(struct fb_info *fbi, | |
60 | const struct fb_fillrect *rect) | |
61 | { | |
62 | sys_fillrect(fbi, rect); | |
63 | omap_fbdev_flush(fbi, rect->dx, rect->dy, rect->width, rect->height); | |
64 | } | |
65 | ||
66 | static void omap_fbdev_copyarea(struct fb_info *fbi, | |
67 | const struct fb_copyarea *area) | |
68 | { | |
69 | sys_copyarea(fbi, area); | |
70 | omap_fbdev_flush(fbi, area->dx, area->dy, area->width, area->height); | |
71 | } | |
72 | ||
73 | static void omap_fbdev_imageblit(struct fb_info *fbi, | |
74 | const struct fb_image *image) | |
75 | { | |
76 | sys_imageblit(fbi, image); | |
77 | omap_fbdev_flush(fbi, image->dx, image->dy, | |
78 | image->width, image->height); | |
79 | } | |
80 | ||
9b55b95a RC |
81 | static void pan_worker(struct work_struct *work) |
82 | { | |
83 | struct omap_fbdev *fbdev = container_of(work, struct omap_fbdev, work); | |
84 | struct fb_info *fbi = fbdev->base.fbdev; | |
85 | int npages; | |
86 | ||
87 | /* DMM roll shifts in 4K pages: */ | |
88 | npages = fbi->fix.line_length >> PAGE_SHIFT; | |
89 | omap_gem_roll(fbdev->bo, fbi->var.yoffset * npages); | |
90 | } | |
91 | ||
a6a91827 RC |
92 | static int omap_fbdev_pan_display(struct fb_var_screeninfo *var, |
93 | struct fb_info *fbi) | |
94 | { | |
95 | struct drm_fb_helper *helper = get_fb(fbi); | |
96 | struct omap_fbdev *fbdev = to_omap_fbdev(helper); | |
a6a91827 RC |
97 | |
98 | if (!helper) | |
99 | goto fallback; | |
100 | ||
510d4d32 | 101 | if (!fbdev->ywrap_enabled) |
a6a91827 RC |
102 | goto fallback; |
103 | ||
9b55b95a RC |
104 | if (drm_can_sleep()) { |
105 | pan_worker(&fbdev->work); | |
106 | } else { | |
107 | struct omap_drm_private *priv = helper->dev->dev_private; | |
108 | queue_work(priv->wq, &fbdev->work); | |
109 | } | |
a6a91827 RC |
110 | |
111 | return 0; | |
112 | ||
113 | fallback: | |
114 | return drm_fb_helper_pan_display(var, fbi); | |
115 | } | |
116 | ||
cd5351f4 RC |
117 | static struct fb_ops omap_fb_ops = { |
118 | .owner = THIS_MODULE, | |
119 | ||
120 | /* Note: to properly handle manual update displays, we wrap the | |
121 | * basic fbdev ops which write to the framebuffer | |
122 | */ | |
123 | .fb_read = fb_sys_read, | |
124 | .fb_write = omap_fbdev_write, | |
125 | .fb_fillrect = omap_fbdev_fillrect, | |
126 | .fb_copyarea = omap_fbdev_copyarea, | |
127 | .fb_imageblit = omap_fbdev_imageblit, | |
128 | ||
129 | .fb_check_var = drm_fb_helper_check_var, | |
130 | .fb_set_par = drm_fb_helper_set_par, | |
a6a91827 | 131 | .fb_pan_display = omap_fbdev_pan_display, |
cd5351f4 RC |
132 | .fb_blank = drm_fb_helper_blank, |
133 | .fb_setcmap = drm_fb_helper_setcmap, | |
134 | ||
135 | .fb_debug_enter = drm_fb_helper_debug_enter, | |
136 | .fb_debug_leave = drm_fb_helper_debug_leave, | |
137 | }; | |
138 | ||
139 | static int omap_fbdev_create(struct drm_fb_helper *helper, | |
140 | struct drm_fb_helper_surface_size *sizes) | |
141 | { | |
142 | struct omap_fbdev *fbdev = to_omap_fbdev(helper); | |
143 | struct drm_device *dev = helper->dev; | |
a6a91827 | 144 | struct omap_drm_private *priv = dev->dev_private; |
cd5351f4 | 145 | struct drm_framebuffer *fb = NULL; |
a6a91827 | 146 | union omap_gem_size gsize; |
cd5351f4 | 147 | struct fb_info *fbi = NULL; |
ae43d7ca | 148 | struct drm_mode_fb_cmd2 mode_cmd = {0}; |
cd5351f4 | 149 | dma_addr_t paddr; |
cd5351f4 RC |
150 | int ret; |
151 | ||
152 | /* only doing ARGB32 since this is what is needed to alpha-blend | |
153 | * with video overlays: | |
154 | */ | |
155 | sizes->surface_bpp = 32; | |
156 | sizes->surface_depth = 32; | |
157 | ||
a6a91827 RC |
158 | DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width, |
159 | sizes->surface_height, sizes->surface_bpp, | |
160 | sizes->fb_width, sizes->fb_height); | |
cd5351f4 | 161 | |
ae43d7ca RC |
162 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, |
163 | sizes->surface_depth); | |
164 | ||
cd5351f4 RC |
165 | mode_cmd.width = sizes->surface_width; |
166 | mode_cmd.height = sizes->surface_height; | |
167 | ||
ae43d7ca RC |
168 | mode_cmd.pitches[0] = align_pitch( |
169 | mode_cmd.width * ((sizes->surface_bpp + 7) / 8), | |
170 | mode_cmd.width, sizes->surface_bpp); | |
a6a91827 | 171 | |
510d4d32 RC |
172 | fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled; |
173 | if (fbdev->ywrap_enabled) { | |
a6a91827 | 174 | /* need to align pitch to page size if using DMM scrolling */ |
ae43d7ca | 175 | mode_cmd.pitches[0] = ALIGN(mode_cmd.pitches[0], PAGE_SIZE); |
a6a91827 RC |
176 | } |
177 | ||
178 | /* allocate backing bo */ | |
179 | gsize = (union omap_gem_size){ | |
ae43d7ca | 180 | .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height), |
a6a91827 RC |
181 | }; |
182 | DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index); | |
183 | fbdev->bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC); | |
184 | if (!fbdev->bo) { | |
185 | dev_err(dev->dev, "failed to allocate buffer object\n"); | |
ae43d7ca | 186 | ret = -ENOMEM; |
a6a91827 RC |
187 | goto fail; |
188 | } | |
189 | ||
ae43d7ca RC |
190 | fb = omap_framebuffer_init(dev, &mode_cmd, &fbdev->bo); |
191 | if (IS_ERR(fb)) { | |
cd5351f4 | 192 | dev_err(dev->dev, "failed to allocate fb\n"); |
ae43d7ca RC |
193 | /* note: if fb creation failed, we can't rely on fb destroy |
194 | * to unref the bo: | |
195 | */ | |
196 | drm_gem_object_unreference(fbdev->bo); | |
197 | ret = PTR_ERR(fb); | |
198 | goto fail; | |
199 | } | |
200 | ||
201 | /* note: this keeps the bo pinned.. which is perhaps not ideal, | |
202 | * but is needed as long as we use fb_mmap() to mmap to userspace | |
203 | * (since this happens using fix.smem_start). Possibly we could | |
204 | * implement our own mmap using GEM mmap support to avoid this | |
205 | * (non-tiled buffer doesn't need to be pinned for fbcon to write | |
206 | * to it). Then we just need to be sure that we are able to re- | |
207 | * pin it in case of an opps. | |
208 | */ | |
209 | ret = omap_gem_get_paddr(fbdev->bo, &paddr, true); | |
210 | if (ret) { | |
afb6a6a0 AG |
211 | dev_err(dev->dev, |
212 | "could not map (paddr)! Skipping framebuffer alloc\n"); | |
cd5351f4 RC |
213 | ret = -ENOMEM; |
214 | goto fail; | |
215 | } | |
216 | ||
217 | mutex_lock(&dev->struct_mutex); | |
218 | ||
219 | fbi = framebuffer_alloc(0, dev->dev); | |
220 | if (!fbi) { | |
221 | dev_err(dev->dev, "failed to allocate fb info\n"); | |
222 | ret = -ENOMEM; | |
223 | goto fail_unlock; | |
224 | } | |
225 | ||
226 | DBG("fbi=%p, dev=%p", fbi, dev); | |
227 | ||
228 | fbdev->fb = fb; | |
229 | helper->fb = fb; | |
230 | helper->fbdev = fbi; | |
231 | ||
232 | fbi->par = helper; | |
233 | fbi->flags = FBINFO_DEFAULT; | |
234 | fbi->fbops = &omap_fb_ops; | |
235 | ||
236 | strcpy(fbi->fix.id, MODULE_NAME); | |
237 | ||
238 | ret = fb_alloc_cmap(&fbi->cmap, 256, 0); | |
239 | if (ret) { | |
240 | ret = -ENOMEM; | |
241 | goto fail_unlock; | |
242 | } | |
243 | ||
ae43d7ca | 244 | drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); |
a6a91827 | 245 | drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); |
cd5351f4 | 246 | |
cd5351f4 RC |
247 | dev->mode_config.fb_base = paddr; |
248 | ||
ae43d7ca RC |
249 | fbi->screen_base = omap_gem_vaddr(fbdev->bo); |
250 | fbi->screen_size = fbdev->bo->size; | |
cd5351f4 | 251 | fbi->fix.smem_start = paddr; |
ae43d7ca | 252 | fbi->fix.smem_len = fbdev->bo->size; |
cd5351f4 | 253 | |
a6a91827 RC |
254 | /* if we have DMM, then we can use it for scrolling by just |
255 | * shuffling pages around in DMM rather than doing sw blit. | |
256 | */ | |
510d4d32 | 257 | if (fbdev->ywrap_enabled) { |
a6a91827 RC |
258 | DRM_INFO("Enabling DMM ywrap scrolling\n"); |
259 | fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST; | |
260 | fbi->fix.ywrapstep = 1; | |
261 | } | |
262 | ||
510d4d32 | 263 | |
cd5351f4 RC |
264 | DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); |
265 | DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height); | |
266 | ||
267 | mutex_unlock(&dev->struct_mutex); | |
268 | ||
269 | return 0; | |
270 | ||
271 | fail_unlock: | |
272 | mutex_unlock(&dev->struct_mutex); | |
273 | fail: | |
274 | ||
275 | if (ret) { | |
276 | if (fbi) | |
277 | framebuffer_release(fbi); | |
278 | if (fb) | |
279 | fb->funcs->destroy(fb); | |
280 | } | |
281 | ||
282 | return ret; | |
283 | } | |
284 | ||
285 | static void omap_crtc_fb_gamma_set(struct drm_crtc *crtc, | |
286 | u16 red, u16 green, u16 blue, int regno) | |
287 | { | |
288 | DBG("fbdev: set gamma"); | |
289 | } | |
290 | ||
291 | static void omap_crtc_fb_gamma_get(struct drm_crtc *crtc, | |
292 | u16 *red, u16 *green, u16 *blue, int regno) | |
293 | { | |
294 | DBG("fbdev: get gamma"); | |
295 | } | |
296 | ||
297 | static int omap_fbdev_probe(struct drm_fb_helper *helper, | |
298 | struct drm_fb_helper_surface_size *sizes) | |
299 | { | |
300 | int new_fb = 0; | |
301 | int ret; | |
302 | ||
303 | if (!helper->fb) { | |
304 | ret = omap_fbdev_create(helper, sizes); | |
305 | if (ret) | |
306 | return ret; | |
307 | new_fb = 1; | |
308 | } | |
309 | return new_fb; | |
310 | } | |
311 | ||
312 | static struct drm_fb_helper_funcs omap_fb_helper_funcs = { | |
313 | .gamma_set = omap_crtc_fb_gamma_set, | |
314 | .gamma_get = omap_crtc_fb_gamma_get, | |
315 | .fb_probe = omap_fbdev_probe, | |
316 | }; | |
317 | ||
318 | static struct drm_fb_helper *get_fb(struct fb_info *fbi) | |
319 | { | |
320 | if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) { | |
321 | /* these are not the fb's you're looking for */ | |
322 | return NULL; | |
323 | } | |
324 | return fbi->par; | |
325 | } | |
326 | ||
327 | /* flush an area of the framebuffer (in case of manual update display that | |
328 | * is not automatically flushed) | |
329 | */ | |
330 | static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h) | |
331 | { | |
332 | struct drm_fb_helper *helper = get_fb(fbi); | |
333 | ||
334 | if (!helper) | |
335 | return; | |
336 | ||
337 | VERB("flush fbdev: %d,%d %dx%d, fbi=%p", x, y, w, h, fbi); | |
338 | ||
339 | omap_framebuffer_flush(helper->fb, x, y, w, h); | |
340 | } | |
341 | ||
342 | /* initialize fbdev helper */ | |
343 | struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev) | |
344 | { | |
345 | struct omap_drm_private *priv = dev->dev_private; | |
346 | struct omap_fbdev *fbdev = NULL; | |
347 | struct drm_fb_helper *helper; | |
348 | int ret = 0; | |
349 | ||
350 | fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); | |
351 | if (!fbdev) { | |
352 | dev_err(dev->dev, "could not allocate fbdev\n"); | |
353 | goto fail; | |
354 | } | |
355 | ||
9b55b95a RC |
356 | INIT_WORK(&fbdev->work, pan_worker); |
357 | ||
cd5351f4 RC |
358 | helper = &fbdev->base; |
359 | ||
360 | helper->funcs = &omap_fb_helper_funcs; | |
361 | ||
362 | ret = drm_fb_helper_init(dev, helper, | |
363 | priv->num_crtcs, priv->num_connectors); | |
364 | if (ret) { | |
365 | dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret); | |
366 | goto fail; | |
367 | } | |
368 | ||
369 | drm_fb_helper_single_add_all_connectors(helper); | |
370 | drm_fb_helper_initial_config(helper, 32); | |
371 | ||
372 | priv->fbdev = helper; | |
373 | ||
374 | return helper; | |
375 | ||
376 | fail: | |
377 | kfree(fbdev); | |
378 | return NULL; | |
379 | } | |
380 | ||
381 | void omap_fbdev_free(struct drm_device *dev) | |
382 | { | |
383 | struct omap_drm_private *priv = dev->dev_private; | |
384 | struct drm_fb_helper *helper = priv->fbdev; | |
385 | struct omap_fbdev *fbdev; | |
386 | struct fb_info *fbi; | |
387 | ||
388 | DBG(); | |
389 | ||
390 | fbi = helper->fbdev; | |
391 | ||
afb6a6a0 AG |
392 | /* only cleanup framebuffer if it is present */ |
393 | if (fbi) { | |
394 | unregister_framebuffer(fbi); | |
395 | framebuffer_release(fbi); | |
396 | } | |
cd5351f4 RC |
397 | |
398 | drm_fb_helper_fini(helper); | |
399 | ||
400 | fbdev = to_omap_fbdev(priv->fbdev); | |
401 | ||
a6a91827 RC |
402 | /* this will free the backing object */ |
403 | if (fbdev->fb) | |
404 | fbdev->fb->funcs->destroy(fbdev->fb); | |
405 | ||
a9e8d70c JL |
406 | kfree(fbdev); |
407 | ||
cd5351f4 RC |
408 | priv->fbdev = NULL; |
409 | } |