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