]>
Commit | Line | Data |
---|---|---|
cd5351f4 | 1 | /* |
bb5cdf8d | 2 | * Copyright (C) 2011 Texas Instruments Incorporated - http://www.ti.com/ |
cd5351f4 RC |
3 | * Author: Rob Clark <rob@ti.com> |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify it | |
6 | * under the terms of the GNU General Public License version 2 as published by | |
7 | * the Free Software Foundation. | |
8 | * | |
9 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
10 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
11 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
12 | * more details. | |
13 | * | |
14 | * You should have received a copy of the GNU General Public License along with | |
15 | * this program. If not, see <http://www.gnu.org/licenses/>. | |
16 | */ | |
17 | ||
2d278f54 LP |
18 | #include <drm/drm_crtc.h> |
19 | #include <drm/drm_fb_helper.h> | |
cd5351f4 | 20 | |
2d278f54 | 21 | #include "omap_drv.h" |
cd5351f4 | 22 | |
510d4d32 RC |
23 | MODULE_PARM_DESC(ywrap, "Enable ywrap scrolling (omap44xx and later, default 'y')"); |
24 | static bool ywrap_enabled = true; | |
25 | module_param_named(ywrap, ywrap_enabled, bool, 0644); | |
26 | ||
cd5351f4 RC |
27 | /* |
28 | * fbdev funcs, to implement legacy fbdev interface on top of drm driver | |
29 | */ | |
30 | ||
31 | #define to_omap_fbdev(x) container_of(x, struct omap_fbdev, base) | |
32 | ||
33 | struct omap_fbdev { | |
34 | struct drm_fb_helper base; | |
35 | struct drm_framebuffer *fb; | |
a6a91827 | 36 | struct drm_gem_object *bo; |
510d4d32 | 37 | bool ywrap_enabled; |
9b55b95a RC |
38 | |
39 | /* for deferred dmm roll when getting called in atomic ctx */ | |
40 | struct work_struct work; | |
cd5351f4 RC |
41 | }; |
42 | ||
a6a91827 | 43 | static struct drm_fb_helper *get_fb(struct fb_info *fbi); |
cd5351f4 | 44 | |
9b55b95a RC |
45 | static void pan_worker(struct work_struct *work) |
46 | { | |
47 | struct omap_fbdev *fbdev = container_of(work, struct omap_fbdev, work); | |
48 | struct fb_info *fbi = fbdev->base.fbdev; | |
49 | int npages; | |
50 | ||
51 | /* DMM roll shifts in 4K pages: */ | |
52 | npages = fbi->fix.line_length >> PAGE_SHIFT; | |
53 | omap_gem_roll(fbdev->bo, fbi->var.yoffset * npages); | |
54 | } | |
55 | ||
a6a91827 RC |
56 | static int omap_fbdev_pan_display(struct fb_var_screeninfo *var, |
57 | struct fb_info *fbi) | |
58 | { | |
59 | struct drm_fb_helper *helper = get_fb(fbi); | |
60 | struct omap_fbdev *fbdev = to_omap_fbdev(helper); | |
a6a91827 RC |
61 | |
62 | if (!helper) | |
63 | goto fallback; | |
64 | ||
510d4d32 | 65 | if (!fbdev->ywrap_enabled) |
a6a91827 RC |
66 | goto fallback; |
67 | ||
9b55b95a RC |
68 | if (drm_can_sleep()) { |
69 | pan_worker(&fbdev->work); | |
70 | } else { | |
71 | struct omap_drm_private *priv = helper->dev->dev_private; | |
72 | queue_work(priv->wq, &fbdev->work); | |
73 | } | |
a6a91827 RC |
74 | |
75 | return 0; | |
76 | ||
77 | fallback: | |
78 | return drm_fb_helper_pan_display(var, fbi); | |
79 | } | |
80 | ||
cd5351f4 RC |
81 | static struct fb_ops omap_fb_ops = { |
82 | .owner = THIS_MODULE, | |
d074e55d | 83 | DRM_FB_HELPER_DEFAULT_OPS, |
cd5351f4 | 84 | |
231e6faf AT |
85 | .fb_read = drm_fb_helper_sys_read, |
86 | .fb_write = drm_fb_helper_sys_write, | |
87 | .fb_fillrect = drm_fb_helper_sys_fillrect, | |
88 | .fb_copyarea = drm_fb_helper_sys_copyarea, | |
89 | .fb_imageblit = drm_fb_helper_sys_imageblit, | |
cd5351f4 | 90 | |
a6a91827 | 91 | .fb_pan_display = omap_fbdev_pan_display, |
cd5351f4 RC |
92 | }; |
93 | ||
94 | static int omap_fbdev_create(struct drm_fb_helper *helper, | |
95 | struct drm_fb_helper_surface_size *sizes) | |
96 | { | |
97 | struct omap_fbdev *fbdev = to_omap_fbdev(helper); | |
98 | struct drm_device *dev = helper->dev; | |
a6a91827 | 99 | struct omap_drm_private *priv = dev->dev_private; |
cd5351f4 | 100 | struct drm_framebuffer *fb = NULL; |
a6a91827 | 101 | union omap_gem_size gsize; |
cd5351f4 | 102 | struct fb_info *fbi = NULL; |
ae43d7ca | 103 | struct drm_mode_fb_cmd2 mode_cmd = {0}; |
16869083 | 104 | dma_addr_t dma_addr; |
cd5351f4 RC |
105 | int ret; |
106 | ||
cd5351f4 | 107 | sizes->surface_bpp = 32; |
1d977b06 | 108 | sizes->surface_depth = 24; |
cd5351f4 | 109 | |
a6a91827 RC |
110 | DBG("create fbdev: %dx%d@%d (%dx%d)", sizes->surface_width, |
111 | sizes->surface_height, sizes->surface_bpp, | |
112 | sizes->fb_width, sizes->fb_height); | |
cd5351f4 | 113 | |
ae43d7ca RC |
114 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, |
115 | sizes->surface_depth); | |
116 | ||
cd5351f4 RC |
117 | mode_cmd.width = sizes->surface_width; |
118 | mode_cmd.height = sizes->surface_height; | |
119 | ||
ce481eda TV |
120 | mode_cmd.pitches[0] = |
121 | DIV_ROUND_UP(mode_cmd.width * sizes->surface_bpp, 8); | |
a6a91827 | 122 | |
510d4d32 RC |
123 | fbdev->ywrap_enabled = priv->has_dmm && ywrap_enabled; |
124 | if (fbdev->ywrap_enabled) { | |
a6a91827 | 125 | /* need to align pitch to page size if using DMM scrolling */ |
743c1671 | 126 | mode_cmd.pitches[0] = PAGE_ALIGN(mode_cmd.pitches[0]); |
a6a91827 RC |
127 | } |
128 | ||
129 | /* allocate backing bo */ | |
130 | gsize = (union omap_gem_size){ | |
ae43d7ca | 131 | .bytes = PAGE_ALIGN(mode_cmd.pitches[0] * mode_cmd.height), |
a6a91827 RC |
132 | }; |
133 | DBG("allocating %d bytes for fb %d", gsize.bytes, dev->primary->index); | |
134 | fbdev->bo = omap_gem_new(dev, gsize, OMAP_BO_SCANOUT | OMAP_BO_WC); | |
135 | if (!fbdev->bo) { | |
136 | dev_err(dev->dev, "failed to allocate buffer object\n"); | |
ae43d7ca | 137 | ret = -ENOMEM; |
a6a91827 RC |
138 | goto fail; |
139 | } | |
140 | ||
ae43d7ca RC |
141 | fb = omap_framebuffer_init(dev, &mode_cmd, &fbdev->bo); |
142 | if (IS_ERR(fb)) { | |
cd5351f4 | 143 | dev_err(dev->dev, "failed to allocate fb\n"); |
ae43d7ca RC |
144 | /* note: if fb creation failed, we can't rely on fb destroy |
145 | * to unref the bo: | |
146 | */ | |
5bb682c4 | 147 | drm_gem_object_unreference_unlocked(fbdev->bo); |
ae43d7ca RC |
148 | ret = PTR_ERR(fb); |
149 | goto fail; | |
150 | } | |
151 | ||
152 | /* note: this keeps the bo pinned.. which is perhaps not ideal, | |
153 | * but is needed as long as we use fb_mmap() to mmap to userspace | |
154 | * (since this happens using fix.smem_start). Possibly we could | |
155 | * implement our own mmap using GEM mmap support to avoid this | |
156 | * (non-tiled buffer doesn't need to be pinned for fbcon to write | |
157 | * to it). Then we just need to be sure that we are able to re- | |
158 | * pin it in case of an opps. | |
159 | */ | |
bc20c85c | 160 | ret = omap_gem_pin(fbdev->bo, &dma_addr); |
ae43d7ca | 161 | if (ret) { |
bc20c85c | 162 | dev_err(dev->dev, "could not pin framebuffer\n"); |
cd5351f4 RC |
163 | ret = -ENOMEM; |
164 | goto fail; | |
165 | } | |
166 | ||
167 | mutex_lock(&dev->struct_mutex); | |
168 | ||
231e6faf AT |
169 | fbi = drm_fb_helper_alloc_fbi(helper); |
170 | if (IS_ERR(fbi)) { | |
cd5351f4 | 171 | dev_err(dev->dev, "failed to allocate fb info\n"); |
231e6faf | 172 | ret = PTR_ERR(fbi); |
cd5351f4 RC |
173 | goto fail_unlock; |
174 | } | |
175 | ||
176 | DBG("fbi=%p, dev=%p", fbi, dev); | |
177 | ||
178 | fbdev->fb = fb; | |
179 | helper->fb = fb; | |
cd5351f4 RC |
180 | |
181 | fbi->par = helper; | |
cd5351f4 RC |
182 | fbi->fbops = &omap_fb_ops; |
183 | ||
184 | strcpy(fbi->fix.id, MODULE_NAME); | |
185 | ||
b00c600e | 186 | drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->format->depth); |
a6a91827 | 187 | drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); |
cd5351f4 | 188 | |
16869083 | 189 | dev->mode_config.fb_base = dma_addr; |
cd5351f4 | 190 | |
5db08a7e | 191 | fbi->screen_buffer = omap_gem_vaddr(fbdev->bo); |
ae43d7ca | 192 | fbi->screen_size = fbdev->bo->size; |
16869083 | 193 | fbi->fix.smem_start = dma_addr; |
ae43d7ca | 194 | fbi->fix.smem_len = fbdev->bo->size; |
cd5351f4 | 195 | |
a6a91827 RC |
196 | /* if we have DMM, then we can use it for scrolling by just |
197 | * shuffling pages around in DMM rather than doing sw blit. | |
198 | */ | |
510d4d32 | 199 | if (fbdev->ywrap_enabled) { |
a6a91827 RC |
200 | DRM_INFO("Enabling DMM ywrap scrolling\n"); |
201 | fbi->flags |= FBINFO_HWACCEL_YWRAP | FBINFO_READS_FAST; | |
202 | fbi->fix.ywrapstep = 1; | |
203 | } | |
204 | ||
510d4d32 | 205 | |
cd5351f4 RC |
206 | DBG("par=%p, %dx%d", fbi->par, fbi->var.xres, fbi->var.yres); |
207 | DBG("allocated %dx%d fb", fbdev->fb->width, fbdev->fb->height); | |
208 | ||
209 | mutex_unlock(&dev->struct_mutex); | |
210 | ||
211 | return 0; | |
212 | ||
213 | fail_unlock: | |
214 | mutex_unlock(&dev->struct_mutex); | |
215 | fail: | |
216 | ||
217 | if (ret) { | |
b77bc10b | 218 | if (fb) |
f7eff60e | 219 | drm_framebuffer_remove(fb); |
cd5351f4 RC |
220 | } |
221 | ||
222 | return ret; | |
223 | } | |
224 | ||
3a493879 | 225 | static const struct drm_fb_helper_funcs omap_fb_helper_funcs = { |
cd5428a5 | 226 | .fb_probe = omap_fbdev_create, |
cd5351f4 RC |
227 | }; |
228 | ||
229 | static struct drm_fb_helper *get_fb(struct fb_info *fbi) | |
230 | { | |
231 | if (!fbi || strcmp(fbi->fix.id, MODULE_NAME)) { | |
232 | /* these are not the fb's you're looking for */ | |
233 | return NULL; | |
234 | } | |
235 | return fbi->par; | |
236 | } | |
237 | ||
cd5351f4 RC |
238 | /* initialize fbdev helper */ |
239 | struct drm_fb_helper *omap_fbdev_init(struct drm_device *dev) | |
240 | { | |
241 | struct omap_drm_private *priv = dev->dev_private; | |
242 | struct omap_fbdev *fbdev = NULL; | |
243 | struct drm_fb_helper *helper; | |
244 | int ret = 0; | |
245 | ||
246 | fbdev = kzalloc(sizeof(*fbdev), GFP_KERNEL); | |
78110bb8 | 247 | if (!fbdev) |
cd5351f4 | 248 | goto fail; |
cd5351f4 | 249 | |
9b55b95a RC |
250 | INIT_WORK(&fbdev->work, pan_worker); |
251 | ||
cd5351f4 RC |
252 | helper = &fbdev->base; |
253 | ||
10a23102 | 254 | drm_fb_helper_prepare(dev, helper, &omap_fb_helper_funcs); |
cd5351f4 | 255 | |
e4563f6b | 256 | ret = drm_fb_helper_init(dev, helper, priv->num_connectors); |
cd5351f4 RC |
257 | if (ret) { |
258 | dev_err(dev->dev, "could not init fbdev: ret=%d\n", ret); | |
259 | goto fail; | |
260 | } | |
261 | ||
01934c2a TR |
262 | ret = drm_fb_helper_single_add_all_connectors(helper); |
263 | if (ret) | |
264 | goto fini; | |
76a39dbf | 265 | |
01934c2a TR |
266 | ret = drm_fb_helper_initial_config(helper, 32); |
267 | if (ret) | |
268 | goto fini; | |
cd5351f4 RC |
269 | |
270 | priv->fbdev = helper; | |
271 | ||
272 | return helper; | |
273 | ||
01934c2a TR |
274 | fini: |
275 | drm_fb_helper_fini(helper); | |
cd5351f4 RC |
276 | fail: |
277 | kfree(fbdev); | |
e1c1174f LP |
278 | |
279 | dev_warn(dev->dev, "omap_fbdev_init failed\n"); | |
280 | /* well, limp along without an fbdev.. maybe X11 will work? */ | |
281 | ||
cd5351f4 RC |
282 | return NULL; |
283 | } | |
284 | ||
285 | void omap_fbdev_free(struct drm_device *dev) | |
286 | { | |
287 | struct omap_drm_private *priv = dev->dev_private; | |
288 | struct drm_fb_helper *helper = priv->fbdev; | |
289 | struct omap_fbdev *fbdev; | |
cd5351f4 RC |
290 | |
291 | DBG(); | |
292 | ||
231e6faf | 293 | drm_fb_helper_unregister_fbi(helper); |
cd5351f4 RC |
294 | |
295 | drm_fb_helper_fini(helper); | |
296 | ||
297 | fbdev = to_omap_fbdev(priv->fbdev); | |
298 | ||
bc20c85c LP |
299 | /* unpin the GEM object pinned in omap_fbdev_create() */ |
300 | omap_gem_unpin(fbdev->bo); | |
5e19c06d | 301 | |
a6a91827 | 302 | /* this will free the backing object */ |
b77bc10b | 303 | if (fbdev->fb) |
f7eff60e | 304 | drm_framebuffer_remove(fbdev->fb); |
a6a91827 | 305 | |
a9e8d70c JL |
306 | kfree(fbdev); |
307 | ||
cd5351f4 RC |
308 | priv->fbdev = NULL; |
309 | } |