GNU Linux-libre 4.14.324-gnu1
[releases.git] / drivers / gpu / drm / armada / armada_fbdev.c
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/kernel.h>
11 #include <linux/module.h>
12
13 #include <drm/drmP.h>
14 #include <drm/drm_fb_helper.h>
15 #include "armada_crtc.h"
16 #include "armada_drm.h"
17 #include "armada_fb.h"
18 #include "armada_gem.h"
19
20 static /*const*/ struct fb_ops armada_fb_ops = {
21         .owner          = THIS_MODULE,
22         DRM_FB_HELPER_DEFAULT_OPS,
23         .fb_fillrect    = drm_fb_helper_cfb_fillrect,
24         .fb_copyarea    = drm_fb_helper_cfb_copyarea,
25         .fb_imageblit   = drm_fb_helper_cfb_imageblit,
26 };
27
28 static int armada_fb_create(struct drm_fb_helper *fbh,
29         struct drm_fb_helper_surface_size *sizes)
30 {
31         struct drm_device *dev = fbh->dev;
32         struct drm_mode_fb_cmd2 mode;
33         struct armada_framebuffer *dfb;
34         struct armada_gem_object *obj;
35         struct fb_info *info;
36         int size, ret;
37         void *ptr;
38
39         memset(&mode, 0, sizeof(mode));
40         mode.width = sizes->surface_width;
41         mode.height = sizes->surface_height;
42         mode.pitches[0] = armada_pitch(mode.width, sizes->surface_bpp);
43         mode.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp,
44                                         sizes->surface_depth);
45
46         size = mode.pitches[0] * mode.height;
47         obj = armada_gem_alloc_private_object(dev, size);
48         if (!obj) {
49                 DRM_ERROR("failed to allocate fb memory\n");
50                 return -ENOMEM;
51         }
52
53         ret = armada_gem_linear_back(dev, obj);
54         if (ret) {
55                 drm_gem_object_unreference_unlocked(&obj->obj);
56                 return ret;
57         }
58
59         ptr = armada_gem_map_object(dev, obj);
60         if (!ptr) {
61                 drm_gem_object_unreference_unlocked(&obj->obj);
62                 return -ENOMEM;
63         }
64
65         dfb = armada_framebuffer_create(dev, &mode, obj);
66
67         /*
68          * A reference is now held by the framebuffer object if
69          * successful, otherwise this drops the ref for the error path.
70          */
71         drm_gem_object_unreference_unlocked(&obj->obj);
72
73         if (IS_ERR(dfb))
74                 return PTR_ERR(dfb);
75
76         info = drm_fb_helper_alloc_fbi(fbh);
77         if (IS_ERR(info)) {
78                 ret = PTR_ERR(info);
79                 goto err_fballoc;
80         }
81
82         strlcpy(info->fix.id, "armada-drmfb", sizeof(info->fix.id));
83         info->par = fbh;
84         info->fbops = &armada_fb_ops;
85         info->fix.smem_start = obj->phys_addr;
86         info->fix.smem_len = obj->obj.size;
87         info->screen_size = obj->obj.size;
88         info->screen_base = ptr;
89         fbh->fb = &dfb->fb;
90
91         drm_fb_helper_fill_fix(info, dfb->fb.pitches[0],
92                                dfb->fb.format->depth);
93         drm_fb_helper_fill_var(info, fbh, sizes->fb_width, sizes->fb_height);
94
95         DRM_DEBUG_KMS("allocated %dx%d %dbpp fb: 0x%08llx\n",
96                 dfb->fb.width, dfb->fb.height, dfb->fb.format->cpp[0] * 8,
97                 (unsigned long long)obj->phys_addr);
98
99         return 0;
100
101  err_fballoc:
102         dfb->fb.funcs->destroy(&dfb->fb);
103         return ret;
104 }
105
106 static int armada_fb_probe(struct drm_fb_helper *fbh,
107         struct drm_fb_helper_surface_size *sizes)
108 {
109         int ret = 0;
110
111         if (!fbh->fb) {
112                 ret = armada_fb_create(fbh, sizes);
113                 if (ret == 0)
114                         ret = 1;
115         }
116         return ret;
117 }
118
119 static const struct drm_fb_helper_funcs armada_fb_helper_funcs = {
120         .fb_probe       = armada_fb_probe,
121 };
122
123 int armada_fbdev_init(struct drm_device *dev)
124 {
125         struct armada_private *priv = dev->dev_private;
126         struct drm_fb_helper *fbh;
127         int ret;
128
129         fbh = devm_kzalloc(dev->dev, sizeof(*fbh), GFP_KERNEL);
130         if (!fbh)
131                 return -ENOMEM;
132
133         priv->fbdev = fbh;
134
135         drm_fb_helper_prepare(dev, fbh, &armada_fb_helper_funcs);
136
137         ret = drm_fb_helper_init(dev, fbh, 1);
138         if (ret) {
139                 DRM_ERROR("failed to initialize drm fb helper\n");
140                 goto err_fb_helper;
141         }
142
143         ret = drm_fb_helper_single_add_all_connectors(fbh);
144         if (ret) {
145                 DRM_ERROR("failed to add fb connectors\n");
146                 goto err_fb_setup;
147         }
148
149         ret = drm_fb_helper_initial_config(fbh, 32);
150         if (ret) {
151                 DRM_ERROR("failed to set initial config\n");
152                 goto err_fb_setup;
153         }
154
155         return 0;
156  err_fb_setup:
157         drm_fb_helper_fini(fbh);
158  err_fb_helper:
159         priv->fbdev = NULL;
160         return ret;
161 }
162
163 void armada_fbdev_lastclose(struct drm_device *dev)
164 {
165         struct armada_private *priv = dev->dev_private;
166
167         if (priv->fbdev)
168                 drm_fb_helper_restore_fbdev_mode_unlocked(priv->fbdev);
169 }
170
171 void armada_fbdev_fini(struct drm_device *dev)
172 {
173         struct armada_private *priv = dev->dev_private;
174         struct drm_fb_helper *fbh = priv->fbdev;
175
176         if (fbh) {
177                 drm_fb_helper_unregister_fbi(fbh);
178
179                 drm_fb_helper_fini(fbh);
180
181                 if (fbh->fb)
182                         fbh->fb->funcs->destroy(fbh->fb);
183
184                 priv->fbdev = NULL;
185         }
186 }