| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Copyright (C) 2013 Red Hat
- * Author: Rob Clark <robdclark@gmail.com>
- */
- #include <linux/delay.h>
- #include <linux/gpio/consumer.h>
- #include <linux/pinctrl/consumer.h>
- #include "msm_kms.h"
- #include "hdmi.h"
- static void msm_hdmi_phy_reset(struct hdmi *hdmi)
- {
- unsigned int val;
- val = hdmi_read(hdmi, REG_HDMI_PHY_CTRL);
- if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
- /* pull low */
- hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
- val & ~HDMI_PHY_CTRL_SW_RESET);
- } else {
- /* pull high */
- hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
- val | HDMI_PHY_CTRL_SW_RESET);
- }
- if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
- /* pull low */
- hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
- val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
- } else {
- /* pull high */
- hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
- val | HDMI_PHY_CTRL_SW_RESET_PLL);
- }
- msleep(100);
- if (val & HDMI_PHY_CTRL_SW_RESET_LOW) {
- /* pull high */
- hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
- val | HDMI_PHY_CTRL_SW_RESET);
- } else {
- /* pull low */
- hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
- val & ~HDMI_PHY_CTRL_SW_RESET);
- }
- if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW) {
- /* pull high */
- hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
- val | HDMI_PHY_CTRL_SW_RESET_PLL);
- } else {
- /* pull low */
- hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
- val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
- }
- }
- void msm_hdmi_hpd_enable(struct drm_bridge *bridge)
- {
- struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
- struct hdmi *hdmi = hdmi_bridge->hdmi;
- struct device *dev = &hdmi->pdev->dev;
- uint32_t hpd_ctrl;
- int ret;
- unsigned long flags;
- if (hdmi->hpd_gpiod)
- gpiod_set_value_cansleep(hdmi->hpd_gpiod, 1);
- ret = pm_runtime_resume_and_get(dev);
- if (WARN_ON(ret))
- return;
- mutex_lock(&hdmi->state_mutex);
- msm_hdmi_set_mode(hdmi, false);
- msm_hdmi_phy_reset(hdmi);
- msm_hdmi_set_mode(hdmi, true);
- hdmi->hpd_enabled = true;
- mutex_unlock(&hdmi->state_mutex);
- hdmi_write(hdmi, REG_HDMI_USEC_REFTIMER, 0x0001001b);
- /* enable HPD events: */
- hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL,
- HDMI_HPD_INT_CTRL_INT_CONNECT |
- HDMI_HPD_INT_CTRL_INT_EN);
- /* set timeout to 4.1ms (max) for hardware debounce */
- spin_lock_irqsave(&hdmi->reg_lock, flags);
- hpd_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_CTRL);
- hpd_ctrl |= HDMI_HPD_CTRL_TIMEOUT(0x1fff);
- /* Toggle HPD circuit to trigger HPD sense */
- hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
- ~HDMI_HPD_CTRL_ENABLE & hpd_ctrl);
- hdmi_write(hdmi, REG_HDMI_HPD_CTRL,
- HDMI_HPD_CTRL_ENABLE | hpd_ctrl);
- spin_unlock_irqrestore(&hdmi->reg_lock, flags);
- }
- void msm_hdmi_hpd_disable(struct drm_bridge *bridge)
- {
- struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
- struct hdmi *hdmi = hdmi_bridge->hdmi;
- struct device *dev = &hdmi->pdev->dev;
- /* Disable HPD interrupt */
- hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0);
- mutex_lock(&hdmi->state_mutex);
- hdmi->hpd_enabled = false;
- msm_hdmi_set_mode(hdmi, hdmi->power_on);
- mutex_unlock(&hdmi->state_mutex);
- pm_runtime_put(dev);
- }
- void msm_hdmi_hpd_irq(struct drm_bridge *bridge)
- {
- struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
- struct hdmi *hdmi = hdmi_bridge->hdmi;
- uint32_t hpd_int_status, hpd_int_ctrl;
- /* Process HPD: */
- hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS);
- hpd_int_ctrl = hdmi_read(hdmi, REG_HDMI_HPD_INT_CTRL);
- if ((hpd_int_ctrl & HDMI_HPD_INT_CTRL_INT_EN) &&
- (hpd_int_status & HDMI_HPD_INT_STATUS_INT)) {
- bool detected = !!(hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED);
- /* ack & disable (temporarily) HPD events: */
- hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL,
- HDMI_HPD_INT_CTRL_INT_ACK);
- DBG("status=%04x, ctrl=%04x", hpd_int_status, hpd_int_ctrl);
- /* detect disconnect if we are connected or visa versa: */
- hpd_int_ctrl = HDMI_HPD_INT_CTRL_INT_EN;
- if (!detected)
- hpd_int_ctrl |= HDMI_HPD_INT_CTRL_INT_CONNECT;
- hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, hpd_int_ctrl);
- queue_work(hdmi->workq, &hdmi_bridge->hpd_work);
- }
- }
- static enum drm_connector_status detect_reg(struct hdmi *hdmi)
- {
- u32 hpd_int_status = 0;
- int ret;
- ret = pm_runtime_resume_and_get(&hdmi->pdev->dev);
- if (ret)
- goto out;
- hpd_int_status = hdmi_read(hdmi, REG_HDMI_HPD_INT_STATUS);
- out:
- pm_runtime_put(&hdmi->pdev->dev);
- return (hpd_int_status & HDMI_HPD_INT_STATUS_CABLE_DETECTED) ?
- connector_status_connected : connector_status_disconnected;
- }
- #define HPD_GPIO_INDEX 2
- static enum drm_connector_status detect_gpio(struct hdmi *hdmi)
- {
- return gpiod_get_value(hdmi->hpd_gpiod) ?
- connector_status_connected :
- connector_status_disconnected;
- }
- enum drm_connector_status
- msm_hdmi_bridge_detect(struct drm_bridge *bridge, struct drm_connector *connector)
- {
- struct hdmi_bridge *hdmi_bridge = to_hdmi_bridge(bridge);
- struct hdmi *hdmi = hdmi_bridge->hdmi;
- enum drm_connector_status stat_gpio, stat_reg;
- int retry = 20;
- /*
- * some platforms may not have hpd gpio. Rely only on the status
- * provided by REG_HDMI_HPD_INT_STATUS in this case.
- */
- if (!hdmi->hpd_gpiod)
- return detect_reg(hdmi);
- do {
- stat_gpio = detect_gpio(hdmi);
- stat_reg = detect_reg(hdmi);
- if (stat_gpio == stat_reg)
- break;
- mdelay(10);
- } while (--retry);
- /* the status we get from reading gpio seems to be more reliable,
- * so trust that one the most if we didn't manage to get hdmi and
- * gpio status to agree:
- */
- if (stat_gpio != stat_reg) {
- DBG("HDMI_HPD_INT_STATUS tells us: %d", stat_reg);
- DBG("hpd gpio tells us: %d", stat_gpio);
- }
- return stat_gpio;
- }
|