1 | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | /* |
3 | * Generic System Framebuffers |
4 | * Copyright (c) 2012-2013 David Herrmann <dh.herrmann@gmail.com> |
5 | */ |
6 | |
7 | /* |
8 | * Simple-Framebuffer support |
9 | * Create a platform-device for any available boot framebuffer. The |
10 | * simple-framebuffer platform device is already available on DT systems, so |
11 | * this module parses the global "screen_info" object and creates a suitable |
12 | * platform device compatible with the "simple-framebuffer" DT object. If |
13 | * the framebuffer is incompatible, we instead create a legacy |
14 | * "vesa-framebuffer", "efi-framebuffer" or "platform-framebuffer" device and |
15 | * pass the screen_info as platform_data. This allows legacy drivers |
16 | * to pick these devices up without messing with simple-framebuffer drivers. |
17 | * The global "screen_info" is still valid at all times. |
18 | * |
19 | * If CONFIG_SYSFB_SIMPLEFB is not selected, never register "simple-framebuffer" |
20 | * platform devices, but only use legacy framebuffer devices for |
21 | * backwards compatibility. |
22 | * |
23 | * TODO: We set the dev_id field of all platform-devices to 0. This allows |
24 | * other OF/DT parsers to create such devices, too. However, they must |
25 | * start at offset 1 for this to work. |
26 | */ |
27 | |
28 | #include <linux/err.h> |
29 | #include <linux/init.h> |
30 | #include <linux/kernel.h> |
31 | #include <linux/mm.h> |
32 | #include <linux/pci.h> |
33 | #include <linux/platform_data/simplefb.h> |
34 | #include <linux/platform_device.h> |
35 | #include <linux/screen_info.h> |
36 | #include <linux/sysfb.h> |
37 | |
38 | static struct platform_device *pd; |
39 | static DEFINE_MUTEX(disable_lock); |
40 | static bool disabled; |
41 | |
42 | static bool sysfb_unregister(void) |
43 | { |
44 | if (IS_ERR_OR_NULL(ptr: pd)) |
45 | return false; |
46 | |
47 | platform_device_unregister(pd); |
48 | pd = NULL; |
49 | |
50 | return true; |
51 | } |
52 | |
53 | /** |
54 | * sysfb_disable() - disable the Generic System Framebuffers support |
55 | * |
56 | * This disables the registration of system framebuffer devices that match the |
57 | * generic drivers that make use of the system framebuffer set up by firmware. |
58 | * |
59 | * It also unregisters a device if this was already registered by sysfb_init(). |
60 | * |
61 | * Context: The function can sleep. A @disable_lock mutex is acquired to serialize |
62 | * against sysfb_init(), that registers a system framebuffer device. |
63 | */ |
64 | void sysfb_disable(void) |
65 | { |
66 | mutex_lock(&disable_lock); |
67 | sysfb_unregister(); |
68 | disabled = true; |
69 | mutex_unlock(lock: &disable_lock); |
70 | } |
71 | EXPORT_SYMBOL_GPL(sysfb_disable); |
72 | |
73 | #if defined(CONFIG_PCI) |
74 | static __init bool sysfb_pci_dev_is_enabled(struct pci_dev *pdev) |
75 | { |
76 | /* |
77 | * TODO: Try to integrate this code into the PCI subsystem |
78 | */ |
79 | int ret; |
80 | u16 command; |
81 | |
82 | ret = pci_read_config_word(dev: pdev, PCI_COMMAND, val: &command); |
83 | if (ret != PCIBIOS_SUCCESSFUL) |
84 | return false; |
85 | if (!(command & PCI_COMMAND_MEMORY)) |
86 | return false; |
87 | return true; |
88 | } |
89 | #else |
90 | static __init bool sysfb_pci_dev_is_enabled(struct pci_dev *pdev) |
91 | { |
92 | return false; |
93 | } |
94 | #endif |
95 | |
96 | static __init struct device *sysfb_parent_dev(const struct screen_info *si) |
97 | { |
98 | struct pci_dev *pdev; |
99 | |
100 | pdev = screen_info_pci_dev(si); |
101 | if (IS_ERR(ptr: pdev)) { |
102 | return ERR_CAST(ptr: pdev); |
103 | } else if (pdev) { |
104 | if (!sysfb_pci_dev_is_enabled(pdev)) |
105 | return ERR_PTR(error: -ENODEV); |
106 | return &pdev->dev; |
107 | } |
108 | |
109 | return NULL; |
110 | } |
111 | |
112 | static __init int sysfb_init(void) |
113 | { |
114 | struct screen_info *si = &screen_info; |
115 | struct device *parent; |
116 | struct simplefb_platform_data mode; |
117 | const char *name; |
118 | bool compatible; |
119 | int ret = 0; |
120 | |
121 | screen_info_apply_fixups(); |
122 | |
123 | mutex_lock(&disable_lock); |
124 | if (disabled) |
125 | goto unlock_mutex; |
126 | |
127 | sysfb_apply_efi_quirks(); |
128 | |
129 | parent = sysfb_parent_dev(si); |
130 | if (IS_ERR(ptr: parent)) { |
131 | ret = PTR_ERR(ptr: parent); |
132 | goto unlock_mutex; |
133 | } |
134 | |
135 | /* try to create a simple-framebuffer device */ |
136 | compatible = sysfb_parse_mode(si, mode: &mode); |
137 | if (compatible) { |
138 | pd = sysfb_create_simplefb(si, mode: &mode, parent); |
139 | if (!IS_ERR(ptr: pd)) |
140 | goto unlock_mutex; |
141 | } |
142 | |
143 | /* if the FB is incompatible, create a legacy framebuffer device */ |
144 | if (si->orig_video_isVGA == VIDEO_TYPE_EFI) |
145 | name = "efi-framebuffer" ; |
146 | else if (si->orig_video_isVGA == VIDEO_TYPE_VLFB) |
147 | name = "vesa-framebuffer" ; |
148 | else if (si->orig_video_isVGA == VIDEO_TYPE_VGAC) |
149 | name = "vga-framebuffer" ; |
150 | else if (si->orig_video_isVGA == VIDEO_TYPE_EGAC) |
151 | name = "ega-framebuffer" ; |
152 | else |
153 | name = "platform-framebuffer" ; |
154 | |
155 | pd = platform_device_alloc(name, id: 0); |
156 | if (!pd) { |
157 | ret = -ENOMEM; |
158 | goto unlock_mutex; |
159 | } |
160 | |
161 | pd->dev.parent = parent; |
162 | |
163 | sysfb_set_efifb_fwnode(pd); |
164 | |
165 | ret = platform_device_add_data(pdev: pd, data: si, size: sizeof(*si)); |
166 | if (ret) |
167 | goto err; |
168 | |
169 | ret = platform_device_add(pdev: pd); |
170 | if (ret) |
171 | goto err; |
172 | |
173 | goto unlock_mutex; |
174 | err: |
175 | platform_device_put(pdev: pd); |
176 | unlock_mutex: |
177 | mutex_unlock(lock: &disable_lock); |
178 | return ret; |
179 | } |
180 | |
181 | /* must execute after PCI subsystem for EFI quirks */ |
182 | device_initcall(sysfb_init); |
183 | |