1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* exynos_drm_fbdev.c |
3 | * |
4 | * Copyright (c) 2011 Samsung Electronics Co., Ltd. |
5 | * Authors: |
6 | * Inki Dae <inki.dae@samsung.com> |
7 | * Joonyoung Shim <jy0922.shim@samsung.com> |
8 | * Seung-Woo Kim <sw0312.kim@samsung.com> |
9 | */ |
10 | |
11 | #include <linux/fb.h> |
12 | |
13 | #include <drm/drm_crtc_helper.h> |
14 | #include <drm/drm_drv.h> |
15 | #include <drm/drm_fb_helper.h> |
16 | #include <drm/drm_framebuffer.h> |
17 | #include <drm/drm_gem_framebuffer_helper.h> |
18 | #include <drm/drm_prime.h> |
19 | #include <drm/exynos_drm.h> |
20 | |
21 | #include "exynos_drm_drv.h" |
22 | #include "exynos_drm_fb.h" |
23 | #include "exynos_drm_fbdev.h" |
24 | |
25 | #define MAX_CONNECTOR 4 |
26 | #define PREFERRED_BPP 32 |
27 | |
28 | static int exynos_drm_fb_mmap(struct fb_info *info, struct vm_area_struct *vma) |
29 | { |
30 | struct drm_fb_helper *helper = info->par; |
31 | struct drm_gem_object *obj = drm_gem_fb_get_obj(fb: helper->fb, plane: 0); |
32 | |
33 | return drm_gem_prime_mmap(obj, vma); |
34 | } |
35 | |
36 | static void exynos_drm_fb_destroy(struct fb_info *info) |
37 | { |
38 | struct drm_fb_helper *fb_helper = info->par; |
39 | struct drm_framebuffer *fb = fb_helper->fb; |
40 | |
41 | drm_fb_helper_fini(helper: fb_helper); |
42 | |
43 | drm_framebuffer_remove(fb); |
44 | |
45 | drm_client_release(client: &fb_helper->client); |
46 | drm_fb_helper_unprepare(fb_helper); |
47 | kfree(objp: fb_helper); |
48 | } |
49 | |
50 | static const struct fb_ops exynos_drm_fb_ops = { |
51 | .owner = THIS_MODULE, |
52 | __FB_DEFAULT_DMAMEM_OPS_RDWR, |
53 | DRM_FB_HELPER_DEFAULT_OPS, |
54 | __FB_DEFAULT_DMAMEM_OPS_DRAW, |
55 | .fb_mmap = exynos_drm_fb_mmap, |
56 | .fb_destroy = exynos_drm_fb_destroy, |
57 | }; |
58 | |
59 | static int exynos_drm_fbdev_update(struct drm_fb_helper *helper, |
60 | struct drm_fb_helper_surface_size *sizes, |
61 | struct exynos_drm_gem *exynos_gem) |
62 | { |
63 | struct fb_info *fbi; |
64 | struct drm_framebuffer *fb = helper->fb; |
65 | unsigned int size = fb->width * fb->height * fb->format->cpp[0]; |
66 | unsigned long offset; |
67 | |
68 | fbi = drm_fb_helper_alloc_info(fb_helper: helper); |
69 | if (IS_ERR(ptr: fbi)) { |
70 | DRM_DEV_ERROR(to_dma_dev(helper->dev), |
71 | "failed to allocate fb info.\n" ); |
72 | return PTR_ERR(ptr: fbi); |
73 | } |
74 | |
75 | fbi->fbops = &exynos_drm_fb_ops; |
76 | |
77 | drm_fb_helper_fill_info(info: fbi, fb_helper: helper, sizes); |
78 | |
79 | offset = fbi->var.xoffset * fb->format->cpp[0]; |
80 | offset += fbi->var.yoffset * fb->pitches[0]; |
81 | |
82 | fbi->flags |= FBINFO_VIRTFB; |
83 | fbi->screen_buffer = exynos_gem->kvaddr + offset; |
84 | fbi->screen_size = size; |
85 | fbi->fix.smem_len = size; |
86 | |
87 | return 0; |
88 | } |
89 | |
90 | static int exynos_drm_fbdev_create(struct drm_fb_helper *helper, |
91 | struct drm_fb_helper_surface_size *sizes) |
92 | { |
93 | struct exynos_drm_gem *exynos_gem; |
94 | struct drm_device *dev = helper->dev; |
95 | struct drm_mode_fb_cmd2 mode_cmd = { 0 }; |
96 | unsigned long size; |
97 | int ret; |
98 | |
99 | DRM_DEV_DEBUG_KMS(dev->dev, |
100 | "surface width(%d), height(%d) and bpp(%d\n" , |
101 | sizes->surface_width, sizes->surface_height, |
102 | sizes->surface_bpp); |
103 | |
104 | mode_cmd.width = sizes->surface_width; |
105 | mode_cmd.height = sizes->surface_height; |
106 | mode_cmd.pitches[0] = sizes->surface_width * (sizes->surface_bpp >> 3); |
107 | mode_cmd.pixel_format = drm_mode_legacy_fb_format(bpp: sizes->surface_bpp, |
108 | depth: sizes->surface_depth); |
109 | |
110 | size = mode_cmd.pitches[0] * mode_cmd.height; |
111 | |
112 | exynos_gem = exynos_drm_gem_create(dev, flags: EXYNOS_BO_WC, size, kvmap: true); |
113 | if (IS_ERR(ptr: exynos_gem)) |
114 | return PTR_ERR(ptr: exynos_gem); |
115 | |
116 | helper->fb = |
117 | exynos_drm_framebuffer_init(dev, mode_cmd: &mode_cmd, exynos_gem: &exynos_gem, count: 1); |
118 | if (IS_ERR(ptr: helper->fb)) { |
119 | DRM_DEV_ERROR(dev->dev, "failed to create drm framebuffer.\n" ); |
120 | ret = PTR_ERR(ptr: helper->fb); |
121 | goto err_destroy_gem; |
122 | } |
123 | |
124 | ret = exynos_drm_fbdev_update(helper, sizes, exynos_gem); |
125 | if (ret < 0) |
126 | goto err_destroy_framebuffer; |
127 | |
128 | return 0; |
129 | |
130 | err_destroy_framebuffer: |
131 | drm_framebuffer_cleanup(fb: helper->fb); |
132 | helper->fb = NULL; |
133 | err_destroy_gem: |
134 | exynos_drm_gem_destroy(exynos_gem); |
135 | return ret; |
136 | } |
137 | |
138 | static const struct drm_fb_helper_funcs exynos_drm_fb_helper_funcs = { |
139 | .fb_probe = exynos_drm_fbdev_create, |
140 | }; |
141 | |
142 | /* |
143 | * struct drm_client |
144 | */ |
145 | |
146 | static void exynos_drm_fbdev_client_unregister(struct drm_client_dev *client) |
147 | { |
148 | struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); |
149 | |
150 | if (fb_helper->info) { |
151 | drm_fb_helper_unregister_info(fb_helper); |
152 | } else { |
153 | drm_client_release(client: &fb_helper->client); |
154 | drm_fb_helper_unprepare(fb_helper); |
155 | kfree(objp: fb_helper); |
156 | } |
157 | } |
158 | |
159 | static int exynos_drm_fbdev_client_restore(struct drm_client_dev *client) |
160 | { |
161 | drm_fb_helper_lastclose(dev: client->dev); |
162 | |
163 | return 0; |
164 | } |
165 | |
166 | static int exynos_drm_fbdev_client_hotplug(struct drm_client_dev *client) |
167 | { |
168 | struct drm_fb_helper *fb_helper = drm_fb_helper_from_client(client); |
169 | struct drm_device *dev = client->dev; |
170 | int ret; |
171 | |
172 | if (dev->fb_helper) |
173 | return drm_fb_helper_hotplug_event(fb_helper: dev->fb_helper); |
174 | |
175 | ret = drm_fb_helper_init(dev, helper: fb_helper); |
176 | if (ret) |
177 | goto err_drm_err; |
178 | |
179 | if (!drm_drv_uses_atomic_modeset(dev)) |
180 | drm_helper_disable_unused_functions(dev); |
181 | |
182 | ret = drm_fb_helper_initial_config(fb_helper); |
183 | if (ret) |
184 | goto err_drm_fb_helper_fini; |
185 | |
186 | return 0; |
187 | |
188 | err_drm_fb_helper_fini: |
189 | drm_fb_helper_fini(helper: fb_helper); |
190 | err_drm_err: |
191 | drm_err(dev, "Failed to setup fbdev emulation (ret=%d)\n" , ret); |
192 | return ret; |
193 | } |
194 | |
195 | static const struct drm_client_funcs exynos_drm_fbdev_client_funcs = { |
196 | .owner = THIS_MODULE, |
197 | .unregister = exynos_drm_fbdev_client_unregister, |
198 | .restore = exynos_drm_fbdev_client_restore, |
199 | .hotplug = exynos_drm_fbdev_client_hotplug, |
200 | }; |
201 | |
202 | void exynos_drm_fbdev_setup(struct drm_device *dev) |
203 | { |
204 | struct drm_fb_helper *fb_helper; |
205 | int ret; |
206 | |
207 | drm_WARN(dev, !dev->registered, "Device has not been registered.\n" ); |
208 | drm_WARN(dev, dev->fb_helper, "fb_helper is already set!\n" ); |
209 | |
210 | fb_helper = kzalloc(size: sizeof(*fb_helper), GFP_KERNEL); |
211 | if (!fb_helper) |
212 | return; |
213 | drm_fb_helper_prepare(dev, helper: fb_helper, PREFERRED_BPP, funcs: &exynos_drm_fb_helper_funcs); |
214 | |
215 | ret = drm_client_init(dev, client: &fb_helper->client, name: "fbdev" , funcs: &exynos_drm_fbdev_client_funcs); |
216 | if (ret) |
217 | goto err_drm_client_init; |
218 | |
219 | drm_client_register(client: &fb_helper->client); |
220 | |
221 | return; |
222 | |
223 | err_drm_client_init: |
224 | drm_fb_helper_unprepare(fb_helper); |
225 | kfree(objp: fb_helper); |
226 | } |
227 | |