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