| 
									
										
										
										
											2012-08-15 13:59:49 +01:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * Copyright (C) 2012 Russell King | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or modify | 
					
						
							|  |  |  |  * it under the terms of the GNU General Public License version 2 as | 
					
						
							|  |  |  |  * published by the Free Software Foundation. | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | #include <drm/drmP.h>
 | 
					
						
							|  |  |  | #include <drm/drm_crtc_helper.h>
 | 
					
						
							|  |  |  | #include <drm/drm_edid.h>
 | 
					
						
							|  |  |  | #include <drm/drm_encoder_slave.h>
 | 
					
						
							|  |  |  | #include "armada_output.h"
 | 
					
						
							|  |  |  | #include "armada_drm.h"
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct armada_connector { | 
					
						
							|  |  |  | 	struct drm_connector conn; | 
					
						
							|  |  |  | 	const struct armada_output_type *type; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #define drm_to_armada_conn(c) container_of(c, struct armada_connector, conn)
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | struct drm_encoder *armada_drm_connector_encoder(struct drm_connector *conn) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct drm_encoder *enc = conn->encoder; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return enc ? enc : drm_encoder_find(conn->dev, conn->encoder_ids[0]); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static enum drm_connector_status armada_drm_connector_detect( | 
					
						
							|  |  |  | 	struct drm_connector *conn, bool force) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct armada_connector *dconn = drm_to_armada_conn(conn); | 
					
						
							|  |  |  | 	enum drm_connector_status status = connector_status_disconnected; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (dconn->type->detect) { | 
					
						
							|  |  |  | 		status = dconn->type->detect(conn, force); | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		struct drm_encoder *enc = armada_drm_connector_encoder(conn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if (enc) | 
					
						
							|  |  |  | 			status = encoder_helper_funcs(enc)->detect(enc, conn); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return status; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static void armada_drm_connector_destroy(struct drm_connector *conn) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct armada_connector *dconn = drm_to_armada_conn(conn); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-29 16:57:41 +01:00
										 |  |  | 	drm_connector_unregister(conn); | 
					
						
							| 
									
										
										
										
											2012-08-15 13:59:49 +01:00
										 |  |  | 	drm_connector_cleanup(conn); | 
					
						
							|  |  |  | 	kfree(dconn); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static int armada_drm_connector_set_property(struct drm_connector *conn, | 
					
						
							|  |  |  | 	struct drm_property *property, uint64_t value) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct armada_connector *dconn = drm_to_armada_conn(conn); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (!dconn->type->set_property) | 
					
						
							|  |  |  | 		return -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return dconn->type->set_property(conn, property, value); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | static const struct drm_connector_funcs armada_drm_conn_funcs = { | 
					
						
							|  |  |  | 	.dpms		= drm_helper_connector_dpms, | 
					
						
							|  |  |  | 	.fill_modes	= drm_helper_probe_single_connector_modes, | 
					
						
							|  |  |  | 	.detect		= armada_drm_connector_detect, | 
					
						
							|  |  |  | 	.destroy	= armada_drm_connector_destroy, | 
					
						
							|  |  |  | 	.set_property	= armada_drm_connector_set_property, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void armada_drm_encoder_prepare(struct drm_encoder *encoder) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_OFF); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | void armada_drm_encoder_commit(struct drm_encoder *encoder) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	encoder_helper_funcs(encoder)->dpms(encoder, DRM_MODE_DPMS_ON); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | bool armada_drm_encoder_mode_fixup(struct drm_encoder *encoder, | 
					
						
							|  |  |  | 	const struct drm_display_mode *mode, struct drm_display_mode *adjusted) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	return true; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Shouldn't this be a generic helper function? */ | 
					
						
							|  |  |  | int armada_drm_slave_encoder_mode_valid(struct drm_connector *conn, | 
					
						
							|  |  |  | 	struct drm_display_mode *mode) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct drm_encoder *encoder = armada_drm_connector_encoder(conn); | 
					
						
							|  |  |  | 	int valid = MODE_BAD; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (encoder) { | 
					
						
							|  |  |  | 		struct drm_encoder_slave *slave = to_encoder_slave(encoder); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		valid = slave->slave_funcs->mode_valid(encoder, mode); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return valid; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int armada_drm_slave_encoder_set_property(struct drm_connector *conn, | 
					
						
							|  |  |  | 	struct drm_property *property, uint64_t value) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct drm_encoder *encoder = armada_drm_connector_encoder(conn); | 
					
						
							|  |  |  | 	int rc = -EINVAL; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if (encoder) { | 
					
						
							|  |  |  | 		struct drm_encoder_slave *slave = to_encoder_slave(encoder); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		rc = slave->slave_funcs->set_property(encoder, conn, property, | 
					
						
							|  |  |  | 						      value); | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	return rc; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | int armada_output_create(struct drm_device *dev, | 
					
						
							|  |  |  | 	const struct armada_output_type *type, const void *data) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  | 	struct armada_connector *dconn; | 
					
						
							|  |  |  | 	int ret; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dconn = kzalloc(sizeof(*dconn), GFP_KERNEL); | 
					
						
							|  |  |  | 	if (!dconn) | 
					
						
							|  |  |  | 		return -ENOMEM; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	dconn->type = type; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = drm_connector_init(dev, &dconn->conn, &armada_drm_conn_funcs, | 
					
						
							|  |  |  | 				 type->connector_type); | 
					
						
							|  |  |  | 	if (ret) { | 
					
						
							|  |  |  | 		DRM_ERROR("unable to init connector\n"); | 
					
						
							|  |  |  | 		goto err_destroy_dconn; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	ret = type->create(&dconn->conn, data); | 
					
						
							|  |  |  | 	if (ret) | 
					
						
							|  |  |  | 		goto err_conn; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2014-05-29 16:57:41 +01:00
										 |  |  | 	ret = drm_connector_register(&dconn->conn); | 
					
						
							| 
									
										
										
										
											2012-08-15 13:59:49 +01:00
										 |  |  | 	if (ret) | 
					
						
							|  |  |  | 		goto err_sysfs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |  err_sysfs: | 
					
						
							|  |  |  | 	if (dconn->conn.encoder) | 
					
						
							|  |  |  | 		dconn->conn.encoder->funcs->destroy(dconn->conn.encoder); | 
					
						
							|  |  |  |  err_conn: | 
					
						
							|  |  |  | 	drm_connector_cleanup(&dconn->conn); | 
					
						
							|  |  |  |  err_destroy_dconn: | 
					
						
							|  |  |  | 	kfree(dconn); | 
					
						
							|  |  |  | 	return ret; | 
					
						
							|  |  |  | } |