1 | // SPDX-License-Identifier: GPL-2.0-only |
2 | /* |
3 | * OMAP Display Subsystem Base |
4 | * |
5 | * Copyright (C) 2015-2017 Texas Instruments Incorporated - https://www.ti.com/ |
6 | */ |
7 | |
8 | #include <linux/kernel.h> |
9 | #include <linux/list.h> |
10 | #include <linux/module.h> |
11 | #include <linux/mutex.h> |
12 | #include <linux/of.h> |
13 | #include <linux/of_graph.h> |
14 | #include <linux/platform_device.h> |
15 | |
16 | #include "dss.h" |
17 | #include "omapdss.h" |
18 | |
19 | struct dispc_device *dispc_get_dispc(struct dss_device *dss) |
20 | { |
21 | return dss->dispc; |
22 | } |
23 | |
24 | /* ----------------------------------------------------------------------------- |
25 | * OMAP DSS Devices Handling |
26 | */ |
27 | |
28 | static LIST_HEAD(omapdss_devices_list); |
29 | static DEFINE_MUTEX(omapdss_devices_lock); |
30 | |
31 | void omapdss_device_register(struct omap_dss_device *dssdev) |
32 | { |
33 | mutex_lock(&omapdss_devices_lock); |
34 | list_add_tail(new: &dssdev->list, head: &omapdss_devices_list); |
35 | mutex_unlock(lock: &omapdss_devices_lock); |
36 | } |
37 | |
38 | void omapdss_device_unregister(struct omap_dss_device *dssdev) |
39 | { |
40 | mutex_lock(&omapdss_devices_lock); |
41 | list_del(entry: &dssdev->list); |
42 | mutex_unlock(lock: &omapdss_devices_lock); |
43 | } |
44 | |
45 | static bool omapdss_device_is_registered(struct device_node *node) |
46 | { |
47 | struct omap_dss_device *dssdev; |
48 | bool found = false; |
49 | |
50 | mutex_lock(&omapdss_devices_lock); |
51 | |
52 | list_for_each_entry(dssdev, &omapdss_devices_list, list) { |
53 | if (dssdev->dev->of_node == node) { |
54 | found = true; |
55 | break; |
56 | } |
57 | } |
58 | |
59 | mutex_unlock(lock: &omapdss_devices_lock); |
60 | return found; |
61 | } |
62 | |
63 | struct omap_dss_device *omapdss_device_get(struct omap_dss_device *dssdev) |
64 | { |
65 | if (get_device(dev: dssdev->dev) == NULL) |
66 | return NULL; |
67 | |
68 | return dssdev; |
69 | } |
70 | |
71 | void omapdss_device_put(struct omap_dss_device *dssdev) |
72 | { |
73 | put_device(dev: dssdev->dev); |
74 | } |
75 | |
76 | struct omap_dss_device *omapdss_find_device_by_node(struct device_node *node) |
77 | { |
78 | struct omap_dss_device *dssdev; |
79 | |
80 | list_for_each_entry(dssdev, &omapdss_devices_list, list) { |
81 | if (dssdev->dev->of_node == node) |
82 | return omapdss_device_get(dssdev); |
83 | } |
84 | |
85 | return NULL; |
86 | } |
87 | |
88 | /* |
89 | * Search for the next output device starting at @from. Release the reference to |
90 | * the @from device, and acquire a reference to the returned device if found. |
91 | */ |
92 | struct omap_dss_device *omapdss_device_next_output(struct omap_dss_device *from) |
93 | { |
94 | struct omap_dss_device *dssdev; |
95 | struct list_head *list; |
96 | |
97 | mutex_lock(&omapdss_devices_lock); |
98 | |
99 | if (list_empty(head: &omapdss_devices_list)) { |
100 | dssdev = NULL; |
101 | goto done; |
102 | } |
103 | |
104 | /* |
105 | * Start from the from entry if given or from omapdss_devices_list |
106 | * otherwise. |
107 | */ |
108 | list = from ? &from->list : &omapdss_devices_list; |
109 | |
110 | list_for_each_entry(dssdev, list, list) { |
111 | /* |
112 | * Stop if we reach the omapdss_devices_list, that's the end of |
113 | * the list. |
114 | */ |
115 | if (&dssdev->list == &omapdss_devices_list) { |
116 | dssdev = NULL; |
117 | goto done; |
118 | } |
119 | |
120 | if (dssdev->id && dssdev->bridge) |
121 | goto done; |
122 | } |
123 | |
124 | dssdev = NULL; |
125 | |
126 | done: |
127 | if (from) |
128 | omapdss_device_put(dssdev: from); |
129 | if (dssdev) |
130 | omapdss_device_get(dssdev); |
131 | |
132 | mutex_unlock(lock: &omapdss_devices_lock); |
133 | return dssdev; |
134 | } |
135 | |
136 | static bool omapdss_device_is_connected(struct omap_dss_device *dssdev) |
137 | { |
138 | return dssdev->dss; |
139 | } |
140 | |
141 | int omapdss_device_connect(struct dss_device *dss, |
142 | struct omap_dss_device *src, |
143 | struct omap_dss_device *dst) |
144 | { |
145 | dev_dbg(&dss->pdev->dev, "connect(%s, %s)\n" , |
146 | src ? dev_name(src->dev) : "NULL" , |
147 | dst ? dev_name(dst->dev) : "NULL" ); |
148 | |
149 | if (!dst) { |
150 | /* |
151 | * The destination is NULL when the source is connected to a |
152 | * bridge instead of a DSS device. Stop here, we will attach |
153 | * the bridge later when we will have a DRM encoder. |
154 | */ |
155 | return src && src->bridge ? 0 : -EINVAL; |
156 | } |
157 | |
158 | if (omapdss_device_is_connected(dssdev: dst)) |
159 | return -EBUSY; |
160 | |
161 | dst->dss = dss; |
162 | |
163 | return 0; |
164 | } |
165 | |
166 | void omapdss_device_disconnect(struct omap_dss_device *src, |
167 | struct omap_dss_device *dst) |
168 | { |
169 | struct dss_device *dss = src ? src->dss : dst->dss; |
170 | |
171 | dev_dbg(&dss->pdev->dev, "disconnect(%s, %s)\n" , |
172 | src ? dev_name(src->dev) : "NULL" , |
173 | dst ? dev_name(dst->dev) : "NULL" ); |
174 | |
175 | if (!dst) { |
176 | WARN_ON(!src->bridge); |
177 | return; |
178 | } |
179 | |
180 | if (!dst->id && !omapdss_device_is_connected(dssdev: dst)) { |
181 | WARN_ON(1); |
182 | return; |
183 | } |
184 | |
185 | dst->dss = NULL; |
186 | } |
187 | |
188 | /* ----------------------------------------------------------------------------- |
189 | * Components Handling |
190 | */ |
191 | |
192 | static struct list_head omapdss_comp_list; |
193 | |
194 | struct omapdss_comp_node { |
195 | struct list_head list; |
196 | struct device_node *node; |
197 | bool dss_core_component; |
198 | const char *compat; |
199 | }; |
200 | |
201 | static bool omapdss_list_contains(const struct device_node *node) |
202 | { |
203 | struct omapdss_comp_node *comp; |
204 | |
205 | list_for_each_entry(comp, &omapdss_comp_list, list) { |
206 | if (comp->node == node) |
207 | return true; |
208 | } |
209 | |
210 | return false; |
211 | } |
212 | |
213 | static void omapdss_walk_device(struct device *dev, struct device_node *node, |
214 | bool dss_core) |
215 | { |
216 | struct omapdss_comp_node *comp; |
217 | struct device_node *n; |
218 | const char *compat; |
219 | int ret; |
220 | |
221 | ret = of_property_read_string(np: node, propname: "compatible" , out_string: &compat); |
222 | if (ret < 0) |
223 | return; |
224 | |
225 | comp = devm_kzalloc(dev, size: sizeof(*comp), GFP_KERNEL); |
226 | if (comp) { |
227 | comp->node = node; |
228 | comp->dss_core_component = dss_core; |
229 | comp->compat = compat; |
230 | list_add(new: &comp->list, head: &omapdss_comp_list); |
231 | } |
232 | |
233 | /* |
234 | * of_graph_get_remote_port_parent() prints an error if there is no |
235 | * port/ports node. To avoid that, check first that there's the node. |
236 | */ |
237 | n = of_get_child_by_name(node, name: "ports" ); |
238 | if (!n) |
239 | n = of_get_child_by_name(node, name: "port" ); |
240 | if (!n) |
241 | return; |
242 | |
243 | of_node_put(node: n); |
244 | |
245 | n = NULL; |
246 | while ((n = of_graph_get_next_endpoint(parent: node, previous: n)) != NULL) { |
247 | struct device_node *pn = of_graph_get_remote_port_parent(node: n); |
248 | |
249 | if (!pn) |
250 | continue; |
251 | |
252 | if (!of_device_is_available(device: pn) || omapdss_list_contains(node: pn)) { |
253 | of_node_put(node: pn); |
254 | continue; |
255 | } |
256 | |
257 | omapdss_walk_device(dev, node: pn, dss_core: false); |
258 | } |
259 | } |
260 | |
261 | void omapdss_gather_components(struct device *dev) |
262 | { |
263 | struct device_node *child; |
264 | |
265 | INIT_LIST_HEAD(list: &omapdss_comp_list); |
266 | |
267 | omapdss_walk_device(dev, node: dev->of_node, dss_core: true); |
268 | |
269 | for_each_available_child_of_node(dev->of_node, child) |
270 | omapdss_walk_device(dev, node: child, dss_core: true); |
271 | } |
272 | |
273 | static bool omapdss_component_is_loaded(struct omapdss_comp_node *comp) |
274 | { |
275 | if (comp->dss_core_component) |
276 | return true; |
277 | if (!strstarts(str: comp->compat, prefix: "omapdss," )) |
278 | return true; |
279 | if (omapdss_device_is_registered(node: comp->node)) |
280 | return true; |
281 | |
282 | return false; |
283 | } |
284 | |
285 | bool omapdss_stack_is_ready(void) |
286 | { |
287 | struct omapdss_comp_node *comp; |
288 | |
289 | list_for_each_entry(comp, &omapdss_comp_list, list) { |
290 | if (!omapdss_component_is_loaded(comp)) |
291 | return false; |
292 | } |
293 | |
294 | return true; |
295 | } |
296 | |