From 5a3ce06f3a3dfa9412b9660c1e1f35d24c815dbb Mon Sep 17 00:00:00 2001 From: Jesse Barnes Date: Tue, 8 Apr 2008 12:42:23 -0700 Subject: [PATCH] Improved DRM sysfs support This patch ties outputs, output properties and hotplug events into the DRM core. Each output has a corresponding directory under the primary DRM device (usually card0) containing dpms, edid, modes, and connection status files. New hotplug change events occur when outputs are added or hotplug events are detected. --- linux-core/drmP.h | 4 + linux-core/drm_crtc.c | 46 +++++++++ linux-core/drm_crtc.h | 6 ++ linux-core/drm_sysfs.c | 197 +++++++++++++++++++++++++++++++++++-- linux-core/intel_display.c | 5 +- linux-core/intel_drv.h | 1 + 6 files changed, 249 insertions(+), 10 deletions(-) diff --git a/linux-core/drmP.h b/linux-core/drmP.h index f96e6bee..24f8c3d8 100644 --- a/linux-core/drmP.h +++ b/linux-core/drmP.h @@ -1311,7 +1311,11 @@ struct drm_sysfs_class; extern struct class *drm_sysfs_create(struct module *owner, char *name); extern void drm_sysfs_destroy(void); extern int drm_sysfs_device_add(struct drm_minor *minor); +extern void drm_sysfs_hotplug_event(struct drm_device *dev); extern void drm_sysfs_device_remove(struct drm_minor *minor); +extern char *drm_get_output_status_name(enum drm_output_status status); +extern int drm_sysfs_output_add(struct drm_output *output); +extern void drm_sysfs_output_remove(struct drm_output *output); /* * Basic memory manager support (drm_mm.c) diff --git a/linux-core/drm_crtc.c b/linux-core/drm_crtc.c index 0cd0ebd0..19155e1f 100644 --- a/linux-core/drm_crtc.c +++ b/linux-core/drm_crtc.c @@ -47,6 +47,18 @@ static struct drm_prop_enum_list drm_dpms_enum_list[] = { DPMSModeSuspend, "Suspend" }, { DPMSModeOff, "Off" } }; + +char *drm_get_dpms_name(int val) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(drm_dpms_enum_list); i++) + if (drm_dpms_enum_list[i].type == val) + return drm_dpms_enum_list[i].name; + + return "unknown"; +} + static struct drm_prop_enum_list drm_conn_enum_list[] = { { ConnectorUnknown, "Unknown" }, { ConnectorVGA, "VGA" }, @@ -79,6 +91,16 @@ char *drm_get_output_name(struct drm_output *output) return buf; } +char *drm_get_output_status_name(enum drm_output_status status) +{ + if (status == output_status_connected) + return "connected"; + else if (status == output_status_disconnected) + return "disconnected"; + else + return "unknown"; +} + /** * drm_idr_get - allocate a new identifier * @dev: DRM device @@ -629,6 +651,8 @@ struct drm_output *drm_output_create(struct drm_device *dev, /* output_set_monitor(output)? */ /* check for output_ignored(output)? */ + drm_sysfs_output_add(output); + mutex_lock(&dev->mode_config.mutex); list_add_tail(&output->head, &dev->mode_config.output_list); dev->mode_config.num_output++; @@ -659,6 +683,8 @@ void drm_output_destroy(struct drm_output *output) struct drm_device *dev = output->dev; struct drm_display_mode *mode, *t; + drm_sysfs_output_remove(output); + if (*output->funcs->cleanup) (*output->funcs->cleanup)(output); @@ -1226,6 +1252,8 @@ int drm_hotplug_stage_two(struct drm_device *dev, struct drm_output *output, DRM_ERROR("failed to set mode after hotplug\n"); } + drm_sysfs_hotplug_event(dev); + drm_disable_unused_functions(dev); return 0; @@ -2223,6 +2251,24 @@ int drm_output_property_set_value(struct drm_output *output, } EXPORT_SYMBOL(drm_output_property_set_value); +int drm_output_property_get_value(struct drm_output *output, + struct drm_property *property, uint64_t *val) +{ + int i; + + for (i = 0; i < DRM_OUTPUT_MAX_PROPERTY; i++) { + if (output->property_ids[i] == property->id) { + *val = output->property_values[i]; + break; + } + } + + if (i == DRM_OUTPUT_MAX_PROPERTY) + return -EINVAL; + return 0; +} +EXPORT_SYMBOL(drm_output_property_get_value); + int drm_mode_getproperty_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv) { diff --git a/linux-core/drm_crtc.h b/linux-core/drm_crtc.h index bbeab603..ac0d2d5a 100644 --- a/linux-core/drm_crtc.h +++ b/linux-core/drm_crtc.h @@ -451,6 +451,8 @@ struct drm_output_funcs { */ struct drm_output { struct drm_device *dev; + struct device kdev; + struct device_attribute *attr; struct list_head head; struct drm_crtc *crtc; int id; /* idr assigned */ @@ -563,6 +565,7 @@ struct drm_output *drm_output_create(struct drm_device *dev, int type); extern char *drm_get_output_name(struct drm_output *output); +extern char *drm_get_dpms_name(int val); extern void drm_output_destroy(struct drm_output *output); extern void drm_fb_release(struct file *filp); @@ -606,6 +609,9 @@ extern int drm_mode_output_update_edid_property(struct drm_output *output, extern int drm_output_property_set_value(struct drm_output *output, struct drm_property *property, uint64_t value); +extern int drm_output_property_get_value(struct drm_output *output, + struct drm_property *property, + uint64_t *value); extern struct drm_display_mode *drm_crtc_mode_create(struct drm_device *dev); extern bool drm_initial_config(struct drm_device *dev, bool cangrow); extern void drm_framebuffer_set_object(struct drm_device *dev, diff --git a/linux-core/drm_sysfs.c b/linux-core/drm_sysfs.c index 3372a713..3e682c99 100644 --- a/linux-core/drm_sysfs.c +++ b/linux-core/drm_sysfs.c @@ -133,10 +133,6 @@ static ssize_t show_dri(struct device *device, struct device_attribute *attr, return snprintf(buf, PAGE_SIZE, "%s\n", drm_dev->driver->pci_driver.name); } -static struct device_attribute device_attrs[] = { - __ATTR(dri_library_name, S_IRUGO, show_dri, NULL), -}; - /** * drm_sysfs_device_release - do nothing * @dev: Linux device @@ -150,6 +146,189 @@ static void drm_sysfs_device_release(struct device *dev) return; } +/* + * Output properties + */ +static ssize_t status_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + struct drm_output *output = container_of(device, struct drm_output, kdev); + return snprintf(buf, PAGE_SIZE, "%s", + drm_get_output_status_name(output->funcs->detect(output))); +} + +static ssize_t dpms_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + struct drm_output *output = container_of(device, struct drm_output, kdev); + struct drm_device *dev = output->dev; + uint64_t dpms_status; + int ret; + + ret = drm_output_property_get_value(output, + dev->mode_config.dpms_property, + &dpms_status); + if (ret) + return 0; + + return snprintf(buf, PAGE_SIZE, "%s", drm_get_dpms_name((int)dpms_status)); +} + +static ssize_t edid_show(struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct device *output_dev = container_of(kobj, struct device, kobj); + struct drm_output *output = container_of(output_dev, struct drm_output, + kdev); + unsigned char *edid; + size_t size; + + if (!output->edid_blob_ptr) + return 0; + + edid = output->edid_blob_ptr->data; + size = output->edid_blob_ptr->length; + if (!edid) + return 0; + + if (off >= size) + return 0; + + if (off + count > size) + count = size - off; + memcpy(buf, edid + off, count); + + return count; +} + +static ssize_t modes_show(struct device *device, + struct device_attribute *attr, + char *buf) +{ + struct drm_output *output = container_of(device, struct drm_output, kdev); + struct drm_display_mode *mode; + int written = 0; + + list_for_each_entry(mode, &output->modes, head) { + written += snprintf(buf + written, PAGE_SIZE - written, "%s\n", + mode->name); + } + + return written; +} + +static struct device_attribute output_attrs[] = { + __ATTR_RO(status), + __ATTR_RO(dpms), + __ATTR_RO(modes), +}; + +static struct bin_attribute edid_attr = { + .attr.name = "edid", + .size = 128, + .read = edid_show, +}; + +/** + * drm_sysfs_output_add - add an output to sysfs + * @output: output to add + * + * Create an output device in sysfs, along with its associated output + * properties (so far, connection status, dpms, mode list & edid) and + * generate a hotplug event so userspace knows there's a new output + * available. + */ +int drm_sysfs_output_add(struct drm_output *output) +{ + struct drm_device *dev = output->dev; + int ret = 0, i, j; + + if (device_is_registered(&output->kdev)) + return 0; + + output->kdev.parent = &dev->primary->kdev; + output->kdev.class = drm_class; + output->kdev.release = drm_sysfs_device_release; + + DRM_DEBUG("adding \"%s\" to sysfs", drm_get_output_name(output)); + + snprintf(output->kdev.bus_id, BUS_ID_SIZE, "card%d-%s", + dev->primary->index, drm_get_output_name(output)); + ret = device_register(&output->kdev); + + if (ret) { + DRM_ERROR("failed to register output device: %d\n", ret); + goto out; + } + + for (i = 0; i < ARRAY_SIZE(output_attrs); i++) { + ret = device_create_file(&output->kdev, &output_attrs[i]); + if (ret) + goto err_out_files; + } + + ret = sysfs_create_bin_file(&output->kdev.kobj, &edid_attr); + if (ret) + goto err_out_files; + + /* Let userspace know we have a new output */ + drm_sysfs_hotplug_event(dev); + + return 0; + +err_out_files: + if (i > 0) + for (j = 0; j < i; j++) + device_remove_file(&output->kdev, &output_attrs[i]); + device_unregister(&output->kdev); + +out: + return ret; +} + +/** + * drm_sysfs_output_remove - remove an output device from sysfs + * @output: output to remove + * + * Remove @output and its associated attributes from sysfs. Note that + * the device model core will take care of sending the "remove" uevent + * at this time, so we don't need to do it. + */ +void drm_sysfs_output_remove(struct drm_output *output) +{ + int i; + + DRM_DEBUG("removing \"%s\" from sysfs\n", drm_get_output_name(output)); + for (i = 0; i < i; i++) + device_remove_file(&output->kdev, &output_attrs[i]); + sysfs_remove_bin_file(&output->kdev.kobj, &edid_attr); + device_unregister(&output->kdev); +} + +/** + * drm_sysfs_hotplug_event - generate a DRM uevent + * @dev: DRM device + * + * Send a uevent for the DRM device specified by @dev. Currently we only + * set HOTPLUG=1 in the uevent environment, but this could be expanded to + * deal with other types of events. + */ +void drm_sysfs_hotplug_event(struct drm_device *dev) +{ + char *event_string = "HOTPLUG=1"; + char *envp[] = { event_string, NULL }; + + DRM_DEBUG("generating hotplug event\n"); + + kobject_uevent_env(&dev->primary->kdev.kobj, KOBJ_CHANGE, envp); +} + +static struct device_attribute dri_attrs[] = { + __ATTR(dri_library_name, S_IRUGO, show_dri, NULL), +}; + /** * drm_sysfs_device_add - adds a class device to sysfs for a character driver * @dev: DRM device to be added @@ -184,8 +363,8 @@ int drm_sysfs_device_add(struct drm_minor *minor) goto err_out; } - for (i = 0; i < ARRAY_SIZE(device_attrs); i++) { - err = device_create_file(&minor->kdev, &device_attrs[i]); + for (i = 0; i < ARRAY_SIZE(dri_attrs); i++) { + err = device_create_file(&minor->kdev, &dri_attrs[i]); if (err) goto err_out_files; } @@ -195,7 +374,7 @@ int drm_sysfs_device_add(struct drm_minor *minor) err_out_files: if (i > 0) for (j = 0; j < i; j++) - device_remove_file(&minor->kdev, &device_attrs[i]); + device_remove_file(&minor->kdev, &dri_attrs[i]); device_unregister(&minor->kdev); err_out: @@ -213,7 +392,7 @@ void drm_sysfs_device_remove(struct drm_minor *minor) { int i; - for (i = 0; i < ARRAY_SIZE(device_attrs); i++) - device_remove_file(&minor->kdev, &device_attrs[i]); + for (i = 0; i < ARRAY_SIZE(dri_attrs); i++) + device_remove_file(&minor->kdev, &dri_attrs[i]); device_unregister(&minor->kdev); } diff --git a/linux-core/intel_display.c b/linux-core/intel_display.c index fa2b9bea..0615c1c4 100644 --- a/linux-core/intel_display.c +++ b/linux-core/intel_display.c @@ -1292,7 +1292,10 @@ static void intel_setup_outputs(struct drm_device *dev) intel_sdvo_init(dev, SDVOB); intel_sdvo_init(dev, SDVOC); } - +#if 0 + if (IS_I9XX(dev) && !IS_I915G(dev)) + intel_tv_init(dev); +#endif list_for_each_entry(output, &dev->mode_config.output_list, head) { struct intel_output *intel_output = output->driver_private; int crtc_mask = 0, clone_mask = 0; diff --git a/linux-core/intel_drv.h b/linux-core/intel_drv.h index a36fd3f1..62e21a5e 100644 --- a/linux-core/intel_drv.h +++ b/linux-core/intel_drv.h @@ -68,6 +68,7 @@ extern bool intel_ddc_probe(struct drm_output *output); extern void intel_crt_init(struct drm_device *dev); extern void intel_sdvo_init(struct drm_device *dev, int output_device); +extern void intel_tv_init(struct drm_device *dev); extern void intel_lvds_init(struct drm_device *dev); extern void intel_crtc_load_lut(struct drm_crtc *crtc);