1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * Copyright (C) 2012 Russell King |
4 | * Written from the i915 driver. |
5 | */ |
6 | |
7 | #include <linux/errno.h> |
8 | #include <linux/fb.h> |
9 | #include <linux/kernel.h> |
10 | #include <linux/module.h> |
11 | |
12 | #include <drm/drm_crtc_helper.h> |
13 | #include <drm/drm_drv.h> |
14 | #include <drm/drm_fb_helper.h> |
15 | #include <drm/drm_fourcc.h> |
16 | |
17 | #include "armada_crtc.h" |
18 | #include "armada_drm.h" |
19 | #include "armada_fb.h" |
20 | #include "armada_gem.h" |
21 | |
22 | static void armada_fbdev_fb_destroy(struct fb_info *info) |
23 | { |
24 | struct drm_fb_helper *fbh = info->par; |
25 | |
26 | drm_fb_helper_fini(helper: fbh); |
27 | |
28 | fbh->fb->funcs->destroy(fbh->fb); |
29 | |
30 | drm_client_release(client: &fbh->client); |
31 | drm_fb_helper_unprepare(fb_helper: fbh); |
32 | kfree(objp: fbh); |
33 | } |
34 | |
35 | static const struct fb_ops armada_fb_ops = { |
36 | .owner = THIS_MODULE, |
37 | FB_DEFAULT_IOMEM_OPS, |
38 | DRM_FB_HELPER_DEFAULT_OPS, |
39 | .fb_destroy = armada_fbdev_fb_destroy, |
40 | }; |
41 | |
42 | static int armada_fbdev_create(struct drm_fb_helper *fbh, |
43 | struct drm_fb_helper_surface_size *sizes) |
44 | { |
45 | struct drm_device *dev = fbh->dev; |
46 | struct drm_mode_fb_cmd2 mode; |
47 | struct armada_framebuffer *dfb; |
48 | struct armada_gem_object *obj; |
49 | struct fb_info *info; |
50 | int size, ret; |
51 | void *ptr; |
52 | |
53 | memset(&mode, 0, sizeof(mode)); |
54 | mode.width = sizes->surface_width; |
55 | mode.height = sizes->surface_height; |
56 | mode.pitches[0] = armada_pitch(width: mode.width, bpp: sizes->surface_bpp); |
57 | mode.pixel_format = drm_mode_legacy_fb_format(bpp: sizes->surface_bpp, |
58 | depth: sizes->surface_depth); |
59 | |
60 | size = mode.pitches[0] * mode.height; |
61 | obj = armada_gem_alloc_private_object(dev, size); |
62 | if (!obj) { |
63 | DRM_ERROR("failed to allocate fb memory\n" ); |
64 | return -ENOMEM; |
65 | } |
66 | |
67 | ret = armada_gem_linear_back(dev, obj); |
68 | if (ret) { |
69 | drm_gem_object_put(obj: &obj->obj); |
70 | return ret; |
71 | } |
72 | |
73 | ptr = armada_gem_map_object(dev, obj); |
74 | if (!ptr) { |
75 | drm_gem_object_put(obj: &obj->obj); |
76 | return -ENOMEM; |
77 | } |
78 | |
79 | dfb = armada_framebuffer_create(dev, &mode, obj); |
80 | |
81 | /* |
82 | * A reference is now held by the framebuffer object if |
83 | * successful, otherwise this drops the ref for the error path. |
84 | */ |
85 | drm_gem_object_put(obj: &obj->obj); |
86 | |
87 | if (IS_ERR(ptr: dfb)) |
88 | return PTR_ERR(ptr: dfb); |
89 | |
90 | info = drm_fb_helper_alloc_info(fb_helper: fbh); |
91 | if (IS_ERR(ptr: info)) { |
92 | ret = PTR_ERR(ptr: info); |
93 | goto err_fballoc; |
94 | } |
95 | |
96 | info->fbops = &armada_fb_ops; |
97 | info->fix.smem_start = obj->phys_addr; |
98 | info->fix.smem_len = obj->obj.size; |
99 | info->screen_size = obj->obj.size; |
100 | info->screen_base = ptr; |
101 | fbh->fb = &dfb->fb; |
102 | |
103 | drm_fb_helper_fill_info(info, fb_helper: fbh, sizes); |
104 | |
105 | DRM_DEBUG_KMS("allocated %dx%d %dbpp fb: 0x%08llx\n" , |
106 | dfb->fb.width, dfb->fb.height, dfb->fb.format->cpp[0] * 8, |
107 | (unsigned long long)obj->phys_addr); |
108 | |
109 | return 0; |
110 | |
111 | err_fballoc: |
112 | dfb->fb.funcs->destroy(&dfb->fb); |
113 | return ret; |
114 | } |
115 | |
116 | static int armada_fb_probe(struct drm_fb_helper *fbh, |
117 | struct drm_fb_helper_surface_size *sizes) |
118 | { |
119 | int ret = 0; |
120 | |
121 | if (!fbh->fb) { |
122 | ret = armada_fbdev_create(fbh, sizes); |
123 | if (ret == 0) |
124 | ret = 1; |
125 | } |
126 | return ret; |
127 | } |
128 | |
129 | static const struct drm_fb_helper_funcs armada_fb_helper_funcs = { |
130 | .fb_probe = armada_fb_probe, |
131 | }; |
132 | |
133 | /* |
134 | * Fbdev client and struct drm_client_funcs |
135 | */ |
136 | |
137 | static void armada_fbdev_client_unregister(struct drm_client_dev *client) |
138 | { |
139 | struct drm_fb_helper *fbh = drm_fb_helper_from_client(client); |
140 | |
141 | if (fbh->info) { |
142 | drm_fb_helper_unregister_info(fb_helper: fbh); |
143 | } else { |
144 | drm_client_release(client: &fbh->client); |
145 | drm_fb_helper_unprepare(fb_helper: fbh); |
146 | kfree(objp: fbh); |
147 | } |
148 | } |
149 | |
150 | static int armada_fbdev_client_restore(struct drm_client_dev *client) |
151 | { |
152 | drm_fb_helper_lastclose(dev: client->dev); |
153 | |
154 | return 0; |
155 | } |
156 | |
157 | static int armada_fbdev_client_hotplug(struct drm_client_dev *client) |
158 | { |
159 | struct drm_fb_helper *fbh = drm_fb_helper_from_client(client); |
160 | struct drm_device *dev = client->dev; |
161 | int ret; |
162 | |
163 | if (dev->fb_helper) |
164 | return drm_fb_helper_hotplug_event(fb_helper: dev->fb_helper); |
165 | |
166 | ret = drm_fb_helper_init(dev, helper: fbh); |
167 | if (ret) |
168 | goto err_drm_err; |
169 | |
170 | if (!drm_drv_uses_atomic_modeset(dev)) |
171 | drm_helper_disable_unused_functions(dev); |
172 | |
173 | ret = drm_fb_helper_initial_config(fb_helper: fbh); |
174 | if (ret) |
175 | goto err_drm_fb_helper_fini; |
176 | |
177 | return 0; |
178 | |
179 | err_drm_fb_helper_fini: |
180 | drm_fb_helper_fini(helper: fbh); |
181 | err_drm_err: |
182 | drm_err(dev, "armada: Failed to setup fbdev emulation (ret=%d)\n" , ret); |
183 | return ret; |
184 | } |
185 | |
186 | static const struct drm_client_funcs armada_fbdev_client_funcs = { |
187 | .owner = THIS_MODULE, |
188 | .unregister = armada_fbdev_client_unregister, |
189 | .restore = armada_fbdev_client_restore, |
190 | .hotplug = armada_fbdev_client_hotplug, |
191 | }; |
192 | |
193 | void armada_fbdev_setup(struct drm_device *dev) |
194 | { |
195 | struct drm_fb_helper *fbh; |
196 | int ret; |
197 | |
198 | drm_WARN(dev, !dev->registered, "Device has not been registered.\n" ); |
199 | drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n" ); |
200 | |
201 | fbh = kzalloc(size: sizeof(*fbh), GFP_KERNEL); |
202 | if (!fbh) |
203 | return; |
204 | drm_fb_helper_prepare(dev, helper: fbh, preferred_bpp: 32, funcs: &armada_fb_helper_funcs); |
205 | |
206 | ret = drm_client_init(dev, client: &fbh->client, name: "fbdev" , funcs: &armada_fbdev_client_funcs); |
207 | if (ret) { |
208 | drm_err(dev, "Failed to register client: %d\n" , ret); |
209 | goto err_drm_client_init; |
210 | } |
211 | |
212 | drm_client_register(client: &fbh->client); |
213 | |
214 | return; |
215 | |
216 | err_drm_client_init: |
217 | drm_fb_helper_unprepare(fb_helper: fbh); |
218 | kfree(objp: fbh); |
219 | return; |
220 | } |
221 | |