diff --git a/Documentation/devicetree/bindings/display/msm/dsi.txt b/Documentation/devicetree/bindings/display/msm/dsi.txt index fa00e62e1cf6983874207b30095b0451e8ef4334..8334828a58e321941232fbdca865bf39b5c03259 100644 --- a/Documentation/devicetree/bindings/display/msm/dsi.txt +++ b/Documentation/devicetree/bindings/display/msm/dsi.txt @@ -107,6 +107,20 @@ Required properties: Optional properties: - qcom,dsi-phy-regulator-ldo-mode: Boolean value indicating if the LDO mode PHY regulator is wanted. +- qcom,mdss-mdp-transfer-time-us: Specifies the dsi transfer time for command mode + panels in microseconds. Driver uses this number to adjust + the clock rate according to the expected transfer time. + Increasing this value would slow down the mdp processing + and can result in slower performance. + Decreasing this value can speed up the mdp processing, + but this can also impact power consumption. + As a rule this time should not be higher than the time + that would be expected with the processing at the + dsi link rate since anyways this would be the maximum + transfer time that could be achieved. + If ping pong split is enabled, this time should not be higher + than two times the dsi link rate time. + If the property is not specified, then the default value is 14000 us. [1] Documentation/devicetree/bindings/clock/clock-bindings.txt [2] Documentation/devicetree/bindings/graph.txt @@ -157,6 +171,8 @@ Example: qcom,master-dsi; qcom,sync-dual-dsi; + qcom,mdss-mdp-transfer-time-us = <12000>; + pinctrl-names = "default", "sleep"; pinctrl-0 = <&dsi_active>; pinctrl-1 = <&dsi_suspend>; diff --git a/Documentation/devicetree/bindings/display/msm/sde-rsc.txt b/Documentation/devicetree/bindings/display/msm/sde-rsc.txt new file mode 100644 index 0000000000000000000000000000000000000000..55d18cf6fe328040b9fde461ef53bceca3998b35 --- /dev/null +++ b/Documentation/devicetree/bindings/display/msm/sde-rsc.txt @@ -0,0 +1,96 @@ +Qualcomm Technologies, Inc. SDE RSC + +Snapdragon Display Engine implements display rsc to driver +display core to different modes for power saving + +Required properties +- compatible: Must be "qcom,sde-rsc" +- reg: Offset and length of the register set for + the device. +- reg-names: Names to refer to register sets related + to this device + +Optional properties: +- clocks: List of phandles for clock device nodes + needed by the device. +- clock-names: List of clock names needed by the device. +- vdd-supply: phandle for vdd regulator device node. +- qcom,sde-rsc-version: U32 property represents the rsc version. It helps to + select correct sequence for sde rsc based on version. +- qcom,sde-dram-channels: U32 property represents the number of channels in the + Bus memory controller. +- qcom,sde-num-nrt-paths: U32 property represents the number of non-realtime + paths in each Bus Scaling Usecase. This value depends on + number of AXI ports that are dedicated to non-realtime VBIF + for particular chipset. + These paths must be defined after rt-paths in + "qcom,msm-bus,vectors-KBps" vector request. + +Bus Scaling Subnodes: +- qcom,sde-data-bus: Property to provide Bus scaling for data bus access for + sde blocks. +- qcom,sde-llcc-bus: Property to provide Bus scaling for data bus access for + mnoc to llcc. +- qcom,sde-ebi-bus: Property to provide Bus scaling for data bus access for + llcc to ebi. + +Bus Scaling Data: +- qcom,msm-bus,name: String property describing client name. +- qcom,msm-bus,active-only: Boolean context flag for requests in active or + dual (active & sleep) contex +- qcom,msm-bus,num-cases: This is the number of Bus Scaling use cases + defined in the vectors property. +- qcom,msm-bus,num-paths: This represents the number of paths in each + Bus Scaling Usecase. +- qcom,msm-bus,vectors-KBps: * A series of 4 cell properties, with a format + of (src, dst, ab, ib) which is defined at + Documentation/devicetree/bindings/arm/msm/msm_bus.txt + * Current values of src & dst are defined at + include/linux/msm-bus-board.h +Example: + sde_rscc { + cell-index = <0>; + compatible = "qcom,sde-rsc"; + reg = <0xaf20000 0x1c44>, + <0xaf30000 0x3fd4>; + reg-names = "drv", "wrapper"; + clocks = <&clock_mmss clk_mdss_ahb_clk>, + <&clock_mmss clk_mdss_axi_clk>; + clock-names = "iface_clk", "bus_clk"; + vdd-supply = <&gdsc_mdss>; + + qcom,sde-rsc-version = <1>; + qcom,sde-dram-channels = <2>; + qcom,sde-num-nrt-paths = <1>; + + qcom,sde-data-bus { + qcom,msm-bus,name = "sde_rsc"; + qcom,msm-bus,active-only; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <2>; + qcom,msm-bus,vectors-KBps = + <22 512 0 0>, <23 512 0 0>, + <22 512 0 6400000>, <23 512 0 6400000>, + <22 512 0 6400000>, <23 512 0 6400000>; + }; + qcom,sde-llcc-bus { + qcom,msm-bus,name = "sde_rsc_llcc"; + qcom,msm-bus,active-only; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,vectors-KBps = + <20001 20513 0 0>, + <20001 20513 0 6400000>, + <20001 20513 0 6400000>; + }; + qcom,sde-ebi-bus { + qcom,msm-bus,name = "sde_rsc_ebi"; + qcom,msm-bus,active-only; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,vectors-KBps = + <20000 20512 0 0>, + <20000 20512 0 6400000>, + <20000 20512 0 6400000>; + }; + }; diff --git a/Documentation/devicetree/bindings/display/msm/sde.txt b/Documentation/devicetree/bindings/display/msm/sde.txt new file mode 100644 index 0000000000000000000000000000000000000000..b570448ebadca10760879b1a4c8f1f34b4003463 --- /dev/null +++ b/Documentation/devicetree/bindings/display/msm/sde.txt @@ -0,0 +1,736 @@ +Qualcomm Technologies, Inc. SDE KMS + +Snapdragon Display Engine implements Linux DRM/KMS APIs to drive user +interface to different panel interfaces. SDE driver is the core of +display subsystem which manage all data paths to different panel interfaces. + +Required properties +- compatible: Must be "qcom,sde-kms" +- compatible: "msm-hdmi-audio-codec-rx"; +- reg: Offset and length of the register set for the device. +- reg-names : Names to refer to register sets related to this device +- clocks: List of Phandles for clock device nodes + needed by the device. +- clock-names: List of clock names needed by the device. +- mmagic-supply: Phandle for mmagic mdss supply regulator device node. +- vdd-supply: Phandle for vdd regulator device node. +- interrupt-parent: Must be core interrupt controller. +- interrupts: Interrupt associated with MDSS. +- interrupt-controller: Mark the device node as an interrupt controller. +- #interrupt-cells: Should be one. The first cell is interrupt number. +- iommus: Specifies the SID's used by this context bank. +- qcom,sde-sspp-type: Array of strings for SDE source surface pipes type information. + A source pipe can be "vig", "rgb", "dma" or "cursor" type. + Number of xin ids defined should match the number of offsets + defined in property: qcom,sde-sspp-off. +- qcom,sde-sspp-off: Array of offset for SDE source surface pipes. The offsets + are calculated from register "mdp_phys" defined in + reg property + "sde-off". The number of offsets defined here should + reflect the amount of pipes that can be active in SDE for + this configuration. +- qcom,sde-sspp-xin-id: Array of VBIF clients ids (xins) corresponding + to the respective source pipes. Number of xin ids + defined should match the number of offsets + defined in property: qcom,sde-sspp-off. +- qcom,sde-ctl-off: Array of offset addresses for the available ctl + hw blocks within SDE, these offsets are + calculated from register "mdp_phys" defined in + reg property. The number of ctl offsets defined + here should reflect the number of control paths + that can be configured concurrently on SDE for + this configuration. +- qcom,sde-wb-off: Array of offset addresses for the programmable + writeback blocks within SDE. +- qcom,sde-wb-xin-id: Array of VBIF clients ids (xins) corresponding + to the respective writeback. Number of xin ids + defined should match the number of offsets + defined in property: qcom,sde-wb-off. +- qcom,sde-mixer-off: Array of offset addresses for the available + mixer blocks that can drive data to panel + interfaces. These offsets are be calculated from + register "mdp_phys" defined in reg property. + The number of offsets defined should reflect the + amount of mixers that can drive data to a panel + interface. +- qcom,sde-dspp-top-off: Offset address for the dspp top block. + The offset is calculated from register "mdp_phys" + defined in reg property. +- qcom,sde-dspp-off: Array of offset addresses for the available dspp + blocks. These offsets are calculated from + register "mdp_phys" defined in reg property. +- qcom,sde-pp-off: Array of offset addresses for the available + pingpong blocks. These offsets are calculated + from register "mdp_phys" defined in reg property. +- qcom,sde-pp-slave: Array of flags indicating whether each ping pong + block may be configured as a pp slave. +- qcom,sde-intf-off: Array of offset addresses for the available SDE + interface blocks that can drive data to a + panel controller. The offsets are calculated + from "mdp_phys" defined in reg property. The number + of offsets defined should reflect the number of + programmable interface blocks available in hardware. +- qcom,sde-mixer-blend-op-off Array of offset addresses for the available + blending stages. The offsets are relative to + qcom,sde-mixer-off. +- qcom,sde-mixer-pair-mask Array of mixer numbers that can be paired with + mixer number corresponding to the array index. + +Optional properties: +- clock-rate: List of clock rates in Hz. +- clock-max-rate: List of maximum clock rate in Hz that this device supports. +- qcom,platform-supply-entries: A node that lists the elements of the supply. There + can be more than one instance of this binding, + in which case the entry would be appended with + the supply entry index. + e.g. qcom,platform-supply-entry@0 + -- reg: offset and length of the register set for the device. + -- qcom,supply-name: name of the supply (vdd/vdda/vddio) + -- qcom,supply-min-voltage: minimum voltage level (uV) + -- qcom,supply-max-voltage: maximum voltage level (uV) + -- qcom,supply-enable-load: load drawn (uA) from enabled supply + -- qcom,supply-disable-load: load drawn (uA) from disabled supply + -- qcom,supply-pre-on-sleep: time to sleep (ms) before turning on + -- qcom,supply-post-on-sleep: time to sleep (ms) after turning on + -- qcom,supply-pre-off-sleep: time to sleep (ms) before turning off + -- qcom,supply-post-off-sleep: time to sleep (ms) after turning off +- qcom,sde-sspp-src-size: A u32 value indicates the address range for each sspp. +- qcom,sde-mixer-size: A u32 value indicates the address range for each mixer. +- qcom,sde-ctl-size: A u32 value indicates the address range for each ctl. +- qcom,sde-dspp-size: A u32 value indicates the address range for each dspp. +- qcom,sde-intf-size: A u32 value indicates the address range for each intf. +- qcom,sde-dsc-size: A u32 value indicates the address range for each dsc. +- qcom,sde-cdm-size: A u32 value indicates the address range for each cdm. +- qcom,sde-pp-size: A u32 value indicates the address range for each pingpong. +- qcom,sde-wb-size: A u32 value indicates the address range for each writeback. +- qcom,sde-len: A u32 entry for SDE address range. +- qcom,sde-intf-max-prefetch-lines: Array of u32 values for max prefetch lines on + each interface. +- qcom,sde-sspp-linewidth: A u32 value indicates the max sspp line width. +- qcom,sde-mixer-linewidth: A u32 value indicates the max mixer line width. +- qcom,sde-wb-linewidth: A u32 value indicates the max writeback line width. +- qcom,sde-sspp-scale-size: A u32 value indicates the scaling block size on sspp. +- qcom,sde-mixer-blendstages: A u32 value indicates the max mixer blend stages for + alpha blending. +- qcom,sde-qseed-type: A string entry indiates qseed support on sspp and wb. + It supports "qssedv3" and "qseedv2" entries for qseed + type. By default "qseedv2" is used if this optional property + is not defined. +- qcom,sde-csc-type: A string entry indicates csc support on sspp and wb. + It supports "csc" and "csc-10bit" entries for csc + type. +- qcom,sde-highest-bank-bit: A u32 property to indicate GPU/Camera/Video highest memory + bank bit used for tile format buffers. +- qcom,sde-ubwc-version: Property to specify the UBWC feature version. +- qcom,sde-ubwc-static: Property to specify the default UBWC static + configuration value. +- qcom,sde-ubwc-swizzle: Property to specify the default UBWC swizzle + configuration value. +- qcom,sde-panic-per-pipe: Boolean property to indicate if panic signal + control feature is available on each source pipe. +- qcom,sde-has-src-split: Boolean property to indicate if source split + feature is available or not. +- qcom,sde-has-dim-layer: Boolean property to indicate if mixer has dim layer + feature is available or not. +- qcom,sde-has-idle-pc: Boolean property to indicate if target has idle + power collapse feature available or not. +- qcom,sde-has-mixer-gc: Boolean property to indicate if mixer has gamma correction + feature available or not. +- qcom,sde-has-dest-scaler: Boolean property to indicate if destination scaler + feature is available or not. +- qcom,sde-max-dest-scaler-input-linewidth: A u32 value indicates the + maximum input line width to destination scaler. +- qcom,sde-max-dest-scaler-output-linewidth: A u32 value indicates the + maximum output line width of destination scaler. +- qcom,sde-dest-scaler-top-off: A u32 value provides the + offset from mdp base to destination scaler block. +- qcom,sde-dest-scaler-top-size: A u32 value indicates the address range for ds top +- qcom,sde-dest-scaler-off: Array of u32 offsets indicate the qseed3 scaler blocks + offset from destination scaler top offset. +- qcom,sde-dest-scaler-size: A u32 value indicates the address range for each scaler block +- qcom,sde-sspp-clk-ctrl: Array of offsets describing clk control + offsets for dynamic clock gating. 1st value + in the array represents offset of the control + register. 2nd value represents bit offset within + control register. Number of offsets defined should + match the number of offsets defined in + property: qcom,sde-sspp-off +- qcom,sde-sspp-clk-status: Array of offsets describing clk status + offsets for dynamic clock gating. 1st value + in the array represents offset of the status + register. 2nd value represents bit offset within + control register. Number of offsets defined should + match the number of offsets defined in + property: qcom,sde-sspp-off. +- qcom,sde-sspp-excl-rect: Array of u32 values indicating exclusion rectangle + support on each sspp. +- qcom,sde-sspp-smart-dma-priority: Array of u32 values indicating hw pipe + priority of secondary rectangles when smart dma + is supported. Number of priority values should + match the number of offsets defined in + qcom,sde-sspp-off node. Zero indicates no support + for smart dma for the sspp. +- qcom,sde-smart-dma-rev: A string entry indicating the smart dma version + supported on the device. Supported entries are + "smart_dma_v1" and "smart_dma_v2". +- qcom,sde-intf-type: Array of string provides the interface type information. + Possible string values + "dsi" - dsi display interface + "dp" - Display Port interface + "hdmi" - HDMI display interface + An interface is considered as "none" if interface type + is not defined. +- qcom,sde-off: SDE offset from "mdp_phys" defined in reg property. +- qcom,sde-cdm-off: Array of offset addresses for the available + cdm blocks. These offsets will be calculated from + register "mdp_phys" defined in reg property. +- qcom,sde-vbif-off: Array of offset addresses for the available + vbif blocks. These offsets will be calculated from + register "vbif_phys" defined in reg property. +- qcom,sde-vbif-size: A u32 value indicates the vbif block address range. +- qcom,sde-te-off: A u32 offset indicates the te block offset on pingpong. + This offset is 0x0 by default. +- qcom,sde-te2-off: A u32 offset indicates the te2 block offset on pingpong. +- qcom,sde-te-size: A u32 value indicates the te block address range. +- qcom,sde-te2-size: A u32 value indicates the te2 block address range. +- qcom,sde-dsc-off: A u32 offset indicates the dsc block offset on pingpong. +- qcom,sde-dither-off: A u32 offset indicates the dither block offset on pingpong. +- qcom,sde-dither-version: A u32 value indicates the dither block version. +- qcom,sde-dither-size: A u32 value indicates the dither block address range. +- qcom,sde-sspp-vig-blocks: A node that lists the blocks inside the VIG hardware. The + block entries will contain the offset and version (if needed) + of each feature block. The presence of a block entry + indicates that the SSPP VIG contains that feature hardware. + e.g. qcom,sde-sspp-vig-blocks + -- qcom,sde-vig-csc-off: offset of CSC hardware + -- qcom,sde-vig-qseed-off: offset of QSEED hardware + -- qcom,sde-vig-qseed-size: A u32 address range for qseed scaler. + -- qcom,sde-vig-pcc: offset and version of PCC hardware + -- qcom,sde-vig-hsic: offset and version of global PA adjustment + -- qcom,sde-vig-memcolor: offset and version of PA memcolor hardware +- qcom,sde-sspp-rgb-blocks: A node that lists the blocks inside the RGB hardware. The + block entries will contain the offset and version (if needed) + of each feature block. The presence of a block entry + indicates that the SSPP RGB contains that feature hardware. + e.g. qcom,sde-sspp-vig-blocks + -- qcom,sde-rgb-scaler-off: offset of RGB scaler hardware + -- qcom,sde-rgb-scaler-size: A u32 address range for scaler. + -- qcom,sde-rgb-pcc: offset and version of PCC hardware +- qcom,sde-dspp-blocks: A node that lists the blocks inside the DSPP hardware. The + block entries will contain the offset and version of each + feature block. The presence of a block entry indicates that + the DSPP contains that feature hardware. + e.g. qcom,sde-dspp-blocks + -- qcom,sde-dspp-pcc: offset and version of PCC hardware + -- qcom,sde-dspp-gc: offset and version of GC hardware + -- qcom,sde-dspp-igc: offset and version of IGC hardware + -- qcom,sde-dspp-hsic: offset and version of global PA adjustment + -- qcom,sde-dspp-memcolor: offset and version of PA memcolor hardware + -- qcom,sde-dspp-sixzone: offset and version of PA sixzone hardware + -- qcom,sde-dspp-gamut: offset and version of Gamut mapping hardware + -- qcom,sde-dspp-dither: offset and version of dither hardware + -- qcom,sde-dspp-hist: offset and version of histogram hardware + -- qcom,sde-dspp-vlut: offset and version of PA vLUT hardware +- qcom,sde-mixer-blocks: A node that lists the blocks inside the layer mixer hardware. The + block entries will contain the offset and version (if needed) + of each feature block. The presence of a block entry + indicates that the layer mixer contains that feature hardware. + e.g. qcom,sde-mixer-blocks + -- qcom,sde-mixer-gc: offset and version of mixer GC hardware +- qcom,sde-dspp-ad-off: Array of u32 offsets indicate the ad block offset from the + DSPP offset. Since AD hardware is represented as part of + DSPP block, the AD offsets must be offset from the + corresponding DSPP base. +- qcom,sde-dspp-ad-version A u32 value indicating the version of the AD hardware +- qcom,sde-vbif-id: Array of vbif ids corresponding to the + offsets defined in property: qcom,sde-vbif-off. +- qcom,sde-vbif-default-ot-rd-limit: A u32 value indicates the default read OT limit +- qcom,sde-vbif-default-ot-wr-limit: A u32 value indicates the default write OT limit +- qcom,sde-vbif-dynamic-ot-rd-limit: A series of 2 cell property, with a format + of (pps, OT limit), where pps is pixel per second and + OT limit is the read limit to apply if the given + pps is not exceeded. +- qcom,sde-vbif-dynamic-ot-wr-limit: A series of 2 cell property, with a format + of (pps, OT limit), where pps is pixel per second and + OT limit is the write limit to apply if the given + pps is not exceeded. +- qcom,sde-vbif-memtype-0: Array of u32 vbif memory type settings, group 0 +- qcom,sde-vbif-memtype-1: Array of u32 vbif memory type settings, group 1 +- qcom,sde-wb-id: Array of writeback ids corresponding to the + offsets defined in property: qcom,sde-wb-off. +- qcom,sde-wb-clk-ctrl: Array of 2 cell property describing clk control + offsets for dynamic clock gating. 1st value + in the array represents offset of the control + register. 2nd value represents bit offset within + control register. Number of offsets defined should + match the number of offsets defined in + property: qcom,sde-wb-off +- qcom,sde-reg-dma-off: Offset of the register dma hardware block from + "regdma_phys" defined in reg property. +- qcom,sde-reg-dma-version: Version of the reg dma hardware block. +- qcom,sde-reg-dma-trigger-off: Offset of the lut dma trigger reg from "mdp_phys" + defined in reg property. +- qcom,sde-dram-channels: This represents the number of channels in the + Bus memory controller. +- qcom,sde-num-nrt-paths: Integer property represents the number of non-realtime + paths in each Bus Scaling Usecase. This value depends on + number of AXI ports that are dedicated to non-realtime VBIF + for particular chipset. + These paths must be defined after rt-paths in + "qcom,msm-bus,vectors-KBps" vector request. +- qcom,sde-max-bw-low-kbps: This value indicates the max bandwidth in Kbps + that can be supported without underflow. + This is a low bandwidth threshold which should + be applied in most scenarios to be safe from + underflows when unable to satisfy bandwidth + requirements. +- qcom,sde-max-bw-high-kbps: This value indicates the max bandwidth in Kbps + that can be supported without underflow. + This is a high bandwidth threshold which can be + applied in scenarios where panel interface can + be more tolerant to memory latency such as + command mode panels. +- qcom,sde-core-ib-ff: A string entry indicating the fudge factor for + core ib calculation. +- qcom,sde-core-clk-ff: A string entry indicating the fudge factor for + core clock calculation. +- qcom,sde-min-core-ib-kbps: This u32 value indicates the minimum mnoc ib + vote in Kbps that can be reduced without hitting underflow. + BW calculation logic will choose the IB bandwidth requirement + based on usecase if this floor value is not defined. +- qcom,sde-min-llcc-ib-kbps: This u32 value indicates the minimum llcc ib + vote in Kbps that can be reduced without hitting underflow. + BW calculation logic will choose the IB bandwidth requirement + based on usecase if this floor value is not defined. +- qcom,sde-min-dram-ib-kbps: This u32 value indicates the minimum dram ib + vote in Kbps that can be reduced without hitting underflow. + BW calculation logic will choose the IB bandwidth requirement + based on usecase if this floor value is not defined. +- qcom,sde-comp-ratio-rt: A string entry indicating the compression ratio + for each supported compressed format on realtime interface. + The string is composed of one or more of + /// + separated with spaces. +- qcom,sde-comp-ratio-nrt: A string entry indicating the compression ratio + for each supported compressed format on non-realtime interface. + The string is composed of one or more of + /// + separated with spaces. +- qcom,sde-undersized-prefill-lines: A u32 value indicates the size of undersized prefill in lines. +- qcom,sde-xtra-prefill-lines: A u32 value indicates the extra prefill in lines. +- qcom,sde-dest-scale-prefill-lines: A u32 value indicates the latency of destination scaler in lines. +- qcom,sde-macrotile-prefill-lines: A u32 value indicates the latency of macrotile in lines. +- qcom,sde-yuv-nv12-prefill-lines: A u32 value indicates the latency of yuv/nv12 in lines. +- qcom,sde-linear-prefill-lines: A u32 value indicates the latency of linear in lines. +- qcom,sde-downscaling-prefill-lines: A u32 value indicates the latency of downscaling in lines. +- qcom,sde-max-per-pipe-bw-kbps: Array of u32 value indicates the max per pipe bandwidth in Kbps. +- qcom,sde-amortizable-threshold: This value indicates the min for traffic shaping in lines. +- qcom,sde-vbif-qos-rt-remap: This array is used to program vbif qos remapper register + priority for realtime clients. +- qcom,sde-vbif-qos-nrt-remap: This array is used to program vbif qos remapper register + priority for non-realtime clients. +- qcom,sde-danger-lut: A 4 cell property, with a format of , + indicating the danger luts on sspp. +- qcom,sde-safe-lut: A 4 cell property, with a format of , + indicating the safe luts on sspp. +- qcom,sde-qos-lut-linear: Array of 3 cell property, with a format of + in ascending fill level + indicating the qos luts for linear format on sspp. + Zero fill level on the last entry identifies the default lut. +- qcom,sde-qos-lut-macrotile: Array of 3 cell property, with a format of + in ascending fill level + indicating the qos luts for macrotile format on sspp. + Zero fill level on the last entry identifies the default lut. +- qcom,sde-qos-lut-nrt: Array of 3 cell property, with a format of + in ascending fill level + indicating the qos luts for nrt (e.g wfd) on sspp. + Zero fill level on the last entry identifies the default lut. +- qcom,sde-qos-lut-cwb: Array of 3 cell property, with a format of + in ascending fill level + indicating the qos luts for cwb on sspp. + Zero fill level on the last entry identifies the default lut. +- qcom,sde-cdp-setting: Array of 2 cell property, with a format of + for cdp use cases in + order of , and . +- qcom,sde-inline-rot-xin: An integer array of xin-ids related to inline + rotation. +- qcom,sde-inline-rot-xin-type: A string array indicating the type of xin, + namely sspp or wb. Number of entries should match + the number of xin-ids defined in + property: qcom,sde-inline-rot-xin +- qcom,sde-inline-rot-clk-ctrl: Array of offsets describing clk control + offsets for dynamic clock gating. 1st value + in the array represents offset of the control + register. 2nd value represents bit offset within + control register. Number of offsets defined should + match the number of xin-ids defined in + property: qcom,sde-inline-rot-xin + +Bus Scaling Subnodes: +- qcom,sde-reg-bus: Property to provide Bus scaling for register access for + mdss blocks. +- qcom,sde-data-bus: Property to provide Bus scaling for data bus access for + mdss blocks. +- qcom,sde-llcc-bus: Property to provide Bus scaling for data bus access for + mnoc to llcc. +- qcom,sde-ebi-bus: Property to provide Bus scaling for data bus access for + llcc to ebi. + +- qcom,sde-inline-rotator: A 2 cell property, with format of (rotator phandle, + instance id), of inline rotator device. + +Bus Scaling Data: +- qcom,msm-bus,name: String property describing client name. +- qcom,msm-bus,num-cases: This is the number of Bus Scaling use cases + defined in the vectors property. +- qcom,msm-bus,num-paths: This represents the number of paths in each + Bus Scaling Usecase. +- qcom,msm-bus,vectors-KBps: * A series of 4 cell properties, with a format + of (src, dst, ab, ib) which is defined at + Documentation/devicetree/bindings/arm/msm/msm_bus.txt + * Current values of src & dst are defined at + include/linux/msm-bus-board.h + +SMMU Subnodes: +- smmu_sde_****: Child nodes representing sde smmu virtual + devices + +Subnode properties: +- compatible: Compatible names used for smmu devices. + names should be: + "qcom,smmu_sde_unsec": smmu context bank device + for unsecure sde real time domain. + "qcom,smmu_sde_sec": smmu context bank device + for secure sde real time domain. + "qcom,smmu_sde_nrt_unsec": smmu context bank device + for unsecure sde non-real time domain. + "qcom,smmu_sde_nrt_sec": smmu context bank device + for secure sde non-real time domain. + + +Please refer to ../../interrupt-controller/interrupts.txt for a general +description of interrupt bindings. + +Example: + mdss_mdp: qcom,mdss_mdp@900000 { + compatible = "qcom,sde-kms"; + reg = <0x00900000 0x90000>, + <0x009b0000 0x1040>, + <0x009b8000 0x1040>, + <0x0aeac000 0x00f0>; + reg-names = "mdp_phys", + "vbif_phys", + "vbif_nrt_phys", + "regdma_phys"; + clocks = <&clock_mmss clk_mdss_ahb_clk>, + <&clock_mmss clk_mdss_axi_clk>, + <&clock_mmss clk_mdp_clk_src>, + <&clock_mmss clk_mdss_mdp_vote_clk>, + <&clock_mmss clk_smmu_mdp_axi_clk>, + <&clock_mmss clk_mmagic_mdss_axi_clk>, + <&clock_mmss clk_mdss_vsync_clk>; + clock-names = "iface_clk", + "bus_clk", + "core_clk_src", + "core_clk", + "iommu_clk", + "mmagic_clk", + "vsync_clk"; + clock-rate = <0>, <0>, <0>; + clock-max-rate= <0 320000000 0>; + mmagic-supply = <&gdsc_mmagic_mdss>; + vdd-supply = <&gdsc_mdss>; + interrupt-parent = <&intc>; + interrupts = <0 83 0>; + interrupt-controller; + #interrupt-cells = <1>; + iommus = <&mdp_smmu 0>; + + qcom,sde-off = <0x1000>; + qcom,sde-ctl-off = <0x00002000 0x00002200 0x00002400 + 0x00002600 0x00002800>; + qcom,sde-mixer-off = <0x00045000 0x00046000 + 0x00047000 0x0004a000>; + qcom,sde-dspp-top-off = <0x1300>; + qcom,sde-dspp-off = <0x00055000 0x00057000>; + qcom,sde-dspp-ad-off = <0x24000 0x22800>; + qcom,sde-dspp-ad-version = <0x00030000>; + qcom,sde-dest-scaler-top-off = <0x00061000>; + qcom,sde-dest-scaler-off = <0x800 0x1000>; + qcom,sde-wb-off = <0x00066000>; + qcom,sde-wb-xin-id = <6>; + qcom,sde-intf-off = <0x0006b000 0x0006b800 + 0x0006c000 0x0006c800>; + qcom,sde-intf-type = "none", "dsi", "dsi", "hdmi"; + qcom,sde-pp-off = <0x00071000 0x00071800 + 0x00072000 0x00072800>; + qcom,sde-pp-slave = <0x0 0x0 0x0 0x0>; + qcom,sde-cdm-off = <0x0007a200>; + qcom,sde-dsc-off = <0x00081000 0x00081400>; + qcom,sde-intf-max-prefetch-lines = <0x15 0x15 0x15 0x15>; + + qcom,sde-mixer-pair-mask = <2 1 6 0 0 3>; + qcom,sde-mixer-blend-op-off = <0x20 0x38 0x50 0x68 0x80 0x98 + 0xb0 0xc8 0xe0 0xf8 0x110>; + + + qcom,sde-sspp-type = "vig", "vig", "vig", + "vig", "rgb", "rgb", + "rgb", "rgb", "dma", + "dma", "cursor", "cursor"; + + qcom,sde-sspp-off = <0x00005000 0x00007000 0x00009000 + 0x0000b000 0x00015000 0x00017000 + 0x00019000 0x0001b000 0x00025000 + 0x00027000 0x00035000 0x00037000>; + + qcom,sde-sspp-xin-id = <0 4 8 + 12 1 5 + 9 13 2 + 10 7 7>; + + /* offsets are relative to "mdp_phys + qcom,sde-off */ + qcom,sde-sspp-clk-ctrl = <0x2ac 0>, <0x2b4 0>, <0x2bc 0>, + <0x2c4 0>, <0x2ac 4>, <0x2b4 4>, <0x2bc 4>, + <0x2c4 4>, <0x2ac 8>, <0x2b4 8>, <0x3a8 16>, + <0x3b0 16>; + qcom,sde-sspp-clk-status = <0x2ac 0>, <0x2b4 0>, <0x2bc 0>, + <0x2c4 0>, <0x2ac 4>, <0x2b4 4>, <0x2bc 4>, + <0x2c4 4>, <0x2ac 8>, <0x2b4 8>, <0x3a8 16>, + <0x3b0 16>; + qcom,sde-mixer-linewidth = <2560>; + qcom,sde-sspp-linewidth = <2560>; + qcom,sde-mixer-blendstages = <0x7>; + qcom,sde-highest-bank-bit = <0x2>; + qcom,sde-ubwc-version = <0x100>; + qcom,sde-ubwc-static = <0x100>; + qcom,sde-ubwc-swizzle = <0>; + qcom,sde-panic-per-pipe; + qcom,sde-has-src-split; + qcom,sde-has-dim-layer; + qcom,sde-sspp-src-size = <0x100>; + qcom,sde-mixer-size = <0x100>; + qcom,sde-ctl-size = <0x100>; + qcom,sde-dspp-top-size = <0xc>; + qcom,sde-dspp-size = <0x100>; + qcom,sde-intf-size = <0x100>; + qcom,sde-dsc-size = <0x100>; + qcom,sde-cdm-size = <0x100>; + qcom,sde-pp-size = <0x100>; + qcom,sde-wb-size = <0x100>; + qcom,sde-dest-scaler-top-size = <0xc>; + qcom,sde-dest-scaler-size = <0x800>; + qcom,sde-len = <0x100>; + qcom,sde-wb-linewidth = <2560>; + qcom,sde-sspp-scale-size = <0x100>; + qcom,sde-mixer-blendstages = <0x8>; + qcom,sde-qseed-type = "qseedv2"; + qcom,sde-csc-type = "csc-10bit"; + qcom,sde-highest-bank-bit = <15>; + qcom,sde-has-mixer-gc; + qcom,sde-has-idle-pc; + qcom,sde-has-dest-scaler; + qcom,sde-max-dest-scaler-input-linewidth = <2048>; + qcom,sde-max-dest-scaler-output-linewidth = <2560>; + qcom,sde-sspp-max-rects = <1 1 1 1 + 1 1 1 1 + 1 1 + 1 1>; + qcom,sde-sspp-excl-rect = <1 1 1 1 + 1 1 1 1 + 1 1 + 1 1>; + qcom,sde-sspp-smart-dma-priority = <0 0 0 0 + 0 0 0 0 + 0 0 + 1 2>; + qcom,sde-smart-dma-rev = "smart_dma_v2"; + qcom,sde-te-off = <0x100>; + qcom,sde-te2-off = <0x100>; + qcom,sde-te-size = <0xffff>; + qcom,sde-te2-size = <0xffff>; + + qcom,sde-wb-id = <2>; + qcom,sde-wb-clk-ctrl = <0x2bc 16>; + + qcom,sde-danger-lut = <0x0000000f 0x0000ffff 0x00000000 + 0x00000000>; + qcom,sde-safe-lut = <0xfffc 0xff00 0xffff 0xffff>; + qcom,sde-qos-lut-linear = + <4 0x00000000 0x00000357>, + <5 0x00000000 0x00003357>, + <6 0x00000000 0x00023357>, + <7 0x00000000 0x00223357>, + <8 0x00000000 0x02223357>, + <9 0x00000000 0x22223357>, + <10 0x00000002 0x22223357>, + <11 0x00000022 0x22223357>, + <12 0x00000222 0x22223357>, + <13 0x00002222 0x22223357>, + <14 0x00012222 0x22223357>, + <0 0x00112222 0x22223357>; + qcom,sde-qos-lut-macrotile = + <10 0x00000003 0x44556677>, + <11 0x00000033 0x44556677>, + <12 0x00000233 0x44556677>, + <13 0x00002233 0x44556677>, + <14 0x00012233 0x44556677>, + <0 0x00112233 0x44556677>; + qcom,sde-qos-lut-nrt = + <0 0x00000000 0x00000000>; + qcom,sde-qos-lut-cwb = + <0 0x75300000 0x00000000>; + + qcom,sde-cdp-setting = <1 1>, <1 0>; + + qcom,sde-vbif-off = <0 0>; + qcom,sde-vbif-id = <0 1>; + qcom,sde-vbif-default-ot-rd-limit = <32>; + qcom,sde-vbif-default-ot-wr-limit = <16>; + qcom,sde-vbif-dynamic-ot-rd-limit = <62208000 2>, + <124416000 4>, <248832000 16>; + qcom,sde-vbif-dynamic-ot-wr-limit = <62208000 2>, + <124416000 4>, <248832000 16>; + qcom,sde-vbif-memtype-0 = <3 3 3 3 3 3 3 3>; + qcom,sde-vbif-memtype-1 = <3 3 3 3 3 3>; + + qcom,sde-dram-channels = <2>; + qcom,sde-num-nrt-paths = <1>; + + qcom,sde-max-bw-high-kbps = <9000000>; + qcom,sde-max-bw-low-kbps = <9000000>; + + qcom,sde-core-ib-ff = "1.1"; + qcom,sde-core-clk-ff = "1.0"; + qcom,sde-min-core-ib-kbps = <2400000>; + qcom,sde-min-llcc-ib-kbps = <800000>; + qcom,sde-min-dram-ib-kbps = <800000>; + qcom,sde-comp-ratio-rt = "NV12/5/1/1.1 AB24/5/1/1.2 XB24/5/1/1.3"; + qcom,sde-comp-ratio-nrt = "NV12/5/1/1.1 AB24/5/1/1.2 XB24/5/1/1.3"; + qcom,sde-undersized-prefill-lines = <4>; + qcom,sde-xtra-prefill-lines = <5>; + qcom,sde-dest-scale-prefill-lines = <6>; + qcom,sde-macrotile-prefill-lines = <7>; + qcom,sde-yuv-nv12-prefill-lines = <8>; + qcom,sde-linear-prefill-lines = <9>; + qcom,sde-downscaling-prefill-lines = <10>; + qcom,sde-max-per-pipe-bw-kbps = <2400000 2400000 2400000 2400000 + 2400000 2400000 2400000 2400000>; + qcom,sde-amortizable-threshold = <11>; + + qcom,sde-vbif-qos-rt-remap = <3 3 4 4 5 5 6 6>; + qcom,sde-vbif-qos-nrt-remap = <3 3 3 3 3 3 3 3>; + + qcom,sde-sspp-vig-blocks { + qcom,sde-vig-csc-off = <0x320>; + qcom,sde-vig-qseed-off = <0x200>; + qcom,sde-vig-qseed-size = <0x74>; + /* Offset from vig top, version of HSIC */ + qcom,sde-vig-hsic = <0x200 0x00010000>; + qcom,sde-vig-memcolor = <0x200 0x00010000>; + qcom,sde-vig-pcc = <0x1780 0x00010000>; + }; + + qcom,sde-sspp-rgb-blocks { + qcom,sde-rgb-scaler-off = <0x200>; + qcom,sde-rgb-scaler-size = <0x74>; + qcom,sde-rgb-pcc = <0x380 0x00010000>; + }; + + qcom,sde-dspp-blocks { + qcom,sde-dspp-igc = <0x0 0x00010000>; + qcom,sde-dspp-pcc = <0x1700 0x00010000>; + qcom,sde-dspp-gc = <0x17c0 0x00010000>; + qcom,sde-dspp-hsic = <0x0 0x00010000>; + qcom,sde-dspp-memcolor = <0x0 0x00010000>; + qcom,sde-dspp-sixzone = <0x0 0x00010000>; + qcom,sde-dspp-gamut = <0x1600 0x00010000>; + qcom,sde-dspp-dither = <0x0 0x00010000>; + qcom,sde-dspp-hist = <0x0 0x00010000>; + qcom,sde-dspp-vlut = <0x0 0x00010000>; + }; + + qcom,sde-mixer-blocks { + qcom,sde-mixer-gc = <0x3c0 0x00010000>; + }; + + qcom,msm-hdmi-audio-rx { + compatible = "qcom,msm-hdmi-audio-codec-rx"; + }; + + qcom,sde-inline-rotator = <&mdss_rotator 0>; + qcom,sde-inline-rot-xin = <10 11>; + qcom,sde-inline-rot-xin-type = "sspp", "wb"; + qcom,sde-inline-rot-clk-ctrl = <0x2bc 0x8>, <0x2bc 0xc>; + + qcom,platform-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + qcom,platform-supply-entry@0 { + reg = <0>; + qcom,supply-name = "vdd"; + qcom,supply-min-voltage = <0>; + qcom,supply-max-voltage = <0>; + qcom,supply-enable-load = <0>; + qcom,supply-disable-load = <0>; + qcom,supply-pre-on-sleep = <0>; + qcom,supply-post-on-sleep = <0>; + qcom,supply-pre-off-sleep = <0>; + qcom,supply-post-off-sleep = <0>; + }; + }; + + qcom,sde-data-bus { + qcom,msm-bus,name = "mdss_sde"; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <3>; + qcom,msm-bus,vectors-KBps = + <22 512 0 0>, <23 512 0 0>, <25 512 0 0>, + <22 512 0 6400000>, <23 512 0 6400000>, + <25 512 0 6400000>, + <22 512 0 6400000>, <23 512 0 6400000>, + <25 512 0 6400000>; + }; + qcom,sde-llcc-bus { + qcom,msm-bus,name = "mdss_sde_llcc"; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,vectors-KBps = + <132 770 0 0>, + <132 770 0 6400000>, + <132 770 0 6400000>; + }; + qcom,sde-ebi-bus { + qcom,msm-bus,name = "mdss_sde_ebi"; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,vectors-KBps = + <129 512 0 0>, + <129 512 0 6400000>, + <129 512 0 6400000>; + }; + + qcom,sde-reg-bus { + /* Reg Bus Scale Settings */ + qcom,msm-bus,name = "mdss_reg"; + qcom,msm-bus,num-cases = <4>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,active-only; + qcom,msm-bus,vectors-KBps = + <1 590 0 0>, + <1 590 0 76800>, + <1 590 0 160000>, + <1 590 0 320000>; + }; + + smmu_kms_unsec: qcom,smmu_kms_unsec_cb { + compatible = "qcom,smmu_sde_unsec"; + iommus = <&mmss_smmu 0>; + }; + + smmu_kms_sec: qcom,smmu_kms_sec_cb { + compatible = "qcom,smmu_sde_sec"; + iommus = <&mmss_smmu 1>; + }; + }; diff --git a/Documentation/devicetree/bindings/drm/msm/mdss-dsi-panel.txt b/Documentation/devicetree/bindings/drm/msm/mdss-dsi-panel.txt new file mode 100644 index 0000000000000000000000000000000000000000..a965e0e2105e7c625632bbe01ae5447f49e8c553 --- /dev/null +++ b/Documentation/devicetree/bindings/drm/msm/mdss-dsi-panel.txt @@ -0,0 +1,772 @@ +QTI mdss-dsi-panel + +mdss-dsi-panel is a dsi panel device which supports panels that +are compatible with MIPI display serial interface specification. + +Required properties: +- compatible: This property applies to DSI V2 panels only. + This property should not be added for panels + that work based on version "V6.0" + DSI panels that are of different versions + are initialized by the drivers for dsi controller. + This property specifies the version + for DSI HW that this panel will work with + "qcom,dsi-panel-v2" = DSI V2.0 +- status: This property applies to DSI V2 panels only. + This property should not be added for panels + that work based on version "V6.0" + DSI panels that are of different versions + are initialized by the drivers for dsi controller. + A string that has to be set to "okay/ok" + to enable the panel driver. By default this property + will be set to "disable". Will be set to "ok/okay" + status for specific platforms. +- qcom,mdss-dsi-panel-controller: Specifies the phandle for the DSI controller that + this panel will be mapped to. +- qcom,mdss-dsi-panel-width: Specifies panel width in pixels. +- qcom,mdss-dsi-panel-height: Specifies panel height in pixels. +- qcom,mdss-dsi-bpp: Specifies the panel bits per pixel. + 3 = for rgb111 + 8 = for rgb332 + 12 = for rgb444 + 16 = for rgb565 + 18 = for rgb666 + 24 = for rgb888 +- qcom,mdss-dsi-panel-destination: A string that specifies the destination display for the panel. + "display_1" = DISPLAY_1 + "display_2" = DISPLAY_2 +- qcom,mdss-dsi-panel-timings: An array of length 12 that specifies the PHY + timing settings for the panel. +- qcom,mdss-dsi-panel-timings-8996: An array of length 40 char that specifies the 8996 PHY lane + timing settings for the panel. +- qcom,mdss-dsi-on-command: A byte stream formed by multiple dcs packets base on + qcom dsi controller protocol. + byte 0: dcs data type + byte 1: set to indicate this is an individual packet + (no chain) + byte 2: virtual channel number + byte 3: expect ack from client (dcs read command) + byte 4: wait number of specified ms after dcs command + transmitted + byte 5, 6: 16 bits length in network byte order + byte 7 and beyond: number byte of payload +- qcom,mdss-dsi-off-command: A byte stream formed by multiple dcs packets base on + qcom dsi controller protocol. + byte 0: dcs data type + byte 1: set to indicate this is an individual packet + (no chain) + byte 2: virtual channel number + byte 3: expect ack from client (dcs read command) + byte 4: wait number of specified ms after dcs command + transmitted + byte 5, 6: 16 bits length in network byte order + byte 7 and beyond: number byte of payload +- qcom,mdss-dsi-post-panel-on-command: same as "qcom,mdss-dsi-on-command" except commands are + sent after displaying an image. + +Note, if a short DCS packet(i.e packet with Byte 0:dcs data type as 05) mentioned in +qcom,mdss-dsi-on-command/qcom,mdss-dsi-off-command stream fails to transmit, +then 3 options can be tried. + 1. Send the packet as a long packet instead + Byte 0: dcs data type = 05 (DCS short Packet) + Byte 0: dcs data type = 29 (DCS long Packet) + 2. Send the packet in one burst by prepending with the next packet in packet stream + Byte 1 = 01 (indicates this is an individual packet) + Byte 1 = 00 (indicates this will be appended to the next + individual packet in the packet stream) + 3. Prepend a NULL packet to the short packet and send both in one burst instead of + combining multiple short packets and sending them in one burst. + +Optional properties: +- qcom,mdss-dsi-panel-name: A string used as a descriptive name of the panel +- qcom,mdss-dsi-panel-phy-timings: An array of length 'n' char that specifies the DSI PHY lane + timing settings for the panel. This is specific to SDE DRM driver. + The value of 'n' depends on the DSI PHY h/w revision and parsing this + property properly will be taken care in the DSI PHY DRM driver. +- qcom,cmd-sync-wait-broadcast: Boolean used to broadcast dcs command to panels. +- qcom,mdss-dsi-fbc-enable: Boolean used to enable frame buffer compression mode. +- qcom,mdss-dsi-fbc-slice-height: Slice height(in lines) of compressed block. + Expressed as power of 2. To set as 128 lines, + this should be set to 7. +- qcom,mdss-dsi-fbc-2d-pred-mode: Boolean to enable 2D map prediction. +- qcom,mdss-dsi-fbc-ver2-mode: Boolean to enable FBC 2.0 that supports 1/3 + compression. +- qcom,mdss-dsi-fbc-bpp: Compressed bpp supported by the panel. + Specified color order is used as default value. +- qcom,mdss-dsi-fbc-packing: Component packing. + 0 = default value. +- qcom,mdss-dsi-fbc-quant-error: Boolean used to enable quantization error calculation. +- qcom,mdss-dsi-fbc-bias: Bias for CD. + 0 = default value. +- qcom,mdss-dsi-fbc-pat-mode: Boolean used to enable PAT mode. +- qcom,mdss-dsi-fbc-vlc-mode: Boolean used to enable VLC mode. +- qcom,mdss-dsi-fbc-bflc-mode: Boolean used to enable BFLC mode. +- qcom,mdss-dsi-fbc-h-line-budget: Per line extra budget. + 0 = default value. +- qcom,mdss-dsi-fbc-budget-ctrl: Extra budget level. + 0 = default value. +- qcom,mdss-dsi-fbc-block-budget: Per block budget. + 0 = default value. +- qcom,mdss-dsi-fbc-lossless-threshold: Lossless mode threshold. + 0 = default value. +- qcom,mdss-dsi-fbc-lossy-threshold: Lossy mode threshold. + 0 = default value. +- qcom,mdss-dsi-fbc-rgb-threshold: Lossy RGB threshold. + 0 = default value. +- qcom,mdss-dsi-fbc-lossy-mode-idx: Lossy mode index value. + 0 = default value. +- qcom,mdss-dsi-fbc-max-pred-err: Max quantization prediction error. + 0 = default value +- qcom,mdss-dsi-h-back-porch: Horizontal back porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-h-front-porch: Horizontal front porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-h-pulse-width: Horizontal pulse width. + 2 = default value. +- qcom,mdss-dsi-h-sync-skew: Horizontal sync skew value. + 0 = default value. +- qcom,mdss-dsi-v-back-porch: Vertical back porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-v-front-porch: Vertical front porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-v-pulse-width: Vertical pulse width. + 2 = default value. +- qcom,mdss-dsi-h-left-border: Horizontal left border in pixel. + 0 = default value +- qcom,mdss-dsi-h-right-border: Horizontal right border in pixel. + 0 = default value +- qcom,mdss-dsi-v-top-border: Vertical top border in pixel. + 0 = default value +- qcom,mdss-dsi-v-bottom-border: Vertical bottom border in pixel. + 0 = default value +- qcom,mdss-dsi-underflow-color: Specifies the controller settings for the + panel under flow color. + 0xff = default value. +- qcom,mdss-dsi-border-color: Defines the border color value if border is present. + 0 = default value. +- qcom,mdss-dsi-panel-jitter: Panel jitter value is expressed in terms of numerator + and denominator. It contains two u32 values - numerator + followed by denominator. The jitter configurition causes + the early wakeup if panel needs to adjust before vsync. + Default jitter value is 2.0%. Max allowed value is 10%. +- qcom,mdss-dsi-panel-prefill-lines: An integer value defines the panel prefill lines required to + calculate the backoff time of rsc. + Default value is 16 lines. Max allowed value is vtotal. +- qcom,mdss-dsi-pan-enable-dynamic-fps: Boolean used to enable change in frame rate dynamically. +- qcom,mdss-dsi-pan-fps-update: A string that specifies when to change the frame rate. + "dfps_suspend_resume_mode"= FPS change request is + implemented during suspend/resume. + "dfps_immediate_clk_mode" = FPS change request is + implemented immediately using DSI clocks. + "dfps_immediate_porch_mode_hfp" = FPS change request is + implemented immediately by changing panel horizontal + front porch values. + "dfps_immediate_porch_mode_vfp" = FPS change request is + implemented immediately by changing panel vertical + front porch values. +- qcom,min-refresh-rate: Minimum refresh rate supported by the panel. +- qcom,max-refresh-rate: Maximum refresh rate supported by the panel. If max refresh + rate is not specified, then the frame rate of the panel in + qcom,mdss-dsi-panel-framerate is used. +- qcom,mdss-dsi-bl-pmic-control-type: A string that specifies the implementation of backlight + control for this panel. + "bl_ctrl_pwm" = Backlight controlled by PWM gpio. + "bl_ctrl_wled" = Backlight controlled by WLED. + "bl_ctrl_dcs" = Backlight controlled by DCS commands. + other: Unknown backlight control. (default) +- qcom,mdss-dsi-bl-pwm-pmi: Boolean to indicate that PWM control is through second pmic chip. +- qcom,mdss-dsi-bl-pmic-bank-select: LPG channel for backlight. + Required if backlight pmic control type is PWM +- qcom,mdss-dsi-bl-pmic-pwm-frequency: PWM period in microseconds. + Required if backlight pmic control type is PWM +- qcom,mdss-dsi-pwm-gpio: PMIC gpio binding to backlight. + Required if backlight pmic control type is PWM +- qcom,mdss-dsi-bl-min-level: Specifies the min backlight level supported by the panel. + 0 = default value. +- qcom,mdss-dsi-bl-max-level: Specifies the max backlight level supported by the panel. + 255 = default value. +- qcom,mdss-brightness-max-level: Specifies the max brightness level supported. + 255 = default value. +- qcom,mdss-dsi-interleave-mode: Specifies interleave mode. + 0 = default value. +- qcom,mdss-dsi-panel-type: Specifies the panel operating mode. + "dsi_video_mode" = enable video mode (default). + "dsi_cmd_mode" = enable command mode. +- qcom,5v-boost-gpio: Specifies the panel gpio for display 5v boost. +- qcom,mdss-dsi-te-check-enable: Boolean to enable Tear Check configuration. +- qcom,mdss-dsi-te-using-wd: Boolean entry enables the watchdog timer support to generate the vsync signal + for command mode panel. By default, panel TE will be used to generate the vsync. +- qcom,mdss-dsi-te-using-te-pin: Boolean to specify whether using hardware vsync. +- qcom,mdss-dsi-te-pin-select: Specifies TE operating mode. + 0 = TE through embedded dcs command + 1 = TE through TE gpio pin. (default) +- qcom,mdss-dsi-te-dcs-command: Inserts the dcs command. + 1 = default value. +- qcom,mdss-dsi-wr-mem-start: DCS command for write_memory_start. + 0x2c = default value. +- qcom,mdss-dsi-wr-mem-continue: DCS command for write_memory_continue. + 0x3c = default value. +- qcom,mdss-dsi-h-sync-pulse: Specifies the pulse mode option for the panel. + 0 = Don't send hsa/he following vs/ve packet(default) + 1 = Send hsa/he following vs/ve packet +- qcom,mdss-dsi-hfp-power-mode: Boolean to determine DSI lane state during + horizontal front porch (HFP) blanking period. +- qcom,mdss-dsi-hbp-power-mode: Boolean to determine DSI lane state during + horizontal back porch (HBP) blanking period. +- qcom,mdss-dsi-hsa-power-mode: Boolean to determine DSI lane state during + horizontal sync active (HSA) mode. +- qcom,mdss-dsi-last-line-interleave Boolean to determine if last line + interleave flag needs to be enabled. +- qcom,mdss-dsi-bllp-eof-power-mode: Boolean to determine DSI lane state during + blanking low power period (BLLP) EOF mode. +- qcom,mdss-dsi-bllp-power-mode: Boolean to determine DSI lane state during + blanking low power period (BLLP) mode. +- qcom,mdss-dsi-traffic-mode: Specifies the panel traffic mode. + "non_burst_sync_pulse" = non burst with sync pulses (default). + "non_burst_sync_event" = non burst with sync start event. + "burst_mode" = burst mode. +- qcom,mdss-dsi-pixel-packing: Specifies if pixel packing is used (in case of RGB666). + "tight" = Tight packing (default value). + "loose" = Loose packing. +- qcom,mdss-dsi-virtual-channel-id: Specifies the virtual channel identefier. + 0 = default value. +- qcom,mdss-dsi-color-order: Specifies the R, G and B channel ordering. + "rgb_swap_rgb" = DSI_RGB_SWAP_RGB (default value) + "rgb_swap_rbg" = DSI_RGB_SWAP_RBG + "rgb_swap_brg" = DSI_RGB_SWAP_BRG + "rgb_swap_grb" = DSI_RGB_SWAP_GRB + "rgb_swap_gbr" = DSI_RGB_SWAP_GBR +- qcom,mdss-dsi-lane-0-state: Boolean that specifies whether data lane 0 is enabled. +- qcom,mdss-dsi-lane-1-state: Boolean that specifies whether data lane 1 is enabled. +- qcom,mdss-dsi-lane-2-state: Boolean that specifies whether data lane 2 is enabled. +- qcom,mdss-dsi-lane-3-state: Boolean that specifies whether data lane 3 is enabled. +- qcom,mdss-dsi-t-clk-post: Specifies the byte clock cycles after mode switch. + 0x03 = default value. +- qcom,mdss-dsi-t-clk-pre: Specifies the byte clock cycles before mode switch. + 0x24 = default value. +- qcom,mdss-dsi-stream: Specifies the packet stream to be used. + 0 = stream 0 (default) + 1 = stream 1 +- qcom,mdss-dsi-mdp-trigger: Specifies the trigger mechanism to be used for MDP path. + "none" = no trigger + "trigger_te" = Tear check signal line used for trigger + "trigger_sw" = Triggered by software (default) + "trigger_sw_te" = Software trigger and TE +- qcom,mdss-dsi-dma-trigger: Specifies the trigger mechanism to be used for DMA path. + "none" = no trigger + "trigger_te" = Tear check signal line used for trigger + "trigger_sw" = Triggered by software (default) + "trigger_sw_seof" = Software trigger and start/end of frame trigger. + "trigger_sw_te" = Software trigger and TE +- qcom,mdss-dsi-panel-framerate: Specifies the frame rate for the panel. + 60 = 60 frames per second (default) +- qcom,mdss-dsi-panel-clockrate: A 64 bit value specifies the panel clock speed in Hz. + 0 = default value. +- qcom,mdss-mdp-transfer-time-us: Specifies the dsi transfer time for command mode + panels in microseconds. Driver uses this number to adjust + the clock rate according to the expected transfer time. + Increasing this value would slow down the mdp processing + and can result in slower performance. + Decreasing this value can speed up the mdp processing, + but this can also impact power consumption. + As a rule this time should not be higher than the time + that would be expected with the processing at the + dsi link rate since anyways this would be the maximum + transfer time that could be achieved. + If ping pong split enabled, this time should not be higher + than two times the dsi link rate time. + 14000 = default value. +- qcom,mdss-dsi-on-command-state: String that specifies the ctrl state for sending ON commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-off-command-state: String that specifies the ctrl state for sending OFF commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-post-mode-switch-on-command-state: String that specifies the ctrl state for sending ON commands post mode switch. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-pan-physical-width-dimension: Specifies panel physical width in mm which corresponds + to the physical width in the framebuffer information. +- qcom,mdss-pan-physical-height-dimension: Specifies panel physical height in mm which corresponds + to the physical height in the framebuffer information. +- qcom,mdss-dsi-mode-sel-gpio-state: String that specifies the lcd mode for panel + (such as single-port/dual-port), if qcom,panel-mode-gpio + binding is defined in dsi controller. + "dual_port" = Set GPIO to LOW + "single_port" = Set GPIO to HIGH + "high" = Set GPIO to HIGH + "low" = Set GPIO to LOW + The default value is "dual_port". +- qcom,mdss-tear-check-disable: Boolean to disable mdp tear check. Tear check is enabled by default to avoid + tearing. Other tear-check properties are ignored if this property is present. + The below tear check configuration properties can be individually tuned if + tear check is enabled. +- qcom,mdss-tear-check-sync-cfg-height: Specifies the vertical total number of lines. + The default value is 0xfff0. +- qcom,mdss-tear-check-sync-init-val: Specifies the init value at which the read pointer gets loaded + at vsync edge. The reader pointer refers to the line number of + panel buffer that is currently being updated. + The default value is panel height. +- qcom,mdss-tear-check-sync-threshold-start: + Allows the first ROI line write to an panel when read pointer is + between the range of ROI start line and ROI start line plus this + setting. + The default value is 4. +- qcom,mdss-tear-check-sync-threshold-continue: + The minimum number of lines the write pointer needs to be + above the read pointer so that it is safe to write to the panel. + (This check is not done for the first ROI line write of an update) + The default value is 4. +- qcom,mdss-tear-check-start-pos: Specify the y position from which the start_threshold value is + added and write is kicked off if the read pointer falls within that + region. + The default value is panel height. +- qcom,mdss-tear-check-rd-ptr-trigger-intr: + Specify the read pointer value at which an interrupt has to be + generated. + The default value is panel height + 1. +- qcom,mdss-tear-check-frame-rate: Specify the value to be a real frame rate(fps) x 100 factor to tune the + timing of TE simulation with more precision. + The default value is 6000 with 60 fps. +- qcom,mdss-dsi-reset-sequence: An array that lists the + sequence of reset gpio values and sleeps + Each command will have the format defined + as below: + --> Reset GPIO value + --> Sleep value (in ms) +- qcom,partial-update-enabled: String used to enable partial + panel update for command mode panels. + "none": partial update is disabled + "single_roi": default enable mode, only single roi is sent to panel + "dual_roi": two rois are merged into one big roi. Panel ddic should be able + to process two roi's along with the DCS command to send two rois. + disabled if property is not specified. +- qcom,mdss-dsi-horizontal-line-idle: List of width ranges (EC - SC) in pixels indicating + additional idle time in dsi clock cycles that is needed + to compensate for smaller line width. +- qcom,partial-update-roi-merge: Boolean indicates roi combination is need + and function has been provided for dcs + 2A/2B command. +- qcom,dcs-cmd-by-left: Boolean to indicate that dcs command are sent + through the left DSI controller only in a dual-dsi configuration +- qcom,mdss-dsi-panel-hdr-enabled: Boolean to indicate HDR support in panel. +- qcom,mdss-dsi-panel-hdr-color-primaries: + Array of 8 unsigned integers denoting chromaticity of panel.These + values are specified in nits units. The value range is 0 through 50000. + To obtain real chromacity, these values should be divided by factor of + 50000. The structure of array is defined in below order + value 1: x value of white chromaticity of display panel + value 2: y value of white chromaticity of display panel + value 3: x value of red chromaticity of display panel + value 4: y value of red chromaticity of display panel + value 5: x value of green chromaticity of display panel + value 6: y value of green chromaticity of display panel + value 7: x value of blue chromaticity of display panel + value 8: y value of blue chromaticity of display panel +- qcom,mdss-dsi-panel-peak-brightness: Maximum brightness supported by panel.In absence of maximum value + typical value becomes peak brightness. Value is specified in nits units. + To obtain real peak brightness, this value should be divided by factor of + 10000. +- qcom,mdss-dsi-panel-blackness-level: Blackness level supported by panel. Blackness level is defined as + ratio of peak brightness to contrast. Value is specified in nits units. + To obtain real blackness level, this value should be divided by factor of + 10000. +- qcom,mdss-dsi-lp11-init: Boolean used to enable the DSI clocks and data lanes (low power 11) + before issuing hardware reset line. +- qcom,mdss-dsi-init-delay-us: Delay in microseconds(us) before performing any DSI activity in lp11 + mode. This master delay (t_init_delay as per DSI spec) should be sum + of DSI internal delay to reach fuctional after power up and minimum + delay required by panel to reach functional. +- qcom,mdss-dsi-rx-eot-ignore: Boolean used to enable ignoring end of transmission packets. +- qcom,mdss-dsi-tx-eot-append: Boolean used to enable appending end of transmission packets. +- qcom,ulps-enabled: Boolean to enable support for Ultra Low Power State (ULPS) mode. +- qcom,suspend-ulps-enabled: Boolean to enable support for ULPS mode for panels during suspend state. +- qcom,panel-roi-alignment: Specifies the panel ROI alignment restrictions on its + left, top, width, height alignments and minimum width and + height values +- qcom,esd-check-enabled: Boolean used to enable ESD recovery feature. +- qcom,mdss-dsi-panel-status-command: A byte stream formed by multiple dcs packets based on + qcom dsi controller protocol, to read the panel status. + This value is used to kick in the ESD recovery. + byte 0: dcs data type + byte 1: set to indicate this is an individual packet + (no chain) + byte 2: virtual channel number + byte 3: expect ack from client (dcs read command) + byte 4: wait number of specified ms after dcs command + transmitted + byte 5, 6: 16 bits length in network byte order + byte 7 and beyond: number byte of payload +- qcom,mdss-dsi-panel-status-command-mode: + String that specifies the ctrl state for reading the panel status. + "dsi_lp_mode" = DSI low power mode + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-lp1-command: An optional byte stream to request low + power mode on a panel +- qcom,mdss-dsi-lp1-command-mode: String that specifies the ctrl state for + setting the panel power mode. + "dsi_lp_mode" = DSI low power mode + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-lp2-command: An optional byte stream to request ultra + low power mode on a panel +- qcom,mdss-dsi-lp2-command-mode: String that specifies the ctrl state for + setting the panel power mode. + "dsi_lp_mode" = DSI low power mode + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-nolp-command: An optional byte stream to disable low + power and ultra low power panel modes +- qcom,mdss-dsi-nolp-command-mode: String that specifies the ctrl state for + setting the panel power mode. + "dsi_lp_mode" = DSI low power mode + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-panel-status-check-mode:Specifies the panel status check method for ESD recovery. + "bta_check" = Uses BTA to check the panel status + "reg_read" = Reads panel status register to check the panel status + "reg_read_nt35596" = Reads panel status register to check the panel + status for NT35596 panel. + "te_signal_check" = Uses TE signal behaviour to check the panel status +- qcom,mdss-dsi-panel-status-read-length: Integer array that specify the expected read-back length of values + for each of panel registers. Each length is corresponding to number of + returned parameters of register introduced in specification. +- qcom,mdss-dsi-panel-status-valid-params: Integer array that specify the valid returned values which need to check + for each of register. + Some panel need only check the first few values returned from panel. + So: if this property is the same to qcom,mdss-dsi-panel-status-read-length, + then just ignore this one. +- qcom,mdss-dsi-panel-status-value: Multiple integer arrays, each specifies the values of the panel status register + which is used to check the panel status. The size of each array is the sum of + length specified in qcom,mdss-dsi-panel-status-read-length, and must be equal. + This can cover that Some panel may return several alternative values. +- qcom,mdss-dsi-panel-max-error-count: Integer value that specifies the maximum number of errors from register + read that can be ignored before treating that the panel has gone bad. +- qcom,dynamic-mode-switch-enabled: Boolean used to mention whether panel supports + dynamic switching from video mode to command mode + and vice versa. +- qcom,dynamic-mode-switch-type: A string specifies how to perform dynamic mode switch. + If qcom,dynamic-mode-switch-enabled is set and no string specified, default value is + dynamic-switch-suspend-resume. + "dynamic-switch-suspend-resume"= Switch using suspend/resume. Panel will + go blank during transition. + "dynamic-switch-immediate"= Switch on next frame update. Panel will + not go blank for this transition. + "dynamic-resolution-switch-immediate"= Switch the panel resolution. Panel will + not go blank for this transition. +- qcom,mdss-dsi-post-mode-switch-on-command: Multiple dcs packets used for turning on DSI panel + after panel has switch modes. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,video-to-cmd-mode-switch-commands: List of commands that need to be sent + to panel in order to switch from video mode to command mode dynamically. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,cmd-to-video-mode-switch-commands: List of commands that need to be sent + to panel in order to switch from command mode to video mode dynamically. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,send-pps-before-switch: Boolean propety to indicate when PPS commands should be sent, + either before or after switch commands during dynamic resolution + switch in DSC panels. If the property is not present, the default + behavior is to send PPS commands after the switch commands. +- qcom,mdss-dsi-panel-orientation: String used to indicate orientation of panel + "180" = panel is flipped in both horizontal and vertical directions + "hflip" = panel is flipped in horizontal direction + "vflip" = panel is flipped in vertical direction +- qcom,panel-ack-disabled: A boolean property to indicate, whether we need to wait for any ACK from the panel + for any commands that we send. +- qcom,mdss-dsi-force-clock-lane-hs: Boolean to force dsi clock lanes to HS mode always. + +- qcom,compression-mode: Select compression mode for panel. + "fbc" - frame buffer compression + "dsc" - display stream compression. + If "dsc" compression is used then config subnodes needs to be defined. +- qcom,panel-supply-entries: A node that lists the elements of the supply used to + power the DSI panel. There can be more than one instance + of this binding, in which case the entry would be appended + with the supply entry index. For a detailed description of + fields in the supply entry, refer to the qcom,ctrl-supply-entries + binding above. +- qcom,mdss-dsc-version: An 8 bit value indicates the DSC version supported by panel. Bits[0.3] + provides information about minor version while Bits[4.7] provides + major version information. It supports only DSC rev 1(Major).1(Minor) + right now. +- qcom,mdss-dsc-scr-version: Each DSC version can have multiple SCR. This 8 bit value indicates + current SCR revision information supported by panel. +- qcom,mdss-dsc-encoders: An integer value indicating how many DSC encoders should be used + to drive data stream to DSI. + Default value is 1 and max value is 2. + 2 encoder should be used only if qcom,mdss-lm-split or + qcom,split-mode with pingpong-split is used. +- qcom,mdss-dsc-slice-height: An integer value indicates the dsc slice height. +- qcom,mdss-dsc-slice-width: An integer value indicates the dsc slice width. + Multiple of slice width should be equal to panel-width. + Maximum 2 slices per DSC encoder can be used so if 2 DSC encoders + are used then minimum slice width is equal to panel-width/4. +- qcom,mdss-dsc-slice-per-pkt: An integer value indicates the slice per dsi packet. +- qcom,mdss-dsc-bit-per-component: An integer value indicates the bits per component before compression. +- qcom,mdss-dsc-bit-per-pixel: An integer value indicates the bits per pixel after compression. +- qcom,mdss-dsc-block-prediction-enable: A boolean value to enable/disable the block prediction at decoder. +- qcom,mdss-dsc-config-by-manufacture-cmd: A boolean to indicates panel use manufacture command to setup pps + instead of standard dcs type 0x0A. +- qcom,display-topology: Array of u32 values which specifies the list of topologies available + for the display. A display topology is defined by a + set of 3 values in the order: + - number of mixers + - number of compression encoders + - number of interfaces + Therefore, the array should always contain a tuple of 3 elements. +- qcom,default-topology-index: An u32 value which indexes the topology set + specified by the node "qcom,display-topology" + to identify the default topology for the + display. The first set is indexed by the + value 0. + +Required properties for sub-nodes: None +Optional properties: +- qcom,dba-panel: Indicates whether the current panel is used as a display bridge + to a non-DSI interface. +- qcom,bridge-name: A string to indicate the name of the bridge chip connected to DSI. qcom,bridge-name + is required if qcom,dba-panel is defined for the panel. +- qcom,adjust-timer-wakeup-ms: An integer value to indicate the timer delay(in ms) to accommodate + s/w delay while configuring the event timer wakeup logic. + +- qcom,mdss-dsi-display-timings: Parent node that lists the different resolutions that the panel supports. + Each child represents timings settings for a specific resolution. +- qcom,mdss-dsi-post-init-delay: Specifies required number of frames to wait so that panel can be functional + to show proper display. + +Additional properties added to the second level nodes that represent timings properties: +- qcom,mdss-dsi-timing-default: Property that specifies the current child as the default + timing configuration that will be used. +- qcom,mdss-dsi-timing-switch-command: List of commands that need to be sent + to panel when the resolution/timing switch happens dynamically. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,mdss-dsi-timing-switch-command-state: String that specifies the ctrl state for sending resolution switch + commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode + +Note, if a given optional qcom,* binding is not present, then the driver will configure +the default values specified. + +Example: +&mdss_mdp { + dsi_sim_vid: qcom,mdss_dsi_sim_video { + qcom,mdss-dsi-panel-name = "simulator video mode dsi panel"; + qcom,mdss-dsi-panel-controller = <&mdss_dsi0>; + qcom,mdss-dsi-panel-height = <1280>; + qcom,mdss-dsi-panel-width = <720>; + qcom,mdss-dsi-bpp = <24>; + qcom,mdss-dsi-pixel-packing = <0>; + qcom,mdss-dsi-panel-destination = "display_1"; + qcom,cmd-sync-wait-broadcast; + qcom,mdss-dsi-fbc-enable; + qcom,mdss-dsi-fbc-slice-height = <5>; + qcom,mdss-dsi-fbc-2d-pred-mode; + qcom,mdss-dsi-fbc-ver2-mode; + qcom,mdss-dsi-fbc-bpp = <0>; + qcom,mdss-dsi-fbc-packing = <0>; + qcom,mdss-dsi-fbc-quant-error; + qcom,mdss-dsi-fbc-bias = <0>; + qcom,mdss-dsi-fbc-pat-mode; + qcom,mdss-dsi-fbc-vlc-mode; + qcom,mdss-dsi-fbc-bflc-mode; + qcom,mdss-dsi-fbc-h-line-budget = <0>; + qcom,mdss-dsi-fbc-budget-ctrl = <0>; + qcom,mdss-dsi-fbc-block-budget = <0>; + qcom,mdss-dsi-fbc-lossless-threshold = <0>; + qcom,mdss-dsi-fbc-lossy-threshold = <0>; + qcom,mdss-dsi-fbc-rgb-threshold = <0>; + qcom,mdss-dsi-fbc-lossy-mode-idx = <0>; + qcom,mdss-dsi-fbc-max-pred-err = <2>; + qcom,mdss-dsi-h-front-porch = <140>; + qcom,mdss-dsi-h-back-porch = <164>; + qcom,mdss-dsi-h-pulse-width = <8>; + qcom,mdss-dsi-h-sync-skew = <0>; + qcom,mdss-dsi-v-back-porch = <6>; + qcom,mdss-dsi-v-front-porch = <1>; + qcom,mdss-dsi-v-pulse-width = <1>; + qcom,mdss-dsi-h-left-border = <0>; + qcom,mdss-dsi-h-right-border = <0>; + qcom,mdss-dsi-v-top-border = <0>; + qcom,mdss-dsi-v-bottom-border = <0>; + qcom,mdss-dsi-border-color = <0>; + qcom,mdss-dsi-underflow-color = <0xff>; + qcom,mdss-dsi-bl-min-level = <1>; + qcom,mdss-dsi-bl-max-level = < 15>; + qcom,mdss-brightness-max-level = <255>; + qcom,mdss-dsi-interleave-mode = <0>; + qcom,mdss-dsi-panel-type = "dsi_video_mode"; + qcom,mdss-dsi-te-check-enable; + qcom,mdss-dsi-te-using-wd; + qcom,mdss-dsi-te-using-te-pin; + qcom,mdss-dsi-te-dcs-command = <1>; + qcom,mdss-dsi-wr-mem-continue = <0x3c>; + qcom,mdss-dsi-wr-mem-start = <0x2c>; + qcom,mdss-dsi-te-pin-select = <1>; + qcom,mdss-dsi-h-sync-pulse = <1>; + qcom,mdss-dsi-hfp-power-mode; + qcom,mdss-dsi-hbp-power-mode; + qcom,mdss-dsi-hsa-power-mode; + qcom,mdss-dsi-bllp-eof-power-mode; + qcom,mdss-dsi-bllp-power-mode; + qcom,mdss-dsi-last-line-interleave; + qcom,mdss-dsi-traffic-mode = <0>; + qcom,mdss-dsi-virtual-channel-id = <0>; + qcom,mdss-dsi-color-order = <0>; + qcom,mdss-dsi-lane-0-state; + qcom,mdss-dsi-lane-1-state; + qcom,mdss-dsi-lane-2-state; + qcom,mdss-dsi-lane-3-state; + qcom,mdss-dsi-t-clk-post = <0x20>; + qcom,mdss-dsi-t-clk-pre = <0x2c>; + qcom,mdss-dsi-stream = <0>; + qcom,mdss-dsi-mdp-trigger = <0>; + qcom,mdss-dsi-dma-trigger = <0>; + qcom,mdss-dsi-panel-framerate = <60>; + qcom,mdss-dsi-panel-clockrate = <424000000>; + qcom,mdss-mdp-transfer-time-us = <12500>; + qcom,mdss-dsi-panel-timings = [7d 25 1d 00 37 33 + 22 27 1e 03 04 00]; + qcom,mdss-dsi-panel-timings-8996 = [23 20 06 09 05 03 04 a0 + 23 20 06 09 05 03 04 a0 + 23 20 06 09 05 03 04 a0 + 23 20 06 09 05 03 04 a0 + 23 2e 06 08 05 03 04 a0]; + qcom,mdss-dsi-on-command = [32 01 00 00 00 00 02 00 00 + 29 01 00 00 10 00 02 FF 99]; + qcom,mdss-dsi-on-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-off-command = [22 01 00 00 00 00 00]; + qcom,mdss-dsi-off-command-state = "dsi_hs_mode"; + qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; + qcom,mdss-dsi-pan-enable-dynamic-fps; + qcom,mdss-dsi-pan-fps-update = "dfps_suspend_resume_mode"; + qcom,min-refresh-rate = <30>; + qcom,max-refresh-rate = <60>; + qcom,mdss-dsi-bl-pmic-bank-select = <0>; + qcom,mdss-dsi-bl-pmic-pwm-frequency = <0>; + qcom,mdss-dsi-pwm-gpio = <&pm8941_mpps 5 0>; + qcom,5v-boost-gpio = <&pm8994_gpios 14 0>; + qcom,mdss-pan-physical-width-dimension = <60>; + qcom,mdss-pan-physical-height-dimension = <140>; + qcom,mdss-dsi-mode-sel-gpio-state = "dsc_mode"; + qcom,mdss-tear-check-sync-cfg-height = <0xfff0>; + qcom,mdss-tear-check-sync-init-val = <1280>; + qcom,mdss-tear-check-sync-threshold-start = <4>; + qcom,mdss-tear-check-sync-threshold-continue = <4>; + qcom,mdss-tear-check-start-pos = <1280>; + qcom,mdss-tear-check-rd-ptr-trigger-intr = <1281>; + qcom,mdss-tear-check-frame-rate = <6000>; + qcom,mdss-dsi-reset-sequence = <1 2>, <0 10>, <1 10>; + qcom,partial-update-enabled = "single_roi"; + qcom,dcs-cmd-by-left; + qcom,mdss-dsi-lp11-init; + qcom,mdss-dsi-init-delay-us = <100>; + mdss-dsi-rx-eot-ignore; + mdss-dsi-tx-eot-append; + qcom,ulps-enabled; + qcom,suspend-ulps-enabled; + qcom,panel-roi-alignment = <4 4 2 2 20 20>; + qcom,esd-check-enabled; + qcom,mdss-dsi-panel-status-command = [06 01 00 01 05 00 02 0A 08]; + qcom,mdss-dsi-panel-status-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-panel-status-check-mode = "reg_read"; + qcom,mdss-dsi-panel-status-read-length = <8>; + qcom,mdss-dsi-panel-max-error-count = <3>; + qcom,mdss-dsi-panel-status-value = <0x1c 0x00 0x05 0x02 0x40 0x84 0x06 0x01>; + qcom,dynamic-mode-switch-enabled; + qcom,dynamic-mode-switch-type = "dynamic-switch-immediate"; + qcom,mdss-dsi-post-mode-switch-on-command = [32 01 00 00 00 00 02 00 00 + 29 01 00 00 10 00 02 B0 03]; + qcom,video-to-cmd-mode-switch-commands = [15 01 00 00 00 00 02 C2 0B + 15 01 00 00 00 00 02 C2 08]; + qcom,cmd-to-video-mode-switch-commands = [15 01 00 00 00 00 02 C2 03]; + qcom,send-pps-before-switch; + qcom,panel-ack-disabled; + qcom,mdss-dsi-horizontal-line-idle = <0 40 256>, + <40 120 128>, + <128 240 64>; + qcom,mdss-dsi-panel-orientation = "180" + qcom,mdss-dsi-panel-jitter = <0x8 0x10>; + qcom,mdss-dsi-panel-prefill-lines = <0x10>; + qcom,mdss-dsi-force-clock-lane-hs; + qcom,compression-mode = "dsc"; + qcom,adjust-timer-wakeup-ms = <1>; + qcom,mdss-dsi-display-timings { + wqhd { + qcom,mdss-dsi-timing-default; + qcom,mdss-dsi-panel-width = <720>; + qcom,mdss-dsi-panel-height = <2560>; + qcom,mdss-dsi-h-front-porch = <20>; + qcom,mdss-dsi-h-back-porch = <8>; + qcom,mdss-dsi-h-pulse-width = <8>; + qcom,mdss-dsi-h-sync-skew = <0>; + qcom,mdss-dsi-v-back-porch = <4>; + qcom,mdss-dsi-v-front-porch = <728>; + qcom,mdss-dsi-v-pulse-width = <4>; + qcom,mdss-dsi-panel-framerate = <60>; + qcom,mdss-dsi-panel-timings = [E6 38 26 00 68 6E 2A 3C 2C 03 04 00]; + qcom,mdss-dsi-t-clk-post = <0x02>; + qcom,mdss-dsi-t-clk-pre = <0x2a>; + qcom,mdss-dsi-on-command = [05 01 00 00 a0 00 02 11 00 + 05 01 00 00 02 00 02 29 00]; + qcom,mdss-dsi-on-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-timing-switch-command = [ + 29 00 00 00 00 00 02 B0 04 + 29 00 00 00 00 00 02 F1 00]; + qcom,mdss-dsi-timing-switch-command-state = "dsi_lp_mode"; + + qcom,mdss-dsc-slice-height = <16>; + qcom,mdss-dsc-slice-width = <360>; + qcom,mdss-dsc-slice-per-pkt = <2>; + qcom,mdss-dsc-bit-per-component = <8>; + qcom,mdss-dsc-bit-per-pixel = <8>; + qcom,mdss-dsc-block-prediction-enable; + qcom,mdss-dsc-config-by-manufacture-cmd; + qcom,display-topology = <1 1 1>; + qcom,default-topology-index = <0>; + }; + }; + qcom,panel-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + + qcom,panel-supply-entry@0 { + reg = <0>; + qcom,supply-name = "vdd"; + qcom,supply-min-voltage = <2800000>; + qcom,supply-max-voltage = <2800000>; + qcom,supply-enable-load = <100000>; + qcom,supply-disable-load = <100>; + qcom,supply-pre-on-sleep = <0>; + qcom,supply-post-on-sleep = <0>; + qcom,supply-pre-off-sleep = <0>; + qcom,supply-post-off-sleep = <0>; + }; + + qcom,panel-supply-entry@1 { + reg = <1>; + qcom,supply-name = "vddio"; + qcom,supply-min-voltage = <1800000>; + qcom,supply-max-voltage = <1800000>; + qcom,supply-enable-load = <100000>; + qcom,supply-disable-load = <100>; + qcom,supply-pre-on-sleep = <0>; + qcom,supply-post-on-sleep = <0>; + qcom,supply-pre-off-sleep = <0>; + qcom,supply-post-off-sleep = <0>; + }; + }; + + qcom,dba-panel; + qcom,bridge-name = "adv7533"; + qcom,mdss-dsc-version = <0x11>; + qcom,mdss-dsc-scr-version = <0x1>; + qcom,mdss-dsc-slice-height = <16>; + qcom,mdss-dsc-slice-width = <360>; + qcom,mdss-dsc-slice-per-pkt = <2>; + qcom,mdss-dsc-bit-per-component = <8>; + qcom,mdss-dsc-bit-per-pixel = <8>; + qcom,mdss-dsc-block-prediction-enable; + qcom,mdss-dsc-config-by-manufacture-cmd; + qcom,display-topology = <1 1 1>, + <2 2 1>; + qcom,default-topology-index = <0>; + }; +}; diff --git a/Documentation/devicetree/bindings/drm/msm/sde-dp.txt b/Documentation/devicetree/bindings/drm/msm/sde-dp.txt new file mode 100644 index 0000000000000000000000000000000000000000..ada2eab37e3ec57ba48868e8f450ac65b9bbba9a --- /dev/null +++ b/Documentation/devicetree/bindings/drm/msm/sde-dp.txt @@ -0,0 +1,217 @@ +Qualcomm Technologies, Inc. +sde-dp is the master Display Port device which supports DP host controllers that are compatible with VESA Display Port interface specification. +DP Controller: Required properties: +- compatible: Should be "qcom,dp-display". +- reg: Base address and length of DP hardware's memory mapped regions. +- reg-names: A list of strings that name the list of regs. "dp_ctrl" - DP controller memory region. + "dp_phy" - DP PHY memory region. + "dp_ln_tx0" - USB3 DP PHY combo TX-0 lane memory region. + "dp_ln_tx1" - USB3 DP PHY combo TX-1 lane memory region. + "dp_mmss_cc" - Display Clock Control memory region. + "qfprom_physical" - QFPROM Phys memory region. + "dp_pll" - USB3 DP combo PLL memory region. + "usb3_dp_com" - USB3 DP PHY combo memory region. + "hdcp_physical" - DP HDCP memory region. +- cell-index: Specifies the controller instance. +- clocks: Clocks required for Display Port operation. +- clock-names: Names of the clocks corresponding to handles. Following clocks are required: + "core_aux_clk", "core_usb_ref_clk_src","core_usb_ref_clk", "core_usb_cfg_ahb_clk", + "core_usb_pipe_clk", "ctrl_link_clk", "ctrl_link_iface_clk", "ctrl_crypto_clk", + "ctrl_pixel_clk", "pixel_clk_rcg", "pixel_parent". +- gdsc-supply: phandle to gdsc regulator node. +- vdda-1p2-supply: phandle to vdda 1.2V regulator node. +- vdda-0p9-supply: phandle to vdda 0.9V regulator node. +- interrupt-parent phandle to the interrupt parent device node. +- interrupts: The interrupt signal from the DSI block. +- qcom,aux-en-gpio: Specifies the aux-channel enable gpio. +- qcom,aux-sel-gpio: Specifies the aux-channel select gpio. +- qcom,usbplug-cc-gpio: Specifies the usbplug orientation gpio. +- qcom,aux-cfg0-settings: Specifies the DP AUX configuration 0 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg1-settings: Specifies the DP AUX configuration 1 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg2-settings: Specifies the DP AUX configuration 2 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg3-settings: Specifies the DP AUX configuration 3 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg4-settings: Specifies the DP AUX configuration 4 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg5-settings: Specifies the DP AUX configuration 5 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg6-settings: Specifies the DP AUX configuration 6 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg7-settings: Specifies the DP AUX configuration 7 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg8-settings: Specifies the DP AUX configuration 8 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,aux-cfg9-settings: Specifies the DP AUX configuration 9 settings. The first + entry in this array corresponds to the register offset + within DP AUX, while the remaining entries indicate the + programmable values. +- qcom,max-pclk-frequency-khz: An integer specifying the max. pixel clock in KHz supported by Display Port. +- qcom,dp-usbpd-detection: Phandle for the PMI regulator node for USB PHY PD detection. +- qcom,-supply-entries: A node that lists the elements of the supply used by the a particular "type" of DSI module. The module "types" + can be "core", "ctrl", and "phy". Within the same type, + there can be more than one instance of this binding, + in which case the entry would be appended with the + supply entry index. + e.g. qcom,ctrl-supply-entry@0 + -- qcom,supply-name: name of the supply (vdd/vdda/vddio) + -- qcom,supply-min-voltage: minimum voltage level (uV) + -- qcom,supply-max-voltage: maximum voltage level (uV) + -- qcom,supply-enable-load: load drawn (uA) from enabled supply + -- qcom,supply-disable-load: load drawn (uA) from disabled supply + -- qcom,supply-pre-on-sleep: time to sleep (ms) before turning on + -- qcom,supply-post-on-sleep: time to sleep (ms) after turning on + -- qcom,supply-pre-off-sleep: time to sleep (ms) before turning off + -- qcom,supply-post-off-sleep: time to sleep (ms) after turning off +- pinctrl-names: List of names to assign mdss pin states defined in pinctrl device node + Refer to pinctrl-bindings.txt +- pinctrl-<0..n>: Lists phandles each pointing to the pin configuration node within a pin + controller. These pin configurations are installed in the pinctrl + device node. Refer to pinctrl-bindings.txt + +msm_ext_disp is a device which manages the interaction between external +display interfaces, e.g. Display Port, and the audio subsystem. + +Optional properties: +- qcom,ext-disp: phandle for msm-ext-display module +- compatible: Must be "qcom,msm-ext-disp" + +[Optional child nodes]: These nodes are for devices which are +dependent on msm_ext_disp. If msm_ext_disp is disabled then +these devices will be disabled as well. Ex. Audio Codec device. + +- ext_disp_audio_codec: Node for Audio Codec. +- compatible : "qcom,msm-ext-disp-audio-codec-rx"; + +Example: + ext_disp: qcom,msm-ext-disp { + compatible = "qcom,msm-ext-disp"; + ext_disp_audio_codec: qcom,msm-ext-disp-audio-codec-rx { + compatible = "qcom,msm-ext-disp-audio-codec-rx"; + }; + }; + + sde_dp: qcom,dp_display@0{ + cell-index = <0>; + compatible = "qcom,dp-display"; + + gdsc-supply = <&mdss_core_gdsc>; + vdda-1p2-supply = <&pm8998_l26>; + vdda-0p9-supply = <&pm8998_l1>; + + reg = <0xae90000 0xa84>, + <0x88eaa00 0x200>, + <0x88ea200 0x200>, + <0x88ea600 0x200>, + <0xaf02000 0x1a0>, + <0x780000 0x621c>, + <0x88ea030 0x10>, + <0x88e8000 0x621c>, + <0x0aee1000 0x034>; + reg-names = "dp_ctrl", "dp_phy", "dp_ln_tx0", "dp_ln_tx1", + "dp_mmss_cc", "qfprom_physical", "dp_pll", + "usb3_dp_com", "hdcp_physical"; + + interrupt-parent = <&mdss_mdp>; + interrupts = <12 0>; + + clocks = <&clock_dispcc DISP_CC_MDSS_DP_AUX_CLK>, + <&clock_rpmh RPMH_CXO_CLK>, + <&clock_gcc GCC_USB3_PRIM_CLKREF_CLK>, + <&clock_gcc GCC_USB_PHY_CFG_AHB2PHY_CLK>, + <&clock_gcc GCC_USB3_PRIM_PHY_PIPE_CLK>, + <&clock_dispcc DISP_CC_MDSS_DP_LINK_CLK>, + <&clock_dispcc DISP_CC_MDSS_DP_LINK_INTF_CLK>, + <&clock_dispcc DISP_CC_MDSS_DP_CRYPTO_CLK>, + <&clock_dispcc DISP_CC_MDSS_DP_PIXEL_CLK>, + <&clock_dispcc DISP_CC_MDSS_DP_PIXEL_CLK_SRC>, + <&mdss_dp_pll DP_VCO_DIVIDED_CLK_SRC_MUX>; + clock-names = "core_aux_clk", "core_usb_ref_clk_src", + "core_usb_ref_clk", "core_usb_cfg_ahb_clk", + "core_usb_pipe_clk", "ctrl_link_clk", + "ctrl_link_iface_clk", "ctrl_crypto_clk", + "ctrl_pixel_clk", "pixel_clk_rcg", "pixel_parent"; + + qcom,dp-usbpd-detection = <&pmi8998_pdphy>; + qcom,ext-disp = <&ext_disp>; + + qcom,aux-cfg0-settings = [1c 00]; + qcom,aux-cfg1-settings = [20 13 23 1d]; + qcom,aux-cfg2-settings = [24 00]; + qcom,aux-cfg3-settings = [28 00]; + qcom,aux-cfg4-settings = [2c 0a]; + qcom,aux-cfg5-settings = [30 26]; + qcom,aux-cfg6-settings = [34 0a]; + qcom,aux-cfg7-settings = [38 03]; + qcom,aux-cfg8-settings = [3c bb]; + qcom,aux-cfg9-settings = [40 03]; + qcom,max-pclk-frequency-khz = <593470>; + pinctrl-names = "mdss_dp_active", "mdss_dp_sleep"; + pinctrl-0 = <&sde_dp_aux_active &sde_dp_usbplug_cc_active>; + pinctrl-1 = <&sde_dp_aux_suspend &sde_dp_usbplug_cc_suspend>; + qcom,aux-en-gpio = <&tlmm 43 0>; + qcom,aux-sel-gpio = <&tlmm 51 0>; + qcom,usbplug-cc-gpio = <&tlmm 38 0>; + qcom,core-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + + qcom,core-supply-entry@0 { + reg = <0>; + qcom,supply-name = "gdsc"; + qcom,supply-min-voltage = <0>; + qcom,supply-max-voltage = <0>; + qcom,supply-enable-load = <0>; + qcom,supply-disable-load = <0>; + }; + }; + + qcom,ctrl-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + + qcom,ctrl-supply-entry@0 { + reg = <0>; + qcom,supply-name = "vdda-1p2"; + qcom,supply-min-voltage = <1200000>; + qcom,supply-max-voltage = <1200000>; + qcom,supply-enable-load = <21800>; + qcom,supply-disable-load = <4>; + }; + }; + + qcom,phy-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + + qcom,phy-supply-entry@0 { + reg = <0>; + qcom,supply-name = "vdda-0p9"; + qcom,supply-min-voltage = <880000>; + qcom,supply-max-voltage = <880000>; + qcom,supply-enable-load = <36000>; + qcom,supply-disable-load = <32>; + }; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/drm/msm/sde-dsi.txt b/Documentation/devicetree/bindings/drm/msm/sde-dsi.txt new file mode 100644 index 0000000000000000000000000000000000000000..641cc26fc07e4c771f44faaeb35764ce25c54c60 --- /dev/null +++ b/Documentation/devicetree/bindings/drm/msm/sde-dsi.txt @@ -0,0 +1,102 @@ +Qualcomm Technologies, Inc. + +mdss-dsi is the master DSI device which supports multiple DSI host controllers +that are compatible with MIPI display serial interface specification. + +DSI Controller: +Required properties: +- compatible: Should be "qcom,dsi-ctrl-hw-v". Supported + versions include 1.4, 2.0 and 2.2. + eg: qcom,dsi-ctrl-hw-v1.4, qcom,dsi-ctrl-hw-v2.0, + qcom,dsi-ctrl-hw-v2.2 + And for dsi phy driver: + qcom,dsi-phy-v0.0-hpm, qcom,dsi-phy-v0.0-lpm, + qcom,dsi-phy-v1.0, qcom,dsi-phy-v2.0, + qcom,dsi-phy-v3.0 +- reg: Base address and length of DSI controller's memory + mapped regions. +- reg-names: A list of strings that name the list of regs. + "dsi_ctrl" - DSI controller memory region. + "mmss_misc" - MMSS misc memory region. +- cell-index: Specifies the controller instance. +- clocks: Clocks required for DSI controller operation. +- clock-names: Names of the clocks corresponding to handles. Following + clocks are required: + "mdp_core_clk" + "iface_clk" + "core_mmss_clk" + "bus_clk" + "byte_clk" + "pixel_clk" + "core_clk" + "byte_clk_rcg" + "pixel_clk_rcg" +- gdsc-supply: phandle to gdsc regulator node. +- vdda-supply: phandle to vdda regulator node. +- vcca-supply: phandle to vcca regulator node. +- interrupt-parent phandle to the interrupt parent device node. +- interrupts: The interrupt signal from the DSI block. + +Bus Scaling Data: +- qcom,msm-bus,name: String property describing MDSS client. +- qcom,msm-bus,num-cases: This is the number of bus scaling use cases + defined in the vectors property. This must be + set to <2> for MDSS DSI driver where use-case 0 + is used to remove BW votes from the system. Use + case 1 is used to generate bandwidth requestes + when sending command packets. +- qcom,msm-bus,num-paths: This represents number of paths in each bus + scaling usecase. This value depends on number of + AXI master ports dedicated to MDSS for + particular chipset. +- qcom,msm-bus,vectors-KBps: A series of 4 cell properties, with a format + of (src, dst, ab, ib) which is defined at + Documentation/devicetree/bindings/arm/msm/msm_bus.txt. + DSI driver should always set average bandwidth + (ab) to 0 and always use instantaneous + bandwidth(ib) values. + +Optional properties: +- label: String to describe controller. +- qcom,platform-te-gpio: Specifies the gpio used for TE. +- qcom,dsi-display-active: Current active display +- qcom,dsi-ctrl: handle to dsi controller device +- qcom,dsi-phy: handle to dsi phy device +- qcom,dsi-manager: Specifies dsi manager is present +- qcom,dsi-display: Specifies dsi display is present +- qcom,hdmi-display: Specifies hdmi is present +- qcom,dp-display: Specified dp is present +- qcom,-supply-entries: A node that lists the elements of the supply used by the + a particular "type" of DSI module. The module "types" + can be "core", "ctrl", and "phy". Within the same type, + there can be more than one instance of this binding, + in which case the entry would be appended with the + supply entry index. + e.g. qcom,ctrl-supply-entry@0 + -- qcom,supply-name: name of the supply (vdd/vdda/vddio) + -- qcom,supply-min-voltage: minimum voltage level (uV) + -- qcom,supply-max-voltage: maximum voltage level (uV) + -- qcom,supply-enable-load: load drawn (uA) from enabled supply + -- qcom,supply-disable-load: load drawn (uA) from disabled supply + -- qcom,supply-pre-on-sleep: time to sleep (ms) before turning on + -- qcom,supply-post-on-sleep: time to sleep (ms) after turning on + -- qcom,supply-pre-off-sleep: time to sleep (ms) before turning off + -- qcom,supply-post-off-sleep: time to sleep (ms) after turning off +- qcom,mdss-mdp-transfer-time-us: Specifies the dsi transfer time for command mode + panels in microseconds. Driver uses this number to adjust + the clock rate according to the expected transfer time. + Increasing this value would slow down the mdp processing + and can result in slower performance. + Decreasing this value can speed up the mdp processing, + but this can also impact power consumption. + As a rule this time should not be higher than the time + that would be expected with the processing at the + dsi link rate since anyways this would be the maximum + transfer time that could be achieved. + If ping pong split enabled, this time should not be higher + than two times the dsi link rate time. + If the property is not specified, then the default value is 14000 us. +- qcom,dsi-phy-isolation-enabled: A boolean property enables the phy isolation from dsi + controller. This must be enabled for debugging purpose + only with simulator panel. It should not be enabled for + normal DSI panels. diff --git a/Documentation/devicetree/bindings/drm/msm/sde-wb.txt b/Documentation/devicetree/bindings/drm/msm/sde-wb.txt new file mode 100644 index 0000000000000000000000000000000000000000..863b334e438a704f8ff9396e6bf2ac3f02b0224b --- /dev/null +++ b/Documentation/devicetree/bindings/drm/msm/sde-wb.txt @@ -0,0 +1,23 @@ +QTI Snapdragon Display Engine (SDE) writeback display + +Required properties: +- compatible: "qcom,wb-display" + +Optional properties: +- cell-index: Index of writeback device instance. + Default to 0 if not specified. +- label: String to describe this writeback display. + Default to "unknown" if not specified. + +Example: + +/ { + ... + + sde_wb: qcom,wb-display { + compatible = "qcom,wb-display"; + cell-index = <2>; + label = "wb_display"; + }; + +}; diff --git a/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt b/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt new file mode 100644 index 0000000000000000000000000000000000000000..608b4260a0ab6182c568a3f2196c358ce9efef6d --- /dev/null +++ b/Documentation/devicetree/bindings/fb/mdss-dsi-panel.txt @@ -0,0 +1,742 @@ +Qualcomm mdss-dsi-panel + +mdss-dsi-panel is a dsi panel device which supports panels that +are compatible with MIPI display serial interface specification. + +Required properties: +- compatible: This property applies to DSI V2 panels only. + This property should not be added for panels + that work based on version "V6.0" + DSI panels that are of different versions + are initialized by the drivers for dsi controller. + This property specifies the version + for DSI HW that this panel will work with + "qcom,dsi-panel-v2" = DSI V2.0 +- status: This property applies to DSI V2 panels only. + This property should not be added for panels + that work based on version "V6.0" + DSI panels that are of different versions + are initialized by the drivers for dsi controller. + A string that has to be set to "okay/ok" + to enable the panel driver. By default this property + will be set to "disable". Will be set to "ok/okay" + status for specific platforms. +- qcom,mdss-dsi-panel-controller: Specifies the phandle for the DSI controller that + this panel will be mapped to. +- qcom,mdss-dsi-panel-width: Specifies panel width in pixels. +- qcom,mdss-dsi-panel-height: Specifies panel height in pixels. +- qcom,mdss-dsi-bpp: Specifies the panel bits per pixel. + 3 = for rgb111 + 8 = for rgb332 + 12 = for rgb444 + 16 = for rgb565 + 18 = for rgb666 + 24 = for rgb888 +- qcom,mdss-dsi-panel-destination: A string that specifies the destination display for the panel. + "display_1" = DISPLAY_1 + "display_2" = DISPLAY_2 +- qcom,mdss-dsi-panel-timings: An array of length 12 that specifies the PHY + timing settings for the panel. +- qcom,mdss-dsi-panel-timings-8996: An array of length 40 char that specifies the 8996 PHY lane + timing settings for the panel. +- qcom,mdss-dsi-on-command: A byte stream formed by multiple dcs packets base on + qcom dsi controller protocol. + byte 0: dcs data type + byte 1: set to indicate this is an individual packet + (no chain) + byte 2: virtual channel number + byte 3: expect ack from client (dcs read command) + byte 4: wait number of specified ms after dcs command + transmitted + byte 5, 6: 16 bits length in network byte order + byte 7 and beyond: number byte of payload +- qcom,mdss-dsi-off-command: A byte stream formed by multiple dcs packets base on + qcom dsi controller protocol. + byte 0: dcs data type + byte 1: set to indicate this is an individual packet + (no chain) + byte 2: virtual channel number + byte 3: expect ack from client (dcs read command) + byte 4: wait number of specified ms after dcs command + transmitted + byte 5, 6: 16 bits length in network byte order + byte 7 and beyond: number byte of payload +- qcom,mdss-dsi-post-panel-on-command: same as "qcom,mdss-dsi-on-command" except commands are + sent after displaying an image. + +Note, if a short DCS packet(i.e packet with Byte 0:dcs data type as 05) mentioned in +qcom,mdss-dsi-on-command/qcom,mdss-dsi-off-command stream fails to transmit, +then 3 options can be tried. + 1. Send the packet as a long packet instead + Byte 0: dcs data type = 05 (DCS short Packet) + Byte 0: dcs data type = 29 (DCS long Packet) + 2. Send the packet in one burst by prepending with the next packet in packet stream + Byte 1 = 01 (indicates this is an individual packet) + Byte 1 = 00 (indicates this will be appended to the next + individual packet in the packet stream) + 3. Prepend a NULL packet to the short packet and send both in one burst instead of + combining multiple short packets and sending them in one burst. + +Optional properties: +- qcom,mdss-dsi-panel-name: A string used as a descriptive name of the panel +- qcom,cmd-sync-wait-broadcast: Boolean used to broadcast dcs command to panels. +- qcom,mdss-dsi-fbc-enable: Boolean used to enable frame buffer compression mode. +- qcom,mdss-dsi-fbc-slice-height: Slice height(in lines) of compressed block. + Expressed as power of 2. To set as 128 lines, + this should be set to 7. +- qcom,mdss-dsi-fbc-2d-pred-mode: Boolean to enable 2D map prediction. +- qcom,mdss-dsi-fbc-ver2-mode: Boolean to enable FBC 2.0 that supports 1/3 + compression. +- qcom,mdss-dsi-fbc-bpp: Compressed bpp supported by the panel. + Specified color order is used as default value. +- qcom,mdss-dsi-fbc-packing: Component packing. + 0 = default value. +- qcom,mdss-dsi-fbc-quant-error: Boolean used to enable quantization error calculation. +- qcom,mdss-dsi-fbc-bias: Bias for CD. + 0 = default value. +- qcom,mdss-dsi-fbc-pat-mode: Boolean used to enable PAT mode. +- qcom,mdss-dsi-fbc-vlc-mode: Boolean used to enable VLC mode. +- qcom,mdss-dsi-fbc-bflc-mode: Boolean used to enable BFLC mode. +- qcom,mdss-dsi-fbc-h-line-budget: Per line extra budget. + 0 = default value. +- qcom,mdss-dsi-fbc-budget-ctrl: Extra budget level. + 0 = default value. +- qcom,mdss-dsi-fbc-block-budget: Per block budget. + 0 = default value. +- qcom,mdss-dsi-fbc-lossless-threshold: Lossless mode threshold. + 0 = default value. +- qcom,mdss-dsi-fbc-lossy-threshold: Lossy mode threshold. + 0 = default value. +- qcom,mdss-dsi-fbc-rgb-threshold: Lossy RGB threshold. + 0 = default value. +- qcom,mdss-dsi-fbc-lossy-mode-idx: Lossy mode index value. + 0 = default value. +- qcom,mdss-dsi-fbc-max-pred-err: Max quantization prediction error. + 0 = default value +- qcom,mdss-dsi-h-back-porch: Horizontal back porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-h-front-porch: Horizontal front porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-h-pulse-width: Horizontal pulse width. + 2 = default value. +- qcom,mdss-dsi-h-sync-skew: Horizontal sync skew value. + 0 = default value. +- qcom,mdss-dsi-v-back-porch: Vertical back porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-v-front-porch: Vertical front porch value in pixel. + 6 = default value. +- qcom,mdss-dsi-v-pulse-width: Vertical pulse width. + 2 = default value. +- qcom,mdss-dsi-h-left-border: Horizontal left border in pixel. + 0 = default value +- qcom,mdss-dsi-h-right-border: Horizontal right border in pixel. + 0 = default value +- qcom,mdss-dsi-v-top-border: Vertical top border in pixel. + 0 = default value +- qcom,mdss-dsi-v-bottom-border: Vertical bottom border in pixel. + 0 = default value +- qcom,mdss-dsi-underflow-color: Specifies the controller settings for the + panel under flow color. + 0xff = default value. +- qcom,mdss-dsi-border-color: Defines the border color value if border is present. + 0 = default value. +- qcom,mdss-dsi-pan-enable-dynamic-fps: Boolean used to enable change in frame rate dynamically. +- qcom,mdss-dsi-pan-fps-update: A string that specifies when to change the frame rate. + "dfps_suspend_resume_mode"= FPS change request is + implemented during suspend/resume. + "dfps_immediate_clk_mode" = FPS change request is + implemented immediately using DSI clocks. + "dfps_immediate_porch_mode_hfp" = FPS change request is + implemented immediately by changing panel horizontal + front porch values. + "dfps_immediate_porch_mode_vfp" = FPS change request is + implemented immediately by changing panel vertical + front porch values. +- qcom,min-refresh-rate: Minimum refresh rate supported by the panel. +- qcom,max-refresh-rate: Maximum refresh rate supported by the panel. If max refresh + rate is not specified, then the frame rate of the panel in + qcom,mdss-dsi-panel-framerate is used. +- qcom,mdss-dsi-bl-pmic-control-type: A string that specifies the implementation of backlight + control for this panel. + "bl_ctrl_pwm" = Backlight controlled by PWM gpio. + "bl_ctrl_wled" = Backlight controlled by WLED. + "bl_ctrl_dcs" = Backlight controlled by DCS commands. + other: Unknown backlight control. (default) +- qcom,mdss-dsi-bl-pwm-pmi: Boolean to indicate that PWM control is through second pmic chip. +- qcom,mdss-dsi-bl-pmic-bank-select: LPG channel for backlight. + Required if blpmiccontroltype is PWM +- qcom,mdss-dsi-bl-pmic-pwm-frequency: PWM period in microseconds. + Required if blpmiccontroltype is PWM +- qcom,mdss-dsi-pwm-gpio: PMIC gpio binding to backlight. + Required if blpmiccontroltype is PWM +- qcom,mdss-dsi-bl-min-level: Specifies the min backlight level supported by the panel. + 0 = default value. +- qcom,mdss-dsi-bl-max-level: Specifies the max backlight level supported by the panel. + 255 = default value. +- qcom,mdss-brightness-max-level: Specifies the max brightness level supported. + 255 = default value. +- qcom,mdss-dsi-interleave-mode: Specifies interleave mode. + 0 = default value. +- qcom,mdss-dsi-panel-type: Specifies the panel operating mode. + "dsi_video_mode" = enable video mode (default). + "dsi_cmd_mode" = enable command mode. +- qcom,5v-boost-gpio: Specifies the panel gpio for display 5v boost. +- qcom,mdss-dsi-te-check-enable: Boolean to enable Tear Check configuration. +- qcom,mdss-dsi-te-using-te-pin: Boolean to specify whether using hardware vsync. +- qcom,mdss-dsi-te-pin-select: Specifies TE operating mode. + 0 = TE through embedded dcs command + 1 = TE through TE gpio pin. (default) +- qcom,mdss-dsi-te-dcs-command: Inserts the dcs command. + 1 = default value. +- qcom,mdss-dsi-wr-mem-start: DCS command for write_memory_start. + 0x2c = default value. +- qcom,mdss-dsi-wr-mem-continue: DCS command for write_memory_continue. + 0x3c = default value. +- qcom,mdss-dsi-h-sync-pulse: Specifies the pulse mode option for the panel. + 0 = Don't send hsa/he following vs/ve packet(default) + 1 = Send hsa/he following vs/ve packet +- qcom,mdss-dsi-hfp-power-mode: Boolean to determine DSI lane state during + horizontal front porch (HFP) blanking period. +- qcom,mdss-dsi-hbp-power-mode: Boolean to determine DSI lane state during + horizontal back porch (HBP) blanking period. +- qcom,mdss-dsi-hsa-power-mode: Boolean to determine DSI lane state during + horizontal sync active (HSA) mode. +- qcom,mdss-dsi-last-line-interleave Boolean to determine if last line + interleave flag needs to be enabled. +- qcom,mdss-dsi-bllp-eof-power-mode: Boolean to determine DSI lane state during + blanking low power period (BLLP) EOF mode. +- qcom,mdss-dsi-bllp-power-mode: Boolean to determine DSI lane state during + blanking low power period (BLLP) mode. +- qcom,mdss-dsi-traffic-mode: Specifies the panel traffic mode. + "non_burst_sync_pulse" = non burst with sync pulses (default). + "non_burst_sync_event" = non burst with sync start event. + "burst_mode" = burst mode. +- qcom,mdss-dsi-pixel-packing: Specifies if pixel packing is used (in case of RGB666). + "tight" = Tight packing (default value). + "loose" = Loose packing. +- qcom,mdss-dsi-virtual-channel-id: Specifies the virtual channel identefier. + 0 = default value. +- qcom,mdss-dsi-color-order: Specifies the R, G and B channel ordering. + "rgb_swap_rgb" = DSI_RGB_SWAP_RGB (default value) + "rgb_swap_rbg" = DSI_RGB_SWAP_RBG + "rgb_swap_brg" = DSI_RGB_SWAP_BRG + "rgb_swap_grb" = DSI_RGB_SWAP_GRB + "rgb_swap_gbr" = DSI_RGB_SWAP_GBR +- qcom,mdss-dsi-lane-0-state: Boolean that specifies whether data lane 0 is enabled. +- qcom,mdss-dsi-lane-1-state: Boolean that specifies whether data lane 1 is enabled. +- qcom,mdss-dsi-lane-2-state: Boolean that specifies whether data lane 2 is enabled. +- qcom,mdss-dsi-lane-3-state: Boolean that specifies whether data lane 3 is enabled. +- qcom,mdss-dsi-t-clk-post: Specifies the byte clock cycles after mode switch. + 0x03 = default value. +- qcom,mdss-dsi-t-clk-pre: Specifies the byte clock cycles before mode switch. + 0x24 = default value. +- qcom,mdss-dsi-stream: Specifies the packet stream to be used. + 0 = stream 0 (default) + 1 = stream 1 +- qcom,mdss-dsi-mdp-trigger: Specifies the trigger mechanism to be used for MDP path. + "none" = no trigger + "trigger_te" = Tear check signal line used for trigger + "trigger_sw" = Triggered by software (default) + "trigger_sw_te" = Software trigger and TE +- qcom,mdss-dsi-dma-trigger: Specifies the trigger mechanism to be used for DMA path. + "none" = no trigger + "trigger_te" = Tear check signal line used for trigger + "trigger_sw" = Triggered by software (default) + "trigger_sw_seof" = Software trigger and start/end of frame trigger. + "trigger_sw_te" = Software trigger and TE +- qcom,mdss-dsi-panel-framerate: Specifies the frame rate for the panel. + 60 = 60 frames per second (default) +- qcom,mdss-dsi-panel-clockrate: A 64 bit value specifies the panel clock speed in Hz. + 0 = default value. +- qcom,mdss-mdp-transfer-time-us: Specifies the dsi transfer time for command mode + panels in microseconds. Driver uses this number to adjust + the clock rate according to the expected transfer time. + Increasing this value would slow down the mdp processing + and can result in slower performance. + Decreasing this value can speed up the mdp processing, + but this can also impact power consumption. + As a rule this time should not be higher than the time + that would be expected with the processing at the + dsi link rate since anyways this would be the maximum + transfer time that could be achieved. + If ping pong split enabled, this time should not be higher + than two times the dsi link rate time. + 14000 = default value. +- qcom,mdss-dsi-on-command-state: String that specifies the ctrl state for sending ON commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-off-command-state: String that specifies the ctrl state for sending OFF commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-post-mode-switch-on-command-state: String that specifies the ctrl state for sending ON commands post mode switch. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-pan-physical-width-dimension: Specifies panel physical width in mm which corresponds + to the physical width in the framebuffer information. +- qcom,mdss-pan-physical-height-dimension: Specifies panel physical height in mm which corresponds + to the physical height in the framebuffer information. +- qcom,mdss-dsi-mode-sel-gpio-state: String that specifies the lcd mode for panel + (such as single-port/dual-port), if qcom,panel-mode-gpio + binding is defined in dsi controller. + "dual_port" = Set GPIO to LOW + "single_port" = Set GPIO to HIGH + "high" = Set GPIO to HIGH + "low" = Set GPIO to LOW + The default value is "dual_port". +- qcom,mdss-tear-check-disable: Boolean to disable mdp tear check. Tear check is enabled by default to avoid + tearing. Other tear-check properties are ignored if this property is present. + The below tear check configuration properties can be individually tuned if + tear check is enabled. +- qcom,mdss-tear-check-sync-cfg-height: Specifies the vertical total number of lines. + The default value is 0xfff0. +- qcom,mdss-tear-check-sync-init-val: Specifies the init value at which the read pointer gets loaded + at vsync edge. The reader pointer refers to the line number of + panel buffer that is currently being updated. + The default value is panel height. +- qcom,mdss-tear-check-sync-threshold-start: + Allows the first ROI line write to an panel when read pointer is + between the range of ROI start line and ROI start line plus this + setting. + The default value is 4. +- qcom,mdss-tear-check-sync-threshold-continue: + The minimum number of lines the write pointer needs to be + above the read pointer so that it is safe to write to the panel. + (This check is not done for the first ROI line write of an update) + The default value is 4. +- qcom,mdss-tear-check-start-pos: Specify the y position from which the start_threshold value is + added and write is kicked off if the read pointer falls within that + region. + The default value is panel height. +- qcom,mdss-tear-check-rd-ptr-trigger-intr: + Specify the read pointer value at which an interrupt has to be + generated. + The default value is panel height + 1. +- qcom,mdss-tear-check-frame-rate: Specify the value to be a real frame rate(fps) x 100 factor to tune the + timing of TE simulation with more precision. + The default value is 6000 with 60 fps. +- qcom,mdss-dsi-reset-sequence: An array that lists the + sequence of reset gpio values and sleeps + Each command will have the format defined + as below: + --> Reset GPIO value + --> Sleep value (in ms) +- qcom,partial-update-enabled: Boolean used to enable partial + panel update for command mode panels. +- qcom,mdss-dsi-horizontal-line-idle: List of width ranges (EC - SC) in pixels indicating + additional idle time in dsi clock cycles that is needed + to compensate for smaller line width. +- qcom,partial-update-roi-merge: Boolean indicates roi combination is need + and function has been provided for dcs + 2A/2B command. +- qcom,dcs-cmd-by-left: Boolean to indicate that dcs command are sent + through the left DSI controller only in a dual-dsi configuration +- qcom,mdss-dsi-lp11-init: Boolean used to enable the DSI clocks and data lanes (low power 11) + before issuing hardware reset line. +- qcom,mdss-dsi-init-delay-us: Delay in microseconds(us) before performing any DSI activity in lp11 + mode. This master delay (t_init_delay as per DSI spec) should be sum + of DSI internal delay to reach fuctional after power up and minimum + delay required by panel to reach functional. +- qcom,mdss-dsi-rx-eot-ignore: Boolean used to enable ignoring end of transmission packets. +- qcom,mdss-dsi-tx-eot-append: Boolean used to enable appending end of transmission packets. +- qcom,ulps-enabled: Boolean to enable support for Ultra Low Power State (ULPS) mode. +- qcom,suspend-ulps-enabled: Boolean to enable support for ULPS mode for panels during suspend state. +- qcom,panel-roi-alignment: Specifies the panel ROI alignment restrictions on its + left, top, width, height alignments and minimum width and + height values +- qcom,esd-check-enabled: Boolean used to enable ESD recovery feature. +- qcom,mdss-dsi-panel-status-command: A byte stream formed by multiple dcs packets based on + qcom dsi controller protocol, to read the panel status. + This value is used to kick in the ESD recovery. + byte 0: dcs data type + byte 1: set to indicate this is an individual packet + (no chain) + byte 2: virtual channel number + byte 3: expect ack from client (dcs read command) + byte 4: wait number of specified ms after dcs command + transmitted + byte 5, 6: 16 bits length in network byte order + byte 7 and beyond: number byte of payload +- qcom,mdss-dsi-panel-status-command-mode: + String that specifies the ctrl state for reading the panel status. + "dsi_lp_mode" = DSI low power mode + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-panel-status-check-mode:Specifies the panel status check method for ESD recovery. + "bta_check" = Uses BTA to check the panel status + "reg_read" = Reads panel status register to check the panel status + "reg_read_nt35596" = Reads panel status register to check the panel + status for NT35596 panel. + "te_signal_check" = Uses TE signal behaviour to check the panel status +- qcom,mdss-dsi-panel-status-read-length: Integer array that specify the expected read-back length of values + for each of panel registers. Each length is corresponding to number of + returned parameters of register introduced in specification. +- qcom,mdss-dsi-panel-status-valid-params: Integer array that specify the valid returned values which need to check + for each of register. + Some panel need only check the first few values returned from panel. + So: if this property is the same to qcom,mdss-dsi-panel-status-read-length, + then just ignore this one. +- qcom,mdss-dsi-panel-status-value: Multiple integer arrays, each specifies the values of the panel status register + which is used to check the panel status. The size of each array is the sum of + length specified in qcom,mdss-dsi-panel-status-read-length, and must be equal. + This can cover that Some panel may return several alternative values. +- qcom,mdss-dsi-panel-max-error-count: Integer value that specifies the maximum number of errors from register + read that can be ignored before treating that the panel has gone bad. +- qcom,dynamic-mode-switch-enabled: Boolean used to mention whether panel supports + dynamic switching from video mode to command mode + and vice versa. +- qcom,dynamic-mode-switch-type: A string specifies how to perform dynamic mode switch. + If qcom,dynamic-mode-switch-enabled is set and no string specified, default value is + dynamic-switch-suspend-resume. + "dynamic-switch-suspend-resume"= Switch using suspend/resume. Panel will + go blank during transition. + "dynamic-switch-immediate"= Switch on next frame update. Panel will + not go blank for this transition. + "dynamic-resolution-switch-immediate"= Switch the panel resolution. Panel will + not go blank for this transition. +- qcom,mdss-dsi-post-mode-switch-on-command: Multiple dcs packets used for turning on DSI panel + after panel has switch modes. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,video-to-cmd-mode-switch-commands: List of commands that need to be sent + to panel in order to switch from video mode to command mode dynamically. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,cmd-to-video-mode-switch-commands: List of commands that need to be sent + to panel in order to switch from command mode to video mode dynamically. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,send-pps-before-switch: Boolean propety to indicate when PPS commands should be sent, + either before or after switch commands during dynamic resolution + switch in DSC panels. If the property is not present, the default + behavior is to send PPS commands after the switch commands. +- qcom,mdss-dsi-panel-orientation: String used to indicate orientation of panel + "180" = panel is flipped in both horizontal and vertical directions + "hflip" = panel is flipped in horizontal direction + "vflip" = panel is flipped in vertical direction +- qcom,panel-ack-disabled: A boolean property to indicate, whether we need to wait for any ACK from the panel + for any commands that we send. +- qcom,mdss-dsi-force-clock-lane-hs: Boolean to force dsi clock lanes to HS mode always. + +- qcom,compression-mode: Select compression mode for panel. + "fbc" - frame buffer compression + "dsc" - display stream compression. + If "dsc" compression is used then config subnodes needs to be defined. +- qcom,panel-supply-entries: A node that lists the elements of the supply used to + power the DSI panel. There can be more than one instance + of this binding, in which case the entry would be appended + with the supply entry index. For a detailed description of + fields in the supply entry, refer to the qcom,ctrl-supply-entries + binding above. +- qcom,config-select: Optional property to select default configuration. + +[[Optional config sub-nodes]] These subnodes provide different configurations for a given same panel. + Default configuration can be chosen by specifying phandle of the + selected subnode in the qcom,config-select. +Required properties for sub-nodes: None +Optional properites: +- qcom,lm-split: An array of two values indicating MDP should use two layer + mixers to reduce power. + Ex: Normally 1080x1920 display uses single DSI and thus one layer + mixer. But if we use two layer mixers then mux the output of + those two mixers into single stream and route it to single DSI + then we can lower the clock requirements of MDP. To use this + configuration we need two fill this array with <540 540>. + Both values doesn't have to be same, but recommended, however sum of + both values has to be equal to the panel-width. + By default two mixer streams are merged using 2D mux, however if + 2 DSC encoders are used then merge is performed within compression + engine. +- qcom,split-mode: String property indicating which split mode MDP should use. Valid + entries are "pingpong-split" and "dualctl-split". + This property is mutually exclusive with qcom,lm-split. +- qcom,mdss-dsc-version: An 8 bit value indicates the DSC version supported by panel. Bits[0.3] + provides information about minor version while Bits[4.7] provides + major version information. It supports only DSC rev 1(Major).1(Minor) + right now. +- qcom,mdss-dsc-scr-version: Each DSC version can have multiple SCR. This 8 bit value indicates + current SCR revision information supported by panel. +- qcom,mdss-dsc-encoders: An integer value indicating how many DSC encoders should be used + to drive data stream to DSI. + Default value is 1 and max value is 2. + 2 encoder should be used only if qcom,mdss-lm-split or + qcom,split-mode with pingpong-split is used. +- qcom,mdss-dsc-slice-height: An integer value indicates the dsc slice height. +- qcom,mdss-dsc-slice-width: An integer value indicates the dsc slice width. + Multiple of slice width should be equal to panel-width. + Maximum 2 slices per DSC encoder can be used so if 2 DSC encoders + are used then minimum slice width is equal to panel-width/4. +- qcom,mdss-dsc-slice-per-pkt: An integer value indicates the slice per dsi packet. +- qcom,mdss-dsc-bit-per-component: An integer value indicates the bits per component before compression. +- qcom,mdss-dsc-bit-per-pixel: An integer value indicates the bits per pixel after compression. +- qcom,mdss-dsc-block-prediction-enable: A boolean value to enable/disable the block prediction at decoder. +- qcom,mdss-dsc-config-by-manufacture-cmd: A boolean to indicates panel use manufacture command to setup pps + instead of standard dcs type 0x0A. +- qcom,dba-panel: Indicates whether the current panel is used as a display bridge + to a non-DSI interface. +- qcom,bridge-name: A string to indicate the name of the bridge chip connected to DSI. qcom,bridge-name + is required if qcom,dba-panel is defined for the panel. +- qcom,adjust-timer-wakeup-ms: An integer value to indicate the timer delay(in ms) to accommodate + s/w delay while configuring the event timer wakeup logic. + +- qcom,mdss-dsi-display-timings: Parent node that lists the different resolutions that the panel supports. + Each child represents timings settings for a specific resolution. +- qcom,mdss-dsi-post-init-delay: Specifies required number of frames to wait so that panel can be functional + to show proper display. + +Additional properties added to the second level nodes that represent timings properties: +- qcom,mdss-dsi-timing-default: Property that specifies the current child as the default + timing configuration that will be used. +- qcom,mdss-dsi-timing-switch-command: List of commands that need to be sent + to panel when the resolution/timing switch happens dynamically. + Refer to "qcom,mdss-dsi-on-command" section for adding commands. +- qcom,mdss-dsi-timing-switch-command-state: String that specifies the ctrl state for sending resolution switch + commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode + +Note, if a given optional qcom,* binding is not present, then the driver will configure +the default values specified. + +Example: +&mdss_mdp { + dsi_sim_vid: qcom,mdss_dsi_sim_video { + qcom,mdss-dsi-panel-name = "simulator video mode dsi panel"; + qcom,mdss-dsi-panel-controller = <&mdss_dsi0>; + qcom,mdss-dsi-panel-height = <1280>; + qcom,mdss-dsi-panel-width = <720>; + qcom,mdss-dsi-bpp = <24>; + qcom,mdss-dsi-pixel-packing = <0>; + qcom,mdss-dsi-panel-destination = "display_1"; + qcom,cmd-sync-wait-broadcast; + qcom,mdss-dsi-fbc-enable; + qcom,mdss-dsi-fbc-slice-height = <5>; + qcom,mdss-dsi-fbc-2d-pred-mode; + qcom,mdss-dsi-fbc-ver2-mode; + qcom,mdss-dsi-fbc-bpp = <0>; + qcom,mdss-dsi-fbc-packing = <0>; + qcom,mdss-dsi-fbc-quant-error; + qcom,mdss-dsi-fbc-bias = <0>; + qcom,mdss-dsi-fbc-pat-mode; + qcom,mdss-dsi-fbc-vlc-mode; + qcom,mdss-dsi-fbc-bflc-mode; + qcom,mdss-dsi-fbc-h-line-budget = <0>; + qcom,mdss-dsi-fbc-budget-ctrl = <0>; + qcom,mdss-dsi-fbc-block-budget = <0>; + qcom,mdss-dsi-fbc-lossless-threshold = <0>; + qcom,mdss-dsi-fbc-lossy-threshold = <0>; + qcom,mdss-dsi-fbc-rgb-threshold = <0>; + qcom,mdss-dsi-fbc-lossy-mode-idx = <0>; + qcom,mdss-dsi-fbc-max-pred-err = <2>; + qcom,mdss-dsi-h-front-porch = <140>; + qcom,mdss-dsi-h-back-porch = <164>; + qcom,mdss-dsi-h-pulse-width = <8>; + qcom,mdss-dsi-h-sync-skew = <0>; + qcom,mdss-dsi-v-back-porch = <6>; + qcom,mdss-dsi-v-front-porch = <1>; + qcom,mdss-dsi-v-pulse-width = <1>; + qcom,mdss-dsi-h-left-border = <0>; + qcom,mdss-dsi-h-right-border = <0>; + qcom,mdss-dsi-v-top-border = <0>; + qcom,mdss-dsi-v-bottom-border = <0>; + qcom,mdss-dsi-border-color = <0>; + qcom,mdss-dsi-underflow-color = <0xff>; + qcom,mdss-dsi-bl-min-level = <1>; + qcom,mdss-dsi-bl-max-level = < 15>; + qcom,mdss-brightness-max-level = <255>; + qcom,mdss-dsi-interleave-mode = <0>; + qcom,mdss-dsi-panel-type = "dsi_video_mode"; + qcom,mdss-dsi-te-check-enable; + qcom,mdss-dsi-te-using-te-pin; + qcom,mdss-dsi-te-dcs-command = <1>; + qcom,mdss-dsi-wr-mem-continue = <0x3c>; + qcom,mdss-dsi-wr-mem-start = <0x2c>; + qcom,mdss-dsi-te-pin-select = <1>; + qcom,mdss-dsi-h-sync-pulse = <1>; + qcom,mdss-dsi-hfp-power-mode; + qcom,mdss-dsi-hbp-power-mode; + qcom,mdss-dsi-hsa-power-mode; + qcom,mdss-dsi-bllp-eof-power-mode; + qcom,mdss-dsi-bllp-power-mode; + qcom,mdss-dsi-last-line-interleave; + qcom,mdss-dsi-traffic-mode = <0>; + qcom,mdss-dsi-virtual-channel-id = <0>; + qcom,mdss-dsi-color-order = <0>; + qcom,mdss-dsi-lane-0-state; + qcom,mdss-dsi-lane-1-state; + qcom,mdss-dsi-lane-2-state; + qcom,mdss-dsi-lane-3-state; + qcom,mdss-dsi-t-clk-post = <0x20>; + qcom,mdss-dsi-t-clk-pre = <0x2c>; + qcom,mdss-dsi-stream = <0>; + qcom,mdss-dsi-mdp-trigger = <0>; + qcom,mdss-dsi-dma-trigger = <0>; + qcom,mdss-dsi-panel-framerate = <60>; + qcom,mdss-dsi-panel-clockrate = <424000000>; + qcom,mdss-mdp-transfer-time-us = <12500>; + qcom,mdss-dsi-panel-timings = [7d 25 1d 00 37 33 + 22 27 1e 03 04 00]; + qcom,mdss-dsi-panel-timings-8996 = [23 20 06 09 05 03 04 a0 + 23 20 06 09 05 03 04 a0 + 23 20 06 09 05 03 04 a0 + 23 20 06 09 05 03 04 a0 + 23 2e 06 08 05 03 04 a0]; + qcom,mdss-dsi-on-command = [32 01 00 00 00 00 02 00 00 + 29 01 00 00 10 00 02 FF 99]; + qcom,mdss-dsi-on-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-off-command = [22 01 00 00 00 00 00]; + qcom,mdss-dsi-off-command-state = "dsi_hs_mode"; + qcom,mdss-dsi-bl-pmic-control-type = "bl_ctrl_wled"; + qcom,mdss-dsi-pan-enable-dynamic-fps; + qcom,mdss-dsi-pan-fps-update = "dfps_suspend_resume_mode"; + qcom,min-refresh-rate = <30>; + qcom,max-refresh-rate = <60>; + qcom,mdss-dsi-bl-pmic-bank-select = <0>; + qcom,mdss-dsi-bl-pmic-pwm-frequency = <0>; + qcom,mdss-dsi-pwm-gpio = <&pm8941_mpps 5 0>; + qcom,5v-boost-gpio = <&pm8994_gpios 14 0>; + qcom,mdss-pan-physical-width-dimension = <60>; + qcom,mdss-pan-physical-height-dimension = <140>; + qcom,mdss-dsi-mode-sel-gpio-state = "dsc_mode"; + qcom,mdss-tear-check-sync-cfg-height = <0xfff0>; + qcom,mdss-tear-check-sync-init-val = <1280>; + qcom,mdss-tear-check-sync-threshold-start = <4>; + qcom,mdss-tear-check-sync-threshold-continue = <4>; + qcom,mdss-tear-check-start-pos = <1280>; + qcom,mdss-tear-check-rd-ptr-trigger-intr = <1281>; + qcom,mdss-tear-check-frame-rate = <6000>; + qcom,mdss-dsi-reset-sequence = <1 2>, <0 10>, <1 10>; + qcom,partial-update-enabled; + qcom,dcs-cmd-by-left; + qcom,mdss-dsi-lp11-init; + qcom,mdss-dsi-init-delay-us = <100>; + mdss-dsi-rx-eot-ignore; + mdss-dsi-tx-eot-append; + qcom,ulps-enabled; + qcom,suspend-ulps-enabled; + qcom,panel-roi-alignment = <4 4 2 2 20 20>; + qcom,esd-check-enabled; + qcom,mdss-dsi-panel-status-command = [06 01 00 01 05 00 02 0A 08]; + qcom,mdss-dsi-panel-status-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-panel-status-check-mode = "reg_read"; + qcom,mdss-dsi-panel-status-read-length = <8>; + qcom,mdss-dsi-panel-max-error-count = <3>; + qcom,mdss-dsi-panel-status-value = <0x1c 0x00 0x05 0x02 0x40 0x84 0x06 0x01>; + qcom,dynamic-mode-switch-enabled; + qcom,dynamic-mode-switch-type = "dynamic-switch-immediate"; + qcom,mdss-dsi-post-mode-switch-on-command = [32 01 00 00 00 00 02 00 00 + 29 01 00 00 10 00 02 B0 03]; + qcom,video-to-cmd-mode-switch-commands = [15 01 00 00 00 00 02 C2 0B + 15 01 00 00 00 00 02 C2 08]; + qcom,cmd-to-video-mode-switch-commands = [15 01 00 00 00 00 02 C2 03]; + qcom,send-pps-before-switch; + qcom,panel-ack-disabled; + qcom,mdss-dsi-horizontal-line-idle = <0 40 256>, + <40 120 128>, + <128 240 64>; + qcom,mdss-dsi-panel-orientation = "180" + qcom,mdss-dsi-force-clock-lane-hs; + qcom,compression-mode = "dsc"; + qcom,adjust-timer-wakeup-ms = <1>; + qcom,mdss-dsi-display-timings { + wqhd { + qcom,mdss-dsi-timing-default; + qcom,mdss-dsi-panel-width = <720>; + qcom,mdss-dsi-panel-height = <2560>; + qcom,mdss-dsi-h-front-porch = <20>; + qcom,mdss-dsi-h-back-porch = <8>; + qcom,mdss-dsi-h-pulse-width = <8>; + qcom,mdss-dsi-h-sync-skew = <0>; + qcom,mdss-dsi-v-back-porch = <4>; + qcom,mdss-dsi-v-front-porch = <728>; + qcom,mdss-dsi-v-pulse-width = <4>; + qcom,mdss-dsi-panel-framerate = <60>; + qcom,mdss-dsi-panel-timings = [E6 38 26 00 68 6E 2A 3C 2C 03 04 00]; + qcom,mdss-dsi-t-clk-post = <0x02>; + qcom,mdss-dsi-t-clk-pre = <0x2a>; + qcom,mdss-dsi-on-command = [05 01 00 00 a0 00 02 11 00 + 05 01 00 00 02 00 02 29 00]; + qcom,mdss-dsi-on-command-state = "dsi_lp_mode"; + qcom,mdss-dsi-timing-switch-command = [ + 29 00 00 00 00 00 02 B0 04 + 29 00 00 00 00 00 02 F1 00]; + qcom,mdss-dsi-timing-switch-command-state = "dsi_lp_mode"; + + qcom,config-select = <&dsi_sim_vid_config0>; + dsi_sim_vid_config0: config0 { + qcom,lm-split = <360 360>; + qcom,mdss-dsc-encoders = <2>; + qcom,mdss-dsc-slice-height = <16>; + qcom,mdss-dsc-slice-width = <360>; + qcom,mdss-dsc-slice-per-pkt = <2>; + qcom,mdss-dsc-bit-per-component = <8>; + qcom,mdss-dsc-bit-per-pixel = <8>; + qcom,mdss-dsc-block-prediction-enable; + qcom,mdss-dsc-config-by-manufacture-cmd; + }; + }; + }; + qcom,panel-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + + qcom,panel-supply-entry@0 { + reg = <0>; + qcom,supply-name = "vdd"; + qcom,supply-min-voltage = <2800000>; + qcom,supply-max-voltage = <2800000>; + qcom,supply-enable-load = <100000>; + qcom,supply-disable-load = <100>; + qcom,supply-pre-on-sleep = <0>; + qcom,supply-post-on-sleep = <0>; + qcom,supply-pre-off-sleep = <0>; + qcom,supply-post-off-sleep = <0>; + }; + + qcom,panel-supply-entry@1 { + reg = <1>; + qcom,supply-name = "vddio"; + qcom,supply-min-voltage = <1800000>; + qcom,supply-max-voltage = <1800000>; + qcom,supply-enable-load = <100000>; + qcom,supply-disable-load = <100>; + qcom,supply-pre-on-sleep = <0>; + qcom,supply-post-on-sleep = <0>; + qcom,supply-pre-off-sleep = <0>; + qcom,supply-post-off-sleep = <0>; + }; + }; + + qcom,config-select = <&dsi_sim_vid_config0>; + qcom,dba-panel; + qcom,bridge-name = "adv7533"; + qcom,mdss-dsc-version = <0x11>; + qcom,mdss-dsc-scr-version = <0x1>; + + dsi_sim_vid_config0: config0 { + qcom,lm-split = <360 360>; + qcom,mdss-dsc-encoders = <2>; + qcom,mdss-dsc-slice-height = <16>; + qcom,mdss-dsc-slice-width = <360>; + qcom,mdss-dsc-slice-per-pkt = <2>; + qcom,mdss-dsc-bit-per-component = <8>; + qcom,mdss-dsc-bit-per-pixel = <8>; + qcom,mdss-dsc-block-prediction-enable; + qcom,mdss-dsc-config-by-manufacture-cmd; + }; + + dsi_sim_vid_config1: config1 { + qcom,mdss-dsc-encoders = <1>; + qcom,mdss-dsc-slice-height = <16>; + qcom,mdss-dsc-slice-width = <360>; + qcom,mdss-dsc-slice-per-pkt = <2>; + qcom,mdss-dsc-bit-per-component = <8>; + qcom,mdss-dsc-bit-per-pixel = <8>; + qcom,mdss-dsc-block-prediction-enable; + qcom,mdss-dsc-config-by-manufacture-cmd; + }; + + dsi_sim_vid_config2: config2 { + qcom,split-mode = "dualctl-split"; + }; + + dsi_sim_vid_config3: config3 { + qcom,split-mode = "pingpong-split"; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/fb/mdss-pll.txt b/Documentation/devicetree/bindings/fb/mdss-pll.txt new file mode 100644 index 0000000000000000000000000000000000000000..59fa6a0b80b4bd822f9de79a84b70e500410902d --- /dev/null +++ b/Documentation/devicetree/bindings/fb/mdss-pll.txt @@ -0,0 +1,103 @@ +Qualcomm Technologies MDSS pll for DSI/EDP/HDMI + +mdss-pll is a pll controller device which supports pll devices that +are compatible with MIPI display serial interface specification, +HDMI and edp. + +Required properties: +- compatible: Compatible name used in the driver + "qcom,mdss_dsi_pll_8916", "qcom,mdss_dsi_pll_8939", + "qcom,mdss_dsi_pll_8974", "qcom,mdss_dsi_pll_8994", + "qcom,mdss_dsi_pll_8994", "qcom,mdss_dsi_pll_8909", + "qcom,mdss_hdmi_pll", "qcom,mdss_hdmi_pll_8994", + "qcom,mdss_dsi_pll_8992", "qcom,mdss_hdmi_pll_8992", + "qcom,mdss_dsi_pll_8996", "qcom,mdss_hdmi_pll_8996", + "qcom,mdss_hdmi_pll_8996_v2", "qcom,mdss_dsi_pll_8996_v2", + "qcom,mdss_hdmi_pll_8996_v3", "qcom,mdss_hdmi_pll_8996_v3_1p8", + "qcom,mdss_edp_pll_8996_v3", "qcom,mdss_edp_pll_8996_v3_1p8", + "qcom,mdss_dsi_pll_10nm", "qcom,mdss_dp_pll_8998", + "qcom,mdss_hdmi_pll_8998", "qcom,mdss_dp_pll_10nm". +- cell-index: Specifies the controller used +- reg: offset and length of the register set for the device. +- reg-names : names to refer to register sets related to this device +- gdsc-supply: Phandle for gdsc regulator device node. +- vddio-supply: Phandle for vddio regulator device node. +- clocks: List of Phandles for clock device nodes + needed by the device. +- clock-names: List of clock names needed by the device. +- clock-rate: List of clock rates in Hz. + +Optional properties: +- label: A string used to describe the driver used. +- vcca-supply: Phandle for vcca regulator device node. + + +- qcom,dsi-pll-ssc-en: Boolean property to indicate that ssc is enabled. +- qcom,dsi-pll-ssc-mode: Spread-spectrum clocking. It can be either "down-spread" + or "center-spread". Default is "down-spread" if it is not specified. +- qcom,ssc-frequency-hz: Integer property to specify the spread frequency + to be programmed for the SSC. +- qcom,ssc-ppm: Integer property to specify the Parts per Million + value of SSC. + +- qcom,platform-supply-entries: A node that lists the elements of the supply. There + can be more than one instance of this binding, + in which case the entry would be appended with + the supply entry index. + e.g. qcom,platform-supply-entry@0 + - reg: offset and length of the register set for the device. + -- qcom,supply-name: name of the supply (vdd/vdda/vddio) + -- qcom,supply-min-voltage: minimum voltage level (uV) + -- qcom,supply-max-voltage: maximum voltage level (uV) + -- qcom,supply-enable-load: load drawn (uA) from enabled supply + -- qcom,supply-disable-load: load drawn (uA) from disabled supply + -- qcom,supply-pre-on-sleep: time to sleep (ms) before turning on + -- qcom,supply-post-on-sleep: time to sleep (ms) after turning on + -- qcom,supply-pre-off-sleep: time to sleep (ms) before turning off + -- qcom,supply-post-off-sleep: time to sleep (ms) after turning off + +Example: + mdss_dsi0_pll: qcom,mdss_dsi_pll@fd922A00 { + compatible = "qcom,mdss_dsi_pll_8974"; + label = "MDSS DSI 0 PLL"; + cell-index = <0>; + + reg = <0xfd922A00 0xD4>, + <0xfd922900 0x64>, + <0xfd8c2300 0x8>; + reg-names = "pll_base", "dynamic_pll_base", "gdsc_base"; + gdsc-supply = <&gdsc_mdss>; + vddio-supply = <&pm8941_l12>; + vcca-supply = <&pm8941_l28>; + + clocks = <&clock_gcc clk_gcc_mdss_mdp_clk>, + <&clock_gcc clk_gcc_mdss_ahb_clk>, + <&clock_gcc clk_gcc_mdss_axi_clk>; + clock-names = "mdp_core_clk", "iface_clk", "bus_clk"; + clock-rate = <0>, <0>, <0>; + + qcom,dsi-pll-slave; + qcom,dsi-pll-ssc-en; + qcom,dsi-pll-ssc-mode = "down-spread"; + qcom,ssc-frequency-hz = <30000>; + qcom,ssc-ppm = <5000>; + + qcom,platform-supply-entries { + #address-cells = <1>; + #size-cells = <0>; + + qcom,platform-supply-entry@0 { + reg = <0>; + qcom,supply-name = "vddio"; + qcom,supply-min-voltage = <1800000>; + qcom,supply-max-voltage = <1800000>; + qcom,supply-enable-load = <100000>; + qcom,supply-disable-load = <100>; + qcom,supply-pre-on-sleep = <0>; + qcom,supply-post-on-sleep = <20>; + qcom,supply-pre-off-sleep = <0>; + qcom,supply-post-off-sleep = <0>; + }; + }; + }; + diff --git a/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt b/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt new file mode 100644 index 0000000000000000000000000000000000000000..46649afcc37eab7bd7928b9208635cd298cd7571 --- /dev/null +++ b/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt @@ -0,0 +1,223 @@ +SDE Rotator + +SDE rotator is a v4l2 rotator driver, which manages the rotator hw +block inside the Snapdragon Display Engine (or Mobile Display Subsystem) + +Required properties +- compatible: Must be "qcom,sde-rotator". +- reg: offset and length of the register set for the device. +- reg-names: names to refer to register sets related to this device +- interrupt-parent: phandle for the interrupt controller that + services interrupts for this device. +- interrupts: Interrupt associated with rotator. +- -supply: Phandle for regulator device node. +- qcom,supply-names: names to refer to regulator device node. +- clocks: List of Phandles for clock device nodes + needed by the device. +- clock-names: List of clock names needed by the device. +- #list-cells: Number of rotator cells, must be 1 + +Bus Scaling Data: +- qcom,msm-bus,name: String property describing rotator client. +- qcom,msm-bus,num-cases: This is the the number of Bus Scaling use cases + defined in the vectors property. This must be + set to <3> for rotator driver where use-case 0 is + used to take off rotator BW votes from the system. + And use-case 1 & 2 are used in ping-pong fashion + to generate run-time BW requests. +- qcom,msm-bus,num-paths: This represents the number of paths in each + Bus Scaling Usecase. This value depends on + how many number of AXI master ports are + dedicated to rotator for particular chipset. +- qcom,msm-bus,vectors-KBps: * A series of 4 cell properties, with a format + of (src, dst, ab, ib) which is defined at + Documentation/devicetree/bindings/arm/msm/msm_bus.txt + * Current values of src & dst are defined at + include/linux/msm-bus-board.h + src values allowed for rotator are: + 25 = MSM_BUS_MASTER_ROTATOR + dst values allowed for rotator are: + 512 = MSM_BUS_SLAVE_EBI_CH0 + ab: Represents aggregated bandwidth. + ib: Represents instantaneous bandwidth. + * Total number of 4 cell properties will be + (number of use-cases * number of paths). + * These values will be overridden by the driver + based on the run-time requirements. So initial + ab and ib values defined here are random and + bare no logic except for the use-case 0 where ab + and ib values needs to be 0. + * Define realtime vector properties followed by + non-realtime vector properties. + +Optional properties +- qcom,rot-vbif-settings: Array with key-value pairs of constant VBIF register + settings used to setup MDSS QoS for optimum performance. + The key used should be offset from "rot_vbif_phys" register + defined in reg property. +- qcom,mdss-rot-block-size: This integer value indicates the size of a memory block + (in pixels) to be used by the rotator. If this property + is not specified, then a default value of 128 pixels + would be used. +- qcom,mdss-highest-bank-bit: This integer value indicate tile format as opposed to usual + linear format. The value tells the GPU highest memory + bank bit used. +- qcom,mdss-default-ot-wr-limit: This integer value indicates maximum number of pending + writes that can be allowed on each WR xin. + This value can be used to reduce the pending writes + limit and can be tuned to match performance + requirements depending upon system state. + Some platforms require a dynamic ot limiting in + some cases. Setting this default ot write limit + will enable this dynamic limiting for the write + operations in the platforms that require these + limits. +- qcom,mdss-default-ot-rd-limit: This integer value indicates the default number of pending + reads that can be allowed on each RD xin. + Some platforms require a dynamic ot limiting in + some cases. Setting this default ot read limit + will enable this dynamic limiting for the read + operations in the platforms that require these + limits. +- qcom,mdss-rot-vbif-qos-setting: This array is used to program vbif qos remapper register + priority for rotator clients. +- qcom,mdss-rot-vbif-memtype: Array of u32 vbif memory type settings for each xin port. +- qcom,mdss-rot-cdp-setting: Integer array of size two, to indicate client driven + prefetch is available or not. Index 0 represents + if CDP is enabled for read and index 1, if CDP + is enabled for write operation. +- qcom,mdss-rot-qos-lut A 4 cell property with the format of indicating the qos + lut settings for the rotator sspp and writeback + client. +- qcom,mdss-rot-danger-lut A two cell property with the format of indicating the danger lut settings for + the rotator sspp and writeback client. +- qcom,mdss-rot-safe-lut A two cell property with the format of indicating the safe lut settings for the + rotator sspp and writeback client. +- qcom,mdss-inline-rot-qos-lut: A 4 cell property with the format of indicating the qos + lut settings for the inline rotator sspp and + writeback client. +- qcom,mdss-inline-rot-danger-lut: A two cell property with the format of + indicating the danger lut + settings for the inline rotator sspp and + writeback client. +- qcom,mdss-inline-rot-safe-lut: A two cell property with the format of + indicating the safe lut + settings for the inline rotator sspp and + writeback client. +- qcom,mdss-rot-mode: This is integer value indicates operation mode + of the rotator device +- qcom,mdss-sbuf-headroom: This integer value indicates stream buffer headroom in lines. +- qcom,mdss-rot-linewidth: This integer value indicates rotator line width supported in pixels. +- cache-slice-names: A set of names that identify the usecase names of a client that uses + cache slice. These strings are used to look up the cache slice + entries by name. +- cache-slices: The tuple has phandle to llcc device as the first argument and the + second argument is the usecase id of the client. +- qcom,sde-ubwc-malsize: A u32 property to specify the default UBWC + minimum allowable length configuration value. +- qcom,sde-ubwc-swizzle: A u32 property to specify the default UBWC + swizzle configuration value. +- qcom,rot-reg-bus: Property to provide Bus scaling for register + access for rotator blocks. + +Subnode properties: +- compatible: Compatible name used in smmu v2. + smmu_v2 names should be: + "qcom,smmu_sde_rot_unsec"- smmu context bank device for + unsecure rotation domain. + "qcom,smmu_sde_rot_sec" - smmu context bank device for + secure rotation domain. +- iommus: specifies the SID's used by this context bank +- gdsc-mdss-supply: Phandle for mdss supply regulator device node. +- clocks: List of Phandles for clock device nodes + needed by the device. +- clock-names: List of clock names needed by the device. + + +Example: + mdss_rotator: qcom,mdss_rotator { + compatible = "qcom,sde_rotator"; + reg = <0xfd900000 0x22100>, + <0xfd925000 0x1000>; + reg-names = "mdp_phys", "rot_vbif_phys"; + + #list-cells = <1>; + + interrupt-parent = <&mdss_mdp>; + interrupts = <2 0>; + + qcom,mdss-mdp-reg-offset = <0x00001000>; + + rot-vdd-supply = <&gdsc_mdss>; + qcom,supply-names = "rot-vdd"; + + clocks = <&clock_mmss clk_mmss_mdss_ahb_clk>, + <&clock_mmss clk_mmss_mdss_rot_clk>; + clock-names = "iface_clk", "rot_core_clk"; + + qcom,mdss-highest-bank-bit = <0x2>; + qcom,sde-ubwc-malsize = <0>; + qcom,sde-ubwc-swizzle = <1>; + + /* Bus Scale Settings */ + qcom,msm-bus,name = "mdss_rotator"; + qcom,msm-bus,num-cases = <3>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,vectors-KBps = + <25 512 0 0>, + <25 512 0 6400000>, + <25 512 0 6400000>; + + /* VBIF QoS remapper settings*/ + qcom,mdss-rot-vbif-qos-setting = <1 1 1 1>; + qcom,mdss-rot-vbif-memtype = <3 3>; + + com,mdss-rot-cdp-setting = <1 1>; + + qcom,mdss-default-ot-rd-limit = <8>; + qcom,mdss-default-ot-wr-limit = <16>; + + qcom,mdss-rot-qos-lut = <0x0 0x0 0x0 0x0>; + qcom,mdss-rot-danger-lut = <0x0 0x0>; + qcom,mdss-rot-safe-lut = <0x0000ffff 0x0>; + + qcom,mdss-inline-rot-qos-lut = <0x0 0x0 0x00112233 0x44556677>; + qcom,mdss-inline-rot-danger-lut = <0x0 0x0000ffff>; + qcom,mdss-inline-rot-safe-lut = <0x0 0x0000ff00>; + + qcom,mdss-sbuf-headroom = <20>; + cache-slice-names = "rotator"; + cache-slices = <&llcc 4>; + + rot_reg: qcom,rot-reg-bus { + qcom,msm-bus,name = "mdss_rot_reg"; + qcom,msm-bus,num-cases = <2>; + qcom,msm-bus,num-paths = <1>; + qcom,msm-bus,active-only; + qcom,msm-bus,vectors-KBps = + <1 590 0 0>, + <1 590 0 76800>; + }; + + smmu_rot_unsec: qcom,smmu_rot_unsec_cb { + compatible = "qcom,smmu_sde_rot_unsec"; + iommus = <&mdp_smmu 0xe00>; + gdsc-mdss-supply = <&gdsc_bimc_smmu>; + clocks = <&clock_mmss clk_bimc_smmu_ahb_clk>, + <&clock_mmss clk_bimc_smmu_axi_clk>; + clock-names = "rot_ahb_clk", "rot_axi_clk"; + }; + + smmu_sde_rot_sec: qcom,smmu_sde_rot_sec_cb { + compatible = "qcom,smmu_sde_rot_sec"; + iommus = <&mmss_smmu 0xe01>; + gdsc-mdss-supply = <&gdsc_bimc_smmu>; + clocks = <&clock_mmss clk_bimc_smmu_ahb_clk>, + <&clock_mmss clk_bimc_smmu_axi_clk>; + clock-names = "rot_ahb_clk", "rot_axi_clk"; + }; + }; diff --git a/Documentation/devicetree/bindings/msm_hdcp/msm_hdcp.txt b/Documentation/devicetree/bindings/msm_hdcp/msm_hdcp.txt new file mode 100644 index 0000000000000000000000000000000000000000..8d5f55d7a8ca2428e822abb5a6efd7c59b3564dd --- /dev/null +++ b/Documentation/devicetree/bindings/msm_hdcp/msm_hdcp.txt @@ -0,0 +1,14 @@ +MSM HDCP driver + +Standalone driver managing HDCP related communications +between TZ and HLOS for MSM chipset. + +Required properties: + +compatible = "qcom,msm-hdcp"; + +Example: + +qcom_msmhdcp: qcom,msm_hdcp { + compatible = "qcom,msm-hdcp"; +}; diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig index 229d42509c1ee517c5392455efcd573c1f19e448..603e09381198f1df29802d7800cedcf7c62576ab 100644 --- a/drivers/clk/qcom/Kconfig +++ b/drivers/clk/qcom/Kconfig @@ -1,3 +1,5 @@ +source "drivers/clk/qcom/mdss/Kconfig" + config QCOM_GDSC bool select PM_GENERIC_DOMAINS if PM diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile index 7a61b39e112fa8039846deb14ed5610befb6c9df..f3acafe3f89a7483de7610012d2bd8ffd8daf2fb 100644 --- a/drivers/clk/qcom/Makefile +++ b/drivers/clk/qcom/Makefile @@ -42,3 +42,5 @@ obj-$(CONFIG_MSM_NPUCC_SDM855) += npucc-sdm855.o obj-$(CONFIG_MSM_VIDEOCC_SDM855) += videocc-sdm855.o obj-$(CONFIG_QCOM_CLK_RPM) += clk-rpm.o obj-$(CONFIG_QCOM_CLK_SMD_RPM) += clk-smd-rpm.o + +obj-y += mdss/ diff --git a/drivers/clk/qcom/clk-rcg2.c b/drivers/clk/qcom/clk-rcg2.c index 9a72862c7384fddbffed1c05454ea3f0282dbfe8..7e05f58f2e37b06099ffb1df76a4b0dbeafeb826 100644 --- a/drivers/clk/qcom/clk-rcg2.c +++ b/drivers/clk/qcom/clk-rcg2.c @@ -989,10 +989,11 @@ const struct clk_ops clk_byte2_ops = { EXPORT_SYMBOL_GPL(clk_byte2_ops); static const struct frac_entry frac_table_pixel[] = { + { 1, 1 }, + { 2, 3 }, + { 4, 9 }, { 3, 8 }, { 2, 9 }, - { 4, 9 }, - { 1, 1 }, { } }; diff --git a/drivers/clk/qcom/mdss/Kconfig b/drivers/clk/qcom/mdss/Kconfig new file mode 100644 index 0000000000000000000000000000000000000000..7213e375f1ef57e787f43617b43446a3ec78bc7d --- /dev/null +++ b/drivers/clk/qcom/mdss/Kconfig @@ -0,0 +1,7 @@ +config QCOM_MDSS_PLL + bool "MDSS pll programming" + depends on COMMON_CLK_QCOM + ---help--- + It provides support for DSI, eDP and HDMI interface pll programming on MDSS + hardware. It also handles the pll specific resources and turn them on/off when + mdss pll client tries to enable/disable pll clocks. diff --git a/drivers/clk/qcom/mdss/Makefile b/drivers/clk/qcom/mdss/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..87feee6f282d6978c1634523fb3645a009261317 --- /dev/null +++ b/drivers/clk/qcom/mdss/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-pll-util.o +obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-pll.o +obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-dsi-pll-10nm.o +obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-dp-pll-10nm.o +obj-$(CONFIG_QCOM_MDSS_PLL) += mdss-dp-pll-10nm-util.o + diff --git a/drivers/clk/qcom/mdss/mdss-dp-pll-10nm-util.c b/drivers/clk/qcom/mdss/mdss-dp-pll-10nm-util.c new file mode 100644 index 0000000000000000000000000000000000000000..eb2092a9e102993d25b4fba2c78511ba4259c459 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dp-pll-10nm-util.c @@ -0,0 +1,766 @@ +/* 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. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dp-pll.h" +#include "mdss-dp-pll-10nm.h" + +#define DP_PHY_REVISION_ID0 0x0000 +#define DP_PHY_REVISION_ID1 0x0004 +#define DP_PHY_REVISION_ID2 0x0008 +#define DP_PHY_REVISION_ID3 0x000C + +#define DP_PHY_CFG 0x0010 +#define DP_PHY_PD_CTL 0x0018 +#define DP_PHY_MODE 0x001C + +#define DP_PHY_AUX_CFG0 0x0020 +#define DP_PHY_AUX_CFG1 0x0024 +#define DP_PHY_AUX_CFG2 0x0028 +#define DP_PHY_AUX_CFG3 0x002C +#define DP_PHY_AUX_CFG4 0x0030 +#define DP_PHY_AUX_CFG5 0x0034 +#define DP_PHY_AUX_CFG6 0x0038 +#define DP_PHY_AUX_CFG7 0x003C +#define DP_PHY_AUX_CFG8 0x0040 +#define DP_PHY_AUX_CFG9 0x0044 +#define DP_PHY_AUX_INTERRUPT_MASK 0x0048 +#define DP_PHY_AUX_INTERRUPT_CLEAR 0x004C +#define DP_PHY_AUX_BIST_CFG 0x0050 + +#define DP_PHY_VCO_DIV 0x0064 +#define DP_PHY_TX0_TX1_LANE_CTL 0x006C +#define DP_PHY_TX2_TX3_LANE_CTL 0x0088 + +#define DP_PHY_SPARE0 0x00AC +#define DP_PHY_STATUS 0x00C0 + +/* Tx registers */ +#define TXn_BIST_MODE_LANENO 0x0000 +#define TXn_CLKBUF_ENABLE 0x0008 +#define TXn_TX_EMP_POST1_LVL 0x000C + +#define TXn_TX_DRV_LVL 0x001C + +#define TXn_RESET_TSYNC_EN 0x0024 +#define TXn_PRE_STALL_LDO_BOOST_EN 0x0028 +#define TXn_TX_BAND 0x002C +#define TXn_SLEW_CNTL 0x0030 +#define TXn_INTERFACE_SELECT 0x0034 + +#define TXn_RES_CODE_LANE_TX 0x003C +#define TXn_RES_CODE_LANE_RX 0x0040 +#define TXn_RES_CODE_LANE_OFFSET_TX 0x0044 +#define TXn_RES_CODE_LANE_OFFSET_RX 0x0048 + +#define TXn_DEBUG_BUS_SEL 0x0058 +#define TXn_TRANSCEIVER_BIAS_EN 0x005C +#define TXn_HIGHZ_DRVR_EN 0x0060 +#define TXn_TX_POL_INV 0x0064 +#define TXn_PARRATE_REC_DETECT_IDLE_EN 0x0068 + +#define TXn_LANE_MODE_1 0x008C + +#define TXn_TRAN_DRVR_EMP_EN 0x00C0 +#define TXn_TX_INTERFACE_MODE 0x00C4 + +#define TXn_VMODE_CTRL1 0x00F0 + +/* PLL register offset */ +#define QSERDES_COM_ATB_SEL1 0x0000 +#define QSERDES_COM_ATB_SEL2 0x0004 +#define QSERDES_COM_FREQ_UPDATE 0x0008 +#define QSERDES_COM_BG_TIMER 0x000C +#define QSERDES_COM_SSC_EN_CENTER 0x0010 +#define QSERDES_COM_SSC_ADJ_PER1 0x0014 +#define QSERDES_COM_SSC_ADJ_PER2 0x0018 +#define QSERDES_COM_SSC_PER1 0x001C +#define QSERDES_COM_SSC_PER2 0x0020 +#define QSERDES_COM_SSC_STEP_SIZE1 0x0024 +#define QSERDES_COM_SSC_STEP_SIZE2 0x0028 +#define QSERDES_COM_POST_DIV 0x002C +#define QSERDES_COM_POST_DIV_MUX 0x0030 +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN 0x0034 +#define QSERDES_COM_CLK_ENABLE1 0x0038 +#define QSERDES_COM_SYS_CLK_CTRL 0x003C +#define QSERDES_COM_SYSCLK_BUF_ENABLE 0x0040 +#define QSERDES_COM_PLL_EN 0x0044 +#define QSERDES_COM_PLL_IVCO 0x0048 +#define QSERDES_COM_CMN_IETRIM 0x004C +#define QSERDES_COM_CMN_IPTRIM 0x0050 + +#define QSERDES_COM_CP_CTRL_MODE0 0x0060 +#define QSERDES_COM_CP_CTRL_MODE1 0x0064 +#define QSERDES_COM_PLL_RCTRL_MODE0 0x0068 +#define QSERDES_COM_PLL_RCTRL_MODE1 0x006C +#define QSERDES_COM_PLL_CCTRL_MODE0 0x0070 +#define QSERDES_COM_PLL_CCTRL_MODE1 0x0074 +#define QSERDES_COM_PLL_CNTRL 0x0078 +#define QSERDES_COM_BIAS_EN_CTRL_BY_PSM 0x007C +#define QSERDES_COM_SYSCLK_EN_SEL 0x0080 +#define QSERDES_COM_CML_SYSCLK_SEL 0x0084 +#define QSERDES_COM_RESETSM_CNTRL 0x0088 +#define QSERDES_COM_RESETSM_CNTRL2 0x008C +#define QSERDES_COM_LOCK_CMP_EN 0x0090 +#define QSERDES_COM_LOCK_CMP_CFG 0x0094 +#define QSERDES_COM_LOCK_CMP1_MODE0 0x0098 +#define QSERDES_COM_LOCK_CMP2_MODE0 0x009C +#define QSERDES_COM_LOCK_CMP3_MODE0 0x00A0 + +#define QSERDES_COM_DEC_START_MODE0 0x00B0 +#define QSERDES_COM_DEC_START_MODE1 0x00B4 +#define QSERDES_COM_DIV_FRAC_START1_MODE0 0x00B8 +#define QSERDES_COM_DIV_FRAC_START2_MODE0 0x00BC +#define QSERDES_COM_DIV_FRAC_START3_MODE0 0x00C0 +#define QSERDES_COM_DIV_FRAC_START1_MODE1 0x00C4 +#define QSERDES_COM_DIV_FRAC_START2_MODE1 0x00C8 +#define QSERDES_COM_DIV_FRAC_START3_MODE1 0x00CC +#define QSERDES_COM_INTEGLOOP_INITVAL 0x00D0 +#define QSERDES_COM_INTEGLOOP_EN 0x00D4 +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 0x00D8 +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 0x00DC +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 0x00E0 +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 0x00E4 +#define QSERDES_COM_VCOCAL_DEADMAN_CTRL 0x00E8 +#define QSERDES_COM_VCO_TUNE_CTRL 0x00EC +#define QSERDES_COM_VCO_TUNE_MAP 0x00F0 + +#define QSERDES_COM_CMN_STATUS 0x0124 +#define QSERDES_COM_RESET_SM_STATUS 0x0128 + +#define QSERDES_COM_CLK_SEL 0x0138 +#define QSERDES_COM_HSCLK_SEL 0x013C + +#define QSERDES_COM_CORECLK_DIV_MODE0 0x0148 + +#define QSERDES_COM_SW_RESET 0x0150 +#define QSERDES_COM_CORE_CLK_EN 0x0154 +#define QSERDES_COM_C_READY_STATUS 0x0158 +#define QSERDES_COM_CMN_CONFIG 0x015C + +#define QSERDES_COM_SVS_MODE_CLK_SEL 0x0164 + +#define DP_PHY_PLL_POLL_SLEEP_US 500 +#define DP_PHY_PLL_POLL_TIMEOUT_US 10000 + +#define DP_VCO_RATE_8100MHZDIV1000 8100000UL +#define DP_VCO_RATE_9720MHZDIV1000 9720000UL +#define DP_VCO_RATE_10800MHZDIV1000 10800000UL + +int dp_mux_set_parent_10nm(void *context, unsigned int reg, unsigned int val) +{ + struct mdss_pll_resources *dp_res = context; + int rc; + u32 auxclk_div; + + rc = mdss_pll_resource_enable(dp_res, true); + if (rc) { + pr_err("Failed to enable mdss DP PLL resources\n"); + return rc; + } + + auxclk_div = MDSS_PLL_REG_R(dp_res->phy_base, DP_PHY_VCO_DIV); + auxclk_div &= ~0x03; /* bits 0 to 1 */ + + if (val == 0) /* mux parent index = 0 */ + auxclk_div |= 1; + else if (val == 1) /* mux parent index = 1 */ + auxclk_div |= 2; + else if (val == 2) /* mux parent index = 2 */ + auxclk_div |= 0; + + MDSS_PLL_REG_W(dp_res->phy_base, + DP_PHY_VCO_DIV, auxclk_div); + /* Make sure the PHY registers writes are done */ + wmb(); + pr_debug("%s: mux=%d auxclk_div=%x\n", __func__, val, auxclk_div); + + mdss_pll_resource_enable(dp_res, false); + + return 0; +} + +int dp_mux_get_parent_10nm(void *context, unsigned int reg, unsigned int *val) +{ + int rc; + u32 auxclk_div = 0; + struct mdss_pll_resources *dp_res = context; + + rc = mdss_pll_resource_enable(dp_res, true); + if (rc) { + pr_err("Failed to enable dp_res resources\n"); + return rc; + } + + auxclk_div = MDSS_PLL_REG_R(dp_res->phy_base, DP_PHY_VCO_DIV); + auxclk_div &= 0x03; + + if (auxclk_div == 1) /* Default divider */ + *val = 0; + else if (auxclk_div == 2) + *val = 1; + else if (auxclk_div == 0) + *val = 2; + + mdss_pll_resource_enable(dp_res, false); + + pr_debug("%s: auxclk_div=%d, val=%d\n", __func__, auxclk_div, *val); + + return 0; +} + +static int dp_vco_pll_init_db_10nm(struct dp_pll_db *pdb, + unsigned long rate) +{ + struct mdss_pll_resources *dp_res = pdb->pll; + u32 spare_value = 0; + + spare_value = MDSS_PLL_REG_R(dp_res->phy_base, DP_PHY_SPARE0); + pdb->lane_cnt = spare_value & 0x0F; + pdb->orientation = (spare_value & 0xF0) >> 4; + + pr_debug("%s: spare_value=0x%x, ln_cnt=0x%x, orientation=0x%x\n", + __func__, spare_value, pdb->lane_cnt, pdb->orientation); + + switch (rate) { + case DP_VCO_HSCLK_RATE_1620MHZDIV1000: + pr_debug("%s: VCO rate: %ld\n", __func__, + DP_VCO_RATE_9720MHZDIV1000); + pdb->hsclk_sel = 0x0c; + pdb->dec_start_mode0 = 0x69; + pdb->div_frac_start1_mode0 = 0x00; + pdb->div_frac_start2_mode0 = 0x80; + pdb->div_frac_start3_mode0 = 0x07; + pdb->integloop_gain0_mode0 = 0x3f; + pdb->integloop_gain1_mode0 = 0x00; + pdb->vco_tune_map = 0x00; + pdb->lock_cmp1_mode0 = 0x6f; + pdb->lock_cmp2_mode0 = 0x08; + pdb->lock_cmp3_mode0 = 0x00; + pdb->phy_vco_div = 0x1; + pdb->lock_cmp_en = 0x00; + break; + case DP_VCO_HSCLK_RATE_2700MHZDIV1000: + pr_debug("%s: VCO rate: %ld\n", __func__, + DP_VCO_RATE_10800MHZDIV1000); + pdb->hsclk_sel = 0x04; + pdb->dec_start_mode0 = 0x69; + pdb->div_frac_start1_mode0 = 0x00; + pdb->div_frac_start2_mode0 = 0x80; + pdb->div_frac_start3_mode0 = 0x07; + pdb->integloop_gain0_mode0 = 0x3f; + pdb->integloop_gain1_mode0 = 0x00; + pdb->vco_tune_map = 0x00; + pdb->lock_cmp1_mode0 = 0x0f; + pdb->lock_cmp2_mode0 = 0x0e; + pdb->lock_cmp3_mode0 = 0x00; + pdb->phy_vco_div = 0x1; + pdb->lock_cmp_en = 0x00; + break; + case DP_VCO_HSCLK_RATE_5400MHZDIV1000: + pr_debug("%s: VCO rate: %ld\n", __func__, + DP_VCO_RATE_10800MHZDIV1000); + pdb->hsclk_sel = 0x00; + pdb->dec_start_mode0 = 0x8c; + pdb->div_frac_start1_mode0 = 0x00; + pdb->div_frac_start2_mode0 = 0x00; + pdb->div_frac_start3_mode0 = 0x0a; + pdb->integloop_gain0_mode0 = 0x3f; + pdb->integloop_gain1_mode0 = 0x00; + pdb->vco_tune_map = 0x00; + pdb->lock_cmp1_mode0 = 0x1f; + pdb->lock_cmp2_mode0 = 0x1c; + pdb->lock_cmp3_mode0 = 0x00; + pdb->phy_vco_div = 0x2; + pdb->lock_cmp_en = 0x00; + break; + case DP_VCO_HSCLK_RATE_8100MHZDIV1000: + pr_debug("%s: VCO rate: %ld\n", __func__, + DP_VCO_RATE_8100MHZDIV1000); + pdb->hsclk_sel = 0x03; + pdb->dec_start_mode0 = 0x69; + pdb->div_frac_start1_mode0 = 0x00; + pdb->div_frac_start2_mode0 = 0x80; + pdb->div_frac_start3_mode0 = 0x07; + pdb->integloop_gain0_mode0 = 0x3f; + pdb->integloop_gain1_mode0 = 0x00; + pdb->vco_tune_map = 0x00; + pdb->lock_cmp1_mode0 = 0x2f; + pdb->lock_cmp2_mode0 = 0x2a; + pdb->lock_cmp3_mode0 = 0x00; + pdb->phy_vco_div = 0x0; + pdb->lock_cmp_en = 0x08; + break; + default: + return -EINVAL; + } + return 0; +} + +static int dp_config_vco_rate_10nm(struct dp_pll_vco_clk *vco, + unsigned long rate) +{ + u32 res = 0; + struct mdss_pll_resources *dp_res = vco->priv; + struct dp_pll_db *pdb = (struct dp_pll_db *)dp_res->priv; + + res = dp_vco_pll_init_db_10nm(pdb, rate); + if (res) { + pr_err("VCO Init DB failed\n"); + return res; + } + + if (pdb->lane_cnt != 4) { + if (pdb->orientation == ORIENTATION_CC2) + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_PD_CTL, 0x6d); + else + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_PD_CTL, 0x75); + } else { + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_PD_CTL, 0x7d); + } + + /* Make sure the PHY register writes are done */ + wmb(); + + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_SVS_MODE_CLK_SEL, 0x01); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_SYSCLK_EN_SEL, 0x37); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_SYS_CLK_CTRL, 0x02); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CLK_ENABLE1, 0x0e); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_SYSCLK_BUF_ENABLE, 0x06); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CLK_SEL, 0x30); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CMN_CONFIG, 0x02); + + /* Different for each clock rates */ + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_HSCLK_SEL, pdb->hsclk_sel); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_DEC_START_MODE0, pdb->dec_start_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_DIV_FRAC_START1_MODE0, pdb->div_frac_start1_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_DIV_FRAC_START2_MODE0, pdb->div_frac_start2_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_DIV_FRAC_START3_MODE0, pdb->div_frac_start3_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_INTEGLOOP_GAIN0_MODE0, pdb->integloop_gain0_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_INTEGLOOP_GAIN1_MODE0, pdb->integloop_gain1_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_VCO_TUNE_MAP, pdb->vco_tune_map); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_LOCK_CMP1_MODE0, pdb->lock_cmp1_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_LOCK_CMP2_MODE0, pdb->lock_cmp2_mode0); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_LOCK_CMP3_MODE0, pdb->lock_cmp3_mode0); + /* Make sure the PLL register writes are done */ + wmb(); + + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_BG_TIMER, 0x0a); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CORECLK_DIV_MODE0, 0x0a); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_VCO_TUNE_CTRL, 0x00); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x3f); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CORE_CLK_EN, 0x1f); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_PLL_IVCO, 0x07); + MDSS_PLL_REG_W(dp_res->pll_base, + QSERDES_COM_LOCK_CMP_EN, pdb->lock_cmp_en); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_PLL_CCTRL_MODE0, 0x36); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_PLL_RCTRL_MODE0, 0x16); + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_CP_CTRL_MODE0, 0x06); + /* Make sure the PHY register writes are done */ + wmb(); + + if (pdb->orientation == ORIENTATION_CC2) + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_MODE, 0x4c); + else + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_MODE, 0x5c); + /* Make sure the PLL register writes are done */ + wmb(); + + /* TX Lane configuration */ + MDSS_PLL_REG_W(dp_res->phy_base, + DP_PHY_TX0_TX1_LANE_CTL, 0x05); + MDSS_PLL_REG_W(dp_res->phy_base, + DP_PHY_TX2_TX3_LANE_CTL, 0x05); + + /* TX-0 register configuration */ + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TRANSCEIVER_BIAS_EN, 0x1a); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_VMODE_CTRL1, 0x40); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_PRE_STALL_LDO_BOOST_EN, 0x30); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_INTERFACE_SELECT, 0x3d); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_CLKBUF_ENABLE, 0x0f); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_RESET_TSYNC_EN, 0x03); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, + TXn_PARRATE_REC_DETECT_IDLE_EN, 0x00); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TX_INTERFACE_MODE, 0x00); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TX_BAND, 0x4); + + /* TX-1 register configuration */ + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TRANSCEIVER_BIAS_EN, 0x1a); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_VMODE_CTRL1, 0x40); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_PRE_STALL_LDO_BOOST_EN, 0x30); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_INTERFACE_SELECT, 0x3d); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_CLKBUF_ENABLE, 0x0f); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_RESET_TSYNC_EN, 0x03); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, + TXn_PARRATE_REC_DETECT_IDLE_EN, 0x00); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TX_INTERFACE_MODE, 0x00); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TX_BAND, 0x4); + /* Make sure the PHY register writes are done */ + wmb(); + + /* dependent on the vco frequency */ + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_VCO_DIV, pdb->phy_vco_div); + + return res; +} + +static bool dp_10nm_pll_lock_status(struct mdss_pll_resources *dp_res) +{ + u32 status; + bool pll_locked; + + /* poll for PLL lock status */ + if (readl_poll_timeout_atomic((dp_res->pll_base + + QSERDES_COM_C_READY_STATUS), + status, + ((status & BIT(0)) > 0), + DP_PHY_PLL_POLL_SLEEP_US, + DP_PHY_PLL_POLL_TIMEOUT_US)) { + pr_err("%s: C_READY status is not high. Status=%x\n", + __func__, status); + pll_locked = false; + } else { + pll_locked = true; + } + + return pll_locked; +} + +static bool dp_10nm_phy_rdy_status(struct mdss_pll_resources *dp_res) +{ + u32 status; + bool phy_ready = true; + + /* poll for PHY ready status */ + if (readl_poll_timeout_atomic((dp_res->phy_base + + DP_PHY_STATUS), + status, + ((status & (BIT(1))) > 0), + DP_PHY_PLL_POLL_SLEEP_US, + DP_PHY_PLL_POLL_TIMEOUT_US)) { + pr_err("%s: Phy_ready is not high. Status=%x\n", + __func__, status); + phy_ready = false; + } + + return phy_ready; +} + +static int dp_pll_enable_10nm(struct clk_hw *hw) +{ + int rc = 0; + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + struct mdss_pll_resources *dp_res = vco->priv; + struct dp_pll_db *pdb = (struct dp_pll_db *)dp_res->priv; + u32 bias_en, drvr_en; + + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_AUX_CFG2, 0x04); + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x01); + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x05); + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x01); + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x09); + wmb(); /* Make sure the PHY register writes are done */ + + MDSS_PLL_REG_W(dp_res->pll_base, QSERDES_COM_RESETSM_CNTRL, 0x20); + wmb(); /* Make sure the PLL register writes are done */ + + if (!dp_10nm_pll_lock_status(dp_res)) { + rc = -EINVAL; + goto lock_err; + } + + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x19); + /* Make sure the PHY register writes are done */ + wmb(); + /* poll for PHY ready status */ + if (!dp_10nm_phy_rdy_status(dp_res)) { + rc = -EINVAL; + goto lock_err; + } + + pr_debug("%s: PLL is locked\n", __func__); + + if (pdb->lane_cnt == 1) { + bias_en = 0x3e; + drvr_en = 0x13; + } else { + bias_en = 0x3f; + drvr_en = 0x10; + } + + if (pdb->lane_cnt != 4) { + if (pdb->orientation == ORIENTATION_CC1) { + MDSS_PLL_REG_W(dp_res->ln_tx1_base, + TXn_HIGHZ_DRVR_EN, drvr_en); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, + TXn_TRANSCEIVER_BIAS_EN, bias_en); + } else { + MDSS_PLL_REG_W(dp_res->ln_tx0_base, + TXn_HIGHZ_DRVR_EN, drvr_en); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, + TXn_TRANSCEIVER_BIAS_EN, bias_en); + } + } else { + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_HIGHZ_DRVR_EN, drvr_en); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, + TXn_TRANSCEIVER_BIAS_EN, bias_en); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_HIGHZ_DRVR_EN, drvr_en); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, + TXn_TRANSCEIVER_BIAS_EN, bias_en); + } + + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TX_POL_INV, 0x0a); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TX_POL_INV, 0x0a); + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x18); + udelay(2000); + + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_CFG, 0x19); + + /* + * Make sure all the register writes are completed before + * doing any other operation + */ + wmb(); + + /* poll for PHY ready status */ + if (!dp_10nm_phy_rdy_status(dp_res)) { + rc = -EINVAL; + goto lock_err; + } + + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TX_DRV_LVL, 0x38); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TX_DRV_LVL, 0x38); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_TX_EMP_POST1_LVL, 0x20); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_TX_EMP_POST1_LVL, 0x20); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_RES_CODE_LANE_OFFSET_TX, 0x06); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_RES_CODE_LANE_OFFSET_TX, 0x06); + MDSS_PLL_REG_W(dp_res->ln_tx0_base, TXn_RES_CODE_LANE_OFFSET_RX, 0x07); + MDSS_PLL_REG_W(dp_res->ln_tx1_base, TXn_RES_CODE_LANE_OFFSET_RX, 0x07); + /* Make sure the PHY register writes are done */ + wmb(); + +lock_err: + return rc; +} + +static int dp_pll_disable_10nm(struct clk_hw *hw) +{ + int rc = 0; + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + struct mdss_pll_resources *dp_res = vco->priv; + + /* Assert DP PHY power down */ + MDSS_PLL_REG_W(dp_res->phy_base, DP_PHY_PD_CTL, 0x2); + /* + * Make sure all the register writes to disable PLL are + * completed before doing any other operation + */ + wmb(); + + return rc; +} + + +int dp_vco_prepare_10nm(struct clk_hw *hw) +{ + int rc = 0; + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + struct mdss_pll_resources *dp_res = vco->priv; + + pr_debug("rate=%ld\n", vco->rate); + rc = mdss_pll_resource_enable(dp_res, true); + if (rc) { + pr_err("Failed to enable mdss DP pll resources\n"); + goto error; + } + + if ((dp_res->vco_cached_rate != 0) + && (dp_res->vco_cached_rate == vco->rate)) { + rc = vco->hw.init->ops->set_rate(hw, + dp_res->vco_cached_rate, dp_res->vco_cached_rate); + if (rc) { + pr_err("index=%d vco_set_rate failed. rc=%d\n", + rc, dp_res->index); + mdss_pll_resource_enable(dp_res, false); + goto error; + } + } + + rc = dp_pll_enable_10nm(hw); + if (rc) { + mdss_pll_resource_enable(dp_res, false); + pr_err("ndx=%d failed to enable dp pll\n", + dp_res->index); + goto error; + } + + mdss_pll_resource_enable(dp_res, false); +error: + return rc; +} + +void dp_vco_unprepare_10nm(struct clk_hw *hw) +{ + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + struct mdss_pll_resources *dp_res = vco->priv; + + if (!dp_res) { + pr_err("Invalid input parameter\n"); + return; + } + + if (!dp_res->pll_on && + mdss_pll_resource_enable(dp_res, true)) { + pr_err("pll resource can't be enabled\n"); + return; + } + dp_res->vco_cached_rate = vco->rate; + dp_pll_disable_10nm(hw); + + dp_res->handoff_resources = false; + mdss_pll_resource_enable(dp_res, false); + dp_res->pll_on = false; +} + +int dp_vco_set_rate_10nm(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + struct mdss_pll_resources *dp_res = vco->priv; + int rc; + + rc = mdss_pll_resource_enable(dp_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + pr_debug("DP lane CLK rate=%ld\n", rate); + + rc = dp_config_vco_rate_10nm(vco, rate); + if (rc) + pr_err("%s: Failed to set clk rate\n", __func__); + + mdss_pll_resource_enable(dp_res, false); + + vco->rate = rate; + + return 0; +} + +unsigned long dp_vco_recalc_rate_10nm(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + int rc; + u32 div, hsclk_div, link_clk_div = 0; + u64 vco_rate; + struct mdss_pll_resources *dp_res = vco->priv; + + rc = mdss_pll_resource_enable(dp_res, true); + if (rc) { + pr_err("Failed to enable mdss DP pll=%d\n", dp_res->index); + return rc; + } + + div = MDSS_PLL_REG_R(dp_res->pll_base, QSERDES_COM_HSCLK_SEL); + div &= 0x0f; + + if (div == 12) + hsclk_div = 6; /* Default */ + else if (div == 4) + hsclk_div = 4; + else if (div == 0) + hsclk_div = 2; + else if (div == 3) + hsclk_div = 1; + else { + pr_debug("unknown divider. forcing to default\n"); + hsclk_div = 5; + } + + div = MDSS_PLL_REG_R(dp_res->phy_base, DP_PHY_AUX_CFG2); + div >>= 2; + + if ((div & 0x3) == 0) + link_clk_div = 5; + else if ((div & 0x3) == 1) + link_clk_div = 10; + else if ((div & 0x3) == 2) + link_clk_div = 20; + else + pr_err("%s: unsupported div. Phy_mode: %d\n", __func__, div); + + if (link_clk_div == 20) { + vco_rate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; + } else { + if (hsclk_div == 6) + vco_rate = DP_VCO_HSCLK_RATE_1620MHZDIV1000; + else if (hsclk_div == 4) + vco_rate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; + else if (hsclk_div == 2) + vco_rate = DP_VCO_HSCLK_RATE_5400MHZDIV1000; + else + vco_rate = DP_VCO_HSCLK_RATE_8100MHZDIV1000; + } + + pr_debug("returning vco rate = %lu\n", (unsigned long)vco_rate); + + mdss_pll_resource_enable(dp_res, false); + + dp_res->vco_cached_rate = vco->rate = vco_rate; + return (unsigned long)vco_rate; +} + +long dp_vco_round_rate_10nm(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long rrate = rate; + struct dp_pll_vco_clk *vco = to_dp_vco_hw(hw); + + if (rate <= vco->min_rate) + rrate = vco->min_rate; + else if (rate <= DP_VCO_HSCLK_RATE_2700MHZDIV1000) + rrate = DP_VCO_HSCLK_RATE_2700MHZDIV1000; + else if (rate <= DP_VCO_HSCLK_RATE_5400MHZDIV1000) + rrate = DP_VCO_HSCLK_RATE_5400MHZDIV1000; + else + rrate = vco->max_rate; + + pr_debug("%s: rrate=%ld\n", __func__, rrate); + + *parent_rate = rrate; + return rrate; +} + diff --git a/drivers/clk/qcom/mdss/mdss-dp-pll-10nm.c b/drivers/clk/qcom/mdss/mdss-dp-pll-10nm.c new file mode 100644 index 0000000000000000000000000000000000000000..e30ef8251903b7ebf7e808e9baa81ac4635378a9 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dp-pll-10nm.c @@ -0,0 +1,310 @@ +/* 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. + * + */ + +/* + * Display Port PLL driver block diagram for branch clocks + * + * +------------------------------+ + * | DP_VCO_CLK | + * | | + * | +-------------------+ | + * | | (DP PLL/VCO) | | + * | +---------+---------+ | + * | v | + * | +----------+-----------+ | + * | | hsclk_divsel_clk_src | | + * | +----------+-----------+ | + * +------------------------------+ + * | + * +------------<---------v------------>----------+ + * | | + * +-----v------------+ | + * | dp_link_clk_src | | + * | divsel_ten | | + * +---------+--------+ | + * | | + * | | + * v v + * Input to DISPCC block | + * for link clk, crypto clk | + * and interface clock | + * | + * | + * +--------<------------+-----------------+---<---+ + * | | | + * +-------v------+ +--------v-----+ +--------v------+ + * | vco_divided | | vco_divided | | vco_divided | + * | _clk_src | | _clk_src | | _clk_src | + * | | | | | | + * |divsel_six | | divsel_two | | divsel_four | + * +-------+------+ +-----+--------+ +--------+------+ + * | | | + * v------->----------v-------------<------v + * | + * +----------+---------+ + * | vco_divided_clk | + * | _src_mux | + * +---------+----------+ + * | + * v + * Input to DISPCC block + * for DP pixel clock + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dp-pll.h" +#include "mdss-dp-pll-10nm.h" + +static struct dp_pll_db dp_pdb; +static struct clk_ops mux_clk_ops; + +static struct regmap_config dp_pll_10nm_cfg = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x910, +}; + +static struct regmap_bus dp_pixel_mux_regmap_ops = { + .reg_write = dp_mux_set_parent_10nm, + .reg_read = dp_mux_get_parent_10nm, +}; + +/* Op structures */ +static const struct clk_ops dp_10nm_vco_clk_ops = { + .recalc_rate = dp_vco_recalc_rate_10nm, + .set_rate = dp_vco_set_rate_10nm, + .round_rate = dp_vco_round_rate_10nm, + .prepare = dp_vco_prepare_10nm, + .unprepare = dp_vco_unprepare_10nm, +}; + +static struct dp_pll_vco_clk dp_vco_clk = { + .min_rate = DP_VCO_HSCLK_RATE_1620MHZDIV1000, + .max_rate = DP_VCO_HSCLK_RATE_8100MHZDIV1000, + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_clk", + .parent_names = (const char *[]){ "xo_board" }, + .num_parents = 1, + .ops = &dp_10nm_vco_clk_ops, + }, +}; + +static struct clk_fixed_factor dp_link_clk_divsel_ten = { + .div = 10, + .mult = 1, + + .hw.init = &(struct clk_init_data){ + .name = "dp_link_clk_divsel_ten", + .parent_names = + (const char *[]){ "dp_vco_clk" }, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dp_vco_divsel_two_clk_src = { + .div = 2, + .mult = 1, + + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_divsel_two_clk_src", + .parent_names = + (const char *[]){ "dp_vco_clk" }, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dp_vco_divsel_four_clk_src = { + .div = 4, + .mult = 1, + + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_divsel_four_clk_src", + .parent_names = + (const char *[]){ "dp_vco_clk" }, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dp_vco_divsel_six_clk_src = { + .div = 6, + .mult = 1, + + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_divsel_six_clk_src", + .parent_names = + (const char *[]){ "dp_vco_clk" }, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE), + .ops = &clk_fixed_factor_ops, + }, +}; + + +static int clk_mux_determine_rate(struct clk_hw *hw, + struct clk_rate_request *req) +{ + int ret = 0; + + ret = __clk_mux_determine_rate_closest(hw, req); + if (ret) + return ret; + + /* Set the new parent of mux if there is a new valid parent */ + if (hw->clk && req->best_parent_hw->clk) + clk_set_parent(hw->clk, req->best_parent_hw->clk); + + return 0; +} + +static unsigned long mux_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct clk *div_clk = NULL, *vco_clk = NULL; + struct dp_pll_vco_clk *vco = NULL; + + div_clk = clk_get_parent(hw->clk); + if (!div_clk) + return 0; + + vco_clk = clk_get_parent(div_clk); + if (!vco_clk) + return 0; + + vco = to_dp_vco_hw(__clk_get_hw(vco_clk)); + if (!vco) + return 0; + + if (vco->rate == DP_VCO_HSCLK_RATE_8100MHZDIV1000) + return (vco->rate / 6); + else if (vco->rate == DP_VCO_HSCLK_RATE_5400MHZDIV1000) + return (vco->rate / 4); + else + return (vco->rate / 2); +} + +static struct clk_regmap_mux dp_vco_divided_clk_src_mux = { + .reg = 0x64, + .shift = 0, + .width = 2, + + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dp_vco_divided_clk_src_mux", + .parent_names = + (const char *[]){"dp_vco_divsel_two_clk_src", + "dp_vco_divsel_four_clk_src", + "dp_vco_divsel_six_clk_src"}, + .num_parents = 3, + .ops = &mux_clk_ops, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + }, + }, +}; + +static struct clk_hw *mdss_dp_pllcc_10nm[] = { + [DP_VCO_CLK] = &dp_vco_clk.hw, + [DP_LINK_CLK_DIVSEL_TEN] = &dp_link_clk_divsel_ten.hw, + [DP_VCO_DIVIDED_TWO_CLK_SRC] = &dp_vco_divsel_two_clk_src.hw, + [DP_VCO_DIVIDED_FOUR_CLK_SRC] = &dp_vco_divsel_four_clk_src.hw, + [DP_VCO_DIVIDED_SIX_CLK_SRC] = &dp_vco_divsel_six_clk_src.hw, + [DP_VCO_DIVIDED_CLK_SRC_MUX] = &dp_vco_divided_clk_src_mux.clkr.hw, +}; + +int dp_pll_clock_register_10nm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = -ENOTSUPP, i = 0; + struct clk_onecell_data *clk_data; + struct clk *clk; + struct regmap *regmap; + int num_clks = ARRAY_SIZE(mdss_dp_pllcc_10nm); + + if (!pdev || !pdev->dev.of_node) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + if (!pll_res || !pll_res->pll_base || !pll_res->phy_base || + !pll_res->ln_tx0_base || !pll_res->ln_tx1_base) { + pr_err("%s: Invalid input parameters\n", __func__); + return -EINVAL; + } + + clk_data = devm_kzalloc(&pdev->dev, sizeof(struct clk_onecell_data), + GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->clks = devm_kzalloc(&pdev->dev, (num_clks * + sizeof(struct clk *)), GFP_KERNEL); + if (!clk_data->clks) { + devm_kfree(&pdev->dev, clk_data); + return -ENOMEM; + } + clk_data->clk_num = num_clks; + + pll_res->priv = &dp_pdb; + dp_pdb.pll = pll_res; + + /* Set client data for vco, mux and div clocks */ + regmap = devm_regmap_init(&pdev->dev, &dp_pixel_mux_regmap_ops, + pll_res, &dp_pll_10nm_cfg); + dp_vco_divided_clk_src_mux.clkr.regmap = regmap; + mux_clk_ops = clk_regmap_mux_closest_ops; + mux_clk_ops.determine_rate = clk_mux_determine_rate; + mux_clk_ops.recalc_rate = mux_recalc_rate; + + dp_vco_clk.priv = pll_res; + + for (i = DP_VCO_CLK; i <= DP_VCO_DIVIDED_CLK_SRC_MUX; i++) { + pr_debug("reg clk: %d index: %d\n", i, pll_res->index); + clk = devm_clk_register(&pdev->dev, + mdss_dp_pllcc_10nm[i]); + if (IS_ERR(clk)) { + pr_err("clk registration failed for DP: %d\n", + pll_res->index); + rc = -EINVAL; + goto clk_reg_fail; + } + clk_data->clks[i] = clk; + } + + rc = of_clk_add_provider(pdev->dev.of_node, + of_clk_src_onecell_get, clk_data); + if (rc) { + pr_err("%s: Clock register failed rc=%d\n", __func__, rc); + rc = -EPROBE_DEFER; + } else { + pr_debug("%s SUCCESS\n", __func__); + } + return 0; +clk_reg_fail: + devm_kfree(&pdev->dev, clk_data->clks); + devm_kfree(&pdev->dev, clk_data); + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dp-pll-10nm.h b/drivers/clk/qcom/mdss/mdss-dp-pll-10nm.h new file mode 100644 index 0000000000000000000000000000000000000000..c3b5635a55ae9790464bd529a3bbf0286e2ec8c6 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dp-pll-10nm.h @@ -0,0 +1,59 @@ +/* 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 __MDSS_DP_PLL_10NM_H +#define __MDSS_DP_PLL_10NM_H + +#define DP_VCO_HSCLK_RATE_1620MHZDIV1000 1620000UL +#define DP_VCO_HSCLK_RATE_2700MHZDIV1000 2700000UL +#define DP_VCO_HSCLK_RATE_5400MHZDIV1000 5400000UL +#define DP_VCO_HSCLK_RATE_8100MHZDIV1000 8100000UL + +struct dp_pll_db { + struct mdss_pll_resources *pll; + + /* lane and orientation settings */ + u8 lane_cnt; + u8 orientation; + + /* COM PHY settings */ + u32 hsclk_sel; + u32 dec_start_mode0; + u32 div_frac_start1_mode0; + u32 div_frac_start2_mode0; + u32 div_frac_start3_mode0; + u32 integloop_gain0_mode0; + u32 integloop_gain1_mode0; + u32 vco_tune_map; + u32 lock_cmp1_mode0; + u32 lock_cmp2_mode0; + u32 lock_cmp3_mode0; + u32 lock_cmp_en; + + /* PHY vco divider */ + u32 phy_vco_div; +}; + +int dp_vco_set_rate_10nm(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate); +unsigned long dp_vco_recalc_rate_10nm(struct clk_hw *hw, + unsigned long parent_rate); +long dp_vco_round_rate_10nm(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate); +int dp_vco_prepare_10nm(struct clk_hw *hw); +void dp_vco_unprepare_10nm(struct clk_hw *hw); +int dp_mux_set_parent_10nm(void *context, + unsigned int reg, unsigned int val); +int dp_mux_get_parent_10nm(void *context, + unsigned int reg, unsigned int *val); +#endif /* __MDSS_DP_PLL_10NM_H */ diff --git a/drivers/clk/qcom/mdss/mdss-dp-pll.h b/drivers/clk/qcom/mdss/mdss-dp-pll.h new file mode 100644 index 0000000000000000000000000000000000000000..2b1d70ea8f2a1926cc72f9e4f5153efad6c088d4 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dp-pll.h @@ -0,0 +1,33 @@ +/* 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 __MDSS_DP_PLL_H +#define __MDSS_DP_PLL_H + +struct dp_pll_vco_clk { + struct clk_hw hw; + unsigned long rate; /* current vco rate */ + u64 min_rate; /* min vco rate */ + u64 max_rate; /* max vco rate */ + void *priv; +}; + +static inline struct dp_pll_vco_clk *to_dp_vco_hw(struct clk_hw *hw) +{ + return container_of(hw, struct dp_pll_vco_clk, hw); +} + +int dp_pll_clock_register_10nm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +#endif /* __MDSS_DP_PLL_H */ diff --git a/drivers/clk/qcom/mdss/mdss-dsi-20nm-pll-util.c b/drivers/clk/qcom/mdss/mdss-dsi-20nm-pll-util.c new file mode 100644 index 0000000000000000000000000000000000000000..bc775b19b3d9bf9b413ae44379a38f510cefa24e --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-20nm-pll-util.c @@ -0,0 +1,1011 @@ +/* Copyright (c) 2014-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) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" + +#define MMSS_DSI_PHY_PLL_SYS_CLK_CTRL 0x0000 +#define MMSS_DSI_PHY_PLL_PLL_VCOTAIL_EN 0x0004 +#define MMSS_DSI_PHY_PLL_CMN_MODE 0x0008 +#define MMSS_DSI_PHY_PLL_IE_TRIM 0x000C +#define MMSS_DSI_PHY_PLL_IP_TRIM 0x0010 + +#define MMSS_DSI_PHY_PLL_PLL_PHSEL_CONTROL 0x0018 +#define MMSS_DSI_PHY_PLL_IPTAT_TRIM_VCCA_TX_SEL 0x001C +#define MMSS_DSI_PHY_PLL_PLL_PHSEL_DC 0x0020 +#define MMSS_DSI_PHY_PLL_PLL_IP_SETI 0x0024 +#define MMSS_DSI_PHY_PLL_CORE_CLK_IN_SYNC_SEL 0x0028 + +#define MMSS_DSI_PHY_PLL_BIAS_EN_CLKBUFLR_EN 0x0030 +#define MMSS_DSI_PHY_PLL_PLL_CP_SETI 0x0034 +#define MMSS_DSI_PHY_PLL_PLL_IP_SETP 0x0038 +#define MMSS_DSI_PHY_PLL_PLL_CP_SETP 0x003C +#define MMSS_DSI_PHY_PLL_ATB_SEL1 0x0040 +#define MMSS_DSI_PHY_PLL_ATB_SEL2 0x0044 +#define MMSS_DSI_PHY_PLL_SYSCLK_EN_SEL_TXBAND 0x0048 +#define MMSS_DSI_PHY_PLL_RESETSM_CNTRL 0x004C +#define MMSS_DSI_PHY_PLL_RESETSM_CNTRL2 0x0050 +#define MMSS_DSI_PHY_PLL_RESETSM_CNTRL3 0x0054 +#define MMSS_DSI_PHY_PLL_RESETSM_PLL_CAL_COUNT1 0x0058 +#define MMSS_DSI_PHY_PLL_RESETSM_PLL_CAL_COUNT2 0x005C +#define MMSS_DSI_PHY_PLL_DIV_REF1 0x0060 +#define MMSS_DSI_PHY_PLL_DIV_REF2 0x0064 +#define MMSS_DSI_PHY_PLL_KVCO_COUNT1 0x0068 +#define MMSS_DSI_PHY_PLL_KVCO_COUNT2 0x006C +#define MMSS_DSI_PHY_PLL_KVCO_CAL_CNTRL 0x0070 +#define MMSS_DSI_PHY_PLL_KVCO_CODE 0x0074 +#define MMSS_DSI_PHY_PLL_VREF_CFG1 0x0078 +#define MMSS_DSI_PHY_PLL_VREF_CFG2 0x007C +#define MMSS_DSI_PHY_PLL_VREF_CFG3 0x0080 +#define MMSS_DSI_PHY_PLL_VREF_CFG4 0x0084 +#define MMSS_DSI_PHY_PLL_VREF_CFG5 0x0088 +#define MMSS_DSI_PHY_PLL_VREF_CFG6 0x008C +#define MMSS_DSI_PHY_PLL_PLLLOCK_CMP1 0x0090 +#define MMSS_DSI_PHY_PLL_PLLLOCK_CMP2 0x0094 +#define MMSS_DSI_PHY_PLL_PLLLOCK_CMP3 0x0098 + +#define MMSS_DSI_PHY_PLL_BGTC 0x00A0 +#define MMSS_DSI_PHY_PLL_PLL_TEST_UPDN 0x00A4 +#define MMSS_DSI_PHY_PLL_PLL_VCO_TUNE 0x00A8 +#define MMSS_DSI_PHY_PLL_DEC_START1 0x00AC +#define MMSS_DSI_PHY_PLL_PLL_AMP_OS 0x00B0 +#define MMSS_DSI_PHY_PLL_SSC_EN_CENTER 0x00B4 +#define MMSS_DSI_PHY_PLL_SSC_ADJ_PER1 0x00B8 +#define MMSS_DSI_PHY_PLL_SSC_ADJ_PER2 0x00BC +#define MMSS_DSI_PHY_PLL_SSC_PER1 0x00C0 +#define MMSS_DSI_PHY_PLL_SSC_PER2 0x00C4 +#define MMSS_DSI_PHY_PLL_SSC_STEP_SIZE1 0x00C8 +#define MMSS_DSI_PHY_PLL_SSC_STEP_SIZE2 0x00CC +#define MMSS_DSI_PHY_PLL_RES_CODE_UP 0x00D0 +#define MMSS_DSI_PHY_PLL_RES_CODE_DN 0x00D4 +#define MMSS_DSI_PHY_PLL_RES_CODE_UP_OFFSET 0x00D8 +#define MMSS_DSI_PHY_PLL_RES_CODE_DN_OFFSET 0x00DC +#define MMSS_DSI_PHY_PLL_RES_CODE_START_SEG1 0x00E0 +#define MMSS_DSI_PHY_PLL_RES_CODE_START_SEG2 0x00E4 +#define MMSS_DSI_PHY_PLL_RES_CODE_CAL_CSR 0x00E8 +#define MMSS_DSI_PHY_PLL_RES_CODE 0x00EC +#define MMSS_DSI_PHY_PLL_RES_TRIM_CONTROL 0x00F0 +#define MMSS_DSI_PHY_PLL_RES_TRIM_CONTROL2 0x00F4 +#define MMSS_DSI_PHY_PLL_RES_TRIM_EN_VCOCALDONE 0x00F8 +#define MMSS_DSI_PHY_PLL_FAUX_EN 0x00FC + +#define MMSS_DSI_PHY_PLL_DIV_FRAC_START1 0x0100 +#define MMSS_DSI_PHY_PLL_DIV_FRAC_START2 0x0104 +#define MMSS_DSI_PHY_PLL_DIV_FRAC_START3 0x0108 +#define MMSS_DSI_PHY_PLL_DEC_START2 0x010C +#define MMSS_DSI_PHY_PLL_PLL_RXTXEPCLK_EN 0x0110 +#define MMSS_DSI_PHY_PLL_PLL_CRCTRL 0x0114 +#define MMSS_DSI_PHY_PLL_LOW_POWER_RO_CONTROL 0x013C +#define MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL 0x0140 +#define MMSS_DSI_PHY_PLL_HR_OCLK2_DIVIDER 0x0144 +#define MMSS_DSI_PHY_PLL_HR_OCLK3_DIVIDER 0x0148 +#define MMSS_DSI_PHY_PLL_PLL_VCO_HIGH 0x014C +#define MMSS_DSI_PHY_PLL_RESET_SM 0x0150 +#define MMSS_DSI_PHY_PLL_MUXVAL 0x0154 +#define MMSS_DSI_PHY_PLL_CORE_RES_CODE_DN 0x0158 +#define MMSS_DSI_PHY_PLL_CORE_RES_CODE_UP 0x015C +#define MMSS_DSI_PHY_PLL_CORE_VCO_TUNE 0x0160 +#define MMSS_DSI_PHY_PLL_CORE_VCO_TAIL 0x0164 +#define MMSS_DSI_PHY_PLL_CORE_KVCO_CODE 0x0168 + +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL0 0x014 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL1 0x018 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL2 0x01C +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL3 0x020 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL4 0x024 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL5 0x028 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL6 0x02C +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL7 0x030 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL8 0x034 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL9 0x038 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL10 0x03C +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL11 0x040 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL12 0x044 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL13 0x048 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL14 0x04C +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL15 0x050 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL16 0x054 +#define MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL17 0x058 + +#define DSI_PLL_POLL_DELAY_US 1000 +#define DSI_PLL_POLL_TIMEOUT_US 15000 + +int set_mdss_byte_mux_sel(struct mux_clk *clk, int sel) +{ + return 0; +} + +int get_mdss_byte_mux_sel(struct mux_clk *clk) +{ + return 0; +} + +int set_mdss_pixel_mux_sel(struct mux_clk *clk, int sel) +{ + return 0; +} + +int get_mdss_pixel_mux_sel(struct mux_clk *clk) +{ + return 0; +} + +static void pll_20nm_cache_trim_codes(struct mdss_pll_resources *dsi_pll_res) +{ + int rc; + + if (dsi_pll_res->reg_upd) + return; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return; + } + + dsi_pll_res->cache_pll_trim_codes[0] = + MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_CORE_KVCO_CODE); + dsi_pll_res->cache_pll_trim_codes[1] = + MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_CORE_VCO_TUNE); + + pr_debug("core_kvco_code=0x%x core_vco_turn=0x%x\n", + dsi_pll_res->cache_pll_trim_codes[0], + dsi_pll_res->cache_pll_trim_codes[1]); + + mdss_pll_resource_enable(dsi_pll_res, false); + + dsi_pll_res->reg_upd = true; +} + +static void pll_20nm_override_trim_codes(struct mdss_pll_resources *dsi_pll_res) +{ + u32 reg_data; + void __iomem *pll_base = dsi_pll_res->pll_base; + + /* + * Override mux config for all cached trim codes from + * saved config except for VCO Tune + */ + reg_data = (dsi_pll_res->cache_pll_trim_codes[0] & 0x3f) | BIT(5); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_KVCO_CODE, reg_data); + + reg_data = (dsi_pll_res->cache_pll_trim_codes[1] & 0x7f) | BIT(7); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_VCO_TUNE, reg_data); +} + + +int set_bypass_lp_div_mux_sel(struct mux_clk *clk, int sel) +{ + struct mdss_pll_resources *dsi_pll_res = clk->priv; + int reg_data; + + pr_debug("bypass_lp_div mux set to %s mode\n", + sel ? "indirect" : "direct"); + + pr_debug("POST_DIVIDER_CONTROL = 0x%x\n", + MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL)); + + reg_data = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL); + reg_data |= BIT(7); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + reg_data | (sel << 5)); + pr_debug("POST_DIVIDER_CONTROL = 0x%x\n", + MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL)); + + return 0; +} + +int set_shadow_bypass_lp_div_mux_sel(struct mux_clk *clk, int sel) +{ + struct mdss_pll_resources *dsi_pll_res = clk->priv; + int reg_data, rem; + + if (!dsi_pll_res->resource_enable) { + pr_err("PLL resources disabled. Dynamic fps invalid\n"); + return -EINVAL; + } + + reg_data = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL); + reg_data |= BIT(7); + + pr_debug("%d: reg_data = %x\n", __LINE__, reg_data); + + /* Repeat POST DIVIDER 2 times (4 writes)*/ + for (rem = 0; rem < 2; rem++) + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL16 + (4 * rem), + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + (reg_data | (sel << 5)), (reg_data | (sel << 5))); + + return 0; +} + +int get_bypass_lp_div_mux_sel(struct mux_clk *clk) +{ + int mux_mode, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + mux_mode = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL) & BIT(5); + + pr_debug("bypass_lp_div mux mode = %s", + mux_mode ? "indirect" : "direct"); + mdss_pll_resource_enable(dsi_pll_res, false); + + return !!mux_mode; +} + +int ndiv_set_div(struct div_clk *clk, int div) +{ + int rc, reg_data; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + reg_data = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + reg_data | div); + + pr_debug("POST_DIVIDER_CONTROL = 0x%x\n", + MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL)); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int shadow_ndiv_set_div(struct div_clk *clk, int div) +{ + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (!dsi_pll_res->resource_enable) { + pr_err("PLL resources disabled. Dynamic fps invalid\n"); + return -EINVAL; + } + + pr_debug("%d div=%i\n", __LINE__, div); + + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL14, + MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + 0x07, (0xB | div)); + + return 0; +} + +int ndiv_get_div(struct div_clk *clk) +{ + int div = 0, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(clk->priv, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + div = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL) & 0x0F; + + mdss_pll_resource_enable(dsi_pll_res, false); + + return div; +} + +int fixed_hr_oclk2_set_div(struct div_clk *clk, int div) +{ + int rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_HR_OCLK2_DIVIDER, + (div - 1)); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int shadow_fixed_hr_oclk2_set_div(struct div_clk *clk, int div) +{ + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (!dsi_pll_res->resource_enable) { + pr_err("PLL resources disabled. Dynamic fps invalid\n"); + return -EINVAL; + } + pr_debug("%d div = %d\n", __LINE__, div); + + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL5, + MMSS_DSI_PHY_PLL_HR_OCLK2_DIVIDER, + MMSS_DSI_PHY_PLL_HR_OCLK2_DIVIDER, + (div - 1), (div - 1)); + + return 0; +} + +int fixed_hr_oclk2_get_div(struct div_clk *clk) +{ + int div = 0, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + div = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_HR_OCLK2_DIVIDER); + + mdss_pll_resource_enable(dsi_pll_res, false); + return div + 1; +} + +int hr_oclk3_set_div(struct div_clk *clk, int div) +{ + int rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + pr_debug("%d div = %d\n", __LINE__, div); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_HR_OCLK3_DIVIDER, + (div - 1)); + pr_debug("%s: HR_OCLK3_DIVIDER = 0x%x\n", __func__, + MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_HR_OCLK3_DIVIDER)); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int shadow_hr_oclk3_set_div(struct div_clk *clk, int div) +{ + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (!dsi_pll_res->resource_enable) { + pr_err("PLL resources disabled. Dynamic fps invalid\n"); + return -EINVAL; + } + + pr_debug("%d div = %d\n", __LINE__, div); + + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL6, + MMSS_DSI_PHY_PLL_HR_OCLK3_DIVIDER, + MMSS_DSI_PHY_PLL_HR_OCLK3_DIVIDER, + (div - 1), (div - 1)); + + return 0; +} + +int hr_oclk3_get_div(struct div_clk *clk) +{ + int div = 0, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + div = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_HR_OCLK3_DIVIDER); + + mdss_pll_resource_enable(dsi_pll_res, false); + return div + 1; +} + +static bool pll_20nm_is_pll_locked(struct mdss_pll_resources *dsi_pll_res) +{ + u32 status; + bool pll_locked; + + /* poll for PLL ready status */ + if (readl_poll_timeout_atomic((dsi_pll_res->pll_base + + MMSS_DSI_PHY_PLL_RESET_SM), + status, + ((status & BIT(5)) > 0), + DSI_PLL_POLL_DELAY_US, + DSI_PLL_POLL_TIMEOUT_US)) { + pr_debug("DSI PLL status=%x failed to Lock\n", status); + pll_locked = false; + } else if (readl_poll_timeout_atomic((dsi_pll_res->pll_base + + MMSS_DSI_PHY_PLL_RESET_SM), + status, + ((status & BIT(6)) > 0), + DSI_PLL_POLL_DELAY_US, + DSI_PLL_POLL_TIMEOUT_US)) { + pr_debug("DSI PLL status=%x PLl not ready\n", status); + pll_locked = false; + } else { + pll_locked = true; + } + + return pll_locked; +} + +void __dsi_pll_disable(void __iomem *pll_base) +{ + if (!pll_base) { + pr_err("Invalid pll base\n"); + return; + } + pr_debug("Disabling PHY PLL for PLL_BASE=%p\n", pll_base); + + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_VCOTAIL_EN, 0x02); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, 0x06); +} + +static void pll_20nm_config_powerdown(void __iomem *pll_base) +{ + if (!pll_base) { + pr_err("Invalid pll base.\n"); + return; + } + + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_SYS_CLK_CTRL, 0x00); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_CMN_MODE, 0x01); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_VCOTAIL_EN, 0x82); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_BIAS_EN_CLKBUFLR_EN, 0x02); +} + +static int dsi_pll_enable(struct clk *c) +{ + int i, rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + /* Try all enable sequences until one succeeds */ + for (i = 0; i < vco->pll_en_seq_cnt; i++) { + rc = vco->pll_enable_seqs[i](dsi_pll_res); + pr_debug("DSI PLL %s after sequence #%d\n", + rc ? "unlocked" : "locked", i + 1); + if (!rc) + break; + } + /* Disable PLL1 to avoid current leakage while toggling MDSS GDSC */ + if (dsi_pll_res->pll_1_base) + pll_20nm_config_powerdown(dsi_pll_res->pll_1_base); + + if (rc) { + mdss_pll_resource_enable(dsi_pll_res, false); + pr_err("DSI PLL failed to lock\n"); + } + dsi_pll_res->pll_on = true; + + return rc; +} + +static void dsi_pll_disable(struct clk *c) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res->pll_on && + mdss_pll_resource_enable(dsi_pll_res, true)) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return; + } + + dsi_pll_res->handoff_resources = false; + + __dsi_pll_disable(dsi_pll_res->pll_base); + + /* Disable PLL1 to avoid current leakage while toggling MDSS GDSC */ + if (dsi_pll_res->pll_1_base) + pll_20nm_config_powerdown(dsi_pll_res->pll_1_base); + + pll_20nm_config_powerdown(dsi_pll_res->pll_base); + + mdss_pll_resource_enable(dsi_pll_res, false); + + dsi_pll_res->pll_on = false; + + pr_debug("DSI PLL Disabled\n"); +} + +static void pll_20nm_config_common_block_1(void __iomem *pll_base) +{ + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_VCOTAIL_EN, 0x82); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_BIAS_EN_CLKBUFLR_EN, 0x2a); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_BIAS_EN_CLKBUFLR_EN, 0x2b); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, 0x02); +} + +static void pll_20nm_config_common_block_2(void __iomem *pll_base) +{ + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_SYS_CLK_CTRL, 0x40); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_IE_TRIM, 0x0F); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_IP_TRIM, 0x0F); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_PHSEL_CONTROL, 0x08); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_IPTAT_TRIM_VCCA_TX_SEL, 0x0E); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_BKG_KVCO_CAL_EN, 0x08); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_SYSCLK_EN_SEL_TXBAND, 0x4A); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DIV_REF1, 0x00); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DIV_REF2, 0x01); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_CNTRL, 0x07); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_KVCO_CAL_CNTRL, 0x1f); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_KVCO_COUNT1, 0x8A); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_VREF_CFG3, 0x10); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_SSC_EN_CENTER, 0x00); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_FAUX_EN, 0x0C); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_RXTXEPCLK_EN, 0x0a); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_LOW_POWER_RO_CONTROL, 0x0f); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_CMN_MODE, 0x00); +} + +static void pll_20nm_config_loop_bw(void __iomem *pll_base) +{ + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_IP_SETI, 0x03); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_CP_SETI, 0x3F); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_IP_SETP, 0x03); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_CP_SETP, 0x1F); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_CRCTRL, 0x77); +} + +static void pll_20nm_vco_rate_calc(struct mdss_pll_vco_calc *vco_calc, + s64 vco_clk_rate, s64 ref_clk_rate) +{ + s64 multiplier = (1 << 20); + s64 duration = 1024, pll_comp_val; + s64 dec_start_multiple, dec_start; + s32 div_frac_start; + s64 dec_start1, dec_start2; + s32 div_frac_start1, div_frac_start2, div_frac_start3; + s64 pll_plllock_cmp1, pll_plllock_cmp2, pll_plllock_cmp3; + + memset(vco_calc, 0, sizeof(*vco_calc)); + pr_debug("vco_clk_rate=%lld ref_clk_rate=%lld\n", vco_clk_rate, + ref_clk_rate); + + dec_start_multiple = div_s64(vco_clk_rate * multiplier, + 2 * ref_clk_rate); + div_s64_rem(dec_start_multiple, + multiplier, &div_frac_start); + + dec_start = div_s64(dec_start_multiple, multiplier); + dec_start1 = (dec_start & 0x7f) | BIT(7); + dec_start2 = ((dec_start & 0x80) >> 7) | BIT(1); + div_frac_start1 = (div_frac_start & 0x7f) | BIT(7); + div_frac_start2 = ((div_frac_start >> 7) & 0x7f) | BIT(7); + div_frac_start3 = ((div_frac_start >> 14) & 0x3f) | BIT(6); + pll_comp_val = (div_s64(dec_start_multiple * 2 * duration, + 10 * multiplier)) - 1; + pll_plllock_cmp1 = pll_comp_val & 0xff; + pll_plllock_cmp2 = (pll_comp_val >> 8) & 0xff; + pll_plllock_cmp3 = (pll_comp_val >> 16) & 0xff; + + pr_debug("dec_start_multiple = 0x%llx\n", dec_start_multiple); + pr_debug("dec_start = 0x%llx, div_frac_start = 0x%x\n", + dec_start, div_frac_start); + pr_debug("dec_start1 = 0x%llx, dec_start2 = 0x%llx\n", + dec_start1, dec_start2); + pr_debug("div_frac_start1 = 0x%x, div_frac_start2 = 0x%x\n", + div_frac_start1, div_frac_start2); + pr_debug("div_frac_start3 = 0x%x\n", div_frac_start3); + pr_debug("pll_comp_val = 0x%llx\n", pll_comp_val); + pr_debug("pll_plllock_cmp1 = 0x%llx, pll_plllock_cmp2 =%llx\n", + pll_plllock_cmp1, pll_plllock_cmp2); + pr_debug("pll_plllock_cmp3 = 0x%llx\n", pll_plllock_cmp3); + + /* Assign to vco struct */ + vco_calc->div_frac_start1 = div_frac_start1; + vco_calc->div_frac_start2 = div_frac_start2; + vco_calc->div_frac_start3 = div_frac_start3; + vco_calc->dec_start1 = dec_start1; + vco_calc->dec_start2 = dec_start2; + vco_calc->pll_plllock_cmp1 = pll_plllock_cmp1; + vco_calc->pll_plllock_cmp2 = pll_plllock_cmp2; + vco_calc->pll_plllock_cmp3 = pll_plllock_cmp3; +} + +static void pll_20nm_config_vco_rate(void __iomem *pll_base, + struct mdss_pll_vco_calc *vco_calc) +{ + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DIV_FRAC_START1, + vco_calc->div_frac_start1); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DIV_FRAC_START2, + vco_calc->div_frac_start2); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DIV_FRAC_START3, + vco_calc->div_frac_start3); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DEC_START1, + vco_calc->dec_start1); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_DEC_START2, + vco_calc->dec_start2); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLLLOCK_CMP1, + vco_calc->pll_plllock_cmp1); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLLLOCK_CMP2, + vco_calc->pll_plllock_cmp2); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLLLOCK_CMP3, + vco_calc->pll_plllock_cmp3); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLLLOCK_CMP_EN, 0x01); +} + +int pll_20nm_vco_set_rate(struct dsi_pll_vco_clk *vco, unsigned long rate) +{ + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + dsi_pll_res->vco_current_rate = rate; + dsi_pll_res->vco_ref_clk_rate = vco->ref_clk_rate; + + return 0; +} + +int shadow_pll_20nm_vco_set_rate(struct dsi_pll_vco_clk *vco, + unsigned long rate) +{ + struct mdss_pll_resources *dsi_pll_res = vco->priv; + struct mdss_pll_vco_calc vco_calc; + s64 vco_clk_rate = rate; + u32 rem; + + if (!dsi_pll_res->resource_enable) { + pr_err("PLL resources disabled. Dynamic fps invalid\n"); + return -EINVAL; + } + + pr_debug("req vco set rate: %lld\n", vco_clk_rate); + + pll_20nm_override_trim_codes(dsi_pll_res); + + /* div fraction, start and comp calculations */ + pll_20nm_vco_rate_calc(&vco_calc, vco_clk_rate, + dsi_pll_res->vco_ref_clk_rate); + + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL0, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + MMSS_DSI_PHY_PLL_PLLLOCK_CMP_EN, + 0xB1, 0); + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL1, + MMSS_DSI_PHY_PLL_PLLLOCK_CMP1, + MMSS_DSI_PHY_PLL_PLLLOCK_CMP2, + vco_calc.pll_plllock_cmp1, vco_calc.pll_plllock_cmp2); + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL2, + MMSS_DSI_PHY_PLL_PLLLOCK_CMP3, + MMSS_DSI_PHY_PLL_DEC_START1, + vco_calc.pll_plllock_cmp3, vco_calc.dec_start1); + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL3, + MMSS_DSI_PHY_PLL_DEC_START2, + MMSS_DSI_PHY_PLL_DIV_FRAC_START1, + vco_calc.dec_start2, vco_calc.div_frac_start1); + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL4, + MMSS_DSI_PHY_PLL_DIV_FRAC_START2, + MMSS_DSI_PHY_PLL_DIV_FRAC_START3, + vco_calc.div_frac_start2, vco_calc.div_frac_start3); + /* Method 2 - Auto PLL calibration */ + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL7, + MMSS_DSI_PHY_PLL_PLL_VCO_TUNE, + MMSS_DSI_PHY_PLL_PLLLOCK_CMP_EN, + 0, 0x0D); + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL8, + MMSS_DSI_PHY_PLL_POST_DIVIDER_CONTROL, + MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, + 0xF0, 0x07); + + /* + * RESETSM_CTRL3 has to be set for 12 times (6 reg writes), + * Each register setting write 2 times, running in loop for 5 + * times (5 reg writes) and other two iterations are taken + * care (one above and other in shadow_bypass + */ + for (rem = 0; rem < 5; rem++) { + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL9 + (4 * rem), + MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, + MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, + 0x07, 0x07); + } + + MDSS_DYN_PLL_REG_W(dsi_pll_res->dyn_pll_base, + MMSS_DSI_DYNAMIC_REFRESH_PLL_CTRL15, + MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, + MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, + 0x03, 0x03); + + /* memory barrier */ + wmb(); + return 0; +} + +unsigned long pll_20nm_vco_get_rate(struct clk *c) +{ + u64 vco_rate, multiplier = (1 << 20); + s32 div_frac_start; + u32 dec_start; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + u64 ref_clk = vco->ref_clk_rate; + int rc; + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + dec_start = (MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_DEC_START2) & BIT(0)) << 7; + dec_start |= (MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_DEC_START1) & 0x7f); + pr_debug("dec_start = 0x%x\n", dec_start); + + div_frac_start = (MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_DIV_FRAC_START3) & 0x3f) << 14; + div_frac_start |= (MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_DIV_FRAC_START2) & 0x7f) << 7; + div_frac_start |= MDSS_PLL_REG_R(dsi_pll_res->pll_base, + MMSS_DSI_PHY_PLL_DIV_FRAC_START1) & 0x7f; + pr_debug("div_frac_start = 0x%x\n", div_frac_start); + + vco_rate = ref_clk * 2 * dec_start; + vco_rate += ((ref_clk * 2 * div_frac_start) / multiplier); + pr_debug("returning vco rate = %lu\n", (unsigned long)vco_rate); + + mdss_pll_resource_enable(dsi_pll_res, false); + + return (unsigned long)vco_rate; +} +long pll_20nm_vco_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + return rrate; +} + +enum handoff pll_20nm_vco_handoff(struct clk *c) +{ + int rc; + enum handoff ret = HANDOFF_DISABLED_CLK; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return HANDOFF_DISABLED_CLK; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return ret; + } + + if (pll_20nm_is_pll_locked(dsi_pll_res)) { + dsi_pll_res->handoff_resources = true; + dsi_pll_res->pll_on = true; + c->rate = pll_20nm_vco_get_rate(c); + ret = HANDOFF_ENABLED_CLK; + dsi_pll_res->vco_locking_rate = c->rate; + dsi_pll_res->is_init_locked = true; + pll_20nm_cache_trim_codes(dsi_pll_res); + pr_debug("handoff vco_locking_rate=%llu\n", + dsi_pll_res->vco_locking_rate); + } else { + mdss_pll_resource_enable(dsi_pll_res, false); + dsi_pll_res->vco_locking_rate = 0; + dsi_pll_res->is_init_locked = false; + } + + return ret; +} + +int pll_20nm_vco_prepare(struct clk *c) +{ + int rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res) { + pr_err("Dsi pll resources are not available\n"); + return -EINVAL; + } + + if ((dsi_pll_res->vco_cached_rate != 0) + && (dsi_pll_res->vco_cached_rate == c->rate)) { + rc = c->ops->set_rate(c, dsi_pll_res->vco_cached_rate); + if (rc) { + pr_err("vco_set_rate failed. rc=%d\n", rc); + goto error; + } + } + + rc = dsi_pll_enable(c); + +error: + return rc; +} + +void pll_20nm_vco_unprepare(struct clk *c) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res) { + pr_err("Dsi pll resources are not available\n"); + return; + } + + dsi_pll_res->vco_cached_rate = c->rate; + dsi_pll_disable(c); +} + +static void pll_20nm_config_resetsm(void __iomem *pll_base) +{ + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL, 0x24); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL2, 0x07); +} + +static void pll_20nm_config_vco_start(void __iomem *pll_base) +{ + + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_VCOTAIL_EN, 0x03); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, 0x02); + udelay(10); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL3, 0x03); +} + +static void pll_20nm_config_bypass_cal(void __iomem *pll_base) +{ + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_RESETSM_CNTRL, 0xac); + MDSS_PLL_REG_W(pll_base, MMSS_DSI_PHY_PLL_PLL_BKG_KVCO_CAL_EN, 0x28); +} + +static int pll_20nm_vco_relock(struct mdss_pll_resources *dsi_pll_res) +{ + int rc = 0; + + pll_20nm_override_trim_codes(dsi_pll_res); + pll_20nm_config_bypass_cal(dsi_pll_res->pll_base); + pll_20nm_config_vco_start(dsi_pll_res->pll_base); + + if (!pll_20nm_is_pll_locked(dsi_pll_res)) { + pr_err("DSI PLL re-lock failed\n"); + rc = -EINVAL; + } + + return rc; +} + +static int pll_20nm_vco_init_lock(struct mdss_pll_resources *dsi_pll_res) +{ + int rc = 0; + + pll_20nm_config_resetsm(dsi_pll_res->pll_base); + pll_20nm_config_vco_start(dsi_pll_res->pll_base); + + if (!pll_20nm_is_pll_locked(dsi_pll_res)) { + pr_err("DSI PLL init lock failed\n"); + rc = -EINVAL; + goto init_lock_err; + } + + pll_20nm_cache_trim_codes(dsi_pll_res); + +init_lock_err: + return rc; +} + +int pll_20nm_vco_enable_seq(struct mdss_pll_resources *dsi_pll_res) +{ + int rc = 0; + struct mdss_pll_vco_calc vco_calc; + + if (!dsi_pll_res) { + pr_err("Invalid PLL resources\n"); + return -EINVAL; + } + + pll_20nm_config_common_block_1(dsi_pll_res->pll_1_base); + pll_20nm_config_common_block_1(dsi_pll_res->pll_base); + pll_20nm_config_common_block_2(dsi_pll_res->pll_base); + pll_20nm_config_loop_bw(dsi_pll_res->pll_base); + + pll_20nm_vco_rate_calc(&vco_calc, dsi_pll_res->vco_current_rate, + dsi_pll_res->vco_ref_clk_rate); + pll_20nm_config_vco_rate(dsi_pll_res->pll_base, &vco_calc); + + pr_debug("init lock=%d prev vco_rate=%llu, new vco_rate=%llu\n", + dsi_pll_res->is_init_locked, dsi_pll_res->vco_locking_rate, + dsi_pll_res->vco_current_rate); + /* + * Run auto-lock sequence if it is either bootup initial + * locking or when the vco rate is changed. Otherwise, just + * use stored codes and bypass caliberation. + */ + if (!dsi_pll_res->is_init_locked || (dsi_pll_res->vco_locking_rate != + dsi_pll_res->vco_current_rate)) { + rc = pll_20nm_vco_init_lock(dsi_pll_res); + dsi_pll_res->is_init_locked = (rc) ? false : true; + } else { + rc = pll_20nm_vco_relock(dsi_pll_res); + } + + dsi_pll_res->vco_locking_rate = (rc) ? 0 : + dsi_pll_res->vco_current_rate; + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-10nm.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-10nm.c new file mode 100644 index 0000000000000000000000000000000000000000..1cd6c9cb8e17fd360061fc46dff674c2d797a6fe --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-10nm.c @@ -0,0 +1,1519 @@ +/* + * 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include "mdss-dsi-pll.h" +#include "mdss-pll.h" +#include + +#define VCO_DELAY_USEC 1 + +#define MHZ_250 250000000UL +#define MHZ_500 500000000UL +#define MHZ_1000 1000000000UL +#define MHZ_1100 1100000000UL +#define MHZ_1900 1900000000UL +#define MHZ_3000 3000000000UL + +/* Register Offsets from PLL base address */ +#define PLL_ANALOG_CONTROLS_ONE 0x000 +#define PLL_ANALOG_CONTROLS_TWO 0x004 +#define PLL_ANALOG_CONTROLS_THREE 0x010 +#define PLL_DSM_DIVIDER 0x01c +#define PLL_FEEDBACK_DIVIDER 0x020 +#define PLL_SYSTEM_MUXES 0x024 +#define PLL_CMODE 0x02c +#define PLL_CALIBRATION_SETTINGS 0x030 +#define PLL_BAND_SEL_CAL_SETTINGS_THREE 0x054 +#define PLL_FREQ_DETECT_SETTINGS_ONE 0x064 +#define PLL_PFILT 0x07c +#define PLL_IFILT 0x080 +#define PLL_OUTDIV 0x094 +#define PLL_CORE_OVERRIDE 0x0a4 +#define PLL_CORE_INPUT_OVERRIDE 0x0a8 +#define PLL_PLL_DIGITAL_TIMERS_TWO 0x0b4 +#define PLL_DECIMAL_DIV_START_1 0x0cc +#define PLL_FRAC_DIV_START_LOW_1 0x0d0 +#define PLL_FRAC_DIV_START_MID_1 0x0d4 +#define PLL_FRAC_DIV_START_HIGH_1 0x0d8 +#define PLL_SSC_STEPSIZE_LOW_1 0x10c +#define PLL_SSC_STEPSIZE_HIGH_1 0x110 +#define PLL_SSC_DIV_PER_LOW_1 0x114 +#define PLL_SSC_DIV_PER_HIGH_1 0x118 +#define PLL_SSC_DIV_ADJPER_LOW_1 0x11c +#define PLL_SSC_DIV_ADJPER_HIGH_1 0x120 +#define PLL_SSC_CONTROL 0x13c +#define PLL_PLL_OUTDIV_RATE 0x140 +#define PLL_PLL_LOCKDET_RATE_1 0x144 +#define PLL_PLL_PROP_GAIN_RATE_1 0x14c +#define PLL_PLL_BAND_SET_RATE_1 0x154 +#define PLL_PLL_INT_GAIN_IFILT_BAND_1 0x15c +#define PLL_PLL_FL_INT_GAIN_PFILT_BAND_1 0x164 +#define PLL_PLL_LOCK_OVERRIDE 0x180 +#define PLL_PLL_LOCK_DELAY 0x184 +#define PLL_CLOCK_INVERTERS 0x18c +#define PLL_COMMON_STATUS_ONE 0x1a0 + +/* Register Offsets from PHY base address */ +#define PHY_CMN_CLK_CFG0 0x010 +#define PHY_CMN_CLK_CFG1 0x014 +#define PHY_CMN_RBUF_CTRL 0x01c +#define PHY_CMN_PLL_CNTRL 0x038 +#define PHY_CMN_CTRL_0 0x024 + +/* Bit definition of SSC control registers */ +#define SSC_CENTER BIT(0) +#define SSC_EN BIT(1) +#define SSC_FREQ_UPDATE BIT(2) +#define SSC_FREQ_UPDATE_MUX BIT(3) +#define SSC_UPDATE_SSC BIT(4) +#define SSC_UPDATE_SSC_MUX BIT(5) +#define SSC_START BIT(6) +#define SSC_START_MUX BIT(7) + +enum { + DSI_PLL_0, + DSI_PLL_1, + DSI_PLL_MAX +}; + +struct dsi_pll_regs { + u32 pll_prop_gain_rate; + u32 pll_lockdet_rate; + u32 decimal_div_start; + u32 frac_div_start_low; + u32 frac_div_start_mid; + u32 frac_div_start_high; + u32 pll_clock_inverters; + u32 ssc_stepsize_low; + u32 ssc_stepsize_high; + u32 ssc_div_per_low; + u32 ssc_div_per_high; + u32 ssc_adjper_low; + u32 ssc_adjper_high; + u32 ssc_control; +}; + +struct dsi_pll_config { + u32 ref_freq; + bool div_override; + u32 output_div; + bool ignore_frac; + bool disable_prescaler; + bool enable_ssc; + bool ssc_center; + u32 dec_bits; + u32 frac_bits; + u32 lock_timer; + u32 ssc_freq; + u32 ssc_offset; + u32 ssc_adj_per; + u32 thresh_cycles; + u32 refclk_cycles; +}; + +struct dsi_pll_10nm { + struct mdss_pll_resources *rsc; + struct dsi_pll_config pll_configuration; + struct dsi_pll_regs reg_setup; +}; + +static inline int pll_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + *val = MDSS_PLL_REG_R(rsc->pll_base, reg); + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static inline int pll_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + MDSS_PLL_REG_W(rsc->pll_base, reg, val); + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static inline int phy_reg_read(void *context, unsigned int reg, + unsigned int *val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + *val = MDSS_PLL_REG_R(rsc->phy_base, reg); + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static inline int phy_reg_write(void *context, unsigned int reg, + unsigned int val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + MDSS_PLL_REG_W(rsc->phy_base, reg, val); + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static inline int phy_reg_update_bits_sub(struct mdss_pll_resources *rsc, + unsigned int reg, unsigned int mask, unsigned int val) +{ + u32 reg_val; + int rc = 0; + + reg_val = MDSS_PLL_REG_R(rsc->phy_base, reg); + reg_val &= ~mask; + reg_val |= (val & mask); + MDSS_PLL_REG_W(rsc->phy_base, reg, reg_val); + + return rc; +} + +static inline int phy_reg_update_bits(void *context, unsigned int reg, + unsigned int mask, unsigned int val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + rc = phy_reg_update_bits_sub(rsc, reg, mask, val); + if (!rc && rsc->slave) + rc = phy_reg_update_bits_sub(rsc->slave, reg, mask, val); + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static inline int pclk_mux_read_sel(void *context, unsigned int reg, + unsigned int *val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + else + *val = (MDSS_PLL_REG_R(rsc->pll_base, reg) & 0x3); + + (void)mdss_pll_resource_enable(rsc, false); + return rc; +} + + +static inline int pclk_mux_write_sel_sub(struct mdss_pll_resources *rsc, + unsigned int reg, unsigned int val) +{ + u32 reg_val; + int rc = 0; + + reg_val = MDSS_PLL_REG_R(rsc->phy_base, reg); + reg_val &= ~0x03; + reg_val |= val; + + MDSS_PLL_REG_W(rsc->phy_base, reg, reg_val); + + return rc; +} + +static inline int pclk_mux_write_sel(void *context, unsigned int reg, + unsigned int val) +{ + int rc = 0; + struct mdss_pll_resources *rsc = context; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + rc = pclk_mux_write_sel_sub(rsc, reg, val); + if (!rc && rsc->slave) + rc = pclk_mux_write_sel_sub(rsc->slave, reg, val); + + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static struct mdss_pll_resources *pll_rsc_db[DSI_PLL_MAX]; +static struct dsi_pll_10nm plls[DSI_PLL_MAX]; + +static void dsi_pll_config_slave(struct mdss_pll_resources *rsc) +{ + u32 reg; + struct mdss_pll_resources *orsc = pll_rsc_db[DSI_PLL_1]; + + if (!rsc) + return; + + /* Only DSI PLL0 can act as a master */ + if (rsc->index != DSI_PLL_0) + return; + + /* default configuration: source is either internal or ref clock */ + rsc->slave = NULL; + + if (!orsc) { + pr_warn("slave PLL unavilable, assuming standalone config\n"); + return; + } + + /* check to see if the source of DSI1 PLL bitclk is set to external */ + reg = MDSS_PLL_REG_R(orsc->phy_base, PHY_CMN_CLK_CFG1); + reg &= (BIT(2) | BIT(3)); + if (reg == 0x04) + rsc->slave = pll_rsc_db[DSI_PLL_1]; /* external source */ + + pr_debug("Slave PLL %s\n", rsc->slave ? "configured" : "absent"); +} + +static void dsi_pll_setup_config(struct dsi_pll_10nm *pll, + struct mdss_pll_resources *rsc) +{ + struct dsi_pll_config *config = &pll->pll_configuration; + + config->ref_freq = 19200000; + config->output_div = 1; + config->dec_bits = 8; + config->frac_bits = 18; + config->lock_timer = 64; + config->ssc_freq = 31500; + config->ssc_offset = 5000; + config->ssc_adj_per = 2; + config->thresh_cycles = 32; + config->refclk_cycles = 256; + + config->div_override = false; + config->ignore_frac = false; + config->disable_prescaler = false; + config->enable_ssc = rsc->ssc_en; + config->ssc_center = rsc->ssc_center; + + if (config->enable_ssc) { + if (rsc->ssc_freq) + config->ssc_freq = rsc->ssc_freq; + if (rsc->ssc_ppm) + config->ssc_offset = rsc->ssc_ppm; + } + + dsi_pll_config_slave(rsc); +} + +static void dsi_pll_calc_dec_frac(struct dsi_pll_10nm *pll, + struct mdss_pll_resources *rsc) +{ + struct dsi_pll_config *config = &pll->pll_configuration; + struct dsi_pll_regs *regs = &pll->reg_setup; + u64 fref = rsc->vco_ref_clk_rate; + u64 pll_freq; + u64 divider; + u64 dec, dec_multiple; + u32 frac; + u64 multiplier; + + pll_freq = rsc->vco_current_rate; + + if (config->disable_prescaler) + divider = fref; + else + divider = fref * 2; + + multiplier = 1 << config->frac_bits; + dec_multiple = div_u64(pll_freq * multiplier, divider); + div_u64_rem(dec_multiple, multiplier, &frac); + + dec = div_u64(dec_multiple, multiplier); + + if (pll_freq <= MHZ_1900) + regs->pll_prop_gain_rate = 8; + else if (pll_freq <= MHZ_3000) + regs->pll_prop_gain_rate = 10; + else + regs->pll_prop_gain_rate = 12; + if (pll_freq < MHZ_1100) + regs->pll_clock_inverters = 8; + else + regs->pll_clock_inverters = 0; + + regs->pll_lockdet_rate = config->lock_timer; + regs->decimal_div_start = dec; + regs->frac_div_start_low = (frac & 0xff); + regs->frac_div_start_mid = (frac & 0xff00) >> 8; + regs->frac_div_start_high = (frac & 0x30000) >> 16; +} + +static void dsi_pll_calc_ssc(struct dsi_pll_10nm *pll, + struct mdss_pll_resources *rsc) +{ + struct dsi_pll_config *config = &pll->pll_configuration; + struct dsi_pll_regs *regs = &pll->reg_setup; + u32 ssc_per; + u32 ssc_mod; + u64 ssc_step_size; + u64 frac; + + if (!config->enable_ssc) { + pr_debug("SSC not enabled\n"); + return; + } + + ssc_per = DIV_ROUND_CLOSEST(config->ref_freq, config->ssc_freq) / 2 - 1; + ssc_mod = (ssc_per + 1) % (config->ssc_adj_per + 1); + ssc_per -= ssc_mod; + + frac = regs->frac_div_start_low | + (regs->frac_div_start_mid << 8) | + (regs->frac_div_start_high << 16); + ssc_step_size = regs->decimal_div_start; + ssc_step_size *= (1 << config->frac_bits); + ssc_step_size += frac; + ssc_step_size *= config->ssc_offset; + ssc_step_size *= (config->ssc_adj_per + 1); + ssc_step_size = div_u64(ssc_step_size, (ssc_per + 1)); + ssc_step_size = DIV_ROUND_CLOSEST_ULL(ssc_step_size, 1000000); + + regs->ssc_div_per_low = ssc_per & 0xFF; + regs->ssc_div_per_high = (ssc_per & 0xFF00) >> 8; + regs->ssc_stepsize_low = (u32)(ssc_step_size & 0xFF); + regs->ssc_stepsize_high = (u32)((ssc_step_size & 0xFF00) >> 8); + regs->ssc_adjper_low = config->ssc_adj_per & 0xFF; + regs->ssc_adjper_high = (config->ssc_adj_per & 0xFF00) >> 8; + + regs->ssc_control = config->ssc_center ? SSC_CENTER : 0; + + pr_debug("SCC: Dec:%d, frac:%llu, frac_bits:%d\n", + regs->decimal_div_start, frac, config->frac_bits); + pr_debug("SSC: div_per:0x%X, stepsize:0x%X, adjper:0x%X\n", + ssc_per, (u32)ssc_step_size, config->ssc_adj_per); +} + +static void dsi_pll_ssc_commit(struct dsi_pll_10nm *pll, + struct mdss_pll_resources *rsc) +{ + void __iomem *pll_base = rsc->pll_base; + struct dsi_pll_regs *regs = &pll->reg_setup; + + if (pll->pll_configuration.enable_ssc) { + pr_debug("SSC is enabled\n"); + + MDSS_PLL_REG_W(pll_base, PLL_SSC_STEPSIZE_LOW_1, + regs->ssc_stepsize_low); + MDSS_PLL_REG_W(pll_base, PLL_SSC_STEPSIZE_HIGH_1, + regs->ssc_stepsize_high); + MDSS_PLL_REG_W(pll_base, PLL_SSC_DIV_PER_LOW_1, + regs->ssc_div_per_low); + MDSS_PLL_REG_W(pll_base, PLL_SSC_DIV_PER_HIGH_1, + regs->ssc_div_per_high); + MDSS_PLL_REG_W(pll_base, PLL_SSC_DIV_ADJPER_LOW_1, + regs->ssc_adjper_low); + MDSS_PLL_REG_W(pll_base, PLL_SSC_DIV_ADJPER_HIGH_1, + regs->ssc_adjper_high); + MDSS_PLL_REG_W(pll_base, PLL_SSC_CONTROL, + SSC_EN | regs->ssc_control); + } +} + +static void dsi_pll_config_hzindep_reg(struct dsi_pll_10nm *pll, + struct mdss_pll_resources *rsc) +{ + void __iomem *pll_base = rsc->pll_base; + + MDSS_PLL_REG_W(pll_base, PLL_ANALOG_CONTROLS_ONE, 0x80); + MDSS_PLL_REG_W(pll_base, PLL_ANALOG_CONTROLS_TWO, 0x03); + MDSS_PLL_REG_W(pll_base, PLL_ANALOG_CONTROLS_THREE, 0x00); + MDSS_PLL_REG_W(pll_base, PLL_DSM_DIVIDER, 0x00); + MDSS_PLL_REG_W(pll_base, PLL_FEEDBACK_DIVIDER, 0x4e); + MDSS_PLL_REG_W(pll_base, PLL_CALIBRATION_SETTINGS, 0x40); + MDSS_PLL_REG_W(pll_base, PLL_BAND_SEL_CAL_SETTINGS_THREE, 0xba); + MDSS_PLL_REG_W(pll_base, PLL_FREQ_DETECT_SETTINGS_ONE, 0x0c); + MDSS_PLL_REG_W(pll_base, PLL_OUTDIV, 0x00); + MDSS_PLL_REG_W(pll_base, PLL_CORE_OVERRIDE, 0x00); + MDSS_PLL_REG_W(pll_base, PLL_PLL_DIGITAL_TIMERS_TWO, 0x08); + MDSS_PLL_REG_W(pll_base, PLL_PLL_PROP_GAIN_RATE_1, 0x08); + MDSS_PLL_REG_W(pll_base, PLL_PLL_BAND_SET_RATE_1, 0xc0); + MDSS_PLL_REG_W(pll_base, PLL_PLL_INT_GAIN_IFILT_BAND_1, 0xfa); + MDSS_PLL_REG_W(pll_base, PLL_PLL_FL_INT_GAIN_PFILT_BAND_1, 0x4c); + MDSS_PLL_REG_W(pll_base, PLL_PLL_LOCK_OVERRIDE, 0x80); + MDSS_PLL_REG_W(pll_base, PLL_PFILT, 0x29); + MDSS_PLL_REG_W(pll_base, PLL_IFILT, 0x3f); +} + +static void dsi_pll_commit(struct dsi_pll_10nm *pll, + struct mdss_pll_resources *rsc) +{ + void __iomem *pll_base = rsc->pll_base; + struct dsi_pll_regs *reg = &pll->reg_setup; + + MDSS_PLL_REG_W(pll_base, PLL_CORE_INPUT_OVERRIDE, 0x12); + MDSS_PLL_REG_W(pll_base, PLL_DECIMAL_DIV_START_1, + reg->decimal_div_start); + MDSS_PLL_REG_W(pll_base, PLL_FRAC_DIV_START_LOW_1, + reg->frac_div_start_low); + MDSS_PLL_REG_W(pll_base, PLL_FRAC_DIV_START_MID_1, + reg->frac_div_start_mid); + MDSS_PLL_REG_W(pll_base, PLL_FRAC_DIV_START_HIGH_1, + reg->frac_div_start_high); + MDSS_PLL_REG_W(pll_base, PLL_PLL_LOCKDET_RATE_1, 0x40); + MDSS_PLL_REG_W(pll_base, PLL_PLL_LOCK_DELAY, 0x06); + MDSS_PLL_REG_W(pll_base, PLL_CMODE, 0x10); + MDSS_PLL_REG_W(pll_base, PLL_CLOCK_INVERTERS, reg->pll_clock_inverters); + +} + +static int vco_10nm_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *rsc = vco->priv; + struct dsi_pll_10nm *pll; + + if (!rsc) { + pr_err("pll resource not found\n"); + return -EINVAL; + } + + if (rsc->pll_on) + return 0; + + pll = rsc->priv; + if (!pll) { + pr_err("pll configuration not found\n"); + return -EINVAL; + } + + pr_debug("ndx=%d, rate=%lu\n", rsc->index, rate); + + rsc->vco_current_rate = rate; + rsc->vco_ref_clk_rate = vco->ref_clk_rate; + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("failed to enable mdss dsi pll(%d), rc=%d\n", + rsc->index, rc); + return rc; + } + + dsi_pll_setup_config(pll, rsc); + + dsi_pll_calc_dec_frac(pll, rsc); + + dsi_pll_calc_ssc(pll, rsc); + + dsi_pll_commit(pll, rsc); + + dsi_pll_config_hzindep_reg(pll, rsc); + + dsi_pll_ssc_commit(pll, rsc); + + /* flush, ensure all register writes are done*/ + wmb(); + + mdss_pll_resource_enable(rsc, false); + + return 0; +} + +static int dsi_pll_10nm_lock_status(struct mdss_pll_resources *pll) +{ + int rc; + u32 status; + u32 const delay_us = 100; + u32 const timeout_us = 5000; + + rc = readl_poll_timeout_atomic(pll->pll_base + PLL_COMMON_STATUS_ONE, + status, + ((status & BIT(0)) > 0), + delay_us, + timeout_us); + if (rc) + pr_err("DSI PLL(%d) lock failed, status=0x%08x\n", + pll->index, status); + + return rc; +} + +static void dsi_pll_disable_pll_bias(struct mdss_pll_resources *rsc) +{ + u32 data = MDSS_PLL_REG_R(rsc->phy_base, PHY_CMN_CTRL_0); + + MDSS_PLL_REG_W(rsc->pll_base, PLL_SYSTEM_MUXES, 0); + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_CTRL_0, data & ~BIT(5)); + ndelay(250); +} + +static void dsi_pll_enable_pll_bias(struct mdss_pll_resources *rsc) +{ + u32 data = MDSS_PLL_REG_R(rsc->phy_base, PHY_CMN_CTRL_0); + + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_CTRL_0, data | BIT(5)); + MDSS_PLL_REG_W(rsc->pll_base, PLL_SYSTEM_MUXES, 0xc0); + ndelay(250); +} + +static void dsi_pll_disable_global_clk(struct mdss_pll_resources *rsc) +{ + u32 data; + + data = MDSS_PLL_REG_R(rsc->phy_base, PHY_CMN_CLK_CFG1); + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_CLK_CFG1, (data & ~BIT(5))); +} + +static void dsi_pll_enable_global_clk(struct mdss_pll_resources *rsc) +{ + u32 data; + + data = MDSS_PLL_REG_R(rsc->phy_base, PHY_CMN_CLK_CFG1); + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_CLK_CFG1, (data | BIT(5))); +} + +static int dsi_pll_enable(struct dsi_pll_vco_clk *vco) +{ + int rc; + struct mdss_pll_resources *rsc = vco->priv; + + dsi_pll_enable_pll_bias(rsc); + if (rsc->slave) + dsi_pll_enable_pll_bias(rsc->slave); + + /* Start PLL */ + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_PLL_CNTRL, 0x01); + + /* + * ensure all PLL configurations are written prior to checking + * for PLL lock. + */ + wmb(); + + /* Check for PLL lock */ + rc = dsi_pll_10nm_lock_status(rsc); + if (rc) { + pr_err("PLL(%d) lock failed\n", rsc->index); + goto error; + } + + rsc->pll_on = true; + + dsi_pll_enable_global_clk(rsc); + if (rsc->slave) + dsi_pll_enable_global_clk(rsc->slave); + + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_RBUF_CTRL, 0x01); + if (rsc->slave) + MDSS_PLL_REG_W(rsc->slave->phy_base, PHY_CMN_RBUF_CTRL, 0x01); + +error: + return rc; +} + +static void dsi_pll_disable_sub(struct mdss_pll_resources *rsc) +{ + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_RBUF_CTRL, 0); + dsi_pll_disable_pll_bias(rsc); +} + +static void dsi_pll_disable(struct dsi_pll_vco_clk *vco) +{ + struct mdss_pll_resources *rsc = vco->priv; + + if (!rsc->pll_on && + mdss_pll_resource_enable(rsc, true)) { + pr_err("failed to enable pll (%d) resources\n", rsc->index); + return; + } + + rsc->handoff_resources = false; + + pr_debug("stop PLL (%d)\n", rsc->index); + + /* + * To avoid any stray glitches while + * abruptly powering down the PLL + * make sure to gate the clock using + * the clock enable bit before powering + * down the PLL + */ + dsi_pll_disable_global_clk(rsc); + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_PLL_CNTRL, 0); + dsi_pll_disable_sub(rsc); + if (rsc->slave) { + dsi_pll_disable_global_clk(rsc->slave); + dsi_pll_disable_sub(rsc->slave); + } + /* flush, ensure all register writes are done*/ + wmb(); + rsc->pll_on = false; +} + +long vco_10nm_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + unsigned long rrate = rate; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + *parent_rate = rrate; + + return rrate; +} + +static void vco_10nm_unprepare(struct clk_hw *hw) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + + if (!pll) { + pr_err("dsi pll resources not available\n"); + return; + } + pll->cached_cfg0 = MDSS_PLL_REG_R(pll->phy_base, PHY_CMN_CLK_CFG0); + pll->cached_cfg1 = MDSS_PLL_REG_R(pll->phy_base, PHY_CMN_CLK_CFG1); + pll->cached_outdiv = MDSS_PLL_REG_R(pll->pll_base, PLL_PLL_OUTDIV_RATE); + pr_debug("cfg0=%d,cfg1=%d, outdiv=%d\n", pll->cached_cfg0, + pll->cached_cfg1, pll->cached_outdiv); + + pll->vco_cached_rate = clk_hw_get_rate(hw); + dsi_pll_disable(vco); + mdss_pll_resource_enable(pll, false); +} + +static int vco_10nm_prepare(struct clk_hw *hw) +{ + int rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + + if (!pll) { + pr_err("dsi pll resources are not available\n"); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("failed to enable pll (%d) resource, rc=%d\n", + pll->index, rc); + return rc; + } + + if ((pll->vco_cached_rate != 0) && + (pll->vco_cached_rate == clk_hw_get_rate(hw))) { + rc = hw->init->ops->set_rate(hw, pll->vco_cached_rate, + pll->vco_cached_rate); + if (rc) { + pr_err("pll(%d) set_rate failed, rc=%d\n", + pll->index, rc); + mdss_pll_resource_enable(pll, false); + return rc; + } + pr_debug("cfg0=%d, cfg1=%d\n", pll->cached_cfg0, + pll->cached_cfg1); + MDSS_PLL_REG_W(pll->phy_base, PHY_CMN_CLK_CFG0, + pll->cached_cfg0); + MDSS_PLL_REG_W(pll->phy_base, PHY_CMN_CLK_CFG1, + pll->cached_cfg1); + MDSS_PLL_REG_W(pll->pll_base, PLL_PLL_OUTDIV_RATE, + pll->cached_outdiv); + } + + rc = dsi_pll_enable(vco); + if (rc) { + mdss_pll_resource_enable(pll, false); + pr_err("pll(%d) enable failed, rc=%d\n", pll->index, rc); + return rc; + } + + return rc; +} + +static unsigned long vco_10nm_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk_hw(hw); + struct mdss_pll_resources *pll = vco->priv; + int rc; + u64 ref_clk = vco->ref_clk_rate; + u64 vco_rate; + u64 multiplier; + u32 frac; + u32 dec; + u32 outdiv; + u64 pll_freq, tmp64; + + if (!vco->priv) + pr_err("vco priv is null\n"); + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("failed to enable pll(%d) resource, rc=%d\n", + pll->index, rc); + return 0; + } + + dec = MDSS_PLL_REG_R(pll->pll_base, PLL_DECIMAL_DIV_START_1); + dec &= 0xFF; + + frac = MDSS_PLL_REG_R(pll->pll_base, PLL_FRAC_DIV_START_LOW_1); + frac |= ((MDSS_PLL_REG_R(pll->pll_base, PLL_FRAC_DIV_START_MID_1) & + 0xFF) << + 8); + frac |= ((MDSS_PLL_REG_R(pll->pll_base, PLL_FRAC_DIV_START_HIGH_1) & + 0x3) << + 16); + + /* OUTDIV_1:0 field is (log(outdiv, 2)) */ + outdiv = MDSS_PLL_REG_R(pll->pll_base, PLL_PLL_OUTDIV_RATE); + outdiv &= 0x3; + outdiv = 1 << outdiv; + + /* + * TODO: + * 1. Assumes prescaler is disabled + * 2. Multiplier is 2^18. it should be 2^(num_of_frac_bits) + **/ + multiplier = 1 << 18; + pll_freq = dec * (ref_clk * 2); + tmp64 = (ref_clk * 2 * frac); + pll_freq += div_u64(tmp64, multiplier); + + vco_rate = div_u64(pll_freq, outdiv); + + pr_debug("dec=0x%x, frac=0x%x, outdiv=%d, vco=%llu\n", + dec, frac, outdiv, vco_rate); + + (void)mdss_pll_resource_enable(pll, false); + + return (unsigned long)vco_rate; +} + +static int pixel_clk_get_div(void *context, unsigned int reg, unsigned int *div) +{ + int rc; + struct mdss_pll_resources *pll = context; + u32 reg_val; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + reg_val = MDSS_PLL_REG_R(pll->phy_base, PHY_CMN_CLK_CFG0); + *div = (reg_val & 0xF0) >> 4; + + /** + * Common clock framework the divider value is interpreted as one less + * hence we return one less for all dividers except when zero + */ + if (*div != 0) + *div -= 1; + + (void)mdss_pll_resource_enable(pll, false); + + return rc; +} + +static void pixel_clk_set_div_sub(struct mdss_pll_resources *pll, int div) +{ + u32 reg_val; + + reg_val = MDSS_PLL_REG_R(pll->phy_base, PHY_CMN_CLK_CFG0); + reg_val &= ~0xF0; + reg_val |= (div << 4); + MDSS_PLL_REG_W(pll->phy_base, PHY_CMN_CLK_CFG0, reg_val); +} + +static int pixel_clk_set_div(void *context, unsigned int reg, unsigned int div) +{ + int rc; + struct mdss_pll_resources *pll = context; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + /** + * In common clock framework the divider value provided is one less and + * and hence adjusting the divider value by one prior to writing it to + * hardware + */ + div++; + pixel_clk_set_div_sub(pll, div); + if (pll->slave) + pixel_clk_set_div_sub(pll->slave, div); + (void)mdss_pll_resource_enable(pll, false); + + return 0; +} + +static int bit_clk_get_div(void *context, unsigned int reg, unsigned int *div) +{ + int rc; + struct mdss_pll_resources *pll = context; + u32 reg_val; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + reg_val = MDSS_PLL_REG_R(pll->phy_base, PHY_CMN_CLK_CFG0); + *div = (reg_val & 0x0F); + + /** + *Common clock framework the divider value is interpreted as one less + * hence we return one less for all dividers except when zero + */ + if (*div != 0) + *div -= 1; + (void)mdss_pll_resource_enable(pll, false); + + return rc; +} + +static void bit_clk_set_div_sub(struct mdss_pll_resources *rsc, int div) +{ + u32 reg_val; + + reg_val = MDSS_PLL_REG_R(rsc->phy_base, PHY_CMN_CLK_CFG0); + reg_val &= ~0x0F; + reg_val |= div; + MDSS_PLL_REG_W(rsc->phy_base, PHY_CMN_CLK_CFG0, reg_val); +} + +static int bit_clk_set_div(void *context, unsigned int reg, unsigned int div) +{ + int rc; + struct mdss_pll_resources *rsc = context; + struct dsi_pll_8998 *pll; + + if (!rsc) { + pr_err("pll resource not found\n"); + return -EINVAL; + } + + pll = rsc->priv; + if (!pll) { + pr_err("pll configuration not found\n"); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(rsc, true); + if (rc) { + pr_err("Failed to enable dsi pll resources, rc=%d\n", rc); + return rc; + } + + /** + * In common clock framework the divider value provided is one less and + * and hence adjusting the divider value by one prior to writing it to + * hardware + */ + div++; + + bit_clk_set_div_sub(rsc, div); + /* For slave PLL, this divider always should be set to 1 */ + if (rsc->slave) + bit_clk_set_div_sub(rsc->slave, 1); + + (void)mdss_pll_resource_enable(rsc, false); + + return rc; +} + +static struct regmap_config dsi_pll_10nm_config = { + .reg_bits = 32, + .reg_stride = 4, + .val_bits = 32, + .max_register = 0x7c0, +}; + +static struct regmap_bus pll_regmap_bus = { + .reg_write = pll_reg_write, + .reg_read = pll_reg_read, +}; + +static struct regmap_bus pclk_mux_regmap_bus = { + .reg_read = phy_reg_read, + .reg_write = pclk_mux_write_sel, +}; + +static struct regmap_bus pclk_src_regmap_bus = { + .reg_write = pixel_clk_set_div, + .reg_read = pixel_clk_get_div, +}; + +static struct regmap_bus bitclk_src_regmap_bus = { + .reg_write = bit_clk_set_div, + .reg_read = bit_clk_get_div, +}; + +static const struct clk_ops clk_ops_vco_10nm = { + .recalc_rate = vco_10nm_recalc_rate, + .set_rate = vco_10nm_set_rate, + .round_rate = vco_10nm_round_rate, + .prepare = vco_10nm_prepare, + .unprepare = vco_10nm_unprepare, +}; + +static struct regmap_bus mdss_mux_regmap_bus = { + .reg_write = mdss_set_mux_sel, + .reg_read = mdss_get_mux_sel, +}; + +/* + * Clock tree for generating DSI byte and pixel clocks. + * + * + * +---------------+ + * | vco_clk | + * +-------+-------+ + * | + * | + * +---------------+ + * | pll_out_div | + * | DIV(1,2,4,8) | + * +-------+-------+ + * | + * +-----------------------------+--------+ + * | | | + * +-------v-------+ | | + * | bitclk_src | | | + * | DIV(1..15) | | | + * +-------+-------+ | | + * | | | + * +----------+---------+ | | + * Shadow Path | | | | | + * + +-------v-------+ | +------v------+ | +------v-------+ + * | | byteclk_src | | |post_bit_div | | |post_vco_div | + * | | DIV(8) | | |DIV (2) | | |DIV(4) | + * | +-------+-------+ | +------+------+ | +------+-------+ + * | | | | | | | + * | | | +------+ | | + * | | +-------------+ | | +----+ + * | +--------+ | | | | + * | | +-v--v-v---v------+ + * +-v---------v----+ \ pclk_src_mux / + * \ byteclk_mux / \ / + * \ / +-----+-----+ + * +----+-----+ | Shadow Path + * | | + + * v +-----v------+ | + * dsi_byte_clk | pclk_src | | + * | DIV(1..15) | | + * +-----+------+ | + * | | + * | | + * +--------+ | + * | | + * +---v----v----+ + * \ pclk_mux / + * \ / + * +---+---+ + * | + * | + * v + * dsi_pclk + * + */ + +static struct dsi_pll_vco_clk dsi0pll_vco_clk = { + .ref_clk_rate = 19200000UL, + .min_rate = 1000000000UL, + .max_rate = 3500000000UL, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_vco_clk", + .parent_names = (const char *[]){"bi_tcxo"}, + .num_parents = 1, + .ops = &clk_ops_vco_10nm, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct dsi_pll_vco_clk dsi1pll_vco_clk = { + .ref_clk_rate = 19200000UL, + .min_rate = 1000000000UL, + .max_rate = 3500000000UL, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_vco_clk", + .parent_names = (const char *[]){"bi_tcxo"}, + .num_parents = 1, + .ops = &clk_ops_vco_10nm, + .flags = CLK_GET_RATE_NOCACHE, + }, +}; + +static struct clk_regmap_div dsi0pll_pll_out_div = { + .reg = PLL_PLL_OUTDIV_RATE, + .shift = 0, + .width = 2, + .flags = CLK_DIVIDER_POWER_OF_TWO, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_pll_out_div", + .parent_names = (const char *[]){"dsi0pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_div dsi1pll_pll_out_div = { + .reg = PLL_PLL_OUTDIV_RATE, + .shift = 0, + .width = 2, + .flags = CLK_DIVIDER_POWER_OF_TWO, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_pll_out_div", + .parent_names = (const char *[]){"dsi1pll_vco_clk"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_div dsi0pll_bitclk_src = { + .shift = 0, + .width = 4, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_bitclk_src", + .parent_names = (const char *[]){"dsi0pll_pll_out_div"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_div dsi1pll_bitclk_src = { + .shift = 0, + .width = 4, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_bitclk_src", + .parent_names = (const char *[]){"dsi1pll_pll_out_div"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_fixed_factor dsi0pll_post_vco_div = { + .div = 4, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_post_vco_div", + .parent_names = (const char *[]){"dsi0pll_pll_out_div"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_post_vco_div = { + .div = 4, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_post_vco_div", + .parent_names = (const char *[]){"dsi1pll_pll_out_div"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_byteclk_src = { + .div = 8, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_byteclk_src", + .parent_names = (const char *[]){"dsi0pll_bitclk_src"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_byteclk_src = { + .div = 8, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_byteclk_src", + .parent_names = (const char *[]){"dsi1pll_bitclk_src"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi0pll_post_bit_div = { + .div = 2, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_post_bit_div", + .parent_names = (const char *[]){"dsi0pll_bitclk_src"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_fixed_factor dsi1pll_post_bit_div = { + .div = 2, + .mult = 1, + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_post_bit_div", + .parent_names = (const char *[]){"dsi1pll_bitclk_src"}, + .num_parents = 1, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_fixed_factor_ops, + }, +}; + +static struct clk_regmap_mux dsi0pll_byteclk_mux = { + .shift = 0, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0_phy_pll_out_byteclk", + .parent_names = (const char *[]){"dsi0pll_byteclk_src"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_regmap_mux dsi1pll_byteclk_mux = { + .shift = 0, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1_phy_pll_out_byteclk", + .parent_names = (const char *[]){"dsi1pll_byteclk_src"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_regmap_mux dsi0pll_pclk_src_mux = { + .reg = PHY_CMN_CLK_CFG1, + .shift = 0, + .width = 2, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_pclk_src_mux", + .parent_names = (const char *[]){"dsi0pll_bitclk_src", + "dsi0pll_post_bit_div", + "dsi0pll_pll_out_div", + "dsi0pll_post_vco_div"}, + .num_parents = 4, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_regmap_mux dsi1pll_pclk_src_mux = { + .reg = PHY_CMN_CLK_CFG1, + .shift = 0, + .width = 2, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_pclk_src_mux", + .parent_names = (const char *[]){"dsi1pll_bitclk_src", + "dsi1pll_post_bit_div", + "dsi1pll_pll_out_div", + "dsi1pll_post_vco_div"}, + .num_parents = 4, + .flags = CLK_GET_RATE_NOCACHE, + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_regmap_div dsi0pll_pclk_src = { + .shift = 0, + .width = 4, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0pll_pclk_src", + .parent_names = (const char *[]){ + "dsi0pll_pclk_src_mux"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_div dsi1pll_pclk_src = { + .shift = 0, + .width = 4, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1pll_pclk_src", + .parent_names = (const char *[]){ + "dsi1pll_pclk_src_mux"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_div_ops, + }, + }, +}; + +static struct clk_regmap_mux dsi0pll_pclk_mux = { + .shift = 0, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi0_phy_pll_out_dsiclk", + .parent_names = (const char *[]){"dsi0pll_pclk_src"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_regmap_mux dsi1pll_pclk_mux = { + .shift = 0, + .width = 1, + .clkr = { + .hw.init = &(struct clk_init_data){ + .name = "dsi1_phy_pll_out_dsiclk", + .parent_names = (const char *[]){"dsi1pll_pclk_src"}, + .num_parents = 1, + .flags = (CLK_GET_RATE_NOCACHE | CLK_SET_RATE_PARENT), + .ops = &clk_regmap_mux_closest_ops, + }, + }, +}; + +static struct clk_hw *mdss_dsi_pllcc_10nm[] = { + [VCO_CLK_0] = &dsi0pll_vco_clk.hw, + [PLL_OUT_DIV_0_CLK] = &dsi0pll_pll_out_div.clkr.hw, + [BITCLK_SRC_0_CLK] = &dsi0pll_bitclk_src.clkr.hw, + [BYTECLK_SRC_0_CLK] = &dsi0pll_byteclk_src.hw, + [POST_BIT_DIV_0_CLK] = &dsi0pll_post_bit_div.hw, + [POST_VCO_DIV_0_CLK] = &dsi0pll_post_vco_div.hw, + [BYTECLK_MUX_0_CLK] = &dsi0pll_byteclk_mux.clkr.hw, + [PCLK_SRC_MUX_0_CLK] = &dsi0pll_pclk_src_mux.clkr.hw, + [PCLK_SRC_0_CLK] = &dsi0pll_pclk_src.clkr.hw, + [PCLK_MUX_0_CLK] = &dsi0pll_pclk_mux.clkr.hw, + [VCO_CLK_1] = &dsi1pll_vco_clk.hw, + [PLL_OUT_DIV_1_CLK] = &dsi1pll_pll_out_div.clkr.hw, + [BITCLK_SRC_1_CLK] = &dsi1pll_bitclk_src.clkr.hw, + [BYTECLK_SRC_1_CLK] = &dsi1pll_byteclk_src.hw, + [POST_BIT_DIV_1_CLK] = &dsi1pll_post_bit_div.hw, + [POST_VCO_DIV_1_CLK] = &dsi1pll_post_vco_div.hw, + [BYTECLK_MUX_1_CLK] = &dsi1pll_byteclk_mux.clkr.hw, + [PCLK_SRC_MUX_1_CLK] = &dsi1pll_pclk_src_mux.clkr.hw, + [PCLK_SRC_1_CLK] = &dsi1pll_pclk_src.clkr.hw, + [PCLK_MUX_1_CLK] = &dsi1pll_pclk_mux.clkr.hw, +}; + +int dsi_pll_clock_register_10nm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0, ndx, i; + struct clk *clk; + struct clk_onecell_data *clk_data; + int num_clks = ARRAY_SIZE(mdss_dsi_pllcc_10nm); + struct regmap *rmap; + + if (!pdev || !pdev->dev.of_node || + !pll_res || !pll_res->pll_base || !pll_res->phy_base) { + pr_err("Invalid params\n"); + return -EINVAL; + } + + ndx = pll_res->index; + + if (ndx >= DSI_PLL_MAX) { + pr_err("pll index(%d) NOT supported\n", ndx); + return -EINVAL; + } + + pll_rsc_db[ndx] = pll_res; + plls[ndx].rsc = pll_res; + pll_res->priv = &plls[ndx]; + pll_res->vco_delay = VCO_DELAY_USEC; + + clk_data = devm_kzalloc(&pdev->dev, sizeof(struct clk_onecell_data), + GFP_KERNEL); + if (!clk_data) + return -ENOMEM; + + clk_data->clks = devm_kzalloc(&pdev->dev, (num_clks * + sizeof(struct clk *)), GFP_KERNEL); + if (!clk_data->clks) { + devm_kfree(&pdev->dev, clk_data); + return -ENOMEM; + } + clk_data->clk_num = num_clks; + + /* Establish client data */ + if (ndx == 0) { + + rmap = devm_regmap_init(&pdev->dev, &pll_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi0pll_pll_out_div.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &bitclk_src_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi0pll_bitclk_src.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &pclk_src_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi0pll_pclk_src.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi0pll_pclk_mux.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &pclk_mux_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi0pll_pclk_src_mux.clkr.regmap = rmap; + rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi0pll_byteclk_mux.clkr.regmap = rmap; + + dsi0pll_vco_clk.priv = pll_res; + for (i = VCO_CLK_0; i <= PCLK_MUX_0_CLK; i++) { + clk = devm_clk_register(&pdev->dev, + mdss_dsi_pllcc_10nm[i]); + if (IS_ERR(clk)) { + pr_err("clk registration failed for DSI clock:%d\n", + pll_res->index); + rc = -EINVAL; + goto clk_register_fail; + } + clk_data->clks[i] = clk; + + } + + rc = of_clk_add_provider(pdev->dev.of_node, + of_clk_src_onecell_get, clk_data); + + + } else { + rmap = devm_regmap_init(&pdev->dev, &pll_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi1pll_pll_out_div.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &bitclk_src_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi1pll_bitclk_src.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &pclk_src_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi1pll_pclk_src.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &pclk_mux_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi1pll_pclk_mux.clkr.regmap = rmap; + + rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi1pll_pclk_src_mux.clkr.regmap = rmap; + rmap = devm_regmap_init(&pdev->dev, &mdss_mux_regmap_bus, + pll_res, &dsi_pll_10nm_config); + dsi1pll_byteclk_mux.clkr.regmap = rmap; + dsi1pll_vco_clk.priv = pll_res; + + for (i = VCO_CLK_1; i <= PCLK_MUX_1_CLK; i++) { + clk = devm_clk_register(&pdev->dev, + mdss_dsi_pllcc_10nm[i]); + if (IS_ERR(clk)) { + pr_err("clk registration failed for DSI clock:%d\n", + pll_res->index); + rc = -EINVAL; + goto clk_register_fail; + } + clk_data->clks[i] = clk; + + } + + rc = of_clk_add_provider(pdev->dev.of_node, + of_clk_src_onecell_get, clk_data); + } + if (!rc) { + pr_info("Registered DSI PLL ndx=%d, clocks successfully", ndx); + + return rc; + } +clk_register_fail: + devm_kfree(&pdev->dev, clk_data->clks); + devm_kfree(&pdev->dev, clk_data); + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-20nm.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-20nm.c new file mode 100644 index 0000000000000000000000000000000000000000..787c93b5c8901734435794698435253a2effb10f --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-20nm.c @@ -0,0 +1,608 @@ +/* Copyright (c) 2014-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) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" + +#define VCO_DELAY_USEC 1 + +static const struct clk_ops bypass_lp_div_mux_clk_ops; +static const struct clk_ops pixel_clk_src_ops; +static const struct clk_ops byte_clk_src_ops; +static const struct clk_ops ndiv_clk_ops; + +static const struct clk_ops shadow_pixel_clk_src_ops; +static const struct clk_ops shadow_byte_clk_src_ops; +static const struct clk_ops clk_ops_gen_mux_dsi; + +static int vco_set_rate_20nm(struct clk *c, unsigned long rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + pr_debug("Cancel pending pll off work\n"); + cancel_work_sync(&dsi_pll_res->pll_off); + rc = pll_20nm_vco_set_rate(vco, rate); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +static int pll1_vco_set_rate_20nm(struct clk *c, unsigned long rate) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll_res = vco->priv; + + mdss_pll_resource_enable(pll_res, true); + __dsi_pll_disable(pll_res->pll_base); + mdss_pll_resource_enable(pll_res, false); + + pr_debug("Configuring PLL1 registers.\n"); + + return 0; +} + +static int shadow_vco_set_rate_20nm(struct clk *c, unsigned long rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res->resource_enable) { + pr_err("PLL resources disabled. Dynamic fps invalid\n"); + return -EINVAL; + } + + rc = shadow_pll_20nm_vco_set_rate(vco, rate); + + return rc; +} + +/* Op structures */ + +static const struct clk_ops pll1_clk_ops_dsi_vco = { + .set_rate = pll1_vco_set_rate_20nm, +}; + +static const struct clk_ops clk_ops_dsi_vco = { + .set_rate = vco_set_rate_20nm, + .round_rate = pll_20nm_vco_round_rate, + .handoff = pll_20nm_vco_handoff, + .prepare = pll_20nm_vco_prepare, + .unprepare = pll_20nm_vco_unprepare, +}; + +static struct clk_div_ops fixed_hr_oclk2_div_ops = { + .set_div = fixed_hr_oclk2_set_div, + .get_div = fixed_hr_oclk2_get_div, +}; + +static struct clk_div_ops ndiv_ops = { + .set_div = ndiv_set_div, + .get_div = ndiv_get_div, +}; + +static struct clk_div_ops hr_oclk3_div_ops = { + .set_div = hr_oclk3_set_div, + .get_div = hr_oclk3_get_div, +}; + +static struct clk_mux_ops bypass_lp_div_mux_ops = { + .set_mux_sel = set_bypass_lp_div_mux_sel, + .get_mux_sel = get_bypass_lp_div_mux_sel, +}; + +static const struct clk_ops shadow_clk_ops_dsi_vco = { + .set_rate = shadow_vco_set_rate_20nm, + .round_rate = pll_20nm_vco_round_rate, + .handoff = pll_20nm_vco_handoff, +}; + +static struct clk_div_ops shadow_fixed_hr_oclk2_div_ops = { + .set_div = shadow_fixed_hr_oclk2_set_div, + .get_div = fixed_hr_oclk2_get_div, +}; + +static struct clk_div_ops shadow_ndiv_ops = { + .set_div = shadow_ndiv_set_div, + .get_div = ndiv_get_div, +}; + +static struct clk_div_ops shadow_hr_oclk3_div_ops = { + .set_div = shadow_hr_oclk3_set_div, + .get_div = hr_oclk3_get_div, +}; + +static struct clk_mux_ops shadow_bypass_lp_div_mux_ops = { + .set_mux_sel = set_shadow_bypass_lp_div_mux_sel, + .get_mux_sel = get_bypass_lp_div_mux_sel, +}; + +static struct clk_mux_ops mdss_byte_mux_ops = { + .set_mux_sel = set_mdss_byte_mux_sel, + .get_mux_sel = get_mdss_byte_mux_sel, +}; + +static struct clk_mux_ops mdss_pixel_mux_ops = { + .set_mux_sel = set_mdss_pixel_mux_sel, + .get_mux_sel = get_mdss_pixel_mux_sel, +}; + +static struct dsi_pll_vco_clk mdss_dsi1_vco_clk_src = { + .c = { + .dbg_name = "mdss_dsi1_vco_clk_src", + .ops = &pll1_clk_ops_dsi_vco, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(mdss_dsi1_vco_clk_src.c), + }, +}; + +static struct dsi_pll_vco_clk dsi_vco_clk_8994 = { + .ref_clk_rate = 19200000, + .min_rate = 300000000, + .max_rate = 1500000000, + .pll_en_seq_cnt = 1, + .pll_enable_seqs[0] = pll_20nm_vco_enable_seq, + .c = { + .dbg_name = "dsi_vco_clk_8994", + .ops = &clk_ops_dsi_vco, + CLK_INIT(dsi_vco_clk_8994.c), + }, +}; + +static struct dsi_pll_vco_clk shadow_dsi_vco_clk_8994 = { + .ref_clk_rate = 19200000, + .min_rate = 300000000, + .max_rate = 1500000000, + .c = { + .dbg_name = "shadow_dsi_vco_clk_8994", + .ops = &shadow_clk_ops_dsi_vco, + CLK_INIT(shadow_dsi_vco_clk_8994.c), + }, +}; + +static struct div_clk ndiv_clk_8994 = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &ndiv_ops, + .c = { + .parent = &dsi_vco_clk_8994.c, + .dbg_name = "ndiv_clk_8994", + .ops = &ndiv_clk_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(ndiv_clk_8994.c), + }, +}; + +static struct div_clk shadow_ndiv_clk_8994 = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &shadow_ndiv_ops, + .c = { + .parent = &shadow_dsi_vco_clk_8994.c, + .dbg_name = "shadow_ndiv_clk_8994", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(shadow_ndiv_clk_8994.c), + }, +}; + +static struct div_clk indirect_path_div2_clk_8994 = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &ndiv_clk_8994.c, + .dbg_name = "indirect_path_div2_clk_8994", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(indirect_path_div2_clk_8994.c), + }, +}; + +static struct div_clk shadow_indirect_path_div2_clk_8994 = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &shadow_ndiv_clk_8994.c, + .dbg_name = "shadow_indirect_path_div2_clk_8994", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(shadow_indirect_path_div2_clk_8994.c), + }, +}; + +static struct div_clk hr_oclk3_div_clk_8994 = { + .data = { + .max_div = 255, + .min_div = 1, + }, + .ops = &hr_oclk3_div_ops, + .c = { + .parent = &dsi_vco_clk_8994.c, + .dbg_name = "hr_oclk3_div_clk_8994", + .ops = &pixel_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(hr_oclk3_div_clk_8994.c), + }, +}; + +static struct div_clk shadow_hr_oclk3_div_clk_8994 = { + .data = { + .max_div = 255, + .min_div = 1, + }, + .ops = &shadow_hr_oclk3_div_ops, + .c = { + .parent = &shadow_dsi_vco_clk_8994.c, + .dbg_name = "shadow_hr_oclk3_div_clk_8994", + .ops = &shadow_pixel_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(shadow_hr_oclk3_div_clk_8994.c), + }, +}; + +static struct div_clk pixel_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &hr_oclk3_div_clk_8994.c, + .dbg_name = "pixel_clk_src", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(pixel_clk_src.c), + }, +}; + +static struct div_clk shadow_pixel_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &shadow_hr_oclk3_div_clk_8994.c, + .dbg_name = "shadow_pixel_clk_src", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(shadow_pixel_clk_src.c), + }, +}; + +static struct mux_clk bypass_lp_div_mux_8994 = { + .num_parents = 2, + .parents = (struct clk_src[]){ + {&dsi_vco_clk_8994.c, 0}, + {&indirect_path_div2_clk_8994.c, 1}, + }, + .ops = &bypass_lp_div_mux_ops, + .c = { + .parent = &dsi_vco_clk_8994.c, + .dbg_name = "bypass_lp_div_mux_8994", + .ops = &bypass_lp_div_mux_clk_ops, + CLK_INIT(bypass_lp_div_mux_8994.c), + }, +}; + +static struct mux_clk shadow_bypass_lp_div_mux_8994 = { + .num_parents = 2, + .parents = (struct clk_src[]){ + {&shadow_dsi_vco_clk_8994.c, 0}, + {&shadow_indirect_path_div2_clk_8994.c, 1}, + }, + .ops = &shadow_bypass_lp_div_mux_ops, + .c = { + .parent = &shadow_dsi_vco_clk_8994.c, + .dbg_name = "shadow_bypass_lp_div_mux_8994", + .ops = &clk_ops_gen_mux, + CLK_INIT(shadow_bypass_lp_div_mux_8994.c), + }, +}; + +static struct div_clk fixed_hr_oclk2_div_clk_8994 = { + .ops = &fixed_hr_oclk2_div_ops, + .data = { + .min_div = 4, + .max_div = 4, + }, + .c = { + .parent = &bypass_lp_div_mux_8994.c, + .dbg_name = "fixed_hr_oclk2_div_clk_8994", + .ops = &byte_clk_src_ops, + CLK_INIT(fixed_hr_oclk2_div_clk_8994.c), + }, +}; + +static struct div_clk shadow_fixed_hr_oclk2_div_clk_8994 = { + .ops = &shadow_fixed_hr_oclk2_div_ops, + .data = { + .min_div = 4, + .max_div = 4, + }, + .c = { + .parent = &shadow_bypass_lp_div_mux_8994.c, + .dbg_name = "shadow_fixed_hr_oclk2_div_clk_8994", + .ops = &shadow_byte_clk_src_ops, + CLK_INIT(shadow_fixed_hr_oclk2_div_clk_8994.c), + }, +}; + +static struct div_clk byte_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &fixed_hr_oclk2_div_clk_8994.c, + .dbg_name = "byte_clk_src", + .ops = &clk_ops_div, + CLK_INIT(byte_clk_src.c), + }, +}; + +static struct div_clk shadow_byte_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &shadow_fixed_hr_oclk2_div_clk_8994.c, + .dbg_name = "shadow_byte_clk_src", + .ops = &clk_ops_div, + CLK_INIT(shadow_byte_clk_src.c), + }, +}; + +static struct mux_clk mdss_pixel_clk_mux = { + .num_parents = 2, + .parents = (struct clk_src[]) { + {&pixel_clk_src.c, 0}, + {&shadow_pixel_clk_src.c, 1}, + }, + .ops = &mdss_pixel_mux_ops, + .c = { + .parent = &pixel_clk_src.c, + .dbg_name = "mdss_pixel_clk_mux", + .ops = &clk_ops_gen_mux, + CLK_INIT(mdss_pixel_clk_mux.c), + } +}; + +static struct mux_clk mdss_byte_clk_mux = { + .num_parents = 2, + .parents = (struct clk_src[]) { + {&byte_clk_src.c, 0}, + {&shadow_byte_clk_src.c, 1}, + }, + .ops = &mdss_byte_mux_ops, + .c = { + .parent = &byte_clk_src.c, + .dbg_name = "mdss_byte_clk_mux", + .ops = &clk_ops_gen_mux_dsi, + CLK_INIT(mdss_byte_clk_mux.c), + } +}; + +static struct clk_lookup mdss_dsi_pll_1_cc_8994[] = { + CLK_LIST(mdss_dsi1_vco_clk_src), +}; + +static struct clk_lookup mdss_dsi_pllcc_8994[] = { + CLK_LIST(mdss_pixel_clk_mux), + CLK_LIST(mdss_byte_clk_mux), + CLK_LIST(pixel_clk_src), + CLK_LIST(byte_clk_src), + CLK_LIST(fixed_hr_oclk2_div_clk_8994), + CLK_LIST(bypass_lp_div_mux_8994), + CLK_LIST(hr_oclk3_div_clk_8994), + CLK_LIST(indirect_path_div2_clk_8994), + CLK_LIST(ndiv_clk_8994), + CLK_LIST(dsi_vco_clk_8994), + CLK_LIST(shadow_pixel_clk_src), + CLK_LIST(shadow_byte_clk_src), + CLK_LIST(shadow_fixed_hr_oclk2_div_clk_8994), + CLK_LIST(shadow_bypass_lp_div_mux_8994), + CLK_LIST(shadow_hr_oclk3_div_clk_8994), + CLK_LIST(shadow_indirect_path_div2_clk_8994), + CLK_LIST(shadow_ndiv_clk_8994), + CLK_LIST(shadow_dsi_vco_clk_8994), +}; + +static void dsi_pll_off_work(struct work_struct *work) +{ + struct mdss_pll_resources *pll_res; + + if (!work) { + pr_err("pll_resource is invalid\n"); + return; + } + + pr_debug("Starting PLL off Worker%s\n", __func__); + + pll_res = container_of(work, struct + mdss_pll_resources, pll_off); + + mdss_pll_resource_enable(pll_res, true); + __dsi_pll_disable(pll_res->pll_base); + if (pll_res->pll_1_base) + __dsi_pll_disable(pll_res->pll_1_base); + mdss_pll_resource_enable(pll_res, false); +} + +static int dsi_pll_regulator_notifier_call(struct notifier_block *self, + unsigned long event, void *data) +{ + + struct mdss_pll_resources *pll_res; + + if (!self) { + pr_err("pll_resource is invalid\n"); + goto error; + } + + pll_res = container_of(self, struct + mdss_pll_resources, gdsc_cb); + + if (event & REGULATOR_EVENT_ENABLE) { + pr_debug("Regulator ON event. Scheduling pll off worker\n"); + schedule_work(&pll_res->pll_off); + } + + if (event & REGULATOR_EVENT_DISABLE) + pr_debug("Regulator OFF event.\n"); + +error: + return NOTIFY_OK; +} + +int dsi_pll_clock_register_20nm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc; + struct dss_vreg *pll_reg; + + if (!pdev || !pdev->dev.of_node) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + if (!pll_res || !pll_res->pll_base) { + pr_err("Invalid PLL resources\n"); + return -EPROBE_DEFER; + } + + /* + * Set client data to mux, div and vco clocks. + * This needs to be done only for PLL0 since, that is the one in + * use. + **/ + if (!pll_res->index) { + byte_clk_src.priv = pll_res; + pixel_clk_src.priv = pll_res; + bypass_lp_div_mux_8994.priv = pll_res; + indirect_path_div2_clk_8994.priv = pll_res; + ndiv_clk_8994.priv = pll_res; + fixed_hr_oclk2_div_clk_8994.priv = pll_res; + hr_oclk3_div_clk_8994.priv = pll_res; + dsi_vco_clk_8994.priv = pll_res; + + shadow_byte_clk_src.priv = pll_res; + shadow_pixel_clk_src.priv = pll_res; + shadow_bypass_lp_div_mux_8994.priv = pll_res; + shadow_indirect_path_div2_clk_8994.priv = pll_res; + shadow_ndiv_clk_8994.priv = pll_res; + shadow_fixed_hr_oclk2_div_clk_8994.priv = pll_res; + shadow_hr_oclk3_div_clk_8994.priv = pll_res; + shadow_dsi_vco_clk_8994.priv = pll_res; + + pll_res->vco_delay = VCO_DELAY_USEC; + + /* Set clock source operations */ + pixel_clk_src_ops = clk_ops_slave_div; + pixel_clk_src_ops.prepare = dsi_pll_div_prepare; + + ndiv_clk_ops = clk_ops_div; + ndiv_clk_ops.prepare = dsi_pll_div_prepare; + + byte_clk_src_ops = clk_ops_div; + byte_clk_src_ops.prepare = dsi_pll_div_prepare; + + bypass_lp_div_mux_clk_ops = clk_ops_gen_mux; + bypass_lp_div_mux_clk_ops.prepare = dsi_pll_mux_prepare; + + clk_ops_gen_mux_dsi = clk_ops_gen_mux; + clk_ops_gen_mux_dsi.round_rate = parent_round_rate; + clk_ops_gen_mux_dsi.set_rate = parent_set_rate; + + shadow_pixel_clk_src_ops = clk_ops_slave_div; + shadow_pixel_clk_src_ops.prepare = dsi_pll_div_prepare; + + shadow_byte_clk_src_ops = clk_ops_div; + shadow_byte_clk_src_ops.prepare = dsi_pll_div_prepare; + } else { + mdss_dsi1_vco_clk_src.priv = pll_res; + } + + if ((pll_res->target_id == MDSS_PLL_TARGET_8994) || + (pll_res->target_id == MDSS_PLL_TARGET_8992)) { + if (pll_res->index) { + rc = of_msm_clock_register(pdev->dev.of_node, + mdss_dsi_pll_1_cc_8994, + ARRAY_SIZE(mdss_dsi_pll_1_cc_8994)); + if (rc) { + pr_err("Clock register failed\n"); + rc = -EPROBE_DEFER; + } + } else { + rc = of_msm_clock_register(pdev->dev.of_node, + mdss_dsi_pllcc_8994, + ARRAY_SIZE(mdss_dsi_pllcc_8994)); + if (rc) { + pr_err("Clock register failed\n"); + rc = -EPROBE_DEFER; + } + pll_res->gdsc_cb.notifier_call = + dsi_pll_regulator_notifier_call; + INIT_WORK(&pll_res->pll_off, dsi_pll_off_work); + + pll_reg = mdss_pll_get_mp_by_reg_name(pll_res, "gdsc"); + if (pll_reg) { + pr_debug("Registering for gdsc regulator events\n"); + if (regulator_register_notifier(pll_reg->vreg, + &(pll_res->gdsc_cb))) + pr_err("Regulator notification registration failed!\n"); + } + } + + } else { + pr_err("Invalid target ID\n"); + rc = -EINVAL; + } + + if (!rc) + pr_info("Registered DSI PLL clocks successfully\n"); + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-28hpm.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-28hpm.c new file mode 100644 index 0000000000000000000000000000000000000000..285ba0c58f3e144869b6914e5de9755e4ec0ed57 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-28hpm.c @@ -0,0 +1,335 @@ +/* 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) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" + +#define VCO_DELAY_USEC 1 + +static struct clk_div_ops fixed_2div_ops; +static const struct clk_ops byte_mux_clk_ops; +static const struct clk_ops pixel_clk_src_ops; +static const struct clk_ops byte_clk_src_ops; +static const struct clk_ops analog_postdiv_clk_ops; +static struct lpfr_cfg lpfr_lut_struct[] = { + {479500000, 8}, + {480000000, 11}, + {575500000, 8}, + {576000000, 12}, + {610500000, 8}, + {659500000, 9}, + {671500000, 10}, + {672000000, 14}, + {708500000, 10}, + {750000000, 11}, +}; + +static void dsi_pll_software_reset(struct mdss_pll_resources *dsi_pll_res) +{ + /* + * Add HW recommended delays after toggling the software + * reset bit off and back on. + */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x01); + udelay(1); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x00); + udelay(1); +} + +static int vco_set_rate_hpm(struct clk *c, unsigned long rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + rc = vco_set_rate(vco, rate); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +static int dsi_pll_enable_seq_8974(struct mdss_pll_resources *dsi_pll_res) +{ + int i, rc = 0; + int pll_locked; + + dsi_pll_software_reset(dsi_pll_res); + + /* + * PLL power up sequence. + * Add necessary delays recommeded by hardware. + */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01); + udelay(1); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05); + udelay(200); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x07); + udelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f); + udelay(500); + + for (i = 0; i < 2; i++) { + udelay(100); + /* DSI Uniphy lock detect setting */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x0c); + udelay(100); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x0d); + + pll_locked = dsi_pll_lock_status(dsi_pll_res); + if (pll_locked) + break; + + dsi_pll_software_reset(dsi_pll_res); + /* + * PLL power up sequence. + * Add necessary delays recommeded by hardware. + */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x1); + udelay(1); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x5); + udelay(200); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x7); + udelay(250); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x5); + udelay(200); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x7); + udelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0xf); + udelay(500); + + } + + if (!pll_locked) { + pr_err("DSI PLL lock failed\n"); + rc = -EINVAL; + } else { + pr_debug("DSI PLL Lock success\n"); + } + + return rc; +} + +/* Op structures */ + +static const struct clk_ops clk_ops_dsi_vco = { + .set_rate = vco_set_rate_hpm, + .round_rate = vco_round_rate, + .handoff = vco_handoff, + .prepare = vco_prepare, + .unprepare = vco_unprepare, +}; + + +static struct clk_div_ops fixed_4div_ops = { + .set_div = fixed_4div_set_div, + .get_div = fixed_4div_get_div, +}; + +static struct clk_div_ops analog_postdiv_ops = { + .set_div = analog_set_div, + .get_div = analog_get_div, +}; + +static struct clk_div_ops digital_postdiv_ops = { + .set_div = digital_set_div, + .get_div = digital_get_div, +}; + +static struct clk_mux_ops byte_mux_ops = { + .set_mux_sel = set_byte_mux_sel, + .get_mux_sel = get_byte_mux_sel, +}; + +static struct dsi_pll_vco_clk dsi_vco_clk_8974 = { + .ref_clk_rate = 19200000, + .min_rate = 350000000, + .max_rate = 750000000, + .pll_en_seq_cnt = 3, + .pll_enable_seqs[0] = dsi_pll_enable_seq_8974, + .pll_enable_seqs[1] = dsi_pll_enable_seq_8974, + .pll_enable_seqs[2] = dsi_pll_enable_seq_8974, + .lpfr_lut_size = 10, + .lpfr_lut = lpfr_lut_struct, + .c = { + .dbg_name = "dsi_vco_clk_8974", + .ops = &clk_ops_dsi_vco, + CLK_INIT(dsi_vco_clk_8974.c), + }, +}; + +static struct div_clk analog_postdiv_clk_8974 = { + .data = { + .max_div = 255, + .min_div = 1, + }, + .ops = &analog_postdiv_ops, + .c = { + .parent = &dsi_vco_clk_8974.c, + .dbg_name = "analog_postdiv_clk", + .ops = &analog_postdiv_clk_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(analog_postdiv_clk_8974.c), + }, +}; + +static struct div_clk indirect_path_div2_clk_8974 = { + .ops = &fixed_2div_ops, + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &analog_postdiv_clk_8974.c, + .dbg_name = "indirect_path_div2_clk", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(indirect_path_div2_clk_8974.c), + }, +}; + +static struct div_clk pixel_clk_src_8974 = { + .data = { + .max_div = 255, + .min_div = 1, + }, + .ops = &digital_postdiv_ops, + .c = { + .parent = &dsi_vco_clk_8974.c, + .dbg_name = "pixel_clk_src_8974", + .ops = &pixel_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(pixel_clk_src_8974.c), + }, +}; + +static struct mux_clk byte_mux_8974 = { + .num_parents = 2, + .parents = (struct clk_src[]){ + {&dsi_vco_clk_8974.c, 0}, + {&indirect_path_div2_clk_8974.c, 1}, + }, + .ops = &byte_mux_ops, + .c = { + .parent = &dsi_vco_clk_8974.c, + .dbg_name = "byte_mux_8974", + .ops = &byte_mux_clk_ops, + CLK_INIT(byte_mux_8974.c), + }, +}; + +static struct div_clk byte_clk_src_8974 = { + .ops = &fixed_4div_ops, + .data = { + .min_div = 4, + .max_div = 4, + }, + .c = { + .parent = &byte_mux_8974.c, + .dbg_name = "byte_clk_src_8974", + .ops = &byte_clk_src_ops, + CLK_INIT(byte_clk_src_8974.c), + }, +}; + +static struct clk_lookup mdss_dsi_pllcc_8974[] = { + CLK_LOOKUP_OF("pixel_src", pixel_clk_src_8974, + "fd8c0000.qcom,mmsscc-mdss"), + CLK_LOOKUP_OF("byte_src", byte_clk_src_8974, + "fd8c0000.qcom,mmsscc-mdss"), +}; + +int dsi_pll_clock_register_hpm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc; + + if (!pdev || !pdev->dev.of_node) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + if (!pll_res || !pll_res->pll_base) { + pr_err("Invalid PLL resources\n"); + return -EPROBE_DEFER; + } + + /* Set client data to mux, div and vco clocks */ + byte_clk_src_8974.priv = pll_res; + pixel_clk_src_8974.priv = pll_res; + byte_mux_8974.priv = pll_res; + indirect_path_div2_clk_8974.priv = pll_res; + analog_postdiv_clk_8974.priv = pll_res; + dsi_vco_clk_8974.priv = pll_res; + pll_res->vco_delay = VCO_DELAY_USEC; + + /* Set clock source operations */ + pixel_clk_src_ops = clk_ops_slave_div; + pixel_clk_src_ops.prepare = dsi_pll_div_prepare; + + analog_postdiv_clk_ops = clk_ops_div; + analog_postdiv_clk_ops.prepare = dsi_pll_div_prepare; + + byte_clk_src_ops = clk_ops_div; + byte_clk_src_ops.prepare = dsi_pll_div_prepare; + + byte_mux_clk_ops = clk_ops_gen_mux; + byte_mux_clk_ops.prepare = dsi_pll_mux_prepare; + + if (pll_res->target_id == MDSS_PLL_TARGET_8974) { + rc = of_msm_clock_register(pdev->dev.of_node, + mdss_dsi_pllcc_8974, ARRAY_SIZE(mdss_dsi_pllcc_8974)); + if (rc) { + pr_err("Clock register failed\n"); + rc = -EPROBE_DEFER; + } + } else { + pr_err("Invalid target ID\n"); + rc = -EINVAL; + } + + if (!rc) + pr_info("Registered DSI PLL clocks successfully\n"); + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c new file mode 100644 index 0000000000000000000000000000000000000000..f6c7e287b8caf80c76660e43429c4fb3310dc454 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-28lpm.c @@ -0,0 +1,306 @@ +/* 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) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" + +#define VCO_DELAY_USEC 1000 + +static struct clk_div_ops fixed_2div_ops; +static const struct clk_ops byte_mux_clk_ops; +static const struct clk_ops pixel_clk_src_ops; +static const struct clk_ops byte_clk_src_ops; +static const struct clk_ops analog_postdiv_clk_ops; +static struct lpfr_cfg lpfr_lut_struct[] = { + {479500000, 8}, + {480000000, 11}, + {575500000, 8}, + {576000000, 12}, + {610500000, 8}, + {659500000, 9}, + {671500000, 10}, + {672000000, 14}, + {708500000, 10}, + {750000000, 11}, +}; + +static int vco_set_rate_lpm(struct clk *c, unsigned long rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + /* + * DSI PLL software reset. Add HW recommended delays after toggling + * the software reset bit off and back on. + */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x01); + udelay(1000); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x00); + udelay(1000); + + rc = vco_set_rate(vco, rate); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +static int dsi_pll_enable_seq_8916(struct mdss_pll_resources *dsi_pll_res) +{ + int pll_locked = 0; + + /* + * DSI PLL software reset. Add HW recommended delays after toggling + * the software reset bit off and back on. + */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x01); + ndelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG, 0x00); + + /* + * PLL power up sequence. + * Add necessary delays recommended by hardware. + */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG1, 0x34); + ndelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01); + ndelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05); + ndelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f); + ndelay(500); + + /* DSI PLL toggle lock detect setting */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x04); + ndelay(500); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x05); + udelay(512); + + pll_locked = dsi_pll_lock_status(dsi_pll_res); + + if (pll_locked) + pr_debug("PLL Locked\n"); + else + pr_err("PLL failed to lock\n"); + + return pll_locked ? 0 : -EINVAL; +} + +/* Op structures */ + +static const struct clk_ops clk_ops_dsi_vco = { + .set_rate = vco_set_rate_lpm, + .round_rate = vco_round_rate, + .handoff = vco_handoff, + .prepare = vco_prepare, + .unprepare = vco_unprepare, +}; + + +static struct clk_div_ops fixed_4div_ops = { + .set_div = fixed_4div_set_div, + .get_div = fixed_4div_get_div, +}; + +static struct clk_div_ops analog_postdiv_ops = { + .set_div = analog_set_div, + .get_div = analog_get_div, +}; + +static struct clk_div_ops digital_postdiv_ops = { + .set_div = digital_set_div, + .get_div = digital_get_div, +}; + +static struct clk_mux_ops byte_mux_ops = { + .set_mux_sel = set_byte_mux_sel, + .get_mux_sel = get_byte_mux_sel, +}; + +static struct dsi_pll_vco_clk dsi_vco_clk_8916 = { + .ref_clk_rate = 19200000, + .min_rate = 350000000, + .max_rate = 750000000, + .pll_en_seq_cnt = 1, + .pll_enable_seqs[0] = dsi_pll_enable_seq_8916, + .lpfr_lut_size = 10, + .lpfr_lut = lpfr_lut_struct, + .c = { + .dbg_name = "dsi_vco_clk_8916", + .ops = &clk_ops_dsi_vco, + CLK_INIT(dsi_vco_clk_8916.c), + }, +}; + +static struct div_clk analog_postdiv_clk_8916 = { + .data = { + .max_div = 255, + .min_div = 1, + }, + .ops = &analog_postdiv_ops, + .c = { + .parent = &dsi_vco_clk_8916.c, + .dbg_name = "analog_postdiv_clk", + .ops = &analog_postdiv_clk_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(analog_postdiv_clk_8916.c), + }, +}; + +static struct div_clk indirect_path_div2_clk_8916 = { + .ops = &fixed_2div_ops, + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &analog_postdiv_clk_8916.c, + .dbg_name = "indirect_path_div2_clk", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(indirect_path_div2_clk_8916.c), + }, +}; + +static struct div_clk pixel_clk_src = { + .data = { + .max_div = 255, + .min_div = 1, + }, + .ops = &digital_postdiv_ops, + .c = { + .parent = &dsi_vco_clk_8916.c, + .dbg_name = "pixel_clk_src_8916", + .ops = &pixel_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(pixel_clk_src.c), + }, +}; + +static struct mux_clk byte_mux_8916 = { + .num_parents = 2, + .parents = (struct clk_src[]){ + {&dsi_vco_clk_8916.c, 0}, + {&indirect_path_div2_clk_8916.c, 1}, + }, + .ops = &byte_mux_ops, + .c = { + .parent = &dsi_vco_clk_8916.c, + .dbg_name = "byte_mux_8916", + .ops = &byte_mux_clk_ops, + CLK_INIT(byte_mux_8916.c), + }, +}; + +static struct div_clk byte_clk_src = { + .ops = &fixed_4div_ops, + .data = { + .min_div = 4, + .max_div = 4, + }, + .c = { + .parent = &byte_mux_8916.c, + .dbg_name = "byte_clk_src_8916", + .ops = &byte_clk_src_ops, + CLK_INIT(byte_clk_src.c), + }, +}; + +static struct clk_lookup mdss_dsi_pllcc_8916[] = { + CLK_LIST(pixel_clk_src), + CLK_LIST(byte_clk_src), +}; + +int dsi_pll_clock_register_lpm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc; + + if (!pdev || !pdev->dev.of_node) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + if (!pll_res || !pll_res->pll_base) { + pr_err("Invalid PLL resources\n"); + return -EPROBE_DEFER; + } + + /* Set client data to mux, div and vco clocks */ + byte_clk_src.priv = pll_res; + pixel_clk_src.priv = pll_res; + byte_mux_8916.priv = pll_res; + indirect_path_div2_clk_8916.priv = pll_res; + analog_postdiv_clk_8916.priv = pll_res; + dsi_vco_clk_8916.priv = pll_res; + pll_res->vco_delay = VCO_DELAY_USEC; + + /* Set clock source operations */ + pixel_clk_src_ops = clk_ops_slave_div; + pixel_clk_src_ops.prepare = dsi_pll_div_prepare; + + analog_postdiv_clk_ops = clk_ops_div; + analog_postdiv_clk_ops.prepare = dsi_pll_div_prepare; + + byte_clk_src_ops = clk_ops_div; + byte_clk_src_ops.prepare = dsi_pll_div_prepare; + + byte_mux_clk_ops = clk_ops_gen_mux; + byte_mux_clk_ops.prepare = dsi_pll_mux_prepare; + + if (pll_res->target_id == MDSS_PLL_TARGET_8916 || + pll_res->target_id == MDSS_PLL_TARGET_8939 || + pll_res->target_id == MDSS_PLL_TARGET_8909) { + rc = of_msm_clock_register(pdev->dev.of_node, + mdss_dsi_pllcc_8916, ARRAY_SIZE(mdss_dsi_pllcc_8916)); + if (rc) { + pr_err("Clock register failed\n"); + rc = -EPROBE_DEFER; + } + } else { + pr_err("Invalid target ID\n"); + rc = -EINVAL; + } + + if (!rc) + pr_info("Registered DSI PLL clocks successfully\n"); + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-8996-util.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-8996-util.c new file mode 100644 index 0000000000000000000000000000000000000000..afe57f394f60fcaca0c7d56a0bfc4c9cf6fce64d --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-8996-util.c @@ -0,0 +1,1137 @@ +/* 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" +#include "mdss-dsi-pll-8996.h" + +#define DSI_PLL_POLL_MAX_READS 15 +#define DSI_PLL_POLL_TIMEOUT_US 1000 +#define MSM8996_DSI_PLL_REVISION_2 2 + +#define CEIL(x, y) (((x) + ((y)-1)) / (y)) + +int set_mdss_byte_mux_sel_8996(struct mux_clk *clk, int sel) +{ + return 0; +} + +int get_mdss_byte_mux_sel_8996(struct mux_clk *clk) +{ + return 0; +} + +int set_mdss_pixel_mux_sel_8996(struct mux_clk *clk, int sel) +{ + return 0; +} + +int get_mdss_pixel_mux_sel_8996(struct mux_clk *clk) +{ + return 0; +} + +static int mdss_pll_read_stored_trim_codes( + struct mdss_pll_resources *dsi_pll_res, s64 vco_clk_rate) +{ + int i; + int rc = 0; + bool found = false; + + if (!dsi_pll_res->dfps) { + rc = -EINVAL; + goto end_read; + } + + for (i = 0; i < dsi_pll_res->dfps->panel_dfps.frame_rate_cnt; i++) { + struct dfps_codes_info *codes_info = + &dsi_pll_res->dfps->codes_dfps[i]; + + pr_debug("valid=%d frame_rate=%d, vco_rate=%d, code %d %d\n", + codes_info->is_valid, codes_info->frame_rate, + codes_info->clk_rate, codes_info->pll_codes.pll_codes_1, + codes_info->pll_codes.pll_codes_2); + + if (vco_clk_rate != codes_info->clk_rate && + codes_info->is_valid) + continue; + + dsi_pll_res->cache_pll_trim_codes[0] = + codes_info->pll_codes.pll_codes_1; + dsi_pll_res->cache_pll_trim_codes[1] = + codes_info->pll_codes.pll_codes_2; + found = true; + break; + } + + if (!found) { + rc = -EINVAL; + goto end_read; + } + + pr_debug("core_kvco_code=0x%x core_vco_tune=0x%x\n", + dsi_pll_res->cache_pll_trim_codes[0], + dsi_pll_res->cache_pll_trim_codes[1]); + +end_read: + return rc; +} + +int post_n1_div_set_div(struct div_clk *clk, int div) +{ + struct mdss_pll_resources *pll = clk->priv; + struct dsi_pll_db *pdb; + struct dsi_pll_output *pout; + int rc; + u32 n1div = 0; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + pdb = (struct dsi_pll_db *)pll->priv; + pout = &pdb->out; + + /* + * vco rate = bit_clk * postdiv * n1div + * vco range from 1300 to 2600 Mhz + * postdiv = 1 + * n1div = 1 to 15 + * n1div = roundup(1300Mhz / bit_clk) + * support bit_clk above 86.67Mhz + */ + + /* this is for vco/bit clock */ + pout->pll_postdiv = 1; /* fixed, divided by 1 */ + pout->pll_n1div = div; + + n1div = MDSS_PLL_REG_R(pll->pll_base, DSIPHY_CMN_CLK_CFG0); + n1div &= ~0xf; + n1div |= (div & 0xf); + MDSS_PLL_REG_W(pll->pll_base, DSIPHY_CMN_CLK_CFG0, n1div); + /* ensure n1 divider is programed */ + wmb(); + pr_debug("ndx=%d div=%d postdiv=%x n1div=%x\n", + pll->index, div, pout->pll_postdiv, pout->pll_n1div); + + mdss_pll_resource_enable(pll, false); + + return 0; +} + +int post_n1_div_get_div(struct div_clk *clk) +{ + u32 div; + int rc; + struct mdss_pll_resources *pll = clk->priv; + + if (is_gdsc_disabled(pll)) + return 0; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + /* + * postdiv = 1/2/4/8 + * n1div = 1 - 15 + * fot the time being, assume postdiv = 1 + */ + + div = MDSS_PLL_REG_R(pll->pll_base, DSIPHY_CMN_CLK_CFG0); + div &= 0xF; + pr_debug("n1 div = %d\n", div); + + mdss_pll_resource_enable(pll, false); + + return div; +} + +int n2_div_set_div(struct div_clk *clk, int div) +{ + int rc; + u32 n2div; + struct mdss_pll_resources *pll = clk->priv; + struct dsi_pll_db *pdb; + struct dsi_pll_output *pout; + struct mdss_pll_resources *slave; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + pdb = (struct dsi_pll_db *)pll->priv; + pout = &pdb->out; + + /* this is for pixel clock */ + n2div = MDSS_PLL_REG_R(pll->pll_base, DSIPHY_CMN_CLK_CFG0); + n2div &= ~0xf0; /* bits 4 to 7 */ + n2div |= (div << 4); + MDSS_PLL_REG_W(pll->pll_base, DSIPHY_CMN_CLK_CFG0, n2div); + + /* commit slave if split display is enabled */ + slave = pll->slave; + if (slave) + MDSS_PLL_REG_W(slave->pll_base, DSIPHY_CMN_CLK_CFG0, n2div); + + pout->pll_n2div = div; + + /* set dsiclk_sel=1 so that n2div *= 2 */ + MDSS_PLL_REG_W(pll->pll_base, DSIPHY_CMN_CLK_CFG1, 1); + pr_debug("ndx=%d div=%d n2div=%x\n", pll->index, div, n2div); + + mdss_pll_resource_enable(pll, false); + + return rc; +} + +int shadow_n2_div_set_div(struct div_clk *clk, int div) +{ + struct mdss_pll_resources *pll = clk->priv; + struct dsi_pll_db *pdb; + struct dsi_pll_output *pout; + u32 data; + + pdb = pll->priv; + pout = &pdb->out; + + pout->pll_n2div = div; + + data = (pout->pll_n1div | (pout->pll_n2div << 4)); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL19, + DSIPHY_CMN_CLK_CFG0, DSIPHY_CMN_CLK_CFG1, + data, 1); + return 0; +} + +int n2_div_get_div(struct div_clk *clk) +{ + int rc; + u32 n2div; + struct mdss_pll_resources *pll = clk->priv; + + if (is_gdsc_disabled(pll)) + return 0; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll=%d resources\n", + pll->index); + return rc; + } + + n2div = MDSS_PLL_REG_R(pll->pll_base, DSIPHY_CMN_CLK_CFG0); + n2div >>= 4; + n2div &= 0x0f; + + mdss_pll_resource_enable(pll, false); + + pr_debug("ndx=%d div=%d\n", pll->index, n2div); + + return n2div; +} + +static bool pll_is_pll_locked_8996(struct mdss_pll_resources *pll) +{ + u32 status; + bool pll_locked; + + /* poll for PLL ready status */ + if (readl_poll_timeout_atomic((pll->pll_base + + DSIPHY_PLL_RESET_SM_READY_STATUS), + status, + ((status & BIT(5)) > 0), + DSI_PLL_POLL_MAX_READS, + DSI_PLL_POLL_TIMEOUT_US)) { + pr_err("DSI PLL ndx=%d status=%x failed to Lock\n", + pll->index, status); + pll_locked = false; + } else if (readl_poll_timeout_atomic((pll->pll_base + + DSIPHY_PLL_RESET_SM_READY_STATUS), + status, + ((status & BIT(0)) > 0), + DSI_PLL_POLL_MAX_READS, + DSI_PLL_POLL_TIMEOUT_US)) { + pr_err("DSI PLL ndx=%d status=%x PLl not ready\n", + pll->index, status); + pll_locked = false; + } else { + pll_locked = true; + } + + return pll_locked; +} + +static void dsi_pll_start_8996(void __iomem *pll_base) +{ + pr_debug("start PLL at base=%p\n", pll_base); + + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_VREF_CFG1, 0x10); + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_PLL_CNTRL, 1); +} + +static void dsi_pll_stop_8996(void __iomem *pll_base) +{ + pr_debug("stop PLL at base=%p\n", pll_base); + + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_PLL_CNTRL, 0); +} + +int dsi_pll_enable_seq_8996(struct mdss_pll_resources *pll) +{ + int rc = 0; + + if (!pll) { + pr_err("Invalid PLL resources\n"); + return -EINVAL; + } + + dsi_pll_start_8996(pll->pll_base); + + /* + * both DSIPHY_PLL_CLKBUFLR_EN and DSIPHY_CMN_GLBL_TEST_CTRL + * enabled at mdss_dsi_8996_phy_config() + */ + + if (!pll_is_pll_locked_8996(pll)) { + pr_err("DSI PLL ndx=%d lock failed\n", pll->index); + rc = -EINVAL; + goto init_lock_err; + } + + pr_debug("DSI PLL ndx=%d Lock success\n", pll->index); + +init_lock_err: + return rc; +} + +static int dsi_pll_enable(struct clk *c) +{ + int i, rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + + /* Try all enable sequences until one succeeds */ + for (i = 0; i < vco->pll_en_seq_cnt; i++) { + rc = vco->pll_enable_seqs[i](pll); + pr_debug("DSI PLL %s after sequence #%d\n", + rc ? "unlocked" : "locked", i + 1); + if (!rc) + break; + } + + if (rc) + pr_err("ndx=%d DSI PLL failed to lock\n", pll->index); + else + pll->pll_on = true; + + return rc; +} + +static void dsi_pll_disable(struct clk *c) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + struct mdss_pll_resources *slave; + + if (!pll->pll_on && + mdss_pll_resource_enable(pll, true)) { + pr_err("Failed to enable mdss dsi pll=%d\n", pll->index); + return; + } + + pll->handoff_resources = false; + slave = pll->slave; + + dsi_pll_stop_8996(pll->pll_base); + + mdss_pll_resource_enable(pll, false); + + pll->pll_on = false; + + pr_debug("DSI PLL ndx=%d Disabled\n", pll->index); +} + +static void mdss_dsi_pll_8996_input_init(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + pdb->in.fref = 19200000; /* 19.2 Mhz*/ + pdb->in.fdata = 0; /* bit clock rate */ + pdb->in.dsiclk_sel = 1; /* 1, reg: 0x0014 */ + pdb->in.ssc_en = pll->ssc_en; /* 1, reg: 0x0494, bit 0 */ + pdb->in.ldo_en = 0; /* 0, reg: 0x004c, bit 0 */ + + /* fixed input */ + pdb->in.refclk_dbler_en = 0; /* 0, reg: 0x04c0, bit 1 */ + pdb->in.vco_measure_time = 5; /* 5, unknown */ + pdb->in.kvco_measure_time = 5; /* 5, unknown */ + pdb->in.bandgap_timer = 4; /* 4, reg: 0x0430, bit 3 - 5 */ + pdb->in.pll_wakeup_timer = 5; /* 5, reg: 0x043c, bit 0 - 2 */ + pdb->in.plllock_cnt = 1; /* 1, reg: 0x0488, bit 1 - 2 */ + pdb->in.plllock_rng = 0; /* 0, reg: 0x0488, bit 3 - 4 */ + pdb->in.ssc_center = pll->ssc_center;/* 0, reg: 0x0494, bit 1 */ + pdb->in.ssc_adj_period = 37; /* 37, reg: 0x498, bit 0 - 9 */ + pdb->in.ssc_spread = pll->ssc_ppm / 1000; + pdb->in.ssc_freq = pll->ssc_freq; + + pdb->in.pll_ie_trim = 4; /* 4, reg: 0x0400 */ + pdb->in.pll_ip_trim = 4; /* 4, reg: 0x0404 */ + pdb->in.pll_cpcset_cur = 1; /* 1, reg: 0x04f0, bit 0 - 2 */ + pdb->in.pll_cpmset_cur = 1; /* 1, reg: 0x04f0, bit 3 - 5 */ + pdb->in.pll_icpmset = 4; /* 4, reg: 0x04fc, bit 3 - 5 */ + pdb->in.pll_icpcset = 4; /* 4, reg: 0x04fc, bit 0 - 2 */ + pdb->in.pll_icpmset_p = 0; /* 0, reg: 0x04f4, bit 0 - 2 */ + pdb->in.pll_icpmset_m = 0; /* 0, reg: 0x04f4, bit 3 - 5 */ + pdb->in.pll_icpcset_p = 0; /* 0, reg: 0x04f8, bit 0 - 2 */ + pdb->in.pll_icpcset_m = 0; /* 0, reg: 0x04f8, bit 3 - 5 */ + pdb->in.pll_lpf_res1 = 3; /* 3, reg: 0x0504, bit 0 - 3 */ + pdb->in.pll_lpf_cap1 = 11; /* 11, reg: 0x0500, bit 0 - 3 */ + pdb->in.pll_lpf_cap2 = 1; /* 1, reg: 0x0500, bit 4 - 7 */ + pdb->in.pll_iptat_trim = 7; + pdb->in.pll_c3ctrl = 2; /* 2 */ + pdb->in.pll_r3ctrl = 1; /* 1 */ +} + +static void pll_8996_ssc_calc(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + u32 period, ssc_period; + u32 ref, rem; + s64 step_size; + + pr_debug("%s: vco=%lld ref=%lld\n", __func__, + pll->vco_current_rate, pll->vco_ref_clk_rate); + + ssc_period = pdb->in.ssc_freq / 500; + period = (unsigned long)pll->vco_ref_clk_rate / 1000; + ssc_period = CEIL(period, ssc_period); + ssc_period -= 1; + pdb->out.ssc_period = ssc_period; + + pr_debug("%s: ssc, freq=%d spread=%d period=%d\n", __func__, + pdb->in.ssc_freq, pdb->in.ssc_spread, pdb->out.ssc_period); + + step_size = (u32)pll->vco_current_rate; + ref = pll->vco_ref_clk_rate; + ref /= 1000; + step_size = div_s64(step_size, ref); + step_size <<= 20; + step_size = div_s64(step_size, 1000); + step_size *= pdb->in.ssc_spread; + step_size = div_s64(step_size, 1000); + step_size *= (pdb->in.ssc_adj_period + 1); + + rem = 0; + step_size = div_s64_rem(step_size, ssc_period + 1, &rem); + if (rem) + step_size++; + + pr_debug("%s: step_size=%lld\n", __func__, step_size); + + step_size &= 0x0ffff; /* take lower 16 bits */ + + pdb->out.ssc_step_size = step_size; +} + +static void pll_8996_dec_frac_calc(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + struct dsi_pll_input *pin = &pdb->in; + struct dsi_pll_output *pout = &pdb->out; + s64 multiplier = BIT(20); + s64 dec_start_multiple, dec_start, pll_comp_val; + s32 duration, div_frac_start; + s64 vco_clk_rate = pll->vco_current_rate; + s64 fref = pll->vco_ref_clk_rate; + + pr_debug("vco_clk_rate=%lld ref_clk_rate=%lld\n", + vco_clk_rate, fref); + + dec_start_multiple = div_s64(vco_clk_rate * multiplier, fref); + div_s64_rem(dec_start_multiple, multiplier, &div_frac_start); + + dec_start = div_s64(dec_start_multiple, multiplier); + + pout->dec_start = (u32)dec_start; + pout->div_frac_start = div_frac_start; + + if (pin->plllock_cnt == 0) + duration = 1024; + else if (pin->plllock_cnt == 1) + duration = 256; + else if (pin->plllock_cnt == 2) + duration = 128; + else + duration = 32; + + pll_comp_val = duration * dec_start_multiple; + pll_comp_val = div_s64(pll_comp_val, multiplier); + do_div(pll_comp_val, 10); + + pout->plllock_cmp = (u32)pll_comp_val; + + pout->pll_txclk_en = 1; + if (pll->revision == MSM8996_DSI_PLL_REVISION_2) + pout->cmn_ldo_cntrl = 0x3c; + else + pout->cmn_ldo_cntrl = 0x1c; +} + +static u32 pll_8996_kvco_slop(u32 vrate) +{ + u32 slop = 0; + + if (vrate > 1300000000UL && vrate <= 1800000000UL) + slop = 600; + else if (vrate > 1800000000UL && vrate < 2300000000UL) + slop = 400; + else if (vrate > 2300000000UL && vrate < 2600000000UL) + slop = 280; + + return slop; +} + +static void pll_8996_calc_vco_count(struct dsi_pll_db *pdb, + s64 vco_clk_rate, s64 fref) +{ + struct dsi_pll_input *pin = &pdb->in; + struct dsi_pll_output *pout = &pdb->out; + s64 data; + u32 cnt; + + data = fref * pin->vco_measure_time; + do_div(data, 1000000); + data &= 0x03ff; /* 10 bits */ + data -= 2; + pout->pll_vco_div_ref = data; + + data = (unsigned long)vco_clk_rate / 1000000; /* unit is Mhz */ + data *= pin->vco_measure_time; + do_div(data, 10); + pout->pll_vco_count = data; /* reg: 0x0474, 0x0478 */ + + data = fref * pin->kvco_measure_time; + do_div(data, 1000000); + data &= 0x03ff; /* 10 bits */ + data -= 1; + pout->pll_kvco_div_ref = data; + + cnt = pll_8996_kvco_slop(vco_clk_rate); + cnt *= 2; + do_div(cnt, 100); + cnt *= pin->kvco_measure_time; + pout->pll_kvco_count = cnt; + + pout->pll_misc1 = 16; + pout->pll_resetsm_cntrl = 48; + pout->pll_resetsm_cntrl2 = pin->bandgap_timer << 3; + pout->pll_resetsm_cntrl5 = pin->pll_wakeup_timer; + pout->pll_kvco_code = 0; +} + +static void pll_db_commit_ssc(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + void __iomem *pll_base = pll->pll_base; + struct dsi_pll_input *pin = &pdb->in; + struct dsi_pll_output *pout = &pdb->out; + char data; + + data = pin->ssc_adj_period; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_ADJ_PER1, data); + data = (pin->ssc_adj_period >> 8); + data &= 0x03; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_ADJ_PER2, data); + + data = pout->ssc_period; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_PER1, data); + data = (pout->ssc_period >> 8); + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_PER2, data); + + data = pout->ssc_step_size; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_STEP_SIZE1, data); + data = (pout->ssc_step_size >> 8); + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_STEP_SIZE2, data); + + data = (pin->ssc_center & 0x01); + data <<= 1; + data |= 0x01; /* enable */ + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SSC_EN_CENTER, data); + + wmb(); /* make sure register committed */ +} + +static void pll_db_commit_common(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + void __iomem *pll_base = pll->pll_base; + struct dsi_pll_input *pin = &pdb->in; + struct dsi_pll_output *pout = &pdb->out; + char data; + + /* confgiure the non frequency dependent pll registers */ + data = 0; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_SYSCLK_EN_RESET, data); + + /* DSIPHY_PLL_CLKBUFLR_EN updated at dsi phy */ + + data = pout->pll_txclk_en; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_TXCLK_EN, data); + + data = pout->pll_resetsm_cntrl; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_RESETSM_CNTRL, data); + data = pout->pll_resetsm_cntrl2; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_RESETSM_CNTRL2, data); + data = pout->pll_resetsm_cntrl5; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_RESETSM_CNTRL5, data); + + data = pout->pll_vco_div_ref; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_VCO_DIV_REF1, data); + data = (pout->pll_vco_div_ref >> 8); + data &= 0x03; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_VCO_DIV_REF2, data); + + data = pout->pll_kvco_div_ref; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_KVCO_DIV_REF1, data); + data = (pout->pll_kvco_div_ref >> 8); + data &= 0x03; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_KVCO_DIV_REF2, data); + + data = pout->pll_misc1; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_MISC1, data); + + data = pin->pll_ie_trim; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_IE_TRIM, data); + + data = pin->pll_ip_trim; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_IP_TRIM, data); + + data = ((pin->pll_cpmset_cur << 3) | pin->pll_cpcset_cur); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_CP_SET_CUR, data); + + data = ((pin->pll_icpcset_p << 3) | pin->pll_icpcset_m); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_ICPCSET, data); + + data = ((pin->pll_icpmset_p << 3) | pin->pll_icpcset_m); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_ICPMSET, data); + + data = ((pin->pll_icpmset << 3) | pin->pll_icpcset); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_ICP_SET, data); + + data = ((pdb->in.pll_lpf_cap2 << 4) | pdb->in.pll_lpf_cap1); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_LPF1, data); + + data = pin->pll_iptat_trim; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_IPTAT_TRIM, data); + + data = (pdb->in.pll_c3ctrl | (pdb->in.pll_r3ctrl << 4)); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_CRCTRL, data); +} + +static void pll_db_commit_8996(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + void __iomem *pll_base = pll->pll_base; + struct dsi_pll_input *pin = &pdb->in; + struct dsi_pll_output *pout = &pdb->out; + char data; + + data = pout->cmn_ldo_cntrl; + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_LDO_CNTRL, data); + + pll_db_commit_common(pll, pdb); + + /* de assert pll start and apply pll sw reset */ + /* stop pll */ + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_PLL_CNTRL, 0); + + /* pll sw reset */ + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_CTRL_1, 0x20); + wmb(); /* make sure register committed */ + udelay(10); + + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_CTRL_1, 0); + wmb(); /* make sure register committed */ + + data = pdb->in.dsiclk_sel; /* set dsiclk_sel = 1 */ + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_CLK_CFG1, data); + + data = 0xff; /* data, clk, pll normal operation */ + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_CTRL_0, data); + + /* confgiure the frequency dependent pll registers */ + data = pout->dec_start; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_DEC_START, data); + + data = pout->div_frac_start; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_DIV_FRAC_START1, data); + data = (pout->div_frac_start >> 8); + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_DIV_FRAC_START2, data); + data = (pout->div_frac_start >> 16); + data &= 0x0f; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_DIV_FRAC_START3, data); + + data = pout->plllock_cmp; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLLLOCK_CMP1, data); + data = (pout->plllock_cmp >> 8); + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLLLOCK_CMP2, data); + data = (pout->plllock_cmp >> 16); + data &= 0x03; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLLLOCK_CMP3, data); + + data = ((pin->plllock_cnt << 1) | (pin->plllock_rng << 3)); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLLLOCK_CMP_EN, data); + + data = pout->pll_vco_count; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_VCO_COUNT1, data); + data = (pout->pll_vco_count >> 8); + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_VCO_COUNT2, data); + + data = pout->pll_kvco_count; + data &= 0x0ff; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_KVCO_COUNT1, data); + data = (pout->pll_kvco_count >> 8); + data &= 0x03; + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_KVCO_COUNT2, data); + + /* + * tx_band = pll_postdiv + * 0: divided by 1 <== for now + * 1: divided by 2 + * 2: divided by 4 + * 3: divided by 8 + */ + data = (((pout->pll_postdiv - 1) << 4) | pdb->in.pll_lpf_res1); + MDSS_PLL_REG_W(pll_base, DSIPHY_PLL_PLL_LPF2_POSTDIV, data); + + data = (pout->pll_n1div | (pout->pll_n2div << 4)); + MDSS_PLL_REG_W(pll_base, DSIPHY_CMN_CLK_CFG0, data); + + if (pll->ssc_en) + pll_db_commit_ssc(pll, pdb); + + wmb(); /* make sure register committed */ +} + +/* + * pll_source_finding: + * Both GLBL_TEST_CTRL and CLKBUFLR_EN are configured + * at mdss_dsi_8996_phy_config() + */ +static int pll_source_finding(struct mdss_pll_resources *pll) +{ + u32 clk_buf_en; + u32 glbl_test_ctrl; + + glbl_test_ctrl = MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_CMN_GLBL_TEST_CTRL); + clk_buf_en = MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_CLKBUFLR_EN); + + glbl_test_ctrl &= BIT(2); + glbl_test_ctrl >>= 2; + + pr_debug("%s: pll=%d clk_buf_en=%x glbl_test_ctrl=%x\n", + __func__, pll->index, clk_buf_en, glbl_test_ctrl); + + clk_buf_en &= (PLL_OUTPUT_RIGHT | PLL_OUTPUT_LEFT); + + if ((glbl_test_ctrl == PLL_SOURCE_FROM_LEFT) && + (clk_buf_en == PLL_OUTPUT_BOTH)) + return PLL_MASTER; + + if ((glbl_test_ctrl == PLL_SOURCE_FROM_RIGHT) && + (clk_buf_en == PLL_OUTPUT_NONE)) + return PLL_SLAVE; + + if ((glbl_test_ctrl == PLL_SOURCE_FROM_LEFT) && + (clk_buf_en == PLL_OUTPUT_RIGHT)) + return PLL_STANDALONE; + + pr_debug("%s: Error pll setup, clk_buf_en=%x glbl_test_ctrl=%x\n", + __func__, clk_buf_en, glbl_test_ctrl); + + return PLL_UNKNOWN; +} + +static void pll_source_setup(struct mdss_pll_resources *pll) +{ + int status; + struct dsi_pll_db *pdb = (struct dsi_pll_db *)pll->priv; + struct mdss_pll_resources *other; + + if (pdb->source_setup_done) + return; + + pdb->source_setup_done++; + + status = pll_source_finding(pll); + + if (status == PLL_STANDALONE || status == PLL_UNKNOWN) + return; + + other = pdb->next->pll; + if (!other) + return; + + pr_debug("%s: status=%d pll=%d other=%d\n", __func__, + status, pll->index, other->index); + + if (status == PLL_MASTER) + pll->slave = other; + else + other->slave = pll; +} + +int pll_vco_set_rate_8996(struct clk *c, unsigned long rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + struct mdss_pll_resources *slave; + struct dsi_pll_db *pdb; + + pdb = (struct dsi_pll_db *)pll->priv; + if (!pdb) { + pr_err("No prov found\n"); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi plla=%d\n", pll->index); + return rc; + } + + pll_source_setup(pll); + + pr_debug("%s: ndx=%d base=%p rate=%lu slave=%p\n", __func__, + pll->index, pll->pll_base, rate, pll->slave); + + pll->vco_current_rate = rate; + pll->vco_ref_clk_rate = vco->ref_clk_rate; + + mdss_dsi_pll_8996_input_init(pll, pdb); + + pll_8996_dec_frac_calc(pll, pdb); + + if (pll->ssc_en) + pll_8996_ssc_calc(pll, pdb); + + pll_8996_calc_vco_count(pdb, pll->vco_current_rate, + pll->vco_ref_clk_rate); + + /* commit slave if split display is enabled */ + slave = pll->slave; + if (slave) + pll_db_commit_8996(slave, pdb); + + /* commit master itself */ + pll_db_commit_8996(pll, pdb); + + mdss_pll_resource_enable(pll, false); + + return rc; +} + +static void shadow_pll_dynamic_refresh_8996(struct mdss_pll_resources *pll, + struct dsi_pll_db *pdb) +{ + struct dsi_pll_output *pout = &pdb->out; + + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL20, + DSIPHY_CMN_CTRL_0, DSIPHY_PLL_SYSCLK_EN_RESET, + 0xFF, 0x0); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL21, + DSIPHY_PLL_DEC_START, DSIPHY_PLL_DIV_FRAC_START1, + pout->dec_start, (pout->div_frac_start & 0x0FF)); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL22, + DSIPHY_PLL_DIV_FRAC_START2, DSIPHY_PLL_DIV_FRAC_START3, + ((pout->div_frac_start >> 8) & 0x0FF), + ((pout->div_frac_start >> 16) & 0x0F)); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL23, + DSIPHY_PLL_PLLLOCK_CMP1, DSIPHY_PLL_PLLLOCK_CMP2, + (pout->plllock_cmp & 0x0FF), + ((pout->plllock_cmp >> 8) & 0x0FF)); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL24, + DSIPHY_PLL_PLLLOCK_CMP3, DSIPHY_PLL_PLL_VCO_TUNE, + ((pout->plllock_cmp >> 16) & 0x03), + (pll->cache_pll_trim_codes[1] | BIT(7))); /* VCO tune*/ + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL25, + DSIPHY_PLL_KVCO_CODE, DSIPHY_PLL_RESETSM_CNTRL, + (pll->cache_pll_trim_codes[0] | BIT(5)), 0x38); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL26, + DSIPHY_PLL_PLL_LPF2_POSTDIV, DSIPHY_CMN_PLL_CNTRL, + (((pout->pll_postdiv - 1) << 4) | pdb->in.pll_lpf_res1), 0x01); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL27, + DSIPHY_CMN_PLL_CNTRL, DSIPHY_CMN_PLL_CNTRL, + 0x01, 0x01); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL28, + DSIPHY_CMN_PLL_CNTRL, DSIPHY_CMN_PLL_CNTRL, + 0x01, 0x01); + MDSS_DYN_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_CTRL29, + DSIPHY_CMN_PLL_CNTRL, DSIPHY_CMN_PLL_CNTRL, + 0x01, 0x01); + MDSS_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_UPPER_ADDR, 0x0000001E); + MDSS_PLL_REG_W(pll->dyn_pll_base, + DSI_DYNAMIC_REFRESH_PLL_UPPER_ADDR2, 0x001FFE00); + + /* + * Ensure all the dynamic refresh registers are written before + * dynamic refresh to change the fps is triggered + */ + wmb(); +} + +int shadow_pll_vco_set_rate_8996(struct clk *c, unsigned long rate) +{ + int rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + struct dsi_pll_db *pdb; + s64 vco_clk_rate = (s64)rate; + + if (!pll) { + pr_err("PLL data not found\n"); + return -EINVAL; + } + + pdb = pll->priv; + if (!pdb) { + pr_err("No priv data found\n"); + return -EINVAL; + } + + rc = mdss_pll_read_stored_trim_codes(pll, vco_clk_rate); + if (rc) { + pr_err("cannot find pll codes rate=%lld\n", vco_clk_rate); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi plla=%d\n", pll->index); + return rc; + } + + pr_debug("%s: ndx=%d base=%p rate=%lu\n", __func__, + pll->index, pll->pll_base, rate); + + pll->vco_current_rate = rate; + pll->vco_ref_clk_rate = vco->ref_clk_rate; + + mdss_dsi_pll_8996_input_init(pll, pdb); + + pll_8996_dec_frac_calc(pll, pdb); + + pll_8996_calc_vco_count(pdb, pll->vco_current_rate, + pll->vco_ref_clk_rate); + + shadow_pll_dynamic_refresh_8996(pll, pdb); + + rc = mdss_pll_resource_enable(pll, false); + if (rc) { + pr_err("Failed to enable mdss dsi plla=%d\n", pll->index); + return rc; + } + + return rc; +} + +unsigned long pll_vco_get_rate_8996(struct clk *c) +{ + u64 vco_rate, multiplier = BIT(20); + s32 div_frac_start; + u32 dec_start; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + u64 ref_clk = vco->ref_clk_rate; + int rc; + struct mdss_pll_resources *pll = vco->priv; + + if (is_gdsc_disabled(pll)) + return 0; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll=%d\n", pll->index); + return rc; + } + + dec_start = MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_DEC_START); + dec_start &= 0x0ff; + pr_debug("dec_start = 0x%x\n", dec_start); + + div_frac_start = (MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_DIV_FRAC_START3) & 0x0f) << 16; + div_frac_start |= (MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_DIV_FRAC_START2) & 0x0ff) << 8; + div_frac_start |= MDSS_PLL_REG_R(pll->pll_base, + DSIPHY_PLL_DIV_FRAC_START1) & 0x0ff; + pr_debug("div_frac_start = 0x%x\n", div_frac_start); + + vco_rate = ref_clk * dec_start; + vco_rate += ((ref_clk * div_frac_start) / multiplier); + + pr_debug("returning vco rate = %lu\n", (unsigned long)vco_rate); + + mdss_pll_resource_enable(pll, false); + + return (unsigned long)vco_rate; +} + +long pll_vco_round_rate_8996(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + u32 div; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + + div = vco->min_rate / rate; + if (div > 15) { + /* rate < 86.67 Mhz */ + pr_err("rate=%lu NOT supportted\n", rate); + return -EINVAL; + } + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + return rrate; +} + +enum handoff pll_vco_handoff_8996(struct clk *c) +{ + int rc; + enum handoff ret = HANDOFF_DISABLED_CLK; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + + if (is_gdsc_disabled(pll)) + return HANDOFF_DISABLED_CLK; + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll=%d\n", pll->index); + return ret; + } + + if (pll_is_pll_locked_8996(pll)) { + pll->handoff_resources = true; + pll->pll_on = true; + c->rate = pll_vco_get_rate_8996(c); + ret = HANDOFF_ENABLED_CLK; + } else { + mdss_pll_resource_enable(pll, false); + } + + return ret; +} + +enum handoff shadow_pll_vco_handoff_8996(struct clk *c) +{ + return HANDOFF_DISABLED_CLK; +} + +int pll_vco_prepare_8996(struct clk *c) +{ + int rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + + if (!pll) { + pr_err("Dsi pll resources are not available\n"); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(pll, true); + if (rc) { + pr_err("ndx=%d Failed to enable mdss dsi pll resources\n", + pll->index); + return rc; + } + + if ((pll->vco_cached_rate != 0) + && (pll->vco_cached_rate == c->rate)) { + rc = c->ops->set_rate(c, pll->vco_cached_rate); + if (rc) { + pr_err("index=%d vco_set_rate failed. rc=%d\n", + rc, pll->index); + mdss_pll_resource_enable(pll, false); + goto error; + } + } + + rc = dsi_pll_enable(c); + + if (rc) { + mdss_pll_resource_enable(pll, false); + pr_err("ndx=%d failed to enable dsi pll\n", pll->index); + } + +error: + return rc; +} + +void pll_vco_unprepare_8996(struct clk *c) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *pll = vco->priv; + + if (!pll) { + pr_err("Dsi pll resources are not available\n"); + return; + } + + pll->vco_cached_rate = c->rate; + dsi_pll_disable(c); +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-8996.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-8996.c new file mode 100644 index 0000000000000000000000000000000000000000..e9e484c3cc9fde2a4f5d0e97f8e57f14cb2f1fff --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-8996.c @@ -0,0 +1,566 @@ +/* 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. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" +#include "mdss-dsi-pll-8996.h" + +#define VCO_DELAY_USEC 1 + +static struct dsi_pll_db pll_db[DSI_PLL_NUM]; + +static const struct clk_ops n2_clk_src_ops; +static const struct clk_ops shadow_n2_clk_src_ops; +static const struct clk_ops byte_clk_src_ops; +static const struct clk_ops post_n1_div_clk_src_ops; +static const struct clk_ops shadow_post_n1_div_clk_src_ops; + +static const struct clk_ops clk_ops_gen_mux_dsi; + +/* Op structures */ +static const struct clk_ops clk_ops_dsi_vco = { + .set_rate = pll_vco_set_rate_8996, + .round_rate = pll_vco_round_rate_8996, + .handoff = pll_vco_handoff_8996, + .prepare = pll_vco_prepare_8996, + .unprepare = pll_vco_unprepare_8996, +}; + +static struct clk_div_ops post_n1_div_ops = { + .set_div = post_n1_div_set_div, + .get_div = post_n1_div_get_div, +}; + +static struct clk_div_ops n2_div_ops = { /* hr_oclk3 */ + .set_div = n2_div_set_div, + .get_div = n2_div_get_div, +}; + +static struct clk_mux_ops mdss_byte_mux_ops = { + .set_mux_sel = set_mdss_byte_mux_sel_8996, + .get_mux_sel = get_mdss_byte_mux_sel_8996, +}; + +static struct clk_mux_ops mdss_pixel_mux_ops = { + .set_mux_sel = set_mdss_pixel_mux_sel_8996, + .get_mux_sel = get_mdss_pixel_mux_sel_8996, +}; + +/* Shadow ops for dynamic refresh */ +static const struct clk_ops clk_ops_shadow_dsi_vco = { + .set_rate = shadow_pll_vco_set_rate_8996, + .round_rate = pll_vco_round_rate_8996, + .handoff = shadow_pll_vco_handoff_8996, +}; + +static struct clk_div_ops shadow_post_n1_div_ops = { + .set_div = post_n1_div_set_div, +}; + +static struct clk_div_ops shadow_n2_div_ops = { + .set_div = shadow_n2_div_set_div, +}; + +static struct dsi_pll_vco_clk dsi0pll_vco_clk = { + .ref_clk_rate = 19200000UL, + .min_rate = 1300000000UL, + .max_rate = 2600000000UL, + .pll_en_seq_cnt = 1, + .pll_enable_seqs[0] = dsi_pll_enable_seq_8996, + .c = { + .dbg_name = "dsi0pll_vco_clk_8996", + .ops = &clk_ops_dsi_vco, + CLK_INIT(dsi0pll_vco_clk.c), + }, +}; + +static struct dsi_pll_vco_clk dsi0pll_shadow_vco_clk = { + .ref_clk_rate = 19200000u, + .min_rate = 1300000000u, + .max_rate = 2600000000u, + .c = { + .dbg_name = "dsi0pll_shadow_vco_clk", + .ops = &clk_ops_shadow_dsi_vco, + CLK_INIT(dsi0pll_shadow_vco_clk.c), + }, +}; + +static struct dsi_pll_vco_clk dsi1pll_vco_clk = { + .ref_clk_rate = 19200000UL, + .min_rate = 1300000000UL, + .max_rate = 2600000000UL, + .pll_en_seq_cnt = 1, + .pll_enable_seqs[0] = dsi_pll_enable_seq_8996, + .c = { + .dbg_name = "dsi1pll_vco_clk_8996", + .ops = &clk_ops_dsi_vco, + CLK_INIT(dsi1pll_vco_clk.c), + }, +}; + +static struct dsi_pll_vco_clk dsi1pll_shadow_vco_clk = { + .ref_clk_rate = 19200000u, + .min_rate = 1300000000u, + .max_rate = 2600000000u, + .pll_en_seq_cnt = 1, + .pll_enable_seqs[0] = dsi_pll_enable_seq_8996, + .c = { + .dbg_name = "dsi1pll_shadow_vco_clk", + .ops = &clk_ops_shadow_dsi_vco, + CLK_INIT(dsi1pll_shadow_vco_clk.c), + }, +}; + +static struct div_clk dsi0pll_post_n1_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &post_n1_div_ops, + .c = { + .parent = &dsi0pll_vco_clk.c, + .dbg_name = "dsi0pll_post_n1_div_clk", + .ops = &post_n1_div_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_post_n1_div_clk.c), + }, +}; + +static struct div_clk dsi0pll_shadow_post_n1_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &shadow_post_n1_div_ops, + .c = { + .parent = &dsi0pll_shadow_vco_clk.c, + .dbg_name = "dsi0pll_shadow_post_n1_div_clk", + .ops = &shadow_post_n1_div_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_shadow_post_n1_div_clk.c), + }, +}; + +static struct div_clk dsi1pll_post_n1_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &post_n1_div_ops, + .c = { + .parent = &dsi1pll_vco_clk.c, + .dbg_name = "dsi1pll_post_n1_div_clk", + .ops = &post_n1_div_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_post_n1_div_clk.c), + }, +}; + +static struct div_clk dsi1pll_shadow_post_n1_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &shadow_post_n1_div_ops, + .c = { + .parent = &dsi1pll_shadow_vco_clk.c, + .dbg_name = "dsi1pll_shadow_post_n1_div_clk", + .ops = &shadow_post_n1_div_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_shadow_post_n1_div_clk.c), + }, +}; + +static struct div_clk dsi0pll_n2_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &n2_div_ops, + .c = { + .parent = &dsi0pll_post_n1_div_clk.c, + .dbg_name = "dsi0pll_n2_div_clk", + .ops = &n2_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_n2_div_clk.c), + }, +}; + +static struct div_clk dsi0pll_shadow_n2_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &shadow_n2_div_ops, + .c = { + .parent = &dsi0pll_shadow_post_n1_div_clk.c, + .dbg_name = "dsi0pll_shadow_n2_div_clk", + .ops = &shadow_n2_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_shadow_n2_div_clk.c), + }, +}; + +static struct div_clk dsi1pll_n2_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &n2_div_ops, + .c = { + .parent = &dsi1pll_post_n1_div_clk.c, + .dbg_name = "dsi1pll_n2_div_clk", + .ops = &n2_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_n2_div_clk.c), + }, +}; + +static struct div_clk dsi1pll_shadow_n2_div_clk = { + .data = { + .max_div = 15, + .min_div = 1, + }, + .ops = &shadow_n2_div_ops, + .c = { + .parent = &dsi1pll_shadow_post_n1_div_clk.c, + .dbg_name = "dsi1pll_shadow_n2_div_clk", + .ops = &shadow_n2_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_shadow_n2_div_clk.c), + }, +}; + +static struct div_clk dsi0pll_pixel_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &dsi0pll_n2_div_clk.c, + .dbg_name = "dsi0pll_pixel_clk_src", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_pixel_clk_src.c), + }, +}; + +static struct div_clk dsi0pll_shadow_pixel_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &dsi0pll_shadow_n2_div_clk.c, + .dbg_name = "dsi0pll_shadow_pixel_clk_src", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_shadow_pixel_clk_src.c), + }, +}; + +static struct div_clk dsi1pll_pixel_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &dsi1pll_n2_div_clk.c, + .dbg_name = "dsi1pll_pixel_clk_src", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_pixel_clk_src.c), + }, +}; + +static struct div_clk dsi1pll_shadow_pixel_clk_src = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &dsi1pll_shadow_n2_div_clk.c, + .dbg_name = "dsi1pll_shadow_pixel_clk_src", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_shadow_pixel_clk_src.c), + }, +}; + +static struct mux_clk dsi0pll_pixel_clk_mux = { + .num_parents = 2, + .parents = (struct clk_src[]) { + {&dsi0pll_pixel_clk_src.c, 0}, + {&dsi0pll_shadow_pixel_clk_src.c, 1}, + }, + .ops = &mdss_pixel_mux_ops, + .c = { + .parent = &dsi0pll_pixel_clk_src.c, + .dbg_name = "dsi0pll_pixel_clk_mux", + .ops = &clk_ops_gen_mux_dsi, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_pixel_clk_mux.c), + } +}; + +static struct mux_clk dsi1pll_pixel_clk_mux = { + .num_parents = 2, + .parents = (struct clk_src[]) { + {&dsi1pll_pixel_clk_src.c, 0}, + {&dsi1pll_shadow_pixel_clk_src.c, 1}, + }, + .ops = &mdss_pixel_mux_ops, + .c = { + .parent = &dsi1pll_pixel_clk_src.c, + .dbg_name = "dsi1pll_pixel_clk_mux", + .ops = &clk_ops_gen_mux_dsi, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_pixel_clk_mux.c), + } +}; + +static struct div_clk dsi0pll_byte_clk_src = { + .data = { + .div = 8, + .min_div = 8, + .max_div = 8, + }, + .c = { + .parent = &dsi0pll_post_n1_div_clk.c, + .dbg_name = "dsi0pll_byte_clk_src", + .ops = &clk_ops_div, + CLK_INIT(dsi0pll_byte_clk_src.c), + }, +}; + +static struct div_clk dsi0pll_shadow_byte_clk_src = { + .data = { + .div = 8, + .min_div = 8, + .max_div = 8, + }, + .c = { + .parent = &dsi0pll_shadow_post_n1_div_clk.c, + .dbg_name = "dsi0pll_shadow_byte_clk_src", + .ops = &clk_ops_div, + CLK_INIT(dsi0pll_shadow_byte_clk_src.c), + }, +}; + +static struct div_clk dsi1pll_byte_clk_src = { + .data = { + .div = 8, + .min_div = 8, + .max_div = 8, + }, + .c = { + .parent = &dsi1pll_post_n1_div_clk.c, + .dbg_name = "dsi1pll_byte_clk_src", + .ops = &clk_ops_div, + CLK_INIT(dsi1pll_byte_clk_src.c), + }, +}; + +static struct div_clk dsi1pll_shadow_byte_clk_src = { + .data = { + .div = 8, + .min_div = 8, + .max_div = 8, + }, + .c = { + .parent = &dsi1pll_shadow_post_n1_div_clk.c, + .dbg_name = "dsi1pll_shadow_byte_clk_src", + .ops = &clk_ops_div, + CLK_INIT(dsi1pll_shadow_byte_clk_src.c), + }, +}; + +static struct mux_clk dsi0pll_byte_clk_mux = { + .num_parents = 2, + .parents = (struct clk_src[]) { + {&dsi0pll_byte_clk_src.c, 0}, + {&dsi0pll_shadow_byte_clk_src.c, 1}, + }, + .ops = &mdss_byte_mux_ops, + .c = { + .parent = &dsi0pll_byte_clk_src.c, + .dbg_name = "dsi0pll_byte_clk_mux", + .ops = &clk_ops_gen_mux_dsi, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi0pll_byte_clk_mux.c), + } +}; +static struct mux_clk dsi1pll_byte_clk_mux = { + .num_parents = 2, + .parents = (struct clk_src[]) { + {&dsi1pll_byte_clk_src.c, 0}, + {&dsi1pll_shadow_byte_clk_src.c, 1}, + }, + .ops = &mdss_byte_mux_ops, + .c = { + .parent = &dsi1pll_byte_clk_src.c, + .dbg_name = "dsi1pll_byte_clk_mux", + .ops = &clk_ops_gen_mux_dsi, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(dsi1pll_byte_clk_mux.c), + } +}; + +static struct clk_lookup mdss_dsi_pllcc_8996[] = { + CLK_LIST(dsi0pll_byte_clk_mux), + CLK_LIST(dsi0pll_byte_clk_src), + CLK_LIST(dsi0pll_pixel_clk_mux), + CLK_LIST(dsi0pll_pixel_clk_src), + CLK_LIST(dsi0pll_n2_div_clk), + CLK_LIST(dsi0pll_post_n1_div_clk), + CLK_LIST(dsi0pll_vco_clk), + CLK_LIST(dsi0pll_shadow_byte_clk_src), + CLK_LIST(dsi0pll_shadow_pixel_clk_src), + CLK_LIST(dsi0pll_shadow_n2_div_clk), + CLK_LIST(dsi0pll_shadow_post_n1_div_clk), + CLK_LIST(dsi0pll_shadow_vco_clk), +}; + +static struct clk_lookup mdss_dsi_pllcc_8996_1[] = { + CLK_LIST(dsi1pll_byte_clk_mux), + CLK_LIST(dsi1pll_byte_clk_src), + CLK_LIST(dsi1pll_pixel_clk_mux), + CLK_LIST(dsi1pll_pixel_clk_src), + CLK_LIST(dsi1pll_n2_div_clk), + CLK_LIST(dsi1pll_post_n1_div_clk), + CLK_LIST(dsi1pll_vco_clk), + CLK_LIST(dsi1pll_shadow_byte_clk_src), + CLK_LIST(dsi1pll_shadow_pixel_clk_src), + CLK_LIST(dsi1pll_shadow_n2_div_clk), + CLK_LIST(dsi1pll_shadow_post_n1_div_clk), + CLK_LIST(dsi1pll_shadow_vco_clk), +}; + +int dsi_pll_clock_register_8996(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0, ndx; + int const ssc_freq_default = 31500; /* default h/w recommended value */ + int const ssc_ppm_default = 5000; /* default h/w recommended value */ + struct dsi_pll_db *pdb; + + if (!pdev || !pdev->dev.of_node) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + if (!pll_res || !pll_res->pll_base) { + pr_err("Invalid PLL resources\n"); + return -EPROBE_DEFER; + } + + if (pll_res->index >= DSI_PLL_NUM) { + pr_err("pll ndx=%d is NOT supported\n", pll_res->index); + return -EINVAL; + } + + ndx = pll_res->index; + pdb = &pll_db[ndx]; + pll_res->priv = pdb; + pdb->pll = pll_res; + ndx++; + ndx %= DSI_PLL_NUM; + pdb->next = &pll_db[ndx]; + + /* Set clock source operations */ + + /* hr_oclk3, pixel */ + n2_clk_src_ops = clk_ops_slave_div; + n2_clk_src_ops.prepare = mdss_pll_div_prepare; + + shadow_n2_clk_src_ops = clk_ops_slave_div; + + /* hr_ockl2, byte, vco pll */ + post_n1_div_clk_src_ops = clk_ops_div; + post_n1_div_clk_src_ops.prepare = mdss_pll_div_prepare; + + shadow_post_n1_div_clk_src_ops = clk_ops_div; + + byte_clk_src_ops = clk_ops_div; + byte_clk_src_ops.prepare = mdss_pll_div_prepare; + + clk_ops_gen_mux_dsi = clk_ops_gen_mux; + clk_ops_gen_mux_dsi.round_rate = parent_round_rate; + clk_ops_gen_mux_dsi.set_rate = parent_set_rate; + + if (pll_res->ssc_en) { + if (!pll_res->ssc_freq) + pll_res->ssc_freq = ssc_freq_default; + if (!pll_res->ssc_ppm) + pll_res->ssc_ppm = ssc_ppm_default; + } + + /* Set client data to mux, div and vco clocks. */ + if (pll_res->index == DSI_PLL_1) { + dsi1pll_byte_clk_src.priv = pll_res; + dsi1pll_pixel_clk_src.priv = pll_res; + dsi1pll_post_n1_div_clk.priv = pll_res; + dsi1pll_n2_div_clk.priv = pll_res; + dsi1pll_vco_clk.priv = pll_res; + + dsi1pll_shadow_byte_clk_src.priv = pll_res; + dsi1pll_shadow_pixel_clk_src.priv = pll_res; + dsi1pll_shadow_post_n1_div_clk.priv = pll_res; + dsi1pll_shadow_n2_div_clk.priv = pll_res; + dsi1pll_shadow_vco_clk.priv = pll_res; + + pll_res->vco_delay = VCO_DELAY_USEC; + rc = of_msm_clock_register(pdev->dev.of_node, + mdss_dsi_pllcc_8996_1, + ARRAY_SIZE(mdss_dsi_pllcc_8996_1)); + } else { + dsi0pll_byte_clk_src.priv = pll_res; + dsi0pll_pixel_clk_src.priv = pll_res; + dsi0pll_post_n1_div_clk.priv = pll_res; + dsi0pll_n2_div_clk.priv = pll_res; + dsi0pll_vco_clk.priv = pll_res; + + dsi0pll_shadow_byte_clk_src.priv = pll_res; + dsi0pll_shadow_pixel_clk_src.priv = pll_res; + dsi0pll_shadow_post_n1_div_clk.priv = pll_res; + dsi0pll_shadow_n2_div_clk.priv = pll_res; + dsi0pll_shadow_vco_clk.priv = pll_res; + + pll_res->vco_delay = VCO_DELAY_USEC; + rc = of_msm_clock_register(pdev->dev.of_node, + mdss_dsi_pllcc_8996, + ARRAY_SIZE(mdss_dsi_pllcc_8996)); + } + + if (!rc) { + pr_info("Registered DSI PLL ndx=%d clocks successfully\n", + pll_res->index); + } + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-8996.h b/drivers/clk/qcom/mdss/mdss-dsi-pll-8996.h new file mode 100644 index 0000000000000000000000000000000000000000..783a18449495ddd8f6729ab089c7e0488405dc89 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-8996.h @@ -0,0 +1,221 @@ +/* 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 MDSS_DSI_PLL_8996_H +#define MDSS_DSI_PLL_8996_H + +#define DSIPHY_CMN_CLK_CFG0 0x0010 +#define DSIPHY_CMN_CLK_CFG1 0x0014 +#define DSIPHY_CMN_GLBL_TEST_CTRL 0x0018 + +#define DSIPHY_CMN_PLL_CNTRL 0x0048 +#define DSIPHY_CMN_CTRL_0 0x001c +#define DSIPHY_CMN_CTRL_1 0x0020 + +#define DSIPHY_CMN_LDO_CNTRL 0x004c + +#define DSIPHY_PLL_IE_TRIM 0x0400 +#define DSIPHY_PLL_IP_TRIM 0x0404 + +#define DSIPHY_PLL_IPTAT_TRIM 0x0410 + +#define DSIPHY_PLL_CLKBUFLR_EN 0x041c + +#define DSIPHY_PLL_SYSCLK_EN_RESET 0x0428 +#define DSIPHY_PLL_RESETSM_CNTRL 0x042c +#define DSIPHY_PLL_RESETSM_CNTRL2 0x0430 +#define DSIPHY_PLL_RESETSM_CNTRL3 0x0434 +#define DSIPHY_PLL_RESETSM_CNTRL4 0x0438 +#define DSIPHY_PLL_RESETSM_CNTRL5 0x043c +#define DSIPHY_PLL_KVCO_DIV_REF1 0x0440 +#define DSIPHY_PLL_KVCO_DIV_REF2 0x0444 +#define DSIPHY_PLL_KVCO_COUNT1 0x0448 +#define DSIPHY_PLL_KVCO_COUNT2 0x044c +#define DSIPHY_PLL_VREF_CFG1 0x045c + +#define DSIPHY_PLL_KVCO_CODE 0x0458 + +#define DSIPHY_PLL_VCO_DIV_REF1 0x046c +#define DSIPHY_PLL_VCO_DIV_REF2 0x0470 +#define DSIPHY_PLL_VCO_COUNT1 0x0474 +#define DSIPHY_PLL_VCO_COUNT2 0x0478 +#define DSIPHY_PLL_PLLLOCK_CMP1 0x047c +#define DSIPHY_PLL_PLLLOCK_CMP2 0x0480 +#define DSIPHY_PLL_PLLLOCK_CMP3 0x0484 +#define DSIPHY_PLL_PLLLOCK_CMP_EN 0x0488 +#define DSIPHY_PLL_PLL_VCO_TUNE 0x048C +#define DSIPHY_PLL_DEC_START 0x0490 +#define DSIPHY_PLL_SSC_EN_CENTER 0x0494 +#define DSIPHY_PLL_SSC_ADJ_PER1 0x0498 +#define DSIPHY_PLL_SSC_ADJ_PER2 0x049c +#define DSIPHY_PLL_SSC_PER1 0x04a0 +#define DSIPHY_PLL_SSC_PER2 0x04a4 +#define DSIPHY_PLL_SSC_STEP_SIZE1 0x04a8 +#define DSIPHY_PLL_SSC_STEP_SIZE2 0x04ac +#define DSIPHY_PLL_DIV_FRAC_START1 0x04b4 +#define DSIPHY_PLL_DIV_FRAC_START2 0x04b8 +#define DSIPHY_PLL_DIV_FRAC_START3 0x04bc +#define DSIPHY_PLL_TXCLK_EN 0x04c0 +#define DSIPHY_PLL_PLL_CRCTRL 0x04c4 + +#define DSIPHY_PLL_RESET_SM_READY_STATUS 0x04cc + +#define DSIPHY_PLL_PLL_MISC1 0x04e8 + +#define DSIPHY_PLL_CP_SET_CUR 0x04f0 +#define DSIPHY_PLL_PLL_ICPMSET 0x04f4 +#define DSIPHY_PLL_PLL_ICPCSET 0x04f8 +#define DSIPHY_PLL_PLL_ICP_SET 0x04fc +#define DSIPHY_PLL_PLL_LPF1 0x0500 +#define DSIPHY_PLL_PLL_LPF2_POSTDIV 0x0504 +#define DSIPHY_PLL_PLL_BANDGAP 0x0508 + +#define DSI_DYNAMIC_REFRESH_PLL_CTRL15 0x050 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL19 0x060 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL20 0x064 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL21 0x068 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL22 0x06C +#define DSI_DYNAMIC_REFRESH_PLL_CTRL23 0x070 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL24 0x074 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL25 0x078 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL26 0x07C +#define DSI_DYNAMIC_REFRESH_PLL_CTRL27 0x080 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL28 0x084 +#define DSI_DYNAMIC_REFRESH_PLL_CTRL29 0x088 +#define DSI_DYNAMIC_REFRESH_PLL_UPPER_ADDR 0x094 +#define DSI_DYNAMIC_REFRESH_PLL_UPPER_ADDR2 0x098 + +struct dsi_pll_input { + u32 fref; /* 19.2 Mhz, reference clk */ + u32 fdata; /* bit clock rate */ + u32 dsiclk_sel; /* 1, reg: 0x0014 */ + u32 n2div; /* 1, reg: 0x0010, bit 4-7 */ + u32 ssc_en; /* 1, reg: 0x0494, bit 0 */ + u32 ldo_en; /* 0, reg: 0x004c, bit 0 */ + + /* fixed */ + u32 refclk_dbler_en; /* 0, reg: 0x04c0, bit 1 */ + u32 vco_measure_time; /* 5, unknown */ + u32 kvco_measure_time; /* 5, unknown */ + u32 bandgap_timer; /* 4, reg: 0x0430, bit 3 - 5 */ + u32 pll_wakeup_timer; /* 5, reg: 0x043c, bit 0 - 2 */ + u32 plllock_cnt; /* 1, reg: 0x0488, bit 1 - 2 */ + u32 plllock_rng; /* 1, reg: 0x0488, bit 3 - 4 */ + u32 ssc_center; /* 0, reg: 0x0494, bit 1 */ + u32 ssc_adj_period; /* 37, reg: 0x498, bit 0 - 9 */ + u32 ssc_spread; /* 0.005 */ + u32 ssc_freq; /* unknown */ + u32 pll_ie_trim; /* 4, reg: 0x0400 */ + u32 pll_ip_trim; /* 4, reg: 0x0404 */ + u32 pll_iptat_trim; /* reg: 0x0410 */ + u32 pll_cpcset_cur; /* 1, reg: 0x04f0, bit 0 - 2 */ + u32 pll_cpmset_cur; /* 1, reg: 0x04f0, bit 3 - 5 */ + + u32 pll_icpmset; /* 4, reg: 0x04fc, bit 3 - 5 */ + u32 pll_icpcset; /* 4, reg: 0x04fc, bit 0 - 2 */ + + u32 pll_icpmset_p; /* 0, reg: 0x04f4, bit 0 - 2 */ + u32 pll_icpmset_m; /* 0, reg: 0x04f4, bit 3 - 5 */ + + u32 pll_icpcset_p; /* 0, reg: 0x04f8, bit 0 - 2 */ + u32 pll_icpcset_m; /* 0, reg: 0x04f8, bit 3 - 5 */ + + u32 pll_lpf_res1; /* 3, reg: 0x0504, bit 0 - 3 */ + u32 pll_lpf_cap1; /* 11, reg: 0x0500, bit 0 - 3 */ + u32 pll_lpf_cap2; /* 1, reg: 0x0500, bit 4 - 7 */ + u32 pll_c3ctrl; /* 2, reg: 0x04c4 */ + u32 pll_r3ctrl; /* 1, reg: 0x04c4 */ +}; + +struct dsi_pll_output { + u32 pll_txclk_en; /* reg: 0x04c0 */ + u32 dec_start; /* reg: 0x0490 */ + u32 div_frac_start; /* reg: 0x04b4, 0x4b8, 0x04bc */ + u32 ssc_period; /* reg: 0x04a0, 0x04a4 */ + u32 ssc_step_size; /* reg: 0x04a8, 0x04ac */ + u32 plllock_cmp; /* reg: 0x047c, 0x0480, 0x0484 */ + u32 pll_vco_div_ref; /* reg: 0x046c, 0x0470 */ + u32 pll_vco_count; /* reg: 0x0474, 0x0478 */ + u32 pll_kvco_div_ref; /* reg: 0x0440, 0x0444 */ + u32 pll_kvco_count; /* reg: 0x0448, 0x044c */ + u32 pll_misc1; /* reg: 0x04e8 */ + u32 pll_lpf2_postdiv; /* reg: 0x0504 */ + u32 pll_resetsm_cntrl; /* reg: 0x042c */ + u32 pll_resetsm_cntrl2; /* reg: 0x0430 */ + u32 pll_resetsm_cntrl5; /* reg: 0x043c */ + u32 pll_kvco_code; /* reg: 0x0458 */ + + u32 cmn_clk_cfg0; /* reg: 0x0010 */ + u32 cmn_clk_cfg1; /* reg: 0x0014 */ + u32 cmn_ldo_cntrl; /* reg: 0x004c */ + + u32 pll_postdiv; /* vco */ + u32 pll_n1div; /* vco */ + u32 pll_n2div; /* hr_oclk3, pixel */ + u32 fcvo; +}; + +enum { + DSI_PLL_0, + DSI_PLL_1, + DSI_PLL_NUM +}; + +struct dsi_pll_db { + struct dsi_pll_db *next; + struct mdss_pll_resources *pll; + struct dsi_pll_input in; + struct dsi_pll_output out; + int source_setup_done; +}; + +enum { + PLL_OUTPUT_NONE, + PLL_OUTPUT_RIGHT, + PLL_OUTPUT_LEFT, + PLL_OUTPUT_BOTH +}; + +enum { + PLL_SOURCE_FROM_LEFT, + PLL_SOURCE_FROM_RIGHT +}; + +enum { + PLL_UNKNOWN, + PLL_STANDALONE, + PLL_SLAVE, + PLL_MASTER +}; + +int pll_vco_set_rate_8996(struct clk *c, unsigned long rate); +long pll_vco_round_rate_8996(struct clk *c, unsigned long rate); +enum handoff pll_vco_handoff_8996(struct clk *c); +enum handoff shadow_pll_vco_handoff_8996(struct clk *c); +int shadow_post_n1_div_set_div(struct div_clk *clk, int div); +int shadow_post_n1_div_get_div(struct div_clk *clk); +int shadow_n2_div_set_div(struct div_clk *clk, int div); +int shadow_n2_div_get_div(struct div_clk *clk); +int shadow_pll_vco_set_rate_8996(struct clk *c, unsigned long rate); +int pll_vco_prepare_8996(struct clk *c); +void pll_vco_unprepare_8996(struct clk *c); +int set_mdss_byte_mux_sel_8996(struct mux_clk *clk, int sel); +int get_mdss_byte_mux_sel_8996(struct mux_clk *clk); +int set_mdss_pixel_mux_sel_8996(struct mux_clk *clk, int sel); +int get_mdss_pixel_mux_sel_8996(struct mux_clk *clk); +int post_n1_div_set_div(struct div_clk *clk, int div); +int post_n1_div_get_div(struct div_clk *clk); +int n2_div_set_div(struct div_clk *clk, int div); +int n2_div_get_div(struct div_clk *clk); +int dsi_pll_enable_seq_8996(struct mdss_pll_resources *pll); + +#endif /* MDSS_DSI_PLL_8996_H */ diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll-util.c b/drivers/clk/qcom/mdss/mdss-dsi-pll-util.c new file mode 100644 index 0000000000000000000000000000000000000000..4e66b7837eb85bb368f133671a2d75c7b0557fb6 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll-util.c @@ -0,0 +1,587 @@ +/* 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) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" + +#define DSI_PHY_PLL_UNIPHY_PLL_REFCLK_CFG (0x0) +#define DSI_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG (0x0004) +#define DSI_PHY_PLL_UNIPHY_PLL_CHGPUMP_CFG (0x0008) +#define DSI_PHY_PLL_UNIPHY_PLL_VCOLPF_CFG (0x000C) +#define DSI_PHY_PLL_UNIPHY_PLL_VREG_CFG (0x0010) +#define DSI_PHY_PLL_UNIPHY_PLL_PWRGEN_CFG (0x0014) +#define DSI_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG (0x0024) +#define DSI_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG (0x0028) +#define DSI_PHY_PLL_UNIPHY_PLL_LPFR_CFG (0x002C) +#define DSI_PHY_PLL_UNIPHY_PLL_LPFC1_CFG (0x0030) +#define DSI_PHY_PLL_UNIPHY_PLL_LPFC2_CFG (0x0034) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG0 (0x0038) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG1 (0x003C) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG2 (0x0040) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG3 (0x0044) +#define DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG4 (0x0048) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG0 (0x006C) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG2 (0x0074) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG3 (0x0078) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG4 (0x007C) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG5 (0x0080) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG6 (0x0084) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG7 (0x0088) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG8 (0x008C) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG9 (0x0090) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG10 (0x0094) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG11 (0x0098) +#define DSI_PHY_PLL_UNIPHY_PLL_EFUSE_CFG (0x009C) +#define DSI_PHY_PLL_UNIPHY_PLL_STATUS (0x00C0) + +#define DSI_PLL_POLL_DELAY_US 50 +#define DSI_PLL_POLL_TIMEOUT_US 500 + +int set_byte_mux_sel(struct mux_clk *clk, int sel) +{ + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + pr_debug("byte mux set to %s mode\n", sel ? "indirect" : "direct"); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_VREG_CFG, (sel << 1)); + + return 0; +} + +int get_byte_mux_sel(struct mux_clk *clk) +{ + int mux_mode, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + mux_mode = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_VREG_CFG) & BIT(1); + + pr_debug("byte mux mode = %s", mux_mode ? "indirect" : "direct"); + mdss_pll_resource_enable(dsi_pll_res, false); + + return !!mux_mode; +} + +int dsi_pll_div_prepare(struct clk *c) +{ + struct div_clk *div = to_div_clk(c); + /* Restore the divider's value */ + return div->ops->set_div(div, div->data.div); +} + +int dsi_pll_mux_prepare(struct clk *c) +{ + struct mux_clk *mux = to_mux_clk(c); + int i, rc, sel = 0; + struct mdss_pll_resources *dsi_pll_res = mux->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + for (i = 0; i < mux->num_parents; i++) + if (mux->parents[i].src == c->parent) { + sel = mux->parents[i].sel; + break; + } + + if (i == mux->num_parents) { + pr_err("Failed to select the parent clock\n"); + rc = -EINVAL; + goto error; + } + + /* Restore the mux source select value */ + rc = mux->ops->set_mux_sel(mux, sel); + +error: + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int fixed_4div_set_div(struct div_clk *clk, int div) +{ + int rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG, (div - 1)); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int fixed_4div_get_div(struct div_clk *clk) +{ + int div = 0, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + div = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG); + + mdss_pll_resource_enable(dsi_pll_res, false); + return div + 1; +} + +int digital_set_div(struct div_clk *clk, int div) +{ + int rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG, (div - 1)); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int digital_get_div(struct div_clk *clk) +{ + int div = 0, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + div = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG); + + mdss_pll_resource_enable(dsi_pll_res, false); + return div + 1; +} + +int analog_set_div(struct div_clk *clk, int div) +{ + int rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG, div - 1); + + mdss_pll_resource_enable(dsi_pll_res, false); + return rc; +} + +int analog_get_div(struct div_clk *clk) +{ + int div = 0, rc; + struct mdss_pll_resources *dsi_pll_res = clk->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(clk->priv, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + div = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG) + 1; + + mdss_pll_resource_enable(dsi_pll_res, false); + + return div; +} + +int dsi_pll_lock_status(struct mdss_pll_resources *dsi_pll_res) +{ + u32 status; + int pll_locked; + + /* poll for PLL ready status */ + if (readl_poll_timeout_atomic((dsi_pll_res->pll_base + + DSI_PHY_PLL_UNIPHY_PLL_STATUS), + status, + ((status & BIT(0)) == 1), + DSI_PLL_POLL_DELAY_US, + DSI_PLL_POLL_TIMEOUT_US)) { + pr_debug("DSI PLL status=%x failed to Lock\n", status); + pll_locked = 0; + } else { + pll_locked = 1; + } + + return pll_locked; +} + +int vco_set_rate(struct dsi_pll_vco_clk *vco, unsigned long rate) +{ + s64 vco_clk_rate = rate; + s32 rem; + s64 refclk_cfg, frac_n_mode, ref_doubler_en_b; + s64 ref_clk_to_pll, div_fbx1000, frac_n_value; + s64 sdm_cfg0, sdm_cfg1, sdm_cfg2, sdm_cfg3; + s64 gen_vco_clk, cal_cfg10, cal_cfg11; + u32 res; + int i; + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + /* Configure the Loop filter resistance */ + for (i = 0; i < vco->lpfr_lut_size; i++) + if (vco_clk_rate <= vco->lpfr_lut[i].vco_rate) + break; + if (i == vco->lpfr_lut_size) { + pr_err("unable to get loop filter resistance. vco=%ld\n", rate); + return -EINVAL; + } + res = vco->lpfr_lut[i].r; + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LPFR_CFG, res); + + /* Loop filter capacitance values : c1 and c2 */ + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LPFC1_CFG, 0x70); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LPFC2_CFG, 0x15); + + div_s64_rem(vco_clk_rate, vco->ref_clk_rate, &rem); + if (rem) { + refclk_cfg = 0x1; + frac_n_mode = 1; + ref_doubler_en_b = 0; + } else { + refclk_cfg = 0x0; + frac_n_mode = 0; + ref_doubler_en_b = 1; + } + + pr_debug("refclk_cfg = %lld\n", refclk_cfg); + + ref_clk_to_pll = ((vco->ref_clk_rate * 2 * (refclk_cfg)) + + (ref_doubler_en_b * vco->ref_clk_rate)); + div_fbx1000 = div_s64((vco_clk_rate * 1000), ref_clk_to_pll); + + div_s64_rem(div_fbx1000, 1000, &rem); + frac_n_value = div_s64((rem * (1 << 16)), 1000); + gen_vco_clk = div_s64(div_fbx1000 * ref_clk_to_pll, 1000); + + pr_debug("ref_clk_to_pll = %lld\n", ref_clk_to_pll); + pr_debug("div_fb = %lld\n", div_fbx1000); + pr_debug("frac_n_value = %lld\n", frac_n_value); + + pr_debug("Generated VCO Clock: %lld\n", gen_vco_clk); + rem = 0; + if (frac_n_mode) { + sdm_cfg0 = (0x0 << 5); + sdm_cfg0 |= (0x0 & 0x3f); + sdm_cfg1 = (div_s64(div_fbx1000, 1000) & 0x3f) - 1; + sdm_cfg3 = div_s64_rem(frac_n_value, 256, &rem); + sdm_cfg2 = rem; + } else { + sdm_cfg0 = (0x1 << 5); + sdm_cfg0 |= (div_s64(div_fbx1000, 1000) & 0x3f) - 1; + sdm_cfg1 = (0x0 & 0x3f); + sdm_cfg2 = 0; + sdm_cfg3 = 0; + } + + pr_debug("sdm_cfg0=%lld\n", sdm_cfg0); + pr_debug("sdm_cfg1=%lld\n", sdm_cfg1); + pr_debug("sdm_cfg2=%lld\n", sdm_cfg2); + pr_debug("sdm_cfg3=%lld\n", sdm_cfg3); + + cal_cfg11 = div_s64_rem(gen_vco_clk, 256 * 1000000, &rem); + cal_cfg10 = rem / 1000000; + pr_debug("cal_cfg10=%lld, cal_cfg11=%lld\n", cal_cfg10, cal_cfg11); + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CHGPUMP_CFG, 0x02); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG3, 0x2b); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG4, 0x66); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x0d); + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG1, (u32)(sdm_cfg1 & 0xff)); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG2, (u32)(sdm_cfg2 & 0xff)); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG3, (u32)(sdm_cfg3 & 0xff)); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG4, 0x00); + + /* Add hardware recommended delay for correct PLL configuration */ + if (dsi_pll_res->vco_delay) + udelay(dsi_pll_res->vco_delay); + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_REFCLK_CFG, (u32)refclk_cfg); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_PWRGEN_CFG, 0x00); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_VCOLPF_CFG, 0x71); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG0, (u32)sdm_cfg0); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG0, 0x12); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG6, 0x30); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG7, 0x00); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG10, (u32)(cal_cfg10 & 0xff)); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG11, (u32)(cal_cfg11 & 0xff)); + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_EFUSE_CFG, 0x20); + + return 0; +} + +unsigned long vco_get_rate(struct clk *c) +{ + u32 sdm0, doubler, sdm_byp_div; + u64 vco_rate; + u32 sdm_dc_off, sdm_freq_seed, sdm2, sdm3; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + u64 ref_clk = vco->ref_clk_rate; + int rc; + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + /* Check to see if the ref clk doubler is enabled */ + doubler = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_REFCLK_CFG) & BIT(0); + ref_clk += (doubler * vco->ref_clk_rate); + + /* see if it is integer mode or sdm mode */ + sdm0 = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG0); + if (sdm0 & BIT(6)) { + /* integer mode */ + sdm_byp_div = (MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG0) & 0x3f) + 1; + vco_rate = ref_clk * sdm_byp_div; + } else { + /* sdm mode */ + sdm_dc_off = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG1) & 0xFF; + pr_debug("sdm_dc_off = %d\n", sdm_dc_off); + sdm2 = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG2) & 0xFF; + sdm3 = MDSS_PLL_REG_R(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_SDM_CFG3) & 0xFF; + sdm_freq_seed = (sdm3 << 8) | sdm2; + pr_debug("sdm_freq_seed = %d\n", sdm_freq_seed); + + vco_rate = (ref_clk * (sdm_dc_off + 1)) + + mult_frac(ref_clk, sdm_freq_seed, BIT(16)); + pr_debug("vco rate = %lld", vco_rate); + } + + pr_debug("returning vco rate = %lu\n", (unsigned long)vco_rate); + + mdss_pll_resource_enable(dsi_pll_res, false); + + return (unsigned long)vco_rate; +} + +static int dsi_pll_enable(struct clk *c) +{ + int i, rc; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return rc; + } + + /* Try all enable sequences until one succeeds */ + for (i = 0; i < vco->pll_en_seq_cnt; i++) { + rc = vco->pll_enable_seqs[i](dsi_pll_res); + pr_debug("DSI PLL %s after sequence #%d\n", + rc ? "unlocked" : "locked", i + 1); + if (!rc) + break; + } + + if (rc) { + mdss_pll_resource_enable(dsi_pll_res, false); + pr_err("DSI PLL failed to lock\n"); + } + dsi_pll_res->pll_on = true; + + return rc; +} + +static void dsi_pll_disable(struct clk *c) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res->pll_on && + mdss_pll_resource_enable(dsi_pll_res, true)) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return; + } + + dsi_pll_res->handoff_resources = false; + + MDSS_PLL_REG_W(dsi_pll_res->pll_base, + DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x00); + + mdss_pll_resource_enable(dsi_pll_res, false); + dsi_pll_res->pll_on = false; + + pr_debug("DSI PLL Disabled\n"); +} + +long vco_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + return rrate; +} + +enum handoff vco_handoff(struct clk *c) +{ + int rc; + enum handoff ret = HANDOFF_DISABLED_CLK; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (is_gdsc_disabled(dsi_pll_res)) + return HANDOFF_DISABLED_CLK; + + rc = mdss_pll_resource_enable(dsi_pll_res, true); + if (rc) { + pr_err("Failed to enable mdss dsi pll resources\n"); + return ret; + } + + if (dsi_pll_lock_status(dsi_pll_res)) { + dsi_pll_res->handoff_resources = true; + dsi_pll_res->pll_on = true; + c->rate = vco_get_rate(c); + ret = HANDOFF_ENABLED_CLK; + } else { + mdss_pll_resource_enable(dsi_pll_res, false); + } + + return ret; +} + +int vco_prepare(struct clk *c) +{ + int rc = 0; + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res) { + pr_err("Dsi pll resources are not available\n"); + return -EINVAL; + } + + if ((dsi_pll_res->vco_cached_rate != 0) + && (dsi_pll_res->vco_cached_rate == c->rate)) { + rc = c->ops->set_rate(c, dsi_pll_res->vco_cached_rate); + if (rc) { + pr_err("vco_set_rate failed. rc=%d\n", rc); + goto error; + } + } + + rc = dsi_pll_enable(c); + +error: + return rc; +} + +void vco_unprepare(struct clk *c) +{ + struct dsi_pll_vco_clk *vco = to_vco_clk(c); + struct mdss_pll_resources *dsi_pll_res = vco->priv; + + if (!dsi_pll_res) { + pr_err("Dsi pll resources are not available\n"); + return; + } + + dsi_pll_res->vco_cached_rate = c->rate; + dsi_pll_disable(c); +} + diff --git a/drivers/clk/qcom/mdss/mdss-dsi-pll.h b/drivers/clk/qcom/mdss/mdss-dsi-pll.h new file mode 100644 index 0000000000000000000000000000000000000000..3324a2e94eb28da42848ae70ecbe61e34c5757f1 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-dsi-pll.h @@ -0,0 +1,57 @@ +/* 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 __MDSS_DSI_PLL_H +#define __MDSS_DSI_PLL_H + +#include +#include "mdss-pll.h" +#define MAX_DSI_PLL_EN_SEQS 10 + +#define DSI_PHY_PLL_UNIPHY_PLL_GLB_CFG (0x0020) +#define DSI_PHY_PLL_UNIPHY_PLL_LKDET_CFG2 (0x0064) +#define DSI_PHY_PLL_UNIPHY_PLL_TEST_CFG (0x0068) +#define DSI_PHY_PLL_UNIPHY_PLL_CAL_CFG1 (0x0070) + +/* Register offsets for 20nm PHY PLL */ +#define MMSS_DSI_PHY_PLL_PLL_CNTRL (0x0014) +#define MMSS_DSI_PHY_PLL_PLL_BKG_KVCO_CAL_EN (0x002C) +#define MMSS_DSI_PHY_PLL_PLLLOCK_CMP_EN (0x009C) + +struct lpfr_cfg { + unsigned long vco_rate; + u32 r; +}; + +struct dsi_pll_vco_clk { + struct clk_hw hw; + unsigned long ref_clk_rate; + unsigned long min_rate; + unsigned long max_rate; + u32 pll_en_seq_cnt; + struct lpfr_cfg *lpfr_lut; + u32 lpfr_lut_size; + void *priv; + + int (*pll_enable_seqs[MAX_DSI_PLL_EN_SEQS]) + (struct mdss_pll_resources *dsi_pll_Res); +}; + +int dsi_pll_clock_register_10nm(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +static inline struct dsi_pll_vco_clk *to_vco_clk_hw(struct clk_hw *hw) +{ + return container_of(hw, struct dsi_pll_vco_clk, hw); +} + +#endif diff --git a/drivers/clk/qcom/mdss/mdss-edp-pll-28hpm.c b/drivers/clk/qcom/mdss/mdss-edp-pll-28hpm.c new file mode 100644 index 0000000000000000000000000000000000000000..7bd4031d4652aea0e46dffc01d0176a282981250 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-edp-pll-28hpm.c @@ -0,0 +1,593 @@ +/* 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) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "mdss-pll.h" +#include "mdss-edp-pll.h" + +#define EDP_PHY_PLL_UNIPHY_PLL_REFCLK_CFG (0x0) +#define EDP_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG (0x0004) +#define EDP_PHY_PLL_UNIPHY_PLL_VCOLPF_CFG (0x000C) +#define EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG (0x0020) +#define EDP_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG (0x0024) +#define EDP_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG (0x0028) +#define EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG0 (0x0038) +#define EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG1 (0x003C) +#define EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG2 (0x0040) +#define EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG3 (0x0044) +#define EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG4 (0x0048) +#define EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG0 (0x004C) +#define EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG1 (0x0050) +#define EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG2 (0x0054) +#define EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG3 (0x0058) +#define EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG2 (0x0064) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG0 (0x006C) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG2 (0x0074) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG6 (0x0084) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG7 (0x0088) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG8 (0x008C) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG9 (0x0090) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG10 (0x0094) +#define EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG11 (0x0098) +#define EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG0 (0x005C) +#define EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG1 (0x0060) + +#define EDP_PLL_POLL_DELAY_US 50 +#define EDP_PLL_POLL_TIMEOUT_US 500 + +static const struct clk_ops edp_mainlink_clk_src_ops; +static struct clk_div_ops fixed_5div_ops; /* null ops */ +static const struct clk_ops edp_pixel_clk_ops; + +static inline struct edp_pll_vco_clk *to_edp_vco_clk(struct clk *clk) +{ + return container_of(clk, struct edp_pll_vco_clk, c); +} + +int edp_div_prepare(struct clk *c) +{ + struct div_clk *div = to_div_clk(c); + /* Restore the divider's value */ + return div->ops->set_div(div, div->data.div); +} + +static int edp_vco_set_rate(struct clk *c, unsigned long vco_rate) +{ + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + struct mdss_pll_resources *edp_pll_res = vco->priv; + int rc; + + pr_debug("vco_rate=%d\n", (int)vco_rate); + + rc = mdss_pll_resource_enable(edp_pll_res, true); + if (rc) { + pr_err("failed to enable edp pll res rc=%d\n", rc); + rc = -EINVAL; + } + + if (vco_rate == 810000000) { + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_VCOLPF_CFG, 0x18); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x0d); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_REFCLK_CFG, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG0, 0x36); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG1, 0x69); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG2, 0xff); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG3, 0x2f); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG0, 0x80); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG1, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG2, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG3, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG0, 0x12); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG6, 0x5a); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG7, 0x0); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG9, 0x0); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG10, 0x2a); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG11, 0x3); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG1, 0x1a); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG, 0x00); + } else if (vco_rate == 1350000000) { + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG2, 0x0d); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG0, 0x36); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG1, 0x62); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG2, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG3, 0x28); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG0, 0x80); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG1, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG2, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_SSC_CFG3, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG0, 0x12); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG6, 0x5a); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG7, 0x0); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG9, 0x0); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG10, 0x46); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_CAL_CFG11, 0x5); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_LKDET_CFG1, 0x1a); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_POSTDIV1_CFG, 0x00); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_POSTDIV3_CFG, 0x00); + } else { + pr_err("rate=%d is NOT supported\n", (int)vco_rate); + vco_rate = 0; + rc = -EINVAL; + } + + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01); + udelay(100); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05); + udelay(100); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x07); + udelay(100); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f); + udelay(100); + mdss_pll_resource_enable(edp_pll_res, false); + + vco->rate = vco_rate; + + return rc; +} + +static int edp_pll_ready_poll(struct mdss_pll_resources *edp_pll_res) +{ + int cnt; + u32 status; + + cnt = 100; + while (cnt--) { + udelay(100); + status = MDSS_PLL_REG_R(edp_pll_res->pll_base, 0xc0); + status &= 0x01; + if (status) + break; + } + pr_debug("cnt=%d status=%d\n", cnt, (int)status); + + if (status) + return 1; + + return 0; +} + +static int edp_vco_enable(struct clk *c) +{ + int i, ready; + int rc; + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + struct mdss_pll_resources *edp_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(edp_pll_res, true); + if (rc) { + pr_err("edp pll resources not available\n"); + return rc; + } + + for (i = 0; i < 3; i++) { + ready = edp_pll_ready_poll(edp_pll_res); + if (ready) + break; + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x01); + udelay(100); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x05); + udelay(100); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x07); + udelay(100); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_GLB_CFG, 0x0f); + udelay(100); + } + + if (ready) { + pr_debug("EDP PLL lock success\n"); + edp_pll_res->pll_on = true; + rc = 0; + } else { + pr_err("EDP PLL failed to lock\n"); + mdss_pll_resource_enable(edp_pll_res, false); + rc = -EINVAL; + } + + return rc; +} + +static void edp_vco_disable(struct clk *c) +{ + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + struct mdss_pll_resources *edp_pll_res = vco->priv; + + if (!edp_pll_res) { + pr_err("Invalid input parameter\n"); + return; + } + + if (!edp_pll_res->pll_on && + mdss_pll_resource_enable(edp_pll_res, true)) { + pr_err("edp pll resources not available\n"); + return; + } + + MDSS_PLL_REG_W(edp_pll_res->pll_base, 0x20, 0x00); + + edp_pll_res->handoff_resources = false; + edp_pll_res->pll_on = false; + + mdss_pll_resource_enable(edp_pll_res, false); + + pr_debug("EDP PLL Disabled\n"); +} + +static unsigned long edp_vco_get_rate(struct clk *c) +{ + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + struct mdss_pll_resources *edp_pll_res = vco->priv; + u32 pll_status, div2; + int rc; + + if (is_gdsc_disabled(edp_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(edp_pll_res, true); + if (rc) { + pr_err("edp pll resources not available\n"); + return rc; + } + + if (vco->rate == 0) { + pll_status = MDSS_PLL_REG_R(edp_pll_res->pll_base, 0xc0); + if (pll_status & 0x01) { + div2 = MDSS_PLL_REG_R(edp_pll_res->pll_base, 0x24); + if (div2 & 0x01) + vco->rate = 1350000000; + else + vco->rate = 810000000; + } + } + mdss_pll_resource_enable(edp_pll_res, false); + + pr_debug("rate=%d\n", (int)vco->rate); + + return vco->rate; +} + +static long edp_vco_round_rate(struct clk *c, unsigned long rate) +{ + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + unsigned long rrate = -ENOENT; + unsigned long *lp; + + lp = vco->rate_list; + while (*lp) { + rrate = *lp; + if (rate <= rrate) + break; + lp++; + } + + pr_debug("rrate=%d\n", (int)rrate); + + return rrate; +} + +static int edp_vco_prepare(struct clk *c) +{ + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + + pr_debug("rate=%d\n", (int)vco->rate); + + return edp_vco_set_rate(c, vco->rate); +} + +static void edp_vco_unprepare(struct clk *c) +{ + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + + pr_debug("rate=%d\n", (int)vco->rate); + + edp_vco_disable(c); +} + +static int edp_pll_lock_status(struct mdss_pll_resources *edp_pll_res) +{ + u32 status; + int pll_locked = 0; + int rc; + + rc = mdss_pll_resource_enable(edp_pll_res, true); + if (rc) { + pr_err("edp pll resources not available\n"); + return rc; + } + + /* poll for PLL ready status */ + if (readl_poll_timeout_atomic((edp_pll_res->pll_base + 0xc0), + status, ((status & BIT(0)) == 1), + EDP_PLL_POLL_DELAY_US, + EDP_PLL_POLL_TIMEOUT_US)) { + pr_debug("EDP PLL status=%x failed to Lock\n", status); + pll_locked = 0; + } else { + pll_locked = 1; + } + mdss_pll_resource_enable(edp_pll_res, false); + + return pll_locked; +} + +static enum handoff edp_vco_handoff(struct clk *c) +{ + enum handoff ret = HANDOFF_DISABLED_CLK; + struct edp_pll_vco_clk *vco = to_edp_vco_clk(c); + struct mdss_pll_resources *edp_pll_res = vco->priv; + + if (is_gdsc_disabled(edp_pll_res)) + return HANDOFF_DISABLED_CLK; + + if (mdss_pll_resource_enable(edp_pll_res, true)) { + pr_err("edp pll resources not available\n"); + return ret; + } + + edp_pll_res->handoff_resources = true; + + if (edp_pll_lock_status(edp_pll_res)) { + c->rate = edp_vco_get_rate(c); + edp_pll_res->pll_on = true; + ret = HANDOFF_ENABLED_CLK; + } else { + edp_pll_res->handoff_resources = false; + mdss_pll_resource_enable(edp_pll_res, false); + } + + pr_debug("done, ret=%d\n", ret); + return ret; +} + +static unsigned long edp_vco_rate_list[] = { + 810000000, 1350000000, 0}; + +struct const clk_ops edp_vco_clk_ops = { + .enable = edp_vco_enable, + .set_rate = edp_vco_set_rate, + .get_rate = edp_vco_get_rate, + .round_rate = edp_vco_round_rate, + .prepare = edp_vco_prepare, + .unprepare = edp_vco_unprepare, + .handoff = edp_vco_handoff, +}; + +struct edp_pll_vco_clk edp_vco_clk = { + .ref_clk_rate = 19200000, + .rate = 0, + .rate_list = edp_vco_rate_list, + .c = { + .dbg_name = "edp_vco_clk", + .ops = &edp_vco_clk_ops, + CLK_INIT(edp_vco_clk.c), + }, +}; + +static unsigned long edp_mainlink_get_rate(struct clk *c) +{ + struct div_clk *mclk = to_div_clk(c); + struct clk *pclk; + unsigned long rate = 0; + + pclk = clk_get_parent(c); + + if (pclk && pclk->ops->get_rate) { + rate = pclk->ops->get_rate(pclk); + rate /= mclk->data.div; + } + + pr_debug("rate=%d div=%d\n", (int)rate, mclk->data.div); + + return rate; +} + + +struct div_clk edp_mainlink_clk_src = { + .ops = &fixed_5div_ops, + .data = { + .div = 5, + .min_div = 5, + .max_div = 5, + }, + .c = { + .parent = &edp_vco_clk.c, + .dbg_name = "edp_mainlink_clk_src", + .ops = &edp_mainlink_clk_src_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(edp_mainlink_clk_src.c), + } +}; + +/* + * this rate is from pll to clock controller + * output from pll to CC has two possibilities + * 1: if mainlink rate is 270M, then 675M + * 2: if mainlink rate is 162M, then 810M + */ +static int edp_pixel_set_div(struct div_clk *clk, int div) +{ + int rc; + struct mdss_pll_resources *edp_pll_res = clk->priv; + + rc = mdss_pll_resource_enable(edp_pll_res, true); + if (rc) { + pr_err("edp pll resources not available\n"); + return rc; + } + + pr_debug("div=%d\n", div); + MDSS_PLL_REG_W(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG, (div - 1)); + mdss_pll_resource_enable(edp_pll_res, false); + + return 0; +} + +static int edp_pixel_get_div(struct div_clk *clk) +{ + int div = 0; + int rc; + struct mdss_pll_resources *edp_pll_res = clk->priv; + + if (is_gdsc_disabled(edp_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(edp_pll_res, true); + if (rc) { + pr_err("edp pll resources not available\n"); + return rc; + } + + div = MDSS_PLL_REG_R(edp_pll_res->pll_base, + EDP_PHY_PLL_UNIPHY_PLL_POSTDIV2_CFG); + mdss_pll_resource_enable(edp_pll_res, false); + div &= 0x01; + pr_debug("div=%d\n", div); + return div + 1; +} + +static struct clk_div_ops edp_pixel_ops = { + .set_div = edp_pixel_set_div, + .get_div = edp_pixel_get_div, +}; + +struct div_clk edp_pixel_clk_src = { + .data = { + .max_div = 2, + .min_div = 1, + }, + .ops = &edp_pixel_ops, + .c = { + .parent = &edp_vco_clk.c, + .dbg_name = "edp_pixel_clk_src", + .ops = &edp_pixel_clk_ops, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(edp_pixel_clk_src.c), + }, +}; + +static struct clk_lookup mdss_edp_pllcc_8974[] = { + CLK_LOOKUP("edp_pixel_src", edp_pixel_clk_src.c, + "fd8c0000.qcom,mmsscc-mdss"), + CLK_LOOKUP("edp_mainlink_src", edp_mainlink_clk_src.c, + "fd8c0000.qcom,mmsscc-mdss"), +}; + +int edp_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = -ENOTSUPP; + + if (!pll_res || !pll_res->pll_base) { + pr_err("Invalid input parameters\n"); + return -EPROBE_DEFER; + } + + /* Set client data to div and vco clocks */ + edp_pixel_clk_src.priv = pll_res; + edp_mainlink_clk_src.priv = pll_res; + edp_vco_clk.priv = pll_res; + + /* Set clock operation for mainlink and pixel clock */ + edp_mainlink_clk_src_ops = clk_ops_div; + edp_mainlink_clk_src_ops.get_parent = clk_get_parent; + edp_mainlink_clk_src_ops.get_rate = edp_mainlink_get_rate; + + edp_pixel_clk_ops = clk_ops_slave_div; + edp_pixel_clk_ops.prepare = edp_div_prepare; + + rc = of_msm_clock_register(pdev->dev.of_node, mdss_edp_pllcc_8974, + ARRAY_SIZE(mdss_edp_pllcc_8974)); + if (rc) { + pr_err("Clock register failed rc=%d\n", rc); + rc = -EPROBE_DEFER; + } + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-hdmi-pll-20nm.c b/drivers/clk/qcom/mdss/mdss-hdmi-pll-20nm.c new file mode 100644 index 0000000000000000000000000000000000000000..23f809475f02732d2a95d52a21ef99ee68961a06 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-hdmi-pll-20nm.c @@ -0,0 +1,983 @@ +/* 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. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-hdmi-pll.h" + +/* hdmi phy registers */ + +#define HDMI_PHY_CMD_SIZE 68 +#define HDMI_PHY_CLK_SIZE 97 + +/* Set to 1 for auto KVCO cal; set to 0 for fixed value */ +#define HDMI_PHY_AUTO_KVCO_CAL 1 + +/* PLL REGISTERS */ +#define QSERDES_COM_SYS_CLK_CTRL (0x000) +#define QSERDES_COM_PLL_VCOTAIL_EN (0x004) +#define QSERDES_COM_CMN_MODE (0x008) +#define QSERDES_COM_IE_TRIM (0x00C) +#define QSERDES_COM_IP_TRIM (0x010) +#define QSERDES_COM_PLL_CNTRL (0x014) +#define QSERDES_COM_PLL_PHSEL_CONTROL (0x018) +#define QSERDES_COM_IPTAT_TRIM_VCCA_TX_SEL (0x01C) +#define QSERDES_COM_PLL_PHSEL_DC (0x020) +#define QSERDES_COM_PLL_IP_SETI (0x024) +#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL (0x028) +#define QSERDES_COM_PLL_BKG_KVCO_CAL_EN (0x02C) +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN (0x030) +#define QSERDES_COM_PLL_CP_SETI (0x034) +#define QSERDES_COM_PLL_IP_SETP (0x038) +#define QSERDES_COM_PLL_CP_SETP (0x03C) +#define QSERDES_COM_ATB_SEL1 (0x040) +#define QSERDES_COM_ATB_SEL2 (0x044) +#define QSERDES_COM_SYSCLK_EN_SEL_TXBAND (0x048) +#define QSERDES_COM_RESETSM_CNTRL (0x04C) +#define QSERDES_COM_RESETSM_CNTRL2 (0x050) +#define QSERDES_COM_RESETSM_CNTRL3 (0x054) +#define QSERDES_COM_RESETSM_PLL_CAL_COUNT1 (0x058) +#define QSERDES_COM_RESETSM_PLL_CAL_COUNT2 (0x05C) +#define QSERDES_COM_DIV_REF1 (0x060) +#define QSERDES_COM_DIV_REF2 (0x064) +#define QSERDES_COM_KVCO_COUNT1 (0x068) +#define QSERDES_COM_KVCO_COUNT2 (0x06C) +#define QSERDES_COM_KVCO_CAL_CNTRL (0x070) +#define QSERDES_COM_KVCO_CODE (0x074) +#define QSERDES_COM_VREF_CFG1 (0x078) +#define QSERDES_COM_VREF_CFG2 (0x07C) +#define QSERDES_COM_VREF_CFG3 (0x080) +#define QSERDES_COM_VREF_CFG4 (0x084) +#define QSERDES_COM_VREF_CFG5 (0x088) +#define QSERDES_COM_VREF_CFG6 (0x08C) +#define QSERDES_COM_PLLLOCK_CMP1 (0x090) +#define QSERDES_COM_PLLLOCK_CMP2 (0x094) +#define QSERDES_COM_PLLLOCK_CMP3 (0x098) +#define QSERDES_COM_PLLLOCK_CMP_EN (0x09C) +#define QSERDES_COM_BGTC (0x0A0) +#define QSERDES_COM_PLL_TEST_UPDN (0x0A4) +#define QSERDES_COM_PLL_VCO_TUNE (0x0A8) +#define QSERDES_COM_DEC_START1 (0x0AC) +#define QSERDES_COM_PLL_AMP_OS (0x0B0) +#define QSERDES_COM_SSC_EN_CENTER (0x0B4) +#define QSERDES_COM_SSC_ADJ_PER1 (0x0B8) +#define QSERDES_COM_SSC_ADJ_PER2 (0x0BC) +#define QSERDES_COM_SSC_PER1 (0x0C0) +#define QSERDES_COM_SSC_PER2 (0x0C4) +#define QSERDES_COM_SSC_STEP_SIZE1 (0x0C8) +#define QSERDES_COM_SSC_STEP_SIZE2 (0x0CC) +#define QSERDES_COM_RES_CODE_UP (0x0D0) +#define QSERDES_COM_RES_CODE_DN (0x0D4) +#define QSERDES_COM_RES_CODE_UP_OFFSET (0x0D8) +#define QSERDES_COM_RES_CODE_DN_OFFSET (0x0DC) +#define QSERDES_COM_RES_CODE_START_SEG1 (0x0E0) +#define QSERDES_COM_RES_CODE_START_SEG2 (0x0E4) +#define QSERDES_COM_RES_CODE_CAL_CSR (0x0E8) +#define QSERDES_COM_RES_CODE (0x0EC) +#define QSERDES_COM_RES_TRIM_CONTROL (0x0F0) +#define QSERDES_COM_RES_TRIM_CONTROL2 (0x0F4) +#define QSERDES_COM_RES_TRIM_EN_VCOCALDONE (0x0F8) +#define QSERDES_COM_FAUX_EN (0x0FC) +#define QSERDES_COM_DIV_FRAC_START1 (0x100) +#define QSERDES_COM_DIV_FRAC_START2 (0x104) +#define QSERDES_COM_DIV_FRAC_START3 (0x108) +#define QSERDES_COM_DEC_START2 (0x10C) +#define QSERDES_COM_PLL_RXTXEPCLK_EN (0x110) +#define QSERDES_COM_PLL_CRCTRL (0x114) +#define QSERDES_COM_PLL_CLKEPDIV (0x118) +#define QSERDES_COM_PLL_FREQUPDATE (0x11C) +#define QSERDES_COM_PLL_BKGCAL_TRIM_UP (0x120) +#define QSERDES_COM_PLL_BKGCAL_TRIM_DN (0x124) +#define QSERDES_COM_PLL_BKGCAL_TRIM_MUX (0x128) +#define QSERDES_COM_PLL_BKGCAL_VREF_CFG (0x12C) +#define QSERDES_COM_PLL_BKGCAL_DIV_REF1 (0x130) +#define QSERDES_COM_PLL_BKGCAL_DIV_REF2 (0x134) +#define QSERDES_COM_MUXADDR (0x138) +#define QSERDES_COM_LOW_POWER_RO_CONTROL (0x13C) +#define QSERDES_COM_POST_DIVIDER_CONTROL (0x140) +#define QSERDES_COM_HR_OCLK2_DIVIDER (0x144) +#define QSERDES_COM_HR_OCLK3_DIVIDER (0x148) +#define QSERDES_COM_PLL_VCO_HIGH (0x14C) +#define QSERDES_COM_RESET_SM (0x150) +#define QSERDES_COM_MUXVAL (0x154) +#define QSERDES_COM_CORE_RES_CODE_DN (0x158) +#define QSERDES_COM_CORE_RES_CODE_UP (0x15C) +#define QSERDES_COM_CORE_VCO_TUNE (0x160) +#define QSERDES_COM_CORE_VCO_TAIL (0x164) +#define QSERDES_COM_CORE_KVCO_CODE (0x168) + +/* Tx Channel 0 REGISTERS */ +#define QSERDES_TX_L0_BIST_MODE_LANENO (0x00) +#define QSERDES_TX_L0_CLKBUF_ENABLE (0x04) +#define QSERDES_TX_L0_TX_EMP_POST1_LVL (0x08) +#define QSERDES_TX_L0_TX_DRV_LVL (0x0C) +#define QSERDES_TX_L0_RESET_TSYNC_EN (0x10) +#define QSERDES_TX_L0_LPB_EN (0x14) +#define QSERDES_TX_L0_RES_CODE_UP (0x18) +#define QSERDES_TX_L0_RES_CODE_DN (0x1C) +#define QSERDES_TX_L0_PERL_LENGTH1 (0x20) +#define QSERDES_TX_L0_PERL_LENGTH2 (0x24) +#define QSERDES_TX_L0_SERDES_BYP_EN_OUT (0x28) +#define QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN (0x2C) +#define QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN (0x30) +#define QSERDES_TX_L0_BIST_PATTERN1 (0x34) +#define QSERDES_TX_L0_BIST_PATTERN2 (0x38) +#define QSERDES_TX_L0_BIST_PATTERN3 (0x3C) +#define QSERDES_TX_L0_BIST_PATTERN4 (0x40) +#define QSERDES_TX_L0_BIST_PATTERN5 (0x44) +#define QSERDES_TX_L0_BIST_PATTERN6 (0x48) +#define QSERDES_TX_L0_BIST_PATTERN7 (0x4C) +#define QSERDES_TX_L0_BIST_PATTERN8 (0x50) +#define QSERDES_TX_L0_LANE_MODE (0x54) +#define QSERDES_TX_L0_IDAC_CAL_LANE_MODE (0x58) +#define QSERDES_TX_L0_IDAC_CAL_LANE_MODE_CONFIGURATION (0x5C) +#define QSERDES_TX_L0_ATB_SEL1 (0x60) +#define QSERDES_TX_L0_ATB_SEL2 (0x64) +#define QSERDES_TX_L0_RCV_DETECT_LVL (0x68) +#define QSERDES_TX_L0_PRBS_SEED1 (0x6C) +#define QSERDES_TX_L0_PRBS_SEED2 (0x70) +#define QSERDES_TX_L0_PRBS_SEED3 (0x74) +#define QSERDES_TX_L0_PRBS_SEED4 (0x78) +#define QSERDES_TX_L0_RESET_GEN (0x7C) +#define QSERDES_TX_L0_TRAN_DRVR_EMP_EN (0x80) +#define QSERDES_TX_L0_TX_INTERFACE_MODE (0x84) +#define QSERDES_TX_L0_PWM_CTRL (0x88) +#define QSERDES_TX_L0_PWM_DATA (0x8C) +#define QSERDES_TX_L0_PWM_ENC_DIV_CTRL (0x90) +#define QSERDES_TX_L0_VMODE_CTRL1 (0x94) +#define QSERDES_TX_L0_VMODE_CTRL2 (0x98) +#define QSERDES_TX_L0_VMODE_CTRL3 (0x9C) +#define QSERDES_TX_L0_VMODE_CTRL4 (0xA0) +#define QSERDES_TX_L0_VMODE_CTRL5 (0xA4) +#define QSERDES_TX_L0_VMODE_CTRL6 (0xA8) +#define QSERDES_TX_L0_VMODE_CTRL7 (0xAC) +#define QSERDES_TX_L0_TX_ALOG_INTF_OBSV_CNTL (0xB0) +#define QSERDES_TX_L0_BIST_STATUS (0xB4) +#define QSERDES_TX_L0_BIST_ERROR_COUNT1 (0xB8) +#define QSERDES_TX_L0_BIST_ERROR_COUNT2 (0xBC) +#define QSERDES_TX_L0_TX_ALOG_INTF_OBSV (0xC0) +#define QSERDES_TX_L0_PWM_DEC_STATUS (0xC4) + +/* Tx Channel 1 REGISTERS */ +#define QSERDES_TX_L1_BIST_MODE_LANENO (0x00) +#define QSERDES_TX_L1_CLKBUF_ENABLE (0x04) +#define QSERDES_TX_L1_TX_EMP_POST1_LVL (0x08) +#define QSERDES_TX_L1_TX_DRV_LVL (0x0C) +#define QSERDES_TX_L1_RESET_TSYNC_EN (0x10) +#define QSERDES_TX_L1_LPB_EN (0x14) +#define QSERDES_TX_L1_RES_CODE_UP (0x18) +#define QSERDES_TX_L1_RES_CODE_DN (0x1C) +#define QSERDES_TX_L1_PERL_LENGTH1 (0x20) +#define QSERDES_TX_L1_PERL_LENGTH2 (0x24) +#define QSERDES_TX_L1_SERDES_BYP_EN_OUT (0x28) +#define QSERDES_TX_L1_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN (0x2C) +#define QSERDES_TX_L1_PARRATE_REC_DETECT_IDLE_EN (0x30) +#define QSERDES_TX_L1_BIST_PATTERN1 (0x34) +#define QSERDES_TX_L1_BIST_PATTERN2 (0x38) +#define QSERDES_TX_L1_BIST_PATTERN3 (0x3C) +#define QSERDES_TX_L1_BIST_PATTERN4 (0x40) +#define QSERDES_TX_L1_BIST_PATTERN5 (0x44) +#define QSERDES_TX_L1_BIST_PATTERN6 (0x48) +#define QSERDES_TX_L1_BIST_PATTERN7 (0x4C) +#define QSERDES_TX_L1_BIST_PATTERN8 (0x50) +#define QSERDES_TX_L1_LANE_MODE (0x54) +#define QSERDES_TX_L1_IDAC_CAL_LANE_MODE (0x58) +#define QSERDES_TX_L1_IDAC_CAL_LANE_MODE_CONFIGURATION (0x5C) +#define QSERDES_TX_L1_ATB_SEL1 (0x60) +#define QSERDES_TX_L1_ATB_SEL2 (0x64) +#define QSERDES_TX_L1_RCV_DETECT_LVL (0x68) +#define QSERDES_TX_L1_PRBS_SEED1 (0x6C) +#define QSERDES_TX_L1_PRBS_SEED2 (0x70) +#define QSERDES_TX_L1_PRBS_SEED3 (0x74) +#define QSERDES_TX_L1_PRBS_SEED4 (0x78) +#define QSERDES_TX_L1_RESET_GEN (0x7C) +#define QSERDES_TX_L1_TRAN_DRVR_EMP_EN (0x80) +#define QSERDES_TX_L1_TX_INTERFACE_MODE (0x84) +#define QSERDES_TX_L1_PWM_CTRL (0x88) +#define QSERDES_TX_L1_PWM_DATA (0x8C) +#define QSERDES_TX_L1_PWM_ENC_DIV_CTRL (0x90) +#define QSERDES_TX_L1_VMODE_CTRL1 (0x94) +#define QSERDES_TX_L1_VMODE_CTRL2 (0x98) +#define QSERDES_TX_L1_VMODE_CTRL3 (0x9C) +#define QSERDES_TX_L1_VMODE_CTRL4 (0xA0) +#define QSERDES_TX_L1_VMODE_CTRL5 (0xA4) +#define QSERDES_TX_L1_VMODE_CTRL6 (0xA8) +#define QSERDES_TX_L1_VMODE_CTRL7 (0xAC) +#define QSERDES_TX_L1_TX_ALOG_INTF_OBSV_CNTL (0xB0) +#define QSERDES_TX_L1_BIST_STATUS (0xB4) +#define QSERDES_TX_L1_BIST_ERROR_COUNT1 (0xB8) +#define QSERDES_TX_L1_BIST_ERROR_COUNT2 (0xBC) +#define QSERDES_TX_L1_TX_ALOG_INTF_OBSV (0xC0) +#define QSERDES_TX_L1_PWM_DEC_STATUS (0xC4) + +/* Tx Channel 2 REGISERS */ +#define QSERDES_TX_L2_BIST_MODE_LANENO (0x00) +#define QSERDES_TX_L2_CLKBUF_ENABLE (0x04) +#define QSERDES_TX_L2_TX_EMP_POST1_LVL (0x08) +#define QSERDES_TX_L2_TX_DRV_LVL (0x0C) +#define QSERDES_TX_L2_RESET_TSYNC_EN (0x10) +#define QSERDES_TX_L2_LPB_EN (0x14) +#define QSERDES_TX_L2_RES_CODE_UP (0x18) +#define QSERDES_TX_L2_RES_CODE_DN (0x1C) +#define QSERDES_TX_L2_PERL_LENGTH1 (0x20) +#define QSERDES_TX_L2_PERL_LENGTH2 (0x24) +#define QSERDES_TX_L2_SERDES_BYP_EN_OUT (0x28) +#define QSERDES_TX_L2_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN (0x2C) +#define QSERDES_TX_L2_PARRATE_REC_DETECT_IDLE_EN (0x30) +#define QSERDES_TX_L2_BIST_PATTERN1 (0x34) +#define QSERDES_TX_L2_BIST_PATTERN2 (0x38) +#define QSERDES_TX_L2_BIST_PATTERN3 (0x3C) +#define QSERDES_TX_L2_BIST_PATTERN4 (0x40) +#define QSERDES_TX_L2_BIST_PATTERN5 (0x44) +#define QSERDES_TX_L2_BIST_PATTERN6 (0x48) +#define QSERDES_TX_L2_BIST_PATTERN7 (0x4C) +#define QSERDES_TX_L2_BIST_PATTERN8 (0x50) +#define QSERDES_TX_L2_LANE_MODE (0x54) +#define QSERDES_TX_L2_IDAC_CAL_LANE_MODE (0x58) +#define QSERDES_TX_L2_IDAC_CAL_LANE_MODE_CONFIGURATION (0x5C) +#define QSERDES_TX_L2_ATB_SEL1 (0x60) +#define QSERDES_TX_L2_ATB_SEL2 (0x64) +#define QSERDES_TX_L2_RCV_DETECT_LVL (0x68) +#define QSERDES_TX_L2_PRBS_SEED1 (0x6C) +#define QSERDES_TX_L2_PRBS_SEED2 (0x70) +#define QSERDES_TX_L2_PRBS_SEED3 (0x74) +#define QSERDES_TX_L2_PRBS_SEED4 (0x78) +#define QSERDES_TX_L2_RESET_GEN (0x7C) +#define QSERDES_TX_L2_TRAN_DRVR_EMP_EN (0x80) +#define QSERDES_TX_L2_TX_INTERFACE_MODE (0x84) +#define QSERDES_TX_L2_PWM_CTRL (0x88) +#define QSERDES_TX_L2_PWM_DATA (0x8C) +#define QSERDES_TX_L2_PWM_ENC_DIV_CTRL (0x90) +#define QSERDES_TX_L2_VMODE_CTRL1 (0x94) +#define QSERDES_TX_L2_VMODE_CTRL2 (0x98) +#define QSERDES_TX_L2_VMODE_CTRL3 (0x9C) +#define QSERDES_TX_L2_VMODE_CTRL4 (0xA0) +#define QSERDES_TX_L2_VMODE_CTRL5 (0xA4) +#define QSERDES_TX_L2_VMODE_CTRL6 (0xA8) +#define QSERDES_TX_L2_VMODE_CTRL7 (0xAC) +#define QSERDES_TX_L2_TX_ALOG_INTF_OBSV_CNTL (0xB0) +#define QSERDES_TX_L2_BIST_STATUS (0xB4) +#define QSERDES_TX_L2_BIST_ERROR_COUNT1 (0xB8) +#define QSERDES_TX_L2_BIST_ERROR_COUNT2 (0xBC) +#define QSERDES_TX_L2_TX_ALOG_INTF_OBSV (0xC0) +#define QSERDES_TX_L2_PWM_DEC_STATUS (0xC4) + +/* Tx Channel 3 REGISERS */ +#define QSERDES_TX_L3_BIST_MODE_LANENO (0x00) +#define QSERDES_TX_L3_CLKBUF_ENABLE (0x04) +#define QSERDES_TX_L3_TX_EMP_POST1_LVL (0x08) +#define QSERDES_TX_L3_TX_DRV_LVL (0x0C) +#define QSERDES_TX_L3_RESET_TSYNC_EN (0x10) +#define QSERDES_TX_L3_LPB_EN (0x14) +#define QSERDES_TX_L3_RES_CODE_UP (0x18) +#define QSERDES_TX_L3_RES_CODE_DN (0x1C) +#define QSERDES_TX_L3_PERL_LENGTH1 (0x20) +#define QSERDES_TX_L3_PERL_LENGTH2 (0x24) +#define QSERDES_TX_L3_SERDES_BYP_EN_OUT (0x28) +#define QSERDES_TX_L3_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN (0x2C) +#define QSERDES_TX_L3_PARRATE_REC_DETECT_IDLE_EN (0x30) +#define QSERDES_TX_L3_BIST_PATTERN1 (0x34) +#define QSERDES_TX_L3_BIST_PATTERN2 (0x38) +#define QSERDES_TX_L3_BIST_PATTERN3 (0x3C) +#define QSERDES_TX_L3_BIST_PATTERN4 (0x40) +#define QSERDES_TX_L3_BIST_PATTERN5 (0x44) +#define QSERDES_TX_L3_BIST_PATTERN6 (0x48) +#define QSERDES_TX_L3_BIST_PATTERN7 (0x4C) +#define QSERDES_TX_L3_BIST_PATTERN8 (0x50) +#define QSERDES_TX_L3_LANE_MODE (0x54) +#define QSERDES_TX_L3_IDAC_CAL_LANE_MODE (0x58) +#define QSERDES_TX_L3_IDAC_CAL_LANE_MODE_CONFIGURATION (0x5C) +#define QSERDES_TX_L3_ATB_SEL1 (0x60) +#define QSERDES_TX_L3_ATB_SEL2 (0x64) +#define QSERDES_TX_L3_RCV_DETECT_LVL (0x68) +#define QSERDES_TX_L3_PRBS_SEED1 (0x6C) +#define QSERDES_TX_L3_PRBS_SEED2 (0x70) +#define QSERDES_TX_L3_PRBS_SEED3 (0x74) +#define QSERDES_TX_L3_PRBS_SEED4 (0x78) +#define QSERDES_TX_L3_RESET_GEN (0x7C) +#define QSERDES_TX_L3_TRAN_DRVR_EMP_EN (0x80) +#define QSERDES_TX_L3_TX_INTERFACE_MODE (0x84) +#define QSERDES_TX_L3_PWM_CTRL (0x88) +#define QSERDES_TX_L3_PWM_DATA (0x8C) +#define QSERDES_TX_L3_PWM_ENC_DIV_CTRL (0x90) +#define QSERDES_TX_L3_VMODE_CTRL1 (0x94) +#define QSERDES_TX_L3_VMODE_CTRL2 (0x98) +#define QSERDES_TX_L3_VMODE_CTRL3 (0x9C) +#define QSERDES_TX_L3_VMODE_CTRL4 (0xA0) +#define QSERDES_TX_L3_VMODE_CTRL5 (0xA4) +#define QSERDES_TX_L3_VMODE_CTRL6 (0xA8) +#define QSERDES_TX_L3_VMODE_CTRL7 (0xAC) +#define QSERDES_TX_L3_TX_ALOG_INTF_OBSV_CNTL (0xB0) +#define QSERDES_TX_L3_BIST_STATUS (0xB4) +#define QSERDES_TX_L3_BIST_ERROR_COUNT1 (0xB8) +#define QSERDES_TX_L3_BIST_ERROR_COUNT2 (0xBC) +#define QSERDES_TX_L3_TX_ALOG_INTF_OBSV (0xC0) +#define QSERDES_TX_L3_PWM_DEC_STATUS (0xC4) + +/* HDMI PHY REGISTERS */ +#define HDMI_PHY_CFG (0x00) +#define HDMI_PHY_PD_CTL (0x04) +#define HDMI_PHY_MODE (0x08) +#define HDMI_PHY_MISR_CLEAR (0x0C) +#define HDMI_PHY_TX0_TX1_BIST_CFG0 (0x10) +#define HDMI_PHY_TX0_TX1_BIST_CFG1 (0x14) +#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE0 (0x18) +#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE1 (0x1C) +#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE2 (0x20) +#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE3 (0x24) +#define HDMI_PHY_TX0_TX1_PRBS_POLY_BYTE0 (0x28) +#define HDMI_PHY_TX0_TX1_PRBS_POLY_BYTE1 (0x2C) +#define HDMI_PHY_TX0_TX1_PRBS_POLY_BYTE2 (0x30) +#define HDMI_PHY_TX0_TX1_PRBS_POLY_BYTE3 (0x34) +#define HDMI_PHY_TX2_TX3_BIST_CFG0 (0x38) +#define HDMI_PHY_TX2_TX3_BIST_CFG1 (0x3C) +#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE0 (0x40) +#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE1 (0x44) +#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE2 (0x48) +#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE3 (0x4C) +#define HDMI_PHY_TX2_TX3_PRBS_POLY_BYTE0 (0x50) +#define HDMI_PHY_TX2_TX3_PRBS_POLY_BYTE1 (0x54) +#define HDMI_PHY_TX2_TX3_PRBS_POLY_BYTE2 (0x58) +#define HDMI_PHY_TX2_TX3_PRBS_POLY_BYTE3 (0x5C) +#define HDMI_PHY_DEBUG_BUS_SEL (0x60) +#define HDMI_PHY_TXCAL_CFG0 (0x64) +#define HDMI_PHY_TXCAL_CFG1 (0x68) +#define HDMI_PHY_TX0_TX1_BIST_STATUS0 (0x6C) +#define HDMI_PHY_TX0_TX1_BIST_STATUS1 (0x70) +#define HDMI_PHY_TX0_TX1_BIST_STATUS2 (0x74) +#define HDMI_PHY_TX2_TX3_BIST_STATUS0 (0x78) +#define HDMI_PHY_TX2_TX3_BIST_STATUS1 (0x7C) +#define HDMI_PHY_TX2_TX3_BIST_STATUS2 (0x80) +#define HDMI_PHY_PRE_MISR_STATUS0 (0x84) +#define HDMI_PHY_PRE_MISR_STATUS1 (0x88) +#define HDMI_PHY_PRE_MISR_STATUS2 (0x8C) +#define HDMI_PHY_PRE_MISR_STATUS3 (0x90) +#define HDMI_PHY_POST_MISR_STATUS0 (0x94) +#define HDMI_PHY_POST_MISR_STATUS1 (0x98) +#define HDMI_PHY_POST_MISR_STATUS2 (0x9C) +#define HDMI_PHY_POST_MISR_STATUS3 (0xA0) +#define HDMI_PHY_STATUS (0xA4) +#define HDMI_PHY_MISC3_STATUS (0xA8) +#define HDMI_PHY_DEBUG_BUS0 (0xAC) +#define HDMI_PHY_DEBUG_BUS1 (0xB0) +#define HDMI_PHY_DEBUG_BUS2 (0xB4) +#define HDMI_PHY_DEBUG_BUS3 (0xB8) +#define HDMI_PHY_REVISION_ID0 (0xBC) +#define HDMI_PHY_REVISION_ID1 (0xC0) +#define HDMI_PHY_REVISION_ID2 (0xC4) +#define HDMI_PHY_REVISION_ID3 (0xC8) + +#define HDMI_PLL_POLL_DELAY_US 50 +#define HDMI_PLL_POLL_TIMEOUT_US 125000 +#define HDMI_PLL_REF_CLK_RATE 192ULL +#define HDMI_PLL_DIVISOR 10000000000ULL +#define HDMI_PLL_DIVISOR_32 100000U +#define HDMI_PLL_MIN_VCO_CLK 160000000ULL +#define HDMI_PLL_TMDS_MAX 800000000U + + +static int hdmi_20nm_pll_lock_status(struct mdss_pll_resources *io) +{ + u32 status; + int pll_locked = 0; + int phy_ready = 0; + int rc; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + /* Poll for C_READY and PHY READY */ + pr_debug("%s: Waiting for PHY Ready\n", __func__); + + /* poll for PLL ready status */ + if (!readl_poll_timeout_atomic( + (io->pll_base + QSERDES_COM_RESET_SM), + status, ((status & BIT(6)) == 1), + HDMI_PLL_POLL_DELAY_US, + HDMI_PLL_POLL_TIMEOUT_US)) { + pr_debug("%s: C READY\n", __func__); + pll_locked = 1; + } else { + pr_debug("%s: C READY TIMEOUT\n", __func__); + pll_locked = 0; + } + + /* poll for PHY ready status */ + if (pll_locked && !readl_poll_timeout_atomic( + (io->phy_base + HDMI_PHY_STATUS), + status, ((status & BIT(0)) == 1), + HDMI_PLL_POLL_DELAY_US, + HDMI_PLL_POLL_TIMEOUT_US)) { + pr_debug("%s: PHY READY\n", __func__); + phy_ready = 1; + } else { + pr_debug("%s: PHY READY TIMEOUT\n", __func__); + phy_ready = 0; + } + mdss_pll_resource_enable(io, false); + + return phy_ready; +} + +static inline struct hdmi_pll_vco_clk *to_hdmi_20nm_vco_clk(struct clk *clk) +{ + return container_of(clk, struct hdmi_pll_vco_clk, c); +} + +static inline u32 hdmi_20nm_phy_pll_vco_reg_val(struct hdmi_pll_cfg *pll_cfg, + u32 tmds_clk) +{ + u32 index = 0; + + while (pll_cfg[index].vco_rate < HDMI_PLL_TMDS_MAX && + pll_cfg[index].vco_rate < tmds_clk) + index++; + return pll_cfg[index].reg; +} + +static void hdmi_20nm_phy_pll_calc_settings(struct mdss_pll_resources *io, + struct hdmi_pll_vco_clk *vco, u32 vco_clk, u32 tmds_clk) +{ + u32 val = 0; + u64 dec_start_val, frac_start_val, pll_lock_cmp; + + /* Calculate decimal and fractional values */ + dec_start_val = 1000000UL * vco_clk; + do_div(dec_start_val, HDMI_PLL_REF_CLK_RATE); + do_div(dec_start_val, 2U); + frac_start_val = dec_start_val; + do_div(frac_start_val, HDMI_PLL_DIVISOR_32); + do_div(frac_start_val, HDMI_PLL_DIVISOR_32); + frac_start_val *= HDMI_PLL_DIVISOR; + frac_start_val = dec_start_val - frac_start_val; + frac_start_val *= (u64)(2 << 19); + do_div(frac_start_val, HDMI_PLL_DIVISOR_32); + do_div(frac_start_val, HDMI_PLL_DIVISOR_32); + pll_lock_cmp = dec_start_val; + do_div(pll_lock_cmp, 10U); + pll_lock_cmp *= 0x800; + do_div(pll_lock_cmp, HDMI_PLL_DIVISOR_32); + do_div(pll_lock_cmp, HDMI_PLL_DIVISOR_32); + pll_lock_cmp -= 1U; + do_div(dec_start_val, HDMI_PLL_DIVISOR_32); + do_div(dec_start_val, HDMI_PLL_DIVISOR_32); + + /* PLL loop bandwidth */ + val = hdmi_20nm_phy_pll_vco_reg_val(vco->ip_seti, tmds_clk); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_IP_SETI, val); + val = hdmi_20nm_phy_pll_vco_reg_val(vco->cp_seti, tmds_clk); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CP_SETI, val); + val = hdmi_20nm_phy_pll_vco_reg_val(vco->cp_setp, tmds_clk); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CP_SETP, val); + val = hdmi_20nm_phy_pll_vco_reg_val(vco->ip_setp, tmds_clk); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_IP_SETP, val); + val = hdmi_20nm_phy_pll_vco_reg_val(vco->crctrl, tmds_clk); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CRCTRL, val); + + /* PLL calibration */ + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START1, + 0x80 | (frac_start_val & 0x7F)); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START2, + 0x80 | ((frac_start_val >> 7) & 0x7F)); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START3, + 0x40 | ((frac_start_val >> 14) & 0x3F)); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEC_START1, + 0x80 | (dec_start_val & 0x7F)); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEC_START2, + 0x02 | (0x01 & (dec_start_val >> 7))); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLLLOCK_CMP1, + pll_lock_cmp & 0xFF); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLLLOCK_CMP2, + (pll_lock_cmp >> 8) & 0xFF); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLLLOCK_CMP3, + (pll_lock_cmp >> 16) & 0xFF); +} + +static u32 hdmi_20nm_phy_pll_set_clk_rate(struct clk *c, u32 tmds_clk) +{ + u32 tx_band = 0; + + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + u64 vco_clk = tmds_clk; + + while (vco_clk > 0 && vco_clk < HDMI_PLL_MIN_VCO_CLK) { + tx_band++; + vco_clk *= 2; + } + + /* Initially shut down PHY */ + pr_debug("%s: Disabling PHY\n", __func__); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x0); + udelay(1000); + /* memory barrier */ + mb(); + + /* power-up and recommended common block settings */ + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x1F); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x01); + udelay(1000); + /* memory barrier */ + mb(); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x07); + udelay(1000); + /* memory barrier */ + mb(); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x05); + udelay(1000); + /* memory barrier */ + mb(); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYS_CLK_CTRL, 0x42); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_VCOTAIL_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MODE, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_IE_TRIM, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_IP_TRIM, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CNTRL, 0x07); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_PHSEL_CONTROL, 0x04); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_IPTAT_TRIM_VCCA_TX_SEL, 0xA0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_PHSEL_DC, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CORE_CLK_IN_SYNC_SEL, 0x00); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_BKG_KVCO_CAL_EN, 0x00); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x0F); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL1, 0x01); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL2, 0x01); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYSCLK_EN_SEL_TXBAND, + 0x4A + (0x10 * tx_band)); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VREF_CFG1, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VREF_CFG2, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BGTC, 0xFF); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_TEST_UPDN, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_VCO_TUNE, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_AMP_OS, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_EN_CENTER, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_CODE_UP, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_CODE_DN, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_KVCO_CODE, + tmds_clk > 300000000 ? 0x3F : 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_KVCO_COUNT1, + tmds_clk > 300000000 ? 0x00 : 0x8A); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_REF1, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_REF2, + tmds_clk > 300000000 ? 0x00 : 0x01); + + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_KVCO_CAL_CNTRL, + tmds_clk > 300000000 ? 0x00 : 0x1F); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VREF_CFG3, + tmds_clk > 300000000 ? 0x00 : 0x40); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VREF_CFG4, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VREF_CFG5, 0x10); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESETSM_CNTRL, + tmds_clk > 300000000 ? 0x80 : 0x00); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_CODE_CAL_CSR, 0x77); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_TRIM_EN_VCOCALDONE, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_RXTXEPCLK_EN, 0x0C); + + hdmi_20nm_phy_pll_calc_settings(io, vco, vco_clk, tmds_clk); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLLLOCK_CMP_EN, 0x11); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CNTRL, 0x07); + + /* Resistor calibration linear search */ + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_CODE_START_SEG1, 0x60); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_CODE_START_SEG2, 0x60); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RES_TRIM_CONTROL, 0x01); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESETSM_CNTRL2, 0x07); + + udelay(1000); + /* memory barrier */ + mb(); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_MODE, tx_band); + + /* TX lanes (transceivers) power-up sequence */ + MDSS_PLL_REG_W(io->pll_base + 0x400, QSERDES_TX_L0_CLKBUF_ENABLE, 0x03); + MDSS_PLL_REG_W(io->pll_base + 0x600, QSERDES_TX_L1_CLKBUF_ENABLE, 0x03); + MDSS_PLL_REG_W(io->pll_base + 0x800, QSERDES_TX_L2_CLKBUF_ENABLE, 0x03); + MDSS_PLL_REG_W(io->pll_base + 0xA00, QSERDES_TX_L3_CLKBUF_ENABLE, 0x03); + + MDSS_PLL_REG_W(io->pll_base + 0x400, + QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + 0x600, + QSERDES_TX_L1_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + 0x800, + QSERDES_TX_L2_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + 0xA00, + QSERDES_TX_L3_TRAN_DRVR_EMP_EN, 0x03); + + MDSS_PLL_REG_W(io->pll_base + 0x400, + QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x6F); + MDSS_PLL_REG_W(io->pll_base + 0x600, + QSERDES_TX_L1_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x6F); + MDSS_PLL_REG_W(io->pll_base + 0x800, + QSERDES_TX_L2_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x6F); + MDSS_PLL_REG_W(io->pll_base + 0xA00, + QSERDES_TX_L3_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, 0x6F); + + MDSS_PLL_REG_W(io->pll_base + 0x400, + QSERDES_TX_L0_TX_EMP_POST1_LVL, 0x0000002F); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TXCAL_CFG0, 0x000000AF); + + MDSS_PLL_REG_W(io->pll_base + 0x400, QSERDES_TX_L0_VMODE_CTRL1, 0x08); + MDSS_PLL_REG_W(io->pll_base + 0x800, QSERDES_TX_L2_VMODE_CTRL1, 0x09); + MDSS_PLL_REG_W(io->pll_base + 0x400, QSERDES_TX_L0_VMODE_CTRL5, 0xA0); + MDSS_PLL_REG_W(io->pll_base + 0x400, QSERDES_TX_L0_VMODE_CTRL6, 0x01); + MDSS_PLL_REG_W(io->pll_base + 0x800, QSERDES_TX_L2_VMODE_CTRL5, 0xA0); + MDSS_PLL_REG_W(io->pll_base + 0x800, QSERDES_TX_L2_VMODE_CTRL6, 0x01); + + MDSS_PLL_REG_W(io->pll_base + 0x400, + QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + 0x400, + QSERDES_TX_L0_TX_INTERFACE_MODE, 0x00); + MDSS_PLL_REG_W(io->pll_base + 0x600, + QSERDES_TX_L1_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + 0x600, + QSERDES_TX_L1_TX_INTERFACE_MODE, 0x00); + MDSS_PLL_REG_W(io->pll_base + 0x800, + QSERDES_TX_L2_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + 0x800, + QSERDES_TX_L2_TX_INTERFACE_MODE, 0x00); + MDSS_PLL_REG_W(io->pll_base + 0xA00, + QSERDES_TX_L3_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + 0xA00, + QSERDES_TX_L3_TX_INTERFACE_MODE, 0x00); + + return 0; +} + +static int hdmi_20nm_vco_enable(struct clk *c) +{ + u32 ready_poll; + u32 time_out_loop; + /* Hardware recommended timeout iterator */ + u32 time_out_max = 50000; + + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x00000000); + udelay(100); + /* memory barrier */ + mb(); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x00000003); + udelay(100); + /* memory barrier */ + mb(); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x00000009); + udelay(100); + /* memory barrier */ + mb(); + + /* Poll for C_READY and PHY READY */ + pr_debug("%s: Waiting for PHY Ready\n", __func__); + time_out_loop = 0; + do { + ready_poll = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_RESET_SM); + time_out_loop++; + udelay(10); + } while (((ready_poll & (1 << 6)) == 0) && + (time_out_loop < time_out_max)); + if (time_out_loop >= time_out_max) + pr_err("%s: ERROR: TIMED OUT BEFORE C READY\n", __func__); + else + pr_debug("%s: C READY\n", __func__); + + /* Poll for PHY READY */ + pr_debug("%s: Waiting for PHY Ready\n", __func__); + time_out_loop = 0; + do { + ready_poll = MDSS_PLL_REG_R(io->phy_base, HDMI_PHY_STATUS); + time_out_loop++; + udelay(1); + } while (((ready_poll & 0x1) == 0) && (time_out_loop < time_out_max)); + + if (time_out_loop >= time_out_max) + pr_err("%s: TIMED OUT BEFORE PHY READY\n", __func__); + else + pr_debug("%s: HDMI PHY READY\n", __func__); + + io->pll_on = true; + + return 0; +} + + +static int hdmi_20nm_vco_set_rate(struct clk *c, unsigned long rate) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + void __iomem *pll_base; + void __iomem *phy_base; + unsigned int set_power_dwn = 0; + int rc; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + if (io->pll_on) + set_power_dwn = 1; + + pll_base = io->pll_base; + phy_base = io->phy_base; + + pr_debug("rate=%ld\n", rate); + + hdmi_20nm_phy_pll_set_clk_rate(c, rate); + + mdss_pll_resource_enable(io, false); + + if (set_power_dwn) + hdmi_20nm_vco_enable(c); + + vco->rate = rate; + vco->rate_set = true; + + return 0; +} + +static unsigned long hdmi_20nm_vco_get_rate(struct clk *c) +{ + unsigned long freq = 0; + int rc; + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + if (is_gdsc_disabled(io)) + return 0; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + mdss_pll_resource_enable(io, false); + + return freq; +} + +static long hdmi_20nm_vco_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + + pr_debug("rrate=%ld\n", rrate); + + return rrate; +} + +static int hdmi_20nm_vco_prepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + int ret = 0; + + pr_debug("rate=%ld\n", vco->rate); + + if (!vco->rate_set && vco->rate) + ret = hdmi_20nm_vco_set_rate(c, vco->rate); + + if (!ret) { + ret = mdss_pll_resource_enable(io, true); + if (ret) + pr_err("pll resource can't be enabled\n"); + } + + return ret; +} + +static void hdmi_20nm_vco_unprepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + vco->rate_set = false; + + if (!io) { + pr_err("Invalid input parameter\n"); + return; + } + + if (!io->pll_on && + mdss_pll_resource_enable(io, true)) { + pr_err("pll resource can't be enabled\n"); + return; + } + + io->handoff_resources = false; + mdss_pll_resource_enable(io, false); + io->pll_on = false; +} + +static enum handoff hdmi_20nm_vco_handoff(struct clk *c) +{ + enum handoff ret = HANDOFF_DISABLED_CLK; + struct hdmi_pll_vco_clk *vco = to_hdmi_20nm_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + if (is_gdsc_disabled(io)) + return HANDOFF_DISABLED_CLK; + + if (mdss_pll_resource_enable(io, true)) { + pr_err("pll resource can't be enabled\n"); + return ret; + } + + io->handoff_resources = true; + + if (hdmi_20nm_pll_lock_status(io)) { + io->pll_on = true; + c->rate = hdmi_20nm_vco_get_rate(c); + ret = HANDOFF_ENABLED_CLK; + } else { + io->handoff_resources = false; + mdss_pll_resource_enable(io, false); + } + + pr_debug("done, ret=%d\n", ret); + return ret; +} + +static const struct clk_ops hdmi_20nm_vco_clk_ops = { + .enable = hdmi_20nm_vco_enable, + .set_rate = hdmi_20nm_vco_set_rate, + .get_rate = hdmi_20nm_vco_get_rate, + .round_rate = hdmi_20nm_vco_round_rate, + .prepare = hdmi_20nm_vco_prepare, + .unprepare = hdmi_20nm_vco_unprepare, + .handoff = hdmi_20nm_vco_handoff, +}; + +static struct hdmi_pll_vco_clk hdmi_20nm_vco_clk = { + .ip_seti = (struct hdmi_pll_cfg[]){ + {550890000, 0x03}, + {589240000, 0x07}, + {689290000, 0x03}, + {727600000, 0x07}, + {HDMI_PLL_TMDS_MAX, 0x03}, + }, + .cp_seti = (struct hdmi_pll_cfg[]){ + {34440000, 0x3F}, + {36830000, 0x2F}, + {68870000, 0x3F}, + {73660000, 0x2F}, + {137730000, 0x3F}, + {147310000, 0x2F}, + {275450000, 0x3F}, + {294620000, 0x2F}, + {344650000, 0x3F}, + {363800000, 0x2F}, + {477960000, 0x3F}, + {512530000, 0x2F}, + {550890000, 0x1F}, + {589240000, 0x2F}, + {630900000, 0x3F}, + {650590000, 0x2F}, + {689290000, 0x1F}, + {727600000, 0x2F}, + {HDMI_PLL_TMDS_MAX, 0x3F}, + }, + .ip_setp = (struct hdmi_pll_cfg[]){ + {497340000, 0x03}, + {512530000, 0x07}, + {535680000, 0x03}, + {550890000, 0x07}, + {574060000, 0x03}, + {727600000, 0x07}, + {HDMI_PLL_TMDS_MAX, 0x03}, + }, + .cp_setp = (struct hdmi_pll_cfg[]){ + {36830000, 0x1F}, + {40010000, 0x17}, + {73660000, 0x1F}, + {80000000, 0x17}, + {147310000, 0x1F}, + {160010000, 0x17}, + {294620000, 0x1F}, + {363800000, 0x17}, + {497340000, 0x0F}, + {512530000, 0x1F}, + {535680000, 0x0F}, + {550890000, 0x1F}, + {574060000, 0x0F}, + {589240000, 0x1F}, + {727600000, 0x17}, + {HDMI_PLL_TMDS_MAX, 0x07}, + }, + .crctrl = (struct hdmi_pll_cfg[]){ + {40010000, 0xBB}, + {40030000, 0x77}, + {80000000, 0xBB}, + {80060000, 0x77}, + {160010000, 0xBB}, + {160120000, 0x77}, + {772930000, 0xBB}, + {HDMI_PLL_TMDS_MAX, 0xFF}, + }, + .c = { + .dbg_name = "hdmi_20nm_vco_clk", + .ops = &hdmi_20nm_vco_clk_ops, + CLK_INIT(hdmi_20nm_vco_clk.c), + }, +}; + +static struct clk_lookup hdmipllcc_8994[] = { + CLK_LIST(hdmi_20nm_vco_clk), +}; + +int hdmi_20nm_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = -ENOTSUPP; + + if (!pll_res || !pll_res->phy_base || !pll_res->pll_base) { + pr_err("Invalid input parameters\n"); + return -EPROBE_DEFER; + } + + /* Set client data for vco, mux and div clocks */ + hdmi_20nm_vco_clk.priv = pll_res; + + rc = of_msm_clock_register(pdev->dev.of_node, hdmipllcc_8994, + ARRAY_SIZE(hdmipllcc_8994)); + if (rc) { + pr_err("Clock register failed rc=%d\n", rc); + rc = -EPROBE_DEFER; + } else { + pr_debug("%s: SUCCESS\n", __func__); + } + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-hdmi-pll-28hpm.c b/drivers/clk/qcom/mdss/mdss-hdmi-pll-28hpm.c new file mode 100644 index 0000000000000000000000000000000000000000..9a3525ae502af195f4114979cd93f51feb61537d --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-hdmi-pll-28hpm.c @@ -0,0 +1,1104 @@ +/* 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) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-hdmi-pll.h" + +/* hdmi phy registers */ +#define HDMI_PHY_ANA_CFG0 (0x0000) +#define HDMI_PHY_ANA_CFG1 (0x0004) +#define HDMI_PHY_ANA_CFG2 (0x0008) +#define HDMI_PHY_ANA_CFG3 (0x000C) +#define HDMI_PHY_PD_CTRL0 (0x0010) +#define HDMI_PHY_PD_CTRL1 (0x0014) +#define HDMI_PHY_GLB_CFG (0x0018) +#define HDMI_PHY_DCC_CFG0 (0x001C) +#define HDMI_PHY_DCC_CFG1 (0x0020) +#define HDMI_PHY_TXCAL_CFG0 (0x0024) +#define HDMI_PHY_TXCAL_CFG1 (0x0028) +#define HDMI_PHY_TXCAL_CFG2 (0x002C) +#define HDMI_PHY_TXCAL_CFG3 (0x0030) +#define HDMI_PHY_BIST_CFG0 (0x0034) +#define HDMI_PHY_BIST_CFG1 (0x0038) +#define HDMI_PHY_BIST_PATN0 (0x003C) +#define HDMI_PHY_BIST_PATN1 (0x0040) +#define HDMI_PHY_BIST_PATN2 (0x0044) +#define HDMI_PHY_BIST_PATN3 (0x0048) +#define HDMI_PHY_STATUS (0x005C) + +/* hdmi phy unified pll registers */ +#define HDMI_UNI_PLL_REFCLK_CFG (0x0000) +#define HDMI_UNI_PLL_POSTDIV1_CFG (0x0004) +#define HDMI_UNI_PLL_CHFPUMP_CFG (0x0008) +#define HDMI_UNI_PLL_VCOLPF_CFG (0x000C) +#define HDMI_UNI_PLL_VREG_CFG (0x0010) +#define HDMI_UNI_PLL_PWRGEN_CFG (0x0014) +#define HDMI_UNI_PLL_GLB_CFG (0x0020) +#define HDMI_UNI_PLL_POSTDIV2_CFG (0x0024) +#define HDMI_UNI_PLL_POSTDIV3_CFG (0x0028) +#define HDMI_UNI_PLL_LPFR_CFG (0x002C) +#define HDMI_UNI_PLL_LPFC1_CFG (0x0030) +#define HDMI_UNI_PLL_LPFC2_CFG (0x0034) +#define HDMI_UNI_PLL_SDM_CFG0 (0x0038) +#define HDMI_UNI_PLL_SDM_CFG1 (0x003C) +#define HDMI_UNI_PLL_SDM_CFG2 (0x0040) +#define HDMI_UNI_PLL_SDM_CFG3 (0x0044) +#define HDMI_UNI_PLL_SDM_CFG4 (0x0048) +#define HDMI_UNI_PLL_SSC_CFG0 (0x004C) +#define HDMI_UNI_PLL_SSC_CFG1 (0x0050) +#define HDMI_UNI_PLL_SSC_CFG2 (0x0054) +#define HDMI_UNI_PLL_SSC_CFG3 (0x0058) +#define HDMI_UNI_PLL_LKDET_CFG0 (0x005C) +#define HDMI_UNI_PLL_LKDET_CFG1 (0x0060) +#define HDMI_UNI_PLL_LKDET_CFG2 (0x0064) +#define HDMI_UNI_PLL_CAL_CFG0 (0x006C) +#define HDMI_UNI_PLL_CAL_CFG1 (0x0070) +#define HDMI_UNI_PLL_CAL_CFG2 (0x0074) +#define HDMI_UNI_PLL_CAL_CFG3 (0x0078) +#define HDMI_UNI_PLL_CAL_CFG4 (0x007C) +#define HDMI_UNI_PLL_CAL_CFG5 (0x0080) +#define HDMI_UNI_PLL_CAL_CFG6 (0x0084) +#define HDMI_UNI_PLL_CAL_CFG7 (0x0088) +#define HDMI_UNI_PLL_CAL_CFG8 (0x008C) +#define HDMI_UNI_PLL_CAL_CFG9 (0x0090) +#define HDMI_UNI_PLL_CAL_CFG10 (0x0094) +#define HDMI_UNI_PLL_CAL_CFG11 (0x0098) +#define HDMI_UNI_PLL_STATUS (0x00C0) + +#define HDMI_PLL_POLL_DELAY_US 50 +#define HDMI_PLL_POLL_TIMEOUT_US 500 + +static inline struct hdmi_pll_vco_clk *to_hdmi_vco_clk(struct clk *clk) +{ + return container_of(clk, struct hdmi_pll_vco_clk, c); +} + +static void hdmi_vco_disable(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *hdmi_pll_res = vco->priv; + + if (!hdmi_pll_res) { + pr_err("Invalid input parameter\n"); + return; + } + + if (!hdmi_pll_res->pll_on && + mdss_pll_resource_enable(hdmi_pll_res, true)) { + pr_err("pll resource can't be enabled\n"); + return; + } + + MDSS_PLL_REG_W(hdmi_pll_res->pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0); + udelay(5); + MDSS_PLL_REG_W(hdmi_pll_res->phy_base, HDMI_PHY_GLB_CFG, 0x0); + + hdmi_pll_res->handoff_resources = false; + mdss_pll_resource_enable(hdmi_pll_res, false); + hdmi_pll_res->pll_on = false; +} /* hdmi_vco_disable */ + +static int hdmi_vco_enable(struct clk *c) +{ + u32 status; + u32 delay_us, timeout_us; + int rc; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *hdmi_pll_res = vco->priv; + + rc = mdss_pll_resource_enable(hdmi_pll_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + /* Global Enable */ + MDSS_PLL_REG_W(hdmi_pll_res->phy_base, HDMI_PHY_GLB_CFG, 0x81); + /* Power up power gen */ + MDSS_PLL_REG_W(hdmi_pll_res->phy_base, HDMI_PHY_PD_CTRL0, 0x00); + udelay(350); + + /* PLL Power-Up */ + MDSS_PLL_REG_W(hdmi_pll_res->pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + udelay(5); + /* Power up PLL LDO */ + MDSS_PLL_REG_W(hdmi_pll_res->pll_base, HDMI_UNI_PLL_GLB_CFG, 0x03); + udelay(350); + + /* PLL Power-Up */ + MDSS_PLL_REG_W(hdmi_pll_res->pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + udelay(350); + + /* poll for PLL ready status */ + delay_us = 100; + timeout_us = 2000; + if (readl_poll_timeout_atomic( + (hdmi_pll_res->pll_base + HDMI_UNI_PLL_STATUS), + status, ((status & BIT(0)) == 1), delay_us, timeout_us)) { + pr_err("hdmi phy pll status=%x failed to Lock\n", status); + hdmi_vco_disable(c); + mdss_pll_resource_enable(hdmi_pll_res, false); + return -EINVAL; + } + pr_debug("hdmi phy pll is locked\n"); + + udelay(350); + /* poll for PHY ready status */ + delay_us = 100; + timeout_us = 2000; + if (readl_poll_timeout_atomic( + (hdmi_pll_res->phy_base + HDMI_PHY_STATUS), + status, ((status & BIT(0)) == 1), delay_us, timeout_us)) { + pr_err("hdmi phy status=%x failed to Lock\n", status); + hdmi_vco_disable(c); + mdss_pll_resource_enable(hdmi_pll_res, false); + return -EINVAL; + } + hdmi_pll_res->pll_on = true; + pr_debug("hdmi phy is locked\n"); + + return 0; +} /* hdmi_vco_enable */ + + +static void hdmi_phy_pll_calculator(u32 vco_freq, + struct mdss_pll_resources *hdmi_pll_res) +{ + u32 ref_clk = 19200000; + u32 sdm_mode = 1; + u32 ref_clk_multiplier = sdm_mode == 1 ? 2 : 1; + u32 int_ref_clk_freq = ref_clk * ref_clk_multiplier; + u32 fbclk_pre_div = 1; + u32 ssc_mode = 0; + u32 kvco = 270; + u32 vdd = 95; + u32 ten_power_six = 1000000; + u32 ssc_ds_ppm = ssc_mode ? 5000 : 0; + u32 sdm_res = 16; + u32 ssc_tri_step = 32; + u32 ssc_freq = 2; + u64 ssc_ds = vco_freq * ssc_ds_ppm; + u32 div_in_freq = vco_freq / fbclk_pre_div; + u64 dc_offset = (div_in_freq / int_ref_clk_freq - 1) * + ten_power_six * 10; + u32 ssc_kdiv = (int_ref_clk_freq / ssc_freq) - + ten_power_six; + u64 sdm_freq_seed; + u32 ssc_tri_inc; + u64 fb_div_n; + void __iomem *pll_base = hdmi_pll_res->pll_base; + u32 val; + + pr_debug("vco_freq = %u\n", vco_freq); + + do_div(ssc_ds, (u64)ten_power_six); + + fb_div_n = (u64)div_in_freq * (u64)ten_power_six * 10; + do_div(fb_div_n, int_ref_clk_freq); + + sdm_freq_seed = ((fb_div_n - dc_offset - ten_power_six * 10) * + (1 << sdm_res) * 10) + 5; + do_div(sdm_freq_seed, ((u64)ten_power_six * 100)); + + ssc_tri_inc = (u32)ssc_ds; + ssc_tri_inc = (ssc_tri_inc / int_ref_clk_freq) * (1 << 16) / + ssc_tri_step; + + val = (ref_clk_multiplier == 2 ? 1 : 0) + + ((fbclk_pre_div == 2 ? 1 : 0) * 16); + pr_debug("HDMI_UNI_PLL_REFCLK_CFG = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, val); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CHFPUMP_CFG, 0x02); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_PWRGEN_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + + do_div(dc_offset, (u64)ten_power_six * 10); + val = sdm_mode == 0 ? 64 + dc_offset : 0; + pr_debug("HDMI_UNI_PLL_SDM_CFG0 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, val); + + val = 64 + dc_offset; + pr_debug("HDMI_UNI_PLL_SDM_CFG1 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, val); + + val = sdm_freq_seed & 0xFF; + pr_debug("HDMI_UNI_PLL_SDM_CFG2 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, val); + + val = (sdm_freq_seed >> 8) & 0xFF; + pr_debug("HDMI_UNI_PLL_SDM_CFG3 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, val); + + val = (sdm_freq_seed >> 16) & 0xFF; + pr_debug("HDMI_UNI_PLL_SDM_CFG4 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, val); + + val = (ssc_mode == 0 ? 128 : 0) + (ssc_kdiv / ten_power_six); + pr_debug("HDMI_UNI_PLL_SSC_CFG0 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SSC_CFG0, val); + + val = ssc_tri_inc & 0xFF; + pr_debug("HDMI_UNI_PLL_SSC_CFG1 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SSC_CFG1, val); + + val = (ssc_tri_inc >> 8) & 0xFF; + pr_debug("HDMI_UNI_PLL_SSC_CFG2 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SSC_CFG2, val); + + pr_debug("HDMI_UNI_PLL_SSC_CFG3 = 0x%x\n", ssc_tri_step); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SSC_CFG3, ssc_tri_step); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG0, 0x0A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG1, 0x04); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG5, 0x00); + + val = (kvco * vdd * 10000) / 6; + val += 500000; + val /= ten_power_six; + pr_debug("HDMI_UNI_PLL_CAL_CFG6 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG6, val & 0xFF); + + val = (kvco * vdd * 10000) / 6; + val -= ten_power_six; + val /= ten_power_six; + val = (val >> 8) & 0xFF; + pr_debug("HDMI_UNI_PLL_CAL_CFG7 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG7, val); + + val = (ref_clk * 5) / ten_power_six; + pr_debug("HDMI_UNI_PLL_CAL_CFG8 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, val); + + val = ((ref_clk * 5) / ten_power_six) >> 8; + pr_debug("HDMI_UNI_PLL_CAL_CFG9 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, val); + + vco_freq /= ten_power_six; + val = vco_freq & 0xFF; + pr_debug("HDMI_UNI_PLL_CAL_CFG10 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, val); + + val = vco_freq >> 8; + pr_debug("HDMI_UNI_PLL_CAL_CFG11 = 0x%x\n", val); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, val); +} /* hdmi_phy_pll_calculator */ + +static int hdmi_vco_set_rate(struct clk *c, unsigned long rate) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *hdmi_pll_res = vco->priv; + void __iomem *pll_base; + void __iomem *phy_base; + unsigned int set_power_dwn = 0; + int rc; + + rc = mdss_pll_resource_enable(hdmi_pll_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + if (hdmi_pll_res->pll_on) { + hdmi_vco_disable(c); + set_power_dwn = 1; + } + + pll_base = hdmi_pll_res->pll_base; + phy_base = hdmi_pll_res->phy_base; + + pr_debug("rate=%ld\n", rate); + + switch (rate) { + case 0: + break; + + case 756000000: + /* 640x480p60 */ + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x52); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0xB0); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0xF4); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 810000000: + /* 576p50/576i50 case */ + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x54); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0x18); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0x2A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x03); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 810900000: + /* 480p60/480i60 case */ + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x54); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x66); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0x1D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0x2A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x03); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 650000000: + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x4F); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x55); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0xED); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0x8A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 742500000: + /* + * 720p60/720p50/1080i60/1080i50 + * 1080p24/1080p30/1080p25 case + */ + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x52); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0x56); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0xE6); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 1080000000: + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x5B); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0x38); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 1342500000: + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x36); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x61); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0xF6); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0x3E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x05); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x05); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x11); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + case 1485000000: + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_REFCLK_CFG, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VCOLPF_CFG, 0x19); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFR_CFG, 0x0E); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC1_CFG, 0x20); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LPFC2_CFG, 0x0D); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG0, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG1, 0x65); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG2, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG3, 0xAC); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_SDM_CFG4, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG0, 0x10); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG1, 0x1A); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_LKDET_CFG2, 0x05); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV2_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_POSTDIV3_CFG, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG2, 0x01); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG8, 0x60); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG9, 0x00); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG10, 0xCD); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_CAL_CFG11, 0x05); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x06); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x03); + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x02); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + udelay(200); + break; + + default: + pr_debug("Use pll settings calculator for rate=%ld\n", rate); + + MDSS_PLL_REG_W(phy_base, HDMI_PHY_GLB_CFG, 0x81); + hdmi_phy_pll_calculator(rate, hdmi_pll_res); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL0, 0x1F); + udelay(50); + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_GLB_CFG, 0x0F); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_PD_CTRL1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x10); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG0, 0xDB); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG1, 0x43); + + if (rate < 825000000) { + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x01); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x00); + } else if (rate >= 825000000 && rate < 1342500000) { + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x05); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x03); + } else { + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG2, 0x06); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_ANA_CFG3, 0x03); + } + + MDSS_PLL_REG_W(pll_base, HDMI_UNI_PLL_VREG_CFG, 0x04); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG0, 0xD0); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_DCC_CFG1, 0x1A); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG1, 0x00); + + if (rate < 825000000) + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x01); + else + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG2, 0x00); + + MDSS_PLL_REG_W(phy_base, HDMI_PHY_TXCAL_CFG3, 0x05); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_BIST_PATN0, 0x62); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_BIST_PATN1, 0x03); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_BIST_PATN2, 0x69); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_BIST_PATN3, 0x02); + + udelay(200); + + MDSS_PLL_REG_W(phy_base, HDMI_PHY_BIST_CFG1, 0x00); + MDSS_PLL_REG_W(phy_base, HDMI_PHY_BIST_CFG0, 0x00); + } + + /* Make sure writes complete before disabling iface clock */ + mb(); + + mdss_pll_resource_enable(hdmi_pll_res, false); + + if (set_power_dwn) + hdmi_vco_enable(c); + + vco->rate = rate; + vco->rate_set = true; + + return 0; +} /* hdmi_pll_set_rate */ + +/* HDMI PLL DIV CLK */ + +static unsigned long hdmi_vco_get_rate(struct clk *c) +{ + unsigned long freq = 0; + int rc; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *hdmi_pll_res = vco->priv; + + if (is_gdsc_disabled(hdmi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(hdmi_pll_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + freq = MDSS_PLL_REG_R(hdmi_pll_res->pll_base, + HDMI_UNI_PLL_CAL_CFG11) << 8 | + MDSS_PLL_REG_R(hdmi_pll_res->pll_base, HDMI_UNI_PLL_CAL_CFG10); + + switch (freq) { + case 742: + freq = 742500000; + break; + case 810: + if (MDSS_PLL_REG_R(hdmi_pll_res->pll_base, + HDMI_UNI_PLL_SDM_CFG3) == 0x18) + freq = 810000000; + else + freq = 810900000; + break; + case 1342: + freq = 1342500000; + break; + default: + freq *= 1000000; + } + mdss_pll_resource_enable(hdmi_pll_res, false); + + return freq; +} + +static long hdmi_vco_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + pr_debug("rrate=%ld\n", rrate); + + return rrate; +} + +static int hdmi_vco_prepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + int ret = 0; + + pr_debug("rate=%ld\n", vco->rate); + + if (!vco->rate_set && vco->rate) + ret = hdmi_vco_set_rate(c, vco->rate); + + return ret; +} + +static void hdmi_vco_unprepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + + vco->rate_set = false; +} + +static int hdmi_pll_lock_status(struct mdss_pll_resources *hdmi_pll_res) +{ + u32 status; + int pll_locked = 0; + int rc; + + rc = mdss_pll_resource_enable(hdmi_pll_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + /* poll for PLL ready status */ + if (readl_poll_timeout_atomic( + (hdmi_pll_res->phy_base + HDMI_PHY_STATUS), + status, ((status & BIT(0)) == 1), + HDMI_PLL_POLL_DELAY_US, + HDMI_PLL_POLL_TIMEOUT_US)) { + pr_debug("HDMI PLL status=%x failed to Lock\n", status); + pll_locked = 0; + } else { + pll_locked = 1; + } + mdss_pll_resource_enable(hdmi_pll_res, false); + + return pll_locked; +} + +static enum handoff hdmi_vco_handoff(struct clk *c) +{ + enum handoff ret = HANDOFF_DISABLED_CLK; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *hdmi_pll_res = vco->priv; + + if (is_gdsc_disabled(hdmi_pll_res)) + return HANDOFF_DISABLED_CLK; + + if (mdss_pll_resource_enable(hdmi_pll_res, true)) { + pr_err("pll resource can't be enabled\n"); + return ret; + } + + hdmi_pll_res->handoff_resources = true; + + if (hdmi_pll_lock_status(hdmi_pll_res)) { + hdmi_pll_res->pll_on = true; + c->rate = hdmi_vco_get_rate(c); + ret = HANDOFF_ENABLED_CLK; + } else { + hdmi_pll_res->handoff_resources = false; + mdss_pll_resource_enable(hdmi_pll_res, false); + } + + pr_debug("done, ret=%d\n", ret); + return ret; +} + +static const struct clk_ops hdmi_vco_clk_ops = { + .enable = hdmi_vco_enable, + .set_rate = hdmi_vco_set_rate, + .get_rate = hdmi_vco_get_rate, + .round_rate = hdmi_vco_round_rate, + .prepare = hdmi_vco_prepare, + .unprepare = hdmi_vco_unprepare, + .disable = hdmi_vco_disable, + .handoff = hdmi_vco_handoff, +}; + +static struct hdmi_pll_vco_clk hdmi_vco_clk = { + .min_rate = 600000000, + .max_rate = 1800000000, + .c = { + .dbg_name = "hdmi_vco_clk", + .ops = &hdmi_vco_clk_ops, + CLK_INIT(hdmi_vco_clk.c), + }, +}; + +struct div_clk hdmipll_div1_clk = { + .data = { + .div = 1, + .min_div = 1, + .max_div = 1, + }, + .c = { + .parent = &hdmi_vco_clk.c, + .dbg_name = "hdmipll_div1_clk", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(hdmipll_div1_clk.c), + }, +}; + +struct div_clk hdmipll_div2_clk = { + .data = { + .div = 2, + .min_div = 2, + .max_div = 2, + }, + .c = { + .parent = &hdmi_vco_clk.c, + .dbg_name = "hdmipll_div2_clk", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(hdmipll_div2_clk.c), + }, +}; + +struct div_clk hdmipll_div4_clk = { + .data = { + .div = 4, + .min_div = 4, + .max_div = 4, + }, + .c = { + .parent = &hdmi_vco_clk.c, + .dbg_name = "hdmipll_div4_clk", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(hdmipll_div4_clk.c), + }, +}; + +struct div_clk hdmipll_div6_clk = { + .data = { + .div = 6, + .min_div = 6, + .max_div = 6, + }, + .c = { + .parent = &hdmi_vco_clk.c, + .dbg_name = "hdmipll_div6_clk", + .ops = &clk_ops_div, + .flags = CLKFLAG_NO_RATE_CACHE, + CLK_INIT(hdmipll_div6_clk.c), + }, +}; + +static int hdmipll_set_mux_sel(struct mux_clk *clk, int mux_sel) +{ + struct mdss_pll_resources *hdmi_pll_res = clk->priv; + int rc; + + rc = mdss_pll_resource_enable(hdmi_pll_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + pr_debug("mux_sel=%d\n", mux_sel); + MDSS_PLL_REG_W(hdmi_pll_res->pll_base, + HDMI_UNI_PLL_POSTDIV1_CFG, mux_sel); + mdss_pll_resource_enable(hdmi_pll_res, false); + + return 0; +} + +static int hdmipll_get_mux_sel(struct mux_clk *clk) +{ + int rc; + int mux_sel = 0; + struct mdss_pll_resources *hdmi_pll_res = clk->priv; + + if (is_gdsc_disabled(hdmi_pll_res)) + return 0; + + rc = mdss_pll_resource_enable(hdmi_pll_res, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + mux_sel = MDSS_PLL_REG_R(hdmi_pll_res->pll_base, + HDMI_UNI_PLL_POSTDIV1_CFG); + mdss_pll_resource_enable(hdmi_pll_res, false); + mux_sel &= 0x03; + pr_debug("mux_sel=%d\n", mux_sel); + + return mux_sel; +} + +static struct clk_mux_ops hdmipll_mux_ops = { + .set_mux_sel = hdmipll_set_mux_sel, + .get_mux_sel = hdmipll_get_mux_sel, +}; + +static const struct clk_ops hdmi_mux_ops; + +static int hdmi_mux_prepare(struct clk *c) +{ + int ret = 0; + + if (c && c->ops && c->ops->set_rate) + ret = c->ops->set_rate(c, c->rate); + + return ret; +} + +struct mux_clk hdmipll_mux_clk = { + MUX_SRC_LIST( + { &hdmipll_div1_clk.c, 0 }, + { &hdmipll_div2_clk.c, 1 }, + { &hdmipll_div4_clk.c, 2 }, + { &hdmipll_div6_clk.c, 3 }, + ), + .ops = &hdmipll_mux_ops, + .c = { + .parent = &hdmipll_div1_clk.c, + .dbg_name = "hdmipll_mux_clk", + .ops = &hdmi_mux_ops, + CLK_INIT(hdmipll_mux_clk.c), + }, +}; + +struct div_clk hdmipll_clk_src = { + .data = { + .div = 5, + .min_div = 5, + .max_div = 5, + }, + .c = { + .parent = &hdmipll_mux_clk.c, + .dbg_name = "hdmipll_clk_src", + .ops = &clk_ops_div, + CLK_INIT(hdmipll_clk_src.c), + }, +}; + +static struct clk_lookup hdmipllcc_8974[] = { + CLK_LOOKUP("extp_clk_src", hdmipll_clk_src.c, + "fd8c0000.qcom,mmsscc-mdss"), +}; + +int hdmi_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = -ENOTSUPP; + + if (!pll_res || !pll_res->phy_base || !pll_res->pll_base) { + pr_err("Invalid input parameters\n"); + return -EPROBE_DEFER; + } + + /* Set client data for vco, mux and div clocks */ + hdmipll_clk_src.priv = pll_res; + hdmipll_mux_clk.priv = pll_res; + hdmipll_div1_clk.priv = pll_res; + hdmipll_div2_clk.priv = pll_res; + hdmipll_div4_clk.priv = pll_res; + hdmipll_div6_clk.priv = pll_res; + hdmi_vco_clk.priv = pll_res; + + /* Set hdmi mux clock operation */ + hdmi_mux_ops = clk_ops_gen_mux; + hdmi_mux_ops.prepare = hdmi_mux_prepare; + + rc = of_msm_clock_register(pdev->dev.of_node, hdmipllcc_8974, + ARRAY_SIZE(hdmipllcc_8974)); + if (rc) { + pr_err("Clock register failed rc=%d\n", rc); + rc = -EPROBE_DEFER; + } + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-hdmi-pll-8996.c b/drivers/clk/qcom/mdss/mdss-hdmi-pll-8996.c new file mode 100644 index 0000000000000000000000000000000000000000..c9fb382bad34c19a3f5f620a038076f93bf41b30 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-hdmi-pll-8996.c @@ -0,0 +1,2685 @@ +/* Copyright (c) 2014-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 +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-hdmi-pll.h" + +/* CONSTANTS */ +#define HDMI_BIT_CLK_TO_PIX_CLK_RATIO 10 +#define HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD 3400000000UL +#define HDMI_DIG_FREQ_BIT_CLK_THRESHOLD 1500000000UL +#define HDMI_MID_FREQ_BIT_CLK_THRESHOLD 750000000 +#define HDMI_CLKS_PLL_DIVSEL 0 +#define HDMI_CORECLK_DIV 5 +#define HDMI_REF_CLOCK 19200000 +#define HDMI_64B_ERR_VAL 0xFFFFFFFFFFFFFFFF +#define HDMI_VERSION_8996_V1 1 +#define HDMI_VERSION_8996_V2 2 +#define HDMI_VERSION_8996_V3 3 +#define HDMI_VERSION_8996_V3_1_8 4 + +#define HDMI_VCO_MAX_FREQ 12000000000 +#define HDMI_VCO_MIN_FREQ 8000000000 +#define HDMI_2400MHZ_BIT_CLK_HZ 2400000000UL +#define HDMI_2250MHZ_BIT_CLK_HZ 2250000000UL +#define HDMI_2000MHZ_BIT_CLK_HZ 2000000000UL +#define HDMI_1700MHZ_BIT_CLK_HZ 1700000000UL +#define HDMI_1200MHZ_BIT_CLK_HZ 1200000000UL +#define HDMI_1334MHZ_BIT_CLK_HZ 1334000000UL +#define HDMI_1000MHZ_BIT_CLK_HZ 1000000000UL +#define HDMI_850MHZ_BIT_CLK_HZ 850000000 +#define HDMI_667MHZ_BIT_CLK_HZ 667000000 +#define HDMI_600MHZ_BIT_CLK_HZ 600000000 +#define HDMI_500MHZ_BIT_CLK_HZ 500000000 +#define HDMI_450MHZ_BIT_CLK_HZ 450000000 +#define HDMI_334MHZ_BIT_CLK_HZ 334000000 +#define HDMI_300MHZ_BIT_CLK_HZ 300000000 +#define HDMI_282MHZ_BIT_CLK_HZ 282000000 +#define HDMI_250MHZ_BIT_CLK_HZ 250000000 +#define HDMI_KHZ_TO_HZ 1000 + +/* PLL REGISTERS */ +#define QSERDES_COM_ATB_SEL1 (0x000) +#define QSERDES_COM_ATB_SEL2 (0x004) +#define QSERDES_COM_FREQ_UPDATE (0x008) +#define QSERDES_COM_BG_TIMER (0x00C) +#define QSERDES_COM_SSC_EN_CENTER (0x010) +#define QSERDES_COM_SSC_ADJ_PER1 (0x014) +#define QSERDES_COM_SSC_ADJ_PER2 (0x018) +#define QSERDES_COM_SSC_PER1 (0x01C) +#define QSERDES_COM_SSC_PER2 (0x020) +#define QSERDES_COM_SSC_STEP_SIZE1 (0x024) +#define QSERDES_COM_SSC_STEP_SIZE2 (0x028) +#define QSERDES_COM_POST_DIV (0x02C) +#define QSERDES_COM_POST_DIV_MUX (0x030) +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN (0x034) +#define QSERDES_COM_CLK_ENABLE1 (0x038) +#define QSERDES_COM_SYS_CLK_CTRL (0x03C) +#define QSERDES_COM_SYSCLK_BUF_ENABLE (0x040) +#define QSERDES_COM_PLL_EN (0x044) +#define QSERDES_COM_PLL_IVCO (0x048) +#define QSERDES_COM_LOCK_CMP1_MODE0 (0x04C) +#define QSERDES_COM_LOCK_CMP2_MODE0 (0x050) +#define QSERDES_COM_LOCK_CMP3_MODE0 (0x054) +#define QSERDES_COM_LOCK_CMP1_MODE1 (0x058) +#define QSERDES_COM_LOCK_CMP2_MODE1 (0x05C) +#define QSERDES_COM_LOCK_CMP3_MODE1 (0x060) +#define QSERDES_COM_LOCK_CMP1_MODE2 (0x064) +#define QSERDES_COM_CMN_RSVD0 (0x064) +#define QSERDES_COM_LOCK_CMP2_MODE2 (0x068) +#define QSERDES_COM_EP_CLOCK_DETECT_CTRL (0x068) +#define QSERDES_COM_LOCK_CMP3_MODE2 (0x06C) +#define QSERDES_COM_SYSCLK_DET_COMP_STATUS (0x06C) +#define QSERDES_COM_BG_TRIM (0x070) +#define QSERDES_COM_CLK_EP_DIV (0x074) +#define QSERDES_COM_CP_CTRL_MODE0 (0x078) +#define QSERDES_COM_CP_CTRL_MODE1 (0x07C) +#define QSERDES_COM_CP_CTRL_MODE2 (0x080) +#define QSERDES_COM_CMN_RSVD1 (0x080) +#define QSERDES_COM_PLL_RCTRL_MODE0 (0x084) +#define QSERDES_COM_PLL_RCTRL_MODE1 (0x088) +#define QSERDES_COM_PLL_RCTRL_MODE2 (0x08C) +#define QSERDES_COM_CMN_RSVD2 (0x08C) +#define QSERDES_COM_PLL_CCTRL_MODE0 (0x090) +#define QSERDES_COM_PLL_CCTRL_MODE1 (0x094) +#define QSERDES_COM_PLL_CCTRL_MODE2 (0x098) +#define QSERDES_COM_CMN_RSVD3 (0x098) +#define QSERDES_COM_PLL_CNTRL (0x09C) +#define QSERDES_COM_PHASE_SEL_CTRL (0x0A0) +#define QSERDES_COM_PHASE_SEL_DC (0x0A4) +#define QSERDES_COM_CORE_CLK_IN_SYNC_SEL (0x0A8) +#define QSERDES_COM_BIAS_EN_CTRL_BY_PSM (0x0A8) +#define QSERDES_COM_SYSCLK_EN_SEL (0x0AC) +#define QSERDES_COM_CML_SYSCLK_SEL (0x0B0) +#define QSERDES_COM_RESETSM_CNTRL (0x0B4) +#define QSERDES_COM_RESETSM_CNTRL2 (0x0B8) +#define QSERDES_COM_RESTRIM_CTRL (0x0BC) +#define QSERDES_COM_RESTRIM_CTRL2 (0x0C0) +#define QSERDES_COM_RESCODE_DIV_NUM (0x0C4) +#define QSERDES_COM_LOCK_CMP_EN (0x0C8) +#define QSERDES_COM_LOCK_CMP_CFG (0x0CC) +#define QSERDES_COM_DEC_START_MODE0 (0x0D0) +#define QSERDES_COM_DEC_START_MODE1 (0x0D4) +#define QSERDES_COM_DEC_START_MODE2 (0x0D8) +#define QSERDES_COM_VCOCAL_DEADMAN_CTRL (0x0D8) +#define QSERDES_COM_DIV_FRAC_START1_MODE0 (0x0DC) +#define QSERDES_COM_DIV_FRAC_START2_MODE0 (0x0E0) +#define QSERDES_COM_DIV_FRAC_START3_MODE0 (0x0E4) +#define QSERDES_COM_DIV_FRAC_START1_MODE1 (0x0E8) +#define QSERDES_COM_DIV_FRAC_START2_MODE1 (0x0EC) +#define QSERDES_COM_DIV_FRAC_START3_MODE1 (0x0F0) +#define QSERDES_COM_DIV_FRAC_START1_MODE2 (0x0F4) +#define QSERDES_COM_VCO_TUNE_MINVAL1 (0x0F4) +#define QSERDES_COM_DIV_FRAC_START2_MODE2 (0x0F8) +#define QSERDES_COM_VCO_TUNE_MINVAL2 (0x0F8) +#define QSERDES_COM_DIV_FRAC_START3_MODE2 (0x0FC) +#define QSERDES_COM_CMN_RSVD4 (0x0FC) +#define QSERDES_COM_INTEGLOOP_INITVAL (0x100) +#define QSERDES_COM_INTEGLOOP_EN (0x104) +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE0 (0x108) +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE0 (0x10C) +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE1 (0x110) +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE1 (0x114) +#define QSERDES_COM_INTEGLOOP_GAIN0_MODE2 (0x118) +#define QSERDES_COM_VCO_TUNE_MAXVAL1 (0x118) +#define QSERDES_COM_INTEGLOOP_GAIN1_MODE2 (0x11C) +#define QSERDES_COM_VCO_TUNE_MAXVAL2 (0x11C) +#define QSERDES_COM_RES_TRIM_CONTROL2 (0x120) +#define QSERDES_COM_VCO_TUNE_CTRL (0x124) +#define QSERDES_COM_VCO_TUNE_MAP (0x128) +#define QSERDES_COM_VCO_TUNE1_MODE0 (0x12C) +#define QSERDES_COM_VCO_TUNE2_MODE0 (0x130) +#define QSERDES_COM_VCO_TUNE1_MODE1 (0x134) +#define QSERDES_COM_VCO_TUNE2_MODE1 (0x138) +#define QSERDES_COM_VCO_TUNE1_MODE2 (0x13C) +#define QSERDES_COM_VCO_TUNE_INITVAL1 (0x13C) +#define QSERDES_COM_VCO_TUNE2_MODE2 (0x140) +#define QSERDES_COM_VCO_TUNE_INITVAL2 (0x140) +#define QSERDES_COM_VCO_TUNE_TIMER1 (0x144) +#define QSERDES_COM_VCO_TUNE_TIMER2 (0x148) +#define QSERDES_COM_SAR (0x14C) +#define QSERDES_COM_SAR_CLK (0x150) +#define QSERDES_COM_SAR_CODE_OUT_STATUS (0x154) +#define QSERDES_COM_SAR_CODE_READY_STATUS (0x158) +#define QSERDES_COM_CMN_STATUS (0x15C) +#define QSERDES_COM_RESET_SM_STATUS (0x160) +#define QSERDES_COM_RESTRIM_CODE_STATUS (0x164) +#define QSERDES_COM_PLLCAL_CODE1_STATUS (0x168) +#define QSERDES_COM_PLLCAL_CODE2_STATUS (0x16C) +#define QSERDES_COM_BG_CTRL (0x170) +#define QSERDES_COM_CLK_SELECT (0x174) +#define QSERDES_COM_HSCLK_SEL (0x178) +#define QSERDES_COM_INTEGLOOP_BINCODE_STATUS (0x17C) +#define QSERDES_COM_PLL_ANALOG (0x180) +#define QSERDES_COM_CORECLK_DIV (0x184) +#define QSERDES_COM_SW_RESET (0x188) +#define QSERDES_COM_CORE_CLK_EN (0x18C) +#define QSERDES_COM_C_READY_STATUS (0x190) +#define QSERDES_COM_CMN_CONFIG (0x194) +#define QSERDES_COM_CMN_RATE_OVERRIDE (0x198) +#define QSERDES_COM_SVS_MODE_CLK_SEL (0x19C) +#define QSERDES_COM_DEBUG_BUS0 (0x1A0) +#define QSERDES_COM_DEBUG_BUS1 (0x1A4) +#define QSERDES_COM_DEBUG_BUS2 (0x1A8) +#define QSERDES_COM_DEBUG_BUS3 (0x1AC) +#define QSERDES_COM_DEBUG_BUS_SEL (0x1B0) +#define QSERDES_COM_CMN_MISC1 (0x1B4) +#define QSERDES_COM_CMN_MISC2 (0x1B8) +#define QSERDES_COM_CORECLK_DIV_MODE1 (0x1BC) +#define QSERDES_COM_CORECLK_DIV_MODE2 (0x1C0) +#define QSERDES_COM_CMN_RSVD5 (0x1C0) + +/* Tx Channel base addresses */ +#define HDMI_TX_L0_BASE_OFFSET (0x400) +#define HDMI_TX_L1_BASE_OFFSET (0x600) +#define HDMI_TX_L2_BASE_OFFSET (0x800) +#define HDMI_TX_L3_BASE_OFFSET (0xA00) + +/* Tx Channel PHY registers */ +#define QSERDES_TX_L0_BIST_MODE_LANENO (0x000) +#define QSERDES_TX_L0_BIST_INVERT (0x004) +#define QSERDES_TX_L0_CLKBUF_ENABLE (0x008) +#define QSERDES_TX_L0_CMN_CONTROL_ONE (0x00C) +#define QSERDES_TX_L0_CMN_CONTROL_TWO (0x010) +#define QSERDES_TX_L0_CMN_CONTROL_THREE (0x014) +#define QSERDES_TX_L0_TX_EMP_POST1_LVL (0x018) +#define QSERDES_TX_L0_TX_POST2_EMPH (0x01C) +#define QSERDES_TX_L0_TX_BOOST_LVL_UP_DN (0x020) +#define QSERDES_TX_L0_HP_PD_ENABLES (0x024) +#define QSERDES_TX_L0_TX_IDLE_LVL_LARGE_AMP (0x028) +#define QSERDES_TX_L0_TX_DRV_LVL (0x02C) +#define QSERDES_TX_L0_TX_DRV_LVL_OFFSET (0x030) +#define QSERDES_TX_L0_RESET_TSYNC_EN (0x034) +#define QSERDES_TX_L0_PRE_STALL_LDO_BOOST_EN (0x038) +#define QSERDES_TX_L0_TX_BAND (0x03C) +#define QSERDES_TX_L0_SLEW_CNTL (0x040) +#define QSERDES_TX_L0_INTERFACE_SELECT (0x044) +#define QSERDES_TX_L0_LPB_EN (0x048) +#define QSERDES_TX_L0_RES_CODE_LANE_TX (0x04C) +#define QSERDES_TX_L0_RES_CODE_LANE_RX (0x050) +#define QSERDES_TX_L0_RES_CODE_LANE_OFFSET (0x054) +#define QSERDES_TX_L0_PERL_LENGTH1 (0x058) +#define QSERDES_TX_L0_PERL_LENGTH2 (0x05C) +#define QSERDES_TX_L0_SERDES_BYP_EN_OUT (0x060) +#define QSERDES_TX_L0_DEBUG_BUS_SEL (0x064) +#define QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN (0x068) +#define QSERDES_TX_L0_TX_POL_INV (0x06C) +#define QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN (0x070) +#define QSERDES_TX_L0_BIST_PATTERN1 (0x074) +#define QSERDES_TX_L0_BIST_PATTERN2 (0x078) +#define QSERDES_TX_L0_BIST_PATTERN3 (0x07C) +#define QSERDES_TX_L0_BIST_PATTERN4 (0x080) +#define QSERDES_TX_L0_BIST_PATTERN5 (0x084) +#define QSERDES_TX_L0_BIST_PATTERN6 (0x088) +#define QSERDES_TX_L0_BIST_PATTERN7 (0x08C) +#define QSERDES_TX_L0_BIST_PATTERN8 (0x090) +#define QSERDES_TX_L0_LANE_MODE (0x094) +#define QSERDES_TX_L0_IDAC_CAL_LANE_MODE (0x098) +#define QSERDES_TX_L0_IDAC_CAL_LANE_MODE_CONFIGURATION (0x09C) +#define QSERDES_TX_L0_ATB_SEL1 (0x0A0) +#define QSERDES_TX_L0_ATB_SEL2 (0x0A4) +#define QSERDES_TX_L0_RCV_DETECT_LVL (0x0A8) +#define QSERDES_TX_L0_RCV_DETECT_LVL_2 (0x0AC) +#define QSERDES_TX_L0_PRBS_SEED1 (0x0B0) +#define QSERDES_TX_L0_PRBS_SEED2 (0x0B4) +#define QSERDES_TX_L0_PRBS_SEED3 (0x0B8) +#define QSERDES_TX_L0_PRBS_SEED4 (0x0BC) +#define QSERDES_TX_L0_RESET_GEN (0x0C0) +#define QSERDES_TX_L0_RESET_GEN_MUXES (0x0C4) +#define QSERDES_TX_L0_TRAN_DRVR_EMP_EN (0x0C8) +#define QSERDES_TX_L0_TX_INTERFACE_MODE (0x0CC) +#define QSERDES_TX_L0_PWM_CTRL (0x0D0) +#define QSERDES_TX_L0_PWM_ENCODED_OR_DATA (0x0D4) +#define QSERDES_TX_L0_PWM_GEAR_1_DIVIDER_BAND2 (0x0D8) +#define QSERDES_TX_L0_PWM_GEAR_2_DIVIDER_BAND2 (0x0DC) +#define QSERDES_TX_L0_PWM_GEAR_3_DIVIDER_BAND2 (0x0E0) +#define QSERDES_TX_L0_PWM_GEAR_4_DIVIDER_BAND2 (0x0E4) +#define QSERDES_TX_L0_PWM_GEAR_1_DIVIDER_BAND0_1 (0x0E8) +#define QSERDES_TX_L0_PWM_GEAR_2_DIVIDER_BAND0_1 (0x0EC) +#define QSERDES_TX_L0_PWM_GEAR_3_DIVIDER_BAND0_1 (0x0F0) +#define QSERDES_TX_L0_PWM_GEAR_4_DIVIDER_BAND0_1 (0x0F4) +#define QSERDES_TX_L0_VMODE_CTRL1 (0x0F8) +#define QSERDES_TX_L0_VMODE_CTRL2 (0x0FC) +#define QSERDES_TX_L0_TX_ALOG_INTF_OBSV_CNTL (0x100) +#define QSERDES_TX_L0_BIST_STATUS (0x104) +#define QSERDES_TX_L0_BIST_ERROR_COUNT1 (0x108) +#define QSERDES_TX_L0_BIST_ERROR_COUNT2 (0x10C) +#define QSERDES_TX_L0_TX_ALOG_INTF_OBSV (0x110) + +/* HDMI PHY REGISTERS */ +#define HDMI_PHY_BASE_OFFSET (0xC00) + +#define HDMI_PHY_CFG (0x00) +#define HDMI_PHY_PD_CTL (0x04) +#define HDMI_PHY_MODE (0x08) +#define HDMI_PHY_MISR_CLEAR (0x0C) +#define HDMI_PHY_TX0_TX1_BIST_CFG0 (0x10) +#define HDMI_PHY_TX0_TX1_BIST_CFG1 (0x14) +#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE0 (0x18) +#define HDMI_PHY_TX0_TX1_PRBS_SEED_BYTE1 (0x1C) +#define HDMI_PHY_TX0_TX1_BIST_PATTERN0 (0x20) +#define HDMI_PHY_TX0_TX1_BIST_PATTERN1 (0x24) +#define HDMI_PHY_TX2_TX3_BIST_CFG0 (0x28) +#define HDMI_PHY_TX2_TX3_BIST_CFG1 (0x2C) +#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE0 (0x30) +#define HDMI_PHY_TX2_TX3_PRBS_SEED_BYTE1 (0x34) +#define HDMI_PHY_TX2_TX3_BIST_PATTERN0 (0x38) +#define HDMI_PHY_TX2_TX3_BIST_PATTERN1 (0x3C) +#define HDMI_PHY_DEBUG_BUS_SEL (0x40) +#define HDMI_PHY_TXCAL_CFG0 (0x44) +#define HDMI_PHY_TXCAL_CFG1 (0x48) +#define HDMI_PHY_TX0_TX1_LANE_CTL (0x4C) +#define HDMI_PHY_TX2_TX3_LANE_CTL (0x50) +#define HDMI_PHY_LANE_BIST_CONFIG (0x54) +#define HDMI_PHY_CLOCK (0x58) +#define HDMI_PHY_MISC1 (0x5C) +#define HDMI_PHY_MISC2 (0x60) +#define HDMI_PHY_TX0_TX1_BIST_STATUS0 (0x64) +#define HDMI_PHY_TX0_TX1_BIST_STATUS1 (0x68) +#define HDMI_PHY_TX0_TX1_BIST_STATUS2 (0x6C) +#define HDMI_PHY_TX2_TX3_BIST_STATUS0 (0x70) +#define HDMI_PHY_TX2_TX3_BIST_STATUS1 (0x74) +#define HDMI_PHY_TX2_TX3_BIST_STATUS2 (0x78) +#define HDMI_PHY_PRE_MISR_STATUS0 (0x7C) +#define HDMI_PHY_PRE_MISR_STATUS1 (0x80) +#define HDMI_PHY_PRE_MISR_STATUS2 (0x84) +#define HDMI_PHY_PRE_MISR_STATUS3 (0x88) +#define HDMI_PHY_POST_MISR_STATUS0 (0x8C) +#define HDMI_PHY_POST_MISR_STATUS1 (0x90) +#define HDMI_PHY_POST_MISR_STATUS2 (0x94) +#define HDMI_PHY_POST_MISR_STATUS3 (0x98) +#define HDMI_PHY_STATUS (0x9C) +#define HDMI_PHY_MISC3_STATUS (0xA0) +#define HDMI_PHY_MISC4_STATUS (0xA4) +#define HDMI_PHY_DEBUG_BUS0 (0xA8) +#define HDMI_PHY_DEBUG_BUS1 (0xAC) +#define HDMI_PHY_DEBUG_BUS2 (0xB0) +#define HDMI_PHY_DEBUG_BUS3 (0xB4) +#define HDMI_PHY_PHY_REVISION_ID0 (0xB8) +#define HDMI_PHY_PHY_REVISION_ID1 (0xBC) +#define HDMI_PHY_PHY_REVISION_ID2 (0xC0) +#define HDMI_PHY_PHY_REVISION_ID3 (0xC4) + +#define HDMI_PLL_POLL_MAX_READS 100 +#define HDMI_PLL_POLL_TIMEOUT_US 1500 + +enum hdmi_pll_freqs { + HDMI_PCLK_25200_KHZ, + HDMI_PCLK_27027_KHZ, + HDMI_PCLK_27000_KHZ, + HDMI_PCLK_74250_KHZ, + HDMI_PCLK_148500_KHZ, + HDMI_PCLK_154000_KHZ, + HDMI_PCLK_268500_KHZ, + HDMI_PCLK_297000_KHZ, + HDMI_PCLK_594000_KHZ, + HDMI_PCLK_MAX +}; + +struct hdmi_8996_phy_pll_reg_cfg { + u32 tx_l0_lane_mode; + u32 tx_l2_lane_mode; + u32 tx_l0_tx_band; + u32 tx_l1_tx_band; + u32 tx_l2_tx_band; + u32 tx_l3_tx_band; + u32 com_svs_mode_clk_sel; + u32 com_hsclk_sel; + u32 com_pll_cctrl_mode0; + u32 com_pll_rctrl_mode0; + u32 com_cp_ctrl_mode0; + u32 com_dec_start_mode0; + u32 com_div_frac_start1_mode0; + u32 com_div_frac_start2_mode0; + u32 com_div_frac_start3_mode0; + u32 com_integloop_gain0_mode0; + u32 com_integloop_gain1_mode0; + u32 com_lock_cmp_en; + u32 com_lock_cmp1_mode0; + u32 com_lock_cmp2_mode0; + u32 com_lock_cmp3_mode0; + u32 com_core_clk_en; + u32 com_coreclk_div; + u32 com_restrim_ctrl; + u32 com_vco_tune_ctrl; + + u32 tx_l0_tx_drv_lvl; + u32 tx_l0_tx_emp_post1_lvl; + u32 tx_l1_tx_drv_lvl; + u32 tx_l1_tx_emp_post1_lvl; + u32 tx_l2_tx_drv_lvl; + u32 tx_l2_tx_emp_post1_lvl; + u32 tx_l3_tx_drv_lvl; + u32 tx_l3_tx_emp_post1_lvl; + u32 tx_l0_vmode_ctrl1; + u32 tx_l0_vmode_ctrl2; + u32 tx_l1_vmode_ctrl1; + u32 tx_l1_vmode_ctrl2; + u32 tx_l2_vmode_ctrl1; + u32 tx_l2_vmode_ctrl2; + u32 tx_l3_vmode_ctrl1; + u32 tx_l3_vmode_ctrl2; + u32 tx_l0_res_code_lane_tx; + u32 tx_l1_res_code_lane_tx; + u32 tx_l2_res_code_lane_tx; + u32 tx_l3_res_code_lane_tx; + + u32 phy_mode; +}; + +struct hdmi_8996_v3_post_divider { + u64 vco_freq; + u64 hsclk_divsel; + u64 vco_ratio; + u64 tx_band_sel; + u64 half_rate_mode; +}; + +static inline struct hdmi_pll_vco_clk *to_hdmi_8996_vco_clk(struct clk *clk) +{ + return container_of(clk, struct hdmi_pll_vco_clk, c); +} + +static inline u64 hdmi_8996_v1_get_post_div_lt_2g(u64 bclk) +{ + if (bclk >= HDMI_2400MHZ_BIT_CLK_HZ) + return 2; + else if (bclk >= HDMI_1700MHZ_BIT_CLK_HZ) + return 3; + else if (bclk >= HDMI_1200MHZ_BIT_CLK_HZ) + return 4; + else if (bclk >= HDMI_850MHZ_BIT_CLK_HZ) + return 3; + else if (bclk >= HDMI_600MHZ_BIT_CLK_HZ) + return 4; + else if (bclk >= HDMI_450MHZ_BIT_CLK_HZ) + return 3; + else if (bclk >= HDMI_300MHZ_BIT_CLK_HZ) + return 4; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_v2_get_post_div_lt_2g(u64 bclk, u64 vco_range) +{ + u64 hdmi_8ghz = vco_range; + u64 tmp_calc; + + hdmi_8ghz <<= 2; + tmp_calc = hdmi_8ghz; + do_div(tmp_calc, 6U); + + if (bclk >= vco_range) + return 2; + else if (bclk >= tmp_calc) + return 3; + else if (bclk >= vco_range >> 1) + return 4; + + tmp_calc = hdmi_8ghz; + do_div(tmp_calc, 12U); + if (bclk >= tmp_calc) + return 3; + else if (bclk >= vco_range >> 2) + return 4; + + tmp_calc = hdmi_8ghz; + do_div(tmp_calc, 24U); + if (bclk >= tmp_calc) + return 3; + else if (bclk >= vco_range >> 3) + return 4; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_v2_get_post_div_gt_2g(u64 hsclk) +{ + if (hsclk >= 0 && hsclk <= 3) + return hsclk + 1; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_get_coreclk_div_lt_2g(u64 bclk) +{ + if (bclk >= HDMI_1334MHZ_BIT_CLK_HZ) + return 1; + else if (bclk >= HDMI_1000MHZ_BIT_CLK_HZ) + return 1; + else if (bclk >= HDMI_667MHZ_BIT_CLK_HZ) + return 2; + else if (bclk >= HDMI_500MHZ_BIT_CLK_HZ) + return 2; + else if (bclk >= HDMI_334MHZ_BIT_CLK_HZ) + return 3; + else if (bclk >= HDMI_250MHZ_BIT_CLK_HZ) + return 3; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_get_coreclk_div_ratio(u64 clks_pll_divsel, + u64 coreclk_div) +{ + if (clks_pll_divsel == 0) + return coreclk_div*2; + else if (clks_pll_divsel == 1) + return coreclk_div*4; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_v1_get_tx_band(u64 bclk) +{ + if (bclk >= 2400000000UL) + return 0; + if (bclk >= 1200000000UL) + return 1; + if (bclk >= 600000000UL) + return 2; + if (bclk >= 300000000UL) + return 3; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_v2_get_tx_band(u64 bclk, u64 vco_range) +{ + if (bclk >= vco_range) + return 0; + else if (bclk >= vco_range >> 1) + return 1; + else if (bclk >= vco_range >> 2) + return 2; + else if (bclk >= vco_range >> 3) + return 3; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_v1_get_hsclk(u64 fdata) +{ + if (fdata >= 9600000000UL) + return 0; + else if (fdata >= 4800000000UL) + return 1; + else if (fdata >= 3200000000UL) + return 2; + else if (fdata >= 2400000000UL) + return 3; + + return HDMI_64B_ERR_VAL; +} + +static inline u64 hdmi_8996_v2_get_hsclk(u64 fdata, u64 vco_range) +{ + u64 tmp_calc = vco_range; + + tmp_calc <<= 2; + do_div(tmp_calc, 3U); + if (fdata >= (vco_range << 2)) + return 0; + else if (fdata >= (vco_range << 1)) + return 1; + else if (fdata >= tmp_calc) + return 2; + else if (fdata >= vco_range) + return 3; + + return HDMI_64B_ERR_VAL; + +} + +static inline u64 hdmi_8996_v2_get_vco_freq(u64 bclk, u64 vco_range) +{ + u64 tx_band_div_ratio = 1U << hdmi_8996_v2_get_tx_band(bclk, vco_range); + u64 pll_post_div_ratio; + + if (bclk >= vco_range) { + u64 hsclk = hdmi_8996_v2_get_hsclk(bclk, vco_range); + + pll_post_div_ratio = hdmi_8996_v2_get_post_div_gt_2g(hsclk); + } else { + pll_post_div_ratio = hdmi_8996_v2_get_post_div_lt_2g(bclk, + vco_range); + } + + return bclk * (pll_post_div_ratio * tx_band_div_ratio); +} + +static inline u64 hdmi_8996_v2_get_fdata(u64 bclk, u64 vco_range) +{ + if (bclk >= vco_range) + return bclk; + u64 tmp_calc = hdmi_8996_v2_get_vco_freq(bclk, vco_range); + u64 pll_post_div_ratio_lt_2g = hdmi_8996_v2_get_post_div_lt_2g( + bclk, vco_range); + if (pll_post_div_ratio_lt_2g == HDMI_64B_ERR_VAL) + return HDMI_64B_ERR_VAL; + + do_div(tmp_calc, pll_post_div_ratio_lt_2g); + return tmp_calc; +} + +static inline u64 hdmi_8996_get_cpctrl(u64 frac_start, bool gen_ssc) +{ + if ((frac_start != 0) || + (gen_ssc == true)) + /* + * This should be ROUND(11/(19.2/20))). + * Since ref clock does not change, hardcoding to 11 + */ + return 0xB; + + return 0x23; +} + +static inline u64 hdmi_8996_get_rctrl(u64 frac_start, bool gen_ssc) +{ + if ((frac_start != 0) || (gen_ssc == true)) + return 0x16; + + return 0x10; +} + +static inline u64 hdmi_8996_get_cctrl(u64 frac_start, bool gen_ssc) +{ + if ((frac_start != 0) || (gen_ssc == true)) + return 0x28; + + return 0x1; +} + +static inline u64 hdmi_8996_get_integloop_gain(u64 frac_start, bool gen_ssc) +{ + if ((frac_start != 0) || (gen_ssc == true)) + return 0x80; + + return 0xC4; +} + +static inline u64 hdmi_8996_v3_get_integloop_gain(u64 frac_start, u64 bclk, + bool gen_ssc) +{ + u64 digclk_divsel = bclk >= HDMI_DIG_FREQ_BIT_CLK_THRESHOLD ? 1 : 2; + u64 base = ((frac_start != 0) || (gen_ssc == true)) ? 0x40 : 0xC4; + + base <<= digclk_divsel; + + return (base <= 2046 ? base : 0x7FE); +} + +static inline u64 hdmi_8996_get_vco_tune(u64 fdata, u64 div) +{ + u64 vco_tune; + + vco_tune = fdata * div; + do_div(vco_tune, 1000000); + vco_tune = 13000 - vco_tune - 256; + do_div(vco_tune, 5); + + return vco_tune; +} + +static inline u64 hdmi_8996_get_pll_cmp(u64 pll_cmp_cnt, u64 core_clk) +{ + u64 pll_cmp; + u64 rem; + + pll_cmp = pll_cmp_cnt * core_clk; + rem = do_div(pll_cmp, HDMI_REF_CLOCK); + if (rem > (HDMI_REF_CLOCK >> 1)) + pll_cmp++; + pll_cmp -= 1; + + return pll_cmp; +} + +static inline u64 hdmi_8996_v3_get_pll_cmp(u64 pll_cmp_cnt, u64 fdata) +{ + u64 dividend = pll_cmp_cnt * fdata; + u64 divisor = HDMI_REF_CLOCK * 10; + u64 rem; + + rem = do_div(dividend, divisor); + if (rem > (divisor >> 1)) + dividend++; + + return dividend - 1; +} + +static int hdmi_8996_v3_get_post_div(struct hdmi_8996_v3_post_divider *pd, + u64 bclk) +{ + u32 ratio[] = {2, 3, 4, 5, 6, 9, 10, 12, 14, 15, 20, 21, 25, 28, 35}; + u32 tx_band_sel[] = {0, 1, 2, 3}; + u64 vco_freq[60]; + u64 vco, vco_optimal, half_rate_mode = 0; + int vco_optimal_index, vco_freq_index; + int i, j, k, x; + + for (i = 0; i <= 1; i++) { + vco_optimal = HDMI_VCO_MAX_FREQ; + vco_optimal_index = -1; + vco_freq_index = 0; + for (j = 0; j < 15; j++) { + for (k = 0; k < 4; k++) { + u64 ratio_mult = ratio[j] << tx_band_sel[k]; + + vco = bclk >> half_rate_mode; + vco *= ratio_mult; + vco_freq[vco_freq_index++] = vco; + } + } + + for (x = 0; x < 60; x++) { + u64 vco_tmp = vco_freq[x]; + + if ((vco_tmp >= HDMI_VCO_MIN_FREQ) && + (vco_tmp <= vco_optimal)) { + vco_optimal = vco_tmp; + vco_optimal_index = x; + } + } + + if (vco_optimal_index == -1) { + if (!half_rate_mode) + half_rate_mode++; + else + return -EINVAL; + } else { + pd->vco_freq = vco_optimal; + pd->tx_band_sel = tx_band_sel[vco_optimal_index % 4]; + pd->vco_ratio = ratio[vco_optimal_index / 4]; + break; + } + } + + switch (pd->vco_ratio) { + case 2: + pd->hsclk_divsel = 0; + break; + case 3: + pd->hsclk_divsel = 4; + break; + case 4: + pd->hsclk_divsel = 8; + break; + case 5: + pd->hsclk_divsel = 12; + break; + case 6: + pd->hsclk_divsel = 1; + break; + case 9: + pd->hsclk_divsel = 5; + break; + case 10: + pd->hsclk_divsel = 2; + break; + case 12: + pd->hsclk_divsel = 9; + break; + case 14: + pd->hsclk_divsel = 3; + break; + case 15: + pd->hsclk_divsel = 13; + break; + case 20: + pd->hsclk_divsel = 10; + break; + case 21: + pd->hsclk_divsel = 7; + break; + case 25: + pd->hsclk_divsel = 14; + break; + case 28: + pd->hsclk_divsel = 11; + break; + case 35: + pd->hsclk_divsel = 15; + break; + }; + + return 0; +} + +static int hdmi_8996_v1_calculate(u32 pix_clk, + struct hdmi_8996_phy_pll_reg_cfg *cfg) +{ + int rc = -EINVAL; + u64 fdata, clk_divtx, tmds_clk; + u64 bclk; + u64 post_div_gt_2g; + u64 post_div_lt_2g; + u64 coreclk_div1_lt_2g; + u64 core_clk_div_ratio; + u64 core_clk; + u64 pll_cmp; + u64 tx_band; + u64 tx_band_div_ratio; + u64 hsclk; + u64 dec_start; + u64 frac_start; + u64 pll_divisor = 4 * HDMI_REF_CLOCK; + u64 cpctrl; + u64 rctrl; + u64 cctrl; + u64 integloop_gain; + u64 vco_tune; + u64 vco_freq; + u64 rem; + + /* FDATA, CLK_DIVTX, PIXEL_CLK, TMDS_CLK */ + bclk = ((u64)pix_clk) * HDMI_BIT_CLK_TO_PIX_CLK_RATIO; + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) + tmds_clk = bclk/4; + else + tmds_clk = bclk; + + post_div_lt_2g = hdmi_8996_v1_get_post_div_lt_2g(bclk); + if (post_div_lt_2g == HDMI_64B_ERR_VAL) + goto fail; + + coreclk_div1_lt_2g = hdmi_8996_get_coreclk_div_lt_2g(bclk); + + core_clk_div_ratio = hdmi_8996_get_coreclk_div_ratio( + HDMI_CLKS_PLL_DIVSEL, HDMI_CORECLK_DIV); + + tx_band = hdmi_8996_v1_get_tx_band(bclk); + if (tx_band == HDMI_64B_ERR_VAL) + goto fail; + + tx_band_div_ratio = 1 << tx_band; + + if (bclk >= HDMI_2400MHZ_BIT_CLK_HZ) { + fdata = bclk; + hsclk = hdmi_8996_v1_get_hsclk(fdata); + if (hsclk == HDMI_64B_ERR_VAL) + goto fail; + + post_div_gt_2g = (hsclk <= 3) ? (hsclk + 1) : HDMI_64B_ERR_VAL; + if (post_div_gt_2g == HDMI_64B_ERR_VAL) + goto fail; + + vco_freq = bclk * (post_div_gt_2g * tx_band_div_ratio); + clk_divtx = vco_freq; + do_div(clk_divtx, post_div_gt_2g); + } else { + vco_freq = bclk * (post_div_lt_2g * tx_band_div_ratio); + fdata = vco_freq; + do_div(fdata, post_div_lt_2g); + hsclk = hdmi_8996_v1_get_hsclk(fdata); + if (hsclk == HDMI_64B_ERR_VAL) + goto fail; + + clk_divtx = vco_freq; + do_div(clk_divtx, post_div_lt_2g); + post_div_gt_2g = (hsclk <= 3) ? (hsclk + 1) : HDMI_64B_ERR_VAL; + if (post_div_gt_2g == HDMI_64B_ERR_VAL) + goto fail; + } + + /* Decimal and fraction values */ + dec_start = fdata * post_div_gt_2g; + do_div(dec_start, pll_divisor); + frac_start = ((pll_divisor - (((dec_start + 1) * pll_divisor) - + (fdata * post_div_gt_2g))) * (1 << 20)); + rem = do_div(frac_start, pll_divisor); + /* Round off frac_start to closest integer */ + if (rem >= (pll_divisor >> 1)) + frac_start++; + + cpctrl = hdmi_8996_get_cpctrl(frac_start, false); + rctrl = hdmi_8996_get_rctrl(frac_start, false); + cctrl = hdmi_8996_get_cctrl(frac_start, false); + integloop_gain = hdmi_8996_get_integloop_gain(frac_start, false); + vco_tune = hdmi_8996_get_vco_tune(fdata, post_div_gt_2g); + + core_clk = clk_divtx; + do_div(core_clk, core_clk_div_ratio); + pll_cmp = hdmi_8996_get_pll_cmp(1024, core_clk); + + /* Debug dump */ + DEV_DBG("%s: VCO freq: %llu\n", __func__, vco_freq); + DEV_DBG("%s: fdata: %llu\n", __func__, fdata); + DEV_DBG("%s: CLK_DIVTX: %llu\n", __func__, clk_divtx); + DEV_DBG("%s: pix_clk: %d\n", __func__, pix_clk); + DEV_DBG("%s: tmds clk: %llu\n", __func__, tmds_clk); + DEV_DBG("%s: HSCLK_SEL: %llu\n", __func__, hsclk); + DEV_DBG("%s: DEC_START: %llu\n", __func__, dec_start); + DEV_DBG("%s: DIV_FRAC_START: %llu\n", __func__, frac_start); + DEV_DBG("%s: PLL_CPCTRL: %llu\n", __func__, cpctrl); + DEV_DBG("%s: PLL_RCTRL: %llu\n", __func__, rctrl); + DEV_DBG("%s: PLL_CCTRL: %llu\n", __func__, cctrl); + DEV_DBG("%s: INTEGLOOP_GAIN: %llu\n", __func__, integloop_gain); + DEV_DBG("%s: VCO_TUNE: %llu\n", __func__, vco_tune); + DEV_DBG("%s: TX_BAND: %llu\n", __func__, tx_band); + DEV_DBG("%s: PLL_CMP: %llu\n", __func__, pll_cmp); + + /* Convert these values to register specific values */ + cfg->tx_l0_lane_mode = 0x3; + cfg->tx_l2_lane_mode = 0x3; + cfg->tx_l0_tx_band = tx_band + 4; + cfg->tx_l1_tx_band = tx_band + 4; + cfg->tx_l2_tx_band = tx_band + 4; + cfg->tx_l3_tx_band = tx_band + 4; + cfg->tx_l0_res_code_lane_tx = 0x33; + cfg->tx_l1_res_code_lane_tx = 0x33; + cfg->tx_l2_res_code_lane_tx = 0x33; + cfg->tx_l3_res_code_lane_tx = 0x33; + cfg->com_restrim_ctrl = 0x0; + cfg->com_vco_tune_ctrl = 0x1C; + + cfg->com_svs_mode_clk_sel = + (bclk >= HDMI_DIG_FREQ_BIT_CLK_THRESHOLD ? 1 : 2); + cfg->com_hsclk_sel = (0x28 | hsclk); + cfg->com_pll_cctrl_mode0 = cctrl; + cfg->com_pll_rctrl_mode0 = rctrl; + cfg->com_cp_ctrl_mode0 = cpctrl; + cfg->com_dec_start_mode0 = dec_start; + cfg->com_div_frac_start1_mode0 = (frac_start & 0xFF); + cfg->com_div_frac_start2_mode0 = ((frac_start & 0xFF00) >> 8); + cfg->com_div_frac_start3_mode0 = ((frac_start & 0xF0000) >> 16); + cfg->com_integloop_gain0_mode0 = (integloop_gain & 0xFF); + cfg->com_integloop_gain1_mode0 = ((integloop_gain & 0xF00) >> 8); + cfg->com_lock_cmp1_mode0 = (pll_cmp & 0xFF); + cfg->com_lock_cmp2_mode0 = ((pll_cmp & 0xFF00) >> 8); + cfg->com_lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16); + cfg->com_core_clk_en = (0x6C | (HDMI_CLKS_PLL_DIVSEL << 4)); + cfg->com_coreclk_div = HDMI_CORECLK_DIV; + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) { + cfg->tx_l0_tx_drv_lvl = 0x25; + cfg->tx_l0_tx_emp_post1_lvl = 0x23; + cfg->tx_l1_tx_drv_lvl = 0x25; + cfg->tx_l1_tx_emp_post1_lvl = 0x23; + cfg->tx_l2_tx_drv_lvl = 0x25; + cfg->tx_l2_tx_emp_post1_lvl = 0x23; + cfg->tx_l3_tx_drv_lvl = 0x22; + cfg->tx_l3_tx_emp_post1_lvl = 0x27; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0D; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0D; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0D; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x00; + cfg->com_restrim_ctrl = 0x0; + } else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) { + cfg->tx_l0_tx_drv_lvl = 0x25; + cfg->tx_l0_tx_emp_post1_lvl = 0x23; + cfg->tx_l1_tx_drv_lvl = 0x25; + cfg->tx_l1_tx_emp_post1_lvl = 0x23; + cfg->tx_l2_tx_drv_lvl = 0x25; + cfg->tx_l2_tx_emp_post1_lvl = 0x23; + cfg->tx_l3_tx_drv_lvl = 0x25; + cfg->tx_l3_tx_emp_post1_lvl = 0x23; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0D; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0D; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0D; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x00; + cfg->com_restrim_ctrl = 0x0; + } else { + cfg->tx_l0_tx_drv_lvl = 0x20; + cfg->tx_l0_tx_emp_post1_lvl = 0x20; + cfg->tx_l1_tx_drv_lvl = 0x20; + cfg->tx_l1_tx_emp_post1_lvl = 0x20; + cfg->tx_l2_tx_drv_lvl = 0x20; + cfg->tx_l2_tx_emp_post1_lvl = 0x20; + cfg->tx_l3_tx_drv_lvl = 0x20; + cfg->tx_l3_tx_emp_post1_lvl = 0x20; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0E; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0E; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0E; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x0E; + cfg->com_restrim_ctrl = 0xD8; + } + + cfg->phy_mode = (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) ? 0x10 : 0x0; + DEV_DBG("HDMI 8996 PLL: PLL Settings\n"); + DEV_DBG("PLL PARAM: tx_l0_lane_mode = 0x%x\n", cfg->tx_l0_lane_mode); + DEV_DBG("PLL PARAM: tx_l2_lane_mode = 0x%x\n", cfg->tx_l2_lane_mode); + DEV_DBG("PLL PARAM: tx_l0_tx_band = 0x%x\n", cfg->tx_l0_tx_band); + DEV_DBG("PLL PARAM: tx_l1_tx_band = 0x%x\n", cfg->tx_l1_tx_band); + DEV_DBG("PLL PARAM: tx_l2_tx_band = 0x%x\n", cfg->tx_l2_tx_band); + DEV_DBG("PLL PARAM: tx_l3_tx_band = 0x%x\n", cfg->tx_l3_tx_band); + DEV_DBG("PLL PARAM: com_svs_mode_clk_sel = 0x%x\n", + cfg->com_svs_mode_clk_sel); + DEV_DBG("PLL PARAM: com_hsclk_sel = 0x%x\n", cfg->com_hsclk_sel); + DEV_DBG("PLL PARAM: com_pll_cctrl_mode0 = 0x%x\n", + cfg->com_pll_cctrl_mode0); + DEV_DBG("PLL PARAM: com_pll_rctrl_mode0 = 0x%x\n", + cfg->com_pll_rctrl_mode0); + DEV_DBG("PLL PARAM: com_cp_ctrl_mode0 = 0x%x\n", + cfg->com_cp_ctrl_mode0); + DEV_DBG("PLL PARAM: com_dec_start_mode0 = 0x%x\n", + cfg->com_dec_start_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start1_mode0 = 0x%x\n", + cfg->com_div_frac_start1_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start2_mode0 = 0x%x\n", + cfg->com_div_frac_start2_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start3_mode0 = 0x%x\n", + cfg->com_div_frac_start3_mode0); + DEV_DBG("PLL PARAM: com_integloop_gain0_mode0 = 0x%x\n", + cfg->com_integloop_gain0_mode0); + DEV_DBG("PLL PARAM: com_integloop_gain1_mode0 = 0x%x\n", + cfg->com_integloop_gain1_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp1_mode0 = 0x%x\n", + cfg->com_lock_cmp1_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp2_mode0 = 0x%x\n", + cfg->com_lock_cmp2_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp3_mode0 = 0x%x\n", + cfg->com_lock_cmp3_mode0); + DEV_DBG("PLL PARAM: com_core_clk_en = 0x%x\n", cfg->com_core_clk_en); + DEV_DBG("PLL PARAM: com_coreclk_div = 0x%x\n", cfg->com_coreclk_div); + DEV_DBG("PLL PARAM: com_restrim_ctrl = 0x%x\n", cfg->com_restrim_ctrl); + + DEV_DBG("PLL PARAM: l0_tx_drv_lvl = 0x%x\n", cfg->tx_l0_tx_drv_lvl); + DEV_DBG("PLL PARAM: l0_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l0_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l1_tx_drv_lvl = 0x%x\n", cfg->tx_l1_tx_drv_lvl); + DEV_DBG("PLL PARAM: l1_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l1_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l2_tx_drv_lvl = 0x%x\n", cfg->tx_l2_tx_drv_lvl); + DEV_DBG("PLL PARAM: l2_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l2_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l3_tx_drv_lvl = 0x%x\n", cfg->tx_l3_tx_drv_lvl); + DEV_DBG("PLL PARAM: l3_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l3_tx_emp_post1_lvl); + + DEV_DBG("PLL PARAM: l0_vmode_ctrl1 = 0x%x\n", cfg->tx_l0_vmode_ctrl1); + DEV_DBG("PLL PARAM: l0_vmode_ctrl2 = 0x%x\n", cfg->tx_l0_vmode_ctrl2); + DEV_DBG("PLL PARAM: l1_vmode_ctrl1 = 0x%x\n", cfg->tx_l1_vmode_ctrl1); + DEV_DBG("PLL PARAM: l1_vmode_ctrl2 = 0x%x\n", cfg->tx_l1_vmode_ctrl2); + DEV_DBG("PLL PARAM: l2_vmode_ctrl1 = 0x%x\n", cfg->tx_l2_vmode_ctrl1); + DEV_DBG("PLL PARAM: l2_vmode_ctrl2 = 0x%x\n", cfg->tx_l2_vmode_ctrl2); + DEV_DBG("PLL PARAM: l3_vmode_ctrl1 = 0x%x\n", cfg->tx_l3_vmode_ctrl1); + DEV_DBG("PLL PARAM: l3_vmode_ctrl2 = 0x%x\n", cfg->tx_l3_vmode_ctrl2); + DEV_DBG("PLL PARAM: tx_l0_res_code_lane_tx = 0x%x\n", + cfg->tx_l0_res_code_lane_tx); + DEV_DBG("PLL PARAM: tx_l1_res_code_lane_tx = 0x%x\n", + cfg->tx_l1_res_code_lane_tx); + DEV_DBG("PLL PARAM: tx_l2_res_code_lane_tx = 0x%x\n", + cfg->tx_l2_res_code_lane_tx); + DEV_DBG("PLL PARAM: tx_l3_res_code_lane_tx = 0x%x\n", + cfg->tx_l3_res_code_lane_tx); + + DEV_DBG("PLL PARAM: phy_mode = 0x%x\n", cfg->phy_mode); + rc = 0; +fail: + return rc; +} + +static int hdmi_8996_v2_calculate(u32 pix_clk, + struct hdmi_8996_phy_pll_reg_cfg *cfg) +{ + int rc = -EINVAL; + u64 fdata, clk_divtx, tmds_clk; + u64 bclk; + u64 post_div; + u64 core_clk_div; + u64 core_clk_div_ratio; + u64 core_clk; + u64 pll_cmp; + u64 tx_band; + u64 tx_band_div_ratio; + u64 hsclk; + u64 dec_start; + u64 frac_start; + u64 pll_divisor = 4 * HDMI_REF_CLOCK; + u64 cpctrl; + u64 rctrl; + u64 cctrl; + u64 integloop_gain; + u64 vco_tune; + u64 vco_freq; + u64 vco_range; + u64 rem; + + /* FDATA, CLK_DIVTX, PIXEL_CLK, TMDS_CLK */ + bclk = ((u64)pix_clk) * HDMI_BIT_CLK_TO_PIX_CLK_RATIO; + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) + tmds_clk = pix_clk >> 2; + else + tmds_clk = pix_clk; + + vco_range = bclk < HDMI_282MHZ_BIT_CLK_HZ ? HDMI_2000MHZ_BIT_CLK_HZ : + HDMI_2250MHZ_BIT_CLK_HZ; + + fdata = hdmi_8996_v2_get_fdata(bclk, vco_range); + if (fdata == HDMI_64B_ERR_VAL) + goto fail; + + hsclk = hdmi_8996_v2_get_hsclk(fdata, vco_range); + if (hsclk == HDMI_64B_ERR_VAL) + goto fail; + + if (bclk >= vco_range) + post_div = hdmi_8996_v2_get_post_div_gt_2g(hsclk); + else + post_div = hdmi_8996_v2_get_post_div_lt_2g(bclk, vco_range); + + if (post_div == HDMI_64B_ERR_VAL) + goto fail; + + core_clk_div = 5; + core_clk_div_ratio = core_clk_div * 2; + + tx_band = hdmi_8996_v2_get_tx_band(bclk, vco_range); + if (tx_band == HDMI_64B_ERR_VAL) + goto fail; + + tx_band_div_ratio = 1 << tx_band; + + vco_freq = hdmi_8996_v2_get_vco_freq(bclk, vco_range); + clk_divtx = vco_freq; + do_div(clk_divtx, post_div); + + /* Decimal and fraction values */ + dec_start = fdata * post_div; + do_div(dec_start, pll_divisor); + frac_start = ((pll_divisor - (((dec_start + 1) * pll_divisor) - + (fdata * post_div))) * (1 << 20)); + rem = do_div(frac_start, pll_divisor); + /* Round off frac_start to closest integer */ + if (rem >= (pll_divisor >> 1)) + frac_start++; + + cpctrl = hdmi_8996_get_cpctrl(frac_start, false); + rctrl = hdmi_8996_get_rctrl(frac_start, false); + cctrl = hdmi_8996_get_cctrl(frac_start, false); + integloop_gain = hdmi_8996_get_integloop_gain(frac_start, false); + vco_tune = hdmi_8996_get_vco_tune(fdata, post_div); + + core_clk = clk_divtx; + do_div(core_clk, core_clk_div_ratio); + pll_cmp = hdmi_8996_get_pll_cmp(1024, core_clk); + + /* Debug dump */ + DEV_DBG("%s: VCO freq: %llu\n", __func__, vco_freq); + DEV_DBG("%s: fdata: %llu\n", __func__, fdata); + DEV_DBG("%s: CLK_DIVTX: %llu\n", __func__, clk_divtx); + DEV_DBG("%s: pix_clk: %d\n", __func__, pix_clk); + DEV_DBG("%s: tmds clk: %llu\n", __func__, tmds_clk); + DEV_DBG("%s: HSCLK_SEL: %llu\n", __func__, hsclk); + DEV_DBG("%s: DEC_START: %llu\n", __func__, dec_start); + DEV_DBG("%s: DIV_FRAC_START: %llu\n", __func__, frac_start); + DEV_DBG("%s: PLL_CPCTRL: %llu\n", __func__, cpctrl); + DEV_DBG("%s: PLL_RCTRL: %llu\n", __func__, rctrl); + DEV_DBG("%s: PLL_CCTRL: %llu\n", __func__, cctrl); + DEV_DBG("%s: INTEGLOOP_GAIN: %llu\n", __func__, integloop_gain); + DEV_DBG("%s: VCO_TUNE: %llu\n", __func__, vco_tune); + DEV_DBG("%s: TX_BAND: %llu\n", __func__, tx_band); + DEV_DBG("%s: PLL_CMP: %llu\n", __func__, pll_cmp); + + /* Convert these values to register specific values */ + cfg->tx_l0_lane_mode = 0x3; + cfg->tx_l2_lane_mode = 0x3; + cfg->tx_l0_tx_band = tx_band + 4; + cfg->tx_l1_tx_band = tx_band + 4; + cfg->tx_l2_tx_band = tx_band + 4; + cfg->tx_l3_tx_band = tx_band + 4; + + if (bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD) + cfg->com_svs_mode_clk_sel = 1; + else + cfg->com_svs_mode_clk_sel = 2; + + cfg->com_hsclk_sel = (0x28 | hsclk); + cfg->com_pll_cctrl_mode0 = cctrl; + cfg->com_pll_rctrl_mode0 = rctrl; + cfg->com_cp_ctrl_mode0 = cpctrl; + cfg->com_dec_start_mode0 = dec_start; + cfg->com_div_frac_start1_mode0 = (frac_start & 0xFF); + cfg->com_div_frac_start2_mode0 = ((frac_start & 0xFF00) >> 8); + cfg->com_div_frac_start3_mode0 = ((frac_start & 0xF0000) >> 16); + cfg->com_integloop_gain0_mode0 = (integloop_gain & 0xFF); + cfg->com_integloop_gain1_mode0 = ((integloop_gain & 0xF00) >> 8); + cfg->com_lock_cmp1_mode0 = (pll_cmp & 0xFF); + cfg->com_lock_cmp2_mode0 = ((pll_cmp & 0xFF00) >> 8); + cfg->com_lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16); + cfg->com_core_clk_en = (0x6C | (HDMI_CLKS_PLL_DIVSEL << 4)); + cfg->com_coreclk_div = HDMI_CORECLK_DIV; + cfg->com_vco_tune_ctrl = 0x0; + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) { + cfg->tx_l0_tx_drv_lvl = 0x25; + cfg->tx_l0_tx_emp_post1_lvl = 0x23; + cfg->tx_l1_tx_drv_lvl = 0x25; + cfg->tx_l1_tx_emp_post1_lvl = 0x23; + cfg->tx_l2_tx_drv_lvl = 0x25; + cfg->tx_l2_tx_emp_post1_lvl = 0x23; + cfg->tx_l3_tx_drv_lvl = 0x22; + cfg->tx_l3_tx_emp_post1_lvl = 0x27; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0D; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0D; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0D; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x00; + cfg->tx_l0_res_code_lane_tx = 0x3F; + cfg->tx_l1_res_code_lane_tx = 0x3F; + cfg->tx_l2_res_code_lane_tx = 0x3F; + cfg->tx_l3_res_code_lane_tx = 0x3F; + cfg->com_restrim_ctrl = 0x0; + } else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) { + cfg->tx_l0_tx_drv_lvl = 0x25; + cfg->tx_l0_tx_emp_post1_lvl = 0x23; + cfg->tx_l1_tx_drv_lvl = 0x25; + cfg->tx_l1_tx_emp_post1_lvl = 0x23; + cfg->tx_l2_tx_drv_lvl = 0x25; + cfg->tx_l2_tx_emp_post1_lvl = 0x23; + cfg->tx_l3_tx_drv_lvl = 0x25; + cfg->tx_l3_tx_emp_post1_lvl = 0x23; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0D; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0D; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0D; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x00; + cfg->tx_l0_res_code_lane_tx = 0x39; + cfg->tx_l1_res_code_lane_tx = 0x39; + cfg->tx_l2_res_code_lane_tx = 0x39; + cfg->tx_l3_res_code_lane_tx = 0x39; + cfg->com_restrim_ctrl = 0x0; + } else { + cfg->tx_l0_tx_drv_lvl = 0x20; + cfg->tx_l0_tx_emp_post1_lvl = 0x20; + cfg->tx_l1_tx_drv_lvl = 0x20; + cfg->tx_l1_tx_emp_post1_lvl = 0x20; + cfg->tx_l2_tx_drv_lvl = 0x20; + cfg->tx_l2_tx_emp_post1_lvl = 0x20; + cfg->tx_l3_tx_drv_lvl = 0x20; + cfg->tx_l3_tx_emp_post1_lvl = 0x20; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0E; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0E; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0E; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x0E; + cfg->tx_l0_res_code_lane_tx = 0x3F; + cfg->tx_l1_res_code_lane_tx = 0x3F; + cfg->tx_l2_res_code_lane_tx = 0x3F; + cfg->tx_l3_res_code_lane_tx = 0x3F; + cfg->com_restrim_ctrl = 0xD8; + } + + cfg->phy_mode = (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) ? 0x10 : 0x0; + DEV_DBG("HDMI 8996 PLL: PLL Settings\n"); + DEV_DBG("PLL PARAM: tx_l0_lane_mode = 0x%x\n", cfg->tx_l0_lane_mode); + DEV_DBG("PLL PARAM: tx_l2_lane_mode = 0x%x\n", cfg->tx_l2_lane_mode); + DEV_DBG("PLL PARAM: tx_l0_tx_band = 0x%x\n", cfg->tx_l0_tx_band); + DEV_DBG("PLL PARAM: tx_l1_tx_band = 0x%x\n", cfg->tx_l1_tx_band); + DEV_DBG("PLL PARAM: tx_l2_tx_band = 0x%x\n", cfg->tx_l2_tx_band); + DEV_DBG("PLL PARAM: tx_l3_tx_band = 0x%x\n", cfg->tx_l3_tx_band); + DEV_DBG("PLL PARAM: com_svs_mode_clk_sel = 0x%x\n", + cfg->com_svs_mode_clk_sel); + DEV_DBG("PLL PARAM: com_vco_tune_ctrl = 0x%x\n", + cfg->com_vco_tune_ctrl); + DEV_DBG("PLL PARAM: com_hsclk_sel = 0x%x\n", cfg->com_hsclk_sel); + DEV_DBG("PLL PARAM: com_lock_cmp_en = 0x%x\n", cfg->com_lock_cmp_en); + DEV_DBG("PLL PARAM: com_pll_cctrl_mode0 = 0x%x\n", + cfg->com_pll_cctrl_mode0); + DEV_DBG("PLL PARAM: com_pll_rctrl_mode0 = 0x%x\n", + cfg->com_pll_rctrl_mode0); + DEV_DBG("PLL PARAM: com_cp_ctrl_mode0 = 0x%x\n", + cfg->com_cp_ctrl_mode0); + DEV_DBG("PLL PARAM: com_dec_start_mode0 = 0x%x\n", + cfg->com_dec_start_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start1_mode0 = 0x%x\n", + cfg->com_div_frac_start1_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start2_mode0 = 0x%x\n", + cfg->com_div_frac_start2_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start3_mode0 = 0x%x\n", + cfg->com_div_frac_start3_mode0); + DEV_DBG("PLL PARAM: com_integloop_gain0_mode0 = 0x%x\n", + cfg->com_integloop_gain0_mode0); + DEV_DBG("PLL PARAM: com_integloop_gain1_mode0 = 0x%x\n", + cfg->com_integloop_gain1_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp1_mode0 = 0x%x\n", + cfg->com_lock_cmp1_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp2_mode0 = 0x%x\n", + cfg->com_lock_cmp2_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp3_mode0 = 0x%x\n", + cfg->com_lock_cmp3_mode0); + DEV_DBG("PLL PARAM: com_core_clk_en = 0x%x\n", cfg->com_core_clk_en); + DEV_DBG("PLL PARAM: com_coreclk_div = 0x%x\n", cfg->com_coreclk_div); + + DEV_DBG("PLL PARAM: l0_tx_drv_lvl = 0x%x\n", cfg->tx_l0_tx_drv_lvl); + DEV_DBG("PLL PARAM: l0_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l0_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l1_tx_drv_lvl = 0x%x\n", cfg->tx_l1_tx_drv_lvl); + DEV_DBG("PLL PARAM: l1_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l1_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l2_tx_drv_lvl = 0x%x\n", cfg->tx_l2_tx_drv_lvl); + DEV_DBG("PLL PARAM: l2_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l2_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l3_tx_drv_lvl = 0x%x\n", cfg->tx_l3_tx_drv_lvl); + DEV_DBG("PLL PARAM: l3_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l3_tx_emp_post1_lvl); + + DEV_DBG("PLL PARAM: l0_vmode_ctrl1 = 0x%x\n", cfg->tx_l0_vmode_ctrl1); + DEV_DBG("PLL PARAM: l0_vmode_ctrl2 = 0x%x\n", cfg->tx_l0_vmode_ctrl2); + DEV_DBG("PLL PARAM: l1_vmode_ctrl1 = 0x%x\n", cfg->tx_l1_vmode_ctrl1); + DEV_DBG("PLL PARAM: l1_vmode_ctrl2 = 0x%x\n", cfg->tx_l1_vmode_ctrl2); + DEV_DBG("PLL PARAM: l2_vmode_ctrl1 = 0x%x\n", cfg->tx_l2_vmode_ctrl1); + DEV_DBG("PLL PARAM: l2_vmode_ctrl2 = 0x%x\n", cfg->tx_l2_vmode_ctrl2); + DEV_DBG("PLL PARAM: l3_vmode_ctrl1 = 0x%x\n", cfg->tx_l3_vmode_ctrl1); + DEV_DBG("PLL PARAM: l3_vmode_ctrl2 = 0x%x\n", cfg->tx_l3_vmode_ctrl2); + DEV_DBG("PLL PARAM: tx_l0_res_code_lane_tx = 0x%x\n", + cfg->tx_l0_res_code_lane_tx); + DEV_DBG("PLL PARAM: tx_l1_res_code_lane_tx = 0x%x\n", + cfg->tx_l1_res_code_lane_tx); + DEV_DBG("PLL PARAM: tx_l2_res_code_lane_tx = 0x%x\n", + cfg->tx_l2_res_code_lane_tx); + DEV_DBG("PLL PARAM: tx_l3_res_code_lane_tx = 0x%x\n", + cfg->tx_l3_res_code_lane_tx); + DEV_DBG("PLL PARAM: com_restrim_ctrl = 0x%x\n", cfg->com_restrim_ctrl); + + DEV_DBG("PLL PARAM: phy_mode = 0x%x\n", cfg->phy_mode); + rc = 0; +fail: + return rc; +} + +static int hdmi_8996_v3_calculate(u32 pix_clk, + struct hdmi_8996_phy_pll_reg_cfg *cfg) +{ + int rc = -EINVAL; + struct hdmi_8996_v3_post_divider pd; + u64 fdata, tmds_clk; + u64 bclk; + u64 pll_cmp; + u64 tx_band; + u64 hsclk; + u64 dec_start; + u64 frac_start; + u64 pll_divisor = 4 * HDMI_REF_CLOCK; + u64 cpctrl; + u64 rctrl; + u64 cctrl; + u64 integloop_gain; + u64 vco_freq; + u64 rem; + + /* FDATA, HSCLK, PIXEL_CLK, TMDS_CLK */ + bclk = ((u64)pix_clk) * HDMI_BIT_CLK_TO_PIX_CLK_RATIO; + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) + tmds_clk = pix_clk >> 2; + else + tmds_clk = pix_clk; + + if (hdmi_8996_v3_get_post_div(&pd, bclk) || pd.vco_ratio <= 0 || + pd.vco_freq <= 0) + goto fail; + + vco_freq = pd.vco_freq; + fdata = pd.vco_freq; + do_div(fdata, pd.vco_ratio); + + hsclk = pd.hsclk_divsel; + dec_start = vco_freq; + do_div(dec_start, pll_divisor); + + frac_start = vco_freq * (1 << 20); + rem = do_div(frac_start, pll_divisor); + frac_start -= dec_start * (1 << 20); + if (rem > (pll_divisor >> 1)) + frac_start++; + + cpctrl = hdmi_8996_get_cpctrl(frac_start, false); + rctrl = hdmi_8996_get_rctrl(frac_start, false); + cctrl = hdmi_8996_get_cctrl(frac_start, false); + integloop_gain = hdmi_8996_v3_get_integloop_gain(frac_start, bclk, + false); + pll_cmp = hdmi_8996_v3_get_pll_cmp(1024, fdata); + tx_band = pd.tx_band_sel; + + /* Debug dump */ + DEV_DBG("%s: VCO freq: %llu\n", __func__, vco_freq); + DEV_DBG("%s: fdata: %llu\n", __func__, fdata); + DEV_DBG("%s: pix_clk: %d\n", __func__, pix_clk); + DEV_DBG("%s: tmds clk: %llu\n", __func__, tmds_clk); + DEV_DBG("%s: HSCLK_SEL: %llu\n", __func__, hsclk); + DEV_DBG("%s: DEC_START: %llu\n", __func__, dec_start); + DEV_DBG("%s: DIV_FRAC_START: %llu\n", __func__, frac_start); + DEV_DBG("%s: PLL_CPCTRL: %llu\n", __func__, cpctrl); + DEV_DBG("%s: PLL_RCTRL: %llu\n", __func__, rctrl); + DEV_DBG("%s: PLL_CCTRL: %llu\n", __func__, cctrl); + DEV_DBG("%s: INTEGLOOP_GAIN: %llu\n", __func__, integloop_gain); + DEV_DBG("%s: TX_BAND: %llu\n", __func__, tx_band); + DEV_DBG("%s: PLL_CMP: %llu\n", __func__, pll_cmp); + + /* Convert these values to register specific values */ + cfg->tx_l0_tx_band = tx_band + 4; + cfg->tx_l1_tx_band = tx_band + 4; + cfg->tx_l2_tx_band = tx_band + 4; + cfg->tx_l3_tx_band = tx_band + 4; + + if (bclk > HDMI_DIG_FREQ_BIT_CLK_THRESHOLD) + cfg->com_svs_mode_clk_sel = 1; + else + cfg->com_svs_mode_clk_sel = 2; + + cfg->com_hsclk_sel = (0x20 | hsclk); + cfg->com_pll_cctrl_mode0 = cctrl; + cfg->com_pll_rctrl_mode0 = rctrl; + cfg->com_cp_ctrl_mode0 = cpctrl; + cfg->com_dec_start_mode0 = dec_start; + cfg->com_div_frac_start1_mode0 = (frac_start & 0xFF); + cfg->com_div_frac_start2_mode0 = ((frac_start & 0xFF00) >> 8); + cfg->com_div_frac_start3_mode0 = ((frac_start & 0xF0000) >> 16); + cfg->com_integloop_gain0_mode0 = (integloop_gain & 0xFF); + cfg->com_integloop_gain1_mode0 = ((integloop_gain & 0xF00) >> 8); + cfg->com_lock_cmp1_mode0 = (pll_cmp & 0xFF); + cfg->com_lock_cmp2_mode0 = ((pll_cmp & 0xFF00) >> 8); + cfg->com_lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16); + cfg->com_lock_cmp_en = 0x04; + cfg->com_core_clk_en = 0x2C; + cfg->com_coreclk_div = HDMI_CORECLK_DIV; + cfg->phy_mode = (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) ? 0x10 : 0x0; + cfg->com_vco_tune_ctrl = 0x0; + + cfg->tx_l0_lane_mode = 0x43; + cfg->tx_l2_lane_mode = 0x43; + + if (bclk > HDMI_HIGH_FREQ_BIT_CLK_THRESHOLD) { + cfg->tx_l0_tx_drv_lvl = 0x25; + cfg->tx_l0_tx_emp_post1_lvl = 0x23; + cfg->tx_l1_tx_drv_lvl = 0x25; + cfg->tx_l1_tx_emp_post1_lvl = 0x23; + cfg->tx_l2_tx_drv_lvl = 0x25; + cfg->tx_l2_tx_emp_post1_lvl = 0x23; + cfg->tx_l3_tx_drv_lvl = 0x22; + cfg->tx_l3_tx_emp_post1_lvl = 0x27; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0D; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0D; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0D; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x00; + } else if (bclk > HDMI_MID_FREQ_BIT_CLK_THRESHOLD) { + cfg->tx_l0_tx_drv_lvl = 0x25; + cfg->tx_l0_tx_emp_post1_lvl = 0x23; + cfg->tx_l1_tx_drv_lvl = 0x25; + cfg->tx_l1_tx_emp_post1_lvl = 0x23; + cfg->tx_l2_tx_drv_lvl = 0x25; + cfg->tx_l2_tx_emp_post1_lvl = 0x23; + cfg->tx_l3_tx_drv_lvl = 0x25; + cfg->tx_l3_tx_emp_post1_lvl = 0x23; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0D; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0D; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0D; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x00; + } else { + cfg->tx_l0_tx_drv_lvl = 0x20; + cfg->tx_l0_tx_emp_post1_lvl = 0x20; + cfg->tx_l1_tx_drv_lvl = 0x20; + cfg->tx_l1_tx_emp_post1_lvl = 0x20; + cfg->tx_l2_tx_drv_lvl = 0x20; + cfg->tx_l2_tx_emp_post1_lvl = 0x20; + cfg->tx_l3_tx_drv_lvl = 0x20; + cfg->tx_l3_tx_emp_post1_lvl = 0x20; + cfg->tx_l0_vmode_ctrl1 = 0x00; + cfg->tx_l0_vmode_ctrl2 = 0x0E; + cfg->tx_l1_vmode_ctrl1 = 0x00; + cfg->tx_l1_vmode_ctrl2 = 0x0E; + cfg->tx_l2_vmode_ctrl1 = 0x00; + cfg->tx_l2_vmode_ctrl2 = 0x0E; + cfg->tx_l3_vmode_ctrl1 = 0x00; + cfg->tx_l3_vmode_ctrl2 = 0x0E; + } + + DEV_DBG("HDMI 8996 PLL: PLL Settings\n"); + DEV_DBG("PLL PARAM: tx_l0_tx_band = 0x%x\n", cfg->tx_l0_tx_band); + DEV_DBG("PLL PARAM: tx_l1_tx_band = 0x%x\n", cfg->tx_l1_tx_band); + DEV_DBG("PLL PARAM: tx_l2_tx_band = 0x%x\n", cfg->tx_l2_tx_band); + DEV_DBG("PLL PARAM: tx_l3_tx_band = 0x%x\n", cfg->tx_l3_tx_band); + DEV_DBG("PLL PARAM: com_svs_mode_clk_sel = 0x%x\n", + cfg->com_svs_mode_clk_sel); + DEV_DBG("PLL PARAM: com_hsclk_sel = 0x%x\n", cfg->com_hsclk_sel); + DEV_DBG("PLL PARAM: com_lock_cmp_en = 0x%x\n", cfg->com_lock_cmp_en); + DEV_DBG("PLL PARAM: com_pll_cctrl_mode0 = 0x%x\n", + cfg->com_pll_cctrl_mode0); + DEV_DBG("PLL PARAM: com_pll_rctrl_mode0 = 0x%x\n", + cfg->com_pll_rctrl_mode0); + DEV_DBG("PLL PARAM: com_cp_ctrl_mode0 = 0x%x\n", + cfg->com_cp_ctrl_mode0); + DEV_DBG("PLL PARAM: com_dec_start_mode0 = 0x%x\n", + cfg->com_dec_start_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start1_mode0 = 0x%x\n", + cfg->com_div_frac_start1_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start2_mode0 = 0x%x\n", + cfg->com_div_frac_start2_mode0); + DEV_DBG("PLL PARAM: com_div_frac_start3_mode0 = 0x%x\n", + cfg->com_div_frac_start3_mode0); + DEV_DBG("PLL PARAM: com_integloop_gain0_mode0 = 0x%x\n", + cfg->com_integloop_gain0_mode0); + DEV_DBG("PLL PARAM: com_integloop_gain1_mode0 = 0x%x\n", + cfg->com_integloop_gain1_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp1_mode0 = 0x%x\n", + cfg->com_lock_cmp1_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp2_mode0 = 0x%x\n", + cfg->com_lock_cmp2_mode0); + DEV_DBG("PLL PARAM: com_lock_cmp3_mode0 = 0x%x\n", + cfg->com_lock_cmp3_mode0); + DEV_DBG("PLL PARAM: com_core_clk_en = 0x%x\n", cfg->com_core_clk_en); + DEV_DBG("PLL PARAM: com_coreclk_div = 0x%x\n", cfg->com_coreclk_div); + DEV_DBG("PLL PARAM: phy_mode = 0x%x\n", cfg->phy_mode); + + DEV_DBG("PLL PARAM: tx_l0_lane_mode = 0x%x\n", cfg->tx_l0_lane_mode); + DEV_DBG("PLL PARAM: tx_l2_lane_mode = 0x%x\n", cfg->tx_l2_lane_mode); + DEV_DBG("PLL PARAM: l0_tx_drv_lvl = 0x%x\n", cfg->tx_l0_tx_drv_lvl); + DEV_DBG("PLL PARAM: l0_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l0_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l1_tx_drv_lvl = 0x%x\n", cfg->tx_l1_tx_drv_lvl); + DEV_DBG("PLL PARAM: l1_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l1_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l2_tx_drv_lvl = 0x%x\n", cfg->tx_l2_tx_drv_lvl); + DEV_DBG("PLL PARAM: l2_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l2_tx_emp_post1_lvl); + DEV_DBG("PLL PARAM: l3_tx_drv_lvl = 0x%x\n", cfg->tx_l3_tx_drv_lvl); + DEV_DBG("PLL PARAM: l3_tx_emp_post1_lvl = 0x%x\n", + cfg->tx_l3_tx_emp_post1_lvl); + + DEV_DBG("PLL PARAM: l0_vmode_ctrl1 = 0x%x\n", cfg->tx_l0_vmode_ctrl1); + DEV_DBG("PLL PARAM: l0_vmode_ctrl2 = 0x%x\n", cfg->tx_l0_vmode_ctrl2); + DEV_DBG("PLL PARAM: l1_vmode_ctrl1 = 0x%x\n", cfg->tx_l1_vmode_ctrl1); + DEV_DBG("PLL PARAM: l1_vmode_ctrl2 = 0x%x\n", cfg->tx_l1_vmode_ctrl2); + DEV_DBG("PLL PARAM: l2_vmode_ctrl1 = 0x%x\n", cfg->tx_l2_vmode_ctrl1); + DEV_DBG("PLL PARAM: l2_vmode_ctrl2 = 0x%x\n", cfg->tx_l2_vmode_ctrl2); + DEV_DBG("PLL PARAM: l3_vmode_ctrl1 = 0x%x\n", cfg->tx_l3_vmode_ctrl1); + DEV_DBG("PLL PARAM: l3_vmode_ctrl2 = 0x%x\n", cfg->tx_l3_vmode_ctrl2); + rc = 0; +fail: + return rc; +} + +static int hdmi_8996_calculate(u32 pix_clk, + struct hdmi_8996_phy_pll_reg_cfg *cfg, u32 ver) +{ + switch (ver) { + case HDMI_VERSION_8996_V3: + case HDMI_VERSION_8996_V3_1_8: + return hdmi_8996_v3_calculate(pix_clk, cfg); + case HDMI_VERSION_8996_V2: + return hdmi_8996_v2_calculate(pix_clk, cfg); + default: + return hdmi_8996_v1_calculate(pix_clk, cfg); + } +} + +static int hdmi_8996_phy_pll_set_clk_rate(struct clk *c, u32 tmds_clk, u32 ver) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + struct hdmi_8996_phy_pll_reg_cfg cfg = {0}; + + rc = hdmi_8996_calculate(tmds_clk, &cfg, ver); + if (rc) { + DEV_ERR("%s: PLL calculation failed\n", __func__); + return rc; + } + + /* Initially shut down PHY */ + DEV_DBG("%s: Disabling PHY\n", __func__); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x0); + udelay(500); + + /* Power up sequence */ + switch (ver) { + case HDMI_VERSION_8996_V2: + case HDMI_VERSION_8996_V3: + case HDMI_VERSION_8996_V3_1_8: + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BG_CTRL, 0x04); + break; + }; + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x1); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESETSM_CNTRL, 0x20); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TX0_TX1_LANE_CTL, 0x0F); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TX2_TX3_LANE_CTL, 0x0F); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_CLKBUF_ENABLE, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_CLKBUF_ENABLE, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_CLKBUF_ENABLE, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_CLKBUF_ENABLE, 0x03); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_LANE_MODE, cfg.tx_l0_lane_mode); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_LANE_MODE, cfg.tx_l2_lane_mode); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TX_BAND, cfg.tx_l0_tx_band); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_TX_BAND, cfg.tx_l1_tx_band); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_TX_BAND, cfg.tx_l2_tx_band); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_TX_BAND, cfg.tx_l3_tx_band); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_RESET_TSYNC_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_RESET_TSYNC_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_RESET_TSYNC_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_RESET_TSYNC_EN, 0x03); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYSCLK_BUF_ENABLE, 0x1E); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x07); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYSCLK_EN_SEL, 0x37); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SYS_CLK_CTRL, 0x02); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CLK_ENABLE1, 0x0E); + if (ver == HDMI_VERSION_8996_V1) + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BG_CTRL, 0x06); + + /* Bypass VCO calibration */ + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SVS_MODE_CLK_SEL, + cfg.com_svs_mode_clk_sel); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BG_TRIM, 0x0F); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_IVCO, 0x0F); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE_CTRL, + cfg.com_vco_tune_ctrl); + + switch (ver) { + case HDMI_VERSION_8996_V1: + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SVS_MODE_CLK_SEL, + cfg.com_svs_mode_clk_sel); + break; + default: + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_BG_CTRL, 0x06); + } + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CLK_SELECT, 0x30); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_HSCLK_SEL, + cfg.com_hsclk_sel); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_EN, + cfg.com_lock_cmp_en); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_CCTRL_MODE0, + cfg.com_pll_cctrl_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_PLL_RCTRL_MODE0, + cfg.com_pll_rctrl_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CP_CTRL_MODE0, + cfg.com_cp_ctrl_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEC_START_MODE0, + cfg.com_dec_start_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START1_MODE0, + cfg.com_div_frac_start1_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START2_MODE0, + cfg.com_div_frac_start2_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DIV_FRAC_START3_MODE0, + cfg.com_div_frac_start3_mode0); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_INTEGLOOP_GAIN0_MODE0, + cfg.com_integloop_gain0_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_INTEGLOOP_GAIN1_MODE0, + cfg.com_integloop_gain1_mode0); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP1_MODE0, + cfg.com_lock_cmp1_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP2_MODE0, + cfg.com_lock_cmp2_mode0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP3_MODE0, + cfg.com_lock_cmp3_mode0); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE_MAP, 0x00); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CORE_CLK_EN, + cfg.com_core_clk_en); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CORECLK_DIV, + cfg.com_coreclk_div); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_CONFIG, 0x02); + + if (ver == HDMI_VERSION_8996_V3 || ver == HDMI_VERSION_8996_V3_1_8) + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESCODE_DIV_NUM, 0x15); + + /* TX lanes setup (TX 0/1/2/3) */ + if (ver == HDMI_VERSION_8996_V3_1_8) { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + 0x00000023); + } else { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + cfg.tx_l0_tx_drv_lvl); + } + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TX_EMP_POST1_LVL, + cfg.tx_l0_tx_emp_post1_lvl); + + if (ver == HDMI_VERSION_8996_V3_1_8) { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + 0x00000023); + } else { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + cfg.tx_l1_tx_drv_lvl); + } + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_TX_EMP_POST1_LVL, + cfg.tx_l1_tx_emp_post1_lvl); + + if (ver == HDMI_VERSION_8996_V3_1_8) { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + 0x00000023); + } else { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + cfg.tx_l2_tx_drv_lvl); + } + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_TX_EMP_POST1_LVL, + cfg.tx_l2_tx_emp_post1_lvl); + + if (ver == HDMI_VERSION_8996_V3_1_8) { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + 0x00000020); + } else { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL, + cfg.tx_l3_tx_drv_lvl); + } + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_TX_EMP_POST1_LVL, + cfg.tx_l3_tx_emp_post1_lvl); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL1, + cfg.tx_l0_vmode_ctrl1); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL2, + cfg.tx_l0_vmode_ctrl2); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL1, + cfg.tx_l1_vmode_ctrl1); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL2, + cfg.tx_l1_vmode_ctrl2); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL1, + cfg.tx_l2_vmode_ctrl1); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL2, + cfg.tx_l2_vmode_ctrl2); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL1, + cfg.tx_l3_vmode_ctrl1); + if (ver == HDMI_VERSION_8996_V3_1_8) { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL2, + 0x0000000D); + } else { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_VMODE_CTRL2, + cfg.tx_l3_vmode_ctrl2); + } + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL_OFFSET, 0x00); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL_OFFSET, 0x00); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL_OFFSET, 0x00); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_TX_DRV_LVL_OFFSET, 0x00); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_OFFSET, 0x00); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_OFFSET, 0x00); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_OFFSET, 0x00); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_OFFSET, 0x00); + + if (ver < HDMI_VERSION_8996_V3) { + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_TX, + cfg.tx_l0_res_code_lane_tx); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_TX, + cfg.tx_l1_res_code_lane_tx); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_TX, + cfg.tx_l2_res_code_lane_tx); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_RES_CODE_LANE_TX, + cfg.tx_l3_res_code_lane_tx); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_RESTRIM_CTRL, + cfg.com_restrim_ctrl); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TXCAL_CFG0, 0x00); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_TXCAL_CFG1, 0x05); + } + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_MODE, cfg.phy_mode); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_PD_CTL, 0x1F); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_TRAN_DRVR_EMP_EN, 0x03); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_PARRATE_REC_DETECT_IDLE_EN, 0x40); + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_HP_PD_ENABLES, 0x0C); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_HP_PD_ENABLES, 0x0C); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_HP_PD_ENABLES, 0x0C); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_HP_PD_ENABLES, 0x03); + + if (ver == HDMI_VERSION_8996_V2) { + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL1, 0x01); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL2, 0x01); + } + /* + * Ensure that vco configuration gets flushed to hardware before + * enabling the PLL + */ + wmb(); + return 0; +} + +static int hdmi_8996_phy_ready_status(struct mdss_pll_resources *io) +{ + u32 status = 0; + int phy_ready = 0; + int rc; + u32 read_count = 0; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + DEV_ERR("%s: pll resource can't be enabled\n", __func__); + return rc; + } + + DEV_DBG("%s: Waiting for PHY Ready\n", __func__); + + /* Poll for PHY read status */ + while (read_count < HDMI_PLL_POLL_MAX_READS) { + status = MDSS_PLL_REG_R(io->phy_base, HDMI_PHY_STATUS); + if ((status & BIT(0)) == 1) { + phy_ready = 1; + DEV_DBG("%s: PHY READY\n", __func__); + break; + } + udelay(HDMI_PLL_POLL_TIMEOUT_US); + read_count++; + } + + if (read_count == HDMI_PLL_POLL_MAX_READS) { + phy_ready = 0; + DEV_DBG("%s: PHY READY TIMEOUT\n", __func__); + } + + mdss_pll_resource_enable(io, false); + + return phy_ready; +} + +static int hdmi_8996_pll_lock_status(struct mdss_pll_resources *io) +{ + u32 status; + int pll_locked = 0; + int rc; + u32 read_count = 0; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + DEV_ERR("%s: pll resource can't be enabled\n", __func__); + return rc; + } + + DEV_DBG("%s: Waiting for PLL lock\n", __func__); + + while (read_count < HDMI_PLL_POLL_MAX_READS) { + status = MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_C_READY_STATUS); + if ((status & BIT(0)) == 1) { + pll_locked = 1; + DEV_DBG("%s: C READY\n", __func__); + break; + } + udelay(HDMI_PLL_POLL_TIMEOUT_US); + read_count++; + } + + if (read_count == HDMI_PLL_POLL_MAX_READS) { + pll_locked = 0; + DEV_DBG("%s: C READY TIMEOUT\n", __func__); + } + + mdss_pll_resource_enable(io, false); + + return pll_locked; +} + +static int hdmi_8996_v1_perform_sw_calibration(struct clk *c) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + u32 max_code = 0x190; + u32 min_code = 0x0; + u32 max_cnt = 0; + u32 min_cnt = 0; + u32 expected_counter_value = 0; + u32 step = 0; + u32 dbus_all = 0; + u32 dbus_sel = 0; + u32 vco_code = 0; + u32 val = 0; + + vco_code = 0xC8; + + DEV_DBG("%s: Starting SW calibration with vco_code = %d\n", __func__, + vco_code); + + expected_counter_value = + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP3_MODE0) << 16) | + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP2_MODE0) << 8) | + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP1_MODE0)); + + DEV_DBG("%s: expected_counter_value = %d\n", __func__, + expected_counter_value); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_CMN_MISC1); + val |= BIT(4); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MISC1, val); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_CMN_MISC1); + val |= BIT(3); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MISC1, val); + + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEBUG_BUS_SEL, 0x4); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP_CFG); + val |= BIT(1); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_CFG, val); + + udelay(60); + + while (1) { + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE1_MODE0, + vco_code & 0xFF); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE2_MODE0, + (vco_code >> 8) & 0x3); + + udelay(20); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP_CFG); + val &= ~BIT(1); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_CFG, val); + + udelay(60); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP_CFG); + val |= BIT(1); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_CFG, val); + + udelay(60); + + dbus_all = + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEBUG_BUS3) << 24) | + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEBUG_BUS2) << 16) | + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEBUG_BUS1) << 8) | + (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEBUG_BUS0)); + + dbus_sel = (dbus_all >> 9) & 0x3FFFF; + DEV_DBG("%s: loop[%d], dbus_all = 0x%x, dbus_sel = 0x%x\n", + __func__, step, dbus_all, dbus_sel); + if (dbus_sel == 0) + DEV_ERR("%s: CHECK HDMI REF CLK\n", __func__); + + if (dbus_sel == expected_counter_value) { + max_code = vco_code; + max_cnt = dbus_sel; + min_code = vco_code; + min_cnt = dbus_sel; + } else if (dbus_sel == 0) { + max_code = vco_code; + max_cnt = dbus_sel; + vco_code = (max_code + min_code)/2; + } else if (dbus_sel > expected_counter_value) { + min_code = vco_code; + min_cnt = dbus_sel; + vco_code = (max_code + min_code)/2; + } else if (dbus_sel < expected_counter_value) { + max_code = vco_code; + max_cnt = dbus_sel; + vco_code = (max_code + min_code)/2; + } + + step++; + + if ((vco_code == 0) || (vco_code == 0x3FF) || (step > 0x3FF)) { + DEV_ERR("%s: VCO tune code search failed\n", __func__); + rc = -ENOTSUPP; + break; + } + if ((max_code - min_code) <= 1) { + if ((max_code - min_code) == 1) { + if (abs((int)(max_cnt - expected_counter_value)) + < abs((int)(min_cnt - expected_counter_value + ))) { + vco_code = max_code; + } else { + vco_code = min_code; + } + } + break; + } + DEV_DBG("%s: loop[%d], new vco_code = %d\n", __func__, step, + vco_code); + } + + DEV_DBG("%s: CALIB done. vco_code = %d\n", __func__, vco_code); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE1_MODE0, + vco_code & 0xFF); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_VCO_TUNE2_MODE0, + (vco_code >> 8) & 0x3); + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_LOCK_CMP_CFG); + val &= ~BIT(1); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_LOCK_CMP_CFG, val); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_CMN_MISC1); + val |= BIT(4); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MISC1, val); + + val = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_CMN_MISC1); + val &= ~BIT(3); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_CMN_MISC1, val); + + return rc; +} + +static int hdmi_8996_v2_perform_sw_calibration(struct clk *c) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + u32 vco_code1, vco_code2, integral_loop, ready_poll; + u32 read_count = 0; + + while (read_count < (HDMI_PLL_POLL_MAX_READS << 1)) { + ready_poll = MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_C_READY_STATUS); + if ((ready_poll & BIT(0)) == 1) { + ready_poll = 1; + DEV_DBG("%s: C READY\n", __func__); + break; + } + udelay(HDMI_PLL_POLL_TIMEOUT_US); + read_count++; + } + + if (read_count == (HDMI_PLL_POLL_MAX_READS << 1)) { + ready_poll = 0; + DEV_DBG("%s: C READY TIMEOUT, TRYING SW CALIBRATION\n", + __func__); + } + + vco_code1 = MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_PLLCAL_CODE1_STATUS); + vco_code2 = MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_PLLCAL_CODE2_STATUS); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_DEBUG_BUS_SEL, 0x5); + integral_loop = MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_DEBUG_BUS0); + + if (((ready_poll & 0x1) == 0) || (((ready_poll & 1) == 1) && + (vco_code1 == 0xFF) && ((vco_code2 & 0x3) == 0x1) && + (integral_loop > 0xC0))) { + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL1, 0x04); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_ATB_SEL2, 0x00); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x17); + udelay(100); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x11); + udelay(100); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x19); + } + return rc; +} + +static int hdmi_8996_perform_sw_calibration(struct clk *c, u32 ver) +{ + switch (ver) { + case HDMI_VERSION_8996_V1: + return hdmi_8996_v1_perform_sw_calibration(c); + case HDMI_VERSION_8996_V2: + return hdmi_8996_v2_perform_sw_calibration(c); + } + return 0; +} + +static int hdmi_8996_vco_enable(struct clk *c, u32 ver) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x1); + udelay(100); + + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x19); + udelay(100); + + rc = hdmi_8996_perform_sw_calibration(c, ver); + if (rc) { + DEV_ERR("%s: software calibration failed\n", __func__); + return rc; + } + + rc = hdmi_8996_pll_lock_status(io); + if (!rc) { + DEV_ERR("%s: PLL not locked\n", __func__); + return rc; + } + + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, + 0x6F); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L1_BASE_OFFSET, + QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, + 0x6F); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L2_BASE_OFFSET, + QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, + 0x6F); + MDSS_PLL_REG_W(io->pll_base + HDMI_TX_L3_BASE_OFFSET, + QSERDES_TX_L0_HIGHZ_TRANSCEIVEREN_BIAS_DRVR_EN, + 0x6F); + + /* Disable SSC */ + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_PER1, 0x0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_PER2, 0x0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_STEP_SIZE1, 0x0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_STEP_SIZE2, 0x0); + MDSS_PLL_REG_W(io->pll_base, QSERDES_COM_SSC_EN_CENTER, 0x2); + + rc = hdmi_8996_phy_ready_status(io); + if (!rc) { + DEV_ERR("%s: PHY not READY\n", __func__); + return rc; + } + + /* Restart the retiming buffer */ + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x18); + udelay(1); + MDSS_PLL_REG_W(io->phy_base, HDMI_PHY_CFG, 0x19); + + io->pll_on = true; + return 0; +} + +static int hdmi_8996_v1_vco_enable(struct clk *c) +{ + return hdmi_8996_vco_enable(c, HDMI_VERSION_8996_V1); +} + +static int hdmi_8996_v2_vco_enable(struct clk *c) +{ + return hdmi_8996_vco_enable(c, HDMI_VERSION_8996_V2); +} + +static int hdmi_8996_v3_vco_enable(struct clk *c) +{ + return hdmi_8996_vco_enable(c, HDMI_VERSION_8996_V3); +} + +static int hdmi_8996_v3_1p8_vco_enable(struct clk *c) +{ + return hdmi_8996_vco_enable(c, HDMI_VERSION_8996_V3_1_8); +} + +static int hdmi_8996_vco_get_lock_range(struct clk *c, unsigned long pixel_clk) +{ + u32 rng = 64, cmp_cnt = 1024; + u32 coreclk_div = 5, clks_pll_divsel = 2; + u32 vco_freq, vco_ratio, ppm_range; + u64 bclk; + struct hdmi_8996_v3_post_divider pd; + + bclk = ((u64)pixel_clk) * HDMI_BIT_CLK_TO_PIX_CLK_RATIO; + + DEV_DBG("%s: rate=%ld\n", __func__, pixel_clk); + + if (hdmi_8996_v3_get_post_div(&pd, bclk) || + pd.vco_ratio <= 0 || pd.vco_freq <= 0) { + DEV_ERR("%s: couldn't get post div\n", __func__); + return -EINVAL; + } + + do_div(pd.vco_freq, HDMI_KHZ_TO_HZ * HDMI_KHZ_TO_HZ); + + vco_freq = (u32) pd.vco_freq; + vco_ratio = (u32) pd.vco_ratio; + + DEV_DBG("%s: freq %d, ratio %d\n", __func__, + vco_freq, vco_ratio); + + ppm_range = (rng * HDMI_REF_CLOCK) / cmp_cnt; + ppm_range /= vco_freq / vco_ratio; + ppm_range *= coreclk_div * clks_pll_divsel; + + DEV_DBG("%s: ppm range: %d\n", __func__, ppm_range); + + return ppm_range; +} + +static int hdmi_8996_vco_rate_atomic_update(struct clk *c, + unsigned long rate, u32 ver) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + void __iomem *pll; + struct hdmi_8996_phy_pll_reg_cfg cfg = {0}; + int rc = 0; + + rc = hdmi_8996_calculate(rate, &cfg, ver); + if (rc) { + DEV_ERR("%s: PLL calculation failed\n", __func__); + goto end; + } + + pll = io->pll_base; + + MDSS_PLL_REG_W(pll, QSERDES_COM_DEC_START_MODE0, + cfg.com_dec_start_mode0); + MDSS_PLL_REG_W(pll, QSERDES_COM_DIV_FRAC_START1_MODE0, + cfg.com_div_frac_start1_mode0); + MDSS_PLL_REG_W(pll, QSERDES_COM_DIV_FRAC_START2_MODE0, + cfg.com_div_frac_start2_mode0); + MDSS_PLL_REG_W(pll, QSERDES_COM_DIV_FRAC_START3_MODE0, + cfg.com_div_frac_start3_mode0); + + MDSS_PLL_REG_W(pll, QSERDES_COM_FREQ_UPDATE, 0x01); + MDSS_PLL_REG_W(pll, QSERDES_COM_FREQ_UPDATE, 0x00); + + DEV_DBG("%s: updated to rate %ld\n", __func__, rate); +end: + return rc; +} + +static int hdmi_8996_vco_set_rate(struct clk *c, unsigned long rate, u32 ver) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + unsigned int set_power_dwn = 0; + bool atomic_update = false; + int rc, pll_lock_range; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + DEV_ERR("pll resource can't be enabled\n"); + return rc; + } + + DEV_DBG("%s: rate %ld\n", __func__, rate); + + if (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_C_READY_STATUS) & BIT(0) && + MDSS_PLL_REG_R(io->phy_base, HDMI_PHY_STATUS) & BIT(0)) { + pll_lock_range = hdmi_8996_vco_get_lock_range(c, vco->rate); + + if (pll_lock_range > 0 && vco->rate) { + u32 range_limit; + + range_limit = vco->rate * + (pll_lock_range / HDMI_KHZ_TO_HZ); + range_limit /= HDMI_KHZ_TO_HZ; + + DEV_DBG("%s: range limit %d\n", __func__, range_limit); + + if (abs(rate - vco->rate) < range_limit) + atomic_update = true; + } + } + + if (io->pll_on && !atomic_update) + set_power_dwn = 1; + + if (atomic_update) { + hdmi_8996_vco_rate_atomic_update(c, rate, ver); + } else { + rc = hdmi_8996_phy_pll_set_clk_rate(c, rate, ver); + if (rc) + DEV_ERR("%s: Failed to set clk rate\n", __func__); + } + + mdss_pll_resource_enable(io, false); + + if (set_power_dwn) + hdmi_8996_vco_enable(c, ver); + + vco->rate = rate; + vco->rate_set = true; + + return 0; +} + +static int hdmi_8996_v1_vco_set_rate(struct clk *c, unsigned long rate) +{ + return hdmi_8996_vco_set_rate(c, rate, HDMI_VERSION_8996_V1); +} + +static int hdmi_8996_v2_vco_set_rate(struct clk *c, unsigned long rate) +{ + return hdmi_8996_vco_set_rate(c, rate, HDMI_VERSION_8996_V2); +} + +static int hdmi_8996_v3_vco_set_rate(struct clk *c, unsigned long rate) +{ + return hdmi_8996_vco_set_rate(c, rate, HDMI_VERSION_8996_V3); +} + +static int hdmi_8996_v3_1p8_vco_set_rate(struct clk *c, unsigned long rate) +{ + return hdmi_8996_vco_set_rate(c, rate, HDMI_VERSION_8996_V3_1_8); +} + +static unsigned long hdmi_get_hsclk_sel_divisor(unsigned long hsclk_sel) +{ + unsigned long divisor; + + switch (hsclk_sel) { + case 0: + divisor = 2; + break; + case 1: + divisor = 6; + break; + case 2: + divisor = 10; + break; + case 3: + divisor = 14; + break; + case 4: + divisor = 3; + break; + case 5: + divisor = 9; + break; + case 6: + case 13: + divisor = 15; + break; + case 7: + divisor = 21; + break; + case 8: + divisor = 4; + break; + case 9: + divisor = 12; + break; + case 10: + divisor = 20; + break; + case 11: + divisor = 28; + break; + case 12: + divisor = 5; + break; + case 14: + divisor = 25; + break; + case 15: + divisor = 35; + break; + default: + divisor = 1; + DEV_ERR("%s: invalid hsclk_sel value = %lu", + __func__, hsclk_sel); + break; + } + + return divisor; +} + +static unsigned long hdmi_8996_vco_get_rate(struct clk *c) +{ + unsigned long freq = 0, hsclk_sel = 0, tx_band = 0, dec_start = 0, + div_frac_start = 0, vco_clock_freq = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + if (mdss_pll_resource_enable(io, true)) { + DEV_ERR("%s: pll resource can't be enabled\n", __func__); + return freq; + } + + dec_start = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_DEC_START_MODE0); + + div_frac_start = + MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_DIV_FRAC_START1_MODE0) | + MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_DIV_FRAC_START2_MODE0) << 8 | + MDSS_PLL_REG_R(io->pll_base, + QSERDES_COM_DIV_FRAC_START3_MODE0) << 16; + + vco_clock_freq = (dec_start + (div_frac_start / (1 << 20))) + * 4 * (HDMI_REF_CLOCK); + + hsclk_sel = MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_HSCLK_SEL) & 0x15; + hsclk_sel = hdmi_get_hsclk_sel_divisor(hsclk_sel); + tx_band = MDSS_PLL_REG_R(io->pll_base + HDMI_TX_L0_BASE_OFFSET, + QSERDES_TX_L0_TX_BAND) & 0x3; + + freq = vco_clock_freq / (10 * hsclk_sel * (1 << tx_band)); + + mdss_pll_resource_enable(io, false); + + DEV_DBG("%s: freq = %lu\n", __func__, freq); + + return freq; +} + +static long hdmi_8996_vco_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + + DEV_DBG("rrate=%ld\n", rrate); + + return rrate; +} + +static int hdmi_8996_vco_prepare(struct clk *c, u32 ver) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + int ret = 0; + + DEV_DBG("rate=%ld\n", vco->rate); + + if (!vco->rate_set && vco->rate) + ret = hdmi_8996_vco_set_rate(c, vco->rate, ver); + + if (!ret) { + ret = mdss_pll_resource_enable(io, true); + if (ret) + DEV_ERR("pll resource can't be enabled\n"); + } + + return ret; +} + +static int hdmi_8996_v1_vco_prepare(struct clk *c) +{ + return hdmi_8996_vco_prepare(c, HDMI_VERSION_8996_V1); +} + +static int hdmi_8996_v2_vco_prepare(struct clk *c) +{ + return hdmi_8996_vco_prepare(c, HDMI_VERSION_8996_V2); +} + +static int hdmi_8996_v3_vco_prepare(struct clk *c) +{ + return hdmi_8996_vco_prepare(c, HDMI_VERSION_8996_V3); +} + +static int hdmi_8996_v3_1p8_vco_prepare(struct clk *c) +{ + return hdmi_8996_vco_prepare(c, HDMI_VERSION_8996_V3_1_8); +} + +static void hdmi_8996_vco_unprepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + vco->rate_set = false; + + if (!io) { + DEV_ERR("Invalid input parameter\n"); + return; + } + + if (!io->pll_on && + mdss_pll_resource_enable(io, true)) { + DEV_ERR("pll resource can't be enabled\n"); + return; + } + + io->handoff_resources = false; + mdss_pll_resource_enable(io, false); + io->pll_on = false; +} + +static enum handoff hdmi_8996_vco_handoff(struct clk *c) +{ + enum handoff ret = HANDOFF_DISABLED_CLK; + struct hdmi_pll_vco_clk *vco = to_hdmi_8996_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + if (is_gdsc_disabled(io)) + return HANDOFF_DISABLED_CLK; + + if (mdss_pll_resource_enable(io, true)) { + DEV_ERR("pll resource can't be enabled\n"); + return ret; + } + + io->handoff_resources = true; + + if (MDSS_PLL_REG_R(io->pll_base, QSERDES_COM_C_READY_STATUS) & BIT(0)) { + if (MDSS_PLL_REG_R(io->phy_base, HDMI_PHY_STATUS) & BIT(0)) { + io->pll_on = true; + c->rate = hdmi_8996_vco_get_rate(c); + vco->rate = c->rate; + ret = HANDOFF_ENABLED_CLK; + } else { + io->handoff_resources = false; + mdss_pll_resource_enable(io, false); + DEV_DBG("%s: PHY not ready\n", __func__); + } + } else { + io->handoff_resources = false; + mdss_pll_resource_enable(io, false); + DEV_DBG("%s: PLL not locked\n", __func__); + } + + DEV_DBG("done, ret=%d\n", ret); + return ret; +} + +static const struct clk_ops hdmi_8996_v1_vco_clk_ops = { + .enable = hdmi_8996_v1_vco_enable, + .set_rate = hdmi_8996_v1_vco_set_rate, + .get_rate = hdmi_8996_vco_get_rate, + .round_rate = hdmi_8996_vco_round_rate, + .prepare = hdmi_8996_v1_vco_prepare, + .unprepare = hdmi_8996_vco_unprepare, + .handoff = hdmi_8996_vco_handoff, +}; + +static const struct clk_ops hdmi_8996_v2_vco_clk_ops = { + .enable = hdmi_8996_v2_vco_enable, + .set_rate = hdmi_8996_v2_vco_set_rate, + .get_rate = hdmi_8996_vco_get_rate, + .round_rate = hdmi_8996_vco_round_rate, + .prepare = hdmi_8996_v2_vco_prepare, + .unprepare = hdmi_8996_vco_unprepare, + .handoff = hdmi_8996_vco_handoff, +}; + +static const struct clk_ops hdmi_8996_v3_vco_clk_ops = { + .enable = hdmi_8996_v3_vco_enable, + .set_rate = hdmi_8996_v3_vco_set_rate, + .get_rate = hdmi_8996_vco_get_rate, + .round_rate = hdmi_8996_vco_round_rate, + .prepare = hdmi_8996_v3_vco_prepare, + .unprepare = hdmi_8996_vco_unprepare, + .handoff = hdmi_8996_vco_handoff, +}; + +static const struct clk_ops hdmi_8996_v3_1p8_vco_clk_ops = { + .enable = hdmi_8996_v3_1p8_vco_enable, + .set_rate = hdmi_8996_v3_1p8_vco_set_rate, + .get_rate = hdmi_8996_vco_get_rate, + .round_rate = hdmi_8996_vco_round_rate, + .prepare = hdmi_8996_v3_1p8_vco_prepare, + .unprepare = hdmi_8996_vco_unprepare, + .handoff = hdmi_8996_vco_handoff, +}; + + +static struct hdmi_pll_vco_clk hdmi_vco_clk = { + .c = { + .dbg_name = "hdmi_8996_vco_clk", + .ops = &hdmi_8996_v1_vco_clk_ops, + CLK_INIT(hdmi_vco_clk.c), + }, +}; + +static struct clk_lookup hdmipllcc_8996[] = { + CLK_LIST(hdmi_vco_clk), +}; + +int hdmi_8996_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res, u32 ver) +{ + int rc = -ENOTSUPP; + + if (!pll_res || !pll_res->phy_base || !pll_res->pll_base) { + DEV_ERR("%s: Invalid input parameters\n", __func__); + return -EPROBE_DEFER; + } + + /* Set client data for vco, mux and div clocks */ + hdmi_vco_clk.priv = pll_res; + + switch (ver) { + case HDMI_VERSION_8996_V2: + hdmi_vco_clk.c.ops = &hdmi_8996_v2_vco_clk_ops; + break; + case HDMI_VERSION_8996_V3: + hdmi_vco_clk.c.ops = &hdmi_8996_v3_vco_clk_ops; + break; + case HDMI_VERSION_8996_V3_1_8: + hdmi_vco_clk.c.ops = &hdmi_8996_v3_1p8_vco_clk_ops; + break; + default: + hdmi_vco_clk.c.ops = &hdmi_8996_v1_vco_clk_ops; + break; + }; + + rc = of_msm_clock_register(pdev->dev.of_node, hdmipllcc_8996, + ARRAY_SIZE(hdmipllcc_8996)); + if (rc) { + DEV_ERR("%s: Clock register failed rc=%d\n", __func__, rc); + rc = -EPROBE_DEFER; + } else { + DEV_DBG("%s SUCCESS\n", __func__); + } + + return rc; +} + +int hdmi_8996_v1_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + return hdmi_8996_pll_clock_register(pdev, pll_res, + HDMI_VERSION_8996_V1); +} + +int hdmi_8996_v2_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + return hdmi_8996_pll_clock_register(pdev, pll_res, + HDMI_VERSION_8996_V2); +} + +int hdmi_8996_v3_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + return hdmi_8996_pll_clock_register(pdev, pll_res, + HDMI_VERSION_8996_V3); +} + +int hdmi_8996_v3_1p8_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + return hdmi_8996_pll_clock_register(pdev, pll_res, + HDMI_VERSION_8996_V3_1_8); +} diff --git a/drivers/clk/qcom/mdss/mdss-hdmi-pll-8998.c b/drivers/clk/qcom/mdss/mdss-hdmi-pll-8998.c new file mode 100644 index 0000000000000000000000000000000000000000..f27b816e41249e9b3c71089715650571f822dfa2 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-hdmi-pll-8998.c @@ -0,0 +1,841 @@ +/* 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. + * + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" +#include "mdss-hdmi-pll.h" + +#define _W(x, y, z) MDSS_PLL_REG_W(x, y, z) +#define _R(x, y) MDSS_PLL_REG_R(x, y) + +/* PLL REGISTERS */ +#define BIAS_EN_CLKBUFLR_EN (0x034) +#define CLK_ENABLE1 (0x038) +#define SYS_CLK_CTRL (0x03C) +#define SYSCLK_BUF_ENABLE (0x040) +#define PLL_IVCO (0x048) +#define CP_CTRL_MODE0 (0x060) +#define PLL_RCTRL_MODE0 (0x068) +#define PLL_CCTRL_MODE0 (0x070) +#define SYSCLK_EN_SEL (0x080) +#define RESETSM_CNTRL (0x088) +#define LOCK_CMP_EN (0x090) +#define LOCK_CMP1_MODE0 (0x098) +#define LOCK_CMP2_MODE0 (0x09C) +#define LOCK_CMP3_MODE0 (0x0A0) +#define DEC_START_MODE0 (0x0B0) +#define DIV_FRAC_START1_MODE0 (0x0B8) +#define DIV_FRAC_START2_MODE0 (0x0BC) +#define DIV_FRAC_START3_MODE0 (0x0C0) +#define INTEGLOOP_GAIN0_MODE0 (0x0D8) +#define INTEGLOOP_GAIN1_MODE0 (0x0DC) +#define VCO_TUNE_CTRL (0x0EC) +#define VCO_TUNE_MAP (0x0F0) +#define CLK_SELECT (0x138) +#define HSCLK_SEL (0x13C) +#define CORECLK_DIV_MODE0 (0x148) +#define CORE_CLK_EN (0x154) +#define C_READY_STATUS (0x158) +#define SVS_MODE_CLK_SEL (0x164) + +/* Tx Channel PHY registers */ +#define PHY_TX_EMP_POST1_LVL(n) ((((n) * 0x200) + 0x400) + 0x000) +#define PHY_TX_INTERFACE_SELECT_TX_BAND(n) ((((n) * 0x200) + 0x400) + 0x008) +#define PHY_TX_CLKBUF_TERM_ENABLE(n) ((((n) * 0x200) + 0x400) + 0x00C) +#define PHY_TX_DRV_LVL_RES_CODE_OFFSET(n) ((((n) * 0x200) + 0x400) + 0x014) +#define PHY_TX_DRV_LVL(n) ((((n) * 0x200) + 0x400) + 0x018) +#define PHY_TX_LANE_CONFIG(n) ((((n) * 0x200) + 0x400) + 0x01C) +#define PHY_TX_PRE_DRIVER_1(n) ((((n) * 0x200) + 0x400) + 0x024) +#define PHY_TX_PRE_DRIVER_2(n) ((((n) * 0x200) + 0x400) + 0x028) +#define PHY_TX_LANE_MODE(n) ((((n) * 0x200) + 0x400) + 0x02C) + +/* HDMI PHY registers */ +#define PHY_CFG (0x00) +#define PHY_PD_CTL (0x04) +#define PHY_MODE (0x10) +#define PHY_CLOCK (0x5C) +#define PHY_CMN_CTRL (0x68) +#define PHY_STATUS (0xB4) + +#define HDMI_BIT_CLK_TO_PIX_CLK_RATIO 10 +#define HDMI_MHZ_TO_HZ 1000000 +#define HDMI_HZ_TO_MHZ 1000000 +#define HDMI_REF_CLOCK_MHZ 19.2 +#define HDMI_REF_CLOCK_HZ (HDMI_REF_CLOCK_MHZ * 1000000) +#define HDMI_VCO_MIN_RATE_HZ 25000000 +#define HDMI_VCO_MAX_RATE_HZ 600000000 + +struct 8998_reg_cfg { + u32 tx_band; + u32 svs_mode_clk_sel; + u32 hsclk_sel; + u32 lock_cmp_en; + u32 cctrl_mode0; + u32 rctrl_mode0; + u32 cpctrl_mode0; + u32 dec_start_mode0; + u32 div_frac_start1_mode0; + u32 div_frac_start2_mode0; + u32 div_frac_start3_mode0; + u32 integloop_gain0_mode0; + u32 integloop_gain1_mode0; + u32 lock_cmp1_mode0; + u32 lock_cmp2_mode0; + u32 lock_cmp3_mode0; + u32 ssc_per1; + u32 ssc_per2; + u32 ssc_step_size1; + u32 ssc_step_size2; + u32 core_clk_en; + u32 coreclk_div_mode0; + u32 phy_mode; + u32 vco_freq; + u32 hsclk_divsel; + u32 vco_ratio; + u32 ssc_en_center; + + u32 l0_tx_drv_lvl; + u32 l0_tx_emp_post1_lvl; + u32 l1_tx_drv_lvl; + u32 l1_tx_emp_post1_lvl; + u32 l2_tx_drv_lvl; + u32 l2_tx_emp_post1_lvl; + u32 l3_tx_drv_lvl; + u32 l3_tx_emp_post1_lvl; + + u32 l0_pre_driver_1; + u32 l0_pre_driver_2; + u32 l1_pre_driver_1; + u32 l1_pre_driver_2; + u32 l2_pre_driver_1; + u32 l2_pre_driver_2; + u32 l3_pre_driver_1; + u32 l3_pre_driver_2; + + bool debug; +}; + +static void hdmi_8998_get_div(struct 8998_reg_cfg * cfg, unsigned long pclk) +{ + u32 const ratio_list[] = {1, 2, 3, 4, 5, 6, + 9, 10, 12, 15, 25}; + u32 const band_list[] = {0, 1, 2, 3}; + u32 const sz_ratio = ARRAY_SIZE(ratio_list); + u32 const sz_band = ARRAY_SIZE(band_list); + u32 const min_freq = 8000, max_freq = 12000; + u32 const cmp_cnt = 1024; + u32 const th_min = 500, th_max = 1000; + u64 bit_clk = pclk * HDMI_BIT_CLK_TO_PIX_CLK_RATIO; + u32 half_rate_mode = 0; + u32 freq_optimal, list_elements; + int optimal_index; + u32 i, j, k; + u32 freq_list[sz_ratio * sz_band]; + u32 found_hsclk_divsel = 0, found_vco_ratio; + u32 found_tx_band_sel, found_vco_freq; + +find_optimal_index: + freq_optimal = max_freq; + optimal_index = -1; + list_elements = 0; + + for (i = 0; i < sz_ratio; i++) { + for (j = 0; j < sz_band; j++) { + u64 freq = (bit_clk / (1 << half_rate_mode)); + + freq *= (ratio_list[i] * (1 << band_list[j])); + do_div(freq, (u64) HDMI_MHZ_TO_HZ); + freq_list[list_elements++] = freq; + } + } + + for (k = 0; k < ARRAY_SIZE(freq_list); k++) { + u32 const clks_pll_div = 2, core_clk_div = 5; + u32 const rng1 = 16, rng2 = 8; + u32 core_clk, rvar1; + u32 th1, th2; + + core_clk = (((freq_list[k] / + ratio_list[k / sz_band]) / + clks_pll_div) / core_clk_div); + + rvar1 = HDMI_REF_CLOCK_HZ / cmp_cnt; + rvar1 *= rng1; + rvar1 /= core_clk; + + th1 = rvar1; + + rvar1 = HDMI_REF_CLOCK_HZ / cmp_cnt; + rvar1 *= rng2; + rvar1 /= core_clk; + + th2 = rvar1; + + if (freq_list[k] >= min_freq && + freq_list[k] <= max_freq) { + if ((th1 >= th_min && th1 <= th_max) || + (th2 >= th_min && th2 <= th_max)) { + if (freq_list[k] <= freq_optimal) { + freq_optimal = freq_list[k]; + optimal_index = k; + } + } + } + } + + if (optimal_index == -1) { + if (!half_rate_mode) { + half_rate_mode = 1; + goto find_optimal_index; + } else { + /* set to default values */ + found_vco_freq = max_freq; + found_hsclk_divsel = 0; + found_vco_ratio = 2; + found_tx_band_sel = 0; + pr_err("Config error for pclk %ld\n", pclk); + } + } else { + found_vco_ratio = ratio_list[optimal_index / sz_band]; + found_tx_band_sel = band_list[optimal_index % sz_band]; + found_vco_freq = freq_optimal; + } + + switch (found_vco_ratio) { + case 1: + found_hsclk_divsel = 15; + break; + case 2: + found_hsclk_divsel = 0; + break; + case 3: + found_hsclk_divsel = 4; + break; + case 4: + found_hsclk_divsel = 8; + break; + case 5: + found_hsclk_divsel = 12; + break; + case 6: + found_hsclk_divsel = 1; + break; + case 9: + found_hsclk_divsel = 5; + break; + case 10: + found_hsclk_divsel = 2; + break; + case 12: + found_hsclk_divsel = 9; + break; + case 15: + found_hsclk_divsel = 13; + break; + case 25: + found_hsclk_divsel = 14; + break; + }; + + pr_debug("found_vco_freq=%d\n", found_vco_freq); + pr_debug("found_hsclk_divsel=%d\n", found_hsclk_divsel); + pr_debug("found_vco_ratio=%d\n", found_vco_ratio); + pr_debug("found_tx_band_sel=%d\n", found_tx_band_sel); + pr_debug("half_rate_mode=%d\n", half_rate_mode); + pr_debug("optimal_index=%d\n", optimal_index); + + cfg->vco_freq = found_vco_freq; + cfg->hsclk_divsel = found_hsclk_divsel; + cfg->vco_ratio = found_vco_ratio; + cfg->tx_band = found_tx_band_sel; +} + +static int hdmi_8998_config_phy(unsigned long rate, + struct 8998_reg_cfg * cfg) +{ + u64 const high_freq_bit_clk_threshold = 3400000000UL; + u64 const dig_freq_bit_clk_threshold = 1500000000UL; + u64 const mid_freq_bit_clk_threshold = 750000000; + int rc = 0; + u64 fdata, tmds_clk; + u64 pll_div = 4 * HDMI_REF_CLOCK_HZ; + u64 bclk; + u64 vco_freq_mhz; + u64 hsclk_sel, dec_start, div_frac_start; + u64 rem; + u64 cpctrl, rctrl, cctrl; + u64 integloop_gain; + u32 digclk_divsel; + u32 tmds_bclk_ratio; + u64 cmp_rng, cmp_cnt = 1024, pll_cmp; + bool gen_ssc = false; + + bclk = rate * HDMI_BIT_CLK_TO_PIX_CLK_RATIO; + + if (bclk > high_freq_bit_clk_threshold) { + tmds_clk = rate / 4; + tmds_bclk_ratio = 1; + } else { + tmds_clk = rate; + tmds_bclk_ratio = 0; + } + + hdmi_8998_get_div(cfg, rate); + + vco_freq_mhz = cfg->vco_freq * (u64) HDMI_HZ_TO_MHZ; + fdata = cfg->vco_freq; + do_div(fdata, cfg->vco_ratio); + + hsclk_sel = cfg->hsclk_divsel; + dec_start = vco_freq_mhz; + do_div(dec_start, pll_div); + + div_frac_start = vco_freq_mhz * (1 << 20); + rem = do_div(div_frac_start, pll_div); + div_frac_start -= (dec_start * (1 << 20)); + if (rem > (pll_div >> 1)) + div_frac_start++; + + if ((div_frac_start != 0) || (gen_ssc == true)) { + cpctrl = 0x8; + rctrl = 0x16; + cctrl = 0x34; + } else { + cpctrl = 0x30; + rctrl = 0x18; + cctrl = 0x2; + } + + digclk_divsel = (bclk > dig_freq_bit_clk_threshold) ? 0x1 : 0x2; + + integloop_gain = ((div_frac_start != 0) || + (gen_ssc == true)) ? 0x3F : 0xC4; + integloop_gain <<= digclk_divsel; + integloop_gain = (integloop_gain <= 2046 ? integloop_gain : 0x7FE); + + cmp_rng = gen_ssc ? 0x40 : 0x10; + + pll_cmp = cmp_cnt * fdata; + rem = do_div(pll_cmp, (u64)(HDMI_REF_CLOCK_MHZ * 10)); + if (rem > ((u64)(HDMI_REF_CLOCK_MHZ * 10) >> 1)) + pll_cmp++; + + pll_cmp = pll_cmp - 1; + + pr_debug("VCO_FREQ = %u\n", cfg->vco_freq); + pr_debug("FDATA = %llu\n", fdata); + pr_debug("DEC_START = %llu\n", dec_start); + pr_debug("DIV_FRAC_START = %llu\n", div_frac_start); + pr_debug("CPCTRL = %llu\n", cpctrl); + pr_debug("RCTRL = %llu\n", rctrl); + pr_debug("CCTRL = %llu\n", cctrl); + pr_debug("DIGCLK_DIVSEL = %u\n", digclk_divsel); + pr_debug("INTEGLOOP_GAIN = %llu\n", integloop_gain); + pr_debug("CMP_RNG = %llu\n", cmp_rng); + pr_debug("PLL_CMP = %llu\n", pll_cmp); + + cfg->svs_mode_clk_sel = (digclk_divsel & 0xFF); + cfg->hsclk_sel = (0x20 | hsclk_sel); + cfg->lock_cmp_en = (gen_ssc ? 0x4 : 0x0); + cfg->cctrl_mode0 = (cctrl & 0xFF); + cfg->rctrl_mode0 = (rctrl & 0xFF); + cfg->cpctrl_mode0 = (cpctrl & 0xFF); + cfg->dec_start_mode0 = (dec_start & 0xFF); + cfg->div_frac_start1_mode0 = (div_frac_start & 0xFF); + cfg->div_frac_start2_mode0 = ((div_frac_start & 0xFF00) >> 8); + cfg->div_frac_start3_mode0 = ((div_frac_start & 0xF0000) >> 16); + cfg->integloop_gain0_mode0 = (integloop_gain & 0xFF); + cfg->integloop_gain1_mode0 = (integloop_gain & 0xF00) >> 8; + cfg->lock_cmp1_mode0 = (pll_cmp & 0xFF); + cfg->lock_cmp2_mode0 = ((pll_cmp & 0xFF00) >> 8); + cfg->lock_cmp3_mode0 = ((pll_cmp & 0x30000) >> 16); + cfg->ssc_per1 = 0; + cfg->ssc_per2 = 0; + cfg->ssc_step_size1 = 0; + cfg->ssc_step_size2 = 0; + cfg->core_clk_en = 0x2C; + cfg->coreclk_div_mode0 = 0x5; + cfg->phy_mode = (tmds_bclk_ratio ? 0x5 : 0x4); + cfg->ssc_en_center = 0x0; + + if (bclk > high_freq_bit_clk_threshold) { + cfg->l0_tx_drv_lvl = 0xA; + cfg->l0_tx_emp_post1_lvl = 0x3; + cfg->l1_tx_drv_lvl = 0xA; + cfg->l1_tx_emp_post1_lvl = 0x3; + cfg->l2_tx_drv_lvl = 0xA; + cfg->l2_tx_emp_post1_lvl = 0x3; + cfg->l3_tx_drv_lvl = 0x8; + cfg->l3_tx_emp_post1_lvl = 0x3; + cfg->l0_pre_driver_1 = 0x0; + cfg->l0_pre_driver_2 = 0x1C; + cfg->l1_pre_driver_1 = 0x0; + cfg->l1_pre_driver_2 = 0x1C; + cfg->l2_pre_driver_1 = 0x0; + cfg->l2_pre_driver_2 = 0x1C; + cfg->l3_pre_driver_1 = 0x0; + cfg->l3_pre_driver_2 = 0x0; + } else if (bclk > dig_freq_bit_clk_threshold) { + cfg->l0_tx_drv_lvl = 0x9; + cfg->l0_tx_emp_post1_lvl = 0x3; + cfg->l1_tx_drv_lvl = 0x9; + cfg->l1_tx_emp_post1_lvl = 0x3; + cfg->l2_tx_drv_lvl = 0x9; + cfg->l2_tx_emp_post1_lvl = 0x3; + cfg->l3_tx_drv_lvl = 0x8; + cfg->l3_tx_emp_post1_lvl = 0x3; + cfg->l0_pre_driver_1 = 0x0; + cfg->l0_pre_driver_2 = 0x16; + cfg->l1_pre_driver_1 = 0x0; + cfg->l1_pre_driver_2 = 0x16; + cfg->l2_pre_driver_1 = 0x0; + cfg->l2_pre_driver_2 = 0x16; + cfg->l3_pre_driver_1 = 0x0; + cfg->l3_pre_driver_2 = 0x0; + } else if (bclk > mid_freq_bit_clk_threshold) { + cfg->l0_tx_drv_lvl = 0x9; + cfg->l0_tx_emp_post1_lvl = 0x3; + cfg->l1_tx_drv_lvl = 0x9; + cfg->l1_tx_emp_post1_lvl = 0x3; + cfg->l2_tx_drv_lvl = 0x9; + cfg->l2_tx_emp_post1_lvl = 0x3; + cfg->l3_tx_drv_lvl = 0x8; + cfg->l3_tx_emp_post1_lvl = 0x3; + cfg->l0_pre_driver_1 = 0x0; + cfg->l0_pre_driver_2 = 0x0E; + cfg->l1_pre_driver_1 = 0x0; + cfg->l1_pre_driver_2 = 0x0E; + cfg->l2_pre_driver_1 = 0x0; + cfg->l2_pre_driver_2 = 0x0E; + cfg->l3_pre_driver_1 = 0x0; + cfg->l3_pre_driver_2 = 0x0; + } else { + cfg->l0_tx_drv_lvl = 0x0; + cfg->l0_tx_emp_post1_lvl = 0x0; + cfg->l1_tx_drv_lvl = 0x0; + cfg->l1_tx_emp_post1_lvl = 0x0; + cfg->l2_tx_drv_lvl = 0x0; + cfg->l2_tx_emp_post1_lvl = 0x0; + cfg->l3_tx_drv_lvl = 0x0; + cfg->l3_tx_emp_post1_lvl = 0x0; + cfg->l0_pre_driver_1 = 0x0; + cfg->l0_pre_driver_2 = 0x01; + cfg->l1_pre_driver_1 = 0x0; + cfg->l1_pre_driver_2 = 0x01; + cfg->l2_pre_driver_1 = 0x0; + cfg->l2_pre_driver_2 = 0x01; + cfg->l3_pre_driver_1 = 0x0; + cfg->l3_pre_driver_2 = 0x0; + } + + return rc; +} + +static int hdmi_8998_pll_set_clk_rate(struct clk *c, unsigned long rate) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + struct 8998_reg_cfg cfg = {0}; + void __iomem *phy = io->phy_base, *pll = io->pll_base; + + rc = hdmi_8998_config_phy(rate, &cfg); + if (rc) { + pr_err("rate calculation failed\n, rc=%d", rc); + return rc; + } + + _W(phy, PHY_PD_CTL, 0x0); + udelay(500); + + _W(phy, PHY_PD_CTL, 0x1); + _W(pll, RESETSM_CNTRL, 0x20); + _W(phy, PHY_CMN_CTRL, 0x6); + _W(pll, PHY_TX_INTERFACE_SELECT_TX_BAND(0), cfg.tx_band); + _W(pll, PHY_TX_INTERFACE_SELECT_TX_BAND(1), cfg.tx_band); + _W(pll, PHY_TX_INTERFACE_SELECT_TX_BAND(2), cfg.tx_band); + _W(pll, PHY_TX_INTERFACE_SELECT_TX_BAND(3), cfg.tx_band); + _W(pll, PHY_TX_CLKBUF_TERM_ENABLE(0), 0x1); + _W(pll, PHY_TX_LANE_MODE(0), 0x20); + _W(pll, PHY_TX_LANE_MODE(1), 0x20); + _W(pll, PHY_TX_LANE_MODE(2), 0x20); + _W(pll, PHY_TX_LANE_MODE(3), 0x20); + _W(pll, PHY_TX_CLKBUF_TERM_ENABLE(1), 0x1); + _W(pll, PHY_TX_CLKBUF_TERM_ENABLE(2), 0x1); + _W(pll, PHY_TX_CLKBUF_TERM_ENABLE(3), 0x1); + _W(pll, SYSCLK_BUF_ENABLE, 0x2); + _W(pll, BIAS_EN_CLKBUFLR_EN, 0xB); + _W(pll, SYSCLK_EN_SEL, 0x37); + _W(pll, SYS_CLK_CTRL, 0x2); + _W(pll, CLK_ENABLE1, 0xE); + _W(pll, PLL_IVCO, 0xF); + _W(pll, VCO_TUNE_CTRL, 0x0); + _W(pll, SVS_MODE_CLK_SEL, cfg.svs_mode_clk_sel); + _W(pll, CLK_SELECT, 0x30); + _W(pll, HSCLK_SEL, cfg.hsclk_sel); + _W(pll, LOCK_CMP_EN, cfg.lock_cmp_en); + _W(pll, PLL_CCTRL_MODE0, cfg.cctrl_mode0); + _W(pll, PLL_RCTRL_MODE0, cfg.rctrl_mode0); + _W(pll, CP_CTRL_MODE0, cfg.cpctrl_mode0); + _W(pll, DEC_START_MODE0, cfg.dec_start_mode0); + _W(pll, DIV_FRAC_START1_MODE0, cfg.div_frac_start1_mode0); + _W(pll, DIV_FRAC_START2_MODE0, cfg.div_frac_start2_mode0); + _W(pll, DIV_FRAC_START3_MODE0, cfg.div_frac_start3_mode0); + _W(pll, INTEGLOOP_GAIN0_MODE0, cfg.integloop_gain0_mode0); + _W(pll, INTEGLOOP_GAIN1_MODE0, cfg.integloop_gain1_mode0); + _W(pll, LOCK_CMP1_MODE0, cfg.lock_cmp1_mode0); + _W(pll, LOCK_CMP2_MODE0, cfg.lock_cmp2_mode0); + _W(pll, LOCK_CMP3_MODE0, cfg.lock_cmp3_mode0); + _W(pll, VCO_TUNE_MAP, 0x0); + _W(pll, CORE_CLK_EN, cfg.core_clk_en); + _W(pll, CORECLK_DIV_MODE0, cfg.coreclk_div_mode0); + + _W(pll, PHY_TX_DRV_LVL(0), cfg.l0_tx_drv_lvl); + _W(pll, PHY_TX_DRV_LVL(1), cfg.l1_tx_drv_lvl); + _W(pll, PHY_TX_DRV_LVL(2), cfg.l2_tx_drv_lvl); + _W(pll, PHY_TX_DRV_LVL(3), cfg.l3_tx_drv_lvl); + + _W(pll, PHY_TX_EMP_POST1_LVL(0), cfg.l0_tx_emp_post1_lvl); + _W(pll, PHY_TX_EMP_POST1_LVL(1), cfg.l1_tx_emp_post1_lvl); + _W(pll, PHY_TX_EMP_POST1_LVL(2), cfg.l2_tx_emp_post1_lvl); + _W(pll, PHY_TX_EMP_POST1_LVL(3), cfg.l3_tx_emp_post1_lvl); + + _W(pll, PHY_TX_PRE_DRIVER_1(0), cfg.l0_pre_driver_1); + _W(pll, PHY_TX_PRE_DRIVER_1(1), cfg.l1_pre_driver_1); + _W(pll, PHY_TX_PRE_DRIVER_1(2), cfg.l2_pre_driver_1); + _W(pll, PHY_TX_PRE_DRIVER_1(3), cfg.l3_pre_driver_1); + + _W(pll, PHY_TX_PRE_DRIVER_2(0), cfg.l0_pre_driver_2); + _W(pll, PHY_TX_PRE_DRIVER_2(1), cfg.l1_pre_driver_2); + _W(pll, PHY_TX_PRE_DRIVER_2(2), cfg.l2_pre_driver_2); + _W(pll, PHY_TX_PRE_DRIVER_2(3), cfg.l3_pre_driver_2); + + _W(pll, PHY_TX_DRV_LVL_RES_CODE_OFFSET(0), 0x0); + _W(pll, PHY_TX_DRV_LVL_RES_CODE_OFFSET(1), 0x0); + _W(pll, PHY_TX_DRV_LVL_RES_CODE_OFFSET(2), 0x0); + _W(pll, PHY_TX_DRV_LVL_RES_CODE_OFFSET(3), 0x0); + + _W(phy, PHY_MODE, cfg.phy_mode); + + _W(pll, PHY_TX_LANE_CONFIG(0), 0x10); + _W(pll, PHY_TX_LANE_CONFIG(1), 0x10); + _W(pll, PHY_TX_LANE_CONFIG(2), 0x10); + _W(pll, PHY_TX_LANE_CONFIG(3), 0x10); + + /* Ensure all registers are flushed to hardware */ + wmb(); + + return 0; +} + +static int hdmi_8998_pll_lock_status(struct mdss_pll_resources *io) +{ + u32 const delay_us = 100; + u32 const timeout_us = 5000; + u32 status; + int rc = 0; + void __iomem *pll = io->pll_base; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + rc = readl_poll_timeout_atomic(pll + C_READY_STATUS, + status, + ((status & BIT(0)) > 0), + delay_us, + timeout_us); + if (rc) + pr_err("HDMI PLL(%d) lock failed, status=0x%08x\n", + io->index, status); + else + pr_debug("HDMI PLL(%d) lock passed, status=0x%08x\n", + io->index, status); + + mdss_pll_resource_enable(io, false); + + return rc; +} + +static int hdmi_8998_phy_ready_status(struct mdss_pll_resources *io) +{ + u32 const delay_us = 100; + u32 const timeout_us = 5000; + u32 status; + int rc = 0; + void __iomem *phy = io->phy_base; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource can't be enabled\n"); + return rc; + } + + rc = readl_poll_timeout_atomic(phy + PHY_STATUS, + status, + ((status & BIT(0)) > 0), + delay_us, + timeout_us); + if (rc) + pr_err("HDMI PHY(%d) not ready, status=0x%08x\n", + io->index, status); + else + pr_debug("HDMI PHY(%d) ready, status=0x%08x\n", + io->index, status); + + mdss_pll_resource_enable(io, false); + + return rc; +} + +static int hdmi_8998_vco_set_rate(struct clk *c, unsigned long rate) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource enable failed, rc=%d\n", rc); + return rc; + } + + if (io->pll_on) + goto error; + + rc = hdmi_8998_pll_set_clk_rate(c, rate); + if (rc) { + pr_err("failed to set clk rate, rc=%d\n", rc); + goto error; + } + + vco->rate = rate; + vco->rate_set = true; + +error: + (void)mdss_pll_resource_enable(io, false); + + return rc; +} + +static long hdmi_8998_vco_round_rate(struct clk *c, unsigned long rate) +{ + unsigned long rrate = rate; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + + if (rate < vco->min_rate) + rrate = vco->min_rate; + if (rate > vco->max_rate) + rrate = vco->max_rate; + + return rrate; +} + +static int hdmi_8998_pll_enable(struct clk *c) +{ + int rc = 0; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + void __iomem *phy = io->phy_base, *pll = io->pll_base; + + _W(phy, PHY_CFG, 0x1); + udelay(100); + _W(phy, PHY_CFG, 0x59); + udelay(100); + + _W(phy, PHY_CLOCK, 0x6); + + /* Ensure all registers are flushed to hardware */ + wmb(); + + rc = hdmi_8998_pll_lock_status(io); + if (rc) { + pr_err("PLL not locked, rc=%d\n", rc); + return rc; + } + + _W(pll, PHY_TX_LANE_CONFIG(0), 0x1F); + _W(pll, PHY_TX_LANE_CONFIG(1), 0x1F); + _W(pll, PHY_TX_LANE_CONFIG(2), 0x1F); + _W(pll, PHY_TX_LANE_CONFIG(3), 0x1F); + + /* Ensure all registers are flushed to hardware */ + wmb(); + + rc = hdmi_8998_phy_ready_status(io); + if (rc) { + pr_err("PHY NOT READY, rc=%d\n", rc); + return rc; + } + + _W(phy, PHY_CFG, 0x58); + udelay(1); + _W(phy, PHY_CFG, 0x59); + + /* Ensure all registers are flushed to hardware */ + wmb(); + + io->pll_on = true; + return rc; +} + +static int hdmi_8998_vco_prepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + int rc = 0; + + if (!io) { + pr_err("hdmi pll resources are not available\n"); + return -EINVAL; + } + + rc = mdss_pll_resource_enable(io, true); + if (rc) { + pr_err("pll resource enable failed, rc=%d\n", rc); + return rc; + } + + if (!vco->rate_set && vco->rate) { + rc = hdmi_8998_pll_set_clk_rate(c, vco->rate); + if (rc) { + pr_err("set rate failed, rc=%d\n", rc); + goto error; + } + } + + rc = hdmi_8998_pll_enable(c); + if (rc) + pr_err("pll enabled failed, rc=%d\n", rc); + +error: + if (rc) + mdss_pll_resource_enable(io, false); + + return rc; +} + +static void hdmi_8998_pll_disable(struct hdmi_pll_vco_clk *vco) +{ + struct mdss_pll_resources *io = vco->priv; + void __iomem *phy = io->phy_base; + + if (!io->pll_on) + return; + + _W(phy, PHY_PD_CTL, 0x0); + + /* Ensure all registers are flushed to hardware */ + wmb(); + + vco->rate_set = false; + io->handoff_resources = false; + io->pll_on = false; +} + +static void hdmi_8998_vco_unprepare(struct clk *c) +{ + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + if (!io) { + pr_err("HDMI pll resources not available\n"); + return; + } + + hdmi_8998_pll_disable(vco); + mdss_pll_resource_enable(io, false); +} + +static enum handoff hdmi_8998_vco_handoff(struct clk *c) +{ + enum handoff ret = HANDOFF_DISABLED_CLK; + struct hdmi_pll_vco_clk *vco = to_hdmi_vco_clk(c); + struct mdss_pll_resources *io = vco->priv; + + if (mdss_pll_resource_enable(io, true)) { + pr_err("pll resource can't be enabled\n"); + return ret; + } + + io->handoff_resources = true; + + if (_R(io->pll_base, C_READY_STATUS) & BIT(0) && + _R(io->phy_base, PHY_STATUS) & BIT(0)) { + io->pll_on = true; + /* TODO: calculate rate based on the phy/pll register values. */ + ret = HANDOFF_ENABLED_CLK; + } else { + io->handoff_resources = false; + mdss_pll_resource_enable(io, false); + pr_debug("%s: PHY/PLL not ready\n", __func__); + } + + pr_debug("done, ret=%d\n", ret); + return ret; +} + +static const struct clk_ops hdmi_8998_vco_clk_ops = { + .set_rate = hdmi_8998_vco_set_rate, + .round_rate = hdmi_8998_vco_round_rate, + .prepare = hdmi_8998_vco_prepare, + .unprepare = hdmi_8998_vco_unprepare, + .handoff = hdmi_8998_vco_handoff, +}; + +static struct hdmi_pll_vco_clk hdmi_vco_clk = { + .min_rate = HDMI_VCO_MIN_RATE_HZ, + .max_rate = HDMI_VCO_MAX_RATE_HZ, + .c = { + .dbg_name = "hdmi_8998_vco_clk", + .ops = &hdmi_8998_vco_clk_ops, + CLK_INIT(hdmi_vco_clk.c), + }, +}; + +static struct clk_lookup hdmipllcc_8998[] = { + CLK_LIST(hdmi_vco_clk), +}; + +int hdmi_8998_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0; + + if (!pdev || !pll_res) { + pr_err("invalid input parameters\n"); + return -EINVAL; + } + + hdmi_vco_clk.priv = pll_res; + + rc = of_msm_clock_register(pdev->dev.of_node, hdmipllcc_8998, + ARRAY_SIZE(hdmipllcc_8998)); + if (rc) { + pr_err("clock register failed, rc=%d\n", rc); + return rc; + } + + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-hdmi-pll.h b/drivers/clk/qcom/mdss/mdss-hdmi-pll.h new file mode 100644 index 0000000000000000000000000000000000000000..5c73ed4714c3ef0596485d96409062058fdf4207 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-hdmi-pll.h @@ -0,0 +1,61 @@ +/* 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 __MDSS_HDMI_PLL_H +#define __MDSS_HDMI_PLL_H + +struct hdmi_pll_cfg { + unsigned long vco_rate; + u32 reg; +}; + +struct hdmi_pll_vco_clk { + unsigned long rate; /* current vco rate */ + unsigned long min_rate; /* min vco rate */ + unsigned long max_rate; /* max vco rate */ + bool rate_set; + struct hdmi_pll_cfg *ip_seti; + struct hdmi_pll_cfg *cp_seti; + struct hdmi_pll_cfg *ip_setp; + struct hdmi_pll_cfg *cp_setp; + struct hdmi_pll_cfg *crctrl; + void *priv; + + struct clk c; +}; + +static inline struct hdmi_pll_vco_clk *to_hdmi_vco_clk(struct clk *clk) +{ + return container_of(clk, struct hdmi_pll_vco_clk, c); +} + +int hdmi_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +int hdmi_20nm_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +int hdmi_8996_v1_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +int hdmi_8996_v2_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +int hdmi_8996_v3_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +int hdmi_8996_v3_1p8_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); + +int hdmi_8998_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); +#endif diff --git a/drivers/clk/qcom/mdss/mdss-pll-util.c b/drivers/clk/qcom/mdss/mdss-pll-util.c new file mode 100644 index 0000000000000000000000000000000000000000..4d797729bb75e08d4dd82a5e7076cf12ea11906a --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-pll-util.c @@ -0,0 +1,437 @@ +/* Copyright (c) 2013-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) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include "mdss-pll.h" + +int mdss_pll_util_resource_init(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0; + struct dss_module_power *mp = &pll_res->mp; + + rc = msm_dss_config_vreg(&pdev->dev, + mp->vreg_config, mp->num_vreg, 1); + if (rc) { + pr_err("Vreg config failed rc=%d\n", rc); + goto vreg_err; + } + + rc = msm_dss_get_clk(&pdev->dev, mp->clk_config, mp->num_clk); + if (rc) { + pr_err("Clock get failed rc=%d\n", rc); + goto clk_err; + } + + return rc; + +clk_err: + msm_dss_config_vreg(&pdev->dev, mp->vreg_config, mp->num_vreg, 0); +vreg_err: + return rc; +} + +/** + * mdss_pll_get_mp_by_reg_name() -- Find power module by regulator name + *@pll_res: Pointer to the PLL resource + *@name: Regulator name as specified in the pll dtsi + * + * This is a helper function to retrieve the regulator information + * for each pll resource. + */ +struct dss_vreg *mdss_pll_get_mp_by_reg_name(struct mdss_pll_resources *pll_res + , char *name) +{ + + struct dss_vreg *regulator = NULL; + int i; + + if ((pll_res == NULL) || (pll_res->mp.vreg_config == NULL)) { + pr_err("%s Invalid PLL resource\n", __func__); + goto error; + } + + regulator = pll_res->mp.vreg_config; + + for (i = 0; i < pll_res->mp.num_vreg; i++) { + if (!strcmp(name, regulator->vreg_name)) { + pr_debug("Found regulator match for %s\n", name); + break; + } + regulator++; + } + +error: + return regulator; +} + +void mdss_pll_util_resource_deinit(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + struct dss_module_power *mp = &pll_res->mp; + + msm_dss_put_clk(mp->clk_config, mp->num_clk); + + msm_dss_config_vreg(&pdev->dev, mp->vreg_config, mp->num_vreg, 0); +} + +void mdss_pll_util_resource_release(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + struct dss_module_power *mp = &pll_res->mp; + + devm_kfree(&pdev->dev, mp->clk_config); + devm_kfree(&pdev->dev, mp->vreg_config); + mp->num_vreg = 0; + mp->num_clk = 0; +} + +int mdss_pll_util_resource_enable(struct mdss_pll_resources *pll_res, + bool enable) +{ + int rc = 0; + struct dss_module_power *mp = &pll_res->mp; + + if (enable) { + rc = msm_dss_enable_vreg(mp->vreg_config, mp->num_vreg, enable); + if (rc) { + pr_err("Failed to enable vregs rc=%d\n", rc); + goto vreg_err; + } + + rc = msm_dss_clk_set_rate(mp->clk_config, mp->num_clk); + if (rc) { + pr_err("Failed to set clock rate rc=%d\n", rc); + goto clk_err; + } + + rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, enable); + if (rc) { + pr_err("clock enable failed rc:%d\n", rc); + goto clk_err; + } + } else { + msm_dss_enable_clk(mp->clk_config, mp->num_clk, enable); + + msm_dss_enable_vreg(mp->vreg_config, mp->num_vreg, enable); + } + + return rc; + +clk_err: + msm_dss_enable_vreg(mp->vreg_config, mp->num_vreg, 0); +vreg_err: + return rc; +} + +static int mdss_pll_util_parse_dt_supply(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int i = 0, rc = 0; + u32 tmp = 0; + struct device_node *of_node = NULL, *supply_root_node = NULL; + struct device_node *supply_node = NULL; + struct dss_module_power *mp = &pll_res->mp; + + of_node = pdev->dev.of_node; + + mp->num_vreg = 0; + supply_root_node = of_get_child_by_name(of_node, + "qcom,platform-supply-entries"); + if (!supply_root_node) { + pr_err("no supply entry present\n"); + return rc; + } + + for_each_child_of_node(supply_root_node, supply_node) { + mp->num_vreg++; + } + + if (mp->num_vreg == 0) { + pr_debug("no vreg\n"); + return rc; + } + pr_debug("vreg found. count=%d\n", mp->num_vreg); + + mp->vreg_config = devm_kzalloc(&pdev->dev, sizeof(struct dss_vreg) * + mp->num_vreg, GFP_KERNEL); + if (!mp->vreg_config) { + rc = -ENOMEM; + return rc; + } + + for_each_child_of_node(supply_root_node, supply_node) { + + const char *st = NULL; + + rc = of_property_read_string(supply_node, + "qcom,supply-name", &st); + if (rc) { + pr_err(":error reading name. rc=%d\n", rc); + goto error; + } + + strlcpy(mp->vreg_config[i].vreg_name, st, + sizeof(mp->vreg_config[i].vreg_name)); + + rc = of_property_read_u32(supply_node, + "qcom,supply-min-voltage", &tmp); + if (rc) { + pr_err(": error reading min volt. rc=%d\n", rc); + goto error; + } + mp->vreg_config[i].min_voltage = tmp; + + rc = of_property_read_u32(supply_node, + "qcom,supply-max-voltage", &tmp); + if (rc) { + pr_err(": error reading max volt. rc=%d\n", rc); + goto error; + } + mp->vreg_config[i].max_voltage = tmp; + + rc = of_property_read_u32(supply_node, + "qcom,supply-enable-load", &tmp); + if (rc) { + pr_err(": error reading enable load. rc=%d\n", rc); + goto error; + } + mp->vreg_config[i].enable_load = tmp; + + rc = of_property_read_u32(supply_node, + "qcom,supply-disable-load", &tmp); + if (rc) { + pr_err(": error reading disable load. rc=%d\n", rc); + goto error; + } + mp->vreg_config[i].disable_load = tmp; + + rc = of_property_read_u32(supply_node, + "qcom,supply-pre-on-sleep", &tmp); + if (rc) + pr_debug("error reading supply pre sleep value. rc=%d\n", + rc); + + mp->vreg_config[i].pre_on_sleep = (!rc ? tmp : 0); + + rc = of_property_read_u32(supply_node, + "qcom,supply-pre-off-sleep", &tmp); + if (rc) + pr_debug("error reading supply pre sleep value. rc=%d\n", + rc); + + mp->vreg_config[i].pre_off_sleep = (!rc ? tmp : 0); + + rc = of_property_read_u32(supply_node, + "qcom,supply-post-on-sleep", &tmp); + if (rc) + pr_debug("error reading supply post sleep value. rc=%d\n", + rc); + + mp->vreg_config[i].post_on_sleep = (!rc ? tmp : 0); + + rc = of_property_read_u32(supply_node, + "qcom,supply-post-off-sleep", &tmp); + if (rc) + pr_debug("error reading supply post sleep value. rc=%d\n", + rc); + + mp->vreg_config[i].post_off_sleep = (!rc ? tmp : 0); + + pr_debug("%s min=%d, max=%d, enable=%d, disable=%d, preonsleep=%d, postonsleep=%d, preoffsleep=%d, postoffsleep=%d\n", + mp->vreg_config[i].vreg_name, + mp->vreg_config[i].min_voltage, + mp->vreg_config[i].max_voltage, + mp->vreg_config[i].enable_load, + mp->vreg_config[i].disable_load, + mp->vreg_config[i].pre_on_sleep, + mp->vreg_config[i].post_on_sleep, + mp->vreg_config[i].pre_off_sleep, + mp->vreg_config[i].post_off_sleep); + ++i; + + rc = 0; + } + + return rc; + +error: + if (mp->vreg_config) { + devm_kfree(&pdev->dev, mp->vreg_config); + mp->vreg_config = NULL; + mp->num_vreg = 0; + } + + return rc; +} + +static int mdss_pll_util_parse_dt_clock(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + u32 i = 0, rc = 0; + struct dss_module_power *mp = &pll_res->mp; + const char *clock_name; + u32 clock_rate; + + mp->num_clk = of_property_count_strings(pdev->dev.of_node, + "clock-names"); + if (mp->num_clk <= 0) { + pr_err("clocks are not defined\n"); + goto clk_err; + } + + mp->clk_config = devm_kzalloc(&pdev->dev, + sizeof(struct dss_clk) * mp->num_clk, GFP_KERNEL); + if (!mp->clk_config) { + rc = -ENOMEM; + mp->num_clk = 0; + goto clk_err; + } + + for (i = 0; i < mp->num_clk; i++) { + of_property_read_string_index(pdev->dev.of_node, "clock-names", + i, &clock_name); + strlcpy(mp->clk_config[i].clk_name, clock_name, + sizeof(mp->clk_config[i].clk_name)); + + of_property_read_u32_index(pdev->dev.of_node, "clock-rate", + i, &clock_rate); + mp->clk_config[i].rate = clock_rate; + + if (!clock_rate) + mp->clk_config[i].type = DSS_CLK_AHB; + else + mp->clk_config[i].type = DSS_CLK_PCLK; + } + +clk_err: + return rc; +} + +static void mdss_pll_free_bootmem(u32 mem_addr, u32 size) +{ + unsigned long pfn_start, pfn_end, pfn_idx; + + pfn_start = mem_addr >> PAGE_SHIFT; + pfn_end = (mem_addr + size) >> PAGE_SHIFT; + for (pfn_idx = pfn_start; pfn_idx < pfn_end; pfn_idx++) + free_reserved_page(pfn_to_page(pfn_idx)); +} + +static int mdss_pll_util_parse_dt_dfps(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0; + struct device_node *pnode; + const u32 *addr; + struct vm_struct *area; + u64 size; + u32 offsets[2]; + unsigned long virt_add; + + pnode = of_parse_phandle(pdev->dev.of_node, "memory-region", 0); + if (IS_ERR_OR_NULL(pnode)) { + rc = PTR_ERR(pnode); + goto pnode_err; + } + + addr = of_get_address(pnode, 0, &size, NULL); + if (!addr) { + pr_err("failed to parse the dfps memory address\n"); + rc = -EINVAL; + goto pnode_err; + } + /* maintain compatibility for 32/64 bit */ + offsets[0] = (u32) of_read_ulong(addr, 2); + offsets[1] = (u32) size; + + area = get_vm_area(offsets[1], VM_IOREMAP); + if (!area) { + rc = -ENOMEM; + goto dfps_mem_err; + } + + virt_add = (unsigned long)area->addr; + rc = ioremap_page_range(virt_add, (virt_add + offsets[1]), + offsets[0], PAGE_KERNEL); + if (rc) { + rc = -ENOMEM; + goto ioremap_err; + } + + pll_res->dfps = kzalloc(sizeof(struct dfps_info), GFP_KERNEL); + if (IS_ERR_OR_NULL(pll_res->dfps)) { + rc = PTR_ERR(pll_res->dfps); + pr_err("couldn't allocate dfps kernel memory\n"); + goto addr_err; + } + + /* memcopy complete dfps structure from kernel virtual memory */ + memcpy_fromio(pll_res->dfps, area->addr, sizeof(struct dfps_info)); + +addr_err: + if (virt_add) + unmap_kernel_range(virt_add, (unsigned long) size); +ioremap_err: + if (area) + vfree(area->addr); +dfps_mem_err: + /* free the dfps memory here */ + memblock_free(offsets[0], offsets[1]); + mdss_pll_free_bootmem(offsets[0], offsets[1]); +pnode_err: + if (pnode) + of_node_put(pnode); + + dma_release_declared_memory(&pdev->dev); + return rc; +} + +int mdss_pll_util_resource_parse(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0; + struct dss_module_power *mp = &pll_res->mp; + + rc = mdss_pll_util_parse_dt_supply(pdev, pll_res); + if (rc) { + pr_err("vreg parsing failed rc=%d\n", rc); + goto end; + } + + rc = mdss_pll_util_parse_dt_clock(pdev, pll_res); + if (rc) { + pr_err("clock name parsing failed rc=%d", rc); + goto clk_err; + } + + if (mdss_pll_util_parse_dt_dfps(pdev, pll_res)) + pr_err("dfps not enabled!\n"); + + return rc; + +clk_err: + devm_kfree(&pdev->dev, mp->vreg_config); + mp->num_vreg = 0; +end: + return rc; +} diff --git a/drivers/clk/qcom/mdss/mdss-pll.c b/drivers/clk/qcom/mdss/mdss-pll.c new file mode 100644 index 0000000000000000000000000000000000000000..e292ef863ff76c4f1b5eb2d5f534f23b83c22df5 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-pll.c @@ -0,0 +1,421 @@ +/* Copyright (c) 2013-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) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include "mdss-pll.h" +#include "mdss-dsi-pll.h" +#include "mdss-dp-pll.h" + +int mdss_pll_resource_enable(struct mdss_pll_resources *pll_res, bool enable) +{ + int rc = 0; + int changed = 0; + + if (!pll_res) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + /* + * Don't turn off resources during handoff or add more than + * 1 refcount. + */ + if (pll_res->handoff_resources && + (!enable || (enable & pll_res->resource_enable))) { + pr_debug("Do not turn on/off pll resources during handoff case\n"); + return rc; + } + + if (enable) { + if (pll_res->resource_ref_cnt == 0) + changed++; + pll_res->resource_ref_cnt++; + } else { + if (pll_res->resource_ref_cnt) { + pll_res->resource_ref_cnt--; + if (pll_res->resource_ref_cnt == 0) + changed++; + } else { + pr_err("PLL Resources already OFF\n"); + } + } + + if (changed) { + rc = mdss_pll_util_resource_enable(pll_res, enable); + if (rc) + pr_err("Resource update failed rc=%d\n", rc); + else + pll_res->resource_enable = enable; + } + + return rc; +} + +static int mdss_pll_resource_init(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + if (!pdev || !pll_res) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + return mdss_pll_util_resource_init(pdev, pll_res); +} + +static void mdss_pll_resource_deinit(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + if (!pdev || !pll_res) { + pr_err("Invalid input parameters\n"); + return; + } + + mdss_pll_util_resource_deinit(pdev, pll_res); +} + +static void mdss_pll_resource_release(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + if (!pdev || !pll_res) { + pr_err("Invalid input parameters\n"); + return; + } + + mdss_pll_util_resource_release(pdev, pll_res); +} + +static int mdss_pll_resource_parse(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc = 0; + const char *compatible_stream; + + if (!pdev || !pll_res) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + rc = mdss_pll_util_resource_parse(pdev, pll_res); + if (rc) { + pr_err("Failed to parse the resources rc=%d\n", rc); + goto end; + } + + compatible_stream = of_get_property(pdev->dev.of_node, + "compatible", NULL); + if (!compatible_stream) { + pr_err("Failed to parse the compatible stream\n"); + goto err; + } + + if (!strcmp(compatible_stream, "qcom,mdss_dsi_pll_10nm")) + pll_res->pll_interface_type = MDSS_DSI_PLL_10NM; + if (!strcmp(compatible_stream, "qcom,mdss_dp_pll_10nm")) + pll_res->pll_interface_type = MDSS_DP_PLL_10NM; + else + goto err; + + return rc; + +err: + mdss_pll_resource_release(pdev, pll_res); +end: + return rc; +} + +static int mdss_pll_clock_register(struct platform_device *pdev, + struct mdss_pll_resources *pll_res) +{ + int rc; + + if (!pdev || !pll_res) { + pr_err("Invalid input parameters\n"); + return -EINVAL; + } + + switch (pll_res->pll_interface_type) { + case MDSS_DSI_PLL_10NM: + rc = dsi_pll_clock_register_10nm(pdev, pll_res); + break; + case MDSS_DP_PLL_10NM: + rc = dp_pll_clock_register_10nm(pdev, pll_res); + break; + case MDSS_UNKNOWN_PLL: + default: + rc = -EINVAL; + break; + } + + if (rc) { + pr_err("Pll ndx=%d clock register failed rc=%d\n", + pll_res->index, rc); + } + + return rc; +} + +static int mdss_pll_probe(struct platform_device *pdev) +{ + int rc = 0; + const char *label; + struct resource *pll_base_reg; + struct resource *phy_base_reg; + struct resource *tx0_base_reg, *tx1_base_reg; + struct resource *dynamic_pll_base_reg; + struct resource *gdsc_base_reg; + struct mdss_pll_resources *pll_res; + + if (!pdev->dev.of_node) { + pr_err("MDSS pll driver only supports device tree probe\n"); + rc = -ENOTSUPP; + goto error; + } + + label = of_get_property(pdev->dev.of_node, "label", NULL); + if (!label) + pr_info("%d: MDSS pll label not specified\n", __LINE__); + else + pr_info("MDSS pll label = %s\n", label); + + pll_res = devm_kzalloc(&pdev->dev, sizeof(struct mdss_pll_resources), + GFP_KERNEL); + if (!pll_res) { + rc = -ENOMEM; + goto error; + } + platform_set_drvdata(pdev, pll_res); + + rc = of_property_read_u32(pdev->dev.of_node, "cell-index", + &pll_res->index); + if (rc) { + pr_err("Unable to get the cell-index rc=%d\n", rc); + pll_res->index = 0; + } + + pll_res->ssc_en = of_property_read_bool(pdev->dev.of_node, + "qcom,dsi-pll-ssc-en"); + + if (pll_res->ssc_en) { + pr_info("%s: label=%s PLL SSC enabled\n", __func__, label); + + rc = of_property_read_u32(pdev->dev.of_node, + "qcom,ssc-frequency-hz", &pll_res->ssc_freq); + + rc = of_property_read_u32(pdev->dev.of_node, + "qcom,ssc-ppm", &pll_res->ssc_ppm); + + pll_res->ssc_center = false; + + label = of_get_property(pdev->dev.of_node, + "qcom,dsi-pll-ssc-mode", NULL); + + if (label && !strcmp(label, "center-spread")) + pll_res->ssc_center = true; + } + + pll_base_reg = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "pll_base"); + if (!pll_base_reg) { + pr_err("Unable to get the pll base resources\n"); + rc = -ENOMEM; + goto io_error; + } + + pll_res->pll_base = ioremap(pll_base_reg->start, + resource_size(pll_base_reg)); + if (!pll_res->pll_base) { + pr_err("Unable to remap pll base resources\n"); + rc = -ENOMEM; + goto io_error; + } + + pr_debug("%s: ndx=%d base=%p\n", __func__, + pll_res->index, pll_res->pll_base); + + rc = mdss_pll_resource_parse(pdev, pll_res); + if (rc) { + pr_err("Pll resource parsing from dt failed rc=%d\n", rc); + goto res_parse_error; + } + + phy_base_reg = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "phy_base"); + if (phy_base_reg) { + pll_res->phy_base = ioremap(phy_base_reg->start, + resource_size(phy_base_reg)); + if (!pll_res->phy_base) { + pr_err("Unable to remap pll phy base resources\n"); + rc = -ENOMEM; + goto phy_io_error; + } + } + + dynamic_pll_base_reg = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "dynamic_pll_base"); + if (dynamic_pll_base_reg) { + pll_res->dyn_pll_base = ioremap(dynamic_pll_base_reg->start, + resource_size(dynamic_pll_base_reg)); + if (!pll_res->dyn_pll_base) { + pr_err("Unable to remap dynamic pll base resources\n"); + rc = -ENOMEM; + goto dyn_pll_io_error; + } + } + + tx0_base_reg = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "ln_tx0_base"); + if (tx0_base_reg) { + pll_res->ln_tx0_base = ioremap(tx0_base_reg->start, + resource_size(tx0_base_reg)); + if (!pll_res->ln_tx0_base) { + pr_err("Unable to remap Lane TX0 base resources\n"); + rc = -ENOMEM; + goto tx0_io_error; + } + } + + tx1_base_reg = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "ln_tx1_base"); + if (tx1_base_reg) { + pll_res->ln_tx1_base = ioremap(tx1_base_reg->start, + resource_size(tx1_base_reg)); + if (!pll_res->ln_tx1_base) { + pr_err("Unable to remap Lane TX1 base resources\n"); + rc = -ENOMEM; + goto tx1_io_error; + } + } + + gdsc_base_reg = platform_get_resource_byname(pdev, + IORESOURCE_MEM, "gdsc_base"); + if (!gdsc_base_reg) { + pr_err("Unable to get the gdsc base resource\n"); + rc = -ENOMEM; + goto gdsc_io_error; + } + pll_res->gdsc_base = ioremap(gdsc_base_reg->start, + resource_size(gdsc_base_reg)); + if (!pll_res->gdsc_base) { + pr_err("Unable to remap gdsc base resources\n"); + rc = -ENOMEM; + goto gdsc_io_error; + } + + rc = mdss_pll_resource_init(pdev, pll_res); + if (rc) { + pr_err("Pll ndx=%d resource init failed rc=%d\n", + pll_res->index, rc); + goto res_init_error; + } + + rc = mdss_pll_clock_register(pdev, pll_res); + if (rc) { + pr_err("Pll ndx=%d clock register failed rc=%d\n", + pll_res->index, rc); + goto clock_register_error; + } + + return rc; + +clock_register_error: + mdss_pll_resource_deinit(pdev, pll_res); +res_init_error: + if (pll_res->gdsc_base) + iounmap(pll_res->gdsc_base); +gdsc_io_error: + if (pll_res->ln_tx1_base) + iounmap(pll_res->ln_tx1_base); +tx1_io_error: + if (pll_res->ln_tx0_base) + iounmap(pll_res->ln_tx0_base); +tx0_io_error: + if (pll_res->dyn_pll_base) + iounmap(pll_res->dyn_pll_base); +dyn_pll_io_error: + if (pll_res->phy_base) + iounmap(pll_res->phy_base); +phy_io_error: + mdss_pll_resource_release(pdev, pll_res); +res_parse_error: + iounmap(pll_res->pll_base); +io_error: + devm_kfree(&pdev->dev, pll_res); +error: + return rc; +} + +static int mdss_pll_remove(struct platform_device *pdev) +{ + struct mdss_pll_resources *pll_res; + + pll_res = platform_get_drvdata(pdev); + if (!pll_res) { + pr_err("Invalid PLL resource data"); + return 0; + } + + mdss_pll_resource_deinit(pdev, pll_res); + if (pll_res->phy_base) + iounmap(pll_res->phy_base); + if (pll_res->gdsc_base) + iounmap(pll_res->gdsc_base); + mdss_pll_resource_release(pdev, pll_res); + iounmap(pll_res->pll_base); + devm_kfree(&pdev->dev, pll_res); + return 0; +} + +static const struct of_device_id mdss_pll_dt_match[] = { + {.compatible = "qcom,mdss_dsi_pll_10nm"}, + {.compatible = "qcom,mdss_dp_pll_10nm"}, + {} +}; + +MODULE_DEVICE_TABLE(of, mdss_clock_dt_match); + +static struct platform_driver mdss_pll_driver = { + .probe = mdss_pll_probe, + .remove = mdss_pll_remove, + .driver = { + .name = "mdss_pll", + .of_match_table = mdss_pll_dt_match, + }, +}; + +static int __init mdss_pll_driver_init(void) +{ + int rc; + + rc = platform_driver_register(&mdss_pll_driver); + if (rc) + pr_err("mdss_register_pll_driver() failed!\n"); + + return rc; +} +fs_initcall(mdss_pll_driver_init); + +static void __exit mdss_pll_driver_deinit(void) +{ + platform_driver_unregister(&mdss_pll_driver); +} +module_exit(mdss_pll_driver_deinit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("mdss pll driver"); diff --git a/drivers/clk/qcom/mdss/mdss-pll.h b/drivers/clk/qcom/mdss/mdss-pll.h new file mode 100644 index 0000000000000000000000000000000000000000..2f92270841ac0bf32828d72dadf011f7b28901f4 --- /dev/null +++ b/drivers/clk/qcom/mdss/mdss-pll.h @@ -0,0 +1,240 @@ +/* Copyright (c) 2013-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 __MDSS_PLL_H +#define __MDSS_PLL_H +#include +#include +#include +#include +#include +#include +#include "../clk-regmap.h" +#include "../clk-regmap-divider.h" +#include "../clk-regmap-mux.h" + + +#define MDSS_PLL_REG_W(base, offset, data) \ + writel_relaxed((data), (base) + (offset)) +#define MDSS_PLL_REG_R(base, offset) readl_relaxed((base) + (offset)) + +#define PLL_CALC_DATA(addr0, addr1, data0, data1) \ + (((data1) << 24) | ((((addr1) / 4) & 0xFF) << 16) | \ + ((data0) << 8) | (((addr0) / 4) & 0xFF)) + +#define MDSS_DYN_PLL_REG_W(base, offset, addr0, addr1, data0, data1) \ + writel_relaxed(PLL_CALC_DATA(addr0, addr1, data0, data1), \ + (base) + (offset)) + +enum { + MDSS_DSI_PLL_10NM, + MDSS_DP_PLL_10NM, + MDSS_UNKNOWN_PLL, +}; + +enum { + MDSS_PLL_TARGET_8996, +}; + +#define DFPS_MAX_NUM_OF_FRAME_RATES 20 + +struct dfps_panel_info { + uint32_t enabled; + uint32_t frame_rate_cnt; + uint32_t frame_rate[DFPS_MAX_NUM_OF_FRAME_RATES]; /* hz */ +}; + +struct dfps_pll_codes { + uint32_t pll_codes_1; + uint32_t pll_codes_2; +}; + +struct dfps_codes_info { + uint32_t is_valid; + uint32_t frame_rate; /* hz */ + uint32_t clk_rate; /* hz */ + struct dfps_pll_codes pll_codes; +}; + +struct dfps_info { + struct dfps_panel_info panel_dfps; + struct dfps_codes_info codes_dfps[DFPS_MAX_NUM_OF_FRAME_RATES]; + void *dfps_fb_base; +}; + +struct mdss_pll_resources { + + /* Pll specific resources like GPIO, power supply, clocks, etc*/ + struct dss_module_power mp; + + /* + * dsi/edp/hmdi plls' base register, phy, gdsc and dynamic refresh + * register mapping + */ + void __iomem *pll_base; + void __iomem *phy_base; + void __iomem *ln_tx0_base; + void __iomem *ln_tx1_base; + void __iomem *gdsc_base; + void __iomem *dyn_pll_base; + + bool is_init_locked; + s64 vco_current_rate; + s64 vco_locking_rate; + s64 vco_ref_clk_rate; + + /* + * Certain pll's needs to update the same vco rate after resume in + * suspend/resume scenario. Cached the vco rate for such plls. + */ + unsigned long vco_cached_rate; + u32 cached_cfg0; + u32 cached_cfg1; + u32 cached_outdiv; + + /* dsi/edp/hmdi pll interface type */ + u32 pll_interface_type; + + /* + * Target ID. Used in pll_register API for valid target check before + * registering the PLL clocks. + */ + u32 target_id; + + /* HW recommended delay during configuration of vco clock rate */ + u32 vco_delay; + + /* Ref-count of the PLL resources */ + u32 resource_ref_cnt; + + /* + * Keep track to resource status to avoid updating same status for the + * pll from different paths + */ + bool resource_enable; + + /* + * Certain plls' do not allow vco rate update if it is on. Keep track of + * status for them to turn on/off after set rate success. + */ + bool pll_on; + + /* + * handoff_status is true of pll is already enabled by bootloader with + * continuous splash enable case. Clock API will call the handoff API + * to enable the status. It is disabled if continuous splash + * feature is disabled. + */ + bool handoff_resources; + + /* + * caching the pll trim codes in the case of dynamic refresh + */ + int cache_pll_trim_codes[2]; + + /* + * for maintaining the status of saving trim codes + */ + bool reg_upd; + + /* + * Notifier callback for MDSS gdsc regulator events + */ + struct notifier_block gdsc_cb; + + /* + * Worker function to call PLL off event + */ + struct work_struct pll_off; + + /* + * PLL index if multiple index are available. Eg. in case of + * DSI we have 2 plls. + */ + uint32_t index; + + bool ssc_en; /* share pll with master */ + bool ssc_center; /* default is down spread */ + u32 ssc_freq; + u32 ssc_ppm; + + struct mdss_pll_resources *slave; + + /* + * target pll revision information + */ + int revision; + + void *priv; + + /* + * dynamic refresh pll codes stored in this structure + */ + struct dfps_info *dfps; + +}; + +struct mdss_pll_vco_calc { + s32 div_frac_start1; + s32 div_frac_start2; + s32 div_frac_start3; + s64 dec_start1; + s64 dec_start2; + s64 pll_plllock_cmp1; + s64 pll_plllock_cmp2; + s64 pll_plllock_cmp3; +}; + +static inline bool is_gdsc_disabled(struct mdss_pll_resources *pll_res) +{ + if (!pll_res->gdsc_base) { + WARN(1, "gdsc_base register is not defined\n"); + return true; + } + return readl_relaxed(pll_res->gdsc_base) & BIT(31) ? false : true; +} + +static inline int mdss_pll_div_prepare(struct clk_hw *hw) +{ + struct clk_hw *parent_hw = clk_hw_get_parent(hw); + /* Restore the divider's value */ + return hw->init->ops->set_rate(hw, clk_hw_get_rate(hw), + clk_hw_get_rate(parent_hw)); +} + +static inline int mdss_set_mux_sel(void *context, unsigned int reg, + unsigned int val) +{ + return 0; +} + +static inline int mdss_get_mux_sel(void *context, unsigned int reg, + unsigned int *val) +{ + *val = 0; + return 0; +} + +int mdss_pll_resource_enable(struct mdss_pll_resources *pll_res, bool enable); +int mdss_pll_util_resource_init(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); +void mdss_pll_util_resource_deinit(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); +void mdss_pll_util_resource_release(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); +int mdss_pll_util_resource_enable(struct mdss_pll_resources *pll_res, + bool enable); +int mdss_pll_util_resource_parse(struct platform_device *pdev, + struct mdss_pll_resources *pll_res); +struct dss_vreg *mdss_pll_get_mp_by_reg_name(struct mdss_pll_resources *pll_res + , char *name); +#endif diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 83cb2a88c204f7c6824396ceb296c7ea0a20577f..9e420497479ce89926308d4232cf00addd0ee06b 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -8,6 +8,7 @@ menuconfig DRM tristate "Direct Rendering Manager (XFree86 4.1.0 and higher DRI support)" depends on (AGP || AGP=n) && !EMULATED_CMPXCHG && HAS_DMA select HDMI + select FB select FB_CMDLINE select I2C select I2C_ALGOBIT @@ -91,7 +92,7 @@ config DRM_FBDEV_EMULATION depends on DRM select DRM_KMS_HELPER select DRM_KMS_FB_HELPER - default y + default n help Choose this option if you have a need for the legacy fbdev support. Note that this support also provides the linux console diff --git a/drivers/gpu/drm/drm_dp_helper.c b/drivers/gpu/drm/drm_dp_helper.c index 08af8d6b844b67e0653f93c5dc012706d9a13b19..abfd4530f98d45f8b33c308c986cd55afe1973f8 100644 --- a/drivers/gpu/drm/drm_dp_helper.c +++ b/drivers/gpu/drm/drm_dp_helper.c @@ -144,6 +144,8 @@ u8 drm_dp_link_rate_to_bw_code(int link_rate) return DP_LINK_BW_2_7; case 540000: return DP_LINK_BW_5_4; + case 810000: + return DP_LINK_BW_8_1; } } EXPORT_SYMBOL(drm_dp_link_rate_to_bw_code); @@ -158,6 +160,8 @@ int drm_dp_bw_code_to_link_rate(u8 link_bw) return 270000; case DP_LINK_BW_5_4: return 540000; + case DP_LINK_BW_8_1: + return 810000; } } EXPORT_SYMBOL(drm_dp_bw_code_to_link_rate); @@ -354,20 +358,13 @@ EXPORT_SYMBOL(drm_dp_link_probe); */ int drm_dp_link_power_up(struct drm_dp_aux *aux, struct drm_dp_link *link) { - u8 value; + u8 value = DP_SET_POWER_D0; int err; /* DP_SET_POWER register is only available on DPCD v1.1 and later */ if (link->revision < 0x11) return 0; - err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value); - if (err < 0) - return err; - - value &= ~DP_SET_POWER_MASK; - value |= DP_SET_POWER_D0; - err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value); if (err < 0) return err; diff --git a/drivers/gpu/drm/drm_edid.c b/drivers/gpu/drm/drm_edid.c index 6bb6337be920c6b440ef33f579832368feed86e5..f0890dee0658387bb8a95bd02a2846a6fad6d484 100644 --- a/drivers/gpu/drm/drm_edid.c +++ b/drivers/gpu/drm/drm_edid.c @@ -96,6 +96,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; @@ -2781,7 +2789,9 @@ add_detailed_modes(struct drm_connector *connector, struct edid *edid, #define VIDEO_BLOCK 0x02 #define VENDOR_BLOCK 0x03 #define SPEAKER_BLOCK 0x04 +#define HDR_STATIC_METADATA_EXTENDED_DATA_BLOCK 0x06 #define USE_EXTENDED_TAG 0x07 +#define VIDEO_CAPABILITY_BLOCK 0x07 #define EXT_VIDEO_CAPABILITY_BLOCK 0x00 #define EXT_VIDEO_DATA_BLOCK_420 0x0E #define EXT_VIDEO_CAP_BLOCK_Y420CMDB 0x0F @@ -3767,6 +3777,72 @@ drm_parse_hdmi_vsdb_audio(struct drm_connector *connector, const u8 *db) connector->audio_latency[1]); } +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) == USE_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, EXT_VIDEO_DATA_BLOCK_420); + /* 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) { @@ -3804,7 +3880,7 @@ void drm_edid_get_monitor_name(struct edid *edid, char *name, int bufsize) { int name_length; char buf[13]; - + if (bufsize <= 0) return; @@ -4635,6 +4711,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/drm_framebuffer.c b/drivers/gpu/drm/drm_framebuffer.c index af279844d7ceb69d44ec9bf260ea63d9435b6de4..64d45fe156e799ada7d79911fc1ad35b1630bdc9 100644 --- a/drivers/gpu/drm/drm_framebuffer.c +++ b/drivers/gpu/drm/drm_framebuffer.c @@ -265,7 +265,8 @@ drm_internal_framebuffer_create(struct drm_device *dev, struct drm_framebuffer *fb; int ret; - if (r->flags & ~(DRM_MODE_FB_INTERLACED | DRM_MODE_FB_MODIFIERS)) { + if (r->flags & ~(DRM_MODE_FB_INTERLACED | DRM_MODE_FB_MODIFIERS | + DRM_MODE_FB_SECURE)) { DRM_DEBUG_KMS("bad framebuffer flags 0x%08x\n", r->flags); return ERR_PTR(-EINVAL); } diff --git a/drivers/gpu/drm/drm_mipi_dsi.c b/drivers/gpu/drm/drm_mipi_dsi.c index 4b47226b90d4bb2731e7bdc19b551693ae4cfe1d..efc3de178974116d1a044b455bec1ee7f42384d2 100644 --- a/drivers/gpu/drm/drm_mipi_dsi.c +++ b/drivers/gpu/drm/drm_mipi_dsi.c @@ -393,6 +393,7 @@ bool mipi_dsi_packet_format_is_short(u8 type) case MIPI_DSI_DCS_SHORT_WRITE_PARAM: case MIPI_DSI_DCS_READ: case MIPI_DSI_SET_MAXIMUM_RETURN_PACKET_SIZE: + case MIPI_DSI_COMPRESSION_MODE: return true; } @@ -424,6 +425,7 @@ bool mipi_dsi_packet_format_is_long(u8 type) case MIPI_DSI_PACKED_PIXEL_STREAM_18: case MIPI_DSI_PIXEL_STREAM_3BYTE_18: case MIPI_DSI_PACKED_PIXEL_STREAM_24: + case MIPI_DSI_PPS: return true; } @@ -454,7 +456,7 @@ int mipi_dsi_create_packet(struct mipi_dsi_packet *packet, return -EINVAL; memset(packet, 0, sizeof(*packet)); - packet->header[0] = ((msg->channel & 0x3) << 6) | (msg->type & 0x3f); + packet->header[2] = ((msg->channel & 0x3) << 6) | (msg->type & 0x3f); /* TODO: compute ECC if hardware support is not available */ @@ -466,16 +468,16 @@ int mipi_dsi_create_packet(struct mipi_dsi_packet *packet, * and 2. */ if (mipi_dsi_packet_format_is_long(msg->type)) { - packet->header[1] = (msg->tx_len >> 0) & 0xff; - packet->header[2] = (msg->tx_len >> 8) & 0xff; + packet->header[0] = (msg->tx_len >> 0) & 0xff; + packet->header[1] = (msg->tx_len >> 8) & 0xff; packet->payload_length = msg->tx_len; packet->payload = msg->tx_buf; } else { const u8 *tx = msg->tx_buf; - packet->header[1] = (msg->tx_len > 0) ? tx[0] : 0; - packet->header[2] = (msg->tx_len > 1) ? tx[1] : 0; + packet->header[0] = (msg->tx_len > 0) ? tx[0] : 0; + packet->header[1] = (msg->tx_len > 1) ? tx[1] : 0; } packet->size = sizeof(packet->header) + packet->payload_length; diff --git a/drivers/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig index 99d39b2aefa675941d42c86b3c9b5a4d2cda937b..c4ad821fef37239d014f873011407078fd373804 100644 --- a/drivers/gpu/drm/msm/Kconfig +++ b/drivers/gpu/drm/msm/Kconfig @@ -14,7 +14,8 @@ config DRM_MSM select QCOM_SCM select SND_SOC_HDMI_CODEC if SND_SOC select SYNC_FILE - select PM_OPP + select HDCP_QSEECOM + select MSM_EXT_DISPLAY default y help DRM/KMS driver for MSM/snapdragon. @@ -31,20 +32,44 @@ config DRM_MSM_REGISTER_LOGGING config DRM_MSM_HDMI_HDCP bool "Enable HDMI HDCP support in MSM DRM driver" depends on DRM_MSM && QCOM_SCM - default y + default n + help + Compile in support for logging register reads/writes in a format + that can be parsed by envytools demsm tool. If enabled, register + logging can be switched on via msm.reglog=y module param. + +config DRM_MSM_HDMI + bool "Enable HDMI support in MSM DRM driver" + depends on DRM_MSM + default n help - Choose this option to enable HDCP state machine + Compile in support for HDMI driver in msm drm + driver. HDMI external display support is enabled + through this config option. It can be primary or + secondary display on device. config DRM_MSM_DSI bool "Enable DSI support in MSM DRM driver" depends on DRM_MSM select DRM_PANEL select DRM_MIPI_DSI - default y + default n help Choose this option if you have a need for MIPI DSI connector support. +config DRM_MSM_DSI_STAGING + bool "Enable new DSI driver support in MSM DRM driver" + depends on DRM_MSM + select DRM_PANEL + select DRM_MIPI_DSI + default y + help + Choose this option if you need MIPI DSI connector support on MSM + which conforms to DRM. MIPI stands for Mobile Industry Processor + Interface and DSI stands for Display Serial Interface which powers + the primary display of your mobile device. + config DRM_MSM_DSI_PLL bool "Enable DSI PLL driver in MSM DRM" depends on DRM_MSM_DSI && COMMON_CLK @@ -75,9 +100,70 @@ config DRM_MSM_DSI_28NM_8960_PHY Choose this option if the 28nm DSI PHY 8960 variant is used on the platform. +config DRM_MSM_MDP5 + tristate "MSM MDP5 DRM driver" + depends on DRM_MSM + default n + help + Choose this option if MSM MDP5 revision support is + needed in DRM/KMS. This is not required if sde/mdp4 + only target enabled. MDP5 supports DSI and HDMI + displays. + +config DRM_MSM_MDP4 + tristate "MSM MDP4 DRM driver" + depends on DRM_MSM + default n + help + Choose this option if MSM MDP4 revision support is needed in DRM/KMS. + MSM MDP4 DRM driver should be disabled for other MDP revisions to + avoid possible conflicts. Only select this option if the target + MSM platform is MDP4 based. + +config DRM_MSM_HDCP + tristate "HDCP for MSM DRM" + depends on DRM_MSM + default n + help + Chose this option if High-bandwidth Digital Content Protection (HDCP) + support is needed in the MSM DRM/KMS driver. Enabling this config + will allow HDCP encrypted (copy protected) media to be displayed. + This should only be enabled if required for platforms supporting HDCP + over the desired interface. + +config DRM_SDE_WB + bool "Enable Writeback support in SDE DRM" + depends on DRM_MSM + default y + help + Choose this option for writeback connector support. + This option enables a virtual writeback connector where + the output image is written back to memory in the format + selected by the connector's mode and property settings. + config DRM_MSM_DSI_14NM_PHY bool "Enable DSI 14nm PHY driver in MSM DRM (used by MSM8996/APQ8096)" depends on DRM_MSM_DSI default y help Choose this option if DSI PHY on 8996 is used on the platform. + +config DRM_SDE_EVTLOG_DEBUG + bool "Enable event logging in MSM DRM" + depends on DRM_MSM + help + The SDE DRM debugging provides support to enable display debugging + features to: dump SDE registers during driver errors, panic + driver during fatal errors and enable some display-driver logging + into an internal buffer (this avoids logging overhead). + +config DRM_SDE_RSC + bool "Enable sde resource state coordinator(rsc) driver" + depends on DRM_MSM + help + The SDE DRM RSC provides display Resource State Coordinator support + to vote the ab/ib bandwidth for primary display. Each rsc client + can vote their active state. Any active request from any client + avoids the display core power collapse. A client can also register + for display core power collapse events on rsc. + diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile index 33008fa1be9b868898a7238859fe56ff22651530..331a223525d308747b7ba8ee4f92c8ed765e19e4 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -1,14 +1,49 @@ -ccflags-y := -Idrivers/gpu/drm/msm +ccflags-y := -Idrivers/gpu/drm/msm -Idrivers/gpu/drm/msm/dsi-staging -Idrivers/gpu/drm/msm/dp +ccflags-y += -Idrivers/gpu/drm/msm/display-manager ccflags-$(CONFIG_DRM_MSM_DSI) += -Idrivers/gpu/drm/msm/dsi +ccflags-$(CONFIG_DRM_MSM_DSI_PLL) += -Idrivers/gpu/drm/msm/dsi +ccflags-y += -Idrivers/gpu/drm/msm/sde +ccflags-y += -Idrivers/media/platform/msm/sde/rotator +ccflags-y += -Idrivers/gpu/drm/msm/hdmi -msm-y := \ - adreno/adreno_device.o \ - adreno/adreno_gpu.o \ - adreno/a3xx_gpu.o \ - adreno/a4xx_gpu.o \ - adreno/a5xx_gpu.o \ - adreno/a5xx_power.o \ - hdmi/hdmi.o \ +msm_drm-y := \ + dp/dp_usbpd.o \ + dp/dp_parser.o \ + dp/dp_power.o \ + dp/dp_catalog.o \ + dp/dp_aux.o \ + dp/dp_panel.o \ + dp/dp_link.o \ + dp/dp_ctrl.o \ + dp/dp_audio.o \ + dp/dp_debug.o \ + dp/dp_display.o \ + dp/dp_drm.o \ + dp/dp_hdcp2p2.o \ + sde/sde_crtc.o \ + sde/sde_encoder.o \ + sde/sde_encoder_phys_vid.o \ + sde/sde_encoder_phys_cmd.o \ + sde/sde_irq.o \ + sde/sde_core_irq.o \ + sde/sde_core_perf.o \ + sde/sde_rm.o \ + sde/sde_kms_utils.o \ + sde/sde_kms.o \ + sde/sde_plane.o \ + sde/sde_connector.o \ + sde/sde_color_processing.o \ + sde/sde_vbif.o \ + sde_dbg.o \ + sde_dbg_evtlog.o \ + sde_io_util.o \ + 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 \ + sde_hdcp_1x.o + +msm_drm-$(CONFIG_DRM_MSM_HDMI) += hdmi/hdmi.o \ hdmi/hdmi_audio.o \ hdmi/hdmi_bridge.o \ hdmi/hdmi_connector.o \ @@ -17,21 +52,16 @@ msm-y := \ hdmi/hdmi_phy_8960.o \ hdmi/hdmi_phy_8x60.o \ hdmi/hdmi_phy_8x74.o \ - edp/edp.o \ + +msm_drm-$(CONFIG_DRM_MSM_EDP) += edp/edp.o \ edp/edp_aux.o \ edp/edp_bridge.o \ edp/edp_connector.o \ edp/edp_ctrl.o \ edp/edp_phy.o \ - mdp/mdp_format.o \ + +msm_drm-$(CONFIG_DRM_MSM_MDP5) += mdp/mdp_format.o \ mdp/mdp_kms.o \ - mdp/mdp4/mdp4_crtc.o \ - mdp/mdp4/mdp4_dtv_encoder.o \ - mdp/mdp4/mdp4_lcdc_encoder.o \ - mdp/mdp4/mdp4_lvds_connector.o \ - mdp/mdp4/mdp4_irq.o \ - mdp/mdp4/mdp4_kms.o \ - mdp/mdp4/mdp4_plane.o \ mdp/mdp5/mdp5_cfg.o \ mdp/mdp5/mdp5_ctl.o \ mdp/mdp5/mdp5_crtc.o \ @@ -43,47 +73,123 @@ msm-y := \ mdp/mdp5/mdp5_mixer.o \ mdp/mdp5/mdp5_plane.o \ mdp/mdp5/mdp5_smp.o \ - msm_atomic.o \ - msm_debugfs.o \ - msm_drv.o \ - msm_fb.o \ - msm_fence.o \ - msm_gem.o \ - msm_gem_prime.o \ - msm_gem_shrinker.o \ - msm_gem_submit.o \ - msm_gem_vma.o \ - msm_gpu.o \ - msm_iommu.o \ - msm_perf.o \ - msm_rd.o \ - msm_ringbuffer.o -msm-$(CONFIG_DRM_FBDEV_EMULATION) += msm_fbdev.o -msm-$(CONFIG_COMMON_CLK) += mdp/mdp4/mdp4_lvds_pll.o -msm-$(CONFIG_COMMON_CLK) += hdmi/hdmi_pll_8960.o -msm-$(CONFIG_COMMON_CLK) += hdmi/hdmi_phy_8996.o +msm_drm-$(CONFIG_DRM_SDE_RSC) += sde_rsc.o \ + sde_rsc_hw.o \ + +# use drm gpu driver only if qcom_kgsl driver not available +ifneq ($(CONFIG_QCOM_KGSL),y) +msm_drm-y += adreno/adreno_device.o \ + adreno/adreno_gpu.o \ + adreno/a3xx_gpu.o \ + adreno/a4xx_gpu.o \ + adreno/a5xx_gpu.o \ + adreno/a5xx_power.o +endif + +msm_drm-$(CONFIG_DRM_MSM_MDP4) += mdp/mdp4/mdp4_crtc.o \ + mdp/mdp4/mdp4_dtv_encoder.o \ + mdp/mdp4/mdp4_lcdc_encoder.o \ + mdp/mdp4/mdp4_lvds_connector.o \ + mdp/mdp4/mdp4_irq.o \ + mdp/mdp4/mdp4_kms.o \ + mdp/mdp4/mdp4_dsi_encoder.o \ + mdp/mdp4/mdp4_plane.o + +msm_drm-$(CONFIG_DRM_FBDEV_EMULATION) += msm_fbdev.o +msm_drm-$(CONFIG_SYNC_FILE) += sde/sde_fence.o +msm_drm-$(CONFIG_DRM_MSM_MDP4) += mdp/mdp4/mdp4_lvds_pll.o +msm_drm-$(CONFIG_DRM_MSM_HDMI) += hdmi/hdmi_pll_8960.o +msm_drm-$(CONFIG_DRM_MSM_HDMI) += hdmi/hdmi_phy_8996.o -msm-$(CONFIG_DRM_MSM_HDMI_HDCP) += hdmi/hdmi_hdcp.o +msm_drm-$(CONFIG_DRM_MSM_HDMI_HDCP) += hdmi/hdmi_hdcp.o -msm-$(CONFIG_DRM_MSM_DSI) += dsi/dsi.o \ +msm_drm-$(CONFIG_DRM_MSM_DSI) += dsi/dsi.o \ mdp/mdp4/mdp4_dsi_encoder.o \ dsi/dsi_cfg.o \ dsi/dsi_host.o \ dsi/dsi_manager.o \ dsi/phy/dsi_phy.o \ + dsi/dsi_manager.o \ mdp/mdp5/mdp5_cmd_encoder.o -msm-$(CONFIG_DRM_MSM_DSI_28NM_PHY) += dsi/phy/dsi_phy_28nm.o -msm-$(CONFIG_DRM_MSM_DSI_20NM_PHY) += dsi/phy/dsi_phy_20nm.o -msm-$(CONFIG_DRM_MSM_DSI_28NM_8960_PHY) += dsi/phy/dsi_phy_28nm_8960.o -msm-$(CONFIG_DRM_MSM_DSI_14NM_PHY) += dsi/phy/dsi_phy_14nm.o +msm_drm-$(CONFIG_DRM_MSM_DSI_28NM_PHY) += dsi/phy/dsi_phy_28nm.o +msm_drm-$(CONFIG_DRM_MSM_DSI_20NM_PHY) += dsi/phy/dsi_phy_20nm.o +msm_drm-$(CONFIG_DRM_MSM_DSI_28NM_8960_PHY) += dsi/phy/dsi_phy_28nm_8960.o +msm_drm-$(CONFIG_DRM_MSM_DSI_14NM_PHY) += dsi/phy/dsi_phy_14nm.o ifeq ($(CONFIG_DRM_MSM_DSI_PLL),y) -msm-y += dsi/pll/dsi_pll.o -msm-$(CONFIG_DRM_MSM_DSI_28NM_PHY) += dsi/pll/dsi_pll_28nm.o -msm-$(CONFIG_DRM_MSM_DSI_28NM_8960_PHY) += dsi/pll/dsi_pll_28nm_8960.o -msm-$(CONFIG_DRM_MSM_DSI_14NM_PHY) += dsi/pll/dsi_pll_14nm.o +msm_drm-y += dsi/pll/dsi_pll.o +msm_drm-$(CONFIG_DRM_MSM_DSI_28NM_PHY) += dsi/pll/dsi_pll_28nm.o +msm_drm-$(CONFIG_DRM_MSM_DSI_28NM_8960_PHY) += dsi/pll/dsi_pll_28nm_8960.o +msm_drm-$(CONFIG_DRM_MSM_DSI_14NM_PHY) += dsi/pll/dsi_pll_14nm.o endif +msm_drm-$(CONFIG_DRM_MSM_DSI_STAGING) += dsi-staging/dsi_phy.o \ + dsi-staging/dsi_pwr.o \ + dsi-staging/dsi_phy.o \ + dsi-staging/dsi_phy_hw_v2_0.o \ + dsi-staging/dsi_phy_hw_v3_0.o \ + dsi-staging/dsi_phy_timing_calc.o \ + dsi-staging/dsi_phy_timing_v2_0.o \ + dsi-staging/dsi_phy_timing_v3_0.o \ + dsi-staging/dsi_ctrl_hw_cmn.o \ + dsi-staging/dsi_ctrl_hw_1_4.o \ + dsi-staging/dsi_ctrl_hw_2_0.o \ + dsi-staging/dsi_ctrl_hw_2_2.o \ + dsi-staging/dsi_ctrl.o \ + dsi-staging/dsi_catalog.o \ + dsi-staging/dsi_drm.o \ + dsi-staging/dsi_display.o \ + dsi-staging/dsi_panel.o \ + dsi-staging/dsi_clk_manager.o \ + dsi-staging/dsi_display_test.o + +msm_drm-$(CONFIG_DRM_MSM_DSI_PLL) += dsi/pll/dsi_pll.o \ + dsi/pll/dsi_pll_28nm.o + +msm_drm-$(CONFIG_DRM_MSM) += \ + sde/sde_hw_catalog.o \ + sde/sde_hw_cdm.o \ + sde/sde_hw_dspp.o \ + sde/sde_hw_intf.o \ + sde/sde_hw_lm.o \ + sde/sde_hw_ctl.o \ + sde/sde_hw_util.o \ + sde/sde_hw_sspp.o \ + sde/sde_hw_wb.o \ + sde/sde_hw_rot.o \ + sde/sde_hw_pingpong.o \ + sde/sde_hw_top.o \ + sde/sde_hw_interrupts.o \ + sde/sde_hw_vbif.o \ + sde/sde_hw_blk.o \ + sde/sde_formats.o \ + sde_power_handle.o \ + sde/sde_hw_color_processing_v1_7.o \ + sde/sde_reg_dma.o \ + sde/sde_hw_reg_dma_v1.o \ + sde/sde_hw_dsc.o \ + sde/sde_hw_ds.o + +msm_drm-$(CONFIG_DRM_SDE_WB) += sde/sde_wb.o \ + sde/sde_encoder_phys_wb.o + +msm_drm-$(CONFIG_DRM_MSM) += \ + msm_atomic.o \ + msm_drv.o \ + msm_fb.o \ + msm_gem.o \ + msm_gem_prime.o \ + msm_gem_submit.o \ + msm_gem_shrinker.o \ + msm_gpu.o \ + msm_iommu.o \ + msm_smmu.o \ + msm_perf.o \ + msm_rd.o \ + msm_ringbuffer.o \ + msm_prop.o \ + msm_fence.o \ + msm_debugfs.o -obj-$(CONFIG_DRM_MSM) += msm.o +obj-$(CONFIG_DRM_MSM) += msm_drm.o diff --git a/drivers/gpu/drm/msm/dp/dp_audio.c b/drivers/gpu/drm/msm/dp/dp_audio.c new file mode 100644 index 0000000000000000000000000000000000000000..c4a60dc87e9d678758dd0d3e705c9546aa44aadb --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_audio.c @@ -0,0 +1,796 @@ +/* + * 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. + * + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include + +#include + +#include "dp_catalog.h" +#include "dp_audio.h" +#include "dp_panel.h" + +#define HEADER_BYTE_2_BIT 0 +#define PARITY_BYTE_2_BIT 8 +#define HEADER_BYTE_1_BIT 16 +#define PARITY_BYTE_1_BIT 24 +#define HEADER_BYTE_3_BIT 16 +#define PARITY_BYTE_3_BIT 24 + +struct dp_audio_private { + struct platform_device *ext_pdev; + struct platform_device *pdev; + struct dp_catalog_audio *catalog; + struct msm_ext_disp_init_data ext_audio_data; + struct dp_panel *panel; + + bool ack_enabled; + bool session_on; + bool engine_on; + + u32 channels; + + struct completion hpd_comp; + + struct dp_audio dp_audio; +}; + +static u8 dp_audio_get_g0_value(u8 data) +{ + u8 c[4]; + u8 g[4]; + u8 ret_data = 0; + u8 i; + + for (i = 0; i < 4; i++) + c[i] = (data >> i) & 0x01; + + g[0] = c[3]; + g[1] = c[0] ^ c[3]; + g[2] = c[1]; + g[3] = c[2]; + + for (i = 0; i < 4; i++) + ret_data = ((g[i] & 0x01) << i) | ret_data; + + return ret_data; +} + +static u8 dp_audio_get_g1_value(u8 data) +{ + u8 c[4]; + u8 g[4]; + u8 ret_data = 0; + u8 i; + + for (i = 0; i < 4; i++) + c[i] = (data >> i) & 0x01; + + g[0] = c[0] ^ c[3]; + g[1] = c[0] ^ c[1] ^ c[3]; + g[2] = c[1] ^ c[2]; + g[3] = c[2] ^ c[3]; + + for (i = 0; i < 4; i++) + ret_data = ((g[i] & 0x01) << i) | ret_data; + + return ret_data; +} + +static u8 dp_audio_calculate_parity(u32 data) +{ + u8 x0 = 0; + u8 x1 = 0; + u8 ci = 0; + u8 iData = 0; + u8 i = 0; + u8 parity_byte; + u8 num_byte = (data & 0xFF00) > 0 ? 8 : 2; + + for (i = 0; i < num_byte; i++) { + iData = (data >> i*4) & 0xF; + + ci = iData ^ x1; + x1 = x0 ^ dp_audio_get_g1_value(ci); + x0 = dp_audio_get_g0_value(ci); + } + + parity_byte = x1 | (x0 << 4); + + return parity_byte; +} + +static u32 dp_audio_get_header(struct dp_catalog_audio *catalog, + enum dp_catalog_audio_sdp_type sdp, + enum dp_catalog_audio_header_type header) +{ + catalog->sdp_type = sdp; + catalog->sdp_header = header; + catalog->get_header(catalog); + + return catalog->data; +} + +static void dp_audio_set_header(struct dp_catalog_audio *catalog, + u32 data, + enum dp_catalog_audio_sdp_type sdp, + enum dp_catalog_audio_header_type header) +{ + catalog->sdp_type = sdp; + catalog->sdp_header = header; + catalog->data = data; + catalog->set_header(catalog); +} + +static void dp_audio_stream_sdp(struct dp_audio_private *audio) +{ + struct dp_catalog_audio *catalog = audio->catalog; + u32 value, new_value; + u8 parity_byte; + + /* Config header and parity byte 1 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_STREAM, DP_AUDIO_SDP_HEADER_1); + + new_value = 0x02; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_1_BIT) + | (parity_byte << PARITY_BYTE_1_BIT)); + pr_debug("Header Byte 1: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_STREAM, DP_AUDIO_SDP_HEADER_1); + + /* Config header and parity byte 2 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_STREAM, DP_AUDIO_SDP_HEADER_2); + new_value = value; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_2_BIT) + | (parity_byte << PARITY_BYTE_2_BIT)); + pr_debug("Header Byte 2: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_STREAM, DP_AUDIO_SDP_HEADER_2); + + /* Config header and parity byte 3 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_STREAM, DP_AUDIO_SDP_HEADER_3); + + new_value = audio->channels - 1; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_3_BIT) + | (parity_byte << PARITY_BYTE_3_BIT)); + pr_debug("Header Byte 3: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_STREAM, DP_AUDIO_SDP_HEADER_3); +} + +static void dp_audio_timestamp_sdp(struct dp_audio_private *audio) +{ + struct dp_catalog_audio *catalog = audio->catalog; + u32 value, new_value; + u8 parity_byte; + + /* Config header and parity byte 1 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_TIMESTAMP, DP_AUDIO_SDP_HEADER_1); + + new_value = 0x1; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_1_BIT) + | (parity_byte << PARITY_BYTE_1_BIT)); + pr_debug("Header Byte 1: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_TIMESTAMP, DP_AUDIO_SDP_HEADER_1); + + /* Config header and parity byte 2 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_TIMESTAMP, DP_AUDIO_SDP_HEADER_2); + + new_value = 0x17; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_2_BIT) + | (parity_byte << PARITY_BYTE_2_BIT)); + pr_debug("Header Byte 2: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_TIMESTAMP, DP_AUDIO_SDP_HEADER_2); + + /* Config header and parity byte 3 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_TIMESTAMP, DP_AUDIO_SDP_HEADER_3); + + new_value = (0x0 | (0x11 << 2)); + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_3_BIT) + | (parity_byte << PARITY_BYTE_3_BIT)); + pr_debug("Header Byte 3: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_TIMESTAMP, DP_AUDIO_SDP_HEADER_3); +} + +static void dp_audio_infoframe_sdp(struct dp_audio_private *audio) +{ + struct dp_catalog_audio *catalog = audio->catalog; + u32 value, new_value; + u8 parity_byte; + + /* Config header and parity byte 1 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_INFOFRAME, DP_AUDIO_SDP_HEADER_1); + + new_value = 0x84; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_1_BIT) + | (parity_byte << PARITY_BYTE_1_BIT)); + pr_debug("Header Byte 1: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_INFOFRAME, DP_AUDIO_SDP_HEADER_1); + + /* Config header and parity byte 2 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_INFOFRAME, DP_AUDIO_SDP_HEADER_2); + + new_value = 0x1b; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_2_BIT) + | (parity_byte << PARITY_BYTE_2_BIT)); + pr_debug("Header Byte 2: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_INFOFRAME, DP_AUDIO_SDP_HEADER_2); + + /* Config header and parity byte 3 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_INFOFRAME, DP_AUDIO_SDP_HEADER_3); + + new_value = (0x0 | (0x11 << 2)); + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_3_BIT) + | (parity_byte << PARITY_BYTE_3_BIT)); + pr_debug("Header Byte 3: value = 0x%x, parity_byte = 0x%x\n", + new_value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_INFOFRAME, DP_AUDIO_SDP_HEADER_3); +} + +static void dp_audio_copy_management_sdp(struct dp_audio_private *audio) +{ + struct dp_catalog_audio *catalog = audio->catalog; + u32 value, new_value; + u8 parity_byte; + + /* Config header and parity byte 1 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_COPYMANAGEMENT, DP_AUDIO_SDP_HEADER_1); + + new_value = 0x05; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_1_BIT) + | (parity_byte << PARITY_BYTE_1_BIT)); + pr_debug("Header Byte 1: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_COPYMANAGEMENT, DP_AUDIO_SDP_HEADER_1); + + /* Config header and parity byte 2 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_COPYMANAGEMENT, DP_AUDIO_SDP_HEADER_2); + + new_value = 0x0F; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_2_BIT) + | (parity_byte << PARITY_BYTE_2_BIT)); + pr_debug("Header Byte 2: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_COPYMANAGEMENT, DP_AUDIO_SDP_HEADER_2); + + /* Config header and parity byte 3 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_COPYMANAGEMENT, DP_AUDIO_SDP_HEADER_3); + + new_value = 0x0; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_3_BIT) + | (parity_byte << PARITY_BYTE_3_BIT)); + pr_debug("Header Byte 3: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_COPYMANAGEMENT, DP_AUDIO_SDP_HEADER_3); +} + +static void dp_audio_isrc_sdp(struct dp_audio_private *audio) +{ + struct dp_catalog_audio *catalog = audio->catalog; + u32 value, new_value; + u8 parity_byte; + + /* Config header and parity byte 1 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_ISRC, DP_AUDIO_SDP_HEADER_1); + + new_value = 0x06; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_1_BIT) + | (parity_byte << PARITY_BYTE_1_BIT)); + pr_debug("Header Byte 1: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_ISRC, DP_AUDIO_SDP_HEADER_1); + + /* Config header and parity byte 2 */ + value = dp_audio_get_header(catalog, + DP_AUDIO_SDP_ISRC, DP_AUDIO_SDP_HEADER_2); + + new_value = 0x0F; + parity_byte = dp_audio_calculate_parity(new_value); + value |= ((new_value << HEADER_BYTE_2_BIT) + | (parity_byte << PARITY_BYTE_2_BIT)); + pr_debug("Header Byte 2: value = 0x%x, parity_byte = 0x%x\n", + value, parity_byte); + dp_audio_set_header(catalog, value, + DP_AUDIO_SDP_ISRC, DP_AUDIO_SDP_HEADER_2); +} + +static void dp_audio_setup_sdp(struct dp_audio_private *audio) +{ + audio->catalog->config_sdp(audio->catalog); + + dp_audio_stream_sdp(audio); + dp_audio_timestamp_sdp(audio); + dp_audio_infoframe_sdp(audio); + dp_audio_copy_management_sdp(audio); + dp_audio_isrc_sdp(audio); +} + +static void dp_audio_setup_acr(struct dp_audio_private *audio) +{ + u32 select = 0; + struct dp_catalog_audio *catalog = audio->catalog; + + switch (audio->dp_audio.bw_code) { + case DP_LINK_BW_1_62: + select = 0; + break; + case DP_LINK_BW_2_7: + select = 1; + break; + case DP_LINK_BW_5_4: + select = 2; + break; + case DP_LINK_BW_8_1: + select = 3; + break; + default: + pr_debug("Unknown link rate\n"); + select = 0; + break; + } + + catalog->data = select; + catalog->config_acr(catalog); +} + +static void dp_audio_safe_to_exit_level(struct dp_audio_private *audio) +{ + struct dp_catalog_audio *catalog = audio->catalog; + u32 safe_to_exit_level = 0; + + switch (audio->dp_audio.lane_count) { + case 1: + safe_to_exit_level = 14; + break; + case 2: + safe_to_exit_level = 8; + break; + case 4: + safe_to_exit_level = 5; + break; + default: + pr_debug("setting the default safe_to_exit_level = %u\n", + safe_to_exit_level); + safe_to_exit_level = 14; + break; + } + + catalog->data = safe_to_exit_level; + catalog->safe_to_exit_level(catalog); +} + +static void dp_audio_enable(struct dp_audio_private *audio, bool enable) +{ + struct dp_catalog_audio *catalog = audio->catalog; + + catalog->data = enable; + catalog->enable(catalog); + + audio->engine_on = enable; +} + +static struct dp_audio_private *get_audio_get_data(struct platform_device *pdev) +{ + struct msm_ext_disp_data *ext_data; + struct dp_audio *dp_audio; + + if (!pdev) { + pr_err("invalid input\n"); + return ERR_PTR(-ENODEV); + } + + ext_data = platform_get_drvdata(pdev); + if (!ext_data) { + pr_err("invalid ext disp data\n"); + return ERR_PTR(-EINVAL); + } + + dp_audio = ext_data->intf_data; + if (!ext_data) { + pr_err("invalid intf data\n"); + return ERR_PTR(-EINVAL); + } + + return container_of(dp_audio, struct dp_audio_private, dp_audio); +} + +static int dp_audio_info_setup(struct platform_device *pdev, + struct msm_ext_disp_audio_setup_params *params) +{ + int rc = 0; + struct dp_audio_private *audio; + + audio = get_audio_get_data(pdev); + if (IS_ERR(audio)) { + rc = PTR_ERR(audio); + goto end; + } + + audio->channels = params->num_of_channels; + + dp_audio_setup_sdp(audio); + dp_audio_setup_acr(audio); + dp_audio_safe_to_exit_level(audio); + dp_audio_enable(audio, true); +end: + return rc; +} + +static int dp_audio_get_edid_blk(struct platform_device *pdev, + struct msm_ext_disp_audio_edid_blk *blk) +{ + int rc = 0; + struct dp_audio_private *audio; + struct sde_edid_ctrl *edid; + + audio = get_audio_get_data(pdev); + if (IS_ERR(audio)) { + rc = PTR_ERR(audio); + goto end; + } + + if (!audio->panel || !audio->panel->edid_ctrl) { + pr_err("invalid panel data\n"); + rc = -EINVAL; + goto end; + } + + edid = audio->panel->edid_ctrl; + + blk->audio_data_blk = edid->audio_data_block; + blk->audio_data_blk_size = edid->adb_size; + + blk->spk_alloc_data_blk = edid->spkr_alloc_data_block; + blk->spk_alloc_data_blk_size = edid->sadb_size; +end: + return rc; +} + +static int dp_audio_get_cable_status(struct platform_device *pdev, u32 vote) +{ + int rc = 0; + struct dp_audio_private *audio; + + audio = get_audio_get_data(pdev); + if (IS_ERR(audio)) { + rc = PTR_ERR(audio); + goto end; + } + + if (!audio->panel) { + pr_err("invalid panel data\n"); + rc = -EINVAL; + goto end; + } + + return audio->session_on; +end: + return rc; +} + +static int dp_audio_get_intf_id(struct platform_device *pdev) +{ + int rc = 0; + struct dp_audio_private *audio; + + audio = get_audio_get_data(pdev); + if (IS_ERR(audio)) { + rc = PTR_ERR(audio); + goto end; + } + + return EXT_DISPLAY_TYPE_DP; +end: + return rc; +} + +static void dp_audio_teardown_done(struct platform_device *pdev) +{ + struct dp_audio_private *audio; + + audio = get_audio_get_data(pdev); + if (IS_ERR(audio)) + return; + + if (!audio->panel) { + pr_err("invalid panel data\n"); + return; + } + + dp_audio_enable(audio, false); + + complete_all(&audio->hpd_comp); + + pr_debug("audio engine disabled\n"); +} + +static int dp_audio_ack_done(struct platform_device *pdev, u32 ack) +{ + int rc = 0, ack_hpd; + struct dp_audio_private *audio; + + audio = get_audio_get_data(pdev); + if (IS_ERR(audio)) { + rc = PTR_ERR(audio); + goto end; + } + + if (ack & AUDIO_ACK_SET_ENABLE) { + audio->ack_enabled = ack & AUDIO_ACK_ENABLE ? + true : false; + + pr_debug("audio ack feature %s\n", + audio->ack_enabled ? "enabled" : "disabled"); + goto end; + } + + if (!audio->ack_enabled) + goto end; + + ack_hpd = ack & AUDIO_ACK_CONNECT; + + pr_debug("acknowledging audio (%d)\n", ack_hpd); + + if (!audio->engine_on) + complete_all(&audio->hpd_comp); +end: + return rc; +} + +static int dp_audio_init_ext_disp(struct dp_audio_private *audio) +{ + int rc = 0; + struct device_node *pd = NULL; + const char *phandle = "qcom,ext-disp"; + struct msm_ext_disp_init_data *ext; + struct msm_ext_disp_audio_codec_ops *ops; + + ext = &audio->ext_audio_data; + ops = &ext->codec_ops; + + ext->type = EXT_DISPLAY_TYPE_DP; + ext->pdev = audio->pdev; + ext->intf_data = &audio->dp_audio; + + ops->audio_info_setup = dp_audio_info_setup; + ops->get_audio_edid_blk = dp_audio_get_edid_blk; + ops->cable_status = dp_audio_get_cable_status; + ops->get_intf_id = dp_audio_get_intf_id; + ops->teardown_done = dp_audio_teardown_done; + ops->acknowledge = dp_audio_ack_done; + + if (!audio->pdev->dev.of_node) { + pr_err("cannot find audio dev.of_node\n"); + rc = -ENODEV; + goto end; + } + + pd = of_parse_phandle(audio->pdev->dev.of_node, phandle, 0); + if (!pd) { + pr_err("cannot parse %s handle\n", phandle); + rc = -ENODEV; + goto end; + } + + audio->ext_pdev = of_find_device_by_node(pd); + if (!audio->ext_pdev) { + pr_err("cannot find %s pdev\n", phandle); + rc = -ENODEV; + goto end; + } + + rc = msm_ext_disp_register_intf(audio->ext_pdev, ext); + if (rc) + pr_err("failed to register disp\n"); +end: + if (pd) + of_node_put(pd); + + return rc; +} + +static int dp_audio_on(struct dp_audio *dp_audio) +{ + int rc = 0; + struct dp_audio_private *audio; + struct msm_ext_disp_init_data *ext; + + if (!dp_audio) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + audio = container_of(dp_audio, struct dp_audio_private, dp_audio); + + ext = &audio->ext_audio_data; + + audio->session_on = true; + + rc = ext->intf_ops.audio_config(audio->ext_pdev, + EXT_DISPLAY_TYPE_DP, + EXT_DISPLAY_CABLE_CONNECT); + if (rc) { + pr_err("failed to config audio, err=%d\n", rc); + goto end; + } + + rc = ext->intf_ops.audio_notify(audio->ext_pdev, + EXT_DISPLAY_TYPE_DP, + EXT_DISPLAY_CABLE_CONNECT); + if (rc) { + pr_err("failed to notify audio, err=%d\n", rc); + goto end; + } + + reinit_completion(&audio->hpd_comp); + rc = wait_for_completion_timeout(&audio->hpd_comp, HZ * 5); + if (!rc) { + pr_err("timeout\n"); + rc = -ETIMEDOUT; + goto end; + } + + pr_debug("success\n"); +end: + return rc; +} + +static int dp_audio_off(struct dp_audio *dp_audio) +{ + int rc = 0; + struct dp_audio_private *audio; + struct msm_ext_disp_init_data *ext; + + if (!dp_audio) { + pr_err("invalid input\n"); + return -EINVAL; + } + + audio = container_of(dp_audio, struct dp_audio_private, dp_audio); + ext = &audio->ext_audio_data; + + rc = ext->intf_ops.audio_notify(audio->ext_pdev, + EXT_DISPLAY_TYPE_DP, + EXT_DISPLAY_CABLE_DISCONNECT); + if (rc) { + pr_err("failed to notify audio, err=%d\n", rc); + goto end; + } + + reinit_completion(&audio->hpd_comp); + rc = wait_for_completion_timeout(&audio->hpd_comp, HZ * 5); + if (!rc) { + pr_err("timeout\n"); + rc = -ETIMEDOUT; + goto end; + } + + pr_debug("success\n"); +end: + rc = ext->intf_ops.audio_config(audio->ext_pdev, + EXT_DISPLAY_TYPE_DP, + EXT_DISPLAY_CABLE_DISCONNECT); + if (rc) + pr_err("failed to config audio, err=%d\n", rc); + + audio->session_on = false; + audio->engine_on = false; + + return rc; +} + +struct dp_audio *dp_audio_get(struct platform_device *pdev, + struct dp_panel *panel, + struct dp_catalog_audio *catalog) +{ + int rc = 0; + struct dp_audio_private *audio; + struct dp_audio *dp_audio; + + if (!pdev || !panel || !catalog) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + audio = devm_kzalloc(&pdev->dev, sizeof(*audio), GFP_KERNEL); + if (!audio) { + rc = -ENOMEM; + goto error; + } + + init_completion(&audio->hpd_comp); + + audio->pdev = pdev; + audio->panel = panel; + audio->catalog = catalog; + + dp_audio = &audio->dp_audio; + + dp_audio->on = dp_audio_on; + dp_audio->off = dp_audio_off; + + rc = dp_audio_init_ext_disp(audio); + if (rc) { + devm_kfree(&pdev->dev, audio); + goto error; + } + + catalog->init(catalog); + + return dp_audio; +error: + return ERR_PTR(rc); +} + +void dp_audio_put(struct dp_audio *dp_audio) +{ + struct dp_audio_private *audio; + + if (!dp_audio) + return; + + audio = container_of(dp_audio, struct dp_audio_private, dp_audio); + + devm_kfree(&audio->pdev->dev, audio); +} diff --git a/drivers/gpu/drm/msm/dp/dp_audio.h b/drivers/gpu/drm/msm/dp/dp_audio.h new file mode 100644 index 0000000000000000000000000000000000000000..d6e6b74b7a6336688afd3a8b10e46787862faaeb --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_audio.h @@ -0,0 +1,81 @@ +/* + * 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 _DP_AUDIO_H_ +#define _DP_AUDIO_H_ + +#include + +#include "dp_panel.h" +#include "dp_catalog.h" + +/** + * struct dp_audio + * @lane_count: number of lanes configured in current session + * @bw_code: link rate's bandwidth code for current session + */ +struct dp_audio { + u32 lane_count; + u32 bw_code; + + /** + * on() + * + * Enables the audio by notifying the user module. + * + * @dp_audio: an instance of struct dp_audio. + * + * Returns the error code in case of failure, 0 in success case. + */ + int (*on)(struct dp_audio *dp_audio); + + /** + * off() + * + * Disables the audio by notifying the user module. + * + * @dp_audio: an instance of struct dp_audio. + * + * Returns the error code in case of failure, 0 in success case. + */ + int (*off)(struct dp_audio *dp_audio); +}; + +/** + * dp_audio_get() + * + * Creates and instance of dp audio. + * + * @pdev: caller's platform device instance. + * @panel: an instance of dp_panel module. + * @catalog: an instance of dp_catalog_audio module. + * + * Returns the error code in case of failure, otherwize + * an instance of newly created dp_module. + */ +struct dp_audio *dp_audio_get(struct platform_device *pdev, + struct dp_panel *panel, + struct dp_catalog_audio *catalog); + +/** + * dp_audio_put() + * + * Cleans the dp_audio instance. + * + * @dp_audio: an instance of dp_audio. + */ +void dp_audio_put(struct dp_audio *dp_audio); +#endif /* _DP_AUDIO_H_ */ + + diff --git a/drivers/gpu/drm/msm/dp/dp_aux.c b/drivers/gpu/drm/msm/dp/dp_aux.c new file mode 100644 index 0000000000000000000000000000000000000000..7426fc447805c5ab9e4860ee4e88350e092b0c74 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_aux.c @@ -0,0 +1,570 @@ +/* + * 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 + +#include "dp_aux.h" + +#define DP_AUX_ENUM_STR(x) #x + +enum { + DP_AUX_DATA_INDEX_WRITE = BIT(31), +}; + +struct dp_aux_private { + struct device *dev; + struct dp_aux dp_aux; + struct dp_catalog_aux *catalog; + struct dp_aux_cfg *cfg; + + struct mutex mutex; + struct completion comp; + + u32 aux_error_num; + u32 retry_cnt; + bool cmd_busy; + bool native; + bool read; + bool no_send_addr; + bool no_send_stop; + u32 offset; + u32 segment; + + struct drm_dp_aux drm_aux; +}; + +static char *dp_aux_get_error(u32 aux_error) +{ + switch (aux_error) { + case DP_AUX_ERR_NONE: + return DP_AUX_ENUM_STR(DP_AUX_ERR_NONE); + case DP_AUX_ERR_ADDR: + return DP_AUX_ENUM_STR(DP_AUX_ERR_ADDR); + case DP_AUX_ERR_TOUT: + return DP_AUX_ENUM_STR(DP_AUX_ERR_TOUT); + case DP_AUX_ERR_NACK: + return DP_AUX_ENUM_STR(DP_AUX_ERR_NACK); + case DP_AUX_ERR_DEFER: + return DP_AUX_ENUM_STR(DP_AUX_ERR_DEFER); + case DP_AUX_ERR_NACK_DEFER: + return DP_AUX_ENUM_STR(DP_AUX_ERR_NACK_DEFER); + default: + return "unknown"; + } +} + +static u32 dp_aux_write(struct dp_aux_private *aux, + struct drm_dp_aux_msg *msg) +{ + u32 data[4], reg, len; + u8 *msgdata = msg->buffer; + int const aux_cmd_fifo_len = 128; + int i = 0; + + if (aux->read) + len = 4; + else + len = msg->size + 4; + + /* + * cmd fifo only has depth of 144 bytes + * limit buf length to 128 bytes here + */ + if (len > aux_cmd_fifo_len) { + pr_err("buf len error\n"); + return 0; + } + + /* Pack cmd and write to HW */ + data[0] = (msg->address >> 16) & 0xf; /* addr[19:16] */ + if (aux->read) + data[0] |= BIT(4); /* R/W */ + + data[1] = (msg->address >> 8) & 0xff; /* addr[15:8] */ + data[2] = msg->address & 0xff; /* addr[7:0] */ + data[3] = (msg->size - 1) & 0xff; /* len[7:0] */ + + for (i = 0; i < len; i++) { + reg = (i < 4) ? data[i] : msgdata[i - 4]; + reg = ((reg) << 8) & 0x0000ff00; /* index = 0, write */ + if (i == 0) + reg |= DP_AUX_DATA_INDEX_WRITE; + aux->catalog->data = reg; + aux->catalog->write_data(aux->catalog); + } + + aux->catalog->clear_trans(aux->catalog, false); + + reg = 0; /* Transaction number == 1 */ + if (!aux->native) { /* i2c */ + reg |= BIT(8); + + if (aux->no_send_addr) + reg |= BIT(10); + + if (aux->no_send_stop) + reg |= BIT(11); + } + + reg |= BIT(9); + aux->catalog->data = reg; + aux->catalog->write_trans(aux->catalog); + + return len; +} + +static int dp_aux_cmd_fifo_tx(struct dp_aux_private *aux, + struct drm_dp_aux_msg *msg) +{ + u32 ret = 0, len = 0, timeout; + int const aux_timeout_ms = HZ/4; + + reinit_completion(&aux->comp); + + len = dp_aux_write(aux, msg); + if (len == 0) { + pr_err("DP AUX write failed\n"); + return -EINVAL; + } + + timeout = wait_for_completion_timeout(&aux->comp, aux_timeout_ms); + if (!timeout) { + pr_err("aux %s timeout\n", (aux->read ? "read" : "write")); + return -ETIMEDOUT; + } + + if (aux->aux_error_num == DP_AUX_ERR_NONE) { + ret = len; + } else { + pr_err_ratelimited("aux err: %s\n", + dp_aux_get_error(aux->aux_error_num)); + + ret = -EINVAL; + } + + return ret; +} + +static void dp_aux_cmd_fifo_rx(struct dp_aux_private *aux, + struct drm_dp_aux_msg *msg) +{ + u32 data; + u8 *dp; + u32 i, actual_i; + u32 len = msg->size; + + aux->catalog->clear_trans(aux->catalog, true); + + data = 0; + data |= DP_AUX_DATA_INDEX_WRITE; /* INDEX_WRITE */ + data |= BIT(0); /* read */ + + aux->catalog->data = data; + aux->catalog->write_data(aux->catalog); + + dp = msg->buffer; + + /* discard first byte */ + data = aux->catalog->read_data(aux->catalog); + + for (i = 0; i < len; i++) { + data = aux->catalog->read_data(aux->catalog); + *dp++ = (u8)((data >> 8) & 0xff); + + actual_i = (data >> 16) & 0xFF; + if (i != actual_i) + pr_warn("Index mismatch: expected %d, found %d\n", + i, actual_i); + } +} + +static void dp_aux_native_handler(struct dp_aux_private *aux) +{ + u32 isr = aux->catalog->isr; + + if (isr & DP_INTR_AUX_I2C_DONE) + aux->aux_error_num = DP_AUX_ERR_NONE; + else if (isr & DP_INTR_WRONG_ADDR) + aux->aux_error_num = DP_AUX_ERR_ADDR; + else if (isr & DP_INTR_TIMEOUT) + aux->aux_error_num = DP_AUX_ERR_TOUT; + if (isr & DP_INTR_NACK_DEFER) + aux->aux_error_num = DP_AUX_ERR_NACK; + + complete(&aux->comp); +} + +static void dp_aux_i2c_handler(struct dp_aux_private *aux) +{ + u32 isr = aux->catalog->isr; + + if (isr & DP_INTR_AUX_I2C_DONE) { + if (isr & (DP_INTR_I2C_NACK | DP_INTR_I2C_DEFER)) + aux->aux_error_num = DP_AUX_ERR_NACK; + else + aux->aux_error_num = DP_AUX_ERR_NONE; + } else { + if (isr & DP_INTR_WRONG_ADDR) + aux->aux_error_num = DP_AUX_ERR_ADDR; + else if (isr & DP_INTR_TIMEOUT) + aux->aux_error_num = DP_AUX_ERR_TOUT; + if (isr & DP_INTR_NACK_DEFER) + aux->aux_error_num = DP_AUX_ERR_NACK_DEFER; + if (isr & DP_INTR_I2C_NACK) + aux->aux_error_num = DP_AUX_ERR_NACK; + if (isr & DP_INTR_I2C_DEFER) + aux->aux_error_num = DP_AUX_ERR_DEFER; + } + + complete(&aux->comp); +} + +static void dp_aux_isr(struct dp_aux *dp_aux) +{ + struct dp_aux_private *aux; + + if (!dp_aux) { + pr_err("invalid input\n"); + return; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + aux->catalog->get_irq(aux->catalog, aux->cmd_busy); + + if (!aux->cmd_busy) + return; + + if (aux->native) + dp_aux_native_handler(aux); + else + dp_aux_i2c_handler(aux); +} + +static void dp_aux_reconfig(struct dp_aux *dp_aux) +{ + struct dp_aux_private *aux; + + if (!dp_aux) { + pr_err("invalid input\n"); + return; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + aux->catalog->update_aux_cfg(aux->catalog, + aux->cfg, PHY_AUX_CFG1); + aux->catalog->reset(aux->catalog); +} + +static void dp_aux_update_offset_and_segment(struct dp_aux_private *aux, + struct drm_dp_aux_msg *input_msg) +{ + u32 const edid_address = 0x50; + u32 const segment_address = 0x30; + bool i2c_read = input_msg->request & + (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ); + u8 *data = NULL; + + if (aux->native || i2c_read || ((input_msg->address != edid_address) && + (input_msg->address != segment_address))) + return; + + + data = input_msg->buffer; + if (input_msg->address == segment_address) + aux->segment = *data; + else + aux->offset = *data; +} + +/** + * dp_aux_transfer_helper() - helper function for EDID read transactions + * + * @aux: DP AUX private structure + * @input_msg: input message from DRM upstream APIs + * + * return: void + * + * This helper function is used to fix EDID reads for non-compliant + * sinks that do not handle the i2c middle-of-transaction flag correctly. + */ +static void dp_aux_transfer_helper(struct dp_aux_private *aux, + struct drm_dp_aux_msg *input_msg) +{ + struct drm_dp_aux_msg helper_msg; + u32 const message_size = 0x10; + u32 const segment_address = 0x30; + bool i2c_mot = input_msg->request & DP_AUX_I2C_MOT; + bool i2c_read = input_msg->request & + (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ); + + if (!i2c_mot || !i2c_read || (input_msg->size == 0)) + return; + + aux->read = false; + aux->cmd_busy = true; + aux->no_send_addr = true; + aux->no_send_stop = true; + + /* + * Send the segment address for every i2c read in which the + * middle-of-tranaction flag is set. This is required to support EDID + * reads of more than 2 blocks as the segment address is reset to 0 + * since we are overriding the middle-of-transaction flag for read + * transactions. + */ + memset(&helper_msg, 0, sizeof(helper_msg)); + helper_msg.address = segment_address; + helper_msg.buffer = &aux->segment; + helper_msg.size = 1; + dp_aux_cmd_fifo_tx(aux, &helper_msg); + + /* + * Send the offset address for every i2c read in which the + * middle-of-transaction flag is set. This will ensure that the sink + * will update its read pointer and return the correct portion of the + * EDID buffer in the subsequent i2c read trasntion triggered in the + * native AUX transfer function. + */ + memset(&helper_msg, 0, sizeof(helper_msg)); + helper_msg.address = input_msg->address; + helper_msg.buffer = &aux->offset; + helper_msg.size = 1; + dp_aux_cmd_fifo_tx(aux, &helper_msg); + aux->offset += message_size; + + if (aux->offset == 0x80 || aux->offset == 0x100) + aux->segment = 0x0; /* reset segment at end of block */ +} + +/* + * This function does the real job to process an AUX transaction. + * It will call aux_reset() function to reset the AUX channel, + * if the waiting is timeout. + */ +static ssize_t dp_aux_transfer(struct drm_dp_aux *drm_aux, + struct drm_dp_aux_msg *msg) +{ + ssize_t ret; + int const aux_cmd_native_max = 16; + int const aux_cmd_i2c_max = 128; + int const retry_count = 5; + struct dp_aux_private *aux = container_of(drm_aux, + struct dp_aux_private, drm_aux); + + mutex_lock(&aux->mutex); + + aux->native = msg->request & (DP_AUX_NATIVE_WRITE & DP_AUX_NATIVE_READ); + + /* Ignore address only message */ + if ((msg->size == 0) || (msg->buffer == NULL)) { + msg->reply = aux->native ? + DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK; + ret = msg->size; + goto unlock_exit; + } + + /* msg sanity check */ + if ((aux->native && (msg->size > aux_cmd_native_max)) || + (msg->size > aux_cmd_i2c_max)) { + pr_err("%s: invalid msg: size(%zu), request(%x)\n", + __func__, msg->size, msg->request); + ret = -EINVAL; + goto unlock_exit; + } + + dp_aux_update_offset_and_segment(aux, msg); + dp_aux_transfer_helper(aux, msg); + + aux->read = msg->request & (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ); + aux->cmd_busy = true; + + if (aux->read) { + aux->no_send_addr = true; + aux->no_send_stop = false; + } else { + aux->no_send_addr = true; + aux->no_send_stop = true; + } + + ret = dp_aux_cmd_fifo_tx(aux, msg); + if ((ret < 0) && aux->native) { + aux->retry_cnt++; + if (!(aux->retry_cnt % retry_count)) + aux->catalog->update_aux_cfg(aux->catalog, + aux->cfg, PHY_AUX_CFG1); + aux->catalog->reset(aux->catalog); + goto unlock_exit; + } else if (ret < 0) { + goto unlock_exit; + } + + if (aux->aux_error_num == DP_AUX_ERR_NONE) { + if (aux->read) + dp_aux_cmd_fifo_rx(aux, msg); + + msg->reply = aux->native ? + DP_AUX_NATIVE_REPLY_ACK : DP_AUX_I2C_REPLY_ACK; + } else { + /* Reply defer to retry */ + msg->reply = aux->native ? + DP_AUX_NATIVE_REPLY_DEFER : DP_AUX_I2C_REPLY_DEFER; + } + + /* Return requested size for success or retry */ + ret = msg->size; + aux->retry_cnt = 0; + +unlock_exit: + aux->cmd_busy = false; + mutex_unlock(&aux->mutex); + return ret; +} + +static void dp_aux_reset_phy_config_indices(struct dp_aux_cfg *aux_cfg) +{ + int i = 0; + + for (i = 0; i < PHY_AUX_CFG_MAX; i++) + aux_cfg[i].current_index = 0; +} + +static void dp_aux_init(struct dp_aux *dp_aux, struct dp_aux_cfg *aux_cfg) +{ + struct dp_aux_private *aux; + + if (!dp_aux || !aux_cfg) { + pr_err("invalid input\n"); + return; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + aux->catalog->reset(aux->catalog); + aux->catalog->enable(aux->catalog, true); + aux->retry_cnt = 0; + dp_aux_reset_phy_config_indices(aux_cfg); + aux->catalog->setup(aux->catalog, aux_cfg); +} + +static void dp_aux_deinit(struct dp_aux *dp_aux) +{ + struct dp_aux_private *aux; + + if (!dp_aux) { + pr_err("invalid input\n"); + return; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + aux->catalog->enable(aux->catalog, false); +} + +static int dp_aux_register(struct dp_aux *dp_aux) +{ + struct dp_aux_private *aux; + int ret = 0; + + if (!dp_aux) { + pr_err("invalid input\n"); + ret = -EINVAL; + goto exit; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + aux->drm_aux.name = "sde_dp_aux"; + aux->drm_aux.dev = aux->dev; + aux->drm_aux.transfer = dp_aux_transfer; + ret = drm_dp_aux_register(&aux->drm_aux); + if (ret) { + pr_err("%s: failed to register drm aux: %d\n", __func__, ret); + goto exit; + } + dp_aux->drm_aux = &aux->drm_aux; +exit: + return ret; +} + +static void dp_aux_deregister(struct dp_aux *dp_aux) +{ + struct dp_aux_private *aux; + + if (!dp_aux) { + pr_err("invalid input\n"); + return; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + drm_dp_aux_unregister(&aux->drm_aux); +} + +struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog, + struct dp_aux_cfg *aux_cfg) +{ + int rc = 0; + struct dp_aux_private *aux; + struct dp_aux *dp_aux; + + if (!catalog || !aux_cfg) { + pr_err("invalid input\n"); + rc = -ENODEV; + goto error; + } + + aux = devm_kzalloc(dev, sizeof(*aux), GFP_KERNEL); + if (!aux) { + rc = -ENOMEM; + goto error; + } + + init_completion(&aux->comp); + aux->cmd_busy = false; + mutex_init(&aux->mutex); + + aux->dev = dev; + aux->catalog = catalog; + aux->cfg = aux_cfg; + dp_aux = &aux->dp_aux; + aux->retry_cnt = 0; + + dp_aux->isr = dp_aux_isr; + dp_aux->init = dp_aux_init; + dp_aux->deinit = dp_aux_deinit; + dp_aux->drm_aux_register = dp_aux_register; + dp_aux->drm_aux_deregister = dp_aux_deregister; + dp_aux->reconfig = dp_aux_reconfig; + + return dp_aux; +error: + return ERR_PTR(rc); +} + +void dp_aux_put(struct dp_aux *dp_aux) +{ + struct dp_aux_private *aux; + + if (!dp_aux) + return; + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + mutex_destroy(&aux->mutex); + + devm_kfree(aux->dev, aux); +} diff --git a/drivers/gpu/drm/msm/dp/dp_aux.h b/drivers/gpu/drm/msm/dp/dp_aux.h new file mode 100644 index 0000000000000000000000000000000000000000..5d96fd9b71d6fd7c2e68935468676606a2ec6342 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_aux.h @@ -0,0 +1,44 @@ +/* + * 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_AUX_H_ +#define _DP_AUX_H_ + +#include "dp_catalog.h" +#include "drm_dp_helper.h" + +enum dp_aux_error { + DP_AUX_ERR_NONE = 0, + DP_AUX_ERR_ADDR = -1, + DP_AUX_ERR_TOUT = -2, + DP_AUX_ERR_NACK = -3, + DP_AUX_ERR_DEFER = -4, + DP_AUX_ERR_NACK_DEFER = -5, +}; + +struct dp_aux { + struct drm_dp_aux *drm_aux; + int (*drm_aux_register)(struct dp_aux *aux); + void (*drm_aux_deregister)(struct dp_aux *aux); + void (*isr)(struct dp_aux *aux); + void (*init)(struct dp_aux *aux, struct dp_aux_cfg *aux_cfg); + void (*deinit)(struct dp_aux *aux); + void (*reconfig)(struct dp_aux *aux); +}; + +struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog, + struct dp_aux_cfg *aux_cfg); +void dp_aux_put(struct dp_aux *aux); + +#endif /*__DP_AUX_H_*/ diff --git a/drivers/gpu/drm/msm/dp/dp_catalog.c b/drivers/gpu/drm/msm/dp/dp_catalog.c new file mode 100644 index 0000000000000000000000000000000000000000..8b17aede3ed002b68a091d6b05d7fec0c8557af9 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_catalog.c @@ -0,0 +1,1320 @@ +/* + * 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 +#include + +#include "dp_catalog.h" +#include "dp_reg.h" + +#define DP_GET_MSB(x) (x >> 8) +#define DP_GET_LSB(x) (x & 0xff) + +#define dp_read(offset) readl_relaxed((offset)) +#define dp_write(offset, data) writel_relaxed((data), (offset)) + +#define dp_catalog_get_priv(x) { \ + struct dp_catalog *dp_catalog; \ + dp_catalog = container_of(x, struct dp_catalog, x); \ + catalog = container_of(dp_catalog, struct dp_catalog_private, \ + dp_catalog); \ +} + +#define DP_INTERRUPT_STATUS1 \ + (DP_INTR_AUX_I2C_DONE| \ + DP_INTR_WRONG_ADDR | DP_INTR_TIMEOUT | \ + DP_INTR_NACK_DEFER | DP_INTR_WRONG_DATA_CNT | \ + DP_INTR_I2C_NACK | DP_INTR_I2C_DEFER | \ + DP_INTR_PLL_UNLOCKED | DP_INTR_AUX_ERROR) + +#define DP_INTR_MASK1 (DP_INTERRUPT_STATUS1 << 2) + +#define DP_INTERRUPT_STATUS2 \ + (DP_INTR_READY_FOR_VIDEO | DP_INTR_IDLE_PATTERN_SENT | \ + DP_INTR_FRAME_END | DP_INTR_CRC_UPDATED) + +#define DP_INTR_MASK2 (DP_INTERRUPT_STATUS2 << 2) + +static u8 const vm_pre_emphasis[4][4] = { + {0x00, 0x0B, 0x12, 0xFF}, /* pe0, 0 db */ + {0x00, 0x0A, 0x12, 0xFF}, /* pe1, 3.5 db */ + {0x00, 0x0C, 0xFF, 0xFF}, /* pe2, 6.0 db */ + {0xFF, 0xFF, 0xFF, 0xFF} /* pe3, 9.5 db */ +}; + +/* voltage swing, 0.2v and 1.0v are not support */ +static u8 const vm_voltage_swing[4][4] = { + {0x07, 0x0F, 0x14, 0xFF}, /* sw0, 0.4v */ + {0x11, 0x1D, 0x1F, 0xFF}, /* sw1, 0.6 v */ + {0x18, 0x1F, 0xFF, 0xFF}, /* sw1, 0.8 v */ + {0xFF, 0xFF, 0xFF, 0xFF} /* sw1, 1.2 v, optional */ +}; + +/* audio related catalog functions */ +struct dp_catalog_private { + struct device *dev; + struct dp_io *io; + + u32 (*audio_map)[DP_AUDIO_SDP_HEADER_MAX]; + struct dp_catalog dp_catalog; +}; + +/* aux related catalog functions */ +static u32 dp_catalog_aux_read_data(struct dp_catalog_aux *aux) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + goto end; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + return dp_read(base + DP_AUX_DATA); +end: + return 0; +} + +static int dp_catalog_aux_write_data(struct dp_catalog_aux *aux) +{ + int rc = 0; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_AUX_DATA, aux->data); +end: + return rc; +} + +static int dp_catalog_aux_write_trans(struct dp_catalog_aux *aux) +{ + int rc = 0; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_AUX_TRANS_CTRL, aux->data); +end: + return rc; +} + +static int dp_catalog_aux_clear_trans(struct dp_catalog_aux *aux, bool read) +{ + int rc = 0; + u32 data = 0; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + if (read) { + data = dp_read(base + DP_AUX_TRANS_CTRL); + data &= ~BIT(9); + dp_write(base + DP_AUX_TRANS_CTRL, data); + } else { + dp_write(base + DP_AUX_TRANS_CTRL, 0); + } +end: + return rc; +} + +static void dp_catalog_aux_reset(struct dp_catalog_aux *aux) +{ + u32 aux_ctrl; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + aux_ctrl = dp_read(base + DP_AUX_CTRL); + + aux_ctrl |= BIT(1); + dp_write(base + DP_AUX_CTRL, aux_ctrl); + usleep_range(1000, 1010); /* h/w recommended delay */ + + aux_ctrl &= ~BIT(1); + dp_write(base + DP_AUX_CTRL, aux_ctrl); +} + +static void dp_catalog_aux_enable(struct dp_catalog_aux *aux, bool enable) +{ + u32 aux_ctrl; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + aux_ctrl = dp_read(base + DP_AUX_CTRL); + + if (enable) { + dp_write(base + DP_TIMEOUT_COUNT, 0xffff); + dp_write(base + DP_AUX_LIMITS, 0xffff); + aux_ctrl |= BIT(0); + } else { + aux_ctrl &= ~BIT(0); + } + + dp_write(base + DP_AUX_CTRL, aux_ctrl); +} + +static void dp_catalog_aux_update_cfg(struct dp_catalog_aux *aux, + struct dp_aux_cfg *cfg, enum dp_phy_aux_config_type type) +{ + struct dp_catalog_private *catalog; + u32 new_index = 0, current_index = 0; + + if (!aux || !cfg || (type >= PHY_AUX_CFG_MAX)) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(aux); + + current_index = cfg[type].current_index; + new_index = (current_index + 1) % cfg[type].cfg_cnt; + pr_debug("Updating %s from 0x%08x to 0x%08x\n", + dp_phy_aux_config_type_to_string(type), + cfg[type].lut[current_index], cfg[type].lut[new_index]); + + dp_write(catalog->io->phy_io.base + cfg[type].offset, + cfg[type].lut[new_index]); + cfg[type].current_index = new_index; +} + +static void dp_catalog_aux_setup(struct dp_catalog_aux *aux, + struct dp_aux_cfg *cfg) +{ + struct dp_catalog_private *catalog; + int i = 0; + + if (!aux || !cfg) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(aux); + + dp_write(catalog->io->phy_io.base + DP_PHY_PD_CTL, 0x65); + wmb(); /* make sure PD programming happened */ + + /* Turn on BIAS current for PHY/PLL */ + dp_write(catalog->io->dp_pll_io.base + + QSERDES_COM_BIAS_EN_CLKBUFLR_EN, 0x1b); + + /* DP AUX CFG register programming */ + for (i = 0; i < PHY_AUX_CFG_MAX; i++) { + pr_debug("%s: offset=0x%08x, value=0x%08x\n", + dp_phy_aux_config_type_to_string(i), + cfg[i].offset, cfg[i].lut[cfg[i].current_index]); + dp_write(catalog->io->phy_io.base + cfg[i].offset, + cfg[i].lut[cfg[i].current_index]); + } + + dp_write(catalog->io->phy_io.base + DP_PHY_AUX_INTERRUPT_MASK, 0x1F); +} + +static void dp_catalog_aux_get_irq(struct dp_catalog_aux *aux, bool cmd_busy) +{ + u32 ack; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!aux) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(aux); + base = catalog->io->ctrl_io.base; + + aux->isr = dp_read(base + DP_INTR_STATUS); + aux->isr &= ~DP_INTR_MASK1; + ack = aux->isr & DP_INTERRUPT_STATUS1; + ack <<= 1; + ack |= DP_INTR_MASK1; + dp_write(base + DP_INTR_STATUS, ack); +} + +/* controller related catalog functions */ +static u32 dp_catalog_ctrl_read_hdcp_status(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + return dp_read(base + DP_HDCP_STATUS); +} + +static void dp_catalog_ctrl_setup_infoframe_sdp(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + u32 header, data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + header = dp_read(base + MMSS_DP_VSCEXT_0); + header |= ctrl->hdr_data.vsc_hdr_byte1; + dp_write(base + MMSS_DP_VSCEXT_0, header); + + header = dp_read(base + MMSS_DP_VSCEXT_1); + header |= ctrl->hdr_data.vsc_hdr_byte1; + dp_write(base + MMSS_DP_VSCEXT_1, header); + + header = dp_read(base + MMSS_DP_VSCEXT_1); + header |= ctrl->hdr_data.vsc_hdr_byte1; + dp_write(base + MMSS_DP_VSCEXT_1, header); + + header = ctrl->hdr_data.version; + header |= ctrl->hdr_data.length << 8; + header |= ctrl->hdr_data.eotf << 16; + header |= (ctrl->hdr_data.descriptor_id << 24); + dp_write(base + MMSS_DP_VSCEXT_2, header); + + data = (DP_GET_LSB(ctrl->hdr_data.display_primaries_x[0]) | + (DP_GET_MSB(ctrl->hdr_data.display_primaries_x[0]) << 8) | + (DP_GET_LSB(ctrl->hdr_data.display_primaries_y[0]) << 16) | + (DP_GET_MSB(ctrl->hdr_data.display_primaries_y[0]) << 24)); + dp_write(base + MMSS_DP_VSCEXT_3, data); + + data = (DP_GET_LSB(ctrl->hdr_data.display_primaries_x[1]) | + (DP_GET_MSB(ctrl->hdr_data.display_primaries_x[1]) << 8) | + (DP_GET_LSB(ctrl->hdr_data.display_primaries_y[1]) << 16) | + (DP_GET_MSB(ctrl->hdr_data.display_primaries_y[1]) << 24)); + dp_write(base + MMSS_DP_VSCEXT_4, data); + + data = (DP_GET_LSB(ctrl->hdr_data.display_primaries_x[2]) | + (DP_GET_MSB(ctrl->hdr_data.display_primaries_x[2]) << 8) | + (DP_GET_LSB(ctrl->hdr_data.display_primaries_y[2]) << 16) | + (DP_GET_MSB(ctrl->hdr_data.display_primaries_y[2]) << 24)); + dp_write(base + MMSS_DP_VSCEXT_5, data); + + data = (DP_GET_LSB(ctrl->hdr_data.white_point_x) | + (DP_GET_MSB(ctrl->hdr_data.white_point_x) << 8) | + (DP_GET_LSB(ctrl->hdr_data.white_point_y) << 16) | + (DP_GET_MSB(ctrl->hdr_data.white_point_y) << 24)); + dp_write(base + MMSS_DP_VSCEXT_6, data); + + data = (DP_GET_LSB(ctrl->hdr_data.max_luminance) | + (DP_GET_MSB(ctrl->hdr_data.max_luminance) << 8) | + (DP_GET_LSB(ctrl->hdr_data.min_luminance) << 16) | + (DP_GET_MSB(ctrl->hdr_data.min_luminance) << 24)); + dp_write(base + MMSS_DP_VSCEXT_7, data); + + data = (DP_GET_LSB(ctrl->hdr_data.max_content_light_level) | + (DP_GET_MSB(ctrl->hdr_data.max_content_light_level) << 8) | + (DP_GET_LSB(ctrl->hdr_data.max_average_light_level) << 16) | + (DP_GET_MSB(ctrl->hdr_data.max_average_light_level) << 24)); + dp_write(base + MMSS_DP_VSCEXT_8, data); + + dp_write(base + MMSS_DP_VSCEXT_9, 0x00); +} + +static void dp_catalog_ctrl_setup_vsc_sdp(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + u32 value; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + value = dp_read(base + MMSS_DP_GENERIC0_0); + value |= ctrl->hdr_data.vsc_hdr_byte1; + dp_write(base + MMSS_DP_GENERIC0_0, value); + + value = dp_read(base + MMSS_DP_GENERIC0_1); + value |= ctrl->hdr_data.vsc_hdr_byte2; + dp_write(base + MMSS_DP_GENERIC0_1, value); + + value = dp_read(base + MMSS_DP_GENERIC0_1); + value |= ctrl->hdr_data.vsc_hdr_byte3; + dp_write(base + MMSS_DP_GENERIC0_1, value); + + dp_write(base + MMSS_DP_GENERIC0_2, 0x00); + dp_write(base + MMSS_DP_GENERIC0_3, 0x00); + dp_write(base + MMSS_DP_GENERIC0_4, 0x00); + dp_write(base + MMSS_DP_GENERIC0_5, 0x00); + + dp_write(base + MMSS_DP_GENERIC0_6, ctrl->hdr_data.pkt_payload); + dp_write(base + MMSS_DP_GENERIC0_7, 0x00); + dp_write(base + MMSS_DP_GENERIC0_8, 0x00); + dp_write(base + MMSS_DP_GENERIC0_9, 0x00); +} + +static void dp_catalog_ctrl_config_hdr(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + u32 cfg, cfg2; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + cfg = dp_read(base + MMSS_DP_SDP_CFG); + /* VSCEXT_SDP_EN */ + cfg |= BIT(16); + + /* GEN0_SDP_EN */ + cfg |= BIT(17); + + dp_write(base + MMSS_DP_SDP_CFG, cfg); + + cfg2 = dp_read(base + MMSS_DP_SDP_CFG2); + /* Generic0 SDP Payload is 19 bytes which is > 16, so Bit16 is 1 */ + cfg2 |= BIT(16); + dp_write(base + MMSS_DP_SDP_CFG2, cfg2); + + dp_catalog_ctrl_setup_vsc_sdp(ctrl); + dp_catalog_ctrl_setup_infoframe_sdp(ctrl); + + cfg = dp_read(base + DP_MISC1_MISC0); + /* Indicates presence of VSC */ + cfg |= BIT(6) << 8; + + dp_write(base + DP_MISC1_MISC0, cfg); + + cfg = dp_read(base + DP_CONFIGURATION_CTRL); + /* Send VSC */ + cfg |= BIT(7); + + switch (ctrl->hdr_data.bpc) { + default: + case 10: + cfg |= BIT(9); + break; + case 8: + cfg |= BIT(8); + break; + } + + dp_write(base + DP_CONFIGURATION_CTRL, cfg); + + cfg = dp_read(base + DP_COMPRESSION_MODE_CTRL); + + /* Trigger SDP values in registers */ + cfg |= BIT(8); + dp_write(base + DP_COMPRESSION_MODE_CTRL, cfg); +} + +static void dp_catalog_ctrl_update_transfer_unit(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_VALID_BOUNDARY, ctrl->valid_boundary); + dp_write(base + DP_TU, ctrl->dp_tu); + dp_write(base + DP_VALID_BOUNDARY_2, ctrl->valid_boundary2); +} + +static void dp_catalog_ctrl_state_ctrl(struct dp_catalog_ctrl *ctrl, u32 state) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_STATE_CTRL, state); +} + +static void dp_catalog_ctrl_config_ctrl(struct dp_catalog_ctrl *ctrl, u32 cfg) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + pr_debug("DP_CONFIGURATION_CTRL=0x%x\n", cfg); + + dp_write(base + DP_CONFIGURATION_CTRL, cfg); + dp_write(base + DP_MAINLINK_LEVELS, 0xa08); + dp_write(base + MMSS_DP_ASYNC_FIFO_CONFIG, 0x1); +} + +static void dp_catalog_ctrl_lane_mapping(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_LOGICAL2PHYSCIAL_LANE_MAPPING, 0xe4); +} + +static void dp_catalog_ctrl_mainlink_ctrl(struct dp_catalog_ctrl *ctrl, + bool enable) +{ + u32 mainlink_ctrl; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + if (enable) { + dp_write(base + DP_MAINLINK_CTRL, 0x02000000); + wmb(); /* make sure mainlink is turned off before reset */ + dp_write(base + DP_MAINLINK_CTRL, 0x02000002); + wmb(); /* make sure mainlink entered reset */ + dp_write(base + DP_MAINLINK_CTRL, 0x02000000); + wmb(); /* make sure mainlink reset done */ + dp_write(base + DP_MAINLINK_CTRL, 0x02000001); + wmb(); /* make sure mainlink turned on */ + } else { + mainlink_ctrl = dp_read(base + DP_MAINLINK_CTRL); + mainlink_ctrl &= ~BIT(0); + dp_write(base + DP_MAINLINK_CTRL, mainlink_ctrl); + } +} + +static void dp_catalog_ctrl_config_misc(struct dp_catalog_ctrl *ctrl, + u32 cc, u32 tb) +{ + u32 misc_val; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + misc_val = dp_read(base + DP_MISC1_MISC0); + misc_val |= cc; + misc_val |= (tb << 5); + misc_val |= BIT(0); /* Configure clock to synchronous mode */ + + pr_debug("misc settings = 0x%x\n", misc_val); + dp_write(base + DP_MISC1_MISC0, misc_val); +} + +static void dp_catalog_ctrl_config_msa(struct dp_catalog_ctrl *ctrl, + u32 rate, u32 stream_rate_khz, + bool fixed_nvid) +{ + u32 pixel_m, pixel_n; + u32 mvid, nvid; + u64 mvid_calc; + u32 const nvid_fixed = 0x8000; + u32 const link_rate_hbr2 = 540000; + u32 const link_rate_hbr3 = 810000; + struct dp_catalog_private *catalog; + void __iomem *base_cc, *base_ctrl; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + if (fixed_nvid) { + pr_debug("use fixed NVID=0x%x\n", nvid_fixed); + nvid = nvid_fixed; + + pr_debug("link rate=%dkbps, stream_rate_khz=%uKhz", + rate, stream_rate_khz); + + /* + * For intermediate results, use 64 bit arithmetic to avoid + * loss of precision. + */ + mvid_calc = (u64) stream_rate_khz * nvid; + mvid_calc = div_u64(mvid_calc, rate); + + /* + * truncate back to 32 bits as this final divided value will + * always be within the range of a 32 bit unsigned int. + */ + mvid = (u32) mvid_calc; + } else { + base_cc = catalog->io->dp_cc_io.base; + + pixel_m = dp_read(base_cc + MMSS_DP_PIXEL_M); + pixel_n = dp_read(base_cc + MMSS_DP_PIXEL_N); + pr_debug("pixel_m=0x%x, pixel_n=0x%x\n", pixel_m, pixel_n); + + mvid = (pixel_m & 0xFFFF) * 5; + nvid = (0xFFFF & (~pixel_n)) + (pixel_m & 0xFFFF); + + pr_debug("rate = %d\n", rate); + + if (link_rate_hbr2 == rate) + nvid *= 2; + + if (link_rate_hbr3 == rate) + nvid *= 3; + } + + base_ctrl = catalog->io->ctrl_io.base; + pr_debug("mvid=0x%x, nvid=0x%x\n", mvid, nvid); + dp_write(base_ctrl + DP_SOFTWARE_MVID, mvid); + dp_write(base_ctrl + DP_SOFTWARE_NVID, nvid); +} + +static void dp_catalog_ctrl_set_pattern(struct dp_catalog_ctrl *ctrl, + u32 pattern) +{ + int bit, cnt = 10; + u32 data; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + bit = 1; + bit <<= (pattern - 1); + pr_debug("hw: bit=%d train=%d\n", bit, pattern); + dp_write(base + DP_STATE_CTRL, bit); + + bit = 8; + bit <<= (pattern - 1); + + while (cnt--) { + data = dp_read(base + DP_MAINLINK_READY); + if (data & bit) + break; + } + + if (cnt == 0) + pr_err("set link_train=%d failed\n", pattern); +} + +static void dp_catalog_ctrl_usb_reset(struct dp_catalog_ctrl *ctrl, bool flip) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + + base = catalog->io->usb3_dp_com.base; + + dp_write(base + USB3_DP_COM_RESET_OVRD_CTRL, 0x0a); + dp_write(base + USB3_DP_COM_PHY_MODE_CTRL, 0x02); + dp_write(base + USB3_DP_COM_SW_RESET, 0x01); + /* make sure usb3 com phy software reset is done */ + wmb(); + + if (!flip) /* CC1 */ + dp_write(base + USB3_DP_COM_TYPEC_CTRL, 0x02); + else /* CC2 */ + dp_write(base + USB3_DP_COM_TYPEC_CTRL, 0x03); + + dp_write(base + USB3_DP_COM_SWI_CTRL, 0x00); + dp_write(base + USB3_DP_COM_SW_RESET, 0x00); + /* make sure the software reset is done */ + wmb(); + + dp_write(base + USB3_DP_COM_POWER_DOWN_CTRL, 0x01); + dp_write(base + USB3_DP_COM_RESET_OVRD_CTRL, 0x00); + /* make sure phy is brought out of reset */ + wmb(); + +} + +static void dp_catalog_ctrl_reset(struct dp_catalog_ctrl *ctrl) +{ + u32 sw_reset; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + sw_reset = dp_read(base + DP_SW_RESET); + + sw_reset |= BIT(0); + dp_write(base + DP_SW_RESET, sw_reset); + usleep_range(1000, 1010); /* h/w recommended delay */ + + sw_reset &= ~BIT(0); + dp_write(base + DP_SW_RESET, sw_reset); +} + +static bool dp_catalog_ctrl_mainlink_ready(struct dp_catalog_ctrl *ctrl) +{ + u32 data; + int cnt = 10; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + goto end; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + while (--cnt) { + /* DP_MAINLINK_READY */ + data = dp_read(base + DP_MAINLINK_READY); + if (data & BIT(0)) + return true; + + usleep_range(1000, 1010); /* 1ms wait before next reg read */ + } + pr_err("mainlink not ready\n"); +end: + return false; +} + +static void dp_catalog_ctrl_enable_irq(struct dp_catalog_ctrl *ctrl, + bool enable) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + if (enable) { + dp_write(base + DP_INTR_STATUS, DP_INTR_MASK1); + dp_write(base + DP_INTR_STATUS2, DP_INTR_MASK2); + } else { + dp_write(base + DP_INTR_STATUS, 0x00); + dp_write(base + DP_INTR_STATUS2, 0x00); + } +} + +static void dp_catalog_ctrl_hpd_config(struct dp_catalog_ctrl *ctrl, bool en) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + if (en) { + u32 reftimer = dp_read(base + DP_DP_HPD_REFTIMER); + + dp_write(base + DP_DP_HPD_INT_ACK, 0xF); + dp_write(base + DP_DP_HPD_INT_MASK, 0xF); + + /* Enabling REFTIMER */ + reftimer |= BIT(16); + dp_write(base + DP_DP_HPD_REFTIMER, 0xF); + /* Enable HPD */ + dp_write(base + DP_DP_HPD_CTRL, 0x1); + } else { + /*Disable HPD */ + dp_write(base + DP_DP_HPD_CTRL, 0x0); + } +} + +static void dp_catalog_ctrl_get_interrupt(struct dp_catalog_ctrl *ctrl) +{ + u32 ack = 0; + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + ctrl->isr = dp_read(base + DP_INTR_STATUS2); + ctrl->isr &= ~DP_INTR_MASK2; + ack = ctrl->isr & DP_INTERRUPT_STATUS2; + ack <<= 1; + ack |= DP_INTR_MASK2; + dp_write(base + DP_INTR_STATUS2, ack); +} + +static void dp_catalog_ctrl_phy_reset(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_PHY_CTRL, 0x5); /* bit 0 & 2 */ + usleep_range(1000, 1010); /* h/w recommended delay */ + dp_write(base + DP_PHY_CTRL, 0x0); + wmb(); /* make sure PHY reset done */ +} + +static void dp_catalog_ctrl_phy_lane_cfg(struct dp_catalog_ctrl *ctrl, + bool flipped, u8 ln_cnt) +{ + u32 info = 0x0; + struct dp_catalog_private *catalog; + u8 orientation = BIT(!!flipped); + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + + info |= (ln_cnt & 0x0F); + info |= ((orientation & 0x0F) << 4); + pr_debug("Shared Info = 0x%x\n", info); + + dp_write(catalog->io->phy_io.base + DP_PHY_SPARE0, info); +} + +static void dp_catalog_ctrl_update_vx_px(struct dp_catalog_ctrl *ctrl, + u8 v_level, u8 p_level) +{ + struct dp_catalog_private *catalog; + void __iomem *base0, *base1; + u8 value0, value1; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + base0 = catalog->io->ln_tx0_io.base; + base1 = catalog->io->ln_tx1_io.base; + + pr_debug("hw: v=%d p=%d\n", v_level, p_level); + + value0 = vm_voltage_swing[v_level][p_level]; + value1 = vm_pre_emphasis[v_level][p_level]; + + /* program default setting first */ + dp_write(base0 + TXn_TX_DRV_LVL, 0x2A); + dp_write(base1 + TXn_TX_DRV_LVL, 0x2A); + dp_write(base0 + TXn_TX_EMP_POST1_LVL, 0x20); + dp_write(base1 + TXn_TX_EMP_POST1_LVL, 0x20); + + /* Enable MUX to use Cursor values from these registers */ + value0 |= BIT(5); + value1 |= BIT(5); + + /* Configure host and panel only if both values are allowed */ + if (value0 != 0xFF && value1 != 0xFF) { + dp_write(base0 + TXn_TX_DRV_LVL, value0); + dp_write(base1 + TXn_TX_DRV_LVL, value0); + dp_write(base0 + TXn_TX_EMP_POST1_LVL, value1); + dp_write(base1 + TXn_TX_EMP_POST1_LVL, value1); + + pr_debug("hw: vx_value=0x%x px_value=0x%x\n", + value0, value1); + } else { + pr_err("invalid vx (0x%x=0x%x), px (0x%x=0x%x\n", + v_level, value0, p_level, value1); + } +} + +static void dp_catalog_ctrl_send_phy_pattern(struct dp_catalog_ctrl *ctrl, + u32 pattern) +{ + struct dp_catalog_private *catalog; + u32 value = 0x0; + void __iomem *base = NULL; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_catalog_get_priv(ctrl); + + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_STATE_CTRL, 0x0); + + switch (pattern) { + case DP_TEST_PHY_PATTERN_D10_2_NO_SCRAMBLING: + dp_write(base + DP_STATE_CTRL, 0x1); + break; + case DP_TEST_PHY_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT: + value &= ~(1 << 16); + dp_write(base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); + value |= 0xFC; + dp_write(base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); + dp_write(base + DP_MAINLINK_LEVELS, 0x2); + dp_write(base + DP_STATE_CTRL, 0x10); + break; + case DP_TEST_PHY_PATTERN_PRBS7: + dp_write(base + DP_STATE_CTRL, 0x20); + break; + case DP_TEST_PHY_PATTERN_80_BIT_CUSTOM_PATTERN: + dp_write(base + DP_STATE_CTRL, 0x40); + /* 00111110000011111000001111100000 */ + dp_write(base + DP_TEST_80BIT_CUSTOM_PATTERN_REG0, 0x3E0F83E0); + /* 00001111100000111110000011111000 */ + dp_write(base + DP_TEST_80BIT_CUSTOM_PATTERN_REG1, 0x0F83E0F8); + /* 1111100000111110 */ + dp_write(base + DP_TEST_80BIT_CUSTOM_PATTERN_REG2, 0x0000F83E); + break; + case DP_TEST_PHY_PATTERN_HBR2_CTS_EYE_PATTERN: + value = BIT(16); + dp_write(base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); + value |= 0xFC; + dp_write(base + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); + dp_write(base + DP_MAINLINK_LEVELS, 0x2); + dp_write(base + DP_STATE_CTRL, 0x10); + break; + default: + pr_debug("No valid test pattern requested: 0x%x\n", pattern); + return; + } + + /* Make sure the test pattern is programmed in the hardware */ + wmb(); +} + +static u32 dp_catalog_ctrl_read_phy_pattern(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + void __iomem *base = NULL; + + if (!ctrl) { + pr_err("invalid input\n"); + return 0; + } + + dp_catalog_get_priv(ctrl); + + base = catalog->io->ctrl_io.base; + + return dp_read(base + DP_MAINLINK_READY); +} + +/* panel related catalog functions */ +static int dp_catalog_panel_timing_cfg(struct dp_catalog_panel *panel) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + + if (!panel) { + pr_err("invalid input\n"); + goto end; + } + + dp_catalog_get_priv(panel); + base = catalog->io->ctrl_io.base; + + dp_write(base + DP_TOTAL_HOR_VER, panel->total); + dp_write(base + DP_START_HOR_VER_FROM_SYNC, panel->sync_start); + dp_write(base + DP_HSYNC_VSYNC_WIDTH_POLARITY, panel->width_blanking); + dp_write(base + DP_ACTIVE_HOR_VER, panel->dp_active); +end: + return 0; +} + +static void dp_catalog_audio_init(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + static u32 sdp_map[][DP_AUDIO_SDP_HEADER_MAX] = { + { + MMSS_DP_AUDIO_STREAM_0, + MMSS_DP_AUDIO_STREAM_1, + MMSS_DP_AUDIO_STREAM_1, + }, + { + MMSS_DP_AUDIO_TIMESTAMP_0, + MMSS_DP_AUDIO_TIMESTAMP_1, + MMSS_DP_AUDIO_TIMESTAMP_1, + }, + { + MMSS_DP_AUDIO_INFOFRAME_0, + MMSS_DP_AUDIO_INFOFRAME_1, + MMSS_DP_AUDIO_INFOFRAME_1, + }, + { + MMSS_DP_AUDIO_COPYMANAGEMENT_0, + MMSS_DP_AUDIO_COPYMANAGEMENT_1, + MMSS_DP_AUDIO_COPYMANAGEMENT_1, + }, + { + MMSS_DP_AUDIO_ISRC_0, + MMSS_DP_AUDIO_ISRC_1, + MMSS_DP_AUDIO_ISRC_1, + }, + }; + + if (!audio) + return; + + dp_catalog_get_priv(audio); + + catalog->audio_map = sdp_map; +} + +static void dp_catalog_audio_config_sdp(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + u32 sdp_cfg = 0; + u32 sdp_cfg2 = 0; + + if (!audio) + return; + + dp_catalog_get_priv(audio); + base = catalog->io->ctrl_io.base; + + /* AUDIO_TIMESTAMP_SDP_EN */ + sdp_cfg |= BIT(1); + /* AUDIO_STREAM_SDP_EN */ + sdp_cfg |= BIT(2); + /* AUDIO_COPY_MANAGEMENT_SDP_EN */ + sdp_cfg |= BIT(5); + /* AUDIO_ISRC_SDP_EN */ + sdp_cfg |= BIT(6); + /* AUDIO_INFOFRAME_SDP_EN */ + sdp_cfg |= BIT(20); + + pr_debug("sdp_cfg = 0x%x\n", sdp_cfg); + dp_write(base + MMSS_DP_SDP_CFG, sdp_cfg); + + sdp_cfg2 = dp_read(base + MMSS_DP_SDP_CFG2); + /* IFRM_REGSRC -> Do not use reg values */ + sdp_cfg2 &= ~BIT(0); + /* AUDIO_STREAM_HB3_REGSRC-> Do not use reg values */ + sdp_cfg2 &= ~BIT(1); + + pr_debug("sdp_cfg2 = 0x%x\n", sdp_cfg2); + dp_write(base + MMSS_DP_SDP_CFG2, sdp_cfg2); +} + +static void dp_catalog_audio_get_header(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + u32 (*sdp_map)[DP_AUDIO_SDP_HEADER_MAX]; + void __iomem *base; + enum dp_catalog_audio_sdp_type sdp; + enum dp_catalog_audio_header_type header; + + if (!audio) + return; + + dp_catalog_get_priv(audio); + + base = catalog->io->ctrl_io.base; + sdp_map = catalog->audio_map; + sdp = audio->sdp_type; + header = audio->sdp_header; + + audio->data = dp_read(base + sdp_map[sdp][header]); +} + +static void dp_catalog_audio_set_header(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + u32 (*sdp_map)[DP_AUDIO_SDP_HEADER_MAX]; + void __iomem *base; + enum dp_catalog_audio_sdp_type sdp; + enum dp_catalog_audio_header_type header; + u32 data; + + if (!audio) + return; + + dp_catalog_get_priv(audio); + + base = catalog->io->ctrl_io.base; + sdp_map = catalog->audio_map; + sdp = audio->sdp_type; + header = audio->sdp_header; + data = audio->data; + + dp_write(base + sdp_map[sdp][header], data); +} + +static void dp_catalog_audio_config_acr(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + u32 acr_ctrl, select; + + dp_catalog_get_priv(audio); + + select = audio->data; + base = catalog->io->ctrl_io.base; + + acr_ctrl = select << 4 | BIT(31) | BIT(8) | BIT(14); + + pr_debug("select = 0x%x, acr_ctrl = 0x%x\n", select, acr_ctrl); + + dp_write(base + MMSS_DP_AUDIO_ACR_CTRL, acr_ctrl); +} + +static void dp_catalog_audio_safe_to_exit_level(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + u32 mainlink_levels, safe_to_exit_level; + + dp_catalog_get_priv(audio); + + base = catalog->io->ctrl_io.base; + safe_to_exit_level = audio->data; + + mainlink_levels = dp_read(base + DP_MAINLINK_LEVELS); + mainlink_levels &= 0xFE0; + mainlink_levels |= safe_to_exit_level; + + pr_debug("mainlink_level = 0x%x, safe_to_exit_level = 0x%x\n", + mainlink_levels, safe_to_exit_level); + + dp_write(base + DP_MAINLINK_LEVELS, mainlink_levels); +} + +static void dp_catalog_audio_enable(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + void __iomem *base; + bool enable; + u32 audio_ctrl; + + dp_catalog_get_priv(audio); + + base = catalog->io->ctrl_io.base; + enable = !!audio->data; + + audio_ctrl = dp_read(base + MMSS_DP_AUDIO_CFG); + + if (enable) + audio_ctrl |= BIT(0); + else + audio_ctrl &= ~BIT(0); + + pr_debug("dp_audio_cfg = 0x%x\n", audio_ctrl); + dp_write(base + MMSS_DP_AUDIO_CFG, audio_ctrl); + + /* make sure audio engine is disabled */ + wmb(); +} + +struct dp_catalog *dp_catalog_get(struct device *dev, struct dp_io *io) +{ + int rc = 0; + struct dp_catalog *dp_catalog; + struct dp_catalog_private *catalog; + struct dp_catalog_aux aux = { + .read_data = dp_catalog_aux_read_data, + .write_data = dp_catalog_aux_write_data, + .write_trans = dp_catalog_aux_write_trans, + .clear_trans = dp_catalog_aux_clear_trans, + .reset = dp_catalog_aux_reset, + .update_aux_cfg = dp_catalog_aux_update_cfg, + .enable = dp_catalog_aux_enable, + .setup = dp_catalog_aux_setup, + .get_irq = dp_catalog_aux_get_irq, + }; + struct dp_catalog_ctrl ctrl = { + .state_ctrl = dp_catalog_ctrl_state_ctrl, + .config_ctrl = dp_catalog_ctrl_config_ctrl, + .lane_mapping = dp_catalog_ctrl_lane_mapping, + .mainlink_ctrl = dp_catalog_ctrl_mainlink_ctrl, + .config_misc = dp_catalog_ctrl_config_misc, + .config_msa = dp_catalog_ctrl_config_msa, + .set_pattern = dp_catalog_ctrl_set_pattern, + .reset = dp_catalog_ctrl_reset, + .usb_reset = dp_catalog_ctrl_usb_reset, + .mainlink_ready = dp_catalog_ctrl_mainlink_ready, + .enable_irq = dp_catalog_ctrl_enable_irq, + .hpd_config = dp_catalog_ctrl_hpd_config, + .phy_reset = dp_catalog_ctrl_phy_reset, + .phy_lane_cfg = dp_catalog_ctrl_phy_lane_cfg, + .update_vx_px = dp_catalog_ctrl_update_vx_px, + .get_interrupt = dp_catalog_ctrl_get_interrupt, + .config_hdr = dp_catalog_ctrl_config_hdr, + .update_transfer_unit = dp_catalog_ctrl_update_transfer_unit, + .read_hdcp_status = dp_catalog_ctrl_read_hdcp_status, + .send_phy_pattern = dp_catalog_ctrl_send_phy_pattern, + .read_phy_pattern = dp_catalog_ctrl_read_phy_pattern, + }; + struct dp_catalog_audio audio = { + .init = dp_catalog_audio_init, + .config_acr = dp_catalog_audio_config_acr, + .enable = dp_catalog_audio_enable, + .config_sdp = dp_catalog_audio_config_sdp, + .set_header = dp_catalog_audio_set_header, + .get_header = dp_catalog_audio_get_header, + .safe_to_exit_level = dp_catalog_audio_safe_to_exit_level, + }; + struct dp_catalog_panel panel = { + .timing_cfg = dp_catalog_panel_timing_cfg, + }; + + if (!io) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + catalog = devm_kzalloc(dev, sizeof(*catalog), GFP_KERNEL); + if (!catalog) { + rc = -ENOMEM; + goto error; + } + + catalog->dev = dev; + catalog->io = io; + + dp_catalog = &catalog->dp_catalog; + + dp_catalog->aux = aux; + dp_catalog->ctrl = ctrl; + dp_catalog->audio = audio; + dp_catalog->panel = panel; + + return dp_catalog; +error: + return ERR_PTR(rc); +} + +void dp_catalog_put(struct dp_catalog *dp_catalog) +{ + struct dp_catalog_private *catalog; + + if (!dp_catalog) + return; + + catalog = container_of(dp_catalog, struct dp_catalog_private, + dp_catalog); + + devm_kfree(catalog->dev, catalog); +} diff --git a/drivers/gpu/drm/msm/dp/dp_catalog.h b/drivers/gpu/drm/msm/dp/dp_catalog.h new file mode 100644 index 0000000000000000000000000000000000000000..c1b7a7ef889fe43ff1f4eb647c207d15a20b49d5 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_catalog.h @@ -0,0 +1,163 @@ +/* + * 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_CATALOG_H_ +#define _DP_CATALOG_H_ + +#include "dp_parser.h" + +/* interrupts */ +#define DP_INTR_HPD BIT(0) +#define DP_INTR_AUX_I2C_DONE BIT(3) +#define DP_INTR_WRONG_ADDR BIT(6) +#define DP_INTR_TIMEOUT BIT(9) +#define DP_INTR_NACK_DEFER BIT(12) +#define DP_INTR_WRONG_DATA_CNT BIT(15) +#define DP_INTR_I2C_NACK BIT(18) +#define DP_INTR_I2C_DEFER BIT(21) +#define DP_INTR_PLL_UNLOCKED BIT(24) +#define DP_INTR_AUX_ERROR BIT(27) + +#define DP_INTR_READY_FOR_VIDEO BIT(0) +#define DP_INTR_IDLE_PATTERN_SENT BIT(3) +#define DP_INTR_FRAME_END BIT(6) +#define DP_INTR_CRC_UPDATED BIT(9) + +#define HDR_PRIMARIES_COUNT 3 + +struct dp_catalog_hdr_data { + u32 vsc_hdr_byte0; + u32 vsc_hdr_byte1; + u32 vsc_hdr_byte2; + u32 vsc_hdr_byte3; + u32 pkt_payload; + + u32 bpc; + + u32 version; + u32 length; + u32 eotf; + u32 descriptor_id; + + u32 display_primaries_x[HDR_PRIMARIES_COUNT]; + u32 display_primaries_y[HDR_PRIMARIES_COUNT]; + u32 white_point_x; + u32 white_point_y; + u32 max_luminance; + u32 min_luminance; + u32 max_content_light_level; + u32 max_average_light_level; +}; + +struct dp_catalog_aux { + u32 data; + u32 isr; + + u32 (*read_data)(struct dp_catalog_aux *aux); + int (*write_data)(struct dp_catalog_aux *aux); + int (*write_trans)(struct dp_catalog_aux *aux); + int (*clear_trans)(struct dp_catalog_aux *aux, bool read); + void (*reset)(struct dp_catalog_aux *aux); + void (*enable)(struct dp_catalog_aux *aux, bool enable); + void (*update_aux_cfg)(struct dp_catalog_aux *aux, + struct dp_aux_cfg *cfg, enum dp_phy_aux_config_type type); + void (*setup)(struct dp_catalog_aux *aux, + struct dp_aux_cfg *aux_cfg); + void (*get_irq)(struct dp_catalog_aux *aux, bool cmd_busy); +}; + +struct dp_catalog_ctrl { + u32 dp_tu; + u32 valid_boundary; + u32 valid_boundary2; + u32 isr; + struct dp_catalog_hdr_data hdr_data; + + void (*state_ctrl)(struct dp_catalog_ctrl *ctrl, u32 state); + void (*config_ctrl)(struct dp_catalog_ctrl *ctrl, u32 config); + void (*lane_mapping)(struct dp_catalog_ctrl *ctrl); + void (*mainlink_ctrl)(struct dp_catalog_ctrl *ctrl, bool enable); + void (*config_misc)(struct dp_catalog_ctrl *ctrl, u32 cc, u32 tb); + void (*config_msa)(struct dp_catalog_ctrl *ctrl, u32 rate, + u32 stream_rate_khz, bool fixed_nvid); + void (*set_pattern)(struct dp_catalog_ctrl *ctrl, u32 pattern); + void (*reset)(struct dp_catalog_ctrl *ctrl); + void (*usb_reset)(struct dp_catalog_ctrl *ctrl, bool flip); + bool (*mainlink_ready)(struct dp_catalog_ctrl *ctrl); + void (*enable_irq)(struct dp_catalog_ctrl *ctrl, bool enable); + void (*hpd_config)(struct dp_catalog_ctrl *ctrl, bool enable); + void (*phy_reset)(struct dp_catalog_ctrl *ctrl); + void (*phy_lane_cfg)(struct dp_catalog_ctrl *ctrl, bool flipped, + u8 lane_cnt); + void (*update_vx_px)(struct dp_catalog_ctrl *ctrl, u8 v_level, + u8 p_level); + void (*get_interrupt)(struct dp_catalog_ctrl *ctrl); + void (*config_hdr)(struct dp_catalog_ctrl *ctrl); + void (*update_transfer_unit)(struct dp_catalog_ctrl *ctrl); + u32 (*read_hdcp_status)(struct dp_catalog_ctrl *ctrl); + void (*send_phy_pattern)(struct dp_catalog_ctrl *ctrl, + u32 pattern); + u32 (*read_phy_pattern)(struct dp_catalog_ctrl *ctrl); +}; + +enum dp_catalog_audio_sdp_type { + DP_AUDIO_SDP_STREAM, + DP_AUDIO_SDP_TIMESTAMP, + DP_AUDIO_SDP_INFOFRAME, + DP_AUDIO_SDP_COPYMANAGEMENT, + DP_AUDIO_SDP_ISRC, + DP_AUDIO_SDP_MAX, +}; + +enum dp_catalog_audio_header_type { + DP_AUDIO_SDP_HEADER_1, + DP_AUDIO_SDP_HEADER_2, + DP_AUDIO_SDP_HEADER_3, + DP_AUDIO_SDP_HEADER_MAX, +}; + +struct dp_catalog_audio { + enum dp_catalog_audio_sdp_type sdp_type; + enum dp_catalog_audio_header_type sdp_header; + u32 data; + + void (*init)(struct dp_catalog_audio *audio); + void (*enable)(struct dp_catalog_audio *audio); + void (*config_acr)(struct dp_catalog_audio *audio); + void (*config_sdp)(struct dp_catalog_audio *audio); + void (*set_header)(struct dp_catalog_audio *audio); + void (*get_header)(struct dp_catalog_audio *audio); + void (*safe_to_exit_level)(struct dp_catalog_audio *audio); +}; + +struct dp_catalog_panel { + u32 total; + u32 sync_start; + u32 width_blanking; + u32 dp_active; + + int (*timing_cfg)(struct dp_catalog_panel *panel); +}; + +struct dp_catalog { + struct dp_catalog_aux aux; + struct dp_catalog_ctrl ctrl; + struct dp_catalog_audio audio; + struct dp_catalog_panel panel; +}; + +struct dp_catalog *dp_catalog_get(struct device *dev, struct dp_io *io); +void dp_catalog_put(struct dp_catalog *catalog); + +#endif /* _DP_CATALOG_H_ */ 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 0000000000000000000000000000000000000000..13ca6b24c1b0aa9f216ff42f9731587bc0697a17 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -0,0 +1,1474 @@ +/* + * 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 +#include +#include + +#include "dp_ctrl.h" + +#define DP_KHZ_TO_HZ 1000 + +#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) + +#define MR_LINK_TRAINING1 0x8 +#define MR_LINK_SYMBOL_ERM 0x80 +#define MR_LINK_PRBS7 0x100 +#define MR_LINK_CUSTOM80 0x200 + +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; + + bool orientation; + atomic_t aborted; + + 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_abort(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); + + atomic_set(&ctrl->aborted, 1); +} + +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); + + 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; + u8 *dpcd = ctrl->panel->dpcd; + + config |= (2 << 13); /* Default-> LSCLK DIV: 1/4 LCLK */ + config |= (0 << 11); /* RGB */ + + /* Scrambler reset enable */ + if (dpcd[DP_EDP_CONFIGURATION_CAP] & DP_ALTERNATE_SCRAMBLER_RESET_CAP) + config |= (1 << 10); + + tbd = ctrl->link->get_test_bits_depth(ctrl->link, + ctrl->panel->pinfo.bpp); + + if (tbd == DP_TEST_BIT_DEPTH_UNKNOWN) + tbd = DP_TEST_BIT_DEPTH_8; + + config |= tbd << 8; + + /* Num of Lanes */ + config |= ((ctrl->link->link_params.lane_count - 1) << 4); + + if (drm_dp_enhanced_frame_cap(dpcd)) + 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->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; + 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; + + ln_cnt = ctrl->link->link_params.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 = drm_dp_bw_code_to_link_rate( + ctrl->link->link_params.bw_code) * DP_KHZ_TO_HZ; + + 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; + + 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); + } + + pre_emphasis_level <<= 3; + + for (i = 0; i < 4; i++) + buf[i] = voltage_level | pre_emphasis_level | max_level_reached; + + pr_debug("sink: p|v=0x%x\n", voltage_level | pre_emphasis_level); + return drm_dp_dpcd_write(ctrl->aux->drm_aux, 0x103, buf, 4); +} + +static void dp_ctrl_update_vx_px(struct dp_ctrl_private *ctrl) +{ + struct dp_link *link = ctrl->link; + + ctrl->catalog->update_vx_px(ctrl->catalog, + link->phy_params.v_level, link->phy_params.p_level); + + dp_ctrl_update_sink_vx_px(ctrl, link->phy_params.v_level, + link->phy_params.p_level); +} + +static void dp_ctrl_train_pattern_set(struct dp_ctrl_private *ctrl, + u8 pattern) +{ + u8 buf[4]; + + pr_debug("sink: pattern=%x\n", pattern); + + buf[0] = pattern; + drm_dp_dpcd_write(ctrl->aux->drm_aux, DP_TRAINING_PATTERN_SET, buf, 1); +} + +static int dp_ctrl_read_link_status(struct dp_ctrl_private *ctrl, + u8 *link_status) +{ + int ret = 0, len; + u32 const offset = DP_LANE_ALIGN_STATUS_UPDATED - DP_LANE0_1_STATUS; + u32 link_status_read_max_retries = 100; + + while (--link_status_read_max_retries) { + len = drm_dp_dpcd_read_link_status(ctrl->aux->drm_aux, + link_status); + if (len != DP_LINK_STATUS_SIZE) { + pr_err("DP link status read failed, err: %d\n", len); + ret = len; + break; + } + + if (!(link_status[offset] & DP_LINK_STATUS_UPDATED)) + break; + } + + return ret; +} + +static int dp_ctrl_link_train_1(struct dp_ctrl_private *ctrl) +{ + int tries, old_v_level, ret = 0; + u8 link_status[DP_LINK_STATUS_SIZE]; + 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_train_pattern_set(ctrl, DP_TRAINING_PATTERN_1 | + DP_LINK_SCRAMBLING_DISABLE); /* train_1 */ + dp_ctrl_update_vx_px(ctrl); + + tries = 0; + old_v_level = ctrl->link->phy_params.v_level; + while (1) { + drm_dp_link_train_clock_recovery_delay(ctrl->panel->dpcd); + + ret = dp_ctrl_read_link_status(ctrl, link_status); + if (ret) + break; + + if (drm_dp_clock_recovery_ok(link_status, + ctrl->link->link_params.lane_count)) { + break; + } + + if (ctrl->link->phy_params.v_level == DP_LINK_VOLTAGE_MAX) { + pr_err_ratelimited("max v_level reached\n"); + ret = -EAGAIN; + break; + } + + if (old_v_level == ctrl->link->phy_params.v_level) { + tries++; + if (tries >= maximum_retries) { + pr_err("max tries reached\n"); + ret = -ETIMEDOUT; + break; + } + } else { + tries = 0; + old_v_level = ctrl->link->phy_params.v_level; + } + + pr_debug("clock recovery not done, adjusting vx px\n"); + + ctrl->link->adjust_levels(ctrl->link, link_status); + 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_params.bw_code) { + case DP_LINK_BW_8_1: + ctrl->link->link_params.bw_code = DP_LINK_BW_5_4; + break; + case DP_LINK_BW_5_4: + ctrl->link->link_params.bw_code = DP_LINK_BW_2_7; + break; + case DP_LINK_BW_2_7: + case DP_LINK_BW_1_62: + default: + ctrl->link->link_params.bw_code = DP_LINK_BW_1_62; + break; + }; + + pr_debug("new bw code=0x%x\n", ctrl->link->link_params.bw_code); + + return ret; +} + +static void dp_ctrl_clear_training_pattern(struct dp_ctrl_private *ctrl) +{ + dp_ctrl_train_pattern_set(ctrl, 0); + drm_dp_link_train_channel_eq_delay(ctrl->panel->dpcd); +} + +static int dp_ctrl_link_training_2(struct dp_ctrl_private *ctrl) +{ + int tries = 0, ret = 0; + char pattern; + int const maximum_retries = 5; + u8 link_status[DP_LINK_STATUS_SIZE]; + + dp_ctrl_state_ctrl(ctrl, 0); + /* Make sure to clear the current pattern before starting a new one */ + wmb(); + + if (drm_dp_tps3_supported(ctrl->panel->dpcd)) + pattern = DP_TRAINING_PATTERN_3; + else + pattern = DP_TRAINING_PATTERN_2; + + dp_ctrl_update_vx_px(ctrl); + ctrl->catalog->set_pattern(ctrl->catalog, pattern); + dp_ctrl_train_pattern_set(ctrl, pattern | DP_RECOVERED_CLOCK_OUT_EN); + + do { + drm_dp_link_train_channel_eq_delay(ctrl->panel->dpcd); + + ret = dp_ctrl_read_link_status(ctrl, link_status); + if (ret) + break; + + if (drm_dp_channel_eq_ok(link_status, + ctrl->link->link_params.lane_count)) + break; + + if (tries > maximum_retries) { + ret = -ETIMEDOUT; + break; + } + tries++; + + ctrl->link->adjust_levels(ctrl->link, link_status); + dp_ctrl_update_vx_px(ctrl); + } while (1); + + return ret; +} + +static int dp_ctrl_link_train(struct dp_ctrl_private *ctrl) +{ + int ret = 0; + u8 encoding = 0x1; + struct drm_dp_link link_info = {0}; + + ctrl->link->phy_params.p_level = 0; + ctrl->link->phy_params.v_level = 0; + + dp_ctrl_config_ctrl(ctrl); + + link_info.num_lanes = ctrl->link->link_params.lane_count; + link_info.rate = drm_dp_bw_code_to_link_rate( + ctrl->link->link_params.bw_code); + link_info.capabilities = ctrl->panel->link_info.capabilities; + + drm_dp_link_configure(ctrl->aux->drm_aux, &link_info); + drm_dp_dpcd_write(ctrl->aux->drm_aux, DP_MAIN_LINK_CHANNEL_CODING_SET, + &encoding, 1); + + ret = dp_ctrl_link_train_1(ctrl); + if (ret) { + pr_err("link training #1 failed\n"); + goto end; + } + + /* print success info as this is a result of user initiated action */ + pr_info("link training #1 successful\n"); + + ret = dp_ctrl_link_training_2(ctrl); + if (ret) { + pr_err("link training #2 failed\n"); + goto end; + } + + /* print success info as this is a result of user initiated action */ + pr_debug("link training #2 successful\n"); + +end: + dp_ctrl_state_ctrl(ctrl, 0); + /* Make sure to clear the current pattern before starting a new one */ + wmb(); + + 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); + + ret = ctrl->link->psm_config(ctrl->link, + &ctrl->panel->link_info, false); + if (ret) + goto end; + + if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) + 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++; + } + + pr_debug("setting rate=%d on clk=%s\n", rate, name); + + 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", + drm_dp_bw_code_to_link_rate(ctrl->link->link_params.bw_code)); + + 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); + + ctrl->orientation = flip; + catalog = ctrl->catalog; + + catalog->usb_reset(ctrl->catalog, flip); + catalog->phy_reset(ctrl->catalog); + catalog->enable_irq(ctrl->catalog, 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); + + ctrl->catalog->enable_irq(ctrl->catalog, false); + + pr_debug("Host deinitialized successfully\n"); +} + +static bool dp_ctrl_use_fixed_nvid(struct dp_ctrl_private *ctrl) +{ + u8 *dpcd = ctrl->panel->dpcd; + + /* + * For better interop experience, used a fixed NVID=0x8000 + * whenever connected to a VGA dongle downstream. + */ + if (dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT) { + u8 type = dpcd[DP_DOWNSTREAMPORT_PRESENT] & + DP_DWN_STRM_PORT_TYPE_MASK; + if (type == DP_DWN_STRM_PORT_TYPE_ANALOG) + return true; + } + + return false; +} + +static int dp_ctrl_link_maintenance(struct dp_ctrl_private *ctrl) +{ + int ret = 0; + + ctrl->dp_ctrl.push_idle(&ctrl->dp_ctrl); + ctrl->dp_ctrl.reset(&ctrl->dp_ctrl); + + ctrl->pixel_rate = ctrl->panel->pinfo.pixel_clk_khz; + + do { + if (ret == -EAGAIN) { + /* try with lower link rate */ + dp_ctrl_link_rate_down_shift(ctrl); + + ctrl->catalog->mainlink_ctrl(ctrl->catalog, false); + } + + ctrl->catalog->phy_lane_cfg(ctrl->catalog, + ctrl->orientation, ctrl->link->link_params.lane_count); + + /* + * Disable and re-enable the mainlink clock since the + * link clock might have been adjusted as part of the + * link maintenance. + */ + dp_ctrl_disable_mainlink_clocks(ctrl); + + ret = dp_ctrl_enable_mainlink_clocks(ctrl); + if (ret) + continue; + + dp_ctrl_configure_source_params(ctrl); + + ctrl->catalog->config_msa(ctrl->catalog, + drm_dp_bw_code_to_link_rate( + ctrl->link->link_params.bw_code), + ctrl->pixel_rate, dp_ctrl_use_fixed_nvid(ctrl)); + + reinit_completion(&ctrl->idle_comp); + + ret = dp_ctrl_setup_main_link(ctrl, true); + } while (ret == -EAGAIN); + + return ret; +} + +static void dp_ctrl_process_phy_test_request(struct dp_ctrl_private *ctrl) +{ + int ret = 0; + + if (!ctrl->link->phy_params.phy_test_pattern_sel) { + pr_debug("no test pattern selected by sink\n"); + return; + } + + pr_debug("start\n"); + + ctrl->dp_ctrl.push_idle(&ctrl->dp_ctrl); + /* + * The global reset will need DP link ralated clocks to be + * running. Add the global reset just before disabling the + * link clocks and core clocks. + */ + ctrl->dp_ctrl.reset(&ctrl->dp_ctrl); + ctrl->dp_ctrl.off(&ctrl->dp_ctrl); + + ret = ctrl->dp_ctrl.on(&ctrl->dp_ctrl); + if (ret) + pr_err("failed to enable DP controller\n"); + + pr_debug("end\n"); +} + +static void dp_ctrl_send_phy_test_pattern(struct dp_ctrl_private *ctrl) +{ + bool success = false; + u32 pattern_sent = 0x0; + u32 pattern_requested = ctrl->link->phy_params.phy_test_pattern_sel; + + pr_debug("request: %s\n", + dp_link_get_phy_test_pattern(pattern_requested)); + + ctrl->catalog->update_vx_px(ctrl->catalog, + ctrl->link->phy_params.v_level, + ctrl->link->phy_params.p_level); + ctrl->catalog->send_phy_pattern(ctrl->catalog, pattern_requested); + ctrl->link->send_test_response(ctrl->link); + + pattern_sent = ctrl->catalog->read_phy_pattern(ctrl->catalog); + + switch (pattern_sent) { + case MR_LINK_TRAINING1: + if (pattern_requested == + DP_TEST_PHY_PATTERN_D10_2_NO_SCRAMBLING) + success = true; + break; + case MR_LINK_SYMBOL_ERM: + if ((pattern_requested == + DP_TEST_PHY_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT) + || (pattern_requested == + DP_TEST_PHY_PATTERN_HBR2_CTS_EYE_PATTERN)) + success = true; + break; + case MR_LINK_PRBS7: + if (pattern_requested == DP_TEST_PHY_PATTERN_PRBS7) + success = true; + break; + case MR_LINK_CUSTOM80: + if (pattern_requested == + DP_TEST_PHY_PATTERN_80_BIT_CUSTOM_PATTERN) + success = true; + break; + default: + success = false; + return; + } + + pr_debug("%s: %s\n", success ? "success" : "failed", + dp_link_get_phy_test_pattern(pattern_requested)); +} + +static void dp_ctrl_handle_sink_request(struct dp_ctrl *dp_ctrl) +{ + struct dp_ctrl_private *ctrl; + u32 sink_request = 0x0; + + if (!dp_ctrl) { + pr_err("invalid input\n"); + return; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + sink_request = ctrl->link->sink_request; + + if (sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { + pr_info("PHY_TEST_PATTERN request\n"); + dp_ctrl_process_phy_test_request(ctrl); + } + + if (sink_request & DP_LINK_STATUS_UPDATED) + dp_ctrl_link_maintenance(ctrl); + + if (sink_request & DP_TEST_LINK_TRAINING) { + ctrl->link->send_test_response(ctrl->link); + dp_ctrl_link_maintenance(ctrl); + } +} + +static void dp_ctrl_reset(struct dp_ctrl *dp_ctrl) +{ + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl) { + pr_err("invalid params\n"); + return; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + ctrl->catalog->reset(ctrl->catalog); +} + +static int dp_ctrl_on(struct dp_ctrl *dp_ctrl) +{ + int rc = 0; + struct dp_ctrl_private *ctrl; + u32 rate = 0; + u32 link_train_max_retries = 100; + u32 const phy_cts_pixel_clk_khz = 148500; + + if (!dp_ctrl) { + rc = -EINVAL; + goto end; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + atomic_set(&ctrl->aborted, 0); + rate = ctrl->panel->link_info.rate; + + ctrl->power->clk_enable(ctrl->power, DP_CORE_PM, true); + ctrl->catalog->hpd_config(ctrl->catalog, true); + + if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { + pr_debug("using phy test link parameters\n"); + if (!ctrl->panel->pinfo.pixel_clk_khz) + ctrl->pixel_rate = phy_cts_pixel_clk_khz; + } else { + ctrl->link->link_params.bw_code = + drm_dp_link_rate_to_bw_code(rate); + ctrl->link->link_params.lane_count = + ctrl->panel->link_info.num_lanes; + ctrl->pixel_rate = ctrl->panel->pinfo.pixel_clk_khz; + } + + pr_debug("bw_code=%d, lane_count=%d, pixel_rate=%d\n", + ctrl->link->link_params.bw_code, + ctrl->link->link_params.lane_count, ctrl->pixel_rate); + + ctrl->catalog->phy_lane_cfg(ctrl->catalog, + ctrl->orientation, ctrl->link->link_params.lane_count); + + rc = dp_ctrl_enable_mainlink_clocks(ctrl); + if (rc) + goto end; + + reinit_completion(&ctrl->idle_comp); + + dp_ctrl_configure_source_params(ctrl); + + while (--link_train_max_retries && !atomic_read(&ctrl->aborted)) { + ctrl->catalog->config_msa(ctrl->catalog, + drm_dp_bw_code_to_link_rate( + ctrl->link->link_params.bw_code), + ctrl->pixel_rate, dp_ctrl_use_fixed_nvid(ctrl)); + + rc = dp_ctrl_setup_main_link(ctrl, true); + if (!rc) + break; + + /* try with lower link rate */ + dp_ctrl_link_rate_down_shift(ctrl); + + ctrl->catalog->mainlink_ctrl(ctrl->catalog, false); + + dp_ctrl_disable_mainlink_clocks(ctrl); + /* hw recommended delay before re-enabling clocks */ + msleep(20); + + dp_ctrl_enable_mainlink_clocks(ctrl); + } + + if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) + dp_ctrl_send_phy_test_pattern(ctrl); + + pr_debug("End-\n"); + +end: + return rc; +} + +static void dp_ctrl_off(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); + + ctrl->catalog->mainlink_ctrl(ctrl->catalog, false); + ctrl->catalog->reset(ctrl->catalog); + + /* Make sure DP is disabled before clk disable */ + wmb(); + + dp_ctrl_disable_mainlink_clocks(ctrl); + + pr_debug("DP off done\n"); +} + +static void dp_ctrl_isr(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); + + ctrl->catalog->get_interrupt(ctrl->catalog); + + if (ctrl->catalog->isr & DP_CTRL_INTR_READY_FOR_VIDEO) + dp_ctrl_video_ready(ctrl); + + if (ctrl->catalog->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); + + /* 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->abort = dp_ctrl_abort; + dp_ctrl->isr = dp_ctrl_isr; + dp_ctrl->reset = dp_ctrl_reset; + dp_ctrl->handle_sink_request = dp_ctrl_handle_sink_request; + + 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 0000000000000000000000000000000000000000..d6d10ed0fa55105ed58998bb48a9d1b3cf4ff0f7 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -0,0 +1,50 @@ +/* + * 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); + void (*off)(struct dp_ctrl *dp_ctrl); + void (*reset)(struct dp_ctrl *dp_ctrl); + void (*push_idle)(struct dp_ctrl *dp_ctrl); + void (*abort)(struct dp_ctrl *dp_ctrl); + void (*isr)(struct dp_ctrl *dp_ctrl); + void (*handle_sink_request)(struct dp_ctrl *dp_ctrl); +}; + +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_debug.c b/drivers/gpu/drm/msm/dp/dp_debug.c new file mode 100644 index 0000000000000000000000000000000000000000..d0512e6fea4592430928bc4830e62be4b62f3df7 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_debug.c @@ -0,0 +1,503 @@ +/* + * 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 + +#include "dp_parser.h" +#include "dp_power.h" +#include "dp_catalog.h" +#include "dp_aux.h" +#include "dp_ctrl.h" +#include "dp_debug.h" +#include "drm_connector.h" +#include "dp_display.h" + +#define DEBUG_NAME "drm_dp" + +struct dp_debug_private { + struct dentry *root; + + struct dp_usbpd *usbpd; + struct dp_link *link; + struct dp_panel *panel; + struct drm_connector **connector; + struct device *dev; + + struct dp_debug dp_debug; +}; + +static ssize_t dp_debug_write_hpd(struct file *file, + const char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + char buf[SZ_8]; + size_t len = 0; + int hpd; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + /* Leave room for termination char */ + len = min_t(size_t, count, SZ_8 - 1); + if (copy_from_user(buf, user_buff, len)) + goto end; + + buf[len] = '\0'; + + if (kstrtoint(buf, 10, &hpd) != 0) + goto end; + + debug->usbpd->connect(debug->usbpd, hpd); +end: + return -len; +} + +static ssize_t dp_debug_write_edid_modes(struct file *file, + const char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + char buf[SZ_32]; + size_t len = 0; + int hdisplay = 0, vdisplay = 0, vrefresh = 0; + + if (!debug) + return -ENODEV; + + if (*ppos) + goto end; + + /* Leave room for termination char */ + len = min_t(size_t, count, SZ_32 - 1); + if (copy_from_user(buf, user_buff, len)) + goto clear; + + buf[len] = '\0'; + + if (sscanf(buf, "%d %d %d", &hdisplay, &vdisplay, &vrefresh) != 3) + goto clear; + + if (!hdisplay || !vdisplay || !vrefresh) + goto clear; + + debug->dp_debug.debug_en = true; + debug->dp_debug.hdisplay = hdisplay; + debug->dp_debug.vdisplay = vdisplay; + debug->dp_debug.vrefresh = vrefresh; + goto end; +clear: + pr_debug("clearing debug modes\n"); + debug->dp_debug.debug_en = false; +end: + return len; +} + +static ssize_t dp_debug_read_connected(struct file *file, + char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + char buf[SZ_8]; + u32 len = 0; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + len += snprintf(buf, SZ_8, "%d\n", debug->usbpd->hpd_high); + + if (copy_to_user(user_buff, buf, len)) + return -EFAULT; + + *ppos += len; + return len; +} + +static ssize_t dp_debug_read_edid_modes(struct file *file, + char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + char *buf; + u32 len = 0; + int rc = 0; + struct drm_connector *connector; + struct drm_display_mode *mode; + + if (!debug) { + pr_err("invalid data\n"); + rc = -ENODEV; + goto error; + } + + connector = *debug->connector; + + if (!connector) { + pr_err("connector is NULL\n"); + rc = -EINVAL; + goto error; + } + + if (*ppos) + goto error; + + buf = kzalloc(SZ_4K, GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; + goto error; + } + + list_for_each_entry(mode, &connector->modes, head) { + len += snprintf(buf + len, SZ_4K - len, + "%s %d %d %d %d %d %d %d %d %d 0x%x\n", + mode->name, mode->vrefresh, mode->hdisplay, + mode->hsync_start, mode->hsync_end, mode->htotal, + mode->vdisplay, mode->vsync_start, mode->vsync_end, + mode->vtotal, mode->flags); + } + + if (copy_to_user(user_buff, buf, len)) { + kfree(buf); + rc = -EFAULT; + goto error; + } + + *ppos += len; + kfree(buf); + + return len; +error: + return rc; +} + +static int dp_debug_check_buffer_overflow(int rc, int *max_size, int *len) +{ + if (rc >= *max_size) { + pr_err("buffer overflow\n"); + return -EINVAL; + } + *len += rc; + *max_size = SZ_4K - *len; + + return 0; +} + +static ssize_t dp_debug_read_info(struct file *file, char __user *user_buff, + size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + char *buf; + u32 len = 0, rc = 0; + u64 lclk = 0; + u32 max_size = SZ_4K; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + buf = kzalloc(SZ_4K, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + rc = snprintf(buf + len, max_size, "\tname = %s\n", DEBUG_NAME); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\tdp_panel\n\t\tmax_pclk_khz = %d\n", + debug->panel->max_pclk_khz); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\tdrm_dp_link\n\t\trate = %u\n", + debug->panel->link_info.rate); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tnum_lanes = %u\n", + debug->panel->link_info.num_lanes); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tcapabilities = %lu\n", + debug->panel->link_info.capabilities); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\tdp_panel_info:\n\t\tactive = %dx%d\n", + debug->panel->pinfo.h_active, + debug->panel->pinfo.v_active); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tback_porch = %dx%d\n", + debug->panel->pinfo.h_back_porch, + debug->panel->pinfo.v_back_porch); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tfront_porch = %dx%d\n", + debug->panel->pinfo.h_front_porch, + debug->panel->pinfo.v_front_porch); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tsync_width = %dx%d\n", + debug->panel->pinfo.h_sync_width, + debug->panel->pinfo.v_sync_width); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tactive_low = %dx%d\n", + debug->panel->pinfo.h_active_low, + debug->panel->pinfo.v_active_low); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\th_skew = %d\n", + debug->panel->pinfo.h_skew); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\trefresh rate = %d\n", + debug->panel->pinfo.refresh_rate); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tpixel clock khz = %d\n", + debug->panel->pinfo.pixel_clk_khz); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tbpp = %d\n", + debug->panel->pinfo.bpp); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + /* Link Information */ + rc = snprintf(buf + len, max_size, + "\tdp_link:\n\t\ttest_requested = %d\n", + debug->link->sink_request); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tlane_count = %d\n", debug->link->link_params.lane_count); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tbw_code = %d\n", debug->link->link_params.bw_code); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + lclk = drm_dp_bw_code_to_link_rate( + debug->link->link_params.bw_code) * 1000; + rc = snprintf(buf + len, max_size, + "\t\tlclk = %lld\n", lclk); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tv_level = %d\n", debug->link->phy_params.v_level); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\t\tp_level = %d\n", debug->link->phy_params.p_level); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + if (copy_to_user(user_buff, buf, len)) + goto error; + + *ppos += len; + + kfree(buf); + return len; +error: + kfree(buf); + return -EINVAL; +} + +static const struct file_operations dp_debug_fops = { + .open = simple_open, + .read = dp_debug_read_info, +}; + +static const struct file_operations edid_modes_fops = { + .open = simple_open, + .read = dp_debug_read_edid_modes, + .write = dp_debug_write_edid_modes, +}; + +static const struct file_operations hpd_fops = { + .open = simple_open, + .write = dp_debug_write_hpd, +}; + +static const struct file_operations connected_fops = { + .open = simple_open, + .read = dp_debug_read_connected, +}; + +static int dp_debug_init(struct dp_debug *dp_debug) +{ + int rc = 0; + struct dp_debug_private *debug = container_of(dp_debug, + struct dp_debug_private, dp_debug); + struct dentry *dir, *file, *edid_modes; + struct dentry *hpd, *connected; + struct dentry *root = debug->root; + + dir = debugfs_create_dir(DEBUG_NAME, NULL); + if (IS_ERR_OR_NULL(dir)) { + rc = PTR_ERR(dir); + pr_err("[%s] debugfs create dir failed, rc = %d\n", + DEBUG_NAME, rc); + goto error; + } + + file = debugfs_create_file("dp_debug", 0444, dir, + debug, &dp_debug_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs create file failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + edid_modes = debugfs_create_file("edid_modes", 0644, dir, + debug, &edid_modes_fops); + if (IS_ERR_OR_NULL(edid_modes)) { + rc = PTR_ERR(edid_modes); + pr_err("[%s] debugfs create edid_modes failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + hpd = debugfs_create_file("hpd", 0644, dir, + debug, &hpd_fops); + if (IS_ERR_OR_NULL(hpd)) { + rc = PTR_ERR(hpd); + pr_err("[%s] debugfs hpd failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + connected = debugfs_create_file("connected", 0444, dir, + debug, &connected_fops); + if (IS_ERR_OR_NULL(connected)) { + rc = PTR_ERR(connected); + pr_err("[%s] debugfs connected failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + root = dir; + return rc; +error_remove_dir: + debugfs_remove(dir); +error: + return rc; +} + +struct dp_debug *dp_debug_get(struct device *dev, struct dp_panel *panel, + struct dp_usbpd *usbpd, struct dp_link *link, + struct drm_connector **connector) +{ + int rc = 0; + struct dp_debug_private *debug; + struct dp_debug *dp_debug; + + if (!dev || !panel || !usbpd || !link) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + debug = devm_kzalloc(dev, sizeof(*debug), GFP_KERNEL); + if (!debug) { + rc = -ENOMEM; + goto error; + } + + debug->dp_debug.debug_en = false; + debug->usbpd = usbpd; + debug->link = link; + debug->panel = panel; + debug->dev = dev; + debug->connector = connector; + + dp_debug = &debug->dp_debug; + dp_debug->vdisplay = 0; + dp_debug->hdisplay = 0; + dp_debug->vrefresh = 0; + + rc = dp_debug_init(dp_debug); + if (rc) { + devm_kfree(dev, debug); + goto error; + } + + return dp_debug; +error: + return ERR_PTR(rc); +} + +static int dp_debug_deinit(struct dp_debug *dp_debug) +{ + struct dp_debug_private *debug; + + if (!dp_debug) + return -EINVAL; + + debug = container_of(dp_debug, struct dp_debug_private, dp_debug); + + debugfs_remove(debug->root); + + return 0; +} + +void dp_debug_put(struct dp_debug *dp_debug) +{ + struct dp_debug_private *debug; + + if (!dp_debug) + return; + + debug = container_of(dp_debug, struct dp_debug_private, dp_debug); + + dp_debug_deinit(dp_debug); + + devm_kfree(debug->dev, debug); +} diff --git a/drivers/gpu/drm/msm/dp/dp_debug.h b/drivers/gpu/drm/msm/dp/dp_debug.h new file mode 100644 index 0000000000000000000000000000000000000000..7fd533082cc29aaa2a38611ec21466f1dc6e63ef --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_debug.h @@ -0,0 +1,60 @@ +/* + * 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_DEBUG_H_ +#define _DP_DEBUG_H_ + +#include "dp_panel.h" +#include "dp_link.h" +#include "dp_usbpd.h" + +/** + * struct dp_debug + * @debug_en: specifies whether debug mode enabled + * @vdisplay: used to filter out vdisplay value + * @hdisplay: used to filter out hdisplay value + * @vrefresh: used to filter out vrefresh value + */ +struct dp_debug { + bool debug_en; + int vdisplay; + int hdisplay; + int vrefresh; +}; + +/** + * dp_debug_get() - configure and get the DisplayPlot debug module data + * + * @dev: device instance of the caller + * @panel: instance of panel module + * @usbpd: instance of usbpd module + * @link: instance of link module + * @connector: double pointer to display connector + * return: pointer to allocated debug module data + * + * This function sets up the debug module and provides a way + * for debugfs input to be communicated with existing modules + */ +struct dp_debug *dp_debug_get(struct device *dev, struct dp_panel *panel, + struct dp_usbpd *usbpd, struct dp_link *link, + struct drm_connector **connector); +/** + * dp_debug_put() + * + * Cleans up dp_debug instance + * + * @dp_debug: instance of dp_debug + */ +void dp_debug_put(struct dp_debug *dp_debug); +#endif /* _DP_DEBUG_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 0000000000000000000000000000000000000000..a0b6cef92fc34158d9100771d7d2ab4000734b05 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -0,0 +1,1263 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include + +#include "msm_drv.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_audio.h" +#include "dp_display.h" +#include "sde_hdcp.h" +#include "dp_debug.h" + +static struct dp_display *g_dp_display; +#define HPD_STRING_SIZE 30 + +struct dp_hdcp { + void *data; + struct sde_hdcp_ops *ops; + + void *hdcp1; + void *hdcp2; + + int enc_lvl; + + bool auth_state; + bool hdcp1_present; + bool hdcp2_present; + bool feature_enabled; +}; + +struct dp_display_private { + char *name; + int irq; + + /* state variables */ + bool core_initialized; + bool power_on; + bool hpd_irq_on; + bool audio_supported; + + struct platform_device *pdev; + struct dentry *root; + struct completion notification_comp; + + 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_audio *audio; + struct dp_debug *debug; + + struct dp_hdcp hdcp; + + struct dp_usbpd_cb usbpd_cb; + struct dp_display_mode mode; + struct dp_display dp_display; + + struct workqueue_struct *hdcp_workqueue; + struct delayed_work hdcp_cb_work; + struct mutex hdcp_mutex; + int hdcp_status; +}; + +static const struct of_device_id dp_dt_match[] = { + {.compatible = "qcom,dp-display"}, + {} +}; + +static inline bool dp_display_is_hdcp_enabled(struct dp_display_private *dp) +{ + return dp->hdcp.feature_enabled && + (dp->hdcp.hdcp1_present || dp->hdcp.hdcp2_present) && + dp->hdcp.ops; +} + +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 controller isr */ + dp->ctrl->isr(dp->ctrl); + + /* DP aux isr */ + dp->aux->isr(dp->aux); + + /* HDCP isr */ + if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->isr) { + if (dp->hdcp.ops->isr(dp->hdcp.data)) + pr_err("dp_hdcp_isr failed\n"); + } + + return IRQ_HANDLED; +} + +static void dp_display_hdcp_cb_work(struct work_struct *work) +{ + struct dp_display_private *dp; + struct delayed_work *dw = to_delayed_work(work); + struct sde_hdcp_ops *ops; + int rc = 0; + u32 hdcp_auth_state; + + dp = container_of(dw, struct dp_display_private, hdcp_cb_work); + + rc = dp->catalog->ctrl.read_hdcp_status(&dp->catalog->ctrl); + if (rc >= 0) { + hdcp_auth_state = (rc >> 20) & 0x3; + pr_debug("hdcp auth state %d\n", hdcp_auth_state); + } + + ops = dp->hdcp.ops; + + switch (dp->hdcp_status) { + case HDCP_STATE_AUTHENTICATING: + pr_debug("start authenticaton\n"); + + if (dp->hdcp.ops && dp->hdcp.ops->authenticate) + rc = dp->hdcp.ops->authenticate(dp->hdcp.data); + + break; + case HDCP_STATE_AUTHENTICATED: + pr_debug("hdcp authenticated\n"); + dp->hdcp.auth_state = true; + break; + case HDCP_STATE_AUTH_FAIL: + dp->hdcp.auth_state = false; + + if (dp->power_on) { + pr_debug("Reauthenticating\n"); + if (ops && ops->reauthenticate) { + rc = ops->reauthenticate(dp->hdcp.data); + if (rc) + pr_err("reauth failed rc=%d\n", rc); + } + } else { + pr_debug("not reauthenticating, cable disconnected\n"); + } + + break; + default: + break; + } +} + +static void dp_display_notify_hdcp_status_cb(void *ptr, + enum sde_hdcp_states status) +{ + struct dp_display_private *dp = ptr; + + if (!dp) { + pr_err("invalid input\n"); + return; + } + + dp->hdcp_status = status; + + if (dp->dp_display.is_connected) + queue_delayed_work(dp->hdcp_workqueue, &dp->hdcp_cb_work, HZ/4); +} + +static int dp_display_create_hdcp_workqueue(struct dp_display_private *dp) +{ + dp->hdcp_workqueue = create_workqueue("sdm_dp_hdcp"); + if (IS_ERR_OR_NULL(dp->hdcp_workqueue)) { + pr_err("Error creating hdcp_workqueue\n"); + return -EPERM; + } + + INIT_DELAYED_WORK(&dp->hdcp_cb_work, dp_display_hdcp_cb_work); + + return 0; +} + +static void dp_display_destroy_hdcp_workqueue(struct dp_display_private *dp) +{ + if (dp->hdcp_workqueue) + destroy_workqueue(dp->hdcp_workqueue); +} + +static void dp_display_update_hdcp_info(struct dp_display_private *dp) +{ + void *fd = NULL; + struct sde_hdcp_ops *ops = NULL; + + if (!dp) { + pr_err("invalid input\n"); + return; + } + + if (!dp->hdcp.feature_enabled) { + pr_debug("feature not enabled\n"); + return; + } + + fd = dp->hdcp.hdcp2; + if (fd) + ops = sde_dp_hdcp2p2_start(fd); + + if (ops && ops->feature_supported) + dp->hdcp.hdcp2_present = ops->feature_supported(fd); + else + dp->hdcp.hdcp2_present = false; + + pr_debug("hdcp2p2: %s\n", + dp->hdcp.hdcp2_present ? "supported" : "not supported"); + + if (!dp->hdcp.hdcp2_present) { + dp->hdcp.hdcp1_present = hdcp1_check_if_supported_load_app(); + + if (dp->hdcp.hdcp1_present) { + fd = dp->hdcp.hdcp1; + ops = sde_hdcp_1x_start(fd); + } + } + + pr_debug("hdcp1x: %s\n", + dp->hdcp.hdcp1_present ? "supported" : "not supported"); + + if (dp->hdcp.hdcp2_present || dp->hdcp.hdcp1_present) { + dp->hdcp.data = fd; + dp->hdcp.ops = ops; + } else { + dp->hdcp.data = NULL; + dp->hdcp.ops = NULL; + } +} + +static void dp_display_deinitialize_hdcp(struct dp_display_private *dp) +{ + if (!dp) { + pr_err("invalid input\n"); + return; + } + + sde_dp_hdcp2p2_deinit(dp->hdcp.data); + dp_display_destroy_hdcp_workqueue(dp); + if (&dp->hdcp_mutex) + mutex_destroy(&dp->hdcp_mutex); +} + +static int dp_display_initialize_hdcp(struct dp_display_private *dp) +{ + struct sde_hdcp_init_data hdcp_init_data; + struct resource *res; + int rc = 0; + + if (!dp) { + pr_err("invalid input\n"); + return -EINVAL; + } + + mutex_init(&dp->hdcp_mutex); + + rc = dp_display_create_hdcp_workqueue(dp); + if (rc) { + pr_err("Failed to create HDCP workqueue\n"); + goto error; + } + + res = platform_get_resource_byname(dp->pdev, + IORESOURCE_MEM, "dp_ctrl"); + if (!res) { + pr_err("Error getting dp ctrl resource\n"); + rc = -EINVAL; + goto error; + } + + hdcp_init_data.phy_addr = res->start; + hdcp_init_data.client_id = HDCP_CLIENT_DP; + hdcp_init_data.drm_aux = dp->aux->drm_aux; + hdcp_init_data.cb_data = (void *)dp; + hdcp_init_data.workq = dp->hdcp_workqueue; + hdcp_init_data.mutex = &dp->hdcp_mutex; + hdcp_init_data.sec_access = true; + hdcp_init_data.notify_status = dp_display_notify_hdcp_status_cb; + hdcp_init_data.core_io = &dp->parser->io.ctrl_io; + hdcp_init_data.qfprom_io = &dp->parser->io.qfprom_io; + hdcp_init_data.hdcp_io = &dp->parser->io.hdcp_io; + hdcp_init_data.revision = &dp->panel->link_info.revision; + + dp->hdcp.hdcp1 = sde_hdcp_1x_init(&hdcp_init_data); + if (IS_ERR_OR_NULL(dp->hdcp.hdcp1)) { + pr_err("Error initializing HDCP 1.x\n"); + rc = -EINVAL; + goto error; + } + + pr_debug("HDCP 1.3 initialized\n"); + + dp->hdcp.hdcp2 = sde_dp_hdcp2p2_init(&hdcp_init_data); + if (!IS_ERR_OR_NULL(dp->hdcp.hdcp2)) + pr_debug("HDCP 2.2 initialized\n"); + + dp->hdcp.feature_enabled = true; + + return 0; +error: + dp_display_deinitialize_hdcp(dp); + return rc; +} + +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 msm_drm_private *priv; + 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 end; + } + + 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 end; + } + + dp->dp_display.drm_dev = drm; + priv = drm->dev_private; + + rc = dp->parser->parse(dp->parser); + if (rc) { + pr_err("device tree parsing failed\n"); + goto end; + } + + rc = dp->aux->drm_aux_register(dp->aux); + if (rc) { + pr_err("DRM DP AUX register failed\n"); + goto end; + } + + rc = dp->panel->sde_edid_register(dp->panel); + if (rc) { + pr_err("DRM DP EDID register failed\n"); + goto end; + } + + rc = dp->power->power_client_init(dp->power, &priv->phandle); + if (rc) { + pr_err("Power client create failed\n"); + goto end; + } + + rc = dp_display_initialize_hdcp(dp); + if (rc) { + pr_err("HDCP initialization failed\n"); + goto end; + } +end: + 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; + } + + (void)dp->power->power_client_deinit(dp->power); + (void)dp->panel->sde_edid_deregister(dp->panel); + (void)dp->aux->drm_aux_deregister(dp->aux); + dp_display_deinitialize_hdcp(dp); +} + +static const struct component_ops dp_display_comp_ops = { + .bind = dp_display_bind, + .unbind = dp_display_unbind, +}; + +static bool dp_display_is_ds_bridge(struct dp_panel *panel) +{ + return (panel->dpcd[DP_DOWNSTREAMPORT_PRESENT] & + DP_DWN_STRM_PORT_PRESENT); +} + +static bool dp_display_is_sink_count_zero(struct dp_display_private *dp) +{ + return dp_display_is_ds_bridge(dp->panel) && + (dp->link->sink_count.count == 0); +} + +static void dp_display_send_hpd_event(struct dp_display *dp_display) +{ + struct drm_device *dev = NULL; + struct dp_display_private *dp; + struct drm_connector *connector; + char name[HPD_STRING_SIZE], status[HPD_STRING_SIZE], + bpp[HPD_STRING_SIZE], pattern[HPD_STRING_SIZE]; + char *envp[5]; + + if (!dp_display) { + pr_err("invalid input\n"); + return; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + if (!dp) { + pr_err("invalid params\n"); + return; + } + connector = dp->dp_display.connector; + dev = dp_display->connector->dev; + + connector->status = connector->funcs->detect(connector, false); + pr_debug("[%s] status updated to %s\n", + connector->name, + drm_get_connector_status_name(connector->status)); + snprintf(name, HPD_STRING_SIZE, "name=%s", connector->name); + snprintf(status, HPD_STRING_SIZE, "status=%s", + drm_get_connector_status_name(connector->status)); + snprintf(bpp, HPD_STRING_SIZE, "bpp=%d", + dp_link_bit_depth_to_bpp( + dp->link->test_video.test_bit_depth)); + snprintf(pattern, HPD_STRING_SIZE, "pattern=%d", + dp->link->test_video.test_video_pattern); + + pr_debug("generating hotplug event [%s]:[%s] [%s] [%s]\n", + name, status, bpp, pattern); + envp[0] = name; + envp[1] = status; + envp[2] = bpp; + envp[3] = pattern; + envp[4] = NULL; + kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, + envp); +} + +static int dp_display_send_hpd_notification(struct dp_display_private *dp, + bool hpd) +{ + if ((hpd && dp->dp_display.is_connected) || + (!hpd && !dp->dp_display.is_connected)) { + pr_info("HPD already %s\n", (hpd ? "on" : "off")); + return 0; + } + + /* reset video pattern flag on disconnect */ + if (!hpd) + dp->panel->video_test = false; + + dp->dp_display.is_connected = hpd; + reinit_completion(&dp->notification_comp); + dp_display_send_hpd_event(&dp->dp_display); + + if (!wait_for_completion_timeout(&dp->notification_comp, HZ * 2)) { + pr_warn("%s timeout\n", hpd ? "connect" : "disconnect"); + return -EINVAL; + } + + return 0; +} + +static int dp_display_process_hpd_high(struct dp_display_private *dp) +{ + int rc = 0; + u32 max_pclk_from_edid = 0; + struct edid *edid; + + dp->aux->init(dp->aux, dp->parser->aux_cfg); + + if (dp->link->psm_enabled) + goto notify; + + rc = dp->panel->read_sink_caps(dp->panel, dp->dp_display.connector); + if (rc) + goto notify; + + dp->link->process_request(dp->link); + + if (dp_display_is_sink_count_zero(dp)) { + pr_debug("no downstream devices connected\n"); + rc = -EINVAL; + goto end; + } + + edid = dp->panel->edid_ctrl->edid; + + dp->audio_supported = drm_detect_monitor_audio(edid); + + dp->panel->handle_sink_request(dp->panel); + + max_pclk_from_edid = dp->panel->get_max_pclk(dp->panel); + + dp->dp_display.max_pclk_khz = min(max_pclk_from_edid, + dp->parser->max_pclk_khz); + +notify: + dp_display_send_hpd_notification(dp, true); + +end: + return rc; +} + +static void dp_display_host_init(struct dp_display_private *dp) +{ + bool flip = false; + + if (dp->core_initialized) { + pr_debug("DP core already initialized\n"); + return; + } + + if (dp->usbpd->orientation == ORIENTATION_CC2) + flip = true; + + dp->power->init(dp->power, flip); + dp->ctrl->init(dp->ctrl, flip); + enable_irq(dp->irq); + dp->core_initialized = true; +} + +static void dp_display_host_deinit(struct dp_display_private *dp) +{ + if (!dp->core_initialized) { + pr_debug("DP core already off\n"); + return; + } + + dp->ctrl->deinit(dp->ctrl); + dp->power->deinit(dp->power); + disable_irq(dp->irq); + dp->core_initialized = false; +} + +static void dp_display_process_hpd_low(struct dp_display_private *dp) +{ + /* cancel any pending request */ + dp->ctrl->abort(dp->ctrl); + + if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->off) { + cancel_delayed_work_sync(&dp->hdcp_cb_work); + dp->hdcp.ops->off(dp->hdcp.data); + } + + if (dp->audio_supported) + dp->audio->off(dp->audio); + + dp_display_send_hpd_notification(dp, false); + + dp->aux->deinit(dp->aux); +} + +static int dp_display_usbpd_configure_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; + } + + dp_display_host_init(dp); + + if (dp->usbpd->hpd_high) + dp_display_process_hpd_high(dp); +end: + return rc; +} + +static void dp_display_clean(struct dp_display_private *dp) +{ + if (dp_display_is_hdcp_enabled(dp)) { + dp->hdcp_status = HDCP_STATE_INACTIVE; + + cancel_delayed_work_sync(&dp->hdcp_cb_work); + if (dp->hdcp.ops->off) + dp->hdcp.ops->off(dp->hdcp.data); + } + + dp->ctrl->push_idle(dp->ctrl); + dp->ctrl->off(dp->ctrl); +} + +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; + } + + /* cancel any pending request */ + dp->ctrl->abort(dp->ctrl); + + if (dp->audio_supported) + dp->audio->off(dp->audio); + + rc = dp_display_send_hpd_notification(dp, false); + + /* if cable is disconnected, reset psm_enabled flag */ + if (!dp->usbpd->alt_mode_cfg_done) + dp->link->psm_enabled = false; + + if ((rc < 0) && dp->power_on) + dp_display_clean(dp); + + dp_display_host_deinit(dp); +end: + return rc; +} + +static void dp_display_handle_video_request(struct dp_display_private *dp) +{ + if (dp->link->sink_request & DP_TEST_LINK_VIDEO_PATTERN) { + /* force disconnect followed by connect */ + dp->usbpd->connect(dp->usbpd, false); + dp->panel->video_test = true; + dp->usbpd->connect(dp->usbpd, true); + dp->link->send_test_response(dp->link); + } +} + +static int dp_display_handle_hpd_irq(struct dp_display_private *dp) +{ + if (dp->link->sink_request & DS_PORT_STATUS_CHANGED) { + dp_display_send_hpd_notification(dp, false); + + if (dp_display_is_sink_count_zero(dp)) { + pr_debug("sink count is zero, nothing to do\n"); + return 0; + } + + return dp_display_process_hpd_high(dp); + } + + dp->ctrl->handle_sink_request(dp->ctrl); + + dp_display_handle_video_request(dp); + + return 0; +} + +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"); + return -EINVAL; + } + + dp = dev_get_drvdata(dev); + if (!dp) { + pr_err("no driver data found\n"); + return -ENODEV; + } + + if (dp->usbpd->hpd_irq) { + dp->hpd_irq_on = true; + + if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->cp_irq) { + if (!dp->hdcp.ops->cp_irq(dp->hdcp.data)) + goto end; + } + + rc = dp->link->process_request(dp->link); + /* check for any test request issued by sink */ + if (!rc) + dp_display_handle_hpd_irq(dp); + + dp->hpd_irq_on = false; + goto end; + } + + if (!dp->usbpd->hpd_high) { + dp_display_process_hpd_low(dp); + goto end; + } + + if (dp->usbpd->alt_mode_cfg_done) + dp_display_process_hpd_high(dp); +end: + return rc; +} + +static void dp_display_deinit_sub_modules(struct dp_display_private *dp) +{ + dp_audio_put(dp->audio); + 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); + dp_debug_put(dp->debug); +} + +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, + }; + struct dp_panel_in panel_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); + dp->usbpd = NULL; + goto error; + } + + 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); + dp->parser = NULL; + goto error_parser; + } + + 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); + dp->catalog = NULL; + goto error_catalog; + } + + 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); + dp->power = NULL; + goto error_power; + } + + dp->aux = dp_aux_get(dev, &dp->catalog->aux, dp->parser->aux_cfg); + if (IS_ERR(dp->aux)) { + rc = PTR_ERR(dp->aux); + pr_err("failed to initialize aux, rc = %d\n", rc); + dp->aux = NULL; + goto error_aux; + } + + 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); + dp->link = NULL; + goto error_link; + } + + panel_in.aux = dp->aux; + panel_in.catalog = &dp->catalog->panel; + panel_in.link = dp->link; + + dp->panel = dp_panel_get(&panel_in); + if (IS_ERR(dp->panel)) { + rc = PTR_ERR(dp->panel); + pr_err("failed to initialize panel, rc = %d\n", rc); + dp->panel = NULL; + goto error_panel; + } + + 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); + dp->ctrl = NULL; + goto error_ctrl; + } + + dp->audio = dp_audio_get(dp->pdev, dp->panel, &dp->catalog->audio); + if (IS_ERR(dp->audio)) { + rc = PTR_ERR(dp->audio); + pr_err("failed to initialize audio, rc = %d\n", rc); + dp->audio = NULL; + goto error_audio; + } + + dp->debug = dp_debug_get(dev, dp->panel, dp->usbpd, + dp->link, &dp->dp_display.connector); + if (IS_ERR(dp->debug)) { + rc = PTR_ERR(dp->debug); + pr_err("failed to initialize debug, rc = %d\n", rc); + dp->debug = NULL; + goto error_debug; + } + + return rc; +error_debug: + dp_audio_put(dp->audio); +error_audio: + dp_ctrl_put(dp->ctrl); +error_ctrl: + dp_panel_put(dp->panel); +error_panel: + dp_link_put(dp->link); +error_link: + dp_aux_put(dp->aux); +error_aux: + dp_power_put(dp->power); +error_power: + dp_catalog_put(dp->catalog); +error_catalog: + dp_parser_put(dp->parser); +error_parser: + dp_usbpd_put(dp->usbpd); +error: + 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); + + if (dp->power_on) { + pr_debug("Link already setup, return\n"); + return 0; + } + + rc = dp->ctrl->on(dp->ctrl); + if (!rc) + dp->power_on = true; +error: + return rc; +} + +static int dp_display_post_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 end; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + if (dp->audio_supported) { + dp->audio->bw_code = dp->link->link_params.bw_code; + dp->audio->lane_count = dp->link->link_params.lane_count; + dp->audio->on(dp->audio); + } + + complete_all(&dp->notification_comp); + + dp_display_update_hdcp_info(dp); + + if (dp_display_is_hdcp_enabled(dp)) { + cancel_delayed_work_sync(&dp->hdcp_cb_work); + + dp->hdcp_status = HDCP_STATE_AUTHENTICATING; + queue_delayed_work(dp->hdcp_workqueue, + &dp->hdcp_cb_work, HZ / 2); + } +end: + return rc; +} + +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); + + if (dp_display_is_hdcp_enabled(dp)) { + dp->hdcp_status = HDCP_STATE_INACTIVE; + + cancel_delayed_work_sync(&dp->hdcp_cb_work); + if (dp->hdcp.ops->off) + dp->hdcp.ops->off(dp->hdcp.data); + } + + if (dp->usbpd->alt_mode_cfg_done && (dp->usbpd->hpd_high || + dp->usbpd->forced_disconnect)) + dp->link->psm_config(dp->link, &dp->panel->link_info, true); + + dp->ctrl->push_idle(dp->ctrl); +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); + + if (!dp->power_on || !dp->core_initialized) + goto error; + + dp->ctrl->off(dp->ctrl); + + dp->power_on = false; + + complete_all(&dp->notification_comp); +error: + return rc; +} + +static int dp_request_irq(struct dp_display *dp_display) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dp_display) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + dp->irq = irq_of_parse_and_map(dp->pdev->dev.of_node, 0); + if (dp->irq < 0) { + rc = dp->irq; + pr_err("failed to get irq: %d\n", rc); + return rc; + } + + rc = devm_request_irq(&dp->pdev->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); + return rc; + } + disable_irq(dp->irq); + + return 0; +} + +static struct dp_debug *dp_get_debug(struct dp_display *dp_display) +{ + struct dp_display_private *dp; + + if (!dp_display) { + pr_err("invalid input\n"); + return ERR_PTR(-EINVAL); + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + return dp->debug; +} + +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 *dp_mode) +{ + struct dp_display_private *dp_display; + int ret = 0; + + if (!dp) { + pr_err("invalid params\n"); + return 0; + } + + dp_display = container_of(dp, struct dp_display_private, dp_display); + + ret = dp_display->panel->get_modes(dp_display->panel, + dp->connector, dp_mode); + if (dp_mode->timing.pixel_clk_khz) + dp->max_pclk_khz = dp_mode->timing.pixel_clk_khz; + return ret; +} + +static bool dp_display_check_video_test(struct dp_display *dp) +{ + struct dp_display_private *dp_display; + + if (!dp) { + pr_err("invalid params\n"); + return false; + } + + dp_display = container_of(dp, struct dp_display_private, dp_display); + + if (dp_display->panel->video_test) + return true; + + return false; +} + +static int dp_display_get_test_bpp(struct dp_display *dp) +{ + struct dp_display_private *dp_display; + + if (!dp) { + pr_err("invalid params\n"); + return 0; + } + + dp_display = container_of(dp, struct dp_display_private, dp_display); + + return dp_link_bit_depth_to_bpp( + dp_display->link->test_video.test_bit_depth); +} + +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; + + init_completion(&dp->notification_comp); + + 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->prepare = dp_display_prepare; + g_dp_display->unprepare = dp_display_unprepare; + g_dp_display->request_irq = dp_request_irq; + g_dp_display->get_debug = dp_get_debug; + g_dp_display->send_hpd_event = dp_display_send_hpd_event; + g_dp_display->is_video_test = dp_display_check_video_test; + g_dp_display->get_test_bpp = dp_display_get_test_bpp; + + rc = component_add(&pdev->dev, &dp_display_comp_ops); + if (rc) { + pr_err("component add failed, rc=%d\n", rc); + dp_display_deinit_sub_modules(dp); + devm_kfree(&pdev->dev, dp); + } + + return rc; +} + +int dp_display_get_displays(void **displays, int count) +{ + if (!displays) { + pr_err("invalid data\n"); + return -EINVAL; + } + + if (count != 1) { + pr_err("invalid number of displays\n"); + return -EINVAL; + } + + displays[0] = g_dp_display; + return count; +} + +int dp_display_get_num_of_displays(void) +{ + return 1; +} + +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 0000000000000000000000000000000000000000..5539d61ef0d74c42e3394ad34c79e8ffe8aec1df --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -0,0 +1,52 @@ +/* + * 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 + +#include "dp_panel.h" + +struct dp_display { + struct drm_device *drm_dev; + struct dp_bridge *bridge; + struct drm_connector *connector; + bool is_connected; + u32 max_pclk_khz; + + 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 *dp_mode); + int (*prepare)(struct dp_display *dp_display); + int (*unprepare)(struct dp_display *dp_display); + int (*request_irq)(struct dp_display *dp_display); + struct dp_debug *(*get_debug)(struct dp_display *dp_display); + void (*send_hpd_event)(struct dp_display *dp_display); + bool (*is_video_test)(struct dp_display *dp_display); + int (*get_test_bpp)(struct dp_display *dp_display); +}; + +int dp_display_get_num_of_displays(void); +int dp_display_get_displays(void **displays, int count); +#endif /* _DP_DISPLAY_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_drm.c b/drivers/gpu/drm/msm/dp/dp_drm.c new file mode 100644 index 0000000000000000000000000000000000000000..06f85580ca7fda5be2a50ad2724d4dc238ad9637 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_drm.c @@ -0,0 +1,538 @@ +/* + * 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 +#include +#include + +#include "msm_drv.h" +#include "msm_kms.h" +#include "sde_connector.h" +#include "dp_drm.h" +#include "dp_debug.h" + +#define to_dp_bridge(x) container_of((x), struct dp_bridge, base) + +static void convert_to_dp_mode(const struct drm_display_mode *drm_mode, + struct dp_display_mode *dp_mode, struct dp_display *dp) +{ + const u32 num_components = 3; + + memset(dp_mode, 0, sizeof(*dp_mode)); + + dp_mode->timing.h_active = drm_mode->hdisplay; + dp_mode->timing.h_back_porch = drm_mode->htotal - drm_mode->hsync_end; + dp_mode->timing.h_sync_width = drm_mode->htotal - + (drm_mode->hsync_start + dp_mode->timing.h_back_porch); + dp_mode->timing.h_front_porch = drm_mode->hsync_start - + drm_mode->hdisplay; + dp_mode->timing.h_skew = drm_mode->hskew; + + dp_mode->timing.v_active = drm_mode->vdisplay; + dp_mode->timing.v_back_porch = drm_mode->vtotal - drm_mode->vsync_end; + dp_mode->timing.v_sync_width = drm_mode->vtotal - + (drm_mode->vsync_start + dp_mode->timing.v_back_porch); + + dp_mode->timing.v_front_porch = drm_mode->vsync_start - + drm_mode->vdisplay; + + if (dp->is_video_test(dp)) + dp_mode->timing.bpp = dp->get_test_bpp(dp); + else + dp_mode->timing.bpp = dp->connector->display_info.bpc * + num_components; + + if (!dp_mode->timing.bpp) + dp_mode->timing.bpp = 24; + + dp_mode->timing.refresh_rate = drm_mode->vrefresh; + + dp_mode->timing.pixel_clk_khz = drm_mode->clock; + + dp_mode->timing.v_active_low = + !!(drm_mode->flags & DRM_MODE_FLAG_NVSYNC); + + dp_mode->timing.h_active_low = + !!(drm_mode->flags & DRM_MODE_FLAG_NHSYNC); +} + +static void convert_to_drm_mode(const struct dp_display_mode *dp_mode, + struct drm_display_mode *drm_mode) +{ + u32 flags = 0; + + memset(drm_mode, 0, sizeof(*drm_mode)); + + drm_mode->hdisplay = dp_mode->timing.h_active; + drm_mode->hsync_start = drm_mode->hdisplay + + dp_mode->timing.h_front_porch; + drm_mode->hsync_end = drm_mode->hsync_start + + dp_mode->timing.h_sync_width; + drm_mode->htotal = drm_mode->hsync_end + dp_mode->timing.h_back_porch; + drm_mode->hskew = dp_mode->timing.h_skew; + + drm_mode->vdisplay = dp_mode->timing.v_active; + drm_mode->vsync_start = drm_mode->vdisplay + + dp_mode->timing.v_front_porch; + drm_mode->vsync_end = drm_mode->vsync_start + + dp_mode->timing.v_sync_width; + drm_mode->vtotal = drm_mode->vsync_end + dp_mode->timing.v_back_porch; + + drm_mode->vrefresh = dp_mode->timing.refresh_rate; + drm_mode->clock = dp_mode->timing.pixel_clk_khz; + + if (dp_mode->timing.h_active_low) + flags |= DRM_MODE_FLAG_NHSYNC; + else + flags |= DRM_MODE_FLAG_PHSYNC; + + if (dp_mode->timing.v_active_low) + flags |= DRM_MODE_FLAG_NVSYNC; + else + flags |= DRM_MODE_FLAG_PVSYNC; + + drm_mode->flags = flags; + + drm_mode->type = 0x48; + drm_mode_set_name(drm_mode); +} + +static int dp_bridge_attach(struct drm_bridge *dp_bridge) +{ + struct dp_bridge *bridge = to_dp_bridge(dp_bridge); + + if (!dp_bridge) { + pr_err("Invalid params\n"); + return -EINVAL; + } + + pr_debug("[%d] attached\n", bridge->id); + + return 0; +} + +static void dp_bridge_pre_enable(struct drm_bridge *drm_bridge) +{ + int rc = 0; + struct dp_bridge *bridge; + struct dp_display *dp; + + if (!drm_bridge) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_bridge(drm_bridge); + dp = bridge->display; + + /* By this point mode should have been validated through mode_fixup */ + rc = dp->set_mode(dp, &bridge->dp_mode); + if (rc) { + pr_err("[%d] failed to perform a mode set, rc=%d\n", + bridge->id, rc); + return; + } + + rc = dp->prepare(dp); + if (rc) { + pr_err("[%d] DP display prepare failed, rc=%d\n", + bridge->id, rc); + return; + } + + rc = dp->enable(dp); + if (rc) { + pr_err("[%d] DP display enable failed, rc=%d\n", + bridge->id, rc); + dp->unprepare(dp); + } +} + +static void dp_bridge_enable(struct drm_bridge *drm_bridge) +{ + int rc = 0; + struct dp_bridge *bridge; + struct dp_display *dp; + + if (!drm_bridge) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_bridge(drm_bridge); + dp = bridge->display; + + rc = dp->post_enable(dp); + if (rc) + pr_err("[%d] DP display post enable failed, rc=%d\n", + bridge->id, rc); +} + +static void dp_bridge_disable(struct drm_bridge *drm_bridge) +{ + int rc = 0; + struct dp_bridge *bridge; + struct dp_display *dp; + + if (!drm_bridge) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_bridge(drm_bridge); + dp = bridge->display; + + rc = dp->pre_disable(dp); + if (rc) { + pr_err("[%d] DP display pre disable failed, rc=%d\n", + bridge->id, rc); + } +} + +static void dp_bridge_post_disable(struct drm_bridge *drm_bridge) +{ + int rc = 0; + struct dp_bridge *bridge; + struct dp_display *dp; + + if (!drm_bridge) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_bridge(drm_bridge); + dp = bridge->display; + + rc = dp->disable(dp); + if (rc) { + pr_err("[%d] DP display disable failed, rc=%d\n", + bridge->id, rc); + return; + } + + rc = dp->unprepare(dp); + if (rc) { + pr_err("[%d] DP display unprepare failed, rc=%d\n", + bridge->id, rc); + return; + } +} + +static void dp_bridge_mode_set(struct drm_bridge *drm_bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct dp_bridge *bridge; + struct dp_display *dp; + + if (!drm_bridge || !mode || !adjusted_mode) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_bridge(drm_bridge); + dp = bridge->display; + + memset(&bridge->dp_mode, 0x0, sizeof(struct dp_display_mode)); + convert_to_dp_mode(adjusted_mode, &bridge->dp_mode, dp); +} + +static bool dp_bridge_mode_fixup(struct drm_bridge *drm_bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + int rc = 0; + bool ret = true; + struct dp_display_mode dp_mode; + struct dp_bridge *bridge; + struct dp_display *dp; + + if (!drm_bridge || !mode || !adjusted_mode) { + pr_err("Invalid params\n"); + ret = false; + goto end; + } + + bridge = to_dp_bridge(drm_bridge); + dp = bridge->display; + + convert_to_dp_mode(mode, &dp_mode, dp); + + rc = dp->validate_mode(dp, &dp_mode); + if (rc) { + pr_err("[%d] mode is not valid, rc=%d\n", bridge->id, rc); + ret = false; + } else { + convert_to_drm_mode(&dp_mode, adjusted_mode); + } +end: + return ret; +} + +static const struct drm_bridge_funcs dp_bridge_ops = { + .attach = dp_bridge_attach, + .mode_fixup = dp_bridge_mode_fixup, + .pre_enable = dp_bridge_pre_enable, + .enable = dp_bridge_enable, + .disable = dp_bridge_disable, + .post_disable = dp_bridge_post_disable, + .mode_set = dp_bridge_mode_set, +}; + +int dp_connector_post_init(struct drm_connector *connector, + void *info, + void *display) +{ + struct dp_display *dp_display = display; + + if (!info || !dp_display) + return -EINVAL; + + dp_display->connector = connector; + return 0; +} + +int dp_connector_get_mode_info(const struct drm_display_mode *drm_mode, + struct msm_mode_info *mode_info, u32 max_mixer_width) +{ + const u32 dual_lm = 2; + const u32 single_lm = 1; + const u32 single_intf = 1; + const u32 no_enc = 0; + struct msm_display_topology *topology; + + if (!drm_mode || !mode_info || !max_mixer_width) { + pr_err("invalid params\n"); + return -EINVAL; + } + + topology = &mode_info->topology; + topology->num_lm = (max_mixer_width <= drm_mode->hdisplay) ? + dual_lm : single_lm; + topology->num_enc = no_enc; + topology->num_intf = single_intf; + + mode_info->frame_rate = drm_mode->vrefresh; + mode_info->vtotal = drm_mode->vtotal; + mode_info->comp_info.comp_type = MSM_DISPLAY_COMPRESSION_NONE; + + return 0; +} + +int dp_connector_get_info(struct msm_display_info *info, void *data) +{ + struct dp_display *display = data; + + if (!info || !display) { + pr_err("invalid params\n"); + return -EINVAL; + } + + info->intf_type = DRM_MODE_CONNECTOR_DisplayPort; + + info->num_of_h_tiles = 1; + info->h_tile_instance[0] = 0; + info->is_connected = display->is_connected; + info->capabilities = MSM_DISPLAY_CAP_VID_MODE | MSM_DISPLAY_CAP_EDID | + MSM_DISPLAY_CAP_HOT_PLUG; + + return 0; +} + +enum drm_connector_status dp_connector_detect(struct drm_connector *conn, + bool force, + void *display) +{ + enum drm_connector_status status = connector_status_unknown; + struct msm_display_info info; + int rc; + + if (!conn || !display) + return status; + + /* get display dp_info */ + memset(&info, 0x0, sizeof(info)); + rc = dp_connector_get_info(&info, display); + if (rc) { + pr_err("failed to get display info, rc=%d\n", rc); + return connector_status_disconnected; + } + + if (info.capabilities & MSM_DISPLAY_CAP_HOT_PLUG) + status = (info.is_connected ? connector_status_connected : + connector_status_disconnected); + else + status = connector_status_connected; + + conn->display_info.width_mm = info.width_mm; + conn->display_info.height_mm = info.height_mm; + + return status; +} + +void dp_connector_send_hpd_event(void *display) +{ + struct dp_display *dp; + + if (!display) { + pr_err("invalid input\n"); + return; + } + + dp = display; + + if (dp->send_hpd_event) + dp->send_hpd_event(dp); +} + +int dp_connector_get_modes(struct drm_connector *connector, + void *display) +{ + int rc = 0; + struct dp_display *dp; + struct dp_display_mode *dp_mode = NULL; + struct drm_display_mode *m, drm_mode; + + if (!connector || !display) + return 0; + + dp = display; + + dp_mode = kzalloc(sizeof(*dp_mode), GFP_KERNEL); + if (!dp_mode) + return 0; + + /* pluggable case assumes EDID is read when HPD */ + if (dp->is_connected) { + rc = dp->get_modes(dp, dp_mode); + if (!rc) + pr_err("failed to get DP sink modes, rc=%d\n", rc); + + if (dp_mode->timing.pixel_clk_khz) { /* valid DP mode */ + memset(&drm_mode, 0x0, sizeof(drm_mode)); + convert_to_drm_mode(dp_mode, &drm_mode); + m = drm_mode_duplicate(connector->dev, &drm_mode); + if (!m) { + pr_err("failed to add mode %ux%u\n", + drm_mode.hdisplay, + drm_mode.vdisplay); + kfree(dp_mode); + return 0; + } + m->width_mm = connector->display_info.width_mm; + m->height_mm = connector->display_info.height_mm; + drm_mode_probed_add(connector, m); + } + } else { + pr_err("No sink connected\n"); + } + kfree(dp_mode); + + return rc; +} + +int dp_drm_bridge_init(void *data, struct drm_encoder *encoder) +{ + int rc = 0; + struct dp_bridge *bridge; + struct drm_device *dev; + struct dp_display *display = data; + struct msm_drm_private *priv = NULL; + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + rc = -ENOMEM; + goto error; + } + + dev = display->drm_dev; + bridge->display = display; + bridge->base.funcs = &dp_bridge_ops; + bridge->base.encoder = encoder; + + priv = dev->dev_private; + + rc = drm_bridge_attach(dev, &bridge->base); + if (rc) { + pr_err("failed to attach bridge, rc=%d\n", rc); + goto error_free_bridge; + } + + rc = display->request_irq(display); + if (rc) { + pr_err("request_irq failed, rc=%d\n", rc); + goto error_free_bridge; + } + + encoder->bridge = &bridge->base; + priv->bridges[priv->num_bridges++] = &bridge->base; + display->bridge = bridge; + + return 0; +error_free_bridge: + kfree(bridge); +error: + return rc; +} + +void dp_drm_bridge_deinit(void *data) +{ + struct dp_display *display = data; + struct dp_bridge *bridge = display->bridge; + + if (bridge && bridge->base.encoder) + bridge->base.encoder->bridge = NULL; + + kfree(bridge); +} + +enum drm_mode_status dp_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode, + void *display) +{ + struct dp_display *dp_disp; + struct dp_debug *debug; + + if (!mode || !display) { + pr_err("invalid params\n"); + return MODE_ERROR; + } + + dp_disp = display; + debug = dp_disp->get_debug(dp_disp); + + if (debug->debug_en) { + if (mode->hdisplay == debug->hdisplay && + mode->vdisplay == debug->vdisplay && + mode->vrefresh == debug->vrefresh && + mode->clock <= dp_disp->max_pclk_khz) + return MODE_OK; + else + return MODE_ERROR; + } else { + if (mode->vrefresh == 0) { + int vrefresh = (mode->clock * 1000) / + (mode->vtotal * mode->htotal); + if (vrefresh > 60) + return MODE_BAD; + } + + if (mode->clock > dp_disp->max_pclk_khz) + return MODE_BAD; + else + return MODE_OK; + } +} diff --git a/drivers/gpu/drm/msm/dp/dp_drm.h b/drivers/gpu/drm/msm/dp/dp_drm.h new file mode 100644 index 0000000000000000000000000000000000000000..53570f5a2a7aa9e014f701c4798dcab32a1c601d --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -0,0 +1,96 @@ +/* + * 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_DRM_H_ +#define _DP_DRM_H_ + +#include +#include +#include +#include + +#include "msm_drv.h" +#include "dp_display.h" + +struct dp_bridge { + struct drm_bridge base; + u32 id; + + struct dp_display *display; + struct dp_display_mode dp_mode; +}; + +/** + * dp_connector_post_init - callback to perform additional initialization steps + * @connector: Pointer to drm connector structure + * @info: Pointer to sde connector info structure + * @display: Pointer to private display handle + * Returns: Zero on success + */ +int dp_connector_post_init(struct drm_connector *connector, + void *info, + void *display); + +/** + * dp_connector_detect - callback to determine if connector is connected + * @connector: Pointer to drm connector structure + * @force: Force detect setting from drm framework + * @display: Pointer to private display handle + * Returns: Connector 'is connected' status + */ +enum drm_connector_status dp_connector_detect(struct drm_connector *conn, + bool force, + void *display); + +/** + * dp_connector_get_modes - callback to add drm modes via drm_mode_probed_add() + * @connector: Pointer to drm connector structure + * @display: Pointer to private display handle + * Returns: Number of modes added + */ +int dp_connector_get_modes(struct drm_connector *connector, + void *display); + +/** + * dp_connector_mode_valid - callback to determine if specified mode is valid + * @connector: Pointer to drm connector structure + * @mode: Pointer to drm mode structure + * @display: Pointer to private display handle + * Returns: Validity status for specified mode + */ +enum drm_mode_status dp_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode, + void *display); + +/** + * dp_connector_get_mode_info - retrieve information of the mode selected + * @drm_mode: Display mode set for the display + * @mode_info: Out parameter. Information of the mode + * @max_mixer_width: max width supported by HW layer mixer + * Returns: zero on success + */ +int dp_connector_get_mode_info(const struct drm_display_mode *drm_mode, + struct msm_mode_info *mode_info, + u32 max_mixer_width); + +int dp_connector_get_info(struct msm_display_info *info, void *display); + +void dp_connector_send_hpd_event(void *display); + +int dp_drm_bridge_init(void *display, + struct drm_encoder *encoder); + +void dp_drm_bridge_deinit(void *display); +#endif /* _DP_DRM_H_ */ + diff --git a/drivers/gpu/drm/msm/dp/dp_hdcp2p2.c b/drivers/gpu/drm/msm/dp/dp_hdcp2p2.c new file mode 100644 index 0000000000000000000000000000000000000000..016e1b87366255b8c0bbb9b130389742b773a660 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_hdcp2p2.c @@ -0,0 +1,927 @@ +/* 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. + */ + +#define pr_fmt(fmt) "[dp-hdcp2p2] %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sde_hdcp.h" + +#define DP_INTR_STATUS2 (0x00000024) +#define DP_INTR_STATUS3 (0x00000028) +#define dp_read(offset) readl_relaxed((offset)) +#define dp_write(offset, data) writel_relaxed((data), (offset)) +#define DP_HDCP_RXCAPS_LENGTH 3 + +enum dp_hdcp2p2_sink_status { + SINK_DISCONNECTED, + SINK_CONNECTED +}; + +enum dp_auth_status { + DP_HDCP_AUTH_STATUS_FAILURE, + DP_HDCP_AUTH_STATUS_SUCCESS +}; + +struct dp_hdcp2p2_ctrl { + atomic_t auth_state; + enum dp_hdcp2p2_sink_status sink_status; /* Is sink connected */ + struct dp_hdcp2p2_interrupts *intr; + struct sde_hdcp_init_data init_data; + struct mutex mutex; /* mutex to protect access to ctrl */ + struct mutex msg_lock; /* mutex to protect access to msg buffer */ + struct mutex wakeup_mutex; /* mutex to protect access to wakeup call*/ + struct sde_hdcp_ops *ops; + void *lib_ctx; /* Handle to HDCP 2.2 Trustzone library */ + struct hdcp_txmtr_ops *lib; /* Ops for driver to call into TZ */ + enum hdcp_wakeup_cmd wakeup_cmd; + enum dp_auth_status auth_status; + + struct task_struct *thread; + struct kthread_worker worker; + struct kthread_work status; + struct kthread_work auth; + struct kthread_work send_msg; + struct kthread_work recv_msg; + struct kthread_work link; + char *msg_buf; + uint32_t send_msg_len; /* length of all parameters in msg */ + uint32_t timeout; + uint32_t num_messages; + struct hdcp_msg_part msg_part[HDCP_MAX_MESSAGE_PARTS]; + u8 sink_rx_status; + u8 rx_status; + char abort_mask; + + bool cp_irq_done; + bool polling; +}; + +struct dp_hdcp2p2_int_set { + u32 interrupt; + char *name; + void (*func)(struct dp_hdcp2p2_ctrl *ctrl); +}; + +struct dp_hdcp2p2_interrupts { + u32 reg; + struct dp_hdcp2p2_int_set *int_set; +}; + +static inline bool dp_hdcp2p2_is_valid_state(struct dp_hdcp2p2_ctrl *ctrl) +{ + if (ctrl->wakeup_cmd == HDCP_WKUP_CMD_AUTHENTICATE) + return true; + + if (atomic_read(&ctrl->auth_state) != HDCP_STATE_INACTIVE) + return true; + + return false; +} + +static int dp_hdcp2p2_copy_buf(struct dp_hdcp2p2_ctrl *ctrl, + struct hdcp_wakeup_data *data) +{ + int i = 0; + + if (!data || !data->message_data) + return 0; + + mutex_lock(&ctrl->msg_lock); + + ctrl->timeout = data->timeout; + ctrl->num_messages = data->message_data->num_messages; + ctrl->send_msg_len = 0; /* Total len of all messages */ + + for (i = 0; i < ctrl->num_messages ; i++) + ctrl->send_msg_len += data->message_data->messages[i].length; + + memcpy(ctrl->msg_part, data->message_data->messages, + sizeof(data->message_data->messages)); + + ctrl->rx_status = data->message_data->rx_status; + ctrl->abort_mask = data->abort_mask; + + if (!data->send_msg_len) { + mutex_unlock(&ctrl->msg_lock); + return 0; + } + + kzfree(ctrl->msg_buf); + + ctrl->msg_buf = kzalloc(ctrl->send_msg_len, GFP_KERNEL); + + if (!ctrl->msg_buf) { + mutex_unlock(&ctrl->msg_lock); + return -ENOMEM; + } + + /* ignore first byte as it contains message id */ + memcpy(ctrl->msg_buf, data->send_msg_buf + 1, ctrl->send_msg_len); + + mutex_unlock(&ctrl->msg_lock); + + return 0; +} + +static int dp_hdcp2p2_wakeup(struct hdcp_wakeup_data *data) +{ + struct dp_hdcp2p2_ctrl *ctrl; + u32 const default_timeout_us = 500; + + if (!data) { + pr_err("invalid input\n"); + return -EINVAL; + } + + ctrl = data->context; + if (!ctrl) { + pr_err("invalid ctrl\n"); + return -EINVAL; + } + + mutex_lock(&ctrl->wakeup_mutex); + + ctrl->wakeup_cmd = data->cmd; + + if (data->timeout) + ctrl->timeout = (data->timeout) * 2; + else + ctrl->timeout = default_timeout_us; + + if (!dp_hdcp2p2_is_valid_state(ctrl)) { + pr_err("invalid state\n"); + goto exit; + } + + if (dp_hdcp2p2_copy_buf(ctrl, data)) + goto exit; + + if (ctrl->wakeup_cmd == HDCP_WKUP_CMD_STATUS_SUCCESS) + ctrl->auth_status = DP_HDCP_AUTH_STATUS_SUCCESS; + else if (ctrl->wakeup_cmd == HDCP_WKUP_CMD_STATUS_FAILED) + ctrl->auth_status = DP_HDCP_AUTH_STATUS_FAILURE; + + switch (ctrl->wakeup_cmd) { + case HDCP_WKUP_CMD_SEND_MESSAGE: + kthread_queue_work(&ctrl->worker, &ctrl->send_msg); + break; + case HDCP_WKUP_CMD_RECV_MESSAGE: + kthread_queue_work(&ctrl->worker, &ctrl->recv_msg); + break; + case HDCP_WKUP_CMD_STATUS_SUCCESS: + case HDCP_WKUP_CMD_STATUS_FAILED: + kthread_queue_work(&ctrl->worker, &ctrl->status); + break; + case HDCP_WKUP_CMD_LINK_POLL: + if (ctrl->cp_irq_done) + kthread_queue_work(&ctrl->worker, &ctrl->recv_msg); + else + ctrl->polling = true; + break; + case HDCP_WKUP_CMD_AUTHENTICATE: + kthread_queue_work(&ctrl->worker, &ctrl->auth); + break; + default: + pr_err("invalid wakeup command %d\n", ctrl->wakeup_cmd); + } +exit: + mutex_unlock(&ctrl->wakeup_mutex); + + return 0; +} + +static inline void dp_hdcp2p2_wakeup_lib(struct dp_hdcp2p2_ctrl *ctrl, + struct hdcp_lib_wakeup_data *data) +{ + int rc = 0; + + if (ctrl && ctrl->lib && ctrl->lib->wakeup && + data && (data->cmd != HDCP_LIB_WKUP_CMD_INVALID)) { + rc = ctrl->lib->wakeup(data); + if (rc) + pr_err("error sending %s to lib\n", + hdcp_lib_cmd_to_str(data->cmd)); + } +} + +static void dp_hdcp2p2_reset(struct dp_hdcp2p2_ctrl *ctrl) +{ + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + ctrl->sink_status = SINK_DISCONNECTED; + atomic_set(&ctrl->auth_state, HDCP_STATE_INACTIVE); +} + +static void dp_hdcp2p2_set_interrupts(struct dp_hdcp2p2_ctrl *ctrl, bool enable) +{ + void __iomem *base = ctrl->init_data.core_io->base; + struct dp_hdcp2p2_interrupts *intr = ctrl->intr; + + while (intr && intr->reg) { + struct dp_hdcp2p2_int_set *int_set = intr->int_set; + u32 interrupts = 0; + + while (int_set && int_set->interrupt) { + interrupts |= int_set->interrupt; + int_set++; + } + + if (enable) + dp_write(base + intr->reg, + dp_read(base + intr->reg) | interrupts); + else + dp_write(base + intr->reg, + dp_read(base + intr->reg) & ~interrupts); + intr++; + } +} + +static void dp_hdcp2p2_off(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input; + struct hdcp_wakeup_data cdata = {HDCP_WKUP_CMD_AUTHENTICATE}; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + return; + } + + dp_hdcp2p2_set_interrupts(ctrl, false); + + dp_hdcp2p2_reset(ctrl); + + kthread_flush_worker(&ctrl->worker); + + cdata.context = input; + dp_hdcp2p2_wakeup(&cdata); +} + +static int dp_hdcp2p2_authenticate(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = input; + struct hdcp_wakeup_data cdata = {HDCP_WKUP_CMD_AUTHENTICATE}; + int rc = 0; + + kthread_flush_worker(&ctrl->worker); + + dp_hdcp2p2_set_interrupts(ctrl, true); + + ctrl->sink_status = SINK_CONNECTED; + atomic_set(&ctrl->auth_state, HDCP_STATE_AUTHENTICATING); + + cdata.context = input; + dp_hdcp2p2_wakeup(&cdata); + + return rc; +} + +static int dp_hdcp2p2_reauthenticate(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input; + + if (!ctrl) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp_hdcp2p2_reset((struct dp_hdcp2p2_ctrl *)input); + + return dp_hdcp2p2_authenticate(input); +} + +static void dp_hdcp2p2_min_level_change(void *client_ctx, + int min_enc_level) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)client_ctx; + struct hdcp_lib_wakeup_data cdata = { + HDCP_LIB_WKUP_CMD_QUERY_STREAM_TYPE}; + bool enc_notify = true; + int enc_lvl; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + switch (min_enc_level) { + case 0: + enc_lvl = HDCP_STATE_AUTH_ENC_NONE; + break; + case 1: + enc_lvl = HDCP_STATE_AUTH_ENC_1X; + break; + case 2: + enc_lvl = HDCP_STATE_AUTH_ENC_2P2; + break; + default: + enc_notify = false; + } + + pr_debug("enc level changed %d\n", min_enc_level); + + cdata.context = ctrl->lib_ctx; + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); + + if (enc_notify && ctrl->init_data.notify_status) + ctrl->init_data.notify_status(ctrl->init_data.cb_data, enc_lvl); +} + +static void dp_hdcp2p2_auth_failed(struct dp_hdcp2p2_ctrl *ctrl) +{ + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + dp_hdcp2p2_set_interrupts(ctrl, false); + + atomic_set(&ctrl->auth_state, HDCP_STATE_AUTH_FAIL); + + /* notify DP about HDCP failure */ + ctrl->init_data.notify_status(ctrl->init_data.cb_data, + HDCP_STATE_AUTH_FAIL); +} + +static int dp_hdcp2p2_aux_read_message(struct dp_hdcp2p2_ctrl *ctrl, + u8 *buf, int size, int offset, u32 timeout) +{ + int const max_size = 16; + int rc = 0, read_size = 0, bytes_read = 0; + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + return -EINVAL; + } + + do { + read_size = min(size, max_size); + + bytes_read = drm_dp_dpcd_read(ctrl->init_data.drm_aux, + offset, buf, read_size); + if (bytes_read != read_size) { + pr_err("fail: offset(0x%x), size(0x%x), rc(0x%x)\n", + offset, read_size, bytes_read); + break; + } + + buf += read_size; + offset += read_size; + size -= read_size; + } while (size > 0); + + return rc; +} + +static int dp_hdcp2p2_aux_write_message(struct dp_hdcp2p2_ctrl *ctrl, + u8 *buf, int size, uint offset, uint timeout) +{ + int const max_size = 16; + int rc = 0, write_size = 0, bytes_written = 0; + + do { + write_size = min(size, max_size); + + bytes_written = drm_dp_dpcd_write(ctrl->init_data.drm_aux, + offset, buf, write_size); + if (bytes_written != write_size) { + pr_err("fail: offset(0x%x), size(0x%x), rc(0x%x)\n", + offset, write_size, bytes_written); + break; + } + + buf += write_size; + offset += write_size; + size -= write_size; + } while (size > 0); + + return rc; +} + +static bool dp_hdcp2p2_feature_supported(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = input; + struct hdcp_txmtr_ops *lib = NULL; + bool supported = false; + + if (!ctrl) { + pr_err("invalid input\n"); + goto end; + } + + lib = ctrl->lib; + if (!lib) { + pr_err("invalid lib ops data\n"); + goto end; + } + + if (lib->feature_supported) + supported = lib->feature_supported( + ctrl->lib_ctx); +end: + return supported; +} + +static void dp_hdcp2p2_send_msg_work(struct kthread_work *work) +{ + int rc = 0; + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, send_msg); + struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID}; + + if (!ctrl) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto exit; + } + + cdata.context = ctrl->lib_ctx; + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + goto exit; + } + + mutex_lock(&ctrl->msg_lock); + + rc = dp_hdcp2p2_aux_write_message(ctrl, ctrl->msg_buf, + ctrl->send_msg_len, ctrl->msg_part->offset, + ctrl->timeout); + if (rc) { + pr_err("Error sending msg to sink %d\n", rc); + mutex_unlock(&ctrl->msg_lock); + goto exit; + } + + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_SEND_SUCCESS; + cdata.timeout = ctrl->timeout; + mutex_unlock(&ctrl->msg_lock); + +exit: + if (rc == -ETIMEDOUT) + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_TIMEOUT; + else if (rc) + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_FAILED; + + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); +} + +static int dp_hdcp2p2_get_msg_from_sink(struct dp_hdcp2p2_ctrl *ctrl) +{ + int rc = 0; + char *recvd_msg_buf = NULL; + struct hdcp_lib_wakeup_data cdata = { HDCP_LIB_WKUP_CMD_INVALID }; + + cdata.context = ctrl->lib_ctx; + + recvd_msg_buf = kzalloc(ctrl->send_msg_len, GFP_KERNEL); + if (!recvd_msg_buf) { + rc = -ENOMEM; + goto exit; + } + + rc = dp_hdcp2p2_aux_read_message(ctrl, recvd_msg_buf, + ctrl->send_msg_len, ctrl->msg_part->offset, + ctrl->timeout); + if (rc) { + pr_err("error reading message %d\n", rc); + goto exit; + } + + cdata.recvd_msg_buf = recvd_msg_buf; + cdata.recvd_msg_len = ctrl->send_msg_len; + cdata.timeout = ctrl->timeout; +exit: + if (rc == -ETIMEDOUT) + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_TIMEOUT; + else if (rc) + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_FAILED; + else + cdata.cmd = HDCP_LIB_WKUP_CMD_MSG_RECV_SUCCESS; + + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); + kfree(recvd_msg_buf); + + return rc; +} + +static void dp_hdcp2p2_recv_msg_work(struct kthread_work *work) +{ + struct hdcp_lib_wakeup_data cdata = { HDCP_LIB_WKUP_CMD_INVALID }; + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, recv_msg); + + cdata.context = ctrl->lib_ctx; + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + return; + } + + if (ctrl->rx_status) { + if (!ctrl->cp_irq_done) { + pr_debug("waiting for CP_IRQ\n"); + ctrl->polling = true; + return; + } + + if (ctrl->rx_status & ctrl->sink_rx_status) { + ctrl->cp_irq_done = false; + ctrl->sink_rx_status = 0; + ctrl->rx_status = 0; + } + } + + dp_hdcp2p2_get_msg_from_sink(ctrl); +} + +static void dp_hdcp2p2_auth_status_work(struct kthread_work *work) +{ + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, status); + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + return; + } + + if (ctrl->auth_status == DP_HDCP_AUTH_STATUS_SUCCESS) { + ctrl->init_data.notify_status(ctrl->init_data.cb_data, + HDCP_STATE_AUTHENTICATED); + + atomic_set(&ctrl->auth_state, HDCP_STATE_AUTHENTICATED); + } else { + dp_hdcp2p2_auth_failed(ctrl); + } +} + +static void dp_hdcp2p2_link_work(struct kthread_work *work) +{ + int rc = 0; + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, link); + struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID}; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_AUTH_FAIL || + atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("invalid hdcp state\n"); + return; + } + + cdata.context = ctrl->lib_ctx; + + if (ctrl->sink_rx_status & ctrl->abort_mask) { + if (ctrl->sink_rx_status & BIT(3)) + pr_err("reauth_req set by sink\n"); + + if (ctrl->sink_rx_status & BIT(4)) + pr_err("link failure reported by sink\n"); + + ctrl->sink_rx_status = 0; + ctrl->rx_status = 0; + + rc = -ENOLINK; + + cdata.cmd = HDCP_LIB_WKUP_CMD_LINK_FAILED; + atomic_set(&ctrl->auth_state, HDCP_STATE_AUTH_FAIL); + goto exit; + } + + if (ctrl->polling && (ctrl->sink_rx_status & ctrl->rx_status)) { + ctrl->sink_rx_status = 0; + ctrl->rx_status = 0; + + dp_hdcp2p2_get_msg_from_sink(ctrl); + + ctrl->polling = false; + } else { + ctrl->cp_irq_done = true; + } +exit: + if (rc) + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); +} + +static void dp_hdcp2p2_auth_work(struct kthread_work *work) +{ + struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID}; + struct dp_hdcp2p2_ctrl *ctrl = container_of(work, + struct dp_hdcp2p2_ctrl, auth); + + cdata.context = ctrl->lib_ctx; + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_AUTHENTICATING) + cdata.cmd = HDCP_LIB_WKUP_CMD_START; + else + cdata.cmd = HDCP_LIB_WKUP_CMD_STOP; + + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); +} + +static int dp_hdcp2p2_read_rx_status(struct dp_hdcp2p2_ctrl *ctrl, + u8 *rx_status) +{ + u32 const cp_irq_dpcd_offset = 0x201; + u32 const rxstatus_dpcd_offset = 0x69493; + ssize_t const bytes_to_read = 1; + ssize_t bytes_read = 0; + u8 buf = 0; + int rc = 0; + bool cp_irq = 0; + + *rx_status = 0; + + bytes_read = drm_dp_dpcd_read(ctrl->init_data.drm_aux, + cp_irq_dpcd_offset, &buf, bytes_to_read); + if (bytes_read != bytes_to_read) { + pr_err("cp irq read failed\n"); + rc = bytes_read; + goto error; + } + + cp_irq = buf & BIT(2); + pr_debug("cp_irq=0x%x\n", cp_irq); + buf = 0; + + if (cp_irq) { + bytes_read = drm_dp_dpcd_read(ctrl->init_data.drm_aux, + rxstatus_dpcd_offset, &buf, bytes_to_read); + if (bytes_read != bytes_to_read) { + pr_err("rxstatus read failed\n"); + rc = bytes_read; + goto error; + } + *rx_status = buf; + pr_debug("rx_status=0x%x\n", *rx_status); + } + +error: + return rc; +} + +static int dp_hdcp2p2_cp_irq(void *input) +{ + int rc = 0; + struct dp_hdcp2p2_ctrl *ctrl = input; + + if (!ctrl) { + pr_err("invalid input\n"); + return -EINVAL; + } + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_AUTH_FAIL || + atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("invalid hdcp state\n"); + rc = -EINVAL; + goto error; + } + + ctrl->sink_rx_status = 0; + rc = dp_hdcp2p2_read_rx_status(ctrl, &ctrl->sink_rx_status); + if (rc) { + pr_err("failed to read rx status\n"); + goto error; + } + + pr_debug("sink_rx_status=0x%x\n", ctrl->sink_rx_status); + + if (!ctrl->sink_rx_status) { + pr_debug("not a hdcp 2.2 irq\n"); + rc = -EINVAL; + goto error; + } + + kthread_queue_work(&ctrl->worker, &ctrl->link); + + return 0; +error: + return rc; +} + +static int dp_hdcp2p2_isr(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input; + int rc = 0; + struct dss_io_data *io; + struct dp_hdcp2p2_interrupts *intr; + u32 hdcp_int_val = 0; + + if (!ctrl || !ctrl->init_data.core_io) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + io = ctrl->init_data.core_io; + intr = ctrl->intr; + + while (intr && intr->reg) { + struct dp_hdcp2p2_int_set *int_set = intr->int_set; + + hdcp_int_val = dp_read(io->base + intr->reg); + + while (int_set && int_set->interrupt) { + if (hdcp_int_val & (int_set->interrupt >> 2)) { + pr_debug("%s\n", int_set->name); + + if (int_set->func) + int_set->func(ctrl); + + dp_write(io->base + intr->reg, hdcp_int_val | + (int_set->interrupt >> 1)); + } + int_set++; + } + intr++; + } +end: + return rc; +} + +void sde_dp_hdcp2p2_deinit(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input; + struct hdcp_lib_wakeup_data cdata = {HDCP_LIB_WKUP_CMD_INVALID}; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + cdata.cmd = HDCP_LIB_WKUP_CMD_STOP; + cdata.context = ctrl->lib_ctx; + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); + + kthread_stop(ctrl->thread); + + mutex_destroy(&ctrl->mutex); + mutex_destroy(&ctrl->msg_lock); + mutex_destroy(&ctrl->wakeup_mutex); + kzfree(ctrl->msg_buf); + kfree(ctrl); +} + +void *sde_dp_hdcp2p2_init(struct sde_hdcp_init_data *init_data) +{ + int rc; + struct dp_hdcp2p2_ctrl *ctrl; + static struct hdcp_txmtr_ops txmtr_ops; + struct hdcp_register_data register_data; + static struct sde_hdcp_ops ops = { + .isr = dp_hdcp2p2_isr, + .reauthenticate = dp_hdcp2p2_reauthenticate, + .authenticate = dp_hdcp2p2_authenticate, + .feature_supported = dp_hdcp2p2_feature_supported, + .off = dp_hdcp2p2_off, + .cp_irq = dp_hdcp2p2_cp_irq, + }; + + static struct hdcp_client_ops client_ops = { + .wakeup = dp_hdcp2p2_wakeup, + .notify_lvl_change = dp_hdcp2p2_min_level_change, + }; + static struct dp_hdcp2p2_int_set int_set1[] = { + {BIT(17), "authentication successful", NULL}, + {BIT(20), "authentication failed", NULL}, + {BIT(24), "encryption enabled", NULL}, + {BIT(27), "encryption disabled", NULL}, + {0}, + }; + static struct dp_hdcp2p2_int_set int_set2[] = { + {BIT(2), "key fifo underflow", NULL}, + {0}, + }; + static struct dp_hdcp2p2_interrupts intr[] = { + {DP_INTR_STATUS2, int_set1}, + {DP_INTR_STATUS3, int_set2}, + {0} + }; + + if (!init_data || !init_data->cb_data || + !init_data->notify_status || !init_data->drm_aux) { + pr_err("invalid input\n"); + return ERR_PTR(-EINVAL); + } + + ctrl = kzalloc(sizeof(*ctrl), GFP_KERNEL); + if (!ctrl) + return ERR_PTR(-ENOMEM); + + ctrl->init_data = *init_data; + ctrl->lib = &txmtr_ops; + ctrl->msg_buf = NULL; + + ctrl->sink_status = SINK_DISCONNECTED; + ctrl->intr = intr; + + atomic_set(&ctrl->auth_state, HDCP_STATE_INACTIVE); + + ctrl->ops = &ops; + mutex_init(&ctrl->mutex); + mutex_init(&ctrl->msg_lock); + mutex_init(&ctrl->wakeup_mutex); + + register_data.hdcp_ctx = &ctrl->lib_ctx; + register_data.client_ops = &client_ops; + register_data.txmtr_ops = &txmtr_ops; + register_data.device_type = HDCP_TXMTR_DP; + register_data.client_ctx = ctrl; + + rc = hdcp_library_register(®ister_data); + if (rc) { + pr_err("Unable to register with HDCP 2.2 library\n"); + goto error; + } + + kthread_init_worker(&ctrl->worker); + + kthread_init_work(&ctrl->auth, dp_hdcp2p2_auth_work); + kthread_init_work(&ctrl->send_msg, dp_hdcp2p2_send_msg_work); + kthread_init_work(&ctrl->recv_msg, dp_hdcp2p2_recv_msg_work); + kthread_init_work(&ctrl->status, dp_hdcp2p2_auth_status_work); + kthread_init_work(&ctrl->link, dp_hdcp2p2_link_work); + + ctrl->thread = kthread_run(kthread_worker_fn, + &ctrl->worker, "dp_hdcp2p2"); + + if (IS_ERR(ctrl->thread)) { + pr_err("unable to start DP hdcp2p2 thread\n"); + rc = PTR_ERR(ctrl->thread); + ctrl->thread = NULL; + goto error; + } + + return ctrl; +error: + kfree(ctrl); + return ERR_PTR(rc); +} + +static bool dp_hdcp2p2_supported(struct dp_hdcp2p2_ctrl *ctrl) +{ + u32 const rxcaps_dpcd_offset = 0x6921d; + ssize_t bytes_read = 0; + u8 buf[DP_HDCP_RXCAPS_LENGTH]; + + bytes_read = drm_dp_dpcd_read(ctrl->init_data.drm_aux, + rxcaps_dpcd_offset, &buf, DP_HDCP_RXCAPS_LENGTH); + if (bytes_read != DP_HDCP_RXCAPS_LENGTH) { + pr_err("RxCaps read failed\n"); + goto error; + } + + pr_debug("HDCP_CAPABLE=%lu\n", (buf[2] & BIT(1)) >> 1); + pr_debug("VERSION=%d\n", buf[0]); + + if ((buf[2] & BIT(1)) && (buf[0] == 0x2)) + return true; + +error: + return false; +} + +struct sde_hdcp_ops *sde_dp_hdcp2p2_start(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = input; + + pr_debug("Checking sink capability\n"); + if (dp_hdcp2p2_supported(ctrl)) + return ctrl->ops; + else + return NULL; +} + 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 0000000000000000000000000000000000000000..0cf488d51c5211667926c025959449ba38eaae25 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_link.c @@ -0,0 +1,1548 @@ +/* + * 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" + +enum dynamic_range { + DP_DYNAMIC_RANGE_RGB_VESA = 0x00, + DP_DYNAMIC_RANGE_RGB_CEA = 0x01, + DP_DYNAMIC_RANGE_UNKNOWN = 0xFFFFFFFF, +}; + +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; +}; + +struct dp_link_private { + u32 prev_sink_count; + struct device *dev; + struct dp_aux *aux; + struct dp_link dp_link; + + struct dp_link_request request; + u8 link_status[DP_LINK_STATUS_SIZE]; +}; + +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; + u32 const param_len = 0x1; + u32 const max_audio_period = 0xA; + + /* TEST_AUDIO_PERIOD_CH_XX */ + if (drm_dp_dpcd_read(link->aux->drm_aux, addr, &bp, + param_len) < 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; + struct dp_link_test_audio *req = &link->dp_link.test_audio; + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH1); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_1 = ret; + pr_debug("test_audio_period_ch_1 = 0x%x\n", ret); + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH2); + 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, DP_TEST_AUDIO_PERIOD_CH3); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_3 = ret; + pr_debug("test_audio_period_ch_3 = 0x%x\n", ret); + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH4); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_4 = ret; + pr_debug("test_audio_period_ch_4 = 0x%x\n", ret); + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH5); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_5 = ret; + pr_debug("test_audio_period_ch_5 = 0x%x\n", ret); + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH6); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_6 = ret; + pr_debug("test_audio_period_ch_6 = 0x%x\n", ret); + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH7); + if (ret == -EINVAL) + goto exit; + + req->test_audio_period_ch_7 = ret; + pr_debug("test_audio_period_ch_7 = 0x%x\n", ret); + + ret = dp_link_get_period(link, DP_TEST_AUDIO_PERIOD_CH8); + 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 max_audio_pattern_type = 0x1; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, + DP_TEST_AUDIO_PATTERN_TYPE, &bp, param_len); + 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->dp_link.test_audio.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 max_audio_sampling_rate = 0x6; + int const max_audio_channel_count = 0x8; + int sampling_rate = 0x0; + int channel_count = 0x0; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_AUDIO_MODE, + &bp, param_len); + 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->dp_link.test_audio.test_audio_sampling_rate = sampling_rate; + link->dp_link.test_audio.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_NO_TEST_PATTERN: + case DP_COLOR_RAMP: + case DP_BLACK_AND_WHITE_VERTICAL_LINES: + case DP_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_NO_TEST_PATTERN: + return DP_LINK_ENUM_STR(DP_NO_TEST_PATTERN); + case DP_COLOR_RAMP: + return DP_LINK_ENUM_STR(DP_COLOR_RAMP); + case DP_BLACK_AND_WHITE_VERTICAL_LINES: + return DP_LINK_ENUM_STR(DP_BLACK_AND_WHITE_VERTICAL_LINES); + case DP_COLOR_SQUARE: + return DP_LINK_ENUM_STR(DP_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[2]; + int rlen; + + if (len < 2) + return -EINVAL; + + /* Read the requested video link pattern (Byte 0x221). */ + rlen = drm_dp_dpcd_read(link->aux->drm_aux, addr, bp, len); + 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[2]; + int rlen; + + if (len < 2) + return -EINVAL; + + /* Read the requested video link pattern (Byte 0x221). */ + rlen = drm_dp_dpcd_read(link->aux->drm_aux, addr, bp, len); + 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; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, addr, &bp, len); + if (rlen < 1) { + pr_err("failed to read 0x%x\n", addr); + return -EINVAL; + } + *val = bp; + + 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; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_PATTERN, + &bp, param_len); + 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->dp_link.test_video.test_video_pattern = data; + pr_debug("link video pattern = 0x%x (%s)\n", + link->dp_link.test_video.test_video_pattern, + dp_link_video_pattern_to_string( + link->dp_link.test_video.test_video_pattern)); + + /* Read the requested color bit depth and dynamic range (Byte 0x232) */ + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_MISC0, + &bp, param_len); + if (rlen < param_len) { + pr_err("failed to read link bit depth\n"); + ret = -EINVAL; + goto exit; + } + data = bp; + + /* Dynamic Range */ + dyn_range = (data & DP_TEST_DYNAMIC_RANGE_CEA) >> 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->dp_link.test_video.test_dyn_range = dyn_range; + pr_debug("link dynamic range = 0x%x (%s)\n", + link->dp_link.test_video.test_dyn_range, + dp_link_dynamic_range_to_string( + link->dp_link.test_video.test_dyn_range)); + + /* Color bit depth */ + data &= DP_TEST_BIT_DEPTH_MASK; + if (!dp_link_is_bit_depth_valid(data)) { + pr_err("invalid link bit depth = 0x%x\n", data); + ret = -EINVAL; + goto exit; + } + + link->dp_link.test_video.test_bit_depth = data; + pr_debug("link bit depth = 0x%x (%s)\n", + link->dp_link.test_video.test_bit_depth, + dp_link_bit_depth_to_string( + link->dp_link.test_video.test_bit_depth)); + + /* resolution timing params */ + ret = dp_link_parse_timing_params1(link, DP_TEST_H_TOTAL_HI, 2, + &link->dp_link.test_video.test_h_total); + if (ret) { + pr_err("failed to parse test_h_total (DP_TEST_H_TOTAL_HI)\n"); + goto exit; + } + pr_debug("TEST_H_TOTAL = %d\n", link->dp_link.test_video.test_h_total); + + ret = dp_link_parse_timing_params1(link, DP_TEST_V_TOTAL_HI, 2, + &link->dp_link.test_video.test_v_total); + if (ret) { + pr_err("failed to parse test_v_total (DP_TEST_V_TOTAL_HI)\n"); + goto exit; + } + pr_debug("TEST_V_TOTAL = %d\n", link->dp_link.test_video.test_v_total); + + ret = dp_link_parse_timing_params1(link, DP_TEST_H_START_HI, 2, + &link->dp_link.test_video.test_h_start); + if (ret) { + pr_err("failed to parse test_h_start (DP_TEST_H_START_HI)\n"); + goto exit; + } + pr_debug("TEST_H_START = %d\n", link->dp_link.test_video.test_h_start); + + ret = dp_link_parse_timing_params1(link, DP_TEST_V_START_HI, 2, + &link->dp_link.test_video.test_v_start); + if (ret) { + pr_err("failed to parse test_v_start (DP_TEST_V_START_HI)\n"); + goto exit; + } + pr_debug("TEST_V_START = %d\n", link->dp_link.test_video.test_v_start); + + ret = dp_link_parse_timing_params2(link, DP_TEST_HSYNC_HI, 2, + &link->dp_link.test_video.test_hsync_pol, + &link->dp_link.test_video.test_hsync_width); + if (ret) { + pr_err("failed to parse (DP_TEST_HSYNC_HI)\n"); + goto exit; + } + pr_debug("TEST_HSYNC_POL = %d\n", + link->dp_link.test_video.test_hsync_pol); + pr_debug("TEST_HSYNC_WIDTH = %d\n", + link->dp_link.test_video.test_hsync_width); + + ret = dp_link_parse_timing_params2(link, DP_TEST_VSYNC_HI, 2, + &link->dp_link.test_video.test_vsync_pol, + &link->dp_link.test_video.test_vsync_width); + if (ret) { + pr_err("failed to parse (DP_TEST_VSYNC_HI)\n"); + goto exit; + } + pr_debug("TEST_VSYNC_POL = %d\n", + link->dp_link.test_video.test_vsync_pol); + pr_debug("TEST_VSYNC_WIDTH = %d\n", + link->dp_link.test_video.test_vsync_width); + + ret = dp_link_parse_timing_params1(link, DP_TEST_H_WIDTH_HI, 2, + &link->dp_link.test_video.test_h_width); + if (ret) { + pr_err("failed to parse test_h_width (DP_TEST_H_WIDTH_HI)\n"); + goto exit; + } + pr_debug("TEST_H_WIDTH = %d\n", link->dp_link.test_video.test_h_width); + + ret = dp_link_parse_timing_params1(link, DP_TEST_V_HEIGHT_HI, 2, + &link->dp_link.test_video.test_v_height); + if (ret) { + pr_err("failed to parse test_v_height (DP_TEST_V_HEIGHT_HI)\n"); + goto exit; + } + pr_debug("TEST_V_HEIGHT = %d\n", + link->dp_link.test_video.test_v_height); + + ret = dp_link_parse_timing_params3(link, DP_TEST_MISC1, + &link->dp_link.test_video.test_rr_d); + link->dp_link.test_video.test_rr_d &= DP_TEST_REFRESH_DENOMINATOR; + if (ret) { + pr_err("failed to parse test_rr_d (DP_TEST_MISC1)\n"); + goto exit; + } + pr_debug("TEST_REFRESH_DENOMINATOR = %d\n", + link->dp_link.test_video.test_rr_d); + + ret = dp_link_parse_timing_params3(link, DP_TEST_REFRESH_RATE_NUMERATOR, + &link->dp_link.test_video.test_rr_n); + if (ret) { + pr_err("failed to parse test_rr_n (DP_TEST_REFRESH_RATE_NUMERATOR)\n"); + goto exit; + } + pr_debug("TEST_REFRESH_NUMERATOR = %d\n", + link->dp_link.test_video.test_rr_n); +exit: + return ret; +} + +/** + * 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; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_LINK_RATE, + &bp, param_len); + if (rlen < param_len) { + pr_err("failed to read link rate\n"); + ret = -EINVAL; + goto exit; + } + data = bp; + + if (!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); + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_LANE_COUNT, + &bp, param_len); + if (rlen < param_len) { + pr_err("failed to read lane count\n"); + ret = -EINVAL; + goto exit; + } + data = bp; + data &= 0x1F; + + if (!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 DP_TEST_PHY_PATTERN_NONE: + case DP_TEST_PHY_PATTERN_D10_2_NO_SCRAMBLING: + case DP_TEST_PHY_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT: + case DP_TEST_PHY_PATTERN_PRBS7: + case DP_TEST_PHY_PATTERN_80_BIT_CUSTOM_PATTERN: + case DP_TEST_PHY_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 ret = 0; + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_PHY_PATTERN, + &bp, param_len); + if (rlen < param_len) { + pr_err("failed to read phy link pattern\n"); + ret = -EINVAL; + goto end; + } + + data = bp; + + link->dp_link.phy_params.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 DP_TEST_LINK_TRAINING: + return DP_LINK_ENUM_STR(DP_TEST_LINK_TRAINING); + case DP_TEST_LINK_VIDEO_PATTERN: + return DP_LINK_ENUM_STR(DP_TEST_LINK_VIDEO_PATTERN); + case DP_TEST_LINK_EDID_READ: + return DP_LINK_ENUM_STR(DP_TEST_LINK_EDID_READ); + case DP_TEST_LINK_PHY_TEST_PATTERN: + return DP_LINK_ENUM_STR(DP_TEST_LINK_PHY_TEST_PATTERN); + case DP_TEST_LINK_AUDIO_PATTERN: + return DP_LINK_ENUM_STR(DP_TEST_LINK_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 == DP_TEST_LINK_VIDEO_PATTERN) || + (link == (DP_TEST_LINK_AUDIO_PATTERN | + DP_TEST_LINK_VIDEO_PATTERN)) || + (link == DP_TEST_LINK_AUDIO_PATTERN) || + (link == (DP_TEST_LINK_AUDIO_PATTERN | + DP_TEST_LINK_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 == DP_TEST_LINK_TRAINING) || + (test_requested == DP_TEST_LINK_EDID_READ) || + (test_requested == DP_TEST_LINK_PHY_TEST_PATTERN) || + dp_link_is_video_audio_test_requested(test_requested); +} + +static bool dp_link_is_test_edid_read(struct dp_link_private *link) +{ + return (link->request.test_requested == DP_TEST_LINK_EDID_READ); +} + +/** + * 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; + + /** + * Read the device service IRQ vector (Byte 0x201) to determine + * whether an automated link has been requested by the sink. + */ + rlen = drm_dp_dpcd_read(link->aux->drm_aux, + DP_DEVICE_SERVICE_IRQ_VECTOR, &bp, param_len); + 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 & DP_AUTOMATED_TEST_REQUEST)) { + pr_debug("no test requested\n"); + return 0; + } + + /** + * Read the link request byte (Byte 0x218) to determine what type + * of automated link has been requested by the sink. + */ + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_TEST_REQUEST, + &bp, param_len); + 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 == DP_TEST_LINK_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 == DP_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: + /** + * Send a DP_TEST_ACK if all link parameters are valid, otherwise send + * a DP_TEST_NAK. + */ + if (ret) { + link->dp_link.test_response = DP_TEST_NAK; + } else { + if (!dp_link_is_test_edid_read(link)) + link->dp_link.test_response = DP_TEST_ACK; + else + link->dp_link.test_response = + DP_TEST_EDID_CHECKSUM_WRITE; + } + + 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 int dp_link_parse_sink_count(struct dp_link *dp_link) +{ + int rlen; + int const param_len = 0x1; + struct dp_link_private *link = container_of(dp_link, + struct dp_link_private, dp_link); + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_SINK_COUNT, + &link->dp_link.sink_count.count, param_len); + if (rlen < param_len) { + pr_err("failed to read sink count\n"); + return -EINVAL; + } + + link->dp_link.sink_count.cp_ready = + link->dp_link.sink_count.count & DP_SINK_CP_READY; + /* BIT 7, BIT 5:0 */ + link->dp_link.sink_count.count = + DP_GET_SINK_COUNT(link->dp_link.sink_count.count); + + pr_debug("sink_count = 0x%x, cp_ready = 0x%x\n", + link->dp_link.sink_count.count, + link->dp_link.sink_count.cp_ready); + return 0; +} + +static void dp_link_parse_sink_status_field(struct dp_link_private *link) +{ + int len = 0; + + link->prev_sink_count = link->dp_link.sink_count.count; + dp_link_parse_sink_count(&link->dp_link); + + len = drm_dp_dpcd_read_link_status(link->aux->drm_aux, + link->link_status); + if (len < DP_LINK_STATUS_SIZE) + pr_err("DP link status read failed\n"); + dp_link_parse_request(link); +} + +static bool dp_link_is_link_training_requested(struct dp_link_private *link) +{ + return (link->request.test_requested == DP_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(DP_TEST_LINK_TRAINING), + link->request.test_link_rate, + link->request.test_lane_count); + + link->dp_link.link_params.lane_count = link->request.test_lane_count; + link->dp_link.link_params.bw_code = link->request.test_link_rate; + + return 0; +} + +static void dp_link_send_test_response(struct dp_link *dp_link) +{ + struct dp_link_private *link = NULL; + u32 const response_len = 0x1; + + if (!dp_link) { + pr_err("invalid input\n"); + return; + } + + link = container_of(dp_link, struct dp_link_private, dp_link); + + drm_dp_dpcd_write(link->aux->drm_aux, DP_TEST_RESPONSE, + &dp_link->test_response, response_len); +} + +static int dp_link_psm_config(struct dp_link *dp_link, + struct drm_dp_link *link_info, bool enable) +{ + struct dp_link_private *link = NULL; + int ret = 0; + + if (!dp_link) { + pr_err("invalid params\n"); + return -EINVAL; + } + + link = container_of(dp_link, struct dp_link_private, dp_link); + + if (enable) + ret = drm_dp_link_power_down(link->aux->drm_aux, link_info); + else + ret = drm_dp_link_power_up(link->aux->drm_aux, link_info); + + if (ret) + pr_err("Failed to %s low power mode\n", + (enable ? "enter" : "exit")); + else + dp_link->psm_enabled = enable; + + return ret; +} + +static void dp_link_send_edid_checksum(struct dp_link *dp_link, u8 checksum) +{ + struct dp_link_private *link = NULL; + u32 const response_len = 0x1; + + if (!dp_link) { + pr_err("invalid input\n"); + return; + } + + link = container_of(dp_link, struct dp_link_private, dp_link); + + drm_dp_dpcd_write(link->aux->drm_aux, DP_TEST_EDID_CHECKSUM, + &checksum, response_len); +} + +static int dp_link_parse_vx_px(struct dp_link_private *link) +{ + u8 bp; + u8 data; + int const param_len = 0x1; + int ret = 0; + u32 v0, p0, v1, p1, v2, p2, v3, p3; + int rlen; + + pr_debug("\n"); + + rlen = drm_dp_dpcd_read(link->aux->drm_aux, DP_ADJUST_REQUEST_LANE0_1, + &bp, param_len); + 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 = drm_dp_dpcd_read(link->aux->drm_aux, DP_ADJUST_REQUEST_LANE2_3, + &bp, param_len); + 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.phy_params.v_level, + link->dp_link.phy_params.p_level); + pr_debug("Requested: v_level = 0x%x, p_level = 0x%x\n", v0, p0); + link->dp_link.phy_params.v_level = v0; + link->dp_link.phy_params.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 (!(link->request.test_requested & DP_TEST_LINK_PHY_TEST_PATTERN)) { + pr_debug("no phy test\n"); + return -EINVAL; + } + + test_link_rate = link->request.test_link_rate; + test_lane_count = link->request.test_lane_count; + + if (!is_link_rate_valid(test_link_rate) || + !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"); + + pr_info("Current: bw_code = 0x%x, lane count = 0x%x\n", + link->dp_link.link_params.bw_code, + link->dp_link.link_params.lane_count); + + pr_info("Requested: bw_code = 0x%x, lane count = 0x%x\n", + test_link_rate, test_lane_count); + + link->dp_link.link_params.lane_count = link->request.test_lane_count; + link->dp_link.link_params.bw_code = link->request.test_link_rate; + + dp_link_parse_vx_px(link); + + pr_debug("end\n"); + + return 0; +} + +static u8 get_link_status(const u8 link_status[DP_LINK_STATUS_SIZE], int r) +{ + return link_status[r - DP_LANE0_1_STATUS]; +} + +/** + * 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 (!(get_link_status(link->link_status, DP_LANE_ALIGN_STATUS_UPDATED) & + DP_LINK_STATUS_UPDATED) || /* link status updated */ + (drm_dp_clock_recovery_ok(link->link_status, + link->dp_link.link_params.lane_count) && + drm_dp_channel_eq_ok(link->link_status, + link->dp_link.link_params.lane_count))) + return -EINVAL; + + pr_debug("channel_eq_done = %d, clock_recovery_done = %d\n", + drm_dp_clock_recovery_ok(link->link_status, + link->dp_link.link_params.lane_count), + drm_dp_clock_recovery_ok(link->link_status, + link->dp_link.link_params.lane_count)); + + return 0; +} + +static bool dp_link_is_ds_port_status_changed(struct dp_link_private *link) +{ + if (get_link_status(link->link_status, DP_LANE_ALIGN_STATUS_UPDATED) & + DP_DOWNSTREAM_PORT_STATUS_CHANGED) /* port status changed */ + return true; + + if (link->prev_sink_count != link->dp_link.sink_count.count) + return true; + + return false; +} + +/** + * 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; + + /* reset prev_sink_count */ + link->prev_sink_count = link->dp_link.sink_count.count; + + return 0; +} + +static bool dp_link_is_video_pattern_requested(struct dp_link_private *link) +{ + return (link->request.test_requested & DP_TEST_LINK_VIDEO_PATTERN) + && !(link->request.test_requested & + DP_TEST_LINK_AUDIO_DISABLED_VIDEO); +} + +static bool dp_link_is_audio_pattern_requested(struct dp_link_private *link) +{ + return (link->request.test_requested & DP_TEST_LINK_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(DP_TEST_LINK_VIDEO_PATTERN), + link->dp_link.test_video.test_bit_depth, + dp_link_bit_depth_to_bpp( + link->dp_link.test_video.test_bit_depth), + dp_link_video_pattern_to_string( + link->dp_link.test_video.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->dp_link.test_audio.test_audio_sampling_rate), + link->dp_link.test_audio.test_audio_channel_count, + dp_link_get_audio_test_pattern( + link->dp_link.test_audio.test_audio_pattern_type)); + + pr_debug("audio_period: ch1=0x%x, ch2=0x%x, ch3=0x%x, ch4=0x%x\n", + link->dp_link.test_audio.test_audio_period_ch_1, + link->dp_link.test_audio.test_audio_period_ch_2, + link->dp_link.test_audio.test_audio_period_ch_3, + link->dp_link.test_audio.test_audio_period_ch_4); + + pr_debug("audio_period: ch5=0x%x, ch6=0x%x, ch7=0x%x, ch8=0x%x\n", + link->dp_link.test_audio.test_audio_period_ch_5, + link->dp_link.test_audio.test_audio_period_ch_6, + link->dp_link.test_audio.test_audio_period_ch_7, + link->dp_link.test_audio.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->dp_link.test_video = (const struct dp_link_test_video){ 0 }; + link->dp_link.test_video.test_bit_depth = DP_TEST_BIT_DEPTH_UNKNOWN; + link->dp_link.test_audio = (const struct dp_link_test_audio){ 0 }; + link->dp_link.phy_params.phy_test_pattern_sel = 0; + link->dp_link.sink_request = 0; + link->dp_link.test_response = 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); + + if (dp_link_is_test_edid_read(link)) { + dp_link->sink_request |= DP_TEST_LINK_EDID_READ; + goto exit; + } + + ret = dp_link_process_ds_port_status_change(link); + if (!ret) { + dp_link->sink_request |= DS_PORT_STATUS_CHANGED; + goto exit; + } + + ret = dp_link_process_link_training_request(link); + if (!ret) { + dp_link->sink_request |= DP_TEST_LINK_TRAINING; + goto exit; + } + + ret = dp_link_process_phy_test_pattern_request(link); + if (!ret) { + dp_link->sink_request |= DP_TEST_LINK_PHY_TEST_PATTERN; + goto exit; + } + + ret = dp_link_process_link_status_update(link); + if (!ret) { + dp_link->sink_request |= DP_LINK_STATUS_UPDATED; + goto exit; + } + + ret = dp_link_process_video_pattern_request(link); + if (!ret) { + dp_link->sink_request |= DP_TEST_LINK_VIDEO_PATTERN; + goto exit; + } + + ret = dp_link_process_audio_pattern_request(link); + if (!ret) { + dp_link->sink_request |= DP_TEST_LINK_AUDIO_PATTERN; + goto exit; + } + + pr_debug("done\n"); +exit: + return ret; +} + +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->dp_link.test_video.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 int dp_link_adjust_levels(struct dp_link *dp_link, u8 *link_status) +{ + int i; + int max = 0; + u8 data; + 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->link_params.lane_count; i++) { + data = drm_dp_get_adjust_request_voltage(link_status, i); + pr_debug("lane=%d req_voltage_swing=%d\n", i, data); + if (max < data) + max = data; + } + + dp_link->phy_params.v_level = max >> DP_TRAIN_VOLTAGE_SWING_SHIFT; + + /* use the max level across lanes */ + max = 0; + for (i = 0; i < dp_link->link_params.lane_count; i++) { + data = drm_dp_get_adjust_request_pre_emphasis(link_status, i); + pr_debug("lane=%d req_pre_emphasis=%d\n", i, data); + if (max < data) + max = data; + } + + dp_link->phy_params.p_level = max >> DP_TRAIN_PRE_EMPHASIS_SHIFT; + + /** + * Adjust the voltage swing and pre-emphasis level combination to within + * the allowable range. + */ + if (dp_link->phy_params.v_level > DP_LINK_VOLTAGE_MAX) { + pr_debug("Requested vSwingLevel=%d, change to %d\n", + dp_link->phy_params.v_level, DP_LINK_VOLTAGE_MAX); + dp_link->phy_params.v_level = DP_LINK_VOLTAGE_MAX; + } + + if (dp_link->phy_params.p_level > DP_LINK_PRE_EMPHASIS_MAX) { + pr_debug("Requested preEmphasisLevel=%d, change to %d\n", + dp_link->phy_params.p_level, DP_LINK_PRE_EMPHASIS_MAX); + dp_link->phy_params.p_level = DP_LINK_PRE_EMPHASIS_MAX; + } + + if ((dp_link->phy_params.p_level > DP_LINK_PRE_EMPHASIS_LEVEL_1) + && (dp_link->phy_params.v_level == DP_LINK_VOLTAGE_LEVEL_2)) { + pr_debug("Requested preEmphasisLevel=%d, change to %d\n", + dp_link->phy_params.p_level, + DP_LINK_PRE_EMPHASIS_LEVEL_1); + dp_link->phy_params.p_level = DP_LINK_PRE_EMPHASIS_LEVEL_1; + } + + pr_debug("adjusted: v_level=%d, p_level=%d\n", + dp_link->phy_params.v_level, dp_link->phy_params.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) +{ + u32 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; + } + + if (tbd != DP_TEST_BIT_DEPTH_UNKNOWN) + tbd = (tbd >> DP_TEST_BIT_DEPTH_SHIFT); + + 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_test_bits_depth = dp_link_get_test_bits_depth; + dp_link->get_colorimetry_config = dp_link_get_colorimetry_config; + dp_link->adjust_levels = dp_link_adjust_levels; + dp_link->send_psm_request = dp_link_send_psm_request; + dp_link->send_test_response = dp_link_send_test_response; + dp_link->psm_config = dp_link_psm_config; + dp_link->send_edid_checksum = dp_link_send_edid_checksum; + + 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 0000000000000000000000000000000000000000..b1d92498d7cb179f850a99920e4850af80ec37dc --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_link.h @@ -0,0 +1,184 @@ +/* + * 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" + +#define DS_PORT_STATUS_CHANGED 0x200 +#define DP_TEST_BIT_DEPTH_UNKNOWN 0xFFFFFFFF +#define DP_LINK_ENUM_STR(x) #x + +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, +}; + +struct dp_link_sink_count { + u32 count; + bool cp_ready; +}; + +struct dp_link_test_video { + 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; +}; + +struct dp_link_test_audio { + 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; +}; + +struct dp_link_phy_params { + u32 phy_test_pattern_sel; + u8 v_level; + u8 p_level; +}; + +struct dp_link_params { + u32 lane_count; + u32 bw_code; +}; + +struct dp_link { + u32 sink_request; + u32 test_response; + bool psm_enabled; + + struct dp_link_sink_count sink_count; + struct dp_link_test_video test_video; + struct dp_link_test_audio test_audio; + struct dp_link_phy_params phy_params; + struct dp_link_params link_params; + + 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, u8 *link_status); + int (*send_psm_request)(struct dp_link *dp_link, bool req); + void (*send_test_response)(struct dp_link *dp_link); + int (*psm_config)(struct dp_link *dp_link, + struct drm_dp_link *link_info, bool enable); + void (*send_edid_checksum)(struct dp_link *dp_link, u8 checksum); +}; + +static inline char *dp_link_get_phy_test_pattern(u32 phy_test_pattern_sel) +{ + switch (phy_test_pattern_sel) { + case DP_TEST_PHY_PATTERN_NONE: + return DP_LINK_ENUM_STR(DP_TEST_PHY_PATTERN_NONE); + case DP_TEST_PHY_PATTERN_D10_2_NO_SCRAMBLING: + return DP_LINK_ENUM_STR( + DP_TEST_PHY_PATTERN_D10_2_NO_SCRAMBLING); + case DP_TEST_PHY_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT: + return DP_LINK_ENUM_STR( + DP_TEST_PHY_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT); + case DP_TEST_PHY_PATTERN_PRBS7: + return DP_LINK_ENUM_STR(DP_TEST_PHY_PATTERN_PRBS7); + case DP_TEST_PHY_PATTERN_80_BIT_CUSTOM_PATTERN: + return DP_LINK_ENUM_STR( + DP_TEST_PHY_PATTERN_80_BIT_CUSTOM_PATTERN); + case DP_TEST_PHY_PATTERN_HBR2_CTS_EYE_PATTERN: + return DP_LINK_ENUM_STR( + DP_TEST_PHY_PATTERN_HBR2_CTS_EYE_PATTERN); + default: + return "unknown"; + } +} + +/** + * 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(u32 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; +} + +/** + * 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 0000000000000000000000000000000000000000..e7fc6465a9883cd1e66ee700f9587ff445aa1f2f --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -0,0 +1,531 @@ +/* + * 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_PANEL_DEFAULT_BPP 24 +#define DP_MAX_DS_PORT_COUNT 1 + +enum { + DP_LINK_RATE_MULTIPLIER = 27000000, +}; + +struct dp_panel_private { + struct device *dev; + struct dp_panel dp_panel; + struct dp_aux *aux; + struct dp_link *link; + struct dp_catalog_panel *catalog; + bool aux_cfg_update_done; +}; + +static const struct dp_panel_info fail_safe = { + .h_active = 640, + .v_active = 480, + .h_back_porch = 48, + .h_front_porch = 16, + .h_sync_width = 96, + .h_active_low = 0, + .v_back_porch = 33, + .v_front_porch = 10, + .v_sync_width = 2, + .v_active_low = 0, + .h_skew = 0, + .refresh_rate = 60, + .pixel_clk_khz = 25200, + .bpp = 24, +}; + +static int dp_panel_read_dpcd(struct dp_panel *dp_panel) +{ + int rlen, rc = 0; + struct dp_panel_private *panel; + struct drm_dp_link *link_info; + u8 *dpcd, major = 0, minor = 0; + u32 dfp_count = 0; + unsigned long caps = DP_LINK_CAP_ENHANCED_FRAMING; + + if (!dp_panel) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + dpcd = dp_panel->dpcd; + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + link_info = &dp_panel->link_info; + + rlen = drm_dp_dpcd_read(panel->aux->drm_aux, DP_DPCD_REV, + dpcd, (DP_RECEIVER_CAP_SIZE + 1)); + if (rlen < (DP_RECEIVER_CAP_SIZE + 1)) { + pr_err("dpcd read failed, rlen=%d\n", rlen); + rc = -EINVAL; + goto end; + } + + link_info->revision = dp_panel->dpcd[DP_DPCD_REV]; + + major = (link_info->revision >> 4) & 0x0f; + minor = link_info->revision & 0x0f; + pr_debug("version: %d.%d\n", major, minor); + + link_info->rate = + drm_dp_bw_code_to_link_rate(dp_panel->dpcd[DP_MAX_LINK_RATE]); + pr_debug("link_rate=%d\n", link_info->rate); + + link_info->num_lanes = dp_panel->dpcd[DP_MAX_LANE_COUNT] & + DP_MAX_LANE_COUNT_MASK; + + pr_debug("lane_count=%d\n", link_info->num_lanes); + + if (drm_dp_enhanced_frame_cap(dpcd)) + link_info->capabilities |= caps; + + dfp_count = dpcd[DP_DOWN_STREAM_PORT_COUNT] & + DP_DOWN_STREAM_PORT_COUNT; + + if ((dpcd[DP_DOWNSTREAMPORT_PRESENT] & DP_DWN_STRM_PORT_PRESENT) + && (dpcd[DP_DPCD_REV] > 0x10)) { + rlen = drm_dp_dpcd_read(panel->aux->drm_aux, + DP_DOWNSTREAM_PORT_0, dp_panel->ds_ports, + DP_MAX_DOWNSTREAM_PORTS); + if (rlen < DP_MAX_DOWNSTREAM_PORTS) { + pr_err("ds port status failed, rlen=%d\n", rlen); + rc = -EINVAL; + goto end; + } + } + + if (dfp_count > DP_MAX_DS_PORT_COUNT) + pr_debug("DS port count %d greater that max (%d) supported\n", + dfp_count, DP_MAX_DS_PORT_COUNT); + +end: + return rc; +} + +static int dp_panel_set_default_link_params(struct dp_panel *dp_panel) +{ + struct drm_dp_link *link_info; + const int default_bw_code = 162000; + const int default_num_lanes = 1; + + if (!dp_panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + link_info = &dp_panel->link_info; + link_info->rate = default_bw_code; + link_info->num_lanes = default_num_lanes; + pr_debug("link_rate=%d num_lanes=%d\n", + link_info->rate, link_info->num_lanes); + return 0; +} + +static int dp_panel_read_edid(struct dp_panel *dp_panel, + struct drm_connector *connector) +{ + int retry_cnt = 0; + const int max_retry = 10; + struct dp_panel_private *panel; + + if (!dp_panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + do { + sde_get_edid(connector, &panel->aux->drm_aux->ddc, + (void **)&dp_panel->edid_ctrl); + if (!dp_panel->edid_ctrl->edid) { + pr_err("EDID read failed\n"); + retry_cnt++; + panel->aux->reconfig(panel->aux); + panel->aux_cfg_update_done = true; + } else { + return 0; + } + } while (retry_cnt < max_retry); + + return -EINVAL; +} + +static int dp_panel_read_sink_caps(struct dp_panel *dp_panel, + struct drm_connector *connector) +{ + int rc = 0; + struct dp_panel_private *panel; + + if (!dp_panel || !connector) { + pr_err("invalid input\n"); + return -EINVAL; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + rc = dp_panel_read_dpcd(dp_panel); + if (rc || !is_link_rate_valid(drm_dp_link_rate_to_bw_code( + dp_panel->link_info.rate)) || !is_lane_count_valid( + dp_panel->link_info.num_lanes)) { + pr_err("panel dpcd read failed/incorrect, set default params\n"); + dp_panel_set_default_link_params(dp_panel); + } + + rc = dp_panel_read_edid(dp_panel, connector); + if (rc) { + pr_err("panel edid read failed, set failsafe mode\n"); + return rc; + } + + if (panel->aux_cfg_update_done) { + pr_debug("read DPCD with updated AUX config\n"); + dp_panel_read_dpcd(dp_panel); + panel->aux_cfg_update_done = false; + } + + return 0; +} + +static u32 dp_panel_get_max_pclk(struct dp_panel *dp_panel) +{ + struct drm_dp_link *link_info; + const u8 num_components = 3; + u32 bpc = 0, bpp = 0, max_data_rate_khz = 0, max_pclk_rate_khz = 0; + + if (!dp_panel) { + pr_err("invalid input\n"); + return 0; + } + + link_info = &dp_panel->link_info; + + bpc = sde_get_sink_bpc(dp_panel->edid_ctrl); + bpp = bpc * num_components; + if (!bpp) + bpp = DP_PANEL_DEFAULT_BPP; + + max_data_rate_khz = (link_info->num_lanes * link_info->rate * 8); + max_pclk_rate_khz = max_data_rate_khz / bpp; + + pr_debug("bpp=%d, max_lane_cnt=%d\n", bpp, link_info->num_lanes); + pr_debug("max_data_rate=%dKHz, max_pclk_rate=%dKHz\n", + max_data_rate_khz, max_pclk_rate_khz); + + return max_pclk_rate_khz; +} + +static void dp_panel_set_test_mode(struct dp_panel_private *panel, + struct dp_display_mode *mode) +{ + struct dp_panel_info *pinfo = NULL; + struct dp_link_test_video *test_info = NULL; + + if (!panel) { + pr_err("invalid params\n"); + return; + } + + pinfo = &mode->timing; + test_info = &panel->link->test_video; + + pinfo->h_active = test_info->test_h_width; + pinfo->h_sync_width = test_info->test_hsync_width; + pinfo->h_back_porch = test_info->test_h_start - + test_info->test_hsync_width; + pinfo->h_front_porch = test_info->test_h_total - + (test_info->test_h_start + test_info->test_h_width); + + pinfo->v_active = test_info->test_v_height; + pinfo->v_sync_width = test_info->test_vsync_width; + pinfo->v_back_porch = test_info->test_v_start - + test_info->test_vsync_width; + pinfo->v_front_porch = test_info->test_v_total - + (test_info->test_v_start + test_info->test_v_height); + + pinfo->bpp = dp_link_bit_depth_to_bpp(test_info->test_bit_depth); + pinfo->h_active_low = test_info->test_hsync_pol; + pinfo->v_active_low = test_info->test_vsync_pol; + + pinfo->refresh_rate = test_info->test_rr_n; + pinfo->pixel_clk_khz = test_info->test_h_total * + test_info->test_v_total * pinfo->refresh_rate; + + if (test_info->test_rr_d == 0) + pinfo->pixel_clk_khz /= 1000; + else + pinfo->pixel_clk_khz /= 1001; + + if (test_info->test_h_width == 640) + pinfo->pixel_clk_khz = 25170; +} + +static int dp_panel_get_modes(struct dp_panel *dp_panel, + struct drm_connector *connector, struct dp_display_mode *mode) +{ + struct dp_panel_private *panel; + + if (!dp_panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + if (dp_panel->video_test) { + dp_panel_set_test_mode(panel, mode); + return 1; + } else if (dp_panel->edid_ctrl->edid) { + return _sde_edid_update_modes(connector, dp_panel->edid_ctrl); + } + + /* fail-safe mode */ + memcpy(&mode->timing, &fail_safe, + sizeof(fail_safe)); + return 1; +} + +static void dp_panel_handle_sink_request(struct dp_panel *dp_panel) +{ + struct dp_panel_private *panel; + + if (!dp_panel) { + pr_err("invalid input\n"); + return; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + if (panel->link->sink_request & DP_TEST_LINK_EDID_READ) { + u8 checksum = sde_get_edid_checksum(dp_panel->edid_ctrl); + + panel->link->send_edid_checksum(panel->link, checksum); + panel->link->send_test_response(panel->link); + } +} + +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_edid_register(struct dp_panel *dp_panel) +{ + int rc = 0; + + if (!dp_panel) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + dp_panel->edid_ctrl = sde_edid_init(); + if (!dp_panel->edid_ctrl) { + pr_err("sde edid init for DP failed\n"); + rc = -ENOMEM; + goto end; + } +end: + return rc; +} + +static void dp_panel_edid_deregister(struct dp_panel *dp_panel) +{ + if (!dp_panel) { + pr_err("invalid input\n"); + return; + } + + sde_edid_deinit((void **)&dp_panel->edid_ctrl); +} + +static int dp_panel_init_panel_info(struct dp_panel *dp_panel) +{ + int rc = 0; + struct dp_panel_info *pinfo; + + if (!dp_panel) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + pinfo = &dp_panel->pinfo; + + /* + * print resolution info as this is a result + * of user initiated action of cable connection + */ + pr_info("SET NEW RESOLUTION:\n"); + pr_info("%dx%d@%dfps\n", pinfo->h_active, + pinfo->v_active, pinfo->refresh_rate); + pr_info("h_porches(back|front|width) = (%d|%d|%d)\n", + pinfo->h_back_porch, + pinfo->h_front_porch, + pinfo->h_sync_width); + pr_info("v_porches(back|front|width) = (%d|%d|%d)\n", + pinfo->v_back_porch, + pinfo->v_front_porch, + pinfo->v_sync_width); + pr_info("pixel clock (KHz)=(%d)\n", pinfo->pixel_clk_khz); + pr_info("bpp = %d\n", pinfo->bpp); + pr_info("active low (h|v)=(%d|%d)\n", pinfo->h_active_low, + pinfo->v_active_low); + + pinfo->bpp = max_t(u32, 18, min_t(u32, pinfo->bpp, 30)); + pr_info("updated bpp = %d\n", pinfo->bpp); +end: + return rc; +} + +static u32 dp_panel_get_min_req_link_rate(struct dp_panel *dp_panel) +{ + const u32 encoding_factx10 = 8; + u32 min_link_rate_khz = 0, lane_cnt; + struct dp_panel_info *pinfo; + + if (!dp_panel) { + pr_err("invalid input\n"); + goto end; + } + + lane_cnt = dp_panel->link_info.num_lanes; + pinfo = &dp_panel->pinfo; + + /* num_lanes * lane_count * 8 >= pclk * bpp * 10 */ + min_link_rate_khz = pinfo->pixel_clk_khz / + (lane_cnt * encoding_factx10); + min_link_rate_khz *= pinfo->bpp; + + pr_debug("min lclk req=%d khz for pclk=%d khz, lanes=%d, bpp=%d\n", + min_link_rate_khz, pinfo->pixel_clk_khz, lane_cnt, + pinfo->bpp); +end: + return min_link_rate_khz; +} + +struct dp_panel *dp_panel_get(struct dp_panel_in *in) +{ + int rc = 0; + struct dp_panel_private *panel; + struct dp_panel *dp_panel; + + if (!in->dev || !in->catalog || !in->aux || !in->link) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + panel = devm_kzalloc(in->dev, sizeof(*panel), GFP_KERNEL); + if (!panel) { + rc = -ENOMEM; + goto error; + } + + panel->dev = in->dev; + panel->aux = in->aux; + panel->catalog = in->catalog; + panel->link = in->link; + + dp_panel = &panel->dp_panel; + panel->aux_cfg_update_done = false; + + dp_panel->sde_edid_register = dp_panel_edid_register; + dp_panel->sde_edid_deregister = dp_panel_edid_deregister; + dp_panel->init_info = dp_panel_init_panel_info; + dp_panel->timing_cfg = dp_panel_timing_cfg; + dp_panel->read_sink_caps = dp_panel_read_sink_caps; + dp_panel->get_min_req_link_rate = dp_panel_get_min_req_link_rate; + dp_panel->get_max_pclk = dp_panel_get_max_pclk; + dp_panel->get_modes = dp_panel_get_modes; + dp_panel->handle_sink_request = dp_panel_handle_sink_request; + + 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, 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 0000000000000000000000000000000000000000..4486a8445fe483c41a4367ac9fe10d19e7ca449b --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -0,0 +1,116 @@ +/* + * 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" +#include "dp_link.h" +#include "dp_usbpd.h" +#include "sde_edid_parser.h" + +enum dp_lane_count { + DP_LANE_COUNT_1 = 1, + DP_LANE_COUNT_2 = 2, + DP_LANE_COUNT_4 = 4, +}; + +#define DP_MAX_DOWNSTREAM_PORTS 0x10 + +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_display_mode { + struct dp_panel_info timing; + u32 capabilities; +}; + +struct dp_panel_in { + struct device *dev; + struct dp_aux *aux; + struct dp_link *link; + struct dp_catalog_panel *catalog; +}; + +struct dp_panel { + /* dpcd raw data */ + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + u8 ds_ports[DP_MAX_DOWNSTREAM_PORTS]; + + struct drm_dp_link link_info; + struct sde_edid_ctrl *edid_ctrl; + struct drm_connector *connector; + struct dp_panel_info pinfo; + bool video_test; + + u32 vic; + u32 max_pclk_khz; + + int (*sde_edid_register)(struct dp_panel *dp_panel); + void (*sde_edid_deregister)(struct dp_panel *dp_panel); + int (*init_info)(struct dp_panel *dp_panel); + int (*timing_cfg)(struct dp_panel *dp_panel); + int (*read_sink_caps)(struct dp_panel *dp_panel, + struct drm_connector *connector); + u32 (*get_min_req_link_rate)(struct dp_panel *dp_panel); + u32 (*get_max_pclk)(struct dp_panel *dp_panel); + int (*get_modes)(struct dp_panel *dp_panel, + struct drm_connector *connector, struct dp_display_mode *mode); + void (*handle_sink_request)(struct dp_panel *dp_panel); +}; + +/** + * 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 inline bool is_link_rate_valid(u32 bw_code) +{ + return ((bw_code == DP_LINK_BW_1_62) || + (bw_code == DP_LINK_BW_2_7) || + (bw_code == DP_LINK_BW_5_4) || + (bw_code == DP_LINK_BW_8_1)); +} + +/** + * 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 inline bool 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); +} + +struct dp_panel *dp_panel_get(struct dp_panel_in *in); +void dp_panel_put(struct dp_panel *dp_panel); +#endif /* _DP_PANEL_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_parser.c b/drivers/gpu/drm/msm/dp/dp_parser.c new file mode 100644 index 0000000000000000000000000000000000000000..c8da99a65b5766205b8ff88532cdb69023a4494a --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_parser.c @@ -0,0 +1,645 @@ +/* + * 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 + +#include "dp_parser.h" + +static void dp_parser_unmap_io_resources(struct dp_parser *parser) +{ + struct dp_io *io = &parser->io; + + msm_dss_iounmap(&io->ctrl_io); + msm_dss_iounmap(&io->phy_io); + msm_dss_iounmap(&io->ln_tx0_io); + msm_dss_iounmap(&io->ln_tx0_io); + msm_dss_iounmap(&io->dp_pll_io); + msm_dss_iounmap(&io->dp_cc_io); + msm_dss_iounmap(&io->usb3_dp_com); + msm_dss_iounmap(&io->qfprom_io); + msm_dss_iounmap(&io->hdcp_io); +} + +static int dp_parser_ctrl_res(struct dp_parser *parser) +{ + int rc = 0; + u32 index; + struct platform_device *pdev = parser->pdev; + struct device_node *of_node = parser->pdev->dev.of_node; + struct dp_io *io = &parser->io; + + rc = of_property_read_u32(of_node, "cell-index", &index); + if (rc) { + pr_err("cell-index not specified, rc=%d\n", rc); + goto err; + } + + rc = msm_dss_ioremap_byname(pdev, &io->ctrl_io, "dp_ctrl"); + if (rc) { + pr_err("unable to remap dp io resources\n"); + goto err; + } + + rc = msm_dss_ioremap_byname(pdev, &io->phy_io, "dp_phy"); + if (rc) { + pr_err("unable to remap dp PHY resources\n"); + goto err; + } + + rc = msm_dss_ioremap_byname(pdev, &io->ln_tx0_io, "dp_ln_tx0"); + if (rc) { + pr_err("unable to remap dp TX0 resources\n"); + goto err; + } + + rc = msm_dss_ioremap_byname(pdev, &io->ln_tx1_io, "dp_ln_tx1"); + if (rc) { + pr_err("unable to remap dp TX1 resources\n"); + goto err; + } + + rc = msm_dss_ioremap_byname(pdev, &io->dp_pll_io, "dp_pll"); + if (rc) { + pr_err("unable to remap DP PLL resources\n"); + goto err; + } + + rc = msm_dss_ioremap_byname(pdev, &io->usb3_dp_com, "usb3_dp_com"); + if (rc) { + pr_err("unable to remap USB3 DP com resources\n"); + goto err; + } + + if (msm_dss_ioremap_byname(pdev, &io->dp_cc_io, "dp_mmss_cc")) { + pr_err("unable to remap dp MMSS_CC resources\n"); + goto err; + } + + if (msm_dss_ioremap_byname(pdev, &io->qfprom_io, "qfprom_physical")) + pr_warn("unable to remap dp qfprom resources\n"); + + if (msm_dss_ioremap_byname(pdev, &io->hdcp_io, "hdcp_physical")) + pr_warn("unable to remap dp hdcp resources\n"); + + return 0; +err: + dp_parser_unmap_io_resources(parser); + return rc; +} + +static const char *dp_get_phy_aux_config_property(u32 cfg_type) +{ + switch (cfg_type) { + case PHY_AUX_CFG0: + return "qcom,aux-cfg0-settings"; + case PHY_AUX_CFG1: + return "qcom,aux-cfg1-settings"; + case PHY_AUX_CFG2: + return "qcom,aux-cfg2-settings"; + case PHY_AUX_CFG3: + return "qcom,aux-cfg3-settings"; + case PHY_AUX_CFG4: + return "qcom,aux-cfg4-settings"; + case PHY_AUX_CFG5: + return "qcom,aux-cfg5-settings"; + case PHY_AUX_CFG6: + return "qcom,aux-cfg6-settings"; + case PHY_AUX_CFG7: + return "qcom,aux-cfg7-settings"; + case PHY_AUX_CFG8: + return "qcom,aux-cfg8-settings"; + case PHY_AUX_CFG9: + return "qcom,aux-cfg9-settings"; + default: + return "unknown"; + } +} + +static void dp_parser_phy_aux_cfg_reset(struct dp_parser *parser) +{ + int i = 0; + + for (i = 0; i < PHY_AUX_CFG_MAX; i++) + parser->aux_cfg[i] = (const struct dp_aux_cfg){ 0 }; +} + +static int dp_parser_aux(struct dp_parser *parser) +{ + struct device_node *of_node = parser->pdev->dev.of_node; + int len = 0, i = 0, j = 0, config_count = 0; + const char *data; + int const minimum_config_count = 1; + + for (i = 0; i < PHY_AUX_CFG_MAX; i++) { + const char *property = dp_get_phy_aux_config_property(i); + + data = of_get_property(of_node, property, &len); + if (!data) { + pr_err("Unable to read %s\n", property); + goto error; + } + + config_count = len - 1; + if ((config_count < minimum_config_count) || + (config_count > DP_AUX_CFG_MAX_VALUE_CNT)) { + pr_err("Invalid config count (%d) configs for %s\n", + config_count, property); + goto error; + } + + parser->aux_cfg[i].offset = data[0]; + parser->aux_cfg[i].cfg_cnt = config_count; + pr_debug("%s offset=0x%x, cfg_cnt=%d\n", + property, + parser->aux_cfg[i].offset, + parser->aux_cfg[i].cfg_cnt); + for (j = 1; j < len; j++) { + parser->aux_cfg[i].lut[j - 1] = data[j]; + pr_debug("%s lut[%d]=0x%x\n", + property, + i, + parser->aux_cfg[i].lut[j - 1]); + } + } + return 0; + +error: + dp_parser_phy_aux_cfg_reset(parser); + return -EINVAL; +} + +static int dp_parser_misc(struct dp_parser *parser) +{ + int rc = 0; + struct device_node *of_node = parser->pdev->dev.of_node; + + rc = of_property_read_u32(of_node, + "qcom,max-pclk-frequency-khz", &parser->max_pclk_khz); + if (rc) + parser->max_pclk_khz = DP_MAX_PIXEL_CLK_KHZ; + + return 0; +} + +static int dp_parser_pinctrl(struct dp_parser *parser) +{ + int rc = 0; + struct dp_pinctrl *pinctrl = &parser->pinctrl; + + pinctrl->pin = devm_pinctrl_get(&parser->pdev->dev); + + if (IS_ERR_OR_NULL(pinctrl->pin)) { + rc = PTR_ERR(pinctrl->pin); + pr_err("failed to get pinctrl, rc=%d\n", rc); + goto error; + } + + pinctrl->state_active = pinctrl_lookup_state(pinctrl->pin, + "mdss_dp_active"); + if (IS_ERR_OR_NULL(pinctrl->state_active)) { + rc = PTR_ERR(pinctrl->state_active); + pr_err("failed to get pinctrl active state, rc=%d\n", rc); + goto error; + } + + pinctrl->state_suspend = pinctrl_lookup_state(pinctrl->pin, + "mdss_dp_sleep"); + if (IS_ERR_OR_NULL(pinctrl->state_suspend)) { + rc = PTR_ERR(pinctrl->state_suspend); + pr_err("failed to get pinctrl suspend state, rc=%d\n", rc); + goto error; + } +error: + return rc; +} + +static int dp_parser_gpio(struct dp_parser *parser) +{ + int i = 0; + struct device *dev = &parser->pdev->dev; + struct device_node *of_node = dev->of_node; + struct dss_module_power *mp = &parser->mp[DP_CORE_PM]; + static const char * const dp_gpios[] = { + "qcom,aux-en-gpio", + "qcom,aux-sel-gpio", + "qcom,usbplug-cc-gpio", + }; + + mp->gpio_config = devm_kzalloc(dev, + sizeof(struct dss_gpio) * ARRAY_SIZE(dp_gpios), GFP_KERNEL); + mp->num_gpio = ARRAY_SIZE(dp_gpios); + + for (i = 0; i < ARRAY_SIZE(dp_gpios); i++) { + mp->gpio_config[i].gpio = of_get_named_gpio(of_node, + dp_gpios[i], 0); + + if (!gpio_is_valid(mp->gpio_config[i].gpio)) { + pr_err("%s gpio not specified\n", dp_gpios[i]); + return -EINVAL; + } + + strlcpy(mp->gpio_config[i].gpio_name, dp_gpios[i], + sizeof(mp->gpio_config[i].gpio_name)); + + mp->gpio_config[i].value = 0; + } + + return 0; +} + +static const char *dp_parser_supply_node_name(enum dp_pm_type module) +{ + switch (module) { + case DP_CORE_PM: return "qcom,core-supply-entries"; + case DP_CTRL_PM: return "qcom,ctrl-supply-entries"; + case DP_PHY_PM: return "qcom,phy-supply-entries"; + default: return "???"; + } +} + +static int dp_parser_get_vreg(struct dp_parser *parser, + enum dp_pm_type module) +{ + int i = 0, rc = 0; + u32 tmp = 0; + const char *pm_supply_name = NULL; + struct device_node *supply_node = NULL; + struct device_node *of_node = parser->pdev->dev.of_node; + struct device_node *supply_root_node = NULL; + struct dss_module_power *mp = &parser->mp[module]; + + mp->num_vreg = 0; + pm_supply_name = dp_parser_supply_node_name(module); + supply_root_node = of_get_child_by_name(of_node, pm_supply_name); + if (!supply_root_node) { + pr_err("no supply entry present: %s\n", pm_supply_name); + goto novreg; + } + + mp->num_vreg = of_get_available_child_count(supply_root_node); + + if (mp->num_vreg == 0) { + pr_debug("no vreg\n"); + goto novreg; + } else { + pr_debug("vreg found. count=%d\n", mp->num_vreg); + } + + mp->vreg_config = devm_kzalloc(&parser->pdev->dev, + sizeof(struct dss_vreg) * mp->num_vreg, GFP_KERNEL); + if (!mp->vreg_config) { + rc = -ENOMEM; + goto error; + } + + for_each_child_of_node(supply_root_node, supply_node) { + const char *st = NULL; + /* vreg-name */ + rc = of_property_read_string(supply_node, + "qcom,supply-name", &st); + if (rc) { + pr_err("error reading name. rc=%d\n", + rc); + goto error; + } + snprintf(mp->vreg_config[i].vreg_name, + ARRAY_SIZE((mp->vreg_config[i].vreg_name)), "%s", st); + /* vreg-min-voltage */ + rc = of_property_read_u32(supply_node, + "qcom,supply-min-voltage", &tmp); + if (rc) { + pr_err("error reading min volt. rc=%d\n", + rc); + goto error; + } + mp->vreg_config[i].min_voltage = tmp; + + /* vreg-max-voltage */ + rc = of_property_read_u32(supply_node, + "qcom,supply-max-voltage", &tmp); + if (rc) { + pr_err("error reading max volt. rc=%d\n", + rc); + goto error; + } + mp->vreg_config[i].max_voltage = tmp; + + /* enable-load */ + rc = of_property_read_u32(supply_node, + "qcom,supply-enable-load", &tmp); + if (rc) { + pr_err("error reading enable load. rc=%d\n", + rc); + goto error; + } + mp->vreg_config[i].enable_load = tmp; + + /* disable-load */ + rc = of_property_read_u32(supply_node, + "qcom,supply-disable-load", &tmp); + if (rc) { + pr_err("error reading disable load. rc=%d\n", + rc); + goto error; + } + mp->vreg_config[i].disable_load = tmp; + + pr_debug("%s min=%d, max=%d, enable=%d, disable=%d\n", + mp->vreg_config[i].vreg_name, + mp->vreg_config[i].min_voltage, + mp->vreg_config[i].max_voltage, + mp->vreg_config[i].enable_load, + mp->vreg_config[i].disable_load + ); + ++i; + } + + return rc; + +error: + if (mp->vreg_config) { + devm_kfree(&parser->pdev->dev, mp->vreg_config); + mp->vreg_config = NULL; + } +novreg: + mp->num_vreg = 0; + + return rc; +} + +static void dp_parser_put_vreg_data(struct device *dev, + struct dss_module_power *mp) +{ + if (!mp) { + DEV_ERR("invalid input\n"); + return; + } + + if (mp->vreg_config) { + devm_kfree(dev, mp->vreg_config); + mp->vreg_config = NULL; + } + mp->num_vreg = 0; +} + +static int dp_parser_regulator(struct dp_parser *parser) +{ + int i, rc = 0; + struct platform_device *pdev = parser->pdev; + + /* Parse the regulator information */ + for (i = DP_CORE_PM; i < DP_MAX_PM; i++) { + rc = dp_parser_get_vreg(parser, i); + if (rc) { + pr_err("get_dt_vreg_data failed for %s. rc=%d\n", + dp_parser_pm_name(i), rc); + i--; + for (; i >= DP_CORE_PM; i--) + dp_parser_put_vreg_data(&pdev->dev, + &parser->mp[i]); + break; + } + } + + return rc; +} + +static bool dp_parser_check_prefix(const char *clk_prefix, const char *clk_name) +{ + return !!strnstr(clk_name, clk_prefix, strlen(clk_name)); +} + +static void dp_parser_put_clk_data(struct device *dev, + struct dss_module_power *mp) +{ + if (!mp) { + DEV_ERR("%s: invalid input\n", __func__); + return; + } + + if (mp->clk_config) { + devm_kfree(dev, mp->clk_config); + mp->clk_config = NULL; + } + + mp->num_clk = 0; +} + +static int dp_parser_init_clk_data(struct dp_parser *parser) +{ + int num_clk = 0, i = 0, rc = 0; + int core_clk_count = 0, ctrl_clk_count = 0; + const char *core_clk = "core"; + const char *ctrl_clk = "ctrl"; + const char *clk_name; + struct device *dev = &parser->pdev->dev; + struct dss_module_power *core_power = &parser->mp[DP_CORE_PM]; + struct dss_module_power *ctrl_power = &parser->mp[DP_CTRL_PM]; + + num_clk = of_property_count_strings(dev->of_node, "clock-names"); + if (num_clk <= 0) { + pr_err("no clocks are defined\n"); + rc = -EINVAL; + goto exit; + } + + for (i = 0; i < num_clk; i++) { + of_property_read_string_index(dev->of_node, + "clock-names", i, &clk_name); + + if (dp_parser_check_prefix(core_clk, clk_name)) + core_clk_count++; + + if (dp_parser_check_prefix(ctrl_clk, clk_name)) + ctrl_clk_count++; + } + + /* Initialize the CORE power module */ + if (core_clk_count <= 0) { + pr_err("no core clocks are defined\n"); + rc = -EINVAL; + goto exit; + } + + core_power->num_clk = core_clk_count; + core_power->clk_config = devm_kzalloc(dev, + sizeof(struct dss_clk) * core_power->num_clk, + GFP_KERNEL); + if (!core_power->clk_config) { + rc = -EINVAL; + goto exit; + } + + /* Initialize the CTRL power module */ + if (ctrl_clk_count <= 0) { + pr_err("no ctrl clocks are defined\n"); + rc = -EINVAL; + goto ctrl_clock_error; + } + + ctrl_power->num_clk = ctrl_clk_count; + ctrl_power->clk_config = devm_kzalloc(dev, + sizeof(struct dss_clk) * ctrl_power->num_clk, + GFP_KERNEL); + if (!ctrl_power->clk_config) { + ctrl_power->num_clk = 0; + rc = -EINVAL; + goto ctrl_clock_error; + } + + return rc; + +ctrl_clock_error: + dp_parser_put_clk_data(dev, core_power); +exit: + return rc; +} + +static int dp_parser_clock(struct dp_parser *parser) +{ + int rc = 0, i = 0; + int num_clk = 0; + int core_clk_index = 0, ctrl_clk_index = 0; + int core_clk_count = 0, ctrl_clk_count = 0; + const char *clk_name; + const char *core_clk = "core"; + const char *ctrl_clk = "ctrl"; + struct device *dev = &parser->pdev->dev; + struct dss_module_power *core_power = &parser->mp[DP_CORE_PM]; + struct dss_module_power *ctrl_power = &parser->mp[DP_CTRL_PM]; + + core_power = &parser->mp[DP_CORE_PM]; + ctrl_power = &parser->mp[DP_CTRL_PM]; + + rc = dp_parser_init_clk_data(parser); + if (rc) { + pr_err("failed to initialize power data\n"); + rc = -EINVAL; + goto exit; + } + + core_clk_count = core_power->num_clk; + ctrl_clk_count = ctrl_power->num_clk; + + num_clk = core_clk_count + ctrl_clk_count; + + for (i = 0; i < num_clk; i++) { + of_property_read_string_index(dev->of_node, "clock-names", + i, &clk_name); + + if (dp_parser_check_prefix(core_clk, clk_name) && + core_clk_index < core_clk_count) { + struct dss_clk *clk = + &core_power->clk_config[core_clk_index]; + strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); + clk->type = DSS_CLK_AHB; + core_clk_index++; + } else if (dp_parser_check_prefix(ctrl_clk, clk_name) && + ctrl_clk_index < ctrl_clk_count) { + struct dss_clk *clk = + &ctrl_power->clk_config[ctrl_clk_index]; + strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); + ctrl_clk_index++; + + if (!strcmp(clk_name, "ctrl_link_clk") || + !strcmp(clk_name, "ctrl_pixel_clk")) + clk->type = DSS_CLK_PCLK; + else + clk->type = DSS_CLK_AHB; + } + } + + pr_debug("clock parsing successful\n"); + +exit: + return rc; +} + +static int dp_parser_parse(struct dp_parser *parser) +{ + int rc = 0; + + if (!parser) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto err; + } + + rc = dp_parser_ctrl_res(parser); + if (rc) + goto err; + + rc = dp_parser_aux(parser); + if (rc) + goto err; + + rc = dp_parser_misc(parser); + if (rc) + goto err; + + rc = dp_parser_clock(parser); + if (rc) + goto err; + + rc = dp_parser_regulator(parser); + if (rc) + goto err; + + rc = dp_parser_gpio(parser); + if (rc) + goto err; + + rc = dp_parser_pinctrl(parser); +err: + return rc; +} + +struct dp_parser *dp_parser_get(struct platform_device *pdev) +{ + struct dp_parser *parser; + + parser = devm_kzalloc(&pdev->dev, sizeof(*parser), GFP_KERNEL); + if (!parser) + return ERR_PTR(-ENOMEM); + + parser->parse = dp_parser_parse; + parser->pdev = pdev; + + return parser; +} + +void dp_parser_put(struct dp_parser *parser) +{ + int i = 0; + struct dss_module_power *power = NULL; + + if (!parser) { + pr_err("invalid parser module\n"); + return; + } + + power = parser->mp; + + for (i = 0; i < DP_MAX_PM; i++) { + struct dss_module_power *mp = &power[i]; + + devm_kfree(&parser->pdev->dev, mp->clk_config); + devm_kfree(&parser->pdev->dev, mp->vreg_config); + devm_kfree(&parser->pdev->dev, mp->gpio_config); + } + + devm_kfree(&parser->pdev->dev, parser); +} diff --git a/drivers/gpu/drm/msm/dp/dp_parser.h b/drivers/gpu/drm/msm/dp/dp_parser.h new file mode 100644 index 0000000000000000000000000000000000000000..d11fe8fb3f118c83c435c9e12f14928473974869 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_parser.h @@ -0,0 +1,200 @@ +/* + * 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_PARSER_H_ +#define _DP_PARSER_H_ + +#include + +#define DP_LABEL "MDSS DP DISPLAY" +#define AUX_CFG_LEN 10 +#define DP_MAX_PIXEL_CLK_KHZ 675000 + +enum dp_pm_type { + DP_CORE_PM, + DP_CTRL_PM, + DP_PHY_PM, + DP_MAX_PM +}; + +static inline const char *dp_parser_pm_name(enum dp_pm_type module) +{ + switch (module) { + case DP_CORE_PM: return "DP_CORE_PM"; + case DP_CTRL_PM: return "DP_CTRL_PM"; + case DP_PHY_PM: return "DP_PHY_PM"; + default: return "???"; + } +} + +/** + * struct dp_display_data - display related device tree data. + * + * @ctrl_node: referece to controller device + * @phy_node: reference to phy device + * @is_active: is the controller currently active + * @name: name of the display + * @display_type: type of the display + */ +struct dp_display_data { + struct device_node *ctrl_node; + struct device_node *phy_node; + bool is_active; + const char *name; + const char *display_type; +}; + +/** + * struct dp_ctrl_resource - controller's IO related data + * + * @ctrl_io: controller's mapped memory address + * @phy_io: phy's mapped memory address + * @ln_tx0_io: USB-DP lane TX0's mapped memory address + * @ln_tx1_io: USB-DP lane TX1's mapped memory address + * @dp_cc_io: DP cc's mapped memory address + * @qfprom_io: qfprom's mapped memory address + * @dp_pll_io: DP PLL mapped memory address + * @usb3_dp_com: USB3 DP PHY combo mapped memory address + * @hdcp_io: hdcp's mapped memory address + */ +struct dp_io { + struct dss_io_data ctrl_io; + struct dss_io_data phy_io; + struct dss_io_data ln_tx0_io; + struct dss_io_data ln_tx1_io; + struct dss_io_data dp_cc_io; + struct dss_io_data qfprom_io; + struct dss_io_data dp_pll_io; + struct dss_io_data usb3_dp_com; + struct dss_io_data hdcp_io; +}; + +/** + * struct dp_pinctrl - DP's pin control + * + * @pin: pin-controller's instance + * @state_active: active state pin control + * @state_hpd_active: hpd active state pin control + * @state_suspend: suspend state pin control + */ +struct dp_pinctrl { + struct pinctrl *pin; + struct pinctrl_state *state_active; + struct pinctrl_state *state_hpd_active; + struct pinctrl_state *state_suspend; +}; + +#define DP_ENUM_STR(x) #x +#define DP_AUX_CFG_MAX_VALUE_CNT 3 +/** + * struct dp_aux_cfg - DP's AUX configuration settings + * + * @cfg_cnt: count of the configurable settings for the AUX register + * @current_index: current index of the AUX config lut + * @offset: register offset of the AUX config register + * @lut: look up table for the AUX config values for this register + */ +struct dp_aux_cfg { + u32 cfg_cnt; + u32 current_index; + u32 offset; + u32 lut[DP_AUX_CFG_MAX_VALUE_CNT]; +}; + +/* PHY AUX config registers */ +enum dp_phy_aux_config_type { + PHY_AUX_CFG0, + PHY_AUX_CFG1, + PHY_AUX_CFG2, + PHY_AUX_CFG3, + PHY_AUX_CFG4, + PHY_AUX_CFG5, + PHY_AUX_CFG6, + PHY_AUX_CFG7, + PHY_AUX_CFG8, + PHY_AUX_CFG9, + PHY_AUX_CFG_MAX, +}; + +static inline char *dp_phy_aux_config_type_to_string(u32 cfg_type) +{ + switch (cfg_type) { + case PHY_AUX_CFG0: + return DP_ENUM_STR(PHY_AUX_CFG0); + case PHY_AUX_CFG1: + return DP_ENUM_STR(PHY_AUX_CFG1); + case PHY_AUX_CFG2: + return DP_ENUM_STR(PHY_AUX_CFG2); + case PHY_AUX_CFG3: + return DP_ENUM_STR(PHY_AUX_CFG3); + case PHY_AUX_CFG4: + return DP_ENUM_STR(PHY_AUX_CFG4); + case PHY_AUX_CFG5: + return DP_ENUM_STR(PHY_AUX_CFG5); + case PHY_AUX_CFG6: + return DP_ENUM_STR(PHY_AUX_CFG6); + case PHY_AUX_CFG7: + return DP_ENUM_STR(PHY_AUX_CFG7); + case PHY_AUX_CFG8: + return DP_ENUM_STR(PHY_AUX_CFG8); + case PHY_AUX_CFG9: + return DP_ENUM_STR(PHY_AUX_CFG9); + default: + return "unknown"; + } +} + +/** + * struct dp_parser - DP parser's data exposed to clients + * + * @pdev: platform data of the client + * @mp: gpio, regulator and clock related data + * @pinctrl: pin-control related data + * @disp_data: controller's display related data + * @parse: function to be called by client to parse device tree. + */ +struct dp_parser { + struct platform_device *pdev; + struct dss_module_power mp[DP_MAX_PM]; + struct dp_pinctrl pinctrl; + struct dp_io io; + struct dp_display_data disp_data; + + u8 l_map[4]; + struct dp_aux_cfg aux_cfg[AUX_CFG_LEN]; + u32 max_pclk_khz; + + int (*parse)(struct dp_parser *parser); +}; + +/** + * dp_parser_get() - get the DP's device tree parser module + * + * @pdev: platform data of the client + * return: pointer to dp_parser structure. + * + * This function provides client capability to parse the + * device tree and populate the data structures. The data + * related to clock, regulators, pin-control and other + * can be parsed using this module. + */ +struct dp_parser *dp_parser_get(struct platform_device *pdev); + +/** + * dp_parser_put() - cleans the dp_parser module + * + * @parser: pointer to the parser's data. + */ +void dp_parser_put(struct dp_parser *parser); +#endif diff --git a/drivers/gpu/drm/msm/dp/dp_power.c b/drivers/gpu/drm/msm/dp/dp_power.c new file mode 100644 index 0000000000000000000000000000000000000000..eb787faa2aaf007b2ca9e151bb52d8a29d226bb0 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_power.c @@ -0,0 +1,623 @@ +/* + * 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 +#include "dp_power.h" + +#define DP_CLIENT_NAME_SIZE 20 + +struct dp_power_private { + struct dp_parser *parser; + struct platform_device *pdev; + struct clk *pixel_clk_rcg; + struct clk *pixel_parent; + + struct dp_power dp_power; + struct sde_power_client *dp_core_client; + struct sde_power_handle *phandle; + + bool core_clks_on; + bool link_clks_on; +}; + +static int dp_power_regulator_init(struct dp_power_private *power) +{ + int rc = 0, i = 0, j = 0; + struct platform_device *pdev; + struct dp_parser *parser; + + parser = power->parser; + pdev = power->pdev; + + for (i = DP_CORE_PM; !rc && (i < DP_MAX_PM); i++) { + rc = msm_dss_config_vreg(&pdev->dev, + parser->mp[i].vreg_config, + parser->mp[i].num_vreg, 1); + if (rc) { + pr_err("failed to init vregs for %s\n", + dp_parser_pm_name(i)); + for (j = i - 1; j >= DP_CORE_PM; j--) { + msm_dss_config_vreg(&pdev->dev, + parser->mp[j].vreg_config, + parser->mp[j].num_vreg, 0); + } + + goto error; + } + } +error: + return rc; +} + +static void dp_power_regulator_deinit(struct dp_power_private *power) +{ + int rc = 0, i = 0; + struct platform_device *pdev; + struct dp_parser *parser; + + parser = power->parser; + pdev = power->pdev; + + for (i = DP_CORE_PM; (i < DP_MAX_PM); i++) { + rc = msm_dss_config_vreg(&pdev->dev, + parser->mp[i].vreg_config, + parser->mp[i].num_vreg, 0); + if (rc) + pr_err("failed to deinit vregs for %s\n", + dp_parser_pm_name(i)); + } +} + +static int dp_power_regulator_ctrl(struct dp_power_private *power, bool enable) +{ + int rc = 0, i = 0, j = 0; + struct dp_parser *parser; + + parser = power->parser; + + for (i = DP_CORE_PM; i < DP_MAX_PM; i++) { + rc = msm_dss_enable_vreg( + parser->mp[i].vreg_config, + parser->mp[i].num_vreg, enable); + if (rc) { + pr_err("failed to '%s' vregs for %s\n", + enable ? "enable" : "disable", + dp_parser_pm_name(i)); + if (enable) { + for (j = i-1; j >= DP_CORE_PM; j--) { + msm_dss_enable_vreg( + parser->mp[j].vreg_config, + parser->mp[j].num_vreg, 0); + } + } + goto error; + } + } +error: + return rc; +} + +static int dp_power_pinctrl_set(struct dp_power_private *power, bool active) +{ + int rc = -EFAULT; + struct pinctrl_state *pin_state; + struct dp_parser *parser; + + parser = power->parser; + + if (IS_ERR_OR_NULL(parser->pinctrl.pin)) + return PTR_ERR(parser->pinctrl.pin); + + pin_state = active ? parser->pinctrl.state_active + : parser->pinctrl.state_suspend; + if (!IS_ERR_OR_NULL(pin_state)) { + rc = pinctrl_select_state(parser->pinctrl.pin, + pin_state); + if (rc) + pr_err("can not set %s pins\n", + active ? "dp_active" + : "dp_sleep"); + } else { + pr_err("invalid '%s' pinstate\n", + active ? "dp_active" + : "dp_sleep"); + } + + return rc; +} + +static int dp_power_clk_init(struct dp_power_private *power, bool enable) +{ + int rc = 0; + struct dss_module_power *core, *ctrl; + struct device *dev; + + core = &power->parser->mp[DP_CORE_PM]; + ctrl = &power->parser->mp[DP_CTRL_PM]; + + dev = &power->pdev->dev; + + if (!core || !ctrl) { + pr_err("invalid power_data\n"); + rc = -EINVAL; + goto exit; + } + + if (enable) { + rc = msm_dss_get_clk(dev, core->clk_config, core->num_clk); + if (rc) { + pr_err("failed to get %s clk. err=%d\n", + dp_parser_pm_name(DP_CORE_PM), rc); + goto exit; + } + + rc = msm_dss_get_clk(dev, ctrl->clk_config, ctrl->num_clk); + if (rc) { + pr_err("failed to get %s clk. err=%d\n", + dp_parser_pm_name(DP_CTRL_PM), rc); + goto ctrl_get_error; + } + + power->pixel_clk_rcg = devm_clk_get(dev, "pixel_clk_rcg"); + if (IS_ERR(power->pixel_clk_rcg)) { + pr_debug("Unable to get DP pixel clk RCG\n"); + power->pixel_clk_rcg = NULL; + } + + power->pixel_parent = devm_clk_get(dev, "pixel_parent"); + if (IS_ERR(power->pixel_parent)) { + pr_debug("Unable to get DP pixel RCG parent\n"); + power->pixel_parent = NULL; + } + } else { + if (power->pixel_parent) + devm_clk_put(dev, power->pixel_parent); + + if (power->pixel_clk_rcg) + devm_clk_put(dev, power->pixel_clk_rcg); + + msm_dss_put_clk(ctrl->clk_config, ctrl->num_clk); + msm_dss_put_clk(core->clk_config, core->num_clk); + } + + return rc; + +ctrl_get_error: + msm_dss_put_clk(core->clk_config, core->num_clk); +exit: + return rc; +} + +static int dp_power_clk_set_rate(struct dp_power_private *power, + enum dp_pm_type module, bool enable) +{ + int rc = 0; + struct dss_module_power *mp; + + if (!power) { + pr_err("invalid power data\n"); + rc = -EINVAL; + goto exit; + } + + mp = &power->parser->mp[module]; + + if (enable) { + rc = msm_dss_clk_set_rate(mp->clk_config, mp->num_clk); + if (rc) { + pr_err("failed to set clks rate.\n"); + goto exit; + } + + rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, 1); + if (rc) { + pr_err("failed to enable clks\n"); + goto exit; + } + } else { + rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, 0); + if (rc) { + pr_err("failed to disable clks\n"); + goto exit; + } + } +exit: + return rc; +} + +static int dp_power_clk_enable(struct dp_power *dp_power, + enum dp_pm_type pm_type, bool enable) +{ + int rc = 0; + struct dss_module_power *mp; + struct dp_power_private *power; + + if (!dp_power) { + pr_err("invalid power data\n"); + rc = -EINVAL; + goto error; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + mp = &power->parser->mp[pm_type]; + + if ((pm_type != DP_CORE_PM) && (pm_type != DP_CTRL_PM)) { + pr_err("unsupported power module: %s\n", + dp_parser_pm_name(pm_type)); + return -EINVAL; + } + + if (enable) { + if ((pm_type == DP_CORE_PM) + && (power->core_clks_on)) { + pr_debug("core clks already enabled\n"); + return 0; + } + + if ((pm_type == DP_CTRL_PM) + && (power->link_clks_on)) { + pr_debug("links clks already enabled\n"); + return 0; + } + + if ((pm_type == DP_CTRL_PM) && (!power->core_clks_on)) { + pr_debug("Need to enable core clks before link clks\n"); + + rc = dp_power_clk_set_rate(power, pm_type, enable); + if (rc) { + pr_err("failed to enable clks: %s. err=%d\n", + dp_parser_pm_name(DP_CORE_PM), rc); + goto error; + } else { + power->core_clks_on = true; + } + } + } + + rc = dp_power_clk_set_rate(power, pm_type, enable); + if (rc) { + pr_err("failed to '%s' clks for: %s. err=%d\n", + enable ? "enable" : "disable", + dp_parser_pm_name(pm_type), rc); + goto error; + } + + if (pm_type == DP_CORE_PM) + power->core_clks_on = enable; + else + power->link_clks_on = enable; + + pr_debug("%s clocks for %s\n", + enable ? "enable" : "disable", + dp_parser_pm_name(pm_type)); + pr_debug("link_clks:%s core_clks:%s\n", + power->link_clks_on ? "on" : "off", + power->core_clks_on ? "on" : "off"); +error: + return rc; +} + +static int dp_power_request_gpios(struct dp_power_private *power) +{ + int rc = 0, i; + struct device *dev; + struct dss_module_power *mp; + static const char * const gpio_names[] = { + "aux_enable", "aux_sel", "usbplug_cc", + }; + + if (!power) { + pr_err("invalid power data\n"); + return -EINVAL; + } + + dev = &power->pdev->dev; + mp = &power->parser->mp[DP_CORE_PM]; + + for (i = 0; i < ARRAY_SIZE(gpio_names); i++) { + unsigned int gpio = mp->gpio_config[i].gpio; + + if (gpio_is_valid(gpio)) { + rc = devm_gpio_request(dev, gpio, gpio_names[i]); + if (rc) { + pr_err("request %s gpio failed, rc=%d\n", + gpio_names[i], rc); + goto error; + } + } + } + return 0; +error: + for (i = 0; i < ARRAY_SIZE(gpio_names); i++) { + unsigned int gpio = mp->gpio_config[i].gpio; + + if (gpio_is_valid(gpio)) + gpio_free(gpio); + } + return rc; +} + +static bool dp_power_find_gpio(const char *gpio1, const char *gpio2) +{ + return !!strnstr(gpio1, gpio2, strlen(gpio1)); +} + +static void dp_power_set_gpio(struct dp_power_private *power, bool flip) +{ + int i; + struct dss_module_power *mp = &power->parser->mp[DP_CORE_PM]; + struct dss_gpio *config = mp->gpio_config; + + for (i = 0; i < mp->num_gpio; i++) { + if (dp_power_find_gpio(config->gpio_name, "aux-sel")) + config->value = flip; + + if (gpio_is_valid(config->gpio)) { + pr_debug("gpio %s, value %d\n", config->gpio_name, + config->value); + + if (dp_power_find_gpio(config->gpio_name, "aux-en") || + dp_power_find_gpio(config->gpio_name, "aux-sel")) + gpio_direction_output(config->gpio, + config->value); + else + gpio_set_value(config->gpio, config->value); + + } + config++; + } +} + +static int dp_power_config_gpios(struct dp_power_private *power, bool flip, + bool enable) +{ + int rc = 0, i; + struct dss_module_power *mp; + struct dss_gpio *config; + + mp = &power->parser->mp[DP_CORE_PM]; + config = mp->gpio_config; + + if (enable) { + rc = dp_power_request_gpios(power); + if (rc) { + pr_err("gpio request failed\n"); + return rc; + } + + dp_power_set_gpio(power, flip); + } else { + for (i = 0; i < mp->num_gpio; i++) { + gpio_set_value(config[i].gpio, 0); + gpio_free(config[i].gpio); + } + } + + return 0; +} + +static int dp_power_client_init(struct dp_power *dp_power, + struct sde_power_handle *phandle) +{ + int rc = 0; + struct dp_power_private *power; + char dp_client_name[DP_CLIENT_NAME_SIZE]; + + if (!dp_power) { + pr_err("invalid power data\n"); + return -EINVAL; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + rc = dp_power_regulator_init(power); + if (rc) { + pr_err("failed to init regulators\n"); + goto error_power; + } + + rc = dp_power_clk_init(power, true); + if (rc) { + pr_err("failed to init clocks\n"); + goto error_clk; + } + + power->phandle = phandle; + snprintf(dp_client_name, DP_CLIENT_NAME_SIZE, "dp_core_client"); + power->dp_core_client = sde_power_client_create(phandle, + dp_client_name); + if (IS_ERR_OR_NULL(power->dp_core_client)) { + pr_err("[%s] client creation failed for DP", dp_client_name); + rc = -EINVAL; + goto error_client; + } + return 0; + +error_client: + dp_power_clk_init(power, false); +error_clk: + dp_power_regulator_deinit(power); +error_power: + return rc; +} + +static void dp_power_client_deinit(struct dp_power *dp_power) +{ + struct dp_power_private *power; + + if (!dp_power) { + pr_err("invalid power data\n"); + return; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + sde_power_client_destroy(power->phandle, power->dp_core_client); + dp_power_clk_init(power, false); + dp_power_regulator_deinit(power); +} + +static int dp_power_set_pixel_clk_parent(struct dp_power *dp_power) +{ + int rc = 0; + struct dp_power_private *power; + + if (!dp_power) { + pr_err("invalid power data\n"); + rc = -EINVAL; + goto exit; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + if (power->pixel_clk_rcg && power->pixel_parent) + clk_set_parent(power->pixel_clk_rcg, power->pixel_parent); +exit: + return rc; +} + +static int dp_power_init(struct dp_power *dp_power, bool flip) +{ + int rc = 0; + struct dp_power_private *power; + + if (!dp_power) { + pr_err("invalid power data\n"); + rc = -EINVAL; + goto exit; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + rc = dp_power_regulator_ctrl(power, true); + if (rc) { + pr_err("failed to enable regulators\n"); + goto exit; + } + + rc = dp_power_pinctrl_set(power, true); + if (rc) { + pr_err("failed to set pinctrl state\n"); + goto err_pinctrl; + } + + rc = dp_power_config_gpios(power, flip, true); + if (rc) { + pr_err("failed to enable gpios\n"); + goto err_gpio; + } + + rc = sde_power_resource_enable(power->phandle, + power->dp_core_client, true); + if (rc) { + pr_err("Power resource enable failed\n"); + goto err_sde_power; + } + + rc = dp_power_clk_enable(dp_power, DP_CORE_PM, true); + if (rc) { + pr_err("failed to enable DP core clocks\n"); + goto err_clk; + } + + return 0; + +err_clk: + sde_power_resource_enable(power->phandle, power->dp_core_client, false); +err_sde_power: + dp_power_config_gpios(power, flip, false); +err_gpio: + dp_power_pinctrl_set(power, false); +err_pinctrl: + dp_power_regulator_ctrl(power, false); +exit: + return rc; +} + +static int dp_power_deinit(struct dp_power *dp_power) +{ + int rc = 0; + struct dp_power_private *power; + + if (!dp_power) { + pr_err("invalid power data\n"); + rc = -EINVAL; + goto exit; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + dp_power_clk_enable(dp_power, DP_CORE_PM, false); + rc = sde_power_resource_enable(power->phandle, + power->dp_core_client, false); + if (rc) { + pr_err("Power resource enable failed, rc=%d\n", rc); + goto exit; + } + dp_power_config_gpios(power, false, false); + dp_power_pinctrl_set(power, false); + dp_power_regulator_ctrl(power, false); +exit: + return rc; +} + +struct dp_power *dp_power_get(struct dp_parser *parser) +{ + int rc = 0; + struct dp_power_private *power; + struct dp_power *dp_power; + + if (!parser) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + power = devm_kzalloc(&parser->pdev->dev, sizeof(*power), GFP_KERNEL); + if (!power) { + rc = -ENOMEM; + goto error; + } + + power->parser = parser; + power->pdev = parser->pdev; + + dp_power = &power->dp_power; + + dp_power->init = dp_power_init; + dp_power->deinit = dp_power_deinit; + dp_power->clk_enable = dp_power_clk_enable; + dp_power->set_pixel_clk_parent = dp_power_set_pixel_clk_parent; + dp_power->power_client_init = dp_power_client_init; + dp_power->power_client_deinit = dp_power_client_deinit; + + return dp_power; +error: + return ERR_PTR(rc); +} + +void dp_power_put(struct dp_power *dp_power) +{ + struct dp_power_private *power = NULL; + + if (!dp_power) + return; + + power = container_of(dp_power, struct dp_power_private, dp_power); + + devm_kfree(&power->pdev->dev, power); +} diff --git a/drivers/gpu/drm/msm/dp/dp_power.h b/drivers/gpu/drm/msm/dp/dp_power.h new file mode 100644 index 0000000000000000000000000000000000000000..e6e990080e53a5680f71ed3eae58eccb9b43fe7a --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_power.h @@ -0,0 +1,58 @@ +/* + * 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_POWER_H_ +#define _DP_POWER_H_ + +#include "dp_parser.h" +#include "sde_power_handle.h" + +/** + * sruct dp_power - DisplayPort's power related data + * + * @init: initializes the regulators/core clocks/GPIOs/pinctrl + * @deinit: turns off the regulators/core clocks/GPIOs/pinctrl + * @clk_enable: enable/disable the DP clocks + * @set_pixel_clk_parent: set the parent of DP pixel clock + */ +struct dp_power { + int (*init)(struct dp_power *power, bool flip); + int (*deinit)(struct dp_power *power); + int (*clk_enable)(struct dp_power *power, enum dp_pm_type pm_type, + bool enable); + int (*set_pixel_clk_parent)(struct dp_power *power); + int (*power_client_init)(struct dp_power *power, + struct sde_power_handle *phandle); + void (*power_client_deinit)(struct dp_power *power); +}; + +/** + * dp_power_get() - configure and get the DisplayPort power module data + * + * @parser: instance of parser module + * return: pointer to allocated power module data + * + * This API will configure the DisplayPort's power module and provides + * methods to be called by the client to configure the power related + * modueles. + */ +struct dp_power *dp_power_get(struct dp_parser *parser); + +/** + * dp_power_put() - release the power related resources + * + * @power: pointer to the power module's data + */ +void dp_power_put(struct dp_power *power); +#endif /* _DP_POWER_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_reg.h b/drivers/gpu/drm/msm/dp/dp_reg.h new file mode 100644 index 0000000000000000000000000000000000000000..5aaad2468b41946b3bdafe95fea4c7af6a2bcbee --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_reg.h @@ -0,0 +1,231 @@ +/* + * 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_REG_H_ +#define _DP_REG_H_ + +/* DP_TX Registers */ +#define DP_HW_VERSION (0x00000000) +#define DP_SW_RESET (0x00000010) +#define DP_PHY_CTRL (0x00000014) +#define DP_CLK_CTRL (0x00000018) +#define DP_CLK_ACTIVE (0x0000001C) +#define DP_INTR_STATUS (0x00000020) +#define DP_INTR_STATUS2 (0x00000024) +#define DP_INTR_STATUS3 (0x00000028) + +#define DP_DP_HPD_CTRL (0x00000200) +#define DP_DP_HPD_INT_STATUS (0x00000204) +#define DP_DP_HPD_INT_ACK (0x00000208) +#define DP_DP_HPD_INT_MASK (0x0000020C) +#define DP_DP_HPD_REFTIMER (0x00000218) +#define DP_DP_HPD_EVENT_TIME_0 (0x0000021C) +#define DP_DP_HPD_EVENT_TIME_1 (0x00000220) +#define DP_AUX_CTRL (0x00000230) +#define DP_AUX_DATA (0x00000234) +#define DP_AUX_TRANS_CTRL (0x00000238) +#define DP_TIMEOUT_COUNT (0x0000023C) +#define DP_AUX_LIMITS (0x00000240) +#define DP_AUX_STATUS (0x00000244) + +#define DP_DPCD_CP_IRQ (0x201) +#define DP_DPCD_RXSTATUS (0x69493) + +#define DP_INTERRUPT_TRANS_NUM (0x000002A0) + +#define DP_MAINLINK_CTRL (0x00000400) +#define DP_STATE_CTRL (0x00000404) +#define DP_CONFIGURATION_CTRL (0x00000408) +#define DP_SOFTWARE_MVID (0x00000410) +#define DP_SOFTWARE_NVID (0x00000418) +#define DP_TOTAL_HOR_VER (0x0000041C) +#define DP_START_HOR_VER_FROM_SYNC (0x00000420) +#define DP_HSYNC_VSYNC_WIDTH_POLARITY (0x00000424) +#define DP_ACTIVE_HOR_VER (0x00000428) +#define DP_MISC1_MISC0 (0x0000042C) +#define DP_VALID_BOUNDARY (0x00000430) +#define DP_VALID_BOUNDARY_2 (0x00000434) +#define DP_LOGICAL2PHYSCIAL_LANE_MAPPING (0x00000438) + +#define DP_MAINLINK_READY (0x00000440) +#define DP_MAINLINK_LEVELS (0x00000444) +#define DP_TU (0x0000044C) + +#define DP_HBR2_COMPLIANCE_SCRAMBLER_RESET (0x00000454) +#define DP_TEST_80BIT_CUSTOM_PATTERN_REG0 (0x000004C0) +#define DP_TEST_80BIT_CUSTOM_PATTERN_REG1 (0x000004C4) +#define DP_TEST_80BIT_CUSTOM_PATTERN_REG2 (0x000004C8) + +#define MMSS_DP_MISC1_MISC0 (0x0000042C) +#define MMSS_DP_AUDIO_TIMING_GEN (0x00000480) +#define MMSS_DP_AUDIO_TIMING_RBR_32 (0x00000484) +#define MMSS_DP_AUDIO_TIMING_HBR_32 (0x00000488) +#define MMSS_DP_AUDIO_TIMING_RBR_44 (0x0000048C) +#define MMSS_DP_AUDIO_TIMING_HBR_44 (0x00000490) +#define MMSS_DP_AUDIO_TIMING_RBR_48 (0x00000494) +#define MMSS_DP_AUDIO_TIMING_HBR_48 (0x00000498) + +#define MMSS_DP_PSR_CRC_RG (0x00000554) +#define MMSS_DP_PSR_CRC_B (0x00000558) + +#define DP_COMPRESSION_MODE_CTRL (0x00000580) + +#define MMSS_DP_AUDIO_CFG (0x00000600) +#define MMSS_DP_AUDIO_STATUS (0x00000604) +#define MMSS_DP_AUDIO_PKT_CTRL (0x00000608) +#define MMSS_DP_AUDIO_PKT_CTRL2 (0x0000060C) +#define MMSS_DP_AUDIO_ACR_CTRL (0x00000610) +#define MMSS_DP_AUDIO_CTRL_RESET (0x00000614) + +#define MMSS_DP_SDP_CFG (0x00000628) +#define MMSS_DP_SDP_CFG2 (0x0000062C) +#define MMSS_DP_AUDIO_TIMESTAMP_0 (0x00000630) +#define MMSS_DP_AUDIO_TIMESTAMP_1 (0x00000634) + +#define MMSS_DP_AUDIO_STREAM_0 (0x00000640) +#define MMSS_DP_AUDIO_STREAM_1 (0x00000644) + +#define MMSS_DP_EXTENSION_0 (0x00000650) +#define MMSS_DP_EXTENSION_1 (0x00000654) +#define MMSS_DP_EXTENSION_2 (0x00000658) +#define MMSS_DP_EXTENSION_3 (0x0000065C) +#define MMSS_DP_EXTENSION_4 (0x00000660) +#define MMSS_DP_EXTENSION_5 (0x00000664) +#define MMSS_DP_EXTENSION_6 (0x00000668) +#define MMSS_DP_EXTENSION_7 (0x0000066C) +#define MMSS_DP_EXTENSION_8 (0x00000670) +#define MMSS_DP_EXTENSION_9 (0x00000674) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_0 (0x00000678) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_1 (0x0000067C) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_2 (0x00000680) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_3 (0x00000684) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_4 (0x00000688) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_5 (0x0000068C) +#define MMSS_DP_AUDIO_ISRC_0 (0x00000690) +#define MMSS_DP_AUDIO_ISRC_1 (0x00000694) +#define MMSS_DP_AUDIO_ISRC_2 (0x00000698) +#define MMSS_DP_AUDIO_ISRC_3 (0x0000069C) +#define MMSS_DP_AUDIO_ISRC_4 (0x000006A0) +#define MMSS_DP_AUDIO_ISRC_5 (0x000006A4) +#define MMSS_DP_AUDIO_INFOFRAME_0 (0x000006A8) +#define MMSS_DP_AUDIO_INFOFRAME_1 (0x000006AC) +#define MMSS_DP_AUDIO_INFOFRAME_2 (0x000006B0) + +#define MMSS_DP_GENERIC0_0 (0x00000700) +#define MMSS_DP_GENERIC0_1 (0x00000704) +#define MMSS_DP_GENERIC0_2 (0x00000708) +#define MMSS_DP_GENERIC0_3 (0x0000070C) +#define MMSS_DP_GENERIC0_4 (0x00000710) +#define MMSS_DP_GENERIC0_5 (0x00000714) +#define MMSS_DP_GENERIC0_6 (0x00000718) +#define MMSS_DP_GENERIC0_7 (0x0000071C) +#define MMSS_DP_GENERIC0_8 (0x00000720) +#define MMSS_DP_GENERIC0_9 (0x00000724) +#define MMSS_DP_GENERIC1_0 (0x00000728) +#define MMSS_DP_GENERIC1_1 (0x0000072C) +#define MMSS_DP_GENERIC1_2 (0x00000730) +#define MMSS_DP_GENERIC1_3 (0x00000734) +#define MMSS_DP_GENERIC1_4 (0x00000738) +#define MMSS_DP_GENERIC1_5 (0x0000073C) +#define MMSS_DP_GENERIC1_6 (0x00000740) +#define MMSS_DP_GENERIC1_7 (0x00000744) +#define MMSS_DP_GENERIC1_8 (0x00000748) +#define MMSS_DP_GENERIC1_9 (0x0000074C) + +#define MMSS_DP_VSCEXT_0 (0x000006D0) +#define MMSS_DP_VSCEXT_1 (0x000006D4) +#define MMSS_DP_VSCEXT_2 (0x000006D8) +#define MMSS_DP_VSCEXT_3 (0x000006DC) +#define MMSS_DP_VSCEXT_4 (0x000006E0) +#define MMSS_DP_VSCEXT_5 (0x000006E4) +#define MMSS_DP_VSCEXT_6 (0x000006E8) +#define MMSS_DP_VSCEXT_7 (0x000006EC) +#define MMSS_DP_VSCEXT_8 (0x000006F0) +#define MMSS_DP_VSCEXT_9 (0x000006F4) + +#define MMSS_DP_TIMING_ENGINE_EN (0x00000A10) +#define MMSS_DP_ASYNC_FIFO_CONFIG (0x00000A88) + +/*DP PHY Register offsets */ +#define DP_PHY_REVISION_ID0 (0x00000000) +#define DP_PHY_REVISION_ID1 (0x00000004) +#define DP_PHY_REVISION_ID2 (0x00000008) +#define DP_PHY_REVISION_ID3 (0x0000000C) + +#define DP_PHY_CFG (0x00000010) +#define DP_PHY_PD_CTL (0x00000018) +#define DP_PHY_MODE (0x0000001C) + +#define DP_PHY_AUX_CFG0 (0x00000020) +#define DP_PHY_AUX_CFG1 (0x00000024) +#define DP_PHY_AUX_CFG2 (0x00000028) +#define DP_PHY_AUX_CFG3 (0x0000002C) +#define DP_PHY_AUX_CFG4 (0x00000030) +#define DP_PHY_AUX_CFG5 (0x00000034) +#define DP_PHY_AUX_CFG6 (0x00000038) +#define DP_PHY_AUX_CFG7 (0x0000003C) +#define DP_PHY_AUX_CFG8 (0x00000040) +#define DP_PHY_AUX_CFG9 (0x00000044) +#define DP_PHY_AUX_INTERRUPT_MASK (0x00000048) +#define DP_PHY_AUX_INTERRUPT_CLEAR (0x0000004C) + +#define DP_PHY_SPARE0 (0x00AC) + +#define TXn_TX_EMP_POST1_LVL (0x000C) +#define TXn_TX_DRV_LVL (0x001C) + +#define QSERDES_COM_BIAS_EN_CLKBUFLR_EN (0x004) + +/* DP MMSS_CC registers */ +#define MMSS_DP_LINK_CMD_RCGR (0x0138) +#define MMSS_DP_LINK_CFG_RCGR (0x013C) +#define MMSS_DP_PIXEL_M (0x0174) +#define MMSS_DP_PIXEL_N (0x0178) + +/* DP HDCP 1.3 registers */ +#define DP_HDCP_CTRL (0x0A0) +#define DP_HDCP_STATUS (0x0A4) +#define DP_HDCP_SW_UPPER_AKSV (0x298) +#define DP_HDCP_SW_LOWER_AKSV (0x29C) +#define DP_HDCP_ENTROPY_CTRL0 (0x750) +#define DP_HDCP_ENTROPY_CTRL1 (0x75C) +#define DP_HDCP_SHA_STATUS (0x0C8) +#define DP_HDCP_RCVPORT_DATA2_0 (0x0B0) +#define DP_HDCP_RCVPORT_DATA3 (0x2A4) +#define DP_HDCP_RCVPORT_DATA4 (0x2A8) +#define DP_HDCP_RCVPORT_DATA5 (0x0C0) +#define DP_HDCP_RCVPORT_DATA6 (0x0C4) + +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_SHA_CTRL (0x024) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_SHA_DATA (0x028) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA0 (0x004) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA1 (0x008) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA7 (0x00C) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA8 (0x010) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA9 (0x014) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA10 (0x018) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA11 (0x01C) +#define HDCP_SEC_DP_TZ_HV_HLOS_HDCP_RCVPORT_DATA12 (0x020) + +/* USB3 DP COM registers */ +#define USB3_DP_COM_RESET_OVRD_CTRL (0x1C) +#define USB3_DP_COM_PHY_MODE_CTRL (0x00) +#define USB3_DP_COM_SW_RESET (0x04) +#define USB3_DP_COM_TYPEC_CTRL (0x10) +#define USB3_DP_COM_SWI_CTRL (0x0c) +#define USB3_DP_COM_POWER_DOWN_CTRL (0x08) + + + +#endif /* _DP_REG_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_usbpd.c b/drivers/gpu/drm/msm/dp/dp_usbpd.c new file mode 100644 index 0000000000000000000000000000000000000000..98781abba4449ee8acab3e09b6b43b393e18d773 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_usbpd.c @@ -0,0 +1,491 @@ +/* + * 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 +#include + +#include "dp_usbpd.h" + +/* DP specific VDM commands */ +#define DP_USBPD_VDM_STATUS 0x10 +#define DP_USBPD_VDM_CONFIGURE 0x11 + +/* USBPD-TypeC specific Macros */ +#define VDM_VERSION 0x0 +#define USB_C_DP_SID 0xFF01 + +enum dp_usbpd_pin_assignment { + DP_USBPD_PIN_A, + DP_USBPD_PIN_B, + DP_USBPD_PIN_C, + DP_USBPD_PIN_D, + DP_USBPD_PIN_E, + DP_USBPD_PIN_F, + DP_USBPD_PIN_MAX, +}; + +enum dp_usbpd_events { + DP_USBPD_EVT_DISCOVER, + DP_USBPD_EVT_ENTER, + DP_USBPD_EVT_STATUS, + DP_USBPD_EVT_CONFIGURE, + DP_USBPD_EVT_CC_PIN_POLARITY, + DP_USBPD_EVT_EXIT, + DP_USBPD_EVT_ATTENTION, +}; + +enum dp_usbpd_alt_mode { + DP_USBPD_ALT_MODE_NONE = 0, + DP_USBPD_ALT_MODE_INIT = BIT(0), + DP_USBPD_ALT_MODE_DISCOVER = BIT(1), + DP_USBPD_ALT_MODE_ENTER = BIT(2), + DP_USBPD_ALT_MODE_STATUS = BIT(3), + DP_USBPD_ALT_MODE_CONFIGURE = BIT(4), +}; + +struct dp_usbpd_capabilities { + enum dp_usbpd_port port; + bool receptacle_state; + u8 ulink_pin_config; + u8 dlink_pin_config; +}; + +struct dp_usbpd_private { + u32 vdo; + struct device *dev; + struct usbpd *pd; + struct usbpd_svid_handler svid_handler; + struct dp_usbpd_cb *dp_cb; + struct dp_usbpd_capabilities cap; + struct dp_usbpd dp_usbpd; + enum dp_usbpd_alt_mode alt_mode; + u32 dp_usbpd_config; +}; + +static const char *dp_usbpd_pin_name(u8 pin) +{ + switch (pin) { + case DP_USBPD_PIN_A: return "DP_USBPD_PIN_ASSIGNMENT_A"; + case DP_USBPD_PIN_B: return "DP_USBPD_PIN_ASSIGNMENT_B"; + case DP_USBPD_PIN_C: return "DP_USBPD_PIN_ASSIGNMENT_C"; + case DP_USBPD_PIN_D: return "DP_USBPD_PIN_ASSIGNMENT_D"; + case DP_USBPD_PIN_E: return "DP_USBPD_PIN_ASSIGNMENT_E"; + case DP_USBPD_PIN_F: return "DP_USBPD_PIN_ASSIGNMENT_F"; + default: return "UNKNOWN"; + } +} + +static const char *dp_usbpd_port_name(enum dp_usbpd_port port) +{ + switch (port) { + case DP_USBPD_PORT_NONE: return "DP_USBPD_PORT_NONE"; + case DP_USBPD_PORT_UFP_D: return "DP_USBPD_PORT_UFP_D"; + case DP_USBPD_PORT_DFP_D: return "DP_USBPD_PORT_DFP_D"; + case DP_USBPD_PORT_D_UFP_D: return "DP_USBPD_PORT_D_UFP_D"; + default: return "DP_USBPD_PORT_NONE"; + } +} + +static const char *dp_usbpd_cmd_name(u8 cmd) +{ + switch (cmd) { + case USBPD_SVDM_DISCOVER_MODES: return "USBPD_SVDM_DISCOVER_MODES"; + case USBPD_SVDM_ENTER_MODE: return "USBPD_SVDM_ENTER_MODE"; + case USBPD_SVDM_ATTENTION: return "USBPD_SVDM_ATTENTION"; + case DP_USBPD_VDM_STATUS: return "DP_USBPD_VDM_STATUS"; + case DP_USBPD_VDM_CONFIGURE: return "DP_USBPD_VDM_CONFIGURE"; + default: return "DP_USBPD_VDM_ERROR"; + } +} + +static void dp_usbpd_init_port(enum dp_usbpd_port *port, u32 in_port) +{ + switch (in_port) { + case 0: + *port = DP_USBPD_PORT_NONE; + break; + case 1: + *port = DP_USBPD_PORT_UFP_D; + break; + case 2: + *port = DP_USBPD_PORT_DFP_D; + break; + case 3: + *port = DP_USBPD_PORT_D_UFP_D; + break; + default: + *port = DP_USBPD_PORT_NONE; + } + pr_debug("port:%s\n", dp_usbpd_port_name(*port)); +} + +static void dp_usbpd_get_capabilities(struct dp_usbpd_private *pd) +{ + struct dp_usbpd_capabilities *cap = &pd->cap; + u32 buf = pd->vdo; + int port = buf & 0x3; + + cap->receptacle_state = (buf & BIT(6)) ? true : false; + cap->dlink_pin_config = (buf >> 8) & 0xff; + cap->ulink_pin_config = (buf >> 16) & 0xff; + + dp_usbpd_init_port(&cap->port, port); +} + +static void dp_usbpd_get_status(struct dp_usbpd_private *pd) +{ + struct dp_usbpd *status = &pd->dp_usbpd; + u32 buf = pd->vdo; + int port = buf & 0x3; + + status->low_pow_st = (buf & BIT(2)) ? true : false; + status->adaptor_dp_en = (buf & BIT(3)) ? true : false; + status->multi_func = (buf & BIT(4)) ? true : false; + status->usb_config_req = (buf & BIT(5)) ? true : false; + status->exit_dp_mode = (buf & BIT(6)) ? true : false; + status->hpd_high = (buf & BIT(7)) ? true : false; + status->hpd_irq = (buf & BIT(8)) ? true : false; + + pr_debug("low_pow_st = %d, adaptor_dp_en = %d, multi_func = %d\n", + status->low_pow_st, status->adaptor_dp_en, + status->multi_func); + pr_debug("usb_config_req = %d, exit_dp_mode = %d, hpd_high =%d\n", + status->usb_config_req, + status->exit_dp_mode, status->hpd_high); + pr_debug("hpd_irq = %d\n", status->hpd_irq); + + dp_usbpd_init_port(&status->port, port); +} + +static u32 dp_usbpd_gen_config_pkt(struct dp_usbpd_private *pd) +{ + u8 pin_cfg, pin; + u32 config = 0; + const u32 ufp_d_config = 0x2, dp_ver = 0x1; + + if (pd->cap.receptacle_state) + pin_cfg = pd->cap.ulink_pin_config; + else + pin_cfg = pd->cap.dlink_pin_config; + + for (pin = DP_USBPD_PIN_A; pin < DP_USBPD_PIN_MAX; pin++) { + if (pin_cfg & BIT(pin)) { + if (pd->dp_usbpd.multi_func) { + if (pin == DP_USBPD_PIN_D) + break; + } else { + break; + } + } + } + + if (pin == DP_USBPD_PIN_MAX) + pin = DP_USBPD_PIN_C; + + pr_debug("pin assignment: %s\n", dp_usbpd_pin_name(pin)); + + config |= BIT(pin) << 8; + + config |= (dp_ver << 2); + config |= ufp_d_config; + + pr_debug("config = 0x%x\n", config); + return config; +} + +static void dp_usbpd_send_event(struct dp_usbpd_private *pd, + enum dp_usbpd_events event) +{ + u32 config; + + switch (event) { + case DP_USBPD_EVT_DISCOVER: + usbpd_send_svdm(pd->pd, USB_C_DP_SID, + USBPD_SVDM_DISCOVER_MODES, + SVDM_CMD_TYPE_INITIATOR, 0x0, 0x0, 0x0); + break; + case DP_USBPD_EVT_ENTER: + usbpd_send_svdm(pd->pd, USB_C_DP_SID, + USBPD_SVDM_ENTER_MODE, + SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0); + break; + case DP_USBPD_EVT_EXIT: + usbpd_send_svdm(pd->pd, USB_C_DP_SID, + USBPD_SVDM_EXIT_MODE, + SVDM_CMD_TYPE_INITIATOR, 0x1, 0x0, 0x0); + break; + case DP_USBPD_EVT_STATUS: + config = 0x1; /* DFP_D connected */ + usbpd_send_svdm(pd->pd, USB_C_DP_SID, DP_USBPD_VDM_STATUS, + SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1); + break; + case DP_USBPD_EVT_CONFIGURE: + config = dp_usbpd_gen_config_pkt(pd); + usbpd_send_svdm(pd->pd, USB_C_DP_SID, DP_USBPD_VDM_CONFIGURE, + SVDM_CMD_TYPE_INITIATOR, 0x1, &config, 0x1); + break; + default: + pr_err("unknown event:%d\n", event); + } +} + +static void dp_usbpd_connect_cb(struct usbpd_svid_handler *hdlr) +{ + struct dp_usbpd_private *pd; + + pd = container_of(hdlr, struct dp_usbpd_private, svid_handler); + if (!pd) { + pr_err("get_usbpd phandle failed\n"); + return; + } + + pr_debug("\n"); + dp_usbpd_send_event(pd, DP_USBPD_EVT_DISCOVER); +} + +static void dp_usbpd_disconnect_cb(struct usbpd_svid_handler *hdlr) +{ + struct dp_usbpd_private *pd; + + pd = container_of(hdlr, struct dp_usbpd_private, svid_handler); + if (!pd) { + pr_err("get_usbpd phandle failed\n"); + return; + } + + pd->alt_mode = DP_USBPD_ALT_MODE_NONE; + pd->dp_usbpd.alt_mode_cfg_done = false; + pr_debug("\n"); + + if (pd->dp_cb && pd->dp_cb->disconnect) + pd->dp_cb->disconnect(pd->dev); +} + +static int dp_usbpd_validate_callback(u8 cmd, + enum usbpd_svdm_cmd_type cmd_type, int num_vdos) +{ + int ret = 0; + + if (cmd_type == SVDM_CMD_TYPE_RESP_NAK) { + pr_err("error: NACK\n"); + ret = -EINVAL; + goto end; + } + + if (cmd_type == SVDM_CMD_TYPE_RESP_BUSY) { + pr_err("error: BUSY\n"); + ret = -EBUSY; + goto end; + } + + if (cmd == USBPD_SVDM_ATTENTION) { + if (cmd_type != SVDM_CMD_TYPE_INITIATOR) { + pr_err("error: invalid cmd type for attention\n"); + ret = -EINVAL; + goto end; + } + + if (!num_vdos) { + pr_err("error: no vdo provided\n"); + ret = -EINVAL; + goto end; + } + } else { + if (cmd_type != SVDM_CMD_TYPE_RESP_ACK) { + pr_err("error: invalid cmd type\n"); + ret = -EINVAL; + } + } +end: + return ret; +} + +static void dp_usbpd_response_cb(struct usbpd_svid_handler *hdlr, u8 cmd, + enum usbpd_svdm_cmd_type cmd_type, + const u32 *vdos, int num_vdos) +{ + struct dp_usbpd_private *pd; + + pd = container_of(hdlr, struct dp_usbpd_private, svid_handler); + + pr_debug("callback -> cmd: %s, *vdos = 0x%x, num_vdos = %d\n", + dp_usbpd_cmd_name(cmd), *vdos, num_vdos); + + if (dp_usbpd_validate_callback(cmd, cmd_type, num_vdos)) { + pr_debug("invalid callback received\n"); + return; + } + + switch (cmd) { + case USBPD_SVDM_DISCOVER_MODES: + pd->vdo = *vdos; + dp_usbpd_get_capabilities(pd); + + pd->alt_mode |= DP_USBPD_ALT_MODE_DISCOVER; + + if (pd->cap.port & BIT(0)) + dp_usbpd_send_event(pd, DP_USBPD_EVT_ENTER); + break; + case USBPD_SVDM_ENTER_MODE: + pd->alt_mode |= DP_USBPD_ALT_MODE_ENTER; + + dp_usbpd_send_event(pd, DP_USBPD_EVT_STATUS); + break; + case USBPD_SVDM_ATTENTION: + if (pd->dp_usbpd.forced_disconnect) + break; + + pd->vdo = *vdos; + dp_usbpd_get_status(pd); + + if (pd->dp_cb && pd->dp_cb->attention) + pd->dp_cb->attention(pd->dev); + + if (!pd->dp_usbpd.alt_mode_cfg_done) + dp_usbpd_send_event(pd, DP_USBPD_EVT_CONFIGURE); + break; + case DP_USBPD_VDM_STATUS: + pd->vdo = *vdos; + dp_usbpd_get_status(pd); + + if (!(pd->alt_mode & DP_USBPD_ALT_MODE_CONFIGURE)) { + pd->alt_mode |= DP_USBPD_ALT_MODE_STATUS; + + if (pd->dp_usbpd.port & BIT(1)) + dp_usbpd_send_event(pd, DP_USBPD_EVT_CONFIGURE); + } + break; + case DP_USBPD_VDM_CONFIGURE: + pd->alt_mode |= DP_USBPD_ALT_MODE_CONFIGURE; + pd->dp_usbpd.alt_mode_cfg_done = true; + dp_usbpd_get_status(pd); + + pd->dp_usbpd.orientation = usbpd_get_plug_orientation(pd->pd); + + /* + * By default, USB reserves two lanes for Super Speed. + * Which means DP has remaining two lanes to operate on. + * If multi-function is not supported, request USB to + * release the Super Speed lanes so that DP can use + * all four lanes in case DPCD indicates support for + * four lanes. + */ + if (!pd->dp_usbpd.multi_func) + pd->svid_handler.request_usb_ss_lane(pd->pd, + &pd->svid_handler); + + if (pd->dp_cb && pd->dp_cb->configure) + pd->dp_cb->configure(pd->dev); + break; + default: + pr_err("unknown cmd: %d\n", cmd); + break; + } +} + +static int dp_usbpd_connect(struct dp_usbpd *dp_usbpd, bool hpd) +{ + int rc = 0; + struct dp_usbpd_private *pd; + + if (!dp_usbpd) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + pd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd); + + dp_usbpd->hpd_high = hpd; + dp_usbpd->forced_disconnect = !hpd; + + if (hpd) + pd->dp_cb->configure(pd->dev); + else + pd->dp_cb->disconnect(pd->dev); + +error: + return rc; +} + +struct dp_usbpd *dp_usbpd_get(struct device *dev, struct dp_usbpd_cb *cb) +{ + int rc = 0; + const char *pd_phandle = "qcom,dp-usbpd-detection"; + struct usbpd *pd = NULL; + struct dp_usbpd_private *usbpd; + struct dp_usbpd *dp_usbpd; + struct usbpd_svid_handler svid_handler = { + .svid = USB_C_DP_SID, + .vdm_received = NULL, + .connect = &dp_usbpd_connect_cb, + .svdm_received = &dp_usbpd_response_cb, + .disconnect = &dp_usbpd_disconnect_cb, + }; + + if (!cb) { + pr_err("invalid cb data\n"); + rc = -EINVAL; + goto error; + } + + pd = devm_usbpd_get_by_phandle(dev, pd_phandle); + if (IS_ERR(pd)) { + pr_err("usbpd phandle failed (%ld)\n", PTR_ERR(pd)); + rc = PTR_ERR(pd); + goto error; + } + + usbpd = devm_kzalloc(dev, sizeof(*usbpd), GFP_KERNEL); + if (!usbpd) { + rc = -ENOMEM; + goto error; + } + + usbpd->dev = dev; + usbpd->pd = pd; + usbpd->svid_handler = svid_handler; + usbpd->dp_cb = cb; + + rc = usbpd_register_svid(pd, &usbpd->svid_handler); + if (rc) { + pr_err("pd registration failed\n"); + rc = -ENODEV; + devm_kfree(dev, usbpd); + goto error; + } + + dp_usbpd = &usbpd->dp_usbpd; + dp_usbpd->connect = dp_usbpd_connect; + + return dp_usbpd; +error: + return ERR_PTR(rc); +} + +void dp_usbpd_put(struct dp_usbpd *dp_usbpd) +{ + struct dp_usbpd_private *usbpd; + + if (!dp_usbpd) + return; + + usbpd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd); + + usbpd_unregister_svid(usbpd->pd, &usbpd->svid_handler); + + devm_kfree(usbpd->dev, usbpd); +} diff --git a/drivers/gpu/drm/msm/dp/dp_usbpd.h b/drivers/gpu/drm/msm/dp/dp_usbpd.h new file mode 100644 index 0000000000000000000000000000000000000000..5b392f5831fafe029e60741a058b4f95a4f154f9 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_usbpd.h @@ -0,0 +1,101 @@ +/* + * 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_USBPD_H_ +#define _DP_USBPD_H_ + +#include + +#include +#include + +/** + * enum dp_usbpd_port - usb/dp port type + * @DP_USBPD_PORT_NONE: port not configured + * @DP_USBPD_PORT_UFP_D: Upstream Facing Port - DisplayPort + * @DP_USBPD_PORT_DFP_D: Downstream Facing Port - DisplayPort + * @DP_USBPD_PORT_D_UFP_D: Both UFP & DFP - DisplayPort + */ + +enum dp_usbpd_port { + DP_USBPD_PORT_NONE, + DP_USBPD_PORT_UFP_D, + DP_USBPD_PORT_DFP_D, + DP_USBPD_PORT_D_UFP_D, +}; + +/** + * struct dp_usbpd - DisplayPort status + * + * @port: port configured + * orientation: plug orientation configuration + * @low_pow_st: low power state + * @adaptor_dp_en: adaptor functionality enabled + * @multi_func: multi-function preferred + * @usb_config_req: request to switch to usb + * @exit_dp_mode: request exit from displayport mode + * @hpd_high: Hot Plug Detect signal is high. + * @hpd_irq: Change in the status since last message + * @alt_mode_cfg_done: bool to specify alt mode status + * @debug_en: bool to specify debug mode + * @connect: simulate disconnect or connect for debug mode + */ +struct dp_usbpd { + enum dp_usbpd_port port; + enum plug_orientation orientation; + bool low_pow_st; + bool adaptor_dp_en; + bool multi_func; + bool usb_config_req; + bool exit_dp_mode; + bool hpd_high; + bool hpd_irq; + bool alt_mode_cfg_done; + bool debug_en; + bool forced_disconnect; + + int (*connect)(struct dp_usbpd *dp_usbpd, bool hpd); +}; + +/** + * struct dp_usbpd_cb - callback functions provided by the client + * + * @configure: called by usbpd module when PD communication has + * been completed and the usb peripheral has been configured on + * dp mode. + * @disconnect: notify the cable disconnect issued by usb. + * @attention: notify any attention message issued by usb. + */ +struct dp_usbpd_cb { + int (*configure)(struct device *dev); + int (*disconnect)(struct device *dev); + int (*attention)(struct device *dev); +}; + +/** + * dp_usbpd_get() - setup usbpd module + * + * @dev: device instance of the caller + * @cb: struct containing callback function pointers. + * + * This function allows the client to initialize the usbpd + * module. The module will communicate with usb driver and + * handles the power delivery (PD) communication with the + * sink/usb device. This module will notify the client using + * the callback functions about the connection and status. + */ +struct dp_usbpd *dp_usbpd_get(struct device *dev, struct dp_usbpd_cb *cb); + +void dp_usbpd_put(struct dp_usbpd *pd); +#endif /* _DP_USBPD_H_ */ diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.c b/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.c new file mode 100644 index 0000000000000000000000000000000000000000..38a6e474aae0959073d43a0c4cac6573c1e56566 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.c @@ -0,0 +1,241 @@ +/* + * 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. + * + */ + +#define pr_fmt(fmt) "msm-dsi-catalog:[%s] " fmt, __func__ +#include + +#include "dsi_catalog.h" + +/** + * dsi_catalog_cmn_init() - catalog init for dsi controller v1.4 + */ +static void dsi_catalog_cmn_init(struct dsi_ctrl_hw *ctrl, + enum dsi_ctrl_version version) +{ + /* common functions */ + ctrl->ops.host_setup = dsi_ctrl_hw_cmn_host_setup; + ctrl->ops.video_engine_en = dsi_ctrl_hw_cmn_video_engine_en; + ctrl->ops.video_engine_setup = dsi_ctrl_hw_cmn_video_engine_setup; + ctrl->ops.set_video_timing = dsi_ctrl_hw_cmn_set_video_timing; + ctrl->ops.set_timing_db = dsi_ctrl_hw_cmn_set_timing_db; + ctrl->ops.cmd_engine_setup = dsi_ctrl_hw_cmn_cmd_engine_setup; + ctrl->ops.setup_cmd_stream = dsi_ctrl_hw_cmn_setup_cmd_stream; + ctrl->ops.ctrl_en = dsi_ctrl_hw_cmn_ctrl_en; + ctrl->ops.cmd_engine_en = dsi_ctrl_hw_cmn_cmd_engine_en; + ctrl->ops.phy_sw_reset = dsi_ctrl_hw_cmn_phy_sw_reset; + ctrl->ops.soft_reset = dsi_ctrl_hw_cmn_soft_reset; + ctrl->ops.kickoff_command = dsi_ctrl_hw_cmn_kickoff_command; + ctrl->ops.kickoff_fifo_command = dsi_ctrl_hw_cmn_kickoff_fifo_command; + ctrl->ops.reset_cmd_fifo = dsi_ctrl_hw_cmn_reset_cmd_fifo; + ctrl->ops.trigger_command_dma = dsi_ctrl_hw_cmn_trigger_command_dma; + ctrl->ops.get_interrupt_status = dsi_ctrl_hw_cmn_get_interrupt_status; + ctrl->ops.get_error_status = dsi_ctrl_hw_cmn_get_error_status; + ctrl->ops.clear_error_status = dsi_ctrl_hw_cmn_clear_error_status; + ctrl->ops.clear_interrupt_status = + dsi_ctrl_hw_cmn_clear_interrupt_status; + ctrl->ops.enable_status_interrupts = + dsi_ctrl_hw_cmn_enable_status_interrupts; + ctrl->ops.enable_error_interrupts = + dsi_ctrl_hw_cmn_enable_error_interrupts; + ctrl->ops.video_test_pattern_setup = + dsi_ctrl_hw_cmn_video_test_pattern_setup; + ctrl->ops.cmd_test_pattern_setup = + dsi_ctrl_hw_cmn_cmd_test_pattern_setup; + ctrl->ops.test_pattern_enable = dsi_ctrl_hw_cmn_test_pattern_enable; + ctrl->ops.trigger_cmd_test_pattern = + dsi_ctrl_hw_cmn_trigger_cmd_test_pattern; + ctrl->ops.clear_phy0_ln_err = dsi_ctrl_hw_dln0_phy_err; + ctrl->ops.phy_reset_config = dsi_ctrl_hw_cmn_phy_reset_config; + ctrl->ops.setup_misr = dsi_ctrl_hw_cmn_setup_misr; + ctrl->ops.collect_misr = dsi_ctrl_hw_cmn_collect_misr; + ctrl->ops.debug_bus = dsi_ctrl_hw_cmn_debug_bus; + + switch (version) { + case DSI_CTRL_VERSION_1_4: + ctrl->ops.setup_lane_map = dsi_ctrl_hw_14_setup_lane_map; + ctrl->ops.ulps_ops.ulps_request = dsi_ctrl_hw_14_ulps_request; + ctrl->ops.ulps_ops.ulps_exit = dsi_ctrl_hw_14_ulps_exit; + ctrl->ops.wait_for_lane_idle = + dsi_ctrl_hw_14_wait_for_lane_idle; + ctrl->ops.ulps_ops.get_lanes_in_ulps = + dsi_ctrl_hw_14_get_lanes_in_ulps; + ctrl->ops.clamp_enable = dsi_ctrl_hw_14_clamp_enable; + ctrl->ops.clamp_disable = dsi_ctrl_hw_14_clamp_disable; + ctrl->ops.reg_dump_to_buffer = + dsi_ctrl_hw_14_reg_dump_to_buffer; + break; + case DSI_CTRL_VERSION_2_0: + ctrl->ops.setup_lane_map = dsi_ctrl_hw_20_setup_lane_map; + ctrl->ops.wait_for_lane_idle = + dsi_ctrl_hw_20_wait_for_lane_idle; + ctrl->ops.reg_dump_to_buffer = + dsi_ctrl_hw_20_reg_dump_to_buffer; + ctrl->ops.ulps_ops.ulps_request = NULL; + ctrl->ops.ulps_ops.ulps_exit = NULL; + ctrl->ops.ulps_ops.get_lanes_in_ulps = NULL; + ctrl->ops.clamp_enable = NULL; + ctrl->ops.clamp_disable = NULL; + break; + case DSI_CTRL_VERSION_2_2: + ctrl->ops.phy_reset_config = dsi_ctrl_hw_22_phy_reset_config; + ctrl->ops.setup_lane_map = dsi_ctrl_hw_20_setup_lane_map; + ctrl->ops.wait_for_lane_idle = + dsi_ctrl_hw_20_wait_for_lane_idle; + ctrl->ops.reg_dump_to_buffer = + dsi_ctrl_hw_20_reg_dump_to_buffer; + ctrl->ops.ulps_ops.ulps_request = NULL; + ctrl->ops.ulps_ops.ulps_exit = NULL; + ctrl->ops.ulps_ops.get_lanes_in_ulps = NULL; + ctrl->ops.clamp_enable = NULL; + ctrl->ops.clamp_disable = NULL; + break; + default: + break; + } +} + +/** + * dsi_catalog_ctrl_setup() - return catalog info for dsi controller + * @ctrl: Pointer to DSI controller hw object. + * @version: DSI controller version. + * @index: DSI controller instance ID. + * @phy_isolation_enabled: DSI controller works isolated from phy. + * + * This function setups the catalog information in the dsi_ctrl_hw object. + * + * return: error code for failure and 0 for success. + */ +int dsi_catalog_ctrl_setup(struct dsi_ctrl_hw *ctrl, + enum dsi_ctrl_version version, u32 index, + bool phy_isolation_enabled) +{ + int rc = 0; + + if (version == DSI_CTRL_VERSION_UNKNOWN || + version >= DSI_CTRL_VERSION_MAX) { + pr_err("Unsupported version: %d\n", version); + return -ENOTSUPP; + } + + ctrl->index = index; + set_bit(DSI_CTRL_VIDEO_TPG, ctrl->feature_map); + set_bit(DSI_CTRL_CMD_TPG, ctrl->feature_map); + set_bit(DSI_CTRL_VARIABLE_REFRESH_RATE, ctrl->feature_map); + set_bit(DSI_CTRL_DYNAMIC_REFRESH, ctrl->feature_map); + set_bit(DSI_CTRL_DESKEW_CALIB, ctrl->feature_map); + set_bit(DSI_CTRL_DPHY, ctrl->feature_map); + + switch (version) { + case DSI_CTRL_VERSION_1_4: + dsi_catalog_cmn_init(ctrl, version); + break; + case DSI_CTRL_VERSION_2_0: + case DSI_CTRL_VERSION_2_2: + ctrl->phy_isolation_enabled = phy_isolation_enabled; + dsi_catalog_cmn_init(ctrl, version); + break; + default: + return -ENOTSUPP; + } + + return rc; +} + +/** + * dsi_catalog_phy_2_0_init() - catalog init for DSI PHY 14nm + */ +static void dsi_catalog_phy_2_0_init(struct dsi_phy_hw *phy) +{ + phy->ops.regulator_enable = dsi_phy_hw_v2_0_regulator_enable; + phy->ops.regulator_disable = dsi_phy_hw_v2_0_regulator_disable; + phy->ops.enable = dsi_phy_hw_v2_0_enable; + phy->ops.disable = dsi_phy_hw_v2_0_disable; + phy->ops.calculate_timing_params = + dsi_phy_hw_calculate_timing_params; + phy->ops.phy_idle_on = dsi_phy_hw_v2_0_idle_on; + phy->ops.phy_idle_off = dsi_phy_hw_v2_0_idle_off; + phy->ops.calculate_timing_params = + dsi_phy_hw_calculate_timing_params; + phy->ops.phy_timing_val = dsi_phy_hw_timing_val_v2_0; +} + +/** + * dsi_catalog_phy_3_0_init() - catalog init for DSI PHY 10nm + */ +static void dsi_catalog_phy_3_0_init(struct dsi_phy_hw *phy) +{ + phy->ops.regulator_enable = dsi_phy_hw_v3_0_regulator_enable; + phy->ops.regulator_disable = dsi_phy_hw_v3_0_regulator_disable; + phy->ops.enable = dsi_phy_hw_v3_0_enable; + phy->ops.disable = dsi_phy_hw_v3_0_disable; + phy->ops.calculate_timing_params = + dsi_phy_hw_calculate_timing_params; + phy->ops.ulps_ops.wait_for_lane_idle = + dsi_phy_hw_v3_0_wait_for_lane_idle; + phy->ops.ulps_ops.ulps_request = + dsi_phy_hw_v3_0_ulps_request; + phy->ops.ulps_ops.ulps_exit = + dsi_phy_hw_v3_0_ulps_exit; + phy->ops.ulps_ops.get_lanes_in_ulps = + dsi_phy_hw_v3_0_get_lanes_in_ulps; + phy->ops.ulps_ops.is_lanes_in_ulps = + dsi_phy_hw_v3_0_is_lanes_in_ulps; + phy->ops.phy_timing_val = dsi_phy_hw_timing_val_v3_0; +} + +/** + * dsi_catalog_phy_setup() - return catalog info for dsi phy hardware + * @ctrl: Pointer to DSI PHY hw object. + * @version: DSI PHY version. + * @index: DSI PHY instance ID. + * + * This function setups the catalog information in the dsi_phy_hw object. + * + * return: error code for failure and 0 for success. + */ +int dsi_catalog_phy_setup(struct dsi_phy_hw *phy, + enum dsi_phy_version version, + u32 index) +{ + int rc = 0; + + if (version == DSI_PHY_VERSION_UNKNOWN || + version >= DSI_PHY_VERSION_MAX) { + pr_err("Unsupported version: %d\n", version); + return -ENOTSUPP; + } + + phy->index = index; + set_bit(DSI_PHY_DPHY, phy->feature_map); + + dsi_phy_timing_calc_init(phy, version); + + switch (version) { + case DSI_PHY_VERSION_2_0: + dsi_catalog_phy_2_0_init(phy); + break; + case DSI_PHY_VERSION_3_0: + dsi_catalog_phy_3_0_init(phy); + break; + case DSI_PHY_VERSION_0_0_HPM: + case DSI_PHY_VERSION_0_0_LPM: + case DSI_PHY_VERSION_1_0: + default: + return -ENOTSUPP; + } + + return rc; +} + + diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.h b/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.h new file mode 100644 index 0000000000000000000000000000000000000000..84448ecf819dad21dfa8b5943a5f6a35b29a6e71 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.h @@ -0,0 +1,201 @@ +/* + * 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 _DSI_CATALOG_H_ +#define _DSI_CATALOG_H_ + +#include "dsi_ctrl_hw.h" +#include "dsi_phy_hw.h" + +/** + * dsi_catalog_ctrl_setup() - return catalog info for dsi controller + * @ctrl: Pointer to DSI controller hw object. + * @version: DSI controller version. + * @index: DSI controller instance ID. + * @phy_isolation_enabled: DSI controller works isolated from phy. + * + * This function setups the catalog information in the dsi_ctrl_hw object. + * + * return: error code for failure and 0 for success. + */ +int dsi_catalog_ctrl_setup(struct dsi_ctrl_hw *ctrl, + enum dsi_ctrl_version version, u32 index, + bool phy_isolation_enabled); + +/** + * dsi_catalog_phy_setup() - return catalog info for dsi phy hardware + * @phy: Pointer to DSI PHY hw object. + * @version: DSI PHY version. + * @index: DSI PHY instance ID. + * + * This function setups the catalog information in the dsi_phy_hw object. + * + * return: error code for failure and 0 for success. + */ +int dsi_catalog_phy_setup(struct dsi_phy_hw *phy, + enum dsi_phy_version version, + u32 index); + +/** + * dsi_phy_timing_calc_init() - initialize info for DSI PHY timing calculations + * @phy: Pointer to DSI PHY hw object. + * @version: DSI PHY version. + * + * This function setups the catalog information in the dsi_phy_hw object. + * + * return: error code for failure and 0 for success. + */ +int dsi_phy_timing_calc_init(struct dsi_phy_hw *phy, + enum dsi_phy_version version); + +/** + * dsi_phy_hw_calculate_timing_params() - DSI PHY timing parameter calculations + * @phy: Pointer to DSI PHY hw object. + * @mode: DSI mode information. + * @host: DSI host configuration. + * @timing: DSI phy lane configurations. + * + * This function setups the catalog information in the dsi_phy_hw object. + * + * return: error code for failure and 0 for success. + */ +int dsi_phy_hw_calculate_timing_params(struct dsi_phy_hw *phy, + struct dsi_mode_info *mode, + struct dsi_host_common_cfg *host, + struct dsi_phy_per_lane_cfgs *timing); + +/* Definitions for 14nm PHY hardware driver */ +void dsi_phy_hw_v2_0_regulator_enable(struct dsi_phy_hw *phy, + struct dsi_phy_per_lane_cfgs *cfg); +void dsi_phy_hw_v2_0_regulator_disable(struct dsi_phy_hw *phy); +void dsi_phy_hw_v2_0_enable(struct dsi_phy_hw *phy, struct dsi_phy_cfg *cfg); +void dsi_phy_hw_v2_0_disable(struct dsi_phy_hw *phy, struct dsi_phy_cfg *cfg); +void dsi_phy_hw_v2_0_idle_on(struct dsi_phy_hw *phy, struct dsi_phy_cfg *cfg); +void dsi_phy_hw_v2_0_idle_off(struct dsi_phy_hw *phy); +int dsi_phy_hw_timing_val_v2_0(struct dsi_phy_per_lane_cfgs *timing_cfg, + u32 *timing_val, u32 size); + +/* Definitions for 10nm PHY hardware driver */ +void dsi_phy_hw_v3_0_regulator_enable(struct dsi_phy_hw *phy, + struct dsi_phy_per_lane_cfgs *cfg); +void dsi_phy_hw_v3_0_regulator_disable(struct dsi_phy_hw *phy); +void dsi_phy_hw_v3_0_enable(struct dsi_phy_hw *phy, struct dsi_phy_cfg *cfg); +void dsi_phy_hw_v3_0_disable(struct dsi_phy_hw *phy, struct dsi_phy_cfg *cfg); +int dsi_phy_hw_v3_0_wait_for_lane_idle(struct dsi_phy_hw *phy, u32 lanes); +void dsi_phy_hw_v3_0_ulps_request(struct dsi_phy_hw *phy, + struct dsi_phy_cfg *cfg, u32 lanes); +void dsi_phy_hw_v3_0_ulps_exit(struct dsi_phy_hw *phy, + struct dsi_phy_cfg *cfg, u32 lanes); +u32 dsi_phy_hw_v3_0_get_lanes_in_ulps(struct dsi_phy_hw *phy); +bool dsi_phy_hw_v3_0_is_lanes_in_ulps(u32 lanes, u32 ulps_lanes); +int dsi_phy_hw_timing_val_v3_0(struct dsi_phy_per_lane_cfgs *timing_cfg, + u32 *timing_val, u32 size); + +/* DSI controller common ops */ +u32 dsi_ctrl_hw_cmn_get_interrupt_status(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_cmn_debug_bus(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_cmn_clear_interrupt_status(struct dsi_ctrl_hw *ctrl, u32 ints); +void dsi_ctrl_hw_cmn_enable_status_interrupts(struct dsi_ctrl_hw *ctrl, + u32 ints); + +u64 dsi_ctrl_hw_cmn_get_error_status(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_cmn_clear_error_status(struct dsi_ctrl_hw *ctrl, u64 errors); +void dsi_ctrl_hw_cmn_enable_error_interrupts(struct dsi_ctrl_hw *ctrl, + u64 errors); + +void dsi_ctrl_hw_cmn_video_test_pattern_setup(struct dsi_ctrl_hw *ctrl, + enum dsi_test_pattern type, + u32 init_val); +void dsi_ctrl_hw_cmn_cmd_test_pattern_setup(struct dsi_ctrl_hw *ctrl, + enum dsi_test_pattern type, + u32 init_val, + u32 stream_id); +void dsi_ctrl_hw_cmn_test_pattern_enable(struct dsi_ctrl_hw *ctrl, bool enable); +void dsi_ctrl_hw_cmn_trigger_cmd_test_pattern(struct dsi_ctrl_hw *ctrl, + u32 stream_id); + +void dsi_ctrl_hw_cmn_host_setup(struct dsi_ctrl_hw *ctrl, + struct dsi_host_common_cfg *config); +void dsi_ctrl_hw_cmn_video_engine_en(struct dsi_ctrl_hw *ctrl, bool on); +void dsi_ctrl_hw_cmn_video_engine_setup(struct dsi_ctrl_hw *ctrl, + struct dsi_host_common_cfg *common_cfg, + struct dsi_video_engine_cfg *cfg); +void dsi_ctrl_hw_cmn_set_video_timing(struct dsi_ctrl_hw *ctrl, + struct dsi_mode_info *mode); +void dsi_ctrl_hw_cmn_set_timing_db(struct dsi_ctrl_hw *ctrl, + bool enable); +void dsi_ctrl_hw_cmn_cmd_engine_setup(struct dsi_ctrl_hw *ctrl, + struct dsi_host_common_cfg *common_cfg, + struct dsi_cmd_engine_cfg *cfg); + +void dsi_ctrl_hw_cmn_ctrl_en(struct dsi_ctrl_hw *ctrl, bool on); +void dsi_ctrl_hw_cmn_cmd_engine_en(struct dsi_ctrl_hw *ctrl, bool on); + +void dsi_ctrl_hw_cmn_setup_cmd_stream(struct dsi_ctrl_hw *ctrl, + struct dsi_mode_info *mode, + u32 h_stride, + u32 vc_id, + struct dsi_rect *roi); +void dsi_ctrl_hw_cmn_phy_sw_reset(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_cmn_soft_reset(struct dsi_ctrl_hw *ctrl); + +void dsi_ctrl_hw_cmn_setup_misr(struct dsi_ctrl_hw *ctrl, + enum dsi_op_mode panel_mode, + bool enable, u32 frame_count); +u32 dsi_ctrl_hw_cmn_collect_misr(struct dsi_ctrl_hw *ctrl, + enum dsi_op_mode panel_mode); + +void dsi_ctrl_hw_cmn_kickoff_command(struct dsi_ctrl_hw *ctrl, + struct dsi_ctrl_cmd_dma_info *cmd, + u32 flags); + +void dsi_ctrl_hw_cmn_kickoff_fifo_command(struct dsi_ctrl_hw *ctrl, + struct dsi_ctrl_cmd_dma_fifo_info *cmd, + u32 flags); +void dsi_ctrl_hw_cmn_reset_cmd_fifo(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_cmn_trigger_command_dma(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_dln0_phy_err(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_cmn_phy_reset_config(struct dsi_ctrl_hw *ctrl, + bool enable); +void dsi_ctrl_hw_22_phy_reset_config(struct dsi_ctrl_hw *ctrl, + bool enable); + +/* Definitions specific to 1.4 DSI controller hardware */ +int dsi_ctrl_hw_14_wait_for_lane_idle(struct dsi_ctrl_hw *ctrl, u32 lanes); +void dsi_ctrl_hw_14_setup_lane_map(struct dsi_ctrl_hw *ctrl, + struct dsi_lane_map *lane_map); +void dsi_ctrl_hw_14_ulps_request(struct dsi_ctrl_hw *ctrl, u32 lanes); +void dsi_ctrl_hw_14_ulps_exit(struct dsi_ctrl_hw *ctrl, u32 lanes); +u32 dsi_ctrl_hw_14_get_lanes_in_ulps(struct dsi_ctrl_hw *ctrl); + +void dsi_ctrl_hw_14_clamp_enable(struct dsi_ctrl_hw *ctrl, + u32 lanes, + bool enable_ulps); + +void dsi_ctrl_hw_14_clamp_disable(struct dsi_ctrl_hw *ctrl, + u32 lanes, + bool disable_ulps); +ssize_t dsi_ctrl_hw_14_reg_dump_to_buffer(struct dsi_ctrl_hw *ctrl, + char *buf, + u32 size); + +/* Definitions specific to 2.0 DSI controller hardware */ +void dsi_ctrl_hw_20_setup_lane_map(struct dsi_ctrl_hw *ctrl, + struct dsi_lane_map *lane_map); +int dsi_ctrl_hw_20_wait_for_lane_idle(struct dsi_ctrl_hw *ctrl, u32 lanes); +ssize_t dsi_ctrl_hw_20_reg_dump_to_buffer(struct dsi_ctrl_hw *ctrl, + char *buf, + u32 size); + +#endif /* _DSI_CATALOG_H_ */ diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_clk.h b/drivers/gpu/drm/msm/dsi-staging/dsi_clk.h new file mode 100644 index 0000000000000000000000000000000000000000..2a84a2d93847529c86c5e83f99deddfcb832ee2c --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_clk.h @@ -0,0 +1,276 @@ +/* + * 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 _DSI_CLK_H_ +#define _DSI_CLK_H_ + +#include +#include +#include +#include +#include "sde_power_handle.h" + +#define MAX_STRING_LEN 32 +#define MAX_DSI_CTRL 2 + +enum dsi_clk_state { + DSI_CLK_OFF, + DSI_CLK_ON, + DSI_CLK_EARLY_GATE, +}; + +enum clk_req_client { + DSI_CLK_REQ_MDP_CLIENT = 0, + DSI_CLK_REQ_DSI_CLIENT, +}; + +enum dsi_link_clk_type { + DSI_LINK_ESC_CLK, + DSI_LINK_BYTE_CLK, + DSI_LINK_PIX_CLK, + DSI_LINK_BYTE_INTF_CLK, + DSI_LINK_CLK_MAX, +}; + +enum dsi_clk_type { + DSI_CORE_CLK = BIT(0), + DSI_LINK_CLK = BIT(1), + DSI_ALL_CLKS = (BIT(0) | BIT(1)), + DSI_CLKS_MAX = BIT(2), +}; + +struct dsi_clk_ctrl_info { + enum dsi_clk_type clk_type; + enum dsi_clk_state clk_state; + enum clk_req_client client; +}; + +struct clk_ctrl_cb { + void *priv; + int (*dsi_clk_cb)(void *priv, struct dsi_clk_ctrl_info clk_ctrl_info); +}; + +/** + * struct dsi_core_clk_info - Core clock information for DSI hardware + * @mdp_core_clk: Handle to MDP core clock. + * @iface_clk: Handle to MDP interface clock. + * @core_mmss_clk: Handle to MMSS core clock. + * @bus_clk: Handle to bus clock. + * @mnoc_clk: Handle to MMSS NOC clock. + * @dsi_core_client: Pointer to SDE power client + * @phandle: Pointer to SDE power handle + */ +struct dsi_core_clk_info { + struct clk *mdp_core_clk; + struct clk *iface_clk; + struct clk *core_mmss_clk; + struct clk *bus_clk; + struct clk *mnoc_clk; + struct sde_power_client *dsi_core_client; + struct sde_power_handle *phandle; +}; + +/** + * struct dsi_link_clk_info - Link clock information for DSI hardware. + * @byte_clk: Handle to DSI byte clock. + * @pixel_clk: Handle to DSI pixel clock. + * @esc_clk: Handle to DSI escape clock. + * @byte_intf_clk: Handle to DSI byte intf. clock. + */ +struct dsi_link_clk_info { + struct clk *byte_clk; + struct clk *pixel_clk; + struct clk *esc_clk; + struct clk *byte_intf_clk; +}; + +/** + * struct link_clk_freq - Clock frequency information for Link clocks + * @byte_clk_rate: Frequency of DSI byte clock in KHz. + * @pixel_clk_rate: Frequency of DSI pixel clock in KHz. + * @esc_clk_rate: Frequency of DSI escape clock in KHz. + */ +struct link_clk_freq { + u32 byte_clk_rate; + u32 pix_clk_rate; + u32 esc_clk_rate; +}; + +/** + * typedef *pre_clockoff_cb() - Callback before clock is turned off + * @priv: private data pointer. + * @clk_type: clock which is being turned off. + * @new_state: next state for the clock. + * + * @return: error code. + */ +typedef int (*pre_clockoff_cb)(void *priv, + enum dsi_clk_type clk_type, + enum dsi_clk_state new_state); + +/** + * typedef *post_clockoff_cb() - Callback after clock is turned off + * @priv: private data pointer. + * @clk_type: clock which was turned off. + * @curr_state: current state for the clock. + * + * @return: error code. + */ +typedef int (*post_clockoff_cb)(void *priv, + enum dsi_clk_type clk_type, + enum dsi_clk_state curr_state); + +/** + * typedef *post_clockon_cb() - Callback after clock is turned on + * @priv: private data pointer. + * @clk_type: clock which was turned on. + * @curr_state: current state for the clock. + * + * @return: error code. + */ +typedef int (*post_clockon_cb)(void *priv, + enum dsi_clk_type clk_type, + enum dsi_clk_state curr_state); + +/** + * typedef *pre_clockon_cb() - Callback before clock is turned on + * @priv: private data pointer. + * @clk_type: clock which is being turned on. + * @new_state: next state for the clock. + * + * @return: error code. + */ +typedef int (*pre_clockon_cb)(void *priv, + enum dsi_clk_type clk_type, + enum dsi_clk_state new_state); + + +/** + * struct dsi_clk_info - clock information for DSI hardware. + * @name: client name. + * @c_clks[MAX_DSI_CTRL] array of core clock configurations + * @l_clks[MAX_DSI_CTRL] array of link clock configurations + * @bus_handle[MAX_DSI_CTRL] array of bus handles + * @ctrl_index[MAX_DSI_CTRL] array of DSI controller indexes mapped + * to core and link clock configurations + * @pre_clkoff_cb callback before clock is turned off + * @post_clkoff_cb callback after clock is turned off + * @post_clkon_cb callback after clock is turned on + * @pre_clkon_cb callback before clock is turned on + * @priv_data pointer to private data + * @master_ndx master DSI controller index + * @dsi_ctrl_count number of DSI controllers + */ +struct dsi_clk_info { + char name[MAX_STRING_LEN]; + struct dsi_core_clk_info c_clks[MAX_DSI_CTRL]; + struct dsi_link_clk_info l_clks[MAX_DSI_CTRL]; + u32 bus_handle[MAX_DSI_CTRL]; + u32 ctrl_index[MAX_DSI_CTRL]; + pre_clockoff_cb pre_clkoff_cb; + post_clockoff_cb post_clkoff_cb; + post_clockon_cb post_clkon_cb; + pre_clockon_cb pre_clkon_cb; + void *priv_data; + u32 master_ndx; + u32 dsi_ctrl_count; +}; + +/** + * struct dsi_clk_link_set - Pair of clock handles to describe link clocks + * @byte_clk: Handle to DSi byte clock. + * @pixel_clk: Handle to DSI pixel clock. + */ +struct dsi_clk_link_set { + struct clk *byte_clk; + struct clk *pixel_clk; +}; + +/** + * dsi_display_clk_mgr_register() - Register DSI clock manager + * @info: Structure containing DSI clock information + */ +void *dsi_display_clk_mngr_register(struct dsi_clk_info *info); + +/** + * dsi_display_clk_mngr_deregister() - Deregister DSI clock manager + * @clk_mngr: DSI clock manager pointer + */ +int dsi_display_clk_mngr_deregister(void *clk_mngr); + +/** + * dsi_register_clk_handle() - Register clock handle with DSI clock manager + * @clk_mngr: DSI clock manager pointer + * @client: DSI clock client pointer. + */ +void *dsi_register_clk_handle(void *clk_mngr, char *client); + +/** + * dsi_deregister_clk_handle() - Deregister clock handle from DSI clock manager + * @client: DSI clock client pointer. + * + * return: error code in case of failure or 0 for success. + */ +int dsi_deregister_clk_handle(void *client); + +/** + * dsi_display_clk_ctrl() - set frequencies for link clks + * @handle: Handle of desired DSI clock client. + * @clk_type: Clock which is being controlled. + * @clk_state: Desired state of clock + * + * return: error code in case of failure or 0 for success. + */ +int dsi_display_clk_ctrl(void *handle, + enum dsi_clk_type clk_type, enum dsi_clk_state clk_state); + +/** + * dsi_clk_set_link_frequencies() - set frequencies for link clks + * @client: DSI clock client pointer. + * @freq: Structure containing link clock frequencies. + * @index: Index of the DSI controller. + * + * return: error code in case of failure or 0 for success. + */ +int dsi_clk_set_link_frequencies(void *client, struct link_clk_freq freq, + u32 index); + + +/** + * dsi_clk_set_pixel_clk_rate() - set frequency for pixel clock + * @client: DSI clock client pointer. + * @pixel_clk: Pixel clock rate in Hz. + * @index: Index of the DSI controller. + * return: error code in case of failure or 0 for success. + */ +int dsi_clk_set_pixel_clk_rate(void *client, u64 pixel_clk, u32 index); + + +/** + * dsi_clk_set_byte_clk_rate() - set frequency for byte clock + * @client: DSI clock client pointer. + * @byte_clk: Pixel clock rate in Hz. + * @index: Index of the DSI controller. + * return: error code in case of failure or 0 for success. + */ +int dsi_clk_set_byte_clk_rate(void *client, u64 byte_clk, u32 index); + +/** + * dsi_clk_update_parent() - update parent clocks for specified clock + * @parent: link clock pair which are set as parent. + * @child: link clock pair whose parent has to be set. + */ +int dsi_clk_update_parent(struct dsi_clk_link_set *parent, + struct dsi_clk_link_set *child); +#endif /* _DSI_CLK_H_ */ diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c b/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c new file mode 100644 index 0000000000000000000000000000000000000000..560964e670d0438af116d51def3bf79b68f10ea2 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c @@ -0,0 +1,1226 @@ +/* + * 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. + * + */ + +#include +#include +#include +#include +#include "dsi_clk.h" + +struct dsi_core_clks { + struct dsi_core_clk_info clks; + u32 bus_handle; +}; + +struct dsi_link_clks { + struct dsi_link_clk_info clks; + struct link_clk_freq freq; +}; + +struct dsi_clk_mngr { + char name[MAX_STRING_LEN]; + struct mutex clk_mutex; + struct list_head client_list; + + u32 dsi_ctrl_count; + u32 master_ndx; + struct dsi_core_clks core_clks[MAX_DSI_CTRL]; + struct dsi_link_clks link_clks[MAX_DSI_CTRL]; + u32 ctrl_index[MAX_DSI_CTRL]; + u32 core_clk_state; + u32 link_clk_state; + + pre_clockoff_cb pre_clkoff_cb; + post_clockoff_cb post_clkoff_cb; + post_clockon_cb post_clkon_cb; + pre_clockon_cb pre_clkon_cb; + + void *priv_data; +}; + +struct dsi_clk_client_info { + char name[MAX_STRING_LEN]; + u32 core_refcount; + u32 link_refcount; + u32 core_clk_state; + u32 link_clk_state; + struct list_head list; + struct dsi_clk_mngr *mngr; +}; + +static int _get_clk_mngr_index(struct dsi_clk_mngr *mngr, + u32 dsi_ctrl_index, + u32 *clk_mngr_index) +{ + int i; + + for (i = 0; i < mngr->dsi_ctrl_count; i++) { + if (mngr->ctrl_index[i] == dsi_ctrl_index) { + *clk_mngr_index = i; + return 0; + } + } + + return -EINVAL; +} + +/** + * dsi_clk_set_link_frequencies() - set frequencies for link clks + * @clks: Link clock information + * @pixel_clk: pixel clock frequency in KHz. + * @byte_clk: Byte clock frequency in KHz. + * @esc_clk: Escape clock frequency in KHz. + * + * return: error code in case of failure or 0 for success. + */ +int dsi_clk_set_link_frequencies(void *client, struct link_clk_freq freq, + u32 index) +{ + int rc = 0, clk_mngr_index = 0; + struct dsi_clk_client_info *c = client; + struct dsi_clk_mngr *mngr; + + if (!client) { + pr_err("invalid params\n"); + return -EINVAL; + } + + mngr = c->mngr; + rc = _get_clk_mngr_index(mngr, index, &clk_mngr_index); + if (rc) { + pr_err("failed to map control index %d\n", index); + return -EINVAL; + } + + memcpy(&mngr->link_clks[clk_mngr_index].freq, &freq, + sizeof(struct link_clk_freq)); + + return rc; +} + +/** + * dsi_clk_set_pixel_clk_rate() - set frequency for pixel clock + * @clks: DSI link clock information. + * @pixel_clk: Pixel clock rate in KHz. + * + * return: error code in case of failure or 0 for success. + */ +int dsi_clk_set_pixel_clk_rate(void *client, u64 pixel_clk, u32 index) +{ + int rc = 0; + struct dsi_clk_client_info *c = client; + struct dsi_clk_mngr *mngr; + + mngr = c->mngr; + rc = clk_set_rate(mngr->link_clks[index].clks.pixel_clk, pixel_clk); + if (rc) + pr_err("failed to set clk rate for pixel clk, rc=%d\n", rc); + else + mngr->link_clks[index].freq.pix_clk_rate = pixel_clk; + + return rc; +} + +/** + * dsi_clk_set_byte_clk_rate() - set frequency for byte clock + * @client: DSI clock client pointer. + * @byte_clk: Pixel clock rate in Hz. + * @index: Index of the DSI controller. + * return: error code in case of failure or 0 for success. + */ +int dsi_clk_set_byte_clk_rate(void *client, u64 byte_clk, u32 index) +{ + int rc = 0; + struct dsi_clk_client_info *c = client; + struct dsi_clk_mngr *mngr; + + mngr = c->mngr; + rc = clk_set_rate(mngr->link_clks[index].clks.byte_clk, byte_clk); + if (rc) + pr_err("failed to set clk rate for byte clk, rc=%d\n", rc); + else + mngr->link_clks[index].freq.byte_clk_rate = byte_clk; + + return rc; + +} + +/** + * dsi_clk_update_parent() - update parent clocks for specified clock + * @parent: link clock pair which are set as parent. + * @child: link clock pair whose parent has to be set. + */ +int dsi_clk_update_parent(struct dsi_clk_link_set *parent, + struct dsi_clk_link_set *child) +{ + int rc = 0; + + rc = clk_set_parent(child->byte_clk, parent->byte_clk); + if (rc) { + pr_err("failed to set byte clk parent\n"); + goto error; + } + + rc = clk_set_parent(child->pixel_clk, parent->pixel_clk); + if (rc) { + pr_err("failed to set pixel clk parent\n"); + goto error; + } +error: + return rc; +} + +int dsi_core_clk_start(struct dsi_core_clks *c_clks) +{ + int rc = 0; + + if (c_clks->clks.mdp_core_clk) { + rc = clk_prepare_enable(c_clks->clks.mdp_core_clk); + if (rc) { + pr_err("failed to enable mdp_core_clk, rc=%d\n", rc); + goto error; + } + } + + if (c_clks->clks.mnoc_clk) { + rc = clk_prepare_enable(c_clks->clks.mnoc_clk); + if (rc) { + pr_err("failed to enable mnoc_clk, rc=%d\n", rc); + goto error_disable_core_clk; + } + } + + if (c_clks->clks.iface_clk) { + rc = clk_prepare_enable(c_clks->clks.iface_clk); + if (rc) { + pr_err("failed to enable iface_clk, rc=%d\n", rc); + goto error_disable_mnoc_clk; + } + } + + if (c_clks->clks.bus_clk) { + rc = clk_prepare_enable(c_clks->clks.bus_clk); + if (rc) { + pr_err("failed to enable bus_clk, rc=%d\n", rc); + goto error_disable_iface_clk; + } + } + + if (c_clks->clks.core_mmss_clk) { + rc = clk_prepare_enable(c_clks->clks.core_mmss_clk); + if (rc) { + pr_err("failed to enable core_mmss_clk, rc=%d\n", rc); + goto error_disable_bus_clk; + } + } + + if (c_clks->bus_handle) { + rc = msm_bus_scale_client_update_request(c_clks->bus_handle, 1); + if (rc) { + pr_err("bus scale client enable failed, rc=%d\n", rc); + goto error_disable_mmss_clk; + } + } + + return rc; + +error_disable_mmss_clk: + if (c_clks->clks.core_mmss_clk) + clk_disable_unprepare(c_clks->clks.core_mmss_clk); +error_disable_bus_clk: + if (c_clks->clks.bus_clk) + clk_disable_unprepare(c_clks->clks.bus_clk); +error_disable_iface_clk: + if (c_clks->clks.iface_clk) + clk_disable_unprepare(c_clks->clks.iface_clk); +error_disable_mnoc_clk: + if (c_clks->clks.mnoc_clk) + clk_disable_unprepare(c_clks->clks.mnoc_clk); +error_disable_core_clk: + if (c_clks->clks.mdp_core_clk) + clk_disable_unprepare(c_clks->clks.mdp_core_clk); +error: + return rc; +} + +int dsi_core_clk_stop(struct dsi_core_clks *c_clks) +{ + int rc = 0; + + if (c_clks->bus_handle) { + rc = msm_bus_scale_client_update_request(c_clks->bus_handle, 0); + if (rc) { + pr_err("bus scale client disable failed, rc=%d\n", rc); + return rc; + } + } + + if (c_clks->clks.core_mmss_clk) + clk_disable_unprepare(c_clks->clks.core_mmss_clk); + + if (c_clks->clks.bus_clk) + clk_disable_unprepare(c_clks->clks.bus_clk); + + if (c_clks->clks.iface_clk) + clk_disable_unprepare(c_clks->clks.iface_clk); + + if (c_clks->clks.mnoc_clk) + clk_disable_unprepare(c_clks->clks.mnoc_clk); + + if (c_clks->clks.mdp_core_clk) + clk_disable_unprepare(c_clks->clks.mdp_core_clk); + + return rc; +} + +static int dsi_link_clk_set_rate(struct dsi_link_clks *l_clks) +{ + int rc = 0; + + rc = clk_set_rate(l_clks->clks.esc_clk, l_clks->freq.esc_clk_rate); + if (rc) { + pr_err("clk_set_rate failed for esc_clk rc = %d\n", rc); + goto error; + } + + rc = clk_set_rate(l_clks->clks.byte_clk, l_clks->freq.byte_clk_rate); + if (rc) { + pr_err("clk_set_rate failed for byte_clk rc = %d\n", rc); + goto error; + } + + rc = clk_set_rate(l_clks->clks.pixel_clk, l_clks->freq.pix_clk_rate); + if (rc) { + pr_err("clk_set_rate failed for pixel_clk rc = %d\n", rc); + goto error; + } + + /* + * If byte_intf_clk is present, set rate for that too. + * For DPHY: byte_intf_clk_rate = byte_clk_rate / 2 + * todo: this needs to be revisited when support for CPHY is added + */ + if (l_clks->clks.byte_intf_clk) { + rc = clk_set_rate(l_clks->clks.byte_intf_clk, + (l_clks->freq.byte_clk_rate / 2)); + if (rc) { + pr_err("set_rate failed for byte_intf_clk rc = %d\n", + rc); + goto error; + } + } +error: + return rc; +} + +static int dsi_link_clk_prepare(struct dsi_link_clks *l_clks) +{ + int rc = 0; + + rc = clk_prepare(l_clks->clks.esc_clk); + if (rc) { + pr_err("Failed to prepare dsi esc clk, rc=%d\n", rc); + goto esc_clk_err; + } + + rc = clk_prepare(l_clks->clks.byte_clk); + if (rc) { + pr_err("Failed to prepare dsi byte clk, rc=%d\n", rc); + goto byte_clk_err; + } + + rc = clk_prepare(l_clks->clks.pixel_clk); + if (rc) { + pr_err("Failed to prepare dsi pixel clk, rc=%d\n", rc); + goto pixel_clk_err; + } + + if (l_clks->clks.byte_intf_clk) { + rc = clk_prepare(l_clks->clks.byte_intf_clk); + if (rc) { + pr_err("Failed to prepare dsi byte intf clk, rc=%d\n", + rc); + goto byte_intf_clk_err; + } + } + + return rc; + +byte_intf_clk_err: + clk_unprepare(l_clks->clks.pixel_clk); +pixel_clk_err: + clk_unprepare(l_clks->clks.byte_clk); +byte_clk_err: + clk_unprepare(l_clks->clks.esc_clk); +esc_clk_err: + return rc; +} + +static void dsi_link_clk_unprepare(struct dsi_link_clks *l_clks) +{ + if (l_clks->clks.byte_intf_clk) + clk_unprepare(l_clks->clks.byte_intf_clk); + clk_unprepare(l_clks->clks.pixel_clk); + clk_unprepare(l_clks->clks.byte_clk); + clk_unprepare(l_clks->clks.esc_clk); +} + +static int dsi_link_clk_enable(struct dsi_link_clks *l_clks) +{ + int rc = 0; + + rc = clk_enable(l_clks->clks.esc_clk); + if (rc) { + pr_err("Failed to enable dsi esc clk, rc=%d\n", rc); + goto esc_clk_err; + } + + rc = clk_enable(l_clks->clks.byte_clk); + if (rc) { + pr_err("Failed to enable dsi byte clk, rc=%d\n", rc); + goto byte_clk_err; + } + + rc = clk_enable(l_clks->clks.pixel_clk); + if (rc) { + pr_err("Failed to enable dsi pixel clk, rc=%d\n", rc); + goto pixel_clk_err; + } + + if (l_clks->clks.byte_intf_clk) { + rc = clk_enable(l_clks->clks.byte_intf_clk); + if (rc) { + pr_err("Failed to enable dsi byte intf clk, rc=%d\n", + rc); + goto byte_intf_clk_err; + } + } + + return rc; + +byte_intf_clk_err: + clk_disable(l_clks->clks.pixel_clk); +pixel_clk_err: + clk_disable(l_clks->clks.byte_clk); +byte_clk_err: + clk_disable(l_clks->clks.esc_clk); +esc_clk_err: + return rc; +} + +static void dsi_link_clk_disable(struct dsi_link_clks *l_clks) +{ + if (l_clks->clks.byte_intf_clk) + clk_disable(l_clks->clks.byte_intf_clk); + clk_disable(l_clks->clks.esc_clk); + clk_disable(l_clks->clks.pixel_clk); + clk_disable(l_clks->clks.byte_clk); +} + +/** + * dsi_link_clk_start() - enable dsi link clocks + */ +int dsi_link_clk_start(struct dsi_link_clks *clks) +{ + int rc = 0; + + rc = dsi_link_clk_set_rate(clks); + if (rc) { + pr_err("failed to set clk rates, rc = %d\n", rc); + goto error; + } + + rc = dsi_link_clk_prepare(clks); + if (rc) { + pr_err("failed to prepare link clks, rc = %d\n", rc); + goto error; + } + + rc = dsi_link_clk_enable(clks); + if (rc) { + pr_err("failed to enable link clks, rc = %d\n", rc); + goto error_unprepare; + } + + pr_debug("Link clocks are enabled\n"); + return rc; +error_unprepare: + dsi_link_clk_unprepare(clks); +error: + return rc; +} + +/** + * dsi_link_clk_stop() - Stop DSI link clocks. + */ +int dsi_link_clk_stop(struct dsi_link_clks *clks) +{ + dsi_link_clk_disable(clks); + dsi_link_clk_unprepare(clks); + + pr_debug("Link clocks disabled\n"); + + return 0; +} + +static int dsi_display_core_clk_enable(struct dsi_core_clks *clks, + u32 ctrl_count, u32 master_ndx) +{ + int rc = 0; + int i; + struct dsi_core_clks *clk, *m_clks; + + /* + * In case of split DSI usecases, the clock for master controller should + * be enabled before the other controller. Master controller in the + * clock context refers to the controller that sources the clock. + */ + + m_clks = &clks[master_ndx]; + rc = sde_power_resource_enable(m_clks->clks.phandle, + m_clks->clks.dsi_core_client, true); + + if (rc) { + pr_err("Power resource enable failed, rc=%d\n", rc); + goto error; + } + + rc = dsi_core_clk_start(m_clks); + if (rc) { + pr_err("failed to turn on master clocks, rc=%d\n", rc); + goto error_disable_master_resource; + } + + /* Turn on rest of the core clocks */ + for (i = 0; i < ctrl_count; i++) { + clk = &clks[i]; + if (!clk || (clk == m_clks)) + continue; + + rc = sde_power_resource_enable(clk->clks.phandle, + clk->clks.dsi_core_client, true); + if (rc) { + pr_err("Power resource enable failed, rc=%d\n", rc); + goto error_disable_master; + } + + rc = dsi_core_clk_start(clk); + if (rc) { + pr_err("failed to turn on clocks, rc=%d\n", rc); + (void)sde_power_resource_enable(clk->clks.phandle, + clk->clks.dsi_core_client, false); + goto error_disable_master; + } + } + return rc; +error_disable_master: + (void)dsi_core_clk_stop(m_clks); + +error_disable_master_resource: + (void)sde_power_resource_enable(m_clks->clks.phandle, + m_clks->clks.dsi_core_client, false); +error: + return rc; +} + +static int dsi_display_link_clk_enable(struct dsi_link_clks *clks, + u32 ctrl_count, u32 master_ndx) +{ + int rc = 0; + int i; + struct dsi_link_clks *clk, *m_clks; + + /* + * In case of split DSI usecases, the clock for master controller should + * be enabled before the other controller. Master controller in the + * clock context refers to the controller that sources the clock. + */ + + m_clks = &clks[master_ndx]; + + rc = dsi_link_clk_start(m_clks); + if (rc) { + pr_err("failed to turn on master clocks, rc=%d\n", rc); + goto error; + } + + /* Turn on rest of the core clocks */ + for (i = 0; i < ctrl_count; i++) { + clk = &clks[i]; + if (!clk || (clk == m_clks)) + continue; + + rc = dsi_link_clk_start(clk); + if (rc) { + pr_err("failed to turn on clocks, rc=%d\n", rc); + goto error_disable_master; + } + } + return rc; +error_disable_master: + (void)dsi_link_clk_stop(m_clks); +error: + return rc; +} + +static int dsi_display_core_clk_disable(struct dsi_core_clks *clks, + u32 ctrl_count, u32 master_ndx) +{ + int rc = 0; + int i; + struct dsi_core_clks *clk, *m_clks; + + /* + * In case of split DSI usecases, clock for slave DSI controllers should + * be disabled first before disabling clock for master controller. Slave + * controllers in the clock context refer to controller which source + * clock from another controller. + */ + + m_clks = &clks[master_ndx]; + + /* Turn off non-master core clocks */ + for (i = 0; i < ctrl_count; i++) { + clk = &clks[i]; + if (!clk || (clk == m_clks)) + continue; + + rc = dsi_core_clk_stop(clk); + if (rc) { + pr_debug("failed to turn off clocks, rc=%d\n", rc); + goto error; + } + + rc = sde_power_resource_enable(clk->clks.phandle, + clk->clks.dsi_core_client, false); + if (rc) { + pr_err("Power resource disable failed: %d\n", rc); + goto error; + } + } + + rc = dsi_core_clk_stop(m_clks); + if (rc) { + pr_err("failed to turn off master clocks, rc=%d\n", rc); + goto error; + } + + rc = sde_power_resource_enable(m_clks->clks.phandle, + m_clks->clks.dsi_core_client, false); + if (rc) + pr_err("Power resource disable failed: %d\n", rc); +error: + return rc; +} + +static int dsi_display_link_clk_disable(struct dsi_link_clks *clks, + u32 ctrl_count, u32 master_ndx) +{ + int rc = 0; + int i; + struct dsi_link_clks *clk, *m_clks; + + /* + * In case of split DSI usecases, clock for slave DSI controllers should + * be disabled first before disabling clock for master controller. Slave + * controllers in the clock context refer to controller which source + * clock from another controller. + */ + + m_clks = &clks[master_ndx]; + + /* Turn off non-master link clocks */ + for (i = 0; i < ctrl_count; i++) { + clk = &clks[i]; + if (!clk || (clk == m_clks)) + continue; + + rc = dsi_link_clk_stop(clk); + if (rc) + pr_err("failed to turn off clocks, rc=%d\n", rc); + } + + rc = dsi_link_clk_stop(m_clks); + if (rc) + pr_err("failed to turn off master clocks, rc=%d\n", rc); + + return rc; +} + +static int dsi_update_clk_state(struct dsi_core_clks *c_clks, u32 c_state, + struct dsi_link_clks *l_clks, u32 l_state) +{ + int rc = 0; + struct dsi_clk_mngr *mngr; + bool l_c_on = false; + + if (c_clks) { + mngr = + container_of(c_clks, struct dsi_clk_mngr, core_clks[0]); + } else if (l_clks) { + mngr = + container_of(l_clks, struct dsi_clk_mngr, link_clks[0]); + } else { + mngr = NULL; + } + + if (!mngr) + return -EINVAL; + + pr_debug("c_state = %d, l_state = %d\n", + c_clks ? c_state : -1, l_clks ? l_state : -1); + /* + * Below is the sequence to toggle DSI clocks: + * 1. For ON sequence, Core clocks before link clocks + * 2. For OFF sequence, Link clocks before core clocks. + */ + if (c_clks && (c_state == DSI_CLK_ON)) { + if (mngr->core_clk_state == DSI_CLK_OFF) { + rc = mngr->pre_clkon_cb(mngr->priv_data, + DSI_CORE_CLK, + DSI_CLK_ON); + if (rc) { + pr_err("failed to turn on MDP FS rc= %d\n", rc); + goto error; + } + } + rc = dsi_display_core_clk_enable(c_clks, mngr->dsi_ctrl_count, + mngr->master_ndx); + if (rc) { + pr_err("failed to turn on core clks rc = %d\n", rc); + goto error; + } + + if (mngr->post_clkon_cb) { + rc = mngr->post_clkon_cb(mngr->priv_data, + DSI_CORE_CLK, + DSI_CLK_ON); + if (rc) + pr_err("post clk on cb failed, rc = %d\n", rc); + } + mngr->core_clk_state = DSI_CLK_ON; + } + + if (l_clks) { + if (l_state == DSI_CLK_ON) { + if (mngr->pre_clkon_cb) { + rc = mngr->pre_clkon_cb(mngr->priv_data, + DSI_LINK_CLK, l_state); + if (rc) + pr_err("pre link clk on cb failed\n"); + } + rc = dsi_display_link_clk_enable(l_clks, + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("failed to start link clk rc= %d\n", rc); + goto error; + } + if (mngr->post_clkon_cb) { + rc = mngr->post_clkon_cb(mngr->priv_data, + DSI_LINK_CLK, + l_state); + if (rc) + pr_err("post link clk on cb failed\n"); + } + } else { + /* + * Two conditions that need to be checked for Link + * clocks: + * 1. Link clocks need core clocks to be on when + * transitioning from EARLY_GATE to OFF state. + * 2. ULPS mode might have to be enabled in case of OFF + * state. For ULPS, Link clocks should be turned ON + * first before they are turned off again. + * + * If Link is going from EARLY_GATE to OFF state AND + * Core clock is already in EARLY_GATE or OFF state, + * turn on Core clocks and link clocks. + * + * ULPS state is managed as part of the pre_clkoff_cb. + */ + if ((l_state == DSI_CLK_OFF) && + (mngr->link_clk_state == + DSI_CLK_EARLY_GATE) && + (mngr->core_clk_state != + DSI_CLK_ON)) { + rc = dsi_display_core_clk_enable( + mngr->core_clks, mngr->dsi_ctrl_count, + mngr->master_ndx); + if (rc) { + pr_err("core clks did not start\n"); + goto error; + } + + rc = dsi_display_link_clk_enable(l_clks, + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("Link clks did not start\n"); + goto error; + } + l_c_on = true; + pr_debug("ECG: core and Link_on\n"); + } + + if (mngr->pre_clkoff_cb) { + rc = mngr->pre_clkoff_cb(mngr->priv_data, + DSI_LINK_CLK, l_state); + if (rc) + pr_err("pre link clk off cb failed\n"); + } + + rc = dsi_display_link_clk_disable(l_clks, + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("failed to stop link clk, rc = %d\n", + rc); + goto error; + } + + if (mngr->post_clkoff_cb) { + rc = mngr->post_clkoff_cb(mngr->priv_data, + DSI_LINK_CLK, l_state); + if (rc) + pr_err("post link clk off cb failed\n"); + } + /* + * This check is to save unnecessary clock state + * change when going from EARLY_GATE to OFF. In the + * case where the request happens for both Core and Link + * clocks in the same call, core clocks need to be + * turned on first before OFF state can be entered. + * + * Core clocks are turned on here for Link clocks to go + * to OFF state. If core clock request is also present, + * then core clocks can be turned off Core clocks are + * transitioned to OFF state. + */ + if (l_c_on && (!(c_clks && (c_state == DSI_CLK_OFF) + && (mngr->core_clk_state == + DSI_CLK_EARLY_GATE)))) { + rc = dsi_display_core_clk_disable( + mngr->core_clks, mngr->dsi_ctrl_count, + mngr->master_ndx); + if (rc) { + pr_err("core clks did not stop\n"); + goto error; + } + + l_c_on = false; + pr_debug("ECG: core off\n"); + } else + pr_debug("ECG: core off skip\n"); + } + + mngr->link_clk_state = l_state; + } + + if (c_clks && (c_state != DSI_CLK_ON)) { + /* + * When going to OFF state from EARLY GATE state, Core clocks + * should be turned on first so that the IOs can be clamped. + * l_c_on flag is set, then the core clocks were turned before + * to the Link clocks go to OFF state. So Core clocks are + * already ON and this step can be skipped. + * + * IOs are clamped in pre_clkoff_cb callback. + */ + if ((c_state == DSI_CLK_OFF) && + (mngr->core_clk_state == + DSI_CLK_EARLY_GATE) && !l_c_on) { + rc = dsi_display_core_clk_enable(mngr->core_clks, + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("core clks did not start\n"); + goto error; + } + pr_debug("ECG: core on\n"); + } else + pr_debug("ECG: core on skip\n"); + + if (mngr->pre_clkoff_cb) { + rc = mngr->pre_clkoff_cb(mngr->priv_data, + DSI_CORE_CLK, + c_state); + if (rc) + pr_err("pre core clk off cb failed\n"); + } + + rc = dsi_display_core_clk_disable(c_clks, mngr->dsi_ctrl_count, + mngr->master_ndx); + if (rc) { + pr_err("failed to turn off core clks rc = %d\n", rc); + goto error; + } + + if (c_state == DSI_CLK_OFF) { + if (mngr->post_clkoff_cb) { + rc = mngr->post_clkoff_cb(mngr->priv_data, + DSI_CORE_CLK, + DSI_CLK_OFF); + if (rc) + pr_err("post clkoff cb fail, rc = %d\n", + rc); + } + } + mngr->core_clk_state = c_state; + } + +error: + return rc; +} + +static int dsi_recheck_clk_state(struct dsi_clk_mngr *mngr) +{ + int rc = 0; + struct list_head *pos = NULL; + struct dsi_clk_client_info *c; + u32 new_core_clk_state = DSI_CLK_OFF; + u32 new_link_clk_state = DSI_CLK_OFF; + u32 old_c_clk_state = DSI_CLK_OFF; + u32 old_l_clk_state = DSI_CLK_OFF; + struct dsi_core_clks *c_clks = NULL; + struct dsi_link_clks *l_clks = NULL; + + /* + * Conditions to maintain DSI manager clock state based on + * clock states of various clients: + * 1. If any client has clock in ON state, DSI manager clock state + * should be ON. + * 2. If any client is in ECG state with rest of them turned OFF, + * go to Early gate state. + * 3. If all clients have clocks as OFF, then go to OFF state. + */ + list_for_each(pos, &mngr->client_list) { + c = list_entry(pos, struct dsi_clk_client_info, list); + if (c->core_clk_state == DSI_CLK_ON) { + new_core_clk_state = DSI_CLK_ON; + break; + } else if (c->core_clk_state == DSI_CLK_EARLY_GATE) { + new_core_clk_state = DSI_CLK_EARLY_GATE; + } + } + + list_for_each(pos, &mngr->client_list) { + c = list_entry(pos, struct dsi_clk_client_info, list); + if (c->link_clk_state == DSI_CLK_ON) { + new_link_clk_state = DSI_CLK_ON; + break; + } else if (c->link_clk_state == DSI_CLK_EARLY_GATE) { + new_link_clk_state = DSI_CLK_EARLY_GATE; + } + } + + if (new_core_clk_state != mngr->core_clk_state) + c_clks = mngr->core_clks; + + if (new_link_clk_state != mngr->link_clk_state) + l_clks = mngr->link_clks; + + old_c_clk_state = mngr->core_clk_state; + old_l_clk_state = mngr->link_clk_state; + + pr_debug("c_clk_state (%d -> %d)\n", + old_c_clk_state, new_core_clk_state); + pr_debug("l_clk_state (%d -> %d)\n", + old_l_clk_state, new_link_clk_state); + + if (c_clks || l_clks) { + rc = dsi_update_clk_state(c_clks, new_core_clk_state, + l_clks, new_link_clk_state); + if (rc) { + pr_err("failed to update clock state, rc = %d\n", rc); + goto error; + } + } + +error: + return rc; +} + +int dsi_clk_req_state(void *client, enum dsi_clk_type clk, + enum dsi_clk_state state) +{ + int rc = 0; + struct dsi_clk_client_info *c = client; + struct dsi_clk_mngr *mngr; + bool changed = false; + + if (!client || !clk || clk > (DSI_CORE_CLK | DSI_LINK_CLK) || + state > DSI_CLK_EARLY_GATE) { + pr_err("Invalid params, client = %pK, clk = 0x%x, state = %d\n", + client, clk, state); + return -EINVAL; + } + + mngr = c->mngr; + mutex_lock(&mngr->clk_mutex); + + pr_debug("[%s]%s: CLK=%d, new_state=%d, core=%d, linkl=%d\n", + mngr->name, c->name, clk, state, c->core_clk_state, + c->link_clk_state); + + /* + * Clock refcount handling as below: + * i. Increment refcount whenever ON is called. + * ii. Decrement refcount when transitioning from ON state to + * either OFF or EARLY_GATE. + * iii. Do not decrement refcount when changing from + * EARLY_GATE to OFF. + */ + if (state == DSI_CLK_ON) { + if (clk & DSI_CORE_CLK) { + c->core_refcount++; + if (c->core_clk_state != DSI_CLK_ON) { + c->core_clk_state = DSI_CLK_ON; + changed = true; + } + } + if (clk & DSI_LINK_CLK) { + c->link_refcount++; + if (c->link_clk_state != DSI_CLK_ON) { + c->link_clk_state = DSI_CLK_ON; + changed = true; + } + } + } else if ((state == DSI_CLK_EARLY_GATE) || + (state == DSI_CLK_OFF)) { + if (clk & DSI_CORE_CLK) { + if (c->core_refcount == 0) { + if ((c->core_clk_state == + DSI_CLK_EARLY_GATE) && + (state == DSI_CLK_OFF)) { + changed = true; + c->core_clk_state = DSI_CLK_OFF; + } else { + pr_warn("Core refcount is zero for %s", + c->name); + } + } else { + c->core_refcount--; + if (c->core_refcount == 0) { + c->core_clk_state = state; + changed = true; + } + } + } + if (clk & DSI_LINK_CLK) { + if (c->link_refcount == 0) { + if ((c->link_clk_state == + DSI_CLK_EARLY_GATE) && + (state == DSI_CLK_OFF)) { + changed = true; + c->link_clk_state = DSI_CLK_OFF; + } else { + pr_warn("Link refcount is zero for %s", + c->name); + } + } else { + c->link_refcount--; + if (c->link_refcount == 0) { + c->link_clk_state = state; + changed = true; + } + } + } + } + pr_debug("[%s]%s: change=%d, Core (ref=%d, state=%d), Link (ref=%d, state=%d)\n", + mngr->name, c->name, changed, c->core_refcount, + c->core_clk_state, c->link_refcount, c->link_clk_state); + + if (changed) { + rc = dsi_recheck_clk_state(mngr); + if (rc) + pr_err("Failed to adjust clock state rc = %d\n", rc); + } + + mutex_unlock(&mngr->clk_mutex); + return rc; +} + +DEFINE_MUTEX(dsi_mngr_clk_mutex); + +int dsi_display_clk_ctrl(void *handle, + enum dsi_clk_type clk_type, enum dsi_clk_state clk_state) +{ + int rc = 0; + + if (!handle) { + pr_err("%s: Invalid arg\n", __func__); + return -EINVAL; + } + + mutex_lock(&dsi_mngr_clk_mutex); + rc = dsi_clk_req_state(handle, clk_type, clk_state); + if (rc) + pr_err("%s: failed set clk state, rc = %d\n", __func__, rc); + mutex_unlock(&dsi_mngr_clk_mutex); + + return rc; +} + +void *dsi_register_clk_handle(void *clk_mngr, char *client) +{ + void *handle = NULL; + struct dsi_clk_mngr *mngr = clk_mngr; + struct dsi_clk_client_info *c; + + if (!mngr) { + pr_err("bad params\n"); + return ERR_PTR(-EINVAL); + } + + mutex_lock(&mngr->clk_mutex); + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) { + handle = ERR_PTR(-ENOMEM); + goto error; + } + + strlcpy(c->name, client, MAX_STRING_LEN); + c->mngr = mngr; + + list_add(&c->list, &mngr->client_list); + + pr_debug("[%s]: Added new client (%s)\n", mngr->name, c->name); + handle = c; +error: + mutex_unlock(&mngr->clk_mutex); + return handle; +} + +int dsi_deregister_clk_handle(void *client) +{ + int rc = 0; + struct dsi_clk_client_info *c = client; + struct dsi_clk_mngr *mngr; + struct list_head *pos = NULL; + struct list_head *tmp = NULL; + struct dsi_clk_client_info *node = NULL; + + if (!client) { + pr_err("Invalid params\n"); + return -EINVAL; + } + + mngr = c->mngr; + pr_debug("%s: ENTER\n", mngr->name); + mutex_lock(&mngr->clk_mutex); + c->core_clk_state = DSI_CLK_OFF; + c->link_clk_state = DSI_CLK_OFF; + + rc = dsi_recheck_clk_state(mngr); + if (rc) { + pr_err("clock state recheck failed rc = %d\n", rc); + goto error; + } + + list_for_each_safe(pos, tmp, &mngr->client_list) { + node = list_entry(pos, struct dsi_clk_client_info, + list); + if (node == c) { + list_del(&node->list); + pr_debug("Removed device (%s)\n", node->name); + kfree(node); + break; + } + } + +error: + mutex_unlock(&mngr->clk_mutex); + pr_debug("%s: EXIT, rc = %d\n", mngr->name, rc); + return rc; +} + +void *dsi_display_clk_mngr_register(struct dsi_clk_info *info) +{ + struct dsi_clk_mngr *mngr; + int i = 0; + + if (!info) { + pr_err("Invalid params\n"); + return ERR_PTR(-EINVAL); + } + + mngr = kzalloc(sizeof(*mngr), GFP_KERNEL); + if (!mngr) { + mngr = ERR_PTR(-ENOMEM); + goto error; + } + + mutex_init(&mngr->clk_mutex); + mngr->dsi_ctrl_count = info->dsi_ctrl_count; + mngr->master_ndx = info->master_ndx; + + if (mngr->dsi_ctrl_count > MAX_DSI_CTRL) { + kfree(mngr); + return ERR_PTR(-EINVAL); + } + + for (i = 0; i < mngr->dsi_ctrl_count; i++) { + memcpy(&mngr->core_clks[i].clks, &info->c_clks[i], + sizeof(struct dsi_core_clk_info)); + memcpy(&mngr->link_clks[i].clks, &info->l_clks[i], + sizeof(struct dsi_link_clk_info)); + mngr->core_clks[i].bus_handle = info->bus_handle[i]; + mngr->ctrl_index[i] = info->ctrl_index[i]; + } + + INIT_LIST_HEAD(&mngr->client_list); + mngr->pre_clkon_cb = info->pre_clkon_cb; + mngr->post_clkon_cb = info->post_clkon_cb; + mngr->pre_clkoff_cb = info->pre_clkoff_cb; + mngr->post_clkoff_cb = info->post_clkoff_cb; + mngr->priv_data = info->priv_data; + memcpy(mngr->name, info->name, MAX_STRING_LEN); + +error: + pr_debug("EXIT, rc = %ld\n", PTR_ERR(mngr)); + return mngr; +} + +int dsi_display_clk_mngr_deregister(void *clk_mngr) +{ + int rc = 0; + struct dsi_clk_mngr *mngr = clk_mngr; + struct list_head *position = NULL; + struct list_head *tmp = NULL; + struct dsi_clk_client_info *node = NULL; + + if (!mngr) { + pr_err("Invalid params\n"); + return -EINVAL; + } + + pr_debug("%s: ENTER\n", mngr->name); + mutex_lock(&mngr->clk_mutex); + + list_for_each_safe(position, tmp, &mngr->client_list) { + node = list_entry(position, struct dsi_clk_client_info, + list); + list_del(&node->list); + pr_debug("Removed device (%s)\n", node->name); + kfree(node); + } + + rc = dsi_recheck_clk_state(mngr); + if (rc) + pr_err("failed to disable all clocks\n"); + + mutex_unlock(&mngr->clk_mutex); + pr_debug("%s: EXIT, rc = %d\n", mngr->name, rc); + kfree(mngr); + return rc; +} + diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c b/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c new file mode 100644 index 0000000000000000000000000000000000000000..a18105587ed068ec1cdf5090cda9cb58dcde8001 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c @@ -0,0 +1,2859 @@ +/* + * 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. + */ + +#define pr_fmt(fmt) "dsi-ctrl:[%s] " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include