]>
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 | ||
a6a91827 | 45 | static struct drm_fb_helper *get_fb(struct fb_info *fbi); |
cd5351f4 | 46 | |
9b55b95a RC |
47 | static void pan_worker(struct work_struct *work) |
48 | { | |
49 | struct omap_fbdev *fbdev = container_of(work, struct omap_fbdev, work); | |
50 | struct fb_info *fbi = fbdev->base.fbdev; | |
51 | int npages; | |
52 | ||
53 | /* DMM roll shifts in 4K pages: */ | |
54 | npages = fbi->fix.line_length >> PAGE_SHIFT; | |
55 | omap_gem_roll(fbdev->bo, fbi->var.yoffset * npages); | |
56 | } | |
57 | ||
a6a91827 RC |
58 | static int omap_fbdev_pan_display(struct fb_var_screeninfo *var, |
59 | struct fb_info *fbi) | |
60 | { | |
61 | struct drm_fb_helper *helper = get_fb(fbi); | |
62 | struct omap_fbdev *fbdev = to_omap_fbdev(helper); | |
a6a91827 RC |
63 | |
64 | if (!helper) | |
65 | goto fallback; | |
66 | ||
510d4d32 | 67 | if (!fbdev->ywrap_enabled) |
a6a91827 RC |
68 | goto fallback; |
69 | ||
9b55b95a RC |
70 | if (drm_can_sleep()) { |
71 | pan_worker(&fbdev->work); | |
72 | } else { | |
73 | struct omap_drm_private *priv = helper->dev->dev_private; | |
74 | queue_work(priv->wq, &fbdev->work); | |
75 | } | |
a6a91827 RC |
76 | |
77 | return 0; | |
78 | ||
79 | fallback: | |
80 | return drm_fb_helper_pan_display(var, fbi); | |
81 | } | |
82 | ||
cd5351f4 RC |
83 | static struct fb_ops omap_fb_ops = { |
84 | .owner = THIS_MODULE, | |
85 | ||
86 | /* Note: to properly handle manual update displays, we wrap the | |
87 | * basic fbdev ops which write to the framebuffer | |
88 | */ | |
89 | .fb_read = fb_sys_read, | |
5a35876e LP |
90 | .fb_write = fb_sys_write, |
91 | .fb_fillrect = sys_fillrect, | |
92 | .fb_copyarea = sys_copyarea, | |
93 | .fb_imageblit = sys_imageblit, | |
cd5351f4 RC |
94 | |
95 | .fb_check_var = drm_fb_helper_check_var, | |
96 | .fb_set_par = drm_fb_helper_set_par, | |
a6a91827 | 97 | .fb_pan_display = omap_fbdev_pan_display, |
cd5351f4 RC |
98 | .fb_blank = drm_fb_helper_blank, |
99 | .fb_setcmap = drm_fb_helper_setcmap, | |
cd5351f4 RC |
100 | }; |
101 | ||
102 | static int omap_fbdev_create(struct drm_fb_helper *helper, | |
103 | struct drm_fb_helper_surface_size *sizes) | |
104 | { | |
105 | struct omap_fbdev *fbdev = to_omap_fbdev(helper); | |
106 | struct drm_device *dev = helper->dev; | |
a6a91827 | 107 | struct omap_drm_private *priv = dev->dev_private; |
cd5351f4 | 108 | struct drm_framebuffer *fb = NULL; |
a6a91827 | 109 | union omap_gem_size gsize; |
cd5351f4 | 110 | struct fb_info *fbi = NULL; |
ae43d7ca | 111 | struct drm_mode_fb_cmd2 mode_cmd = {0}; |
cd5351f4 | 112 | dma_addr_t paddr; |
cd5351f4 RC |
113 | int ret; |
114 | ||
115 | /* only doing ARGB32 since this is what is needed to alpha-blend | |
116 | * with video overlays: | |
117 | */ | |
118 | sizes->surface_bpp = 32; | |
119 | sizes->surface_depth = 32; | |
120 | ||
a6a91827 RC |
121 | DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width, |
122 | sizes->surface_height, sizes->surface_bpp, | |
123 | sizes->fb_width, sizes->fb_height); | |
cd5351f4 | 124 | |
ae43d7ca RC |
125 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, |
126 | sizes->surface_depth); | |
127 | ||
cd5351f4 RC |
128 | mode_cmd.width = sizes->surface_width; |
129 | mode_cmd.height = sizes->surface_height; | |
130 | ||
ae43d7ca RC |
131 | mode_cmd.pitches[0] = align_pitch( |
132 | mode_cmd.width * ((sizes->surface_bpp + 7) / 8), | |
133 | mode_cmd.width, sizes->surface_bpp); | |
a6a91827 | 134 | |
510d4d32 RC |
135 | fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled; |
136 | if (fbdev->ywrap_enabled) { | |
a6a91827 | 137 | /* need to align pitch to page size if using DMM scrolling */ |
ae43d7ca | 138 | mode_cmd.pitches[0] = ALIGN(mode_cmd.pitches[0], PAGE_SIZE); |
a6a91827 RC |
139 | } |
140 | ||
141 | /* allocate backing bo */ | |
142 | gsize = (union omap_gem_size){ | |
ae43d7ca | 143 | .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height), |
a6a91827 RC |
144 | }; |
145 | DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index); | |
146 | fbdev->bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC); | |
147 | if (!fbdev->bo) { | |
148 | dev_err(dev->dev, "failed to allocate buffer object\n"); | |
ae43d7ca | 149 | ret = -ENOMEM; |
a6a91827 RC |
150 | goto fail; |
151 | } | |
152 | ||
ae43d7ca RC |
153 | fb = omap_framebuffer_init(dev, &mode_cmd, &fbdev->bo); |
154 | if (IS_ERR(fb)) { | |
cd5351f4 | 155 | dev_err(dev->dev, "failed to allocate fb\n"); |
ae43d7ca RC |
156 | /* note: if fb creation failed, we can't rely on fb destroy |
157 | * to unref the bo: | |
158 | */ | |
159 | drm_gem_object_unreference(fbdev->bo); | |
160 | ret = PTR_ERR(fb); | |
161 | goto fail; | |
162 | } | |
163 | ||
164 | /* note: this keeps the bo pinned.. which is perhaps not ideal, | |
165 | * but is needed as long as we use fb_mmap() to mmap to userspace | |
166 | * (since this happens using fix.smem_start). Possibly we could | |
167 | * implement our own mmap using GEM mmap support to avoid this | |
168 | * (non-tiled buffer doesn't need to be pinned for fbcon to write | |
169 | * to it). Then we just need to be sure that we are able to re- | |
170 | * pin it in case of an opps. | |
171 | */ | |
172 | ret = omap_gem_get_paddr(fbdev->bo, &paddr, true); | |
173 | if (ret) { | |
afb6a6a0 AG |
174 | dev_err(dev->dev, |
175 | "could not map (paddr)! Skipping framebuffer alloc\n"); | |
cd5351f4 RC |
176 | ret = -ENOMEM; |
177 | goto fail; | |
178 | } | |
179 | ||
180 | mutex_lock(&dev->struct_mutex); | |
181 | ||
182 | fbi = framebuffer_alloc(0, dev->dev); | |
183 | if (!fbi) { | |
184 | dev_err(dev->dev, "failed to allocate fb info\n"); | |
185 | ret = -ENOMEM; | |
186 | goto fail_unlock; | |
187 | } | |
188 | ||
189 | DBG("fbi=%p, dev=%p", fbi, dev); | |
190 | ||
191 | fbdev->fb = fb; | |
192 | helper->fb = fb; | |
193 | helper->fbdev = fbi; | |
194 | ||
195 | fbi->par = helper; | |
196 | fbi->flags = FBINFO_DEFAULT; | |
197 | fbi->fbops = &omap_fb_ops; | |
198 | ||
199 | strcpy(fbi->fix.id, MODULE_NAME); | |
200 | ||
201 | ret = fb_alloc_cmap(&fbi->cmap, 256, 0); | |
202 | if (ret) { | |
203 | ret = -ENOMEM; | |
204 | goto fail_unlock; | |
205 | } | |
206 | ||
ae43d7ca | 207 | drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); |
a6a91827 | 208 | drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); |
cd5351f4 | 209 | |
cd5351f4 RC |
210 | dev->mode_config.fb_base = paddr; |
211 | ||
ae43d7ca RC |
212 | fbi->screen_base = omap_gem_vaddr(fbdev->bo); |
213 | fbi->screen_size = fbdev->bo->size; | |
cd5351f4 | 214 | fbi->fix.smem_start = paddr; |
ae43d7ca | 215 | fbi->fix.smem_len = fbdev->bo->size; |
cd5351f4 | 216 | |
a6a91827 RC |
217 | /* if we have DMM, then we can use it for scrolling by just |
218 | * shuffling pages around in DMM rather than doing sw blit. | |
219 | */ | |
510d4d32 | 220 | if (fbdev->ywrap_enabled) { |
a6a91827 RC |
221 | DRM_INFO("Enabling DMM ywrap scrolling\n"); |
222 | fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST; | |
223 | fbi->fix.ywrapstep = 1; | |
224 | } | |
225 | ||
510d4d32 | 226 | |
cd5351f4 RC |
227 | DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); |
228 | DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height); | |
229 | ||
230 | mutex_unlock(&dev->struct_mutex); | |
231 | ||
232 | return 0; | |
233 | ||
234 | fail_unlock: | |
235 | mutex_unlock(&dev->struct_mutex); | |
236 | fail: | |
237 | ||
238 | if (ret) { | |
239 | if (fbi) | |
240 | framebuffer_release(fbi); | |
36206361 DV |
241 | if (fb) { |
242 | drm_framebuffer_unregister_private(fb); | |
f7eff60e | 243 | drm_framebuffer_remove(fb); |
36206361 | 244 | } |
cd5351f4 RC |
245 | } |
246 | ||
247 | return ret; | |
248 | } | |
249 | ||
3a493879 | 250 | static const struct drm_fb_helper_funcs omap_fb_helper_funcs = { |
cd5428a5 | 251 | .fb_probe = omap_fbdev_create, |
cd5351f4 RC |
252 | }; |
253 | ||
254 | static struct drm_fb_helper *get_fb(struct fb_info *fbi) | |
255 | { | |
256 | if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) { | |
257 | /* these are not the fb's you're looking for */ | |
258 | return NULL; | |
259 | } | |
260 | return fbi->par; | |
261 | } | |
262 | ||
cd5351f4 RC |
263 | /* initialize fbdev helper */ |
264 | struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev) | |
265 | { | |
266 | struct omap_drm_private *priv = dev->dev_private; | |
267 | struct omap_fbdev *fbdev = NULL; | |
268 | struct drm_fb_helper *helper; | |
269 | int ret = 0; | |
270 | ||
271 | fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); | |
78110bb8 | 272 | if (!fbdev) |
cd5351f4 | 273 | goto fail; |
cd5351f4 | 274 | |
9b55b95a RC |
275 | INIT_WORK(&fbdev->work, pan_worker); |
276 | ||
cd5351f4 RC |
277 | helper = &fbdev->base; |
278 | ||
10a23102 | 279 | drm_fb_helper_prepare(dev, helper, &omap_fb_helper_funcs); |
cd5351f4 RC |
280 | |
281 | ret = drm_fb_helper_init(dev, helper, | |
282 | priv->num_crtcs, priv->num_connectors); | |
283 | if (ret) { | |
284 | dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret); | |
285 | goto fail; | |
286 | } | |
287 | ||
01934c2a TR |
288 | ret = drm_fb_helper_single_add_all_connectors(helper); |
289 | if (ret) | |
290 | goto fini; | |
76a39dbf DV |
291 | |
292 | /* disable all the possible outputs/crtcs before entering KMS mode */ | |
293 | drm_helper_disable_unused_functions(dev); | |
294 | ||
01934c2a TR |
295 | ret = drm_fb_helper_initial_config(helper, 32); |
296 | if (ret) | |
297 | goto fini; | |
cd5351f4 RC |
298 | |
299 | priv->fbdev = helper; | |
300 | ||
301 | return helper; | |
302 | ||
01934c2a TR |
303 | fini: |
304 | drm_fb_helper_fini(helper); | |
cd5351f4 RC |
305 | fail: |
306 | kfree(fbdev); | |
307 | return NULL; | |
308 | } | |
309 | ||
310 | void omap_fbdev_free(struct drm_device *dev) | |
311 | { | |
312 | struct omap_drm_private *priv = dev->dev_private; | |
313 | struct drm_fb_helper *helper = priv->fbdev; | |
314 | struct omap_fbdev *fbdev; | |
315 | struct fb_info *fbi; | |
316 | ||
317 | DBG(); | |
318 | ||
319 | fbi = helper->fbdev; | |
320 | ||
afb6a6a0 AG |
321 | /* only cleanup framebuffer if it is present */ |
322 | if (fbi) { | |
323 | unregister_framebuffer(fbi); | |
324 | framebuffer_release(fbi); | |
325 | } | |
cd5351f4 RC |
326 | |
327 | drm_fb_helper_fini(helper); | |
328 | ||
329 | fbdev = to_omap_fbdev(priv->fbdev); | |
330 | ||
5e19c06d TV |
331 | /* release the ref taken in omap_fbdev_create() */ |
332 | omap_gem_put_paddr(fbdev->bo); | |
333 | ||
a6a91827 | 334 | /* this will free the backing object */ |
36206361 DV |
335 | if (fbdev->fb) { |
336 | drm_framebuffer_unregister_private(fbdev->fb); | |
f7eff60e | 337 | drm_framebuffer_remove(fbdev->fb); |
36206361 | 338 | } |
a6a91827 | 339 | |
a9e8d70c JL |
340 | kfree(fbdev); |
341 | ||
cd5351f4 RC |
342 | priv->fbdev = NULL; |
343 | } |