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

Commit 85a233f8 authored by Xiaowen Wu's avatar Xiaowen Wu Committed by Gerrit - the friendly Code Review server
Browse files

drm/msm/dp: add dp aux bridge framework support



Create the framework to support external dp aux bridge device which
can handle DPCD/I2C/HPD from external.

Change-Id: Iabd0998efc8bf7134d186b1751d219c00217385c
Signed-off-by: default avatarXiaowen Wu <wxiaowen@codeaurora.org>
parent 271cca3b
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ msm_drm-y := \
	dp/dp_audio.o \
	dp/dp_debug.o \
	dp/dp_hpd.o \
	dp/dp_aux_bridge.o \
	dp/dp_bridge_hpd.o \
	dp/dp_gpio_hpd.o \
	dp/dp_lphw_hpd.o \
	dp/dp_display.o \
+31 −1
Original line number Diff line number Diff line
@@ -36,6 +36,9 @@ struct dp_aux_private {
	struct completion comp;
	struct drm_dp_aux drm_aux;

	struct msm_dp_aux_bridge *aux_bridge;
	bool bridge_in_transfer;

	bool cmd_busy;
	bool native;
	bool read;
@@ -635,6 +638,25 @@ static ssize_t dp_aux_transfer(struct drm_dp_aux *drm_aux,
	return ret;
}

static ssize_t dp_aux_bridge_transfer(struct drm_dp_aux *drm_aux,
		struct drm_dp_aux_msg *msg)
{
	struct dp_aux_private *aux = container_of(drm_aux,
			struct dp_aux_private, drm_aux);
	ssize_t size;

	if (aux->bridge_in_transfer) {
		size = dp_aux_transfer(drm_aux, msg);
	} else {
		aux->bridge_in_transfer = true;
		size = aux->aux_bridge->transfer(aux->aux_bridge,
				drm_aux, msg);
		aux->bridge_in_transfer = false;
	}

	return size;
}

static void dp_aux_reset_phy_config_indices(struct dp_aux_cfg *aux_cfg)
{
	int i = 0;
@@ -707,6 +729,10 @@ static int dp_aux_register(struct dp_aux *dp_aux)
		goto exit;
	}
	dp_aux->drm_aux = &aux->drm_aux;

	/* if bridge is defined, override transfer function */
	if (aux->aux_bridge && aux->aux_bridge->transfer)
		aux->drm_aux.transfer = dp_aux_bridge_transfer;
exit:
	return ret;
}
@@ -758,6 +784,8 @@ static void dp_aux_set_sim_mode(struct dp_aux *dp_aux, bool en,
	if (en) {
		atomic_set(&aux->aborted, 0);
		aux->drm_aux.transfer = dp_aux_transfer_debug;
	} else if (aux->aux_bridge && aux->aux_bridge->transfer) {
		aux->drm_aux.transfer = dp_aux_bridge_transfer;
	} else {
		aux->drm_aux.transfer = dp_aux_transfer;
	}
@@ -812,7 +840,8 @@ static int dp_aux_configure_aux_switch(struct dp_aux *dp_aux,
}

struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog,
		struct dp_parser *parser, struct device_node *aux_switch)
		struct dp_parser *parser, struct device_node *aux_switch,
		struct msm_dp_aux_bridge *aux_bridge)
{
	int rc = 0;
	struct dp_aux_private *aux;
@@ -841,6 +870,7 @@ struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog,
	aux->catalog = catalog;
	aux->cfg = parser->aux_cfg;
	aux->aux_switch_node = aux_switch;
	aux->aux_bridge = aux_bridge;
	dp_aux = &aux->dp_aux;
	aux->retry_cnt = 0;
	aux->dp_aux.reg = 0xFFFF;
+3 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@

#include "dp_catalog.h"
#include "drm_dp_helper.h"
#include <soc/qcom/msm_dp_aux_bridge.h>

#define DP_STATE_NOTIFICATION_SENT          BIT(0)
#define DP_STATE_TRAIN_1_STARTED            BIT(1)
@@ -63,7 +64,8 @@ struct dp_aux {
};

struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog,
		struct dp_parser *parser, struct device_node *aux_switch);
		struct dp_parser *parser, struct device_node *aux_switch,
		struct msm_dp_aux_bridge *aux_bridge);
void dp_aux_put(struct dp_aux *aux);

#endif /*__DP_AUX_H_*/
+70 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2019, 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.
 *
 */

/*
 * Copyright (c) 2014 Samsung Electronics Co., Ltd
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sub license,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial portions
 * of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include <soc/qcom/msm_dp_aux_bridge.h>

static DEFINE_MUTEX(dp_aux_bridge_lock);
static LIST_HEAD(du_aux_bridge_list);

int msm_dp_aux_add_bridge(struct msm_dp_aux_bridge *bridge)
{
	mutex_lock(&dp_aux_bridge_lock);
	list_add_tail(&bridge->head, &du_aux_bridge_list);
	mutex_unlock(&dp_aux_bridge_lock);

	return 0;
}

#ifdef CONFIG_OF
struct msm_dp_aux_bridge *of_msm_dp_aux_find_bridge(struct device_node *np)
{
	struct msm_dp_aux_bridge *bridge;

	mutex_lock(&dp_aux_bridge_lock);

	list_for_each_entry(bridge, &du_aux_bridge_list, head) {
		if (bridge->of_node == np) {
			mutex_unlock(&dp_aux_bridge_lock);
			return bridge;
		}
	}

	mutex_unlock(&dp_aux_bridge_lock);
	return NULL;
}
#endif
+208 −0
Original line number Diff line number Diff line
/*
 * Copyright (c) 2019, 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.
 *
 */
#define pr_fmt(fmt)	"[drm-dp] %s: " fmt, __func__

#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/slab.h>
#include <linux/device.h>
#include "dp_bridge_hpd.h"

struct dp_bridge_hpd_private {
	struct device *dev;
	struct dp_hpd base;
	struct msm_dp_aux_bridge *bridge;
	struct delayed_work work;
	struct dp_hpd_cb *cb;
	bool hpd;
	bool hpd_irq;
	struct mutex hpd_lock;
};

static int dp_bridge_hpd_connect(struct dp_bridge_hpd_private *bridge_hpd,
		bool hpd)
{
	int rc = 0;

	if (!bridge_hpd) {
		pr_err("invalid input\n");
		rc = -EINVAL;
		goto error;
	}

	bridge_hpd->base.hpd_high = hpd;
	bridge_hpd->base.alt_mode_cfg_done = hpd;
	bridge_hpd->base.hpd_irq = false;

	if (!bridge_hpd->cb ||
		!bridge_hpd->cb->configure ||
		!bridge_hpd->cb->disconnect) {
		pr_err("invalid cb\n");
		rc = -EINVAL;
		goto error;
	}

	if (hpd)
		rc = bridge_hpd->cb->configure(bridge_hpd->dev);
	else
		rc = bridge_hpd->cb->disconnect(bridge_hpd->dev);

error:
	return rc;
}

static int dp_bridge_hpd_attention(struct dp_bridge_hpd_private *bridge_hpd)
{
	int rc = 0;

	if (!bridge_hpd) {
		pr_err("invalid input\n");
		rc = -EINVAL;
		goto error;
	}

	bridge_hpd->base.hpd_irq = true;

	if (bridge_hpd->cb && bridge_hpd->cb->attention)
		rc = bridge_hpd->cb->attention(bridge_hpd->dev);

error:
	return rc;
}

static void dp_bridge_hpd_work(struct work_struct *work)
{
	struct delayed_work *dw = to_delayed_work(work);
	struct dp_bridge_hpd_private *bridge_hpd = container_of(dw,
		struct dp_bridge_hpd_private, work);

	mutex_lock(&bridge_hpd->hpd_lock);

	if (bridge_hpd->hpd_irq)
		dp_bridge_hpd_attention(bridge_hpd);
	else
		dp_bridge_hpd_connect(bridge_hpd, bridge_hpd->hpd);

	mutex_unlock(&bridge_hpd->hpd_lock);
}

static int dp_bridge_hpd_simulate_connect(struct dp_hpd *dp_hpd, bool hpd)
{
	int rc = 0;
	struct dp_bridge_hpd_private *bridge_hpd;

	if (!dp_hpd) {
		pr_err("invalid input\n");
		rc = -EINVAL;
		goto error;
	}

	bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base);

	dp_bridge_hpd_connect(bridge_hpd, hpd);
error:
	return rc;
}

static int dp_bridge_hpd_simulate_attention(struct dp_hpd *dp_hpd, int vdo)
{
	int rc = 0;
	struct dp_bridge_hpd_private *bridge_hpd;

	if (!dp_hpd) {
		pr_err("invalid input\n");
		rc = -EINVAL;
		goto error;
	}

	bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base);

	dp_bridge_hpd_attention(bridge_hpd);
error:
	return rc;
}

static int dp_bridge_hpd_cb(void *dp_hpd, bool hpd, bool hpd_irq)
{
	struct dp_bridge_hpd_private *bridge_hpd = dp_hpd;

	mutex_lock(&bridge_hpd->hpd_lock);

	bridge_hpd->hpd = hpd;
	bridge_hpd->hpd_irq = hpd_irq;
	queue_delayed_work(system_wq, &bridge_hpd->work, 0);

	mutex_unlock(&bridge_hpd->hpd_lock);

	return 0;
}

static int dp_bridge_hpd_register(struct dp_hpd *dp_hpd)
{
	struct dp_bridge_hpd_private *bridge_hpd;

	if (!dp_hpd)
		return -EINVAL;

	bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base);

	return bridge_hpd->bridge->register_hpd(bridge_hpd->bridge,
			dp_bridge_hpd_cb, bridge_hpd);
}

struct dp_hpd *dp_bridge_hpd_get(struct device *dev,
	struct dp_hpd_cb *cb, struct msm_dp_aux_bridge *aux_bridge)
{
	int rc = 0;
	struct dp_bridge_hpd_private *bridge_hpd;

	if (!dev || !cb) {
		pr_err("invalid device\n");
		rc = -EINVAL;
		goto error;
	}

	bridge_hpd = devm_kzalloc(dev, sizeof(*bridge_hpd), GFP_KERNEL);
	if (!bridge_hpd) {
		rc = -ENOMEM;
		goto error;
	}

	bridge_hpd->dev = dev;
	bridge_hpd->cb = cb;
	bridge_hpd->bridge = aux_bridge;
	mutex_init(&bridge_hpd->hpd_lock);
	INIT_DELAYED_WORK(&bridge_hpd->work, dp_bridge_hpd_work);
	bridge_hpd->base.simulate_connect = dp_bridge_hpd_simulate_connect;
	bridge_hpd->base.simulate_attention = dp_bridge_hpd_simulate_attention;
	bridge_hpd->base.register_hpd = dp_bridge_hpd_register;

	return &bridge_hpd->base;
error:
	return ERR_PTR(rc);
}

void dp_bridge_hpd_put(struct dp_hpd *dp_hpd)
{
	struct dp_bridge_hpd_private *bridge_hpd;

	if (!dp_hpd)
		return;

	bridge_hpd = container_of(dp_hpd, struct dp_bridge_hpd_private, base);

	devm_kfree(bridge_hpd->dev, bridge_hpd);
}
Loading