Commit | Line | Data |
---|---|---|
96f60e37 RK |
1 | /* |
2 | * Copyright (C) 2012 Russell King | |
3 | * Written from the i915 driver. | |
4 | * | |
5 | * This program is free software; you can redistribute it and/or modify | |
6 | * it under the terms of the GNU General Public License version 2 as | |
7 | * published by the Free Software Foundation. | |
8 | */ | |
9 | #include <linux/errno.h> | |
10 | #include <linux/fb.h> | |
11 | #include <linux/kernel.h> | |
12 | #include <linux/module.h> | |
13 | ||
14 | #include <drm/drmP.h> | |
15 | #include <drm/drm_fb_helper.h> | |
16 | #include "armada_crtc.h" | |
17 | #include "armada_drm.h" | |
18 | #include "armada_fb.h" | |
19 | #include "armada_gem.h" | |
20 | ||
21 | static /*const*/ struct fb_ops armada_fb_ops = { | |
22 | .owner = THIS_MODULE, | |
23 | .fb_check_var = drm_fb_helper_check_var, | |
24 | .fb_set_par = drm_fb_helper_set_par, | |
25 | .fb_fillrect = cfb_fillrect, | |
26 | .fb_copyarea = cfb_copyarea, | |
27 | .fb_imageblit = cfb_imageblit, | |
28 | .fb_pan_display = drm_fb_helper_pan_display, | |
29 | .fb_blank = drm_fb_helper_blank, | |
30 | .fb_setcmap = drm_fb_helper_setcmap, | |
31 | .fb_debug_enter = drm_fb_helper_debug_enter, | |
32 | .fb_debug_leave = drm_fb_helper_debug_leave, | |
33 | }; | |
34 | ||
35 | static int armada_fb_create(struct drm_fb_helper *fbh, | |
36 | struct drm_fb_helper_surface_size *sizes) | |
37 | { | |
38 | struct drm_device *dev = fbh->dev; | |
39 | struct drm_mode_fb_cmd2 mode; | |
40 | struct armada_framebuffer *dfb; | |
41 | struct armada_gem_object *obj; | |
42 | struct fb_info *info; | |
43 | int size, ret; | |
44 | void *ptr; | |
45 | ||
46 | memset(&mode, 0, sizeof(mode)); | |
47 | mode.width = sizes->surface_width; | |
48 | mode.height = sizes->surface_height; | |
49 | mode.pitches[0] = armada_pitch(mode.width, sizes->surface_bpp); | |
50 | mode.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, | |
51 | sizes->surface_depth); | |
52 | ||
53 | size = mode.pitches[0] * mode.height; | |
54 | obj = armada_gem_alloc_private_object(dev, size); | |
55 | if (!obj) { | |
56 | DRM_ERROR("failed to allocate fb memory\n"); | |
57 | return -ENOMEM; | |
58 | } | |
59 | ||
60 | ret = armada_gem_linear_back(dev, obj); | |
61 | if (ret) { | |
62 | drm_gem_object_unreference_unlocked(&obj->obj); | |
63 | return ret; | |
64 | } | |
65 | ||
66 | ptr = armada_gem_map_object(dev, obj); | |
67 | if (!ptr) { | |
68 | drm_gem_object_unreference_unlocked(&obj->obj); | |
69 | return -ENOMEM; | |
70 | } | |
71 | ||
72 | dfb = armada_framebuffer_create(dev, &mode, obj); | |
73 | ||
74 | /* | |
75 | * A reference is now held by the framebuffer object if | |
76 | * successful, otherwise this drops the ref for the error path. | |
77 | */ | |
78 | drm_gem_object_unreference_unlocked(&obj->obj); | |
79 | ||
80 | if (IS_ERR(dfb)) | |
81 | return PTR_ERR(dfb); | |
82 | ||
83 | info = framebuffer_alloc(0, dev->dev); | |
84 | if (!info) { | |
85 | ret = -ENOMEM; | |
86 | goto err_fballoc; | |
87 | } | |
88 | ||
89 | ret = fb_alloc_cmap(&info->cmap, 256, 0); | |
90 | if (ret) { | |
91 | ret = -ENOMEM; | |
92 | goto err_fbcmap; | |
93 | } | |
94 | ||
95 | strlcpy(info->fix.id, "armada-drmfb", sizeof(info->fix.id)); | |
96 | info->par = fbh; | |
97 | info->flags = FBINFO_DEFAULT | FBINFO_CAN_FORCE_OUTPUT; | |
98 | info->fbops = &armada_fb_ops; | |
99 | info->fix.smem_start = obj->phys_addr; | |
100 | info->fix.smem_len = obj->obj.size; | |
101 | info->screen_size = obj->obj.size; | |
102 | info->screen_base = ptr; | |
103 | fbh->fb = &dfb->fb; | |
104 | fbh->fbdev = info; | |
105 | drm_fb_helper_fill_fix(info, dfb->fb.pitches[0], dfb->fb.depth); | |
106 | drm_fb_helper_fill_var(info, fbh, sizes->fb_width, sizes->fb_height); | |
107 | ||
7513e095 RK |
108 | DRM_DEBUG_KMS("allocated %dx%d %dbpp fb: 0x%08llx\n", |
109 | dfb->fb.width, dfb->fb.height, dfb->fb.bits_per_pixel, | |
110 | (unsigned long long)obj->phys_addr); | |
96f60e37 RK |
111 | |
112 | return 0; | |
113 | ||
114 | err_fbcmap: | |
115 | framebuffer_release(info); | |
116 | err_fballoc: | |
117 | dfb->fb.funcs->destroy(&dfb->fb); | |
118 | return ret; | |
119 | } | |
120 | ||
121 | static int armada_fb_probe(struct drm_fb_helper *fbh, | |
122 | struct drm_fb_helper_surface_size *sizes) | |
123 | { | |
124 | int ret = 0; | |
125 | ||
126 | if (!fbh->fb) { | |
127 | ret = armada_fb_create(fbh, sizes); | |
128 | if (ret == 0) | |
129 | ret = 1; | |
130 | } | |
131 | return ret; | |
132 | } | |
133 | ||
3a493879 | 134 | static const struct drm_fb_helper_funcs armada_fb_helper_funcs = { |
96f60e37 RK |
135 | .gamma_set = armada_drm_crtc_gamma_set, |
136 | .gamma_get = armada_drm_crtc_gamma_get, | |
137 | .fb_probe = armada_fb_probe, | |
138 | }; | |
139 | ||
140 | int armada_fbdev_init(struct drm_device *dev) | |
141 | { | |
142 | struct armada_private *priv = dev->dev_private; | |
143 | struct drm_fb_helper *fbh; | |
144 | int ret; | |
145 | ||
146 | fbh = devm_kzalloc(dev->dev, sizeof(*fbh), GFP_KERNEL); | |
147 | if (!fbh) | |
148 | return -ENOMEM; | |
149 | ||
150 | priv->fbdev = fbh; | |
151 | ||
10a23102 | 152 | drm_fb_helper_prepare(dev, fbh, &armada_fb_helper_funcs); |
96f60e37 RK |
153 | |
154 | ret = drm_fb_helper_init(dev, fbh, 1, 1); | |
155 | if (ret) { | |
156 | DRM_ERROR("failed to initialize drm fb helper\n"); | |
157 | goto err_fb_helper; | |
158 | } | |
159 | ||
160 | ret = drm_fb_helper_single_add_all_connectors(fbh); | |
161 | if (ret) { | |
162 | DRM_ERROR("failed to add fb connectors\n"); | |
163 | goto err_fb_setup; | |
164 | } | |
165 | ||
166 | ret = drm_fb_helper_initial_config(fbh, 32); | |
167 | if (ret) { | |
168 | DRM_ERROR("failed to set initial config\n"); | |
169 | goto err_fb_setup; | |
170 | } | |
171 | ||
172 | return 0; | |
173 | err_fb_setup: | |
174 | drm_fb_helper_fini(fbh); | |
175 | err_fb_helper: | |
176 | priv->fbdev = NULL; | |
177 | return ret; | |
178 | } | |
179 | ||
2f5ae490 RK |
180 | void armada_fbdev_lastclose(struct drm_device *dev) |
181 | { | |
182 | struct armada_private *priv = dev->dev_private; | |
183 | ||
2f5ae490 | 184 | if (priv->fbdev) |
5ea1f752 | 185 | drm_fb_helper_restore_fbdev_mode_unlocked(priv->fbdev); |
2f5ae490 RK |
186 | } |
187 | ||
96f60e37 RK |
188 | void armada_fbdev_fini(struct drm_device *dev) |
189 | { | |
190 | struct armada_private *priv = dev->dev_private; | |
191 | struct drm_fb_helper *fbh = priv->fbdev; | |
192 | ||
193 | if (fbh) { | |
194 | struct fb_info *info = fbh->fbdev; | |
195 | ||
196 | if (info) { | |
197 | unregister_framebuffer(info); | |
198 | if (info->cmap.len) | |
199 | fb_dealloc_cmap(&info->cmap); | |
200 | framebuffer_release(info); | |
201 | } | |
202 | ||
077acbab RK |
203 | drm_fb_helper_fini(fbh); |
204 | ||
96f60e37 RK |
205 | if (fbh->fb) |
206 | fbh->fb->funcs->destroy(fbh->fb); | |
207 | ||
96f60e37 RK |
208 | priv->fbdev = NULL; |
209 | } | |
210 | } |