drm/msm/sde: add generic support for drm connectors
Add generic drm_connector support to sde driver. Place
all reusable code for common drm connector properties and
state handling inside 'sde_connector.'
Change-Id: Iff1871710e7261450d1f23fa6f0938c25b5fcb45
Signed-off-by: Clarence Ip <cip@codeaurora.org>
diff --git a/drivers/gpu/drm/msm/sde/sde_connector.c b/drivers/gpu/drm/msm/sde/sde_connector.c
new file mode 100644
index 0000000..28e8cbc
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde/sde_connector.c
@@ -0,0 +1,502 @@
+/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "sde-drm:[%s] " fmt, __func__
+#include "msm_drv.h"
+
+#include "sde_kms.h"
+#include "sde_connector.h"
+
+static void sde_connector_destroy(struct drm_connector *connector)
+{
+ struct sde_connector *c_conn;
+
+ if (!connector) {
+ SDE_ERROR("invalid connector\n");
+ return;
+ }
+
+ c_conn = to_sde_connector(connector);
+
+ if (c_conn->blob_sde_info)
+ drm_property_unreference_blob(c_conn->blob_sde_info);
+ msm_property_destroy(&c_conn->property_info);
+
+ drm_connector_unregister(connector);
+ drm_connector_cleanup(connector);
+ kfree(c_conn);
+}
+
+/**
+ * _sde_connector_destroy_fb - clean up connector state's out_fb buffer
+ * @c_conn: Pointer to sde connector structure
+ * @c_state: Pointer to sde connector state structure
+ */
+static void _sde_connector_destroy_fb(struct sde_connector *c_conn,
+ struct sde_connector_state *c_state)
+{
+ if (!c_state || !c_state->out_fb) {
+ SDE_ERROR("invalid state %pK\n", c_state);
+ return;
+ }
+
+ msm_framebuffer_cleanup(c_state->out_fb,
+ c_state->mmu_id);
+ drm_framebuffer_unreference(c_state->out_fb);
+ c_state->out_fb = NULL;
+
+ if (c_conn) {
+ c_state->property_values[CONNECTOR_PROP_OUT_FB] =
+ msm_property_get_default(&c_conn->property_info,
+ CONNECTOR_PROP_OUT_FB);
+ } else {
+ c_state->property_values[CONNECTOR_PROP_OUT_FB] = ~0;
+ }
+}
+
+static void sde_connector_atomic_destroy_state(struct drm_connector *connector,
+ struct drm_connector_state *state)
+{
+ struct sde_connector *c_conn = NULL;
+ struct sde_connector_state *c_state = NULL;
+
+ if (!state) {
+ SDE_ERROR("invalid state\n");
+ return;
+ }
+
+ /*
+ * The base DRM framework currently always passes in a NULL
+ * connector pointer. This is not correct, but attempt to
+ * handle that case as much as possible.
+ */
+ if (connector)
+ c_conn = to_sde_connector(connector);
+ c_state = to_sde_connector_state(state);
+
+ if (c_state->out_fb)
+ _sde_connector_destroy_fb(c_conn, c_state);
+
+ if (!c_conn) {
+ kfree(c_state);
+ } else {
+ /* destroy value helper */
+ msm_property_destroy_state(&c_conn->property_info, c_state,
+ c_state->property_values, 0);
+ }
+}
+
+static void sde_connector_atomic_reset(struct drm_connector *connector)
+{
+ struct sde_connector *c_conn;
+ struct sde_connector_state *c_state;
+
+ if (!connector) {
+ SDE_ERROR("invalid connector\n");
+ return;
+ }
+
+ c_conn = to_sde_connector(connector);
+
+ if (connector->state) {
+ sde_connector_atomic_destroy_state(connector, connector->state);
+ connector->state = 0;
+ }
+
+ c_state = msm_property_alloc_state(&c_conn->property_info);
+ if (!c_state) {
+ SDE_ERROR("state alloc failed\n");
+ return;
+ }
+
+ /* reset value helper, zero out state structure and reset properties */
+ msm_property_reset_state(&c_conn->property_info, c_state,
+ c_state->property_values, 0);
+
+ c_state->base.connector = connector;
+ connector->state = &c_state->base;
+}
+
+static struct drm_connector_state *
+sde_connector_atomic_duplicate_state(struct drm_connector *connector)
+{
+ struct sde_connector *c_conn;
+ struct sde_connector_state *c_state, *c_oldstate;
+ int rc;
+
+ if (!connector || !connector->state) {
+ SDE_ERROR("invalid connector %pK\n", connector);
+ return NULL;
+ }
+
+ c_conn = to_sde_connector(connector);
+ c_oldstate = to_sde_connector_state(connector->state);
+ c_state = msm_property_alloc_state(&c_conn->property_info);
+ if (!c_state) {
+ SDE_ERROR("state alloc failed\n");
+ return NULL;
+ }
+
+ /* duplicate value helper */
+ msm_property_duplicate_state(&c_conn->property_info,
+ c_oldstate, c_state, c_state->property_values, 0);
+
+ /* additional handling for drm framebuffer objects */
+ if (c_state->out_fb) {
+ drm_framebuffer_reference(c_state->out_fb);
+ rc = msm_framebuffer_prepare(c_state->out_fb,
+ c_state->mmu_id);
+ if (rc)
+ SDE_ERROR("failed to prepare fb, %d\n", rc);
+ }
+
+ return &c_state->base;
+}
+
+static int sde_connector_atomic_set_property(struct drm_connector *connector,
+ struct drm_connector_state *state,
+ struct drm_property *property,
+ uint64_t val)
+{
+ struct sde_connector *c_conn;
+ struct sde_connector_state *c_state;
+ int idx, rc;
+
+ if (!connector || !state || !property) {
+ SDE_ERROR("invalid argument(s), conn %pK, state %pK, prp %pK\n",
+ connector, state, property);
+ return -EINVAL;
+ }
+
+ c_conn = to_sde_connector(connector);
+ c_state = to_sde_connector_state(state);
+
+ /* generic property handling */
+ rc = msm_property_atomic_set(&c_conn->property_info,
+ c_state->property_values, 0, property, val);
+ if (rc)
+ goto end;
+
+ /* connector-specific property handling */
+ idx = msm_property_index(&c_conn->property_info, property);
+
+ if (idx == CONNECTOR_PROP_OUT_FB) {
+ /* clear old fb, if present */
+ if (c_state->out_fb)
+ _sde_connector_destroy_fb(c_conn, c_state);
+
+ /* convert fb val to drm framebuffer and prepare it */
+ c_state->out_fb =
+ drm_framebuffer_lookup(connector->dev, val);
+ if (!c_state->out_fb) {
+ SDE_ERROR("failed to look up fb %lld\n", val);
+ rc = -EFAULT;
+ } else {
+ c_state->mmu_id = c_conn->mmu_id;
+
+ rc = msm_framebuffer_prepare(c_state->out_fb,
+ c_state->mmu_id);
+ if (rc)
+ SDE_ERROR("prep fb failed, %d\n", rc);
+ }
+ }
+
+ /* check for custom property handling */
+ if (!rc && c_conn->ops.set_property) {
+ rc = c_conn->ops.set_property(connector,
+ state,
+ idx,
+ val,
+ c_conn->display);
+
+ /* potentially clean up out_fb if rc != 0 */
+ if ((idx == CONNECTOR_PROP_OUT_FB) && rc)
+ _sde_connector_destroy_fb(c_conn, c_state);
+ }
+end:
+ return rc;
+}
+
+static int sde_connector_set_property(struct drm_connector *connector,
+ struct drm_property *property,
+ uint64_t val)
+{
+ if (!connector) {
+ SDE_ERROR("invalid connector\n");
+ return -EINVAL;
+ }
+
+ return sde_connector_atomic_set_property(connector,
+ connector->state, property, val);
+}
+
+static int sde_connector_atomic_get_property(struct drm_connector *connector,
+ const struct drm_connector_state *state,
+ struct drm_property *property,
+ uint64_t *val)
+{
+ struct sde_connector *c_conn;
+ struct sde_connector_state *c_state;
+ int idx, rc = -EINVAL;
+
+ if (!connector || !state) {
+ SDE_ERROR("invalid argument(s), conn %pK, state %pK\n",
+ connector, state);
+ return -EINVAL;
+ }
+
+ c_conn = to_sde_connector(connector);
+ c_state = to_sde_connector_state(state);
+
+ idx = msm_property_index(&c_conn->property_info, property);
+
+ /* get cached property value */
+ rc = msm_property_atomic_get(&c_conn->property_info,
+ c_state->property_values, 0, property, val);
+
+ /* allow for custom override */
+ if (c_conn->ops.get_property)
+ rc = c_conn->ops.get_property(connector,
+ (struct drm_connector_state *)state,
+ idx,
+ val,
+ c_conn->display);
+ return rc;
+}
+
+static enum drm_connector_status
+sde_connector_detect(struct drm_connector *connector, bool force)
+{
+ enum drm_connector_status status = connector_status_unknown;
+ struct sde_connector *c_conn;
+
+ if (!connector) {
+ SDE_ERROR("invalid connector\n");
+ return status;
+ }
+
+ c_conn = to_sde_connector(connector);
+
+ if (c_conn->ops.detect)
+ status = c_conn->ops.detect(connector,
+ force,
+ c_conn->display);
+
+ return status;
+}
+
+static const struct drm_connector_funcs sde_connector_ops = {
+ .dpms = drm_atomic_helper_connector_dpms,
+ .reset = sde_connector_atomic_reset,
+ .detect = sde_connector_detect,
+ .destroy = sde_connector_destroy,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .atomic_duplicate_state = sde_connector_atomic_duplicate_state,
+ .atomic_destroy_state = sde_connector_atomic_destroy_state,
+ .atomic_set_property = sde_connector_atomic_set_property,
+ .atomic_get_property = sde_connector_atomic_get_property,
+ .set_property = sde_connector_set_property,
+};
+
+static int sde_connector_get_modes(struct drm_connector *connector)
+{
+ struct sde_connector *c_conn;
+
+ if (!connector) {
+ SDE_ERROR("invalid connector\n");
+ return 0;
+ }
+
+ c_conn = to_sde_connector(connector);
+ if (!c_conn->ops.get_modes) {
+ SDE_DEBUG("missing get_modes callback\n");
+ return 0;
+ }
+
+ return c_conn->ops.get_modes(connector, c_conn->display);
+}
+
+static enum drm_mode_status
+sde_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct sde_connector *c_conn;
+
+ if (!connector || !mode) {
+ SDE_ERROR("invalid argument(s), conn %pK, mode %pK\n",
+ connector, mode);
+ return MODE_ERROR;
+ }
+
+ c_conn = to_sde_connector(connector);
+
+ if (c_conn->ops.mode_valid)
+ return c_conn->ops.mode_valid(connector, mode, c_conn->display);
+
+ /* assume all modes okay by default */
+ return MODE_OK;
+}
+
+static struct drm_encoder *
+sde_connector_best_encoder(struct drm_connector *connector)
+{
+ struct sde_connector *c_conn = to_sde_connector(connector);
+
+ if (!connector) {
+ SDE_ERROR("invalid connector\n");
+ return NULL;
+ }
+
+ /*
+ * This is true for now, revisit this code when multiple encoders are
+ * supported.
+ */
+ return c_conn->encoder;
+}
+
+static const struct drm_connector_helper_funcs sde_connector_helper_ops = {
+ .get_modes = sde_connector_get_modes,
+ .mode_valid = sde_connector_mode_valid,
+ .best_encoder = sde_connector_best_encoder,
+};
+
+struct drm_connector *sde_connector_init(struct drm_device *dev,
+ struct drm_encoder *encoder,
+ struct drm_panel *panel,
+ void *display,
+ const struct sde_connector_ops *ops,
+ int connector_poll,
+ int connector_type)
+{
+ struct msm_drm_private *priv;
+ struct sde_kms *sde_kms;
+ struct sde_kms_info *info;
+ struct sde_connector *c_conn = NULL;
+ int rc;
+
+ if (!dev || !dev->dev_private || !encoder) {
+ SDE_ERROR("invalid argument(s), dev %pK, enc %pK\n",
+ dev, encoder);
+ return ERR_PTR(-EINVAL);
+ }
+
+ priv = dev->dev_private;
+ if (!priv->kms) {
+ SDE_ERROR("invalid kms reference\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ c_conn = kzalloc(sizeof(*c_conn), GFP_KERNEL);
+ if (!c_conn) {
+ SDE_ERROR("failed to alloc sde connector\n");
+ return ERR_PTR(-ENOMEM);
+ }
+
+ rc = drm_connector_init(dev,
+ &c_conn->base,
+ &sde_connector_ops,
+ connector_type);
+ if (rc)
+ goto error_free_conn;
+
+ c_conn->connector_type = connector_type;
+ c_conn->encoder = encoder;
+ c_conn->panel = panel;
+ c_conn->display = display;
+
+ sde_kms = to_sde_kms(priv->kms);
+ c_conn->mmu_id = sde_kms->mmu_id;
+
+ if (ops)
+ c_conn->ops = *ops;
+
+ c_conn->base.helper_private = &sde_connector_helper_ops;
+ c_conn->base.polled = connector_poll;
+ c_conn->base.interlace_allowed = 0;
+ c_conn->base.doublescan_allowed = 0;
+
+ snprintf(c_conn->name,
+ SDE_CONNECTOR_NAME_SIZE,
+ "conn%u",
+ c_conn->base.base.id);
+
+ rc = drm_connector_register(&c_conn->base);
+ if (rc) {
+ SDE_ERROR("failed to register drm connector, %d\n", rc);
+ goto error_cleanup_conn;
+ }
+
+ rc = drm_mode_connector_attach_encoder(&c_conn->base, encoder);
+ if (rc) {
+ SDE_ERROR("failed to attach encoder to connector, %d\n", rc);
+ goto error_unregister_conn;
+ }
+
+ /* create properties */
+ msm_property_init(&c_conn->property_info, &c_conn->base.base, dev,
+ priv->conn_property, c_conn->property_data,
+ CONNECTOR_PROP_COUNT, CONNECTOR_PROP_BLOBCOUNT,
+ sizeof(struct sde_connector_state));
+
+ if (c_conn->ops.post_init) {
+ info = kmalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ SDE_ERROR("failed to allocate info buffer\n");
+ rc = -ENOMEM;
+ goto error_unregister_conn;
+ }
+
+ sde_kms_info_reset(info);
+ rc = c_conn->ops.post_init(&c_conn->base, info, display);
+ if (rc) {
+ SDE_ERROR("post-init failed, %d\n", rc);
+ kfree(info);
+ goto error_unregister_conn;
+ }
+
+ msm_property_install_blob(&c_conn->property_info,
+ "sde_info",
+ DRM_MODE_PROP_IMMUTABLE,
+ CONNECTOR_PROP_SDE_INFO);
+
+ msm_property_set_blob(&c_conn->property_info,
+ &c_conn->blob_sde_info,
+ SDE_KMS_INFO_DATA(info),
+ SDE_KMS_INFO_DATALEN(info),
+ CONNECTOR_PROP_SDE_INFO);
+ kfree(info);
+ }
+
+ rc = msm_property_install_get_status(&c_conn->property_info);
+ if (rc) {
+ SDE_ERROR("failed to create one or more properties\n");
+ goto error_destroy_property;
+ }
+
+ priv->connectors[priv->num_connectors++] = &c_conn->base;
+
+ return &c_conn->base;
+
+error_destroy_property:
+ if (c_conn->blob_sde_info)
+ drm_property_unreference_blob(c_conn->blob_sde_info);
+ msm_property_destroy(&c_conn->property_info);
+error_unregister_conn:
+ drm_connector_unregister(&c_conn->base);
+error_cleanup_conn:
+ drm_connector_cleanup(&c_conn->base);
+error_free_conn:
+ kfree(c_conn);
+
+ return ERR_PTR(rc);
+}
+