Commit | Line | Data |
---|---|---|
b79fe9ab TZ |
1 | // SPDX-License-Identifier: MIT |
2 | ||
859cc65f TZ |
3 | #include <linux/fb.h> |
4 | ||
b79fe9ab TZ |
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(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(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(fb_helper); | |
46 | ||
47 | drm_client_buffer_vunmap(fb_helper->buffer); | |
48 | drm_client_framebuffer_delete(fb_helper->buffer); | |
49 | drm_client_release(&fb_helper->client); | |
50 | drm_fb_helper_unprepare(fb_helper); | |
51 | kfree(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; | |
b79fe9ab | 57 | |
0adec227 | 58 | return drm_gem_prime_mmap(fb_helper->buffer->gem, vma); |
b79fe9ab TZ |
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, | |
b21f187f | 65 | __FB_DEFAULT_DMAMEM_OPS_RDWR, |
b79fe9ab | 66 | DRM_FB_HELPER_DEFAULT_OPS, |
b21f187f | 67 | __FB_DEFAULT_DMAMEM_OPS_DRAW, |
b79fe9ab | 68 | .fb_mmap = drm_fbdev_dma_fb_mmap, |
859cc65f | 69 | .fb_destroy = drm_fbdev_dma_fb_destroy, |
b79fe9ab TZ |
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(sizes->surface_bpp, sizes->surface_depth); | |
94 | buffer = drm_client_framebuffer_create(client, sizes->surface_width, | |
95 | sizes->surface_height, format); | |
96 | if (IS_ERR(buffer)) | |
97 | return PTR_ERR(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); | |
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(info)) { | |
119 | ret = PTR_ERR(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; | |
b79fe9ab TZ |
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; | |
a51c7663 | 133 | info->fix.smem_start = page_to_phys(virt_to_page(info->screen_buffer)); |
b79fe9ab TZ |
134 | info->fix.smem_len = info->screen_size; |
135 | ||
b79fe9ab TZ |
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(&fb_helper->client); | |
163 | drm_fb_helper_unprepare(fb_helper); | |
164 | kfree(fb_helper); | |
165 | } | |
166 | } | |
167 | ||
168 | static int drm_fbdev_dma_client_restore(struct drm_client_dev *client) | |
169 | { | |
170 | drm_fb_helper_lastclose(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(dev->fb_helper); | |
183 | ||
184 | ret = drm_fb_helper_init(dev, 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(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. | |
15008052 | 215 | * 32 is used if this is zero. |
b79fe9ab TZ |
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(sizeof(*fb_helper), GFP_KERNEL); | |
240 | if (!fb_helper) | |
241 | return; | |
242 | drm_fb_helper_prepare(dev, fb_helper, preferred_bpp, &drm_fbdev_dma_helper_funcs); | |
243 | ||
244 | ret = drm_client_init(dev, &fb_helper->client, "fbdev", &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 | ||
b79fe9ab TZ |
250 | drm_client_register(&fb_helper->client); |
251 | ||
252 | return; | |
253 | ||
254 | err_drm_client_init: | |
255 | drm_fb_helper_unprepare(fb_helper); | |
256 | kfree(fb_helper); | |
257 | } | |
258 | EXPORT_SYMBOL(drm_fbdev_dma_setup); |