Merge "ARM: dts: msm: rename mdss_mdp to sde_kms and add HDMI TX device node"

This commit is contained in:
Linux Build Service Account 2017-02-23 21:35:34 -08:00 committed by Gerrit - the friendly Code Review server
commit 43de29b084
17 changed files with 1765 additions and 95 deletions

View file

@ -3,6 +3,7 @@ Qualcomm adreno/snapdragon hdmi output
Required properties:
- compatible: one of the following
* "qcom,hdmi-tx-8996"
* "qcom,hdmi-tx-8998"
* "qcom,hdmi-tx-8994"
* "qcom,hdmi-tx-8084"
* "qcom,hdmi-tx-8974"

View file

@ -0,0 +1,22 @@
Qualcomm Technologies,Inc. Adreno/Snapdragon hdmi display manager
Required properties:
- compatible: "qcom,hdmi-display"
- label: label of this display manager
Optional properties:
- qcom,display-type: display type of this manager. It could be "primary",
"secondary", "tertiary", etc.
Example:
/ {
...
hdmi_display: qcom,hdmi-display {
compatible = "qcom,hdmi-display";
label = "hdmi_display";
qcom,display-type = "secondary";
};
};

View file

@ -0,0 +1,31 @@
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
&soc {
sde_wb: qcom,wb-display@0 {
compatible = "qcom,wb-display";
cell-index = <0>;
label = "wb_display";
};
sde_hdmi: qcom,hdmi-display {
compatible = "qcom,hdmi-display";
label = "sde_hdmi";
qcom,display-type = "secondary";
};
};
&sde_kms {
connectors = <&sde_hdmi_tx &sde_hdmi &sde_wb>;
};

View file

@ -0,0 +1,214 @@
/* Copyright (c) 2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
&soc {
sde_kms: qcom,sde_kms@c900000 {
compatible = "qcom,sde-kms";
reg = <0x0c900000 0x90000>,
<0x0c9b0000 0x1040>;
reg-names = "mdp_phys", "vbif_phys";
/* clock and supply entries */
clocks = <&clock_gcc clk_mmssnoc_axi_clk>,
<&clock_mmss clk_mmss_mnoc_ahb_clk>,
<&clock_mmss clk_mmss_bimc_smmu_ahb_clk>,
<&clock_mmss clk_mmss_bimc_smmu_axi_clk>,
<&clock_mmss clk_mmss_mnoc_ahb_clk>,
<&clock_mmss clk_mmss_mdss_ahb_clk>,
<&clock_mmss clk_mmss_mdss_axi_clk>,
<&clock_mmss clk_mdp_clk_src>,
<&clock_mmss clk_mmss_mdss_mdp_clk>,
<&clock_mmss clk_mmss_mdss_vsync_clk>,
<&clock_mmss clk_mmss_mdss_mdp_lut_clk>;
clock-names = "mmss_noc_axi_clk",
"mmss_noc_ahb_clk",
"mmss_smmu_ahb_clk",
"mmss_smmu_axi_clk",
"mnoc_clk", "iface_clk", "bus_clk",
"core_clk_src", "core_clk", "vsync_clk",
"lut_clk";
clock-rate = <0 0 0 0 0 0 0 330000000 0 0 0 0>;
clock-max-rate = <0 0 0 0 0 0 412500000 412500000 0 0 0 0>;
qcom,sde-max-bw-low-kbps = <6700000>;
qcom,sde-max-bw-high-kbps = <6700000>;
/* interrupt config */
interrupt-parent = <&intc>;
interrupts = <0 83 0>;
interrupt-controller;
#interrupt-cells = <1>;
iommus = <&mmss_smmu 0>;
/* hw blocks */
qcom,sde-off = <0x1000>;
qcom,sde-ctl-off = <0x2000 0x2200 0x2400
0x2600 0x2800>;
qcom,sde-mixer-off = <0x45000 0x46000 0x47000
0x48000 0x49000 0x4a000>;
qcom,sde-dspp-off = <0x55000 0x57000>;
qcom,sde-wb-off = <0x66000>;
qcom,sde-wb-id = <2>;
qcom,sde-wb-xin-id = <6>;
qcom,sde-wb-clk-ctrl = <0x2bc 0x10>;
qcom,sde-intf-off = <0x6b000 0x6b800
0x6c000 0x6c800>;
qcom,sde-intf-type = "dp", "dsi", "dsi", "hdmi";
qcom,sde-pp-off = <0x71000 0x71800
0x72000 0x72800>;
qcom,sde-te2-off = <0x2000 0x2000 0x0 0x0>;
qcom,sde-cdm-off = <0x7a200>;
qcom,sde-dsc-off = <0x10000 0x10000 0x0 0x0>;
qcom,sde-intf-max-prefetch-lines = <0x15 0x15 0x15 0x15>;
qcom,sde-sspp-type = "vig", "vig", "vig", "vig",
"dma", "dma", "dma", "dma",
"cursor", "cursor";
qcom,sde-sspp-off = <0x5000 0x7000 0x9000 0xb000
0x25000 0x27000 0x29000 0x2b000
0x35000 0x37000>;
qcom,sde-sspp-xin-id = <0 4 8 12 1 5 9 13 2 10>;
/* offsets are relative to "mdp_phys + qcom,sde-off */
qcom,sde-sspp-clk-ctrl = <0x2ac 0x8>, <0x2b4 0x8>,
<0x2c4 0x8>, <0x2c4 0xc>, <0x3a8 0x10>,
<0x3b0 0x10>;
qcom,sde-qseed-type = "qseedv3";
qcom,sde-mixer-linewidth = <2560>;
qcom,sde-sspp-linewidth = <2560>;
qcom,sde-mixer-blendstages = <0x7>;
qcom,sde-highest-bank-bit = <0x2>;
qcom,sde-panic-per-pipe;
qcom,sde-has-cdp;
qcom,sde-has-src-split;
qcom,sde-sspp-danger-lut = <0x000f 0xffff 0x0000>;
qcom,sde-sspp-safe-lut = <0xfffc 0xff00 0xffff>;
qcom,sde-vbif-off = <0>;
qcom,sde-vbif-id = <0>;
qcom,sde-vbif-default-ot-rd-limit = <32>;
qcom,sde-vbif-default-ot-wr-limit = <32>;
qcom,sde-vbif-dynamic-ot-rd-limit = <62208000 2>,
<124416000 4>, <248832000 16>;
qcom,sde-vbif-dynamic-ot-wr-limit = <62208000 2>,
<124416000 4>, <248832000 16>;
vdd-supply = <&gdsc_mdss>;
gdsc-mmagic-mdss-supply = <&gdsc_bimc_smmu>;
qcom,sde-csc-type = "csc-10bit";
qcom,sde-sspp-vig-blocks {
qcom,sde-vig-csc-off = <0x1a00>;
qcom,sde-vig-qseed-off = <0xa00>;
};
qcom,platform-supply-entries {
#address-cells = <1>;
#size-cells = <0>;
qcom,platform-supply-entry@0 {
reg = <0>;
qcom,supply-name = "gdsc-mmagic-mdss";
qcom,supply-min-voltage = <0>;
qcom,supply-max-voltage = <0>;
qcom,supply-enable-load = <0>;
qcom,supply-disable-load = <0>;
};
qcom,platform-supply-entry@1 {
reg = <1>;
qcom,supply-name = "vdd";
qcom,supply-min-voltage = <0>;
qcom,supply-max-voltage = <0>;
qcom,supply-enable-load = <0>;
qcom,supply-disable-load = <0>;
};
};
smmu_kms_unsec: qcom,smmu_kms_unsec_cb {
compatible = "qcom,smmu_mdp_unsec";
iommus = <&mmss_smmu 0>;
};
/* data and reg bus scale settings */
qcom,sde-data-bus {
qcom,msm-bus,name = "mdss_sde";
qcom,msm-bus,num-cases = <3>;
qcom,msm-bus,num-paths = <2>;
qcom,msm-bus,vectors-KBps =
<22 512 0 0>, <23 512 0 0>,
<22 512 0 6400000>, <23 512 0 6400000>,
<22 512 0 6400000>, <23 512 0 6400000>;
};
qcom,sde-reg-bus {
qcom,msm-bus,name = "mdss_reg";
qcom,msm-bus,num-cases = <4>;
qcom,msm-bus,num-paths = <1>;
qcom,msm-bus,active-only;
qcom,msm-bus,vectors-KBps =
<1 590 0 0>,
<1 590 0 76800>,
<1 590 0 160000>,
<1 590 0 320000>;
};
};
sde_hdmi_tx: qcom,hdmi_tx_8998@c9a0000 {
cell-index = <0>;
compatible = "qcom,hdmi-tx-8998";
reg = <0xc9a0000 0x50c>,
<0x780000 0x621c>,
<0xc9e0000 0x28>;
reg-names = "core_physical", "qfprom_physical", "hdcp_physical";
interrupt-parent = <&sde_kms>;
interrupts = <8 0>;
qcom,hdmi-tx-ddc-clk-gpio = <&tlmm 32 0>;
qcom,hdmi-tx-ddc-data-gpio = <&tlmm 33 0>;
qcom,hdmi-tx-hpd-gpio = <&tlmm 34 0>;
pinctrl-names = "default", "sleep";
pinctrl-0 = <&mdss_hdmi_hpd_active
&mdss_hdmi_ddc_active
&mdss_hdmi_cec_active>;
pinctrl-1 = <&mdss_hdmi_hpd_suspend
&mdss_hdmi_ddc_suspend
&mdss_hdmi_cec_suspend>;
hpd-gdsc-supply = <&gdsc_mdss>;
qcom,supply-names = "hpd-gdsc";
qcom,min-voltage-level = <0>;
qcom,max-voltage-level = <0>;
qcom,enable-load = <0>;
qcom,disable-load = <0>;
qcom,msm_ext_disp = <&msm_ext_disp>;
clocks = <&clock_mmss clk_mmss_mnoc_ahb_clk>,
<&clock_mmss clk_mmss_mdss_ahb_clk>,
<&clock_mmss clk_mmss_mdss_hdmi_clk>,
<&clock_mmss clk_mmss_mdss_mdp_clk>,
<&clock_mmss clk_mmss_mdss_hdmi_dp_ahb_clk>,
<&clock_mmss clk_mmss_mdss_extpclk_clk>,
<&clock_mmss clk_mmss_mnoc_ahb_clk>,
<&clock_mmss clk_mmss_misc_ahb_clk>,
<&clock_mmss clk_mmss_mdss_axi_clk>;
clock-names = "hpd_mnoc_clk", "hpd_iface_clk",
"hpd_core_clk", "hpd_mdp_core_clk",
"hpd_alt_iface_clk", "core_extp_clk",
"mnoc_clk","hpd_misc_ahb_clk",
"hpd_bus_clk";
/*qcom,mdss-fb-map = <&mdss_fb2>;*/
qcom,pluggable;
};
};
#include "msm8998-sde-display.dtsi"

View file

@ -88,3 +88,9 @@ config DRM_SDE_WB
help
Choose this option for writeback connector support.
config DRM_SDE_HDMI
bool "Enable HDMI driver support in DRM SDE driver"
depends on DRM_MSM
default y
help
Choose this option if HDMI connector support is needed in SDE driver.

View file

@ -1,4 +1,6 @@
ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/msm -Idrivers/gpu/drm/msm/dsi-staging
ccflags-y += -Idrivers/gpu/drm/msm/hdmi
ccflags-y += -Idrivers/gpu/drm/msm/hdmi-staging
ccflags-$(CONFIG_DRM_MSM_DSI) += -Idrivers/gpu/drm/msm/dsi
ccflags-$(CONFIG_SYNC) += -Idrivers/staging/android
ccflags-$(CONFIG_DRM_MSM_DSI_PLL) += -Idrivers/gpu/drm/msm/dsi
@ -94,6 +96,9 @@ msm_drm-$(CONFIG_DRM_MSM_DSI_STAGING) += dsi-staging/dsi_phy.o \
dsi-staging/dsi_panel.o \
dsi-staging/dsi_display_test.o
msm_drm-$(CONFIG_DRM_SDE_HDMI) += \
hdmi-staging/sde_hdmi.o
msm_drm-$(CONFIG_DRM_MSM_DSI_PLL) += dsi/pll/dsi_pll.o \
dsi/pll/dsi_pll_28nm.o

View file

@ -0,0 +1,929 @@
/*
* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 as published by
* the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#define pr_fmt(fmt) "sde-hdmi:[%s] " fmt, __func__
#include <linux/list.h>
#include <linux/of.h>
#include <linux/gpio.h>
#include <linux/of_irq.h>
#include "sde_kms.h"
#include "msm_drv.h"
#include "sde_hdmi.h"
static DEFINE_MUTEX(sde_hdmi_list_lock);
static LIST_HEAD(sde_hdmi_list);
static const struct of_device_id sde_hdmi_dt_match[] = {
{.compatible = "qcom,hdmi-display"},
{}
};
static ssize_t _sde_hdmi_debugfs_dump_info_read(struct file *file,
char __user *buff,
size_t count,
loff_t *ppos)
{
struct sde_hdmi *display = file->private_data;
char *buf;
u32 len = 0;
if (!display)
return -ENODEV;
if (*ppos)
return 0;
buf = kzalloc(SZ_1K, GFP_KERNEL);
if (!buf)
return -ENOMEM;
len += snprintf(buf, SZ_4K, "name = %s\n", display->name);
if (copy_to_user(buff, buf, len)) {
kfree(buf);
return -EFAULT;
}
*ppos += len;
kfree(buf);
return len;
}
static const struct file_operations dump_info_fops = {
.open = simple_open,
.read = _sde_hdmi_debugfs_dump_info_read,
};
static int _sde_hdmi_debugfs_init(struct sde_hdmi *display)
{
int rc = 0;
struct dentry *dir, *dump_file;
dir = debugfs_create_dir(display->name, NULL);
if (!dir) {
rc = -ENOMEM;
SDE_ERROR("[%s]debugfs create dir failed, rc = %d\n",
display->name, rc);
goto error;
}
dump_file = debugfs_create_file("dump_info",
0444,
dir,
display,
&dump_info_fops);
if (IS_ERR_OR_NULL(dump_file)) {
rc = PTR_ERR(dump_file);
SDE_ERROR("[%s]debugfs create file failed, rc=%d\n",
display->name, rc);
goto error_remove_dir;
}
display->root = dir;
return rc;
error_remove_dir:
debugfs_remove(dir);
error:
return rc;
}
static void _sde_hdmi_debugfs_deinit(struct sde_hdmi *display)
{
debugfs_remove(display->root);
}
static void _sde_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)
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET);
else
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET);
if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW)
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
else
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET_PLL);
if (val & HDMI_PHY_CTRL_SW_RESET_LOW)
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET);
else
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET);
if (val & HDMI_PHY_CTRL_SW_RESET_PLL_LOW)
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val | HDMI_PHY_CTRL_SW_RESET_PLL);
else
hdmi_write(hdmi, REG_HDMI_PHY_CTRL,
val & ~HDMI_PHY_CTRL_SW_RESET_PLL);
}
static int _sde_hdmi_gpio_config(struct hdmi *hdmi, bool on)
{
const struct hdmi_platform_config *config = hdmi->config;
int ret;
if (on) {
if (config->ddc_clk_gpio != -1) {
ret = gpio_request(config->ddc_clk_gpio,
"HDMI_DDC_CLK");
if (ret) {
SDE_ERROR("'%s'(%d) gpio_request failed: %d\n",
"HDMI_DDC_CLK", config->ddc_clk_gpio,
ret);
goto error_ddc_clk_gpio;
}
gpio_set_value_cansleep(config->ddc_clk_gpio, 1);
}
if (config->ddc_data_gpio != -1) {
ret = gpio_request(config->ddc_data_gpio,
"HDMI_DDC_DATA");
if (ret) {
SDE_ERROR("'%s'(%d) gpio_request failed: %d\n",
"HDMI_DDC_DATA", config->ddc_data_gpio,
ret);
goto error_ddc_data_gpio;
}
gpio_set_value_cansleep(config->ddc_data_gpio, 1);
}
ret = gpio_request(config->hpd_gpio, "HDMI_HPD");
if (ret) {
SDE_ERROR("'%s'(%d) gpio_request failed: %d\n",
"HDMI_HPD", config->hpd_gpio, ret);
goto error_hpd_gpio;
}
gpio_direction_output(config->hpd_gpio, 1);
if (config->mux_en_gpio != -1) {
ret = gpio_request(config->mux_en_gpio, "HDMI_MUX_EN");
if (ret) {
SDE_ERROR("'%s'(%d) gpio_request failed: %d\n",
"HDMI_MUX_EN", config->mux_en_gpio,
ret);
goto error_en_gpio;
}
gpio_set_value_cansleep(config->mux_en_gpio, 1);
}
if (config->mux_sel_gpio != -1) {
ret = gpio_request(config->mux_sel_gpio,
"HDMI_MUX_SEL");
if (ret) {
SDE_ERROR("'%s'(%d) gpio_request failed: %d\n",
"HDMI_MUX_SEL", config->mux_sel_gpio,
ret);
goto error_sel_gpio;
}
gpio_set_value_cansleep(config->mux_sel_gpio, 0);
}
if (config->mux_lpm_gpio != -1) {
ret = gpio_request(config->mux_lpm_gpio,
"HDMI_MUX_LPM");
if (ret) {
SDE_ERROR("'%s'(%d) gpio_request failed: %d\n",
"HDMI_MUX_LPM",
config->mux_lpm_gpio, ret);
goto error_lpm_gpio;
}
gpio_set_value_cansleep(config->mux_lpm_gpio, 1);
}
SDE_DEBUG("gpio on");
} else {
if (config->ddc_clk_gpio != -1)
gpio_free(config->ddc_clk_gpio);
if (config->ddc_data_gpio != -1)
gpio_free(config->ddc_data_gpio);
gpio_free(config->hpd_gpio);
if (config->mux_en_gpio != -1) {
gpio_set_value_cansleep(config->mux_en_gpio, 0);
gpio_free(config->mux_en_gpio);
}
if (config->mux_sel_gpio != -1) {
gpio_set_value_cansleep(config->mux_sel_gpio, 1);
gpio_free(config->mux_sel_gpio);
}
if (config->mux_lpm_gpio != -1) {
gpio_set_value_cansleep(config->mux_lpm_gpio, 0);
gpio_free(config->mux_lpm_gpio);
}
SDE_DEBUG("gpio off");
}
return 0;
error_lpm_gpio:
if (config->mux_sel_gpio != -1)
gpio_free(config->mux_sel_gpio);
error_sel_gpio:
if (config->mux_en_gpio != -1)
gpio_free(config->mux_en_gpio);
error_en_gpio:
gpio_free(config->hpd_gpio);
error_hpd_gpio:
if (config->ddc_data_gpio != -1)
gpio_free(config->ddc_data_gpio);
error_ddc_data_gpio:
if (config->ddc_clk_gpio != -1)
gpio_free(config->ddc_clk_gpio);
error_ddc_clk_gpio:
return ret;
}
static int _sde_hdmi_hpd_enable(struct sde_hdmi *sde_hdmi)
{
struct hdmi *hdmi = sde_hdmi->ctrl.ctrl;
const struct hdmi_platform_config *config = hdmi->config;
struct device *dev = &hdmi->pdev->dev;
uint32_t hpd_ctrl;
int i, ret;
unsigned long flags;
for (i = 0; i < config->hpd_reg_cnt; i++) {
ret = regulator_enable(hdmi->hpd_regs[i]);
if (ret) {
SDE_ERROR("failed to enable hpd regulator: %s (%d)\n",
config->hpd_reg_names[i], ret);
goto fail;
}
}
ret = pinctrl_pm_select_default_state(dev);
if (ret) {
SDE_ERROR("pinctrl state chg failed: %d\n", ret);
goto fail;
}
ret = _sde_hdmi_gpio_config(hdmi, true);
if (ret) {
SDE_ERROR("failed to configure GPIOs: %d\n", ret);
goto fail;
}
for (i = 0; i < config->hpd_clk_cnt; i++) {
if (config->hpd_freq && config->hpd_freq[i]) {
ret = clk_set_rate(hdmi->hpd_clks[i],
config->hpd_freq[i]);
if (ret)
pr_warn("failed to set clk %s (%d)\n",
config->hpd_clk_names[i], ret);
}
ret = clk_prepare_enable(hdmi->hpd_clks[i]);
if (ret) {
SDE_ERROR("failed to enable hpd clk: %s (%d)\n",
config->hpd_clk_names[i], ret);
goto fail;
}
}
hdmi_set_mode(hdmi, false);
_sde_hdmi_phy_reset(hdmi);
hdmi_set_mode(hdmi, true);
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);
return 0;
fail:
return ret;
}
static void _sde_hdmi_hdp_disable(struct sde_hdmi *sde_hdmi)
{
struct hdmi *hdmi = sde_hdmi->ctrl.ctrl;
const struct hdmi_platform_config *config = hdmi->config;
struct device *dev = &hdmi->pdev->dev;
int i, ret = 0;
/* Disable HPD interrupt */
hdmi_write(hdmi, REG_HDMI_HPD_INT_CTRL, 0);
hdmi_set_mode(hdmi, false);
for (i = 0; i < config->hpd_clk_cnt; i++)
clk_disable_unprepare(hdmi->hpd_clks[i]);
ret = _sde_hdmi_gpio_config(hdmi, false);
if (ret)
pr_warn("failed to unconfigure GPIOs: %d\n", ret);
ret = pinctrl_pm_select_sleep_state(dev);
if (ret)
pr_warn("pinctrl state chg failed: %d\n", ret);
for (i = 0; i < config->hpd_reg_cnt; i++) {
ret = regulator_disable(hdmi->hpd_regs[i]);
if (ret)
pr_warn("failed to disable hpd regulator: %s (%d)\n",
config->hpd_reg_names[i], ret);
}
}
static void _sde_hdmi_hotplug_work(struct work_struct *work)
{
struct sde_hdmi *sde_hdmi =
container_of(work, struct sde_hdmi, hpd_work);
struct drm_connector *connector;
if (!sde_hdmi || !sde_hdmi->ctrl.ctrl ||
!sde_hdmi->ctrl.ctrl->connector) {
SDE_ERROR("sde_hdmi=%p or hdmi or connector is NULL\n",
sde_hdmi);
return;
}
connector = sde_hdmi->ctrl.ctrl->connector;
drm_helper_hpd_irq_event(connector->dev);
}
static void _sde_hdmi_connector_irq(struct sde_hdmi *sde_hdmi)
{
struct hdmi *hdmi = sde_hdmi->ctrl.ctrl;
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);
DRM_DEBUG("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, &sde_hdmi->hpd_work);
}
}
static irqreturn_t _sde_hdmi_irq(int irq, void *dev_id)
{
struct sde_hdmi *sde_hdmi = dev_id;
struct hdmi *hdmi;
if (!sde_hdmi || !sde_hdmi->ctrl.ctrl) {
SDE_ERROR("sde_hdmi=%p or hdmi is NULL\n", sde_hdmi);
return IRQ_NONE;
}
hdmi = sde_hdmi->ctrl.ctrl;
/* Process HPD: */
_sde_hdmi_connector_irq(sde_hdmi);
/* Process DDC: */
hdmi_i2c_irq(hdmi->i2c);
/* Process HDCP: */
if (hdmi->hdcp_ctrl && hdmi->is_hdcp_supported)
hdmi_hdcp_ctrl_irq(hdmi->hdcp_ctrl);
/* TODO audio.. */
return IRQ_HANDLED;
}
int sde_hdmi_get_info(struct msm_display_info *info,
void *display)
{
int rc = 0;
struct sde_hdmi *hdmi_display = (struct sde_hdmi *)display;
if (!display || !info) {
SDE_ERROR("display=%p or info=%p is NULL\n", display, info);
return -EINVAL;
}
mutex_lock(&hdmi_display->display_lock);
info->intf_type = DRM_MODE_CONNECTOR_HDMIA;
info->num_of_h_tiles = 1;
info->h_tile_instance[0] = 0;
info->is_connected = true;
info->capabilities = MSM_DISPLAY_CAP_HOT_PLUG | MSM_DISPLAY_CAP_EDID |
MSM_DISPLAY_CAP_VID_MODE;
info->max_width = 1920;
info->max_height = 1080;
info->compression = MSM_DISPLAY_COMPRESS_NONE;
mutex_unlock(&hdmi_display->display_lock);
return rc;
}
u32 sde_hdmi_get_num_of_displays(void)
{
u32 count = 0;
struct sde_hdmi *display;
mutex_lock(&sde_hdmi_list_lock);
list_for_each_entry(display, &sde_hdmi_list, list)
count++;
mutex_unlock(&sde_hdmi_list_lock);
return count;
}
int sde_hdmi_get_displays(void **display_array, u32 max_display_count)
{
struct sde_hdmi *display;
int i = 0;
SDE_DEBUG("\n");
if (!display_array || !max_display_count) {
if (!display_array)
SDE_ERROR("invalid param\n");
return 0;
}
mutex_lock(&sde_hdmi_list_lock);
list_for_each_entry(display, &sde_hdmi_list, list) {
if (i >= max_display_count)
break;
display_array[i++] = display;
}
mutex_unlock(&sde_hdmi_list_lock);
return i;
}
int sde_hdmi_connector_pre_deinit(struct drm_connector *connector,
void *display)
{
struct sde_hdmi *sde_hdmi = (struct sde_hdmi *)display;
if (!sde_hdmi || !sde_hdmi->ctrl.ctrl) {
SDE_ERROR("sde_hdmi=%p or hdmi is NULL\n", sde_hdmi);
return -EINVAL;
}
_sde_hdmi_hdp_disable(sde_hdmi);
return 0;
}
int sde_hdmi_connector_post_init(struct drm_connector *connector,
void *info,
void *display)
{
int rc = 0;
struct sde_hdmi *sde_hdmi = (struct sde_hdmi *)display;
struct hdmi *hdmi;
if (!sde_hdmi) {
SDE_ERROR("sde_hdmi is NULL\n");
return -EINVAL;
}
hdmi = sde_hdmi->ctrl.ctrl;
if (!hdmi) {
SDE_ERROR("hdmi is NULL\n");
return -EINVAL;
}
if (info)
sde_kms_info_add_keystr(info,
"DISPLAY_TYPE",
sde_hdmi->display_type);
hdmi->connector = connector;
INIT_WORK(&sde_hdmi->hpd_work, _sde_hdmi_hotplug_work);
/* Enable HPD detection */
rc = _sde_hdmi_hpd_enable(sde_hdmi);
if (rc)
SDE_ERROR("failed to enable HPD: %d\n", rc);
return rc;
}
enum drm_connector_status
sde_hdmi_connector_detect(struct drm_connector *connector,
bool force,
void *display)
{
enum drm_connector_status status = connector_status_unknown;
struct msm_display_info info;
int rc;
if (!connector || !display) {
SDE_ERROR("connector=%p or display=%p is NULL\n",
connector, display);
return status;
}
SDE_DEBUG("\n");
/* get display dsi_info */
memset(&info, 0x0, sizeof(info));
rc = sde_hdmi_get_info(&info, display);
if (rc) {
SDE_ERROR("failed to get display info, rc=%d\n", rc);
return connector_status_disconnected;
}
if (info.capabilities & MSM_DISPLAY_CAP_HOT_PLUG)
status = (info.is_connected ? connector_status_connected :
connector_status_disconnected);
else
status = connector_status_connected;
connector->display_info.width_mm = info.width_mm;
connector->display_info.height_mm = info.height_mm;
return status;
}
int sde_hdmi_connector_get_modes(struct drm_connector *connector, void *display)
{
struct sde_hdmi *hdmi_display = (struct sde_hdmi *)display;
struct hdmi *hdmi;
struct edid *edid;
uint32_t hdmi_ctrl;
int ret = 0;
if (!connector || !display) {
SDE_ERROR("connector=%p or display=%p is NULL\n",
connector, display);
return 0;
}
SDE_DEBUG("\n");
hdmi = hdmi_display->ctrl.ctrl;
hdmi_ctrl = hdmi_read(hdmi, REG_HDMI_CTRL);
hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl | HDMI_CTRL_ENABLE);
edid = drm_get_edid(connector, hdmi->i2c);
hdmi_write(hdmi, REG_HDMI_CTRL, hdmi_ctrl);
hdmi->hdmi_mode = drm_detect_hdmi_monitor(edid);
drm_mode_connector_update_edid_property(connector, edid);
if (edid) {
ret = drm_add_edid_modes(connector, edid);
kfree(edid);
}
return ret;
}
enum drm_mode_status sde_hdmi_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode,
void *display)
{
struct sde_hdmi *hdmi_display = (struct sde_hdmi *)display;
struct hdmi *hdmi;
struct msm_drm_private *priv;
struct msm_kms *kms;
long actual, requested;
if (!connector || !display || !mode) {
SDE_ERROR("connector=%p or display=%p or mode=%p is NULL\n",
connector, display, mode);
return 0;
}
SDE_DEBUG("\n");
hdmi = hdmi_display->ctrl.ctrl;
priv = connector->dev->dev_private;
kms = priv->kms;
requested = 1000 * mode->clock;
actual = kms->funcs->round_pixclk(kms,
requested, hdmi->encoder);
SDE_DEBUG("requested=%ld, actual=%ld", requested, actual);
if (actual != requested)
return MODE_CLOCK_RANGE;
return MODE_OK;
}
int sde_hdmi_dev_init(struct sde_hdmi *display)
{
if (!display) {
SDE_ERROR("Invalid params\n");
return -EINVAL;
}
return 0;
}
int sde_hdmi_dev_deinit(struct sde_hdmi *display)
{
if (!display) {
SDE_ERROR("Invalid params\n");
return -EINVAL;
}
return 0;
}
static int sde_hdmi_bind(struct device *dev, struct device *master, void *data)
{
int rc = 0;
struct sde_hdmi_ctrl *display_ctrl = NULL;
struct sde_hdmi *display = NULL;
struct drm_device *drm = NULL;
struct msm_drm_private *priv = NULL;
struct platform_device *pdev = to_platform_device(dev);
SDE_ERROR("E\n");
if (!dev || !pdev || !master) {
pr_err("invalid param(s), dev %pK, pdev %pK, master %pK\n",
dev, pdev, master);
return -EINVAL;
}
drm = dev_get_drvdata(master);
display = platform_get_drvdata(pdev);
if (!drm || !display) {
pr_err("invalid param(s), drm %pK, display %pK\n",
drm, display);
return -EINVAL;
}
priv = drm->dev_private;
mutex_lock(&display->display_lock);
rc = _sde_hdmi_debugfs_init(display);
if (rc) {
SDE_ERROR("[%s]Debugfs init failed, rc=%d\n",
display->name, rc);
goto error;
}
display_ctrl = &display->ctrl;
display_ctrl->ctrl = priv->hdmi;
SDE_ERROR("display_ctrl->ctrl=%p\n", display_ctrl->ctrl);
display->drm_dev = drm;
error:
mutex_unlock(&display->display_lock);
return rc;
}
static void sde_hdmi_unbind(struct device *dev, struct device *master,
void *data)
{
struct sde_hdmi *display = NULL;
if (!dev) {
SDE_ERROR("invalid params\n");
return;
}
display = platform_get_drvdata(to_platform_device(dev));
if (!display) {
SDE_ERROR("Invalid display device\n");
return;
}
mutex_lock(&display->display_lock);
(void)_sde_hdmi_debugfs_deinit(display);
display->drm_dev = NULL;
mutex_unlock(&display->display_lock);
}
static const struct component_ops sde_hdmi_comp_ops = {
.bind = sde_hdmi_bind,
.unbind = sde_hdmi_unbind,
};
static int _sde_hdmi_dev_probe(struct platform_device *pdev)
{
struct sde_hdmi *display;
int ret = 0;
DBG("");
if (!pdev || !pdev->dev.of_node) {
SDE_ERROR("pdev not found\n");
return -ENODEV;
}
display = devm_kzalloc(&pdev->dev, sizeof(*display), GFP_KERNEL);
if (!display)
return -ENOMEM;
DBG("");
display->name = of_get_property(pdev->dev.of_node, "label", NULL);
display->display_type = of_get_property(pdev->dev.of_node,
"qcom,display-type", NULL);
if (!display->display_type)
display->display_type = "unknown";
mutex_init(&display->display_lock);
display->pdev = pdev;
platform_set_drvdata(pdev, display);
mutex_lock(&sde_hdmi_list_lock);
list_add(&display->list, &sde_hdmi_list);
mutex_unlock(&sde_hdmi_list_lock);
if (!sde_hdmi_dev_init(display)) {
ret = component_add(&pdev->dev, &sde_hdmi_comp_ops);
if (ret)
pr_err("component add failed\n");
}
return ret;
}
static int _sde_hdmi_dev_remove(struct platform_device *pdev)
{
struct sde_hdmi *display;
struct sde_hdmi *pos, *tmp;
if (!pdev) {
SDE_ERROR("Invalid device\n");
return -EINVAL;
}
display = platform_get_drvdata(pdev);
mutex_lock(&sde_hdmi_list_lock);
list_for_each_entry_safe(pos, tmp, &sde_hdmi_list, list) {
if (pos == display) {
list_del(&display->list);
break;
}
}
mutex_unlock(&sde_hdmi_list_lock);
platform_set_drvdata(pdev, NULL);
devm_kfree(&pdev->dev, display);
return 0;
}
static struct platform_driver sde_hdmi_driver = {
.probe = _sde_hdmi_dev_probe,
.remove = _sde_hdmi_dev_remove,
.driver = {
.name = "sde_hdmi",
.of_match_table = sde_hdmi_dt_match,
},
};
int sde_hdmi_drm_init(struct sde_hdmi *display, struct drm_encoder *enc)
{
int rc = 0;
struct msm_drm_private *priv = NULL;
struct hdmi *hdmi;
struct platform_device *pdev;
DBG("");
if (!display || !display->drm_dev || !enc) {
SDE_ERROR("display=%p or enc=%p or drm_dev is NULL\n",
display, enc);
return -EINVAL;
}
mutex_lock(&display->display_lock);
priv = display->drm_dev->dev_private;
hdmi = display->ctrl.ctrl;
if (!priv || !hdmi) {
SDE_ERROR("priv=%p or hdmi=%p is NULL\n",
priv, hdmi);
mutex_unlock(&display->display_lock);
return -EINVAL;
}
pdev = hdmi->pdev;
hdmi->dev = display->drm_dev;
hdmi->encoder = enc;
hdmi_audio_infoframe_init(&hdmi->audio.infoframe);
hdmi->bridge = hdmi_bridge_init(hdmi);
if (IS_ERR(hdmi->bridge)) {
rc = PTR_ERR(hdmi->bridge);
SDE_ERROR("failed to create HDMI bridge: %d\n", rc);
hdmi->bridge = NULL;
goto error;
}
hdmi->irq = irq_of_parse_and_map(pdev->dev.of_node, 0);
if (hdmi->irq < 0) {
rc = hdmi->irq;
SDE_ERROR("failed to get irq: %d\n", rc);
goto error;
}
rc = devm_request_irq(&pdev->dev, hdmi->irq,
_sde_hdmi_irq, IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"sde_hdmi_isr", display);
if (rc < 0) {
SDE_ERROR("failed to request IRQ%u: %d\n",
hdmi->irq, rc);
goto error;
}
enc->bridge = hdmi->bridge;
priv->bridges[priv->num_bridges++] = hdmi->bridge;
mutex_unlock(&display->display_lock);
return 0;
error:
/* bridge is normally destroyed by drm: */
if (hdmi->bridge) {
hdmi_bridge_destroy(hdmi->bridge);
hdmi->bridge = NULL;
}
mutex_unlock(&display->display_lock);
return rc;
}
int sde_hdmi_drm_deinit(struct sde_hdmi *display)
{
int rc = 0;
if (!display) {
SDE_ERROR("Invalid params\n");
return -EINVAL;
}
return rc;
}
static int __init sde_hdmi_register(void)
{
int rc = 0;
DBG("");
rc = platform_driver_register(&sde_hdmi_driver);
return rc;
}
static void __exit sde_hdmi_unregister(void)
{
platform_driver_unregister(&sde_hdmi_driver);
}
module_init(sde_hdmi_register);
module_exit(sde_hdmi_unregister);

View file

@ -0,0 +1,296 @@
/*
* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*/
#ifndef _SDE_HDMI_H_
#define _SDE_HDMI_H_
#include <linux/types.h>
#include <linux/bitops.h>
#include <linux/debugfs.h>
#include <linux/of_device.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include "hdmi.h"
/**
* struct sde_hdmi_info - defines hdmi display properties
* @display_type: Display type as defined by device tree.
* @is_hot_pluggable: Can panel be hot plugged.
* @is_connected: Is panel connected.
* @is_edid_supported: Does panel support reading EDID information.
* @width_mm: Physical width of panel in millimeters.
* @height_mm: Physical height of panel in millimeters.
*/
struct sde_hdmi_info {
const char *display_type;
/* HPD */
bool is_hot_pluggable;
bool is_connected;
bool is_edid_supported;
/* Physical properties */
u32 width_mm;
u32 height_mm;
};
/**
* struct sde_hdmi_ctrl - hdmi ctrl/phy information for the display
* @ctrl: Handle to the HDMI controller device.
* @ctrl_of_node: pHandle to the HDMI controller device.
* @hdmi_ctrl_idx: HDMI controller instance id.
*/
struct sde_hdmi_ctrl {
/* controller info */
struct hdmi *ctrl;
struct device_node *ctrl_of_node;
u32 hdmi_ctrl_idx;
};
/**
* struct sde_hdmi - hdmi display information
* @pdev: Pointer to platform device.
* @drm_dev: DRM device associated with the display.
* @name: Name of the display.
* @display_type: Display type as defined in device tree.
* @list: List pointer.
* @display_lock: Mutex for sde_hdmi interface.
* @ctrl: Controller information for HDMI display.
* @num_of_modes: Number of modes supported by display.
* @is_tpg_enabled: TPG state.
* @hpd_work: HPD work structure.
* @root: Debug fs root entry.
*/
struct sde_hdmi {
struct platform_device *pdev;
struct drm_device *drm_dev;
const char *name;
const char *display_type;
struct list_head list;
struct mutex display_lock;
struct sde_hdmi_ctrl ctrl;
u32 num_of_modes;
bool is_tpg_enabled;
struct work_struct hpd_work;
/* DEBUG FS */
struct dentry *root;
};
#ifdef CONFIG_DRM_SDE_HDMI
/**
* sde_hdmi_get_num_of_displays() - returns number of display devices
* supported.
*
* Return: number of displays.
*/
u32 sde_hdmi_get_num_of_displays(void);
/**
* sde_hdmi_get_displays() - returns the display list that's available.
* @display_array: Pointer to display list
* @max_display_count: Number of maximum displays in the list
*
* Return: number of available displays.
*/
int sde_hdmi_get_displays(void **display_array, u32 max_display_count);
/**
* sde_hdmi_connector_pre_deinit()- perform additional deinitialization steps
* @connector: Pointer to drm connector structure
* @display: Pointer to private display handle
*
* Return: error code
*/
int sde_hdmi_connector_pre_deinit(struct drm_connector *connector,
void *display);
/**
* sde_hdmi_connector_post_init()- perform additional initialization steps
* @connector: Pointer to drm connector structure
* @info: Pointer to sde connector info structure
* @display: Pointer to private display handle
*
* Return: error code
*/
int sde_hdmi_connector_post_init(struct drm_connector *connector,
void *info,
void *display);
/**
* sde_hdmi_connector_detect()- determine if connector is connected
* @connector: Pointer to drm connector structure
* @force: Force detect setting from drm framework
* @display: Pointer to private display handle
*
* Return: error code
*/
enum drm_connector_status
sde_hdmi_connector_detect(struct drm_connector *connector,
bool force,
void *display);
/**
* sde_hdmi_connector_get_modes - add drm modes via drm_mode_probed_add()
* @connector: Pointer to drm connector structure
* @display: Pointer to private display handle
* Returns: Number of modes added
*/
int sde_hdmi_connector_get_modes(struct drm_connector *connector,
void *display);
/**
* sde_hdmi_mode_valid - determine if specified mode is valid
* @connector: Pointer to drm connector structure
* @mode: Pointer to drm mode structure
* @display: Pointer to private display handle
*
* Returns: Validity status for specified mode
*/
enum drm_mode_status sde_hdmi_mode_valid(struct drm_connector *connector,
struct drm_display_mode *mode,
void *display);
/**
* sde_hdmi_dev_init() - Initializes the display device
* @display: Handle to the display.
*
* Initialization will acquire references to the resources required for the
* display hardware to function.
*
* Return: error code.
*/
int sde_hdmi_dev_init(struct sde_hdmi *display);
/**
* sde_hdmi_dev_deinit() - Desinitializes the display device
* @display: Handle to the display.
*
* All the resources acquired during device init will be released.
*
* Return: error code.
*/
int sde_hdmi_dev_deinit(struct sde_hdmi *display);
/**
* sde_hdmi_drm_init() - initializes DRM objects for the display device.
* @display: Handle to the display.
* @encoder: Pointer to the encoder object which is connected to the
* display.
*
* Return: error code.
*/
int sde_hdmi_drm_init(struct sde_hdmi *display,
struct drm_encoder *enc);
/**
* sde_hdmi_drm_deinit() - destroys DRM objects assosciated with the display
* @display: Handle to the display.
*
* Return: error code.
*/
int sde_hdmi_drm_deinit(struct sde_hdmi *display);
/**
* sde_hdmi_get_info() - returns the display properties
* @display: Handle to the display.
* @info: Pointer to the structure where info is stored.
*
* Return: error code.
*/
int sde_hdmi_get_info(struct msm_display_info *info,
void *display);
#else /*#ifdef CONFIG_DRM_SDE_HDMI*/
static inline u32 sde_hdmi_get_num_of_displays(void)
{
return 0;
}
static inline int sde_hdmi_get_displays(void **display_array,
u32 max_display_count)
{
return 0;
}
static inline int sde_hdmi_connector_pre_deinit(struct drm_connector *connector,
void *display)
{
return 0;
}
static inline int sde_hdmi_connector_post_init(struct drm_connector *connector,
void *info,
void *display)
{
return 0;
}
static inline enum drm_connector_status
sde_hdmi_connector_detect(struct drm_connector *connector,
bool force,
void *display)
{
return connector_status_disconnected;
}
static inline int sde_hdmi_connector_get_modes(struct drm_connector *connector,
void *display)
{
return 0;
}
static inline enum drm_mode_status sde_hdmi_mode_valid(
struct drm_connector *connector,
struct drm_display_mode *mode,
void *display)
{
return MODE_OK;
}
static inline int sde_hdmi_dev_init(struct sde_hdmi *display)
{
return 0;
}
static inline int sde_hdmi_dev_deinit(struct sde_hdmi *display)
{
return 0;
}
static inline int sde_hdmi_drm_init(struct sde_hdmi *display,
struct drm_encoder *enc)
{
return 0;
}
static inline int sde_hdmi_drm_deinit(struct sde_hdmi *display)
{
return 0;
}
static inline int sde_hdmi_get_info(struct msm_display_info *info,
void *display)
{
return 0;
}
#endif /*#else of CONFIG_DRM_SDE_HDMI*/
#endif /* _SDE_HDMI_H_ */

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2016 The Linux Foundation. All rights reserved.
* Copyright (c) 2014, 2016-2017 The Linux Foundation. All rights reserved.
* Copyright (C) 2013 Red Hat
* Author: Rob Clark <robdclark@gmail.com>
*
@ -234,6 +234,11 @@ static struct hdmi *hdmi_init(struct platform_device *pdev)
dev_warn(&pdev->dev, "failed to init hdcp: disabled\n");
hdmi->hdcp_ctrl = NULL;
}
/*making it false currently to avoid ifdefs
*will get rid of this flag when HDCP SW
*support gets added to HDMI DRM driver
*/
hdmi->is_hdcp_supported = false;
return hdmi;
@ -389,7 +394,34 @@ static struct hdmi_platform_config hdmi_tx_8996_config = {
.hpd_freq = hpd_clk_freq_8x74,
};
/*TO DO*/
static const char *pwr_reg_names_8x98[] = {"core-vdda", "core-vcc"};
/*TO DO*/
static const char *hpd_reg_names_8x98[] = {"hpd-gdsc", "hpd-5v"};
static const char *pwr_clk_names_8x98[] = {"core_extp_clk",
"hpd_alt_iface_clk"};
static const char *hpd_clk_names_8x98[] = {"hpd_iface_clk",
"hpd_core_clk",
"hpd_mdp_core_clk",
"mnoc_clk",
"hpd_misc_ahb_clk",
"hpd_bus_clk"};
static unsigned long hpd_clk_freq_8x98[] = {0, 19200000, 0, 0, 0, 0};
static struct hdmi_platform_config hdmi_tx_8998_config = {
.phy_init = NULL,
HDMI_CFG(pwr_reg, 8x98),
HDMI_CFG(hpd_reg, 8x98),
HDMI_CFG(pwr_clk, 8x98),
HDMI_CFG(hpd_clk, 8x98),
.hpd_freq = hpd_clk_freq_8x98,
};
static const struct of_device_id dt_match[] = {
{ .compatible = "qcom,hdmi-tx-8998", .data = &hdmi_tx_8998_config },
{ .compatible = "qcom,hdmi-tx-8996", .data = &hdmi_tx_8996_config },
{ .compatible = "qcom,hdmi-tx-8994", .data = &hdmi_tx_8994_config },
{ .compatible = "qcom,hdmi-tx-8084", .data = &hdmi_tx_8084_config },
@ -425,7 +457,6 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
#ifdef CONFIG_OF
struct device_node *of_node = dev->of_node;
const struct of_device_id *match;
match = of_match_node(dt_match, of_node);
if (match && match->data) {
hdmi_cfg = (struct hdmi_platform_config *)match->data;
@ -449,6 +480,7 @@ static int hdmi_bind(struct device *dev, struct device *master, void *data)
static const char *hpd_clk_names[] = {
"core_clk", "master_iface_clk", "slave_iface_clk",
};
if (cpu_is_apq8064()) {
static const char *hpd_reg_names[] = {"8921_hdmi_mvs"};
config.phy_init = hdmi_phy_8960_init;

View file

@ -70,7 +70,7 @@ struct hdmi {
struct drm_encoder *encoder;
bool hdmi_mode; /* are we in hdmi mode? */
bool is_hdcp_supported;
int irq;
struct workqueue_struct *workq;

View file

@ -65,8 +65,12 @@ static void sde_connector_destroy(struct drm_connector *connector)
c_conn = to_sde_connector(connector);
if (c_conn->ops.pre_deinit)
c_conn->ops.pre_deinit(connector, c_conn->display);
if (c_conn->blob_caps)
drm_property_unreference_blob(c_conn->blob_caps);
msm_property_destroy(&c_conn->property_info);
drm_connector_unregister(connector);

View file

@ -43,6 +43,15 @@ struct sde_connector_ops {
void *info,
void *display);
/**
* pre_deinit - perform additional deinitialization steps
* @connector: Pointer to drm connector structure
* @display: Pointer to private display handle
* Returns: Zero on success
*/
int (*pre_deinit)(struct drm_connector *connector,
void *display);
/**
* detect - determine if connector is connected
* @connector: Pointer to drm connector structure

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@ -432,6 +432,99 @@ void sde_core_irq_uninstall(struct sde_kms *sde_kms)
sde_kms->irq_obj.total_irqs = 0;
}
static void sde_hw_irq_mask(struct irq_data *irqd)
{
struct sde_kms *sde_kms;
if (!irqd || !irq_data_get_irq_chip_data(irqd)) {
SDE_ERROR("invalid parameters irqd %d\n", irqd != 0);
return;
}
sde_kms = irq_data_get_irq_chip_data(irqd);
smp_mb__before_atomic();
clear_bit(irqd->hwirq, &sde_kms->irq_controller.enabled_mask);
smp_mb__after_atomic();
}
static void sde_hw_irq_unmask(struct irq_data *irqd)
{
struct sde_kms *sde_kms;
if (!irqd || !irq_data_get_irq_chip_data(irqd)) {
SDE_ERROR("invalid parameters irqd %d\n", irqd != 0);
return;
}
sde_kms = irq_data_get_irq_chip_data(irqd);
smp_mb__before_atomic();
set_bit(irqd->hwirq, &sde_kms->irq_controller.enabled_mask);
smp_mb__after_atomic();
}
static struct irq_chip sde_hw_irq_chip = {
.name = "sde",
.irq_mask = sde_hw_irq_mask,
.irq_unmask = sde_hw_irq_unmask,
};
static int sde_hw_irqdomain_map(struct irq_domain *domain,
unsigned int irq, irq_hw_number_t hwirq)
{
struct sde_kms *sde_kms;
int rc;
if (!domain || !domain->host_data) {
SDE_ERROR("invalid parameters domain %d\n", domain != 0);
return -EINVAL;
}
sde_kms = domain->host_data;
irq_set_chip_and_handler(irq, &sde_hw_irq_chip, handle_level_irq);
rc = irq_set_chip_data(irq, sde_kms);
return rc;
}
static struct irq_domain_ops sde_hw_irqdomain_ops = {
.map = sde_hw_irqdomain_map,
.xlate = irq_domain_xlate_onecell,
};
int sde_core_irq_domain_add(struct sde_kms *sde_kms)
{
struct device *dev;
struct irq_domain *domain;
if (!sde_kms->dev || !sde_kms->dev->dev) {
pr_err("invalid device handles\n");
return -EINVAL;
}
dev = sde_kms->dev->dev;
domain = irq_domain_add_linear(dev->of_node, 32,
&sde_hw_irqdomain_ops, sde_kms);
if (!domain) {
pr_err("failed to add irq_domain\n");
return -EINVAL;
}
sde_kms->irq_controller.enabled_mask = 0;
sde_kms->irq_controller.domain = domain;
return 0;
}
int sde_core_irq_domain_fini(struct sde_kms *sde_kms)
{
if (sde_kms->irq_controller.domain) {
irq_domain_remove(sde_kms->irq_controller.domain);
sde_kms->irq_controller.domain = NULL;
}
return 0;
}
irqreturn_t sde_core_irq(struct sde_kms *sde_kms)
{
/*

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@ -37,6 +37,20 @@ int sde_core_irq_postinstall(struct sde_kms *sde_kms);
*/
void sde_core_irq_uninstall(struct sde_kms *sde_kms);
/**
* sde_core_irq_domain_add - Add core IRQ domain for SDE
* @sde_kms: SDE handle
* @return: none
*/
int sde_core_irq_domain_add(struct sde_kms *sde_kms);
/**
* sde_core_irq_domain_fini - uninstall core IRQ domain
* @sde_kms: SDE handle
* @return: 0 if success; error code otherwise
*/
int sde_core_irq_domain_fini(struct sde_kms *sde_kms);
/**
* sde_core_irq - core IRQ handler
* @sde_kms: SDE handle

View file

@ -1,4 +1,4 @@
/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@ -49,86 +49,14 @@ irqreturn_t sde_irq(struct msm_kms *kms)
return IRQ_HANDLED;
}
static void sde_hw_irq_mask(struct irq_data *irqd)
{
struct sde_kms *sde_kms;
if (!irqd || !irq_data_get_irq_chip_data(irqd)) {
SDE_ERROR("invalid parameters irqd %d\n", irqd != 0);
return;
}
sde_kms = irq_data_get_irq_chip_data(irqd);
smp_mb__before_atomic();
clear_bit(irqd->hwirq, &sde_kms->irq_controller.enabled_mask);
smp_mb__after_atomic();
}
static void sde_hw_irq_unmask(struct irq_data *irqd)
{
struct sde_kms *sde_kms;
if (!irqd || !irq_data_get_irq_chip_data(irqd)) {
SDE_ERROR("invalid parameters irqd %d\n", irqd != 0);
return;
}
sde_kms = irq_data_get_irq_chip_data(irqd);
smp_mb__before_atomic();
set_bit(irqd->hwirq, &sde_kms->irq_controller.enabled_mask);
smp_mb__after_atomic();
}
static struct irq_chip sde_hw_irq_chip = {
.name = "sde",
.irq_mask = sde_hw_irq_mask,
.irq_unmask = sde_hw_irq_unmask,
};
static int sde_hw_irqdomain_map(struct irq_domain *domain,
unsigned int irq, irq_hw_number_t hwirq)
{
struct sde_kms *sde_kms;
int rc;
if (!domain || !domain->host_data) {
SDE_ERROR("invalid parameters domain %d\n", domain != 0);
return -EINVAL;
}
sde_kms = domain->host_data;
irq_set_chip_and_handler(irq, &sde_hw_irq_chip, handle_level_irq);
rc = irq_set_chip_data(irq, sde_kms);
return rc;
}
static struct irq_domain_ops sde_hw_irqdomain_ops = {
.map = sde_hw_irqdomain_map,
.xlate = irq_domain_xlate_onecell,
};
void sde_irq_preinstall(struct msm_kms *kms)
{
struct sde_kms *sde_kms = to_sde_kms(kms);
struct device *dev;
struct irq_domain *domain;
if (!sde_kms->dev || !sde_kms->dev->dev) {
pr_err("invalid device handles\n");
return;
}
dev = sde_kms->dev->dev;
domain = irq_domain_add_linear(dev->of_node, 32,
&sde_hw_irqdomain_ops, sde_kms);
if (!domain) {
pr_err("failed to add irq_domain\n");
return;
}
sde_kms->irq_controller.enabled_mask = 0;
sde_kms->irq_controller.domain = domain;
sde_core_irq_preinstall(sde_kms);
}
@ -158,9 +86,5 @@ void sde_irq_uninstall(struct msm_kms *kms)
}
sde_core_irq_uninstall(sde_kms);
if (sde_kms->irq_controller.domain) {
irq_domain_remove(sde_kms->irq_controller.domain);
sde_kms->irq_controller.domain = NULL;
}
sde_core_irq_domain_fini(sde_kms);
}

View file

@ -27,6 +27,7 @@
#include "dsi_display.h"
#include "dsi_drm.h"
#include "sde_wb.h"
#include "sde_hdmi.h"
#include "sde_kms.h"
#include "sde_core_irq.h"
@ -504,8 +505,30 @@ static int _sde_kms_get_displays(struct sde_kms *sde_kms)
wb_display_get_displays(sde_kms->wb_displays,
sde_kms->wb_display_count);
}
/* hdmi */
sde_kms->hdmi_displays = NULL;
sde_kms->hdmi_display_count = sde_hdmi_get_num_of_displays();
SDE_DEBUG("hdmi display count=%d", sde_kms->hdmi_display_count);
if (sde_kms->hdmi_display_count) {
sde_kms->hdmi_displays = kcalloc(sde_kms->hdmi_display_count,
sizeof(void *),
GFP_KERNEL);
if (!sde_kms->hdmi_displays) {
SDE_ERROR("failed to allocate hdmi displays\n");
goto exit_deinit_hdmi;
}
sde_kms->hdmi_display_count =
sde_hdmi_get_displays(sde_kms->hdmi_displays,
sde_kms->hdmi_display_count);
}
return 0;
exit_deinit_hdmi:
sde_kms->hdmi_display_count = 0;
sde_kms->hdmi_displays = NULL;
exit_deinit_wb:
kfree(sde_kms->wb_displays);
sde_kms->wb_display_count = 0;
@ -528,6 +551,9 @@ static void _sde_kms_release_displays(struct sde_kms *sde_kms)
SDE_ERROR("invalid sde kms\n");
return;
}
kfree(sde_kms->hdmi_displays);
sde_kms->hdmi_display_count = 0;
sde_kms->hdmi_displays = NULL;
kfree(sde_kms->wb_displays);
sde_kms->wb_displays = NULL;
@ -565,6 +591,14 @@ static int _sde_kms_setup_displays(struct drm_device *dev,
.set_property = sde_wb_connector_set_property,
.get_info = sde_wb_get_info,
};
static const struct sde_connector_ops hdmi_ops = {
.pre_deinit = sde_hdmi_connector_pre_deinit,
.post_init = sde_hdmi_connector_post_init,
.detect = sde_hdmi_connector_detect,
.get_modes = sde_hdmi_connector_get_modes,
.mode_valid = sde_hdmi_mode_valid,
.get_info = sde_hdmi_get_info,
};
struct msm_display_info info;
struct drm_encoder *encoder;
void *display, *connector;
@ -576,7 +610,10 @@ static int _sde_kms_setup_displays(struct drm_device *dev,
return -EINVAL;
}
max_encoders = sde_kms->dsi_display_count + sde_kms->wb_display_count;
max_encoders = sde_kms->dsi_display_count +
sde_kms->wb_display_count +
sde_kms->hdmi_display_count;
if (max_encoders > ARRAY_SIZE(priv->encoders)) {
max_encoders = ARRAY_SIZE(priv->encoders);
SDE_ERROR("capping number of displays to %d", max_encoders);
@ -666,6 +703,54 @@ static int _sde_kms_setup_displays(struct drm_device *dev,
}
}
/* hdmi */
for (i = 0; i < sde_kms->hdmi_display_count &&
priv->num_encoders < max_encoders; ++i) {
display = sde_kms->hdmi_displays[i];
encoder = NULL;
memset(&info, 0x0, sizeof(info));
rc = sde_hdmi_dev_init(display);
if (rc) {
SDE_ERROR("hdmi dev_init %d failed\n", i);
continue;
}
rc = sde_hdmi_get_info(&info, display);
if (rc) {
SDE_ERROR("hdmi get_info %d failed\n", i);
continue;
}
encoder = sde_encoder_init(dev, &info);
if (IS_ERR_OR_NULL(encoder)) {
SDE_ERROR("encoder init failed for hdmi %d\n", i);
continue;
}
rc = sde_hdmi_drm_init(display, encoder);
if (rc) {
SDE_ERROR("hdmi drm %d init failed, %d\n", i, rc);
sde_encoder_destroy(encoder);
continue;
}
connector = sde_connector_init(dev,
encoder,
0,
display,
&hdmi_ops,
DRM_CONNECTOR_POLL_HPD,
DRM_MODE_CONNECTOR_HDMIA);
if (connector) {
priv->encoders[priv->num_encoders++] = encoder;
} else {
SDE_ERROR("hdmi %d connector init failed\n", i);
sde_hdmi_dev_deinit(display);
sde_hdmi_drm_deinit(display);
sde_encoder_destroy(encoder);
}
}
return 0;
}
@ -726,6 +811,9 @@ static int _sde_kms_drm_obj_init(struct sde_kms *sde_kms)
priv = dev->dev_private;
catalog = sde_kms->catalog;
ret = sde_core_irq_domain_add(sde_kms);
if (ret)
goto fail_irq;
/*
* Query for underlying display drivers, and create connectors,
* bridges and encoders for them.
@ -784,6 +872,8 @@ static int _sde_kms_drm_obj_init(struct sde_kms *sde_kms)
return 0;
fail:
_sde_kms_drm_obj_destroy(sde_kms);
fail_irq:
sde_core_irq_domain_fini(sde_kms);
return ret;
}
@ -1134,6 +1224,14 @@ static int sde_kms_hw_init(struct msm_kms *kms)
goto perf_err;
}
sde_kms->hw_intr = sde_hw_intr_init(sde_kms->mmio, sde_kms->catalog);
if (IS_ERR_OR_NULL(sde_kms->hw_intr)) {
rc = PTR_ERR(sde_kms->hw_intr);
SDE_ERROR("hw_intr init failed: %d\n", rc);
sde_kms->hw_intr = NULL;
goto hw_intr_init_err;
}
/*
* _sde_kms_drm_obj_init should create the DRM related objects
* i.e. CRTCs, planes, encoders, connectors and so forth
@ -1159,21 +1257,12 @@ static int sde_kms_hw_init(struct msm_kms *kms)
*/
dev->mode_config.allow_fb_modifiers = true;
sde_kms->hw_intr = sde_hw_intr_init(sde_kms->mmio, sde_kms->catalog);
if (IS_ERR_OR_NULL(sde_kms->hw_intr)) {
rc = PTR_ERR(sde_kms->hw_intr);
SDE_ERROR("hw_intr init failed: %d\n", rc);
sde_kms->hw_intr = NULL;
goto hw_intr_init_err;
}
sde_power_resource_enable(&priv->phandle, sde_kms->core_client, false);
return 0;
hw_intr_init_err:
_sde_kms_drm_obj_destroy(sde_kms);
drm_obj_init_err:
sde_core_perf_destroy(&sde_kms->perf);
hw_intr_init_err:
perf_err:
power_error:
sde_power_resource_enable(&priv->phandle, sde_kms->core_client, false);

View file

@ -154,8 +154,9 @@ struct sde_kms {
void **dsi_displays;
int wb_display_count;
void **wb_displays;
bool has_danger_ctrl;
void **hdmi_displays;
int hdmi_display_count;
};
struct vsync_info {