Commit | Line | Data |
---|---|---|
cd5351f4 | 1 | /* |
8bb0daff | 2 | * drivers/gpu/drm/omapdrm/omap_fbdev.c |
cd5351f4 RC |
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, | |
cd5351f4 RC |
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; | |
a6a91827 | 141 | struct omap_drm_private *priv = dev->dev_private; |
cd5351f4 | 142 | struct drm_framebuffer *fb = NULL; |
a6a91827 | 143 | union omap_gem_size gsize; |
cd5351f4 | 144 | struct fb_info *fbi = NULL; |
ae43d7ca | 145 | struct drm_mode_fb_cmd2 mode_cmd = {0}; |
cd5351f4 | 146 | dma_addr_t paddr; |
cd5351f4 RC |
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 | ||
a6a91827 RC |
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); | |
cd5351f4 | 158 | |
ae43d7ca RC |
159 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, |
160 | sizes->surface_depth); | |
161 | ||
cd5351f4 RC |
162 | mode_cmd.width = sizes->surface_width; |
163 | mode_cmd.height = sizes->surface_height; | |
164 | ||
ae43d7ca RC |
165 | mode_cmd.pitches[0] = align_pitch( |
166 | mode_cmd.width * ((sizes->surface_bpp + 7) / 8), | |
167 | mode_cmd.width, sizes->surface_bpp); | |
a6a91827 | 168 | |
510d4d32 RC |
169 | fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled; |
170 | if (fbdev->ywrap_enabled) { | |
a6a91827 | 171 | /* need to align pitch to page size if using DMM scrolling */ |
ae43d7ca | 172 | mode_cmd.pitches[0] = ALIGN(mode_cmd.pitches[0], PAGE_SIZE); |
a6a91827 RC |
173 | } |
174 | ||
175 | /* allocate backing bo */ | |
176 | gsize = (union omap_gem_size){ | |
ae43d7ca | 177 | .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height), |
a6a91827 RC |
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"); | |
ae43d7ca | 183 | ret = -ENOMEM; |
a6a91827 RC |
184 | goto fail; |
185 | } | |
186 | ||
ae43d7ca RC |
187 | fb = omap_framebuffer_init(dev, &mode_cmd, &fbdev->bo); |
188 | if (IS_ERR(fb)) { | |
cd5351f4 | 189 | dev_err(dev->dev, "failed to allocate fb\n"); |
ae43d7ca RC |
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) { | |
afb6a6a0 AG |
208 | dev_err(dev->dev, |
209 | "could not map (paddr)! Skipping framebuffer alloc\n"); | |
cd5351f4 RC |
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 | ||
ae43d7ca | 241 | drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); |
a6a91827 | 242 | drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); |
cd5351f4 | 243 | |
cd5351f4 RC |
244 | dev->mode_config.fb_base = paddr; |
245 | ||
ae43d7ca RC |
246 | fbi->screen_base = omap_gem_vaddr(fbdev->bo); |
247 | fbi->screen_size = fbdev->bo->size; | |
cd5351f4 | 248 | fbi->fix.smem_start = paddr; |
ae43d7ca | 249 | fbi->fix.smem_len = fbdev->bo->size; |
cd5351f4 | 250 | |
a6a91827 RC |
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 | */ | |
510d4d32 | 254 | if (fbdev->ywrap_enabled) { |
a6a91827 RC |
255 | DRM_INFO("Enabling DMM ywrap scrolling\n"); |
256 | fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST; | |
257 | fbi->fix.ywrapstep = 1; | |
258 | } | |
259 | ||
510d4d32 | 260 | |
cd5351f4 RC |
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); | |
36206361 DV |
275 | if (fb) { |
276 | drm_framebuffer_unregister_private(fb); | |
f7eff60e | 277 | drm_framebuffer_remove(fb); |
36206361 | 278 | } |
cd5351f4 RC |
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 | ||
cd5351f4 RC |
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, | |
cd5428a5 | 299 | .fb_probe = omap_fbdev_create, |
cd5351f4 RC |
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); | |
78110bb8 | 335 | if (!fbdev) |
cd5351f4 | 336 | goto fail; |
cd5351f4 | 337 | |
9b55b95a RC |
338 | INIT_WORK(&fbdev->work, pan_worker); |
339 | ||
cd5351f4 RC |
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); | |
76a39dbf DV |
352 | |
353 | /* disable all the possible outputs/crtcs before entering KMS mode */ | |
354 | drm_helper_disable_unused_functions(dev); | |
355 | ||
cd5351f4 RC |
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 | ||
afb6a6a0 AG |
378 | /* only cleanup framebuffer if it is present */ |
379 | if (fbi) { | |
380 | unregister_framebuffer(fbi); | |
381 | framebuffer_release(fbi); | |
382 | } | |
cd5351f4 RC |
383 | |
384 | drm_fb_helper_fini(helper); | |
385 | ||
386 | fbdev = to_omap_fbdev(priv->fbdev); | |
387 | ||
a6a91827 | 388 | /* this will free the backing object */ |
36206361 DV |
389 | if (fbdev->fb) { |
390 | drm_framebuffer_unregister_private(fbdev->fb); | |
f7eff60e | 391 | drm_framebuffer_remove(fbdev->fb); |
36206361 | 392 | } |
a6a91827 | 393 | |
a9e8d70c JL |
394 | kfree(fbdev); |
395 | ||
cd5351f4 RC |
396 | priv->fbdev = NULL; |
397 | } |