71a7507afb
Here is the set of driver core and kernfs changes for 6.2-rc1. The "big" change in here is the addition of a new macro, container_of_const() that will preserve the "const-ness" of a pointer passed into it. The "problem" of the current container_of() macro is that if you pass in a "const *", out of it can comes a non-const pointer unless you specifically ask for it. For many usages, we want to preserve the "const" attribute by using the same call. For a specific example, this series changes the kobj_to_dev() macro to use it, allowing it to be used no matter what the const value is. This prevents every subsystem from having to declare 2 different individual macros (i.e. kobj_const_to_dev() and kobj_to_dev()) and having the compiler enforce the const value at build time, which having 2 macros would not do either. The driver for all of this have been discussions with the Rust kernel developers as to how to properly mark driver core, and kobject, objects as being "non-mutable". The changes to the kobject and driver core in this pull request are the result of that, as there are lots of paths where kobjects and device pointers are not modified at all, so marking them as "const" allows the compiler to enforce this. So, a nice side affect of the Rust development effort has been already to clean up the driver core code to be more obvious about object rules. All of this has been bike-shedded in quite a lot of detail on lkml with different names and implementations resulting in the tiny version we have in here, much better than my original proposal. Lots of subsystem maintainers have acked the changes as well. Other than this change, included in here are smaller stuff like: - kernfs fixes and updates to handle lock contention better - vmlinux.lds.h fixes and updates - sysfs and debugfs documentation updates - device property updates All of these have been in the linux-next tree for quite a while with no problems, OTHER than some merge issues with other trees that should be obvious when you hit them (block tree deletes a driver that this tree modifies, iommufd tree modifies code that this tree also touches). If there are merge problems with these trees, please let me know. Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> -----BEGIN PGP SIGNATURE----- iG0EABECAC0WIQT0tgzFv3jCIUoxPcsxR9QN2y37KQUCY5wz3A8cZ3JlZ0Brcm9h aC5jb20ACgkQMUfUDdst+yks0ACeKYUlVgCsER8eYW+x18szFa2QTXgAn2h/VhZe 1Fp53boFaQkGBjl8mGF8 =v+FB -----END PGP SIGNATURE----- Merge tag 'driver-core-6.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core Pull driver core updates from Greg KH: "Here is the set of driver core and kernfs changes for 6.2-rc1. The "big" change in here is the addition of a new macro, container_of_const() that will preserve the "const-ness" of a pointer passed into it. The "problem" of the current container_of() macro is that if you pass in a "const *", out of it can comes a non-const pointer unless you specifically ask for it. For many usages, we want to preserve the "const" attribute by using the same call. For a specific example, this series changes the kobj_to_dev() macro to use it, allowing it to be used no matter what the const value is. This prevents every subsystem from having to declare 2 different individual macros (i.e. kobj_const_to_dev() and kobj_to_dev()) and having the compiler enforce the const value at build time, which having 2 macros would not do either. The driver for all of this have been discussions with the Rust kernel developers as to how to properly mark driver core, and kobject, objects as being "non-mutable". The changes to the kobject and driver core in this pull request are the result of that, as there are lots of paths where kobjects and device pointers are not modified at all, so marking them as "const" allows the compiler to enforce this. So, a nice side affect of the Rust development effort has been already to clean up the driver core code to be more obvious about object rules. All of this has been bike-shedded in quite a lot of detail on lkml with different names and implementations resulting in the tiny version we have in here, much better than my original proposal. Lots of subsystem maintainers have acked the changes as well. Other than this change, included in here are smaller stuff like: - kernfs fixes and updates to handle lock contention better - vmlinux.lds.h fixes and updates - sysfs and debugfs documentation updates - device property updates All of these have been in the linux-next tree for quite a while with no problems" * tag 'driver-core-6.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/gregkh/driver-core: (58 commits) device property: Fix documentation for fwnode_get_next_parent() firmware_loader: fix up to_fw_sysfs() to preserve const usb.h: take advantage of container_of_const() device.h: move kobj_to_dev() to use container_of_const() container_of: add container_of_const() that preserves const-ness of the pointer driver core: fix up missed drivers/s390/char/hmcdrv_dev.c class.devnode() conversion. driver core: fix up missed scsi/cxlflash class.devnode() conversion. driver core: fix up some missing class.devnode() conversions. driver core: make struct class.devnode() take a const * driver core: make struct class.dev_uevent() take a const * cacheinfo: Remove of_node_put() for fw_token device property: Add a blank line in Kconfig of tests device property: Rename goto label to be more precise device property: Move PROPERTY_ENTRY_BOOL() a bit down device property: Get rid of __PROPERTY_ENTRY_ARRAY_EL*SIZE*() kernfs: fix all kernel-doc warnings and multiple typos driver core: pass a const * into of_device_uevent() kobject: kset_uevent_ops: make name() callback take a const * kobject: kset_uevent_ops: make filter() callback take a const * kobject: make kobject_namespace take a const * ...
541 lines
13 KiB
C
541 lines
13 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
|
|
/*
|
|
* drm_sysfs.c - Modifications to drm_sysfs_class.c to support
|
|
* extra sysfs attribute from DRM. Normal drm_sysfs_class
|
|
* does not allow adding attributes.
|
|
*
|
|
* Copyright (c) 2004 Jon Smirl <jonsmirl@gmail.com>
|
|
* Copyright (c) 2003-2004 Greg Kroah-Hartman <greg@kroah.com>
|
|
* Copyright (c) 2003-2004 IBM Corp.
|
|
*/
|
|
|
|
#include <linux/acpi.h>
|
|
#include <linux/device.h>
|
|
#include <linux/err.h>
|
|
#include <linux/export.h>
|
|
#include <linux/gfp.h>
|
|
#include <linux/i2c.h>
|
|
#include <linux/kdev_t.h>
|
|
#include <linux/slab.h>
|
|
|
|
#include <drm/drm_accel.h>
|
|
#include <drm/drm_connector.h>
|
|
#include <drm/drm_device.h>
|
|
#include <drm/drm_file.h>
|
|
#include <drm/drm_modes.h>
|
|
#include <drm/drm_print.h>
|
|
#include <drm/drm_property.h>
|
|
#include <drm/drm_sysfs.h>
|
|
|
|
#include "drm_internal.h"
|
|
#include "drm_crtc_internal.h"
|
|
|
|
#define to_drm_minor(d) dev_get_drvdata(d)
|
|
#define to_drm_connector(d) dev_get_drvdata(d)
|
|
|
|
/**
|
|
* DOC: overview
|
|
*
|
|
* DRM provides very little additional support to drivers for sysfs
|
|
* interactions, beyond just all the standard stuff. Drivers who want to expose
|
|
* additional sysfs properties and property groups can attach them at either
|
|
* &drm_device.dev or &drm_connector.kdev.
|
|
*
|
|
* Registration is automatically handled when calling drm_dev_register(), or
|
|
* drm_connector_register() in case of hot-plugged connectors. Unregistration is
|
|
* also automatically handled by drm_dev_unregister() and
|
|
* drm_connector_unregister().
|
|
*/
|
|
|
|
static struct device_type drm_sysfs_device_minor = {
|
|
.name = "drm_minor"
|
|
};
|
|
|
|
static struct device_type drm_sysfs_device_connector = {
|
|
.name = "drm_connector",
|
|
};
|
|
|
|
struct class *drm_class;
|
|
|
|
#ifdef CONFIG_ACPI
|
|
static bool drm_connector_acpi_bus_match(struct device *dev)
|
|
{
|
|
return dev->type == &drm_sysfs_device_connector;
|
|
}
|
|
|
|
static struct acpi_device *drm_connector_acpi_find_companion(struct device *dev)
|
|
{
|
|
struct drm_connector *connector = to_drm_connector(dev);
|
|
|
|
return to_acpi_device_node(connector->fwnode);
|
|
}
|
|
|
|
static struct acpi_bus_type drm_connector_acpi_bus = {
|
|
.name = "drm_connector",
|
|
.match = drm_connector_acpi_bus_match,
|
|
.find_companion = drm_connector_acpi_find_companion,
|
|
};
|
|
|
|
static void drm_sysfs_acpi_register(void)
|
|
{
|
|
register_acpi_bus_type(&drm_connector_acpi_bus);
|
|
}
|
|
|
|
static void drm_sysfs_acpi_unregister(void)
|
|
{
|
|
unregister_acpi_bus_type(&drm_connector_acpi_bus);
|
|
}
|
|
#else
|
|
static void drm_sysfs_acpi_register(void) { }
|
|
static void drm_sysfs_acpi_unregister(void) { }
|
|
#endif
|
|
|
|
static char *drm_devnode(const struct device *dev, umode_t *mode)
|
|
{
|
|
return kasprintf(GFP_KERNEL, "dri/%s", dev_name(dev));
|
|
}
|
|
|
|
static CLASS_ATTR_STRING(version, S_IRUGO, "drm 1.1.0 20060810");
|
|
|
|
/**
|
|
* drm_sysfs_init - initialize sysfs helpers
|
|
*
|
|
* This is used to create the DRM class, which is the implicit parent of any
|
|
* other top-level DRM sysfs objects.
|
|
*
|
|
* You must call drm_sysfs_destroy() to release the allocated resources.
|
|
*
|
|
* Return: 0 on success, negative error code on failure.
|
|
*/
|
|
int drm_sysfs_init(void)
|
|
{
|
|
int err;
|
|
|
|
drm_class = class_create(THIS_MODULE, "drm");
|
|
if (IS_ERR(drm_class))
|
|
return PTR_ERR(drm_class);
|
|
|
|
err = class_create_file(drm_class, &class_attr_version.attr);
|
|
if (err) {
|
|
class_destroy(drm_class);
|
|
drm_class = NULL;
|
|
return err;
|
|
}
|
|
|
|
drm_class->devnode = drm_devnode;
|
|
|
|
drm_sysfs_acpi_register();
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* drm_sysfs_destroy - destroys DRM class
|
|
*
|
|
* Destroy the DRM device class.
|
|
*/
|
|
void drm_sysfs_destroy(void)
|
|
{
|
|
if (IS_ERR_OR_NULL(drm_class))
|
|
return;
|
|
drm_sysfs_acpi_unregister();
|
|
class_remove_file(drm_class, &class_attr_version.attr);
|
|
class_destroy(drm_class);
|
|
drm_class = NULL;
|
|
}
|
|
|
|
static void drm_sysfs_release(struct device *dev)
|
|
{
|
|
kfree(dev);
|
|
}
|
|
|
|
/*
|
|
* Connector properties
|
|
*/
|
|
static ssize_t status_store(struct device *device,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
struct drm_device *dev = connector->dev;
|
|
enum drm_connector_force old_force;
|
|
int ret;
|
|
|
|
ret = mutex_lock_interruptible(&dev->mode_config.mutex);
|
|
if (ret)
|
|
return ret;
|
|
|
|
old_force = connector->force;
|
|
|
|
if (sysfs_streq(buf, "detect"))
|
|
connector->force = 0;
|
|
else if (sysfs_streq(buf, "on"))
|
|
connector->force = DRM_FORCE_ON;
|
|
else if (sysfs_streq(buf, "on-digital"))
|
|
connector->force = DRM_FORCE_ON_DIGITAL;
|
|
else if (sysfs_streq(buf, "off"))
|
|
connector->force = DRM_FORCE_OFF;
|
|
else
|
|
ret = -EINVAL;
|
|
|
|
if (old_force != connector->force || !connector->force) {
|
|
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] force updated from %d to %d or reprobing\n",
|
|
connector->base.id,
|
|
connector->name,
|
|
old_force, connector->force);
|
|
|
|
connector->funcs->fill_modes(connector,
|
|
dev->mode_config.max_width,
|
|
dev->mode_config.max_height);
|
|
}
|
|
|
|
mutex_unlock(&dev->mode_config.mutex);
|
|
|
|
return ret ? ret : count;
|
|
}
|
|
|
|
static ssize_t status_show(struct device *device,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
enum drm_connector_status status;
|
|
|
|
status = READ_ONCE(connector->status);
|
|
|
|
return sysfs_emit(buf, "%s\n",
|
|
drm_get_connector_status_name(status));
|
|
}
|
|
|
|
static ssize_t dpms_show(struct device *device,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
int dpms;
|
|
|
|
dpms = READ_ONCE(connector->dpms);
|
|
|
|
return sysfs_emit(buf, "%s\n", drm_get_dpms_name(dpms));
|
|
}
|
|
|
|
static ssize_t enabled_show(struct device *device,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
bool enabled;
|
|
|
|
enabled = READ_ONCE(connector->encoder);
|
|
|
|
return sysfs_emit(buf, enabled ? "enabled\n" : "disabled\n");
|
|
}
|
|
|
|
static ssize_t edid_show(struct file *filp, struct kobject *kobj,
|
|
struct bin_attribute *attr, char *buf, loff_t off,
|
|
size_t count)
|
|
{
|
|
struct device *connector_dev = kobj_to_dev(kobj);
|
|
struct drm_connector *connector = to_drm_connector(connector_dev);
|
|
unsigned char *edid;
|
|
size_t size;
|
|
ssize_t ret = 0;
|
|
|
|
mutex_lock(&connector->dev->mode_config.mutex);
|
|
if (!connector->edid_blob_ptr)
|
|
goto unlock;
|
|
|
|
edid = connector->edid_blob_ptr->data;
|
|
size = connector->edid_blob_ptr->length;
|
|
if (!edid)
|
|
goto unlock;
|
|
|
|
if (off >= size)
|
|
goto unlock;
|
|
|
|
if (off + count > size)
|
|
count = size - off;
|
|
memcpy(buf, edid + off, count);
|
|
|
|
ret = count;
|
|
unlock:
|
|
mutex_unlock(&connector->dev->mode_config.mutex);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t modes_show(struct device *device,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
struct drm_display_mode *mode;
|
|
int written = 0;
|
|
|
|
mutex_lock(&connector->dev->mode_config.mutex);
|
|
list_for_each_entry(mode, &connector->modes, head) {
|
|
written += scnprintf(buf + written, PAGE_SIZE - written, "%s\n",
|
|
mode->name);
|
|
}
|
|
mutex_unlock(&connector->dev->mode_config.mutex);
|
|
|
|
return written;
|
|
}
|
|
|
|
static DEVICE_ATTR_RW(status);
|
|
static DEVICE_ATTR_RO(enabled);
|
|
static DEVICE_ATTR_RO(dpms);
|
|
static DEVICE_ATTR_RO(modes);
|
|
|
|
static struct attribute *connector_dev_attrs[] = {
|
|
&dev_attr_status.attr,
|
|
&dev_attr_enabled.attr,
|
|
&dev_attr_dpms.attr,
|
|
&dev_attr_modes.attr,
|
|
NULL
|
|
};
|
|
|
|
static struct bin_attribute edid_attr = {
|
|
.attr.name = "edid",
|
|
.attr.mode = 0444,
|
|
.size = 0,
|
|
.read = edid_show,
|
|
};
|
|
|
|
static struct bin_attribute *connector_bin_attrs[] = {
|
|
&edid_attr,
|
|
NULL
|
|
};
|
|
|
|
static const struct attribute_group connector_dev_group = {
|
|
.attrs = connector_dev_attrs,
|
|
.bin_attrs = connector_bin_attrs,
|
|
};
|
|
|
|
static const struct attribute_group *connector_dev_groups[] = {
|
|
&connector_dev_group,
|
|
NULL
|
|
};
|
|
|
|
int drm_sysfs_connector_add(struct drm_connector *connector)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
struct device *kdev;
|
|
int r;
|
|
|
|
if (connector->kdev)
|
|
return 0;
|
|
|
|
kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
|
|
if (!kdev)
|
|
return -ENOMEM;
|
|
|
|
device_initialize(kdev);
|
|
kdev->class = drm_class;
|
|
kdev->type = &drm_sysfs_device_connector;
|
|
kdev->parent = dev->primary->kdev;
|
|
kdev->groups = connector_dev_groups;
|
|
kdev->release = drm_sysfs_release;
|
|
dev_set_drvdata(kdev, connector);
|
|
|
|
r = dev_set_name(kdev, "card%d-%s", dev->primary->index, connector->name);
|
|
if (r)
|
|
goto err_free;
|
|
|
|
DRM_DEBUG("adding \"%s\" to sysfs\n",
|
|
connector->name);
|
|
|
|
r = device_add(kdev);
|
|
if (r) {
|
|
drm_err(dev, "failed to register connector device: %d\n", r);
|
|
goto err_free;
|
|
}
|
|
|
|
connector->kdev = kdev;
|
|
|
|
if (connector->ddc)
|
|
return sysfs_create_link(&connector->kdev->kobj,
|
|
&connector->ddc->dev.kobj, "ddc");
|
|
return 0;
|
|
|
|
err_free:
|
|
put_device(kdev);
|
|
return r;
|
|
}
|
|
|
|
void drm_sysfs_connector_remove(struct drm_connector *connector)
|
|
{
|
|
if (!connector->kdev)
|
|
return;
|
|
|
|
if (connector->ddc)
|
|
sysfs_remove_link(&connector->kdev->kobj, "ddc");
|
|
|
|
DRM_DEBUG("removing \"%s\" from sysfs\n",
|
|
connector->name);
|
|
|
|
device_unregister(connector->kdev);
|
|
connector->kdev = NULL;
|
|
}
|
|
|
|
void drm_sysfs_lease_event(struct drm_device *dev)
|
|
{
|
|
char *event_string = "LEASE=1";
|
|
char *envp[] = { event_string, NULL };
|
|
|
|
DRM_DEBUG("generating lease event\n");
|
|
|
|
kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*
|
|
* Any new uapi should be using the drm_sysfs_connector_status_event()
|
|
* for uevents on connector status change.
|
|
*/
|
|
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);
|
|
}
|
|
EXPORT_SYMBOL(drm_sysfs_hotplug_event);
|
|
|
|
/**
|
|
* drm_sysfs_connector_hotplug_event - generate a DRM uevent for any connector
|
|
* change
|
|
* @connector: connector which has changed
|
|
*
|
|
* Send a uevent for the DRM connector specified by @connector. This will send
|
|
* a uevent with the properties HOTPLUG=1 and CONNECTOR.
|
|
*/
|
|
void drm_sysfs_connector_hotplug_event(struct drm_connector *connector)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
char hotplug_str[] = "HOTPLUG=1", conn_id[21];
|
|
char *envp[] = { hotplug_str, conn_id, NULL };
|
|
|
|
snprintf(conn_id, sizeof(conn_id),
|
|
"CONNECTOR=%u", connector->base.id);
|
|
|
|
drm_dbg_kms(connector->dev,
|
|
"[CONNECTOR:%d:%s] generating connector hotplug event\n",
|
|
connector->base.id, connector->name);
|
|
|
|
kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
|
|
}
|
|
EXPORT_SYMBOL(drm_sysfs_connector_hotplug_event);
|
|
|
|
/**
|
|
* drm_sysfs_connector_status_event - generate a DRM uevent for connector
|
|
* property status change
|
|
* @connector: connector on which property status changed
|
|
* @property: connector property whose status changed.
|
|
*
|
|
* Send a uevent for the DRM device specified by @dev. Currently we
|
|
* set HOTPLUG=1 and connector id along with the attached property id
|
|
* related to the status change.
|
|
*/
|
|
void drm_sysfs_connector_status_event(struct drm_connector *connector,
|
|
struct drm_property *property)
|
|
{
|
|
struct drm_device *dev = connector->dev;
|
|
char hotplug_str[] = "HOTPLUG=1", conn_id[21], prop_id[21];
|
|
char *envp[4] = { hotplug_str, conn_id, prop_id, NULL };
|
|
|
|
WARN_ON(!drm_mode_obj_find_prop_id(&connector->base,
|
|
property->base.id));
|
|
|
|
snprintf(conn_id, ARRAY_SIZE(conn_id),
|
|
"CONNECTOR=%u", connector->base.id);
|
|
snprintf(prop_id, ARRAY_SIZE(prop_id),
|
|
"PROPERTY=%u", property->base.id);
|
|
|
|
DRM_DEBUG("generating connector status event\n");
|
|
|
|
kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
|
|
}
|
|
EXPORT_SYMBOL(drm_sysfs_connector_status_event);
|
|
|
|
struct device *drm_sysfs_minor_alloc(struct drm_minor *minor)
|
|
{
|
|
const char *minor_str;
|
|
struct device *kdev;
|
|
int r;
|
|
|
|
kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
|
|
if (!kdev)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
device_initialize(kdev);
|
|
|
|
if (minor->type == DRM_MINOR_ACCEL) {
|
|
minor_str = "accel%d";
|
|
accel_set_device_instance_params(kdev, minor->index);
|
|
} else {
|
|
if (minor->type == DRM_MINOR_RENDER)
|
|
minor_str = "renderD%d";
|
|
else
|
|
minor_str = "card%d";
|
|
|
|
kdev->devt = MKDEV(DRM_MAJOR, minor->index);
|
|
kdev->class = drm_class;
|
|
kdev->type = &drm_sysfs_device_minor;
|
|
}
|
|
|
|
kdev->parent = minor->dev->dev;
|
|
kdev->release = drm_sysfs_release;
|
|
dev_set_drvdata(kdev, minor);
|
|
|
|
r = dev_set_name(kdev, minor_str, minor->index);
|
|
if (r < 0)
|
|
goto err_free;
|
|
|
|
return kdev;
|
|
|
|
err_free:
|
|
put_device(kdev);
|
|
return ERR_PTR(r);
|
|
}
|
|
|
|
/**
|
|
* drm_class_device_register - register new device with the DRM sysfs class
|
|
* @dev: device to register
|
|
*
|
|
* Registers a new &struct device within the DRM sysfs class. Essentially only
|
|
* used by ttm to have a place for its global settings. Drivers should never use
|
|
* this.
|
|
*/
|
|
int drm_class_device_register(struct device *dev)
|
|
{
|
|
if (!drm_class || IS_ERR(drm_class))
|
|
return -ENOENT;
|
|
|
|
dev->class = drm_class;
|
|
return device_register(dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(drm_class_device_register);
|
|
|
|
/**
|
|
* drm_class_device_unregister - unregister device with the DRM sysfs class
|
|
* @dev: device to unregister
|
|
*
|
|
* Unregisters a &struct device from the DRM sysfs class. Essentially only used
|
|
* by ttm to have a place for its global settings. Drivers should never use
|
|
* this.
|
|
*/
|
|
void drm_class_device_unregister(struct device *dev)
|
|
{
|
|
return device_unregister(dev);
|
|
}
|
|
EXPORT_SYMBOL_GPL(drm_class_device_unregister);
|