/* * Copyright (c) 2018-2020, 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) "[drm-shd] %s: " fmt, __func__ #include #include #include #include #include #include #include #include #include "sde_connector.h" #include #include #include #include #include "msm_drv.h" #include "msm_kms.h" #include "sde_connector.h" #include "sde_encoder.h" #include "sde_crtc.h" #include "sde_plane.h" #include "shd_drm.h" #include "shd_hw.h" static LIST_HEAD(g_base_list); struct shd_crtc { struct drm_crtc_helper_funcs helper_funcs; const struct drm_crtc_helper_funcs *orig_helper_funcs; struct drm_crtc_funcs funcs; const struct drm_crtc_funcs *orig_funcs; struct shd_display *display; }; struct shd_bridge { struct drm_bridge base; struct shd_display *display; }; struct shd_kms { struct msm_kms_funcs funcs; const struct msm_kms_funcs *orig_funcs; }; struct sde_cp_node_dummy { u32 property_id; u32 prop_flags; u32 feature; void *blob_ptr; uint64_t prop_val; const struct sde_pp_blk *pp_blk; struct list_head feature_list; struct list_head active_list; struct list_head dirty_list; }; static struct shd_kms *g_shd_kms; static enum drm_connector_status shd_display_base_detect( struct drm_connector *connector, bool force, void *disp) { return connector_status_disconnected; } static int shd_display_init_base_connector(struct drm_device *dev, struct shd_display_base *base) { struct drm_encoder *encoder; struct drm_connector *connector; struct sde_connector *sde_conn; struct drm_connector_list_iter conn_iter; int rc = 0; drm_connector_list_iter_begin(dev, &conn_iter); drm_for_each_connector_iter(connector, &conn_iter) { encoder = drm_atomic_helper_best_encoder(connector); if (encoder == base->encoder) { base->connector = connector; break; } } drm_connector_list_iter_end(&conn_iter); if (!base->connector) { SDE_ERROR("failed to find connector\n"); return -ENOENT; } /* set base connector disconnected*/ sde_conn = to_sde_connector(base->connector); base->ops = sde_conn->ops; sde_conn->ops.detect = shd_display_base_detect; SDE_DEBUG("found base connector %d\n", base->connector->base.id); return rc; } static int shd_display_init_base_encoder(struct drm_device *dev, struct shd_display_base *base) { struct drm_encoder *encoder; struct sde_encoder_hw_resources hw_res; struct sde_connector_state conn_state = {}; bool has_mst; int i, rc = 0; drm_for_each_encoder(encoder, dev) { sde_encoder_get_hw_resources(encoder, &hw_res, &conn_state.base); has_mst = (encoder->encoder_type == DRM_MODE_ENCODER_DPMST); for (i = INTF_0; i < INTF_MAX; i++) { if (hw_res.intfs[i - INTF_0] != INTF_MODE_NONE && base->intf_idx == (i - INTF_0) && base->mst_port == has_mst) { base->encoder = encoder; break; } } if (base->encoder) break; } if (!base->encoder) { SDE_ERROR("can't find base encoder for intf %d\n", base->intf_idx); return -ENOENT; } switch (base->encoder->encoder_type) { case DRM_MODE_ENCODER_DSI: base->connector_type = DRM_MODE_CONNECTOR_DSI; break; case DRM_MODE_ENCODER_TMDS: case DRM_MODE_ENCODER_DPMST: base->connector_type = DRM_MODE_CONNECTOR_DisplayPort; break; default: base->connector_type = DRM_MODE_CONNECTOR_Unknown; break; } SDE_DEBUG("found base encoder %d, type %d, connect type %d\n", base->encoder->base.id, base->encoder->encoder_type, base->connector_type); return rc; } static int shd_display_init_base_crtc(struct drm_device *dev, struct shd_display_base *base) { struct drm_crtc *crtc = NULL; struct msm_drm_private *priv; struct drm_plane *primary; int crtc_idx; int i; priv = dev->dev_private; if (base->encoder->crtc) { /* if cont splash is enabled on crtc */ crtc = base->encoder->crtc; crtc_idx = drm_crtc_index(crtc); } else { /* find last crtc for base encoder */ for (i = priv->num_crtcs - 1; i >= 0; i--) { if (base->encoder->possible_crtcs & (1 << i)) { crtc = priv->crtcs[i]; crtc_idx = i; break; } } if (!crtc) return -ENOENT; } if (priv->num_planes >= MAX_PLANES) return -ENOENT; /* create dummy primary plane for base crtc */ primary = sde_plane_init(dev, SSPP_DMA0, true, 0, 0); if (IS_ERR(primary)) return -ENOMEM; priv->planes[priv->num_planes++] = primary; if (primary->funcs->reset) primary->funcs->reset(primary); SDE_DEBUG("create dummay plane%d free plane%d\n", DRMID(primary), DRMID(crtc->primary)); crtc->primary = primary; primary->crtc = crtc; /* disable crtc from other encoders */ for (i = 0; i < priv->num_encoders; i++) { if (priv->encoders[i] != base->encoder) priv->encoders[i]->possible_crtcs &= ~(1 << crtc_idx); } base->crtc = crtc; SDE_DEBUG("found base crtc %d\n", crtc->base.id); return 0; } static int shd_crtc_validate_shared_display(struct drm_crtc *crtc, struct drm_crtc_state *state) { struct sde_crtc *sde_crtc; struct shd_crtc *shd_crtc; struct sde_crtc_state *sde_crtc_state; struct drm_plane *plane; const struct drm_plane_state *pstate; struct sde_plane_state *sde_pstate; int i; sde_crtc = to_sde_crtc(crtc); shd_crtc = sde_crtc->priv_handle; sde_crtc_state = to_sde_crtc_state(state); /* check z-pos for all planes */ drm_atomic_crtc_state_for_each_plane_state(plane, pstate, state) { sde_pstate = to_sde_plane_state(pstate); if (sde_pstate->stage >= shd_crtc->display->stage_range.size + SDE_STAGE_0) { SDE_DEBUG("plane stage %d is larger than maximum %d\n", sde_pstate->stage, shd_crtc->display->stage_range.size); return -EINVAL; } } /* check z-pos for all dim layers */ for (i = 0; i < sde_crtc_state->num_dim_layers; i++) { if (sde_crtc_state->dim_layer[i].stage >= shd_crtc->display->stage_range.size + SDE_STAGE_0) { SDE_DEBUG("dim stage %d is larger than maximum %d\n", sde_crtc_state->dim_layer[i].stage, shd_crtc->display->stage_range.size); return -EINVAL; } } /* update crtc_roi */ sde_crtc_state->crtc_roi.x = -shd_crtc->display->roi.x; sde_crtc_state->crtc_roi.y = -shd_crtc->display->roi.y; sde_crtc_state->crtc_roi.w = 0; sde_crtc_state->crtc_roi.h = 0; return 0; } static int shd_crtc_atomic_check(struct drm_crtc *crtc, struct drm_crtc_state *state) { struct sde_crtc *sde_crtc = to_sde_crtc(crtc); struct shd_crtc *shd_crtc = sde_crtc->priv_handle; struct sde_crtc_state *sde_crtc_state = to_sde_crtc_state(state); int rc; /* disable bw voting if not full size in vertical */ if (shd_crtc->display->roi.h != shd_crtc->display->base->mode.vdisplay) sde_crtc_state->bw_control = false; rc = shd_crtc->orig_helper_funcs->atomic_check(crtc, state); if (rc) return rc; return shd_crtc_validate_shared_display(crtc, state); } static int shd_crtc_atomic_set_property(struct drm_crtc *crtc, struct drm_crtc_state *state, struct drm_property *property, uint64_t val) { struct sde_crtc *sde_crtc = to_sde_crtc(crtc); struct shd_crtc *shd_crtc = sde_crtc->priv_handle; struct sde_cp_node_dummy *prop_node; if (!crtc || !state || !property) { SDE_ERROR("invalid argument(s)\n"); return -EINVAL; } /* ignore all the dspp properties */ list_for_each_entry(prop_node, &sde_crtc->feature_list, feature_list) { if (property->base.id == prop_node->property_id) return 0; } return shd_crtc->orig_funcs->atomic_set_property(crtc, state, property, val); } u32 shd_get_shared_crtc_mask(struct drm_crtc *src_crtc) { struct shd_crtc *shd_src_crtc, *shd_crtc; struct drm_crtc *crtc; u32 crtc_mask = 0; if (!src_crtc) return 0; if (src_crtc->helper_private->atomic_check != shd_crtc_atomic_check) return drm_crtc_mask(src_crtc); shd_src_crtc = to_sde_crtc(src_crtc)->priv_handle; drm_for_each_crtc(crtc, src_crtc->dev) { if (crtc->helper_private->atomic_check != shd_crtc_atomic_check) continue; shd_crtc = to_sde_crtc(crtc)->priv_handle; if (shd_src_crtc->display->base == shd_crtc->display->base) crtc_mask |= drm_crtc_mask(crtc); } return crtc_mask; } void shd_skip_shared_plane_update(struct drm_plane *plane, struct drm_crtc *crtc) { struct sde_crtc *sde_crtc; struct shd_crtc *shd_crtc; enum sde_sspp sspp; bool is_virtual; int i; if (!plane || !crtc) { SDE_ERROR("invalid plane or crtc\n"); return; } if (crtc->funcs->atomic_set_property != shd_crtc_atomic_set_property) { SDE_ERROR("not shared crtc\n"); return; } sde_crtc = to_sde_crtc(crtc); shd_crtc = sde_crtc->priv_handle; sspp = sde_plane_pipe(plane); is_virtual = is_sde_plane_virtual(plane); for (i = 0; i < sde_crtc->num_ctls; i++) sde_shd_hw_skip_sspp_clear( sde_crtc->mixers[i].hw_ctl, sspp, is_virtual); } static int shd_display_atomic_check(struct msm_kms *kms, struct drm_atomic_state *state) { struct msm_drm_private *priv; struct drm_crtc *crtc; struct drm_crtc_state *old_crtc_state, *new_crtc_state; struct drm_connector_state *conn_state; struct sde_crtc *sde_crtc; struct shd_crtc *shd_crtc; struct shd_display *display; struct shd_display_base *base; u32 base_mask = 0, enable_mask = 0, disable_mask = 0; u32 crtc_mask, active_mask; bool active; int i, rc; for_each_oldnew_crtc_in_state(state, crtc, old_crtc_state, new_crtc_state, i) { if (crtc->helper_private->atomic_check != shd_crtc_atomic_check) continue; if (old_crtc_state->active == new_crtc_state->active) continue; sde_crtc = to_sde_crtc(crtc); shd_crtc = sde_crtc->priv_handle; base = shd_crtc->display->base; base_mask |= drm_crtc_mask(base->crtc); if (new_crtc_state->active) enable_mask |= drm_crtc_mask(crtc); else disable_mask |= drm_crtc_mask(crtc); } if (!base_mask) return g_shd_kms->orig_funcs->atomic_check(kms, state); /* * If base display need to be enabled/disabled, add state * changes to the same atomic state. As base crtc is always * ahead of shared crtc in the crtc list, base crtc is * enabled/disabled before shared crtcs. */ list_for_each_entry(base, &g_base_list, head) { if (!(drm_crtc_mask(base->crtc) & base_mask)) continue; /* read old crtc state from all shared displays */ crtc_mask = active_mask = 0; list_for_each_entry(display, &base->disp_list, head) { crtc_mask |= drm_crtc_mask(display->crtc); if (display->crtc->state->active) active_mask |= drm_crtc_mask(display->crtc); } /* apply changes in state */ active_mask |= (enable_mask & crtc_mask); active_mask &= ~disable_mask; active = !!active_mask; /* skip if there is no change */ if (base->crtc->state->active == active) continue; new_crtc_state = drm_atomic_get_crtc_state(state, base->crtc); if (IS_ERR(new_crtc_state)) return PTR_ERR(new_crtc_state); new_crtc_state->active = active; conn_state = drm_atomic_get_connector_state(state, base->connector); if (IS_ERR(conn_state)) return PTR_ERR(conn_state); rc = drm_atomic_set_mode_for_crtc(new_crtc_state, active ? &base->mode : NULL); if (rc) { SDE_ERROR("failed to set mode for crtc\n"); return rc; } rc = drm_atomic_set_crtc_for_connector(conn_state, active ? base->crtc : NULL); if (rc) { SDE_ERROR("failed to set crtc for connector\n"); return rc; } } rc = g_shd_kms->orig_funcs->atomic_check(kms, state); if (rc) return rc; /* wait if there is base thread running */ priv = state->dev->dev_private; spin_lock(&priv->pending_crtcs_event.lock); wait_event_interruptible_locked( priv->pending_crtcs_event, !(priv->pending_crtcs & base_mask)); spin_unlock(&priv->pending_crtcs_event.lock); return 0; } static int shd_connector_get_info(struct drm_connector *connector, struct msm_display_info *info, void *data) { struct shd_display *display = data; if (!info || !data || !display->base || !display->drm_dev) { SDE_ERROR("invalid params\n"); return -EINVAL; } info->intf_type = display->base->connector_type; info->capabilities = MSM_DISPLAY_CAP_VID_MODE | MSM_DISPLAY_CAP_HOT_PLUG | MSM_DISPLAY_CAP_MST_MODE; info->is_connected = true; info->num_of_h_tiles = 1; info->h_tile_instance[0] = display->base->intf_idx; return 0; } static int shd_connector_get_mode_info(struct drm_connector *connector, const struct drm_display_mode *drm_mode, struct msm_mode_info *mode_info, u32 max_mixer_width, void *display) { struct shd_display *shd_display = display; if (!drm_mode || !mode_info || !max_mixer_width || !display) { SDE_ERROR("invalid params\n"); return -EINVAL; } memset(mode_info, 0, sizeof(*mode_info)); mode_info->frame_rate = drm_mode->vrefresh; mode_info->vtotal = drm_mode->vtotal; mode_info->comp_info.comp_type = MSM_DISPLAY_COMPRESSION_NONE; if (shd_display->src.h != shd_display->roi.h) mode_info->vpadding = shd_display->roi.h; return 0; } static enum drm_connector_status shd_connector_detect(struct drm_connector *conn, bool force, void *display) { struct shd_display *disp = display; struct sde_connector *sde_conn; struct drm_connector *b_conn; enum drm_connector_status status = connector_status_disconnected; if (!conn || !display || !disp->base) { SDE_ERROR("invalid params\n"); goto end; } b_conn = disp->base->connector; if (b_conn) { sde_conn = to_sde_connector(b_conn); status = disp->base->ops.detect(b_conn, force, sde_conn->display); conn->display_info.width_mm = b_conn->display_info.width_mm; conn->display_info.height_mm = b_conn->display_info.height_mm; } end: return status; } static int shd_drm_update_edid_name(struct edid *edid, const char *name) { u8 *dtd = (u8 *)&edid->detailed_timings[3]; u8 standard_header[] = {0x00, 0x00, 0x00, 0xFE, 0x00}; u32 dtd_size = 18; u32 header_size = sizeof(standard_header); if (!name) return -EINVAL; /* Fill standard header */ memcpy(dtd, standard_header, header_size); dtd_size -= header_size; dtd_size = min_t(u32, dtd_size, strlen(name)); memcpy(dtd + header_size, name, dtd_size); return 0; } static void shd_drm_update_checksum(struct edid *edid) { u8 *data = (u8 *)edid; u32 i, sum = 0; for (i = 0; i < EDID_LENGTH - 1; i++) sum += data[i]; edid->checksum = 0x100 - (sum & 0xFF); } static int shd_connector_get_modes(struct drm_connector *connector, void *data) { struct drm_display_mode drm_mode; struct shd_display *disp = data; struct drm_display_mode *m; u32 count = 0; int rc; u32 edid_size; struct edid edid; const u8 edid_buf[EDID_LENGTH] = { 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x44, 0x6D, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x1B, 0x10, 0x01, 0x03, 0x80, 0x50, 0x2D, 0x78, 0x0A, 0x0D, 0xC9, 0xA0, 0x57, 0x47, 0x98, 0x27, 0x12, 0x48, 0x4C, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, }; edid_size = min_t(u32, sizeof(edid), EDID_LENGTH); memcpy(&edid, edid_buf, edid_size); memcpy(&drm_mode, &disp->base->mode, sizeof(drm_mode)); drm_mode.hdisplay = disp->src.w; drm_mode.hsync_start = drm_mode.hdisplay; drm_mode.hsync_end = drm_mode.hsync_start; drm_mode.htotal = drm_mode.hsync_end; drm_mode.vdisplay = disp->src.h; drm_mode.vsync_start = drm_mode.vdisplay; drm_mode.vsync_end = drm_mode.vsync_start; drm_mode.vtotal = drm_mode.vsync_end; m = drm_mode_duplicate(disp->drm_dev, &drm_mode); if (!m) return 0; drm_mode_set_name(m); drm_mode_probed_add(connector, m); rc = shd_drm_update_edid_name(&edid, disp->name); if (rc) { count = 0; return count; } shd_drm_update_checksum(&edid); rc = drm_mode_connector_update_edid_property(connector, &edid); if (rc) count = 0; return 1; } static enum drm_mode_status shd_connector_mode_valid(struct drm_connector *connector, struct drm_display_mode *mode, void *display) { return MODE_OK; } static int shd_conn_set_info_blob(struct drm_connector *connector, void *info, void *display, struct msm_mode_info *mode_info) { struct shd_display *shd_display = display; if (!info || !shd_display) return -EINVAL; sde_kms_info_add_keyint(info, "max_blendstages", shd_display->stage_range.size); sde_kms_info_add_keystr(info, "display type", shd_display->display_type); sde_kms_info_add_keystr(info, "display type", shd_display->display_type); return 0; } static int shd_conn_set_property(struct drm_connector *connector, struct drm_connector_state *state, int property_index, uint64_t value, void *display) { struct sde_connector *c_conn; c_conn = to_sde_connector(connector); /* overwrite properties that are not supported */ switch (property_index) { case CONNECTOR_PROP_BL_SCALE: c_conn->bl_scale_dirty = false; c_conn->unset_bl_level = 0; break; case CONNECTOR_PROP_AD_BL_SCALE: c_conn->bl_scale_dirty = false; c_conn->unset_bl_level = 0; break; default: break; } return 0; } static inline int shd_bridge_attach(struct drm_bridge *shd_bridge) { return 0; } static inline void shd_bridge_pre_enable(struct drm_bridge *drm_bridge) { } static inline void shd_bridge_enable(struct drm_bridge *drm_bridge) { } static inline void shd_bridge_disable(struct drm_bridge *drm_bridge) { } static inline void shd_bridge_post_disable(struct drm_bridge *drm_bridge) { } static inline void shd_bridge_mode_set(struct drm_bridge *drm_bridge, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { } static inline bool shd_bridge_mode_fixup(struct drm_bridge *drm_bridge, const struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode) { return true; } static const struct drm_bridge_funcs shd_bridge_ops = { .attach = shd_bridge_attach, .mode_fixup = shd_bridge_mode_fixup, .pre_enable = shd_bridge_pre_enable, .enable = shd_bridge_enable, .disable = shd_bridge_disable, .post_disable = shd_bridge_post_disable, .mode_set = shd_bridge_mode_set, }; static int shd_drm_bridge_init(void *data, struct drm_encoder *encoder) { int rc = 0; struct shd_bridge *bridge; struct drm_device *dev; struct shd_display *display = data; struct msm_drm_private *priv = NULL; bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); if (!bridge) { rc = -ENOMEM; goto error; } dev = display->drm_dev; bridge->display = display; bridge->base.funcs = &shd_bridge_ops; bridge->base.encoder = encoder; priv = dev->dev_private; rc = drm_bridge_attach(encoder, &bridge->base, NULL); if (rc) { SDE_ERROR("failed to attach bridge, rc=%d\n", rc); goto error_free_bridge; } encoder->bridge = &bridge->base; priv->bridges[priv->num_bridges++] = &bridge->base; display->bridge = &bridge->base; return 0; error_free_bridge: kfree(bridge); error: return rc; } static void shd_drm_bridge_deinit(void *data) { struct shd_display *display = data; struct shd_bridge *bridge = container_of(display->bridge, struct shd_bridge, base); if (bridge && bridge->base.encoder) bridge->base.encoder->bridge = NULL; kfree(bridge); } static int shd_drm_obj_init(struct shd_display *display) { struct msm_drm_private *priv; struct drm_device *dev; struct drm_crtc *crtc; struct drm_plane *primary; struct drm_encoder *encoder; struct drm_connector *connector; struct sde_crtc *sde_crtc; struct shd_crtc *shd_crtc; struct msm_display_info info; int rc = 0; uint32_t i; static const struct sde_connector_ops shd_ops = { .set_info_blob = shd_conn_set_info_blob, .detect = shd_connector_detect, .get_modes = shd_connector_get_modes, .mode_valid = shd_connector_mode_valid, .get_info = shd_connector_get_info, .get_mode_info = shd_connector_get_mode_info, .set_property = shd_conn_set_property, }; static const struct sde_encoder_ops enc_ops = { .phys_init = sde_encoder_phys_shd_init, }; dev = display->drm_dev; priv = dev->dev_private; if (priv->num_crtcs >= MAX_CRTCS) { SDE_ERROR("crtc reaches the maximum %d\n", priv->num_crtcs); rc = -ENOENT; goto end; } /* search plane that doesn't belong to any crtc */ primary = NULL; for (i = 0; i < priv->num_planes; i++) { bool found = false; drm_for_each_crtc(crtc, dev) { if (crtc->primary == priv->planes[i]) { found = true; break; } } if (!found) { primary = priv->planes[i]; if (primary->type == DRM_PLANE_TYPE_OVERLAY) dev->mode_config.num_overlay_plane--; primary->type = DRM_PLANE_TYPE_PRIMARY; break; } } if (!primary) { SDE_ERROR("failed to find primary plane\n"); rc = -ENOENT; goto end; } SDE_DEBUG("find primary plane %d\n", DRMID(primary)); memset(&info, 0x0, sizeof(info)); rc = shd_connector_get_info(NULL, &info, display); if (rc) { SDE_ERROR("shd get_info failed\n"); goto end; } encoder = sde_encoder_init_with_ops(dev, &info, &enc_ops); if (IS_ERR_OR_NULL(encoder)) { SDE_ERROR("shd encoder init failed\n"); rc = -ENOENT; goto end; } SDE_DEBUG("create encoder %d\n", DRMID(encoder)); rc = shd_drm_bridge_init(display, encoder); if (rc) { SDE_ERROR("shd bridge init failed, %d\n", rc); sde_encoder_destroy(encoder); goto end; } connector = sde_connector_init(dev, encoder, NULL, display, &shd_ops, DRM_CONNECTOR_POLL_HPD, info.intf_type); if (connector) { priv->encoders[priv->num_encoders++] = encoder; priv->connectors[priv->num_connectors++] = connector; } else { SDE_ERROR("shd connector init failed\n"); shd_drm_bridge_deinit(display); sde_encoder_destroy(encoder); rc = -ENOENT; goto end; } SDE_DEBUG("create connector %d\n", DRMID(connector)); crtc = sde_crtc_init(dev, primary); if (IS_ERR(crtc)) { rc = PTR_ERR(crtc); goto end; } priv->crtcs[priv->num_crtcs++] = crtc; sde_crtc_post_init(dev, crtc); SDE_DEBUG("create crtc %d index %d\n", DRMID(crtc), drm_crtc_index(crtc)); /* update encoder's possible crtcs */ encoder->possible_crtcs = 1 << (priv->num_crtcs - 1); /* update plane's possible crtcs */ for (i = 0; i < priv->num_planes; i++) priv->planes[i]->possible_crtcs |= 1 << (priv->num_crtcs - 1); /* update crtc's check function */ shd_crtc = kzalloc(sizeof(*shd_crtc), GFP_KERNEL); if (!shd_crtc) { rc = -ENOMEM; goto end; } shd_crtc->helper_funcs = *crtc->helper_private; shd_crtc->orig_helper_funcs = crtc->helper_private; shd_crtc->helper_funcs.atomic_check = shd_crtc_atomic_check; shd_crtc->funcs = *crtc->funcs; shd_crtc->orig_funcs = crtc->funcs; shd_crtc->funcs.atomic_set_property = shd_crtc_atomic_set_property; shd_crtc->display = display; sde_crtc = to_sde_crtc(crtc); sde_crtc->priv_handle = shd_crtc; crtc->helper_private = &shd_crtc->helper_funcs; crtc->funcs = &shd_crtc->funcs; display->crtc = crtc; /* initialize display thread */ i = priv->num_crtcs - 1; priv->disp_thread[i].crtc_id = priv->crtcs[i]->base.id; kthread_init_worker(&priv->disp_thread[i].worker); priv->disp_thread[i].dev = dev; priv->disp_thread[i].thread = kthread_run(kthread_worker_fn, &priv->disp_thread[i].worker, "crtc_commit:%d", priv->disp_thread[i].crtc_id); if (IS_ERR(priv->disp_thread[i].thread)) { dev_err(dev->dev, "failed to create crtc_commit kthread\n"); priv->disp_thread[i].thread = NULL; } /* initialize event thread */ priv->event_thread[i].crtc_id = priv->crtcs[i]->base.id; kthread_init_worker(&priv->event_thread[i].worker); priv->event_thread[i].dev = dev; priv->event_thread[i].thread = kthread_run(kthread_worker_fn, &priv->event_thread[i].worker, "crtc_event:%d", priv->event_thread[i].crtc_id); if (IS_ERR(priv->event_thread[i].thread)) { dev_err(dev->dev, "failed to create crtc_event kthread\n"); priv->event_thread[i].thread = NULL; } /* re-initialize vblank as num_crtcs changes */ drm_vblank_cleanup(dev); rc = drm_vblank_init(dev, priv->num_crtcs); if (rc < 0) dev_err(dev->dev, "failed to initialize vblank\n"); /* register components */ if (crtc->funcs->late_register) crtc->funcs->late_register(crtc); if (encoder->funcs->late_register) encoder->funcs->late_register(encoder); drm_connector_register(connector); /* reset components */ if (crtc->funcs->reset) crtc->funcs->reset(crtc); if (encoder->funcs->reset) encoder->funcs->reset(encoder); if (connector->funcs->reset) connector->funcs->reset(connector); end: return rc; } static int shd_drm_base_init(struct drm_device *ddev, struct shd_display_base *base) { struct msm_drm_private *priv; int rc; rc = shd_display_init_base_encoder(ddev, base); if (rc) { SDE_ERROR("failed to find base encoder\n"); return rc; } rc = shd_display_init_base_connector(ddev, base); if (rc) { SDE_ERROR("failed to find base connector\n"); return rc; } rc = shd_display_init_base_crtc(ddev, base); if (rc) { SDE_ERROR("failed to find base crtc\n"); return rc; } if (!g_shd_kms) { priv = ddev->dev_private; g_shd_kms = kzalloc(sizeof(*g_shd_kms), GFP_KERNEL); g_shd_kms->funcs = *priv->kms->funcs; g_shd_kms->orig_funcs = priv->kms->funcs; g_shd_kms->funcs.atomic_check = shd_display_atomic_check; priv->kms->funcs = &g_shd_kms->funcs; } return rc; } static int shd_parse_display(struct shd_display *display) { struct device_node *of_node = display->pdev->dev.of_node; struct device_node *of_src, *of_roi; u32 src_w, src_h, dst_x, dst_y, dst_w, dst_h; u32 range[2]; int rc; display->base_of = of_parse_phandle(of_node, "qcom,shared-display-base", 0); if (!display->base_of) { SDE_ERROR("No base device present\n"); rc = -ENODEV; goto error; } of_src = of_get_child_by_name(of_node, "qcom,shared-display-src-mode"); if (!of_src) { SDE_ERROR("No src mode present\n"); rc = -ENODEV; goto error; } rc = of_property_read_u32(of_src, "qcom,mode-h-active", &src_w); if (rc) { SDE_ERROR("Failed to parse h active\n"); goto error; } rc = of_property_read_u32(of_src, "qcom,mode-v-active", &src_h); if (rc) { SDE_ERROR("Failed to parse v active\n"); goto error; } of_roi = of_get_child_by_name(of_node, "qcom,shared-display-dst-mode"); if (!of_roi) { SDE_ERROR("No roi mode present\n"); rc = -ENODEV; goto error; } rc = of_property_read_u32(of_roi, "qcom,mode-x-offset", &dst_x); if (rc) { SDE_ERROR("Failed to parse x offset\n"); goto error; } rc = of_property_read_u32(of_roi, "qcom,mode-y-offset", &dst_y); if (rc) { SDE_ERROR("Failed to parse y offset\n"); goto error; } rc = of_property_read_u32(of_roi, "qcom,mode-width", &dst_w); if (rc) { SDE_ERROR("Failed to parse roi width\n"); goto error; } rc = of_property_read_u32(of_roi, "qcom,mode-height", &dst_h); if (rc) { SDE_ERROR("Failed to parse roi height\n"); goto error; } rc = of_property_read_u32_array(of_node, "qcom,blend-stage-range", range, 2); if (rc) SDE_ERROR("Failed to parse blend stage range\n"); display->name = of_get_property(of_node, "qcom,shared-display-name", NULL); if (!display->name) display->name = of_node->full_name; display->src.w = src_w; display->src.h = src_h; display->roi.x = dst_x; display->roi.y = dst_y; display->roi.w = dst_w; display->roi.h = dst_h; display->stage_range.start = range[0]; display->stage_range.size = range[1]; SDE_DEBUG("%s src %dx%d dst %d,%d %dx%d range %d-%d\n", display->name, display->src.w, display->src.h, display->roi.x, display->roi.y, display->roi.w, display->roi.h, display->stage_range.start, display->stage_range.size); display->display_type = of_get_property(of_node, "qcom,display-type", NULL); if (!display->display_type) display->display_type = "unknown"; error: return rc; } static int shd_parse_base(struct shd_display_base *base) { struct device_node *of_node = base->of_node; struct device_node *node; struct drm_display_mode *mode = &base->mode; u32 h_front_porch, h_pulse_width, h_back_porch; u32 v_front_porch, v_pulse_width, v_back_porch; bool h_active_high, v_active_high; u32 flags = 0; int rc; rc = of_property_read_u32(of_node, "qcom,shared-display-base-intf", &base->intf_idx); if (rc) { SDE_ERROR("failed to read base intf, rc=%d\n", rc); goto fail; } base->mst_port = of_property_read_bool(of_node, "qcom,shared-display-base-mst"); node = of_get_child_by_name(of_node, "qcom,shared-display-base-mode"); if (!node) { SDE_ERROR("No base mode present\n"); rc = -ENODEV; goto fail; } rc = of_property_read_u32(node, "qcom,mode-h-active", &mode->hdisplay); if (rc) { SDE_ERROR("failed to read h-active, rc=%d\n", rc); goto fail; } rc = of_property_read_u32(node, "qcom,mode-h-front-porch", &h_front_porch); if (rc) { SDE_ERROR("failed to read h-front-porch, rc=%d\n", rc); goto fail; } rc = of_property_read_u32(node, "qcom,mode-h-pulse-width", &h_pulse_width); if (rc) { SDE_ERROR("failed to read h-pulse-width, rc=%d\n", rc); goto fail; } rc = of_property_read_u32(node, "qcom,mode-h-back-porch", &h_back_porch); if (rc) { SDE_ERROR("failed to read h-back-porch, rc=%d\n", rc); goto fail; } h_active_high = of_property_read_bool(node, "qcom,mode-h-active-high"); rc = of_property_read_u32(node, "qcom,mode-v-active", &mode->vdisplay); if (rc) { SDE_ERROR("failed to read v-active, rc=%d\n", rc); goto fail; } rc = of_property_read_u32(node, "qcom,mode-v-front-porch", &v_front_porch); if (rc) { SDE_ERROR("failed to read v-front-porch, rc=%d\n", rc); goto fail; } rc = of_property_read_u32(node, "qcom,mode-v-pulse-width", &v_pulse_width); if (rc) { SDE_ERROR("failed to read v-pulse-width, rc=%d\n", rc); goto fail; } rc = of_property_read_u32(node, "qcom,mode-v-back-porch", &v_back_porch); if (rc) { SDE_ERROR("failed to read v-back-porch, rc=%d\n", rc); goto fail; } v_active_high = of_property_read_bool(node, "qcom,mode-v-active-high"); rc = of_property_read_u32(node, "qcom,mode-refresh-rate", &mode->vrefresh); if (rc) { SDE_ERROR("failed to read refresh-rate, rc=%d\n", rc); goto fail; } rc = of_property_read_u32(node, "qcom,mode-clock-in-khz", &mode->clock); if (rc) { SDE_ERROR("failed to read clock, rc=%d\n", rc); goto fail; } mode->hsync_start = mode->hdisplay + h_front_porch; mode->hsync_end = mode->hsync_start + h_pulse_width; mode->htotal = mode->hsync_end + h_back_porch; mode->vsync_start = mode->vdisplay + v_front_porch; mode->vsync_end = mode->vsync_start + v_pulse_width; mode->vtotal = mode->vsync_end + v_back_porch; if (h_active_high) flags |= DRM_MODE_FLAG_PHSYNC; else flags |= DRM_MODE_FLAG_NHSYNC; if (v_active_high) flags |= DRM_MODE_FLAG_PVSYNC; else flags |= DRM_MODE_FLAG_NVSYNC; mode->flags = flags; SDE_DEBUG("base mode h[%d,%d,%d,%d] v[%d,%d,%d,%d] %d %xH %d\n", mode->hdisplay, mode->hsync_start, mode->hsync_end, mode->htotal, mode->vdisplay, mode->vsync_start, mode->vsync_end, mode->vtotal, mode->vrefresh, mode->flags, mode->clock); fail: return rc; } /** * sde_shd_probe - load shared display module * @pdev: Pointer to platform device */ static int sde_shd_probe(struct platform_device *pdev) { struct shd_display *shd_dev; struct shd_display_base *base; struct drm_minor *minor; struct drm_device *ddev; int ret; /* defer until primary drm is created */ minor = drm_minor_acquire(0); if (IS_ERR(minor)) return -EPROBE_DEFER; ddev = minor->dev; drm_minor_release(minor); if (!ddev) return -EPROBE_DEFER; shd_dev = devm_kzalloc(&pdev->dev, sizeof(*shd_dev), GFP_KERNEL); if (!shd_dev) return -ENOMEM; shd_dev->pdev = pdev; ret = shd_parse_display(shd_dev); if (ret) { SDE_ERROR("failed to parse shared display\n"); goto error; } platform_set_drvdata(pdev, shd_dev); list_for_each_entry(base, &g_base_list, head) { if (base->of_node == shd_dev->base_of) goto next; } base = devm_kzalloc(&pdev->dev, sizeof(*base), GFP_KERNEL); if (!base) { ret = -ENOMEM; goto error; } INIT_LIST_HEAD(&base->disp_list); base->of_node = shd_dev->base_of; ret = shd_parse_base(base); if (ret) { SDE_ERROR("failed to parse shared display base\n"); goto base_error; } mutex_lock(&ddev->mode_config.mutex); ret = shd_drm_base_init(ddev, base); mutex_unlock(&ddev->mode_config.mutex); if (ret) { SDE_ERROR("failed to init crtc for shared display base\n"); goto base_error; } list_add_tail(&base->head, &g_base_list); next: shd_dev->base = base; shd_dev->drm_dev = ddev; mutex_lock(&ddev->mode_config.mutex); ret = shd_drm_obj_init(shd_dev); mutex_unlock(&ddev->mode_config.mutex); if (ret) { SDE_ERROR("failed to init shared drm objects\n"); goto error; } list_add_tail(&shd_dev->head, &base->disp_list); SDE_DEBUG("add shd to intf %d\n", base->intf_idx); return 0; base_error: devm_kfree(&pdev->dev, base); error: devm_kfree(&pdev->dev, shd_dev); return ret; } /** * sde_shd_remove - unload shared display module * @pdev: Pointer to platform device */ static int sde_shd_remove(struct platform_device *pdev) { struct shd_display *shd_dev; shd_dev = platform_get_drvdata(pdev); if (!shd_dev) return 0; list_del_init(&shd_dev->head); if (list_empty(&shd_dev->base->disp_list)) list_del_init(&shd_dev->base->head); platform_set_drvdata(pdev, NULL); return 0; } static const struct of_device_id dt_match[] = { { .compatible = "qcom,shared-display"}, {}, }; static struct platform_driver sde_shd_driver = { .probe = sde_shd_probe, .remove = sde_shd_remove, .driver = { .name = "sde_shd", .of_match_table = dt_match, .suppress_bind_attrs = true, }, }; static int __init sde_shd_register(void) { return platform_driver_register(&sde_shd_driver); } static void __exit sde_shd_unregister(void) { platform_driver_unregister(&sde_shd_driver); } module_init(sde_shd_register); module_exit(sde_shd_unregister);