diff --git a/Documentation/devicetree/bindings/display/msm/dsi.txt b/Documentation/devicetree/bindings/display/msm/dsi.txt index dfc743219bd88e4ab858470fac7d77752ff261a3..577b3cede370e0332489111741eb0ec6cc1654e9 100644 --- a/Documentation/devicetree/bindings/display/msm/dsi.txt +++ b/Documentation/devicetree/bindings/display/msm/dsi.txt @@ -241,4 +241,7 @@ Example: vddio-supply = <&pma8084_l12>; qcom,dsi-phy-regulator-ldo-mode; + qcom,panel-allow-phy-poweroff; + qcom,dsi-phy-regulator-min-datarate-bps = <1200000000>; + qcom,panel-force-clock-lane-hs; }; diff --git a/Documentation/devicetree/bindings/display/msm/mdss-dsi-panel.txt b/Documentation/devicetree/bindings/display/msm/mdss-dsi-panel.txt new file mode 100644 index 0000000000000000000000000000000000000000..43c9d0bbe06d1fa2417d81a593716b78fd7529dc --- /dev/null +++ b/Documentation/devicetree/bindings/display/msm/mdss-dsi-panel.txt @@ -0,0 +1,790 @@ +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-qsync-min-refresh-rate: A u32 entry to specify minimum refresh rate supported by the panel to enable qsync feature. +- qcom,mdss-dsi-qsync-on-commands: String that specifies the commands to enable qsync feature. +- qcom,mdss-dsi-qsync-on-commands-state: String that specifies the ctrl state for sending qsync on commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- qcom,mdss-dsi-qsync-off-commands: String that specifies the commands to disable qsync feature. +- qcom,mdss-dsi-qsync-off-commands-state: String that specifies the ctrl state for sending qsync off commands. + "dsi_lp_mode" = DSI low power mode (default) + "dsi_hs_mode" = DSI high speed mode +- 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. + 0x00 = default value. +- qcom,mdss-dsi-t-clk-pre: Specifies the byte clock cycles before mode switch. + 0x00 = 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. This property is specified + per timing node to support resolution restrictions. +- 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. This property is specified per timing node to support + resolution restrictions. +- 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. This property is specified per timing node to support + resolution's alignment restrictions. +- 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. +- qcom,mdss-dsi-ext-bridge-mode: External bridge chip is connected instead of panel. + +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-qsync-min-refresh-rate = <30>; + 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,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,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-dsi-qsync-on-commands = [15 01 00 00 00 00 02 51 00]; + qcom,mdss-dsi-qsync-on-commands-state = "dsi_hs_mode"; + qcom,mdss-dsi-qsync-off-commands = [15 01 00 00 00 00 02 51 00]; + qcom,mdss-dsi-qsync-off-commands-state = "dsi_hs_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,partial-update-enabled = "single_roi"; + qcom,panel-roi-alignment = <4 4 2 2 20 20>; + }; + }; + 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/display/msm/mdss-pll.txt b/Documentation/devicetree/bindings/display/msm/mdss-pll.txt new file mode 100644 index 0000000000000000000000000000000000000000..4583f4e8d364070e876011fa1e805deb8c8ced49 --- /dev/null +++ b/Documentation/devicetree/bindings/display/msm/mdss-pll.txt @@ -0,0 +1,105 @@ +Qualcomm Technologies, Inc. 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. Should be one of: + "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", + "qcom,mdss_dsi_pll_7nm", "qcom,mdss_dp_pll_7nm", + "qcom,mdss_dsi_pll_28lpm" +- 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/display/msm/sde-dp.txt b/Documentation/devicetree/bindings/display/msm/sde-dp.txt new file mode 100644 index 0000000000000000000000000000000000000000..c5494ebdff866242933431ca3061270e6236badc --- /dev/null +++ b/Documentation/devicetree/bindings/display/msm/sde-dp.txt @@ -0,0 +1,221 @@ +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,dp-aux-switch: Phandle for the driver used to program the AUX switch for Display Port orientation. +- 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" +- qcom,phy-version: Phy version + +[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 = <&pm8150b_pdphy>; + qcom,ext-disp = <&ext_disp>; + qcom,phy-version = <0x420>; + qcom,dp-aux-switch = <&fsa4480>; + + 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/display/msm/sde-dsi.txt b/Documentation/devicetree/bindings/display/msm/sde-dsi.txt new file mode 100644 index 0000000000000000000000000000000000000000..b706967565a7db7dbd381fc384fbbb8ef6a04d87 --- /dev/null +++ b/Documentation/devicetree/bindings/display/msm/sde-dsi.txt @@ -0,0 +1,112 @@ +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, qcom,dsi-ctrl-hw-v2.3 + 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, qcom,dsi-phy-v4.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-ctrl-num: Specifies the DSI controllers to use +- qcom,dsi-phy-num: Specifies the DSI PHYs to use +- qcom,dsi-select-clocks: Specifies the required clocks to use +- qcom,dsi-display-list: Specifies the list of supported displays. +- 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. +- - qcom,null-insertion-enabled: A boolean to enable NULL packet insertion feature for DSI controller. +- ports: This video port is used when external bridge is present. + The connection is modeled using the OF graph bindings + specified in Documentation/devicetree/bindings/graph.txt. + Video port 0 reg 0 is for the bridge output. The remote + endpoint phandle should be mipi_dsi_device device node. 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-wb.txt b/Documentation/devicetree/bindings/display/msm/sde-wb.txt new file mode 100644 index 0000000000000000000000000000000000000000..863b334e438a704f8ff9396e6bf2ac3f02b0224b --- /dev/null +++ b/Documentation/devicetree/bindings/display/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/display/msm/sde.txt b/Documentation/devicetree/bindings/display/msm/sde.txt new file mode 100644 index 0000000000000000000000000000000000000000..923d145ff49c32c73cb0a4fa0a04e47cf6739373 --- /dev/null +++ b/Documentation/devicetree/bindings/display/msm/sde.txt @@ -0,0 +1,835 @@ +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-bw-calc-version: A u32 property to specify version of UBWC bandwidth + calculation algorithm +- qcom,sde-ubwc-swizzle: Property to specify the default UBWC swizzle + configuration value. +- qcom,sde-smart-panel-align-mode: A u32 property to specify the align mode for + split display on smart panel. Possible values: + 0x0 - no alignment + 0xc - align at start of frame + 0xd - align at start of line +- 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-vig-gamut: offset and version of 3D LUT Gamut hardware + -- qcom,sde-vig-igc: offset and version of 1D LUT IGC hardware + -- qcom,sde-vig-inverse-pma: Boolean property to indicate if + inverse PMA feature is available on VIG pipe +- qcom,sde-sspp-dma-blocks: A node that lists the blocks inside the DMA hardware. There + can be more than one instance of this binding, in which case the + entry would be appended with dgm entry index. Each entry will + contain the offset and version (if needed) of each feature block. + The presence of a block entry indicates that the SSPP DMA contains + that feature hardware. + e.g. qcom,sde-sspp-dma-blocks + -- dgm@0 + -- qcom,sde-dma-igc: offset and version of DMA IGC + -- qcom,sde-dma-gc: offset and version of DMA GC + -- qcom,sde-dma-inverse-pma: Boolean property to indicate if + inverse PMA feature is available on DMA pipe + -- qcom,sde-dma-csc-off: offset of CSC 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-rgb-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: Array of 5 cell property, with a format of + , + indicating the danger luts on sspp. +- qcom,sde-safe-lut-linear: Array of 2 cell property, with a format of + in ascending fill level + indicating the safe luts for linear format on sspp. + Zero fill level on the last entry identifies the default lut. +- qcom,sde-safe-lut-macrotile: Array of 2 cell property, with a format of + in ascending fill level + indicating the safe luts for macrotile format on sspp. + Zero fill level on the last entry identifies the default lut. +- qcom,sde-safe-lut-macrotile-qseed: Array of 2 cell property, with a format of + in ascending fill level + indicating the safe luts for macrotile format + with qseed3 on sspp. + Zero fill level on the last entry identifies the default lut. +- qcom,sde-safe-lut-nrt: Array of 2 cell property, with a format of + in ascending fill level + indicating the safe luts for nrt (e.g wfd) on sspp. + Zero fill level on the last entry identifies the default lut. +- qcom,sde-safe-lut-cwb: Array of 2 cell property, with a format of + in ascending fill level + indicating the safe luts for cwb on sspp. + Zero fill level on the last entry identifies the default lut. +- 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-macrotile-qseed: Array of 3 cell property, with a format of + in ascending fill level + indicating the qos luts for macrotile format + with qseed3 enabled 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-qos-cpu-mask: A u32 value indicating desired PM QoS CPU affine mask. +- qcom,sde-qos-cpu-dma-latency: A u32 value indicating desired PM QoS CPU DMA latency in usec. +- 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 +- qcom,sde-secure-sid-mask: Array of secure SID masks used during + secure-camera/secure-display usecases. +- #power-domain-cells: Number of cells in a power-domain specifier and should contain 0. +- qcom,sde-mixer-display-pref: A string array indicating the preferred display type + for the mixer block. Possible values: + "primary" - preferred for primary display + "none" - no preference on display +- qcom,sde-ctl-display-pref: A string array indicating the preferred display type + for the ctl block. Possible values: + "primary" - preferred for primary display + "none" - no preference on display +- qcom,sde-pipe-order-version: A u32 property to indicate version of pipe + ordering block + 0: lower priority pipe has to be on the left for a given pair of pipes. + 1: priority have to be explicitly configured for a given pair of pipes. + +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>; + #power-domain-cells = <0>; + + qcom,sde-off = <0x1000>; + qcom,sde-ctl-off = <0x00002000 0x00002200 0x00002400 + 0x00002600 0x00002800>; + qcom,sde-ctl-display-pref = "primary", "none", "none", + "none", "none"; + qcom,sde-mixer-off = <0x00045000 0x00046000 + 0x00047000 0x0004a000>; + qcom,sde-mixer-display-pref = "primary", "none", + "none", "none"; + 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-ubwc-bw-calc-version = <0x1>; + qcom,sde-smart-panel-align-mode = <0xd>; + qcom,sde-panic-per-pipe; + qcom,sde-has-src-split; + qcom,sde-pipe-order-version = <0x1>; + 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 0x0000ffff>; + qcom,sde-safe-lut-linear = <0 0xfff8>; + qcom,sde-safe-lut-macrotile = <0 0xf000>; + qcom,sde-safe-lut-macrotile-qseed = <0 0xf000>; + qcom,sde-safe-lut-nrt = <0 0xffff>; + qcom,sde-safe-lut-cwb = <0 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-macrotile-qseed = + <0 0x00112233 0x66777777>; + 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-qos-cpu-mask = <0x3>; + qcom,sde-qos-cpu-dma-latency = <300>; + + 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-secure-sid-mask = <0x200801 0x200c01>; + + 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-vig-inverse-pma; + }; + + qcom,sde-sspp-dma-blocks { + dgm@0 { + qcom,sde-dma-igc = <0x400 0x00050000>; + qcom,sde-dma-gc = <0x600 0x00050000>; + qcom,sde-dma-inverse-pma; + qcom,sde-dma-csc-off = <0x200>; + } + dgm@1 { + qcom,sde-dma-igc = <0x1400 0x00050000>; + qcom,sde-dma-gc = <0x600 0x00050000>; + qcom,sde-dma-inverse-pma; + qcom,sde-dma-csc-off = <0x1200>; + } + }; + + 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/media/video/msm-sde-rotator.txt b/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt new file mode 100644 index 0000000000000000000000000000000000000000..5a92bf6e1a4316509a592ae3619418a1260aeed0 --- /dev/null +++ b/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt @@ -0,0 +1,233 @@ +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-qos-cpu-mask: A u32 value indicating desired PM QoS CPU + affine mask. +- qcom,mdss-rot-qos-cpu-dma-latency: A u32 value indicating desired PM QoS CPU DMA + latency in usec. +- 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. +- power-domains: A phandle to respective power domain node. + +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>; + + power-domains = <&mdss_mdp>; + + 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-rot-qos-cpu-mask = <0xf>; + qcom,mdss-rot-qos-cpu-dma-latency = <75>; + + 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/gpu/drm/msm/Kconfig b/drivers/gpu/drm/msm/Kconfig index 843a9d40c05e33f5f13d1cbb7b09c751127940f2..736890fcb73ff27db4affc85b2394e87e3bd2152 100644 --- a/drivers/gpu/drm/msm/Kconfig +++ b/drivers/gpu/drm/msm/Kconfig @@ -20,6 +20,26 @@ config DRM_MSM help DRM/KMS driver for MSM/snapdragon. +config DRM_MSM_SDE + bool "SDE in Display Driver" + default n + help + Enable this option to support display driver + compilation. Choosing this option will compile + SDE folder specifically. Display Port and + DSI-Staging compilation are also dependent on + this option. + +config DRM_MSM_DP + bool "Enable Display Port" + depends on DRM_MSM_SDE + default n + help + This option enables compilation of Display Port + (DP) folders. In order to compile Display Port + files, DRM_MSM_SDE option also needs to be + enabled. + config DRM_MSM_REGISTER_LOGGING bool "MSM DRM register logging" depends on DRM_MSM @@ -45,20 +65,55 @@ config DRM_MSM_GPU_SUDO 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 ina format + that can be parsed by envy tools 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 + depends on COMMON_CLK + 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 && DRM_MSM_SDE + 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 DSI_PARSER + bool "Enable DSI panel configuration parser" + depends on DYNAMIC_DEBUG && DRM_MSM_DSI_STAGING + default y + help + Choose this option if you need text parser for a DSI panel + configurations which can parse a given text file and get the + panel configurations. Also, this module provides a set of APIs + which can be used to get the parsed data. + config DRM_MSM_DSI_PLL bool "Enable DSI PLL driver in MSM DRM" depends on DRM_MSM_DSI && COMMON_CLK @@ -102,3 +157,63 @@ config DRM_MSM_DSI_10NM_PHY default y help Choose this option if DSI PHY on SDM845 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 + Choose 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 && DRM_MSM_SDE + 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_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 && DRM_MSM_SDE + 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 4affc665c0decf2a73f0996bb9bca4b7113a68dd..911a1bf42e340aae41949aa0bec0abc6cd053ad8 100644 --- a/drivers/gpu/drm/msm/Makefile +++ b/drivers/gpu/drm/msm/Makefile @@ -1,19 +1,53 @@ # SPDX-License-Identifier: GPL-2.0 -ccflags-y := -Idrivers/gpu/drm/msm +ccflags-$(CONFIG_DRM_MSM_DP):= -Iinclude/drm -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-$(CONFIG_DRM_MSM_SDE) += -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 \ - adreno/a5xx_preempt.o \ - adreno/a6xx_gpu.o \ - adreno/a6xx_gmu.o \ - adreno/a6xx_hfi.o \ - hdmi/hdmi.o \ +msm_drm-$(CONFIG_DRM_MSM_DP) += dp/dp_usbpd.o \ + dp/dp_parser.o \ + dp/dp_power.o \ + dp/dp_catalog.o \ + dp/dp_catalog_v420.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 \ + dp/dp_mst_drm.o + +msm_drm-$(CONFIG_DRM_MSM_SDE) += 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 \ + sde_hdcp_2x.o + +msm_drm-$(CONFIG_DRM_MSM_HDMI) += hdmi/hdmi.o \ hdmi/hdmi_audio.o \ hdmi/hdmi_bridge.o \ hdmi/hdmi_connector.o \ @@ -22,21 +56,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 \ - disp/mdp_format.o \ + +msm_drm-$(CONFIG_DRM_MSM_MDP5) += disp/mdp_format.o \ disp/mdp_kms.o \ - disp/mdp4/mdp4_crtc.o \ - disp/mdp4/mdp4_dtv_encoder.o \ - disp/mdp4/mdp4_lcdc_encoder.o \ - disp/mdp4/mdp4_lvds_connector.o \ - disp/mdp4/mdp4_irq.o \ - disp/mdp4/mdp4_kms.o \ - disp/mdp4/mdp4_plane.o \ disp/mdp5/mdp5_cfg.o \ disp/mdp5/mdp5_ctl.o \ disp/mdp5/mdp5_crtc.o \ @@ -48,6 +77,118 @@ msm-y := \ disp/mdp5/mdp5_mixer.o \ disp/mdp5/mdp5_plane.o \ disp/mdp5/mdp5_smp.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 \ + adreno/a5xx_preempt.o \ + adreno/a6xx_gpu.o \ + adreno/a6xx_gmu.o \ + adreno/a6xx_hfi.o +endif + +msm_drm-$(CONFIG_DRM_MSM_MDP4) += disp/mdp4/mdp4_crtc.o \ + disp/mdp4/mdp4_dtv_encoder.o \ + disp/mdp4/mdp4_lcdc_encoder.o \ + disp/mdp4/mdp4_lvds_connector.o \ + disp/mdp4/mdp4_irq.o \ + disp/mdp4/mdp4_kms.o \ + disp/mdp4/mdp4_plane.o + +msm_drm-$(CONFIG_DRM_FBDEV_EMULATION) += msm_fbdev.o +ifeq ($(CONFIG_DRM_MSM_SDE),y) +msm_drm-$(CONFIG_SYNC_FILE) += sde/sde_fence.o +endif +msm_drm-$(CONFIG_COMMON_CLK) += disp/mdp4/mdp4_lvds_pll.o +msm_drm-$(CONFIG_COMMON_CLK) += hdmi/hdmi_pll_8960.o +msm_drm-$(CONFIG_COMMON_CLK) += hdmi/hdmi_phy_8996.o + +msm_drm-$(CONFIG_DRM_MSM_HDMI_HDCP) += hdmi/hdmi_hdcp.o + +msm_drm-$(CONFIG_DRM_MSM_DSI) += dsi/dsi.o \ + disp/mdp4/mdp4_dsi_encoder.o \ + dsi/dsi_cfg.o \ + dsi/dsi_host.o \ + dsi/dsi_manager.o \ + dsi/phy/dsi_phy.o \ + disp/mdp5/mdp5_cmd_encoder.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 +msm_drm-$(CONFIG_DRM_MSM_DSI_10NM_PHY) += dsi/phy/dsi_phy_10nm.o + +ifeq ($(CONFIG_DRM_MSM_DSI_PLL),y) +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 +msm_drm-$(CONFIG_DRM_MSM_DSI_10NM_PHY) += dsi/pll/dsi_pll_10nm.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_hw_v4_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_phy_timing_v4_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_DSI_PARSER) += dsi-staging/dsi_parser.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/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_debugfs.o \ msm_drv.o \ @@ -65,35 +206,7 @@ msm-y := \ msm_ringbuffer.o \ msm_submitqueue.o -msm-$(CONFIG_DEBUG_FS) += adreno/a5xx_debugfs.o - -msm-$(CONFIG_DRM_FBDEV_EMULATION) += msm_fbdev.o -msm-$(CONFIG_COMMON_CLK) += disp/mdp4/mdp4_lvds_pll.o -msm-$(CONFIG_COMMON_CLK) += hdmi/hdmi_pll_8960.o -msm-$(CONFIG_COMMON_CLK) += hdmi/hdmi_phy_8996.o - -msm-$(CONFIG_DRM_MSM_HDMI_HDCP) += hdmi/hdmi_hdcp.o - -msm-$(CONFIG_DRM_MSM_DSI) += dsi/dsi.o \ - disp/mdp4/mdp4_dsi_encoder.o \ - dsi/dsi_cfg.o \ - dsi/dsi_host.o \ - dsi/dsi_manager.o \ - dsi/phy/dsi_phy.o \ - disp/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-$(CONFIG_DRM_MSM_DSI_10NM_PHY) += dsi/phy/dsi_phy_10nm.o +msm_drm-$(CONFIG_DEBUG_FS) += adreno/a5xx_debugfs.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-$(CONFIG_DRM_MSM_DSI_10NM_PHY) += dsi/pll/dsi_pll_10nm.o -endif -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..3754692c5fbc3e9e3b4a10bd94933e1efefb8fe7 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_audio.c @@ -0,0 +1,880 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include + +#include + +#include "dp_catalog.h" +#include "dp_audio.h" +#include "dp_panel.h" + +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 workqueue_struct *notify_workqueue; + struct delayed_work notify_delayed_work; + struct mutex ops_lock; + + struct dp_audio dp_audio; + + atomic_t acked; +}; + +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); + value &= 0x0000ffff; + + new_value = 0x02; + parity_byte = dp_header_get_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); + value &= 0xffff0000; + new_value = 0x0; + parity_byte = dp_header_get_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); + value &= 0x0000ffff; + + new_value = audio->channels - 1; + parity_byte = dp_header_get_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); + value &= 0x0000ffff; + + new_value = 0x1; + parity_byte = dp_header_get_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); + value &= 0xffff0000; + + new_value = 0x17; + parity_byte = dp_header_get_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); + value &= 0x0000ffff; + + new_value = (0x0 | (0x11 << 2)); + parity_byte = dp_header_get_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); + value &= 0x0000ffff; + + new_value = 0x84; + parity_byte = dp_header_get_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); + value &= 0xffff0000; + + new_value = 0x1b; + parity_byte = dp_header_get_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); + value &= 0x0000ffff; + + new_value = (0x0 | (0x11 << 2)); + parity_byte = dp_header_get_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); + value &= 0x0000ffff; + + new_value = 0x05; + parity_byte = dp_header_get_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); + value &= 0xffff0000; + + new_value = 0x0F; + parity_byte = dp_header_get_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); + value &= 0x0000ffff; + + new_value = 0x0; + parity_byte = dp_header_get_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); + value &= 0x0000ffff; + + new_value = 0x06; + parity_byte = dp_header_get_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); + value &= 0xffff0000; + + new_value = 0x0F; + parity_byte = dp_header_get_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) +{ + /* always program stream 0 first before actual stream cfg */ + audio->catalog->stream_id = DP_STREAM_0; + audio->catalog->config_sdp(audio->catalog); + + if (audio->panel->stream_id == DP_STREAM_1) { + audio->catalog->stream_id = DP_STREAM_1; + 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 *dp_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 = dp_audio_get_data(pdev); + if (IS_ERR(audio)) { + rc = PTR_ERR(audio); + return rc; + } + + mutex_lock(&audio->ops_lock); + + audio->channels = params->num_of_channels; + + if (audio->panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream id: %d\n", audio->panel->stream_id); + rc = -EINVAL; + mutex_unlock(&audio->ops_lock); + return rc; + } + + dp_audio_setup_sdp(audio); + dp_audio_setup_acr(audio); + dp_audio_safe_to_exit_level(audio); + dp_audio_enable(audio, true); + + mutex_unlock(&audio->ops_lock); + 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 = dp_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 = dp_audio_get_data(pdev); + if (IS_ERR(audio)) { + rc = PTR_ERR(audio); + 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 = dp_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 = dp_audio_get_data(pdev); + if (IS_ERR(audio)) + return; + + mutex_lock(&audio->ops_lock); + dp_audio_enable(audio, false); + mutex_unlock(&audio->ops_lock); + + atomic_set(&audio->acked, 1); + 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 = dp_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) { + atomic_set(&audio->acked, 1); + complete_all(&audio->hpd_comp); + } +end: + return rc; +} + +static int dp_audio_codec_ready(struct platform_device *pdev) +{ + int rc = 0; + struct dp_audio_private *audio; + + audio = dp_audio_get_data(pdev); + if (IS_ERR(audio)) { + pr_err("invalid input\n"); + rc = PTR_ERR(audio); + goto end; + } + + queue_delayed_work(audio->notify_workqueue, + &audio->notify_delayed_work, HZ/4); +end: + return rc; +} + +static int dp_audio_register_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->codec.type = EXT_DISPLAY_TYPE_DP; + ext->codec.ctrl_id = 0; + ext->codec.stream_id = audio->panel->stream_id; + 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; + ops->ready = dp_audio_codec_ready; + + 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_deregister_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; + + ext = &audio->ext_audio_data; + + 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_deregister_intf(audio->ext_pdev, ext); + if (rc) + pr_err("failed to deregister disp\n"); + +end: + return rc; +} + +static int dp_audio_notify(struct dp_audio_private *audio, u32 state) +{ + int rc = 0; + struct msm_ext_disp_init_data *ext = &audio->ext_audio_data; + + atomic_set(&audio->acked, 0); + + if (!ext->intf_ops.audio_notify) { + pr_err("audio notify not defined\n"); + goto end; + } + + reinit_completion(&audio->hpd_comp); + rc = ext->intf_ops.audio_notify(audio->ext_pdev, + &ext->codec, state); + if (rc) { + pr_err("failed to notify audio. state=%d err=%d\n", state, rc); + goto end; + } + + if (atomic_read(&audio->acked)) + goto end; + + rc = wait_for_completion_timeout(&audio->hpd_comp, HZ * 4); + if (!rc) { + pr_err("timeout. state=%d err=%d\n", state, rc); + rc = -ETIMEDOUT; + goto end; + } + + pr_debug("success\n"); +end: + return rc; +} + +static int dp_audio_config(struct dp_audio_private *audio, u32 state) +{ + int rc = 0; + struct msm_ext_disp_init_data *ext = &audio->ext_audio_data; + + if (!ext || !ext->intf_ops.audio_config) { + pr_err("audio_config not defined\n"); + goto end; + } + + /* + * DP Audio sets default STREAM_0 only, other streams are + * set by audio driver based on the hardware/software support. + */ + if (audio->panel->stream_id == DP_STREAM_0) { + rc = ext->intf_ops.audio_config(audio->ext_pdev, + &ext->codec, state); + if (rc) + pr_err("failed to config audio, err=%d\n", rc); + } +end: + 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"); + return -EINVAL; + } + + audio = container_of(dp_audio, struct dp_audio_private, dp_audio); + if (IS_ERR(audio)) { + pr_err("invalid input\n"); + return -EINVAL; + } + + ext = &audio->ext_audio_data; + + audio->session_on = true; + + rc = dp_audio_config(audio, EXT_DISPLAY_CABLE_CONNECT); + if (rc) + goto end; + + rc = dp_audio_notify(audio, EXT_DISPLAY_CABLE_CONNECT); + if (rc) + 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; + bool work_pending = false; + + 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; + + work_pending = cancel_delayed_work_sync(&audio->notify_delayed_work); + if (work_pending) + pr_debug("pending notification work completed\n"); + + rc = dp_audio_notify(audio, EXT_DISPLAY_CABLE_DISCONNECT); + if (rc) + goto end; + + pr_debug("success\n"); +end: + dp_audio_config(audio, EXT_DISPLAY_CABLE_DISCONNECT); + + audio->session_on = false; + audio->engine_on = false; + + return rc; +} + +static void dp_audio_notify_work_fn(struct work_struct *work) +{ + struct dp_audio_private *audio; + struct delayed_work *dw = to_delayed_work(work); + + audio = container_of(dw, struct dp_audio_private, notify_delayed_work); + + dp_audio_notify(audio, EXT_DISPLAY_CABLE_CONNECT); +} + +static int dp_audio_create_notify_workqueue(struct dp_audio_private *audio) +{ + audio->notify_workqueue = create_workqueue("sdm_dp_audio_notify"); + if (IS_ERR_OR_NULL(audio->notify_workqueue)) { + pr_err("Error creating notify_workqueue\n"); + return -EPERM; + } + + INIT_DELAYED_WORK(&audio->notify_delayed_work, dp_audio_notify_work_fn); + + return 0; +} + +static void dp_audio_destroy_notify_workqueue(struct dp_audio_private *audio) +{ + if (audio->notify_workqueue) + destroy_workqueue(audio->notify_workqueue); +} + +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; + } + + rc = dp_audio_create_notify_workqueue(audio); + if (rc) + goto error_notify_workqueue; + + init_completion(&audio->hpd_comp); + + audio->pdev = pdev; + audio->panel = panel; + audio->catalog = catalog; + + atomic_set(&audio->acked, 0); + + dp_audio = &audio->dp_audio; + + mutex_init(&audio->ops_lock); + + dp_audio->on = dp_audio_on; + dp_audio->off = dp_audio_off; + + catalog->init(catalog); + + dp_audio_register_ext_disp(audio); + + return dp_audio; + +error_notify_workqueue: + devm_kfree(&pdev->dev, 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); + + dp_audio_deregister_ext_disp(audio); + + mutex_destroy(&audio->ops_lock); + + dp_audio_destroy_notify_workqueue(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..14404136d3ab760792b2d99412239ff448f15ff7 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_audio.h @@ -0,0 +1,70 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#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..96f97d6a1fa2cf788b74a08fb3cd8b272064c609 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_aux.c @@ -0,0 +1,837 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include +#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 device_node *aux_switch_node; + struct mutex mutex; + struct completion comp; + struct drm_dp_aux drm_aux; + + bool cmd_busy; + bool native; + bool read; + bool no_send_addr; + bool no_send_stop; + bool enabled; + + u32 offset; + u32 segment; + u32 aux_error_num; + u32 retry_cnt; + + atomic_t aborted; + + u8 *dpcd; + u8 *edid; +}; + +#ifdef CONFIG_DYNAMIC_DEBUG +static void dp_aux_hex_dump(struct drm_dp_aux *drm_aux, + struct drm_dp_aux_msg *msg) +{ + DEFINE_DYNAMIC_DEBUG_METADATA(ddm, "dp aux tracker"); + + if (unlikely(ddm.flags & _DPRINTK_FLAGS_PRINT)) { + u8 buf[SZ_64]; + struct dp_aux_private *aux = container_of(drm_aux, + struct dp_aux_private, drm_aux); + + snprintf(buf, SZ_64, "[drm-dp] %5s %5s %5xh(%2zu): ", + aux->native ? "NATIVE" : "I2C", + aux->read ? "READ" : "WRITE", + msg->address, msg->size); + + print_hex_dump(KERN_DEBUG, buf, DUMP_PREFIX_NONE, + 8, 1, msg->buffer, msg->size, false); + } +} +#else +static void dp_aux_hex_dump(struct drm_dp_aux *drm_aux, + struct drm_dp_aux_msg *msg) +{ +} +#endif + +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); + aux->catalog->clear_hw_interrupts(aux->catalog); + + 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; + if (isr & DP_INTR_AUX_ERROR) { + aux->aux_error_num = DP_AUX_ERR_PHY; + aux->catalog->clear_hw_interrupts(aux->catalog); + } + + 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; + if (isr & DP_INTR_AUX_ERROR) { + aux->aux_error_num = DP_AUX_ERR_PHY; + aux->catalog->clear_hw_interrupts(aux->catalog); + } + } + + 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_abort_transaction(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); + + atomic_set(&aux->aborted, 1); +} + +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 + * @send_seg: send the seg to sink + * + * 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, bool send_seg) +{ + struct drm_dp_aux_msg helper_msg; + u32 const message_size = 0x10; + u32 const segment_address = 0x30; + u32 const edid_block_length = 0x80; + 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; + + /* + * Sending the segment value and EDID offset will be performed + * from the DRM upstream EDID driver for each block. Avoid + * duplicate AUX transactions related to this while reading the + * first 16 bytes of each block. + */ + if (!(aux->offset % edid_block_length) || !send_seg) + goto end; + + aux->read = false; + aux->cmd_busy = true; + aux->no_send_addr = true; + aux->no_send_stop = true; + + /* + * Send the segment address for i2c reads for segment > 0 and for which + * the middle-of-transaction 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. + */ + if (aux->segment) { + 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); +end: + aux->offset += message_size; + if (aux->offset == 0x80 || aux->offset == 0x100) + aux->segment = 0x0; /* reset segment at end of block */ +} + +static int dp_aux_transfer_ready(struct dp_aux_private *aux, + struct drm_dp_aux_msg *msg, bool send_seg) +{ + int ret = 0; + int const aux_cmd_native_max = 16; + int const aux_cmd_i2c_max = 128; + + if (atomic_read(&aux->aborted)) { + ret = -ETIMEDOUT; + goto error; + } + + 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; + goto error; + } + + /* 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 error; + } + + dp_aux_update_offset_and_segment(aux, msg); + + dp_aux_transfer_helper(aux, msg, send_seg); + + aux->read = msg->request & (DP_AUX_I2C_READ & DP_AUX_NATIVE_READ); + + if (aux->read) { + aux->no_send_addr = true; + aux->no_send_stop = false; + } else { + aux->no_send_addr = true; + aux->no_send_stop = true; + } + + aux->cmd_busy = true; +error: + return ret; +} + +static ssize_t dp_aux_transfer_debug(struct drm_dp_aux *drm_aux, + struct drm_dp_aux_msg *msg) +{ + u32 timeout; + ssize_t ret; + struct dp_aux_private *aux = container_of(drm_aux, + struct dp_aux_private, drm_aux); + + mutex_lock(&aux->mutex); + + ret = dp_aux_transfer_ready(aux, msg, false); + if (ret) + goto end; + + aux->aux_error_num = DP_AUX_ERR_NONE; + + if (!aux->dpcd || !aux->edid) { + pr_err("invalid aux/dpcd structure\n"); + goto end; + } + + if ((msg->address + msg->size) > SZ_16K) { + pr_err("invalid dpcd access: addr=0x%x, size=0x%x\n", + msg->address + msg->size); + goto address_error; + } + + if (aux->native) { + if (aux->read) { + aux->dp_aux.reg = msg->address; + + reinit_completion(&aux->comp); + timeout = wait_for_completion_timeout(&aux->comp, HZ); + if (!timeout) + pr_err("aux timeout for 0x%x\n", msg->address); + + aux->dp_aux.reg = 0xFFFF; + + memcpy(msg->buffer, aux->dpcd + msg->address, + msg->size); + aux->aux_error_num = DP_AUX_ERR_NONE; + } else { + memcpy(aux->dpcd + msg->address, msg->buffer, + msg->size); + } + } else { + if (aux->read && msg->address == 0x50) { + memcpy(msg->buffer, + aux->edid + aux->offset - 16, + msg->size); + } + } + + if (aux->aux_error_num == DP_AUX_ERR_NONE) { + dp_aux_hex_dump(drm_aux, msg); + + if (!aux->read) + memset(msg->buffer, 0, msg->size); + + 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; + } + + ret = msg->size; + goto end; + +address_error: + memset(msg->buffer, 0, msg->size); + ret = msg->size; +end: + mutex_unlock(&aux->mutex); + return ret; +} + +/* + * 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 retry_count = 5; + struct dp_aux_private *aux = container_of(drm_aux, + struct dp_aux_private, drm_aux); + + mutex_lock(&aux->mutex); + + ret = dp_aux_transfer_ready(aux, msg, true); + if (ret) + goto unlock_exit; + + if (!aux->cmd_busy) { + ret = msg->size; + goto unlock_exit; + } + + ret = dp_aux_cmd_fifo_tx(aux, msg); + if ((ret < 0) && !atomic_read(&aux->aborted)) { + 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); + + dp_aux_hex_dump(drm_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); + + if (aux->enabled) + return; + + dp_aux_reset_phy_config_indices(aux_cfg); + aux->catalog->setup(aux->catalog, aux_cfg); + aux->catalog->reset(aux->catalog); + aux->catalog->enable(aux->catalog, true); + atomic_set(&aux->aborted, 0); + aux->retry_cnt = 0; + aux->enabled = true; +} + +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); + + if (!aux->enabled) + return; + + atomic_set(&aux->aborted, 1); + aux->catalog->enable(aux->catalog, false); + aux->enabled = 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); +} + +static void dp_aux_dpcd_updated(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); + + complete(&aux->comp); +} + +static void dp_aux_set_sim_mode(struct dp_aux *dp_aux, bool en, + u8 *edid, u8 *dpcd) +{ + 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); + + mutex_lock(&aux->mutex); + + aux->edid = edid; + aux->dpcd = dpcd; + + if (en) + aux->drm_aux.transfer = dp_aux_transfer_debug; + else + aux->drm_aux.transfer = dp_aux_transfer; + + mutex_unlock(&aux->mutex); +} + +static int dp_aux_configure_aux_switch(struct dp_aux *dp_aux, + bool enable, int orientation) +{ + struct dp_aux_private *aux; + int rc = 0; + enum fsa_function event = FSA_USBC_DISPLAYPORT_DISCONNECTED; + + if (!dp_aux) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + aux = container_of(dp_aux, struct dp_aux_private, dp_aux); + + if (!aux->aux_switch_node) { + pr_debug("undefined fsa4480 handle\n"); + rc = -EINVAL; + goto end; + } + + if (enable) { + switch (orientation) { + case ORIENTATION_CC1: + event = FSA_USBC_ORIENTATION_CC1; + break; + case ORIENTATION_CC2: + event = FSA_USBC_ORIENTATION_CC2; + break; + default: + pr_err("invalid orientation\n"); + rc = -EINVAL; + goto end; + } + } + + pr_debug("enable=%d, orientation=%d, event=%d\n", + enable, orientation, event); + + rc = fsa4480_switch_event(aux->aux_switch_node, event); + if (rc) + pr_err("failed to configure fsa4480 i2c device (%d)\n", rc); +end: + return rc; +} + +struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog, + struct dp_parser *parser, struct device_node *aux_switch) +{ + int rc = 0; + struct dp_aux_private *aux; + struct dp_aux *dp_aux; + + if (!catalog || !parser || + (!parser->no_aux_switch && !aux_switch)) { + 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 = parser->aux_cfg; + aux->aux_switch_node = aux_switch; + dp_aux = &aux->dp_aux; + aux->retry_cnt = 0; + aux->dp_aux.reg = 0xFFFF; + + 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; + dp_aux->abort = dp_aux_abort_transaction; + dp_aux->dpcd_updated = dp_aux_dpcd_updated; + dp_aux->set_sim_mode = dp_aux_set_sim_mode; + dp_aux->aux_switch = dp_aux_configure_aux_switch; + + 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..d77212bcf19808d4c0a963a9a11dbc217cf47319 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_aux.h @@ -0,0 +1,56 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DP_AUX_H_ +#define _DP_AUX_H_ + +#include "dp_catalog.h" +#include "drm_dp_helper.h" + +#define DP_STATE_NOTIFICATION_SENT BIT(0) +#define DP_STATE_TRAIN_1_STARTED BIT(1) +#define DP_STATE_TRAIN_1_SUCCEEDED BIT(2) +#define DP_STATE_TRAIN_1_FAILED BIT(3) +#define DP_STATE_TRAIN_2_STARTED BIT(4) +#define DP_STATE_TRAIN_2_SUCCEEDED BIT(5) +#define DP_STATE_TRAIN_2_FAILED BIT(6) +#define DP_STATE_CTRL_POWERED_ON BIT(7) +#define DP_STATE_CTRL_POWERED_OFF BIT(8) +#define DP_STATE_LINK_MAINTENANCE_STARTED BIT(9) +#define DP_STATE_LINK_MAINTENANCE_COMPLETED BIT(10) +#define DP_STATE_LINK_MAINTENANCE_FAILED BIT(11) + +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, + DP_AUX_ERR_PHY = -6, +}; + +struct dp_aux { + u32 reg; + u32 state; + + 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); + void (*abort)(struct dp_aux *aux); + void (*dpcd_updated)(struct dp_aux *aux); + void (*set_sim_mode)(struct dp_aux *aux, bool en, u8 *edid, u8 *dpcd); + int (*aux_switch)(struct dp_aux *aux, bool enable, int orientation); +}; + +struct dp_aux *dp_aux_get(struct device *dev, struct dp_catalog_aux *catalog, + struct dp_parser *parser, struct device_node *aux_switch); +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..5d8ae8a3a81150353e18a42e37905845939982e8 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_catalog.c @@ -0,0 +1,2304 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#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_catalog_get_priv(x) ({ \ + struct dp_catalog *dp_catalog; \ + dp_catalog = container_of(x, struct dp_catalog, x); \ + 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) + +#define DP_INTERRUPT_STATUS5 \ + (DP_INTR_MST_DP0_VCPF_SENT | DP_INTR_MST_DP1_VCPF_SENT) + +#define DP_INTR_MASK5 (DP_INTERRUPT_STATUS5 << 2) + +#define dp_catalog_fill_io(x) { \ + catalog->io.x = parser->get_io(parser, #x); \ +} + +#define dp_catalog_fill_io_buf(x) { \ + parser->get_io_buf(parser, #x); \ +} + +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 */ +}; + +struct dp_catalog_io { + struct dp_io_data *dp_ahb; + struct dp_io_data *dp_aux; + struct dp_io_data *dp_link; + struct dp_io_data *dp_p0; + struct dp_io_data *dp_phy; + struct dp_io_data *dp_ln_tx0; + struct dp_io_data *dp_ln_tx1; + struct dp_io_data *dp_mmss_cc; + struct dp_io_data *dp_pll; + struct dp_io_data *usb3_dp_com; + struct dp_io_data *hdcp_physical; + struct dp_io_data *dp_p1; + struct dp_io_data *dp_tcsr; +}; + +/* audio related catalog functions */ +struct dp_catalog_private { + struct device *dev; + struct dp_catalog_io io; + struct dp_parser *parser; + + u32 (*audio_map)[DP_AUDIO_SDP_HEADER_MAX]; + struct dp_catalog dp_catalog; + + char exe_mode[SZ_4]; +}; + +/* aux related catalog functions */ +static u32 dp_catalog_aux_read_data(struct dp_catalog_aux *aux) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!aux) { + pr_err("invalid input\n"); + goto end; + } + + catalog = dp_catalog_get_priv(aux); + io_data = catalog->io.dp_aux; + + return dp_read(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data; + + if (!aux) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + catalog = dp_catalog_get_priv(aux); + io_data = catalog->io.dp_aux; + + dp_write(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data; + + if (!aux) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + catalog = dp_catalog_get_priv(aux); + io_data = catalog->io.dp_aux; + + dp_write(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data; + + if (!aux) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + catalog = dp_catalog_get_priv(aux); + io_data = catalog->io.dp_aux; + + if (read) { + data = dp_read(catalog->exe_mode, io_data, DP_AUX_TRANS_CTRL); + data &= ~BIT(9); + dp_write(catalog->exe_mode, io_data, DP_AUX_TRANS_CTRL, data); + } else { + dp_write(catalog->exe_mode, io_data, DP_AUX_TRANS_CTRL, 0); + } +end: + return rc; +} + +static void dp_catalog_aux_clear_hw_interrupts(struct dp_catalog_aux *aux) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + u32 data = 0; + + if (!aux) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(aux); + io_data = catalog->io.dp_phy; + + data = dp_read(catalog->exe_mode, io_data, DP_PHY_AUX_INTERRUPT_STATUS); + + dp_write(catalog->exe_mode, io_data, DP_PHY_AUX_INTERRUPT_CLEAR, 0x1f); + wmb(); /* make sure 0x1f is written before next write */ + dp_write(catalog->exe_mode, io_data, DP_PHY_AUX_INTERRUPT_CLEAR, 0x9f); + wmb(); /* make sure 0x9f is written before next write */ + dp_write(catalog->exe_mode, io_data, DP_PHY_AUX_INTERRUPT_CLEAR, 0); + wmb(); /* make sure register is cleared */ +} + +static void dp_catalog_aux_reset(struct dp_catalog_aux *aux) +{ + u32 aux_ctrl; + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!aux) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(aux); + io_data = catalog->io.dp_aux; + + aux_ctrl = dp_read(catalog->exe_mode, io_data, DP_AUX_CTRL); + + aux_ctrl |= BIT(1); + dp_write(catalog->exe_mode, io_data, DP_AUX_CTRL, aux_ctrl); + usleep_range(1000, 1010); /* h/w recommended delay */ + + aux_ctrl &= ~BIT(1); + + dp_write(catalog->exe_mode, io_data, DP_AUX_CTRL, aux_ctrl); + wmb(); /* make sure AUX reset is done here */ +} + +static void dp_catalog_aux_enable(struct dp_catalog_aux *aux, bool enable) +{ + u32 aux_ctrl; + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!aux) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(aux); + io_data = catalog->io.dp_aux; + + aux_ctrl = dp_read(catalog->exe_mode, io_data, DP_AUX_CTRL); + + if (enable) { + aux_ctrl |= BIT(0); + dp_write(catalog->exe_mode, io_data, DP_AUX_CTRL, aux_ctrl); + wmb(); /* make sure AUX module is enabled */ + + dp_write(catalog->exe_mode, io_data, DP_TIMEOUT_COUNT, 0xffff); + dp_write(catalog->exe_mode, io_data, DP_AUX_LIMITS, 0xffff); + } else { + aux_ctrl &= ~BIT(0); + dp_write(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data; + + if (!aux || !cfg || (type >= PHY_AUX_CFG_MAX)) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(aux); + + io_data = catalog->io.dp_phy; + + 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->exe_mode, io_data, 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; + struct dp_io_data *io_data; + int i = 0; + + if (!aux || !cfg) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(aux); + + io_data = catalog->io.dp_phy; + dp_write(catalog->exe_mode, io_data, DP_PHY_PD_CTL, 0x65); + wmb(); /* make sure PD programming happened */ + + /* Turn on BIAS current for PHY/PLL */ + io_data = catalog->io.dp_pll; + dp_write(catalog->exe_mode, io_data, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, + 0x1b); + + io_data = catalog->io.dp_phy; + dp_write(catalog->exe_mode, io_data, DP_PHY_PD_CTL, 0x02); + wmb(); /* make sure PD programming happened */ + dp_write(catalog->exe_mode, io_data, DP_PHY_PD_CTL, 0x7d); + + /* Turn on BIAS current for PHY/PLL */ + io_data = catalog->io.dp_pll; + dp_write(catalog->exe_mode, io_data, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, + 0x3f); + + /* DP AUX CFG register programming */ + io_data = catalog->io.dp_phy; + for (i = 0; i < PHY_AUX_CFG_MAX; i++) + dp_write(catalog->exe_mode, io_data, cfg[i].offset, + cfg[i].lut[cfg[i].current_index]); + + dp_write(catalog->exe_mode, io_data, DP_PHY_AUX_INTERRUPT_MASK, 0x1F); + wmb(); /* make sure AUX configuration is done before enabling it */ +} + +static void dp_catalog_aux_get_irq(struct dp_catalog_aux *aux, bool cmd_busy) +{ + u32 ack; + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!aux) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(aux); + io_data = catalog->io.dp_ahb; + + aux->isr = dp_read(catalog->exe_mode, io_data, DP_INTR_STATUS); + aux->isr &= ~DP_INTR_MASK1; + ack = aux->isr & DP_INTERRUPT_STATUS1; + ack <<= 1; + ack |= DP_INTR_MASK1; + dp_write(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + return -EINVAL; + } + + catalog = dp_catalog_get_priv(ctrl); + io_data = catalog->io.dp_ahb; + + return dp_read(catalog->exe_mode, io_data, DP_HDCP_STATUS); +} + +static void dp_catalog_panel_setup_infoframe_sdp(struct dp_catalog_panel *panel) +{ + struct dp_catalog_private *catalog; + struct drm_msm_ext_hdr_metadata *hdr; + struct dp_io_data *io_data; + u32 header, parity, data; + u8 buf[SZ_128], off = 0; + + if (!panel) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(panel); + hdr = &panel->hdr_data.hdr_meta; + io_data = catalog->io.dp_link; + + /* HEADER BYTE 1 */ + header = panel->hdr_data.vscext_header_byte1; + parity = dp_header_get_parity(header); + data = ((header << HEADER_BYTE_1_BIT) + | (parity << PARITY_BYTE_1_BIT)); + dp_write(catalog->exe_mode, io_data, MMSS_DP_VSCEXT_0, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + /* HEADER BYTE 2 */ + header = panel->hdr_data.vscext_header_byte2; + parity = dp_header_get_parity(header); + data = ((header << HEADER_BYTE_2_BIT) + | (parity << PARITY_BYTE_2_BIT)); + dp_write(catalog->exe_mode, io_data, MMSS_DP_VSCEXT_1, data); + + /* HEADER BYTE 3 */ + header = panel->hdr_data.vscext_header_byte3; + parity = dp_header_get_parity(header); + data = ((header << HEADER_BYTE_3_BIT) + | (parity << PARITY_BYTE_3_BIT)); + data |= dp_read(catalog->exe_mode, io_data, MMSS_DP_VSCEXT_1); + dp_write(catalog->exe_mode, io_data, MMSS_DP_VSCEXT_1, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + data = panel->hdr_data.version; + data |= panel->hdr_data.length << 8; + data |= hdr->eotf << 16; + dp_write(catalog->exe_mode, io_data, MMSS_DP_VSCEXT_2, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + data = (DP_GET_LSB(hdr->display_primaries_x[0]) | + (DP_GET_MSB(hdr->display_primaries_x[0]) << 8) | + (DP_GET_LSB(hdr->display_primaries_y[0]) << 16) | + (DP_GET_MSB(hdr->display_primaries_y[0]) << 24)); + dp_write(catalog->exe_mode, io_data, MMSS_DP_VSCEXT_3, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + data = (DP_GET_LSB(hdr->display_primaries_x[1]) | + (DP_GET_MSB(hdr->display_primaries_x[1]) << 8) | + (DP_GET_LSB(hdr->display_primaries_y[1]) << 16) | + (DP_GET_MSB(hdr->display_primaries_y[1]) << 24)); + dp_write(catalog->exe_mode, io_data, MMSS_DP_VSCEXT_4, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + data = (DP_GET_LSB(hdr->display_primaries_x[2]) | + (DP_GET_MSB(hdr->display_primaries_x[2]) << 8) | + (DP_GET_LSB(hdr->display_primaries_y[2]) << 16) | + (DP_GET_MSB(hdr->display_primaries_y[2]) << 24)); + dp_write(catalog->exe_mode, io_data, MMSS_DP_VSCEXT_5, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + data = (DP_GET_LSB(hdr->white_point_x) | + (DP_GET_MSB(hdr->white_point_x) << 8) | + (DP_GET_LSB(hdr->white_point_y) << 16) | + (DP_GET_MSB(hdr->white_point_y) << 24)); + dp_write(catalog->exe_mode, io_data, MMSS_DP_VSCEXT_6, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + data = (DP_GET_LSB(hdr->max_luminance) | + (DP_GET_MSB(hdr->max_luminance) << 8) | + (DP_GET_LSB(hdr->min_luminance) << 16) | + (DP_GET_MSB(hdr->min_luminance) << 24)); + dp_write(catalog->exe_mode, io_data, MMSS_DP_VSCEXT_7, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + data = (DP_GET_LSB(hdr->max_content_light_level) | + (DP_GET_MSB(hdr->max_content_light_level) << 8) | + (DP_GET_LSB(hdr->max_average_light_level) << 16) | + (DP_GET_MSB(hdr->max_average_light_level) << 24)); + dp_write(catalog->exe_mode, io_data, MMSS_DP_VSCEXT_8, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + data = 0; + dp_write(catalog->exe_mode, io_data, MMSS_DP_VSCEXT_9, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + print_hex_dump(KERN_DEBUG, "[drm-dp] VSCEXT: ", + DUMP_PREFIX_NONE, 16, 4, buf, off, false); +} + +static void dp_catalog_panel_setup_vsc_sdp(struct dp_catalog_panel *panel) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + u32 header, parity, data; + u8 bpc, off = 0; + u8 buf[SZ_128]; + + if (!panel) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(panel); + io_data = catalog->io.dp_link; + + /* HEADER BYTE 1 */ + header = panel->hdr_data.vsc_header_byte1; + parity = dp_header_get_parity(header); + data = ((header << HEADER_BYTE_1_BIT) + | (parity << PARITY_BYTE_1_BIT)); + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC0_0, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + /* HEADER BYTE 2 */ + header = panel->hdr_data.vsc_header_byte2; + parity = dp_header_get_parity(header); + data = ((header << HEADER_BYTE_2_BIT) + | (parity << PARITY_BYTE_2_BIT)); + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC0_1, data); + + /* HEADER BYTE 3 */ + header = panel->hdr_data.vsc_header_byte3; + parity = dp_header_get_parity(header); + data = ((header << HEADER_BYTE_3_BIT) + | (parity << PARITY_BYTE_3_BIT)); + data |= dp_read(catalog->exe_mode, io_data, MMSS_DP_GENERIC0_1); + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC0_1, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + data = 0; + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC0_2, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC0_3, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC0_4, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC0_5, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + switch (panel->hdr_data.bpc) { + default: + case 10: + bpc = BIT(1); + break; + case 8: + bpc = BIT(0); + break; + case 6: + bpc = 0; + break; + } + + data = (panel->hdr_data.colorimetry & 0xF) | + ((panel->hdr_data.pixel_encoding & 0xF) << 4) | + (bpc << 8) | + ((panel->hdr_data.dynamic_range & 0x1) << 15) | + ((panel->hdr_data.content_type & 0x7) << 16); + + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC0_6, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + data = 0; + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC0_7, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC0_8, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC0_9, data); + memcpy(buf + off, &data, sizeof(data)); + off += sizeof(data); + + print_hex_dump(KERN_DEBUG, "[drm-dp] VSC: ", + DUMP_PREFIX_NONE, 16, 4, buf, off, false); +} + +static void dp_catalog_panel_config_hdr(struct dp_catalog_panel *panel, bool en) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + u32 cfg, cfg2, misc; + u32 sdp_cfg_off = 0; + u32 sdp_cfg2_off = 0; + u32 sdp_cfg3_off = 0; + u32 misc1_misc0_off = 0; + + if (!panel) { + pr_err("invalid input\n"); + return; + } + + if (panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream_id:%d\n", panel->stream_id); + return; + } + + catalog = dp_catalog_get_priv(panel); + io_data = catalog->io.dp_link; + + if (panel->stream_id == DP_STREAM_1) { + sdp_cfg_off = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG; + sdp_cfg2_off = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG; + sdp_cfg3_off = MMSS_DP1_SDP_CFG3 - MMSS_DP_SDP_CFG; + misc1_misc0_off = DP1_MISC1_MISC0 - DP_MISC1_MISC0; + } + + cfg = dp_read(catalog->exe_mode, io_data, + MMSS_DP_SDP_CFG + sdp_cfg_off); + cfg2 = dp_read(catalog->exe_mode, io_data, + MMSS_DP_SDP_CFG2 + sdp_cfg2_off); + misc = dp_read(catalog->exe_mode, io_data, + DP_MISC1_MISC0 + misc1_misc0_off); + + if (en) { + /* VSCEXT_SDP_EN, GEN0_SDP_EN */ + cfg |= BIT(16) | BIT(17); + dp_write(catalog->exe_mode, io_data, + MMSS_DP_SDP_CFG + sdp_cfg_off, cfg); + + /* EXTN_SDPSIZE GENERIC0_SDPSIZE */ + cfg2 |= BIT(15) | BIT(16); + dp_write(catalog->exe_mode, io_data, + MMSS_DP_SDP_CFG2 + sdp_cfg2_off, cfg2); + + dp_catalog_panel_setup_vsc_sdp(panel); + dp_catalog_panel_setup_infoframe_sdp(panel); + + /* indicates presence of VSC (BIT(6) of MISC1) */ + misc |= BIT(14); + + if (panel->hdr_data.hdr_meta.eotf) + pr_debug("Enabled\n"); + else + pr_debug("Reset\n"); + } else { + /* VSCEXT_SDP_EN, GEN0_SDP_EN */ + cfg &= ~BIT(16) & ~BIT(17); + dp_write(catalog->exe_mode, io_data, + MMSS_DP_SDP_CFG + sdp_cfg_off, cfg); + + /* EXTN_SDPSIZE GENERIC0_SDPSIZE */ + cfg2 &= ~BIT(15) & ~BIT(16); + dp_write(catalog->exe_mode, io_data, + MMSS_DP_SDP_CFG2 + sdp_cfg2_off, cfg2); + + /* switch back to MSA */ + misc &= ~BIT(14); + + pr_debug("Disabled\n"); + } + + dp_write(catalog->exe_mode, io_data, DP_MISC1_MISC0 + misc1_misc0_off, + misc); + + dp_write(catalog->exe_mode, io_data, MMSS_DP_SDP_CFG3 + sdp_cfg3_off, + 0x01); + dp_write(catalog->exe_mode, io_data, MMSS_DP_SDP_CFG3 + sdp_cfg3_off, + 0x00); +} + +static void dp_catalog_panel_update_transfer_unit( + struct dp_catalog_panel *panel) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!panel || panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(panel); + io_data = catalog->io.dp_link; + + dp_write(catalog->exe_mode, io_data, DP_VALID_BOUNDARY, + panel->valid_boundary); + dp_write(catalog->exe_mode, io_data, DP_TU, panel->dp_tu); + dp_write(catalog->exe_mode, io_data, DP_VALID_BOUNDARY_2, + panel->valid_boundary2); +} + +static void dp_catalog_ctrl_state_ctrl(struct dp_catalog_ctrl *ctrl, u32 state) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + io_data = catalog->io.dp_link; + + dp_write(catalog->exe_mode, io_data, DP_STATE_CTRL, state); + /* make sure to change the hw state */ + wmb(); +} + +static void dp_catalog_ctrl_config_ctrl(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + u32 cfg; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + io_data = catalog->io.dp_link; + + cfg = dp_read(catalog->exe_mode, io_data, DP_MAINLINK_CTRL); + cfg |= 0x02000000; + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_CTRL, cfg); + + pr_debug("DP_MAINLINK_CTRL=0x%x\n", cfg); + + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_LEVELS, 0xa08); +} + +static void dp_catalog_panel_config_ctrl(struct dp_catalog_panel *panel, + u32 cfg) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + u32 strm_reg_off = 0, mainlink_ctrl; + + if (!panel) { + pr_err("invalid input\n"); + return; + } + + if (panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream_id:%d\n", panel->stream_id); + return; + } + + catalog = dp_catalog_get_priv(panel); + io_data = catalog->io.dp_link; + + if (panel->stream_id == DP_STREAM_1) + strm_reg_off = DP1_CONFIGURATION_CTRL - DP_CONFIGURATION_CTRL; + + pr_debug("DP_CONFIGURATION_CTRL=0x%x\n", cfg); + + dp_write(catalog->exe_mode, io_data, + DP_CONFIGURATION_CTRL + strm_reg_off, cfg); + + mainlink_ctrl = dp_read(catalog->exe_mode, io_data, DP_MAINLINK_CTRL); + + if (panel->stream_id == DP_STREAM_0) + io_data = catalog->io.dp_p0; + else if (panel->stream_id == DP_STREAM_1) + io_data = catalog->io.dp_p1; + + if (mainlink_ctrl & BIT(8)) + dp_write(catalog->exe_mode, io_data, MMSS_DP_ASYNC_FIFO_CONFIG, + 0x01); + else + dp_write(catalog->exe_mode, io_data, MMSS_DP_ASYNC_FIFO_CONFIG, + 0x00); +} + +static void dp_catalog_panel_config_dto(struct dp_catalog_panel *panel, + bool ack) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!panel) { + pr_err("invalid input\n"); + return; + } + + if (panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream_id:%d\n", panel->stream_id); + return; + } + + catalog = dp_catalog_get_priv(panel); + io_data = catalog->io.dp_link; + + switch (panel->stream_id) { + case DP_STREAM_0: + io_data = catalog->io.dp_p0; + break; + case DP_STREAM_1: + io_data = catalog->io.dp_p1; + break; + default: + pr_err("invalid stream id\n"); + return; + } + + dp_write(catalog->exe_mode, io_data, MMSS_DP_DSC_DTO, ack << 1); +} + +static void dp_catalog_ctrl_lane_mapping(struct dp_catalog_ctrl *ctrl, + bool flipped, char *lane_map) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + io_data = catalog->io.dp_link; + + dp_write(catalog->exe_mode, io_data, DP_LOGICAL2PHYSICAL_LANE_MAPPING, + 0xe4); +} + +static void dp_catalog_ctrl_mainlink_ctrl(struct dp_catalog_ctrl *ctrl, + bool enable) +{ + u32 mainlink_ctrl, reg; + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + io_data = catalog->io.dp_link; + + if (enable) { + reg = dp_read(catalog->exe_mode, io_data, DP_MAINLINK_CTRL); + mainlink_ctrl = reg & ~(0x03); + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_CTRL, + mainlink_ctrl); + wmb(); /* make sure mainlink is turned off before reset */ + mainlink_ctrl = reg | 0x02; + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_CTRL, + mainlink_ctrl); + wmb(); /* make sure mainlink entered reset */ + mainlink_ctrl = reg & ~(0x03); + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_CTRL, + mainlink_ctrl); + wmb(); /* make sure mainlink reset done */ + mainlink_ctrl = reg | 0x01; + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_CTRL, + mainlink_ctrl); + wmb(); /* make sure mainlink turned on */ + } else { + mainlink_ctrl = dp_read(catalog->exe_mode, io_data, + DP_MAINLINK_CTRL); + mainlink_ctrl &= ~BIT(0); + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_CTRL, + mainlink_ctrl); + } +} + +static void dp_catalog_panel_config_misc(struct dp_catalog_panel *panel) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + u32 reg_offset = 0; + + if (!panel) { + pr_err("invalid input\n"); + return; + } + + if (panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream_id:%d\n", panel->stream_id); + return; + } + + catalog = dp_catalog_get_priv(panel); + io_data = catalog->io.dp_link; + + if (panel->stream_id == DP_STREAM_1) + reg_offset = DP1_MISC1_MISC0 - DP_MISC1_MISC0; + + pr_debug("misc settings = 0x%x\n", panel->misc_val); + dp_write(catalog->exe_mode, io_data, DP_MISC1_MISC0 + reg_offset, + panel->misc_val); +} + +static void dp_catalog_panel_config_msa(struct dp_catalog_panel *panel, + 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; + struct dp_io_data *io_data; + u32 strm_reg_off = 0; + u32 mvid_reg_off = 0, nvid_reg_off = 0; + + if (!panel) { + pr_err("invalid input\n"); + return; + } + + if (panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream_id:%d\n", panel->stream_id); + return; + } + + catalog = dp_catalog_get_priv(panel); + 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\n", + 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 { + io_data = catalog->io.dp_mmss_cc; + + if (panel->stream_id == DP_STREAM_1) + strm_reg_off = MMSS_DP_PIXEL1_M - MMSS_DP_PIXEL_M; + + pixel_m = dp_read(catalog->exe_mode, io_data, + MMSS_DP_PIXEL_M + strm_reg_off); + pixel_n = dp_read(catalog->exe_mode, io_data, + MMSS_DP_PIXEL_N + strm_reg_off); + 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; + } + + io_data = catalog->io.dp_link; + + if (panel->stream_id == DP_STREAM_1) { + mvid_reg_off = DP1_SOFTWARE_MVID - DP_SOFTWARE_MVID; + nvid_reg_off = DP1_SOFTWARE_NVID - DP_SOFTWARE_NVID; + } + + pr_debug("mvid=0x%x, nvid=0x%x\n", mvid, nvid); + dp_write(catalog->exe_mode, io_data, DP_SOFTWARE_MVID + mvid_reg_off, + mvid); + dp_write(catalog->exe_mode, io_data, DP_SOFTWARE_NVID + nvid_reg_off, + 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; + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + io_data = catalog->io.dp_link; + + bit = 1; + bit <<= (pattern - 1); + pr_debug("hw: bit=%d train=%d\n", bit, pattern); + dp_write(catalog->exe_mode, io_data, DP_STATE_CTRL, bit); + + bit = 8; + bit <<= (pattern - 1); + + while (cnt--) { + data = dp_read(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + + io_data = catalog->io.usb3_dp_com; + + dp_write(catalog->exe_mode, io_data, USB3_DP_COM_RESET_OVRD_CTRL, 0x0a); + dp_write(catalog->exe_mode, io_data, USB3_DP_COM_PHY_MODE_CTRL, 0x02); + dp_write(catalog->exe_mode, io_data, USB3_DP_COM_SW_RESET, 0x01); + /* make sure usb3 com phy software reset is done */ + wmb(); + + if (!flip) { /* CC1 */ + dp_write(catalog->exe_mode, io_data, USB3_DP_COM_TYPEC_CTRL, + 0x02); + } else { /* CC2 */ + dp_write(catalog->exe_mode, io_data, USB3_DP_COM_TYPEC_CTRL, + 0x03); + } + + dp_write(catalog->exe_mode, io_data, USB3_DP_COM_SWI_CTRL, 0x00); + dp_write(catalog->exe_mode, io_data, USB3_DP_COM_SW_RESET, 0x00); + /* make sure the software reset is done */ + wmb(); + + dp_write(catalog->exe_mode, io_data, USB3_DP_COM_POWER_DOWN_CTRL, 0x01); + dp_write(catalog->exe_mode, io_data, USB3_DP_COM_RESET_OVRD_CTRL, 0x00); + /* make sure phy is brought out of reset */ + wmb(); +} + +static void dp_catalog_panel_tpg_cfg(struct dp_catalog_panel *panel, + bool enable) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!panel) { + pr_err("invalid input\n"); + return; + } + + if (panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream_id:%d\n", panel->stream_id); + return; + } + + catalog = dp_catalog_get_priv(panel); + + if (panel->stream_id == DP_STREAM_0) + io_data = catalog->io.dp_p0; + else if (panel->stream_id == DP_STREAM_1) + io_data = catalog->io.dp_p1; + + if (!enable) { + dp_write(catalog->exe_mode, io_data, MMSS_DP_TPG_MAIN_CONTROL, + 0x0); + dp_write(catalog->exe_mode, io_data, MMSS_DP_BIST_ENABLE, 0x0); + dp_write(catalog->exe_mode, io_data, MMSS_DP_TIMING_ENGINE_EN, + 0x0); + wmb(); /* ensure Timing generator is turned off */ + return; + } + + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_CONFIG, 0x0); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_HSYNC_CTL, + panel->hsync_ctl); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_VSYNC_PERIOD_F0, + panel->vsync_period * panel->hsync_period); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F0, + panel->v_sync_width * panel->hsync_period); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_VSYNC_PERIOD_F1, 0); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F1, + 0); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_DISPLAY_HCTL, + panel->display_hctl); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_ACTIVE_HCTL, 0); + dp_write(catalog->exe_mode, io_data, MMSS_INTF_DISPLAY_V_START_F0, + panel->display_v_start); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_DISPLAY_V_END_F0, + panel->display_v_end); + dp_write(catalog->exe_mode, io_data, MMSS_INTF_DISPLAY_V_START_F1, 0); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_DISPLAY_V_END_F1, 0); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_ACTIVE_V_START_F0, 0); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_ACTIVE_V_END_F0, 0); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_ACTIVE_V_START_F1, 0); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_ACTIVE_V_END_F1, 0); + dp_write(catalog->exe_mode, io_data, MMSS_DP_INTF_POLARITY_CTL, 0); + wmb(); /* ensure TPG registers are programmed */ + + dp_write(catalog->exe_mode, io_data, MMSS_DP_TPG_MAIN_CONTROL, 0x100); + dp_write(catalog->exe_mode, io_data, MMSS_DP_TPG_VIDEO_CONFIG, 0x5); + wmb(); /* ensure TPG config is programmed */ + dp_write(catalog->exe_mode, io_data, MMSS_DP_BIST_ENABLE, 0x1); + dp_write(catalog->exe_mode, io_data, MMSS_DP_TIMING_ENGINE_EN, 0x1); + wmb(); /* ensure Timing generator is turned on */ +} + +static void dp_catalog_ctrl_reset(struct dp_catalog_ctrl *ctrl) +{ + u32 sw_reset; + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + io_data = catalog->io.dp_ahb; + + sw_reset = dp_read(catalog->exe_mode, io_data, DP_SW_RESET); + + sw_reset |= BIT(0); + dp_write(catalog->exe_mode, io_data, DP_SW_RESET, sw_reset); + usleep_range(1000, 1010); /* h/w recommended delay */ + + sw_reset &= ~BIT(0); + dp_write(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + goto end; + } + + catalog = dp_catalog_get_priv(ctrl); + io_data = catalog->io.dp_link; + + while (--cnt) { + /* DP_MAINLINK_READY */ + data = dp_read(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + io_data = catalog->io.dp_ahb; + + if (enable) { + dp_write(catalog->exe_mode, io_data, DP_INTR_STATUS, + DP_INTR_MASK1); + dp_write(catalog->exe_mode, io_data, DP_INTR_STATUS2, + DP_INTR_MASK2); + dp_write(catalog->exe_mode, io_data, DP_INTR_STATUS5, + DP_INTR_MASK5); + } else { + dp_write(catalog->exe_mode, io_data, DP_INTR_STATUS, 0x00); + dp_write(catalog->exe_mode, io_data, DP_INTR_STATUS2, 0x00); + dp_write(catalog->exe_mode, io_data, DP_INTR_STATUS5, 0x00); + } +} + +static void dp_catalog_ctrl_hpd_config(struct dp_catalog_ctrl *ctrl, bool en) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + io_data = catalog->io.dp_aux; + + if (en) { + u32 reftimer = dp_read(catalog->exe_mode, io_data, + DP_DP_HPD_REFTIMER); + + dp_write(catalog->exe_mode, io_data, DP_DP_HPD_INT_ACK, 0xF); + dp_write(catalog->exe_mode, io_data, DP_DP_HPD_INT_MASK, 0xF); + /* Enabling REFTIMER */ + reftimer |= BIT(16); + dp_write(catalog->exe_mode, io_data, DP_DP_HPD_REFTIMER, + reftimer); + /* Enable HPD */ + dp_write(catalog->exe_mode, io_data, DP_DP_HPD_CTRL, 0x1); + } else { + /*Disable HPD */ + dp_write(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + io_data = catalog->io.dp_ahb; + + ctrl->isr = dp_read(catalog->exe_mode, io_data, DP_INTR_STATUS2); + ctrl->isr &= ~DP_INTR_MASK2; + ack = ctrl->isr & DP_INTERRUPT_STATUS2; + ack <<= 1; + ack |= DP_INTR_MASK2; + dp_write(catalog->exe_mode, io_data, DP_INTR_STATUS2, ack); + + ctrl->isr5 = dp_read(catalog->exe_mode, io_data, DP_INTR_STATUS5); + ctrl->isr5 &= ~DP_INTR_MASK5; + ack = ctrl->isr5 & DP_INTERRUPT_STATUS5; + ack <<= 1; + ack |= DP_INTR_MASK5; + dp_write(catalog->exe_mode, io_data, DP_INTR_STATUS5, ack); +} + +static void dp_catalog_ctrl_phy_reset(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + io_data = catalog->io.dp_ahb; + + dp_write(catalog->exe_mode, io_data, DP_PHY_CTRL, 0x5); /* bit 0 & 2 */ + usleep_range(1000, 1010); /* h/w recommended delay */ + dp_write(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data; + u8 orientation = BIT(!!flipped); + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + + io_data = catalog->io.dp_phy; + + info |= (ln_cnt & 0x0F); + info |= ((orientation & 0x0F) << 4); + pr_debug("Shared Info = 0x%x\n", info); + + dp_write(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data; + u8 value0, value1; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + + 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 */ + + io_data = catalog->io.dp_ln_tx0; + dp_write(catalog->exe_mode, io_data, TXn_TX_DRV_LVL, 0x2A); + dp_write(catalog->exe_mode, io_data, TXn_TX_EMP_POST1_LVL, 0x20); + + io_data = catalog->io.dp_ln_tx1; + dp_write(catalog->exe_mode, io_data, TXn_TX_DRV_LVL, 0x2A); + dp_write(catalog->exe_mode, io_data, 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) { + io_data = catalog->io.dp_ln_tx0; + dp_write(catalog->exe_mode, io_data, TXn_TX_DRV_LVL, value0); + dp_write(catalog->exe_mode, io_data, TXn_TX_EMP_POST1_LVL, + value1); + + io_data = catalog->io.dp_ln_tx1; + dp_write(catalog->exe_mode, io_data, TXn_TX_DRV_LVL, value0); + dp_write(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data = NULL; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + + io_data = catalog->io.dp_link; + + dp_write(catalog->exe_mode, io_data, DP_STATE_CTRL, 0x0); + + switch (pattern) { + case DP_TEST_PHY_PATTERN_D10_2_NO_SCRAMBLING: + dp_write(catalog->exe_mode, io_data, DP_STATE_CTRL, 0x1); + break; + case DP_TEST_PHY_PATTERN_SYMBOL_ERR_MEASUREMENT_CNT: + value &= ~(1 << 16); + dp_write(catalog->exe_mode, io_data, + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); + value |= 0xFC; + dp_write(catalog->exe_mode, io_data, + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_LEVELS, 0x2); + dp_write(catalog->exe_mode, io_data, DP_STATE_CTRL, 0x10); + break; + case DP_TEST_PHY_PATTERN_PRBS7: + dp_write(catalog->exe_mode, io_data, DP_STATE_CTRL, 0x20); + break; + case DP_TEST_PHY_PATTERN_80_BIT_CUSTOM_PATTERN: + dp_write(catalog->exe_mode, io_data, DP_STATE_CTRL, 0x40); + /* 00111110000011111000001111100000 */ + dp_write(catalog->exe_mode, io_data, + DP_TEST_80BIT_CUSTOM_PATTERN_REG0, 0x3E0F83E0); + /* 00001111100000111110000011111000 */ + dp_write(catalog->exe_mode, io_data, + DP_TEST_80BIT_CUSTOM_PATTERN_REG1, 0x0F83E0F8); + /* 1111100000111110 */ + dp_write(catalog->exe_mode, io_data, + DP_TEST_80BIT_CUSTOM_PATTERN_REG2, 0x0000F83E); + break; + case DP_TEST_PHY_PATTERN_CP2520_PATTERN_1: + value = dp_read(catalog->exe_mode, io_data, DP_MAINLINK_CTRL); + value &= ~BIT(4); + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_CTRL, value); + + value = BIT(16); + dp_write(catalog->exe_mode, io_data, + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); + value |= 0xFC; + dp_write(catalog->exe_mode, io_data, + DP_HBR2_COMPLIANCE_SCRAMBLER_RESET, value); + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_LEVELS, 0x2); + dp_write(catalog->exe_mode, io_data, DP_STATE_CTRL, 0x10); + + value = dp_read(catalog->exe_mode, io_data, DP_MAINLINK_CTRL); + value |= BIT(0); + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_CTRL, value); + break; + case DP_TEST_PHY_PATTERN_CP2520_PATTERN_3: + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_CTRL, 0x11); + dp_write(catalog->exe_mode, io_data, DP_STATE_CTRL, 0x8); + 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; + struct dp_io_data *io_data = NULL; + + if (!ctrl) { + pr_err("invalid input\n"); + return 0; + } + + catalog = dp_catalog_get_priv(ctrl); + + io_data = catalog->io.dp_link; + + return dp_read(catalog->exe_mode, io_data, DP_MAINLINK_READY); +} + +static int dp_catalog_reg_dump(struct dp_catalog *dp_catalog, + char *name, u8 **out_buf, u32 *out_buf_len) +{ + int ret = 0; + u8 *buf; + u32 len; + struct dp_io_data *io_data; + struct dp_catalog_private *catalog; + struct dp_parser *parser; + + if (!dp_catalog) { + pr_err("invalid input\n"); + return -EINVAL; + } + + catalog = container_of(dp_catalog, struct dp_catalog_private, + dp_catalog); + + parser = catalog->parser; + parser->get_io_buf(parser, name); + io_data = parser->get_io(parser, name); + if (!io_data) { + pr_err("IO %s not found\n", name); + ret = -EINVAL; + goto end; + } + + buf = io_data->buf; + len = io_data->io.len; + + if (!buf || !len) { + pr_err("no buffer available\n"); + ret = -ENOMEM; + goto end; + } + + if (!strcmp(catalog->exe_mode, "hw") || + !strcmp(catalog->exe_mode, "all")) { + u32 i, data; + u32 const rowsize = 4; + void __iomem *addr = io_data->io.base; + + memset(buf, 0, len); + + for (i = 0; i < len / rowsize; i++) { + data = readl_relaxed(addr); + memcpy(buf + (rowsize * i), &data, sizeof(u32)); + + addr += rowsize; + } + } + + *out_buf = buf; + *out_buf_len = len; +end: + if (ret) + parser->clear_io_buf(parser); + + return ret; +} + +static void dp_catalog_ctrl_mst_config(struct dp_catalog_ctrl *ctrl, + bool enable) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data = NULL; + u32 reg; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + + io_data = catalog->io.dp_link; + + reg = dp_read(catalog->exe_mode, io_data, DP_MAINLINK_CTRL); + if (enable) + reg |= (0x04000100); + else + reg &= ~(0x04000100); + + dp_write(catalog->exe_mode, io_data, DP_MAINLINK_CTRL, reg); + /* make sure mainlink MST configuration is updated */ + wmb(); +} + +static void dp_catalog_ctrl_trigger_act(struct dp_catalog_ctrl *ctrl) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data = NULL; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + + io_data = catalog->io.dp_link; + + dp_write(catalog->exe_mode, io_data, DP_MST_ACT, 0x1); + /* make sure ACT signal is performed */ + wmb(); +} + +static void dp_catalog_ctrl_read_act_complete_sts(struct dp_catalog_ctrl *ctrl, + bool *sts) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data = NULL; + u32 reg; + + if (!ctrl || !sts) { + pr_err("invalid input\n"); + return; + } + + *sts = false; + + catalog = dp_catalog_get_priv(ctrl); + + io_data = catalog->io.dp_link; + + reg = dp_read(catalog->exe_mode, io_data, DP_MST_ACT); + + if (!reg) + *sts = true; +} + +static void dp_catalog_ctrl_channel_alloc(struct dp_catalog_ctrl *ctrl, + u32 ch, u32 ch_start_slot, u32 tot_slot_cnt) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data = NULL; + u32 i, slot_reg_1, slot_reg_2, slot; + u32 reg_off = 0; + int const num_slots_per_reg = 32; + + if (!ctrl || ch >= DP_STREAM_MAX) { + pr_err("invalid input. ch %d\n", ch); + return; + } + + if (ch_start_slot > DP_MAX_TIME_SLOTS || + (ch_start_slot + tot_slot_cnt > DP_MAX_TIME_SLOTS)) { + pr_err("invalid slots start %d, tot %d\n", + ch_start_slot, tot_slot_cnt); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + + io_data = catalog->io.dp_link; + + pr_debug("ch %d, start_slot %d, tot_slot %d\n", + ch, ch_start_slot, tot_slot_cnt); + + if (ch == DP_STREAM_1) + reg_off = DP_DP1_TIMESLOT_1_32 - DP_DP0_TIMESLOT_1_32; + + slot_reg_1 = 0; + slot_reg_2 = 0; + + if (ch_start_slot && tot_slot_cnt) { + ch_start_slot--; + for (i = 0; i < tot_slot_cnt; i++) { + if (ch_start_slot < num_slots_per_reg) { + slot_reg_1 |= BIT(ch_start_slot); + } else { + slot = ch_start_slot - num_slots_per_reg; + slot_reg_2 |= BIT(slot); + } + ch_start_slot++; + } + } + + pr_debug("ch:%d slot_reg_1:%d, slot_reg_2:%d\n", ch, + slot_reg_1, slot_reg_2); + + dp_write(catalog->exe_mode, io_data, DP_DP0_TIMESLOT_1_32 + reg_off, + slot_reg_1); + dp_write(catalog->exe_mode, io_data, DP_DP0_TIMESLOT_33_63 + reg_off, + slot_reg_2); +} + +static void dp_catalog_ctrl_channel_dealloc(struct dp_catalog_ctrl *ctrl, + u32 ch, u32 ch_start_slot, u32 tot_slot_cnt) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data = NULL; + u32 i, slot_reg_1, slot_reg_2, slot; + u32 reg_off = 0; + + if (!ctrl || ch >= DP_STREAM_MAX) { + pr_err("invalid input. ch %d\n", ch); + return; + } + + if (ch_start_slot > DP_MAX_TIME_SLOTS || + (ch_start_slot + tot_slot_cnt > DP_MAX_TIME_SLOTS)) { + pr_err("invalid slots start %d, tot %d\n", + ch_start_slot, tot_slot_cnt); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + + io_data = catalog->io.dp_link; + + pr_debug("dealloc ch %d, start_slot %d, tot_slot %d\n", + ch, ch_start_slot, tot_slot_cnt); + + if (ch == DP_STREAM_1) + reg_off = DP_DP1_TIMESLOT_1_32 - DP_DP0_TIMESLOT_1_32; + + slot_reg_1 = dp_read(catalog->exe_mode, io_data, + DP_DP0_TIMESLOT_1_32 + reg_off); + slot_reg_2 = dp_read(catalog->exe_mode, io_data, + DP_DP0_TIMESLOT_33_63 + reg_off); + + ch_start_slot = ch_start_slot - 1; + for (i = 0; i < tot_slot_cnt; i++) { + if (ch_start_slot < 33) { + slot_reg_1 &= ~BIT(ch_start_slot); + } else { + slot = ch_start_slot - 33; + slot_reg_2 &= ~BIT(slot); + } + ch_start_slot++; + } + + pr_debug("dealloc ch:%d slot_reg_1:%d, slot_reg_2:%d\n", ch, + slot_reg_1, slot_reg_2); + + dp_write(catalog->exe_mode, io_data, DP_DP0_TIMESLOT_1_32 + reg_off, + slot_reg_1); + dp_write(catalog->exe_mode, io_data, DP_DP0_TIMESLOT_33_63 + reg_off, + slot_reg_2); +} + +static void dp_catalog_ctrl_update_rg(struct dp_catalog_ctrl *ctrl, u32 ch, + u32 x_int, u32 y_frac_enum) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data = NULL; + u32 rg, reg_off = 0; + + if (!ctrl || ch >= DP_STREAM_MAX) { + pr_err("invalid input. ch %d\n", ch); + return; + } + + catalog = dp_catalog_get_priv(ctrl); + + io_data = catalog->io.dp_link; + + rg = y_frac_enum; + rg |= (x_int << 16); + + pr_debug("ch: %d x_int:%d y_frac_enum:%d rg:%d\n", ch, x_int, + y_frac_enum, rg); + + if (ch == DP_STREAM_1) + reg_off = DP_DP1_RG - DP_DP0_RG; + + dp_write(catalog->exe_mode, io_data, DP_DP0_RG + reg_off, rg); +} + +/* panel related catalog functions */ +static int dp_catalog_panel_timing_cfg(struct dp_catalog_panel *panel) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + u32 offset = 0; + + if (!panel) { + pr_err("invalid input\n"); + goto end; + } + + if (panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream_id:%d\n", panel->stream_id); + goto end; + } + + catalog = dp_catalog_get_priv(panel); + io_data = catalog->io.dp_link; + + if (panel->stream_id == DP_STREAM_1) + offset = DP1_TOTAL_HOR_VER - DP_TOTAL_HOR_VER; + + dp_write(catalog->exe_mode, io_data, DP_TOTAL_HOR_VER + offset, + panel->total); + dp_write(catalog->exe_mode, io_data, + DP_START_HOR_VER_FROM_SYNC + offset, panel->sync_start); + dp_write(catalog->exe_mode, io_data, + DP_HSYNC_VSYNC_WIDTH_POLARITY + offset, panel->width_blanking); + dp_write(catalog->exe_mode, io_data, DP_ACTIVE_HOR_VER + offset, + 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; + + catalog = 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; + struct dp_io_data *io_data; + u32 sdp_cfg = 0, sdp_cfg_off = 0; + u32 sdp_cfg2 = 0, sdp_cfg2_off = 0; + + if (!audio) + return; + + if (audio->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream id:%d\n", audio->stream_id); + return; + } + + if (audio->stream_id == DP_STREAM_1) { + sdp_cfg_off = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG; + sdp_cfg2_off = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG2; + } + + catalog = dp_catalog_get_priv(audio); + io_data = catalog->io.dp_link; + + sdp_cfg = dp_read(catalog->exe_mode, io_data, + MMSS_DP_SDP_CFG + sdp_cfg_off); + + /* 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(catalog->exe_mode, io_data, MMSS_DP_SDP_CFG + sdp_cfg_off, + sdp_cfg); + + sdp_cfg2 = dp_read(catalog->exe_mode, io_data, + MMSS_DP_SDP_CFG2 + sdp_cfg_off); + /* 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(catalog->exe_mode, io_data, MMSS_DP_SDP_CFG2 + sdp_cfg_off, + 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]; + struct dp_io_data *io_data; + enum dp_catalog_audio_sdp_type sdp; + enum dp_catalog_audio_header_type header; + + if (!audio) + return; + + catalog = dp_catalog_get_priv(audio); + + io_data = catalog->io.dp_link; + sdp_map = catalog->audio_map; + sdp = audio->sdp_type; + header = audio->sdp_header; + + audio->data = dp_read(catalog->exe_mode, io_data, 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]; + struct dp_io_data *io_data; + enum dp_catalog_audio_sdp_type sdp; + enum dp_catalog_audio_header_type header; + u32 data; + + if (!audio) + return; + + catalog = dp_catalog_get_priv(audio); + + io_data = catalog->io.dp_link; + sdp_map = catalog->audio_map; + sdp = audio->sdp_type; + header = audio->sdp_header; + data = audio->data; + + dp_write(catalog->exe_mode, io_data, sdp_map[sdp][header], data); +} + +static void dp_catalog_audio_config_acr(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + u32 acr_ctrl, select; + + catalog = dp_catalog_get_priv(audio); + + select = audio->data; + io_data = catalog->io.dp_link; + + 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(catalog->exe_mode, io_data, 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; + struct dp_io_data *io_data; + u32 mainlink_levels, safe_to_exit_level; + + catalog = dp_catalog_get_priv(audio); + + io_data = catalog->io.dp_link; + safe_to_exit_level = audio->data; + + mainlink_levels = dp_read(catalog->exe_mode, io_data, + 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(catalog->exe_mode, io_data, DP_MAINLINK_LEVELS, + mainlink_levels); +} + +static void dp_catalog_audio_enable(struct dp_catalog_audio *audio) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + bool enable; + u32 audio_ctrl; + + catalog = dp_catalog_get_priv(audio); + + io_data = catalog->io.dp_link; + enable = !!audio->data; + + audio_ctrl = dp_read(catalog->exe_mode, io_data, 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(catalog->exe_mode, io_data, MMSS_DP_AUDIO_CFG, audio_ctrl); + + /* make sure audio engine is disabled */ + wmb(); +} + +static void dp_catalog_config_spd_header(struct dp_catalog_panel *panel) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + u32 value, new_value, offset = 0; + u8 parity_byte; + + if (!panel || panel->stream_id >= DP_STREAM_MAX) + return; + + catalog = dp_catalog_get_priv(panel); + io_data = catalog->io.dp_link; + + if (panel->stream_id == DP_STREAM_1) + offset = MMSS_DP1_GENERIC0_0 - MMSS_DP_GENERIC0_0; + + /* Config header and parity byte 1 */ + value = dp_read(catalog->exe_mode, io_data, + MMSS_DP_GENERIC1_0 + offset); + + new_value = 0x83; + parity_byte = dp_header_get_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_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC1_0 + offset, + value); + + /* Config header and parity byte 2 */ + value = dp_read(catalog->exe_mode, io_data, + MMSS_DP_GENERIC1_1 + offset); + + new_value = 0x1b; + parity_byte = dp_header_get_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_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC1_1 + offset, + value); + + /* Config header and parity byte 3 */ + value = dp_read(catalog->exe_mode, io_data, + MMSS_DP_GENERIC1_1 + offset); + + new_value = (0x0 | (0x12 << 2)); + parity_byte = dp_header_get_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_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC1_1 + offset, + value); +} + +static void dp_catalog_panel_config_spd(struct dp_catalog_panel *panel) +{ + struct dp_catalog_private *catalog; + struct dp_io_data *io_data; + u32 spd_cfg = 0, spd_cfg2 = 0; + u8 *vendor = NULL, *product = NULL; + u32 offset = 0; + u32 sdp_cfg_off = 0; + u32 sdp_cfg2_off = 0; + u32 sdp_cfg3_off = 0; + + /* + * Source Device Information + * 00h unknown + * 01h Digital STB + * 02h DVD + * 03h D-VHS + * 04h HDD Video + * 05h DVC + * 06h DSC + * 07h Video CD + * 08h Game + * 09h PC general + * 0ah Bluray-Disc + * 0bh Super Audio CD + * 0ch HD DVD + * 0dh PMP + * 0eh-ffh reserved + */ + u32 device_type = 0; + + if (!panel || panel->stream_id >= DP_STREAM_MAX) + return; + + catalog = dp_catalog_get_priv(panel); + io_data = catalog->io.dp_link; + + if (panel->stream_id == DP_STREAM_1) + offset = MMSS_DP1_GENERIC0_0 - MMSS_DP_GENERIC0_0; + + dp_catalog_config_spd_header(panel); + + vendor = panel->spd_vendor_name; + product = panel->spd_product_description; + + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC1_2 + offset, + ((vendor[0] & 0x7f) | + ((vendor[1] & 0x7f) << 8) | + ((vendor[2] & 0x7f) << 16) | + ((vendor[3] & 0x7f) << 24))); + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC1_3 + offset, + ((vendor[4] & 0x7f) | + ((vendor[5] & 0x7f) << 8) | + ((vendor[6] & 0x7f) << 16) | + ((vendor[7] & 0x7f) << 24))); + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC1_4 + offset, + ((product[0] & 0x7f) | + ((product[1] & 0x7f) << 8) | + ((product[2] & 0x7f) << 16) | + ((product[3] & 0x7f) << 24))); + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC1_5 + offset, + ((product[4] & 0x7f) | + ((product[5] & 0x7f) << 8) | + ((product[6] & 0x7f) << 16) | + ((product[7] & 0x7f) << 24))); + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC1_6 + offset, + ((product[8] & 0x7f) | + ((product[9] & 0x7f) << 8) | + ((product[10] & 0x7f) << 16) | + ((product[11] & 0x7f) << 24))); + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC1_7 + offset, + ((product[12] & 0x7f) | + ((product[13] & 0x7f) << 8) | + ((product[14] & 0x7f) << 16) | + ((product[15] & 0x7f) << 24))); + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC1_8 + offset, + device_type); + dp_write(catalog->exe_mode, io_data, MMSS_DP_GENERIC1_9 + offset, 0x00); + + if (panel->stream_id == DP_STREAM_1) { + sdp_cfg_off = MMSS_DP1_SDP_CFG - MMSS_DP_SDP_CFG; + sdp_cfg2_off = MMSS_DP1_SDP_CFG2 - MMSS_DP_SDP_CFG; + sdp_cfg3_off = MMSS_DP1_SDP_CFG3 - MMSS_DP_SDP_CFG; + } + + spd_cfg = dp_read(catalog->exe_mode, io_data, + MMSS_DP_SDP_CFG + sdp_cfg_off); + /* GENERIC1_SDP for SPD Infoframe */ + spd_cfg |= BIT(18); + dp_write(catalog->exe_mode, io_data, MMSS_DP_SDP_CFG + sdp_cfg_off, + spd_cfg); + + spd_cfg2 = dp_read(catalog->exe_mode, io_data, + MMSS_DP_SDP_CFG2 + sdp_cfg2_off); + /* 28 data bytes for SPD Infoframe with GENERIC1 set */ + spd_cfg2 |= BIT(17); + dp_write(catalog->exe_mode, io_data, MMSS_DP_SDP_CFG2 + sdp_cfg2_off, + spd_cfg2); + + dp_write(catalog->exe_mode, io_data, MMSS_DP_SDP_CFG3 + sdp_cfg3_off, + 0x1); + dp_write(catalog->exe_mode, io_data, MMSS_DP_SDP_CFG3 + sdp_cfg3_off, + 0x0); +} + +static void dp_catalog_get_io_buf(struct dp_catalog_private *catalog) +{ + struct dp_parser *parser = catalog->parser; + + dp_catalog_fill_io_buf(dp_ahb); + dp_catalog_fill_io_buf(dp_aux); + dp_catalog_fill_io_buf(dp_link); + dp_catalog_fill_io_buf(dp_p0); + dp_catalog_fill_io_buf(dp_phy); + dp_catalog_fill_io_buf(dp_ln_tx0); + dp_catalog_fill_io_buf(dp_ln_tx1); + dp_catalog_fill_io_buf(dp_pll); + dp_catalog_fill_io_buf(usb3_dp_com); + dp_catalog_fill_io_buf(dp_mmss_cc); + dp_catalog_fill_io_buf(hdcp_physical); + dp_catalog_fill_io_buf(dp_p1); + dp_catalog_fill_io_buf(dp_tcsr); +} + +static void dp_catalog_get_io(struct dp_catalog_private *catalog) +{ + struct dp_parser *parser = catalog->parser; + + dp_catalog_fill_io(dp_ahb); + dp_catalog_fill_io(dp_aux); + dp_catalog_fill_io(dp_link); + dp_catalog_fill_io(dp_p0); + dp_catalog_fill_io(dp_phy); + dp_catalog_fill_io(dp_ln_tx0); + dp_catalog_fill_io(dp_ln_tx1); + dp_catalog_fill_io(dp_pll); + dp_catalog_fill_io(usb3_dp_com); + dp_catalog_fill_io(dp_mmss_cc); + dp_catalog_fill_io(hdcp_physical); + dp_catalog_fill_io(dp_p1); + dp_catalog_fill_io(dp_tcsr); +} + +static void dp_catalog_set_exe_mode(struct dp_catalog *dp_catalog, char *mode) +{ + struct dp_catalog_private *catalog; + + if (!dp_catalog) { + pr_err("invalid input\n"); + return; + } + + catalog = container_of(dp_catalog, struct dp_catalog_private, + dp_catalog); + + strlcpy(catalog->exe_mode, mode, sizeof(catalog->exe_mode)); + + if (!strcmp(catalog->exe_mode, "hw")) + catalog->parser->clear_io_buf(catalog->parser); + else + dp_catalog_get_io_buf(catalog); + + if (dp_catalog->priv.data && dp_catalog->priv.put) + dp_catalog->priv.set_exe_mode(dp_catalog, mode); +} + +static int dp_catalog_init(struct device *dev, struct dp_catalog *catalog, + struct dp_parser *parser) +{ + int rc = 0; + struct dp_catalog_private *catalog_priv; + + catalog_priv = container_of(catalog, struct dp_catalog_private, + dp_catalog); + + if (parser->hw_cfg.phy_version == DP_PHY_VERSION_4_2_0) + rc = dp_catalog_get_v420(dev, catalog, &catalog_priv->io); + else if (parser->hw_cfg.phy_version == DP_PHY_VERSION_2_0_0) + rc = dp_catalog_get_v200(dev, catalog, &catalog_priv->io); + + return 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); + + if (dp_catalog->priv.data && dp_catalog->priv.put) + dp_catalog->priv.put(dp_catalog); + + catalog->parser->clear_io_buf(catalog->parser); + devm_kfree(catalog->dev, catalog); +} + +struct dp_catalog *dp_catalog_get(struct device *dev, struct dp_parser *parser) +{ + 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, + .clear_hw_interrupts = dp_catalog_aux_clear_hw_interrupts, + }; + 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, + .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, + .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, + .mst_config = dp_catalog_ctrl_mst_config, + .trigger_act = dp_catalog_ctrl_trigger_act, + .read_act_complete_sts = dp_catalog_ctrl_read_act_complete_sts, + .channel_alloc = dp_catalog_ctrl_channel_alloc, + .update_rg = dp_catalog_ctrl_update_rg, + .channel_dealloc = dp_catalog_ctrl_channel_dealloc, + }; + 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, + .config_hdr = dp_catalog_panel_config_hdr, + .tpg_config = dp_catalog_panel_tpg_cfg, + .config_spd = dp_catalog_panel_config_spd, + .config_misc = dp_catalog_panel_config_misc, + .config_msa = dp_catalog_panel_config_msa, + .update_transfer_unit = dp_catalog_panel_update_transfer_unit, + .config_ctrl = dp_catalog_panel_config_ctrl, + .config_dto = dp_catalog_panel_config_dto, + }; + + if (!dev || !parser) { + 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->parser = parser; + + dp_catalog_get_io(catalog); + + strlcpy(catalog->exe_mode, "hw", sizeof(catalog->exe_mode)); + + dp_catalog = &catalog->dp_catalog; + + dp_catalog->aux = aux; + dp_catalog->ctrl = ctrl; + dp_catalog->audio = audio; + dp_catalog->panel = panel; + + rc = dp_catalog_init(dev, dp_catalog, parser); + if (rc) { + dp_catalog_put(dp_catalog); + goto error; + } + + dp_catalog->set_exe_mode = dp_catalog_set_exe_mode; + dp_catalog->get_reg_dump = dp_catalog_reg_dump; + + return dp_catalog; +error: + return ERR_PTR(rc); +} 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..6051d806f736f5bb5d8cacf7a2e5e766fa3349e5 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_catalog.h @@ -0,0 +1,324 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DP_CATALOG_H_ +#define _DP_CATALOG_H_ + +#include + +#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 DP_INTR_MST_DP0_VCPF_SENT BIT(0) +#define DP_INTR_MST_DP1_VCPF_SENT BIT(3) + +#define DP_MAX_TIME_SLOTS 64 + +/* stream id */ +enum dp_stream_id { + DP_STREAM_0, + DP_STREAM_1, + DP_STREAM_MAX, +}; + +struct dp_catalog_hdr_data { + u32 ext_header_byte0; + u32 ext_header_byte1; + u32 ext_header_byte2; + u32 ext_header_byte3; + + u32 vsc_header_byte0; + u32 vsc_header_byte1; + u32 vsc_header_byte2; + u32 vsc_header_byte3; + + u32 vscext_header_byte0; + u32 vscext_header_byte1; + u32 vscext_header_byte2; + u32 vscext_header_byte3; + + u32 bpc; + + u32 version; + u32 length; + u32 pixel_encoding; + u32 colorimetry; + u32 dynamic_range; + u32 content_type; + + struct drm_msm_ext_hdr_metadata hdr_meta; +}; + +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); + void (*clear_hw_interrupts)(struct dp_catalog_aux *aux); +}; + +struct dp_catalog_ctrl { + u32 isr; + u32 isr5; + + void (*state_ctrl)(struct dp_catalog_ctrl *ctrl, u32 state); + void (*config_ctrl)(struct dp_catalog_ctrl *ctrl); + void (*lane_mapping)(struct dp_catalog_ctrl *ctrl, bool flipped, + char *lane_map); + void (*mainlink_ctrl)(struct dp_catalog_ctrl *ctrl, bool enable); + 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); + 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); + void (*mst_config)(struct dp_catalog_ctrl *ctrl, bool enable); + void (*trigger_act)(struct dp_catalog_ctrl *ctrl); + void (*read_act_complete_sts)(struct dp_catalog_ctrl *ctrl, bool *sts); + void (*channel_alloc)(struct dp_catalog_ctrl *ctrl, + u32 ch, u32 ch_start_timeslot, u32 tot_ch_cnt); + void (*update_rg)(struct dp_catalog_ctrl *ctrl, u32 ch, u32 x_int, + u32 y_frac_enum); + void (*channel_dealloc)(struct dp_catalog_ctrl *ctrl, + u32 ch, u32 ch_start_timeslot, u32 tot_ch_cnt); +}; + +#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 + +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; + + enum dp_stream_id stream_id; + + 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; + u8 *spd_vendor_name; + u8 *spd_product_description; + + struct dp_catalog_hdr_data hdr_data; + + /* TPG */ + u32 hsync_period; + u32 vsync_period; + u32 display_v_start; + u32 display_v_end; + u32 v_sync_width; + u32 hsync_ctl; + u32 display_hctl; + + /* TU */ + u32 dp_tu; + u32 valid_boundary; + u32 valid_boundary2; + + u32 misc_val; + + enum dp_stream_id stream_id; + + int (*timing_cfg)(struct dp_catalog_panel *panel); + void (*config_hdr)(struct dp_catalog_panel *panel, bool en); + void (*tpg_config)(struct dp_catalog_panel *panel, bool enable); + void (*config_spd)(struct dp_catalog_panel *panel); + void (*config_misc)(struct dp_catalog_panel *panel); + void (*config_msa)(struct dp_catalog_panel *panel, + u32 rate, u32 stream_rate_khz, bool fixed_nvid); + void (*update_transfer_unit)(struct dp_catalog_panel *panel); + void (*config_ctrl)(struct dp_catalog_panel *panel, u32 cfg); + void (*config_dto)(struct dp_catalog_panel *panel, bool ack); +}; + +struct dp_catalog; +struct dp_catalog_priv { + void *data; + + void (*put)(struct dp_catalog *catalog); + void (*set_exe_mode)(struct dp_catalog *dp_catalog, char *mode); +}; + +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_priv priv; + + void (*set_exe_mode)(struct dp_catalog *dp_catalog, char *mode); + int (*get_reg_dump)(struct dp_catalog *dp_catalog, + char *mode, u8 **out_buf, u32 *out_buf_len); +}; + +static inline u8 dp_ecc_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 inline u8 dp_ecc_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 inline u8 dp_header_get_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_ecc_get_g1_value(ci); + x0 = dp_ecc_get_g0_value(ci); + } + + parity_byte = x1 | (x0 << 4); + + return parity_byte; +} + +static inline u32 dp_read(char *exe_mode, struct dp_io_data *io_data, + u32 offset) +{ + u32 data = 0; + + if (!strcmp(exe_mode, "hw") || !strcmp(exe_mode, "all")) { + data = readl_relaxed(io_data->io.base + offset); + } else if (!strcmp(exe_mode, "sw")) { + if (io_data->buf) + memcpy(&data, io_data->buf + offset, sizeof(offset)); + } + + return data; +} + +static inline void dp_write(char *exe_mode, struct dp_io_data *io_data, + u32 offset, u32 data) +{ + if (!strcmp(exe_mode, "hw") || !strcmp(exe_mode, "all")) + writel_relaxed(data, io_data->io.base + offset); + + if (!strcmp(exe_mode, "sw") || !strcmp(exe_mode, "all")) { + if (io_data->buf) + memcpy(io_data->buf + offset, &data, sizeof(data)); + } +} + +struct dp_catalog *dp_catalog_get(struct device *dev, struct dp_parser *parser); +void dp_catalog_put(struct dp_catalog *catalog); + +int dp_catalog_get_v420(struct device *dev, struct dp_catalog *catalog, + void *io); + +int dp_catalog_get_v200(struct device *dev, struct dp_catalog *catalog, + void *io); + +#endif /* _DP_CATALOG_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_catalog_v200.c b/drivers/gpu/drm/msm/dp/dp_catalog_v200.c new file mode 100644 index 0000000000000000000000000000000000000000..72cad77a483ae4bdf419fd833e573c0032c3be6b --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_catalog_v200.c @@ -0,0 +1,316 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include + +#include "dp_catalog.h" +#include "dp_reg.h" + +#define dp_catalog_get_priv_v200(x) ({ \ + struct dp_catalog *dp_catalog; \ + dp_catalog = container_of(x, struct dp_catalog, x); \ + dp_catalog->priv.data; \ +}) + +struct dp_catalog_io { + struct dp_io_data *dp_ahb; + struct dp_io_data *dp_aux; + struct dp_io_data *dp_link; + struct dp_io_data *dp_p0; + struct dp_io_data *dp_phy; + struct dp_io_data *dp_ln_tx0; + struct dp_io_data *dp_ln_tx1; + struct dp_io_data *dp_mmss_cc; + struct dp_io_data *dp_pll; + struct dp_io_data *usb3_dp_com; + struct dp_io_data *hdcp_physical; + struct dp_io_data *dp_p1; + struct dp_io_data *dp_tcsr; +}; + +struct dp_catalog_private_v200 { + struct device *dev; + struct dp_catalog_io *io; + + char exe_mode[SZ_4]; +}; + +static void dp_catalog_aux_clear_hw_interrupts_v200(struct dp_catalog_aux *aux) +{ + struct dp_catalog_private_v200 *catalog; + struct dp_io_data *io_data; + u32 data = 0; + + if (!aux) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv_v200(aux); + io_data = catalog->io->dp_phy; + + data = dp_read(catalog->exe_mode, io_data, + DP_PHY_AUX_INTERRUPT_STATUS_V200); + + dp_write(catalog->exe_mode, io_data, DP_PHY_AUX_INTERRUPT_CLEAR_V200, + 0x1f); + wmb(); /* make sure 0x1f is written before next write */ + dp_write(catalog->exe_mode, io_data, DP_PHY_AUX_INTERRUPT_CLEAR_V200, + 0x9f); + wmb(); /* make sure 0x9f is written before next write */ + dp_write(catalog->exe_mode, io_data, DP_PHY_AUX_INTERRUPT_CLEAR_V200, + 0); + wmb(); /* make sure register is cleared */ +} + +static void dp_catalog_aux_setup_v200(struct dp_catalog_aux *aux, + struct dp_aux_cfg *cfg) +{ + struct dp_catalog_private_v200 *catalog; + struct dp_io_data *io_data; + int i = 0, sw_reset = 0; + + if (!aux || !cfg) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv_v200(aux); + + io_data = catalog->io->dp_ahb; + sw_reset = dp_read(catalog->exe_mode, io_data, DP_SW_RESET); + + sw_reset |= BIT(0); + dp_write(catalog->exe_mode, io_data, DP_SW_RESET, sw_reset); + usleep_range(1000, 1010); /* h/w recommended delay */ + + sw_reset &= ~BIT(0); + dp_write(catalog->exe_mode, io_data, DP_SW_RESET, sw_reset); + + dp_write(catalog->exe_mode, io_data, DP_PHY_CTRL, 0x4); /* bit 2 */ + udelay(1000); + dp_write(catalog->exe_mode, io_data, DP_PHY_CTRL, 0x0); /* bit 2 */ + wmb(); /* make sure programming happened */ + + io_data = catalog->io->dp_tcsr; + dp_write(catalog->exe_mode, io_data, 0x4c, 0x1); /* bit 0 & 2 */ + wmb(); /* make sure programming happened */ + + io_data = catalog->io->dp_phy; + dp_write(catalog->exe_mode, io_data, DP_PHY_PD_CTL, 0x3c); + wmb(); /* make sure PD programming happened */ + dp_write(catalog->exe_mode, io_data, DP_PHY_PD_CTL, 0x3d); + wmb(); /* make sure PD programming happened */ + + /* DP AUX CFG register programming */ + io_data = catalog->io->dp_phy; + for (i = 0; i < PHY_AUX_CFG_MAX; i++) + dp_write(catalog->exe_mode, io_data, cfg[i].offset, + cfg[i].lut[cfg[i].current_index]); + + dp_write(catalog->exe_mode, io_data, DP_PHY_AUX_INTERRUPT_MASK_V200, + 0x1F); + wmb(); /* make sure AUX configuration is done before enabling it */ +} + +static void dp_catalog_panel_config_msa_v200(struct dp_catalog_panel *panel, + 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_v200 *catalog; + struct dp_io_data *io_data; + u32 strm_reg_off = 0; + u32 mvid_reg_off = 0, nvid_reg_off = 0; + + if (!panel) { + pr_err("invalid input\n"); + return; + } + + if (panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream_id:%d\n", panel->stream_id); + return; + } + + catalog = dp_catalog_get_priv_v200(panel); + 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\n", + 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 { + io_data = catalog->io->dp_mmss_cc; + + if (panel->stream_id == DP_STREAM_1) + strm_reg_off = MMSS_DP_PIXEL1_M_V200 - + MMSS_DP_PIXEL_M_V200; + + pixel_m = dp_read(catalog->exe_mode, io_data, + MMSS_DP_PIXEL_M_V200 + strm_reg_off); + pixel_n = dp_read(catalog->exe_mode, io_data, + MMSS_DP_PIXEL_N_V200 + strm_reg_off); + 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; + } + + io_data = catalog->io->dp_link; + + if (panel->stream_id == DP_STREAM_1) { + mvid_reg_off = DP1_SOFTWARE_MVID - DP_SOFTWARE_MVID; + nvid_reg_off = DP1_SOFTWARE_NVID - DP_SOFTWARE_NVID; + } + + pr_debug("mvid=0x%x, nvid=0x%x\n", mvid, nvid); + dp_write(catalog->exe_mode, io_data, DP_SOFTWARE_MVID + mvid_reg_off, + mvid); + dp_write(catalog->exe_mode, io_data, DP_SOFTWARE_NVID + nvid_reg_off, + nvid); +} + +static void dp_catalog_ctrl_lane_mapping_v200(struct dp_catalog_ctrl *ctrl, + bool flipped, char *lane_map) +{ + struct dp_catalog_private_v200 *catalog; + struct dp_io_data *io_data; + u8 l_map[4] = { 0 }, i = 0, j = 0; + u32 lane_map_reg = 0; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv_v200(ctrl); + io_data = catalog->io->dp_link; + + /* For flip case, swap phy lanes with ML0 and ML3, ML1 and ML2 */ + if (flipped) { + for (i = 0; i < DP_MAX_PHY_LN; i++) { + if (lane_map[i] == DP_ML0) { + for (j = 0; j < DP_MAX_PHY_LN; j++) { + if (lane_map[j] == DP_ML3) { + l_map[i] = DP_ML3; + l_map[j] = DP_ML0; + break; + } + } + } else if (lane_map[i] == DP_ML1) { + for (j = 0; j < DP_MAX_PHY_LN; j++) { + if (lane_map[j] == DP_ML2) { + l_map[i] = DP_ML2; + l_map[j] = DP_ML1; + break; + } + } + } + } + } else { + /* Normal orientation */ + for (i = 0; i < DP_MAX_PHY_LN; i++) + l_map[i] = lane_map[i]; + } + + lane_map_reg = ((l_map[3]&3)<<6)|((l_map[2]&3)<<4)|((l_map[1]&3)<<2) + |(l_map[0]&3); + + dp_write(catalog->exe_mode, io_data, DP_LOGICAL2PHYSICAL_LANE_MAPPING, + lane_map_reg); +} + +static void dp_catalog_ctrl_usb_reset_v200(struct dp_catalog_ctrl *ctrl, + bool flip) +{ +} + +static void dp_catalog_put_v200(struct dp_catalog *catalog) +{ + struct dp_catalog_private_v200 *catalog_priv; + + if (!catalog || !catalog->priv.data) + return; + + catalog_priv = catalog->priv.data; + devm_kfree(catalog_priv->dev, catalog_priv); +} + +static void dp_catalog_set_exe_mode_v200(struct dp_catalog *catalog, char *mode) +{ + struct dp_catalog_private_v200 *catalog_priv; + + if (!catalog || !catalog->priv.data) + return; + + catalog_priv = catalog->priv.data; + + strlcpy(catalog_priv->exe_mode, mode, sizeof(catalog_priv->exe_mode)); +} + +int dp_catalog_get_v200(struct device *dev, struct dp_catalog *catalog, + void *io) +{ + struct dp_catalog_private_v200 *catalog_priv; + + if (!dev || !catalog) { + pr_err("invalid input\n"); + return -EINVAL; + } + + catalog_priv = devm_kzalloc(dev, sizeof(*catalog_priv), GFP_KERNEL); + if (!catalog_priv) + return -ENOMEM; + + catalog_priv->dev = dev; + catalog_priv->io = io; + catalog->priv.data = catalog_priv; + + catalog->priv.put = dp_catalog_put_v200; + catalog->priv.set_exe_mode = dp_catalog_set_exe_mode_v200; + + catalog->aux.clear_hw_interrupts = + dp_catalog_aux_clear_hw_interrupts_v200; + catalog->aux.setup = dp_catalog_aux_setup_v200; + + catalog->panel.config_msa = dp_catalog_panel_config_msa_v200; + + catalog->ctrl.lane_mapping = dp_catalog_ctrl_lane_mapping_v200; + catalog->ctrl.usb_reset = dp_catalog_ctrl_usb_reset_v200; + + /* Set the default execution mode to hardware mode */ + dp_catalog_set_exe_mode_v200(catalog, "hw"); + + return 0; +} diff --git a/drivers/gpu/drm/msm/dp/dp_catalog_v420.c b/drivers/gpu/drm/msm/dp/dp_catalog_v420.c new file mode 100644 index 0000000000000000000000000000000000000000..13a2f1fc2ed65e0f4b4a18b016d4b0cff447b7f7 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_catalog_v420.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include "dp_catalog.h" +#include "dp_reg.h" + +#define dp_catalog_get_priv_v420(x) ({ \ + struct dp_catalog *dp_catalog; \ + dp_catalog = container_of(x, struct dp_catalog, x); \ + dp_catalog->priv.data; \ +}) + +#define MAX_VOLTAGE_LEVELS 4 +#define MAX_PRE_EMP_LEVELS 4 + +static u8 const vm_pre_emphasis[MAX_VOLTAGE_LEVELS][MAX_PRE_EMP_LEVELS] = { + {0x00, 0x0B, 0x14, 0xFF}, /* pe0, 0 db */ + {0x00, 0x0B, 0x12, 0xFF}, /* pe1, 3.5 db */ + {0x00, 0x0B, 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[MAX_VOLTAGE_LEVELS][MAX_PRE_EMP_LEVELS] = { + {0x07, 0x0F, 0x16, 0xFF}, /* sw0, 0.4v */ + {0x11, 0x1E, 0x1F, 0xFF}, /* sw1, 0.6 v */ + {0x19, 0x1F, 0xFF, 0xFF}, /* sw1, 0.8 v */ + {0xFF, 0xFF, 0xFF, 0xFF} /* sw1, 1.2 v, optional */ +}; + +struct dp_catalog_io { + struct dp_io_data *dp_ahb; + struct dp_io_data *dp_aux; + struct dp_io_data *dp_link; + struct dp_io_data *dp_p0; + struct dp_io_data *dp_phy; + struct dp_io_data *dp_ln_tx0; + struct dp_io_data *dp_ln_tx1; + struct dp_io_data *dp_mmss_cc; + struct dp_io_data *dp_pll; + struct dp_io_data *usb3_dp_com; + struct dp_io_data *hdcp_physical; + struct dp_io_data *dp_p1; +}; + +struct dp_catalog_private_v420 { + struct device *dev; + struct dp_catalog_io *io; + + char exe_mode[SZ_4]; +}; + +static void dp_catalog_aux_setup_v420(struct dp_catalog_aux *aux, + struct dp_aux_cfg *cfg) +{ + struct dp_catalog_private_v420 *catalog; + struct dp_io_data *io_data; + int i = 0; + + if (!aux || !cfg) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv_v420(aux); + + io_data = catalog->io->dp_phy; + dp_write(catalog->exe_mode, io_data, DP_PHY_PD_CTL, 0x67); + wmb(); /* make sure PD programming happened */ + + /* Turn on BIAS current for PHY/PLL */ + io_data = catalog->io->dp_pll; + dp_write(catalog->exe_mode, io_data, QSERDES_COM_BIAS_EN_CLKBUFLR_EN, + 0x17); + wmb(); /* make sure BIAS programming happened */ + + io_data = catalog->io->dp_phy; + /* 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->exe_mode, io_data, cfg[i].offset, + cfg[i].lut[cfg[i].current_index]); + } + wmb(); /* make sure DP AUX CFG programming happened */ + + dp_write(catalog->exe_mode, io_data, DP_PHY_AUX_INTERRUPT_MASK_V420, + 0x1F); +} + +static void dp_catalog_panel_config_msa_v420(struct dp_catalog_panel *panel, + u32 rate, u32 stream_rate_khz, + bool fixed_nvid) +{ + u32 pixel_m, pixel_n; + u32 mvid, nvid, reg_off = 0, mvid_off = 0, nvid_off = 0; + u64 mvid_calc; + u32 const nvid_fixed = 0x8000; + u32 const link_rate_hbr2 = 540000; + u32 const link_rate_hbr3 = 810000; + struct dp_catalog_private_v420 *catalog; + struct dp_io_data *io_data; + + if (!panel || !rate) { + pr_err("invalid input\n"); + return; + } + + if (panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream id:%d\n", panel->stream_id); + return; + } + + catalog = dp_catalog_get_priv_v420(panel); + 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\n", + 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 { + io_data = catalog->io->dp_mmss_cc; + + if (panel->stream_id == DP_STREAM_1) + reg_off = MMSS_DP_PIXEL1_M_V420 - MMSS_DP_PIXEL_M_V420; + + pixel_m = dp_read(catalog->exe_mode, io_data, + MMSS_DP_PIXEL_M_V420 + reg_off); + pixel_n = dp_read(catalog->exe_mode, io_data, + MMSS_DP_PIXEL_N_V420 + reg_off); + 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; + } + + io_data = catalog->io->dp_link; + + if (panel->stream_id == DP_STREAM_1) { + mvid_off = DP1_SOFTWARE_MVID - DP_SOFTWARE_MVID; + nvid_off = DP1_SOFTWARE_NVID - DP_SOFTWARE_NVID; + } + + pr_debug("mvid=0x%x, nvid=0x%x\n", mvid, nvid); + dp_write(catalog->exe_mode, io_data, DP_SOFTWARE_MVID + mvid_off, mvid); + dp_write(catalog->exe_mode, io_data, DP_SOFTWARE_NVID + nvid_off, nvid); +} + +static void dp_catalog_ctrl_phy_lane_cfg_v420(struct dp_catalog_ctrl *ctrl, + bool flipped, u8 ln_cnt) +{ + u32 info = 0x0; + struct dp_catalog_private_v420 *catalog; + u8 orientation = BIT(!!flipped); + struct dp_io_data *io_data; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv_v420(ctrl); + io_data = catalog->io->dp_phy; + + info |= (ln_cnt & 0x0F); + info |= ((orientation & 0x0F) << 4); + pr_debug("Shared Info = 0x%x\n", info); + + dp_write(catalog->exe_mode, io_data, DP_PHY_SPARE0_V420, info); +} + +static void dp_catalog_ctrl_update_vx_px_v420(struct dp_catalog_ctrl *ctrl, + u8 v_level, u8 p_level) +{ + struct dp_catalog_private_v420 *catalog; + struct dp_io_data *io_data; + u8 value0, value1; + + if (!ctrl || !((v_level < MAX_VOLTAGE_LEVELS) + && (p_level < MAX_PRE_EMP_LEVELS))) { + pr_err("invalid input\n"); + return; + } + + catalog = dp_catalog_get_priv_v420(ctrl); + + 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 */ + io_data = catalog->io->dp_ln_tx0; + dp_write(catalog->exe_mode, io_data, TXn_TX_DRV_LVL_V420, 0x2A); + dp_write(catalog->exe_mode, io_data, TXn_TX_EMP_POST1_LVL, 0x20); + + io_data = catalog->io->dp_ln_tx1; + dp_write(catalog->exe_mode, io_data, TXn_TX_DRV_LVL_V420, 0x2A); + dp_write(catalog->exe_mode, io_data, 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) { + io_data = catalog->io->dp_ln_tx0; + dp_write(catalog->exe_mode, io_data, TXn_TX_DRV_LVL_V420, + value0); + dp_write(catalog->exe_mode, io_data, TXn_TX_EMP_POST1_LVL, + value1); + + io_data = catalog->io->dp_ln_tx1; + dp_write(catalog->exe_mode, io_data, TXn_TX_DRV_LVL_V420, + value0); + dp_write(catalog->exe_mode, io_data, 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_put_v420(struct dp_catalog *catalog) +{ + struct dp_catalog_private_v420 *catalog_priv; + + if (!catalog || !catalog->priv.data) + return; + + catalog_priv = catalog->priv.data; + devm_kfree(catalog_priv->dev, catalog_priv); +} + +static void dp_catalog_set_exe_mode_v420(struct dp_catalog *catalog, char *mode) +{ + struct dp_catalog_private_v420 *catalog_priv; + + if (!catalog || !catalog->priv.data) + return; + + catalog_priv = catalog->priv.data; + + strlcpy(catalog_priv->exe_mode, mode, sizeof(catalog_priv->exe_mode)); +} + +int dp_catalog_get_v420(struct device *dev, struct dp_catalog *catalog, + void *io) +{ + struct dp_catalog_private_v420 *catalog_priv; + + if (!dev || !catalog) { + pr_err("invalid input\n"); + return -EINVAL; + } + + catalog_priv = devm_kzalloc(dev, sizeof(*catalog_priv), GFP_KERNEL); + if (!catalog_priv) + return -ENOMEM; + + catalog_priv->dev = dev; + catalog_priv->io = io; + catalog->priv.data = catalog_priv; + + catalog->priv.put = dp_catalog_put_v420; + catalog->priv.set_exe_mode = dp_catalog_set_exe_mode_v420; + + catalog->aux.setup = dp_catalog_aux_setup_v420; + catalog->panel.config_msa = dp_catalog_panel_config_msa_v420; + catalog->ctrl.phy_lane_cfg = dp_catalog_ctrl_phy_lane_cfg_v420; + catalog->ctrl.update_vx_px = dp_catalog_ctrl_update_vx_px_v420; + + /* Set the default execution mode to hardware mode */ + dp_catalog_set_exe_mode_v420(catalog, "hw"); + + return 0; +} 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..404028582c0b6739a9036b1d55a331ce054dd83b --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.c @@ -0,0 +1,1256 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include +#include +#include + +#include "dp_ctrl.h" + +#define DP_MST_DEBUG(fmt, ...) pr_debug(fmt, ##__VA_ARGS__) + +#define DP_CTRL_INTR_READY_FOR_VIDEO BIT(0) +#define DP_CTRL_INTR_IDLE_PATTERN_SENT BIT(3) + +#define DP_CTRL_INTR_MST_DP0_VCPF_SENT BIT(0) +#define DP_CTRL_INTR_MST_DP1_VCPF_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 MST_DP0_PUSH_VCPF BIT(12) +#define MST_DP0_FORCE_VCPF BIT(13) +#define MST_DP1_PUSH_VCPF BIT(14) +#define MST_DP1_FORCE_VCPF BIT(15) + +#define MR_LINK_TRAINING1 0x8 +#define MR_LINK_SYMBOL_ERM 0x80 +#define MR_LINK_PRBS7 0x100 +#define MR_LINK_CUSTOM80 0x200 +#define MR_LINK_TRAINING4 0x40 + +struct dp_mst_ch_slot_info { + u32 start_slot; + u32 tot_slots; +}; + +struct dp_mst_channel_info { + struct dp_mst_ch_slot_info slot_info[DP_STREAM_MAX]; +}; + +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; + bool power_on; + bool mst_mode; + + atomic_t aborted; + + u32 vic; + u32 stream_count; + struct dp_mst_channel_info mst_ch_info; +}; + +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_private *ctrl, + enum dp_stream_id strm) +{ + int const idle_pattern_completion_timeout_ms = HZ / 10; + u32 state = 0x0; + + if (!ctrl->power_on) + return; + + if (!ctrl->mst_mode) { + state = ST_PUSH_IDLE; + goto trigger_idle; + } + + if (strm >= DP_STREAM_MAX) { + pr_err("mst push idle, invalid stream:%d\n", strm); + return; + } + + state |= (strm == DP_STREAM_0) ? MST_DP0_PUSH_VCPF : MST_DP1_PUSH_VCPF; + +trigger_idle: + reinit_completion(&ctrl->idle_comp); + dp_ctrl_state_ctrl(ctrl, state); + + if (!wait_for_completion_timeout(&ctrl->idle_comp, + idle_pattern_completion_timeout_ms)) + pr_warn("time out\n"); + else + pr_debug("mainlink off done\n"); +} + +/** + * dp_ctrl_configure_source_link_params() - configures DP TX source params + * @ctrl: Display Port Driver data + * @enable: enable or disable DP transmitter + * + * 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_link_params(struct dp_ctrl_private *ctrl, + bool enable) +{ + if (enable) { + ctrl->catalog->lane_mapping(ctrl->catalog, ctrl->orientation, + ctrl->parser->l_map); + ctrl->catalog->mst_config(ctrl->catalog, ctrl->mst_mode); + ctrl->catalog->config_ctrl(ctrl->catalog); + ctrl->catalog->mainlink_ctrl(ctrl->catalog, true); + } else { + ctrl->catalog->mainlink_ctrl(ctrl->catalog, false); + } +} + +static void dp_ctrl_wait4video_ready(struct dp_ctrl_private *ctrl) +{ + if (!wait_for_completion_timeout(&ctrl->video_comp, HZ / 2)) + pr_warn("SEND_VIDEO time out\n"); +} + +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 int 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); + + return dp_ctrl_update_sink_vx_px(ctrl, link->phy_params.v_level, + link->phy_params.p_level); +} + +static int 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; + return 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; + + ctrl->aux->state &= ~DP_STATE_TRAIN_1_FAILED; + ctrl->aux->state &= ~DP_STATE_TRAIN_1_SUCCEEDED; + ctrl->aux->state |= DP_STATE_TRAIN_1_STARTED; + + 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); + ret = dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_1 | + DP_LINK_SCRAMBLING_DISABLE); /* train_1 */ + if (ret <= 0) { + ret = -EINVAL; + goto end; + } + + ret = dp_ctrl_update_vx_px(ctrl); + if (ret <= 0) { + ret = -EINVAL; + goto end; + } + + tries = 0; + old_v_level = ctrl->link->phy_params.v_level; + while (1) { + if (atomic_read(&ctrl->aborted)) { + ret = -EINVAL; + break; + } + + 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); + ret = dp_ctrl_update_vx_px(ctrl); + if (ret <= 0) { + ret = -EINVAL; + break; + } + } +end: + ctrl->aux->state &= ~DP_STATE_TRAIN_1_STARTED; + + if (ret) + ctrl->aux->state |= DP_STATE_TRAIN_1_FAILED; + else + ctrl->aux->state |= DP_STATE_TRAIN_1_SUCCEEDED; + + 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]; + + ctrl->aux->state &= ~DP_STATE_TRAIN_2_FAILED; + ctrl->aux->state &= ~DP_STATE_TRAIN_2_SUCCEEDED; + ctrl->aux->state |= DP_STATE_TRAIN_2_STARTED; + + 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; + + ret = dp_ctrl_update_vx_px(ctrl); + if (ret <= 0) { + ret = -EINVAL; + goto end; + } + ctrl->catalog->set_pattern(ctrl->catalog, pattern); + ret = dp_ctrl_train_pattern_set(ctrl, + pattern | DP_RECOVERED_CLOCK_OUT_EN); + if (ret <= 0) { + ret = -EINVAL; + goto end; + } + + do { + if (atomic_read(&ctrl->aborted)) { + ret = -EINVAL; + break; + } + + 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); + ret = dp_ctrl_update_vx_px(ctrl); + if (ret <= 0) { + ret = -EINVAL; + break; + } + } while (1); +end: + ctrl->aux->state &= ~DP_STATE_TRAIN_2_STARTED; + + if (ret) + ctrl->aux->state |= DP_STATE_TRAIN_2_FAILED; + else + ctrl->aux->state |= DP_STATE_TRAIN_2_SUCCEEDED; + 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; + + 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; + + ret = drm_dp_link_configure(ctrl->aux->drm_aux, &link_info); + if (ret) + goto end; + + ret = drm_dp_dpcd_write(ctrl->aux->drm_aux, + DP_MAIN_LINK_CHANNEL_CODING_SET, &encoding, 1); + if (ret <= 0) { + ret = -EINVAL; + goto end; + } + + 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_info("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) +{ + int ret = 0; + + if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) + goto end; + + /* + * 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); + +end: + return ret; +} + +static void dp_ctrl_set_clock_rate(struct dp_ctrl_private *ctrl, + char *name, enum dp_pm_type clk_type, u32 rate) +{ + u32 num = ctrl->parser->mp[clk_type].num_clk; + struct dss_clk *cfg = ctrl->parser->mp[clk_type].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_link_clock(struct dp_ctrl_private *ctrl) +{ + int ret = 0; + u32 rate = drm_dp_bw_code_to_link_rate(ctrl->link->link_params.bw_code); + enum dp_pm_type type = DP_LINK_PM; + + pr_debug("rate=%d\n", rate); + + dp_ctrl_set_clock_rate(ctrl, "link_clk", type, rate); + + ret = ctrl->power->clk_enable(ctrl->power, type, true); + if (ret) { + pr_err("Unabled to start link clocks\n"); + ret = -EINVAL; + } + + return ret; +} + +static void dp_ctrl_disable_link_clock(struct dp_ctrl_private *ctrl) +{ + ctrl->power->clk_enable(ctrl->power, DP_LINK_PM, false); +} + +static int dp_ctrl_link_setup(struct dp_ctrl_private *ctrl, bool shallow) +{ + int rc = -EINVAL; + u32 link_train_max_retries = 100; + struct dp_catalog_ctrl *catalog; + struct dp_link_params *link_params; + + catalog = ctrl->catalog; + link_params = &ctrl->link->link_params; + + catalog->hpd_config(catalog, true); + catalog->phy_lane_cfg(catalog, ctrl->orientation, + link_params->lane_count); + + while (--link_train_max_retries && !atomic_read(&ctrl->aborted)) { + pr_debug("bw_code=%d, lane_count=%d\n", + link_params->bw_code, link_params->lane_count); + + rc = dp_ctrl_enable_link_clock(ctrl); + if (rc) + break; + + dp_ctrl_configure_source_link_params(ctrl, true); + + rc = dp_ctrl_setup_main_link(ctrl); + if (!rc) + break; + + /* + * Shallow means link training failure is not important. + * If it fails, we still keep the link clocks on. + * In this mode, the system expects DP to be up + * even though the cable is removed. Disconnect interrupt + * will eventually trigger and shutdown DP. + */ + if (shallow) + break; + + dp_ctrl_link_rate_down_shift(ctrl); + + dp_ctrl_configure_source_link_params(ctrl, false); + dp_ctrl_disable_link_clock(ctrl); + + /* hw recommended delays before retrying link training */ + msleep(20); + } + + return rc; +} + +static int dp_ctrl_enable_stream_clocks(struct dp_ctrl_private *ctrl, + struct dp_panel *dp_panel) +{ + int ret = 0; + + ret = ctrl->power->set_pixel_clk_parent(ctrl->power, + dp_panel->stream_id); + + if (ret) + return ret; + + if (dp_panel->stream_id == DP_STREAM_0) { + dp_ctrl_set_clock_rate(ctrl, "strm0_pixel_clk", DP_STREAM0_PM, + dp_panel->pinfo.pixel_clk_khz); + + ret = ctrl->power->clk_enable(ctrl->power, DP_STREAM0_PM, true); + if (ret) { + pr_err("Unabled to start stream0 clocks\n"); + ret = -EINVAL; + } + } else if (dp_panel->stream_id == DP_STREAM_1) { + dp_ctrl_set_clock_rate(ctrl, "strm1_pixel_clk", DP_STREAM1_PM, + dp_panel->pinfo.pixel_clk_khz); + + ret = ctrl->power->clk_enable(ctrl->power, DP_STREAM1_PM, true); + if (ret) { + pr_err("Unabled to start stream1 clocks\n"); + ret = -EINVAL; + } + } else { + pr_err("Invalid stream:%d for clk enable\n", + dp_panel->stream_id); + ret = -EINVAL; + } + + return ret; +} + +static int dp_ctrl_disable_stream_clocks(struct dp_ctrl_private *ctrl, + struct dp_panel *dp_panel) +{ + int ret = 0; + + if (dp_panel->stream_id == DP_STREAM_0) { + return ctrl->power->clk_enable(ctrl->power, + DP_STREAM0_PM, false); + } else if (dp_panel->stream_id == DP_STREAM_1) { + return ctrl->power->clk_enable(ctrl->power, + DP_STREAM1_PM, false); + } else { + pr_err("Invalid stream:%d for clk disable\n", + dp_panel->stream_id); + ret = -EINVAL; + } + return ret; +} +static int dp_ctrl_host_init(struct dp_ctrl *dp_ctrl, bool flip, bool reset) +{ + 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; + + if (reset) { + catalog->usb_reset(ctrl->catalog, flip); + catalog->phy_reset(ctrl->catalog); + } + catalog->enable_irq(ctrl->catalog, true); + atomic_set(&ctrl->aborted, 0); + + 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 void dp_ctrl_send_video(struct dp_ctrl_private *ctrl) +{ + ctrl->catalog->state_ctrl(ctrl->catalog, ST_SEND_VIDEO); +} + +static int dp_ctrl_link_maintenance(struct dp_ctrl *dp_ctrl) +{ + int ret = 0; + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl) { + pr_err("Invalid input data\n"); + return -EINVAL; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + ctrl->aux->state &= ~DP_STATE_LINK_MAINTENANCE_COMPLETED; + ctrl->aux->state &= ~DP_STATE_LINK_MAINTENANCE_FAILED; + + if (!ctrl->power_on) { + pr_err("ctrl off\n"); + ret = -EINVAL; + goto end; + } + + if (atomic_read(&ctrl->aborted)) + goto end; + + ctrl->aux->state |= DP_STATE_LINK_MAINTENANCE_STARTED; + ret = dp_ctrl_setup_main_link(ctrl); + ctrl->aux->state &= ~DP_STATE_LINK_MAINTENANCE_STARTED; + + if (ret) { + ctrl->aux->state |= DP_STATE_LINK_MAINTENANCE_FAILED; + goto end; + } + + ctrl->aux->state |= DP_STATE_LINK_MAINTENANCE_COMPLETED; + + if (ctrl->stream_count) { + dp_ctrl_send_video(ctrl); + dp_ctrl_wait4video_ready(ctrl); + } +end: + return ret; +} + +static void dp_ctrl_process_phy_test_request(struct dp_ctrl *dp_ctrl) +{ + int ret = 0; + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl) { + pr_err("Invalid input data\n"); + return; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + if (!ctrl->link->phy_params.phy_test_pattern_sel) { + pr_debug("no test pattern selected by sink\n"); + return; + } + + pr_debug("start\n"); + + /* + * 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->catalog->reset(ctrl->catalog); + ctrl->dp_ctrl.stream_pre_off(&ctrl->dp_ctrl, ctrl->panel); + ctrl->dp_ctrl.stream_off(&ctrl->dp_ctrl, ctrl->panel); + ctrl->dp_ctrl.off(&ctrl->dp_ctrl); + + ctrl->aux->init(ctrl->aux, ctrl->parser->aux_cfg); + + ret = ctrl->dp_ctrl.on(&ctrl->dp_ctrl, ctrl->mst_mode, false); + if (ret) + pr_err("failed to enable DP controller\n"); + + ctrl->dp_ctrl.stream_on(&ctrl->dp_ctrl, ctrl->panel); + 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; + + 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); + pr_debug("pattern_request: %s. pattern_sent: 0x%x\n", + dp_link_get_phy_test_pattern(pattern_requested), + pattern_sent); + + 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_CP2520_PATTERN_1)) + 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; + case MR_LINK_TRAINING4: + if (pattern_requested == + DP_TEST_PHY_PATTERN_CP2520_PATTERN_3) + success = true; + break; + default: + success = false; + break; + } + + pr_debug("%s: %s\n", success ? "success" : "failed", + dp_link_get_phy_test_pattern(pattern_requested)); +} + +static void dp_ctrl_mst_calculate_rg(struct dp_ctrl_private *ctrl, + struct dp_panel *panel, u32 *p_x_int, u32 *p_y_frac_enum) +{ + u64 min_slot_cnt, max_slot_cnt; + u64 raw_target_sc, target_sc_fixp; + u64 ts_denom, ts_enum, ts_int; + u64 pclk = panel->pinfo.pixel_clk_khz; + u64 lclk = panel->link_info.rate; + u64 lanes = panel->link_info.num_lanes; + u64 bpp = panel->pinfo.bpp; + u64 pbn = panel->pbn; + u64 numerator, denominator, temp, temp1, temp2; + u32 x_int = 0, y_frac_enum = 0; + u64 target_strm_sym, ts_int_fixp, ts_frac_fixp, y_frac_enum_fixp; + + /* min_slot_cnt */ + numerator = pclk * bpp * 64 * 1000; + denominator = lclk * lanes * 8 * 1000; + min_slot_cnt = drm_fixp_from_fraction(numerator, denominator); + + /* max_slot_cnt */ + numerator = pbn * 54 * 1000; + denominator = lclk * lanes; + max_slot_cnt = drm_fixp_from_fraction(numerator, denominator); + + /* raw_target_sc */ + numerator = max_slot_cnt + min_slot_cnt; + denominator = drm_fixp_from_fraction(2, 1); + raw_target_sc = drm_fixp_div(numerator, denominator); + + /* target_sc */ + temp = drm_fixp_from_fraction(256 * lanes, 1); + numerator = drm_fixp_mul(raw_target_sc, temp); + denominator = drm_fixp_from_fraction(256 * lanes, 1); + target_sc_fixp = drm_fixp_div(numerator, denominator); + + ts_enum = 256 * lanes; + ts_denom = drm_fixp_from_fraction(256 * lanes, 1); + ts_int = drm_fixp2int(target_sc_fixp); + + temp = drm_fixp2int_ceil(raw_target_sc); + if (temp != ts_int) { + temp = drm_fixp_from_fraction(ts_int, 1); + temp1 = raw_target_sc - temp; + temp2 = drm_fixp_mul(temp1, ts_denom); + ts_enum = drm_fixp2int(temp2); + } + + /* target_strm_sym */ + ts_int_fixp = drm_fixp_from_fraction(ts_int, 1); + ts_frac_fixp = drm_fixp_from_fraction(ts_enum, drm_fixp2int(ts_denom)); + temp = ts_int_fixp + ts_frac_fixp; + temp1 = drm_fixp_from_fraction(lanes, 1); + target_strm_sym = drm_fixp_mul(temp, temp1); + + /* x_int */ + x_int = drm_fixp2int(target_strm_sym); + + /* y_enum_frac */ + temp = drm_fixp_from_fraction(x_int, 1); + temp1 = target_strm_sym - temp; + temp2 = drm_fixp_from_fraction(256, 1); + y_frac_enum_fixp = drm_fixp_mul(temp1, temp2); + + temp1 = drm_fixp2int(y_frac_enum_fixp); + temp2 = drm_fixp2int_ceil(y_frac_enum_fixp); + + y_frac_enum = (u32)((temp1 == temp2) ? temp1 : temp1 + 1); + + *p_x_int = x_int; + *p_y_frac_enum = y_frac_enum; + + pr_debug("x_int: %d, y_frac_enum: %d\n", x_int, y_frac_enum); +} + +static int dp_ctrl_mst_send_act(struct dp_ctrl_private *ctrl) +{ + bool act_complete; + + if (!ctrl->mst_mode) + return 0; + + ctrl->catalog->trigger_act(ctrl->catalog); + msleep(20); /* needs 1 frame time */ + + ctrl->catalog->read_act_complete_sts(ctrl->catalog, &act_complete); + + if (!act_complete) + pr_err("mst act trigger complete failed\n"); + else + DP_MST_DEBUG("mst ACT trigger complete SUCCESS\n"); + + return 0; +} + +static void dp_ctrl_mst_stream_setup(struct dp_ctrl_private *ctrl, + struct dp_panel *panel) +{ + u32 x_int, y_frac_enum, lanes, bw_code; + int i; + + if (!ctrl->mst_mode) + return; + + DP_MST_DEBUG("mst stream channel allocation\n"); + + for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) { + ctrl->catalog->channel_alloc(ctrl->catalog, + i, + ctrl->mst_ch_info.slot_info[i].start_slot, + ctrl->mst_ch_info.slot_info[i].tot_slots); + } + + lanes = ctrl->link->link_params.lane_count; + bw_code = ctrl->link->link_params.bw_code; + + dp_ctrl_mst_calculate_rg(ctrl, panel, &x_int, &y_frac_enum); + + ctrl->catalog->update_rg(ctrl->catalog, panel->stream_id, + x_int, y_frac_enum); + + DP_MST_DEBUG("mst stream:%d, start_slot:%d, tot_slots:%d\n", + panel->stream_id, + panel->channel_start_slot, panel->channel_total_slots); + + DP_MST_DEBUG("mst lane_cnt:%d, bw:%d, x_int:%d, y_frac:%d\n", + lanes, bw_code, x_int, y_frac_enum); +} + +static int dp_ctrl_stream_on(struct dp_ctrl *dp_ctrl, struct dp_panel *panel) +{ + int rc = 0; + bool link_ready = false; + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl || !panel) + return -EINVAL; + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + rc = dp_ctrl_enable_stream_clocks(ctrl, panel); + if (rc) { + pr_err("failure on stream clock enable\n"); + return rc; + } + + if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { + dp_ctrl_send_phy_test_pattern(ctrl); + return 0; + } + + rc = panel->hw_cfg(panel, true); + if (rc) + return rc; + + dp_ctrl_mst_stream_setup(ctrl, panel); + + dp_ctrl_send_video(ctrl); + + dp_ctrl_mst_send_act(ctrl); + + dp_ctrl_wait4video_ready(ctrl); + + ctrl->stream_count++; + + link_ready = ctrl->catalog->mainlink_ready(ctrl->catalog); + pr_debug("mainlink %s\n", link_ready ? "READY" : "NOT READY"); + + return rc; +} + +static void dp_ctrl_mst_stream_pre_off(struct dp_ctrl *dp_ctrl, + struct dp_panel *panel) +{ + struct dp_ctrl_private *ctrl; + bool act_complete; + int i; + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + if (!ctrl->mst_mode) + return; + + for (i = DP_STREAM_0; i < DP_STREAM_MAX; i++) { + ctrl->catalog->channel_alloc(ctrl->catalog, + i, + ctrl->mst_ch_info.slot_info[i].start_slot, + ctrl->mst_ch_info.slot_info[i].tot_slots); + } + + ctrl->catalog->trigger_act(ctrl->catalog); + msleep(20); /* needs 1 frame time */ + ctrl->catalog->read_act_complete_sts(ctrl->catalog, &act_complete); + + if (!act_complete) + pr_err("mst stream_off act trigger complete failed\n"); + else + DP_MST_DEBUG("mst stream_off ACT trigger complete SUCCESS\n"); +} + +static void dp_ctrl_stream_pre_off(struct dp_ctrl *dp_ctrl, + struct dp_panel *panel) +{ + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl || !panel) { + pr_err("invalid input\n"); + return; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + dp_ctrl_push_idle(ctrl, panel->stream_id); + + dp_ctrl_mst_stream_pre_off(dp_ctrl, panel); +} + +static void dp_ctrl_stream_off(struct dp_ctrl *dp_ctrl, struct dp_panel *panel) +{ + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl || !panel) + return; + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + if (!ctrl->power_on) + return; + + panel->hw_cfg(panel, false); + + dp_ctrl_disable_stream_clocks(ctrl, panel); + ctrl->stream_count--; +} + +static int dp_ctrl_on(struct dp_ctrl *dp_ctrl, bool mst_mode, bool shallow) +{ + int rc = 0; + struct dp_ctrl_private *ctrl; + u32 rate = 0; + + if (!dp_ctrl) { + rc = -EINVAL; + goto end; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + if (ctrl->power_on) + goto end; + + ctrl->mst_mode = mst_mode; + rate = ctrl->panel->link_info.rate; + + if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { + pr_debug("using phy test link parameters\n"); + } 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; + } + + pr_debug("bw_code=%d, lane_count=%d\n", + ctrl->link->link_params.bw_code, + ctrl->link->link_params.lane_count); + + rc = dp_ctrl_link_setup(ctrl, shallow); + /* Ignore errors in case of shallow processing */ + if (!shallow && rc) + goto end; + + ctrl->power_on = true; +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); + + if (!ctrl->power_on) + return; + + dp_ctrl_configure_source_link_params(ctrl, false); + ctrl->catalog->reset(ctrl->catalog); + + /* Make sure DP is disabled before clk disable */ + wmb(); + + dp_ctrl_disable_link_clock(ctrl); + + ctrl->mst_mode = false; + ctrl->power_on = false; + memset(&ctrl->mst_ch_info, 0, sizeof(ctrl->mst_ch_info)); + pr_debug("DP off done\n"); +} + +static void dp_ctrl_set_mst_channel_info(struct dp_ctrl *dp_ctrl, + enum dp_stream_id strm, + u32 start_slot, u32 tot_slots) +{ + struct dp_ctrl_private *ctrl; + + if (!dp_ctrl || strm >= DP_STREAM_MAX) { + pr_err("invalid input\n"); + return; + } + + ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl); + + ctrl->mst_ch_info.slot_info[strm].start_slot = start_slot; + ctrl->mst_ch_info.slot_info[strm].tot_slots = tot_slots; +} + +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); + + if (ctrl->catalog->isr5 & DP_CTRL_INTR_MST_DP0_VCPF_SENT) + dp_ctrl_idle_patterns_sent(ctrl); + + if (ctrl->catalog->isr5 & DP_CTRL_INTR_MST_DP1_VCPF_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; + ctrl->dev = in->dev; + ctrl->mst_mode = false; + + 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->abort = dp_ctrl_abort; + dp_ctrl->isr = dp_ctrl_isr; + dp_ctrl->link_maintenance = dp_ctrl_link_maintenance; + dp_ctrl->process_phy_test_request = dp_ctrl_process_phy_test_request; + dp_ctrl->stream_on = dp_ctrl_stream_on; + dp_ctrl->stream_off = dp_ctrl_stream_off; + dp_ctrl->stream_pre_off = dp_ctrl_stream_pre_off; + dp_ctrl->set_mst_channel_info = dp_ctrl_set_mst_channel_info; + + 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..e74051d2e8bffc25940fb8a5eece40262658337d --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_ctrl.h @@ -0,0 +1,47 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#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, bool reset); + void (*deinit)(struct dp_ctrl *dp_ctrl); + int (*on)(struct dp_ctrl *dp_ctrl, bool mst_mode, bool shallow); + void (*off)(struct dp_ctrl *dp_ctrl); + void (*abort)(struct dp_ctrl *dp_ctrl); + void (*isr)(struct dp_ctrl *dp_ctrl); + bool (*handle_sink_request)(struct dp_ctrl *dp_ctrl); + void (*process_phy_test_request)(struct dp_ctrl *dp_ctrl); + int (*link_maintenance)(struct dp_ctrl *dp_ctrl); + int (*stream_on)(struct dp_ctrl *dp_ctrl, struct dp_panel *panel); + void (*stream_off)(struct dp_ctrl *dp_ctrl, struct dp_panel *panel); + void (*stream_pre_off)(struct dp_ctrl *dp_ctrl, struct dp_panel *panel); + void (*set_mst_channel_info)(struct dp_ctrl *dp_ctrl, + enum dp_stream_id strm, + u32 ch_start_slot, u32 ch_tot_slots); +}; + +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..fb2d89a93a1f6f47e60f86ce276a71e15e5ba4f3 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_debug.c @@ -0,0 +1,1908 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include + +#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 "sde_connector.h" +#include "dp_display.h" + +#define DEBUG_NAME "drm_dp" + +struct dp_debug_private { + struct dentry *root; + u8 *edid; + u32 edid_size; + + u8 *dpcd; + u32 dpcd_size; + + u32 mst_con_id; + + char exe_mode[SZ_32]; + char reg_dump[SZ_32]; + + struct dp_hpd *hpd; + struct dp_link *link; + struct dp_panel *panel; + struct dp_aux *aux; + struct dp_catalog *catalog; + struct drm_connector **connector; + struct device *dev; + struct dp_debug dp_debug; + struct dp_parser *parser; +}; + +static int dp_debug_get_edid_buf(struct dp_debug_private *debug) +{ + int rc = 0; + + if (!debug->edid) { + debug->edid = devm_kzalloc(debug->dev, SZ_256, GFP_KERNEL); + if (!debug->edid) { + rc = -ENOMEM; + goto end; + } + + debug->edid_size = SZ_256; + } +end: + return rc; +} + +static int dp_debug_get_dpcd_buf(struct dp_debug_private *debug) +{ + int rc = 0; + + if (!debug->dpcd) { + debug->dpcd = devm_kzalloc(debug->dev, SZ_16K, GFP_KERNEL); + if (!debug->dpcd) { + rc = -ENOMEM; + goto end; + } + + debug->dpcd_size = SZ_16K; + } +end: + return rc; +} + +static ssize_t dp_debug_write_edid(struct file *file, + const char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + u8 *buf = NULL, *buf_t = NULL, *edid = NULL; + const int char_to_nib = 2; + size_t edid_size = 0; + size_t size = 0, edid_buf_index = 0; + ssize_t rc = count; + + if (!debug) + return -ENODEV; + + if (*ppos) + goto bail; + + size = min_t(size_t, count, SZ_1K); + + buf = kzalloc(size, GFP_KERNEL); + if (ZERO_OR_NULL_PTR(buf)) { + rc = -ENOMEM; + goto bail; + } + + if (copy_from_user(buf, user_buff, size)) + goto bail; + + edid_size = size / char_to_nib; + buf_t = buf; + + if (dp_debug_get_edid_buf(debug)) + goto bail; + + if (edid_size != debug->edid_size) { + pr_debug("realloc debug edid\n"); + + if (debug->edid) { + devm_kfree(debug->dev, debug->edid); + + debug->edid = devm_kzalloc(debug->dev, + edid_size, GFP_KERNEL); + if (!debug->edid) { + rc = -ENOMEM; + goto bail; + } + + debug->edid_size = edid_size; + + debug->aux->set_sim_mode(debug->aux, + debug->dp_debug.sim_mode, + debug->edid, debug->dpcd); + } + } + + while (edid_size--) { + char t[3]; + int d; + + memcpy(t, buf_t, sizeof(char) * char_to_nib); + t[char_to_nib] = '\0'; + + if (kstrtoint(t, 16, &d)) { + pr_err("kstrtoint error\n"); + goto bail; + } + + if (debug->edid && (edid_buf_index < debug->edid_size)) + debug->edid[edid_buf_index++] = d; + + buf_t += char_to_nib; + } + + edid = debug->edid; +bail: + kfree(buf); + + if (!debug->dp_debug.sim_mode) + debug->panel->set_edid(debug->panel, edid); + + return rc; +} + +static ssize_t dp_debug_write_dpcd(struct file *file, + const char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + u8 *buf = NULL, *buf_t = NULL, *dpcd = NULL; + const int char_to_nib = 2; + size_t dpcd_size = 0; + size_t size = 0, dpcd_buf_index = 0; + ssize_t rc = count; + char offset_ch[5]; + u32 offset; + + if (!debug) + return -ENODEV; + + if (*ppos) + goto bail; + + size = min_t(size_t, count, SZ_2K); + + buf = kzalloc(size, GFP_KERNEL); + if (ZERO_OR_NULL_PTR(buf)) { + rc = -ENOMEM; + goto bail; + } + + if (copy_from_user(buf, user_buff, size)) + goto bail; + + memcpy(offset_ch, buf, 4); + offset_ch[4] = '\0'; + + if (kstrtoint(offset_ch, 16, &offset)) { + pr_err("offset kstrtoint error\n"); + goto bail; + } + + if (dp_debug_get_dpcd_buf(debug)) + goto bail; + + if (offset == 0xFFFF) { + pr_err("clearing dpcd\n"); + memset(debug->dpcd, 0, debug->dpcd_size); + goto bail; + } + + size -= 4; + + dpcd_size = size / char_to_nib; + buf_t = buf + 4; + + dpcd_buf_index = offset; + + while (dpcd_size--) { + char t[3]; + int d; + + memcpy(t, buf_t, sizeof(char) * char_to_nib); + t[char_to_nib] = '\0'; + + if (kstrtoint(t, 16, &d)) { + pr_err("kstrtoint error\n"); + goto bail; + } + + if (dpcd_buf_index < debug->dpcd_size) + debug->dpcd[dpcd_buf_index++] = d; + + buf_t += char_to_nib; + } + + dpcd = debug->dpcd; +bail: + kfree(buf); + if (debug->dp_debug.sim_mode) + debug->aux->dpcd_updated(debug->aux); + else + debug->panel->set_dpcd(debug->panel, dpcd); + + return rc; +} + +static ssize_t dp_debug_read_dpcd(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, "0x%x\n", debug->aux->reg); + + if (copy_to_user(user_buff, buf, len)) + return -EFAULT; + + *ppos += len; + return len; +} + +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 const hpd_data_mask = 0x7; + int hpd = 0; + + 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; + + hpd &= hpd_data_mask; + + debug->dp_debug.psm_enabled = !!(hpd & BIT(1)); + + debug->hpd->simulate_connect(debug->hpd, !!(hpd & BIT(0))); +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, aspect_ratio; + + 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 %d", &hdisplay, &vdisplay, &vrefresh, + &aspect_ratio) != 4) + 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; + debug->dp_debug.aspect_ratio = aspect_ratio; + goto end; +clear: + pr_debug("clearing debug modes\n"); + debug->dp_debug.debug_en = false; +end: + return len; +} + +static ssize_t dp_debug_write_edid_modes_mst(struct file *file, + const char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + struct dp_mst_connector *mst_connector; + char buf[SZ_32]; + char *read_buf; + size_t len = 0; + + int hdisplay = 0, vdisplay = 0, vrefresh = 0, aspect_ratio = 0; + int con_id = 0, offset = 0, debug_en = 0; + bool in_list = false; + + if (!debug) + return -ENODEV; + + if (*ppos) + goto end; + + len = min_t(size_t, count, SZ_32 - 1); + if (copy_from_user(buf, user_buff, len)) + goto end; + + buf[len] = '\0'; + read_buf = buf; + + mutex_lock(&debug->dp_debug.dp_mst_connector_list.lock); + while (sscanf(read_buf, "%d %d %d %d %d %d%n", &debug_en, &con_id, + &hdisplay, &vdisplay, &vrefresh, &aspect_ratio, + &offset) == 6) { + list_for_each_entry(mst_connector, + &debug->dp_debug.dp_mst_connector_list.list, + list) { + if (mst_connector->con_id == con_id) { + in_list = true; + mst_connector->debug_en = (bool) debug_en; + mst_connector->hdisplay = hdisplay; + mst_connector->vdisplay = vdisplay; + mst_connector->vrefresh = vrefresh; + mst_connector->aspect_ratio = aspect_ratio; + } + } + + if (!in_list) + pr_debug("dp connector id %d is invalid\n", con_id); + + in_list = false; + read_buf += offset; + } + mutex_unlock(&debug->dp_debug.dp_mst_connector_list.lock); +end: + return len; +} + +static ssize_t dp_debug_write_mst_con_id(struct file *file, + const char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + struct dp_mst_connector *mst_connector; + char buf[SZ_32]; + size_t len = 0; + int con_id = 0; + bool in_list = false; + + 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 (kstrtoint(buf, 10, &con_id) != 0) + goto clear; + + if (!con_id) + goto clear; + + /* Verify that the connector id is for a valid mst connector. */ + mutex_lock(&debug->dp_debug.dp_mst_connector_list.lock); + list_for_each_entry(mst_connector, + &debug->dp_debug.dp_mst_connector_list.list, list) { + if (mst_connector->con_id == con_id) { + in_list = true; + debug->mst_con_id = con_id; + break; + } + } + mutex_unlock(&debug->dp_debug.dp_mst_connector_list.lock); + + if (!in_list) + pr_err("invalid connector id %u\n", con_id); + + goto end; +clear: + pr_debug("clearing mst_con_id\n"); + debug->mst_con_id = 0; +end: + return len; +} + +static ssize_t dp_debug_bw_code_write(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; + u32 max_bw_code = 0; + + 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)) + return 0; + + buf[len] = '\0'; + + if (kstrtoint(buf, 10, &max_bw_code) != 0) + return 0; + + if (!is_link_rate_valid(max_bw_code)) { + pr_err("Unsupported bw code %d\n", max_bw_code); + return len; + } + debug->panel->max_bw_code = max_bw_code; + pr_debug("max_bw_code: %d\n", max_bw_code); + + return len; +} + +static ssize_t dp_debug_mst_mode_write(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; + u32 mst_mode = 0; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + len = min_t(size_t, count, SZ_8 - 1); + if (copy_from_user(buf, user_buff, len)) + return 0; + + buf[len] = '\0'; + + if (kstrtoint(buf, 10, &mst_mode) != 0) + return 0; + + debug->parser->has_mst = mst_mode ? true : false; + pr_debug("mst_enable: %d\n", mst_mode); + + return len; +} + +static ssize_t dp_debug_max_pclk_khz_write(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; + u32 max_pclk = 0; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + len = min_t(size_t, count, SZ_8 - 1); + if (copy_from_user(buf, user_buff, len)) + return 0; + + buf[len] = '\0'; + + if (kstrtoint(buf, 10, &max_pclk) != 0) + return 0; + + if (max_pclk > debug->parser->max_pclk_khz) + pr_err("requested: %d, max_pclk_khz:%d\n", max_pclk, + debug->parser->max_pclk_khz); + else + debug->dp_debug.max_pclk_khz = max_pclk; + + pr_debug("max_pclk_khz: %d\n", max_pclk); + + return len; +} + +static ssize_t dp_debug_max_pclk_khz_read(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; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + buf = kzalloc(SZ_4K, GFP_KERNEL); + if (ZERO_OR_NULL_PTR(buf)) + return -ENOMEM; + + len += snprintf(buf + len, (SZ_4K - len), + "max_pclk_khz = %d, org: %d\n", + debug->dp_debug.max_pclk_khz, + debug->parser->max_pclk_khz); + + if (copy_to_user(user_buff, buf, len)) { + kfree(buf); + return -EFAULT; + } + + *ppos += len; + kfree(buf); + return len; +} + +static ssize_t dp_debug_mst_sideband_mode_write(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; + u32 mst_sideband_mode = 0; + + 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)) + return 0; + + buf[len] = '\0'; + + if (kstrtoint(buf, 10, &mst_sideband_mode) != 0) + return 0; + + debug->parser->has_mst_sideband = mst_sideband_mode ? true : false; + pr_debug("mst_enable: %d\n", mst_sideband_mode); + + return len; +} + +static ssize_t dp_debug_tpg_write(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; + u32 tpg_state = 0; + + 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 bail; + + buf[len] = '\0'; + + if (kstrtoint(buf, 10, &tpg_state) != 0) + goto bail; + + tpg_state &= 0x1; + pr_debug("tpg_state: %d\n", tpg_state); + + if (tpg_state == debug->dp_debug.tpg_state) + goto bail; + + if (debug->panel) + debug->panel->tpg_config(debug->panel, tpg_state); + + debug->dp_debug.tpg_state = tpg_state; +bail: + return len; +} + +static ssize_t dp_debug_write_exe_mode(struct file *file, + const char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + char *buf; + size_t len = 0; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + len = min_t(size_t, count, SZ_32 - 1); + buf = memdup_user(user_buff, len); + buf[len] = '\0'; + + if (sscanf(buf, "%3s", debug->exe_mode) != 1) + goto end; + + if (strcmp(debug->exe_mode, "hw") && + strcmp(debug->exe_mode, "sw") && + strcmp(debug->exe_mode, "all")) + goto end; + + debug->catalog->set_exe_mode(debug->catalog, debug->exe_mode); +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->hpd->hpd_high); + + if (copy_to_user(user_buff, buf, len)) + return -EFAULT; + + *ppos += len; + return len; +} + +static ssize_t dp_debug_write_hdcp(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 hdcp = 0; + + 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, &hdcp) != 0) + goto end; + + debug->dp_debug.hdcp_disabled = !hdcp; +end: + return len; +} + +static ssize_t dp_debug_read_hdcp(struct file *file, + char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + u32 len = 0; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + len = sizeof(debug->dp_debug.hdcp_status); + + if (copy_to_user(user_buff, debug->dp_debug.hdcp_status, len)) + return -EFAULT; + + *ppos += len; + return len; +} + +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_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, ret = 0, max_size = SZ_4K; + 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 (ZERO_OR_NULL_PTR(buf)) { + rc = -ENOMEM; + goto error; + } + + mutex_lock(&connector->dev->mode_config.mutex); + list_for_each_entry(mode, &connector->modes, head) { + ret = snprintf(buf + len, max_size, + "%s %d %d %d %d %d 0x%x\n", + mode->name, mode->vrefresh, mode->picture_aspect_ratio, + mode->htotal, mode->vtotal, mode->clock, mode->flags); + if (dp_debug_check_buffer_overflow(ret, &max_size, &len)) + break; + } + mutex_unlock(&connector->dev->mode_config.mutex); + + if (copy_to_user(user_buff, buf, len)) { + kfree(buf); + rc = -EFAULT; + goto error; + } + + *ppos += len; + kfree(buf); + + return len; +error: + return rc; +} + +static ssize_t dp_debug_read_edid_modes_mst(struct file *file, + char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + struct dp_mst_connector *mst_connector; + char *buf; + u32 len = 0, ret = 0, max_size = SZ_4K; + int rc = 0; + struct drm_connector *connector; + struct drm_display_mode *mode; + bool in_list = false; + + if (!debug) { + pr_err("invalid data\n"); + rc = -ENODEV; + goto error; + } + + mutex_lock(&debug->dp_debug.dp_mst_connector_list.lock); + list_for_each_entry(mst_connector, + &debug->dp_debug.dp_mst_connector_list.list, list) { + if (mst_connector->con_id == debug->mst_con_id) { + connector = mst_connector->conn; + in_list = true; + } + } + mutex_unlock(&debug->dp_debug.dp_mst_connector_list.lock); + + if (!in_list) { + pr_err("connector %u not in mst list\n", debug->mst_con_id); + rc = -EINVAL; + goto error; + } + + 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; + } + + mutex_lock(&connector->dev->mode_config.mutex); + list_for_each_entry(mode, &connector->modes, head) { + ret = snprintf(buf + len, max_size, + "%s %d %d %d %d %d 0x%x\n", + mode->name, mode->vrefresh, + mode->picture_aspect_ratio, mode->htotal, + mode->vtotal, mode->clock, mode->flags); + if (dp_debug_check_buffer_overflow(ret, &max_size, &len)) + break; + } + mutex_unlock(&connector->dev->mode_config.mutex); + + if (copy_to_user(user_buff, buf, len)) { + kfree(buf); + rc = -EFAULT; + goto error; + } + + *ppos += len; + kfree(buf); + + return len; +error: + return rc; +} + +static ssize_t dp_debug_read_mst_con_id(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, ret = 0, max_size = SZ_4K; + int rc = 0; + + if (!debug) { + pr_err("invalid data\n"); + rc = -ENODEV; + goto error; + } + + if (*ppos) + goto error; + + buf = kzalloc(SZ_4K, GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; + goto error; + } + + ret = snprintf(buf, max_size, "%u\n", debug->mst_con_id); + len += ret; + + if (copy_to_user(user_buff, buf, len)) { + kfree(buf); + rc = -EFAULT; + goto error; + } + + *ppos += len; + kfree(buf); + + return len; +error: + return rc; +} + +static ssize_t dp_debug_read_mst_conn_info(struct file *file, + char __user *user_buff, size_t count, loff_t *ppos) +{ + struct dp_debug_private *debug = file->private_data; + struct dp_mst_connector *mst_connector; + char *buf; + u32 len = 0, ret = 0, max_size = SZ_4K; + int rc = 0; + struct drm_connector *connector; + + if (!debug) { + pr_err("invalid data\n"); + rc = -ENODEV; + goto error; + } + + if (*ppos) + goto error; + + buf = kzalloc(SZ_4K, GFP_KERNEL); + if (!buf) { + rc = -ENOMEM; + goto error; + } + + mutex_lock(&debug->dp_debug.dp_mst_connector_list.lock); + list_for_each_entry(mst_connector, + &debug->dp_debug.dp_mst_connector_list.list, list) { + /* Do not print info for head node */ + if (mst_connector->con_id == -1) + continue; + + connector = mst_connector->conn; + + if (!connector) { + pr_err("connector for id %d is NULL\n", + mst_connector->con_id); + continue; + } + + ret = snprintf(buf + len, max_size, + "conn name:%s, conn id:%d\n", connector->name, + connector->base.id); + if (dp_debug_check_buffer_overflow(ret, &max_size, &len)) + break; + } + mutex_unlock(&debug->dp_debug.dp_mst_connector_list.lock); + + if (copy_to_user(user_buff, buf, len)) { + kfree(buf); + rc = -EFAULT; + goto error; + } + + *ppos += len; + kfree(buf); + + return len; +error: + return rc; +} + +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; + u32 max_size = SZ_4K; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + buf = kzalloc(SZ_4K, GFP_KERNEL); + if (ZERO_OR_NULL_PTR(buf)) + return -ENOMEM; + + rc = snprintf(buf + len, max_size, "\tstate=0x%x\n", debug->aux->state); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "\tlink_rate=%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, "\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, "\tresolution=%dx%d@%dHz\n", + debug->panel->pinfo.h_active, + debug->panel->pinfo.v_active, + debug->panel->pinfo.refresh_rate); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "\tpclock=%dKHz\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, "\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, "\ttest_req=%s\n", + dp_link_get_test_name(debug->link->sink_request)); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\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, + "\tbw_code=%d\n", debug->link->link_params.bw_code); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "\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, + "\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 ssize_t dp_debug_bw_code_read(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; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + buf = kzalloc(SZ_4K, GFP_KERNEL); + if (ZERO_OR_NULL_PTR(buf)) + return -ENOMEM; + + len += snprintf(buf + len, (SZ_4K - len), + "max_bw_code = %d\n", debug->panel->max_bw_code); + + if (copy_to_user(user_buff, buf, len)) { + kfree(buf); + return -EFAULT; + } + + *ppos += len; + kfree(buf); + return len; +} + +static ssize_t dp_debug_tpg_read(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->dp_debug.tpg_state); + + if (copy_to_user(user_buff, buf, len)) + return -EFAULT; + + *ppos += len; + return len; +} + +static ssize_t dp_debug_write_hdr(struct file *file, + const char __user *user_buff, size_t count, loff_t *ppos) +{ + struct drm_connector *connector; + struct sde_connector *c_conn; + struct sde_connector_state *c_state; + struct dp_debug_private *debug = file->private_data; + char buf[SZ_512]; + size_t len = 0; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + connector = *debug->connector; + c_conn = to_sde_connector(connector); + c_state = to_sde_connector_state(connector->state); + + /* Leave room for termination char */ + len = min_t(size_t, count, SZ_512 - 1); + if (copy_from_user(buf, user_buff, len)) + goto end; + + buf[len] = '\0'; + + if (sscanf(buf, "%x %x %x %x %x %x %x %x %x %x %x %x %x %x %x", + &c_state->hdr_meta.hdr_supported, + &c_state->hdr_meta.hdr_state, + &c_state->hdr_meta.eotf, + &c_state->hdr_meta.display_primaries_x[0], + &c_state->hdr_meta.display_primaries_x[1], + &c_state->hdr_meta.display_primaries_x[2], + &c_state->hdr_meta.display_primaries_y[0], + &c_state->hdr_meta.display_primaries_y[1], + &c_state->hdr_meta.display_primaries_y[2], + &c_state->hdr_meta.white_point_x, + &c_state->hdr_meta.white_point_y, + &c_state->hdr_meta.max_luminance, + &c_state->hdr_meta.min_luminance, + &c_state->hdr_meta.max_content_light_level, + &c_state->hdr_meta.max_average_light_level) != 15) { + pr_err("invalid input\n"); + len = -EINVAL; + } + + debug->panel->setup_hdr(debug->panel, &c_state->hdr_meta); +end: + return len; +} + +static ssize_t dp_debug_read_hdr(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, i; + u32 max_size = SZ_4K; + int rc = 0; + struct drm_connector *connector; + struct sde_connector *c_conn; + struct sde_connector_state *c_state; + struct drm_msm_ext_hdr_metadata *hdr; + + 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 (ZERO_OR_NULL_PTR(buf)) { + rc = -ENOMEM; + goto error; + } + + c_conn = to_sde_connector(connector); + c_state = to_sde_connector_state(connector->state); + + hdr = &c_state->hdr_meta; + + rc = snprintf(buf + len, max_size, + "============SINK HDR PARAMETERS===========\n"); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "eotf = %d\n", + connector->hdr_eotf); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "type_one = %d\n", + connector->hdr_metadata_type_one); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "max_luminance = %d\n", + connector->hdr_max_luminance); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "avg_luminance = %d\n", + connector->hdr_avg_luminance); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "min_luminance = %d\n", + connector->hdr_min_luminance); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, + "============VIDEO HDR PARAMETERS===========\n"); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "hdr_state = %d\n", hdr->hdr_state); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "hdr_supported = %d\n", + hdr->hdr_supported); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "eotf = %d\n", hdr->eotf); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "white_point_x = %d\n", + hdr->white_point_x); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "white_point_y = %d\n", + hdr->white_point_y); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "max_luminance = %d\n", + hdr->max_luminance); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "min_luminance = %d\n", + hdr->min_luminance); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "max_content_light_level = %d\n", + hdr->max_content_light_level); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "min_content_light_level = %d\n", + hdr->max_average_light_level); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + for (i = 0; i < HDR_PRIMARIES_COUNT; i++) { + rc = snprintf(buf + len, max_size, "primaries_x[%d] = %d\n", + i, hdr->display_primaries_x[i]); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + + rc = snprintf(buf + len, max_size, "primaries_y[%d] = %d\n", + i, hdr->display_primaries_y[i]); + if (dp_debug_check_buffer_overflow(rc, &max_size, &len)) + goto error; + } + + if (copy_to_user(user_buff, buf, len)) { + kfree(buf); + rc = -EFAULT; + goto error; + } + + *ppos += len; + kfree(buf); + + return len; +error: + return rc; +} + +static ssize_t dp_debug_write_sim(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 sim; + + 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, &sim) != 0) + goto end; + + if (sim) { + if (dp_debug_get_edid_buf(debug)) + goto end; + + if (dp_debug_get_dpcd_buf(debug)) + goto error; + + debug->dp_debug.sim_mode = true; + debug->aux->set_sim_mode(debug->aux, true, + debug->edid, debug->dpcd); + } else { + debug->aux->set_sim_mode(debug->aux, false, NULL, NULL); + debug->dp_debug.sim_mode = false; + + if (debug->edid) { + devm_kfree(debug->dev, debug->edid); + debug->edid = NULL; + } + + if (debug->dpcd) { + devm_kfree(debug->dev, debug->dpcd); + debug->dpcd = NULL; + } + } +end: + return len; +error: + devm_kfree(debug->dev, debug->edid); + return len; +} + +static ssize_t dp_debug_write_attention(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 vdo; + + 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, &vdo) != 0) + goto end; + + debug->hpd->simulate_attention(debug->hpd, vdo); +end: + return len; +} + +static ssize_t dp_debug_write_dump(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; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + /* Leave room for termination char */ + len = min_t(size_t, count, SZ_32 - 1); + if (copy_from_user(buf, user_buff, len)) + goto end; + + buf[len] = '\0'; + + if (sscanf(buf, "%31s", debug->reg_dump) != 1) + goto end; + + /* qfprom register dump not supported */ + if (!strcmp(debug->reg_dump, "qfprom_physical")) + strlcpy(debug->reg_dump, "clear", sizeof(debug->reg_dump)); +end: + return len; +} + +static ssize_t dp_debug_read_dump(struct file *file, + char __user *user_buff, size_t count, loff_t *ppos) +{ + int rc = 0; + struct dp_debug_private *debug = file->private_data; + u8 *buf = NULL; + u32 len = 0; + char prefix[SZ_32]; + + if (!debug) + return -ENODEV; + + if (*ppos) + return 0; + + if (!debug->hpd->hpd_high || !strlen(debug->reg_dump)) + goto end; + + rc = debug->catalog->get_reg_dump(debug->catalog, + debug->reg_dump, &buf, &len); + if (rc) + goto end; + + snprintf(prefix, sizeof(prefix), "%s: ", debug->reg_dump); + print_hex_dump(KERN_DEBUG, prefix, DUMP_PREFIX_NONE, + 16, 4, buf, len, false); + + if (copy_to_user(user_buff, buf, len)) + return -EFAULT; + + *ppos += len; +end: + return len; +} + +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 edid_modes_mst_fops = { + .open = simple_open, + .read = dp_debug_read_edid_modes_mst, + .write = dp_debug_write_edid_modes_mst, +}; + +static const struct file_operations mst_conn_info_fops = { + .open = simple_open, + .read = dp_debug_read_mst_conn_info, +}; + +static const struct file_operations mst_con_id_fops = { + .open = simple_open, + .read = dp_debug_read_mst_con_id, + .write = dp_debug_write_mst_con_id, +}; + +static const struct file_operations hpd_fops = { + .open = simple_open, + .write = dp_debug_write_hpd, +}; + +static const struct file_operations edid_fops = { + .open = simple_open, + .write = dp_debug_write_edid, +}; + +static const struct file_operations dpcd_fops = { + .open = simple_open, + .write = dp_debug_write_dpcd, + .read = dp_debug_read_dpcd, +}; + +static const struct file_operations connected_fops = { + .open = simple_open, + .read = dp_debug_read_connected, +}; + +static const struct file_operations bw_code_fops = { + .open = simple_open, + .read = dp_debug_bw_code_read, + .write = dp_debug_bw_code_write, +}; +static const struct file_operations exe_mode_fops = { + .open = simple_open, + .write = dp_debug_write_exe_mode, +}; + +static const struct file_operations tpg_fops = { + .open = simple_open, + .read = dp_debug_tpg_read, + .write = dp_debug_tpg_write, +}; + +static const struct file_operations hdr_fops = { + .open = simple_open, + .write = dp_debug_write_hdr, + .read = dp_debug_read_hdr, +}; + +static const struct file_operations sim_fops = { + .open = simple_open, + .write = dp_debug_write_sim, +}; + +static const struct file_operations attention_fops = { + .open = simple_open, + .write = dp_debug_write_attention, +}; + +static const struct file_operations dump_fops = { + .open = simple_open, + .write = dp_debug_write_dump, + .read = dp_debug_read_dump, +}; + +static const struct file_operations mst_mode_fops = { + .open = simple_open, + .write = dp_debug_mst_mode_write, +}; + +static const struct file_operations mst_sideband_mode_fops = { + .open = simple_open, + .write = dp_debug_mst_sideband_mode_write, +}; + +static const struct file_operations max_pclk_khz_fops = { + .open = simple_open, + .write = dp_debug_max_pclk_khz_write, + .read = dp_debug_max_pclk_khz_read, +}; + +static const struct file_operations hdcp_fops = { + .open = simple_open, + .write = dp_debug_write_hdcp, + .read = dp_debug_read_hdcp, +}; + +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; + + dir = debugfs_create_dir(DEBUG_NAME, NULL); + if (IS_ERR_OR_NULL(dir)) { + if (!dir) + rc = -EINVAL; + else + rc = PTR_ERR(dir); + pr_err("[%s] debugfs create dir failed, rc = %d\n", + DEBUG_NAME, rc); + goto error; + } + + debug->root = dir; + + 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; + } + + file = debugfs_create_file("edid_modes", 0644, dir, + debug, &edid_modes_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs create edid_modes failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("edid_modes_mst", 0644, dir, + debug, &edid_modes_mst_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs create edid_modes_mst failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("mst_con_id", 0644, dir, + debug, &mst_con_id_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs create mst_con_id failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("mst_con_info", 0644, dir, + debug, &mst_conn_info_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs create mst_conn_info failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("hpd", 0644, dir, + debug, &hpd_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs hpd failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("connected", 0444, dir, + debug, &connected_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs connected failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("max_bw_code", 0644, dir, + debug, &bw_code_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs max_bw_code failed, rc=%d\n", + DEBUG_NAME, rc); + } + + file = debugfs_create_file("exe_mode", 0644, dir, + debug, &exe_mode_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs register failed, rc=%d\n", + DEBUG_NAME, rc); + } + + file = debugfs_create_file("edid", 0644, dir, + debug, &edid_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs edid failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("dpcd", 0644, dir, + debug, &dpcd_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs dpcd failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("tpg_ctrl", 0644, dir, + debug, &tpg_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs tpg failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("hdr", 0644, dir, + debug, &hdr_fops); + + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs hdr failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("sim", 0644, dir, + debug, &sim_fops); + + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs sim failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("attention", 0644, dir, + debug, &attention_fops); + + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs attention failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("dump", 0644, dir, + debug, &dump_fops); + + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs dump failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("mst_mode", 0644, dir, + debug, &mst_mode_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs max_bw_code failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("mst_sideband_mode", 0644, dir, + debug, &mst_sideband_mode_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs max_bw_code failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("max_pclk_khz", 0644, dir, + debug, &max_pclk_khz_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs max_pclk_khz failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_bool("force_encryption", 0644, dir, + &debug->dp_debug.force_encryption); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs force_encryption failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + file = debugfs_create_file("hdcp", 0644, dir, + debug, &hdcp_fops); + if (IS_ERR_OR_NULL(file)) { + rc = PTR_ERR(file); + pr_err("[%s] debugfs hdcp failed, rc=%d\n", + DEBUG_NAME, rc); + goto error_remove_dir; + } + + return 0; + +error_remove_dir: + if (!file) + rc = -EINVAL; + debugfs_remove_recursive(dir); +error: + return rc; +} + +u8 *dp_debug_get_edid(struct dp_debug *dp_debug) +{ + struct dp_debug_private *debug; + + if (!dp_debug) + return NULL; + + debug = container_of(dp_debug, struct dp_debug_private, dp_debug); + + return debug->edid; +} + +struct dp_debug *dp_debug_get(struct dp_debug_in *in) +{ + int rc = 0; + struct dp_debug_private *debug; + struct dp_debug *dp_debug; + + if (!in->dev || !in->panel || !in->hpd || !in->link || !in->catalog) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + debug = devm_kzalloc(in->dev, sizeof(*debug), GFP_KERNEL); + if (!debug) { + rc = -ENOMEM; + goto error; + } + + debug->dp_debug.debug_en = false; + debug->hpd = in->hpd; + debug->link = in->link; + debug->panel = in->panel; + debug->aux = in->aux; + debug->dev = in->dev; + debug->connector = in->connector; + debug->catalog = in->catalog; + debug->parser = in->parser; + + 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(in->dev, debug); + goto error; + } + + dp_debug->get_edid = dp_debug_get_edid; + + INIT_LIST_HEAD(&dp_debug->dp_mst_connector_list.list); + + /* + * Do not associate the head of the list with any connector in order to + * maintain backwards compatibility with the SST use case. + */ + dp_debug->dp_mst_connector_list.con_id = -1; + dp_debug->dp_mst_connector_list.conn = NULL; + dp_debug->dp_mst_connector_list.debug_en = false; + + 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_recursive(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); + + if (debug->edid) + devm_kfree(debug->dev, debug->edid); + + if (debug->dpcd) + devm_kfree(debug->dev, debug->dpcd); + + 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..9c90ddc3b88aa6bf6dd0bfac76955eaa3da2d385 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_debug.h @@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DP_DEBUG_H_ +#define _DP_DEBUG_H_ + +#include "dp_panel.h" +#include "dp_link.h" +#include "dp_usbpd.h" +#include "dp_aux.h" +#include "dp_display.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 + * @tpg_state: specifies whether tpg feature is enabled + * @max_pclk_khz: max pclk supported + * @force_encryption: enable/disable forced encryption for HDCP 2.2 + */ +struct dp_debug { + bool debug_en; + bool sim_mode; + bool psm_enabled; + bool hdcp_disabled; + int aspect_ratio; + int vdisplay; + int hdisplay; + int vrefresh; + bool tpg_state; + u32 max_pclk_khz; + bool force_encryption; + char hdcp_status[SZ_128]; + struct dp_mst_connector dp_mst_connector_list; + + u8 *(*get_edid)(struct dp_debug *dp_debug); +}; + +/** + * struct dp_debug_in + * @dev: device instance of the caller + * @panel: instance of panel module + * @hpd: instance of hpd module + * @link: instance of link module + * @aux: instance of aux module + * @connector: double pointer to display connector + * @catalog: instance of catalog module + * @parser: instance of parser module + */ +struct dp_debug_in { + struct device *dev; + struct dp_panel *panel; + struct dp_hpd *hpd; + struct dp_link *link; + struct dp_aux *aux; + struct drm_connector **connector; + struct dp_catalog *catalog; + struct dp_parser *parser; +}; + +/** + * dp_debug_get() - configure and get the DisplayPlot debug module data + * + * @in: input structure containing data to initialize the debug module + * 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 dp_debug_in *in); + +/** + * 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..593680ebf515ab5860b9a9cc40a56bce1f235167 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_display.c @@ -0,0 +1,2272 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include + +#include "sde_connector.h" + +#include "msm_drv.h" +#include "dp_hpd.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" + +#define DP_MST_DEBUG(fmt, ...) pr_debug(fmt, ##__VA_ARGS__) + +static struct dp_display *g_dp_display; +#define HPD_STRING_SIZE 30 + +struct dp_hdcp_dev { + void *fd; + struct sde_hdcp_ops *ops; + enum sde_hdcp_version ver; +}; + +struct dp_hdcp { + void *data; + struct sde_hdcp_ops *ops; + + u32 source_cap; + + struct dp_hdcp_dev dev[HDCP_VERSION_MAX]; +}; + +struct dp_mst { + bool mst_active; + + bool drm_registered; + struct dp_mst_drm_cbs cbs; +}; + +struct dp_display_private { + char *name; + int irq; + + /* state variables */ + bool core_initialized; + bool power_on; + bool is_connected; + + atomic_t aborted; + + struct platform_device *pdev; + struct device_node *aux_switch_node; + struct dentry *root; + struct completion notification_comp; + + struct dp_hpd *hpd; + 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_debug *debug; + + struct dp_panel *active_panels[DP_STREAM_MAX]; + struct dp_hdcp hdcp; + + struct dp_hpd_cb hpd_cb; + struct dp_display_mode mode; + struct dp_display dp_display; + struct msm_drm_private *priv; + + struct workqueue_struct *wq; + struct delayed_work hdcp_cb_work; + struct delayed_work connect_work; + struct work_struct attention_work; + struct mutex session_lock; + + u32 active_stream_cnt; + struct dp_mst mst; +}; + +static const struct of_device_id dp_dt_match[] = { + {.compatible = "qcom,dp-display"}, + {} +}; + +static bool dp_display_framework_ready(struct dp_display_private *dp) +{ + return dp->dp_display.post_open ? false : true; +} + +static inline bool dp_display_is_hdcp_enabled(struct dp_display_private *dp) +{ + return dp->link->hdcp_status.hdcp_version && 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 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 bool dp_display_is_ready(struct dp_display_private *dp) +{ + return dp->hpd->hpd_high && dp->is_connected && + !dp_display_is_sink_count_zero(dp) && + dp->hpd->alt_mode_cfg_done; +} + +static void dp_display_update_hdcp_status(struct dp_display_private *dp, + bool reset) +{ + if (reset) { + dp->link->hdcp_status.hdcp_state = HDCP_STATE_INACTIVE; + dp->link->hdcp_status.hdcp_version = HDCP_VERSION_NONE; + } + + memset(dp->debug->hdcp_status, 0, sizeof(dp->debug->hdcp_status)); + + snprintf(dp->debug->hdcp_status, sizeof(dp->debug->hdcp_status), + "%s: %s\ncaps: %d\n", + sde_hdcp_version(dp->link->hdcp_status.hdcp_version), + sde_hdcp_state_name(dp->link->hdcp_status.hdcp_state), + dp->hdcp.source_cap); +} + +static void dp_display_update_hdcp_info(struct dp_display_private *dp) +{ + void *fd = NULL; + struct dp_hdcp_dev *dev = NULL; + struct sde_hdcp_ops *ops = NULL; + int i = HDCP_VERSION_2P2; + + dp_display_update_hdcp_status(dp, true); + + dp->hdcp.data = NULL; + dp->hdcp.ops = NULL; + + if (dp->debug->hdcp_disabled || dp->debug->sim_mode) + return; + + while (i) { + dev = &dp->hdcp.dev[i]; + ops = dev->ops; + fd = dev->fd; + + i >>= 1; + + if (!(dp->hdcp.source_cap & dev->ver)) + continue; + + if (ops->sink_support(fd)) { + dp->hdcp.data = fd; + dp->hdcp.ops = ops; + dp->link->hdcp_status.hdcp_version = dev->ver; + break; + } + } + + pr_debug("HDCP version supported: %s\n", + sde_hdcp_version(dp->link->hdcp_status.hdcp_version)); +} + +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; + struct dp_link_hdcp_status *status; + void *data; + int rc = 0; + u32 hdcp_auth_state; + + dp = container_of(dw, struct dp_display_private, hdcp_cb_work); + + if (!dp->power_on || atomic_read(&dp->aborted)) + return; + + status = &dp->link->hdcp_status; + + if (status->hdcp_state == HDCP_STATE_INACTIVE) { + dp_display_update_hdcp_info(dp); + + if (dp_display_is_hdcp_enabled(dp)) { + status->hdcp_state = HDCP_STATE_AUTHENTICATING; + } else { + dp_display_update_hdcp_status(dp, true); + return; + } + } + + 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; + data = dp->hdcp.data; + + pr_debug("%s: %s\n", sde_hdcp_version(status->hdcp_version), + sde_hdcp_state_name(status->hdcp_state)); + + dp_display_update_hdcp_status(dp, false); + + if (dp->debug->force_encryption && ops && ops->force_encryption) + ops->force_encryption(data, dp->debug->force_encryption); + + switch (status->hdcp_state) { + case HDCP_STATE_AUTHENTICATING: + if (dp->hdcp.ops && dp->hdcp.ops->authenticate) + rc = dp->hdcp.ops->authenticate(data); + break; + case HDCP_STATE_AUTH_FAIL: + if (dp_display_is_ready(dp) && dp->power_on) { + if (ops && ops->reauthenticate) { + rc = ops->reauthenticate(data); + if (rc) + pr_err("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_state state) +{ + struct dp_display_private *dp = ptr; + + if (!dp) { + pr_err("invalid input\n"); + return; + } + + dp->link->hdcp_status.hdcp_state = state; + + mutex_lock(&dp->session_lock); + if (dp->power_on && !atomic_read(&dp->aborted)) + queue_delayed_work(dp->wq, &dp->hdcp_cb_work, HZ/4); + mutex_unlock(&dp->session_lock); +} + +static void dp_display_check_source_hdcp_caps(struct dp_display_private *dp) +{ + int i; + struct dp_hdcp_dev *hdcp_dev = dp->hdcp.dev; + + if (dp->debug->hdcp_disabled) { + pr_debug("hdcp disabled\n"); + return; + } + + for (i = 0; i < HDCP_VERSION_MAX; i++) { + struct dp_hdcp_dev *dev = &hdcp_dev[i]; + struct sde_hdcp_ops *ops = dev->ops; + void *fd = dev->fd; + + if (!fd || !ops) + continue; + + if (ops->feature_supported(fd)) + dp->hdcp.source_cap |= dev->ver; + else + pr_warn("This device doesn't support %s\n", + sde_hdcp_version(dev->ver)); + } + + if (!dp->hdcp.source_cap) + dp->debug->hdcp_disabled = true; + + dp_display_update_hdcp_status(dp, false); +} + +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); +} + +static int dp_display_initialize_hdcp(struct dp_display_private *dp) +{ + struct sde_hdcp_init_data hdcp_init_data; + struct dp_parser *parser; + void *fd; + int rc = 0; + + if (!dp) { + pr_err("invalid input\n"); + return -EINVAL; + } + + parser = dp->parser; + + 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->wq; + hdcp_init_data.sec_access = true; + hdcp_init_data.notify_status = dp_display_notify_hdcp_status_cb; + hdcp_init_data.dp_ahb = &parser->get_io(parser, "dp_ahb")->io; + hdcp_init_data.dp_aux = &parser->get_io(parser, "dp_aux")->io; + hdcp_init_data.dp_link = &parser->get_io(parser, "dp_link")->io; + hdcp_init_data.dp_p0 = &parser->get_io(parser, "dp_p0")->io; + hdcp_init_data.qfprom_io = &parser->get_io(parser, + "qfprom_physical")->io; + hdcp_init_data.hdcp_io = &parser->get_io(parser, + "hdcp_physical")->io; + hdcp_init_data.revision = &dp->panel->link_info.revision; + hdcp_init_data.msm_hdcp_dev = dp->parser->msm_hdcp_dev; + + fd = sde_hdcp_1x_init(&hdcp_init_data); + if (IS_ERR_OR_NULL(fd)) { + pr_err("Error initializing HDCP 1.x\n"); + rc = -EINVAL; + goto error; + } + + dp->hdcp.dev[HDCP_VERSION_1X].fd = fd; + dp->hdcp.dev[HDCP_VERSION_1X].ops = sde_hdcp_1x_get(fd); + dp->hdcp.dev[HDCP_VERSION_1X].ver = HDCP_VERSION_1X; + pr_debug("HDCP 1.3 initialized\n"); + + fd = sde_dp_hdcp2p2_init(&hdcp_init_data); + if (IS_ERR_OR_NULL(fd)) { + pr_err("Error initializing HDCP 2.x\n"); + rc = -EINVAL; + goto error; + } + + dp->hdcp.dev[HDCP_VERSION_2P2].fd = fd; + dp->hdcp.dev[HDCP_VERSION_2P2].ops = sde_dp_hdcp2p2_get(fd); + dp->hdcp.dev[HDCP_VERSION_2P2].ver = HDCP_VERSION_2P2; + pr_debug("HDCP 2.2 initialized\n"); + + return 0; +error: + dp_display_deinitialize_hdcp(dp); + dp->debug->hdcp_disabled = true; + + 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 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; + dp->priv = drm->dev_private; +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; + } + + if (dp->power) + (void)dp->power->power_client_deinit(dp->power); + if (dp->aux) + (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 void dp_display_send_hpd_event(struct dp_display_private *dp) +{ + struct drm_device *dev = NULL; + 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->mst.mst_active) { + pr_debug("skip notification for mst mode\n"); + return; + } + + connector = dp->dp_display.base_connector; + + if (!connector) { + pr_err("connector not set\n"); + return; + } + + connector->status = connector->funcs->detect(connector, false); + + dev = connector->dev; + + 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("[%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 void dp_display_post_open(struct dp_display *dp_display) +{ + struct drm_connector *connector; + struct dp_display_private *dp; + + if (!dp_display) { + pr_err("invalid input\n"); + return; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + if (IS_ERR_OR_NULL(dp)) { + pr_err("invalid params\n"); + return; + } + + connector = dp->dp_display.base_connector; + + if (!connector) { + pr_err("base connector not set\n"); + return; + } + + dp_display_update_hdcp_status(dp, true); + dp_display_check_source_hdcp_caps(dp); + + /* if cable is already connected, send notification */ + if (dp->hpd->hpd_high) + queue_delayed_work(dp->wq, &dp->connect_work, HZ * 10); + else + dp_display->post_open = NULL; +} + +static int dp_display_send_hpd_notification(struct dp_display_private *dp) +{ + u32 timeout_sec; + int ret = 0; + bool hpd = dp->is_connected; + + if (dp_display_framework_ready(dp)) + timeout_sec = 5; + else + timeout_sec = 10; + + dp->aux->state |= DP_STATE_NOTIFICATION_SENT; + + if (!dp->mst.mst_active) + dp->dp_display.is_sst_connected = hpd; + + reinit_completion(&dp->notification_comp); + dp_display_send_hpd_event(dp); + + if (hpd && dp->mst.mst_active) + goto skip_wait; + + if (!wait_for_completion_timeout(&dp->notification_comp, + HZ * timeout_sec)) { + pr_warn("%s timeout\n", hpd ? "connect" : "disconnect"); + ret = -EINVAL; + } + return ret; +skip_wait: + return 0; +} + +static void dp_display_process_mst_hpd_high(struct dp_display_private *dp) +{ + bool is_mst_receiver; + struct dp_mst_hdp_info info; + + if (dp->parser->has_mst && dp->mst.drm_registered) { + DP_MST_DEBUG("mst_hpd_high work\n"); + + is_mst_receiver = dp->panel->read_mst_cap(dp->panel); + + if (is_mst_receiver && !dp->mst.mst_active) { + dp->mst.mst_active = true; + + info.mst_protocol = dp->parser->has_mst_sideband; + info.edid = dp->debug->get_edid(dp->debug); + + if (dp->mst.cbs.hpd) + dp->mst.cbs.hpd(&dp->dp_display, true, &info); + } + } + + DP_MST_DEBUG("mst_hpd_high. mst_active:%d\n", dp->mst.mst_active); +} + +static void dp_display_host_init(struct dp_display_private *dp) +{ + bool flip = false; + bool reset; + + if (dp->core_initialized) { + pr_debug("DP core already initialized\n"); + return; + } + + if (dp->hpd->orientation == ORIENTATION_CC2) + flip = true; + + reset = dp->debug->sim_mode ? false : !dp->hpd->multi_func; + + dp->power->init(dp->power, flip); + dp->ctrl->init(dp->ctrl, flip, reset); + dp->aux->init(dp->aux, dp->parser->aux_cfg); + 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; + } + + if (dp->active_stream_cnt) { + pr_debug("active stream present\n"); + return; + } + + dp->aux->deinit(dp->aux); + dp->ctrl->deinit(dp->ctrl); + dp->power->deinit(dp->power); + disable_irq(dp->irq); + dp->core_initialized = false; + dp->aux->state = 0; +} + +static int dp_display_process_hpd_high(struct dp_display_private *dp) +{ + int rc = 0; + + if (!dp->debug->sim_mode && !dp->parser->no_aux_switch) { + rc = dp->aux->aux_switch(dp->aux, true, dp->hpd->orientation); + if (rc) + goto end; + } + + dp->is_connected = true; + + dp->dp_display.max_pclk_khz = dp->parser->max_pclk_khz; + + dp_display_host_init(dp); + + if (dp->debug->psm_enabled) { + dp->link->psm_config(dp->link, &dp->panel->link_info, false); + dp->debug->psm_enabled = false; + } + + if (!dp->dp_display.base_connector) + goto end; + + rc = dp->panel->read_sink_caps(dp->panel, + dp->dp_display.base_connector, dp->hpd->multi_func); + /* + * ETIMEDOUT --> cable may have been removed + * ENOTCONN --> no downstream device connected + */ + if (rc == -ETIMEDOUT || rc == -ENOTCONN) + goto end; + + dp->link->process_request(dp->link); + dp->panel->handle_sink_request(dp->panel); + + dp_display_process_mst_hpd_high(dp); + + mutex_lock(&dp->session_lock); + rc = dp->ctrl->on(dp->ctrl, dp->mst.mst_active, false); + if (rc) { + mutex_unlock(&dp->session_lock); + goto end; + } + mutex_unlock(&dp->session_lock); + + dp_display_send_hpd_notification(dp); +end: + return rc; +} + +static void dp_display_process_mst_hpd_low(struct dp_display_private *dp) +{ + struct dp_mst_hdp_info info = {0}; + + if (dp->mst.mst_active) { + DP_MST_DEBUG("mst_hpd_low work\n"); + + if (dp->mst.cbs.hpd) { + info.mst_protocol = dp->parser->has_mst_sideband; + dp->mst.cbs.hpd(&dp->dp_display, false, &info); + } + + dp->mst.mst_active = false; + } + + DP_MST_DEBUG("mst_hpd_low. mst_active:%d\n", dp->mst.mst_active); +} + +static int dp_display_process_hpd_low(struct dp_display_private *dp) +{ + int rc = 0, idx; + struct dp_panel *dp_panel; + + mutex_lock(&dp->session_lock); + + dp->is_connected = false; + + if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->off) + dp->hdcp.ops->off(dp->hdcp.data); + + for (idx = DP_STREAM_0; idx < DP_STREAM_MAX; idx++) { + if (!dp->active_panels[idx]) + continue; + + dp_panel = dp->active_panels[idx]; + + if (dp_panel->audio_supported) { + dp_panel->audio->off(dp_panel->audio); + dp_panel->audio_supported = false; + } + } + + mutex_unlock(&dp->session_lock); + + dp_display_process_mst_hpd_low(dp); + + rc = dp_display_send_hpd_notification(dp); + + mutex_lock(&dp->session_lock); + if (!dp->active_stream_cnt) + dp->ctrl->off(dp->ctrl); + mutex_unlock(&dp->session_lock); + + dp->panel->video_test = false; + + return rc; +} + +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; + } + + /* check for hpd high and framework ready */ + if (dp->hpd->hpd_high && dp_display_framework_ready(dp)) + queue_delayed_work(dp->wq, &dp->connect_work, 0); +end: + return rc; +} + +static void dp_display_clean(struct dp_display_private *dp) +{ + int idx; + struct dp_panel *dp_panel; + + if (dp_display_is_hdcp_enabled(dp)) { + cancel_delayed_work_sync(&dp->hdcp_cb_work); + if (dp->hdcp.ops->off) + dp->hdcp.ops->off(dp->hdcp.data); + + dp_display_update_hdcp_status(dp, true); + } + + for (idx = DP_STREAM_0; idx < DP_STREAM_MAX; idx++) { + if (!dp->active_panels[idx]) + continue; + + dp_panel = dp->active_panels[idx]; + + dp->ctrl->stream_pre_off(dp->ctrl, dp_panel); + dp->ctrl->stream_off(dp->ctrl, dp_panel); + } + + dp->power_on = false; +} + +static int dp_display_handle_disconnect(struct dp_display_private *dp) +{ + int rc; + + rc = dp_display_process_hpd_low(dp); + if (rc) { + /* cancel any pending request */ + dp->ctrl->abort(dp->ctrl); + dp->aux->abort(dp->aux); + } + + mutex_lock(&dp->session_lock); + if (rc && dp->power_on) + dp_display_clean(dp); + + dp_display_host_deinit(dp); + + mutex_unlock(&dp->session_lock); + + return rc; +} + +static int dp_display_usbpd_disconnect_cb(struct device *dev) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dev) { + pr_err("invalid dev\n"); + rc = -EINVAL; + goto end; + } + + dp = dev_get_drvdata(dev); + if (!dp) { + pr_err("no driver data found\n"); + rc = -ENODEV; + goto end; + } + + /* + * In case cable/dongle is disconnected during adb shell stop, + * reset psm_enabled flag to false since it is no more needed + */ + if (dp->dp_display.post_open) + dp->debug->psm_enabled = false; + + if (dp->debug->psm_enabled) + dp->link->psm_config(dp->link, &dp->panel->link_info, true); + + /* cancel any pending request */ + atomic_set(&dp->aborted, 1); + dp->ctrl->abort(dp->ctrl); + dp->aux->abort(dp->aux); + + /* wait for idle state */ + cancel_delayed_work(&dp->connect_work); + cancel_work(&dp->attention_work); + flush_workqueue(dp->wq); + + if (!dp->debug->sim_mode && !dp->parser->no_aux_switch) + dp->aux->aux_switch(dp->aux, false, ORIENTATION_NONE); + + dp_display_handle_disconnect(dp); + + /* Reset abort value to allow future connections */ + atomic_set(&dp->aborted, 0); + + dp->dp_display.post_open = NULL; +end: + return rc; +} + +static void dp_display_stream_disable(struct dp_display_private *dp, + struct dp_panel *dp_panel) +{ + if (!dp->active_stream_cnt) { + pr_err("invalid active_stream_cnt (%d)\n"); + return; + } + + pr_debug("stream_id=%d, active_stream_cnt=%d\n", + dp_panel->stream_id, dp->active_stream_cnt); + + dp->ctrl->stream_off(dp->ctrl, dp_panel); + dp->active_panels[dp_panel->stream_id] = NULL; + dp->active_stream_cnt--; +} + +static int dp_display_stream_enable(struct dp_display_private *dp, + struct dp_panel *dp_panel) +{ + int rc = 0; + + rc = dp->ctrl->stream_on(dp->ctrl, dp_panel); + + if (dp->debug->tpg_state) + dp_panel->tpg_config(dp_panel, true); + + if (!rc) { + dp->active_panels[dp_panel->stream_id] = dp_panel; + dp->active_stream_cnt++; + } + + pr_debug("dp active_stream_cnt:%d\n", dp->active_stream_cnt); + + return rc; +} + +static void dp_display_mst_attention(struct dp_display_private *dp) +{ + if (dp->mst.mst_active && dp->mst.cbs.hpd_irq) + dp->mst.cbs.hpd_irq(&dp->dp_display); + + DP_MST_DEBUG("mst_attention_work. mst_active:%d\n", dp->mst.mst_active); +} + +static void dp_display_attention_work(struct work_struct *work) +{ + struct dp_display_private *dp = container_of(work, + struct dp_display_private, attention_work); + + if (dp->link->process_request(dp->link)) + goto cp_irq; + + if (dp->link->sink_request & DS_PORT_STATUS_CHANGED) { + if (dp_display_is_sink_count_zero(dp)) { + dp_display_handle_disconnect(dp); + } else { + if (!dp->mst.mst_active) + queue_delayed_work(dp->wq, + &dp->connect_work, 0); + } + + goto mst_attention; + } + + if (dp->link->sink_request & DP_TEST_LINK_VIDEO_PATTERN) { + dp_display_handle_disconnect(dp); + + dp->panel->video_test = true; + queue_delayed_work(dp->wq, &dp->connect_work, 0); + + goto mst_attention; + } + + if (dp->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) { + dp->ctrl->process_phy_test_request(dp->ctrl); + goto mst_attention; + } + + if (dp->link->sink_request & DP_TEST_LINK_TRAINING) { + dp->link->send_test_response(dp->link); + dp->ctrl->link_maintenance(dp->ctrl); + goto mst_attention; + } + + if (dp->link->sink_request & DP_LINK_STATUS_UPDATED) + dp->ctrl->link_maintenance(dp->ctrl); +cp_irq: + if (dp_display_is_hdcp_enabled(dp) && dp->hdcp.ops->cp_irq) + dp->hdcp.ops->cp_irq(dp->hdcp.data); +mst_attention: + dp_display_mst_attention(dp); +} + +static int dp_display_usbpd_attention_cb(struct device *dev) +{ + 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; + } + + DP_MST_DEBUG("mst: hpd_irq:%d, hpd_high:%d, power_on:%d\n", + dp->hpd->hpd_irq, dp->hpd->hpd_high, + dp->power_on); + + if (!dp->hpd->hpd_high) { + if (!dp->is_connected) { + pr_debug("already disconnected\n"); + return 0; + } + + /* cancel any pending request */ + atomic_set(&dp->aborted, 1); + dp->ctrl->abort(dp->ctrl); + dp->aux->abort(dp->aux); + + /* wait for idle state */ + cancel_delayed_work(&dp->connect_work); + cancel_work(&dp->attention_work); + flush_workqueue(dp->wq); + + dp_display_handle_disconnect(dp); + atomic_set(&dp->aborted, 0); + } else if (dp->hpd->hpd_irq && dp->core_initialized) { + queue_work(dp->wq, &dp->attention_work); + } else { + queue_delayed_work(dp->wq, &dp->connect_work, 0); + } + + return 0; +} + +static void dp_display_connect_work(struct work_struct *work) +{ + int rc = 0; + struct delayed_work *dw = to_delayed_work(work); + struct dp_display_private *dp = container_of(dw, + struct dp_display_private, connect_work); + + if (atomic_read(&dp->aborted)) { + pr_warn("HPD off requested\n"); + return; + } + + if (!dp->hpd->hpd_high) { + pr_warn("Sink disconnected\n"); + return; + } + + rc = dp_display_process_hpd_high(dp); + + if (!rc && dp->panel->video_test) + dp->link->send_test_response(dp->link); +} + +static void dp_display_deinit_sub_modules(struct dp_display_private *dp) +{ + dp_audio_put(dp->panel->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_hpd_put(dp->hpd); + mutex_destroy(&dp->session_lock); + 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_hpd_cb *cb = &dp->hpd_cb; + struct dp_ctrl_in ctrl_in = { + .dev = dev, + }; + struct dp_panel_in panel_in = { + .dev = dev, + }; + struct dp_debug_in debug_in = { + .dev = dev, + }; + + mutex_init(&dp->session_lock); + + 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; + } + + rc = dp->parser->parse(dp->parser); + if (rc) { + pr_err("device tree parsing failed\n"); + goto error_catalog; + } + + dp->catalog = dp_catalog_get(dev, dp->parser); + 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; + } + + rc = dp->power->power_client_init(dp->power, &dp->priv->phandle); + if (rc) { + pr_err("Power client create failed\n"); + goto error_aux; + } + + dp->aux = dp_aux_get(dev, &dp->catalog->aux, dp->parser, + dp->aux_switch_node); + 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; + } + + rc = dp->aux->drm_aux_register(dp->aux); + if (rc) { + pr_err("DRM DP AUX register failed\n"); + goto error_link; + } + + 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; + panel_in.connector = dp->dp_display.base_connector; + panel_in.base_panel = NULL; + + 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->panel->audio = dp_audio_get(dp->pdev, dp->panel, + &dp->catalog->audio); + if (IS_ERR(dp->panel->audio)) { + rc = PTR_ERR(dp->panel->audio); + pr_err("failed to initialize audio, rc = %d\n", rc); + dp->panel->audio = NULL; + goto error_audio; + } + + memset(&dp->mst, 0, sizeof(dp->mst)); + dp->active_stream_cnt = 0; + + cb->configure = dp_display_usbpd_configure_cb; + cb->disconnect = dp_display_usbpd_disconnect_cb; + cb->attention = dp_display_usbpd_attention_cb; + + dp->hpd = dp_hpd_get(dev, dp->parser, cb); + if (IS_ERR(dp->hpd)) { + rc = PTR_ERR(dp->hpd); + pr_err("failed to initialize hpd, rc = %d\n", rc); + dp->hpd = NULL; + goto error_hpd; + } + + dp_display_initialize_hdcp(dp); + + debug_in.panel = dp->panel; + debug_in.hpd = dp->hpd; + debug_in.link = dp->link; + debug_in.aux = dp->aux; + debug_in.connector = &dp->dp_display.base_connector; + debug_in.catalog = dp->catalog; + debug_in.parser = dp->parser; + + dp->debug = dp_debug_get(&debug_in); + 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_hpd_put(dp->hpd); +error_hpd: + dp_audio_put(dp->panel->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: + mutex_destroy(&dp->session_lock); + return rc; +} + +static int dp_display_post_init(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 (IS_ERR_OR_NULL(dp)) { + pr_err("invalid params\n"); + rc = -EINVAL; + goto end; + } + + rc = dp_init_sub_modules(dp); + if (rc) + goto end; + + dp_display->post_init = NULL; +end: + pr_debug("%s\n", rc ? "failed" : "success"); + return rc; +} + +static int dp_display_set_mode(struct dp_display *dp_display, void *panel, + struct dp_display_mode *mode) +{ + const u32 num_components = 3, default_bpp = 24; + struct dp_display_private *dp; + struct dp_panel *dp_panel; + + if (!dp_display || !panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp_panel = panel; + if (!dp_panel->connector) { + pr_err("invalid connector input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + mutex_lock(&dp->session_lock); + mode->timing.bpp = + dp_panel->connector->display_info.bpc * num_components; + if (!mode->timing.bpp) + mode->timing.bpp = default_bpp; + + mode->timing.bpp = dp->panel->get_mode_bpp(dp->panel, + mode->timing.bpp, mode->timing.pixel_clk_khz); + + dp_panel->pinfo = mode->timing; + dp_panel->init(dp_panel); + mutex_unlock(&dp->session_lock); + + return 0; +} + +static int dp_display_prepare(struct dp_display *dp_display, void *panel) +{ + struct dp_display_private *dp; + struct dp_panel *dp_panel; + int rc = 0; + + if (!dp_display || !panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp_panel = panel; + if (!dp_panel->connector) { + pr_err("invalid connector input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + mutex_lock(&dp->session_lock); + + if (atomic_read(&dp->aborted)) + goto end; + + if (dp->power_on) + goto end; + + if (!dp_display_is_ready(dp)) + goto end; + + dp_display_host_init(dp); + + /* + * Execute the dp controller power on in shallow mode here. + * In normal cases, controller should have been powered on + * by now. In some cases like suspend/resume or framework + * reboot, we end up here without a powered on controller. + * Cable may have been removed in suspended state. In that + * case, link training is bound to fail on system resume. + * So, we execute in shallow mode here to do only minimal + * and required things. + */ + rc = dp->ctrl->on(dp->ctrl, dp->mst.mst_active, true); + if (rc) + goto end; + + if (dp->debug->psm_enabled) { + dp->link->psm_config(dp->link, &dp->panel->link_info, false); + dp->debug->psm_enabled = false; + } + +end: + mutex_unlock(&dp->session_lock); + + return 0; +} + +static int dp_display_set_stream_info(struct dp_display *dp_display, + void *panel, u32 strm_id, u32 start_slot, + u32 num_slots, u32 pbn) +{ + int rc = 0; + struct dp_panel *dp_panel; + struct dp_display_private *dp; + const int max_slots = 64; + + if (!dp_display) { + pr_err("invalid input\n"); + return -EINVAL; + } + + if (strm_id >= DP_STREAM_MAX) { + pr_err("invalid stream id:%d\n", strm_id); + return -EINVAL; + } + + if (start_slot + num_slots > max_slots) { + pr_err("invalid channel info received. start:%d, slots:%d\n", + start_slot, num_slots); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + mutex_lock(&dp->session_lock); + + dp->ctrl->set_mst_channel_info(dp->ctrl, strm_id, + start_slot, num_slots); + + if (panel) { + dp_panel = panel; + dp_panel->set_stream_info(dp_panel, strm_id, start_slot, + num_slots, pbn); + } + + mutex_unlock(&dp->session_lock); + + return rc; +} + +static int dp_display_enable(struct dp_display *dp_display, void *panel) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dp_display || !panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + mutex_lock(&dp->session_lock); + + if (!dp->core_initialized) { + pr_err("host not initialized\n"); + goto end; + } + + rc = dp_display_stream_enable(dp, panel); + if (rc) + goto end; + + dp->power_on = true; +end: + mutex_unlock(&dp->session_lock); + return rc; +} + +static void dp_display_stream_post_enable(struct dp_display_private *dp, + struct dp_panel *dp_panel) +{ + dp_panel->spd_config(dp_panel); + dp_panel->setup_hdr(dp_panel, NULL); +} + +static int dp_display_post_enable(struct dp_display *dp_display, void *panel) +{ + struct dp_display_private *dp; + struct dp_panel *dp_panel; + struct edid *edid; + + if (!dp_display || !panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + dp_panel = panel; + + mutex_lock(&dp->session_lock); + + if (!dp->power_on) { + pr_debug("stream not setup, return\n"); + goto end; + } + + if (atomic_read(&dp->aborted)) + goto end; + + if (!dp_display_is_ready(dp) || !dp->core_initialized) { + pr_err("display not ready\n"); + goto end; + } + + dp_display_stream_post_enable(dp, dp_panel); + + edid = dp_panel->edid_ctrl->edid; + dp_panel->audio_supported = drm_detect_monitor_audio(edid); + + if (dp_panel->audio_supported) { + dp_panel->audio->bw_code = dp->link->link_params.bw_code; + dp_panel->audio->lane_count = dp->link->link_params.lane_count; + dp_panel->audio->on(dp_panel->audio); + } + + cancel_delayed_work_sync(&dp->hdcp_cb_work); + queue_delayed_work(dp->wq, &dp->hdcp_cb_work, HZ); +end: + /* clear framework event notifier */ + dp_display->post_open = NULL; + dp->aux->state |= DP_STATE_CTRL_POWERED_ON; + + complete_all(&dp->notification_comp); + mutex_unlock(&dp->session_lock); + return 0; +} + +static int dp_display_stream_pre_disable(struct dp_display_private *dp, + struct dp_panel *dp_panel) +{ + dp->ctrl->stream_pre_off(dp->ctrl, dp_panel); + + return 0; +} + +static int dp_display_pre_disable(struct dp_display *dp_display, void *panel) +{ + struct dp_display_private *dp; + struct dp_panel *dp_panel = panel; + int rc = 0; + + if (!dp_display || !panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + mutex_lock(&dp->session_lock); + + if (!dp->power_on) { + pr_debug("stream already powered off, return\n"); + goto end; + } + + if (dp_display_is_hdcp_enabled(dp)) { + cancel_delayed_work_sync(&dp->hdcp_cb_work); + if (dp->hdcp.ops->off) + dp->hdcp.ops->off(dp->hdcp.data); + + dp_display_update_hdcp_status(dp, true); + } + + if (dp_panel->audio_supported) + dp_panel->audio->off(dp_panel->audio); + + rc = dp_display_stream_pre_disable(dp, dp_panel); + + if (dp_display_is_ready(dp) && !dp->mst.mst_active) { + dp->link->psm_config(dp->link, &dp->panel->link_info, true); + dp->debug->psm_enabled = true; + } + +end: + mutex_unlock(&dp->session_lock); + return 0; +} + +static int dp_display_disable(struct dp_display *dp_display, void *panel) +{ + struct dp_display_private *dp = NULL; + struct dp_panel *dp_panel = NULL; + + if (!dp_display || !panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + dp_panel = panel; + + mutex_lock(&dp->session_lock); + + if (!dp->power_on || !dp->core_initialized) { + pr_debug("Link already powered off, return\n"); + goto end; + } + + dp_display_stream_disable(dp, dp_panel); + + if (dp->active_stream_cnt) { + pr_debug("active stream present\n"); + goto end; + } + + /* + * In case of framework reboot, the DP off sequence is executed without + * any notification from driver. Initialize post_open callback to notify + * DP connection once framework restarts. + */ + if (dp_display_is_ready(dp) && !dp->mst.mst_active) { + dp_display->post_open = dp_display_post_open; + dp->dp_display.is_sst_connected = false; + + dp->ctrl->off(dp->ctrl); + dp_display_host_deinit(dp); + } + + dp->power_on = false; +end: + dp_panel->deinit(dp_panel); + mutex_unlock(&dp->session_lock); + return 0; +} + +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_display, void *panel) +{ + struct dp_display_private *dp; + + if (!dp_display || !panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + mutex_lock(&dp->session_lock); + + if (dp->active_stream_cnt) + goto end; + + dp->aux->state = DP_STATE_CTRL_POWERED_OFF; + + complete_all(&dp->notification_comp); + +end: + mutex_unlock(&dp->session_lock); + + return 0; +} + +static enum drm_mode_status dp_display_validate_mode( + struct dp_display *dp_display, + void *panel, struct drm_display_mode *mode) +{ + const u32 num_components = 3, default_bpp = 24; + struct dp_display_private *dp; + struct drm_dp_link *link_info; + u32 mode_rate_khz = 0, supported_rate_khz = 0, mode_bpp = 0; + struct dp_panel *dp_panel; + struct dp_debug *debug; + enum drm_mode_status mode_status = MODE_BAD; + bool in_list = false; + struct dp_mst_connector *mst_connector; + int hdis, vdis, vref, ar, _hdis, _vdis, _vref, _ar; + + if (!dp_display || !mode || !panel) { + pr_err("invalid params\n"); + return mode_status; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + mutex_lock(&dp->session_lock); + + dp_panel = panel; + if (!dp_panel->connector) { + pr_err("invalid connector\n"); + goto end; + } + + link_info = &dp->panel->link_info; + + debug = dp->debug; + if (!debug) + goto end; + + mode_bpp = dp_panel->connector->display_info.bpc * num_components; + if (!mode_bpp) + mode_bpp = default_bpp; + + mode_bpp = dp_panel->get_mode_bpp(dp_panel, mode_bpp, mode->clock); + + mode_rate_khz = mode->clock * mode_bpp; + supported_rate_khz = link_info->num_lanes * link_info->rate * 8; + + if (mode_rate_khz > supported_rate_khz) { + DP_MST_DEBUG("pclk:%d, supported_rate:%d\n", + mode->clock, supported_rate_khz); + goto end; + } + + if (mode->clock > dp_display->max_pclk_khz) { + DP_MST_DEBUG("clk:%d, max:%d\n", mode->clock, + dp_display->max_pclk_khz); + goto end; + } + + /* + * If the connector exists in the mst connector list and if debug is + * enabled for that connector, use the mst connector settings from the + * list for validation. Otherwise, use non-mst default settings. + */ + mutex_lock(&debug->dp_mst_connector_list.lock); + + if (list_empty(&debug->dp_mst_connector_list.list)) { + mutex_unlock(&debug->dp_mst_connector_list.lock); + goto verify_default; + } + + list_for_each_entry(mst_connector, &debug->dp_mst_connector_list.list, + list) { + if (mst_connector->con_id == dp_panel->connector->base.id) { + in_list = true; + + if (!mst_connector->debug_en) { + mode_status = MODE_OK; + mutex_unlock( + &debug->dp_mst_connector_list.lock); + goto end; + } + + hdis = mst_connector->hdisplay; + vdis = mst_connector->vdisplay; + vref = mst_connector->vrefresh; + ar = mst_connector->aspect_ratio; + + _hdis = mode->hdisplay; + _vdis = mode->vdisplay; + _vref = mode->vrefresh; + _ar = mode->picture_aspect_ratio; + + if (hdis == _hdis && vdis == _vdis && vref == _vref && + ar == _ar) { + mode_status = MODE_OK; + mutex_unlock( + &debug->dp_mst_connector_list.lock); + goto end; + } + + break; + } + } + + mutex_unlock(&debug->dp_mst_connector_list.lock); + + if (in_list) + goto end; + +verify_default: + if (debug->debug_en && (mode->hdisplay != debug->hdisplay || + mode->vdisplay != debug->vdisplay || + mode->vrefresh != debug->vrefresh || + mode->picture_aspect_ratio != debug->aspect_ratio)) + goto end; + + mode_status = MODE_OK; +end: + mutex_unlock(&dp->session_lock); + return mode_status; +} + +static int dp_display_get_modes(struct dp_display *dp, void *panel, + struct dp_display_mode *dp_mode) +{ + struct dp_display_private *dp_display; + struct dp_panel *dp_panel; + int ret = 0; + + if (!dp || !panel) { + pr_err("invalid params\n"); + return 0; + } + + dp_panel = panel; + if (!dp_panel->connector) { + pr_err("invalid connector\n"); + return 0; + } + + dp_display = container_of(dp, struct dp_display_private, dp_display); + + ret = dp_panel->get_modes(dp_panel, dp_panel->connector, dp_mode); + if (dp_mode->timing.pixel_clk_khz) + dp->max_pclk_khz = dp_mode->timing.pixel_clk_khz; + return ret; +} + +static void dp_display_convert_to_dp_mode(struct dp_display *dp_display, + void *panel, + const struct drm_display_mode *drm_mode, + struct dp_display_mode *dp_mode) +{ + struct dp_display_private *dp; + struct dp_panel *dp_panel; + + if (!dp_display || !drm_mode || !dp_mode || !panel) { + pr_err("invalid input\n"); + return; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + dp_panel = panel; + + memset(dp_mode, 0, sizeof(*dp_mode)); + dp_panel->convert_to_dp_mode(dp_panel, drm_mode, dp_mode); +} + +static int dp_display_config_hdr(struct dp_display *dp_display, void *panel, + struct drm_msm_ext_hdr_metadata *hdr) +{ + int rc = 0; + struct dp_display_private *dp; + struct dp_panel *dp_panel; + + if (!dp_display || !panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + dp_panel = panel; + + rc = dp_panel->setup_hdr(dp_panel, hdr); + + return rc; +} + +static int dp_display_create_workqueue(struct dp_display_private *dp) +{ + dp->wq = create_singlethread_workqueue("drm_dp"); + if (IS_ERR_OR_NULL(dp->wq)) { + pr_err("Error creating wq\n"); + return -EPERM; + } + + INIT_DELAYED_WORK(&dp->hdcp_cb_work, dp_display_hdcp_cb_work); + INIT_DELAYED_WORK(&dp->connect_work, dp_display_connect_work); + INIT_WORK(&dp->attention_work, dp_display_attention_work); + + return 0; +} + +static int dp_display_fsa4480_callback(struct notifier_block *self, + unsigned long event, void *data) +{ + return 0; +} + +static int dp_display_init_aux_switch(struct dp_display_private *dp) +{ + int rc = 0; + const char *phandle = "qcom,dp-aux-switch"; + struct notifier_block nb; + + if (!dp->pdev->dev.of_node) { + pr_err("cannot find dev.of_node\n"); + rc = -ENODEV; + goto end; + } + + dp->aux_switch_node = of_parse_phandle(dp->pdev->dev.of_node, + phandle, 0); + if (!dp->aux_switch_node) { + pr_warn("cannot parse %s handle\n", phandle); + goto end; + } + + nb.notifier_call = dp_display_fsa4480_callback; + nb.priority = 0; + + rc = fsa4480_reg_notifier(&nb, dp->aux_switch_node); + if (rc) { + pr_err("failed to register notifier (%d)\n", rc); + goto end; + } + + fsa4480_unreg_notifier(&nb, dp->aux_switch_node); +end: + return rc; +} + +static int dp_display_mst_install(struct dp_display *dp_display, + struct dp_mst_drm_install_info *mst_install_info) +{ + struct dp_display_private *dp; + + if (!dp_display || !mst_install_info) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + if (!mst_install_info->cbs->hpd || !mst_install_info->cbs->hpd_irq) { + pr_err("invalid mst cbs\n"); + return -EINVAL; + } + + dp_display->dp_mst_prv_info = mst_install_info->dp_mst_prv_info; + + if (!dp->parser->has_mst) { + pr_debug("mst not enabled\n"); + return -EPERM; + } + + memcpy(&dp->mst.cbs, mst_install_info->cbs, sizeof(dp->mst.cbs)); + dp->mst.drm_registered = true; + + DP_MST_DEBUG("dp mst drm installed\n"); + + return 0; +} + +static int dp_display_mst_uninstall(struct dp_display *dp_display) +{ + 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); + + if (!dp->mst.drm_registered) { + pr_debug("drm mst not registered\n"); + return -EPERM; + } + + dp = container_of(dp_display, struct dp_display_private, + dp_display); + memset(&dp->mst.cbs, 0, sizeof(dp->mst.cbs)); + dp->mst.drm_registered = false; + + DP_MST_DEBUG("dp mst drm uninstalled\n"); + + return 0; +} + +static int dp_display_mst_connector_install(struct dp_display *dp_display, + struct drm_connector *connector) +{ + int rc = 0; + struct dp_panel_in panel_in; + struct dp_panel *dp_panel; + struct dp_display_private *dp; + struct dp_mst_connector *mst_connector; + + if (!dp_display || !connector) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + mutex_lock(&dp->session_lock); + + if (!dp->mst.drm_registered) { + pr_debug("drm mst not registered\n"); + mutex_unlock(&dp->session_lock); + return -EPERM; + } + + panel_in.dev = &dp->pdev->dev; + panel_in.aux = dp->aux; + panel_in.catalog = &dp->catalog->panel; + panel_in.link = dp->link; + panel_in.connector = connector; + panel_in.base_panel = dp->panel; + + 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); + mutex_unlock(&dp->session_lock); + return rc; + } + + dp_panel->audio = dp_audio_get(dp->pdev, dp_panel, &dp->catalog->audio); + if (IS_ERR(dp_panel->audio)) { + rc = PTR_ERR(dp_panel->audio); + pr_err("[mst] failed to initialize audio, rc = %d\n", rc); + dp_panel->audio = NULL; + mutex_unlock(&dp->session_lock); + return rc; + } + + DP_MST_DEBUG("dp mst connector installed. conn:%d\n", + connector->base.id); + + mutex_lock(&dp->debug->dp_mst_connector_list.lock); + + mst_connector = kmalloc(sizeof(struct dp_mst_connector), + GFP_KERNEL); + mst_connector->debug_en = false; + mst_connector->conn = connector; + mst_connector->con_id = connector->base.id; + INIT_LIST_HEAD(&mst_connector->list); + + list_add(&mst_connector->list, + &dp->debug->dp_mst_connector_list.list); + + mutex_unlock(&dp->debug->dp_mst_connector_list.lock); + mutex_unlock(&dp->session_lock); + + return 0; +} + +static int dp_display_mst_connector_uninstall(struct dp_display *dp_display, + struct drm_connector *connector) +{ + int rc = 0; + struct sde_connector *sde_conn; + struct dp_panel *dp_panel; + struct dp_display_private *dp; + struct dp_mst_connector *con_to_remove, *temp_con; + + if (!dp_display || !connector) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + mutex_lock(&dp->session_lock); + + if (!dp->mst.drm_registered) { + pr_debug("drm mst not registered\n"); + mutex_unlock(&dp->session_lock); + return -EPERM; + } + + sde_conn = to_sde_connector(connector); + if (!sde_conn->drv_panel) { + pr_err("invalid panel for connector:%d\n", connector->base.id); + mutex_unlock(&dp->session_lock); + return -EINVAL; + } + + dp_panel = sde_conn->drv_panel; + dp_audio_put(dp_panel->audio); + dp_panel_put(dp_panel); + + DP_MST_DEBUG("dp mst connector uninstalled. conn:%d\n", + connector->base.id); + + mutex_lock(&dp->debug->dp_mst_connector_list.lock); + + list_for_each_entry_safe(con_to_remove, temp_con, + &dp->debug->dp_mst_connector_list.list, list) { + if (con_to_remove->con_id == connector->base.id) { + list_del(&con_to_remove->list); + kfree(con_to_remove); + } + } + + mutex_unlock(&dp->debug->dp_mst_connector_list.lock); + mutex_unlock(&dp->session_lock); + + return rc; +} + +static int dp_display_mst_connector_update_edid(struct dp_display *dp_display, + struct drm_connector *connector, + struct edid *edid) +{ + int rc = 0; + struct sde_connector *sde_conn; + struct dp_panel *dp_panel; + struct dp_display_private *dp; + + if (!dp_display || !connector || !edid) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + if (!dp->mst.drm_registered) { + pr_debug("drm mst not registered\n"); + return -EPERM; + } + + sde_conn = to_sde_connector(connector); + if (!sde_conn->drv_panel) { + pr_err("invalid panel for connector:%d\n", connector->base.id); + return -EINVAL; + } + + dp_panel = sde_conn->drv_panel; + rc = dp_panel->update_edid(dp_panel, edid); + + DP_MST_DEBUG("dp mst connector:%d edid updated. mode_cnt:%d\n", + connector->base.id, rc); + + return rc; +} + +static int dp_display_get_mst_caps(struct dp_display *dp_display, + struct dp_mst_caps *mst_caps) +{ + int rc = 0; + struct dp_display_private *dp; + + if (!dp_display || !mst_caps) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + mst_caps->has_mst = dp->parser->has_mst; + mst_caps->max_streams_supported = (mst_caps->has_mst) ? 2 : 0; + mst_caps->max_dpcd_transaction_bytes = (mst_caps->has_mst) ? 16 : 0; + mst_caps->drm_aux = dp->aux->drm_aux; + + return rc; +} + +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"); + rc = -ENODEV; + goto bail; + } + + dp = devm_kzalloc(&pdev->dev, sizeof(*dp), GFP_KERNEL); + if (!dp) { + rc = -ENOMEM; + goto bail; + } + + init_completion(&dp->notification_comp); + + dp->pdev = pdev; + dp->name = "drm_dp"; + + memset(&dp->mst, 0, sizeof(dp->mst)); + atomic_set(&dp->aborted, 0); + + rc = dp_display_init_aux_switch(dp); + if (rc) { + rc = -EPROBE_DEFER; + goto error; + } + + rc = dp_display_create_workqueue(dp); + if (rc) { + pr_err("Failed to create workqueue\n"); + goto error; + } + + 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->post_open = dp_display_post_open; + g_dp_display->post_init = dp_display_post_init; + g_dp_display->config_hdr = dp_display_config_hdr; + g_dp_display->mst_install = dp_display_mst_install; + g_dp_display->mst_uninstall = dp_display_mst_uninstall; + g_dp_display->mst_connector_install = dp_display_mst_connector_install; + g_dp_display->mst_connector_uninstall = + dp_display_mst_connector_uninstall; + g_dp_display->mst_connector_update_edid = + dp_display_mst_connector_update_edid; + g_dp_display->get_mst_caps = dp_display_get_mst_caps; + g_dp_display->set_stream_info = dp_display_set_stream_info; + g_dp_display->convert_to_dp_mode = dp_display_convert_to_dp_mode; + + rc = component_add(&pdev->dev, &dp_display_comp_ops); + if (rc) { + pr_err("component add failed, rc=%d\n", rc); + goto error; + } + + return 0; +error: + devm_kfree(&pdev->dev, dp); +bail: + 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) +{ + if (!g_dp_display) + return 0; + + return 1; +} + +int dp_display_get_num_of_streams(void) +{ + return 2; +} + +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); + + if (dp->wq) + destroy_workqueue(dp->wq); + + 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, + .suppress_bind_attrs = true, + }, +}; + +static int __init dp_display_init(void) +{ + int ret; + + ret = platform_driver_register(&dp_display_driver); + if (ret) { + pr_err("driver register failed\n"); + return ret; + } + + return ret; +} +late_initcall(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..0e1e422912a985db86826ce655ea227a795f22ff --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_display.h @@ -0,0 +1,101 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DP_DISPLAY_H_ +#define _DP_DISPLAY_H_ + +#include +#include +#include + +#include "dp_panel.h" + +struct dp_mst_hdp_info { + bool mst_protocol; + u8 *edid; +}; + +struct dp_mst_drm_cbs { + void (*hpd)(void *display, bool hpd_status, + struct dp_mst_hdp_info *info); + void (*hpd_irq)(void *display); +}; + +struct dp_mst_drm_install_info { + void *dp_mst_prv_info; + const struct dp_mst_drm_cbs *cbs; +}; + +struct dp_mst_caps { + bool has_mst; + u32 max_streams_supported; + u32 max_dpcd_transaction_bytes; + struct drm_dp_aux *drm_aux; +}; + +struct dp_mst_connector { + bool debug_en; + int con_id; + int hdisplay; + int vdisplay; + int vrefresh; + int aspect_ratio; + struct drm_connector *conn; + struct mutex lock; + struct list_head list; +}; + +struct dp_display { + struct drm_device *drm_dev; + struct dp_bridge *bridge; + struct drm_connector *base_connector; + void *base_dp_panel; + bool is_sst_connected; + u32 max_pclk_khz; + void *dp_mst_prv_info; + + int (*enable)(struct dp_display *dp_display, void *panel); + int (*post_enable)(struct dp_display *dp_display, void *panel); + + int (*pre_disable)(struct dp_display *dp_display, void *panel); + int (*disable)(struct dp_display *dp_display, void *panel); + + int (*set_mode)(struct dp_display *dp_display, void *panel, + struct dp_display_mode *mode); + enum drm_mode_status (*validate_mode)(struct dp_display *dp_display, + void *panel, struct drm_display_mode *mode); + int (*get_modes)(struct dp_display *dp_display, void *panel, + struct dp_display_mode *dp_mode); + int (*prepare)(struct dp_display *dp_display, void *panel); + int (*unprepare)(struct dp_display *dp_display, void *panel); + int (*request_irq)(struct dp_display *dp_display); + struct dp_debug *(*get_debug)(struct dp_display *dp_display); + void (*post_open)(struct dp_display *dp_display); + int (*config_hdr)(struct dp_display *dp_display, void *panel, + struct drm_msm_ext_hdr_metadata *hdr_meta); + int (*post_init)(struct dp_display *dp_display); + int (*mst_install)(struct dp_display *dp_display, + struct dp_mst_drm_install_info *mst_install_info); + int (*mst_uninstall)(struct dp_display *dp_display); + int (*mst_connector_install)(struct dp_display *dp_display, + struct drm_connector *connector); + int (*mst_connector_uninstall)(struct dp_display *dp_display, + struct drm_connector *connector); + int (*mst_connector_update_edid)(struct dp_display *dp_display, + struct drm_connector *connector, + struct edid *edid); + int (*get_mst_caps)(struct dp_display *dp_display, + struct dp_mst_caps *mst_caps); + int (*set_stream_info)(struct dp_display *dp_display, void *panel, + u32 strm_id, u32 start_slot, u32 num_slots, u32 pbn); + void (*convert_to_dp_mode)(struct dp_display *dp_display, void *panel, + const struct drm_display_mode *drm_mode, + struct dp_display_mode *dp_mode); +}; + +int dp_display_get_num_of_displays(void); +int dp_display_get_displays(void **displays, int count); +int dp_display_get_num_of_streams(void); +#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..8e8f7fb6105edb6f59e4dc316b17cd09783b278e --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_drm.c @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#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 DP_MST_DEBUG(fmt, ...) pr_debug(fmt, ##__VA_ARGS__) + +#define to_dp_bridge(x) container_of((x), struct dp_bridge, base) + +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; + + if (!bridge->connector) { + pr_err("Invalid connector\n"); + return; + } + + if (!bridge->dp_panel) { + pr_err("Invalid dp_panel\n"); + return; + } + + /* By this point mode should have been validated through mode_fixup */ + rc = dp->set_mode(dp, bridge->dp_panel, &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, bridge->dp_panel); + if (rc) { + pr_err("[%d] DP display prepare failed, rc=%d\n", + bridge->id, rc); + return; + } + + /* for SST force stream id, start slot and total slots to 0 */ + dp->set_stream_info(dp, bridge->dp_panel, 0, 0, 0, 0); + + rc = dp->enable(dp, bridge->dp_panel); + if (rc) { + pr_err("[%d] DP display enable failed, rc=%d\n", + bridge->id, rc); + dp->unprepare(dp, bridge->dp_panel); + } +} + +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); + if (!bridge->connector) { + pr_err("Invalid connector\n"); + return; + } + + if (!bridge->dp_panel) { + pr_err("Invalid dp_panel\n"); + return; + } + + dp = bridge->display; + + rc = dp->post_enable(dp, bridge->dp_panel); + 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); + if (!bridge->connector) { + pr_err("Invalid connector\n"); + return; + } + + if (!bridge->dp_panel) { + pr_err("Invalid dp_panel\n"); + return; + } + + dp = bridge->display; + + if (!dp) { + pr_err("dp is null\n"); + return; + } + + if (dp) + sde_connector_helper_bridge_disable(bridge->connector); + + rc = dp->pre_disable(dp, bridge->dp_panel); + 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); + if (!bridge->connector) { + pr_err("Invalid connector\n"); + return; + } + + if (!bridge->dp_panel) { + pr_err("Invalid dp_panel\n"); + return; + } + + dp = bridge->display; + + rc = dp->disable(dp, bridge->dp_panel); + if (rc) { + pr_err("[%d] DP display disable failed, rc=%d\n", + bridge->id, rc); + return; + } + + rc = dp->unprepare(dp, bridge->dp_panel); + 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); + if (!bridge->connector) { + pr_err("Invalid connector\n"); + return; + } + + if (!bridge->dp_panel) { + pr_err("Invalid dp_panel\n"); + return; + } + + dp = bridge->display; + + dp->convert_to_dp_mode(dp, bridge->dp_panel, adjusted_mode, + &bridge->dp_mode); +} + +static bool dp_bridge_mode_fixup(struct drm_bridge *drm_bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + 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); + if (!bridge->connector) { + pr_err("Invalid connector\n"); + ret = false; + goto end; + } + + if (!bridge->dp_panel) { + pr_err("Invalid dp_panel\n"); + ret = false; + goto end; + } + + dp = bridge->display; + + dp->convert_to_dp_mode(dp, bridge->dp_panel, mode, &dp_mode); + 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_config_hdr(struct drm_connector *connector, void *display, + struct sde_connector_state *c_state) +{ + struct dp_display *dp = display; + struct sde_connector *sde_conn; + + if (!display || !c_state || !connector) { + pr_err("invalid params\n"); + return -EINVAL; + } + + sde_conn = to_sde_connector(connector); + if (!sde_conn->drv_panel) { + pr_err("invalid dp panel\n"); + return -EINVAL; + } + + return dp->config_hdr(dp, sde_conn->drv_panel, &c_state->hdr_meta); +} + +int dp_connector_post_init(struct drm_connector *connector, void *display) +{ + int rc; + struct dp_display *dp_display = display; + struct sde_connector *sde_conn; + + if (!dp_display || !connector) + return -EINVAL; + + dp_display->base_connector = connector; + dp_display->bridge->connector = connector; + + if (dp_display->post_init) { + rc = dp_display->post_init(dp_display); + if (rc) + goto end; + } + + sde_conn = to_sde_connector(connector); + dp_display->bridge->dp_panel = sde_conn->drv_panel; + + rc = dp_mst_init(dp_display); +end: + return rc; +} + +int dp_connector_get_mode_info(struct drm_connector *connector, + const struct drm_display_mode *drm_mode, + struct msm_mode_info *mode_info, + u32 max_mixer_width, void *display) +{ + 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; + } + + memset(mode_info, 0, sizeof(*mode_info)); + + 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; + + return 0; +} + +int dp_connector_get_info(struct drm_connector *connector, + struct msm_display_info *info, void *data) +{ + struct dp_display *display = data; + + if (!info || !display || !display->drm_dev) { + 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_sst_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(conn, &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_post_open(struct drm_connector *connector, void *display) +{ + struct dp_display *dp; + + if (!display) { + pr_err("invalid input\n"); + return; + } + + dp = display; + + if (dp->post_open) + dp->post_open(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; + struct sde_connector *sde_conn; + + if (!connector || !display) + return 0; + + sde_conn = to_sde_connector(connector); + if (!sde_conn->drv_panel) { + pr_err("invalid dp panel\n"); + 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_sst_connected) { + rc = dp->get_modes(dp, sde_conn->drv_panel, 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(encoder, &bridge->base, NULL); + 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 sde_connector *sde_conn; + + if (!mode || !display || !connector) { + pr_err("invalid params\n"); + return MODE_ERROR; + } + + sde_conn = to_sde_connector(connector); + if (!sde_conn->drv_panel) { + pr_err("invalid dp panel\n"); + return MODE_ERROR; + } + + dp_disp = display; + mode->vrefresh = drm_mode_vrefresh(mode); + + return dp_disp->validate_mode(dp_disp, sde_conn->drv_panel, mode); +} 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..8c94cef9d159741f93ad0b8bf5d9d4112a54e817 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_drm.h @@ -0,0 +1,147 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#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 drm_connector *connector; + struct dp_display *display; + struct dp_display_mode dp_mode; + void *dp_panel; +}; + +/** + * dp_connector_config_hdr - callback to configure HDR + * @connector: Pointer to drm connector structure + * @display: Pointer to private display handle + * @c_state: connect state data + * Returns: Zero on success + */ +int dp_connector_config_hdr(struct drm_connector *connector, + void *display, + struct sde_connector_state *c_state); + +/** + * dp_connector_post_init - callback to perform additional initialization steps + * @connector: Pointer to drm connector structure + * @display: Pointer to private display handle + * Returns: Zero on success + */ +int dp_connector_post_init(struct drm_connector *connector, 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 + * @connector: Pointer to drm connector structure + * @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 + * @display: Pointer to private display structure + * Returns: zero on success + */ +int dp_connector_get_mode_info(struct drm_connector *connector, + const struct drm_display_mode *drm_mode, + struct msm_mode_info *mode_info, + u32 max_mixer_width, void *display); + +/** + * dp_connector_get_info - retrieve connector display info + * @connector: Pointer to drm connector structure + * @info: Out parameter. Information of the connected display + * @display: Pointer to private display structure + * Returns: zero on success + */ +int dp_connector_get_info(struct drm_connector *connector, + struct msm_display_info *info, void *display); + +/** + * dp_connector_post_open - handle the post open functionalites + * @connector: Pointer to drm connector structure + * @display: Pointer to private display structure + */ +void dp_connector_post_open(struct drm_connector *connector, void *display); + +int dp_drm_bridge_init(void *display, + struct drm_encoder *encoder); + +void dp_drm_bridge_deinit(void *display); + +/** + * convert_to_drm_mode - convert dp mode to drm mode + * @dp_mode: Point to dp mode + * @drm_mode: Pointer to drm mode + */ +void convert_to_drm_mode(const struct dp_display_mode *dp_mode, + struct drm_display_mode *drm_mode); + +/** + * dp_mst_drm_bridge_init - initialize mst bridge + * @display: Pointer to private display structure + * @encoder: Pointer to encoder for mst bridge mapping + */ +int dp_mst_drm_bridge_init(void *display, + struct drm_encoder *encoder); + +/** + * dp_mst_drm_bridge_deinit - de-initialize mst bridges + * @display: Pointer to private display structure + */ +void dp_mst_drm_bridge_deinit(void *display); + +/** + * dp_mst_init - initialize mst objects for the given display + * @display: Pointer to private display structure + */ +int dp_mst_init(struct dp_display *dp_display); + +/** + * dp_mst_deinit - de-initialize mst objects for the given display + * @display: Pointer to private display structure + */ +void dp_mst_deinit(struct dp_display *dp_display); + +#endif /* _DP_DRM_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_gpio_hpd.c b/drivers/gpu/drm/msm/dp/dp_gpio_hpd.c new file mode 100644 index 0000000000000000000000000000000000000000..56da34664a4fa3092f9f8280f7ae7f4d0abdc377 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_gpio_hpd.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dp_gpio_hpd.h" + +struct dp_gpio_hpd_private { + struct device *dev; + struct dp_hpd base; + struct dss_gpio gpio_cfg; + struct delayed_work work; + struct dp_hpd_cb *cb; + int irq; + bool hpd; +}; + +static int dp_gpio_hpd_connect(struct dp_gpio_hpd_private *gpio_hpd, bool hpd) +{ + int rc = 0; + + if (!gpio_hpd) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + gpio_hpd->base.hpd_high = hpd; + gpio_hpd->base.alt_mode_cfg_done = hpd; + gpio_hpd->base.hpd_irq = false; + + if (!gpio_hpd->cb || + !gpio_hpd->cb->configure || + !gpio_hpd->cb->disconnect) { + pr_err("invalid cb\n"); + rc = -EINVAL; + goto error; + } + + if (hpd) + rc = gpio_hpd->cb->configure(gpio_hpd->dev); + else + rc = gpio_hpd->cb->disconnect(gpio_hpd->dev); + +error: + return rc; +} + +static int dp_gpio_hpd_attention(struct dp_gpio_hpd_private *gpio_hpd) +{ + int rc = 0; + + if (!gpio_hpd) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + gpio_hpd->base.hpd_irq = true; + + if (gpio_hpd->cb && gpio_hpd->cb->attention) + rc = gpio_hpd->cb->attention(gpio_hpd->dev); + +error: + return rc; +} + +static irqreturn_t dp_gpio_isr(int unused, void *data) +{ + struct dp_gpio_hpd_private *gpio_hpd = data; + u32 const disconnect_timeout_retry = 50; + bool hpd; + int i; + + if (!gpio_hpd) + return IRQ_NONE; + + hpd = gpio_get_value_cansleep(gpio_hpd->gpio_cfg.gpio); + + if (!gpio_hpd->hpd && hpd) { + gpio_hpd->hpd = true; + queue_delayed_work(system_wq, &gpio_hpd->work, 0); + return IRQ_HANDLED; + } + + if (!gpio_hpd->hpd) + return IRQ_HANDLED; + + /* In DP 1.2 spec, 100msec is recommended for the detection + * of HPD connect event. Here we'll poll HPD status for + * 50x2ms = 100ms and if HPD is always low, we know DP is + * disconnected. If HPD is high, HPD_IRQ will be handled + */ + for (i = 0; i < disconnect_timeout_retry; i++) { + if (hpd) { + dp_gpio_hpd_attention(gpio_hpd); + return IRQ_HANDLED; + } + usleep_range(2000, 2100); + hpd = gpio_get_value_cansleep(gpio_hpd->gpio_cfg.gpio); + } + + gpio_hpd->hpd = false; + queue_delayed_work(system_wq, &gpio_hpd->work, 0); + return IRQ_HANDLED; +} + +static void dp_gpio_hpd_work(struct work_struct *work) +{ + struct delayed_work *dw = to_delayed_work(work); + struct dp_gpio_hpd_private *gpio_hpd = container_of(dw, + struct dp_gpio_hpd_private, work); + + if (gpio_hpd->hpd) { + devm_free_irq(gpio_hpd->dev, + gpio_hpd->irq, gpio_hpd); + devm_request_threaded_irq(gpio_hpd->dev, + gpio_hpd->irq, NULL, + dp_gpio_isr, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + "dp-gpio-intp", gpio_hpd); + dp_gpio_hpd_connect(gpio_hpd, true); + } else { + devm_free_irq(gpio_hpd->dev, + gpio_hpd->irq, gpio_hpd); + devm_request_threaded_irq(gpio_hpd->dev, + gpio_hpd->irq, NULL, + dp_gpio_isr, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "dp-gpio-intp", gpio_hpd); + dp_gpio_hpd_connect(gpio_hpd, false); + } +} + +static int dp_gpio_hpd_simulate_connect(struct dp_hpd *dp_hpd, bool hpd) +{ + int rc = 0; + struct dp_gpio_hpd_private *gpio_hpd; + + if (!dp_hpd) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + gpio_hpd = container_of(dp_hpd, struct dp_gpio_hpd_private, base); + + dp_gpio_hpd_connect(gpio_hpd, hpd); +error: + return rc; +} + +static int dp_gpio_hpd_simulate_attention(struct dp_hpd *dp_hpd, int vdo) +{ + int rc = 0; + struct dp_gpio_hpd_private *gpio_hpd; + + if (!dp_hpd) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + gpio_hpd = container_of(dp_hpd, struct dp_gpio_hpd_private, base); + + dp_gpio_hpd_attention(gpio_hpd); +error: + return rc; +} + +struct dp_hpd *dp_gpio_hpd_get(struct device *dev, + struct dp_hpd_cb *cb) +{ + int rc = 0; + const char *hpd_gpio_name = "qcom,dp-hpd-gpio"; + struct dp_gpio_hpd_private *gpio_hpd; + int edge; + + if (!dev || !cb) { + pr_err("invalid device\n"); + rc = -EINVAL; + goto error; + } + + gpio_hpd = devm_kzalloc(dev, sizeof(*gpio_hpd), GFP_KERNEL); + if (!gpio_hpd) { + rc = -ENOMEM; + goto error; + } + + gpio_hpd->gpio_cfg.gpio = of_get_named_gpio(dev->of_node, + hpd_gpio_name, 0); + if (!gpio_is_valid(gpio_hpd->gpio_cfg.gpio)) { + pr_err("%s gpio not specified\n", hpd_gpio_name); + rc = -EINVAL; + goto gpio_error; + } + + strlcpy(gpio_hpd->gpio_cfg.gpio_name, hpd_gpio_name, + sizeof(gpio_hpd->gpio_cfg.gpio_name)); + gpio_hpd->gpio_cfg.value = 0; + + rc = gpio_request(gpio_hpd->gpio_cfg.gpio, + gpio_hpd->gpio_cfg.gpio_name); + if (rc) { + pr_err("%s: failed to request gpio\n", hpd_gpio_name); + goto gpio_error; + } + gpio_direction_input(gpio_hpd->gpio_cfg.gpio); + + gpio_hpd->dev = dev; + gpio_hpd->cb = cb; + gpio_hpd->irq = gpio_to_irq(gpio_hpd->gpio_cfg.gpio); + gpio_hpd->hpd = gpio_get_value_cansleep(gpio_hpd->gpio_cfg.gpio); + edge = gpio_hpd->hpd ? IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING; + INIT_DELAYED_WORK(&gpio_hpd->work, dp_gpio_hpd_work); + + rc = devm_request_threaded_irq(dev, gpio_hpd->irq, NULL, + dp_gpio_isr, + edge | IRQF_ONESHOT, + "dp-gpio-intp", gpio_hpd); + if (rc) { + pr_err("Failed to request INTP threaded IRQ: %d\n", rc); + goto gpio_irq_error; + } + + gpio_hpd->base.simulate_connect = dp_gpio_hpd_simulate_connect; + gpio_hpd->base.simulate_attention = dp_gpio_hpd_simulate_attention; + + if (gpio_hpd->hpd) + queue_delayed_work(system_wq, &gpio_hpd->work, 0); + + return &gpio_hpd->base; + +gpio_irq_error: + devm_gpio_free(dev, gpio_hpd->gpio_cfg.gpio); +gpio_error: + devm_kfree(dev, gpio_hpd); +error: + return ERR_PTR(rc); +} + +void dp_gpio_hpd_put(struct dp_hpd *dp_hpd) +{ + struct dp_gpio_hpd_private *gpio_hpd; + + if (!dp_hpd) + return; + + gpio_hpd = container_of(dp_hpd, struct dp_gpio_hpd_private, base); + + gpio_free(gpio_hpd->gpio_cfg.gpio); + devm_kfree(gpio_hpd->dev, gpio_hpd); +} diff --git a/drivers/gpu/drm/msm/dp/dp_gpio_hpd.h b/drivers/gpu/drm/msm/dp/dp_gpio_hpd.h new file mode 100644 index 0000000000000000000000000000000000000000..c56612af9dc4fab4e5fc517c636a7ab4a4cefadd --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_gpio_hpd.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + + +#ifndef _DP_GPIO_HPD_H_ +#define _DP_GPIO_HPD_H_ + +#include "dp_hpd.h" + +/** + * dp_gpio_hpd_get() - configure and get the DisplayPlot HPD module data + * + * @dev: device instance of the caller + * return: pointer to allocated gpio hpd module data + * + * This function sets up the gpio hpd module + */ +struct dp_hpd *dp_gpio_hpd_get(struct device *dev, + struct dp_hpd_cb *cb); + +/** + * dp_gpio_hpd_put() + * + * Cleans up dp_hpd instance + * + * @hpd: instance of gpio_hpd + */ +void dp_gpio_hpd_put(struct dp_hpd *hpd); + +#endif /* _DP_GPIO_HPD_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..d03b24f29c68bf92f37cec3e4c353e425d33b5c4 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_hdcp2p2.c @@ -0,0 +1,925 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[dp-hdcp2p2] %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sde_hdcp_2x.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 sde_hdcp_2x_ops *lib; /* Ops for driver to call into TZ */ + enum hdcp_transport_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; + struct hdcp2_buffer response; + struct hdcp2_buffer request; + uint32_t total_message_length; + uint32_t timeout; + struct sde_hdcp_2x_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_TRANSPORT_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_transport_wakeup_data *data) +{ + int i = 0; + uint32_t num_messages = 0; + + if (!data || !data->message_data) + return 0; + + mutex_lock(&ctrl->msg_lock); + + ctrl->timeout = data->timeout; + num_messages = data->message_data->num_messages; + ctrl->total_message_length = 0; /* Total length of all messages */ + + for (i = 0; i < num_messages; i++) + ctrl->total_message_length += + 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 (!ctrl->total_message_length) { + mutex_unlock(&ctrl->msg_lock); + return 0; + } + + ctrl->response.data = data->buf; + ctrl->response.length = ctrl->total_message_length; + ctrl->request.data = data->buf; + ctrl->request.length = ctrl->total_message_length; + + mutex_unlock(&ctrl->msg_lock); + + return 0; +} + +static int dp_hdcp2p2_wakeup(struct hdcp_transport_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; + + pr_debug("%s\n", hdcp_transport_cmd_to_str(ctrl->wakeup_cmd)); + + if (ctrl->wakeup_cmd == HDCP_TRANSPORT_CMD_STATUS_SUCCESS) + ctrl->auth_status = DP_HDCP_AUTH_STATUS_SUCCESS; + else if (ctrl->wakeup_cmd == HDCP_TRANSPORT_CMD_STATUS_FAILED) + ctrl->auth_status = DP_HDCP_AUTH_STATUS_FAILURE; + + switch (ctrl->wakeup_cmd) { + case HDCP_TRANSPORT_CMD_SEND_MESSAGE: + kthread_queue_work(&ctrl->worker, &ctrl->send_msg); + break; + case HDCP_TRANSPORT_CMD_RECV_MESSAGE: + kthread_queue_work(&ctrl->worker, &ctrl->recv_msg); + break; + case HDCP_TRANSPORT_CMD_STATUS_SUCCESS: + case HDCP_TRANSPORT_CMD_STATUS_FAILED: + kthread_queue_work(&ctrl->worker, &ctrl->status); + break; + case HDCP_TRANSPORT_CMD_LINK_POLL: + if (ctrl->cp_irq_done) + kthread_queue_work(&ctrl->worker, &ctrl->recv_msg); + else + ctrl->polling = true; + break; + case HDCP_TRANSPORT_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 sde_hdcp_2x_wakeup_data *data) +{ + int rc = 0; + + if (ctrl && ctrl->lib && ctrl->lib->wakeup && + data && (data->cmd != HDCP_2X_CMD_INVALID)) { + rc = ctrl->lib->wakeup(data); + if (rc) + pr_err("error sending %s to lib\n", + sde_hdcp_2x_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.dp_ahb->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_transport_wakeup_data cdata = { + HDCP_TRANSPORT_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_transport_wakeup_data cdata = { + HDCP_TRANSPORT_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 sde_hdcp_2x_wakeup_data cdata = { + HDCP_2X_CMD_QUERY_STREAM_TYPE}; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + pr_debug("enc level changed %d\n", min_enc_level); + + cdata.context = ctrl->lib_ctx; + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); +} + +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) +{ + int rc = 0, max_size = 16, read_size = 0, bytes_read = 0; + int size = ctrl->request.length, offset = ctrl->msg_part->offset; + u8 *buf = ctrl->request.data; + + if (atomic_read(&ctrl->auth_state) == HDCP_STATE_INACTIVE) { + pr_err("hdcp is off\n"); + rc = -EINVAL; + goto exit; + } + + if (!buf) { + pr_err("invalid request buffer\n"); + rc = -EINVAL; + goto exit; + } + + pr_debug("request: offset(0x%x), size(%d)\n", offset, size); + + 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); + rc = -EINVAL; + break; + } + + buf += read_size; + offset += read_size; + size -= read_size; + } while (size > 0); + +exit: + 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); + rc = -EINVAL; + 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 sde_hdcp_2x_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_force_encryption(void *data, bool enable) +{ + struct dp_hdcp2p2_ctrl *ctrl = data; + struct sde_hdcp_2x_ops *lib = NULL; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + lib = ctrl->lib; + if (!lib) { + pr_err("invalid lib ops data\n"); + return; + } + + if (lib->force_encryption) + lib->force_encryption(ctrl->lib_ctx, enable); +} + +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 sde_hdcp_2x_wakeup_data cdata = {HDCP_2X_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; + } + + print_hex_dump(KERN_DEBUG, ": ", + DUMP_PREFIX_NONE, 16, 1, ctrl->response.data, + ctrl->response.length, false); + + mutex_lock(&ctrl->msg_lock); + + rc = dp_hdcp2p2_aux_write_message(ctrl, ctrl->response.data, + ctrl->response.length, 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_2X_CMD_MSG_SEND_SUCCESS; + cdata.timeout = ctrl->timeout; + mutex_unlock(&ctrl->msg_lock); + +exit: + if (rc == -ETIMEDOUT) + cdata.cmd = HDCP_2X_CMD_MSG_SEND_TIMEOUT; + else if (rc) + cdata.cmd = HDCP_2X_CMD_MSG_SEND_FAILED; + + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); +} + +static int dp_hdcp2p2_get_msg_from_sink(struct dp_hdcp2p2_ctrl *ctrl) +{ + int rc = 0; + struct sde_hdcp_2x_wakeup_data cdata = { HDCP_2X_CMD_INVALID }; + + cdata.context = ctrl->lib_ctx; + + rc = dp_hdcp2p2_aux_read_message(ctrl); + if (rc) { + pr_err("error reading message %d\n", rc); + goto exit; + } + + cdata.total_message_length = ctrl->total_message_length; + cdata.timeout = ctrl->timeout; +exit: + if (rc == -ETIMEDOUT) + cdata.cmd = HDCP_2X_CMD_MSG_RECV_TIMEOUT; + else if (rc) + cdata.cmd = HDCP_2X_CMD_MSG_RECV_FAILED; + else + cdata.cmd = HDCP_2X_CMD_MSG_RECV_SUCCESS; + + dp_hdcp2p2_wakeup_lib(ctrl, &cdata); + + return rc; +} + +static void dp_hdcp2p2_recv_msg_work(struct kthread_work *work) +{ + struct sde_hdcp_2x_wakeup_data cdata = { HDCP_2X_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 sde_hdcp_2x_wakeup_data cdata = {HDCP_2X_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_2X_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 sde_hdcp_2x_wakeup_data cdata = {HDCP_2X_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_2X_CMD_START; + else + cdata.cmd = HDCP_2X_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 = false; + + *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.dp_ahb) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + io = ctrl->init_data.dp_ahb; + 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; +} + +static bool dp_hdcp2p2_supported(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = input; + u32 const rxcaps_dpcd_offset = 0x6921d; + ssize_t bytes_read = 0; + u8 buf[DP_HDCP_RXCAPS_LENGTH]; + + pr_debug("Checking sink capability\n"); + + 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; +} + +void sde_dp_hdcp2p2_deinit(void *input) +{ + struct dp_hdcp2p2_ctrl *ctrl = (struct dp_hdcp2p2_ctrl *)input; + struct sde_hdcp_2x_wakeup_data cdata = {HDCP_2X_CMD_INVALID}; + + if (!ctrl) { + pr_err("invalid input\n"); + return; + } + + cdata.cmd = HDCP_2X_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); + kfree(ctrl); +} + +void *sde_dp_hdcp2p2_init(struct sde_hdcp_init_data *init_data) +{ + int rc; + struct dp_hdcp2p2_ctrl *ctrl; + static struct sde_hdcp_ops ops = { + .isr = dp_hdcp2p2_isr, + .reauthenticate = dp_hdcp2p2_reauthenticate, + .authenticate = dp_hdcp2p2_authenticate, + .feature_supported = dp_hdcp2p2_feature_supported, + .force_encryption = dp_hdcp2p2_force_encryption, + .sink_support = dp_hdcp2p2_supported, + .off = dp_hdcp2p2_off, + .cp_irq = dp_hdcp2p2_cp_irq, + }; + + static struct hdcp_transport_ops client_ops = { + .wakeup = dp_hdcp2p2_wakeup, + }; + 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} + }; + static struct sde_hdcp_2x_ops hdcp2x_ops; + struct sde_hdcp_2x_register_data register_data = {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 = &hdcp2x_ops; + ctrl->response.data = NULL; + ctrl->request.data = 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_data = &ctrl->lib_ctx; + register_data.client_ops = &client_ops; + register_data.ops = &hdcp2x_ops; + register_data.device_type = HDCP_TXMTR_DP; + register_data.client_data = ctrl; + + rc = sde_hdcp_2x_register(®ister_data); + if (rc) { + pr_err("Unable to register with HDCP 2.2 library\n"); + goto error; + } + + if (IS_ENABLED(CONFIG_HDCP_QSEECOM)) + msm_hdcp_register_cb(init_data->msm_hdcp_dev, ctrl, + dp_hdcp2p2_min_level_change); + + 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); +} + +struct sde_hdcp_ops *sde_dp_hdcp2p2_get(void *input) +{ + return ((struct dp_hdcp2p2_ctrl *)input)->ops; +} diff --git a/drivers/gpu/drm/msm/dp/dp_hpd.c b/drivers/gpu/drm/msm/dp/dp_hpd.c new file mode 100644 index 0000000000000000000000000000000000000000..d6040662a193b59a0a37bd83069f9670b5a4c697 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_hpd.c @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include +#include +#include + +#include "dp_hpd.h" +#include "dp_usbpd.h" +#include "dp_gpio_hpd.h" + +struct dp_hpd *dp_hpd_get(struct device *dev, + struct dp_parser *parser, struct dp_hpd_cb *cb) +{ + struct dp_hpd *dp_hpd; + + if (parser->no_aux_switch) { + dp_hpd = dp_gpio_hpd_get(dev, cb); + if (!dp_hpd) { + pr_err("failed to get gpio hpd\n"); + goto out; + } + dp_hpd->type = DP_HPD_GPIO; + } else { + dp_hpd = dp_usbpd_get(dev, cb); + if (!dp_hpd) { + pr_err("failed to get usbpd\n"); + goto out; + } + dp_hpd->type = DP_HPD_USBPD; + } + +out: + return dp_hpd; +} + +void dp_hpd_put(struct dp_hpd *dp_hpd) +{ + if (!dp_hpd) + return; + + switch (dp_hpd->type) { + case DP_HPD_USBPD: + dp_usbpd_put(dp_hpd); + break; + case DP_HPD_GPIO: + dp_gpio_hpd_put(dp_hpd); + break; + default: + pr_err("unknown hpd type %d\n", dp_hpd->type); + break; + } +} diff --git a/drivers/gpu/drm/msm/dp/dp_hpd.h b/drivers/gpu/drm/msm/dp/dp_hpd.h new file mode 100644 index 0000000000000000000000000000000000000000..b37ee27e3bff0186a45899da0fcc21ef2fc34671 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_hpd.h @@ -0,0 +1,89 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DP_HPD_H_ +#define _DP_HPD_H_ + +#include +#include +#include +#include "dp_parser.h" + +/** + * enum dp_hpd_type - dp hpd type + * @DP_HPD_USBPD: USB type-c based HPD + * @DP_HPD_GPIO: GPIO based HPD + * @DP_HPD_BUILTIN: Controller built-in HPD + */ + +enum dp_hpd_type { + DP_HPD_USBPD, + DP_HPD_GPIO, + DP_HPD_BUILTIN, +}; + +/** + * struct dp_hpd_cb - callback functions provided by the client + * + * @configure: called when dp connection is ready. + * @disconnect: notify the cable disconnect event. + * @attention: notify any attention message event. + */ +struct dp_hpd_cb { + int (*configure)(struct device *dev); + int (*disconnect)(struct device *dev); + int (*attention)(struct device *dev); +}; + +/** + * struct dp_hpd - DisplayPort HPD status + * + * @type: type of HPD + * @orientation: plug orientation configuration, USBPD type only. + * @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 + * @switch_needed: bool to specify if switch is needed + * @multi_func: multi-function preferred, USBPD type only + * @isr: event interrupt, BUILTIN type only + * @simulate_connect: simulate disconnect or connect for debug mode + * @simulate_attention: simulate attention messages for debug mode + */ +struct dp_hpd { + enum dp_hpd_type type; + enum plug_orientation orientation; + bool hpd_high; + bool hpd_irq; + bool alt_mode_cfg_done; + bool multi_func; + + void (*isr)(struct dp_hpd *dp_hpd, int event); + int (*simulate_connect)(struct dp_hpd *dp_hpd, bool hpd); + int (*simulate_attention)(struct dp_hpd *dp_hpd, int vdo); +}; + +/** + * dp_hpd_get() - configure and get the DisplayPlot HPD module data + * + * @dev: device instance of the caller + * @parser: DP parser + * @cb: callback function for HPD response + * return: pointer to allocated hpd module data + * + * This function sets up the hpd module + */ +struct dp_hpd *dp_hpd_get(struct device *dev, + struct dp_parser *parser, struct dp_hpd_cb *cb); + +/** + * dp_hpd_put() + * + * Cleans up dp_hpd instance + * + * @dp_hpd: instance of dp_hpd + */ +void dp_hpd_put(struct dp_hpd *dp_hpd); + +#endif /* _DP_HPD_H_ */ diff --git a/drivers/gpu/drm/msm/dp/dp_link.c b/drivers/gpu/drm/msm/dp/dp_link.c new file mode 100644 index 0000000000000000000000000000000000000000..5a77062193e691ab1a003716340ce654ecfad38c --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_link.c @@ -0,0 +1,1520 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#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\n", 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_CP2520_PATTERN_1: + case DP_TEST_PHY_PATTERN_CP2520_PATTERN_3: + 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; +} + +/** + * 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")); + + 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..b1a49236b55d030e36ac02e7ce435a3a1fcafca3 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_link.h @@ -0,0 +1,201 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#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_hdcp_status { + int hdcp_state; + int hdcp_version; +}; + +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; +}; + +static inline 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"; + } +} + +struct dp_link { + u32 sink_request; + u32 test_response; + + 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; + struct dp_link_hdcp_status hdcp_status; + + 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_CP2520_PATTERN_1: + return DP_LINK_ENUM_STR(DP_TEST_PHY_PATTERN_CP2520_PATTERN_1); + case DP_TEST_PHY_PATTERN_CP2520_PATTERN_2: + return DP_LINK_ENUM_STR(DP_TEST_PHY_PATTERN_CP2520_PATTERN_2); + case DP_TEST_PHY_PATTERN_CP2520_PATTERN_3: + return DP_LINK_ENUM_STR(DP_TEST_PHY_PATTERN_CP2520_PATTERN_3); + 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_mst_drm.c b/drivers/gpu/drm/msm/dp/dp_mst_drm.c new file mode 100644 index 0000000000000000000000000000000000000000..cca0034b3ec51577d6ef009ab48fde4b8995ce20 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_mst_drm.c @@ -0,0 +1,1499 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp-mst]: %s: " fmt, __func__ + +#include +#include +#include + +#include +#include +#include +#include + +#include "msm_drv.h" +#include "msm_kms.h" +#include "sde_connector.h" +#include "dp_drm.h" + +#define DP_MST_DEBUG(fmt, ...) pr_debug(fmt, ##__VA_ARGS__) + +#define MAX_DP_MST_STREAMS 2 +#define MAX_DP_MST_DRM_ENCODERS 2 +#define MAX_DP_MST_DRM_BRIDGES 2 +#define DP_MST_SIM_MAX_PORTS 2 + +struct dp_drm_mst_fw_helper_ops { + int (*calc_pbn_mode)(int clock, int bpp); + int (*find_vcpi_slots)(struct drm_dp_mst_topology_mgr *mgr, int pbn); + int (*atomic_find_vcpi_slots)(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, int pbn); + bool (*allocate_vcpi)(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, + int pbn, int slots); + int (*update_payload_part1)(struct drm_dp_mst_topology_mgr *mgr); + int (*check_act_status)(struct drm_dp_mst_topology_mgr *mgr); + int (*update_payload_part2)(struct drm_dp_mst_topology_mgr *mgr); + enum drm_connector_status (*detect_port)( + struct drm_connector *connector, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port); + struct edid *(*get_edid)(struct drm_connector *connector, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port); + int (*topology_mgr_set_mst)(struct drm_dp_mst_topology_mgr *mgr, + bool mst_state); + int (*atomic_release_vcpi_slots)(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr, + int slots); + void (*get_vcpi_info)(struct drm_dp_mst_topology_mgr *mgr, + int vcpi, int *start_slot, int *num_slots); + void (*reset_vcpi_slots)(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port); + void (*deallocate_vcpi)(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port); +}; + +struct dp_mst_sim_port { + struct drm_dp_mst_port port; + int port_id; + int vcpi; + int slots; + int start_slot; + struct drm_connector *connector; + struct edid *edid; + enum drm_connector_status status; +}; + +struct dp_mst_sim_mode { + bool mst_state; + struct dp_mst_sim_port sim_port[DP_MST_SIM_MAX_PORTS]; + int vcpi[DP_MST_SIM_MAX_PORTS]; + int free_slot_id; + struct edid *edid; + struct work_struct probe_work; + struct work_struct conn_destroy_work; + const struct drm_dp_mst_topology_cbs *cbs; + int pbn_div; +}; + +struct dp_mst_bridge { + struct drm_bridge base; + u32 id; + + bool in_use; + + struct dp_display *display; + struct drm_encoder *encoder; + bool encoder_active_sts; + + struct drm_display_mode drm_mode; + struct dp_display_mode dp_mode; + struct drm_connector *connector; + void *dp_panel; + + int vcpi; + int pbn; + int num_slots; + int start_slot; +}; + +struct dp_mst_private { + bool mst_initialized; + struct dp_mst_caps caps; + struct drm_dp_mst_topology_mgr mst_mgr; + struct dp_mst_bridge mst_bridge[MAX_DP_MST_DRM_BRIDGES]; + struct dp_display *dp_display; + const struct dp_drm_mst_fw_helper_ops *mst_fw_cbs; + struct dp_mst_sim_mode simulator; + struct mutex mst_lock; +}; + +#define to_dp_mst_bridge(x) container_of((x), struct dp_mst_bridge, base) + +struct dp_mst_private dp_mst; + +/* DRM DP MST Framework simulator OPs */ + +static void drm_dp_mst_sim_link_probe_work(struct work_struct *work) +{ + struct dp_mst_sim_mode *sim; + struct dp_mst_private *mst; + u8 cnt, dp_link_count; + u8 dpcd[DP_RECEIVER_CAP_SIZE]; + char pathprop[] = "0.1"; + int rc; + + DP_MST_DEBUG("enter\n"); + + sim = container_of(work, struct dp_mst_sim_mode, probe_work); + mst = container_of(sim, struct dp_mst_private, simulator); + + rc = drm_dp_dpcd_read(mst->caps.drm_aux, DP_DPCD_REV, + dpcd, DP_RECEIVER_CAP_SIZE); + if (rc != DP_RECEIVER_CAP_SIZE) { + pr_err("dpcd sync read failed, rlen=%d\n", rc); + return; + } + + dp_link_count = dpcd[2] & DP_MAX_LANE_COUNT_MASK; + + switch (dpcd[1]) { + default: + pr_err("invalid link bandwidth in DPCD: %x (link count: %d)\n", + dpcd[1], dp_link_count); + return; + + case DP_LINK_BW_1_62: + sim->pbn_div = 3 * dp_link_count; + break; + case DP_LINK_BW_2_7: + sim->pbn_div = 5 * dp_link_count; + break; + case DP_LINK_BW_5_4: + sim->pbn_div = 10 * dp_link_count; + break; + } + + for (cnt = 0; cnt < DP_MST_SIM_MAX_PORTS; cnt++) { + sim->sim_port[cnt].port_id = cnt; + + sim->sim_port[cnt].connector = sim->cbs->add_connector( + &mst->mst_mgr, + &sim->sim_port[cnt].port, + pathprop); + + sim->sim_port[cnt].status = connector_status_connected; + + sim->cbs->register_connector(sim->sim_port[cnt].connector); + } + + sim->free_slot_id = 0; + mst->mst_mgr.cbs->hotplug(&mst->mst_mgr); + + DP_MST_DEBUG("completed\n"); +} + +static void drm_dp_mst_sim_conn_destroy_work(struct work_struct *work) +{ + struct dp_mst_sim_mode *sim; + struct dp_mst_private *mst; + u8 cnt; + + DP_MST_DEBUG("enter\n"); + + sim = container_of(work, struct dp_mst_sim_mode, conn_destroy_work); + mst = container_of(sim, struct dp_mst_private, simulator); + + for (cnt = 0; cnt < DP_MST_SIM_MAX_PORTS; cnt++) { + + sim->sim_port[cnt].status = connector_status_disconnected; + + sim->cbs->destroy_connector(&mst->mst_mgr, + sim->sim_port[cnt].connector); + } + + mst->mst_mgr.cbs->hotplug(&mst->mst_mgr); + + DP_MST_DEBUG("completed\n"); +} + +static int drm_dp_sim_find_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, + int pbn) +{ + int num_slots; + struct dp_mst_private *mst = container_of(mgr, + struct dp_mst_private, mst_mgr); + + DP_MST_DEBUG("enter\n"); + + num_slots = DIV_ROUND_UP(pbn, mst->simulator.pbn_div); + DP_MST_DEBUG("num_slots:%d\n", num_slots); + + DP_MST_DEBUG("completed\n"); + + return num_slots; +} + +static int drm_dp_sim_atomic_find_vcpi_slots(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, int pbn) +{ + return drm_dp_sim_find_vcpi_slots(mgr, pbn); +} + +static bool drm_dp_sim_mst_allocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, int pbn, int slots) +{ + int i; + struct dp_mst_private *mst = container_of(mgr, + struct dp_mst_private, mst_mgr); + struct dp_mst_sim_port *sim_port = container_of(port, + struct dp_mst_sim_port, port); + + DP_MST_DEBUG("enter\n"); + + for (i = 0; i < DP_MST_SIM_MAX_PORTS; i++) { + if (!mst->simulator.vcpi[i]) + break; + } + + if (i == DP_MST_SIM_MAX_PORTS) { + pr_err("simulator allocate vcpi failed\n"); + return false; + } + + sim_port->vcpi = i + 1; /* vcpi starts from 1 */ + sim_port->slots = slots; + sim_port->start_slot = mst->simulator.free_slot_id; + mst->simulator.vcpi[i] = sim_port->vcpi; + mst->simulator.free_slot_id = mst->simulator.free_slot_id + slots; + + DP_MST_DEBUG("ch:%d, start_slot:%d, slots:%d\n", + sim_port->port_id, sim_port->start_slot, + sim_port->start_slot); + + DP_MST_DEBUG("completed\n"); + + return true; +} + +static int drm_dp_sim_atomic_release_vcpi_slots(struct drm_atomic_state *state, + struct drm_dp_mst_topology_mgr *mgr, + int slots) +{ + return 0; +} + +static int drm_dp_sim_update_payload_part1(struct drm_dp_mst_topology_mgr *mgr) +{ + return 0; +} + +static int drm_dp_sim_check_act_status(struct drm_dp_mst_topology_mgr *mgr) +{ + return 0; +} + +static int drm_dp_sim_update_payload_part2(struct drm_dp_mst_topology_mgr *mgr) +{ + return 0; +} + +static void drm_dp_sim_reset_vcpi_slots(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ +} + +static void drm_dp_sim_deallocate_vcpi(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + struct dp_mst_private *mst = container_of(mgr, + struct dp_mst_private, mst_mgr); + struct dp_mst_sim_port *sim_port = container_of(port, + struct dp_mst_sim_port, port); + + mst->simulator.vcpi[sim_port->vcpi - 1] = 0; + sim_port->vcpi = 0; +} + +static enum drm_connector_status drm_dp_sim_mst_detect_port( + struct drm_connector *connector, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + struct dp_mst_sim_port *sim_port; + + if (!port) + return connector_status_disconnected; + + sim_port = container_of(port, struct dp_mst_sim_port, port); + + return sim_port->status; +} + +static struct edid *drm_dp_sim_mst_get_edid(struct drm_connector *connector, + struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port) +{ + struct dp_mst_private *mst = container_of(mgr, + struct dp_mst_private, mst_mgr); + struct dp_mst_sim_port *sim_port = container_of(port, + struct dp_mst_sim_port, port); + + sim_port->edid = drm_edid_duplicate(mst->simulator.edid); + + return sim_port->edid; +} + +static int drm_dp_sim_mst_topology_mgr_set_mst( + struct drm_dp_mst_topology_mgr *mgr, + bool mst_state) +{ + struct dp_mst_private *mst = container_of(mgr, + struct dp_mst_private, mst_mgr); + + if (mst->simulator.mst_state == mst_state) + return 0; + + if (mst_state) + queue_work(system_long_wq, &mst->simulator.probe_work); + else + queue_work(system_long_wq, &mst->simulator.conn_destroy_work); + + mst->simulator.mst_state = mst_state; + + return 0; +} + +static void _drm_dp_sim_mst_get_vcpi_info( + struct drm_dp_mst_topology_mgr *mgr, + int vcpi, int *start_slot, int *num_slots) +{ + int i; + struct dp_mst_private *mst = container_of(mgr, + struct dp_mst_private, mst_mgr); + + *start_slot = 0; + *num_slots = 0; + + if (!mst->simulator.vcpi[vcpi - 1]) + return; + + for (i = 0; i < DP_MST_SIM_MAX_PORTS; i++) { + if (mst->simulator.sim_port[i].vcpi == vcpi) { + *start_slot = mst->simulator.sim_port[i].start_slot; + *num_slots = mst->simulator.sim_port[i].slots; + return; + } + } +} + +static void _drm_dp_mst_get_vcpi_info( + struct drm_dp_mst_topology_mgr *mgr, + int vcpi, int *start_slot, int *num_slots) +{ + int i; + + *start_slot = 0; + *num_slots = 0; + + mutex_lock(&mgr->payload_lock); + for (i = 0; i < mgr->max_payloads; i++) { + if (mgr->payloads[i].vcpi == vcpi) { + *start_slot = mgr->payloads[i].start_slot; + *num_slots = mgr->payloads[i].num_slots; + break; + } + } + mutex_unlock(&mgr->payload_lock); + + pr_info("vcpi_info. vcpi:%d, start_slot:%d, num_slots:%d\n", + vcpi, *start_slot, *num_slots); +} + +static const struct dp_drm_mst_fw_helper_ops drm_dp_mst_fw_helper_ops = { + .calc_pbn_mode = drm_dp_calc_pbn_mode, + .find_vcpi_slots = drm_dp_find_vcpi_slots, + .atomic_find_vcpi_slots = drm_dp_atomic_find_vcpi_slots, + .allocate_vcpi = drm_dp_mst_allocate_vcpi, + .update_payload_part1 = drm_dp_update_payload_part1, + .check_act_status = drm_dp_check_act_status, + .update_payload_part2 = drm_dp_update_payload_part2, + .detect_port = drm_dp_mst_detect_port, + .get_edid = drm_dp_mst_get_edid, + .topology_mgr_set_mst = drm_dp_mst_topology_mgr_set_mst, + .get_vcpi_info = _drm_dp_mst_get_vcpi_info, + .atomic_release_vcpi_slots = drm_dp_atomic_release_vcpi_slots, + .reset_vcpi_slots = drm_dp_mst_reset_vcpi_slots, + .deallocate_vcpi = drm_dp_mst_deallocate_vcpi, +}; + +static const struct dp_drm_mst_fw_helper_ops drm_dp_sim_mst_fw_helper_ops = { + .calc_pbn_mode = drm_dp_calc_pbn_mode, + .find_vcpi_slots = drm_dp_sim_find_vcpi_slots, + .atomic_find_vcpi_slots = drm_dp_sim_atomic_find_vcpi_slots, + .allocate_vcpi = drm_dp_sim_mst_allocate_vcpi, + .update_payload_part1 = drm_dp_sim_update_payload_part1, + .check_act_status = drm_dp_sim_check_act_status, + .update_payload_part2 = drm_dp_sim_update_payload_part2, + .detect_port = drm_dp_sim_mst_detect_port, + .get_edid = drm_dp_sim_mst_get_edid, + .topology_mgr_set_mst = drm_dp_sim_mst_topology_mgr_set_mst, + .get_vcpi_info = _drm_dp_sim_mst_get_vcpi_info, + .atomic_release_vcpi_slots = drm_dp_sim_atomic_release_vcpi_slots, + .reset_vcpi_slots = drm_dp_sim_reset_vcpi_slots, + .deallocate_vcpi = drm_dp_sim_deallocate_vcpi, +}; + +/* DP MST Bridge OPs */ + +static int dp_mst_bridge_attach(struct drm_bridge *dp_bridge) +{ + struct dp_mst_bridge *bridge; + + DP_MST_DEBUG("enter\n"); + + if (!dp_bridge) { + pr_err("Invalid params\n"); + return -EINVAL; + } + + bridge = to_dp_mst_bridge(dp_bridge); + + DP_MST_DEBUG("mst bridge [%d] attached\n", bridge->id); + + return 0; +} + +static bool dp_mst_bridge_mode_fixup(struct drm_bridge *drm_bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + bool ret = true; + struct dp_display_mode dp_mode; + struct dp_mst_bridge *bridge; + struct dp_display *dp; + + DP_MST_DEBUG("enter\n"); + + if (!drm_bridge || !mode || !adjusted_mode) { + pr_err("Invalid params\n"); + ret = false; + goto end; + } + + bridge = to_dp_mst_bridge(drm_bridge); + if (!bridge->connector) { + pr_err("Invalid connector\n"); + ret = false; + goto end; + } + + if (!bridge->dp_panel) { + pr_err("Invalid dp_panel\n"); + ret = false; + goto end; + } + + dp = bridge->display; + + dp->convert_to_dp_mode(dp, bridge->dp_panel, mode, &dp_mode); + convert_to_drm_mode(&dp_mode, adjusted_mode); + + DP_MST_DEBUG("mst bridge [%d] mode:%s fixup\n", bridge->id, mode->name); +end: + return ret; +} + +static int _dp_mst_compute_config(struct drm_atomic_state *state, + struct dp_mst_private *mst, struct drm_connector *connector, + struct dp_display_mode *mode) +{ + int slots = 0, pbn; + struct sde_connector *c_conn = to_sde_connector(connector); + int rc = 0; + + DP_MST_DEBUG("enter\n"); + + pbn = mst->mst_fw_cbs->calc_pbn_mode(mode->timing.pixel_clk_khz, + mode->timing.bpp); + + slots = mst->mst_fw_cbs->atomic_find_vcpi_slots(state, + &mst->mst_mgr, c_conn->mst_port, pbn); + if (slots < 0) { + pr_err("mst: failed to find vcpi slots. pbn:%d, slots:%d\n", + pbn, slots); + return slots; + } + + DP_MST_DEBUG("exit\n"); + + return rc; +} + +static void _dp_mst_update_timeslots(struct dp_mst_private *mst, + struct dp_mst_bridge *mst_bridge) +{ + int i; + struct dp_mst_bridge *dp_bridge; + int pbn, start_slot, num_slots; + + for (i = 0; i < MAX_DP_MST_DRM_BRIDGES; i++) { + dp_bridge = &mst->mst_bridge[i]; + + pbn = 0; + start_slot = 0; + num_slots = 0; + + if (dp_bridge->vcpi) { + mst->mst_fw_cbs->get_vcpi_info(&mst->mst_mgr, + dp_bridge->vcpi, + &start_slot, &num_slots); + pbn = dp_bridge->pbn; + } + + if (mst_bridge == dp_bridge) + dp_bridge->num_slots = num_slots; + + mst->dp_display->set_stream_info(mst->dp_display, + dp_bridge->dp_panel, + dp_bridge->id, start_slot, num_slots, pbn); + + pr_info("bridge:%d vcpi:%d start_slot:%d num_slots:%d, pbn:%d\n", + dp_bridge->id, dp_bridge->vcpi, + start_slot, num_slots, pbn); + } +} + +static void _dp_mst_bridge_pre_enable_part1(struct dp_mst_bridge *dp_bridge) +{ + struct dp_display *dp_display = dp_bridge->display; + struct sde_connector *c_conn = + to_sde_connector(dp_bridge->connector); + struct dp_mst_private *mst = dp_display->dp_mst_prv_info; + struct drm_dp_mst_port *port = c_conn->mst_port; + bool ret; + int pbn, slots; + + pbn = mst->mst_fw_cbs->calc_pbn_mode( + dp_bridge->dp_mode.timing.pixel_clk_khz, + dp_bridge->dp_mode.timing.bpp); + + slots = mst->mst_fw_cbs->find_vcpi_slots(&mst->mst_mgr, pbn); + + pr_info("bridge:%d, pbn:%d, slots:%d\n", dp_bridge->id, + dp_bridge->pbn, dp_bridge->num_slots); + + ret = mst->mst_fw_cbs->allocate_vcpi(&mst->mst_mgr, + port, pbn, slots); + if (!ret) { + pr_err("mst: failed to allocate vcpi. bridge:%d\n", + dp_bridge->id); + return; + } + + dp_bridge->vcpi = port->vcpi.vcpi; + dp_bridge->pbn = pbn; + + ret = mst->mst_fw_cbs->update_payload_part1(&mst->mst_mgr); + + _dp_mst_update_timeslots(mst, dp_bridge); +} + +static void _dp_mst_bridge_pre_enable_part2(struct dp_mst_bridge *dp_bridge) +{ + struct dp_display *dp_display = dp_bridge->display; + struct dp_mst_private *mst = dp_display->dp_mst_prv_info; + + DP_MST_DEBUG("enter\n"); + + mst->mst_fw_cbs->check_act_status(&mst->mst_mgr); + + mst->mst_fw_cbs->update_payload_part2(&mst->mst_mgr); + + DP_MST_DEBUG("mst bridge [%d] _pre enable part-2 complete\n", + dp_bridge->id); +} + +static void _dp_mst_bridge_pre_disable_part1(struct dp_mst_bridge *dp_bridge) +{ + struct dp_display *dp_display = dp_bridge->display; + struct sde_connector *c_conn = + to_sde_connector(dp_bridge->connector); + struct dp_mst_private *mst = dp_display->dp_mst_prv_info; + struct drm_dp_mst_port *port = c_conn->mst_port; + + DP_MST_DEBUG("enter\n"); + + mst->mst_fw_cbs->reset_vcpi_slots(&mst->mst_mgr, port); + + mst->mst_fw_cbs->update_payload_part1(&mst->mst_mgr); + + _dp_mst_update_timeslots(mst, dp_bridge); + + DP_MST_DEBUG("mst bridge [%d] _pre disable part-1 complete\n", + dp_bridge->id); +} + +static void _dp_mst_bridge_pre_disable_part2(struct dp_mst_bridge *dp_bridge) +{ + struct dp_display *dp_display = dp_bridge->display; + struct dp_mst_private *mst = dp_display->dp_mst_prv_info; + struct sde_connector *c_conn = + to_sde_connector(dp_bridge->connector); + struct drm_dp_mst_port *port = c_conn->mst_port; + + DP_MST_DEBUG("enter\n"); + + mst->mst_fw_cbs->check_act_status(&mst->mst_mgr); + + mst->mst_fw_cbs->update_payload_part2(&mst->mst_mgr); + + mst->mst_fw_cbs->deallocate_vcpi(&mst->mst_mgr, port); + + dp_bridge->vcpi = 0; + dp_bridge->pbn = 0; + + DP_MST_DEBUG("mst bridge [%d] _pre disable part-2 complete\n", + dp_bridge->id); +} + +static void dp_mst_bridge_pre_enable(struct drm_bridge *drm_bridge) +{ + int rc = 0; + struct dp_mst_bridge *bridge; + struct dp_display *dp; + struct dp_mst_private *mst; + + DP_MST_DEBUG("enter\n"); + + if (!drm_bridge) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_mst_bridge(drm_bridge); + dp = bridge->display; + + if (!bridge->connector) { + pr_err("Invalid connector\n"); + return; + } + + mst = dp->dp_mst_prv_info; + + mutex_lock(&mst->mst_lock); + + /* By this point mode should have been validated through mode_fixup */ + rc = dp->set_mode(dp, bridge->dp_panel, &bridge->dp_mode); + if (rc) { + pr_err("[%d] failed to perform a mode set, rc=%d\n", + bridge->id, rc); + goto end; + } + + rc = dp->prepare(dp, bridge->dp_panel); + if (rc) { + pr_err("[%d] DP display prepare failed, rc=%d\n", + bridge->id, rc); + goto end; + } + + _dp_mst_bridge_pre_enable_part1(bridge); + + rc = dp->enable(dp, bridge->dp_panel); + if (rc) { + pr_err("[%d] DP display enable failed, rc=%d\n", + bridge->id, rc); + dp->unprepare(dp, bridge->dp_panel); + goto end; + } else { + _dp_mst_bridge_pre_enable_part2(bridge); + } + + DP_MST_DEBUG("mst bridge [%d] pre enable complete\n", bridge->id); +end: + mutex_unlock(&mst->mst_lock); +} + +static void dp_mst_bridge_enable(struct drm_bridge *drm_bridge) +{ + int rc = 0; + struct dp_mst_bridge *bridge; + struct dp_display *dp; + + DP_MST_DEBUG("enter\n"); + + if (!drm_bridge) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_mst_bridge(drm_bridge); + if (!bridge->connector) { + pr_err("Invalid connector\n"); + return; + } + + dp = bridge->display; + + rc = dp->post_enable(dp, bridge->dp_panel); + if (rc) { + pr_err("mst bridge [%d] post enable failed, rc=%d\n", + bridge->id, rc); + return; + } + + DP_MST_DEBUG("mst bridge [%d] post enable complete\n", bridge->id); +} + +static void dp_mst_bridge_disable(struct drm_bridge *drm_bridge) +{ + int rc = 0; + struct dp_mst_bridge *bridge; + struct dp_display *dp; + struct dp_mst_private *mst; + + DP_MST_DEBUG("enter\n"); + + if (!drm_bridge) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_mst_bridge(drm_bridge); + if (!bridge->connector) { + pr_err("Invalid connector\n"); + return; + } + + dp = bridge->display; + + mst = dp->dp_mst_prv_info; + + sde_connector_helper_bridge_disable(bridge->connector); + + mutex_lock(&mst->mst_lock); + + _dp_mst_bridge_pre_disable_part1(bridge); + + rc = dp->pre_disable(dp, bridge->dp_panel); + if (rc) + pr_err("[%d] DP display pre disable failed, rc=%d\n", + bridge->id, rc); + + _dp_mst_bridge_pre_disable_part2(bridge); + + DP_MST_DEBUG("mst bridge [%d] disable complete\n", bridge->id); + + mutex_unlock(&mst->mst_lock); +} + +static void dp_mst_bridge_post_disable(struct drm_bridge *drm_bridge) +{ + int rc = 0; + struct dp_mst_bridge *bridge; + struct dp_display *dp; + struct dp_mst_private *mst; + + DP_MST_DEBUG("enter\n"); + + if (!drm_bridge) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_mst_bridge(drm_bridge); + if (!bridge->connector) { + pr_err("Invalid connector\n"); + return; + } + + dp = bridge->display; + mst = dp->dp_mst_prv_info; + + rc = dp->disable(dp, bridge->dp_panel); + if (rc) { + pr_err("[%d] DP display disable failed, rc=%d\n", + bridge->id, rc); + return; + } + + rc = dp->unprepare(dp, bridge->dp_panel); + if (rc) { + pr_err("[%d] DP display unprepare failed, rc=%d\n", + bridge->id, rc); + return; + } + + /* Disconnect the connector and panel info from bridge */ + mst->mst_bridge[bridge->id].connector = NULL; + mst->mst_bridge[bridge->id].dp_panel = NULL; + mst->mst_bridge[bridge->id].encoder_active_sts = false; + + DP_MST_DEBUG("mst bridge [%d] post disable complete\n", bridge->id); +} + +static void dp_mst_bridge_mode_set(struct drm_bridge *drm_bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct dp_mst_bridge *bridge; + struct dp_display *dp; + + DP_MST_DEBUG("enter\n"); + + if (!drm_bridge || !mode || !adjusted_mode) { + pr_err("Invalid params\n"); + return; + } + + bridge = to_dp_mst_bridge(drm_bridge); + if (!bridge->connector) { + pr_err("Invalid connector\n"); + return; + } + + if (!bridge->dp_panel) { + pr_err("Invalid dp_panel\n"); + return; + } + + dp = bridge->display; + + memset(&bridge->dp_mode, 0x0, sizeof(struct dp_display_mode)); + memcpy(&bridge->drm_mode, adjusted_mode, sizeof(bridge->drm_mode)); + dp->convert_to_dp_mode(dp, bridge->dp_panel, adjusted_mode, + &bridge->dp_mode); + + DP_MST_DEBUG("mst bridge [%d] mode set complete\n", bridge->id); +} + +/* DP MST Bridge APIs */ + +static const struct drm_bridge_funcs dp_mst_bridge_ops = { + .attach = dp_mst_bridge_attach, + .mode_fixup = dp_mst_bridge_mode_fixup, + .pre_enable = dp_mst_bridge_pre_enable, + .enable = dp_mst_bridge_enable, + .disable = dp_mst_bridge_disable, + .post_disable = dp_mst_bridge_post_disable, + .mode_set = dp_mst_bridge_mode_set, +}; + +int dp_mst_drm_bridge_init(void *data, struct drm_encoder *encoder) +{ + int rc = 0; + struct dp_mst_bridge *bridge = NULL; + struct drm_device *dev; + struct dp_display *display = data; + struct msm_drm_private *priv = NULL; + struct dp_mst_private *mst = display->dp_mst_prv_info; + int i; + + if (!mst || !mst->mst_initialized) { + pr_err("mst not initialized\n"); + return -ENODEV; + } + + for (i = 0; i < MAX_DP_MST_DRM_BRIDGES; i++) { + if (!mst->mst_bridge[i].in_use) { + bridge = &mst->mst_bridge[i]; + bridge->encoder = encoder; + bridge->in_use = true; + bridge->id = i; + break; + } + } + + if (i == MAX_DP_MST_DRM_BRIDGES) { + pr_err("mst supports only %d bridges\n", i); + rc = -EACCES; + goto end; + } + + dev = display->drm_dev; + bridge->display = display; + bridge->base.funcs = &dp_mst_bridge_ops; + bridge->base.encoder = encoder; + + priv = dev->dev_private; + + rc = drm_bridge_attach(encoder, &bridge->base, NULL); + if (rc) { + pr_err("failed to attach bridge, rc=%d\n", rc); + goto end; + } + + encoder->bridge = &bridge->base; + priv->bridges[priv->num_bridges++] = &bridge->base; + + DP_MST_DEBUG("mst drm bridge init. bridge id:%d\n", i); + + return 0; + +end: + return rc; +} + +void dp_mst_drm_bridge_deinit(void *display) +{ + DP_MST_DEBUG("mst bridge deinit\n"); +} + +/* DP MST Connector OPs */ + +static enum drm_connector_status +dp_mst_connector_detect(struct drm_connector *connector, bool force, + void *display) +{ + struct sde_connector *c_conn = to_sde_connector(connector); + struct dp_display *dp_display = c_conn->display; + struct dp_mst_private *mst = dp_display->dp_mst_prv_info; + enum drm_connector_status status; + + DP_MST_DEBUG("enter:\n"); + + status = mst->mst_fw_cbs->detect_port(connector, + &mst->mst_mgr, + c_conn->mst_port); + + DP_MST_DEBUG("mst connector:%d detect, status:%d\n", + connector->base.id, status); + + DP_MST_DEBUG("exit:\n"); + + return status; +} + +static int dp_mst_connector_get_modes(struct drm_connector *connector, + void *display) +{ + struct sde_connector *c_conn = to_sde_connector(connector); + struct dp_display *dp_display = display; + struct dp_mst_private *mst = dp_display->dp_mst_prv_info; + struct edid *edid; + int rc = 0; + + DP_MST_DEBUG("enter:\n"); + + edid = mst->mst_fw_cbs->get_edid(connector, &mst->mst_mgr, + c_conn->mst_port); + + if (edid) + rc = dp_display->mst_connector_update_edid(dp_display, + connector, edid); + + DP_MST_DEBUG("mst connector get modes. id: %d\n", connector->base.id); + + DP_MST_DEBUG("exit:\n"); + + return rc; +} + +enum drm_mode_status dp_mst_connector_mode_valid( + struct drm_connector *connector, + struct drm_display_mode *mode, + void *display) +{ + enum drm_mode_status status; + + DP_MST_DEBUG("enter:\n"); + + status = dp_connector_mode_valid(connector, mode, display); + + DP_MST_DEBUG("mst connector:%d mode:%s valid status:%d\n", + connector->base.id, mode->name, status); + + DP_MST_DEBUG("exit:\n"); + + return status; +} + +int dp_mst_connector_get_info(struct drm_connector *connector, + struct msm_display_info *info, + void *display) +{ + int rc; + enum drm_connector_status status = connector_status_unknown; + + DP_MST_DEBUG("enter:\n"); + + rc = dp_connector_get_info(connector, info, display); + + if (!rc) { + status = dp_mst_connector_detect(connector, false, display); + + if (status == connector_status_connected) + info->is_connected = true; + else + info->is_connected = false; + } + + DP_MST_DEBUG("mst connector:%d get info:%d, rc:%d\n", + connector->base.id, status, rc); + + DP_MST_DEBUG("exit:\n"); + + return rc; +} + +int dp_mst_connector_get_mode_info(struct drm_connector *connector, + const struct drm_display_mode *drm_mode, + struct msm_mode_info *mode_info, + u32 max_mixer_width, void *display) +{ + int rc; + + DP_MST_DEBUG("enter:\n"); + + rc = dp_connector_get_mode_info(connector, drm_mode, mode_info, + max_mixer_width, display); + + DP_MST_DEBUG("mst connector:%d get mode info. rc:%d\n", + connector->base.id, rc); + + DP_MST_DEBUG("exit:\n"); + + return rc; +} + +static struct drm_encoder * +dp_mst_atomic_best_encoder(struct drm_connector *connector, + void *display, struct drm_connector_state *state) +{ + struct dp_display *dp_display = display; + struct dp_mst_private *mst = dp_display->dp_mst_prv_info; + struct sde_connector *conn = to_sde_connector(connector); + struct drm_encoder *enc = NULL; + u32 i; + + for (i = 0; i < MAX_DP_MST_DRM_BRIDGES; i++) { + if (mst->mst_bridge[i].connector == connector) { + enc = mst->mst_bridge[i].encoder; + goto end; + } + } + + for (i = 0; i < MAX_DP_MST_DRM_BRIDGES; i++) { + if (!mst->mst_bridge[i].encoder_active_sts) { + mst->mst_bridge[i].encoder_active_sts = true; + mst->mst_bridge[i].connector = connector; + mst->mst_bridge[i].dp_panel = conn->drv_panel; + enc = mst->mst_bridge[i].encoder; + break; + } + } + +end: + if (enc) + DP_MST_DEBUG("mst connector:%d atomic best encoder:%d\n", + connector->base.id, i); + else + DP_MST_DEBUG("mst connector:%d atomic best encoder failed\n", + connector->base.id); + + return enc; +} + +static struct dp_mst_bridge *_dp_mst_get_bridge_from_encoder( + struct dp_display *dp_display, + struct drm_encoder *encoder) +{ + struct dp_mst_private *mst = dp_display->dp_mst_prv_info; + int i; + + for (i = 0; i < MAX_DP_MST_DRM_BRIDGES; i++) { + if (mst->mst_bridge[i].encoder == encoder) + return &mst->mst_bridge[i]; + } + + DP_MST_DEBUG("mst bridge detect for encoder failed\n"); + + return NULL; +} + +static int dp_mst_connector_atomic_check(struct drm_connector *connector, + void *display, struct drm_connector_state *new_conn_state) +{ + int rc = 0, slots, i; + struct drm_atomic_state *state; + struct drm_connector_state *old_conn_state; + struct drm_crtc *old_crtc; + struct drm_crtc_state *crtc_state; + struct dp_mst_bridge *bridge = NULL; + struct dp_display *dp_display = display; + struct dp_mst_private *mst = dp_display->dp_mst_prv_info; + struct sde_connector *c_conn; + struct dp_display_mode dp_mode; + + DP_MST_DEBUG("enter:\n"); + + if (!new_conn_state) + return rc; + + state = new_conn_state->state; + + old_conn_state = drm_atomic_get_old_connector_state(state, connector); + + if (!old_conn_state) + goto mode_set; + + old_crtc = old_conn_state->crtc; + if (!old_crtc) + goto mode_set; + + crtc_state = drm_atomic_get_new_crtc_state(state, old_crtc); + + for (i = 0; i < MAX_DP_MST_DRM_BRIDGES; i++) { + bridge = &mst->mst_bridge[i]; + DP_MST_DEBUG("bridge id:%d, vcpi:%d, pbn:%d, slots:%d\n", + bridge->id, bridge->vcpi, bridge->pbn, + bridge->num_slots); + } + + bridge = _dp_mst_get_bridge_from_encoder(dp_display, + old_conn_state->best_encoder); + + if (!bridge) + return rc; + + slots = bridge->num_slots; + if (drm_atomic_crtc_needs_modeset(crtc_state) && slots > 0) { + rc = mst->mst_fw_cbs->atomic_release_vcpi_slots(state, + &mst->mst_mgr, slots); + if (rc) { + pr_err("failed releasing %d vcpi slots rc:%d\n", + slots, rc); + return rc; + } + } + +mode_set: + if (!new_conn_state->crtc) + return rc; + + crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc); + + if (drm_atomic_crtc_needs_modeset(crtc_state)) { + c_conn = to_sde_connector(connector); + + dp_display->convert_to_dp_mode(dp_display, c_conn->drv_panel, + &crtc_state->mode, &dp_mode); + + slots = _dp_mst_compute_config(state, mst, connector, &dp_mode); + if (slots < 0) + rc = slots; + } + + DP_MST_DEBUG("mst connector:%d atomic check\n", connector->base.id); + + DP_MST_DEBUG("exit:\n"); + + return rc; +} + +static int dp_mst_connector_config_hdr(struct drm_connector *connector, + void *display, struct sde_connector_state *c_state) +{ + int rc; + + DP_MST_DEBUG("enter:\n"); + + rc = dp_connector_config_hdr(connector, display, c_state); + + DP_MST_DEBUG("mst connector:%d cfg hdr. rc:%d\n", + connector->base.id, rc); + + DP_MST_DEBUG("exit:\n"); + + return rc; +} + +static void dp_mst_connector_pre_destroy(struct drm_connector *connector, + void *display) +{ + struct dp_display *dp_display = display; + + DP_MST_DEBUG("enter:\n"); + dp_display->mst_connector_uninstall(dp_display, connector); + DP_MST_DEBUG("exit:\n"); +} + +/* DRM MST callbacks */ + +static struct drm_connector * +dp_mst_add_connector(struct drm_dp_mst_topology_mgr *mgr, + struct drm_dp_mst_port *port, const char *pathprop) +{ + static const struct sde_connector_ops dp_mst_connector_ops = { + .post_init = NULL, + .detect = dp_mst_connector_detect, + .get_modes = dp_mst_connector_get_modes, + .mode_valid = dp_mst_connector_mode_valid, + .get_info = dp_mst_connector_get_info, + .get_mode_info = dp_mst_connector_get_mode_info, + .atomic_best_encoder = dp_mst_atomic_best_encoder, + .atomic_check = dp_mst_connector_atomic_check, + .config_hdr = dp_mst_connector_config_hdr, + .pre_destroy = dp_mst_connector_pre_destroy, + }; + struct dp_mst_private *dp_mst; + struct drm_device *dev; + struct dp_display *dp_display; + struct drm_connector *connector; + struct sde_connector *c_conn; + int rc, i; + + DP_MST_DEBUG("enter\n"); + + dp_mst = container_of(mgr, struct dp_mst_private, mst_mgr); + + dp_display = dp_mst->dp_display; + dev = dp_display->drm_dev; + + /* make sure connector is not accessed before reset */ + drm_modeset_lock_all(dev); + + connector = sde_connector_init(dev, + dp_mst->mst_bridge[0].encoder, + NULL, + dp_display, + &dp_mst_connector_ops, + DRM_CONNECTOR_POLL_HPD, + DRM_MODE_CONNECTOR_DisplayPort); + + if (!connector) { + pr_err("mst sde_connector_init failed\n"); + return connector; + } + + rc = dp_display->mst_connector_install(dp_display, connector); + if (rc) { + pr_err("mst connector install failed\n"); + sde_connector_destroy(connector); + return NULL; + } + + c_conn = to_sde_connector(connector); + c_conn->mst_port = port; + + if (connector->funcs->reset) + connector->funcs->reset(connector); + + for (i = 1; i < MAX_DP_MST_DRM_BRIDGES; i++) { + drm_mode_connector_attach_encoder(connector, + dp_mst->mst_bridge[i].encoder); + } + + drm_object_attach_property(&connector->base, + dev->mode_config.path_property, 0); + drm_object_attach_property(&connector->base, + dev->mode_config.tile_property, 0); + + /* unlock connector and make it accessible */ + drm_modeset_unlock_all(dev); + + DP_MST_DEBUG("add mst connector:%d\n", connector->base.id); + + return connector; +} + +static void dp_mst_register_connector(struct drm_connector *connector) +{ + DP_MST_DEBUG("enter\n"); + + connector->status = connector->funcs->detect(connector, false); + + DP_MST_DEBUG("register mst connector:%d\n", connector->base.id); + drm_connector_register(connector); +} + +static void dp_mst_destroy_connector(struct drm_dp_mst_topology_mgr *mgr, + struct drm_connector *connector) +{ + DP_MST_DEBUG("enter\n"); + + DP_MST_DEBUG("destroy mst connector:%d\n", connector->base.id); + + drm_connector_unregister(connector); + drm_connector_put(connector); +} + +static void dp_mst_hotplug(struct drm_dp_mst_topology_mgr *mgr) +{ + struct dp_mst_private *mst = container_of(mgr, struct dp_mst_private, + mst_mgr); + struct drm_device *dev = mst->dp_display->drm_dev; + char event_string[] = "MST_HOTPLUG=1"; + char *envp[2]; + + envp[0] = event_string; + envp[1] = NULL; + + kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp); + + DP_MST_DEBUG("mst hot plug event\n"); +} + +/* DP Driver Callback OPs */ + +static void dp_mst_display_hpd(void *dp_display, bool hpd_status, + struct dp_mst_hdp_info *info) +{ + int rc; + struct dp_display *dp = dp_display; + struct dp_mst_private *mst = dp->dp_mst_prv_info; + + DP_MST_DEBUG("enter:\n"); + + if (!hpd_status) + rc = mst->mst_fw_cbs->topology_mgr_set_mst(&mst->mst_mgr, + hpd_status); + + if (info && !info->mst_protocol) { + if (hpd_status) + mst->simulator.edid = (struct edid *)info->edid; + mst->mst_fw_cbs = &drm_dp_sim_mst_fw_helper_ops; + } else { + mst->mst_fw_cbs = &drm_dp_mst_fw_helper_ops; + } + + if (hpd_status) + rc = mst->mst_fw_cbs->topology_mgr_set_mst(&mst->mst_mgr, + hpd_status); + + DP_MST_DEBUG("mst display hpd:%d, rc:%d\n", hpd_status, rc); + + DP_MST_DEBUG("exit:\n"); +} + +static void dp_mst_display_hpd_irq(void *dp_display) +{ + int rc; + struct dp_display *dp = dp_display; + struct dp_mst_private *mst = dp->dp_mst_prv_info; + u8 esi[14], idx; + unsigned int esi_res = DP_SINK_COUNT_ESI + 1; + bool handled; + + DP_MST_DEBUG("enter:\n"); + + rc = drm_dp_dpcd_read(mst->caps.drm_aux, DP_SINK_COUNT_ESI, + esi, 14); + if (rc != 14) { + pr_err("dpcd sync status read failed, rlen=%d\n", rc); + goto end; + } + + for (idx = 0; idx < 14; idx++) + DP_MST_DEBUG("mst irq: esi[%d]: 0x%x\n", idx, esi[idx]); + + rc = drm_dp_mst_hpd_irq(&mst->mst_mgr, esi, &handled); + + /* ack the request */ + if (handled) { + rc = drm_dp_dpcd_write(mst->caps.drm_aux, esi_res, &esi[1], 3); + + if (rc != 3) + pr_err("dpcd esi_res failed. rlen=%d\n", rc); + } + + DP_MST_DEBUG("mst display hpd_irq handled:%d rc:%d\n", handled, rc); + +end: + DP_MST_DEBUG("exit:\n"); +} + +/* DP MST APIs */ + +static const struct dp_mst_drm_cbs dp_mst_display_cbs = { + .hpd = dp_mst_display_hpd, + .hpd_irq = dp_mst_display_hpd_irq, +}; + +static const struct drm_dp_mst_topology_cbs dp_mst_drm_cbs = { + .add_connector = dp_mst_add_connector, + .register_connector = dp_mst_register_connector, + .destroy_connector = dp_mst_destroy_connector, + .hotplug = dp_mst_hotplug, +}; + +static void drm_dp_sim_mst_init(struct dp_mst_private *mst) +{ + INIT_WORK(&mst->simulator.probe_work, drm_dp_mst_sim_link_probe_work); + INIT_WORK(&mst->simulator.conn_destroy_work, + drm_dp_mst_sim_conn_destroy_work); + mst->simulator.cbs = &dp_mst_drm_cbs; +} + +int dp_mst_init(struct dp_display *dp_display) +{ + struct drm_device *dev; + int conn_base_id = 0; + int ret; + struct dp_mst_drm_install_info install_info; + + memset(&dp_mst, 0, sizeof(dp_mst)); + + if (!dp_display) { + pr_err("invalid params\n"); + return 0; + } + + dev = dp_display->drm_dev; + + /* register with DP driver */ + install_info.dp_mst_prv_info = &dp_mst; + install_info.cbs = &dp_mst_display_cbs; + dp_display->mst_install(dp_display, &install_info); + + dp_display->get_mst_caps(dp_display, &dp_mst.caps); + + if (!dp_mst.caps.has_mst) { + DP_MST_DEBUG("mst not supported\n"); + return 0; + } + + dp_mst.mst_fw_cbs = &drm_dp_mst_fw_helper_ops; + + memset(&dp_mst.mst_mgr, 0, sizeof(dp_mst.mst_mgr)); + dp_mst.mst_mgr.cbs = &dp_mst_drm_cbs; + conn_base_id = dp_display->base_connector->base.id; + dp_mst.dp_display = dp_display; + + mutex_init(&dp_mst.mst_lock); + + ret = drm_dp_mst_topology_mgr_init(&dp_mst.mst_mgr, dev, + dp_mst.caps.drm_aux, + dp_mst.caps.max_dpcd_transaction_bytes, + dp_mst.caps.max_streams_supported, + conn_base_id); + if (ret) { + pr_err("dp drm mst topology manager init failed\n"); + goto error; + } + + drm_dp_sim_mst_init(&dp_mst); + + dp_mst.mst_initialized = true; + + DP_MST_DEBUG("dp drm mst topology manager init completed\n"); + + return ret; + +error: + mutex_destroy(&dp_mst.mst_lock); + return ret; +} + +void dp_mst_deinit(struct dp_display *dp_display) +{ + struct dp_mst_private *mst; + + if (!dp_display) { + pr_err("invalid params\n"); + return; + } + + mst = dp_display->dp_mst_prv_info; + + if (!mst->mst_initialized) + return; + + dp_display->mst_uninstall(dp_display); + + drm_dp_mst_topology_mgr_destroy(&mst->mst_mgr); + + dp_mst.mst_initialized = false; + + mutex_destroy(&mst->mst_lock); + + DP_MST_DEBUG("dp drm mst topology manager deinit completed\n"); +} + 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..4eddb28346dc106d089d8c51b31ceb6f78029c8a --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_panel.c @@ -0,0 +1,1723 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include "dp_panel.h" + +#define DP_KHZ_TO_HZ 1000 +#define DP_PANEL_DEFAULT_BPP 24 +#define DP_MAX_DS_PORT_COUNT 1 + +#define DPRX_FEATURE_ENUMERATION_LIST 0x2210 +#define VSC_SDP_EXTENSION_FOR_COLORIMETRY_SUPPORTED BIT(3) +#define VSC_EXT_VESA_SDP_SUPPORTED BIT(4) +#define VSC_EXT_VESA_SDP_CHAINING_SUPPORTED BIT(5) + +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; + u32 valid_boundary_link; + u32 delay_start_link; + bool boundary_moderation_en; + u32 valid_lower_boundary_link; + u32 upper_boundary_count; + u32 lower_boundary_count; + u32 tu_size_minus1; +}; + +enum dp_panel_hdr_pixel_encoding { + RGB, + YCbCr444, + YCbCr422, + YCbCr420, + YONLY, + RAW, +}; + +enum dp_panel_hdr_rgb_colorimetry { + sRGB, + RGB_WIDE_GAMUT_FIXED_POINT, + RGB_WIDE_GAMUT_FLOATING_POINT, + ADOBERGB, + DCI_P3, + CUSTOM_COLOR_PROFILE, + ITU_R_BT_2020_RGB, +}; + +enum dp_panel_hdr_dynamic_range { + VESA, + CEA, +}; + +enum dp_panel_hdr_content_type { + NOT_DEFINED, + GRAPHICS, + PHOTO, + VIDEO, + GAME, +}; + +enum dp_panel_hdr_state { + HDR_DISABLED, + HDR_ENABLED, +}; + +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 custom_edid; + bool custom_dpcd; + bool panel_on; + bool vsc_supported; + bool vscext_supported; + bool vscext_chaining_supported; + enum dp_panel_hdr_state hdr_state; + u8 spd_vendor_name[8]; + u8 spd_product_description[16]; + u8 major; + u8 minor; +}; + +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, +}; + +/* OEM NAME */ +static const u8 vendor_name[8] = {81, 117, 97, 108, 99, 111, 109, 109}; + +/* MODEL NAME */ +static const u8 product_desc[16] = {83, 110, 97, 112, 100, 114, 97, 103, + 111, 110, 0, 0, 0, 0, 0, 0}; + +static void dp_panel_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_panel_calc_tu_parameters(struct dp_panel *dp_panel, + 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; + u32 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 = &dp_panel->pinfo; + + u8 dp_brute_force = 1; + u64 brute_force_threshold = 10; + u64 diff_abs; + + struct dp_panel_private *panel; + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + ln_cnt = panel->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( + panel->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_panel_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 = + (int)(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 = (u32)(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_panel_config_tr_unit(struct dp_panel *dp_panel) +{ + struct dp_panel_private *panel; + struct dp_catalog_panel *catalog; + u32 dp_tu = 0x0; + u32 valid_boundary = 0x0; + u32 valid_boundary2 = 0x0; + struct dp_vc_tu_mapping_table tu_calc_table; + + if (!dp_panel) { + pr_err("invalid input\n"); + return; + } + + if (dp_panel->stream_id != DP_STREAM_0) + return; + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + catalog = panel->catalog; + + dp_panel_calc_tu_parameters(dp_panel, &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); + + catalog->dp_tu = dp_tu; + catalog->valid_boundary = valid_boundary; + catalog->valid_boundary2 = valid_boundary2; + + catalog->update_transfer_unit(catalog); +} + +static int dp_panel_read_dpcd(struct dp_panel *dp_panel, bool multi_func) +{ + int rlen, rc = 0; + struct dp_panel_private *panel; + struct drm_dp_link *link_info; + u8 *dpcd, rx_feature; + 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; + + if (!panel->custom_dpcd) { + rlen = drm_dp_dpcd_read(panel->aux->drm_aux, DP_DPCD_REV, + dp_panel->dpcd, (DP_RECEIVER_CAP_SIZE + 1)); + if (rlen < (DP_RECEIVER_CAP_SIZE + 1)) { + pr_err("dpcd read failed, rlen=%d\n", rlen); + if (rlen == -ETIMEDOUT) + rc = rlen; + else + rc = -EINVAL; + + goto end; + } + + print_hex_dump(KERN_DEBUG, "[drm-dp] SINK DPCD: ", + DUMP_PREFIX_NONE, 8, 1, dp_panel->dpcd, rlen, false); + } + + rlen = drm_dp_dpcd_read(panel->aux->drm_aux, + DPRX_FEATURE_ENUMERATION_LIST, &rx_feature, 1); + if (rlen != 1) { + pr_debug("failed to read DPRX_FEATURE_ENUMERATION_LIST\n"); + panel->vsc_supported = false; + panel->vscext_supported = false; + panel->vscext_chaining_supported = false; + } else { + panel->vsc_supported = !!(rx_feature & + VSC_SDP_EXTENSION_FOR_COLORIMETRY_SUPPORTED); + + panel->vscext_supported = !!(rx_feature & + VSC_EXT_VESA_SDP_SUPPORTED); + + panel->vscext_chaining_supported = !!(rx_feature & + VSC_EXT_VESA_SDP_CHAINING_SUPPORTED); + } + + pr_debug("vsc=%d, vscext=%d, vscext_chaining=%d\n", + panel->vsc_supported, panel->vscext_supported, + panel->vscext_chaining_supported); + + link_info->revision = dp_panel->dpcd[DP_DPCD_REV]; + + panel->major = (link_info->revision >> 4) & 0x0f; + panel->minor = link_info->revision & 0x0f; + pr_debug("version: %d.%d\n", panel->major, panel->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; + + if (multi_func) + link_info->num_lanes = min_t(unsigned int, + link_info->num_lanes, 2); + + 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_set_edid(struct dp_panel *dp_panel, u8 *edid) +{ + 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 (edid) { + dp_panel->edid_ctrl->edid = (struct edid *)edid; + panel->custom_edid = true; + } else { + panel->custom_edid = false; + } + + return 0; +} + +static int dp_panel_set_dpcd(struct dp_panel *dp_panel, u8 *dpcd) +{ + struct dp_panel_private *panel; + u8 *dp_dpcd; + + if (!dp_panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + dp_dpcd = dp_panel->dpcd; + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + if (dpcd) { + memcpy(dp_dpcd, dpcd, DP_RECEIVER_CAP_SIZE + 1); + panel->custom_dpcd = true; + } else { + panel->custom_dpcd = false; + } + + return 0; +} + +static int dp_panel_read_edid(struct dp_panel *dp_panel, + struct drm_connector *connector) +{ + int ret = 0; + struct dp_panel_private *panel; + + if (!dp_panel) { + pr_err("invalid input\n"); + ret = -EINVAL; + goto end; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + if (panel->custom_edid) { + pr_debug("skip edid read in debug mode\n"); + goto end; + } + + 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"); + ret = -EINVAL; + goto end; + } +end: + return ret; +} + +static int dp_panel_read_sink_caps(struct dp_panel *dp_panel, + struct drm_connector *connector, bool multi_func) +{ + int rc = 0, rlen, count, downstream_ports; + const int count_len = 1; + struct dp_panel_private *panel; + + if (!dp_panel || !connector) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + rc = dp_panel_read_dpcd(dp_panel, multi_func); + 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) || + ((drm_dp_link_rate_to_bw_code(dp_panel->link_info.rate)) > + dp_panel->max_bw_code)) { + if ((rc == -ETIMEDOUT) || (rc == -ENODEV)) { + pr_err("DPCD read failed, return early\n"); + goto end; + } + pr_err("panel dpcd read failed/incorrect, set default params\n"); + dp_panel_set_default_link_params(dp_panel); + } + + downstream_ports = dp_panel->dpcd[DP_DOWNSTREAMPORT_PRESENT] & + DP_DWN_STRM_PORT_PRESENT; + + if (downstream_ports) { + rlen = drm_dp_dpcd_read(panel->aux->drm_aux, DP_SINK_COUNT, + &count, count_len); + if (rlen == count_len) { + count = DP_GET_SINK_COUNT(count); + if (!count) { + pr_err("no downstream ports connected\n"); + panel->link->sink_count.count = 0; + rc = -ENOTCONN; + goto end; + } + } + } + + rc = dp_panel_read_edid(dp_panel, connector); + if (rc) { + pr_err("panel edid read failed, set failsafe mode\n"); + return rc; + } +end: + return rc; +} + +static u32 dp_panel_get_supported_bpp(struct dp_panel *dp_panel, + u32 mode_edid_bpp, u32 mode_pclk_khz) +{ + struct drm_dp_link *link_info; + const u32 max_supported_bpp = 30, min_supported_bpp = 18; + u32 bpp = 0, data_rate_khz = 0; + + bpp = min_t(u32, mode_edid_bpp, max_supported_bpp); + + link_info = &dp_panel->link_info; + data_rate_khz = link_info->num_lanes * link_info->rate * 8; + + while (bpp > min_supported_bpp) { + if (mode_pclk_khz * bpp <= data_rate_khz) + break; + bpp -= 6; + } + + return bpp; +} + +static u32 dp_panel_get_mode_bpp(struct dp_panel *dp_panel, + u32 mode_edid_bpp, u32 mode_pclk_khz) +{ + struct dp_panel_private *panel; + u32 bpp = mode_edid_bpp; + + if (!dp_panel || !mode_edid_bpp || !mode_pclk_khz) { + pr_err("invalid input\n"); + return 0; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + if (dp_panel->video_test) + bpp = dp_link_bit_depth_to_bpp( + panel->link->test_video.test_bit_depth); + else + bpp = dp_panel_get_supported_bpp(dp_panel, mode_edid_bpp, + mode_pclk_khz); + + return bpp; +} + +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 void dp_panel_tpg_config(struct dp_panel *dp_panel, bool enable) +{ + u32 hsync_start_x, hsync_end_x; + struct dp_catalog_panel *catalog; + struct dp_panel_private *panel; + struct dp_panel_info *pinfo; + + if (!dp_panel) { + pr_err("invalid input\n"); + return; + } + + if (dp_panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream id:%d\n", dp_panel->stream_id); + return; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + catalog = panel->catalog; + pinfo = &panel->dp_panel.pinfo; + + if (!panel->panel_on) { + pr_debug("DP panel not enabled, handle TPG on next panel on\n"); + return; + } + + if (!enable) { + panel->catalog->tpg_config(catalog, false); + return; + } + + /* TPG config */ + catalog->hsync_period = pinfo->h_sync_width + pinfo->h_back_porch + + pinfo->h_active + pinfo->h_front_porch; + catalog->vsync_period = pinfo->v_sync_width + pinfo->v_back_porch + + pinfo->v_active + pinfo->v_front_porch; + + catalog->display_v_start = ((pinfo->v_sync_width + + pinfo->v_back_porch) * catalog->hsync_period); + catalog->display_v_end = ((catalog->vsync_period - + pinfo->v_front_porch) * catalog->hsync_period) - 1; + + catalog->display_v_start += pinfo->h_sync_width + pinfo->h_back_porch; + catalog->display_v_end -= pinfo->h_front_porch; + + hsync_start_x = pinfo->h_back_porch + pinfo->h_sync_width; + hsync_end_x = catalog->hsync_period - pinfo->h_front_porch - 1; + + catalog->v_sync_width = pinfo->v_sync_width; + + catalog->hsync_ctl = (catalog->hsync_period << 16) | + pinfo->h_sync_width; + catalog->display_hctl = (hsync_end_x << 16) | hsync_start_x; + + panel->catalog->tpg_config(catalog, true); +} + +static int dp_panel_config_timing(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); + panel->panel_on = true; +end: + return rc; +} + +static int dp_panel_edid_register(struct dp_panel_private *panel) +{ + int rc = 0; + + panel->dp_panel.edid_ctrl = sde_edid_init(); + if (!panel->dp_panel.edid_ctrl) { + pr_err("sde edid init for DP failed\n"); + rc = -ENOMEM; + } + + return rc; +} + +static void dp_panel_edid_deregister(struct dp_panel_private *panel) +{ + sde_edid_deinit((void **)&panel->dp_panel.edid_ctrl); +} + +static int dp_panel_set_stream_info(struct dp_panel *dp_panel, + enum dp_stream_id stream_id, u32 ch_start_slot, + u32 ch_tot_slots, u32 pbn) +{ + if (!dp_panel || stream_id > DP_STREAM_MAX) { + pr_err("invalid input. stream_id: %d\n", stream_id); + return -EINVAL; + } + + dp_panel->stream_id = stream_id; + dp_panel->channel_start_slot = ch_start_slot; + dp_panel->channel_total_slots = ch_tot_slots; + dp_panel->pbn = pbn; + + return 0; +} + +static int dp_panel_init_panel_info(struct dp_panel *dp_panel) +{ + int rc = 0; + 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); + pinfo = &dp_panel->pinfo; + + /* + * print resolution info as this is a result + * of user initiated action of cable connection + */ + pr_info("DP RESOLUTION: active(back|front|width|low)\n"); + pr_info("%d(%d|%d|%d|%d)x%d(%d|%d|%d|%d)@%dfps %dbpp %dKhz %dLR %dLn\n", + pinfo->h_active, pinfo->h_back_porch, pinfo->h_front_porch, + pinfo->h_sync_width, pinfo->h_active_low, + pinfo->v_active, pinfo->v_back_porch, pinfo->v_front_porch, + pinfo->v_sync_width, pinfo->v_active_low, + pinfo->refresh_rate, pinfo->bpp, pinfo->pixel_clk_khz, + panel->link->link_params.bw_code, + panel->link->link_params.lane_count); +end: + return rc; +} + +static int dp_panel_deinit_panel_info(struct dp_panel *dp_panel) +{ + int rc = 0; + struct dp_panel_private *panel; + struct dp_catalog_hdr_data *hdr; + struct drm_connector *connector; + struct sde_connector_state *c_state; + + if (!dp_panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + hdr = &panel->catalog->hdr_data; + + if (!panel->custom_edid) + sde_free_edid((void **)&dp_panel->edid_ctrl); + + dp_panel_set_stream_info(dp_panel, DP_STREAM_MAX, 0, 0, 0); + memset(&dp_panel->pinfo, 0, sizeof(dp_panel->pinfo)); + memset(&hdr->hdr_meta, 0, sizeof(hdr->hdr_meta)); + panel->panel_on = false; + + connector = dp_panel->connector; + c_state = to_sde_connector_state(connector->state); + + connector->hdr_eotf = 0; + connector->hdr_metadata_type_one = 0; + connector->hdr_max_luminance = 0; + connector->hdr_avg_luminance = 0; + connector->hdr_min_luminance = 0; + connector->hdr_supported = false; + + memset(&c_state->hdr_meta, 0, sizeof(c_state->hdr_meta)); + + 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; +} + +static bool dp_panel_hdr_supported(struct dp_panel *dp_panel) +{ + struct dp_panel_private *panel; + + if (!dp_panel) { + pr_err("invalid input\n"); + return false; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + return panel->major >= 1 && panel->vsc_supported && + (panel->minor >= 4 || panel->vscext_supported); +} + +static int dp_panel_setup_hdr(struct dp_panel *dp_panel, + struct drm_msm_ext_hdr_metadata *hdr_meta) +{ + int rc = 0; + struct dp_panel_private *panel; + struct dp_catalog_hdr_data *hdr; + + if (!dp_panel) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + hdr = &panel->catalog->hdr_data; + + /* use cached meta data in case meta data not provided */ + if (!hdr_meta) { + if (hdr->hdr_meta.hdr_state) + goto cached; + else + goto end; + } + + panel->hdr_state = hdr_meta->hdr_state; + + hdr->ext_header_byte0 = 0x00; + hdr->ext_header_byte1 = 0x04; + hdr->ext_header_byte2 = 0x1F; + hdr->ext_header_byte3 = 0x00; + + hdr->vsc_header_byte0 = 0x00; + hdr->vsc_header_byte1 = 0x07; + hdr->vsc_header_byte2 = 0x05; + hdr->vsc_header_byte3 = 0x13; + + hdr->vscext_header_byte0 = 0x00; + hdr->vscext_header_byte1 = 0x87; + hdr->vscext_header_byte2 = 0x1D; + hdr->vscext_header_byte3 = 0x13 << 2; + + /* VSC SDP Payload for DB16 */ + hdr->pixel_encoding = RGB; + hdr->colorimetry = ITU_R_BT_2020_RGB; + + /* VSC SDP Payload for DB17 */ + hdr->dynamic_range = CEA; + + /* VSC SDP Payload for DB18 */ + hdr->content_type = GRAPHICS; + + hdr->bpc = dp_panel->pinfo.bpp / 3; + + hdr->version = 0x01; + hdr->length = 0x1A; + + if (panel->hdr_state) + memcpy(&hdr->hdr_meta, hdr_meta, sizeof(hdr->hdr_meta)); + else + memset(&hdr->hdr_meta, 0, sizeof(hdr->hdr_meta)); +cached: + if (panel->panel_on) { + panel->catalog->stream_id = dp_panel->stream_id; + panel->catalog->config_hdr(panel->catalog, panel->hdr_state); + } +end: + return rc; +} + +static int dp_panel_spd_config(struct dp_panel *dp_panel) +{ + int rc = 0; + struct dp_panel_private *panel; + + if (!dp_panel) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto end; + } + + if (dp_panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream id:%d\n", dp_panel->stream_id); + return -EINVAL; + } + + if (!dp_panel->spd_enabled) { + pr_debug("SPD Infoframe not enabled\n"); + goto end; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + panel->catalog->spd_vendor_name = panel->spd_vendor_name; + panel->catalog->spd_product_description = + panel->spd_product_description; + + panel->catalog->stream_id = dp_panel->stream_id; + panel->catalog->config_spd(panel->catalog); +end: + return rc; +} + +static void dp_panel_config_ctrl(struct dp_panel *dp_panel) +{ + u32 config = 0, tbd; + u8 *dpcd = dp_panel->dpcd; + struct dp_panel_private *panel; + struct dp_catalog_panel *catalog; + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + catalog = panel->catalog; + + 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 = panel->link->get_test_bits_depth(panel->link, + dp_panel->pinfo.bpp); + + if (tbd == DP_TEST_BIT_DEPTH_UNKNOWN) + tbd = DP_TEST_BIT_DEPTH_8; + + config |= tbd << 8; + + /* Num of Lanes */ + config |= ((panel->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 */ + + catalog->config_ctrl(catalog, config); +} + +static void dp_panel_config_misc(struct dp_panel *dp_panel) +{ + struct dp_panel_private *panel; + struct dp_catalog_panel *catalog; + u32 misc_val; + u32 tb, cc; + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + catalog = panel->catalog; + + tb = panel->link->get_test_bits_depth(panel->link, dp_panel->pinfo.bpp); + cc = panel->link->get_colorimetry_config(panel->link); + + misc_val = cc; + misc_val |= (tb << 5); + misc_val |= BIT(0); /* Configure clock to synchronous mode */ + + catalog->misc_val = misc_val; + catalog->config_misc(catalog); +} + +static bool dp_panel_use_fixed_nvid(struct dp_panel *dp_panel) +{ + u8 *dpcd = dp_panel->dpcd; + struct sde_connector *c_conn = to_sde_connector(dp_panel->connector); + + /* use fixe mvid and nvid for MST streams */ + if (c_conn->mst_port) + return true; + + /* + * 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 void dp_panel_config_msa(struct dp_panel *dp_panel) +{ + struct dp_panel_private *panel; + struct dp_catalog_panel *catalog; + u32 rate; + u32 stream_rate_khz; + bool fixed_nvid; + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + catalog = panel->catalog; + + fixed_nvid = dp_panel_use_fixed_nvid(dp_panel); + rate = drm_dp_bw_code_to_link_rate(panel->link->link_params.bw_code); + stream_rate_khz = dp_panel->pinfo.pixel_clk_khz; + + catalog->config_msa(catalog, rate, stream_rate_khz, fixed_nvid); +} + +static int dp_panel_hw_cfg(struct dp_panel *dp_panel, bool enable) +{ + struct dp_panel_private *panel; + + if (!dp_panel) { + pr_err("invalid input\n"); + return -EINVAL; + } + + if (dp_panel->stream_id >= DP_STREAM_MAX) { + pr_err("invalid stream_id: %d\n", dp_panel->stream_id); + return -EINVAL; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + panel->catalog->stream_id = dp_panel->stream_id; + + if (enable) { + dp_panel_config_ctrl(dp_panel); + dp_panel_config_misc(dp_panel); + dp_panel_config_msa(dp_panel); + dp_panel_config_tr_unit(dp_panel); + dp_panel_config_timing(dp_panel); + } + + panel->catalog->config_dto(panel->catalog, !enable); + + return 0; +} + +static int dp_panel_read_sink_sts(struct dp_panel *dp_panel, u8 *sts, u32 size) +{ + int rlen, rc = 0; + struct dp_panel_private *panel; + + if (!dp_panel || !sts || !size) { + pr_err("invalid input\n"); + rc = -EINVAL; + return rc; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + rlen = drm_dp_dpcd_read(panel->aux->drm_aux, DP_SINK_COUNT_ESI, + sts, size); + if (rlen != size) { + pr_err("dpcd sink sts fail rlen:%d size:%d\n", rlen, size); + rc = -EINVAL; + return rc; + } + + return 0; +} + +static int dp_panel_update_edid(struct dp_panel *dp_panel, struct edid *edid) +{ + dp_panel->edid_ctrl->edid = edid; + sde_parse_edid(dp_panel->edid_ctrl); + return _sde_edid_update_modes(dp_panel->connector, dp_panel->edid_ctrl); +} + +static bool dp_panel_read_mst_cap(struct dp_panel *dp_panel) +{ + int rlen; + struct dp_panel_private *panel; + u8 dpcd; + bool mst_cap = false; + + if (!dp_panel) { + pr_err("invalid input\n"); + goto end; + } + + panel = container_of(dp_panel, struct dp_panel_private, dp_panel); + + rlen = drm_dp_dpcd_read(panel->aux->drm_aux, DP_MSTM_CAP, + &dpcd, 1); + if (rlen < 1) { + pr_err("dpcd mstm_cap read failed, rlen=%d\n", rlen); + goto end; + } + + mst_cap = (dpcd & DP_MST_CAP) ? true : false; + +end: + pr_debug("dp mst-cap: %d\n", mst_cap); + + return mst_cap; +} + +static void dp_panel_convert_to_dp_mode(struct dp_panel *dp_panel, + const struct drm_display_mode *drm_mode, + struct dp_display_mode *dp_mode) +{ + const u32 num_components = 3, default_bpp = 24; + + 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; + + 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); + + dp_mode->timing.bpp = + dp_panel->connector->display_info.bpc * num_components; + if (!dp_mode->timing.bpp) + dp_mode->timing.bpp = default_bpp; + + dp_mode->timing.bpp = dp_panel_get_mode_bpp(dp_panel, + dp_mode->timing.bpp, dp_mode->timing.pixel_clk_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; + struct sde_connector *sde_conn; + + if (!in->dev || !in->catalog || !in->aux || + !in->link || !in->connector) { + 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; + dp_panel->max_bw_code = DP_LINK_BW_8_1; + dp_panel->spd_enabled = true; + memcpy(panel->spd_vendor_name, vendor_name, (sizeof(u8) * 8)); + memcpy(panel->spd_product_description, product_desc, (sizeof(u8) * 16)); + dp_panel->connector = in->connector; + + if (in->base_panel) { + memcpy(dp_panel->dpcd, in->base_panel->dpcd, + DP_RECEIVER_CAP_SIZE + 1); + memcpy(&dp_panel->link_info, &in->base_panel->link_info, + sizeof(dp_panel->link_info)); + } + + dp_panel->init = dp_panel_init_panel_info; + dp_panel->deinit = dp_panel_deinit_panel_info; + dp_panel->hw_cfg = dp_panel_hw_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_mode_bpp = dp_panel_get_mode_bpp; + dp_panel->get_modes = dp_panel_get_modes; + dp_panel->handle_sink_request = dp_panel_handle_sink_request; + dp_panel->set_edid = dp_panel_set_edid; + dp_panel->set_dpcd = dp_panel_set_dpcd; + dp_panel->tpg_config = dp_panel_tpg_config; + dp_panel->spd_config = dp_panel_spd_config; + dp_panel->setup_hdr = dp_panel_setup_hdr; + dp_panel->hdr_supported = dp_panel_hdr_supported; + dp_panel->set_stream_info = dp_panel_set_stream_info; + dp_panel->read_sink_status = dp_panel_read_sink_sts; + dp_panel->update_edid = dp_panel_update_edid; + dp_panel->read_mst_cap = dp_panel_read_mst_cap; + dp_panel->convert_to_dp_mode = dp_panel_convert_to_dp_mode; + + sde_conn = to_sde_connector(dp_panel->connector); + sde_conn->drv_panel = dp_panel; + + dp_panel_edid_register(panel); + + 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); + + dp_panel_edid_deregister(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..f43bc8a88c23dfc59e335b24713d6c0bed7a2154 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_panel.h @@ -0,0 +1,152 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DP_PANEL_H_ +#define _DP_PANEL_H_ + +#include + +#include "dp_aux.h" +#include "dp_link.h" +#include "dp_usbpd.h" +#include "sde_edid_parser.h" +#include "sde_connector.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; + +struct dp_panel_in { + struct device *dev; + struct dp_aux *aux; + struct dp_link *link; + struct dp_catalog_panel *catalog; + struct drm_connector *connector; + struct dp_panel *base_panel; +}; + +struct dp_audio; + +struct dp_panel { + /* dpcd raw data */ + u8 dpcd[DP_RECEIVER_CAP_SIZE + 1]; + u8 ds_ports[DP_MAX_DOWNSTREAM_PORTS]; + + struct drm_dp_link link_info; + struct sde_edid_ctrl *edid_ctrl; + struct dp_panel_info pinfo; + bool video_test; + bool spd_enabled; + + u32 vic; + u32 max_pclk_khz; + + /* debug */ + u32 max_bw_code; + + /* By default, stream_id is assigned to DP_INVALID_STREAM. + * Client sets the stream id value using set_stream_id interface. + */ + enum dp_stream_id stream_id; + + u32 channel_start_slot; + u32 channel_total_slots; + u32 pbn; + + /* DRM connector assosiated with this panel */ + struct drm_connector *connector; + + struct dp_audio *audio; + bool audio_supported; + + int (*init)(struct dp_panel *dp_panel); + int (*deinit)(struct dp_panel *dp_panel); + int (*hw_cfg)(struct dp_panel *dp_panel, bool enable); + int (*read_sink_caps)(struct dp_panel *dp_panel, + struct drm_connector *connector, bool multi_func); + u32 (*get_min_req_link_rate)(struct dp_panel *dp_panel); + u32 (*get_mode_bpp)(struct dp_panel *dp_panel, u32 mode_max_bpp, + u32 mode_pclk_khz); + 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); + int (*set_edid)(struct dp_panel *dp_panel, u8 *edid); + int (*set_dpcd)(struct dp_panel *dp_panel, u8 *dpcd); + int (*setup_hdr)(struct dp_panel *dp_panel, + struct drm_msm_ext_hdr_metadata *hdr_meta); + void (*tpg_config)(struct dp_panel *dp_panel, bool enable); + int (*spd_config)(struct dp_panel *dp_panel); + bool (*hdr_supported)(struct dp_panel *dp_panel); + + int (*set_stream_info)(struct dp_panel *dp_panel, + enum dp_stream_id stream_id, u32 ch_start_slot, + u32 ch_tot_slots, u32 pbn); + + int (*read_sink_status)(struct dp_panel *dp_panel, u8 *sts, u32 size); + int (*update_edid)(struct dp_panel *dp_panel, struct edid *edid); + bool (*read_mst_cap)(struct dp_panel *dp_panel); + void (*convert_to_dp_mode)(struct dp_panel *dp_panel, + const struct drm_display_mode *drm_mode, + struct dp_display_mode *dp_mode); +}; + +/** + * 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..3e3c0dab140048c3d76dfa5ebcfd8d9d602b0a3f --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_parser.c @@ -0,0 +1,838 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include + +#include "dp_parser.h" + +static void dp_parser_unmap_io_resources(struct dp_parser *parser) +{ + int i = 0; + struct dp_io *io = &parser->io; + + for (i = 0; i < io->len; i++) + msm_dss_iounmap(&io->data[i].io); +} + +static int dp_parser_reg(struct dp_parser *parser) +{ + int rc = 0, i = 0; + u32 reg_count; + struct platform_device *pdev = parser->pdev; + struct dp_io *io = &parser->io; + struct device *dev = &pdev->dev; + + reg_count = of_property_count_strings(dev->of_node, "reg-names"); + if (reg_count <= 0) { + pr_err("no reg defined\n"); + return -EINVAL; + } + + io->len = reg_count; + io->data = devm_kzalloc(dev, sizeof(struct dp_io_data) * reg_count, + GFP_KERNEL); + if (!io->data) + return -ENOMEM; + + for (i = 0; i < reg_count; i++) { + of_property_read_string_index(dev->of_node, + "reg-names", i, &io->data[i].name); + rc = msm_dss_ioremap_byname(pdev, &io->data[i].io, + io->data[i].name); + if (rc) { + pr_err("unable to remap %s resources\n", + io->data[i].name); + goto err; + } + } + + 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, len = 0, i = 0; + const char *data = NULL; + + struct device_node *of_node = parser->pdev->dev.of_node; + + data = of_get_property(of_node, "qcom,logical2physical-lane-map", &len); + if (data && (len == DP_MAX_PHY_LN)) { + for (i = 0; i < len; i++) + parser->l_map[i] = data[i]; + } + + 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_msm_hdcp_dev(struct dp_parser *parser) +{ + struct device_node *node; + struct platform_device *pdev; + + node = of_find_compatible_node(NULL, NULL, "qcom,msm-hdcp"); + if (!node) { + // This is a non-fatal error, module initialization can proceed + pr_warn("couldn't find msm-hdcp node\n"); + return 0; + } + + pdev = of_find_device_by_node(node); + if (!pdev) { + // This is a non-fatal error, module initialization can proceed + pr_warn("couldn't find msm-hdcp pdev\n"); + return 0; + } + + parser->msm_hdcp_dev = &pdev->dev; + + 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", + }; + + if (of_find_property(of_node, "qcom,dp-hpd-gpio", NULL)) { + parser->no_aux_switch = true; + return 0; + } + + mp->gpio_config = devm_kzalloc(dev, + sizeof(struct dss_gpio) * ARRAY_SIZE(dp_gpios), GFP_KERNEL); + if (!mp->gpio_config) + return -ENOMEM; + + 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_debug("%s gpio not specified\n", dp_gpios[i]); + continue; + } + + 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 void dp_parser_put_gpio_data(struct device *dev, + struct dss_module_power *mp) +{ + if (!mp) { + DEV_ERR("%s: invalid input\n", __func__); + return; + } + + if (mp->gpio_config) { + devm_kfree(dev, mp->gpio_config); + mp->gpio_config = NULL; + } + + mp->num_gpio = 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, link_clk_count = 0; + int strm0_clk_count = 0, strm1_clk_count = 0; + const char *core_clk = "core"; + const char *strm0_clk = "strm0"; + const char *strm1_clk = "strm1"; + const char *link_clk = "link"; + 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 *strm0_power = &parser->mp[DP_STREAM0_PM]; + struct dss_module_power *strm1_power = &parser->mp[DP_STREAM1_PM]; + struct dss_module_power *link_power = &parser->mp[DP_LINK_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(strm0_clk, clk_name)) + strm0_clk_count++; + + if (dp_parser_check_prefix(strm1_clk, clk_name)) + strm1_clk_count++; + + if (dp_parser_check_prefix(link_clk, clk_name)) + link_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 STREAM0 power module */ + if (strm0_clk_count <= 0) { + pr_debug("no strm0 clocks are defined\n"); + } else { + strm0_power->num_clk = strm0_clk_count; + strm0_power->clk_config = devm_kzalloc(dev, + sizeof(struct dss_clk) * strm0_power->num_clk, + GFP_KERNEL); + if (!strm0_power->clk_config) { + strm0_power->num_clk = 0; + rc = -EINVAL; + goto strm0_clock_error; + } + } + + /* Initialize the STREAM1 power module */ + if (strm1_clk_count <= 0) { + pr_debug("no strm1 clocks are defined\n"); + } else { + strm1_power->num_clk = strm1_clk_count; + strm1_power->clk_config = devm_kzalloc(dev, + sizeof(struct dss_clk) * strm1_power->num_clk, + GFP_KERNEL); + if (!strm1_power->clk_config) { + strm1_power->num_clk = 0; + rc = -EINVAL; + goto strm1_clock_error; + } + } + + /* Initialize the link power module */ + if (link_clk_count <= 0) { + pr_err("no link clocks are defined\n"); + rc = -EINVAL; + goto link_clock_error; + } + + link_power->num_clk = link_clk_count; + link_power->clk_config = devm_kzalloc(dev, + sizeof(struct dss_clk) * link_power->num_clk, + GFP_KERNEL); + if (!link_power->clk_config) { + link_power->num_clk = 0; + rc = -EINVAL; + goto link_clock_error; + } + + return rc; + +link_clock_error: + dp_parser_put_clk_data(dev, strm1_power); +strm1_clock_error: + dp_parser_put_clk_data(dev, strm0_power); +strm0_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, link_clk_index = 0; + int core_clk_count = 0, link_clk_count = 0; + int strm0_clk_index = 0, strm1_clk_index = 0; + int strm0_clk_count = 0, strm1_clk_count = 0; + const char *clk_name; + const char *core_clk = "core"; + const char *strm0_clk = "strm0"; + const char *strm1_clk = "strm1"; + const char *link_clk = "link"; + struct device *dev = &parser->pdev->dev; + struct dss_module_power *core_power; + struct dss_module_power *strm0_power; + struct dss_module_power *strm1_power; + struct dss_module_power *link_power; + + core_power = &parser->mp[DP_CORE_PM]; + strm0_power = &parser->mp[DP_STREAM0_PM]; + strm1_power = &parser->mp[DP_STREAM1_PM]; + link_power = &parser->mp[DP_LINK_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; + link_clk_count = link_power->num_clk; + strm0_clk_count = strm0_power->num_clk; + strm1_clk_count = strm1_power->num_clk; + + num_clk = of_property_count_strings(dev->of_node, "clock-names"); + + 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(link_clk, clk_name) && + link_clk_index < link_clk_count) { + struct dss_clk *clk = + &link_power->clk_config[link_clk_index]; + strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); + link_clk_index++; + + if (!strcmp(clk_name, "link_clk")) + clk->type = DSS_CLK_PCLK; + else + clk->type = DSS_CLK_AHB; + } else if (dp_parser_check_prefix(strm0_clk, clk_name) && + strm0_clk_index < strm0_clk_count) { + struct dss_clk *clk = + &strm0_power->clk_config[strm0_clk_index]; + strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); + strm0_clk_index++; + + clk->type = DSS_CLK_PCLK; + } else if (dp_parser_check_prefix(strm1_clk, clk_name) && + strm1_clk_index < strm1_clk_count) { + struct dss_clk *clk = + &strm1_power->clk_config[strm1_clk_index]; + strlcpy(clk->clk_name, clk_name, sizeof(clk->clk_name)); + strm1_clk_index++; + + clk->type = DSS_CLK_PCLK; + } + } + + pr_debug("clock parsing successful\n"); + +exit: + return rc; +} + +static int dp_parser_catalog(struct dp_parser *parser) +{ + int rc; + u32 version; + struct device *dev = &parser->pdev->dev; + + rc = of_property_read_u32(dev->of_node, "qcom,phy-version", &version); + + if (!rc) + parser->hw_cfg.phy_version = version; + + return 0; +} + +static int dp_parser_mst(struct dp_parser *parser) +{ + struct device *dev = &parser->pdev->dev; + + parser->has_mst = of_property_read_bool(dev->of_node, + "qcom,mst-enable"); + parser->has_mst_sideband = parser->has_mst; + + pr_debug("mst parsing successful. mst:%d\n", parser->has_mst); + + return 0; +} + +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_reg(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_catalog(parser); + if (rc) + goto err; + + rc = dp_parser_pinctrl(parser); + if (rc) + goto err; + + rc = dp_parser_msm_hdcp_dev(parser); + if (rc) + goto err; + + rc = dp_parser_mst(parser); +err: + return rc; +} + +static struct dp_io_data *dp_parser_get_io(struct dp_parser *dp_parser, + char *name) +{ + int i = 0; + struct dp_io *io; + + if (!dp_parser) { + pr_err("invalid input\n"); + goto err; + } + + io = &dp_parser->io; + + for (i = 0; i < io->len; i++) { + struct dp_io_data *data = &io->data[i]; + + if (!strcmp(data->name, name)) + return data; + } +err: + return NULL; +} + +static void dp_parser_get_io_buf(struct dp_parser *dp_parser, char *name) +{ + int i = 0; + struct dp_io *io; + + if (!dp_parser) { + pr_err("invalid input\n"); + return; + } + + io = &dp_parser->io; + + for (i = 0; i < io->len; i++) { + struct dp_io_data *data = &io->data[i]; + + if (!strcmp(data->name, name)) { + if (!data->buf) + data->buf = devm_kzalloc(&dp_parser->pdev->dev, + data->io.len, GFP_KERNEL); + } + } +} + +static void dp_parser_clear_io_buf(struct dp_parser *dp_parser) +{ + int i = 0; + struct dp_io *io; + + if (!dp_parser) { + pr_err("invalid input\n"); + return; + } + + io = &dp_parser->io; + + for (i = 0; i < io->len; i++) { + struct dp_io_data *data = &io->data[i]; + + if (data->buf) + devm_kfree(&dp_parser->pdev->dev, data->buf); + + data->buf = NULL; + } +} + +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->get_io = dp_parser_get_io; + parser->get_io_buf = dp_parser_get_io_buf; + parser->clear_io_buf = dp_parser_clear_io_buf; + 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++) { + dp_parser_put_clk_data(&parser->pdev->dev, &power[i]); + dp_parser_put_vreg_data(&parser->pdev->dev, &power[i]); + dp_parser_put_gpio_data(&parser->pdev->dev, &power[i]); + } + + dp_parser_clear_io_buf(parser); + devm_kfree(&parser->pdev->dev, parser->io.data); + 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..57fd0d4998397e3171daaa68f2732d5b46945cf5 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_parser.h @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#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_STREAM0_PM, + DP_STREAM1_PM, + DP_LINK_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"; + case DP_STREAM0_PM: return "DP_STREAM0_PM"; + case DP_STREAM1_PM: return "DP_STREAM1_PM"; + case DP_LINK_PM: return "DP_LINK_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_io_data - data structure to store DP IO related info + * @name: name of the IO + * @buf: buffer corresponding to IO for debugging + * @io: io data which give len and mapped address + */ +struct dp_io_data { + const char *name; + u8 *buf; + struct dss_io_data io; +}; + +/** + * struct dp_io - data struct to store array of DP IO info + * @len: total number of IOs + * @data: pointer to an array of DP IO data structures. + */ +struct dp_io { + u32 len; + struct dp_io_data *data; +}; + +/** + * 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, +}; + +/** + * enum dp_phy_version - version of the dp phy + * @DP_PHY_VERSION_UNKNOWN: Unknown controller version + * @DP_PHY_VERSION_4_2_0: DP phy v4.2.0 controller + * @DP_PHY_VERSION_MAX: max version + */ +enum dp_phy_version { + DP_PHY_VERSION_UNKNOWN, + DP_PHY_VERSION_2_0_0 = 0x200, + DP_PHY_VERSION_4_2_0 = 0x420, + DP_PHY_VERSION_MAX +}; + +/** + * struct dp_hw_cfg - DP HW specific configuration + * + * @phy_version: DP PHY HW version + */ +struct dp_hw_cfg { + enum dp_phy_version phy_version; +}; + +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 + * @msm_hdcp_dev: device pointer for the HDCP driver + * @mp: gpio, regulator and clock related data + * @pinctrl: pin-control related data + * @disp_data: controller's display related data + * @hw_cfg: DP HW specific settings + * @parse: function to be called by client to parse device tree. + * @get_io: function to be called by client to get io data. + * @get_io_buf: function to be called by client to get io buffers. + * @clear_io_buf: function to be called by client to clear io buffers. + */ +struct dp_parser { + struct platform_device *pdev; + struct device *msm_hdcp_dev; + 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; + struct dp_hw_cfg hw_cfg; + bool has_mst; + bool has_mst_sideband; + bool no_aux_switch; + + int (*parse)(struct dp_parser *parser); + struct dp_io_data *(*get_io)(struct dp_parser *parser, char *name); + void (*get_io_buf)(struct dp_parser *parser, char *name); + void (*clear_io_buf)(struct dp_parser *parser); +}; + +enum dp_phy_lane_num { + DP_PHY_LN0 = 0, + DP_PHY_LN1 = 1, + DP_PHY_LN2 = 2, + DP_PHY_LN3 = 3, + DP_MAX_PHY_LN = 4, +}; + +enum dp_mainlink_lane_num { + DP_ML0 = 0, + DP_ML1 = 1, + DP_ML2 = 2, + DP_ML3 = 3, +}; + +/** + * 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..94ceeb907749abad2ce008c4b125151d10eba4ab --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_power.c @@ -0,0 +1,661 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#include "dp_power.h" +#include "dp_catalog.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 clk *pixel1_clk_rcg; + struct clk *pixel1_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; + bool strm0_clks_on; + bool strm1_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 device *dev; + enum dp_pm_type module; + + dev = &power->pdev->dev; + + if (enable) { + for (module = DP_CORE_PM; module < DP_MAX_PM; module++) { + struct dss_module_power *pm = + &power->parser->mp[module]; + + if (!pm->num_clk) + continue; + + rc = msm_dss_get_clk(dev, pm->clk_config, pm->num_clk); + if (rc) { + pr_err("failed to get %s clk. err=%d\n", + dp_parser_pm_name(module), rc); + goto exit; + } + } + + 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; + } + + power->pixel1_clk_rcg = devm_clk_get(dev, "pixel1_clk_rcg"); + if (IS_ERR(power->pixel1_clk_rcg)) { + pr_debug("Unable to get DP pixel1 clk RCG\n"); + power->pixel1_clk_rcg = NULL; + } + + power->pixel1_parent = devm_clk_get(dev, "pixel1_parent"); + if (IS_ERR(power->pixel1_parent)) { + pr_debug("Unable to get DP pixel1 RCG parent\n"); + power->pixel1_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); + + if (power->pixel1_parent) + devm_clk_put(dev, power->pixel1_parent); + + if (power->pixel1_clk_rcg) + devm_clk_put(dev, power->pixel1_clk_rcg); + + for (module = DP_CORE_PM; module < DP_MAX_PM; module++) { + struct dss_module_power *pm = + &power->parser->mp[module]; + + if (!pm->num_clk) + continue; + + msm_dss_put_clk(pm->clk_config, pm->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_MAX_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_STREAM0_PM) && (power->strm0_clks_on)) { + pr_debug("strm0 clks already enabled\n"); + return 0; + } + + if ((pm_type == DP_STREAM1_PM) && (power->strm1_clks_on)) { + pr_debug("strm1 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; + } + } + + if (pm_type == DP_LINK_PM && power->link_clks_on) { + pr_debug("links clks already enabled\n"); + return 0; + } + } + + 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 if (pm_type == DP_STREAM0_PM) + power->strm0_clks_on = enable; + else if (pm_type == DP_STREAM1_PM) + power->strm1_clks_on = enable; + else if (pm_type == DP_LINK_PM) + 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 strm0_clks:%s strm1_clks:%s\n", + power->link_clks_on ? "on" : "off", + power->core_clks_on ? "on" : "off", + power->strm0_clks_on ? "on" : "off", + power->strm1_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; + + if (power->parser->no_aux_switch) + return 0; + + 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++) { + if (gpio_is_valid(config[i].gpio)) { + 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\n", 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, u32 strm_id) +{ + int rc = 0; + struct dp_power_private *power; + + if (!dp_power || strm_id >= DP_STREAM_MAX) { + pr_err("invalid power data. stream %d\n", strm_id); + rc = -EINVAL; + goto exit; + } + + power = container_of(dp_power, struct dp_power_private, dp_power); + + if (strm_id == DP_STREAM_0) { + if (power->pixel_clk_rcg && power->pixel_parent) + clk_set_parent(power->pixel_clk_rcg, + power->pixel_parent); + } else if (strm_id == DP_STREAM_1) { + if (power->pixel1_clk_rcg && power->pixel1_parent) + clk_set_parent(power->pixel1_clk_rcg, + power->pixel1_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); + + if (power->link_clks_on) + dp_power_clk_enable(dp_power, DP_LINK_PM, false); + + rc = sde_power_resource_enable(power->phandle, + power->dp_core_client, false); + if (rc) { + pr_err("Power resource disable 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..8fc0c90af9fdcfd709832390fac05848e2c1d1da --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_power.h @@ -0,0 +1,49 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#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, u32 stream_id); + 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..e0b9fd9f49cbbe041f96922264ef5c7afd60cb71 --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_reg.h @@ -0,0 +1,332 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#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_INTR_STATUS5 (0x00000034) + +#define DP_DP_HPD_CTRL (0x00000000) +#define DP_DP_HPD_INT_STATUS (0x00000004) +#define DP_DP_HPD_INT_ACK (0x00000008) +#define DP_DP_HPD_INT_MASK (0x0000000C) +#define DP_DP_HPD_REFTIMER (0x00000018) +#define DP_DP_HPD_EVENT_TIME_0 (0x0000001C) +#define DP_DP_HPD_EVENT_TIME_1 (0x00000020) +#define DP_AUX_CTRL (0x00000030) +#define DP_AUX_DATA (0x00000034) +#define DP_AUX_TRANS_CTRL (0x00000038) +#define DP_TIMEOUT_COUNT (0x0000003C) +#define DP_AUX_LIMITS (0x00000040) +#define DP_AUX_STATUS (0x00000044) + +#define DP_DPCD_CP_IRQ (0x201) +#define DP_DPCD_RXSTATUS (0x69493) + +#define DP_INTERRUPT_TRANS_NUM (0x000000A0) + +#define DP_MAINLINK_CTRL (0x00000000) +#define DP_STATE_CTRL (0x00000004) +#define DP_CONFIGURATION_CTRL (0x00000008) +#define DP_SOFTWARE_MVID (0x00000010) +#define DP_SOFTWARE_NVID (0x00000018) +#define DP_TOTAL_HOR_VER (0x0000001C) +#define DP_START_HOR_VER_FROM_SYNC (0x00000020) +#define DP_HSYNC_VSYNC_WIDTH_POLARITY (0x00000024) +#define DP_ACTIVE_HOR_VER (0x00000028) +#define DP_MISC1_MISC0 (0x0000002C) +#define DP_VALID_BOUNDARY (0x00000030) +#define DP_VALID_BOUNDARY_2 (0x00000034) +#define DP_LOGICAL2PHYSICAL_LANE_MAPPING (0x00000038) + +#define DP1_CONFIGURATION_CTRL (0x00000400) +#define DP_DP0_TIMESLOT_1_32 (0x00000404) +#define DP_DP0_TIMESLOT_33_63 (0x00000408) +#define DP_DP1_TIMESLOT_1_32 (0x0000040C) +#define DP_DP1_TIMESLOT_33_63 (0x00000410) +#define DP1_SOFTWARE_MVID (0x00000414) +#define DP1_SOFTWARE_NVID (0x00000418) +#define DP1_TOTAL_HOR_VER (0x0000041C) +#define DP1_START_HOR_VER_FROM_SYNC (0x00000420) +#define DP1_HSYNC_VSYNC_WIDTH_POLARITY (0x00000424) +#define DP1_ACTIVE_HOR_VER (0x00000428) +#define DP1_MISC1_MISC0 (0x0000042C) +#define DP_DP0_RG (0x000004F8) +#define DP_DP1_RG (0x000004FC) + +#define DP_MST_ACT (0x00000500) +#define DP_MST_MAINLINK_READY (0x00000504) + +#define DP_MAINLINK_READY (0x00000040) +#define DP_MAINLINK_LEVELS (0x00000044) +#define DP_TU (0x0000004C) + +#define DP_HBR2_COMPLIANCE_SCRAMBLER_RESET (0x00000054) +#define DP_TEST_80BIT_CUSTOM_PATTERN_REG0 (0x000000C0) +#define DP_TEST_80BIT_CUSTOM_PATTERN_REG1 (0x000000C4) +#define DP_TEST_80BIT_CUSTOM_PATTERN_REG2 (0x000000C8) + +#define MMSS_DP_MISC1_MISC0 (0x0000002C) +#define MMSS_DP_AUDIO_TIMING_GEN (0x00000080) +#define MMSS_DP_AUDIO_TIMING_RBR_32 (0x00000084) +#define MMSS_DP_AUDIO_TIMING_HBR_32 (0x00000088) +#define MMSS_DP_AUDIO_TIMING_RBR_44 (0x0000008C) +#define MMSS_DP_AUDIO_TIMING_HBR_44 (0x00000090) +#define MMSS_DP_AUDIO_TIMING_RBR_48 (0x00000094) +#define MMSS_DP_AUDIO_TIMING_HBR_48 (0x00000098) + +#define MMSS_DP_PSR_CRC_RG (0x00000154) +#define MMSS_DP_PSR_CRC_B (0x00000158) + +#define DP_COMPRESSION_MODE_CTRL (0x00000180) + +#define MMSS_DP_AUDIO_CFG (0x00000200) +#define MMSS_DP_AUDIO_STATUS (0x00000204) +#define MMSS_DP_AUDIO_PKT_CTRL (0x00000208) +#define MMSS_DP_AUDIO_PKT_CTRL2 (0x0000020C) +#define MMSS_DP_AUDIO_ACR_CTRL (0x00000210) +#define MMSS_DP_AUDIO_CTRL_RESET (0x00000214) + +#define MMSS_DP_SDP_CFG (0x00000228) +#define MMSS_DP_SDP_CFG2 (0x0000022C) +#define MMSS_DP_SDP_CFG3 (0x0000024C) +#define MMSS_DP_AUDIO_TIMESTAMP_0 (0x00000230) +#define MMSS_DP_AUDIO_TIMESTAMP_1 (0x00000234) + +#define MMSS_DP_AUDIO_STREAM_0 (0x00000240) +#define MMSS_DP_AUDIO_STREAM_1 (0x00000244) + +#define MMSS_DP_EXTENSION_0 (0x00000250) +#define MMSS_DP_EXTENSION_1 (0x00000254) +#define MMSS_DP_EXTENSION_2 (0x00000258) +#define MMSS_DP_EXTENSION_3 (0x0000025C) +#define MMSS_DP_EXTENSION_4 (0x00000260) +#define MMSS_DP_EXTENSION_5 (0x00000264) +#define MMSS_DP_EXTENSION_6 (0x00000268) +#define MMSS_DP_EXTENSION_7 (0x0000026C) +#define MMSS_DP_EXTENSION_8 (0x00000270) +#define MMSS_DP_EXTENSION_9 (0x00000274) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_0 (0x00000278) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_1 (0x0000027C) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_2 (0x00000280) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_3 (0x00000284) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_4 (0x00000288) +#define MMSS_DP_AUDIO_COPYMANAGEMENT_5 (0x0000028C) +#define MMSS_DP_AUDIO_ISRC_0 (0x00000290) +#define MMSS_DP_AUDIO_ISRC_1 (0x00000294) +#define MMSS_DP_AUDIO_ISRC_2 (0x00000298) +#define MMSS_DP_AUDIO_ISRC_3 (0x0000029C) +#define MMSS_DP_AUDIO_ISRC_4 (0x000002A0) +#define MMSS_DP_AUDIO_ISRC_5 (0x000002A4) +#define MMSS_DP_AUDIO_INFOFRAME_0 (0x000002A8) +#define MMSS_DP_AUDIO_INFOFRAME_1 (0x000002AC) +#define MMSS_DP_AUDIO_INFOFRAME_2 (0x000002B0) + +#define MMSS_DP_FLUSH (0x000002F8) + +#define MMSS_DP_GENERIC0_0 (0x00000300) +#define MMSS_DP_GENERIC0_1 (0x00000304) +#define MMSS_DP_GENERIC0_2 (0x00000308) +#define MMSS_DP_GENERIC0_3 (0x0000030C) +#define MMSS_DP_GENERIC0_4 (0x00000310) +#define MMSS_DP_GENERIC0_5 (0x00000314) +#define MMSS_DP_GENERIC0_6 (0x00000318) +#define MMSS_DP_GENERIC0_7 (0x0000031C) +#define MMSS_DP_GENERIC0_8 (0x00000320) +#define MMSS_DP_GENERIC0_9 (0x00000324) +#define MMSS_DP_GENERIC1_0 (0x00000328) +#define MMSS_DP_GENERIC1_1 (0x0000032C) +#define MMSS_DP_GENERIC1_2 (0x00000330) +#define MMSS_DP_GENERIC1_3 (0x00000334) +#define MMSS_DP_GENERIC1_4 (0x00000338) +#define MMSS_DP_GENERIC1_5 (0x0000033C) +#define MMSS_DP_GENERIC1_6 (0x00000340) +#define MMSS_DP_GENERIC1_7 (0x00000344) +#define MMSS_DP_GENERIC1_8 (0x00000348) +#define MMSS_DP_GENERIC1_9 (0x0000034C) + +#define MMSS_DP1_GENERIC0_0 (0x00000490) +#define MMSS_DP1_GENERIC0_1 (0x00000494) +#define MMSS_DP1_GENERIC0_2 (0x00000498) +#define MMSS_DP1_GENERIC0_3 (0x0000049C) +#define MMSS_DP1_GENERIC0_4 (0x000004A0) +#define MMSS_DP1_GENERIC0_5 (0x000004A4) +#define MMSS_DP1_GENERIC0_6 (0x000004A8) +#define MMSS_DP1_GENERIC0_7 (0x000004AC) +#define MMSS_DP1_GENERIC0_8 (0x000004B0) +#define MMSS_DP1_GENERIC0_9 (0x000004B4) +#define MMSS_DP1_GENERIC1_0 (0x000004B8) +#define MMSS_DP1_GENERIC1_1 (0x000004BC) +#define MMSS_DP1_GENERIC1_2 (0x000004C0) +#define MMSS_DP1_GENERIC1_3 (0x000004C4) +#define MMSS_DP1_GENERIC1_4 (0x000004C8) +#define MMSS_DP1_GENERIC1_5 (0x000004CC) +#define MMSS_DP1_GENERIC1_6 (0x000004D0) +#define MMSS_DP1_GENERIC1_7 (0x000004D4) +#define MMSS_DP1_GENERIC1_8 (0x000004D8) +#define MMSS_DP1_GENERIC1_9 (0x000004DC) + +#define MMSS_DP1_SDP_CFG (0x000004E0) +#define MMSS_DP1_SDP_CFG2 (0x000004E4) +#define MMSS_DP1_SDP_CFG3 (0x000004E8) + +#define MMSS_DP_VSCEXT_0 (0x000002D0) +#define MMSS_DP_VSCEXT_1 (0x000002D4) +#define MMSS_DP_VSCEXT_2 (0x000002D8) +#define MMSS_DP_VSCEXT_3 (0x000002DC) +#define MMSS_DP_VSCEXT_4 (0x000002E0) +#define MMSS_DP_VSCEXT_5 (0x000002E4) +#define MMSS_DP_VSCEXT_6 (0x000002E8) +#define MMSS_DP_VSCEXT_7 (0x000002EC) +#define MMSS_DP_VSCEXT_8 (0x000002F0) +#define MMSS_DP_VSCEXT_9 (0x000002F4) + +#define MMSS_DP_BIST_ENABLE (0x00000000) +#define MMSS_DP_TIMING_ENGINE_EN (0x00000010) +#define MMSS_DP_INTF_CONFIG (0x00000014) +#define MMSS_DP_INTF_HSYNC_CTL (0x00000018) +#define MMSS_DP_INTF_VSYNC_PERIOD_F0 (0x0000001C) +#define MMSS_DP_INTF_VSYNC_PERIOD_F1 (0x00000020) +#define MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F0 (0x00000024) +#define MMSS_DP_INTF_VSYNC_PULSE_WIDTH_F1 (0x00000028) +#define MMSS_INTF_DISPLAY_V_START_F0 (0x0000002C) +#define MMSS_INTF_DISPLAY_V_START_F1 (0x00000030) +#define MMSS_DP_INTF_DISPLAY_V_END_F0 (0x00000034) +#define MMSS_DP_INTF_DISPLAY_V_END_F1 (0x00000038) +#define MMSS_DP_INTF_ACTIVE_V_START_F0 (0x0000003C) +#define MMSS_DP_INTF_ACTIVE_V_START_F1 (0x00000040) +#define MMSS_DP_INTF_ACTIVE_V_END_F0 (0x00000044) +#define MMSS_DP_INTF_ACTIVE_V_END_F1 (0x00000048) +#define MMSS_DP_INTF_DISPLAY_HCTL (0x0000004C) +#define MMSS_DP_INTF_ACTIVE_HCTL (0x00000050) +#define MMSS_DP_INTF_POLARITY_CTL (0x00000058) +#define MMSS_DP_TPG_MAIN_CONTROL (0x00000060) +#define MMSS_DP_TPG_VIDEO_CONFIG (0x00000064) +#define MMSS_DP_DSC_DTO (0x0000007C) +#define MMSS_DP_ASYNC_FIFO_CONFIG (0x00000088) + +#define MMSS_DP1_BIST_ENABLE (0x00000000) +#define MMSS_DP1_TIMING_ENGINE_EN (0x00000010) +#define MMSS_DP1_INTF_CONFIG (0x00000014) +#define MMSS_DP1_INTF_HSYNC_CTL (0x00000018) +#define MMSS_DP1_INTF_VSYNC_PERIOD_F0 (0x0000001C) +#define MMSS_DP1_INTF_VSYNC_PERIOD_F1 (0x00000020) +#define MMSS_DP1_INTF_VSYNC_PULSE_WIDTH_F0 (0x00000024) +#define MMSS_DP1_INTF_VSYNC_PULSE_WIDTH_F1 (0x00000028) +#define MMSS_DP1_INTF_DISPLAY_V_START_F0 (0x0000002C) +#define MMSS_DP1_INTF_DISPLAY_V_START_F1 (0x00000030) +#define MMSS_DP1_INTF_DISPLAY_V_END_F0 (0x00000034) +#define MMSS_DP1_INTF_DISPLAY_V_END_F1 (0x00000038) +#define MMSS_DP1_INTF_ACTIVE_V_START_F0 (0x0000003C) +#define MMSS_DP1_INTF_ACTIVE_V_START_F1 (0x00000040) +#define MMSS_DP1_INTF_ACTIVE_V_END_F0 (0x00000044) +#define MMSS_DP1_INTF_ACTIVE_V_END_F1 (0x00000048) +#define MMSS_DP1_INTF_DISPLAY_HCTL (0x0000004C) +#define MMSS_DP1_INTF_ACTIVE_HCTL (0x00000050) +#define MMSS_DP1_INTF_POLARITY_CTL (0x00000058) +#define MMSS_DP1_TPG_MAIN_CONTROL (0x00000060) +#define MMSS_DP1_TPG_VIDEO_CONFIG (0x00000064) +#define MMSS_DP1_DSC_DTO (0x0000007C) +#define MMSS_DP1_ASYNC_FIFO_CONFIG (0x00000088) + +/*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 (0x00000054) +#define DP_PHY_AUX_INTERRUPT_CLEAR (0x00000058) +#define DP_PHY_AUX_INTERRUPT_STATUS (0x000000D8) +#define DP_PHY_AUX_INTERRUPT_MASK_V200 (0x00000048) +#define DP_PHY_AUX_INTERRUPT_CLEAR_V200 (0x0000004C) +#define DP_PHY_AUX_INTERRUPT_STATUS_V200 (0x000000BC) + +#define DP_PHY_SPARE0 (0x00AC) + +#define TXn_TX_EMP_POST1_LVL (0x000C) +#define TXn_TX_DRV_LVL (0x001C) + +#define DP_PHY_AUX_INTERRUPT_MASK_V420 (0x0054) +#define DP_PHY_SPARE0_V420 (0x00C8) +#define TXn_TX_DRV_LVL_V420 (0x0014) + +#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) +#define MMSS_DP_PIXEL_M_V200 (0x0130) +#define MMSS_DP_PIXEL_N_V200 (0x0134) +#define MMSS_DP_PIXEL_M_V420 (0x01B4) +#define MMSS_DP_PIXEL_N_V420 (0x01B8) +#define MMSS_DP_PIXEL1_M (0x018C) +#define MMSS_DP_PIXEL1_N (0x0190) +#define MMSS_DP_PIXEL1_M_V200 (0x0148) +#define MMSS_DP_PIXEL1_N_V200 (0x014C) +#define MMSS_DP_PIXEL1_M_V420 (0x01CC) +#define MMSS_DP_PIXEL1_N_V420 (0x01D0) + +/* DP HDCP 1.3 registers */ +#define DP_HDCP_CTRL (0x0A0) +#define DP_HDCP_STATUS (0x0A4) +#define DP_HDCP_SW_UPPER_AKSV (0x098) +#define DP_HDCP_SW_LOWER_AKSV (0x09C) +#define DP_HDCP_ENTROPY_CTRL0 (0x350) +#define DP_HDCP_ENTROPY_CTRL1 (0x35C) +#define DP_HDCP_SHA_STATUS (0x0C8) +#define DP_HDCP_RCVPORT_DATA2_0 (0x0B0) +#define DP_HDCP_RCVPORT_DATA3 (0x0A4) +#define DP_HDCP_RCVPORT_DATA4 (0x0A8) +#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..d25d268f12365b027dc4f91294d4b5958fc61caf --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_usbpd.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__ + +#include +#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 { + bool forced_disconnect; + u32 vdo; + struct device *dev; + struct usbpd *pd; + struct usbpd_svid_handler svid_handler; + struct dp_hpd_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->base.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->base.hpd_high = (buf & BIT(7)) ? true : false; + status->base.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->base.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->base.hpd_high); + pr_debug("hpd_irq = %d\n", status->base.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.base.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.base.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 int dp_usbpd_get_ss_lanes(struct dp_usbpd_private *pd) +{ + int rc = 0; + int timeout = 250; + + /* + * 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.base.multi_func) { + while (timeout) { + rc = pd->svid_handler.request_usb_ss_lane( + pd->pd, &pd->svid_handler); + if (rc != -EBUSY) + break; + + pr_warn("USB busy, retry\n"); + + /* wait for hw recommended delay for usb */ + msleep(20); + timeout--; + } + } + + return rc; +} + +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; + int rc = 0; + + 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->forced_disconnect) + break; + + pd->vdo = *vdos; + dp_usbpd_get_status(pd); + + if (!pd->dp_usbpd.base.alt_mode_cfg_done) { + if (pd->dp_usbpd.port & BIT(1)) + dp_usbpd_send_event(pd, DP_USBPD_EVT_CONFIGURE); + break; + } + + if (pd->dp_cb && pd->dp_cb->attention) + pd->dp_cb->attention(pd->dev); + + 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.base.alt_mode_cfg_done = true; + dp_usbpd_get_status(pd); + + pd->dp_usbpd.base.orientation = + usbpd_get_plug_orientation(pd->pd); + + rc = dp_usbpd_get_ss_lanes(pd); + if (rc) { + pr_err("failed to get SuperSpeed lanes\n"); + break; + } + + 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_simulate_connect(struct dp_hpd *dp_hpd, bool hpd) +{ + int rc = 0; + struct dp_usbpd *dp_usbpd; + struct dp_usbpd_private *pd; + + if (!dp_hpd) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base); + pd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd); + + dp_usbpd->base.hpd_high = hpd; + pd->forced_disconnect = !hpd; + pd->dp_usbpd.base.alt_mode_cfg_done = hpd; + + pr_debug("hpd_high=%d, forced_disconnect=%d, orientation=%d\n", + dp_usbpd->base.hpd_high, pd->forced_disconnect, + pd->dp_usbpd.base.orientation); + if (hpd) + pd->dp_cb->configure(pd->dev); + else + pd->dp_cb->disconnect(pd->dev); + +error: + return rc; +} + +static int dp_usbpd_simulate_attention(struct dp_hpd *dp_hpd, int vdo) +{ + int rc = 0; + struct dp_usbpd *dp_usbpd; + struct dp_usbpd_private *pd; + + dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base); + if (!dp_usbpd) { + pr_err("invalid input\n"); + rc = -EINVAL; + goto error; + } + + pd = container_of(dp_usbpd, struct dp_usbpd_private, dp_usbpd); + + pd->vdo = vdo; + dp_usbpd_get_status(pd); + + if (pd->dp_cb && pd->dp_cb->attention) + pd->dp_cb->attention(pd->dev); +error: + return rc; +} + +struct dp_hpd *dp_usbpd_get(struct device *dev, struct dp_hpd_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->base.simulate_connect = dp_usbpd_simulate_connect; + dp_usbpd->base.simulate_attention = dp_usbpd_simulate_attention; + + return &dp_usbpd->base; +error: + return ERR_PTR(rc); +} + +void dp_usbpd_put(struct dp_hpd *dp_hpd) +{ + struct dp_usbpd *dp_usbpd; + struct dp_usbpd_private *usbpd; + + dp_usbpd = container_of(dp_hpd, struct dp_usbpd, base); + 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..258f107cbc59504ff6e91f406060fa6413ece56b --- /dev/null +++ b/drivers/gpu/drm/msm/dp/dp_usbpd.h @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2012-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DP_USBPD_H_ +#define _DP_USBPD_H_ + +#include + +#include +#include +#include "dp_hpd.h" + +/** + * 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 + * @low_pow_st: low power state + * @adaptor_dp_en: adaptor functionality enabled + * @usb_config_req: request to switch to usb + * @exit_dp_mode: request exit from displayport mode + * @debug_en: bool to specify debug mode + */ +struct dp_usbpd { + struct dp_hpd base; + enum dp_usbpd_port port; + bool low_pow_st; + bool adaptor_dp_en; + bool usb_config_req; + bool exit_dp_mode; + bool debug_en; +}; + +/** + * 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_hpd *dp_usbpd_get(struct device *dev, struct dp_hpd_cb *cb); + +void dp_usbpd_put(struct dp_hpd *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..a7177a547695ec7df74b181c6fcd70b972e9fc16 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.c @@ -0,0 +1,292 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#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; + ctrl->ops.get_cmd_read_data = dsi_ctrl_hw_cmn_get_cmd_read_data; + ctrl->ops.clear_rdbk_register = dsi_ctrl_hw_cmn_clear_rdbk_reg; + ctrl->ops.ctrl_reset = dsi_ctrl_hw_cmn_ctrl_reset; + ctrl->ops.mask_error_intr = dsi_ctrl_hw_cmn_mask_error_intr; + ctrl->ops.error_intr_ctrl = dsi_ctrl_hw_cmn_error_intr_ctrl; + ctrl->ops.get_error_mask = dsi_ctrl_hw_cmn_get_error_mask; + ctrl->ops.get_hw_version = dsi_ctrl_hw_cmn_get_hw_version; + ctrl->ops.wait_for_cmd_mode_mdp_idle = + dsi_ctrl_hw_cmn_wait_for_cmd_mode_mdp_idle; + ctrl->ops.setup_avr = dsi_ctrl_hw_cmn_setup_avr; + ctrl->ops.set_continuous_clk = dsi_ctrl_hw_cmn_set_continuous_clk; + + 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; + ctrl->ops.schedule_dma_cmd = NULL; + ctrl->ops.get_cont_splash_status = NULL; + ctrl->ops.kickoff_command_non_embedded_mode = NULL; + ctrl->ops.config_clk_gating = NULL; + 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; + ctrl->ops.schedule_dma_cmd = NULL; + ctrl->ops.get_cont_splash_status = NULL; + ctrl->ops.kickoff_command_non_embedded_mode = NULL; + ctrl->ops.config_clk_gating = NULL; + break; + case DSI_CTRL_VERSION_2_2: + case DSI_CTRL_VERSION_2_3: + ctrl->ops.phy_reset_config = dsi_ctrl_hw_22_phy_reset_config; + ctrl->ops.config_clk_gating = dsi_ctrl_hw_22_config_clk_gating; + ctrl->ops.get_cont_splash_status = + dsi_ctrl_hw_22_get_cont_splash_status; + 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; + ctrl->ops.schedule_dma_cmd = dsi_ctrl_hw_22_schedule_dma_cmd; + ctrl->ops.kickoff_command_non_embedded_mode = + dsi_ctrl_hw_kickoff_non_embedded_mode; + 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. + * @null_insertion_enabled: DSI controller inserts null packet. + * + * 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, bool null_insertion_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; + ctrl->null_insertion_enabled = null_insertion_enabled; + 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: + case DSI_CTRL_VERSION_2_3: + 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; + phy->ops.clamp_ctrl = dsi_phy_hw_v3_0_clamp_ctrl; + phy->ops.phy_lane_reset = dsi_phy_hw_v3_0_lane_reset; + phy->ops.toggle_resync_fifo = dsi_phy_hw_v3_0_toggle_resync_fifo; +} + +/** + * dsi_catalog_phy_4_0_init() - catalog init for DSI PHY 7nm + */ +static void dsi_catalog_phy_4_0_init(struct dsi_phy_hw *phy) +{ + phy->ops.regulator_enable = NULL; + phy->ops.regulator_disable = NULL; + phy->ops.enable = dsi_phy_hw_v4_0_enable; + phy->ops.disable = dsi_phy_hw_v4_0_disable; + phy->ops.calculate_timing_params = + dsi_phy_hw_calculate_timing_params; + phy->ops.ulps_ops.wait_for_lane_idle = + dsi_phy_hw_v4_0_wait_for_lane_idle; + phy->ops.ulps_ops.ulps_request = + dsi_phy_hw_v4_0_ulps_request; + phy->ops.ulps_ops.ulps_exit = + dsi_phy_hw_v4_0_ulps_exit; + phy->ops.ulps_ops.get_lanes_in_ulps = + dsi_phy_hw_v4_0_get_lanes_in_ulps; + phy->ops.ulps_ops.is_lanes_in_ulps = + dsi_phy_hw_v4_0_is_lanes_in_ulps; + phy->ops.phy_timing_val = dsi_phy_hw_timing_val_v4_0; + phy->ops.phy_lane_reset = dsi_phy_hw_v4_0_lane_reset; + phy->ops.toggle_resync_fifo = dsi_phy_hw_v4_0_toggle_resync_fifo; + phy->ops.reset_clk_en_sel = dsi_phy_hw_v4_0_reset_clk_en_sel; +} + +/** + * 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_4_0: + dsi_catalog_phy_4_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..a0869d2239e7fd0c954ba7757c9926f730e1dda3 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_catalog.h @@ -0,0 +1,241 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#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. + * @null_insertion_enabled: DSI controller inserts null packet. + * + * 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, bool null_insertion_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); +void dsi_phy_hw_v3_0_clamp_ctrl(struct dsi_phy_hw *phy, bool enable); +int dsi_phy_hw_v3_0_lane_reset(struct dsi_phy_hw *phy); +void dsi_phy_hw_v3_0_toggle_resync_fifo(struct dsi_phy_hw *phy); + +/* Definitions for 7nm PHY hardware driver */ +void dsi_phy_hw_v4_0_enable(struct dsi_phy_hw *phy, struct dsi_phy_cfg *cfg); +void dsi_phy_hw_v4_0_disable(struct dsi_phy_hw *phy, struct dsi_phy_cfg *cfg); +int dsi_phy_hw_v4_0_wait_for_lane_idle(struct dsi_phy_hw *phy, u32 lanes); +void dsi_phy_hw_v4_0_ulps_request(struct dsi_phy_hw *phy, + struct dsi_phy_cfg *cfg, u32 lanes); +void dsi_phy_hw_v4_0_ulps_exit(struct dsi_phy_hw *phy, + struct dsi_phy_cfg *cfg, u32 lanes); +u32 dsi_phy_hw_v4_0_get_lanes_in_ulps(struct dsi_phy_hw *phy); +bool dsi_phy_hw_v4_0_is_lanes_in_ulps(u32 lanes, u32 ulps_lanes); +int dsi_phy_hw_timing_val_v4_0(struct dsi_phy_per_lane_cfgs *timing_cfg, + u32 *timing_val, u32 size); +int dsi_phy_hw_v4_0_lane_reset(struct dsi_phy_hw *phy); +void dsi_phy_hw_v4_0_toggle_resync_fifo(struct dsi_phy_hw *phy); +void dsi_phy_hw_v4_0_reset_clk_en_sel(struct dsi_phy_hw *phy); + +/* 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, u32 *entries, + u32 size); +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_setup_avr(struct dsi_ctrl_hw *ctrl, bool enable); + +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); +u32 dsi_ctrl_hw_cmn_get_cmd_read_data(struct dsi_ctrl_hw *ctrl, + u8 *rd_buf, + u32 read_offset, + u32 rx_byte, + u32 pkt_size, u32 *hw_read_cnt); +void dsi_ctrl_hw_cmn_clear_rdbk_reg(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_22_schedule_dma_cmd(struct dsi_ctrl_hw *ctrl, int line_on); +int dsi_ctrl_hw_cmn_ctrl_reset(struct dsi_ctrl_hw *ctrl, + int mask); +void dsi_ctrl_hw_cmn_mask_error_intr(struct dsi_ctrl_hw *ctrl, u32 idx, + bool en); +void dsi_ctrl_hw_cmn_error_intr_ctrl(struct dsi_ctrl_hw *ctrl, bool en); +u32 dsi_ctrl_hw_cmn_get_error_mask(struct dsi_ctrl_hw *ctrl); +u32 dsi_ctrl_hw_cmn_get_hw_version(struct dsi_ctrl_hw *ctrl); +int dsi_ctrl_hw_cmn_wait_for_cmd_mode_mdp_idle(struct dsi_ctrl_hw *ctrl); + +/* 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); +void dsi_ctrl_hw_kickoff_non_embedded_mode(struct dsi_ctrl_hw *ctrl, + struct dsi_ctrl_cmd_dma_info *cmd, + u32 flags); + +/* Definitions specific to 2.2 DSI controller hardware */ +bool dsi_ctrl_hw_22_get_cont_splash_status(struct dsi_ctrl_hw *ctrl); +void dsi_ctrl_hw_22_config_clk_gating(struct dsi_ctrl_hw *ctrl, bool enable, + enum dsi_clk_gate_type clk_selection); + +void dsi_ctrl_hw_cmn_set_continuous_clk(struct dsi_ctrl_hw *ctrl, bool enable); + +#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..fa4c81209b4e46c3d9ad97c18070263a4d336a8c --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_clk.h @@ -0,0 +1,312 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#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_link_clk_op_type { + DSI_LINK_CLK_SET_RATE = BIT(0), + DSI_LINK_CLK_PREPARE = BIT(1), + DSI_LINK_CLK_ENABLE = BIT(2), + DSI_LINK_CLK_START = BIT(0) | BIT(1) | BIT(2), +}; + +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), +}; + +enum dsi_lclk_type { + DSI_LINK_NONE = 0, + DSI_LINK_LP_CLK = BIT(0), + DSI_LINK_HS_CLK = BIT(1), +}; + +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_hs_clk_info - Set of high speed link clocks for DSI HW + * @byte_clk: Handle to DSI byte_clk. + * @pixel_clk: Handle to DSI pixel_clk. + * @byte_intf_clk: Handle to DSI byte intf. clock. + */ +struct dsi_link_hs_clk_info { + struct clk *byte_clk; + struct clk *pixel_clk; + struct clk *byte_intf_clk; +}; + +/** + * struct dsi_link_lp_clk_info - Set of low power link clocks for DSI HW. + * @esc_clk: Handle to DSI escape clock. + */ +struct dsi_link_lp_clk_info { + struct clk *esc_clk; +}; + +/** + * struct link_clk_freq - Clock frequency information for Link clocks + * @byte_clk_rate: Frequency of DSI byte_clk in KHz. + * @pixel_clk_rate: Frequency of DSI pixel_clk 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. + * @l_type: specifies if the clock is HS or LP type. Valid only for link clocks. + * @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_lclk_type l_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. + * @l_type: specifies if the clock is HS or LP type. Valid only for link clocks. + * @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_lclk_type l_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. + * @l_type: specifies if the clock is HS or LP type. Valid only for link clocks. + * @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_lclk_type l_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. + * @l_type: specifies if the clock is HS or LP type.Valid only for link clocks. + * @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_lclk_type l_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_lp_clks[MAX_DSI_CTRL] array of low power(esc) clock configurations + * @l_hs_clks[MAX_DSI_CTRL] array of high speed 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_lp_clk_info l_lp_clks[MAX_DSI_CTRL]; + struct dsi_link_hs_clk_info l_hs_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_clk. + * @pixel_clk: Handle to DSI pixel_clk. + */ +struct dsi_clk_link_set { + struct clk *byte_clk; + struct clk *pixel_clk; +}; + +/** + * dsi_display_clk_mngr_update_splash_status() - Update splash stattus + * @clk_mngr: Structure containing DSI clock information + * @status: Splash status + */ +void dsi_display_clk_mngr_update_splash_status(void *clk_mgr, bool status); + +/** + * 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_link_clk_force_update_ctrl() - force to set link clks + * @handle: Handle of desired DSI clock client. + * + * return: error code in case of failure or 0 for success. + */ + +int dsi_display_link_clk_force_update_ctrl(void *handle); + +/** + * 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_clk + * @client: DSI clock client pointer. + * @pixel_clk: Pixel_clk 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..2a58db103bfffa799573b75aedfe08722242ff85 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c @@ -0,0 +1,1458 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#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_hs_clk_info hs_clks; + struct dsi_link_lp_clk_info lp_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; + + bool is_cont_splash_enabled; + 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].hs_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].hs_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_hs_clk_set_rate(struct dsi_link_hs_clk_info *link_hs_clks, + int index) +{ + int rc = 0; + struct dsi_clk_mngr *mngr; + struct dsi_link_clks *l_clks; + + if (index >= MAX_DSI_CTRL) { + pr_err("Invalid DSI ctrl index\n"); + return -EINVAL; + } + + l_clks = container_of(link_hs_clks, struct dsi_link_clks, hs_clks); + mngr = container_of(l_clks, struct dsi_clk_mngr, link_clks[index]); + + /* + * In an ideal world, cont_splash_enabled should not be required inside + * the clock manager. But, in the current driver cont_splash_enabled + * flag is set inside mdp driver and there is no interface event + * associated with this flag setting. + */ + if (mngr->is_cont_splash_enabled) + return 0; + + rc = clk_set_rate(link_hs_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(link_hs_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 (link_hs_clks->byte_intf_clk) { + rc = clk_set_rate(link_hs_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_hs_clk_prepare(struct dsi_link_hs_clk_info *link_hs_clks) +{ + int rc = 0; + + rc = clk_prepare(link_hs_clks->byte_clk); + if (rc) { + pr_err("Failed to prepare dsi byte clk, rc=%d\n", rc); + goto byte_clk_err; + } + + rc = clk_prepare(link_hs_clks->pixel_clk); + if (rc) { + pr_err("Failed to prepare dsi pixel clk, rc=%d\n", rc); + goto pixel_clk_err; + } + + if (link_hs_clks->byte_intf_clk) { + rc = clk_prepare(link_hs_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(link_hs_clks->pixel_clk); +pixel_clk_err: + clk_unprepare(link_hs_clks->byte_clk); +byte_clk_err: + return rc; +} + +static void dsi_link_hs_clk_unprepare(struct dsi_link_hs_clk_info *link_hs_clks) +{ + if (link_hs_clks->byte_intf_clk) + clk_unprepare(link_hs_clks->byte_intf_clk); + clk_unprepare(link_hs_clks->pixel_clk); + clk_unprepare(link_hs_clks->byte_clk); +} + +static int dsi_link_hs_clk_enable(struct dsi_link_hs_clk_info *link_hs_clks) +{ + int rc = 0; + + rc = clk_enable(link_hs_clks->byte_clk); + if (rc) { + pr_err("Failed to enable dsi byte clk, rc=%d\n", rc); + goto byte_clk_err; + } + + rc = clk_enable(link_hs_clks->pixel_clk); + if (rc) { + pr_err("Failed to enable dsi pixel clk, rc=%d\n", rc); + goto pixel_clk_err; + } + + if (link_hs_clks->byte_intf_clk) { + rc = clk_enable(link_hs_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(link_hs_clks->pixel_clk); +pixel_clk_err: + clk_disable(link_hs_clks->byte_clk); +byte_clk_err: + return rc; +} + +static void dsi_link_hs_clk_disable(struct dsi_link_hs_clk_info *link_hs_clks) +{ + if (link_hs_clks->byte_intf_clk) + clk_disable(link_hs_clks->byte_intf_clk); + clk_disable(link_hs_clks->pixel_clk); + clk_disable(link_hs_clks->byte_clk); +} + +/** + * dsi_link_clk_start() - enable dsi link clocks + */ +static int dsi_link_hs_clk_start(struct dsi_link_hs_clk_info *link_hs_clks, + enum dsi_link_clk_op_type op_type, int index) +{ + int rc = 0; + + if (index >= MAX_DSI_CTRL) { + pr_err("Invalid DSI ctrl index\n"); + return -EINVAL; + } + + if (op_type & DSI_LINK_CLK_SET_RATE) { + rc = dsi_link_hs_clk_set_rate(link_hs_clks, index); + if (rc) { + pr_err("failed to set HS clk rates, rc = %d\n", rc); + goto error; + } + } + + if (op_type & DSI_LINK_CLK_PREPARE) { + rc = dsi_link_hs_clk_prepare(link_hs_clks); + if (rc) { + pr_err("failed to prepare link HS clks, rc = %d\n", rc); + goto error; + } + } + + if (op_type & DSI_LINK_CLK_ENABLE) { + rc = dsi_link_hs_clk_enable(link_hs_clks); + if (rc) { + pr_err("failed to enable link HS clks, rc = %d\n", rc); + goto error_unprepare; + } + } + + pr_debug("HS Link clocks are enabled\n"); + return rc; +error_unprepare: + dsi_link_hs_clk_unprepare(link_hs_clks); +error: + return rc; +} + +/** + * dsi_link_clk_stop() - Stop DSI link clocks. + */ +static int dsi_link_hs_clk_stop(struct dsi_link_hs_clk_info *link_hs_clks) +{ + struct dsi_link_clks *l_clks; + + l_clks = container_of(link_hs_clks, struct dsi_link_clks, hs_clks); + + dsi_link_hs_clk_disable(link_hs_clks); + dsi_link_hs_clk_unprepare(link_hs_clks); + + pr_debug("HS Link clocks disabled\n"); + + return 0; +} + +static int dsi_link_lp_clk_start(struct dsi_link_lp_clk_info *link_lp_clks, + int index) +{ + int rc = 0; + struct dsi_clk_mngr *mngr; + struct dsi_link_clks *l_clks; + + if (index >= MAX_DSI_CTRL) { + pr_err("Invalid DSI ctrl index\n"); + return -EINVAL; + } + + l_clks = container_of(link_lp_clks, struct dsi_link_clks, lp_clks); + + mngr = container_of(l_clks, struct dsi_clk_mngr, link_clks[index]); + if (!mngr) + return -EINVAL; + + /* + * In an ideal world, cont_splash_enabled should not be required inside + * the clock manager. But, in the current driver cont_splash_enabled + * flag is set inside mdp driver and there is no interface event + * associated with this flag setting. Also, set rate for clock need not + * be called for every enable call. It should be done only once when + * coming out of suspend. + */ + if (mngr->is_cont_splash_enabled) + goto prepare; + + rc = clk_set_rate(link_lp_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; + } + +prepare: + rc = clk_prepare_enable(link_lp_clks->esc_clk); + if (rc) { + pr_err("Failed to enable dsi esc clk\n"); + clk_unprepare(l_clks->lp_clks.esc_clk); + } +error: + pr_debug("LP Link clocks are enabled\n"); + return rc; +} + +static int dsi_link_lp_clk_stop( + struct dsi_link_lp_clk_info *link_lp_clks) +{ + struct dsi_link_clks *l_clks; + + l_clks = container_of(link_lp_clks, struct dsi_link_clks, lp_clks); + + clk_disable_unprepare(l_clks->lp_clks.esc_clk); + + pr_debug("LP Link clocks are 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, + enum dsi_lclk_type l_type, 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]; + + if (l_type & DSI_LINK_LP_CLK) { + rc = dsi_link_lp_clk_start(&m_clks->lp_clks, master_ndx); + if (rc) { + pr_err("failed to turn on master lp link clocks, rc=%d\n", + rc); + goto error; + } + } + + if (l_type & DSI_LINK_HS_CLK) { + rc = dsi_link_hs_clk_start(&m_clks->hs_clks, + DSI_LINK_CLK_START, master_ndx); + if (rc) { + pr_err("failed to turn on master hs link clocks, rc=%d\n", + rc); + goto error; + } + } + + for (i = 0; i < ctrl_count; i++) { + clk = &clks[i]; + if (!clk || (clk == m_clks)) + continue; + + if (l_type & DSI_LINK_LP_CLK) { + rc = dsi_link_lp_clk_start(&clk->lp_clks, i); + if (rc) { + pr_err("failed to turn on lp link clocks, rc=%d\n", + rc); + goto error_disable_master; + } + } + + if (l_type & DSI_LINK_HS_CLK) { + rc = dsi_link_hs_clk_start(&clk->hs_clks, + DSI_LINK_CLK_START, i); + if (rc) { + pr_err("failed to turn on hs link clocks, rc=%d\n", + rc); + goto error_disable_master; + } + } + } + return rc; + +error_disable_master: + if (l_type == DSI_LINK_LP_CLK) + (void)dsi_link_lp_clk_stop(&m_clks->lp_clks); + else if (l_type == DSI_LINK_HS_CLK) + (void)dsi_link_hs_clk_stop(&m_clks->hs_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, + enum dsi_lclk_type l_type, 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; + + if (l_type & DSI_LINK_LP_CLK) { + rc = dsi_link_lp_clk_stop(&clk->lp_clks); + if (rc) + pr_err("failed to turn off lp link clocks, rc=%d\n", + rc); + } + + if (l_type & DSI_LINK_HS_CLK) { + rc = dsi_link_hs_clk_stop(&clk->hs_clks); + if (rc) + pr_err("failed to turn off hs link clocks, rc=%d\n", + rc); + } + } + + if (l_type & DSI_LINK_LP_CLK) { + rc = dsi_link_lp_clk_stop(&m_clks->lp_clks); + if (rc) + pr_err("failed to turn off master lp link clocks, rc=%d\n", + rc); + } + + if (l_type & DSI_LINK_HS_CLK) { + rc = dsi_link_hs_clk_stop(&m_clks->hs_clks); + if (rc) + pr_err("failed to turn off master hs link clocks, rc=%d\n", + rc); + } + + return rc; +} + +static int dsi_clk_update_link_clk_state(struct dsi_clk_mngr *mngr, + struct dsi_link_clks *l_clks, enum dsi_lclk_type l_type, u32 l_state, + bool enable) +{ + int rc = 0; + + if (!mngr) + return -EINVAL; + + if (enable) { + if (mngr->pre_clkon_cb) { + rc = mngr->pre_clkon_cb(mngr->priv_data, DSI_LINK_CLK, + l_type, l_state); + if (rc) { + pr_err("pre link clk on cb failed for type %d\n", + l_type); + goto error; + } + } + rc = dsi_display_link_clk_enable(l_clks, l_type, + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("failed to start link clk type %d rc=%d\n", + l_type, rc); + goto error; + } + + if (mngr->post_clkon_cb) { + rc = mngr->post_clkon_cb(mngr->priv_data, DSI_LINK_CLK, + l_type, l_state); + if (rc) { + pr_err("post link clk on cb failed for type %d\n", + l_type); + goto error; + } + } + } else { + if (mngr->pre_clkoff_cb) { + rc = mngr->pre_clkoff_cb(mngr->priv_data, + DSI_LINK_CLK, l_type, l_state); + if (rc) + pr_err("pre link clk off cb failed\n"); + } + + rc = dsi_display_link_clk_disable(l_clks, l_type, + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("failed to stop link clk type %d, rc = %d\n", + l_type, rc); + goto error; + } + + if (mngr->post_clkoff_cb) { + rc = mngr->post_clkoff_cb(mngr->priv_data, + DSI_LINK_CLK, l_type, l_state); + if (rc) + pr_err("post link clk off cb failed\n"); + } + } + +error: + return rc; +} + +static int dsi_update_clk_state(struct dsi_clk_mngr *mngr, + struct dsi_core_clks *c_clks, u32 c_state, + struct dsi_link_clks *l_clks, u32 l_state) +{ + int rc = 0; + bool l_c_on = false; + + 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_LINK_NONE, + 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_LINK_NONE, + 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) { + rc = dsi_clk_update_link_clk_state(mngr, l_clks, + DSI_LINK_LP_CLK, l_state, true); + if (rc) + goto error; + + rc = dsi_clk_update_link_clk_state(mngr, l_clks, + DSI_LINK_HS_CLK, l_state, true); + if (rc) + goto error; + } 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, + (DSI_LINK_LP_CLK & DSI_LINK_HS_CLK), + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("LP Link clks did not start\n"); + goto error; + } + l_c_on = true; + pr_debug("ECG: core and Link_on\n"); + } + + rc = dsi_clk_update_link_clk_state(mngr, l_clks, + DSI_LINK_HS_CLK, l_state, false); + if (rc) + goto error; + + rc = dsi_clk_update_link_clk_state(mngr, l_clks, + DSI_LINK_LP_CLK, l_state, false); + if (rc) + goto error; + + /* + * 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, + DSI_LINK_NONE, + 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_LINK_NONE, + 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(mngr, 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\n", + 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\n", + 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); + +static int dsi_display_link_clk_force_update(void *client) +{ + int rc = 0; + struct dsi_clk_client_info *c = client; + struct dsi_clk_mngr *mngr; + struct dsi_link_clks *l_clks; + + mngr = c->mngr; + mutex_lock(&mngr->clk_mutex); + + l_clks = mngr->link_clks; + + /* + * When link_clk_state is DSI_CLK_OFF, don't change DSI clock rate + * since it is possible to be overwritten, and return -EAGAIN to + * dynamic DSI writing interface to defer the reenabling to the next + * drm commit. + */ + if (mngr->link_clk_state == DSI_CLK_OFF) { + rc = -EAGAIN; + goto error; + } + + rc = dsi_display_link_clk_disable(l_clks, + (DSI_LINK_LP_CLK | DSI_LINK_HS_CLK), + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("%s, failed to stop link clk, rc = %d\n", + __func__, rc); + goto error; + } + + rc = dsi_display_link_clk_enable(l_clks, + (DSI_LINK_LP_CLK | DSI_LINK_HS_CLK), + mngr->dsi_ctrl_count, mngr->master_ndx); + if (rc) { + pr_err("%s, failed to start link clk rc= %d\n", + __func__, rc); + goto error; + } + +error: + mutex_unlock(&mngr->clk_mutex); + return rc; + +} + +int dsi_display_link_clk_force_update_ctrl(void *handle) +{ + int rc = 0; + + if (!handle) { + pr_err("%s: Invalid arg\n", __func__); + return -EINVAL; + } + + mutex_lock(&dsi_mngr_clk_mutex); + + rc = dsi_display_link_clk_force_update(handle); + + mutex_unlock(&dsi_mngr_clk_mutex); + + return rc; +} + +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_update_splash_status(void *clk_mgr, bool status) +{ + struct dsi_clk_mngr *mngr; + + if (!clk_mgr) { + pr_err("Invalid params\n"); + return; + } + + mngr = (struct dsi_clk_mngr *)clk_mgr; + mngr->is_cont_splash_enabled = status; +} + +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].hs_clks, &info->l_hs_clks[i], + sizeof(struct dsi_link_hs_clk_info)); + memcpy(&mngr->link_clks[i].lp_clks, &info->l_lp_clks[i], + sizeof(struct dsi_link_lp_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..a47ca946fff2e7cc4b8faad5ea1187de37d84e2c --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c @@ -0,0 +1,3582 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "dsi-ctrl:[%s] " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include