diff options
Diffstat (limited to 'drivers/gpu/drm/drm_sysfs.c')
-rw-r--r-- | drivers/gpu/drm/drm_sysfs.c | 329 |
1 files changed, 326 insertions, 3 deletions
diff --git a/drivers/gpu/drm/drm_sysfs.c b/drivers/gpu/drm/drm_sysfs.c index 1611b9bcbe7f..65d72d094c81 100644 --- a/drivers/gpu/drm/drm_sysfs.c +++ b/drivers/gpu/drm/drm_sysfs.c | |||
@@ -20,6 +20,7 @@ | |||
20 | #include "drmP.h" | 20 | #include "drmP.h" |
21 | 21 | ||
22 | #define to_drm_minor(d) container_of(d, struct drm_minor, kdev) | 22 | #define to_drm_minor(d) container_of(d, struct drm_minor, kdev) |
23 | #define to_drm_connector(d) container_of(d, struct drm_connector, kdev) | ||
23 | 24 | ||
24 | /** | 25 | /** |
25 | * drm_sysfs_suspend - DRM class suspend hook | 26 | * drm_sysfs_suspend - DRM class suspend hook |
@@ -34,7 +35,7 @@ static int drm_sysfs_suspend(struct device *dev, pm_message_t state) | |||
34 | struct drm_minor *drm_minor = to_drm_minor(dev); | 35 | struct drm_minor *drm_minor = to_drm_minor(dev); |
35 | struct drm_device *drm_dev = drm_minor->dev; | 36 | struct drm_device *drm_dev = drm_minor->dev; |
36 | 37 | ||
37 | if (drm_dev->driver->suspend) | 38 | if (drm_minor->type == DRM_MINOR_LEGACY && drm_dev->driver->suspend) |
38 | return drm_dev->driver->suspend(drm_dev, state); | 39 | return drm_dev->driver->suspend(drm_dev, state); |
39 | 40 | ||
40 | return 0; | 41 | return 0; |
@@ -52,7 +53,7 @@ static int drm_sysfs_resume(struct device *dev) | |||
52 | struct drm_minor *drm_minor = to_drm_minor(dev); | 53 | struct drm_minor *drm_minor = to_drm_minor(dev); |
53 | struct drm_device *drm_dev = drm_minor->dev; | 54 | struct drm_device *drm_dev = drm_minor->dev; |
54 | 55 | ||
55 | if (drm_dev->driver->resume) | 56 | if (drm_minor->type == DRM_MINOR_LEGACY && drm_dev->driver->resume) |
56 | return drm_dev->driver->resume(drm_dev); | 57 | return drm_dev->driver->resume(drm_dev); |
57 | 58 | ||
58 | return 0; | 59 | return 0; |
@@ -144,6 +145,323 @@ static void drm_sysfs_device_release(struct device *dev) | |||
144 | return; | 145 | return; |
145 | } | 146 | } |
146 | 147 | ||
148 | /* | ||
149 | * Connector properties | ||
150 | */ | ||
151 | static ssize_t status_show(struct device *device, | ||
152 | struct device_attribute *attr, | ||
153 | char *buf) | ||
154 | { | ||
155 | struct drm_connector *connector = to_drm_connector(device); | ||
156 | enum drm_connector_status status; | ||
157 | |||
158 | status = connector->funcs->detect(connector); | ||
159 | return snprintf(buf, PAGE_SIZE, "%s", | ||
160 | drm_get_connector_status_name(status)); | ||
161 | } | ||
162 | |||
163 | static ssize_t dpms_show(struct device *device, | ||
164 | struct device_attribute *attr, | ||
165 | char *buf) | ||
166 | { | ||
167 | struct drm_connector *connector = to_drm_connector(device); | ||
168 | struct drm_device *dev = connector->dev; | ||
169 | uint64_t dpms_status; | ||
170 | int ret; | ||
171 | |||
172 | ret = drm_connector_property_get_value(connector, | ||
173 | dev->mode_config.dpms_property, | ||
174 | &dpms_status); | ||
175 | if (ret) | ||
176 | return 0; | ||
177 | |||
178 | return snprintf(buf, PAGE_SIZE, "%s", | ||
179 | drm_get_dpms_name((int)dpms_status)); | ||
180 | } | ||
181 | |||
182 | static ssize_t enabled_show(struct device *device, | ||
183 | struct device_attribute *attr, | ||
184 | char *buf) | ||
185 | { | ||
186 | struct drm_connector *connector = to_drm_connector(device); | ||
187 | |||
188 | return snprintf(buf, PAGE_SIZE, connector->encoder ? "enabled" : | ||
189 | "disabled"); | ||
190 | } | ||
191 | |||
192 | static ssize_t edid_show(struct kobject *kobj, struct bin_attribute *attr, | ||
193 | char *buf, loff_t off, size_t count) | ||
194 | { | ||
195 | struct device *connector_dev = container_of(kobj, struct device, kobj); | ||
196 | struct drm_connector *connector = to_drm_connector(connector_dev); | ||
197 | unsigned char *edid; | ||
198 | size_t size; | ||
199 | |||
200 | if (!connector->edid_blob_ptr) | ||
201 | return 0; | ||
202 | |||
203 | edid = connector->edid_blob_ptr->data; | ||
204 | size = connector->edid_blob_ptr->length; | ||
205 | if (!edid) | ||
206 | return 0; | ||
207 | |||
208 | if (off >= size) | ||
209 | return 0; | ||
210 | |||
211 | if (off + count > size) | ||
212 | count = size - off; | ||
213 | memcpy(buf, edid + off, count); | ||
214 | |||
215 | return count; | ||
216 | } | ||
217 | |||
218 | static ssize_t modes_show(struct device *device, | ||
219 | struct device_attribute *attr, | ||
220 | char *buf) | ||
221 | { | ||
222 | struct drm_connector *connector = to_drm_connector(device); | ||
223 | struct drm_display_mode *mode; | ||
224 | int written = 0; | ||
225 | |||
226 | list_for_each_entry(mode, &connector->modes, head) { | ||
227 | written += snprintf(buf + written, PAGE_SIZE - written, "%s\n", | ||
228 | mode->name); | ||
229 | } | ||
230 | |||
231 | return written; | ||
232 | } | ||
233 | |||
234 | static ssize_t subconnector_show(struct device *device, | ||
235 | struct device_attribute *attr, | ||
236 | char *buf) | ||
237 | { | ||
238 | struct drm_connector *connector = to_drm_connector(device); | ||
239 | struct drm_device *dev = connector->dev; | ||
240 | struct drm_property *prop = NULL; | ||
241 | uint64_t subconnector; | ||
242 | int is_tv = 0; | ||
243 | int ret; | ||
244 | |||
245 | switch (connector->connector_type) { | ||
246 | case DRM_MODE_CONNECTOR_DVII: | ||
247 | prop = dev->mode_config.dvi_i_subconnector_property; | ||
248 | break; | ||
249 | case DRM_MODE_CONNECTOR_Composite: | ||
250 | case DRM_MODE_CONNECTOR_SVIDEO: | ||
251 | case DRM_MODE_CONNECTOR_Component: | ||
252 | prop = dev->mode_config.tv_subconnector_property; | ||
253 | is_tv = 1; | ||
254 | break; | ||
255 | default: | ||
256 | DRM_ERROR("Wrong connector type for this property\n"); | ||
257 | return 0; | ||
258 | } | ||
259 | |||
260 | if (!prop) { | ||
261 | DRM_ERROR("Unable to find subconnector property\n"); | ||
262 | return 0; | ||
263 | } | ||
264 | |||
265 | ret = drm_connector_property_get_value(connector, prop, &subconnector); | ||
266 | if (ret) | ||
267 | return 0; | ||
268 | |||
269 | return snprintf(buf, PAGE_SIZE, "%s", is_tv ? | ||
270 | drm_get_tv_subconnector_name((int)subconnector) : | ||
271 | drm_get_dvi_i_subconnector_name((int)subconnector)); | ||
272 | } | ||
273 | |||
274 | static ssize_t select_subconnector_show(struct device *device, | ||
275 | struct device_attribute *attr, | ||
276 | char *buf) | ||
277 | { | ||
278 | struct drm_connector *connector = to_drm_connector(device); | ||
279 | struct drm_device *dev = connector->dev; | ||
280 | struct drm_property *prop = NULL; | ||
281 | uint64_t subconnector; | ||
282 | int is_tv = 0; | ||
283 | int ret; | ||
284 | |||
285 | switch (connector->connector_type) { | ||
286 | case DRM_MODE_CONNECTOR_DVII: | ||
287 | prop = dev->mode_config.dvi_i_select_subconnector_property; | ||
288 | break; | ||
289 | case DRM_MODE_CONNECTOR_Composite: | ||
290 | case DRM_MODE_CONNECTOR_SVIDEO: | ||
291 | case DRM_MODE_CONNECTOR_Component: | ||
292 | prop = dev->mode_config.tv_select_subconnector_property; | ||
293 | is_tv = 1; | ||
294 | break; | ||
295 | default: | ||
296 | DRM_ERROR("Wrong connector type for this property\n"); | ||
297 | return 0; | ||
298 | } | ||
299 | |||
300 | if (!prop) { | ||
301 | DRM_ERROR("Unable to find select subconnector property\n"); | ||
302 | return 0; | ||
303 | } | ||
304 | |||
305 | ret = drm_connector_property_get_value(connector, prop, &subconnector); | ||
306 | if (ret) | ||
307 | return 0; | ||
308 | |||
309 | return snprintf(buf, PAGE_SIZE, "%s", is_tv ? | ||
310 | drm_get_tv_select_name((int)subconnector) : | ||
311 | drm_get_dvi_i_select_name((int)subconnector)); | ||
312 | } | ||
313 | |||
314 | static struct device_attribute connector_attrs[] = { | ||
315 | __ATTR_RO(status), | ||
316 | __ATTR_RO(enabled), | ||
317 | __ATTR_RO(dpms), | ||
318 | __ATTR_RO(modes), | ||
319 | }; | ||
320 | |||
321 | /* These attributes are for both DVI-I connectors and all types of tv-out. */ | ||
322 | static struct device_attribute connector_attrs_opt1[] = { | ||
323 | __ATTR_RO(subconnector), | ||
324 | __ATTR_RO(select_subconnector), | ||
325 | }; | ||
326 | |||
327 | static struct bin_attribute edid_attr = { | ||
328 | .attr.name = "edid", | ||
329 | .size = 128, | ||
330 | .read = edid_show, | ||
331 | }; | ||
332 | |||
333 | /** | ||
334 | * drm_sysfs_connector_add - add an connector to sysfs | ||
335 | * @connector: connector to add | ||
336 | * | ||
337 | * Create an connector device in sysfs, along with its associated connector | ||
338 | * properties (so far, connection status, dpms, mode list & edid) and | ||
339 | * generate a hotplug event so userspace knows there's a new connector | ||
340 | * available. | ||
341 | * | ||
342 | * Note: | ||
343 | * This routine should only be called *once* for each DRM minor registered. | ||
344 | * A second call for an already registered device will trigger the BUG_ON | ||
345 | * below. | ||
346 | */ | ||
347 | int drm_sysfs_connector_add(struct drm_connector *connector) | ||
348 | { | ||
349 | struct drm_device *dev = connector->dev; | ||
350 | int ret = 0, i, j; | ||
351 | |||
352 | /* We shouldn't get called more than once for the same connector */ | ||
353 | BUG_ON(device_is_registered(&connector->kdev)); | ||
354 | |||
355 | connector->kdev.parent = &dev->primary->kdev; | ||
356 | connector->kdev.class = drm_class; | ||
357 | connector->kdev.release = drm_sysfs_device_release; | ||
358 | |||
359 | DRM_DEBUG("adding \"%s\" to sysfs\n", | ||
360 | drm_get_connector_name(connector)); | ||
361 | |||
362 | snprintf(connector->kdev.bus_id, BUS_ID_SIZE, "card%d-%s", | ||
363 | dev->primary->index, drm_get_connector_name(connector)); | ||
364 | ret = device_register(&connector->kdev); | ||
365 | |||
366 | if (ret) { | ||
367 | DRM_ERROR("failed to register connector device: %d\n", ret); | ||
368 | goto out; | ||
369 | } | ||
370 | |||
371 | /* Standard attributes */ | ||
372 | |||
373 | for (i = 0; i < ARRAY_SIZE(connector_attrs); i++) { | ||
374 | ret = device_create_file(&connector->kdev, &connector_attrs[i]); | ||
375 | if (ret) | ||
376 | goto err_out_files; | ||
377 | } | ||
378 | |||
379 | /* Optional attributes */ | ||
380 | /* | ||
381 | * In the long run it maybe a good idea to make one set of | ||
382 | * optionals per connector type. | ||
383 | */ | ||
384 | switch (connector->connector_type) { | ||
385 | case DRM_MODE_CONNECTOR_DVII: | ||
386 | case DRM_MODE_CONNECTOR_Composite: | ||
387 | case DRM_MODE_CONNECTOR_SVIDEO: | ||
388 | case DRM_MODE_CONNECTOR_Component: | ||
389 | for (i = 0; i < ARRAY_SIZE(connector_attrs_opt1); i++) { | ||
390 | ret = device_create_file(&connector->kdev, &connector_attrs_opt1[i]); | ||
391 | if (ret) | ||
392 | goto err_out_files; | ||
393 | } | ||
394 | break; | ||
395 | default: | ||
396 | break; | ||
397 | } | ||
398 | |||
399 | ret = sysfs_create_bin_file(&connector->kdev.kobj, &edid_attr); | ||
400 | if (ret) | ||
401 | goto err_out_files; | ||
402 | |||
403 | /* Let userspace know we have a new connector */ | ||
404 | drm_sysfs_hotplug_event(dev); | ||
405 | |||
406 | return 0; | ||
407 | |||
408 | err_out_files: | ||
409 | if (i > 0) | ||
410 | for (j = 0; j < i; j++) | ||
411 | device_remove_file(&connector->kdev, | ||
412 | &connector_attrs[i]); | ||
413 | device_unregister(&connector->kdev); | ||
414 | |||
415 | out: | ||
416 | return ret; | ||
417 | } | ||
418 | EXPORT_SYMBOL(drm_sysfs_connector_add); | ||
419 | |||
420 | /** | ||
421 | * drm_sysfs_connector_remove - remove an connector device from sysfs | ||
422 | * @connector: connector to remove | ||
423 | * | ||
424 | * Remove @connector and its associated attributes from sysfs. Note that | ||
425 | * the device model core will take care of sending the "remove" uevent | ||
426 | * at this time, so we don't need to do it. | ||
427 | * | ||
428 | * Note: | ||
429 | * This routine should only be called if the connector was previously | ||
430 | * successfully registered. If @connector hasn't been registered yet, | ||
431 | * you'll likely see a panic somewhere deep in sysfs code when called. | ||
432 | */ | ||
433 | void drm_sysfs_connector_remove(struct drm_connector *connector) | ||
434 | { | ||
435 | int i; | ||
436 | |||
437 | DRM_DEBUG("removing \"%s\" from sysfs\n", | ||
438 | drm_get_connector_name(connector)); | ||
439 | |||
440 | for (i = 0; i < ARRAY_SIZE(connector_attrs); i++) | ||
441 | device_remove_file(&connector->kdev, &connector_attrs[i]); | ||
442 | sysfs_remove_bin_file(&connector->kdev.kobj, &edid_attr); | ||
443 | device_unregister(&connector->kdev); | ||
444 | } | ||
445 | EXPORT_SYMBOL(drm_sysfs_connector_remove); | ||
446 | |||
447 | /** | ||
448 | * drm_sysfs_hotplug_event - generate a DRM uevent | ||
449 | * @dev: DRM device | ||
450 | * | ||
451 | * Send a uevent for the DRM device specified by @dev. Currently we only | ||
452 | * set HOTPLUG=1 in the uevent environment, but this could be expanded to | ||
453 | * deal with other types of events. | ||
454 | */ | ||
455 | void drm_sysfs_hotplug_event(struct drm_device *dev) | ||
456 | { | ||
457 | char *event_string = "HOTPLUG=1"; | ||
458 | char *envp[] = { event_string, NULL }; | ||
459 | |||
460 | DRM_DEBUG("generating hotplug event\n"); | ||
461 | |||
462 | kobject_uevent_env(&dev->primary->kdev.kobj, KOBJ_CHANGE, envp); | ||
463 | } | ||
464 | |||
147 | /** | 465 | /** |
148 | * drm_sysfs_device_add - adds a class device to sysfs for a character driver | 466 | * drm_sysfs_device_add - adds a class device to sysfs for a character driver |
149 | * @dev: DRM device to be added | 467 | * @dev: DRM device to be added |
@@ -163,7 +481,12 @@ int drm_sysfs_device_add(struct drm_minor *minor) | |||
163 | minor->kdev.class = drm_class; | 481 | minor->kdev.class = drm_class; |
164 | minor->kdev.release = drm_sysfs_device_release; | 482 | minor->kdev.release = drm_sysfs_device_release; |
165 | minor->kdev.devt = minor->device; | 483 | minor->kdev.devt = minor->device; |
166 | minor_str = "card%d"; | 484 | if (minor->type == DRM_MINOR_CONTROL) |
485 | minor_str = "controlD%d"; | ||
486 | else if (minor->type == DRM_MINOR_RENDER) | ||
487 | minor_str = "renderD%d"; | ||
488 | else | ||
489 | minor_str = "card%d"; | ||
167 | 490 | ||
168 | snprintf(minor->kdev.bus_id, BUS_ID_SIZE, minor_str, minor->index); | 491 | snprintf(minor->kdev.bus_id, BUS_ID_SIZE, minor_str, minor->index); |
169 | 492 | ||