diff --git a/Documentation/devicetree/bindings/display/msm/hdmi.txt b/Documentation/devicetree/bindings/display/msm/hdmi.txt index 379ee2ea9a3d..0ad1e4257553 100644 --- a/Documentation/devicetree/bindings/display/msm/hdmi.txt +++ b/Documentation/devicetree/bindings/display/msm/hdmi.txt @@ -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" diff --git a/Documentation/devicetree/bindings/drm/msm/hdmi-display.txt b/Documentation/devicetree/bindings/drm/msm/hdmi-display.txt new file mode 100644 index 000000000000..68fa9e37aa61 --- /dev/null +++ b/Documentation/devicetree/bindings/drm/msm/hdmi-display.txt @@ -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"; + }; + +}; diff --git a/arch/arm/boot/dts/qcom/msm8998-sde-display.dtsi b/arch/arm/boot/dts/qcom/msm8998-sde-display.dtsi new file mode 100644 index 000000000000..6cef416eb5b0 --- /dev/null +++ b/arch/arm/boot/dts/qcom/msm8998-sde-display.dtsi @@ -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>; +}; diff --git a/arch/arm/boot/dts/qcom/msm8998-sde.dtsi b/arch/arm/boot/dts/qcom/msm8998-sde.dtsi new file mode 100644 index 000000000000..93274f391ac1 --- /dev/null +++ b/arch/arm/boot/dts/qcom/msm8998-sde.dtsi @@ -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" diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig index afd94a1e85d3..64ae44f4cbc9 100644 --- a/drivers/gpu/drm/msm/Kconfig +++ b/drivers/gpu/drm/msm/Kconfig @@ -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. diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index b77fdd098471..f83d32305569 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -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 diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c new file mode 100644 index 000000000000..840202f5760c --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.c @@ -0,0 +1,929 @@ +/* + * Copyright (c) 2016-2017, The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark + * + * 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 . + */ + +#define pr_fmt(fmt) "sde-hdmi:[%s] " fmt, __func__ + +#include +#include +#include +#include + +#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); diff --git a/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h new file mode 100644 index 000000000000..9a948b0fbcf2 --- /dev/null +++ b/drivers/gpu/drm/msm/hdmi-staging/sde_hdmi.h @@ -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 +#include +#include +#include + +#include +#include +#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_ */ diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.c b/drivers/gpu/drm/msm/hdmi/hdmi.c index ba5921149ac3..723ec887252b 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.c +++ b/drivers/gpu/drm/msm/hdmi/hdmi.c @@ -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 * @@ -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; diff --git a/drivers/gpu/drm/msm/hdmi/hdmi.h b/drivers/gpu/drm/msm/hdmi/hdmi.h index e22ddcd51248..5ee7a0feda07 100644 --- a/drivers/gpu/drm/msm/hdmi/hdmi.h +++ b/drivers/gpu/drm/msm/hdmi/hdmi.h @@ -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; diff --git a/drivers/gpu/drm/msm/sde/sde_connector.c b/drivers/gpu/drm/msm/sde/sde_connector.c index 6e27ac09c7a4..31cf25ab5691 100644 --- a/drivers/gpu/drm/msm/sde/sde_connector.c +++ b/drivers/gpu/drm/msm/sde/sde_connector.c @@ -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); diff --git a/drivers/gpu/drm/msm/sde/sde_connector.h b/drivers/gpu/drm/msm/sde/sde_connector.h index 7d86528c81bc..3f26ee7d5965 100644 --- a/drivers/gpu/drm/msm/sde/sde_connector.h +++ b/drivers/gpu/drm/msm/sde/sde_connector.h @@ -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 diff --git a/drivers/gpu/drm/msm/sde/sde_core_irq.c b/drivers/gpu/drm/msm/sde/sde_core_irq.c index b2853e874d92..dbfc2dd11a17 100644 --- a/drivers/gpu/drm/msm/sde/sde_core_irq.c +++ b/drivers/gpu/drm/msm/sde/sde_core_irq.c @@ -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) { /* diff --git a/drivers/gpu/drm/msm/sde/sde_core_irq.h b/drivers/gpu/drm/msm/sde/sde_core_irq.h index 92642e73daa8..ee1b9bd1d32b 100644 --- a/drivers/gpu/drm/msm/sde/sde_core_irq.h +++ b/drivers/gpu/drm/msm/sde/sde_core_irq.h @@ -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 diff --git a/drivers/gpu/drm/msm/sde/sde_irq.c b/drivers/gpu/drm/msm/sde/sde_irq.c index 909d6df38260..eeb7a0002eab 100644 --- a/drivers/gpu/drm/msm/sde/sde_irq.c +++ b/drivers/gpu/drm/msm/sde/sde_irq.c @@ -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); } diff --git a/drivers/gpu/drm/msm/sde/sde_kms.c b/drivers/gpu/drm/msm/sde/sde_kms.c index 576e7d1e7189..7c23421fb8a7 100644 --- a/drivers/gpu/drm/msm/sde/sde_kms.c +++ b/drivers/gpu/drm/msm/sde/sde_kms.c @@ -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); diff --git a/drivers/gpu/drm/msm/sde/sde_kms.h b/drivers/gpu/drm/msm/sde/sde_kms.h index cd3722fa7df8..44f6be959ac9 100644 --- a/drivers/gpu/drm/msm/sde/sde_kms.h +++ b/drivers/gpu/drm/msm/sde/sde_kms.h @@ -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 {