Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ab5b0107 authored by Hai Li's avatar Hai Li Committed by Rob Clark
Browse files

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: default avatarHai Li <hali@codeaurora.org>
[robclark: v5: rebase on drm_bridge changes in drm-next]
Signed-off-by: default avatarRob Clark <robdclark@gmail.com>
parent b1b1c74e
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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 \
+208 −0
Original line number Diff line number Diff line
/*
 * 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;
}
+85 −0
Original line number Diff line number Diff line
/*
 * 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__ */
+268 −0
Original line number Diff line number Diff line
/*
 * 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);
	}
}
+120 −0
Original line number Diff line number Diff line
/*
 * 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);
}
Loading