Merge "scsi: ufs: Initiate crypto before SCSI for UFS fast boot" into msm-4.9
diff --git a/Documentation/devicetree/bindings/nfc/nq-nci.txt b/Documentation/devicetree/bindings/nfc/nq-nci.txt
new file mode 100644
index 0000000..b85e070
--- /dev/null
+++ b/Documentation/devicetree/bindings/nfc/nq-nci.txt
@@ -0,0 +1,49 @@
+Qualcomm Technologies, Inc NQxxxx NFC NCI device
+
+Near Field Communication (NFC) device is based on NFC Controller Interface (NCI)
+
+Required properties:
+
+- compatible: "qcom,nq-nci"
+- reg: NCI i2c slave address.
+- qcom,nq-ven: specific gpio for hardware reset.
+- qcom,nq-irq: specific gpio for read interrupt.
+- qcom,nq-firm: gpio for firmware download
+- qcom,nq-clkreq: gpio for clock
+- interrupt-parent: Should be phandle for the interrupt controller
+ that services interrupts for this device.
+- interrupts: Nfc read interrupt,gpio-clk-req interrupt
+
+
+Recommended properties:
+
+- interrupt-names: names of interrupts, should include "nfc_irq", used for reference
+
+
+Optional properties:
+
+- pinctrl-names, pinctrl-0, pincntrl-1: references to our pincntrl settings
+- clocks, clock-names: must contain the NQxxxx's core clock.
+- qcom,nq-esepwr: gpio to control power of secure element
+
+Example:
+
+ nq-nci@2b {
+ compatible = "qcom,nq-nci";
+ reg = <0x2b>;
+ qcom,nq-irq = <&tlmm 29 0x00>;
+ qcom,nq-ven = <&tlmm 30 0x00>;
+ qcom,nq-firm = <&tlmm 93 0x00>;
+ qcom,nq-clkreq = <&pm8998_gpios 21 0x00>;
+ qcom,nq-esepwr = <&tlmm 116 0x00>;
+ qcom,clk-src = "BBCLK2";
+ interrupt-parent = <&tlmm>;
+ interrupts = <29 0>;
+ interrupt-names = "nfc_irq";
+ pinctrl-names = "nfc_active","nfc_suspend";
+ pinctrl-0 = <&nfc_int_active &nfc_disable_active>;
+ pinctrl-1 = <&nfc_int_suspend &nfc_disable_suspend>;
+ qcom,clk-gpio = <&pm8916_gpios 2 0>;
+ clocks = <&clock_rpm clk_bb_clk2_pin>;
+ clock-names = "ref_clk";
+ };
diff --git a/arch/arm64/boot/dts/qcom/sdm845-bus.dtsi b/arch/arm64/boot/dts/qcom/sdm845-bus.dtsi
index ea66a13..b1c91bf 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-bus.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-bus.dtsi
@@ -1644,7 +1644,7 @@
qcom,buswidth = <4>;
qcom,agg-ports = <4>;
qcom,bus-dev = <&fab_mc_virt>;
- qcom,bcms = <&bcm_mc0>;
+ qcom,bcms = <&bcm_mc0>, <&bcm_acv>;
};
slv_qhs_mdsp_ms_mpu_cfg:slv-qhs-mdsp-ms-mpu-cfg {
diff --git a/arch/arm64/boot/dts/qcom/sdm845-camera.dtsi b/arch/arm64/boot/dts/qcom/sdm845-camera.dtsi
index 4c642e3..51e1ccf 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-camera.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-camera.dtsi
@@ -464,7 +464,7 @@
cell-index = <0>;
label = "cam-cdm-intf";
num-hw-cdm = <1>;
- cdm-client-names = "ife",
+ cdm-client-names = "vfe",
"jpeg-dma",
"jpeg",
"fd";
@@ -493,7 +493,7 @@
<&clock_camcc CAM_CC_CPAS_AHB_CLK>,
<&clock_camcc CAM_CC_CAMNOC_AXI_CLK>;
clock-rates = <0 0 0 0 0>;
- cdm-client-names = "vfe";
+ cdm-client-names = "ife";
status = "ok";
};
diff --git a/arch/arm64/boot/dts/qcom/sdm845-cdp.dtsi b/arch/arm64/boot/dts/qcom/sdm845-cdp.dtsi
index 1fdf740..7038d48 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-cdp.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-cdp.dtsi
@@ -291,6 +291,24 @@
&qupv3_se3_i2c {
status = "ok";
+ nq@28 {
+ compatible = "qcom,nq-nci";
+ reg = <0x28>;
+ qcom,nq-irq = <&tlmm 63 0x00>;
+ qcom,nq-ven = <&tlmm 12 0x00>;
+ qcom,nq-firm = <&tlmm 62 0x00>;
+ qcom,nq-clkreq = <&pm8998_gpios 21 0x00>;
+ qcom,nq-esepwr = <&tlmm 116 0x00>;
+ interrupt-parent = <&tlmm>;
+ qcom,clk-src = "BBCLK3";
+ interrupts = <63 0>;
+ interrupt-names = "nfc_irq";
+ pinctrl-names = "nfc_active", "nfc_suspend";
+ pinctrl-0 = <&nfc_int_active &nfc_enable_active>;
+ pinctrl-1 = <&nfc_int_suspend &nfc_enable_suspend>;
+ clocks = <&clock_rpmh RPMH_LN_BB_CLK3>;
+ clock-names = "ref_clk";
+ };
};
&qupv3_se10_i2c {
diff --git a/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi b/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi
index 1b3f2a6..faf43fca 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-gpu.dtsi
@@ -237,7 +237,7 @@
label = "kgsl-gmu";
compatible = "qcom,gpu-gmu";
- reg = <0x506a000 0x26000>, <0xb200000 0x300000>;
+ reg = <0x506a000 0x30000>, <0xb200000 0x300000>;
reg-names = "kgsl_gmu_reg", "kgsl_gmu_pdc_reg";
interrupts = <0 304 0>, <0 305 0>;
diff --git a/arch/arm64/boot/dts/qcom/sdm845-mtp.dtsi b/arch/arm64/boot/dts/qcom/sdm845-mtp.dtsi
index 508b645..521fd6b3 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-mtp.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-mtp.dtsi
@@ -262,6 +262,24 @@
&qupv3_se3_i2c {
status = "ok";
+ nq@28 {
+ compatible = "qcom,nq-nci";
+ reg = <0x28>;
+ qcom,nq-irq = <&tlmm 63 0x00>;
+ qcom,nq-ven = <&tlmm 12 0x00>;
+ qcom,nq-firm = <&tlmm 62 0x00>;
+ qcom,nq-clkreq = <&pm8998_gpios 21 0x00>;
+ qcom,nq-esepwr = <&tlmm 116 0x00>;
+ interrupt-parent = <&tlmm>;
+ qcom,clk-src = "BBCLK3";
+ interrupts = <63 0>;
+ interrupt-names = "nfc_irq";
+ pinctrl-names = "nfc_active", "nfc_suspend";
+ pinctrl-0 = <&nfc_int_active &nfc_enable_active>;
+ pinctrl-1 = <&nfc_int_suspend &nfc_enable_suspend>;
+ clocks = <&clock_rpmh RPMH_LN_BB_CLK3>;
+ clock-names = "ref_clk";
+ };
};
&qupv3_se10_i2c {
diff --git a/arch/arm64/boot/dts/qcom/sdm845-pinctrl.dtsi b/arch/arm64/boot/dts/qcom/sdm845-pinctrl.dtsi
index cfbf3e5..f534891 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-pinctrl.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-pinctrl.dtsi
@@ -1556,6 +1556,68 @@
};
};
+ nfc {
+ nfc_int_active: nfc_int_active {
+ /* active state */
+ mux {
+ /* GPIO 63 NFC Read Interrupt */
+ pins = "gpio63";
+ function = "gpio";
+ };
+
+ config {
+ pins = "gpio63";
+ drive-strength = <2>; /* 2 MA */
+ bias-pull-up;
+ };
+ };
+
+ nfc_int_suspend: nfc_int_suspend {
+ /* sleep state */
+ mux {
+ /* GPIO 63 NFC Read Interrupt */
+ pins = "gpio63";
+ function = "gpio";
+ };
+
+ config {
+ pins = "gpio63";
+ drive-strength = <2>; /* 2 MA */
+ bias-pull-up;
+ };
+ };
+
+ nfc_enable_active: nfc_enable_active {
+ /* active state */
+ mux {
+ /* 12: NFC ENABLE 116:ESE Enable */
+ pins = "gpio12", "gpio62", "gpio116";
+ function = "gpio";
+ };
+
+ config {
+ pins = "gpio12", "gpio62", "gpio116";
+ drive-strength = <2>; /* 2 MA */
+ bias-pull-up;
+ };
+ };
+
+ nfc_enable_suspend: nfc_enable_suspend {
+ /* sleep state */
+ mux {
+ /* 12: NFC ENABLE 116:ESE Enable */
+ pins = "gpio12", "gpio62", "gpio116";
+ function = "gpio";
+ };
+
+ config {
+ pins = "gpio12", "gpio62", "gpio116";
+ drive-strength = <2>; /* 2 MA */
+ bias-disable;
+ };
+ };
+ };
+
qupv3_se3_spi_pins: qupv3_se3_spi_pins {
qupv3_se3_spi_active: qupv3_se3_spi_active {
mux {
@@ -2681,6 +2743,14 @@
};
&pm8998_gpios {
+ gpio@d400 {
+ qcom,mode = <0>;
+ qcom,vin-sel = <1>;
+ qcom,src-sel = <0>;
+ qcom,master-en = <1>;
+ status = "okay";
+ };
+
key_home {
key_home_default: key_home_default {
pins = "gpio5";
diff --git a/arch/arm64/boot/dts/qcom/sdm845-qrd.dtsi b/arch/arm64/boot/dts/qcom/sdm845-qrd.dtsi
index 0aaac6f..3bf1ea4 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-qrd.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-qrd.dtsi
@@ -60,6 +60,24 @@
&qupv3_se3_i2c {
status = "ok";
+ nq@28 {
+ compatible = "qcom,nq-nci";
+ reg = <0x28>;
+ qcom,nq-irq = <&tlmm 63 0x00>;
+ qcom,nq-ven = <&tlmm 12 0x00>;
+ qcom,nq-firm = <&tlmm 62 0x00>;
+ qcom,nq-clkreq = <&pm8998_gpios 21 0x00>;
+ qcom,nq-esepwr = <&tlmm 116 0x00>;
+ interrupt-parent = <&tlmm>;
+ qcom,clk-src = "BBCLK3";
+ interrupts = <63 0>;
+ interrupt-names = "nfc_irq";
+ pinctrl-names = "nfc_active", "nfc_suspend";
+ pinctrl-0 = <&nfc_int_active &nfc_enable_active>;
+ pinctrl-1 = <&nfc_int_suspend &nfc_enable_suspend>;
+ clocks = <&clock_rpmh RPMH_LN_BB_CLK3>;
+ clock-names = "ref_clk";
+ };
};
&qupv3_se10_i2c {
diff --git a/arch/arm64/boot/dts/qcom/sdm845-sde.dtsi b/arch/arm64/boot/dts/qcom/sdm845-sde.dtsi
index 21819a9..d18e2e7 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-sde.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-sde.dtsi
@@ -261,7 +261,7 @@
interrupt-parent = <&mdss_mdp>;
interrupts = <2 0>;
- qcom,mdss-rot-vbif-qos-setting = <1 1 1 1>;
+ qcom,mdss-rot-vbif-qos-setting = <3 3 3 3 3 3 3 3>;
qcom,mdss-default-ot-rd-limit = <32>;
qcom,mdss-default-ot-wr-limit = <32>;
diff --git a/arch/arm64/boot/dts/qcom/sdm845.dtsi b/arch/arm64/boot/dts/qcom/sdm845.dtsi
index 7fea651..fc90fef 100644
--- a/arch/arm64/boot/dts/qcom/sdm845.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi
@@ -808,7 +808,8 @@
< 1881600 >,
< 1958400 >,
< 2035200 >,
- < 2092800 >;
+ < 2092800 >,
+ < 2208000 >;
};
cpubw: qcom,cpubw {
@@ -962,7 +963,9 @@
< 883200 >,
< 960000 >,
< 1036800 >,
- < 1094400 >;
+ < 1094400 >,
+ < 1209600 >,
+ < 1305600 >;
};
l3_cpu4: qcom,l3-cpu4 {
@@ -982,7 +985,9 @@
< 883200 >,
< 960000 >,
< 1036800 >,
- < 1094400 >;
+ < 1094400 >,
+ < 1209600 >,
+ < 1305600 >;
};
devfreq_l3lat_0: qcom,cpu0-l3lat-mon {
@@ -1042,8 +1047,8 @@
cpu-to-dev-map-0 =
< 1708800 762 >;
cpu-to-dev-map-4 =
- < 2035200 762 >,
- < 2092800 2597 >;
+ < 1881600 762 >,
+ < 2208000 2597 >;
};
};
@@ -1141,6 +1146,9 @@
vdd-pwrcl-supply = <&apc0_pwrcl_vreg>;
vdd-perfcl-supply = <&apc1_perfcl_vreg>;
+ l3-dev0 = <&l3_cpu0>;
+ l3-dev4 = <&l3_cpu4>;
+
qcom,l3-speedbin0-v0 =
< 300000000 0x000c000f 0x00002020 0x1 1 >,
< 422400000 0x50140116 0x00002020 0x1 2 >,
@@ -1165,6 +1173,21 @@
< 1036800000 0x40240936 0x00002b2b 0x3 10 >,
< 1094400000 0x402c0a39 0x00002e2e 0x3 11 >;
+ qcom,l3-speedbin2-v0 =
+ < 300000000 0x000c000f 0x00002020 0x1 1 >,
+ < 422400000 0x50140116 0x00002020 0x1 2 >,
+ < 499200000 0x5014021a 0x00002020 0x1 3 >,
+ < 576000000 0x5014031e 0x00002020 0x1 4 >,
+ < 652800000 0x401c0422 0x00002020 0x1 5 >,
+ < 729600000 0x401c0526 0x00002020 0x1 6 >,
+ < 806400000 0x401c062a 0x00002222 0x1 7 >,
+ < 883200000 0x4024072e 0x00002525 0x2 8 >,
+ < 960000000 0x40240832 0x00002828 0x2 9 >,
+ < 1036800000 0x40240936 0x00002b2b 0x3 10 >,
+ < 1113600000 0x402c0a3a 0x00002e2e 0x3 11 >,
+ < 1209600000 0x402c0b3f 0x00003232 0x3 12 >,
+ < 1305600000 0x40340c44 0x00003636 0x3 13 >;
+
qcom,pwrcl-speedbin0-v0 =
< 300000000 0x000c000f 0x00002020 0x1 1 >,
< 422400000 0x50140116 0x00002020 0x1 2 >,
@@ -1205,6 +1228,27 @@
< 1651200000 0x403c1156 0x00004545 0x3 18 >,
< 1708800000 0x40441259 0x00004747 0x3 19 >;
+ qcom,pwrcl-speedbin2-v0 =
+ < 300000000 0x000c000f 0x00002020 0x1 1 >,
+ < 422400000 0x50140116 0x00002020 0x1 2 >,
+ < 499200000 0x5014021a 0x00002020 0x1 3 >,
+ < 576000000 0x5014031e 0x00002020 0x1 4 >,
+ < 652800000 0x401c0422 0x00002020 0x1 5 >,
+ < 748800000 0x401c0527 0x00002020 0x1 6 >,
+ < 825600000 0x401c062b 0x00002222 0x1 7 >,
+ < 902400000 0x4024072f 0x00002626 0x1 8 >,
+ < 979200000 0x40240833 0x00002929 0x1 9 >,
+ < 1056000000 0x402c0937 0x00002c2c 0x1 10 >,
+ < 1132800000 0x402c0a3b 0x00002f2f 0x1 11 >,
+ < 1209600000 0x402c0b3f 0x00003232 0x1 12 >,
+ < 1286400000 0x40340c43 0x00003636 0x2 13 >,
+ < 1363200000 0x40340d47 0x00003939 0x2 14 >,
+ < 1440000000 0x40340e4b 0x00003c3c 0x2 15 >,
+ < 1516800000 0x403c0f4f 0x00003f3f 0x2 16 >,
+ < 1593600000 0x403c1053 0x00004242 0x2 17 >,
+ < 1670400000 0x40441157 0x00004646 0x3 18 >,
+ < 1747200000 0x4044125b 0x00004949 0x3 19 >;
+
qcom,perfcl-speedbin0-v0 =
< 300000000 0x000c000f 0x00002020 0x1 1 >,
< 422400000 0x50140116 0x00002020 0x1 2 >,
@@ -1255,6 +1299,33 @@
< 2035200000 0x404c166a 0x00005555 0x3 23 >,
< 2092800000 0x4054176d 0x00005757 0x3 24 >;
+ qcom,perfcl-speedbin2-v0 =
+ < 300000000 0x000c000f 0x00002020 0x1 1 >,
+ < 422400000 0x50140116 0x00002020 0x1 2 >,
+ < 499200000 0x5014021a 0x00002020 0x1 3 >,
+ < 576000000 0x5014031e 0x00002020 0x1 4 >,
+ < 652800000 0x401c0422 0x00002020 0x1 5 >,
+ < 729600000 0x401c0526 0x00002020 0x1 6 >,
+ < 806400000 0x401c062a 0x00002222 0x1 7 >,
+ < 883200000 0x4024072e 0x00002525 0x1 8 >,
+ < 960000000 0x40240832 0x00002828 0x1 9 >,
+ < 1036800000 0x40240936 0x00002b2b 0x1 10 >,
+ < 1113600000 0x402c0a3a 0x00002e2e 0x1 11 >,
+ < 1190400000 0x402c0b3e 0x00003232 0x1 12 >,
+ < 1267200000 0x40340c42 0x00003535 0x2 13 >,
+ < 1344000000 0x40340d46 0x00003838 0x2 14 >,
+ < 1420800000 0x40340e4a 0x00003b3b 0x2 15 >,
+ < 1497600000 0x403c0f4e 0x00003e3e 0x2 16 >,
+ < 1574400000 0x403c1052 0x00004242 0x2 17 >,
+ < 1651200000 0x403c1156 0x00004545 0x2 18 >,
+ < 1728000000 0x4044125a 0x00004848 0x3 19 >,
+ < 1804800000 0x4044135e 0x00004b4b 0x3 20 >,
+ < 1881600000 0x404c1462 0x00004e4e 0x3 21 >,
+ < 1958400000 0x404c1566 0x00005252 0x3 22 >,
+ < 2035200000 0x404c166a 0x00005555 0x3 23 >,
+ < 2112000000 0x4054176e 0x00005858 0x3 24 >,
+ < 2208000000 0x40541873 0x00005c5c 0x3 25 >;
+
qcom,l3-min-cpr-vc-bin0 = <7>;
qcom,pwrcl-min-cpr-vc-bin0 = <6>;
qcom,perfcl-min-cpr-vc-bin0 = <7>;
diff --git a/drivers/base/firmware_class.c b/drivers/base/firmware_class.c
index 4256d9b..b0beb52 100644
--- a/drivers/base/firmware_class.c
+++ b/drivers/base/firmware_class.c
@@ -206,6 +206,7 @@ static struct firmware_buf *__allocate_fw_buf(const char *fw_name,
buf->data = dbuf;
buf->allocated_size = size;
init_completion(&buf->completion);
+ INIT_LIST_HEAD(&buf->list);
#ifdef CONFIG_FW_LOADER_USER_HELPER
INIT_LIST_HEAD(&buf->pending_list);
#endif
diff --git a/drivers/clk/qcom/clk-cpu-osm.c b/drivers/clk/qcom/clk-cpu-osm.c
index 4efecef..3a0677f 100644
--- a/drivers/clk/qcom/clk-cpu-osm.c
+++ b/drivers/clk/qcom/clk-cpu-osm.c
@@ -1737,6 +1737,8 @@ static void populate_opp_table(struct platform_device *pdev)
struct device *cpu_dev;
struct clk_osm *c, *parent;
struct clk_hw *hw_parent;
+ struct device_node *l3_node_0, *l3_node_4;
+ struct platform_device *l3_dev_0, *l3_dev_4;
for_each_possible_cpu(cpu) {
c = logical_cpu_to_clk(cpu);
@@ -1754,7 +1756,35 @@ static void populate_opp_table(struct platform_device *pdev)
dev_name(cpu_dev));
}
- /*TODO: Figure out which device to tag the L3 table to */
+ l3_node_0 = of_parse_phandle(pdev->dev.of_node, "l3-dev0", 0);
+ if (!l3_node_0) {
+ pr_err("can't find the L3 cluster 0 dt node\n");
+ return;
+ }
+
+ l3_dev_0 = of_find_device_by_node(l3_node_0);
+ if (!l3_dev_0) {
+ pr_err("can't find the L3 cluster 0 dt device\n");
+ return;
+ }
+
+ if (add_opp(&l3_clk, &l3_dev_0->dev))
+ pr_err("Failed to add OPP levels for L3 cluster 0\n");
+
+ l3_node_4 = of_parse_phandle(pdev->dev.of_node, "l3-dev4", 0);
+ if (!l3_node_4) {
+ pr_err("can't find the L3 cluster 1 dt node\n");
+ return;
+ }
+
+ l3_dev_4 = of_find_device_by_node(l3_node_4);
+ if (!l3_dev_4) {
+ pr_err("can't find the L3 cluster 1 dt device\n");
+ return;
+ }
+
+ if (add_opp(&l3_clk, &l3_dev_4->dev))
+ pr_err("Failed to add OPP levels for L3 cluster 1\n");
}
static u64 clk_osm_get_cpu_cycle_counter(int cpu)
diff --git a/drivers/devfreq/governor_memlat.h b/drivers/devfreq/governor_memlat.h
index a0e52a0..8c533ee 100644
--- a/drivers/devfreq/governor_memlat.h
+++ b/drivers/devfreq/governor_memlat.h
@@ -1,5 +1,5 @@
/*
- * 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
@@ -11,8 +11,8 @@
* GNU General Public License for more details.
*/
-#ifndef _GOVERNOR_BW_HWMON_H
-#define _GOVERNOR_BW_HWMON_H
+#ifndef _GOVERNOR_MEMLAT_H
+#define _GOVERNOR_MEMLAT_H
#include <linux/kernel.h>
#include <linux/devfreq.h>
diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c
index 0bc0afb..22a5a8d 100644
--- a/drivers/gpu/drm/drm_edid.c
+++ b/drivers/gpu/drm/drm_edid.c
@@ -90,6 +90,14 @@ struct detailed_mode_closure {
#define LEVEL_GTF2 2
#define LEVEL_CVT 3
+/*Enum storing luminance types for HDR blocks in EDID*/
+enum luminance_value {
+ NO_LUMINANCE_DATA = 3,
+ MAXIMUM_LUMINANCE = 4,
+ FRAME_AVERAGE_LUMINANCE = 5,
+ MINIMUM_LUMINANCE = 6
+};
+
static const struct edid_quirk {
char vendor[4];
int product_id;
@@ -997,6 +1005,221 @@ static const struct drm_display_mode edid_cea_modes[] = {
2492, 2640, 0, 1080, 1084, 1089, 1125, 0,
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
.vrefresh = 100, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9, },
+ /* 65 - 1280x720@24Hz */
+ { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 59400, 1280, 3040,
+ 3080, 3300, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 24, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 66 - 1280x720@25Hz */
+ { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 3700,
+ 3740, 3960, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 25, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 67 - 1280x720@30Hz */
+ { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 3040,
+ 3080, 3300, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 30, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 68 - 1280x720@50Hz */
+ { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1720,
+ 1760, 1980, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 50, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 69 - 1280x720@60Hz */
+ { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 74250, 1280, 1390,
+ 1430, 1650, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 60, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 70 - 1280x720@100Hz */
+ { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1720,
+ 1760, 1980, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 100, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 71 - 1280x720@120Hz */
+ { DRM_MODE("1280x720", DRM_MODE_TYPE_DRIVER, 148500, 1280, 1390,
+ 1430, 1650, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 120, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 72 - 1920x1080@24Hz */
+ { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2558,
+ 2602, 2750, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 24, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 73 - 1920x1080@25Hz */
+ { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2448,
+ 2492, 2640, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 25, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 74 - 1920x1080@30Hz */
+ { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 74250, 1920, 2008,
+ 2052, 2200, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 30, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 75 - 1920x1080@50Hz */
+ { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2448,
+ 2492, 2640, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 50, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 76 - 1920x1080@60Hz */
+ { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 148500, 1920, 2008,
+ 2052, 2200, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 60, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 77 - 1920x1080@100Hz */
+ { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2448,
+ 2492, 2640, 0, 1080, 1084, 1094, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 100, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 78 - 1920x1080@120Hz */
+ { DRM_MODE("1920x1080", DRM_MODE_TYPE_DRIVER, 297000, 1920, 2008,
+ 2052, 2200, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 120, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 79 - 1680x720@24Hz */
+ { DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 59400, 1680, 3040,
+ 3080, 3300, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 24, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 80 - 1680x720@25Hz */
+ { DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 59400, 1680, 2908,
+ 2948, 3168, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 25, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 81 - 1680x720@30Hz */
+ { DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 59400, 1680, 2380,
+ 2420, 2640, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 30, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 82 - 1680x720@50Hz */
+ { DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 82500, 1680, 1940,
+ 1980, 2200, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 50, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 83 - 1680x720@60Hz */
+ { DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 99000, 1680, 1940,
+ 1980, 2200, 0, 720, 725, 730, 750, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 60, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 84 - 1680x720@100Hz */
+ { DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 165000, 1680, 1740,
+ 1780, 2000, 0, 720, 725, 730, 825, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 100, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 85 - 1680x720@120Hz */
+ { DRM_MODE("1680x720", DRM_MODE_TYPE_DRIVER, 198000, 1680, 1740,
+ 1780, 2000, 0, 720, 725, 730, 825, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 120, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 86 - 2560x1080@24Hz */
+ { DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 99000, 2560, 3558,
+ 3602, 3750, 0, 1080, 1084, 1089, 1100, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 24, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 87 - 2560x1080@25Hz */
+ { DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 90000, 2560, 3008,
+ 3052, 3200, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 25, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 88 - 2560x1080@30Hz */
+ { DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 118800, 2560, 3328,
+ 3372, 3520, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 30, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 89 - 2560x1080@50Hz */
+ { DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 185625, 2560, 3108,
+ 3152, 3300, 0, 1080, 1084, 1089, 1125, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 50, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 90 - 2560x1080@60Hz */
+ { DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 198000, 2560, 2808,
+ 2852, 3000, 0, 1080, 1084, 1089, 1100, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 60, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 91 - 2560x1080@100Hz */
+ { DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 371250, 2560, 2778,
+ 2822, 2970, 0, 1080, 1084, 1089, 1250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 100, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 92 - 2560x1080@120Hz */
+ { DRM_MODE("2560x1080", DRM_MODE_TYPE_DRIVER, 495000, 2560, 3108,
+ 3152, 3300, 0, 1080, 1084, 1089, 1250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 120, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27, },
+ /* 93 - 3840x2160p@24Hz 16:9 */
+ { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 5116,
+ 5204, 5500, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 24, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9,},
+ /* 94 - 3840x2160p@25Hz 16:9 */
+ { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 4896,
+ 4984, 5280, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 25, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9},
+ /* 95 - 3840x2160p@30Hz 16:9 */
+ { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 4016,
+ 4104, 4400, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 30, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9},
+ /* 96 - 3840x2160p@50Hz 16:9 */
+ { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4896,
+ 4984, 5280, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 50, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9},
+ /* 97 - 3840x2160p@60Hz 16:9 */
+ { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4016,
+ 4104, 4400, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 60, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_16_9},
+ /* 98 - 4096x2160p@24Hz 256:135 */
+ { DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 297000, 4096, 5116,
+ 5204, 5500, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 24, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135},
+ /* 99 - 4096x2160p@25Hz 256:135 */
+ { DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 297000, 4096, 5064,
+ 5152, 5280, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 25, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135},
+ /* 100 - 4096x2160p@30Hz 256:135 */
+ { DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 297000, 4096, 4184,
+ 4272, 4400, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 30, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135},
+ /* 101 - 4096x2160p@50Hz 256:135 */
+ { DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 594000, 4096, 5064,
+ 5152, 5280, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 50, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135},
+ /* 102 - 4096x2160p@60Hz 256:135 */
+ { DRM_MODE("4096x2160", DRM_MODE_TYPE_DRIVER, 594000, 4096, 4184,
+ 4272, 4400, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 60, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_256_135},
+ /* 103 - 3840x2160p@24Hz 64:27 */
+ { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 5116,
+ 5204, 5500, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 24, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27},
+ /* 104 - 3840x2160p@25Hz 64:27 */
+ { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 4016,
+ 4104, 4400, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 25, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27},
+ /* 105 - 3840x2160p@30Hz 64:27 */
+ { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 297000, 3840, 4016,
+ 4104, 4400, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 30, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27},
+ /* 106 - 3840x2160p@50Hz 64:27 */
+ { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4896,
+ 4984, 5280, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 50, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27},
+ /* 107 - 3840x2160p@60Hz 64:27 */
+ { DRM_MODE("3840x2160", DRM_MODE_TYPE_DRIVER, 594000, 3840, 4016,
+ 4104, 4400, 0, 2160, 2168, 2178, 2250, 0,
+ DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_PVSYNC),
+ .vrefresh = 60, .picture_aspect_ratio = HDMI_PICTURE_ASPECT_64_27},
};
/*
@@ -2514,12 +2737,15 @@ add_detailed_modes(struct drm_connector *connector, struct edid *edid,
return closure.modes;
}
-
+#define VIDEO_CAPABILITY_EXTENDED_DATA_BLOCK 0x0
#define AUDIO_BLOCK 0x01
#define VIDEO_BLOCK 0x02
#define VENDOR_BLOCK 0x03
#define SPEAKER_BLOCK 0x04
+#define HDR_STATIC_METADATA_EXTENDED_DATA_BLOCK 0x06
+#define EXTENDED_TAG 0x07
#define VIDEO_CAPABILITY_BLOCK 0x07
+#define Y420_VIDEO_DATA_BLOCK 0x0E
#define EDID_BASIC_AUDIO (1 << 6)
#define EDID_CEA_YCRCB444 (1 << 5)
#define EDID_CEA_YCRCB422 (1 << 4)
@@ -3168,6 +3394,21 @@ static bool cea_db_is_hdmi_vsdb(const u8 *db)
return hdmi_id == HDMI_IEEE_OUI;
}
+static bool cea_db_is_hdmi_hf_vsdb(const u8 *db)
+{
+ int hdmi_id;
+
+ if (cea_db_tag(db) != VENDOR_BLOCK)
+ return false;
+
+ if (cea_db_payload_len(db) < 7)
+ return false;
+
+ hdmi_id = db[1] | (db[2] << 8) | (db[3] << 16);
+
+ return hdmi_id == HDMI_IEEE_OUI_HF;
+}
+
#define for_each_cea_db(cea, i, start, end) \
for ((i) = (start); (i) < (end) && (i) + cea_db_payload_len(&(cea)[(i)]) < (end); (i) += cea_db_payload_len(&(cea)[(i)]) + 1)
@@ -3287,6 +3528,227 @@ drm_parse_hdmi_vsdb_audio(struct drm_connector *connector, const u8 *db)
}
static void
+parse_hdmi_hf_vsdb(struct drm_connector *connector, const u8 *db)
+{
+ u8 len = cea_db_payload_len(db);
+
+ if (len < 7)
+ return;
+
+ if (db[4] != 1)
+ return; /* invalid version */
+
+ connector->max_tmds_char = db[5] * 5;
+ connector->scdc_present = db[6] & (1 << 7);
+ connector->rr_capable = db[6] & (1 << 6);
+ connector->flags_3d = db[6] & 0x7;
+ connector->supports_scramble = connector->scdc_present &&
+ (db[6] & (1 << 3));
+
+ DRM_DEBUG_KMS("HDMI v2: max TMDS char %d, "
+ "scdc %s, "
+ "rr %s, "
+ "3D flags 0x%x, "
+ "scramble %s\n",
+ connector->max_tmds_char,
+ connector->scdc_present ? "available" : "not available",
+ connector->rr_capable ? "capable" : "not capable",
+ connector->flags_3d,
+ connector->supports_scramble ?
+ "supported" : "not supported");
+}
+
+/*
+ * drm_extract_vcdb_info - Parse the HDMI Video Capability Data Block
+ * @connector: connector corresponding to the HDMI sink
+ * @db: start of the CEA vendor specific block
+ *
+ * Parses the HDMI VCDB to extract sink info for @connector.
+ */
+static void
+drm_extract_vcdb_info(struct drm_connector *connector, const u8 *db)
+{
+ /*
+ * Check if the sink specifies underscan
+ * support for:
+ * BIT 5: preferred video format
+ * BIT 3: IT video format
+ * BIT 1: CE video format
+ */
+
+ connector->pt_scan_info =
+ (db[2] & (BIT(4) | BIT(5))) >> 4;
+ connector->it_scan_info =
+ (db[2] & (BIT(3) | BIT(2))) >> 2;
+ connector->ce_scan_info =
+ db[2] & (BIT(1) | BIT(0));
+
+ DRM_DEBUG_KMS("Scan Info (pt|it|ce): (%d|%d|%d)",
+ (int) connector->pt_scan_info,
+ (int) connector->it_scan_info,
+ (int) connector->ce_scan_info);
+}
+
+static bool drm_edid_is_luminance_value_present(
+u32 block_length, enum luminance_value value)
+{
+ return block_length > NO_LUMINANCE_DATA && value <= block_length;
+}
+
+/*
+ * drm_extract_hdr_db - Parse the HDMI HDR extended block
+ * @connector: connector corresponding to the HDMI sink
+ * @db: start of the HDMI HDR extended block
+ *
+ * Parses the HDMI HDR extended block to extract sink info for @connector.
+ */
+static void
+drm_extract_hdr_db(struct drm_connector *connector, const u8 *db)
+{
+
+ u8 len = 0;
+
+ if (!db)
+ return;
+
+ len = db[0] & 0x1f;
+ /* Byte 3: Electro-Optical Transfer Functions */
+ connector->hdr_eotf = db[2] & 0x3F;
+
+ /* Byte 4: Static Metadata Descriptor Type 1 */
+ connector->hdr_metadata_type_one = (db[3] & BIT(0));
+
+ /* Byte 5: Desired Content Maximum Luminance */
+ if (drm_edid_is_luminance_value_present(len, MAXIMUM_LUMINANCE))
+ connector->hdr_max_luminance =
+ db[MAXIMUM_LUMINANCE];
+
+ /* Byte 6: Desired Content Max Frame-average Luminance */
+ if (drm_edid_is_luminance_value_present(len, FRAME_AVERAGE_LUMINANCE))
+ connector->hdr_avg_luminance =
+ db[FRAME_AVERAGE_LUMINANCE];
+
+ /* Byte 7: Desired Content Min Luminance */
+ if (drm_edid_is_luminance_value_present(len, MINIMUM_LUMINANCE))
+ connector->hdr_min_luminance =
+ db[MINIMUM_LUMINANCE];
+
+ connector->hdr_supported = true;
+
+ DRM_DEBUG_KMS("HDR electro-optical %d\n", connector->hdr_eotf);
+ DRM_DEBUG_KMS("metadata desc 1 %d\n", connector->hdr_metadata_type_one);
+ DRM_DEBUG_KMS("max luminance %d\n", connector->hdr_max_luminance);
+ DRM_DEBUG_KMS("avg luminance %d\n", connector->hdr_avg_luminance);
+ DRM_DEBUG_KMS("min luminance %d\n", connector->hdr_min_luminance);
+}
+
+/*
+ * drm_hdmi_extract_extended_blk_info - Parse the HDMI extended tag blocks
+ * @connector: connector corresponding to the HDMI sink
+ * @edid: handle to the EDID structure
+ * Parses the all extended tag blocks extract sink info for @connector.
+ */
+static void
+drm_hdmi_extract_extended_blk_info(struct drm_connector *connector,
+struct edid *edid)
+{
+ const u8 *cea = drm_find_cea_extension(edid);
+ const u8 *db = NULL;
+
+ if (cea && cea_revision(cea) >= 3) {
+ int i, start, end;
+
+ if (cea_db_offsets(cea, &start, &end))
+ return;
+
+ for_each_cea_db(cea, i, start, end) {
+ db = &cea[i];
+
+ if (cea_db_tag(db) == EXTENDED_TAG) {
+ DRM_DEBUG_KMS("found extended tag block = %d\n",
+ db[1]);
+ switch (db[1]) {
+ case VIDEO_CAPABILITY_EXTENDED_DATA_BLOCK:
+ drm_extract_vcdb_info(connector, db);
+ break;
+ case HDR_STATIC_METADATA_EXTENDED_DATA_BLOCK:
+ drm_extract_hdr_db(connector, db);
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
+
+static u8 *
+drm_edid_find_extended_tag_block(struct edid *edid, int blk_id)
+{
+ u8 *db = NULL;
+ u8 *cea = NULL;
+
+ if (!edid)
+ return NULL;
+
+ cea = drm_find_cea_extension(edid);
+
+ if (cea && cea_revision(cea) >= 3) {
+ int i, start, end;
+
+ if (cea_db_offsets(cea, &start, &end))
+ return NULL;
+
+ for_each_cea_db(cea, i, start, end) {
+ db = &cea[i];
+ if ((cea_db_tag(db) == EXTENDED_TAG) &&
+ (db[1] == blk_id))
+ return db;
+ }
+ }
+ return NULL;
+}
+
+/*
+ * add_YCbCr420VDB_modes - add the modes found in Ycbcr420 VDB block
+ * @connector: connector corresponding to the HDMI sink
+ * @edid: handle to the EDID structure
+ * Parses the YCbCr420 VDB block and adds the modes to @connector.
+ */
+static int
+add_YCbCr420VDB_modes(struct drm_connector *connector, struct edid *edid)
+{
+
+ const u8 *db = NULL;
+ u32 i = 0;
+ u32 modes = 0;
+ u32 video_format = 0;
+ u8 len = 0;
+
+ /*Find the YCbCr420 VDB*/
+ db = drm_edid_find_extended_tag_block(edid, Y420_VIDEO_DATA_BLOCK);
+ /* Offset to byte 3 */
+ if (db) {
+ len = db[0] & 0x1F;
+ db += 2;
+ for (i = 0; i < len - 1; i++) {
+ struct drm_display_mode *mode;
+
+ video_format = *(db + i) & 0x7F;
+ mode = drm_display_mode_from_vic_index(connector,
+ db, len-1, i);
+ if (mode) {
+ DRM_DEBUG_KMS("Adding mode for vic = %d\n",
+ video_format);
+ drm_mode_probed_add(connector, mode);
+ modes++;
+ }
+ }
+ }
+ return modes;
+}
+
+static void
monitor_name(struct detailed_timing *t, void *data)
{
if (t->data.other_data.type == EDID_DETAIL_MONITOR_NAME)
@@ -3410,6 +3872,9 @@ void drm_edid_to_eld(struct drm_connector *connector, struct edid *edid)
/* HDMI Vendor-Specific Data Block */
if (cea_db_is_hdmi_vsdb(db))
drm_parse_hdmi_vsdb_audio(connector, db);
+ /* HDMI Forum Vendor-Specific Data Block */
+ else if (cea_db_is_hdmi_hf_vsdb(db))
+ parse_hdmi_hf_vsdb(connector, db);
break;
default:
break;
@@ -3840,6 +4305,37 @@ static void drm_parse_cea_ext(struct drm_connector *connector,
}
}
+static void
+drm_hdmi_extract_vsdbs_info(struct drm_connector *connector, struct edid *edid)
+{
+ const u8 *cea = drm_find_cea_extension(edid);
+ const u8 *db = NULL;
+
+ if (cea && cea_revision(cea) >= 3) {
+ int i, start, end;
+
+ if (cea_db_offsets(cea, &start, &end))
+ return;
+
+ for_each_cea_db(cea, i, start, end) {
+ db = &cea[i];
+
+ if (cea_db_tag(db) == VENDOR_BLOCK) {
+ /* HDMI Vendor-Specific Data Block */
+ if (cea_db_is_hdmi_vsdb(db)) {
+ drm_parse_hdmi_vsdb_video(
+ connector, db);
+ drm_parse_hdmi_vsdb_audio(
+ connector, db);
+ }
+ /* HDMI Forum Vendor-Specific Data Block */
+ else if (cea_db_is_hdmi_hf_vsdb(db))
+ parse_hdmi_hf_vsdb(connector, db);
+ }
+ }
+ }
+}
+
static void drm_add_display_info(struct drm_connector *connector,
struct edid *edid)
{
@@ -3877,6 +4373,11 @@ static void drm_add_display_info(struct drm_connector *connector,
connector->name, info->bpc);
}
+ /* Extract audio and video latency fields for the sink */
+ drm_hdmi_extract_vsdbs_info(connector, edid);
+ /* Extract info from extended tag blocks */
+ drm_hdmi_extract_extended_blk_info(connector, edid);
+
/* Only defined for 1.4 with digital displays */
if (edid->revision < 4)
return;
@@ -4091,6 +4592,7 @@ int drm_add_edid_modes(struct drm_connector *connector, struct edid *edid)
num_modes += add_cea_modes(connector, edid);
num_modes += add_alternate_cea_modes(connector, edid);
num_modes += add_displayid_detailed_modes(connector, edid);
+ num_modes += add_YCbCr420VDB_modes(connector, edid);
if (edid->features & DRM_EDID_FEATURE_DEFAULT_GTF)
num_modes += add_inferred_modes(connector, edid);
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 4112bef..651c0576 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -51,6 +51,7 @@
sde/sde_hw_reg_dma_v1_color_proc.o \
sde/sde_hw_color_proc_v4.o \
sde/sde_hw_ad4.o \
+ sde_edid_parser.o
msm_drm-$(CONFIG_DRM_SDE_RSC) += sde_rsc.o \
sde_rsc_hw.o \
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.c b/drivers/gpu/drm/msm/dp/dp_ctrl.c
new file mode 100644
index 0000000..7102d8a
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c
@@ -0,0 +1,1396 @@
+/*
+ * Copyright (c) 2012-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.
+ *
+ */
+
+#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__
+
+#include <linux/types.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+
+#include "dp_ctrl.h"
+
+#define DP_LINK_RATE_MULTIPLIER 27000000
+#define DP_KHZ_TO_HZ 1000
+#define DP_CRYPTO_CLK_RATE_KHZ 180000
+
+/* sink power state */
+#define SINK_POWER_ON 1
+#define SINK_POWER_OFF 2
+
+#define DP_CTRL_INTR_READY_FOR_VIDEO BIT(0)
+#define DP_CTRL_INTR_IDLE_PATTERN_SENT BIT(3)
+
+/* dp state ctrl */
+#define ST_TRAIN_PATTERN_1 BIT(0)
+#define ST_TRAIN_PATTERN_2 BIT(1)
+#define ST_TRAIN_PATTERN_3 BIT(2)
+#define ST_TRAIN_PATTERN_4 BIT(3)
+#define ST_SYMBOL_ERR_RATE_MEASUREMENT BIT(4)
+#define ST_PRBS7 BIT(5)
+#define ST_CUSTOM_80_BIT_PATTERN BIT(6)
+#define ST_SEND_VIDEO BIT(7)
+#define ST_PUSH_IDLE BIT(8)
+
+struct dp_vc_tu_mapping_table {
+ u32 vic;
+ u8 lanes;
+ u8 lrate; /* DP_LINK_RATE -> 162(6), 270(10), 540(20), 810 (30) */
+ u8 bpp;
+ u8 valid_boundary_link;
+ u16 delay_start_link;
+ bool boundary_moderation_en;
+ u8 valid_lower_boundary_link;
+ u8 upper_boundary_count;
+ u8 lower_boundary_count;
+ u8 tu_size_minus1;
+};
+
+struct dp_ctrl_private {
+ struct dp_ctrl dp_ctrl;
+
+ struct device *dev;
+ struct dp_aux *aux;
+ struct dp_panel *panel;
+ struct dp_link *link;
+ struct dp_power *power;
+ struct dp_parser *parser;
+ struct dp_catalog_ctrl *catalog;
+
+ struct completion idle_comp;
+ struct completion video_comp;
+ struct completion irq_comp;
+
+ bool hpd_irq_on;
+ bool power_on;
+ bool sink_info_read;
+ bool cont_splash;
+ bool psm_enabled;
+ bool initialized;
+ bool orientation;
+
+ u32 pixel_rate;
+ u32 vic;
+};
+
+enum notification_status {
+ NOTIFY_UNKNOWN,
+ NOTIFY_CONNECT,
+ NOTIFY_DISCONNECT,
+ NOTIFY_CONNECT_IRQ_HPD,
+ NOTIFY_DISCONNECT_IRQ_HPD,
+};
+
+static void dp_ctrl_idle_patterns_sent(struct dp_ctrl_private *ctrl)
+{
+ pr_debug("idle_patterns_sent\n");
+ complete(&ctrl->idle_comp);
+}
+
+static void dp_ctrl_video_ready(struct dp_ctrl_private *ctrl)
+{
+ pr_debug("dp_video_ready\n");
+ complete(&ctrl->video_comp);
+}
+
+static void dp_ctrl_set_sink_power_state(struct dp_ctrl_private *ctrl,
+ u8 power_state)
+{
+ const int len = 1;
+
+ ctrl->aux->write(ctrl->aux, 0x600, len, AUX_NATIVE, &power_state);
+}
+
+static void dp_ctrl_state_ctrl(struct dp_ctrl_private *ctrl, u32 state)
+{
+ ctrl->catalog->state_ctrl(ctrl->catalog, state);
+}
+
+static void dp_ctrl_push_idle(struct dp_ctrl *dp_ctrl)
+{
+ int const idle_pattern_completion_timeout_ms = 3 * HZ / 100;
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl) {
+ pr_err("Invalid input data\n");
+ return;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ dp_ctrl_set_sink_power_state(ctrl, SINK_POWER_OFF);
+
+ reinit_completion(&ctrl->idle_comp);
+ dp_ctrl_state_ctrl(ctrl, ST_PUSH_IDLE);
+
+ if (!wait_for_completion_timeout(&ctrl->idle_comp,
+ idle_pattern_completion_timeout_ms))
+ pr_warn("PUSH_IDLE pattern timedout\n");
+
+ pr_debug("mainlink off done\n");
+}
+
+static void dp_ctrl_config_ctrl(struct dp_ctrl_private *ctrl)
+{
+ u32 config = 0, tbd;
+
+ config |= (2 << 13); /* Default-> LSCLK DIV: 1/4 LCLK */
+ config |= (0 << 11); /* RGB */
+
+ /* Scrambler reset enable */
+ if (ctrl->panel->dpcd.scrambler_reset)
+ config |= (1 << 10);
+
+ tbd = ctrl->link->get_test_bits_depth(ctrl->link,
+ ctrl->panel->pinfo.bpp);
+ config |= tbd << 8;
+
+ /* Num of Lanes */
+ config |= ((ctrl->link->lane_count - 1) << 4);
+
+ if (ctrl->panel->dpcd.enhanced_frame)
+ config |= 0x40;
+
+ config |= 0x04; /* progressive video */
+
+ config |= 0x03; /* sycn clock & static Mvid */
+
+ ctrl->catalog->config_ctrl(ctrl->catalog, config);
+}
+
+/**
+ * dp_ctrl_configure_source_params() - configures DP transmitter source params
+ * @ctrl: Display Port Driver data
+ *
+ * Configures the DP transmitter source params including details such as lane
+ * configuration, output format and sink/panel timing information.
+ */
+static void dp_ctrl_configure_source_params(struct dp_ctrl_private *ctrl)
+{
+ u32 cc, tb;
+
+ ctrl->catalog->lane_mapping(ctrl->catalog);
+ ctrl->catalog->mainlink_ctrl(ctrl->catalog, true);
+
+ dp_ctrl_config_ctrl(ctrl);
+
+ tb = ctrl->link->get_test_bits_depth(ctrl->link,
+ ctrl->panel->pinfo.bpp);
+ cc = ctrl->link->get_colorimetry_config(ctrl->link);
+ ctrl->catalog->config_misc(ctrl->catalog, cc, tb);
+
+ ctrl->catalog->config_msa(ctrl->catalog);
+
+ ctrl->panel->timing_cfg(ctrl->panel);
+}
+
+static void dp_ctrl_get_extra_req_bytes(u64 result_valid,
+ int valid_bdary_link,
+ u64 value1, u64 value2,
+ bool *negative, u64 *result,
+ u64 compare)
+{
+ *negative = false;
+ if (result_valid >= compare) {
+ if (valid_bdary_link
+ >= compare)
+ *result = value1 + value2;
+ else {
+ if (value1 < value2)
+ *negative = true;
+ *result = (value1 >= value2) ?
+ (value1 - value2) : (value2 - value1);
+ }
+ } else {
+ if (valid_bdary_link
+ >= compare) {
+ if (value1 >= value2)
+ *negative = true;
+ *result = (value1 >= value2) ?
+ (value1 - value2) : (value2 - value1);
+ } else {
+ *result = value1 + value2;
+ *negative = true;
+ }
+ }
+}
+
+static u64 roundup_u64(u64 x, u64 y)
+{
+ x += (y - 1);
+ return (div64_ul(x, y) * y);
+}
+
+static u64 rounddown_u64(u64 x, u64 y)
+{
+ u64 rem;
+
+ div64_u64_rem(x, y, &rem);
+ return (x - rem);
+}
+
+static void dp_ctrl_calc_tu_parameters(struct dp_ctrl_private *ctrl,
+ struct dp_vc_tu_mapping_table *tu_table)
+{
+ u32 const multiplier = 1000000;
+ u64 pclk, lclk;
+ u8 bpp, ln_cnt, link_rate;
+ int run_idx = 0;
+ u32 lwidth, h_blank;
+ u32 fifo_empty = 0;
+ u32 ratio_scale = 1001;
+ u64 temp, ratio, original_ratio;
+ u64 temp2, reminder;
+ u64 temp3, temp4, result = 0;
+
+ u64 err = multiplier;
+ u64 n_err = 0, n_n_err = 0;
+ bool n_err_neg, nn_err_neg;
+ u8 hblank_margin = 16;
+
+ u8 tu_size, tu_size_desired = 0, tu_size_minus1;
+ int valid_boundary_link;
+ u64 resulting_valid;
+ u64 total_valid;
+ u64 effective_valid;
+ u64 effective_valid_recorded;
+ int n_tus;
+ int n_tus_per_lane;
+ int paired_tus;
+ int remainder_tus;
+ int remainder_tus_upper, remainder_tus_lower;
+ int extra_bytes;
+ int filler_size;
+ int delay_start_link;
+ int boundary_moderation_en = 0;
+ int upper_bdry_cnt = 0;
+ int lower_bdry_cnt = 0;
+ int i_upper_bdry_cnt = 0;
+ int i_lower_bdry_cnt = 0;
+ int valid_lower_boundary_link = 0;
+ int even_distribution_bf = 0;
+ int even_distribution_legacy = 0;
+ int even_distribution = 0;
+ int min_hblank = 0;
+ int extra_pclk_cycles;
+ u8 extra_pclk_cycle_delay = 4;
+ int extra_pclk_cycles_in_link_clk;
+ u64 ratio_by_tu;
+ u64 average_valid2;
+ u64 extra_buffer_margin;
+ int new_valid_boundary_link;
+
+ u64 resulting_valid_tmp;
+ u64 ratio_by_tu_tmp;
+ int n_tus_tmp;
+ int extra_pclk_cycles_tmp;
+ int extra_pclk_cycles_in_lclk_tmp;
+ int extra_req_bytes_new_tmp;
+ int filler_size_tmp;
+ int lower_filler_size_tmp;
+ int delay_start_link_tmp;
+ int min_hblank_tmp = 0;
+ bool extra_req_bytes_is_neg = false;
+ struct dp_panel_info *pinfo = &ctrl->panel->pinfo;
+
+ u8 dp_brute_force = 1;
+ u64 brute_force_threshold = 10;
+ u64 diff_abs;
+
+ link_rate = ctrl->link->link_rate;
+ ln_cnt = ctrl->link->lane_count;
+
+ bpp = pinfo->bpp;
+ lwidth = pinfo->h_active;
+ h_blank = pinfo->h_back_porch + pinfo->h_front_porch +
+ pinfo->h_sync_width;
+ pclk = pinfo->pixel_clk_khz * 1000;
+
+ boundary_moderation_en = 0;
+ upper_bdry_cnt = 0;
+ lower_bdry_cnt = 0;
+ i_upper_bdry_cnt = 0;
+ i_lower_bdry_cnt = 0;
+ valid_lower_boundary_link = 0;
+ even_distribution_bf = 0;
+ even_distribution_legacy = 0;
+ even_distribution = 0;
+ min_hblank = 0;
+
+ lclk = link_rate * DP_LINK_RATE_MULTIPLIER;
+
+ pr_debug("pclk=%lld, active_width=%d, h_blank=%d\n",
+ pclk, lwidth, h_blank);
+ pr_debug("lclk = %lld, ln_cnt = %d\n", lclk, ln_cnt);
+ ratio = div64_u64_rem(pclk * bpp * multiplier,
+ 8 * ln_cnt * lclk, &reminder);
+ ratio = div64_u64((pclk * bpp * multiplier), (8 * ln_cnt * lclk));
+ original_ratio = ratio;
+
+ extra_buffer_margin = roundup_u64(div64_u64(extra_pclk_cycle_delay
+ * lclk * multiplier, pclk), multiplier);
+ extra_buffer_margin = div64_u64(extra_buffer_margin, multiplier);
+
+ /* To deal with cases where lines are not distributable */
+ if (((lwidth % ln_cnt) != 0) && ratio < multiplier) {
+ ratio = ratio * ratio_scale;
+ ratio = ratio < (1000 * multiplier)
+ ? ratio : (1000 * multiplier);
+ }
+ pr_debug("ratio = %lld\n", ratio);
+
+ for (tu_size = 32; tu_size <= 64; tu_size++) {
+ temp = ratio * tu_size;
+ temp2 = ((temp / multiplier) + 1) * multiplier;
+ n_err = roundup_u64(temp, multiplier) - temp;
+
+ if (n_err < err) {
+ err = n_err;
+ tu_size_desired = tu_size;
+ }
+ }
+ pr_debug("Info: tu_size_desired = %d\n", tu_size_desired);
+
+ tu_size_minus1 = tu_size_desired - 1;
+
+ valid_boundary_link = roundup_u64(ratio * tu_size_desired, multiplier);
+ valid_boundary_link /= multiplier;
+ n_tus = rounddown((lwidth * bpp * multiplier)
+ / (8 * valid_boundary_link), multiplier) / multiplier;
+ even_distribution_legacy = n_tus % ln_cnt == 0 ? 1 : 0;
+ pr_debug("Info: n_symbol_per_tu=%d, number_of_tus=%d\n",
+ valid_boundary_link, n_tus);
+
+ extra_bytes = roundup_u64((n_tus + 1)
+ * ((valid_boundary_link * multiplier)
+ - (original_ratio * tu_size_desired)), multiplier);
+ extra_bytes /= multiplier;
+ extra_pclk_cycles = roundup(extra_bytes * 8 * multiplier / bpp,
+ multiplier);
+ extra_pclk_cycles /= multiplier;
+ extra_pclk_cycles_in_link_clk = roundup_u64(div64_u64(extra_pclk_cycles
+ * lclk * multiplier, pclk), multiplier);
+ extra_pclk_cycles_in_link_clk /= multiplier;
+ filler_size = roundup_u64((tu_size_desired - valid_boundary_link)
+ * multiplier, multiplier);
+ filler_size /= multiplier;
+ ratio_by_tu = div64_u64(ratio * tu_size_desired, multiplier);
+
+ pr_debug("extra_pclk_cycles_in_link_clk=%d, extra_bytes=%d\n",
+ extra_pclk_cycles_in_link_clk, extra_bytes);
+ pr_debug("extra_pclk_cycles_in_link_clk=%d\n",
+ extra_pclk_cycles_in_link_clk);
+ pr_debug("filler_size=%d, extra_buffer_margin=%lld\n",
+ filler_size, extra_buffer_margin);
+
+ delay_start_link = ((extra_bytes > extra_pclk_cycles_in_link_clk)
+ ? extra_bytes
+ : extra_pclk_cycles_in_link_clk)
+ + filler_size + extra_buffer_margin;
+ resulting_valid = valid_boundary_link;
+ pr_debug("Info: delay_start_link=%d, filler_size=%d\n",
+ delay_start_link, filler_size);
+ pr_debug("valid_boundary_link=%d ratio_by_tu=%lld\n",
+ valid_boundary_link, ratio_by_tu);
+
+ diff_abs = (resulting_valid >= ratio_by_tu)
+ ? (resulting_valid - ratio_by_tu)
+ : (ratio_by_tu - resulting_valid);
+
+ if (err != 0 && ((diff_abs > brute_force_threshold)
+ || (even_distribution_legacy == 0)
+ || (dp_brute_force == 1))) {
+ err = multiplier;
+ for (tu_size = 32; tu_size <= 64; tu_size++) {
+ for (i_upper_bdry_cnt = 1; i_upper_bdry_cnt <= 15;
+ i_upper_bdry_cnt++) {
+ for (i_lower_bdry_cnt = 1;
+ i_lower_bdry_cnt <= 15;
+ i_lower_bdry_cnt++) {
+ new_valid_boundary_link =
+ roundup_u64(ratio
+ * tu_size, multiplier);
+ average_valid2 = (i_upper_bdry_cnt
+ * new_valid_boundary_link
+ + i_lower_bdry_cnt
+ * (new_valid_boundary_link
+ - multiplier))
+ / (i_upper_bdry_cnt
+ + i_lower_bdry_cnt);
+ n_tus = rounddown_u64(div64_u64(lwidth
+ * multiplier * multiplier
+ * (bpp / 8), average_valid2),
+ multiplier);
+ n_tus /= multiplier;
+ n_tus_per_lane
+ = rounddown(n_tus
+ * multiplier
+ / ln_cnt, multiplier);
+ n_tus_per_lane /= multiplier;
+ paired_tus =
+ rounddown((n_tus_per_lane)
+ * multiplier
+ / (i_upper_bdry_cnt
+ + i_lower_bdry_cnt),
+ multiplier);
+ paired_tus /= multiplier;
+ remainder_tus = n_tus_per_lane
+ - paired_tus
+ * (i_upper_bdry_cnt
+ + i_lower_bdry_cnt);
+ if ((remainder_tus
+ - i_upper_bdry_cnt) > 0) {
+ remainder_tus_upper
+ = i_upper_bdry_cnt;
+ remainder_tus_lower =
+ remainder_tus
+ - i_upper_bdry_cnt;
+ } else {
+ remainder_tus_upper
+ = remainder_tus;
+ remainder_tus_lower = 0;
+ }
+ total_valid = paired_tus
+ * (i_upper_bdry_cnt
+ * new_valid_boundary_link
+ + i_lower_bdry_cnt
+ * (new_valid_boundary_link
+ - multiplier))
+ + (remainder_tus_upper
+ * new_valid_boundary_link)
+ + (remainder_tus_lower
+ * (new_valid_boundary_link
+ - multiplier));
+ n_err_neg = nn_err_neg = false;
+ effective_valid
+ = div_u64(total_valid,
+ n_tus_per_lane);
+ n_n_err = (effective_valid
+ >= (ratio * tu_size))
+ ? (effective_valid
+ - (ratio * tu_size))
+ : ((ratio * tu_size)
+ - effective_valid);
+ if (effective_valid < (ratio * tu_size))
+ nn_err_neg = true;
+ n_err = (average_valid2
+ >= (ratio * tu_size))
+ ? (average_valid2
+ - (ratio * tu_size))
+ : ((ratio * tu_size)
+ - average_valid2);
+ if (average_valid2 < (ratio * tu_size))
+ n_err_neg = true;
+ even_distribution =
+ n_tus % ln_cnt == 0 ? 1 : 0;
+ diff_abs =
+ resulting_valid >= ratio_by_tu
+ ? (resulting_valid
+ - ratio_by_tu)
+ : (ratio_by_tu
+ - resulting_valid);
+
+ resulting_valid_tmp = div64_u64(
+ (i_upper_bdry_cnt
+ * new_valid_boundary_link
+ + i_lower_bdry_cnt
+ * (new_valid_boundary_link
+ - multiplier)),
+ (i_upper_bdry_cnt
+ + i_lower_bdry_cnt));
+ ratio_by_tu_tmp =
+ original_ratio * tu_size;
+ ratio_by_tu_tmp /= multiplier;
+ n_tus_tmp = rounddown_u64(
+ div64_u64(lwidth
+ * multiplier * multiplier
+ * bpp / 8,
+ resulting_valid_tmp),
+ multiplier);
+ n_tus_tmp /= multiplier;
+
+ temp3 = (resulting_valid_tmp
+ >= (original_ratio * tu_size))
+ ? (resulting_valid_tmp
+ - original_ratio * tu_size)
+ : (original_ratio * tu_size)
+ - resulting_valid_tmp;
+ temp3 = (n_tus_tmp + 1) * temp3;
+ temp4 = (new_valid_boundary_link
+ >= (original_ratio * tu_size))
+ ? (new_valid_boundary_link
+ - original_ratio
+ * tu_size)
+ : (original_ratio * tu_size)
+ - new_valid_boundary_link;
+ temp4 = (i_upper_bdry_cnt
+ * ln_cnt * temp4);
+
+ temp3 = roundup_u64(temp3, multiplier);
+ temp4 = roundup_u64(temp4, multiplier);
+ dp_ctrl_get_extra_req_bytes
+ (resulting_valid_tmp,
+ new_valid_boundary_link,
+ temp3, temp4,
+ &extra_req_bytes_is_neg,
+ &result,
+ (original_ratio * tu_size));
+ extra_req_bytes_new_tmp
+ = div64_ul(result, multiplier);
+ if ((extra_req_bytes_is_neg)
+ && (extra_req_bytes_new_tmp
+ > 1))
+ extra_req_bytes_new_tmp
+ = extra_req_bytes_new_tmp - 1;
+ if (extra_req_bytes_new_tmp == 0)
+ extra_req_bytes_new_tmp = 1;
+ extra_pclk_cycles_tmp =
+ (u64)(extra_req_bytes_new_tmp
+ * 8 * multiplier) / bpp;
+ extra_pclk_cycles_tmp /= multiplier;
+
+ if (extra_pclk_cycles_tmp <= 0)
+ extra_pclk_cycles_tmp = 1;
+ extra_pclk_cycles_in_lclk_tmp =
+ roundup_u64(div64_u64(
+ extra_pclk_cycles_tmp
+ * lclk * multiplier,
+ pclk), multiplier);
+ extra_pclk_cycles_in_lclk_tmp
+ /= multiplier;
+ filler_size_tmp = roundup_u64(
+ (tu_size * multiplier *
+ new_valid_boundary_link),
+ multiplier);
+ filler_size_tmp /= multiplier;
+ lower_filler_size_tmp =
+ filler_size_tmp + 1;
+ if (extra_req_bytes_is_neg)
+ temp3 = (extra_req_bytes_new_tmp
+ > extra_pclk_cycles_in_lclk_tmp
+ ? extra_pclk_cycles_in_lclk_tmp
+ : extra_req_bytes_new_tmp);
+ else
+ temp3 = (extra_req_bytes_new_tmp
+ > extra_pclk_cycles_in_lclk_tmp
+ ? extra_req_bytes_new_tmp :
+ extra_pclk_cycles_in_lclk_tmp);
+
+ temp4 = lower_filler_size_tmp
+ + extra_buffer_margin;
+ if (extra_req_bytes_is_neg)
+ delay_start_link_tmp
+ = (temp3 >= temp4)
+ ? (temp3 - temp4)
+ : (temp4 - temp3);
+ else
+ delay_start_link_tmp
+ = temp3 + temp4;
+
+ min_hblank_tmp = (int)div64_u64(
+ roundup_u64(
+ div64_u64(delay_start_link_tmp
+ * pclk * multiplier, lclk),
+ multiplier), multiplier)
+ + hblank_margin;
+
+ if (((even_distribution == 1)
+ || ((even_distribution_bf == 0)
+ && (even_distribution_legacy
+ == 0)))
+ && !n_err_neg && !nn_err_neg
+ && n_n_err < err
+ && (n_n_err < diff_abs
+ || (dp_brute_force == 1))
+ && (new_valid_boundary_link
+ - 1) > 0
+ && (h_blank >=
+ (u32)min_hblank_tmp)) {
+ upper_bdry_cnt =
+ i_upper_bdry_cnt;
+ lower_bdry_cnt =
+ i_lower_bdry_cnt;
+ err = n_n_err;
+ boundary_moderation_en = 1;
+ tu_size_desired = tu_size;
+ valid_boundary_link =
+ new_valid_boundary_link;
+ effective_valid_recorded
+ = effective_valid;
+ delay_start_link
+ = delay_start_link_tmp;
+ filler_size = filler_size_tmp;
+ min_hblank = min_hblank_tmp;
+ n_tus = n_tus_tmp;
+ even_distribution_bf = 1;
+
+ pr_debug("upper_bdry_cnt=%d, lower_boundary_cnt=%d, err=%lld, tu_size_desired=%d, valid_boundary_link=%d, effective_valid=%lld\n",
+ upper_bdry_cnt,
+ lower_bdry_cnt, err,
+ tu_size_desired,
+ valid_boundary_link,
+ effective_valid);
+ }
+ }
+ }
+ }
+
+ if (boundary_moderation_en == 1) {
+ resulting_valid = (u64)(upper_bdry_cnt
+ *valid_boundary_link + lower_bdry_cnt
+ * (valid_boundary_link - 1))
+ / (upper_bdry_cnt + lower_bdry_cnt);
+ ratio_by_tu = original_ratio * tu_size_desired;
+ valid_lower_boundary_link =
+ (valid_boundary_link / multiplier) - 1;
+
+ tu_size_minus1 = tu_size_desired - 1;
+ even_distribution_bf = 1;
+ valid_boundary_link /= multiplier;
+ pr_debug("Info: Boundary_moderation enabled\n");
+ }
+ }
+
+ min_hblank = ((int) roundup_u64(div64_u64(delay_start_link * pclk
+ * multiplier, lclk), multiplier))
+ / multiplier + hblank_margin;
+ if (h_blank < (u32)min_hblank) {
+ pr_debug(" WARNING: run_idx=%d Programmed h_blank %d is smaller than the min_hblank %d supported.\n",
+ run_idx, h_blank, min_hblank);
+ }
+
+ if (fifo_empty) {
+ tu_size_minus1 = 31;
+ valid_boundary_link = 32;
+ delay_start_link = 0;
+ boundary_moderation_en = 0;
+ }
+
+ pr_debug("tu_size_minus1=%d valid_boundary_link=%d delay_start_link=%d boundary_moderation_en=%d\n upper_boundary_cnt=%d lower_boundary_cnt=%d valid_lower_boundary_link=%d min_hblank=%d\n",
+ tu_size_minus1, valid_boundary_link, delay_start_link,
+ boundary_moderation_en, upper_bdry_cnt, lower_bdry_cnt,
+ valid_lower_boundary_link, min_hblank);
+
+ tu_table->valid_boundary_link = valid_boundary_link;
+ tu_table->delay_start_link = delay_start_link;
+ tu_table->boundary_moderation_en = boundary_moderation_en;
+ tu_table->valid_lower_boundary_link = valid_lower_boundary_link;
+ tu_table->upper_boundary_count = upper_bdry_cnt;
+ tu_table->lower_boundary_count = lower_bdry_cnt;
+ tu_table->tu_size_minus1 = tu_size_minus1;
+}
+
+static void dp_ctrl_setup_tr_unit(struct dp_ctrl_private *ctrl)
+{
+ u32 dp_tu = 0x0;
+ u32 valid_boundary = 0x0;
+ u32 valid_boundary2 = 0x0;
+ struct dp_vc_tu_mapping_table tu_calc_table;
+
+ dp_ctrl_calc_tu_parameters(ctrl, &tu_calc_table);
+
+ dp_tu |= tu_calc_table.tu_size_minus1;
+ valid_boundary |= tu_calc_table.valid_boundary_link;
+ valid_boundary |= (tu_calc_table.delay_start_link << 16);
+
+ valid_boundary2 |= (tu_calc_table.valid_lower_boundary_link << 1);
+ valid_boundary2 |= (tu_calc_table.upper_boundary_count << 16);
+ valid_boundary2 |= (tu_calc_table.lower_boundary_count << 20);
+
+ if (tu_calc_table.boundary_moderation_en)
+ valid_boundary2 |= BIT(0);
+
+ pr_debug("dp_tu=0x%x, valid_boundary=0x%x, valid_boundary2=0x%x\n",
+ dp_tu, valid_boundary, valid_boundary2);
+
+ ctrl->catalog->dp_tu = dp_tu;
+ ctrl->catalog->valid_boundary = valid_boundary;
+ ctrl->catalog->valid_boundary2 = valid_boundary2;
+
+ ctrl->catalog->update_transfer_unit(ctrl->catalog);
+}
+
+static int dp_ctrl_wait4video_ready(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+
+ if (ctrl->cont_splash)
+ return ret;
+
+ ret = wait_for_completion_timeout(&ctrl->video_comp, HZ / 2);
+ if (ret <= 0) {
+ pr_err("Link Train timedout\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int dp_ctrl_update_sink_vx_px(struct dp_ctrl_private *ctrl,
+ u32 voltage_level, u32 pre_emphasis_level)
+{
+ int i;
+ u8 buf[4];
+ u32 max_level_reached = 0;
+
+ if (voltage_level == DP_LINK_VOLTAGE_MAX) {
+ pr_debug("max. voltage swing level reached %d\n",
+ voltage_level);
+ max_level_reached |= BIT(2);
+ }
+
+ if (pre_emphasis_level == DP_LINK_PRE_EMPHASIS_MAX) {
+ pr_debug("max. pre-emphasis level reached %d\n",
+ pre_emphasis_level);
+ max_level_reached |= BIT(5);
+ }
+
+ pr_debug("max_level_reached = 0x%x\n", max_level_reached);
+
+ pre_emphasis_level <<= 3;
+
+ for (i = 0; i < 4; i++)
+ buf[i] = voltage_level | pre_emphasis_level | max_level_reached;
+
+ pr_debug("p|v=0x%x\n", voltage_level | pre_emphasis_level);
+ return ctrl->aux->write(ctrl->aux, 0x103, 4, AUX_NATIVE, buf);
+}
+
+static void dp_ctrl_update_vx_px(struct dp_ctrl_private *ctrl)
+{
+ struct dp_link *link = ctrl->link;
+
+ pr_debug("v=%d p=%d\n", link->v_level, link->p_level);
+
+ ctrl->catalog->update_vx_px(ctrl->catalog,
+ link->v_level, link->p_level);
+
+ dp_ctrl_update_sink_vx_px(ctrl, link->v_level, link->p_level);
+}
+
+static void dp_ctrl_cap_lane_rate_set(struct dp_ctrl_private *ctrl)
+{
+ u8 buf[4];
+ struct dp_panel_dpcd *cap;
+
+ cap = &ctrl->panel->dpcd;
+
+ pr_debug("bw=%x lane=%d\n", ctrl->link->link_rate,
+ ctrl->link->lane_count);
+
+ buf[0] = ctrl->link->link_rate;
+ buf[1] = ctrl->link->lane_count;
+
+ if (cap->enhanced_frame)
+ buf[1] |= 0x80;
+
+ ctrl->aux->write(ctrl->aux, 0x100, 2, AUX_NATIVE, buf);
+}
+
+static void dp_ctrl_train_pattern_set(struct dp_ctrl_private *ctrl,
+ u8 pattern)
+{
+ u8 buf[4];
+
+ pr_debug("pattern=%x\n", pattern);
+
+ buf[0] = pattern;
+ ctrl->aux->write(ctrl->aux, 0x102, 1, AUX_NATIVE, buf);
+}
+
+static int dp_ctrl_link_train_1(struct dp_ctrl_private *ctrl)
+{
+ int tries, old_v_level;
+ int ret = 0;
+ int usleep_time;
+ int const maximum_retries = 5;
+
+ dp_ctrl_state_ctrl(ctrl, 0);
+
+ /* Make sure to clear the current pattern before starting a new one */
+ wmb();
+
+ ctrl->catalog->set_pattern(ctrl->catalog, 0x01);
+ dp_ctrl_cap_lane_rate_set(ctrl);
+ dp_ctrl_train_pattern_set(ctrl, 0x21); /* train_1 */
+ dp_ctrl_update_vx_px(ctrl);
+
+ tries = 0;
+ old_v_level = ctrl->link->v_level;
+ while (1) {
+ usleep_time = ctrl->panel->dpcd.training_read_interval;
+ usleep_range(usleep_time, usleep_time * 2);
+
+ if (ctrl->link->clock_recovery(ctrl->link)) {
+ ret = 0;
+ break;
+ }
+
+ if (ctrl->link->v_level == DP_LINK_VOLTAGE_MAX) {
+ ret = -1;
+ break; /* quit */
+ }
+
+ if (old_v_level == ctrl->link->v_level) {
+ tries++;
+ if (tries >= maximum_retries) {
+ ret = -1;
+ break; /* quit */
+ }
+ } else {
+ tries = 0;
+ old_v_level = ctrl->link->v_level;
+ }
+
+ ctrl->link->adjust_levels(ctrl->link);
+
+ dp_ctrl_update_vx_px(ctrl);
+ }
+
+ return ret;
+}
+
+static int dp_ctrl_link_rate_down_shift(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+
+ if (!ctrl)
+ return -EINVAL;
+
+ switch (ctrl->link->link_rate) {
+ case DP_LINK_RATE_810:
+ ctrl->link->link_rate = DP_LINK_RATE_540;
+ break;
+ case DP_LINK_RATE_540:
+ ctrl->link->link_rate = DP_LINK_RATE_270;
+ break;
+ case DP_LINK_RATE_270:
+ ctrl->link->link_rate = DP_LINK_RATE_162;
+ break;
+ case DP_LINK_RATE_162:
+ default:
+ ret = -EINVAL;
+ break;
+ };
+
+ pr_debug("new rate=%d\n", ctrl->link->link_rate);
+
+ return ret;
+}
+
+static void dp_ctrl_clear_training_pattern(struct dp_ctrl_private *ctrl)
+{
+ int usleep_time;
+
+ dp_ctrl_train_pattern_set(ctrl, 0);
+
+ usleep_time = ctrl->panel->dpcd.training_read_interval;
+ usleep_range(usleep_time, usleep_time * 2);
+}
+
+static int dp_ctrl_link_training_2(struct dp_ctrl_private *ctrl)
+{
+ int tries = 0;
+ int ret = 0;
+ int usleep_time;
+ char pattern;
+ int const maximum_retries = 5;
+
+ if (ctrl->panel->dpcd.flags & DPCD_TPS3)
+ pattern = 0x03;
+ else
+ pattern = 0x02;
+
+ dp_ctrl_update_vx_px(ctrl);
+ ctrl->catalog->set_pattern(ctrl->catalog, pattern);
+ dp_ctrl_train_pattern_set(ctrl, pattern | 0x20);
+
+ do {
+ usleep_time = ctrl->panel->dpcd.training_read_interval;
+ usleep_range(usleep_time, usleep_time * 2);
+
+ if (ctrl->link->channel_equalization(ctrl->link)) {
+ ret = 0;
+ break;
+ }
+
+ if (tries > maximum_retries) {
+ ret = -1;
+ break;
+ }
+ tries++;
+
+ ctrl->link->adjust_levels(ctrl->link);
+
+ dp_ctrl_update_vx_px(ctrl);
+ } while (1);
+
+ return ret;
+}
+
+static int dp_ctrl_link_train(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+
+ ret = ctrl->aux->ready(ctrl->aux);
+ if (!ret) {
+ pr_err("aux chan NOT ready\n");
+ return ret;
+ }
+
+ ctrl->link->p_level = 0;
+ ctrl->link->v_level = 0;
+
+ dp_ctrl_config_ctrl(ctrl);
+ dp_ctrl_state_ctrl(ctrl, 0);
+
+ ret = dp_ctrl_link_train_1(ctrl);
+ if (ret < 0) {
+ if (!dp_ctrl_link_rate_down_shift(ctrl)) {
+ pr_debug("retry with lower rate\n");
+
+ dp_ctrl_clear_training_pattern(ctrl);
+ return -EAGAIN;
+ }
+
+ pr_err("Training 1 failed\n");
+ ret = -EINVAL;
+ goto clear;
+ }
+
+ pr_debug("Training 1 completed successfully\n");
+
+ dp_ctrl_state_ctrl(ctrl, 0);
+
+ /* Make sure to clear the current pattern before starting a new one */
+ wmb();
+
+ ret = dp_ctrl_link_training_2(ctrl);
+ if (ret < 0) {
+ if (!dp_ctrl_link_rate_down_shift(ctrl)) {
+ pr_debug("retry with lower rate\n");
+
+ dp_ctrl_clear_training_pattern(ctrl);
+ return -EAGAIN;
+ }
+
+ pr_err("Training 2 failed\n");
+ ret = -EINVAL;
+ goto clear;
+ }
+
+ pr_debug("Training 2 completed successfully\n");
+
+ dp_ctrl_state_ctrl(ctrl, 0);
+ /* Make sure to clear the current pattern before starting a new one */
+ wmb();
+
+clear:
+ dp_ctrl_clear_training_pattern(ctrl);
+ return ret;
+}
+
+static int dp_ctrl_setup_main_link(struct dp_ctrl_private *ctrl, bool train)
+{
+ bool mainlink_ready = false;
+ int ret = 0;
+
+ ctrl->catalog->mainlink_ctrl(ctrl->catalog, true);
+
+ dp_ctrl_set_sink_power_state(ctrl, SINK_POWER_ON);
+
+ if (ctrl->link->phy_pattern_requested(ctrl->link))
+ goto end;
+
+ if (!train)
+ goto send_video;
+
+ /*
+ * As part of previous calls, DP controller state might have
+ * transitioned to PUSH_IDLE. In order to start transmitting a link
+ * training pattern, we have to first to a DP software reset.
+ */
+ ctrl->catalog->reset(ctrl->catalog);
+
+ ret = dp_ctrl_link_train(ctrl);
+ if (ret)
+ goto end;
+
+send_video:
+ /*
+ * Set up transfer unit values and set controller state to send
+ * video.
+ */
+ dp_ctrl_setup_tr_unit(ctrl);
+ ctrl->catalog->state_ctrl(ctrl->catalog, ST_SEND_VIDEO);
+
+ dp_ctrl_wait4video_ready(ctrl);
+ mainlink_ready = ctrl->catalog->mainlink_ready(ctrl->catalog);
+ pr_debug("mainlink %s\n", mainlink_ready ? "READY" : "NOT READY");
+end:
+ return ret;
+}
+
+static void dp_ctrl_set_clock_rate(struct dp_ctrl_private *ctrl,
+ char *name, u32 rate)
+{
+ u32 num = ctrl->parser->mp[DP_CTRL_PM].num_clk;
+ struct dss_clk *cfg = ctrl->parser->mp[DP_CTRL_PM].clk_config;
+
+ while (num && strcmp(cfg->clk_name, name)) {
+ num--;
+ cfg++;
+ }
+
+ if (num)
+ cfg->rate = rate;
+ else
+ pr_err("%s clock could not be set with rate %d\n", name, rate);
+}
+
+static int dp_ctrl_enable_mainlink_clocks(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+
+ ctrl->power->set_pixel_clk_parent(ctrl->power);
+
+ dp_ctrl_set_clock_rate(ctrl, "ctrl_link_clk",
+ (ctrl->link->link_rate * DP_LINK_RATE_MULTIPLIER) /
+ DP_KHZ_TO_HZ);
+
+ dp_ctrl_set_clock_rate(ctrl, "ctrl_crypto_clk", DP_CRYPTO_CLK_RATE_KHZ);
+
+ dp_ctrl_set_clock_rate(ctrl, "ctrl_pixel_clk", ctrl->pixel_rate);
+
+ ret = ctrl->power->clk_enable(ctrl->power, DP_CTRL_PM, true);
+ if (ret) {
+ pr_err("Unabled to start link clocks\n");
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int dp_ctrl_disable_mainlink_clocks(struct dp_ctrl_private *ctrl)
+{
+ return ctrl->power->clk_enable(ctrl->power, DP_CTRL_PM, false);
+}
+
+static int dp_ctrl_host_init(struct dp_ctrl *dp_ctrl, bool flip)
+{
+ struct dp_ctrl_private *ctrl;
+ struct dp_catalog_ctrl *catalog;
+
+ if (!dp_ctrl) {
+ pr_err("Invalid input data\n");
+ return -EINVAL;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ if (ctrl->initialized) {
+ pr_debug("host init done already\n");
+ return 0;
+ }
+
+ ctrl->orientation = flip;
+ catalog = ctrl->catalog;
+
+ catalog->reset(ctrl->catalog);
+ catalog->phy_reset(ctrl->catalog);
+ catalog->enable_irq(ctrl->catalog, true);
+
+ ctrl->initialized = true;
+
+ return 0;
+}
+
+/**
+ * dp_ctrl_host_deinit() - Uninitialize DP controller
+ * @ctrl: Display Port Driver data
+ *
+ * Perform required steps to uninitialize DP controller
+ * and its resources.
+ */
+static void dp_ctrl_host_deinit(struct dp_ctrl *dp_ctrl)
+{
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl) {
+ pr_err("Invalid input data\n");
+ return;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ if (!ctrl->initialized) {
+ pr_debug("host deinit done already\n");
+ return;
+ }
+
+ ctrl->catalog->enable_irq(ctrl->catalog, false);
+ ctrl->catalog->reset(ctrl->catalog);
+
+ /* Make sure DP is disabled before clk disable */
+ wmb();
+
+ dp_ctrl_disable_mainlink_clocks(ctrl);
+
+ ctrl->initialized = false;
+ pr_debug("Host deinitialized successfully\n");
+}
+
+static int dp_ctrl_on_irq(struct dp_ctrl_private *ctrl, bool lt_needed)
+{
+ int ret = 0;
+
+ do {
+ if (ret == -EAGAIN)
+ ctrl->catalog->mainlink_ctrl(ctrl->catalog, false);
+
+ ctrl->catalog->phy_lane_cfg(ctrl->catalog,
+ ctrl->orientation, ctrl->link->lane_count);
+
+ if (lt_needed) {
+ /*
+ * Diasable and re-enable the mainlink clock since the
+ * link clock might have been adjusted as part of the
+ * link maintenance.
+ */
+ if (!ctrl->link->phy_pattern_requested(
+ ctrl->link))
+ dp_ctrl_disable_mainlink_clocks(ctrl);
+
+ ret = dp_ctrl_enable_mainlink_clocks(ctrl);
+ if (ret)
+ continue;
+ }
+
+ dp_ctrl_configure_source_params(ctrl);
+
+ reinit_completion(&ctrl->idle_comp);
+
+ ctrl->power_on = true;
+
+ if (ctrl->psm_enabled) {
+ ret = ctrl->link->send_psm_request(ctrl->link, false);
+ if (ret) {
+ pr_err("failed to exit low power mode, rc=%d\n",
+ ret);
+ continue;
+ }
+ }
+
+ ret = dp_ctrl_setup_main_link(ctrl, lt_needed);
+ } while (ret == -EAGAIN);
+
+ return ret;
+}
+
+static int dp_ctrl_on_hpd(struct dp_ctrl_private *ctrl)
+{
+ int ret = 0;
+
+ if (ctrl->cont_splash)
+ goto link_training;
+
+ ctrl->power->clk_enable(ctrl->power, DP_CORE_PM, true);
+ ctrl->catalog->hpd_config(ctrl->catalog, true);
+
+ ctrl->link->link_rate = ctrl->panel->get_link_rate(ctrl->panel);
+ ctrl->link->lane_count = ctrl->panel->dpcd.max_lane_count;
+ ctrl->pixel_rate = ctrl->panel->pinfo.pixel_clk_khz;
+
+ pr_debug("link_rate=%d, lane_count=%d, pixel_rate=%d\n",
+ ctrl->link->link_rate, ctrl->link->lane_count,
+ ctrl->pixel_rate);
+
+ ctrl->catalog->phy_lane_cfg(ctrl->catalog,
+ ctrl->orientation, ctrl->link->lane_count);
+
+ ret = dp_ctrl_enable_mainlink_clocks(ctrl);
+ if (ret)
+ goto exit;
+
+ reinit_completion(&ctrl->idle_comp);
+
+ dp_ctrl_configure_source_params(ctrl);
+
+ if (ctrl->psm_enabled)
+ ret = ctrl->link->send_psm_request(ctrl->link, false);
+link_training:
+ ctrl->power_on = true;
+
+ while (-EAGAIN == dp_ctrl_setup_main_link(ctrl, true))
+ pr_debug("MAIN LINK TRAINING RETRY\n");
+
+ ctrl->cont_splash = 0;
+
+ ctrl->power_on = true;
+ pr_debug("End-\n");
+
+exit:
+ return ret;
+}
+
+static int dp_ctrl_off_irq(struct dp_ctrl_private *ctrl)
+{
+ if (!ctrl->power_on) {
+ pr_debug("ctrl already powered off\n");
+ return 0;
+ }
+
+ ctrl->catalog->mainlink_ctrl(ctrl->catalog, false);
+
+ /* Make sure DP mainlink and audio engines are disabled */
+ wmb();
+
+ complete_all(&ctrl->irq_comp);
+ pr_debug("end\n");
+
+ return 0;
+}
+
+static int dp_ctrl_off_hpd(struct dp_ctrl_private *ctrl)
+{
+ if (!ctrl->power_on) {
+ pr_debug("panel already powered off\n");
+ return 0;
+ }
+
+ ctrl->catalog->mainlink_ctrl(ctrl->catalog, false);
+
+ ctrl->power_on = false;
+ ctrl->sink_info_read = false;
+
+ pr_debug("DP off done\n");
+
+ return 0;
+}
+
+static int dp_ctrl_on(struct dp_ctrl *dp_ctrl)
+{
+ int rc = 0;
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl) {
+ rc = -EINVAL;
+ goto end;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ if (ctrl->hpd_irq_on)
+ rc = dp_ctrl_on_irq(ctrl, false);
+ else
+ rc = dp_ctrl_on_hpd(ctrl);
+end:
+ return rc;
+}
+
+static int dp_ctrl_off(struct dp_ctrl *dp_ctrl)
+{
+ int rc = 0;
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl) {
+ rc = -EINVAL;
+ goto end;
+ }
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ if (ctrl->hpd_irq_on)
+ rc = dp_ctrl_off_irq(ctrl);
+ else
+ rc = dp_ctrl_off_hpd(ctrl);
+end:
+ return rc;
+}
+
+static void dp_ctrl_isr(struct dp_ctrl *dp_ctrl, u32 irq)
+{
+ u32 isr;
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl)
+ return;
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ isr = ctrl->catalog->get_interrupt(ctrl->catalog);
+
+ if (isr & DP_CTRL_INTR_READY_FOR_VIDEO)
+ dp_ctrl_video_ready(ctrl);
+
+ if (isr & DP_CTRL_INTR_IDLE_PATTERN_SENT)
+ dp_ctrl_idle_patterns_sent(ctrl);
+}
+
+struct dp_ctrl *dp_ctrl_get(struct dp_ctrl_in *in)
+{
+ int rc = 0;
+ struct dp_ctrl_private *ctrl;
+ struct dp_ctrl *dp_ctrl;
+
+ if (!in->dev || !in->panel || !in->aux ||
+ !in->link || !in->catalog) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto error;
+ }
+
+ ctrl = devm_kzalloc(in->dev, sizeof(*ctrl), GFP_KERNEL);
+ if (!ctrl) {
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ init_completion(&ctrl->idle_comp);
+ init_completion(&ctrl->video_comp);
+ init_completion(&ctrl->irq_comp);
+
+ /* in parameters */
+ ctrl->parser = in->parser;
+ ctrl->panel = in->panel;
+ ctrl->power = in->power;
+ ctrl->aux = in->aux;
+ ctrl->link = in->link;
+ ctrl->catalog = in->catalog;
+
+ dp_ctrl = &ctrl->dp_ctrl;
+
+ /* out parameters */
+ dp_ctrl->init = dp_ctrl_host_init;
+ dp_ctrl->deinit = dp_ctrl_host_deinit;
+ dp_ctrl->on = dp_ctrl_on;
+ dp_ctrl->off = dp_ctrl_off;
+ dp_ctrl->push_idle = dp_ctrl_push_idle;
+ dp_ctrl->isr = dp_ctrl_isr;
+
+ return dp_ctrl;
+error:
+ return ERR_PTR(rc);
+}
+
+void dp_ctrl_put(struct dp_ctrl *dp_ctrl)
+{
+ struct dp_ctrl_private *ctrl;
+
+ if (!dp_ctrl)
+ return;
+
+ ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
+
+ devm_kfree(ctrl->dev, ctrl);
+}
diff --git a/drivers/gpu/drm/msm/dp/dp_ctrl.h b/drivers/gpu/drm/msm/dp/dp_ctrl.h
new file mode 100644
index 0000000..c9cf7f8
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2012-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 _DP_CTRL_H_
+#define _DP_CTRL_H_
+
+#include "dp_aux.h"
+#include "dp_panel.h"
+#include "dp_link.h"
+#include "dp_parser.h"
+#include "dp_power.h"
+#include "dp_catalog.h"
+
+struct dp_ctrl {
+ int (*init)(struct dp_ctrl *dp_ctrl, bool flip);
+ void (*deinit)(struct dp_ctrl *dp_ctrl);
+ int (*on)(struct dp_ctrl *dp_ctrl);
+ int (*off)(struct dp_ctrl *dp_ctrl);
+ void (*push_idle)(struct dp_ctrl *dp_ctrl);
+ void (*isr)(struct dp_ctrl *dp_ctrl, u32 isr);
+};
+
+struct dp_ctrl_in {
+ struct device *dev;
+ struct dp_panel *panel;
+ struct dp_aux *aux;
+ struct dp_link *link;
+ struct dp_parser *parser;
+ struct dp_power *power;
+ struct dp_catalog_ctrl *catalog;
+};
+
+struct dp_ctrl *dp_ctrl_get(struct dp_ctrl_in *in);
+void dp_ctrl_put(struct dp_ctrl *dp_ctrl);
+
+#endif /* _DP_CTRL_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_display.c b/drivers/gpu/drm/msm/dp/dp_display.c
new file mode 100644
index 0000000..e3c30a5
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_display.c
@@ -0,0 +1,683 @@
+/*
+ * 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.
+ *
+ */
+
+#define pr_fmt(fmt) "[drm-dp]: %s: " fmt, __func__
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/debugfs.h>
+#include <linux/component.h>
+#include <linux/of_irq.h>
+
+#include "dp_usbpd.h"
+#include "dp_parser.h"
+#include "dp_power.h"
+#include "dp_catalog.h"
+#include "dp_aux.h"
+#include "dp_link.h"
+#include "dp_panel.h"
+#include "dp_ctrl.h"
+#include "dp_display.h"
+
+static struct dp_display *g_dp_display;
+
+struct dp_display_private {
+ char *name;
+ int irq;
+
+ struct platform_device *pdev;
+ struct dentry *root;
+ struct mutex lock;
+
+ struct dp_usbpd *usbpd;
+ struct dp_parser *parser;
+ struct dp_power *power;
+ struct dp_catalog *catalog;
+ struct dp_aux *aux;
+ struct dp_link *link;
+ struct dp_panel *panel;
+ struct dp_ctrl *ctrl;
+
+ struct dp_usbpd_cb usbpd_cb;
+ struct dp_display_mode mode;
+ struct dp_display dp_display;
+};
+
+static const struct of_device_id dp_dt_match[] = {
+ {.compatible = "qcom,dp-display"},
+ {}
+};
+
+static ssize_t debugfs_dp_info_read(struct file *file, char __user *buff,
+ size_t count, loff_t *ppos)
+{
+ struct dp_display_private *dp = file->private_data;
+ char *buf;
+ u32 len = 0;
+
+ if (!dp)
+ return -ENODEV;
+
+ if (*ppos)
+ return 0;
+
+ buf = kzalloc(SZ_4K, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ len += snprintf(buf + len, (SZ_4K - len), "name = %s\n", dp->name);
+ len += snprintf(buf + len, (SZ_4K - len),
+ "\tResolution = %dx%d\n",
+ dp->panel->pinfo.h_active,
+ dp->panel->pinfo.v_active);
+
+ if (copy_to_user(buff, buf, len)) {
+ kfree(buf);
+ return -EFAULT;
+ }
+
+ *ppos += len;
+
+ kfree(buf);
+ return len;
+}
+
+static const struct file_operations dp_debug_fops = {
+ .open = simple_open,
+ .read = debugfs_dp_info_read,
+};
+
+static int dp_display_debugfs_init(struct dp_display_private *dp)
+{
+ int rc = 0;
+ struct dentry *dir, *file;
+
+ dir = debugfs_create_dir(dp->name, NULL);
+ if (IS_ERR_OR_NULL(dir)) {
+ rc = PTR_ERR(dir);
+ pr_err("[%s] debugfs create dir failed, rc = %d\n",
+ dp->name, rc);
+ goto error;
+ }
+
+ file = debugfs_create_file("dp_debug", 0444, dir, dp, &dp_debug_fops);
+ if (IS_ERR_OR_NULL(file)) {
+ rc = PTR_ERR(file);
+ pr_err("[%s] debugfs create file failed, rc=%d\n",
+ dp->name, rc);
+ goto error_remove_dir;
+ }
+
+ dp->root = dir;
+ return rc;
+error_remove_dir:
+ debugfs_remove(dir);
+error:
+ return rc;
+}
+
+static int dp_display_debugfs_deinit(struct dp_display_private *dp)
+{
+ debugfs_remove(dp->root);
+ return 0;
+}
+
+static int dp_display_bind(struct device *dev, struct device *master,
+ void *data)
+{
+ int rc = 0;
+ struct dp_display_private *dp;
+ struct drm_device *drm;
+ struct platform_device *pdev = to_platform_device(dev);
+
+ if (!dev || !pdev || !master) {
+ pr_err("invalid param(s), dev %pK, pdev %pK, master %pK\n",
+ dev, pdev, master);
+ rc = -EINVAL;
+ goto error;
+ }
+
+ drm = dev_get_drvdata(master);
+ dp = platform_get_drvdata(pdev);
+ if (!drm || !dp) {
+ pr_err("invalid param(s), drm %pK, dp %pK\n",
+ drm, dp);
+ rc = -EINVAL;
+ goto error;
+ }
+
+ dp->dp_display.drm_dev = drm;
+
+ mutex_lock(&dp->lock);
+
+ rc = dp_display_debugfs_init(dp);
+ if (rc) {
+ pr_err("[%s]Debugfs init failed, rc=%d\n", dp->name, rc);
+ goto end;
+ }
+
+ rc = dp->parser->parse(dp->parser);
+ if (rc) {
+ pr_err("device tree parsing failed\n");
+ goto end;
+ }
+end:
+ mutex_unlock(&dp->lock);
+error:
+ return rc;
+}
+
+static void dp_display_unbind(struct device *dev, struct device *master,
+ void *data)
+{
+ struct dp_display_private *dp;
+ struct platform_device *pdev = to_platform_device(dev);
+
+ if (!dev || !pdev) {
+ pr_err("invalid param(s)\n");
+ return;
+ }
+
+ dp = platform_get_drvdata(pdev);
+ if (!dp) {
+ pr_err("Invalid params\n");
+ return;
+ }
+
+ mutex_lock(&dp->lock);
+
+ (void)dp_display_debugfs_deinit(dp);
+
+ mutex_unlock(&dp->lock);
+}
+
+static const struct component_ops dp_display_comp_ops = {
+ .bind = dp_display_bind,
+ .unbind = dp_display_unbind,
+};
+
+static int dp_display_process_hpd_high(struct dp_display_private *dp)
+{
+ int rc;
+
+ rc = dp->panel->read_dpcd(dp->panel);
+ if (rc)
+ goto end;
+
+ rc = dp->panel->read_edid(dp->panel);
+ if (rc)
+ goto end;
+
+ return 0;
+end:
+ return rc;
+}
+
+static int dp_display_process_hpd_low(struct dp_display_private *dp)
+{
+ return 0;
+}
+
+static irqreturn_t dp_display_irq(int irq, void *dev_id)
+{
+ struct dp_display_private *dp = dev_id;
+
+ if (!dp) {
+ pr_err("invalid data\n");
+ return IRQ_NONE;
+ }
+
+ dp->aux->isr(dp->aux);
+
+ return IRQ_HANDLED;
+}
+
+static int dp_display_usbpd_configure_cb(struct device *dev)
+{
+ int rc = 0;
+ bool flip = false;
+ struct dp_display_private *dp;
+
+ if (!dev) {
+ pr_err("invalid dev\n");
+ rc = -EINVAL;
+ goto end;
+ }
+
+ dp = dev_get_drvdata(dev);
+ if (!dp) {
+ pr_err("no driver data found\n");
+ rc = -ENODEV;
+ goto end;
+ }
+
+ if (!dp->irq) {
+ dp->irq = irq_of_parse_and_map(dev->of_node, 0);
+ if (dp->irq < 0) {
+ rc = dp->irq;
+ pr_err("failed to get irq: %d\n", rc);
+ goto end;
+ }
+
+ rc = devm_request_irq(dev, dp->irq,
+ dp_display_irq, IRQF_TRIGGER_HIGH,
+ "dp_display_isr", dp);
+ if (rc < 0) {
+ pr_err("failed to request IRQ%u: %d\n",
+ dp->irq, rc);
+ goto end;
+ }
+ }
+
+ mutex_lock(&dp->lock);
+
+ if (dp->usbpd->orientation == ORIENTATION_CC2)
+ flip = true;
+
+ dp->power->init(dp->power, flip);
+ dp->ctrl->init(dp->ctrl, flip);
+ dp->aux->init(dp->aux, dp->parser->aux_cfg);
+
+ if (dp->usbpd->hpd_high)
+ dp_display_process_hpd_high(dp);
+
+ mutex_unlock(&dp->lock);
+end:
+ return rc;
+}
+
+static int dp_display_usbpd_disconnect_cb(struct device *dev)
+{
+ int rc = 0;
+ struct dp_display_private *dp;
+
+ if (!dev) {
+ pr_err("invalid dev\n");
+ rc = -EINVAL;
+ goto end;
+ }
+
+ dp = dev_get_drvdata(dev);
+ if (!dp) {
+ pr_err("no driver data found\n");
+ rc = -ENODEV;
+ goto end;
+ }
+
+ mutex_lock(&dp->lock);
+ mutex_unlock(&dp->lock);
+
+end:
+ return rc;
+}
+
+static int dp_display_usbpd_attention_cb(struct device *dev)
+{
+ int rc = 0;
+ struct dp_display_private *dp;
+
+ if (!dev) {
+ pr_err("invalid dev\n");
+ rc = -EINVAL;
+ goto end;
+ }
+
+ dp = dev_get_drvdata(dev);
+ if (!dp) {
+ pr_err("no driver data found\n");
+ rc = -ENODEV;
+ goto end;
+ }
+
+ mutex_lock(&dp->lock);
+
+ if (dp->usbpd->hpd_irq) {
+ if (!dp->link->process_request(dp->link))
+ goto end;
+ }
+
+ if (dp->usbpd->hpd_high)
+ dp_display_process_hpd_high(dp);
+ else
+ dp_display_process_hpd_low(dp);
+
+ mutex_unlock(&dp->lock);
+end:
+ return rc;
+}
+
+static int dp_init_sub_modules(struct dp_display_private *dp)
+{
+ int rc = 0;
+ struct device *dev = &dp->pdev->dev;
+ struct dp_usbpd_cb *cb = &dp->usbpd_cb;
+ struct dp_ctrl_in ctrl_in = {
+ .dev = dev,
+ };
+
+ cb->configure = dp_display_usbpd_configure_cb;
+ cb->disconnect = dp_display_usbpd_disconnect_cb;
+ cb->attention = dp_display_usbpd_attention_cb;
+
+ dp->usbpd = dp_usbpd_get(dev, cb);
+ if (IS_ERR(dp->usbpd)) {
+ rc = PTR_ERR(dp->usbpd);
+ pr_err("failed to initialize usbpd, rc = %d\n", rc);
+ goto err;
+ }
+
+ dp->parser = dp_parser_get(dp->pdev);
+ if (IS_ERR(dp->parser)) {
+ rc = PTR_ERR(dp->parser);
+ pr_err("failed to initialize parser, rc = %d\n", rc);
+ goto err;
+ }
+
+ dp->catalog = dp_catalog_get(dev, &dp->parser->io);
+ if (IS_ERR(dp->catalog)) {
+ rc = PTR_ERR(dp->catalog);
+ pr_err("failed to initialize catalog, rc = %d\n", rc);
+ goto err;
+ }
+
+ dp->power = dp_power_get(dp->parser);
+ if (IS_ERR(dp->power)) {
+ rc = PTR_ERR(dp->power);
+ pr_err("failed to initialize power, rc = %d\n", rc);
+ goto err;
+ }
+
+ dp->aux = dp_aux_get(dev, &dp->catalog->aux);
+ if (IS_ERR(dp->aux)) {
+ rc = PTR_ERR(dp->aux);
+ pr_err("failed to initialize aux, rc = %d\n", rc);
+ goto err;
+ }
+
+ dp->panel = dp_panel_get(dev, dp->aux, &dp->catalog->panel);
+ if (IS_ERR(dp->panel)) {
+ rc = PTR_ERR(dp->panel);
+ pr_err("failed to initialize panel, rc = %d\n", rc);
+ goto err;
+ }
+
+ dp->link = dp_link_get(dev, dp->aux);
+ if (IS_ERR(dp->link)) {
+ rc = PTR_ERR(dp->link);
+ pr_err("failed to initialize link, rc = %d\n", rc);
+ goto err;
+ }
+
+ ctrl_in.link = dp->link;
+ ctrl_in.panel = dp->panel;
+ ctrl_in.aux = dp->aux;
+ ctrl_in.power = dp->power;
+ ctrl_in.catalog = &dp->catalog->ctrl;
+ ctrl_in.parser = dp->parser;
+
+ dp->ctrl = dp_ctrl_get(&ctrl_in);
+ if (IS_ERR(dp->ctrl)) {
+ rc = PTR_ERR(dp->ctrl);
+ pr_err("failed to initialize ctrl, rc = %d\n", rc);
+ goto err;
+ }
+err:
+ return rc;
+}
+
+static int dp_display_set_mode(struct dp_display *dp_display,
+ struct dp_display_mode *mode)
+{
+ int rc = 0;
+ struct dp_display_private *dp;
+
+ if (!dp_display) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto error;
+ }
+ dp = container_of(dp_display, struct dp_display_private, dp_display);
+
+ dp->panel->pinfo = mode->timing;
+ dp->panel->init_info(dp->panel);
+error:
+ return rc;
+}
+
+static int dp_display_prepare(struct dp_display *dp)
+{
+ return 0;
+}
+
+static int dp_display_enable(struct dp_display *dp_display)
+{
+ int rc = 0;
+ struct dp_display_private *dp;
+
+ if (!dp_display) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto error;
+ }
+
+ dp = container_of(dp_display, struct dp_display_private, dp_display);
+
+ mutex_lock(&dp->lock);
+ dp->ctrl->on(dp->ctrl);
+ mutex_unlock(&dp->lock);
+error:
+ return rc;
+}
+
+static int dp_display_post_enable(struct dp_display *dp)
+{
+ return 0;
+}
+
+static int dp_display_pre_disable(struct dp_display *dp_display)
+{
+ int rc = 0;
+ struct dp_display_private *dp;
+
+ if (!dp_display) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto error;
+ }
+
+ dp = container_of(dp_display, struct dp_display_private, dp_display);
+
+ mutex_lock(&dp->lock);
+
+ dp->ctrl->off(dp->ctrl);
+
+ mutex_unlock(&dp->lock);
+error:
+ return rc;
+}
+
+static int dp_display_disable(struct dp_display *dp_display)
+{
+ int rc = 0;
+ struct dp_display_private *dp;
+
+ if (!dp_display) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto error;
+ }
+
+ dp = container_of(dp_display, struct dp_display_private, dp_display);
+
+ mutex_lock(&dp->lock);
+
+ dp->aux->deinit(dp->aux);
+ dp->ctrl->deinit(dp->ctrl);
+ dp->power->deinit(dp->power);
+
+ mutex_unlock(&dp->lock);
+error:
+ return rc;
+}
+
+static int dp_display_unprepare(struct dp_display *dp)
+{
+ return 0;
+}
+
+static int dp_display_validate_mode(struct dp_display *dp,
+ struct dp_display_mode *mode)
+{
+ return 0;
+}
+
+static int dp_display_get_modes(struct dp_display *dp,
+ struct dp_display_mode *modes, u32 *count)
+{
+ *count = 1;
+
+ if (modes) {
+ modes->timing.h_active = 1920;
+ modes->timing.v_active = 1080;
+ modes->timing.h_back_porch = 148;
+ modes->timing.h_front_porch = 88;
+ modes->timing.h_sync_width = 44;
+ modes->timing.h_active_low = 0;
+ modes->timing.v_back_porch = 36;
+ modes->timing.v_front_porch = 4;
+ modes->timing.v_sync_width = 5;
+ modes->timing.v_active_low = 0;
+ modes->timing.h_skew = 0;
+ modes->timing.refresh_rate = 60;
+ modes->timing.pixel_clk_khz = 148500;
+ }
+
+ return 0;
+}
+
+static int dp_display_detect(struct dp_display *dp)
+{
+ return 0;
+}
+
+static int dp_display_probe(struct platform_device *pdev)
+{
+ int rc = 0;
+ struct dp_display_private *dp;
+
+ if (!pdev || !pdev->dev.of_node) {
+ pr_err("pdev not found\n");
+ return -ENODEV;
+ }
+
+ dp = devm_kzalloc(&pdev->dev, sizeof(*dp), GFP_KERNEL);
+ if (!dp)
+ return -ENOMEM;
+
+ mutex_init(&dp->lock);
+ dp->pdev = pdev;
+ dp->name = "drm_dp";
+
+ rc = dp_init_sub_modules(dp);
+ if (rc) {
+ devm_kfree(&pdev->dev, dp);
+ return -EPROBE_DEFER;
+ }
+
+ platform_set_drvdata(pdev, dp);
+
+ g_dp_display = &dp->dp_display;
+
+ g_dp_display->enable = dp_display_enable;
+ g_dp_display->post_enable = dp_display_post_enable;
+ g_dp_display->pre_disable = dp_display_pre_disable;
+ g_dp_display->disable = dp_display_disable;
+ g_dp_display->set_mode = dp_display_set_mode;
+ g_dp_display->validate_mode = dp_display_validate_mode;
+ g_dp_display->get_modes = dp_display_get_modes;
+ g_dp_display->detect = dp_display_detect;
+ g_dp_display->prepare = dp_display_prepare;
+ g_dp_display->unprepare = dp_display_unprepare;
+
+ rc = component_add(&pdev->dev, &dp_display_comp_ops);
+ if (rc)
+ pr_err("component add failed, rc=%d\n", rc);
+
+ return rc;
+}
+
+struct dp_display *dp_display_get(void)
+{
+ return g_dp_display;
+}
+
+static void dp_display_deinit_sub_modules(struct dp_display_private *dp)
+{
+ dp_ctrl_put(dp->ctrl);
+ dp_link_put(dp->link);
+ dp_panel_put(dp->panel);
+ dp_aux_put(dp->aux);
+ dp_power_put(dp->power);
+ dp_catalog_put(dp->catalog);
+ dp_parser_put(dp->parser);
+ dp_usbpd_put(dp->usbpd);
+}
+
+static int dp_display_remove(struct platform_device *pdev)
+{
+ struct dp_display_private *dp;
+
+ if (!pdev)
+ return -EINVAL;
+
+ dp = platform_get_drvdata(pdev);
+
+ dp_display_deinit_sub_modules(dp);
+
+ platform_set_drvdata(pdev, NULL);
+ devm_kfree(&pdev->dev, dp);
+
+ return 0;
+}
+
+static struct platform_driver dp_display_driver = {
+ .probe = dp_display_probe,
+ .remove = dp_display_remove,
+ .driver = {
+ .name = "msm-dp-display",
+ .of_match_table = dp_dt_match,
+ },
+};
+
+static int __init dp_display_init(void)
+{
+ int ret;
+
+ ret = platform_driver_register(&dp_display_driver);
+ if (ret) {
+ pr_err("driver register failed");
+ return ret;
+ }
+
+ return ret;
+}
+module_init(dp_display_init);
+
+static void __exit dp_display_cleanup(void)
+{
+ platform_driver_unregister(&dp_display_driver);
+}
+module_exit(dp_display_cleanup);
+
diff --git a/drivers/gpu/drm/msm/dp/dp_display.h b/drivers/gpu/drm/msm/dp/dp_display.h
new file mode 100644
index 0000000..2bc591c
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_display.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef _DP_DISPLAY_H_
+#define _DP_DISPLAY_H_
+
+#include <drm/drmP.h>
+
+#include "dp_panel.h"
+
+struct dp_display_mode {
+ struct dp_panel_info timing;
+ u32 capabilities;
+};
+
+struct dp_display {
+ struct drm_device *drm_dev;
+ struct dp_bridge *bridge;
+
+ int (*enable)(struct dp_display *dp_display);
+ int (*post_enable)(struct dp_display *dp_display);
+
+ int (*pre_disable)(struct dp_display *dp_display);
+ int (*disable)(struct dp_display *dp_display);
+
+ int (*set_mode)(struct dp_display *dp_display,
+ struct dp_display_mode *mode);
+ int (*validate_mode)(struct dp_display *dp_display,
+ struct dp_display_mode *mode);
+ int (*get_modes)(struct dp_display *dp_display,
+ struct dp_display_mode *modes, u32 *count);
+
+ int (*detect)(struct dp_display *dp_display);
+
+ int (*prepare)(struct dp_display *dp_display);
+ int (*unprepare)(struct dp_display *dp_display);
+};
+
+struct dp_display *dp_display_get(void);
+#endif /* _DP_DISPLAY_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_link.c b/drivers/gpu/drm/msm/dp/dp_link.c
new file mode 100644
index 0000000..e9955a9
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_link.c
@@ -0,0 +1,1809 @@
+/*
+ * Copyright (c) 2012-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.
+ *
+ */
+
+#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__
+
+#include "dp_link.h"
+#include "dp_panel.h"
+
+#define DP_LINK_ENUM_STR(x) #x
+
+enum dp_lane_count {
+ DP_LANE_COUNT_1 = 1,
+ DP_LANE_COUNT_2 = 2,
+ DP_LANE_COUNT_4 = 4,
+};
+
+enum phy_test_pattern {
+ PHY_TEST_PATTERN_NONE,
+ PHY_TEST_PATTERN_D10_2_NO_SCRAMBLING,
+ PHY_TEST_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT,
+ PHY_TEST_PATTERN_PRBS7,
+ PHY_TEST_PATTERN_80_BIT_CUSTOM_PATTERN,
+ PHY_TEST_PATTERN_HBR2_CTS_EYE_PATTERN,
+};
+
+enum dynamic_range {
+ DP_DYNAMIC_RANGE_RGB_VESA = 0x00,
+ DP_DYNAMIC_RANGE_RGB_CEA = 0x01,
+ DP_DYNAMIC_RANGE_UNKNOWN = 0xFFFFFFFF,
+};
+
+enum test_video_pattern {
+ DP_TEST_VIDEO_PATTERN_NONE = 0x00,
+ DP_TEST_VIDEO_PATTERN_COLOR_RAMPS = 0x01,
+ DP_TEST_VIDEO_PATTERN_BW_VERT_LINES = 0x02,
+ DP_TEST_VIDEO_PATTERN_COLOR_SQUARE = 0x03,
+};
+
+enum test_bit_depth {
+ DP_TEST_BIT_DEPTH_6 = 0x00,
+ DP_TEST_BIT_DEPTH_8 = 0x01,
+ DP_TEST_BIT_DEPTH_10 = 0x02,
+ DP_TEST_BIT_DEPTH_UNKNOWN = 0xFFFFFFFF,
+};
+
+enum dp_link_response {
+ TEST_ACK = 0x1,
+ TEST_NACK = 0x2,
+ TEST_EDID_CHECKSUM_WRITE = 0x4,
+};
+
+enum audio_sample_rate {
+ AUDIO_SAMPLE_RATE_32_KHZ = 0x00,
+ AUDIO_SAMPLE_RATE_44_1_KHZ = 0x01,
+ AUDIO_SAMPLE_RATE_48_KHZ = 0x02,
+ AUDIO_SAMPLE_RATE_88_2_KHZ = 0x03,
+ AUDIO_SAMPLE_RATE_96_KHZ = 0x04,
+ AUDIO_SAMPLE_RATE_176_4_KHZ = 0x05,
+ AUDIO_SAMPLE_RATE_192_KHZ = 0x06,
+};
+
+enum audio_pattern_type {
+ AUDIO_TEST_PATTERN_OPERATOR_DEFINED = 0x00,
+ AUDIO_TEST_PATTERN_SAWTOOTH = 0x01,
+};
+
+struct dp_link_request {
+ u32 test_requested;
+ u32 test_link_rate;
+ u32 test_lane_count;
+ u32 phy_test_pattern_sel;
+ u32 test_video_pattern;
+ u32 test_bit_depth;
+ u32 test_dyn_range;
+ u32 test_h_total;
+ u32 test_v_total;
+ u32 test_h_start;
+ u32 test_v_start;
+ u32 test_hsync_pol;
+ u32 test_hsync_width;
+ u32 test_vsync_pol;
+ u32 test_vsync_width;
+ u32 test_h_width;
+ u32 test_v_height;
+ u32 test_rr_d;
+ u32 test_rr_n;
+ u32 test_audio_sampling_rate;
+ u32 test_audio_channel_count;
+ u32 test_audio_pattern_type;
+ u32 test_audio_period_ch_1;
+ u32 test_audio_period_ch_2;
+ u32 test_audio_period_ch_3;
+ u32 test_audio_period_ch_4;
+ u32 test_audio_period_ch_5;
+ u32 test_audio_period_ch_6;
+ u32 test_audio_period_ch_7;
+ u32 test_audio_period_ch_8;
+ u32 response;
+};
+
+struct dp_link_sink_count {
+ u32 count;
+ bool cp_ready;
+};
+
+struct dp_link_status {
+ u8 lane_01_status;
+ u8 lane_23_status;
+ u8 interlane_align_done;
+ u8 downstream_port_status_changed;
+ u8 link_status_updated;
+ u8 port_0_in_sync;
+ u8 port_1_in_sync;
+ u8 req_voltage_swing[4];
+ u8 req_pre_emphasis[4];
+};
+
+struct dp_link_private {
+ struct device *dev;
+ struct dp_aux *aux;
+ struct dp_link dp_link;
+
+ struct dp_link_request request;
+ struct dp_link_sink_count sink_count;
+ struct dp_link_status link_status;
+};
+
+/**
+ * mdss_dp_test_bit_depth_to_bpp() - convert test bit depth to bpp
+ * @tbd: test bit depth
+ *
+ * Returns the bits per pixel (bpp) to be used corresponding to the
+ * git bit depth value. This function assumes that bit depth has
+ * already been validated.
+ */
+static inline u32 dp_link_bit_depth_to_bpp(enum test_bit_depth tbd)
+{
+ u32 bpp;
+
+ /*
+ * Few simplistic rules and assumptions made here:
+ * 1. Bit depth is per color component
+ * 2. If bit depth is unknown return 0
+ * 3. Assume 3 color components
+ */
+ switch (tbd) {
+ case DP_TEST_BIT_DEPTH_6:
+ bpp = 18;
+ break;
+ case DP_TEST_BIT_DEPTH_8:
+ bpp = 24;
+ break;
+ case DP_TEST_BIT_DEPTH_10:
+ bpp = 30;
+ break;
+ case DP_TEST_BIT_DEPTH_UNKNOWN:
+ default:
+ bpp = 0;
+ }
+
+ return bpp;
+}
+
+static char *dp_link_get_phy_test_pattern(u32 phy_test_pattern_sel)
+{
+ switch (phy_test_pattern_sel) {
+ case PHY_TEST_PATTERN_NONE:
+ return DP_LINK_ENUM_STR(PHY_TEST_PATTERN_NONE);
+ case PHY_TEST_PATTERN_D10_2_NO_SCRAMBLING:
+ return DP_LINK_ENUM_STR(PHY_TEST_PATTERN_D10_2_NO_SCRAMBLING);
+ case PHY_TEST_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT:
+ return DP_LINK_ENUM_STR(
+ PHY_TEST_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT);
+ case PHY_TEST_PATTERN_PRBS7:
+ return DP_LINK_ENUM_STR(PHY_TEST_PATTERN_PRBS7);
+ case PHY_TEST_PATTERN_80_BIT_CUSTOM_PATTERN:
+ return DP_LINK_ENUM_STR(PHY_TEST_PATTERN_80_BIT_CUSTOM_PATTERN);
+ case PHY_TEST_PATTERN_HBR2_CTS_EYE_PATTERN:
+ return DP_LINK_ENUM_STR(PHY_TEST_PATTERN_HBR2_CTS_EYE_PATTERN);
+ default:
+ return "unknown";
+ }
+}
+
+static char *dp_link_get_audio_test_pattern(u32 pattern)
+{
+ switch (pattern) {
+ case AUDIO_TEST_PATTERN_OPERATOR_DEFINED:
+ return DP_LINK_ENUM_STR(AUDIO_TEST_PATTERN_OPERATOR_DEFINED);
+ case AUDIO_TEST_PATTERN_SAWTOOTH:
+ return DP_LINK_ENUM_STR(AUDIO_TEST_PATTERN_SAWTOOTH);
+ default:
+ return "unknown";
+ }
+}
+
+static char *dp_link_get_audio_sample_rate(u32 rate)
+{
+ switch (rate) {
+ case AUDIO_SAMPLE_RATE_32_KHZ:
+ return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_32_KHZ);
+ case AUDIO_SAMPLE_RATE_44_1_KHZ:
+ return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_44_1_KHZ);
+ case AUDIO_SAMPLE_RATE_48_KHZ:
+ return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_48_KHZ);
+ case AUDIO_SAMPLE_RATE_88_2_KHZ:
+ return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_88_2_KHZ);
+ case AUDIO_SAMPLE_RATE_96_KHZ:
+ return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_96_KHZ);
+ case AUDIO_SAMPLE_RATE_176_4_KHZ:
+ return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_176_4_KHZ);
+ case AUDIO_SAMPLE_RATE_192_KHZ:
+ return DP_LINK_ENUM_STR(AUDIO_SAMPLE_RATE_192_KHZ);
+ default:
+ return "unknown";
+ }
+}
+
+static int dp_link_get_period(struct dp_link_private *link, int const addr)
+{
+ int ret = 0;
+ u8 *bp;
+ u8 data;
+ int rlen;
+ u32 const param_len = 0x1;
+ u32 const max_audio_period = 0xA;
+
+ /* TEST_AUDIO_PERIOD_CH_XX */
+ rlen = link->aux->read(link->aux, addr, param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("failed to read test_audio_period (0x%x)\n", addr);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ data = *bp;
+
+ /* Period - Bits 3:0 */
+ data = data & 0xF;
+ if ((int)data > max_audio_period) {
+ pr_err("invalid test_audio_period_ch_1 = 0x%x\n", data);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ ret = data;
+exit:
+ return ret;
+}
+
+static int dp_link_parse_audio_channel_period(struct dp_link_private *link)
+{
+ int ret = 0;
+ int const test_audio_period_ch_1_addr = 0x273;
+ int const test_audio_period_ch_2_addr = 0x274;
+ int const test_audio_period_ch_3_addr = 0x275;
+ int const test_audio_period_ch_4_addr = 0x276;
+ int const test_audio_period_ch_5_addr = 0x277;
+ int const test_audio_period_ch_6_addr = 0x278;
+ int const test_audio_period_ch_7_addr = 0x279;
+ int const test_audio_period_ch_8_addr = 0x27A;
+ struct dp_link_request *req = &link->request;
+
+ /* TEST_AUDIO_PERIOD_CH_1 (Byte 0x273) */
+ ret = dp_link_get_period(link, test_audio_period_ch_1_addr);
+ if (ret == -EINVAL)
+ goto exit;
+
+ req->test_audio_period_ch_1 = ret;
+ pr_debug("test_audio_period_ch_1 = 0x%x\n", ret);
+
+ /* TEST_AUDIO_PERIOD_CH_2 (Byte 0x274) */
+ ret = dp_link_get_period(link, test_audio_period_ch_2_addr);
+ if (ret == -EINVAL)
+ goto exit;
+
+ req->test_audio_period_ch_2 = ret;
+ pr_debug("test_audio_period_ch_2 = 0x%x\n", ret);
+
+ /* TEST_AUDIO_PERIOD_CH_3 (Byte 0x275) */
+ ret = dp_link_get_period(link, test_audio_period_ch_3_addr);
+ if (ret == -EINVAL)
+ goto exit;
+
+ req->test_audio_period_ch_3 = ret;
+ pr_debug("test_audio_period_ch_3 = 0x%x\n", ret);
+
+ /* TEST_AUDIO_PERIOD_CH_4 (Byte 0x276) */
+ ret = dp_link_get_period(link, test_audio_period_ch_4_addr);
+ if (ret == -EINVAL)
+ goto exit;
+
+ req->test_audio_period_ch_4 = ret;
+ pr_debug("test_audio_period_ch_4 = 0x%x\n", ret);
+
+ /* TEST_AUDIO_PERIOD_CH_5 (Byte 0x277) */
+ ret = dp_link_get_period(link, test_audio_period_ch_5_addr);
+ if (ret == -EINVAL)
+ goto exit;
+
+ req->test_audio_period_ch_5 = ret;
+ pr_debug("test_audio_period_ch_5 = 0x%x\n", ret);
+
+ /* TEST_AUDIO_PERIOD_CH_6 (Byte 0x278) */
+ ret = dp_link_get_period(link, test_audio_period_ch_6_addr);
+ if (ret == -EINVAL)
+ goto exit;
+
+ req->test_audio_period_ch_6 = ret;
+ pr_debug("test_audio_period_ch_6 = 0x%x\n", ret);
+
+ /* TEST_AUDIO_PERIOD_CH_7 (Byte 0x279) */
+ ret = dp_link_get_period(link, test_audio_period_ch_7_addr);
+ if (ret == -EINVAL)
+ goto exit;
+
+ req->test_audio_period_ch_7 = ret;
+ pr_debug("test_audio_period_ch_7 = 0x%x\n", ret);
+
+ /* TEST_AUDIO_PERIOD_CH_8 (Byte 0x27A) */
+ ret = dp_link_get_period(link, test_audio_period_ch_8_addr);
+ if (ret == -EINVAL)
+ goto exit;
+
+ req->test_audio_period_ch_8 = ret;
+ pr_debug("test_audio_period_ch_8 = 0x%x\n", ret);
+exit:
+ return ret;
+}
+
+static int dp_link_parse_audio_pattern_type(struct dp_link_private *link)
+{
+ int ret = 0;
+ u8 *bp;
+ u8 data;
+ int rlen;
+ int const param_len = 0x1;
+ int const test_audio_pattern_type_addr = 0x272;
+ int const max_audio_pattern_type = 0x1;
+
+ /* Read the requested audio pattern type (Byte 0x272). */
+ rlen = link->aux->read(link->aux, test_audio_pattern_type_addr,
+ param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("failed to read link audio mode data\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+ data = *bp;
+
+ /* Audio Pattern Type - Bits 7:0 */
+ if ((int)data > max_audio_pattern_type) {
+ pr_err("invalid audio pattern type = 0x%x\n", data);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ link->request.test_audio_pattern_type = data;
+ pr_debug("audio pattern type = %s\n",
+ dp_link_get_audio_test_pattern(data));
+exit:
+ return ret;
+}
+
+static int dp_link_parse_audio_mode(struct dp_link_private *link)
+{
+ int ret = 0;
+ u8 *bp;
+ u8 data;
+ int rlen;
+ int const param_len = 0x1;
+ int const test_audio_mode_addr = 0x271;
+ int const max_audio_sampling_rate = 0x6;
+ int const max_audio_channel_count = 0x8;
+ int sampling_rate = 0x0;
+ int channel_count = 0x0;
+
+ /* Read the requested audio mode (Byte 0x271). */
+ rlen = link->aux->read(link->aux, test_audio_mode_addr,
+ param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("failed to read link audio mode data\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+ data = *bp;
+
+ /* Sampling Rate - Bits 3:0 */
+ sampling_rate = data & 0xF;
+ if (sampling_rate > max_audio_sampling_rate) {
+ pr_err("sampling rate (0x%x) greater than max (0x%x)\n",
+ sampling_rate, max_audio_sampling_rate);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ /* Channel Count - Bits 7:4 */
+ channel_count = ((data & 0xF0) >> 4) + 1;
+ if (channel_count > max_audio_channel_count) {
+ pr_err("channel_count (0x%x) greater than max (0x%x)\n",
+ channel_count, max_audio_channel_count);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ link->request.test_audio_sampling_rate = sampling_rate;
+ link->request.test_audio_channel_count = channel_count;
+ pr_debug("sampling_rate = %s, channel_count = 0x%x\n",
+ dp_link_get_audio_sample_rate(sampling_rate), channel_count);
+exit:
+ return ret;
+}
+
+/**
+ * dp_parse_audio_pattern_params() - parses audio pattern parameters from DPCD
+ * @link: Display Port Driver data
+ *
+ * Returns 0 if it successfully parses the audio link pattern parameters.
+ */
+static int dp_link_parse_audio_pattern_params(struct dp_link_private *link)
+{
+ int ret = 0;
+
+ ret = dp_link_parse_audio_mode(link);
+ if (ret)
+ goto exit;
+
+ ret = dp_link_parse_audio_pattern_type(link);
+ if (ret)
+ goto exit;
+
+ ret = dp_link_parse_audio_channel_period(link);
+
+exit:
+ return ret;
+}
+
+/**
+ * dp_link_is_video_pattern_valid() - validates the video pattern
+ * @pattern: video pattern requested by the sink
+ *
+ * Returns true if the requested video pattern is supported.
+ */
+static bool dp_link_is_video_pattern_valid(u32 pattern)
+{
+ switch (pattern) {
+ case DP_TEST_VIDEO_PATTERN_NONE:
+ case DP_TEST_VIDEO_PATTERN_COLOR_RAMPS:
+ case DP_TEST_VIDEO_PATTERN_BW_VERT_LINES:
+ case DP_TEST_VIDEO_PATTERN_COLOR_SQUARE:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static char *dp_link_video_pattern_to_string(u32 test_video_pattern)
+{
+ switch (test_video_pattern) {
+ case DP_TEST_VIDEO_PATTERN_NONE:
+ return DP_LINK_ENUM_STR(DP_TEST_VIDEO_PATTERN_NONE);
+ case DP_TEST_VIDEO_PATTERN_COLOR_RAMPS:
+ return DP_LINK_ENUM_STR(DP_TEST_VIDEO_PATTERN_COLOR_RAMPS);
+ case DP_TEST_VIDEO_PATTERN_BW_VERT_LINES:
+ return DP_LINK_ENUM_STR(DP_TEST_VIDEO_PATTERN_BW_VERT_LINES);
+ case DP_TEST_VIDEO_PATTERN_COLOR_SQUARE:
+ return DP_LINK_ENUM_STR(DP_TEST_VIDEO_PATTERN_COLOR_SQUARE);
+ default:
+ return "unknown";
+ }
+}
+
+/**
+ * dp_link_is_dynamic_range_valid() - validates the dynamic range
+ * @bit_depth: the dynamic range value to be checked
+ *
+ * Returns true if the dynamic range value is supported.
+ */
+static bool dp_link_is_dynamic_range_valid(u32 dr)
+{
+ switch (dr) {
+ case DP_DYNAMIC_RANGE_RGB_VESA:
+ case DP_DYNAMIC_RANGE_RGB_CEA:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static char *dp_link_dynamic_range_to_string(u32 dr)
+{
+ switch (dr) {
+ case DP_DYNAMIC_RANGE_RGB_VESA:
+ return DP_LINK_ENUM_STR(DP_DYNAMIC_RANGE_RGB_VESA);
+ case DP_DYNAMIC_RANGE_RGB_CEA:
+ return DP_LINK_ENUM_STR(DP_DYNAMIC_RANGE_RGB_CEA);
+ case DP_DYNAMIC_RANGE_UNKNOWN:
+ default:
+ return "unknown";
+ }
+}
+
+/**
+ * dp_link_is_bit_depth_valid() - validates the bit depth requested
+ * @bit_depth: bit depth requested by the sink
+ *
+ * Returns true if the requested bit depth is supported.
+ */
+static bool dp_link_is_bit_depth_valid(u32 tbd)
+{
+ /* DP_TEST_VIDEO_PATTERN_NONE is treated as invalid */
+ switch (tbd) {
+ case DP_TEST_BIT_DEPTH_6:
+ case DP_TEST_BIT_DEPTH_8:
+ case DP_TEST_BIT_DEPTH_10:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static char *dp_link_bit_depth_to_string(u32 tbd)
+{
+ switch (tbd) {
+ case DP_TEST_BIT_DEPTH_6:
+ return DP_LINK_ENUM_STR(DP_TEST_BIT_DEPTH_6);
+ case DP_TEST_BIT_DEPTH_8:
+ return DP_LINK_ENUM_STR(DP_TEST_BIT_DEPTH_8);
+ case DP_TEST_BIT_DEPTH_10:
+ return DP_LINK_ENUM_STR(DP_TEST_BIT_DEPTH_10);
+ case DP_TEST_BIT_DEPTH_UNKNOWN:
+ default:
+ return "unknown";
+ }
+}
+
+static int dp_link_parse_timing_params1(struct dp_link_private *link,
+ int const addr, int const len, u32 *val)
+{
+ u8 *bp;
+ int rlen;
+
+ if (len < 2)
+ return -EINVAL;
+
+ /* Read the requested video link pattern (Byte 0x221). */
+ rlen = link->aux->read(link->aux, addr, len, AUX_NATIVE, &bp);
+ if (rlen < len) {
+ pr_err("failed to read 0x%x\n", addr);
+ return -EINVAL;
+ }
+
+ *val = bp[1] | (bp[0] << 8);
+
+ return 0;
+}
+
+static int dp_link_parse_timing_params2(struct dp_link_private *link,
+ int const addr, int const len, u32 *val1, u32 *val2)
+{
+ u8 *bp;
+ int rlen;
+
+ if (len < 2)
+ return -EINVAL;
+
+ /* Read the requested video link pattern (Byte 0x221). */
+ rlen = link->aux->read(link->aux, addr, len, AUX_NATIVE, &bp);
+ if (rlen < len) {
+ pr_err("failed to read 0x%x\n", addr);
+ return -EINVAL;
+ }
+
+ *val1 = (bp[0] & BIT(7)) >> 7;
+ *val2 = bp[1] | ((bp[0] & 0x7F) << 8);
+
+ return 0;
+}
+
+static int dp_link_parse_timing_params3(struct dp_link_private *link,
+ int const addr, u32 *val)
+{
+ u8 *bp;
+ u32 len = 1;
+ int rlen;
+
+ /* Read the requested video link pattern (Byte 0x221). */
+ rlen = link->aux->read(link->aux, addr, len, AUX_NATIVE, &bp);
+ if (rlen < 1) {
+ pr_err("failed to read 0x%x\n", addr);
+ return -EINVAL;
+ }
+ *val = bp[0];
+
+ return 0;
+}
+
+/**
+ * dp_parse_video_pattern_params() - parses video pattern parameters from DPCD
+ * @link: Display Port Driver data
+ *
+ * Returns 0 if it successfully parses the video link pattern and the link
+ * bit depth requested by the sink and, and if the values parsed are valid.
+ */
+static int dp_link_parse_video_pattern_params(struct dp_link_private *link)
+{
+ int ret = 0;
+ int rlen;
+ u8 *bp;
+ u8 data;
+ u32 dyn_range;
+ int const param_len = 0x1;
+ int const test_video_pattern_addr = 0x221;
+ int const test_misc_addr = 0x232;
+
+ /* Read the requested video link pattern (Byte 0x221). */
+ rlen = link->aux->read(link->aux, test_video_pattern_addr,
+ param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("failed to read link video pattern\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+ data = *bp;
+
+ if (!dp_link_is_video_pattern_valid(data)) {
+ pr_err("invalid link video pattern = 0x%x\n", data);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ link->request.test_video_pattern = data;
+ pr_debug("link video pattern = 0x%x (%s)\n",
+ link->request.test_video_pattern,
+ dp_link_video_pattern_to_string(
+ link->request.test_video_pattern));
+
+ /* Read the requested color bit depth and dynamic range (Byte 0x232) */
+ rlen = link->aux->read(link->aux, test_misc_addr,
+ param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("failed to read link bit depth\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+ data = *bp;
+
+ /* Dynamic Range */
+ dyn_range = (data & BIT(3)) >> 3;
+ if (!dp_link_is_dynamic_range_valid(dyn_range)) {
+ pr_err("invalid link dynamic range = 0x%x", dyn_range);
+ ret = -EINVAL;
+ goto exit;
+ }
+ link->request.test_dyn_range = dyn_range;
+ pr_debug("link dynamic range = 0x%x (%s)\n",
+ link->request.test_dyn_range,
+ dp_link_dynamic_range_to_string(
+ link->request.test_dyn_range));
+
+ /* Color bit depth */
+ data &= (BIT(5) | BIT(6) | BIT(7));
+ data >>= 5;
+ if (!dp_link_is_bit_depth_valid(data)) {
+ pr_err("invalid link bit depth = 0x%x\n", data);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ link->request.test_bit_depth = data;
+ pr_debug("link bit depth = 0x%x (%s)\n",
+ link->request.test_bit_depth,
+ dp_link_bit_depth_to_string(link->request.test_bit_depth));
+
+ /* resolution timing params */
+ ret = dp_link_parse_timing_params1(link, 0x222, 2,
+ &link->request.test_h_total);
+ if (ret) {
+ pr_err("failed to parse test_h_total (0x222)\n");
+ goto exit;
+ }
+ pr_debug("TEST_H_TOTAL = %d\n", link->request.test_h_total);
+
+ ret = dp_link_parse_timing_params1(link, 0x224, 2,
+ &link->request.test_v_total);
+ if (ret) {
+ pr_err("failed to parse test_v_total (0x224)\n");
+ goto exit;
+ }
+ pr_debug("TEST_V_TOTAL = %d\n", link->request.test_v_total);
+
+ ret = dp_link_parse_timing_params1(link, 0x226, 2,
+ &link->request.test_h_start);
+ if (ret) {
+ pr_err("failed to parse test_h_start (0x226)\n");
+ goto exit;
+ }
+ pr_debug("TEST_H_START = %d\n", link->request.test_h_start);
+
+ ret = dp_link_parse_timing_params1(link, 0x228, 2,
+ &link->request.test_v_start);
+ if (ret) {
+ pr_err("failed to parse test_v_start (0x228)\n");
+ goto exit;
+ }
+ pr_debug("TEST_V_START = %d\n", link->request.test_v_start);
+
+ ret = dp_link_parse_timing_params2(link, 0x22A, 2,
+ &link->request.test_hsync_pol,
+ &link->request.test_hsync_width);
+ if (ret) {
+ pr_err("failed to parse (0x22A)\n");
+ goto exit;
+ }
+ pr_debug("TEST_HSYNC_POL = %d\n", link->request.test_hsync_pol);
+ pr_debug("TEST_HSYNC_WIDTH = %d\n", link->request.test_hsync_width);
+
+ ret = dp_link_parse_timing_params2(link, 0x22C, 2,
+ &link->request.test_vsync_pol,
+ &link->request.test_vsync_width);
+ if (ret) {
+ pr_err("failed to parse (0x22C)\n");
+ goto exit;
+ }
+ pr_debug("TEST_VSYNC_POL = %d\n", link->request.test_vsync_pol);
+ pr_debug("TEST_VSYNC_WIDTH = %d\n", link->request.test_vsync_width);
+
+ ret = dp_link_parse_timing_params1(link, 0x22E, 2,
+ &link->request.test_h_width);
+ if (ret) {
+ pr_err("failed to parse test_h_width (0x22E)\n");
+ goto exit;
+ }
+ pr_debug("TEST_H_WIDTH = %d\n", link->request.test_h_width);
+
+ ret = dp_link_parse_timing_params1(link, 0x230, 2,
+ &link->request.test_v_height);
+ if (ret) {
+ pr_err("failed to parse test_v_height (0x230)\n");
+ goto exit;
+ }
+ pr_debug("TEST_V_HEIGHT = %d\n", link->request.test_v_height);
+
+ ret = dp_link_parse_timing_params3(link, 0x233,
+ &link->request.test_rr_d);
+ link->request.test_rr_d &= BIT(0);
+ if (ret) {
+ pr_err("failed to parse test_rr_d (0x233)\n");
+ goto exit;
+ }
+ pr_debug("TEST_REFRESH_DENOMINATOR = %d\n", link->request.test_rr_d);
+
+ ret = dp_link_parse_timing_params3(link, 0x234,
+ &link->request.test_rr_n);
+ if (ret) {
+ pr_err("failed to parse test_rr_n (0x234)\n");
+ goto exit;
+ }
+ pr_debug("TEST_REFRESH_NUMERATOR = %d\n", link->request.test_rr_n);
+exit:
+ return ret;
+}
+
+/**
+ * dp_link_is_link_rate_valid() - validates the link rate
+ * @lane_rate: link rate requested by the sink
+ *
+ * Returns true if the requested link rate is supported.
+ */
+static bool dp_link_is_link_rate_valid(u32 link_rate)
+{
+ return ((link_rate == DP_LINK_RATE_162) ||
+ (link_rate == DP_LINK_RATE_270) ||
+ (link_rate == DP_LINK_RATE_540) ||
+ (link_rate == DP_LINK_RATE_810));
+}
+
+/**
+ * dp_link_is_lane_count_valid() - validates the lane count
+ * @lane_count: lane count requested by the sink
+ *
+ * Returns true if the requested lane count is supported.
+ */
+static bool dp_link_is_lane_count_valid(u32 lane_count)
+{
+ return (lane_count == DP_LANE_COUNT_1) ||
+ (lane_count == DP_LANE_COUNT_2) ||
+ (lane_count == DP_LANE_COUNT_4);
+}
+
+/**
+ * dp_link_parse_link_training_params() - parses link training parameters from
+ * DPCD
+ * @link: Display Port Driver data
+ *
+ * Returns 0 if it successfully parses the link rate (Byte 0x219) and lane
+ * count (Byte 0x220), and if these values parse are valid.
+ */
+static int dp_link_parse_link_training_params(struct dp_link_private *link)
+{
+ u8 *bp;
+ u8 data;
+ int ret = 0;
+ int rlen;
+ int const param_len = 0x1;
+ int const test_link_rate_addr = 0x219;
+ int const test_lane_count_addr = 0x220;
+
+ /* Read the requested link rate (Byte 0x219). */
+ rlen = link->aux->read(link->aux, test_link_rate_addr,
+ param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("failed to read link rate\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+ data = *bp;
+
+ if (!dp_link_is_link_rate_valid(data)) {
+ pr_err("invalid link rate = 0x%x\n", data);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ link->request.test_link_rate = data;
+ pr_debug("link rate = 0x%x\n", link->request.test_link_rate);
+
+ /* Read the requested lane count (Byte 0x220). */
+ rlen = link->aux->read(link->aux, test_lane_count_addr,
+ param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("failed to read lane count\n");
+ ret = -EINVAL;
+ goto exit;
+ }
+ data = *bp;
+ data &= 0x1F;
+
+ if (!dp_link_is_lane_count_valid(data)) {
+ pr_err("invalid lane count = 0x%x\n", data);
+ ret = -EINVAL;
+ goto exit;
+ }
+
+ link->request.test_lane_count = data;
+ pr_debug("lane count = 0x%x\n", link->request.test_lane_count);
+exit:
+ return ret;
+}
+
+static bool dp_link_is_phy_test_pattern_supported(u32 phy_test_pattern_sel)
+{
+ switch (phy_test_pattern_sel) {
+ case PHY_TEST_PATTERN_NONE:
+ case PHY_TEST_PATTERN_D10_2_NO_SCRAMBLING:
+ case PHY_TEST_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT:
+ case PHY_TEST_PATTERN_PRBS7:
+ case PHY_TEST_PATTERN_80_BIT_CUSTOM_PATTERN:
+ case PHY_TEST_PATTERN_HBR2_CTS_EYE_PATTERN:
+ return true;
+ default:
+ return false;
+ }
+}
+
+/**
+ * dp_parse_phy_test_params() - parses the phy link parameters
+ * @link: Display Port Driver data
+ *
+ * Parses the DPCD (Byte 0x248) for the DP PHY link pattern that is being
+ * requested.
+ */
+static int dp_link_parse_phy_test_params(struct dp_link_private *link)
+{
+ u8 *bp;
+ u8 data;
+ int rlen;
+ int const param_len = 0x1;
+ int const phy_test_pattern_addr = 0x248;
+ int ret = 0;
+
+ rlen = link->aux->read(link->aux, phy_test_pattern_addr,
+ param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("failed to read phy link pattern\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ data = *bp;
+
+ link->request.phy_test_pattern_sel = data;
+
+ pr_debug("phy_test_pattern_sel = %s\n",
+ dp_link_get_phy_test_pattern(data));
+
+ if (!dp_link_is_phy_test_pattern_supported(data))
+ ret = -EINVAL;
+end:
+ return ret;
+}
+
+static char *dp_link_get_test_name(u32 test_requested)
+{
+ switch (test_requested) {
+ case TEST_LINK_TRAINING: return DP_LINK_ENUM_STR(TEST_LINK_TRAINING);
+ case TEST_VIDEO_PATTERN: return DP_LINK_ENUM_STR(TEST_VIDEO_PATTERN);
+ case PHY_TEST_PATTERN: return DP_LINK_ENUM_STR(PHY_TEST_PATTERN);
+ case TEST_EDID_READ: return DP_LINK_ENUM_STR(TEST_EDID_READ);
+ case TEST_AUDIO_PATTERN: return DP_LINK_ENUM_STR(TEST_AUDIO_PATTERN);
+ default: return "unknown";
+ }
+}
+
+/**
+ * dp_link_is_video_audio_test_requested() - checks for audio/video link request
+ * @link: link requested by the sink
+ *
+ * Returns true if the requested link is a permitted audio/video link.
+ */
+static bool dp_link_is_video_audio_test_requested(u32 link)
+{
+ return (link == TEST_VIDEO_PATTERN) ||
+ (link == (TEST_AUDIO_PATTERN | TEST_VIDEO_PATTERN)) ||
+ (link == TEST_AUDIO_PATTERN) ||
+ (link == (TEST_AUDIO_PATTERN | TEST_AUDIO_DISABLED_VIDEO));
+}
+
+/**
+ * dp_link_supported() - checks if link requested by sink is supported
+ * @test_requested: link requested by the sink
+ *
+ * Returns true if the requested link is supported.
+ */
+static bool dp_link_is_test_supported(u32 test_requested)
+{
+ return (test_requested == TEST_LINK_TRAINING) ||
+ (test_requested == TEST_EDID_READ) ||
+ (test_requested == PHY_TEST_PATTERN) ||
+ dp_link_is_video_audio_test_requested(test_requested);
+}
+
+/**
+ * dp_sink_parse_test_request() - parses link request parameters from sink
+ * @link: Display Port Driver data
+ *
+ * Parses the DPCD to check if an automated link is requested (Byte 0x201),
+ * and what type of link automation is being requested (Byte 0x218).
+ */
+static int dp_link_parse_request(struct dp_link_private *link)
+{
+ int ret = 0;
+ u8 *bp;
+ u8 data;
+ int rlen;
+ u32 const param_len = 0x1;
+ u32 const device_service_irq_addr = 0x201;
+ u32 const test_request_addr = 0x218;
+ u8 buf[4];
+
+ /**
+ * Read the device service IRQ vector (Byte 0x201) to determine
+ * whether an automated link has been requested by the sink.
+ */
+ rlen = link->aux->read(link->aux, device_service_irq_addr,
+ param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("aux read failed\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ data = *bp;
+
+ pr_debug("device service irq vector = 0x%x\n", data);
+
+ if (!(data & BIT(1))) {
+ pr_debug("no link requested\n");
+ goto end;
+ }
+
+ /**
+ * Read the link request byte (Byte 0x218) to determine what type
+ * of automated link has been requested by the sink.
+ */
+ rlen = link->aux->read(link->aux, test_request_addr,
+ param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("aux read failed\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ data = *bp;
+
+ if (!dp_link_is_test_supported(data)) {
+ pr_debug("link 0x%x not supported\n", data);
+ goto end;
+ }
+
+ pr_debug("%s (0x%x) requested\n", dp_link_get_test_name(data), data);
+ link->request.test_requested = data;
+
+ if (link->request.test_requested == PHY_TEST_PATTERN) {
+ ret = dp_link_parse_phy_test_params(link);
+ if (ret)
+ goto end;
+ ret = dp_link_parse_link_training_params(link);
+ }
+
+ if (link->request.test_requested == TEST_LINK_TRAINING)
+ ret = dp_link_parse_link_training_params(link);
+
+ if (dp_link_is_video_audio_test_requested(
+ link->request.test_requested)) {
+ ret = dp_link_parse_video_pattern_params(link);
+ if (ret)
+ goto end;
+
+ ret = dp_link_parse_audio_pattern_params(link);
+ }
+end:
+ /* clear the link request IRQ */
+ buf[0] = 1;
+ link->aux->write(link->aux, test_request_addr, 1, AUX_NATIVE, buf);
+
+ /**
+ * Send a TEST_ACK if all link parameters are valid, otherwise send
+ * a TEST_NACK.
+ */
+ if (ret)
+ link->request.response = TEST_NACK;
+ else
+ link->request.response = TEST_ACK;
+
+ return ret;
+}
+
+/**
+ * dp_link_parse_sink_count() - parses the sink count
+ *
+ * Parses the DPCD to check if there is an update to the sink count
+ * (Byte 0x200), and whether all the sink devices connected have Content
+ * Protection enabled.
+ */
+static void dp_link_parse_sink_count(struct dp_link_private *link)
+{
+ u8 *bp;
+ u8 data;
+ int rlen;
+ int const param_len = 0x1;
+ int const sink_count_addr = 0x200;
+
+ rlen = link->aux->read(link->aux, sink_count_addr,
+ param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("failed to read sink count\n");
+ return;
+ }
+
+ data = *bp;
+
+ /* BIT 7, BIT 5:0 */
+ link->sink_count.count = (data & BIT(7)) << 6 | (data & 0x63);
+ /* BIT 6*/
+ link->sink_count.cp_ready = data & BIT(6);
+
+ pr_debug("sink_count = 0x%x, cp_ready = 0x%x\n",
+ link->sink_count.count, link->sink_count.cp_ready);
+}
+
+static int dp_link_link_status_read(struct dp_link_private *link)
+{
+ u8 *bp;
+ u8 data;
+ int rlen, ret = 0;
+ int const addr = 0x202;
+ int const len = 6;
+ struct dp_link_status *sp;
+
+ rlen = link->aux->read(link->aux, addr, len, AUX_NATIVE, &bp);
+ if (rlen < len) {
+ pr_err("edp aux read failed\n");
+ ret = -EINVAL;
+ goto error;
+ }
+
+ sp = &link->link_status;
+
+ data = *bp++; /* byte 0x202 */
+ sp->lane_01_status = data; /* lane 0, 1 */
+
+ data = *bp++; /* byte 0x203 */
+ sp->lane_23_status = data; /* lane 2, 3 */
+
+ data = *bp++; /* byte 0x204 */
+ sp->interlane_align_done = (data & BIT(0));
+ sp->downstream_port_status_changed = (data & BIT(6));
+ sp->link_status_updated = (data & BIT(7));
+
+ data = *bp++; /* byte 0x205 */
+ sp->port_0_in_sync = (data & BIT(0));
+ sp->port_1_in_sync = (data & BIT(1));
+
+ data = *bp++; /* byte 0x206 */
+ sp->req_voltage_swing[0] = data & 0x03;
+ data >>= 2;
+ sp->req_pre_emphasis[0] = data & 0x03;
+ data >>= 2;
+ sp->req_voltage_swing[1] = data & 0x03;
+ data >>= 2;
+ sp->req_pre_emphasis[1] = data & 0x03;
+
+ data = *bp++; /* byte 0x207 */
+ sp->req_voltage_swing[2] = data & 0x03;
+ data >>= 2;
+ sp->req_pre_emphasis[2] = data & 0x03;
+ data >>= 2;
+ sp->req_voltage_swing[3] = data & 0x03;
+ data >>= 2;
+ sp->req_pre_emphasis[3] = data & 0x03;
+
+ return 0;
+error:
+ return ret;
+}
+
+static void dp_link_parse_sink_status_field(struct dp_link_private *link)
+{
+ dp_link_parse_sink_count(link);
+ dp_link_parse_request(link);
+ dp_link_link_status_read(link);
+}
+
+static bool dp_link_is_link_training_requested(struct dp_link_private *link)
+{
+ return (link->request.test_requested == TEST_LINK_TRAINING);
+}
+
+/**
+ * dp_link_process_link_training_request() - processes new training requests
+ * @link: Display Port link data
+ *
+ * This function will handle new link training requests that are initiated by
+ * the sink. In particular, it will update the requested lane count and link
+ * link rate, and then trigger the link retraining procedure.
+ *
+ * The function will return 0 if a link training request has been processed,
+ * otherwise it will return -EINVAL.
+ */
+static int dp_link_process_link_training_request(struct dp_link_private *link)
+{
+ if (!dp_link_is_link_training_requested(link))
+ return -EINVAL;
+
+ pr_debug("%s link rate = 0x%x, lane count = 0x%x\n",
+ dp_link_get_test_name(TEST_LINK_TRAINING),
+ link->request.test_link_rate,
+ link->request.test_lane_count);
+
+ link->dp_link.lane_count = link->request.test_lane_count;
+ link->dp_link.link_rate = link->request.test_link_rate;
+
+ return 0;
+}
+
+static bool dp_link_phy_pattern_requested(struct dp_link *dp_link)
+{
+ struct dp_link_private *link = container_of(dp_link,
+ struct dp_link_private, dp_link);
+
+ return (link->request.test_requested == PHY_TEST_PATTERN);
+}
+
+static int dp_link_parse_vx_px(struct dp_link_private *link)
+{
+ u8 *bp;
+ u8 data;
+ int const param_len = 0x1;
+ int const addr1 = 0x206;
+ int const addr2 = 0x207;
+ int ret = 0;
+ u32 v0, p0, v1, p1, v2, p2, v3, p3;
+ int rlen;
+
+ pr_debug("\n");
+
+ rlen = link->aux->read(link->aux, addr1, param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("failed reading lanes 0/1\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ data = *bp;
+
+ pr_debug("lanes 0/1 (Byte 0x206): 0x%x\n", data);
+
+ v0 = data & 0x3;
+ data = data >> 2;
+ p0 = data & 0x3;
+ data = data >> 2;
+
+ v1 = data & 0x3;
+ data = data >> 2;
+ p1 = data & 0x3;
+ data = data >> 2;
+
+ rlen = link->aux->read(link->aux, addr2, param_len, AUX_NATIVE, &bp);
+ if (rlen < param_len) {
+ pr_err("failed reading lanes 2/3\n");
+ ret = -EINVAL;
+ goto end;
+ }
+
+ data = *bp;
+
+ pr_debug("lanes 2/3 (Byte 0x207): 0x%x\n", data);
+
+ v2 = data & 0x3;
+ data = data >> 2;
+ p2 = data & 0x3;
+ data = data >> 2;
+
+ v3 = data & 0x3;
+ data = data >> 2;
+ p3 = data & 0x3;
+ data = data >> 2;
+
+ pr_debug("vx: 0=%d, 1=%d, 2=%d, 3=%d\n", v0, v1, v2, v3);
+ pr_debug("px: 0=%d, 1=%d, 2=%d, 3=%d\n", p0, p1, p2, p3);
+
+ /**
+ * Update the voltage and pre-emphasis levels as per DPCD request
+ * vector.
+ */
+ pr_debug("Current: v_level = 0x%x, p_level = 0x%x\n",
+ link->dp_link.v_level, link->dp_link.p_level);
+ pr_debug("Requested: v_level = 0x%x, p_level = 0x%x\n", v0, p0);
+ link->dp_link.v_level = v0;
+ link->dp_link.p_level = p0;
+
+ pr_debug("Success\n");
+end:
+ return ret;
+}
+
+/**
+ * dp_link_process_phy_test_pattern_request() - process new phy link requests
+ * @link: Display Port Driver data
+ *
+ * This function will handle new phy link pattern requests that are initiated
+ * by the sink. The function will return 0 if a phy link pattern has been
+ * processed, otherwise it will return -EINVAL.
+ */
+static int dp_link_process_phy_test_pattern_request(
+ struct dp_link_private *link)
+{
+ u32 test_link_rate = 0, test_lane_count = 0;
+
+ if (!dp_link_phy_pattern_requested(&link->dp_link))
+ return -EINVAL;
+
+ test_link_rate = link->request.test_link_rate;
+ test_lane_count = link->request.test_lane_count;
+
+ if (!dp_link_is_link_rate_valid(test_link_rate) ||
+ !dp_link_is_lane_count_valid(test_lane_count)) {
+ pr_err("Invalid params: link rate = 0x%x, lane count = 0x%x\n",
+ test_link_rate, test_lane_count);
+ return -EINVAL;
+ }
+
+ pr_debug("start\n");
+
+ link->dp_link.lane_count = link->request.test_lane_count;
+ link->dp_link.link_rate = link->request.test_link_rate;
+
+ dp_link_parse_vx_px(link);
+
+ pr_debug("end\n");
+
+ return 0;
+}
+
+static bool dp_link_is_link_status_updated(struct dp_link_private *link)
+{
+ return link->link_status.link_status_updated;
+}
+
+static bool dp_link_channel_eq_done(struct dp_link_private *link)
+{
+ u32 mask, data;
+ struct dp_link *dp_link = &link->dp_link;
+
+ pr_debug("\n");
+
+ dp_link_link_status_read(link);
+
+ if (!link->link_status.interlane_align_done) { /* not align */
+ pr_err("interlane align failed\n");
+ return 0;
+ }
+
+ if (dp_link->lane_count == 1) {
+ mask = 0x7;
+ data = link->link_status.lane_01_status;
+ } else if (dp_link->lane_count == 2) {
+ mask = 0x77;
+ data = link->link_status.lane_01_status;
+ } else {
+ mask = 0x7777;
+ data = link->link_status.lane_23_status;
+ data <<= 8;
+ data |= link->link_status.lane_01_status;
+ }
+
+ data &= mask;
+ pr_debug("data=%x mask=%x\n", data, mask);
+
+ if (data == mask)/* all done */
+ return true;
+
+ return false;
+}
+
+static bool dp_link_clock_recovery_done(struct dp_link_private *link)
+{
+ u32 mask, data;
+ struct dp_link *dp_link = &link->dp_link;
+
+ dp_link_link_status_read(link);
+
+ if (dp_link->lane_count == 1) {
+ mask = 0x01; /* lane 0 */
+ data = link->link_status.lane_01_status;
+ } else if (dp_link->lane_count == 2) {
+ mask = 0x011; /*B lane 0, 1 */
+ data = link->link_status.lane_01_status;
+ } else {
+ mask = 0x01111; /*B lane 0, 1 */
+ data = link->link_status.lane_23_status;
+ data <<= 8;
+ data |= link->link_status.lane_01_status;
+ }
+
+ data &= mask;
+ pr_debug("data=%x mask=%x\n", data, mask);
+
+ if (data == mask) /* all done */
+ return true;
+
+ return false;
+}
+
+/**
+ * dp_link_process_link_status_update() - processes link status updates
+ * @link: Display Port link module data
+ *
+ * This function will check for changes in the link status, e.g. clock
+ * recovery done on all lanes, and trigger link training if there is a
+ * failure/error on the link.
+ *
+ * The function will return 0 if the a link status update has been processed,
+ * otherwise it will return -EINVAL.
+ */
+static int dp_link_process_link_status_update(struct dp_link_private *link)
+{
+ if (!dp_link_is_link_status_updated(link) ||
+ (dp_link_channel_eq_done(link) &&
+ dp_link_clock_recovery_done(link)))
+ return -EINVAL;
+
+ pr_debug("channel_eq_done = %d, clock_recovery_done = %d\n",
+ dp_link_channel_eq_done(link),
+ dp_link_clock_recovery_done(link));
+
+ return 0;
+}
+
+static bool dp_link_is_ds_port_status_changed(struct dp_link_private *link)
+{
+ return link->link_status.downstream_port_status_changed;
+}
+
+/**
+ * dp_link_process_downstream_port_status_change() - process port status changes
+ * @link: Display Port Driver data
+ *
+ * This function will handle downstream port updates that are initiated by
+ * the sink. If the downstream port status has changed, the EDID is read via
+ * AUX.
+ *
+ * The function will return 0 if a downstream port update has been
+ * processed, otherwise it will return -EINVAL.
+ */
+static int dp_link_process_ds_port_status_change(struct dp_link_private *link)
+{
+ if (!dp_link_is_ds_port_status_changed(link))
+ return -EINVAL;
+
+ return 0;
+}
+
+static bool dp_link_is_video_pattern_requested(struct dp_link_private *link)
+{
+ return (link->request.test_requested & TEST_VIDEO_PATTERN)
+ && !(link->request.test_requested & TEST_AUDIO_DISABLED_VIDEO);
+}
+
+static bool dp_link_is_audio_pattern_requested(struct dp_link_private *link)
+{
+ return (link->request.test_requested & TEST_AUDIO_PATTERN);
+}
+
+/**
+ * dp_link_process_video_pattern_request() - process new video pattern request
+ * @link: Display Port link module's data
+ *
+ * This function will handle a new video pattern request that are initiated by
+ * the sink. This is acheieved by first sending a disconnect notification to
+ * the sink followed by a subsequent connect notification to the user modules,
+ * where it is expected that the user modules would draw the required link
+ * pattern.
+ */
+static int dp_link_process_video_pattern_request(struct dp_link_private *link)
+{
+ if (!dp_link_is_video_pattern_requested(link))
+ goto end;
+
+ pr_debug("%s: bit depth=%d(%d bpp) pattern=%s\n",
+ dp_link_get_test_name(TEST_VIDEO_PATTERN),
+ link->request.test_bit_depth,
+ dp_link_bit_depth_to_bpp(link->request.test_bit_depth),
+ dp_link_video_pattern_to_string(
+ link->request.test_video_pattern));
+
+ return 0;
+end:
+ return -EINVAL;
+}
+
+/**
+ * dp_link_process_audio_pattern_request() - process new audio pattern request
+ * @link: Display Port link module data
+ *
+ * This function will handle a new audio pattern request that is initiated by
+ * the sink. This is acheieved by sending the necessary secondary data packets
+ * to the sink. It is expected that any simulatenous requests for video
+ * patterns will be handled before the audio pattern is sent to the sink.
+ */
+static int dp_link_process_audio_pattern_request(struct dp_link_private *link)
+{
+ if (!dp_link_is_audio_pattern_requested(link))
+ return -EINVAL;
+
+ pr_debug("sampling_rate=%s, channel_count=%d, pattern_type=%s\n",
+ dp_link_get_audio_sample_rate(
+ link->request.test_audio_sampling_rate),
+ link->request.test_audio_channel_count,
+ dp_link_get_audio_test_pattern(
+ link->request.test_audio_pattern_type));
+
+ pr_debug("audio_period: ch1=0x%x, ch2=0x%x, ch3=0x%x, ch4=0x%x\n",
+ link->request.test_audio_period_ch_1,
+ link->request.test_audio_period_ch_2,
+ link->request.test_audio_period_ch_3,
+ link->request.test_audio_period_ch_4);
+
+ pr_debug("audio_period: ch5=0x%x, ch6=0x%x, ch7=0x%x, ch8=0x%x\n",
+ link->request.test_audio_period_ch_5,
+ link->request.test_audio_period_ch_6,
+ link->request.test_audio_period_ch_7,
+ link->request.test_audio_period_ch_8);
+
+ return 0;
+}
+
+static void dp_link_reset_data(struct dp_link_private *link)
+{
+ link->request = (const struct dp_link_request){ 0 };
+ link->request.test_bit_depth = DP_TEST_BIT_DEPTH_UNKNOWN;
+
+ link->dp_link.test_requested = 0;
+}
+
+/**
+ * dp_link_process_request() - handle HPD IRQ transition to HIGH
+ * @link: pointer to link module data
+ *
+ * This function will handle the HPD IRQ state transitions from LOW to HIGH
+ * (including cases when there are back to back HPD IRQ HIGH) indicating
+ * the start of a new link training request or sink status update.
+ */
+static int dp_link_process_request(struct dp_link *dp_link)
+{
+ int ret = 0;
+ struct dp_link_private *link;
+
+ if (!dp_link) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ link = container_of(dp_link, struct dp_link_private, dp_link);
+
+ pr_debug("start\n");
+
+ dp_link_reset_data(link);
+
+ dp_link_parse_sink_status_field(link);
+
+ ret = dp_link_process_link_training_request(link);
+ if (!ret) {
+ dp_link->test_requested |= TEST_LINK_TRAINING;
+ goto exit;
+ }
+
+ ret = dp_link_process_phy_test_pattern_request(link);
+ if (!ret) {
+ dp_link->test_requested |= PHY_TEST_PATTERN;
+ goto exit;
+ }
+
+ ret = dp_link_process_link_status_update(link);
+ if (!ret) {
+ dp_link->test_requested |= LINK_STATUS_UPDATED;
+ goto exit;
+ }
+
+ ret = dp_link_process_ds_port_status_change(link);
+ if (!ret) {
+ dp_link->test_requested |= DS_PORT_STATUS_CHANGED;
+ goto exit;
+ }
+
+ ret = dp_link_process_video_pattern_request(link);
+ if (!ret) {
+ dp_link->test_requested |= TEST_VIDEO_PATTERN;
+ goto exit;
+ }
+
+ ret = dp_link_process_audio_pattern_request(link);
+ if (!ret) {
+ dp_link->test_requested |= TEST_AUDIO_PATTERN;
+ goto exit;
+ }
+
+ pr_debug("done\n");
+exit:
+ return ret;
+}
+
+static u8 *dp_link_get_voltage_swing(struct dp_link *dp_link)
+
+{
+ struct dp_link_private *link;
+
+ if (!dp_link) {
+ pr_err("invalid input\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ link = container_of(dp_link, struct dp_link_private, dp_link);
+
+ return link->link_status.req_voltage_swing;
+}
+
+static u8 *dp_link_get_pre_emphasis(struct dp_link *dp_link)
+
+{
+ struct dp_link_private *link;
+
+
+ if (!dp_link) {
+ pr_err("invalid input\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ link = container_of(dp_link, struct dp_link_private, dp_link);
+
+ return link->link_status.req_pre_emphasis;
+}
+
+static int dp_link_get_colorimetry_config(struct dp_link *dp_link)
+{
+ u32 cc;
+ enum dynamic_range dr;
+ struct dp_link_private *link;
+
+ if (!dp_link) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ link = container_of(dp_link, struct dp_link_private, dp_link);
+
+ /* unless a video pattern CTS test is ongoing, use CEA_VESA */
+ if (dp_link_is_video_pattern_requested(link))
+ dr = link->request.test_dyn_range;
+ else
+ dr = DP_DYNAMIC_RANGE_RGB_VESA;
+
+ /* Only RGB_VESA nd RGB_CEA supported for now */
+ switch (dr) {
+ case DP_DYNAMIC_RANGE_RGB_CEA:
+ cc = BIT(3);
+ break;
+ case DP_DYNAMIC_RANGE_RGB_VESA:
+ default:
+ cc = 0;
+ }
+
+ return cc;
+}
+
+static bool dp_link_clock_recovery(struct dp_link *dp_link)
+{
+ struct dp_link_private *link;
+
+ if (!dp_link) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ link = container_of(dp_link, struct dp_link_private, dp_link);
+
+ return dp_link_clock_recovery_done(link);
+}
+
+static bool dp_link_channel_equalization(struct dp_link *dp_link)
+{
+ struct dp_link_private *link;
+
+ if (!dp_link) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ link = container_of(dp_link, struct dp_link_private, dp_link);
+
+ return dp_link_channel_eq_done(link);
+}
+
+static int dp_link_adjust_levels(struct dp_link *dp_link)
+{
+ int i;
+ int max = 0;
+ struct dp_link_private *link;
+
+ if (!dp_link) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ link = container_of(dp_link, struct dp_link_private, dp_link);
+
+ /* use the max level across lanes */
+ for (i = 0; i < dp_link->lane_count; i++) {
+ pr_debug("lane=%d req_voltage_swing=%d\n",
+ i, link->link_status.req_voltage_swing[i]);
+ if (max < link->link_status.req_voltage_swing[i])
+ max = link->link_status.req_voltage_swing[i];
+ }
+
+ dp_link->v_level = max;
+
+ /* use the max level across lanes */
+ max = 0;
+ for (i = 0; i < dp_link->lane_count; i++) {
+ pr_debug("lane=%d req_pre_emphasis=%d\n",
+ i, link->link_status.req_pre_emphasis[i]);
+ if (max < link->link_status.req_pre_emphasis[i])
+ max = link->link_status.req_pre_emphasis[i];
+ }
+
+ dp_link->p_level = max;
+
+ /**
+ * Adjust the voltage swing and pre-emphasis level combination to within
+ * the allowable range.
+ */
+ if (dp_link->v_level > DP_LINK_VOLTAGE_MAX) {
+ pr_debug("Requested vSwingLevel=%d, change to %d\n",
+ dp_link->v_level, DP_LINK_VOLTAGE_MAX);
+ dp_link->v_level = DP_LINK_VOLTAGE_MAX;
+ }
+
+ if (dp_link->p_level > DP_LINK_PRE_EMPHASIS_MAX) {
+ pr_debug("Requested preEmphasisLevel=%d, change to %d\n",
+ dp_link->p_level, DP_LINK_PRE_EMPHASIS_MAX);
+ dp_link->p_level = DP_LINK_PRE_EMPHASIS_MAX;
+ }
+
+ if ((dp_link->p_level > DP_LINK_PRE_EMPHASIS_LEVEL_1)
+ && (dp_link->v_level == DP_LINK_VOLTAGE_LEVEL_2)) {
+ pr_debug("Requested preEmphasisLevel=%d, change to %d\n",
+ dp_link->p_level, DP_LINK_PRE_EMPHASIS_LEVEL_1);
+ dp_link->p_level = DP_LINK_PRE_EMPHASIS_LEVEL_1;
+ }
+
+ pr_debug("v_level=%d, p_level=%d\n",
+ dp_link->v_level, dp_link->p_level);
+
+ return 0;
+}
+
+static int dp_link_send_psm_request(struct dp_link *dp_link, bool req)
+{
+ struct dp_link_private *link;
+
+ if (!dp_link) {
+ pr_err("invalid input\n");
+ return -EINVAL;
+ }
+
+ link = container_of(dp_link, struct dp_link_private, dp_link);
+
+ return 0;
+}
+
+static u32 dp_link_get_test_bits_depth(struct dp_link *dp_link, u32 bpp)
+{
+ enum test_bit_depth tbd;
+
+ /*
+ * Few simplistic rules and assumptions made here:
+ * 1. Test bit depth is bit depth per color component
+ * 2. Assume 3 color components
+ */
+ switch (bpp) {
+ case 18:
+ tbd = DP_TEST_BIT_DEPTH_6;
+ break;
+ case 24:
+ tbd = DP_TEST_BIT_DEPTH_8;
+ break;
+ case 30:
+ tbd = DP_TEST_BIT_DEPTH_10;
+ break;
+ default:
+ tbd = DP_TEST_BIT_DEPTH_UNKNOWN;
+ break;
+ }
+
+ return tbd;
+}
+
+struct dp_link *dp_link_get(struct device *dev, struct dp_aux *aux)
+{
+ int rc = 0;
+ struct dp_link_private *link;
+ struct dp_link *dp_link;
+
+ if (!dev || !aux) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto error;
+ }
+
+ link = devm_kzalloc(dev, sizeof(*link), GFP_KERNEL);
+ if (!link) {
+ rc = -EINVAL;
+ goto error;
+ }
+
+ link->dev = dev;
+ link->aux = aux;
+
+ dp_link = &link->dp_link;
+
+ dp_link->process_request = dp_link_process_request;
+ dp_link->get_voltage_swing = dp_link_get_voltage_swing;
+ dp_link->get_test_bits_depth = dp_link_get_test_bits_depth;
+ dp_link->get_pre_emphasis = dp_link_get_pre_emphasis;
+ dp_link->get_colorimetry_config = dp_link_get_colorimetry_config;
+ dp_link->clock_recovery = dp_link_clock_recovery;
+ dp_link->channel_equalization = dp_link_channel_equalization;
+ dp_link->adjust_levels = dp_link_adjust_levels;
+ dp_link->send_psm_request = dp_link_send_psm_request;
+ dp_link->phy_pattern_requested = dp_link_phy_pattern_requested;
+
+ return dp_link;
+error:
+ return ERR_PTR(rc);
+}
+
+void dp_link_put(struct dp_link *dp_link)
+{
+ struct dp_link_private *link;
+
+ if (!dp_link)
+ return;
+
+ link = container_of(dp_link, struct dp_link_private, dp_link);
+
+ devm_kfree(link->dev, link);
+}
diff --git a/drivers/gpu/drm/msm/dp/dp_link.h b/drivers/gpu/drm/msm/dp/dp_link.h
new file mode 100644
index 0000000..de10e9a
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_link.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2012-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 _DP_LINK_H_
+#define _DP_LINK_H_
+
+#include "dp_aux.h"
+
+enum dp_link_voltage_level {
+ DP_LINK_VOLTAGE_LEVEL_0 = 0,
+ DP_LINK_VOLTAGE_LEVEL_1 = 1,
+ DP_LINK_VOLTAGE_LEVEL_2 = 2,
+ DP_LINK_VOLTAGE_MAX = DP_LINK_VOLTAGE_LEVEL_2,
+};
+
+enum dp_link_preemaphasis_level {
+ DP_LINK_PRE_EMPHASIS_LEVEL_0 = 0,
+ DP_LINK_PRE_EMPHASIS_LEVEL_1 = 1,
+ DP_LINK_PRE_EMPHASIS_LEVEL_2 = 2,
+ DP_LINK_PRE_EMPHASIS_MAX = DP_LINK_PRE_EMPHASIS_LEVEL_2,
+};
+
+enum test_type {
+ UNKNOWN_TEST = 0,
+ TEST_LINK_TRAINING = 0x01,
+ TEST_VIDEO_PATTERN = 0x02,
+ PHY_TEST_PATTERN = 0x08,
+ TEST_EDID_READ = 0x04,
+ TEST_AUDIO_PATTERN = 0x20,
+ TEST_AUDIO_DISABLED_VIDEO = 0x40,
+};
+
+enum status_update {
+ LINK_STATUS_UPDATED = 0x100,
+ DS_PORT_STATUS_CHANGED = 0x200,
+};
+
+struct dp_link {
+ u32 test_requested;
+
+ u32 lane_count;
+ u32 link_rate;
+ u32 v_level;
+ u32 p_level;
+
+ u8 *(*get_voltage_swing)(struct dp_link *dp_link);
+ u8 *(*get_pre_emphasis)(struct dp_link *dp_link);
+ u32 (*get_test_bits_depth)(struct dp_link *dp_link, u32 bpp);
+ int (*process_request)(struct dp_link *dp_link);
+ int (*get_colorimetry_config)(struct dp_link *dp_link);
+ int (*adjust_levels)(struct dp_link *dp_link);
+ int (*send_psm_request)(struct dp_link *dp_link, bool req);
+ bool (*clock_recovery)(struct dp_link *dp_link);
+ bool (*channel_equalization)(struct dp_link *dp_link);
+ bool (*phy_pattern_requested)(struct dp_link *dp_link);
+};
+
+/**
+ * dp_link_get() - get the functionalities of dp test module
+ *
+ *
+ * return: a pointer to dp_link struct
+ */
+struct dp_link *dp_link_get(struct device *dev, struct dp_aux *aux);
+
+/**
+ * dp_link_put() - releases the dp test module's resources
+ *
+ * @dp_link: an instance of dp_link module
+ *
+ */
+void dp_link_put(struct dp_link *dp_link);
+
+#endif /* _DP_LINK_H_ */
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.c b/drivers/gpu/drm/msm/dp/dp_panel.c
new file mode 100644
index 0000000..f9616c4
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_panel.c
@@ -0,0 +1,468 @@
+/*
+ * Copyright (c) 2012-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.
+ *
+ */
+
+#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__
+
+#include "dp_panel.h"
+
+#define DP_LINK_RATE_MULTIPLIER 27000000
+
+struct dp_panel_private {
+ struct device *dev;
+ struct dp_panel dp_panel;
+ struct dp_aux *aux;
+ struct dp_catalog_panel *catalog;
+};
+
+static int dp_panel_read_dpcd(struct dp_panel *dp_panel)
+{
+ u8 *bp;
+ u8 data;
+ u32 const addr = 0x0;
+ u32 const len = 16;
+ int rlen, rc = 0;
+ struct dp_panel_private *panel;
+ struct dp_panel_dpcd *cap;
+
+ if (!dp_panel) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto end;
+ }
+
+ cap = &dp_panel->dpcd;
+ panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+
+ rlen = panel->aux->read(panel->aux, addr, len, AUX_NATIVE, &bp);
+ if (rlen != len) {
+ pr_err("dpcd read failed, rlen=%d\n", rlen);
+ rc = -EINVAL;
+ goto end;
+ }
+
+ memset(cap, 0, sizeof(*cap));
+
+ data = *bp++; /* byte 0 */
+ cap->major = (data >> 4) & 0x0f;
+ cap->minor = data & 0x0f;
+ pr_debug("version: %d.%d\n", cap->major, cap->minor);
+
+ data = *bp++; /* byte 1 */
+ /* 162, 270, 540, 810 MB, symbol rate, NOT bit rate */
+ cap->max_link_rate = data;
+ pr_debug("link_rate=%d\n", cap->max_link_rate);
+
+ data = *bp++; /* byte 2 */
+ if (data & BIT(7))
+ cap->enhanced_frame++;
+
+ if (data & 0x40) {
+ cap->flags |= DPCD_TPS3;
+ pr_debug("pattern 3 supported\n");
+ } else {
+ pr_debug("pattern 3 not supported\n");
+ }
+
+ data &= 0x0f;
+ cap->max_lane_count = data;
+ pr_debug("lane_count=%d\n", cap->max_lane_count);
+
+ data = *bp++; /* byte 3 */
+ if (data & BIT(0)) {
+ cap->flags |= DPCD_MAX_DOWNSPREAD_0_5;
+ pr_debug("max_downspread\n");
+ }
+
+ if (data & BIT(6)) {
+ cap->flags |= DPCD_NO_AUX_HANDSHAKE;
+ pr_debug("NO Link Training\n");
+ }
+
+ data = *bp++; /* byte 4 */
+ cap->num_rx_port = (data & BIT(0)) + 1;
+ pr_debug("rx_ports=%d", cap->num_rx_port);
+
+ data = *bp++; /* Byte 5: DOWN_STREAM_PORT_PRESENT */
+ cap->downstream_port.dfp_present = data & BIT(0);
+ cap->downstream_port.dfp_type = data & 0x6;
+ cap->downstream_port.format_conversion = data & BIT(3);
+ cap->downstream_port.detailed_cap_info_available = data & BIT(4);
+ pr_debug("dfp_present = %d, dfp_type = %d\n",
+ cap->downstream_port.dfp_present,
+ cap->downstream_port.dfp_type);
+ pr_debug("format_conversion = %d, detailed_cap_info_available = %d\n",
+ cap->downstream_port.format_conversion,
+ cap->downstream_port.detailed_cap_info_available);
+
+ bp += 1; /* Skip Byte 6 */
+ rlen -= 1;
+
+ data = *bp++; /* Byte 7: DOWN_STREAM_PORT_COUNT */
+ cap->downstream_port.dfp_count = data & 0x7;
+ cap->downstream_port.msa_timing_par_ignored = data & BIT(6);
+ cap->downstream_port.oui_support = data & BIT(7);
+ pr_debug("dfp_count = %d, msa_timing_par_ignored = %d\n",
+ cap->downstream_port.dfp_count,
+ cap->downstream_port.msa_timing_par_ignored);
+ pr_debug("oui_support = %d\n", cap->downstream_port.oui_support);
+
+ data = *bp++; /* byte 8 */
+ if (data & BIT(1)) {
+ cap->flags |= DPCD_PORT_0_EDID_PRESENTED;
+ pr_debug("edid presented\n");
+ }
+
+ data = *bp++; /* byte 9 */
+ cap->rx_port0_buf_size = (data + 1) * 32;
+ pr_debug("lane_buf_size=%d\n", cap->rx_port0_buf_size);
+
+ bp += 2; /* skip 10, 11 port1 capability */
+ rlen -= 2;
+
+ data = *bp++; /* byte 12 */
+ cap->i2c_speed_ctrl = data;
+ if (cap->i2c_speed_ctrl > 0)
+ pr_debug("i2c_rate=%d", cap->i2c_speed_ctrl);
+
+ data = *bp++; /* byte 13 */
+ cap->scrambler_reset = data & BIT(0);
+ pr_debug("scrambler_reset=%d\n", cap->scrambler_reset);
+
+ if (data & BIT(1))
+ cap->enhanced_frame++;
+
+ pr_debug("enhanced_framing=%d\n", cap->enhanced_frame);
+
+ data = *bp++; /* byte 14 */
+ if (data == 0)
+ cap->training_read_interval = 4000; /* us */
+ else
+ cap->training_read_interval = 4000 * data; /* us */
+ pr_debug("training_interval=%d\n", cap->training_read_interval);
+end:
+ return rc;
+}
+
+/*
+ * edid standard header bytes
+ */
+static u8 edid_hdr[8] = {0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00};
+
+static bool dp_panel_is_edid_header_valid(u8 *buf)
+{
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(edid_hdr); i++) {
+ if (buf[i] != edid_hdr[i])
+ return false;
+ }
+
+ return true;
+}
+
+static int dp_panel_validate_edid(u8 *bp, int len)
+{
+ int i;
+ u8 csum = 0;
+ u32 const size = 128;
+
+ if (len < size) {
+ pr_err("Error: len=%x\n", len);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < size; i++)
+ csum += *bp++;
+
+ if (csum != 0) {
+ pr_err("error: csum=0x%x\n", csum);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int dp_panel_read_edid(struct dp_panel *dp_panel)
+{
+ u8 *edid_buf;
+ u32 checksum = 0;
+ int rlen, ret = 0;
+ int edid_blk = 0, blk_num = 0, retries = 10;
+ u32 const segment_addr = 0x30;
+ bool edid_parsing_done = false;
+ struct dp_panel_private *panel;
+
+ panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+
+ ret = panel->aux->ready(panel->aux);
+ if (!ret) {
+ pr_err("aux chan NOT ready\n");
+ goto end;
+ }
+
+ do {
+ u8 segment;
+
+
+ /*
+ * Write the segment first.
+ * Segment = 0, for blocks 0 and 1
+ * Segment = 1, for blocks 2 and 3
+ * Segment = 2, for blocks 3 and 4
+ * and so on ...
+ */
+ segment = blk_num >> 1;
+
+ panel->aux->write(panel->aux, segment_addr, 1, AUX_I2C,
+ &segment);
+
+ rlen = panel->aux->read(panel->aux, EDID_START_ADDRESS +
+ (blk_num * EDID_BLOCK_SIZE),
+ EDID_BLOCK_SIZE, AUX_I2C, &edid_buf);
+ if (rlen != EDID_BLOCK_SIZE) {
+ pr_err("invalid edid len: %d\n", rlen);
+ continue;
+ }
+
+ pr_debug("=== EDID data ===\n");
+ print_hex_dump(KERN_DEBUG, "EDID: ", DUMP_PREFIX_NONE, 16, 1,
+ edid_buf, EDID_BLOCK_SIZE, false);
+
+ pr_debug("blk_num=%d, rlen=%d\n", blk_num, rlen);
+
+ if (dp_panel_is_edid_header_valid(edid_buf)) {
+ ret = dp_panel_validate_edid(edid_buf, rlen);
+ if (ret) {
+ pr_err("corrupt edid block detected\n");
+ goto end;
+ }
+
+ if (edid_parsing_done) {
+ blk_num++;
+ continue;
+ }
+
+ dp_panel->edid.ext_block_cnt = edid_buf[0x7E];
+ edid_parsing_done = true;
+ checksum = edid_buf[rlen - 1];
+ } else {
+ edid_blk++;
+ blk_num++;
+ }
+
+ memcpy(dp_panel->edid.buf + (edid_blk * EDID_BLOCK_SIZE),
+ edid_buf, EDID_BLOCK_SIZE);
+
+ if (edid_blk == dp_panel->edid.ext_block_cnt)
+ goto end;
+ } while (retries--);
+end:
+ return ret;
+}
+
+static int dp_panel_timing_cfg(struct dp_panel *dp_panel)
+{
+ int rc = 0;
+ u32 data, total_ver, total_hor;
+ struct dp_catalog_panel *catalog;
+ struct dp_panel_private *panel;
+ struct dp_panel_info *pinfo;
+
+ if (!dp_panel) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto end;
+ }
+
+ panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+ catalog = panel->catalog;
+ pinfo = &panel->dp_panel.pinfo;
+
+ pr_debug("width=%d hporch= %d %d %d\n",
+ pinfo->h_active, pinfo->h_back_porch,
+ pinfo->h_front_porch, pinfo->h_sync_width);
+
+ pr_debug("height=%d vporch= %d %d %d\n",
+ pinfo->v_active, pinfo->v_back_porch,
+ pinfo->v_front_porch, pinfo->v_sync_width);
+
+ total_hor = pinfo->h_active + pinfo->h_back_porch +
+ pinfo->h_front_porch + pinfo->h_sync_width;
+
+ total_ver = pinfo->v_active + pinfo->v_back_porch +
+ pinfo->v_front_porch + pinfo->v_sync_width;
+
+ data = total_ver;
+ data <<= 16;
+ data |= total_hor;
+
+ catalog->total = data;
+
+ data = (pinfo->v_back_porch + pinfo->v_sync_width);
+ data <<= 16;
+ data |= (pinfo->h_back_porch + pinfo->h_sync_width);
+
+ catalog->sync_start = data;
+
+ data = pinfo->v_sync_width;
+ data <<= 16;
+ data |= (pinfo->v_active_low << 31);
+ data |= pinfo->h_sync_width;
+ data |= (pinfo->h_active_low << 15);
+
+ catalog->width_blanking = data;
+
+ data = pinfo->v_active;
+ data <<= 16;
+ data |= pinfo->h_active;
+
+ catalog->dp_active = data;
+
+ panel->catalog->timing_cfg(catalog);
+end:
+ return rc;
+}
+
+static int dp_panel_init_panel_info(struct dp_panel *dp_panel)
+{
+ int rc = 0;
+ struct dp_panel_private *panel;
+
+ if (!dp_panel) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto end;
+ }
+
+ panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+end:
+ return rc;
+}
+
+static u8 dp_panel_get_link_rate(struct dp_panel *dp_panel)
+{
+ const u32 encoding_factx10 = 8;
+ const u32 ln_to_link_ratio = 10;
+ u32 min_link_rate, reminder = 0;
+ u8 calc_link_rate = 0, lane_cnt;
+ struct dp_panel_private *panel;
+ struct dp_panel_info *pinfo;
+
+ if (!dp_panel) {
+ pr_err("invalid input\n");
+ goto end;
+ }
+
+ panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+
+ lane_cnt = dp_panel->dpcd.max_lane_count;
+ pinfo = &dp_panel->pinfo;
+
+ pinfo->bpp = 24;
+
+ /*
+ * The max pixel clock supported is 675Mhz. The
+ * current calculations below will make sure
+ * the min_link_rate is within 32 bit limits.
+ * Any changes in the section of code should
+ * consider this limitation.
+ */
+ min_link_rate = (u32)div_u64(pinfo->pixel_clk_khz * 1000,
+ (lane_cnt * encoding_factx10));
+ min_link_rate /= ln_to_link_ratio;
+ min_link_rate = (min_link_rate * pinfo->bpp);
+ min_link_rate = (u32)div_u64_rem(min_link_rate * 10,
+ DP_LINK_RATE_MULTIPLIER, &reminder);
+
+ /*
+ * To avoid any fractional values,
+ * increment the min_link_rate
+ */
+ if (reminder)
+ min_link_rate += 1;
+ pr_debug("min_link_rate = %d\n", min_link_rate);
+
+ if (min_link_rate <= DP_LINK_RATE_162)
+ calc_link_rate = DP_LINK_RATE_162;
+ else if (min_link_rate <= DP_LINK_RATE_270)
+ calc_link_rate = DP_LINK_RATE_270;
+ else if (min_link_rate <= DP_LINK_RATE_540)
+ calc_link_rate = DP_LINK_RATE_540;
+ else if (min_link_rate <= DP_LINK_RATE_810)
+ calc_link_rate = DP_LINK_RATE_810;
+ else {
+ /* Cap the link rate to the max supported rate */
+ pr_debug("link_rate = %d is unsupported\n", min_link_rate);
+ calc_link_rate = DP_LINK_RATE_810;
+ }
+
+ if (calc_link_rate > dp_panel->dpcd.max_link_rate)
+ calc_link_rate = dp_panel->dpcd.max_link_rate;
+
+ pr_debug("calc_link_rate = 0x%x\n", calc_link_rate);
+end:
+ return calc_link_rate;
+}
+
+struct dp_panel *dp_panel_get(struct device *dev, struct dp_aux *aux,
+ struct dp_catalog_panel *catalog)
+{
+ int rc = 0;
+ struct dp_panel_private *panel;
+ struct dp_panel *dp_panel;
+
+ if (!dev || !aux || !catalog) {
+ pr_err("invalid input\n");
+ rc = -EINVAL;
+ goto error;
+ }
+
+ panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
+ if (!panel) {
+ rc = -ENOMEM;
+ goto error;
+ }
+
+ panel->dev = dev;
+ panel->aux = aux;
+ panel->catalog = catalog;
+
+ dp_panel = &panel->dp_panel;
+
+ dp_panel->edid.buf = devm_kzalloc(dev,
+ sizeof(EDID_BLOCK_SIZE) * 4, GFP_KERNEL);
+
+ dp_panel->init_info = dp_panel_init_panel_info;
+ dp_panel->timing_cfg = dp_panel_timing_cfg;
+ dp_panel->read_edid = dp_panel_read_edid;
+ dp_panel->read_dpcd = dp_panel_read_dpcd;
+ dp_panel->get_link_rate = dp_panel_get_link_rate;
+
+ return dp_panel;
+error:
+ return ERR_PTR(rc);
+}
+
+void dp_panel_put(struct dp_panel *dp_panel)
+{
+ struct dp_panel_private *panel;
+
+ if (!dp_panel)
+ return;
+
+ panel = container_of(dp_panel, struct dp_panel_private, dp_panel);
+
+ devm_kfree(panel->dev, dp_panel->edid.buf);
+ devm_kfree(panel->dev, panel);
+}
diff --git a/drivers/gpu/drm/msm/dp/dp_panel.h b/drivers/gpu/drm/msm/dp/dp_panel.h
new file mode 100644
index 0000000..5c145eb
--- /dev/null
+++ b/drivers/gpu/drm/msm/dp/dp_panel.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (c) 2012-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 _DP_PANEL_H_
+#define _DP_PANEL_H_
+
+#include "dp_aux.h"
+
+#define DPCD_ENHANCED_FRAME BIT(0)
+#define DPCD_TPS3 BIT(1)
+#define DPCD_MAX_DOWNSPREAD_0_5 BIT(2)
+#define DPCD_NO_AUX_HANDSHAKE BIT(3)
+#define DPCD_PORT_0_EDID_PRESENTED BIT(4)
+
+#define EDID_START_ADDRESS 0x50
+#define EDID_BLOCK_SIZE 0x80
+
+
+#define DP_LINK_RATE_162 6 /* 1.62G = 270M * 6 */
+#define DP_LINK_RATE_270 10 /* 2.70G = 270M * 10 */
+#define DP_LINK_RATE_540 20 /* 5.40G = 270M * 20 */
+#define DP_LINK_RATE_810 30 /* 8.10G = 270M * 30 */
+#define DP_LINK_RATE_MAX DP_LINK_RATE_810
+
+struct downstream_port_config {
+ /* Byte 02205h */
+ bool dfp_present;
+ u32 dfp_type;
+ bool format_conversion;
+ bool detailed_cap_info_available;
+ /* Byte 02207h */
+ u32 dfp_count;
+ bool msa_timing_par_ignored;
+ bool oui_support;
+};
+
+struct dp_panel_dpcd {
+ u8 major;
+ u8 minor;
+ u8 max_lane_count;
+ u8 num_rx_port;
+ u8 i2c_speed_ctrl;
+ u8 scrambler_reset;
+ u8 enhanced_frame;
+ u32 max_link_rate; /* 162, 270 and 540 Mb, divided by 10 */
+ u32 flags;
+ u32 rx_port0_buf_size;
+ u32 training_read_interval;/* us */
+ struct downstream_port_config downstream_port;
+};
+
+struct dp_panel_edid {
+ u8 *buf;
+ u8 id_name[4];
+ u8 id_product;
+ u8 version;
+ u8 revision;
+ u8 video_intf; /* dp == 0x5 */
+ u8 color_depth; /* 6, 8, 10, 12 and 14 bits */
+ u8 color_format; /* RGB 4:4:4, YCrCb 4:4:4, Ycrcb 4:2:2 */
+ u8 dpm; /* display power management */
+ u8 sync_digital; /* 1 = digital */
+ u8 sync_separate; /* 1 = separate */
+ u8 vsync_pol; /* 0 = negative, 1 = positive */
+ u8 hsync_pol; /* 0 = negative, 1 = positive */
+ u8 ext_block_cnt;
+};
+
+struct dp_panel_info {
+ u32 h_active;
+ u32 v_active;
+ u32 h_back_porch;
+ u32 h_front_porch;
+ u32 h_sync_width;
+ u32 h_active_low;
+ u32 v_back_porch;
+ u32 v_front_porch;
+ u32 v_sync_width;
+ u32 v_active_low;
+ u32 h_skew;
+ u32 refresh_rate;
+ u32 pixel_clk_khz;
+ u32 bpp;
+};
+
+struct dp_panel {
+ struct dp_panel_dpcd dpcd;
+ struct dp_panel_edid edid;
+ struct dp_panel_info pinfo;
+
+ u32 vic;
+
+ int (*init_info)(struct dp_panel *dp_panel);
+ int (*timing_cfg)(struct dp_panel *dp_panel);
+ int (*read_edid)(struct dp_panel *dp_panel);
+ int (*read_dpcd)(struct dp_panel *dp_panel);
+ u8 (*get_link_rate)(struct dp_panel *dp_panel);
+};
+
+struct dp_panel *dp_panel_get(struct device *dev, struct dp_aux *aux,
+ struct dp_catalog_panel *catalog);
+void dp_panel_put(struct dp_panel *dp_panel);
+#endif /* _DP_PANEL_H_ */
diff --git a/drivers/gpu/drm/msm/sde/sde_core_irq.c b/drivers/gpu/drm/msm/sde/sde_core_irq.c
index 5adef2d..1b40161 100644
--- a/drivers/gpu/drm/msm/sde/sde_core_irq.c
+++ b/drivers/gpu/drm/msm/sde/sde_core_irq.c
@@ -428,6 +428,103 @@ void sde_core_irq_uninstall(struct sde_kms *sde_kms)
sde_kms->irq_obj.total_irqs = 0;
}
+static void sde_core_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 != NULL);
+ return;
+ }
+ sde_kms = irq_data_get_irq_chip_data(irqd);
+
+ /* memory barrier */
+ smp_mb__before_atomic();
+ clear_bit(irqd->hwirq, &sde_kms->irq_controller.enabled_mask);
+ /* memory barrier */
+ smp_mb__after_atomic();
+}
+
+static void sde_core_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 != NULL);
+ return;
+ }
+ sde_kms = irq_data_get_irq_chip_data(irqd);
+
+ /* memory barrier */
+ smp_mb__before_atomic();
+ set_bit(irqd->hwirq, &sde_kms->irq_controller.enabled_mask);
+ /* memory barrier */
+ smp_mb__after_atomic();
+}
+
+static struct irq_chip sde_core_irq_chip = {
+ .name = "sde",
+ .irq_mask = sde_core_irq_mask,
+ .irq_unmask = sde_core_irq_unmask,
+};
+
+static int sde_core_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 != NULL);
+ return -EINVAL;
+ }
+ sde_kms = domain->host_data;
+
+ irq_set_chip_and_handler(irq, &sde_core_irq_chip, handle_level_irq);
+ rc = irq_set_chip_data(irq, sde_kms);
+
+ return rc;
+}
+
+static const struct irq_domain_ops sde_core_irqdomain_ops = {
+ .map = sde_core_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_core_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 64f4160..c775f8c 100644
--- a/drivers/gpu/drm/msm/sde/sde_core_irq.h
+++ b/drivers/gpu/drm/msm/sde/sde_core_irq.h
@@ -38,6 +38,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
* @return: interrupt handling status
diff --git a/drivers/gpu/drm/msm/sde/sde_core_perf.c b/drivers/gpu/drm/msm/sde/sde_core_perf.c
index 7671649..448a1e7 100644
--- a/drivers/gpu/drm/msm/sde/sde_core_perf.c
+++ b/drivers/gpu/drm/msm/sde/sde_core_perf.c
@@ -42,6 +42,23 @@ enum sde_perf_mode {
SDE_PERF_MODE_MAX
};
+/**
+ * enum sde_perf_vote_mode: perf vote mode.
+ * @APPS_RSC_MODE: It combines the vote for all displays and votes it
+ * through APPS rsc. This is default mode when display
+ * rsc is not available.
+ * @DISP_RSC_MODE: It combines the vote for all displays and votes it
+ * through display rsc. This is default configuration
+ * when display rsc is available.
+ * @DISP_RSC_PRIMARY_MODE: The primary display votes through display rsc
+ * while all other displays votes through apps rsc.
+ */
+enum sde_perf_vote_mode {
+ APPS_RSC_MODE,
+ DISP_RSC_MODE,
+ DISP_RSC_PRIMARY_MODE,
+};
+
static struct sde_kms *_sde_crtc_get_kms(struct drm_crtc *crtc)
{
struct msm_drm_private *priv;
@@ -169,7 +186,9 @@ int sde_core_perf_crtc_check(struct drm_crtc *crtc,
SDE_DEBUG("final threshold bw limit = %d\n", threshold);
- if (!threshold) {
+ if (!sde_cstate->bw_control) {
+ SDE_DEBUG("bypass bandwidth check\n");
+ } else if (!threshold) {
sde_cstate->new_perf = sde_cstate->cur_perf;
SDE_ERROR("no bandwidth limits specified\n");
return -E2BIG;
@@ -182,12 +201,38 @@ int sde_core_perf_crtc_check(struct drm_crtc *crtc,
return 0;
}
+static inline bool _is_crtc_client_type_matches(struct drm_crtc *tmp_crtc,
+ enum sde_crtc_client_type curr_client_type,
+ struct sde_core_perf *perf)
+{
+ if (!tmp_crtc)
+ return false;
+ else if (perf->bw_vote_mode == DISP_RSC_PRIMARY_MODE &&
+ perf->sde_rsc_available)
+ return curr_client_type == sde_crtc_get_client_type(tmp_crtc);
+ else
+ return true;
+}
+
+static inline enum sde_crtc_client_type _get_sde_client_type(
+ enum sde_crtc_client_type curr_client_type,
+ struct sde_core_perf *perf)
+{
+ if (perf->bw_vote_mode == DISP_RSC_PRIMARY_MODE &&
+ perf->sde_rsc_available)
+ return curr_client_type;
+ else if (perf->bw_vote_mode != APPS_RSC_MODE && perf->sde_rsc_available)
+ return RT_RSC_CLIENT;
+ else
+ return RT_CLIENT;
+}
+
static void _sde_core_perf_crtc_update_bus(struct sde_kms *kms,
struct drm_crtc *crtc)
{
u64 bw_sum_of_intfs = 0, bus_ab_quota, bus_ib_quota;
struct sde_core_perf_params perf = {0};
- enum sde_crtc_client_type curr_client_type
+ enum sde_crtc_client_type client_vote, curr_client_type
= sde_crtc_get_client_type(crtc);
struct drm_crtc *tmp_crtc;
struct sde_crtc_state *sde_cstate;
@@ -195,7 +240,8 @@ static void _sde_core_perf_crtc_update_bus(struct sde_kms *kms,
drm_for_each_crtc(tmp_crtc, crtc->dev) {
if (_sde_core_perf_crtc_is_power_on(tmp_crtc) &&
- (curr_client_type == sde_crtc_get_client_type(tmp_crtc))) {
+ _is_crtc_client_type_matches(tmp_crtc, curr_client_type,
+ &kms->perf)) {
sde_cstate = to_sde_crtc_state(tmp_crtc->state);
perf.max_per_pipe_ib = max(perf.max_per_pipe_ib,
@@ -217,7 +263,8 @@ static void _sde_core_perf_crtc_update_bus(struct sde_kms *kms,
bus_ib_quota = kms->perf.fix_core_ib_vote;
}
- switch (curr_client_type) {
+ client_vote = _get_sde_client_type(curr_client_type, &kms->perf);
+ switch (client_vote) {
case NRT_CLIENT:
sde_power_data_bus_set_quota(&priv->phandle, kms->core_client,
SDE_POWER_HANDLE_DATA_BUS_CLIENT_NRT,
@@ -246,6 +293,32 @@ static void _sde_core_perf_crtc_update_bus(struct sde_kms *kms,
SDE_ERROR("invalid client type:%d\n", curr_client_type);
break;
}
+
+ if (kms->perf.bw_vote_mode_updated) {
+ switch (kms->perf.bw_vote_mode) {
+ case DISP_RSC_MODE:
+ sde_power_data_bus_set_quota(&priv->phandle,
+ kms->core_client,
+ SDE_POWER_HANDLE_DATA_BUS_CLIENT_NRT, 0, 0);
+ sde_power_data_bus_set_quota(&priv->phandle,
+ kms->core_client,
+ SDE_POWER_HANDLE_DATA_BUS_CLIENT_RT, 0, 0);
+ kms->perf.bw_vote_mode_updated = false;
+ break;
+
+ case APPS_RSC_MODE:
+ sde_cstate = to_sde_crtc_state(crtc->state);
+ if (sde_cstate->rsc_client) {
+ sde_rsc_client_vote(sde_cstate->rsc_client,
+ 0, 0);
+ kms->perf.bw_vote_mode_updated = false;
+ }
+ break;
+
+ default:
+ break;
+ }
+ }
}
/**
@@ -349,6 +422,10 @@ void sde_core_perf_crtc_update(struct drm_crtc *crtc,
}
priv = kms->dev->dev_private;
+ /* wake vote update is not required with display rsc */
+ if (kms->perf.bw_vote_mode == DISP_RSC_MODE && stop_req)
+ return;
+
sde_crtc = to_sde_crtc(crtc);
sde_cstate = to_sde_crtc_state(crtc->state);
@@ -376,6 +453,21 @@ void sde_core_perf_crtc_update(struct drm_crtc *crtc,
update_bus = 1;
}
+ /* display rsc override during solver mode */
+ if (kms->perf.bw_vote_mode == DISP_RSC_MODE &&
+ get_sde_rsc_current_state(SDE_RSC_INDEX) ==
+ SDE_RSC_CMD_STATE) {
+ /* update new bandwdith in all cases */
+ if (params_changed && new->bw_ctl != old->bw_ctl) {
+ old->bw_ctl = new->bw_ctl;
+ old->max_per_pipe_ib = new->max_per_pipe_ib;
+ update_bus = 1;
+ /* reduce bw vote is not required in solver mode */
+ } else if (!params_changed) {
+ update_bus = 0;
+ }
+ }
+
if ((params_changed &&
(new->core_clk_rate > old->core_clk_rate)) ||
(!params_changed &&
@@ -535,6 +627,10 @@ int sde_core_perf_debugfs_init(struct sde_core_perf *perf,
(u32 *)&catalog->perf.max_bw_high);
debugfs_create_file("perf_mode", 0644, perf->debugfs_root,
(u32 *)perf, &sde_core_perf_mode_fops);
+ debugfs_create_u32("bw_vote_mode", 0600, perf->debugfs_root,
+ &perf->bw_vote_mode);
+ debugfs_create_bool("bw_vote_mode_updated", 0600, perf->debugfs_root,
+ &perf->bw_vote_mode_updated);
debugfs_create_u64("fix_core_clk_rate", 0644, perf->debugfs_root,
&perf->fix_core_clk_rate);
debugfs_create_u64("fix_core_ib_vote", 0644, perf->debugfs_root,
@@ -566,7 +662,6 @@ void sde_core_perf_destroy(struct sde_core_perf *perf)
sde_core_perf_debugfs_destroy(perf);
perf->max_core_clk_rate = 0;
perf->core_clk = NULL;
- mutex_destroy(&perf->perf_lock);
perf->clk_name = NULL;
perf->phandle = NULL;
perf->catalog = NULL;
@@ -590,7 +685,12 @@ int sde_core_perf_init(struct sde_core_perf *perf,
perf->phandle = phandle;
perf->pclient = pclient;
perf->clk_name = clk_name;
- mutex_init(&perf->perf_lock);
+ perf->sde_rsc_available = is_sde_rsc_available(SDE_RSC_INDEX);
+ /* set default mode */
+ if (perf->sde_rsc_available)
+ perf->bw_vote_mode = DISP_RSC_MODE;
+ else
+ perf->bw_vote_mode = APPS_RSC_MODE;
perf->core_clk = sde_power_clk_get_clk(phandle, clk_name);
if (!perf->core_clk) {
diff --git a/drivers/gpu/drm/msm/sde/sde_core_perf.h b/drivers/gpu/drm/msm/sde/sde_core_perf.h
index 31851be..4a1bdad 100644
--- a/drivers/gpu/drm/msm/sde/sde_core_perf.h
+++ b/drivers/gpu/drm/msm/sde/sde_core_perf.h
@@ -22,8 +22,6 @@
#include "sde_power_handle.h"
#define SDE_PERF_DEFAULT_MAX_CORE_CLK_RATE 320000000
-#define SDE_PERF_DEFAULT_MAX_BUS_AB_QUOTA 2000000000
-#define SDE_PERF_DEFAULT_MAX_BUS_IB_QUOTA 2000000000
/**
* struct sde_core_perf_params - definition of performance parameters
@@ -53,7 +51,6 @@ struct sde_core_perf_tune {
* struct sde_core_perf - definition of core performance context
* @dev: Pointer to drm device
* @debugfs_root: top level debug folder
- * @perf_lock: serialization lock for this context
* @catalog: Pointer to catalog configuration
* @phandle: Pointer to power handler
* @pclient: Pointer to power client
@@ -66,11 +63,13 @@ struct sde_core_perf_tune {
* @fix_core_clk_rate: fixed core clock request in Hz used in mode 2
* @fix_core_ib_vote: fixed core ib vote in bps used in mode 2
* @fix_core_ab_vote: fixed core ab vote in bps used in mode 2
+ * @bw_vote_mode: apps rsc vs display rsc bandwidth vote mode
+ * @sde_rsc_available: is display rsc available
+ * @bw_vote_mode_updated: bandwidth vote mode update
*/
struct sde_core_perf {
struct drm_device *dev;
struct dentry *debugfs_root;
- struct mutex perf_lock;
struct sde_mdss_cfg *catalog;
struct sde_power_handle *phandle;
struct sde_power_client *pclient;
@@ -83,6 +82,9 @@ struct sde_core_perf {
u64 fix_core_clk_rate;
u64 fix_core_ib_vote;
u64 fix_core_ab_vote;
+ u32 bw_vote_mode;
+ bool sde_rsc_available;
+ bool bw_vote_mode_updated;
};
/**
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.c b/drivers/gpu/drm/msm/sde/sde_crtc.c
index d0ade33..7d0fad0 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.c
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.c
@@ -2912,19 +2912,19 @@ static void sde_crtc_install_properties(struct drm_crtc *crtc,
CRTC_PROP_CORE_CLK);
msm_property_install_range(&sde_crtc->property_info,
"core_ab", 0x0, 0, U64_MAX,
- SDE_PERF_DEFAULT_MAX_BUS_AB_QUOTA,
+ catalog->perf.max_bw_high * 1000ULL,
CRTC_PROP_CORE_AB);
msm_property_install_range(&sde_crtc->property_info,
"core_ib", 0x0, 0, U64_MAX,
- SDE_PERF_DEFAULT_MAX_BUS_IB_QUOTA,
+ catalog->perf.max_bw_high * 1000ULL,
CRTC_PROP_CORE_IB);
msm_property_install_range(&sde_crtc->property_info,
"mem_ab", 0x0, 0, U64_MAX,
- SDE_PERF_DEFAULT_MAX_BUS_AB_QUOTA,
+ catalog->perf.max_bw_high * 1000ULL,
CRTC_PROP_MEM_AB);
msm_property_install_range(&sde_crtc->property_info,
"mem_ib", 0x0, 0, U64_MAX,
- SDE_PERF_DEFAULT_MAX_BUS_IB_QUOTA,
+ catalog->perf.max_bw_high * 1000ULL,
CRTC_PROP_MEM_IB);
msm_property_install_range(&sde_crtc->property_info,
"rot_prefill_bw", 0, 0, U64_MAX,
@@ -3050,6 +3050,12 @@ static int sde_crtc_atomic_set_property(struct drm_crtc *crtc,
case CRTC_PROP_ROI_V1:
ret = _sde_crtc_set_roi_v1(state, (void *)val);
break;
+ case CRTC_PROP_CORE_AB:
+ case CRTC_PROP_CORE_IB:
+ case CRTC_PROP_MEM_AB:
+ case CRTC_PROP_MEM_IB:
+ cstate->bw_control = true;
+ break;
default:
/* nothing to do */
break;
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.h b/drivers/gpu/drm/msm/sde/sde_crtc.h
index 9ef6f25..4b3c814 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.h
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.h
@@ -255,6 +255,7 @@ struct sde_crtc_respool {
* @intf_mode : Interface mode of the primary connector
* @rsc_client : sde rsc client when mode is valid
* @is_ppsplit : Whether current topology requires PPSplit special handling
+ * @bw_control : true if bw controlled by bw properties
* @crtc_roi : Current CRTC ROI. Possibly sub-rectangle of mode.
* Origin top left of CRTC.
* @lm_bounds : LM boundaries based on current mode full resolution, no ROI.
@@ -280,6 +281,7 @@ struct sde_crtc_state {
enum sde_intf_mode intf_mode;
struct sde_rsc_client *rsc_client;
bool rsc_update;
+ bool bw_control;
bool is_ppsplit;
struct sde_rect crtc_roi;
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c
index 53f5b89..7adab09 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c
@@ -700,6 +700,8 @@ static void sde_encoder_phys_cmd_disable(struct sde_encoder_phys *phys_enc)
}
}
+ if (phys_enc->hw_pp->ops.enable_tearcheck)
+ phys_enc->hw_pp->ops.enable_tearcheck(phys_enc->hw_pp, false);
phys_enc->enable_state = SDE_ENC_DISABLED;
}
diff --git a/drivers/gpu/drm/msm/sde/sde_irq.c b/drivers/gpu/drm/msm/sde/sde_irq.c
index e3b658a..eeb7a00 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,90 +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);
-
- /* memory barrier */
- smp_mb__before_atomic();
- clear_bit(irqd->hwirq, &sde_kms->irq_controller.enabled_mask);
- /* memory barrier */
- 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);
-
- /* memory barrier */
- smp_mb__before_atomic();
- set_bit(irqd->hwirq, &sde_kms->irq_controller.enabled_mask);
- /* memory barrier */
- 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 const 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);
}
@@ -162,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 8cc196a..f0ce55c 100644
--- a/drivers/gpu/drm/msm/sde/sde_kms.c
+++ b/drivers/gpu/drm/msm/sde/sde_kms.c
@@ -744,6 +744,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.
@@ -821,6 +824,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;
}
@@ -1539,6 +1544,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
@@ -1564,23 +1577,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);
- if (!sde_kms->hw_intr)
- rc = -EINVAL;
- 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_edid_parser.c b/drivers/gpu/drm/msm/sde_edid_parser.c
new file mode 100644
index 0000000..12165e8
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde_edid_parser.c
@@ -0,0 +1,511 @@
+/* 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.
+ */
+
+#include <drm/drm_edid.h>
+
+#include "sde_kms.h"
+#include "sde_edid_parser.h"
+
+#define DBC_START_OFFSET 4
+#define EDID_DTD_LEN 18
+
+enum data_block_types {
+ RESERVED,
+ AUDIO_DATA_BLOCK,
+ VIDEO_DATA_BLOCK,
+ VENDOR_SPECIFIC_DATA_BLOCK,
+ SPEAKER_ALLOCATION_DATA_BLOCK,
+ VESA_DTC_DATA_BLOCK,
+ RESERVED2,
+ USE_EXTENDED_TAG
+};
+
+static u8 *sde_find_edid_extension(struct edid *edid, int ext_id)
+{
+ u8 *edid_ext = NULL;
+ int i;
+
+ /* No EDID or EDID extensions */
+ if (edid == NULL || edid->extensions == 0)
+ return NULL;
+
+ /* Find CEA extension */
+ for (i = 0; i < edid->extensions; i++) {
+ edid_ext = (u8 *)edid + EDID_LENGTH * (i + 1);
+ if (edid_ext[0] == ext_id)
+ break;
+ }
+
+ if (i == edid->extensions)
+ return NULL;
+
+ return edid_ext;
+}
+
+static u8 *sde_find_cea_extension(struct edid *edid)
+{
+ return sde_find_edid_extension(edid, SDE_CEA_EXT);
+}
+
+static int
+sde_cea_db_payload_len(const u8 *db)
+{
+ return db[0] & 0x1f;
+}
+
+static int
+sde_cea_db_tag(const u8 *db)
+{
+ return db[0] >> 5;
+}
+
+static int
+sde_cea_revision(const u8 *cea)
+{
+ return cea[1];
+}
+
+static int
+sde_cea_db_offsets(const u8 *cea, int *start, int *end)
+{
+ /* Data block offset in CEA extension block */
+ *start = 4;
+ *end = cea[2];
+ if (*end == 0)
+ *end = 127;
+ if (*end < 4 || *end > 127)
+ return -ERANGE;
+ return 0;
+}
+
+#define sde_for_each_cea_db(cea, i, start, end) \
+for ((i) = (start); \
+(i) < (end) && (i) + sde_cea_db_payload_len(&(cea)[(i)]) < (end); \
+(i) += sde_cea_db_payload_len(&(cea)[(i)]) + 1)
+
+static u8 *sde_edid_find_extended_tag_block(struct edid *edid, int blk_id)
+{
+ u8 *db = NULL;
+ u8 *cea = NULL;
+
+ if (!edid) {
+ SDE_ERROR("%s: invalid input\n", __func__);
+ return NULL;
+ }
+
+ cea = sde_find_cea_extension(edid);
+
+ if (cea && sde_cea_revision(cea) >= 3) {
+ int i, start, end;
+
+ if (sde_cea_db_offsets(cea, &start, &end))
+ return NULL;
+
+ sde_for_each_cea_db(cea, i, start, end) {
+ db = &cea[i];
+ if ((sde_cea_db_tag(db) == SDE_EXTENDED_TAG) &&
+ (db[1] == blk_id))
+ return db;
+ }
+ }
+ return NULL;
+}
+
+static u8 *
+sde_edid_find_block(struct edid *edid, int blk_id)
+{
+ u8 *db = NULL;
+ u8 *cea = NULL;
+
+ if (!edid) {
+ SDE_ERROR("%s: invalid input\n", __func__);
+ return NULL;
+ }
+
+ cea = sde_find_cea_extension(edid);
+
+ if (cea && sde_cea_revision(cea) >= 3) {
+ int i, start, end;
+
+ if (sde_cea_db_offsets(cea, &start, &end))
+ return NULL;
+
+ sde_for_each_cea_db(cea, i, start, end) {
+ db = &cea[i];
+ if (sde_cea_db_tag(db) == blk_id)
+ return db;
+ }
+ }
+ return NULL;
+}
+
+
+static const u8 *_sde_edid_find_block(const u8 *in_buf, u32 start_offset,
+ u8 type, u8 *len)
+{
+ /* the start of data block collection, start of Video Data Block */
+ u32 offset = start_offset;
+ u32 dbc_offset = in_buf[2];
+
+ SDE_EDID_DEBUG("%s +", __func__);
+ /*
+ * * edid buffer 1, byte 2 being 4 means no non-DTD/Data block
+ * collection present.
+ * * edid buffer 1, byte 2 being 0 means no non-DTD/DATA block
+ * collection present and no DTD data present.
+ */
+ if ((dbc_offset == 0) || (dbc_offset == 4)) {
+ SDE_ERROR("EDID: no DTD or non-DTD data present\n");
+ return NULL;
+ }
+
+ while (offset < dbc_offset) {
+ u8 block_len = in_buf[offset] & 0x1F;
+
+ if ((offset + block_len <= dbc_offset) &&
+ (in_buf[offset] >> 5) == type) {
+ *len = block_len;
+ SDE_EDID_DEBUG("block=%d found @ 0x%x w/ len=%d\n",
+ type, offset, block_len);
+
+ return in_buf + offset;
+ }
+ offset += 1 + block_len;
+ }
+
+ return NULL;
+}
+
+static void sde_edid_extract_vendor_id(struct sde_edid_ctrl *edid_ctrl)
+{
+ char *vendor_id;
+ u32 id_codes;
+
+ SDE_EDID_DEBUG("%s +", __func__);
+ if (!edid_ctrl) {
+ SDE_ERROR("%s: invalid input\n", __func__);
+ return;
+ }
+
+ vendor_id = edid_ctrl->vendor_id;
+ id_codes = ((u32)edid_ctrl->edid->mfg_id[0] << 8) +
+ edid_ctrl->edid->mfg_id[1];
+
+ vendor_id[0] = 'A' - 1 + ((id_codes >> 10) & 0x1F);
+ vendor_id[1] = 'A' - 1 + ((id_codes >> 5) & 0x1F);
+ vendor_id[2] = 'A' - 1 + (id_codes & 0x1F);
+ vendor_id[3] = 0;
+ SDE_EDID_DEBUG("vendor id is %s ", vendor_id);
+ SDE_EDID_DEBUG("%s -", __func__);
+}
+
+static void sde_edid_set_y420_support(struct drm_connector *connector,
+u32 video_format)
+{
+ u8 cea_mode = 0;
+ struct drm_display_mode *mode;
+
+ /* Need to add Y420 support flag to the modes */
+ list_for_each_entry(mode, &connector->probed_modes, head) {
+ cea_mode = drm_match_cea_mode(mode);
+ if ((cea_mode != 0) && (cea_mode == video_format)) {
+ SDE_EDID_DEBUG("%s found match for %d ", __func__,
+ video_format);
+ mode->flags |= DRM_MODE_FLAG_SUPPORTS_YUV;
+ }
+ }
+}
+
+static void sde_edid_parse_Y420CMDB(
+struct drm_connector *connector, struct sde_edid_ctrl *edid_ctrl,
+const u8 *db)
+{
+ u32 offset = 0;
+ u8 len = 0;
+ u8 svd_len = 0;
+ const u8 *svd = NULL;
+ u32 i = 0, j = 0;
+ u32 video_format = 0;
+
+ if (!edid_ctrl) {
+ SDE_ERROR("%s: edid_ctrl is NULL\n", __func__);
+ return;
+ }
+
+ if (!db) {
+ SDE_ERROR("%s: invalid input\n", __func__);
+ return;
+ }
+ SDE_EDID_DEBUG("%s +\n", __func__);
+ len = db[0] & 0x1f;
+
+ if (len < 7)
+ return;
+ /* Byte 3 to L+1 contain SVDs */
+ offset += 2;
+
+ svd = sde_edid_find_block(edid_ctrl->edid, VIDEO_DATA_BLOCK);
+
+ if (svd) {
+ /*moving to the next byte as vic info begins there*/
+ ++svd;
+ svd_len = svd[0] & 0x1f;
+ }
+
+ for (i = 0; i < svd_len; i++, j++) {
+ video_format = *svd & 0x7F;
+ if (db[offset] & (1 << j))
+ sde_edid_set_y420_support(connector, video_format);
+
+ if (j & 0x80) {
+ j = j/8;
+ offset++;
+ if (offset >= len)
+ break;
+ }
+ }
+
+ SDE_EDID_DEBUG("%s -\n", __func__);
+
+}
+
+static void sde_edid_parse_Y420VDB(
+struct drm_connector *connector, struct sde_edid_ctrl *edid_ctrl,
+const u8 *db)
+{
+ u8 len = db[0] & 0x1f;
+ u32 i = 0;
+ u32 video_format = 0;
+
+ if (!edid_ctrl) {
+ SDE_ERROR("%s: invalid input\n", __func__);
+ return;
+ }
+
+ SDE_EDID_DEBUG("%s +\n", __func__);
+
+ /* Offset to byte 3 */
+ db += 2;
+ for (i = 0; i < len - 1; i++) {
+ video_format = *(db + i) & 0x7F;
+ /*
+ * mode was already added in get_modes()
+ * only need to set the Y420 support flag
+ */
+ sde_edid_set_y420_support(connector, video_format);
+ }
+ SDE_EDID_DEBUG("%s -", __func__);
+}
+
+static void sde_edid_set_mode_format(
+struct drm_connector *connector, struct sde_edid_ctrl *edid_ctrl)
+{
+ const u8 *db = NULL;
+ struct drm_display_mode *mode;
+
+ SDE_EDID_DEBUG("%s +\n", __func__);
+ /* Set YUV mode support flags for YCbcr420VDB */
+ db = sde_edid_find_extended_tag_block(edid_ctrl->edid,
+ Y420_VIDEO_DATA_BLOCK);
+ if (db)
+ sde_edid_parse_Y420VDB(connector, edid_ctrl, db);
+ else
+ SDE_EDID_DEBUG("YCbCr420 VDB is not present\n");
+
+ /* Set RGB supported on all modes where YUV is not set */
+ list_for_each_entry(mode, &connector->probed_modes, head) {
+ if (!(mode->flags & DRM_MODE_FLAG_SUPPORTS_YUV))
+ mode->flags |= DRM_MODE_FLAG_SUPPORTS_RGB;
+ }
+
+
+ db = sde_edid_find_extended_tag_block(edid_ctrl->edid,
+ Y420_CAPABILITY_MAP_DATA_BLOCK);
+ if (db)
+ sde_edid_parse_Y420CMDB(connector, edid_ctrl, db);
+ else
+ SDE_EDID_DEBUG("YCbCr420 CMDB is not present\n");
+
+ SDE_EDID_DEBUG("%s -\n", __func__);
+}
+
+static void _sde_edid_extract_audio_data_blocks(
+ struct sde_edid_ctrl *edid_ctrl)
+{
+ u8 len = 0;
+ u8 adb_max = 0;
+ const u8 *adb = NULL;
+ u32 offset = DBC_START_OFFSET;
+ u8 *cea = NULL;
+
+ if (!edid_ctrl) {
+ SDE_ERROR("invalid edid_ctrl\n");
+ return;
+ }
+ SDE_EDID_DEBUG("%s +", __func__);
+ cea = sde_find_cea_extension(edid_ctrl->edid);
+ if (!cea) {
+ SDE_DEBUG("CEA extension not found\n");
+ return;
+ }
+
+ edid_ctrl->adb_size = 0;
+
+ memset(edid_ctrl->audio_data_block, 0,
+ sizeof(edid_ctrl->audio_data_block));
+
+ do {
+ len = 0;
+ adb = _sde_edid_find_block(cea, offset, AUDIO_DATA_BLOCK,
+ &len);
+
+ if ((adb == NULL) || (len > MAX_AUDIO_DATA_BLOCK_SIZE ||
+ adb_max >= MAX_NUMBER_ADB)) {
+ if (!edid_ctrl->adb_size) {
+ SDE_DEBUG("No/Invalid Audio Data Block\n");
+ return;
+ }
+
+ continue;
+ }
+
+ memcpy(edid_ctrl->audio_data_block + edid_ctrl->adb_size,
+ adb + 1, len);
+ offset = (adb - cea) + 1 + len;
+
+ edid_ctrl->adb_size += len;
+ adb_max++;
+ } while (adb);
+ SDE_EDID_DEBUG("%s -", __func__);
+}
+
+static void _sde_edid_extract_speaker_allocation_data(
+ struct sde_edid_ctrl *edid_ctrl)
+{
+ u8 len;
+ const u8 *sadb = NULL;
+ u8 *cea = NULL;
+
+ if (!edid_ctrl) {
+ SDE_ERROR("invalid edid_ctrl\n");
+ return;
+ }
+ SDE_EDID_DEBUG("%s +", __func__);
+ cea = sde_find_cea_extension(edid_ctrl->edid);
+ if (!cea) {
+ SDE_DEBUG("CEA extension not found\n");
+ return;
+ }
+
+ sadb = _sde_edid_find_block(cea, DBC_START_OFFSET,
+ SPEAKER_ALLOCATION_DATA_BLOCK, &len);
+ if ((sadb == NULL) || (len != MAX_SPKR_ALLOC_DATA_BLOCK_SIZE)) {
+ SDE_DEBUG("No/Invalid Speaker Allocation Data Block\n");
+ return;
+ }
+
+ memcpy(edid_ctrl->spkr_alloc_data_block, sadb + 1, len);
+ edid_ctrl->sadb_size = len;
+
+ SDE_EDID_DEBUG("speaker alloc data SP byte = %08x %s%s%s%s%s%s%s\n",
+ sadb[1],
+ (sadb[1] & BIT(0)) ? "FL/FR," : "",
+ (sadb[1] & BIT(1)) ? "LFE," : "",
+ (sadb[1] & BIT(2)) ? "FC," : "",
+ (sadb[1] & BIT(3)) ? "RL/RR," : "",
+ (sadb[1] & BIT(4)) ? "RC," : "",
+ (sadb[1] & BIT(5)) ? "FLC/FRC," : "",
+ (sadb[1] & BIT(6)) ? "RLC/RRC," : "");
+ SDE_EDID_DEBUG("%s -", __func__);
+}
+
+struct sde_edid_ctrl *sde_edid_init(void)
+{
+ struct sde_edid_ctrl *edid_ctrl = NULL;
+
+ SDE_EDID_DEBUG("%s +\n", __func__);
+ edid_ctrl = kzalloc(sizeof(*edid_ctrl), GFP_KERNEL);
+ if (!edid_ctrl) {
+ SDE_ERROR("edid_ctrl alloc failed\n");
+ return NULL;
+ }
+ memset((edid_ctrl), 0, sizeof(*edid_ctrl));
+ SDE_EDID_DEBUG("%s -\n", __func__);
+ return edid_ctrl;
+}
+
+void sde_free_edid(void **input)
+{
+ struct sde_edid_ctrl *edid_ctrl = (struct sde_edid_ctrl *)(*input);
+
+ SDE_EDID_DEBUG("%s +", __func__);
+ kfree(edid_ctrl->edid);
+ edid_ctrl->edid = NULL;
+}
+
+void sde_edid_deinit(void **input)
+{
+ struct sde_edid_ctrl *edid_ctrl = (struct sde_edid_ctrl *)(*input);
+
+ SDE_EDID_DEBUG("%s +", __func__);
+ sde_free_edid((void *)&edid_ctrl);
+ kfree(edid_ctrl);
+ SDE_EDID_DEBUG("%s -", __func__);
+}
+
+int _sde_edid_update_modes(struct drm_connector *connector,
+ void *input)
+{
+ int rc = 0;
+ struct sde_edid_ctrl *edid_ctrl = (struct sde_edid_ctrl *)(input);
+
+ SDE_EDID_DEBUG("%s +", __func__);
+ if (edid_ctrl->edid) {
+ drm_mode_connector_update_edid_property(connector,
+ edid_ctrl->edid);
+
+ rc = drm_add_edid_modes(connector, edid_ctrl->edid);
+ sde_edid_set_mode_format(connector, edid_ctrl);
+ SDE_EDID_DEBUG("%s -", __func__);
+ return rc;
+ }
+
+ drm_mode_connector_update_edid_property(connector, NULL);
+ SDE_EDID_DEBUG("%s null edid -", __func__);
+ return rc;
+}
+
+bool sde_detect_hdmi_monitor(void *input)
+{
+ struct sde_edid_ctrl *edid_ctrl = (struct sde_edid_ctrl *)(input);
+
+ return drm_detect_hdmi_monitor(edid_ctrl->edid);
+}
+
+void sde_get_edid(struct drm_connector *connector,
+ struct i2c_adapter *adapter, void **input)
+{
+ struct sde_edid_ctrl *edid_ctrl = (struct sde_edid_ctrl *)(*input);
+
+ edid_ctrl->edid = drm_get_edid(connector, adapter);
+ SDE_EDID_DEBUG("%s +\n", __func__);
+
+ if (!edid_ctrl->edid)
+ SDE_ERROR("EDID read failed\n");
+
+ if (edid_ctrl->edid) {
+ sde_edid_extract_vendor_id(edid_ctrl);
+ _sde_edid_extract_audio_data_blocks(edid_ctrl);
+ _sde_edid_extract_speaker_allocation_data(edid_ctrl);
+ }
+ SDE_EDID_DEBUG("%s -\n", __func__);
+};
diff --git a/drivers/gpu/drm/msm/sde_edid_parser.h b/drivers/gpu/drm/msm/sde_edid_parser.h
new file mode 100644
index 0000000..1143dc2
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde_edid_parser.h
@@ -0,0 +1,148 @@
+/*
+ * 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.
+ *
+ */
+
+#ifndef _SDE_EDID_PARSER_H_
+#define _SDE_EDID_PARSER_H_
+
+#include <linux/types.h>
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/of_device.h>
+#include <linux/i2c.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_edid.h>
+
+
+#define MAX_NUMBER_ADB 5
+#define MAX_AUDIO_DATA_BLOCK_SIZE 30
+#define MAX_SPKR_ALLOC_DATA_BLOCK_SIZE 3
+#define EDID_VENDOR_ID_SIZE 4
+
+#define SDE_CEA_EXT 0x02
+#define SDE_EXTENDED_TAG 0x07
+
+enum extended_data_block_types {
+ VIDEO_CAPABILITY_DATA_BLOCK = 0x0,
+ VENDOR_SPECIFIC_VIDEO_DATA_BLOCK = 0x01,
+ HDMI_VIDEO_DATA_BLOCK = 0x04,
+ HDR_STATIC_METADATA_DATA_BLOCK = 0x06,
+ Y420_VIDEO_DATA_BLOCK = 0x0E,
+ VIDEO_FORMAT_PREFERENCE_DATA_BLOCK = 0x0D,
+ Y420_CAPABILITY_MAP_DATA_BLOCK = 0x0F,
+ VENDOR_SPECIFIC_AUDIO_DATA_BLOCK = 0x11,
+ INFOFRAME_DATA_BLOCK = 0x20,
+};
+
+#ifdef SDE_EDID_DEBUG_ENABLE
+#define SDE_EDID_DEBUG(fmt, args...) SDE_ERROR(fmt, ##args)
+#else
+#define SDE_EDID_DEBUG(fmt, args...) SDE_DEBUG(fmt, ##args)
+#endif
+
+/*
+ * struct hdmi_edid_hdr_data - HDR Static Metadata
+ * @eotf: Electro-Optical Transfer Function
+ * @metadata_type_one: Static Metadata Type 1 support
+ * @max_luminance: Desired Content Maximum Luminance
+ * @avg_luminance: Desired Content Frame-average Luminance
+ * @min_luminance: Desired Content Minimum Luminance
+ */
+struct sde_edid_hdr_data {
+ u32 eotf;
+ bool metadata_type_one;
+ u32 max_luminance;
+ u32 avg_luminance;
+ u32 min_luminance;
+};
+
+struct sde_edid_sink_caps {
+ u32 max_pclk_in_hz;
+ bool scdc_present;
+ bool scramble_support; /* scramble support for less than 340Mcsc */
+ bool read_req_support;
+ bool osd_disparity;
+ bool dual_view_support;
+ bool ind_view_support;
+};
+
+struct sde_edid_ctrl {
+ struct edid *edid;
+ u8 pt_scan_info;
+ u8 it_scan_info;
+ u8 ce_scan_info;
+ u8 audio_data_block[MAX_NUMBER_ADB * MAX_AUDIO_DATA_BLOCK_SIZE];
+ int adb_size;
+ u8 spkr_alloc_data_block[MAX_SPKR_ALLOC_DATA_BLOCK_SIZE];
+ int sadb_size;
+ bool hdr_supported;
+ char vendor_id[EDID_VENDOR_ID_SIZE];
+ struct sde_edid_sink_caps sink_caps;
+ struct sde_edid_hdr_data hdr_data;
+};
+
+/**
+ * sde_edid_init() - init edid structure.
+ * @edid_ctrl: Handle to the edid_ctrl structure.
+ * Return: handle to sde_edid_ctrl for the client.
+ */
+struct sde_edid_ctrl *sde_edid_init(void);
+
+/**
+ * sde_edid_deinit() - deinit edid structure.
+ * @edid_ctrl: Handle to the edid_ctrl structure.
+ *
+ * Return: void.
+ */
+void sde_edid_deinit(void **edid_ctrl);
+
+/**
+ * sde_get_edid() - get edid info.
+ * @connector: Handle to the drm_connector.
+ * @adapter: handle to i2c adapter for DDC read
+ * @edid_ctrl: Handle to the edid_ctrl structure.
+ *
+ * Return: void.
+ */
+void sde_get_edid(struct drm_connector *connector,
+struct i2c_adapter *adapter,
+void **edid_ctrl);
+
+/**
+ * sde_free_edid() - free edid structure.
+ * @edid_ctrl: Handle to the edid_ctrl structure.
+ *
+ * Return: void.
+ */
+void sde_free_edid(void **edid_ctrl);
+
+/**
+ * sde_detect_hdmi_monitor() - detect HDMI mode.
+ * @edid_ctrl: Handle to the edid_ctrl structure.
+ *
+ * Return: error code.
+ */
+bool sde_detect_hdmi_monitor(void *edid_ctrl);
+
+/**
+ * _sde_edid_update_modes() - populate EDID modes.
+ * @edid_ctrl: Handle to the edid_ctrl structure.
+ *
+ * Return: error code.
+ */
+int _sde_edid_update_modes(struct drm_connector *connector,
+ void *edid_ctrl);
+
+#endif /* _SDE_EDID_PARSER_H_ */
+
diff --git a/drivers/gpu/drm/msm/sde_power_handle.c b/drivers/gpu/drm/msm/sde_power_handle.c
index 1e4f6b1..fb7f85c 100644
--- a/drivers/gpu/drm/msm/sde_power_handle.c
+++ b/drivers/gpu/drm/msm/sde_power_handle.c
@@ -333,6 +333,31 @@ static int _sde_power_data_bus_set_quota(
return -EINVAL;
}
+ pdbus->ab_rt = ab_quota_rt;
+ pdbus->ib_rt = ib_quota_rt;
+ pdbus->ab_nrt = ab_quota_nrt;
+ pdbus->ib_nrt = ib_quota_nrt;
+
+ if (pdbus->enable) {
+ ab_quota_rt = max_t(u64, ab_quota_rt,
+ SDE_POWER_HANDLE_ENABLE_BUS_AB_QUOTA);
+ ib_quota_rt = max_t(u64, ib_quota_rt,
+ SDE_POWER_HANDLE_ENABLE_BUS_IB_QUOTA);
+ ab_quota_nrt = max_t(u64, ab_quota_nrt,
+ SDE_POWER_HANDLE_ENABLE_BUS_AB_QUOTA);
+ ib_quota_nrt = max_t(u64, ib_quota_nrt,
+ SDE_POWER_HANDLE_ENABLE_BUS_IB_QUOTA);
+ } else {
+ ab_quota_rt = max_t(u64, ab_quota_rt,
+ SDE_POWER_HANDLE_DISABLE_BUS_AB_QUOTA);
+ ib_quota_rt = max_t(u64, ib_quota_rt,
+ SDE_POWER_HANDLE_DISABLE_BUS_IB_QUOTA);
+ ab_quota_nrt = max_t(u64, ab_quota_nrt,
+ SDE_POWER_HANDLE_DISABLE_BUS_AB_QUOTA);
+ ib_quota_nrt = max_t(u64, ib_quota_nrt,
+ SDE_POWER_HANDLE_DISABLE_BUS_IB_QUOTA);
+ }
+
if (!ab_quota_rt && !ab_quota_nrt && !ib_quota_rt && !ib_quota_nrt) {
new_uc_idx = 0;
} else {
@@ -571,19 +596,12 @@ static int sde_power_data_bus_update(struct sde_power_data_bus_handle *pdbus,
bool enable)
{
int rc = 0;
- u64 ab_quota_rt, ab_quota_nrt;
- u64 ib_quota_rt, ib_quota_nrt;
- ab_quota_rt = ab_quota_nrt = enable ?
- SDE_POWER_HANDLE_ENABLE_BUS_AB_QUOTA :
- SDE_POWER_HANDLE_DISABLE_BUS_AB_QUOTA;
- ib_quota_rt = ib_quota_nrt = enable ?
- SDE_POWER_HANDLE_ENABLE_BUS_IB_QUOTA :
- SDE_POWER_HANDLE_DISABLE_BUS_IB_QUOTA;
+ pdbus->enable = enable;
if (pdbus->data_bus_hdl)
- rc = _sde_power_data_bus_set_quota(pdbus, ab_quota_rt,
- ab_quota_nrt, ib_quota_rt, ib_quota_nrt);
+ rc = _sde_power_data_bus_set_quota(pdbus, pdbus->ab_rt,
+ pdbus->ab_nrt, pdbus->ib_rt, pdbus->ib_nrt);
if (rc)
pr_err("failed to set data bus vote rc=%d enable:%d\n",
diff --git a/drivers/gpu/drm/msm/sde_power_handle.h b/drivers/gpu/drm/msm/sde_power_handle.h
index 38bf21f..c526b71 100644
--- a/drivers/gpu/drm/msm/sde_power_handle.h
+++ b/drivers/gpu/drm/msm/sde_power_handle.h
@@ -16,9 +16,9 @@
#define MAX_CLIENT_NAME_LEN 128
-#define SDE_POWER_HANDLE_ENABLE_BUS_AB_QUOTA 6000000000
+#define SDE_POWER_HANDLE_ENABLE_BUS_AB_QUOTA 2000000
#define SDE_POWER_HANDLE_DISABLE_BUS_AB_QUOTA 0
-#define SDE_POWER_HANDLE_ENABLE_BUS_IB_QUOTA 6000000000
+#define SDE_POWER_HANDLE_ENABLE_BUS_IB_QUOTA 2000000
#define SDE_POWER_HANDLE_DISABLE_BUS_IB_QUOTA 0
#include <linux/sde_io_util.h>
@@ -93,6 +93,11 @@ struct sde_power_client {
* @bus_channels: number of memory bus channels
* @curr_bw_uc_idx: current use case index of data bus
* @ao_bw_uc_idx: active only use case index of data bus
+ * @ab_rt: realtime ab quota
+ * @ib_rt: realtime ib quota
+ * @ab_nrt: non-realtime ab quota
+ * @ib_nrt: non-realtime ib quota
+ * @enable: true if bus is enabled
*/
struct sde_power_data_bus_handle {
struct msm_bus_scale_pdata *data_bus_scale_table;
@@ -102,6 +107,11 @@ struct sde_power_data_bus_handle {
u32 bus_channels;
u32 curr_bw_uc_idx;
u32 ao_bw_uc_idx;
+ u64 ab_rt;
+ u64 ib_rt;
+ u64 ab_nrt;
+ u64 ib_nrt;
+ bool enable;
};
/*
diff --git a/drivers/gpu/drm/msm/sde_rsc.c b/drivers/gpu/drm/msm/sde_rsc.c
index 46d1df8..7bf2211 100644
--- a/drivers/gpu/drm/msm/sde_rsc.c
+++ b/drivers/gpu/drm/msm/sde_rsc.c
@@ -220,6 +220,39 @@ void sde_rsc_unregister_event(struct sde_rsc_event *event)
}
EXPORT_SYMBOL(sde_rsc_unregister_event);
+bool is_sde_rsc_available(int rsc_index)
+{
+ if (rsc_index >= MAX_RSC_COUNT) {
+ pr_err("invalid rsc index:%d\n", rsc_index);
+ return false;
+ } else if (!rsc_prv_list[rsc_index]) {
+ pr_err("rsc idx:%d not probed yet or not available\n",
+ rsc_index);
+ return false;
+ }
+
+ return true;
+}
+EXPORT_SYMBOL(is_sde_rsc_available);
+
+enum sde_rsc_state get_sde_rsc_current_state(int rsc_index)
+{
+ struct sde_rsc_priv *rsc;
+
+ if (rsc_index >= MAX_RSC_COUNT) {
+ pr_err("invalid rsc index:%d\n", rsc_index);
+ return SDE_RSC_IDLE_STATE;
+ } else if (!rsc_prv_list[rsc_index]) {
+ pr_err("rsc idx:%d not probed yet or not available\n",
+ rsc_index);
+ return SDE_RSC_IDLE_STATE;
+ }
+
+ rsc = rsc_prv_list[rsc_index];
+ return rsc->current_state;
+}
+EXPORT_SYMBOL(get_sde_rsc_current_state);
+
static int sde_rsc_clk_enable(struct sde_power_handle *phandle,
struct sde_power_client *pclient, bool enable)
{
diff --git a/drivers/gpu/msm/a6xx_reg.h b/drivers/gpu/msm/a6xx_reg.h
index e982afe..1f76233a 100644
--- a/drivers/gpu/msm/a6xx_reg.h
+++ b/drivers/gpu/msm/a6xx_reg.h
@@ -860,6 +860,9 @@
#define A6XX_GMU_AHB_FENCE_RANGE_0 0x23B11
#define A6XX_GMU_AHB_FENCE_RANGE_1 0x23B12
+/* GPUCC registers */
+#define A6XX_GPU_CC_GX_GDSCR 0x24403
+
/* GPU RSC sequencer registers */
#define A6XX_RSCC_PDC_SEQ_START_ADDR 0x23408
#define A6XX_RSCC_PDC_MATCH_VALUE_LO 0x23409
diff --git a/drivers/gpu/msm/adreno_a6xx.c b/drivers/gpu/msm/adreno_a6xx.c
index 6e025c8..9a56bec 100644
--- a/drivers/gpu/msm/adreno_a6xx.c
+++ b/drivers/gpu/msm/adreno_a6xx.c
@@ -1007,6 +1007,7 @@ static inline void a6xx_gpu_keepalive(struct adreno_device *adreno_dev,
#define SPTPRAC_POWEROFF_STATUS_MASK BIT(2)
#define SPTPRAC_POWERON_STATUS_MASK BIT(3)
#define SPTPRAC_CTRL_TIMEOUT 10 /* ms */
+#define A6XX_RETAIN_FF_ENABLE_ENABLE_MASK BIT(11)
/*
* a6xx_sptprac_enable() - Power on SPTPRAC
@@ -1047,6 +1048,10 @@ static void a6xx_sptprac_disable(struct adreno_device *adreno_dev)
if (!gmu->pdev)
return;
+ /* Ensure that retention is on */
+ kgsl_gmu_regrmw(device, A6XX_GPU_CC_GX_GDSCR, 0,
+ A6XX_RETAIN_FF_ENABLE_ENABLE_MASK);
+
kgsl_gmu_regwrite(device, A6XX_GMU_GX_SPTPRAC_POWER_CONTROL,
SPTPRAC_POWEROFF_CTRL_MASK);
@@ -1101,6 +1106,10 @@ static int a6xx_hm_disable(struct adreno_device *adreno_dev)
if (!regulator_is_enabled(gmu->gx_gdsc))
return 0;
+ /* Ensure that retention is on */
+ kgsl_gmu_regrmw(device, A6XX_GPU_CC_GX_GDSCR, 0,
+ A6XX_RETAIN_FF_ENABLE_ENABLE_MASK);
+
clk_disable_unprepare(pwr->grp_clks[0]);
clk_set_rate(pwr->grp_clks[0],
diff --git a/drivers/gpu/msm/kgsl_gmu.c b/drivers/gpu/msm/kgsl_gmu.c
index 7354e82..f72b3fa 100644
--- a/drivers/gpu/msm/kgsl_gmu.c
+++ b/drivers/gpu/msm/kgsl_gmu.c
@@ -1543,8 +1543,6 @@ void gmu_remove(struct kgsl_device *device)
if (gmu->reg_virt) {
devm_iounmap(&gmu->pdev->dev, gmu->reg_virt);
- devm_release_mem_region(&gmu->pdev->dev,
- gmu->reg_phys, gmu->reg_len);
gmu->reg_virt = NULL;
}
diff --git a/drivers/media/platform/msm/camera/cam_isp/isp_hw_mgr/cam_ife_hw_mgr.c b/drivers/media/platform/msm/camera/cam_isp/isp_hw_mgr/cam_ife_hw_mgr.c
index 49085d7..4b2db07 100644
--- a/drivers/media/platform/msm/camera/cam_isp/isp_hw_mgr/cam_ife_hw_mgr.c
+++ b/drivers/media/platform/msm/camera/cam_isp/isp_hw_mgr/cam_ife_hw_mgr.c
@@ -3017,7 +3017,7 @@ int cam_ife_hw_mgr_init(struct cam_hw_mgr_intf *hw_mgr_intf)
/* Create Worker for ife_hw_mgr with 10 tasks */
rc = cam_req_mgr_workq_create("cam_ife_worker", 10,
- &g_ife_hw_mgr.workq);
+ &g_ife_hw_mgr.workq, CRM_WORKQ_USAGE_NON_IRQ);
if (rc < 0) {
pr_err("%s: Unable to create worker\n", __func__);
diff --git a/drivers/media/platform/msm/camera/cam_req_mgr/cam_req_mgr_core.c b/drivers/media/platform/msm/camera/cam_req_mgr/cam_req_mgr_core.c
index b97593b..ed251eb 100644
--- a/drivers/media/platform/msm/camera/cam_req_mgr/cam_req_mgr_core.c
+++ b/drivers/media/platform/msm/camera/cam_req_mgr/cam_req_mgr_core.c
@@ -1813,7 +1813,8 @@ int cam_req_mgr_link(struct cam_req_mgr_link_info *link_info)
/* Create worker for current link */
snprintf(buf, sizeof(buf), "%x-%x",
link_info->session_hdl, link->link_hdl);
- rc = cam_req_mgr_workq_create(buf, CRM_WORKQ_NUM_TASKS, &link->workq);
+ rc = cam_req_mgr_workq_create(buf, CRM_WORKQ_NUM_TASKS,
+ &link->workq, CRM_WORKQ_USAGE_NON_IRQ);
if (rc < 0) {
CRM_ERR("FATAL: unable to create worker");
__cam_req_mgr_destroy_link_info(link);
diff --git a/drivers/media/platform/msm/camera/cam_req_mgr/cam_req_mgr_workq.c b/drivers/media/platform/msm/camera/cam_req_mgr/cam_req_mgr_workq.c
index b026b7c..38dcb42 100644
--- a/drivers/media/platform/msm/camera/cam_req_mgr/cam_req_mgr_workq.c
+++ b/drivers/media/platform/msm/camera/cam_req_mgr/cam_req_mgr_workq.c
@@ -12,16 +12,30 @@
#include "cam_req_mgr_workq.h"
+#define WORKQ_ACQUIRE_LOCK(workq, flags) {\
+ if ((workq)->in_irq) \
+ spin_lock_irqsave(&(workq)->lock_bh, (flags)); \
+ else \
+ spin_lock_bh(&(workq)->lock_bh); \
+}
+
+#define WORKQ_RELEASE_LOCK(workq, flags) {\
+ if ((workq)->in_irq) \
+ spin_unlock_irqrestore(&(workq)->lock_bh, (flags)); \
+ else \
+ spin_unlock_bh(&(workq)->lock_bh); \
+}
struct crm_workq_task *cam_req_mgr_workq_get_task(
struct cam_req_mgr_core_workq *workq)
{
struct crm_workq_task *task = NULL;
+ unsigned long flags = 0;
if (!workq)
return NULL;
- spin_lock_bh(&workq->lock_bh);
+ WORKQ_ACQUIRE_LOCK(workq, flags);
if (list_empty(&workq->task.empty_head))
goto end;
@@ -33,7 +47,8 @@ struct crm_workq_task *cam_req_mgr_workq_get_task(
}
end:
- spin_unlock_bh(&workq->lock_bh);
+ WORKQ_RELEASE_LOCK(workq, flags);
+
return task;
}
@@ -41,8 +56,9 @@ static void cam_req_mgr_workq_put_task(struct crm_workq_task *task)
{
struct cam_req_mgr_core_workq *workq =
(struct cam_req_mgr_core_workq *)task->parent;
+ unsigned long flags = 0;
- spin_lock_bh(&workq->lock_bh);
+ WORKQ_ACQUIRE_LOCK(workq, flags);
list_del_init(&task->entry);
task->cancel = 0;
task->process_cb = NULL;
@@ -50,7 +66,7 @@ static void cam_req_mgr_workq_put_task(struct crm_workq_task *task)
list_add_tail(&task->entry,
&workq->task.empty_head);
atomic_add(1, &workq->task.free_cnt);
- spin_unlock_bh(&workq->lock_bh);
+ WORKQ_RELEASE_LOCK(workq, flags);
}
/**
@@ -131,6 +147,7 @@ int cam_req_mgr_workq_enqueue_task(struct crm_workq_task *task,
{
int rc = 0;
struct cam_req_mgr_core_workq *workq = NULL;
+ unsigned long flags = 0;
if (!task) {
CRM_WARN("NULL task pointer can not schedule");
@@ -159,10 +176,10 @@ int cam_req_mgr_workq_enqueue_task(struct crm_workq_task *task,
(prio < CRM_TASK_PRIORITY_MAX && prio >= CRM_TASK_PRIORITY_0)
? prio : CRM_TASK_PRIORITY_0;
- spin_lock_bh(&workq->lock_bh);
+ WORKQ_ACQUIRE_LOCK(workq, flags);
list_add_tail(&task->entry,
&workq->task.process_head[task->priority]);
- spin_unlock_bh(&workq->lock_bh);
+ WORKQ_RELEASE_LOCK(workq, flags);
atomic_add(1, &workq->task.pending_cnt);
CRM_DBG("enq task %pK pending_cnt %d",
@@ -175,7 +192,7 @@ int cam_req_mgr_workq_enqueue_task(struct crm_workq_task *task,
}
int cam_req_mgr_workq_create(char *name, int32_t num_tasks,
- struct cam_req_mgr_core_workq **workq)
+ struct cam_req_mgr_core_workq **workq, enum crm_workq_context in_irq)
{
int32_t i;
struct crm_workq_task *task;
@@ -210,6 +227,7 @@ int cam_req_mgr_workq_create(char *name, int32_t num_tasks,
for (i = CRM_TASK_PRIORITY_0; i < CRM_TASK_PRIORITY_MAX; i++)
INIT_LIST_HEAD(&crm_workq->task.process_head[i]);
INIT_LIST_HEAD(&crm_workq->task.empty_head);
+ crm_workq->in_irq = in_irq;
crm_workq->task.num_task = num_tasks;
crm_workq->task.pool = (struct crm_workq_task *)
kzalloc(sizeof(struct crm_workq_task) *
diff --git a/drivers/media/platform/msm/camera/cam_req_mgr/cam_req_mgr_workq.h b/drivers/media/platform/msm/camera/cam_req_mgr/cam_req_mgr_workq.h
index 7d8ca59..eb3b804 100644
--- a/drivers/media/platform/msm/camera/cam_req_mgr/cam_req_mgr_workq.h
+++ b/drivers/media/platform/msm/camera/cam_req_mgr/cam_req_mgr_workq.h
@@ -25,9 +25,16 @@
/* Task priorities, lower the number higher the priority*/
enum crm_task_priority {
- CRM_TASK_PRIORITY_0 = 0,
- CRM_TASK_PRIORITY_1 = 1,
- CRM_TASK_PRIORITY_MAX = 2,
+ CRM_TASK_PRIORITY_0,
+ CRM_TASK_PRIORITY_1,
+ CRM_TASK_PRIORITY_MAX,
+};
+
+/* workqueue will be used from irq context or not */
+enum crm_workq_context {
+ CRM_WORKQ_USAGE_NON_IRQ,
+ CRM_WORKQ_USAGE_IRQ,
+ CRM_WORKQ_USAGE_INVALID,
};
/** struct crm_workq_task
@@ -58,8 +65,9 @@ struct crm_workq_task {
* @work : work token used by workqueue
* @job : workqueue internal job struct
* task -
- * @lock : lock for task structs
- * @free_cnt : num of free/available tasks
+ * @lock_bh : lock for task structs
+ * @in_irq : set true if workque can be used in irq context
+ * @free_cnt : num of free/available tasks
* @empty_head : list head of available taska which can be used
* or acquired in order to enqueue a task to workq
* @pool : pool of tasks used for handling events in workq context
@@ -70,6 +78,7 @@ struct cam_req_mgr_core_workq {
struct work_struct work;
struct workqueue_struct *job;
spinlock_t lock_bh;
+ uint32_t in_irq;
/* tasks */
struct {
@@ -91,11 +100,12 @@ struct cam_req_mgr_core_workq {
* of session handle and link handle
* @num_task : Num_tasks to be allocated for workq
* @workq : Double pointer worker
+ * @in_irq : Set to one if workq might be used in irq context
* This function will allocate and create workqueue and pass
* the workq pointer to caller.
*/
int cam_req_mgr_workq_create(char *name, int32_t num_tasks,
- struct cam_req_mgr_core_workq **workq);
+ struct cam_req_mgr_core_workq **workq, enum crm_workq_context in_irq);
/**
* cam_req_mgr_workq_destroy()
diff --git a/drivers/media/platform/msm/camera/icp/icp_hw/icp_hw_mgr/cam_icp_hw_mgr.c b/drivers/media/platform/msm/camera/icp/icp_hw/icp_hw_mgr/cam_icp_hw_mgr.c
index 2fa39c8..140542b 100644
--- a/drivers/media/platform/msm/camera/icp/icp_hw/icp_hw_mgr/cam_icp_hw_mgr.c
+++ b/drivers/media/platform/msm/camera/icp/icp_hw/icp_hw_mgr/cam_icp_hw_mgr.c
@@ -1904,14 +1904,14 @@ int cam_icp_hw_mgr_init(struct device_node *of_node, uint64_t *hw_mgr_hdl)
}
rc = cam_req_mgr_workq_create("icp_command_queue", ICP_WORKQ_NUM_TASK,
- &icp_hw_mgr.cmd_work);
+ &icp_hw_mgr.cmd_work, CRM_WORKQ_USAGE_NON_IRQ);
if (rc < 0) {
pr_err("unable to create a worker\n");
goto cmd_work_failed;
}
rc = cam_req_mgr_workq_create("icp_message_queue", ICP_WORKQ_NUM_TASK,
- &icp_hw_mgr.msg_work);
+ &icp_hw_mgr.msg_work, CRM_WORKQ_USAGE_IRQ);
if (rc < 0) {
pr_err("unable to create a worker\n");
goto msg_work_failed;
diff --git a/drivers/media/platform/msm/vidc/hfi_packetization.c b/drivers/media/platform/msm/vidc/hfi_packetization.c
index e7e9278..88250e1 100644
--- a/drivers/media/platform/msm/vidc/hfi_packetization.c
+++ b/drivers/media/platform/msm/vidc/hfi_packetization.c
@@ -1523,14 +1523,6 @@ int create_pkt_cmd_session_set_property(
sizeof(struct hfi_vui_timing_info);
break;
}
- case HAL_CONFIG_VPE_DEINTERLACE:
- {
- create_pkt_enable(pkt->rg_property_data,
- HFI_PROPERTY_CONFIG_VPE_DEINTERLACE,
- ((struct hal_enable *)pdata)->enable);
- pkt->size += sizeof(u32) + sizeof(struct hfi_enable);
- break;
- }
case HAL_PARAM_VENC_GENERATE_AUDNAL:
{
create_pkt_enable(pkt->rg_property_data,
diff --git a/drivers/media/platform/msm/vidc/msm_vidc.c b/drivers/media/platform/msm/vidc/msm_vidc.c
index 499d851..6253632 100644
--- a/drivers/media/platform/msm/vidc/msm_vidc.c
+++ b/drivers/media/platform/msm/vidc/msm_vidc.c
@@ -171,6 +171,12 @@ int msm_vidc_query_ctrl(void *instance, struct v4l2_queryctrl *ctrl)
case V4L2_CID_MPEG_VIDC_VIDEO_NUM_B_FRAMES:
msm_vidc_ctrl_get_range(ctrl, &inst->capability.bframe);
break;
+ case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_MB:
+ msm_vidc_ctrl_get_range(ctrl, &inst->capability.slice_mbs);
+ break;
+ case V4L2_CID_MPEG_VIDEO_MULTI_SLICE_MAX_BYTES:
+ msm_vidc_ctrl_get_range(ctrl, &inst->capability.slice_bytes);
+ break;
default:
rc = -EINVAL;
}
diff --git a/drivers/media/platform/msm/vidc/vidc_hfi_api.h b/drivers/media/platform/msm/vidc/vidc_hfi_api.h
index cc35bb3..a2f076b 100644
--- a/drivers/media/platform/msm/vidc/vidc_hfi_api.h
+++ b/drivers/media/platform/msm/vidc/vidc_hfi_api.h
@@ -157,7 +157,6 @@ enum hal_property {
HAL_CONFIG_VPE_OPERATIONS,
HAL_PARAM_VENC_INTRA_REFRESH,
HAL_PARAM_VENC_MULTI_SLICE_CONTROL,
- HAL_CONFIG_VPE_DEINTERLACE,
HAL_SYS_DEBUG_CONFIG,
HAL_CONFIG_BUFFER_REQUIREMENTS,
HAL_CONFIG_PRIORITY,
diff --git a/drivers/media/platform/msm/vidc/vidc_hfi_helper.h b/drivers/media/platform/msm/vidc/vidc_hfi_helper.h
index 0df4812..2d4a573 100644
--- a/drivers/media/platform/msm/vidc/vidc_hfi_helper.h
+++ b/drivers/media/platform/msm/vidc/vidc_hfi_helper.h
@@ -349,8 +349,6 @@ struct hfi_buffer_info {
(HFI_DOMAIN_BASE_VPE + HFI_ARCH_COMMON_OFFSET + 0x8000)
#define HFI_PROPERTY_CONFIG_VENC_BLUR_FRAME_SIZE \
(HFI_PROPERTY_CONFIG_COMMON_START + 0x010)
-#define HFI_PROPERTY_CONFIG_VPE_DEINTERLACE \
- (HFI_PROPERTY_CONFIG_VPE_COMMON_START + 0x001)
#define HFI_PROPERTY_CONFIG_VPE_OPERATIONS \
(HFI_PROPERTY_CONFIG_VPE_COMMON_START + 0x002)
diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig
index 9d23692..e1c6f99 100644
--- a/drivers/nfc/Kconfig
+++ b/drivers/nfc/Kconfig
@@ -70,3 +70,11 @@
source "drivers/nfc/s3fwrn5/Kconfig"
source "drivers/nfc/st95hf/Kconfig"
endmenu
+
+config NFC_NQ
+ tristate "QTI NCI based NFC Controller Driver for NQx"
+ depends on I2C
+ help
+ This enables the NFC driver for NQx based devices.
+ This is for i2c connected version. NCI protocol logic
+ resides in the usermode and it has no other NFC dependencies.
diff --git a/drivers/nfc/Makefile b/drivers/nfc/Makefile
index bab8ef0..b691fd4 100644
--- a/drivers/nfc/Makefile
+++ b/drivers/nfc/Makefile
@@ -17,3 +17,4 @@
obj-$(CONFIG_NFC_NXP_NCI) += nxp-nci/
obj-$(CONFIG_NFC_S3FWRN5) += s3fwrn5/
obj-$(CONFIG_NFC_ST95HF) += st95hf/
+obj-$(CONFIG_NFC_NQ) += nq-nci.o
diff --git a/drivers/nfc/nq-nci.c b/drivers/nfc/nq-nci.c
new file mode 100644
index 0000000..baa4f94
--- /dev/null
+++ b/drivers/nfc/nq-nci.c
@@ -0,0 +1,1242 @@
+/* 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
+ * 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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+#include <linux/irq.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/spinlock.h>
+#include <linux/of_gpio.h>
+#include <linux/of_device.h>
+#include <linux/uaccess.h>
+#include "nq-nci.h"
+#include <linux/clk.h>
+#ifdef CONFIG_COMPAT
+#include <linux/compat.h>
+#endif
+
+struct nqx_platform_data {
+ unsigned int irq_gpio;
+ unsigned int en_gpio;
+ unsigned int clkreq_gpio;
+ unsigned int firm_gpio;
+ unsigned int ese_gpio;
+ const char *clk_src_name;
+};
+
+static const struct of_device_id msm_match_table[] = {
+ {.compatible = "qcom,nq-nci"},
+ {}
+};
+
+MODULE_DEVICE_TABLE(of, msm_match_table);
+
+#define MAX_BUFFER_SIZE (320)
+#define WAKEUP_SRC_TIMEOUT (2000)
+#define MAX_RETRY_COUNT 3
+
+struct nqx_dev {
+ wait_queue_head_t read_wq;
+ struct mutex read_mutex;
+ struct i2c_client *client;
+ struct miscdevice nqx_device;
+ union nqx_uinfo nqx_info;
+ /* NFC GPIO variables */
+ unsigned int irq_gpio;
+ unsigned int en_gpio;
+ unsigned int firm_gpio;
+ unsigned int clkreq_gpio;
+ unsigned int ese_gpio;
+ /* NFC VEN pin state powered by Nfc */
+ bool nfc_ven_enabled;
+ /* NFC_IRQ state */
+ bool irq_enabled;
+ /* NFC_IRQ wake-up state */
+ bool irq_wake_up;
+ spinlock_t irq_enabled_lock;
+ unsigned int count_irq;
+ /* Initial CORE RESET notification */
+ unsigned int core_reset_ntf;
+ /* CLK control */
+ bool clk_run;
+ struct clk *s_clk;
+ /* read buffer*/
+ size_t kbuflen;
+ u8 *kbuf;
+ struct nqx_platform_data *pdata;
+};
+
+static int nfcc_reboot(struct notifier_block *notifier, unsigned long val,
+ void *v);
+/*clock enable function*/
+static int nqx_clock_select(struct nqx_dev *nqx_dev);
+/*clock disable function*/
+static int nqx_clock_deselect(struct nqx_dev *nqx_dev);
+static struct notifier_block nfcc_notifier = {
+ .notifier_call = nfcc_reboot,
+ .next = NULL,
+ .priority = 0
+};
+
+unsigned int disable_ctrl;
+
+static void nqx_init_stat(struct nqx_dev *nqx_dev)
+{
+ nqx_dev->count_irq = 0;
+}
+
+static void nqx_disable_irq(struct nqx_dev *nqx_dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&nqx_dev->irq_enabled_lock, flags);
+ if (nqx_dev->irq_enabled) {
+ disable_irq_nosync(nqx_dev->client->irq);
+ nqx_dev->irq_enabled = false;
+ }
+ spin_unlock_irqrestore(&nqx_dev->irq_enabled_lock, flags);
+}
+
+/**
+ * nqx_enable_irq()
+ *
+ * Check if interrupt is enabled or not
+ * and enable interrupt
+ *
+ * Return: void
+ */
+static void nqx_enable_irq(struct nqx_dev *nqx_dev)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&nqx_dev->irq_enabled_lock, flags);
+ if (!nqx_dev->irq_enabled) {
+ nqx_dev->irq_enabled = true;
+ enable_irq(nqx_dev->client->irq);
+ }
+ spin_unlock_irqrestore(&nqx_dev->irq_enabled_lock, flags);
+}
+
+static irqreturn_t nqx_dev_irq_handler(int irq, void *dev_id)
+{
+ struct nqx_dev *nqx_dev = dev_id;
+ unsigned long flags;
+
+ if (device_may_wakeup(&nqx_dev->client->dev))
+ pm_wakeup_event(&nqx_dev->client->dev, WAKEUP_SRC_TIMEOUT);
+
+ nqx_disable_irq(nqx_dev);
+ spin_lock_irqsave(&nqx_dev->irq_enabled_lock, flags);
+ nqx_dev->count_irq++;
+ spin_unlock_irqrestore(&nqx_dev->irq_enabled_lock, flags);
+ wake_up(&nqx_dev->read_wq);
+
+ return IRQ_HANDLED;
+}
+
+static ssize_t nfc_read(struct file *filp, char __user *buf,
+ size_t count, loff_t *offset)
+{
+ struct nqx_dev *nqx_dev = filp->private_data;
+ unsigned char *tmp = NULL;
+ int ret;
+ int irq_gpio_val = 0;
+
+ if (!nqx_dev) {
+ ret = -ENODEV;
+ goto out;
+ }
+
+ if (count > nqx_dev->kbuflen)
+ count = nqx_dev->kbuflen;
+
+ dev_dbg(&nqx_dev->client->dev, "%s : reading %zu bytes.\n",
+ __func__, count);
+
+ mutex_lock(&nqx_dev->read_mutex);
+
+ irq_gpio_val = gpio_get_value(nqx_dev->irq_gpio);
+ if (irq_gpio_val == 0) {
+ if (filp->f_flags & O_NONBLOCK) {
+ dev_err(&nqx_dev->client->dev,
+ ":f_falg has O_NONBLOCK. EAGAIN\n");
+ ret = -EAGAIN;
+ goto err;
+ }
+ while (1) {
+ ret = 0;
+ if (!nqx_dev->irq_enabled) {
+ nqx_dev->irq_enabled = true;
+ enable_irq(nqx_dev->client->irq);
+ }
+ if (!gpio_get_value(nqx_dev->irq_gpio)) {
+ ret = wait_event_interruptible(nqx_dev->read_wq,
+ !nqx_dev->irq_enabled);
+ }
+ if (ret)
+ goto err;
+ nqx_disable_irq(nqx_dev);
+
+ if (gpio_get_value(nqx_dev->irq_gpio))
+ break;
+ dev_err_ratelimited(&nqx_dev->client->dev,
+ "gpio is low, no need to read data\n");
+ }
+ }
+
+ tmp = nqx_dev->kbuf;
+ if (!tmp) {
+ dev_err(&nqx_dev->client->dev,
+ "%s: device doesn't exist anymore\n", __func__);
+ ret = -ENODEV;
+ goto err;
+ }
+ memset(tmp, 0x00, count);
+
+ /* Read data */
+ ret = i2c_master_recv(nqx_dev->client, tmp, count);
+ if (ret < 0) {
+ dev_err(&nqx_dev->client->dev,
+ "%s: i2c_master_recv returned %d\n", __func__, ret);
+ goto err;
+ }
+ if (ret > count) {
+ dev_err(&nqx_dev->client->dev,
+ "%s: received too many bytes from i2c (%d)\n",
+ __func__, ret);
+ ret = -EIO;
+ goto err;
+ }
+#ifdef NFC_KERNEL_BU
+ dev_dbg(&nqx_dev->client->dev, "%s : NfcNciRx %x %x %x\n",
+ __func__, tmp[0], tmp[1], tmp[2]);
+#endif
+ if (copy_to_user(buf, tmp, ret)) {
+ dev_warn(&nqx_dev->client->dev,
+ "%s : failed to copy to user space\n", __func__);
+ ret = -EFAULT;
+ goto err;
+ }
+ mutex_unlock(&nqx_dev->read_mutex);
+ return ret;
+
+err:
+ mutex_unlock(&nqx_dev->read_mutex);
+out:
+ return ret;
+}
+
+static ssize_t nfc_write(struct file *filp, const char __user *buf,
+ size_t count, loff_t *offset)
+{
+ struct nqx_dev *nqx_dev = filp->private_data;
+ char *tmp = NULL;
+ int ret = 0;
+
+ if (!nqx_dev) {
+ ret = -ENODEV;
+ goto out;
+ }
+ if (count > nqx_dev->kbuflen) {
+ dev_err(&nqx_dev->client->dev, "%s: out of memory\n",
+ __func__);
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ tmp = memdup_user(buf, count);
+ if (IS_ERR(tmp)) {
+ dev_err(&nqx_dev->client->dev, "%s: memdup_user failed\n",
+ __func__);
+ ret = PTR_ERR(tmp);
+ goto out;
+ }
+
+ ret = i2c_master_send(nqx_dev->client, tmp, count);
+ if (ret != count) {
+ dev_err(&nqx_dev->client->dev,
+ "%s: failed to write %d\n", __func__, ret);
+ ret = -EIO;
+ goto out_free;
+ }
+#ifdef NFC_KERNEL_BU
+ dev_dbg(&nqx_dev->client->dev,
+ "%s : i2c-%d: NfcNciTx %x %x %x\n",
+ __func__, iminor(file_inode(filp)),
+ tmp[0], tmp[1], tmp[2]);
+#endif
+ usleep_range(1000, 1100);
+out_free:
+ kfree(tmp);
+out:
+ return ret;
+}
+
+/**
+ * nqx_standby_write()
+ * @buf: pointer to data buffer
+ * @len: # of bytes need to transfer
+ *
+ * write data buffer over I2C and retry
+ * if NFCC is in stand by mode
+ *
+ * Return: # of bytes written or -ve value in case of error
+ */
+static int nqx_standby_write(struct nqx_dev *nqx_dev,
+ const unsigned char *buf, size_t len)
+{
+ int ret = -EINVAL;
+ int retry_cnt;
+
+ for (retry_cnt = 1; retry_cnt <= MAX_RETRY_COUNT; retry_cnt++) {
+ ret = i2c_master_send(nqx_dev->client, buf, len);
+ if (ret < 0) {
+ dev_err(&nqx_dev->client->dev,
+ "%s: write failed, Maybe in Standby Mode - Retry(%d)\n",
+ __func__, retry_cnt);
+ usleep_range(1000, 1100);
+ } else if (ret == len)
+ break;
+ }
+ return ret;
+}
+
+/*
+ * Power management of the eSE
+ * NFC & eSE ON : NFC_EN high and eSE_pwr_req high.
+ * NFC OFF & eSE ON : NFC_EN high and eSE_pwr_req high.
+ * NFC OFF & eSE OFF : NFC_EN low and eSE_pwr_req low.
+ */
+static int nqx_ese_pwr(struct nqx_dev *nqx_dev, unsigned long int arg)
+{
+ int r = -1;
+ const unsigned char svdd_off_cmd_warn[] = {0x2F, 0x31, 0x01, 0x01};
+ const unsigned char svdd_off_cmd_done[] = {0x2F, 0x31, 0x01, 0x00};
+
+ if (!gpio_is_valid(nqx_dev->ese_gpio)) {
+ dev_err(&nqx_dev->client->dev,
+ "%s: ese_gpio is not valid\n", __func__);
+ return -EINVAL;
+ }
+
+ if (arg == 0) {
+ /*
+ * We want to power on the eSE and to do so we need the
+ * eSE_pwr_req pin and the NFC_EN pin to be high
+ */
+ if (gpio_get_value(nqx_dev->ese_gpio)) {
+ dev_dbg(&nqx_dev->client->dev, "ese_gpio is already high\n");
+ r = 0;
+ } else {
+ /**
+ * Let's store the NFC_EN pin state
+ * only if the eSE is not yet on
+ */
+ nqx_dev->nfc_ven_enabled =
+ gpio_get_value(nqx_dev->en_gpio);
+ if (!nqx_dev->nfc_ven_enabled) {
+ gpio_set_value(nqx_dev->en_gpio, 1);
+ /* hardware dependent delay */
+ usleep_range(1000, 1100);
+ }
+ gpio_set_value(nqx_dev->ese_gpio, 1);
+ if (gpio_get_value(nqx_dev->ese_gpio)) {
+ dev_dbg(&nqx_dev->client->dev, "ese_gpio is enabled\n");
+ r = 0;
+ }
+ }
+ } else if (arg == 1) {
+ if (nqx_dev->nfc_ven_enabled &&
+ ((nqx_dev->nqx_info.info.chip_type == NFCC_NQ_220) ||
+ (nqx_dev->nqx_info.info.chip_type == NFCC_PN66T))) {
+ /**
+ * Let's inform the CLF we're
+ * powering off the eSE
+ */
+ r = nqx_standby_write(nqx_dev, svdd_off_cmd_warn,
+ sizeof(svdd_off_cmd_warn));
+ if (r < 0) {
+ dev_err(&nqx_dev->client->dev,
+ "%s: write failed after max retry\n",
+ __func__);
+ return -ENXIO;
+ }
+ dev_dbg(&nqx_dev->client->dev,
+ "%s: svdd_off_cmd_warn sent\n", __func__);
+
+ /* let's power down the eSE */
+ gpio_set_value(nqx_dev->ese_gpio, 0);
+ dev_dbg(&nqx_dev->client->dev,
+ "%s: nqx_dev->ese_gpio set to 0\n", __func__);
+
+ /**
+ * Time needed for the SVDD capacitor
+ * to get discharged
+ */
+ usleep_range(8000, 8100);
+
+ /* Let's inform the CLF the eSE is now off */
+ r = nqx_standby_write(nqx_dev, svdd_off_cmd_done,
+ sizeof(svdd_off_cmd_done));
+ if (r < 0) {
+ dev_err(&nqx_dev->client->dev,
+ "%s: write failed after max retry\n",
+ __func__);
+ return -ENXIO;
+ }
+ dev_dbg(&nqx_dev->client->dev,
+ "%s: svdd_off_cmd_done sent\n", __func__);
+ } else {
+ /**
+ * In case the NFC is off,
+ * there's no need to send the i2c commands
+ */
+ gpio_set_value(nqx_dev->ese_gpio, 0);
+ }
+
+ if (!gpio_get_value(nqx_dev->ese_gpio)) {
+ dev_dbg(&nqx_dev->client->dev, "ese_gpio is disabled\n");
+ r = 0;
+ }
+
+ if (!nqx_dev->nfc_ven_enabled) {
+ /* hardware dependent delay */
+ usleep_range(1000, 1100);
+ dev_dbg(&nqx_dev->client->dev, "disabling en_gpio\n");
+ gpio_set_value(nqx_dev->en_gpio, 0);
+ }
+ } else if (arg == 3) {
+ r = gpio_get_value(nqx_dev->ese_gpio);
+ }
+ return r;
+}
+
+static int nfc_open(struct inode *inode, struct file *filp)
+{
+ int ret = 0;
+ struct nqx_dev *nqx_dev = container_of(filp->private_data,
+ struct nqx_dev, nqx_device);
+
+ filp->private_data = nqx_dev;
+ nqx_init_stat(nqx_dev);
+
+ dev_dbg(&nqx_dev->client->dev,
+ "%s: %d,%d\n", __func__, imajor(inode), iminor(inode));
+ return ret;
+}
+
+/*
+ * nfc_ioctl_power_states() - power control
+ * @filp: pointer to the file descriptor
+ * @arg: mode that we want to move to
+ *
+ * Device power control. Depending on the arg value, device moves to
+ * different states
+ * (arg = 0): NFC_ENABLE GPIO = 0, FW_DL GPIO = 0
+ * (arg = 1): NFC_ENABLE GPIO = 1, FW_DL GPIO = 0
+ * (arg = 2): FW_DL GPIO = 1
+ *
+ * Return: -ENOIOCTLCMD if arg is not supported, 0 in any other case
+ */
+int nfc_ioctl_power_states(struct file *filp, unsigned long arg)
+{
+ int r = 0;
+ struct nqx_dev *nqx_dev = filp->private_data;
+
+ if (arg == 0) {
+ /*
+ * We are attempting a hardware reset so let us disable
+ * interrupts to avoid spurious notifications to upper
+ * layers.
+ */
+ nqx_disable_irq(nqx_dev);
+ dev_dbg(&nqx_dev->client->dev,
+ "gpio_set_value disable: %s: info: %p\n",
+ __func__, nqx_dev);
+ if (gpio_is_valid(nqx_dev->firm_gpio))
+ gpio_set_value(nqx_dev->firm_gpio, 0);
+
+ if (gpio_is_valid(nqx_dev->ese_gpio)) {
+ if (!gpio_get_value(nqx_dev->ese_gpio)) {
+ dev_dbg(&nqx_dev->client->dev, "disabling en_gpio\n");
+ gpio_set_value(nqx_dev->en_gpio, 0);
+ } else {
+ dev_dbg(&nqx_dev->client->dev, "keeping en_gpio high\n");
+ }
+ } else {
+ dev_dbg(&nqx_dev->client->dev, "ese_gpio invalid, set en_gpio to low\n");
+ gpio_set_value(nqx_dev->en_gpio, 0);
+ }
+ r = nqx_clock_deselect(nqx_dev);
+ if (r < 0)
+ dev_err(&nqx_dev->client->dev, "unable to disable clock\n");
+ nqx_dev->nfc_ven_enabled = false;
+ /* hardware dependent delay */
+ msleep(100);
+ } else if (arg == 1) {
+ nqx_enable_irq(nqx_dev);
+ dev_dbg(&nqx_dev->client->dev,
+ "gpio_set_value enable: %s: info: %p\n",
+ __func__, nqx_dev);
+ if (gpio_is_valid(nqx_dev->firm_gpio))
+ gpio_set_value(nqx_dev->firm_gpio, 0);
+ gpio_set_value(nqx_dev->en_gpio, 1);
+ r = nqx_clock_select(nqx_dev);
+ if (r < 0)
+ dev_err(&nqx_dev->client->dev, "unable to enable clock\n");
+ nqx_dev->nfc_ven_enabled = true;
+ msleep(20);
+ } else if (arg == 2) {
+ /*
+ * We are switching to Dowload Mode, toggle the enable pin
+ * in order to set the NFCC in the new mode
+ */
+ if (gpio_is_valid(nqx_dev->ese_gpio)) {
+ if (gpio_get_value(nqx_dev->ese_gpio)) {
+ dev_err(&nqx_dev->client->dev,
+ "FW download forbidden while ese is on\n");
+ return -EBUSY; /* Device or resource busy */
+ }
+ }
+ gpio_set_value(nqx_dev->en_gpio, 1);
+ msleep(20);
+ if (gpio_is_valid(nqx_dev->firm_gpio))
+ gpio_set_value(nqx_dev->firm_gpio, 1);
+ msleep(20);
+ gpio_set_value(nqx_dev->en_gpio, 0);
+ msleep(100);
+ gpio_set_value(nqx_dev->en_gpio, 1);
+ msleep(20);
+ } else {
+ r = -ENOIOCTLCMD;
+ }
+
+ return r;
+}
+
+#ifdef CONFIG_COMPAT
+static long nfc_compat_ioctl(struct file *pfile, unsigned int cmd,
+ unsigned long arg)
+{
+ long r = 0;
+
+ arg = (compat_u64)arg;
+ switch (cmd) {
+ case NFC_SET_PWR:
+ nfc_ioctl_power_states(pfile, arg);
+ break;
+ case ESE_SET_PWR:
+ nqx_ese_pwr(pfile->private_data, arg);
+ break;
+ case ESE_GET_PWR:
+ nqx_ese_pwr(pfile->private_data, 3);
+ break;
+ case SET_RX_BLOCK:
+ break;
+ case SET_EMULATOR_TEST_POINT:
+ break;
+ default:
+ r = -ENOTTY;
+ }
+ return r;
+}
+#endif
+
+/*
+ * nfc_ioctl_core_reset_ntf()
+ * @filp: pointer to the file descriptor
+ *
+ * Allows callers to determine if a CORE_RESET_NTF has arrived
+ *
+ * Return: the value of variable core_reset_ntf
+ */
+int nfc_ioctl_core_reset_ntf(struct file *filp)
+{
+ struct nqx_dev *nqx_dev = filp->private_data;
+
+ dev_dbg(&nqx_dev->client->dev, "%s: returning = %d\n", __func__,
+ nqx_dev->core_reset_ntf);
+ return nqx_dev->core_reset_ntf;
+}
+
+/*
+ * Inside nfc_ioctl_nfcc_info
+ *
+ * @brief nfc_ioctl_nfcc_info
+ *
+ * Check the NQ Chipset and firmware version details
+ */
+unsigned int nfc_ioctl_nfcc_info(struct file *filp, unsigned long arg)
+{
+ unsigned int r = 0;
+ struct nqx_dev *nqx_dev = filp->private_data;
+
+ r = nqx_dev->nqx_info.i;
+ dev_dbg(&nqx_dev->client->dev,
+ "nqx nfc : nfc_ioctl_nfcc_info r = %d\n", r);
+
+ return r;
+}
+
+static long nfc_ioctl(struct file *pfile, unsigned int cmd,
+ unsigned long arg)
+{
+ int r = 0;
+
+ switch (cmd) {
+ case NFC_SET_PWR:
+ r = nfc_ioctl_power_states(pfile, arg);
+ break;
+ case ESE_SET_PWR:
+ r = nqx_ese_pwr(pfile->private_data, arg);
+ break;
+ case ESE_GET_PWR:
+ r = nqx_ese_pwr(pfile->private_data, 3);
+ break;
+ case SET_RX_BLOCK:
+ break;
+ case SET_EMULATOR_TEST_POINT:
+ break;
+ case NFCC_INITIAL_CORE_RESET_NTF:
+ r = nfc_ioctl_core_reset_ntf(pfile);
+ break;
+ case NFCC_GET_INFO:
+ r = nfc_ioctl_nfcc_info(pfile, arg);
+ break;
+ default:
+ r = -ENOIOCTLCMD;
+ }
+ return r;
+}
+
+static const struct file_operations nfc_dev_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .read = nfc_read,
+ .write = nfc_write,
+ .open = nfc_open,
+ .unlocked_ioctl = nfc_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = nfc_compat_ioctl
+#endif
+};
+
+/* Check for availability of NQ_ NFC controller hardware */
+static int nfcc_hw_check(struct i2c_client *client, struct nqx_dev *nqx_dev)
+{
+ int ret = 0;
+
+ unsigned char raw_nci_reset_cmd[] = {0x20, 0x00, 0x01, 0x00};
+ unsigned char raw_nci_init_cmd[] = {0x20, 0x01, 0x00};
+ unsigned char nci_init_rsp[28];
+ unsigned char nci_reset_rsp[6];
+ unsigned char init_rsp_len = 0;
+ unsigned int enable_gpio = nqx_dev->en_gpio;
+ /* making sure that the NFCC starts in a clean state. */
+ gpio_set_value(enable_gpio, 0);/* ULPM: Disable */
+ /* hardware dependent delay */
+ msleep(20);
+ gpio_set_value(enable_gpio, 1);/* HPD : Enable*/
+ /* hardware dependent delay */
+ msleep(20);
+
+ /* send NCI CORE RESET CMD with Keep Config parameters */
+ ret = i2c_master_send(client, raw_nci_reset_cmd,
+ sizeof(raw_nci_reset_cmd));
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "%s: - i2c_master_send Error\n", __func__);
+ goto err_nfcc_hw_check;
+ }
+ /* hardware dependent delay */
+ msleep(30);
+
+ /* Read Response of RESET command */
+ ret = i2c_master_recv(client, nci_reset_rsp,
+ sizeof(nci_reset_rsp));
+ dev_err(&client->dev,
+ "%s: - nq - reset cmd answer : NfcNciRx %x %x %x\n",
+ __func__, nci_reset_rsp[0],
+ nci_reset_rsp[1], nci_reset_rsp[2]);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "%s: - i2c_master_recv Error\n", __func__);
+ goto err_nfcc_hw_check;
+ }
+ ret = i2c_master_send(client, raw_nci_init_cmd,
+ sizeof(raw_nci_init_cmd));
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "%s: - i2c_master_send Error\n", __func__);
+ goto err_nfcc_hw_check;
+ }
+ /* hardware dependent delay */
+ msleep(30);
+ /* Read Response of INIT command */
+ ret = i2c_master_recv(client, nci_init_rsp,
+ sizeof(nci_init_rsp));
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "%s: - i2c_master_recv Error\n", __func__);
+ goto err_nfcc_hw_check;
+ }
+ init_rsp_len = 2 + nci_init_rsp[2]; /*payload + len*/
+ if (init_rsp_len > PAYLOAD_HEADER_LENGTH) {
+ nqx_dev->nqx_info.info.chip_type =
+ nci_init_rsp[init_rsp_len - 3];
+ nqx_dev->nqx_info.info.rom_version =
+ nci_init_rsp[init_rsp_len - 2];
+ nqx_dev->nqx_info.info.fw_major =
+ nci_init_rsp[init_rsp_len - 1];
+ nqx_dev->nqx_info.info.fw_minor =
+ nci_init_rsp[init_rsp_len];
+ }
+ dev_dbg(&nqx_dev->client->dev, "NQ NFCC chip_type = %x\n",
+ nqx_dev->nqx_info.info.chip_type);
+ dev_dbg(&nqx_dev->client->dev, "NQ fw version = %x.%x.%x\n",
+ nqx_dev->nqx_info.info.rom_version,
+ nqx_dev->nqx_info.info.fw_major,
+ nqx_dev->nqx_info.info.fw_minor);
+
+ switch (nqx_dev->nqx_info.info.chip_type) {
+ case NFCC_NQ_210:
+ dev_dbg(&client->dev,
+ "%s: ## NFCC == NQ210 ##\n", __func__);
+ break;
+ case NFCC_NQ_220:
+ dev_dbg(&client->dev,
+ "%s: ## NFCC == NQ220 ##\n", __func__);
+ break;
+ case NFCC_NQ_310:
+ dev_dbg(&client->dev,
+ "%s: ## NFCC == NQ310 ##\n", __func__);
+ break;
+ case NFCC_NQ_330:
+ dev_dbg(&client->dev,
+ "%s: ## NFCC == NQ330 ##\n", __func__);
+ break;
+ case NFCC_PN66T:
+ dev_dbg(&client->dev,
+ "%s: ## NFCC == PN66T ##\n", __func__);
+ break;
+ default:
+ dev_err(&client->dev,
+ "%s: - NFCC HW not Supported\n", __func__);
+ break;
+ }
+
+ /*Disable NFC by default to save power on boot*/
+ gpio_set_value(enable_gpio, 0);/* ULPM: Disable */
+ ret = 0;
+ goto done;
+
+err_nfcc_hw_check:
+ ret = -ENXIO;
+ dev_err(&client->dev,
+ "%s: - NFCC HW not available\n", __func__);
+done:
+ return ret;
+}
+
+/*
+ * Routine to enable clock.
+ * this routine can be extended to select from multiple
+ * sources based on clk_src_name.
+ */
+static int nqx_clock_select(struct nqx_dev *nqx_dev)
+{
+ int r = 0;
+
+ nqx_dev->s_clk = clk_get(&nqx_dev->client->dev, "ref_clk");
+
+ if (nqx_dev->s_clk == NULL)
+ goto err_clk;
+
+ if (nqx_dev->clk_run == false)
+ r = clk_prepare_enable(nqx_dev->s_clk);
+
+ if (r)
+ goto err_clk;
+
+ nqx_dev->clk_run = true;
+
+ return r;
+
+err_clk:
+ r = -1;
+ return r;
+}
+
+/*
+ * Routine to disable clocks
+ */
+static int nqx_clock_deselect(struct nqx_dev *nqx_dev)
+{
+ int r = -1;
+
+ if (nqx_dev->s_clk != NULL) {
+ if (nqx_dev->clk_run == true) {
+ clk_disable_unprepare(nqx_dev->s_clk);
+ nqx_dev->clk_run = false;
+ }
+ return 0;
+ }
+ return r;
+}
+
+static int nfc_parse_dt(struct device *dev, struct nqx_platform_data *pdata)
+{
+ int r = 0;
+ struct device_node *np = dev->of_node;
+
+ pdata->en_gpio = of_get_named_gpio(np, "qcom,nq-ven", 0);
+ if ((!gpio_is_valid(pdata->en_gpio)))
+ return -EINVAL;
+ disable_ctrl = pdata->en_gpio;
+
+ pdata->irq_gpio = of_get_named_gpio(np, "qcom,nq-irq", 0);
+ if ((!gpio_is_valid(pdata->irq_gpio)))
+ return -EINVAL;
+
+ pdata->firm_gpio = of_get_named_gpio(np, "qcom,nq-firm", 0);
+ if (!gpio_is_valid(pdata->firm_gpio)) {
+ dev_warn(dev,
+ "FIRM GPIO <OPTIONAL> error getting from OF node\n");
+ pdata->firm_gpio = -EINVAL;
+ }
+
+ pdata->ese_gpio = of_get_named_gpio(np, "qcom,nq-esepwr", 0);
+ if (!gpio_is_valid(pdata->ese_gpio)) {
+ dev_warn(dev,
+ "ese GPIO <OPTIONAL> error getting from OF node\n");
+ pdata->ese_gpio = -EINVAL;
+ }
+
+ r = of_property_read_string(np, "qcom,clk-src", &pdata->clk_src_name);
+
+ pdata->clkreq_gpio = of_get_named_gpio(np, "qcom,nq-clkreq", 0);
+
+ if (r)
+ return -EINVAL;
+ return r;
+}
+
+static inline int gpio_input_init(const struct device * const dev,
+ const int gpio, const char * const gpio_name)
+{
+ int r = gpio_request(gpio, gpio_name);
+
+ if (r) {
+ dev_err(dev, "unable to request gpio [%d]\n", gpio);
+ return r;
+ }
+
+ r = gpio_direction_input(gpio);
+ if (r)
+ dev_err(dev, "unable to set direction for gpio [%d]\n", gpio);
+
+ return r;
+}
+
+static int nqx_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int r = 0;
+ int irqn = 0;
+ struct nqx_platform_data *platform_data;
+ struct nqx_dev *nqx_dev;
+
+ dev_dbg(&client->dev, "%s: enter\n", __func__);
+ if (client->dev.of_node) {
+ platform_data = devm_kzalloc(&client->dev,
+ sizeof(struct nqx_platform_data), GFP_KERNEL);
+ if (!platform_data) {
+ r = -ENOMEM;
+ goto err_platform_data;
+ }
+ r = nfc_parse_dt(&client->dev, platform_data);
+ if (r)
+ goto err_free_data;
+ } else
+ platform_data = client->dev.platform_data;
+
+ dev_dbg(&client->dev,
+ "%s, inside nfc-nci flags = %x\n",
+ __func__, client->flags);
+
+ if (platform_data == NULL) {
+ dev_err(&client->dev, "%s: failed\n", __func__);
+ r = -ENODEV;
+ goto err_platform_data;
+ }
+ if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) {
+ dev_err(&client->dev, "%s: need I2C_FUNC_I2C\n", __func__);
+ r = -ENODEV;
+ goto err_free_data;
+ }
+ nqx_dev = kzalloc(sizeof(*nqx_dev), GFP_KERNEL);
+ if (nqx_dev == NULL) {
+ r = -ENOMEM;
+ goto err_free_data;
+ }
+ nqx_dev->client = client;
+ nqx_dev->kbuflen = MAX_BUFFER_SIZE;
+ nqx_dev->kbuf = kzalloc(MAX_BUFFER_SIZE, GFP_KERNEL);
+ if (!nqx_dev->kbuf) {
+ dev_err(&client->dev,
+ "failed to allocate memory for nqx_dev->kbuf\n");
+ r = -ENOMEM;
+ goto err_free_dev;
+ }
+
+ if (gpio_is_valid(platform_data->en_gpio)) {
+ r = gpio_request(platform_data->en_gpio, "nfc_reset_gpio");
+ if (r) {
+ dev_err(&client->dev,
+ "%s: unable to request nfc reset gpio [%d]\n",
+ __func__,
+ platform_data->en_gpio);
+ goto err_mem;
+ }
+ r = gpio_direction_output(platform_data->en_gpio, 0);
+ if (r) {
+ dev_err(&client->dev,
+ "%s: unable to set direction for nfc reset gpio [%d]\n",
+ __func__,
+ platform_data->en_gpio);
+ goto err_en_gpio;
+ }
+ } else {
+ dev_err(&client->dev,
+ "%s: nfc reset gpio not provided\n", __func__);
+ goto err_mem;
+ }
+
+ if (gpio_is_valid(platform_data->irq_gpio)) {
+ r = gpio_request(platform_data->irq_gpio, "nfc_irq_gpio");
+ if (r) {
+ dev_err(&client->dev, "%s: unable to request nfc irq gpio [%d]\n",
+ __func__, platform_data->irq_gpio);
+ goto err_en_gpio;
+ }
+ r = gpio_direction_input(platform_data->irq_gpio);
+ if (r) {
+ dev_err(&client->dev,
+ "%s: unable to set direction for nfc irq gpio [%d]\n",
+ __func__,
+ platform_data->irq_gpio);
+ goto err_irq_gpio;
+ }
+ irqn = gpio_to_irq(platform_data->irq_gpio);
+ if (irqn < 0) {
+ r = irqn;
+ goto err_irq_gpio;
+ }
+ client->irq = irqn;
+ } else {
+ dev_err(&client->dev, "%s: irq gpio not provided\n", __func__);
+ goto err_en_gpio;
+ }
+ if (gpio_is_valid(platform_data->firm_gpio)) {
+ r = gpio_request(platform_data->firm_gpio,
+ "nfc_firm_gpio");
+ if (r) {
+ dev_err(&client->dev,
+ "%s: unable to request nfc firmware gpio [%d]\n",
+ __func__, platform_data->firm_gpio);
+ goto err_irq_gpio;
+ }
+ r = gpio_direction_output(platform_data->firm_gpio, 0);
+ if (r) {
+ dev_err(&client->dev,
+ "%s: cannot set direction for nfc firmware gpio [%d]\n",
+ __func__, platform_data->firm_gpio);
+ goto err_firm_gpio;
+ }
+ } else {
+ dev_err(&client->dev,
+ "%s: firm gpio not provided\n", __func__);
+ goto err_irq_gpio;
+ }
+ if (gpio_is_valid(platform_data->ese_gpio)) {
+ r = gpio_request(platform_data->ese_gpio,
+ "nfc-ese_pwr");
+ if (r) {
+ nqx_dev->ese_gpio = -EINVAL;
+ dev_err(&client->dev,
+ "%s: unable to request nfc ese gpio [%d]\n",
+ __func__, platform_data->ese_gpio);
+ /* ese gpio optional so we should continue */
+ } else {
+ nqx_dev->ese_gpio = platform_data->ese_gpio;
+ r = gpio_direction_output(platform_data->ese_gpio, 0);
+ if (r) {
+ /*
+ * free ese gpio and set invalid
+ * to avoid further use
+ */
+ gpio_free(platform_data->ese_gpio);
+ nqx_dev->ese_gpio = -EINVAL;
+ dev_err(&client->dev,
+ "%s: cannot set direction for nfc ese gpio [%d]\n",
+ __func__, platform_data->ese_gpio);
+ /* ese gpio optional so we should continue */
+ }
+ }
+ } else {
+ nqx_dev->ese_gpio = -EINVAL;
+ dev_err(&client->dev,
+ "%s: ese gpio not provided\n", __func__);
+ /* ese gpio optional so we should continue */
+ }
+ if (gpio_is_valid(platform_data->clkreq_gpio)) {
+ r = gpio_request(platform_data->clkreq_gpio,
+ "nfc_clkreq_gpio");
+ if (r) {
+ dev_err(&client->dev,
+ "%s: unable to request nfc clkreq gpio [%d]\n",
+ __func__, platform_data->clkreq_gpio);
+ goto err_ese_gpio;
+ }
+ r = gpio_direction_input(platform_data->clkreq_gpio);
+ if (r) {
+ dev_err(&client->dev,
+ "%s: cannot set direction for nfc clkreq gpio [%d]\n",
+ __func__, platform_data->clkreq_gpio);
+ goto err_clkreq_gpio;
+ }
+ } else {
+ dev_err(&client->dev,
+ "%s: clkreq gpio not provided\n", __func__);
+ goto err_ese_gpio;
+ }
+
+ nqx_dev->en_gpio = platform_data->en_gpio;
+ nqx_dev->irq_gpio = platform_data->irq_gpio;
+ nqx_dev->firm_gpio = platform_data->firm_gpio;
+ nqx_dev->clkreq_gpio = platform_data->clkreq_gpio;
+ nqx_dev->pdata = platform_data;
+
+ /* init mutex and queues */
+ init_waitqueue_head(&nqx_dev->read_wq);
+ mutex_init(&nqx_dev->read_mutex);
+ spin_lock_init(&nqx_dev->irq_enabled_lock);
+
+ nqx_dev->nqx_device.minor = MISC_DYNAMIC_MINOR;
+ nqx_dev->nqx_device.name = "nq-nci";
+ nqx_dev->nqx_device.fops = &nfc_dev_fops;
+
+ r = misc_register(&nqx_dev->nqx_device);
+ if (r) {
+ dev_err(&client->dev, "%s: misc_register failed\n", __func__);
+ goto err_misc_register;
+ }
+
+ /* NFC_INT IRQ */
+ nqx_dev->irq_enabled = true;
+ r = request_irq(client->irq, nqx_dev_irq_handler,
+ IRQF_TRIGGER_HIGH, client->name, nqx_dev);
+ if (r) {
+ dev_err(&client->dev, "%s: request_irq failed\n", __func__);
+ goto err_request_irq_failed;
+ }
+ nqx_disable_irq(nqx_dev);
+
+ /*
+ * To be efficient we need to test whether nfcc hardware is physically
+ * present before attempting further hardware initialisation.
+ *
+ */
+ r = nfcc_hw_check(client, nqx_dev);
+ if (r) {
+ /* make sure NFCC is not enabled */
+ gpio_set_value(platform_data->en_gpio, 0);
+ /* We don't think there is hardware switch NFC OFF */
+ goto err_request_hw_check_failed;
+ }
+
+ /* Register reboot notifier here */
+ r = register_reboot_notifier(&nfcc_notifier);
+ if (r) {
+ dev_err(&client->dev,
+ "%s: cannot register reboot notifier(err = %d)\n",
+ __func__, r);
+ /*
+ * nfcc_hw_check function not doing memory
+ * allocation so using same goto target here
+ */
+ goto err_request_hw_check_failed;
+ }
+
+#ifdef NFC_KERNEL_BU
+ r = nqx_clock_select(nqx_dev);
+ if (r < 0) {
+ dev_err(&client->dev,
+ "%s: nqx_clock_select failed\n", __func__);
+ goto err_clock_en_failed;
+ }
+ gpio_set_value(platform_data->en_gpio, 1);
+#endif
+ device_init_wakeup(&client->dev, true);
+ device_set_wakeup_capable(&client->dev, true);
+ i2c_set_clientdata(client, nqx_dev);
+ nqx_dev->irq_wake_up = false;
+
+ dev_err(&client->dev,
+ "%s: probing NFCC NQxxx exited successfully\n",
+ __func__);
+ return 0;
+
+#ifdef NFC_KERNEL_BU
+err_clock_en_failed:
+ unregister_reboot_notifier(&nfcc_notifier);
+#endif
+err_request_hw_check_failed:
+ free_irq(client->irq, nqx_dev);
+err_request_irq_failed:
+ misc_deregister(&nqx_dev->nqx_device);
+err_misc_register:
+ mutex_destroy(&nqx_dev->read_mutex);
+err_clkreq_gpio:
+ gpio_free(platform_data->clkreq_gpio);
+err_ese_gpio:
+ /* optional gpio, not sure was configured in probe */
+ if (nqx_dev->ese_gpio > 0)
+ gpio_free(platform_data->ese_gpio);
+err_firm_gpio:
+ gpio_free(platform_data->firm_gpio);
+err_irq_gpio:
+ gpio_free(platform_data->irq_gpio);
+err_en_gpio:
+ gpio_free(platform_data->en_gpio);
+err_mem:
+ kfree(nqx_dev->kbuf);
+err_free_dev:
+ kfree(nqx_dev);
+err_free_data:
+ if (client->dev.of_node)
+ devm_kfree(&client->dev, platform_data);
+err_platform_data:
+ dev_err(&client->dev,
+ "%s: probing nqxx failed, check hardware\n",
+ __func__);
+ return r;
+}
+
+static int nqx_remove(struct i2c_client *client)
+{
+ int ret = 0;
+ struct nqx_dev *nqx_dev;
+
+ nqx_dev = i2c_get_clientdata(client);
+ if (!nqx_dev) {
+ dev_err(&client->dev,
+ "%s: device doesn't exist anymore\n", __func__);
+ ret = -ENODEV;
+ goto err;
+ }
+
+ unregister_reboot_notifier(&nfcc_notifier);
+ free_irq(client->irq, nqx_dev);
+ misc_deregister(&nqx_dev->nqx_device);
+ mutex_destroy(&nqx_dev->read_mutex);
+ gpio_free(nqx_dev->clkreq_gpio);
+ /* optional gpio, not sure was configured in probe */
+ if (nqx_dev->ese_gpio > 0)
+ gpio_free(nqx_dev->ese_gpio);
+ gpio_free(nqx_dev->firm_gpio);
+ gpio_free(nqx_dev->irq_gpio);
+ gpio_free(nqx_dev->en_gpio);
+ kfree(nqx_dev->kbuf);
+ if (client->dev.of_node)
+ devm_kfree(&client->dev, nqx_dev->pdata);
+
+ kfree(nqx_dev);
+err:
+ return ret;
+}
+
+static int nqx_suspend(struct device *device)
+{
+ struct i2c_client *client = to_i2c_client(device);
+ struct nqx_dev *nqx_dev = i2c_get_clientdata(client);
+
+ if (device_may_wakeup(&client->dev) && nqx_dev->irq_enabled) {
+ if (!enable_irq_wake(client->irq))
+ nqx_dev->irq_wake_up = true;
+ }
+ return 0;
+}
+
+static int nqx_resume(struct device *device)
+{
+ struct i2c_client *client = to_i2c_client(device);
+ struct nqx_dev *nqx_dev = i2c_get_clientdata(client);
+
+ if (device_may_wakeup(&client->dev) && nqx_dev->irq_wake_up) {
+ if (!disable_irq_wake(client->irq))
+ nqx_dev->irq_wake_up = false;
+ }
+ return 0;
+}
+
+static const struct i2c_device_id nqx_id[] = {
+ {"nqx-i2c", 0},
+ {}
+};
+
+static const struct dev_pm_ops nfc_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(nqx_suspend, nqx_resume)
+};
+
+static struct i2c_driver nqx = {
+ .id_table = nqx_id,
+ .probe = nqx_probe,
+ .remove = nqx_remove,
+ .driver = {
+ .owner = THIS_MODULE,
+ .name = "nq-nci",
+ .of_match_table = msm_match_table,
+ .pm = &nfc_pm_ops,
+ },
+};
+
+static int nfcc_reboot(struct notifier_block *notifier, unsigned long val,
+ void *v)
+{
+ gpio_set_value(disable_ctrl, 1);
+ return NOTIFY_OK;
+}
+
+/*
+ * module load/unload record keeping
+ */
+static int __init nqx_dev_init(void)
+{
+ return i2c_add_driver(&nqx);
+}
+module_init(nqx_dev_init);
+
+static void __exit nqx_dev_exit(void)
+{
+ unregister_reboot_notifier(&nfcc_notifier);
+ i2c_del_driver(&nqx);
+}
+module_exit(nqx_dev_exit);
+
+MODULE_DESCRIPTION("NFC nqx");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/nfc/nq-nci.h b/drivers/nfc/nq-nci.h
new file mode 100644
index 0000000..87715c2
--- /dev/null
+++ b/drivers/nfc/nq-nci.h
@@ -0,0 +1,54 @@
+/* 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
+ * 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 __NQ_NCI_H
+#define __NQ_NCI_H
+
+#include <linux/i2c.h>
+#include <linux/types.h>
+#include <linux/version.h>
+
+#include <linux/semaphore.h>
+#include <linux/completion.h>
+
+#include <linux/ioctl.h>
+#include <linux/miscdevice.h>
+#include <linux/nfcinfo.h>
+
+#define NFC_SET_PWR _IOW(0xE9, 0x01, unsigned int)
+#define ESE_SET_PWR _IOW(0xE9, 0x02, unsigned int)
+#define ESE_GET_PWR _IOR(0xE9, 0x03, unsigned int)
+#define SET_RX_BLOCK _IOW(0xE9, 0x04, unsigned int)
+#define SET_EMULATOR_TEST_POINT _IOW(0xE9, 0x05, unsigned int)
+#define NFCC_INITIAL_CORE_RESET_NTF _IOW(0xE9, 0x10, unsigned int)
+
+#define NFC_RX_BUFFER_CNT_START (0x0)
+#define PAYLOAD_HEADER_LENGTH (0x3)
+#define PAYLOAD_LENGTH_MAX (256)
+#define BYTE (0x8)
+#define NCI_IDENTIFIER (0x10)
+
+enum nfcc_initial_core_reset_ntf {
+ TIMEDOUT_INITIAL_CORE_RESET_NTF = 0, /* 0*/
+ ARRIVED_INITIAL_CORE_RESET_NTF, /* 1 */
+ DEFAULT_INITIAL_CORE_RESET_NTF, /*2*/
+};
+
+enum nfcc_chip_variant {
+ NFCC_NQ_210 = 0x48, /**< NFCC NQ210 */
+ NFCC_NQ_220 = 0x58, /**< NFCC NQ220 */
+ NFCC_NQ_310 = 0x40, /**< NFCC NQ310 */
+ NFCC_NQ_330 = 0x51, /**< NFCC NQ330 */
+ NFCC_PN66T = 0x18, /**< NFCC PN66T */
+ NFCC_NOT_SUPPORTED = 0xFF /**< NFCC is not supported */
+};
+#endif
diff --git a/drivers/platform/msm/ipa/ipa_v2/rmnet_ipa.c b/drivers/platform/msm/ipa/ipa_v2/rmnet_ipa.c
index 4672233..bcd602c 100644
--- a/drivers/platform/msm/ipa/ipa_v2/rmnet_ipa.c
+++ b/drivers/platform/msm/ipa/ipa_v2/rmnet_ipa.c
@@ -80,6 +80,7 @@ static void *subsys_notify_handle;
u32 apps_to_ipa_hdl, ipa_to_apps_hdl; /* get handler from ipa */
static struct mutex ipa_to_apps_pipe_handle_guard;
+static struct mutex add_mux_channel_lock;
static int wwan_add_ul_flt_rule_to_ipa(void);
static int wwan_del_ul_flt_rule_to_ipa(void);
static void ipa_wwan_msg_free_cb(void*, u32, u32);
@@ -1527,9 +1528,11 @@ static int ipa_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
rmnet_mux_val.mux_id);
return rc;
}
+ mutex_lock(&add_mux_channel_lock);
if (rmnet_index >= MAX_NUM_OF_MUX_CHANNEL) {
IPAWANERR("Exceed mux_channel limit(%d)\n",
rmnet_index);
+ mutex_unlock(&add_mux_channel_lock);
return -EFAULT;
}
IPAWANDBG("ADD_MUX_CHANNEL(%d, name: %s)\n",
@@ -1558,6 +1561,7 @@ static int ipa_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
IPAWANERR("device %s reg IPA failed\n",
extend_ioctl_data.u.
rmnet_mux_val.vchannel_name);
+ mutex_unlock(&add_mux_channel_lock);
return -ENODEV;
}
mux_channel[rmnet_index].mux_channel_set = true;
@@ -1570,6 +1574,7 @@ static int ipa_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
mux_channel[rmnet_index].ul_flt_reg = false;
}
rmnet_index++;
+ mutex_unlock(&add_mux_channel_lock);
break;
case RMNET_IOCTL_SET_EGRESS_DATA_FORMAT:
IPAWANDBG("get RMNET_IOCTL_SET_EGRESS_DATA_FORMAT\n");
@@ -3084,6 +3089,7 @@ static int __init ipa_wwan_init(void)
atomic_set(&is_ssr, 0);
mutex_init(&ipa_to_apps_pipe_handle_guard);
+ mutex_init(&add_mux_channel_lock);
ipa_to_apps_hdl = -1;
ipa_qmi_init();
@@ -3103,6 +3109,7 @@ static void __exit ipa_wwan_cleanup(void)
ipa_qmi_cleanup();
mutex_destroy(&ipa_to_apps_pipe_handle_guard);
+ mutex_destroy(&add_mux_channel_lock);
ret = subsys_notif_unregister_notifier(subsys_notify_handle,
&ssr_notifier);
if (ret)
diff --git a/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa.c b/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa.c
index a15bd04..b198348 100644
--- a/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa.c
+++ b/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa.c
@@ -141,6 +141,7 @@ struct rmnet_ipa3_context {
u32 apps_to_ipa3_hdl;
u32 ipa3_to_apps_hdl;
struct mutex pipe_handle_guard;
+ struct mutex add_mux_channel_lock;
};
static struct rmnet_ipa3_context *rmnet_ipa3_ctx;
@@ -1636,10 +1637,13 @@ static int ipa3_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
rmnet_mux_val.mux_id);
return rc;
}
+ mutex_lock(&rmnet_ipa3_ctx->add_mux_channel_lock);
if (rmnet_ipa3_ctx->rmnet_index
>= MAX_NUM_OF_MUX_CHANNEL) {
IPAWANERR("Exceed mux_channel limit(%d)\n",
rmnet_ipa3_ctx->rmnet_index);
+ mutex_unlock(&rmnet_ipa3_ctx->
+ add_mux_channel_lock);
return -EFAULT;
}
IPAWANDBG("ADD_MUX_CHANNEL(%d, name: %s)\n",
@@ -1673,6 +1677,8 @@ static int ipa3_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
IPAWANERR("device %s reg IPA failed\n",
extend_ioctl_data.u.
rmnet_mux_val.vchannel_name);
+ mutex_unlock(&rmnet_ipa3_ctx->
+ add_mux_channel_lock);
return -ENODEV;
}
mux_channel[rmnet_index].mux_channel_set = true;
@@ -1685,6 +1691,7 @@ static int ipa3_wwan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
mux_channel[rmnet_index].ul_flt_reg = false;
}
rmnet_ipa3_ctx->rmnet_index++;
+ mutex_unlock(&rmnet_ipa3_ctx->add_mux_channel_lock);
break;
case RMNET_IOCTL_SET_EGRESS_DATA_FORMAT:
rc = handle3_egress_format(dev, &extend_ioctl_data);
@@ -3204,6 +3211,7 @@ static int __init ipa3_wwan_init(void)
atomic_set(&rmnet_ipa3_ctx->is_ssr, 0);
mutex_init(&rmnet_ipa3_ctx->pipe_handle_guard);
+ mutex_init(&rmnet_ipa3_ctx->add_mux_channel_lock);
rmnet_ipa3_ctx->ipa3_to_apps_hdl = -1;
rmnet_ipa3_ctx->apps_to_ipa3_hdl = -1;
@@ -3222,8 +3230,10 @@ static int __init ipa3_wwan_init(void)
static void __exit ipa3_wwan_cleanup(void)
{
int ret;
+
ipa3_qmi_cleanup();
mutex_destroy(&rmnet_ipa3_ctx->pipe_handle_guard);
+ mutex_destroy(&rmnet_ipa3_ctx->add_mux_channel_lock);
ret = subsys_notif_unregister_notifier(
rmnet_ipa3_ctx->subsys_notify_handle, &ipa3_ssr_notifier);
if (ret)
diff --git a/drivers/scsi/ufs/ufshcd.c b/drivers/scsi/ufs/ufshcd.c
index 2f6cd95..6418c11 100644
--- a/drivers/scsi/ufs/ufshcd.c
+++ b/drivers/scsi/ufs/ufshcd.c
@@ -1523,6 +1523,7 @@ int ufshcd_hold(struct ufs_hba *hba, bool async)
}
spin_unlock_irqrestore(hba->host->host_lock, flags);
out:
+ hba->ufs_stats.clk_hold.ts = ktime_get();
return rc;
}
EXPORT_SYMBOL_GPL(ufshcd_hold);
@@ -1627,6 +1628,7 @@ static void __ufshcd_release(struct ufs_hba *hba, bool no_sched)
hba->clk_gating.state = REQ_CLKS_OFF;
trace_ufshcd_clk_gating(dev_name(hba->dev), hba->clk_gating.state);
+ hba->ufs_stats.clk_rel.ts = ktime_get();
hrtimer_start(&hba->clk_gating.gate_hrtimer,
ms_to_ktime(hba->clk_gating.delay_ms),
@@ -2073,8 +2075,10 @@ static void ufshcd_hibern8_exit_work(struct work_struct *work)
/* Exit from hibern8 */
if (ufshcd_is_link_hibern8(hba)) {
+ hba->ufs_stats.clk_hold.ctx = H8_EXIT_WORK;
ufshcd_hold(hba, false);
ret = ufshcd_uic_hibern8_exit(hba);
+ hba->ufs_stats.clk_rel.ctx = H8_EXIT_WORK;
ufshcd_release(hba, false);
if (!ret) {
spin_lock_irqsave(hba->host->host_lock, flags);
@@ -2500,6 +2504,7 @@ ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
int ret;
unsigned long flags;
+ hba->ufs_stats.clk_hold.ctx = UIC_CMD_SEND;
ufshcd_hold_all(hba);
mutex_lock(&hba->uic_cmd_mutex);
ufshcd_add_delay_before_dme_cmd(hba);
@@ -2513,6 +2518,7 @@ ufshcd_send_uic_cmd(struct ufs_hba *hba, struct uic_command *uic_cmd)
ufshcd_save_tstamp_of_last_dme_cmd(hba);
mutex_unlock(&hba->uic_cmd_mutex);
ufshcd_release_all(hba);
+ hba->ufs_stats.clk_rel.ctx = UIC_CMD_SEND;
ufsdbg_error_inject_dispatcher(hba,
ERR_INJECT_UIC, 0, &ret);
@@ -2999,6 +3005,7 @@ static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd)
goto out;
}
+ hba->ufs_stats.clk_hold.ctx = QUEUE_CMD;
err = ufshcd_hold(hba, true);
if (err) {
err = SCSI_MLQUEUE_HOST_BUSY;
@@ -3013,6 +3020,7 @@ static int ufshcd_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd)
if (err) {
clear_bit_unlock(tag, &hba->lrb_in_use);
err = SCSI_MLQUEUE_HOST_BUSY;
+ hba->ufs_stats.clk_rel.ctx = QUEUE_CMD;
ufshcd_release(hba, true);
goto out;
}
@@ -4392,8 +4400,10 @@ static int ufshcd_uic_change_pwr_mode(struct ufs_hba *hba, u8 mode)
uic_cmd.command = UIC_CMD_DME_SET;
uic_cmd.argument1 = UIC_ARG_MIB(PA_PWRMODE);
uic_cmd.argument3 = mode;
+ hba->ufs_stats.clk_hold.ctx = PWRCTL_CMD_SEND;
ufshcd_hold_all(hba);
ret = ufshcd_uic_pwr_ctrl(hba, &uic_cmd);
+ hba->ufs_stats.clk_rel.ctx = PWRCTL_CMD_SEND;
ufshcd_release_all(hba);
out:
return ret;
@@ -5580,6 +5590,7 @@ static void __ufshcd_transfer_req_compl(struct ufs_hba *hba,
update_req_stats(hba, lrbp);
/* Mark completed command as NULL in LRB */
lrbp->cmd = NULL;
+ hba->ufs_stats.clk_rel.ctx = XFR_REQ_COMPL;
__ufshcd_release(hba, false);
__ufshcd_hibern8_release(hba, false);
if (cmd->request) {
@@ -6101,6 +6112,7 @@ static void ufshcd_err_handler(struct work_struct *work)
if (unlikely((hba->clk_gating.state != CLKS_ON) &&
ufshcd_is_auto_hibern8_supported(hba))) {
spin_unlock_irqrestore(hba->host->host_lock, flags);
+ hba->ufs_stats.clk_hold.ctx = ERR_HNDLR_WORK;
ufshcd_hold(hba, false);
spin_lock_irqsave(hba->host->host_lock, flags);
clks_enabled = true;
@@ -6245,8 +6257,10 @@ static void ufshcd_err_handler(struct work_struct *work)
hba->silence_err_logs = false;
- if (clks_enabled)
+ if (clks_enabled) {
__ufshcd_release(hba, false);
+ hba->ufs_stats.clk_rel.ctx = ERR_HNDLR_WORK;
+ }
out:
ufshcd_clear_eh_in_progress(hba);
spin_unlock_irqrestore(hba->host->host_lock, flags);
@@ -6482,7 +6496,8 @@ static irqreturn_t ufshcd_intr(int irq, void *__hba)
spin_lock(hba->host->host_lock);
intr_status = ufshcd_readl(hba, REG_INTERRUPT_STATUS);
-
+ hba->ufs_stats.last_intr_status = intr_status;
+ hba->ufs_stats.last_intr_ts = ktime_get();
/*
* There could be max of hba->nutrs reqs in flight and in worst case
* if the reqs get finished 1 by 1 after the interrupt status is
@@ -6561,6 +6576,7 @@ static int ufshcd_issue_tm_cmd(struct ufs_hba *hba, int lun_id, int task_id,
* the maximum wait time is bounded by %TM_CMD_TIMEOUT.
*/
wait_event(hba->tm_tag_wq, ufshcd_get_tm_free_slot(hba, &free_slot));
+ hba->ufs_stats.clk_hold.ctx = TM_CMD_SEND;
ufshcd_hold_all(hba);
spin_lock_irqsave(host->host_lock, flags);
@@ -6618,6 +6634,7 @@ static int ufshcd_issue_tm_cmd(struct ufs_hba *hba, int lun_id, int task_id,
clear_bit(free_slot, &hba->tm_condition);
ufshcd_put_tm_slot(hba, free_slot);
wake_up(&hba->tm_tag_wq);
+ hba->ufs_stats.clk_rel.ctx = TM_CMD_SEND;
ufshcd_release_all(hba);
return err;
@@ -9635,6 +9652,7 @@ static int ufshcd_devfreq_scale(struct ufs_hba *hba, bool scale_up)
int ret = 0;
/* let's not get into low power until clock scaling is completed */
+ hba->ufs_stats.clk_hold.ctx = CLK_SCALE_WORK;
ufshcd_hold_all(hba);
ret = ufshcd_clock_scaling_prepare(hba);
@@ -9698,6 +9716,7 @@ static int ufshcd_devfreq_scale(struct ufs_hba *hba, bool scale_up)
clk_scaling_unprepare:
ufshcd_clock_scaling_unprepare(hba);
out:
+ hba->ufs_stats.clk_rel.ctx = CLK_SCALE_WORK;
ufshcd_release_all(hba);
return ret;
}
diff --git a/drivers/scsi/ufs/ufshcd.h b/drivers/scsi/ufs/ufshcd.h
index 6966aac..77ccc39 100644
--- a/drivers/scsi/ufs/ufshcd.h
+++ b/drivers/scsi/ufs/ufshcd.h
@@ -584,6 +584,22 @@ struct ufshcd_req_stat {
};
#endif
+enum ufshcd_ctx {
+ QUEUE_CMD,
+ ERR_HNDLR_WORK,
+ H8_EXIT_WORK,
+ UIC_CMD_SEND,
+ PWRCTL_CMD_SEND,
+ TM_CMD_SEND,
+ XFR_REQ_COMPL,
+ CLK_SCALE_WORK,
+};
+
+struct ufshcd_clk_ctx {
+ ktime_t ts;
+ enum ufshcd_ctx ctx;
+};
+
/**
* struct ufs_stats - keeps usage/err statistics
* @enabled: enable tag stats for debugfs
@@ -612,6 +628,10 @@ struct ufs_stats {
int query_stats_arr[UPIU_QUERY_OPCODE_MAX][MAX_QUERY_IDN];
#endif
+ u32 last_intr_status;
+ ktime_t last_intr_ts;
+ struct ufshcd_clk_ctx clk_hold;
+ struct ufshcd_clk_ctx clk_rel;
u32 hibern8_exit_cnt;
ktime_t last_hibern8_exit_tstamp;
struct ufs_uic_err_reg_hist pa_err;
diff --git a/drivers/soc/qcom/icnss.c b/drivers/soc/qcom/icnss.c
index 69e0ebc..e8a6418 100644
--- a/drivers/soc/qcom/icnss.c
+++ b/drivers/soc/qcom/icnss.c
@@ -3969,6 +3969,9 @@ static ssize_t icnss_regread_write(struct file *fp, const char __user *user_buf,
data_len > QMI_WLFW_MAX_DATA_SIZE_V01)
return -EINVAL;
+ kfree(priv->diag_reg_read_buf);
+ priv->diag_reg_read_buf = NULL;
+
reg_buf = kzalloc(data_len, GFP_KERNEL);
if (!reg_buf)
return -ENOMEM;
@@ -4002,12 +4005,13 @@ static const struct file_operations icnss_regread_fops = {
.llseek = seq_lseek,
};
+#ifdef CONFIG_ICNSS_DEBUG
static int icnss_debugfs_create(struct icnss_priv *priv)
{
int ret = 0;
struct dentry *root_dentry;
- root_dentry = debugfs_create_dir("icnss", 0);
+ root_dentry = debugfs_create_dir("icnss", NULL);
if (IS_ERR(root_dentry)) {
ret = PTR_ERR(root_dentry);
@@ -4017,19 +4021,40 @@ static int icnss_debugfs_create(struct icnss_priv *priv)
priv->root_dentry = root_dentry;
- debugfs_create_file("fw_debug", 0644, root_dentry, priv,
+ debugfs_create_file("fw_debug", 0600, root_dentry, priv,
&icnss_fw_debug_fops);
- debugfs_create_file("stats", 0644, root_dentry, priv,
+ debugfs_create_file("stats", 0600, root_dentry, priv,
&icnss_stats_fops);
debugfs_create_file("reg_read", 0600, root_dentry, priv,
&icnss_regread_fops);
- debugfs_create_file("reg_write", 0644, root_dentry, priv,
+ debugfs_create_file("reg_write", 0600, root_dentry, priv,
&icnss_regwrite_fops);
out:
return ret;
}
+#else
+static int icnss_debugfs_create(struct icnss_priv *priv)
+{
+ int ret = 0;
+ struct dentry *root_dentry;
+
+ root_dentry = debugfs_create_dir("icnss", NULL);
+
+ if (IS_ERR(root_dentry)) {
+ ret = PTR_ERR(root_dentry);
+ icnss_pr_err("Unable to create debugfs %d\n", ret);
+ return ret;
+ }
+
+ priv->root_dentry = root_dentry;
+
+ debugfs_create_file("stats", 0600, root_dentry, priv,
+ &icnss_stats_fops);
+ return 0;
+}
+#endif
static void icnss_debugfs_destroy(struct icnss_priv *priv)
{
diff --git a/drivers/soc/qcom/watchdog_v2.c b/drivers/soc/qcom/watchdog_v2.c
index 7a784aa..8bf5659 100644
--- a/drivers/soc/qcom/watchdog_v2.c
+++ b/drivers/soc/qcom/watchdog_v2.c
@@ -136,6 +136,8 @@ static int msm_watchdog_suspend(struct device *dev)
return 0;
__raw_writel(1, wdog_dd->base + WDT0_RST);
if (wdog_dd->wakeup_irq_enable) {
+ /* Make sure register write is complete before proceeding */
+ mb();
wdog_dd->last_pet = sched_clock();
return 0;
}
@@ -151,8 +153,15 @@ static int msm_watchdog_resume(struct device *dev)
{
struct msm_watchdog_data *wdog_dd =
(struct msm_watchdog_data *)dev_get_drvdata(dev);
- if (!enable || wdog_dd->wakeup_irq_enable)
+ if (!enable)
return 0;
+ if (wdog_dd->wakeup_irq_enable) {
+ __raw_writel(1, wdog_dd->base + WDT0_RST);
+ /* Make sure register write is complete before proceeding */
+ mb();
+ wdog_dd->last_pet = sched_clock();
+ return 0;
+ }
__raw_writel(1, wdog_dd->base + WDT0_EN);
__raw_writel(1, wdog_dd->base + WDT0_RST);
/* Make sure watchdog is reset before setting enable */
diff --git a/drivers/thermal/cpu_cooling.c b/drivers/thermal/cpu_cooling.c
index 7da9211..355d013 100644
--- a/drivers/thermal/cpu_cooling.c
+++ b/drivers/thermal/cpu_cooling.c
@@ -620,6 +620,7 @@ static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
if (cpufreq_device->cpufreq_state == state)
return 0;
+ cpufreq_device->cpufreq_state = state;
/* If state is the last, isolate the CPU */
if (state == cpufreq_device->max_level)
return sched_isolate_cpu(cpu);
@@ -627,7 +628,6 @@ static int cpufreq_set_cur_state(struct thermal_cooling_device *cdev,
sched_unisolate_cpu(cpu);
clip_freq = cpufreq_device->freq_table[state];
- cpufreq_device->cpufreq_state = state;
cpufreq_device->clipped_freq = clip_freq;
/* Check if the device has a platform mitigation function that
diff --git a/drivers/video/hdmi.c b/drivers/video/hdmi.c
index 1626892..1cf907e 100644
--- a/drivers/video/hdmi.c
+++ b/drivers/video/hdmi.c
@@ -533,6 +533,10 @@ hdmi_picture_aspect_get_name(enum hdmi_picture_aspect picture_aspect)
return "4:3";
case HDMI_PICTURE_ASPECT_16_9:
return "16:9";
+ case HDMI_PICTURE_ASPECT_64_27:
+ return "64:27";
+ case HDMI_PICTURE_ASPECT_256_135:
+ return "256:135";
case HDMI_PICTURE_ASPECT_RESERVED:
return "Reserved";
}
diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h
index ac9d7d8..1c12875 100644
--- a/include/drm/drm_connector.h
+++ b/include/drm/drm_connector.h
@@ -531,6 +531,20 @@ struct drm_cmdline_mode {
* @audio_latency: audio latency info from ELD, if found
* @null_edid_counter: track sinks that give us all zeros for the EDID
* @bad_edid_counter: track sinks that give us an EDID with invalid checksum
+ * @max_tmds_char: indicates the maximum TMDS Character Rate supported
+ * @scdc_present: when set the sink supports SCDC functionality
+ * @rr_capable: when set the sink is capable of initiating an SCDC read request
+ * @supports_scramble: when set the sink supports less than 340Mcsc scrambling
+ * @flags_3d: 3D view(s) supported by the sink, see drm_edid.h (DRM_EDID_3D_*)
+ * @pt_scan_info: PT scan info obtained from the VCDB of EDID
+ * @it_scan_info: IT scan info obtained from the VCDB of EDID
+ * @ce_scan_info: CE scan info obtained from the VCDB of EDID
+ * @hdr_eotf: Electro optical transfer function obtained from HDR block
+ * @hdr_metadata_type_one: Metadata type one obtained from HDR block
+ * @hdr_max_luminance: desired max luminance obtained from HDR block
+ * @hdr_avg_luminance: desired avg luminance obtained from HDR block
+ * @hdr_min_luminance: desired min luminance obtained from HDR block
+ * @hdr_supported: does the sink support HDR content
* @edid_corrupt: indicates whether the last read EDID was corrupt
* @debugfs_entry: debugfs directory for this connector
* @state: current atomic state for this connector
@@ -665,6 +679,22 @@ struct drm_connector {
int null_edid_counter; /* needed to workaround some HW bugs where we get all 0s */
unsigned bad_edid_counter;
+ /* EDID bits HDMI 2.0 */
+ int max_tmds_char; /* in Mcsc */
+ bool scdc_present;
+ bool rr_capable;
+ bool supports_scramble;
+ int flags_3d;
+ u8 pt_scan_info;
+ u8 it_scan_info;
+ u8 ce_scan_info;
+ u32 hdr_eotf;
+ bool hdr_metadata_type_one;
+ u32 hdr_max_luminance;
+ u32 hdr_avg_luminance;
+ u32 hdr_min_luminance;
+ bool hdr_supported;
+
/* Flag for raw EDID header corruption - used in Displayport
* compliance testing - * Displayport Link CTS Core 1.2 rev1.1 4.2.2.6
*/
diff --git a/include/drm/drm_edid.h b/include/drm/drm_edid.h
index c3a7d44..32bd104 100644
--- a/include/drm/drm_edid.h
+++ b/include/drm/drm_edid.h
@@ -269,6 +269,11 @@ struct detailed_timing {
#define DRM_ELD_CEA_SAD(mnl, sad) (20 + (mnl) + 3 * (sad))
+/* HDMI 2.0 */
+#define DRM_EDID_3D_INDEPENDENT_VIEW (1 << 2)
+#define DRM_EDID_3D_DUAL_VIEW (1 << 1)
+#define DRM_EDID_3D_OSD_DISPARITY (1 << 0)
+
struct edid {
u8 header[8];
/* Vendor & product info */
diff --git a/include/dt-bindings/clock/qcom,gcc-sdxpoorwills.h b/include/dt-bindings/clock/qcom,gcc-sdxpoorwills.h
index 915ac08..e773848 100644
--- a/include/dt-bindings/clock/qcom,gcc-sdxpoorwills.h
+++ b/include/dt-bindings/clock/qcom,gcc-sdxpoorwills.h
@@ -83,19 +83,20 @@
#define GCC_SPMI_FETCHER_CLK 65
#define GCC_SPMI_FETCHER_CLK_SRC 66
#define GCC_SYS_NOC_CPUSS_AHB_CLK 67
-#define GCC_USB30_MASTER_CLK 68
-#define GCC_USB30_MASTER_CLK_SRC 69
-#define GCC_USB30_MOCK_UTMI_CLK 70
-#define GCC_USB30_MOCK_UTMI_CLK_SRC 71
-#define GCC_USB30_SLEEP_CLK 72
-#define GCC_USB3_PRIM_CLKREF_CLK 73
-#define GCC_USB3_PHY_AUX_CLK 74
-#define GCC_USB3_PHY_AUX_CLK_SRC 75
-#define GCC_USB3_PHY_PIPE_CLK 76
-#define GCC_USB_PHY_CFG_AHB2PHY_CLK 77
-#define GCC_XO_DIV4_CLK 78
-#define GPLL0 79
-#define GPLL0_OUT_EVEN 80
+#define GCC_SYS_NOC_USB3_CLK 68
+#define GCC_USB30_MASTER_CLK 69
+#define GCC_USB30_MASTER_CLK_SRC 70
+#define GCC_USB30_MOCK_UTMI_CLK 71
+#define GCC_USB30_MOCK_UTMI_CLK_SRC 72
+#define GCC_USB30_SLEEP_CLK 73
+#define GCC_USB3_PRIM_CLKREF_CLK 74
+#define GCC_USB3_PHY_AUX_CLK 75
+#define GCC_USB3_PHY_AUX_CLK_SRC 76
+#define GCC_USB3_PHY_PIPE_CLK 77
+#define GCC_USB_PHY_CFG_AHB2PHY_CLK 78
+#define GCC_XO_DIV4_CLK 79
+#define GPLL0 80
+#define GPLL0_OUT_EVEN 81
/* GDSCs */
#define PCIE_GDSC 0
diff --git a/include/linux/hdmi.h b/include/linux/hdmi.h
index e974420..bc38b99a 100644
--- a/include/linux/hdmi.h
+++ b/include/linux/hdmi.h
@@ -35,6 +35,7 @@ enum hdmi_infoframe_type {
};
#define HDMI_IEEE_OUI 0x000c03
+#define HDMI_IEEE_OUI_HF 0xc45dd8
#define HDMI_INFOFRAME_HEADER_SIZE 4
#define HDMI_AVI_INFOFRAME_SIZE 13
#define HDMI_SPD_INFOFRAME_SIZE 25
@@ -78,6 +79,8 @@ enum hdmi_picture_aspect {
HDMI_PICTURE_ASPECT_NONE,
HDMI_PICTURE_ASPECT_4_3,
HDMI_PICTURE_ASPECT_16_9,
+ HDMI_PICTURE_ASPECT_64_27,
+ HDMI_PICTURE_ASPECT_256_135,
HDMI_PICTURE_ASPECT_RESERVED,
};
diff --git a/include/linux/nfcinfo.h b/include/linux/nfcinfo.h
new file mode 100644
index 0000000..b67a65f
--- /dev/null
+++ b/include/linux/nfcinfo.h
@@ -0,0 +1,18 @@
+/* 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 _NFCINFO_H
+#define _NFCINFO_H
+
+#include <uapi/linux/nfc/nfcinfo.h>
+
+#endif
diff --git a/include/linux/sde_rsc.h b/include/linux/sde_rsc.h
index f3fa9e6..f921909 100644
--- a/include/linux/sde_rsc.h
+++ b/include/linux/sde_rsc.h
@@ -206,6 +206,23 @@ struct sde_rsc_event *sde_rsc_register_event(int rsc_index, uint32_t event_type,
*/
void sde_rsc_unregister_event(struct sde_rsc_event *event);
+/**
+ * is_sde_rsc_available - check if display rsc available.
+ * @rsc_index: A client will be created on this RSC. As of now only
+ * SDE_RSC_INDEX is valid rsc index.
+ * Returns: true if rsc is available; false in all other cases
+ */
+bool is_sde_rsc_available(int rsc_index);
+
+/**
+ * get_sde_rsc_current_state - gets the current state of sde rsc.
+ * @rsc_index: A client will be created on this RSC. As of now only
+ * SDE_RSC_INDEX is valid rsc index.
+ * Returns: current state if rsc available; SDE_RSC_IDLE_STATE for
+ * all other cases
+ */
+enum sde_rsc_state get_sde_rsc_current_state(int rsc_index);
+
#else
static inline struct sde_rsc_client *sde_rsc_client_create(u32 rsc_index,
@@ -242,6 +259,15 @@ static inline void sde_rsc_unregister_event(struct sde_rsc_event *event)
{
}
+static inline bool is_sde_rsc_available(int rsc_index)
+{
+ return false;
+}
+
+static inline enum sde_rsc_state get_sde_rsc_current_state(int rsc_index)
+{
+ return SDE_RSC_IDLE_STATE;
+}
#endif /* CONFIG_DRM_SDE_RSC */
#endif /* _SDE_RSC_H_ */
diff --git a/include/uapi/drm/drm_mode.h b/include/uapi/drm/drm_mode.h
index f66ba9c..9c927a5 100644
--- a/include/uapi/drm/drm_mode.h
+++ b/include/uapi/drm/drm_mode.h
@@ -77,7 +77,8 @@ extern "C" {
#define DRM_MODE_FLAG_3D_TOP_AND_BOTTOM (7<<14)
#define DRM_MODE_FLAG_3D_SIDE_BY_SIDE_HALF (8<<14)
#define DRM_MODE_FLAG_SEAMLESS (1<<19)
-
+#define DRM_MODE_FLAG_SUPPORTS_RGB (1<<20)
+#define DRM_MODE_FLAG_SUPPORTS_YUV (1<<21)
/* DPMS flags */
/* bit compatible with the xorg definitions. */
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index 7cf7779..cd758c2 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -525,3 +525,4 @@
header-y += msm_dsps.h
header-y += msm-core-interface.h
header-y += msm_rotator.h
+header-y += nfc/
diff --git a/include/uapi/linux/nfc/Kbuild b/include/uapi/linux/nfc/Kbuild
new file mode 100644
index 0000000..9071015
--- /dev/null
+++ b/include/uapi/linux/nfc/Kbuild
@@ -0,0 +1,2 @@
+#UAPI export list
+header-y += nfcinfo.h
diff --git a/include/uapi/linux/nfc/nfcinfo.h b/include/uapi/linux/nfc/nfcinfo.h
new file mode 100644
index 0000000..df178e2
--- /dev/null
+++ b/include/uapi/linux/nfc/nfcinfo.h
@@ -0,0 +1,21 @@
+#ifndef _UAPI_NFCINFO_H_
+#define _UAPI_NFCINFO_H_
+
+#include <linux/ioctl.h>
+
+#define NFCC_MAGIC 0xE9
+#define NFCC_GET_INFO _IOW(NFCC_MAGIC, 0x09, unsigned int)
+
+struct nqx_devinfo {
+ unsigned char chip_type;
+ unsigned char rom_version;
+ unsigned char fw_major;
+ unsigned char fw_minor;
+};
+
+union nqx_uinfo {
+ unsigned int i;
+ struct nqx_devinfo info;
+};
+
+#endif
diff --git a/kernel/sched/cpufreq_schedutil.c b/kernel/sched/cpufreq_schedutil.c
index 42630ec..c42380a 100644
--- a/kernel/sched/cpufreq_schedutil.c
+++ b/kernel/sched/cpufreq_schedutil.c
@@ -24,6 +24,7 @@ struct sugov_tunables {
struct gov_attr_set attr_set;
unsigned int rate_limit_us;
unsigned int hispeed_freq;
+ bool pl;
};
struct sugov_policy {
@@ -224,7 +225,8 @@ static void sugov_walt_adjust(struct sugov_cpu *sg_cpu, unsigned long *util,
if (is_hiload && nl >= mult_frac(cpu_util, NL_RATIO, 100))
*util = *max;
- *util = max(*util, sg_cpu->walt_load.pl);
+ if (sg_policy->tunables->pl)
+ *util = max(*util, sg_cpu->walt_load.pl);
}
static void sugov_update_single(struct update_util_data *hook, u64 time,
@@ -450,12 +452,32 @@ static ssize_t hispeed_freq_store(struct gov_attr_set *attr_set,
return count;
}
+static ssize_t pl_show(struct gov_attr_set *attr_set, char *buf)
+{
+ struct sugov_tunables *tunables = to_sugov_tunables(attr_set);
+
+ return sprintf(buf, "%u\n", tunables->pl);
+}
+
+static ssize_t pl_store(struct gov_attr_set *attr_set, const char *buf,
+ size_t count)
+{
+ struct sugov_tunables *tunables = to_sugov_tunables(attr_set);
+
+ if (kstrtobool(buf, &tunables->pl))
+ return -EINVAL;
+
+ return count;
+}
+
static struct governor_attr rate_limit_us = __ATTR_RW(rate_limit_us);
static struct governor_attr hispeed_freq = __ATTR_RW(hispeed_freq);
+static struct governor_attr pl = __ATTR_RW(pl);
static struct attribute *sugov_attributes[] = {
&rate_limit_us.attr,
&hispeed_freq.attr,
+ &pl.attr,
NULL
};