]>
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 | ||
ae43d7ca RC |
128 | mode_cmd.pitches[0] = align_pitch( |
129 | mode_cmd.width * ((sizes->surface_bpp + 7) / 8), | |
130 | mode_cmd.width, sizes->surface_bpp); | |
a6a91827 | 131 | |
510d4d32 RC |
132 | fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled; |
133 | if (fbdev->ywrap_enabled) { | |
a6a91827 | 134 | /* need to align pitch to page size if using DMM scrolling */ |
743c1671 | 135 | mode_cmd.pitches[0] = PAGE_ALIGN(mode_cmd.pitches[0]); |
a6a91827 RC |
136 | } |
137 | ||
138 | /* allocate backing bo */ | |
139 | gsize = (union omap_gem_size){ | |
ae43d7ca | 140 | .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height), |
a6a91827 RC |
141 | }; |
142 | DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index); | |
143 | fbdev->bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC); | |
144 | if (!fbdev->bo) { | |
145 | dev_err(dev->dev, "failed to allocate buffer object\n"); | |
ae43d7ca | 146 | ret = -ENOMEM; |
a6a91827 RC |
147 | goto fail; |
148 | } | |
149 | ||
ae43d7ca RC |
150 | fb = omap_framebuffer_init(dev, &mode_cmd, &fbdev->bo); |
151 | if (IS_ERR(fb)) { | |
cd5351f4 | 152 | dev_err(dev->dev, "failed to allocate fb\n"); |
ae43d7ca RC |
153 | /* note: if fb creation failed, we can't rely on fb destroy |
154 | * to unref the bo: | |
155 | */ | |
156 | drm_gem_object_unreference(fbdev->bo); | |
157 | ret = PTR_ERR(fb); | |
158 | goto fail; | |
159 | } | |
160 | ||
161 | /* note: this keeps the bo pinned.. which is perhaps not ideal, | |
162 | * but is needed as long as we use fb_mmap() to mmap to userspace | |
163 | * (since this happens using fix.smem_start). Possibly we could | |
164 | * implement our own mmap using GEM mmap support to avoid this | |
165 | * (non-tiled buffer doesn't need to be pinned for fbcon to write | |
166 | * to it). Then we just need to be sure that we are able to re- | |
167 | * pin it in case of an opps. | |
168 | */ | |
169 | ret = omap_gem_get_paddr(fbdev->bo, &paddr, true); | |
170 | if (ret) { | |
afb6a6a0 AG |
171 | dev_err(dev->dev, |
172 | "could not map (paddr)! Skipping framebuffer alloc\n"); | |
cd5351f4 RC |
173 | ret = -ENOMEM; |
174 | goto fail; | |
175 | } | |
176 | ||
177 | mutex_lock(&dev->struct_mutex); | |
178 | ||
231e6faf AT |
179 | fbi = drm_fb_helper_alloc_fbi(helper); |
180 | if (IS_ERR(fbi)) { | |
cd5351f4 | 181 | dev_err(dev->dev, "failed to allocate fb info\n"); |
231e6faf | 182 | ret = PTR_ERR(fbi); |
cd5351f4 RC |
183 | goto fail_unlock; |
184 | } | |
185 | ||
186 | DBG("fbi=%p, dev=%p", fbi, dev); | |
187 | ||
188 | fbdev->fb = fb; | |
189 | helper->fb = fb; | |
cd5351f4 RC |
190 | |
191 | fbi->par = helper; | |
192 | fbi->flags = FBINFO_DEFAULT; | |
193 | fbi->fbops = &omap_fb_ops; | |
194 | ||
195 | strcpy(fbi->fix.id, MODULE_NAME); | |
196 | ||
ae43d7ca | 197 | drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); |
a6a91827 | 198 | drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); |
cd5351f4 | 199 | |
cd5351f4 RC |
200 | dev->mode_config.fb_base = paddr; |
201 | ||
ae43d7ca RC |
202 | fbi->screen_base = omap_gem_vaddr(fbdev->bo); |
203 | fbi->screen_size = fbdev->bo->size; | |
cd5351f4 | 204 | fbi->fix.smem_start = paddr; |
ae43d7ca | 205 | fbi->fix.smem_len = fbdev->bo->size; |
cd5351f4 | 206 | |
a6a91827 RC |
207 | /* if we have DMM, then we can use it for scrolling by just |
208 | * shuffling pages around in DMM rather than doing sw blit. | |
209 | */ | |
510d4d32 | 210 | if (fbdev->ywrap_enabled) { |
a6a91827 RC |
211 | DRM_INFO("Enabling DMM ywrap scrolling\n"); |
212 | fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST; | |
213 | fbi->fix.ywrapstep = 1; | |
214 | } | |
215 | ||
510d4d32 | 216 | |
cd5351f4 RC |
217 | DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); |
218 | DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height); | |
219 | ||
220 | mutex_unlock(&dev->struct_mutex); | |
221 | ||
222 | return 0; | |
223 | ||
224 | fail_unlock: | |
225 | mutex_unlock(&dev->struct_mutex); | |
226 | fail: | |
227 | ||
228 | if (ret) { | |
231e6faf AT |
229 | |
230 | drm_fb_helper_release_fbi(helper); | |
231 | ||
36206361 DV |
232 | if (fb) { |
233 | drm_framebuffer_unregister_private(fb); | |
f7eff60e | 234 | drm_framebuffer_remove(fb); |
36206361 | 235 | } |
cd5351f4 RC |
236 | } |
237 | ||
238 | return ret; | |
239 | } | |
240 | ||
3a493879 | 241 | static const struct drm_fb_helper_funcs omap_fb_helper_funcs = { |
cd5428a5 | 242 | .fb_probe = omap_fbdev_create, |
cd5351f4 RC |
243 | }; |
244 | ||
245 | static struct drm_fb_helper *get_fb(struct fb_info *fbi) | |
246 | { | |
247 | if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) { | |
248 | /* these are not the fb's you're looking for */ | |
249 | return NULL; | |
250 | } | |
251 | return fbi->par; | |
252 | } | |
253 | ||
cd5351f4 RC |
254 | /* initialize fbdev helper */ |
255 | struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev) | |
256 | { | |
257 | struct omap_drm_private *priv = dev->dev_private; | |
258 | struct omap_fbdev *fbdev = NULL; | |
259 | struct drm_fb_helper *helper; | |
260 | int ret = 0; | |
261 | ||
262 | fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); | |
78110bb8 | 263 | if (!fbdev) |
cd5351f4 | 264 | goto fail; |
cd5351f4 | 265 | |
9b55b95a RC |
266 | INIT_WORK(&fbdev->work, pan_worker); |
267 | ||
cd5351f4 RC |
268 | helper = &fbdev->base; |
269 | ||
10a23102 | 270 | drm_fb_helper_prepare(dev, helper, &omap_fb_helper_funcs); |
cd5351f4 RC |
271 | |
272 | ret = drm_fb_helper_init(dev, helper, | |
273 | priv->num_crtcs, priv->num_connectors); | |
274 | if (ret) { | |
275 | dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret); | |
276 | goto fail; | |
277 | } | |
278 | ||
01934c2a TR |
279 | ret = drm_fb_helper_single_add_all_connectors(helper); |
280 | if (ret) | |
281 | goto fini; | |
76a39dbf DV |
282 | |
283 | /* disable all the possible outputs/crtcs before entering KMS mode */ | |
284 | drm_helper_disable_unused_functions(dev); | |
285 | ||
01934c2a TR |
286 | ret = drm_fb_helper_initial_config(helper, 32); |
287 | if (ret) | |
288 | goto fini; | |
cd5351f4 RC |
289 | |
290 | priv->fbdev = helper; | |
291 | ||
292 | return helper; | |
293 | ||
01934c2a TR |
294 | fini: |
295 | drm_fb_helper_fini(helper); | |
cd5351f4 RC |
296 | fail: |
297 | kfree(fbdev); | |
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 | } |