// SPDX-License-Identifier: GPL-2.0-only /* * Copyright (c) 2015-2020, The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ #include #include #include "msm_kms.h" #include "sde_kms.h" #include "sde_wb.h" #include "sde_formats.h" /* maximum display mode resolution if not available from catalog */ #define SDE_WB_MODE_MAX_WIDTH 4096 #define SDE_WB_MODE_MAX_HEIGHT 4096 /* Serialization lock for sde_wb_list */ static DEFINE_MUTEX(sde_wb_list_lock); /* List of all writeback devices installed */ static LIST_HEAD(sde_wb_list); /** * sde_wb_is_format_valid - check if given format/modifier is supported * @wb_dev: Pointer to writeback device * @pixel_format: Fourcc pixel format * @format_modifier: Format modifier * Returns: true if valid; false otherwise */ static int sde_wb_is_format_valid(struct sde_wb_device *wb_dev, u32 pixel_format, u64 format_modifier) { const struct sde_format_extended *fmts = wb_dev->wb_cfg->format_list; int i; if (!fmts) return false; for (i = 0; fmts[i].fourcc_format; i++) if ((fmts[i].modifier == format_modifier) && (fmts[i].fourcc_format == pixel_format)) return true; return false; } enum drm_connector_status sde_wb_connector_detect(struct drm_connector *connector, bool force, void *display) { enum drm_connector_status rc = connector_status_unknown; SDE_DEBUG("\n"); if (display) rc = ((struct sde_wb_device *)display)->detect_status; return rc; } int sde_wb_connector_get_modes(struct drm_connector *connector, void *display, const struct msm_resource_caps_info *avail_res) { struct sde_wb_device *wb_dev; int num_modes = 0; if (!connector || !display) return 0; wb_dev = display; SDE_DEBUG("\n"); mutex_lock(&wb_dev->wb_lock); if (wb_dev->count_modes && wb_dev->modes) { struct drm_display_mode *mode; int i, ret; for (i = 0; i < wb_dev->count_modes; i++) { mode = drm_mode_create(connector->dev); if (!mode) { SDE_ERROR("failed to create mode\n"); break; } ret = drm_mode_convert_umode(wb_dev->drm_dev, mode, &wb_dev->modes[i]); if (ret) { SDE_ERROR("failed to convert mode %d\n", ret); break; } drm_mode_probed_add(connector, mode); num_modes++; } } else { u32 max_width = (wb_dev->wb_cfg && wb_dev->wb_cfg->sblk) ? wb_dev->wb_cfg->sblk->maxlinewidth : SDE_WB_MODE_MAX_WIDTH; num_modes = drm_add_modes_noedid(connector, max_width, SDE_WB_MODE_MAX_HEIGHT); } mutex_unlock(&wb_dev->wb_lock); return num_modes; } struct drm_framebuffer * sde_wb_connector_state_get_output_fb(struct drm_connector_state *state) { if (!state || !state->connector || (state->connector->connector_type != DRM_MODE_CONNECTOR_VIRTUAL)) { SDE_ERROR("invalid params\n"); return NULL; } SDE_DEBUG("\n"); return sde_connector_get_out_fb(state); } int sde_wb_connector_state_get_output_roi(struct drm_connector_state *state, struct sde_rect *roi) { if (!state || !roi || !state->connector || (state->connector->connector_type != DRM_MODE_CONNECTOR_VIRTUAL)) { SDE_ERROR("invalid params\n"); return -EINVAL; } SDE_DEBUG("\n"); roi->x = sde_connector_get_property(state, CONNECTOR_PROP_DST_X); roi->y = sde_connector_get_property(state, CONNECTOR_PROP_DST_Y); roi->w = sde_connector_get_property(state, CONNECTOR_PROP_DST_W); roi->h = sde_connector_get_property(state, CONNECTOR_PROP_DST_H); return 0; } /** * sde_wb_connector_set_modes - set writeback modes and connection status * @wb_dev: Pointer to write back device * @count_modes: Count of modes * @modes: Pointer to writeback mode requested * @connected: Connection status requested * Returns: 0 if success; error code otherwise */ static int sde_wb_connector_set_modes(struct sde_wb_device *wb_dev, u32 count_modes, struct drm_mode_modeinfo __user *modes, bool connected) { struct drm_mode_modeinfo *modeinfo = NULL; int ret = 0; int i; if (!wb_dev || !wb_dev->connector || (wb_dev->connector->connector_type != DRM_MODE_CONNECTOR_VIRTUAL)) { SDE_ERROR("invalid params\n"); return -EINVAL; } SDE_DEBUG("\n"); if (connected) { SDE_DEBUG("connect\n"); if (!count_modes || !modes) { SDE_ERROR("invalid count_modes :%u and modes :%d\n", count_modes, !modes); return -EINVAL; } modeinfo = kcalloc(count_modes, sizeof(struct drm_mode_modeinfo), GFP_KERNEL); if (!modeinfo) { SDE_ERROR("invalid params\n"); ret = -ENOMEM; goto error; } if (copy_from_user(modeinfo, modes, count_modes * sizeof(struct drm_mode_modeinfo))) { SDE_ERROR("failed to copy modes\n"); kfree(modeinfo); ret = -EFAULT; goto error; } for (i = 0; i < count_modes; i++) { struct drm_display_mode dispmode; memset(&dispmode, 0, sizeof(dispmode)); ret = drm_mode_convert_umode(wb_dev->drm_dev, &dispmode, &modeinfo[i]); if (ret) { SDE_ERROR( "failed to convert mode %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x status:%d rc:%d\n", i, modeinfo[i].name, modeinfo[i].vrefresh, modeinfo[i].clock, modeinfo[i].hdisplay, modeinfo[i].hsync_start, modeinfo[i].hsync_end, modeinfo[i].htotal, modeinfo[i].vdisplay, modeinfo[i].vsync_start, modeinfo[i].vsync_end, modeinfo[i].vtotal, modeinfo[i].type, modeinfo[i].flags, dispmode.status, ret); kfree(modeinfo); goto error; } } if (wb_dev->modes) { wb_dev->count_modes = 0; kfree(wb_dev->modes); wb_dev->modes = NULL; } wb_dev->count_modes = count_modes; wb_dev->modes = modeinfo; wb_dev->detect_status = connector_status_connected; } else { SDE_DEBUG("disconnect\n"); if (wb_dev->modes) { wb_dev->count_modes = 0; kfree(wb_dev->modes); wb_dev->modes = NULL; } wb_dev->detect_status = connector_status_disconnected; } error: return ret; } int sde_wb_connector_set_property(struct drm_connector *connector, struct drm_connector_state *state, int property_index, uint64_t value, void *display) { struct sde_wb_device *wb_dev = display; struct drm_framebuffer *out_fb; int rc = 0; SDE_DEBUG("\n"); if (state && (property_index == CONNECTOR_PROP_OUT_FB)) { const struct sde_format *sde_format; out_fb = sde_connector_get_out_fb(state); if (!out_fb) goto done; sde_format = sde_get_sde_format_ext(out_fb->format->format, out_fb->modifier); if (!sde_format) { SDE_ERROR("failed to get sde format\n"); rc = -EINVAL; goto done; } if (!sde_wb_is_format_valid(wb_dev, out_fb->format->format, out_fb->modifier)) { SDE_ERROR("unsupported writeback format 0x%x/0x%llx\n", out_fb->format->format, out_fb->modifier); rc = -EINVAL; goto done; } } done: return rc; } int sde_wb_get_info(struct drm_connector *connector, struct msm_display_info *info, void *display) { struct sde_wb_device *wb_dev = display; if (!info || !wb_dev) { pr_err("invalid params\n"); return -EINVAL; } memset(info, 0, sizeof(struct msm_display_info)); info->intf_type = DRM_MODE_CONNECTOR_VIRTUAL; info->num_of_h_tiles = 1; info->h_tile_instance[0] = sde_wb_get_index(display); info->is_connected = true; info->capabilities = MSM_DISPLAY_CAP_HOT_PLUG | MSM_DISPLAY_CAP_EDID; info->max_width = (wb_dev->wb_cfg && wb_dev->wb_cfg->sblk) ? wb_dev->wb_cfg->sblk->maxlinewidth : SDE_WB_MODE_MAX_WIDTH; info->max_height = SDE_WB_MODE_MAX_HEIGHT; return 0; } int sde_wb_get_mode_info(struct drm_connector *connector, const struct drm_display_mode *drm_mode, struct msm_mode_info *mode_info, void *display, const struct msm_resource_caps_info *avail_res) { const u32 dual_lm = 2; const u32 single_lm = 1; const u32 single_intf = 1; const u32 no_enc = 0; struct msm_display_topology *topology; struct sde_wb_device *wb_dev = display; u16 hdisplay; int i; if (!drm_mode || !mode_info || !avail_res || !avail_res->max_mixer_width || !display) { pr_err("invalid params\n"); return -EINVAL; } hdisplay = drm_mode->hdisplay; /* find maximum display width to support */ for (i = 0; i < wb_dev->count_modes; i++) hdisplay = max(hdisplay, wb_dev->modes[i].hdisplay); topology = &mode_info->topology; topology->num_lm = (avail_res->max_mixer_width <= hdisplay) ? dual_lm : single_lm; topology->num_enc = no_enc; topology->num_intf = single_intf; mode_info->comp_info.comp_type = MSM_DISPLAY_COMPRESSION_NONE; mode_info->wide_bus_en = false; mode_info->comp_info.comp_ratio = MSM_DISPLAY_COMPRESSION_RATIO_NONE; return 0; } int sde_wb_connector_set_info_blob(struct drm_connector *connector, void *info, void *display, struct msm_mode_info *mode_info) { struct sde_wb_device *wb_dev = display; const struct sde_format_extended *format_list; if (!connector || !info || !display || !wb_dev->wb_cfg) { SDE_ERROR("invalid params\n"); return -EINVAL; } format_list = wb_dev->wb_cfg->format_list; /* * Populate info buffer */ if (format_list) { sde_kms_info_start(info, "pixel_formats"); while (format_list->fourcc_format) { sde_kms_info_append_format(info, format_list->fourcc_format, format_list->modifier); ++format_list; } sde_kms_info_stop(info); } sde_kms_info_add_keyint(info, "wb_intf_index", wb_dev->wb_idx - WB_0); sde_kms_info_add_keyint(info, "maxlinewidth", wb_dev->wb_cfg->sblk->maxlinewidth); sde_kms_info_start(info, "features"); if (wb_dev->wb_cfg && (wb_dev->wb_cfg->features & BIT(SDE_WB_UBWC))) sde_kms_info_append(info, "wb_ubwc"); sde_kms_info_stop(info); return 0; } int sde_wb_connector_post_init(struct drm_connector *connector, void *display) { struct sde_connector *c_conn; struct sde_wb_device *wb_dev = display; static const struct drm_prop_enum_list e_fb_translation_mode[] = { {SDE_DRM_FB_NON_SEC, "non_sec"}, {SDE_DRM_FB_SEC, "sec"}, }; if (!connector || !display || !wb_dev->wb_cfg) { SDE_ERROR("invalid params\n"); return -EINVAL; } c_conn = to_sde_connector(connector); wb_dev->connector = connector; wb_dev->detect_status = connector_status_connected; /* * Add extra connector properties */ msm_property_install_range(&c_conn->property_info, "FB_ID", 0x0, 0, ~0, 0, CONNECTOR_PROP_OUT_FB); msm_property_install_range(&c_conn->property_info, "DST_X", 0x0, 0, UINT_MAX, 0, CONNECTOR_PROP_DST_X); msm_property_install_range(&c_conn->property_info, "DST_Y", 0x0, 0, UINT_MAX, 0, CONNECTOR_PROP_DST_Y); msm_property_install_range(&c_conn->property_info, "DST_W", 0x0, 0, UINT_MAX, 0, CONNECTOR_PROP_DST_W); msm_property_install_range(&c_conn->property_info, "DST_H", 0x0, 0, UINT_MAX, 0, CONNECTOR_PROP_DST_H); msm_property_install_enum(&c_conn->property_info, "fb_translation_mode", 0x0, 0, e_fb_translation_mode, ARRAY_SIZE(e_fb_translation_mode), CONNECTOR_PROP_FB_TRANSLATION_MODE); return 0; } struct drm_framebuffer *sde_wb_get_output_fb(struct sde_wb_device *wb_dev) { struct drm_framebuffer *fb; if (!wb_dev || !wb_dev->connector) { SDE_ERROR("invalid params\n"); return NULL; } SDE_DEBUG("\n"); mutex_lock(&wb_dev->wb_lock); fb = sde_wb_connector_state_get_output_fb(wb_dev->connector->state); mutex_unlock(&wb_dev->wb_lock); return fb; } int sde_wb_get_output_roi(struct sde_wb_device *wb_dev, struct sde_rect *roi) { int rc; if (!wb_dev || !wb_dev->connector || !roi) { SDE_ERROR("invalid params\n"); return -EINVAL; } SDE_DEBUG("\n"); mutex_lock(&wb_dev->wb_lock); rc = sde_wb_connector_state_get_output_roi( wb_dev->connector->state, roi); mutex_unlock(&wb_dev->wb_lock); return rc; } u32 sde_wb_get_num_of_displays(void) { u32 count = 0; struct sde_wb_device *wb_dev; SDE_DEBUG("\n"); mutex_lock(&sde_wb_list_lock); list_for_each_entry(wb_dev, &sde_wb_list, wb_list) { count++; } mutex_unlock(&sde_wb_list_lock); return count; } int wb_display_get_displays(void **display_array, u32 max_display_count) { struct sde_wb_device *curr; int i = 0; SDE_DEBUG("\n"); if (!display_array || !max_display_count) { if (!display_array) SDE_ERROR("invalid param\n"); return 0; } mutex_lock(&sde_wb_list_lock); list_for_each_entry(curr, &sde_wb_list, wb_list) { if (i >= max_display_count) break; display_array[i++] = curr; } mutex_unlock(&sde_wb_list_lock); return i; } int sde_wb_config(struct drm_device *drm_dev, void *data, struct drm_file *file_priv) { struct sde_drm_wb_cfg *config = data; struct msm_drm_private *priv; struct sde_wb_device *wb_dev = NULL; struct sde_wb_device *curr; struct drm_connector *connector; uint32_t flags; uint32_t connector_id; uint32_t count_modes; uint64_t modes; int rc; if (!drm_dev || !data) { SDE_ERROR("invalid params\n"); return -EINVAL; } SDE_DEBUG("\n"); flags = config->flags; connector_id = config->connector_id; count_modes = config->count_modes; modes = config->modes; priv = drm_dev->dev_private; connector = drm_connector_lookup(drm_dev, file_priv, connector_id); if (!connector) { SDE_ERROR("failed to find connector\n"); rc = -ENOENT; goto fail; } mutex_lock(&sde_wb_list_lock); list_for_each_entry(curr, &sde_wb_list, wb_list) { if (curr->connector == connector) { wb_dev = curr; break; } } mutex_unlock(&sde_wb_list_lock); if (!wb_dev) { SDE_ERROR("failed to find wb device\n"); rc = -ENOENT; goto fail; } mutex_lock(&wb_dev->wb_lock); rc = sde_wb_connector_set_modes(wb_dev, count_modes, (struct drm_mode_modeinfo __user *) (uintptr_t) modes, (flags & SDE_DRM_WB_CFG_FLAGS_CONNECTED) ? true : false); mutex_unlock(&wb_dev->wb_lock); drm_helper_hpd_irq_event(drm_dev); fail: return rc; } /** * _sde_wb_dev_init - perform device initialization * @wb_dev: Pointer to writeback device */ static int _sde_wb_dev_init(struct sde_wb_device *wb_dev) { int rc = 0; if (!wb_dev) { SDE_ERROR("invalid params\n"); return -EINVAL; } SDE_DEBUG("\n"); return rc; } /** * _sde_wb_dev_deinit - perform device de-initialization * @wb_dev: Pointer to writeback device */ static int _sde_wb_dev_deinit(struct sde_wb_device *wb_dev) { int rc = 0; if (!wb_dev) { SDE_ERROR("invalid params\n"); return -EINVAL; } SDE_DEBUG("\n"); return rc; } /** * sde_wb_bind - bind writeback device with controlling device * @dev: Pointer to base of platform device * @master: Pointer to container of drm device * @data: Pointer to private data * Returns: Zero on success */ static int sde_wb_bind(struct device *dev, struct device *master, void *data) { struct sde_wb_device *wb_dev; if (!dev || !master) { SDE_ERROR("invalid params\n"); return -EINVAL; } wb_dev = platform_get_drvdata(to_platform_device(dev)); if (!wb_dev) { SDE_ERROR("invalid wb device\n"); return -EINVAL; } SDE_DEBUG("\n"); mutex_lock(&wb_dev->wb_lock); wb_dev->drm_dev = dev_get_drvdata(master); mutex_unlock(&wb_dev->wb_lock); return 0; } /** * sde_wb_unbind - unbind writeback from controlling device * @dev: Pointer to base of platform device * @master: Pointer to container of drm device * @data: Pointer to private data */ static void sde_wb_unbind(struct device *dev, struct device *master, void *data) { struct sde_wb_device *wb_dev; if (!dev) { SDE_ERROR("invalid params\n"); return; } wb_dev = platform_get_drvdata(to_platform_device(dev)); if (!wb_dev) { SDE_ERROR("invalid wb device\n"); return; } SDE_DEBUG("\n"); mutex_lock(&wb_dev->wb_lock); wb_dev->drm_dev = NULL; mutex_unlock(&wb_dev->wb_lock); } static const struct component_ops sde_wb_comp_ops = { .bind = sde_wb_bind, .unbind = sde_wb_unbind, }; /** * sde_wb_drm_init - perform DRM initialization * @wb_dev: Pointer to writeback device * @encoder: Pointer to associated encoder */ int sde_wb_drm_init(struct sde_wb_device *wb_dev, struct drm_encoder *encoder) { int rc = 0; if (!wb_dev || !wb_dev->drm_dev || !encoder) { SDE_ERROR("invalid params\n"); return -EINVAL; } SDE_DEBUG("\n"); mutex_lock(&wb_dev->wb_lock); if (wb_dev->drm_dev->dev_private) { struct msm_drm_private *priv = wb_dev->drm_dev->dev_private; struct sde_kms *sde_kms = to_sde_kms(priv->kms); if (wb_dev->index < sde_kms->catalog->wb_count) { wb_dev->wb_idx = sde_kms->catalog->wb[wb_dev->index].id; wb_dev->wb_cfg = &sde_kms->catalog->wb[wb_dev->index]; } } wb_dev->drm_dev = encoder->dev; wb_dev->encoder = encoder; mutex_unlock(&wb_dev->wb_lock); return rc; } int sde_wb_drm_deinit(struct sde_wb_device *wb_dev) { int rc = 0; if (!wb_dev) { SDE_ERROR("invalid params\n"); return -EINVAL; } SDE_DEBUG("\n"); return rc; } /** * sde_wb_probe - load writeback module * @pdev: Pointer to platform device */ static int sde_wb_probe(struct platform_device *pdev) { struct sde_wb_device *wb_dev; int ret; wb_dev = devm_kzalloc(&pdev->dev, sizeof(*wb_dev), GFP_KERNEL); if (!wb_dev) return -ENOMEM; SDE_DEBUG("\n"); ret = of_property_read_u32(pdev->dev.of_node, "cell-index", &wb_dev->index); if (ret) { SDE_DEBUG("cell index not set, default to 0\n"); wb_dev->index = 0; } wb_dev->name = of_get_property(pdev->dev.of_node, "label", NULL); if (!wb_dev->name) { SDE_DEBUG("label not set, default to unknown\n"); wb_dev->name = "unknown"; } wb_dev->wb_idx = SDE_NONE; mutex_init(&wb_dev->wb_lock); platform_set_drvdata(pdev, wb_dev); mutex_lock(&sde_wb_list_lock); list_add(&wb_dev->wb_list, &sde_wb_list); mutex_unlock(&sde_wb_list_lock); if (!_sde_wb_dev_init(wb_dev)) { ret = component_add(&pdev->dev, &sde_wb_comp_ops); if (ret) pr_err("component add failed\n"); } return ret; } /** * sde_wb_remove - unload writeback module * @pdev: Pointer to platform device */ static int sde_wb_remove(struct platform_device *pdev) { struct sde_wb_device *wb_dev; struct sde_wb_device *curr, *next; wb_dev = platform_get_drvdata(pdev); if (!wb_dev) return 0; SDE_DEBUG("\n"); (void)_sde_wb_dev_deinit(wb_dev); mutex_lock(&sde_wb_list_lock); list_for_each_entry_safe(curr, next, &sde_wb_list, wb_list) { if (curr == wb_dev) { list_del(&wb_dev->wb_list); break; } } mutex_unlock(&sde_wb_list_lock); kfree(wb_dev->modes); mutex_destroy(&wb_dev->wb_lock); platform_set_drvdata(pdev, NULL); devm_kfree(&pdev->dev, wb_dev); return 0; } static const struct of_device_id dt_match[] = { { .compatible = "qcom,wb-display"}, {} }; static struct platform_driver sde_wb_driver = { .probe = sde_wb_probe, .remove = sde_wb_remove, .driver = { .name = "sde_wb", .of_match_table = dt_match, .suppress_bind_attrs = true, }, }; static int __init sde_wb_register(void) { return platform_driver_register(&sde_wb_driver); } static void __exit sde_wb_unregister(void) { platform_driver_unregister(&sde_wb_driver); } module_init(sde_wb_register); module_exit(sde_wb_unregister);