drm-misc-next for 5.17:

UAPI Changes:
 
  * Remove restrictions on DMA_BUF_SET_NAME ioctl
  * connector: State of privacy screen
  * sysfs: Send hotplug uevent
 
 Cross-subsystem Changes:
 
  * clk/bmc-2835: Fixes
 
  * dma-buf: Add dma_resv selftest; Error-handling fixes; Add debugfs
    helpers; Remove dma_resv_get_excl_unlocked(); Documentation fixes
 
  * pwm: Introduce of_pwm_single_xlate()
 
 Core Changes:
 
  * Support for privacy screens
  * Make drm_irq.c legacy
  * Fix __stack_depot_* name conflict
  * Documentation fixes
  * Fixes and cleanups
 
  * dp-helper: Reuse 8b/10b link-training delay helpers
 
  * format-helper: Update interfaces
 
  * fb-helper: Allocate shadow buffer of correct size
 
  * gem: Link GEM SHMEM and CMA helpers into separate modules; Use
 	    dma_resv iterator; Import DMA_BUF namespace into GEM-helper modules
 
  * gem/shmem-helper: Interface cleanups
 
  * scheduler: Grab fence in drm_sched_job_add_implicit_dependencies();
    Lockdep fixes
 
  * kms-helpers: Link several files from core into the KMS-helper module
 
 Driver Changes:
 
  * Use dma_resv_iter in several places
  * Fixes and cleanups
 
  * amdgpu: Use drm_kms_helper_connector_hotplug_event(); Get all fences
    at once
 
  * bridge: Switch to managed MIPI DSI helpers in several places; Register
    and attach during probe in several places; Convert to YAML in several
    places
 
  * bridge/anx7625: Support MIPI DPI input; Support HDMI audio; Fixes
 
  * bridge/dw-hdmi: Allow interlace on bridge
 
  * bridge/ps8640: Enable PM; Support aux-bus
 
  * bridge/tc358768: Enabled reference clock; Support pulse mode;
    Modesetting fixes
 
  * bridge/ti-sn65dsi86: Use regmap_bulk_write(); Implement PWM
 
  * etnaviv: Get all fences at once
 
  * gma500: GEM object cleanups; Remove generic drivers in probe function
 
  * i915: Support VESA panel backlights
 
  * ingenic: Fixes and cleanups
 
  * kirin: Adjust probe order
 
  * kmb: Enable framebuffer console
 
  * lima: Kconfig fixes
 
  * meson: Refactoring to supperot DRM_BRIDGE_ATTACH_NO_ENCODER
 
  * msm: Fixes and cleanups
 
  * msm/dsi: Adjust probe order
 
  * omap: Fixes and cleanups
 
  * nouveau: CRC fixes; Validate LUTs in atomic check; Set HDMI AVI RGB
    quantization to FULL; Fixes and cleanups
 
  * panel: Support Innolux G070Y2-T02, Vivax TPC-9150, JDI R63452,
    Newhaven 1.8-128160EF, Wanchanglong W552964ABA, Novatek NT35950,
    BOE BF060Y8M, Sony Tulip Truly NT35521; Use dev_err_probe() throughout
    drivers; Fixes and cleanups
 
  * panel/ili9881c: Orientation fixes
 
  * radeon: Use dma_resv_wait_timeout()
 
  * rockchip: Add timeout for DSP hold; Suspend/resume fixes; PLL clock
    fixes; Implement mmap in GEM object functions
 
  * simpledrm: Support FB_DAMAGE_CLIPS and virtual screen sizes
 
  * sun4i: Use CMA helpers without vmap support
 
  * tidss: Fixes and cleanups
 
  * v3d: Cleanups
 
  * vc4: Fix HDMI-CEC hang when display is off; Power on HDMI controller
    while disabling; Support 4k@60 Hz modes; Fixes and cleanups
 
  * video: Convert to sysfs_emit() in several places
 
  * video/omapfb: Fix fall-through
 
  * virtio: Overflow fixes
 
  * xen: Implement mmap as GEM object functions
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEEchf7rIzpz2NEoWjlaA3BHVMLeiMFAmGWF0wACgkQaA3BHVML
 eiN55ggAr6QN7S7Uxu98XnqfAHC9RErY7r3PoTXXS6ODvxY41bWOpHk8TQzuw626
 JCNnpQCk6Gi8L3yl8r/l1fqoirGXrfDR1YvrnmG4I9xhPxOqBmgxDWw7HQrROm2B
 FctOvgFukvzn5jzQk2FqYgs5JBV20WqLrfEhttPFFMvjLGti/U31/+d+aGMJdRIQ
 kE2eVpPZtAlbBAP+S4mglKp6w+WrrzNHHULSGOPcGS5jwLrNFaQg7w75JiLInzyR
 RWum28USXSkKE0d6XBqHw+PWAwo3B/Vq9XSnbCX4+3GQX4tJh+hosyU7ju+rA0BY
 FJCyN/YgZvc+474o/LVtMh7PbKMEHQ==
 =TV6Q
 -----END PGP SIGNATURE-----

Merge tag 'drm-misc-next-2021-11-18' of git://anongit.freedesktop.org/drm/drm-misc into drm-next

drm-misc-next for 5.17:

UAPI Changes:

 * Remove restrictions on DMA_BUF_SET_NAME ioctl
 * connector: State of privacy screen
 * sysfs: Send hotplug uevent

Cross-subsystem Changes:

 * clk/bmc-2835: Fixes

 * dma-buf: Add dma_resv selftest; Error-handling fixes; Add debugfs
   helpers; Remove dma_resv_get_excl_unlocked(); Documentation fixes

 * pwm: Introduce of_pwm_single_xlate()

Core Changes:

 * Support for privacy screens
 * Make drm_irq.c legacy
 * Fix __stack_depot_* name conflict
 * Documentation fixes
 * Fixes and cleanups

 * dp-helper: Reuse 8b/10b link-training delay helpers

 * format-helper: Update interfaces

 * fb-helper: Allocate shadow buffer of correct size

 * gem: Link GEM SHMEM and CMA helpers into separate modules; Use
	    dma_resv iterator; Import DMA_BUF namespace into GEM-helper modules

 * gem/shmem-helper: Interface cleanups

 * scheduler: Grab fence in drm_sched_job_add_implicit_dependencies();
   Lockdep fixes

 * kms-helpers: Link several files from core into the KMS-helper module

Driver Changes:

 * Use dma_resv_iter in several places
 * Fixes and cleanups

 * amdgpu: Use drm_kms_helper_connector_hotplug_event(); Get all fences
   at once

 * bridge: Switch to managed MIPI DSI helpers in several places; Register
   and attach during probe in several places; Convert to YAML in several
   places

 * bridge/anx7625: Support MIPI DPI input; Support HDMI audio; Fixes

 * bridge/dw-hdmi: Allow interlace on bridge

 * bridge/ps8640: Enable PM; Support aux-bus

 * bridge/tc358768: Enabled reference clock; Support pulse mode;
   Modesetting fixes

 * bridge/ti-sn65dsi86: Use regmap_bulk_write(); Implement PWM

 * etnaviv: Get all fences at once

 * gma500: GEM object cleanups; Remove generic drivers in probe function

 * i915: Support VESA panel backlights

 * ingenic: Fixes and cleanups

 * kirin: Adjust probe order

 * kmb: Enable framebuffer console

 * lima: Kconfig fixes

 * meson: Refactoring to supperot DRM_BRIDGE_ATTACH_NO_ENCODER

 * msm: Fixes and cleanups

 * msm/dsi: Adjust probe order

 * omap: Fixes and cleanups

 * nouveau: CRC fixes; Validate LUTs in atomic check; Set HDMI AVI RGB
   quantization to FULL; Fixes and cleanups

 * panel: Support Innolux G070Y2-T02, Vivax TPC-9150, JDI R63452,
   Newhaven 1.8-128160EF, Wanchanglong W552964ABA, Novatek NT35950,
   BOE BF060Y8M, Sony Tulip Truly NT35521; Use dev_err_probe() throughout
   drivers; Fixes and cleanups

 * panel/ili9881c: Orientation fixes

 * radeon: Use dma_resv_wait_timeout()

 * rockchip: Add timeout for DSP hold; Suspend/resume fixes; PLL clock
   fixes; Implement mmap in GEM object functions

 * simpledrm: Support FB_DAMAGE_CLIPS and virtual screen sizes

 * sun4i: Use CMA helpers without vmap support

 * tidss: Fixes and cleanups

 * v3d: Cleanups

 * vc4: Fix HDMI-CEC hang when display is off; Power on HDMI controller
   while disabling; Support 4k@60 Hz modes; Fixes and cleanups

 * video: Convert to sysfs_emit() in several places

 * video/omapfb: Fix fall-through

 * virtio: Overflow fixes

 * xen: Implement mmap as GEM object functions

Signed-off-by: Dave Airlie <airlied@redhat.com>

From: Thomas Zimmermann <tzimmermann@suse.de>
Link: https://patchwork.freedesktop.org/patch/msgid/YZYZSypIrr+qcih3@linux-uq9g.fritz.box
This commit is contained in:
Dave Airlie 2021-11-23 09:38:29 +10:00
commit c18c889111
269 changed files with 10506 additions and 3580 deletions

View File

@ -43,14 +43,70 @@ properties:
vdd33-supply: vdd33-supply:
description: Regulator that provides the supply 3.3V power. 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: ports:
$ref: /schemas/graph.yaml#/properties/ports $ref: /schemas/graph.yaml#/properties/ports
properties: properties:
port@0: port@0:
$ref: /schemas/graph.yaml#/properties/port $ref: /schemas/graph.yaml#/$defs/port-base
unevaluatedProperties: false
description: 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: port@1:
$ref: /schemas/graph.yaml#/properties/port $ref: /schemas/graph.yaml#/properties/port
@ -87,6 +143,9 @@ examples:
vdd10-supply = <&pp1000_mipibrdg>; vdd10-supply = <&pp1000_mipibrdg>;
vdd18-supply = <&pp1800_mipibrdg>; vdd18-supply = <&pp1800_mipibrdg>;
vdd33-supply = <&pp3300_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 { ports {
#address-cells = <1>; #address-cells = <1>;
@ -96,6 +155,8 @@ examples:
reg = <0>; reg = <0>;
anx7625_in: endpoint { anx7625_in: endpoint {
remote-endpoint = <&mipi_dsi>; remote-endpoint = <&mipi_dsi>;
bus-type = <5>;
data-lanes = <0 1 2 3>;
}; };
}; };

View File

@ -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>;
};
};
};
};
};
...

View File

@ -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>;
};
};
};
};

View File

@ -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>;
};
};
};
};

View File

@ -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>;
};
};
};
};
};
...

View File

@ -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>;
};
};
};
};

View File

@ -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>;
};
};
...

View File

@ -9,24 +9,28 @@ title: Ilitek ILI9881c based MIPI-DSI panels
maintainers: maintainers:
- Maxime Ripard <mripard@kernel.org> - Maxime Ripard <mripard@kernel.org>
allOf:
- $ref: panel-common.yaml#
properties: properties:
compatible: compatible:
items: items:
- enum: - enum:
- bananapi,lhr050h41 - bananapi,lhr050h41
- feixin,k101-im2byl02 - feixin,k101-im2byl02
- wanchanglong,w552946aba
- const: ilitek,ili9881c - const: ilitek,ili9881c
backlight: true backlight: true
power-supply: true power-supply: true
reg: true reg: true
reset-gpios: true reset-gpios: true
rotation: true
required: required:
- compatible - compatible
- power-supply - power-supply
- reg - reg
- reset-gpios
additionalProperties: false additionalProperties: false

View File

@ -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>;
};
};
};
};
};
...

View File

@ -35,6 +35,8 @@ properties:
- boe,tv080wum-nl0 - boe,tv080wum-nl0
# Innolux P079ZCA 7.85" 768x1024 TFT LCD panel # Innolux P079ZCA 7.85" 768x1024 TFT LCD panel
- innolux,p079zca - innolux,p079zca
# JDI FHD_R63452 1080x1920 5.2" IPS LCD Panel
- jdi,fhd-r63452
# Khadas TS050 5" 1080x1920 LCD panel # Khadas TS050 5" 1080x1920 LCD panel
- khadas,ts050 - khadas,ts050
# Kingdisplay KD097D04 9.7" 1536x2048 TFT LCD panel # Kingdisplay KD097D04 9.7" 1536x2048 TFT LCD panel

View File

@ -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>;
};
};
};
};
...

View File

@ -1328,6 +1328,8 @@ patternProperties:
description: Wondermedia Technologies, Inc. description: Wondermedia Technologies, Inc.
"^wobo,.*": "^wobo,.*":
description: Wobo description: Wobo
"^wanchanglong,.*":
description: Wanchanglong Electronics TechnologySHENZHENCo.Ltd.
"^x-powers,.*": "^x-powers,.*":
description: X-Powers description: X-Powers
"^xes,.*": "^xes,.*":

View File

@ -435,3 +435,18 @@ Legacy CRTC/Modeset Helper Functions Reference
.. kernel-doc:: drivers/gpu/drm/drm_crtc_helper.c .. kernel-doc:: drivers/gpu/drm/drm_crtc_helper.c
:export: :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:

View File

@ -506,6 +506,8 @@ Property Types and Blob Property Support
.. kernel-doc:: drivers/gpu/drm/drm_property.c .. kernel-doc:: drivers/gpu/drm/drm_property.c
:export: :export:
.. _standard_connector_properties:
Standard Connector Properties Standard Connector Properties
----------------------------- -----------------------------

View File

@ -268,17 +268,6 @@ Contact: Daniel Vetter
Level: Intermediate 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 Generic fbdev defio support
--------------------------- ---------------------------
@ -463,6 +452,21 @@ Contact: Thomas Zimmermann <tzimmermann@suse.de>, Christian König, Daniel Vette
Level: Intermediate 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 Core refactorings
================= =================

View File

@ -6063,10 +6063,17 @@ F: drivers/gpu/drm/panel/panel-novatek-nt36672a.c
DRM DRIVER FOR NVIDIA GEFORCE/QUADRO GPUS DRM DRIVER FOR NVIDIA GEFORCE/QUADRO GPUS
M: Ben Skeggs <bskeggs@redhat.com> 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: dri-devel@lists.freedesktop.org
L: nouveau@lists.freedesktop.org L: nouveau@lists.freedesktop.org
S: Supported 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: drivers/gpu/drm/nouveau/
F: include/uapi/drm/nouveau_drm.h F: include/uapi/drm/nouveau_drm.h
@ -6524,6 +6531,14 @@ F: drivers/gpu/drm/drm_panel.c
F: drivers/gpu/drm/panel/ F: drivers/gpu/drm/panel/
F: include/drm/drm_panel.h 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 DRM TTM SUBSYSTEM
M: Christian Koenig <christian.koenig@amd.com> M: Christian Koenig <christian.koenig@amd.com>
M: Huang Rui <ray.huang@amd.com> M: Huang Rui <ray.huang@amd.com>
@ -20167,6 +20182,8 @@ F: include/uapi/linux/virtio_gpio.h
VIRTIO GPU DRIVER VIRTIO GPU DRIVER
M: David Airlie <airlied@linux.ie> M: David Airlie <airlied@linux.ie>
M: Gerd Hoffmann <kraxel@redhat.com> 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: dri-devel@lists.freedesktop.org
L: virtualization@lists.linux-foundation.org L: virtualization@lists.linux-foundation.org
S: Maintained S: Maintained

View File

@ -932,8 +932,7 @@ static int bcm2835_clock_is_on(struct clk_hw *hw)
static u32 bcm2835_clock_choose_div(struct clk_hw *hw, static u32 bcm2835_clock_choose_div(struct clk_hw *hw,
unsigned long rate, unsigned long rate,
unsigned long parent_rate, unsigned long parent_rate)
bool round_up)
{ {
struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw); struct bcm2835_clock *clock = bcm2835_clock_from_hw(hw);
const struct bcm2835_clock_data *data = clock->data; 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); rem = do_div(temp, rate);
div = temp; 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; div &= ~unused_frac_mask;
/* different clamping limits apply for a mash clock */ /* 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_clock *clock = bcm2835_clock_from_hw(hw);
struct bcm2835_cprman *cprman = clock->cprman; struct bcm2835_cprman *cprman = clock->cprman;
const struct bcm2835_clock_data *data = clock->data; 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; u32 ctl;
spin_lock(&cprman->regs_lock); 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)) { if (!(BIT(parent_idx) & data->set_rate_parent)) {
*prate = clk_hw_get_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); *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, rate = bcm2835_clock_choose_div_and_prate(hw, i, req->rate,
&div, &prate, &div, &prate,
&avgrate); &avgrate);
if (rate > best_rate && rate <= req->rate) { if (abs(req->rate - rate) < abs(req->rate - best_rate)) {
best_parent = parent; best_parent = parent;
best_prate = prate; best_prate = prate;
best_rate = rate; best_rate = rate;

View File

@ -11,6 +11,7 @@ obj-$(CONFIG_DMABUF_SYSFS_STATS) += dma-buf-sysfs-stats.o
dmabuf_selftests-y := \ dmabuf_selftests-y := \
selftest.o \ selftest.o \
st-dma-fence.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 obj-$(CONFIG_DMABUF_SELFTESTS) += dmabuf_selftests.o

View File

@ -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. * 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 * It could support changing the name of the dma-buf if the same
* attached to any devices. It could theoritically support changing the * piece of memory is used for multiple purpose between different devices.
* 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. * @dmabuf: [in] dmabuf buffer that will be renamed.
* @buf: [in] A piece of userspace memory that contains the name of * @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) static long dma_buf_set_name(struct dma_buf *dmabuf, const char __user *buf)
{ {
char *name = strndup_user(buf, DMA_BUF_NAME_LEN); char *name = strndup_user(buf, DMA_BUF_NAME_LEN);
long ret = 0;
if (IS_ERR(name)) if (IS_ERR(name))
return PTR_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); spin_lock(&dmabuf->name_lock);
kfree(dmabuf->name); kfree(dmabuf->name);
dmabuf->name = name; dmabuf->name = name;
spin_unlock(&dmabuf->name_lock); spin_unlock(&dmabuf->name_lock);
out_unlock: return 0;
dma_resv_unlock(dmabuf->resv);
return ret;
} }
static long dma_buf_ioctl(struct file *file, static long dma_buf_ioctl(struct file *file,
@ -1058,8 +1047,8 @@ EXPORT_SYMBOL_NS_GPL(dma_buf_move_notify, DMA_BUF);
* *
* Interfaces:: * Interfaces::
* *
* void \*dma_buf_vmap(struct dma_buf \*dmabuf) * void \*dma_buf_vmap(struct dma_buf \*dmabuf, struct dma_buf_map \*map)
* void dma_buf_vunmap(struct dma_buf \*dmabuf, void \*vaddr) * 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 * 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 * 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 *buf_obj;
struct dma_buf_attachment *attach_obj; struct dma_buf_attachment *attach_obj;
struct dma_resv_iter cursor;
struct dma_fence *fence;
int count = 0, attach_count; int count = 0, attach_count;
size_t size = 0; size_t size = 0;
int ret; int ret;
@ -1370,14 +1357,7 @@ static int dma_buf_debug_show(struct seq_file *s, void *unused)
buf_obj->name ?: ""); buf_obj->name ?: "");
spin_unlock(&buf_obj->name_lock); spin_unlock(&buf_obj->name_lock);
dma_resv_for_each_fence(&cursor, buf_obj->resv, true, fence) { dma_resv_describe(buf_obj->resv, s);
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");
}
seq_puts(s, "\tAttached Devices:\n"); seq_puts(s, "\tAttached Devices:\n");
attach_count = 0; attach_count = 0;

View File

@ -15,6 +15,7 @@
#include <linux/atomic.h> #include <linux/atomic.h>
#include <linux/dma-fence.h> #include <linux/dma-fence.h>
#include <linux/sched/signal.h> #include <linux/sched/signal.h>
#include <linux/seq_file.h>
#define CREATE_TRACE_POINTS #define CREATE_TRACE_POINTS
#include <trace/events/dma_fence.h> #include <trace/events/dma_fence.h>
@ -907,6 +908,22 @@ err_free_cb:
} }
EXPORT_SYMBOL(dma_fence_wait_any_timeout); 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. * dma_fence_init - Initialize a custom fence.
* @fence: the fence to initialize * @fence: the fence to initialize

View File

@ -38,6 +38,7 @@
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/sched/mm.h> #include <linux/sched/mm.h>
#include <linux/mmu_notifier.h> #include <linux/mmu_notifier.h>
#include <linux/seq_file.h>
/** /**
* DOC: Reservation Object Overview * 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); 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) #if IS_ENABLED(CONFIG_LOCKDEP)
static int __init dma_resv_lockdep(void) static int __init dma_resv_lockdep(void)
{ {

View File

@ -12,3 +12,4 @@
selftest(sanitycheck, __sanitycheck__) /* keep first (igt selfcheck) */ selftest(sanitycheck, __sanitycheck__) /* keep first (igt selfcheck) */
selftest(dma_fence, dma_fence) selftest(dma_fence, dma_fence)
selftest(dma_fence_chain, dma_fence_chain) selftest(dma_fence_chain, dma_fence_chain)
selftest(dma_resv, dma_resv)

View File

@ -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);
}

View File

@ -211,7 +211,7 @@ config DRM_TTM_HELPER
Helpers for ttm-based gem objects Helpers for ttm-based gem objects
config DRM_GEM_CMA_HELPER config DRM_GEM_CMA_HELPER
bool tristate
depends on DRM depends on DRM
help help
Choose this if you need the GEM CMA helper functions 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 Choose this if you need the KMS CMA helper functions
config DRM_GEM_SHMEM_HELPER config DRM_GEM_SHMEM_HELPER
bool tristate
depends on DRM && MMU depends on DRM && MMU
help help
Choose this if you need the GEM shmem helper functions Choose this if you need the GEM shmem helper functions
@ -495,3 +495,7 @@ config DRM_PANEL_ORIENTATION_QUIRKS
config DRM_LIB_RANDOM config DRM_LIB_RANDOM
bool bool
default n default n
config DRM_PRIVACY_SCREEN
bool
default n

View File

@ -4,37 +4,41 @@
# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. # Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher.
drm-y := drm_aperture.o drm_auth.o drm_cache.o \ 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_drv.o \
drm_sysfs.o drm_hashtab.o drm_mm.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_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_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_modeset_lock.o drm_atomic.o drm_bridge.o \
drm_framebuffer.o drm_connector.o drm_blend.o \ drm_framebuffer.o drm_connector.o drm_blend.o \
drm_encoder.o drm_mode_object.o drm_property.o \ drm_encoder.o drm_mode_object.o drm_property.o \
drm_plane.o drm_color_mgmt.o drm_print.o \ drm_plane.o drm_color_mgmt.o drm_print.o \
drm_dumb_buffers.o drm_mode_config.o drm_vblank.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_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_managed.o drm_vblank_work.o
drm-$(CONFIG_DRM_LEGACY) += drm_agpsupport.o drm_bufs.o drm_context.o drm_dma.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_irq.o drm_legacy_misc.o drm_lock.o drm_memory.o \
drm_vm.o drm_scatter.o drm_vm.o
drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o drm-$(CONFIG_DRM_LIB_RANDOM) += lib/drm_random.o
drm-$(CONFIG_COMPAT) += drm_ioc32.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_DRM_PANEL) += drm_panel.o
drm-$(CONFIG_OF) += drm_of.o drm-$(CONFIG_OF) += drm_of.o
drm-$(CONFIG_PCI) += drm_pci.o drm-$(CONFIG_PCI) += drm_pci.o
drm-$(CONFIG_DEBUG_FS) += drm_debugfs.o drm_debugfs_crc.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_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 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 drm_vram_helper-y := drm_gem_vram_helper.o
obj-$(CONFIG_DRM_VRAM_HELPER) += drm_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 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_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_plane_helper.o drm_dp_mst_topology.o drm_atomic_helper.o \
drm_kms_helper_common.o drm_dp_dual_mode_helper.o \ drm_kms_helper_common.o drm_dp_dual_mode_helper.o \
drm_simple_kms_helper.o drm_modeset_helper.o \ drm_simple_kms_helper.o drm_modeset_helper.o \
drm_scdc_helper.o drm_gem_atomic_helper.o \ drm_scdc_helper.o drm_gem_atomic_helper.o \
drm_gem_framebuffer_helper.o \ drm_gem_framebuffer_helper.o \
drm_atomic_state_helper.o drm_damage_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_PANEL_BRIDGE) += bridge/panel.o
drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o drm_kms_helper-$(CONFIG_DRM_FBDEV_EMULATION) += drm_fb_helper.o

View File

@ -458,7 +458,6 @@ struct amdgpu_flip_work {
uint64_t base; uint64_t base;
struct drm_pending_vblank_event *event; struct drm_pending_vblank_event *event;
struct amdgpu_bo *old_abo; struct amdgpu_bo *old_abo;
struct dma_fence *excl;
unsigned shared_count; unsigned shared_count;
struct dma_fence **shared; struct dma_fence **shared;
struct dma_fence_cb cb; struct dma_fence_cb cb;

View File

@ -83,9 +83,6 @@ static void amdgpu_display_flip_work_func(struct work_struct *__work)
unsigned i; unsigned i;
int vpos, hpos; int vpos, hpos;
if (amdgpu_display_flip_handle_fence(work, &work->excl))
return;
for (i = 0; i < work->shared_count; ++i) for (i = 0; i < work->shared_count; ++i)
if (amdgpu_display_flip_handle_fence(work, &work->shared[i])) if (amdgpu_display_flip_handle_fence(work, &work->shared[i]))
return; return;
@ -203,7 +200,7 @@ int amdgpu_display_crtc_page_flip_target(struct drm_crtc *crtc,
goto unpin; 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); &work->shared_count, &work->shared);
if (unlikely(r != 0)) { if (unlikely(r != 0)) {
DRM_ERROR("failed to get fences for buffer\n"); DRM_ERROR("failed to get fences for buffer\n");
@ -253,7 +250,6 @@ unreserve:
cleanup: cleanup:
amdgpu_bo_unref(&work->old_abo); amdgpu_bo_unref(&work->old_abo);
dma_fence_put(work->excl);
for (i = 0; i < work->shared_count; ++i) for (i = 0; i < work->shared_count; ++i)
dma_fence_put(work->shared[i]); dma_fence_put(work->shared[i]);
kfree(work->shared); kfree(work->shared);

View File

@ -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, struct dma_resv *resv, enum amdgpu_sync_mode mode,
void *owner) void *owner)
{ {
struct dma_resv_list *flist; struct dma_resv_iter cursor;
struct dma_fence *f; struct dma_fence *f;
unsigned i; int r;
int r = 0;
if (resv == NULL) if (resv == NULL)
return -EINVAL; return -EINVAL;
/* always sync to the exclusive fence */ dma_resv_for_each_fence(&cursor, resv, true, f) {
f = dma_resv_excl_fence(resv); dma_fence_chain_for_each(f, f) {
dma_fence_chain_for_each(f, f) { struct dma_fence_chain *chain = to_dma_fence_chain(f);
struct dma_fence_chain *chain = to_dma_fence_chain(f);
if (amdgpu_sync_test_fence(adev, mode, owner, chain ? if (amdgpu_sync_test_fence(adev, mode, owner, chain ?
chain->fence : f)) { chain->fence : f)) {
r = amdgpu_sync_fence(sync, f); r = amdgpu_sync_fence(sync, f);
dma_fence_put(f); dma_fence_put(f);
if (r) if (r)
return r; return r;
break; 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;
} }
} }
return 0; return 0;

View File

@ -1353,10 +1353,9 @@ static bool amdgpu_ttm_bo_eviction_valuable(struct ttm_buffer_object *bo,
const struct ttm_place *place) const struct ttm_place *place)
{ {
unsigned long num_pages = bo->resource->num_pages; unsigned long num_pages = bo->resource->num_pages;
struct dma_resv_iter resv_cursor;
struct amdgpu_res_cursor cursor; struct amdgpu_res_cursor cursor;
struct dma_resv_list *flist;
struct dma_fence *f; struct dma_fence *f;
int i;
/* Swapout? */ /* Swapout? */
if (bo->resource->mem_type == TTM_PL_SYSTEM) 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 * If true, then return false as any KFD process needs all its BOs to
* be resident to run successfully * be resident to run successfully
*/ */
flist = dma_resv_shared_list(bo->base.resv); dma_resv_for_each_fence(&resv_cursor, bo->base.resv, true, f) {
if (flist) { if (amdkfd_fence_check_mm(f, current->mm))
for (i = 0; i < flist->shared_count; ++i) { return false;
f = rcu_dereference_protected(flist->shared[i],
dma_resv_held(bo->base.resv));
if (amdkfd_fence_check_mm(f, current->mm))
return false;
}
} }
switch (bo->resource->mem_type) { switch (bo->resource->mem_type) {

View File

@ -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) 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_resv *resv = vm->root.bo->tbo.base.resv;
struct dma_fence *excl, **shared; struct dma_resv_iter cursor;
unsigned i, shared_count; struct dma_fence *fence;
int r;
r = dma_resv_get_fences(resv, &excl, &shared_count, &shared); dma_resv_for_each_fence(&cursor, resv, true, fence) {
if (r) { /* Add a callback for each fence in the reservation object */
/* 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) {
amdgpu_vm_prt_get(adev); amdgpu_vm_prt_get(adev);
amdgpu_vm_add_prt_cb(adev, shared[i]); amdgpu_vm_add_prt_cb(adev, fence);
} }
kfree(shared);
} }
/** /**

View File

@ -3012,7 +3012,7 @@ static void handle_hpd_irq_helper(struct amdgpu_dm_connector *aconnector)
drm_modeset_unlock_all(dev); drm_modeset_unlock_all(dev);
if (aconnector->base.force == DRM_FORCE_UNSPECIFIED) 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)) { } else if (dc_link_detect(aconnector->dc_link, DETECT_REASON_HPD)) {
if (new_connection_type == dc_connection_none && 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); drm_modeset_unlock_all(dev);
if (aconnector->base.force == DRM_FORCE_UNSPECIFIED) 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); mutex_unlock(&aconnector->hpd_lock);
@ -3221,7 +3221,7 @@ out:
dm_restore_drm_connector_state(dev, connector); dm_restore_drm_connector_state(dev, connector);
drm_modeset_unlock_all(dev); 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)) { } else if (dc_link_detect(dc_link, DETECT_REASON_HPDRX)) {
if (aconnector->fake_enable) if (aconnector->fake_enable)
@ -3234,7 +3234,7 @@ out:
dm_restore_drm_connector_state(dev, connector); dm_restore_drm_connector_state(dev, connector);
drm_modeset_unlock_all(dev); drm_modeset_unlock_all(dev);
drm_kms_helper_hotplug_event(dev); drm_kms_helper_connector_hotplug_event(connector);
} }
} }
#ifdef CONFIG_DRM_AMD_DC_HDCP #ifdef CONFIG_DRM_AMD_DC_HDCP

View File

@ -1243,7 +1243,7 @@ static ssize_t trigger_hotplug(struct file *f, const char __user *buf,
dm_restore_drm_connector_state(dev, connector); dm_restore_drm_connector_state(dev, connector);
drm_modeset_unlock_all(dev); drm_modeset_unlock_all(dev);
drm_kms_helper_hotplug_event(dev); drm_kms_helper_connector_hotplug_event(connector);
} else if (param[0] == 0) { } else if (param[0] == 0) {
if (!aconnector->dc_link) if (!aconnector->dc_link)
goto unlock; 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); dm_restore_drm_connector_state(dev, connector);
drm_modeset_unlock_all(dev); drm_modeset_unlock_all(dev);
drm_kms_helper_hotplug_event(dev); drm_kms_helper_connector_hotplug_event(connector);
} }
unlock: unlock:

View File

@ -182,6 +182,7 @@ config DRM_PARADE_PS8622
config DRM_PARADE_PS8640 config DRM_PARADE_PS8640
tristate "Parade PS8640 MIPI DSI to eDP Converter" tristate "Parade PS8640 MIPI DSI to eDP Converter"
depends on OF depends on OF
select DRM_DP_AUX_BUS
select DRM_KMS_HELPER select DRM_KMS_HELPER
select DRM_MIPI_DSI select DRM_MIPI_DSI
select DRM_PANEL select DRM_PANEL

View File

@ -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_registers(struct adv7511 *adv);
int adv7533_patch_cec_registers(struct adv7511 *adv); int adv7533_patch_cec_registers(struct adv7511 *adv);
int adv7533_attach_dsi(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); int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv);
#ifdef CONFIG_DRM_I2C_ADV7511_AUDIO #ifdef CONFIG_DRM_I2C_ADV7511_AUDIO

View File

@ -910,9 +910,6 @@ static int adv7511_bridge_attach(struct drm_bridge *bridge,
return ret; return ret;
} }
if (adv->type == ADV7533 || adv->type == ADV7535)
ret = adv7533_attach_dsi(adv);
if (adv->i2c_main->irq) if (adv->i2c_main->irq)
regmap_write(adv->regmap, ADV7511_REG_INT_ENABLE(0), regmap_write(adv->regmap, ADV7511_REG_INT_ENABLE(0),
ADV7511_INT0_HPD); 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); drm_bridge_add(&adv7511->bridge);
adv7511_audio_init(dev, adv7511); 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; return 0;
err_unregister_audio:
adv7511_audio_exit(adv7511);
drm_bridge_remove(&adv7511->bridge);
err_unregister_cec: err_unregister_cec:
i2c_unregister_device(adv7511->i2c_cec); i2c_unregister_device(adv7511->i2c_cec);
clk_disable_unprepare(adv7511->cec_clk); 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); struct adv7511 *adv7511 = i2c_get_clientdata(i2c);
if (adv7511->type == ADV7533 || adv7511->type == ADV7535)
adv7533_detach_dsi(adv7511);
i2c_unregister_device(adv7511->i2c_cec); i2c_unregister_device(adv7511->i2c_cec);
clk_disable_unprepare(adv7511->cec_clk); clk_disable_unprepare(adv7511->cec_clk);

View File

@ -153,11 +153,10 @@ int adv7533_attach_dsi(struct adv7511 *adv)
return -EPROBE_DEFER; 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)) { if (IS_ERR(dsi)) {
dev_err(dev, "failed to create dsi device\n"); dev_err(dev, "failed to create dsi device\n");
ret = PTR_ERR(dsi); return PTR_ERR(dsi);
goto err_dsi_device;
} }
adv->dsi = 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 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_MODE_VIDEO_HSE; 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) { if (ret < 0) {
dev_err(dev, "failed to attach dsi to host\n"); dev_err(dev, "failed to attach dsi to host\n");
goto err_dsi_attach; return ret;
} }
return 0; 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) int adv7533_parse_dt(struct device_node *np, struct adv7511 *adv)

View File

@ -998,11 +998,21 @@ int analogix_dp_send_psr_spd(struct analogix_dp_device *dp,
if (!blocking) if (!blocking)
return 0; 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, ret = readx_poll_timeout(analogix_dp_get_psr_status, dp, psr_status,
psr_status >= 0 && psr_status >= 0 &&
((vsc->db[1] && psr_status == DP_PSR_SINK_ACTIVE_RFB) || ((vsc->db[1] && psr_status == DP_PSR_SINK_ACTIVE_RFB) ||
(!vsc->db[1] && psr_status == DP_PSR_SINK_INACTIVE)), 1500, (!vsc->db[1] && (psr_status == DP_PSR_SINK_ACTIVE_RESYNC ||
DP_TIMEOUT_PSR_LOOP_MS * 1000); psr_status == DP_PSR_SINK_INACTIVE))),
1500, DP_TIMEOUT_PSR_LOOP_MS * 1000);
if (ret) { if (ret) {
dev_warn(dp->dev, "Failed to apply PSR %d\n", ret); dev_warn(dp->dev, "Failed to apply PSR %d\n", ret);
return ret; return ret;

View File

@ -32,6 +32,8 @@
#include <drm/drm_print.h> #include <drm/drm_print.h>
#include <drm/drm_probe_helper.h> #include <drm/drm_probe_helper.h>
#include <media/v4l2-fwnode.h>
#include <sound/hdmi-codec.h>
#include <video/display_timing.h> #include <video/display_timing.h>
#include "anx7625.h" #include "anx7625.h"
@ -166,6 +168,20 @@ static int anx7625_write_and_or(struct anx7625_data *ctx,
offset, (val & and_mask) | (or_mask)); 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) 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); 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); AP_AUX_CTRL_STATUS);
if (val < 0 || (val & 0x0F)) { if (val < 0 || (val & 0x0F)) {
DRM_DEV_ERROR(dev, "aux status %02x\n", val); 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, 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; 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 */ /* Reduction of fraction a/b */
static void anx7625_reduction_of_a_fraction(unsigned long *a, unsigned long *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, ret |= anx7625_write_and(ctx, ctx->i2c.rx_p1_client,
MIPI_LANE_CTRL_0, 0xfc); MIPI_LANE_CTRL_0, 0xfc);
ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client, 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 */
htotal = ctx->dt.hactive.min + ctx->dt.hfront_porch.min + 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; 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) static void anx7625_dp_start(struct anx7625_data *ctx)
{ {
int ret; int ret;
@ -625,9 +679,10 @@ static void anx7625_dp_start(struct anx7625_data *ctx)
return; return;
} }
anx7625_config_audio_input(ctx); if (ctx->pdata.is_dpi)
ret = anx7625_dpi_config(ctx);
ret = anx7625_dsi_config(ctx); else
ret = anx7625_dsi_config(ctx);
if (ret < 0) if (ret < 0)
DRM_DEV_ERROR(dev, "MIPI phy setup error.\n"); DRM_DEV_ERROR(dev, "MIPI phy setup error.\n");
@ -1075,6 +1130,7 @@ static void anx7625_start_dp_work(struct anx7625_data *ctx)
return; return;
} }
ctx->hpd_status = 1;
ctx->hpd_high_cnt++; ctx->hpd_high_cnt++;
/* Not support HDCP */ /* 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); ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client, 0xec, 0x10);
/* Interrupt for DRM */ /* Interrupt for DRM */
ret |= anx7625_write_or(ctx, ctx->i2c.rx_p1_client, 0xff, 0x01); 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; return;
}
ret = anx7625_reg_read(ctx, ctx->i2c.rx_p1_client, 0x86); ret = anx7625_reg_read(ctx, ctx->i2c.rx_p1_client, 0x86);
if (ret < 0) if (ret < 0)
@ -1104,6 +1162,10 @@ static void anx7625_hpd_polling(struct anx7625_data *ctx)
int ret, val; int ret, val;
struct device *dev = &ctx->client->dev; 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, ret = readx_poll_timeout(anx7625_read_hpd_status_p0,
ctx, val, ctx, val,
((val & HPD_STATUS) || (val < 0)), ((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; 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) static void dp_hpd_change_handler(struct anx7625_data *ctx, bool on)
{ {
struct device *dev = &ctx->client->dev; struct device *dev = &ctx->client->dev;
@ -1146,9 +1223,8 @@ static void dp_hpd_change_handler(struct anx7625_data *ctx, bool on)
} else { } else {
DRM_DEV_DEBUG_DRIVER(dev, " HPD high\n"); DRM_DEV_DEBUG_DRIVER(dev, " HPD high\n");
anx7625_start_dp_work(ctx); anx7625_start_dp_work(ctx);
anx7625_dp_adjust_swing(ctx);
} }
ctx->hpd_status = 1;
} }
static int anx7625_hpd_change_detect(struct anx7625_data *ctx) 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; 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, static int anx7625_parse_dt(struct device *dev,
struct anx7625_platform_data *pdata) struct anx7625_platform_data *pdata)
{ {
struct device_node *np = dev->of_node; struct device_node *np = dev->of_node, *ep0;
struct drm_panel *panel; struct drm_panel *panel;
int ret; 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); pdata->mipi_host_node = of_graph_get_remote_node(np, 0, 0);
if (!pdata->mipi_host_node) { if (!pdata->mipi_host_node) {
DRM_DEV_ERROR(dev, "fail to get internal panel.\n"); DRM_DEV_ERROR(dev, "fail to get internal panel.\n");
return -ENODEV; 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); ret = drm_of_find_panel_or_bridge(np, 1, 0, &panel, NULL);
if (ret < 0) { 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; 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) static int anx7625_attach_dsi(struct anx7625_data *ctx)
@ -1316,6 +1653,7 @@ static int anx7625_attach_dsi(struct anx7625_data *ctx)
.channel = 0, .channel = 0,
.node = NULL, .node = NULL,
}; };
int ret;
DRM_DEV_DEBUG_DRIVER(dev, "attach dsi\n"); DRM_DEV_DEBUG_DRIVER(dev, "attach dsi\n");
@ -1325,22 +1663,22 @@ static int anx7625_attach_dsi(struct anx7625_data *ctx)
return -EINVAL; return -EINVAL;
} }
dsi = mipi_dsi_device_register_full(host, &info); dsi = devm_mipi_dsi_device_register_full(dev, host, &info);
if (IS_ERR(dsi)) { if (IS_ERR(dsi)) {
DRM_DEV_ERROR(dev, "fail to create dsi device.\n"); DRM_DEV_ERROR(dev, "fail to create dsi device.\n");
return -EINVAL; return -EINVAL;
} }
dsi->lanes = 4; dsi->lanes = ctx->pdata.mipi_lanes;
dsi->format = MIPI_DSI_FMT_RGB888; dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | dsi->mode_flags = MIPI_DSI_MODE_VIDEO |
MIPI_DSI_MODE_VIDEO_SYNC_PULSE | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_VIDEO_HSE; 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"); DRM_DEV_ERROR(dev, "fail to attach dsi to host.\n");
mipi_dsi_device_unregister(dsi); return ret;
return -EINVAL;
} }
ctx->dsi = dsi; ctx->dsi = dsi;
@ -1350,16 +1688,6 @@ static int anx7625_attach_dsi(struct anx7625_data *ctx)
return 0; 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, static int anx7625_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags) enum drm_bridge_attach_flags flags)
{ {
@ -1376,12 +1704,6 @@ static int anx7625_bridge_attach(struct drm_bridge *bridge,
return -ENODEV; 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) { if (ctx->pdata.panel_bridge) {
err = drm_bridge_attach(bridge->encoder, err = drm_bridge_attach(bridge->encoder,
ctx->pdata.panel_bridge, 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"); 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; hsync = mode->hsync_end - mode->hsync_start;
hfp = mode->hsync_start - mode->hdisplay; hfp = mode->hsync_start - mode->hdisplay;
hbp = mode->htotal - mode->hsync_end; 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 = { static const struct drm_bridge_funcs anx7625_bridge_funcs = {
.attach = anx7625_bridge_attach, .attach = anx7625_bridge_attach,
.detach = anx7625_bridge_detach,
.disable = anx7625_bridge_disable, .disable = anx7625_bridge_disable,
.mode_valid = anx7625_bridge_mode_valid, .mode_valid = anx7625_bridge_mode_valid,
.mode_set = anx7625_bridge_mode_set, .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.funcs = &anx7625_bridge_funcs;
platform->bridge.of_node = client->dev.of_node; platform->bridge.of_node = client->dev.of_node;
platform->bridge.ops = DRM_BRIDGE_OP_EDID | DRM_BRIDGE_OP_HPD; platform->bridge.ops = DRM_BRIDGE_OP_EDID;
platform->bridge.type = DRM_MODE_CONNECTOR_eDP; 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); 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"); DRM_DEV_DEBUG_DRIVER(dev, "probe done\n");
return 0; 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: free_wq:
if (platform->workqueue) if (platform->workqueue)
destroy_workqueue(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); anx7625_unregister_i2c_dummy_clients(platform);
if (platform->pdata.audio_en)
anx7625_unregister_audio(platform);
kfree(platform); kfree(platform);
return 0; return 0;
} }

View File

@ -111,6 +111,7 @@
#define AUDIO_CHANNEL_STATUS_6 0xd5 #define AUDIO_CHANNEL_STATUS_6 0xd5
#define TDM_SLAVE_MODE 0x10 #define TDM_SLAVE_MODE 0x10
#define I2S_SLAVE_MODE 0x08 #define I2S_SLAVE_MODE 0x08
#define AUDIO_LAYOUT 0x01
#define AUDIO_CONTROL_REGISTER 0xe6 #define AUDIO_CONTROL_REGISTER 0xe6
#define TDM_TIMING_MODE 0x08 #define TDM_TIMING_MODE 0x08
@ -141,12 +142,20 @@
#define HORIZONTAL_BACK_PORCH_H 0x22 /* Bit[7:4] are reserved */ #define HORIZONTAL_BACK_PORCH_H 0x22 /* Bit[7:4] are reserved */
/******** END of I2C Address 0x72 *********/ /******** 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 */ /* Register definition of device address 0x7e */
#define I2C_ADDR_7E_FLASH_CONTROLLER 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 FLASH_LOAD_STA_CHK BIT(7)
#define XTAL_FRQ_SEL 0x3F #define XTAL_FRQ_SEL 0x3F
@ -349,12 +358,21 @@ struct s_edid_data {
/***************** Display End *****************/ /***************** Display End *****************/
#define MAX_LANES_SUPPORT 4
struct anx7625_platform_data { struct anx7625_platform_data {
struct gpio_desc *gpio_p_on; struct gpio_desc *gpio_p_on;
struct gpio_desc *gpio_reset; struct gpio_desc *gpio_reset;
struct regulator_bulk_data supplies[3]; struct regulator_bulk_data supplies[3];
struct drm_bridge *panel_bridge; struct drm_bridge *panel_bridge;
int intp_irq; 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; u32 low_power_mode;
struct device_node *mipi_host_node; struct device_node *mipi_host_node;
}; };
@ -371,6 +389,7 @@ struct anx7625_i2c_client {
struct anx7625_data { struct anx7625_data {
struct anx7625_platform_data pdata; struct anx7625_platform_data pdata;
struct platform_device *audio_pdev;
int hpd_status; int hpd_status;
int hpd_high_cnt; int hpd_high_cnt;
/* Lock for work queue */ /* Lock for work queue */
@ -379,6 +398,8 @@ struct anx7625_data {
struct anx7625_i2c_client i2c; struct anx7625_i2c_client i2c;
struct i2c_client *last_client; struct i2c_client *last_client;
struct s_edid_data slimport_edid_p; struct s_edid_data slimport_edid_p;
struct device *codec_dev;
hdmi_codec_plugged_cb plugged_cb;
struct work_struct work; struct work_struct work;
struct workqueue_struct *workqueue; struct workqueue_struct *workqueue;
char edid_block; char edid_block;

View File

@ -13,6 +13,7 @@
#include <linux/platform_device.h> #include <linux/platform_device.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h> #include <drm/drm_bridge.h>
#include <drm/drm_edid.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); 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 = { static const struct drm_bridge_funcs display_connector_bridge_funcs = {
.attach = display_connector_attach, .attach = display_connector_attach,
.detect = display_connector_detect, .detect = display_connector_detect,
.get_edid = display_connector_get_edid, .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) 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; struct display_connector *conn;
unsigned int type; unsigned int type;
const char *label; const char *label = NULL;
int ret; int ret;
conn = devm_kzalloc(&pdev->dev, sizeof(*conn), GFP_KERNEL); conn = devm_kzalloc(&pdev->dev, sizeof(*conn), GFP_KERNEL);

View File

@ -472,11 +472,11 @@ static int lt8912_attach_dsi(struct lt8912 *lt)
return -EPROBE_DEFER; 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)) { if (IS_ERR(dsi)) {
ret = PTR_ERR(dsi); ret = PTR_ERR(dsi);
dev_err(dev, "failed to create dsi device (%d)\n", ret); dev_err(dev, "failed to create dsi device (%d)\n", ret);
goto err_dsi_device; return ret;
} }
lt->dsi = dsi; lt->dsi = dsi;
@ -489,24 +489,13 @@ static int lt8912_attach_dsi(struct lt8912 *lt)
MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_LPM |
MIPI_DSI_MODE_NO_EOT_PACKET; MIPI_DSI_MODE_NO_EOT_PACKET;
ret = mipi_dsi_attach(dsi); ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "failed to attach dsi to host\n"); dev_err(dev, "failed to attach dsi to host\n");
goto err_dsi_attach; return ret;
} }
return 0; 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) 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) if (ret)
goto error; goto error;
ret = lt8912_attach_dsi(lt);
if (ret)
goto error;
lt->is_attached = true; lt->is_attached = true;
return 0; return 0;
@ -573,7 +558,6 @@ static void lt8912_bridge_detach(struct drm_bridge *bridge)
struct lt8912 *lt = bridge_to_lt8912(bridge); struct lt8912 *lt = bridge_to_lt8912(bridge);
if (lt->is_attached) { if (lt->is_attached) {
lt8912_detach_dsi(lt);
lt8912_hard_power_off(lt); lt8912_hard_power_off(lt);
drm_connector_unregister(&lt->connector); drm_connector_unregister(&lt->connector);
drm_connector_cleanup(&lt->connector); drm_connector_cleanup(&lt->connector);
@ -718,8 +702,15 @@ static int lt8912_probe(struct i2c_client *client,
drm_bridge_add(&lt->bridge); drm_bridge_add(&lt->bridge);
ret = lt8912_attach_dsi(lt);
if (ret)
goto err_attach;
return 0; return 0;
err_attach:
drm_bridge_remove(&lt->bridge);
lt8912_free_i2c(lt);
err_i2c: err_i2c:
lt8912_put_dt(lt); lt8912_put_dt(lt);
err_dt_parse: err_dt_parse:

View File

@ -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 }; const struct mipi_dsi_device_info info = { "lt9611", 0, NULL };
struct mipi_dsi_device *dsi; struct mipi_dsi_device *dsi;
struct mipi_dsi_host *host; struct mipi_dsi_host *host;
struct device *dev = lt9611->dev;
int ret; int ret;
host = of_find_mipi_dsi_host_by_node(dsi_node); 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); 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)) { if (IS_ERR(dsi)) {
dev_err(lt9611->dev, "failed to create dsi device\n"); dev_err(lt9611->dev, "failed to create dsi device\n");
return dsi; 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 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_VIDEO_HSE; MIPI_DSI_MODE_VIDEO_HSE;
ret = mipi_dsi_attach(dsi); ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0) { if (ret < 0) {
dev_err(lt9611->dev, "failed to attach dsi to host\n"); dev_err(dev, "failed to attach dsi to host\n");
mipi_dsi_device_unregister(dsi);
return ERR_PTR(ret); return ERR_PTR(ret);
} }
return dsi; 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) static int lt9611_connector_init(struct drm_bridge *bridge, struct lt9611 *lt9611)
{ {
int ret; int ret;
@ -838,28 +825,7 @@ static int lt9611_bridge_attach(struct drm_bridge *bridge,
return ret; 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; return 0;
err_unregister_dsi0:
lt9611_bridge_detach(bridge);
drm_connector_cleanup(&lt9611->connector);
mipi_dsi_device_unregister(lt9611->dsi0);
return ret;
} }
static enum drm_mode_status lt9611_bridge_mode_valid(struct drm_bridge *bridge, 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 = { static const struct drm_bridge_funcs lt9611_bridge_funcs = {
.attach = lt9611_bridge_attach, .attach = lt9611_bridge_attach,
.detach = lt9611_bridge_detach,
.mode_valid = lt9611_bridge_mode_valid, .mode_valid = lt9611_bridge_mode_valid,
.enable = lt9611_bridge_enable, .enable = lt9611_bridge_enable,
.disable = lt9611_bridge_disable, .disable = lt9611_bridge_disable,
@ -1181,10 +1146,29 @@ static int lt9611_probe(struct i2c_client *client,
drm_bridge_add(&lt9611->bridge); drm_bridge_add(&lt9611->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); lt9611_enable_hpd_interrupts(lt9611);
return lt9611_audio_init(dev, lt9611); return lt9611_audio_init(dev, lt9611);
err_remove_bridge:
drm_bridge_remove(&lt9611->bridge);
err_disable_regulators: err_disable_regulators:
regulator_bulk_disable(ARRAY_SIZE(lt9611->supplies), lt9611->supplies); regulator_bulk_disable(ARRAY_SIZE(lt9611->supplies), lt9611->supplies);

View File

@ -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 }; const struct mipi_dsi_device_info info = { "lt9611uxc", 0, NULL };
struct mipi_dsi_device *dsi; struct mipi_dsi_device *dsi;
struct mipi_dsi_host *host; struct mipi_dsi_host *host;
struct device *dev = lt9611uxc->dev;
int ret; int ret;
host = of_find_mipi_dsi_host_by_node(dsi_node); host = of_find_mipi_dsi_host_by_node(dsi_node);
if (!host) { 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); 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)) { 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; 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 | dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
MIPI_DSI_MODE_VIDEO_HSE; MIPI_DSI_MODE_VIDEO_HSE;
ret = mipi_dsi_attach(dsi); ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0) { if (ret < 0) {
dev_err(lt9611uxc->dev, "failed to attach dsi to host\n"); dev_err(dev, "failed to attach dsi to host\n");
mipi_dsi_device_unregister(dsi);
return ERR_PTR(ret); return ERR_PTR(ret);
} }
@ -355,19 +355,6 @@ static int lt9611uxc_connector_init(struct drm_bridge *bridge, struct lt9611uxc
return drm_connector_attach_encoder(&lt9611uxc->connector, bridge->encoder); return drm_connector_attach_encoder(&lt9611uxc->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, static int lt9611uxc_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags) enum drm_bridge_attach_flags flags)
{ {
@ -380,27 +367,7 @@ static int lt9611uxc_bridge_attach(struct drm_bridge *bridge,
return ret; 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; return 0;
err_unregister_dsi0:
mipi_dsi_detach(lt9611uxc->dsi0);
mipi_dsi_device_unregister(lt9611uxc->dsi0);
return ret;
} }
static enum drm_mode_status 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 = { static const struct drm_bridge_funcs lt9611uxc_bridge_funcs = {
.attach = lt9611uxc_bridge_attach, .attach = lt9611uxc_bridge_attach,
.detach = lt9611uxc_bridge_detach,
.mode_valid = lt9611uxc_bridge_mode_valid, .mode_valid = lt9611uxc_bridge_mode_valid,
.mode_set = lt9611uxc_bridge_mode_set, .mode_set = lt9611uxc_bridge_mode_set,
.detect = lt9611uxc_bridge_detect, .detect = lt9611uxc_bridge_detect,
@ -980,8 +946,27 @@ retry:
drm_bridge_add(&lt9611uxc->bridge); drm_bridge_add(&lt9611uxc->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); return lt9611uxc_audio_init(dev, lt9611uxc);
err_remove_bridge:
drm_bridge_remove(&lt9611uxc->bridge);
err_disable_regulators: err_disable_regulators:
regulator_bulk_disable(ARRAY_SIZE(lt9611uxc->supplies), lt9611uxc->supplies); regulator_bulk_disable(ARRAY_SIZE(lt9611uxc->supplies), lt9611uxc->supplies);

View File

@ -14,6 +14,7 @@
#include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h> #include <drm/drm_bridge.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h> #include <drm/drm_panel.h>
struct lvds_codec { struct lvds_codec {
@ -118,7 +119,6 @@ static int lvds_codec_probe(struct platform_device *pdev)
struct device_node *bus_node; struct device_node *bus_node;
struct drm_panel *panel; struct drm_panel *panel;
struct lvds_codec *lvds_codec; struct lvds_codec *lvds_codec;
const char *mapping;
int ret; int ret;
lvds_codec = devm_kzalloc(dev, sizeof(*lvds_codec), GFP_KERNEL); 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; return -ENXIO;
} }
ret = of_property_read_string(bus_node, "data-mapping", ret = drm_of_lvds_get_data_mapping(bus_node);
&mapping);
of_node_put(bus_node); of_node_put(bus_node);
if (ret < 0) { if (ret == -ENODEV) {
dev_warn(dev, "missing 'data-mapping' DT property\n"); dev_warn(dev, "missing 'data-mapping' DT property\n");
} else if (ret) {
dev_err(dev, "invalid 'data-mapping' DT property\n");
return ret;
} else { } else {
if (!strcmp(mapping, "jeida-18")) { lvds_codec->bus_format = ret;
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->bridge.funcs = &funcs_decoder; lvds_codec->bridge.funcs = &funcs_decoder;
} }
} }

View File

@ -9,10 +9,12 @@
#include <linux/i2c.h> #include <linux/i2c.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/of_graph.h> #include <linux/of_graph.h>
#include <linux/pm_runtime.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
#include <drm/drm_bridge.h> #include <drm/drm_bridge.h>
#include <drm/drm_dp_aux_bus.h>
#include <drm/drm_dp_helper.h> #include <drm/drm_dp_helper.h>
#include <drm/drm_mipi_dsi.h> #include <drm/drm_mipi_dsi.h>
#include <drm/drm_of.h> #include <drm/drm_of.h>
@ -100,7 +102,7 @@ struct ps8640 {
struct regulator_bulk_data supplies[2]; struct regulator_bulk_data supplies[2];
struct gpio_desc *gpio_reset; struct gpio_desc *gpio_reset;
struct gpio_desc *gpio_powerdown; struct gpio_desc *gpio_powerdown;
bool powered; bool pre_enabled;
}; };
static const struct regmap_config ps8640_regmap_config[] = { 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); return container_of(aux, struct ps8640, aux);
} }
static ssize_t ps8640_aux_transfer(struct drm_dp_aux *aux, static bool ps8640_of_panel_on_aux_bus(struct device *dev)
struct drm_dp_aux_msg *msg) {
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 ps8640 *ps_bridge = aux_to_ps8640(aux);
struct regmap *map = ps_bridge->regmap[PAGE0_DP_CNTL]; 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; return len;
} }
static int ps8640_bridge_vdo_control(struct ps8640 *ps_bridge, static ssize_t ps8640_aux_transfer(struct drm_dp_aux *aux,
const enum ps8640_vdo_control ctrl) 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 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 }; u8 vdo_ctrl_buf[] = { VDO_CTL_ADD, ctrl };
int ret; int ret;
ret = regmap_bulk_write(map, PAGE3_SET_ADD, ret = regmap_bulk_write(map, PAGE3_SET_ADD,
vdo_ctrl_buf, sizeof(vdo_ctrl_buf)); vdo_ctrl_buf, sizeof(vdo_ctrl_buf));
if (ret < 0) { if (ret < 0)
DRM_ERROR("failed to %sable VDO: %d\n", dev_err(dev, "failed to %sable VDO: %d\n",
ctrl == ENABLE ? "en" : "dis", ret); ctrl == ENABLE ? "en" : "dis", ret);
return ret;
}
return 0;
} }
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]; struct ps8640 *ps_bridge = dev_get_drvdata(dev);
int ret, status; int ret;
if (ps_bridge->powered)
return;
ret = regulator_bulk_enable(ARRAY_SIZE(ps_bridge->supplies), ret = regulator_bulk_enable(ARRAY_SIZE(ps_bridge->supplies),
ps_bridge->supplies); ps_bridge->supplies);
if (ret < 0) { if (ret < 0) {
DRM_ERROR("cannot enable regulators %d\n", ret); dev_err(dev, "cannot enable regulators %d\n", ret);
return; return ret;
} }
gpiod_set_value(ps_bridge->gpio_powerdown, 0); 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); gpiod_set_value(ps_bridge->gpio_reset, 0);
/* /*
* Wait for the ps8640 embedded MCU to be ready * Mystery 200 ms delay for the "MCU to be ready". It's unclear if
* First wait 200ms and then check the MCU ready flag every 20ms * 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); msleep(200);
ret = regmap_read_poll_timeout(map, PAGE2_GPIO_H, status, return 0;
status & PS_GPIO9, 20 * 1000, 200 * 1000); }
if (ret < 0) { static int __maybe_unused ps8640_suspend(struct device *dev)
DRM_ERROR("failed read PAGE2_GPIO_H: %d\n", ret); {
goto err_regulators_disable; 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 * 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); ret = regmap_update_bits(map, PAGE2_MCS_EN, MCS_EN, 0);
if (ret < 0) { if (ret < 0)
DRM_ERROR("failed write PAGE2_MCS_EN: %d\n", ret); dev_warn(dev, "failed write PAGE2_MCS_EN: %d\n", ret);
goto err_regulators_disable;
}
/* Switch access edp panel's edid through i2c */ /* Switch access edp panel's edid through i2c */
ret = regmap_write(map, PAGE2_I2C_BYPASS, I2C_BYPASS_EN); 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) 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) ps_bridge->pre_enabled = true;
{
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);
} }
static void ps8640_post_disable(struct drm_bridge *bridge) static void ps8640_post_disable(struct drm_bridge *bridge)
{ {
struct ps8640 *ps_bridge = bridge_to_ps8640(bridge); struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
ps_bridge->pre_enabled = false;
ps8640_bridge_vdo_control(ps_bridge, DISABLE); 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, 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 ps8640 *ps_bridge = bridge_to_ps8640(bridge);
struct device *dev = &ps_bridge->page[0]->dev; 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; int ret;
const struct mipi_dsi_device_info info = { .type = "ps8640",
.channel = 0,
.node = NULL,
};
if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR)) if (!(flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR))
return -EINVAL; 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); ret = drm_dp_aux_register(&ps_bridge->aux);
if (ret) { if (ret) {
dev_err(dev, "failed to register DP AUX channel: %d\n", 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 */ /* Attach the panel-bridge to the dsi bridge */
return drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge, return drm_bridge_attach(bridge->encoder, ps_bridge->panel_bridge,
&ps_bridge->bridge, flags); &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) 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 drm_connector *connector)
{ {
struct ps8640 *ps_bridge = bridge_to_ps8640(bridge); struct ps8640 *ps_bridge = bridge_to_ps8640(bridge);
bool poweroff = !ps_bridge->powered; bool poweroff = !ps_bridge->pre_enabled;
struct edid *edid; struct edid *edid;
/* /*
@ -504,6 +499,12 @@ static struct edid *ps8640_bridge_get_edid(struct drm_bridge *bridge,
return edid; 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 = { static const struct drm_bridge_funcs ps8640_bridge_funcs = {
.attach = ps8640_bridge_attach, .attach = ps8640_bridge_attach,
.detach = ps8640_bridge_detach, .detach = ps8640_bridge_detach,
@ -512,6 +513,53 @@ static const struct drm_bridge_funcs ps8640_bridge_funcs = {
.pre_enable = ps8640_pre_enable, .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) static int ps8640_probe(struct i2c_client *client)
{ {
struct device *dev = &client->dev; struct device *dev = &client->dev;
@ -525,17 +573,6 @@ static int ps8640_probe(struct i2c_client *client)
if (!ps_bridge) if (!ps_bridge)
return -ENOMEM; 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[0].supply = "vdd33";
ps_bridge->supplies[1].supply = "vdd12"; ps_bridge->supplies[1].supply = "vdd12";
ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ps_bridge->supplies), 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.funcs = &ps8640_bridge_funcs;
ps_bridge->bridge.of_node = dev->of_node; ps_bridge->bridge.of_node = dev->of_node;
ps_bridge->bridge.ops = DRM_BRIDGE_OP_EDID;
ps_bridge->bridge.type = DRM_MODE_CONNECTOR_eDP; 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->page[PAGE0_DP_CNTL] = client;
ps_bridge->regmap[PAGE0_DP_CNTL] = devm_regmap_init_i2c(client, ps8640_regmap_config); 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; ps_bridge->aux.transfer = ps8640_aux_transfer;
drm_dp_aux_init(&ps_bridge->aux); 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); drm_bridge_add(&ps_bridge->bridge);
ret = ps8640_bridge_host_attach(dev, ps_bridge);
if (ret)
goto err_bridge_remove;
return 0; return 0;
err_bridge_remove:
drm_bridge_remove(&ps_bridge->bridge);
return ret;
} }
static int ps8640_remove(struct i2c_client *client) static int ps8640_remove(struct i2c_client *client)
@ -613,6 +694,7 @@ static struct i2c_driver ps8640_driver = {
.driver = { .driver = {
.name = "ps8640", .name = "ps8640",
.of_match_table = ps8640_match, .of_match_table = ps8640_match,
.pm = &ps8640_pm_ops,
}, },
}; };
module_i2c_driver(ps8640_driver); module_i2c_driver(ps8640_driver);

View File

@ -320,13 +320,17 @@ static int dw_hdmi_open(struct snd_pcm_substream *substream)
struct snd_pcm_runtime *runtime = substream->runtime; struct snd_pcm_runtime *runtime = substream->runtime;
struct snd_dw_hdmi *dw = substream->private_data; struct snd_dw_hdmi *dw = substream->private_data;
void __iomem *base = dw->data.base; void __iomem *base = dw->data.base;
u8 *eld;
int ret; int ret;
runtime->hw = dw_hdmi_hw; runtime->hw = dw_hdmi_hw;
ret = snd_pcm_hw_constraint_eld(runtime, dw->data.eld); eld = dw->data.get_eld(dw->data.hdmi);
if (ret < 0) if (eld) {
return ret; ret = snd_pcm_hw_constraint_eld(runtime, eld);
if (ret < 0)
return ret;
}
ret = snd_pcm_limit_hw_rates(runtime); ret = snd_pcm_limit_hw_rates(runtime);
if (ret < 0) if (ret < 0)

View File

@ -9,15 +9,15 @@ struct dw_hdmi_audio_data {
void __iomem *base; void __iomem *base;
int irq; int irq;
struct dw_hdmi *hdmi; struct dw_hdmi *hdmi;
u8 *eld; u8 *(*get_eld)(struct dw_hdmi *hdmi);
}; };
struct dw_hdmi_i2s_audio_data { struct dw_hdmi_i2s_audio_data {
struct dw_hdmi *hdmi; struct dw_hdmi *hdmi;
u8 *eld;
void (*write)(struct dw_hdmi *hdmi, u8 val, int offset); void (*write)(struct dw_hdmi *hdmi, u8 val, int offset);
u8 (*read)(struct dw_hdmi *hdmi, int offset); u8 (*read)(struct dw_hdmi *hdmi, int offset);
u8 *(*get_eld)(struct dw_hdmi *hdmi);
}; };
#endif #endif

View File

@ -135,8 +135,15 @@ static int dw_hdmi_i2s_get_eld(struct device *dev, void *data, uint8_t *buf,
size_t len) size_t len)
{ {
struct dw_hdmi_i2s_audio_data *audio = data; 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; return 0;
} }

View File

@ -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); 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) static void dw_hdmi_ahb_audio_enable(struct dw_hdmi *hdmi)
{ {
hdmi_set_cts_n(hdmi, hdmi->audio_cts, hdmi->audio_n); 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.funcs = &dw_hdmi_bridge_funcs;
hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID hdmi->bridge.ops = DRM_BRIDGE_OP_DETECT | DRM_BRIDGE_OP_EDID
| DRM_BRIDGE_OP_HPD; | DRM_BRIDGE_OP_HPD;
hdmi->bridge.interlace_allowed = true;
#ifdef CONFIG_OF #ifdef CONFIG_OF
hdmi->bridge.of_node = pdev->dev.of_node; hdmi->bridge.of_node = pdev->dev.of_node;
#endif #endif
@ -3431,7 +3440,7 @@ struct dw_hdmi *dw_hdmi_probe(struct platform_device *pdev,
audio.base = hdmi->regs; audio.base = hdmi->regs;
audio.irq = irq; audio.irq = irq;
audio.hdmi = hdmi; audio.hdmi = hdmi;
audio.eld = hdmi->connector.eld; audio.get_eld = hdmi_audio_get_eld;
hdmi->enable_audio = dw_hdmi_ahb_audio_enable; hdmi->enable_audio = dw_hdmi_ahb_audio_enable;
hdmi->disable_audio = dw_hdmi_ahb_audio_disable; 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; struct dw_hdmi_i2s_audio_data audio;
audio.hdmi = hdmi; audio.hdmi = hdmi;
audio.eld = hdmi->connector.eld; audio.get_eld = hdmi_audio_get_eld;
audio.write = hdmi_writeb; audio.write = hdmi_writeb;
audio.read = hdmi_readb; audio.read = hdmi_readb;
hdmi->enable_audio = dw_hdmi_i2s_audio_enable; hdmi->enable_audio = dw_hdmi_i2s_audio_enable;

View File

@ -237,6 +237,10 @@ static void tc358768_hw_enable(struct tc358768_priv *priv)
if (priv->enabled) if (priv->enabled)
return; 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); ret = regulator_bulk_enable(ARRAY_SIZE(priv->supplies), priv->supplies);
if (ret < 0) if (ret < 0)
dev_err(priv->dev, "error enabling regulators (%d)\n", ret); 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) if (ret < 0)
dev_err(priv->dev, "error disabling regulators (%d)\n", ret); dev_err(priv->dev, "error disabling regulators (%d)\n", ret);
clk_disable_unprepare(priv->refclk);
priv->enabled = false; 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 tc358768_priv *priv = bridge_to_tc358768(bridge);
struct mipi_dsi_device *dsi_dev = priv->output.dev; struct mipi_dsi_device *dsi_dev = priv->output.dev;
unsigned long mode_flags = dsi_dev->mode_flags;
u32 val, val2, lptxcnt, hact, data_type; u32 val, val2, lptxcnt, hact, data_type;
const struct drm_display_mode *mode; const struct drm_display_mode *mode;
u32 dsibclk_nsk, dsiclk_nsk, ui_nsk, phy_delay_nsk; 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; 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); tc358768_hw_enable(priv);
ret = tc358768_sw_reset(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: case MIPI_DSI_FMT_RGB888:
val |= (0x3 << 4); val |= (0x3 << 4);
hact = mode->hdisplay * 3; hact = mode->hdisplay * 3;
video_start = (mode->htotal - mode->hsync_start) * 3;
data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24; data_type = MIPI_DSI_PACKED_PIXEL_STREAM_24;
break; break;
case MIPI_DSI_FMT_RGB666: case MIPI_DSI_FMT_RGB666:
val |= (0x4 << 4); val |= (0x4 << 4);
hact = mode->hdisplay * 3; hact = mode->hdisplay * 3;
video_start = (mode->htotal - mode->hsync_start) * 3;
data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18; data_type = MIPI_DSI_PACKED_PIXEL_STREAM_18;
break; break;
case MIPI_DSI_FMT_RGB666_PACKED: case MIPI_DSI_FMT_RGB666_PACKED:
val |= (0x4 << 4) | BIT(3); val |= (0x4 << 4) | BIT(3);
hact = mode->hdisplay * 18 / 8; hact = mode->hdisplay * 18 / 8;
video_start = (mode->htotal - mode->hsync_start) * 18 / 8;
data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18; data_type = MIPI_DSI_PIXEL_STREAM_3BYTE_18;
break; break;
case MIPI_DSI_FMT_RGB565: case MIPI_DSI_FMT_RGB565:
val |= (0x5 << 4); val |= (0x5 << 4);
hact = mode->hdisplay * 2; hact = mode->hdisplay * 2;
video_start = (mode->htotal - mode->hsync_start) * 2;
data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16; data_type = MIPI_DSI_PACKED_PIXEL_STREAM_16;
break; break;
default: default:
@ -684,7 +701,8 @@ static void tc358768_bridge_pre_enable(struct drm_bridge *bridge)
} }
/* VSDly[9:0] */ /* 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_DATAFMT, val);
tc358768_write(priv, TC358768_DSITX_DT, data_type); 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); val |= BIT(i + 1);
tc358768_write(priv, TC358768_HSTXVREGEN, val); 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); tc358768_write(priv, TC358768_TXOPTIONCNTRL, 0x1);
/* TXTAGOCNT[26:16] RXTASURECNT[10:0] */ /* 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; val = tc358768_ns_to_cnt(val, dsibclk_nsk) - 1;
val2 = tc358768_ns_to_cnt(tc358768_to_ns((lptxcnt + 1) * dsibclk_nsk), val2 = tc358768_ns_to_cnt(tc358768_to_ns((lptxcnt + 1) * dsibclk_nsk),
dsibclk_nsk) - 2; dsibclk_nsk) - 2;
val |= val2 << 16; val = val << 16 | val2;
dev_dbg(priv->dev, "BTACNTRL1: 0x%x\n", val); dev_dbg(priv->dev, "BTACNTRL1: 0x%x\n", val);
tc358768_write(priv, TC358768_BTACNTRL1, val); tc358768_write(priv, TC358768_BTACNTRL1, val);
/* START[0] */ /* START[0] */
tc358768_write(priv, TC358768_STARTCNTRL, 1); tc358768_write(priv, TC358768_STARTCNTRL, 1);
/* Set event mode */ if (dsi_dev->mode_flags & MIPI_DSI_MODE_VIDEO_SYNC_PULSE) {
tc358768_write(priv, TC358768_DSI_EVENT, 1); /* Set pulse mode */
tc358768_write(priv, TC358768_DSI_EVENT, 0);
/* vsw (+ vbp) */ /* vact */
tc358768_write(priv, TC358768_DSI_VSW, tc358768_write(priv, TC358768_DSI_VACT, mode->vdisplay);
mode->vtotal - mode->vsync_start);
/* vbp (not used in event mode) */ /* vsw */
tc358768_write(priv, TC358768_DSI_VBPR, 0); tc358768_write(priv, TC358768_DSI_VSW,
/* vact */ mode->vsync_end - mode->vsync_start);
tc358768_write(priv, TC358768_DSI_VACT, mode->vdisplay); /* 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) */ /* hact (bytes) */
tc358768_write(priv, TC358768_DSI_HACT, hact); 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)) if (!(dsi_dev->mode_flags & MIPI_DSI_MODE_LPM))
val |= TC358768_DSI_CONTROL_TXMD; 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; val |= TC358768_DSI_CONTROL_HSCKMD;
if (dsi_dev->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET) if (dsi_dev->mode_flags & MIPI_DSI_MODE_NO_EOT_PACKET)

View File

@ -594,11 +594,26 @@ static int tc_bridge_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags) enum drm_bridge_attach_flags flags)
{ {
struct tc_data *tc = bridge_to_tc(bridge); 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 device *dev = &tc->i2c->dev;
struct mipi_dsi_host *host; struct mipi_dsi_host *host;
struct mipi_dsi_device *dsi; struct mipi_dsi_device *dsi;
int ret; int ret;
const struct mipi_dsi_device_info info = { .type = "tc358775", const struct mipi_dsi_device_info info = { .type = "tc358775",
.channel = 0, .channel = 0,
.node = NULL, .node = NULL,
@ -610,11 +625,10 @@ static int tc_bridge_attach(struct drm_bridge *bridge,
return -EPROBE_DEFER; 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)) { if (IS_ERR(dsi)) {
dev_err(dev, "failed to create dsi device\n"); dev_err(dev, "failed to create dsi device\n");
ret = PTR_ERR(dsi); return PTR_ERR(dsi);
goto err_dsi_device;
} }
tc->dsi = dsi; tc->dsi = dsi;
@ -623,29 +637,15 @@ static int tc_bridge_attach(struct drm_bridge *bridge,
dsi->format = MIPI_DSI_FMT_RGB888; dsi->format = MIPI_DSI_FMT_RGB888;
dsi->mode_flags = MIPI_DSI_MODE_VIDEO; dsi->mode_flags = MIPI_DSI_MODE_VIDEO;
ret = mipi_dsi_attach(dsi); ret = devm_mipi_dsi_attach(dev, dsi);
if (ret < 0) { if (ret < 0) {
dev_err(dev, "failed to attach dsi to host\n"); 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 0;
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;
} }
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) static int tc_probe(struct i2c_client *client, const struct i2c_device_id *id)
{ {
struct device *dev = &client->dev; 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); i2c_set_clientdata(client, tc);
ret = tc_attach_host(tc);
if (ret)
goto err_bridge_remove;
return 0; return 0;
err_bridge_remove:
drm_bridge_remove(&tc->bridge);
return ret;
} }
static int tc_remove(struct i2c_client *client) static int tc_remove(struct i2c_client *client)

View File

@ -245,47 +245,9 @@ static int sn65dsi83_attach(struct drm_bridge *bridge,
enum drm_bridge_attach_flags flags) enum drm_bridge_attach_flags flags)
{ {
struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge); 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, return drm_bridge_attach(bridge->encoder, ctx->panel_bridge,
&ctx->bridge, flags); &ctx->bridge, flags);
err_dsi_attach:
mipi_dsi_device_unregister(dsi);
return ret;
} }
static void sn65dsi83_detach(struct drm_bridge *bridge) static void sn65dsi83_detach(struct drm_bridge *bridge)
@ -295,28 +257,9 @@ static void sn65dsi83_detach(struct drm_bridge *bridge)
if (!ctx->dsi) if (!ctx->dsi)
return; return;
mipi_dsi_detach(ctx->dsi);
mipi_dsi_device_unregister(ctx->dsi);
drm_bridge_remove(&ctx->bridge);
ctx->dsi = NULL; 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, static u8 sn65dsi83_get_lvds_range(struct sn65dsi83 *ctx,
const struct drm_display_mode *mode) const struct drm_display_mode *mode)
{ {
@ -394,6 +337,10 @@ static void sn65dsi83_atomic_enable(struct drm_bridge *bridge,
u16 val; u16 val;
int ret; int ret;
/* Deassert reset */
gpiod_set_value(ctx->enable_gpio, 1);
usleep_range(1000, 1100);
/* Get the LVDS format from the bridge state. */ /* Get the LVDS format from the bridge state. */
bridge_state = drm_atomic_get_new_bridge_state(state, bridge); 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); struct sn65dsi83 *ctx = bridge_to_sn65dsi83(bridge);
/* Clear reset, disable PLL */ /* Put the chip in reset, pull EN line low, and assure 10ms reset low timing. */
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. */
gpiod_set_value(ctx->enable_gpio, 0); gpiod_set_value(ctx->enable_gpio, 0);
usleep_range(10000, 11000);
regcache_mark_dirty(ctx->regmap);
} }
static enum drm_mode_status 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 = { static const struct drm_bridge_funcs sn65dsi83_funcs = {
.attach = sn65dsi83_attach, .attach = sn65dsi83_attach,
.detach = sn65dsi83_detach, .detach = sn65dsi83_detach,
.atomic_pre_enable = sn65dsi83_atomic_pre_enable,
.atomic_enable = sn65dsi83_atomic_enable, .atomic_enable = sn65dsi83_atomic_enable,
.atomic_disable = sn65dsi83_atomic_disable, .atomic_disable = sn65dsi83_atomic_disable,
.atomic_post_disable = sn65dsi83_atomic_post_disable,
.mode_valid = sn65dsi83_mode_valid, .mode_valid = sn65dsi83_mode_valid,
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state, .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; 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, static int sn65dsi83_probe(struct i2c_client *client,
const struct i2c_device_id *id) const struct i2c_device_id *id)
{ {
@ -685,10 +661,13 @@ static int sn65dsi83_probe(struct i2c_client *client,
model = id->driver_data; 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); ctx->enable_gpio = devm_gpiod_get(ctx->dev, "enable", GPIOD_OUT_LOW);
if (IS_ERR(ctx->enable_gpio)) if (IS_ERR(ctx->enable_gpio))
return PTR_ERR(ctx->enable_gpio); return PTR_ERR(ctx->enable_gpio);
usleep_range(10000, 11000);
ret = sn65dsi83_parse_dt(ctx, model); ret = sn65dsi83_parse_dt(ctx, model);
if (ret) if (ret)
return ret; return ret;
@ -704,13 +683,22 @@ static int sn65dsi83_probe(struct i2c_client *client,
ctx->bridge.of_node = dev->of_node; ctx->bridge.of_node = dev->of_node;
drm_bridge_add(&ctx->bridge); drm_bridge_add(&ctx->bridge);
ret = sn65dsi83_host_attach(ctx);
if (ret)
goto err_remove_bridge;
return 0; return 0;
err_remove_bridge:
drm_bridge_remove(&ctx->bridge);
return ret;
} }
static int sn65dsi83_remove(struct i2c_client *client) static int sn65dsi83_remove(struct i2c_client *client)
{ {
struct sn65dsi83 *ctx = i2c_get_clientdata(client); struct sn65dsi83 *ctx = i2c_get_clientdata(client);
drm_bridge_remove(&ctx->bridge);
of_node_put(ctx->host_node); of_node_put(ctx->host_node);
return 0; return 0;

View File

@ -4,7 +4,9 @@
* datasheet: https://www.ti.com/lit/ds/symlink/sn65dsi86.pdf * datasheet: https://www.ti.com/lit/ds/symlink/sn65dsi86.pdf
*/ */
#include <linux/atomic.h>
#include <linux/auxiliary_bus.h> #include <linux/auxiliary_bus.h>
#include <linux/bitfield.h>
#include <linux/bits.h> #include <linux/bits.h>
#include <linux/clk.h> #include <linux/clk.h>
#include <linux/debugfs.h> #include <linux/debugfs.h>
@ -15,6 +17,7 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/of_graph.h> #include <linux/of_graph.h>
#include <linux/pm_runtime.h> #include <linux/pm_runtime.h>
#include <linux/pwm.h>
#include <linux/regmap.h> #include <linux/regmap.h>
#include <linux/regulator/consumer.h> #include <linux/regulator/consumer.h>
@ -91,6 +94,13 @@
#define SN_ML_TX_MODE_REG 0x96 #define SN_ML_TX_MODE_REG 0x96
#define ML_TX_MAIN_LINK_OFF 0 #define ML_TX_MAIN_LINK_OFF 0
#define ML_TX_NORMAL_MODE BIT(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 SN_AUX_CMD_STATUS_REG 0xF4
#define AUX_IRQ_STATUS_AUX_RPLY_TOUT BIT(3) #define AUX_IRQ_STATUS_AUX_RPLY_TOUT BIT(3)
#define AUX_IRQ_STATUS_AUX_SHORT BIT(5) #define AUX_IRQ_STATUS_AUX_SHORT BIT(5)
@ -113,11 +123,14 @@
#define SN_LINK_TRAINING_TRIES 10 #define SN_LINK_TRAINING_TRIES 10
#define SN_PWM_GPIO_IDX 3 /* 4th GPIO */
/** /**
* struct ti_sn65dsi86 - Platform data for ti-sn65dsi86 driver. * struct ti_sn65dsi86 - Platform data for ti-sn65dsi86 driver.
* @bridge_aux: AUX-bus sub device for MIPI-to-eDP bridge functionality. * @bridge_aux: AUX-bus sub device for MIPI-to-eDP bridge functionality.
* @gpio_aux: AUX-bus sub device for GPIO controller functionality. * @gpio_aux: AUX-bus sub device for GPIO controller functionality.
* @aux_aux: AUX-bus sub device for eDP AUX channel 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. * @dev: Pointer to the top level (i2c) device.
* @regmap: Regmap for accessing i2c. * @regmap: Regmap for accessing i2c.
@ -145,11 +158,17 @@
* bitmap so we can do atomic ops on it without an extra * bitmap so we can do atomic ops on it without an extra
* lock so concurrent users of our 4 GPIOs don't stomp on * lock so concurrent users of our 4 GPIOs don't stomp on
* each other's read-modify-write. * 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 ti_sn65dsi86 {
struct auxiliary_device bridge_aux; struct auxiliary_device bridge_aux;
struct auxiliary_device gpio_aux; struct auxiliary_device gpio_aux;
struct auxiliary_device aux_aux; struct auxiliary_device aux_aux;
struct auxiliary_device pwm_aux;
struct device *dev; struct device *dev;
struct regmap *regmap; struct regmap *regmap;
@ -172,6 +191,12 @@ struct ti_sn65dsi86 {
struct gpio_chip gchip; struct gpio_chip gchip;
DECLARE_BITMAP(gchip_output, SN_NUM_GPIOS); DECLARE_BITMAP(gchip_output, SN_NUM_GPIOS);
#endif #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[] = { 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, .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, static void ti_sn65dsi86_write_u16(struct ti_sn65dsi86 *pdata,
unsigned int reg, u16 val) unsigned int reg, u16 val)
{ {
regmap_write(pdata->regmap, reg, val & 0xFF); u8 buf[2] = { val & 0xff, val >> 8 };
regmap_write(pdata->regmap, reg + 1, val >> 8);
regmap_bulk_write(pdata->regmap, reg, buf, ARRAY_SIZE(buf));
} }
static u32 ti_sn_bridge_get_dsi_freq(struct ti_sn65dsi86 *pdata) 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, regmap_update_bits(pdata->regmap, SN_DPPLL_SRC_REG, REFCLK_FREQ_MASK,
REFCLK_FREQ(i)); 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) 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); return container_of(bridge, struct ti_sn65dsi86, bridge);
} }
static int ti_sn_bridge_attach(struct drm_bridge *bridge, static int ti_sn_attach_host(struct ti_sn65dsi86 *pdata)
enum drm_bridge_attach_flags flags)
{ {
int ret, val; int ret, val;
struct ti_sn65dsi86 *pdata = bridge_to_ti_sn65dsi86(bridge);
struct mipi_dsi_host *host; struct mipi_dsi_host *host;
struct mipi_dsi_device *dsi; struct mipi_dsi_device *dsi;
struct device *dev = pdata->dev;
const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge", const struct mipi_dsi_device_info info = { .type = "ti_sn_bridge",
.channel = 0, .channel = 0,
.node = NULL, .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) { if (flags & DRM_BRIDGE_ATTACH_NO_CONNECTOR) {
DRM_ERROR("Fix bridge driver to make connector optional!"); 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) if (ret < 0)
goto err_conn_init; 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: */ /* We never want the next bridge to *also* create a connector: */
flags |= DRM_BRIDGE_ATTACH_NO_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, ret = drm_bridge_attach(bridge->encoder, pdata->next_bridge,
&pdata->bridge, flags); &pdata->bridge, flags);
if (ret < 0) if (ret < 0)
goto err_dsi_detach; goto err_dsi_host;
return 0; return 0;
err_dsi_detach:
mipi_dsi_detach(dsi);
err_dsi_attach:
mipi_dsi_device_unregister(dsi);
err_dsi_host: err_dsi_host:
drm_connector_cleanup(&pdata->connector); drm_connector_cleanup(&pdata->connector);
err_conn_init: err_conn_init:
@ -1227,7 +1266,15 @@ static int ti_sn_bridge_probe(struct auxiliary_device *adev,
drm_bridge_add(&pdata->bridge); drm_bridge_add(&pdata->bridge);
ret = ti_sn_attach_host(pdata);
if (ret)
goto err_remove_bridge;
return 0; return 0;
err_remove_bridge:
drm_bridge_remove(&pdata->bridge);
return ret;
} }
static void ti_sn_bridge_remove(struct auxiliary_device *adev) 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) if (!pdata)
return; return;
if (pdata->dsi) {
mipi_dsi_detach(pdata->dsi);
mipi_dsi_device_unregister(pdata->dsi);
}
drm_bridge_remove(&pdata->bridge); drm_bridge_remove(&pdata->bridge);
of_node_put(pdata->host_node); 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, .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 * GPIO Controller
*/ */
#if defined(CONFIG_OF_GPIO) #if defined(CONFIG_OF_GPIO)
static int tn_sn_bridge_of_xlate(struct gpio_chip *chip, 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; 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) 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 */ /* We won't keep pm_runtime if we're input, so switch there on free */
ti_sn_bridge_gpio_direction_input(chip, offset); 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] = { 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.owner = THIS_MODULE;
pdata->gchip.of_xlate = tn_sn_bridge_of_xlate; pdata->gchip.of_xlate = tn_sn_bridge_of_xlate;
pdata->gchip.of_gpio_n_cells = 2; 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.free = ti_sn_bridge_gpio_free;
pdata->gchip.get_direction = ti_sn_bridge_gpio_get_direction; pdata->gchip.get_direction = ti_sn_bridge_gpio_get_direction;
pdata->gchip.direction_input = ti_sn_bridge_gpio_direction_input; 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. * ordering. The bridge wants the panel to be there when it probes.
* The panel wants its HPD GPIO (provided by sn65dsi86 on some boards) * The panel wants its HPD GPIO (provided by sn65dsi86 on some boards)
* when it probes. The panel and maybe backlight might want the DDC * 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 * bus or the pwm_chip. Having sub-devices allows the some sub devices
* problem. Having sub-devices allows the some sub devices to finish * to finish probing even if others return -EPROBE_DEFER and gets us
* probing even if others return -EPROBE_DEFER and gets us around the * around the problems.
* problems.
*/ */
if (IS_ENABLED(CONFIG_OF_GPIO)) { if (IS_ENABLED(CONFIG_OF_GPIO)) {
@ -1558,6 +1893,12 @@ static int ti_sn65dsi86_probe(struct i2c_client *client,
return ret; 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 * 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 * 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) if (ret)
goto err_main_was_registered; goto err_main_was_registered;
ret = auxiliary_driver_register(&ti_sn_aux_driver); ret = ti_sn_pwm_register();
if (ret) if (ret)
goto err_gpio_was_registered; 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); ret = auxiliary_driver_register(&ti_sn_bridge_driver);
if (ret) if (ret)
goto err_aux_was_registered; goto err_aux_was_registered;
@ -1613,6 +1958,8 @@ static int __init ti_sn65dsi86_init(void)
err_aux_was_registered: err_aux_was_registered:
auxiliary_driver_unregister(&ti_sn_aux_driver); auxiliary_driver_unregister(&ti_sn_aux_driver);
err_pwm_was_registered:
ti_sn_pwm_unregister();
err_gpio_was_registered: err_gpio_was_registered:
ti_sn_gpio_unregister(); ti_sn_gpio_unregister();
err_main_was_registered: 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_bridge_driver);
auxiliary_driver_unregister(&ti_sn_aux_driver); auxiliary_driver_unregister(&ti_sn_aux_driver);
ti_sn_pwm_unregister();
ti_sn_gpio_unregister(); ti_sn_gpio_unregister();
i2c_del_driver(&ti_sn65dsi86_driver); i2c_del_driver(&ti_sn65dsi86_driver);
} }

View File

@ -797,6 +797,8 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
fence_ptr); fence_ptr);
} else if (property == connector->max_bpc_property) { } else if (property == connector->max_bpc_property) {
state->max_requested_bpc = val; 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) { } else if (connector->funcs->atomic_set_property) {
return connector->funcs->atomic_set_property(connector, return connector->funcs->atomic_set_property(connector,
state, property, val); state, property, val);
@ -874,6 +876,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
*val = 0; *val = 0;
} else if (property == connector->max_bpc_property) { } else if (property == connector->max_bpc_property) {
*val = state->max_requested_bpc; *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) { } else if (connector->funcs->atomic_get_property) {
return connector->funcs->atomic_get_property(connector, return connector->funcs->atomic_get_property(connector,
state, property, val); state, property, val);

View File

@ -28,6 +28,7 @@
#include <drm/drm_print.h> #include <drm/drm_print.h>
#include <drm/drm_drv.h> #include <drm/drm_drv.h>
#include <drm/drm_file.h> #include <drm/drm_file.h>
#include <drm/drm_privacy_screen_consumer.h>
#include <drm/drm_sysfs.h> #include <drm/drm_sysfs.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
@ -462,6 +463,11 @@ void drm_connector_cleanup(struct drm_connector *connector)
DRM_CONNECTOR_REGISTERED)) DRM_CONNECTOR_REGISTERED))
drm_connector_unregister(connector); drm_connector_unregister(connector);
if (connector->privacy_screen) {
drm_privacy_screen_put(connector->privacy_screen);
connector->privacy_screen = NULL;
}
if (connector->tile_group) { if (connector->tile_group) {
drm_mode_put_tile_group(dev, connector->tile_group); drm_mode_put_tile_group(dev, connector->tile_group);
connector->tile_group = NULL; connector->tile_group = NULL;
@ -541,7 +547,11 @@ int drm_connector_register(struct drm_connector *connector)
connector->registration_state = DRM_CONNECTOR_REGISTERED; connector->registration_state = DRM_CONNECTOR_REGISTERED;
/* Let userspace know we have a new connector */ /* 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); mutex_lock(&connector_list_lock);
list_add_tail(&connector->global_connector_list_entry, &connector_list); 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); list_del_init(&connector->global_connector_list_entry);
mutex_unlock(&connector_list_lock); 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) if (connector->funcs->early_unregister)
connector->funcs->early_unregister(connector); 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" * For DVI-I and TVout there is also a matching property "select subconnector"
* allowing to switch between signal types. * allowing to switch between signal types.
* DP subconnector corresponds to a downstream port. * 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) 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); 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, int drm_connector_set_obj_prop(struct drm_mode_object *obj,
struct drm_property *property, struct drm_property *property,
uint64_t value) uint64_t value)

View File

@ -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); 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, void drm_dp_link_train_clock_recovery_delay(const struct drm_dp_aux *aux,
const u8 dpcd[DP_RECEIVER_CAP_SIZE]) const u8 dpcd[DP_RECEIVER_CAP_SIZE])
{ {
unsigned long rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] & u8 rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] &
DP_TRAINING_AUX_RD_MASK; DP_TRAINING_AUX_RD_MASK;
int delay_us;
if (rd_interval > 4) if (dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14)
drm_dbg_kms(aux->drm_dev, "%s: AUX interval %lu, out of range (max 4)\n", delay_us = 100;
aux->name, rd_interval);
if (rd_interval == 0 || dpcd[DP_DPCD_REV] >= DP_DPCD_REV_14)
rd_interval = 100;
else 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); EXPORT_SYMBOL(drm_dp_link_train_clock_recovery_delay);
static void __drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux, 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) int delay_us = __8b10b_channel_eq_delay_us(aux, rd_interval);
drm_dbg_kms(aux->drm_dev, "%s: AUX interval %lu, out of range (max 4)\n",
aux->name, rd_interval);
if (rd_interval == 0) usleep_range(delay_us, delay_us * 2);
rd_interval = 400;
else
rd_interval *= 4 * USEC_PER_MSEC;
usleep_range(rd_interval, rd_interval * 2);
} }
void drm_dp_link_train_channel_eq_delay(const struct drm_dp_aux *aux, 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; int ret;
u8 buf[2] = { 0 }; u8 buf[2] = { 0 };
/* The panel uses the PWM for controlling brightness levels */
if (!bl->aux_set)
return 0;
if (bl->lsb_reg_used) { if (bl->lsb_reg_used) {
buf[0] = (level & 0xff00) >> 8; buf[0] = (level & 0xff00) >> 8;
buf[1] = (level & 0x00ff); 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; int ret;
u8 buf; 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) if (!bl->aux_enable)
return 0; 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 * restoring any important backlight state such as the given backlight level, the brightness byte
* count, backlight frequency, etc. * count, backlight frequency, etc.
* *
* Note that certain panels, while supporting brightness level controls over DPCD, may not support * Note that certain panels do not support being enabled or disabled via DPCD, but instead require
* having their backlights enabled via the standard %DP_EDP_DISPLAY_CONTROL_REGISTER. On such panels * that the driver handle enabling/disabling the panel through implementation-specific means using
* &drm_edp_backlight_info.aux_enable will be set to %false, this function will skip the step of * the EDP_BL_PWR GPIO. For such panels, &drm_edp_backlight_info.aux_enable will be set to %false,
* programming the %DP_EDP_DISPLAY_CONTROL_REGISTER, and the driver must perform the required * this function becomes a no-op, and the driver is expected to handle powering the panel on using
* implementation specific step for enabling the backlight after calling this function. * the EDP_BL_PWR GPIO.
* *
* Returns: %0 on success, negative error code on failure. * 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) const u16 level)
{ {
int ret; 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 (bl->aux_set)
if (ret != 1) { dpcd_buf = DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD;
drm_dbg_kms(aux->drm_dev, else
"%s: Failed to read backlight mode: %d\n", aux->name, ret); dpcd_buf = DP_EDP_BACKLIGHT_CONTROL_MODE_PWM;
return ret < 0 ? ret : -EIO;
}
new_dpcd_buf = dpcd_buf; if (bl->pwmgen_bit_count) {
ret = drm_dp_dpcd_writeb(aux, DP_EDP_PWMGEN_BIT_COUNT, bl->pwmgen_bit_count);
if ((dpcd_buf & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK) != DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) { if (ret != 1)
new_dpcd_buf &= ~DP_EDP_BACKLIGHT_CONTROL_MODE_MASK; drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux pwmgen bit count: %d\n",
new_dpcd_buf |= DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD; 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) { 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", "%s: Failed to write aux backlight frequency: %d\n",
aux->name, ret); aux->name, ret);
else 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, dpcd_buf);
ret = drm_dp_dpcd_writeb(aux, DP_EDP_BACKLIGHT_MODE_SET_REGISTER, new_dpcd_buf); if (ret != 1) {
if (ret != 1) { drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux backlight mode: %d\n",
drm_dbg_kms(aux->drm_dev, "%s: Failed to write aux backlight mode: %d\n", aux->name, ret);
aux->name, ret); return ret < 0 ? ret : -EIO;
return ret < 0 ? ret : -EIO;
}
} }
ret = drm_edp_backlight_set_level(aux, bl, level); 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 * @aux: The DP AUX channel to use
* @bl: Backlight capability info from drm_edp_backlight_init() * @bl: Backlight capability info from drm_edp_backlight_init()
* *
* This function handles disabling DPCD backlight controls on a panel over AUX. Note that some * This function handles disabling DPCD backlight controls on a panel over AUX.
* 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 * Note that certain panels do not support being enabled or disabled via DPCD, but instead require
* %false, this function will become a no-op (and we will skip updating * that the driver handle enabling/disabling the panel through implementation-specific means using
* %DP_EDP_DISPLAY_CONTROL_REGISTER), and the driver must take care to perform it's own * the EDP_BL_PWR GPIO. For such panels, &drm_edp_backlight_info.aux_enable will be set to %false,
* implementation specific step for disabling the backlight. * 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. * 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; int ret;
u8 pn, pn_min, pn_max; u8 pn, pn_min, pn_max;
if (!bl->aux_set)
return 0;
ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT, &pn); ret = drm_dp_dpcd_readb(aux, DP_EDP_PWMGEN_BIT_COUNT, &pn);
if (ret != 1) { if (ret != 1) {
drm_dbg_kms(aux->drm_dev, "%s: Failed to read pwmgen bit count cap: %d\n", 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 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) u8 *current_mode)
{ {
int ret; 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); *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) { if (*current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) {
int size = 1 + bl->lsb_reg_used; 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 * @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 * @driver_pwm_freq_hz: Optional PWM frequency from the driver in hz
* @edp_dpcd: A cached copy of the eDP DPCD * @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 * @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, * 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) if (edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP)
bl->aux_enable = true; 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) if (edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT)
bl->lsb_reg_used = true; 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); ret = drm_edp_backlight_probe_max(aux, bl, driver_pwm_freq_hz, edp_dpcd);
if (ret < 0) if (ret < 0)
return ret; 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) if (ret < 0)
return ret; return ret;
*current_level = ret; *current_level = ret;
drm_dbg_kms(aux->drm_dev, drm_dbg_kms(aux->drm_dev,
"%s: Found backlight level=%d/%d pwm_freq_pre_divider=%d mode=%x\n", "%s: Found backlight: aux_set=%d aux_enable=%d mode=%d\n",
aux->name, *current_level, bl->max, bl->pwm_freq_pre_divider, *current_mode); aux->name, bl->aux_set, bl->aux_enable, *current_mode);
drm_dbg_kms(aux->drm_dev, if (bl->aux_set) {
"%s: Backlight caps: pwmgen_bit_count=%d lsb_reg_used=%d aux_enable=%d\n", drm_dbg_kms(aux->drm_dev,
aux->name, bl->pwmgen_bit_count, bl->lsb_reg_used, bl->aux_enable); "%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; return 0;
} }
EXPORT_SYMBOL(drm_edp_backlight_init); EXPORT_SYMBOL(drm_edp_backlight_init);

View File

@ -43,6 +43,7 @@
#include <drm/drm_managed.h> #include <drm/drm_managed.h>
#include <drm/drm_mode_object.h> #include <drm/drm_mode_object.h>
#include <drm/drm_print.h> #include <drm/drm_print.h>
#include <drm/drm_privacy_screen_machine.h>
#include "drm_crtc_internal.h" #include "drm_crtc_internal.h"
#include "drm_internal.h" #include "drm_internal.h"
@ -581,6 +582,7 @@ static int drm_dev_init(struct drm_device *dev,
const struct drm_driver *driver, const struct drm_driver *driver,
struct device *parent) struct device *parent)
{ {
struct inode *inode;
int ret; int ret;
if (!drm_core_init_complete) { if (!drm_core_init_complete) {
@ -617,13 +619,15 @@ static int drm_dev_init(struct drm_device *dev,
if (ret) if (ret)
return ret; return ret;
dev->anon_inode = drm_fs_inode_new(); inode = drm_fs_inode_new();
if (IS_ERR(dev->anon_inode)) { if (IS_ERR(inode)) {
ret = PTR_ERR(dev->anon_inode); ret = PTR_ERR(inode);
DRM_ERROR("Cannot allocate anonymous inode: %d\n", ret); DRM_ERROR("Cannot allocate anonymous inode: %d\n", ret);
goto err; goto err;
} }
dev->anon_inode = inode;
if (drm_core_check_feature(dev, DRIVER_RENDER)) { if (drm_core_check_feature(dev, DRIVER_RENDER)) {
ret = drm_minor_alloc(dev, DRM_MINOR_RENDER); ret = drm_minor_alloc(dev, DRM_MINOR_RENDER);
if (ret) if (ret)
@ -1029,6 +1033,7 @@ static const struct file_operations drm_stub_fops = {
static void drm_core_exit(void) static void drm_core_exit(void)
{ {
drm_privacy_screen_lookup_exit();
unregister_chrdev(DRM_MAJOR, "drm"); unregister_chrdev(DRM_MAJOR, "drm");
debugfs_remove(drm_debugfs_root); debugfs_remove(drm_debugfs_root);
drm_sysfs_destroy(); drm_sysfs_destroy();
@ -1056,6 +1061,8 @@ static int __init drm_core_init(void)
if (ret < 0) if (ret < 0)
goto error; goto error;
drm_privacy_screen_lookup_init();
drm_core_init_complete = true; drm_core_init_complete = true;
DRM_DEBUG("Initialized\n"); DRM_DEBUG("Initialized\n");

View File

@ -2338,7 +2338,7 @@ static int drm_fb_helper_generic_probe(struct drm_fb_helper *fb_helper,
return PTR_ERR(fbi); return PTR_ERR(fbi);
fbi->fbops = &drm_fbdev_fb_ops; 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; fbi->fix.smem_len = fbi->screen_size;
drm_fb_helper_fill_info(fbi, fb_helper, sizes); drm_fb_helper_fill_info(fbi, fb_helper, sizes);

View File

@ -17,71 +17,91 @@
#include <drm/drm_fourcc.h> #include <drm/drm_fourcc.h>
#include <drm/drm_rect.h> #include <drm/drm_rect.h>
static unsigned int clip_offset(struct drm_rect *clip, static unsigned int clip_offset(const struct drm_rect *clip, unsigned int pitch, unsigned int cpp)
unsigned int pitch, unsigned int cpp)
{ {
return clip->y1 * pitch + clip->x1 * 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 * drm_fb_memcpy - Copy clip buffer
* @dst: Destination buffer * @dst: Destination buffer
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
* @vaddr: Source buffer * @vaddr: Source buffer
* @fb: DRM framebuffer * @fb: DRM framebuffer
* @clip: Clip rectangle area to copy * @clip: Clip rectangle area to copy
* *
* This function does not apply clipping on dst, i.e. the destination * 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, void drm_fb_memcpy(void *dst, unsigned int dst_pitch, const void *vaddr,
struct drm_rect *clip) const struct drm_framebuffer *fb, const struct drm_rect *clip)
{ {
unsigned int cpp = fb->format->cpp[0]; unsigned int cpp = fb->format->cpp[0];
size_t len = (clip->x2 - clip->x1) * cpp; size_t len = (clip->x2 - clip->x1) * cpp;
unsigned int y, lines = clip->y2 - clip->y1; unsigned int y, lines = clip->y2 - clip->y1;
if (!dst_pitch)
dst_pitch = len;
vaddr += clip_offset(clip, fb->pitches[0], cpp); vaddr += clip_offset(clip, fb->pitches[0], cpp);
for (y = 0; y < lines; y++) { for (y = 0; y < lines; y++) {
memcpy(dst, vaddr, len); memcpy(dst, vaddr, len);
vaddr += fb->pitches[0]; vaddr += fb->pitches[0];
dst += len; dst += dst_pitch;
} }
} }
EXPORT_SYMBOL(drm_fb_memcpy); EXPORT_SYMBOL(drm_fb_memcpy);
/** /**
* drm_fb_memcpy_dstclip - Copy clip buffer * drm_fb_memcpy_toio - Copy clip buffer
* @dst: Destination buffer (iomem) * @dst: Destination buffer (iomem)
* @dst_pitch: Number of bytes between two consecutive scanlines within dst * @dst_pitch: Number of bytes between two consecutive scanlines within dst
* @vaddr: Source buffer * @vaddr: Source buffer
* @fb: DRM framebuffer * @fb: DRM framebuffer
* @clip: Clip rectangle area to copy * @clip: Clip rectangle area to copy
* *
* This function applies clipping on dst, i.e. the destination is a * This function does not apply clipping on dst, i.e. the destination
* full (iomem) framebuffer but only the clip rect content is copied over. * is at the top-left corner.
*/ */
void drm_fb_memcpy_dstclip(void __iomem *dst, unsigned int dst_pitch, void drm_fb_memcpy_toio(void __iomem *dst, unsigned int dst_pitch, const void *vaddr,
void *vaddr, struct drm_framebuffer *fb, const struct drm_framebuffer *fb, const struct drm_rect *clip)
struct drm_rect *clip)
{ {
unsigned int cpp = fb->format->cpp[0]; unsigned int cpp = fb->format->cpp[0];
unsigned int offset = clip_offset(clip, dst_pitch, cpp);
size_t len = (clip->x2 - clip->x1) * cpp; size_t len = (clip->x2 - clip->x1) * cpp;
unsigned int y, lines = clip->y2 - clip->y1; unsigned int y, lines = clip->y2 - clip->y1;
vaddr += offset; if (!dst_pitch)
dst += offset; dst_pitch = len;
vaddr += clip_offset(clip, fb->pitches[0], cpp);
for (y = 0; y < lines; y++) { for (y = 0; y < lines; y++) {
memcpy_toio(dst, vaddr, len); memcpy_toio(dst, vaddr, len);
vaddr += fb->pitches[0]; vaddr += fb->pitches[0];
dst += dst_pitch; dst += dst_pitch;
} }
} }
EXPORT_SYMBOL(drm_fb_memcpy_dstclip); EXPORT_SYMBOL(drm_fb_memcpy_toio);
/** /**
* drm_fb_swab - Swap bytes into clip buffer * drm_fb_swab - Swap bytes into clip buffer
* @dst: Destination buffer * @dst: Destination buffer
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
* @src: Source buffer * @src: Source buffer
* @fb: DRM framebuffer * @fb: DRM framebuffer
* @clip: Clip rectangle area to copy * @clip: Clip rectangle area to copy
@ -91,21 +111,27 @@ EXPORT_SYMBOL(drm_fb_memcpy_dstclip);
* time to speed up slow uncached reads. * time to speed up slow uncached reads.
* *
* This function does not apply clipping on dst, i.e. the destination * 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, void drm_fb_swab(void *dst, unsigned int dst_pitch, const void *src,
struct drm_rect *clip, bool cached) const struct drm_framebuffer *fb, const struct drm_rect *clip,
bool cached)
{ {
u8 cpp = fb->format->cpp[0]; u8 cpp = fb->format->cpp[0];
size_t len = drm_rect_width(clip) * cpp; size_t len = drm_rect_width(clip) * cpp;
u16 *src16, *dst16 = dst; const u16 *src16;
u32 *src32, *dst32 = dst; const u32 *src32;
u16 *dst16;
u32 *dst32;
unsigned int x, y; unsigned int x, y;
void *buf = NULL; void *buf = NULL;
if (WARN_ON_ONCE(cpp != 2 && cpp != 4)) if (WARN_ON_ONCE(cpp != 2 && cpp != 4))
return; return;
if (!dst_pitch)
dst_pitch = len;
if (!cached) if (!cached)
buf = kmalloc(len, GFP_KERNEL); buf = kmalloc(len, GFP_KERNEL);
@ -121,6 +147,9 @@ void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
src32 = src; src32 = src;
} }
dst16 = dst;
dst32 = dst;
for (x = clip->x1; x < clip->x2; x++) { for (x = clip->x1; x < clip->x2; x++) {
if (cpp == 4) if (cpp == 4)
*dst32++ = swab32(*src32++); *dst32++ = swab32(*src32++);
@ -129,13 +158,14 @@ void drm_fb_swab(void *dst, void *src, struct drm_framebuffer *fb,
} }
src += fb->pitches[0]; src += fb->pitches[0];
dst += dst_pitch;
} }
kfree(buf); kfree(buf);
} }
EXPORT_SYMBOL(drm_fb_swab); 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; unsigned int x;
u32 pix; 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 * drm_fb_xrgb8888_to_rgb332 - Convert XRGB8888 to RGB332 clip buffer
* @dst: RGB332 destination buffer * @dst: RGB332 destination buffer
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
* @src: XRGB8888 source buffer * @src: XRGB8888 source buffer
* @fb: DRM framebuffer * @fb: DRM framebuffer
* @clip: Clip rectangle area to copy * @clip: Clip rectangle area to copy
* *
* Drivers can use this function for RGB332 devices that don't natively support XRGB8888. * 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, void drm_fb_xrgb8888_to_rgb332(void *dst, unsigned int dst_pitch, const void *src,
struct drm_rect *clip) const struct drm_framebuffer *fb, const struct drm_rect *clip)
{ {
size_t width = drm_rect_width(clip); size_t width = drm_rect_width(clip);
size_t src_len = width * sizeof(u32); size_t src_len = width * sizeof(u32);
unsigned int y; unsigned int y;
void *sbuf; void *sbuf;
if (!dst_pitch)
dst_pitch = width;
/* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */ /* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */
sbuf = kmalloc(src_len, GFP_KERNEL); sbuf = kmalloc(src_len, GFP_KERNEL);
if (!sbuf) 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); memcpy(sbuf, src, src_len);
drm_fb_xrgb8888_to_rgb332_line(dst, sbuf, width); drm_fb_xrgb8888_to_rgb332_line(dst, sbuf, width);
src += fb->pitches[0]; src += fb->pitches[0];
dst += width; dst += dst_pitch;
} }
kfree(sbuf); kfree(sbuf);
} }
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb332); 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, unsigned int pixels,
bool swab) 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 * drm_fb_xrgb8888_to_rgb565 - Convert XRGB8888 to RGB565 clip buffer
* @dst: RGB565 destination buffer * @dst: RGB565 destination buffer
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
* @vaddr: XRGB8888 source buffer * @vaddr: XRGB8888 source buffer
* @fb: DRM framebuffer * @fb: DRM framebuffer
* @clip: Clip rectangle area to copy * @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 * Drivers can use this function for RGB565 devices that don't natively
* support XRGB8888. * 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, void drm_fb_xrgb8888_to_rgb565(void *dst, unsigned int dst_pitch, const void *vaddr,
struct drm_framebuffer *fb, const struct drm_framebuffer *fb, const struct drm_rect *clip,
struct drm_rect *clip, bool swab) bool swab)
{ {
size_t linepixels = clip->x2 - clip->x1; size_t linepixels = clip->x2 - clip->x1;
size_t src_len = linepixels * sizeof(u32); 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; unsigned y, lines = clip->y2 - clip->y1;
void *sbuf; void *sbuf;
if (!dst_pitch)
dst_pitch = dst_len;
/* /*
* The cma memory is write-combined so reads are uncached. * The cma memory is write-combined so reads are uncached.
* Speed up by fetching one line at a time. * 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); memcpy(sbuf, vaddr, src_len);
drm_fb_xrgb8888_to_rgb565_line(dst, sbuf, linepixels, swab); drm_fb_xrgb8888_to_rgb565_line(dst, sbuf, linepixels, swab);
vaddr += fb->pitches[0]; vaddr += fb->pitches[0];
dst += dst_len; dst += dst_pitch;
} }
kfree(sbuf); kfree(sbuf);
@ -248,9 +280,9 @@ void drm_fb_xrgb8888_to_rgb565(void *dst, void *vaddr,
EXPORT_SYMBOL(drm_fb_xrgb8888_to_rgb565); 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: RGB565 destination buffer (iomem)
* @dst_pitch: destination buffer pitch * @dst_pitch: Number of bytes between two consecutive scanlines within dst
* @vaddr: XRGB8888 source buffer * @vaddr: XRGB8888 source buffer
* @fb: DRM framebuffer * @fb: DRM framebuffer
* @clip: Clip rectangle area to copy * @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 * Drivers can use this function for RGB565 devices that don't natively
* support XRGB8888. * 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 drm_fb_xrgb8888_to_rgb565_toio(void __iomem *dst, unsigned int dst_pitch,
void *vaddr, struct drm_framebuffer *fb, const void *vaddr, const struct drm_framebuffer *fb,
struct drm_rect *clip, bool swab) const struct drm_rect *clip, bool swab)
{ {
size_t linepixels = clip->x2 - clip->x1; size_t linepixels = clip->x2 - clip->x1;
size_t dst_len = linepixels * sizeof(u16); size_t dst_len = linepixels * sizeof(u16);
unsigned y, lines = clip->y2 - clip->y1; unsigned y, lines = clip->y2 - clip->y1;
void *dbuf; void *dbuf;
if (!dst_pitch)
dst_pitch = dst_len;
dbuf = kmalloc(dst_len, GFP_KERNEL); dbuf = kmalloc(dst_len, GFP_KERNEL);
if (!dbuf) if (!dbuf)
return; return;
vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
dst += clip_offset(clip, dst_pitch, sizeof(u16));
for (y = 0; y < lines; y++) { for (y = 0; y < lines; y++) {
drm_fb_xrgb8888_to_rgb565_line(dbuf, vaddr, linepixels, swab); drm_fb_xrgb8888_to_rgb565_line(dbuf, vaddr, linepixels, swab);
memcpy_toio(dst, dbuf, dst_len); memcpy_toio(dst, dbuf, dst_len);
vaddr += fb->pitches[0]; vaddr += fb->pitches[0];
dst += dst_len; dst += dst_pitch;
} }
kfree(dbuf); 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 pixels)
{ {
unsigned int x; 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 * drm_fb_xrgb8888_to_rgb888 - Convert XRGB8888 to RGB888 clip buffer
* @dst: RGB888 destination buffer * @dst: RGB888 destination buffer
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
* @src: XRGB8888 source buffer * @src: XRGB8888 source buffer
* @fb: DRM framebuffer * @fb: DRM framebuffer
* @clip: Clip rectangle area to copy * @clip: Clip rectangle area to copy
* *
* Drivers can use this function for RGB888 devices that don't natively * Drivers can use this function for RGB888 devices that don't natively
* support XRGB8888. * 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, void drm_fb_xrgb8888_to_rgb888(void *dst, unsigned int dst_pitch, const void *src,
struct drm_rect *clip) const struct drm_framebuffer *fb, const struct drm_rect *clip)
{ {
size_t width = drm_rect_width(clip); size_t width = drm_rect_width(clip);
size_t src_len = width * sizeof(u32); size_t src_len = width * sizeof(u32);
unsigned int y; unsigned int y;
void *sbuf; 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) */ /* Use a buffer to speed up access on buffers with uncached read mapping (i.e. WC) */
sbuf = kmalloc(src_len, GFP_KERNEL); sbuf = kmalloc(src_len, GFP_KERNEL);
if (!sbuf) 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); memcpy(sbuf, src, src_len);
drm_fb_xrgb8888_to_rgb888_line(dst, sbuf, width); drm_fb_xrgb8888_to_rgb888_line(dst, sbuf, width);
src += fb->pitches[0]; src += fb->pitches[0];
dst += width * 3; dst += dst_pitch;
} }
kfree(sbuf); 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); 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: RGB565 destination buffer (iomem)
* @dst_pitch: destination buffer pitch * @dst_pitch: Number of bytes between two consecutive scanlines within dst
* @vaddr: XRGB8888 source buffer * @vaddr: XRGB8888 source buffer
* @fb: DRM framebuffer * @fb: DRM framebuffer
* @clip: Clip rectangle area to copy * @clip: Clip rectangle area to copy
* *
* Drivers can use this function for RGB888 devices that don't natively * Drivers can use this function for RGB888 devices that don't natively
* support XRGB8888. * 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 drm_fb_xrgb8888_to_rgb888_toio(void __iomem *dst, unsigned int dst_pitch,
void *vaddr, struct drm_framebuffer *fb, const void *vaddr, const struct drm_framebuffer *fb,
struct drm_rect *clip) const struct drm_rect *clip)
{ {
size_t linepixels = clip->x2 - clip->x1; size_t linepixels = clip->x2 - clip->x1;
size_t dst_len = linepixels * 3; size_t dst_len = linepixels * 3;
unsigned y, lines = clip->y2 - clip->y1; unsigned y, lines = clip->y2 - clip->y1;
void *dbuf; void *dbuf;
if (!dst_pitch)
dst_pitch = dst_len;
dbuf = kmalloc(dst_len, GFP_KERNEL); dbuf = kmalloc(dst_len, GFP_KERNEL);
if (!dbuf) if (!dbuf)
return; return;
vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32)); vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
dst += clip_offset(clip, dst_pitch, sizeof(u16));
for (y = 0; y < lines; y++) { for (y = 0; y < lines; y++) {
drm_fb_xrgb8888_to_rgb888_line(dbuf, vaddr, linepixels); drm_fb_xrgb8888_to_rgb888_line(dbuf, vaddr, linepixels);
memcpy_toio(dst, dbuf, dst_len); memcpy_toio(dst, dbuf, dst_len);
vaddr += fb->pitches[0]; vaddr += fb->pitches[0];
dst += dst_len; dst += dst_pitch;
} }
kfree(dbuf); 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 * drm_fb_xrgb8888_to_gray8 - Convert XRGB8888 to grayscale
* @dst: 8-bit grayscale destination buffer * @dst: 8-bit grayscale destination buffer
* @dst_pitch: Number of bytes between two consecutive scanlines within dst
* @vaddr: XRGB8888 source buffer * @vaddr: XRGB8888 source buffer
* @fb: DRM framebuffer * @fb: DRM framebuffer
* @clip: Clip rectangle area to copy * @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. * 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, void drm_fb_xrgb8888_to_gray8(void *dst, unsigned int dst_pitch, const void *vaddr,
struct drm_rect *clip) const struct drm_framebuffer *fb, const struct drm_rect *clip)
{ {
unsigned int len = (clip->x2 - clip->x1) * sizeof(u32); unsigned int len = (clip->x2 - clip->x1) * sizeof(u32);
unsigned int x, y; unsigned int x, y;
void *buf; void *buf;
u32 *src; u8 *dst8;
u32 *src32;
if (WARN_ON(fb->format->format != DRM_FORMAT_XRGB8888)) if (WARN_ON(fb->format->format != DRM_FORMAT_XRGB8888))
return; return;
if (!dst_pitch)
dst_pitch = drm_rect_width(clip);
/* /*
* The cma memory is write-combined so reads are uncached. * The cma memory is write-combined so reads are uncached.
* Speed up by fetching one line at a time. * 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) if (!buf)
return; return;
vaddr += clip_offset(clip, fb->pitches[0], sizeof(u32));
for (y = clip->y1; y < clip->y2; y++) { for (y = clip->y1; y < clip->y2; y++) {
src = vaddr + (y * fb->pitches[0]); dst8 = dst;
src += clip->x1; src32 = memcpy(buf, vaddr, len);
memcpy(buf, src, len);
src = buf;
for (x = clip->x1; x < clip->x2; x++) { for (x = clip->x1; x < clip->x2; x++) {
u8 r = (*src & 0x00ff0000) >> 16; u8 r = (*src32 & 0x00ff0000) >> 16;
u8 g = (*src & 0x0000ff00) >> 8; u8 g = (*src32 & 0x0000ff00) >> 8;
u8 b = *src & 0x000000ff; u8 b = *src32 & 0x000000ff;
/* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */ /* ITU BT.601: Y = 0.299 R + 0.587 G + 0.114 B */
*dst++ = (3 * r + 6 * g + b) / 10; *dst8++ = (3 * r + 6 * g + b) / 10;
src++; src32++;
} }
vaddr += fb->pitches[0];
dst += dst_pitch;
} }
kfree(buf); 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); 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: The display memory to copy to
* @dst_pitch: Number of bytes between two consecutive scanlines within dst * @dst_pitch: Number of bytes between two consecutive scanlines within dst
* @dst_format: FOURCC code of the display's color format * @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 * formats of the display and the framebuffer mismatch, the blit function
* will attempt to convert between them. * will attempt to convert between them.
* *
* Use drm_fb_blit_dstclip() to copy the full framebuffer.
*
* Returns: * Returns:
* 0 on success, or * 0 on success, or
* -EINVAL if the color-format conversion failed, or * -EINVAL if the color-format conversion failed, or
* a negative error code otherwise. * a negative error code otherwise.
*/ */
int drm_fb_blit_rect_dstclip(void __iomem *dst, unsigned int dst_pitch, int drm_fb_blit_toio(void __iomem *dst, unsigned int dst_pitch, uint32_t dst_format,
uint32_t dst_format, void *vmap, const void *vmap, const struct drm_framebuffer *fb,
struct drm_framebuffer *fb, const struct drm_rect *clip)
struct drm_rect *clip)
{ {
uint32_t fb_format = fb->format->format; 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; dst_format = DRM_FORMAT_XRGB8888;
if (dst_format == fb_format) { 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; return 0;
} else if (dst_format == DRM_FORMAT_RGB565) { } else if (dst_format == DRM_FORMAT_RGB565) {
if (fb_format == DRM_FORMAT_XRGB8888) { if (fb_format == DRM_FORMAT_XRGB8888) {
drm_fb_xrgb8888_to_rgb565_dstclip(dst, dst_pitch, drm_fb_xrgb8888_to_rgb565_toio(dst, dst_pitch, vmap, fb, clip, false);
vmap, fb, clip,
false);
return 0; return 0;
} }
} else if (dst_format == DRM_FORMAT_RGB888) { } else if (dst_format == DRM_FORMAT_RGB888) {
if (fb_format == DRM_FORMAT_XRGB8888) { if (fb_format == DRM_FORMAT_XRGB8888) {
drm_fb_xrgb8888_to_rgb888_dstclip(dst, dst_pitch, drm_fb_xrgb8888_to_rgb888_toio(dst, dst_pitch, vmap, fb, clip);
vmap, fb, clip);
return 0; return 0;
} }
} }
return -EINVAL; return -EINVAL;
} }
EXPORT_SYMBOL(drm_fb_blit_rect_dstclip); EXPORT_SYMBOL(drm_fb_blit_toio);
/**
* 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);

View File

@ -143,6 +143,7 @@
*/ */
int drm_gem_plane_helper_prepare_fb(struct drm_plane *plane, struct drm_plane_state *state) 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 drm_gem_object *obj;
struct dma_fence *fence; 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; return 0;
obj = drm_gem_fb_get_obj(state->fb, 0); obj = drm_gem_fb_get_obj(state->fb, 0);
fence = dma_resv_get_excl_unlocked(obj->resv); dma_resv_iter_begin(&cursor, obj->resv, false);
drm_atomic_set_fence_for_plane(state, fence); 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; return 0;
} }
EXPORT_SYMBOL_GPL(drm_gem_plane_helper_prepare_fb); EXPORT_SYMBOL_GPL(drm_gem_plane_helper_prepare_fb);

View File

@ -13,6 +13,7 @@
#include <linux/dma-mapping.h> #include <linux/dma-mapping.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/mm.h> #include <linux/mm.h>
#include <linux/module.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/slab.h> #include <linux/slab.h>
@ -583,3 +584,7 @@ drm_gem_cma_prime_import_sg_table_vmap(struct drm_device *dev,
return obj; return obj;
} }
EXPORT_SYMBOL(drm_gem_cma_prime_import_sg_table_vmap); 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");

View File

@ -5,6 +5,7 @@
#include <linux/dma-buf.h> #include <linux/dma-buf.h>
#include <linux/export.h> #include <linux/export.h>
#include <linux/module.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/shmem_fs.h> #include <linux/shmem_fs.h>
#include <linux/slab.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 * This library provides helpers for GEM objects backed by shmem buffers
* allocated using anonymous pageable memory. * 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 = { static const struct drm_gem_object_funcs drm_gem_shmem_funcs = {
.free = drm_gem_shmem_free_object, .free = drm_gem_shmem_object_free,
.print_info = drm_gem_shmem_print_info, .print_info = drm_gem_shmem_object_print_info,
.pin = drm_gem_shmem_pin, .pin = drm_gem_shmem_object_pin,
.unpin = drm_gem_shmem_unpin, .unpin = drm_gem_shmem_object_unpin,
.get_sg_table = drm_gem_shmem_get_sg_table, .get_sg_table = drm_gem_shmem_object_get_sg_table,
.vmap = drm_gem_shmem_vmap, .vmap = drm_gem_shmem_object_vmap,
.vunmap = drm_gem_shmem_vunmap, .vunmap = drm_gem_shmem_object_vunmap,
.mmap = drm_gem_shmem_mmap, .mmap = drm_gem_shmem_object_mmap,
}; };
static struct drm_gem_shmem_object * 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); EXPORT_SYMBOL_GPL(drm_gem_shmem_create);
/** /**
* drm_gem_shmem_free_object - Free resources associated with a shmem GEM object * drm_gem_shmem_free - Free resources associated with a shmem GEM object
* @obj: GEM object to free * @shmem: shmem GEM object to free
* *
* This function cleans up the GEM object state and frees the memory used to * This function cleans up the GEM object state and frees the memory used to
* store the object itself. It should be used to implement * store the object itself.
* &drm_gem_object_funcs.free.
*/ */
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); 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); mutex_destroy(&shmem->vmap_lock);
kfree(shmem); 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) 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 * 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 * This function makes sure the backing pages are pinned in memory while the
* buffer is exported. It should only be used to implement * buffer is exported.
* &drm_gem_object_funcs.pin.
* *
* Returns: * Returns:
* 0 on success or a negative error code on failure. * 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); WARN_ON(shmem->base.import_attach);
return drm_gem_shmem_get_pages(shmem); 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 * 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 * 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); WARN_ON(shmem->base.import_attach);
drm_gem_shmem_put_pages(shmem); drm_gem_shmem_put_pages(shmem);
@ -341,20 +341,16 @@ err_zero_use:
* store. * store.
* *
* This function makes sure that a contiguous kernel virtual address mapping * This function makes sure that a contiguous kernel virtual address mapping
* exists for the buffer backing the shmem GEM object. * exists for the buffer backing the shmem GEM object. It hides the differences
* * between dma-buf imported and natively allocated objects.
* 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.
* *
* Acquired mappings should be cleaned up by calling drm_gem_shmem_vunmap(). * Acquired mappings should be cleaned up by calling drm_gem_shmem_vunmap().
* *
* Returns: * Returns:
* 0 on success or a negative error code on failure. * 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; int ret;
ret = mutex_lock_interruptible(&shmem->vmap_lock); 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 * drm_gem_shmem_vmap(). The mapping is only removed when the use count drops to
* zero. * zero.
* *
* This function can be used to implement &drm_gem_object_funcs.vmap. But it can * This function hides the differences between dma-buf imported and natively
* also be called by drivers directly, in which case it will hide the * allocated objects.
* 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); mutex_lock(&shmem->vmap_lock);
drm_gem_shmem_vunmap_locked(shmem, map); drm_gem_shmem_vunmap_locked(shmem, map);
mutex_unlock(&shmem->vmap_lock); mutex_unlock(&shmem->vmap_lock);
} }
EXPORT_SYMBOL(drm_gem_shmem_vunmap); 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, drm_gem_shmem_create_with_handle(struct drm_file *file_priv,
struct drm_device *dev, size_t size, struct drm_device *dev, size_t size,
uint32_t *handle) uint32_t *handle)
@ -435,15 +428,12 @@ drm_gem_shmem_create_with_handle(struct drm_file *file_priv,
return shmem; return shmem;
} }
EXPORT_SYMBOL(drm_gem_shmem_create_with_handle);
/* Update madvise status, returns true if not purged, else /* Update madvise status, returns true if not purged, else
* false or -errno. * 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); mutex_lock(&shmem->pages_lock);
if (shmem->madv >= 0) 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); 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_device *dev = obj->dev;
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
WARN_ON(!drm_gem_shmem_is_purgeable(shmem)); 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); sg_free_table(shmem->sgt);
kfree(shmem->sgt); kfree(shmem->sgt);
shmem->sgt = NULL; 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); shmem_truncate_range(file_inode(obj->filp), 0, (loff_t)-1);
invalidate_mapping_pages(file_inode(obj->filp)->i_mapping, invalidate_mapping_pages(file_inode(obj->filp)->i_mapping, 0, (loff_t)-1);
0, (loff_t)-1);
} }
EXPORT_SYMBOL(drm_gem_shmem_purge_locked); 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)) if (!mutex_trylock(&shmem->pages_lock))
return false; return false;
drm_gem_shmem_purge_locked(obj); drm_gem_shmem_purge_locked(shmem);
mutex_unlock(&shmem->pages_lock); mutex_unlock(&shmem->pages_lock);
return true; 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 * 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 * @vma: VMA for the area to be mapped
* *
* This function implements an augmented version of the GEM DRM file mmap * This function implements an augmented version of the GEM DRM file mmap
* operation for shmem objects. Drivers which employ the shmem helpers should * operation for shmem objects.
* use this function as their &drm_gem_object_funcs.mmap handler.
* *
* Returns: * Returns:
* 0 on success or a negative error code on failure. * 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; int ret;
if (obj->import_attach) { 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); return dma_buf_mmap(obj->dma_buf, vma, 0);
} }
shmem = to_drm_gem_shmem_obj(obj);
ret = drm_gem_shmem_get_pages(shmem); ret = drm_gem_shmem_get_pages(shmem);
if (ret) { if (ret) {
drm_gem_vm_close(vma); 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 * drm_gem_shmem_print_info() - Print &drm_gem_shmem_object info for debugfs
* @shmem: shmem GEM object
* @p: DRM printer * @p: DRM printer
* @indent: Tab indentation level * @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, void drm_gem_shmem_print_info(const struct drm_gem_shmem_object *shmem,
const struct drm_gem_object *obj) 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, "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, "vmap_use_count=%u\n", shmem->vmap_use_count);
drm_printf_indent(p, indent, "vaddr=%p\n", shmem->vaddr); 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 * drm_gem_shmem_get_sg_table - Provide a scatter/gather table of pinned
* pages for a shmem GEM object * 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 * This function exports a scatter/gather table suitable for PRIME usage by
* calling the standard DMA mapping API. Drivers should not call this function * calling the standard DMA mapping API.
* directly, instead it should only be used as an implementation for
* &drm_gem_object_funcs.get_sg_table.
* *
* Drivers who need to acquire an scatter/gather table for objects need to call * Drivers who need to acquire an scatter/gather table for objects need to call
* drm_gem_shmem_get_pages_sgt() instead. * drm_gem_shmem_get_pages_sgt() instead.
@ -678,9 +656,9 @@ EXPORT_SYMBOL(drm_gem_shmem_print_info);
* Returns: * Returns:
* A pointer to the scatter/gather table of pinned pages or NULL on failure. * 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); 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 * drm_gem_shmem_get_pages_sgt - Pin pages, dma map them, and return a
* scatter/gather table for a shmem GEM object. * 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 * 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 * 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: * Returns:
* A pointer to the scatter/gather table of pinned pages or errno on failure. * 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; int ret;
struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
struct sg_table *sgt; struct sg_table *sgt;
if (shmem->sgt) if (shmem->sgt)
@ -719,7 +697,7 @@ struct sg_table *drm_gem_shmem_get_pages_sgt(struct drm_gem_object *obj)
if (ret) if (ret)
return ERR_PTR(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)) { if (IS_ERR(sgt)) {
ret = PTR_ERR(sgt); ret = PTR_ERR(sgt);
goto err_put_pages; goto err_put_pages;
@ -776,3 +754,7 @@ drm_gem_shmem_prime_import_sg_table(struct drm_device *dev,
return &shmem->base; return &shmem->base;
} }
EXPORT_SYMBOL_GPL(drm_gem_shmem_prime_import_sg_table); 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");

View File

@ -66,7 +66,6 @@
#include "drm_internal.h" #include "drm_internal.h"
#if IS_ENABLED(CONFIG_DRM_LEGACY)
static int drm_legacy_irq_install(struct drm_device *dev, int irq) static int drm_legacy_irq_install(struct drm_device *dev, int irq)
{ {
int ret; int ret;
@ -203,4 +202,3 @@ int drm_legacy_irq_control(struct drm_device *dev, void *data,
return -EINVAL; return -EINVAL;
} }
} }
#endif

View File

@ -211,12 +211,12 @@ int mipi_dbi_buf_copy(void *dst, struct drm_framebuffer *fb,
switch (fb->format->format) { switch (fb->format->format) {
case DRM_FORMAT_RGB565: case DRM_FORMAT_RGB565:
if (swap) if (swap)
drm_fb_swab(dst, src, fb, clip, !gem->import_attach); drm_fb_swab(dst, 0, src, fb, clip, !gem->import_attach);
else else
drm_fb_memcpy(dst, src, fb, clip); drm_fb_memcpy(dst, 0, src, fb, clip);
break; break;
case DRM_FORMAT_XRGB8888: 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; break;
default: default:
drm_err_once(fb->dev, "Format is not supported: %p4cc\n", drm_err_once(fb->dev, "Format is not supported: %p4cc\n",

View File

@ -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; DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS;
} }
EXPORT_SYMBOL_GPL(drm_of_lvds_get_dual_link_pixel_order); 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);

View File

@ -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);

View File

@ -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 */

View File

@ -604,6 +604,9 @@ EXPORT_SYMBOL(drm_helper_probe_single_connector_modes);
* *
* This function must be called from process context with no mode * This function must be called from process context with no mode
* setting locks held. * 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) 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); 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) static void output_poll_execute(struct work_struct *work)
{ {
struct delayed_work *delayed_work = to_delayed_work(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); mutex_unlock(&dev->mode_config.mutex);
if (changed) { 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", drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Sent hotplug event\n",
connector->base.id, connector->base.id,
connector->name); connector->name);
@ -904,9 +927,9 @@ EXPORT_SYMBOL(drm_connector_helper_hpd_irq_event);
*/ */
bool drm_helper_hpd_irq_event(struct drm_device *dev) 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; struct drm_connector_list_iter conn_iter;
bool changed = false; int changed = 0;
if (!dev->mode_config.poll_enabled) if (!dev->mode_config.poll_enabled)
return false; return false;
@ -918,16 +941,25 @@ bool drm_helper_hpd_irq_event(struct drm_device *dev)
if (!(connector->polled & DRM_CONNECTOR_POLL_HPD)) if (!(connector->polled & DRM_CONNECTOR_POLL_HPD))
continue; continue;
if (check_connector_changed(connector)) if (check_connector_changed(connector)) {
changed = true; if (!first_changed_connector) {
drm_connector_get(connector);
first_changed_connector = connector;
}
changed++;
}
} }
drm_connector_list_iter_end(&conn_iter); drm_connector_list_iter_end(&conn_iter);
mutex_unlock(&dev->mode_config.mutex); 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_kms_helper_hotplug_event(dev);
DRM_DEBUG_KMS("Sent hotplug event\n");
} if (first_changed_connector)
drm_connector_put(first_changed_connector);
return changed; return changed;
} }

View File

@ -409,6 +409,31 @@ void drm_sysfs_hotplug_event(struct drm_device *dev)
} }
EXPORT_SYMBOL(drm_sysfs_hotplug_event); 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 * drm_sysfs_connector_status_event - generate a DRM uevent for connector
* property status change * property status change

View File

@ -424,45 +424,24 @@ int etnaviv_gem_wait_bo(struct etnaviv_gpu *gpu, struct drm_gem_object *obj,
} }
#ifdef CONFIG_DEBUG_FS #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) 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 etnaviv_gem_object *etnaviv_obj = to_etnaviv_bo(obj);
struct dma_resv *robj = obj->resv; 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); unsigned long off = drm_vma_node_start(&obj->vma_node);
int r;
seq_printf(m, "%08x: %c %2d (%2d) %08lx %p %zd\n", seq_printf(m, "%08x: %c %2d (%2d) %08lx %p %zd\n",
etnaviv_obj->flags, is_active(etnaviv_obj) ? 'A' : 'I', etnaviv_obj->flags, is_active(etnaviv_obj) ? 'A' : 'I',
obj->name, kref_read(&obj->refcount), obj->name, kref_read(&obj->refcount),
off, etnaviv_obj->vaddr, obj->size); off, etnaviv_obj->vaddr, obj->size);
rcu_read_lock(); r = dma_resv_lock(robj, NULL);
fobj = dma_resv_shared_list(robj); if (r)
if (fobj) { return;
unsigned int i, shared_count = fobj->shared_count;
for (i = 0; i < shared_count; i++) { dma_resv_describe(robj, m);
fence = rcu_dereference(fobj->shared[i]); dma_resv_unlock(robj);
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();
} }
void etnaviv_gem_describe_objects(struct etnaviv_drm_private *priv, void etnaviv_gem_describe_objects(struct etnaviv_drm_private *priv,

View File

@ -189,13 +189,13 @@ static int submit_fence_sync(struct etnaviv_gem_submit *submit)
continue; continue;
if (bo->flags & ETNA_SUBMIT_BO_WRITE) { 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->nr_shared,
&bo->shared); &bo->shared);
if (ret) if (ret)
return ret; return ret;
} else { } else {
bo->excl = dma_resv_get_excl_unlocked(robj); bo->excl = dma_fence_get(dma_resv_excl_fence(robj));
} }
} }

View File

@ -25,7 +25,6 @@
#include "framebuffer.h" #include "framebuffer.h"
#include "gem.h" #include "gem.h"
#include "gtt.h"
#include "psb_drv.h" #include "psb_drv.h"
#include "psb_intel_drv.h" #include "psb_intel_drv.h"
#include "psb_intel_reg.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_framebuffer *fb = vma->vm_private_data;
struct drm_device *dev = fb->dev; struct drm_device *dev = fb->dev;
struct drm_psb_private *dev_priv = to_drm_psb_private(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 page_num;
int i; int i;
unsigned long address; unsigned long address;
vm_fault_t ret = VM_FAULT_SIGBUS; vm_fault_t ret = VM_FAULT_SIGBUS;
unsigned long pfn; unsigned long pfn;
unsigned long phys_addr = (unsigned long)dev_priv->stolen_base + unsigned long phys_addr = (unsigned long)dev_priv->stolen_base + pobj->offset;
gtt->offset;
page_num = vma_pages(vma); page_num = vma_pages(vma);
address = vmf->address - (vmf->pgoff << PAGE_SHIFT); address = vmf->address - (vmf->pgoff << PAGE_SHIFT);
@ -225,31 +223,6 @@ static struct drm_framebuffer *psb_framebuffer_create
return fb; 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 * psbfb_create - create a framebuffer
* @fb_helper: the framebuffer helper * @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; struct drm_mode_fb_cmd2 mode_cmd;
int size; int size;
int ret; int ret;
struct gtt_range *backing; struct psb_gem_object *backing;
struct drm_gem_object *obj;
u32 bpp, depth; u32 bpp, depth;
mode_cmd.width = sizes->surface_width; 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); size = ALIGN(size, PAGE_SIZE);
/* Allocate the framebuffer in the GTT with stolen page backing */ /* Allocate the framebuffer in the GTT with stolen page backing */
backing = psbfb_alloc(dev, size); backing = psb_gem_create(dev, size, "fb", true, PAGE_SIZE);
if (backing == NULL) if (IS_ERR(backing))
return -ENOMEM; return PTR_ERR(backing);
obj = &backing->base;
memset(dev_priv->vram_addr + backing->offset, 0, size); memset(dev_priv->vram_addr + backing->offset, 0, size);
info = drm_fb_helper_alloc_fbi(fb_helper); info = drm_fb_helper_alloc_fbi(fb_helper);
if (IS_ERR(info)) { if (IS_ERR(info)) {
ret = PTR_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); 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)) { if (IS_ERR(fb)) {
ret = PTR_ERR(fb); ret = PTR_ERR(fb);
goto out; goto err_drm_gem_object_put;
} }
fb_helper->fb = fb; 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); dev_dbg(dev->dev, "allocated %dx%d fb\n", fb->width, fb->height);
return 0; return 0;
out:
psb_gtt_free_range(dev, backing); err_drm_gem_object_put:
drm_gem_object_put(obj);
return ret; return ret;
} }

View File

@ -13,24 +13,105 @@
#include <linux/pagemap.h> #include <linux/pagemap.h>
#include <asm/set_memory.h>
#include <drm/drm.h> #include <drm/drm.h>
#include <drm/drm_vma_manager.h> #include <drm/drm_vma_manager.h>
#include "gem.h" #include "gem.h"
#include "psb_drv.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 vm_fault_t psb_gem_fault(struct vm_fault *vmf);
static void psb_gem_free_object(struct drm_gem_object *obj) 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); drm_gem_object_release(obj);
/* This must occur last as it frees up the memory of the GEM object */ /* Undo the mmap pin if we are destroying the object */
psb_gtt_free_range(obj->dev, gtt); 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 = { 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, .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, .free = psb_gem_free_object,
.vm_ops = &psb_gem_vm_ops, .vm_ops = &psb_gem_vm_ops,
}; };
/** struct psb_gem_object *
* psb_gem_create - create a mappable object psb_gem_create(struct drm_device *dev, u64 size, const char *name, bool stolen, u32 align)
* @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 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; int ret;
u32 handle;
size = roundup(size, PAGE_SIZE); size = roundup(size, PAGE_SIZE);
/* Allocate our object - for now a direct gtt range which is not pobj = kzalloc(sizeof(*pobj), GFP_KERNEL);
stolen memory backed */ if (!pobj)
r = psb_gtt_alloc_range(dev, size, "gem", 0, PAGE_SIZE); return ERR_PTR(-ENOMEM);
if (r == NULL) { obj = &pobj->base;
dev_err(dev->dev, "no memory for %lld byte GEM object\n", size);
return -ENOSPC; /* 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 */ /* GEM object */
if (drm_gem_object_init(dev, &r->gem, size) != 0) {
psb_gtt_free_range(dev, r); obj->funcs = &psb_gem_object_funcs;
/* GEM doesn't give an error code so use -ENOMEM */
dev_err(dev->dev, "GEM init failed for %lld\n", size); if (stolen) {
return -ENOMEM; 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); return pobj;
/* Give the object a handle so we can carry it more easily */
ret = drm_gem_handle_create(file, &r->gem, &handle); err_release_resource:
if (ret) { release_resource(&pobj->resource);
dev_err(dev->dev, "GEM handle failed for %p, %lld\n", err_kfree:
&r->gem, size); kfree(pobj);
drm_gem_object_release(&r->gem); return ERR_PTR(ret);
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;
} }
/** /**
@ -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, int psb_gem_dumb_create(struct drm_file *file, struct drm_device *dev,
struct drm_mode_create_dumb *args) struct drm_mode_create_dumb *args)
{ {
args->pitch = ALIGN(args->width * ((args->bpp + 7) / 8), 64); size_t pitch, size;
args->size = args->pitch * args->height; struct psb_gem_object *pobj;
return psb_gem_create(file, dev, args->size, &args->handle, 0, struct drm_gem_object *obj;
PAGE_SIZE); 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 vm_area_struct *vma = vmf->vma;
struct drm_gem_object *obj; struct drm_gem_object *obj;
struct gtt_range *r; struct psb_gem_object *pobj;
int err; int err;
vm_fault_t ret; vm_fault_t ret;
unsigned long pfn; unsigned long pfn;
@ -149,7 +257,7 @@ static vm_fault_t psb_gem_fault(struct vm_fault *vmf)
dev = obj->dev; dev = obj->dev;
dev_priv = to_drm_psb_private(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 /* Make sure we don't parallel update on a fault, nor move or remove
something from beneath our feet */ 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 /* For now the mmap pins the object and it stays pinned. As things
stand that will do us no harm */ stand that will do us no harm */
if (r->mmapping == 0) { if (pobj->mmapping == 0) {
err = psb_gtt_pin(r); err = psb_gem_pin(pobj);
if (err < 0) { if (err < 0) {
dev_err(dev->dev, "gma500: pin failed: %d\n", err); dev_err(dev->dev, "gma500: pin failed: %d\n", err);
ret = vmf_error(err); ret = vmf_error(err);
goto fail; goto fail;
} }
r->mmapping = 1; pobj->mmapping = 1;
} }
/* Page relative to the VMA start - we must calculate this ourselves /* 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; page_offset = (vmf->address - vma->vm_start) >> PAGE_SHIFT;
/* CPU view of the page, don't go via the GART for CPU writes */ /* CPU view of the page, don't go via the GART for CPU writes */
if (r->stolen) if (pobj->stolen)
pfn = (dev_priv->stolen_base + r->offset) >> PAGE_SHIFT; pfn = (dev_priv->stolen_base + pobj->offset) >> PAGE_SHIFT;
else 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); ret = vmf_insert_pfn(vma, vmf->address, pfn);
fail: fail:
mutex_unlock(&dev_priv->mmap_mutex); mutex_unlock(&dev_priv->mmap_mutex);

View File

@ -8,11 +8,33 @@
#ifndef _GEM_H #ifndef _GEM_H
#define _GEM_H #define _GEM_H
#include <linux/kernel.h>
#include <drm/drm_gem.h>
struct drm_device; 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, struct resource resource; /* GTT resource for our allocation */
u64 size, u32 *handlep, int stolen, u32 align); 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 #endif

View File

@ -15,6 +15,7 @@
#include <drm/drm_vblank.h> #include <drm/drm_vblank.h>
#include "framebuffer.h" #include "framebuffer.h"
#include "gem.h"
#include "gma_display.h" #include "gma_display.h"
#include "psb_drv.h" #include "psb_drv.h"
#include "psb_intel_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 drm_psb_private *dev_priv = to_drm_psb_private(dev);
struct gma_crtc *gma_crtc = to_gma_crtc(crtc); struct gma_crtc *gma_crtc = to_gma_crtc(crtc);
struct drm_framebuffer *fb = crtc->primary->fb; struct drm_framebuffer *fb = crtc->primary->fb;
struct gtt_range *gtt; struct psb_gem_object *pobj;
int pipe = gma_crtc->pipe; int pipe = gma_crtc->pipe;
const struct psb_offset *map = &dev_priv->regmap[pipe]; const struct psb_offset *map = &dev_priv->regmap[pipe];
unsigned long start, offset; 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; 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 /* We are displaying this buffer, make sure it is actually loaded
into the GTT */ into the GTT */
ret = psb_gtt_pin(gtt); ret = psb_gem_pin(pobj);
if (ret < 0) if (ret < 0)
goto gma_pipe_set_base_exit; goto gma_pipe_set_base_exit;
start = gtt->offset; start = pobj->offset;
offset = y * fb->pitches[0] + x * fb->format->cpp[0]; offset = y * fb->pitches[0] + x * fb->format->cpp[0];
REG_WRITE(map->stride, fb->pitches[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: gma_pipe_cleaner:
/* If there was a previous display we can now unpin it */ /* If there was a previous display we can now unpin it */
if (old_fb) 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_pipe_set_base_exit:
gma_power_end(dev); 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 base = (pipe == 0) ? CURABASE : CURBBASE;
uint32_t temp; uint32_t temp;
size_t addr = 0; size_t addr = 0;
struct gtt_range *gt; struct psb_gem_object *pobj;
struct gtt_range *cursor_gt = gma_crtc->cursor_gt; struct psb_gem_object *cursor_pobj = gma_crtc->cursor_pobj;
struct drm_gem_object *obj; struct drm_gem_object *obj;
void *tmp_dst, *tmp_src; void *tmp_dst, *tmp_src;
int ret = 0, i, cursor_pages; int ret = 0, i, cursor_pages;
@ -348,9 +349,8 @@ int gma_crtc_cursor_set(struct drm_crtc *crtc,
/* Unpin the old GEM object */ /* Unpin the old GEM object */
if (gma_crtc->cursor_obj) { if (gma_crtc->cursor_obj) {
gt = container_of(gma_crtc->cursor_obj, pobj = to_psb_gem_object(gma_crtc->cursor_obj);
struct gtt_range, gem); psb_gem_unpin(pobj);
psb_gtt_unpin(gt);
drm_gem_object_put(gma_crtc->cursor_obj); drm_gem_object_put(gma_crtc->cursor_obj);
gma_crtc->cursor_obj = NULL; gma_crtc->cursor_obj = NULL;
} }
@ -375,40 +375,40 @@ int gma_crtc_cursor_set(struct drm_crtc *crtc,
goto unref_cursor; goto unref_cursor;
} }
gt = container_of(obj, struct gtt_range, gem); pobj = to_psb_gem_object(obj);
/* Pin the memory into the GTT */ /* Pin the memory into the GTT */
ret = psb_gtt_pin(gt); ret = psb_gem_pin(pobj);
if (ret) { if (ret) {
dev_err(dev->dev, "Can not pin down handle 0x%x\n", handle); dev_err(dev->dev, "Can not pin down handle 0x%x\n", handle);
goto unref_cursor; goto unref_cursor;
} }
if (dev_priv->ops->cursor_needs_phys) { if (dev_priv->ops->cursor_needs_phys) {
if (cursor_gt == NULL) { if (!cursor_pobj) {
dev_err(dev->dev, "No hardware cursor mem available"); dev_err(dev->dev, "No hardware cursor mem available");
ret = -ENOMEM; ret = -ENOMEM;
goto unref_cursor; goto unref_cursor;
} }
/* Prevent overflow */ /* Prevent overflow */
if (gt->npage > 4) if (pobj->npage > 4)
cursor_pages = 4; cursor_pages = 4;
else else
cursor_pages = gt->npage; cursor_pages = pobj->npage;
/* Copy the cursor to cursor mem */ /* 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++) { 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); memcpy(tmp_dst, tmp_src, PAGE_SIZE);
kunmap(gt->pages[i]); kunmap(pobj->pages[i]);
tmp_dst += PAGE_SIZE; tmp_dst += PAGE_SIZE;
} }
addr = gma_crtc->cursor_addr; addr = gma_crtc->cursor_addr;
} else { } else {
addr = gt->offset; addr = pobj->offset;
gma_crtc->cursor_addr = addr; gma_crtc->cursor_addr = addr;
} }
@ -425,8 +425,8 @@ int gma_crtc_cursor_set(struct drm_crtc *crtc,
/* unpin the old bo */ /* unpin the old bo */
if (gma_crtc->cursor_obj) { if (gma_crtc->cursor_obj) {
gt = container_of(gma_crtc->cursor_obj, struct gtt_range, gem); pobj = to_psb_gem_object(gma_crtc->cursor_obj);
psb_gtt_unpin(gt); psb_gem_unpin(pobj);
drm_gem_object_put(gma_crtc->cursor_obj); 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) 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; const struct drm_crtc_helper_funcs *crtc_funcs = crtc->helper_private;
crtc_funcs->dpms(crtc, DRM_MODE_DPMS_OFF); crtc_funcs->dpms(crtc, DRM_MODE_DPMS_OFF);
if (crtc->primary->fb) { if (crtc->primary->fb) {
gt = to_gtt_range(crtc->primary->fb->obj[0]); pobj = to_psb_gem_object(crtc->primary->fb->obj[0]);
psb_gtt_unpin(gt); 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); 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); kfree(gma_crtc->crtc_state);
drm_crtc_cleanup(crtc); drm_crtc_cleanup(crtc);
kfree(gma_crtc); kfree(gma_crtc);

View File

@ -7,10 +7,7 @@
* Alan Cox <alan@linux.intel.com> * Alan Cox <alan@linux.intel.com>
*/ */
#include <linux/shmem_fs.h> #include "gem.h" /* TODO: for struct psb_gem_object, see psb_gtt_restore() */
#include <asm/set_memory.h>
#include "psb_drv.h" #include "psb_drv.h"
@ -18,6 +15,33 @@
* GTT resource allocator - manage page mappings in GTT space * 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 * psb_gtt_mask_pte - generate GTT pte entry
* @pfn: page number to encode * @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; return (pfn << PAGE_SHIFT) | mask;
} }
/** static u32 __iomem *psb_gtt_entry(struct drm_psb_private *pdev, const struct resource *res)
* 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)
{ {
struct drm_psb_private *dev_priv = to_drm_psb_private(dev); unsigned long offset = res->start - pdev->gtt_mem->start;
unsigned long offset;
offset = r->resource.start - dev_priv->gtt_mem->start; return pdev->gtt_map + (offset >> PAGE_SHIFT);
return dev_priv->gtt_map + (offset >> PAGE_SHIFT);
} }
/** /*
* psb_gtt_insert - put an object into the GTT * Take our preallocated GTT range and insert the GEM object into
* @dev: our DRM device * the GTT. This is protected via the gtt mutex which the caller
* @r: our GTT range * must hold.
* @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.
*/ */
static int psb_gtt_insert(struct drm_device *dev, struct gtt_range *r, void psb_gtt_insert_pages(struct drm_psb_private *pdev, const struct resource *res,
int resume) struct page **pages)
{ {
resource_size_t npages, i;
u32 __iomem *gtt_slot; u32 __iomem *gtt_slot;
u32 pte; 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 */ /* 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]), npages = resource_size(res) >> PAGE_SHIFT;
PSB_MMU_CACHED_MEMORY); gtt_slot = psb_gtt_entry(pdev, res);
iowrite32(pte, gtt_slot++);
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 */ /* Make sure all the entries are set before we return */
ioread32(gtt_slot - 1); 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(&gt->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(&gt->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.
*/ */
void psb_gtt_remove_pages(struct drm_psb_private *pdev, const struct resource *res)
/**
* 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)
{ {
struct drm_psb_private *dev_priv = to_drm_psb_private(dev); resource_size_t npages, i;
struct gtt_range *gt; u32 __iomem *gtt_slot;
struct resource *r = dev_priv->gtt_mem; u32 pte;
int ret;
unsigned long start, end;
if (backed) { /* Install scratch page for the resource */
/* 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;
}
gt = kzalloc(sizeof(struct gtt_range), GFP_KERNEL); pte = psb_gtt_mask_pte(page_to_pfn(pdev->scratch_page), PSB_MMU_CACHED_MEMORY);
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, &gt->resource,
len, start, end, align, NULL, NULL);
if (ret == 0) {
gt->offset = gt->resource.start - r->start;
return gt;
}
kfree(gt);
return NULL;
}
/** npages = resource_size(res) >> PAGE_SHIFT;
* psb_gtt_free_range - release GTT address space gtt_slot = psb_gtt_entry(pdev, res);
* @dev: our DRM device
* @gt: a mapping created with psb_gtt_alloc_range for (i = 0; i < npages; ++i, ++gtt_slot)
* iowrite32(pte, gtt_slot);
* 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. /* Make sure all the entries are set before we return */
*/ ioread32(gtt_slot - 1);
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(&gt->resource);
kfree(gt);
} }
static void psb_gtt_alloc(struct drm_device *dev) 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 drm_psb_private *dev_priv = to_drm_psb_private(dev);
struct resource *r = dev_priv->gtt_mem->child; 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; unsigned int restored = 0, total = 0, size = 0;
/* On resume, the gtt_mutex is already initialized */ /* 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); psb_gtt_init(dev, 1);
while (r != NULL) { while (r != NULL) {
range = container_of(r, struct gtt_range, resource); /*
if (range->pages) { * TODO: GTT restoration needs a refactoring, so that we don't have to touch
psb_gtt_insert(dev, range, 1); * struct psb_gem_object here. The type represents a GEM object and is
size += range->resource.end - range->resource.start; * 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++; restored++;
} }
r = r->sibling; r = r->sibling;

View File

@ -10,6 +10,8 @@
#include <drm/drm_gem.h> #include <drm/drm_gem.h>
struct drm_psb_private;
/* This wants cleaning up with respect to the psb_dev and un-needed stuff */ /* This wants cleaning up with respect to the psb_dev and un-needed stuff */
struct psb_gtt { struct psb_gtt {
uint32_t gatt_start; uint32_t gatt_start;
@ -26,27 +28,14 @@ struct psb_gtt {
/* Exported functions */ /* Exported functions */
extern int psb_gtt_init(struct drm_device *dev, int resume); extern int psb_gtt_init(struct drm_device *dev, int resume);
extern void psb_gtt_takedown(struct drm_device *dev); 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); 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 #endif

View File

@ -10,6 +10,7 @@
#include <drm/drm_fourcc.h> #include <drm/drm_fourcc.h>
#include "framebuffer.h" #include "framebuffer.h"
#include "gem.h"
#include "gma_display.h" #include "gma_display.h"
#include "power.h" #include "power.h"
#include "psb_drv.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)) if (!gma_power_begin(dev, true))
return 0; 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]; offset = y * fb->pitches[0] + x * fb->format->cpp[0];
REG_WRITE(map->stride, fb->pitches[0]); REG_WRITE(map->stride, fb->pitches[0]);

View File

@ -19,6 +19,7 @@
#include <acpi/video.h> #include <acpi/video.h>
#include <drm/drm.h> #include <drm/drm.h>
#include <drm/drm_aperture.h>
#include <drm/drm_drv.h> #include <drm/drm_drv.h>
#include <drm/drm_fb_helper.h> #include <drm/drm_fb_helper.h>
#include <drm/drm_file.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; struct drm_device *dev;
int ret; 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); ret = pcim_enable_device(pdev);
if (ret) if (ret)
return ret; return ret;

View File

@ -12,6 +12,7 @@
#include <drm/drm_plane_helper.h> #include <drm/drm_plane_helper.h>
#include "framebuffer.h" #include "framebuffer.h"
#include "gem.h"
#include "gma_display.h" #include "gma_display.h"
#include "power.h" #include "power.h"
#include "psb_drv.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); struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
u32 control[3] = { CURACNTR, CURBCNTR, CURCCNTR }; u32 control[3] = { CURACNTR, CURBCNTR, CURCCNTR };
u32 base[3] = { CURABASE, CURBBASE, CURCBASE }; u32 base[3] = { CURABASE, CURBBASE, CURCBASE };
struct gtt_range *cursor_gt; struct psb_gem_object *cursor_pobj;
if (dev_priv->ops->cursor_needs_phys) { if (dev_priv->ops->cursor_needs_phys) {
/* Allocate 4 pages of stolen mem for a hardware cursor. That /* Allocate 4 pages of stolen mem for a hardware cursor. That
* is enough for the 64 x 64 ARGB cursors we support. * is enough for the 64 x 64 ARGB cursors we support.
*/ */
cursor_gt = psb_gtt_alloc_range(dev, 4 * PAGE_SIZE, "cursor", 1, cursor_pobj = psb_gem_create(dev, 4 * PAGE_SIZE, "cursor", true, PAGE_SIZE);
PAGE_SIZE); if (IS_ERR(cursor_pobj)) {
if (!cursor_gt) { gma_crtc->cursor_pobj = NULL;
gma_crtc->cursor_gt = NULL;
goto out; goto out;
} }
gma_crtc->cursor_gt = cursor_gt; gma_crtc->cursor_pobj = cursor_pobj;
gma_crtc->cursor_addr = dev_priv->stolen_base + gma_crtc->cursor_addr = dev_priv->stolen_base + cursor_pobj->offset;
cursor_gt->offset;
} else { } else {
gma_crtc->cursor_gt = NULL; gma_crtc->cursor_pobj = NULL;
} }
out: out:

View File

@ -140,7 +140,7 @@ struct gma_crtc {
int pipe; int pipe;
int plane; int plane;
uint32_t cursor_addr; uint32_t cursor_addr;
struct gtt_range *cursor_gt; struct psb_gem_object *cursor_pobj;
u8 lut_adj[256]; u8 lut_adj[256];
struct psb_intel_framebuffer *fbdev_fb; struct psb_intel_framebuffer *fbdev_fb;
/* a mode_set for fbdev users on this crtc */ /* a mode_set for fbdev users on this crtc */

View File

@ -74,7 +74,7 @@ static size_t gud_xrgb8888_to_r124(u8 *dst, const struct drm_format_info *format
if (!buf) if (!buf)
return 0; return 0;
drm_fb_xrgb8888_to_gray8(buf, src, fb, rect); drm_fb_xrgb8888_to_gray8(buf, 0, src, fb, rect);
pix8 = buf; pix8 = buf;
for (y = 0; y < height; y++) { for (y = 0; y < height; y++) {
@ -190,23 +190,23 @@ retry:
goto end_cpu_access; goto end_cpu_access;
} }
} else if (format->format == DRM_FORMAT_R8) { } 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) { } 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) { } 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) { } 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 { } else {
len = gud_xrgb8888_to_color(buf, format, vaddr, fb, rect); len = gud_xrgb8888_to_color(buf, format, vaddr, fb, rect);
} }
} else if (gud_is_big_endian() && format->cpp[0] > 1) { } 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]) { } else if (compression && !import_attach && pitch == fb->pitches[0]) {
/* can compress directly from the framebuffer */ /* can compress directly from the framebuffer */
buf = vaddr + rect->y1 * pitch; buf = vaddr + rect->y1 * pitch;
} else { } else {
drm_fb_memcpy(buf, vaddr, fb, rect); drm_fb_memcpy(buf, 0, vaddr, fb, rect);
} }
memset(req, 0, sizeof(*req)); memset(req, 0, sizeof(*req));

View File

@ -81,7 +81,7 @@ struct dsi_hw_ctx {
struct dw_dsi { struct dw_dsi {
struct drm_encoder encoder; struct drm_encoder encoder;
struct drm_bridge *bridge; struct device *dev;
struct mipi_dsi_host host; struct mipi_dsi_host host;
struct drm_display_mode cur_mode; struct drm_display_mode cur_mode;
struct dsi_hw_ctx *ctx; struct dsi_hw_ctx *ctx;
@ -720,10 +720,13 @@ static int dw_drm_encoder_init(struct device *dev,
return 0; return 0;
} }
static const struct component_ops dsi_ops;
static int dsi_host_attach(struct mipi_dsi_host *host, static int dsi_host_attach(struct mipi_dsi_host *host,
struct mipi_dsi_device *mdsi) struct mipi_dsi_device *mdsi)
{ {
struct dw_dsi *dsi = host_to_dsi(host); struct dw_dsi *dsi = host_to_dsi(host);
struct device *dev = host->dev;
int ret;
if (mdsi->lanes < 1 || mdsi->lanes > 4) { if (mdsi->lanes < 1 || mdsi->lanes > 4) {
DRM_ERROR("dsi device params invalid\n"); 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->format = mdsi->format;
dsi->mode_flags = mdsi->mode_flags; dsi->mode_flags = mdsi->mode_flags;
ret = component_add(dev, &dsi_ops);
if (ret)
return ret;
return 0; return 0;
} }
static int dsi_host_detach(struct mipi_dsi_host *host, static int dsi_host_detach(struct mipi_dsi_host *host,
struct mipi_dsi_device *mdsi) struct mipi_dsi_device *mdsi)
{ {
/* do nothing */ struct device *dev = host->dev;
component_del(dev, &dsi_ops);
return 0; 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) static int dsi_bridge_init(struct drm_device *dev, struct dw_dsi *dsi)
{ {
struct drm_encoder *encoder = &dsi->encoder; 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 */ /* associate the bridge to dsi encoder */
return drm_bridge_attach(encoder, bridge, NULL, 0); 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) if (ret)
return ret; return ret;
ret = dsi_host_init(dev, dsi);
if (ret)
return ret;
ret = dsi_bridge_init(drm_dev, dsi); ret = dsi_bridge_init(drm_dev, dsi);
if (ret) if (ret)
return 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) static int dsi_parse_dt(struct platform_device *pdev, struct dw_dsi *dsi)
{ {
struct dsi_hw_ctx *ctx = dsi->ctx; struct dsi_hw_ctx *ctx = dsi->ctx;
struct device_node *np = pdev->dev.of_node;
struct resource *res; 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"); ctx->pclk = devm_clk_get(&pdev->dev, "pclk");
if (IS_ERR(ctx->pclk)) { if (IS_ERR(ctx->pclk)) {
@ -852,6 +858,7 @@ static int dsi_probe(struct platform_device *pdev)
dsi = &data->dsi; dsi = &data->dsi;
ctx = &data->ctx; ctx = &data->ctx;
dsi->ctx = ctx; dsi->ctx = ctx;
dsi->dev = &pdev->dev;
ret = dsi_parse_dt(pdev, dsi); ret = dsi_parse_dt(pdev, dsi);
if (ret) if (ret)
@ -859,12 +866,19 @@ static int dsi_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, data); 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) 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; return 0;
} }

View File

@ -23,13 +23,16 @@ static int hyperv_blit_to_vram_rect(struct drm_framebuffer *fb,
struct drm_rect *rect) struct drm_rect *rect)
{ {
struct hyperv_drm_device *hv = to_hv(fb->dev); struct hyperv_drm_device *hv = to_hv(fb->dev);
void __iomem *dst = hv->vram;
void *vmap = map->vaddr; /* TODO: Use mapping abstraction properly */ void *vmap = map->vaddr; /* TODO: Use mapping abstraction properly */
int idx; int idx;
if (!drm_dev_enter(&hv->dev, &idx)) if (!drm_dev_enter(&hv->dev, &idx))
return -ENODEV; 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); drm_dev_exit(idx);
return 0; return 0;

View File

@ -738,6 +738,7 @@ intel_prepare_plane_fb(struct drm_plane *_plane,
i915_gem_object_wait_priority(obj, 0, &attr); i915_gem_object_wait_priority(obj, 0, &attr);
if (!new_plane_state->uapi.fence) { /* implicit fencing */ if (!new_plane_state->uapi.fence) { /* implicit fencing */
struct dma_resv_iter cursor;
struct dma_fence *fence; struct dma_fence *fence;
ret = i915_sw_fence_await_reservation(&state->commit_ready, ret = i915_sw_fence_await_reservation(&state->commit_ready,
@ -748,12 +749,12 @@ intel_prepare_plane_fb(struct drm_plane *_plane,
if (ret < 0) if (ret < 0)
goto unpin_fb; goto unpin_fb;
fence = dma_resv_get_excl_unlocked(obj->base.resv); dma_resv_iter_begin(&cursor, obj->base.resv, false);
if (fence) { dma_resv_for_each_fence_unlocked(&cursor, fence) {
add_rps_boost_after_vblank(new_plane_state->hw.crtc, add_rps_boost_after_vblank(new_plane_state->hw.crtc,
fence); fence);
dma_fence_put(fence);
} }
dma_resv_iter_end(&cursor);
} else { } else {
add_rps_boost_after_vblank(new_plane_state->hw.crtc, add_rps_boost_after_vblank(new_plane_state->hw.crtc,
new_plane_state->uapi.fence); new_plane_state->uapi.fence);

View File

@ -4960,7 +4960,7 @@ static void intel_dp_modeset_retry_work_fn(struct work_struct *work)
DRM_MODE_LINK_STATUS_BAD); DRM_MODE_LINK_STATUS_BAD);
mutex_unlock(&connector->dev->mode_config.mutex); mutex_unlock(&connector->dev->mode_config.mutex);
/* Send Hotplug uevent so userspace can reprobe */ /* Send Hotplug uevent so userspace can reprobe */
drm_kms_helper_hotplug_event(connector->dev); drm_kms_helper_connector_hotplug_event(connector);
} }
bool bool

View File

@ -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_panel *panel = &connector->panel;
struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); 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); 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_panel *panel = &connector->panel;
struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); 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); 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); struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder);
drm_edp_backlight_disable(&intel_dp->aux, &panel->backlight.edp.vesa.info); 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) 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) if (ret < 0)
return ret; return ret;
panel->backlight.max = panel->backlight.edp.vesa.info.max; if (!panel->backlight.edp.vesa.info.aux_set || !panel->backlight.edp.vesa.info.aux_enable) {
panel->backlight.min = 0; ret = panel->backlight.pwm_funcs->setup(connector, pipe);
if (current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) { if (ret < 0) {
panel->backlight.level = current_level; drm_err(&i915->drm,
panel->backlight.enabled = panel->backlight.level != 0; "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 { } else {
panel->backlight.level = panel->backlight.max; panel->backlight.max = panel->backlight.pwm_level_max;
panel->backlight.enabled = false; 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; 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 intel_dp *intel_dp = intel_attached_dp(connector);
struct drm_i915_private *i915 = dp_to_i915(intel_dp); struct drm_i915_private *i915 = dp_to_i915(intel_dp);
/* TODO: We currently only support AUX only backlight configurations, not backlights which if (drm_edp_backlight_supported(intel_dp->edp_dpcd)) {
* 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)) {
drm_dbg_kms(&i915->drm, "AUX Backlight Control Supported!\n"); drm_dbg_kms(&i915->drm, "AUX Backlight Control Supported!\n");
return true; 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 * Since Intel has their own backlight control interface, the majority of machines out there
* Intel proprietary backlight control interface, and the VESA * using DPCD backlight controls with Intel GPUs will be using this interface as opposed to
* backlight control interface. Many of these panels are liars though, * the VESA interface. However, other GPUs (such as Nvidia's) will always use the VESA
* and will only work with the Intel interface. So, always probe for * interface. This means that there's quite a number of panels out there that will advertise
* that first. * 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)) { if (try_intel_interface && intel_dp_aux_supports_hdr_backlight(connector)) {
drm_dbg_kms(dev, "Using Intel proprietary eDP backlight controls\n"); drm_dbg_kms(dev, "Using Intel proprietary eDP backlight controls\n");

View File

@ -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_busy *args = data;
struct drm_i915_gem_object *obj; struct drm_i915_gem_object *obj;
struct dma_resv_list *list; struct dma_resv_iter cursor;
unsigned int seq; struct dma_fence *fence;
int err; int err;
err = -ENOENT; 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. * to report the overall busyness. This is what the wait-ioctl does.
* *
*/ */
retry: args->busy = 0;
seq = raw_read_seqcount(&obj->base.resv->seq); dma_resv_iter_begin(&cursor, obj->base.resv, true);
dma_resv_for_each_fence_unlocked(&cursor, fence) {
/* Translate the exclusive fence to the READ *and* WRITE engine */ if (dma_resv_iter_is_restarted(&cursor))
args->busy = busy_check_writer(dma_resv_excl_fence(obj->base.resv)); args->busy = 0;
/* 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]);
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); args->busy |= busy_check_reader(fence);
}
} }
dma_resv_iter_end(&cursor);
if (args->busy && read_seqcount_retry(&obj->base.resv->seq, seq))
goto retry;
err = 0; err = 0;
out: out:

View File

@ -25,7 +25,7 @@ i915_gem_object_wait_fence(struct dma_fence *fence,
return timeout; return timeout;
if (dma_fence_is_i915(fence)) 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, return dma_fence_wait_timeout(fence,
flags & I915_WAIT_INTERRUPTIBLE, flags & I915_WAIT_INTERRUPTIBLE,
@ -37,58 +37,29 @@ i915_gem_object_wait_reservation(struct dma_resv *resv,
unsigned int flags, unsigned int flags,
long timeout) long timeout)
{ {
struct dma_fence *excl; struct dma_resv_iter cursor;
bool prune_fences = false; struct dma_fence *fence;
long ret = timeout ?: 1;
if (flags & I915_WAIT_ALL) { dma_resv_iter_begin(&cursor, resv, flags & I915_WAIT_ALL);
struct dma_fence **shared; dma_resv_for_each_fence_unlocked(&cursor, fence) {
unsigned int count, i; ret = i915_gem_object_wait_fence(fence, flags, timeout);
int ret; if (ret <= 0)
break;
ret = dma_resv_get_fences(resv, &excl, &count, &shared); if (timeout)
if (ret) timeout = 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);
} }
dma_resv_iter_end(&cursor);
if (excl && timeout >= 0)
timeout = i915_gem_object_wait_fence(excl, flags, timeout);
dma_fence_put(excl);
/* /*
* Opportunistically prune the fences iff we know they have *all* been * Opportunistically prune the fences iff we know they have *all* been
* signaled. * signaled.
*/ */
if (prune_fences) if (timeout > 0)
dma_resv_prune(resv); dma_resv_prune(resv);
return timeout; return ret;
} }
static void fence_set_priority(struct dma_fence *fence, 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, unsigned int flags,
const struct i915_sched_attr *attr) const struct i915_sched_attr *attr)
{ {
struct dma_fence *excl; struct dma_resv_iter cursor;
struct dma_fence *fence;
if (flags & I915_WAIT_ALL) { dma_resv_iter_begin(&cursor, obj->base.resv, flags & I915_WAIT_ALL);
struct dma_fence **shared; dma_resv_for_each_fence_unlocked(&cursor, fence)
unsigned int count, i; i915_gem_fence_wait_priority(fence, attr);
int ret; dma_resv_iter_end(&cursor);
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);
}
return 0; 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, timeout = i915_gem_object_wait_reservation(obj->base.resv,
flags, timeout); 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) static inline unsigned long nsecs_to_jiffies_timeout(const u64 n)

View File

@ -96,9 +96,9 @@ static signed long i915_fence_wait(struct dma_fence *fence,
bool interruptible, bool interruptible,
signed long timeout) signed long timeout)
{ {
return i915_request_wait(to_request(fence), return i915_request_wait_timeout(to_request(fence),
interruptible | I915_WAIT_PRIORITY, interruptible | I915_WAIT_PRIORITY,
timeout); timeout);
} }
struct kmem_cache *i915_request_slab_cache(void) 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 * @rq: the request to wait upon
* @flags: how to wait * @flags: how to wait
* @timeout: how long to wait in jiffies * @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 * maximum of @timeout jiffies (with MAX_SCHEDULE_TIMEOUT implying an
* unbounded wait). * unbounded wait).
* *
* Returns the remaining time (in jiffies) if the request completed, which may * 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 * May return -EINTR is called with I915_WAIT_INTERRUPTIBLE and a signal is
* pending before the request completes. * pending before the request completes.
*
* NOTE: This function has the same wait semantics as dma-fence.
*/ */
long i915_request_wait(struct i915_request *rq, long i915_request_wait_timeout(struct i915_request *rq,
unsigned int flags, unsigned int flags,
long timeout) long timeout)
{ {
const int state = flags & I915_WAIT_INTERRUPTIBLE ? const int state = flags & I915_WAIT_INTERRUPTIBLE ?
TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE; TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE;
@ -1883,7 +1887,7 @@ long i915_request_wait(struct i915_request *rq,
GEM_BUG_ON(timeout < 0); GEM_BUG_ON(timeout < 0);
if (dma_fence_is_signaled(&rq->fence)) if (dma_fence_is_signaled(&rq->fence))
return timeout; return timeout ?: 1;
if (!timeout) if (!timeout)
return -ETIME; return -ETIME;
@ -1992,6 +1996,39 @@ out:
return timeout; 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, static int print_sched_attr(const struct i915_sched_attr *attr,
char *buf, int x, int len) char *buf, int x, int len)
{ {

View File

@ -414,6 +414,11 @@ void i915_request_unsubmit(struct i915_request *request);
void i915_request_cancel(struct i915_request *rq, int error); 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, long i915_request_wait(struct i915_request *rq,
unsigned int flags, unsigned int flags,
long timeout) long timeout)

View File

@ -572,56 +572,25 @@ int i915_sw_fence_await_reservation(struct i915_sw_fence *fence,
unsigned long timeout, unsigned long timeout,
gfp_t gfp) gfp_t gfp)
{ {
struct dma_fence *excl; struct dma_resv_iter cursor;
struct dma_fence *f;
int ret = 0, pending; int ret = 0, pending;
debug_fence_assert(fence); debug_fence_assert(fence);
might_sleep_if(gfpflags_allow_blocking(gfp)); might_sleep_if(gfpflags_allow_blocking(gfp));
if (write) { dma_resv_iter_begin(&cursor, resv, write);
struct dma_fence **shared; dma_resv_for_each_fence_unlocked(&cursor, f) {
unsigned int count, i; pending = i915_sw_fence_await_dma_fence(fence, f, timeout,
gfp);
ret = dma_resv_get_fences(resv, &excl, &count, &shared); if (pending < 0) {
if (ret) ret = pending;
return ret; break;
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;
} }
for (i = 0; i < count; i++) ret |= pending;
dma_fence_put(shared[i]);
kfree(shared);
} else {
excl = dma_resv_get_excl_unlocked(resv);
} }
dma_resv_iter_end(&cursor);
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);
return ret; return ret;
} }

View File

@ -21,6 +21,7 @@
#include <drm/drm_atomic.h> #include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h> #include <drm/drm_bridge.h>
#include <drm/drm_bridge_connector.h>
#include <drm/drm_color_mgmt.h> #include <drm/drm_color_mgmt.h>
#include <drm/drm_crtc.h> #include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h> #include <drm/drm_crtc_helper.h>
@ -41,6 +42,8 @@
#include <drm/drm_probe_helper.h> #include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h> #include <drm/drm_vblank.h>
#define HWDESC_PALETTE 2
struct ingenic_dma_hwdesc { struct ingenic_dma_hwdesc {
u32 next; u32 next;
u32 addr; u32 addr;
@ -49,9 +52,7 @@ struct ingenic_dma_hwdesc {
} __aligned(16); } __aligned(16);
struct ingenic_dma_hwdescs { struct ingenic_dma_hwdescs {
struct ingenic_dma_hwdesc hwdesc_f0; struct ingenic_dma_hwdesc hwdesc[3];
struct ingenic_dma_hwdesc hwdesc_f1;
struct ingenic_dma_hwdesc hwdesc_pal;
u16 palette[256] __aligned(16); u16 palette[256] __aligned(16);
}; };
@ -64,6 +65,11 @@ struct jz_soc_info {
unsigned int num_formats_f0, num_formats_f1; 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 ingenic_drm {
struct drm_device drm; struct drm_device drm;
/* /*
@ -99,8 +105,53 @@ struct ingenic_drm {
struct mutex clk_mutex; struct mutex clk_mutex;
bool update_clk_rate; bool update_clk_rate;
struct notifier_block clock_nb; 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) static bool ingenic_drm_writeable_reg(struct device *dev, unsigned int reg)
{ {
switch (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); 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, static int ingenic_drm_update_pixclk(struct notifier_block *nb,
unsigned long action, unsigned long action,
void *data) void *data)
@ -163,9 +222,20 @@ static void ingenic_drm_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_atomic_state *state) struct drm_atomic_state *state)
{ {
struct ingenic_drm *priv = drm_crtc_get_priv(crtc); 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); 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, regmap_update_bits(priv->map, JZ_REG_LCD_CTRL,
JZ_LCD_CTRL_ENABLE | JZ_LCD_CTRL_DISABLE, JZ_LCD_CTRL_ENABLE | JZ_LCD_CTRL_DISABLE,
JZ_LCD_CTRL_ENABLE); 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, struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
plane); plane);
struct ingenic_drm *priv = drm_device_get_priv(plane->dev); 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_state *crtc_state;
struct drm_crtc *crtc = new_plane_state->crtc ?: old_plane_state->crtc; struct drm_crtc *crtc = new_plane_state->crtc ?: old_plane_state->crtc;
int ret; int ret;
@ -381,6 +452,10 @@ static int ingenic_drm_plane_atomic_check(struct drm_plane *plane,
if (WARN_ON(!crtc_state)) if (WARN_ON(!crtc_state))
return -EINVAL; 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, ret = drm_atomic_helper_check_plane_state(new_plane_state, crtc_state,
DRM_PLANE_HELPER_NO_SCALING, DRM_PLANE_HELPER_NO_SCALING,
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)) (new_plane_state->src_h >> 16) != new_plane_state->crtc_h))
return -EINVAL; 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 * Require full modeset if enabling or disabling a plane, or changing
* its position, size or depth. * 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 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 *newstate = drm_atomic_get_new_plane_state(state, plane);
struct drm_plane_state *oldstate = drm_atomic_get_old_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 drm_crtc_state *crtc_state;
struct ingenic_dma_hwdesc *hwdesc; struct ingenic_dma_hwdesc *hwdesc;
unsigned int width, height, cpp, offset;
dma_addr_t addr; dma_addr_t addr;
u32 fourcc; 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); drm_fb_cma_sync_non_coherent(&priv->drm, oldstate, newstate);
crtc_state = newstate->crtc->state; 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); addr = drm_fb_cma_get_gem_addr(newstate->fb, newstate, 0);
width = newstate->src_w >> 16; width = newstate->src_w >> 16;
height = newstate->src_h >> 16; height = newstate->src_h >> 16;
cpp = newstate->fb->format->cpp[0]; cpp = newstate->fb->format->cpp[0];
if (!priv->soc_info->has_osd || plane == &priv->f0) priv_state = ingenic_drm_get_new_priv_state(priv, state);
hwdesc = &priv->dma_hwdescs->hwdesc_f0; next_id = (priv_state && priv_state->use_palette) ? HWDESC_PALETTE : plane_id;
else
hwdesc = &priv->dma_hwdescs->hwdesc_f1;
hwdesc = &priv->dma_hwdescs->hwdesc[plane_id];
hwdesc->addr = addr; hwdesc->addr = addr;
hwdesc->cmd = JZ_LCD_CMD_EOF_IRQ | (width * height * cpp / 4); 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)) { if (drm_atomic_crtc_needs_modeset(crtc_state)) {
fourcc = newstate->fb->format->format; fourcc = newstate->fb->format->format;
ingenic_drm_plane_config(priv->dev, plane, fourcc); 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; 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 ingenic_drm *priv = drm_device_get_priv(encoder->dev);
struct drm_display_mode *mode = &crtc_state->adjusted_mode; struct drm_display_mode *mode = &crtc_state->adjusted_mode;
struct drm_connector *conn = conn_state->connector; struct ingenic_drm_bridge *bridge = to_ingenic_drm_bridge(encoder);
struct drm_display_info *info = &conn->display_info;
unsigned int cfg, rgbcfg = 0; 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) { if (priv->panel_is_sharp) {
cfg = JZ_LCD_CFG_MODE_SPECIAL_TFT_1 | JZ_LCD_CFG_REV_POLARITY; 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; cfg |= JZ_LCD_CFG_HSYNC_ACTIVE_LOW;
if (mode->flags & DRM_MODE_FLAG_NVSYNC) if (mode->flags & DRM_MODE_FLAG_NVSYNC)
cfg |= JZ_LCD_CFG_VSYNC_ACTIVE_LOW; 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; 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; cfg |= JZ_LCD_CFG_PCLK_FALLING_EDGE;
if (!priv->panel_is_sharp) { 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) if (mode->flags & DRM_MODE_FLAG_INTERLACE)
cfg |= JZ_LCD_CFG_MODE_TV_OUT_I; cfg |= JZ_LCD_CFG_MODE_TV_OUT_I;
else else
cfg |= JZ_LCD_CFG_MODE_TV_OUT_P; cfg |= JZ_LCD_CFG_MODE_TV_OUT_P;
} else { } else {
switch (*info->bus_formats) { switch (bridge->bus_cfg.format) {
case MEDIA_BUS_FMT_RGB565_1X16: case MEDIA_BUS_FMT_RGB565_1X16:
cfg |= JZ_LCD_CFG_MODE_GENERIC_16BIT; cfg |= JZ_LCD_CFG_MODE_GENERIC_16BIT;
break; 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); regmap_write(priv->map, JZ_REG_LCD_RGBC, rgbcfg);
} }
static int ingenic_drm_encoder_atomic_check(struct drm_encoder *encoder, static int ingenic_drm_bridge_attach(struct drm_bridge *bridge,
struct drm_crtc_state *crtc_state, enum drm_bridge_attach_flags flags)
struct drm_connector_state *conn_state)
{ {
struct drm_display_info *info = &conn_state->connector->display_info; struct ingenic_drm_bridge *ib = to_ingenic_drm_bridge(bridge->encoder);
struct drm_display_mode *mode = &crtc_state->adjusted_mode;
if (info->num_bus_formats != 1) return drm_bridge_attach(bridge->encoder, ib->next_bridge,
return -EINVAL; &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) if (conn_state->connector->connector_type == DRM_MODE_CONNECTOR_TV)
return 0; 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:
case MEDIA_BUS_FMT_RGB888_3X8_DELTA: 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; 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); DEFINE_DRM_GEM_CMA_FOPS(ingenic_drm_fops);
static const struct drm_driver ingenic_drm_driver_data = { 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 = { static const struct drm_encoder_helper_funcs ingenic_drm_encoder_helper_funcs = {
.atomic_mode_set = ingenic_drm_encoder_atomic_mode_set, .atomic_mode_set = ingenic_drm_encoder_atomic_mode_set,
.atomic_check = ingenic_drm_encoder_atomic_check, };
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 = { 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, .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) static void ingenic_drm_unbind_all(void *d)
{ {
struct ingenic_drm *priv = 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); 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) static int ingenic_drm_bind(struct device *dev, bool has_components)
{ {
struct platform_device *pdev = to_platform_device(dev); struct platform_device *pdev = to_platform_device(dev);
struct ingenic_drm_private_state *private_state;
const struct jz_soc_info *soc_info; const struct jz_soc_info *soc_info;
struct ingenic_drm *priv; struct ingenic_drm *priv;
struct clk *parent_clk; struct clk *parent_clk;
struct drm_plane *primary; struct drm_plane *primary;
struct drm_bridge *bridge; struct drm_bridge *bridge;
struct drm_panel *panel; struct drm_panel *panel;
struct drm_connector *connector;
struct drm_encoder *encoder; struct drm_encoder *encoder;
struct ingenic_drm_bridge *ib;
struct drm_device *drm; struct drm_device *drm;
void __iomem *base; void __iomem *base;
long parent_rate; long parent_rate;
unsigned int i, clone_mask = 0; unsigned int i, clone_mask = 0;
dma_addr_t dma_hwdesc_phys_f0, dma_hwdesc_phys_f1;
int ret, irq; int ret, irq;
soc_info = of_device_get_match_data(dev); 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) if (!priv->dma_hwdescs)
return -ENOMEM; return -ENOMEM;
/* Configure DMA hwdesc for foreground0 plane */ /* Configure DMA hwdesc for foreground0 plane */
dma_hwdesc_phys_f0 = priv->dma_hwdescs_phys ingenic_drm_configure_hwdesc_plane(priv, 0);
+ offsetof(struct ingenic_dma_hwdescs, hwdesc_f0);
priv->dma_hwdescs->hwdesc_f0.next = dma_hwdesc_phys_f0;
priv->dma_hwdescs->hwdesc_f0.id = 0xf0;
/* Configure DMA hwdesc for foreground1 plane */ /* Configure DMA hwdesc for foreground1 plane */
dma_hwdesc_phys_f1 = priv->dma_hwdescs_phys ingenic_drm_configure_hwdesc_plane(priv, 1);
+ offsetof(struct ingenic_dma_hwdescs, hwdesc_f1);
priv->dma_hwdescs->hwdesc_f1.next = dma_hwdesc_phys_f1;
priv->dma_hwdescs->hwdesc_f1.id = 0xf1;
/* Configure DMA hwdesc for palette */ /* Configure DMA hwdesc for palette */
priv->dma_hwdescs->hwdesc_pal.next = priv->dma_hwdescs_phys ingenic_drm_configure_hwdesc_palette(priv);
+ 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);
primary = priv->soc_info->has_osd ? &priv->f1 : &priv->f0; 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, bridge = devm_drm_panel_bridge_add_typed(dev, panel,
DRM_MODE_CONNECTOR_DPI); DRM_MODE_CONNECTOR_DPI);
encoder = drmm_plain_encoder_alloc(drm, NULL, DRM_MODE_ENCODER_DPI, NULL); ib = drmm_encoder_alloc(drm, struct ingenic_drm_bridge, encoder,
if (IS_ERR(encoder)) { NULL, DRM_MODE_ENCODER_DPI, NULL);
ret = PTR_ERR(encoder); if (IS_ERR(ib)) {
ret = PTR_ERR(ib);
dev_err(dev, "Failed to init encoder: %d\n", ret); dev_err(dev, "Failed to init encoder: %d\n", ret);
return 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); drm_encoder_helper_add(encoder, &ingenic_drm_encoder_helper_funcs);
ret = drm_bridge_attach(encoder, bridge, NULL, 0); ib->bridge.funcs = &ingenic_drm_bridge_funcs;
if (ret) 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; 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) { 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 */ /* Enable OSD if available */
if (soc_info->has_osd) if (soc_info->has_osd)
regmap_write(priv->map, JZ_REG_LCD_OSDC, JZ_LCD_OSDC_OSDEN); 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; 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); ret = drm_dev_register(drm, 0);
if (ret) { if (ret) {
dev_err(dev, "Failed to register DRM driver\n"); 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; return 0;
err_private_state_free:
kfree(private_state);
err_clk_notifier_unregister: err_clk_notifier_unregister:
clk_notifier_unregister(parent_clk, &priv->clock_nb); clk_notifier_unregister(parent_clk, &priv->clock_nb);
err_devclk_disable: err_devclk_disable:

View File

@ -45,6 +45,12 @@ struct soc_info {
unsigned int weight, unsigned int offset); 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 ingenic_ipu {
struct drm_plane plane; struct drm_plane plane;
struct drm_device *drm; struct drm_device *drm;
@ -54,12 +60,12 @@ struct ingenic_ipu {
const struct soc_info *soc_info; const struct soc_info *soc_info;
bool clk_enabled; bool clk_enabled;
unsigned int num_w, num_h, denom_w, denom_h;
dma_addr_t addr_y, addr_u, addr_v; dma_addr_t addr_y, addr_u, addr_v;
struct drm_property *sharpness_prop; struct drm_property *sharpness_prop;
unsigned int sharpness; unsigned int sharpness;
struct drm_private_obj private_obj;
}; };
/* Signed 15.16 fixed-point math (for bicubic scaling coefficients) */ /* 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); 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 * Apply conventional cubic convolution kernel. Both parameters
* and return value are 15.16 signed fixed-point. * 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; const struct drm_format_info *finfo;
u32 ctrl, stride = 0, coef_index = 0, format = 0; u32 ctrl, stride = 0, coef_index = 0, format = 0;
bool needs_modeset, upscaling_w, upscaling_h; bool needs_modeset, upscaling_w, upscaling_h;
struct ingenic_ipu_private_state *ipu_state;
int err; int err;
if (!newstate || !newstate->fb) if (!newstate || !newstate->fb)
return; 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); finfo = drm_format_info(newstate->fb->format->format);
if (!ipu->clk_enabled) { 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) if (ipu->soc_info->has_bicubic)
ctrl |= JZ_IPU_CTRL_ZOOM_SEL; 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) if (upscaling_w)
ctrl |= JZ_IPU_CTRL_HSCALE; 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) if (!ipu->soc_info->has_bicubic && !upscaling_w)
coef_index |= (ipu->denom_w - 1) << 16; coef_index |= (ipu_state->denom_w - 1) << 16;
else else
coef_index |= (ipu->num_w - 1) << 16; coef_index |= (ipu_state->num_w - 1) << 16;
ctrl |= JZ_IPU_CTRL_HRSZ_EN; 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) if (upscaling_h)
ctrl |= JZ_IPU_CTRL_VSCALE; 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) if (!ipu->soc_info->has_bicubic && !upscaling_h)
coef_index |= ipu->denom_h - 1; coef_index |= ipu_state->denom_h - 1;
else else
coef_index |= ipu->num_h - 1; coef_index |= ipu_state->num_h - 1;
ctrl |= JZ_IPU_CTRL_VRSZ_EN; 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 */ /* Set the LUT index register */
regmap_write(ipu->map, JZ_REG_IPU_RSZ_COEF_INDEX, coef_index); 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, 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, 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 */ /* Clear STATUS register */
regmap_write(ipu->map, JZ_REG_IPU_STATUS, 0); 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", 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->src_w >> 16, newstate->src_h >> 16,
newstate->crtc_w, newstate->crtc_h, 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, 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 ingenic_ipu *ipu = plane_to_ingenic_ipu(plane);
struct drm_crtc *crtc = new_plane_state->crtc ?: old_plane_state->crtc; struct drm_crtc *crtc = new_plane_state->crtc ?: old_plane_state->crtc;
struct drm_crtc_state *crtc_state; struct drm_crtc_state *crtc_state;
struct ingenic_ipu_private_state *ipu_state;
if (!crtc) if (!crtc)
return 0; return 0;
@ -541,6 +584,10 @@ static int ingenic_ipu_plane_atomic_check(struct drm_plane *plane,
if (WARN_ON(!crtc_state)) if (WARN_ON(!crtc_state))
return -EINVAL; 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. */ /* Request a full modeset if we are enabling or disabling the IPU. */
if (!old_plane_state->crtc ^ !new_plane_state->crtc) if (!old_plane_state->crtc ^ !new_plane_state->crtc)
crtc_state->mode_changed = true; 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) if (num_h > max_h)
return -EINVAL; return -EINVAL;
ipu->num_w = num_w; ipu_state->num_w = num_w;
ipu->num_h = num_h; ipu_state->num_h = num_h;
ipu->denom_w = denom_w; ipu_state->denom_w = denom_w;
ipu->denom_h = denom_h; ipu_state->denom_h = denom_h;
out_check_damage: out_check_damage:
if (ingenic_drm_map_noncoherent(ipu->master)) 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, .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) static irqreturn_t ingenic_ipu_irq_handler(int irq, void *arg)
{ {
struct ingenic_ipu *ipu = 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) static int ingenic_ipu_bind(struct device *dev, struct device *master, void *d)
{ {
struct platform_device *pdev = to_platform_device(dev); struct platform_device *pdev = to_platform_device(dev);
struct ingenic_ipu_private_state *private_state;
const struct soc_info *soc_info; const struct soc_info *soc_info;
struct drm_device *drm = d; struct drm_device *drm = d;
struct drm_plane *plane; struct drm_plane *plane;
@ -810,7 +885,20 @@ static int ingenic_ipu_bind(struct device *dev, struct device *master, void *d)
return err; 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; return 0;
err_clk_unprepare:
clk_unprepare(ipu->clk);
return err;
} }
static void ingenic_ipu_unbind(struct device *dev, 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); struct ingenic_ipu *ipu = dev_get_drvdata(dev);
drm_atomic_private_obj_fini(&ipu->private_obj);
clk_unprepare(ipu->clk); clk_unprepare(ipu->clk);
} }

View File

@ -15,6 +15,7 @@
#include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_helper.h>
#include <drm/drm_drv.h> #include <drm/drm_drv.h>
#include <drm/drm_fb_helper.h>
#include <drm/drm_gem_cma_helper.h> #include <drm/drm_gem_cma_helper.h>
#include <drm/drm_gem_framebuffer_helper.h> #include <drm/drm_gem_framebuffer_helper.h>
#include <drm/drm_probe_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.min_height = KMB_FB_MIN_HEIGHT;
drm->mode_config.max_width = KMB_FB_MAX_WIDTH; drm->mode_config.max_width = KMB_FB_MAX_WIDTH;
drm->mode_config.max_height = KMB_FB_MAX_HEIGHT; drm->mode_config.max_height = KMB_FB_MAX_HEIGHT;
drm->mode_config.preferred_depth = 24;
drm->mode_config.funcs = &kmb_mode_config_funcs; drm->mode_config.funcs = &kmb_mode_config_funcs;
ret = kmb_setup_crtc(drm); ret = kmb_setup_crtc(drm);
@ -559,6 +561,8 @@ static int kmb_probe(struct platform_device *pdev)
if (ret) if (ret)
goto err_register; goto err_register;
drm_fbdev_generic_setup(&kmb->drm, 0);
return 0; return 0;
err_register: err_register:

View File

@ -357,6 +357,7 @@ int lima_device_init(struct lima_device *ldev)
int err, i; int err, i;
dma_set_coherent_mask(ldev->dev, DMA_BIT_MASK(32)); dma_set_coherent_mask(ldev->dev, DMA_BIT_MASK(32));
dma_set_max_seg_size(ldev->dev, UINT_MAX);
err = lima_clk_init(ldev); err = lima_clk_init(ldev);
if (err) if (err)

View File

@ -127,7 +127,7 @@ int lima_gem_create_handle(struct drm_device *dev, struct drm_file *file,
if (err) if (err)
goto out; goto out;
} else { } 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)) { if (IS_ERR(sgt)) {
err = PTR_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)) if (!list_empty(&bo->va))
dev_err(obj->dev->dev, "lima gem free bo still has va\n"); 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) 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) if (bo->heap_size)
return -EINVAL; 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) 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) if (bo->heap_size)
return -EINVAL; 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) 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) if (bo->heap_size)
return -EINVAL; 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 = { static const struct drm_gem_object_funcs lima_gem_funcs = {
.free = lima_gem_free_object, .free = lima_gem_free_object,
.open = lima_gem_object_open, .open = lima_gem_object_open,
.close = lima_gem_object_close, .close = lima_gem_object_close,
.print_info = drm_gem_shmem_print_info, .print_info = drm_gem_shmem_object_print_info,
.pin = lima_gem_pin, .pin = lima_gem_pin,
.unpin = drm_gem_shmem_unpin, .unpin = drm_gem_shmem_object_unpin,
.get_sg_table = drm_gem_shmem_get_sg_table, .get_sg_table = drm_gem_shmem_object_get_sg_table,
.vmap = lima_gem_vmap, .vmap = lima_gem_vmap,
.vunmap = drm_gem_shmem_vunmap, .vunmap = drm_gem_shmem_object_vunmap,
.mmap = lima_gem_mmap, .mmap = lima_gem_mmap,
}; };

View File

@ -371,7 +371,7 @@ static void lima_sched_build_error_task_list(struct lima_sched_task *task)
} else { } else {
buffer_chunk->size = lima_bo_size(bo); 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) { if (ret) {
kvfree(et); kvfree(et);
goto out; 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); 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; buffer_chunk = (void *)(buffer_chunk + 1) + buffer_chunk->size;

View File

@ -6,9 +6,11 @@ config DRM_MESON
select DRM_KMS_HELPER select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER select DRM_KMS_CMA_HELPER
select DRM_GEM_CMA_HELPER select DRM_GEM_CMA_HELPER
select DRM_DISPLAY_CONNECTOR
select VIDEOMODE_HELPERS select VIDEOMODE_HELPERS
select REGMAP_MMIO select REGMAP_MMIO
select MESON_CANVAS select MESON_CANVAS
select CEC_CORE if CEC_NOTIFIER
config DRM_MESON_DW_HDMI config DRM_MESON_DW_HDMI
tristate "HDMI Synopsys Controller support for Amlogic Meson Display" tristate "HDMI Synopsys Controller support for Amlogic Meson Display"

View File

@ -1,7 +1,8 @@
# SPDX-License-Identifier: GPL-2.0-only # 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_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_rdma.o meson_osd_afbcd.o
meson-drm-y += meson_encoder_hdmi.o
obj-$(CONFIG_DRM_MESON) += meson-drm.o obj-$(CONFIG_DRM_MESON) += meson-drm.o
obj-$(CONFIG_DRM_MESON_DW_HDMI) += meson_dw_hdmi.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