]>
Commit | Line | Data |
---|---|---|
cd5351f4 | 1 | /* |
8bb0daff | 2 | * drivers/gpu/drm/omapdrm/omap_fb.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 | ||
2d802453 AB |
20 | #include <linux/seq_file.h> |
21 | ||
2d278f54 LP |
22 | #include <drm/drm_crtc.h> |
23 | #include <drm/drm_crtc_helper.h> | |
cd5351f4 | 24 | |
2d278f54 LP |
25 | #include "omap_dmm_tiler.h" |
26 | #include "omap_drv.h" | |
cd5351f4 | 27 | |
cd5351f4 RC |
28 | /* |
29 | * framebuffer funcs | |
30 | */ | |
31 | ||
c2e52e32 | 32 | static const u32 formats[] = { |
ae43d7ca | 33 | /* 16bpp [A]RGB: */ |
c2e52e32 TV |
34 | DRM_FORMAT_RGB565, /* RGB16-565 */ |
35 | DRM_FORMAT_RGBX4444, /* RGB12x-4444 */ | |
36 | DRM_FORMAT_XRGB4444, /* xRGB12-4444 */ | |
37 | DRM_FORMAT_RGBA4444, /* RGBA12-4444 */ | |
38 | DRM_FORMAT_ARGB4444, /* ARGB16-4444 */ | |
39 | DRM_FORMAT_XRGB1555, /* xRGB15-1555 */ | |
40 | DRM_FORMAT_ARGB1555, /* ARGB16-1555 */ | |
ae43d7ca | 41 | /* 24bpp RGB: */ |
c2e52e32 | 42 | DRM_FORMAT_RGB888, /* RGB24-888 */ |
ae43d7ca | 43 | /* 32bpp [A]RGB: */ |
c2e52e32 TV |
44 | DRM_FORMAT_RGBX8888, /* RGBx24-8888 */ |
45 | DRM_FORMAT_XRGB8888, /* xRGB24-8888 */ | |
46 | DRM_FORMAT_RGBA8888, /* RGBA32-8888 */ | |
47 | DRM_FORMAT_ARGB8888, /* ARGB32-8888 */ | |
ae43d7ca | 48 | /* YUV: */ |
c2e52e32 TV |
49 | DRM_FORMAT_NV12, |
50 | DRM_FORMAT_YUYV, | |
51 | DRM_FORMAT_UYVY, | |
ae43d7ca RC |
52 | }; |
53 | ||
9a0774e0 RC |
54 | /* per-plane info for the fb: */ |
55 | struct plane { | |
56 | struct drm_gem_object *bo; | |
57 | uint32_t pitch; | |
58 | uint32_t offset; | |
16869083 | 59 | dma_addr_t dma_addr; |
9a0774e0 RC |
60 | }; |
61 | ||
cd5351f4 RC |
62 | #define to_omap_framebuffer(x) container_of(x, struct omap_framebuffer, base) |
63 | ||
64 | struct omap_framebuffer { | |
65 | struct drm_framebuffer base; | |
f36eb5a8 | 66 | int pin_count; |
c9028b39 | 67 | const struct drm_format_info *format; |
4d20dfc0 | 68 | struct plane planes[2]; |
16869083 | 69 | /* lock for pinning (pin_count and planes.dma_addr) */ |
f524ab7c | 70 | struct mutex lock; |
cd5351f4 RC |
71 | }; |
72 | ||
73 | static int omap_framebuffer_create_handle(struct drm_framebuffer *fb, | |
74 | struct drm_file *file_priv, | |
75 | unsigned int *handle) | |
76 | { | |
77 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); | |
9a0774e0 RC |
78 | return drm_gem_handle_create(file_priv, |
79 | omap_fb->planes[0].bo, handle); | |
cd5351f4 RC |
80 | } |
81 | ||
82 | static void omap_framebuffer_destroy(struct drm_framebuffer *fb) | |
83 | { | |
cd5351f4 | 84 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); |
bcb0b461 | 85 | int i, n = fb->format->num_planes; |
cd5351f4 RC |
86 | |
87 | DBG("destroy: FB ID: %d (%p)", fb->base.id, fb); | |
88 | ||
89 | drm_framebuffer_cleanup(fb); | |
90 | ||
9a0774e0 RC |
91 | for (i = 0; i < n; i++) { |
92 | struct plane *plane = &omap_fb->planes[i]; | |
76e4c327 ME |
93 | |
94 | drm_gem_object_unreference_unlocked(plane->bo); | |
cd5351f4 RC |
95 | } |
96 | ||
97 | kfree(omap_fb); | |
98 | } | |
99 | ||
cd5351f4 RC |
100 | static const struct drm_framebuffer_funcs omap_framebuffer_funcs = { |
101 | .create_handle = omap_framebuffer_create_handle, | |
102 | .destroy = omap_framebuffer_destroy, | |
cd5351f4 RC |
103 | }; |
104 | ||
3c810c61 | 105 | static uint32_t get_linear_addr(struct plane *plane, |
c9028b39 | 106 | const struct drm_format_info *format, int n, int x, int y) |
3c810c61 RC |
107 | { |
108 | uint32_t offset; | |
109 | ||
c9028b39 LP |
110 | offset = plane->offset |
111 | + (x * format->cpp[n] / (n == 0 ? 1 : format->hsub)) | |
112 | + (y * plane->pitch / (n == 0 ? 1 : format->vsub)); | |
3c810c61 | 113 | |
16869083 | 114 | return plane->dma_addr + offset; |
3c810c61 RC |
115 | } |
116 | ||
bfeece55 TV |
117 | bool omap_framebuffer_supports_rotation(struct drm_framebuffer *fb) |
118 | { | |
119 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); | |
120 | struct plane *plane = &omap_fb->planes[0]; | |
121 | ||
122 | return omap_gem_flags(plane->bo) & OMAP_BO_TILED; | |
123 | } | |
124 | ||
8958aeb9 TV |
125 | /* Note: DRM rotates counter-clockwise, TILER & DSS rotates clockwise */ |
126 | static uint32_t drm_rotation_to_tiler(unsigned int drm_rot) | |
127 | { | |
128 | uint32_t orient; | |
129 | ||
130 | switch (drm_rot & DRM_MODE_ROTATE_MASK) { | |
131 | default: | |
132 | case DRM_MODE_ROTATE_0: | |
133 | orient = 0; | |
134 | break; | |
135 | case DRM_MODE_ROTATE_90: | |
136 | orient = MASK_XY_FLIP | MASK_X_INVERT; | |
137 | break; | |
138 | case DRM_MODE_ROTATE_180: | |
139 | orient = MASK_X_INVERT | MASK_Y_INVERT; | |
140 | break; | |
141 | case DRM_MODE_ROTATE_270: | |
142 | orient = MASK_XY_FLIP | MASK_Y_INVERT; | |
143 | break; | |
144 | } | |
145 | ||
146 | if (drm_rot & DRM_MODE_REFLECT_X) | |
147 | orient ^= MASK_X_INVERT; | |
148 | ||
149 | if (drm_rot & DRM_MODE_REFLECT_Y) | |
150 | orient ^= MASK_Y_INVERT; | |
151 | ||
152 | return orient; | |
153 | } | |
154 | ||
9a0774e0 RC |
155 | /* update ovl info for scanout, handles cases of multi-planar fb's, etc. |
156 | */ | |
3c810c61 | 157 | void omap_framebuffer_update_scanout(struct drm_framebuffer *fb, |
218ed535 | 158 | struct drm_plane_state *state, struct omap_overlay_info *info) |
9a0774e0 RC |
159 | { |
160 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); | |
c9028b39 | 161 | const struct drm_format_info *format = omap_fb->format; |
9a0774e0 | 162 | struct plane *plane = &omap_fb->planes[0]; |
3c810c61 RC |
163 | uint32_t x, y, orient = 0; |
164 | ||
41aff42a | 165 | info->fourcc = fb->format->format; |
3c810c61 | 166 | |
218ed535 TV |
167 | info->pos_x = state->crtc_x; |
168 | info->pos_y = state->crtc_y; | |
169 | info->out_width = state->crtc_w; | |
170 | info->out_height = state->crtc_h; | |
171 | info->width = state->src_w >> 16; | |
172 | info->height = state->src_h >> 16; | |
3c810c61 | 173 | |
218ed535 TV |
174 | /* DSS driver wants the w & h in rotated orientation */ |
175 | if (drm_rotation_90_or_270(state->rotation)) | |
176 | swap(info->width, info->height); | |
177 | ||
178 | x = state->src_x >> 16; | |
179 | y = state->src_y >> 16; | |
3c810c61 RC |
180 | |
181 | if (omap_gem_flags(plane->bo) & OMAP_BO_TILED) { | |
218ed535 TV |
182 | uint32_t w = state->src_w >> 16; |
183 | uint32_t h = state->src_h >> 16; | |
3c810c61 | 184 | |
218ed535 | 185 | orient = drm_rotation_to_tiler(state->rotation); |
3c810c61 | 186 | |
c4df6e42 TV |
187 | /* |
188 | * omap_gem_rotated_paddr() wants the x & y in tiler units. | |
189 | * Usually tiler unit size is the same as the pixel size, except | |
190 | * for YUV422 formats, for which the tiler unit size is 32 bits | |
191 | * and pixel size is 16 bits. | |
192 | */ | |
193 | if (fb->format->format == DRM_FORMAT_UYVY || | |
194 | fb->format->format == DRM_FORMAT_YUYV) { | |
195 | x /= 2; | |
196 | w /= 2; | |
197 | } | |
198 | ||
218ed535 | 199 | /* adjust x,y offset for invert: */ |
3c810c61 RC |
200 | if (orient & MASK_Y_INVERT) |
201 | y += h - 1; | |
202 | if (orient & MASK_X_INVERT) | |
203 | x += w - 1; | |
9a0774e0 | 204 | |
c4df6e42 | 205 | /* Note: x and y are in TILER units, not pixels */ |
16869083 LP |
206 | omap_gem_rotated_dma_addr(plane->bo, orient, x, y, |
207 | &info->paddr); | |
3c810c61 | 208 | info->rotation_type = OMAP_DSS_ROT_TILER; |
16f9ede5 | 209 | info->rotation = state->rotation ?: DRM_MODE_ROTATE_0; |
c4df6e42 | 210 | /* Note: stride in TILER units, not pixels */ |
3c810c61 RC |
211 | info->screen_width = omap_gem_tiled_stride(plane->bo, orient); |
212 | } else { | |
218ed535 | 213 | switch (state->rotation & DRM_MODE_ROTATE_MASK) { |
5ac96345 | 214 | case 0: |
c2c446ad | 215 | case DRM_MODE_ROTATE_0: |
5ac96345 TV |
216 | /* OK */ |
217 | break; | |
218 | ||
219 | default: | |
220 | dev_warn(fb->dev->dev, | |
221 | "rotation '%d' ignored for non-tiled fb\n", | |
218ed535 | 222 | state->rotation); |
5ac96345 TV |
223 | break; |
224 | } | |
225 | ||
3c810c61 | 226 | info->paddr = get_linear_addr(plane, format, 0, x, y); |
517a8a95 | 227 | info->rotation_type = OMAP_DSS_ROT_NONE; |
16f9ede5 | 228 | info->rotation = DRM_MODE_ROTATE_0; |
3c810c61 RC |
229 | info->screen_width = plane->pitch; |
230 | } | |
231 | ||
232 | /* convert to pixels: */ | |
c9028b39 | 233 | info->screen_width /= format->cpp[0]; |
9a0774e0 | 234 | |
c2e52e32 | 235 | if (fb->format->format == DRM_FORMAT_NV12) { |
9a0774e0 | 236 | plane = &omap_fb->planes[1]; |
3c810c61 RC |
237 | |
238 | if (info->rotation_type == OMAP_DSS_ROT_TILER) { | |
239 | WARN_ON(!(omap_gem_flags(plane->bo) & OMAP_BO_TILED)); | |
16869083 LP |
240 | omap_gem_rotated_dma_addr(plane->bo, orient, x/2, y/2, |
241 | &info->p_uv_addr); | |
3c810c61 RC |
242 | } else { |
243 | info->p_uv_addr = get_linear_addr(plane, format, 1, x, y); | |
244 | } | |
9a0774e0 RC |
245 | } else { |
246 | info->p_uv_addr = 0; | |
247 | } | |
248 | } | |
249 | ||
5833bd2f RC |
250 | /* pin, prepare for scanout: */ |
251 | int omap_framebuffer_pin(struct drm_framebuffer *fb) | |
b33f34d3 | 252 | { |
5833bd2f | 253 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); |
bcb0b461 | 254 | int ret, i, n = fb->format->num_planes; |
5833bd2f | 255 | |
f524ab7c TV |
256 | mutex_lock(&omap_fb->lock); |
257 | ||
f36eb5a8 TV |
258 | if (omap_fb->pin_count > 0) { |
259 | omap_fb->pin_count++; | |
f524ab7c | 260 | mutex_unlock(&omap_fb->lock); |
f36eb5a8 TV |
261 | return 0; |
262 | } | |
263 | ||
5833bd2f RC |
264 | for (i = 0; i < n; i++) { |
265 | struct plane *plane = &omap_fb->planes[i]; | |
bc20c85c | 266 | ret = omap_gem_pin(plane->bo, &plane->dma_addr); |
5833bd2f RC |
267 | if (ret) |
268 | goto fail; | |
d61ce7da | 269 | omap_gem_dma_sync_buffer(plane->bo, DMA_TO_DEVICE); |
5833bd2f | 270 | } |
b33f34d3 | 271 | |
f36eb5a8 TV |
272 | omap_fb->pin_count++; |
273 | ||
f524ab7c TV |
274 | mutex_unlock(&omap_fb->lock); |
275 | ||
5833bd2f | 276 | return 0; |
b33f34d3 | 277 | |
5833bd2f RC |
278 | fail: |
279 | for (i--; i >= 0; i--) { | |
280 | struct plane *plane = &omap_fb->planes[i]; | |
bc20c85c | 281 | omap_gem_unpin(plane->bo); |
16869083 | 282 | plane->dma_addr = 0; |
5833bd2f | 283 | } |
b33f34d3 | 284 | |
f524ab7c TV |
285 | mutex_unlock(&omap_fb->lock); |
286 | ||
5833bd2f RC |
287 | return ret; |
288 | } | |
b33f34d3 | 289 | |
5833bd2f | 290 | /* unpin, no longer being scanned out: */ |
9c368506 | 291 | void omap_framebuffer_unpin(struct drm_framebuffer *fb) |
5833bd2f RC |
292 | { |
293 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); | |
bcb0b461 | 294 | int i, n = fb->format->num_planes; |
b33f34d3 | 295 | |
f524ab7c TV |
296 | mutex_lock(&omap_fb->lock); |
297 | ||
f36eb5a8 TV |
298 | omap_fb->pin_count--; |
299 | ||
f524ab7c TV |
300 | if (omap_fb->pin_count > 0) { |
301 | mutex_unlock(&omap_fb->lock); | |
9c368506 | 302 | return; |
f524ab7c | 303 | } |
f36eb5a8 | 304 | |
5833bd2f RC |
305 | for (i = 0; i < n; i++) { |
306 | struct plane *plane = &omap_fb->planes[i]; | |
bc20c85c | 307 | omap_gem_unpin(plane->bo); |
16869083 | 308 | plane->dma_addr = 0; |
b33f34d3 RC |
309 | } |
310 | ||
f524ab7c | 311 | mutex_unlock(&omap_fb->lock); |
b33f34d3 RC |
312 | } |
313 | ||
cd5351f4 RC |
314 | /* iterate thru all the connectors, returning ones that are attached |
315 | * to the same fb.. | |
316 | */ | |
317 | struct drm_connector *omap_framebuffer_get_next_connector( | |
318 | struct drm_framebuffer *fb, struct drm_connector *from) | |
319 | { | |
320 | struct drm_device *dev = fb->dev; | |
321 | struct list_head *connector_list = &dev->mode_config.connector_list; | |
322 | struct drm_connector *connector = from; | |
323 | ||
ddcd49ed | 324 | if (!from) |
06fb220b LP |
325 | return list_first_entry_or_null(connector_list, typeof(*from), |
326 | head); | |
cd5351f4 RC |
327 | |
328 | list_for_each_entry_from(connector, connector_list, head) { | |
329 | if (connector != from) { | |
330 | struct drm_encoder *encoder = connector->encoder; | |
331 | struct drm_crtc *crtc = encoder ? encoder->crtc : NULL; | |
f4510a27 | 332 | if (crtc && crtc->primary->fb == fb) |
cd5351f4 | 333 | return connector; |
ddcd49ed | 334 | |
cd5351f4 RC |
335 | } |
336 | } | |
337 | ||
338 | return NULL; | |
339 | } | |
cd5351f4 | 340 | |
f6b6036e RC |
341 | #ifdef CONFIG_DEBUG_FS |
342 | void omap_framebuffer_describe(struct drm_framebuffer *fb, struct seq_file *m) | |
343 | { | |
344 | struct omap_framebuffer *omap_fb = to_omap_framebuffer(fb); | |
bcb0b461 | 345 | int i, n = fb->format->num_planes; |
f6b6036e RC |
346 | |
347 | seq_printf(m, "fb: %dx%d@%4.4s\n", fb->width, fb->height, | |
438b74a5 | 348 | (char *)&fb->format->format); |
f6b6036e RC |
349 | |
350 | for (i = 0; i < n; i++) { | |
351 | struct plane *plane = &omap_fb->planes[i]; | |
352 | seq_printf(m, " %d: offset=%d pitch=%d, obj: ", | |
353 | i, plane->offset, plane->pitch); | |
354 | omap_gem_describe(plane->bo, m); | |
355 | } | |
356 | } | |
357 | #endif | |
358 | ||
cd5351f4 | 359 | struct drm_framebuffer *omap_framebuffer_create(struct drm_device *dev, |
1eb83451 | 360 | struct drm_file *file, const struct drm_mode_fb_cmd2 *mode_cmd) |
cd5351f4 | 361 | { |
a39c94e8 | 362 | unsigned int num_planes = drm_format_num_planes(mode_cmd->pixel_format); |
ae43d7ca | 363 | struct drm_gem_object *bos[4]; |
cd5351f4 | 364 | struct drm_framebuffer *fb; |
a39c94e8 | 365 | int i; |
ae43d7ca | 366 | |
a39c94e8 LP |
367 | for (i = 0; i < num_planes; i++) { |
368 | bos[i] = drm_gem_object_lookup(file, mode_cmd->handles[i]); | |
369 | if (!bos[i]) { | |
370 | fb = ERR_PTR(-ENOENT); | |
371 | goto error; | |
372 | } | |
373 | } | |
ae43d7ca RC |
374 | |
375 | fb = omap_framebuffer_init(dev, mode_cmd, bos); | |
a39c94e8 LP |
376 | if (IS_ERR(fb)) |
377 | goto error; | |
378 | ||
379 | return fb; | |
380 | ||
381 | error: | |
382 | while (--i > 0) | |
383 | drm_gem_object_unreference_unlocked(bos[i]); | |
384 | ||
cd5351f4 RC |
385 | return fb; |
386 | } | |
387 | ||
388 | struct drm_framebuffer *omap_framebuffer_init(struct drm_device *dev, | |
1eb83451 | 389 | const struct drm_mode_fb_cmd2 *mode_cmd, struct drm_gem_object **bos) |
cd5351f4 | 390 | { |
c9028b39 | 391 | const struct drm_format_info *format = NULL; |
925e4940 | 392 | struct omap_framebuffer *omap_fb = NULL; |
cd5351f4 | 393 | struct drm_framebuffer *fb = NULL; |
6941e3d1 | 394 | unsigned int pitch = mode_cmd->pitches[0]; |
c9028b39 | 395 | int ret, i; |
cd5351f4 | 396 | |
ae43d7ca | 397 | DBG("create framebuffer: dev=%p, mode_cmd=%p (%dx%d@%4.4s)", |
cd5351f4 | 398 | dev, mode_cmd, mode_cmd->width, mode_cmd->height, |
ae43d7ca RC |
399 | (char *)&mode_cmd->pixel_format); |
400 | ||
c9028b39 LP |
401 | format = drm_format_info(mode_cmd->pixel_format); |
402 | ||
ae43d7ca | 403 | for (i = 0; i < ARRAY_SIZE(formats); i++) { |
c2e52e32 | 404 | if (formats[i] == mode_cmd->pixel_format) |
ae43d7ca | 405 | break; |
ae43d7ca RC |
406 | } |
407 | ||
c2e52e32 | 408 | if (!format || i == ARRAY_SIZE(formats)) { |
a078a3dd LP |
409 | dev_dbg(dev->dev, "unsupported pixel format: %4.4s\n", |
410 | (char *)&mode_cmd->pixel_format); | |
ae43d7ca RC |
411 | ret = -EINVAL; |
412 | goto fail; | |
413 | } | |
cd5351f4 | 414 | |
cd5351f4 RC |
415 | omap_fb = kzalloc(sizeof(*omap_fb), GFP_KERNEL); |
416 | if (!omap_fb) { | |
ae43d7ca | 417 | ret = -ENOMEM; |
cd5351f4 RC |
418 | goto fail; |
419 | } | |
420 | ||
421 | fb = &omap_fb->base; | |
9a0774e0 | 422 | omap_fb->format = format; |
f524ab7c | 423 | mutex_init(&omap_fb->lock); |
cd5351f4 | 424 | |
6941e3d1 LP |
425 | /* |
426 | * The code below assumes that no format use more than two planes, and | |
427 | * that the two planes of multiplane formats need the same number of | |
428 | * bytes per pixel. | |
429 | */ | |
430 | if (format->num_planes == 2 && pitch != mode_cmd->pitches[1]) { | |
a078a3dd | 431 | dev_dbg(dev->dev, "pitches differ between planes 0 and 1\n"); |
6941e3d1 LP |
432 | ret = -EINVAL; |
433 | goto fail; | |
434 | } | |
cd5351f4 | 435 | |
6941e3d1 | 436 | if (pitch % format->cpp[0]) { |
a078a3dd | 437 | dev_dbg(dev->dev, |
6941e3d1 LP |
438 | "buffer pitch (%u bytes) is not a multiple of pixel size (%u bytes)\n", |
439 | pitch, format->cpp[0]); | |
440 | ret = -EINVAL; | |
441 | goto fail; | |
442 | } | |
2dab0bab | 443 | |
c9028b39 | 444 | for (i = 0; i < format->num_planes; i++) { |
9a0774e0 | 445 | struct plane *plane = &omap_fb->planes[i]; |
c9028b39 LP |
446 | unsigned int vsub = i == 0 ? 1 : format->vsub; |
447 | unsigned int size; | |
9a0774e0 | 448 | |
c9028b39 | 449 | size = pitch * mode_cmd->height / vsub; |
9a0774e0 | 450 | |
6941e3d1 | 451 | if (size > omap_gem_mmap_size(bos[i]) - mode_cmd->offsets[i]) { |
a078a3dd | 452 | dev_dbg(dev->dev, |
2150c19b | 453 | "provided buffer object is too small! %zu < %d\n", |
6941e3d1 | 454 | bos[i]->size - mode_cmd->offsets[i], size); |
be4f235c TV |
455 | ret = -EINVAL; |
456 | goto fail; | |
457 | } | |
458 | ||
9a0774e0 RC |
459 | plane->bo = bos[i]; |
460 | plane->offset = mode_cmd->offsets[i]; | |
9f18c95a | 461 | plane->pitch = pitch; |
16869083 | 462 | plane->dma_addr = 0; |
cd5351f4 RC |
463 | } |
464 | ||
a3f913ca | 465 | drm_helper_mode_fill_fb_struct(dev, fb, mode_cmd); |
cd5351f4 | 466 | |
c7d73f6a DV |
467 | ret = drm_framebuffer_init(dev, fb, &omap_framebuffer_funcs); |
468 | if (ret) { | |
469 | dev_err(dev->dev, "framebuffer init failed: %d\n", ret); | |
470 | goto fail; | |
471 | } | |
472 | ||
473 | DBG("create: FB ID: %d (%p)", fb->base.id, fb); | |
474 | ||
cd5351f4 RC |
475 | return fb; |
476 | ||
477 | fail: | |
925e4940 | 478 | kfree(omap_fb); |
ddcd49ed | 479 | |
ae43d7ca | 480 | return ERR_PTR(ret); |
cd5351f4 | 481 | } |