diff options
Diffstat (limited to 'drivers/gpu/drm/omapdrm/omap_fbdev.c')
-rw-r--r-- | drivers/gpu/drm/omapdrm/omap_fbdev.c | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/drivers/gpu/drm/omapdrm/omap_fbdev.c b/drivers/gpu/drm/omapdrm/omap_fbdev.c new file mode 100644 index 000000000000..b11ce609fcc2 --- /dev/null +++ b/drivers/gpu/drm/omapdrm/omap_fbdev.c | |||
@@ -0,0 +1,397 @@ | |||
1 | /* | ||
2 | * drivers/gpu/drm/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 | |||
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 | |||
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; | ||
38 | struct drm_gem_object *bo; | ||
39 | bool ywrap_enabled; | ||
40 | |||
41 | /* for deferred dmm roll when getting called in atomic ctx */ | ||
42 | struct work_struct work; | ||
43 | }; | ||
44 | |||
45 | static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h); | ||
46 | static struct drm_fb_helper *get_fb(struct fb_info *fbi); | ||
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 | |||
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 | |||
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); | ||
97 | |||
98 | if (!helper) | ||
99 | goto fallback; | ||
100 | |||
101 | if (!fbdev->ywrap_enabled) | ||
102 | goto fallback; | ||
103 | |||
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 | } | ||
110 | |||
111 | return 0; | ||
112 | |||
113 | fallback: | ||
114 | return drm_fb_helper_pan_display(var, fbi); | ||
115 | } | ||
116 | |||
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, | ||
131 | .fb_pan_display = omap_fbdev_pan_display, | ||
132 | .fb_blank = drm_fb_helper_blank, | ||
133 | .fb_setcmap = drm_fb_helper_setcmap, | ||
134 | }; | ||
135 | |||
136 | static int omap_fbdev_create(struct drm_fb_helper *helper, | ||
137 | struct drm_fb_helper_surface_size *sizes) | ||
138 | { | ||
139 | struct omap_fbdev *fbdev = to_omap_fbdev(helper); | ||
140 | struct drm_device *dev = helper->dev; | ||
141 | struct omap_drm_private *priv = dev->dev_private; | ||
142 | struct drm_framebuffer *fb = NULL; | ||
143 | union omap_gem_size gsize; | ||
144 | struct fb_info *fbi = NULL; | ||
145 | struct drm_mode_fb_cmd2 mode_cmd = {0}; | ||
146 | dma_addr_t paddr; | ||
147 | int ret; | ||
148 | |||
149 | /* only doing ARGB32 since this is what is needed to alpha-blend | ||
150 | * with video overlays: | ||
151 | */ | ||
152 | sizes->surface_bpp = 32; | ||
153 | sizes->surface_depth = 32; | ||
154 | |||
155 | DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width, | ||
156 | sizes->surface_height, sizes->surface_bpp, | ||
157 | sizes->fb_width, sizes->fb_height); | ||
158 | |||
159 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, | ||
160 | sizes->surface_depth); | ||
161 | |||
162 | mode_cmd.width = sizes->surface_width; | ||
163 | mode_cmd.height = sizes->surface_height; | ||
164 | |||
165 | mode_cmd.pitches[0] = align_pitch( | ||
166 | mode_cmd.width * ((sizes->surface_bpp + 7) / 8), | ||
167 | mode_cmd.width, sizes->surface_bpp); | ||
168 | |||
169 | fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled; | ||
170 | if (fbdev->ywrap_enabled) { | ||
171 | /* need to align pitch to page size if using DMM scrolling */ | ||
172 | mode_cmd.pitches[0] = ALIGN(mode_cmd.pitches[0], PAGE_SIZE); | ||
173 | } | ||
174 | |||
175 | /* allocate backing bo */ | ||
176 | gsize = (union omap_gem_size){ | ||
177 | .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height), | ||
178 | }; | ||
179 | DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index); | ||
180 | fbdev->bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC); | ||
181 | if (!fbdev->bo) { | ||
182 | dev_err(dev->dev, "failed to allocate buffer object\n"); | ||
183 | ret = -ENOMEM; | ||
184 | goto fail; | ||
185 | } | ||
186 | |||
187 | fb = omap_framebuffer_init(dev, &mode_cmd, &fbdev->bo); | ||
188 | if (IS_ERR(fb)) { | ||
189 | dev_err(dev->dev, "failed to allocate fb\n"); | ||
190 | /* note: if fb creation failed, we can't rely on fb destroy | ||
191 | * to unref the bo: | ||
192 | */ | ||
193 | drm_gem_object_unreference(fbdev->bo); | ||
194 | ret = PTR_ERR(fb); | ||
195 | goto fail; | ||
196 | } | ||
197 | |||
198 | /* note: this keeps the bo pinned.. which is perhaps not ideal, | ||
199 | * but is needed as long as we use fb_mmap() to mmap to userspace | ||
200 | * (since this happens using fix.smem_start). Possibly we could | ||
201 | * implement our own mmap using GEM mmap support to avoid this | ||
202 | * (non-tiled buffer doesn't need to be pinned for fbcon to write | ||
203 | * to it). Then we just need to be sure that we are able to re- | ||
204 | * pin it in case of an opps. | ||
205 | */ | ||
206 | ret = omap_gem_get_paddr(fbdev->bo, &paddr, true); | ||
207 | if (ret) { | ||
208 | dev_err(dev->dev, | ||
209 | "could not map (paddr)! Skipping framebuffer alloc\n"); | ||
210 | ret = -ENOMEM; | ||
211 | goto fail; | ||
212 | } | ||
213 | |||
214 | mutex_lock(&dev->struct_mutex); | ||
215 | |||
216 | fbi = framebuffer_alloc(0, dev->dev); | ||
217 | if (!fbi) { | ||
218 | dev_err(dev->dev, "failed to allocate fb info\n"); | ||
219 | ret = -ENOMEM; | ||
220 | goto fail_unlock; | ||
221 | } | ||
222 | |||
223 | DBG("fbi=%p, dev=%p", fbi, dev); | ||
224 | |||
225 | fbdev->fb = fb; | ||
226 | helper->fb = fb; | ||
227 | helper->fbdev = fbi; | ||
228 | |||
229 | fbi->par = helper; | ||
230 | fbi->flags = FBINFO_DEFAULT; | ||
231 | fbi->fbops = &omap_fb_ops; | ||
232 | |||
233 | strcpy(fbi->fix.id, MODULE_NAME); | ||
234 | |||
235 | ret = fb_alloc_cmap(&fbi->cmap, 256, 0); | ||
236 | if (ret) { | ||
237 | ret = -ENOMEM; | ||
238 | goto fail_unlock; | ||
239 | } | ||
240 | |||
241 | drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); | ||
242 | drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); | ||
243 | |||
244 | dev->mode_config.fb_base = paddr; | ||
245 | |||
246 | fbi->screen_base = omap_gem_vaddr(fbdev->bo); | ||
247 | fbi->screen_size = fbdev->bo->size; | ||
248 | fbi->fix.smem_start = paddr; | ||
249 | fbi->fix.smem_len = fbdev->bo->size; | ||
250 | |||
251 | /* if we have DMM, then we can use it for scrolling by just | ||
252 | * shuffling pages around in DMM rather than doing sw blit. | ||
253 | */ | ||
254 | if (fbdev->ywrap_enabled) { | ||
255 | DRM_INFO("Enabling DMM ywrap scrolling\n"); | ||
256 | fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST; | ||
257 | fbi->fix.ywrapstep = 1; | ||
258 | } | ||
259 | |||
260 | |||
261 | DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); | ||
262 | DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height); | ||
263 | |||
264 | mutex_unlock(&dev->struct_mutex); | ||
265 | |||
266 | return 0; | ||
267 | |||
268 | fail_unlock: | ||
269 | mutex_unlock(&dev->struct_mutex); | ||
270 | fail: | ||
271 | |||
272 | if (ret) { | ||
273 | if (fbi) | ||
274 | framebuffer_release(fbi); | ||
275 | if (fb) { | ||
276 | drm_framebuffer_unregister_private(fb); | ||
277 | drm_framebuffer_remove(fb); | ||
278 | } | ||
279 | } | ||
280 | |||
281 | return ret; | ||
282 | } | ||
283 | |||
284 | static void omap_crtc_fb_gamma_set(struct drm_crtc *crtc, | ||
285 | u16 red, u16 green, u16 blue, int regno) | ||
286 | { | ||
287 | DBG("fbdev: set gamma"); | ||
288 | } | ||
289 | |||
290 | static void omap_crtc_fb_gamma_get(struct drm_crtc *crtc, | ||
291 | u16 *red, u16 *green, u16 *blue, int regno) | ||
292 | { | ||
293 | DBG("fbdev: get gamma"); | ||
294 | } | ||
295 | |||
296 | static struct drm_fb_helper_funcs omap_fb_helper_funcs = { | ||
297 | .gamma_set = omap_crtc_fb_gamma_set, | ||
298 | .gamma_get = omap_crtc_fb_gamma_get, | ||
299 | .fb_probe = omap_fbdev_create, | ||
300 | }; | ||
301 | |||
302 | static struct drm_fb_helper *get_fb(struct fb_info *fbi) | ||
303 | { | ||
304 | if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) { | ||
305 | /* these are not the fb's you're looking for */ | ||
306 | return NULL; | ||
307 | } | ||
308 | return fbi->par; | ||
309 | } | ||
310 | |||
311 | /* flush an area of the framebuffer (in case of manual update display that | ||
312 | * is not automatically flushed) | ||
313 | */ | ||
314 | static void omap_fbdev_flush(struct fb_info *fbi, int x, int y, int w, int h) | ||
315 | { | ||
316 | struct drm_fb_helper *helper = get_fb(fbi); | ||
317 | |||
318 | if (!helper) | ||
319 | return; | ||
320 | |||
321 | VERB("flush fbdev: %d,%d %dx%d, fbi=%p", x, y, w, h, fbi); | ||
322 | |||
323 | omap_framebuffer_flush(helper->fb, x, y, w, h); | ||
324 | } | ||
325 | |||
326 | /* initialize fbdev helper */ | ||
327 | struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev) | ||
328 | { | ||
329 | struct omap_drm_private *priv = dev->dev_private; | ||
330 | struct omap_fbdev *fbdev = NULL; | ||
331 | struct drm_fb_helper *helper; | ||
332 | int ret = 0; | ||
333 | |||
334 | fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); | ||
335 | if (!fbdev) | ||
336 | goto fail; | ||
337 | |||
338 | INIT_WORK(&fbdev->work, pan_worker); | ||
339 | |||
340 | helper = &fbdev->base; | ||
341 | |||
342 | helper->funcs = &omap_fb_helper_funcs; | ||
343 | |||
344 | ret = drm_fb_helper_init(dev, helper, | ||
345 | priv->num_crtcs, priv->num_connectors); | ||
346 | if (ret) { | ||
347 | dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret); | ||
348 | goto fail; | ||
349 | } | ||
350 | |||
351 | drm_fb_helper_single_add_all_connectors(helper); | ||
352 | |||
353 | /* disable all the possible outputs/crtcs before entering KMS mode */ | ||
354 | drm_helper_disable_unused_functions(dev); | ||
355 | |||
356 | drm_fb_helper_initial_config(helper, 32); | ||
357 | |||
358 | priv->fbdev = helper; | ||
359 | |||
360 | return helper; | ||
361 | |||
362 | fail: | ||
363 | kfree(fbdev); | ||
364 | return NULL; | ||
365 | } | ||
366 | |||
367 | void omap_fbdev_free(struct drm_device *dev) | ||
368 | { | ||
369 | struct omap_drm_private *priv = dev->dev_private; | ||
370 | struct drm_fb_helper *helper = priv->fbdev; | ||
371 | struct omap_fbdev *fbdev; | ||
372 | struct fb_info *fbi; | ||
373 | |||
374 | DBG(); | ||
375 | |||
376 | fbi = helper->fbdev; | ||
377 | |||
378 | /* only cleanup framebuffer if it is present */ | ||
379 | if (fbi) { | ||
380 | unregister_framebuffer(fbi); | ||
381 | framebuffer_release(fbi); | ||
382 | } | ||
383 | |||
384 | drm_fb_helper_fini(helper); | ||
385 | |||
386 | fbdev = to_omap_fbdev(priv->fbdev); | ||
387 | |||
388 | /* this will free the backing object */ | ||
389 | if (fbdev->fb) { | ||
390 | drm_framebuffer_unregister_private(fbdev->fb); | ||
391 | drm_framebuffer_remove(fbdev->fb); | ||
392 | } | ||
393 | |||
394 | kfree(fbdev); | ||
395 | |||
396 | priv->fbdev = NULL; | ||
397 | } | ||