File Index
Files and directories
Sign in
FILE

hdmi_hpd.c

File preview with syntax highlighting.

linux-6.19.14/drivers/gpu/drm/msm/hdmi/hdmi_hpd.c C
  1// SPDX-License-Identifier: GPL-2.0-only
  2/*
  3 * Copyright (C) 2013 Red Hat
  4 * Author: Rob Clark <robdclark@gmail.com>
  5 */
  6
  7#include <linux/delay.h>
  8#include <linux/gpio/consumer.h>
  9#include <linux/pinctrl/consumer.h>
 10
 11#include "msm_kms.h"
 12#include "hdmi.h"
 13
 14static void msm_hdmi_phy_reset(struct hdmi *hdmi)
 15{
 16	unsigned int val;
 17
 18	val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL);
 19
 20	if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
 21		/* pull low */
 22		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
 23				val & ~HDMI_PHY_CTRL_SW_RESET);
 24	} else {
 25		/* pull high */
 26		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
 27				val | HDMI_PHY_CTRL_SW_RESET);
 28	}
 29
 30	if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
 31		/* pull low */
 32		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
 33				val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
 34	} else {
 35		/* pull high */
 36		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
 37				val | HDMI_PHY_CTRL_SW_RESET_PLL);
 38	}
 39
 40	msleep(100);
 41
 42	if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
 43		/* pull high */
 44		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
 45				val | HDMI_PHY_CTRL_SW_RESET);
 46	} else {
 47		/* pull low */
 48		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
 49				val & ~HDMI_PHY_CTRL_SW_RESET);
 50	}
 51
 52	if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
 53		/* pull high */
 54		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
 55				val | HDMI_PHY_CTRL_SW_RESET_PLL);
 56	} else {
 57		/* pull low */
 58		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
 59				val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
 60	}
 61}
 62
 63void msm_hdmi_hpd_enable(struct drm_bridge *bridge)
 64{
 65	struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
 66	struct hdmi *hdmi = hdmi_bridge->hdmi;
 67	struct device *dev = &hdmi->pdev->dev;
 68	uint32_t hpd_ctrl;
 69	int ret;
 70	unsigned long flags;
 71
 72	if (hdmi->hpd_gpiod)
 73		gpiod_set_value_cansleep(hdmi->hpd_gpiod, 1);
 74
 75	ret = pm_runtime_resume_and_get(dev);
 76	if (WARN_ON(ret))
 77		return;
 78
 79	mutex_lock(&hdmi->state_mutex);
 80	msm_hdmi_set_mode(hdmi, false);
 81	msm_hdmi_phy_reset(hdmi);
 82	msm_hdmi_set_mode(hdmi, true);
 83
 84	hdmi->hpd_enabled = true;
 85	mutex_unlock(&hdmi->state_mutex);
 86
 87	hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, 0x0001001b);
 88
 89	/* enable HPD events: */
 90	hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL,
 91			HDMI_HPD_INT_CTRL_INT_CONNECT |
 92			HDMI_HPD_INT_CTRL_INT_EN);
 93
 94	/* set timeout to 4.1ms (max) for hardware debounce */
 95	spin_lock_irqsave(&hdmi->reg_lock, flags);
 96	hpd_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
 97	hpd_ctrl |= HDMI_HPD_CTRL_TIMEOUT(0x1fff);
 98
 99	/* Toggle HPD circuit to trigger HPD sense */
100	hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
101			~HDMI_HPD_CTRL_ENABLE & hpd_ctrl);
102	hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
103			HDMI_HPD_CTRL_ENABLE | hpd_ctrl);
104	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
105}
106
107void msm_hdmi_hpd_disable(struct drm_bridge *bridge)
108{
109	struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
110	struct hdmi *hdmi = hdmi_bridge->hdmi;
111	struct device *dev = &hdmi->pdev->dev;
112
113	/* Disable HPD interrupt */
114	hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0);
115
116	mutex_lock(&hdmi->state_mutex);
117	hdmi->hpd_enabled = false;
118	msm_hdmi_set_mode(hdmi, hdmi->power_on);
119	mutex_unlock(&hdmi->state_mutex);
120
121	pm_runtime_put(dev);
122}
123
124void msm_hdmi_hpd_irq(struct drm_bridge *bridge)
125{
126	struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
127	struct hdmi *hdmi = hdmi_bridge->hdmi;
128	uint32_t hpd_int_status, hpd_int_ctrl;
129
130	/* Process HPD: */
131	hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS);
132	hpd_int_ctrl   = hdmi_read(hdmi, REG_HDMI_HPD_INT_CTRL);
133
134	if ((hpd_int_ctrl & HDMI_HPD_INT_CTRL_INT_EN) &&
135			(hpd_int_status & HDMI_HPD_INT_STATUS_INT)) {
136		bool detected = !!(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED);
137
138		/* ack & disable (temporarily) HPD events: */
139		hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL,
140			HDMI_HPD_INT_CTRL_INT_ACK);
141
142		DBG("status=%04x, ctrl=%04x", hpd_int_status, hpd_int_ctrl);
143
144		/* detect disconnect if we are connected or visa versa: */
145		hpd_int_ctrl = HDMI_HPD_INT_CTRL_INT_EN;
146		if (!detected)
147			hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT;
148		hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl);
149
150		queue_work(hdmi->workq, &hdmi_bridge->hpd_work);
151	}
152}
153
154static enum drm_connector_status detect_reg(struct hdmi *hdmi)
155{
156	u32 hpd_int_status = 0;
157	int ret;
158
159	ret = pm_runtime_resume_and_get(&hdmi->pdev->dev);
160	if (ret)
161		goto out;
162
163	hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS);
164
165out:
166	pm_runtime_put(&hdmi->pdev->dev);
167
168	return (hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED) ?
169			connector_status_connected : connector_status_disconnected;
170}
171
172#define HPD_GPIO_INDEX	2
173static enum drm_connector_status detect_gpio(struct hdmi *hdmi)
174{
175	return gpiod_get_value(hdmi->hpd_gpiod) ?
176			connector_status_connected :
177			connector_status_disconnected;
178}
179
180enum drm_connector_status
181msm_hdmi_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector)
182{
183	struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
184	struct hdmi *hdmi = hdmi_bridge->hdmi;
185	enum drm_connector_status stat_gpio, stat_reg;
186	int retry = 20;
187
188	/*
189	 * some platforms may not have hpd gpio. Rely only on the status
190	 * provided by REG_HDMI_HPD_INT_STATUS in this case.
191	 */
192	if (!hdmi->hpd_gpiod)
193		return detect_reg(hdmi);
194
195	do {
196		stat_gpio = detect_gpio(hdmi);
197		stat_reg  = detect_reg(hdmi);
198
199		if (stat_gpio == stat_reg)
200			break;
201
202		mdelay(10);
203	} while (--retry);
204
205	/* the status we get from reading gpio seems to be more reliable,
206	 * so trust that one the most if we didn't manage to get hdmi and
207	 * gpio status to agree:
208	 */
209	if (stat_gpio != stat_reg) {
210		DBG("HDMI_HPD_INT_STATUS tells us: %d", stat_reg);
211		DBG("hpd gpio tells us: %d", stat_gpio);
212	}
213
214	return stat_gpio;
215}
~/ Read-only file preview.