| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (C) 2012 Red Hat | 
					
						
							|  |  |  |  * based in parts on udlfb.c: | 
					
						
							|  |  |  |  * Copyright (C) 2009 Roberto De Ioris <roberto@unbit.it> | 
					
						
							|  |  |  |  * Copyright (C) 2009 Jaya Kumar <jayakumar.lkml@gmail.com> | 
					
						
							|  |  |  |  * Copyright (C) 2009 Bernie Thompson <bernie@plugable.com> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This file is subject to the terms and conditions of the GNU General Public | 
					
						
							|  |  |  |  * License v2. See the file COPYING in the main directory of this archive for | 
					
						
							|  |  |  |  * more details. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-02 18:01:07 +01:00
										 |  |  | #include <drm/drmP.h>
 | 
					
						
							|  |  |  | #include <drm/drm_crtc.h>
 | 
					
						
							|  |  |  | #include <drm/drm_edid.h>
 | 
					
						
							|  |  |  | #include <drm/drm_crtc_helper.h>
 | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | #include "udl_drv.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* dummy connector to just get EDID,
 | 
					
						
							|  |  |  |    all UDL appear to have a DVI-D */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static u8 *udl_get_edid(struct udl_device *udl) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	u8 *block; | 
					
						
							| 
									
										
										
										
											2013-01-11 12:08:57 +01:00
										 |  |  | 	char *rbuf; | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | 	int ret, i; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	block = kmalloc(EDID_LENGTH, GFP_KERNEL); | 
					
						
							|  |  |  | 	if (block == NULL) | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-01-11 12:08:57 +01:00
										 |  |  | 	rbuf = kmalloc(2, GFP_KERNEL); | 
					
						
							|  |  |  | 	if (rbuf == NULL) | 
					
						
							|  |  |  | 		goto error; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | 	for (i = 0; i < EDID_LENGTH; i++) { | 
					
						
							|  |  |  | 		ret = usb_control_msg(udl->ddev->usbdev, | 
					
						
							|  |  |  | 				      usb_rcvctrlpipe(udl->ddev->usbdev, 0), (0x02), | 
					
						
							|  |  |  | 				      (0x80 | (0x02 << 5)), i << 8, 0xA1, rbuf, 2, | 
					
						
							|  |  |  | 				      HZ); | 
					
						
							|  |  |  | 		if (ret < 1) { | 
					
						
							|  |  |  | 			DRM_ERROR("Read EDID byte %d failed err %x\n", i, ret); | 
					
						
							|  |  |  | 			goto error; | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		block[i] = rbuf[1]; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-01-11 12:08:57 +01:00
										 |  |  | 	kfree(rbuf); | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | 	return block; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | error: | 
					
						
							|  |  |  | 	kfree(block); | 
					
						
							| 
									
										
										
										
											2013-01-11 12:08:57 +01:00
										 |  |  | 	kfree(rbuf); | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | 	return NULL; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int udl_get_modes(struct drm_connector *connector) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct udl_device *udl = connector->dev->dev_private; | 
					
						
							|  |  |  | 	struct edid *edid; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	edid = (struct edid *)udl_get_edid(udl); | 
					
						
							| 
									
										
										
										
											2013-04-12 13:25:20 +10:00
										 |  |  | 	if (!edid) { | 
					
						
							|  |  |  | 		drm_mode_connector_update_edid_property(connector, NULL); | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2013-01-11 12:08:56 +01:00
										 |  |  | 	/*
 | 
					
						
							|  |  |  | 	 * We only read the main block, but if the monitor reports extension | 
					
						
							|  |  |  | 	 * blocks then the drm edid code expects them to be present, so patch | 
					
						
							|  |  |  | 	 * the extension count to 0. | 
					
						
							|  |  |  | 	 */ | 
					
						
							|  |  |  | 	edid->checksum += edid->extensions; | 
					
						
							|  |  |  | 	edid->extensions = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | 	drm_mode_connector_update_edid_property(connector, edid); | 
					
						
							|  |  |  | 	ret = drm_add_edid_modes(connector, edid); | 
					
						
							|  |  |  | 	kfree(edid); | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int udl_mode_valid(struct drm_connector *connector, | 
					
						
							|  |  |  | 			  struct drm_display_mode *mode) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2012-09-25 16:17:43 +10:00
										 |  |  | 	struct udl_device *udl = connector->dev->dev_private; | 
					
						
							|  |  |  | 	if (!udl->sku_pixel_limit) | 
					
						
							|  |  |  | 		return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (mode->vdisplay * mode->hdisplay > udl->sku_pixel_limit) | 
					
						
							|  |  |  | 		return MODE_VIRTUAL_Y; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static enum drm_connector_status | 
					
						
							|  |  |  | udl_detect(struct drm_connector *connector, bool force) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	if (drm_device_is_unplugged(connector->dev)) | 
					
						
							|  |  |  | 		return connector_status_disconnected; | 
					
						
							|  |  |  | 	return connector_status_connected; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-11-19 05:27:43 +00:00
										 |  |  | static struct drm_encoder* | 
					
						
							|  |  |  | udl_best_single_encoder(struct drm_connector *connector) | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | { | 
					
						
							|  |  |  | 	int enc_id = connector->encoder_ids[0]; | 
					
						
							|  |  |  | 	struct drm_mode_object *obj; | 
					
						
							|  |  |  | 	struct drm_encoder *encoder; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	obj = drm_mode_object_find(connector->dev, enc_id, DRM_MODE_OBJECT_ENCODER); | 
					
						
							|  |  |  | 	if (!obj) | 
					
						
							|  |  |  | 		return NULL; | 
					
						
							|  |  |  | 	encoder = obj_to_encoder(obj); | 
					
						
							|  |  |  | 	return encoder; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-11-19 05:27:43 +00:00
										 |  |  | static int udl_connector_set_property(struct drm_connector *connector, | 
					
						
							|  |  |  | 				      struct drm_property *property, | 
					
						
							|  |  |  | 				      uint64_t val) | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | { | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void udl_connector_destroy(struct drm_connector *connector) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	drm_sysfs_connector_remove(connector); | 
					
						
							|  |  |  | 	drm_connector_cleanup(connector); | 
					
						
							|  |  |  | 	kfree(connector); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-11-19 05:27:43 +00:00
										 |  |  | static struct drm_connector_helper_funcs udl_connector_helper_funcs = { | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | 	.get_modes = udl_get_modes, | 
					
						
							|  |  |  | 	.mode_valid = udl_mode_valid, | 
					
						
							|  |  |  | 	.best_encoder = udl_best_single_encoder, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-11-19 05:27:43 +00:00
										 |  |  | static struct drm_connector_funcs udl_connector_funcs = { | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | 	.dpms = drm_helper_connector_dpms, | 
					
						
							|  |  |  | 	.detect = udl_detect, | 
					
						
							|  |  |  | 	.fill_modes = drm_helper_probe_single_connector_modes, | 
					
						
							|  |  |  | 	.destroy = udl_connector_destroy, | 
					
						
							|  |  |  | 	.set_property = udl_connector_set_property, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int udl_connector_init(struct drm_device *dev, struct drm_encoder *encoder) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct drm_connector *connector; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	connector = kzalloc(sizeof(struct drm_connector), GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!connector) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	drm_connector_init(dev, connector, &udl_connector_funcs, DRM_MODE_CONNECTOR_DVII); | 
					
						
							|  |  |  | 	drm_connector_helper_add(connector, &udl_connector_helper_funcs); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	drm_sysfs_connector_add(connector); | 
					
						
							|  |  |  | 	drm_mode_connector_attach_encoder(connector, encoder); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2012-10-11 20:46:48 -05:00
										 |  |  | 	drm_object_attach_property(&connector->base, | 
					
						
							| 
									
										
										
										
											2010-12-15 07:14:24 +10:00
										 |  |  | 				      dev->mode_config.dirty_info_property, | 
					
						
							|  |  |  | 				      1); | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | } |