1 | // SPDX-License-Identifier: MIT |
2 | |
3 | #include <linux/fb.h> |
4 | |
5 | #include <drm/drm_crtc_helper.h> |
6 | #include <drm/drm_drv.h> |
7 | #include <drm/drm_fb_helper.h> |
8 | #include <drm/drm_framebuffer.h> |
9 | #include <drm/drm_gem_dma_helper.h> |
10 | |
11 | #include <drm/drm_fbdev_dma.h> |
12 | |
13 | /* |
14 | * struct fb_ops |
15 | */ |
16 | |
17 | static int drm_fbdev_dma_fb_open(struct fb_info *info, int user) |
18 | { |
19 | struct drm_fb_helper *fb_helper = info->par; |
20 | |
21 | /* No need to take a ref for fbcon because it unbinds on unregister */ |
22 | if (user && !try_module_get(module: fb_helper->dev->driver->fops->owner)) |
23 | return -ENODEV; |
24 | |
25 | return 0; |
26 | } |
27 | |
28 | static int drm_fbdev_dma_fb_release(struct fb_info *info, int user) |
29 | { |
30 | struct drm_fb_helper *fb_helper = info->par; |
31 | |
32 | if (user) |
33 | module_put(module: fb_helper->dev->driver->fops->owner); |
34 | |
35 | return 0; |
36 | } |
37 | |
38 | static void drm_fbdev_dma_fb_destroy(struct fb_info *info) |
39 | { |
40 | struct drm_fb_helper *fb_helper = info->par; |
41 | |
42 | if (!fb_helper->dev) |
43 | return; |
44 | |
45 | drm_fb_helper_fini(helper: fb_helper); |
46 | |
47 | drm_client_buffer_vunmap(buffer: fb_helper->buffer); |
48 | drm_client_framebuffer_delete(buffer: fb_helper->buffer); |
49 | drm_client_release(client: &fb_helper->client); |
50 | drm_fb_helper_unprepare(fb_helper); |
51 | kfree(objp: fb_helper); |
52 | } |
53 | |
54 | static int drm_fbdev_dma_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) |
55 | { |
56 | struct drm_fb_helper *fb_helper = info->par; |
57 | |
58 | return drm_gem_prime_mmap(obj: fb_helper->buffer->gem, vma); |
59 | } |
60 | |
61 | static const struct fb_ops drm_fbdev_dma_fb_ops = { |
62 | .owner = THIS_MODULE, |
63 | .fb_open = drm_fbdev_dma_fb_open, |
64 | .fb_release = drm_fbdev_dma_fb_release, |
65 | __FB_DEFAULT_DMAMEM_OPS_RDWR, |
66 | DRM_FB_HELPER_DEFAULT_OPS, |
67 | __FB_DEFAULT_DMAMEM_OPS_DRAW, |
68 | .fb_mmap = drm_fbdev_dma_fb_mmap, |
69 | .fb_destroy = drm_fbdev_dma_fb_destroy, |
70 | }; |
71 | |
72 | /* |
73 | * struct drm_fb_helper |
74 | */ |
75 | |
76 | static int drm_fbdev_dma_helper_fb_probe(struct drm_fb_helper *fb_helper, |
77 | struct drm_fb_helper_surface_size *sizes) |
78 | { |
79 | struct drm_client_dev *client = &fb_helper->client; |
80 | struct drm_device *dev = fb_helper->dev; |
81 | struct drm_client_buffer *buffer; |
82 | struct drm_gem_dma_object *dma_obj; |
83 | struct drm_framebuffer *fb; |
84 | struct fb_info *info; |
85 | u32 format; |
86 | struct iosys_map map; |
87 | int ret; |
88 | |
89 | drm_dbg_kms(dev, "surface width(%d), height(%d) and bpp(%d)\n" , |
90 | sizes->surface_width, sizes->surface_height, |
91 | sizes->surface_bpp); |
92 | |
93 | format = drm_mode_legacy_fb_format(bpp: sizes->surface_bpp, depth: sizes->surface_depth); |
94 | buffer = drm_client_framebuffer_create(client, width: sizes->surface_width, |
95 | height: sizes->surface_height, format); |
96 | if (IS_ERR(ptr: buffer)) |
97 | return PTR_ERR(ptr: buffer); |
98 | dma_obj = to_drm_gem_dma_obj(buffer->gem); |
99 | |
100 | fb = buffer->fb; |
101 | if (drm_WARN_ON(dev, fb->funcs->dirty)) { |
102 | ret = -ENODEV; /* damage handling not supported; use generic emulation */ |
103 | goto err_drm_client_buffer_delete; |
104 | } |
105 | |
106 | ret = drm_client_buffer_vmap(buffer, map: &map); |
107 | if (ret) { |
108 | goto err_drm_client_buffer_delete; |
109 | } else if (drm_WARN_ON(dev, map.is_iomem)) { |
110 | ret = -ENODEV; /* I/O memory not supported; use generic emulation */ |
111 | goto err_drm_client_buffer_delete; |
112 | } |
113 | |
114 | fb_helper->buffer = buffer; |
115 | fb_helper->fb = buffer->fb; |
116 | |
117 | info = drm_fb_helper_alloc_info(fb_helper); |
118 | if (IS_ERR(ptr: info)) { |
119 | ret = PTR_ERR(ptr: info); |
120 | goto err_drm_client_buffer_vunmap; |
121 | } |
122 | |
123 | drm_fb_helper_fill_info(info, fb_helper, sizes); |
124 | |
125 | info->fbops = &drm_fbdev_dma_fb_ops; |
126 | |
127 | /* screen */ |
128 | info->flags |= FBINFO_VIRTFB; /* system memory */ |
129 | if (dma_obj->map_noncoherent) |
130 | info->flags |= FBINFO_READS_FAST; /* signal caching */ |
131 | info->screen_size = sizes->surface_height * fb->pitches[0]; |
132 | info->screen_buffer = map.vaddr; |
133 | info->fix.smem_start = page_to_phys(virt_to_page(info->screen_buffer)); |
134 | info->fix.smem_len = info->screen_size; |
135 | |
136 | return 0; |
137 | |
138 | err_drm_client_buffer_vunmap: |
139 | fb_helper->fb = NULL; |
140 | fb_helper->buffer = NULL; |
141 | drm_client_buffer_vunmap(buffer); |
142 | err_drm_client_buffer_delete: |
143 | drm_client_framebuffer_delete(buffer); |
144 | return ret; |
145 | } |
146 | |
147 | static const struct drm_fb_helper_funcs drm_fbdev_dma_helper_funcs = { |
148 | .fb_probe = drm_fbdev_dma_helper_fb_probe, |
149 | }; |
150 | |
151 | /* |
152 | * struct drm_client_funcs |
153 | */ |
154 | |
155 | static void drm_fbdev_dma_client_unregister(struct drm_client_dev *client) |
156 | { |
157 | struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); |
158 | |
159 | if (fb_helper->info) { |
160 | drm_fb_helper_unregister_info(fb_helper); |
161 | } else { |
162 | drm_client_release(client: &fb_helper->client); |
163 | drm_fb_helper_unprepare(fb_helper); |
164 | kfree(objp: fb_helper); |
165 | } |
166 | } |
167 | |
168 | static int drm_fbdev_dma_client_restore(struct drm_client_dev *client) |
169 | { |
170 | drm_fb_helper_lastclose(dev: client->dev); |
171 | |
172 | return 0; |
173 | } |
174 | |
175 | static int drm_fbdev_dma_client_hotplug(struct drm_client_dev *client) |
176 | { |
177 | struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); |
178 | struct drm_device *dev = client->dev; |
179 | int ret; |
180 | |
181 | if (dev->fb_helper) |
182 | return drm_fb_helper_hotplug_event(fb_helper: dev->fb_helper); |
183 | |
184 | ret = drm_fb_helper_init(dev, helper: fb_helper); |
185 | if (ret) |
186 | goto err_drm_err; |
187 | |
188 | if (!drm_drv_uses_atomic_modeset(dev)) |
189 | drm_helper_disable_unused_functions(dev); |
190 | |
191 | ret = drm_fb_helper_initial_config(fb_helper); |
192 | if (ret) |
193 | goto err_drm_fb_helper_fini; |
194 | |
195 | return 0; |
196 | |
197 | err_drm_fb_helper_fini: |
198 | drm_fb_helper_fini(helper: fb_helper); |
199 | err_drm_err: |
200 | drm_err(dev, "fbdev-dma: Failed to setup generic emulation (ret=%d)\n" , ret); |
201 | return ret; |
202 | } |
203 | |
204 | static const struct drm_client_funcs drm_fbdev_dma_client_funcs = { |
205 | .owner = THIS_MODULE, |
206 | .unregister = drm_fbdev_dma_client_unregister, |
207 | .restore = drm_fbdev_dma_client_restore, |
208 | .hotplug = drm_fbdev_dma_client_hotplug, |
209 | }; |
210 | |
211 | /** |
212 | * drm_fbdev_dma_setup() - Setup fbdev emulation for GEM DMA helpers |
213 | * @dev: DRM device |
214 | * @preferred_bpp: Preferred bits per pixel for the device. |
215 | * 32 is used if this is zero. |
216 | * |
217 | * This function sets up fbdev emulation for GEM DMA drivers that support |
218 | * dumb buffers with a virtual address and that can be mmap'ed. |
219 | * drm_fbdev_dma_setup() shall be called after the DRM driver registered |
220 | * the new DRM device with drm_dev_register(). |
221 | * |
222 | * Restore, hotplug events and teardown are all taken care of. Drivers that do |
223 | * suspend/resume need to call drm_fb_helper_set_suspend_unlocked() themselves. |
224 | * Simple drivers might use drm_mode_config_helper_suspend(). |
225 | * |
226 | * This function is safe to call even when there are no connectors present. |
227 | * Setup will be retried on the next hotplug event. |
228 | * |
229 | * The fbdev is destroyed by drm_dev_unregister(). |
230 | */ |
231 | void drm_fbdev_dma_setup(struct drm_device *dev, unsigned int preferred_bpp) |
232 | { |
233 | struct drm_fb_helper *fb_helper; |
234 | int ret; |
235 | |
236 | drm_WARN(dev, !dev->registered, "Device has not been registered.\n" ); |
237 | drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n" ); |
238 | |
239 | fb_helper = kzalloc(size: sizeof(*fb_helper), GFP_KERNEL); |
240 | if (!fb_helper) |
241 | return; |
242 | drm_fb_helper_prepare(dev, helper: fb_helper, preferred_bpp, funcs: &drm_fbdev_dma_helper_funcs); |
243 | |
244 | ret = drm_client_init(dev, client: &fb_helper->client, name: "fbdev" , funcs: &drm_fbdev_dma_client_funcs); |
245 | if (ret) { |
246 | drm_err(dev, "Failed to register client: %d\n" , ret); |
247 | goto err_drm_client_init; |
248 | } |
249 | |
250 | drm_client_register(client: &fb_helper->client); |
251 | |
252 | return; |
253 | |
254 | err_drm_client_init: |
255 | drm_fb_helper_unprepare(fb_helper); |
256 | kfree(objp: fb_helper); |
257 | } |
258 | EXPORT_SYMBOL(drm_fbdev_dma_setup); |
259 | |