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