18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Copyright (C) 2013 Red Hat
48c2ecf20Sopenharmony_ci * Author: Rob Clark <robdclark@gmail.com>
58c2ecf20Sopenharmony_ci */
68c2ecf20Sopenharmony_ci
78c2ecf20Sopenharmony_ci#include <linux/delay.h>
88c2ecf20Sopenharmony_ci#include <linux/gpio/consumer.h>
98c2ecf20Sopenharmony_ci#include <linux/pinctrl/consumer.h>
108c2ecf20Sopenharmony_ci
118c2ecf20Sopenharmony_ci#include "msm_kms.h"
128c2ecf20Sopenharmony_ci#include "hdmi.h"
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_cistatic void msm_hdmi_phy_reset(struct hdmi *hdmi)
158c2ecf20Sopenharmony_ci{
168c2ecf20Sopenharmony_ci	unsigned int val;
178c2ecf20Sopenharmony_ci
188c2ecf20Sopenharmony_ci	val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL);
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci	if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
218c2ecf20Sopenharmony_ci		/* pull low */
228c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
238c2ecf20Sopenharmony_ci				val & ~HDMI_PHY_CTRL_SW_RESET);
248c2ecf20Sopenharmony_ci	} else {
258c2ecf20Sopenharmony_ci		/* pull high */
268c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
278c2ecf20Sopenharmony_ci				val | HDMI_PHY_CTRL_SW_RESET);
288c2ecf20Sopenharmony_ci	}
298c2ecf20Sopenharmony_ci
308c2ecf20Sopenharmony_ci	if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
318c2ecf20Sopenharmony_ci		/* pull low */
328c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
338c2ecf20Sopenharmony_ci				val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
348c2ecf20Sopenharmony_ci	} else {
358c2ecf20Sopenharmony_ci		/* pull high */
368c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
378c2ecf20Sopenharmony_ci				val | HDMI_PHY_CTRL_SW_RESET_PLL);
388c2ecf20Sopenharmony_ci	}
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	msleep(100);
418c2ecf20Sopenharmony_ci
428c2ecf20Sopenharmony_ci	if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
438c2ecf20Sopenharmony_ci		/* pull high */
448c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
458c2ecf20Sopenharmony_ci				val | HDMI_PHY_CTRL_SW_RESET);
468c2ecf20Sopenharmony_ci	} else {
478c2ecf20Sopenharmony_ci		/* pull low */
488c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
498c2ecf20Sopenharmony_ci				val & ~HDMI_PHY_CTRL_SW_RESET);
508c2ecf20Sopenharmony_ci	}
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
538c2ecf20Sopenharmony_ci		/* pull high */
548c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
558c2ecf20Sopenharmony_ci				val | HDMI_PHY_CTRL_SW_RESET_PLL);
568c2ecf20Sopenharmony_ci	} else {
578c2ecf20Sopenharmony_ci		/* pull low */
588c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
598c2ecf20Sopenharmony_ci				val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
608c2ecf20Sopenharmony_ci	}
618c2ecf20Sopenharmony_ci}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistatic void enable_hpd_clocks(struct hdmi *hdmi, bool enable)
648c2ecf20Sopenharmony_ci{
658c2ecf20Sopenharmony_ci	const struct hdmi_platform_config *config = hdmi->config;
668c2ecf20Sopenharmony_ci	struct device *dev = &hdmi->pdev->dev;
678c2ecf20Sopenharmony_ci	int i, ret;
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci	if (enable) {
708c2ecf20Sopenharmony_ci		for (i = 0; i < config->hpd_clk_cnt; i++) {
718c2ecf20Sopenharmony_ci			if (config->hpd_freq && config->hpd_freq[i]) {
728c2ecf20Sopenharmony_ci				ret = clk_set_rate(hdmi->hpd_clks[i],
738c2ecf20Sopenharmony_ci						   config->hpd_freq[i]);
748c2ecf20Sopenharmony_ci				if (ret)
758c2ecf20Sopenharmony_ci					dev_warn(dev,
768c2ecf20Sopenharmony_ci						 "failed to set clk %s (%d)\n",
778c2ecf20Sopenharmony_ci						 config->hpd_clk_names[i], ret);
788c2ecf20Sopenharmony_ci			}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_ci			ret = clk_prepare_enable(hdmi->hpd_clks[i]);
818c2ecf20Sopenharmony_ci			if (ret) {
828c2ecf20Sopenharmony_ci				DRM_DEV_ERROR(dev,
838c2ecf20Sopenharmony_ci					"failed to enable hpd clk: %s (%d)\n",
848c2ecf20Sopenharmony_ci					config->hpd_clk_names[i], ret);
858c2ecf20Sopenharmony_ci			}
868c2ecf20Sopenharmony_ci		}
878c2ecf20Sopenharmony_ci	} else {
888c2ecf20Sopenharmony_ci		for (i = config->hpd_clk_cnt - 1; i >= 0; i--)
898c2ecf20Sopenharmony_ci			clk_disable_unprepare(hdmi->hpd_clks[i]);
908c2ecf20Sopenharmony_ci	}
918c2ecf20Sopenharmony_ci}
928c2ecf20Sopenharmony_ci
938c2ecf20Sopenharmony_ciint msm_hdmi_hpd_enable(struct drm_bridge *bridge)
948c2ecf20Sopenharmony_ci{
958c2ecf20Sopenharmony_ci	struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
968c2ecf20Sopenharmony_ci	struct hdmi *hdmi = hdmi_bridge->hdmi;
978c2ecf20Sopenharmony_ci	const struct hdmi_platform_config *config = hdmi->config;
988c2ecf20Sopenharmony_ci	struct device *dev = &hdmi->pdev->dev;
998c2ecf20Sopenharmony_ci	uint32_t hpd_ctrl;
1008c2ecf20Sopenharmony_ci	int i, ret;
1018c2ecf20Sopenharmony_ci	unsigned long flags;
1028c2ecf20Sopenharmony_ci
1038c2ecf20Sopenharmony_ci	for (i = 0; i < config->hpd_reg_cnt; i++) {
1048c2ecf20Sopenharmony_ci		ret = regulator_enable(hdmi->hpd_regs[i]);
1058c2ecf20Sopenharmony_ci		if (ret) {
1068c2ecf20Sopenharmony_ci			DRM_DEV_ERROR(dev, "failed to enable hpd regulator: %s (%d)\n",
1078c2ecf20Sopenharmony_ci					config->hpd_reg_names[i], ret);
1088c2ecf20Sopenharmony_ci			goto fail;
1098c2ecf20Sopenharmony_ci		}
1108c2ecf20Sopenharmony_ci	}
1118c2ecf20Sopenharmony_ci
1128c2ecf20Sopenharmony_ci	ret = pinctrl_pm_select_default_state(dev);
1138c2ecf20Sopenharmony_ci	if (ret) {
1148c2ecf20Sopenharmony_ci		DRM_DEV_ERROR(dev, "pinctrl state chg failed: %d\n", ret);
1158c2ecf20Sopenharmony_ci		goto fail;
1168c2ecf20Sopenharmony_ci	}
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci	if (hdmi->hpd_gpiod)
1198c2ecf20Sopenharmony_ci		gpiod_set_value_cansleep(hdmi->hpd_gpiod, 1);
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci	pm_runtime_get_sync(dev);
1228c2ecf20Sopenharmony_ci	enable_hpd_clocks(hdmi, true);
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	msm_hdmi_set_mode(hdmi, false);
1258c2ecf20Sopenharmony_ci	msm_hdmi_phy_reset(hdmi);
1268c2ecf20Sopenharmony_ci	msm_hdmi_set_mode(hdmi, true);
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci	hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, 0x0001001b);
1298c2ecf20Sopenharmony_ci
1308c2ecf20Sopenharmony_ci	/* enable HPD events: */
1318c2ecf20Sopenharmony_ci	hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL,
1328c2ecf20Sopenharmony_ci			HDMI_HPD_INT_CTRL_INT_CONNECT |
1338c2ecf20Sopenharmony_ci			HDMI_HPD_INT_CTRL_INT_EN);
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	/* set timeout to 4.1ms (max) for hardware debounce */
1368c2ecf20Sopenharmony_ci	spin_lock_irqsave(&hdmi->reg_lock, flags);
1378c2ecf20Sopenharmony_ci	hpd_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
1388c2ecf20Sopenharmony_ci	hpd_ctrl |= HDMI_HPD_CTRL_TIMEOUT(0x1fff);
1398c2ecf20Sopenharmony_ci
1408c2ecf20Sopenharmony_ci	/* Toggle HPD circuit to trigger HPD sense */
1418c2ecf20Sopenharmony_ci	hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
1428c2ecf20Sopenharmony_ci			~HDMI_HPD_CTRL_ENABLE & hpd_ctrl);
1438c2ecf20Sopenharmony_ci	hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
1448c2ecf20Sopenharmony_ci			HDMI_HPD_CTRL_ENABLE | hpd_ctrl);
1458c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&hdmi->reg_lock, flags);
1468c2ecf20Sopenharmony_ci
1478c2ecf20Sopenharmony_ci	return 0;
1488c2ecf20Sopenharmony_ci
1498c2ecf20Sopenharmony_cifail:
1508c2ecf20Sopenharmony_ci	return ret;
1518c2ecf20Sopenharmony_ci}
1528c2ecf20Sopenharmony_ci
1538c2ecf20Sopenharmony_civoid msm_hdmi_hpd_disable(struct hdmi_bridge *hdmi_bridge)
1548c2ecf20Sopenharmony_ci{
1558c2ecf20Sopenharmony_ci	struct hdmi *hdmi = hdmi_bridge->hdmi;
1568c2ecf20Sopenharmony_ci	const struct hdmi_platform_config *config = hdmi->config;
1578c2ecf20Sopenharmony_ci	struct device *dev = &hdmi->pdev->dev;
1588c2ecf20Sopenharmony_ci	int i, ret = 0;
1598c2ecf20Sopenharmony_ci
1608c2ecf20Sopenharmony_ci	/* Disable HPD interrupt */
1618c2ecf20Sopenharmony_ci	hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0);
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_ci	msm_hdmi_set_mode(hdmi, false);
1648c2ecf20Sopenharmony_ci
1658c2ecf20Sopenharmony_ci	enable_hpd_clocks(hdmi, false);
1668c2ecf20Sopenharmony_ci	pm_runtime_put_autosuspend(dev);
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	ret = pinctrl_pm_select_sleep_state(dev);
1698c2ecf20Sopenharmony_ci	if (ret)
1708c2ecf20Sopenharmony_ci		dev_warn(dev, "pinctrl state chg failed: %d\n", ret);
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_ci	for (i = 0; i < config->hpd_reg_cnt; i++) {
1738c2ecf20Sopenharmony_ci		ret = regulator_disable(hdmi->hpd_regs[i]);
1748c2ecf20Sopenharmony_ci		if (ret)
1758c2ecf20Sopenharmony_ci			dev_warn(dev, "failed to disable hpd regulator: %s (%d)\n",
1768c2ecf20Sopenharmony_ci					config->hpd_reg_names[i], ret);
1778c2ecf20Sopenharmony_ci	}
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_civoid msm_hdmi_hpd_irq(struct drm_bridge *bridge)
1818c2ecf20Sopenharmony_ci{
1828c2ecf20Sopenharmony_ci	struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
1838c2ecf20Sopenharmony_ci	struct hdmi *hdmi = hdmi_bridge->hdmi;
1848c2ecf20Sopenharmony_ci	uint32_t hpd_int_status, hpd_int_ctrl;
1858c2ecf20Sopenharmony_ci
1868c2ecf20Sopenharmony_ci	/* Process HPD: */
1878c2ecf20Sopenharmony_ci	hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS);
1888c2ecf20Sopenharmony_ci	hpd_int_ctrl   = hdmi_read(hdmi, REG_HDMI_HPD_INT_CTRL);
1898c2ecf20Sopenharmony_ci
1908c2ecf20Sopenharmony_ci	if ((hpd_int_ctrl & HDMI_HPD_INT_CTRL_INT_EN) &&
1918c2ecf20Sopenharmony_ci			(hpd_int_status & HDMI_HPD_INT_STATUS_INT)) {
1928c2ecf20Sopenharmony_ci		bool detected = !!(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED);
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_ci		/* ack & disable (temporarily) HPD events: */
1958c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL,
1968c2ecf20Sopenharmony_ci			HDMI_HPD_INT_CTRL_INT_ACK);
1978c2ecf20Sopenharmony_ci
1988c2ecf20Sopenharmony_ci		DBG("status=%04x, ctrl=%04x", hpd_int_status, hpd_int_ctrl);
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci		/* detect disconnect if we are connected or visa versa: */
2018c2ecf20Sopenharmony_ci		hpd_int_ctrl = HDMI_HPD_INT_CTRL_INT_EN;
2028c2ecf20Sopenharmony_ci		if (!detected)
2038c2ecf20Sopenharmony_ci			hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT;
2048c2ecf20Sopenharmony_ci		hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl);
2058c2ecf20Sopenharmony_ci
2068c2ecf20Sopenharmony_ci		queue_work(hdmi->workq, &hdmi_bridge->hpd_work);
2078c2ecf20Sopenharmony_ci	}
2088c2ecf20Sopenharmony_ci}
2098c2ecf20Sopenharmony_ci
2108c2ecf20Sopenharmony_cistatic enum drm_connector_status detect_reg(struct hdmi *hdmi)
2118c2ecf20Sopenharmony_ci{
2128c2ecf20Sopenharmony_ci	uint32_t hpd_int_status;
2138c2ecf20Sopenharmony_ci
2148c2ecf20Sopenharmony_ci	pm_runtime_get_sync(&hdmi->pdev->dev);
2158c2ecf20Sopenharmony_ci	enable_hpd_clocks(hdmi, true);
2168c2ecf20Sopenharmony_ci
2178c2ecf20Sopenharmony_ci	hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS);
2188c2ecf20Sopenharmony_ci
2198c2ecf20Sopenharmony_ci	enable_hpd_clocks(hdmi, false);
2208c2ecf20Sopenharmony_ci	pm_runtime_put_autosuspend(&hdmi->pdev->dev);
2218c2ecf20Sopenharmony_ci
2228c2ecf20Sopenharmony_ci	return (hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED) ?
2238c2ecf20Sopenharmony_ci			connector_status_connected : connector_status_disconnected;
2248c2ecf20Sopenharmony_ci}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_ci#define HPD_GPIO_INDEX	2
2278c2ecf20Sopenharmony_cistatic enum drm_connector_status detect_gpio(struct hdmi *hdmi)
2288c2ecf20Sopenharmony_ci{
2298c2ecf20Sopenharmony_ci	return gpiod_get_value(hdmi->hpd_gpiod) ?
2308c2ecf20Sopenharmony_ci			connector_status_connected :
2318c2ecf20Sopenharmony_ci			connector_status_disconnected;
2328c2ecf20Sopenharmony_ci}
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_cienum drm_connector_status msm_hdmi_bridge_detect(
2358c2ecf20Sopenharmony_ci		struct drm_bridge *bridge)
2368c2ecf20Sopenharmony_ci{
2378c2ecf20Sopenharmony_ci	struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
2388c2ecf20Sopenharmony_ci	struct hdmi *hdmi = hdmi_bridge->hdmi;
2398c2ecf20Sopenharmony_ci	enum drm_connector_status stat_gpio, stat_reg;
2408c2ecf20Sopenharmony_ci	int retry = 20;
2418c2ecf20Sopenharmony_ci
2428c2ecf20Sopenharmony_ci	/*
2438c2ecf20Sopenharmony_ci	 * some platforms may not have hpd gpio. Rely only on the status
2448c2ecf20Sopenharmony_ci	 * provided by REG_HDMI_HPD_INT_STATUS in this case.
2458c2ecf20Sopenharmony_ci	 */
2468c2ecf20Sopenharmony_ci	if (!hdmi->hpd_gpiod)
2478c2ecf20Sopenharmony_ci		return detect_reg(hdmi);
2488c2ecf20Sopenharmony_ci
2498c2ecf20Sopenharmony_ci	do {
2508c2ecf20Sopenharmony_ci		stat_gpio = detect_gpio(hdmi);
2518c2ecf20Sopenharmony_ci		stat_reg  = detect_reg(hdmi);
2528c2ecf20Sopenharmony_ci
2538c2ecf20Sopenharmony_ci		if (stat_gpio == stat_reg)
2548c2ecf20Sopenharmony_ci			break;
2558c2ecf20Sopenharmony_ci
2568c2ecf20Sopenharmony_ci		mdelay(10);
2578c2ecf20Sopenharmony_ci	} while (--retry);
2588c2ecf20Sopenharmony_ci
2598c2ecf20Sopenharmony_ci	/* the status we get from reading gpio seems to be more reliable,
2608c2ecf20Sopenharmony_ci	 * so trust that one the most if we didn't manage to get hdmi and
2618c2ecf20Sopenharmony_ci	 * gpio status to agree:
2628c2ecf20Sopenharmony_ci	 */
2638c2ecf20Sopenharmony_ci	if (stat_gpio != stat_reg) {
2648c2ecf20Sopenharmony_ci		DBG("HDMI_HPD_INT_STATUS tells us: %d", stat_reg);
2658c2ecf20Sopenharmony_ci		DBG("hpd gpio tells us: %d", stat_gpio);
2668c2ecf20Sopenharmony_ci	}
2678c2ecf20Sopenharmony_ci
2688c2ecf20Sopenharmony_ci	return stat_gpio;
2698c2ecf20Sopenharmony_ci}
270