drm/msm: Initial add DSI connector support
This change adds the DSI connector support in msm drm driver. v1: Initial change v2: - Address comments from Archit + minor clean-ups - Rebase to not depend on msm_drm_sub_dev change [Rob's comment] v3: Fix issues when initialization is failed Signed-off-by: Hai Li <hali@codeaurora.org> Signed-off-by: Rob Clark <robdclark@gmail.com>
This commit is contained in:
parent
7a6dc9550d
commit
a689554ba6
|
@ -35,3 +35,14 @@ config DRM_MSM_REGISTER_LOGGING
|
|||
Compile in support for logging register reads/writes in a format
|
||||
that can be parsed by envytools demsm tool. If enabled, register
|
||||
logging can be switched on via msm.reglog=y module param.
|
||||
|
||||
config DRM_MSM_DSI
|
||||
bool "Enable DSI support in MSM DRM driver"
|
||||
depends on DRM_MSM
|
||||
select DRM_PANEL
|
||||
select DRM_MIPI_DSI
|
||||
default y
|
||||
help
|
||||
Choose this option if you have a need for MIPI DSI connector
|
||||
support.
|
||||
|
||||
|
|
|
@ -50,5 +50,9 @@ msm-y := \
|
|||
|
||||
msm-$(CONFIG_DRM_MSM_FBDEV) += msm_fbdev.o
|
||||
msm-$(CONFIG_COMMON_CLK) += mdp/mdp4/mdp4_lvds_pll.o
|
||||
msm-$(CONFIG_DRM_MSM_DSI) += dsi/dsi.o \
|
||||
dsi/dsi_host.o \
|
||||
dsi/dsi_manager.o \
|
||||
dsi/dsi_phy.o
|
||||
|
||||
obj-$(CONFIG_DRM_MSM) += msm.o
|
||||
|
|
|
@ -0,0 +1,212 @@
|
|||
/*
|
||||
* Copyright (c) 2015, 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 "dsi.h"
|
||||
|
||||
struct drm_encoder *msm_dsi_get_encoder(struct msm_dsi *msm_dsi)
|
||||
{
|
||||
if (!msm_dsi || !msm_dsi->panel)
|
||||
return NULL;
|
||||
|
||||
return (msm_dsi->panel_flags & MIPI_DSI_MODE_VIDEO) ?
|
||||
msm_dsi->encoders[MSM_DSI_VIDEO_ENCODER_ID] :
|
||||
msm_dsi->encoders[MSM_DSI_CMD_ENCODER_ID];
|
||||
}
|
||||
|
||||
static void dsi_destroy(struct msm_dsi *msm_dsi)
|
||||
{
|
||||
if (!msm_dsi)
|
||||
return;
|
||||
|
||||
msm_dsi_manager_unregister(msm_dsi);
|
||||
if (msm_dsi->host) {
|
||||
msm_dsi_host_destroy(msm_dsi->host);
|
||||
msm_dsi->host = NULL;
|
||||
}
|
||||
|
||||
platform_set_drvdata(msm_dsi->pdev, NULL);
|
||||
}
|
||||
|
||||
static struct msm_dsi *dsi_init(struct platform_device *pdev)
|
||||
{
|
||||
struct msm_dsi *msm_dsi = NULL;
|
||||
int ret;
|
||||
|
||||
if (!pdev) {
|
||||
dev_err(&pdev->dev, "no dsi device\n");
|
||||
ret = -ENXIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
msm_dsi = devm_kzalloc(&pdev->dev, sizeof(*msm_dsi), GFP_KERNEL);
|
||||
if (!msm_dsi) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
DBG("dsi probed=%p", msm_dsi);
|
||||
|
||||
msm_dsi->pdev = pdev;
|
||||
platform_set_drvdata(pdev, msm_dsi);
|
||||
|
||||
/* Init dsi host */
|
||||
ret = msm_dsi_host_init(msm_dsi);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
/* Register to dsi manager */
|
||||
ret = msm_dsi_manager_register(msm_dsi);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
return msm_dsi;
|
||||
|
||||
fail:
|
||||
if (msm_dsi)
|
||||
dsi_destroy(msm_dsi);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static int dsi_bind(struct device *dev, struct device *master, void *data)
|
||||
{
|
||||
struct drm_device *drm = dev_get_drvdata(master);
|
||||
struct msm_drm_private *priv = drm->dev_private;
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct msm_dsi *msm_dsi;
|
||||
|
||||
DBG("");
|
||||
msm_dsi = dsi_init(pdev);
|
||||
if (IS_ERR(msm_dsi))
|
||||
return PTR_ERR(msm_dsi);
|
||||
|
||||
priv->dsi[msm_dsi->id] = msm_dsi;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_unbind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
struct drm_device *drm = dev_get_drvdata(master);
|
||||
struct msm_drm_private *priv = drm->dev_private;
|
||||
struct msm_dsi *msm_dsi = dev_get_drvdata(dev);
|
||||
int id = msm_dsi->id;
|
||||
|
||||
if (priv->dsi[id]) {
|
||||
dsi_destroy(msm_dsi);
|
||||
priv->dsi[id] = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct component_ops dsi_ops = {
|
||||
.bind = dsi_bind,
|
||||
.unbind = dsi_unbind,
|
||||
};
|
||||
|
||||
static int dsi_dev_probe(struct platform_device *pdev)
|
||||
{
|
||||
return component_add(&pdev->dev, &dsi_ops);
|
||||
}
|
||||
|
||||
static int dsi_dev_remove(struct platform_device *pdev)
|
||||
{
|
||||
DBG("");
|
||||
component_del(&pdev->dev, &dsi_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id dt_match[] = {
|
||||
{ .compatible = "qcom,mdss-dsi-ctrl" },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver dsi_driver = {
|
||||
.probe = dsi_dev_probe,
|
||||
.remove = dsi_dev_remove,
|
||||
.driver = {
|
||||
.name = "msm_dsi",
|
||||
.of_match_table = dt_match,
|
||||
},
|
||||
};
|
||||
|
||||
void __init msm_dsi_register(void)
|
||||
{
|
||||
DBG("");
|
||||
platform_driver_register(&dsi_driver);
|
||||
}
|
||||
|
||||
void __exit msm_dsi_unregister(void)
|
||||
{
|
||||
DBG("");
|
||||
platform_driver_unregister(&dsi_driver);
|
||||
}
|
||||
|
||||
int msm_dsi_modeset_init(struct msm_dsi *msm_dsi, struct drm_device *dev,
|
||||
struct drm_encoder *encoders[MSM_DSI_ENCODER_NUM])
|
||||
{
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
int ret, i;
|
||||
|
||||
if (WARN_ON(!encoders[MSM_DSI_VIDEO_ENCODER_ID] ||
|
||||
!encoders[MSM_DSI_CMD_ENCODER_ID]))
|
||||
return -EINVAL;
|
||||
|
||||
msm_dsi->dev = dev;
|
||||
|
||||
ret = msm_dsi_host_modeset_init(msm_dsi->host, dev);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "failed to modeset init host: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
msm_dsi->bridge = msm_dsi_manager_bridge_init(msm_dsi->id);
|
||||
if (IS_ERR(msm_dsi->bridge)) {
|
||||
ret = PTR_ERR(msm_dsi->bridge);
|
||||
dev_err(dev->dev, "failed to create dsi bridge: %d\n", ret);
|
||||
msm_dsi->bridge = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
msm_dsi->connector = msm_dsi_manager_connector_init(msm_dsi->id);
|
||||
if (IS_ERR(msm_dsi->connector)) {
|
||||
ret = PTR_ERR(msm_dsi->connector);
|
||||
dev_err(dev->dev, "failed to create dsi connector: %d\n", ret);
|
||||
msm_dsi->connector = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
for (i = 0; i < MSM_DSI_ENCODER_NUM; i++) {
|
||||
encoders[i]->bridge = msm_dsi->bridge;
|
||||
msm_dsi->encoders[i] = encoders[i];
|
||||
}
|
||||
|
||||
priv->bridges[priv->num_bridges++] = msm_dsi->bridge;
|
||||
priv->connectors[priv->num_connectors++] = msm_dsi->connector;
|
||||
|
||||
return 0;
|
||||
fail:
|
||||
if (msm_dsi) {
|
||||
/* bridge/connector are normally destroyed by drm: */
|
||||
if (msm_dsi->bridge) {
|
||||
msm_dsi_manager_bridge_destroy(msm_dsi->bridge);
|
||||
msm_dsi->bridge = NULL;
|
||||
}
|
||||
if (msm_dsi->connector) {
|
||||
msm_dsi->connector->funcs->destroy(msm_dsi->connector);
|
||||
msm_dsi->connector = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright (c) 2015, 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.
|
||||
*/
|
||||
|
||||
#ifndef __DSI_CONNECTOR_H__
|
||||
#define __DSI_CONNECTOR_H__
|
||||
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "drm_crtc.h"
|
||||
#include "drm_mipi_dsi.h"
|
||||
#include "drm_panel.h"
|
||||
|
||||
#include "msm_drv.h"
|
||||
|
||||
#define DSI_0 0
|
||||
#define DSI_1 1
|
||||
#define DSI_MAX 2
|
||||
|
||||
#define DSI_CLOCK_MASTER DSI_0
|
||||
#define DSI_CLOCK_SLAVE DSI_1
|
||||
|
||||
#define DSI_LEFT DSI_0
|
||||
#define DSI_RIGHT DSI_1
|
||||
|
||||
/* According to the current drm framework sequence, take the encoder of
|
||||
* DSI_1 as master encoder
|
||||
*/
|
||||
#define DSI_ENCODER_MASTER DSI_1
|
||||
#define DSI_ENCODER_SLAVE DSI_0
|
||||
|
||||
struct msm_dsi {
|
||||
struct drm_device *dev;
|
||||
struct platform_device *pdev;
|
||||
|
||||
struct drm_connector *connector;
|
||||
struct drm_bridge *bridge;
|
||||
|
||||
struct mipi_dsi_host *host;
|
||||
struct msm_dsi_phy *phy;
|
||||
struct drm_panel *panel;
|
||||
unsigned long panel_flags;
|
||||
bool phy_enabled;
|
||||
|
||||
/* the encoders we are hooked to (outside of dsi block) */
|
||||
struct drm_encoder *encoders[MSM_DSI_ENCODER_NUM];
|
||||
|
||||
int id;
|
||||
};
|
||||
|
||||
/* dsi manager */
|
||||
struct drm_bridge *msm_dsi_manager_bridge_init(u8 id);
|
||||
void msm_dsi_manager_bridge_destroy(struct drm_bridge *bridge);
|
||||
struct drm_connector *msm_dsi_manager_connector_init(u8 id);
|
||||
int msm_dsi_manager_phy_enable(int id,
|
||||
const unsigned long bit_rate, const unsigned long esc_rate,
|
||||
u32 *clk_pre, u32 *clk_post);
|
||||
void msm_dsi_manager_phy_disable(int id);
|
||||
int msm_dsi_manager_cmd_xfer(int id, const struct mipi_dsi_msg *msg);
|
||||
bool msm_dsi_manager_cmd_xfer_trigger(int id, u32 iova, u32 len);
|
||||
int msm_dsi_manager_register(struct msm_dsi *msm_dsi);
|
||||
void msm_dsi_manager_unregister(struct msm_dsi *msm_dsi);
|
||||
|
||||
/* msm dsi */
|
||||
struct drm_encoder *msm_dsi_get_encoder(struct msm_dsi *msm_dsi);
|
||||
|
||||
/* dsi host */
|
||||
int msm_dsi_host_xfer_prepare(struct mipi_dsi_host *host,
|
||||
const struct mipi_dsi_msg *msg);
|
||||
void msm_dsi_host_xfer_restore(struct mipi_dsi_host *host,
|
||||
const struct mipi_dsi_msg *msg);
|
||||
int msm_dsi_host_cmd_tx(struct mipi_dsi_host *host,
|
||||
const struct mipi_dsi_msg *msg);
|
||||
int msm_dsi_host_cmd_rx(struct mipi_dsi_host *host,
|
||||
const struct mipi_dsi_msg *msg);
|
||||
void msm_dsi_host_cmd_xfer_commit(struct mipi_dsi_host *host,
|
||||
u32 iova, u32 len);
|
||||
int msm_dsi_host_enable(struct mipi_dsi_host *host);
|
||||
int msm_dsi_host_disable(struct mipi_dsi_host *host);
|
||||
int msm_dsi_host_power_on(struct mipi_dsi_host *host);
|
||||
int msm_dsi_host_power_off(struct mipi_dsi_host *host);
|
||||
int msm_dsi_host_set_display_mode(struct mipi_dsi_host *host,
|
||||
struct drm_display_mode *mode);
|
||||
struct drm_panel *msm_dsi_host_get_panel(struct mipi_dsi_host *host,
|
||||
unsigned long *panel_flags);
|
||||
int msm_dsi_host_register(struct mipi_dsi_host *host, bool check_defer);
|
||||
void msm_dsi_host_unregister(struct mipi_dsi_host *host);
|
||||
void msm_dsi_host_destroy(struct mipi_dsi_host *host);
|
||||
int msm_dsi_host_modeset_init(struct mipi_dsi_host *host,
|
||||
struct drm_device *dev);
|
||||
int msm_dsi_host_init(struct msm_dsi *msm_dsi);
|
||||
|
||||
/* dsi phy */
|
||||
struct msm_dsi_phy;
|
||||
enum msm_dsi_phy_type {
|
||||
MSM_DSI_PHY_UNKNOWN,
|
||||
MSM_DSI_PHY_28NM,
|
||||
MSM_DSI_PHY_MAX
|
||||
};
|
||||
struct msm_dsi_phy *msm_dsi_phy_init(struct platform_device *pdev,
|
||||
enum msm_dsi_phy_type type, int id);
|
||||
int msm_dsi_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel,
|
||||
const unsigned long bit_rate, const unsigned long esc_rate);
|
||||
int msm_dsi_phy_disable(struct msm_dsi_phy *phy);
|
||||
void msm_dsi_phy_get_clk_pre_post(struct msm_dsi_phy *phy,
|
||||
u32 *clk_pre, u32 *clk_post);
|
||||
#endif /* __DSI_CONNECTOR_H__ */
|
||||
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,705 @@
|
|||
/*
|
||||
* Copyright (c) 2015, 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 "msm_kms.h"
|
||||
#include "dsi.h"
|
||||
|
||||
struct msm_dsi_manager {
|
||||
struct msm_dsi *dsi[DSI_MAX];
|
||||
|
||||
bool is_dual_panel;
|
||||
bool is_sync_needed;
|
||||
int master_panel_id;
|
||||
};
|
||||
|
||||
static struct msm_dsi_manager msm_dsim_glb;
|
||||
|
||||
#define IS_DUAL_PANEL() (msm_dsim_glb.is_dual_panel)
|
||||
#define IS_SYNC_NEEDED() (msm_dsim_glb.is_sync_needed)
|
||||
#define IS_MASTER_PANEL(id) (msm_dsim_glb.master_panel_id == id)
|
||||
|
||||
static inline struct msm_dsi *dsi_mgr_get_dsi(int id)
|
||||
{
|
||||
return msm_dsim_glb.dsi[id];
|
||||
}
|
||||
|
||||
static inline struct msm_dsi *dsi_mgr_get_other_dsi(int id)
|
||||
{
|
||||
return msm_dsim_glb.dsi[(id + 1) % DSI_MAX];
|
||||
}
|
||||
|
||||
static int dsi_mgr_parse_dual_panel(struct device_node *np, int id)
|
||||
{
|
||||
struct msm_dsi_manager *msm_dsim = &msm_dsim_glb;
|
||||
|
||||
/* We assume 2 dsi nodes have the same information of dual-panel and
|
||||
* sync-mode, and only one node specifies master in case of dual mode.
|
||||
*/
|
||||
if (!msm_dsim->is_dual_panel)
|
||||
msm_dsim->is_dual_panel = of_property_read_bool(
|
||||
np, "qcom,dual-panel-mode");
|
||||
|
||||
if (msm_dsim->is_dual_panel) {
|
||||
if (of_property_read_bool(np, "qcom,master-panel"))
|
||||
msm_dsim->master_panel_id = id;
|
||||
if (!msm_dsim->is_sync_needed)
|
||||
msm_dsim->is_sync_needed = of_property_read_bool(
|
||||
np, "qcom,sync-dual-panel");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct dsi_connector {
|
||||
struct drm_connector base;
|
||||
int id;
|
||||
};
|
||||
|
||||
struct dsi_bridge {
|
||||
struct drm_bridge base;
|
||||
int id;
|
||||
};
|
||||
|
||||
#define to_dsi_connector(x) container_of(x, struct dsi_connector, base)
|
||||
#define to_dsi_bridge(x) container_of(x, struct dsi_bridge, base)
|
||||
|
||||
static inline int dsi_mgr_connector_get_id(struct drm_connector *connector)
|
||||
{
|
||||
struct dsi_connector *dsi_connector = to_dsi_connector(connector);
|
||||
return dsi_connector->id;
|
||||
}
|
||||
|
||||
static int dsi_mgr_bridge_get_id(struct drm_bridge *bridge)
|
||||
{
|
||||
struct dsi_bridge *dsi_bridge = to_dsi_bridge(bridge);
|
||||
return dsi_bridge->id;
|
||||
}
|
||||
|
||||
static enum drm_connector_status dsi_mgr_connector_detect(
|
||||
struct drm_connector *connector, bool force)
|
||||
{
|
||||
int id = dsi_mgr_connector_get_id(connector);
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct msm_dsi *other_dsi = dsi_mgr_get_other_dsi(id);
|
||||
struct msm_drm_private *priv = connector->dev->dev_private;
|
||||
struct msm_kms *kms = priv->kms;
|
||||
|
||||
DBG("id=%d", id);
|
||||
if (!msm_dsi->panel) {
|
||||
msm_dsi->panel = msm_dsi_host_get_panel(msm_dsi->host,
|
||||
&msm_dsi->panel_flags);
|
||||
|
||||
/* There is only 1 panel in the global panel list
|
||||
* for dual panel mode. Therefore slave dsi should get
|
||||
* the drm_panel instance from master dsi, and
|
||||
* keep using the panel flags got from the current DSI link.
|
||||
*/
|
||||
if (!msm_dsi->panel && IS_DUAL_PANEL() &&
|
||||
!IS_MASTER_PANEL(id) && other_dsi)
|
||||
msm_dsi->panel = msm_dsi_host_get_panel(
|
||||
other_dsi->host, NULL);
|
||||
|
||||
if (msm_dsi->panel && IS_DUAL_PANEL())
|
||||
drm_object_attach_property(&connector->base,
|
||||
connector->dev->mode_config.tile_property, 0);
|
||||
|
||||
/* Set split display info to kms once dual panel is connected
|
||||
* to both hosts
|
||||
*/
|
||||
if (msm_dsi->panel && IS_DUAL_PANEL() &&
|
||||
other_dsi && other_dsi->panel) {
|
||||
bool cmd_mode = !(msm_dsi->panel_flags &
|
||||
MIPI_DSI_MODE_VIDEO);
|
||||
struct drm_encoder *encoder = msm_dsi_get_encoder(
|
||||
dsi_mgr_get_dsi(DSI_ENCODER_MASTER));
|
||||
struct drm_encoder *slave_enc = msm_dsi_get_encoder(
|
||||
dsi_mgr_get_dsi(DSI_ENCODER_SLAVE));
|
||||
|
||||
if (kms->funcs->set_split_display)
|
||||
kms->funcs->set_split_display(kms, encoder,
|
||||
slave_enc, cmd_mode);
|
||||
else
|
||||
pr_err("mdp does not support dual panel\n");
|
||||
}
|
||||
}
|
||||
|
||||
return msm_dsi->panel ? connector_status_connected :
|
||||
connector_status_disconnected;
|
||||
}
|
||||
|
||||
static void dsi_mgr_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
DBG("");
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
}
|
||||
|
||||
static void dsi_dual_connector_fix_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct drm_display_mode *mode, *m;
|
||||
|
||||
/* Only support left-right mode */
|
||||
list_for_each_entry_safe(mode, m, &connector->probed_modes, head) {
|
||||
mode->clock >>= 1;
|
||||
mode->hdisplay >>= 1;
|
||||
mode->hsync_start >>= 1;
|
||||
mode->hsync_end >>= 1;
|
||||
mode->htotal >>= 1;
|
||||
drm_mode_set_name(mode);
|
||||
}
|
||||
}
|
||||
|
||||
static int dsi_dual_connector_tile_init(
|
||||
struct drm_connector *connector, int id)
|
||||
{
|
||||
struct drm_display_mode *mode;
|
||||
/* Fake topology id */
|
||||
char topo_id[8] = {'M', 'S', 'M', 'D', 'U', 'D', 'S', 'I'};
|
||||
|
||||
if (connector->tile_group) {
|
||||
DBG("Tile property has been initialized");
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Use the first mode only for now */
|
||||
mode = list_first_entry(&connector->probed_modes,
|
||||
struct drm_display_mode,
|
||||
head);
|
||||
if (!mode)
|
||||
return -EINVAL;
|
||||
|
||||
connector->tile_group = drm_mode_get_tile_group(
|
||||
connector->dev, topo_id);
|
||||
if (!connector->tile_group)
|
||||
connector->tile_group = drm_mode_create_tile_group(
|
||||
connector->dev, topo_id);
|
||||
if (!connector->tile_group) {
|
||||
pr_err("%s: failed to create tile group\n", __func__);
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
connector->has_tile = true;
|
||||
connector->tile_is_single_monitor = true;
|
||||
|
||||
/* mode has been fixed */
|
||||
connector->tile_h_size = mode->hdisplay;
|
||||
connector->tile_v_size = mode->vdisplay;
|
||||
|
||||
/* Only support left-right mode */
|
||||
connector->num_h_tile = 2;
|
||||
connector->num_v_tile = 1;
|
||||
|
||||
connector->tile_v_loc = 0;
|
||||
connector->tile_h_loc = (id == DSI_RIGHT) ? 1 : 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_mgr_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
int id = dsi_mgr_connector_get_id(connector);
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct drm_panel *panel = msm_dsi->panel;
|
||||
int ret, num;
|
||||
|
||||
if (!panel)
|
||||
return 0;
|
||||
|
||||
/* Since we have 2 connectors, but only 1 drm_panel in dual DSI mode,
|
||||
* panel should not attach to any connector.
|
||||
* Only temporarily attach panel to the current connector here,
|
||||
* to let panel set mode to this connector.
|
||||
*/
|
||||
drm_panel_attach(panel, connector);
|
||||
num = drm_panel_get_modes(panel);
|
||||
drm_panel_detach(panel);
|
||||
if (!num)
|
||||
return 0;
|
||||
|
||||
if (IS_DUAL_PANEL()) {
|
||||
/* report half resolution to user */
|
||||
dsi_dual_connector_fix_modes(connector);
|
||||
ret = dsi_dual_connector_tile_init(connector, id);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = drm_mode_connector_set_tile_property(connector);
|
||||
if (ret) {
|
||||
pr_err("%s: set tile property failed, %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
static int dsi_mgr_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
int id = dsi_mgr_connector_get_id(connector);
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct drm_encoder *encoder = msm_dsi_get_encoder(msm_dsi);
|
||||
struct msm_drm_private *priv = connector->dev->dev_private;
|
||||
struct msm_kms *kms = priv->kms;
|
||||
long actual, requested;
|
||||
|
||||
DBG("");
|
||||
requested = 1000 * mode->clock;
|
||||
actual = kms->funcs->round_pixclk(kms, requested, encoder);
|
||||
|
||||
DBG("requested=%ld, actual=%ld", requested, actual);
|
||||
if (actual != requested)
|
||||
return MODE_CLOCK_RANGE;
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static struct drm_encoder *
|
||||
dsi_mgr_connector_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
int id = dsi_mgr_connector_get_id(connector);
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
|
||||
DBG("");
|
||||
return msm_dsi_get_encoder(msm_dsi);
|
||||
}
|
||||
|
||||
static void dsi_mgr_bridge_pre_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
int id = dsi_mgr_bridge_get_id(bridge);
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct msm_dsi *msm_dsi1 = dsi_mgr_get_dsi(DSI_1);
|
||||
struct mipi_dsi_host *host = msm_dsi->host;
|
||||
struct drm_panel *panel = msm_dsi->panel;
|
||||
bool is_dual_panel = IS_DUAL_PANEL();
|
||||
int ret;
|
||||
|
||||
DBG("id=%d", id);
|
||||
if (!panel || (is_dual_panel && (DSI_1 == id)))
|
||||
return;
|
||||
|
||||
ret = msm_dsi_host_power_on(host);
|
||||
if (ret) {
|
||||
pr_err("%s: power on host %d failed, %d\n", __func__, id, ret);
|
||||
goto host_on_fail;
|
||||
}
|
||||
|
||||
if (is_dual_panel && msm_dsi1) {
|
||||
ret = msm_dsi_host_power_on(msm_dsi1->host);
|
||||
if (ret) {
|
||||
pr_err("%s: power on host1 failed, %d\n",
|
||||
__func__, ret);
|
||||
goto host1_on_fail;
|
||||
}
|
||||
}
|
||||
|
||||
/* Always call panel functions once, because even for dual panels,
|
||||
* there is only one drm_panel instance.
|
||||
*/
|
||||
ret = drm_panel_prepare(panel);
|
||||
if (ret) {
|
||||
pr_err("%s: prepare panel %d failed, %d\n", __func__, id, ret);
|
||||
goto panel_prep_fail;
|
||||
}
|
||||
|
||||
ret = msm_dsi_host_enable(host);
|
||||
if (ret) {
|
||||
pr_err("%s: enable host %d failed, %d\n", __func__, id, ret);
|
||||
goto host_en_fail;
|
||||
}
|
||||
|
||||
if (is_dual_panel && msm_dsi1) {
|
||||
ret = msm_dsi_host_enable(msm_dsi1->host);
|
||||
if (ret) {
|
||||
pr_err("%s: enable host1 failed, %d\n", __func__, ret);
|
||||
goto host1_en_fail;
|
||||
}
|
||||
}
|
||||
|
||||
ret = drm_panel_enable(panel);
|
||||
if (ret) {
|
||||
pr_err("%s: enable panel %d failed, %d\n", __func__, id, ret);
|
||||
goto panel_en_fail;
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
panel_en_fail:
|
||||
if (is_dual_panel && msm_dsi1)
|
||||
msm_dsi_host_disable(msm_dsi1->host);
|
||||
host1_en_fail:
|
||||
msm_dsi_host_disable(host);
|
||||
host_en_fail:
|
||||
drm_panel_unprepare(panel);
|
||||
panel_prep_fail:
|
||||
if (is_dual_panel && msm_dsi1)
|
||||
msm_dsi_host_power_off(msm_dsi1->host);
|
||||
host1_on_fail:
|
||||
msm_dsi_host_power_off(host);
|
||||
host_on_fail:
|
||||
return;
|
||||
}
|
||||
|
||||
static void dsi_mgr_bridge_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
DBG("");
|
||||
}
|
||||
|
||||
static void dsi_mgr_bridge_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
DBG("");
|
||||
}
|
||||
|
||||
static void dsi_mgr_bridge_post_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
int id = dsi_mgr_bridge_get_id(bridge);
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct msm_dsi *msm_dsi1 = dsi_mgr_get_dsi(DSI_1);
|
||||
struct mipi_dsi_host *host = msm_dsi->host;
|
||||
struct drm_panel *panel = msm_dsi->panel;
|
||||
bool is_dual_panel = IS_DUAL_PANEL();
|
||||
int ret;
|
||||
|
||||
DBG("id=%d", id);
|
||||
|
||||
if (!panel || (is_dual_panel && (DSI_1 == id)))
|
||||
return;
|
||||
|
||||
ret = drm_panel_disable(panel);
|
||||
if (ret)
|
||||
pr_err("%s: Panel %d OFF failed, %d\n", __func__, id, ret);
|
||||
|
||||
ret = msm_dsi_host_disable(host);
|
||||
if (ret)
|
||||
pr_err("%s: host %d disable failed, %d\n", __func__, id, ret);
|
||||
|
||||
if (is_dual_panel && msm_dsi1) {
|
||||
ret = msm_dsi_host_disable(msm_dsi1->host);
|
||||
if (ret)
|
||||
pr_err("%s: host1 disable failed, %d\n", __func__, ret);
|
||||
}
|
||||
|
||||
ret = drm_panel_unprepare(panel);
|
||||
if (ret)
|
||||
pr_err("%s: Panel %d unprepare failed,%d\n", __func__, id, ret);
|
||||
|
||||
ret = msm_dsi_host_power_off(host);
|
||||
if (ret)
|
||||
pr_err("%s: host %d power off failed,%d\n", __func__, id, ret);
|
||||
|
||||
if (is_dual_panel && msm_dsi1) {
|
||||
ret = msm_dsi_host_power_off(msm_dsi1->host);
|
||||
if (ret)
|
||||
pr_err("%s: host1 power off failed, %d\n",
|
||||
__func__, ret);
|
||||
}
|
||||
}
|
||||
|
||||
static void dsi_mgr_bridge_mode_set(struct drm_bridge *bridge,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
int id = dsi_mgr_bridge_get_id(bridge);
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct msm_dsi *other_dsi = dsi_mgr_get_other_dsi(id);
|
||||
struct mipi_dsi_host *host = msm_dsi->host;
|
||||
bool is_dual_panel = IS_DUAL_PANEL();
|
||||
|
||||
DBG("set mode: %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x",
|
||||
mode->base.id, mode->name,
|
||||
mode->vrefresh, mode->clock,
|
||||
mode->hdisplay, mode->hsync_start,
|
||||
mode->hsync_end, mode->htotal,
|
||||
mode->vdisplay, mode->vsync_start,
|
||||
mode->vsync_end, mode->vtotal,
|
||||
mode->type, mode->flags);
|
||||
|
||||
if (is_dual_panel && (DSI_1 == id))
|
||||
return;
|
||||
|
||||
msm_dsi_host_set_display_mode(host, adjusted_mode);
|
||||
if (is_dual_panel && other_dsi)
|
||||
msm_dsi_host_set_display_mode(other_dsi->host, adjusted_mode);
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs dsi_mgr_connector_funcs = {
|
||||
.dpms = drm_atomic_helper_connector_dpms,
|
||||
.detect = dsi_mgr_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = dsi_mgr_connector_destroy,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static const struct drm_connector_helper_funcs dsi_mgr_conn_helper_funcs = {
|
||||
.get_modes = dsi_mgr_connector_get_modes,
|
||||
.mode_valid = dsi_mgr_connector_mode_valid,
|
||||
.best_encoder = dsi_mgr_connector_best_encoder,
|
||||
};
|
||||
|
||||
static const struct drm_bridge_funcs dsi_mgr_bridge_funcs = {
|
||||
.pre_enable = dsi_mgr_bridge_pre_enable,
|
||||
.enable = dsi_mgr_bridge_enable,
|
||||
.disable = dsi_mgr_bridge_disable,
|
||||
.post_disable = dsi_mgr_bridge_post_disable,
|
||||
.mode_set = dsi_mgr_bridge_mode_set,
|
||||
};
|
||||
|
||||
/* initialize connector */
|
||||
struct drm_connector *msm_dsi_manager_connector_init(u8 id)
|
||||
{
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct drm_connector *connector = NULL;
|
||||
struct dsi_connector *dsi_connector;
|
||||
int ret;
|
||||
|
||||
dsi_connector = devm_kzalloc(msm_dsi->dev->dev,
|
||||
sizeof(*dsi_connector), GFP_KERNEL);
|
||||
if (!dsi_connector) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dsi_connector->id = id;
|
||||
|
||||
connector = &dsi_connector->base;
|
||||
|
||||
ret = drm_connector_init(msm_dsi->dev, connector,
|
||||
&dsi_mgr_connector_funcs, DRM_MODE_CONNECTOR_DSI);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
drm_connector_helper_add(connector, &dsi_mgr_conn_helper_funcs);
|
||||
|
||||
/* Enable HPD to let hpd event is handled
|
||||
* when panel is attached to the host.
|
||||
*/
|
||||
connector->polled = DRM_CONNECTOR_POLL_HPD;
|
||||
|
||||
/* Display driver doesn't support interlace now. */
|
||||
connector->interlace_allowed = 0;
|
||||
connector->doublescan_allowed = 0;
|
||||
|
||||
ret = drm_connector_register(connector);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
return connector;
|
||||
|
||||
fail:
|
||||
if (connector)
|
||||
dsi_mgr_connector_destroy(connector);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/* initialize bridge */
|
||||
struct drm_bridge *msm_dsi_manager_bridge_init(u8 id)
|
||||
{
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct drm_bridge *bridge = NULL;
|
||||
struct dsi_bridge *dsi_bridge;
|
||||
int ret;
|
||||
|
||||
dsi_bridge = devm_kzalloc(msm_dsi->dev->dev,
|
||||
sizeof(*dsi_bridge), GFP_KERNEL);
|
||||
if (!dsi_bridge) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
dsi_bridge->id = id;
|
||||
|
||||
bridge = &dsi_bridge->base;
|
||||
bridge->funcs = &dsi_mgr_bridge_funcs;
|
||||
|
||||
ret = drm_bridge_attach(msm_dsi->dev, bridge);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
return bridge;
|
||||
|
||||
fail:
|
||||
if (bridge)
|
||||
msm_dsi_manager_bridge_destroy(bridge);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
void msm_dsi_manager_bridge_destroy(struct drm_bridge *bridge)
|
||||
{
|
||||
}
|
||||
|
||||
int msm_dsi_manager_phy_enable(int id,
|
||||
const unsigned long bit_rate, const unsigned long esc_rate,
|
||||
u32 *clk_pre, u32 *clk_post)
|
||||
{
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct msm_dsi_phy *phy = msm_dsi->phy;
|
||||
int ret;
|
||||
|
||||
ret = msm_dsi_phy_enable(phy, IS_DUAL_PANEL(), bit_rate, esc_rate);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
msm_dsi->phy_enabled = true;
|
||||
msm_dsi_phy_get_clk_pre_post(phy, clk_pre, clk_post);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void msm_dsi_manager_phy_disable(int id)
|
||||
{
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct msm_dsi *mdsi = dsi_mgr_get_dsi(DSI_CLOCK_MASTER);
|
||||
struct msm_dsi *sdsi = dsi_mgr_get_dsi(DSI_CLOCK_SLAVE);
|
||||
struct msm_dsi_phy *phy = msm_dsi->phy;
|
||||
|
||||
/* disable DSI phy
|
||||
* In dual-dsi configuration, the phy should be disabled for the
|
||||
* first controller only when the second controller is disabled.
|
||||
*/
|
||||
msm_dsi->phy_enabled = false;
|
||||
if (IS_DUAL_PANEL() && mdsi && sdsi) {
|
||||
if (!mdsi->phy_enabled && !sdsi->phy_enabled) {
|
||||
msm_dsi_phy_disable(sdsi->phy);
|
||||
msm_dsi_phy_disable(mdsi->phy);
|
||||
}
|
||||
} else {
|
||||
msm_dsi_phy_disable(phy);
|
||||
}
|
||||
}
|
||||
|
||||
int msm_dsi_manager_cmd_xfer(int id, const struct mipi_dsi_msg *msg)
|
||||
{
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct msm_dsi *msm_dsi0 = dsi_mgr_get_dsi(DSI_0);
|
||||
struct mipi_dsi_host *host = msm_dsi->host;
|
||||
bool is_read = (msg->rx_buf && msg->rx_len);
|
||||
bool need_sync = (IS_SYNC_NEEDED() && !is_read);
|
||||
int ret;
|
||||
|
||||
if (!msg->tx_buf || !msg->tx_len)
|
||||
return 0;
|
||||
|
||||
/* In dual master case, panel requires the same commands sent to
|
||||
* both DSI links. Host issues the command trigger to both links
|
||||
* when DSI_1 calls the cmd transfer function, no matter it happens
|
||||
* before or after DSI_0 cmd transfer.
|
||||
*/
|
||||
if (need_sync && (id == DSI_0))
|
||||
return is_read ? msg->rx_len : msg->tx_len;
|
||||
|
||||
if (need_sync && msm_dsi0) {
|
||||
ret = msm_dsi_host_xfer_prepare(msm_dsi0->host, msg);
|
||||
if (ret) {
|
||||
pr_err("%s: failed to prepare non-trigger host, %d\n",
|
||||
__func__, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
ret = msm_dsi_host_xfer_prepare(host, msg);
|
||||
if (ret) {
|
||||
pr_err("%s: failed to prepare host, %d\n", __func__, ret);
|
||||
goto restore_host0;
|
||||
}
|
||||
|
||||
ret = is_read ? msm_dsi_host_cmd_rx(host, msg) :
|
||||
msm_dsi_host_cmd_tx(host, msg);
|
||||
|
||||
msm_dsi_host_xfer_restore(host, msg);
|
||||
|
||||
restore_host0:
|
||||
if (need_sync && msm_dsi0)
|
||||
msm_dsi_host_xfer_restore(msm_dsi0->host, msg);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool msm_dsi_manager_cmd_xfer_trigger(int id, u32 iova, u32 len)
|
||||
{
|
||||
struct msm_dsi *msm_dsi = dsi_mgr_get_dsi(id);
|
||||
struct msm_dsi *msm_dsi0 = dsi_mgr_get_dsi(DSI_0);
|
||||
struct mipi_dsi_host *host = msm_dsi->host;
|
||||
|
||||
if (IS_SYNC_NEEDED() && (id == DSI_0))
|
||||
return false;
|
||||
|
||||
if (IS_SYNC_NEEDED() && msm_dsi0)
|
||||
msm_dsi_host_cmd_xfer_commit(msm_dsi0->host, iova, len);
|
||||
|
||||
msm_dsi_host_cmd_xfer_commit(host, iova, len);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int msm_dsi_manager_register(struct msm_dsi *msm_dsi)
|
||||
{
|
||||
struct msm_dsi_manager *msm_dsim = &msm_dsim_glb;
|
||||
int id = msm_dsi->id;
|
||||
struct msm_dsi *other_dsi = dsi_mgr_get_other_dsi(id);
|
||||
int ret;
|
||||
|
||||
if (id > DSI_MAX) {
|
||||
pr_err("%s: invalid id %d\n", __func__, id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (msm_dsim->dsi[id]) {
|
||||
pr_err("%s: dsi%d already registered\n", __func__, id);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
msm_dsim->dsi[id] = msm_dsi;
|
||||
|
||||
ret = dsi_mgr_parse_dual_panel(msm_dsi->pdev->dev.of_node, id);
|
||||
if (ret) {
|
||||
pr_err("%s: failed to parse dual panel info\n", __func__);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!IS_DUAL_PANEL()) {
|
||||
ret = msm_dsi_host_register(msm_dsi->host, true);
|
||||
} else if (!other_dsi) {
|
||||
return 0;
|
||||
} else {
|
||||
struct msm_dsi *mdsi = IS_MASTER_PANEL(id) ?
|
||||
msm_dsi : other_dsi;
|
||||
struct msm_dsi *sdsi = IS_MASTER_PANEL(id) ?
|
||||
other_dsi : msm_dsi;
|
||||
/* Register slave host first, so that slave DSI device
|
||||
* has a chance to probe, and do not block the master
|
||||
* DSI device's probe.
|
||||
* Also, do not check defer for the slave host,
|
||||
* because only master DSI device adds the panel to global
|
||||
* panel list. The panel's device is the master DSI device.
|
||||
*/
|
||||
ret = msm_dsi_host_register(sdsi->host, false);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = msm_dsi_host_register(mdsi->host, true);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void msm_dsi_manager_unregister(struct msm_dsi *msm_dsi)
|
||||
{
|
||||
struct msm_dsi_manager *msm_dsim = &msm_dsim_glb;
|
||||
|
||||
if (msm_dsi->host)
|
||||
msm_dsi_host_unregister(msm_dsi->host);
|
||||
msm_dsim->dsi[msm_dsi->id] = NULL;
|
||||
}
|
||||
|
|
@ -0,0 +1,352 @@
|
|||
/*
|
||||
* Copyright (c) 2015, 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 "dsi.h"
|
||||
#include "dsi.xml.h"
|
||||
|
||||
#define dsi_phy_read(offset) msm_readl((offset))
|
||||
#define dsi_phy_write(offset, data) msm_writel((data), (offset))
|
||||
|
||||
struct dsi_dphy_timing {
|
||||
u32 clk_pre;
|
||||
u32 clk_post;
|
||||
u32 clk_zero;
|
||||
u32 clk_trail;
|
||||
u32 clk_prepare;
|
||||
u32 hs_exit;
|
||||
u32 hs_zero;
|
||||
u32 hs_prepare;
|
||||
u32 hs_trail;
|
||||
u32 hs_rqst;
|
||||
u32 ta_go;
|
||||
u32 ta_sure;
|
||||
u32 ta_get;
|
||||
};
|
||||
|
||||
struct msm_dsi_phy {
|
||||
void __iomem *base;
|
||||
void __iomem *reg_base;
|
||||
int id;
|
||||
struct dsi_dphy_timing timing;
|
||||
int (*enable)(struct msm_dsi_phy *phy, bool is_dual_panel,
|
||||
const unsigned long bit_rate, const unsigned long esc_rate);
|
||||
int (*disable)(struct msm_dsi_phy *phy);
|
||||
};
|
||||
|
||||
#define S_DIV_ROUND_UP(n, d) \
|
||||
(((n) >= 0) ? (((n) + (d) - 1) / (d)) : (((n) - (d) + 1) / (d)))
|
||||
|
||||
static inline s32 linear_inter(s32 tmax, s32 tmin, s32 percent,
|
||||
s32 min_result, bool even)
|
||||
{
|
||||
s32 v;
|
||||
v = (tmax - tmin) * percent;
|
||||
v = S_DIV_ROUND_UP(v, 100) + tmin;
|
||||
if (even && (v & 0x1))
|
||||
return max_t(s32, min_result, v - 1);
|
||||
else
|
||||
return max_t(s32, min_result, v);
|
||||
}
|
||||
|
||||
static void dsi_dphy_timing_calc_clk_zero(struct dsi_dphy_timing *timing,
|
||||
s32 ui, s32 coeff, s32 pcnt)
|
||||
{
|
||||
s32 tmax, tmin, clk_z;
|
||||
s32 temp;
|
||||
|
||||
/* reset */
|
||||
temp = 300 * coeff - ((timing->clk_prepare >> 1) + 1) * 2 * ui;
|
||||
tmin = S_DIV_ROUND_UP(temp, ui) - 2;
|
||||
if (tmin > 255) {
|
||||
tmax = 511;
|
||||
clk_z = linear_inter(2 * tmin, tmin, pcnt, 0, true);
|
||||
} else {
|
||||
tmax = 255;
|
||||
clk_z = linear_inter(tmax, tmin, pcnt, 0, true);
|
||||
}
|
||||
|
||||
/* adjust */
|
||||
temp = (timing->hs_rqst + timing->clk_prepare + clk_z) & 0x7;
|
||||
timing->clk_zero = clk_z + 8 - temp;
|
||||
}
|
||||
|
||||
static int dsi_dphy_timing_calc(struct dsi_dphy_timing *timing,
|
||||
const unsigned long bit_rate, const unsigned long esc_rate)
|
||||
{
|
||||
s32 ui, lpx;
|
||||
s32 tmax, tmin;
|
||||
s32 pcnt0 = 10;
|
||||
s32 pcnt1 = (bit_rate > 1200000000) ? 15 : 10;
|
||||
s32 pcnt2 = 10;
|
||||
s32 pcnt3 = (bit_rate > 180000000) ? 10 : 40;
|
||||
s32 coeff = 1000; /* Precision, should avoid overflow */
|
||||
s32 temp;
|
||||
|
||||
if (!bit_rate || !esc_rate)
|
||||
return -EINVAL;
|
||||
|
||||
ui = mult_frac(NSEC_PER_MSEC, coeff, bit_rate / 1000);
|
||||
lpx = mult_frac(NSEC_PER_MSEC, coeff, esc_rate / 1000);
|
||||
|
||||
tmax = S_DIV_ROUND_UP(95 * coeff, ui) - 2;
|
||||
tmin = S_DIV_ROUND_UP(38 * coeff, ui) - 2;
|
||||
timing->clk_prepare = linear_inter(tmax, tmin, pcnt0, 0, true);
|
||||
|
||||
temp = lpx / ui;
|
||||
if (temp & 0x1)
|
||||
timing->hs_rqst = temp;
|
||||
else
|
||||
timing->hs_rqst = max_t(s32, 0, temp - 2);
|
||||
|
||||
/* Calculate clk_zero after clk_prepare and hs_rqst */
|
||||
dsi_dphy_timing_calc_clk_zero(timing, ui, coeff, pcnt2);
|
||||
|
||||
temp = 105 * coeff + 12 * ui - 20 * coeff;
|
||||
tmax = S_DIV_ROUND_UP(temp, ui) - 2;
|
||||
tmin = S_DIV_ROUND_UP(60 * coeff, ui) - 2;
|
||||
timing->clk_trail = linear_inter(tmax, tmin, pcnt3, 0, true);
|
||||
|
||||
temp = 85 * coeff + 6 * ui;
|
||||
tmax = S_DIV_ROUND_UP(temp, ui) - 2;
|
||||
temp = 40 * coeff + 4 * ui;
|
||||
tmin = S_DIV_ROUND_UP(temp, ui) - 2;
|
||||
timing->hs_prepare = linear_inter(tmax, tmin, pcnt1, 0, true);
|
||||
|
||||
tmax = 255;
|
||||
temp = ((timing->hs_prepare >> 1) + 1) * 2 * ui + 2 * ui;
|
||||
temp = 145 * coeff + 10 * ui - temp;
|
||||
tmin = S_DIV_ROUND_UP(temp, ui) - 2;
|
||||
timing->hs_zero = linear_inter(tmax, tmin, pcnt2, 24, true);
|
||||
|
||||
temp = 105 * coeff + 12 * ui - 20 * coeff;
|
||||
tmax = S_DIV_ROUND_UP(temp, ui) - 2;
|
||||
temp = 60 * coeff + 4 * ui;
|
||||
tmin = DIV_ROUND_UP(temp, ui) - 2;
|
||||
timing->hs_trail = linear_inter(tmax, tmin, pcnt3, 0, true);
|
||||
|
||||
tmax = 255;
|
||||
tmin = S_DIV_ROUND_UP(100 * coeff, ui) - 2;
|
||||
timing->hs_exit = linear_inter(tmax, tmin, pcnt2, 0, true);
|
||||
|
||||
tmax = 63;
|
||||
temp = ((timing->hs_exit >> 1) + 1) * 2 * ui;
|
||||
temp = 60 * coeff + 52 * ui - 24 * ui - temp;
|
||||
tmin = S_DIV_ROUND_UP(temp, 8 * ui) - 1;
|
||||
timing->clk_post = linear_inter(tmax, tmin, pcnt2, 0, false);
|
||||
|
||||
tmax = 63;
|
||||
temp = ((timing->clk_prepare >> 1) + 1) * 2 * ui;
|
||||
temp += ((timing->clk_zero >> 1) + 1) * 2 * ui;
|
||||
temp += 8 * ui + lpx;
|
||||
tmin = S_DIV_ROUND_UP(temp, 8 * ui) - 1;
|
||||
if (tmin > tmax) {
|
||||
temp = linear_inter(2 * tmax, tmin, pcnt2, 0, false) >> 1;
|
||||
timing->clk_pre = temp >> 1;
|
||||
temp = (2 * tmax - tmin) * pcnt2;
|
||||
} else {
|
||||
timing->clk_pre = linear_inter(tmax, tmin, pcnt2, 0, false);
|
||||
}
|
||||
|
||||
timing->ta_go = 3;
|
||||
timing->ta_sure = 0;
|
||||
timing->ta_get = 4;
|
||||
|
||||
DBG("PHY timings: %d, %d, %d, %d, %d, %d, %d, %d, %d, %d",
|
||||
timing->clk_pre, timing->clk_post, timing->clk_zero,
|
||||
timing->clk_trail, timing->clk_prepare, timing->hs_exit,
|
||||
timing->hs_zero, timing->hs_prepare, timing->hs_trail,
|
||||
timing->hs_rqst);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dsi_28nm_phy_regulator_ctrl(struct msm_dsi_phy *phy, bool enable)
|
||||
{
|
||||
void __iomem *base = phy->reg_base;
|
||||
|
||||
if (!enable) {
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CAL_PWR_CFG, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_0, 0x0);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CAL_PWR_CFG, 1);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_5, 0);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_3, 0);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_2, 0x3);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_1, 0x9);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_0, 0x7);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_REGULATOR_CTRL_4, 0x20);
|
||||
}
|
||||
|
||||
static int dsi_28nm_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel,
|
||||
const unsigned long bit_rate, const unsigned long esc_rate)
|
||||
{
|
||||
struct dsi_dphy_timing *timing = &phy->timing;
|
||||
int i;
|
||||
void __iomem *base = phy->base;
|
||||
|
||||
DBG("");
|
||||
|
||||
if (dsi_dphy_timing_calc(timing, bit_rate, esc_rate)) {
|
||||
pr_err("%s: D-PHY timing calculation failed\n", __func__);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_STRENGTH_0, 0xff);
|
||||
|
||||
dsi_28nm_phy_regulator_ctrl(phy, true);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LDO_CNTRL, 0x00);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_0,
|
||||
DSI_28nm_PHY_TIMING_CTRL_0_CLK_ZERO(timing->clk_zero));
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_1,
|
||||
DSI_28nm_PHY_TIMING_CTRL_1_CLK_TRAIL(timing->clk_trail));
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_2,
|
||||
DSI_28nm_PHY_TIMING_CTRL_2_CLK_PREPARE(timing->clk_prepare));
|
||||
if (timing->clk_zero & BIT(8))
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_3,
|
||||
DSI_28nm_PHY_TIMING_CTRL_3_CLK_ZERO_8);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_4,
|
||||
DSI_28nm_PHY_TIMING_CTRL_4_HS_EXIT(timing->hs_exit));
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_5,
|
||||
DSI_28nm_PHY_TIMING_CTRL_5_HS_ZERO(timing->hs_zero));
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_6,
|
||||
DSI_28nm_PHY_TIMING_CTRL_6_HS_PREPARE(timing->hs_prepare));
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_7,
|
||||
DSI_28nm_PHY_TIMING_CTRL_7_HS_TRAIL(timing->hs_trail));
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_8,
|
||||
DSI_28nm_PHY_TIMING_CTRL_8_HS_RQST(timing->hs_rqst));
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_9,
|
||||
DSI_28nm_PHY_TIMING_CTRL_9_TA_GO(timing->ta_go) |
|
||||
DSI_28nm_PHY_TIMING_CTRL_9_TA_SURE(timing->ta_sure));
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_10,
|
||||
DSI_28nm_PHY_TIMING_CTRL_10_TA_GET(timing->ta_get));
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_TIMING_CTRL_11,
|
||||
DSI_28nm_PHY_TIMING_CTRL_11_TRIG3_CMD(0));
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_CTRL_1, 0x00);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_CTRL_0, 0x5f);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_STRENGTH_1, 0x6);
|
||||
|
||||
for (i = 0; i < 4; i++) {
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_0(i), 0);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_1(i), 0);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_2(i), 0);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_3(i), 0);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LN_TEST_DATAPATH(i), 0);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LN_DEBUG_SEL(i), 0);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LN_TEST_STR_0(i), 0x1);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LN_TEST_STR_1(i), 0x97);
|
||||
}
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_4(0), 0);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_4(1), 0x5);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_4(2), 0xa);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LN_CFG_4(3), 0xf);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LNCK_CFG_1, 0xc0);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LNCK_TEST_STR0, 0x1);
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_LNCK_TEST_STR1, 0xbb);
|
||||
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_CTRL_0, 0x5f);
|
||||
|
||||
if (is_dual_panel && (phy->id != DSI_CLOCK_MASTER))
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_GLBL_TEST_CTRL, 0x00);
|
||||
else
|
||||
dsi_phy_write(base + REG_DSI_28nm_PHY_GLBL_TEST_CTRL, 0x01);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_28nm_phy_disable(struct msm_dsi_phy *phy)
|
||||
{
|
||||
dsi_phy_write(phy->base + REG_DSI_28nm_PHY_CTRL_0, 0);
|
||||
dsi_28nm_phy_regulator_ctrl(phy, false);
|
||||
|
||||
/*
|
||||
* Wait for the registers writes to complete in order to
|
||||
* ensure that the phy is completely disabled
|
||||
*/
|
||||
wmb();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define dsi_phy_func_init(name) \
|
||||
do { \
|
||||
phy->enable = dsi_##name##_phy_enable; \
|
||||
phy->disable = dsi_##name##_phy_disable; \
|
||||
} while (0)
|
||||
|
||||
struct msm_dsi_phy *msm_dsi_phy_init(struct platform_device *pdev,
|
||||
enum msm_dsi_phy_type type, int id)
|
||||
{
|
||||
struct msm_dsi_phy *phy;
|
||||
|
||||
phy = devm_kzalloc(&pdev->dev, sizeof(*phy), GFP_KERNEL);
|
||||
if (!phy)
|
||||
return NULL;
|
||||
|
||||
phy->base = msm_ioremap(pdev, "dsi_phy", "DSI_PHY");
|
||||
if (IS_ERR_OR_NULL(phy->base)) {
|
||||
pr_err("%s: failed to map phy base\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
phy->reg_base = msm_ioremap(pdev, "dsi_phy_regulator", "DSI_PHY_REG");
|
||||
if (IS_ERR_OR_NULL(phy->reg_base)) {
|
||||
pr_err("%s: failed to map phy regulator base\n", __func__);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case MSM_DSI_PHY_28NM:
|
||||
dsi_phy_func_init(28nm);
|
||||
break;
|
||||
default:
|
||||
pr_err("%s: unsupported type, %d\n", __func__, type);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
phy->id = id;
|
||||
|
||||
return phy;
|
||||
}
|
||||
|
||||
int msm_dsi_phy_enable(struct msm_dsi_phy *phy, bool is_dual_panel,
|
||||
const unsigned long bit_rate, const unsigned long esc_rate)
|
||||
{
|
||||
if (!phy || !phy->enable)
|
||||
return -EINVAL;
|
||||
return phy->enable(phy, is_dual_panel, bit_rate, esc_rate);
|
||||
}
|
||||
|
||||
int msm_dsi_phy_disable(struct msm_dsi_phy *phy)
|
||||
{
|
||||
if (!phy || !phy->disable)
|
||||
return -EINVAL;
|
||||
return phy->disable(phy);
|
||||
}
|
||||
|
||||
void msm_dsi_phy_get_clk_pre_post(struct msm_dsi_phy *phy,
|
||||
u32 *clk_pre, u32 *clk_post)
|
||||
{
|
||||
if (!phy)
|
||||
return;
|
||||
if (clk_pre)
|
||||
*clk_pre = phy->timing.clk_pre;
|
||||
if (clk_post)
|
||||
*clk_post = phy->timing.clk_post;
|
||||
}
|
||||
|
|
@ -82,6 +82,9 @@ struct msm_drm_private {
|
|||
*/
|
||||
struct msm_edp *edp;
|
||||
|
||||
/* DSI is shared by mdp4 and mdp5 */
|
||||
struct msm_dsi *dsi[2];
|
||||
|
||||
/* when we have more than one 'msm_gpu' these need to be an array: */
|
||||
struct msm_gpu *gpu;
|
||||
struct msm_file_private *lastctx;
|
||||
|
@ -236,6 +239,32 @@ void __exit msm_edp_unregister(void);
|
|||
int msm_edp_modeset_init(struct msm_edp *edp, struct drm_device *dev,
|
||||
struct drm_encoder *encoder);
|
||||
|
||||
struct msm_dsi;
|
||||
enum msm_dsi_encoder_id {
|
||||
MSM_DSI_VIDEO_ENCODER_ID = 0,
|
||||
MSM_DSI_CMD_ENCODER_ID = 1,
|
||||
MSM_DSI_ENCODER_NUM = 2
|
||||
};
|
||||
#ifdef CONFIG_DRM_MSM_DSI
|
||||
void __init msm_dsi_register(void);
|
||||
void __exit msm_dsi_unregister(void);
|
||||
int msm_dsi_modeset_init(struct msm_dsi *msm_dsi, struct drm_device *dev,
|
||||
struct drm_encoder *encoders[MSM_DSI_ENCODER_NUM]);
|
||||
#else
|
||||
static inline void __init msm_dsi_register(void)
|
||||
{
|
||||
}
|
||||
static inline void __exit msm_dsi_unregister(void)
|
||||
{
|
||||
}
|
||||
static inline int msm_dsi_modeset_init(struct msm_dsi *msm_dsi,
|
||||
struct drm_device *dev,
|
||||
struct drm_encoder *encoders[MSM_DSI_ENCODER_NUM])
|
||||
{
|
||||
return -EINVAL;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void msm_gem_describe(struct drm_gem_object *obj, struct seq_file *m);
|
||||
void msm_gem_describe_objects(struct list_head *list, struct seq_file *m);
|
||||
|
|
Loading…
Reference in New Issue