/src/barebox/drivers/video/fb.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-only |
2 | | #include <common.h> |
3 | | #include <malloc.h> |
4 | | #include <fb.h> |
5 | | #include <errno.h> |
6 | | #include <command.h> |
7 | | #include <getopt.h> |
8 | | #include <fcntl.h> |
9 | | #include <fs.h> |
10 | | #include <init.h> |
11 | | |
12 | | DEFINE_DEV_CLASS(fb_class, "fb"); |
13 | | EXPORT_SYMBOL(fb_class); |
14 | | |
15 | | static int fb_ioctl(struct cdev* cdev, unsigned int req, void *data) |
16 | 0 | { |
17 | 0 | struct fb_info *info = cdev->priv; |
18 | 0 | struct fb_info **fb; |
19 | 0 | int ret; |
20 | |
|
21 | 0 | switch (req) { |
22 | 0 | case FBIOGET_SCREENINFO: |
23 | 0 | fb = data; |
24 | 0 | *fb = info; |
25 | 0 | ret = 0; |
26 | 0 | break; |
27 | 0 | case FBIO_ENABLE: |
28 | 0 | ret = fb_enable(info); |
29 | 0 | break; |
30 | 0 | case FBIO_DISABLE: |
31 | 0 | ret = fb_disable(info); |
32 | 0 | break; |
33 | 0 | default: |
34 | 0 | return -ENOSYS; |
35 | 0 | } |
36 | | |
37 | 0 | return ret; |
38 | 0 | } |
39 | | |
40 | | static int fb_close(struct cdev *cdev) |
41 | 0 | { |
42 | 0 | struct fb_info *info = cdev->priv; |
43 | |
|
44 | 0 | if (info->fbops->fb_flush) |
45 | 0 | info->fbops->fb_flush(info); |
46 | 0 | return 0; |
47 | 0 | } |
48 | | |
49 | | void fb_damage(struct fb_info *info, struct fb_rect *rect) |
50 | 0 | { |
51 | 0 | if (info->fbops->fb_damage) |
52 | 0 | info->fbops->fb_damage(info, rect); |
53 | 0 | } |
54 | | |
55 | | static int fb_op_flush(struct cdev *cdev) |
56 | 0 | { |
57 | 0 | struct fb_info *info = cdev->priv; |
58 | |
|
59 | 0 | if (info->fbops->fb_flush) |
60 | 0 | info->fbops->fb_flush(info); |
61 | 0 | return 0; |
62 | 0 | } |
63 | | |
64 | | void fb_flush(struct fb_info *info) |
65 | 0 | { |
66 | 0 | if (info->fbops->fb_flush) |
67 | 0 | info->fbops->fb_flush(info); |
68 | 0 | } |
69 | | |
70 | | static void fb_release_shadowfb(struct fb_info *info) |
71 | 0 | { |
72 | 0 | free(info->screen_base_shadow); |
73 | 0 | info->screen_base_shadow = NULL; |
74 | 0 | } |
75 | | |
76 | | static int fb_alloc_shadowfb(struct fb_info *info) |
77 | 0 | { |
78 | 0 | if (info->screen_base_shadow && info->shadowfb) |
79 | 0 | return 0; |
80 | | |
81 | 0 | if (!info->screen_base_shadow && !info->shadowfb) |
82 | 0 | return 0; |
83 | | |
84 | 0 | if (info->shadowfb) { |
85 | 0 | info->screen_base_shadow = memalign(PAGE_SIZE, |
86 | 0 | info->line_length * info->yres); |
87 | 0 | if (!info->screen_base_shadow) |
88 | 0 | return -ENOMEM; |
89 | 0 | memcpy(info->screen_base_shadow, info->screen_base, |
90 | 0 | info->line_length * info->yres); |
91 | 0 | } else { |
92 | 0 | fb_release_shadowfb(info); |
93 | 0 | } |
94 | | |
95 | 0 | return 0; |
96 | 0 | } |
97 | | |
98 | | int fb_enable(struct fb_info *info) |
99 | 0 | { |
100 | 0 | int ret; |
101 | |
|
102 | 0 | if (info->enabled) |
103 | 0 | return 0; |
104 | | |
105 | 0 | ret = fb_alloc_shadowfb(info); |
106 | 0 | if (ret) |
107 | 0 | return ret; |
108 | | |
109 | 0 | if (info->fbops->fb_enable) |
110 | 0 | info->fbops->fb_enable(info); |
111 | |
|
112 | 0 | info->enabled = true; |
113 | |
|
114 | 0 | return 0; |
115 | 0 | } |
116 | | |
117 | | int fb_disable(struct fb_info *info) |
118 | 0 | { |
119 | 0 | if (!info->enabled) |
120 | 0 | return 0; |
121 | | |
122 | 0 | if (info->fbops->fb_disable) |
123 | 0 | info->fbops->fb_disable(info); |
124 | |
|
125 | 0 | fb_release_shadowfb(info); |
126 | |
|
127 | 0 | info->enabled = false; |
128 | |
|
129 | 0 | return 0; |
130 | 0 | } |
131 | | |
132 | | static int fb_enable_get(struct param_d *param, void *priv) |
133 | 0 | { |
134 | 0 | struct fb_info *info = priv; |
135 | |
|
136 | 0 | info->p_enable = info->enabled; |
137 | 0 | return 0; |
138 | 0 | } |
139 | | |
140 | | static int fb_enable_set(struct param_d *param, void *priv) |
141 | 0 | { |
142 | 0 | struct fb_info *info = priv; |
143 | |
|
144 | 0 | if (!info->mode && !info->base_plane) |
145 | 0 | return -EINVAL; |
146 | | |
147 | 0 | if (info->p_enable) |
148 | 0 | return fb_enable(info); |
149 | 0 | else |
150 | 0 | return fb_disable(info); |
151 | 0 | } |
152 | | |
153 | | static struct fb_videomode *fb_num_to_mode(struct fb_info *info, int num) |
154 | 0 | { |
155 | 0 | int num_modes; |
156 | |
|
157 | 0 | num_modes = info->modes.num_modes + info->edid_modes.num_modes; |
158 | |
|
159 | 0 | if (num >= num_modes) |
160 | 0 | return NULL; |
161 | | |
162 | 0 | if (num >= info->modes.num_modes) |
163 | 0 | return &info->edid_modes.modes[num - info->modes.num_modes]; |
164 | | |
165 | 0 | return &info->modes.modes[num]; |
166 | 0 | } |
167 | | |
168 | | static int fb_setup_mode(struct fb_info *info) |
169 | 0 | { |
170 | 0 | struct device *dev = &info->dev; |
171 | 0 | int ret; |
172 | 0 | struct fb_videomode *mode; |
173 | |
|
174 | 0 | if (info->enabled != 0) |
175 | 0 | return -EPERM; |
176 | | |
177 | 0 | mode = fb_num_to_mode(info, info->current_mode); |
178 | 0 | if (!mode) |
179 | 0 | return -EINVAL; |
180 | | |
181 | 0 | info->mode = mode; |
182 | |
|
183 | 0 | info->xres = info->mode->xres; |
184 | 0 | info->yres = info->mode->yres; |
185 | 0 | info->line_length = 0; |
186 | |
|
187 | 0 | if (info->fbops->fb_activate_var) { |
188 | 0 | ret = info->fbops->fb_activate_var(info); |
189 | 0 | if (ret) { |
190 | 0 | info->cdev.size = 0; |
191 | 0 | return ret; |
192 | 0 | } |
193 | 0 | } |
194 | | |
195 | 0 | if (!info->line_length) |
196 | 0 | info->line_length = info->xres * (info->bits_per_pixel >> 3); |
197 | 0 | if (!info->screen_size) |
198 | 0 | info->screen_size = info->line_length * info->yres; |
199 | |
|
200 | 0 | dev->resource[0].start = (resource_size_t)info->screen_base; |
201 | 0 | info->cdev.size = info->line_length * info->yres; |
202 | 0 | dev->resource[0].end = dev->resource[0].start + info->cdev.size - 1; |
203 | |
|
204 | 0 | return 0; |
205 | 0 | } |
206 | | |
207 | | static int fb_of_reserve_fixup(struct device_node *root, void *context) |
208 | 0 | { |
209 | 0 | struct fb_info *info = context; |
210 | |
|
211 | 0 | of_del_reserve_entry((unsigned long)info->screen_base, |
212 | 0 | (unsigned long)info->screen_base + info->screen_size); |
213 | |
|
214 | 0 | if (info->enabled) |
215 | 0 | of_add_reserve_entry((unsigned long)info->screen_base, |
216 | 0 | (unsigned long)info->screen_base + info->screen_size); |
217 | |
|
218 | 0 | return 0; |
219 | 0 | } |
220 | | |
221 | | void fb_of_reserve_add_fixup(struct fb_info *info) |
222 | 0 | { |
223 | 0 | if (!IS_ENABLED(CONFIG_OFDEVICE)) |
224 | 0 | return; |
225 | | |
226 | 0 | of_register_fixup(fb_of_reserve_fixup, info); |
227 | 0 | } |
228 | | |
229 | | static int fb_set_modename(struct param_d *param, void *priv) |
230 | 0 | { |
231 | 0 | struct fb_info *info = priv; |
232 | 0 | int ret; |
233 | |
|
234 | 0 | ret = fb_setup_mode(info); |
235 | 0 | if (ret) |
236 | 0 | return ret; |
237 | | |
238 | 0 | return 0; |
239 | 0 | } |
240 | | |
241 | | static ssize_t fb_cdev_write(struct cdev *cdev, const void *buf, size_t count, |
242 | | loff_t offset, ulong flags) |
243 | 0 | { |
244 | 0 | struct fb_info *info = cdev->priv; |
245 | 0 | ssize_t ret; |
246 | |
|
247 | 0 | ret = mem_write(cdev, buf, count, offset, flags); |
248 | 0 | if (ret > 0 && info->screen_base_shadow) |
249 | 0 | memcpy(info->screen_base_shadow + offset, buf, ret); |
250 | |
|
251 | 0 | return ret; |
252 | 0 | } |
253 | | |
254 | | static struct cdev_operations fb_ops = { |
255 | | .read = mem_read, |
256 | | .write = fb_cdev_write, |
257 | | .memmap = generic_memmap_rw, |
258 | | .ioctl = fb_ioctl, |
259 | | .close = fb_close, |
260 | | .flush = fb_op_flush, |
261 | | }; |
262 | | |
263 | | static void fb_print_mode(struct fb_videomode *mode) |
264 | 0 | { |
265 | 0 | printf(" %s: %dx%d@%d\n", mode->name, |
266 | 0 | mode->xres, mode->yres, mode->refresh); |
267 | 0 | } |
268 | | |
269 | | static void fb_print_modes(struct display_timings *modes) |
270 | 0 | { |
271 | 0 | int i; |
272 | |
|
273 | 0 | for (i = 0; i < modes->num_modes; i++) |
274 | 0 | fb_print_mode(&modes->modes[i]); |
275 | 0 | } |
276 | | |
277 | | static void fb_info(struct device *dev) |
278 | 0 | { |
279 | 0 | struct fb_info *info = dev->priv; |
280 | |
|
281 | 0 | printf("Available modes:\n"); |
282 | 0 | fb_print_modes(&info->modes); |
283 | 0 | fb_print_modes(&info->edid_modes); |
284 | 0 | if (info->base_plane) { |
285 | 0 | printf("Type: overlay\n"); |
286 | 0 | printf("base plane: %s\n", dev_name(&info->base_plane->dev)); |
287 | 0 | } else { |
288 | 0 | printf("Type: primary\n"); |
289 | 0 | } |
290 | 0 | printf("xres: %u\n", info->xres); |
291 | 0 | printf("yres: %u\n", info->yres); |
292 | 0 | printf("line_length: %u\n", info->line_length); |
293 | 0 | } |
294 | | |
295 | | void *fb_get_screen_base(struct fb_info *info) |
296 | 0 | { |
297 | 0 | return info->screen_base_shadow ? |
298 | 0 | info->screen_base_shadow : info->screen_base; |
299 | 0 | } |
300 | | |
301 | | static int fb_set_shadowfb(struct param_d *p, void *priv) |
302 | 0 | { |
303 | 0 | struct fb_info *info = priv; |
304 | |
|
305 | 0 | if (!info->enabled) |
306 | 0 | return 0; |
307 | | |
308 | 0 | return fb_alloc_shadowfb(info); |
309 | 0 | } |
310 | | |
311 | | static const char *mode_name(struct fb_videomode *mode) |
312 | 0 | { |
313 | 0 | return basprintf("%dx%d@%d", mode->xres, mode->yres, mode->refresh); |
314 | 0 | } |
315 | | |
316 | | int register_framebuffer(struct fb_info *info) |
317 | 0 | { |
318 | 0 | int id; |
319 | 0 | struct device *dev; |
320 | 0 | int ret, num_modes, i; |
321 | 0 | const char **names; |
322 | |
|
323 | 0 | dev = &info->dev; |
324 | | |
325 | | /* |
326 | | * If info->mode is set at this point it's the only mode |
327 | | * the fb supports. move it over to the modes list. |
328 | | */ |
329 | 0 | if (info->mode) { |
330 | 0 | info->modes.modes = info->mode; |
331 | 0 | info->modes.num_modes = 1; |
332 | 0 | } |
333 | |
|
334 | 0 | if (!info->line_length) |
335 | 0 | info->line_length = info->xres * (info->bits_per_pixel >> 3); |
336 | |
|
337 | 0 | info->cdev.ops = &fb_ops; |
338 | 0 | if (info->base_plane) { |
339 | 0 | int ovl_id = info->base_plane->n_overlays++; |
340 | |
|
341 | 0 | info->cdev.name = basprintf("%s_%d", dev_name(&info->base_plane->dev), ovl_id); |
342 | 0 | dev->id = DEVICE_ID_SINGLE; |
343 | 0 | dev_set_name(dev, "%s", info->cdev.name); |
344 | 0 | } else { |
345 | 0 | id = get_free_deviceid("fb"); |
346 | 0 | dev->id = id; |
347 | 0 | dev_set_name(dev, "fb"); |
348 | 0 | info->cdev.name = basprintf("fb%d", id); |
349 | 0 | } |
350 | 0 | info->cdev.size = info->line_length * info->yres; |
351 | 0 | info->cdev.dev = dev; |
352 | 0 | info->cdev.priv = info; |
353 | 0 | dev->resource = xzalloc(sizeof(struct resource)); |
354 | 0 | dev->resource[0].start = (resource_size_t)info->screen_base; |
355 | 0 | dev->resource[0].end = dev->resource[0].start + info->cdev.size - 1; |
356 | 0 | dev->resource[0].flags = IORESOURCE_MEM; |
357 | 0 | dev->num_resources = 1; |
358 | |
|
359 | 0 | dev->priv = info; |
360 | 0 | devinfo_add(dev, fb_info); |
361 | |
|
362 | 0 | ret = register_device(&info->dev); |
363 | 0 | if (ret) |
364 | 0 | goto err_free; |
365 | | |
366 | 0 | class_add_device(&fb_class, &info->dev); |
367 | |
|
368 | 0 | dev_add_param_bool(dev, "enable", fb_enable_set, fb_enable_get, |
369 | 0 | &info->p_enable, info); |
370 | |
|
371 | 0 | if (IS_ENABLED(CONFIG_DRIVER_VIDEO_EDID)) |
372 | 0 | fb_edid_add_modes(info); |
373 | |
|
374 | 0 | num_modes = info->modes.num_modes + info->edid_modes.num_modes; |
375 | |
|
376 | 0 | names = xzalloc(sizeof(char *) * num_modes); |
377 | |
|
378 | 0 | for (i = 0; i < info->modes.num_modes; i++) { |
379 | 0 | if (!info->modes.modes[i].name) |
380 | 0 | info->modes.modes[i].name = mode_name(&info->modes.modes[i]); |
381 | 0 | names[i] = info->modes.modes[i].name; |
382 | 0 | } |
383 | |
|
384 | 0 | for (i = 0; i < info->edid_modes.num_modes; i++) { |
385 | 0 | if (!info->edid_modes.modes[i].name) |
386 | 0 | info->modes.modes[i].name = mode_name(&info->edid_modes.modes[i]); |
387 | 0 | names[i + info->modes.num_modes] = info->edid_modes.modes[i].name; |
388 | 0 | } |
389 | |
|
390 | 0 | dev_add_param_enum(dev, "mode_name", fb_set_modename, NULL, &info->current_mode, names, num_modes, info); |
391 | 0 | info->shadowfb = 1; |
392 | 0 | dev_add_param_bool(dev, "shadowfb", fb_set_shadowfb, NULL, &info->shadowfb, info); |
393 | |
|
394 | 0 | info->mode = fb_num_to_mode(info, 0); |
395 | |
|
396 | 0 | fb_setup_mode(info); |
397 | |
|
398 | 0 | ret = devfs_create(&info->cdev); |
399 | 0 | if (ret) |
400 | 0 | goto err_unregister; |
401 | | |
402 | 0 | if (IS_ENABLED(CONFIG_DRIVER_VIDEO_SIMPLEFB)) { |
403 | 0 | ret = fb_register_simplefb(info); |
404 | 0 | if (ret) |
405 | 0 | dev_err(&info->dev, "failed to register simplefb: %pe\n", |
406 | 0 | ERR_PTR(ret)); |
407 | 0 | } |
408 | |
|
409 | 0 | if (IS_ENABLED(CONFIG_FRAMEBUFFER_CONSOLE)) |
410 | 0 | register_fbconsole(info); |
411 | |
|
412 | 0 | return 0; |
413 | | |
414 | 0 | err_unregister: |
415 | 0 | unregister_device(&info->dev); |
416 | 0 | err_free: |
417 | 0 | free(dev->resource); |
418 | |
|
419 | 0 | return ret; |
420 | 0 | } |