aacd9e9585
Adding prefixes for error, debug and info messages in dp files. To enable debug logs run "echo 0x100 > /sys/module/drm/parameters/debug" CRs-Fixed: 2493739 Change-Id: Ibf509e837f527be6bff6b7a1c34b0cde2921b388 Signed-off-by: Satya Rama Aditya Pinapala <psraditya30@codeaurora.org>
297 lines
6.5 KiB
C
297 lines
6.5 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2016-2019, The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#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 <linux/gpio/consumer.h>
|
|
#include <linux/sde_io_util.h>
|
|
#include <linux/of_gpio.h>
|
|
#include "dp_gpio_hpd.h"
|
|
#include "dp_debug.h"
|
|
|
|
struct dp_gpio_hpd_private {
|
|
struct device *dev;
|
|
struct dp_hpd base;
|
|
struct dss_gpio gpio_cfg;
|
|
struct delayed_work work;
|
|
struct dp_hpd_cb *cb;
|
|
int irq;
|
|
bool hpd;
|
|
};
|
|
|
|
static int dp_gpio_hpd_connect(struct dp_gpio_hpd_private *gpio_hpd, bool hpd)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!gpio_hpd) {
|
|
DP_ERR("invalid input\n");
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
gpio_hpd->base.hpd_high = hpd;
|
|
gpio_hpd->base.alt_mode_cfg_done = hpd;
|
|
gpio_hpd->base.hpd_irq = false;
|
|
|
|
if (!gpio_hpd->cb ||
|
|
!gpio_hpd->cb->configure ||
|
|
!gpio_hpd->cb->disconnect) {
|
|
DP_ERR("invalid cb\n");
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
if (hpd)
|
|
rc = gpio_hpd->cb->configure(gpio_hpd->dev);
|
|
else
|
|
rc = gpio_hpd->cb->disconnect(gpio_hpd->dev);
|
|
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
static int dp_gpio_hpd_attention(struct dp_gpio_hpd_private *gpio_hpd)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!gpio_hpd) {
|
|
DP_ERR("invalid input\n");
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
gpio_hpd->base.hpd_irq = true;
|
|
|
|
if (gpio_hpd->cb && gpio_hpd->cb->attention)
|
|
rc = gpio_hpd->cb->attention(gpio_hpd->dev);
|
|
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
static irqreturn_t dp_gpio_isr(int unused, void *data)
|
|
{
|
|
struct dp_gpio_hpd_private *gpio_hpd = data;
|
|
u32 const disconnect_timeout_retry = 50;
|
|
bool hpd;
|
|
int i;
|
|
|
|
if (!gpio_hpd)
|
|
return IRQ_NONE;
|
|
|
|
hpd = gpio_get_value_cansleep(gpio_hpd->gpio_cfg.gpio);
|
|
|
|
if (!gpio_hpd->hpd && hpd) {
|
|
gpio_hpd->hpd = true;
|
|
queue_delayed_work(system_wq, &gpio_hpd->work, 0);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
if (!gpio_hpd->hpd)
|
|
return IRQ_HANDLED;
|
|
|
|
/* In DP 1.2 spec, 100msec is recommended for the detection
|
|
* of HPD connect event. Here we'll poll HPD status for
|
|
* 50x2ms = 100ms and if HPD is always low, we know DP is
|
|
* disconnected. If HPD is high, HPD_IRQ will be handled
|
|
*/
|
|
for (i = 0; i < disconnect_timeout_retry; i++) {
|
|
if (hpd) {
|
|
dp_gpio_hpd_attention(gpio_hpd);
|
|
return IRQ_HANDLED;
|
|
}
|
|
usleep_range(2000, 2100);
|
|
hpd = gpio_get_value_cansleep(gpio_hpd->gpio_cfg.gpio);
|
|
}
|
|
|
|
gpio_hpd->hpd = false;
|
|
queue_delayed_work(system_wq, &gpio_hpd->work, 0);
|
|
return IRQ_HANDLED;
|
|
}
|
|
|
|
static void dp_gpio_hpd_work(struct work_struct *work)
|
|
{
|
|
struct delayed_work *dw = to_delayed_work(work);
|
|
struct dp_gpio_hpd_private *gpio_hpd = container_of(dw,
|
|
struct dp_gpio_hpd_private, work);
|
|
int ret;
|
|
|
|
if (gpio_hpd->hpd) {
|
|
devm_free_irq(gpio_hpd->dev,
|
|
gpio_hpd->irq, gpio_hpd);
|
|
ret = devm_request_threaded_irq(gpio_hpd->dev,
|
|
gpio_hpd->irq, NULL,
|
|
dp_gpio_isr,
|
|
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
|
"dp-gpio-intp", gpio_hpd);
|
|
dp_gpio_hpd_connect(gpio_hpd, true);
|
|
} else {
|
|
devm_free_irq(gpio_hpd->dev,
|
|
gpio_hpd->irq, gpio_hpd);
|
|
ret = devm_request_threaded_irq(gpio_hpd->dev,
|
|
gpio_hpd->irq, NULL,
|
|
dp_gpio_isr,
|
|
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
|
|
"dp-gpio-intp", gpio_hpd);
|
|
dp_gpio_hpd_connect(gpio_hpd, false);
|
|
}
|
|
|
|
if (ret < 0)
|
|
DP_ERR("Cannot claim IRQ dp-gpio-intp\n");
|
|
}
|
|
|
|
static int dp_gpio_hpd_simulate_connect(struct dp_hpd *dp_hpd, bool hpd)
|
|
{
|
|
int rc = 0;
|
|
struct dp_gpio_hpd_private *gpio_hpd;
|
|
|
|
if (!dp_hpd) {
|
|
DP_ERR("invalid input\n");
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
gpio_hpd = container_of(dp_hpd, struct dp_gpio_hpd_private, base);
|
|
|
|
dp_gpio_hpd_connect(gpio_hpd, hpd);
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
static int dp_gpio_hpd_simulate_attention(struct dp_hpd *dp_hpd, int vdo)
|
|
{
|
|
int rc = 0;
|
|
struct dp_gpio_hpd_private *gpio_hpd;
|
|
|
|
if (!dp_hpd) {
|
|
DP_ERR("invalid input\n");
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
gpio_hpd = container_of(dp_hpd, struct dp_gpio_hpd_private, base);
|
|
|
|
dp_gpio_hpd_attention(gpio_hpd);
|
|
error:
|
|
return rc;
|
|
}
|
|
|
|
int dp_gpio_hpd_register(struct dp_hpd *dp_hpd)
|
|
{
|
|
struct dp_gpio_hpd_private *gpio_hpd;
|
|
int edge;
|
|
int rc = 0;
|
|
|
|
if (!dp_hpd)
|
|
return -EINVAL;
|
|
|
|
gpio_hpd = container_of(dp_hpd, struct dp_gpio_hpd_private, base);
|
|
|
|
gpio_hpd->hpd = gpio_get_value_cansleep(gpio_hpd->gpio_cfg.gpio);
|
|
|
|
edge = gpio_hpd->hpd ? IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
|
|
rc = devm_request_threaded_irq(gpio_hpd->dev, gpio_hpd->irq, NULL,
|
|
dp_gpio_isr,
|
|
edge | IRQF_ONESHOT,
|
|
"dp-gpio-intp", gpio_hpd);
|
|
if (rc) {
|
|
DP_ERR("Failed to request INTP threaded IRQ: %d\n", rc);
|
|
return rc;
|
|
}
|
|
|
|
if (gpio_hpd->hpd)
|
|
queue_delayed_work(system_wq, &gpio_hpd->work, 0);
|
|
|
|
return rc;
|
|
}
|
|
|
|
struct dp_hpd *dp_gpio_hpd_get(struct device *dev,
|
|
struct dp_hpd_cb *cb)
|
|
{
|
|
int rc = 0;
|
|
const char *hpd_gpio_name = "qcom,dp-hpd-gpio";
|
|
struct dp_gpio_hpd_private *gpio_hpd;
|
|
struct dp_pinctrl pinctrl = {0};
|
|
|
|
if (!dev || !cb) {
|
|
DP_ERR("invalid device\n");
|
|
rc = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
gpio_hpd = devm_kzalloc(dev, sizeof(*gpio_hpd), GFP_KERNEL);
|
|
if (!gpio_hpd) {
|
|
rc = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
pinctrl.pin = devm_pinctrl_get(dev);
|
|
if (!IS_ERR_OR_NULL(pinctrl.pin)) {
|
|
pinctrl.state_hpd_active = pinctrl_lookup_state(pinctrl.pin,
|
|
"mdss_dp_hpd_active");
|
|
if (!IS_ERR_OR_NULL(pinctrl.state_hpd_active)) {
|
|
rc = pinctrl_select_state(pinctrl.pin,
|
|
pinctrl.state_hpd_active);
|
|
if (rc) {
|
|
DP_ERR("failed to set hpd active state\n");
|
|
goto gpio_error;
|
|
}
|
|
}
|
|
}
|
|
|
|
gpio_hpd->gpio_cfg.gpio = of_get_named_gpio(dev->of_node,
|
|
hpd_gpio_name, 0);
|
|
if (!gpio_is_valid(gpio_hpd->gpio_cfg.gpio)) {
|
|
DP_ERR("%s gpio not specified\n", hpd_gpio_name);
|
|
rc = -EINVAL;
|
|
goto gpio_error;
|
|
}
|
|
|
|
strlcpy(gpio_hpd->gpio_cfg.gpio_name, hpd_gpio_name,
|
|
sizeof(gpio_hpd->gpio_cfg.gpio_name));
|
|
gpio_hpd->gpio_cfg.value = 0;
|
|
|
|
rc = gpio_request(gpio_hpd->gpio_cfg.gpio,
|
|
gpio_hpd->gpio_cfg.gpio_name);
|
|
if (rc) {
|
|
DP_ERR("%s: failed to request gpio\n", hpd_gpio_name);
|
|
goto gpio_error;
|
|
}
|
|
gpio_direction_input(gpio_hpd->gpio_cfg.gpio);
|
|
|
|
gpio_hpd->dev = dev;
|
|
gpio_hpd->cb = cb;
|
|
gpio_hpd->irq = gpio_to_irq(gpio_hpd->gpio_cfg.gpio);
|
|
INIT_DELAYED_WORK(&gpio_hpd->work, dp_gpio_hpd_work);
|
|
|
|
gpio_hpd->base.simulate_connect = dp_gpio_hpd_simulate_connect;
|
|
gpio_hpd->base.simulate_attention = dp_gpio_hpd_simulate_attention;
|
|
gpio_hpd->base.register_hpd = dp_gpio_hpd_register;
|
|
|
|
return &gpio_hpd->base;
|
|
|
|
gpio_error:
|
|
devm_kfree(dev, gpio_hpd);
|
|
error:
|
|
return ERR_PTR(rc);
|
|
}
|
|
|
|
void dp_gpio_hpd_put(struct dp_hpd *dp_hpd)
|
|
{
|
|
struct dp_gpio_hpd_private *gpio_hpd;
|
|
|
|
if (!dp_hpd)
|
|
return;
|
|
|
|
gpio_hpd = container_of(dp_hpd, struct dp_gpio_hpd_private, base);
|
|
|
|
gpio_free(gpio_hpd->gpio_cfg.gpio);
|
|
devm_kfree(gpio_hpd->dev, gpio_hpd);
|
|
}
|