drm_sysfs.c 14.8 KB
Newer Older
1

Linus Torvalds's avatar
Linus Torvalds committed
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * 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>
17
#include <linux/gfp.h>
Linus Torvalds's avatar
Linus Torvalds committed
18
#include <linux/err.h>
19
#include <linux/export.h>
Linus Torvalds's avatar
Linus Torvalds committed
20

21 22 23
#include <drm/drm_sysfs.h>
#include <drm/drm_core.h>
#include <drm/drmP.h>
Linus Torvalds's avatar
Linus Torvalds committed
24

25 26
#define to_drm_minor(d) dev_get_drvdata(d)
#define to_drm_connector(d) dev_get_drvdata(d)
Jesse Barnes's avatar
Jesse Barnes committed
27

28 29 30 31
static struct device_type drm_sysfs_device_minor = {
	.name = "drm_minor"
};

Jesse Barnes's avatar
Jesse Barnes committed
32
/**
33
 * __drm_class_suspend - internal DRM class suspend routine
Jesse Barnes's avatar
Jesse Barnes committed
34 35 36 37 38 39
 * @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.
 */
40
static int __drm_class_suspend(struct device *dev, pm_message_t state)
Jesse Barnes's avatar
Jesse Barnes committed
41
{
42 43 44 45 46 47 48 49 50
	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);
	}
Jesse Barnes's avatar
Jesse Barnes committed
51 52 53
	return 0;
}

54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
/**
 * 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);
}

Jesse Barnes's avatar
Jesse Barnes committed
74
/**
75
 * drm_class_resume - DRM class resume hook
Jesse Barnes's avatar
Jesse Barnes committed
76 77 78 79 80
 * @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.
 */
81
static int drm_class_resume(struct device *dev)
Jesse Barnes's avatar
Jesse Barnes committed
82
{
83 84 85 86 87 88 89 90 91
	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);
	}
Jesse Barnes's avatar
Jesse Barnes committed
92 93 94
	return 0;
}

95 96 97 98 99 100
static const struct dev_pm_ops drm_class_dev_pm_ops = {
	.suspend	= drm_class_suspend,
	.resume		= drm_class_resume,
	.freeze		= drm_class_freeze,
};

101
static char *drm_devnode(struct device *dev, umode_t *mode)
102 103 104 105
{
	return kasprintf(GFP_KERNEL, "dri/%s", dev_name(dev));
}

106 107 108 109 110 111
static CLASS_ATTR_STRING(version, S_IRUGO,
		CORE_NAME " "
		__stringify(CORE_MAJOR) "."
		__stringify(CORE_MINOR) "."
		__stringify(CORE_PATCHLEVEL) " "
		CORE_DATE);
Linus Torvalds's avatar
Linus Torvalds committed
112 113 114 115 116 117

/**
 * 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.
 *
Jesse Barnes's avatar
Jesse Barnes committed
118
 * This is used to create DRM class pointer that can then be used
Linus Torvalds's avatar
Linus Torvalds committed
119 120 121 122 123
 * 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().
 */
124
struct class *drm_sysfs_create(struct module *owner, char *name)
Linus Torvalds's avatar
Linus Torvalds committed
125
{
126
	struct class *class;
127
	int err;
128 129

	class = class_create(owner, name);
Akinobu Mita's avatar
Akinobu Mita committed
130 131
	if (IS_ERR(class)) {
		err = PTR_ERR(class);
132 133 134
		goto err_out;
	}

135
	class->pm = &drm_class_dev_pm_ops;
Jesse Barnes's avatar
Jesse Barnes committed
136

137
	err = class_create_file(class, &class_attr_version.attr);
138 139
	if (err)
		goto err_out_class;
140

141
	class->devnode = drm_devnode;
142

143
	return class;
144 145 146 147 148

err_out_class:
	class_destroy(class);
err_out:
	return ERR_PTR(err);
Linus Torvalds's avatar
Linus Torvalds committed
149 150 151
}

/**
Jesse Barnes's avatar
Jesse Barnes committed
152
 * drm_sysfs_destroy - destroys DRM class
Linus Torvalds's avatar
Linus Torvalds committed
153
 *
Jesse Barnes's avatar
Jesse Barnes committed
154
 * Destroy the DRM device class.
Linus Torvalds's avatar
Linus Torvalds committed
155
 */
Jesse Barnes's avatar
Jesse Barnes committed
156
void drm_sysfs_destroy(void)
Linus Torvalds's avatar
Linus Torvalds committed
157
{
Jesse Barnes's avatar
Jesse Barnes committed
158
	if ((drm_class == NULL) || (IS_ERR(drm_class)))
Linus Torvalds's avatar
Linus Torvalds committed
159
		return;
160
	class_remove_file(drm_class, &class_attr_version.attr);
Jesse Barnes's avatar
Jesse Barnes committed
161
	class_destroy(drm_class);
162
	drm_class = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
163 164
}

Dave Airlie's avatar
Dave Airlie committed
165 166 167 168 169 170 171 172 173
/*
 * Connector properties
 */
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;
174 175 176 177 178
	int ret;

	ret = mutex_lock_interruptible(&connector->dev->mode_config.mutex);
	if (ret)
		return ret;
Dave Airlie's avatar
Dave Airlie committed
179

180
	status = connector->funcs->detect(connector, true);
181 182
	mutex_unlock(&connector->dev->mode_config.mutex);

183
	return snprintf(buf, PAGE_SIZE, "%s\n",
Dave Airlie's avatar
Dave Airlie committed
184 185 186 187 188 189 190 191 192 193 194 195
			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);
	struct drm_device *dev = connector->dev;
	uint64_t dpms_status;
	int ret;

196
	ret = drm_object_property_get_value(&connector->base,
Dave Airlie's avatar
Dave Airlie committed
197 198 199 200 201
					    dev->mode_config.dpms_property,
					    &dpms_status);
	if (ret)
		return 0;

202
	return snprintf(buf, PAGE_SIZE, "%s\n",
Dave Airlie's avatar
Dave Airlie committed
203 204 205 206 207 208 209 210 211
			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);

212
	return snprintf(buf, PAGE_SIZE, "%s\n", connector->encoder ? "enabled" :
Dave Airlie's avatar
Dave Airlie committed
213 214 215
			"disabled");
}

216 217 218
static ssize_t edid_show(struct file *filp, struct kobject *kobj,
			 struct bin_attribute *attr, char *buf, loff_t off,
			 size_t count)
Dave Airlie's avatar
Dave Airlie committed
219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
{
	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;
}

static ssize_t 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 = NULL;
	uint64_t subconnector;
	int is_tv = 0;
	int ret;

	switch (connector->connector_type) {
		case DRM_MODE_CONNECTOR_DVII:
			prop = dev->mode_config.dvi_i_subconnector_property;
			break;
		case DRM_MODE_CONNECTOR_Composite:
		case DRM_MODE_CONNECTOR_SVIDEO:
		case DRM_MODE_CONNECTOR_Component:
277
		case DRM_MODE_CONNECTOR_TV:
Dave Airlie's avatar
Dave Airlie committed
278 279 280 281 282 283 284 285 286 287 288 289 290
			prop = dev->mode_config.tv_subconnector_property;
			is_tv = 1;
			break;
		default:
			DRM_ERROR("Wrong connector type for this property\n");
			return 0;
	}

	if (!prop) {
		DRM_ERROR("Unable to find subconnector property\n");
		return 0;
	}

291
	ret = drm_object_property_get_value(&connector->base, prop, &subconnector);
Dave Airlie's avatar
Dave Airlie committed
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
	if (ret)
		return 0;

	return snprintf(buf, PAGE_SIZE, "%s", is_tv ?
			drm_get_tv_subconnector_name((int)subconnector) :
			drm_get_dvi_i_subconnector_name((int)subconnector));
}

static ssize_t 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 = NULL;
	uint64_t subconnector;
	int is_tv = 0;
	int ret;

	switch (connector->connector_type) {
		case DRM_MODE_CONNECTOR_DVII:
			prop = dev->mode_config.dvi_i_select_subconnector_property;
			break;
		case DRM_MODE_CONNECTOR_Composite:
		case DRM_MODE_CONNECTOR_SVIDEO:
		case DRM_MODE_CONNECTOR_Component:
318
		case DRM_MODE_CONNECTOR_TV:
Dave Airlie's avatar
Dave Airlie committed
319 320 321 322 323 324 325 326 327 328 329 330 331
			prop = dev->mode_config.tv_select_subconnector_property;
			is_tv = 1;
			break;
		default:
			DRM_ERROR("Wrong connector type for this property\n");
			return 0;
	}

	if (!prop) {
		DRM_ERROR("Unable to find select subconnector property\n");
		return 0;
	}

332
	ret = drm_object_property_get_value(&connector->base, prop, &subconnector);
Dave Airlie's avatar
Dave Airlie committed
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355
	if (ret)
		return 0;

	return snprintf(buf, PAGE_SIZE, "%s", is_tv ?
			drm_get_tv_select_name((int)subconnector) :
			drm_get_dvi_i_select_name((int)subconnector));
}

static struct device_attribute connector_attrs[] = {
	__ATTR_RO(status),
	__ATTR_RO(enabled),
	__ATTR_RO(dpms),
	__ATTR_RO(modes),
};

/* These attributes are for both DVI-I connectors and all types of tv-out. */
static struct device_attribute connector_attrs_opt1[] = {
	__ATTR_RO(subconnector),
	__ATTR_RO(select_subconnector),
};

static struct bin_attribute edid_attr = {
	.attr.name = "edid",
356
	.attr.mode = 0444,
357
	.size = 0,
Dave Airlie's avatar
Dave Airlie committed
358 359 360 361
	.read = edid_show,
};

/**
362
 * drm_sysfs_connector_add - add a connector to sysfs
Dave Airlie's avatar
Dave Airlie committed
363 364
 * @connector: connector to add
 *
365
 * Create a connector device in sysfs, along with its associated connector
Dave Airlie's avatar
Dave Airlie committed
366 367 368 369 370 371 372
 * 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;
373 374 375
	int attr_cnt = 0;
	int opt_cnt = 0;
	int i;
376
	int ret;
Dave Airlie's avatar
Dave Airlie committed
377

378 379
	if (connector->kdev)
		return 0;
Dave Airlie's avatar
Dave Airlie committed
380

381 382 383
	connector->kdev = device_create(drm_class, dev->primary->kdev,
					0, connector, "card%d-%s",
					dev->primary->index, drm_get_connector_name(connector));
Dave Airlie's avatar
Dave Airlie committed
384 385 386
	DRM_DEBUG("adding \"%s\" to sysfs\n",
		  drm_get_connector_name(connector));

387 388 389
	if (IS_ERR(connector->kdev)) {
		DRM_ERROR("failed to register connector device: %ld\n", PTR_ERR(connector->kdev));
		ret = PTR_ERR(connector->kdev);
Dave Airlie's avatar
Dave Airlie committed
390 391 392 393 394
		goto out;
	}

	/* Standard attributes */

395
	for (attr_cnt = 0; attr_cnt < ARRAY_SIZE(connector_attrs); attr_cnt++) {
396
		ret = device_create_file(connector->kdev, &connector_attrs[attr_cnt]);
Dave Airlie's avatar
Dave Airlie committed
397 398 399 400 401 402 403 404 405 406 407 408 409 410
		if (ret)
			goto err_out_files;
	}

	/* Optional attributes */
	/*
	 * In the long run it maybe a good idea to make one set of
	 * optionals per connector type.
	 */
	switch (connector->connector_type) {
		case DRM_MODE_CONNECTOR_DVII:
		case DRM_MODE_CONNECTOR_Composite:
		case DRM_MODE_CONNECTOR_SVIDEO:
		case DRM_MODE_CONNECTOR_Component:
411
		case DRM_MODE_CONNECTOR_TV:
412
			for (opt_cnt = 0; opt_cnt < ARRAY_SIZE(connector_attrs_opt1); opt_cnt++) {
413
				ret = device_create_file(connector->kdev, &connector_attrs_opt1[opt_cnt]);
Dave Airlie's avatar
Dave Airlie committed
414 415 416 417 418 419 420 421
				if (ret)
					goto err_out_files;
			}
			break;
		default:
			break;
	}

422
	ret = sysfs_create_bin_file(&connector->kdev->kobj, &edid_attr);
Dave Airlie's avatar
Dave Airlie committed
423 424 425 426 427 428 429 430 431
	if (ret)
		goto err_out_files;

	/* Let userspace know we have a new connector */
	drm_sysfs_hotplug_event(dev);

	return 0;

err_out_files:
432
	for (i = 0; i < opt_cnt; i++)
433
		device_remove_file(connector->kdev, &connector_attrs_opt1[i]);
434
	for (i = 0; i < attr_cnt; i++)
435 436
		device_remove_file(connector->kdev, &connector_attrs[i]);
	device_unregister(connector->kdev);
Dave Airlie's avatar
Dave Airlie committed
437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459

out:
	return ret;
}
EXPORT_SYMBOL(drm_sysfs_connector_add);

/**
 * 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)
{
	int i;

460
	if (!connector->kdev)
461
		return;
Dave Airlie's avatar
Dave Airlie committed
462 463 464 465
	DRM_DEBUG("removing \"%s\" from sysfs\n",
		  drm_get_connector_name(connector));

	for (i = 0; i < ARRAY_SIZE(connector_attrs); i++)
466 467 468 469
		device_remove_file(connector->kdev, &connector_attrs[i]);
	sysfs_remove_bin_file(&connector->kdev->kobj, &edid_attr);
	device_unregister(connector->kdev);
	connector->kdev = NULL;
Dave Airlie's avatar
Dave Airlie committed
470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487
}
EXPORT_SYMBOL(drm_sysfs_connector_remove);

/**
 * 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");

488
	kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
Dave Airlie's avatar
Dave Airlie committed
489
}
490
EXPORT_SYMBOL(drm_sysfs_hotplug_event);
Dave Airlie's avatar
Dave Airlie committed
491

492 493 494 495 496
static void drm_sysfs_release(struct device *dev)
{
	kfree(dev);
}

Linus Torvalds's avatar
Linus Torvalds committed
497 498
/**
 * drm_sysfs_device_add - adds a class device to sysfs for a character driver
Jesse Barnes's avatar
Jesse Barnes committed
499 500
 * @dev: DRM device to be added
 * @head: DRM head in question
Linus Torvalds's avatar
Linus Torvalds committed
501
 *
Jesse Barnes's avatar
Jesse Barnes committed
502 503 504
 * Add a DRM device to the DRM's device model class.  We use @dev's PCI device
 * as the parent for the Linux device, and make sure it has a file containing
 * the driver we're using (for userspace compatibility).
Linus Torvalds's avatar
Linus Torvalds committed
505
 */
506
int drm_sysfs_device_add(struct drm_minor *minor)
Linus Torvalds's avatar
Linus Torvalds committed
507
{
508
	char *minor_str;
509
	int r;
Jesse Barnes's avatar
Jesse Barnes committed
510

Dave Airlie's avatar
Dave Airlie committed
511 512 513 514 515 516
	if (minor->type == DRM_MINOR_CONTROL)
		minor_str = "controlD%d";
        else if (minor->type == DRM_MINOR_RENDER)
                minor_str = "renderD%d";
        else
                minor_str = "card%d";
Jesse Barnes's avatar
Jesse Barnes committed
517

518
	minor->kdev = kzalloc(sizeof(*minor->kdev), GFP_KERNEL);
519
	if (!minor->kdev) {
520 521
		r = -ENOMEM;
		goto error;
522
	}
523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539

	device_initialize(minor->kdev);
	minor->kdev->devt = MKDEV(DRM_MAJOR, minor->index);
	minor->kdev->class = drm_class;
	minor->kdev->type = &drm_sysfs_device_minor;
	minor->kdev->parent = minor->dev->dev;
	minor->kdev->release = drm_sysfs_release;
	dev_set_drvdata(minor->kdev, minor);

	r = dev_set_name(minor->kdev, minor_str, minor->index);
	if (r < 0)
		goto error;

	r = device_add(minor->kdev);
	if (r < 0)
		goto error;

Jesse Barnes's avatar
Jesse Barnes committed
540
	return 0;
541 542 543 544 545

error:
	DRM_ERROR("device create failed %d\n", r);
	put_device(minor->kdev);
	return r;
Linus Torvalds's avatar
Linus Torvalds committed
546 547 548
}

/**
Jesse Barnes's avatar
Jesse Barnes committed
549 550
 * drm_sysfs_device_remove - remove DRM device
 * @dev: DRM device to remove
Linus Torvalds's avatar
Linus Torvalds committed
551 552 553 554
 *
 * This call unregisters and cleans up a class device that was created with a
 * call to drm_sysfs_device_add()
 */
555
void drm_sysfs_device_remove(struct drm_minor *minor)
Linus Torvalds's avatar
Linus Torvalds committed
556
{
557
	if (minor->kdev)
558
		device_unregister(minor->kdev);
559
	minor->kdev = NULL;
Linus Torvalds's avatar
Linus Torvalds committed
560
}
561 562 563 564 565 566 567 568 569 570 571 572 573 574


/**
 * 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)
{
575 576 577
	if (!drm_class || IS_ERR(drm_class))
		return -ENOENT;

578 579 580 581 582 583 584 585 586 587
	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);