drm/msm: Initial add eDP support in msm drm driver (v5)
This change adds a new eDP connector in msm drm driver. With this change, eDP panel can work with msm platform under drm framework. v1: Initial change v2: Address Rob's comments Use generated header file for register definitions Change to devm_* APIs v3: Address Thierry's comments and rebase on top of atomic changes Remove edp_bridge_mode_fixup Remove backlight control code and rely on pwm-backlight Remove continuous splash screen support for now Change to gpiod_* APIs v4: Fix kbuild test issue Signed-off-by: Hai Li <hali@codeaurora.org> [robclark: v5: rebase on drm_bridge changes in drm-next] Signed-off-by: Rob Clark <robdclark@gmail.com>
This commit is contained in:
parent
b1b1c74e36
commit
ab5b0107cc
|
@ -16,6 +16,12 @@ msm-y := \
|
|||
hdmi/hdmi_phy_8960.o \
|
||||
hdmi/hdmi_phy_8x60.o \
|
||||
hdmi/hdmi_phy_8x74.o \
|
||||
edp/edp.o \
|
||||
edp/edp_aux.o \
|
||||
edp/edp_bridge.o \
|
||||
edp/edp_connector.o \
|
||||
edp/edp_ctrl.o \
|
||||
edp/edp_phy.o \
|
||||
mdp/mdp_format.o \
|
||||
mdp/mdp_kms.o \
|
||||
mdp/mdp4/mdp4_crtc.o \
|
||||
|
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Copyright (c) 2014-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 <linux/of_irq.h>
|
||||
#include "edp.h"
|
||||
|
||||
static irqreturn_t edp_irq(int irq, void *dev_id)
|
||||
{
|
||||
struct msm_edp *edp = dev_id;
|
||||
|
||||
/* Process eDP irq */
|
||||
return msm_edp_ctrl_irq(edp->ctrl);
|
||||
}
|
||||
|
||||
static void edp_destroy(struct platform_device *pdev)
|
||||
{
|
||||
struct msm_edp *edp = platform_get_drvdata(pdev);
|
||||
|
||||
if (!edp)
|
||||
return;
|
||||
|
||||
if (edp->ctrl) {
|
||||
msm_edp_ctrl_destroy(edp->ctrl);
|
||||
edp->ctrl = NULL;
|
||||
}
|
||||
|
||||
platform_set_drvdata(pdev, NULL);
|
||||
}
|
||||
|
||||
/* construct eDP at bind/probe time, grab all the resources. */
|
||||
static struct msm_edp *edp_init(struct platform_device *pdev)
|
||||
{
|
||||
struct msm_edp *edp = NULL;
|
||||
int ret;
|
||||
|
||||
if (!pdev) {
|
||||
pr_err("no eDP device\n");
|
||||
ret = -ENXIO;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
edp = devm_kzalloc(&pdev->dev, sizeof(*edp), GFP_KERNEL);
|
||||
if (!edp) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
DBG("eDP probed=%p", edp);
|
||||
|
||||
edp->pdev = pdev;
|
||||
platform_set_drvdata(pdev, edp);
|
||||
|
||||
ret = msm_edp_ctrl_init(edp);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
return edp;
|
||||
|
||||
fail:
|
||||
if (edp)
|
||||
edp_destroy(pdev);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
static int edp_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 msm_edp *edp;
|
||||
|
||||
DBG("");
|
||||
edp = edp_init(to_platform_device(dev));
|
||||
if (IS_ERR(edp))
|
||||
return PTR_ERR(edp);
|
||||
priv->edp = edp;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void edp_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;
|
||||
|
||||
DBG("");
|
||||
if (priv->edp) {
|
||||
edp_destroy(to_platform_device(dev));
|
||||
priv->edp = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static const struct component_ops edp_ops = {
|
||||
.bind = edp_bind,
|
||||
.unbind = edp_unbind,
|
||||
};
|
||||
|
||||
static int edp_dev_probe(struct platform_device *pdev)
|
||||
{
|
||||
DBG("");
|
||||
return component_add(&pdev->dev, &edp_ops);
|
||||
}
|
||||
|
||||
static int edp_dev_remove(struct platform_device *pdev)
|
||||
{
|
||||
DBG("");
|
||||
component_del(&pdev->dev, &edp_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id dt_match[] = {
|
||||
{ .compatible = "qcom,mdss-edp" },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver edp_driver = {
|
||||
.probe = edp_dev_probe,
|
||||
.remove = edp_dev_remove,
|
||||
.driver = {
|
||||
.name = "msm_edp",
|
||||
.of_match_table = dt_match,
|
||||
},
|
||||
};
|
||||
|
||||
void __init msm_edp_register(void)
|
||||
{
|
||||
DBG("");
|
||||
platform_driver_register(&edp_driver);
|
||||
}
|
||||
|
||||
void __exit msm_edp_unregister(void)
|
||||
{
|
||||
DBG("");
|
||||
platform_driver_unregister(&edp_driver);
|
||||
}
|
||||
|
||||
/* Second part of initialization, the drm/kms level modeset_init */
|
||||
int msm_edp_modeset_init(struct msm_edp *edp, struct drm_device *dev,
|
||||
struct drm_encoder *encoder)
|
||||
{
|
||||
struct platform_device *pdev = edp->pdev;
|
||||
struct msm_drm_private *priv = dev->dev_private;
|
||||
int ret;
|
||||
|
||||
edp->encoder = encoder;
|
||||
edp->dev = dev;
|
||||
|
||||
edp->bridge = msm_edp_bridge_init(edp);
|
||||
if (IS_ERR(edp->bridge)) {
|
||||
ret = PTR_ERR(edp->bridge);
|
||||
dev_err(dev->dev, "failed to create eDP bridge: %d\n", ret);
|
||||
edp->bridge = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
edp->connector = msm_edp_connector_init(edp);
|
||||
if (IS_ERR(edp->connector)) {
|
||||
ret = PTR_ERR(edp->connector);
|
||||
dev_err(dev->dev, "failed to create eDP connector: %d\n", ret);
|
||||
edp->connector = NULL;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
edp->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
|
||||
if (edp->irq < 0) {
|
||||
ret = edp->irq;
|
||||
dev_err(dev->dev, "failed to get IRQ: %d\n", ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = devm_request_irq(&pdev->dev, edp->irq,
|
||||
edp_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
|
||||
"edp_isr", edp);
|
||||
if (ret < 0) {
|
||||
dev_err(dev->dev, "failed to request IRQ%u: %d\n",
|
||||
edp->irq, ret);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
encoder->bridge = edp->bridge;
|
||||
|
||||
priv->bridges[priv->num_bridges++] = edp->bridge;
|
||||
priv->connectors[priv->num_connectors++] = edp->connector;
|
||||
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
/* bridge/connector are normally destroyed by drm */
|
||||
if (edp->bridge) {
|
||||
edp_bridge_destroy(edp->bridge);
|
||||
edp->bridge = NULL;
|
||||
}
|
||||
if (edp->connector) {
|
||||
edp->connector->funcs->destroy(edp->connector);
|
||||
edp->connector = NULL;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
/*
|
||||
* Copyright (c) 2014-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 __EDP_CONNECTOR_H__
|
||||
#define __EDP_CONNECTOR_H__
|
||||
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
||||
#include "drm_crtc.h"
|
||||
#include "drm_dp_helper.h"
|
||||
#include "msm_drv.h"
|
||||
|
||||
#define edp_read(offset) msm_readl((offset))
|
||||
#define edp_write(offset, data) msm_writel((data), (offset))
|
||||
|
||||
struct edp_ctrl;
|
||||
struct edp_aux;
|
||||
struct edp_phy;
|
||||
|
||||
struct msm_edp {
|
||||
struct drm_device *dev;
|
||||
struct platform_device *pdev;
|
||||
|
||||
struct drm_connector *connector;
|
||||
struct drm_bridge *bridge;
|
||||
|
||||
/* the encoder we are hooked to (outside of eDP block) */
|
||||
struct drm_encoder *encoder;
|
||||
|
||||
struct edp_ctrl *ctrl;
|
||||
|
||||
int irq;
|
||||
};
|
||||
|
||||
/* eDP bridge */
|
||||
struct drm_bridge *msm_edp_bridge_init(struct msm_edp *edp);
|
||||
void edp_bridge_destroy(struct drm_bridge *bridge);
|
||||
|
||||
/* eDP connector */
|
||||
struct drm_connector *msm_edp_connector_init(struct msm_edp *edp);
|
||||
|
||||
/* AUX */
|
||||
void *msm_edp_aux_init(struct device *dev, void __iomem *regbase,
|
||||
struct drm_dp_aux **drm_aux);
|
||||
void msm_edp_aux_destroy(struct device *dev, struct edp_aux *aux);
|
||||
irqreturn_t msm_edp_aux_irq(struct edp_aux *aux, u32 isr);
|
||||
void msm_edp_aux_ctrl(struct edp_aux *aux, int enable);
|
||||
|
||||
/* Phy */
|
||||
bool msm_edp_phy_ready(struct edp_phy *phy);
|
||||
void msm_edp_phy_ctrl(struct edp_phy *phy, int enable);
|
||||
void msm_edp_phy_vm_pe_init(struct edp_phy *phy);
|
||||
void msm_edp_phy_vm_pe_cfg(struct edp_phy *phy, u32 v0, u32 v1);
|
||||
void msm_edp_phy_lane_power_ctrl(struct edp_phy *phy, bool up, u32 max_lane);
|
||||
void *msm_edp_phy_init(struct device *dev, void __iomem *regbase);
|
||||
|
||||
/* Ctrl */
|
||||
irqreturn_t msm_edp_ctrl_irq(struct edp_ctrl *ctrl);
|
||||
void msm_edp_ctrl_power(struct edp_ctrl *ctrl, bool on);
|
||||
int msm_edp_ctrl_init(struct msm_edp *edp);
|
||||
void msm_edp_ctrl_destroy(struct edp_ctrl *ctrl);
|
||||
bool msm_edp_ctrl_panel_connected(struct edp_ctrl *ctrl);
|
||||
int msm_edp_ctrl_get_panel_info(struct edp_ctrl *ctrl,
|
||||
struct drm_connector *connector, struct edid **edid);
|
||||
int msm_edp_ctrl_timing_cfg(struct edp_ctrl *ctrl,
|
||||
const struct drm_display_mode *mode,
|
||||
const struct drm_display_info *info);
|
||||
/* @pixel_rate is in kHz */
|
||||
bool msm_edp_ctrl_pixel_clock_valid(struct edp_ctrl *ctrl,
|
||||
u32 pixel_rate, u32 *pm, u32 *pn);
|
||||
|
||||
#endif /* __EDP_CONNECTOR_H__ */
|
|
@ -0,0 +1,268 @@
|
|||
/*
|
||||
* Copyright (c) 2014-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 "edp.h"
|
||||
#include "edp.xml.h"
|
||||
|
||||
#define AUX_CMD_FIFO_LEN 144
|
||||
#define AUX_CMD_NATIVE_MAX 16
|
||||
#define AUX_CMD_I2C_MAX 128
|
||||
|
||||
#define EDP_INTR_AUX_I2C_ERR \
|
||||
(EDP_INTERRUPT_REG_1_WRONG_ADDR | EDP_INTERRUPT_REG_1_TIMEOUT | \
|
||||
EDP_INTERRUPT_REG_1_NACK_DEFER | EDP_INTERRUPT_REG_1_WRONG_DATA_CNT | \
|
||||
EDP_INTERRUPT_REG_1_I2C_NACK | EDP_INTERRUPT_REG_1_I2C_DEFER)
|
||||
#define EDP_INTR_TRANS_STATUS \
|
||||
(EDP_INTERRUPT_REG_1_AUX_I2C_DONE | EDP_INTR_AUX_I2C_ERR)
|
||||
|
||||
struct edp_aux {
|
||||
void __iomem *base;
|
||||
bool msg_err;
|
||||
|
||||
struct completion msg_comp;
|
||||
|
||||
/* To prevent the message transaction routine from reentry. */
|
||||
struct mutex msg_mutex;
|
||||
|
||||
struct drm_dp_aux drm_aux;
|
||||
};
|
||||
#define to_edp_aux(x) container_of(x, struct edp_aux, drm_aux)
|
||||
|
||||
static int edp_msg_fifo_tx(struct edp_aux *aux, struct drm_dp_aux_msg *msg)
|
||||
{
|
||||
u32 data[4];
|
||||
u32 reg, len;
|
||||
bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ);
|
||||
bool read = msg->request & (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ);
|
||||
u8 *msgdata = msg->buffer;
|
||||
int i;
|
||||
|
||||
if (read)
|
||||
len = 4;
|
||||
else
|
||||
len = msg->size + 4;
|
||||
|
||||
/*
|
||||
* cmd fifo only has depth of 144 bytes
|
||||
*/
|
||||
if (len > AUX_CMD_FIFO_LEN)
|
||||
return -EINVAL;
|
||||
|
||||
/* Pack cmd and write to HW */
|
||||
data[0] = (msg->address >> 16) & 0xf; /* addr[19:16] */
|
||||
if (read)
|
||||
data[0] |= BIT(4); /* R/W */
|
||||
|
||||
data[1] = (msg->address >> 8) & 0xff; /* addr[15:8] */
|
||||
data[2] = msg->address & 0xff; /* addr[7:0] */
|
||||
data[3] = (msg->size - 1) & 0xff; /* len[7:0] */
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
reg = (i < 4) ? data[i] : msgdata[i - 4];
|
||||
reg = EDP_AUX_DATA_DATA(reg); /* index = 0, write */
|
||||
if (i == 0)
|
||||
reg |= EDP_AUX_DATA_INDEX_WRITE;
|
||||
edp_write(aux->base + REG_EDP_AUX_DATA, reg);
|
||||
}
|
||||
|
||||
reg = 0; /* Transaction number is always 1 */
|
||||
if (!native) /* i2c */
|
||||
reg |= EDP_AUX_TRANS_CTRL_I2C;
|
||||
|
||||
reg |= EDP_AUX_TRANS_CTRL_GO;
|
||||
edp_write(aux->base + REG_EDP_AUX_TRANS_CTRL, reg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int edp_msg_fifo_rx(struct edp_aux *aux, struct drm_dp_aux_msg *msg)
|
||||
{
|
||||
u32 data;
|
||||
u8 *dp;
|
||||
int i;
|
||||
u32 len = msg->size;
|
||||
|
||||
edp_write(aux->base + REG_EDP_AUX_DATA,
|
||||
EDP_AUX_DATA_INDEX_WRITE | EDP_AUX_DATA_READ); /* index = 0 */
|
||||
|
||||
dp = msg->buffer;
|
||||
|
||||
/* discard first byte */
|
||||
data = edp_read(aux->base + REG_EDP_AUX_DATA);
|
||||
for (i = 0; i < len; i++) {
|
||||
data = edp_read(aux->base + REG_EDP_AUX_DATA);
|
||||
dp[i] = (u8)((data >> 8) & 0xff);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* This function does the real job to process an AUX transaction.
|
||||
* It will call msm_edp_aux_ctrl() function to reset the AUX channel,
|
||||
* if the waiting is timeout.
|
||||
* The caller who triggers the transaction should avoid the
|
||||
* msm_edp_aux_ctrl() running concurrently in other threads, i.e.
|
||||
* start transaction only when AUX channel is fully enabled.
|
||||
*/
|
||||
ssize_t edp_aux_transfer(struct drm_dp_aux *drm_aux, struct drm_dp_aux_msg *msg)
|
||||
{
|
||||
struct edp_aux *aux = to_edp_aux(drm_aux);
|
||||
ssize_t ret;
|
||||
bool native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ);
|
||||
bool read = msg->request & (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ);
|
||||
|
||||
/* Ignore address only message */
|
||||
if ((msg->size == 0) || (msg->buffer == NULL)) {
|
||||
msg->reply = native ?
|
||||
DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK;
|
||||
return msg->size;
|
||||
}
|
||||
|
||||
/* msg sanity check */
|
||||
if ((native && (msg->size > AUX_CMD_NATIVE_MAX)) ||
|
||||
(msg->size > AUX_CMD_I2C_MAX)) {
|
||||
pr_err("%s: invalid msg: size(%d), request(%x)\n",
|
||||
__func__, msg->size, msg->request);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
mutex_lock(&aux->msg_mutex);
|
||||
|
||||
aux->msg_err = false;
|
||||
reinit_completion(&aux->msg_comp);
|
||||
|
||||
ret = edp_msg_fifo_tx(aux, msg);
|
||||
if (ret < 0)
|
||||
goto unlock_exit;
|
||||
|
||||
DBG("wait_for_completion");
|
||||
ret = wait_for_completion_timeout(&aux->msg_comp, 300);
|
||||
if (ret <= 0) {
|
||||
/*
|
||||
* Clear GO and reset AUX channel
|
||||
* to cancel the current transaction.
|
||||
*/
|
||||
edp_write(aux->base + REG_EDP_AUX_TRANS_CTRL, 0);
|
||||
msm_edp_aux_ctrl(aux, 1);
|
||||
pr_err("%s: aux timeout, %d\n", __func__, ret);
|
||||
goto unlock_exit;
|
||||
}
|
||||
DBG("completion");
|
||||
|
||||
if (!aux->msg_err) {
|
||||
if (read) {
|
||||
ret = edp_msg_fifo_rx(aux, msg);
|
||||
if (ret < 0)
|
||||
goto unlock_exit;
|
||||
}
|
||||
|
||||
msg->reply = native ?
|
||||
DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK;
|
||||
} else {
|
||||
/* Reply defer to retry */
|
||||
msg->reply = native ?
|
||||
DP_AUX_NATIVE_REPLY_DEFER : DP_AUX_I2C_REPLY_DEFER;
|
||||
/*
|
||||
* The sleep time in caller is not long enough to make sure
|
||||
* our H/W completes transactions. Add more defer time here.
|
||||
*/
|
||||
msleep(100);
|
||||
}
|
||||
|
||||
/* Return requested size for success or retry */
|
||||
ret = msg->size;
|
||||
|
||||
unlock_exit:
|
||||
mutex_unlock(&aux->msg_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void *msm_edp_aux_init(struct device *dev, void __iomem *regbase,
|
||||
struct drm_dp_aux **drm_aux)
|
||||
{
|
||||
struct edp_aux *aux = NULL;
|
||||
int ret;
|
||||
|
||||
DBG("");
|
||||
aux = devm_kzalloc(dev, sizeof(*aux), GFP_KERNEL);
|
||||
if (!aux)
|
||||
return NULL;
|
||||
|
||||
aux->base = regbase;
|
||||
mutex_init(&aux->msg_mutex);
|
||||
init_completion(&aux->msg_comp);
|
||||
|
||||
aux->drm_aux.name = "msm_edp_aux";
|
||||
aux->drm_aux.dev = dev;
|
||||
aux->drm_aux.transfer = edp_aux_transfer;
|
||||
ret = drm_dp_aux_register(&aux->drm_aux);
|
||||
if (ret) {
|
||||
pr_err("%s: failed to register drm aux: %d\n", __func__, ret);
|
||||
mutex_destroy(&aux->msg_mutex);
|
||||
}
|
||||
|
||||
if (drm_aux && aux)
|
||||
*drm_aux = &aux->drm_aux;
|
||||
|
||||
return aux;
|
||||
}
|
||||
|
||||
void msm_edp_aux_destroy(struct device *dev, struct edp_aux *aux)
|
||||
{
|
||||
if (aux) {
|
||||
drm_dp_aux_unregister(&aux->drm_aux);
|
||||
mutex_destroy(&aux->msg_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
irqreturn_t msm_edp_aux_irq(struct edp_aux *aux, u32 isr)
|
||||
{
|
||||
if (isr & EDP_INTR_TRANS_STATUS) {
|
||||
DBG("isr=%x", isr);
|
||||
edp_write(aux->base + REG_EDP_AUX_TRANS_CTRL, 0);
|
||||
|
||||
if (isr & EDP_INTR_AUX_I2C_ERR)
|
||||
aux->msg_err = true;
|
||||
else
|
||||
aux->msg_err = false;
|
||||
|
||||
complete(&aux->msg_comp);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
void msm_edp_aux_ctrl(struct edp_aux *aux, int enable)
|
||||
{
|
||||
u32 data;
|
||||
|
||||
DBG("enable=%d", enable);
|
||||
data = edp_read(aux->base + REG_EDP_AUX_CTRL);
|
||||
|
||||
if (enable) {
|
||||
data |= EDP_AUX_CTRL_RESET;
|
||||
edp_write(aux->base + REG_EDP_AUX_CTRL, data);
|
||||
/* Make sure full reset */
|
||||
wmb();
|
||||
usleep_range(500, 1000);
|
||||
|
||||
data &= ~EDP_AUX_CTRL_RESET;
|
||||
data |= EDP_AUX_CTRL_ENABLE;
|
||||
edp_write(aux->base + REG_EDP_AUX_CTRL, data);
|
||||
} else {
|
||||
data &= ~EDP_AUX_CTRL_ENABLE;
|
||||
edp_write(aux->base + REG_EDP_AUX_CTRL, data);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
* Copyright (c) 2014-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 "edp.h"
|
||||
|
||||
struct edp_bridge {
|
||||
struct drm_bridge base;
|
||||
struct msm_edp *edp;
|
||||
};
|
||||
#define to_edp_bridge(x) container_of(x, struct edp_bridge, base)
|
||||
|
||||
void edp_bridge_destroy(struct drm_bridge *bridge)
|
||||
{
|
||||
}
|
||||
|
||||
static void edp_bridge_pre_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct edp_bridge *edp_bridge = to_edp_bridge(bridge);
|
||||
struct msm_edp *edp = edp_bridge->edp;
|
||||
|
||||
DBG("");
|
||||
msm_edp_ctrl_power(edp->ctrl, true);
|
||||
}
|
||||
|
||||
static void edp_bridge_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
DBG("");
|
||||
}
|
||||
|
||||
static void edp_bridge_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
DBG("");
|
||||
}
|
||||
|
||||
static void edp_bridge_post_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct edp_bridge *edp_bridge = to_edp_bridge(bridge);
|
||||
struct msm_edp *edp = edp_bridge->edp;
|
||||
|
||||
DBG("");
|
||||
msm_edp_ctrl_power(edp->ctrl, false);
|
||||
}
|
||||
|
||||
static void edp_bridge_mode_set(struct drm_bridge *bridge,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adjusted_mode)
|
||||
{
|
||||
struct drm_device *dev = bridge->dev;
|
||||
struct drm_connector *connector;
|
||||
struct edp_bridge *edp_bridge = to_edp_bridge(bridge);
|
||||
struct msm_edp *edp = edp_bridge->edp;
|
||||
|
||||
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);
|
||||
|
||||
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
|
||||
if ((connector->encoder != NULL) &&
|
||||
(connector->encoder->bridge == bridge)) {
|
||||
msm_edp_ctrl_timing_cfg(edp->ctrl,
|
||||
adjusted_mode, &connector->display_info);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs edp_bridge_funcs = {
|
||||
.pre_enable = edp_bridge_pre_enable,
|
||||
.enable = edp_bridge_enable,
|
||||
.disable = edp_bridge_disable,
|
||||
.post_disable = edp_bridge_post_disable,
|
||||
.mode_set = edp_bridge_mode_set,
|
||||
};
|
||||
|
||||
/* initialize bridge */
|
||||
struct drm_bridge *msm_edp_bridge_init(struct msm_edp *edp)
|
||||
{
|
||||
struct drm_bridge *bridge = NULL;
|
||||
struct edp_bridge *edp_bridge;
|
||||
int ret;
|
||||
|
||||
edp_bridge = devm_kzalloc(edp->dev->dev,
|
||||
sizeof(*edp_bridge), GFP_KERNEL);
|
||||
if (!edp_bridge) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
edp_bridge->edp = edp;
|
||||
|
||||
bridge = &edp_bridge->base;
|
||||
bridge->funcs = &edp_bridge_funcs;
|
||||
|
||||
ret = drm_bridge_attach(edp->dev, bridge);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
return bridge;
|
||||
|
||||
fail:
|
||||
if (bridge)
|
||||
edp_bridge_destroy(bridge);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
|
@ -0,0 +1,161 @@
|
|||
/*
|
||||
* Copyright (c) 2014-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 "drm/drm_edid.h"
|
||||
#include "msm_kms.h"
|
||||
#include "edp.h"
|
||||
|
||||
struct edp_connector {
|
||||
struct drm_connector base;
|
||||
struct msm_edp *edp;
|
||||
};
|
||||
#define to_edp_connector(x) container_of(x, struct edp_connector, base)
|
||||
|
||||
static enum drm_connector_status edp_connector_detect(
|
||||
struct drm_connector *connector, bool force)
|
||||
{
|
||||
struct edp_connector *edp_connector = to_edp_connector(connector);
|
||||
struct msm_edp *edp = edp_connector->edp;
|
||||
|
||||
DBG("");
|
||||
return msm_edp_ctrl_panel_connected(edp->ctrl) ?
|
||||
connector_status_connected : connector_status_disconnected;
|
||||
}
|
||||
|
||||
static void edp_connector_destroy(struct drm_connector *connector)
|
||||
{
|
||||
struct edp_connector *edp_connector = to_edp_connector(connector);
|
||||
|
||||
DBG("");
|
||||
drm_connector_unregister(connector);
|
||||
drm_connector_cleanup(connector);
|
||||
|
||||
kfree(edp_connector);
|
||||
}
|
||||
|
||||
static int edp_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct edp_connector *edp_connector = to_edp_connector(connector);
|
||||
struct msm_edp *edp = edp_connector->edp;
|
||||
|
||||
struct edid *drm_edid = NULL;
|
||||
int ret = 0;
|
||||
|
||||
DBG("");
|
||||
ret = msm_edp_ctrl_get_panel_info(edp->ctrl, connector, &drm_edid);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
drm_mode_connector_update_edid_property(connector, drm_edid);
|
||||
if (drm_edid)
|
||||
ret = drm_add_edid_modes(connector, drm_edid);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int edp_connector_mode_valid(struct drm_connector *connector,
|
||||
struct drm_display_mode *mode)
|
||||
{
|
||||
struct edp_connector *edp_connector = to_edp_connector(connector);
|
||||
struct msm_edp *edp = edp_connector->edp;
|
||||
struct msm_drm_private *priv = connector->dev->dev_private;
|
||||
struct msm_kms *kms = priv->kms;
|
||||
long actual, requested;
|
||||
|
||||
requested = 1000 * mode->clock;
|
||||
actual = kms->funcs->round_pixclk(kms,
|
||||
requested, edp_connector->edp->encoder);
|
||||
|
||||
DBG("requested=%ld, actual=%ld", requested, actual);
|
||||
if (actual != requested)
|
||||
return MODE_CLOCK_RANGE;
|
||||
|
||||
if (!msm_edp_ctrl_pixel_clock_valid(
|
||||
edp->ctrl, mode->clock, NULL, NULL))
|
||||
return MODE_CLOCK_RANGE;
|
||||
|
||||
/* Invalidate all modes if color format is not supported */
|
||||
if (connector->display_info.bpc > 8)
|
||||
return MODE_BAD;
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static struct drm_encoder *
|
||||
edp_connector_best_encoder(struct drm_connector *connector)
|
||||
{
|
||||
struct edp_connector *edp_connector = to_edp_connector(connector);
|
||||
|
||||
DBG("");
|
||||
return edp_connector->edp->encoder;
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs edp_connector_funcs = {
|
||||
.dpms = drm_atomic_helper_connector_dpms,
|
||||
.detect = edp_connector_detect,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = edp_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 edp_connector_helper_funcs = {
|
||||
.get_modes = edp_connector_get_modes,
|
||||
.mode_valid = edp_connector_mode_valid,
|
||||
.best_encoder = edp_connector_best_encoder,
|
||||
};
|
||||
|
||||
/* initialize connector */
|
||||
struct drm_connector *msm_edp_connector_init(struct msm_edp *edp)
|
||||
{
|
||||
struct drm_connector *connector = NULL;
|
||||
struct edp_connector *edp_connector;
|
||||
int ret;
|
||||
|
||||
edp_connector = kzalloc(sizeof(*edp_connector), GFP_KERNEL);
|
||||
if (!edp_connector) {
|
||||
ret = -ENOMEM;
|
||||
goto fail;
|
||||
}
|
||||
|
||||
edp_connector->edp = edp;
|
||||
|
||||
connector = &edp_connector->base;
|
||||
|
||||
ret = drm_connector_init(edp->dev, connector, &edp_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_eDP);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
drm_connector_helper_add(connector, &edp_connector_helper_funcs);
|
||||
|
||||
/* We don't support HPD, so only poll status until connected. */
|
||||
connector->polled = DRM_CONNECTOR_POLL_CONNECT;
|
||||
|
||||
/* Display driver doesn't support interlace now. */
|
||||
connector->interlace_allowed = false;
|
||||
connector->doublescan_allowed = false;
|
||||
|
||||
ret = drm_connector_register(connector);
|
||||
if (ret)
|
||||
goto fail;
|
||||
|
||||
return connector;
|
||||
|
||||
fail:
|
||||
if (connector)
|
||||
edp_connector_destroy(connector);
|
||||
|
||||
return ERR_PTR(ret);
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* Copyright (c) 2014-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 "edp.h"
|
||||
#include "edp.xml.h"
|
||||
|
||||
#define EDP_MAX_LANE 4
|
||||
|
||||
struct edp_phy {
|
||||
void __iomem *base;
|
||||
};
|
||||
|
||||
bool msm_edp_phy_ready(struct edp_phy *phy)
|
||||
{
|
||||
u32 status;
|
||||
int cnt = 100;
|
||||
|
||||
while (--cnt) {
|
||||
status = edp_read(phy->base +
|
||||
REG_EDP_PHY_GLB_PHY_STATUS);
|
||||
if (status & 0x01)
|
||||
break;
|
||||
usleep_range(500, 1000);
|
||||
}
|
||||
|
||||
if (cnt == 0) {
|
||||
pr_err("%s: PHY NOT ready\n", __func__);
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void msm_edp_phy_ctrl(struct edp_phy *phy, int enable)
|
||||
{
|
||||
DBG("enable=%d", enable);
|
||||
if (enable) {
|
||||
/* Reset */
|
||||
edp_write(phy->base + REG_EDP_PHY_CTRL,
|
||||
EDP_PHY_CTRL_SW_RESET | EDP_PHY_CTRL_SW_RESET_PLL);
|
||||
/* Make sure fully reset */
|
||||
wmb();
|
||||
usleep_range(500, 1000);
|
||||
edp_write(phy->base + REG_EDP_PHY_CTRL, 0x000);
|
||||
edp_write(phy->base + REG_EDP_PHY_GLB_PD_CTL, 0x3f);
|
||||
edp_write(phy->base + REG_EDP_PHY_GLB_CFG, 0x1);
|
||||
} else {
|
||||
edp_write(phy->base + REG_EDP_PHY_GLB_PD_CTL, 0xc0);
|
||||
}
|
||||
}
|
||||
|
||||
/* voltage mode and pre emphasis cfg */
|
||||
void msm_edp_phy_vm_pe_init(struct edp_phy *phy)
|
||||
{
|
||||
edp_write(phy->base + REG_EDP_PHY_GLB_VM_CFG0, 0x3);
|
||||
edp_write(phy->base + REG_EDP_PHY_GLB_VM_CFG1, 0x64);
|
||||
edp_write(phy->base + REG_EDP_PHY_GLB_MISC9, 0x6c);
|
||||
}
|
||||
|
||||
void msm_edp_phy_vm_pe_cfg(struct edp_phy *phy, u32 v0, u32 v1)
|
||||
{
|
||||
edp_write(phy->base + REG_EDP_PHY_GLB_VM_CFG0, v0);
|
||||
edp_write(phy->base + REG_EDP_PHY_GLB_VM_CFG1, v1);
|
||||
}
|
||||
|
||||
void msm_edp_phy_lane_power_ctrl(struct edp_phy *phy, bool up, u32 max_lane)
|
||||
{
|
||||
u32 i;
|
||||
u32 data;
|
||||
|
||||
if (up)
|
||||
data = 0; /* power up */
|
||||
else
|
||||
data = 0x7; /* power down */
|
||||
|
||||
for (i = 0; i < max_lane; i++)
|
||||
edp_write(phy->base + REG_EDP_PHY_LN_PD_CTL(i) , data);
|
||||
|
||||
/* power down unused lane */
|
||||
data = 0x7; /* power down */
|
||||
for (i = max_lane; i < EDP_MAX_LANE; i++)
|
||||
edp_write(phy->base + REG_EDP_PHY_LN_PD_CTL(i) , data);
|
||||
}
|
||||
|
||||
void *msm_edp_phy_init(struct device *dev, void __iomem *regbase)
|
||||
{
|
||||
struct edp_phy *phy = NULL;
|
||||
|
||||
phy = devm_kzalloc(dev, sizeof(*phy), GFP_KERNEL);
|
||||
if (!phy)
|
||||
return NULL;
|
||||
|
||||
phy->base = regbase;
|
||||
return phy;
|
||||
}
|
||||
|
|
@ -76,6 +76,12 @@ struct msm_drm_private {
|
|||
*/
|
||||
struct hdmi *hdmi;
|
||||
|
||||
/* eDP is for mdp5 only, but kms has not been created
|
||||
* when edp_bind() and edp_init() are called. Here is the only
|
||||
* place to keep the edp instance.
|
||||
*/
|
||||
struct msm_edp *edp;
|
||||
|
||||
/* when we have more than one 'msm_gpu' these need to be an array: */
|
||||
struct msm_gpu *gpu;
|
||||
struct msm_file_private *lastctx;
|
||||
|
|
Loading…
Reference in New Issue