diff options
Diffstat (limited to 'drivers/gpu/drm/drm_probe_helper.c')
-rw-r--r-- | drivers/gpu/drm/drm_probe_helper.c | 426 |
1 files changed, 426 insertions, 0 deletions
diff --git a/drivers/gpu/drm/drm_probe_helper.c b/drivers/gpu/drm/drm_probe_helper.c new file mode 100644 index 000000000000..e70f54d4a581 --- /dev/null +++ b/drivers/gpu/drm/drm_probe_helper.c | |||
@@ -0,0 +1,426 @@ | |||
1 | /* | ||
2 | * Copyright (c) 2006-2008 Intel Corporation | ||
3 | * Copyright (c) 2007 Dave Airlie <airlied@linux.ie> | ||
4 | * | ||
5 | * DRM core CRTC related functions | ||
6 | * | ||
7 | * Permission to use, copy, modify, distribute, and sell this software and its | ||
8 | * documentation for any purpose is hereby granted without fee, provided that | ||
9 | * the above copyright notice appear in all copies and that both that copyright | ||
10 | * notice and this permission notice appear in supporting documentation, and | ||
11 | * that the name of the copyright holders not be used in advertising or | ||
12 | * publicity pertaining to distribution of the software without specific, | ||
13 | * written prior permission. The copyright holders make no representations | ||
14 | * about the suitability of this software for any purpose. It is provided "as | ||
15 | * is" without express or implied warranty. | ||
16 | * | ||
17 | * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, | ||
18 | * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO | ||
19 | * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR | ||
20 | * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, | ||
21 | * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER | ||
22 | * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE | ||
23 | * OF THIS SOFTWARE. | ||
24 | * | ||
25 | * Authors: | ||
26 | * Keith Packard | ||
27 | * Eric Anholt <eric@anholt.net> | ||
28 | * Dave Airlie <airlied@linux.ie> | ||
29 | * Jesse Barnes <jesse.barnes@intel.com> | ||
30 | */ | ||
31 | |||
32 | #include <linux/export.h> | ||
33 | #include <linux/moduleparam.h> | ||
34 | |||
35 | #include <drm/drmP.h> | ||
36 | #include <drm/drm_crtc.h> | ||
37 | #include <drm/drm_fourcc.h> | ||
38 | #include <drm/drm_crtc_helper.h> | ||
39 | #include <drm/drm_fb_helper.h> | ||
40 | #include <drm/drm_edid.h> | ||
41 | |||
42 | /** | ||
43 | * DOC: output probing helper overview | ||
44 | * | ||
45 | * This library provides some helper code for output probing. It provides an | ||
46 | * implementation of the core connector->fill_modes interface with | ||
47 | * drm_helper_probe_single_connector_modes. | ||
48 | * | ||
49 | * It also provides support for polling connectors with a work item and for | ||
50 | * generic hotplug interrupt handling where the driver doesn't or cannot keep | ||
51 | * track of a per-connector hpd interrupt. | ||
52 | * | ||
53 | * This helper library can be used independently of the modeset helper library. | ||
54 | * Drivers can also overwrite different parts e.g. use their own hotplug | ||
55 | * handling code to avoid probing unrelated outputs. | ||
56 | */ | ||
57 | |||
58 | static bool drm_kms_helper_poll = true; | ||
59 | module_param_named(poll, drm_kms_helper_poll, bool, 0600); | ||
60 | |||
61 | static void drm_mode_validate_flag(struct drm_connector *connector, | ||
62 | int flags) | ||
63 | { | ||
64 | struct drm_display_mode *mode; | ||
65 | |||
66 | if (flags == (DRM_MODE_FLAG_DBLSCAN | DRM_MODE_FLAG_INTERLACE | | ||
67 | DRM_MODE_FLAG_3D_MASK)) | ||
68 | return; | ||
69 | |||
70 | list_for_each_entry(mode, &connector->modes, head) { | ||
71 | if ((mode->flags & DRM_MODE_FLAG_INTERLACE) && | ||
72 | !(flags & DRM_MODE_FLAG_INTERLACE)) | ||
73 | mode->status = MODE_NO_INTERLACE; | ||
74 | if ((mode->flags & DRM_MODE_FLAG_DBLSCAN) && | ||
75 | !(flags & DRM_MODE_FLAG_DBLSCAN)) | ||
76 | mode->status = MODE_NO_DBLESCAN; | ||
77 | if ((mode->flags & DRM_MODE_FLAG_3D_MASK) && | ||
78 | !(flags & DRM_MODE_FLAG_3D_MASK)) | ||
79 | mode->status = MODE_NO_STEREO; | ||
80 | } | ||
81 | |||
82 | return; | ||
83 | } | ||
84 | |||
85 | /** | ||
86 | * drm_helper_probe_single_connector_modes - get complete set of display modes | ||
87 | * @connector: connector to probe | ||
88 | * @maxX: max width for modes | ||
89 | * @maxY: max height for modes | ||
90 | * | ||
91 | * Based on the helper callbacks implemented by @connector try to detect all | ||
92 | * valid modes. Modes will first be added to the connector's probed_modes list, | ||
93 | * then culled (based on validity and the @maxX, @maxY parameters) and put into | ||
94 | * the normal modes list. | ||
95 | * | ||
96 | * Intended to be use as a generic implementation of the ->fill_modes() | ||
97 | * @connector vfunc for drivers that use the crtc helpers for output mode | ||
98 | * filtering and detection. | ||
99 | * | ||
100 | * Returns: | ||
101 | * The number of modes found on @connector. | ||
102 | */ | ||
103 | int drm_helper_probe_single_connector_modes(struct drm_connector *connector, | ||
104 | uint32_t maxX, uint32_t maxY) | ||
105 | { | ||
106 | struct drm_device *dev = connector->dev; | ||
107 | struct drm_display_mode *mode; | ||
108 | struct drm_connector_helper_funcs *connector_funcs = | ||
109 | connector->helper_private; | ||
110 | int count = 0; | ||
111 | int mode_flags = 0; | ||
112 | bool verbose_prune = true; | ||
113 | |||
114 | WARN_ON(!mutex_is_locked(&dev->mode_config.mutex)); | ||
115 | |||
116 | DRM_DEBUG_KMS("[CONNECTOR:%d:%s]\n", connector->base.id, | ||
117 | drm_get_connector_name(connector)); | ||
118 | /* set all modes to the unverified state */ | ||
119 | list_for_each_entry(mode, &connector->modes, head) | ||
120 | mode->status = MODE_UNVERIFIED; | ||
121 | |||
122 | if (connector->force) { | ||
123 | if (connector->force == DRM_FORCE_ON) | ||
124 | connector->status = connector_status_connected; | ||
125 | else | ||
126 | connector->status = connector_status_disconnected; | ||
127 | if (connector->funcs->force) | ||
128 | connector->funcs->force(connector); | ||
129 | } else { | ||
130 | connector->status = connector->funcs->detect(connector, true); | ||
131 | } | ||
132 | |||
133 | /* Re-enable polling in case the global poll config changed. */ | ||
134 | if (drm_kms_helper_poll != dev->mode_config.poll_running) | ||
135 | drm_kms_helper_poll_enable(dev); | ||
136 | |||
137 | dev->mode_config.poll_running = drm_kms_helper_poll; | ||
138 | |||
139 | if (connector->status == connector_status_disconnected) { | ||
140 | DRM_DEBUG_KMS("[CONNECTOR:%d:%s] disconnected\n", | ||
141 | connector->base.id, drm_get_connector_name(connector)); | ||
142 | drm_mode_connector_update_edid_property(connector, NULL); | ||
143 | verbose_prune = false; | ||
144 | goto prune; | ||
145 | } | ||
146 | |||
147 | #ifdef CONFIG_DRM_LOAD_EDID_FIRMWARE | ||
148 | count = drm_load_edid_firmware(connector); | ||
149 | if (count == 0) | ||
150 | #endif | ||
151 | count = (*connector_funcs->get_modes)(connector); | ||
152 | |||
153 | if (count == 0 && connector->status == connector_status_connected) | ||
154 | count = drm_add_modes_noedid(connector, 1024, 768); | ||
155 | if (count == 0) | ||
156 | goto prune; | ||
157 | |||
158 | drm_mode_connector_list_update(connector); | ||
159 | |||
160 | if (maxX && maxY) | ||
161 | drm_mode_validate_size(dev, &connector->modes, maxX, maxY); | ||
162 | |||
163 | if (connector->interlace_allowed) | ||
164 | mode_flags |= DRM_MODE_FLAG_INTERLACE; | ||
165 | if (connector->doublescan_allowed) | ||
166 | mode_flags |= DRM_MODE_FLAG_DBLSCAN; | ||
167 | if (connector->stereo_allowed) | ||
168 | mode_flags |= DRM_MODE_FLAG_3D_MASK; | ||
169 | drm_mode_validate_flag(connector, mode_flags); | ||
170 | |||
171 | list_for_each_entry(mode, &connector->modes, head) { | ||
172 | if (mode->status == MODE_OK) | ||
173 | mode->status = connector_funcs->mode_valid(connector, | ||
174 | mode); | ||
175 | } | ||
176 | |||
177 | prune: | ||
178 | drm_mode_prune_invalid(dev, &connector->modes, verbose_prune); | ||
179 | |||
180 | if (list_empty(&connector->modes)) | ||
181 | return 0; | ||
182 | |||
183 | list_for_each_entry(mode, &connector->modes, head) | ||
184 | mode->vrefresh = drm_mode_vrefresh(mode); | ||
185 | |||
186 | drm_mode_sort(&connector->modes); | ||
187 | |||
188 | DRM_DEBUG_KMS("[CONNECTOR:%d:%s] probed modes :\n", connector->base.id, | ||
189 | drm_get_connector_name(connector)); | ||
190 | list_for_each_entry(mode, &connector->modes, head) { | ||
191 | drm_mode_set_crtcinfo(mode, CRTC_INTERLACE_HALVE_V); | ||
192 | drm_mode_debug_printmodeline(mode); | ||
193 | } | ||
194 | |||
195 | return count; | ||
196 | } | ||
197 | EXPORT_SYMBOL(drm_helper_probe_single_connector_modes); | ||
198 | |||
199 | /** | ||
200 | * drm_kms_helper_hotplug_event - fire off KMS hotplug events | ||
201 | * @dev: drm_device whose connector state changed | ||
202 | * | ||
203 | * This function fires off the uevent for userspace and also calls the | ||
204 | * output_poll_changed function, which is most commonly used to inform the fbdev | ||
205 | * emulation code and allow it to update the fbcon output configuration. | ||
206 | * | ||
207 | * Drivers should call this from their hotplug handling code when a change is | ||
208 | * detected. Note that this function does not do any output detection of its | ||
209 | * own, like drm_helper_hpd_irq_event() does - this is assumed to be done by the | ||
210 | * driver already. | ||
211 | * | ||
212 | * This function must be called from process context with no mode | ||
213 | * setting locks held. | ||
214 | */ | ||
215 | void drm_kms_helper_hotplug_event(struct drm_device *dev) | ||
216 | { | ||
217 | /* send a uevent + call fbdev */ | ||
218 | drm_sysfs_hotplug_event(dev); | ||
219 | if (dev->mode_config.funcs->output_poll_changed) | ||
220 | dev->mode_config.funcs->output_poll_changed(dev); | ||
221 | } | ||
222 | EXPORT_SYMBOL(drm_kms_helper_hotplug_event); | ||
223 | |||
224 | #define DRM_OUTPUT_POLL_PERIOD (10*HZ) | ||
225 | static void output_poll_execute(struct work_struct *work) | ||
226 | { | ||
227 | struct delayed_work *delayed_work = to_delayed_work(work); | ||
228 | struct drm_device *dev = container_of(delayed_work, struct drm_device, mode_config.output_poll_work); | ||
229 | struct drm_connector *connector; | ||
230 | enum drm_connector_status old_status; | ||
231 | bool repoll = false, changed = false; | ||
232 | |||
233 | if (!drm_kms_helper_poll) | ||
234 | return; | ||
235 | |||
236 | mutex_lock(&dev->mode_config.mutex); | ||
237 | list_for_each_entry(connector, &dev->mode_config.connector_list, head) { | ||
238 | |||
239 | /* Ignore forced connectors. */ | ||
240 | if (connector->force) | ||
241 | continue; | ||
242 | |||
243 | /* Ignore HPD capable connectors and connectors where we don't | ||
244 | * want any hotplug detection at all for polling. */ | ||
245 | if (!connector->polled || connector->polled == DRM_CONNECTOR_POLL_HPD) | ||
246 | continue; | ||
247 | |||
248 | repoll = true; | ||
249 | |||
250 | old_status = connector->status; | ||
251 | /* if we are connected and don't want to poll for disconnect | ||
252 | skip it */ | ||
253 | if (old_status == connector_status_connected && | ||
254 | !(connector->polled & DRM_CONNECTOR_POLL_DISCONNECT)) | ||
255 | continue; | ||
256 | |||
257 | connector->status = connector->funcs->detect(connector, false); | ||
258 | if (old_status != connector->status) { | ||
259 | const char *old, *new; | ||
260 | |||
261 | old = drm_get_connector_status_name(old_status); | ||
262 | new = drm_get_connector_status_name(connector->status); | ||
263 | |||
264 | DRM_DEBUG_KMS("[CONNECTOR:%d:%s] " | ||
265 | "status updated from %s to %s\n", | ||
266 | connector->base.id, | ||
267 | drm_get_connector_name(connector), | ||
268 | old, new); | ||
269 | |||
270 | changed = true; | ||
271 | } | ||
272 | } | ||
273 | |||
274 | mutex_unlock(&dev->mode_config.mutex); | ||
275 | |||
276 | if (changed) | ||
277 | drm_kms_helper_hotplug_event(dev); | ||
278 | |||
279 | if (repoll) | ||
280 | schedule_delayed_work(delayed_work, DRM_OUTPUT_POLL_PERIOD); | ||
281 | } | ||
282 | |||
283 | /** | ||
284 | * drm_kms_helper_poll_disable - disable output polling | ||
285 | * @dev: drm_device | ||
286 | * | ||
287 | * This function disables the output polling work. | ||
288 | * | ||
289 | * Drivers can call this helper from their device suspend implementation. It is | ||
290 | * not an error to call this even when output polling isn't enabled or arlready | ||
291 | * disabled. | ||
292 | */ | ||
293 | void drm_kms_helper_poll_disable(struct drm_device *dev) | ||
294 | { | ||
295 | if (!dev->mode_config.poll_enabled) | ||
296 | return; | ||
297 | cancel_delayed_work_sync(&dev->mode_config.output_poll_work); | ||
298 | } | ||
299 | EXPORT_SYMBOL(drm_kms_helper_poll_disable); | ||
300 | |||
301 | /** | ||
302 | * drm_kms_helper_poll_enable - re-enable output polling. | ||
303 | * @dev: drm_device | ||
304 | * | ||
305 | * This function re-enables the output polling work. | ||
306 | * | ||
307 | * Drivers can call this helper from their device resume implementation. It is | ||
308 | * an error to call this when the output polling support has not yet been set | ||
309 | * up. | ||
310 | */ | ||
311 | void drm_kms_helper_poll_enable(struct drm_device *dev) | ||
312 | { | ||
313 | bool poll = false; | ||
314 | struct drm_connector *connector; | ||
315 | |||
316 | if (!dev->mode_config.poll_enabled || !drm_kms_helper_poll) | ||
317 | return; | ||
318 | |||
319 | list_for_each_entry(connector, &dev->mode_config.connector_list, head) { | ||
320 | if (connector->polled & (DRM_CONNECTOR_POLL_CONNECT | | ||
321 | DRM_CONNECTOR_POLL_DISCONNECT)) | ||
322 | poll = true; | ||
323 | } | ||
324 | |||
325 | if (poll) | ||
326 | schedule_delayed_work(&dev->mode_config.output_poll_work, DRM_OUTPUT_POLL_PERIOD); | ||
327 | } | ||
328 | EXPORT_SYMBOL(drm_kms_helper_poll_enable); | ||
329 | |||
330 | /** | ||
331 | * drm_kms_helper_poll_init - initialize and enable output polling | ||
332 | * @dev: drm_device | ||
333 | * | ||
334 | * This function intializes and then also enables output polling support for | ||
335 | * @dev. Drivers which do not have reliable hotplug support in hardware can use | ||
336 | * this helper infrastructure to regularly poll such connectors for changes in | ||
337 | * their connection state. | ||
338 | * | ||
339 | * Drivers can control which connectors are polled by setting the | ||
340 | * DRM_CONNECTOR_POLL_CONNECT and DRM_CONNECTOR_POLL_DISCONNECT flags. On | ||
341 | * connectors where probing live outputs can result in visual distortion drivers | ||
342 | * should not set the DRM_CONNECTOR_POLL_DISCONNECT flag to avoid this. | ||
343 | * Connectors which have no flag or only DRM_CONNECTOR_POLL_HPD set are | ||
344 | * completely ignored by the polling logic. | ||
345 | * | ||
346 | * Note that a connector can be both polled and probed from the hotplug handler, | ||
347 | * in case the hotplug interrupt is known to be unreliable. | ||
348 | */ | ||
349 | void drm_kms_helper_poll_init(struct drm_device *dev) | ||
350 | { | ||
351 | INIT_DELAYED_WORK(&dev->mode_config.output_poll_work, output_poll_execute); | ||
352 | dev->mode_config.poll_enabled = true; | ||
353 | |||
354 | drm_kms_helper_poll_enable(dev); | ||
355 | } | ||
356 | EXPORT_SYMBOL(drm_kms_helper_poll_init); | ||
357 | |||
358 | /** | ||
359 | * drm_kms_helper_poll_fini - disable output polling and clean it up | ||
360 | * @dev: drm_device | ||
361 | */ | ||
362 | void drm_kms_helper_poll_fini(struct drm_device *dev) | ||
363 | { | ||
364 | drm_kms_helper_poll_disable(dev); | ||
365 | } | ||
366 | EXPORT_SYMBOL(drm_kms_helper_poll_fini); | ||
367 | |||
368 | /** | ||
369 | * drm_helper_hpd_irq_event - hotplug processing | ||
370 | * @dev: drm_device | ||
371 | * | ||
372 | * Drivers can use this helper function to run a detect cycle on all connectors | ||
373 | * which have the DRM_CONNECTOR_POLL_HPD flag set in their &polled member. All | ||
374 | * other connectors are ignored, which is useful to avoid reprobing fixed | ||
375 | * panels. | ||
376 | * | ||
377 | * This helper function is useful for drivers which can't or don't track hotplug | ||
378 | * interrupts for each connector. | ||
379 | * | ||
380 | * Drivers which support hotplug interrupts for each connector individually and | ||
381 | * which have a more fine-grained detect logic should bypass this code and | ||
382 | * directly call drm_kms_helper_hotplug_event() in case the connector state | ||
383 | * changed. | ||
384 | * | ||
385 | * This function must be called from process context with no mode | ||
386 | * setting locks held. | ||
387 | * | ||
388 | * Note that a connector can be both polled and probed from the hotplug handler, | ||
389 | * in case the hotplug interrupt is known to be unreliable. | ||
390 | */ | ||
391 | bool drm_helper_hpd_irq_event(struct drm_device *dev) | ||
392 | { | ||
393 | struct drm_connector *connector; | ||
394 | enum drm_connector_status old_status; | ||
395 | bool changed = false; | ||
396 | |||
397 | if (!dev->mode_config.poll_enabled) | ||
398 | return false; | ||
399 | |||
400 | mutex_lock(&dev->mode_config.mutex); | ||
401 | list_for_each_entry(connector, &dev->mode_config.connector_list, head) { | ||
402 | |||
403 | /* Only handle HPD capable connectors. */ | ||
404 | if (!(connector->polled & DRM_CONNECTOR_POLL_HPD)) | ||
405 | continue; | ||
406 | |||
407 | old_status = connector->status; | ||
408 | |||
409 | connector->status = connector->funcs->detect(connector, false); | ||
410 | DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %s to %s\n", | ||
411 | connector->base.id, | ||
412 | drm_get_connector_name(connector), | ||
413 | drm_get_connector_status_name(old_status), | ||
414 | drm_get_connector_status_name(connector->status)); | ||
415 | if (old_status != connector->status) | ||
416 | changed = true; | ||
417 | } | ||
418 | |||
419 | mutex_unlock(&dev->mode_config.mutex); | ||
420 | |||
421 | if (changed) | ||
422 | drm_kms_helper_hotplug_event(dev); | ||
423 | |||
424 | return changed; | ||
425 | } | ||
426 | EXPORT_SYMBOL(drm_helper_hpd_irq_event); | ||