Merge drm/drm-next into drm-intel-gt-next
Maarten requested a backmerge due his work depending on subtle semantic changes introduced by:7e2e69ed46
("drm/i915: Fix i915_request fence wait semantics")2cbb8d4d67
("drm/i915: use new iterator in i915_gem_object_wait_reservation") Both should probably have been merged to drm-intel-gt-next anyway. Signed-off-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com> Acked-by: Daniel Vetter <daniel.vetter@ffwll.ch>
This commit is contained in:
commit
16d69a8919
|
@ -43,14 +43,70 @@ properties:
|
|||
vdd33-supply:
|
||||
description: Regulator that provides the supply 3.3V power.
|
||||
|
||||
analogix,lane0-swing:
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
minItems: 1
|
||||
maxItems: 20
|
||||
description:
|
||||
an array of swing register setting for DP tx lane0 PHY.
|
||||
Registers 0~9 are Swing0_Pre0, Swing1_Pre0, Swing2_Pre0,
|
||||
Swing3_Pre0, Swing0_Pre1, Swing1_Pre1, Swing2_Pre1, Swing0_Pre2,
|
||||
Swing1_Pre2, Swing0_Pre3, they are for [Boost control] and
|
||||
[Swing control] setting.
|
||||
Registers 0~9, bit 3:0 is [Boost control], these bits control
|
||||
post cursor manual, increase the [Boost control] to increase
|
||||
Pre-emphasis value.
|
||||
Registers 0~9, bit 6:4 is [Swing control], these bits control
|
||||
swing manual, increase [Swing control] setting to add Vp-p value
|
||||
for each Swing, Pre.
|
||||
Registers 10~19 are Swing0_Pre0, Swing1_Pre0, Swing2_Pre0,
|
||||
Swing3_Pre0, Swing0_Pre1, Swing1_Pre1, Swing2_Pre1, Swing0_Pre2,
|
||||
Swing1_Pre2, Swing0_Pre3, they are for [R select control] and
|
||||
[R Termination control] setting.
|
||||
Registers 10~19, bit 4:0 is [R select control], these bits are
|
||||
compensation manual, increase it can enhance IO driven strength
|
||||
and Vp-p.
|
||||
Registers 10~19, bit 5:6 is [R termination control], these bits
|
||||
adjust 50ohm impedance of DP tx termination. 00:55 ohm,
|
||||
01:50 ohm(default), 10:45 ohm, 11:40 ohm.
|
||||
|
||||
analogix,lane1-swing:
|
||||
$ref: /schemas/types.yaml#/definitions/uint8-array
|
||||
minItems: 1
|
||||
maxItems: 20
|
||||
description:
|
||||
an array of swing register setting for DP tx lane1 PHY.
|
||||
DP TX lane1 swing register setting same with lane0
|
||||
swing, please refer lane0-swing property description.
|
||||
|
||||
analogix,audio-enable:
|
||||
type: boolean
|
||||
description: let the driver enable audio HDMI codec function or not.
|
||||
|
||||
ports:
|
||||
$ref: /schemas/graph.yaml#/properties/ports
|
||||
|
||||
properties:
|
||||
port@0:
|
||||
$ref: /schemas/graph.yaml#/properties/port
|
||||
$ref: /schemas/graph.yaml#/$defs/port-base
|
||||
unevaluatedProperties: false
|
||||
description:
|
||||
Video port for MIPI DSI input.
|
||||
MIPI DSI/DPI input.
|
||||
|
||||
properties:
|
||||
endpoint:
|
||||
$ref: /schemas/media/video-interfaces.yaml#
|
||||
type: object
|
||||
additionalProperties: false
|
||||
|
||||
properties:
|
||||
remote-endpoint: true
|
||||
|
||||
bus-type:
|
||||
enum: [1, 5]
|
||||
default: 1
|
||||
|
||||
data-lanes: true
|
||||
|
||||
port@1:
|
||||
$ref: /schemas/graph.yaml#/properties/port
|
||||
|
@ -87,6 +143,9 @@ examples:
|
|||
vdd10-supply = <&pp1000_mipibrdg>;
|
||||
vdd18-supply = <&pp1800_mipibrdg>;
|
||||
vdd33-supply = <&pp3300_mipibrdg>;
|
||||
analogix,audio-enable;
|
||||
analogix,lane0-swing = /bits/ 8 <0x14 0x54 0x64 0x74>;
|
||||
analogix,lane1-swing = /bits/ 8 <0x14 0x54 0x64 0x74>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
|
@ -96,6 +155,8 @@ examples:
|
|||
reg = <0>;
|
||||
anx7625_in: endpoint {
|
||||
remote-endpoint = <&mipi_dsi>;
|
||||
bus-type = <5>;
|
||||
data-lanes = <0 1 2 3>;
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/bridge/nxp,ptn3460.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: NXP PTN3460 eDP to LVDS bridge
|
||||
|
||||
maintainers:
|
||||
- Sean Paul <seanpaul@chromium.org>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: nxp,ptn3460
|
||||
|
||||
reg:
|
||||
description: I2C address of the bridge
|
||||
maxItems: 1
|
||||
|
||||
edid-emulation:
|
||||
$ref: "/schemas/types.yaml#/definitions/uint32"
|
||||
description:
|
||||
The EDID emulation entry to use
|
||||
Value Resolution Description
|
||||
0 1024x768 NXP Generic
|
||||
1 1920x1080 NXP Generic
|
||||
2 1920x1080 NXP Generic
|
||||
3 1600x900 Samsung LTM200KT
|
||||
4 1920x1080 Samsung LTM230HT
|
||||
5 1366x768 NXP Generic
|
||||
6 1600x900 ChiMei M215HGE
|
||||
enum: [0, 1, 2, 3, 4, 5, 6]
|
||||
|
||||
powerdown-gpios:
|
||||
description: GPIO connected to the PD_N signal.
|
||||
maxItems: 1
|
||||
|
||||
reset-gpios:
|
||||
description: GPIO connected to the RST_N signal.
|
||||
maxItems: 1
|
||||
|
||||
ports:
|
||||
$ref: /schemas/graph.yaml#/properties/ports
|
||||
|
||||
properties:
|
||||
port@0:
|
||||
$ref: /schemas/graph.yaml#/properties/port
|
||||
description:
|
||||
Video port for LVDS output
|
||||
|
||||
port@1:
|
||||
$ref: /schemas/graph.yaml#/properties/port
|
||||
description:
|
||||
Video port for eDP input
|
||||
|
||||
required:
|
||||
- port@0
|
||||
- port@1
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- edid-emulation
|
||||
- powerdown-gpios
|
||||
- reset-gpios
|
||||
- ports
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
i2c1 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
bridge@20 {
|
||||
compatible = "nxp,ptn3460";
|
||||
reg = <0x20>;
|
||||
edid-emulation = <5>;
|
||||
powerdown-gpios = <&gpy2 5 GPIO_ACTIVE_HIGH>;
|
||||
reset-gpios = <&gpx1 5 GPIO_ACTIVE_LOW>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
bridge_out: endpoint {
|
||||
remote-endpoint = <&panel_in>;
|
||||
};
|
||||
};
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
bridge_in: endpoint {
|
||||
remote-endpoint = <&dp_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
|
@ -1,39 +0,0 @@
|
|||
ptn3460 bridge bindings
|
||||
|
||||
Required properties:
|
||||
- compatible: "nxp,ptn3460"
|
||||
- reg: i2c address of the bridge
|
||||
- powerdown-gpio: OF device-tree gpio specification for PD_N pin.
|
||||
- reset-gpio: OF device-tree gpio specification for RST_N pin.
|
||||
- edid-emulation: The EDID emulation entry to use
|
||||
+-------+------------+------------------+
|
||||
| Value | Resolution | Description |
|
||||
| 0 | 1024x768 | NXP Generic |
|
||||
| 1 | 1920x1080 | NXP Generic |
|
||||
| 2 | 1920x1080 | NXP Generic |
|
||||
| 3 | 1600x900 | Samsung LTM200KT |
|
||||
| 4 | 1920x1080 | Samsung LTM230HT |
|
||||
| 5 | 1366x768 | NXP Generic |
|
||||
| 6 | 1600x900 | ChiMei M215HGE |
|
||||
+-------+------------+------------------+
|
||||
|
||||
- video interfaces: Device node can contain video interface port
|
||||
nodes for panel according to [1].
|
||||
|
||||
[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
|
||||
|
||||
Example:
|
||||
lvds-bridge@20 {
|
||||
compatible = "nxp,ptn3460";
|
||||
reg = <0x20>;
|
||||
powerdown-gpio = <&gpy2 5 1 0 0>;
|
||||
reset-gpio = <&gpx1 5 1 0 0>;
|
||||
edid-emulation = <5>;
|
||||
ports {
|
||||
port@0 {
|
||||
bridge_out: endpoint {
|
||||
remote-endpoint = <&panel_in>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -1,49 +0,0 @@
|
|||
Silicon Image SiI9234 HDMI/MHL bridge bindings
|
||||
|
||||
Required properties:
|
||||
- compatible : "sil,sii9234".
|
||||
- reg : I2C address for TPI interface, use 0x39
|
||||
- avcc33-supply : MHL/USB Switch Supply Voltage (3.3V)
|
||||
- iovcc18-supply : I/O Supply Voltage (1.8V)
|
||||
- avcc12-supply : TMDS Analog Supply Voltage (1.2V)
|
||||
- cvcc12-supply : Digital Core Supply Voltage (1.2V)
|
||||
- interrupts: interrupt specifier of INT pin
|
||||
- reset-gpios: gpio specifier of RESET pin (active low)
|
||||
- video interfaces: Device node can contain two video interface port
|
||||
nodes for HDMI encoder and connector according to [1].
|
||||
- port@0 - MHL to HDMI
|
||||
- port@1 - MHL to connector
|
||||
|
||||
[1]: Documentation/devicetree/bindings/media/video-interfaces.txt
|
||||
|
||||
|
||||
Example:
|
||||
sii9234@39 {
|
||||
compatible = "sil,sii9234";
|
||||
reg = <0x39>;
|
||||
avcc33-supply = <&vcc33mhl>;
|
||||
iovcc18-supply = <&vcc18mhl>;
|
||||
avcc12-supply = <&vsil12>;
|
||||
cvcc12-supply = <&vsil12>;
|
||||
reset-gpios = <&gpf3 4 GPIO_ACTIVE_LOW>;
|
||||
interrupt-parent = <&gpf3>;
|
||||
interrupts = <5 IRQ_TYPE_LEVEL_HIGH>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
mhl_to_hdmi: endpoint {
|
||||
remote-endpoint = <&hdmi_to_mhl>;
|
||||
};
|
||||
};
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
mhl_to_connector: endpoint {
|
||||
remote-endpoint = <&connector_to_mhl>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,110 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/bridge/sil,sii9234.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Silicon Image SiI9234 HDMI/MHL bridge
|
||||
|
||||
maintainers:
|
||||
- Maciej Purski <m.purski@samsung.com>
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: sil,sii9234
|
||||
|
||||
reg:
|
||||
description: I2C address for TPI interface
|
||||
maxItems: 1
|
||||
|
||||
avcc12-supply:
|
||||
description: TMDS Analog Supply Voltage, 1.2V
|
||||
|
||||
avcc33-supply:
|
||||
description: MHL/USB Switch Supply Voltage, 3.3V
|
||||
|
||||
cvcc12-supply:
|
||||
description: Digital Core Supply Voltage, 1.2V
|
||||
|
||||
iovcc18-supply:
|
||||
description: I/O voltage supply, 1.8V
|
||||
|
||||
interrupts:
|
||||
maxItems: 1
|
||||
|
||||
reset-gpios:
|
||||
description: GPIO connected to the reset pin.
|
||||
maxItems: 1
|
||||
|
||||
ports:
|
||||
$ref: /schemas/graph.yaml#/properties/ports
|
||||
|
||||
properties:
|
||||
port@0:
|
||||
$ref: /schemas/graph.yaml#/properties/port
|
||||
description:
|
||||
Video port for HDMI (encoder) input
|
||||
|
||||
port@1:
|
||||
$ref: /schemas/graph.yaml#/properties/port
|
||||
description:
|
||||
MHL to connector port
|
||||
|
||||
required:
|
||||
- port@0
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- avcc12-supply
|
||||
- avcc33-supply
|
||||
- cvcc12-supply
|
||||
- iovcc18-supply
|
||||
- interrupts
|
||||
- reset-gpios
|
||||
- ports
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
#include <dt-bindings/interrupt-controller/irq.h>
|
||||
|
||||
i2c1 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
bridge@39 {
|
||||
compatible = "sil,sii9234";
|
||||
reg = <0x39>;
|
||||
avcc12-supply = <&vsil12>;
|
||||
avcc33-supply = <&vcc33mhl>;
|
||||
cvcc12-supply = <&vsil12>;
|
||||
iovcc18-supply = <&vcc18mhl>;
|
||||
interrupt-parent = <&gpf3>;
|
||||
interrupts = <5 IRQ_TYPE_LEVEL_HIGH>;
|
||||
reset-gpios = <&gpf3 4 GPIO_ACTIVE_LOW>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
mhl_to_hdmi: endpoint {
|
||||
remote-endpoint = <&hdmi_to_mhl>;
|
||||
};
|
||||
};
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
mhl_to_connector: endpoint {
|
||||
remote-endpoint = <&connector_to_mhl>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
|
@ -0,0 +1,81 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/panel/boe,bf060y8m-aj0.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: BOE BF060Y8M-AJ0 5.99" 1080x2160 AMOLED Panel
|
||||
|
||||
maintainers:
|
||||
- AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
|
||||
|
||||
description: |
|
||||
This is a 5.99" 1080x2160 16.7M Color active matrix AMOLED
|
||||
video mode panel module on MIPI-DSI 4-Lane interface, GGRB
|
||||
pixel arrangement, 63 micrometers pitch, with an active
|
||||
area of 68.04 x 136.08 millimeters.
|
||||
Each pixel is divided into red and green dots, or blue and
|
||||
green dots, and two pixels share red or blue dots which are
|
||||
arranged in vertical stripe.
|
||||
The DriverIC for this panel module is SW43404.
|
||||
|
||||
allOf:
|
||||
- $ref: panel-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: boe,bf060y8m-aj0
|
||||
|
||||
elvdd-supply:
|
||||
description: EL Driving positive (VDD) supply (4.40-4.80V)
|
||||
elvss-supply:
|
||||
description: EL Driving negative (VSS) supply (-5.00V to -1.40V)
|
||||
vcc-supply:
|
||||
description: Core (TSP) voltage supply (2.70-3.60V)
|
||||
vci-supply:
|
||||
description: DriverIC Operation supply (2.60-3.60V)
|
||||
vddio-supply:
|
||||
description: I/O voltage supply (1.62-1.98V)
|
||||
|
||||
port: true
|
||||
reg: true
|
||||
reset-gpios: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- elvdd-supply
|
||||
- elvss-supply
|
||||
- vcc-supply
|
||||
- vci-supply
|
||||
- vddio-supply
|
||||
- reg
|
||||
- reset-gpios
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
dsi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
panel@0 {
|
||||
compatible = "boe,bf060y8m-aj0";
|
||||
reg = <0>;
|
||||
|
||||
reset-gpios = <&tlmm 94 GPIO_ACTIVE_HIGH>;
|
||||
|
||||
vcc-supply = <&disp_vcc_vreg>;
|
||||
vddio-supply = <&disp_vddio_vreg>;
|
||||
vci-supply = <&disp_vci_vreg>;
|
||||
elvdd-supply = <&disp_elvdd_vreg>;
|
||||
elvss-supply = <&disp_elvss_vreg>;
|
||||
|
||||
port {
|
||||
panel_in: endpoint {
|
||||
remote-endpoint = <&dsi0_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,69 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/panel/ilitek,ili9163.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Ilitek ILI9163 display panels device tree bindings
|
||||
|
||||
maintainers:
|
||||
- Daniel Mack <daniel@zonque.org>
|
||||
|
||||
description:
|
||||
This binding is for display panels using an Ilitek ILI9163 controller in SPI
|
||||
mode.
|
||||
|
||||
allOf:
|
||||
- $ref: panel-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- enum:
|
||||
- newhaven,1.8-128160EF
|
||||
- const: ilitek,ili9163
|
||||
|
||||
spi-max-frequency:
|
||||
maximum: 32000000
|
||||
|
||||
dc-gpios:
|
||||
maxItems: 1
|
||||
description: Display data/command selection (D/CX)
|
||||
|
||||
backlight: true
|
||||
reg: true
|
||||
reset-gpios: true
|
||||
rotation: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- dc-gpios
|
||||
- reset-gpios
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
backlight: backlight {
|
||||
compatible = "gpio-backlight";
|
||||
gpios = <&gpio 22 GPIO_ACTIVE_HIGH>;
|
||||
};
|
||||
spi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
display@0 {
|
||||
compatible = "newhaven,1.8-128160EF", "ilitek,ili9163";
|
||||
reg = <0>;
|
||||
spi-max-frequency = <32000000>;
|
||||
dc-gpios = <&gpio0 24 GPIO_ACTIVE_HIGH>;
|
||||
reset-gpios = <&gpio0 25 GPIO_ACTIVE_HIGH>;
|
||||
rotation = <180>;
|
||||
backlight = <&backlight>;
|
||||
};
|
||||
};
|
||||
|
||||
...
|
|
@ -9,24 +9,28 @@ title: Ilitek ILI9881c based MIPI-DSI panels
|
|||
maintainers:
|
||||
- Maxime Ripard <mripard@kernel.org>
|
||||
|
||||
allOf:
|
||||
- $ref: panel-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- enum:
|
||||
- bananapi,lhr050h41
|
||||
- feixin,k101-im2byl02
|
||||
- wanchanglong,w552946aba
|
||||
- const: ilitek,ili9881c
|
||||
|
||||
backlight: true
|
||||
power-supply: true
|
||||
reg: true
|
||||
reset-gpios: true
|
||||
rotation: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- power-supply
|
||||
- reg
|
||||
- reset-gpios
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
|
|
|
@ -0,0 +1,106 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/panel/novatek,nt35950.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Novatek NT35950-based display panels
|
||||
|
||||
maintainers:
|
||||
- AngeloGioacchino Del Regno <angelogioacchino.delregno@somainline.org>
|
||||
|
||||
description: |
|
||||
The nt35950 IC from Novatek is a Driver IC used to drive MIPI-DSI panels,
|
||||
with Static RAM for content retention in command mode and also supports
|
||||
video mode with VESA Frame Buffer Compression or Display Stream Compression
|
||||
on single, or dual dsi port(s).
|
||||
This DDIC is also capable of upscaling an input image to the panel's native
|
||||
resolution, for example it can upscale a 1920x1080 input to 3840x2160 with
|
||||
either bilinear interpolation or pixel duplication.
|
||||
|
||||
allOf:
|
||||
- $ref: panel-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- enum:
|
||||
- sharp,ls055d1sx04
|
||||
- const: novatek,nt35950
|
||||
description: This indicates the panel manufacturer of the panel
|
||||
that is in turn using the NT35950 panel driver. The compatible
|
||||
string determines how the NT35950 panel driver shall be configured
|
||||
to work with the indicated panel. The novatek,nt35950 compatible shall
|
||||
always be provided as a fallback.
|
||||
|
||||
reset-gpios:
|
||||
maxItems: 1
|
||||
description: phandle of gpio for reset line - This should be 8mA, gpio
|
||||
can be configured using mux, pinctrl, pinctrl-names (active high)
|
||||
|
||||
avdd-supply:
|
||||
description: positive boost supply regulator
|
||||
avee-supply:
|
||||
description: negative boost supply regulator
|
||||
dvdd-supply:
|
||||
description: regulator that supplies the digital voltage
|
||||
vddio-supply:
|
||||
description: regulator that supplies the I/O voltage
|
||||
|
||||
backlight: true
|
||||
ports: true
|
||||
reg: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- reset-gpios
|
||||
- avdd-supply
|
||||
- avee-supply
|
||||
- dvdd-supply
|
||||
- vddio-supply
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
dsi0 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
panel@0 {
|
||||
compatible = "sharp,ls055d1sx04", "novatek,nt35950";
|
||||
reg = <0>;
|
||||
|
||||
backlight = <&pmi8998_wled>;
|
||||
reset-gpios = <&tlmm 94 GPIO_ACTIVE_HIGH>;
|
||||
|
||||
avdd-supply = <&lab>;
|
||||
avee-supply = <&ibb>;
|
||||
dvdd-supply = <&disp_dvdd_vreg>;
|
||||
vddio-supply = <&vreg_l14a_1p85>;
|
||||
|
||||
ports {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
port@0 {
|
||||
reg = <0>;
|
||||
panel_in0: endpoint {
|
||||
remote-endpoint = <&dsi0_out>;
|
||||
};
|
||||
};
|
||||
|
||||
port@1 {
|
||||
reg = <1>;
|
||||
panel_in1: endpoint {
|
||||
remote-endpoint = <&dsi1_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
|
@ -35,6 +35,8 @@ properties:
|
|||
- boe,tv080wum-nl0
|
||||
# Innolux P079ZCA 7.85" 768x1024 TFT LCD panel
|
||||
- innolux,p079zca
|
||||
# JDI FHD_R63452 1080x1920 5.2" IPS LCD Panel
|
||||
- jdi,fhd-r63452
|
||||
# Khadas TS050 5" 1080x1920 LCD panel
|
||||
- khadas,ts050
|
||||
# Kingdisplay KD097D04 9.7" 1536x2048 TFT LCD panel
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/panel/sony,tulip-truly-nt35521.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Sony Tulip Truly NT35521 5.24" 1280x720 MIPI-DSI Panel
|
||||
|
||||
maintainers:
|
||||
- Shawn Guo <shawn.guo@linaro.org>
|
||||
|
||||
description: |
|
||||
The Sony Tulip Truly NT35521 is a 5.24" 1280x720 MIPI-DSI panel, which
|
||||
can be found no Sony Xperia M4 phone. The panel backlight is managed
|
||||
through DSI link.
|
||||
|
||||
allOf:
|
||||
- $ref: panel-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
const: sony,tulip-truly-nt35521
|
||||
|
||||
reg: true
|
||||
|
||||
positive5-supply:
|
||||
description: Positive 5V supply
|
||||
|
||||
negative5-supply:
|
||||
description: Negative 5V supply
|
||||
|
||||
reset-gpios: true
|
||||
|
||||
enable-gpios: true
|
||||
|
||||
port: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- positive5-supply
|
||||
- negative5-supply
|
||||
- reset-gpios
|
||||
- enable-gpios
|
||||
- port
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
dsi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
panel@0 {
|
||||
compatible = "sony,tulip-truly-nt35521";
|
||||
reg = <0>;
|
||||
positive5-supply = <&positive5_reg>;
|
||||
negative5-supply = <&negative5_reg>;
|
||||
reset-gpios = <&msmgpio 25 GPIO_ACTIVE_LOW>;
|
||||
enable-gpios = <&msmgpio 10 GPIO_ACTIVE_HIGH>;
|
||||
|
||||
port {
|
||||
panel_in: endpoint {
|
||||
remote-endpoint = <&dsi0_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
|
@ -1328,6 +1328,8 @@ patternProperties:
|
|||
description: Wondermedia Technologies, Inc.
|
||||
"^wobo,.*":
|
||||
description: Wobo
|
||||
"^wanchanglong,.*":
|
||||
description: Wanchanglong Electronics Technology(SHENZHEN)Co.,Ltd.
|
||||
"^x-powers,.*":
|
||||
description: X-Powers
|
||||
"^xes,.*":
|
||||
|
|
|
@ -435,3 +435,18 @@ Legacy CRTC/Modeset Helper Functions Reference
|
|||
|
||||
.. kernel-doc:: drivers/gpu/drm/drm_crtc_helper.c
|
||||
:export:
|
||||
|
||||
Privacy-screen class
|
||||
====================
|
||||
|
||||
.. kernel-doc:: drivers/gpu/drm/drm_privacy_screen.c
|
||||
:doc: overview
|
||||
|
||||
.. kernel-doc:: include/drm/drm_privacy_screen_driver.h
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: include/drm/drm_privacy_screen_machine.h
|
||||
:internal:
|
||||
|
||||
.. kernel-doc:: drivers/gpu/drm/drm_privacy_screen.c
|
||||
:export:
|
||||
|
|
|
@ -506,6 +506,8 @@ Property Types and Blob Property Support
|
|||
.. kernel-doc:: drivers/gpu/drm/drm_property.c
|
||||
:export:
|
||||
|
||||
.. _standard_connector_properties:
|
||||
|
||||
Standard Connector Properties
|
||||
-----------------------------
|
||||
|
||||
|
|
|
@ -268,17 +268,6 @@ Contact: Daniel Vetter
|
|||
|
||||
Level: Intermediate
|
||||
|
||||
Clean up mmap forwarding
|
||||
------------------------
|
||||
|
||||
A lot of drivers forward gem mmap calls to dma-buf mmap for imported buffers.
|
||||
And also a lot of them forward dma-buf mmap to the gem mmap implementations.
|
||||
There's drm_gem_prime_mmap() for this now, but still needs to be rolled out.
|
||||
|
||||
Contact: Daniel Vetter
|
||||
|
||||
Level: Intermediate
|
||||
|
||||
Generic fbdev defio support
|
||||
---------------------------
|
||||
|
||||
|
@ -463,6 +452,21 @@ Contact: Thomas Zimmermann <tzimmermann@suse.de>, Christian König, Daniel Vette
|
|||
|
||||
Level: Intermediate
|
||||
|
||||
Review all drivers for setting struct drm_mode_config.{max_width,max_height} correctly
|
||||
--------------------------------------------------------------------------------------
|
||||
|
||||
The values in struct drm_mode_config.{max_width,max_height} describe the
|
||||
maximum supported framebuffer size. It's the virtual screen size, but many
|
||||
drivers treat it like limitations of the physical resolution.
|
||||
|
||||
The maximum width depends on the hardware's maximum scanline pitch. The
|
||||
maximum height depends on the amount of addressable video memory. Review all
|
||||
drivers to initialize the fields to the correct values.
|
||||
|
||||
Contact: Thomas Zimmermann <tzimmermann@suse.de>
|
||||
|
||||
Level: Intermediate
|
||||
|
||||
|
||||
Core refactorings
|
||||
=================
|
||||
|
|
19
MAINTAINERS
19
MAINTAINERS
|
@ -6063,10 +6063,17 @@ F: drivers/gpu/drm/panel/panel-novatek-nt36672a.c
|
|||
|
||||
DRM DRIVER FOR NVIDIA GEFORCE/QUADRO GPUS
|
||||
M: Ben Skeggs <bskeggs@redhat.com>
|
||||
M: Karol Herbst <kherbst@redhat.com>
|
||||
M: Lyude Paul <lyude@redhat.com>
|
||||
L: dri-devel@lists.freedesktop.org
|
||||
L: nouveau@lists.freedesktop.org
|
||||
S: Supported
|
||||
T: git git://github.com/skeggsb/linux
|
||||
W: https://nouveau.freedesktop.org/
|
||||
Q: https://patchwork.freedesktop.org/project/nouveau/
|
||||
Q: https://gitlab.freedesktop.org/drm/nouveau/-/merge_requests
|
||||
B: https://gitlab.freedesktop.org/drm/nouveau/-/issues
|
||||
C: irc://irc.oftc.net/nouveau
|
||||
T: git https://gitlab.freedesktop.org/drm/nouveau.git
|
||||
F: drivers/gpu/drm/nouveau/
|
||||
F: include/uapi/drm/nouveau_drm.h
|
||||
|
||||
|
@ -6524,6 +6531,14 @@ F: drivers/gpu/drm/drm_panel.c
|
|||
F: drivers/gpu/drm/panel/
|
||||
F: include/drm/drm_panel.h
|
||||
|
||||
DRM PRIVACY-SCREEN CLASS
|
||||
M: Hans de Goede <hdegoede@redhat.com>
|
||||
L: dri-devel@lists.freedesktop.org
|
||||
S: Maintained
|
||||
T: git git://anongit.freedesktop.org/drm/drm-misc
|
||||
F: drivers/gpu/drm/drm_privacy_screen*
|
||||
F: include/drm/drm_privacy_screen*
|
||||
|
||||
DRM TTM SUBSYSTEM
|
||||
M: Christian Koenig <christian.koenig@amd.com>
|
||||
M: Huang Rui <ray.huang@amd.com>
|
||||
|
@ -20168,6 +20183,8 @@ F: include/uapi/linux/virtio_gpio.h
|
|||
VIRTIO GPU DRIVER
|
||||
M: David Airlie <airlied@linux.ie>
|
||||
M: Gerd Hoffmann <kraxel@redhat.com>
|
||||
R: Gurchetan Singh <gurchetansingh@chromium.org>
|
||||
R: Chia-I Wu <olvaffe@gmail.com>
|
||||
L: dri-devel@lists.freedesktop.org
|
||||
L: virtualization@lists.linux-foundation.org
|
||||
S: Maintained
|
||||
|
|
|
@ -932,8 +932,7 @@ static int bcm2835_clock_is_on(struct clk_hw *hw)
|
|||
|
||||
static u32 bcm2835_clock_choose_div(struct clk_hw *hw,
|
||||
unsigned long rate,
|
||||
unsigned long parent_rate,
|
||||
bool round_up)
|
||||
unsigned long parent_rate)
|
||||
{
|
||||
struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw);
|
||||
const struct bcm2835_clock_data *data = clock->data;
|
||||
|
@ -945,10 +944,6 @@ static u32 bcm2835_clock_choose_div(struct clk_hw *hw,
|
|||
|
||||
rem = do_div(temp, rate);
|
||||
div = temp;
|
||||
|
||||
/* Round up and mask off the unused bits */
|
||||
if (round_up && ((div & unused_frac_mask) != 0 || rem != 0))
|
||||
div += unused_frac_mask + 1;
|
||||
div &= ~unused_frac_mask;
|
||||
|
||||
/* different clamping limits apply for a mash clock */
|
||||
|
@ -1079,7 +1074,7 @@ static int bcm2835_clock_set_rate(struct clk_hw *hw,
|
|||
struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw);
|
||||
struct bcm2835_cprman *cprman = clock->cprman;
|
||||
const struct bcm2835_clock_data *data = clock->data;
|
||||
u32 div = bcm2835_clock_choose_div(hw, rate, parent_rate, false);
|
||||
u32 div = bcm2835_clock_choose_div(hw, rate, parent_rate);
|
||||
u32 ctl;
|
||||
|
||||
spin_lock(&cprman->regs_lock);
|
||||
|
@ -1130,7 +1125,7 @@ static unsigned long bcm2835_clock_choose_div_and_prate(struct clk_hw *hw,
|
|||
|
||||
if (!(BIT(parent_idx) & data->set_rate_parent)) {
|
||||
*prate = clk_hw_get_rate(parent);
|
||||
*div = bcm2835_clock_choose_div(hw, rate, *prate, true);
|
||||
*div = bcm2835_clock_choose_div(hw, rate, *prate);
|
||||
|
||||
*avgrate = bcm2835_clock_rate_from_divisor(clock, *prate, *div);
|
||||
|
||||
|
@ -1216,7 +1211,7 @@ static int bcm2835_clock_determine_rate(struct clk_hw *hw,
|
|||
rate = bcm2835_clock_choose_div_and_prate(hw, i, req->rate,
|
||||
&div, &prate,
|
||||
&avgrate);
|
||||
if (rate > best_rate && rate <= req->rate) {
|
||||
if (abs(req->rate - rate) < abs(req->rate - best_rate)) {
|
||||
best_parent = parent;
|
||||
best_prate = prate;
|
||||
best_rate = rate;
|
||||
|
|
|
@ -11,6 +11,7 @@ obj-$(CONFIG_DMABUF_SYSFS_STATS) += dma-buf-sysfs-stats.o
|
|||
dmabuf_selftests-y := \
|
||||
selftest.o \
|
||||
st-dma-fence.o \
|
||||
st-dma-fence-chain.o
|
||||
st-dma-fence-chain.o \
|
||||
st-dma-resv.o
|
||||
|
||||
obj-$(CONFIG_DMABUF_SELFTESTS) += dmabuf_selftests.o
|
||||
|
|
|
@ -299,10 +299,8 @@ static __poll_t dma_buf_poll(struct file *file, poll_table *poll)
|
|||
|
||||
/**
|
||||
* dma_buf_set_name - Set a name to a specific dma_buf to track the usage.
|
||||
* The name of the dma-buf buffer can only be set when the dma-buf is not
|
||||
* attached to any devices. It could theoritically support changing the
|
||||
* name of the dma-buf if the same piece of memory is used for multiple
|
||||
* purpose between different devices.
|
||||
* It could support changing the name of the dma-buf if the same
|
||||
* piece of memory is used for multiple purpose between different devices.
|
||||
*
|
||||
* @dmabuf: [in] dmabuf buffer that will be renamed.
|
||||
* @buf: [in] A piece of userspace memory that contains the name of
|
||||
|
@ -315,25 +313,16 @@ static __poll_t dma_buf_poll(struct file *file, poll_table *poll)
|
|||
static long dma_buf_set_name(struct dma_buf *dmabuf, const char __user *buf)
|
||||
{
|
||||
char *name = strndup_user(buf, DMA_BUF_NAME_LEN);
|
||||
long ret = 0;
|
||||
|
||||
if (IS_ERR(name))
|
||||
return PTR_ERR(name);
|
||||
|
||||
dma_resv_lock(dmabuf->resv, NULL);
|
||||
if (!list_empty(&dmabuf->attachments)) {
|
||||
ret = -EBUSY;
|
||||
kfree(name);
|
||||
goto out_unlock;
|
||||
}
|
||||
spin_lock(&dmabuf->name_lock);
|
||||
kfree(dmabuf->name);
|
||||
dmabuf->name = name;
|
||||
spin_unlock(&dmabuf->name_lock);
|
||||
|
||||
out_unlock:
|
||||
dma_resv_unlock(dmabuf->resv);
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static long dma_buf_ioctl(struct file *file,
|
||||
|
@ -1058,8 +1047,8 @@ EXPORT_SYMBOL_NS_GPL(dma_buf_move_notify, DMA_BUF);
|
|||
*
|
||||
* Interfaces::
|
||||
*
|
||||
* void \*dma_buf_vmap(struct dma_buf \*dmabuf)
|
||||
* void dma_buf_vunmap(struct dma_buf \*dmabuf, void \*vaddr)
|
||||
* void \*dma_buf_vmap(struct dma_buf \*dmabuf, struct dma_buf_map \*map)
|
||||
* void dma_buf_vunmap(struct dma_buf \*dmabuf, struct dma_buf_map \*map)
|
||||
*
|
||||
* The vmap call can fail if there is no vmap support in the exporter, or if
|
||||
* it runs out of vmalloc space. Note that the dma-buf layer keeps a reference
|
||||
|
@ -1338,8 +1327,6 @@ static int dma_buf_debug_show(struct seq_file *s, void *unused)
|
|||
{
|
||||
struct dma_buf *buf_obj;
|
||||
struct dma_buf_attachment *attach_obj;
|
||||
struct dma_resv_iter cursor;
|
||||
struct dma_fence *fence;
|
||||
int count = 0, attach_count;
|
||||
size_t size = 0;
|
||||
int ret;
|
||||
|
@ -1370,14 +1357,7 @@ static int dma_buf_debug_show(struct seq_file *s, void *unused)
|
|||
buf_obj->name ?: "");
|
||||
spin_unlock(&buf_obj->name_lock);
|
||||
|
||||
dma_resv_for_each_fence(&cursor, buf_obj->resv, true, fence) {
|
||||
seq_printf(s, "\t%s fence: %s %s %ssignalled\n",
|
||||
dma_resv_iter_is_exclusive(&cursor) ?
|
||||
"Exclusive" : "Shared",
|
||||
fence->ops->get_driver_name(fence),
|
||||
fence->ops->get_timeline_name(fence),
|
||||
dma_fence_is_signaled(fence) ? "" : "un");
|
||||
}
|
||||
dma_resv_describe(buf_obj->resv, s);
|
||||
|
||||
seq_puts(s, "\tAttached Devices:\n");
|
||||
attach_count = 0;
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/atomic.h>
|
||||
#include <linux/dma-fence.h>
|
||||
#include <linux/sched/signal.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
#define CREATE_TRACE_POINTS
|
||||
#include <trace/events/dma_fence.h>
|
||||
|
@ -907,6 +908,22 @@ err_free_cb:
|
|||
}
|
||||
EXPORT_SYMBOL(dma_fence_wait_any_timeout);
|
||||
|
||||
/**
|
||||
* dma_fence_describe - Dump fence describtion into seq_file
|
||||
* @fence: the 6fence to describe
|
||||
* @seq: the seq_file to put the textual description into
|
||||
*
|
||||
* Dump a textual description of the fence and it's state into the seq_file.
|
||||
*/
|
||||
void dma_fence_describe(struct dma_fence *fence, struct seq_file *seq)
|
||||
{
|
||||
seq_printf(seq, "%s %s seq %llu %ssignalled\n",
|
||||
fence->ops->get_driver_name(fence),
|
||||
fence->ops->get_timeline_name(fence), fence->seqno,
|
||||
dma_fence_is_signaled(fence) ? "" : "un");
|
||||
}
|
||||
EXPORT_SYMBOL(dma_fence_describe);
|
||||
|
||||
/**
|
||||
* dma_fence_init - Initialize a custom fence.
|
||||
* @fence: the fence to initialize
|
||||
|
|
|
@ -38,6 +38,7 @@
|
|||
#include <linux/mm.h>
|
||||
#include <linux/sched/mm.h>
|
||||
#include <linux/mmu_notifier.h>
|
||||
#include <linux/seq_file.h>
|
||||
|
||||
/**
|
||||
* DOC: Reservation Object Overview
|
||||
|
@ -666,6 +667,28 @@ bool dma_resv_test_signaled(struct dma_resv *obj, bool test_all)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(dma_resv_test_signaled);
|
||||
|
||||
/**
|
||||
* dma_resv_describe - Dump description of the resv object into seq_file
|
||||
* @obj: the reservation object
|
||||
* @seq: the seq_file to dump the description into
|
||||
*
|
||||
* Dump a textual description of the fences inside an dma_resv object into the
|
||||
* seq_file.
|
||||
*/
|
||||
void dma_resv_describe(struct dma_resv *obj, struct seq_file *seq)
|
||||
{
|
||||
struct dma_resv_iter cursor;
|
||||
struct dma_fence *fence;
|
||||
|
||||
dma_resv_for_each_fence(&cursor, obj, true, fence) {
|
||||
seq_printf(seq, "\t%s fence:",
|
||||
dma_resv_iter_is_exclusive(&cursor) ?
|
||||
"Exclusive" : "Shared");
|
||||
dma_fence_describe(fence, seq);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dma_resv_describe);
|
||||
|
||||
#if IS_ENABLED(CONFIG_LOCKDEP)
|
||||
static int __init dma_resv_lockdep(void)
|
||||
{
|
||||
|
|
|
@ -12,3 +12,4 @@
|
|||
selftest(sanitycheck, __sanitycheck__) /* keep first (igt selfcheck) */
|
||||
selftest(dma_fence, dma_fence)
|
||||
selftest(dma_fence_chain, dma_fence_chain)
|
||||
selftest(dma_resv, dma_resv)
|
||||
|
|
|
@ -0,0 +1,371 @@
|
|||
/* SPDX-License-Identifier: MIT */
|
||||
|
||||
/*
|
||||
* Copyright © 2019 Intel Corporation
|
||||
* Copyright © 2021 Advanced Micro Devices, Inc.
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/dma-resv.h>
|
||||
|
||||
#include "selftest.h"
|
||||
|
||||
static struct spinlock fence_lock;
|
||||
|
||||
static const char *fence_name(struct dma_fence *f)
|
||||
{
|
||||
return "selftest";
|
||||
}
|
||||
|
||||
static const struct dma_fence_ops fence_ops = {
|
||||
.get_driver_name = fence_name,
|
||||
.get_timeline_name = fence_name,
|
||||
};
|
||||
|
||||
static struct dma_fence *alloc_fence(void)
|
||||
{
|
||||
struct dma_fence *f;
|
||||
|
||||
f = kmalloc(sizeof(*f), GFP_KERNEL);
|
||||
if (!f)
|
||||
return NULL;
|
||||
|
||||
dma_fence_init(f, &fence_ops, &fence_lock, 0, 0);
|
||||
return f;
|
||||
}
|
||||
|
||||
static int sanitycheck(void *arg)
|
||||
{
|
||||
struct dma_resv resv;
|
||||
struct dma_fence *f;
|
||||
int r;
|
||||
|
||||
f = alloc_fence();
|
||||
if (!f)
|
||||
return -ENOMEM;
|
||||
|
||||
dma_fence_signal(f);
|
||||
dma_fence_put(f);
|
||||
|
||||
dma_resv_init(&resv);
|
||||
r = dma_resv_lock(&resv, NULL);
|
||||
if (r)
|
||||
pr_err("Resv locking failed\n");
|
||||
else
|
||||
dma_resv_unlock(&resv);
|
||||
dma_resv_fini(&resv);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int test_signaling(void *arg, bool shared)
|
||||
{
|
||||
struct dma_resv resv;
|
||||
struct dma_fence *f;
|
||||
int r;
|
||||
|
||||
f = alloc_fence();
|
||||
if (!f)
|
||||
return -ENOMEM;
|
||||
|
||||
dma_resv_init(&resv);
|
||||
r = dma_resv_lock(&resv, NULL);
|
||||
if (r) {
|
||||
pr_err("Resv locking failed\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
if (shared) {
|
||||
r = dma_resv_reserve_shared(&resv, 1);
|
||||
if (r) {
|
||||
pr_err("Resv shared slot allocation failed\n");
|
||||
goto err_unlock;
|
||||
}
|
||||
|
||||
dma_resv_add_shared_fence(&resv, f);
|
||||
} else {
|
||||
dma_resv_add_excl_fence(&resv, f);
|
||||
}
|
||||
|
||||
if (dma_resv_test_signaled(&resv, shared)) {
|
||||
pr_err("Resv unexpectedly signaled\n");
|
||||
r = -EINVAL;
|
||||
goto err_unlock;
|
||||
}
|
||||
dma_fence_signal(f);
|
||||
if (!dma_resv_test_signaled(&resv, shared)) {
|
||||
pr_err("Resv not reporting signaled\n");
|
||||
r = -EINVAL;
|
||||
goto err_unlock;
|
||||
}
|
||||
err_unlock:
|
||||
dma_resv_unlock(&resv);
|
||||
err_free:
|
||||
dma_resv_fini(&resv);
|
||||
dma_fence_put(f);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int test_excl_signaling(void *arg)
|
||||
{
|
||||
return test_signaling(arg, false);
|
||||
}
|
||||
|
||||
static int test_shared_signaling(void *arg)
|
||||
{
|
||||
return test_signaling(arg, true);
|
||||
}
|
||||
|
||||
static int test_for_each(void *arg, bool shared)
|
||||
{
|
||||
struct dma_resv_iter cursor;
|
||||
struct dma_fence *f, *fence;
|
||||
struct dma_resv resv;
|
||||
int r;
|
||||
|
||||
f = alloc_fence();
|
||||
if (!f)
|
||||
return -ENOMEM;
|
||||
|
||||
dma_resv_init(&resv);
|
||||
r = dma_resv_lock(&resv, NULL);
|
||||
if (r) {
|
||||
pr_err("Resv locking failed\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
if (shared) {
|
||||
r = dma_resv_reserve_shared(&resv, 1);
|
||||
if (r) {
|
||||
pr_err("Resv shared slot allocation failed\n");
|
||||
goto err_unlock;
|
||||
}
|
||||
|
||||
dma_resv_add_shared_fence(&resv, f);
|
||||
} else {
|
||||
dma_resv_add_excl_fence(&resv, f);
|
||||
}
|
||||
|
||||
r = -ENOENT;
|
||||
dma_resv_for_each_fence(&cursor, &resv, shared, fence) {
|
||||
if (!r) {
|
||||
pr_err("More than one fence found\n");
|
||||
r = -EINVAL;
|
||||
goto err_unlock;
|
||||
}
|
||||
if (f != fence) {
|
||||
pr_err("Unexpected fence\n");
|
||||
r = -EINVAL;
|
||||
goto err_unlock;
|
||||
}
|
||||
if (dma_resv_iter_is_exclusive(&cursor) != !shared) {
|
||||
pr_err("Unexpected fence usage\n");
|
||||
r = -EINVAL;
|
||||
goto err_unlock;
|
||||
}
|
||||
r = 0;
|
||||
}
|
||||
if (r) {
|
||||
pr_err("No fence found\n");
|
||||
goto err_unlock;
|
||||
}
|
||||
dma_fence_signal(f);
|
||||
err_unlock:
|
||||
dma_resv_unlock(&resv);
|
||||
err_free:
|
||||
dma_resv_fini(&resv);
|
||||
dma_fence_put(f);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int test_excl_for_each(void *arg)
|
||||
{
|
||||
return test_for_each(arg, false);
|
||||
}
|
||||
|
||||
static int test_shared_for_each(void *arg)
|
||||
{
|
||||
return test_for_each(arg, true);
|
||||
}
|
||||
|
||||
static int test_for_each_unlocked(void *arg, bool shared)
|
||||
{
|
||||
struct dma_resv_iter cursor;
|
||||
struct dma_fence *f, *fence;
|
||||
struct dma_resv resv;
|
||||
int r;
|
||||
|
||||
f = alloc_fence();
|
||||
if (!f)
|
||||
return -ENOMEM;
|
||||
|
||||
dma_resv_init(&resv);
|
||||
r = dma_resv_lock(&resv, NULL);
|
||||
if (r) {
|
||||
pr_err("Resv locking failed\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
if (shared) {
|
||||
r = dma_resv_reserve_shared(&resv, 1);
|
||||
if (r) {
|
||||
pr_err("Resv shared slot allocation failed\n");
|
||||
dma_resv_unlock(&resv);
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
dma_resv_add_shared_fence(&resv, f);
|
||||
} else {
|
||||
dma_resv_add_excl_fence(&resv, f);
|
||||
}
|
||||
dma_resv_unlock(&resv);
|
||||
|
||||
r = -ENOENT;
|
||||
dma_resv_iter_begin(&cursor, &resv, shared);
|
||||
dma_resv_for_each_fence_unlocked(&cursor, fence) {
|
||||
if (!r) {
|
||||
pr_err("More than one fence found\n");
|
||||
r = -EINVAL;
|
||||
goto err_iter_end;
|
||||
}
|
||||
if (!dma_resv_iter_is_restarted(&cursor)) {
|
||||
pr_err("No restart flag\n");
|
||||
goto err_iter_end;
|
||||
}
|
||||
if (f != fence) {
|
||||
pr_err("Unexpected fence\n");
|
||||
r = -EINVAL;
|
||||
goto err_iter_end;
|
||||
}
|
||||
if (dma_resv_iter_is_exclusive(&cursor) != !shared) {
|
||||
pr_err("Unexpected fence usage\n");
|
||||
r = -EINVAL;
|
||||
goto err_iter_end;
|
||||
}
|
||||
|
||||
/* We use r as state here */
|
||||
if (r == -ENOENT) {
|
||||
r = -EINVAL;
|
||||
/* That should trigger an restart */
|
||||
cursor.seq--;
|
||||
} else if (r == -EINVAL) {
|
||||
r = 0;
|
||||
}
|
||||
}
|
||||
if (r)
|
||||
pr_err("No fence found\n");
|
||||
err_iter_end:
|
||||
dma_resv_iter_end(&cursor);
|
||||
dma_fence_signal(f);
|
||||
err_free:
|
||||
dma_resv_fini(&resv);
|
||||
dma_fence_put(f);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int test_excl_for_each_unlocked(void *arg)
|
||||
{
|
||||
return test_for_each_unlocked(arg, false);
|
||||
}
|
||||
|
||||
static int test_shared_for_each_unlocked(void *arg)
|
||||
{
|
||||
return test_for_each_unlocked(arg, true);
|
||||
}
|
||||
|
||||
static int test_get_fences(void *arg, bool shared)
|
||||
{
|
||||
struct dma_fence *f, *excl = NULL, **fences = NULL;
|
||||
struct dma_resv resv;
|
||||
int r, i;
|
||||
|
||||
f = alloc_fence();
|
||||
if (!f)
|
||||
return -ENOMEM;
|
||||
|
||||
dma_resv_init(&resv);
|
||||
r = dma_resv_lock(&resv, NULL);
|
||||
if (r) {
|
||||
pr_err("Resv locking failed\n");
|
||||
goto err_resv;
|
||||
}
|
||||
|
||||
if (shared) {
|
||||
r = dma_resv_reserve_shared(&resv, 1);
|
||||
if (r) {
|
||||
pr_err("Resv shared slot allocation failed\n");
|
||||
dma_resv_unlock(&resv);
|
||||
goto err_resv;
|
||||
}
|
||||
|
||||
dma_resv_add_shared_fence(&resv, f);
|
||||
} else {
|
||||
dma_resv_add_excl_fence(&resv, f);
|
||||
}
|
||||
dma_resv_unlock(&resv);
|
||||
|
||||
r = dma_resv_get_fences(&resv, &excl, &i, &fences);
|
||||
if (r) {
|
||||
pr_err("get_fences failed\n");
|
||||
goto err_free;
|
||||
}
|
||||
|
||||
if (shared) {
|
||||
if (excl != NULL) {
|
||||
pr_err("get_fences returned unexpected excl fence\n");
|
||||
goto err_free;
|
||||
}
|
||||
if (i != 1 || fences[0] != f) {
|
||||
pr_err("get_fences returned unexpected shared fence\n");
|
||||
goto err_free;
|
||||
}
|
||||
} else {
|
||||
if (excl != f) {
|
||||
pr_err("get_fences returned unexpected excl fence\n");
|
||||
goto err_free;
|
||||
}
|
||||
if (i != 0) {
|
||||
pr_err("get_fences returned unexpected shared fence\n");
|
||||
goto err_free;
|
||||
}
|
||||
}
|
||||
|
||||
dma_fence_signal(f);
|
||||
err_free:
|
||||
dma_fence_put(excl);
|
||||
while (i--)
|
||||
dma_fence_put(fences[i]);
|
||||
kfree(fences);
|
||||
err_resv:
|
||||
dma_resv_fini(&resv);
|
||||
dma_fence_put(f);
|
||||
return r;
|
||||
}
|
||||
|
||||
static int test_excl_get_fences(void *arg)
|
||||
{
|
||||
return test_get_fences(arg, false);
|
||||
}
|
||||
|
||||
static int test_shared_get_fences(void *arg)
|
||||
{
|
||||
return test_get_fences(arg, true);
|
||||
}
|
||||
|
||||
int dma_resv(void)
|
||||
{
|
||||
static const struct subtest tests[] = {
|
||||
SUBTEST(sanitycheck),
|
||||
SUBTEST(test_excl_signaling),
|
||||
SUBTEST(test_shared_signaling),
|
||||
SUBTEST(test_excl_for_each),
|
||||
SUBTEST(test_shared_for_each),
|
||||
SUBTEST(test_excl_for_each_unlocked),
|
||||
SUBTEST(test_shared_for_each_unlocked),
|
||||
SUBTEST(test_excl_get_fences),
|
||||
SUBTEST(test_shared_get_fences),
|
||||
};
|
||||
|
||||
spin_lock_init(&fence_lock);
|
||||
return subtests(tests, NULL);
|
||||
}
|
|
@ -211,7 +211,7 @@ config DRM_TTM_HELPER
|
|||
Helpers for ttm-based gem objects
|
||||
|
||||
config DRM_GEM_CMA_HELPER
|
||||
bool
|
||||
tristate
|
||||
depends on DRM
|
||||
help
|
||||
Choose this if you need the GEM CMA helper functions
|
||||
|
@ -224,7 +224,7 @@ config DRM_KMS_CMA_HELPER
|
|||
Choose this if you need the KMS CMA helper functions
|
||||
|
||||
config DRM_GEM_SHMEM_HELPER
|
||||
bool
|
||||
tristate
|
||||
depends on DRM && MMU
|
||||
help
|
||||
Choose this if you need the GEM shmem helper functions
|
||||
|
@ -495,3 +495,7 @@ config DRM_PANEL_ORIENTATION_QUIRKS
|
|||
config DRM_LIB_RANDOM
|
||||
bool
|
||||
default n
|
||||
|
||||
config DRM_PRIVACY_SCREEN
|
||||
bool
|
||||
default n
|
||||
|
|
|
@ -4,37 +4,41 @@
|
|||
# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
|
||||
|
||||
drm-y := drm_aperture.o drm_auth.o drm_cache.o \
|
||||
drm_file.o drm_gem.o drm_ioctl.o drm_irq.o \
|
||||
drm_file.o drm_gem.o drm_ioctl.o \
|
||||
drm_drv.o \
|
||||
drm_sysfs.o drm_hashtab.o drm_mm.o \
|
||||
drm_crtc.o drm_fourcc.o drm_modes.o drm_edid.o drm_displayid.o \
|
||||
drm_encoder_slave.o \
|
||||
drm_trace_points.o drm_prime.o \
|
||||
drm_rect.o drm_vma_manager.o drm_flip_work.o \
|
||||
drm_vma_manager.o \
|
||||
drm_modeset_lock.o drm_atomic.o drm_bridge.o \
|
||||
drm_framebuffer.o drm_connector.o drm_blend.o \
|
||||
drm_encoder.o drm_mode_object.o drm_property.o \
|
||||
drm_plane.o drm_color_mgmt.o drm_print.o \
|
||||
drm_dumb_buffers.o drm_mode_config.o drm_vblank.o \
|
||||
drm_syncobj.o drm_lease.o drm_writeback.o drm_client.o \
|
||||
drm_client_modeset.o drm_atomic_uapi.o drm_hdcp.o \
|
||||
drm_client_modeset.o drm_atomic_uapi.o \
|
||||
drm_managed.o drm_vblank_work.o
|
||||
|
||||
drm-$(CONFIG_DRM_LEGACY) += drm_agpsupport.o drm_bufs.o drm_context.o drm_dma.o \
|
||||
drm_legacy_misc.o drm_lock.o drm_memory.o drm_scatter.o \
|
||||
drm_vm.o
|
||||
drm_irq.o drm_legacy_misc.o drm_lock.o drm_memory.o \
|
||||
drm_scatter.o drm_vm.o
|
||||
drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
|
||||
drm-$(CONFIG_COMPAT) += drm_ioc32.o
|
||||
drm-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_gem_cma_helper.o
|
||||
drm-$(CONFIG_DRM_GEM_SHMEM_HELPER) += drm_gem_shmem_helper.o
|
||||
drm-$(CONFIG_DRM_PANEL) += drm_panel.o
|
||||
drm-$(CONFIG_OF) += drm_of.o
|
||||
drm-$(CONFIG_PCI) += drm_pci.o
|
||||
drm-$(CONFIG_DEBUG_FS) += drm_debugfs.o drm_debugfs_crc.o
|
||||
drm-$(CONFIG_DRM_LOAD_EDID_FIRMWARE) += drm_edid_load.o
|
||||
drm-$(CONFIG_DRM_PRIVACY_SCREEN) += drm_privacy_screen.o drm_privacy_screen_x86.o
|
||||
|
||||
obj-$(CONFIG_DRM_DP_AUX_BUS) += drm_dp_aux_bus.o
|
||||
|
||||
drm_cma_helper-y := drm_gem_cma_helper.o
|
||||
obj-$(CONFIG_DRM_GEM_CMA_HELPER) += drm_cma_helper.o
|
||||
|
||||
drm_shmem_helper-y := drm_gem_shmem_helper.o
|
||||
obj-$(CONFIG_DRM_GEM_SHMEM_HELPER) += drm_shmem_helper.o
|
||||
|
||||
drm_vram_helper-y := drm_gem_vram_helper.o
|
||||
obj-$(CONFIG_DRM_VRAM_HELPER) += drm_vram_helper.o
|
||||
|
||||
|
@ -42,14 +46,15 @@ drm_ttm_helper-y := drm_gem_ttm_helper.o
|
|||
obj-$(CONFIG_DRM_TTM_HELPER) += drm_ttm_helper.o
|
||||
|
||||
drm_kms_helper-y := drm_bridge_connector.o drm_crtc_helper.o drm_dp_helper.o \
|
||||
drm_dsc.o drm_probe_helper.o \
|
||||
drm_dsc.o drm_encoder_slave.o drm_flip_work.o drm_hdcp.o \
|
||||
drm_probe_helper.o \
|
||||
drm_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \
|
||||
drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
|
||||
drm_simple_kms_helper.o drm_modeset_helper.o \
|
||||
drm_scdc_helper.o drm_gem_atomic_helper.o \
|
||||
drm_gem_framebuffer_helper.o \
|
||||
drm_atomic_state_helper.o drm_damage_helper.o \
|
||||
drm_format_helper.o drm_self_refresh_helper.o
|
||||
drm_format_helper.o drm_self_refresh_helper.o drm_rect.o
|
||||
|
||||
drm_kms_helper-$(CONFIG_DRM_PANEL_BRIDGE) += bridge/panel.o
|
||||
drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o
|
||||
|
|
|
@ -458,7 +458,6 @@ struct amdgpu_flip_work {
|
|||
uint64_t base;
|
||||
struct drm_pending_vblank_event *event;
|
||||
struct amdgpu_bo *old_abo;
|
||||
struct dma_fence *excl;
|
||||
unsigned shared_count;
|
||||
struct dma_fence **shared;
|
||||
struct dma_fence_cb cb;
|
||||
|
|
|
@ -83,9 +83,6 @@ static void amdgpu_display_flip_work_func(struct work_struct *__work)
|
|||
unsigned i;
|
||||
int vpos, hpos;
|
||||
|
||||
if (amdgpu_display_flip_handle_fence(work, &work->excl))
|
||||
return;
|
||||
|
||||
for (i = 0; i < work->shared_count; ++i)
|
||||
if (amdgpu_display_flip_handle_fence(work, &work->shared[i]))
|
||||
return;
|
||||
|
@ -203,7 +200,7 @@ int amdgpu_display_crtc_page_flip_target(struct drm_crtc *crtc,
|
|||
goto unpin;
|
||||
}
|
||||
|
||||
r = dma_resv_get_fences(new_abo->tbo.base.resv, &work->excl,
|
||||
r = dma_resv_get_fences(new_abo->tbo.base.resv, NULL,
|
||||
&work->shared_count, &work->shared);
|
||||
if (unlikely(r != 0)) {
|
||||
DRM_ERROR("failed to get fences for buffer\n");
|
||||
|
@ -253,7 +250,6 @@ unreserve:
|
|||
|
||||
cleanup:
|
||||
amdgpu_bo_unref(&work->old_abo);
|
||||
dma_fence_put(work->excl);
|
||||
for (i = 0; i < work->shared_count; ++i)
|
||||
dma_fence_put(work->shared[i]);
|
||||
kfree(work->shared);
|
||||
|
|
|
@ -252,41 +252,25 @@ int amdgpu_sync_resv(struct amdgpu_device *adev, struct amdgpu_sync *sync,
|
|||
struct dma_resv *resv, enum amdgpu_sync_mode mode,
|
||||
void *owner)
|
||||
{
|
||||
struct dma_resv_list *flist;
|
||||
struct dma_resv_iter cursor;
|
||||
struct dma_fence *f;
|
||||
unsigned i;
|
||||
int r = 0;
|
||||
int r;
|
||||
|
||||
if (resv == NULL)
|
||||
return -EINVAL;
|
||||
|
||||
/* always sync to the exclusive fence */
|
||||
f = dma_resv_excl_fence(resv);
|
||||
dma_fence_chain_for_each(f, f) {
|
||||
struct dma_fence_chain *chain = to_dma_fence_chain(f);
|
||||
dma_resv_for_each_fence(&cursor, resv, true, f) {
|
||||
dma_fence_chain_for_each(f, f) {
|
||||
struct dma_fence_chain *chain = to_dma_fence_chain(f);
|
||||
|
||||
if (amdgpu_sync_test_fence(adev, mode, owner, chain ?
|
||||
chain->fence : f)) {
|
||||
r = amdgpu_sync_fence(sync, f);
|
||||
dma_fence_put(f);
|
||||
if (r)
|
||||
return r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
flist = dma_resv_shared_list(resv);
|
||||
if (!flist)
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < flist->shared_count; ++i) {
|
||||
f = rcu_dereference_protected(flist->shared[i],
|
||||
dma_resv_held(resv));
|
||||
|
||||
if (amdgpu_sync_test_fence(adev, mode, owner, f)) {
|
||||
r = amdgpu_sync_fence(sync, f);
|
||||
if (r)
|
||||
return r;
|
||||
if (amdgpu_sync_test_fence(adev, mode, owner, chain ?
|
||||
chain->fence : f)) {
|
||||
r = amdgpu_sync_fence(sync, f);
|
||||
dma_fence_put(f);
|
||||
if (r)
|
||||
return r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
|
|
|
@ -1353,10 +1353,9 @@ static bool amdgpu_ttm_bo_eviction_valuable(struct ttm_buffer_object *bo,
|
|||
const struct ttm_place *place)
|
||||
{
|
||||
unsigned long num_pages = bo->resource->num_pages;
|
||||
struct dma_resv_iter resv_cursor;
|
||||
struct amdgpu_res_cursor cursor;
|
||||
struct dma_resv_list *flist;
|
||||
struct dma_fence *f;
|
||||
int i;
|
||||
|
||||
/* Swapout? */
|
||||
if (bo->resource->mem_type == TTM_PL_SYSTEM)
|
||||
|
@ -1370,14 +1369,9 @@ static bool amdgpu_ttm_bo_eviction_valuable(struct ttm_buffer_object *bo,
|
|||
* If true, then return false as any KFD process needs all its BOs to
|
||||
* be resident to run successfully
|
||||
*/
|
||||
flist = dma_resv_shared_list(bo->base.resv);
|
||||
if (flist) {
|
||||
for (i = 0; i < flist->shared_count; ++i) {
|
||||
f = rcu_dereference_protected(flist->shared[i],
|
||||
dma_resv_held(bo->base.resv));
|
||||
if (amdkfd_fence_check_mm(f, current->mm))
|
||||
return false;
|
||||
}
|
||||
dma_resv_for_each_fence(&resv_cursor, bo->base.resv, true, f) {
|
||||
if (amdkfd_fence_check_mm(f, current->mm))
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (bo->resource->mem_type) {
|
||||
|
|
|
@ -2102,30 +2102,14 @@ static void amdgpu_vm_free_mapping(struct amdgpu_device *adev,
|
|||
static void amdgpu_vm_prt_fini(struct amdgpu_device *adev, struct amdgpu_vm *vm)
|
||||
{
|
||||
struct dma_resv *resv = vm->root.bo->tbo.base.resv;
|
||||
struct dma_fence *excl, **shared;
|
||||
unsigned i, shared_count;
|
||||
int r;
|
||||
struct dma_resv_iter cursor;
|
||||
struct dma_fence *fence;
|
||||
|
||||
r = dma_resv_get_fences(resv, &excl, &shared_count, &shared);
|
||||
if (r) {
|
||||
/* Not enough memory to grab the fence list, as last resort
|
||||
* block for all the fences to complete.
|
||||
*/
|
||||
dma_resv_wait_timeout(resv, true, false,
|
||||
MAX_SCHEDULE_TIMEOUT);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Add a callback for each fence in the reservation object */
|
||||
amdgpu_vm_prt_get(adev);
|
||||
amdgpu_vm_add_prt_cb(adev, excl);
|
||||
|
||||
for (i = 0; i < shared_count; ++i) {
|
||||
dma_resv_for_each_fence(&cursor, resv, true, fence) {
|
||||
/* Add a callback for each fence in the reservation object */
|
||||
amdgpu_vm_prt_get(adev);
|
||||
amdgpu_vm_add_prt_cb(adev, shared[i]);
|
||||
amdgpu_vm_add_prt_cb(adev, fence);
|
||||
}
|
||||
|
||||
kfree(shared);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -3012,7 +3012,7 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector)
|
|||
drm_modeset_unlock_all(dev);
|
||||
|
||||
if (aconnector->base.force == DRM_FORCE_UNSPECIFIED)
|
||||
drm_kms_helper_hotplug_event(dev);
|
||||
drm_kms_helper_connector_hotplug_event(connector);
|
||||
|
||||
} else if (dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD)) {
|
||||
if (new_connection_type == dc_connection_none &&
|
||||
|
@ -3027,7 +3027,7 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector)
|
|||
drm_modeset_unlock_all(dev);
|
||||
|
||||
if (aconnector->base.force == DRM_FORCE_UNSPECIFIED)
|
||||
drm_kms_helper_hotplug_event(dev);
|
||||
drm_kms_helper_connector_hotplug_event(connector);
|
||||
}
|
||||
mutex_unlock(&aconnector->hpd_lock);
|
||||
|
||||
|
@ -3221,7 +3221,7 @@ out:
|
|||
dm_restore_drm_connector_state(dev, connector);
|
||||
drm_modeset_unlock_all(dev);
|
||||
|
||||
drm_kms_helper_hotplug_event(dev);
|
||||
drm_kms_helper_connector_hotplug_event(connector);
|
||||
} else if (dc_link_detect(dc_link, DETECT_REASON_HPDRX)) {
|
||||
|
||||
if (aconnector->fake_enable)
|
||||
|
@ -3234,7 +3234,7 @@ out:
|
|||
dm_restore_drm_connector_state(dev, connector);
|
||||
drm_modeset_unlock_all(dev);
|
||||
|
||||
drm_kms_helper_hotplug_event(dev);
|
||||
drm_kms_helper_connector_hotplug_event(connector);
|
||||
}
|
||||
}
|
||||
#ifdef CONFIG_DRM_AMD_DC_HDCP
|
||||
|
|
|
@ -1243,7 +1243,7 @@ static ssize_t trigger_hotplug(struct file *f, const char __user *buf,
|
|||
dm_restore_drm_connector_state(dev, connector);
|
||||
drm_modeset_unlock_all(dev);
|
||||
|
||||
drm_kms_helper_hotplug_event(dev);
|
||||
drm_kms_helper_connector_hotplug_event(connector);
|
||||
} else if (param[0] == 0) {
|
||||
if (!aconnector->dc_link)
|
||||
goto unlock;
|
||||
|
@ -1265,7 +1265,7 @@ static ssize_t trigger_hotplug(struct file *f, const char __user *buf,
|
|||
dm_restore_drm_connector_state(dev, connector);
|
||||
drm_modeset_unlock_all(dev);
|
||||
|
||||
drm_kms_helper_hotplug_event(dev);
|
||||
drm_kms_helper_connector_hotplug_event(connector);
|
||||
}
|
||||
|
||||
unlock:
|
||||
|
|
|
@ -182,6 +182,7 @@ config DRM_PARADE_PS8622
|
|||
config DRM_PARADE_PS8640
|
||||
tristate "Parade PS8640 MIPI DSI to eDP Converter"
|
||||
depends on OF
|
||||
select DRM_DP_AUX_BUS
|
||||
select DRM_KMS_HELPER
|
||||
select DRM_MIPI_DSI
|
||||
select DRM_PANEL
|
||||
|
|
|
@ -401,7 +401,6 @@ void adv7533_mode_set(struct adv7511 *adv, const struct drm_display_mode *mode);
|
|||
int adv7533_patch_registers(struct adv7511 *adv);
|
||||
int adv7533_patch_cec_registers(struct adv7511 *adv);
|
||||
int adv7533_attach_dsi(struct adv7511 *adv);
|
||||
void adv7533_detach_dsi(struct adv7511 *adv);
|
||||
int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv);
|
||||
|
||||
#ifdef CONFIG_DRM_I2C_ADV7511_AUDIO
|
||||
|
|
|
@ -910,9 +910,6 @@ static int adv7511_bridge_attach(struct drm_bridge *bridge,
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (adv->type == ADV7533 || adv->type == ADV7535)
|
||||
ret = adv7533_attach_dsi(adv);
|
||||
|
||||
if (adv->i2c_main->irq)
|
||||
regmap_write(adv->regmap, ADV7511_REG_INT_ENABLE(0),
|
||||
ADV7511_INT0_HPD);
|
||||
|
@ -1288,8 +1285,18 @@ static int adv7511_probe(struct i2c_client *i2c, const struct i2c_device_id *id)
|
|||
drm_bridge_add(&adv7511->bridge);
|
||||
|
||||
adv7511_audio_init(dev, adv7511);
|
||||
|
||||
if (adv7511->type == ADV7533 || adv7511->type == ADV7535) {
|
||||
ret = adv7533_attach_dsi(adv7511);
|
||||
if (ret)
|
||||
goto err_unregister_audio;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister_audio:
|
||||
adv7511_audio_exit(adv7511);
|
||||
drm_bridge_remove(&adv7511->bridge);
|
||||
err_unregister_cec:
|
||||
i2c_unregister_device(adv7511->i2c_cec);
|
||||
clk_disable_unprepare(adv7511->cec_clk);
|
||||
|
@ -1307,8 +1314,6 @@ static int adv7511_remove(struct i2c_client *i2c)
|
|||
{
|
||||
struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
|
||||
|
||||
if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
|
||||
adv7533_detach_dsi(adv7511);
|
||||
i2c_unregister_device(adv7511->i2c_cec);
|
||||
clk_disable_unprepare(adv7511->cec_clk);
|
||||
|
||||
|
|
|
@ -153,11 +153,10 @@ int adv7533_attach_dsi(struct adv7511 *adv)
|
|||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
dsi = mipi_dsi_device_register_full(host, &info);
|
||||
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
|
||||
if (IS_ERR(dsi)) {
|
||||
dev_err(dev, "failed to create dsi device\n");
|
||||
ret = PTR_ERR(dsi);
|
||||
goto err_dsi_device;
|
||||
return PTR_ERR(dsi);
|
||||
}
|
||||
|
||||
adv->dsi = dsi;
|
||||
|
@ -167,24 +166,13 @@ int adv7533_attach_dsi(struct adv7511 *adv)
|
|||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
|
||||
MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE;
|
||||
|
||||
ret = mipi_dsi_attach(dsi);
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to attach dsi to host\n");
|
||||
goto err_dsi_attach;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_dsi_attach:
|
||||
mipi_dsi_device_unregister(dsi);
|
||||
err_dsi_device:
|
||||
return ret;
|
||||
}
|
||||
|
||||
void adv7533_detach_dsi(struct adv7511 *adv)
|
||||
{
|
||||
mipi_dsi_detach(adv->dsi);
|
||||
mipi_dsi_device_unregister(adv->dsi);
|
||||
}
|
||||
|
||||
int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv)
|
||||
|
|
|
@ -998,11 +998,21 @@ int analogix_dp_send_psr_spd(struct analogix_dp_device *dp,
|
|||
if (!blocking)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* db[1]!=0: entering PSR, wait for fully active remote frame buffer.
|
||||
* db[1]==0: exiting PSR, wait for either
|
||||
* (a) ACTIVE_RESYNC - the sink "must display the
|
||||
* incoming active frames from the Source device with no visible
|
||||
* glitches and/or artifacts", even though timings may still be
|
||||
* re-synchronizing; or
|
||||
* (b) INACTIVE - the transition is fully complete.
|
||||
*/
|
||||
ret = readx_poll_timeout(analogix_dp_get_psr_status, dp, psr_status,
|
||||
psr_status >= 0 &&
|
||||
((vsc->db[1] && psr_status == DP_PSR_SINK_ACTIVE_RFB) ||
|
||||
(!vsc->db[1] && psr_status == DP_PSR_SINK_INACTIVE)), 1500,
|
||||
DP_TIMEOUT_PSR_LOOP_MS * 1000);
|
||||
(!vsc->db[1] && (psr_status == DP_PSR_SINK_ACTIVE_RESYNC ||
|
||||
psr_status == DP_PSR_SINK_INACTIVE))),
|
||||
1500, DP_TIMEOUT_PSR_LOOP_MS * 1000);
|
||||
if (ret) {
|
||||
dev_warn(dp->dev, "Failed to apply PSR %d\n", ret);
|
||||
return ret;
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
#include <media/v4l2-fwnode.h>
|
||||
#include <sound/hdmi-codec.h>
|
||||
#include <video/display_timing.h>
|
||||
|
||||
#include "anx7625.h"
|
||||
|
@ -166,6 +168,20 @@ static int anx7625_write_and_or(struct anx7625_data *ctx,
|
|||
offset, (val & and_mask) | (or_mask));
|
||||
}
|
||||
|
||||
static int anx7625_config_bit_matrix(struct anx7625_data *ctx)
|
||||
{
|
||||
int i, ret;
|
||||
|
||||
ret = anx7625_write_or(ctx, ctx->i2c.tx_p2_client,
|
||||
AUDIO_CONTROL_REGISTER, 0x80);
|
||||
for (i = 0; i < 13; i++)
|
||||
ret |= anx7625_reg_write(ctx, ctx->i2c.tx_p2_client,
|
||||
VIDEO_BIT_MATRIX_12 + i,
|
||||
0x18 + i);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int anx7625_read_ctrl_status_p0(struct anx7625_data *ctx)
|
||||
{
|
||||
return anx7625_reg_read(ctx, ctx->i2c.rx_p0_client, AP_AUX_CTRL_STATUS);
|
||||
|
@ -191,10 +207,10 @@ static int wait_aux_op_finish(struct anx7625_data *ctx)
|
|||
AP_AUX_CTRL_STATUS);
|
||||
if (val < 0 || (val & 0x0F)) {
|
||||
DRM_DEV_ERROR(dev, "aux status %02x\n", val);
|
||||
val = -EIO;
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int anx7625_video_mute_control(struct anx7625_data *ctx,
|
||||
|
@ -221,38 +237,6 @@ static int anx7625_video_mute_control(struct anx7625_data *ctx,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int anx7625_config_audio_input(struct anx7625_data *ctx)
|
||||
{
|
||||
struct device *dev = &ctx->client->dev;
|
||||
int ret;
|
||||
|
||||
/* Channel num */
|
||||
ret = anx7625_reg_write(ctx, ctx->i2c.tx_p2_client,
|
||||
AUDIO_CHANNEL_STATUS_6, I2S_CH_2 << 5);
|
||||
|
||||
/* FS */
|
||||
ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
|
||||
AUDIO_CHANNEL_STATUS_4,
|
||||
0xf0, AUDIO_FS_48K);
|
||||
/* Word length */
|
||||
ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
|
||||
AUDIO_CHANNEL_STATUS_5,
|
||||
0xf0, AUDIO_W_LEN_24_24MAX);
|
||||
/* I2S */
|
||||
ret |= anx7625_write_or(ctx, ctx->i2c.tx_p2_client,
|
||||
AUDIO_CHANNEL_STATUS_6, I2S_SLAVE_MODE);
|
||||
ret |= anx7625_write_and(ctx, ctx->i2c.tx_p2_client,
|
||||
AUDIO_CONTROL_REGISTER, ~TDM_TIMING_MODE);
|
||||
/* Audio change flag */
|
||||
ret |= anx7625_write_or(ctx, ctx->i2c.rx_p0_client,
|
||||
AP_AV_STATUS, AP_AUDIO_CHG);
|
||||
|
||||
if (ret < 0)
|
||||
DRM_DEV_ERROR(dev, "fail to config audio.\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Reduction of fraction a/b */
|
||||
static void anx7625_reduction_of_a_fraction(unsigned long *a, unsigned long *b)
|
||||
{
|
||||
|
@ -431,7 +415,7 @@ static int anx7625_dsi_video_timing_config(struct anx7625_data *ctx)
|
|||
ret |= anx7625_write_and(ctx, ctx->i2c.rx_p1_client,
|
||||
MIPI_LANE_CTRL_0, 0xfc);
|
||||
ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client,
|
||||
MIPI_LANE_CTRL_0, 3);
|
||||
MIPI_LANE_CTRL_0, ctx->pdata.mipi_lanes - 1);
|
||||
|
||||
/* Htotal */
|
||||
htotal = ctx->dt.hactive.min + ctx->dt.hfront_porch.min +
|
||||
|
@ -615,6 +599,76 @@ static int anx7625_dsi_config(struct anx7625_data *ctx)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int anx7625_api_dpi_config(struct anx7625_data *ctx)
|
||||
{
|
||||
struct device *dev = &ctx->client->dev;
|
||||
u16 freq = ctx->dt.pixelclock.min / 1000;
|
||||
int ret;
|
||||
|
||||
/* configure pixel clock */
|
||||
ret = anx7625_reg_write(ctx, ctx->i2c.rx_p0_client,
|
||||
PIXEL_CLOCK_L, freq & 0xFF);
|
||||
ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p0_client,
|
||||
PIXEL_CLOCK_H, (freq >> 8));
|
||||
|
||||
/* set DPI mode */
|
||||
/* set to DPI PLL module sel */
|
||||
ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p1_client,
|
||||
MIPI_DIGITAL_PLL_9, 0x20);
|
||||
/* power down MIPI */
|
||||
ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p1_client,
|
||||
MIPI_LANE_CTRL_10, 0x08);
|
||||
/* enable DPI mode */
|
||||
ret |= anx7625_reg_write(ctx, ctx->i2c.rx_p1_client,
|
||||
MIPI_DIGITAL_PLL_18, 0x1C);
|
||||
/* set first edge */
|
||||
ret |= anx7625_reg_write(ctx, ctx->i2c.tx_p2_client,
|
||||
VIDEO_CONTROL_0, 0x06);
|
||||
if (ret < 0)
|
||||
DRM_DEV_ERROR(dev, "IO error : dpi phy set failed.\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int anx7625_dpi_config(struct anx7625_data *ctx)
|
||||
{
|
||||
struct device *dev = &ctx->client->dev;
|
||||
int ret;
|
||||
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "config dpi\n");
|
||||
|
||||
/* DSC disable */
|
||||
ret = anx7625_write_and(ctx, ctx->i2c.rx_p0_client,
|
||||
R_DSC_CTRL_0, ~DSC_EN);
|
||||
if (ret < 0) {
|
||||
DRM_DEV_ERROR(dev, "IO error : disable dsc failed.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = anx7625_config_bit_matrix(ctx);
|
||||
if (ret < 0) {
|
||||
DRM_DEV_ERROR(dev, "config bit matrix failed.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = anx7625_api_dpi_config(ctx);
|
||||
if (ret < 0) {
|
||||
DRM_DEV_ERROR(dev, "mipi phy(dpi) setup failed.\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* set MIPI RX EN */
|
||||
ret = anx7625_write_or(ctx, ctx->i2c.rx_p0_client,
|
||||
AP_AV_STATUS, AP_MIPI_RX_EN);
|
||||
/* clear mute flag */
|
||||
ret |= anx7625_write_and(ctx, ctx->i2c.rx_p0_client,
|
||||
AP_AV_STATUS, (u8)~AP_MIPI_MUTE);
|
||||
if (ret < 0)
|
||||
DRM_DEV_ERROR(dev, "IO error : enable mipi rx failed.\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void anx7625_dp_start(struct anx7625_data *ctx)
|
||||
{
|
||||
int ret;
|
||||
|
@ -625,9 +679,10 @@ static void anx7625_dp_start(struct anx7625_data *ctx)
|
|||
return;
|
||||
}
|
||||
|
||||
anx7625_config_audio_input(ctx);
|
||||
|
||||
ret = anx7625_dsi_config(ctx);
|
||||
if (ctx->pdata.is_dpi)
|
||||
ret = anx7625_dpi_config(ctx);
|
||||
else
|
||||
ret = anx7625_dsi_config(ctx);
|
||||
|
||||
if (ret < 0)
|
||||
DRM_DEV_ERROR(dev, "MIPI phy setup error.\n");
|
||||
|
@ -1075,6 +1130,7 @@ static void anx7625_start_dp_work(struct anx7625_data *ctx)
|
|||
return;
|
||||
}
|
||||
|
||||
ctx->hpd_status = 1;
|
||||
ctx->hpd_high_cnt++;
|
||||
|
||||
/* Not support HDCP */
|
||||
|
@ -1084,8 +1140,10 @@ static void anx7625_start_dp_work(struct anx7625_data *ctx)
|
|||
ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client, 0xec, 0x10);
|
||||
/* Interrupt for DRM */
|
||||
ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client, 0xff, 0x01);
|
||||
if (ret < 0)
|
||||
if (ret < 0) {
|
||||
DRM_DEV_ERROR(dev, "fail to setting HDCP/auth\n");
|
||||
return;
|
||||
}
|
||||
|
||||
ret = anx7625_reg_read(ctx, ctx->i2c.rx_p1_client, 0x86);
|
||||
if (ret < 0)
|
||||
|
@ -1104,6 +1162,10 @@ static void anx7625_hpd_polling(struct anx7625_data *ctx)
|
|||
int ret, val;
|
||||
struct device *dev = &ctx->client->dev;
|
||||
|
||||
/* Interrupt mode, no need poll HPD status, just return */
|
||||
if (ctx->pdata.intp_irq)
|
||||
return;
|
||||
|
||||
ret = readx_poll_timeout(anx7625_read_hpd_status_p0,
|
||||
ctx, val,
|
||||
((val & HPD_STATUS) || (val < 0)),
|
||||
|
@ -1131,6 +1193,21 @@ static void anx7625_remove_edid(struct anx7625_data *ctx)
|
|||
ctx->slimport_edid_p.edid_block_num = -1;
|
||||
}
|
||||
|
||||
static void anx7625_dp_adjust_swing(struct anx7625_data *ctx)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ctx->pdata.dp_lane0_swing_reg_cnt; i++)
|
||||
anx7625_reg_write(ctx, ctx->i2c.tx_p1_client,
|
||||
DP_TX_LANE0_SWING_REG0 + i,
|
||||
ctx->pdata.lane0_reg_data[i] & 0xFF);
|
||||
|
||||
for (i = 0; i < ctx->pdata.dp_lane1_swing_reg_cnt; i++)
|
||||
anx7625_reg_write(ctx, ctx->i2c.tx_p1_client,
|
||||
DP_TX_LANE1_SWING_REG0 + i,
|
||||
ctx->pdata.lane1_reg_data[i] & 0xFF);
|
||||
}
|
||||
|
||||
static void dp_hpd_change_handler(struct anx7625_data *ctx, bool on)
|
||||
{
|
||||
struct device *dev = &ctx->client->dev;
|
||||
|
@ -1146,9 +1223,8 @@ static void dp_hpd_change_handler(struct anx7625_data *ctx, bool on)
|
|||
} else {
|
||||
DRM_DEV_DEBUG_DRIVER(dev, " HPD high\n");
|
||||
anx7625_start_dp_work(ctx);
|
||||
anx7625_dp_adjust_swing(ctx);
|
||||
}
|
||||
|
||||
ctx->hpd_status = 1;
|
||||
}
|
||||
|
||||
static int anx7625_hpd_change_detect(struct anx7625_data *ctx)
|
||||
|
@ -1225,20 +1301,75 @@ static irqreturn_t anx7625_intr_hpd_isr(int irq, void *data)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int anx7625_get_swing_setting(struct device *dev,
|
||||
struct anx7625_platform_data *pdata)
|
||||
{
|
||||
int num_regs;
|
||||
|
||||
if (of_get_property(dev->of_node,
|
||||
"analogix,lane0-swing", &num_regs)) {
|
||||
if (num_regs > DP_TX_SWING_REG_CNT)
|
||||
num_regs = DP_TX_SWING_REG_CNT;
|
||||
|
||||
pdata->dp_lane0_swing_reg_cnt = num_regs;
|
||||
of_property_read_u32_array(dev->of_node, "analogix,lane0-swing",
|
||||
pdata->lane0_reg_data, num_regs);
|
||||
}
|
||||
|
||||
if (of_get_property(dev->of_node,
|
||||
"analogix,lane1-swing", &num_regs)) {
|
||||
if (num_regs > DP_TX_SWING_REG_CNT)
|
||||
num_regs = DP_TX_SWING_REG_CNT;
|
||||
|
||||
pdata->dp_lane1_swing_reg_cnt = num_regs;
|
||||
of_property_read_u32_array(dev->of_node, "analogix,lane1-swing",
|
||||
pdata->lane1_reg_data, num_regs);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int anx7625_parse_dt(struct device *dev,
|
||||
struct anx7625_platform_data *pdata)
|
||||
{
|
||||
struct device_node *np = dev->of_node;
|
||||
struct device_node *np = dev->of_node, *ep0;
|
||||
struct drm_panel *panel;
|
||||
int ret;
|
||||
int bus_type, mipi_lanes;
|
||||
|
||||
anx7625_get_swing_setting(dev, pdata);
|
||||
|
||||
pdata->is_dpi = 1; /* default dpi mode */
|
||||
pdata->mipi_host_node = of_graph_get_remote_node(np, 0, 0);
|
||||
if (!pdata->mipi_host_node) {
|
||||
DRM_DEV_ERROR(dev, "fail to get internal panel.\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "found dsi host node.\n");
|
||||
bus_type = V4L2_FWNODE_BUS_TYPE_PARALLEL;
|
||||
mipi_lanes = MAX_LANES_SUPPORT;
|
||||
ep0 = of_graph_get_endpoint_by_regs(np, 0, 0);
|
||||
if (ep0) {
|
||||
if (of_property_read_u32(ep0, "bus-type", &bus_type))
|
||||
bus_type = 0;
|
||||
|
||||
mipi_lanes = of_property_count_u32_elems(ep0, "data-lanes");
|
||||
}
|
||||
|
||||
if (bus_type == V4L2_FWNODE_BUS_TYPE_PARALLEL) /* bus type is Parallel(DSI) */
|
||||
pdata->is_dpi = 0;
|
||||
|
||||
pdata->mipi_lanes = mipi_lanes;
|
||||
if (pdata->mipi_lanes > MAX_LANES_SUPPORT || pdata->mipi_lanes <= 0)
|
||||
pdata->mipi_lanes = MAX_LANES_SUPPORT;
|
||||
|
||||
if (pdata->is_dpi)
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "found MIPI DPI host node.\n");
|
||||
else
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "found MIPI DSI host node.\n");
|
||||
|
||||
if (of_property_read_bool(np, "analogix,audio-enable"))
|
||||
pdata->audio_en = 1;
|
||||
|
||||
ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL);
|
||||
if (ret < 0) {
|
||||
|
@ -1301,9 +1432,215 @@ static enum drm_connector_status anx7625_sink_detect(struct anx7625_data *ctx)
|
|||
{
|
||||
struct device *dev = &ctx->client->dev;
|
||||
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "sink detect, return connected\n");
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "sink detect\n");
|
||||
|
||||
return connector_status_connected;
|
||||
if (ctx->pdata.panel_bridge)
|
||||
return connector_status_connected;
|
||||
|
||||
return ctx->hpd_status ? connector_status_connected :
|
||||
connector_status_disconnected;
|
||||
}
|
||||
|
||||
static int anx7625_audio_hw_params(struct device *dev, void *data,
|
||||
struct hdmi_codec_daifmt *fmt,
|
||||
struct hdmi_codec_params *params)
|
||||
{
|
||||
struct anx7625_data *ctx = dev_get_drvdata(dev);
|
||||
int wl, ch, rate;
|
||||
int ret = 0;
|
||||
|
||||
if (fmt->fmt != HDMI_DSP_A) {
|
||||
DRM_DEV_ERROR(dev, "only supports DSP_A\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "setting %d Hz, %d bit, %d channels\n",
|
||||
params->sample_rate, params->sample_width,
|
||||
params->cea.channels);
|
||||
|
||||
ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
|
||||
AUDIO_CHANNEL_STATUS_6,
|
||||
~I2S_SLAVE_MODE,
|
||||
TDM_SLAVE_MODE);
|
||||
|
||||
/* Word length */
|
||||
switch (params->sample_width) {
|
||||
case 16:
|
||||
wl = AUDIO_W_LEN_16_20MAX;
|
||||
break;
|
||||
case 18:
|
||||
wl = AUDIO_W_LEN_18_20MAX;
|
||||
break;
|
||||
case 20:
|
||||
wl = AUDIO_W_LEN_20_20MAX;
|
||||
break;
|
||||
case 24:
|
||||
wl = AUDIO_W_LEN_24_24MAX;
|
||||
break;
|
||||
default:
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "wordlength: %d bit not support",
|
||||
params->sample_width);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
|
||||
AUDIO_CHANNEL_STATUS_5,
|
||||
0xf0, wl);
|
||||
|
||||
/* Channel num */
|
||||
switch (params->cea.channels) {
|
||||
case 2:
|
||||
ch = I2S_CH_2;
|
||||
break;
|
||||
case 4:
|
||||
ch = TDM_CH_4;
|
||||
break;
|
||||
case 6:
|
||||
ch = TDM_CH_6;
|
||||
break;
|
||||
case 8:
|
||||
ch = TDM_CH_8;
|
||||
break;
|
||||
default:
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "channel number: %d not support",
|
||||
params->cea.channels);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
|
||||
AUDIO_CHANNEL_STATUS_6, 0x1f, ch << 5);
|
||||
if (ch > I2S_CH_2)
|
||||
ret |= anx7625_write_or(ctx, ctx->i2c.tx_p2_client,
|
||||
AUDIO_CHANNEL_STATUS_6, AUDIO_LAYOUT);
|
||||
else
|
||||
ret |= anx7625_write_and(ctx, ctx->i2c.tx_p2_client,
|
||||
AUDIO_CHANNEL_STATUS_6, ~AUDIO_LAYOUT);
|
||||
|
||||
/* FS */
|
||||
switch (params->sample_rate) {
|
||||
case 32000:
|
||||
rate = AUDIO_FS_32K;
|
||||
break;
|
||||
case 44100:
|
||||
rate = AUDIO_FS_441K;
|
||||
break;
|
||||
case 48000:
|
||||
rate = AUDIO_FS_48K;
|
||||
break;
|
||||
case 88200:
|
||||
rate = AUDIO_FS_882K;
|
||||
break;
|
||||
case 96000:
|
||||
rate = AUDIO_FS_96K;
|
||||
break;
|
||||
case 176400:
|
||||
rate = AUDIO_FS_1764K;
|
||||
break;
|
||||
case 192000:
|
||||
rate = AUDIO_FS_192K;
|
||||
break;
|
||||
default:
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "sample rate: %d not support",
|
||||
params->sample_rate);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret |= anx7625_write_and_or(ctx, ctx->i2c.tx_p2_client,
|
||||
AUDIO_CHANNEL_STATUS_4,
|
||||
0xf0, rate);
|
||||
ret |= anx7625_write_or(ctx, ctx->i2c.rx_p0_client,
|
||||
AP_AV_STATUS, AP_AUDIO_CHG);
|
||||
if (ret < 0) {
|
||||
DRM_DEV_ERROR(dev, "IO error : config audio.\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void anx7625_audio_shutdown(struct device *dev, void *data)
|
||||
{
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "stop audio\n");
|
||||
}
|
||||
|
||||
static int anx7625_hdmi_i2s_get_dai_id(struct snd_soc_component *component,
|
||||
struct device_node *endpoint)
|
||||
{
|
||||
struct of_endpoint of_ep;
|
||||
int ret;
|
||||
|
||||
ret = of_graph_parse_endpoint(endpoint, &of_ep);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/*
|
||||
* HDMI sound should be located at external DPI port
|
||||
* Didn't have good way to check where is internal(DSI)
|
||||
* or external(DPI) bridge
|
||||
*/
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
anx7625_audio_update_connector_status(struct anx7625_data *ctx,
|
||||
enum drm_connector_status status)
|
||||
{
|
||||
if (ctx->plugged_cb && ctx->codec_dev) {
|
||||
ctx->plugged_cb(ctx->codec_dev,
|
||||
status == connector_status_connected);
|
||||
}
|
||||
}
|
||||
|
||||
static int anx7625_audio_hook_plugged_cb(struct device *dev, void *data,
|
||||
hdmi_codec_plugged_cb fn,
|
||||
struct device *codec_dev)
|
||||
{
|
||||
struct anx7625_data *ctx = data;
|
||||
|
||||
ctx->plugged_cb = fn;
|
||||
ctx->codec_dev = codec_dev;
|
||||
anx7625_audio_update_connector_status(ctx, anx7625_sink_detect(ctx));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct hdmi_codec_ops anx7625_codec_ops = {
|
||||
.hw_params = anx7625_audio_hw_params,
|
||||
.audio_shutdown = anx7625_audio_shutdown,
|
||||
.get_dai_id = anx7625_hdmi_i2s_get_dai_id,
|
||||
.hook_plugged_cb = anx7625_audio_hook_plugged_cb,
|
||||
};
|
||||
|
||||
static void anx7625_unregister_audio(struct anx7625_data *ctx)
|
||||
{
|
||||
struct device *dev = &ctx->client->dev;
|
||||
|
||||
if (ctx->audio_pdev) {
|
||||
platform_device_unregister(ctx->audio_pdev);
|
||||
ctx->audio_pdev = NULL;
|
||||
}
|
||||
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "unbound to %s", HDMI_CODEC_DRV_NAME);
|
||||
}
|
||||
|
||||
static int anx7625_register_audio(struct device *dev, struct anx7625_data *ctx)
|
||||
{
|
||||
struct hdmi_codec_pdata codec_data = {
|
||||
.ops = &anx7625_codec_ops,
|
||||
.max_i2s_channels = 8,
|
||||
.i2s = 1,
|
||||
.data = ctx,
|
||||
};
|
||||
|
||||
ctx->audio_pdev = platform_device_register_data(dev,
|
||||
HDMI_CODEC_DRV_NAME,
|
||||
PLATFORM_DEVID_AUTO,
|
||||
&codec_data,
|
||||
sizeof(codec_data));
|
||||
|
||||
if (IS_ERR(ctx->audio_pdev))
|
||||
return IS_ERR(ctx->audio_pdev);
|
||||
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "bound to %s", HDMI_CODEC_DRV_NAME);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int anx7625_attach_dsi(struct anx7625_data *ctx)
|
||||
|
@ -1316,6 +1653,7 @@ static int anx7625_attach_dsi(struct anx7625_data *ctx)
|
|||
.channel = 0,
|
||||
.node = NULL,
|
||||
};
|
||||
int ret;
|
||||
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "attach dsi\n");
|
||||
|
||||
|
@ -1325,22 +1663,22 @@ static int anx7625_attach_dsi(struct anx7625_data *ctx)
|
|||
return -EINVAL;
|
||||
}
|
||||
|
||||
dsi = mipi_dsi_device_register_full(host, &info);
|
||||
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
|
||||
if (IS_ERR(dsi)) {
|
||||
DRM_DEV_ERROR(dev, "fail to create dsi device.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dsi->lanes = 4;
|
||||
dsi->lanes = ctx->pdata.mipi_lanes;
|
||||
dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
|
||||
MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
|
||||
MIPI_DSI_MODE_VIDEO_HSE;
|
||||
|
||||
if (mipi_dsi_attach(dsi) < 0) {
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(dev, "fail to attach dsi to host.\n");
|
||||
mipi_dsi_device_unregister(dsi);
|
||||
return -EINVAL;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ctx->dsi = dsi;
|
||||
|
@ -1350,16 +1688,6 @@ static int anx7625_attach_dsi(struct anx7625_data *ctx)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void anx7625_bridge_detach(struct drm_bridge *bridge)
|
||||
{
|
||||
struct anx7625_data *ctx = bridge_to_anx7625(bridge);
|
||||
|
||||
if (ctx->dsi) {
|
||||
mipi_dsi_detach(ctx->dsi);
|
||||
mipi_dsi_device_unregister(ctx->dsi);
|
||||
}
|
||||
}
|
||||
|
||||
static int anx7625_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
|
@ -1376,12 +1704,6 @@ static int anx7625_bridge_attach(struct drm_bridge *bridge,
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
err = anx7625_attach_dsi(ctx);
|
||||
if (err) {
|
||||
DRM_DEV_ERROR(dev, "Fail to attach to dsi : %d\n", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ctx->pdata.panel_bridge) {
|
||||
err = drm_bridge_attach(bridge->encoder,
|
||||
ctx->pdata.panel_bridge,
|
||||
|
@ -1475,6 +1797,10 @@ static bool anx7625_bridge_mode_fixup(struct drm_bridge *bridge,
|
|||
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "drm mode fixup set\n");
|
||||
|
||||
/* No need fixup for external monitor */
|
||||
if (!ctx->pdata.panel_bridge)
|
||||
return true;
|
||||
|
||||
hsync = mode->hsync_end - mode->hsync_start;
|
||||
hfp = mode->hsync_start - mode->hdisplay;
|
||||
hbp = mode->htotal - mode->hsync_end;
|
||||
|
@ -1624,7 +1950,6 @@ static struct edid *anx7625_bridge_get_edid(struct drm_bridge *bridge,
|
|||
|
||||
static const struct drm_bridge_funcs anx7625_bridge_funcs = {
|
||||
.attach = anx7625_bridge_attach,
|
||||
.detach = anx7625_bridge_detach,
|
||||
.disable = anx7625_bridge_disable,
|
||||
.mode_valid = anx7625_bridge_mode_valid,
|
||||
.mode_set = anx7625_bridge_mode_set,
|
||||
|
@ -1851,14 +2176,39 @@ static int anx7625_i2c_probe(struct i2c_client *client,
|
|||
|
||||
platform->bridge.funcs = &anx7625_bridge_funcs;
|
||||
platform->bridge.of_node = client->dev.of_node;
|
||||
platform->bridge.ops = DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD;
|
||||
platform->bridge.type = DRM_MODE_CONNECTOR_eDP;
|
||||
platform->bridge.ops = DRM_BRIDGE_OP_EDID;
|
||||
if (!platform->pdata.panel_bridge)
|
||||
platform->bridge.ops |= DRM_BRIDGE_OP_HPD |
|
||||
DRM_BRIDGE_OP_DETECT;
|
||||
platform->bridge.type = platform->pdata.panel_bridge ?
|
||||
DRM_MODE_CONNECTOR_eDP :
|
||||
DRM_MODE_CONNECTOR_DisplayPort;
|
||||
|
||||
drm_bridge_add(&platform->bridge);
|
||||
|
||||
if (!platform->pdata.is_dpi) {
|
||||
ret = anx7625_attach_dsi(platform);
|
||||
if (ret) {
|
||||
DRM_DEV_ERROR(dev, "Fail to attach to dsi : %d\n", ret);
|
||||
goto unregister_bridge;
|
||||
}
|
||||
}
|
||||
|
||||
if (platform->pdata.audio_en)
|
||||
anx7625_register_audio(dev, platform);
|
||||
|
||||
DRM_DEV_DEBUG_DRIVER(dev, "probe done\n");
|
||||
|
||||
return 0;
|
||||
|
||||
unregister_bridge:
|
||||
drm_bridge_remove(&platform->bridge);
|
||||
|
||||
if (!platform->pdata.low_power_mode)
|
||||
pm_runtime_put_sync_suspend(&client->dev);
|
||||
|
||||
anx7625_unregister_i2c_dummy_clients(platform);
|
||||
|
||||
free_wq:
|
||||
if (platform->workqueue)
|
||||
destroy_workqueue(platform->workqueue);
|
||||
|
@ -1883,6 +2233,9 @@ static int anx7625_i2c_remove(struct i2c_client *client)
|
|||
|
||||
anx7625_unregister_i2c_dummy_clients(platform);
|
||||
|
||||
if (platform->pdata.audio_en)
|
||||
anx7625_unregister_audio(platform);
|
||||
|
||||
kfree(platform);
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -111,6 +111,7 @@
|
|||
#define AUDIO_CHANNEL_STATUS_6 0xd5
|
||||
#define TDM_SLAVE_MODE 0x10
|
||||
#define I2S_SLAVE_MODE 0x08
|
||||
#define AUDIO_LAYOUT 0x01
|
||||
|
||||
#define AUDIO_CONTROL_REGISTER 0xe6
|
||||
#define TDM_TIMING_MODE 0x08
|
||||
|
@ -141,12 +142,20 @@
|
|||
#define HORIZONTAL_BACK_PORCH_H 0x22 /* Bit[7:4] are reserved */
|
||||
|
||||
/******** END of I2C Address 0x72 *********/
|
||||
|
||||
/***************************************************************/
|
||||
/* Register definition of device address 0x7a */
|
||||
#define DP_TX_SWING_REG_CNT 0x14
|
||||
#define DP_TX_LANE0_SWING_REG0 0x00
|
||||
#define DP_TX_LANE1_SWING_REG0 0x14
|
||||
/******** END of I2C Address 0x7a *********/
|
||||
|
||||
/***************************************************************/
|
||||
/* Register definition of device address 0x7e */
|
||||
|
||||
#define I2C_ADDR_7E_FLASH_CONTROLLER 0x7E
|
||||
|
||||
#define FLASH_LOAD_STA 0x05
|
||||
#define FLASH_LOAD_STA 0x05
|
||||
#define FLASH_LOAD_STA_CHK BIT(7)
|
||||
|
||||
#define XTAL_FRQ_SEL 0x3F
|
||||
|
@ -349,12 +358,21 @@ struct s_edid_data {
|
|||
|
||||
/***************** Display End *****************/
|
||||
|
||||
#define MAX_LANES_SUPPORT 4
|
||||
|
||||
struct anx7625_platform_data {
|
||||
struct gpio_desc *gpio_p_on;
|
||||
struct gpio_desc *gpio_reset;
|
||||
struct regulator_bulk_data supplies[3];
|
||||
struct drm_bridge *panel_bridge;
|
||||
int intp_irq;
|
||||
int is_dpi;
|
||||
int mipi_lanes;
|
||||
int audio_en;
|
||||
int dp_lane0_swing_reg_cnt;
|
||||
int lane0_reg_data[DP_TX_SWING_REG_CNT];
|
||||
int dp_lane1_swing_reg_cnt;
|
||||
int lane1_reg_data[DP_TX_SWING_REG_CNT];
|
||||
u32 low_power_mode;
|
||||
struct device_node *mipi_host_node;
|
||||
};
|
||||
|
@ -371,6 +389,7 @@ struct anx7625_i2c_client {
|
|||
|
||||
struct anx7625_data {
|
||||
struct anx7625_platform_data pdata;
|
||||
struct platform_device *audio_pdev;
|
||||
int hpd_status;
|
||||
int hpd_high_cnt;
|
||||
/* Lock for work queue */
|
||||
|
@ -379,6 +398,8 @@ struct anx7625_data {
|
|||
struct anx7625_i2c_client i2c;
|
||||
struct i2c_client *last_client;
|
||||
struct s_edid_data slimport_edid_p;
|
||||
struct device *codec_dev;
|
||||
hdmi_codec_plugged_cb plugged_cb;
|
||||
struct work_struct work;
|
||||
struct workqueue_struct *workqueue;
|
||||
char edid_block;
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_bridge.h>
|
||||
#include <drm/drm_edid.h>
|
||||
|
||||
|
@ -87,10 +88,95 @@ static struct edid *display_connector_get_edid(struct drm_bridge *bridge,
|
|||
return drm_get_edid(connector, conn->bridge.ddc);
|
||||
}
|
||||
|
||||
/*
|
||||
* Since this bridge is tied to the connector, it acts like a passthrough,
|
||||
* so concerning the output bus formats, either pass the bus formats from the
|
||||
* previous bridge or return fallback data like done in the bridge function:
|
||||
* drm_atomic_bridge_chain_select_bus_fmts().
|
||||
* This supports negotiation if the bridge chain has all bits in place.
|
||||
*/
|
||||
static u32 *display_connector_get_output_bus_fmts(struct drm_bridge *bridge,
|
||||
struct drm_bridge_state *bridge_state,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state,
|
||||
unsigned int *num_output_fmts)
|
||||
{
|
||||
struct drm_bridge *prev_bridge = drm_bridge_get_prev_bridge(bridge);
|
||||
struct drm_bridge_state *prev_bridge_state;
|
||||
|
||||
if (!prev_bridge || !prev_bridge->funcs->atomic_get_output_bus_fmts) {
|
||||
struct drm_connector *conn = conn_state->connector;
|
||||
u32 *out_bus_fmts;
|
||||
|
||||
*num_output_fmts = 1;
|
||||
out_bus_fmts = kmalloc(sizeof(*out_bus_fmts), GFP_KERNEL);
|
||||
if (!out_bus_fmts)
|
||||
return NULL;
|
||||
|
||||
if (conn->display_info.num_bus_formats &&
|
||||
conn->display_info.bus_formats)
|
||||
out_bus_fmts[0] = conn->display_info.bus_formats[0];
|
||||
else
|
||||
out_bus_fmts[0] = MEDIA_BUS_FMT_FIXED;
|
||||
|
||||
return out_bus_fmts;
|
||||
}
|
||||
|
||||
prev_bridge_state = drm_atomic_get_new_bridge_state(crtc_state->state,
|
||||
prev_bridge);
|
||||
|
||||
return prev_bridge->funcs->atomic_get_output_bus_fmts(prev_bridge, prev_bridge_state,
|
||||
crtc_state, conn_state,
|
||||
num_output_fmts);
|
||||
}
|
||||
|
||||
/*
|
||||
* Since this bridge is tied to the connector, it acts like a passthrough,
|
||||
* so concerning the input bus formats, either pass the bus formats from the
|
||||
* previous bridge or MEDIA_BUS_FMT_FIXED (like select_bus_fmt_recursive())
|
||||
* when atomic_get_input_bus_fmts is not supported.
|
||||
* This supports negotiation if the bridge chain has all bits in place.
|
||||
*/
|
||||
static u32 *display_connector_get_input_bus_fmts(struct drm_bridge *bridge,
|
||||
struct drm_bridge_state *bridge_state,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state,
|
||||
u32 output_fmt,
|
||||
unsigned int *num_input_fmts)
|
||||
{
|
||||
struct drm_bridge *prev_bridge = drm_bridge_get_prev_bridge(bridge);
|
||||
struct drm_bridge_state *prev_bridge_state;
|
||||
|
||||
if (!prev_bridge || !prev_bridge->funcs->atomic_get_input_bus_fmts) {
|
||||
u32 *in_bus_fmts;
|
||||
|
||||
*num_input_fmts = 1;
|
||||
in_bus_fmts = kmalloc(sizeof(*in_bus_fmts), GFP_KERNEL);
|
||||
if (!in_bus_fmts)
|
||||
return NULL;
|
||||
|
||||
in_bus_fmts[0] = MEDIA_BUS_FMT_FIXED;
|
||||
|
||||
return in_bus_fmts;
|
||||
}
|
||||
|
||||
prev_bridge_state = drm_atomic_get_new_bridge_state(crtc_state->state,
|
||||
prev_bridge);
|
||||
|
||||
return prev_bridge->funcs->atomic_get_input_bus_fmts(prev_bridge, prev_bridge_state,
|
||||
crtc_state, conn_state, output_fmt,
|
||||
num_input_fmts);
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs display_connector_bridge_funcs = {
|
||||
.attach = display_connector_attach,
|
||||
.detect = display_connector_detect,
|
||||
.get_edid = display_connector_get_edid,
|
||||
.atomic_get_output_bus_fmts = display_connector_get_output_bus_fmts,
|
||||
.atomic_get_input_bus_fmts = display_connector_get_input_bus_fmts,
|
||||
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
||||
.atomic_reset = drm_atomic_helper_bridge_reset,
|
||||
};
|
||||
|
||||
static irqreturn_t display_connector_hpd_irq(int irq, void *arg)
|
||||
|
@ -107,7 +193,7 @@ static int display_connector_probe(struct platform_device *pdev)
|
|||
{
|
||||
struct display_connector *conn;
|
||||
unsigned int type;
|
||||
const char *label;
|
||||
const char *label = NULL;
|
||||
int ret;
|
||||
|
||||
conn = devm_kzalloc(&pdev->dev, sizeof(*conn), GFP_KERNEL);
|
||||
|
|
|
@ -472,11 +472,11 @@ static int lt8912_attach_dsi(struct lt8912 *lt)
|
|||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
dsi = mipi_dsi_device_register_full(host, &info);
|
||||
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
|
||||
if (IS_ERR(dsi)) {
|
||||
ret = PTR_ERR(dsi);
|
||||
dev_err(dev, "failed to create dsi device (%d)\n", ret);
|
||||
goto err_dsi_device;
|
||||
return ret;
|
||||
}
|
||||
|
||||
lt->dsi = dsi;
|
||||
|
@ -489,24 +489,13 @@ static int lt8912_attach_dsi(struct lt8912 *lt)
|
|||
MIPI_DSI_MODE_LPM |
|
||||
MIPI_DSI_MODE_NO_EOT_PACKET;
|
||||
|
||||
ret = mipi_dsi_attach(dsi);
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to attach dsi to host\n");
|
||||
goto err_dsi_attach;
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_dsi_attach:
|
||||
mipi_dsi_device_unregister(dsi);
|
||||
err_dsi_device:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void lt8912_detach_dsi(struct lt8912 *lt)
|
||||
{
|
||||
mipi_dsi_detach(lt->dsi);
|
||||
mipi_dsi_device_unregister(lt->dsi);
|
||||
}
|
||||
|
||||
static int lt8912_bridge_connector_init(struct drm_bridge *bridge)
|
||||
|
@ -555,10 +544,6 @@ static int lt8912_bridge_attach(struct drm_bridge *bridge,
|
|||
if (ret)
|
||||
goto error;
|
||||
|
||||
ret = lt8912_attach_dsi(lt);
|
||||
if (ret)
|
||||
goto error;
|
||||
|
||||
lt->is_attached = true;
|
||||
|
||||
return 0;
|
||||
|
@ -573,7 +558,6 @@ static void lt8912_bridge_detach(struct drm_bridge *bridge)
|
|||
struct lt8912 *lt = bridge_to_lt8912(bridge);
|
||||
|
||||
if (lt->is_attached) {
|
||||
lt8912_detach_dsi(lt);
|
||||
lt8912_hard_power_off(lt);
|
||||
drm_connector_unregister(<->connector);
|
||||
drm_connector_cleanup(<->connector);
|
||||
|
@ -718,8 +702,15 @@ static int lt8912_probe(struct i2c_client *client,
|
|||
|
||||
drm_bridge_add(<->bridge);
|
||||
|
||||
ret = lt8912_attach_dsi(lt);
|
||||
if (ret)
|
||||
goto err_attach;
|
||||
|
||||
return 0;
|
||||
|
||||
err_attach:
|
||||
drm_bridge_remove(<->bridge);
|
||||
lt8912_free_i2c(lt);
|
||||
err_i2c:
|
||||
lt8912_put_dt(lt);
|
||||
err_dt_parse:
|
||||
|
|
|
@ -760,6 +760,7 @@ static struct mipi_dsi_device *lt9611_attach_dsi(struct lt9611 *lt9611,
|
|||
const struct mipi_dsi_device_info info = { "lt9611", 0, NULL };
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct mipi_dsi_host *host;
|
||||
struct device *dev = lt9611->dev;
|
||||
int ret;
|
||||
|
||||
host = of_find_mipi_dsi_host_by_node(dsi_node);
|
||||
|
@ -768,7 +769,7 @@ static struct mipi_dsi_device *lt9611_attach_dsi(struct lt9611 *lt9611,
|
|||
return ERR_PTR(-EPROBE_DEFER);
|
||||
}
|
||||
|
||||
dsi = mipi_dsi_device_register_full(host, &info);
|
||||
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
|
||||
if (IS_ERR(dsi)) {
|
||||
dev_err(lt9611->dev, "failed to create dsi device\n");
|
||||
return dsi;
|
||||
|
@ -779,29 +780,15 @@ static struct mipi_dsi_device *lt9611_attach_dsi(struct lt9611 *lt9611,
|
|||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
|
||||
MIPI_DSI_MODE_VIDEO_HSE;
|
||||
|
||||
ret = mipi_dsi_attach(dsi);
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
if (ret < 0) {
|
||||
dev_err(lt9611->dev, "failed to attach dsi to host\n");
|
||||
mipi_dsi_device_unregister(dsi);
|
||||
dev_err(dev, "failed to attach dsi to host\n");
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
return dsi;
|
||||
}
|
||||
|
||||
static void lt9611_bridge_detach(struct drm_bridge *bridge)
|
||||
{
|
||||
struct lt9611 *lt9611 = bridge_to_lt9611(bridge);
|
||||
|
||||
if (lt9611->dsi1) {
|
||||
mipi_dsi_detach(lt9611->dsi1);
|
||||
mipi_dsi_device_unregister(lt9611->dsi1);
|
||||
}
|
||||
|
||||
mipi_dsi_detach(lt9611->dsi0);
|
||||
mipi_dsi_device_unregister(lt9611->dsi0);
|
||||
}
|
||||
|
||||
static int lt9611_connector_init(struct drm_bridge *bridge, struct lt9611 *lt9611)
|
||||
{
|
||||
int ret;
|
||||
|
@ -838,28 +825,7 @@ static int lt9611_bridge_attach(struct drm_bridge *bridge,
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Attach primary DSI */
|
||||
lt9611->dsi0 = lt9611_attach_dsi(lt9611, lt9611->dsi0_node);
|
||||
if (IS_ERR(lt9611->dsi0))
|
||||
return PTR_ERR(lt9611->dsi0);
|
||||
|
||||
/* Attach secondary DSI, if specified */
|
||||
if (lt9611->dsi1_node) {
|
||||
lt9611->dsi1 = lt9611_attach_dsi(lt9611, lt9611->dsi1_node);
|
||||
if (IS_ERR(lt9611->dsi1)) {
|
||||
ret = PTR_ERR(lt9611->dsi1);
|
||||
goto err_unregister_dsi0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister_dsi0:
|
||||
lt9611_bridge_detach(bridge);
|
||||
drm_connector_cleanup(<9611->connector);
|
||||
mipi_dsi_device_unregister(lt9611->dsi0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum drm_mode_status lt9611_bridge_mode_valid(struct drm_bridge *bridge,
|
||||
|
@ -952,7 +918,6 @@ static void lt9611_bridge_hpd_enable(struct drm_bridge *bridge)
|
|||
|
||||
static const struct drm_bridge_funcs lt9611_bridge_funcs = {
|
||||
.attach = lt9611_bridge_attach,
|
||||
.detach = lt9611_bridge_detach,
|
||||
.mode_valid = lt9611_bridge_mode_valid,
|
||||
.enable = lt9611_bridge_enable,
|
||||
.disable = lt9611_bridge_disable,
|
||||
|
@ -1181,10 +1146,29 @@ static int lt9611_probe(struct i2c_client *client,
|
|||
|
||||
drm_bridge_add(<9611->bridge);
|
||||
|
||||
/* Attach primary DSI */
|
||||
lt9611->dsi0 = lt9611_attach_dsi(lt9611, lt9611->dsi0_node);
|
||||
if (IS_ERR(lt9611->dsi0)) {
|
||||
ret = PTR_ERR(lt9611->dsi0);
|
||||
goto err_remove_bridge;
|
||||
}
|
||||
|
||||
/* Attach secondary DSI, if specified */
|
||||
if (lt9611->dsi1_node) {
|
||||
lt9611->dsi1 = lt9611_attach_dsi(lt9611, lt9611->dsi1_node);
|
||||
if (IS_ERR(lt9611->dsi1)) {
|
||||
ret = PTR_ERR(lt9611->dsi1);
|
||||
goto err_remove_bridge;
|
||||
}
|
||||
}
|
||||
|
||||
lt9611_enable_hpd_interrupts(lt9611);
|
||||
|
||||
return lt9611_audio_init(dev, lt9611);
|
||||
|
||||
err_remove_bridge:
|
||||
drm_bridge_remove(<9611->bridge);
|
||||
|
||||
err_disable_regulators:
|
||||
regulator_bulk_disable(ARRAY_SIZE(lt9611->supplies), lt9611->supplies);
|
||||
|
||||
|
|
|
@ -258,17 +258,18 @@ static struct mipi_dsi_device *lt9611uxc_attach_dsi(struct lt9611uxc *lt9611uxc,
|
|||
const struct mipi_dsi_device_info info = { "lt9611uxc", 0, NULL };
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct mipi_dsi_host *host;
|
||||
struct device *dev = lt9611uxc->dev;
|
||||
int ret;
|
||||
|
||||
host = of_find_mipi_dsi_host_by_node(dsi_node);
|
||||
if (!host) {
|
||||
dev_err(lt9611uxc->dev, "failed to find dsi host\n");
|
||||
dev_err(dev, "failed to find dsi host\n");
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
}
|
||||
|
||||
dsi = mipi_dsi_device_register_full(host, &info);
|
||||
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
|
||||
if (IS_ERR(dsi)) {
|
||||
dev_err(lt9611uxc->dev, "failed to create dsi device\n");
|
||||
dev_err(dev, "failed to create dsi device\n");
|
||||
return dsi;
|
||||
}
|
||||
|
||||
|
@ -277,10 +278,9 @@ static struct mipi_dsi_device *lt9611uxc_attach_dsi(struct lt9611uxc *lt9611uxc,
|
|||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
|
||||
MIPI_DSI_MODE_VIDEO_HSE;
|
||||
|
||||
ret = mipi_dsi_attach(dsi);
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
if (ret < 0) {
|
||||
dev_err(lt9611uxc->dev, "failed to attach dsi to host\n");
|
||||
mipi_dsi_device_unregister(dsi);
|
||||
dev_err(dev, "failed to attach dsi to host\n");
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
|
@ -355,19 +355,6 @@ static int lt9611uxc_connector_init(struct drm_bridge *bridge, struct lt9611uxc
|
|||
return drm_connector_attach_encoder(<9611uxc->connector, bridge->encoder);
|
||||
}
|
||||
|
||||
static void lt9611uxc_bridge_detach(struct drm_bridge *bridge)
|
||||
{
|
||||
struct lt9611uxc *lt9611uxc = bridge_to_lt9611uxc(bridge);
|
||||
|
||||
if (lt9611uxc->dsi1) {
|
||||
mipi_dsi_detach(lt9611uxc->dsi1);
|
||||
mipi_dsi_device_unregister(lt9611uxc->dsi1);
|
||||
}
|
||||
|
||||
mipi_dsi_detach(lt9611uxc->dsi0);
|
||||
mipi_dsi_device_unregister(lt9611uxc->dsi0);
|
||||
}
|
||||
|
||||
static int lt9611uxc_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
|
@ -380,27 +367,7 @@ static int lt9611uxc_bridge_attach(struct drm_bridge *bridge,
|
|||
return ret;
|
||||
}
|
||||
|
||||
/* Attach primary DSI */
|
||||
lt9611uxc->dsi0 = lt9611uxc_attach_dsi(lt9611uxc, lt9611uxc->dsi0_node);
|
||||
if (IS_ERR(lt9611uxc->dsi0))
|
||||
return PTR_ERR(lt9611uxc->dsi0);
|
||||
|
||||
/* Attach secondary DSI, if specified */
|
||||
if (lt9611uxc->dsi1_node) {
|
||||
lt9611uxc->dsi1 = lt9611uxc_attach_dsi(lt9611uxc, lt9611uxc->dsi1_node);
|
||||
if (IS_ERR(lt9611uxc->dsi1)) {
|
||||
ret = PTR_ERR(lt9611uxc->dsi1);
|
||||
goto err_unregister_dsi0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_unregister_dsi0:
|
||||
mipi_dsi_detach(lt9611uxc->dsi0);
|
||||
mipi_dsi_device_unregister(lt9611uxc->dsi0);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum drm_mode_status
|
||||
|
@ -544,7 +511,6 @@ static struct edid *lt9611uxc_bridge_get_edid(struct drm_bridge *bridge,
|
|||
|
||||
static const struct drm_bridge_funcs lt9611uxc_bridge_funcs = {
|
||||
.attach = lt9611uxc_bridge_attach,
|
||||
.detach = lt9611uxc_bridge_detach,
|
||||
.mode_valid = lt9611uxc_bridge_mode_valid,
|
||||
.mode_set = lt9611uxc_bridge_mode_set,
|
||||
.detect = lt9611uxc_bridge_detect,
|
||||
|
@ -980,8 +946,27 @@ retry:
|
|||
|
||||
drm_bridge_add(<9611uxc->bridge);
|
||||
|
||||
/* Attach primary DSI */
|
||||
lt9611uxc->dsi0 = lt9611uxc_attach_dsi(lt9611uxc, lt9611uxc->dsi0_node);
|
||||
if (IS_ERR(lt9611uxc->dsi0)) {
|
||||
ret = PTR_ERR(lt9611uxc->dsi0);
|
||||
goto err_remove_bridge;
|
||||
}
|
||||
|
||||
/* Attach secondary DSI, if specified */
|
||||
if (lt9611uxc->dsi1_node) {
|
||||
lt9611uxc->dsi1 = lt9611uxc_attach_dsi(lt9611uxc, lt9611uxc->dsi1_node);
|
||||
if (IS_ERR(lt9611uxc->dsi1)) {
|
||||
ret = PTR_ERR(lt9611uxc->dsi1);
|
||||
goto err_remove_bridge;
|
||||
}
|
||||
}
|
||||
|
||||
return lt9611uxc_audio_init(dev, lt9611uxc);
|
||||
|
||||
err_remove_bridge:
|
||||
drm_bridge_remove(<9611uxc->bridge);
|
||||
|
||||
err_disable_regulators:
|
||||
regulator_bulk_disable(ARRAY_SIZE(lt9611uxc->supplies), lt9611uxc->supplies);
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_bridge.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_panel.h>
|
||||
|
||||
struct lvds_codec {
|
||||
|
@ -118,7 +119,6 @@ static int lvds_codec_probe(struct platform_device *pdev)
|
|||
struct device_node *bus_node;
|
||||
struct drm_panel *panel;
|
||||
struct lvds_codec *lvds_codec;
|
||||
const char *mapping;
|
||||
int ret;
|
||||
|
||||
lvds_codec = devm_kzalloc(dev, sizeof(*lvds_codec), GFP_KERNEL);
|
||||
|
@ -174,22 +174,15 @@ static int lvds_codec_probe(struct platform_device *pdev)
|
|||
return -ENXIO;
|
||||
}
|
||||
|
||||
ret = of_property_read_string(bus_node, "data-mapping",
|
||||
&mapping);
|
||||
ret = drm_of_lvds_get_data_mapping(bus_node);
|
||||
of_node_put(bus_node);
|
||||
if (ret < 0) {
|
||||
if (ret == -ENODEV) {
|
||||
dev_warn(dev, "missing 'data-mapping' DT property\n");
|
||||
} else if (ret) {
|
||||
dev_err(dev, "invalid 'data-mapping' DT property\n");
|
||||
return ret;
|
||||
} else {
|
||||
if (!strcmp(mapping, "jeida-18")) {
|
||||
lvds_codec->bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG;
|
||||
} else if (!strcmp(mapping, "jeida-24")) {
|
||||
lvds_codec->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
|
||||
} else if (!strcmp(mapping, "vesa-24")) {
|
||||
lvds_codec->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
|
||||
} else {
|
||||
dev_err(dev, "invalid 'data-mapping' DT property\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
lvds_codec->bus_format = ret;
|
||||
lvds_codec->bridge.funcs = &funcs_decoder;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,10 +9,12 @@
|
|||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <drm/drm_bridge.h>
|
||||
#include <drm/drm_dp_aux_bus.h>
|
||||
#include <drm/drm_dp_helper.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_of.h>
|
||||
|
@ -100,7 +102,7 @@ struct ps8640 {
|
|||
struct regulator_bulk_data supplies[2];
|
||||
struct gpio_desc *gpio_reset;
|
||||
struct gpio_desc *gpio_powerdown;
|
||||
bool powered;
|
||||
bool pre_enabled;
|
||||
};
|
||||
|
||||
static const struct regmap_config ps8640_regmap_config[] = {
|
||||
|
@ -148,8 +150,46 @@ static inline struct ps8640 *aux_to_ps8640(struct drm_dp_aux *aux)
|
|||
return container_of(aux, struct ps8640, aux);
|
||||
}
|
||||
|
||||
static ssize_t ps8640_aux_transfer(struct drm_dp_aux *aux,
|
||||
struct drm_dp_aux_msg *msg)
|
||||
static bool ps8640_of_panel_on_aux_bus(struct device *dev)
|
||||
{
|
||||
struct device_node *bus, *panel;
|
||||
|
||||
bus = of_get_child_by_name(dev->of_node, "aux-bus");
|
||||
if (!bus)
|
||||
return false;
|
||||
|
||||
panel = of_get_child_by_name(bus, "panel");
|
||||
of_node_put(bus);
|
||||
if (!panel)
|
||||
return false;
|
||||
of_node_put(panel);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static int ps8640_ensure_hpd(struct ps8640 *ps_bridge)
|
||||
{
|
||||
struct regmap *map = ps_bridge->regmap[PAGE2_TOP_CNTL];
|
||||
struct device *dev = &ps_bridge->page[PAGE2_TOP_CNTL]->dev;
|
||||
int status;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Apparently something about the firmware in the chip signals that
|
||||
* HPD goes high by reporting GPIO9 as high (even though HPD isn't
|
||||
* actually connected to GPIO9).
|
||||
*/
|
||||
ret = regmap_read_poll_timeout(map, PAGE2_GPIO_H, status,
|
||||
status & PS_GPIO9, 20 * 1000, 200 * 1000);
|
||||
|
||||
if (ret < 0)
|
||||
dev_warn(dev, "HPD didn't go high: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static ssize_t ps8640_aux_transfer_msg(struct drm_dp_aux *aux,
|
||||
struct drm_dp_aux_msg *msg)
|
||||
{
|
||||
struct ps8640 *ps_bridge = aux_to_ps8640(aux);
|
||||
struct regmap *map = ps_bridge->regmap[PAGE0_DP_CNTL];
|
||||
|
@ -274,38 +314,49 @@ static ssize_t ps8640_aux_transfer(struct drm_dp_aux *aux,
|
|||
return len;
|
||||
}
|
||||
|
||||
static int ps8640_bridge_vdo_control(struct ps8640 *ps_bridge,
|
||||
const enum ps8640_vdo_control ctrl)
|
||||
static ssize_t ps8640_aux_transfer(struct drm_dp_aux *aux,
|
||||
struct drm_dp_aux_msg *msg)
|
||||
{
|
||||
struct ps8640 *ps_bridge = aux_to_ps8640(aux);
|
||||
struct device *dev = &ps_bridge->page[PAGE0_DP_CNTL]->dev;
|
||||
int ret;
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
ret = ps8640_ensure_hpd(ps_bridge);
|
||||
if (!ret)
|
||||
ret = ps8640_aux_transfer_msg(aux, msg);
|
||||
pm_runtime_mark_last_busy(dev);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ps8640_bridge_vdo_control(struct ps8640 *ps_bridge,
|
||||
const enum ps8640_vdo_control ctrl)
|
||||
{
|
||||
struct regmap *map = ps_bridge->regmap[PAGE3_DSI_CNTL1];
|
||||
struct device *dev = &ps_bridge->page[PAGE3_DSI_CNTL1]->dev;
|
||||
u8 vdo_ctrl_buf[] = { VDO_CTL_ADD, ctrl };
|
||||
int ret;
|
||||
|
||||
ret = regmap_bulk_write(map, PAGE3_SET_ADD,
|
||||
vdo_ctrl_buf, sizeof(vdo_ctrl_buf));
|
||||
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("failed to %sable VDO: %d\n",
|
||||
ctrl == ENABLE ? "en" : "dis", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (ret < 0)
|
||||
dev_err(dev, "failed to %sable VDO: %d\n",
|
||||
ctrl == ENABLE ? "en" : "dis", ret);
|
||||
}
|
||||
|
||||
static void ps8640_bridge_poweron(struct ps8640 *ps_bridge)
|
||||
static int __maybe_unused ps8640_resume(struct device *dev)
|
||||
{
|
||||
struct regmap *map = ps_bridge->regmap[PAGE2_TOP_CNTL];
|
||||
int ret, status;
|
||||
|
||||
if (ps_bridge->powered)
|
||||
return;
|
||||
struct ps8640 *ps_bridge = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(ps_bridge->supplies),
|
||||
ps_bridge->supplies);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("cannot enable regulators %d\n", ret);
|
||||
return;
|
||||
dev_err(dev, "cannot enable regulators %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
gpiod_set_value(ps_bridge->gpio_powerdown, 0);
|
||||
|
@ -314,20 +365,47 @@ static void ps8640_bridge_poweron(struct ps8640 *ps_bridge)
|
|||
gpiod_set_value(ps_bridge->gpio_reset, 0);
|
||||
|
||||
/*
|
||||
* Wait for the ps8640 embedded MCU to be ready
|
||||
* First wait 200ms and then check the MCU ready flag every 20ms
|
||||
* Mystery 200 ms delay for the "MCU to be ready". It's unclear if
|
||||
* this is truly necessary since the MCU will already signal that
|
||||
* things are "good to go" by signaling HPD on "gpio 9". See
|
||||
* ps8640_ensure_hpd(). For now we'll keep this mystery delay just in
|
||||
* case.
|
||||
*/
|
||||
msleep(200);
|
||||
|
||||
ret = regmap_read_poll_timeout(map, PAGE2_GPIO_H, status,
|
||||
status & PS_GPIO9, 20 * 1000, 200 * 1000);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("failed read PAGE2_GPIO_H: %d\n", ret);
|
||||
goto err_regulators_disable;
|
||||
}
|
||||
static int __maybe_unused ps8640_suspend(struct device *dev)
|
||||
{
|
||||
struct ps8640 *ps_bridge = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
msleep(50);
|
||||
gpiod_set_value(ps_bridge->gpio_reset, 1);
|
||||
gpiod_set_value(ps_bridge->gpio_powerdown, 1);
|
||||
ret = regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies),
|
||||
ps_bridge->supplies);
|
||||
if (ret < 0)
|
||||
dev_err(dev, "cannot disable regulators %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops ps8640_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(ps8640_suspend, ps8640_resume, NULL)
|
||||
SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend,
|
||||
pm_runtime_force_resume)
|
||||
};
|
||||
|
||||
static void ps8640_pre_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
|
||||
struct regmap *map = ps_bridge->regmap[PAGE2_TOP_CNTL];
|
||||
struct device *dev = &ps_bridge->page[PAGE0_DP_CNTL]->dev;
|
||||
int ret;
|
||||
|
||||
pm_runtime_get_sync(dev);
|
||||
ps8640_ensure_hpd(ps_bridge);
|
||||
|
||||
/*
|
||||
* The Manufacturer Command Set (MCS) is a device dependent interface
|
||||
|
@ -338,62 +416,27 @@ static void ps8640_bridge_poweron(struct ps8640 *ps_bridge)
|
|||
*/
|
||||
|
||||
ret = regmap_update_bits(map, PAGE2_MCS_EN, MCS_EN, 0);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("failed write PAGE2_MCS_EN: %d\n", ret);
|
||||
goto err_regulators_disable;
|
||||
}
|
||||
if (ret < 0)
|
||||
dev_warn(dev, "failed write PAGE2_MCS_EN: %d\n", ret);
|
||||
|
||||
/* Switch access edp panel's edid through i2c */
|
||||
ret = regmap_write(map, PAGE2_I2C_BYPASS, I2C_BYPASS_EN);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("failed write PAGE2_I2C_BYPASS: %d\n", ret);
|
||||
goto err_regulators_disable;
|
||||
}
|
||||
|
||||
ps_bridge->powered = true;
|
||||
|
||||
return;
|
||||
|
||||
err_regulators_disable:
|
||||
regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies),
|
||||
ps_bridge->supplies);
|
||||
}
|
||||
|
||||
static void ps8640_bridge_poweroff(struct ps8640 *ps_bridge)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!ps_bridge->powered)
|
||||
return;
|
||||
|
||||
gpiod_set_value(ps_bridge->gpio_reset, 1);
|
||||
gpiod_set_value(ps_bridge->gpio_powerdown, 1);
|
||||
ret = regulator_bulk_disable(ARRAY_SIZE(ps_bridge->supplies),
|
||||
ps_bridge->supplies);
|
||||
if (ret < 0)
|
||||
DRM_ERROR("cannot disable regulators %d\n", ret);
|
||||
dev_warn(dev, "failed write PAGE2_MCS_EN: %d\n", ret);
|
||||
|
||||
ps_bridge->powered = false;
|
||||
}
|
||||
ps8640_bridge_vdo_control(ps_bridge, ENABLE);
|
||||
|
||||
static void ps8640_pre_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
|
||||
int ret;
|
||||
|
||||
ps8640_bridge_poweron(ps_bridge);
|
||||
|
||||
ret = ps8640_bridge_vdo_control(ps_bridge, ENABLE);
|
||||
if (ret < 0)
|
||||
ps8640_bridge_poweroff(ps_bridge);
|
||||
ps_bridge->pre_enabled = true;
|
||||
}
|
||||
|
||||
static void ps8640_post_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
|
||||
|
||||
ps_bridge->pre_enabled = false;
|
||||
|
||||
ps8640_bridge_vdo_control(ps_bridge, DISABLE);
|
||||
ps8640_bridge_poweroff(ps_bridge);
|
||||
pm_runtime_put_sync_suspend(&ps_bridge->page[PAGE0_DP_CNTL]->dev);
|
||||
}
|
||||
|
||||
static int ps8640_bridge_attach(struct drm_bridge *bridge,
|
||||
|
@ -401,68 +444,20 @@ static int ps8640_bridge_attach(struct drm_bridge *bridge,
|
|||
{
|
||||
struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
|
||||
struct device *dev = &ps_bridge->page[0]->dev;
|
||||
struct device_node *in_ep, *dsi_node;
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct mipi_dsi_host *host;
|
||||
int ret;
|
||||
const struct mipi_dsi_device_info info = { .type = "ps8640",
|
||||
.channel = 0,
|
||||
.node = NULL,
|
||||
};
|
||||
|
||||
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
|
||||
return -EINVAL;
|
||||
|
||||
/* port@0 is ps8640 dsi input port */
|
||||
in_ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
|
||||
if (!in_ep)
|
||||
return -ENODEV;
|
||||
|
||||
dsi_node = of_graph_get_remote_port_parent(in_ep);
|
||||
of_node_put(in_ep);
|
||||
if (!dsi_node)
|
||||
return -ENODEV;
|
||||
|
||||
host = of_find_mipi_dsi_host_by_node(dsi_node);
|
||||
of_node_put(dsi_node);
|
||||
if (!host)
|
||||
return -ENODEV;
|
||||
|
||||
dsi = mipi_dsi_device_register_full(host, &info);
|
||||
if (IS_ERR(dsi)) {
|
||||
dev_err(dev, "failed to create dsi device\n");
|
||||
ret = PTR_ERR(dsi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ps_bridge->dsi = dsi;
|
||||
|
||||
dsi->host = host;
|
||||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
|
||||
MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
|
||||
dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
dsi->lanes = NUM_MIPI_LANES;
|
||||
ret = mipi_dsi_attach(dsi);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to attach dsi device: %d\n", ret);
|
||||
goto err_dsi_attach;
|
||||
}
|
||||
|
||||
ret = drm_dp_aux_register(&ps_bridge->aux);
|
||||
if (ret) {
|
||||
dev_err(dev, "failed to register DP AUX channel: %d\n", ret);
|
||||
goto err_aux_register;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Attach the panel-bridge to the dsi bridge */
|
||||
return drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge,
|
||||
&ps_bridge->bridge, flags);
|
||||
|
||||
err_aux_register:
|
||||
mipi_dsi_detach(dsi);
|
||||
err_dsi_attach:
|
||||
mipi_dsi_device_unregister(dsi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ps8640_bridge_detach(struct drm_bridge *bridge)
|
||||
|
@ -474,7 +469,7 @@ static struct edid *ps8640_bridge_get_edid(struct drm_bridge *bridge,
|
|||
struct drm_connector *connector)
|
||||
{
|
||||
struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
|
||||
bool poweroff = !ps_bridge->powered;
|
||||
bool poweroff = !ps_bridge->pre_enabled;
|
||||
struct edid *edid;
|
||||
|
||||
/*
|
||||
|
@ -504,6 +499,12 @@ static struct edid *ps8640_bridge_get_edid(struct drm_bridge *bridge,
|
|||
return edid;
|
||||
}
|
||||
|
||||
static void ps8640_runtime_disable(void *data)
|
||||
{
|
||||
pm_runtime_dont_use_autosuspend(data);
|
||||
pm_runtime_disable(data);
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs ps8640_bridge_funcs = {
|
||||
.attach = ps8640_bridge_attach,
|
||||
.detach = ps8640_bridge_detach,
|
||||
|
@ -512,6 +513,53 @@ static const struct drm_bridge_funcs ps8640_bridge_funcs = {
|
|||
.pre_enable = ps8640_pre_enable,
|
||||
};
|
||||
|
||||
static int ps8640_bridge_host_attach(struct device *dev, struct ps8640 *ps_bridge)
|
||||
{
|
||||
struct device_node *in_ep, *dsi_node;
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct mipi_dsi_host *host;
|
||||
int ret;
|
||||
const struct mipi_dsi_device_info info = { .type = "ps8640",
|
||||
.channel = 0,
|
||||
.node = NULL,
|
||||
};
|
||||
|
||||
/* port@0 is ps8640 dsi input port */
|
||||
in_ep = of_graph_get_endpoint_by_regs(dev->of_node, 0, -1);
|
||||
if (!in_ep)
|
||||
return -ENODEV;
|
||||
|
||||
dsi_node = of_graph_get_remote_port_parent(in_ep);
|
||||
of_node_put(in_ep);
|
||||
if (!dsi_node)
|
||||
return -ENODEV;
|
||||
|
||||
host = of_find_mipi_dsi_host_by_node(dsi_node);
|
||||
of_node_put(dsi_node);
|
||||
if (!host)
|
||||
return -EPROBE_DEFER;
|
||||
|
||||
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
|
||||
if (IS_ERR(dsi)) {
|
||||
dev_err(dev, "failed to create dsi device\n");
|
||||
return PTR_ERR(dsi);
|
||||
}
|
||||
|
||||
ps_bridge->dsi = dsi;
|
||||
|
||||
dsi->host = host;
|
||||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
|
||||
MIPI_DSI_MODE_VIDEO_SYNC_PULSE;
|
||||
dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
dsi->lanes = NUM_MIPI_LANES;
|
||||
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ps8640_probe(struct i2c_client *client)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
|
@ -525,17 +573,6 @@ static int ps8640_probe(struct i2c_client *client)
|
|||
if (!ps_bridge)
|
||||
return -ENOMEM;
|
||||
|
||||
/* port@1 is ps8640 output port */
|
||||
ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!panel)
|
||||
return -ENODEV;
|
||||
|
||||
ps_bridge->panel_bridge = devm_drm_panel_bridge_add(dev, panel);
|
||||
if (IS_ERR(ps_bridge->panel_bridge))
|
||||
return PTR_ERR(ps_bridge->panel_bridge);
|
||||
|
||||
ps_bridge->supplies[0].supply = "vdd33";
|
||||
ps_bridge->supplies[1].supply = "vdd12";
|
||||
ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ps_bridge->supplies),
|
||||
|
@ -558,9 +595,16 @@ static int ps8640_probe(struct i2c_client *client)
|
|||
|
||||
ps_bridge->bridge.funcs = &ps8640_bridge_funcs;
|
||||
ps_bridge->bridge.of_node = dev->of_node;
|
||||
ps_bridge->bridge.ops = DRM_BRIDGE_OP_EDID;
|
||||
ps_bridge->bridge.type = DRM_MODE_CONNECTOR_eDP;
|
||||
|
||||
/*
|
||||
* In the device tree, if panel is listed under aux-bus of the bridge
|
||||
* node, panel driver should be able to retrieve EDID by itself using
|
||||
* aux-bus. So let's not set DRM_BRIDGE_OP_EDID here.
|
||||
*/
|
||||
if (!ps8640_of_panel_on_aux_bus(&client->dev))
|
||||
ps_bridge->bridge.ops = DRM_BRIDGE_OP_EDID;
|
||||
|
||||
ps_bridge->page[PAGE0_DP_CNTL] = client;
|
||||
|
||||
ps_bridge->regmap[PAGE0_DP_CNTL] = devm_regmap_init_i2c(client, ps8640_regmap_config);
|
||||
|
@ -587,9 +631,46 @@ static int ps8640_probe(struct i2c_client *client)
|
|||
ps_bridge->aux.transfer = ps8640_aux_transfer;
|
||||
drm_dp_aux_init(&ps_bridge->aux);
|
||||
|
||||
pm_runtime_enable(dev);
|
||||
/*
|
||||
* Powering on ps8640 takes ~300ms. To avoid wasting time on power
|
||||
* cycling ps8640 too often, set autosuspend_delay to 1000ms to ensure
|
||||
* the bridge wouldn't suspend in between each _aux_transfer_msg() call
|
||||
* during EDID read (~20ms in my experiment) and in between the last
|
||||
* _aux_transfer_msg() call during EDID read and the _pre_enable() call
|
||||
* (~100ms in my experiment).
|
||||
*/
|
||||
pm_runtime_set_autosuspend_delay(dev, 1000);
|
||||
pm_runtime_use_autosuspend(dev);
|
||||
pm_suspend_ignore_children(dev, true);
|
||||
ret = devm_add_action_or_reset(dev, ps8640_runtime_disable, dev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
devm_of_dp_aux_populate_ep_devices(&ps_bridge->aux);
|
||||
|
||||
/* port@1 is ps8640 output port */
|
||||
ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (!panel)
|
||||
return -ENODEV;
|
||||
|
||||
ps_bridge->panel_bridge = devm_drm_panel_bridge_add(dev, panel);
|
||||
if (IS_ERR(ps_bridge->panel_bridge))
|
||||
return PTR_ERR(ps_bridge->panel_bridge);
|
||||
|
||||
drm_bridge_add(&ps_bridge->bridge);
|
||||
|
||||
ret = ps8640_bridge_host_attach(dev, ps_bridge);
|
||||
if (ret)
|
||||
goto err_bridge_remove;
|
||||
|
||||
return 0;
|
||||
|
||||
err_bridge_remove:
|
||||
drm_bridge_remove(&ps_bridge->bridge);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ps8640_remove(struct i2c_client *client)
|
||||
|
@ -613,6 +694,7 @@ static struct i2c_driver ps8640_driver = {
|
|||
.driver = {
|
||||
.name = "ps8640",
|
||||
.of_match_table = ps8640_match,
|
||||
.pm = &ps8640_pm_ops,
|
||||
},
|
||||
};
|
||||
module_i2c_driver(ps8640_driver);
|
||||
|
|
|
@ -320,13 +320,17 @@ static int dw_hdmi_open(struct snd_pcm_substream *substream)
|
|||
struct snd_pcm_runtime *runtime = substream->runtime;
|
||||
struct snd_dw_hdmi *dw = substream->private_data;
|
||||
void __iomem *base = dw->data.base;
|
||||
u8 *eld;
|
||||
int ret;
|
||||
|
||||
runtime->hw = dw_hdmi_hw;
|
||||
|
||||
ret = snd_pcm_hw_constraint_eld(runtime, dw->data.eld);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
eld = dw->data.get_eld(dw->data.hdmi);
|
||||
if (eld) {
|
||||
ret = snd_pcm_hw_constraint_eld(runtime, eld);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = snd_pcm_limit_hw_rates(runtime);
|
||||
if (ret < 0)
|
||||
|
|
|
@ -9,15 +9,15 @@ struct dw_hdmi_audio_data {
|
|||
void __iomem *base;
|
||||
int irq;
|
||||
struct dw_hdmi *hdmi;
|
||||
u8 *eld;
|
||||
u8 *(*get_eld)(struct dw_hdmi *hdmi);
|
||||
};
|
||||
|
||||
struct dw_hdmi_i2s_audio_data {
|
||||
struct dw_hdmi *hdmi;
|
||||
u8 *eld;
|
||||
|
||||
void (*write)(struct dw_hdmi *hdmi, u8 val, int offset);
|
||||
u8 (*read)(struct dw_hdmi *hdmi, int offset);
|
||||
u8 *(*get_eld)(struct dw_hdmi *hdmi);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -135,8 +135,15 @@ static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf,
|
|||
size_t len)
|
||||
{
|
||||
struct dw_hdmi_i2s_audio_data *audio = data;
|
||||
u8 *eld;
|
||||
|
||||
eld = audio->get_eld(audio->hdmi);
|
||||
if (eld)
|
||||
memcpy(buf, eld, min_t(size_t, MAX_ELD_BYTES, len));
|
||||
else
|
||||
/* Pass en empty ELD if connector not available */
|
||||
memset(buf, 0, len);
|
||||
|
||||
memcpy(buf, audio->eld, min_t(size_t, MAX_ELD_BYTES, len));
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -757,6 +757,14 @@ static void hdmi_enable_audio_clk(struct dw_hdmi *hdmi, bool enable)
|
|||
hdmi_writeb(hdmi, hdmi->mc_clkdis, HDMI_MC_CLKDIS);
|
||||
}
|
||||
|
||||
static u8 *hdmi_audio_get_eld(struct dw_hdmi *hdmi)
|
||||
{
|
||||
if (!hdmi->curr_conn)
|
||||
return NULL;
|
||||
|
||||
return hdmi->curr_conn->eld;
|
||||
}
|
||||
|
||||
static void dw_hdmi_ahb_audio_enable(struct dw_hdmi *hdmi)
|
||||
{
|
||||
hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n);
|
||||
|
@ -3413,6 +3421,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
|
|||
hdmi->bridge.funcs = &dw_hdmi_bridge_funcs;
|
||||
hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID
|
||||
| DRM_BRIDGE_OP_HPD;
|
||||
hdmi->bridge.interlace_allowed = true;
|
||||
#ifdef CONFIG_OF
|
||||
hdmi->bridge.of_node = pdev->dev.of_node;
|
||||
#endif
|
||||
|
@ -3431,7 +3440,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
|
|||
audio.base = hdmi->regs;
|
||||
audio.irq = irq;
|
||||
audio.hdmi = hdmi;
|
||||
audio.eld = hdmi->connector.eld;
|
||||
audio.get_eld = hdmi_audio_get_eld;
|
||||
hdmi->enable_audio = dw_hdmi_ahb_audio_enable;
|
||||
hdmi->disable_audio = dw_hdmi_ahb_audio_disable;
|
||||
|
||||
|
@ -3444,7 +3453,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
|
|||
struct dw_hdmi_i2s_audio_data audio;
|
||||
|
||||
audio.hdmi = hdmi;
|
||||
audio.eld = hdmi->connector.eld;
|
||||
audio.get_eld = hdmi_audio_get_eld;
|
||||
audio.write = hdmi_writeb;
|
||||
audio.read = hdmi_readb;
|
||||
hdmi->enable_audio = dw_hdmi_i2s_audio_enable;
|
||||
|
|
|
@ -237,6 +237,10 @@ static void tc358768_hw_enable(struct tc358768_priv *priv)
|
|||
if (priv->enabled)
|
||||
return;
|
||||
|
||||
ret = clk_prepare_enable(priv->refclk);
|
||||
if (ret < 0)
|
||||
dev_err(priv->dev, "error enabling refclk (%d)\n", ret);
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies);
|
||||
if (ret < 0)
|
||||
dev_err(priv->dev, "error enabling regulators (%d)\n", ret);
|
||||
|
@ -274,6 +278,8 @@ static void tc358768_hw_disable(struct tc358768_priv *priv)
|
|||
if (ret < 0)
|
||||
dev_err(priv->dev, "error disabling regulators (%d)\n", ret);
|
||||
|
||||
clk_disable_unprepare(priv->refclk);
|
||||
|
||||
priv->enabled = false;
|
||||
}
|
||||
|
||||
|
@ -625,12 +631,19 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
|
|||
{
|
||||
struct tc358768_priv *priv = bridge_to_tc358768(bridge);
|
||||
struct mipi_dsi_device *dsi_dev = priv->output.dev;
|
||||
unsigned long mode_flags = dsi_dev->mode_flags;
|
||||
u32 val, val2, lptxcnt, hact, data_type;
|
||||
const struct drm_display_mode *mode;
|
||||
u32 dsibclk_nsk, dsiclk_nsk, ui_nsk, phy_delay_nsk;
|
||||
u32 dsiclk, dsibclk;
|
||||
u32 dsiclk, dsibclk, video_start;
|
||||
const u32 internal_delay = 40;
|
||||
int ret, i;
|
||||
|
||||
if (mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS) {
|
||||
dev_warn_once(priv->dev, "Non-continuous mode unimplemented, falling back to continuous\n");
|
||||
mode_flags &= ~MIPI_DSI_CLOCK_NON_CONTINUOUS;
|
||||
}
|
||||
|
||||
tc358768_hw_enable(priv);
|
||||
|
||||
ret = tc358768_sw_reset(priv);
|
||||
|
@ -657,23 +670,27 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
|
|||
case MIPI_DSI_FMT_RGB888:
|
||||
val |= (0x3 << 4);
|
||||
hact = mode->hdisplay * 3;
|
||||
video_start = (mode->htotal - mode->hsync_start) * 3;
|
||||
data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24;
|
||||
break;
|
||||
case MIPI_DSI_FMT_RGB666:
|
||||
val |= (0x4 << 4);
|
||||
hact = mode->hdisplay * 3;
|
||||
video_start = (mode->htotal - mode->hsync_start) * 3;
|
||||
data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18;
|
||||
break;
|
||||
|
||||
case MIPI_DSI_FMT_RGB666_PACKED:
|
||||
val |= (0x4 << 4) | BIT(3);
|
||||
hact = mode->hdisplay * 18 / 8;
|
||||
video_start = (mode->htotal - mode->hsync_start) * 18 / 8;
|
||||
data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18;
|
||||
break;
|
||||
|
||||
case MIPI_DSI_FMT_RGB565:
|
||||
val |= (0x5 << 4);
|
||||
hact = mode->hdisplay * 2;
|
||||
video_start = (mode->htotal - mode->hsync_start) * 2;
|
||||
data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16;
|
||||
break;
|
||||
default:
|
||||
|
@ -684,7 +701,8 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
|
|||
}
|
||||
|
||||
/* VSDly[9:0] */
|
||||
tc358768_write(priv, TC358768_VSDLY, 1);
|
||||
video_start = max(video_start, internal_delay + 1) - internal_delay;
|
||||
tc358768_write(priv, TC358768_VSDLY, video_start);
|
||||
|
||||
tc358768_write(priv, TC358768_DATAFMT, val);
|
||||
tc358768_write(priv, TC358768_DSITX_DT, data_type);
|
||||
|
@ -764,7 +782,7 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
|
|||
val |= BIT(i + 1);
|
||||
tc358768_write(priv, TC358768_HSTXVREGEN, val);
|
||||
|
||||
if (!(dsi_dev->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
|
||||
if (!(mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
|
||||
tc358768_write(priv, TC358768_TXOPTIONCNTRL, 0x1);
|
||||
|
||||
/* TXTAGOCNT[26:16] RXTASURECNT[10:0] */
|
||||
|
@ -772,31 +790,61 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
|
|||
val = tc358768_ns_to_cnt(val, dsibclk_nsk) - 1;
|
||||
val2 = tc358768_ns_to_cnt(tc358768_to_ns((lptxcnt + 1) * dsibclk_nsk),
|
||||
dsibclk_nsk) - 2;
|
||||
val |= val2 << 16;
|
||||
val = val << 16 | val2;
|
||||
dev_dbg(priv->dev, "BTACNTRL1: 0x%x\n", val);
|
||||
tc358768_write(priv, TC358768_BTACNTRL1, val);
|
||||
|
||||
/* START[0] */
|
||||
tc358768_write(priv, TC358768_STARTCNTRL, 1);
|
||||
|
||||
/* Set event mode */
|
||||
tc358768_write(priv, TC358768_DSI_EVENT, 1);
|
||||
if (dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
|
||||
/* Set pulse mode */
|
||||
tc358768_write(priv, TC358768_DSI_EVENT, 0);
|
||||
|
||||
/* vsw (+ vbp) */
|
||||
tc358768_write(priv, TC358768_DSI_VSW,
|
||||
mode->vtotal - mode->vsync_start);
|
||||
/* vbp (not used in event mode) */
|
||||
tc358768_write(priv, TC358768_DSI_VBPR, 0);
|
||||
/* vact */
|
||||
tc358768_write(priv, TC358768_DSI_VACT, mode->vdisplay);
|
||||
/* vact */
|
||||
tc358768_write(priv, TC358768_DSI_VACT, mode->vdisplay);
|
||||
|
||||
/* vsw */
|
||||
tc358768_write(priv, TC358768_DSI_VSW,
|
||||
mode->vsync_end - mode->vsync_start);
|
||||
/* vbp */
|
||||
tc358768_write(priv, TC358768_DSI_VBPR,
|
||||
mode->vtotal - mode->vsync_end);
|
||||
|
||||
/* hsw * byteclk * ndl / pclk */
|
||||
val = (u32)div_u64((mode->hsync_end - mode->hsync_start) *
|
||||
((u64)priv->dsiclk / 4) * priv->dsi_lanes,
|
||||
mode->clock * 1000);
|
||||
tc358768_write(priv, TC358768_DSI_HSW, val);
|
||||
|
||||
/* hbp * byteclk * ndl / pclk */
|
||||
val = (u32)div_u64((mode->htotal - mode->hsync_end) *
|
||||
((u64)priv->dsiclk / 4) * priv->dsi_lanes,
|
||||
mode->clock * 1000);
|
||||
tc358768_write(priv, TC358768_DSI_HBPR, val);
|
||||
} else {
|
||||
/* Set event mode */
|
||||
tc358768_write(priv, TC358768_DSI_EVENT, 1);
|
||||
|
||||
/* vact */
|
||||
tc358768_write(priv, TC358768_DSI_VACT, mode->vdisplay);
|
||||
|
||||
/* vsw (+ vbp) */
|
||||
tc358768_write(priv, TC358768_DSI_VSW,
|
||||
mode->vtotal - mode->vsync_start);
|
||||
/* vbp (not used in event mode) */
|
||||
tc358768_write(priv, TC358768_DSI_VBPR, 0);
|
||||
|
||||
/* (hsw + hbp) * byteclk * ndl / pclk */
|
||||
val = (u32)div_u64((mode->htotal - mode->hsync_start) *
|
||||
((u64)priv->dsiclk / 4) * priv->dsi_lanes,
|
||||
mode->clock * 1000);
|
||||
tc358768_write(priv, TC358768_DSI_HSW, val);
|
||||
|
||||
/* hbp (not used in event mode) */
|
||||
tc358768_write(priv, TC358768_DSI_HBPR, 0);
|
||||
}
|
||||
|
||||
/* (hsw + hbp) * byteclk * ndl / pclk */
|
||||
val = (u32)div_u64((mode->htotal - mode->hsync_start) *
|
||||
((u64)priv->dsiclk / 4) * priv->dsi_lanes,
|
||||
mode->clock * 1000);
|
||||
tc358768_write(priv, TC358768_DSI_HSW, val);
|
||||
/* hbp (not used in event mode) */
|
||||
tc358768_write(priv, TC358768_DSI_HBPR, 0);
|
||||
/* hact (bytes) */
|
||||
tc358768_write(priv, TC358768_DSI_HACT, hact);
|
||||
|
||||
|
@ -822,7 +870,7 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
|
|||
if (!(dsi_dev->mode_flags & MIPI_DSI_MODE_LPM))
|
||||
val |= TC358768_DSI_CONTROL_TXMD;
|
||||
|
||||
if (!(dsi_dev->mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
|
||||
if (!(mode_flags & MIPI_DSI_CLOCK_NON_CONTINUOUS))
|
||||
val |= TC358768_DSI_CONTROL_HSCKMD;
|
||||
|
||||
if (dsi_dev->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)
|
||||
|
|
|
@ -594,11 +594,26 @@ static int tc_bridge_attach(struct drm_bridge *bridge,
|
|||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct tc_data *tc = bridge_to_tc(bridge);
|
||||
|
||||
/* Attach the panel-bridge to the dsi bridge */
|
||||
return drm_bridge_attach(bridge->encoder, tc->panel_bridge,
|
||||
&tc->bridge, flags);
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs tc_bridge_funcs = {
|
||||
.attach = tc_bridge_attach,
|
||||
.pre_enable = tc_bridge_pre_enable,
|
||||
.enable = tc_bridge_enable,
|
||||
.mode_valid = tc_mode_valid,
|
||||
.post_disable = tc_bridge_post_disable,
|
||||
};
|
||||
|
||||
static int tc_attach_host(struct tc_data *tc)
|
||||
{
|
||||
struct device *dev = &tc->i2c->dev;
|
||||
struct mipi_dsi_host *host;
|
||||
struct mipi_dsi_device *dsi;
|
||||
int ret;
|
||||
|
||||
const struct mipi_dsi_device_info info = { .type = "tc358775",
|
||||
.channel = 0,
|
||||
.node = NULL,
|
||||
|
@ -610,11 +625,10 @@ static int tc_bridge_attach(struct drm_bridge *bridge,
|
|||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
dsi = mipi_dsi_device_register_full(host, &info);
|
||||
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
|
||||
if (IS_ERR(dsi)) {
|
||||
dev_err(dev, "failed to create dsi device\n");
|
||||
ret = PTR_ERR(dsi);
|
||||
goto err_dsi_device;
|
||||
return PTR_ERR(dsi);
|
||||
}
|
||||
|
||||
tc->dsi = dsi;
|
||||
|
@ -623,29 +637,15 @@ static int tc_bridge_attach(struct drm_bridge *bridge,
|
|||
dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO;
|
||||
|
||||
ret = mipi_dsi_attach(dsi);
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to attach dsi to host\n");
|
||||
goto err_dsi_attach;
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Attach the panel-bridge to the dsi bridge */
|
||||
return drm_bridge_attach(bridge->encoder, tc->panel_bridge,
|
||||
&tc->bridge, flags);
|
||||
err_dsi_attach:
|
||||
mipi_dsi_device_unregister(dsi);
|
||||
err_dsi_device:
|
||||
return ret;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct drm_bridge_funcs tc_bridge_funcs = {
|
||||
.attach = tc_bridge_attach,
|
||||
.pre_enable = tc_bridge_pre_enable,
|
||||
.enable = tc_bridge_enable,
|
||||
.mode_valid = tc_mode_valid,
|
||||
.post_disable = tc_bridge_post_disable,
|
||||
};
|
||||
|
||||
static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
||||
{
|
||||
struct device *dev = &client->dev;
|
||||
|
@ -709,7 +709,15 @@ static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
|
|||
|
||||
i2c_set_clientdata(client, tc);
|
||||
|
||||
ret = tc_attach_host(tc);
|
||||
if (ret)
|
||||
goto err_bridge_remove;
|
||||
|
||||
return 0;
|
||||
|
||||
err_bridge_remove:
|
||||
drm_bridge_remove(&tc->bridge);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int tc_remove(struct i2c_client *client)
|
||||
|
|
|
@ -245,47 +245,9 @@ static int sn65dsi83_attach(struct drm_bridge *bridge,
|
|||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
|
||||
struct device *dev = ctx->dev;
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct mipi_dsi_host *host;
|
||||
int ret = 0;
|
||||
|
||||
const struct mipi_dsi_device_info info = {
|
||||
.type = "sn65dsi83",
|
||||
.channel = 0,
|
||||
.node = NULL,
|
||||
};
|
||||
|
||||
host = of_find_mipi_dsi_host_by_node(ctx->host_node);
|
||||
if (!host) {
|
||||
dev_err(dev, "failed to find dsi host\n");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
dsi = mipi_dsi_device_register_full(host, &info);
|
||||
if (IS_ERR(dsi)) {
|
||||
return dev_err_probe(dev, PTR_ERR(dsi),
|
||||
"failed to create dsi device\n");
|
||||
}
|
||||
|
||||
ctx->dsi = dsi;
|
||||
|
||||
dsi->lanes = ctx->dsi_lanes;
|
||||
dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST;
|
||||
|
||||
ret = mipi_dsi_attach(dsi);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to attach dsi to host\n");
|
||||
goto err_dsi_attach;
|
||||
}
|
||||
|
||||
return drm_bridge_attach(bridge->encoder, ctx->panel_bridge,
|
||||
&ctx->bridge, flags);
|
||||
|
||||
err_dsi_attach:
|
||||
mipi_dsi_device_unregister(dsi);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void sn65dsi83_detach(struct drm_bridge *bridge)
|
||||
|
@ -295,28 +257,9 @@ static void sn65dsi83_detach(struct drm_bridge *bridge)
|
|||
if (!ctx->dsi)
|
||||
return;
|
||||
|
||||
mipi_dsi_detach(ctx->dsi);
|
||||
mipi_dsi_device_unregister(ctx->dsi);
|
||||
drm_bridge_remove(&ctx->bridge);
|
||||
ctx->dsi = NULL;
|
||||
}
|
||||
|
||||
static void sn65dsi83_atomic_pre_enable(struct drm_bridge *bridge,
|
||||
struct drm_bridge_state *old_bridge_state)
|
||||
{
|
||||
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
|
||||
|
||||
/*
|
||||
* Reset the chip, pull EN line low for t_reset=10ms,
|
||||
* then high for t_en=1ms.
|
||||
*/
|
||||
regcache_mark_dirty(ctx->regmap);
|
||||
gpiod_set_value(ctx->enable_gpio, 0);
|
||||
usleep_range(10000, 11000);
|
||||
gpiod_set_value(ctx->enable_gpio, 1);
|
||||
usleep_range(1000, 1100);
|
||||
}
|
||||
|
||||
static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
|
@ -394,6 +337,10 @@ static void sn65dsi83_atomic_enable(struct drm_bridge *bridge,
|
|||
u16 val;
|
||||
int ret;
|
||||
|
||||
/* Deassert reset */
|
||||
gpiod_set_value(ctx->enable_gpio, 1);
|
||||
usleep_range(1000, 1100);
|
||||
|
||||
/* Get the LVDS format from the bridge state. */
|
||||
bridge_state = drm_atomic_get_new_bridge_state(state, bridge);
|
||||
|
||||
|
@ -540,18 +487,11 @@ static void sn65dsi83_atomic_disable(struct drm_bridge *bridge,
|
|||
{
|
||||
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
|
||||
|
||||
/* Clear reset, disable PLL */
|
||||
regmap_write(ctx->regmap, REG_RC_RESET, 0x00);
|
||||
regmap_write(ctx->regmap, REG_RC_PLL_EN, 0x00);
|
||||
}
|
||||
|
||||
static void sn65dsi83_atomic_post_disable(struct drm_bridge *bridge,
|
||||
struct drm_bridge_state *old_bridge_state)
|
||||
{
|
||||
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
|
||||
|
||||
/* Put the chip in reset, pull EN line low. */
|
||||
/* Put the chip in reset, pull EN line low, and assure 10ms reset low timing. */
|
||||
gpiod_set_value(ctx->enable_gpio, 0);
|
||||
usleep_range(10000, 11000);
|
||||
|
||||
regcache_mark_dirty(ctx->regmap);
|
||||
}
|
||||
|
||||
static enum drm_mode_status
|
||||
|
@ -597,10 +537,8 @@ sn65dsi83_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
|
|||
static const struct drm_bridge_funcs sn65dsi83_funcs = {
|
||||
.attach = sn65dsi83_attach,
|
||||
.detach = sn65dsi83_detach,
|
||||
.atomic_pre_enable = sn65dsi83_atomic_pre_enable,
|
||||
.atomic_enable = sn65dsi83_atomic_enable,
|
||||
.atomic_disable = sn65dsi83_atomic_disable,
|
||||
.atomic_post_disable = sn65dsi83_atomic_post_disable,
|
||||
.mode_valid = sn65dsi83_mode_valid,
|
||||
|
||||
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
||||
|
@ -664,6 +602,44 @@ static int sn65dsi83_parse_dt(struct sn65dsi83 *ctx, enum sn65dsi83_model model)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int sn65dsi83_host_attach(struct sn65dsi83 *ctx)
|
||||
{
|
||||
struct device *dev = ctx->dev;
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct mipi_dsi_host *host;
|
||||
const struct mipi_dsi_device_info info = {
|
||||
.type = "sn65dsi83",
|
||||
.channel = 0,
|
||||
.node = NULL,
|
||||
};
|
||||
int ret;
|
||||
|
||||
host = of_find_mipi_dsi_host_by_node(ctx->host_node);
|
||||
if (!host) {
|
||||
dev_err(dev, "failed to find dsi host\n");
|
||||
return -EPROBE_DEFER;
|
||||
}
|
||||
|
||||
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
|
||||
if (IS_ERR(dsi))
|
||||
return dev_err_probe(dev, PTR_ERR(dsi),
|
||||
"failed to create dsi device\n");
|
||||
|
||||
ctx->dsi = dsi;
|
||||
|
||||
dsi->lanes = ctx->dsi_lanes;
|
||||
dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST;
|
||||
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "failed to attach dsi to host: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sn65dsi83_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
|
@ -685,10 +661,13 @@ static int sn65dsi83_probe(struct i2c_client *client,
|
|||
model = id->driver_data;
|
||||
}
|
||||
|
||||
/* Put the chip in reset, pull EN line low, and assure 10ms reset low timing. */
|
||||
ctx->enable_gpio = devm_gpiod_get(ctx->dev, "enable", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(ctx->enable_gpio))
|
||||
return PTR_ERR(ctx->enable_gpio);
|
||||
|
||||
usleep_range(10000, 11000);
|
||||
|
||||
ret = sn65dsi83_parse_dt(ctx, model);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -704,13 +683,22 @@ static int sn65dsi83_probe(struct i2c_client *client,
|
|||
ctx->bridge.of_node = dev->of_node;
|
||||
drm_bridge_add(&ctx->bridge);
|
||||
|
||||
ret = sn65dsi83_host_attach(ctx);
|
||||
if (ret)
|
||||
goto err_remove_bridge;
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_bridge:
|
||||
drm_bridge_remove(&ctx->bridge);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sn65dsi83_remove(struct i2c_client *client)
|
||||
{
|
||||
struct sn65dsi83 *ctx = i2c_get_clientdata(client);
|
||||
|
||||
drm_bridge_remove(&ctx->bridge);
|
||||
of_node_put(ctx->host_node);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
* datasheet: https://www.ti.com/lit/ds/symlink/sn65dsi86.pdf
|
||||
*/
|
||||
|
||||
#include <linux/atomic.h>
|
||||
#include <linux/auxiliary_bus.h>
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/clk.h>
|
||||
#include <linux/debugfs.h>
|
||||
|
@ -15,6 +17,7 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/pwm.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
|
@ -91,6 +94,13 @@
|
|||
#define SN_ML_TX_MODE_REG 0x96
|
||||
#define ML_TX_MAIN_LINK_OFF 0
|
||||
#define ML_TX_NORMAL_MODE BIT(0)
|
||||
#define SN_PWM_PRE_DIV_REG 0xA0
|
||||
#define SN_BACKLIGHT_SCALE_REG 0xA1
|
||||
#define BACKLIGHT_SCALE_MAX 0xFFFF
|
||||
#define SN_BACKLIGHT_REG 0xA3
|
||||
#define SN_PWM_EN_INV_REG 0xA5
|
||||
#define SN_PWM_INV_MASK BIT(0)
|
||||
#define SN_PWM_EN_MASK BIT(1)
|
||||
#define SN_AUX_CMD_STATUS_REG 0xF4
|
||||
#define AUX_IRQ_STATUS_AUX_RPLY_TOUT BIT(3)
|
||||
#define AUX_IRQ_STATUS_AUX_SHORT BIT(5)
|
||||
|
@ -113,11 +123,14 @@
|
|||
|
||||
#define SN_LINK_TRAINING_TRIES 10
|
||||
|
||||
#define SN_PWM_GPIO_IDX 3 /* 4th GPIO */
|
||||
|
||||
/**
|
||||
* struct ti_sn65dsi86 - Platform data for ti-sn65dsi86 driver.
|
||||
* @bridge_aux: AUX-bus sub device for MIPI-to-eDP bridge functionality.
|
||||
* @gpio_aux: AUX-bus sub device for GPIO controller functionality.
|
||||
* @aux_aux: AUX-bus sub device for eDP AUX channel functionality.
|
||||
* @pwm_aux: AUX-bus sub device for PWM controller functionality.
|
||||
*
|
||||
* @dev: Pointer to the top level (i2c) device.
|
||||
* @regmap: Regmap for accessing i2c.
|
||||
|
@ -145,11 +158,17 @@
|
|||
* bitmap so we can do atomic ops on it without an extra
|
||||
* lock so concurrent users of our 4 GPIOs don't stomp on
|
||||
* each other's read-modify-write.
|
||||
*
|
||||
* @pchip: pwm_chip if the PWM is exposed.
|
||||
* @pwm_enabled: Used to track if the PWM signal is currently enabled.
|
||||
* @pwm_pin_busy: Track if GPIO4 is currently requested for GPIO or PWM.
|
||||
* @pwm_refclk_freq: Cache for the reference clock input to the PWM.
|
||||
*/
|
||||
struct ti_sn65dsi86 {
|
||||
struct auxiliary_device bridge_aux;
|
||||
struct auxiliary_device gpio_aux;
|
||||
struct auxiliary_device aux_aux;
|
||||
struct auxiliary_device pwm_aux;
|
||||
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
|
@ -172,6 +191,12 @@ struct ti_sn65dsi86 {
|
|||
struct gpio_chip gchip;
|
||||
DECLARE_BITMAP(gchip_output, SN_NUM_GPIOS);
|
||||
#endif
|
||||
#if defined(CONFIG_PWM)
|
||||
struct pwm_chip pchip;
|
||||
bool pwm_enabled;
|
||||
atomic_t pwm_pin_busy;
|
||||
#endif
|
||||
unsigned int pwm_refclk_freq;
|
||||
};
|
||||
|
||||
static const struct regmap_range ti_sn65dsi86_volatile_ranges[] = {
|
||||
|
@ -190,11 +215,27 @@ static const struct regmap_config ti_sn65dsi86_regmap_config = {
|
|||
.cache_type = REGCACHE_NONE,
|
||||
};
|
||||
|
||||
static int __maybe_unused ti_sn65dsi86_read_u16(struct ti_sn65dsi86 *pdata,
|
||||
unsigned int reg, u16 *val)
|
||||
{
|
||||
u8 buf[2];
|
||||
int ret;
|
||||
|
||||
ret = regmap_bulk_read(pdata->regmap, reg, buf, ARRAY_SIZE(buf));
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
*val = buf[0] | (buf[1] << 8);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ti_sn65dsi86_write_u16(struct ti_sn65dsi86 *pdata,
|
||||
unsigned int reg, u16 val)
|
||||
{
|
||||
regmap_write(pdata->regmap, reg, val & 0xFF);
|
||||
regmap_write(pdata->regmap, reg + 1, val >> 8);
|
||||
u8 buf[2] = { val & 0xff, val >> 8 };
|
||||
|
||||
regmap_bulk_write(pdata->regmap, reg, buf, ARRAY_SIZE(buf));
|
||||
}
|
||||
|
||||
static u32 ti_sn_bridge_get_dsi_freq(struct ti_sn65dsi86 *pdata)
|
||||
|
@ -253,6 +294,12 @@ static void ti_sn_bridge_set_refclk_freq(struct ti_sn65dsi86 *pdata)
|
|||
|
||||
regmap_update_bits(pdata->regmap, SN_DPPLL_SRC_REG, REFCLK_FREQ_MASK,
|
||||
REFCLK_FREQ(i));
|
||||
|
||||
/*
|
||||
* The PWM refclk is based on the value written to SN_DPPLL_SRC_REG,
|
||||
* regardless of its actual sourcing.
|
||||
*/
|
||||
pdata->pwm_refclk_freq = ti_sn_bridge_refclk_lut[i];
|
||||
}
|
||||
|
||||
static void ti_sn65dsi86_enable_comms(struct ti_sn65dsi86 *pdata)
|
||||
|
@ -655,17 +702,57 @@ static struct ti_sn65dsi86 *bridge_to_ti_sn65dsi86(struct drm_bridge *bridge)
|
|||
return container_of(bridge, struct ti_sn65dsi86, bridge);
|
||||
}
|
||||
|
||||
static int ti_sn_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
static int ti_sn_attach_host(struct ti_sn65dsi86 *pdata)
|
||||
{
|
||||
int ret, val;
|
||||
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
|
||||
struct mipi_dsi_host *host;
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct device *dev = pdata->dev;
|
||||
const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
|
||||
.channel = 0,
|
||||
.node = NULL,
|
||||
};
|
||||
};
|
||||
|
||||
host = of_find_mipi_dsi_host_by_node(pdata->host_node);
|
||||
if (!host) {
|
||||
DRM_ERROR("failed to find dsi host\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
|
||||
if (IS_ERR(dsi)) {
|
||||
DRM_ERROR("failed to create dsi device\n");
|
||||
return PTR_ERR(dsi);
|
||||
}
|
||||
|
||||
/* TODO: setting to 4 MIPI lanes always for now */
|
||||
dsi->lanes = 4;
|
||||
dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO;
|
||||
|
||||
/* check if continuous dsi clock is required or not */
|
||||
pm_runtime_get_sync(dev);
|
||||
regmap_read(pdata->regmap, SN_DPPLL_SRC_REG, &val);
|
||||
pm_runtime_put_autosuspend(dev);
|
||||
if (!(val & DPPLL_CLK_SRC_DSICLK))
|
||||
dsi->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS;
|
||||
|
||||
pdata->dsi = dsi;
|
||||
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("failed to attach dsi to host\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ti_sn_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
|
||||
int ret;
|
||||
|
||||
if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
|
||||
DRM_ERROR("Fix bridge driver to make connector optional!");
|
||||
|
@ -683,50 +770,6 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge,
|
|||
if (ret < 0)
|
||||
goto err_conn_init;
|
||||
|
||||
/*
|
||||
* TODO: ideally finding host resource and dsi dev registration needs
|
||||
* to be done in bridge probe. But some existing DSI host drivers will
|
||||
* wait for any of the drm_bridge/drm_panel to get added to the global
|
||||
* bridge/panel list, before completing their probe. So if we do the
|
||||
* dsi dev registration part in bridge probe, before populating in
|
||||
* the global bridge list, then it will cause deadlock as dsi host probe
|
||||
* will never complete, neither our bridge probe. So keeping it here
|
||||
* will satisfy most of the existing host drivers. Once the host driver
|
||||
* is fixed we can move the below code to bridge probe safely.
|
||||
*/
|
||||
host = of_find_mipi_dsi_host_by_node(pdata->host_node);
|
||||
if (!host) {
|
||||
DRM_ERROR("failed to find dsi host\n");
|
||||
ret = -ENODEV;
|
||||
goto err_dsi_host;
|
||||
}
|
||||
|
||||
dsi = mipi_dsi_device_register_full(host, &info);
|
||||
if (IS_ERR(dsi)) {
|
||||
DRM_ERROR("failed to create dsi device\n");
|
||||
ret = PTR_ERR(dsi);
|
||||
goto err_dsi_host;
|
||||
}
|
||||
|
||||
/* TODO: setting to 4 MIPI lanes always for now */
|
||||
dsi->lanes = 4;
|
||||
dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO;
|
||||
|
||||
/* check if continuous dsi clock is required or not */
|
||||
pm_runtime_get_sync(pdata->dev);
|
||||
regmap_read(pdata->regmap, SN_DPPLL_SRC_REG, &val);
|
||||
pm_runtime_put_autosuspend(pdata->dev);
|
||||
if (!(val & DPPLL_CLK_SRC_DSICLK))
|
||||
dsi->mode_flags |= MIPI_DSI_CLOCK_NON_CONTINUOUS;
|
||||
|
||||
ret = mipi_dsi_attach(dsi);
|
||||
if (ret < 0) {
|
||||
DRM_ERROR("failed to attach dsi to host\n");
|
||||
goto err_dsi_attach;
|
||||
}
|
||||
pdata->dsi = dsi;
|
||||
|
||||
/* We never want the next bridge to *also* create a connector: */
|
||||
flags |= DRM_BRIDGE_ATTACH_NO_CONNECTOR;
|
||||
|
||||
|
@ -734,14 +777,10 @@ static int ti_sn_bridge_attach(struct drm_bridge *bridge,
|
|||
ret = drm_bridge_attach(bridge->encoder, pdata->next_bridge,
|
||||
&pdata->bridge, flags);
|
||||
if (ret < 0)
|
||||
goto err_dsi_detach;
|
||||
goto err_dsi_host;
|
||||
|
||||
return 0;
|
||||
|
||||
err_dsi_detach:
|
||||
mipi_dsi_detach(dsi);
|
||||
err_dsi_attach:
|
||||
mipi_dsi_device_unregister(dsi);
|
||||
err_dsi_host:
|
||||
drm_connector_cleanup(&pdata->connector);
|
||||
err_conn_init:
|
||||
|
@ -1227,7 +1266,15 @@ static int ti_sn_bridge_probe(struct auxiliary_device *adev,
|
|||
|
||||
drm_bridge_add(&pdata->bridge);
|
||||
|
||||
ret = ti_sn_attach_host(pdata);
|
||||
if (ret)
|
||||
goto err_remove_bridge;
|
||||
|
||||
return 0;
|
||||
|
||||
err_remove_bridge:
|
||||
drm_bridge_remove(&pdata->bridge);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ti_sn_bridge_remove(struct auxiliary_device *adev)
|
||||
|
@ -1237,11 +1284,6 @@ static void ti_sn_bridge_remove(struct auxiliary_device *adev)
|
|||
if (!pdata)
|
||||
return;
|
||||
|
||||
if (pdata->dsi) {
|
||||
mipi_dsi_detach(pdata->dsi);
|
||||
mipi_dsi_device_unregister(pdata->dsi);
|
||||
}
|
||||
|
||||
drm_bridge_remove(&pdata->bridge);
|
||||
|
||||
of_node_put(pdata->host_node);
|
||||
|
@ -1259,10 +1301,288 @@ static struct auxiliary_driver ti_sn_bridge_driver = {
|
|||
.id_table = ti_sn_bridge_id_table,
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* PWM Controller
|
||||
*/
|
||||
#if defined(CONFIG_PWM)
|
||||
static int ti_sn_pwm_pin_request(struct ti_sn65dsi86 *pdata)
|
||||
{
|
||||
return atomic_xchg(&pdata->pwm_pin_busy, 1) ? -EBUSY : 0;
|
||||
}
|
||||
|
||||
static void ti_sn_pwm_pin_release(struct ti_sn65dsi86 *pdata)
|
||||
{
|
||||
atomic_set(&pdata->pwm_pin_busy, 0);
|
||||
}
|
||||
|
||||
static struct ti_sn65dsi86 *pwm_chip_to_ti_sn_bridge(struct pwm_chip *chip)
|
||||
{
|
||||
return container_of(chip, struct ti_sn65dsi86, pchip);
|
||||
}
|
||||
|
||||
static int ti_sn_pwm_request(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
|
||||
|
||||
return ti_sn_pwm_pin_request(pdata);
|
||||
}
|
||||
|
||||
static void ti_sn_pwm_free(struct pwm_chip *chip, struct pwm_device *pwm)
|
||||
{
|
||||
struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
|
||||
|
||||
ti_sn_pwm_pin_release(pdata);
|
||||
}
|
||||
|
||||
/*
|
||||
* Limitations:
|
||||
* - The PWM signal is not driven when the chip is powered down, or in its
|
||||
* reset state and the driver does not implement the "suspend state"
|
||||
* described in the documentation. In order to save power, state->enabled is
|
||||
* interpreted as denoting if the signal is expected to be valid, and is used
|
||||
* to determine if the chip needs to be kept powered.
|
||||
* - Changing both period and duty_cycle is not done atomically, neither is the
|
||||
* multi-byte register updates, so the output might briefly be undefined
|
||||
* during update.
|
||||
*/
|
||||
static int ti_sn_pwm_apply(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
const struct pwm_state *state)
|
||||
{
|
||||
struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
|
||||
unsigned int pwm_en_inv;
|
||||
unsigned int backlight;
|
||||
unsigned int pre_div;
|
||||
unsigned int scale;
|
||||
u64 period_max;
|
||||
u64 period;
|
||||
int ret;
|
||||
|
||||
if (!pdata->pwm_enabled) {
|
||||
ret = pm_runtime_get_sync(pdata->dev);
|
||||
if (ret < 0) {
|
||||
pm_runtime_put_sync(pdata->dev);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (state->enabled) {
|
||||
if (!pdata->pwm_enabled) {
|
||||
/*
|
||||
* The chip might have been powered down while we
|
||||
* didn't hold a PM runtime reference, so mux in the
|
||||
* PWM function on the GPIO pin again.
|
||||
*/
|
||||
ret = regmap_update_bits(pdata->regmap, SN_GPIO_CTRL_REG,
|
||||
SN_GPIO_MUX_MASK << (2 * SN_PWM_GPIO_IDX),
|
||||
SN_GPIO_MUX_SPECIAL << (2 * SN_PWM_GPIO_IDX));
|
||||
if (ret) {
|
||||
dev_err(pdata->dev, "failed to mux in PWM function\n");
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Per the datasheet the PWM frequency is given by:
|
||||
*
|
||||
* REFCLK_FREQ
|
||||
* PWM_FREQ = -----------------------------------
|
||||
* PWM_PRE_DIV * BACKLIGHT_SCALE + 1
|
||||
*
|
||||
* However, after careful review the author is convinced that
|
||||
* the documentation has lost some parenthesis around
|
||||
* "BACKLIGHT_SCALE + 1".
|
||||
*
|
||||
* With the period T_pwm = 1/PWM_FREQ this can be written:
|
||||
*
|
||||
* T_pwm * REFCLK_FREQ = PWM_PRE_DIV * (BACKLIGHT_SCALE + 1)
|
||||
*
|
||||
* In order to keep BACKLIGHT_SCALE within its 16 bits,
|
||||
* PWM_PRE_DIV must be:
|
||||
*
|
||||
* T_pwm * REFCLK_FREQ
|
||||
* PWM_PRE_DIV >= -------------------------
|
||||
* BACKLIGHT_SCALE_MAX + 1
|
||||
*
|
||||
* To simplify the search and to favour higher resolution of
|
||||
* the duty cycle over accuracy of the period, the lowest
|
||||
* possible PWM_PRE_DIV is used. Finally the scale is
|
||||
* calculated as:
|
||||
*
|
||||
* T_pwm * REFCLK_FREQ
|
||||
* BACKLIGHT_SCALE = ---------------------- - 1
|
||||
* PWM_PRE_DIV
|
||||
*
|
||||
* Here T_pwm is represented in seconds, so appropriate scaling
|
||||
* to nanoseconds is necessary.
|
||||
*/
|
||||
|
||||
/* Minimum T_pwm is 1 / REFCLK_FREQ */
|
||||
if (state->period <= NSEC_PER_SEC / pdata->pwm_refclk_freq) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Maximum T_pwm is 255 * (65535 + 1) / REFCLK_FREQ
|
||||
* Limit period to this to avoid overflows
|
||||
*/
|
||||
period_max = div_u64((u64)NSEC_PER_SEC * 255 * (65535 + 1),
|
||||
pdata->pwm_refclk_freq);
|
||||
period = min(state->period, period_max);
|
||||
|
||||
pre_div = DIV64_U64_ROUND_UP(period * pdata->pwm_refclk_freq,
|
||||
(u64)NSEC_PER_SEC * (BACKLIGHT_SCALE_MAX + 1));
|
||||
scale = div64_u64(period * pdata->pwm_refclk_freq, (u64)NSEC_PER_SEC * pre_div) - 1;
|
||||
|
||||
/*
|
||||
* The documentation has the duty ratio given as:
|
||||
*
|
||||
* duty BACKLIGHT
|
||||
* ------- = ---------------------
|
||||
* period BACKLIGHT_SCALE + 1
|
||||
*
|
||||
* Solve for BACKLIGHT, substituting BACKLIGHT_SCALE according
|
||||
* to definition above and adjusting for nanosecond
|
||||
* representation of duty cycle gives us:
|
||||
*/
|
||||
backlight = div64_u64(state->duty_cycle * pdata->pwm_refclk_freq,
|
||||
(u64)NSEC_PER_SEC * pre_div);
|
||||
if (backlight > scale)
|
||||
backlight = scale;
|
||||
|
||||
ret = regmap_write(pdata->regmap, SN_PWM_PRE_DIV_REG, pre_div);
|
||||
if (ret) {
|
||||
dev_err(pdata->dev, "failed to update PWM_PRE_DIV\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
ti_sn65dsi86_write_u16(pdata, SN_BACKLIGHT_SCALE_REG, scale);
|
||||
ti_sn65dsi86_write_u16(pdata, SN_BACKLIGHT_REG, backlight);
|
||||
}
|
||||
|
||||
pwm_en_inv = FIELD_PREP(SN_PWM_EN_MASK, state->enabled) |
|
||||
FIELD_PREP(SN_PWM_INV_MASK, state->polarity == PWM_POLARITY_INVERSED);
|
||||
ret = regmap_write(pdata->regmap, SN_PWM_EN_INV_REG, pwm_en_inv);
|
||||
if (ret) {
|
||||
dev_err(pdata->dev, "failed to update PWM_EN/PWM_INV\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
pdata->pwm_enabled = state->enabled;
|
||||
out:
|
||||
|
||||
if (!pdata->pwm_enabled)
|
||||
pm_runtime_put_sync(pdata->dev);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ti_sn_pwm_get_state(struct pwm_chip *chip, struct pwm_device *pwm,
|
||||
struct pwm_state *state)
|
||||
{
|
||||
struct ti_sn65dsi86 *pdata = pwm_chip_to_ti_sn_bridge(chip);
|
||||
unsigned int pwm_en_inv;
|
||||
unsigned int pre_div;
|
||||
u16 backlight;
|
||||
u16 scale;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(pdata->regmap, SN_PWM_EN_INV_REG, &pwm_en_inv);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
ret = ti_sn65dsi86_read_u16(pdata, SN_BACKLIGHT_SCALE_REG, &scale);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
ret = ti_sn65dsi86_read_u16(pdata, SN_BACKLIGHT_REG, &backlight);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
ret = regmap_read(pdata->regmap, SN_PWM_PRE_DIV_REG, &pre_div);
|
||||
if (ret)
|
||||
return;
|
||||
|
||||
state->enabled = FIELD_GET(SN_PWM_EN_MASK, pwm_en_inv);
|
||||
if (FIELD_GET(SN_PWM_INV_MASK, pwm_en_inv))
|
||||
state->polarity = PWM_POLARITY_INVERSED;
|
||||
else
|
||||
state->polarity = PWM_POLARITY_NORMAL;
|
||||
|
||||
state->period = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pre_div * (scale + 1),
|
||||
pdata->pwm_refclk_freq);
|
||||
state->duty_cycle = DIV_ROUND_UP_ULL((u64)NSEC_PER_SEC * pre_div * backlight,
|
||||
pdata->pwm_refclk_freq);
|
||||
|
||||
if (state->duty_cycle > state->period)
|
||||
state->duty_cycle = state->period;
|
||||
}
|
||||
|
||||
static const struct pwm_ops ti_sn_pwm_ops = {
|
||||
.request = ti_sn_pwm_request,
|
||||
.free = ti_sn_pwm_free,
|
||||
.apply = ti_sn_pwm_apply,
|
||||
.get_state = ti_sn_pwm_get_state,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
static int ti_sn_pwm_probe(struct auxiliary_device *adev,
|
||||
const struct auxiliary_device_id *id)
|
||||
{
|
||||
struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
|
||||
|
||||
pdata->pchip.dev = pdata->dev;
|
||||
pdata->pchip.ops = &ti_sn_pwm_ops;
|
||||
pdata->pchip.npwm = 1;
|
||||
pdata->pchip.of_xlate = of_pwm_single_xlate;
|
||||
pdata->pchip.of_pwm_n_cells = 1;
|
||||
|
||||
return pwmchip_add(&pdata->pchip);
|
||||
}
|
||||
|
||||
static void ti_sn_pwm_remove(struct auxiliary_device *adev)
|
||||
{
|
||||
struct ti_sn65dsi86 *pdata = dev_get_drvdata(adev->dev.parent);
|
||||
|
||||
pwmchip_remove(&pdata->pchip);
|
||||
|
||||
if (pdata->pwm_enabled)
|
||||
pm_runtime_put_sync(pdata->dev);
|
||||
}
|
||||
|
||||
static const struct auxiliary_device_id ti_sn_pwm_id_table[] = {
|
||||
{ .name = "ti_sn65dsi86.pwm", },
|
||||
{},
|
||||
};
|
||||
|
||||
static struct auxiliary_driver ti_sn_pwm_driver = {
|
||||
.name = "pwm",
|
||||
.probe = ti_sn_pwm_probe,
|
||||
.remove = ti_sn_pwm_remove,
|
||||
.id_table = ti_sn_pwm_id_table,
|
||||
};
|
||||
|
||||
static int __init ti_sn_pwm_register(void)
|
||||
{
|
||||
return auxiliary_driver_register(&ti_sn_pwm_driver);
|
||||
}
|
||||
|
||||
static void ti_sn_pwm_unregister(void)
|
||||
{
|
||||
auxiliary_driver_unregister(&ti_sn_pwm_driver);
|
||||
}
|
||||
|
||||
#else
|
||||
static inline int ti_sn_pwm_pin_request(struct ti_sn65dsi86 *pdata) { return 0; }
|
||||
static inline void ti_sn_pwm_pin_release(struct ti_sn65dsi86 *pdata) {}
|
||||
|
||||
static inline int ti_sn_pwm_register(void) { return 0; }
|
||||
static inline void ti_sn_pwm_unregister(void) {}
|
||||
#endif
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
* GPIO Controller
|
||||
*/
|
||||
|
||||
#if defined(CONFIG_OF_GPIO)
|
||||
|
||||
static int tn_sn_bridge_of_xlate(struct gpio_chip *chip,
|
||||
|
@ -1395,10 +1715,25 @@ static int ti_sn_bridge_gpio_direction_output(struct gpio_chip *chip,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int ti_sn_bridge_gpio_request(struct gpio_chip *chip, unsigned int offset)
|
||||
{
|
||||
struct ti_sn65dsi86 *pdata = gpiochip_get_data(chip);
|
||||
|
||||
if (offset == SN_PWM_GPIO_IDX)
|
||||
return ti_sn_pwm_pin_request(pdata);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ti_sn_bridge_gpio_free(struct gpio_chip *chip, unsigned int offset)
|
||||
{
|
||||
struct ti_sn65dsi86 *pdata = gpiochip_get_data(chip);
|
||||
|
||||
/* We won't keep pm_runtime if we're input, so switch there on free */
|
||||
ti_sn_bridge_gpio_direction_input(chip, offset);
|
||||
|
||||
if (offset == SN_PWM_GPIO_IDX)
|
||||
ti_sn_pwm_pin_release(pdata);
|
||||
}
|
||||
|
||||
static const char * const ti_sn_bridge_gpio_names[SN_NUM_GPIOS] = {
|
||||
|
@ -1420,6 +1755,7 @@ static int ti_sn_gpio_probe(struct auxiliary_device *adev,
|
|||
pdata->gchip.owner = THIS_MODULE;
|
||||
pdata->gchip.of_xlate = tn_sn_bridge_of_xlate;
|
||||
pdata->gchip.of_gpio_n_cells = 2;
|
||||
pdata->gchip.request = ti_sn_bridge_gpio_request;
|
||||
pdata->gchip.free = ti_sn_bridge_gpio_free;
|
||||
pdata->gchip.get_direction = ti_sn_bridge_gpio_get_direction;
|
||||
pdata->gchip.direction_input = ti_sn_bridge_gpio_direction_input;
|
||||
|
@ -1546,10 +1882,9 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
|
|||
* ordering. The bridge wants the panel to be there when it probes.
|
||||
* The panel wants its HPD GPIO (provided by sn65dsi86 on some boards)
|
||||
* when it probes. The panel and maybe backlight might want the DDC
|
||||
* bus. Soon the PWM provided by the bridge chip will have the same
|
||||
* problem. Having sub-devices allows the some sub devices to finish
|
||||
* probing even if others return -EPROBE_DEFER and gets us around the
|
||||
* problems.
|
||||
* bus or the pwm_chip. Having sub-devices allows the some sub devices
|
||||
* to finish probing even if others return -EPROBE_DEFER and gets us
|
||||
* around the problems.
|
||||
*/
|
||||
|
||||
if (IS_ENABLED(CONFIG_OF_GPIO)) {
|
||||
|
@ -1558,6 +1893,12 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
|
|||
return ret;
|
||||
}
|
||||
|
||||
if (IS_ENABLED(CONFIG_PWM)) {
|
||||
ret = ti_sn65dsi86_add_aux_device(pdata, &pdata->pwm_aux, "pwm");
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: At the end of the AUX channel probe we'll add the aux device
|
||||
* for the bridge. This is because the bridge can't be used until the
|
||||
|
@ -1601,10 +1942,14 @@ static int __init ti_sn65dsi86_init(void)
|
|||
if (ret)
|
||||
goto err_main_was_registered;
|
||||
|
||||
ret = auxiliary_driver_register(&ti_sn_aux_driver);
|
||||
ret = ti_sn_pwm_register();
|
||||
if (ret)
|
||||
goto err_gpio_was_registered;
|
||||
|
||||
ret = auxiliary_driver_register(&ti_sn_aux_driver);
|
||||
if (ret)
|
||||
goto err_pwm_was_registered;
|
||||
|
||||
ret = auxiliary_driver_register(&ti_sn_bridge_driver);
|
||||
if (ret)
|
||||
goto err_aux_was_registered;
|
||||
|
@ -1613,6 +1958,8 @@ static int __init ti_sn65dsi86_init(void)
|
|||
|
||||
err_aux_was_registered:
|
||||
auxiliary_driver_unregister(&ti_sn_aux_driver);
|
||||
err_pwm_was_registered:
|
||||
ti_sn_pwm_unregister();
|
||||
err_gpio_was_registered:
|
||||
ti_sn_gpio_unregister();
|
||||
err_main_was_registered:
|
||||
|
@ -1626,6 +1973,7 @@ static void __exit ti_sn65dsi86_exit(void)
|
|||
{
|
||||
auxiliary_driver_unregister(&ti_sn_bridge_driver);
|
||||
auxiliary_driver_unregister(&ti_sn_aux_driver);
|
||||
ti_sn_pwm_unregister();
|
||||
ti_sn_gpio_unregister();
|
||||
i2c_del_driver(&ti_sn65dsi86_driver);
|
||||
}
|
||||
|
|
|
@ -797,6 +797,8 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
|
|||
fence_ptr);
|
||||
} else if (property == connector->max_bpc_property) {
|
||||
state->max_requested_bpc = val;
|
||||
} else if (property == connector->privacy_screen_sw_state_property) {
|
||||
state->privacy_screen_sw_state = val;
|
||||
} else if (connector->funcs->atomic_set_property) {
|
||||
return connector->funcs->atomic_set_property(connector,
|
||||
state, property, val);
|
||||
|
@ -874,6 +876,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
|
|||
*val = 0;
|
||||
} else if (property == connector->max_bpc_property) {
|
||||
*val = state->max_requested_bpc;
|
||||
} else if (property == connector->privacy_screen_sw_state_property) {
|
||||
*val = state->privacy_screen_sw_state;
|
||||
} else if (connector->funcs->atomic_get_property) {
|
||||
return connector->funcs->atomic_get_property(connector,
|
||||
state, property, val);
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/drm_file.h>
|
||||
#include <drm/drm_privacy_screen_consumer.h>
|
||||
#include <drm/drm_sysfs.h>
|
||||
|
||||
#include <linux/uaccess.h>
|
||||
|
@ -462,6 +463,11 @@ void drm_connector_cleanup(struct drm_connector *connector)
|
|||
DRM_CONNECTOR_REGISTERED))
|
||||
drm_connector_unregister(connector);
|
||||
|
||||
if (connector->privacy_screen) {
|
||||
drm_privacy_screen_put(connector->privacy_screen);
|
||||
connector->privacy_screen = NULL;
|
||||
}
|
||||
|
||||
if (connector->tile_group) {
|
||||
drm_mode_put_tile_group(dev, connector->tile_group);
|
||||
connector->tile_group = NULL;
|
||||
|
@ -541,7 +547,11 @@ int drm_connector_register(struct drm_connector *connector)
|
|||
connector->registration_state = DRM_CONNECTOR_REGISTERED;
|
||||
|
||||
/* Let userspace know we have a new connector */
|
||||
drm_sysfs_hotplug_event(connector->dev);
|
||||
drm_sysfs_connector_hotplug_event(connector);
|
||||
|
||||
if (connector->privacy_screen)
|
||||
drm_privacy_screen_register_notifier(connector->privacy_screen,
|
||||
&connector->privacy_screen_notifier);
|
||||
|
||||
mutex_lock(&connector_list_lock);
|
||||
list_add_tail(&connector->global_connector_list_entry, &connector_list);
|
||||
|
@ -578,6 +588,11 @@ void drm_connector_unregister(struct drm_connector *connector)
|
|||
list_del_init(&connector->global_connector_list_entry);
|
||||
mutex_unlock(&connector_list_lock);
|
||||
|
||||
if (connector->privacy_screen)
|
||||
drm_privacy_screen_unregister_notifier(
|
||||
connector->privacy_screen,
|
||||
&connector->privacy_screen_notifier);
|
||||
|
||||
if (connector->funcs->early_unregister)
|
||||
connector->funcs->early_unregister(connector);
|
||||
|
||||
|
@ -1271,6 +1286,46 @@ static const struct drm_prop_enum_list dp_colorspaces[] = {
|
|||
* For DVI-I and TVout there is also a matching property "select subconnector"
|
||||
* allowing to switch between signal types.
|
||||
* DP subconnector corresponds to a downstream port.
|
||||
*
|
||||
* privacy-screen sw-state, privacy-screen hw-state:
|
||||
* These 2 optional properties can be used to query the state of the
|
||||
* electronic privacy screen that is available on some displays; and in
|
||||
* some cases also control the state. If a driver implements these
|
||||
* properties then both properties must be present.
|
||||
*
|
||||
* "privacy-screen hw-state" is read-only and reflects the actual state
|
||||
* of the privacy-screen, possible values: "Enabled", "Disabled,
|
||||
* "Enabled-locked", "Disabled-locked". The locked states indicate
|
||||
* that the state cannot be changed through the DRM API. E.g. there
|
||||
* might be devices where the firmware-setup options, or a hardware
|
||||
* slider-switch, offer always on / off modes.
|
||||
*
|
||||
* "privacy-screen sw-state" can be set to change the privacy-screen state
|
||||
* when not locked. In this case the driver must update the hw-state
|
||||
* property to reflect the new state on completion of the commit of the
|
||||
* sw-state property. Setting the sw-state property when the hw-state is
|
||||
* locked must be interpreted by the driver as a request to change the
|
||||
* state to the set state when the hw-state becomes unlocked. E.g. if
|
||||
* "privacy-screen hw-state" is "Enabled-locked" and the sw-state
|
||||
* gets set to "Disabled" followed by the user unlocking the state by
|
||||
* changing the slider-switch position, then the driver must set the
|
||||
* state to "Disabled" upon receiving the unlock event.
|
||||
*
|
||||
* In some cases the privacy-screen's actual state might change outside of
|
||||
* control of the DRM code. E.g. there might be a firmware handled hotkey
|
||||
* which toggles the actual state, or the actual state might be changed
|
||||
* through another userspace API such as writing /proc/acpi/ibm/lcdshadow.
|
||||
* In this case the driver must update both the hw-state and the sw-state
|
||||
* to reflect the new value, overwriting any pending state requests in the
|
||||
* sw-state. Any pending sw-state requests are thus discarded.
|
||||
*
|
||||
* Note that the ability for the state to change outside of control of
|
||||
* the DRM master process means that userspace must not cache the value
|
||||
* of the sw-state. Caching the sw-state value and including it in later
|
||||
* atomic commits may lead to overriding a state change done through e.g.
|
||||
* a firmware handled hotkey. Therefor userspace must not include the
|
||||
* privacy-screen sw-state in an atomic commit unless it wants to change
|
||||
* its value.
|
||||
*/
|
||||
|
||||
int drm_connector_create_standard_properties(struct drm_device *dev)
|
||||
|
@ -2365,6 +2420,154 @@ int drm_connector_set_panel_orientation_with_quirk(
|
|||
}
|
||||
EXPORT_SYMBOL(drm_connector_set_panel_orientation_with_quirk);
|
||||
|
||||
static const struct drm_prop_enum_list privacy_screen_enum[] = {
|
||||
{ PRIVACY_SCREEN_DISABLED, "Disabled" },
|
||||
{ PRIVACY_SCREEN_ENABLED, "Enabled" },
|
||||
{ PRIVACY_SCREEN_DISABLED_LOCKED, "Disabled-locked" },
|
||||
{ PRIVACY_SCREEN_ENABLED_LOCKED, "Enabled-locked" },
|
||||
};
|
||||
|
||||
/**
|
||||
* drm_connector_create_privacy_screen_properties - create the drm connecter's
|
||||
* privacy-screen properties.
|
||||
* @connector: connector for which to create the privacy-screen properties
|
||||
*
|
||||
* This function creates the "privacy-screen sw-state" and "privacy-screen
|
||||
* hw-state" properties for the connector. They are not attached.
|
||||
*/
|
||||
void
|
||||
drm_connector_create_privacy_screen_properties(struct drm_connector *connector)
|
||||
{
|
||||
if (connector->privacy_screen_sw_state_property)
|
||||
return;
|
||||
|
||||
/* Note sw-state only supports the first 2 values of the enum */
|
||||
connector->privacy_screen_sw_state_property =
|
||||
drm_property_create_enum(connector->dev, DRM_MODE_PROP_ENUM,
|
||||
"privacy-screen sw-state",
|
||||
privacy_screen_enum, 2);
|
||||
|
||||
connector->privacy_screen_hw_state_property =
|
||||
drm_property_create_enum(connector->dev,
|
||||
DRM_MODE_PROP_IMMUTABLE | DRM_MODE_PROP_ENUM,
|
||||
"privacy-screen hw-state",
|
||||
privacy_screen_enum,
|
||||
ARRAY_SIZE(privacy_screen_enum));
|
||||
}
|
||||
EXPORT_SYMBOL(drm_connector_create_privacy_screen_properties);
|
||||
|
||||
/**
|
||||
* drm_connector_attach_privacy_screen_properties - attach the drm connecter's
|
||||
* privacy-screen properties.
|
||||
* @connector: connector on which to attach the privacy-screen properties
|
||||
*
|
||||
* This function attaches the "privacy-screen sw-state" and "privacy-screen
|
||||
* hw-state" properties to the connector. The initial state of both is set
|
||||
* to "Disabled".
|
||||
*/
|
||||
void
|
||||
drm_connector_attach_privacy_screen_properties(struct drm_connector *connector)
|
||||
{
|
||||
if (!connector->privacy_screen_sw_state_property)
|
||||
return;
|
||||
|
||||
drm_object_attach_property(&connector->base,
|
||||
connector->privacy_screen_sw_state_property,
|
||||
PRIVACY_SCREEN_DISABLED);
|
||||
|
||||
drm_object_attach_property(&connector->base,
|
||||
connector->privacy_screen_hw_state_property,
|
||||
PRIVACY_SCREEN_DISABLED);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_connector_attach_privacy_screen_properties);
|
||||
|
||||
static void drm_connector_update_privacy_screen_properties(
|
||||
struct drm_connector *connector, bool set_sw_state)
|
||||
{
|
||||
enum drm_privacy_screen_status sw_state, hw_state;
|
||||
|
||||
drm_privacy_screen_get_state(connector->privacy_screen,
|
||||
&sw_state, &hw_state);
|
||||
|
||||
if (set_sw_state)
|
||||
connector->state->privacy_screen_sw_state = sw_state;
|
||||
drm_object_property_set_value(&connector->base,
|
||||
connector->privacy_screen_hw_state_property, hw_state);
|
||||
}
|
||||
|
||||
static int drm_connector_privacy_screen_notifier(
|
||||
struct notifier_block *nb, unsigned long action, void *data)
|
||||
{
|
||||
struct drm_connector *connector =
|
||||
container_of(nb, struct drm_connector, privacy_screen_notifier);
|
||||
struct drm_device *dev = connector->dev;
|
||||
|
||||
drm_modeset_lock(&dev->mode_config.connection_mutex, NULL);
|
||||
drm_connector_update_privacy_screen_properties(connector, true);
|
||||
drm_modeset_unlock(&dev->mode_config.connection_mutex);
|
||||
|
||||
drm_sysfs_connector_status_event(connector,
|
||||
connector->privacy_screen_sw_state_property);
|
||||
drm_sysfs_connector_status_event(connector,
|
||||
connector->privacy_screen_hw_state_property);
|
||||
|
||||
return NOTIFY_DONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_connector_attach_privacy_screen_provider - attach a privacy-screen to
|
||||
* the connector
|
||||
* @connector: connector to attach the privacy-screen to
|
||||
* @priv: drm_privacy_screen to attach
|
||||
*
|
||||
* Create and attach the standard privacy-screen properties and register
|
||||
* a generic notifier for generating sysfs-connector-status-events
|
||||
* on external changes to the privacy-screen status.
|
||||
* This function takes ownership of the passed in drm_privacy_screen and will
|
||||
* call drm_privacy_screen_put() on it when the connector is destroyed.
|
||||
*/
|
||||
void drm_connector_attach_privacy_screen_provider(
|
||||
struct drm_connector *connector, struct drm_privacy_screen *priv)
|
||||
{
|
||||
connector->privacy_screen = priv;
|
||||
connector->privacy_screen_notifier.notifier_call =
|
||||
drm_connector_privacy_screen_notifier;
|
||||
|
||||
drm_connector_create_privacy_screen_properties(connector);
|
||||
drm_connector_update_privacy_screen_properties(connector, true);
|
||||
drm_connector_attach_privacy_screen_properties(connector);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_connector_attach_privacy_screen_provider);
|
||||
|
||||
/**
|
||||
* drm_connector_update_privacy_screen - update connector's privacy-screen sw-state
|
||||
* @connector_state: connector-state to update the privacy-screen for
|
||||
*
|
||||
* This function calls drm_privacy_screen_set_sw_state() on the connector's
|
||||
* privacy-screen.
|
||||
*
|
||||
* If the connector has no privacy-screen, then this is a no-op.
|
||||
*/
|
||||
void drm_connector_update_privacy_screen(const struct drm_connector_state *connector_state)
|
||||
{
|
||||
struct drm_connector *connector = connector_state->connector;
|
||||
int ret;
|
||||
|
||||
if (!connector->privacy_screen)
|
||||
return;
|
||||
|
||||
ret = drm_privacy_screen_set_sw_state(connector->privacy_screen,
|
||||
connector_state->privacy_screen_sw_state);
|
||||
if (ret) {
|
||||
drm_err(connector->dev, "Error updating privacy-screen sw_state\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* The hw_state property value may have changed, update it. */
|
||||
drm_connector_update_privacy_screen_properties(connector, false);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_connector_update_privacy_screen);
|
||||
|
||||
int drm_connector_set_obj_prop(struct drm_mode_object *obj,
|
||||
struct drm_property *property,
|
||||
uint64_t value)
|
||||
|
|
|
@ -154,38 +154,155 @@ u8 drm_dp_get_adjust_request_post_cursor(const u8 link_status[DP_LINK_STATUS_SIZ
|
|||
}
|
||||
EXPORT_SYMBOL(drm_dp_get_adjust_request_post_cursor);
|
||||
|
||||
static int __8b10b_clock_recovery_delay_us(const struct drm_dp_aux *aux, u8 rd_interval)
|
||||
{
|
||||
if (rd_interval > 4)
|
||||
drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x (max 4)\n",
|
||||
aux->name, rd_interval);
|
||||
|
||||
if (rd_interval == 0)
|
||||
return 100;
|
||||
|
||||
return rd_interval * 4 * USEC_PER_MSEC;
|
||||
}
|
||||
|
||||
static int __8b10b_channel_eq_delay_us(const struct drm_dp_aux *aux, u8 rd_interval)
|
||||
{
|
||||
if (rd_interval > 4)
|
||||
drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x (max 4)\n",
|
||||
aux->name, rd_interval);
|
||||
|
||||
if (rd_interval == 0)
|
||||
return 400;
|
||||
|
||||
return rd_interval * 4 * USEC_PER_MSEC;
|
||||
}
|
||||
|
||||
static int __128b132b_channel_eq_delay_us(const struct drm_dp_aux *aux, u8 rd_interval)
|
||||
{
|
||||
switch (rd_interval) {
|
||||
default:
|
||||
drm_dbg_kms(aux->drm_dev, "%s: invalid AUX interval 0x%02x\n",
|
||||
aux->name, rd_interval);
|
||||
fallthrough;
|
||||
case DP_128B132B_TRAINING_AUX_RD_INTERVAL_400_US:
|
||||
return 400;
|
||||
case DP_128B132B_TRAINING_AUX_RD_INTERVAL_4_MS:
|
||||
return 4000;
|
||||
case DP_128B132B_TRAINING_AUX_RD_INTERVAL_8_MS:
|
||||
return 8000;
|
||||
case DP_128B132B_TRAINING_AUX_RD_INTERVAL_12_MS:
|
||||
return 12000;
|
||||
case DP_128B132B_TRAINING_AUX_RD_INTERVAL_16_MS:
|
||||
return 16000;
|
||||
case DP_128B132B_TRAINING_AUX_RD_INTERVAL_32_MS:
|
||||
return 32000;
|
||||
case DP_128B132B_TRAINING_AUX_RD_INTERVAL_64_MS:
|
||||
return 64000;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The link training delays are different for:
|
||||
*
|
||||
* - Clock recovery vs. channel equalization
|
||||
* - DPRX vs. LTTPR
|
||||
* - 128b/132b vs. 8b/10b
|
||||
* - DPCD rev 1.3 vs. later
|
||||
*
|
||||
* Get the correct delay in us, reading DPCD if necessary.
|
||||
*/
|
||||
static int __read_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE],
|
||||
enum drm_dp_phy dp_phy, bool uhbr, bool cr)
|
||||
{
|
||||
int (*parse)(const struct drm_dp_aux *aux, u8 rd_interval);
|
||||
unsigned int offset;
|
||||
u8 rd_interval, mask;
|
||||
|
||||
if (dp_phy == DP_PHY_DPRX) {
|
||||
if (uhbr) {
|
||||
if (cr)
|
||||
return 100;
|
||||
|
||||
offset = DP_128B132B_TRAINING_AUX_RD_INTERVAL;
|
||||
mask = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK;
|
||||
parse = __128b132b_channel_eq_delay_us;
|
||||
} else {
|
||||
if (cr && dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14)
|
||||
return 100;
|
||||
|
||||
offset = DP_TRAINING_AUX_RD_INTERVAL;
|
||||
mask = DP_TRAINING_AUX_RD_MASK;
|
||||
if (cr)
|
||||
parse = __8b10b_clock_recovery_delay_us;
|
||||
else
|
||||
parse = __8b10b_channel_eq_delay_us;
|
||||
}
|
||||
} else {
|
||||
if (uhbr) {
|
||||
offset = DP_128B132B_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy);
|
||||
mask = DP_128B132B_TRAINING_AUX_RD_INTERVAL_MASK;
|
||||
parse = __128b132b_channel_eq_delay_us;
|
||||
} else {
|
||||
if (cr)
|
||||
return 100;
|
||||
|
||||
offset = DP_TRAINING_AUX_RD_INTERVAL_PHY_REPEATER(dp_phy);
|
||||
mask = DP_TRAINING_AUX_RD_MASK;
|
||||
parse = __8b10b_channel_eq_delay_us;
|
||||
}
|
||||
}
|
||||
|
||||
if (offset < DP_RECEIVER_CAP_SIZE) {
|
||||
rd_interval = dpcd[offset];
|
||||
} else {
|
||||
if (drm_dp_dpcd_readb(aux, offset, &rd_interval) != 1) {
|
||||
drm_dbg_kms(aux->drm_dev, "%s: failed rd interval read\n",
|
||||
aux->name);
|
||||
/* arbitrary default delay */
|
||||
return 400;
|
||||
}
|
||||
}
|
||||
|
||||
return parse(aux, rd_interval & mask);
|
||||
}
|
||||
|
||||
int drm_dp_read_clock_recovery_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE],
|
||||
enum drm_dp_phy dp_phy, bool uhbr)
|
||||
{
|
||||
return __read_delay(aux, dpcd, dp_phy, uhbr, true);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dp_read_clock_recovery_delay);
|
||||
|
||||
int drm_dp_read_channel_eq_delay(struct drm_dp_aux *aux, const u8 dpcd[DP_RECEIVER_CAP_SIZE],
|
||||
enum drm_dp_phy dp_phy, bool uhbr)
|
||||
{
|
||||
return __read_delay(aux, dpcd, dp_phy, uhbr, false);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dp_read_channel_eq_delay);
|
||||
|
||||
void drm_dp_link_train_clock_recovery_delay(const struct drm_dp_aux *aux,
|
||||
const u8 dpcd[DP_RECEIVER_CAP_SIZE])
|
||||
{
|
||||
unsigned long rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
|
||||
DP_TRAINING_AUX_RD_MASK;
|
||||
u8 rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
|
||||
DP_TRAINING_AUX_RD_MASK;
|
||||
int delay_us;
|
||||
|
||||
if (rd_interval > 4)
|
||||
drm_dbg_kms(aux->drm_dev, "%s: AUX interval %lu, out of range (max 4)\n",
|
||||
aux->name, rd_interval);
|
||||
|
||||
if (rd_interval == 0 || dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14)
|
||||
rd_interval = 100;
|
||||
if (dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14)
|
||||
delay_us = 100;
|
||||
else
|
||||
rd_interval *= 4 * USEC_PER_MSEC;
|
||||
delay_us = __8b10b_clock_recovery_delay_us(aux, rd_interval);
|
||||
|
||||
usleep_range(rd_interval, rd_interval * 2);
|
||||
usleep_range(delay_us, delay_us * 2);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_dp_link_train_clock_recovery_delay);
|
||||
|
||||
static void __drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux,
|
||||
unsigned long rd_interval)
|
||||
u8 rd_interval)
|
||||
{
|
||||
if (rd_interval > 4)
|
||||
drm_dbg_kms(aux->drm_dev, "%s: AUX interval %lu, out of range (max 4)\n",
|
||||
aux->name, rd_interval);
|
||||
int delay_us = __8b10b_channel_eq_delay_us(aux, rd_interval);
|
||||
|
||||
if (rd_interval == 0)
|
||||
rd_interval = 400;
|
||||
else
|
||||
rd_interval *= 4 * USEC_PER_MSEC;
|
||||
|
||||
usleep_range(rd_interval, rd_interval * 2);
|
||||
usleep_range(delay_us, delay_us * 2);
|
||||
}
|
||||
|
||||
void drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux,
|
||||
|
@ -3173,6 +3290,10 @@ int drm_edp_backlight_set_level(struct drm_dp_aux *aux, const struct drm_edp_bac
|
|||
int ret;
|
||||
u8 buf[2] = { 0 };
|
||||
|
||||
/* The panel uses the PWM for controlling brightness levels */
|
||||
if (!bl->aux_set)
|
||||
return 0;
|
||||
|
||||
if (bl->lsb_reg_used) {
|
||||
buf[0] = (level & 0xff00) >> 8;
|
||||
buf[1] = (level & 0x00ff);
|
||||
|
@ -3199,7 +3320,7 @@ drm_edp_backlight_set_enable(struct drm_dp_aux *aux, const struct drm_edp_backli
|
|||
int ret;
|
||||
u8 buf;
|
||||
|
||||
/* The panel uses something other then DPCD for enabling its backlight */
|
||||
/* This panel uses the EDP_BL_PWR GPIO for enablement */
|
||||
if (!bl->aux_enable)
|
||||
return 0;
|
||||
|
||||
|
@ -3234,11 +3355,11 @@ drm_edp_backlight_set_enable(struct drm_dp_aux *aux, const struct drm_edp_backli
|
|||
* restoring any important backlight state such as the given backlight level, the brightness byte
|
||||
* count, backlight frequency, etc.
|
||||
*
|
||||
* Note that certain panels, while supporting brightness level controls over DPCD, may not support
|
||||
* having their backlights enabled via the standard %DP_EDP_DISPLAY_CONTROL_REGISTER. On such panels
|
||||
* &drm_edp_backlight_info.aux_enable will be set to %false, this function will skip the step of
|
||||
* programming the %DP_EDP_DISPLAY_CONTROL_REGISTER, and the driver must perform the required
|
||||
* implementation specific step for enabling the backlight after calling this function.
|
||||
* Note that certain panels do not support being enabled or disabled via DPCD, but instead require
|
||||
* that the driver handle enabling/disabling the panel through implementation-specific means using
|
||||
* the EDP_BL_PWR GPIO. For such panels, &drm_edp_backlight_info.aux_enable will be set to %false,
|
||||
* this function becomes a no-op, and the driver is expected to handle powering the panel on using
|
||||
* the EDP_BL_PWR GPIO.
|
||||
*
|
||||
* Returns: %0 on success, negative error code on failure.
|
||||
*/
|
||||
|
@ -3246,27 +3367,18 @@ int drm_edp_backlight_enable(struct drm_dp_aux *aux, const struct drm_edp_backli
|
|||
const u16 level)
|
||||
{
|
||||
int ret;
|
||||
u8 dpcd_buf, new_dpcd_buf;
|
||||
u8 dpcd_buf;
|
||||
|
||||
ret = drm_dp_dpcd_readb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &dpcd_buf);
|
||||
if (ret != 1) {
|
||||
drm_dbg_kms(aux->drm_dev,
|
||||
"%s: Failed to read backlight mode: %d\n", aux->name, ret);
|
||||
return ret < 0 ? ret : -EIO;
|
||||
}
|
||||
if (bl->aux_set)
|
||||
dpcd_buf = DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD;
|
||||
else
|
||||
dpcd_buf = DP_EDP_BACKLIGHT_CONTROL_MODE_PWM;
|
||||
|
||||
new_dpcd_buf = dpcd_buf;
|
||||
|
||||
if ((dpcd_buf & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK) != DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) {
|
||||
new_dpcd_buf &= ~DP_EDP_BACKLIGHT_CONTROL_MODE_MASK;
|
||||
new_dpcd_buf |= DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD;
|
||||
|
||||
if (bl->pwmgen_bit_count) {
|
||||
ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, bl->pwmgen_bit_count);
|
||||
if (ret != 1)
|
||||
drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n",
|
||||
aux->name, ret);
|
||||
}
|
||||
if (bl->pwmgen_bit_count) {
|
||||
ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, bl->pwmgen_bit_count);
|
||||
if (ret != 1)
|
||||
drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n",
|
||||
aux->name, ret);
|
||||
}
|
||||
|
||||
if (bl->pwm_freq_pre_divider) {
|
||||
|
@ -3276,16 +3388,14 @@ int drm_edp_backlight_enable(struct drm_dp_aux *aux, const struct drm_edp_backli
|
|||
"%s: Failed to write aux backlight frequency: %d\n",
|
||||
aux->name, ret);
|
||||
else
|
||||
new_dpcd_buf |= DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE;
|
||||
dpcd_buf |= DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE;
|
||||
}
|
||||
|
||||
if (new_dpcd_buf != dpcd_buf) {
|
||||
ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, new_dpcd_buf);
|
||||
if (ret != 1) {
|
||||
drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux backlight mode: %d\n",
|
||||
aux->name, ret);
|
||||
return ret < 0 ? ret : -EIO;
|
||||
}
|
||||
ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, dpcd_buf);
|
||||
if (ret != 1) {
|
||||
drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux backlight mode: %d\n",
|
||||
aux->name, ret);
|
||||
return ret < 0 ? ret : -EIO;
|
||||
}
|
||||
|
||||
ret = drm_edp_backlight_set_level(aux, bl, level);
|
||||
|
@ -3304,12 +3414,13 @@ EXPORT_SYMBOL(drm_edp_backlight_enable);
|
|||
* @aux: The DP AUX channel to use
|
||||
* @bl: Backlight capability info from drm_edp_backlight_init()
|
||||
*
|
||||
* This function handles disabling DPCD backlight controls on a panel over AUX. Note that some
|
||||
* panels have backlights that are enabled/disabled by other means, despite having their brightness
|
||||
* values controlled through DPCD. On such panels &drm_edp_backlight_info.aux_enable will be set to
|
||||
* %false, this function will become a no-op (and we will skip updating
|
||||
* %DP_EDP_DISPLAY_CONTROL_REGISTER), and the driver must take care to perform it's own
|
||||
* implementation specific step for disabling the backlight.
|
||||
* This function handles disabling DPCD backlight controls on a panel over AUX.
|
||||
*
|
||||
* Note that certain panels do not support being enabled or disabled via DPCD, but instead require
|
||||
* that the driver handle enabling/disabling the panel through implementation-specific means using
|
||||
* the EDP_BL_PWR GPIO. For such panels, &drm_edp_backlight_info.aux_enable will be set to %false,
|
||||
* this function becomes a no-op, and the driver is expected to handle powering the panel off using
|
||||
* the EDP_BL_PWR GPIO.
|
||||
*
|
||||
* Returns: %0 on success or no-op, negative error code on failure.
|
||||
*/
|
||||
|
@ -3333,6 +3444,9 @@ drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_inf
|
|||
int ret;
|
||||
u8 pn, pn_min, pn_max;
|
||||
|
||||
if (!bl->aux_set)
|
||||
return 0;
|
||||
|
||||
ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT, &pn);
|
||||
if (ret != 1) {
|
||||
drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap: %d\n",
|
||||
|
@ -3418,7 +3532,7 @@ drm_edp_backlight_probe_max(struct drm_dp_aux *aux, struct drm_edp_backlight_inf
|
|||
}
|
||||
|
||||
static inline int
|
||||
drm_edp_backlight_probe_level(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl,
|
||||
drm_edp_backlight_probe_state(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl,
|
||||
u8 *current_mode)
|
||||
{
|
||||
int ret;
|
||||
|
@ -3433,6 +3547,9 @@ drm_edp_backlight_probe_level(struct drm_dp_aux *aux, struct drm_edp_backlight_i
|
|||
}
|
||||
|
||||
*current_mode = (mode_reg & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK);
|
||||
if (!bl->aux_set)
|
||||
return 0;
|
||||
|
||||
if (*current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) {
|
||||
int size = 1 + bl->lsb_reg_used;
|
||||
|
||||
|
@ -3463,7 +3580,7 @@ drm_edp_backlight_probe_level(struct drm_dp_aux *aux, struct drm_edp_backlight_i
|
|||
* @bl: The &drm_edp_backlight_info struct to fill out with information on the backlight
|
||||
* @driver_pwm_freq_hz: Optional PWM frequency from the driver in hz
|
||||
* @edp_dpcd: A cached copy of the eDP DPCD
|
||||
* @current_level: Where to store the probed brightness level
|
||||
* @current_level: Where to store the probed brightness level, if any
|
||||
* @current_mode: Where to store the currently set backlight control mode
|
||||
*
|
||||
* Initializes a &drm_edp_backlight_info struct by probing @aux for it's backlight capabilities,
|
||||
|
@ -3483,24 +3600,38 @@ drm_edp_backlight_init(struct drm_dp_aux *aux, struct drm_edp_backlight_info *bl
|
|||
|
||||
if (edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP)
|
||||
bl->aux_enable = true;
|
||||
if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP)
|
||||
bl->aux_set = true;
|
||||
if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT)
|
||||
bl->lsb_reg_used = true;
|
||||
|
||||
/* Sanity check caps */
|
||||
if (!bl->aux_set && !(edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_PWM_PIN_CAP)) {
|
||||
drm_dbg_kms(aux->drm_dev,
|
||||
"%s: Panel supports neither AUX or PWM brightness control? Aborting\n",
|
||||
aux->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = drm_edp_backlight_probe_max(aux, bl, driver_pwm_freq_hz, edp_dpcd);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
ret = drm_edp_backlight_probe_level(aux, bl, current_mode);
|
||||
ret = drm_edp_backlight_probe_state(aux, bl, current_mode);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
*current_level = ret;
|
||||
|
||||
drm_dbg_kms(aux->drm_dev,
|
||||
"%s: Found backlight level=%d/%d pwm_freq_pre_divider=%d mode=%x\n",
|
||||
aux->name, *current_level, bl->max, bl->pwm_freq_pre_divider, *current_mode);
|
||||
drm_dbg_kms(aux->drm_dev,
|
||||
"%s: Backlight caps: pwmgen_bit_count=%d lsb_reg_used=%d aux_enable=%d\n",
|
||||
aux->name, bl->pwmgen_bit_count, bl->lsb_reg_used, bl->aux_enable);
|
||||
"%s: Found backlight: aux_set=%d aux_enable=%d mode=%d\n",
|
||||
aux->name, bl->aux_set, bl->aux_enable, *current_mode);
|
||||
if (bl->aux_set) {
|
||||
drm_dbg_kms(aux->drm_dev,
|
||||
"%s: Backlight caps: level=%d/%d pwm_freq_pre_divider=%d lsb_reg_used=%d\n",
|
||||
aux->name, *current_level, bl->max, bl->pwm_freq_pre_divider,
|
||||
bl->lsb_reg_used);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_edp_backlight_init);
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
#include <drm/drm_managed.h>
|
||||
#include <drm/drm_mode_object.h>
|
||||
#include <drm/drm_print.h>
|
||||
#include <drm/drm_privacy_screen_machine.h>
|
||||
|
||||
#include "drm_crtc_internal.h"
|
||||
#include "drm_internal.h"
|
||||
|
@ -581,6 +582,7 @@ static int drm_dev_init(struct drm_device *dev,
|
|||
const struct drm_driver *driver,
|
||||
struct device *parent)
|
||||
{
|
||||
struct inode *inode;
|
||||
int ret;
|
||||
|
||||
if (!drm_core_init_complete) {
|
||||
|
@ -617,13 +619,15 @@ static int drm_dev_init(struct drm_device *dev,
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
dev->anon_inode = drm_fs_inode_new();
|
||||
if (IS_ERR(dev->anon_inode)) {
|
||||
ret = PTR_ERR(dev->anon_inode);
|
||||
inode = drm_fs_inode_new();
|
||||
if (IS_ERR(inode)) {
|
||||
ret = PTR_ERR(inode);
|
||||
DRM_ERROR("Cannot allocate anonymous inode: %d\n", ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
dev->anon_inode = inode;
|
||||
|
||||
if (drm_core_check_feature(dev, DRIVER_RENDER)) {
|
||||
ret = drm_minor_alloc(dev, DRM_MINOR_RENDER);
|
||||
if (ret)
|
||||
|
@ -1029,6 +1033,7 @@ static const struct file_operations drm_stub_fops = {
|
|||
|
||||
static void drm_core_exit(void)
|
||||
{
|
||||
drm_privacy_screen_lookup_exit();
|
||||
unregister_chrdev(DRM_MAJOR, "drm");
|
||||
debugfs_remove(drm_debugfs_root);
|
||||
drm_sysfs_destroy();
|
||||
|
@ -1056,6 +1061,8 @@ static int __init drm_core_init(void)
|
|||
if (ret < 0)
|
||||
goto error;
|
||||
|
||||
drm_privacy_screen_lookup_init();
|
||||
|
||||
drm_core_init_complete = true;
|
||||
|
||||
DRM_DEBUG("Initialized\n");
|
||||
|
|
|
@ -2338,7 +2338,7 @@ static int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
|
|||
return PTR_ERR(fbi);
|
||||
|
||||
fbi->fbops = &drm_fbdev_fb_ops;
|
||||
fbi->screen_size = fb->height * fb->pitches[0];
|
||||
fbi->screen_size = sizes->surface_height * fb->pitches[0];
|
||||
fbi->fix.smem_len = fbi->screen_size;
|
||||
|
||||
drm_fb_helper_fill_info(fbi, fb_helper, sizes);
|
||||
|
|
|
@ -17,71 +17,91 @@
|
|||
#include <drm/drm_fourcc.h>
|
||||
#include <drm/drm_rect.h>
|
||||
|
||||
static unsigned int clip_offset(struct drm_rect *clip,
|
||||
unsigned int pitch, unsigned int cpp)
|
||||
static unsigned int clip_offset(const struct drm_rect *clip, unsigned int pitch, unsigned int cpp)
|
||||
{
|
||||
return clip->y1 * pitch + clip->x1 * cpp;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_fb_clip_offset - Returns the clipping rectangles byte-offset in a framebuffer
|
||||
* @pitch: Framebuffer line pitch in byte
|
||||
* @format: Framebuffer format
|
||||
* @clip: Clip rectangle
|
||||
*
|
||||
* Returns:
|
||||
* The byte offset of the clip rectangle's top-left corner within the framebuffer.
|
||||
*/
|
||||
unsigned int drm_fb_clip_offset(unsigned int pitch, const struct drm_format_info *format,
|
||||
const struct drm_rect *clip)
|
||||
{
|
||||
return clip_offset(clip, pitch, format->cpp[0]);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_fb_clip_offset);
|
||||
|
||||
/**
|
||||
* drm_fb_memcpy - Copy clip buffer
|
||||
* @dst: Destination buffer
|
||||
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
|
||||
* @vaddr: Source buffer
|
||||
* @fb: DRM framebuffer
|
||||
* @clip: Clip rectangle area to copy
|
||||
*
|
||||
* This function does not apply clipping on dst, i.e. the destination
|
||||
* is a small buffer containing the clip rect only.
|
||||
* is at the top-left corner.
|
||||
*/
|
||||
void drm_fb_memcpy(void *dst, void *vaddr, struct drm_framebuffer *fb,
|
||||
struct drm_rect *clip)
|
||||
void drm_fb_memcpy(void *dst, unsigned int dst_pitch, const void *vaddr,
|
||||
const struct drm_framebuffer *fb, const struct drm_rect *clip)
|
||||
{
|
||||
unsigned int cpp = fb->format->cpp[0];
|
||||
size_t len = (clip->x2 - clip->x1) * cpp;
|
||||
unsigned int y, lines = clip->y2 - clip->y1;
|
||||
|
||||
if (!dst_pitch)
|
||||
dst_pitch = len;
|
||||
|
||||
vaddr += clip_offset(clip, fb->pitches[0], cpp);
|
||||
for (y = 0; y < lines; y++) {
|
||||
memcpy(dst, vaddr, len);
|
||||
vaddr += fb->pitches[0];
|
||||
dst += len;
|
||||
dst += dst_pitch;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(drm_fb_memcpy);
|
||||
|
||||
/**
|
||||
* drm_fb_memcpy_dstclip - Copy clip buffer
|
||||
* drm_fb_memcpy_toio - Copy clip buffer
|
||||
* @dst: Destination buffer (iomem)
|
||||
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
|
||||
* @vaddr: Source buffer
|
||||
* @fb: DRM framebuffer
|
||||
* @clip: Clip rectangle area to copy
|
||||
*
|
||||
* This function applies clipping on dst, i.e. the destination is a
|
||||
* full (iomem) framebuffer but only the clip rect content is copied over.
|
||||
* This function does not apply clipping on dst, i.e. the destination
|
||||
* is at the top-left corner.
|
||||
*/
|
||||
void drm_fb_memcpy_dstclip(void __iomem *dst, unsigned int dst_pitch,
|
||||
void *vaddr, struct drm_framebuffer *fb,
|
||||
struct drm_rect *clip)
|
||||
void drm_fb_memcpy_toio(void __iomem *dst, unsigned int dst_pitch, const void *vaddr,
|
||||
const struct drm_framebuffer *fb, const struct drm_rect *clip)
|
||||
{
|
||||
unsigned int cpp = fb->format->cpp[0];
|
||||
unsigned int offset = clip_offset(clip, dst_pitch, cpp);
|
||||
size_t len = (clip->x2 - clip->x1) * cpp;
|
||||
unsigned int y, lines = clip->y2 - clip->y1;
|
||||
|
||||
vaddr += offset;
|
||||
dst += offset;
|
||||
if (!dst_pitch)
|
||||
dst_pitch = len;
|
||||
|
||||
vaddr += clip_offset(clip, fb->pitches[0], cpp);
|
||||
for (y = 0; y < lines; y++) {
|
||||
memcpy_toio(dst, vaddr, len);
|
||||
vaddr += fb->pitches[0];
|
||||
dst += dst_pitch;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(drm_fb_memcpy_dstclip);
|
||||
EXPORT_SYMBOL(drm_fb_memcpy_toio);
|
||||
|
||||
/**
|
||||
* drm_fb_swab - Swap bytes into clip buffer
|
||||
* @dst: Destination buffer
|
||||
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
|
||||
* @src: Source buffer
|
||||
* @fb: DRM framebuffer
|
||||
* @clip: Clip rectangle area to copy
|
||||
|
@ -91,21 +111,27 @@ EXPORT_SYMBOL(drm_fb_memcpy_dstclip);
|
|||
* time to speed up slow uncached reads.
|
||||
*
|
||||
* This function does not apply clipping on dst, i.e. the destination
|
||||
* is a small buffer containing the clip rect only.
|
||||
* is at the top-left corner.
|
||||
*/
|
||||
void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
|
||||
struct drm_rect *clip, bool cached)
|
||||
void drm_fb_swab(void *dst, unsigned int dst_pitch, const void *src,
|
||||
const struct drm_framebuffer *fb, const struct drm_rect *clip,
|
||||
bool cached)
|
||||
{
|
||||
u8 cpp = fb->format->cpp[0];
|
||||
size_t len = drm_rect_width(clip) * cpp;
|
||||
u16 *src16, *dst16 = dst;
|
||||
u32 *src32, *dst32 = dst;
|
||||
const u16 *src16;
|
||||
const u32 *src32;
|
||||
u16 *dst16;
|
||||
u32 *dst32;
|
||||
unsigned int x, y;
|
||||
void *buf = NULL;
|
||||
|
||||
if (WARN_ON_ONCE(cpp != 2 && cpp != 4))
|
||||
return;
|
||||
|
||||
if (!dst_pitch)
|
||||
dst_pitch = len;
|
||||
|
||||
if (!cached)
|
||||
buf = kmalloc(len, GFP_KERNEL);
|
||||
|
||||
|
@ -121,6 +147,9 @@ void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
|
|||
src32 = src;
|
||||
}
|
||||
|
||||
dst16 = dst;
|
||||
dst32 = dst;
|
||||
|
||||
for (x = clip->x1; x < clip->x2; x++) {
|
||||
if (cpp == 4)
|
||||
*dst32++ = swab32(*src32++);
|
||||
|
@ -129,13 +158,14 @@ void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
|
|||
}
|
||||
|
||||
src += fb->pitches[0];
|
||||
dst += dst_pitch;
|
||||
}
|
||||
|
||||
kfree(buf);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_fb_swab);
|
||||
|
||||
static void drm_fb_xrgb8888_to_rgb332_line(u8 *dbuf, __le32 *sbuf, unsigned int pixels)
|
||||
static void drm_fb_xrgb8888_to_rgb332_line(u8 *dbuf, const __le32 *sbuf, unsigned int pixels)
|
||||
{
|
||||
unsigned int x;
|
||||
u32 pix;
|
||||
|
@ -151,23 +181,24 @@ static void drm_fb_xrgb8888_to_rgb332_line(u8 *dbuf, __le32 *sbuf, unsigned int
|
|||
/**
|
||||
* drm_fb_xrgb8888_to_rgb332 - Convert XRGB8888 to RGB332 clip buffer
|
||||
* @dst: RGB332 destination buffer
|
||||
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
|
||||
* @src: XRGB8888 source buffer
|
||||
* @fb: DRM framebuffer
|
||||
* @clip: Clip rectangle area to copy
|
||||
*
|
||||
* Drivers can use this function for RGB332 devices that don't natively support XRGB8888.
|
||||
*
|
||||
* This function does not apply clipping on dst, i.e. the destination is a small buffer
|
||||
* containing the clip rect only.
|
||||
*/
|
||||
void drm_fb_xrgb8888_to_rgb332(void *dst, void *src, struct drm_framebuffer *fb,
|
||||
struct drm_rect *clip)
|
||||
void drm_fb_xrgb8888_to_rgb332(void *dst, unsigned int dst_pitch, const void *src,
|
||||
const struct drm_framebuffer *fb, const struct drm_rect *clip)
|
||||
{
|
||||
size_t width = drm_rect_width(clip);
|
||||
size_t src_len = width * sizeof(u32);
|
||||
unsigned int y;
|
||||
void *sbuf;
|
||||
|
||||
if (!dst_pitch)
|
||||
dst_pitch = width;
|
||||
|
||||
/* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */
|
||||
sbuf = kmalloc(src_len, GFP_KERNEL);
|
||||
if (!sbuf)
|
||||
|
@ -178,14 +209,14 @@ void drm_fb_xrgb8888_to_rgb332(void *dst, void *src, struct drm_framebuffer *fb,
|
|||
memcpy(sbuf, src, src_len);
|
||||
drm_fb_xrgb8888_to_rgb332_line(dst, sbuf, width);
|
||||
src += fb->pitches[0];
|
||||
dst += width;
|
||||
dst += dst_pitch;
|
||||
}
|
||||
|
||||
kfree(sbuf);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb332);
|
||||
|
||||
static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, u32 *sbuf,
|
||||
static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, const u32 *sbuf,
|
||||
unsigned int pixels,
|
||||
bool swab)
|
||||
{
|
||||
|
@ -206,6 +237,7 @@ static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, u32 *sbuf,
|
|||
/**
|
||||
* drm_fb_xrgb8888_to_rgb565 - Convert XRGB8888 to RGB565 clip buffer
|
||||
* @dst: RGB565 destination buffer
|
||||
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
|
||||
* @vaddr: XRGB8888 source buffer
|
||||
* @fb: DRM framebuffer
|
||||
* @clip: Clip rectangle area to copy
|
||||
|
@ -213,13 +245,10 @@ static void drm_fb_xrgb8888_to_rgb565_line(u16 *dbuf, u32 *sbuf,
|
|||
*
|
||||
* Drivers can use this function for RGB565 devices that don't natively
|
||||
* support XRGB8888.
|
||||
*
|
||||
* This function does not apply clipping on dst, i.e. the destination
|
||||
* is a small buffer containing the clip rect only.
|
||||
*/
|
||||
void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr,
|
||||
struct drm_framebuffer *fb,
|
||||
struct drm_rect *clip, bool swab)
|
||||
void drm_fb_xrgb8888_to_rgb565(void *dst, unsigned int dst_pitch, const void *vaddr,
|
||||
const struct drm_framebuffer *fb, const struct drm_rect *clip,
|
||||
bool swab)
|
||||
{
|
||||
size_t linepixels = clip->x2 - clip->x1;
|
||||
size_t src_len = linepixels * sizeof(u32);
|
||||
|
@ -227,6 +256,9 @@ void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr,
|
|||
unsigned y, lines = clip->y2 - clip->y1;
|
||||
void *sbuf;
|
||||
|
||||
if (!dst_pitch)
|
||||
dst_pitch = dst_len;
|
||||
|
||||
/*
|
||||
* The cma memory is write-combined so reads are uncached.
|
||||
* Speed up by fetching one line at a time.
|
||||
|
@ -240,7 +272,7 @@ void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr,
|
|||
memcpy(sbuf, vaddr, src_len);
|
||||
drm_fb_xrgb8888_to_rgb565_line(dst, sbuf, linepixels, swab);
|
||||
vaddr += fb->pitches[0];
|
||||
dst += dst_len;
|
||||
dst += dst_pitch;
|
||||
}
|
||||
|
||||
kfree(sbuf);
|
||||
|
@ -248,9 +280,9 @@ void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr,
|
|||
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565);
|
||||
|
||||
/**
|
||||
* drm_fb_xrgb8888_to_rgb565_dstclip - Convert XRGB8888 to RGB565 clip buffer
|
||||
* drm_fb_xrgb8888_to_rgb565_toio - Convert XRGB8888 to RGB565 clip buffer
|
||||
* @dst: RGB565 destination buffer (iomem)
|
||||
* @dst_pitch: destination buffer pitch
|
||||
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
|
||||
* @vaddr: XRGB8888 source buffer
|
||||
* @fb: DRM framebuffer
|
||||
* @clip: Clip rectangle area to copy
|
||||
|
@ -258,37 +290,36 @@ EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565);
|
|||
*
|
||||
* Drivers can use this function for RGB565 devices that don't natively
|
||||
* support XRGB8888.
|
||||
*
|
||||
* This function applies clipping on dst, i.e. the destination is a
|
||||
* full (iomem) framebuffer but only the clip rect content is copied over.
|
||||
*/
|
||||
void drm_fb_xrgb8888_to_rgb565_dstclip(void __iomem *dst, unsigned int dst_pitch,
|
||||
void *vaddr, struct drm_framebuffer *fb,
|
||||
struct drm_rect *clip, bool swab)
|
||||
void drm_fb_xrgb8888_to_rgb565_toio(void __iomem *dst, unsigned int dst_pitch,
|
||||
const void *vaddr, const struct drm_framebuffer *fb,
|
||||
const struct drm_rect *clip, bool swab)
|
||||
{
|
||||
size_t linepixels = clip->x2 - clip->x1;
|
||||
size_t dst_len = linepixels * sizeof(u16);
|
||||
unsigned y, lines = clip->y2 - clip->y1;
|
||||
void *dbuf;
|
||||
|
||||
if (!dst_pitch)
|
||||
dst_pitch = dst_len;
|
||||
|
||||
dbuf = kmalloc(dst_len, GFP_KERNEL);
|
||||
if (!dbuf)
|
||||
return;
|
||||
|
||||
vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
|
||||
dst += clip_offset(clip, dst_pitch, sizeof(u16));
|
||||
for (y = 0; y < lines; y++) {
|
||||
drm_fb_xrgb8888_to_rgb565_line(dbuf, vaddr, linepixels, swab);
|
||||
memcpy_toio(dst, dbuf, dst_len);
|
||||
vaddr += fb->pitches[0];
|
||||
dst += dst_len;
|
||||
dst += dst_pitch;
|
||||
}
|
||||
|
||||
kfree(dbuf);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565_dstclip);
|
||||
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565_toio);
|
||||
|
||||
static void drm_fb_xrgb8888_to_rgb888_line(u8 *dbuf, u32 *sbuf,
|
||||
static void drm_fb_xrgb8888_to_rgb888_line(u8 *dbuf, const u32 *sbuf,
|
||||
unsigned int pixels)
|
||||
{
|
||||
unsigned int x;
|
||||
|
@ -303,24 +334,25 @@ static void drm_fb_xrgb8888_to_rgb888_line(u8 *dbuf, u32 *sbuf,
|
|||
/**
|
||||
* drm_fb_xrgb8888_to_rgb888 - Convert XRGB8888 to RGB888 clip buffer
|
||||
* @dst: RGB888 destination buffer
|
||||
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
|
||||
* @src: XRGB8888 source buffer
|
||||
* @fb: DRM framebuffer
|
||||
* @clip: Clip rectangle area to copy
|
||||
*
|
||||
* Drivers can use this function for RGB888 devices that don't natively
|
||||
* support XRGB8888.
|
||||
*
|
||||
* This function does not apply clipping on dst, i.e. the destination
|
||||
* is a small buffer containing the clip rect only.
|
||||
*/
|
||||
void drm_fb_xrgb8888_to_rgb888(void *dst, void *src, struct drm_framebuffer *fb,
|
||||
struct drm_rect *clip)
|
||||
void drm_fb_xrgb8888_to_rgb888(void *dst, unsigned int dst_pitch, const void *src,
|
||||
const struct drm_framebuffer *fb, const struct drm_rect *clip)
|
||||
{
|
||||
size_t width = drm_rect_width(clip);
|
||||
size_t src_len = width * sizeof(u32);
|
||||
unsigned int y;
|
||||
void *sbuf;
|
||||
|
||||
if (!dst_pitch)
|
||||
dst_pitch = width * 3;
|
||||
|
||||
/* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */
|
||||
sbuf = kmalloc(src_len, GFP_KERNEL);
|
||||
if (!sbuf)
|
||||
|
@ -331,7 +363,7 @@ void drm_fb_xrgb8888_to_rgb888(void *dst, void *src, struct drm_framebuffer *fb,
|
|||
memcpy(sbuf, src, src_len);
|
||||
drm_fb_xrgb8888_to_rgb888_line(dst, sbuf, width);
|
||||
src += fb->pitches[0];
|
||||
dst += width * 3;
|
||||
dst += dst_pitch;
|
||||
}
|
||||
|
||||
kfree(sbuf);
|
||||
|
@ -339,48 +371,48 @@ void drm_fb_xrgb8888_to_rgb888(void *dst, void *src, struct drm_framebuffer *fb,
|
|||
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888);
|
||||
|
||||
/**
|
||||
* drm_fb_xrgb8888_to_rgb888_dstclip - Convert XRGB8888 to RGB888 clip buffer
|
||||
* drm_fb_xrgb8888_to_rgb888_toio - Convert XRGB8888 to RGB888 clip buffer
|
||||
* @dst: RGB565 destination buffer (iomem)
|
||||
* @dst_pitch: destination buffer pitch
|
||||
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
|
||||
* @vaddr: XRGB8888 source buffer
|
||||
* @fb: DRM framebuffer
|
||||
* @clip: Clip rectangle area to copy
|
||||
*
|
||||
* Drivers can use this function for RGB888 devices that don't natively
|
||||
* support XRGB8888.
|
||||
*
|
||||
* This function applies clipping on dst, i.e. the destination is a
|
||||
* full (iomem) framebuffer but only the clip rect content is copied over.
|
||||
*/
|
||||
void drm_fb_xrgb8888_to_rgb888_dstclip(void __iomem *dst, unsigned int dst_pitch,
|
||||
void *vaddr, struct drm_framebuffer *fb,
|
||||
struct drm_rect *clip)
|
||||
void drm_fb_xrgb8888_to_rgb888_toio(void __iomem *dst, unsigned int dst_pitch,
|
||||
const void *vaddr, const struct drm_framebuffer *fb,
|
||||
const struct drm_rect *clip)
|
||||
{
|
||||
size_t linepixels = clip->x2 - clip->x1;
|
||||
size_t dst_len = linepixels * 3;
|
||||
unsigned y, lines = clip->y2 - clip->y1;
|
||||
void *dbuf;
|
||||
|
||||
if (!dst_pitch)
|
||||
dst_pitch = dst_len;
|
||||
|
||||
dbuf = kmalloc(dst_len, GFP_KERNEL);
|
||||
if (!dbuf)
|
||||
return;
|
||||
|
||||
vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
|
||||
dst += clip_offset(clip, dst_pitch, sizeof(u16));
|
||||
for (y = 0; y < lines; y++) {
|
||||
drm_fb_xrgb8888_to_rgb888_line(dbuf, vaddr, linepixels);
|
||||
memcpy_toio(dst, dbuf, dst_len);
|
||||
vaddr += fb->pitches[0];
|
||||
dst += dst_len;
|
||||
dst += dst_pitch;
|
||||
}
|
||||
|
||||
kfree(dbuf);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888_dstclip);
|
||||
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888_toio);
|
||||
|
||||
/**
|
||||
* drm_fb_xrgb8888_to_gray8 - Convert XRGB8888 to grayscale
|
||||
* @dst: 8-bit grayscale destination buffer
|
||||
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
|
||||
* @vaddr: XRGB8888 source buffer
|
||||
* @fb: DRM framebuffer
|
||||
* @clip: Clip rectangle area to copy
|
||||
|
@ -394,16 +426,21 @@ EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb888_dstclip);
|
|||
*
|
||||
* ITU BT.601 is used for the RGB -> luma (brightness) conversion.
|
||||
*/
|
||||
void drm_fb_xrgb8888_to_gray8(u8 *dst, void *vaddr, struct drm_framebuffer *fb,
|
||||
struct drm_rect *clip)
|
||||
void drm_fb_xrgb8888_to_gray8(void *dst, unsigned int dst_pitch, const void *vaddr,
|
||||
const struct drm_framebuffer *fb, const struct drm_rect *clip)
|
||||
{
|
||||
unsigned int len = (clip->x2 - clip->x1) * sizeof(u32);
|
||||
unsigned int x, y;
|
||||
void *buf;
|
||||
u32 *src;
|
||||
u8 *dst8;
|
||||
u32 *src32;
|
||||
|
||||
if (WARN_ON(fb->format->format != DRM_FORMAT_XRGB8888))
|
||||
return;
|
||||
|
||||
if (!dst_pitch)
|
||||
dst_pitch = drm_rect_width(clip);
|
||||
|
||||
/*
|
||||
* The cma memory is write-combined so reads are uncached.
|
||||
* Speed up by fetching one line at a time.
|
||||
|
@ -412,20 +449,22 @@ void drm_fb_xrgb8888_to_gray8(u8 *dst, void *vaddr, struct drm_framebuffer *fb,
|
|||
if (!buf)
|
||||
return;
|
||||
|
||||
vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
|
||||
for (y = clip->y1; y < clip->y2; y++) {
|
||||
src = vaddr + (y * fb->pitches[0]);
|
||||
src += clip->x1;
|
||||
memcpy(buf, src, len);
|
||||
src = buf;
|
||||
dst8 = dst;
|
||||
src32 = memcpy(buf, vaddr, len);
|
||||
for (x = clip->x1; x < clip->x2; x++) {
|
||||
u8 r = (*src & 0x00ff0000) >> 16;
|
||||
u8 g = (*src & 0x0000ff00) >> 8;
|
||||
u8 b = *src & 0x000000ff;
|
||||
u8 r = (*src32 & 0x00ff0000) >> 16;
|
||||
u8 g = (*src32 & 0x0000ff00) >> 8;
|
||||
u8 b = *src32 & 0x000000ff;
|
||||
|
||||
/* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */
|
||||
*dst++ = (3 * r + 6 * g + b) / 10;
|
||||
src++;
|
||||
*dst8++ = (3 * r + 6 * g + b) / 10;
|
||||
src32++;
|
||||
}
|
||||
|
||||
vaddr += fb->pitches[0];
|
||||
dst += dst_pitch;
|
||||
}
|
||||
|
||||
kfree(buf);
|
||||
|
@ -433,7 +472,7 @@ void drm_fb_xrgb8888_to_gray8(u8 *dst, void *vaddr, struct drm_framebuffer *fb,
|
|||
EXPORT_SYMBOL(drm_fb_xrgb8888_to_gray8);
|
||||
|
||||
/**
|
||||
* drm_fb_blit_rect_dstclip - Copy parts of a framebuffer to display memory
|
||||
* drm_fb_blit_toio - Copy parts of a framebuffer to display memory
|
||||
* @dst: The display memory to copy to
|
||||
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
|
||||
* @dst_format: FOURCC code of the display's color format
|
||||
|
@ -445,17 +484,14 @@ EXPORT_SYMBOL(drm_fb_xrgb8888_to_gray8);
|
|||
* formats of the display and the framebuffer mismatch, the blit function
|
||||
* will attempt to convert between them.
|
||||
*
|
||||
* Use drm_fb_blit_dstclip() to copy the full framebuffer.
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success, or
|
||||
* -EINVAL if the color-format conversion failed, or
|
||||
* a negative error code otherwise.
|
||||
*/
|
||||
int drm_fb_blit_rect_dstclip(void __iomem *dst, unsigned int dst_pitch,
|
||||
uint32_t dst_format, void *vmap,
|
||||
struct drm_framebuffer *fb,
|
||||
struct drm_rect *clip)
|
||||
int drm_fb_blit_toio(void __iomem *dst, unsigned int dst_pitch, uint32_t dst_format,
|
||||
const void *vmap, const struct drm_framebuffer *fb,
|
||||
const struct drm_rect *clip)
|
||||
{
|
||||
uint32_t fb_format = fb->format->format;
|
||||
|
||||
|
@ -466,56 +502,21 @@ int drm_fb_blit_rect_dstclip(void __iomem *dst, unsigned int dst_pitch,
|
|||
dst_format = DRM_FORMAT_XRGB8888;
|
||||
|
||||
if (dst_format == fb_format) {
|
||||
drm_fb_memcpy_dstclip(dst, dst_pitch, vmap, fb, clip);
|
||||
drm_fb_memcpy_toio(dst, dst_pitch, vmap, fb, clip);
|
||||
return 0;
|
||||
|
||||
} else if (dst_format == DRM_FORMAT_RGB565) {
|
||||
if (fb_format == DRM_FORMAT_XRGB8888) {
|
||||
drm_fb_xrgb8888_to_rgb565_dstclip(dst, dst_pitch,
|
||||
vmap, fb, clip,
|
||||
false);
|
||||
drm_fb_xrgb8888_to_rgb565_toio(dst, dst_pitch, vmap, fb, clip, false);
|
||||
return 0;
|
||||
}
|
||||
} else if (dst_format == DRM_FORMAT_RGB888) {
|
||||
if (fb_format == DRM_FORMAT_XRGB8888) {
|
||||
drm_fb_xrgb8888_to_rgb888_dstclip(dst, dst_pitch,
|
||||
vmap, fb, clip);
|
||||
drm_fb_xrgb8888_to_rgb888_toio(dst, dst_pitch, vmap, fb, clip);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_fb_blit_rect_dstclip);
|
||||
|
||||
/**
|
||||
* drm_fb_blit_dstclip - Copy framebuffer to display memory
|
||||
* @dst: The display memory to copy to
|
||||
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
|
||||
* @dst_format: FOURCC code of the display's color format
|
||||
* @vmap: The framebuffer memory to copy from
|
||||
* @fb: The framebuffer to copy from
|
||||
*
|
||||
* This function copies a full framebuffer to display memory. If the formats
|
||||
* of the display and the framebuffer mismatch, the copy function will
|
||||
* attempt to convert between them.
|
||||
*
|
||||
* See drm_fb_blit_rect_dstclip() for more information.
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success, or a negative error code otherwise.
|
||||
*/
|
||||
int drm_fb_blit_dstclip(void __iomem *dst, unsigned int dst_pitch,
|
||||
uint32_t dst_format, void *vmap,
|
||||
struct drm_framebuffer *fb)
|
||||
{
|
||||
struct drm_rect fullscreen = {
|
||||
.x1 = 0,
|
||||
.x2 = fb->width,
|
||||
.y1 = 0,
|
||||
.y2 = fb->height,
|
||||
};
|
||||
return drm_fb_blit_rect_dstclip(dst, dst_pitch, dst_format, vmap, fb,
|
||||
&fullscreen);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_fb_blit_dstclip);
|
||||
EXPORT_SYMBOL(drm_fb_blit_toio);
|
||||
|
|
|
@ -143,6 +143,7 @@
|
|||
*/
|
||||
int drm_gem_plane_helper_prepare_fb(struct drm_plane *plane, struct drm_plane_state *state)
|
||||
{
|
||||
struct dma_resv_iter cursor;
|
||||
struct drm_gem_object *obj;
|
||||
struct dma_fence *fence;
|
||||
|
||||
|
@ -150,9 +151,18 @@ int drm_gem_plane_helper_prepare_fb(struct drm_plane *plane, struct drm_plane_st
|
|||
return 0;
|
||||
|
||||
obj = drm_gem_fb_get_obj(state->fb, 0);
|
||||
fence = dma_resv_get_excl_unlocked(obj->resv);
|
||||
drm_atomic_set_fence_for_plane(state, fence);
|
||||
dma_resv_iter_begin(&cursor, obj->resv, false);
|
||||
dma_resv_for_each_fence_unlocked(&cursor, fence) {
|
||||
/* TODO: Currently there should be only one write fence, so this
|
||||
* here works fine. But drm_atomic_set_fence_for_plane() should
|
||||
* be changed to be able to handle more fences in general for
|
||||
* multiple BOs per fb anyway. */
|
||||
dma_fence_get(fence);
|
||||
break;
|
||||
}
|
||||
dma_resv_iter_end(&cursor);
|
||||
|
||||
drm_atomic_set_fence_for_plane(state, fence);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_gem_plane_helper_prepare_fb);
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <linux/dma-mapping.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
|
@ -583,3 +584,7 @@ drm_gem_cma_prime_import_sg_table_vmap(struct drm_device *dev,
|
|||
return obj;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_gem_cma_prime_import_sg_table_vmap);
|
||||
|
||||
MODULE_DESCRIPTION("DRM CMA memory-management helpers");
|
||||
MODULE_IMPORT_NS(DMA_BUF);
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <linux/dma-buf.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/shmem_fs.h>
|
||||
#include <linux/slab.h>
|
||||
|
@ -28,17 +29,22 @@ MODULE_IMPORT_NS(DMA_BUF);
|
|||
*
|
||||
* This library provides helpers for GEM objects backed by shmem buffers
|
||||
* allocated using anonymous pageable memory.
|
||||
*
|
||||
* Functions that operate on the GEM object receive struct &drm_gem_shmem_object.
|
||||
* For GEM callback helpers in struct &drm_gem_object functions, see likewise
|
||||
* named functions with an _object_ infix (e.g., drm_gem_shmem_object_vmap() wraps
|
||||
* drm_gem_shmem_vmap()). These helpers perform the necessary type conversion.
|
||||
*/
|
||||
|
||||
static const struct drm_gem_object_funcs drm_gem_shmem_funcs = {
|
||||
.free = drm_gem_shmem_free_object,
|
||||
.print_info = drm_gem_shmem_print_info,
|
||||
.pin = drm_gem_shmem_pin,
|
||||
.unpin = drm_gem_shmem_unpin,
|
||||
.get_sg_table = drm_gem_shmem_get_sg_table,
|
||||
.vmap = drm_gem_shmem_vmap,
|
||||
.vunmap = drm_gem_shmem_vunmap,
|
||||
.mmap = drm_gem_shmem_mmap,
|
||||
.free = drm_gem_shmem_object_free,
|
||||
.print_info = drm_gem_shmem_object_print_info,
|
||||
.pin = drm_gem_shmem_object_pin,
|
||||
.unpin = drm_gem_shmem_object_unpin,
|
||||
.get_sg_table = drm_gem_shmem_object_get_sg_table,
|
||||
.vmap = drm_gem_shmem_object_vmap,
|
||||
.vunmap = drm_gem_shmem_object_vunmap,
|
||||
.mmap = drm_gem_shmem_object_mmap,
|
||||
};
|
||||
|
||||
static struct drm_gem_shmem_object *
|
||||
|
@ -118,16 +124,15 @@ struct drm_gem_shmem_object *drm_gem_shmem_create(struct drm_device *dev, size_t
|
|||
EXPORT_SYMBOL_GPL(drm_gem_shmem_create);
|
||||
|
||||
/**
|
||||
* drm_gem_shmem_free_object - Free resources associated with a shmem GEM object
|
||||
* @obj: GEM object to free
|
||||
* drm_gem_shmem_free - Free resources associated with a shmem GEM object
|
||||
* @shmem: shmem GEM object to free
|
||||
*
|
||||
* This function cleans up the GEM object state and frees the memory used to
|
||||
* store the object itself. It should be used to implement
|
||||
* &drm_gem_object_funcs.free.
|
||||
* store the object itself.
|
||||
*/
|
||||
void drm_gem_shmem_free_object(struct drm_gem_object *obj)
|
||||
void drm_gem_shmem_free(struct drm_gem_shmem_object *shmem)
|
||||
{
|
||||
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
|
||||
struct drm_gem_object *obj = &shmem->base;
|
||||
|
||||
WARN_ON(shmem->vmap_use_count);
|
||||
|
||||
|
@ -151,7 +156,7 @@ void drm_gem_shmem_free_object(struct drm_gem_object *obj)
|
|||
mutex_destroy(&shmem->vmap_lock);
|
||||
kfree(shmem);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_gem_shmem_free_object);
|
||||
EXPORT_SYMBOL_GPL(drm_gem_shmem_free);
|
||||
|
||||
static int drm_gem_shmem_get_pages_locked(struct drm_gem_shmem_object *shmem)
|
||||
{
|
||||
|
@ -246,19 +251,16 @@ EXPORT_SYMBOL(drm_gem_shmem_put_pages);
|
|||
|
||||
/**
|
||||
* drm_gem_shmem_pin - Pin backing pages for a shmem GEM object
|
||||
* @obj: GEM object
|
||||
* @shmem: shmem GEM object
|
||||
*
|
||||
* This function makes sure the backing pages are pinned in memory while the
|
||||
* buffer is exported. It should only be used to implement
|
||||
* &drm_gem_object_funcs.pin.
|
||||
* buffer is exported.
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int drm_gem_shmem_pin(struct drm_gem_object *obj)
|
||||
int drm_gem_shmem_pin(struct drm_gem_shmem_object *shmem)
|
||||
{
|
||||
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
|
||||
|
||||
WARN_ON(shmem->base.import_attach);
|
||||
|
||||
return drm_gem_shmem_get_pages(shmem);
|
||||
|
@ -267,15 +269,13 @@ EXPORT_SYMBOL(drm_gem_shmem_pin);
|
|||
|
||||
/**
|
||||
* drm_gem_shmem_unpin - Unpin backing pages for a shmem GEM object
|
||||
* @obj: GEM object
|
||||
* @shmem: shmem GEM object
|
||||
*
|
||||
* This function removes the requirement that the backing pages are pinned in
|
||||
* memory. It should only be used to implement &drm_gem_object_funcs.unpin.
|
||||
* memory.
|
||||
*/
|
||||
void drm_gem_shmem_unpin(struct drm_gem_object *obj)
|
||||
void drm_gem_shmem_unpin(struct drm_gem_shmem_object *shmem)
|
||||
{
|
||||
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
|
||||
|
||||
WARN_ON(shmem->base.import_attach);
|
||||
|
||||
drm_gem_shmem_put_pages(shmem);
|
||||
|
@ -341,20 +341,16 @@ err_zero_use:
|
|||
* store.
|
||||
*
|
||||
* This function makes sure that a contiguous kernel virtual address mapping
|
||||
* exists for the buffer backing the shmem GEM object.
|
||||
*
|
||||
* This function can be used to implement &drm_gem_object_funcs.vmap. But it can
|
||||
* also be called by drivers directly, in which case it will hide the
|
||||
* differences between dma-buf imported and natively allocated objects.
|
||||
* exists for the buffer backing the shmem GEM object. It hides the differences
|
||||
* between dma-buf imported and natively allocated objects.
|
||||
*
|
||||
* Acquired mappings should be cleaned up by calling drm_gem_shmem_vunmap().
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int drm_gem_shmem_vmap(struct drm_gem_object *obj, struct dma_buf_map *map)
|
||||
int drm_gem_shmem_vmap(struct drm_gem_shmem_object *shmem, struct dma_buf_map *map)
|
||||
{
|
||||
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
|
||||
int ret;
|
||||
|
||||
ret = mutex_lock_interruptible(&shmem->vmap_lock);
|
||||
|
@ -397,21 +393,18 @@ static void drm_gem_shmem_vunmap_locked(struct drm_gem_shmem_object *shmem,
|
|||
* drm_gem_shmem_vmap(). The mapping is only removed when the use count drops to
|
||||
* zero.
|
||||
*
|
||||
* This function can be used to implement &drm_gem_object_funcs.vmap. But it can
|
||||
* also be called by drivers directly, in which case it will hide the
|
||||
* differences between dma-buf imported and natively allocated objects.
|
||||
* This function hides the differences between dma-buf imported and natively
|
||||
* allocated objects.
|
||||
*/
|
||||
void drm_gem_shmem_vunmap(struct drm_gem_object *obj, struct dma_buf_map *map)
|
||||
void drm_gem_shmem_vunmap(struct drm_gem_shmem_object *shmem, struct dma_buf_map *map)
|
||||
{
|
||||
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
|
||||
|
||||
mutex_lock(&shmem->vmap_lock);
|
||||
drm_gem_shmem_vunmap_locked(shmem, map);
|
||||
mutex_unlock(&shmem->vmap_lock);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_gem_shmem_vunmap);
|
||||
|
||||
struct drm_gem_shmem_object *
|
||||
static struct drm_gem_shmem_object *
|
||||
drm_gem_shmem_create_with_handle(struct drm_file *file_priv,
|
||||
struct drm_device *dev, size_t size,
|
||||
uint32_t *handle)
|
||||
|
@ -435,15 +428,12 @@ drm_gem_shmem_create_with_handle(struct drm_file *file_priv,
|
|||
|
||||
return shmem;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_gem_shmem_create_with_handle);
|
||||
|
||||
/* Update madvise status, returns true if not purged, else
|
||||
* false or -errno.
|
||||
*/
|
||||
int drm_gem_shmem_madvise(struct drm_gem_object *obj, int madv)
|
||||
int drm_gem_shmem_madvise(struct drm_gem_shmem_object *shmem, int madv)
|
||||
{
|
||||
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
|
||||
|
||||
mutex_lock(&shmem->pages_lock);
|
||||
|
||||
if (shmem->madv >= 0)
|
||||
|
@ -457,14 +447,14 @@ int drm_gem_shmem_madvise(struct drm_gem_object *obj, int madv)
|
|||
}
|
||||
EXPORT_SYMBOL(drm_gem_shmem_madvise);
|
||||
|
||||
void drm_gem_shmem_purge_locked(struct drm_gem_object *obj)
|
||||
void drm_gem_shmem_purge_locked(struct drm_gem_shmem_object *shmem)
|
||||
{
|
||||
struct drm_gem_object *obj = &shmem->base;
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
|
||||
|
||||
WARN_ON(!drm_gem_shmem_is_purgeable(shmem));
|
||||
|
||||
dma_unmap_sgtable(obj->dev->dev, shmem->sgt, DMA_BIDIRECTIONAL, 0);
|
||||
dma_unmap_sgtable(dev->dev, shmem->sgt, DMA_BIDIRECTIONAL, 0);
|
||||
sg_free_table(shmem->sgt);
|
||||
kfree(shmem->sgt);
|
||||
shmem->sgt = NULL;
|
||||
|
@ -483,18 +473,15 @@ void drm_gem_shmem_purge_locked(struct drm_gem_object *obj)
|
|||
*/
|
||||
shmem_truncate_range(file_inode(obj->filp), 0, (loff_t)-1);
|
||||
|
||||
invalidate_mapping_pages(file_inode(obj->filp)->i_mapping,
|
||||
0, (loff_t)-1);
|
||||
invalidate_mapping_pages(file_inode(obj->filp)->i_mapping, 0, (loff_t)-1);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_gem_shmem_purge_locked);
|
||||
|
||||
bool drm_gem_shmem_purge(struct drm_gem_object *obj)
|
||||
bool drm_gem_shmem_purge(struct drm_gem_shmem_object *shmem)
|
||||
{
|
||||
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
|
||||
|
||||
if (!mutex_trylock(&shmem->pages_lock))
|
||||
return false;
|
||||
drm_gem_shmem_purge_locked(obj);
|
||||
drm_gem_shmem_purge_locked(shmem);
|
||||
mutex_unlock(&shmem->pages_lock);
|
||||
|
||||
return true;
|
||||
|
@ -602,19 +589,18 @@ static const struct vm_operations_struct drm_gem_shmem_vm_ops = {
|
|||
|
||||
/**
|
||||
* drm_gem_shmem_mmap - Memory-map a shmem GEM object
|
||||
* @obj: gem object
|
||||
* @shmem: shmem GEM object
|
||||
* @vma: VMA for the area to be mapped
|
||||
*
|
||||
* This function implements an augmented version of the GEM DRM file mmap
|
||||
* operation for shmem objects. Drivers which employ the shmem helpers should
|
||||
* use this function as their &drm_gem_object_funcs.mmap handler.
|
||||
* operation for shmem objects.
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success or a negative error code on failure.
|
||||
*/
|
||||
int drm_gem_shmem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
|
||||
int drm_gem_shmem_mmap(struct drm_gem_shmem_object *shmem, struct vm_area_struct *vma)
|
||||
{
|
||||
struct drm_gem_shmem_object *shmem;
|
||||
struct drm_gem_object *obj = &shmem->base;
|
||||
int ret;
|
||||
|
||||
if (obj->import_attach) {
|
||||
|
@ -625,8 +611,6 @@ int drm_gem_shmem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
|
|||
return dma_buf_mmap(obj->dma_buf, vma, 0);
|
||||
}
|
||||
|
||||
shmem = to_drm_gem_shmem_obj(obj);
|
||||
|
||||
ret = drm_gem_shmem_get_pages(shmem);
|
||||
if (ret) {
|
||||
drm_gem_vm_close(vma);
|
||||
|
@ -645,17 +629,13 @@ EXPORT_SYMBOL_GPL(drm_gem_shmem_mmap);
|
|||
|
||||
/**
|
||||
* drm_gem_shmem_print_info() - Print &drm_gem_shmem_object info for debugfs
|
||||
* @shmem: shmem GEM object
|
||||
* @p: DRM printer
|
||||
* @indent: Tab indentation level
|
||||
* @obj: GEM object
|
||||
*
|
||||
* This implements the &drm_gem_object_funcs.info callback.
|
||||
*/
|
||||
void drm_gem_shmem_print_info(struct drm_printer *p, unsigned int indent,
|
||||
const struct drm_gem_object *obj)
|
||||
void drm_gem_shmem_print_info(const struct drm_gem_shmem_object *shmem,
|
||||
struct drm_printer *p, unsigned int indent)
|
||||
{
|
||||
const struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
|
||||
|
||||
drm_printf_indent(p, indent, "pages_use_count=%u\n", shmem->pages_use_count);
|
||||
drm_printf_indent(p, indent, "vmap_use_count=%u\n", shmem->vmap_use_count);
|
||||
drm_printf_indent(p, indent, "vaddr=%p\n", shmem->vaddr);
|
||||
|
@ -665,12 +645,10 @@ EXPORT_SYMBOL(drm_gem_shmem_print_info);
|
|||
/**
|
||||
* drm_gem_shmem_get_sg_table - Provide a scatter/gather table of pinned
|
||||
* pages for a shmem GEM object
|
||||
* @obj: GEM object
|
||||
* @shmem: shmem GEM object
|
||||
*
|
||||
* This function exports a scatter/gather table suitable for PRIME usage by
|
||||
* calling the standard DMA mapping API. Drivers should not call this function
|
||||
* directly, instead it should only be used as an implementation for
|
||||
* &drm_gem_object_funcs.get_sg_table.
|
||||
* calling the standard DMA mapping API.
|
||||
*
|
||||
* Drivers who need to acquire an scatter/gather table for objects need to call
|
||||
* drm_gem_shmem_get_pages_sgt() instead.
|
||||
|
@ -678,9 +656,9 @@ EXPORT_SYMBOL(drm_gem_shmem_print_info);
|
|||
* Returns:
|
||||
* A pointer to the scatter/gather table of pinned pages or NULL on failure.
|
||||
*/
|
||||
struct sg_table *drm_gem_shmem_get_sg_table(struct drm_gem_object *obj)
|
||||
struct sg_table *drm_gem_shmem_get_sg_table(struct drm_gem_shmem_object *shmem)
|
||||
{
|
||||
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
|
||||
struct drm_gem_object *obj = &shmem->base;
|
||||
|
||||
WARN_ON(shmem->base.import_attach);
|
||||
|
||||
|
@ -691,7 +669,7 @@ EXPORT_SYMBOL_GPL(drm_gem_shmem_get_sg_table);
|
|||
/**
|
||||
* drm_gem_shmem_get_pages_sgt - Pin pages, dma map them, and return a
|
||||
* scatter/gather table for a shmem GEM object.
|
||||
* @obj: GEM object
|
||||
* @shmem: shmem GEM object
|
||||
*
|
||||
* This function returns a scatter/gather table suitable for driver usage. If
|
||||
* the sg table doesn't exist, the pages are pinned, dma-mapped, and a sg
|
||||
|
@ -704,10 +682,10 @@ EXPORT_SYMBOL_GPL(drm_gem_shmem_get_sg_table);
|
|||
* Returns:
|
||||
* A pointer to the scatter/gather table of pinned pages or errno on failure.
|
||||
*/
|
||||
struct sg_table *drm_gem_shmem_get_pages_sgt(struct drm_gem_object *obj)
|
||||
struct sg_table *drm_gem_shmem_get_pages_sgt(struct drm_gem_shmem_object *shmem)
|
||||
{
|
||||
struct drm_gem_object *obj = &shmem->base;
|
||||
int ret;
|
||||
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
|
||||
struct sg_table *sgt;
|
||||
|
||||
if (shmem->sgt)
|
||||
|
@ -719,7 +697,7 @@ struct sg_table *drm_gem_shmem_get_pages_sgt(struct drm_gem_object *obj)
|
|||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
|
||||
sgt = drm_gem_shmem_get_sg_table(&shmem->base);
|
||||
sgt = drm_gem_shmem_get_sg_table(shmem);
|
||||
if (IS_ERR(sgt)) {
|
||||
ret = PTR_ERR(sgt);
|
||||
goto err_put_pages;
|
||||
|
@ -776,3 +754,7 @@ drm_gem_shmem_prime_import_sg_table(struct drm_device *dev,
|
|||
return &shmem->base;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_gem_shmem_prime_import_sg_table);
|
||||
|
||||
MODULE_DESCRIPTION("DRM SHMEM memory-management helpers");
|
||||
MODULE_IMPORT_NS(DMA_BUF);
|
||||
MODULE_LICENSE("GPL v2");
|
||||
|
|
|
@ -66,7 +66,6 @@
|
|||
|
||||
#include "drm_internal.h"
|
||||
|
||||
#if IS_ENABLED(CONFIG_DRM_LEGACY)
|
||||
static int drm_legacy_irq_install(struct drm_device *dev, int irq)
|
||||
{
|
||||
int ret;
|
||||
|
@ -203,4 +202,3 @@ int drm_legacy_irq_control(struct drm_device *dev, void *data,
|
|||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -211,12 +211,12 @@ int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
|
|||
switch (fb->format->format) {
|
||||
case DRM_FORMAT_RGB565:
|
||||
if (swap)
|
||||
drm_fb_swab(dst, src, fb, clip, !gem->import_attach);
|
||||
drm_fb_swab(dst, 0, src, fb, clip, !gem->import_attach);
|
||||
else
|
||||
drm_fb_memcpy(dst, src, fb, clip);
|
||||
drm_fb_memcpy(dst, 0, src, fb, clip);
|
||||
break;
|
||||
case DRM_FORMAT_XRGB8888:
|
||||
drm_fb_xrgb8888_to_rgb565(dst, src, fb, clip, swap);
|
||||
drm_fb_xrgb8888_to_rgb565(dst, 0, src, fb, clip, swap);
|
||||
break;
|
||||
default:
|
||||
drm_err_once(fb->dev, "Format is not supported: %p4cc\n",
|
||||
|
|
|
@ -402,3 +402,36 @@ int drm_of_lvds_get_dual_link_pixel_order(const struct device_node *port1,
|
|||
DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_of_lvds_get_dual_link_pixel_order);
|
||||
|
||||
/**
|
||||
* drm_of_lvds_get_data_mapping - Get LVDS data mapping
|
||||
* @port: DT port node of the LVDS source or sink
|
||||
*
|
||||
* Convert DT "data-mapping" property string value into media bus format value.
|
||||
*
|
||||
* Return:
|
||||
* * MEDIA_BUS_FMT_RGB666_1X7X3_SPWG - data-mapping is "jeida-18"
|
||||
* * MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA - data-mapping is "jeida-24"
|
||||
* * MEDIA_BUS_FMT_RGB888_1X7X4_SPWG - data-mapping is "vesa-24"
|
||||
* * -EINVAL - the "data-mapping" property is unsupported
|
||||
* * -ENODEV - the "data-mapping" property is missing
|
||||
*/
|
||||
int drm_of_lvds_get_data_mapping(const struct device_node *port)
|
||||
{
|
||||
const char *mapping;
|
||||
int ret;
|
||||
|
||||
ret = of_property_read_string(port, "data-mapping", &mapping);
|
||||
if (ret < 0)
|
||||
return -ENODEV;
|
||||
|
||||
if (!strcmp(mapping, "jeida-18"))
|
||||
return MEDIA_BUS_FMT_RGB666_1X7X3_SPWG;
|
||||
if (!strcmp(mapping, "jeida-24"))
|
||||
return MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
|
||||
if (!strcmp(mapping, "vesa-24"))
|
||||
return MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_of_lvds_get_data_mapping);
|
||||
|
|
|
@ -0,0 +1,467 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
/*
|
||||
* Copyright (C) 2020 - 2021 Red Hat, Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Hans de Goede <hdegoede@redhat.com>
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <drm/drm_privacy_screen_machine.h>
|
||||
#include <drm/drm_privacy_screen_consumer.h>
|
||||
#include <drm/drm_privacy_screen_driver.h>
|
||||
#include "drm_internal.h"
|
||||
|
||||
/**
|
||||
* DOC: overview
|
||||
*
|
||||
* This class allows non KMS drivers, from e.g. drivers/platform/x86 to
|
||||
* register a privacy-screen device, which the KMS drivers can then use
|
||||
* to implement the standard privacy-screen properties, see
|
||||
* :ref:`Standard Connector Properties<standard_connector_properties>`.
|
||||
*
|
||||
* KMS drivers using a privacy-screen class device are advised to use the
|
||||
* drm_connector_attach_privacy_screen_provider() and
|
||||
* drm_connector_update_privacy_screen() helpers for dealing with this.
|
||||
*/
|
||||
|
||||
#define to_drm_privacy_screen(dev) \
|
||||
container_of(dev, struct drm_privacy_screen, dev)
|
||||
|
||||
static DEFINE_MUTEX(drm_privacy_screen_lookup_lock);
|
||||
static LIST_HEAD(drm_privacy_screen_lookup_list);
|
||||
|
||||
static DEFINE_MUTEX(drm_privacy_screen_devs_lock);
|
||||
static LIST_HEAD(drm_privacy_screen_devs);
|
||||
|
||||
/*** drm_privacy_screen_machine.h functions ***/
|
||||
|
||||
/**
|
||||
* drm_privacy_screen_lookup_add - add an entry to the static privacy-screen
|
||||
* lookup list
|
||||
* @lookup: lookup list entry to add
|
||||
*
|
||||
* Add an entry to the static privacy-screen lookup list. Note the
|
||||
* &struct list_head which is part of the &struct drm_privacy_screen_lookup
|
||||
* gets added to a list owned by the privacy-screen core. So the passed in
|
||||
* &struct drm_privacy_screen_lookup must not be free-ed until it is removed
|
||||
* from the lookup list by calling drm_privacy_screen_lookup_remove().
|
||||
*/
|
||||
void drm_privacy_screen_lookup_add(struct drm_privacy_screen_lookup *lookup)
|
||||
{
|
||||
mutex_lock(&drm_privacy_screen_lookup_lock);
|
||||
list_add(&lookup->list, &drm_privacy_screen_lookup_list);
|
||||
mutex_unlock(&drm_privacy_screen_lookup_lock);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_privacy_screen_lookup_add);
|
||||
|
||||
/**
|
||||
* drm_privacy_screen_lookup_remove - remove an entry to the static
|
||||
* privacy-screen lookup list
|
||||
* @lookup: lookup list entry to remove
|
||||
*
|
||||
* Remove an entry previously added with drm_privacy_screen_lookup_add()
|
||||
* from the static privacy-screen lookup list.
|
||||
*/
|
||||
void drm_privacy_screen_lookup_remove(struct drm_privacy_screen_lookup *lookup)
|
||||
{
|
||||
mutex_lock(&drm_privacy_screen_lookup_lock);
|
||||
list_del(&lookup->list);
|
||||
mutex_unlock(&drm_privacy_screen_lookup_lock);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_privacy_screen_lookup_remove);
|
||||
|
||||
/*** drm_privacy_screen_consumer.h functions ***/
|
||||
|
||||
static struct drm_privacy_screen *drm_privacy_screen_get_by_name(
|
||||
const char *name)
|
||||
{
|
||||
struct drm_privacy_screen *priv;
|
||||
struct device *dev = NULL;
|
||||
|
||||
mutex_lock(&drm_privacy_screen_devs_lock);
|
||||
|
||||
list_for_each_entry(priv, &drm_privacy_screen_devs, list) {
|
||||
if (strcmp(dev_name(&priv->dev), name) == 0) {
|
||||
dev = get_device(&priv->dev);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&drm_privacy_screen_devs_lock);
|
||||
|
||||
return dev ? to_drm_privacy_screen(dev) : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_privacy_screen_get - get a privacy-screen provider
|
||||
* @dev: consumer-device for which to get a privacy-screen provider
|
||||
* @con_id: (video)connector name for which to get a privacy-screen provider
|
||||
*
|
||||
* Get a privacy-screen provider for a privacy-screen attached to the
|
||||
* display described by the @dev and @con_id parameters.
|
||||
*
|
||||
* Return:
|
||||
* * A pointer to a &struct drm_privacy_screen on success.
|
||||
* * ERR_PTR(-ENODEV) if no matching privacy-screen is found
|
||||
* * ERR_PTR(-EPROBE_DEFER) if there is a matching privacy-screen,
|
||||
* but it has not been registered yet.
|
||||
*/
|
||||
struct drm_privacy_screen *drm_privacy_screen_get(struct device *dev,
|
||||
const char *con_id)
|
||||
{
|
||||
const char *dev_id = dev ? dev_name(dev) : NULL;
|
||||
struct drm_privacy_screen_lookup *l;
|
||||
struct drm_privacy_screen *priv;
|
||||
const char *provider = NULL;
|
||||
int match, best = -1;
|
||||
|
||||
/*
|
||||
* For now we only support using a static lookup table, which is
|
||||
* populated by the drm_privacy_screen_arch_init() call. This should
|
||||
* be extended with device-tree / fw_node lookup when support is added
|
||||
* for device-tree using hardware with a privacy-screen.
|
||||
*
|
||||
* The lookup algorithm was shamelessly taken from the clock
|
||||
* framework:
|
||||
*
|
||||
* We do slightly fuzzy matching here:
|
||||
* An entry with a NULL ID is assumed to be a wildcard.
|
||||
* If an entry has a device ID, it must match
|
||||
* If an entry has a connection ID, it must match
|
||||
* Then we take the most specific entry - with the following order
|
||||
* of precedence: dev+con > dev only > con only.
|
||||
*/
|
||||
mutex_lock(&drm_privacy_screen_lookup_lock);
|
||||
|
||||
list_for_each_entry(l, &drm_privacy_screen_lookup_list, list) {
|
||||
match = 0;
|
||||
|
||||
if (l->dev_id) {
|
||||
if (!dev_id || strcmp(l->dev_id, dev_id))
|
||||
continue;
|
||||
|
||||
match += 2;
|
||||
}
|
||||
|
||||
if (l->con_id) {
|
||||
if (!con_id || strcmp(l->con_id, con_id))
|
||||
continue;
|
||||
|
||||
match += 1;
|
||||
}
|
||||
|
||||
if (match > best) {
|
||||
provider = l->provider;
|
||||
best = match;
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&drm_privacy_screen_lookup_lock);
|
||||
|
||||
if (!provider)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
priv = drm_privacy_screen_get_by_name(provider);
|
||||
if (!priv)
|
||||
return ERR_PTR(-EPROBE_DEFER);
|
||||
|
||||
return priv;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_privacy_screen_get);
|
||||
|
||||
/**
|
||||
* drm_privacy_screen_put - release a privacy-screen reference
|
||||
* @priv: privacy screen reference to release
|
||||
*
|
||||
* Release a privacy-screen provider reference gotten through
|
||||
* drm_privacy_screen_get(). May be called with a NULL or ERR_PTR,
|
||||
* in which case it is a no-op.
|
||||
*/
|
||||
void drm_privacy_screen_put(struct drm_privacy_screen *priv)
|
||||
{
|
||||
if (IS_ERR_OR_NULL(priv))
|
||||
return;
|
||||
|
||||
put_device(&priv->dev);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_privacy_screen_put);
|
||||
|
||||
/**
|
||||
* drm_privacy_screen_set_sw_state - set a privacy-screen's sw-state
|
||||
* @priv: privacy screen to set the sw-state for
|
||||
* @sw_state: new sw-state value to set
|
||||
*
|
||||
* Set the sw-state of a privacy screen. If the privacy-screen is not
|
||||
* in a locked hw-state, then the actual and hw-state of the privacy-screen
|
||||
* will be immediately updated to the new value. If the privacy-screen is
|
||||
* in a locked hw-state, then the new sw-state will be remembered as the
|
||||
* requested state to put the privacy-screen in when it becomes unlocked.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
int drm_privacy_screen_set_sw_state(struct drm_privacy_screen *priv,
|
||||
enum drm_privacy_screen_status sw_state)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&priv->lock);
|
||||
|
||||
if (!priv->ops) {
|
||||
ret = -ENODEV;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* As per the DRM connector properties documentation, setting the
|
||||
* sw_state while the hw_state is locked is allowed. In this case
|
||||
* it is a no-op other then storing the new sw_state so that it
|
||||
* can be honored when the state gets unlocked.
|
||||
* Also skip the set if the hw already is in the desired state.
|
||||
*/
|
||||
if (priv->hw_state >= PRIVACY_SCREEN_DISABLED_LOCKED ||
|
||||
priv->hw_state == sw_state) {
|
||||
priv->sw_state = sw_state;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = priv->ops->set_sw_state(priv, sw_state);
|
||||
out:
|
||||
mutex_unlock(&priv->lock);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_privacy_screen_set_sw_state);
|
||||
|
||||
/**
|
||||
* drm_privacy_screen_get_state - get privacy-screen's current state
|
||||
* @priv: privacy screen to get the state for
|
||||
* @sw_state_ret: address where to store the privacy-screens current sw-state
|
||||
* @hw_state_ret: address where to store the privacy-screens current hw-state
|
||||
*
|
||||
* Get the current state of a privacy-screen, both the sw-state and the
|
||||
* hw-state.
|
||||
*/
|
||||
void drm_privacy_screen_get_state(struct drm_privacy_screen *priv,
|
||||
enum drm_privacy_screen_status *sw_state_ret,
|
||||
enum drm_privacy_screen_status *hw_state_ret)
|
||||
{
|
||||
mutex_lock(&priv->lock);
|
||||
*sw_state_ret = priv->sw_state;
|
||||
*hw_state_ret = priv->hw_state;
|
||||
mutex_unlock(&priv->lock);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_privacy_screen_get_state);
|
||||
|
||||
/**
|
||||
* drm_privacy_screen_register_notifier - register a notifier
|
||||
* @priv: Privacy screen to register the notifier with
|
||||
* @nb: Notifier-block for the notifier to register
|
||||
*
|
||||
* Register a notifier with the privacy-screen to be notified of changes made
|
||||
* to the privacy-screen state from outside of the privacy-screen class.
|
||||
* E.g. the state may be changed by the hardware itself in response to a
|
||||
* hotkey press.
|
||||
*
|
||||
* The notifier is called with no locks held. The new hw_state and sw_state
|
||||
* can be retrieved using the drm_privacy_screen_get_state() function.
|
||||
* A pointer to the drm_privacy_screen's struct is passed as the void *data
|
||||
* argument of the notifier_block's notifier_call.
|
||||
*
|
||||
* The notifier will NOT be called when changes are made through
|
||||
* drm_privacy_screen_set_sw_state(). It is only called for external changes.
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
int drm_privacy_screen_register_notifier(struct drm_privacy_screen *priv,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
return blocking_notifier_chain_register(&priv->notifier_head, nb);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_privacy_screen_register_notifier);
|
||||
|
||||
/**
|
||||
* drm_privacy_screen_unregister_notifier - unregister a notifier
|
||||
* @priv: Privacy screen to register the notifier with
|
||||
* @nb: Notifier-block for the notifier to register
|
||||
*
|
||||
* Unregister a notifier registered with drm_privacy_screen_register_notifier().
|
||||
*
|
||||
* Return: 0 on success, negative error code on failure.
|
||||
*/
|
||||
int drm_privacy_screen_unregister_notifier(struct drm_privacy_screen *priv,
|
||||
struct notifier_block *nb)
|
||||
{
|
||||
return blocking_notifier_chain_unregister(&priv->notifier_head, nb);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_privacy_screen_unregister_notifier);
|
||||
|
||||
/*** drm_privacy_screen_driver.h functions ***/
|
||||
|
||||
static ssize_t sw_state_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct drm_privacy_screen *priv = to_drm_privacy_screen(dev);
|
||||
const char * const sw_state_names[] = {
|
||||
"Disabled",
|
||||
"Enabled",
|
||||
};
|
||||
ssize_t ret;
|
||||
|
||||
mutex_lock(&priv->lock);
|
||||
|
||||
if (!priv->ops)
|
||||
ret = -ENODEV;
|
||||
else if (WARN_ON(priv->sw_state >= ARRAY_SIZE(sw_state_names)))
|
||||
ret = -ENXIO;
|
||||
else
|
||||
ret = sprintf(buf, "%s\n", sw_state_names[priv->sw_state]);
|
||||
|
||||
mutex_unlock(&priv->lock);
|
||||
return ret;
|
||||
}
|
||||
/*
|
||||
* RO: Do not allow setting the sw_state through sysfs, this MUST be done
|
||||
* through the drm_properties on the drm_connector.
|
||||
*/
|
||||
static DEVICE_ATTR_RO(sw_state);
|
||||
|
||||
static ssize_t hw_state_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct drm_privacy_screen *priv = to_drm_privacy_screen(dev);
|
||||
const char * const hw_state_names[] = {
|
||||
"Disabled",
|
||||
"Enabled",
|
||||
"Disabled, locked",
|
||||
"Enabled, locked",
|
||||
};
|
||||
ssize_t ret;
|
||||
|
||||
mutex_lock(&priv->lock);
|
||||
|
||||
if (!priv->ops)
|
||||
ret = -ENODEV;
|
||||
else if (WARN_ON(priv->hw_state >= ARRAY_SIZE(hw_state_names)))
|
||||
ret = -ENXIO;
|
||||
else
|
||||
ret = sprintf(buf, "%s\n", hw_state_names[priv->hw_state]);
|
||||
|
||||
mutex_unlock(&priv->lock);
|
||||
return ret;
|
||||
}
|
||||
static DEVICE_ATTR_RO(hw_state);
|
||||
|
||||
static struct attribute *drm_privacy_screen_attrs[] = {
|
||||
&dev_attr_sw_state.attr,
|
||||
&dev_attr_hw_state.attr,
|
||||
NULL
|
||||
};
|
||||
ATTRIBUTE_GROUPS(drm_privacy_screen);
|
||||
|
||||
static struct device_type drm_privacy_screen_type = {
|
||||
.name = "privacy_screen",
|
||||
.groups = drm_privacy_screen_groups,
|
||||
};
|
||||
|
||||
static void drm_privacy_screen_device_release(struct device *dev)
|
||||
{
|
||||
struct drm_privacy_screen *priv = to_drm_privacy_screen(dev);
|
||||
|
||||
kfree(priv);
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_privacy_screen_register - register a privacy-screen
|
||||
* @parent: parent-device for the privacy-screen
|
||||
* @ops: &struct drm_privacy_screen_ops pointer with ops for the privacy-screen
|
||||
*
|
||||
* Create and register a privacy-screen.
|
||||
*
|
||||
* Return:
|
||||
* * A pointer to the created privacy-screen on success.
|
||||
* * An ERR_PTR(errno) on failure.
|
||||
*/
|
||||
struct drm_privacy_screen *drm_privacy_screen_register(
|
||||
struct device *parent, const struct drm_privacy_screen_ops *ops)
|
||||
{
|
||||
struct drm_privacy_screen *priv;
|
||||
int ret;
|
||||
|
||||
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
mutex_init(&priv->lock);
|
||||
BLOCKING_INIT_NOTIFIER_HEAD(&priv->notifier_head);
|
||||
|
||||
priv->dev.class = drm_class;
|
||||
priv->dev.type = &drm_privacy_screen_type;
|
||||
priv->dev.parent = parent;
|
||||
priv->dev.release = drm_privacy_screen_device_release;
|
||||
dev_set_name(&priv->dev, "privacy_screen-%s", dev_name(parent));
|
||||
priv->ops = ops;
|
||||
|
||||
priv->ops->get_hw_state(priv);
|
||||
|
||||
ret = device_register(&priv->dev);
|
||||
if (ret) {
|
||||
put_device(&priv->dev);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
mutex_lock(&drm_privacy_screen_devs_lock);
|
||||
list_add(&priv->list, &drm_privacy_screen_devs);
|
||||
mutex_unlock(&drm_privacy_screen_devs_lock);
|
||||
|
||||
return priv;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_privacy_screen_register);
|
||||
|
||||
/**
|
||||
* drm_privacy_screen_unregister - unregister privacy-screen
|
||||
* @priv: privacy-screen to unregister
|
||||
*
|
||||
* Unregister a privacy-screen registered with drm_privacy_screen_register().
|
||||
* May be called with a NULL or ERR_PTR, in which case it is a no-op.
|
||||
*/
|
||||
void drm_privacy_screen_unregister(struct drm_privacy_screen *priv)
|
||||
{
|
||||
if (IS_ERR_OR_NULL(priv))
|
||||
return;
|
||||
|
||||
mutex_lock(&drm_privacy_screen_devs_lock);
|
||||
list_del(&priv->list);
|
||||
mutex_unlock(&drm_privacy_screen_devs_lock);
|
||||
|
||||
mutex_lock(&priv->lock);
|
||||
priv->ops = NULL;
|
||||
mutex_unlock(&priv->lock);
|
||||
|
||||
device_unregister(&priv->dev);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_privacy_screen_unregister);
|
||||
|
||||
/**
|
||||
* drm_privacy_screen_call_notifier_chain - notify consumers of state change
|
||||
* @priv: Privacy screen to register the notifier with
|
||||
*
|
||||
* A privacy-screen provider driver can call this functions upon external
|
||||
* changes to the privacy-screen state. E.g. the state may be changed by the
|
||||
* hardware itself in response to a hotkey press.
|
||||
* This function must be called without holding the privacy-screen lock.
|
||||
* the driver must update sw_state and hw_state to reflect the new state before
|
||||
* calling this function.
|
||||
* The expected behavior from the driver upon receiving an external state
|
||||
* change event is: 1. Take the lock; 2. Update sw_state and hw_state;
|
||||
* 3. Release the lock. 4. Call drm_privacy_screen_call_notifier_chain().
|
||||
*/
|
||||
void drm_privacy_screen_call_notifier_chain(struct drm_privacy_screen *priv)
|
||||
{
|
||||
blocking_notifier_call_chain(&priv->notifier_head, 0, priv);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_privacy_screen_call_notifier_chain);
|
|
@ -0,0 +1,86 @@
|
|||
// SPDX-License-Identifier: MIT
|
||||
/*
|
||||
* Copyright (C) 2020 Red Hat, Inc.
|
||||
*
|
||||
* Authors:
|
||||
* Hans de Goede <hdegoede@redhat.com>
|
||||
*/
|
||||
|
||||
#include <linux/acpi.h>
|
||||
#include <drm/drm_privacy_screen_machine.h>
|
||||
|
||||
#ifdef CONFIG_X86
|
||||
static struct drm_privacy_screen_lookup arch_lookup;
|
||||
|
||||
struct arch_init_data {
|
||||
struct drm_privacy_screen_lookup lookup;
|
||||
bool (*detect)(void);
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_THINKPAD_ACPI)
|
||||
static acpi_status __init acpi_set_handle(acpi_handle handle, u32 level,
|
||||
void *context, void **return_value)
|
||||
{
|
||||
*(acpi_handle *)return_value = handle;
|
||||
return AE_CTRL_TERMINATE;
|
||||
}
|
||||
|
||||
static bool __init detect_thinkpad_privacy_screen(void)
|
||||
{
|
||||
union acpi_object obj = { .type = ACPI_TYPE_INTEGER };
|
||||
struct acpi_object_list args = { .count = 1, .pointer = &obj, };
|
||||
acpi_handle ec_handle = NULL;
|
||||
unsigned long long output;
|
||||
acpi_status status;
|
||||
|
||||
/* Get embedded-controller handle */
|
||||
status = acpi_get_devices("PNP0C09", acpi_set_handle, NULL, &ec_handle);
|
||||
if (ACPI_FAILURE(status) || !ec_handle)
|
||||
return false;
|
||||
|
||||
/* And call the privacy-screen get-status method */
|
||||
status = acpi_evaluate_integer(ec_handle, "HKEY.GSSS", &args, &output);
|
||||
if (ACPI_FAILURE(status))
|
||||
return false;
|
||||
|
||||
return (output & 0x10000) ? true : false;
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct arch_init_data arch_init_data[] __initconst = {
|
||||
#if IS_ENABLED(CONFIG_THINKPAD_ACPI)
|
||||
{
|
||||
.lookup = {
|
||||
.dev_id = NULL,
|
||||
.con_id = NULL,
|
||||
.provider = "privacy_screen-thinkpad_acpi",
|
||||
},
|
||||
.detect = detect_thinkpad_privacy_screen,
|
||||
},
|
||||
#endif
|
||||
};
|
||||
|
||||
void __init drm_privacy_screen_lookup_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(arch_init_data); i++) {
|
||||
if (!arch_init_data[i].detect())
|
||||
continue;
|
||||
|
||||
pr_info("Found '%s' privacy-screen provider\n",
|
||||
arch_init_data[i].lookup.provider);
|
||||
|
||||
/* Make a copy because arch_init_data is __initconst */
|
||||
arch_lookup = arch_init_data[i].lookup;
|
||||
drm_privacy_screen_lookup_add(&arch_lookup);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void drm_privacy_screen_lookup_exit(void)
|
||||
{
|
||||
if (arch_lookup.provider)
|
||||
drm_privacy_screen_lookup_remove(&arch_lookup);
|
||||
}
|
||||
#endif /* ifdef CONFIG_X86 */
|
|
@ -604,6 +604,9 @@ EXPORT_SYMBOL(drm_helper_probe_single_connector_modes);
|
|||
*
|
||||
* This function must be called from process context with no mode
|
||||
* setting locks held.
|
||||
*
|
||||
* If only a single connector has changed, consider calling
|
||||
* drm_kms_helper_connector_hotplug_event() instead.
|
||||
*/
|
||||
void drm_kms_helper_hotplug_event(struct drm_device *dev)
|
||||
{
|
||||
|
@ -616,6 +619,26 @@ void drm_kms_helper_hotplug_event(struct drm_device *dev)
|
|||
}
|
||||
EXPORT_SYMBOL(drm_kms_helper_hotplug_event);
|
||||
|
||||
/**
|
||||
* drm_kms_helper_connector_hotplug_event - fire off a KMS connector hotplug event
|
||||
* @connector: drm_connector which has changed
|
||||
*
|
||||
* This is the same as drm_kms_helper_hotplug_event(), except it fires a more
|
||||
* fine-grained uevent for a single connector.
|
||||
*/
|
||||
void drm_kms_helper_connector_hotplug_event(struct drm_connector *connector)
|
||||
{
|
||||
struct drm_device *dev = connector->dev;
|
||||
|
||||
/* send a uevent + call fbdev */
|
||||
drm_sysfs_connector_hotplug_event(connector);
|
||||
if (dev->mode_config.funcs->output_poll_changed)
|
||||
dev->mode_config.funcs->output_poll_changed(dev);
|
||||
|
||||
drm_client_dev_hotplug(dev);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_kms_helper_connector_hotplug_event);
|
||||
|
||||
static void output_poll_execute(struct work_struct *work)
|
||||
{
|
||||
struct delayed_work *delayed_work = to_delayed_work(work);
|
||||
|
@ -865,7 +888,7 @@ bool drm_connector_helper_hpd_irq_event(struct drm_connector *connector)
|
|||
mutex_unlock(&dev->mode_config.mutex);
|
||||
|
||||
if (changed) {
|
||||
drm_kms_helper_hotplug_event(dev);
|
||||
drm_kms_helper_connector_hotplug_event(connector);
|
||||
drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Sent hotplug event\n",
|
||||
connector->base.id,
|
||||
connector->name);
|
||||
|
@ -904,9 +927,9 @@ EXPORT_SYMBOL(drm_connector_helper_hpd_irq_event);
|
|||
*/
|
||||
bool drm_helper_hpd_irq_event(struct drm_device *dev)
|
||||
{
|
||||
struct drm_connector *connector;
|
||||
struct drm_connector *connector, *first_changed_connector = NULL;
|
||||
struct drm_connector_list_iter conn_iter;
|
||||
bool changed = false;
|
||||
int changed = 0;
|
||||
|
||||
if (!dev->mode_config.poll_enabled)
|
||||
return false;
|
||||
|
@ -918,16 +941,25 @@ bool drm_helper_hpd_irq_event(struct drm_device *dev)
|
|||
if (!(connector->polled & DRM_CONNECTOR_POLL_HPD))
|
||||
continue;
|
||||
|
||||
if (check_connector_changed(connector))
|
||||
changed = true;
|
||||
if (check_connector_changed(connector)) {
|
||||
if (!first_changed_connector) {
|
||||
drm_connector_get(connector);
|
||||
first_changed_connector = connector;
|
||||
}
|
||||
|
||||
changed++;
|
||||
}
|
||||
}
|
||||
drm_connector_list_iter_end(&conn_iter);
|
||||
mutex_unlock(&dev->mode_config.mutex);
|
||||
|
||||
if (changed) {
|
||||
if (changed == 1)
|
||||
drm_kms_helper_connector_hotplug_event(first_changed_connector);
|
||||
else if (changed > 0)
|
||||
drm_kms_helper_hotplug_event(dev);
|
||||
DRM_DEBUG_KMS("Sent hotplug event\n");
|
||||
}
|
||||
|
||||
if (first_changed_connector)
|
||||
drm_connector_put(first_changed_connector);
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
|
|
@ -409,6 +409,31 @@ void drm_sysfs_hotplug_event(struct drm_device *dev)
|
|||
}
|
||||
EXPORT_SYMBOL(drm_sysfs_hotplug_event);
|
||||
|
||||
/**
|
||||
* drm_sysfs_connector_hotplug_event - generate a DRM uevent for any connector
|
||||
* change
|
||||
* @connector: connector which has changed
|
||||
*
|
||||
* Send a uevent for the DRM connector specified by @connector. This will send
|
||||
* a uevent with the properties HOTPLUG=1 and CONNECTOR.
|
||||
*/
|
||||
void drm_sysfs_connector_hotplug_event(struct drm_connector *connector)
|
||||
{
|
||||
struct drm_device *dev = connector->dev;
|
||||
char hotplug_str[] = "HOTPLUG=1", conn_id[21];
|
||||
char *envp[] = { hotplug_str, conn_id, NULL };
|
||||
|
||||
snprintf(conn_id, sizeof(conn_id),
|
||||
"CONNECTOR=%u", connector->base.id);
|
||||
|
||||
drm_dbg_kms(connector->dev,
|
||||
"[CONNECTOR:%d:%s] generating connector hotplug event\n",
|
||||
connector->base.id, connector->name);
|
||||
|
||||
kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_sysfs_connector_hotplug_event);
|
||||
|
||||
/**
|
||||
* drm_sysfs_connector_status_event - generate a DRM uevent for connector
|
||||
* property status change
|
||||
|
|
|
@ -424,45 +424,24 @@ int etnaviv_gem_wait_bo(struct etnaviv_gpu *gpu, struct drm_gem_object *obj,
|
|||
}
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
static void etnaviv_gem_describe_fence(struct dma_fence *fence,
|
||||
const char *type, struct seq_file *m)
|
||||
{
|
||||
if (!test_bit(DMA_FENCE_FLAG_SIGNALED_BIT, &fence->flags))
|
||||
seq_printf(m, "\t%9s: %s %s seq %llu\n",
|
||||
type,
|
||||
fence->ops->get_driver_name(fence),
|
||||
fence->ops->get_timeline_name(fence),
|
||||
fence->seqno);
|
||||
}
|
||||
|
||||
static void etnaviv_gem_describe(struct drm_gem_object *obj, struct seq_file *m)
|
||||
{
|
||||
struct etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj);
|
||||
struct dma_resv *robj = obj->resv;
|
||||
struct dma_resv_list *fobj;
|
||||
struct dma_fence *fence;
|
||||
unsigned long off = drm_vma_node_start(&obj->vma_node);
|
||||
int r;
|
||||
|
||||
seq_printf(m, "%08x: %c %2d (%2d) %08lx %p %zd\n",
|
||||
etnaviv_obj->flags, is_active(etnaviv_obj) ? 'A' : 'I',
|
||||
obj->name, kref_read(&obj->refcount),
|
||||
off, etnaviv_obj->vaddr, obj->size);
|
||||
|
||||
rcu_read_lock();
|
||||
fobj = dma_resv_shared_list(robj);
|
||||
if (fobj) {
|
||||
unsigned int i, shared_count = fobj->shared_count;
|
||||
r = dma_resv_lock(robj, NULL);
|
||||
if (r)
|
||||
return;
|
||||
|
||||
for (i = 0; i < shared_count; i++) {
|
||||
fence = rcu_dereference(fobj->shared[i]);
|
||||
etnaviv_gem_describe_fence(fence, "Shared", m);
|
||||
}
|
||||
}
|
||||
|
||||
fence = dma_resv_excl_fence(robj);
|
||||
if (fence)
|
||||
etnaviv_gem_describe_fence(fence, "Exclusive", m);
|
||||
rcu_read_unlock();
|
||||
dma_resv_describe(robj, m);
|
||||
dma_resv_unlock(robj);
|
||||
}
|
||||
|
||||
void etnaviv_gem_describe_objects(struct etnaviv_drm_private *priv,
|
||||
|
|
|
@ -189,13 +189,13 @@ static int submit_fence_sync(struct etnaviv_gem_submit *submit)
|
|||
continue;
|
||||
|
||||
if (bo->flags & ETNA_SUBMIT_BO_WRITE) {
|
||||
ret = dma_resv_get_fences(robj, &bo->excl,
|
||||
ret = dma_resv_get_fences(robj, NULL,
|
||||
&bo->nr_shared,
|
||||
&bo->shared);
|
||||
if (ret)
|
||||
return ret;
|
||||
} else {
|
||||
bo->excl = dma_resv_get_excl_unlocked(robj);
|
||||
bo->excl = dma_fence_get(dma_resv_excl_fence(robj));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
|
||||
#include "framebuffer.h"
|
||||
#include "gem.h"
|
||||
#include "gtt.h"
|
||||
#include "psb_drv.h"
|
||||
#include "psb_intel_drv.h"
|
||||
#include "psb_intel_reg.h"
|
||||
|
@ -82,14 +81,13 @@ static vm_fault_t psbfb_vm_fault(struct vm_fault *vmf)
|
|||
struct drm_framebuffer *fb = vma->vm_private_data;
|
||||
struct drm_device *dev = fb->dev;
|
||||
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
||||
struct gtt_range *gtt = to_gtt_range(fb->obj[0]);
|
||||
struct psb_gem_object *pobj = to_psb_gem_object(fb->obj[0]);
|
||||
int page_num;
|
||||
int i;
|
||||
unsigned long address;
|
||||
vm_fault_t ret = VM_FAULT_SIGBUS;
|
||||
unsigned long pfn;
|
||||
unsigned long phys_addr = (unsigned long)dev_priv->stolen_base +
|
||||
gtt->offset;
|
||||
unsigned long phys_addr = (unsigned long)dev_priv->stolen_base + pobj->offset;
|
||||
|
||||
page_num = vma_pages(vma);
|
||||
address = vmf->address - (vmf->pgoff << PAGE_SHIFT);
|
||||
|
@ -225,31 +223,6 @@ static struct drm_framebuffer *psb_framebuffer_create
|
|||
return fb;
|
||||
}
|
||||
|
||||
/**
|
||||
* psbfb_alloc - allocate frame buffer memory
|
||||
* @dev: the DRM device
|
||||
* @aligned_size: space needed
|
||||
*
|
||||
* Allocate the frame buffer. In the usual case we get a GTT range that
|
||||
* is stolen memory backed and life is simple. If there isn't sufficient
|
||||
* we fail as we don't have the virtual mapping space to really vmap it
|
||||
* and the kernel console code can't handle non linear framebuffers.
|
||||
*
|
||||
* Re-address this as and if the framebuffer layer grows this ability.
|
||||
*/
|
||||
static struct gtt_range *psbfb_alloc(struct drm_device *dev, int aligned_size)
|
||||
{
|
||||
struct gtt_range *backing;
|
||||
/* Begin by trying to use stolen memory backing */
|
||||
backing = psb_gtt_alloc_range(dev, aligned_size, "fb", 1, PAGE_SIZE);
|
||||
if (backing) {
|
||||
backing->gem.funcs = &psb_gem_object_funcs;
|
||||
drm_gem_private_object_init(dev, &backing->gem, aligned_size);
|
||||
return backing;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* psbfb_create - create a framebuffer
|
||||
* @fb_helper: the framebuffer helper
|
||||
|
@ -268,7 +241,8 @@ static int psbfb_create(struct drm_fb_helper *fb_helper,
|
|||
struct drm_mode_fb_cmd2 mode_cmd;
|
||||
int size;
|
||||
int ret;
|
||||
struct gtt_range *backing;
|
||||
struct psb_gem_object *backing;
|
||||
struct drm_gem_object *obj;
|
||||
u32 bpp, depth;
|
||||
|
||||
mode_cmd.width = sizes->surface_width;
|
||||
|
@ -286,24 +260,25 @@ static int psbfb_create(struct drm_fb_helper *fb_helper,
|
|||
size = ALIGN(size, PAGE_SIZE);
|
||||
|
||||
/* Allocate the framebuffer in the GTT with stolen page backing */
|
||||
backing = psbfb_alloc(dev, size);
|
||||
if (backing == NULL)
|
||||
return -ENOMEM;
|
||||
backing = psb_gem_create(dev, size, "fb", true, PAGE_SIZE);
|
||||
if (IS_ERR(backing))
|
||||
return PTR_ERR(backing);
|
||||
obj = &backing->base;
|
||||
|
||||
memset(dev_priv->vram_addr + backing->offset, 0, size);
|
||||
|
||||
info = drm_fb_helper_alloc_fbi(fb_helper);
|
||||
if (IS_ERR(info)) {
|
||||
ret = PTR_ERR(info);
|
||||
goto out;
|
||||
goto err_drm_gem_object_put;
|
||||
}
|
||||
|
||||
mode_cmd.pixel_format = drm_mode_legacy_fb_format(bpp, depth);
|
||||
|
||||
fb = psb_framebuffer_create(dev, &mode_cmd, &backing->gem);
|
||||
fb = psb_framebuffer_create(dev, &mode_cmd, obj);
|
||||
if (IS_ERR(fb)) {
|
||||
ret = PTR_ERR(fb);
|
||||
goto out;
|
||||
goto err_drm_gem_object_put;
|
||||
}
|
||||
|
||||
fb_helper->fb = fb;
|
||||
|
@ -334,8 +309,9 @@ static int psbfb_create(struct drm_fb_helper *fb_helper,
|
|||
dev_dbg(dev->dev, "allocated %dx%d fb\n", fb->width, fb->height);
|
||||
|
||||
return 0;
|
||||
out:
|
||||
psb_gtt_free_range(dev, backing);
|
||||
|
||||
err_drm_gem_object_put:
|
||||
drm_gem_object_put(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -13,24 +13,105 @@
|
|||
|
||||
#include <linux/pagemap.h>
|
||||
|
||||
#include <asm/set_memory.h>
|
||||
|
||||
#include <drm/drm.h>
|
||||
#include <drm/drm_vma_manager.h>
|
||||
|
||||
#include "gem.h"
|
||||
#include "psb_drv.h"
|
||||
|
||||
int psb_gem_pin(struct psb_gem_object *pobj)
|
||||
{
|
||||
struct drm_gem_object *obj = &pobj->base;
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
||||
u32 gpu_base = dev_priv->gtt.gatt_start;
|
||||
struct page **pages;
|
||||
unsigned int npages;
|
||||
int ret;
|
||||
|
||||
mutex_lock(&dev_priv->gtt_mutex);
|
||||
|
||||
if (pobj->in_gart || pobj->stolen)
|
||||
goto out; /* already mapped */
|
||||
|
||||
pages = drm_gem_get_pages(obj);
|
||||
if (IS_ERR(pages)) {
|
||||
ret = PTR_ERR(pages);
|
||||
goto err_mutex_unlock;
|
||||
}
|
||||
|
||||
npages = obj->size / PAGE_SIZE;
|
||||
|
||||
set_pages_array_wc(pages, npages);
|
||||
|
||||
psb_gtt_insert_pages(dev_priv, &pobj->resource, pages);
|
||||
psb_mmu_insert_pages(psb_mmu_get_default_pd(dev_priv->mmu), pages,
|
||||
(gpu_base + pobj->offset), npages, 0, 0,
|
||||
PSB_MMU_CACHED_MEMORY);
|
||||
|
||||
pobj->npage = npages;
|
||||
pobj->pages = pages;
|
||||
|
||||
out:
|
||||
++pobj->in_gart;
|
||||
mutex_unlock(&dev_priv->gtt_mutex);
|
||||
|
||||
return 0;
|
||||
|
||||
err_mutex_unlock:
|
||||
mutex_unlock(&dev_priv->gtt_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void psb_gem_unpin(struct psb_gem_object *pobj)
|
||||
{
|
||||
struct drm_gem_object *obj = &pobj->base;
|
||||
struct drm_device *dev = obj->dev;
|
||||
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
||||
u32 gpu_base = dev_priv->gtt.gatt_start;
|
||||
|
||||
mutex_lock(&dev_priv->gtt_mutex);
|
||||
|
||||
WARN_ON(!pobj->in_gart);
|
||||
|
||||
--pobj->in_gart;
|
||||
|
||||
if (pobj->in_gart || pobj->stolen)
|
||||
goto out;
|
||||
|
||||
psb_mmu_remove_pages(psb_mmu_get_default_pd(dev_priv->mmu),
|
||||
(gpu_base + pobj->offset), pobj->npage, 0, 0);
|
||||
psb_gtt_remove_pages(dev_priv, &pobj->resource);
|
||||
|
||||
/* Reset caching flags */
|
||||
set_pages_array_wb(pobj->pages, pobj->npage);
|
||||
|
||||
drm_gem_put_pages(obj, pobj->pages, true, false);
|
||||
pobj->pages = NULL;
|
||||
pobj->npage = 0;
|
||||
|
||||
out:
|
||||
mutex_unlock(&dev_priv->gtt_mutex);
|
||||
}
|
||||
|
||||
static vm_fault_t psb_gem_fault(struct vm_fault *vmf);
|
||||
|
||||
static void psb_gem_free_object(struct drm_gem_object *obj)
|
||||
{
|
||||
struct gtt_range *gtt = container_of(obj, struct gtt_range, gem);
|
||||
struct psb_gem_object *pobj = to_psb_gem_object(obj);
|
||||
|
||||
/* Remove the list map if one is present */
|
||||
drm_gem_free_mmap_offset(obj);
|
||||
drm_gem_object_release(obj);
|
||||
|
||||
/* This must occur last as it frees up the memory of the GEM object */
|
||||
psb_gtt_free_range(obj->dev, gtt);
|
||||
/* Undo the mmap pin if we are destroying the object */
|
||||
if (pobj->mmapping)
|
||||
psb_gem_unpin(pobj);
|
||||
|
||||
WARN_ON(pobj->in_gart && !pobj->stolen);
|
||||
|
||||
release_resource(&pobj->resource);
|
||||
kfree(pobj);
|
||||
}
|
||||
|
||||
static const struct vm_operations_struct psb_gem_vm_ops = {
|
||||
|
@ -39,63 +120,60 @@ static const struct vm_operations_struct psb_gem_vm_ops = {
|
|||
.close = drm_gem_vm_close,
|
||||
};
|
||||
|
||||
const struct drm_gem_object_funcs psb_gem_object_funcs = {
|
||||
static const struct drm_gem_object_funcs psb_gem_object_funcs = {
|
||||
.free = psb_gem_free_object,
|
||||
.vm_ops = &psb_gem_vm_ops,
|
||||
};
|
||||
|
||||
/**
|
||||
* psb_gem_create - create a mappable object
|
||||
* @file: the DRM file of the client
|
||||
* @dev: our device
|
||||
* @size: the size requested
|
||||
* @handlep: returned handle (opaque number)
|
||||
* @stolen: unused
|
||||
* @align: unused
|
||||
*
|
||||
* Create a GEM object, fill in the boilerplate and attach a handle to
|
||||
* it so that userspace can speak about it. This does the core work
|
||||
* for the various methods that do/will create GEM objects for things
|
||||
*/
|
||||
int psb_gem_create(struct drm_file *file, struct drm_device *dev, u64 size,
|
||||
u32 *handlep, int stolen, u32 align)
|
||||
struct psb_gem_object *
|
||||
psb_gem_create(struct drm_device *dev, u64 size, const char *name, bool stolen, u32 align)
|
||||
{
|
||||
struct gtt_range *r;
|
||||
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
||||
struct psb_gem_object *pobj;
|
||||
struct drm_gem_object *obj;
|
||||
int ret;
|
||||
u32 handle;
|
||||
|
||||
size = roundup(size, PAGE_SIZE);
|
||||
|
||||
/* Allocate our object - for now a direct gtt range which is not
|
||||
stolen memory backed */
|
||||
r = psb_gtt_alloc_range(dev, size, "gem", 0, PAGE_SIZE);
|
||||
if (r == NULL) {
|
||||
dev_err(dev->dev, "no memory for %lld byte GEM object\n", size);
|
||||
return -ENOSPC;
|
||||
pobj = kzalloc(sizeof(*pobj), GFP_KERNEL);
|
||||
if (!pobj)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
obj = &pobj->base;
|
||||
|
||||
/* GTT resource */
|
||||
|
||||
ret = psb_gtt_allocate_resource(dev_priv, &pobj->resource, name, size, align, stolen,
|
||||
&pobj->offset);
|
||||
if (ret)
|
||||
goto err_kfree;
|
||||
|
||||
if (stolen) {
|
||||
pobj->stolen = true;
|
||||
pobj->in_gart = 1;
|
||||
}
|
||||
r->gem.funcs = &psb_gem_object_funcs;
|
||||
/* Initialize the extra goodies GEM needs to do all the hard work */
|
||||
if (drm_gem_object_init(dev, &r->gem, size) != 0) {
|
||||
psb_gtt_free_range(dev, r);
|
||||
/* GEM doesn't give an error code so use -ENOMEM */
|
||||
dev_err(dev->dev, "GEM init failed for %lld\n", size);
|
||||
return -ENOMEM;
|
||||
|
||||
/* GEM object */
|
||||
|
||||
obj->funcs = &psb_gem_object_funcs;
|
||||
|
||||
if (stolen) {
|
||||
drm_gem_private_object_init(dev, obj, size);
|
||||
} else {
|
||||
ret = drm_gem_object_init(dev, obj, size);
|
||||
if (ret)
|
||||
goto err_release_resource;
|
||||
|
||||
/* Limit the object to 32-bit mappings */
|
||||
mapping_set_gfp_mask(obj->filp->f_mapping, GFP_KERNEL | __GFP_DMA32);
|
||||
}
|
||||
/* Limit the object to 32bit mappings */
|
||||
mapping_set_gfp_mask(r->gem.filp->f_mapping, GFP_KERNEL | __GFP_DMA32);
|
||||
/* Give the object a handle so we can carry it more easily */
|
||||
ret = drm_gem_handle_create(file, &r->gem, &handle);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "GEM handle failed for %p, %lld\n",
|
||||
&r->gem, size);
|
||||
drm_gem_object_release(&r->gem);
|
||||
psb_gtt_free_range(dev, r);
|
||||
return ret;
|
||||
}
|
||||
/* We have the initial and handle reference but need only one now */
|
||||
drm_gem_object_put(&r->gem);
|
||||
*handlep = handle;
|
||||
return 0;
|
||||
|
||||
return pobj;
|
||||
|
||||
err_release_resource:
|
||||
release_resource(&pobj->resource);
|
||||
err_kfree:
|
||||
kfree(pobj);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -111,10 +189,40 @@ int psb_gem_create(struct drm_file *file, struct drm_device *dev, u64 size,
|
|||
int psb_gem_dumb_create(struct drm_file *file, struct drm_device *dev,
|
||||
struct drm_mode_create_dumb *args)
|
||||
{
|
||||
args->pitch = ALIGN(args->width * ((args->bpp + 7) / 8), 64);
|
||||
args->size = args->pitch * args->height;
|
||||
return psb_gem_create(file, dev, args->size, &args->handle, 0,
|
||||
PAGE_SIZE);
|
||||
size_t pitch, size;
|
||||
struct psb_gem_object *pobj;
|
||||
struct drm_gem_object *obj;
|
||||
u32 handle;
|
||||
int ret;
|
||||
|
||||
pitch = args->width * DIV_ROUND_UP(args->bpp, 8);
|
||||
pitch = ALIGN(pitch, 64);
|
||||
|
||||
size = pitch * args->height;
|
||||
size = roundup(size, PAGE_SIZE);
|
||||
if (!size)
|
||||
return -EINVAL;
|
||||
|
||||
pobj = psb_gem_create(dev, size, "gem", false, PAGE_SIZE);
|
||||
if (IS_ERR(pobj))
|
||||
return PTR_ERR(pobj);
|
||||
obj = &pobj->base;
|
||||
|
||||
ret = drm_gem_handle_create(file, obj, &handle);
|
||||
if (ret)
|
||||
goto err_drm_gem_object_put;
|
||||
|
||||
drm_gem_object_put(obj);
|
||||
|
||||
args->pitch = pitch;
|
||||
args->size = size;
|
||||
args->handle = handle;
|
||||
|
||||
return 0;
|
||||
|
||||
err_drm_gem_object_put:
|
||||
drm_gem_object_put(obj);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -137,7 +245,7 @@ static vm_fault_t psb_gem_fault(struct vm_fault *vmf)
|
|||
{
|
||||
struct vm_area_struct *vma = vmf->vma;
|
||||
struct drm_gem_object *obj;
|
||||
struct gtt_range *r;
|
||||
struct psb_gem_object *pobj;
|
||||
int err;
|
||||
vm_fault_t ret;
|
||||
unsigned long pfn;
|
||||
|
@ -149,7 +257,7 @@ static vm_fault_t psb_gem_fault(struct vm_fault *vmf)
|
|||
dev = obj->dev;
|
||||
dev_priv = to_drm_psb_private(dev);
|
||||
|
||||
r = container_of(obj, struct gtt_range, gem); /* Get the gtt range */
|
||||
pobj = to_psb_gem_object(obj);
|
||||
|
||||
/* Make sure we don't parallel update on a fault, nor move or remove
|
||||
something from beneath our feet */
|
||||
|
@ -157,14 +265,14 @@ static vm_fault_t psb_gem_fault(struct vm_fault *vmf)
|
|||
|
||||
/* For now the mmap pins the object and it stays pinned. As things
|
||||
stand that will do us no harm */
|
||||
if (r->mmapping == 0) {
|
||||
err = psb_gtt_pin(r);
|
||||
if (pobj->mmapping == 0) {
|
||||
err = psb_gem_pin(pobj);
|
||||
if (err < 0) {
|
||||
dev_err(dev->dev, "gma500: pin failed: %d\n", err);
|
||||
ret = vmf_error(err);
|
||||
goto fail;
|
||||
}
|
||||
r->mmapping = 1;
|
||||
pobj->mmapping = 1;
|
||||
}
|
||||
|
||||
/* Page relative to the VMA start - we must calculate this ourselves
|
||||
|
@ -172,10 +280,10 @@ static vm_fault_t psb_gem_fault(struct vm_fault *vmf)
|
|||
page_offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT;
|
||||
|
||||
/* CPU view of the page, don't go via the GART for CPU writes */
|
||||
if (r->stolen)
|
||||
pfn = (dev_priv->stolen_base + r->offset) >> PAGE_SHIFT;
|
||||
if (pobj->stolen)
|
||||
pfn = (dev_priv->stolen_base + pobj->offset) >> PAGE_SHIFT;
|
||||
else
|
||||
pfn = page_to_pfn(r->pages[page_offset]);
|
||||
pfn = page_to_pfn(pobj->pages[page_offset]);
|
||||
ret = vmf_insert_pfn(vma, vmf->address, pfn);
|
||||
fail:
|
||||
mutex_unlock(&dev_priv->mmap_mutex);
|
||||
|
|
|
@ -8,11 +8,33 @@
|
|||
#ifndef _GEM_H
|
||||
#define _GEM_H
|
||||
|
||||
#include <linux/kernel.h>
|
||||
|
||||
#include <drm/drm_gem.h>
|
||||
|
||||
struct drm_device;
|
||||
|
||||
extern const struct drm_gem_object_funcs psb_gem_object_funcs;
|
||||
struct psb_gem_object {
|
||||
struct drm_gem_object base;
|
||||
|
||||
extern int psb_gem_create(struct drm_file *file, struct drm_device *dev,
|
||||
u64 size, u32 *handlep, int stolen, u32 align);
|
||||
struct resource resource; /* GTT resource for our allocation */
|
||||
u32 offset; /* GTT offset of our object */
|
||||
int in_gart; /* Currently in the GART (ref ct) */
|
||||
bool stolen; /* Backed from stolen RAM */
|
||||
bool mmapping; /* Is mmappable */
|
||||
struct page **pages; /* Backing pages if present */
|
||||
int npage; /* Number of backing pages */
|
||||
};
|
||||
|
||||
static inline struct psb_gem_object *to_psb_gem_object(struct drm_gem_object *obj)
|
||||
{
|
||||
return container_of(obj, struct psb_gem_object, base);
|
||||
}
|
||||
|
||||
struct psb_gem_object *
|
||||
psb_gem_create(struct drm_device *dev, u64 size, const char *name, bool stolen, u32 align);
|
||||
|
||||
int psb_gem_pin(struct psb_gem_object *pobj);
|
||||
void psb_gem_unpin(struct psb_gem_object *pobj);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <drm/drm_vblank.h>
|
||||
|
||||
#include "framebuffer.h"
|
||||
#include "gem.h"
|
||||
#include "gma_display.h"
|
||||
#include "psb_drv.h"
|
||||
#include "psb_intel_drv.h"
|
||||
|
@ -54,7 +55,7 @@ int gma_pipe_set_base(struct drm_crtc *crtc, int x, int y,
|
|||
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
||||
struct gma_crtc *gma_crtc = to_gma_crtc(crtc);
|
||||
struct drm_framebuffer *fb = crtc->primary->fb;
|
||||
struct gtt_range *gtt;
|
||||
struct psb_gem_object *pobj;
|
||||
int pipe = gma_crtc->pipe;
|
||||
const struct psb_offset *map = &dev_priv->regmap[pipe];
|
||||
unsigned long start, offset;
|
||||
|
@ -70,14 +71,14 @@ int gma_pipe_set_base(struct drm_crtc *crtc, int x, int y,
|
|||
goto gma_pipe_cleaner;
|
||||
}
|
||||
|
||||
gtt = to_gtt_range(fb->obj[0]);
|
||||
pobj = to_psb_gem_object(fb->obj[0]);
|
||||
|
||||
/* We are displaying this buffer, make sure it is actually loaded
|
||||
into the GTT */
|
||||
ret = psb_gtt_pin(gtt);
|
||||
ret = psb_gem_pin(pobj);
|
||||
if (ret < 0)
|
||||
goto gma_pipe_set_base_exit;
|
||||
start = gtt->offset;
|
||||
start = pobj->offset;
|
||||
offset = y * fb->pitches[0] + x * fb->format->cpp[0];
|
||||
|
||||
REG_WRITE(map->stride, fb->pitches[0]);
|
||||
|
@ -125,7 +126,7 @@ int gma_pipe_set_base(struct drm_crtc *crtc, int x, int y,
|
|||
gma_pipe_cleaner:
|
||||
/* If there was a previous display we can now unpin it */
|
||||
if (old_fb)
|
||||
psb_gtt_unpin(to_gtt_range(old_fb->obj[0]));
|
||||
psb_gem_unpin(to_psb_gem_object(old_fb->obj[0]));
|
||||
|
||||
gma_pipe_set_base_exit:
|
||||
gma_power_end(dev);
|
||||
|
@ -331,8 +332,8 @@ int gma_crtc_cursor_set(struct drm_crtc *crtc,
|
|||
uint32_t base = (pipe == 0) ? CURABASE : CURBBASE;
|
||||
uint32_t temp;
|
||||
size_t addr = 0;
|
||||
struct gtt_range *gt;
|
||||
struct gtt_range *cursor_gt = gma_crtc->cursor_gt;
|
||||
struct psb_gem_object *pobj;
|
||||
struct psb_gem_object *cursor_pobj = gma_crtc->cursor_pobj;
|
||||
struct drm_gem_object *obj;
|
||||
void *tmp_dst, *tmp_src;
|
||||
int ret = 0, i, cursor_pages;
|
||||
|
@ -348,9 +349,8 @@ int gma_crtc_cursor_set(struct drm_crtc *crtc,
|
|||
|
||||
/* Unpin the old GEM object */
|
||||
if (gma_crtc->cursor_obj) {
|
||||
gt = container_of(gma_crtc->cursor_obj,
|
||||
struct gtt_range, gem);
|
||||
psb_gtt_unpin(gt);
|
||||
pobj = to_psb_gem_object(gma_crtc->cursor_obj);
|
||||
psb_gem_unpin(pobj);
|
||||
drm_gem_object_put(gma_crtc->cursor_obj);
|
||||
gma_crtc->cursor_obj = NULL;
|
||||
}
|
||||
|
@ -375,40 +375,40 @@ int gma_crtc_cursor_set(struct drm_crtc *crtc,
|
|||
goto unref_cursor;
|
||||
}
|
||||
|
||||
gt = container_of(obj, struct gtt_range, gem);
|
||||
pobj = to_psb_gem_object(obj);
|
||||
|
||||
/* Pin the memory into the GTT */
|
||||
ret = psb_gtt_pin(gt);
|
||||
ret = psb_gem_pin(pobj);
|
||||
if (ret) {
|
||||
dev_err(dev->dev, "Can not pin down handle 0x%x\n", handle);
|
||||
goto unref_cursor;
|
||||
}
|
||||
|
||||
if (dev_priv->ops->cursor_needs_phys) {
|
||||
if (cursor_gt == NULL) {
|
||||
if (!cursor_pobj) {
|
||||
dev_err(dev->dev, "No hardware cursor mem available");
|
||||
ret = -ENOMEM;
|
||||
goto unref_cursor;
|
||||
}
|
||||
|
||||
/* Prevent overflow */
|
||||
if (gt->npage > 4)
|
||||
if (pobj->npage > 4)
|
||||
cursor_pages = 4;
|
||||
else
|
||||
cursor_pages = gt->npage;
|
||||
cursor_pages = pobj->npage;
|
||||
|
||||
/* Copy the cursor to cursor mem */
|
||||
tmp_dst = dev_priv->vram_addr + cursor_gt->offset;
|
||||
tmp_dst = dev_priv->vram_addr + cursor_pobj->offset;
|
||||
for (i = 0; i < cursor_pages; i++) {
|
||||
tmp_src = kmap(gt->pages[i]);
|
||||
tmp_src = kmap(pobj->pages[i]);
|
||||
memcpy(tmp_dst, tmp_src, PAGE_SIZE);
|
||||
kunmap(gt->pages[i]);
|
||||
kunmap(pobj->pages[i]);
|
||||
tmp_dst += PAGE_SIZE;
|
||||
}
|
||||
|
||||
addr = gma_crtc->cursor_addr;
|
||||
} else {
|
||||
addr = gt->offset;
|
||||
addr = pobj->offset;
|
||||
gma_crtc->cursor_addr = addr;
|
||||
}
|
||||
|
||||
|
@ -425,8 +425,8 @@ int gma_crtc_cursor_set(struct drm_crtc *crtc,
|
|||
|
||||
/* unpin the old bo */
|
||||
if (gma_crtc->cursor_obj) {
|
||||
gt = container_of(gma_crtc->cursor_obj, struct gtt_range, gem);
|
||||
psb_gtt_unpin(gt);
|
||||
pobj = to_psb_gem_object(gma_crtc->cursor_obj);
|
||||
psb_gem_unpin(pobj);
|
||||
drm_gem_object_put(gma_crtc->cursor_obj);
|
||||
}
|
||||
|
||||
|
@ -483,14 +483,14 @@ void gma_crtc_commit(struct drm_crtc *crtc)
|
|||
|
||||
void gma_crtc_disable(struct drm_crtc *crtc)
|
||||
{
|
||||
struct gtt_range *gt;
|
||||
struct psb_gem_object *pobj;
|
||||
const struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
|
||||
|
||||
crtc_funcs->dpms(crtc, DRM_MODE_DPMS_OFF);
|
||||
|
||||
if (crtc->primary->fb) {
|
||||
gt = to_gtt_range(crtc->primary->fb->obj[0]);
|
||||
psb_gtt_unpin(gt);
|
||||
pobj = to_psb_gem_object(crtc->primary->fb->obj[0]);
|
||||
psb_gem_unpin(pobj);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -498,6 +498,9 @@ void gma_crtc_destroy(struct drm_crtc *crtc)
|
|||
{
|
||||
struct gma_crtc *gma_crtc = to_gma_crtc(crtc);
|
||||
|
||||
if (gma_crtc->cursor_pobj)
|
||||
drm_gem_object_put(&gma_crtc->cursor_pobj->base);
|
||||
|
||||
kfree(gma_crtc->crtc_state);
|
||||
drm_crtc_cleanup(crtc);
|
||||
kfree(gma_crtc);
|
||||
|
|
|
@ -7,10 +7,7 @@
|
|||
* Alan Cox <alan@linux.intel.com>
|
||||
*/
|
||||
|
||||
#include <linux/shmem_fs.h>
|
||||
|
||||
#include <asm/set_memory.h>
|
||||
|
||||
#include "gem.h" /* TODO: for struct psb_gem_object, see psb_gtt_restore() */
|
||||
#include "psb_drv.h"
|
||||
|
||||
|
||||
|
@ -18,6 +15,33 @@
|
|||
* GTT resource allocator - manage page mappings in GTT space
|
||||
*/
|
||||
|
||||
int psb_gtt_allocate_resource(struct drm_psb_private *pdev, struct resource *res,
|
||||
const char *name, resource_size_t size, resource_size_t align,
|
||||
bool stolen, u32 *offset)
|
||||
{
|
||||
struct resource *root = pdev->gtt_mem;
|
||||
resource_size_t start, end;
|
||||
int ret;
|
||||
|
||||
if (stolen) {
|
||||
/* The start of the GTT is backed by stolen pages. */
|
||||
start = root->start;
|
||||
end = root->start + pdev->gtt.stolen_size - 1;
|
||||
} else {
|
||||
/* The rest is backed by system pages. */
|
||||
start = root->start + pdev->gtt.stolen_size;
|
||||
end = root->end;
|
||||
}
|
||||
|
||||
res->name = name;
|
||||
ret = allocate_resource(root, res, size, start, end, align, NULL, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
*offset = res->start - root->start;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* psb_gtt_mask_pte - generate GTT pte entry
|
||||
* @pfn: page number to encode
|
||||
|
@ -43,281 +67,62 @@ static inline uint32_t psb_gtt_mask_pte(uint32_t pfn, int type)
|
|||
return (pfn << PAGE_SHIFT) | mask;
|
||||
}
|
||||
|
||||
/**
|
||||
* psb_gtt_entry - find the GTT entries for a gtt_range
|
||||
* @dev: our DRM device
|
||||
* @r: our GTT range
|
||||
*
|
||||
* Given a gtt_range object return the GTT offset of the page table
|
||||
* entries for this gtt_range
|
||||
*/
|
||||
static u32 __iomem *psb_gtt_entry(struct drm_device *dev, struct gtt_range *r)
|
||||
static u32 __iomem *psb_gtt_entry(struct drm_psb_private *pdev, const struct resource *res)
|
||||
{
|
||||
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
||||
unsigned long offset;
|
||||
unsigned long offset = res->start - pdev->gtt_mem->start;
|
||||
|
||||
offset = r->resource.start - dev_priv->gtt_mem->start;
|
||||
|
||||
return dev_priv->gtt_map + (offset >> PAGE_SHIFT);
|
||||
return pdev->gtt_map + (offset >> PAGE_SHIFT);
|
||||
}
|
||||
|
||||
/**
|
||||
* psb_gtt_insert - put an object into the GTT
|
||||
* @dev: our DRM device
|
||||
* @r: our GTT range
|
||||
* @resume: on resume
|
||||
*
|
||||
* Take our preallocated GTT range and insert the GEM object into
|
||||
* the GTT. This is protected via the gtt mutex which the caller
|
||||
* must hold.
|
||||
/*
|
||||
* Take our preallocated GTT range and insert the GEM object into
|
||||
* the GTT. This is protected via the gtt mutex which the caller
|
||||
* must hold.
|
||||
*/
|
||||
static int psb_gtt_insert(struct drm_device *dev, struct gtt_range *r,
|
||||
int resume)
|
||||
void psb_gtt_insert_pages(struct drm_psb_private *pdev, const struct resource *res,
|
||||
struct page **pages)
|
||||
{
|
||||
resource_size_t npages, i;
|
||||
u32 __iomem *gtt_slot;
|
||||
u32 pte;
|
||||
struct page **pages;
|
||||
int i;
|
||||
|
||||
if (r->pages == NULL) {
|
||||
WARN_ON(1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
WARN_ON(r->stolen); /* refcount these maybe ? */
|
||||
|
||||
gtt_slot = psb_gtt_entry(dev, r);
|
||||
pages = r->pages;
|
||||
|
||||
if (!resume) {
|
||||
/* Make sure changes are visible to the GPU */
|
||||
set_pages_array_wc(pages, r->npage);
|
||||
}
|
||||
|
||||
/* Write our page entries into the GTT itself */
|
||||
for (i = 0; i < r->npage; i++) {
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(r->pages[i]),
|
||||
PSB_MMU_CACHED_MEMORY);
|
||||
iowrite32(pte, gtt_slot++);
|
||||
|
||||
npages = resource_size(res) >> PAGE_SHIFT;
|
||||
gtt_slot = psb_gtt_entry(pdev, res);
|
||||
|
||||
for (i = 0; i < npages; ++i, ++gtt_slot) {
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(pages[i]), PSB_MMU_CACHED_MEMORY);
|
||||
iowrite32(pte, gtt_slot);
|
||||
}
|
||||
|
||||
/* Make sure all the entries are set before we return */
|
||||
ioread32(gtt_slot - 1);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* psb_gtt_remove - remove an object from the GTT
|
||||
* @dev: our DRM device
|
||||
* @r: our GTT range
|
||||
*
|
||||
* Remove a preallocated GTT range from the GTT. Overwrite all the
|
||||
* page table entries with the dummy page. This is protected via the gtt
|
||||
* mutex which the caller must hold.
|
||||
*/
|
||||
static void psb_gtt_remove(struct drm_device *dev, struct gtt_range *r)
|
||||
{
|
||||
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
||||
u32 __iomem *gtt_slot;
|
||||
u32 pte;
|
||||
int i;
|
||||
|
||||
WARN_ON(r->stolen);
|
||||
|
||||
gtt_slot = psb_gtt_entry(dev, r);
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(dev_priv->scratch_page),
|
||||
PSB_MMU_CACHED_MEMORY);
|
||||
|
||||
for (i = 0; i < r->npage; i++)
|
||||
iowrite32(pte, gtt_slot++);
|
||||
ioread32(gtt_slot - 1);
|
||||
set_pages_array_wb(r->pages, r->npage);
|
||||
}
|
||||
|
||||
/**
|
||||
* psb_gtt_attach_pages - attach and pin GEM pages
|
||||
* @gt: the gtt range
|
||||
*
|
||||
* Pin and build an in kernel list of the pages that back our GEM object.
|
||||
* While we hold this the pages cannot be swapped out. This is protected
|
||||
* via the gtt mutex which the caller must hold.
|
||||
*/
|
||||
static int psb_gtt_attach_pages(struct gtt_range *gt)
|
||||
{
|
||||
struct page **pages;
|
||||
|
||||
WARN_ON(gt->pages);
|
||||
|
||||
pages = drm_gem_get_pages(>->gem);
|
||||
if (IS_ERR(pages))
|
||||
return PTR_ERR(pages);
|
||||
|
||||
gt->npage = gt->gem.size / PAGE_SIZE;
|
||||
gt->pages = pages;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* psb_gtt_detach_pages - attach and pin GEM pages
|
||||
* @gt: the gtt range
|
||||
*
|
||||
* Undo the effect of psb_gtt_attach_pages. At this point the pages
|
||||
* must have been removed from the GTT as they could now be paged out
|
||||
* and move bus address. This is protected via the gtt mutex which the
|
||||
* caller must hold.
|
||||
*/
|
||||
static void psb_gtt_detach_pages(struct gtt_range *gt)
|
||||
{
|
||||
drm_gem_put_pages(>->gem, gt->pages, true, false);
|
||||
gt->pages = NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* psb_gtt_pin - pin pages into the GTT
|
||||
* @gt: range to pin
|
||||
*
|
||||
* Pin a set of pages into the GTT. The pins are refcounted so that
|
||||
* multiple pins need multiple unpins to undo.
|
||||
*
|
||||
* Non GEM backed objects treat this as a no-op as they are always GTT
|
||||
* backed objects.
|
||||
*/
|
||||
int psb_gtt_pin(struct gtt_range *gt)
|
||||
{
|
||||
int ret = 0;
|
||||
struct drm_device *dev = gt->gem.dev;
|
||||
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
||||
u32 gpu_base = dev_priv->gtt.gatt_start;
|
||||
|
||||
mutex_lock(&dev_priv->gtt_mutex);
|
||||
|
||||
if (gt->in_gart == 0 && gt->stolen == 0) {
|
||||
ret = psb_gtt_attach_pages(gt);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
ret = psb_gtt_insert(dev, gt, 0);
|
||||
if (ret < 0) {
|
||||
psb_gtt_detach_pages(gt);
|
||||
goto out;
|
||||
}
|
||||
psb_mmu_insert_pages(psb_mmu_get_default_pd(dev_priv->mmu),
|
||||
gt->pages, (gpu_base + gt->offset),
|
||||
gt->npage, 0, 0, PSB_MMU_CACHED_MEMORY);
|
||||
}
|
||||
gt->in_gart++;
|
||||
out:
|
||||
mutex_unlock(&dev_priv->gtt_mutex);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* psb_gtt_unpin - Drop a GTT pin requirement
|
||||
* @gt: range to pin
|
||||
*
|
||||
* Undoes the effect of psb_gtt_pin. On the last drop the GEM object
|
||||
* will be removed from the GTT which will also drop the page references
|
||||
* and allow the VM to clean up or page stuff.
|
||||
*
|
||||
* Non GEM backed objects treat this as a no-op as they are always GTT
|
||||
* backed objects.
|
||||
*/
|
||||
void psb_gtt_unpin(struct gtt_range *gt)
|
||||
{
|
||||
struct drm_device *dev = gt->gem.dev;
|
||||
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
||||
u32 gpu_base = dev_priv->gtt.gatt_start;
|
||||
|
||||
mutex_lock(&dev_priv->gtt_mutex);
|
||||
|
||||
WARN_ON(!gt->in_gart);
|
||||
|
||||
gt->in_gart--;
|
||||
if (gt->in_gart == 0 && gt->stolen == 0) {
|
||||
psb_mmu_remove_pages(psb_mmu_get_default_pd(dev_priv->mmu),
|
||||
(gpu_base + gt->offset), gt->npage, 0, 0);
|
||||
psb_gtt_remove(dev, gt);
|
||||
psb_gtt_detach_pages(gt);
|
||||
}
|
||||
|
||||
mutex_unlock(&dev_priv->gtt_mutex);
|
||||
}
|
||||
|
||||
/*
|
||||
* GTT resource allocator - allocate and manage GTT address space
|
||||
* Remove a preallocated GTT range from the GTT. Overwrite all the
|
||||
* page table entries with the dummy page. This is protected via the gtt
|
||||
* mutex which the caller must hold.
|
||||
*/
|
||||
|
||||
/**
|
||||
* psb_gtt_alloc_range - allocate GTT address space
|
||||
* @dev: Our DRM device
|
||||
* @len: length (bytes) of address space required
|
||||
* @name: resource name
|
||||
* @backed: resource should be backed by stolen pages
|
||||
* @align: requested alignment
|
||||
*
|
||||
* Ask the kernel core to find us a suitable range of addresses
|
||||
* to use for a GTT mapping.
|
||||
*
|
||||
* Returns a gtt_range structure describing the object, or NULL on
|
||||
* error. On successful return the resource is both allocated and marked
|
||||
* as in use.
|
||||
*/
|
||||
struct gtt_range *psb_gtt_alloc_range(struct drm_device *dev, int len,
|
||||
const char *name, int backed, u32 align)
|
||||
void psb_gtt_remove_pages(struct drm_psb_private *pdev, const struct resource *res)
|
||||
{
|
||||
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
||||
struct gtt_range *gt;
|
||||
struct resource *r = dev_priv->gtt_mem;
|
||||
int ret;
|
||||
unsigned long start, end;
|
||||
resource_size_t npages, i;
|
||||
u32 __iomem *gtt_slot;
|
||||
u32 pte;
|
||||
|
||||
if (backed) {
|
||||
/* The start of the GTT is the stolen pages */
|
||||
start = r->start;
|
||||
end = r->start + dev_priv->gtt.stolen_size - 1;
|
||||
} else {
|
||||
/* The rest we will use for GEM backed objects */
|
||||
start = r->start + dev_priv->gtt.stolen_size;
|
||||
end = r->end;
|
||||
}
|
||||
/* Install scratch page for the resource */
|
||||
|
||||
gt = kzalloc(sizeof(struct gtt_range), GFP_KERNEL);
|
||||
if (gt == NULL)
|
||||
return NULL;
|
||||
gt->resource.name = name;
|
||||
gt->stolen = backed;
|
||||
gt->in_gart = backed;
|
||||
/* Ensure this is set for non GEM objects */
|
||||
gt->gem.dev = dev;
|
||||
ret = allocate_resource(dev_priv->gtt_mem, >->resource,
|
||||
len, start, end, align, NULL, NULL);
|
||||
if (ret == 0) {
|
||||
gt->offset = gt->resource.start - r->start;
|
||||
return gt;
|
||||
}
|
||||
kfree(gt);
|
||||
return NULL;
|
||||
}
|
||||
pte = psb_gtt_mask_pte(page_to_pfn(pdev->scratch_page), PSB_MMU_CACHED_MEMORY);
|
||||
|
||||
/**
|
||||
* psb_gtt_free_range - release GTT address space
|
||||
* @dev: our DRM device
|
||||
* @gt: a mapping created with psb_gtt_alloc_range
|
||||
*
|
||||
* Release a resource that was allocated with psb_gtt_alloc_range. If the
|
||||
* object has been pinned by mmap users we clean this up here currently.
|
||||
*/
|
||||
void psb_gtt_free_range(struct drm_device *dev, struct gtt_range *gt)
|
||||
{
|
||||
/* Undo the mmap pin if we are destroying the object */
|
||||
if (gt->mmapping) {
|
||||
psb_gtt_unpin(gt);
|
||||
gt->mmapping = 0;
|
||||
}
|
||||
WARN_ON(gt->in_gart && !gt->stolen);
|
||||
release_resource(>->resource);
|
||||
kfree(gt);
|
||||
npages = resource_size(res) >> PAGE_SHIFT;
|
||||
gtt_slot = psb_gtt_entry(pdev, res);
|
||||
|
||||
for (i = 0; i < npages; ++i, ++gtt_slot)
|
||||
iowrite32(pte, gtt_slot);
|
||||
|
||||
/* Make sure all the entries are set before we return */
|
||||
ioread32(gtt_slot - 1);
|
||||
}
|
||||
|
||||
static void psb_gtt_alloc(struct drm_device *dev)
|
||||
|
@ -498,7 +303,7 @@ int psb_gtt_restore(struct drm_device *dev)
|
|||
{
|
||||
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
||||
struct resource *r = dev_priv->gtt_mem->child;
|
||||
struct gtt_range *range;
|
||||
struct psb_gem_object *pobj;
|
||||
unsigned int restored = 0, total = 0, size = 0;
|
||||
|
||||
/* On resume, the gtt_mutex is already initialized */
|
||||
|
@ -506,10 +311,15 @@ int psb_gtt_restore(struct drm_device *dev)
|
|||
psb_gtt_init(dev, 1);
|
||||
|
||||
while (r != NULL) {
|
||||
range = container_of(r, struct gtt_range, resource);
|
||||
if (range->pages) {
|
||||
psb_gtt_insert(dev, range, 1);
|
||||
size += range->resource.end - range->resource.start;
|
||||
/*
|
||||
* TODO: GTT restoration needs a refactoring, so that we don't have to touch
|
||||
* struct psb_gem_object here. The type represents a GEM object and is
|
||||
* not related to the GTT itself.
|
||||
*/
|
||||
pobj = container_of(r, struct psb_gem_object, resource);
|
||||
if (pobj->pages) {
|
||||
psb_gtt_insert_pages(dev_priv, &pobj->resource, pobj->pages);
|
||||
size += pobj->resource.end - pobj->resource.start;
|
||||
restored++;
|
||||
}
|
||||
r = r->sibling;
|
||||
|
|
|
@ -10,6 +10,8 @@
|
|||
|
||||
#include <drm/drm_gem.h>
|
||||
|
||||
struct drm_psb_private;
|
||||
|
||||
/* This wants cleaning up with respect to the psb_dev and un-needed stuff */
|
||||
struct psb_gtt {
|
||||
uint32_t gatt_start;
|
||||
|
@ -26,27 +28,14 @@ struct psb_gtt {
|
|||
/* Exported functions */
|
||||
extern int psb_gtt_init(struct drm_device *dev, int resume);
|
||||
extern void psb_gtt_takedown(struct drm_device *dev);
|
||||
|
||||
/* Each gtt_range describes an allocation in the GTT area */
|
||||
struct gtt_range {
|
||||
struct resource resource; /* Resource for our allocation */
|
||||
u32 offset; /* GTT offset of our object */
|
||||
struct drm_gem_object gem; /* GEM high level stuff */
|
||||
int in_gart; /* Currently in the GART (ref ct) */
|
||||
bool stolen; /* Backed from stolen RAM */
|
||||
bool mmapping; /* Is mmappable */
|
||||
struct page **pages; /* Backing pages if present */
|
||||
int npage; /* Number of backing pages */
|
||||
};
|
||||
|
||||
#define to_gtt_range(x) container_of(x, struct gtt_range, gem)
|
||||
|
||||
extern struct gtt_range *psb_gtt_alloc_range(struct drm_device *dev, int len,
|
||||
const char *name, int backed,
|
||||
u32 align);
|
||||
extern void psb_gtt_kref_put(struct gtt_range *gt);
|
||||
extern void psb_gtt_free_range(struct drm_device *dev, struct gtt_range *gt);
|
||||
extern int psb_gtt_pin(struct gtt_range *gt);
|
||||
extern void psb_gtt_unpin(struct gtt_range *gt);
|
||||
extern int psb_gtt_restore(struct drm_device *dev);
|
||||
|
||||
int psb_gtt_allocate_resource(struct drm_psb_private *pdev, struct resource *res,
|
||||
const char *name, resource_size_t size, resource_size_t align,
|
||||
bool stolen, u32 *offset);
|
||||
|
||||
void psb_gtt_insert_pages(struct drm_psb_private *pdev, const struct resource *res,
|
||||
struct page **pages);
|
||||
void psb_gtt_remove_pages(struct drm_psb_private *pdev, const struct resource *res);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <drm/drm_fourcc.h>
|
||||
|
||||
#include "framebuffer.h"
|
||||
#include "gem.h"
|
||||
#include "gma_display.h"
|
||||
#include "power.h"
|
||||
#include "psb_drv.h"
|
||||
|
@ -608,7 +609,7 @@ static int oaktrail_pipe_set_base(struct drm_crtc *crtc,
|
|||
if (!gma_power_begin(dev, true))
|
||||
return 0;
|
||||
|
||||
start = to_gtt_range(fb->obj[0])->offset;
|
||||
start = to_psb_gem_object(fb->obj[0])->offset;
|
||||
offset = y * fb->pitches[0] + x * fb->format->cpp[0];
|
||||
|
||||
REG_WRITE(map->stride, fb->pitches[0]);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <acpi/video.h>
|
||||
|
||||
#include <drm/drm.h>
|
||||
#include <drm/drm_aperture.h>
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/drm_fb_helper.h>
|
||||
#include <drm/drm_file.h>
|
||||
|
@ -448,6 +449,17 @@ static int psb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
|
|||
struct drm_device *dev;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* We cannot yet easily find the framebuffer's location in memory. So
|
||||
* remove all framebuffers here.
|
||||
*
|
||||
* TODO: Refactor psb_driver_load() to map vdc_reg earlier. Then we
|
||||
* might be able to read the framebuffer range from the device.
|
||||
*/
|
||||
ret = drm_aperture_remove_framebuffers(true, &driver);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = pcim_enable_device(pdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <drm/drm_plane_helper.h>
|
||||
|
||||
#include "framebuffer.h"
|
||||
#include "gem.h"
|
||||
#include "gma_display.h"
|
||||
#include "power.h"
|
||||
#include "psb_drv.h"
|
||||
|
@ -454,23 +455,21 @@ static void psb_intel_cursor_init(struct drm_device *dev,
|
|||
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
|
||||
u32 control[3] = { CURACNTR, CURBCNTR, CURCCNTR };
|
||||
u32 base[3] = { CURABASE, CURBBASE, CURCBASE };
|
||||
struct gtt_range *cursor_gt;
|
||||
struct psb_gem_object *cursor_pobj;
|
||||
|
||||
if (dev_priv->ops->cursor_needs_phys) {
|
||||
/* Allocate 4 pages of stolen mem for a hardware cursor. That
|
||||
* is enough for the 64 x 64 ARGB cursors we support.
|
||||
*/
|
||||
cursor_gt = psb_gtt_alloc_range(dev, 4 * PAGE_SIZE, "cursor", 1,
|
||||
PAGE_SIZE);
|
||||
if (!cursor_gt) {
|
||||
gma_crtc->cursor_gt = NULL;
|
||||
cursor_pobj = psb_gem_create(dev, 4 * PAGE_SIZE, "cursor", true, PAGE_SIZE);
|
||||
if (IS_ERR(cursor_pobj)) {
|
||||
gma_crtc->cursor_pobj = NULL;
|
||||
goto out;
|
||||
}
|
||||
gma_crtc->cursor_gt = cursor_gt;
|
||||
gma_crtc->cursor_addr = dev_priv->stolen_base +
|
||||
cursor_gt->offset;
|
||||
gma_crtc->cursor_pobj = cursor_pobj;
|
||||
gma_crtc->cursor_addr = dev_priv->stolen_base + cursor_pobj->offset;
|
||||
} else {
|
||||
gma_crtc->cursor_gt = NULL;
|
||||
gma_crtc->cursor_pobj = NULL;
|
||||
}
|
||||
|
||||
out:
|
||||
|
|
|
@ -140,7 +140,7 @@ struct gma_crtc {
|
|||
int pipe;
|
||||
int plane;
|
||||
uint32_t cursor_addr;
|
||||
struct gtt_range *cursor_gt;
|
||||
struct psb_gem_object *cursor_pobj;
|
||||
u8 lut_adj[256];
|
||||
struct psb_intel_framebuffer *fbdev_fb;
|
||||
/* a mode_set for fbdev users on this crtc */
|
||||
|
|
|
@ -74,7 +74,7 @@ static size_t gud_xrgb8888_to_r124(u8 *dst, const struct drm_format_info *format
|
|||
if (!buf)
|
||||
return 0;
|
||||
|
||||
drm_fb_xrgb8888_to_gray8(buf, src, fb, rect);
|
||||
drm_fb_xrgb8888_to_gray8(buf, 0, src, fb, rect);
|
||||
pix8 = buf;
|
||||
|
||||
for (y = 0; y < height; y++) {
|
||||
|
@ -190,23 +190,23 @@ retry:
|
|||
goto end_cpu_access;
|
||||
}
|
||||
} else if (format->format == DRM_FORMAT_R8) {
|
||||
drm_fb_xrgb8888_to_gray8(buf, vaddr, fb, rect);
|
||||
drm_fb_xrgb8888_to_gray8(buf, 0, vaddr, fb, rect);
|
||||
} else if (format->format == DRM_FORMAT_RGB332) {
|
||||
drm_fb_xrgb8888_to_rgb332(buf, vaddr, fb, rect);
|
||||
drm_fb_xrgb8888_to_rgb332(buf, 0, vaddr, fb, rect);
|
||||
} else if (format->format == DRM_FORMAT_RGB565) {
|
||||
drm_fb_xrgb8888_to_rgb565(buf, vaddr, fb, rect, gud_is_big_endian());
|
||||
drm_fb_xrgb8888_to_rgb565(buf, 0, vaddr, fb, rect, gud_is_big_endian());
|
||||
} else if (format->format == DRM_FORMAT_RGB888) {
|
||||
drm_fb_xrgb8888_to_rgb888(buf, vaddr, fb, rect);
|
||||
drm_fb_xrgb8888_to_rgb888(buf, 0, vaddr, fb, rect);
|
||||
} else {
|
||||
len = gud_xrgb8888_to_color(buf, format, vaddr, fb, rect);
|
||||
}
|
||||
} else if (gud_is_big_endian() && format->cpp[0] > 1) {
|
||||
drm_fb_swab(buf, vaddr, fb, rect, !import_attach);
|
||||
drm_fb_swab(buf, 0, vaddr, fb, rect, !import_attach);
|
||||
} else if (compression && !import_attach && pitch == fb->pitches[0]) {
|
||||
/* can compress directly from the framebuffer */
|
||||
buf = vaddr + rect->y1 * pitch;
|
||||
} else {
|
||||
drm_fb_memcpy(buf, vaddr, fb, rect);
|
||||
drm_fb_memcpy(buf, 0, vaddr, fb, rect);
|
||||
}
|
||||
|
||||
memset(req, 0, sizeof(*req));
|
||||
|
|
|
@ -81,7 +81,7 @@ struct dsi_hw_ctx {
|
|||
|
||||
struct dw_dsi {
|
||||
struct drm_encoder encoder;
|
||||
struct drm_bridge *bridge;
|
||||
struct device *dev;
|
||||
struct mipi_dsi_host host;
|
||||
struct drm_display_mode cur_mode;
|
||||
struct dsi_hw_ctx *ctx;
|
||||
|
@ -720,10 +720,13 @@ static int dw_drm_encoder_init(struct device *dev,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const struct component_ops dsi_ops;
|
||||
static int dsi_host_attach(struct mipi_dsi_host *host,
|
||||
struct mipi_dsi_device *mdsi)
|
||||
{
|
||||
struct dw_dsi *dsi = host_to_dsi(host);
|
||||
struct device *dev = host->dev;
|
||||
int ret;
|
||||
|
||||
if (mdsi->lanes < 1 || mdsi->lanes > 4) {
|
||||
DRM_ERROR("dsi device params invalid\n");
|
||||
|
@ -734,13 +737,20 @@ static int dsi_host_attach(struct mipi_dsi_host *host,
|
|||
dsi->format = mdsi->format;
|
||||
dsi->mode_flags = mdsi->mode_flags;
|
||||
|
||||
ret = component_add(dev, &dsi_ops);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_host_detach(struct mipi_dsi_host *host,
|
||||
struct mipi_dsi_device *mdsi)
|
||||
{
|
||||
/* do nothing */
|
||||
struct device *dev = host->dev;
|
||||
|
||||
component_del(dev, &dsi_ops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -768,7 +778,17 @@ static int dsi_host_init(struct device *dev, struct dw_dsi *dsi)
|
|||
static int dsi_bridge_init(struct drm_device *dev, struct dw_dsi *dsi)
|
||||
{
|
||||
struct drm_encoder *encoder = &dsi->encoder;
|
||||
struct drm_bridge *bridge = dsi->bridge;
|
||||
struct drm_bridge *bridge;
|
||||
struct device_node *np = dsi->dev->of_node;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Get the endpoint node. In our case, dsi has one output port1
|
||||
* to which the external HDMI bridge is connected.
|
||||
*/
|
||||
ret = drm_of_find_panel_or_bridge(np, 1, 0, NULL, &bridge);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* associate the bridge to dsi encoder */
|
||||
return drm_bridge_attach(encoder, bridge, NULL, 0);
|
||||
|
@ -785,10 +805,6 @@ static int dsi_bind(struct device *dev, struct device *master, void *data)
|
|||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = dsi_host_init(dev, dsi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = dsi_bridge_init(drm_dev, dsi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
@ -809,17 +825,7 @@ static const struct component_ops dsi_ops = {
|
|||
static int dsi_parse_dt(struct platform_device *pdev, struct dw_dsi *dsi)
|
||||
{
|
||||
struct dsi_hw_ctx *ctx = dsi->ctx;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
struct resource *res;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* Get the endpoint node. In our case, dsi has one output port1
|
||||
* to which the external HDMI bridge is connected.
|
||||
*/
|
||||
ret = drm_of_find_panel_or_bridge(np, 1, 0, NULL, &dsi->bridge);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ctx->pclk = devm_clk_get(&pdev->dev, "pclk");
|
||||
if (IS_ERR(ctx->pclk)) {
|
||||
|
@ -852,6 +858,7 @@ static int dsi_probe(struct platform_device *pdev)
|
|||
dsi = &data->dsi;
|
||||
ctx = &data->ctx;
|
||||
dsi->ctx = ctx;
|
||||
dsi->dev = &pdev->dev;
|
||||
|
||||
ret = dsi_parse_dt(pdev, dsi);
|
||||
if (ret)
|
||||
|
@ -859,12 +866,19 @@ static int dsi_probe(struct platform_device *pdev)
|
|||
|
||||
platform_set_drvdata(pdev, data);
|
||||
|
||||
return component_add(&pdev->dev, &dsi_ops);
|
||||
ret = dsi_host_init(&pdev->dev, dsi);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int dsi_remove(struct platform_device *pdev)
|
||||
{
|
||||
component_del(&pdev->dev, &dsi_ops);
|
||||
struct dsi_data *data = platform_get_drvdata(pdev);
|
||||
struct dw_dsi *dsi = &data->dsi;
|
||||
|
||||
mipi_dsi_host_unregister(&dsi->host);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -23,13 +23,16 @@ static int hyperv_blit_to_vram_rect(struct drm_framebuffer *fb,
|
|||
struct drm_rect *rect)
|
||||
{
|
||||
struct hyperv_drm_device *hv = to_hv(fb->dev);
|
||||
void __iomem *dst = hv->vram;
|
||||
void *vmap = map->vaddr; /* TODO: Use mapping abstraction properly */
|
||||
int idx;
|
||||
|
||||
if (!drm_dev_enter(&hv->dev, &idx))
|
||||
return -ENODEV;
|
||||
|
||||
drm_fb_memcpy_dstclip(hv->vram, fb->pitches[0], vmap, fb, rect);
|
||||
dst += drm_fb_clip_offset(fb->pitches[0], fb->format, rect);
|
||||
drm_fb_memcpy_toio(dst, fb->pitches[0], vmap, fb, rect);
|
||||
|
||||
drm_dev_exit(idx);
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -738,6 +738,7 @@ intel_prepare_plane_fb(struct drm_plane *_plane,
|
|||
i915_gem_object_wait_priority(obj, 0, &attr);
|
||||
|
||||
if (!new_plane_state->uapi.fence) { /* implicit fencing */
|
||||
struct dma_resv_iter cursor;
|
||||
struct dma_fence *fence;
|
||||
|
||||
ret = i915_sw_fence_await_reservation(&state->commit_ready,
|
||||
|
@ -748,12 +749,12 @@ intel_prepare_plane_fb(struct drm_plane *_plane,
|
|||
if (ret < 0)
|
||||
goto unpin_fb;
|
||||
|
||||
fence = dma_resv_get_excl_unlocked(obj->base.resv);
|
||||
if (fence) {
|
||||
dma_resv_iter_begin(&cursor, obj->base.resv, false);
|
||||
dma_resv_for_each_fence_unlocked(&cursor, fence) {
|
||||
add_rps_boost_after_vblank(new_plane_state->hw.crtc,
|
||||
fence);
|
||||
dma_fence_put(fence);
|
||||
}
|
||||
dma_resv_iter_end(&cursor);
|
||||
} else {
|
||||
add_rps_boost_after_vblank(new_plane_state->hw.crtc,
|
||||
new_plane_state->uapi.fence);
|
||||
|
|
|
@ -4960,7 +4960,7 @@ static void intel_dp_modeset_retry_work_fn(struct work_struct *work)
|
|||
DRM_MODE_LINK_STATUS_BAD);
|
||||
mutex_unlock(&connector->dev->mode_config.mutex);
|
||||
/* Send Hotplug uevent so userspace can reprobe */
|
||||
drm_kms_helper_hotplug_event(connector->dev);
|
||||
drm_kms_helper_connector_hotplug_event(connector);
|
||||
}
|
||||
|
||||
bool
|
||||
|
|
|
@ -282,6 +282,12 @@ intel_dp_aux_vesa_set_backlight(const struct drm_connector_state *conn_state, u3
|
|||
struct intel_panel *panel = &connector->panel;
|
||||
struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder);
|
||||
|
||||
if (!panel->backlight.edp.vesa.info.aux_set) {
|
||||
const u32 pwm_level = intel_backlight_level_to_pwm(connector, level);
|
||||
|
||||
intel_backlight_set_pwm_level(conn_state, pwm_level);
|
||||
}
|
||||
|
||||
drm_edp_backlight_set_level(&intel_dp->aux, &panel->backlight.edp.vesa.info, level);
|
||||
}
|
||||
|
||||
|
@ -293,6 +299,18 @@ intel_dp_aux_vesa_enable_backlight(const struct intel_crtc_state *crtc_state,
|
|||
struct intel_panel *panel = &connector->panel;
|
||||
struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder);
|
||||
|
||||
if (!panel->backlight.edp.vesa.info.aux_enable) {
|
||||
u32 pwm_level;
|
||||
|
||||
if (!panel->backlight.edp.vesa.info.aux_set)
|
||||
pwm_level = intel_backlight_level_to_pwm(connector, level);
|
||||
else
|
||||
pwm_level = intel_backlight_invert_pwm_level(connector,
|
||||
panel->backlight.pwm_level_max);
|
||||
|
||||
panel->backlight.pwm_funcs->enable(crtc_state, conn_state, pwm_level);
|
||||
}
|
||||
|
||||
drm_edp_backlight_enable(&intel_dp->aux, &panel->backlight.edp.vesa.info, level);
|
||||
}
|
||||
|
||||
|
@ -304,6 +322,10 @@ static void intel_dp_aux_vesa_disable_backlight(const struct drm_connector_state
|
|||
struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder);
|
||||
|
||||
drm_edp_backlight_disable(&intel_dp->aux, &panel->backlight.edp.vesa.info);
|
||||
|
||||
if (!panel->backlight.edp.vesa.info.aux_enable)
|
||||
panel->backlight.pwm_funcs->disable(old_conn_state,
|
||||
intel_backlight_invert_pwm_level(connector, 0));
|
||||
}
|
||||
|
||||
static int intel_dp_aux_vesa_setup_backlight(struct intel_connector *connector, enum pipe pipe)
|
||||
|
@ -321,14 +343,36 @@ static int intel_dp_aux_vesa_setup_backlight(struct intel_connector *connector,
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
panel->backlight.max = panel->backlight.edp.vesa.info.max;
|
||||
panel->backlight.min = 0;
|
||||
if (current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) {
|
||||
panel->backlight.level = current_level;
|
||||
panel->backlight.enabled = panel->backlight.level != 0;
|
||||
if (!panel->backlight.edp.vesa.info.aux_set || !panel->backlight.edp.vesa.info.aux_enable) {
|
||||
ret = panel->backlight.pwm_funcs->setup(connector, pipe);
|
||||
if (ret < 0) {
|
||||
drm_err(&i915->drm,
|
||||
"Failed to setup PWM backlight controls for eDP backlight: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (panel->backlight.edp.vesa.info.aux_set) {
|
||||
panel->backlight.max = panel->backlight.edp.vesa.info.max;
|
||||
panel->backlight.min = 0;
|
||||
if (current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) {
|
||||
panel->backlight.level = current_level;
|
||||
panel->backlight.enabled = panel->backlight.level != 0;
|
||||
} else {
|
||||
panel->backlight.level = panel->backlight.max;
|
||||
panel->backlight.enabled = false;
|
||||
}
|
||||
} else {
|
||||
panel->backlight.level = panel->backlight.max;
|
||||
panel->backlight.enabled = false;
|
||||
panel->backlight.max = panel->backlight.pwm_level_max;
|
||||
panel->backlight.min = panel->backlight.pwm_level_min;
|
||||
if (current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_PWM) {
|
||||
panel->backlight.level = panel->backlight.pwm_funcs->get(connector, pipe);
|
||||
panel->backlight.enabled = panel->backlight.pwm_enabled;
|
||||
} else {
|
||||
panel->backlight.level = panel->backlight.max;
|
||||
panel->backlight.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -340,12 +384,7 @@ intel_dp_aux_supports_vesa_backlight(struct intel_connector *connector)
|
|||
struct intel_dp *intel_dp = intel_attached_dp(connector);
|
||||
struct drm_i915_private *i915 = dp_to_i915(intel_dp);
|
||||
|
||||
/* TODO: We currently only support AUX only backlight configurations, not backlights which
|
||||
* require a mix of PWM and AUX controls to work. In the mean time, these machines typically
|
||||
* work just fine using normal PWM controls anyway.
|
||||
*/
|
||||
if ((intel_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP) &&
|
||||
drm_edp_backlight_supported(intel_dp->edp_dpcd)) {
|
||||
if (drm_edp_backlight_supported(intel_dp->edp_dpcd)) {
|
||||
drm_dbg_kms(&i915->drm, "AUX Backlight Control Supported!\n");
|
||||
return true;
|
||||
}
|
||||
|
@ -417,11 +456,17 @@ int intel_dp_aux_init_backlight_funcs(struct intel_connector *connector)
|
|||
}
|
||||
|
||||
/*
|
||||
* A lot of eDP panels in the wild will report supporting both the
|
||||
* Intel proprietary backlight control interface, and the VESA
|
||||
* backlight control interface. Many of these panels are liars though,
|
||||
* and will only work with the Intel interface. So, always probe for
|
||||
* that first.
|
||||
* Since Intel has their own backlight control interface, the majority of machines out there
|
||||
* using DPCD backlight controls with Intel GPUs will be using this interface as opposed to
|
||||
* the VESA interface. However, other GPUs (such as Nvidia's) will always use the VESA
|
||||
* interface. This means that there's quite a number of panels out there that will advertise
|
||||
* support for both interfaces, primarily systems with Intel/Nvidia hybrid GPU setups.
|
||||
*
|
||||
* There's a catch to this though: on many panels that advertise support for both
|
||||
* interfaces, the VESA backlight interface will stop working once we've programmed the
|
||||
* panel with Intel's OUI - which is also required for us to be able to detect Intel's
|
||||
* backlight interface at all. This means that the only sensible way for us to detect both
|
||||
* interfaces is to probe for Intel's first, and VESA's second.
|
||||
*/
|
||||
if (try_intel_interface && intel_dp_aux_supports_hdr_backlight(connector)) {
|
||||
drm_dbg_kms(dev, "Using Intel proprietary eDP backlight controls\n");
|
||||
|
|
|
@ -115,8 +115,8 @@ i915_gem_busy_ioctl(struct drm_device *dev, void *data,
|
|||
{
|
||||
struct drm_i915_gem_busy *args = data;
|
||||
struct drm_i915_gem_object *obj;
|
||||
struct dma_resv_list *list;
|
||||
unsigned int seq;
|
||||
struct dma_resv_iter cursor;
|
||||
struct dma_fence *fence;
|
||||
int err;
|
||||
|
||||
err = -ENOENT;
|
||||
|
@ -142,27 +142,20 @@ i915_gem_busy_ioctl(struct drm_device *dev, void *data,
|
|||
* to report the overall busyness. This is what the wait-ioctl does.
|
||||
*
|
||||
*/
|
||||
retry:
|
||||
seq = raw_read_seqcount(&obj->base.resv->seq);
|
||||
|
||||
/* Translate the exclusive fence to the READ *and* WRITE engine */
|
||||
args->busy = busy_check_writer(dma_resv_excl_fence(obj->base.resv));
|
||||
|
||||
/* Translate shared fences to READ set of engines */
|
||||
list = dma_resv_shared_list(obj->base.resv);
|
||||
if (list) {
|
||||
unsigned int shared_count = list->shared_count, i;
|
||||
|
||||
for (i = 0; i < shared_count; ++i) {
|
||||
struct dma_fence *fence =
|
||||
rcu_dereference(list->shared[i]);
|
||||
args->busy = 0;
|
||||
dma_resv_iter_begin(&cursor, obj->base.resv, true);
|
||||
dma_resv_for_each_fence_unlocked(&cursor, fence) {
|
||||
if (dma_resv_iter_is_restarted(&cursor))
|
||||
args->busy = 0;
|
||||
|
||||
if (dma_resv_iter_is_exclusive(&cursor))
|
||||
/* Translate the exclusive fence to the READ *and* WRITE engine */
|
||||
args->busy |= busy_check_writer(fence);
|
||||
else
|
||||
/* Translate shared fences to READ set of engines */
|
||||
args->busy |= busy_check_reader(fence);
|
||||
}
|
||||
}
|
||||
|
||||
if (args->busy && read_seqcount_retry(&obj->base.resv->seq, seq))
|
||||
goto retry;
|
||||
dma_resv_iter_end(&cursor);
|
||||
|
||||
err = 0;
|
||||
out:
|
||||
|
|
|
@ -25,7 +25,7 @@ i915_gem_object_wait_fence(struct dma_fence *fence,
|
|||
return timeout;
|
||||
|
||||
if (dma_fence_is_i915(fence))
|
||||
return i915_request_wait(to_request(fence), flags, timeout);
|
||||
return i915_request_wait_timeout(to_request(fence), flags, timeout);
|
||||
|
||||
return dma_fence_wait_timeout(fence,
|
||||
flags & I915_WAIT_INTERRUPTIBLE,
|
||||
|
@ -37,58 +37,29 @@ i915_gem_object_wait_reservation(struct dma_resv *resv,
|
|||
unsigned int flags,
|
||||
long timeout)
|
||||
{
|
||||
struct dma_fence *excl;
|
||||
bool prune_fences = false;
|
||||
struct dma_resv_iter cursor;
|
||||
struct dma_fence *fence;
|
||||
long ret = timeout ?: 1;
|
||||
|
||||
if (flags & I915_WAIT_ALL) {
|
||||
struct dma_fence **shared;
|
||||
unsigned int count, i;
|
||||
int ret;
|
||||
dma_resv_iter_begin(&cursor, resv, flags & I915_WAIT_ALL);
|
||||
dma_resv_for_each_fence_unlocked(&cursor, fence) {
|
||||
ret = i915_gem_object_wait_fence(fence, flags, timeout);
|
||||
if (ret <= 0)
|
||||
break;
|
||||
|
||||
ret = dma_resv_get_fences(resv, &excl, &count, &shared);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
timeout = i915_gem_object_wait_fence(shared[i],
|
||||
flags, timeout);
|
||||
if (timeout < 0)
|
||||
break;
|
||||
|
||||
dma_fence_put(shared[i]);
|
||||
}
|
||||
|
||||
for (; i < count; i++)
|
||||
dma_fence_put(shared[i]);
|
||||
kfree(shared);
|
||||
|
||||
/*
|
||||
* If both shared fences and an exclusive fence exist,
|
||||
* then by construction the shared fences must be later
|
||||
* than the exclusive fence. If we successfully wait for
|
||||
* all the shared fences, we know that the exclusive fence
|
||||
* must all be signaled. If all the shared fences are
|
||||
* signaled, we can prune the array and recover the
|
||||
* floating references on the fences/requests.
|
||||
*/
|
||||
prune_fences = count && timeout >= 0;
|
||||
} else {
|
||||
excl = dma_resv_get_excl_unlocked(resv);
|
||||
if (timeout)
|
||||
timeout = ret;
|
||||
}
|
||||
|
||||
if (excl && timeout >= 0)
|
||||
timeout = i915_gem_object_wait_fence(excl, flags, timeout);
|
||||
|
||||
dma_fence_put(excl);
|
||||
dma_resv_iter_end(&cursor);
|
||||
|
||||
/*
|
||||
* Opportunistically prune the fences iff we know they have *all* been
|
||||
* signaled.
|
||||
*/
|
||||
if (prune_fences)
|
||||
if (timeout > 0)
|
||||
dma_resv_prune(resv);
|
||||
|
||||
return timeout;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void fence_set_priority(struct dma_fence *fence,
|
||||
|
@ -151,32 +122,13 @@ i915_gem_object_wait_priority(struct drm_i915_gem_object *obj,
|
|||
unsigned int flags,
|
||||
const struct i915_sched_attr *attr)
|
||||
{
|
||||
struct dma_fence *excl;
|
||||
struct dma_resv_iter cursor;
|
||||
struct dma_fence *fence;
|
||||
|
||||
if (flags & I915_WAIT_ALL) {
|
||||
struct dma_fence **shared;
|
||||
unsigned int count, i;
|
||||
int ret;
|
||||
|
||||
ret = dma_resv_get_fences(obj->base.resv, &excl, &count,
|
||||
&shared);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
i915_gem_fence_wait_priority(shared[i], attr);
|
||||
dma_fence_put(shared[i]);
|
||||
}
|
||||
|
||||
kfree(shared);
|
||||
} else {
|
||||
excl = dma_resv_get_excl_unlocked(obj->base.resv);
|
||||
}
|
||||
|
||||
if (excl) {
|
||||
i915_gem_fence_wait_priority(excl, attr);
|
||||
dma_fence_put(excl);
|
||||
}
|
||||
dma_resv_iter_begin(&cursor, obj->base.resv, flags & I915_WAIT_ALL);
|
||||
dma_resv_for_each_fence_unlocked(&cursor, fence)
|
||||
i915_gem_fence_wait_priority(fence, attr);
|
||||
dma_resv_iter_end(&cursor);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -196,7 +148,11 @@ i915_gem_object_wait(struct drm_i915_gem_object *obj,
|
|||
|
||||
timeout = i915_gem_object_wait_reservation(obj->base.resv,
|
||||
flags, timeout);
|
||||
return timeout < 0 ? timeout : 0;
|
||||
|
||||
if (timeout < 0)
|
||||
return timeout;
|
||||
|
||||
return !timeout ? -ETIME : 0;
|
||||
}
|
||||
|
||||
static inline unsigned long nsecs_to_jiffies_timeout(const u64 n)
|
||||
|
|
|
@ -96,9 +96,9 @@ static signed long i915_fence_wait(struct dma_fence *fence,
|
|||
bool interruptible,
|
||||
signed long timeout)
|
||||
{
|
||||
return i915_request_wait(to_request(fence),
|
||||
interruptible | I915_WAIT_PRIORITY,
|
||||
timeout);
|
||||
return i915_request_wait_timeout(to_request(fence),
|
||||
interruptible | I915_WAIT_PRIORITY,
|
||||
timeout);
|
||||
}
|
||||
|
||||
struct kmem_cache *i915_request_slab_cache(void)
|
||||
|
@ -1857,23 +1857,27 @@ static void request_wait_wake(struct dma_fence *fence, struct dma_fence_cb *cb)
|
|||
}
|
||||
|
||||
/**
|
||||
* i915_request_wait - wait until execution of request has finished
|
||||
* i915_request_wait_timeout - wait until execution of request has finished
|
||||
* @rq: the request to wait upon
|
||||
* @flags: how to wait
|
||||
* @timeout: how long to wait in jiffies
|
||||
*
|
||||
* i915_request_wait() waits for the request to be completed, for a
|
||||
* i915_request_wait_timeout() waits for the request to be completed, for a
|
||||
* maximum of @timeout jiffies (with MAX_SCHEDULE_TIMEOUT implying an
|
||||
* unbounded wait).
|
||||
*
|
||||
* Returns the remaining time (in jiffies) if the request completed, which may
|
||||
* be zero or -ETIME if the request is unfinished after the timeout expires.
|
||||
* be zero if the request is unfinished after the timeout expires.
|
||||
* If the timeout is 0, it will return 1 if the fence is signaled.
|
||||
*
|
||||
* May return -EINTR is called with I915_WAIT_INTERRUPTIBLE and a signal is
|
||||
* pending before the request completes.
|
||||
*
|
||||
* NOTE: This function has the same wait semantics as dma-fence.
|
||||
*/
|
||||
long i915_request_wait(struct i915_request *rq,
|
||||
unsigned int flags,
|
||||
long timeout)
|
||||
long i915_request_wait_timeout(struct i915_request *rq,
|
||||
unsigned int flags,
|
||||
long timeout)
|
||||
{
|
||||
const int state = flags & I915_WAIT_INTERRUPTIBLE ?
|
||||
TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE;
|
||||
|
@ -1883,7 +1887,7 @@ long i915_request_wait(struct i915_request *rq,
|
|||
GEM_BUG_ON(timeout < 0);
|
||||
|
||||
if (dma_fence_is_signaled(&rq->fence))
|
||||
return timeout;
|
||||
return timeout ?: 1;
|
||||
|
||||
if (!timeout)
|
||||
return -ETIME;
|
||||
|
@ -1992,6 +1996,39 @@ out:
|
|||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* i915_request_wait - wait until execution of request has finished
|
||||
* @rq: the request to wait upon
|
||||
* @flags: how to wait
|
||||
* @timeout: how long to wait in jiffies
|
||||
*
|
||||
* i915_request_wait() waits for the request to be completed, for a
|
||||
* maximum of @timeout jiffies (with MAX_SCHEDULE_TIMEOUT implying an
|
||||
* unbounded wait).
|
||||
*
|
||||
* Returns the remaining time (in jiffies) if the request completed, which may
|
||||
* be zero or -ETIME if the request is unfinished after the timeout expires.
|
||||
* May return -EINTR is called with I915_WAIT_INTERRUPTIBLE and a signal is
|
||||
* pending before the request completes.
|
||||
*
|
||||
* NOTE: This function behaves differently from dma-fence wait semantics for
|
||||
* timeout = 0. It returns 0 on success, and -ETIME if not signaled.
|
||||
*/
|
||||
long i915_request_wait(struct i915_request *rq,
|
||||
unsigned int flags,
|
||||
long timeout)
|
||||
{
|
||||
long ret = i915_request_wait_timeout(rq, flags, timeout);
|
||||
|
||||
if (!ret)
|
||||
return -ETIME;
|
||||
|
||||
if (ret > 0 && !timeout)
|
||||
return 0;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int print_sched_attr(const struct i915_sched_attr *attr,
|
||||
char *buf, int x, int len)
|
||||
{
|
||||
|
|
|
@ -414,6 +414,11 @@ void i915_request_unsubmit(struct i915_request *request);
|
|||
|
||||
void i915_request_cancel(struct i915_request *rq, int error);
|
||||
|
||||
long i915_request_wait_timeout(struct i915_request *rq,
|
||||
unsigned int flags,
|
||||
long timeout)
|
||||
__attribute__((nonnull(1)));
|
||||
|
||||
long i915_request_wait(struct i915_request *rq,
|
||||
unsigned int flags,
|
||||
long timeout)
|
||||
|
|
|
@ -572,56 +572,25 @@ int i915_sw_fence_await_reservation(struct i915_sw_fence *fence,
|
|||
unsigned long timeout,
|
||||
gfp_t gfp)
|
||||
{
|
||||
struct dma_fence *excl;
|
||||
struct dma_resv_iter cursor;
|
||||
struct dma_fence *f;
|
||||
int ret = 0, pending;
|
||||
|
||||
debug_fence_assert(fence);
|
||||
might_sleep_if(gfpflags_allow_blocking(gfp));
|
||||
|
||||
if (write) {
|
||||
struct dma_fence **shared;
|
||||
unsigned int count, i;
|
||||
|
||||
ret = dma_resv_get_fences(resv, &excl, &count, &shared);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
for (i = 0; i < count; i++) {
|
||||
if (shared[i]->ops == exclude)
|
||||
continue;
|
||||
|
||||
pending = i915_sw_fence_await_dma_fence(fence,
|
||||
shared[i],
|
||||
timeout,
|
||||
gfp);
|
||||
if (pending < 0) {
|
||||
ret = pending;
|
||||
break;
|
||||
}
|
||||
|
||||
ret |= pending;
|
||||
dma_resv_iter_begin(&cursor, resv, write);
|
||||
dma_resv_for_each_fence_unlocked(&cursor, f) {
|
||||
pending = i915_sw_fence_await_dma_fence(fence, f, timeout,
|
||||
gfp);
|
||||
if (pending < 0) {
|
||||
ret = pending;
|
||||
break;
|
||||
}
|
||||
|
||||
for (i = 0; i < count; i++)
|
||||
dma_fence_put(shared[i]);
|
||||
kfree(shared);
|
||||
} else {
|
||||
excl = dma_resv_get_excl_unlocked(resv);
|
||||
ret |= pending;
|
||||
}
|
||||
|
||||
if (ret >= 0 && excl && excl->ops != exclude) {
|
||||
pending = i915_sw_fence_await_dma_fence(fence,
|
||||
excl,
|
||||
timeout,
|
||||
gfp);
|
||||
if (pending < 0)
|
||||
ret = pending;
|
||||
else
|
||||
ret |= pending;
|
||||
}
|
||||
|
||||
dma_fence_put(excl);
|
||||
|
||||
dma_resv_iter_end(&cursor);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_bridge.h>
|
||||
#include <drm/drm_bridge_connector.h>
|
||||
#include <drm/drm_color_mgmt.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
|
@ -41,6 +42,8 @@
|
|||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/drm_vblank.h>
|
||||
|
||||
#define HWDESC_PALETTE 2
|
||||
|
||||
struct ingenic_dma_hwdesc {
|
||||
u32 next;
|
||||
u32 addr;
|
||||
|
@ -49,9 +52,7 @@ struct ingenic_dma_hwdesc {
|
|||
} __aligned(16);
|
||||
|
||||
struct ingenic_dma_hwdescs {
|
||||
struct ingenic_dma_hwdesc hwdesc_f0;
|
||||
struct ingenic_dma_hwdesc hwdesc_f1;
|
||||
struct ingenic_dma_hwdesc hwdesc_pal;
|
||||
struct ingenic_dma_hwdesc hwdesc[3];
|
||||
u16 palette[256] __aligned(16);
|
||||
};
|
||||
|
||||
|
@ -64,6 +65,11 @@ struct jz_soc_info {
|
|||
unsigned int num_formats_f0, num_formats_f1;
|
||||
};
|
||||
|
||||
struct ingenic_drm_private_state {
|
||||
struct drm_private_state base;
|
||||
bool use_palette;
|
||||
};
|
||||
|
||||
struct ingenic_drm {
|
||||
struct drm_device drm;
|
||||
/*
|
||||
|
@ -99,8 +105,53 @@ struct ingenic_drm {
|
|||
struct mutex clk_mutex;
|
||||
bool update_clk_rate;
|
||||
struct notifier_block clock_nb;
|
||||
|
||||
struct drm_private_obj private_obj;
|
||||
};
|
||||
|
||||
struct ingenic_drm_bridge {
|
||||
struct drm_encoder encoder;
|
||||
struct drm_bridge bridge, *next_bridge;
|
||||
|
||||
struct drm_bus_cfg bus_cfg;
|
||||
};
|
||||
|
||||
static inline struct ingenic_drm_bridge *
|
||||
to_ingenic_drm_bridge(struct drm_encoder *encoder)
|
||||
{
|
||||
return container_of(encoder, struct ingenic_drm_bridge, encoder);
|
||||
}
|
||||
|
||||
static inline struct ingenic_drm_private_state *
|
||||
to_ingenic_drm_priv_state(struct drm_private_state *state)
|
||||
{
|
||||
return container_of(state, struct ingenic_drm_private_state, base);
|
||||
}
|
||||
|
||||
static struct ingenic_drm_private_state *
|
||||
ingenic_drm_get_priv_state(struct ingenic_drm *priv, struct drm_atomic_state *state)
|
||||
{
|
||||
struct drm_private_state *priv_state;
|
||||
|
||||
priv_state = drm_atomic_get_private_obj_state(state, &priv->private_obj);
|
||||
if (IS_ERR(priv_state))
|
||||
return ERR_CAST(priv_state);
|
||||
|
||||
return to_ingenic_drm_priv_state(priv_state);
|
||||
}
|
||||
|
||||
static struct ingenic_drm_private_state *
|
||||
ingenic_drm_get_new_priv_state(struct ingenic_drm *priv, struct drm_atomic_state *state)
|
||||
{
|
||||
struct drm_private_state *priv_state;
|
||||
|
||||
priv_state = drm_atomic_get_new_private_obj_state(state, &priv->private_obj);
|
||||
if (!priv_state)
|
||||
return NULL;
|
||||
|
||||
return to_ingenic_drm_priv_state(priv_state);
|
||||
}
|
||||
|
||||
static bool ingenic_drm_writeable_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
switch (reg) {
|
||||
|
@ -141,6 +192,14 @@ static inline struct ingenic_drm *drm_nb_get_priv(struct notifier_block *nb)
|
|||
return container_of(nb, struct ingenic_drm, clock_nb);
|
||||
}
|
||||
|
||||
static inline dma_addr_t dma_hwdesc_addr(const struct ingenic_drm *priv,
|
||||
unsigned int idx)
|
||||
{
|
||||
u32 offset = offsetof(struct ingenic_dma_hwdescs, hwdesc[idx]);
|
||||
|
||||
return priv->dma_hwdescs_phys + offset;
|
||||
}
|
||||
|
||||
static int ingenic_drm_update_pixclk(struct notifier_block *nb,
|
||||
unsigned long action,
|
||||
void *data)
|
||||
|
@ -163,9 +222,20 @@ static void ingenic_drm_crtc_atomic_enable(struct drm_crtc *crtc,
|
|||
struct drm_atomic_state *state)
|
||||
{
|
||||
struct ingenic_drm *priv = drm_crtc_get_priv(crtc);
|
||||
struct ingenic_drm_private_state *priv_state;
|
||||
unsigned int next_id;
|
||||
|
||||
priv_state = ingenic_drm_get_priv_state(priv, state);
|
||||
if (WARN_ON(IS_ERR(priv_state)))
|
||||
return;
|
||||
|
||||
regmap_write(priv->map, JZ_REG_LCD_STATE, 0);
|
||||
|
||||
/* Set addresses of our DMA descriptor chains */
|
||||
next_id = priv_state->use_palette ? HWDESC_PALETTE : 0;
|
||||
regmap_write(priv->map, JZ_REG_LCD_DA0, dma_hwdesc_addr(priv, next_id));
|
||||
regmap_write(priv->map, JZ_REG_LCD_DA1, dma_hwdesc_addr(priv, 1));
|
||||
|
||||
regmap_update_bits(priv->map, JZ_REG_LCD_CTRL,
|
||||
JZ_LCD_CTRL_ENABLE | JZ_LCD_CTRL_DISABLE,
|
||||
JZ_LCD_CTRL_ENABLE);
|
||||
|
@ -369,6 +439,7 @@ static int ingenic_drm_plane_atomic_check(struct drm_plane *plane,
|
|||
struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
|
||||
plane);
|
||||
struct ingenic_drm *priv = drm_device_get_priv(plane->dev);
|
||||
struct ingenic_drm_private_state *priv_state;
|
||||
struct drm_crtc_state *crtc_state;
|
||||
struct drm_crtc *crtc = new_plane_state->crtc ?: old_plane_state->crtc;
|
||||
int ret;
|
||||
|
@ -381,6 +452,10 @@ static int ingenic_drm_plane_atomic_check(struct drm_plane *plane,
|
|||
if (WARN_ON(!crtc_state))
|
||||
return -EINVAL;
|
||||
|
||||
priv_state = ingenic_drm_get_priv_state(priv, state);
|
||||
if (IS_ERR(priv_state))
|
||||
return PTR_ERR(priv_state);
|
||||
|
||||
ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
|
||||
DRM_PLANE_HELPER_NO_SCALING,
|
||||
DRM_PLANE_HELPER_NO_SCALING,
|
||||
|
@ -399,6 +474,9 @@ static int ingenic_drm_plane_atomic_check(struct drm_plane *plane,
|
|||
(new_plane_state->src_h >> 16) != new_plane_state->crtc_h))
|
||||
return -EINVAL;
|
||||
|
||||
priv_state->use_palette = new_plane_state->fb &&
|
||||
new_plane_state->fb->format->format == DRM_FORMAT_C8;
|
||||
|
||||
/*
|
||||
* Require full modeset if enabling or disabling a plane, or changing
|
||||
* its position, size or depth.
|
||||
|
@ -558,9 +636,10 @@ static void ingenic_drm_plane_atomic_update(struct drm_plane *plane,
|
|||
struct ingenic_drm *priv = drm_device_get_priv(plane->dev);
|
||||
struct drm_plane_state *newstate = drm_atomic_get_new_plane_state(state, plane);
|
||||
struct drm_plane_state *oldstate = drm_atomic_get_old_plane_state(state, plane);
|
||||
unsigned int width, height, cpp, next_id, plane_id;
|
||||
struct ingenic_drm_private_state *priv_state;
|
||||
struct drm_crtc_state *crtc_state;
|
||||
struct ingenic_dma_hwdesc *hwdesc;
|
||||
unsigned int width, height, cpp, offset;
|
||||
dma_addr_t addr;
|
||||
u32 fourcc;
|
||||
|
||||
|
@ -569,32 +648,26 @@ static void ingenic_drm_plane_atomic_update(struct drm_plane *plane,
|
|||
drm_fb_cma_sync_non_coherent(&priv->drm, oldstate, newstate);
|
||||
|
||||
crtc_state = newstate->crtc->state;
|
||||
plane_id = !!(priv->soc_info->has_osd && plane != &priv->f0);
|
||||
|
||||
addr = drm_fb_cma_get_gem_addr(newstate->fb, newstate, 0);
|
||||
width = newstate->src_w >> 16;
|
||||
height = newstate->src_h >> 16;
|
||||
cpp = newstate->fb->format->cpp[0];
|
||||
|
||||
if (!priv->soc_info->has_osd || plane == &priv->f0)
|
||||
hwdesc = &priv->dma_hwdescs->hwdesc_f0;
|
||||
else
|
||||
hwdesc = &priv->dma_hwdescs->hwdesc_f1;
|
||||
priv_state = ingenic_drm_get_new_priv_state(priv, state);
|
||||
next_id = (priv_state && priv_state->use_palette) ? HWDESC_PALETTE : plane_id;
|
||||
|
||||
hwdesc = &priv->dma_hwdescs->hwdesc[plane_id];
|
||||
hwdesc->addr = addr;
|
||||
hwdesc->cmd = JZ_LCD_CMD_EOF_IRQ | (width * height * cpp / 4);
|
||||
hwdesc->next = dma_hwdesc_addr(priv, next_id);
|
||||
|
||||
if (drm_atomic_crtc_needs_modeset(crtc_state)) {
|
||||
fourcc = newstate->fb->format->format;
|
||||
|
||||
ingenic_drm_plane_config(priv->dev, plane, fourcc);
|
||||
|
||||
if (fourcc == DRM_FORMAT_C8)
|
||||
offset = offsetof(struct ingenic_dma_hwdescs, hwdesc_pal);
|
||||
else
|
||||
offset = offsetof(struct ingenic_dma_hwdescs, hwdesc_f0);
|
||||
|
||||
priv->dma_hwdescs->hwdesc_f0.next = priv->dma_hwdescs_phys + offset;
|
||||
|
||||
crtc_state->color_mgmt_changed = fourcc == DRM_FORMAT_C8;
|
||||
}
|
||||
|
||||
|
@ -609,11 +682,10 @@ static void ingenic_drm_encoder_atomic_mode_set(struct drm_encoder *encoder,
|
|||
{
|
||||
struct ingenic_drm *priv = drm_device_get_priv(encoder->dev);
|
||||
struct drm_display_mode *mode = &crtc_state->adjusted_mode;
|
||||
struct drm_connector *conn = conn_state->connector;
|
||||
struct drm_display_info *info = &conn->display_info;
|
||||
struct ingenic_drm_bridge *bridge = to_ingenic_drm_bridge(encoder);
|
||||
unsigned int cfg, rgbcfg = 0;
|
||||
|
||||
priv->panel_is_sharp = info->bus_flags & DRM_BUS_FLAG_SHARP_SIGNALS;
|
||||
priv->panel_is_sharp = bridge->bus_cfg.flags & DRM_BUS_FLAG_SHARP_SIGNALS;
|
||||
|
||||
if (priv->panel_is_sharp) {
|
||||
cfg = JZ_LCD_CFG_MODE_SPECIAL_TFT_1 | JZ_LCD_CFG_REV_POLARITY;
|
||||
|
@ -626,19 +698,19 @@ static void ingenic_drm_encoder_atomic_mode_set(struct drm_encoder *encoder,
|
|||
cfg |= JZ_LCD_CFG_HSYNC_ACTIVE_LOW;
|
||||
if (mode->flags & DRM_MODE_FLAG_NVSYNC)
|
||||
cfg |= JZ_LCD_CFG_VSYNC_ACTIVE_LOW;
|
||||
if (info->bus_flags & DRM_BUS_FLAG_DE_LOW)
|
||||
if (bridge->bus_cfg.flags & DRM_BUS_FLAG_DE_LOW)
|
||||
cfg |= JZ_LCD_CFG_DE_ACTIVE_LOW;
|
||||
if (info->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
|
||||
if (bridge->bus_cfg.flags & DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE)
|
||||
cfg |= JZ_LCD_CFG_PCLK_FALLING_EDGE;
|
||||
|
||||
if (!priv->panel_is_sharp) {
|
||||
if (conn->connector_type == DRM_MODE_CONNECTOR_TV) {
|
||||
if (conn_state->connector->connector_type == DRM_MODE_CONNECTOR_TV) {
|
||||
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
|
||||
cfg |= JZ_LCD_CFG_MODE_TV_OUT_I;
|
||||
else
|
||||
cfg |= JZ_LCD_CFG_MODE_TV_OUT_P;
|
||||
} else {
|
||||
switch (*info->bus_formats) {
|
||||
switch (bridge->bus_cfg.format) {
|
||||
case MEDIA_BUS_FMT_RGB565_1X16:
|
||||
cfg |= JZ_LCD_CFG_MODE_GENERIC_16BIT;
|
||||
break;
|
||||
|
@ -664,20 +736,29 @@ static void ingenic_drm_encoder_atomic_mode_set(struct drm_encoder *encoder,
|
|||
regmap_write(priv->map, JZ_REG_LCD_RGBC, rgbcfg);
|
||||
}
|
||||
|
||||
static int ingenic_drm_encoder_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
static int ingenic_drm_bridge_attach(struct drm_bridge *bridge,
|
||||
enum drm_bridge_attach_flags flags)
|
||||
{
|
||||
struct drm_display_info *info = &conn_state->connector->display_info;
|
||||
struct drm_display_mode *mode = &crtc_state->adjusted_mode;
|
||||
struct ingenic_drm_bridge *ib = to_ingenic_drm_bridge(bridge->encoder);
|
||||
|
||||
if (info->num_bus_formats != 1)
|
||||
return -EINVAL;
|
||||
return drm_bridge_attach(bridge->encoder, ib->next_bridge,
|
||||
&ib->bridge, flags);
|
||||
}
|
||||
|
||||
static int ingenic_drm_bridge_atomic_check(struct drm_bridge *bridge,
|
||||
struct drm_bridge_state *bridge_state,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct drm_display_mode *mode = &crtc_state->adjusted_mode;
|
||||
struct ingenic_drm_bridge *ib = to_ingenic_drm_bridge(bridge->encoder);
|
||||
|
||||
ib->bus_cfg = bridge_state->output_bus_cfg;
|
||||
|
||||
if (conn_state->connector->connector_type == DRM_MODE_CONNECTOR_TV)
|
||||
return 0;
|
||||
|
||||
switch (*info->bus_formats) {
|
||||
switch (bridge_state->output_bus_cfg.format) {
|
||||
case MEDIA_BUS_FMT_RGB888_3X8:
|
||||
case MEDIA_BUS_FMT_RGB888_3X8_DELTA:
|
||||
/*
|
||||
|
@ -764,6 +845,28 @@ ingenic_drm_gem_create_object(struct drm_device *drm, size_t size)
|
|||
return &obj->base;
|
||||
}
|
||||
|
||||
static struct drm_private_state *
|
||||
ingenic_drm_duplicate_state(struct drm_private_obj *obj)
|
||||
{
|
||||
struct ingenic_drm_private_state *state = to_ingenic_drm_priv_state(obj->state);
|
||||
|
||||
state = kmemdup(state, sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return NULL;
|
||||
|
||||
__drm_atomic_helper_private_obj_duplicate_state(obj, &state->base);
|
||||
|
||||
return &state->base;
|
||||
}
|
||||
|
||||
static void ingenic_drm_destroy_state(struct drm_private_obj *obj,
|
||||
struct drm_private_state *state)
|
||||
{
|
||||
struct ingenic_drm_private_state *priv_state = to_ingenic_drm_priv_state(state);
|
||||
|
||||
kfree(priv_state);
|
||||
}
|
||||
|
||||
DEFINE_DRM_GEM_CMA_FOPS(ingenic_drm_fops);
|
||||
|
||||
static const struct drm_driver ingenic_drm_driver_data = {
|
||||
|
@ -819,8 +922,16 @@ static const struct drm_crtc_helper_funcs ingenic_drm_crtc_helper_funcs = {
|
|||
};
|
||||
|
||||
static const struct drm_encoder_helper_funcs ingenic_drm_encoder_helper_funcs = {
|
||||
.atomic_mode_set = ingenic_drm_encoder_atomic_mode_set,
|
||||
.atomic_check = ingenic_drm_encoder_atomic_check,
|
||||
.atomic_mode_set = ingenic_drm_encoder_atomic_mode_set,
|
||||
};
|
||||
|
||||
static const struct drm_bridge_funcs ingenic_drm_bridge_funcs = {
|
||||
.attach = ingenic_drm_bridge_attach,
|
||||
.atomic_check = ingenic_drm_bridge_atomic_check,
|
||||
.atomic_reset = drm_atomic_helper_bridge_reset,
|
||||
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
|
||||
.atomic_get_input_bus_fmts = drm_atomic_helper_bridge_propagate_bus_fmt,
|
||||
};
|
||||
|
||||
static const struct drm_mode_config_funcs ingenic_drm_mode_config_funcs = {
|
||||
|
@ -834,6 +945,11 @@ static struct drm_mode_config_helper_funcs ingenic_drm_mode_config_helpers = {
|
|||
.atomic_commit_tail = drm_atomic_helper_commit_tail,
|
||||
};
|
||||
|
||||
static const struct drm_private_state_funcs ingenic_drm_private_state_funcs = {
|
||||
.atomic_duplicate_state = ingenic_drm_duplicate_state,
|
||||
.atomic_destroy_state = ingenic_drm_destroy_state,
|
||||
};
|
||||
|
||||
static void ingenic_drm_unbind_all(void *d)
|
||||
{
|
||||
struct ingenic_drm *priv = d;
|
||||
|
@ -846,21 +962,57 @@ static void __maybe_unused ingenic_drm_release_rmem(void *d)
|
|||
of_reserved_mem_device_release(d);
|
||||
}
|
||||
|
||||
static void ingenic_drm_configure_hwdesc(struct ingenic_drm *priv,
|
||||
unsigned int hwdesc,
|
||||
unsigned int next_hwdesc, u32 id)
|
||||
{
|
||||
struct ingenic_dma_hwdesc *desc = &priv->dma_hwdescs->hwdesc[hwdesc];
|
||||
|
||||
desc->next = dma_hwdesc_addr(priv, next_hwdesc);
|
||||
desc->id = id;
|
||||
}
|
||||
|
||||
static void ingenic_drm_configure_hwdesc_palette(struct ingenic_drm *priv)
|
||||
{
|
||||
struct ingenic_dma_hwdesc *desc;
|
||||
|
||||
ingenic_drm_configure_hwdesc(priv, HWDESC_PALETTE, 0, 0xc0);
|
||||
|
||||
desc = &priv->dma_hwdescs->hwdesc[HWDESC_PALETTE];
|
||||
desc->addr = priv->dma_hwdescs_phys
|
||||
+ offsetof(struct ingenic_dma_hwdescs, palette);
|
||||
desc->cmd = JZ_LCD_CMD_ENABLE_PAL
|
||||
| (sizeof(priv->dma_hwdescs->palette) / 4);
|
||||
}
|
||||
|
||||
static void ingenic_drm_configure_hwdesc_plane(struct ingenic_drm *priv,
|
||||
unsigned int plane)
|
||||
{
|
||||
ingenic_drm_configure_hwdesc(priv, plane, plane, 0xf0 | plane);
|
||||
}
|
||||
|
||||
static void ingenic_drm_atomic_private_obj_fini(struct drm_device *drm, void *private_obj)
|
||||
{
|
||||
drm_atomic_private_obj_fini(private_obj);
|
||||
}
|
||||
|
||||
static int ingenic_drm_bind(struct device *dev, bool has_components)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct ingenic_drm_private_state *private_state;
|
||||
const struct jz_soc_info *soc_info;
|
||||
struct ingenic_drm *priv;
|
||||
struct clk *parent_clk;
|
||||
struct drm_plane *primary;
|
||||
struct drm_bridge *bridge;
|
||||
struct drm_panel *panel;
|
||||
struct drm_connector *connector;
|
||||
struct drm_encoder *encoder;
|
||||
struct ingenic_drm_bridge *ib;
|
||||
struct drm_device *drm;
|
||||
void __iomem *base;
|
||||
long parent_rate;
|
||||
unsigned int i, clone_mask = 0;
|
||||
dma_addr_t dma_hwdesc_phys_f0, dma_hwdesc_phys_f1;
|
||||
int ret, irq;
|
||||
|
||||
soc_info = of_device_get_match_data(dev);
|
||||
|
@ -942,27 +1094,14 @@ static int ingenic_drm_bind(struct device *dev, bool has_components)
|
|||
if (!priv->dma_hwdescs)
|
||||
return -ENOMEM;
|
||||
|
||||
|
||||
/* Configure DMA hwdesc for foreground0 plane */
|
||||
dma_hwdesc_phys_f0 = priv->dma_hwdescs_phys
|
||||
+ offsetof(struct ingenic_dma_hwdescs, hwdesc_f0);
|
||||
priv->dma_hwdescs->hwdesc_f0.next = dma_hwdesc_phys_f0;
|
||||
priv->dma_hwdescs->hwdesc_f0.id = 0xf0;
|
||||
ingenic_drm_configure_hwdesc_plane(priv, 0);
|
||||
|
||||
/* Configure DMA hwdesc for foreground1 plane */
|
||||
dma_hwdesc_phys_f1 = priv->dma_hwdescs_phys
|
||||
+ offsetof(struct ingenic_dma_hwdescs, hwdesc_f1);
|
||||
priv->dma_hwdescs->hwdesc_f1.next = dma_hwdesc_phys_f1;
|
||||
priv->dma_hwdescs->hwdesc_f1.id = 0xf1;
|
||||
ingenic_drm_configure_hwdesc_plane(priv, 1);
|
||||
|
||||
/* Configure DMA hwdesc for palette */
|
||||
priv->dma_hwdescs->hwdesc_pal.next = priv->dma_hwdescs_phys
|
||||
+ offsetof(struct ingenic_dma_hwdescs, hwdesc_f0);
|
||||
priv->dma_hwdescs->hwdesc_pal.id = 0xc0;
|
||||
priv->dma_hwdescs->hwdesc_pal.addr = priv->dma_hwdescs_phys
|
||||
+ offsetof(struct ingenic_dma_hwdescs, palette);
|
||||
priv->dma_hwdescs->hwdesc_pal.cmd = JZ_LCD_CMD_ENABLE_PAL
|
||||
| (sizeof(priv->dma_hwdescs->palette) / 4);
|
||||
ingenic_drm_configure_hwdesc_palette(priv);
|
||||
|
||||
primary = priv->soc_info->has_osd ? &priv->f1 : &priv->f0;
|
||||
|
||||
|
@ -1046,20 +1185,36 @@ static int ingenic_drm_bind(struct device *dev, bool has_components)
|
|||
bridge = devm_drm_panel_bridge_add_typed(dev, panel,
|
||||
DRM_MODE_CONNECTOR_DPI);
|
||||
|
||||
encoder = drmm_plain_encoder_alloc(drm, NULL, DRM_MODE_ENCODER_DPI, NULL);
|
||||
if (IS_ERR(encoder)) {
|
||||
ret = PTR_ERR(encoder);
|
||||
ib = drmm_encoder_alloc(drm, struct ingenic_drm_bridge, encoder,
|
||||
NULL, DRM_MODE_ENCODER_DPI, NULL);
|
||||
if (IS_ERR(ib)) {
|
||||
ret = PTR_ERR(ib);
|
||||
dev_err(dev, "Failed to init encoder: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
encoder->possible_crtcs = 1;
|
||||
encoder = &ib->encoder;
|
||||
encoder->possible_crtcs = drm_crtc_mask(&priv->crtc);
|
||||
|
||||
drm_encoder_helper_add(encoder, &ingenic_drm_encoder_helper_funcs);
|
||||
|
||||
ret = drm_bridge_attach(encoder, bridge, NULL, 0);
|
||||
if (ret)
|
||||
ib->bridge.funcs = &ingenic_drm_bridge_funcs;
|
||||
ib->next_bridge = bridge;
|
||||
|
||||
ret = drm_bridge_attach(encoder, &ib->bridge, NULL,
|
||||
DRM_BRIDGE_ATTACH_NO_CONNECTOR);
|
||||
if (ret) {
|
||||
dev_err(dev, "Unable to attach bridge\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
connector = drm_bridge_connector_init(drm, encoder);
|
||||
if (IS_ERR(connector)) {
|
||||
dev_err(dev, "Unable to init connector\n");
|
||||
return PTR_ERR(connector);
|
||||
}
|
||||
|
||||
drm_connector_attach_encoder(connector, encoder);
|
||||
}
|
||||
|
||||
drm_for_each_encoder(encoder, drm) {
|
||||
|
@ -1112,10 +1267,6 @@ static int ingenic_drm_bind(struct device *dev, bool has_components)
|
|||
}
|
||||
}
|
||||
|
||||
/* Set address of our DMA descriptor chain */
|
||||
regmap_write(priv->map, JZ_REG_LCD_DA0, dma_hwdesc_phys_f0);
|
||||
regmap_write(priv->map, JZ_REG_LCD_DA1, dma_hwdesc_phys_f1);
|
||||
|
||||
/* Enable OSD if available */
|
||||
if (soc_info->has_osd)
|
||||
regmap_write(priv->map, JZ_REG_LCD_OSDC, JZ_LCD_OSDC_OSDEN);
|
||||
|
@ -1130,6 +1281,20 @@ static int ingenic_drm_bind(struct device *dev, bool has_components)
|
|||
goto err_devclk_disable;
|
||||
}
|
||||
|
||||
private_state = kzalloc(sizeof(*private_state), GFP_KERNEL);
|
||||
if (!private_state) {
|
||||
ret = -ENOMEM;
|
||||
goto err_clk_notifier_unregister;
|
||||
}
|
||||
|
||||
drm_atomic_private_obj_init(drm, &priv->private_obj, &private_state->base,
|
||||
&ingenic_drm_private_state_funcs);
|
||||
|
||||
ret = drmm_add_action_or_reset(drm, ingenic_drm_atomic_private_obj_fini,
|
||||
&priv->private_obj);
|
||||
if (ret)
|
||||
goto err_private_state_free;
|
||||
|
||||
ret = drm_dev_register(drm, 0);
|
||||
if (ret) {
|
||||
dev_err(dev, "Failed to register DRM driver\n");
|
||||
|
@ -1140,6 +1305,8 @@ static int ingenic_drm_bind(struct device *dev, bool has_components)
|
|||
|
||||
return 0;
|
||||
|
||||
err_private_state_free:
|
||||
kfree(private_state);
|
||||
err_clk_notifier_unregister:
|
||||
clk_notifier_unregister(parent_clk, &priv->clock_nb);
|
||||
err_devclk_disable:
|
||||
|
|
|
@ -45,6 +45,12 @@ struct soc_info {
|
|||
unsigned int weight, unsigned int offset);
|
||||
};
|
||||
|
||||
struct ingenic_ipu_private_state {
|
||||
struct drm_private_state base;
|
||||
|
||||
unsigned int num_w, num_h, denom_w, denom_h;
|
||||
};
|
||||
|
||||
struct ingenic_ipu {
|
||||
struct drm_plane plane;
|
||||
struct drm_device *drm;
|
||||
|
@ -54,12 +60,12 @@ struct ingenic_ipu {
|
|||
const struct soc_info *soc_info;
|
||||
bool clk_enabled;
|
||||
|
||||
unsigned int num_w, num_h, denom_w, denom_h;
|
||||
|
||||
dma_addr_t addr_y, addr_u, addr_v;
|
||||
|
||||
struct drm_property *sharpness_prop;
|
||||
unsigned int sharpness;
|
||||
|
||||
struct drm_private_obj private_obj;
|
||||
};
|
||||
|
||||
/* Signed 15.16 fixed-point math (for bicubic scaling coefficients) */
|
||||
|
@ -73,6 +79,36 @@ static inline struct ingenic_ipu *plane_to_ingenic_ipu(struct drm_plane *plane)
|
|||
return container_of(plane, struct ingenic_ipu, plane);
|
||||
}
|
||||
|
||||
static inline struct ingenic_ipu_private_state *
|
||||
to_ingenic_ipu_priv_state(struct drm_private_state *state)
|
||||
{
|
||||
return container_of(state, struct ingenic_ipu_private_state, base);
|
||||
}
|
||||
|
||||
static struct ingenic_ipu_private_state *
|
||||
ingenic_ipu_get_priv_state(struct ingenic_ipu *priv, struct drm_atomic_state *state)
|
||||
{
|
||||
struct drm_private_state *priv_state;
|
||||
|
||||
priv_state = drm_atomic_get_private_obj_state(state, &priv->private_obj);
|
||||
if (IS_ERR(priv_state))
|
||||
return ERR_CAST(priv_state);
|
||||
|
||||
return to_ingenic_ipu_priv_state(priv_state);
|
||||
}
|
||||
|
||||
static struct ingenic_ipu_private_state *
|
||||
ingenic_ipu_get_new_priv_state(struct ingenic_ipu *priv, struct drm_atomic_state *state)
|
||||
{
|
||||
struct drm_private_state *priv_state;
|
||||
|
||||
priv_state = drm_atomic_get_new_private_obj_state(state, &priv->private_obj);
|
||||
if (!priv_state)
|
||||
return NULL;
|
||||
|
||||
return to_ingenic_ipu_priv_state(priv_state);
|
||||
}
|
||||
|
||||
/*
|
||||
* Apply conventional cubic convolution kernel. Both parameters
|
||||
* and return value are 15.16 signed fixed-point.
|
||||
|
@ -293,11 +329,16 @@ static void ingenic_ipu_plane_atomic_update(struct drm_plane *plane,
|
|||
const struct drm_format_info *finfo;
|
||||
u32 ctrl, stride = 0, coef_index = 0, format = 0;
|
||||
bool needs_modeset, upscaling_w, upscaling_h;
|
||||
struct ingenic_ipu_private_state *ipu_state;
|
||||
int err;
|
||||
|
||||
if (!newstate || !newstate->fb)
|
||||
return;
|
||||
|
||||
ipu_state = ingenic_ipu_get_new_priv_state(ipu, state);
|
||||
if (WARN_ON(!ipu_state))
|
||||
return;
|
||||
|
||||
finfo = drm_format_info(newstate->fb->format->format);
|
||||
|
||||
if (!ipu->clk_enabled) {
|
||||
|
@ -470,27 +511,27 @@ static void ingenic_ipu_plane_atomic_update(struct drm_plane *plane,
|
|||
if (ipu->soc_info->has_bicubic)
|
||||
ctrl |= JZ_IPU_CTRL_ZOOM_SEL;
|
||||
|
||||
upscaling_w = ipu->num_w > ipu->denom_w;
|
||||
upscaling_w = ipu_state->num_w > ipu_state->denom_w;
|
||||
if (upscaling_w)
|
||||
ctrl |= JZ_IPU_CTRL_HSCALE;
|
||||
|
||||
if (ipu->num_w != 1 || ipu->denom_w != 1) {
|
||||
if (ipu_state->num_w != 1 || ipu_state->denom_w != 1) {
|
||||
if (!ipu->soc_info->has_bicubic && !upscaling_w)
|
||||
coef_index |= (ipu->denom_w - 1) << 16;
|
||||
coef_index |= (ipu_state->denom_w - 1) << 16;
|
||||
else
|
||||
coef_index |= (ipu->num_w - 1) << 16;
|
||||
coef_index |= (ipu_state->num_w - 1) << 16;
|
||||
ctrl |= JZ_IPU_CTRL_HRSZ_EN;
|
||||
}
|
||||
|
||||
upscaling_h = ipu->num_h > ipu->denom_h;
|
||||
upscaling_h = ipu_state->num_h > ipu_state->denom_h;
|
||||
if (upscaling_h)
|
||||
ctrl |= JZ_IPU_CTRL_VSCALE;
|
||||
|
||||
if (ipu->num_h != 1 || ipu->denom_h != 1) {
|
||||
if (ipu_state->num_h != 1 || ipu_state->denom_h != 1) {
|
||||
if (!ipu->soc_info->has_bicubic && !upscaling_h)
|
||||
coef_index |= ipu->denom_h - 1;
|
||||
coef_index |= ipu_state->denom_h - 1;
|
||||
else
|
||||
coef_index |= ipu->num_h - 1;
|
||||
coef_index |= ipu_state->num_h - 1;
|
||||
ctrl |= JZ_IPU_CTRL_VRSZ_EN;
|
||||
}
|
||||
|
||||
|
@ -501,13 +542,13 @@ static void ingenic_ipu_plane_atomic_update(struct drm_plane *plane,
|
|||
/* Set the LUT index register */
|
||||
regmap_write(ipu->map, JZ_REG_IPU_RSZ_COEF_INDEX, coef_index);
|
||||
|
||||
if (ipu->num_w != 1 || ipu->denom_w != 1)
|
||||
if (ipu_state->num_w != 1 || ipu_state->denom_w != 1)
|
||||
ingenic_ipu_set_coefs(ipu, JZ_REG_IPU_HRSZ_COEF_LUT,
|
||||
ipu->num_w, ipu->denom_w);
|
||||
ipu_state->num_w, ipu_state->denom_w);
|
||||
|
||||
if (ipu->num_h != 1 || ipu->denom_h != 1)
|
||||
if (ipu_state->num_h != 1 || ipu_state->denom_h != 1)
|
||||
ingenic_ipu_set_coefs(ipu, JZ_REG_IPU_VRSZ_COEF_LUT,
|
||||
ipu->num_h, ipu->denom_h);
|
||||
ipu_state->num_h, ipu_state->denom_h);
|
||||
|
||||
/* Clear STATUS register */
|
||||
regmap_write(ipu->map, JZ_REG_IPU_STATUS, 0);
|
||||
|
@ -519,7 +560,8 @@ static void ingenic_ipu_plane_atomic_update(struct drm_plane *plane,
|
|||
dev_dbg(ipu->dev, "Scaling %ux%u to %ux%u (%u:%u horiz, %u:%u vert)\n",
|
||||
newstate->src_w >> 16, newstate->src_h >> 16,
|
||||
newstate->crtc_w, newstate->crtc_h,
|
||||
ipu->num_w, ipu->denom_w, ipu->num_h, ipu->denom_h);
|
||||
ipu_state->num_w, ipu_state->denom_w,
|
||||
ipu_state->num_h, ipu_state->denom_h);
|
||||
}
|
||||
|
||||
static int ingenic_ipu_plane_atomic_check(struct drm_plane *plane,
|
||||
|
@ -533,6 +575,7 @@ static int ingenic_ipu_plane_atomic_check(struct drm_plane *plane,
|
|||
struct ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
|
||||
struct drm_crtc *crtc = new_plane_state->crtc ?: old_plane_state->crtc;
|
||||
struct drm_crtc_state *crtc_state;
|
||||
struct ingenic_ipu_private_state *ipu_state;
|
||||
|
||||
if (!crtc)
|
||||
return 0;
|
||||
|
@ -541,6 +584,10 @@ static int ingenic_ipu_plane_atomic_check(struct drm_plane *plane,
|
|||
if (WARN_ON(!crtc_state))
|
||||
return -EINVAL;
|
||||
|
||||
ipu_state = ingenic_ipu_get_priv_state(ipu, state);
|
||||
if (IS_ERR(ipu_state))
|
||||
return PTR_ERR(ipu_state);
|
||||
|
||||
/* Request a full modeset if we are enabling or disabling the IPU. */
|
||||
if (!old_plane_state->crtc ^ !new_plane_state->crtc)
|
||||
crtc_state->mode_changed = true;
|
||||
|
@ -593,10 +640,10 @@ static int ingenic_ipu_plane_atomic_check(struct drm_plane *plane,
|
|||
if (num_h > max_h)
|
||||
return -EINVAL;
|
||||
|
||||
ipu->num_w = num_w;
|
||||
ipu->num_h = num_h;
|
||||
ipu->denom_w = denom_w;
|
||||
ipu->denom_h = denom_h;
|
||||
ipu_state->num_w = num_w;
|
||||
ipu_state->num_h = num_h;
|
||||
ipu_state->denom_w = denom_w;
|
||||
ipu_state->denom_h = denom_h;
|
||||
|
||||
out_check_damage:
|
||||
if (ingenic_drm_map_noncoherent(ipu->master))
|
||||
|
@ -679,6 +726,33 @@ static const struct drm_plane_funcs ingenic_ipu_plane_funcs = {
|
|||
.atomic_set_property = ingenic_ipu_plane_atomic_set_property,
|
||||
};
|
||||
|
||||
static struct drm_private_state *
|
||||
ingenic_ipu_duplicate_state(struct drm_private_obj *obj)
|
||||
{
|
||||
struct ingenic_ipu_private_state *state = to_ingenic_ipu_priv_state(obj->state);
|
||||
|
||||
state = kmemdup(state, sizeof(*state), GFP_KERNEL);
|
||||
if (!state)
|
||||
return NULL;
|
||||
|
||||
__drm_atomic_helper_private_obj_duplicate_state(obj, &state->base);
|
||||
|
||||
return &state->base;
|
||||
}
|
||||
|
||||
static void ingenic_ipu_destroy_state(struct drm_private_obj *obj,
|
||||
struct drm_private_state *state)
|
||||
{
|
||||
struct ingenic_ipu_private_state *priv_state = to_ingenic_ipu_priv_state(state);
|
||||
|
||||
kfree(priv_state);
|
||||
}
|
||||
|
||||
static const struct drm_private_state_funcs ingenic_ipu_private_state_funcs = {
|
||||
.atomic_duplicate_state = ingenic_ipu_duplicate_state,
|
||||
.atomic_destroy_state = ingenic_ipu_destroy_state,
|
||||
};
|
||||
|
||||
static irqreturn_t ingenic_ipu_irq_handler(int irq, void *arg)
|
||||
{
|
||||
struct ingenic_ipu *ipu = arg;
|
||||
|
@ -717,6 +791,7 @@ static const struct regmap_config ingenic_ipu_regmap_config = {
|
|||
static int ingenic_ipu_bind(struct device *dev, struct device *master, void *d)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct ingenic_ipu_private_state *private_state;
|
||||
const struct soc_info *soc_info;
|
||||
struct drm_device *drm = d;
|
||||
struct drm_plane *plane;
|
||||
|
@ -810,7 +885,20 @@ static int ingenic_ipu_bind(struct device *dev, struct device *master, void *d)
|
|||
return err;
|
||||
}
|
||||
|
||||
private_state = kzalloc(sizeof(*private_state), GFP_KERNEL);
|
||||
if (!private_state) {
|
||||
err = -ENOMEM;
|
||||
goto err_clk_unprepare;
|
||||
}
|
||||
|
||||
drm_atomic_private_obj_init(drm, &ipu->private_obj, &private_state->base,
|
||||
&ingenic_ipu_private_state_funcs);
|
||||
|
||||
return 0;
|
||||
|
||||
err_clk_unprepare:
|
||||
clk_unprepare(ipu->clk);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void ingenic_ipu_unbind(struct device *dev,
|
||||
|
@ -818,6 +906,7 @@ static void ingenic_ipu_unbind(struct device *dev,
|
|||
{
|
||||
struct ingenic_ipu *ipu = dev_get_drvdata(dev);
|
||||
|
||||
drm_atomic_private_obj_fini(&ipu->private_obj);
|
||||
clk_unprepare(ipu->clk);
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/drm_fb_helper.h>
|
||||
#include <drm/drm_gem_cma_helper.h>
|
||||
#include <drm/drm_gem_framebuffer_helper.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
@ -176,6 +177,7 @@ static int kmb_setup_mode_config(struct drm_device *drm)
|
|||
drm->mode_config.min_height = KMB_FB_MIN_HEIGHT;
|
||||
drm->mode_config.max_width = KMB_FB_MAX_WIDTH;
|
||||
drm->mode_config.max_height = KMB_FB_MAX_HEIGHT;
|
||||
drm->mode_config.preferred_depth = 24;
|
||||
drm->mode_config.funcs = &kmb_mode_config_funcs;
|
||||
|
||||
ret = kmb_setup_crtc(drm);
|
||||
|
@ -559,6 +561,8 @@ static int kmb_probe(struct platform_device *pdev)
|
|||
if (ret)
|
||||
goto err_register;
|
||||
|
||||
drm_fbdev_generic_setup(&kmb->drm, 0);
|
||||
|
||||
return 0;
|
||||
|
||||
err_register:
|
||||
|
|
|
@ -357,6 +357,7 @@ int lima_device_init(struct lima_device *ldev)
|
|||
int err, i;
|
||||
|
||||
dma_set_coherent_mask(ldev->dev, DMA_BIT_MASK(32));
|
||||
dma_set_max_seg_size(ldev->dev, UINT_MAX);
|
||||
|
||||
err = lima_clk_init(ldev);
|
||||
if (err)
|
||||
|
|
|
@ -127,7 +127,7 @@ int lima_gem_create_handle(struct drm_device *dev, struct drm_file *file,
|
|||
if (err)
|
||||
goto out;
|
||||
} else {
|
||||
struct sg_table *sgt = drm_gem_shmem_get_pages_sgt(obj);
|
||||
struct sg_table *sgt = drm_gem_shmem_get_pages_sgt(shmem);
|
||||
|
||||
if (IS_ERR(sgt)) {
|
||||
err = PTR_ERR(sgt);
|
||||
|
@ -151,7 +151,7 @@ static void lima_gem_free_object(struct drm_gem_object *obj)
|
|||
if (!list_empty(&bo->va))
|
||||
dev_err(obj->dev->dev, "lima gem free bo still has va\n");
|
||||
|
||||
drm_gem_shmem_free_object(obj);
|
||||
drm_gem_shmem_free(&bo->base);
|
||||
}
|
||||
|
||||
static int lima_gem_object_open(struct drm_gem_object *obj, struct drm_file *file)
|
||||
|
@ -179,7 +179,7 @@ static int lima_gem_pin(struct drm_gem_object *obj)
|
|||
if (bo->heap_size)
|
||||
return -EINVAL;
|
||||
|
||||
return drm_gem_shmem_pin(obj);
|
||||
return drm_gem_shmem_pin(&bo->base);
|
||||
}
|
||||
|
||||
static int lima_gem_vmap(struct drm_gem_object *obj, struct dma_buf_map *map)
|
||||
|
@ -189,7 +189,7 @@ static int lima_gem_vmap(struct drm_gem_object *obj, struct dma_buf_map *map)
|
|||
if (bo->heap_size)
|
||||
return -EINVAL;
|
||||
|
||||
return drm_gem_shmem_vmap(obj, map);
|
||||
return drm_gem_shmem_vmap(&bo->base, map);
|
||||
}
|
||||
|
||||
static int lima_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
|
||||
|
@ -199,19 +199,19 @@ static int lima_gem_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
|
|||
if (bo->heap_size)
|
||||
return -EINVAL;
|
||||
|
||||
return drm_gem_shmem_mmap(obj, vma);
|
||||
return drm_gem_shmem_mmap(&bo->base, vma);
|
||||
}
|
||||
|
||||
static const struct drm_gem_object_funcs lima_gem_funcs = {
|
||||
.free = lima_gem_free_object,
|
||||
.open = lima_gem_object_open,
|
||||
.close = lima_gem_object_close,
|
||||
.print_info = drm_gem_shmem_print_info,
|
||||
.print_info = drm_gem_shmem_object_print_info,
|
||||
.pin = lima_gem_pin,
|
||||
.unpin = drm_gem_shmem_unpin,
|
||||
.get_sg_table = drm_gem_shmem_get_sg_table,
|
||||
.unpin = drm_gem_shmem_object_unpin,
|
||||
.get_sg_table = drm_gem_shmem_object_get_sg_table,
|
||||
.vmap = lima_gem_vmap,
|
||||
.vunmap = drm_gem_shmem_vunmap,
|
||||
.vunmap = drm_gem_shmem_object_vunmap,
|
||||
.mmap = lima_gem_mmap,
|
||||
};
|
||||
|
||||
|
|
|
@ -371,7 +371,7 @@ static void lima_sched_build_error_task_list(struct lima_sched_task *task)
|
|||
} else {
|
||||
buffer_chunk->size = lima_bo_size(bo);
|
||||
|
||||
ret = drm_gem_shmem_vmap(&bo->base.base, &map);
|
||||
ret = drm_gem_shmem_vmap(&bo->base, &map);
|
||||
if (ret) {
|
||||
kvfree(et);
|
||||
goto out;
|
||||
|
@ -379,7 +379,7 @@ static void lima_sched_build_error_task_list(struct lima_sched_task *task)
|
|||
|
||||
memcpy(buffer_chunk + 1, map.vaddr, buffer_chunk->size);
|
||||
|
||||
drm_gem_shmem_vunmap(&bo->base.base, &map);
|
||||
drm_gem_shmem_vunmap(&bo->base, &map);
|
||||
}
|
||||
|
||||
buffer_chunk = (void *)(buffer_chunk + 1) + buffer_chunk->size;
|
||||
|
|
|
@ -6,9 +6,11 @@ config DRM_MESON
|
|||
select DRM_KMS_HELPER
|
||||
select DRM_KMS_CMA_HELPER
|
||||
select DRM_GEM_CMA_HELPER
|
||||
select DRM_DISPLAY_CONNECTOR
|
||||
select VIDEOMODE_HELPERS
|
||||
select REGMAP_MMIO
|
||||
select MESON_CANVAS
|
||||
select CEC_CORE if CEC_NOTIFIER
|
||||
|
||||
config DRM_MESON_DW_HDMI
|
||||
tristate "HDMI Synopsys Controller support for Amlogic Meson Display"
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
meson-drm-y := meson_drv.o meson_plane.o meson_crtc.o meson_venc_cvbs.o
|
||||
meson-drm-y := meson_drv.o meson_plane.o meson_crtc.o meson_encoder_cvbs.o
|
||||
meson-drm-y += meson_viu.o meson_vpp.o meson_venc.o meson_vclk.o meson_overlay.o
|
||||
meson-drm-y += meson_rdma.o meson_osd_afbcd.o
|
||||
meson-drm-y += meson_encoder_hdmi.o
|
||||
|
||||
obj-$(CONFIG_DRM_MESON) += meson-drm.o
|
||||
obj-$(CONFIG_DRM_MESON_DW_HDMI) += meson_dw_hdmi.o
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue