
Create the framework to support external dp aux bridge device which can handle DPCD/I2C/HPD from external. Change-Id: Iabd0998efc8bf7134d186b1751d219c00217385c Signed-off-by: Xiaowen Wu <wxiaowen@codeaurora.org> Signed-off-by: Karim Henain <khenain@codeaurora.org> Signed-off-by: Sudarsan Ramesh <sudarame@codeaurora.org>
209 lines
4.6 KiB
C
209 lines
4.6 KiB
C
/*
|
|
* Copyright (c) 2019-2021, 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 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 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);
|
|
}
|