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 | ||
2d278f54 LP |
20 | #include <drm/drm_crtc.h> |
21 | #include <drm/drm_fb_helper.h> | |
cd5351f4 | 22 | |
2d278f54 | 23 | #include "omap_drv.h" |
cd5351f4 | 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 | */ | |
231e6faf AT |
89 | .fb_read = drm_fb_helper_sys_read, |
90 | .fb_write = drm_fb_helper_sys_write, | |
91 | .fb_fillrect = drm_fb_helper_sys_fillrect, | |
92 | .fb_copyarea = drm_fb_helper_sys_copyarea, | |
93 | .fb_imageblit = drm_fb_helper_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 | ||
cd5351f4 | 115 | sizes->surface_bpp = 32; |
1d977b06 | 116 | sizes->surface_depth = 24; |
cd5351f4 | 117 | |
a6a91827 RC |
118 | DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width, |
119 | sizes->surface_height, sizes->surface_bpp, | |
120 | sizes->fb_width, sizes->fb_height); | |
cd5351f4 | 121 | |
ae43d7ca RC |
122 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, |
123 | sizes->surface_depth); | |
124 | ||
cd5351f4 RC |
125 | mode_cmd.width = sizes->surface_width; |
126 | mode_cmd.height = sizes->surface_height; | |
127 | ||
ce481eda TV |
128 | mode_cmd.pitches[0] = |
129 | DIV_ROUND_UP(mode_cmd.width * sizes->surface_bpp, 8); | |
a6a91827 | 130 | |
510d4d32 RC |
131 | fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled; |
132 | if (fbdev->ywrap_enabled) { | |
a6a91827 | 133 | /* need to align pitch to page size if using DMM scrolling */ |
743c1671 | 134 | mode_cmd.pitches[0] = PAGE_ALIGN(mode_cmd.pitches[0]); |
a6a91827 RC |
135 | } |
136 | ||
137 | /* allocate backing bo */ | |
138 | gsize = (union omap_gem_size){ | |
ae43d7ca | 139 | .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height), |
a6a91827 RC |
140 | }; |
141 | DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index); | |
142 | fbdev->bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC); | |
143 | if (!fbdev->bo) { | |
144 | dev_err(dev->dev, "failed to allocate buffer object\n"); | |
ae43d7ca | 145 | ret = -ENOMEM; |
a6a91827 RC |
146 | goto fail; |
147 | } | |
148 | ||
ae43d7ca RC |
149 | fb = omap_framebuffer_init(dev, &mode_cmd, &fbdev->bo); |
150 | if (IS_ERR(fb)) { | |
cd5351f4 | 151 | dev_err(dev->dev, "failed to allocate fb\n"); |
ae43d7ca RC |
152 | /* note: if fb creation failed, we can't rely on fb destroy |
153 | * to unref the bo: | |
154 | */ | |
5bb682c4 | 155 | drm_gem_object_unreference_unlocked(fbdev->bo); |
ae43d7ca RC |
156 | ret = PTR_ERR(fb); |
157 | goto fail; | |
158 | } | |
159 | ||
160 | /* note: this keeps the bo pinned.. which is perhaps not ideal, | |
161 | * but is needed as long as we use fb_mmap() to mmap to userspace | |
162 | * (since this happens using fix.smem_start). Possibly we could | |
163 | * implement our own mmap using GEM mmap support to avoid this | |
164 | * (non-tiled buffer doesn't need to be pinned for fbcon to write | |
165 | * to it). Then we just need to be sure that we are able to re- | |
166 | * pin it in case of an opps. | |
167 | */ | |
168 | ret = omap_gem_get_paddr(fbdev->bo, &paddr, true); | |
169 | if (ret) { | |
afb6a6a0 AG |
170 | dev_err(dev->dev, |
171 | "could not map (paddr)! Skipping framebuffer alloc\n"); | |
cd5351f4 RC |
172 | ret = -ENOMEM; |
173 | goto fail; | |
174 | } | |
175 | ||
176 | mutex_lock(&dev->struct_mutex); | |
177 | ||
231e6faf AT |
178 | fbi = drm_fb_helper_alloc_fbi(helper); |
179 | if (IS_ERR(fbi)) { | |
cd5351f4 | 180 | dev_err(dev->dev, "failed to allocate fb info\n"); |
231e6faf | 181 | ret = PTR_ERR(fbi); |
cd5351f4 RC |
182 | goto fail_unlock; |
183 | } | |
184 | ||
185 | DBG("fbi=%p, dev=%p", fbi, dev); | |
186 | ||
187 | fbdev->fb = fb; | |
188 | helper->fb = fb; | |
cd5351f4 RC |
189 | |
190 | fbi->par = helper; | |
191 | fbi->flags = FBINFO_DEFAULT; | |
192 | fbi->fbops = &omap_fb_ops; | |
193 | ||
194 | strcpy(fbi->fix.id, MODULE_NAME); | |
195 | ||
ae43d7ca | 196 | drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); |
a6a91827 | 197 | drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); |
cd5351f4 | 198 | |
cd5351f4 RC |
199 | dev->mode_config.fb_base = paddr; |
200 | ||
ae43d7ca RC |
201 | fbi->screen_base = omap_gem_vaddr(fbdev->bo); |
202 | fbi->screen_size = fbdev->bo->size; | |
cd5351f4 | 203 | fbi->fix.smem_start = paddr; |
ae43d7ca | 204 | fbi->fix.smem_len = fbdev->bo->size; |
cd5351f4 | 205 | |
a6a91827 RC |
206 | /* if we have DMM, then we can use it for scrolling by just |
207 | * shuffling pages around in DMM rather than doing sw blit. | |
208 | */ | |
510d4d32 | 209 | if (fbdev->ywrap_enabled) { |
a6a91827 RC |
210 | DRM_INFO("Enabling DMM ywrap scrolling\n"); |
211 | fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST; | |
212 | fbi->fix.ywrapstep = 1; | |
213 | } | |
214 | ||
510d4d32 | 215 | |
cd5351f4 RC |
216 | DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); |
217 | DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height); | |
218 | ||
219 | mutex_unlock(&dev->struct_mutex); | |
220 | ||
221 | return 0; | |
222 | ||
223 | fail_unlock: | |
224 | mutex_unlock(&dev->struct_mutex); | |
225 | fail: | |
226 | ||
227 | if (ret) { | |
231e6faf AT |
228 | |
229 | drm_fb_helper_release_fbi(helper); | |
230 | ||
36206361 DV |
231 | if (fb) { |
232 | drm_framebuffer_unregister_private(fb); | |
f7eff60e | 233 | drm_framebuffer_remove(fb); |
36206361 | 234 | } |
cd5351f4 RC |
235 | } |
236 | ||
237 | return ret; | |
238 | } | |
239 | ||
3a493879 | 240 | static const struct drm_fb_helper_funcs omap_fb_helper_funcs = { |
cd5428a5 | 241 | .fb_probe = omap_fbdev_create, |
cd5351f4 RC |
242 | }; |
243 | ||
244 | static struct drm_fb_helper *get_fb(struct fb_info *fbi) | |
245 | { | |
246 | if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) { | |
247 | /* these are not the fb's you're looking for */ | |
248 | return NULL; | |
249 | } | |
250 | return fbi->par; | |
251 | } | |
252 | ||
cd5351f4 RC |
253 | /* initialize fbdev helper */ |
254 | struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev) | |
255 | { | |
256 | struct omap_drm_private *priv = dev->dev_private; | |
257 | struct omap_fbdev *fbdev = NULL; | |
258 | struct drm_fb_helper *helper; | |
259 | int ret = 0; | |
260 | ||
261 | fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); | |
78110bb8 | 262 | if (!fbdev) |
cd5351f4 | 263 | goto fail; |
cd5351f4 | 264 | |
9b55b95a RC |
265 | INIT_WORK(&fbdev->work, pan_worker); |
266 | ||
cd5351f4 RC |
267 | helper = &fbdev->base; |
268 | ||
10a23102 | 269 | drm_fb_helper_prepare(dev, helper, &omap_fb_helper_funcs); |
cd5351f4 RC |
270 | |
271 | ret = drm_fb_helper_init(dev, helper, | |
272 | priv->num_crtcs, priv->num_connectors); | |
273 | if (ret) { | |
274 | dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret); | |
275 | goto fail; | |
276 | } | |
277 | ||
01934c2a TR |
278 | ret = drm_fb_helper_single_add_all_connectors(helper); |
279 | if (ret) | |
280 | goto fini; | |
76a39dbf | 281 | |
01934c2a TR |
282 | ret = drm_fb_helper_initial_config(helper, 32); |
283 | if (ret) | |
284 | goto fini; | |
cd5351f4 RC |
285 | |
286 | priv->fbdev = helper; | |
287 | ||
288 | return helper; | |
289 | ||
01934c2a TR |
290 | fini: |
291 | drm_fb_helper_fini(helper); | |
cd5351f4 RC |
292 | fail: |
293 | kfree(fbdev); | |
e1c1174f LP |
294 | |
295 | dev_warn(dev->dev, "omap_fbdev_init failed\n"); | |
296 | /* well, limp along without an fbdev.. maybe X11 will work? */ | |
297 | ||
cd5351f4 RC |
298 | return NULL; |
299 | } | |
300 | ||
301 | void omap_fbdev_free(struct drm_device *dev) | |
302 | { | |
303 | struct omap_drm_private *priv = dev->dev_private; | |
304 | struct drm_fb_helper *helper = priv->fbdev; | |
305 | struct omap_fbdev *fbdev; | |
cd5351f4 RC |
306 | |
307 | DBG(); | |
308 | ||
231e6faf AT |
309 | drm_fb_helper_unregister_fbi(helper); |
310 | drm_fb_helper_release_fbi(helper); | |
cd5351f4 RC |
311 | |
312 | drm_fb_helper_fini(helper); | |
313 | ||
314 | fbdev = to_omap_fbdev(priv->fbdev); | |
315 | ||
5e19c06d TV |
316 | /* release the ref taken in omap_fbdev_create() */ |
317 | omap_gem_put_paddr(fbdev->bo); | |
318 | ||
a6a91827 | 319 | /* this will free the backing object */ |
36206361 DV |
320 | if (fbdev->fb) { |
321 | drm_framebuffer_unregister_private(fbdev->fb); | |
f7eff60e | 322 | drm_framebuffer_remove(fbdev->fb); |
36206361 | 323 | } |
a6a91827 | 324 | |
a9e8d70c JL |
325 | kfree(fbdev); |
326 | ||
cd5351f4 RC |
327 | priv->fbdev = NULL; |
328 | } |