2006-01-21 06:08:59 +08:00
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/*
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
* This file is released under the GPLv2
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/kdev_t.h>
|
include cleanup: Update gfp.h and slab.h includes to prepare for breaking implicit slab.h inclusion from percpu.h
percpu.h is included by sched.h and module.h and thus ends up being
included when building most .c files. percpu.h includes slab.h which
in turn includes gfp.h making everything defined by the two files
universally available and complicating inclusion dependencies.
percpu.h -> slab.h dependency is about to be removed. Prepare for
this change by updating users of gfp and slab facilities include those
headers directly instead of assuming availability. As this conversion
needs to touch large number of source files, the following script is
used as the basis of conversion.
http://userweb.kernel.org/~tj/misc/slabh-sweep.py
The script does the followings.
* Scan files for gfp and slab usages and update includes such that
only the necessary includes are there. ie. if only gfp is used,
gfp.h, if slab is used, slab.h.
* When the script inserts a new include, it looks at the include
blocks and try to put the new include such that its order conforms
to its surrounding. It's put in the include block which contains
core kernel includes, in the same order that the rest are ordered -
alphabetical, Christmas tree, rev-Xmas-tree or at the end if there
doesn't seem to be any matching order.
* If the script can't find a place to put a new include (mostly
because the file doesn't have fitting include block), it prints out
an error message indicating which .h file needs to be added to the
file.
The conversion was done in the following steps.
1. The initial automatic conversion of all .c files updated slightly
over 4000 files, deleting around 700 includes and adding ~480 gfp.h
and ~3000 slab.h inclusions. The script emitted errors for ~400
files.
2. Each error was manually checked. Some didn't need the inclusion,
some needed manual addition while adding it to implementation .h or
embedding .c file was more appropriate for others. This step added
inclusions to around 150 files.
3. The script was run again and the output was compared to the edits
from #2 to make sure no file was left behind.
4. Several build tests were done and a couple of problems were fixed.
e.g. lib/decompress_*.c used malloc/free() wrappers around slab
APIs requiring slab.h to be added manually.
5. The script was run on all .h files but without automatically
editing them as sprinkling gfp.h and slab.h inclusions around .h
files could easily lead to inclusion dependency hell. Most gfp.h
inclusion directives were ignored as stuff from gfp.h was usually
wildly available and often used in preprocessor macros. Each
slab.h inclusion directive was examined and added manually as
necessary.
6. percpu.h was updated not to include slab.h.
7. Build test were done on the following configurations and failures
were fixed. CONFIG_GCOV_KERNEL was turned off for all tests (as my
distributed build env didn't work with gcov compiles) and a few
more options had to be turned off depending on archs to make things
build (like ipr on powerpc/64 which failed due to missing writeq).
* x86 and x86_64 UP and SMP allmodconfig and a custom test config.
* powerpc and powerpc64 SMP allmodconfig
* sparc and sparc64 SMP allmodconfig
* ia64 SMP allmodconfig
* s390 SMP allmodconfig
* alpha SMP allmodconfig
* um on x86_64 SMP allmodconfig
8. percpu.h modifications were reverted so that it could be applied as
a separate patch and serve as bisection point.
Given the fact that I had only a couple of failures from tests on step
6, I'm fairly confident about the coverage of this conversion patch.
If there is a breakage, it's likely to be something in one of the arch
headers which should be easily discoverable easily on most builds of
the specific arch.
Signed-off-by: Tejun Heo <tj@kernel.org>
Guess-its-ok-by: Christoph Lameter <cl@linux-foundation.org>
Cc: Ingo Molnar <mingo@redhat.com>
Cc: Lee Schermerhorn <Lee.Schermerhorn@hp.com>
2010-03-24 16:04:11 +08:00
|
|
|
#include <linux/gfp.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
#include <linux/err.h>
|
2011-08-31 06:16:33 +08:00
|
|
|
#include <linux/export.h>
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2012-10-03 01:01:07 +08:00
|
|
|
#include <drm/drm_sysfs.h>
|
|
|
|
#include <drm/drm_core.h>
|
|
|
|
#include <drm/drmP.h>
|
2014-09-10 18:43:53 +08:00
|
|
|
#include "drm_internal.h"
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2013-10-11 12:07:25 +08:00
|
|
|
#define to_drm_minor(d) dev_get_drvdata(d)
|
|
|
|
#define to_drm_connector(d) dev_get_drvdata(d)
|
2007-11-22 12:02:38 +08:00
|
|
|
|
2009-08-20 17:02:31 +08:00
|
|
|
static struct device_type drm_sysfs_device_minor = {
|
|
|
|
.name = "drm_minor"
|
|
|
|
};
|
|
|
|
|
2007-11-22 12:02:38 +08:00
|
|
|
/**
|
2013-07-02 06:06:02 +08:00
|
|
|
* __drm_class_suspend - internal DRM class suspend routine
|
2007-11-22 12:02:38 +08:00
|
|
|
* @dev: Linux device to suspend
|
|
|
|
* @state: power state to enter
|
|
|
|
*
|
|
|
|
* Just figures out what the actual struct drm_device associated with
|
|
|
|
* @dev is and calls its suspend hook, if present.
|
|
|
|
*/
|
2013-07-02 06:06:02 +08:00
|
|
|
static int __drm_class_suspend(struct device *dev, pm_message_t state)
|
2007-11-22 12:02:38 +08:00
|
|
|
{
|
2009-08-20 17:02:31 +08:00
|
|
|
if (dev->type == &drm_sysfs_device_minor) {
|
|
|
|
struct drm_minor *drm_minor = to_drm_minor(dev);
|
|
|
|
struct drm_device *drm_dev = drm_minor->dev;
|
|
|
|
|
|
|
|
if (drm_minor->type == DRM_MINOR_LEGACY &&
|
|
|
|
!drm_core_check_feature(drm_dev, DRIVER_MODESET) &&
|
|
|
|
drm_dev->driver->suspend)
|
|
|
|
return drm_dev->driver->suspend(drm_dev, state);
|
|
|
|
}
|
2007-11-22 12:02:38 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-07-02 06:06:02 +08:00
|
|
|
/**
|
|
|
|
* drm_class_suspend - internal DRM class suspend hook. Simply calls
|
|
|
|
* __drm_class_suspend() with the correct pm state.
|
|
|
|
* @dev: Linux device to suspend
|
|
|
|
*/
|
|
|
|
static int drm_class_suspend(struct device *dev)
|
|
|
|
{
|
|
|
|
return __drm_class_suspend(dev, PMSG_SUSPEND);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* drm_class_freeze - internal DRM class freeze hook. Simply calls
|
|
|
|
* __drm_class_suspend() with the correct pm state.
|
|
|
|
* @dev: Linux device to freeze
|
|
|
|
*/
|
|
|
|
static int drm_class_freeze(struct device *dev)
|
|
|
|
{
|
|
|
|
return __drm_class_suspend(dev, PMSG_FREEZE);
|
|
|
|
}
|
|
|
|
|
2007-11-22 12:02:38 +08:00
|
|
|
/**
|
2009-08-20 17:02:31 +08:00
|
|
|
* drm_class_resume - DRM class resume hook
|
2007-11-22 12:02:38 +08:00
|
|
|
* @dev: Linux device to resume
|
|
|
|
*
|
|
|
|
* Just figures out what the actual struct drm_device associated with
|
|
|
|
* @dev is and calls its resume hook, if present.
|
|
|
|
*/
|
2009-08-20 17:02:31 +08:00
|
|
|
static int drm_class_resume(struct device *dev)
|
2007-11-22 12:02:38 +08:00
|
|
|
{
|
2009-08-20 17:02:31 +08:00
|
|
|
if (dev->type == &drm_sysfs_device_minor) {
|
|
|
|
struct drm_minor *drm_minor = to_drm_minor(dev);
|
|
|
|
struct drm_device *drm_dev = drm_minor->dev;
|
|
|
|
|
|
|
|
if (drm_minor->type == DRM_MINOR_LEGACY &&
|
|
|
|
!drm_core_check_feature(drm_dev, DRIVER_MODESET) &&
|
|
|
|
drm_dev->driver->resume)
|
|
|
|
return drm_dev->driver->resume(drm_dev);
|
|
|
|
}
|
2007-11-22 12:02:38 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2013-07-02 06:06:02 +08:00
|
|
|
static const struct dev_pm_ops drm_class_dev_pm_ops = {
|
|
|
|
.suspend = drm_class_suspend,
|
|
|
|
.resume = drm_class_resume,
|
|
|
|
.freeze = drm_class_freeze,
|
|
|
|
};
|
|
|
|
|
2011-07-24 08:24:48 +08:00
|
|
|
static char *drm_devnode(struct device *dev, umode_t *mode)
|
2009-04-30 21:23:42 +08:00
|
|
|
{
|
|
|
|
return kasprintf(GFP_KERNEL, "dri/%s", dev_name(dev));
|
|
|
|
}
|
|
|
|
|
2010-01-05 19:48:09 +08:00
|
|
|
static CLASS_ATTR_STRING(version, S_IRUGO,
|
|
|
|
CORE_NAME " "
|
|
|
|
__stringify(CORE_MAJOR) "."
|
|
|
|
__stringify(CORE_MINOR) "."
|
|
|
|
__stringify(CORE_PATCHLEVEL) " "
|
|
|
|
CORE_DATE);
|
2005-04-17 06:20:36 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* drm_sysfs_create - create a struct drm_sysfs_class structure
|
|
|
|
* @owner: pointer to the module that is to "own" this struct drm_sysfs_class
|
|
|
|
* @name: pointer to a string for the name of this class.
|
|
|
|
*
|
2007-11-22 12:02:38 +08:00
|
|
|
* This is used to create DRM class pointer that can then be used
|
2005-04-17 06:20:36 +08:00
|
|
|
* in calls to drm_sysfs_device_add().
|
|
|
|
*
|
|
|
|
* Note, the pointer created here is to be destroyed when finished by making a
|
|
|
|
* call to drm_sysfs_destroy().
|
|
|
|
*/
|
2006-01-21 06:08:59 +08:00
|
|
|
struct class *drm_sysfs_create(struct module *owner, char *name)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2006-01-21 06:08:59 +08:00
|
|
|
struct class *class;
|
2006-10-11 05:23:37 +08:00
|
|
|
int err;
|
2006-01-21 06:08:59 +08:00
|
|
|
|
|
|
|
class = class_create(owner, name);
|
2006-12-09 07:49:47 +08:00
|
|
|
if (IS_ERR(class)) {
|
|
|
|
err = PTR_ERR(class);
|
2006-10-11 05:23:37 +08:00
|
|
|
goto err_out;
|
|
|
|
}
|
|
|
|
|
2013-07-02 06:06:02 +08:00
|
|
|
class->pm = &drm_class_dev_pm_ops;
|
2007-11-22 12:02:38 +08:00
|
|
|
|
2010-01-05 19:48:09 +08:00
|
|
|
err = class_create_file(class, &class_attr_version.attr);
|
2006-10-11 05:23:37 +08:00
|
|
|
if (err)
|
|
|
|
goto err_out_class;
|
2006-01-21 06:08:59 +08:00
|
|
|
|
2009-09-19 05:01:12 +08:00
|
|
|
class->devnode = drm_devnode;
|
2009-04-30 21:23:42 +08:00
|
|
|
|
2006-01-21 06:08:59 +08:00
|
|
|
return class;
|
2006-10-11 05:23:37 +08:00
|
|
|
|
|
|
|
err_out_class:
|
|
|
|
class_destroy(class);
|
|
|
|
err_out:
|
|
|
|
return ERR_PTR(err);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2007-11-22 12:02:38 +08:00
|
|
|
* drm_sysfs_destroy - destroys DRM class
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
2007-11-22 12:02:38 +08:00
|
|
|
* Destroy the DRM device class.
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2007-11-22 12:02:38 +08:00
|
|
|
void drm_sysfs_destroy(void)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2007-11-22 12:02:38 +08:00
|
|
|
if ((drm_class == NULL) || (IS_ERR(drm_class)))
|
2005-04-17 06:20:36 +08:00
|
|
|
return;
|
2010-01-05 19:48:09 +08:00
|
|
|
class_remove_file(drm_class, &class_attr_version.attr);
|
2007-11-22 12:02:38 +08:00
|
|
|
class_destroy(drm_class);
|
2012-07-07 01:06:42 +08:00
|
|
|
drm_class = NULL;
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
|
|
|
|
2008-11-08 06:05:41 +08:00
|
|
|
/*
|
|
|
|
* Connector properties
|
|
|
|
*/
|
2015-03-06 20:36:42 +08:00
|
|
|
static ssize_t status_store(struct device *device,
|
2008-11-08 06:05:41 +08:00
|
|
|
struct device_attribute *attr,
|
2015-03-06 20:36:42 +08:00
|
|
|
const char *buf, size_t count)
|
2008-11-08 06:05:41 +08:00
|
|
|
{
|
|
|
|
struct drm_connector *connector = to_drm_connector(device);
|
2015-03-06 20:36:42 +08:00
|
|
|
struct drm_device *dev = connector->dev;
|
|
|
|
enum drm_connector_status old_status;
|
2011-03-15 19:40:00 +08:00
|
|
|
int ret;
|
|
|
|
|
2015-03-06 20:36:42 +08:00
|
|
|
ret = mutex_lock_interruptible(&dev->mode_config.mutex);
|
2011-03-15 19:40:00 +08:00
|
|
|
if (ret)
|
|
|
|
return ret;
|
2008-11-08 06:05:41 +08:00
|
|
|
|
2015-03-06 20:36:42 +08:00
|
|
|
old_status = connector->status;
|
|
|
|
|
|
|
|
if (sysfs_streq(buf, "detect")) {
|
|
|
|
connector->force = 0;
|
|
|
|
connector->status = connector->funcs->detect(connector, true);
|
|
|
|
} 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 (ret == 0 && connector->force) {
|
|
|
|
if (connector->force == DRM_FORCE_ON ||
|
|
|
|
connector->force == DRM_FORCE_ON_DIGITAL)
|
|
|
|
connector->status = connector_status_connected;
|
|
|
|
else
|
|
|
|
connector->status = connector_status_disconnected;
|
|
|
|
if (connector->funcs->force)
|
|
|
|
connector->funcs->force(connector);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (old_status != connector->status) {
|
|
|
|
DRM_DEBUG_KMS("[CONNECTOR:%d:%s] status updated from %d to %d\n",
|
|
|
|
connector->base.id,
|
|
|
|
connector->name,
|
|
|
|
old_status, connector->status);
|
|
|
|
|
|
|
|
dev->mode_config.delayed_event = true;
|
|
|
|
if (dev->mode_config.poll_enabled)
|
|
|
|
schedule_delayed_work(&dev->mode_config.output_poll_work,
|
|
|
|
0);
|
|
|
|
}
|
|
|
|
|
|
|
|
mutex_unlock(&dev->mode_config.mutex);
|
|
|
|
|
2015-06-06 06:27:30 +08:00
|
|
|
return ret ? ret : count;
|
2015-03-06 20:36:42 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t status_show(struct device *device,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct drm_connector *connector = to_drm_connector(device);
|
2011-03-15 19:40:00 +08:00
|
|
|
|
2009-05-31 11:42:25 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",
|
2015-03-06 20:36:42 +08:00
|
|
|
drm_get_connector_status_name(connector->status));
|
2008-11-08 06:05:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t dpms_show(struct device *device,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
|
|
struct drm_device *dev = connector->dev;
|
|
|
|
uint64_t dpms_status;
|
|
|
|
int ret;
|
|
|
|
|
2012-10-12 09:50:56 +08:00
|
|
|
ret = drm_object_property_get_value(&connector->base,
|
2008-11-08 06:05:41 +08:00
|
|
|
dev->mode_config.dpms_property,
|
|
|
|
&dpms_status);
|
|
|
|
if (ret)
|
|
|
|
return 0;
|
|
|
|
|
2009-05-31 11:42:25 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n",
|
2008-11-08 06:05:41 +08:00
|
|
|
drm_get_dpms_name((int)dpms_status));
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t enabled_show(struct device *device,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
|
|
|
2009-05-31 11:42:25 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", connector->encoder ? "enabled" :
|
2008-11-08 06:05:41 +08:00
|
|
|
"disabled");
|
|
|
|
}
|
|
|
|
|
2010-05-13 09:28:57 +08:00
|
|
|
static ssize_t edid_show(struct file *filp, struct kobject *kobj,
|
|
|
|
struct bin_attribute *attr, char *buf, loff_t off,
|
|
|
|
size_t count)
|
2008-11-08 06:05:41 +08:00
|
|
|
{
|
|
|
|
struct device *connector_dev = container_of(kobj, struct device, kobj);
|
|
|
|
struct drm_connector *connector = to_drm_connector(connector_dev);
|
|
|
|
unsigned char *edid;
|
|
|
|
size_t size;
|
|
|
|
|
|
|
|
if (!connector->edid_blob_ptr)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
edid = connector->edid_blob_ptr->data;
|
|
|
|
size = connector->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_connector *connector = to_drm_connector(device);
|
|
|
|
struct drm_display_mode *mode;
|
|
|
|
int written = 0;
|
|
|
|
|
|
|
|
list_for_each_entry(mode, &connector->modes, head) {
|
|
|
|
written += snprintf(buf + written, PAGE_SIZE - written, "%s\n",
|
|
|
|
mode->name);
|
|
|
|
}
|
|
|
|
|
|
|
|
return written;
|
|
|
|
}
|
|
|
|
|
2015-05-12 17:14:54 +08:00
|
|
|
static ssize_t tv_subconnector_show(struct device *device,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
2008-11-08 06:05:41 +08:00
|
|
|
{
|
|
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
|
|
struct drm_device *dev = connector->dev;
|
2015-05-12 17:14:54 +08:00
|
|
|
struct drm_property *prop;
|
2008-11-08 06:05:41 +08:00
|
|
|
uint64_t subconnector;
|
|
|
|
int ret;
|
|
|
|
|
2015-05-12 17:14:54 +08:00
|
|
|
prop = dev->mode_config.tv_subconnector_property;
|
2008-11-08 06:05:41 +08:00
|
|
|
if (!prop) {
|
|
|
|
DRM_ERROR("Unable to find subconnector property\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-10-12 09:50:56 +08:00
|
|
|
ret = drm_object_property_get_value(&connector->base, prop, &subconnector);
|
2008-11-08 06:05:41 +08:00
|
|
|
if (ret)
|
|
|
|
return 0;
|
|
|
|
|
2015-05-12 17:14:54 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%s",
|
|
|
|
drm_get_tv_subconnector_name((int)subconnector));
|
2008-11-08 06:05:41 +08:00
|
|
|
}
|
|
|
|
|
2015-05-12 17:14:54 +08:00
|
|
|
static ssize_t tv_select_subconnector_show(struct device *device,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
2008-11-08 06:05:41 +08:00
|
|
|
{
|
|
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
|
|
struct drm_device *dev = connector->dev;
|
2015-05-12 17:14:54 +08:00
|
|
|
struct drm_property *prop;
|
2008-11-08 06:05:41 +08:00
|
|
|
uint64_t subconnector;
|
|
|
|
int ret;
|
|
|
|
|
2015-05-12 17:14:54 +08:00
|
|
|
prop = dev->mode_config.tv_select_subconnector_property;
|
2008-11-08 06:05:41 +08:00
|
|
|
if (!prop) {
|
2015-05-12 17:14:54 +08:00
|
|
|
DRM_ERROR("Unable to find select subconnector property\n");
|
2008-11-08 06:05:41 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-10-12 09:50:56 +08:00
|
|
|
ret = drm_object_property_get_value(&connector->base, prop, &subconnector);
|
2008-11-08 06:05:41 +08:00
|
|
|
if (ret)
|
|
|
|
return 0;
|
|
|
|
|
2015-05-12 17:14:54 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%s",
|
|
|
|
drm_get_tv_select_name((int)subconnector));
|
2008-11-08 06:05:41 +08:00
|
|
|
}
|
|
|
|
|
2015-05-12 17:14:54 +08:00
|
|
|
static ssize_t dvii_subconnector_show(struct device *device,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
2008-11-08 06:05:41 +08:00
|
|
|
{
|
|
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
|
|
struct drm_device *dev = connector->dev;
|
2015-05-12 17:14:54 +08:00
|
|
|
struct drm_property *prop;
|
2008-11-08 06:05:41 +08:00
|
|
|
uint64_t subconnector;
|
|
|
|
int ret;
|
|
|
|
|
2015-05-12 17:14:54 +08:00
|
|
|
prop = dev->mode_config.dvi_i_subconnector_property;
|
|
|
|
if (!prop) {
|
|
|
|
DRM_ERROR("Unable to find subconnector property\n");
|
|
|
|
return 0;
|
2008-11-08 06:05:41 +08:00
|
|
|
}
|
|
|
|
|
2015-05-12 17:14:54 +08:00
|
|
|
ret = drm_object_property_get_value(&connector->base, prop, &subconnector);
|
|
|
|
if (ret)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s",
|
|
|
|
drm_get_dvi_i_subconnector_name((int)subconnector));
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t dvii_select_subconnector_show(struct device *device,
|
|
|
|
struct device_attribute *attr,
|
|
|
|
char *buf)
|
|
|
|
{
|
|
|
|
struct drm_connector *connector = to_drm_connector(device);
|
|
|
|
struct drm_device *dev = connector->dev;
|
|
|
|
struct drm_property *prop;
|
|
|
|
uint64_t subconnector;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
prop = dev->mode_config.dvi_i_select_subconnector_property;
|
2008-11-08 06:05:41 +08:00
|
|
|
if (!prop) {
|
|
|
|
DRM_ERROR("Unable to find select subconnector property\n");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-10-12 09:50:56 +08:00
|
|
|
ret = drm_object_property_get_value(&connector->base, prop, &subconnector);
|
2008-11-08 06:05:41 +08:00
|
|
|
if (ret)
|
|
|
|
return 0;
|
|
|
|
|
2015-05-12 17:14:54 +08:00
|
|
|
return snprintf(buf, PAGE_SIZE, "%s",
|
2008-11-08 06:05:41 +08:00
|
|
|
drm_get_dvi_i_select_name((int)subconnector));
|
|
|
|
}
|
|
|
|
|
2015-03-06 20:36:42 +08:00
|
|
|
static DEVICE_ATTR_RW(status);
|
2015-02-04 18:58:53 +08:00
|
|
|
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
|
2008-11-08 06:05:41 +08:00
|
|
|
};
|
|
|
|
|
2015-05-12 17:14:54 +08:00
|
|
|
static DEVICE_ATTR_RO(tv_subconnector);
|
|
|
|
static DEVICE_ATTR_RO(tv_select_subconnector);
|
2015-02-04 18:58:53 +08:00
|
|
|
|
2015-05-12 17:14:53 +08:00
|
|
|
static struct attribute *connector_tv_dev_attrs[] = {
|
2015-05-12 17:14:54 +08:00
|
|
|
&dev_attr_tv_subconnector.attr,
|
|
|
|
&dev_attr_tv_select_subconnector.attr,
|
2015-02-04 18:58:53 +08:00
|
|
|
NULL
|
2008-11-08 06:05:41 +08:00
|
|
|
};
|
|
|
|
|
2015-05-12 17:14:54 +08:00
|
|
|
static DEVICE_ATTR_RO(dvii_subconnector);
|
|
|
|
static DEVICE_ATTR_RO(dvii_select_subconnector);
|
|
|
|
|
|
|
|
static struct attribute *connector_dvii_dev_attrs[] = {
|
|
|
|
&dev_attr_dvii_subconnector.attr,
|
|
|
|
&dev_attr_dvii_select_subconnector.attr,
|
2015-02-04 18:58:53 +08:00
|
|
|
NULL
|
2008-11-08 06:05:41 +08:00
|
|
|
};
|
|
|
|
|
2015-05-12 17:14:52 +08:00
|
|
|
/* Connector type related helpers */
|
|
|
|
static int kobj_connector_type(struct kobject *kobj)
|
2015-02-04 18:58:53 +08:00
|
|
|
{
|
|
|
|
struct device *dev = kobj_to_dev(kobj);
|
|
|
|
struct drm_connector *connector = to_drm_connector(dev);
|
|
|
|
|
2015-05-12 17:14:52 +08:00
|
|
|
return connector->connector_type;
|
|
|
|
}
|
|
|
|
|
2015-05-12 17:14:53 +08:00
|
|
|
static umode_t connector_is_dvii(struct kobject *kobj,
|
|
|
|
struct attribute *attr, int idx)
|
|
|
|
{
|
|
|
|
return kobj_connector_type(kobj) == DRM_MODE_CONNECTOR_DVII ?
|
|
|
|
attr->mode : 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static umode_t connector_is_tv(struct kobject *kobj,
|
|
|
|
struct attribute *attr, int idx)
|
2015-05-12 17:14:52 +08:00
|
|
|
{
|
|
|
|
switch (kobj_connector_type(kobj)) {
|
2015-02-04 18:58:53 +08:00
|
|
|
case DRM_MODE_CONNECTOR_Composite:
|
|
|
|
case DRM_MODE_CONNECTOR_SVIDEO:
|
|
|
|
case DRM_MODE_CONNECTOR_Component:
|
|
|
|
case DRM_MODE_CONNECTOR_TV:
|
|
|
|
return attr->mode;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-11-08 06:05:41 +08:00
|
|
|
static struct bin_attribute edid_attr = {
|
|
|
|
.attr.name = "edid",
|
2009-05-31 11:42:26 +08:00
|
|
|
.attr.mode = 0444,
|
2010-03-30 05:43:23 +08:00
|
|
|
.size = 0,
|
2008-11-08 06:05:41 +08:00
|
|
|
.read = edid_show,
|
|
|
|
};
|
|
|
|
|
2015-02-04 18:58:53 +08:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
|
2015-05-12 17:14:53 +08:00
|
|
|
static const struct attribute_group connector_tv_dev_group = {
|
|
|
|
.attrs = connector_tv_dev_attrs,
|
|
|
|
.is_visible = connector_is_tv,
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group connector_dvii_dev_group = {
|
2015-05-12 17:14:54 +08:00
|
|
|
.attrs = connector_dvii_dev_attrs,
|
2015-05-12 17:14:53 +08:00
|
|
|
.is_visible = connector_is_dvii,
|
2015-02-04 18:58:53 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
static const struct attribute_group *connector_dev_groups[] = {
|
|
|
|
&connector_dev_group,
|
2015-05-12 17:14:53 +08:00
|
|
|
&connector_tv_dev_group,
|
|
|
|
&connector_dvii_dev_group,
|
2015-02-04 18:58:53 +08:00
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
2008-11-08 06:05:41 +08:00
|
|
|
/**
|
2012-05-17 19:27:20 +08:00
|
|
|
* drm_sysfs_connector_add - add a connector to sysfs
|
2008-11-08 06:05:41 +08:00
|
|
|
* @connector: connector to add
|
|
|
|
*
|
2012-05-17 19:27:20 +08:00
|
|
|
* Create a connector device in sysfs, along with its associated connector
|
2008-11-08 06:05:41 +08:00
|
|
|
* properties (so far, connection status, dpms, mode list & edid) and
|
|
|
|
* generate a hotplug event so userspace knows there's a new connector
|
|
|
|
* available.
|
|
|
|
*/
|
|
|
|
int drm_sysfs_connector_add(struct drm_connector *connector)
|
|
|
|
{
|
|
|
|
struct drm_device *dev = connector->dev;
|
|
|
|
|
2013-10-11 12:07:25 +08:00
|
|
|
if (connector->kdev)
|
|
|
|
return 0;
|
2008-11-08 06:05:41 +08:00
|
|
|
|
2015-02-04 18:58:53 +08:00
|
|
|
connector->kdev =
|
|
|
|
device_create_with_groups(drm_class, dev->primary->kdev, 0,
|
|
|
|
connector, connector_dev_groups,
|
|
|
|
"card%d-%s", dev->primary->index,
|
|
|
|
connector->name);
|
2008-11-08 06:05:41 +08:00
|
|
|
DRM_DEBUG("adding \"%s\" to sysfs\n",
|
2014-06-03 19:56:20 +08:00
|
|
|
connector->name);
|
2008-11-08 06:05:41 +08:00
|
|
|
|
2013-10-11 12:07:25 +08:00
|
|
|
if (IS_ERR(connector->kdev)) {
|
|
|
|
DRM_ERROR("failed to register connector device: %ld\n", PTR_ERR(connector->kdev));
|
2015-02-04 18:58:53 +08:00
|
|
|
return PTR_ERR(connector->kdev);
|
2008-11-08 06:05:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Let userspace know we have a new connector */
|
|
|
|
drm_sysfs_hotplug_event(dev);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* drm_sysfs_connector_remove - remove an connector device from sysfs
|
|
|
|
* @connector: connector to remove
|
|
|
|
*
|
|
|
|
* Remove @connector 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.
|
|
|
|
*
|
|
|
|
* Note:
|
|
|
|
* This routine should only be called if the connector was previously
|
|
|
|
* successfully registered. If @connector hasn't been registered yet,
|
|
|
|
* you'll likely see a panic somewhere deep in sysfs code when called.
|
|
|
|
*/
|
|
|
|
void drm_sysfs_connector_remove(struct drm_connector *connector)
|
|
|
|
{
|
2013-10-11 12:07:25 +08:00
|
|
|
if (!connector->kdev)
|
2012-02-20 22:15:02 +08:00
|
|
|
return;
|
2008-11-08 06:05:41 +08:00
|
|
|
DRM_DEBUG("removing \"%s\" from sysfs\n",
|
2014-06-03 19:56:20 +08:00
|
|
|
connector->name);
|
2008-11-08 06:05:41 +08:00
|
|
|
|
2013-10-11 12:07:25 +08:00
|
|
|
device_unregister(connector->kdev);
|
|
|
|
connector->kdev = NULL;
|
2008-11-08 06:05:41 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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");
|
|
|
|
|
2013-10-11 12:07:25 +08:00
|
|
|
kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
|
2008-11-08 06:05:41 +08:00
|
|
|
}
|
2009-04-01 05:11:15 +08:00
|
|
|
EXPORT_SYMBOL(drm_sysfs_hotplug_event);
|
2008-11-08 06:05:41 +08:00
|
|
|
|
2013-11-21 18:50:50 +08:00
|
|
|
static void drm_sysfs_release(struct device *dev)
|
|
|
|
{
|
|
|
|
kfree(dev);
|
|
|
|
}
|
|
|
|
|
2005-04-17 06:20:36 +08:00
|
|
|
/**
|
2014-07-23 17:38:38 +08:00
|
|
|
* drm_sysfs_minor_alloc() - Allocate sysfs device for given minor
|
|
|
|
* @minor: minor to allocate sysfs device for
|
2005-04-17 06:20:36 +08:00
|
|
|
*
|
2014-07-23 17:38:38 +08:00
|
|
|
* This allocates a new sysfs device for @minor and returns it. The device is
|
|
|
|
* not registered nor linked. The caller has to use device_add() and
|
|
|
|
* device_del() to register and unregister it.
|
|
|
|
*
|
|
|
|
* Note that dev_get_drvdata() on the new device will return the minor.
|
|
|
|
* However, the device does not hold a ref-count to the minor nor to the
|
|
|
|
* underlying drm_device. This is unproblematic as long as you access the
|
|
|
|
* private data only in sysfs callbacks. device_del() disables those
|
|
|
|
* synchronously, so they cannot be called after you cleanup a minor.
|
2005-04-17 06:20:36 +08:00
|
|
|
*/
|
2014-07-23 17:38:38 +08:00
|
|
|
struct device *drm_sysfs_minor_alloc(struct drm_minor *minor)
|
2005-04-17 06:20:36 +08:00
|
|
|
{
|
2014-07-23 17:38:38 +08:00
|
|
|
const char *minor_str;
|
|
|
|
struct device *kdev;
|
2013-11-21 18:50:50 +08:00
|
|
|
int r;
|
2007-11-22 12:02:38 +08:00
|
|
|
|
2008-11-08 06:05:41 +08:00
|
|
|
if (minor->type == DRM_MINOR_CONTROL)
|
|
|
|
minor_str = "controlD%d";
|
2014-07-23 17:38:38 +08:00
|
|
|
else if (minor->type == DRM_MINOR_RENDER)
|
|
|
|
minor_str = "renderD%d";
|
|
|
|
else
|
|
|
|
minor_str = "card%d";
|
|
|
|
|
|
|
|
kdev = kzalloc(sizeof(*kdev), GFP_KERNEL);
|
|
|
|
if (!kdev)
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
device_initialize(kdev);
|
|
|
|
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);
|
2013-11-21 18:50:50 +08:00
|
|
|
if (r < 0)
|
2014-07-23 17:38:38 +08:00
|
|
|
goto err_free;
|
2013-11-21 18:50:50 +08:00
|
|
|
|
2014-07-23 17:38:38 +08:00
|
|
|
return kdev;
|
2005-04-17 06:20:36 +08:00
|
|
|
|
2014-07-23 17:38:38 +08:00
|
|
|
err_free:
|
|
|
|
put_device(kdev);
|
|
|
|
return ERR_PTR(r);
|
2005-04-17 06:20:36 +08:00
|
|
|
}
|
2009-08-17 22:28:37 +08:00
|
|
|
|
|
|
|
/**
|
|
|
|
* drm_class_device_register - Register a struct device in the drm class.
|
|
|
|
*
|
|
|
|
* @dev: pointer to struct device to register.
|
|
|
|
*
|
|
|
|
* @dev should have all relevant members pre-filled with the exception
|
|
|
|
* of the class member. In particular, the device_type member must
|
|
|
|
* be set.
|
|
|
|
*/
|
|
|
|
|
|
|
|
int drm_class_device_register(struct device *dev)
|
|
|
|
{
|
2012-07-07 01:06:42 +08:00
|
|
|
if (!drm_class || IS_ERR(drm_class))
|
|
|
|
return -ENOENT;
|
|
|
|
|
2009-08-17 22:28:37 +08:00
|
|
|
dev->class = drm_class;
|
|
|
|
return device_register(dev);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(drm_class_device_register);
|
|
|
|
|
|
|
|
void drm_class_device_unregister(struct device *dev)
|
|
|
|
{
|
|
|
|
return device_unregister(dev);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(drm_class_device_unregister);
|