diff options
Diffstat (limited to 'drivers/gpu/drm/msm/dba_bridge.c')
| -rw-r--r-- | drivers/gpu/drm/msm/dba_bridge.c | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/drivers/gpu/drm/msm/dba_bridge.c b/drivers/gpu/drm/msm/dba_bridge.c new file mode 100644 index 000000000000..0a4bcee42106 --- /dev/null +++ b/drivers/gpu/drm/msm/dba_bridge.c @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2016-2017, 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. + * + */ + +#include <video/msm_dba.h> +#include "drm_edid.h" +#include "sde_kms.h" +#include "dba_bridge.h" + +#undef pr_fmt +#define pr_fmt(fmt) "dba_bridge:[%s] " fmt, __func__ + +/** + * struct dba_bridge - DBA bridge information + * @base: drm_bridge base + * @client_name: Client's name who calls the init + * @chip_name: Bridge chip name + * @name: Bridge chip name + * @id: Bridge driver index + * @display: Private display handle + * @list: Bridge chip driver list node + * @ops: DBA operation container + * @dba_ctx: DBA context + * @mode: DRM mode info + * @hdmi_mode: HDMI or DVI mode for the sink + * @num_of_input_lanes: Number of input lanes in case of DSI/LVDS + * @pluggable: If it's pluggable + */ +struct dba_bridge { + struct drm_bridge base; + char client_name[MSM_DBA_CLIENT_NAME_LEN]; + char chip_name[MSM_DBA_CHIP_NAME_MAX_LEN]; + u32 id; + void *display; + struct list_head list; + struct msm_dba_ops ops; + void *dba_ctx; + struct drm_display_mode mode; + bool hdmi_mode; + u32 num_of_input_lanes; + bool pluggable; +}; +#define to_dba_bridge(x) container_of((x), struct dba_bridge, base) + +static void _dba_bridge_cb(void *data, enum msm_dba_callback_event event) +{ + struct dba_bridge *d_bridge = data; + + if (!d_bridge) { + SDE_ERROR("Invalid data\n"); + return; + } + + DRM_DEBUG("event: %d\n", event); + + switch (event) { + case MSM_DBA_CB_HPD_CONNECT: + DRM_DEBUG("HPD CONNECT\n"); + break; + case MSM_DBA_CB_HPD_DISCONNECT: + DRM_DEBUG("HPD DISCONNECT\n"); + break; + default: + DRM_DEBUG("event:%d is not supported\n", event); + break; + } +} + +static int _dba_bridge_attach(struct drm_bridge *bridge) +{ + struct dba_bridge *d_bridge = to_dba_bridge(bridge); + struct msm_dba_reg_info info; + int ret = 0; + + if (!bridge) { + SDE_ERROR("Invalid params\n"); + return -EINVAL; + } + + memset(&info, 0, sizeof(info)); + /* initialize DBA registration data */ + strlcpy(info.client_name, d_bridge->client_name, + MSM_DBA_CLIENT_NAME_LEN); + strlcpy(info.chip_name, d_bridge->chip_name, + MSM_DBA_CHIP_NAME_MAX_LEN); + info.instance_id = d_bridge->id; + info.cb = _dba_bridge_cb; + info.cb_data = d_bridge; + + /* register client with DBA and get device's ops*/ + if (IS_ENABLED(CONFIG_MSM_DBA)) { + d_bridge->dba_ctx = msm_dba_register_client(&info, + &d_bridge->ops); + if (IS_ERR_OR_NULL(d_bridge->dba_ctx)) { + SDE_ERROR("dba register failed\n"); + ret = PTR_ERR(d_bridge->dba_ctx); + goto error; + } + } else { + SDE_ERROR("DBA not enabled\n"); + ret = -ENODEV; + goto error; + } + + DRM_INFO("client:%s bridge:[%s:%d] attached\n", + d_bridge->client_name, d_bridge->chip_name, d_bridge->id); + +error: + return ret; +} + +static void _dba_bridge_pre_enable(struct drm_bridge *bridge) +{ + if (!bridge) { + SDE_ERROR("Invalid params\n"); + return; + } +} + +static void _dba_bridge_enable(struct drm_bridge *bridge) +{ + int rc = 0; + struct dba_bridge *d_bridge = to_dba_bridge(bridge); + struct msm_dba_video_cfg video_cfg; + struct drm_display_mode *mode; + struct hdmi_avi_infoframe avi_frame; + + if (!bridge) { + SDE_ERROR("Invalid params\n"); + return; + } + + memset(&video_cfg, 0, sizeof(video_cfg)); + memset(&avi_frame, 0, sizeof(avi_frame)); + mode = &d_bridge->mode; + video_cfg.h_active = mode->hdisplay; + video_cfg.v_active = mode->vdisplay; + video_cfg.h_front_porch = mode->hsync_start - mode->hdisplay; + video_cfg.v_front_porch = mode->vsync_start - mode->vdisplay; + video_cfg.h_back_porch = mode->htotal - mode->hsync_end; + video_cfg.v_back_porch = mode->vtotal - mode->vsync_end; + video_cfg.h_pulse_width = mode->hsync_end - mode->hsync_start; + video_cfg.v_pulse_width = mode->vsync_end - mode->vsync_start; + video_cfg.pclk_khz = mode->clock; + video_cfg.hdmi_mode = d_bridge->hdmi_mode; + video_cfg.num_of_input_lanes = d_bridge->num_of_input_lanes; + + SDE_DEBUG( + "video=h[%d,%d,%d,%d] v[%d,%d,%d,%d] pclk=%d hdmi=%d lane=%d\n", + video_cfg.h_active, video_cfg.h_front_porch, + video_cfg.h_pulse_width, video_cfg.h_back_porch, + video_cfg.v_active, video_cfg.v_front_porch, + video_cfg.v_pulse_width, video_cfg.v_back_porch, + video_cfg.pclk_khz, video_cfg.hdmi_mode, + video_cfg.num_of_input_lanes); + + rc = drm_hdmi_avi_infoframe_from_display_mode(&avi_frame, mode); + if (rc) { + SDE_ERROR("get avi frame failed ret=%d\n", rc); + } else { + video_cfg.scaninfo = avi_frame.scan_mode; + switch (avi_frame.picture_aspect) { + case HDMI_PICTURE_ASPECT_4_3: + video_cfg.ar = MSM_DBA_AR_4_3; + break; + case HDMI_PICTURE_ASPECT_16_9: + video_cfg.ar = MSM_DBA_AR_16_9; + break; + default: + break; + } + video_cfg.vic = avi_frame.video_code; + DRM_INFO("scaninfo=%d ar=%d vic=%d\n", + video_cfg.scaninfo, video_cfg.ar, video_cfg.vic); + } + + if (d_bridge->ops.video_on) { + rc = d_bridge->ops.video_on(d_bridge->dba_ctx, true, + &video_cfg, 0); + if (rc) + SDE_ERROR("video on failed ret=%d\n", rc); + } +} + +static void _dba_bridge_disable(struct drm_bridge *bridge) +{ + int rc = 0; + struct dba_bridge *d_bridge = to_dba_bridge(bridge); + + if (!bridge) { + SDE_ERROR("Invalid params\n"); + return; + } + + if (d_bridge->ops.video_on) { + rc = d_bridge->ops.video_on(d_bridge->dba_ctx, false, NULL, 0); + if (rc) + SDE_ERROR("video off failed ret=%d\n", rc); + } +} + +static void _dba_bridge_post_disable(struct drm_bridge *bridge) +{ + if (!bridge) { + SDE_ERROR("Invalid params\n"); + return; + } +} + +static void _dba_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct dba_bridge *d_bridge = to_dba_bridge(bridge); + + if (!bridge || !mode || !adjusted_mode || !d_bridge) { + SDE_ERROR("Invalid params\n"); + return; + } + + d_bridge->mode = *adjusted_mode; +} + +static bool _dba_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + bool ret = true; + + if (!bridge || !mode || !adjusted_mode) { + SDE_ERROR("Invalid params\n"); + return false; + } + + return ret; +} + +static const struct drm_bridge_funcs _dba_bridge_ops = { + .attach = _dba_bridge_attach, + .mode_fixup = _dba_bridge_mode_fixup, + .pre_enable = _dba_bridge_pre_enable, + .enable = _dba_bridge_enable, + .disable = _dba_bridge_disable, + .post_disable = _dba_bridge_post_disable, + .mode_set = _dba_bridge_mode_set, +}; + +struct drm_bridge *dba_bridge_init(struct drm_device *dev, + struct drm_encoder *encoder, + struct dba_bridge_init *data) +{ + int rc = 0; + struct dba_bridge *bridge; + struct msm_drm_private *priv = NULL; + + if (!dev || !encoder || !data) { + SDE_ERROR("dev=%p or encoder=%p or data=%p is NULL\n", + dev, encoder, data); + rc = -EINVAL; + goto error; + } + + priv = dev->dev_private; + if (!priv) { + SDE_ERROR("Private data is not present\n"); + rc = -EINVAL; + goto error; + } + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + SDE_ERROR("out of memory\n"); + rc = -ENOMEM; + goto error; + } + + INIT_LIST_HEAD(&bridge->list); + strlcpy(bridge->client_name, data->client_name, + MSM_DBA_CLIENT_NAME_LEN); + strlcpy(bridge->chip_name, data->chip_name, + MSM_DBA_CHIP_NAME_MAX_LEN); + bridge->id = data->id; + bridge->display = data->display; + bridge->hdmi_mode = data->hdmi_mode; + bridge->num_of_input_lanes = data->num_of_input_lanes; + bridge->pluggable = data->pluggable; + bridge->base.funcs = &_dba_bridge_ops; + bridge->base.encoder = encoder; + + rc = drm_bridge_attach(dev, &bridge->base); + if (rc) { + SDE_ERROR("failed to attach bridge, rc=%d\n", rc); + goto error_free_bridge; + } + + if (data->precede_bridge) { + /* Insert current bridge */ + bridge->base.next = data->precede_bridge->next; + data->precede_bridge->next = &bridge->base; + } else { + encoder->bridge = &bridge->base; + } + + if (!bridge->pluggable) { + if (bridge->ops.power_on) + bridge->ops.power_on(bridge->dba_ctx, true, 0); + if (bridge->ops.check_hpd) + bridge->ops.check_hpd(bridge->dba_ctx, 0); + } + + return &bridge->base; + +error_free_bridge: + kfree(bridge); +error: + return ERR_PTR(rc); +} + +void dba_bridge_cleanup(struct drm_bridge *bridge) +{ + struct dba_bridge *d_bridge = to_dba_bridge(bridge); + + if (!bridge) + return; + + if (IS_ENABLED(CONFIG_MSM_DBA)) { + if (!IS_ERR_OR_NULL(d_bridge->dba_ctx)) + msm_dba_deregister_client(d_bridge->dba_ctx); + } + + if (d_bridge->base.encoder) + d_bridge->base.encoder->bridge = NULL; + + kfree(bridge); +} |
