mirror of
https://github.com/torvalds/linux.git
synced 2026-09-13 15:40:03 +02:00
drm-misc-next for 7.3:
UAPI Changes:
- connector: Add color format property
Cross-subsystem Changes:
- dmem: introduce a peak file
Core Changes:
- atomic: Add create_state callback and helpers to all objects, add
documentation on atomic commit lifetime
- buddy: Fix use-after-free, add per-order free and used block
scoreboards
- gem: Remove DRIVER_GEM_GPUVA feature flag
- hdmi: Hook the color format property in the helpers
- mipi-dsi: Add MIPI_DSI_MODE_DSC_ALL_SLICES_IN_PKT flag
- sched: Add test suite for concurrent job submissions
- virtio: Add support for saving and restoring virtio_gpu_objects,
abort virtqueue wait on device removal to avoid hung task
Driver Changes:
- amdgpu: Implement "color format" DRM property
- amdxdna: Disable device buffer exporting
- ethosu: Add performance counter support
- msm: Support DSC configurations with slice_per_pkt > 1
- mxsfb: Fix disable sequence
- panthor: Support sparse mappings
- rockchip: Support YUV background color, Fix layer config timeout, add
edp support for rk3576, cleanups and formats improvements
- solomon: Add a batch command submission function
- tegra: Add DSI support for Tegra 20 and 30, fix dsi driver when the
firmware hasn't enabled the controller,
- v3d: Reduce PM runtime autosuspend delay, Scheduler and submission
fixes and refactoring, Deprecate V3D 3.3 and 4.1 support
- bridge:
- display-connector: don't autoenable HPD IRQ, trigger initial HPD
event for DP
- dw-dp: Null pointer dereference and use-after-free fixes
- ti-sn65dsi83: Remove NO_HFP and NO_HBP mode flags
- panel:
- himax-hx83121a: add backlight regulator support
- novatek-nt36672a: Inline panel init sequences
- panel-edp: Add quirks for AUO B116XAT04.3, CMN N116BCP-EA2, CSW
MNB601LS1-8, BOE NV116WH2-M30, BOE NT116WHM-N21, BOE NV116FH1-M31,
BOE NV116FH1-M30, NV140FHM-N5B, TM156VDXP25
- New panels: Samsung ATNA40HQ08-0, Anbernic TD4310, Chipone
ICNA35XX, Ilitek ILI9488
-----BEGIN PGP SIGNATURE-----
iJUEABMJAB0WIQTkHFbLp4ejekA/qfgnX84Zoj2+dgUCajUI3wAKCRAnX84Zoj2+
dhr8AX4l5GUDLOz5mbUJ4r5T9EQZ0lppj0RJjASkKBsogCC8CjYTrWzRqEbidJXH
DGQ01dkBgM0/uWhVv3AZeNxov7yvzWA3JylYqy5Eg/xxLGRDXVtYT1fy9QvMSP1f
T64BIaKqkw==
=Gg4b
-----END PGP SIGNATURE-----
Merge tag 'drm-misc-next-2026-06-19' of https://gitlab.freedesktop.org/drm/misc/kernel into drm-next
drm-misc-next for 7.3:
UAPI Changes:
- connector: Add color format property
Cross-subsystem Changes:
- dmem: introduce a peak file
Core Changes:
- atomic: Add create_state callback and helpers to all objects, add
documentation on atomic commit lifetime
- buddy: Fix use-after-free, add per-order free and used block
scoreboards
- gem: Remove DRIVER_GEM_GPUVA feature flag
- hdmi: Hook the color format property in the helpers
- mipi-dsi: Add MIPI_DSI_MODE_DSC_ALL_SLICES_IN_PKT flag
- sched: Add test suite for concurrent job submissions
- virtio: Add support for saving and restoring virtio_gpu_objects,
abort virtqueue wait on device removal to avoid hung task
Driver Changes:
- amdgpu: Implement "color format" DRM property
- amdxdna: Disable device buffer exporting
- ethosu: Add performance counter support
- msm: Support DSC configurations with slice_per_pkt > 1
- mxsfb: Fix disable sequence
- panthor: Support sparse mappings
- rockchip: Support YUV background color, Fix layer config timeout, add
edp support for rk3576, cleanups and formats improvements
- solomon: Add a batch command submission function
- tegra: Add DSI support for Tegra 20 and 30, fix dsi driver when the
firmware hasn't enabled the controller,
- v3d: Reduce PM runtime autosuspend delay, Scheduler and submission
fixes and refactoring, Deprecate V3D 3.3 and 4.1 support
- bridge:
- display-connector: don't autoenable HPD IRQ, trigger initial HPD
event for DP
- dw-dp: Null pointer dereference and use-after-free fixes
- ti-sn65dsi83: Remove NO_HFP and NO_HBP mode flags
- panel:
- himax-hx83121a: add backlight regulator support
- novatek-nt36672a: Inline panel init sequences
- panel-edp: Add quirks for AUO B116XAT04.3, CMN N116BCP-EA2, CSW
MNB601LS1-8, BOE NV116WH2-M30, BOE NT116WHM-N21, BOE NV116FH1-M31,
BOE NV116FH1-M30, NV140FHM-N5B, TM156VDXP25
- New panels: Samsung ATNA40HQ08-0, Anbernic TD4310, Chipone
ICNA35XX, Ilitek ILI9488
Signed-off-by: Dave Airlie <airlied@redhat.com>
From: Maxime Ripard <mripard@redhat.com>
Link: https://patch.msgid.link/20260619-burgundy-termite-of-whirlwind-f7a4dd@houat
This commit is contained in:
commit
00422c79d0
|
|
@ -2861,6 +2861,12 @@ DMEM Interface Files
|
|||
The semantics are the same as for the memory cgroup controller, and are
|
||||
calculated in the same way.
|
||||
|
||||
dmem.peak
|
||||
A read-only nested-keyed file that exists on non-root cgroups.
|
||||
|
||||
The max device memory usage recorded for the cgroup and its
|
||||
descendants since the creation of the cgroup for each region.
|
||||
|
||||
dmem.capacity
|
||||
A read-only file that describes maximum region capacity.
|
||||
It only exists on the root cgroup. Not all memory can be
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/panel/anbernic,td4310.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Anbernic TD4310 Based Panels
|
||||
|
||||
maintainers:
|
||||
- Chris Morgan <macromorgan@hotmail.com>
|
||||
|
||||
description:
|
||||
Anbernic TD4310 Based Panels, such as the RG-Vita-Pro panel
|
||||
(a 1080x1920 5.5 inch panel).
|
||||
|
||||
allOf:
|
||||
- $ref: panel-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- enum:
|
||||
- anbernic,panel-vita-pro
|
||||
- const: anbernic,td4310
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
vdd-supply:
|
||||
description: Panel power supply
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- port
|
||||
- reg
|
||||
- reset-gpios
|
||||
- vdd-supply
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
dsi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
panel@0 {
|
||||
compatible = "anbernic,panel-vita-pro", "anbernic,td4310";
|
||||
reg = <0>;
|
||||
backlight = <&backlight>;
|
||||
enable-gpios = <&gpio0 9 GPIO_ACTIVE_HIGH>;
|
||||
reset-gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
|
||||
rotation = <270>;
|
||||
vdd-supply = <&vdd_lcd>;
|
||||
|
||||
port {
|
||||
endpoint {
|
||||
remote-endpoint = <&dsi_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/panel/chipone,icna3512.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Chipone ICNA3512 and ICNA3520 display drivers
|
||||
|
||||
maintainers:
|
||||
- Neil Armstrong <neil.armstrong@linaro.org>
|
||||
|
||||
description:
|
||||
The Chipone ICNA3512 and ICNA3520 are DDICs connected
|
||||
using a MIPI-DSI video interface.
|
||||
|
||||
allOf:
|
||||
- $ref: panel-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
oneOf:
|
||||
- items:
|
||||
- enum:
|
||||
- ayaneo,pocketds-panel-top
|
||||
- ayntec,odin2portal-panel
|
||||
- const: chipone,icna3512
|
||||
|
||||
- items:
|
||||
- enum:
|
||||
- ayntec,odin3-panel
|
||||
- ayntec,thor-panel-top
|
||||
- const: chipone,icna3520
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
description: DSI virtual channel
|
||||
|
||||
vdd-supply: true
|
||||
vddio-supply: true
|
||||
vci-supply: true
|
||||
disp-supply: true
|
||||
blvdd-supply: true
|
||||
|
||||
port: true
|
||||
reset-gpios: true
|
||||
rotation: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- vdd-supply
|
||||
- vddio-supply
|
||||
- vci-supply
|
||||
- disp-supply
|
||||
- blvdd-supply
|
||||
- reset-gpios
|
||||
|
||||
additionalProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
dsi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
panel@0 {
|
||||
compatible = "ayntec,odin2portal-panel", "chipone,icna3512";
|
||||
reg = <0>;
|
||||
vdd-supply = <&vreg_l11b_1p2>;
|
||||
vddio-supply = <&vreg_l12b_1p8>;
|
||||
vci-supply = <&vreg_l13b_3p0>;
|
||||
disp-supply = <&vdd_disp_2v8>;
|
||||
blvdd-supply = <&vdd_bl_5v0>;
|
||||
reset-gpios = <&tlmm 133 GPIO_ACTIVE_LOW>;
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
|
|
@ -40,6 +40,9 @@ properties:
|
|||
vddi-supply:
|
||||
description: power supply for IC
|
||||
|
||||
bl-supply:
|
||||
description: power supply for backlight, in case it's managed via DSC
|
||||
|
||||
backlight: true
|
||||
ports: true
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
|
||||
%YAML 1.2
|
||||
---
|
||||
$id: http://devicetree.org/schemas/display/panel/ilitek,ili9488.yaml#
|
||||
$schema: http://devicetree.org/meta-schemas/core.yaml#
|
||||
|
||||
title: Ilitek ILI9488 based MIPI-DSI panels
|
||||
|
||||
maintainers:
|
||||
- Igor Reznichenko <igor@reznichenko.net>
|
||||
|
||||
allOf:
|
||||
- $ref: panel-common.yaml#
|
||||
|
||||
properties:
|
||||
compatible:
|
||||
items:
|
||||
- enum:
|
||||
- focuslcds,e35gh-i-mw800cb
|
||||
- const: ilitek,ili9488
|
||||
|
||||
reg:
|
||||
maxItems: 1
|
||||
|
||||
vci-supply: true
|
||||
iovcc-supply: true
|
||||
|
||||
required:
|
||||
- compatible
|
||||
- reg
|
||||
- vci-supply
|
||||
- iovcc-supply
|
||||
- reset-gpios
|
||||
- backlight
|
||||
- port
|
||||
|
||||
unevaluatedProperties: false
|
||||
|
||||
examples:
|
||||
- |
|
||||
#include <dt-bindings/gpio/gpio.h>
|
||||
|
||||
dsi {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
|
||||
panel@0 {
|
||||
compatible = "focuslcds,e35gh-i-mw800cb", "ilitek,ili9488";
|
||||
reg = <0>;
|
||||
vci-supply = <®_vci_panel>;
|
||||
iovcc-supply = <®_iovcc_panel>;
|
||||
reset-gpios = <&gpio3 6 GPIO_ACTIVE_LOW>;
|
||||
backlight = <&pwm_bl>;
|
||||
|
||||
port {
|
||||
panel_in: endpoint {
|
||||
remote-endpoint = <&dsi_out>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
...
|
||||
|
|
@ -25,6 +25,8 @@ properties:
|
|||
- samsung,atna40ct06
|
||||
# Samsung 14" WQXGA+ (2880x1800 pixels) eDP AMOLED panel
|
||||
- samsung,atna40cu11
|
||||
# Samsung 14" WQXGA+ (2880x1800 pixels) eDP AMOLED panel
|
||||
- samsung,atna40hq08
|
||||
# Samsung 14" WQXGA+ (2880×1800 pixels) eDP AMOLED panel
|
||||
- samsung,atna40yk20
|
||||
# Samsung 14.5" WQXGA+ (2880x1800 pixels) eDP AMOLED panel
|
||||
|
|
|
|||
|
|
@ -15,18 +15,24 @@ properties:
|
|||
enum:
|
||||
- rockchip,rk3288-dp
|
||||
- rockchip,rk3399-edp
|
||||
- rockchip,rk3576-edp
|
||||
- rockchip,rk3588-edp
|
||||
|
||||
clocks:
|
||||
minItems: 2
|
||||
maxItems: 3
|
||||
items:
|
||||
- description: Reference clock
|
||||
- description: APB bus clock
|
||||
- description: GRF or AHB bus clock
|
||||
|
||||
clock-names:
|
||||
minItems: 2
|
||||
items:
|
||||
- const: dp
|
||||
- const: pclk
|
||||
- const: grf
|
||||
- enum:
|
||||
- grf
|
||||
- hclk
|
||||
|
||||
power-domains:
|
||||
maxItems: 1
|
||||
|
|
@ -65,9 +71,46 @@ allOf:
|
|||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- rockchip,rk3288-dp
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
maxItems: 2
|
||||
clock-names:
|
||||
maxItems: 2
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- rockchip,rk3399-edp
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
minItems: 3
|
||||
clock-names:
|
||||
items:
|
||||
- const: dp
|
||||
- const: pclk
|
||||
- const: grf
|
||||
|
||||
- if:
|
||||
properties:
|
||||
compatible:
|
||||
contains:
|
||||
enum:
|
||||
- rockchip,rk3576-edp
|
||||
- rockchip,rk3588-edp
|
||||
then:
|
||||
properties:
|
||||
clocks:
|
||||
minItems: 3
|
||||
clock-names:
|
||||
items:
|
||||
- const: dp
|
||||
- const: pclk
|
||||
- const: hclk
|
||||
resets:
|
||||
minItems: 2
|
||||
reset-names:
|
||||
|
|
|
|||
|
|
@ -225,6 +225,8 @@ patternProperties:
|
|||
description: Axis Communications AB
|
||||
"^ayaneo,.*":
|
||||
description: Anyun Intelligent Technology (Hong Kong) Co., Ltd
|
||||
"^ayntec,.*":
|
||||
description: AYN Technologies Co., Ltd.
|
||||
"^azoteq,.*":
|
||||
description: Azoteq (Pty) Ltd
|
||||
"^azw,.*":
|
||||
|
|
@ -604,6 +606,8 @@ patternProperties:
|
|||
description: Flipkart Inc.
|
||||
"^focaltech,.*":
|
||||
description: FocalTech Systems Co.,Ltd
|
||||
"^focuslcds,.*":
|
||||
description: Focus Display Solutions, Inc.
|
||||
"^forlinx,.*":
|
||||
description: Baoding Forlinx Embedded Technology Co., Ltd.
|
||||
"^foursemi,.*":
|
||||
|
|
@ -1411,6 +1415,8 @@ patternProperties:
|
|||
description: Embest RIoT
|
||||
"^riscv,.*":
|
||||
description: RISC-V Foundation
|
||||
"^riverdi,.*":
|
||||
description: Riverdi Sp. z o.o
|
||||
"^rockchip,.*":
|
||||
description: Rockchip Electronics Co., Ltd.
|
||||
"^rocktech,.*":
|
||||
|
|
|
|||
|
|
@ -99,7 +99,8 @@ How to enable automated testing on your tree
|
|||
============================================
|
||||
|
||||
1. Create a Linux tree in https://gitlab.freedesktop.org/ if you don't have one
|
||||
yet
|
||||
yet, by forking https://gitlab.freedesktop.org/drm/kernel (this allows GitLab
|
||||
to internally track that these are the same git objects).
|
||||
|
||||
2. In your kernel repo's configuration (eg.
|
||||
https://gitlab.freedesktop.org/janedoe/linux/-/settings/ci_cd), change the
|
||||
|
|
|
|||
|
|
@ -171,6 +171,12 @@ Bridge Operations
|
|||
.. kernel-doc:: drivers/gpu/drm/drm_bridge.c
|
||||
:doc: bridge operations
|
||||
|
||||
Bridge Chain Format Selection
|
||||
-----------------------------
|
||||
|
||||
.. kernel-doc:: drivers/gpu/drm/drm_bridge.c
|
||||
:doc: bridge chain format selection
|
||||
|
||||
Bridge Connector Helper
|
||||
-----------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -287,6 +287,12 @@ structure, ordering of committing state changes to hardware is sequenced using
|
|||
Read on in this chapter, and also in :ref:`drm_atomic_helper` for more detailed
|
||||
coverage of specific topics.
|
||||
|
||||
Atomic State Lifetime
|
||||
---------------------
|
||||
|
||||
.. kernel-doc:: drivers/gpu/drm/drm_atomic.c
|
||||
:doc: state lifetime
|
||||
|
||||
Handling Driver Private State
|
||||
-----------------------------
|
||||
|
||||
|
|
@ -604,6 +610,12 @@ Color Management Properties
|
|||
.. kernel-doc:: drivers/gpu/drm/drm_color_mgmt.c
|
||||
:doc: overview
|
||||
|
||||
Color Format Property
|
||||
---------------------
|
||||
|
||||
.. kernel-doc:: drivers/gpu/drm/drm_connector.c
|
||||
:doc: Color format
|
||||
|
||||
Tile Group Property
|
||||
-------------------
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ There are still drivers that use drm_simple_display_pipe. The task here is to
|
|||
convert them to use regular atomic helpers. Search for a driver that calls
|
||||
drm_simple_display_pipe_init() and inline all helpers from drm_simple_kms_helper.c
|
||||
into the driver, such that no simple-KMS interfaces are required. Please also
|
||||
rename all inlined fucntions according to driver conventions.
|
||||
rename all inlined functions according to driver conventions.
|
||||
|
||||
Contact: Thomas Zimmermann, respective driver maintainer
|
||||
|
||||
|
|
@ -948,6 +948,47 @@ Contact: Philipp Stanner <phasta@kernel.org>
|
|||
|
||||
Level: Intermediate
|
||||
|
||||
Replace the lockless queue with a locked list
|
||||
---------------------------------------------
|
||||
|
||||
drm_sched is the only user in the entire kernel of a special lockless queue, the
|
||||
spsc_queue. This queue utilizes:
|
||||
|
||||
- preempt_disable()
|
||||
- atomic instructions
|
||||
- memory barriers
|
||||
- ACCESS_ONCE()
|
||||
|
||||
whereas a conventional spinlock utilizes:
|
||||
|
||||
- preempt_disable()
|
||||
- 1 atomic instruction for taking / releasing the lock
|
||||
- memory barriers
|
||||
|
||||
Moreover, drm_sched_entity_push_job(), the only user of spsc_queue_push(), has
|
||||
to take a lock in some situations anyways and calls to it are often serialized
|
||||
with a driver lock.
|
||||
|
||||
It is, thus, highly questionable whether the lockless queue grants any advantage
|
||||
at all. Considering that its internals are not well documented and its correctness
|
||||
is not formally proven, it seems desirable to replace the queue with a mere list
|
||||
or hlist that is protected by a spinlock.
|
||||
|
||||
Tasks:
|
||||
|
||||
- Replace the spsc_queue in drm/sched (and those who might access the scheduler's
|
||||
internal queue) with a spinlock + (h)list.
|
||||
- Ideally, check with some micro benchmarks and real world tests (preferably
|
||||
with amdgpu) for relevant performance regressions.
|
||||
- Remove the spsc_queue from the kernel altogether.
|
||||
|
||||
Contact:
|
||||
|
||||
- Philipp Stanner <phasta@kernel.org>
|
||||
- Christian König <christian.koenig@amd.com>
|
||||
|
||||
Level: Beginner
|
||||
|
||||
Outside DRM
|
||||
===========
|
||||
|
||||
|
|
|
|||
|
|
@ -8120,6 +8120,12 @@ T: git https://gitlab.freedesktop.org/drm/misc/kernel.git
|
|||
F: Documentation/devicetree/bindings/display/ilitek,ili9486.yaml
|
||||
F: drivers/gpu/drm/tiny/ili9486.c
|
||||
|
||||
DRM DRIVER FOR ILITEK ILI9488 PANELS
|
||||
M: Igor Reznichenko <igor@reznichenko.net>
|
||||
S: Maintained
|
||||
F: Documentation/devicetree/bindings/display/panel/ilitek,ili9488.yaml
|
||||
F: drivers/gpu/drm/panel/panel-ilitek-ili9488.c
|
||||
|
||||
DRM DRIVER FOR ILITEK ILI9805 PANELS
|
||||
M: Michael Trimarchi <michael@amarulasolutions.com>
|
||||
S: Maintained
|
||||
|
|
|
|||
|
|
@ -731,9 +731,15 @@ static int amdxdna_gem_dev_obj_vmap(struct drm_gem_object *obj, struct iosys_map
|
|||
return 0;
|
||||
}
|
||||
|
||||
static struct dma_buf *amdxdna_gem_dev_obj_export(struct drm_gem_object *gobj, int flags)
|
||||
{
|
||||
return ERR_PTR(-EOPNOTSUPP);
|
||||
}
|
||||
|
||||
static const struct drm_gem_object_funcs amdxdna_gem_dev_obj_funcs = {
|
||||
.free = amdxdna_gem_dev_obj_free,
|
||||
.vmap = amdxdna_gem_dev_obj_vmap,
|
||||
.export = amdxdna_gem_dev_obj_export,
|
||||
};
|
||||
|
||||
static const struct drm_gem_object_funcs amdxdna_gem_shmem_funcs = {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
obj-$(CONFIG_DRM_ACCEL_ARM_ETHOSU) := ethosu.o
|
||||
ethosu-y += ethosu_drv.o ethosu_gem.o ethosu_job.o
|
||||
ethosu-y += ethosu_drv.o ethosu_gem.o ethosu_job.o ethosu_perfmon.o
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/bits.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#include <drm/drm_device.h>
|
||||
|
|
@ -43,6 +44,15 @@ struct gen_pool;
|
|||
#define NPU_REG_BASEP_HI(x) (0x0084 + (x) * 8)
|
||||
#define NPU_BASEP_REGION_MAX 8
|
||||
|
||||
#define NPU_REG_PMCR 0x0180
|
||||
#define NPU_REG_PMCNTENSET 0x0184
|
||||
#define NPU_REG_PMCNTENCLR 0x0188
|
||||
#define NPU_REG_PMCCNTR_LO 0x01A0
|
||||
#define NPU_REG_PMCCNTR_HI 0x01A4
|
||||
#define NPU_REG_PMCCNTR_CFG 0x01A8
|
||||
#define NPU_REG_PMU_EVCNTR(x) (0x0300 + (x) * 4)
|
||||
#define NPU_REG_PMU_EVTYPER(x) (0x0380 + (x) * 4)
|
||||
|
||||
#define ID_ARCH_MAJOR_MASK GENMASK(31, 28)
|
||||
#define ID_ARCH_MINOR_MASK GENMASK(27, 20)
|
||||
#define ID_ARCH_PATCH_MASK GENMASK(19, 16)
|
||||
|
|
@ -67,6 +77,15 @@ struct gen_pool;
|
|||
|
||||
#define PROT_ACTIVE_CSL BIT(1)
|
||||
|
||||
#define PMCR_NUM_EVENT_CNT_MASK GENMASK(15, 11)
|
||||
#define PMCR_CYCLE_CNT_RST BIT(2)
|
||||
#define PMCR_EVENT_CNT_RST BIT(1)
|
||||
#define PMCR_CNT_EN BIT(0)
|
||||
|
||||
#define PMU_EV_TYPE_NONE 0
|
||||
#define PMU_EV_TYPE_CYCLES 0x11
|
||||
#define PMU_EV_TYPE_IDLE 0x20
|
||||
|
||||
enum ethosu_cmds {
|
||||
NPU_OP_CONV = 0x2,
|
||||
NPU_OP_DEPTHWISE = 0x3,
|
||||
|
|
@ -152,6 +171,8 @@ enum ethosu_cmds {
|
|||
|
||||
#define ETHOSU_SRAM_REGION 2 /* Matching Vela compiler */
|
||||
|
||||
struct ethosu_perfmon;
|
||||
|
||||
/**
|
||||
* struct ethosu_device - Ethosu device
|
||||
*/
|
||||
|
|
@ -161,6 +182,7 @@ struct ethosu_device {
|
|||
|
||||
/** @iomem: CPU mapping of the registers. */
|
||||
void __iomem *regs;
|
||||
void __iomem *pmu_regs;
|
||||
|
||||
void __iomem *sram;
|
||||
struct gen_pool *srampool;
|
||||
|
|
@ -173,8 +195,6 @@ struct ethosu_device {
|
|||
struct drm_ethosu_npu_info npu_info;
|
||||
|
||||
struct ethosu_job *in_flight_job;
|
||||
/* For in_flight_job and ethosu_job_hw_submit() */
|
||||
struct mutex job_lock;
|
||||
|
||||
/* For dma_fence */
|
||||
spinlock_t fence_lock;
|
||||
|
|
@ -184,6 +204,17 @@ struct ethosu_device {
|
|||
struct mutex sched_lock;
|
||||
u64 fence_context;
|
||||
u64 emit_seqno;
|
||||
|
||||
/* Tracks the performance monitor state. */
|
||||
struct {
|
||||
/* Protects @active. */
|
||||
struct mutex lock;
|
||||
|
||||
/* Perfmon currently programmed in HW (or NULL if none). */
|
||||
struct ethosu_perfmon *active;
|
||||
} perfmon_state;
|
||||
|
||||
struct ethosu_perfmon *global_perfmon;
|
||||
};
|
||||
|
||||
#define to_ethosu_device(drm_dev) \
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <drm/drm_utils.h>
|
||||
#include <drm/drm_gem.h>
|
||||
#include <drm/drm_accel.h>
|
||||
#include <drm/drm_managed.h>
|
||||
#include <drm/ethosu_accel.h>
|
||||
|
||||
#include "ethosu_drv.h"
|
||||
|
|
@ -154,6 +155,7 @@ static int ethosu_open(struct drm_device *ddev, struct drm_file *file)
|
|||
if (ret)
|
||||
goto err_put_mod;
|
||||
|
||||
ethosu_perfmon_open_file(priv);
|
||||
file->driver_priv = no_free_ptr(priv);
|
||||
return 0;
|
||||
|
||||
|
|
@ -165,6 +167,7 @@ static int ethosu_open(struct drm_device *ddev, struct drm_file *file)
|
|||
static void ethosu_postclose(struct drm_device *ddev, struct drm_file *file)
|
||||
{
|
||||
ethosu_job_close(file->driver_priv);
|
||||
ethosu_perfmon_close_file(file->driver_priv);
|
||||
kfree(file->driver_priv);
|
||||
module_put(THIS_MODULE);
|
||||
}
|
||||
|
|
@ -179,6 +182,10 @@ static const struct drm_ioctl_desc ethosu_drm_driver_ioctls[] = {
|
|||
ETHOSU_IOCTL(BO_MMAP_OFFSET, bo_mmap_offset, 0),
|
||||
ETHOSU_IOCTL(CMDSTREAM_BO_CREATE, cmdstream_bo_create, 0),
|
||||
ETHOSU_IOCTL(SUBMIT, submit, 0),
|
||||
ETHOSU_IOCTL(PERFMON_CREATE, perfmon_create, 0),
|
||||
ETHOSU_IOCTL(PERFMON_DESTROY, perfmon_destroy, 0),
|
||||
ETHOSU_IOCTL(PERFMON_GET_VALUES, perfmon_get_values, 0),
|
||||
ETHOSU_IOCTL(PERFMON_SET_GLOBAL, perfmon_set_global, 0),
|
||||
};
|
||||
|
||||
DEFINE_DRM_ACCEL_FOPS(ethosu_drm_driver_fops);
|
||||
|
|
@ -314,8 +321,14 @@ static int ethosu_init(struct ethosu_device *ethosudev)
|
|||
|
||||
ethosu_sram_init(ethosudev);
|
||||
|
||||
if (!ethosu_is_u65(ethosudev))
|
||||
ethosudev->pmu_regs += 0x1000;
|
||||
|
||||
ethosudev->npu_info.pmu_counters = FIELD_GET(PMCR_NUM_EVENT_CNT_MASK,
|
||||
readl_relaxed(ethosudev->pmu_regs + NPU_REG_PMCR));
|
||||
|
||||
dev_info(ethosudev->base.dev,
|
||||
"Ethos-U NPU, arch v%ld.%ld.%ld, rev r%ldp%ld, cmd stream ver%ld, %d MACs, %dKB SRAM\n",
|
||||
"Ethos-U NPU, arch v%ld.%ld.%ld, rev r%ldp%ld, cmd stream ver%ld, %d MACs, %dKB SRAM, %d PMU cntrs\n",
|
||||
FIELD_GET(ID_ARCH_MAJOR_MASK, id),
|
||||
FIELD_GET(ID_ARCH_MINOR_MASK, id),
|
||||
FIELD_GET(ID_ARCH_PATCH_MASK, id),
|
||||
|
|
@ -323,7 +336,8 @@ static int ethosu_init(struct ethosu_device *ethosudev)
|
|||
FIELD_GET(ID_VER_MINOR_MASK, id),
|
||||
FIELD_GET(CONFIG_CMD_STREAM_VER_MASK, config),
|
||||
1 << FIELD_GET(CONFIG_MACS_PER_CC_MASK, config),
|
||||
ethosudev->npu_info.sram_size / 1024);
|
||||
ethosudev->npu_info.sram_size / 1024,
|
||||
ethosudev->npu_info.pmu_counters);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -342,11 +356,16 @@ static int ethosu_probe(struct platform_device *pdev)
|
|||
dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40));
|
||||
|
||||
ethosudev->regs = devm_platform_ioremap_resource(pdev, 0);
|
||||
ethosudev->pmu_regs = ethosudev->regs;
|
||||
|
||||
ethosudev->num_clks = devm_clk_bulk_get_all(&pdev->dev, ðosudev->clks);
|
||||
if (ethosudev->num_clks < 0)
|
||||
return ethosudev->num_clks;
|
||||
|
||||
ret = drmm_mutex_init(ðosudev->base, ðosudev->perfmon_state.lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ethosu_job_init(ethosudev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,74 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
|
||||
/* Copyright 2025 Arm, Ltd. */
|
||||
/* Copyright 2025-2026 Arm, Ltd. */
|
||||
#ifndef __ETHOSU_DRV_H__
|
||||
#define __ETHOSU_DRV_H__
|
||||
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/xarray.h>
|
||||
#include <drm/gpu_scheduler.h>
|
||||
|
||||
struct ethosu_device;
|
||||
struct drm_device;
|
||||
struct drm_file;
|
||||
|
||||
struct ethosu_file_priv {
|
||||
struct ethosu_device *edev;
|
||||
struct drm_sched_entity sched_entity;
|
||||
struct xarray perfmons;
|
||||
};
|
||||
|
||||
/* Performance monitor object. The perfmon lifetime is controlled by userspace
|
||||
* using perfmon related ioctls. A perfmon can be attached to a DRM_ETHOSU_SUBMIT
|
||||
* request, and when this is the case, HW perf counters will be activated just
|
||||
* before the job is submitted to the NPU and disabled when the job is
|
||||
* done. This way, only events related to a specific job will be counted.
|
||||
*/
|
||||
struct ethosu_perfmon {
|
||||
/* Tracks the number of users of the perfmon, when this counter reaches
|
||||
* zero the perfmon is destroyed.
|
||||
*/
|
||||
refcount_t refcnt;
|
||||
|
||||
/* Number of counters activated in this perfmon instance
|
||||
* (should be less than or equal to DRM_ETHOSU_MAX_PERF_COUNTERS).
|
||||
*/
|
||||
u8 ncounters;
|
||||
|
||||
/* Events counted by the HW perf counters. */
|
||||
u16 counters[DRM_ETHOSU_MAX_PERF_EVENT_COUNTERS];
|
||||
|
||||
/*
|
||||
* Storage for counter values. Counters are incremented by the HW
|
||||
* perf counter values every time the perfmon is attached to an
|
||||
* NPU job. This way, perfmon users don't have to retrieve the
|
||||
* results after each job if they want to track events covering
|
||||
* several submissions. Note that counter values can't be reset,
|
||||
* but you can fake a reset by destroying the perfmon and
|
||||
* creating a new one.
|
||||
*/
|
||||
u64 values[] __counted_by(ncounters);
|
||||
};
|
||||
|
||||
/* ethosu_perfmon.c */
|
||||
void ethosu_perfmon_get(struct ethosu_perfmon *perfmon);
|
||||
void ethosu_perfmon_put(struct ethosu_perfmon *perfmon);
|
||||
void ethosu_perfmon_start(struct ethosu_device *ethosu,
|
||||
struct ethosu_perfmon *perfmon);
|
||||
void ethosu_perfmon_stop(struct ethosu_device *ethosu,
|
||||
struct ethosu_perfmon *perfmon, bool capture);
|
||||
void ethosu_perfmon_stop_locked(struct ethosu_device *ethosu, struct ethosu_perfmon *perfmon,
|
||||
bool capture);
|
||||
struct ethosu_perfmon *ethosu_perfmon_find(struct ethosu_file_priv *ethosu_priv,
|
||||
int id);
|
||||
void ethosu_perfmon_open_file(struct ethosu_file_priv *ethosu_priv);
|
||||
void ethosu_perfmon_close_file(struct ethosu_file_priv *ethosu_priv);
|
||||
int ethosu_ioctl_perfmon_create(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv);
|
||||
int ethosu_ioctl_perfmon_destroy(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv);
|
||||
int ethosu_ioctl_perfmon_get_values(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv);
|
||||
int ethosu_ioctl_perfmon_set_global(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only OR MIT
|
||||
/* Copyright 2024-2025 Tomeu Vizoso <tomeu@tomeuvizoso.net> */
|
||||
/* Copyright 2025 Arm, Ltd. */
|
||||
/* Copyright 2025-2026 Arm, Ltd. */
|
||||
|
||||
#include <linux/bitfield.h>
|
||||
#include <linux/genalloc.h>
|
||||
|
|
@ -147,6 +147,8 @@ static void ethosu_job_err_cleanup(struct ethosu_job *job)
|
|||
{
|
||||
unsigned int i;
|
||||
|
||||
ethosu_perfmon_put(job->perfmon);
|
||||
|
||||
for (i = 0; i < job->region_cnt; i++)
|
||||
drm_gem_object_put(job->region_bo[i]);
|
||||
|
||||
|
|
@ -181,6 +183,26 @@ static void ethosu_job_free(struct drm_sched_job *sched_job)
|
|||
ethosu_job_put(job);
|
||||
}
|
||||
|
||||
static void
|
||||
ethosu_switch_perfmon(struct ethosu_device *ethosu, struct ethosu_job *job)
|
||||
{
|
||||
struct ethosu_perfmon *perfmon;
|
||||
|
||||
guard(mutex)(ðosu->perfmon_state.lock);
|
||||
|
||||
perfmon = ethosu->global_perfmon;
|
||||
if (!perfmon)
|
||||
perfmon = job->perfmon;
|
||||
|
||||
if (perfmon == ethosu->perfmon_state.active)
|
||||
return;
|
||||
|
||||
ethosu_perfmon_stop_locked(ethosu, ethosu->perfmon_state.active, true);
|
||||
|
||||
if (perfmon)
|
||||
ethosu_perfmon_start(ethosu, perfmon);
|
||||
}
|
||||
|
||||
static struct dma_fence *ethosu_job_run(struct drm_sched_job *sched_job)
|
||||
{
|
||||
struct ethosu_job *job = to_ethosu_job(sched_job);
|
||||
|
|
@ -194,10 +216,10 @@ static struct dma_fence *ethosu_job_run(struct drm_sched_job *sched_job)
|
|||
dev->fence_context, ++dev->emit_seqno);
|
||||
dma_fence_get(fence);
|
||||
|
||||
scoped_guard(mutex, &dev->job_lock) {
|
||||
dev->in_flight_job = job;
|
||||
ethosu_job_hw_submit(dev, job);
|
||||
}
|
||||
ethosu_switch_perfmon(dev, job);
|
||||
|
||||
WRITE_ONCE(dev->in_flight_job, job);
|
||||
ethosu_job_hw_submit(dev, job);
|
||||
|
||||
return fence;
|
||||
}
|
||||
|
|
@ -205,6 +227,7 @@ static struct dma_fence *ethosu_job_run(struct drm_sched_job *sched_job)
|
|||
static void ethosu_job_handle_irq(struct ethosu_device *dev)
|
||||
{
|
||||
u32 status = readl_relaxed(dev->regs + NPU_REG_STATUS);
|
||||
struct ethosu_job *job;
|
||||
|
||||
if (status & (STATUS_BUS_STATUS | STATUS_CMD_PARSE_ERR)) {
|
||||
dev_err(dev->base.dev, "Error IRQ - %x\n", status);
|
||||
|
|
@ -212,11 +235,10 @@ static void ethosu_job_handle_irq(struct ethosu_device *dev)
|
|||
return;
|
||||
}
|
||||
|
||||
scoped_guard(mutex, &dev->job_lock) {
|
||||
if (dev->in_flight_job) {
|
||||
dma_fence_signal(dev->in_flight_job->done_fence);
|
||||
dev->in_flight_job = NULL;
|
||||
}
|
||||
job = READ_ONCE(dev->in_flight_job);
|
||||
if (job) {
|
||||
WRITE_ONCE(dev->in_flight_job, NULL);
|
||||
dma_fence_signal(job->done_fence);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -272,8 +294,7 @@ static enum drm_gpu_sched_stat ethosu_job_timedout(struct drm_sched_job *bad)
|
|||
|
||||
drm_sched_stop(&dev->sched, bad);
|
||||
|
||||
scoped_guard(mutex, &dev->job_lock)
|
||||
dev->in_flight_job = NULL;
|
||||
WRITE_ONCE(dev->in_flight_job, NULL);
|
||||
|
||||
/* Proceed with reset now. */
|
||||
pm_runtime_force_suspend(dev->base.dev);
|
||||
|
|
@ -304,9 +325,6 @@ int ethosu_job_init(struct ethosu_device *edev)
|
|||
int ret;
|
||||
|
||||
spin_lock_init(&edev->fence_lock);
|
||||
ret = devm_mutex_init(dev, &edev->job_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = devm_mutex_init(dev, &edev->sched_lock);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
|
@ -365,7 +383,8 @@ void ethosu_job_close(struct ethosu_file_priv *ethosu_priv)
|
|||
}
|
||||
|
||||
static int ethosu_ioctl_submit_job(struct drm_device *dev, struct drm_file *file,
|
||||
struct drm_ethosu_job *job)
|
||||
struct drm_ethosu_job *job,
|
||||
int perfmon_id)
|
||||
{
|
||||
struct ethosu_device *edev = to_ethosu_device(dev);
|
||||
struct ethosu_file_priv *file_priv = file->driver_priv;
|
||||
|
|
@ -389,6 +408,9 @@ static int ethosu_ioctl_submit_job(struct drm_device *dev, struct drm_file *file
|
|||
ejob->dev = edev;
|
||||
ejob->sram_size = job->sram_size;
|
||||
|
||||
if (perfmon_id)
|
||||
ejob->perfmon = ethosu_perfmon_find(file_priv, perfmon_id);
|
||||
|
||||
ejob->done_fence = kzalloc_obj(*ejob->done_fence);
|
||||
if (!ejob->done_fence) {
|
||||
ret = -ENOMEM;
|
||||
|
|
@ -491,11 +513,6 @@ int ethosu_ioctl_submit(struct drm_device *dev, void *data, struct drm_file *fil
|
|||
int ret = 0;
|
||||
unsigned int i = 0;
|
||||
|
||||
if (args->pad) {
|
||||
drm_dbg(dev, "Reserved field in drm_ethosu_submit struct should be 0.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
struct drm_ethosu_job __free(kvfree) *jobs =
|
||||
kvmalloc_objs(*jobs, args->job_count);
|
||||
if (!jobs)
|
||||
|
|
@ -509,7 +526,7 @@ int ethosu_ioctl_submit(struct drm_device *dev, void *data, struct drm_file *fil
|
|||
}
|
||||
|
||||
for (i = 0; i < args->job_count; i++) {
|
||||
ret = ethosu_ioctl_submit_job(dev, file, &jobs[i]);
|
||||
ret = ethosu_ioctl_submit_job(dev, file, &jobs[i], args->perfmon_id);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,8 @@ struct ethosu_job {
|
|||
u8 region_cnt;
|
||||
u32 sram_size;
|
||||
|
||||
struct ethosu_perfmon *perfmon;
|
||||
|
||||
/* Fence to be signaled by drm-sched once its done with the job */
|
||||
struct dma_fence *inference_done_fence;
|
||||
|
||||
|
|
|
|||
301
drivers/accel/ethosu/ethosu_perfmon.c
Normal file
301
drivers/accel/ethosu/ethosu_perfmon.c
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only OR MIT
|
||||
/* Copyright 2026 Arm, Ltd. */
|
||||
/* Based on v3d_perfmon.c, Copyright (C) 2021 Raspberry Pi */
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include <drm/drm_file.h>
|
||||
#include <drm/drm_ioctl.h>
|
||||
|
||||
#include <uapi/drm/ethosu_accel.h>
|
||||
|
||||
#include "ethosu_drv.h"
|
||||
#include "ethosu_device.h"
|
||||
|
||||
void ethosu_perfmon_get(struct ethosu_perfmon *perfmon)
|
||||
{
|
||||
if (perfmon)
|
||||
refcount_inc(&perfmon->refcnt);
|
||||
}
|
||||
|
||||
void ethosu_perfmon_put(struct ethosu_perfmon *perfmon)
|
||||
{
|
||||
if (perfmon && refcount_dec_and_test(&perfmon->refcnt))
|
||||
kfree(perfmon);
|
||||
}
|
||||
|
||||
void ethosu_perfmon_start(struct ethosu_device *ethosu, struct ethosu_perfmon *perfmon)
|
||||
{
|
||||
unsigned int i;
|
||||
u8 ncounters;
|
||||
u32 mask;
|
||||
|
||||
lockdep_assert_held(ðosu->perfmon_state.lock);
|
||||
|
||||
if (WARN_ON_ONCE(!perfmon || ethosu->perfmon_state.active))
|
||||
return;
|
||||
|
||||
writel_relaxed(PMCR_CNT_EN, ethosu->pmu_regs + NPU_REG_PMCR);
|
||||
writel_relaxed(PMU_EV_TYPE_CYCLES, ethosu->pmu_regs + NPU_REG_PMCCNTR_CFG);
|
||||
|
||||
mask = 0x80000000;
|
||||
ncounters = perfmon->ncounters - 1;
|
||||
if (ncounters)
|
||||
mask |= GENMASK(ncounters - 1, 0);
|
||||
|
||||
for (i = 0; i < ncounters; i++)
|
||||
writel_relaxed(perfmon->counters[i], ethosu->pmu_regs + NPU_REG_PMU_EVTYPER(i));
|
||||
|
||||
writel_relaxed(mask, ethosu->pmu_regs + NPU_REG_PMCNTENSET);
|
||||
writel_relaxed(PMCR_CNT_EN | PMCR_EVENT_CNT_RST | PMCR_CYCLE_CNT_RST,
|
||||
ethosu->pmu_regs + NPU_REG_PMCR);
|
||||
ethosu->perfmon_state.active = perfmon;
|
||||
}
|
||||
|
||||
void ethosu_perfmon_stop_locked(struct ethosu_device *ethosu, struct ethosu_perfmon *perfmon,
|
||||
bool capture)
|
||||
{
|
||||
unsigned int i;
|
||||
u8 ncounters;
|
||||
u32 mask;
|
||||
|
||||
lockdep_assert_held(ðosu->perfmon_state.lock);
|
||||
|
||||
if (!perfmon || perfmon != ethosu->perfmon_state.active)
|
||||
return;
|
||||
|
||||
ncounters = perfmon->ncounters - 1;
|
||||
|
||||
if (!pm_runtime_get_if_active(ethosu->base.dev)) {
|
||||
ethosu->perfmon_state.active = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (capture) {
|
||||
for (i = 0; i < ncounters; i++)
|
||||
perfmon->values[i] += readl_relaxed(ethosu->pmu_regs + NPU_REG_PMU_EVCNTR(i));
|
||||
|
||||
perfmon->values[ncounters] +=
|
||||
readl_relaxed(ethosu->pmu_regs + NPU_REG_PMCCNTR_LO) |
|
||||
(u64)readl_relaxed(ethosu->pmu_regs + NPU_REG_PMCCNTR_HI) << 32;
|
||||
}
|
||||
|
||||
mask = 0x80000000;
|
||||
if (ncounters)
|
||||
mask |= GENMASK(ncounters - 1, 0);
|
||||
writel_relaxed(mask, ethosu->pmu_regs + NPU_REG_PMCNTENCLR);
|
||||
|
||||
writel_relaxed(0, ethosu->pmu_regs + NPU_REG_PMCR);
|
||||
ethosu->perfmon_state.active = NULL;
|
||||
|
||||
pm_runtime_put(ethosu->base.dev);
|
||||
}
|
||||
|
||||
void ethosu_perfmon_stop(struct ethosu_device *ethosu, struct ethosu_perfmon *perfmon,
|
||||
bool capture)
|
||||
{
|
||||
if (!perfmon)
|
||||
return;
|
||||
|
||||
guard(mutex)(ðosu->perfmon_state.lock);
|
||||
ethosu_perfmon_stop_locked(ethosu, perfmon, capture);
|
||||
}
|
||||
|
||||
struct ethosu_perfmon *ethosu_perfmon_find(struct ethosu_file_priv *ethosu_priv, int id)
|
||||
{
|
||||
struct ethosu_perfmon *perfmon;
|
||||
|
||||
xa_lock(ðosu_priv->perfmons);
|
||||
perfmon = xa_load(ðosu_priv->perfmons, id);
|
||||
ethosu_perfmon_get(perfmon);
|
||||
xa_unlock(ðosu_priv->perfmons);
|
||||
|
||||
return perfmon;
|
||||
}
|
||||
|
||||
void ethosu_perfmon_open_file(struct ethosu_file_priv *ethosu_priv)
|
||||
{
|
||||
xa_init_flags(ðosu_priv->perfmons, XA_FLAGS_ALLOC1);
|
||||
}
|
||||
|
||||
static void ethosu_perfmon_delete(struct ethosu_file_priv *ethosu_priv,
|
||||
struct ethosu_perfmon *perfmon)
|
||||
{
|
||||
struct ethosu_device *ethosu = ethosu_priv->edev;
|
||||
|
||||
/* If the active perfmon is being destroyed, stop it first */
|
||||
scoped_guard(mutex, ðosu->perfmon_state.lock) {
|
||||
/* If the global perfmon is being destroyed, set it to NULL */
|
||||
if (ethosu->global_perfmon == perfmon) {
|
||||
ethosu->global_perfmon = NULL;
|
||||
ethosu_perfmon_put(perfmon);
|
||||
}
|
||||
|
||||
ethosu_perfmon_stop_locked(ethosu, perfmon, false);
|
||||
}
|
||||
|
||||
ethosu_perfmon_put(perfmon);
|
||||
}
|
||||
|
||||
void ethosu_perfmon_close_file(struct ethosu_file_priv *ethosu_priv)
|
||||
{
|
||||
struct ethosu_perfmon *perfmon;
|
||||
unsigned long id;
|
||||
|
||||
xa_for_each(ðosu_priv->perfmons, id, perfmon)
|
||||
ethosu_perfmon_delete(ethosu_priv, perfmon);
|
||||
|
||||
xa_destroy(ðosu_priv->perfmons);
|
||||
}
|
||||
|
||||
int ethosu_ioctl_perfmon_create(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv)
|
||||
{
|
||||
struct ethosu_file_priv *ethosu_priv = file_priv->driver_priv;
|
||||
struct drm_ethosu_perfmon_create *req = data;
|
||||
struct ethosu_device *ethosu = to_ethosu_device(dev);
|
||||
struct ethosu_perfmon *perfmon;
|
||||
unsigned int i, event_max;
|
||||
int ret;
|
||||
u32 id;
|
||||
|
||||
/* Number of monitored counters cannot exceed HW limits. */
|
||||
if (req->ncounters > ethosu->npu_info.pmu_counters)
|
||||
return -EINVAL;
|
||||
|
||||
/* Make sure all counters are valid. */
|
||||
event_max = ethosu_is_u65(ethosu) ? 433 : 671;
|
||||
for (i = 0; i < req->ncounters; i++) {
|
||||
if (req->counters[i] > event_max)
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Add 1 more counter for cycle counter */
|
||||
req->ncounters++;
|
||||
|
||||
perfmon = kzalloc_flex(*perfmon, values, req->ncounters);
|
||||
if (!perfmon)
|
||||
return -ENOMEM;
|
||||
|
||||
for (i = 0; i < req->ncounters - 1; i++)
|
||||
perfmon->counters[i] = req->counters[i];
|
||||
|
||||
perfmon->ncounters = req->ncounters;
|
||||
|
||||
refcount_set(&perfmon->refcnt, 1);
|
||||
|
||||
ret = xa_alloc(ðosu_priv->perfmons, &id, perfmon, xa_limit_32b,
|
||||
GFP_KERNEL);
|
||||
|
||||
if (ret < 0) {
|
||||
kfree(perfmon);
|
||||
return ret;
|
||||
}
|
||||
|
||||
req->id = id;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ethosu_ioctl_perfmon_destroy(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv)
|
||||
{
|
||||
struct ethosu_file_priv *ethosu_priv = file_priv->driver_priv;
|
||||
struct drm_ethosu_perfmon_destroy *req = data;
|
||||
struct ethosu_perfmon *perfmon;
|
||||
|
||||
perfmon = xa_erase(ðosu_priv->perfmons, req->id);
|
||||
if (!perfmon)
|
||||
return -EINVAL;
|
||||
|
||||
ethosu_perfmon_delete(ethosu_priv, perfmon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ethosu_ioctl_perfmon_get_values(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv)
|
||||
{
|
||||
struct ethosu_device *ethosu = to_ethosu_device(dev);
|
||||
struct ethosu_file_priv *ethosu_priv = file_priv->driver_priv;
|
||||
struct drm_ethosu_perfmon_get_values *req = data;
|
||||
struct ethosu_perfmon *perfmon;
|
||||
int ret = 0;
|
||||
|
||||
if (req->pad != 0)
|
||||
return -EINVAL;
|
||||
|
||||
perfmon = ethosu_perfmon_find(ethosu_priv, req->id);
|
||||
if (!perfmon)
|
||||
return -EINVAL;
|
||||
|
||||
ret = pm_runtime_resume_and_get(dev->dev);
|
||||
if (ret) {
|
||||
ethosu_perfmon_put(perfmon);
|
||||
return ret;
|
||||
}
|
||||
ethosu_perfmon_stop(ethosu, perfmon, true);
|
||||
|
||||
pm_runtime_put_autosuspend(dev->dev);
|
||||
|
||||
if (copy_to_user(u64_to_user_ptr(req->values_ptr), perfmon->values,
|
||||
perfmon->ncounters * sizeof(u64)))
|
||||
ret = -EFAULT;
|
||||
|
||||
ethosu_perfmon_put(perfmon);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int ethosu_ioctl_perfmon_set_global(struct drm_device *dev, void *data,
|
||||
struct drm_file *file_priv)
|
||||
{
|
||||
struct ethosu_file_priv *ethosu_priv = file_priv->driver_priv;
|
||||
struct drm_ethosu_perfmon_set_global *req = data;
|
||||
struct ethosu_device *ethosu = to_ethosu_device(dev);
|
||||
struct ethosu_perfmon *perfmon;
|
||||
|
||||
if (req->flags & ~DRM_ETHOSU_PERFMON_CLEAR_GLOBAL)
|
||||
return -EINVAL;
|
||||
|
||||
perfmon = ethosu_perfmon_find(ethosu_priv, req->id);
|
||||
if (!perfmon)
|
||||
return -EINVAL;
|
||||
|
||||
/* If the request is to clear the global performance monitor */
|
||||
if (req->flags & DRM_ETHOSU_PERFMON_CLEAR_GLOBAL) {
|
||||
struct ethosu_perfmon *old;
|
||||
|
||||
scoped_guard(mutex, ðosu->perfmon_state.lock) {
|
||||
old = ethosu->global_perfmon;
|
||||
if (!old) {
|
||||
ethosu_perfmon_put(perfmon);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ethosu->global_perfmon = NULL;
|
||||
ethosu_perfmon_stop_locked(ethosu, old, true);
|
||||
}
|
||||
|
||||
ethosu_perfmon_put(old);
|
||||
ethosu_perfmon_put(perfmon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
scoped_guard(mutex, ðosu->perfmon_state.lock) {
|
||||
if (ethosu->perfmon_state.active || ethosu->global_perfmon) {
|
||||
ethosu_perfmon_put(perfmon);
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ethosu->global_perfmon = perfmon;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -307,6 +307,11 @@ static int ivpu_open(struct drm_device *dev, struct drm_file *file)
|
|||
return -ENODEV;
|
||||
|
||||
limits = ivpu_user_limits_get(vdev);
|
||||
if (IS_ERR(limits) && PTR_ERR(limits) == -EMFILE) {
|
||||
/* Context limit may be held by jobs pending deferred cleanup */
|
||||
flush_work(&vdev->job_destroy_work);
|
||||
limits = ivpu_user_limits_get(vdev);
|
||||
}
|
||||
if (IS_ERR(limits)) {
|
||||
ret = PTR_ERR(limits);
|
||||
goto err_dev_exit;
|
||||
|
|
@ -510,9 +515,9 @@ void ivpu_prepare_for_reset(struct ivpu_device *vdev)
|
|||
{
|
||||
ivpu_hw_irq_disable(vdev);
|
||||
disable_irq(vdev->irq);
|
||||
flush_work(&vdev->irq_ipc_work);
|
||||
flush_work(&vdev->irq_dct_work);
|
||||
flush_work(&vdev->context_abort_work);
|
||||
flush_work(&vdev->job_destroy_work);
|
||||
ivpu_ipc_disable(vdev);
|
||||
ivpu_mmu_disable(vdev);
|
||||
}
|
||||
|
|
@ -584,6 +589,11 @@ static const struct drm_driver driver = {
|
|||
.major = 1,
|
||||
};
|
||||
|
||||
static void ivpu_destroy_workqueue(void *wq)
|
||||
{
|
||||
destroy_workqueue(wq);
|
||||
}
|
||||
|
||||
static int ivpu_irq_init(struct ivpu_device *vdev)
|
||||
{
|
||||
struct pci_dev *pdev = to_pci_dev(vdev->drm.dev);
|
||||
|
|
@ -595,16 +605,26 @@ static int ivpu_irq_init(struct ivpu_device *vdev)
|
|||
return ret;
|
||||
}
|
||||
|
||||
INIT_WORK(&vdev->irq_ipc_work, ivpu_ipc_irq_work_fn);
|
||||
INIT_WORK(&vdev->irq_dct_work, ivpu_pm_irq_dct_work_fn);
|
||||
INIT_WORK(&vdev->context_abort_work, ivpu_context_abort_work_fn);
|
||||
init_llist_head(&vdev->job_destroy_list);
|
||||
INIT_WORK(&vdev->job_destroy_work, ivpu_job_destroy_work_fn);
|
||||
|
||||
vdev->job_destroy_wq = alloc_workqueue("ivpu_job_destroy", WQ_UNBOUND | WQ_MEM_RECLAIM, 0);
|
||||
if (!vdev->job_destroy_wq)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = devm_add_action_or_reset(vdev->drm.dev, ivpu_destroy_workqueue, vdev->job_destroy_wq);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ivpu_irq_handlers_init(vdev);
|
||||
|
||||
vdev->irq = pci_irq_vector(pdev, 0);
|
||||
|
||||
ret = devm_request_irq(vdev->drm.dev, vdev->irq, ivpu_hw_irq_handler,
|
||||
IRQF_NO_AUTOEN, DRIVER_NAME, vdev);
|
||||
ret = devm_request_threaded_irq(vdev->drm.dev, vdev->irq, ivpu_hw_irq_handler,
|
||||
ivpu_ipc_irq_thread_handler, IRQF_NO_AUTOEN,
|
||||
DRIVER_NAME, vdev);
|
||||
if (ret)
|
||||
ivpu_err(vdev, "Failed to request an IRQ %d\n", ret);
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <drm/drm_print.h>
|
||||
|
||||
#include <linux/hashtable.h>
|
||||
#include <linux/llist.h>
|
||||
#include <linux/pci.h>
|
||||
#include <linux/xarray.h>
|
||||
#include <uapi/drm/ivpu_accel.h>
|
||||
|
|
@ -157,9 +158,11 @@ struct ivpu_device {
|
|||
struct xa_limit db_limit;
|
||||
u32 db_next;
|
||||
|
||||
struct work_struct irq_ipc_work;
|
||||
struct work_struct irq_dct_work;
|
||||
struct work_struct context_abort_work;
|
||||
struct llist_head job_destroy_list;
|
||||
struct work_struct job_destroy_work;
|
||||
struct workqueue_struct *job_destroy_wq;
|
||||
|
||||
struct mutex bo_list_lock; /* Protects bo_list */
|
||||
struct list_head bo_list;
|
||||
|
|
|
|||
|
|
@ -399,6 +399,10 @@ irqreturn_t ivpu_hw_irq_handler(int irq, void *ptr)
|
|||
return IRQ_NONE;
|
||||
|
||||
pm_runtime_mark_last_busy(vdev->drm.dev);
|
||||
|
||||
if (ip_handled)
|
||||
return IRQ_WAKE_THREAD;
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -146,7 +146,7 @@ ivpu_ipc_rx_msg_add(struct ivpu_device *vdev, struct ivpu_ipc_consumer *cons,
|
|||
|
||||
lockdep_assert_held(&ipc->cons_lock);
|
||||
|
||||
rx_msg = kzalloc_obj(*rx_msg, GFP_ATOMIC);
|
||||
rx_msg = kmem_cache_zalloc(ipc->rx_msg_cache, GFP_ATOMIC);
|
||||
if (!rx_msg) {
|
||||
ivpu_ipc_rx_mark_free(vdev, ipc_hdr, jsm_msg);
|
||||
return;
|
||||
|
|
@ -174,7 +174,7 @@ ivpu_ipc_rx_msg_del(struct ivpu_device *vdev, struct ivpu_ipc_rx_msg *rx_msg)
|
|||
list_del(&rx_msg->link);
|
||||
ivpu_ipc_rx_mark_free(vdev, rx_msg->ipc_hdr, rx_msg->jsm_msg);
|
||||
atomic_dec(&vdev->ipc->rx_msg_count);
|
||||
kfree(rx_msg);
|
||||
kmem_cache_free(vdev->ipc->rx_msg_cache, rx_msg);
|
||||
}
|
||||
|
||||
void ivpu_ipc_consumer_add(struct ivpu_device *vdev, struct ivpu_ipc_consumer *cons,
|
||||
|
|
@ -462,13 +462,11 @@ void ivpu_ipc_irq_handler(struct ivpu_device *vdev)
|
|||
ivpu_ipc_rx_mark_free(vdev, ipc_hdr, jsm_msg);
|
||||
}
|
||||
}
|
||||
|
||||
queue_work(system_percpu_wq, &vdev->irq_ipc_work);
|
||||
}
|
||||
|
||||
void ivpu_ipc_irq_work_fn(struct work_struct *work)
|
||||
irqreturn_t ivpu_ipc_irq_thread_handler(int irq, void *ptr)
|
||||
{
|
||||
struct ivpu_device *vdev = container_of(work, struct ivpu_device, irq_ipc_work);
|
||||
struct ivpu_device *vdev = ptr;
|
||||
struct ivpu_ipc_info *ipc = vdev->ipc;
|
||||
struct ivpu_ipc_rx_msg *rx_msg, *r;
|
||||
struct list_head cb_msg_list;
|
||||
|
|
@ -483,6 +481,8 @@ void ivpu_ipc_irq_work_fn(struct work_struct *work)
|
|||
rx_msg->callback(vdev, rx_msg->ipc_hdr, rx_msg->jsm_msg);
|
||||
ivpu_ipc_rx_msg_del(vdev, rx_msg);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int ivpu_ipc_init(struct ivpu_device *vdev)
|
||||
|
|
@ -490,10 +490,18 @@ int ivpu_ipc_init(struct ivpu_device *vdev)
|
|||
struct ivpu_ipc_info *ipc = vdev->ipc;
|
||||
int ret;
|
||||
|
||||
ipc->rx_msg_cache = kmem_cache_create("ivpu_ipc_rx_msg", sizeof(struct ivpu_ipc_rx_msg), 0,
|
||||
SLAB_HWCACHE_ALIGN, NULL);
|
||||
if (!ipc->rx_msg_cache) {
|
||||
ivpu_err(vdev, "Failed to create rx_msg_cache\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
ipc->mem_tx = ivpu_bo_create_global(vdev, SZ_16K, DRM_IVPU_BO_WC | DRM_IVPU_BO_MAPPABLE);
|
||||
if (!ipc->mem_tx) {
|
||||
ivpu_err(vdev, "Failed to allocate mem_tx\n");
|
||||
return -ENOMEM;
|
||||
ret = -ENOMEM;
|
||||
goto err_destroy_cache;
|
||||
}
|
||||
|
||||
ipc->mem_rx = ivpu_bo_create_global(vdev, SZ_16K, DRM_IVPU_BO_WC | DRM_IVPU_BO_MAPPABLE);
|
||||
|
|
@ -532,6 +540,8 @@ int ivpu_ipc_init(struct ivpu_device *vdev)
|
|||
ivpu_bo_free(ipc->mem_rx);
|
||||
err_free_tx:
|
||||
ivpu_bo_free(ipc->mem_tx);
|
||||
err_destroy_cache:
|
||||
kmem_cache_destroy(ipc->rx_msg_cache);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -544,6 +554,7 @@ void ivpu_ipc_fini(struct ivpu_device *vdev)
|
|||
drm_WARN_ON(&vdev->drm, atomic_read(&ipc->rx_msg_count) > 0);
|
||||
|
||||
ivpu_ipc_mem_fini(vdev);
|
||||
kmem_cache_destroy(ipc->rx_msg_cache);
|
||||
}
|
||||
|
||||
void ivpu_ipc_enable(struct ivpu_device *vdev)
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ struct ivpu_ipc_info {
|
|||
struct gen_pool *mm_tx;
|
||||
struct ivpu_bo *mem_tx;
|
||||
struct ivpu_bo *mem_rx;
|
||||
struct kmem_cache *rx_msg_cache;
|
||||
|
||||
atomic_t rx_msg_count;
|
||||
|
||||
|
|
@ -90,7 +91,7 @@ void ivpu_ipc_disable(struct ivpu_device *vdev);
|
|||
void ivpu_ipc_reset(struct ivpu_device *vdev);
|
||||
|
||||
void ivpu_ipc_irq_handler(struct ivpu_device *vdev);
|
||||
void ivpu_ipc_irq_work_fn(struct work_struct *work);
|
||||
irqreturn_t ivpu_ipc_irq_thread_handler(int irq, void *ptr);
|
||||
|
||||
void ivpu_ipc_consumer_add(struct ivpu_device *vdev, struct ivpu_ipc_consumer *cons,
|
||||
u32 channel, ivpu_ipc_rx_callback_t callback);
|
||||
|
|
|
|||
|
|
@ -535,6 +535,20 @@ static void ivpu_job_destroy(struct ivpu_job *job)
|
|||
kfree(job);
|
||||
}
|
||||
|
||||
void ivpu_job_destroy_work_fn(struct work_struct *work)
|
||||
{
|
||||
struct ivpu_device *vdev = container_of(work, struct ivpu_device, job_destroy_work);
|
||||
struct ivpu_job *job, *tmp;
|
||||
struct llist_node *list;
|
||||
|
||||
list = llist_del_all(&vdev->job_destroy_list);
|
||||
|
||||
llist_for_each_entry_safe(job, tmp, list, destroy_node) {
|
||||
ivpu_job_destroy(job);
|
||||
ivpu_rpm_put(vdev);
|
||||
}
|
||||
}
|
||||
|
||||
static struct ivpu_job *
|
||||
ivpu_job_create(struct ivpu_file_priv *file_priv, u32 engine_idx, u32 bo_count)
|
||||
{
|
||||
|
|
@ -619,7 +633,7 @@ bool ivpu_job_handle_engine_error(struct ivpu_device *vdev, u32 job_id, u32 job_
|
|||
return false;
|
||||
}
|
||||
|
||||
static int ivpu_job_signal_and_destroy(struct ivpu_device *vdev, u32 job_id, u32 job_status)
|
||||
static struct ivpu_job *ivpu_job_signal(struct ivpu_device *vdev, u32 job_id, u32 job_status)
|
||||
{
|
||||
struct ivpu_job *job;
|
||||
|
||||
|
|
@ -627,7 +641,7 @@ static int ivpu_job_signal_and_destroy(struct ivpu_device *vdev, u32 job_id, u32
|
|||
|
||||
job = xa_load(&vdev->submitted_jobs_xa, job_id);
|
||||
if (!job)
|
||||
return -ENOENT;
|
||||
return NULL;
|
||||
|
||||
ivpu_job_remove_from_submitted_jobs(vdev, job_id);
|
||||
|
||||
|
|
@ -646,14 +660,37 @@ static int ivpu_job_signal_and_destroy(struct ivpu_device *vdev, u32 job_id, u32
|
|||
job->job_id, job->file_priv->ctx.id, job->cmdq_id, job->engine_idx,
|
||||
job->job_status);
|
||||
|
||||
ivpu_job_destroy(job);
|
||||
ivpu_stop_job_timeout_detection(vdev);
|
||||
|
||||
ivpu_rpm_put(vdev);
|
||||
|
||||
if (!xa_empty(&vdev->submitted_jobs_xa))
|
||||
ivpu_start_job_timeout_detection(vdev);
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
static int ivpu_job_signal_and_destroy(struct ivpu_device *vdev, u32 job_id, u32 job_status)
|
||||
{
|
||||
struct ivpu_job *job = ivpu_job_signal(vdev, job_id, job_status);
|
||||
|
||||
if (!job)
|
||||
return -ENOENT;
|
||||
|
||||
ivpu_job_destroy(job);
|
||||
ivpu_rpm_put(vdev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ivpu_job_signal_and_defer_destroy(struct ivpu_device *vdev, u32 job_id, u32 job_status)
|
||||
{
|
||||
struct ivpu_job *job = ivpu_job_signal(vdev, job_id, job_status);
|
||||
|
||||
if (!job)
|
||||
return -ENOENT;
|
||||
|
||||
llist_add(&job->destroy_node, &vdev->job_destroy_list);
|
||||
queue_work(vdev->job_destroy_wq, &vdev->job_destroy_work);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -689,6 +726,7 @@ static int ivpu_job_submit(struct ivpu_job *job, u8 priority, u32 cmdq_id)
|
|||
struct ivpu_file_priv *file_priv = job->file_priv;
|
||||
struct ivpu_device *vdev = job->vdev;
|
||||
struct ivpu_cmdq *cmdq;
|
||||
bool flushed = false;
|
||||
bool is_first_job;
|
||||
int ret;
|
||||
|
||||
|
|
@ -696,6 +734,7 @@ static int ivpu_job_submit(struct ivpu_job *job, u8 priority, u32 cmdq_id)
|
|||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
retry:
|
||||
mutex_lock(&vdev->submitted_jobs_lock);
|
||||
mutex_lock(&file_priv->lock);
|
||||
|
||||
|
|
@ -709,6 +748,14 @@ static int ivpu_job_submit(struct ivpu_job *job, u8 priority, u32 cmdq_id)
|
|||
}
|
||||
|
||||
ret = ivpu_cmdq_register(file_priv, cmdq);
|
||||
if (ret == -EBUSY && !flushed) {
|
||||
/* Doorbell may be held by jobs pending deferred cleanup */
|
||||
mutex_unlock(&file_priv->lock);
|
||||
mutex_unlock(&vdev->submitted_jobs_lock);
|
||||
flush_work(&vdev->job_destroy_work);
|
||||
flushed = true;
|
||||
goto retry;
|
||||
}
|
||||
if (ret) {
|
||||
ivpu_err(vdev, "Failed to register command queue: %d\n", ret);
|
||||
goto err_unlock;
|
||||
|
|
@ -1101,7 +1148,7 @@ ivpu_job_done_callback(struct ivpu_device *vdev, struct ivpu_ipc_hdr *ipc_hdr,
|
|||
mutex_lock(&vdev->submitted_jobs_lock);
|
||||
if (!ivpu_job_handle_engine_error(vdev, payload->job_id, payload->job_status))
|
||||
/* No engine error, complete the job normally */
|
||||
ivpu_job_signal_and_destroy(vdev, payload->job_id, payload->job_status);
|
||||
ivpu_job_signal_and_defer_destroy(vdev, payload->job_id, payload->job_status);
|
||||
mutex_unlock(&vdev->submitted_jobs_lock);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@
|
|||
#ifndef __IVPU_JOB_H__
|
||||
#define __IVPU_JOB_H__
|
||||
|
||||
#include <linux/kref.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/llist.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "ivpu_gem.h"
|
||||
|
||||
|
|
@ -47,6 +49,7 @@ struct ivpu_cmdq {
|
|||
* @vdev: Pointer to the VPU device
|
||||
* @file_priv: The client context that submitted this job
|
||||
* @done_fence: Fence signaled when job completes
|
||||
* @destroy_node: List node for deferred resource cleanup after job completion
|
||||
* @cmd_buf_vpu_addr: VPU address of the command buffer for this job
|
||||
* @cmdq_id: Command queue ID used for submission
|
||||
* @job_id: Unique job ID for tracking and status reporting
|
||||
|
|
@ -61,6 +64,7 @@ struct ivpu_job {
|
|||
struct ivpu_device *vdev;
|
||||
struct ivpu_file_priv *file_priv;
|
||||
struct dma_fence *done_fence;
|
||||
struct llist_node destroy_node;
|
||||
u64 cmd_buf_vpu_addr;
|
||||
u32 cmdq_id;
|
||||
u32 job_id;
|
||||
|
|
@ -87,6 +91,7 @@ void ivpu_job_done_consumer_init(struct ivpu_device *vdev);
|
|||
void ivpu_job_done_consumer_fini(struct ivpu_device *vdev);
|
||||
bool ivpu_job_handle_engine_error(struct ivpu_device *vdev, u32 job_id, u32 job_status);
|
||||
void ivpu_context_abort_work_fn(struct work_struct *work);
|
||||
void ivpu_job_destroy_work_fn(struct work_struct *work);
|
||||
|
||||
void ivpu_jobs_abort_all(struct ivpu_device *vdev);
|
||||
|
||||
|
|
|
|||
|
|
@ -1102,9 +1102,12 @@ __dma_fence_init(struct dma_fence *fence, const struct dma_fence_ops *ops,
|
|||
* context and seqno are used for easy comparison between fences, allowing
|
||||
* to check which fence is later by simply using dma_fence_later().
|
||||
*
|
||||
* It is strongly discouraged to provide an external lock because this couples
|
||||
* lock and fence life time. This is only allowed for legacy use cases when
|
||||
* multiple fences need to be prevented from signaling out of order.
|
||||
* External locks are a relic of legacy use cases that needed a shared lock
|
||||
* to serialize signaling when no out-of-order signaling was possible through
|
||||
* &dma_fence_ops.signaled. Drivers have abandoned this concept since the
|
||||
* introduction of the callback, but the external lock is still around. New
|
||||
* users MUST NOT use external locks, as they force the issuer to outlive all
|
||||
* fences that reference the lock.
|
||||
*/
|
||||
void
|
||||
dma_fence_init(struct dma_fence *fence, const struct dma_fence_ops *ops,
|
||||
|
|
@ -1129,9 +1132,8 @@ EXPORT_SYMBOL(dma_fence_init);
|
|||
* Context and seqno are used for easy comparison between fences, allowing
|
||||
* to check which fence is later by simply using dma_fence_later().
|
||||
*
|
||||
* It is strongly discouraged to provide an external lock because this couples
|
||||
* lock and fence life time. This is only allowed for legacy use cases when
|
||||
* multiple fences need to be prevented from signaling out of order.
|
||||
* New users MUST NOT use external locks. Check the documentation in
|
||||
* dma_fence_init() to understand the motives behind the legacy use cases.
|
||||
*/
|
||||
void
|
||||
dma_fence_init64(struct dma_fence *fence, const struct dma_fence_ops *ops,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
#include <linux/err.h>
|
||||
#include <linux/export.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/nospec.h>
|
||||
#include <linux/syscalls.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
|
@ -154,7 +155,7 @@ static long dma_heap_ioctl(struct file *file, unsigned int ucmd,
|
|||
in_size = 0;
|
||||
if ((ucmd & kcmd & IOC_OUT) == 0)
|
||||
out_size = 0;
|
||||
ksize = max(max(in_size, out_size), drv_size);
|
||||
ksize = max3(in_size, out_size, drv_size);
|
||||
|
||||
/* If necessary, allocate buffer for ioctl argument */
|
||||
if (ksize > sizeof(stack_kdata)) {
|
||||
|
|
|
|||
|
|
@ -193,6 +193,9 @@ static void mark_allocated(struct gpu_buddy *mm,
|
|||
block->header &= ~GPU_BUDDY_HEADER_STATE;
|
||||
block->header |= GPU_BUDDY_ALLOCATED;
|
||||
|
||||
mm->free_scoreboard[gpu_buddy_block_order(block)]--;
|
||||
mm->used_scoreboard[gpu_buddy_block_order(block)]++;
|
||||
|
||||
rbtree_remove(mm, block);
|
||||
}
|
||||
|
||||
|
|
@ -201,9 +204,14 @@ static void mark_free(struct gpu_buddy *mm,
|
|||
{
|
||||
enum gpu_buddy_free_tree tree;
|
||||
|
||||
if (gpu_buddy_block_is_allocated(block))
|
||||
mm->used_scoreboard[gpu_buddy_block_order(block)]--;
|
||||
|
||||
block->header &= ~GPU_BUDDY_HEADER_STATE;
|
||||
block->header |= GPU_BUDDY_FREE;
|
||||
|
||||
mm->free_scoreboard[gpu_buddy_block_order(block)]++;
|
||||
|
||||
tree = get_block_tree(block);
|
||||
rbtree_insert(mm, block, tree);
|
||||
}
|
||||
|
|
@ -214,6 +222,8 @@ static void mark_split(struct gpu_buddy *mm,
|
|||
block->header &= ~GPU_BUDDY_HEADER_STATE;
|
||||
block->header |= GPU_BUDDY_SPLIT;
|
||||
|
||||
mm->free_scoreboard[gpu_buddy_block_order(block)]--;
|
||||
|
||||
rbtree_remove(mm, block);
|
||||
}
|
||||
|
||||
|
|
@ -271,9 +281,13 @@ static unsigned int __gpu_buddy_free(struct gpu_buddy *mm,
|
|||
}
|
||||
|
||||
rbtree_remove(mm, buddy);
|
||||
mm->free_scoreboard[gpu_buddy_block_order(buddy)]--;
|
||||
if (force_merge && gpu_buddy_block_is_clear(buddy))
|
||||
mm->clear_avail -= gpu_buddy_block_size(mm, buddy);
|
||||
|
||||
if (gpu_buddy_block_is_allocated(block))
|
||||
mm->used_scoreboard[gpu_buddy_block_order(block)]--;
|
||||
|
||||
gpu_block_free(mm, block);
|
||||
gpu_block_free(mm, buddy);
|
||||
|
||||
|
|
@ -335,6 +349,7 @@ static int __force_merge(struct gpu_buddy *mm,
|
|||
iter = rb_prev(iter);
|
||||
|
||||
rbtree_remove(mm, block);
|
||||
mm->free_scoreboard[gpu_buddy_block_order(block)]--;
|
||||
if (gpu_buddy_block_is_clear(block))
|
||||
mm->clear_avail -= gpu_buddy_block_size(mm, block);
|
||||
|
||||
|
|
@ -384,11 +399,23 @@ int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size)
|
|||
|
||||
BUG_ON(mm->max_order > GPU_BUDDY_MAX_ORDER);
|
||||
|
||||
mm->free_scoreboard = kcalloc(mm->max_order + 1,
|
||||
sizeof(*mm->free_scoreboard),
|
||||
GFP_KERNEL);
|
||||
if (!mm->free_scoreboard)
|
||||
return -ENOMEM;
|
||||
|
||||
mm->used_scoreboard = kcalloc(mm->max_order + 1,
|
||||
sizeof(*mm->used_scoreboard),
|
||||
GFP_KERNEL);
|
||||
if (!mm->used_scoreboard)
|
||||
goto out_free_free_scoreboard;
|
||||
|
||||
mm->free_trees = kmalloc_array(GPU_BUDDY_MAX_FREE_TREES,
|
||||
sizeof(*mm->free_trees),
|
||||
GFP_KERNEL);
|
||||
if (!mm->free_trees)
|
||||
return -ENOMEM;
|
||||
goto out_free_used_scoreboard;
|
||||
|
||||
for_each_free_tree(i) {
|
||||
mm->free_trees[i] = kmalloc_array(mm->max_order + 1,
|
||||
|
|
@ -450,6 +477,10 @@ int gpu_buddy_init(struct gpu_buddy *mm, u64 size, u64 chunk_size)
|
|||
while (i--)
|
||||
kfree(mm->free_trees[i]);
|
||||
kfree(mm->free_trees);
|
||||
out_free_used_scoreboard:
|
||||
kfree(mm->used_scoreboard);
|
||||
out_free_free_scoreboard:
|
||||
kfree(mm->free_scoreboard);
|
||||
return -ENOMEM;
|
||||
}
|
||||
EXPORT_SYMBOL(gpu_buddy_init);
|
||||
|
|
@ -484,10 +515,15 @@ void gpu_buddy_fini(struct gpu_buddy *mm)
|
|||
|
||||
gpu_buddy_assert(mm->avail == mm->size);
|
||||
|
||||
for (i = 0; i <= mm->max_order; ++i)
|
||||
gpu_buddy_assert(!mm->used_scoreboard[i]);
|
||||
|
||||
for_each_free_tree(i)
|
||||
kfree(mm->free_trees[i]);
|
||||
kfree(mm->free_trees);
|
||||
kfree(mm->roots);
|
||||
kfree(mm->free_scoreboard);
|
||||
kfree(mm->used_scoreboard);
|
||||
}
|
||||
EXPORT_SYMBOL(gpu_buddy_fini);
|
||||
|
||||
|
|
@ -650,6 +686,20 @@ static bool block_incompatible(struct gpu_buddy_block *block, unsigned int flags
|
|||
return needs_clear != gpu_buddy_block_is_clear(block);
|
||||
}
|
||||
|
||||
static void __gpu_buddy_undo_splits(struct gpu_buddy *mm,
|
||||
struct gpu_buddy_block *block)
|
||||
{
|
||||
struct gpu_buddy_block *buddy = __get_buddy(block);
|
||||
|
||||
if (buddy &&
|
||||
(gpu_buddy_block_is_free(block) &&
|
||||
gpu_buddy_block_is_free(buddy))) {
|
||||
rbtree_remove(mm, block);
|
||||
mm->free_scoreboard[gpu_buddy_block_order(block)]--;
|
||||
__gpu_buddy_free(mm, block, false);
|
||||
}
|
||||
}
|
||||
|
||||
static struct gpu_buddy_block *
|
||||
__alloc_range_bias(struct gpu_buddy *mm,
|
||||
u64 start, u64 end,
|
||||
|
|
@ -659,7 +709,6 @@ __alloc_range_bias(struct gpu_buddy *mm,
|
|||
{
|
||||
u64 req_size = mm->chunk_size << order;
|
||||
struct gpu_buddy_block *block;
|
||||
struct gpu_buddy_block *buddy;
|
||||
LIST_HEAD(dfs);
|
||||
int err;
|
||||
int i;
|
||||
|
|
@ -734,11 +783,7 @@ __alloc_range_bias(struct gpu_buddy *mm,
|
|||
* bigger is better, so make sure we merge everything back before we
|
||||
* free the allocated blocks.
|
||||
*/
|
||||
buddy = __get_buddy(block);
|
||||
if (buddy &&
|
||||
(gpu_buddy_block_is_free(block) &&
|
||||
gpu_buddy_block_is_free(buddy)))
|
||||
__gpu_buddy_free(mm, block, false);
|
||||
__gpu_buddy_undo_splits(mm, block);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
|
|
@ -847,8 +892,7 @@ alloc_from_freetree(struct gpu_buddy *mm,
|
|||
return block;
|
||||
|
||||
err_undo:
|
||||
if (tmp != order)
|
||||
__gpu_buddy_free(mm, block, false);
|
||||
__gpu_buddy_undo_splits(mm, block);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
|
|
@ -912,7 +956,6 @@ gpu_buddy_offset_aligned_allocation(struct gpu_buddy *mm,
|
|||
{
|
||||
struct gpu_buddy_block *block = NULL;
|
||||
unsigned int order, tmp, alignment;
|
||||
struct gpu_buddy_block *buddy;
|
||||
enum gpu_buddy_free_tree tree;
|
||||
unsigned long pages;
|
||||
int err;
|
||||
|
|
@ -965,11 +1008,7 @@ gpu_buddy_offset_aligned_allocation(struct gpu_buddy *mm,
|
|||
* bigger is better, so make sure we merge everything back before we
|
||||
* free the allocated blocks.
|
||||
*/
|
||||
buddy = __get_buddy(block);
|
||||
if (buddy &&
|
||||
(gpu_buddy_block_is_free(block) &&
|
||||
gpu_buddy_block_is_free(buddy)))
|
||||
__gpu_buddy_free(mm, block, false);
|
||||
__gpu_buddy_undo_splits(mm, block);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
|
|
@ -980,7 +1019,6 @@ static int __alloc_range(struct gpu_buddy *mm,
|
|||
u64 *total_allocated_on_err)
|
||||
{
|
||||
struct gpu_buddy_block *block;
|
||||
struct gpu_buddy_block *buddy;
|
||||
u64 total_allocated = 0;
|
||||
LIST_HEAD(allocated);
|
||||
u64 end;
|
||||
|
|
@ -1051,11 +1089,7 @@ static int __alloc_range(struct gpu_buddy *mm,
|
|||
* bigger is better, so make sure we merge everything back before we
|
||||
* free the allocated blocks.
|
||||
*/
|
||||
buddy = __get_buddy(block);
|
||||
if (buddy &&
|
||||
(gpu_buddy_block_is_free(block) &&
|
||||
gpu_buddy_block_is_free(buddy)))
|
||||
__gpu_buddy_free(mm, block, false);
|
||||
__gpu_buddy_undo_splits(mm, block);
|
||||
|
||||
err_free:
|
||||
if (err == -ENOSPC && total_allocated_on_err) {
|
||||
|
|
@ -1490,27 +1524,18 @@ void gpu_buddy_print(struct gpu_buddy *mm)
|
|||
mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20);
|
||||
|
||||
for (order = mm->max_order; order >= 0; order--) {
|
||||
struct gpu_buddy_block *block, *tmp;
|
||||
struct rb_root *root;
|
||||
u64 count = 0, free;
|
||||
unsigned int tree;
|
||||
u64 free_count = mm->free_scoreboard[order];
|
||||
u64 used_count = mm->used_scoreboard[order];
|
||||
u64 block_size = mm->chunk_size << order;
|
||||
u64 free = free_count * block_size;
|
||||
u64 used = used_count * block_size;
|
||||
|
||||
for_each_free_tree(tree) {
|
||||
root = &mm->free_trees[tree][order];
|
||||
|
||||
rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
|
||||
BUG_ON(!gpu_buddy_block_is_free(block));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
free = count * (mm->chunk_size << order);
|
||||
if (free < SZ_1M)
|
||||
pr_info("order-%2d free: %8llu KiB, blocks: %llu\n",
|
||||
order, free >> 10, count);
|
||||
if (block_size < SZ_1M)
|
||||
pr_info("order-%2d free: %8llu KiB, used: %8llu KiB, free_blocks: %llu, used_blocks: %llu\n",
|
||||
order, free >> 10, used >> 10, free_count, used_count);
|
||||
else
|
||||
pr_info("order-%2d free: %8llu MiB, blocks: %llu\n",
|
||||
order, free >> 20, count);
|
||||
pr_info("order-%2d free: %8llu MiB, used: %8llu MiB, free_blocks: %llu, used_blocks: %llu\n",
|
||||
order, free >> 20, used >> 20, free_count, used_count);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(gpu_buddy_print);
|
||||
|
|
|
|||
|
|
@ -6964,11 +6964,14 @@ static void fill_stream_properties_from_drm_display_mode(
|
|||
const struct dc_stream_state *old_stream,
|
||||
int requested_bpc)
|
||||
{
|
||||
bool is_dp_or_hdmi = dc_is_hdmi_signal(stream->signal) || dc_is_dp_signal(stream->signal);
|
||||
struct dc_crtc_timing *timing_out = &stream->timing;
|
||||
const struct drm_display_info *info = &connector->display_info;
|
||||
struct amdgpu_dm_connector *aconnector = NULL;
|
||||
struct hdmi_vendor_infoframe hv_frame;
|
||||
struct hdmi_avi_infoframe avi_frame;
|
||||
bool want_420;
|
||||
bool want_422;
|
||||
ssize_t err;
|
||||
|
||||
if (connector->connector_type != DRM_MODE_CONNECTOR_WRITEBACK)
|
||||
|
|
@ -6981,31 +6984,41 @@ static void fill_stream_properties_from_drm_display_mode(
|
|||
timing_out->h_border_right = 0;
|
||||
timing_out->v_border_top = 0;
|
||||
timing_out->v_border_bottom = 0;
|
||||
/* TODO: un-hardcode */
|
||||
if (drm_mode_is_420_only(info, mode_in)
|
||||
&& (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A ||
|
||||
stream->signal == SIGNAL_TYPE_HDMI_FRL)
|
||||
&& aconnector
|
||||
&& aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420)
|
||||
|
||||
want_420 = (aconnector && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420) ||
|
||||
(connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR420);
|
||||
want_422 = (aconnector && aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR422) ||
|
||||
(connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR422);
|
||||
|
||||
if (drm_mode_is_420_only(info, mode_in) &&
|
||||
(want_420 || connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_AUTO)) {
|
||||
timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420;
|
||||
else if (drm_mode_is_420_also(info, mode_in)
|
||||
&& aconnector
|
||||
&& (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR420
|
||||
|| aconnector->force_yuv420_output))
|
||||
} else if (drm_mode_is_420_also(info, mode_in) && want_420) {
|
||||
timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420;
|
||||
else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422))
|
||||
&& aconnector
|
||||
&& (aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR422
|
||||
|| aconnector->force_yuv422_output))
|
||||
} else if ((info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422)) &&
|
||||
want_422 && is_dp_or_hdmi) {
|
||||
timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR422;
|
||||
else if ((connector->display_info.color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444))
|
||||
&& (stream->signal == SIGNAL_TYPE_HDMI_TYPE_A ||
|
||||
stream->signal == SIGNAL_TYPE_HDMI_FRL)
|
||||
&& aconnector
|
||||
&& aconnector->force_yuv_pixel_format == PIXEL_ENCODING_YCBCR444)
|
||||
} else if (connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_YCBCR444 &&
|
||||
(info->color_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444)) &&
|
||||
is_dp_or_hdmi) {
|
||||
timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR444;
|
||||
else
|
||||
} else if (connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_RGB444 ||
|
||||
connector_state->color_format == DRM_CONNECTOR_COLOR_FORMAT_AUTO) {
|
||||
timing_out->pixel_encoding = PIXEL_ENCODING_RGB;
|
||||
} else {
|
||||
/*
|
||||
* If a format was explicitly requested but the requested format
|
||||
* can't be satisfied, set it to an invalid value so that an
|
||||
* error bubbles up to userspace. This way, userspace knows it
|
||||
* needs to make a better choice.
|
||||
*/
|
||||
if (connector_state->color_format != DRM_CONNECTOR_COLOR_FORMAT_AUTO)
|
||||
timing_out->pixel_encoding = PIXEL_ENCODING_UNDEFINED;
|
||||
else if (drm_mode_is_420_only(info, mode_in))
|
||||
timing_out->pixel_encoding = PIXEL_ENCODING_YCBCR420;
|
||||
else
|
||||
timing_out->pixel_encoding = PIXEL_ENCODING_RGB;
|
||||
}
|
||||
|
||||
timing_out->timing_3d_format = TIMING_3D_FORMAT_NONE;
|
||||
timing_out->display_color_depth = convert_color_depth_from_display_info(
|
||||
|
|
@ -8419,6 +8432,38 @@ static enum dc_status dm_validate_stream_and_context(struct dc *dc,
|
|||
return dc_result;
|
||||
}
|
||||
|
||||
static enum dc_status
|
||||
dm_validate_stream_color_format(const struct drm_connector_state *drm_state,
|
||||
const struct dc_stream_state *stream)
|
||||
{
|
||||
enum dc_pixel_encoding encoding;
|
||||
|
||||
if (!drm_state->color_format)
|
||||
return DC_OK;
|
||||
|
||||
switch (drm_state->color_format) {
|
||||
case DRM_CONNECTOR_COLOR_FORMAT_AUTO:
|
||||
case DRM_CONNECTOR_COLOR_FORMAT_RGB444:
|
||||
encoding = PIXEL_ENCODING_RGB;
|
||||
break;
|
||||
case DRM_CONNECTOR_COLOR_FORMAT_YCBCR444:
|
||||
encoding = PIXEL_ENCODING_YCBCR444;
|
||||
break;
|
||||
case DRM_CONNECTOR_COLOR_FORMAT_YCBCR422:
|
||||
encoding = PIXEL_ENCODING_YCBCR422;
|
||||
break;
|
||||
case DRM_CONNECTOR_COLOR_FORMAT_YCBCR420:
|
||||
encoding = PIXEL_ENCODING_YCBCR420;
|
||||
break;
|
||||
default:
|
||||
encoding = PIXEL_ENCODING_UNDEFINED;
|
||||
break;
|
||||
}
|
||||
|
||||
return encoding == stream->timing.pixel_encoding ?
|
||||
DC_OK : DC_UNSUPPORTED_VALUE;
|
||||
}
|
||||
|
||||
struct dc_stream_state *
|
||||
create_validate_stream_for_sink(struct drm_connector *connector,
|
||||
const struct drm_display_mode *drm_mode,
|
||||
|
|
@ -8466,6 +8511,9 @@ create_validate_stream_for_sink(struct drm_connector *connector,
|
|||
if (dc_result == DC_OK)
|
||||
dc_result = dm_validate_stream_and_context(adev->dm.dc, stream);
|
||||
|
||||
if (dc_result == DC_OK)
|
||||
dc_result = dm_validate_stream_color_format(drm_state, stream);
|
||||
|
||||
if (dc_result != DC_OK) {
|
||||
drm_dbg_kms(connector->dev, "Pruned mode %d x %d (clk %d) %s %s -- %s\n",
|
||||
drm_mode->hdisplay,
|
||||
|
|
@ -9295,6 +9343,12 @@ static const u32 supported_colorspaces =
|
|||
BIT(DRM_MODE_COLORIMETRY_BT2020_RGB) |
|
||||
BIT(DRM_MODE_COLORIMETRY_BT2020_YCC);
|
||||
|
||||
static const u32 supported_colorformats =
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) |
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) |
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) |
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420);
|
||||
|
||||
void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
|
||||
struct amdgpu_dm_connector *aconnector,
|
||||
int connector_type,
|
||||
|
|
@ -9411,8 +9465,11 @@ void amdgpu_dm_connector_init_helper(struct amdgpu_display_manager *dm,
|
|||
connector_type == DRM_MODE_CONNECTOR_eDP) {
|
||||
drm_connector_attach_hdr_output_metadata_property(&aconnector->base);
|
||||
|
||||
if (!aconnector->mst_root)
|
||||
if (!aconnector->mst_root) {
|
||||
drm_connector_attach_vrr_capable_property(&aconnector->base);
|
||||
drm_connector_attach_color_format_property(&aconnector->base,
|
||||
supported_colorformats);
|
||||
}
|
||||
|
||||
if (adev->dm.hdcp_workqueue)
|
||||
drm_connector_attach_content_protection_property(&aconnector->base, true);
|
||||
|
|
|
|||
|
|
@ -619,7 +619,7 @@ static int analogix_dp_config_video(struct analogix_dp_device *dp)
|
|||
|
||||
for (;;) {
|
||||
timeout_loop++;
|
||||
if (analogix_dp_is_slave_video_stream_clock_on(dp) == 0)
|
||||
if (analogix_dp_is_slave_video_stream_clock_on(dp))
|
||||
break;
|
||||
if (timeout_loop > DP_TIMEOUT_LOOP_COUNT) {
|
||||
dev_err(dp->dev, "Timeout of slave video streamclk ok\n");
|
||||
|
|
@ -647,7 +647,7 @@ static int analogix_dp_config_video(struct analogix_dp_device *dp)
|
|||
|
||||
for (;;) {
|
||||
timeout_loop++;
|
||||
if (analogix_dp_is_video_stream_on(dp) == 0) {
|
||||
if (analogix_dp_is_video_stream_on(dp)) {
|
||||
done_count++;
|
||||
if (done_count > 10)
|
||||
break;
|
||||
|
|
@ -870,7 +870,7 @@ static int analogix_dp_bridge_atomic_check(struct drm_bridge *bridge,
|
|||
struct drm_display_info *di = &conn_state->connector->display_info;
|
||||
u32 mask = BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) | BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422);
|
||||
|
||||
if (is_rockchip(dp->plat_data->dev_type)) {
|
||||
if (analogix_dp_is_rockchip(dp->plat_data->dev_type)) {
|
||||
if ((di->color_formats & mask)) {
|
||||
DRM_DEBUG_KMS("Swapping display color format from YUV to RGB\n");
|
||||
di->color_formats &= ~mask;
|
||||
|
|
@ -1249,6 +1249,7 @@ static int analogix_dp_dt_parse_pdata(struct analogix_dp_device *dp)
|
|||
video_info->max_link_rate = 0x0A;
|
||||
video_info->max_lane_count = 0x04;
|
||||
break;
|
||||
case RK3576_EDP:
|
||||
case RK3588_EDP:
|
||||
video_info->max_link_rate = 0x14;
|
||||
video_info->max_lane_count = 0x04;
|
||||
|
|
|
|||
|
|
@ -211,7 +211,7 @@ void analogix_dp_reset_macro(struct analogix_dp_device *dp);
|
|||
void analogix_dp_init_video(struct analogix_dp_device *dp);
|
||||
|
||||
void analogix_dp_set_video_color_format(struct analogix_dp_device *dp);
|
||||
int analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp);
|
||||
bool analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp);
|
||||
void analogix_dp_set_video_cr_mn(struct analogix_dp_device *dp,
|
||||
enum clock_recovery_m_value_type type,
|
||||
u32 m_value,
|
||||
|
|
@ -220,7 +220,7 @@ void analogix_dp_set_video_timing_mode(struct analogix_dp_device *dp, u32 type);
|
|||
void analogix_dp_enable_video_master(struct analogix_dp_device *dp,
|
||||
bool enable);
|
||||
void analogix_dp_start_video(struct analogix_dp_device *dp);
|
||||
int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp);
|
||||
bool analogix_dp_is_video_stream_on(struct analogix_dp_device *dp);
|
||||
void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp);
|
||||
void analogix_dp_enable_scrambling(struct analogix_dp_device *dp);
|
||||
void analogix_dp_disable_scrambling(struct analogix_dp_device *dp);
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ void analogix_dp_init_analog_param(struct analogix_dp_device *dp)
|
|||
reg = SEL_24M | TX_DVDD_BIT_1_0625V;
|
||||
writel(reg, dp->reg_base + ANALOGIX_DP_ANALOG_CTL_2);
|
||||
|
||||
if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) {
|
||||
if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) {
|
||||
reg = REF_CLK_24M;
|
||||
if (dp->plat_data->dev_type == RK3288_DP)
|
||||
reg ^= REF_CLK_MASK;
|
||||
|
|
@ -123,7 +123,7 @@ void analogix_dp_reset(struct analogix_dp_device *dp)
|
|||
analogix_dp_stop_video(dp);
|
||||
analogix_dp_enable_video_mute(dp, 0);
|
||||
|
||||
if (dp->plat_data && is_rockchip(dp->plat_data->dev_type))
|
||||
if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type))
|
||||
reg = RK_VID_CAP_FUNC_EN_N | RK_VID_FIFO_FUNC_EN_N |
|
||||
SW_FUNC_EN_N;
|
||||
else
|
||||
|
|
@ -233,7 +233,7 @@ void analogix_dp_set_pll_power_down(struct analogix_dp_device *dp, bool enable)
|
|||
u32 mask = DP_PLL_PD;
|
||||
u32 pd_addr = ANALOGIX_DP_PLL_CTL;
|
||||
|
||||
if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) {
|
||||
if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) {
|
||||
pd_addr = ANALOGIX_DP_PD;
|
||||
mask = RK_PLL_PD;
|
||||
}
|
||||
|
|
@ -254,12 +254,12 @@ void analogix_dp_set_analog_power_down(struct analogix_dp_device *dp,
|
|||
u32 phy_pd_addr = ANALOGIX_DP_PHY_PD;
|
||||
u32 mask;
|
||||
|
||||
if (dp->plat_data && is_rockchip(dp->plat_data->dev_type))
|
||||
if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type))
|
||||
phy_pd_addr = ANALOGIX_DP_PD;
|
||||
|
||||
switch (block) {
|
||||
case AUX_BLOCK:
|
||||
if (dp->plat_data && is_rockchip(dp->plat_data->dev_type))
|
||||
if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type))
|
||||
mask = RK_AUX_PD;
|
||||
else
|
||||
mask = AUX_PD;
|
||||
|
|
@ -317,7 +317,7 @@ void analogix_dp_set_analog_power_down(struct analogix_dp_device *dp,
|
|||
* to power off everything instead of DP_PHY_PD in
|
||||
* Rockchip
|
||||
*/
|
||||
if (dp->plat_data && is_rockchip(dp->plat_data->dev_type))
|
||||
if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type))
|
||||
mask = DP_INC_BG;
|
||||
else
|
||||
mask = DP_PHY_PD;
|
||||
|
|
@ -329,7 +329,7 @@ void analogix_dp_set_analog_power_down(struct analogix_dp_device *dp,
|
|||
reg &= ~mask;
|
||||
|
||||
writel(reg, dp->reg_base + phy_pd_addr);
|
||||
if (dp->plat_data && is_rockchip(dp->plat_data->dev_type))
|
||||
if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type))
|
||||
usleep_range(10, 15);
|
||||
break;
|
||||
case POWER_ALL:
|
||||
|
|
@ -465,7 +465,7 @@ void analogix_dp_init_aux(struct analogix_dp_device *dp)
|
|||
analogix_dp_reset_aux(dp);
|
||||
|
||||
/* AUX_BIT_PERIOD_EXPECTED_DELAY doesn't apply to Rockchip IP */
|
||||
if (dp->plat_data && is_rockchip(dp->plat_data->dev_type))
|
||||
if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type))
|
||||
reg = 0;
|
||||
else
|
||||
reg = AUX_BIT_PERIOD_EXPECTED_DELAY(3);
|
||||
|
|
@ -713,7 +713,7 @@ void analogix_dp_set_video_color_format(struct analogix_dp_device *dp)
|
|||
writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_3);
|
||||
}
|
||||
|
||||
int analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp)
|
||||
bool analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
|
|
@ -724,7 +724,7 @@ int analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp)
|
|||
|
||||
if (!(reg & DET_STA)) {
|
||||
dev_dbg(dp->dev, "Input stream clock not detected.\n");
|
||||
return -EINVAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_2);
|
||||
|
|
@ -735,10 +735,10 @@ int analogix_dp_is_slave_video_stream_clock_on(struct analogix_dp_device *dp)
|
|||
|
||||
if (reg & CHA_STA) {
|
||||
dev_dbg(dp->dev, "Input stream clk is changing\n");
|
||||
return -EINVAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void analogix_dp_set_video_cr_mn(struct analogix_dp_device *dp,
|
||||
|
|
@ -816,7 +816,7 @@ void analogix_dp_start_video(struct analogix_dp_device *dp)
|
|||
writel(reg, dp->reg_base + ANALOGIX_DP_VIDEO_CTL_1);
|
||||
}
|
||||
|
||||
int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp)
|
||||
bool analogix_dp_is_video_stream_on(struct analogix_dp_device *dp)
|
||||
{
|
||||
u32 reg;
|
||||
|
||||
|
|
@ -826,10 +826,10 @@ int analogix_dp_is_video_stream_on(struct analogix_dp_device *dp)
|
|||
reg = readl(dp->reg_base + ANALOGIX_DP_SYS_CTL_3);
|
||||
if (!(reg & STRM_VALID)) {
|
||||
dev_dbg(dp->dev, "Input video stream is not detected.\n");
|
||||
return -EINVAL;
|
||||
return false;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp)
|
||||
|
|
@ -837,7 +837,7 @@ void analogix_dp_config_video_slave_mode(struct analogix_dp_device *dp)
|
|||
u32 reg;
|
||||
|
||||
reg = readl(dp->reg_base + ANALOGIX_DP_FUNC_EN_1);
|
||||
if (dp->plat_data && is_rockchip(dp->plat_data->dev_type)) {
|
||||
if (dp->plat_data && analogix_dp_is_rockchip(dp->plat_data->dev_type)) {
|
||||
reg &= ~(RK_VID_CAP_FUNC_EN_N | RK_VID_FIFO_FUNC_EN_N);
|
||||
} else {
|
||||
reg &= ~(MASTER_VID_FUNC_EN_N | SLAVE_VID_FUNC_EN_N);
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
#include <linux/of.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_bridge.h>
|
||||
|
|
@ -25,6 +26,8 @@ struct display_connector {
|
|||
|
||||
struct regulator *supply;
|
||||
struct gpio_desc *ddc_en;
|
||||
|
||||
struct work_struct hpd_work;
|
||||
};
|
||||
|
||||
static inline struct display_connector *
|
||||
|
|
@ -87,6 +90,34 @@ display_connector_bridge_detect(struct drm_bridge *bridge, struct drm_connector
|
|||
return display_connector_detect(bridge);
|
||||
}
|
||||
|
||||
static void display_connector_hpd_enable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct display_connector *conn = to_display_connector(bridge);
|
||||
|
||||
enable_irq(conn->hpd_irq);
|
||||
|
||||
if (conn->bridge.type == DRM_MODE_CONNECTOR_DisplayPort)
|
||||
schedule_work(&conn->hpd_work);
|
||||
}
|
||||
|
||||
static void display_connector_hpd_disable(struct drm_bridge *bridge)
|
||||
{
|
||||
struct display_connector *conn = to_display_connector(bridge);
|
||||
|
||||
if (conn->bridge.type == DRM_MODE_CONNECTOR_DisplayPort)
|
||||
cancel_work_sync(&conn->hpd_work);
|
||||
|
||||
disable_irq(conn->hpd_irq);
|
||||
}
|
||||
|
||||
static void display_connector_hpd_work(struct work_struct *work)
|
||||
{
|
||||
struct display_connector *conn = container_of(work, struct display_connector, hpd_work);
|
||||
struct drm_bridge *bridge = &conn->bridge;
|
||||
|
||||
drm_bridge_hpd_notify(bridge, display_connector_detect(bridge));
|
||||
}
|
||||
|
||||
static const struct drm_edid *display_connector_edid_read(struct drm_bridge *bridge,
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
|
|
@ -178,6 +209,8 @@ static u32 *display_connector_get_input_bus_fmts(struct drm_bridge *bridge,
|
|||
static const struct drm_bridge_funcs display_connector_bridge_funcs = {
|
||||
.attach = display_connector_attach,
|
||||
.detect = display_connector_bridge_detect,
|
||||
.hpd_enable = display_connector_hpd_enable,
|
||||
.hpd_disable = display_connector_hpd_disable,
|
||||
.edid_read = display_connector_edid_read,
|
||||
.atomic_get_output_bus_fmts = display_connector_get_output_bus_fmts,
|
||||
.atomic_get_input_bus_fmts = display_connector_get_input_bus_fmts,
|
||||
|
|
@ -307,6 +340,7 @@ static int display_connector_probe(struct platform_device *pdev)
|
|||
NULL, display_connector_hpd_irq,
|
||||
IRQF_TRIGGER_RISING |
|
||||
IRQF_TRIGGER_FALLING |
|
||||
IRQF_NO_AUTOEN |
|
||||
IRQF_ONESHOT,
|
||||
"HPD", conn);
|
||||
if (ret) {
|
||||
|
|
@ -378,6 +412,8 @@ static int display_connector_probe(struct platform_device *pdev)
|
|||
conn->bridge.ops |= DRM_BRIDGE_OP_DETECT;
|
||||
if (conn->hpd_irq >= 0)
|
||||
conn->bridge.ops |= DRM_BRIDGE_OP_HPD;
|
||||
if (conn->hpd_irq >= 0 && type == DRM_MODE_CONNECTOR_DisplayPort)
|
||||
INIT_WORK(&conn->hpd_work, display_connector_hpd_work);
|
||||
|
||||
dev_dbg(&pdev->dev,
|
||||
"Found %s display connector '%s' %s DDC bus and %s HPD GPIO (ops 0x%x)\n",
|
||||
|
|
|
|||
|
|
@ -1158,9 +1158,7 @@ static int sii902x_probe(struct i2c_client *client)
|
|||
static const char * const supplies[] = {"iovcc", "cvcc12"};
|
||||
int ret;
|
||||
|
||||
ret = i2c_check_functionality(client->adapter,
|
||||
I2C_FUNC_SMBUS_BYTE_DATA);
|
||||
if (!ret) {
|
||||
if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
|
||||
dev_err(dev, "I2C adapter not suitable\n");
|
||||
return -EIO;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2093,6 +2093,12 @@ struct dw_dp *dw_dp_bind(struct device *dev, struct drm_encoder *encoder,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(dw_dp_bind);
|
||||
|
||||
void dw_dp_unbind(struct dw_dp *dp)
|
||||
{
|
||||
drm_dp_aux_unregister(&dp->aux);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dw_dp_unbind);
|
||||
|
||||
MODULE_AUTHOR("Andy Yan <andyshrk@163.com>");
|
||||
MODULE_DESCRIPTION("DW DP Core Library");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
|
|||
|
|
@ -1190,6 +1190,7 @@ static int dw_hdmi_qp_cec_transmit(struct drm_bridge *bridge, u8 attempts,
|
|||
#endif /* CONFIG_DRM_DW_HDMI_QP_CEC */
|
||||
|
||||
static const struct drm_bridge_funcs dw_hdmi_qp_bridge_funcs = {
|
||||
.atomic_get_output_bus_fmts = drm_atomic_helper_bridge_get_hdmi_output_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,
|
||||
|
|
|
|||
|
|
@ -977,7 +977,6 @@ static int sn65dsi83_host_attach(struct sn65dsi83 *ctx)
|
|||
dsi->lanes = dsi_lanes;
|
||||
dsi->format = MIPI_DSI_FMT_RGB888;
|
||||
dsi->mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
|
||||
MIPI_DSI_MODE_VIDEO_NO_HFP | MIPI_DSI_MODE_VIDEO_NO_HBP |
|
||||
MIPI_DSI_MODE_VIDEO_NO_HSA | MIPI_DSI_MODE_NO_EOT_PACKET;
|
||||
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
|
|
|
|||
|
|
@ -122,10 +122,12 @@ static void drm_log_draw_line(struct drm_log_scanout *scanout, const char *s,
|
|||
iosys_map_incr(&map, r.y1 * fb->pitches[0]);
|
||||
for (i = 0; i < len && i < scanout->columns; i++) {
|
||||
u32 color = (i < prefix_len) ? scanout->prefix_color : scanout->front_color;
|
||||
src = drm_draw_get_char_bitmap(font, s[i], font_pitch);
|
||||
drm_log_blit(&map, fb->pitches[0], src, font_pitch,
|
||||
scanout->scaled_font_h, scanout->scaled_font_w,
|
||||
px_width, color);
|
||||
src = font_data_glyph_buf(font->data, font->width, font->height,
|
||||
(unsigned char)s[i]);
|
||||
if (src)
|
||||
drm_log_blit(&map, fb->pitches[0], src, font_pitch,
|
||||
scanout->scaled_font_h, scanout->scaled_font_w,
|
||||
px_width, color);
|
||||
iosys_map_incr(&map, scanout->scaled_font_w * px_width);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -265,26 +265,57 @@ static void drm_bridge_connector_debugfs_init(struct drm_connector *connector,
|
|||
}
|
||||
}
|
||||
|
||||
static void drm_bridge_connector_reset(struct drm_connector *connector)
|
||||
static struct drm_connector_state *
|
||||
drm_bridge_connector_create_state(struct drm_connector *connector)
|
||||
{
|
||||
struct drm_bridge_connector *bridge_connector =
|
||||
to_drm_bridge_connector(connector);
|
||||
struct drm_connector_state *conn_state;
|
||||
|
||||
conn_state = drm_atomic_helper_connector_create_state(connector);
|
||||
if (IS_ERR(conn_state))
|
||||
return conn_state;
|
||||
|
||||
drm_atomic_helper_connector_reset(connector);
|
||||
if (bridge_connector->bridge_hdmi)
|
||||
__drm_atomic_helper_connector_hdmi_reset(connector,
|
||||
connector->state);
|
||||
__drm_atomic_helper_connector_hdmi_state_init(connector,
|
||||
conn_state);
|
||||
|
||||
return conn_state;
|
||||
}
|
||||
|
||||
static enum drm_connector_color_format
|
||||
drm_bridge_connector_color_format(const struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct drm_bridge_connector *bridge_connector =
|
||||
to_drm_bridge_connector(conn_state->connector);
|
||||
|
||||
if (bridge_connector->bridge_hdmi) {
|
||||
switch (conn_state->hdmi.output_format) {
|
||||
default:
|
||||
case DRM_OUTPUT_COLOR_FORMAT_RGB444:
|
||||
return DRM_CONNECTOR_COLOR_FORMAT_RGB444;
|
||||
case DRM_OUTPUT_COLOR_FORMAT_YCBCR444:
|
||||
return DRM_CONNECTOR_COLOR_FORMAT_YCBCR444;
|
||||
case DRM_OUTPUT_COLOR_FORMAT_YCBCR422:
|
||||
return DRM_CONNECTOR_COLOR_FORMAT_YCBCR422;
|
||||
case DRM_OUTPUT_COLOR_FORMAT_YCBCR420:
|
||||
return DRM_CONNECTOR_COLOR_FORMAT_YCBCR420;
|
||||
}
|
||||
}
|
||||
|
||||
return conn_state->color_format;
|
||||
}
|
||||
|
||||
static const struct drm_connector_funcs drm_bridge_connector_funcs = {
|
||||
.reset = drm_bridge_connector_reset,
|
||||
.detect = drm_bridge_connector_detect,
|
||||
.force = drm_bridge_connector_force,
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.atomic_create_state = drm_bridge_connector_create_state,
|
||||
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
|
||||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
.debugfs_init = drm_bridge_connector_debugfs_init,
|
||||
.oob_hotplug_event = drm_bridge_connector_oob_hotplug_event,
|
||||
.color_format = drm_bridge_connector_color_format,
|
||||
};
|
||||
|
||||
/* -----------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -306,17 +306,18 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* __drm_atomic_helper_connector_hdmi_reset() - Initializes all HDMI @drm_connector_state resources
|
||||
* __drm_atomic_helper_connector_hdmi_state_init() - Initialize all HDMI @drm_connector_state resources
|
||||
* @connector: DRM connector
|
||||
* @new_conn_state: connector state to reset
|
||||
* @new_conn_state: connector state to initialize
|
||||
*
|
||||
* Initializes all HDMI resources from a @drm_connector_state without
|
||||
* actually allocating it. This is useful for HDMI drivers, in
|
||||
* combination with __drm_atomic_helper_connector_reset() or
|
||||
* drm_atomic_helper_connector_reset().
|
||||
* combination with __drm_atomic_helper_connector_state_init(),
|
||||
* drm_atomic_helper_connector_reset(), or
|
||||
* drm_atomic_helper_connector_create_state().
|
||||
*/
|
||||
void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector,
|
||||
struct drm_connector_state *new_conn_state)
|
||||
void __drm_atomic_helper_connector_hdmi_state_init(struct drm_connector *connector,
|
||||
struct drm_connector_state *new_conn_state)
|
||||
{
|
||||
unsigned int max_bpc = connector->max_bpc;
|
||||
|
||||
|
|
@ -324,7 +325,7 @@ void __drm_atomic_helper_connector_hdmi_reset(struct drm_connector *connector,
|
|||
new_conn_state->max_requested_bpc = max_bpc;
|
||||
new_conn_state->hdmi.broadcast_rgb = DRM_HDMI_BROADCAST_RGB_AUTO;
|
||||
}
|
||||
EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_reset);
|
||||
EXPORT_SYMBOL(__drm_atomic_helper_connector_hdmi_state_init);
|
||||
|
||||
static enum hdmi_colorspace
|
||||
output_color_format_to_hdmi_colorspace(const struct drm_connector *connector,
|
||||
|
|
@ -541,10 +542,11 @@ sink_supports_format_bpc(const struct drm_connector *connector,
|
|||
drm_dbg_kms(dev, "YUV444 format supported in that configuration.\n");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
drm_dbg_kms(dev, "Unsupported pixel format.\n");
|
||||
return false;
|
||||
default:
|
||||
drm_dbg_kms(dev, "Unsupported pixel format.\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static enum drm_mode_status
|
||||
|
|
@ -669,8 +671,39 @@ hdmi_compute_config(const struct drm_connector *connector,
|
|||
unsigned int max_bpc = clamp_t(unsigned int,
|
||||
conn_state->max_bpc,
|
||||
8, connector->max_bpc);
|
||||
enum drm_output_color_format fmt;
|
||||
int ret;
|
||||
|
||||
if (conn_state->color_format != DRM_CONNECTOR_COLOR_FORMAT_AUTO) {
|
||||
switch (conn_state->color_format) {
|
||||
case DRM_CONNECTOR_COLOR_FORMAT_AUTO:
|
||||
drm_warn(connector->dev, "AUTO format in non-AUTO path.\n");
|
||||
fallthrough;
|
||||
case DRM_CONNECTOR_COLOR_FORMAT_RGB444:
|
||||
fmt = DRM_OUTPUT_COLOR_FORMAT_RGB444;
|
||||
break;
|
||||
case DRM_CONNECTOR_COLOR_FORMAT_YCBCR444:
|
||||
fmt = DRM_OUTPUT_COLOR_FORMAT_YCBCR444;
|
||||
break;
|
||||
case DRM_CONNECTOR_COLOR_FORMAT_YCBCR422:
|
||||
fmt = DRM_OUTPUT_COLOR_FORMAT_YCBCR422;
|
||||
break;
|
||||
case DRM_CONNECTOR_COLOR_FORMAT_YCBCR420:
|
||||
fmt = DRM_OUTPUT_COLOR_FORMAT_YCBCR420;
|
||||
break;
|
||||
default:
|
||||
drm_dbg_kms(connector->dev, "HDMI does not support color format '%d'.\n",
|
||||
conn_state->color_format);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return hdmi_compute_format_bpc(connector, conn_state, mode, max_bpc, fmt);
|
||||
}
|
||||
|
||||
/*
|
||||
* For %DRM_CONNECTOR_COLOR_FORMAT_AUTO, try RGB first, and fall back
|
||||
* to the less bandwidth-intensive YCBCR420 if RGB fails.
|
||||
*/
|
||||
ret = hdmi_compute_format_bpc(connector, conn_state, mode, max_bpc,
|
||||
DRM_OUTPUT_COLOR_FORMAT_RGB444);
|
||||
if (ret) {
|
||||
|
|
@ -910,8 +943,21 @@ drm_hdmi_connector_mode_valid(struct drm_connector *connector,
|
|||
const struct drm_display_mode *mode)
|
||||
{
|
||||
unsigned long long clock;
|
||||
enum drm_output_color_format fmt;
|
||||
|
||||
clock = drm_hdmi_compute_mode_clock(mode, 8, DRM_OUTPUT_COLOR_FORMAT_RGB444);
|
||||
if (drm_mode_is_420_only(&connector->display_info, mode)) {
|
||||
if (connector->ycbcr_420_allowed)
|
||||
fmt = DRM_OUTPUT_COLOR_FORMAT_YCBCR420;
|
||||
else
|
||||
return MODE_NO_420;
|
||||
} else if (drm_mode_is_420_also(&connector->display_info, mode) &&
|
||||
connector->ycbcr_420_allowed) {
|
||||
fmt = DRM_OUTPUT_COLOR_FORMAT_YCBCR420;
|
||||
} else {
|
||||
fmt = DRM_OUTPUT_COLOR_FORMAT_RGB444;
|
||||
}
|
||||
|
||||
clock = drm_hdmi_compute_mode_clock(mode, 8, fmt);
|
||||
if (!clock)
|
||||
return MODE_ERROR;
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,86 @@
|
|||
#include "drm_crtc_internal.h"
|
||||
#include "drm_internal.h"
|
||||
|
||||
/**
|
||||
* DOC: state lifetime
|
||||
*
|
||||
* &drm_atomic_commit represents an update to modeset pipeline state.
|
||||
* It's a transient object that holds a state update as a collection of
|
||||
* pointers to individual objects' states. &drm_atomic_commit has a much
|
||||
* shorter lifetime than the objects' states, since it's only allocated
|
||||
* while preparing, checking or committing the update, while object
|
||||
* states are allocated when preparing the update and kept alive as long
|
||||
* as they are active in the device.
|
||||
*
|
||||
* Their respective lifetimes are:
|
||||
*
|
||||
* - at driver initialization time, the driver calls
|
||||
* drm_mode_config_create_initial_state() to allocate an initial,
|
||||
* pristine, state for each object and stores it in the objects state
|
||||
* pointer. Historically, this was one of drm_mode_config_reset() job,
|
||||
* so one might still encounter it in a driver.
|
||||
*
|
||||
* - When resuming from suspend, drm_mode_config_reset() resets the
|
||||
* software and hardware state to a known default and stores it in the
|
||||
* object's state pointer. Not all objects are affected by
|
||||
* drm_mode_config_reset() though.
|
||||
*
|
||||
* - whenever a new update is needed:
|
||||
*
|
||||
* + drm_atomic_commit_alloc() allocates a new &drm_atomic_commit
|
||||
* instance.
|
||||
*
|
||||
* + The code triggering the commit (ioctl, client modeset,
|
||||
* drm_atomic_helper_reset_crtc(), etc.) copies the current active
|
||||
* state of all entities affected by the update into this new
|
||||
* &drm_atomic_commit using drm_atomic_get_plane_state(),
|
||||
* drm_atomic_get_crtc_state(), drm_atomic_get_connector_state(), or
|
||||
* drm_atomic_get_private_obj_state(). This new state can then be
|
||||
* modified.
|
||||
*
|
||||
* At that point, &drm_atomic_commit stores three state pointers for
|
||||
* any affected entity: the "old" and "new" states, and
|
||||
* state_to_destroy. The old state is the state currently active in
|
||||
* the hardware, which is either the one initialized by reset() or a
|
||||
* newer one if a commit has been made. The new state is the state
|
||||
* we just allocated and we might eventually commit to the hardware.
|
||||
* The state_to_destroy points to the state we'll eventually have to
|
||||
* free when the drm_atomic_commit will be destroyed, and points to
|
||||
* the new state for now since the old state is still the active
|
||||
* state.
|
||||
*
|
||||
* + After the calling code populated the commit with the entities
|
||||
* states, it updates the new states with the new values we need to
|
||||
* commit. The new commit instance is now ready.
|
||||
*
|
||||
* + Then we have two branches depending on the calling code intent:
|
||||
*
|
||||
* - If the calling code only wants to check that the commit would
|
||||
* work (for example because of the DRM_MODE_ATOMIC_TEST_ONLY
|
||||
* flag). It calls drm_atomic_check_only(), which in turn checks
|
||||
* all these states by invoking atomic_check on all affected
|
||||
* pipeline stages.
|
||||
*
|
||||
* - If the calling code actually wants to trigger a commit, it
|
||||
* calls drm_atomic_commit(). The first stage is the check
|
||||
* mentioned above, and if the check is successful, it performs
|
||||
* the commit. Part of the commit is a call to
|
||||
* drm_atomic_helper_swap_state() which turns the new states into
|
||||
* the active states. After swapping states, each object's state
|
||||
* pointer now refers to the formerly new state. The
|
||||
* state_to_destroy now refers to the formerly old state.
|
||||
*
|
||||
* + Once done, and when the last reference to our &drm_atomic_commit
|
||||
* is given up through drm_atomic_commit_put(), it calls
|
||||
* __drm_atomic_commit_free(). In turn, __drm_atomic_commit_free()
|
||||
* calls drm_atomic_commit_clear() that will free all
|
||||
* state_to_destroy (ie. old states), and it finally frees
|
||||
* &drm_atomic_commit instance.
|
||||
*
|
||||
* + Now, we don't have any active &drm_atomic_commit anymore, and
|
||||
* only the entity active states remain allocated.
|
||||
*/
|
||||
|
||||
void __drm_crtc_commit_free(struct kref *kref)
|
||||
{
|
||||
struct drm_crtc_commit *commit =
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include <linux/export.h>
|
||||
#include <linux/dma-fence.h>
|
||||
#include <linux/ktime.h>
|
||||
#include <linux/media-bus-format.h>
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
|
|
@ -737,6 +738,11 @@ drm_atomic_helper_check_modeset(struct drm_device *dev,
|
|||
if (old_connector_state->max_requested_bpc !=
|
||||
new_connector_state->max_requested_bpc)
|
||||
new_crtc_state->connectors_changed = true;
|
||||
|
||||
if (old_connector_state->color_format !=
|
||||
new_connector_state->color_format)
|
||||
new_crtc_state->connectors_changed = true;
|
||||
|
||||
}
|
||||
|
||||
if (funcs->atomic_check)
|
||||
|
|
@ -4102,3 +4108,83 @@ drm_atomic_helper_bridge_propagate_bus_fmt(struct drm_bridge *bridge,
|
|||
return input_fmts;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_atomic_helper_bridge_propagate_bus_fmt);
|
||||
|
||||
/**
|
||||
* drm_atomic_helper_bridge_get_hdmi_output_bus_fmts - helper implementing
|
||||
* atomic_get_output_bus_fmts for HDMI
|
||||
* @bridge: pointer to &struct drm_bridge
|
||||
* @bridge_state: pointer to the current bridge state
|
||||
* @crtc_state: pointer to the current CRTC state
|
||||
* @conn_state: pointer to the current connector state
|
||||
* @num_output_fmts: pointer to where the number of entries in the returned array
|
||||
* will be stored. Set to 0 if unsuccessful.
|
||||
*
|
||||
* Common implementation for the &drm_bridge_funcs.atomic_get_output_bus_fmts
|
||||
* operation that's applicable to HDMI connectors.
|
||||
*
|
||||
* Returns: a newly allocated array of u32 values of length \*@num_output_fmts,
|
||||
* representing all the MEDIA_BUS_FMTS\_ for the current connector state's
|
||||
* chosen HDMI output bits per compoennt, or %NULL if it fails to allocate one.
|
||||
*/
|
||||
u32 *
|
||||
drm_atomic_helper_bridge_get_hdmi_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)
|
||||
{
|
||||
unsigned int num_fmts = 0;
|
||||
u32 *out_fmts;
|
||||
|
||||
/*
|
||||
* bridge->supported_formats is a bit field of BIT(enum drm_output_color_format)
|
||||
* values. The smallest hweight that is smaller than or equal to
|
||||
* %DRM_OUTPUT_COLOR_FORMAT_COUNT will do for counting set bits here.
|
||||
*/
|
||||
BUILD_BUG_ON(const_true(DRM_OUTPUT_COLOR_FORMAT_COUNT > 8));
|
||||
out_fmts = kmalloc_array(hweight8(bridge->supported_formats),
|
||||
sizeof(u32), GFP_KERNEL);
|
||||
if (!out_fmts) {
|
||||
*num_output_fmts = 0;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
switch (conn_state->hdmi.output_bpc) {
|
||||
case 12:
|
||||
if (bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444))
|
||||
out_fmts[num_fmts++] = MEDIA_BUS_FMT_RGB121212_1X36;
|
||||
if (bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444))
|
||||
out_fmts[num_fmts++] = MEDIA_BUS_FMT_YUV12_1X36;
|
||||
if (bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422))
|
||||
out_fmts[num_fmts++] = MEDIA_BUS_FMT_UYVY12_1X24;
|
||||
if (bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420))
|
||||
out_fmts[num_fmts++] = MEDIA_BUS_FMT_UYYVYY12_0_5X36;
|
||||
break;
|
||||
case 10:
|
||||
if (bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444))
|
||||
out_fmts[num_fmts++] = MEDIA_BUS_FMT_RGB101010_1X30;
|
||||
if (bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444))
|
||||
out_fmts[num_fmts++] = MEDIA_BUS_FMT_YUV10_1X30;
|
||||
if (bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422))
|
||||
out_fmts[num_fmts++] = MEDIA_BUS_FMT_UYVY10_1X20;
|
||||
if (bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420))
|
||||
out_fmts[num_fmts++] = MEDIA_BUS_FMT_UYYVYY10_0_5X30;
|
||||
break;
|
||||
default:
|
||||
if (bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444))
|
||||
out_fmts[num_fmts++] = MEDIA_BUS_FMT_RGB888_1X24;
|
||||
if (bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444))
|
||||
out_fmts[num_fmts++] = MEDIA_BUS_FMT_YUV8_1X24;
|
||||
if (bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422))
|
||||
out_fmts[num_fmts++] = MEDIA_BUS_FMT_UYVY8_1X16;
|
||||
if (bridge->supported_formats & BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420))
|
||||
out_fmts[num_fmts++] = MEDIA_BUS_FMT_UYYVYY8_0_5X24;
|
||||
break;
|
||||
}
|
||||
|
||||
*num_output_fmts = num_fmts;
|
||||
|
||||
return out_fmts;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_atomic_helper_bridge_get_hdmi_output_bus_fmts);
|
||||
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@
|
|||
*/
|
||||
|
||||
/**
|
||||
* __drm_atomic_helper_crtc_state_reset - reset the CRTC state
|
||||
* __drm_atomic_helper_crtc_state_init - Initialize the CRTC state
|
||||
* @crtc_state: atomic CRTC state, must not be NULL
|
||||
* @crtc: CRTC object, must not be NULL
|
||||
*
|
||||
|
|
@ -71,13 +71,13 @@
|
|||
* values. This is useful for drivers that subclass the CRTC state.
|
||||
*/
|
||||
void
|
||||
__drm_atomic_helper_crtc_state_reset(struct drm_crtc_state *crtc_state,
|
||||
struct drm_crtc *crtc)
|
||||
__drm_atomic_helper_crtc_state_init(struct drm_crtc_state *crtc_state,
|
||||
struct drm_crtc *crtc)
|
||||
{
|
||||
crtc_state->crtc = crtc;
|
||||
crtc_state->background_color = DRM_ARGB64_PREP(0xffff, 0, 0, 0);
|
||||
}
|
||||
EXPORT_SYMBOL(__drm_atomic_helper_crtc_state_reset);
|
||||
EXPORT_SYMBOL(__drm_atomic_helper_crtc_state_init);
|
||||
|
||||
/**
|
||||
* __drm_atomic_helper_crtc_reset - reset state on CRTC
|
||||
|
|
@ -96,7 +96,7 @@ __drm_atomic_helper_crtc_reset(struct drm_crtc *crtc,
|
|||
struct drm_crtc_state *crtc_state)
|
||||
{
|
||||
if (crtc_state)
|
||||
__drm_atomic_helper_crtc_state_reset(crtc_state, crtc);
|
||||
__drm_atomic_helper_crtc_state_init(crtc_state, crtc);
|
||||
|
||||
if (drm_dev_has_vblank(crtc->dev))
|
||||
drm_crtc_vblank_reset(crtc);
|
||||
|
|
@ -124,6 +124,31 @@ void drm_atomic_helper_crtc_reset(struct drm_crtc *crtc)
|
|||
}
|
||||
EXPORT_SYMBOL(drm_atomic_helper_crtc_reset);
|
||||
|
||||
/**
|
||||
* drm_atomic_helper_crtc_create_state - default &drm_crtc_funcs.atomic_create_state hook for crtcs
|
||||
* @crtc: crtc object
|
||||
*
|
||||
* Allocates and initializes pristine @drm_crtc_state.
|
||||
*
|
||||
* This is useful for drivers that don't subclass @drm_crtc_state.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to new crtc state, or ERR_PTR on failure.
|
||||
*/
|
||||
struct drm_crtc_state *drm_atomic_helper_crtc_create_state(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_crtc_state *state;
|
||||
|
||||
state = kzalloc_obj(*state);
|
||||
if (!state)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
__drm_atomic_helper_crtc_state_init(state, crtc);
|
||||
|
||||
return state;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_atomic_helper_crtc_create_state);
|
||||
|
||||
/**
|
||||
* __drm_atomic_helper_crtc_duplicate_state - copy atomic CRTC state
|
||||
* @crtc: CRTC object
|
||||
|
|
@ -237,15 +262,15 @@ void drm_atomic_helper_crtc_destroy_state(struct drm_crtc *crtc,
|
|||
EXPORT_SYMBOL(drm_atomic_helper_crtc_destroy_state);
|
||||
|
||||
/**
|
||||
* __drm_atomic_helper_plane_state_reset - resets plane state to default values
|
||||
* __drm_atomic_helper_plane_state_init - Initialize the plane state
|
||||
* @plane_state: atomic plane state, must not be NULL
|
||||
* @plane: plane object, must not be NULL
|
||||
*
|
||||
* Initializes the newly allocated @plane_state with default
|
||||
* values. This is useful for drivers that subclass the CRTC state.
|
||||
* values. This is useful for drivers that subclass the plane state.
|
||||
*/
|
||||
void __drm_atomic_helper_plane_state_reset(struct drm_plane_state *plane_state,
|
||||
struct drm_plane *plane)
|
||||
void __drm_atomic_helper_plane_state_init(struct drm_plane_state *plane_state,
|
||||
struct drm_plane *plane)
|
||||
{
|
||||
u64 val;
|
||||
|
||||
|
|
@ -297,7 +322,7 @@ void __drm_atomic_helper_plane_state_reset(struct drm_plane_state *plane_state,
|
|||
plane_state->hotspot_y = val;
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(__drm_atomic_helper_plane_state_reset);
|
||||
EXPORT_SYMBOL(__drm_atomic_helper_plane_state_init);
|
||||
|
||||
/**
|
||||
* __drm_atomic_helper_plane_reset - reset state on plane
|
||||
|
|
@ -305,7 +330,7 @@ EXPORT_SYMBOL(__drm_atomic_helper_plane_state_reset);
|
|||
* @plane_state: plane state to assign
|
||||
*
|
||||
* Initializes the newly allocated @plane_state and assigns it to
|
||||
* the &drm_crtc->state pointer of @plane, usually required when
|
||||
* the &drm_plane->state pointer of @plane, usually required when
|
||||
* initializing the drivers or when called from the &drm_plane_funcs.reset
|
||||
* hook.
|
||||
*
|
||||
|
|
@ -315,7 +340,7 @@ void __drm_atomic_helper_plane_reset(struct drm_plane *plane,
|
|||
struct drm_plane_state *plane_state)
|
||||
{
|
||||
if (plane_state)
|
||||
__drm_atomic_helper_plane_state_reset(plane_state, plane);
|
||||
__drm_atomic_helper_plane_state_init(plane_state, plane);
|
||||
|
||||
plane->state = plane_state;
|
||||
}
|
||||
|
|
@ -340,6 +365,31 @@ void drm_atomic_helper_plane_reset(struct drm_plane *plane)
|
|||
}
|
||||
EXPORT_SYMBOL(drm_atomic_helper_plane_reset);
|
||||
|
||||
/**
|
||||
* drm_atomic_helper_plane_create_state - default &drm_plane_funcs.atomic_create_state hook for planes
|
||||
* @plane: plane object
|
||||
*
|
||||
* Allocates and initializes pristine @drm_plane_state.
|
||||
*
|
||||
* This is useful for drivers that don't subclass @drm_plane_state.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to new plane state, or ERR_PTR on failure.
|
||||
*/
|
||||
struct drm_plane_state *drm_atomic_helper_plane_create_state(struct drm_plane *plane)
|
||||
{
|
||||
struct drm_plane_state *state;
|
||||
|
||||
state = kzalloc_obj(*state);
|
||||
if (!state)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
__drm_atomic_helper_plane_state_init(state, plane);
|
||||
|
||||
return state;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_atomic_helper_plane_create_state);
|
||||
|
||||
/**
|
||||
* __drm_atomic_helper_plane_duplicate_state - copy atomic plane state
|
||||
* @plane: plane object
|
||||
|
|
@ -426,20 +476,20 @@ void drm_atomic_helper_plane_destroy_state(struct drm_plane *plane,
|
|||
EXPORT_SYMBOL(drm_atomic_helper_plane_destroy_state);
|
||||
|
||||
/**
|
||||
* __drm_atomic_helper_connector_state_reset - reset the connector state
|
||||
* __drm_atomic_helper_connector_state_init - Initialize the connector state
|
||||
* @conn_state: atomic connector state, must not be NULL
|
||||
* @connector: connectotr object, must not be NULL
|
||||
* @connector: connector object, must not be NULL
|
||||
*
|
||||
* Initializes the newly allocated @conn_state with default
|
||||
* values. This is useful for drivers that subclass the connector state.
|
||||
*/
|
||||
void
|
||||
__drm_atomic_helper_connector_state_reset(struct drm_connector_state *conn_state,
|
||||
struct drm_connector *connector)
|
||||
__drm_atomic_helper_connector_state_init(struct drm_connector_state *conn_state,
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
conn_state->connector = connector;
|
||||
}
|
||||
EXPORT_SYMBOL(__drm_atomic_helper_connector_state_reset);
|
||||
EXPORT_SYMBOL(__drm_atomic_helper_connector_state_init);
|
||||
|
||||
/**
|
||||
* __drm_atomic_helper_connector_reset - reset state on connector
|
||||
|
|
@ -458,7 +508,7 @@ __drm_atomic_helper_connector_reset(struct drm_connector *connector,
|
|||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
if (conn_state)
|
||||
__drm_atomic_helper_connector_state_reset(conn_state, connector);
|
||||
__drm_atomic_helper_connector_state_init(conn_state, connector);
|
||||
|
||||
connector->state = conn_state;
|
||||
}
|
||||
|
|
@ -484,6 +534,32 @@ void drm_atomic_helper_connector_reset(struct drm_connector *connector)
|
|||
}
|
||||
EXPORT_SYMBOL(drm_atomic_helper_connector_reset);
|
||||
|
||||
/**
|
||||
* drm_atomic_helper_connector_create_state - default &drm_connector_funcs.atomic_create_state hook for connectors
|
||||
* @connector: connector object
|
||||
*
|
||||
* Allocates and initializes pristine @drm_connector_state.
|
||||
*
|
||||
* This is useful for drivers that don't subclass @drm_connector_state.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to new connector state, or ERR_PTR on failure.
|
||||
*/
|
||||
struct drm_connector_state *
|
||||
drm_atomic_helper_connector_create_state(struct drm_connector *connector)
|
||||
{
|
||||
struct drm_connector_state *state;
|
||||
|
||||
state = kzalloc_obj(*state);
|
||||
if (!state)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
__drm_atomic_helper_connector_state_init(state, connector);
|
||||
|
||||
return state;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_atomic_helper_connector_create_state);
|
||||
|
||||
/**
|
||||
* drm_atomic_helper_connector_tv_margins_reset - Resets TV connector properties
|
||||
* @connector: DRM connector
|
||||
|
|
@ -731,8 +807,6 @@ void __drm_atomic_helper_private_obj_create_state(struct drm_private_obj *obj,
|
|||
{
|
||||
if (state)
|
||||
state->obj = obj;
|
||||
|
||||
obj->state = state;
|
||||
}
|
||||
EXPORT_SYMBOL(__drm_atomic_helper_private_obj_create_state);
|
||||
|
||||
|
|
|
|||
|
|
@ -960,6 +960,8 @@ static int drm_atomic_connector_set_property(struct drm_connector *connector,
|
|||
state->privacy_screen_sw_state = val;
|
||||
} else if (property == connector->broadcast_rgb_property) {
|
||||
state->hdmi.broadcast_rgb = val;
|
||||
} else if (property == connector->color_format_property) {
|
||||
state->color_format = val;
|
||||
} else if (connector->funcs->atomic_set_property) {
|
||||
return connector->funcs->atomic_set_property(connector,
|
||||
state, property, val);
|
||||
|
|
@ -1045,6 +1047,8 @@ drm_atomic_connector_get_property(struct drm_connector *connector,
|
|||
*val = state->privacy_screen_sw_state;
|
||||
} else if (property == connector->broadcast_rgb_property) {
|
||||
*val = state->hdmi.broadcast_rgb;
|
||||
} else if (property == connector->color_format_property) {
|
||||
*val = state->color_format;
|
||||
} else if (connector->funcs->atomic_get_property) {
|
||||
return connector->funcs->atomic_get_property(connector,
|
||||
state, property, val);
|
||||
|
|
|
|||
|
|
@ -198,6 +198,46 @@
|
|||
* driver.
|
||||
*/
|
||||
|
||||
/**
|
||||
* DOC: bridge chain format selection
|
||||
*
|
||||
* A bridge chain, from display output processor to connector, may contain
|
||||
* bridges capable of converting between bus formats on their inputs, and
|
||||
* output formats on their outputs. For example, a bridge may be able to convert
|
||||
* from RGB to YCbCr 4:4:4, and pass through YCbCr 4:2:0 as-is, but not convert
|
||||
* from RGB to YCbCr 4:2:0. This means not all input formats map to all output
|
||||
* formats.
|
||||
*
|
||||
* Further adding to this, a desired output color format, as specified with the
|
||||
* "color format" DRM property, might not correspond 1:1 to what the display
|
||||
* driver should set at its output. The bridge chain it feeds into may only be
|
||||
* able to reach the desired output format, if a conversion from a different
|
||||
* starting format is performed.
|
||||
*
|
||||
* To deal with this complexity, the recursive bridge chain bus format selection
|
||||
* logic starts with the last bridge in the chain, usually the connector, and
|
||||
* then recursively walks the chain of bridges backwards to the first bridge,
|
||||
* trying to find a path.
|
||||
*
|
||||
* For a display driver to work in such a scenario, it should read the first
|
||||
* bridge's bridge state to figure out which bus format the chain resolved to.
|
||||
* If the first bridge's input format resolved to %MEDIA_BUS_FMT_FIXED, then its
|
||||
* output format should be used.
|
||||
*
|
||||
* Special handling is done for HDMI as it relates to format selection. Instead
|
||||
* of directly using the "color format" DRM property for bridge chains that end
|
||||
* in HDMI bridges, the bridge chain format selection logic will trust the logic
|
||||
* that set the HDMI output format. For the common HDMI state helper
|
||||
* functionality, this means that %DRM_CONNECTOR_COLOR_FORMAT_AUTO will allow
|
||||
* fallbacks to YCBCr 4:2:0 if the bandwidth requirements would otherwise be too
|
||||
* high but the mode and connector allow it.
|
||||
*
|
||||
* For bridge chains that do not end in an HDMI bridge,
|
||||
* %DRM_CONNECTOR_COLOR_FORMAT_AUTO will be satisfied with the first output
|
||||
* format on the last bridge for which it can find a path back to the first
|
||||
* bridge.
|
||||
*/
|
||||
|
||||
/* Protect bridge_list and bridge_lingering_list */
|
||||
static DEFINE_MUTEX(bridge_lock);
|
||||
static LIST_HEAD(bridge_list);
|
||||
|
|
@ -417,6 +457,7 @@ void drm_bridge_add(struct drm_bridge *bridge)
|
|||
if (!list_empty(&bridge->list))
|
||||
list_del_init(&bridge->list);
|
||||
|
||||
mutex_init(&bridge->hpd_state_mutex);
|
||||
mutex_init(&bridge->hpd_mutex);
|
||||
|
||||
if (bridge->ops & DRM_BRIDGE_OP_HDMI)
|
||||
|
|
@ -469,6 +510,7 @@ void drm_bridge_remove(struct drm_bridge *bridge)
|
|||
mutex_unlock(&bridge_lock);
|
||||
|
||||
mutex_destroy(&bridge->hpd_mutex);
|
||||
mutex_destroy(&bridge->hpd_state_mutex);
|
||||
|
||||
drm_bridge_put(bridge);
|
||||
}
|
||||
|
|
@ -1149,6 +1191,47 @@ static int select_bus_fmt_recursive(struct drm_bridge *first_bridge,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static bool __pure bus_format_is_color_fmt(u32 bus_fmt, enum drm_connector_color_format fmt)
|
||||
{
|
||||
if (fmt == DRM_CONNECTOR_COLOR_FORMAT_AUTO)
|
||||
return true;
|
||||
|
||||
switch (bus_fmt) {
|
||||
case MEDIA_BUS_FMT_FIXED:
|
||||
return true;
|
||||
case MEDIA_BUS_FMT_RGB888_1X24:
|
||||
case MEDIA_BUS_FMT_RGB101010_1X30:
|
||||
case MEDIA_BUS_FMT_RGB121212_1X36:
|
||||
case MEDIA_BUS_FMT_RGB161616_1X48:
|
||||
return fmt == DRM_CONNECTOR_COLOR_FORMAT_RGB444;
|
||||
case MEDIA_BUS_FMT_YUV8_1X24:
|
||||
case MEDIA_BUS_FMT_YUV10_1X30:
|
||||
case MEDIA_BUS_FMT_YUV12_1X36:
|
||||
case MEDIA_BUS_FMT_YUV16_1X48:
|
||||
return fmt == DRM_CONNECTOR_COLOR_FORMAT_YCBCR444;
|
||||
case MEDIA_BUS_FMT_UYVY8_1X16:
|
||||
case MEDIA_BUS_FMT_VYUY8_1X16:
|
||||
case MEDIA_BUS_FMT_YUYV8_1X16:
|
||||
case MEDIA_BUS_FMT_YVYU8_1X16:
|
||||
case MEDIA_BUS_FMT_UYVY10_1X20:
|
||||
case MEDIA_BUS_FMT_YUYV10_1X20:
|
||||
case MEDIA_BUS_FMT_VYUY10_1X20:
|
||||
case MEDIA_BUS_FMT_YVYU10_1X20:
|
||||
case MEDIA_BUS_FMT_UYVY12_1X24:
|
||||
case MEDIA_BUS_FMT_VYUY12_1X24:
|
||||
case MEDIA_BUS_FMT_YUYV12_1X24:
|
||||
case MEDIA_BUS_FMT_YVYU12_1X24:
|
||||
return fmt == DRM_CONNECTOR_COLOR_FORMAT_YCBCR422;
|
||||
case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
|
||||
case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
|
||||
case MEDIA_BUS_FMT_UYYVYY12_0_5X36:
|
||||
case MEDIA_BUS_FMT_UYYVYY16_0_5X48:
|
||||
return fmt == DRM_CONNECTOR_COLOR_FORMAT_YCBCR420;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is called by &drm_atomic_bridge_chain_check() just before
|
||||
* calling &drm_bridge_funcs.atomic_check() on all elements of the chain.
|
||||
|
|
@ -1192,6 +1275,7 @@ drm_atomic_bridge_chain_select_bus_fmts(struct drm_bridge *bridge,
|
|||
struct drm_encoder *encoder = bridge->encoder;
|
||||
struct drm_bridge_state *last_bridge_state;
|
||||
unsigned int i, num_out_bus_fmts = 0;
|
||||
enum drm_connector_color_format fmt;
|
||||
u32 *out_bus_fmts;
|
||||
int ret = 0;
|
||||
|
||||
|
|
@ -1233,11 +1317,31 @@ drm_atomic_bridge_chain_select_bus_fmts(struct drm_bridge *bridge,
|
|||
out_bus_fmts[0] = MEDIA_BUS_FMT_FIXED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Instead of directly accessing conn_state.color_format, call into a
|
||||
* connector function that allows connector implementations (e.g. for
|
||||
* bridge connectors including HDMI bridges, where the HDMI helpers will
|
||||
* have already chosen an appropriate output format) to override the
|
||||
* selected format.
|
||||
*/
|
||||
fmt = drm_connector_get_color_format(conn_state);
|
||||
|
||||
for (i = 0; i < num_out_bus_fmts; i++) {
|
||||
if (!bus_format_is_color_fmt(out_bus_fmts[i], fmt)) {
|
||||
drm_dbg_kms(last_bridge->dev,
|
||||
"Skipping bus format 0x%04x as it doesn't match format %d\n",
|
||||
out_bus_fmts[i], fmt);
|
||||
ret = -ENOTSUPP;
|
||||
continue;
|
||||
}
|
||||
ret = select_bus_fmt_recursive(bridge, last_bridge, crtc_state,
|
||||
conn_state, out_bus_fmts[i]);
|
||||
if (ret != -ENOTSUPP)
|
||||
if (ret != -ENOTSUPP) {
|
||||
drm_dbg_kms(last_bridge->dev,
|
||||
"Found bridge chain ending with bus format 0x%04x\n",
|
||||
out_bus_fmts[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
kfree(out_bus_fmts);
|
||||
|
|
@ -1451,19 +1555,25 @@ void drm_bridge_hpd_enable(struct drm_bridge *bridge,
|
|||
if (!(bridge->ops & DRM_BRIDGE_OP_HPD))
|
||||
return;
|
||||
|
||||
mutex_lock(&bridge->hpd_state_mutex);
|
||||
|
||||
mutex_lock(&bridge->hpd_mutex);
|
||||
|
||||
if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n"))
|
||||
if (WARN(bridge->hpd_cb, "Hot plug detection already enabled\n")) {
|
||||
mutex_unlock(&bridge->hpd_mutex);
|
||||
goto unlock;
|
||||
}
|
||||
|
||||
bridge->hpd_cb = cb;
|
||||
bridge->hpd_data = data;
|
||||
|
||||
mutex_unlock(&bridge->hpd_mutex);
|
||||
|
||||
if (bridge->funcs->hpd_enable)
|
||||
bridge->funcs->hpd_enable(bridge);
|
||||
|
||||
unlock:
|
||||
mutex_unlock(&bridge->hpd_mutex);
|
||||
mutex_unlock(&bridge->hpd_state_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_bridge_hpd_enable);
|
||||
|
||||
|
|
@ -1484,13 +1594,15 @@ void drm_bridge_hpd_disable(struct drm_bridge *bridge)
|
|||
if (!(bridge->ops & DRM_BRIDGE_OP_HPD))
|
||||
return;
|
||||
|
||||
mutex_lock(&bridge->hpd_mutex);
|
||||
mutex_lock(&bridge->hpd_state_mutex);
|
||||
if (bridge->funcs->hpd_disable)
|
||||
bridge->funcs->hpd_disable(bridge);
|
||||
|
||||
mutex_lock(&bridge->hpd_mutex);
|
||||
bridge->hpd_cb = NULL;
|
||||
bridge->hpd_data = NULL;
|
||||
mutex_unlock(&bridge->hpd_mutex);
|
||||
mutex_unlock(&bridge->hpd_state_mutex);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(drm_bridge_hpd_disable);
|
||||
|
||||
|
|
|
|||
|
|
@ -47,29 +47,23 @@ void drm_buddy_print(struct gpu_buddy *mm, struct drm_printer *p)
|
|||
mm->chunk_size >> 10, mm->size >> 20, mm->avail >> 20, mm->clear_avail >> 20);
|
||||
|
||||
for (order = mm->max_order; order >= 0; order--) {
|
||||
struct gpu_buddy_block *block, *tmp;
|
||||
struct rb_root *root;
|
||||
u64 count = 0, free;
|
||||
unsigned int tree;
|
||||
|
||||
for_each_free_tree(tree) {
|
||||
root = &mm->free_trees[tree][order];
|
||||
|
||||
rbtree_postorder_for_each_entry_safe(block, tmp, root, rb) {
|
||||
BUG_ON(!gpu_buddy_block_is_free(block));
|
||||
count++;
|
||||
}
|
||||
}
|
||||
u64 free_count = mm->free_scoreboard[order];
|
||||
u64 used_count = mm->used_scoreboard[order];
|
||||
u64 block_size = mm->chunk_size << order;
|
||||
u64 free = free_count * block_size;
|
||||
u64 used = used_count * block_size;
|
||||
|
||||
drm_printf(p, "order-%2d ", order);
|
||||
|
||||
free = count * (mm->chunk_size << order);
|
||||
if (free < SZ_1M)
|
||||
drm_printf(p, "free: %8llu KiB", free >> 10);
|
||||
if (block_size < SZ_1M)
|
||||
drm_printf(p, "free: %8llu KiB, used: %8llu KiB",
|
||||
free >> 10, used >> 10);
|
||||
else
|
||||
drm_printf(p, "free: %8llu MiB", free >> 20);
|
||||
drm_printf(p, "free: %8llu MiB, used: %8llu MiB",
|
||||
free >> 20, used >> 20);
|
||||
|
||||
drm_printf(p, ", blocks: %llu\n", count);
|
||||
drm_printf(p, ", free_blocks: %llu, used_blocks: %llu\n",
|
||||
free_count, used_count);
|
||||
}
|
||||
}
|
||||
EXPORT_SYMBOL(drm_buddy_print);
|
||||
|
|
|
|||
|
|
@ -498,15 +498,15 @@ void drm_colorop_atomic_destroy_state(struct drm_colorop *colorop,
|
|||
}
|
||||
|
||||
/**
|
||||
* __drm_colorop_state_reset - resets colorop state to default values
|
||||
* __drm_colorop_state_init - Initializes colorop state to default values
|
||||
* @colorop_state: atomic colorop state, must not be NULL
|
||||
* @colorop: colorop object, must not be NULL
|
||||
*
|
||||
* Initializes the newly allocated @colorop_state with default
|
||||
* values. This is useful for drivers that subclass the CRTC state.
|
||||
* values. This is useful for drivers that subclass the colorop state.
|
||||
*/
|
||||
static void __drm_colorop_state_reset(struct drm_colorop_state *colorop_state,
|
||||
struct drm_colorop *colorop)
|
||||
static void __drm_colorop_state_init(struct drm_colorop_state *colorop_state,
|
||||
struct drm_colorop *colorop)
|
||||
{
|
||||
u64 val;
|
||||
|
||||
|
|
@ -535,15 +535,38 @@ static void __drm_colorop_state_reset(struct drm_colorop_state *colorop_state,
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_atomic_helper_colorop_create_state - Allocates and initializes colorop atomic state
|
||||
* @colorop: drm colorop
|
||||
*
|
||||
* Initializes a pristine @drm_colorop_state.
|
||||
*
|
||||
* RETURNS:
|
||||
* Pointer to new colorop state, or ERR_PTR on failure.
|
||||
*/
|
||||
struct drm_colorop_state *
|
||||
drm_atomic_helper_colorop_create_state(struct drm_colorop *colorop)
|
||||
{
|
||||
struct drm_colorop_state *state;
|
||||
|
||||
state = kzalloc_obj(*state);
|
||||
if (!state)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
__drm_colorop_state_init(state, colorop);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* __drm_colorop_reset - reset state on colorop
|
||||
* @colorop: drm colorop
|
||||
* @colorop_state: colorop state to assign
|
||||
*
|
||||
* Initializes the newly allocated @colorop_state and assigns it to
|
||||
* the &drm_crtc->state pointer of @colorop, usually required when
|
||||
* initializing the drivers or when called from the &drm_colorop_funcs.reset
|
||||
* hook.
|
||||
* Initializes the newly allocated @colorop_state and assigns it to the
|
||||
* &drm_colorop->state pointer of @colorop, usually required when
|
||||
* initializing the drivers or when called from the
|
||||
* &drm_colorop_funcs.reset hook.
|
||||
*
|
||||
* This is useful for drivers that subclass the colorop state.
|
||||
*/
|
||||
|
|
@ -551,7 +574,7 @@ static void __drm_colorop_reset(struct drm_colorop *colorop,
|
|||
struct drm_colorop_state *colorop_state)
|
||||
{
|
||||
if (colorop_state)
|
||||
__drm_colorop_state_reset(colorop_state, colorop);
|
||||
__drm_colorop_state_init(colorop_state, colorop);
|
||||
|
||||
colorop->state = colorop_state;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -618,8 +618,17 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
|
|||
* drm_connector_attach_max_bpc_property() requires the
|
||||
* connector to have a state.
|
||||
*/
|
||||
if (connector->funcs->reset)
|
||||
if (connector->funcs->atomic_create_state) {
|
||||
struct drm_connector_state *state;
|
||||
|
||||
state = connector->funcs->atomic_create_state(connector);
|
||||
if (IS_ERR(state))
|
||||
return PTR_ERR(state);
|
||||
|
||||
connector->state = state;
|
||||
} else if (connector->funcs->reset) {
|
||||
connector->funcs->reset(connector);
|
||||
}
|
||||
|
||||
drm_connector_attach_max_bpc_property(connector, 8, max_bpc);
|
||||
connector->max_bpc = max_bpc;
|
||||
|
|
@ -627,6 +636,10 @@ int drmm_connector_hdmi_init(struct drm_device *dev,
|
|||
if (max_bpc > 8)
|
||||
drm_connector_attach_hdr_output_metadata_property(connector);
|
||||
|
||||
ret = drm_connector_attach_color_format_property(connector, supported_formats);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
connector->hdmi.funcs = hdmi_funcs;
|
||||
|
||||
return 0;
|
||||
|
|
@ -1388,6 +1401,18 @@ static const u32 hdmi_colorspaces =
|
|||
BIT(DRM_MODE_COLORIMETRY_DCI_P3_RGB_D65) |
|
||||
BIT(DRM_MODE_COLORIMETRY_DCI_P3_RGB_THEATER);
|
||||
|
||||
static const u32 hdmi_colorformats =
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) |
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) |
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) |
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420);
|
||||
|
||||
static const u32 dp_colorformats =
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) |
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) |
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422) |
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR420);
|
||||
|
||||
/*
|
||||
* As per DP 1.4a spec, 2.2.5.7.5 VSC SDP Payload for Pixel Encoding/Colorimetry
|
||||
* Format Table 2-120
|
||||
|
|
@ -2557,7 +2582,8 @@ EXPORT_SYMBOL(drm_mode_create_aspect_ratio_property);
|
|||
* conversion matrix and convert to the appropriate quantization
|
||||
* range.
|
||||
* The variants BT2020_RGB and BT2020_YCC are equivalent and the
|
||||
* driver chooses between RGB and YCbCr on its own.
|
||||
* driver chooses between RGB and YCbCr based on the color format
|
||||
* property.
|
||||
*
|
||||
* SMPTE_170M_YCC:
|
||||
* BT709_YCC:
|
||||
|
|
@ -2935,6 +2961,149 @@ int drm_connector_attach_colorspace_property(struct drm_connector *connector)
|
|||
}
|
||||
EXPORT_SYMBOL(drm_connector_attach_colorspace_property);
|
||||
|
||||
/**
|
||||
* DOC: Color format
|
||||
*
|
||||
* The connector "color format" property allows userspace to request a specific
|
||||
* color model on the output of the connector. Not all values listed by the
|
||||
* property are guaranteed to work for every sink; rather, it is an optimistic
|
||||
* listing of color formats that the source could output depending on
|
||||
* circumstances.
|
||||
*
|
||||
* Whether it actually can output a certain color format is determined during
|
||||
* the atomic check phase. Consequently, a userspace application that sets the
|
||||
* color format to a value other than "AUTO" should check whether its atomic
|
||||
* commit succeeded.
|
||||
*
|
||||
* Possible values for "color format":
|
||||
*
|
||||
* "AUTO":
|
||||
* The driver or display protocol helpers should pick a suitable color
|
||||
* format. All implementations of a specific display protocol will behave
|
||||
* the same way with "AUTO", but different display protocols do not
|
||||
* necessarily have the same "AUTO" semantics.
|
||||
*
|
||||
* For HDMI connectors, "AUTO" picks RGB, but falls back to YUV 4:2:0 if
|
||||
* the bandwidth required for full-scale RGB is not available, or the mode
|
||||
* is YUV 4:2:0-only, as long as the mode, source, and sink all support
|
||||
* YUV 4:2:0.
|
||||
* "RGB":
|
||||
* RGB output format. The quantization range (limited/full) depends on the
|
||||
* value of the "Broadcast RGB" property if it is present on the connector.
|
||||
* "YUV 4:4:4":
|
||||
* YUV 4:4:4 (a.k.a. YCbCr 4:4:4) output format. Chroma is not subsampled.
|
||||
* The quantization range defaults to limited.
|
||||
* "YUV 4:2:2":
|
||||
* YUV 4:2:2 (a.k.a. YCbCr 4:2:2) output format. Chroma has half the
|
||||
* horizontal resolution of Luma. The quantization range defaults to
|
||||
* limited.
|
||||
* "YUV 4:2:0":
|
||||
* YUV 4:2:0 (a.k.a. YCbCr 4:2:0) output format. Chroma has half the
|
||||
* horizontal and vertical resolution of Luma. The quantization range
|
||||
* defaults to limited.
|
||||
*
|
||||
* A sink may only support some color formats in specific modes and at specific
|
||||
* bit depths. The atomic modesetting API should be used to set a working
|
||||
* configuration in one go, as an unsupported combination of parameters is
|
||||
* rejected.
|
||||
*/
|
||||
|
||||
/**
|
||||
* drm_connector_attach_color_format_property - create and attach color format property
|
||||
* @connector: connector to create the color format property on
|
||||
* @supported_color_formats: bitmask of bit-shifted &enum drm_output_color_format
|
||||
* values the connector supports
|
||||
*
|
||||
* Called by a driver to create a color format property. The property is
|
||||
* attached to the connector automatically on success.
|
||||
*
|
||||
* @supported_color_formats should only include color formats the connector
|
||||
* type can actually support.
|
||||
*
|
||||
* Returns:
|
||||
* 0 on success, negative errno on error
|
||||
*/
|
||||
int drm_connector_attach_color_format_property(struct drm_connector *connector,
|
||||
unsigned long supported_color_formats)
|
||||
{
|
||||
struct drm_device *dev = connector->dev;
|
||||
struct drm_prop_enum_list enum_list[DRM_CONNECTOR_COLOR_FORMAT_COUNT];
|
||||
unsigned int i = 0;
|
||||
unsigned long fmt;
|
||||
|
||||
if (connector->color_format_property)
|
||||
return 0;
|
||||
|
||||
if (!supported_color_formats) {
|
||||
drm_err(dev, "No supported color formats provided on [CONNECTOR:%d:%s]\n",
|
||||
connector->base.id, connector->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (supported_color_formats & ~GENMASK(DRM_OUTPUT_COLOR_FORMAT_COUNT - 1, 0)) {
|
||||
drm_err(dev, "Unknown color formats provided on [CONNECTOR:%d:%s]\n",
|
||||
connector->base.id, connector->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
switch (connector->connector_type) {
|
||||
case DRM_MODE_CONNECTOR_HDMIA:
|
||||
case DRM_MODE_CONNECTOR_HDMIB:
|
||||
if (supported_color_formats & ~hdmi_colorformats) {
|
||||
drm_err(dev, "Color formats not allowed for HDMI on [CONNECTOR:%d:%s]\n",
|
||||
connector->base.id, connector->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case DRM_MODE_CONNECTOR_DisplayPort:
|
||||
case DRM_MODE_CONNECTOR_eDP:
|
||||
if (supported_color_formats & ~dp_colorformats) {
|
||||
drm_err(dev, "Color formats not allowed for DP on [CONNECTOR:%d:%s]\n",
|
||||
connector->base.id, connector->name);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
enum_list[0].name = "AUTO";
|
||||
enum_list[0].type = DRM_CONNECTOR_COLOR_FORMAT_AUTO;
|
||||
|
||||
for_each_set_bit(fmt, &supported_color_formats, DRM_OUTPUT_COLOR_FORMAT_COUNT) {
|
||||
switch (fmt) {
|
||||
case DRM_OUTPUT_COLOR_FORMAT_RGB444:
|
||||
enum_list[++i].type = DRM_CONNECTOR_COLOR_FORMAT_RGB444;
|
||||
break;
|
||||
case DRM_OUTPUT_COLOR_FORMAT_YCBCR444:
|
||||
enum_list[++i].type = DRM_CONNECTOR_COLOR_FORMAT_YCBCR444;
|
||||
break;
|
||||
case DRM_OUTPUT_COLOR_FORMAT_YCBCR422:
|
||||
enum_list[++i].type = DRM_CONNECTOR_COLOR_FORMAT_YCBCR422;
|
||||
break;
|
||||
case DRM_OUTPUT_COLOR_FORMAT_YCBCR420:
|
||||
enum_list[++i].type = DRM_CONNECTOR_COLOR_FORMAT_YCBCR420;
|
||||
break;
|
||||
default:
|
||||
drm_warn(dev, "Unknown supported format %ld on [CONNECTOR:%d:%s]\n",
|
||||
fmt, connector->base.id, connector->name);
|
||||
continue;
|
||||
}
|
||||
enum_list[i].name = drm_hdmi_connector_get_output_format_name(fmt);
|
||||
}
|
||||
|
||||
connector->color_format_property =
|
||||
drm_property_create_enum(dev, DRM_MODE_PROP_ENUM, "color format",
|
||||
enum_list, i + 1);
|
||||
|
||||
if (!connector->color_format_property)
|
||||
return -ENOMEM;
|
||||
|
||||
drm_object_attach_property(&connector->base, connector->color_format_property,
|
||||
DRM_CONNECTOR_COLOR_FORMAT_AUTO);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_connector_attach_color_format_property);
|
||||
|
||||
/**
|
||||
* drm_connector_atomic_hdr_metadata_equal - checks if the hdr metadata changed
|
||||
* @old_state: old connector state to compare
|
||||
|
|
@ -3530,6 +3699,22 @@ void drm_connector_oob_hotplug_event(struct fwnode_handle *connector_fwnode,
|
|||
}
|
||||
EXPORT_SYMBOL(drm_connector_oob_hotplug_event);
|
||||
|
||||
/**
|
||||
* drm_connector_get_color_format - Return connector color format of @conn_state
|
||||
* @conn_state: pointer to the &struct drm_connector_state to go check
|
||||
*
|
||||
*/
|
||||
enum drm_connector_color_format
|
||||
drm_connector_get_color_format(const struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct drm_connector *connector = conn_state->connector;
|
||||
|
||||
if (connector->funcs->color_format)
|
||||
return connector->funcs->color_format(conn_state);
|
||||
|
||||
return conn_state->color_format;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_connector_get_color_format);
|
||||
|
||||
/**
|
||||
* DOC: Tile group
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
#ifndef __DRM_DRAW_INTERNAL_H__
|
||||
#define __DRM_DRAW_INTERNAL_H__
|
||||
|
||||
#include <linux/font.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
struct iosys_map;
|
||||
|
|
@ -18,12 +17,6 @@ static inline bool drm_draw_is_pixel_fg(const u8 *sbuf8, unsigned int spitch, in
|
|||
return (sbuf8[(y * spitch) + x / 8] & (0x80 >> (x % 8))) != 0;
|
||||
}
|
||||
|
||||
static inline const u8 *drm_draw_get_char_bitmap(const struct font_desc *font,
|
||||
char c, size_t font_pitch)
|
||||
{
|
||||
return font->data + (c * font->height) * font_pitch;
|
||||
}
|
||||
|
||||
bool drm_draw_can_convert_from_xrgb8888(u32 format);
|
||||
|
||||
u32 drm_draw_color_from_xrgb8888(u32 color, u32 format);
|
||||
|
|
|
|||
|
|
@ -342,7 +342,9 @@ void drm_minor_release(struct drm_minor *minor)
|
|||
*
|
||||
* platform_set_drvdata(pdev, drm);
|
||||
*
|
||||
* drm_mode_config_reset(drm);
|
||||
* ret = drm_mode_config_create_initial_state(drm);
|
||||
* if (ret)
|
||||
* return ret;
|
||||
*
|
||||
* ret = drm_dev_register(drm);
|
||||
* if (ret)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@
|
|||
#include <drm/drm_print.h>
|
||||
|
||||
#include "drm_crtc_internal.h"
|
||||
#include "drm_internal.h"
|
||||
|
||||
/**
|
||||
* DOC: overview
|
||||
|
|
|
|||
|
|
@ -232,8 +232,7 @@ void drm_gem_private_object_init(struct drm_device *dev,
|
|||
if (!obj->resv)
|
||||
obj->resv = &obj->_resv;
|
||||
|
||||
if (drm_core_check_feature(dev, DRIVER_GEM_GPUVA))
|
||||
drm_gem_gpuva_init(obj);
|
||||
drm_gem_gpuva_init(obj);
|
||||
|
||||
drm_vma_node_reset(&obj->vma_node);
|
||||
INIT_LIST_HEAD(&obj->lru_node);
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include <linux/export.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_drv.h>
|
||||
#include <drm/drm_encoder.h>
|
||||
#include <drm/drm_file.h>
|
||||
|
|
@ -182,6 +183,87 @@ int drm_mode_getresources(struct drm_device *dev, void *data,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int drm_mode_config_plane_create_state(struct drm_plane *plane)
|
||||
{
|
||||
struct drm_plane_state *plane_state;
|
||||
|
||||
if (!plane->funcs->atomic_create_state)
|
||||
return 0;
|
||||
|
||||
plane_state = plane->funcs->atomic_create_state(plane);
|
||||
if (IS_ERR(plane_state))
|
||||
return PTR_ERR(plane_state);
|
||||
|
||||
plane->state = plane_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int drm_mode_config_plane_reset_with_create_state(struct drm_plane *plane)
|
||||
{
|
||||
if (plane->state) {
|
||||
plane->funcs->atomic_destroy_state(plane, plane->state);
|
||||
plane->state = NULL;
|
||||
}
|
||||
|
||||
return drm_mode_config_plane_create_state(plane);
|
||||
}
|
||||
|
||||
static int drm_mode_config_crtc_create_state(struct drm_crtc *crtc)
|
||||
{
|
||||
struct drm_crtc_state *crtc_state;
|
||||
|
||||
if (!crtc->funcs->atomic_create_state)
|
||||
return 0;
|
||||
|
||||
crtc_state = crtc->funcs->atomic_create_state(crtc);
|
||||
if (IS_ERR(crtc_state))
|
||||
return PTR_ERR(crtc_state);
|
||||
|
||||
if (drm_dev_has_vblank(crtc->dev))
|
||||
drm_crtc_vblank_reset(crtc);
|
||||
|
||||
crtc->state = crtc_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int drm_mode_config_crtc_reset_with_create_state(struct drm_crtc *crtc)
|
||||
{
|
||||
if (crtc->state) {
|
||||
crtc->funcs->atomic_destroy_state(crtc, crtc->state);
|
||||
crtc->state = NULL;
|
||||
}
|
||||
|
||||
return drm_mode_config_crtc_create_state(crtc);
|
||||
}
|
||||
|
||||
static int drm_mode_config_connector_create_state(struct drm_connector *connector)
|
||||
{
|
||||
struct drm_connector_state *conn_state;
|
||||
|
||||
if (!connector->funcs->atomic_create_state)
|
||||
return 0;
|
||||
|
||||
conn_state = connector->funcs->atomic_create_state(connector);
|
||||
if (IS_ERR(conn_state))
|
||||
return PTR_ERR(conn_state);
|
||||
|
||||
connector->state = conn_state;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int drm_mode_config_connector_reset_with_create_state(struct drm_connector *connector)
|
||||
{
|
||||
if (connector->state) {
|
||||
connector->funcs->atomic_destroy_state(connector, connector->state);
|
||||
connector->state = NULL;
|
||||
}
|
||||
|
||||
return drm_mode_config_connector_create_state(connector);
|
||||
}
|
||||
|
||||
/**
|
||||
* drm_mode_config_reset - call ->reset callbacks
|
||||
* @dev: drm device
|
||||
|
|
@ -189,6 +271,10 @@ int drm_mode_getresources(struct drm_device *dev, void *data,
|
|||
* This functions calls all the crtc's, encoder's and connector's ->reset
|
||||
* callback. Drivers can use this in e.g. their driver load or resume code to
|
||||
* reset hardware and software state.
|
||||
*
|
||||
* Note that &drm_private_obj structures are expected to be stable across
|
||||
* suspend/resume cycles, and drm_mode_config_reset() does not affect these
|
||||
* structures.
|
||||
*/
|
||||
void drm_mode_config_reset(struct drm_device *dev)
|
||||
{
|
||||
|
|
@ -202,26 +288,123 @@ void drm_mode_config_reset(struct drm_device *dev)
|
|||
drm_for_each_colorop(colorop, dev)
|
||||
drm_colorop_reset(colorop);
|
||||
|
||||
drm_for_each_plane(plane, dev)
|
||||
drm_for_each_plane(plane, dev) {
|
||||
if (plane->funcs->reset)
|
||||
plane->funcs->reset(plane);
|
||||
else if (plane->funcs->atomic_create_state)
|
||||
drm_mode_config_plane_reset_with_create_state(plane);
|
||||
}
|
||||
|
||||
drm_for_each_crtc(crtc, dev)
|
||||
drm_for_each_crtc(crtc, dev) {
|
||||
if (crtc->funcs->reset)
|
||||
crtc->funcs->reset(crtc);
|
||||
else if (crtc->funcs->atomic_create_state)
|
||||
drm_mode_config_crtc_reset_with_create_state(crtc);
|
||||
}
|
||||
|
||||
drm_for_each_encoder(encoder, dev)
|
||||
if (encoder->funcs && encoder->funcs->reset)
|
||||
encoder->funcs->reset(encoder);
|
||||
|
||||
drm_connector_list_iter_begin(dev, &conn_iter);
|
||||
drm_for_each_connector_iter(connector, &conn_iter)
|
||||
drm_for_each_connector_iter(connector, &conn_iter) {
|
||||
if (connector->funcs->reset)
|
||||
connector->funcs->reset(connector);
|
||||
else if (connector->funcs->atomic_create_state)
|
||||
drm_mode_config_connector_reset_with_create_state(connector);
|
||||
}
|
||||
drm_connector_list_iter_end(&conn_iter);
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mode_config_reset);
|
||||
|
||||
/**
|
||||
* drm_mode_config_create_initial_state - Allocates the initial state
|
||||
* @dev: drm device
|
||||
*
|
||||
* This functions creates the initial state for all the objects. Drivers
|
||||
* can use this in e.g. probe to initialize their software state.
|
||||
*
|
||||
* It has two main differences with drm_mode_config_reset(): the reset()
|
||||
* hooks aren't called and thus the hardware will be left untouched, but
|
||||
* also the &drm_private_obj structures will be initialized as opposed
|
||||
* to drm_mode_config_reset() that skips them.
|
||||
*
|
||||
* Returns: 0 on success, negative error value on failure.
|
||||
*/
|
||||
int drm_mode_config_create_initial_state(struct drm_device *dev)
|
||||
{
|
||||
struct drm_crtc *crtc;
|
||||
struct drm_colorop *colorop;
|
||||
struct drm_plane *plane;
|
||||
struct drm_connector *connector;
|
||||
struct drm_connector_list_iter conn_iter;
|
||||
struct drm_private_obj *privobj;
|
||||
int ret;
|
||||
|
||||
drm_for_each_privobj(privobj, dev) {
|
||||
struct drm_private_state *privobj_state;
|
||||
|
||||
if (privobj->state)
|
||||
continue;
|
||||
|
||||
if (!privobj->funcs->atomic_create_state)
|
||||
continue;
|
||||
|
||||
privobj_state = privobj->funcs->atomic_create_state(privobj);
|
||||
if (IS_ERR(privobj_state))
|
||||
return PTR_ERR(privobj_state);
|
||||
|
||||
privobj->state = privobj_state;
|
||||
}
|
||||
|
||||
drm_for_each_colorop(colorop, dev) {
|
||||
struct drm_colorop_state *colorop_state;
|
||||
|
||||
if (colorop->state)
|
||||
continue;
|
||||
|
||||
colorop_state = drm_atomic_helper_colorop_create_state(colorop);
|
||||
if (IS_ERR(colorop_state))
|
||||
return PTR_ERR(colorop_state);
|
||||
|
||||
colorop->state = colorop_state;
|
||||
}
|
||||
|
||||
drm_for_each_plane(plane, dev) {
|
||||
if (plane->state)
|
||||
continue;
|
||||
|
||||
ret = drm_mode_config_plane_create_state(plane);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
drm_for_each_crtc(crtc, dev) {
|
||||
if (crtc->state)
|
||||
continue;
|
||||
|
||||
ret = drm_mode_config_crtc_create_state(crtc);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
drm_connector_list_iter_begin(dev, &conn_iter);
|
||||
drm_for_each_connector_iter(connector, &conn_iter) {
|
||||
if (connector->state)
|
||||
continue;
|
||||
|
||||
ret = drm_mode_config_connector_create_state(connector);
|
||||
if (ret) {
|
||||
drm_connector_list_iter_end(&conn_iter);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
drm_connector_list_iter_end(&conn_iter);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL(drm_mode_config_create_initial_state);
|
||||
|
||||
/*
|
||||
* Global properties
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -443,9 +443,11 @@ static void draw_txt_rectangle(struct drm_scanout_buffer *sb,
|
|||
rec.x1 += (drm_rect_width(clip) - (line_len * font->width)) / 2;
|
||||
|
||||
for (j = 0; j < line_len; j++) {
|
||||
src = drm_draw_get_char_bitmap(font, msg[i].txt[j], font_pitch);
|
||||
src = font_data_glyph_buf(font->data, font->width, font->height,
|
||||
(unsigned char)msg[i].txt[j]);
|
||||
rec.x2 = rec.x1 + font->width;
|
||||
drm_panic_blit(sb, &rec, src, font_pitch, 1, color);
|
||||
if (src)
|
||||
drm_panic_blit(sb, &rec, src, font_pitch, 1, color);
|
||||
rec.x1 += font->width;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,8 +50,10 @@ MODULE_PARM_DESC(debug, "Enable debug output, where each bit enables a debug cat
|
|||
"\t\tBit 3 (0x08) will enable PRIME messages (prime code)\n"
|
||||
"\t\tBit 4 (0x10) will enable ATOMIC messages (atomic code)\n"
|
||||
"\t\tBit 5 (0x20) will enable VBL messages (vblank code)\n"
|
||||
"\t\tBit 6 (0x40) will enable STATE messages (atomic state code)\n"
|
||||
"\t\tBit 7 (0x80) will enable LEASE messages (leasing code)\n"
|
||||
"\t\tBit 8 (0x100) will enable DP messages (displayport code)");
|
||||
"\t\tBit 8 (0x100) will enable DP messages (displayport code)\n"
|
||||
"\t\tBit 9 (0x200) will enable DRMRES messages (managed resources code)");
|
||||
|
||||
#if !defined(CONFIG_DRM_USE_DYNAMIC_DEBUG)
|
||||
module_param_named(debug, __drm_debug, ulong, 0600);
|
||||
|
|
|
|||
|
|
@ -58,6 +58,12 @@
|
|||
* framebuffer to be written by the writeback connector. This property is
|
||||
* similar to the FB_ID property on planes, but will always read as zero
|
||||
* and is not preserved across commits.
|
||||
* If the width and height of the framebuffer do not match those of the
|
||||
* attached CRTC, the driver must either fail or scale (not crop) the
|
||||
* content to exactly fit the framebuffer.
|
||||
* If the driver is unable to exactly fill the framebuffer for any reason,
|
||||
* such as hardware scaler constraints or an odd width for a sub-sampled
|
||||
* format, the writeback must fail instead of partially filling the buffer.
|
||||
* Userspace must set this property to an output buffer every time it
|
||||
* wishes the buffer to get filled.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -55,36 +55,36 @@ static int psb_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent);
|
|||
*/
|
||||
static const struct pci_device_id pciidlist[] = {
|
||||
/* Poulsbo */
|
||||
{ 0x8086, 0x8108, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &psb_chip_ops },
|
||||
{ 0x8086, 0x8109, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &psb_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x8108), .driver_data = (long)&psb_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x8109), .driver_data = (long)&psb_chip_ops },
|
||||
/* Oak Trail */
|
||||
{ 0x8086, 0x4100, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops },
|
||||
{ 0x8086, 0x4101, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops },
|
||||
{ 0x8086, 0x4102, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops },
|
||||
{ 0x8086, 0x4103, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops },
|
||||
{ 0x8086, 0x4104, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops },
|
||||
{ 0x8086, 0x4105, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops },
|
||||
{ 0x8086, 0x4106, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops },
|
||||
{ 0x8086, 0x4107, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops },
|
||||
{ 0x8086, 0x4108, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &oaktrail_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x4100), .driver_data = (long)&oaktrail_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x4101), .driver_data = (long)&oaktrail_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x4102), .driver_data = (long)&oaktrail_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x4103), .driver_data = (long)&oaktrail_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x4104), .driver_data = (long)&oaktrail_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x4105), .driver_data = (long)&oaktrail_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x4106), .driver_data = (long)&oaktrail_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x4107), .driver_data = (long)&oaktrail_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x4108), .driver_data = (long)&oaktrail_chip_ops },
|
||||
/* Cedar Trail */
|
||||
{ 0x8086, 0x0be0, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0be1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0be2, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0be3, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0be4, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0be5, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0be6, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0be7, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0be8, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0be9, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0bea, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0beb, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0bec, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0bed, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0bee, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0x8086, 0x0bef, PCI_ANY_ID, PCI_ANY_ID, 0, 0, (long) &cdv_chip_ops },
|
||||
{ 0, }
|
||||
{ PCI_DEVICE(0x8086, 0x0be0), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0be1), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0be2), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0be3), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0be4), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0be5), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0be6), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0be7), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0be8), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0be9), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0bea), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0beb), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0bec), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0bed), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0bee), .driver_data = (long)&cdv_chip_ops },
|
||||
{ PCI_DEVICE(0x8086, 0x0bef), .driver_data = (long)&cdv_chip_ops },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, pciidlist);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
#define VMBUS_MAX_PACKET_SIZE 0x4000
|
||||
|
||||
struct hyperv_drm_device {
|
||||
struct hv_drm_device {
|
||||
/* drm */
|
||||
struct drm_device dev;
|
||||
struct drm_plane plane;
|
||||
|
|
@ -39,17 +39,17 @@ struct hyperv_drm_device {
|
|||
struct hv_device *hdev;
|
||||
};
|
||||
|
||||
#define to_hv(_dev) container_of(_dev, struct hyperv_drm_device, dev)
|
||||
#define to_hv_drm(_dev) container_of(_dev, struct hv_drm_device, dev)
|
||||
|
||||
/* hyperv_drm_modeset */
|
||||
int hyperv_mode_config_init(struct hyperv_drm_device *hv);
|
||||
int hv_drm_mode_config_init(struct hv_drm_device *hv);
|
||||
|
||||
/* hyperv_drm_proto */
|
||||
int hyperv_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp);
|
||||
int hyperv_update_situation(struct hv_device *hdev, u8 active, u32 bpp,
|
||||
int hv_drm_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp);
|
||||
int hv_drm_update_situation(struct hv_device *hdev, u8 active, u32 bpp,
|
||||
u32 w, u32 h, u32 pitch);
|
||||
int hyperv_hide_hw_ptr(struct hv_device *hdev);
|
||||
int hyperv_update_dirt(struct hv_device *hdev, struct drm_rect *rect);
|
||||
int hyperv_connect_vsp(struct hv_device *hdev);
|
||||
int hv_drm_hide_hw_ptr(struct hv_device *hdev);
|
||||
int hv_drm_update_dirt(struct hv_device *hdev, struct drm_rect *rect);
|
||||
int hv_drm_connect_vsp(struct hv_device *hdev);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -24,9 +24,9 @@
|
|||
#define DRIVER_MAJOR 1
|
||||
#define DRIVER_MINOR 0
|
||||
|
||||
DEFINE_DRM_GEM_FOPS(hv_fops);
|
||||
DEFINE_DRM_GEM_FOPS(hv_drm_fops);
|
||||
|
||||
static struct drm_driver hyperv_driver = {
|
||||
static struct drm_driver hv_drm_driver = {
|
||||
.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
|
||||
|
||||
.name = DRIVER_NAME,
|
||||
|
|
@ -34,22 +34,22 @@ static struct drm_driver hyperv_driver = {
|
|||
.major = DRIVER_MAJOR,
|
||||
.minor = DRIVER_MINOR,
|
||||
|
||||
.fops = &hv_fops,
|
||||
.fops = &hv_drm_fops,
|
||||
DRM_GEM_SHMEM_DRIVER_OPS,
|
||||
DRM_FBDEV_SHMEM_DRIVER_OPS,
|
||||
};
|
||||
|
||||
static int hyperv_pci_probe(struct pci_dev *pdev,
|
||||
static int hv_drm_pci_probe(struct pci_dev *pdev,
|
||||
const struct pci_device_id *ent)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void hyperv_pci_remove(struct pci_dev *pdev)
|
||||
static void hv_drm_pci_remove(struct pci_dev *pdev)
|
||||
{
|
||||
}
|
||||
|
||||
static const struct pci_device_id hyperv_pci_tbl[] = {
|
||||
static const struct pci_device_id hv_drm_pci_tbl[] = {
|
||||
{
|
||||
.vendor = PCI_VENDOR_ID_MICROSOFT,
|
||||
.device = PCI_DEVICE_ID_HYPERV_VIDEO,
|
||||
|
|
@ -60,14 +60,14 @@ static const struct pci_device_id hyperv_pci_tbl[] = {
|
|||
/*
|
||||
* PCI stub to support gen1 VM.
|
||||
*/
|
||||
static struct pci_driver hyperv_pci_driver = {
|
||||
static struct pci_driver hv_drm_pci_driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.id_table = hyperv_pci_tbl,
|
||||
.probe = hyperv_pci_probe,
|
||||
.remove = hyperv_pci_remove,
|
||||
.id_table = hv_drm_pci_tbl,
|
||||
.probe = hv_drm_pci_probe,
|
||||
.remove = hv_drm_pci_remove,
|
||||
};
|
||||
|
||||
static int hyperv_setup_vram(struct hyperv_drm_device *hv,
|
||||
static int hv_drm_setup_vram(struct hv_drm_device *hv,
|
||||
struct hv_device *hdev)
|
||||
{
|
||||
struct drm_device *dev = &hv->dev;
|
||||
|
|
@ -102,15 +102,15 @@ static int hyperv_setup_vram(struct hyperv_drm_device *hv,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int hyperv_vmbus_probe(struct hv_device *hdev,
|
||||
static int hv_drm_vmbus_probe(struct hv_device *hdev,
|
||||
const struct hv_vmbus_device_id *dev_id)
|
||||
{
|
||||
struct hyperv_drm_device *hv;
|
||||
struct hv_drm_device *hv;
|
||||
struct drm_device *dev;
|
||||
int ret;
|
||||
|
||||
hv = devm_drm_dev_alloc(&hdev->device, &hyperv_driver,
|
||||
struct hyperv_drm_device, dev);
|
||||
hv = devm_drm_dev_alloc(&hdev->device, &hv_drm_driver,
|
||||
struct hv_drm_device, dev);
|
||||
if (IS_ERR(hv))
|
||||
return PTR_ERR(hv);
|
||||
|
||||
|
|
@ -119,15 +119,15 @@ static int hyperv_vmbus_probe(struct hv_device *hdev,
|
|||
hv_set_drvdata(hdev, hv);
|
||||
hv->hdev = hdev;
|
||||
|
||||
ret = hyperv_connect_vsp(hdev);
|
||||
ret = hv_drm_connect_vsp(hdev);
|
||||
if (ret) {
|
||||
drm_err(dev, "Failed to connect to vmbus.\n");
|
||||
goto err_hv_set_drv_data;
|
||||
}
|
||||
|
||||
aperture_remove_all_conflicting_devices(hyperv_driver.name);
|
||||
aperture_remove_all_conflicting_devices(hv_drm_driver.name);
|
||||
|
||||
ret = hyperv_setup_vram(hv, hdev);
|
||||
ret = hv_drm_setup_vram(hv, hdev);
|
||||
if (ret)
|
||||
goto err_vmbus_close;
|
||||
|
||||
|
|
@ -136,11 +136,11 @@ static int hyperv_vmbus_probe(struct hv_device *hdev,
|
|||
* vram location is not fatal. Device will update dirty area till
|
||||
* preferred resolution only.
|
||||
*/
|
||||
ret = hyperv_update_vram_location(hdev, hv->fb_base);
|
||||
ret = hv_drm_update_vram_location(hdev, hv->fb_base);
|
||||
if (ret)
|
||||
drm_warn(dev, "Failed to update vram location.\n");
|
||||
|
||||
ret = hyperv_mode_config_init(hv);
|
||||
ret = hv_drm_mode_config_init(hv);
|
||||
if (ret)
|
||||
goto err_free_mmio;
|
||||
|
||||
|
|
@ -168,10 +168,10 @@ static int hyperv_vmbus_probe(struct hv_device *hdev,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static void hyperv_vmbus_remove(struct hv_device *hdev)
|
||||
static void hv_drm_vmbus_remove(struct hv_device *hdev)
|
||||
{
|
||||
struct drm_device *dev = hv_get_drvdata(hdev);
|
||||
struct hyperv_drm_device *hv = to_hv(dev);
|
||||
struct hv_drm_device *hv = to_hv_drm(dev);
|
||||
|
||||
vmbus_set_skip_unload(false);
|
||||
drm_dev_unplug(dev);
|
||||
|
|
@ -183,12 +183,12 @@ static void hyperv_vmbus_remove(struct hv_device *hdev)
|
|||
vmbus_free_mmio(hv->mem->start, hv->fb_size);
|
||||
}
|
||||
|
||||
static void hyperv_vmbus_shutdown(struct hv_device *hdev)
|
||||
static void hv_drm_vmbus_shutdown(struct hv_device *hdev)
|
||||
{
|
||||
drm_atomic_helper_shutdown(hv_get_drvdata(hdev));
|
||||
}
|
||||
|
||||
static int hyperv_vmbus_suspend(struct hv_device *hdev)
|
||||
static int hv_drm_vmbus_suspend(struct hv_device *hdev)
|
||||
{
|
||||
struct drm_device *dev = hv_get_drvdata(hdev);
|
||||
int ret;
|
||||
|
|
@ -202,67 +202,67 @@ static int hyperv_vmbus_suspend(struct hv_device *hdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int hyperv_vmbus_resume(struct hv_device *hdev)
|
||||
static int hv_drm_vmbus_resume(struct hv_device *hdev)
|
||||
{
|
||||
struct drm_device *dev = hv_get_drvdata(hdev);
|
||||
struct hyperv_drm_device *hv = to_hv(dev);
|
||||
struct hv_drm_device *hv = to_hv_drm(dev);
|
||||
int ret;
|
||||
|
||||
ret = hyperv_connect_vsp(hdev);
|
||||
ret = hv_drm_connect_vsp(hdev);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = hyperv_update_vram_location(hdev, hv->fb_base);
|
||||
ret = hv_drm_update_vram_location(hdev, hv->fb_base);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return drm_mode_config_helper_resume(dev);
|
||||
}
|
||||
|
||||
static const struct hv_vmbus_device_id hyperv_vmbus_tbl[] = {
|
||||
static const struct hv_vmbus_device_id hv_drm_vmbus_tbl[] = {
|
||||
/* Synthetic Video Device GUID */
|
||||
{HV_SYNTHVID_GUID},
|
||||
{}
|
||||
};
|
||||
|
||||
static struct hv_driver hyperv_hv_driver = {
|
||||
static struct hv_driver hv_drm_hv_driver = {
|
||||
.name = KBUILD_MODNAME,
|
||||
.id_table = hyperv_vmbus_tbl,
|
||||
.probe = hyperv_vmbus_probe,
|
||||
.remove = hyperv_vmbus_remove,
|
||||
.shutdown = hyperv_vmbus_shutdown,
|
||||
.suspend = hyperv_vmbus_suspend,
|
||||
.resume = hyperv_vmbus_resume,
|
||||
.id_table = hv_drm_vmbus_tbl,
|
||||
.probe = hv_drm_vmbus_probe,
|
||||
.remove = hv_drm_vmbus_remove,
|
||||
.shutdown = hv_drm_vmbus_shutdown,
|
||||
.suspend = hv_drm_vmbus_suspend,
|
||||
.resume = hv_drm_vmbus_resume,
|
||||
.driver = {
|
||||
.probe_type = PROBE_PREFER_ASYNCHRONOUS,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init hyperv_init(void)
|
||||
static int __init hv_drm_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (drm_firmware_drivers_only())
|
||||
return -ENODEV;
|
||||
|
||||
ret = pci_register_driver(&hyperv_pci_driver);
|
||||
ret = pci_register_driver(&hv_drm_pci_driver);
|
||||
if (ret != 0)
|
||||
return ret;
|
||||
|
||||
return vmbus_driver_register(&hyperv_hv_driver);
|
||||
return vmbus_driver_register(&hv_drm_hv_driver);
|
||||
}
|
||||
|
||||
static void __exit hyperv_exit(void)
|
||||
static void __exit hv_drm_exit(void)
|
||||
{
|
||||
vmbus_driver_unregister(&hyperv_hv_driver);
|
||||
pci_unregister_driver(&hyperv_pci_driver);
|
||||
vmbus_driver_unregister(&hv_drm_hv_driver);
|
||||
pci_unregister_driver(&hv_drm_pci_driver);
|
||||
}
|
||||
|
||||
module_init(hyperv_init);
|
||||
module_exit(hyperv_exit);
|
||||
module_init(hv_drm_init);
|
||||
module_exit(hv_drm_exit);
|
||||
|
||||
MODULE_DEVICE_TABLE(pci, hyperv_pci_tbl);
|
||||
MODULE_DEVICE_TABLE(vmbus, hyperv_vmbus_tbl);
|
||||
MODULE_DEVICE_TABLE(pci, hv_drm_pci_tbl);
|
||||
MODULE_DEVICE_TABLE(vmbus, hv_drm_vmbus_tbl);
|
||||
MODULE_LICENSE("GPL");
|
||||
MODULE_AUTHOR("Deepak Rawat <drawat.floss@gmail.com>");
|
||||
MODULE_DESCRIPTION("DRM driver for Hyper-V synthetic video device");
|
||||
|
|
|
|||
|
|
@ -25,11 +25,11 @@
|
|||
|
||||
#include "hyperv_drm.h"
|
||||
|
||||
static int hyperv_blit_to_vram_rect(struct drm_framebuffer *fb,
|
||||
static int hv_drm_blit_to_vram_rect(struct drm_framebuffer *fb,
|
||||
const struct iosys_map *vmap,
|
||||
struct drm_rect *rect)
|
||||
{
|
||||
struct hyperv_drm_device *hv = to_hv(fb->dev);
|
||||
struct hv_drm_device *hv = to_hv_drm(fb->dev);
|
||||
struct iosys_map dst = IOSYS_MAP_INIT_VADDR_IOMEM(hv->vram);
|
||||
int idx;
|
||||
|
||||
|
|
@ -44,9 +44,9 @@ static int hyperv_blit_to_vram_rect(struct drm_framebuffer *fb,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int hyperv_connector_get_modes(struct drm_connector *connector)
|
||||
static int hv_drm_connector_get_modes(struct drm_connector *connector)
|
||||
{
|
||||
struct hyperv_drm_device *hv = to_hv(connector->dev);
|
||||
struct hv_drm_device *hv = to_hv_drm(connector->dev);
|
||||
int count;
|
||||
|
||||
count = drm_add_modes_noedid(connector,
|
||||
|
|
@ -58,11 +58,11 @@ static int hyperv_connector_get_modes(struct drm_connector *connector)
|
|||
return count;
|
||||
}
|
||||
|
||||
static const struct drm_connector_helper_funcs hyperv_connector_helper_funcs = {
|
||||
.get_modes = hyperv_connector_get_modes,
|
||||
static const struct drm_connector_helper_funcs hv_drm_connector_helper_funcs = {
|
||||
.get_modes = hv_drm_connector_get_modes,
|
||||
};
|
||||
|
||||
static const struct drm_connector_funcs hyperv_connector_funcs = {
|
||||
static const struct drm_connector_funcs hv_drm_connector_funcs = {
|
||||
.fill_modes = drm_helper_probe_single_connector_modes,
|
||||
.destroy = drm_connector_cleanup,
|
||||
.reset = drm_atomic_helper_connector_reset,
|
||||
|
|
@ -70,15 +70,15 @@ static const struct drm_connector_funcs hyperv_connector_funcs = {
|
|||
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
|
||||
};
|
||||
|
||||
static inline int hyperv_conn_init(struct hyperv_drm_device *hv)
|
||||
static inline int hv_drm_conn_init(struct hv_drm_device *hv)
|
||||
{
|
||||
drm_connector_helper_add(&hv->connector, &hyperv_connector_helper_funcs);
|
||||
drm_connector_helper_add(&hv->connector, &hv_drm_connector_helper_funcs);
|
||||
return drm_connector_init(&hv->dev, &hv->connector,
|
||||
&hyperv_connector_funcs,
|
||||
&hv_drm_connector_funcs,
|
||||
DRM_MODE_CONNECTOR_VIRTUAL);
|
||||
}
|
||||
|
||||
static int hyperv_check_size(struct hyperv_drm_device *hv, int w, int h,
|
||||
static int hv_drm_check_size(struct hv_drm_device *hv, int w, int h,
|
||||
struct drm_framebuffer *fb)
|
||||
{
|
||||
u32 pitch = w * (hv->screen_depth / 8);
|
||||
|
|
@ -92,25 +92,25 @@ static int hyperv_check_size(struct hyperv_drm_device *hv, int w, int h,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const uint32_t hyperv_formats[] = {
|
||||
static const uint32_t hv_drm_formats[] = {
|
||||
DRM_FORMAT_XRGB8888,
|
||||
};
|
||||
|
||||
static const uint64_t hyperv_modifiers[] = {
|
||||
static const uint64_t hv_drm_modifiers[] = {
|
||||
DRM_FORMAT_MOD_LINEAR,
|
||||
DRM_FORMAT_MOD_INVALID
|
||||
};
|
||||
|
||||
static void hyperv_crtc_helper_atomic_enable(struct drm_crtc *crtc,
|
||||
static void hv_drm_crtc_helper_atomic_enable(struct drm_crtc *crtc,
|
||||
struct drm_atomic_commit *state)
|
||||
{
|
||||
struct hyperv_drm_device *hv = to_hv(crtc->dev);
|
||||
struct hv_drm_device *hv = to_hv_drm(crtc->dev);
|
||||
struct drm_plane *plane = &hv->plane;
|
||||
struct drm_plane_state *plane_state = plane->state;
|
||||
struct drm_crtc_state *crtc_state = crtc->state;
|
||||
|
||||
hyperv_hide_hw_ptr(hv->hdev);
|
||||
hyperv_update_situation(hv->hdev, 1, hv->screen_depth,
|
||||
hv_drm_hide_hw_ptr(hv->hdev);
|
||||
hv_drm_update_situation(hv->hdev, 1, hv->screen_depth,
|
||||
crtc_state->mode.hdisplay,
|
||||
crtc_state->mode.vdisplay,
|
||||
plane_state->fb->pitches[0]);
|
||||
|
|
@ -118,14 +118,14 @@ static void hyperv_crtc_helper_atomic_enable(struct drm_crtc *crtc,
|
|||
drm_crtc_vblank_on(crtc);
|
||||
}
|
||||
|
||||
static const struct drm_crtc_helper_funcs hyperv_crtc_helper_funcs = {
|
||||
static const struct drm_crtc_helper_funcs hv_drm_crtc_helper_funcs = {
|
||||
.atomic_check = drm_crtc_helper_atomic_check,
|
||||
.atomic_flush = drm_crtc_vblank_atomic_flush,
|
||||
.atomic_enable = hyperv_crtc_helper_atomic_enable,
|
||||
.atomic_enable = hv_drm_crtc_helper_atomic_enable,
|
||||
.atomic_disable = drm_crtc_vblank_atomic_disable,
|
||||
};
|
||||
|
||||
static const struct drm_crtc_funcs hyperv_crtc_funcs = {
|
||||
static const struct drm_crtc_funcs hv_drm_crtc_funcs = {
|
||||
.reset = drm_atomic_helper_crtc_reset,
|
||||
.destroy = drm_crtc_cleanup,
|
||||
.set_config = drm_atomic_helper_set_config,
|
||||
|
|
@ -135,11 +135,11 @@ static const struct drm_crtc_funcs hyperv_crtc_funcs = {
|
|||
DRM_CRTC_VBLANK_TIMER_FUNCS,
|
||||
};
|
||||
|
||||
static int hyperv_plane_atomic_check(struct drm_plane *plane,
|
||||
static int hv_drm_plane_atomic_check(struct drm_plane *plane,
|
||||
struct drm_atomic_commit *state)
|
||||
{
|
||||
struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane);
|
||||
struct hyperv_drm_device *hv = to_hv(plane->dev);
|
||||
struct hv_drm_device *hv = to_hv_drm(plane->dev);
|
||||
struct drm_framebuffer *fb = plane_state->fb;
|
||||
struct drm_crtc *crtc = plane_state->crtc;
|
||||
struct drm_crtc_state *crtc_state = NULL;
|
||||
|
|
@ -167,10 +167,10 @@ static int hyperv_plane_atomic_check(struct drm_plane *plane,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void hyperv_plane_atomic_update(struct drm_plane *plane,
|
||||
static void hv_drm_plane_atomic_update(struct drm_plane *plane,
|
||||
struct drm_atomic_commit *state)
|
||||
{
|
||||
struct hyperv_drm_device *hv = to_hv(plane->dev);
|
||||
struct hv_drm_device *hv = to_hv_drm(plane->dev);
|
||||
struct drm_plane_state *old_state = drm_atomic_get_old_plane_state(state, plane);
|
||||
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane);
|
||||
struct drm_shadow_plane_state *shadow_plane_state = to_drm_shadow_plane_state(new_state);
|
||||
|
|
@ -185,15 +185,15 @@ static void hyperv_plane_atomic_update(struct drm_plane *plane,
|
|||
if (!drm_rect_intersect(&dst_clip, &damage))
|
||||
continue;
|
||||
|
||||
hyperv_blit_to_vram_rect(new_state->fb, &shadow_plane_state->data[0], &damage);
|
||||
hyperv_update_dirt(hv->hdev, &damage);
|
||||
hv_drm_blit_to_vram_rect(new_state->fb, &shadow_plane_state->data[0], &damage);
|
||||
hv_drm_update_dirt(hv->hdev, &damage);
|
||||
}
|
||||
}
|
||||
|
||||
static int hyperv_plane_get_scanout_buffer(struct drm_plane *plane,
|
||||
static int hv_drm_plane_get_scanout_buffer(struct drm_plane *plane,
|
||||
struct drm_scanout_buffer *sb)
|
||||
{
|
||||
struct hyperv_drm_device *hv = to_hv(plane->dev);
|
||||
struct hv_drm_device *hv = to_hv_drm(plane->dev);
|
||||
struct iosys_map map = IOSYS_MAP_INIT_VADDR_IOMEM(hv->vram);
|
||||
|
||||
if (plane->state && plane->state->fb) {
|
||||
|
|
@ -207,9 +207,9 @@ static int hyperv_plane_get_scanout_buffer(struct drm_plane *plane,
|
|||
return -ENODEV;
|
||||
}
|
||||
|
||||
static void hyperv_plane_panic_flush(struct drm_plane *plane)
|
||||
static void hv_drm_plane_panic_flush(struct drm_plane *plane)
|
||||
{
|
||||
struct hyperv_drm_device *hv = to_hv(plane->dev);
|
||||
struct hv_drm_device *hv = to_hv_drm(plane->dev);
|
||||
struct drm_rect rect;
|
||||
|
||||
if (plane->state && plane->state->fb) {
|
||||
|
|
@ -218,32 +218,32 @@ static void hyperv_plane_panic_flush(struct drm_plane *plane)
|
|||
rect.x2 = plane->state->fb->width;
|
||||
rect.y2 = plane->state->fb->height;
|
||||
|
||||
hyperv_update_dirt(hv->hdev, &rect);
|
||||
hv_drm_update_dirt(hv->hdev, &rect);
|
||||
}
|
||||
|
||||
vmbus_initiate_unload(true);
|
||||
}
|
||||
|
||||
static const struct drm_plane_helper_funcs hyperv_plane_helper_funcs = {
|
||||
static const struct drm_plane_helper_funcs hv_drm_plane_helper_funcs = {
|
||||
DRM_GEM_SHADOW_PLANE_HELPER_FUNCS,
|
||||
.atomic_check = hyperv_plane_atomic_check,
|
||||
.atomic_update = hyperv_plane_atomic_update,
|
||||
.get_scanout_buffer = hyperv_plane_get_scanout_buffer,
|
||||
.panic_flush = hyperv_plane_panic_flush,
|
||||
.atomic_check = hv_drm_plane_atomic_check,
|
||||
.atomic_update = hv_drm_plane_atomic_update,
|
||||
.get_scanout_buffer = hv_drm_plane_get_scanout_buffer,
|
||||
.panic_flush = hv_drm_plane_panic_flush,
|
||||
};
|
||||
|
||||
static const struct drm_plane_funcs hyperv_plane_funcs = {
|
||||
static const struct drm_plane_funcs hv_drm_plane_funcs = {
|
||||
.update_plane = drm_atomic_helper_update_plane,
|
||||
.disable_plane = drm_atomic_helper_disable_plane,
|
||||
.destroy = drm_plane_cleanup,
|
||||
DRM_GEM_SHADOW_PLANE_FUNCS,
|
||||
};
|
||||
|
||||
static const struct drm_encoder_funcs hyperv_drm_simple_encoder_funcs_cleanup = {
|
||||
static const struct drm_encoder_funcs hv_drm_simple_encoder_funcs_cleanup = {
|
||||
.destroy = drm_encoder_cleanup,
|
||||
};
|
||||
|
||||
static inline int hyperv_pipe_init(struct hyperv_drm_device *hv)
|
||||
static inline int hv_drm_pipe_init(struct hv_drm_device *hv)
|
||||
{
|
||||
struct drm_device *dev = &hv->dev;
|
||||
struct drm_encoder *encoder = &hv->encoder;
|
||||
|
|
@ -253,29 +253,29 @@ static inline int hyperv_pipe_init(struct hyperv_drm_device *hv)
|
|||
int ret;
|
||||
|
||||
ret = drm_universal_plane_init(dev, plane, 0,
|
||||
&hyperv_plane_funcs,
|
||||
hyperv_formats, ARRAY_SIZE(hyperv_formats),
|
||||
hyperv_modifiers,
|
||||
&hv_drm_plane_funcs,
|
||||
hv_drm_formats, ARRAY_SIZE(hv_drm_formats),
|
||||
hv_drm_modifiers,
|
||||
DRM_PLANE_TYPE_PRIMARY, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
drm_plane_helper_add(plane, &hyperv_plane_helper_funcs);
|
||||
drm_plane_helper_add(plane, &hv_drm_plane_helper_funcs);
|
||||
drm_plane_enable_fb_damage_clips(plane);
|
||||
|
||||
ret = drm_crtc_init_with_planes(dev, crtc, plane, NULL,
|
||||
&hyperv_crtc_funcs, NULL);
|
||||
&hv_drm_crtc_funcs, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
drm_crtc_helper_add(crtc, &hyperv_crtc_helper_funcs);
|
||||
drm_crtc_helper_add(crtc, &hv_drm_crtc_helper_funcs);
|
||||
|
||||
encoder->possible_crtcs = drm_crtc_mask(crtc);
|
||||
ret = drm_encoder_init(dev, encoder,
|
||||
&hyperv_drm_simple_encoder_funcs_cleanup,
|
||||
&hv_drm_simple_encoder_funcs_cleanup,
|
||||
DRM_MODE_ENCODER_NONE, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = hyperv_conn_init(hv);
|
||||
ret = hv_drm_conn_init(hv);
|
||||
if (ret) {
|
||||
drm_err(dev, "Failed to initialized connector.\n");
|
||||
return ret;
|
||||
|
|
@ -285,25 +285,25 @@ static inline int hyperv_pipe_init(struct hyperv_drm_device *hv)
|
|||
}
|
||||
|
||||
static enum drm_mode_status
|
||||
hyperv_mode_valid(struct drm_device *dev,
|
||||
hv_drm_mode_valid(struct drm_device *dev,
|
||||
const struct drm_display_mode *mode)
|
||||
{
|
||||
struct hyperv_drm_device *hv = to_hv(dev);
|
||||
struct hv_drm_device *hv = to_hv_drm(dev);
|
||||
|
||||
if (hyperv_check_size(hv, mode->hdisplay, mode->vdisplay, NULL))
|
||||
if (hv_drm_check_size(hv, mode->hdisplay, mode->vdisplay, NULL))
|
||||
return MODE_BAD;
|
||||
|
||||
return MODE_OK;
|
||||
}
|
||||
|
||||
static const struct drm_mode_config_funcs hyperv_mode_config_funcs = {
|
||||
static const struct drm_mode_config_funcs hv_drm_mode_config_funcs = {
|
||||
.fb_create = drm_gem_fb_create_with_dirty,
|
||||
.mode_valid = hyperv_mode_valid,
|
||||
.mode_valid = hv_drm_mode_valid,
|
||||
.atomic_check = drm_atomic_helper_check,
|
||||
.atomic_commit = drm_atomic_helper_commit,
|
||||
};
|
||||
|
||||
int hyperv_mode_config_init(struct hyperv_drm_device *hv)
|
||||
int hv_drm_mode_config_init(struct hv_drm_device *hv)
|
||||
{
|
||||
struct drm_device *dev = &hv->dev;
|
||||
int ret;
|
||||
|
|
@ -322,9 +322,9 @@ int hyperv_mode_config_init(struct hyperv_drm_device *hv)
|
|||
dev->mode_config.preferred_depth = hv->screen_depth;
|
||||
dev->mode_config.prefer_shadow = 0;
|
||||
|
||||
dev->mode_config.funcs = &hyperv_mode_config_funcs;
|
||||
dev->mode_config.funcs = &hv_drm_mode_config_funcs;
|
||||
|
||||
ret = hyperv_pipe_init(hv);
|
||||
ret = hv_drm_pipe_init(hv);
|
||||
if (ret) {
|
||||
drm_err(dev, "Failed to initialized pipe.\n");
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ struct synthvid_msg {
|
|||
};
|
||||
} __packed;
|
||||
|
||||
static inline bool hyperv_version_ge(u32 ver1, u32 ver2)
|
||||
static inline bool hv_drm_version_ge(u32 ver1, u32 ver2)
|
||||
{
|
||||
if (SYNTHVID_VER_GET_MAJOR(ver1) > SYNTHVID_VER_GET_MAJOR(ver2) ||
|
||||
(SYNTHVID_VER_GET_MAJOR(ver1) == SYNTHVID_VER_GET_MAJOR(ver2) &&
|
||||
|
|
@ -191,10 +191,10 @@ static inline bool hyperv_version_ge(u32 ver1, u32 ver2)
|
|||
return false;
|
||||
}
|
||||
|
||||
static inline int hyperv_sendpacket(struct hv_device *hdev, struct synthvid_msg *msg)
|
||||
static inline int hv_drm_sendpacket(struct hv_device *hdev, struct synthvid_msg *msg)
|
||||
{
|
||||
static atomic64_t request_id = ATOMIC64_INIT(0);
|
||||
struct hyperv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
int ret;
|
||||
|
||||
msg->pipe_hdr.type = PIPE_MSG_DATA;
|
||||
|
|
@ -211,9 +211,9 @@ static inline int hyperv_sendpacket(struct hv_device *hdev, struct synthvid_msg
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int hyperv_negotiate_version(struct hv_device *hdev, u32 ver)
|
||||
static int hv_drm_negotiate_version(struct hv_device *hdev, u32 ver)
|
||||
{
|
||||
struct hyperv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct synthvid_msg *msg = (struct synthvid_msg *)hv->init_buf;
|
||||
struct drm_device *dev = &hv->dev;
|
||||
unsigned long t;
|
||||
|
|
@ -223,7 +223,7 @@ static int hyperv_negotiate_version(struct hv_device *hdev, u32 ver)
|
|||
msg->vid_hdr.size = sizeof(struct synthvid_msg_hdr) +
|
||||
sizeof(struct synthvid_version_req);
|
||||
msg->ver_req.version = ver;
|
||||
hyperv_sendpacket(hdev, msg);
|
||||
hv_drm_sendpacket(hdev, msg);
|
||||
|
||||
t = wait_for_completion_timeout(&hv->wait, VMBUS_VSP_TIMEOUT);
|
||||
if (!t) {
|
||||
|
|
@ -243,9 +243,9 @@ static int hyperv_negotiate_version(struct hv_device *hdev, u32 ver)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int hyperv_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp)
|
||||
int hv_drm_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp)
|
||||
{
|
||||
struct hyperv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct synthvid_msg *msg = (struct synthvid_msg *)hv->init_buf;
|
||||
struct drm_device *dev = &hv->dev;
|
||||
unsigned long t;
|
||||
|
|
@ -257,7 +257,7 @@ int hyperv_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp)
|
|||
msg->vram.user_ctx = vram_pp;
|
||||
msg->vram.vram_gpa = vram_pp;
|
||||
msg->vram.is_vram_gpa_specified = 1;
|
||||
hyperv_sendpacket(hdev, msg);
|
||||
hv_drm_sendpacket(hdev, msg);
|
||||
|
||||
t = wait_for_completion_timeout(&hv->wait, VMBUS_VSP_TIMEOUT);
|
||||
if (!t) {
|
||||
|
|
@ -272,7 +272,7 @@ int hyperv_update_vram_location(struct hv_device *hdev, phys_addr_t vram_pp)
|
|||
return 0;
|
||||
}
|
||||
|
||||
int hyperv_update_situation(struct hv_device *hdev, u8 active, u32 bpp,
|
||||
int hv_drm_update_situation(struct hv_device *hdev, u8 active, u32 bpp,
|
||||
u32 w, u32 h, u32 pitch)
|
||||
{
|
||||
struct synthvid_msg msg;
|
||||
|
|
@ -292,7 +292,7 @@ int hyperv_update_situation(struct hv_device *hdev, u8 active, u32 bpp,
|
|||
msg.situ.video_output[0].height_pixels = h;
|
||||
msg.situ.video_output[0].pitch_bytes = pitch;
|
||||
|
||||
hyperv_sendpacket(hdev, &msg);
|
||||
hv_drm_sendpacket(hdev, &msg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -306,11 +306,11 @@ int hyperv_update_situation(struct hv_device *hdev, u8 active, u32 bpp,
|
|||
* the msg.ptr_shape.data. Note: setting msg.ptr_pos.is_visible to 0 doesn't
|
||||
* work in tests.
|
||||
*
|
||||
* The hyperv_hide_hw_ptr() is also called in the handler of the
|
||||
* The hv_drm_hide_hw_ptr() is also called in the handler of the
|
||||
* SYNTHVID_FEATURE_CHANGE event, otherwise the host still draws an extra
|
||||
* unwanted mouse pointer after the VM Connection window is closed and reopened.
|
||||
*/
|
||||
int hyperv_hide_hw_ptr(struct hv_device *hdev)
|
||||
int hv_drm_hide_hw_ptr(struct hv_device *hdev)
|
||||
{
|
||||
struct synthvid_msg msg;
|
||||
|
||||
|
|
@ -322,7 +322,7 @@ int hyperv_hide_hw_ptr(struct hv_device *hdev)
|
|||
msg.ptr_pos.video_output = 0;
|
||||
msg.ptr_pos.image_x = 0;
|
||||
msg.ptr_pos.image_y = 0;
|
||||
hyperv_sendpacket(hdev, &msg);
|
||||
hv_drm_sendpacket(hdev, &msg);
|
||||
|
||||
memset(&msg, 0, sizeof(struct synthvid_msg));
|
||||
msg.vid_hdr.type = SYNTHVID_POINTER_SHAPE;
|
||||
|
|
@ -338,14 +338,14 @@ int hyperv_hide_hw_ptr(struct hv_device *hdev)
|
|||
msg.ptr_shape.data[1] = 1;
|
||||
msg.ptr_shape.data[2] = 1;
|
||||
msg.ptr_shape.data[3] = 1;
|
||||
hyperv_sendpacket(hdev, &msg);
|
||||
hv_drm_sendpacket(hdev, &msg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int hyperv_update_dirt(struct hv_device *hdev, struct drm_rect *rect)
|
||||
int hv_drm_update_dirt(struct hv_device *hdev, struct drm_rect *rect)
|
||||
{
|
||||
struct hyperv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct synthvid_msg msg;
|
||||
|
||||
if (!hv->dirt_needed)
|
||||
|
|
@ -363,14 +363,14 @@ int hyperv_update_dirt(struct hv_device *hdev, struct drm_rect *rect)
|
|||
msg.dirt.rect[0].x2 = rect->x2;
|
||||
msg.dirt.rect[0].y2 = rect->y2;
|
||||
|
||||
hyperv_sendpacket(hdev, &msg);
|
||||
hv_drm_sendpacket(hdev, &msg);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int hyperv_get_supported_resolution(struct hv_device *hdev)
|
||||
static int hv_drm_get_supported_resolution(struct hv_device *hdev)
|
||||
{
|
||||
struct hyperv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct synthvid_msg *msg = (struct synthvid_msg *)hv->init_buf;
|
||||
struct drm_device *dev = &hv->dev;
|
||||
unsigned long t;
|
||||
|
|
@ -383,7 +383,7 @@ static int hyperv_get_supported_resolution(struct hv_device *hdev)
|
|||
sizeof(struct synthvid_supported_resolution_req);
|
||||
msg->resolution_req.maximum_resolution_count =
|
||||
SYNTHVID_MAX_RESOLUTION_COUNT;
|
||||
hyperv_sendpacket(hdev, msg);
|
||||
hv_drm_sendpacket(hdev, msg);
|
||||
|
||||
t = wait_for_completion_timeout(&hv->wait, VMBUS_VSP_TIMEOUT);
|
||||
if (!t) {
|
||||
|
|
@ -420,9 +420,9 @@ static int hyperv_get_supported_resolution(struct hv_device *hdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void hyperv_receive_sub(struct hv_device *hdev, u32 bytes_recvd)
|
||||
static void hv_drm_receive_sub(struct hv_device *hdev, u32 bytes_recvd)
|
||||
{
|
||||
struct hyperv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct synthvid_msg *msg;
|
||||
size_t hdr_size;
|
||||
size_t need;
|
||||
|
|
@ -486,7 +486,7 @@ static void hyperv_receive_sub(struct hv_device *hdev, u32 bytes_recvd)
|
|||
}
|
||||
hv->dirt_needed = msg->feature_chg.is_dirt_needed;
|
||||
if (hv->dirt_needed)
|
||||
hyperv_hide_hw_ptr(hv->hdev);
|
||||
hv_drm_hide_hw_ptr(hv->hdev);
|
||||
return;
|
||||
default:
|
||||
return;
|
||||
|
|
@ -508,10 +508,10 @@ static void hyperv_receive_sub(struct hv_device *hdev, u32 bytes_recvd)
|
|||
complete(&hv->wait);
|
||||
}
|
||||
|
||||
static void hyperv_receive(void *ctx)
|
||||
static void hv_drm_receive(void *ctx)
|
||||
{
|
||||
struct hv_device *hdev = ctx;
|
||||
struct hyperv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct synthvid_msg *recv_buf;
|
||||
u32 bytes_recvd;
|
||||
u64 req_id;
|
||||
|
|
@ -539,19 +539,19 @@ static void hyperv_receive(void *ctx)
|
|||
ret, bytes_recvd);
|
||||
} else if (bytes_recvd > 0 &&
|
||||
recv_buf->pipe_hdr.type == PIPE_MSG_DATA) {
|
||||
hyperv_receive_sub(hdev, bytes_recvd);
|
||||
hv_drm_receive_sub(hdev, bytes_recvd);
|
||||
}
|
||||
} while (bytes_recvd > 0 && ret == 0);
|
||||
}
|
||||
|
||||
int hyperv_connect_vsp(struct hv_device *hdev)
|
||||
int hv_drm_connect_vsp(struct hv_device *hdev)
|
||||
{
|
||||
struct hyperv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct hv_drm_device *hv = hv_get_drvdata(hdev);
|
||||
struct drm_device *dev = &hv->dev;
|
||||
int ret;
|
||||
|
||||
ret = vmbus_open(hdev->channel, VMBUS_RING_BUFSIZE, VMBUS_RING_BUFSIZE,
|
||||
NULL, 0, hyperv_receive, hdev);
|
||||
NULL, 0, hv_drm_receive, hdev);
|
||||
if (ret) {
|
||||
drm_err(dev, "Unable to open vmbus channel\n");
|
||||
return ret;
|
||||
|
|
@ -561,16 +561,16 @@ int hyperv_connect_vsp(struct hv_device *hdev)
|
|||
switch (vmbus_proto_version) {
|
||||
case VERSION_WIN10:
|
||||
case VERSION_WIN10_V5:
|
||||
ret = hyperv_negotiate_version(hdev, SYNTHVID_VERSION_WIN10);
|
||||
ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN10);
|
||||
if (!ret)
|
||||
break;
|
||||
fallthrough;
|
||||
case VERSION_WIN8:
|
||||
case VERSION_WIN8_1:
|
||||
ret = hyperv_negotiate_version(hdev, SYNTHVID_VERSION_WIN8);
|
||||
ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN8);
|
||||
break;
|
||||
default:
|
||||
ret = hyperv_negotiate_version(hdev, SYNTHVID_VERSION_WIN10);
|
||||
ret = hv_drm_negotiate_version(hdev, SYNTHVID_VERSION_WIN10);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -581,8 +581,8 @@ int hyperv_connect_vsp(struct hv_device *hdev)
|
|||
|
||||
hv->screen_depth = SYNTHVID_DEPTH_WIN8;
|
||||
|
||||
if (hyperv_version_ge(hv->synthvid_version, SYNTHVID_VERSION_WIN10)) {
|
||||
ret = hyperv_get_supported_resolution(hdev);
|
||||
if (hv_drm_version_ge(hv->synthvid_version, SYNTHVID_VERSION_WIN10)) {
|
||||
ret = hv_drm_get_supported_resolution(hdev);
|
||||
if (ret)
|
||||
drm_err(dev, "Failed to get supported resolution from host, use default\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -182,7 +182,7 @@ void intel_crtc_state_reset(struct intel_crtc_state *crtc_state,
|
|||
{
|
||||
memset(crtc_state, 0, sizeof(*crtc_state));
|
||||
|
||||
__drm_atomic_helper_crtc_state_reset(&crtc_state->uapi, &crtc->base);
|
||||
__drm_atomic_helper_crtc_state_init(&crtc_state->uapi, &crtc->base);
|
||||
|
||||
crtc_state->cpu_transcoder = INVALID_TRANSCODER;
|
||||
crtc_state->master_transcoder = INVALID_TRANSCODER;
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ static void intel_plane_state_reset(struct intel_plane_state *plane_state,
|
|||
{
|
||||
memset(plane_state, 0, sizeof(*plane_state));
|
||||
|
||||
__drm_atomic_helper_plane_state_reset(&plane_state->uapi, &plane->base);
|
||||
__drm_atomic_helper_plane_state_init(&plane_state->uapi, &plane->base);
|
||||
|
||||
plane_state->scaler_id = -1;
|
||||
plane_state->fence_id = -1;
|
||||
|
|
|
|||
|
|
@ -1378,7 +1378,7 @@ pvr_drm_driver_postclose(__always_unused struct drm_device *drm_dev,
|
|||
DEFINE_DRM_GEM_FOPS(pvr_drm_driver_fops);
|
||||
|
||||
static struct drm_driver pvr_drm_driver = {
|
||||
.driver_features = DRIVER_GEM | DRIVER_GEM_GPUVA | DRIVER_RENDER |
|
||||
.driver_features = DRIVER_GEM | DRIVER_RENDER |
|
||||
DRIVER_SYNCOBJ | DRIVER_SYNCOBJ_TIMELINE,
|
||||
.open = pvr_drm_driver_open,
|
||||
.postclose = pvr_drm_driver_postclose,
|
||||
|
|
|
|||
|
|
@ -444,8 +444,8 @@ static const struct dev_pm_ops lsdc_pm_ops = {
|
|||
};
|
||||
|
||||
static const struct pci_device_id lsdc_pciid_list[] = {
|
||||
{PCI_VDEVICE(LOONGSON, 0x7a06), CHIP_LS7A1000},
|
||||
{PCI_VDEVICE(LOONGSON, 0x7a36), CHIP_LS7A2000},
|
||||
{ PCI_VDEVICE(LOONGSON, 0x7a06), .driver_data = CHIP_LS7A1000 },
|
||||
{ PCI_VDEVICE(LOONGSON, 0x7a36), .driver_data = CHIP_LS7A2000 },
|
||||
{ }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -204,18 +204,18 @@ int mgag200_device_init(struct mga_device *mdev,
|
|||
*/
|
||||
|
||||
static const struct pci_device_id mgag200_pciidlist[] = {
|
||||
{ PCI_VENDOR_ID_MATROX, 0x520, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_PCI },
|
||||
{ PCI_VENDOR_ID_MATROX, 0x521, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_AGP },
|
||||
{ PCI_VENDOR_ID_MATROX, 0x522, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_SE_A },
|
||||
{ PCI_VENDOR_ID_MATROX, 0x524, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_SE_B },
|
||||
{ PCI_VENDOR_ID_MATROX, 0x530, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_EV },
|
||||
{ PCI_VENDOR_ID_MATROX, 0x532, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_WB },
|
||||
{ PCI_VENDOR_ID_MATROX, 0x533, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_EH },
|
||||
{ PCI_VENDOR_ID_MATROX, 0x534, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_ER },
|
||||
{ PCI_VENDOR_ID_MATROX, 0x536, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_EW3 },
|
||||
{ PCI_VENDOR_ID_MATROX, 0x538, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_EH3 },
|
||||
{ PCI_VENDOR_ID_MATROX, 0x53a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, G200_EH5 },
|
||||
{0,}
|
||||
{ PCI_VDEVICE(MATROX, 0x0520), .driver_data = G200_PCI },
|
||||
{ PCI_VDEVICE(MATROX, 0x0521), .driver_data = G200_AGP },
|
||||
{ PCI_VDEVICE(MATROX, 0x0522), .driver_data = G200_SE_A },
|
||||
{ PCI_VDEVICE(MATROX, 0x0524), .driver_data = G200_SE_B },
|
||||
{ PCI_VDEVICE(MATROX, 0x0530), .driver_data = G200_EV },
|
||||
{ PCI_VDEVICE(MATROX, 0x0532), .driver_data = G200_WB },
|
||||
{ PCI_VDEVICE(MATROX, 0x0533), .driver_data = G200_EH },
|
||||
{ PCI_VDEVICE(MATROX, 0x0534), .driver_data = G200_ER },
|
||||
{ PCI_VDEVICE(MATROX, 0x0536), .driver_data = G200_EW3 },
|
||||
{ PCI_VDEVICE(MATROX, 0x0538), .driver_data = G200_EH3 },
|
||||
{ PCI_VDEVICE(MATROX, 0x053a), .driver_data = G200_EH5 },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(pci, mgag200_pciidlist);
|
||||
|
|
|
|||
|
|
@ -166,6 +166,7 @@ struct msm_dsi_host {
|
|||
|
||||
struct drm_display_mode *mode;
|
||||
struct drm_dsc_config *dsc;
|
||||
unsigned int dsc_slice_per_pkt;
|
||||
|
||||
/* connected device info */
|
||||
unsigned int channel;
|
||||
|
|
@ -938,17 +939,10 @@ static void dsi_update_dsc_timing(struct msm_dsi_host *msm_host, bool is_cmd_mod
|
|||
slice_per_intf = dsc->slice_count;
|
||||
|
||||
total_bytes_per_intf = dsc->slice_chunk_size * slice_per_intf;
|
||||
bytes_per_pkt = dsc->slice_chunk_size; /* * slice_per_pkt; */
|
||||
bytes_per_pkt = dsc->slice_chunk_size * msm_host->dsc_slice_per_pkt;
|
||||
|
||||
eol_byte_num = total_bytes_per_intf % 3;
|
||||
|
||||
/*
|
||||
* Typically, pkt_per_line = slice_per_intf * slice_per_pkt.
|
||||
*
|
||||
* Since the current driver only supports slice_per_pkt = 1,
|
||||
* pkt_per_line will be equal to slice per intf for now.
|
||||
*/
|
||||
pkt_per_line = slice_per_intf;
|
||||
pkt_per_line = slice_per_intf / msm_host->dsc_slice_per_pkt;
|
||||
|
||||
if (is_cmd_mode) /* packet data type */
|
||||
reg = DSI_COMMAND_COMPRESSION_MODE_CTRL_STREAM0_DATATYPE(MIPI_DSI_DCS_LONG_WRITE);
|
||||
|
|
@ -1104,12 +1098,8 @@ static void dsi_timing_setup(struct msm_dsi_host *msm_host, bool is_bonded_dsi)
|
|||
else
|
||||
/*
|
||||
* When DSC is enabled, WC = slice_chunk_size * slice_per_pkt + 1.
|
||||
* Currently, the driver only supports default value of slice_per_pkt = 1
|
||||
*
|
||||
* TODO: Expand mipi_dsi_device struct to hold slice_per_pkt info
|
||||
* and adjust DSC math to account for slice_per_pkt.
|
||||
*/
|
||||
wc = msm_host->dsc->slice_chunk_size + 1;
|
||||
wc = msm_host->dsc->slice_chunk_size * msm_host->dsc_slice_per_pkt + 1;
|
||||
|
||||
dsi_write(msm_host, REG_DSI_CMD_MDP_STREAM0_CTRL,
|
||||
DSI_CMD_MDP_STREAM0_CTRL_WORD_COUNT(wc) |
|
||||
|
|
@ -1718,8 +1708,13 @@ static int dsi_host_attach(struct mipi_dsi_host *host,
|
|||
msm_host->lanes = dsi->lanes;
|
||||
msm_host->format = dsi->format;
|
||||
msm_host->mode_flags = dsi->mode_flags;
|
||||
if (dsi->dsc)
|
||||
if (dsi->dsc) {
|
||||
msm_host->dsc = dsi->dsc;
|
||||
if (dsi->mode_flags & MIPI_DSI_MODE_DSC_ALL_SLICES_IN_PKT)
|
||||
msm_host->dsc_slice_per_pkt = dsi->dsc->slice_count;
|
||||
else
|
||||
msm_host->dsc_slice_per_pkt = 1;
|
||||
}
|
||||
|
||||
if (msm_host->format == MIPI_DSI_FMT_RGB101010) {
|
||||
if (!msm_dsi_host_version_geq(msm_host, MSM_DSI_VER_MAJOR_6G,
|
||||
|
|
|
|||
|
|
@ -824,7 +824,6 @@ static const struct file_operations fops = {
|
|||
|
||||
#define DRIVER_FEATURES_GPU ( \
|
||||
DRIVER_GEM | \
|
||||
DRIVER_GEM_GPUVA | \
|
||||
DRIVER_RENDER | \
|
||||
DRIVER_SYNCOBJ | \
|
||||
DRIVER_SYNCOBJ_TIMELINE | \
|
||||
|
|
@ -832,7 +831,6 @@ static const struct file_operations fops = {
|
|||
|
||||
#define DRIVER_FEATURES_KMS ( \
|
||||
DRIVER_GEM | \
|
||||
DRIVER_GEM_GPUVA | \
|
||||
DRIVER_ATOMIC | \
|
||||
DRIVER_MODESET | \
|
||||
0 )
|
||||
|
|
|
|||
|
|
@ -338,7 +338,8 @@ static void lcdif_set_mode(struct lcdif_drm_private *lcdif, u32 bus_flags)
|
|||
* Downstream set it to 256B burst size to improve the memory
|
||||
* efficiency so set it here too.
|
||||
*/
|
||||
ctrl = CTRLDESCL0_3_P_SIZE(2) | CTRLDESCL0_3_T_SIZE(2) |
|
||||
ctrl = CTRLDESCL0_3_STATE_CLEAR_VSYNC |
|
||||
CTRLDESCL0_3_P_SIZE(2) | CTRLDESCL0_3_T_SIZE(2) |
|
||||
CTRLDESCL0_3_PITCH(lcdif->crtc.primary->state->fb->pitches[0]);
|
||||
writel(ctrl, lcdif->base + LCDC_V8_CTRLDESCL0_3);
|
||||
}
|
||||
|
|
@ -374,14 +375,23 @@ static void lcdif_disable_controller(struct lcdif_drm_private *lcdif)
|
|||
int ret;
|
||||
|
||||
reg = readl(lcdif->base + LCDC_V8_CTRLDESCL0_5);
|
||||
/* Disable the layer for DMA. */
|
||||
reg &= ~CTRLDESCL0_5_EN;
|
||||
/*
|
||||
* It is necessary to wait for the full frame to finish streaming
|
||||
* through the DMA engine before we can safely disable it by removing
|
||||
* the DISP_PARA_DISP_ON bit. Disabling it in-flight can leave the
|
||||
* hardware confused and unable to resume streaming for the next frame.
|
||||
*/
|
||||
reg |= CTRLDESCL0_5_SHADOW_LOAD_EN;
|
||||
writel(reg, lcdif->base + LCDC_V8_CTRLDESCL0_5);
|
||||
|
||||
/* Wait for the frame to finish or timeout after 50 ms. */
|
||||
ret = readl_poll_timeout(lcdif->base + LCDC_V8_CTRLDESCL0_5,
|
||||
reg, !(reg & CTRLDESCL0_5_EN),
|
||||
0, 36000); /* Wait ~2 frame times max */
|
||||
reg, !(reg & CTRLDESCL0_5_SHADOW_LOAD_EN),
|
||||
200, 50000);
|
||||
if (ret)
|
||||
drm_err(lcdif->drm, "Failed to disable controller!\n");
|
||||
drm_err(lcdif->drm, "Timed out waiting for final vblank!\n");
|
||||
|
||||
reg = readl(lcdif->base + LCDC_V8_DISP_PARA);
|
||||
reg &= ~DISP_PARA_DISP_ON;
|
||||
|
|
|
|||
|
|
@ -190,6 +190,7 @@
|
|||
#define CTRLDESCL0_1_WIDTH(n) ((n) & 0xffff)
|
||||
#define CTRLDESCL0_1_WIDTH_MASK GENMASK(15, 0)
|
||||
|
||||
#define CTRLDESCL0_3_STATE_CLEAR_VSYNC BIT(23)
|
||||
#define CTRLDESCL0_3_P_SIZE(n) (((n) << 20) & CTRLDESCL0_3_P_SIZE_MASK)
|
||||
#define CTRLDESCL0_3_P_SIZE_MASK GENMASK(22, 20)
|
||||
#define CTRLDESCL0_3_T_SIZE(n) (((n) << 16) & CTRLDESCL0_3_T_SIZE_MASK)
|
||||
|
|
|
|||
|
|
@ -1364,7 +1364,6 @@ static struct drm_driver
|
|||
driver_stub = {
|
||||
.driver_features = DRIVER_GEM |
|
||||
DRIVER_SYNCOBJ | DRIVER_SYNCOBJ_TIMELINE |
|
||||
DRIVER_GEM_GPUVA |
|
||||
DRIVER_MODESET |
|
||||
DRIVER_RENDER,
|
||||
.open = nouveau_drm_open,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,16 @@ config DRM_PANEL_ABT_Y030XX067A
|
|||
Y030XX067A 320x480 3.0" panel as found in the YLM RG-280M, RG-300
|
||||
and RG-99 handheld gaming consoles.
|
||||
|
||||
config DRM_PANEL_ANBERNIC_TD4310
|
||||
tristate "Anbernic TD4310 LCD panel"
|
||||
depends on GPIOLIB && OF
|
||||
depends on DRM_MIPI_DSI
|
||||
depends on BACKLIGHT_CLASS_DEVICE
|
||||
help
|
||||
Say Y here to enable support for Anbernic designed panels with the
|
||||
TD4310 panel controller such as the ones used on the Anbernic RG
|
||||
Vita Pro.
|
||||
|
||||
config DRM_PANEL_ARM_VERSATILE
|
||||
tristate "ARM Versatile panel driver"
|
||||
depends on OF
|
||||
|
|
@ -105,6 +115,17 @@ config DRM_PANEL_BOE_TV101WUM_LL2
|
|||
Say Y here if you want to support for BOE TV101WUM-LL2
|
||||
WUXGA PANEL DSI Video Mode panel
|
||||
|
||||
config DRM_PANEL_CHIPONE_ICNA35XX
|
||||
tristate "Chipone ICNA35XX panel driver"
|
||||
depends on OF
|
||||
depends on DRM_MIPI_DSI
|
||||
depends on BACKLIGHT_CLASS_DEVICE
|
||||
select DRM_DISPLAY_HELPER
|
||||
help
|
||||
Say Y here if you want to enable support for the panels built
|
||||
around the Chipone ICNA3512 and ICNA3520 display controllers,
|
||||
such as some Tianma panels used in AYN Odin2 Portal and Thor.
|
||||
|
||||
config DRM_PANEL_CHIPWEALTH_CH13726A
|
||||
tristate "CHIPWEALTH CH13726A-based DSI panel"
|
||||
depends on OF
|
||||
|
|
@ -284,6 +305,15 @@ config DRM_PANEL_ILITEK_ILI9341
|
|||
QVGA (240x320) RGB panels. support serial & parallel rgb
|
||||
interface.
|
||||
|
||||
config DRM_PANEL_ILITEK_ILI9488
|
||||
tristate "Ilitek ILI9488-based panels"
|
||||
depends on OF
|
||||
depends on DRM_MIPI_DSI
|
||||
depends on BACKLIGHT_CLASS_DEVICE
|
||||
help
|
||||
Say Y if you want to enable support for panels based on the
|
||||
Ilitek ILI9488 controller.
|
||||
|
||||
config DRM_PANEL_ILITEK_ILI9805
|
||||
tristate "Ilitek ILI9805-based panels"
|
||||
depends on OF
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_DRM_PANEL_ABT_Y030XX067A) += panel-abt-y030xx067a.o
|
||||
obj-$(CONFIG_DRM_PANEL_ANBERNIC_TD4310) += panel-anbernic-td4310.o
|
||||
obj-$(CONFIG_DRM_PANEL_ARM_VERSATILE) += panel-arm-versatile.o
|
||||
obj-$(CONFIG_DRM_PANEL_ASUS_Z00T_TM5P5_NT35596) += panel-asus-z00t-tm5p5-n35596.o
|
||||
obj-$(CONFIG_DRM_PANEL_AUO_A030JTN01) += panel-auo-a030jtn01.o
|
||||
|
|
@ -9,6 +10,7 @@ obj-$(CONFIG_DRM_PANEL_BOE_TD4320) += panel-boe-td4320.o
|
|||
obj-$(CONFIG_DRM_PANEL_BOE_TH101MB31UIG002_28A) += panel-boe-th101mb31ig002-28a.o
|
||||
obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_LL2) += panel-boe-tv101wum-ll2.o
|
||||
obj-$(CONFIG_DRM_PANEL_BOE_TV101WUM_NL6) += panel-boe-tv101wum-nl6.o
|
||||
obj-$(CONFIG_DRM_PANEL_CHIPONE_ICNA35XX) += panel-chipone-icna35xx.o
|
||||
obj-$(CONFIG_DRM_PANEL_CHIPWEALTH_CH13726A) += panel-chipwealth-ch13726a.o
|
||||
obj-$(CONFIG_DRM_PANEL_DSI_CM) += panel-dsi-cm.o
|
||||
obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o
|
||||
|
|
@ -28,6 +30,7 @@ obj-$(CONFIG_DRM_PANEL_HIMAX_HX8394) += panel-himax-hx8394.o
|
|||
obj-$(CONFIG_DRM_PANEL_HYDIS_HV101HD1) += panel-hydis-hv101hd1.o
|
||||
obj-$(CONFIG_DRM_PANEL_ILITEK_IL9322) += panel-ilitek-ili9322.o
|
||||
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9341) += panel-ilitek-ili9341.o
|
||||
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9488) += panel-ilitek-ili9488.o
|
||||
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9805) += panel-ilitek-ili9805.o
|
||||
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9806E_CORE) += panel-ilitek-ili9806e-core.o
|
||||
obj-$(CONFIG_DRM_PANEL_ILITEK_ILI9806E_DSI) += panel-ilitek-ili9806e-dsi.o
|
||||
|
|
|
|||
257
drivers/gpu/drm/panel/panel-anbernic-td4310.c
Normal file
257
drivers/gpu/drm/panel/panel-anbernic-td4310.c
Normal file
|
|
@ -0,0 +1,257 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Driver for Anbernic panels with TD4310 panel controller.
|
||||
*
|
||||
* Copyright (C) 2026 Chris Morgan <macromorgan@hotmail.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
#include <video/mipi_display.h>
|
||||
|
||||
struct anbernic_panel_td4310_info {
|
||||
const struct drm_display_mode *display_mode;
|
||||
u16 width_mm;
|
||||
u16 height_mm;
|
||||
u32 bus_flags;
|
||||
unsigned long mode_flags;
|
||||
u32 format;
|
||||
u32 lanes;
|
||||
u16 prepare_delay;
|
||||
u16 reset_delay;
|
||||
u16 init_delay;
|
||||
u16 enable_delay;
|
||||
u16 disable_delay;
|
||||
u16 unprepare_delay;
|
||||
};
|
||||
|
||||
struct anbernic_panel_td4310 {
|
||||
struct device *dev;
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct drm_panel panel;
|
||||
const struct anbernic_panel_td4310_info *panel_info;
|
||||
struct gpio_desc *reset_gpio;
|
||||
struct gpio_desc *enable_gpio;
|
||||
struct regulator *vdd;
|
||||
enum drm_panel_orientation orientation;
|
||||
};
|
||||
|
||||
static inline struct anbernic_panel_td4310 *panel_to_anbernic_panel_td4310(struct drm_panel *panel)
|
||||
{
|
||||
return container_of(panel, struct anbernic_panel_td4310, panel);
|
||||
}
|
||||
|
||||
static int panel_anbernic_td4310_prepare(struct drm_panel *panel)
|
||||
{
|
||||
struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
|
||||
struct mipi_dsi_device *dsi = ctx->dsi;
|
||||
struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
|
||||
int ret;
|
||||
|
||||
ret = regulator_enable(ctx->vdd);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = gpiod_set_value_cansleep(ctx->enable_gpio, 1);
|
||||
if (ret)
|
||||
goto err_enable;
|
||||
|
||||
if (ctx->panel_info->enable_delay)
|
||||
mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->enable_delay);
|
||||
|
||||
ret = gpiod_set_value_cansleep(ctx->reset_gpio, 1);
|
||||
if (ret)
|
||||
goto err_reset;
|
||||
|
||||
mipi_dsi_msleep(&dsi_ctx, 10);
|
||||
|
||||
ret = gpiod_set_value_cansleep(ctx->reset_gpio, 0);
|
||||
if (ret)
|
||||
goto err_reset;
|
||||
|
||||
if (ctx->panel_info->reset_delay)
|
||||
mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->reset_delay);
|
||||
|
||||
mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
|
||||
mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->prepare_delay);
|
||||
mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
|
||||
mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->prepare_delay);
|
||||
|
||||
if (dsi_ctx.accum_err) {
|
||||
ret = dsi_ctx.accum_err;
|
||||
goto err_reset;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_reset:
|
||||
gpiod_set_value_cansleep(ctx->enable_gpio, 0);
|
||||
err_enable:
|
||||
regulator_disable(ctx->vdd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int panel_anbernic_td4310_unprepare(struct drm_panel *panel)
|
||||
{
|
||||
struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
|
||||
struct mipi_dsi_device *dsi = ctx->dsi;
|
||||
struct mipi_dsi_multi_context dsi_ctx = { .dsi = dsi };
|
||||
|
||||
mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
|
||||
mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->unprepare_delay);
|
||||
mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
|
||||
mipi_dsi_msleep(&dsi_ctx, ctx->panel_info->disable_delay);
|
||||
|
||||
gpiod_set_value_cansleep(ctx->enable_gpio, 0);
|
||||
|
||||
gpiod_set_value_cansleep(ctx->reset_gpio, 1);
|
||||
|
||||
regulator_disable(ctx->vdd);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int panel_anbernic_td4310_get_mode(struct drm_panel *panel,
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
|
||||
const struct anbernic_panel_td4310_info *panel_info = ctx->panel_info;
|
||||
|
||||
connector->display_info.bpc = 8;
|
||||
connector->display_info.width_mm = panel_info->width_mm;
|
||||
connector->display_info.height_mm = panel_info->height_mm;
|
||||
connector->display_info.bus_flags = panel_info->bus_flags;
|
||||
|
||||
return drm_connector_helper_get_modes_fixed(connector, panel_info->display_mode);
|
||||
}
|
||||
|
||||
static enum drm_panel_orientation panel_anbernic_td4310_get_orientation(struct drm_panel *panel)
|
||||
{
|
||||
struct anbernic_panel_td4310 *ctx = panel_to_anbernic_panel_td4310(panel);
|
||||
|
||||
return ctx->orientation;
|
||||
}
|
||||
|
||||
static const struct drm_panel_funcs panel_anbernic_td4310_funcs = {
|
||||
.prepare = panel_anbernic_td4310_prepare,
|
||||
.unprepare = panel_anbernic_td4310_unprepare,
|
||||
.get_modes = panel_anbernic_td4310_get_mode,
|
||||
.get_orientation = panel_anbernic_td4310_get_orientation,
|
||||
};
|
||||
|
||||
static int panel_anbernic_td4310_probe(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
struct device *dev = &dsi->dev;
|
||||
struct anbernic_panel_td4310 *ctx;
|
||||
int ret;
|
||||
|
||||
ctx = devm_drm_panel_alloc(dev, struct anbernic_panel_td4310, panel,
|
||||
&panel_anbernic_td4310_funcs,
|
||||
DRM_MODE_CONNECTOR_DSI);
|
||||
if (IS_ERR(ctx))
|
||||
return PTR_ERR(ctx);
|
||||
|
||||
ctx->dev = dev;
|
||||
|
||||
ctx->panel_info = of_device_get_match_data(dev);
|
||||
if (!ctx->panel_info)
|
||||
return -EINVAL;
|
||||
|
||||
ret = of_drm_get_panel_orientation(dev->of_node, &ctx->orientation);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to get panel orientation\n");
|
||||
|
||||
ctx->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(ctx->reset_gpio))
|
||||
return dev_err_probe(dev, PTR_ERR(ctx->reset_gpio),
|
||||
"Cannot get reset gpio\n");
|
||||
|
||||
ctx->enable_gpio = devm_gpiod_get_optional(dev, "enable", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(ctx->enable_gpio))
|
||||
return dev_err_probe(dev, PTR_ERR(ctx->enable_gpio),
|
||||
"Cannot get enable gpio\n");
|
||||
|
||||
ctx->vdd = devm_regulator_get(dev, "vdd");
|
||||
if (IS_ERR(ctx->vdd))
|
||||
return dev_err_probe(dev, PTR_ERR(ctx->vdd),
|
||||
"Failed to request vdd regulator\n");
|
||||
|
||||
ctx->dsi = dsi;
|
||||
mipi_dsi_set_drvdata(dsi, ctx);
|
||||
|
||||
dsi->lanes = ctx->panel_info->lanes;
|
||||
dsi->format = ctx->panel_info->format;
|
||||
dsi->mode_flags = ctx->panel_info->mode_flags;
|
||||
|
||||
ret = drm_panel_of_backlight(&ctx->panel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
devm_drm_panel_add(dev, &ctx->panel);
|
||||
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to attach to DSI host\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct drm_display_mode anbernic_vitapro_mode = {
|
||||
.clock = 140020,
|
||||
.hdisplay = 1080,
|
||||
.hsync_start = 1080 + 50,
|
||||
.hsync_end = 1080 + 50 + 4,
|
||||
.htotal = 1080 + 50 + 4 + 50,
|
||||
.vdisplay = 1920,
|
||||
.vsync_start = 1920 + 15,
|
||||
.vsync_end = 1920 + 15 + 4,
|
||||
.vtotal = 1920 + 15 + 4 + 32,
|
||||
.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
|
||||
};
|
||||
|
||||
static const struct anbernic_panel_td4310_info anbernic_vitapro_info = {
|
||||
.display_mode = &anbernic_vitapro_mode,
|
||||
.width_mm = 69,
|
||||
.height_mm = 121,
|
||||
.bus_flags = DRM_BUS_FLAG_DE_LOW | DRM_BUS_FLAG_PIXDATA_DRIVE_NEGEDGE,
|
||||
.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_BURST |
|
||||
MIPI_DSI_MODE_LPM | MIPI_DSI_MODE_NO_EOT_PACKET |
|
||||
MIPI_DSI_CLOCK_NON_CONTINUOUS,
|
||||
.format = MIPI_DSI_FMT_RGB888,
|
||||
.lanes = 4,
|
||||
.prepare_delay = 50,
|
||||
.reset_delay = 220,
|
||||
.enable_delay = 120,
|
||||
.disable_delay = 50,
|
||||
.unprepare_delay = 20,
|
||||
};
|
||||
|
||||
static const struct of_device_id panel_anbernic_td4310_of_match[] = {
|
||||
{
|
||||
.compatible = "anbernic,panel-vita-pro",
|
||||
.data = &anbernic_vitapro_info,
|
||||
},
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, panel_anbernic_td4310_of_match);
|
||||
|
||||
static struct mipi_dsi_driver anbernic_panel_td4310_driver = {
|
||||
.driver = {
|
||||
.name = "panel-anbernic-td4310",
|
||||
.of_match_table = panel_anbernic_td4310_of_match,
|
||||
},
|
||||
.probe = panel_anbernic_td4310_probe,
|
||||
};
|
||||
module_mipi_dsi_driver(anbernic_panel_td4310_driver);
|
||||
|
||||
MODULE_AUTHOR("Chris Morgan <macromorgan@hotmail.com>");
|
||||
MODULE_DESCRIPTION("DRM driver for Anbernic TD4310 MIPI DSI panels");
|
||||
MODULE_LICENSE("GPL");
|
||||
422
drivers/gpu/drm/panel/panel-chipone-icna35xx.c
Normal file
422
drivers/gpu/drm/panel/panel-chipone-icna35xx.c
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-only
|
||||
/*
|
||||
* Chipone ICNA35XX Driver IC panels driver
|
||||
*
|
||||
* Copyright (c) 2025 Teguh Sobirin <teguh@sobir.in>
|
||||
*/
|
||||
|
||||
#include <linux/backlight.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <video/mipi_display.h>
|
||||
|
||||
#include <drm/display/drm_dsc.h>
|
||||
#include <drm/display/drm_dsc_helper.h>
|
||||
#include <drm/drm_connector.h>
|
||||
#include <drm/drm_crtc.h>
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_modes.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
struct panel_info {
|
||||
struct drm_panel panel;
|
||||
struct drm_connector *connector;
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct panel_desc *desc;
|
||||
enum drm_panel_orientation orientation;
|
||||
|
||||
struct gpio_desc *reset_gpio;
|
||||
struct regulator_bulk_data *supplies;
|
||||
};
|
||||
|
||||
struct panel_desc {
|
||||
unsigned int width_mm;
|
||||
unsigned int height_mm;
|
||||
|
||||
unsigned int bpc;
|
||||
unsigned int lanes;
|
||||
unsigned long mode_flags;
|
||||
enum mipi_dsi_pixel_format format;
|
||||
|
||||
const struct drm_display_mode *modes;
|
||||
unsigned int num_modes;
|
||||
int (*init_sequence)(struct panel_info *pinfo);
|
||||
|
||||
struct drm_dsc_config dsc;
|
||||
};
|
||||
|
||||
static const struct regulator_bulk_data panel_supplies[] = {
|
||||
{ .supply = "vdd" },
|
||||
{ .supply = "vddio" },
|
||||
{ .supply = "vci" },
|
||||
{ .supply = "disp" },
|
||||
{ .supply = "blvdd" },
|
||||
};
|
||||
|
||||
static inline struct panel_info *to_panel_info(struct drm_panel *panel)
|
||||
{
|
||||
return container_of(panel, struct panel_info, panel);
|
||||
}
|
||||
|
||||
static int icna3512_init_sequence(struct panel_info *pinfo)
|
||||
{
|
||||
struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi };
|
||||
struct drm_dsc_picture_parameter_set pps;
|
||||
|
||||
pinfo->dsi->mode_flags |= MIPI_DSI_MODE_LPM;
|
||||
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9C, 0xA5, 0xA5);
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xFD, 0x5A, 0x5A);
|
||||
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x53, 0xE0);
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x35, 0x00);
|
||||
|
||||
mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
|
||||
|
||||
mipi_dsi_msleep(&dsi_ctx, 120);
|
||||
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x0F);
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xCE, 0x22);
|
||||
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x01);
|
||||
|
||||
/* 165 hz */
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x20);
|
||||
|
||||
drm_dsc_pps_payload_pack(&pps, &pinfo->desc->dsc);
|
||||
mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps);
|
||||
|
||||
mipi_dsi_msleep(&dsi_ctx, 20);
|
||||
|
||||
mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
|
||||
|
||||
return dsi_ctx.accum_err;
|
||||
}
|
||||
|
||||
static int icna3520_init_sequence(struct panel_info *pinfo)
|
||||
{
|
||||
struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi };
|
||||
struct drm_dsc_picture_parameter_set pps;
|
||||
|
||||
pinfo->dsi->mode_flags |= MIPI_DSI_MODE_LPM;
|
||||
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9C, 0xA5, 0xA5);
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xFD, 0x5A, 0x5A);
|
||||
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x53, 0xE0);
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x35, 0x00);
|
||||
|
||||
mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
|
||||
|
||||
mipi_dsi_msleep(&dsi_ctx, 120);
|
||||
|
||||
/* 120 hz */
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x48, 0x00);
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x00);
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB3,
|
||||
0x00, 0xD8, 0x00, 0x1C, 0x00, 0x4C);
|
||||
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x01);
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, 0x00);
|
||||
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0x9F, 0x0D);
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, 0x27);
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB6, 0x03);
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xBB, 0x01);
|
||||
mipi_dsi_generic_write_seq_multi(&dsi_ctx, 0xB2, 0x24);
|
||||
|
||||
drm_dsc_pps_payload_pack(&pps, &pinfo->desc->dsc);
|
||||
mipi_dsi_picture_parameter_set_multi(&dsi_ctx, &pps);
|
||||
|
||||
mipi_dsi_msleep(&dsi_ctx, 20);
|
||||
|
||||
mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
|
||||
|
||||
return dsi_ctx.accum_err;
|
||||
}
|
||||
|
||||
static const struct drm_display_mode odin2portal_modes[] = {
|
||||
{
|
||||
/* 165Hz */
|
||||
.clock = (1080 + 98 + 1 + 23) * (1920 + 20 + 1 + 15) * 165 / 1000,
|
||||
.hdisplay = 1080,
|
||||
.hsync_start = 1080 + 98,
|
||||
.hsync_end = 1080 + 98 + 1,
|
||||
.htotal = 1080 + 98 + 1 + 23,
|
||||
.vdisplay = 1920,
|
||||
.vsync_start = 1920 + 20,
|
||||
.vsync_end = 1920 + 20 + 1,
|
||||
.vtotal = 1920 + 20 + 1 + 15,
|
||||
}
|
||||
};
|
||||
|
||||
static const struct drm_display_mode thor_top_modes[] = {
|
||||
{
|
||||
/* 120Hz */
|
||||
.clock = (1080 + 24 + 1 + 24) * (1920 + 28 + 1 + 28) * 120 / 1000,
|
||||
.hdisplay = 1080,
|
||||
.hsync_start = 1080 + 24,
|
||||
.hsync_end = 1080 + 24 + 1,
|
||||
.htotal = 1080 + 24 + 1 + 24,
|
||||
.vdisplay = 1920,
|
||||
.vsync_start = 1920 + 28,
|
||||
.vsync_end = 1920 + 28 + 1,
|
||||
.vtotal = 1920 + 28 + 1 + 28,
|
||||
}
|
||||
};
|
||||
|
||||
static struct panel_desc odin2portal_desc = {
|
||||
.modes = odin2portal_modes,
|
||||
.num_modes = ARRAY_SIZE(odin2portal_modes),
|
||||
.width_mm = 160,
|
||||
.height_mm = 89,
|
||||
.bpc = 8,
|
||||
.lanes = 4,
|
||||
.format = MIPI_DSI_FMT_RGB888,
|
||||
.mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_CLOCK_NON_CONTINUOUS |
|
||||
MIPI_DSI_MODE_LPM,
|
||||
.init_sequence = icna3512_init_sequence,
|
||||
.dsc = {
|
||||
.dsc_version_major = 0x1,
|
||||
.dsc_version_minor = 0x1,
|
||||
.slice_height = 20,
|
||||
.slice_width = 540,
|
||||
.slice_count = 2,
|
||||
.bits_per_component = 8,
|
||||
.bits_per_pixel = 8 << 4,
|
||||
.block_pred_enable = true,
|
||||
},
|
||||
};
|
||||
|
||||
static struct panel_desc thor_top_desc = {
|
||||
.modes = thor_top_modes,
|
||||
.num_modes = ARRAY_SIZE(thor_top_modes),
|
||||
.width_mm = 136,
|
||||
.height_mm = 68,
|
||||
.bpc = 8,
|
||||
.lanes = 4,
|
||||
.format = MIPI_DSI_FMT_RGB888,
|
||||
.mode_flags = MIPI_DSI_MODE_NO_EOT_PACKET | MIPI_DSI_CLOCK_NON_CONTINUOUS |
|
||||
MIPI_DSI_MODE_LPM,
|
||||
.init_sequence = icna3520_init_sequence,
|
||||
.dsc = {
|
||||
.dsc_version_major = 0x1,
|
||||
.dsc_version_minor = 0x1,
|
||||
.slice_height = 12,
|
||||
.slice_width = 540,
|
||||
.slice_count = 2,
|
||||
.bits_per_component = 8,
|
||||
.bits_per_pixel = 8 << 4,
|
||||
.block_pred_enable = true,
|
||||
},
|
||||
};
|
||||
|
||||
static void icna35xx_reset(struct panel_info *pinfo)
|
||||
{
|
||||
gpiod_set_value_cansleep(pinfo->reset_gpio, 0);
|
||||
usleep_range(20000, 21000);
|
||||
gpiod_set_value_cansleep(pinfo->reset_gpio, 1);
|
||||
usleep_range(20000, 21000);
|
||||
gpiod_set_value_cansleep(pinfo->reset_gpio, 0);
|
||||
usleep_range(20000, 21000);
|
||||
}
|
||||
|
||||
static int icna35xx_prepare(struct drm_panel *panel)
|
||||
{
|
||||
struct panel_info *pinfo = to_panel_info(panel);
|
||||
int ret;
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(panel_supplies), pinfo->supplies);
|
||||
if (ret < 0) {
|
||||
dev_err(panel->dev, "failed to enable regulators: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
icna35xx_reset(pinfo);
|
||||
|
||||
ret = pinfo->desc->init_sequence(pinfo);
|
||||
if (ret < 0) {
|
||||
regulator_bulk_disable(ARRAY_SIZE(panel_supplies), pinfo->supplies);
|
||||
dev_err(panel->dev, "failed to initialize panel: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int icna35xx_disable(struct drm_panel *panel)
|
||||
{
|
||||
struct panel_info *pinfo = to_panel_info(panel);
|
||||
struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->dsi };
|
||||
|
||||
pinfo->dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
|
||||
|
||||
mipi_dsi_dcs_set_display_off_multi(&dsi_ctx);
|
||||
mipi_dsi_msleep(&dsi_ctx, 50);
|
||||
mipi_dsi_dcs_enter_sleep_mode_multi(&dsi_ctx);
|
||||
mipi_dsi_msleep(&dsi_ctx, 120);
|
||||
|
||||
return dsi_ctx.accum_err;
|
||||
}
|
||||
|
||||
static int icna35xx_unprepare(struct drm_panel *panel)
|
||||
{
|
||||
struct panel_info *pinfo = to_panel_info(panel);
|
||||
|
||||
gpiod_set_value_cansleep(pinfo->reset_gpio, 1);
|
||||
regulator_bulk_disable(ARRAY_SIZE(panel_supplies), pinfo->supplies);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int icna35xx_get_modes(struct drm_panel *panel,
|
||||
struct drm_connector *connector)
|
||||
{
|
||||
struct panel_info *pinfo = to_panel_info(panel);
|
||||
|
||||
return drm_connector_helper_get_modes_fixed(connector, pinfo->desc->modes);
|
||||
}
|
||||
|
||||
static enum drm_panel_orientation icna35xx_get_orientation(struct drm_panel *panel)
|
||||
{
|
||||
struct panel_info *pinfo = to_panel_info(panel);
|
||||
|
||||
return pinfo->orientation;
|
||||
}
|
||||
|
||||
static const struct drm_panel_funcs icna35xx_panel_funcs = {
|
||||
.disable = icna35xx_disable,
|
||||
.prepare = icna35xx_prepare,
|
||||
.unprepare = icna35xx_unprepare,
|
||||
.get_modes = icna35xx_get_modes,
|
||||
.get_orientation = icna35xx_get_orientation,
|
||||
};
|
||||
|
||||
static int icna35xx_bl_update_status(struct backlight_device *bl)
|
||||
{
|
||||
struct mipi_dsi_device *dsi = bl_get_data(bl);
|
||||
u16 brightness = backlight_get_brightness(bl);
|
||||
int ret;
|
||||
|
||||
dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
|
||||
|
||||
ret = mipi_dsi_dcs_set_display_brightness_large(dsi, brightness);
|
||||
|
||||
dsi->mode_flags |= MIPI_DSI_MODE_LPM;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int icna35xx_bl_get_brightness(struct backlight_device *bl)
|
||||
{
|
||||
struct mipi_dsi_device *dsi = bl_get_data(bl);
|
||||
u16 brightness;
|
||||
int ret;
|
||||
|
||||
dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
|
||||
|
||||
ret = mipi_dsi_dcs_get_display_brightness_large(dsi, &brightness);
|
||||
|
||||
dsi->mode_flags |= MIPI_DSI_MODE_LPM;
|
||||
|
||||
return ret < 0 ? ret : brightness;
|
||||
}
|
||||
|
||||
static const struct backlight_ops icna35xx_bl_ops = {
|
||||
.update_status = icna35xx_bl_update_status,
|
||||
.get_brightness = icna35xx_bl_get_brightness,
|
||||
};
|
||||
|
||||
static struct backlight_device *icna35xx_create_backlight(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
struct device *dev = &dsi->dev;
|
||||
const struct backlight_properties props = {
|
||||
.type = BACKLIGHT_RAW,
|
||||
.brightness = 4096,
|
||||
.max_brightness = 4096,
|
||||
};
|
||||
|
||||
return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
|
||||
&icna35xx_bl_ops, &props);
|
||||
}
|
||||
|
||||
static int icna35xx_probe(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
struct device *dev = &dsi->dev;
|
||||
struct panel_info *pinfo;
|
||||
int ret;
|
||||
|
||||
pinfo = devm_drm_panel_alloc(dev, __typeof(*pinfo), panel,
|
||||
&icna35xx_panel_funcs,
|
||||
DRM_MODE_CONNECTOR_DSI);
|
||||
if (IS_ERR(pinfo))
|
||||
return PTR_ERR(pinfo);
|
||||
|
||||
ret = devm_regulator_bulk_get_const(dev, ARRAY_SIZE(panel_supplies),
|
||||
panel_supplies, &pinfo->supplies);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "Failed to get regulators\n");
|
||||
|
||||
pinfo->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(pinfo->reset_gpio))
|
||||
return dev_err_probe(dev, PTR_ERR(pinfo->reset_gpio), "failed to get reset gpio\n");
|
||||
|
||||
pinfo->desc = (struct panel_desc *)of_device_get_match_data(dev);
|
||||
if (!pinfo->desc)
|
||||
return -ENODEV;
|
||||
|
||||
pinfo->dsi = dsi;
|
||||
mipi_dsi_set_drvdata(dsi, pinfo);
|
||||
|
||||
ret = of_drm_get_panel_orientation(dev->of_node, &pinfo->orientation);
|
||||
if (ret < 0) {
|
||||
dev_err(dev, "%pOF: failed to get orientation %d\n", dev->of_node, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
pinfo->panel.prepare_prev_first = true;
|
||||
|
||||
pinfo->panel.backlight = icna35xx_create_backlight(dsi);
|
||||
if (IS_ERR(pinfo->panel.backlight))
|
||||
return dev_err_probe(dev, PTR_ERR(pinfo->panel.backlight),
|
||||
"Failed to create backlight\n");
|
||||
|
||||
ret = devm_drm_panel_add(dev, &pinfo->panel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
pinfo->dsi->lanes = pinfo->desc->lanes;
|
||||
pinfo->dsi->format = pinfo->desc->format;
|
||||
pinfo->dsi->mode_flags = pinfo->desc->mode_flags;
|
||||
pinfo->dsi->dsc = &pinfo->desc->dsc;
|
||||
|
||||
return devm_mipi_dsi_attach(dev, dsi);
|
||||
}
|
||||
|
||||
static const struct of_device_id icna35xx_of_match[] = {
|
||||
{ .compatible = "ayaneo,pocketds-panel-top", .data = &odin2portal_desc },
|
||||
{ .compatible = "ayntec,odin2portal-panel", .data = &odin2portal_desc },
|
||||
{ .compatible = "ayntec,odin3-panel", .data = &thor_top_desc },
|
||||
{ .compatible = "ayntec,thor-panel-top", .data = &thor_top_desc },
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(of, icna35xx_of_match);
|
||||
|
||||
static struct mipi_dsi_driver icna35xx_driver = {
|
||||
.probe = icna35xx_probe,
|
||||
.driver = {
|
||||
.name = "panel-chipone-icna35xx",
|
||||
.of_match_table = icna35xx_of_match,
|
||||
},
|
||||
};
|
||||
module_mipi_dsi_driver(icna35xx_driver);
|
||||
|
||||
MODULE_AUTHOR("Teguh Sobirin <teguh@sobir.in>");
|
||||
MODULE_DESCRIPTION("DRM driver for Chipone ICNA35XX based MIPI DSI panels");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -1855,6 +1855,14 @@ static const struct panel_delay delay_200_500_e50_po2e200 = {
|
|||
.powered_on_to_enable = 200,
|
||||
};
|
||||
|
||||
static const struct panel_delay delay_200_500_e50_po2e200_d100 = {
|
||||
.hpd_absent = 200,
|
||||
.unprepare = 500,
|
||||
.enable = 50,
|
||||
.powered_on_to_enable = 200,
|
||||
.disable = 100,
|
||||
};
|
||||
|
||||
static const struct panel_delay delay_200_150_e50 = {
|
||||
.hpd_absent = 200,
|
||||
.unprepare = 150,
|
||||
|
|
@ -1934,6 +1942,7 @@ static const struct edp_panel_entry edp_panels[] = {
|
|||
EDP_PANEL_ENTRY('A', 'U', 'O', 0x235c, &delay_200_500_e50, "B116XTN02.3"),
|
||||
EDP_PANEL_ENTRY('A', 'U', 'O', 0x239b, &delay_200_500_e50, "B116XAN06.1"),
|
||||
EDP_PANEL_ENTRY('A', 'U', 'O', 0x255c, &delay_200_500_e50, "B116XTN02.5"),
|
||||
EDP_PANEL_ENTRY('A', 'U', 'O', 0x29c0, &delay_200_500_e50, "B116XAT04.3"),
|
||||
EDP_PANEL_ENTRY('A', 'U', 'O', 0x30ed, &delay_200_500_e50, "G156HAN03.0"),
|
||||
EDP_PANEL_ENTRY('A', 'U', 'O', 0x3c9f, &delay_200_500_e50, "B140HAK03.5"),
|
||||
EDP_PANEL_ENTRY('A', 'U', 'O', 0x403d, &delay_200_500_e50, "B140HAN04.0"),
|
||||
|
|
@ -2036,8 +2045,13 @@ static const struct edp_panel_entry edp_panels[] = {
|
|||
EDP_PANEL_ENTRY('B', 'O', 'E', 0x0cfa, &delay_200_500_e50, "NV116WHM-A4D"),
|
||||
EDP_PANEL_ENTRY('B', 'O', 'E', 0x0d45, &delay_200_500_e80, "NV116WHM-N4B"),
|
||||
EDP_PANEL_ENTRY('B', 'O', 'E', 0x0d73, &delay_200_500_e80, "NE140WUM-N6S"),
|
||||
EDP_PANEL_ENTRY('B', 'O', 'E', 0x0d98, &delay_200_500_e50_po2e200_d100, "NV140FHM-N5B"),
|
||||
EDP_PANEL_ENTRY('B', 'O', 'E', 0x0db3, &delay_200_500_e80, "NV153WUM-N42"),
|
||||
EDP_PANEL_ENTRY('B', 'O', 'E', 0x0ddf, &delay_200_500_e80, "NV116WHM-T01"),
|
||||
EDP_PANEL_ENTRY('B', 'O', 'E', 0x386e, &delay_200_500_e80, "NV116WH2-M30"),
|
||||
EDP_PANEL_ENTRY('B', 'O', 'E', 0x3879, &delay_200_500_e80, "NT116WHM-N21"),
|
||||
EDP_PANEL_ENTRY('B', 'O', 'E', 0x388b, &delay_200_500_e80, "NV116FH1-M31"),
|
||||
EDP_PANEL_ENTRY('B', 'O', 'E', 0x388c, &delay_200_500_e80, "NV116FH1-M30"),
|
||||
|
||||
EDP_PANEL_ENTRY('C', 'M', 'N', 0x1130, &delay_200_500_e50, "N116BGE-EB2"),
|
||||
EDP_PANEL_ENTRY('C', 'M', 'N', 0x1132, &delay_200_500_e80_d50, "N116BGE-EA2"),
|
||||
|
|
@ -2060,6 +2074,7 @@ static const struct edp_panel_entry edp_panels[] = {
|
|||
EDP_PANEL_ENTRY('C', 'M', 'N', 0x1161, &delay_200_500_e80, "N116BCP-EA2"),
|
||||
EDP_PANEL_ENTRY('C', 'M', 'N', 0x1163, &delay_200_500_e80_d50, "N116BCJ-EAK"),
|
||||
EDP_PANEL_ENTRY('C', 'M', 'N', 0x1169, &delay_200_500_e80_d50, "N116BCN-EA1"),
|
||||
EDP_PANEL_ENTRY('C', 'M', 'N', 0x116d, &delay_200_500_e80_d50, "N116BCP-EA2"),
|
||||
EDP_PANEL_ENTRY('C', 'M', 'N', 0x117a, &delay_200_500_e80_d50, "N116BCL-EAK"),
|
||||
EDP_PANEL_ENTRY('C', 'M', 'N', 0x1247, &delay_200_500_e80_d50, "N120ACA-EA1"),
|
||||
EDP_PANEL_ENTRY('C', 'M', 'N', 0x124c, &delay_200_500_e80_d50, "N122JCA-ENK"),
|
||||
|
|
@ -2086,6 +2101,7 @@ static const struct edp_panel_entry edp_panels[] = {
|
|||
EDP_PANEL_ENTRY('C', 'S', 'W', 0x1103, &delay_200_500_e80_d50, "MNB601LS1-3"),
|
||||
EDP_PANEL_ENTRY('C', 'S', 'W', 0x1104, &delay_200_500_e50_d100, "MNB601LS1-4"),
|
||||
EDP_PANEL_ENTRY('C', 'S', 'W', 0x110a, &delay_200_500_e50, "PNB601LS1-2"),
|
||||
EDP_PANEL_ENTRY('C', 'S', 'W', 0x110d, &delay_200_500_e50, "MNB601LS1-8"),
|
||||
EDP_PANEL_ENTRY('C', 'S', 'W', 0x143f, &delay_200_500_e50, "MNE007QS3-6"),
|
||||
EDP_PANEL_ENTRY('C', 'S', 'W', 0x1448, &delay_200_500_e50, "MNE007QS3-7"),
|
||||
EDP_PANEL_ENTRY('C', 'S', 'W', 0x144b, &delay_200_500_e80, "MNE001BS1-4"),
|
||||
|
|
@ -2151,6 +2167,7 @@ static const struct edp_panel_entry edp_panels[] = {
|
|||
|
||||
EDP_PANEL_ENTRY('T', 'M', 'A', 0x0811, &delay_200_500_e80_d50, "TM140VDXP01-04"),
|
||||
EDP_PANEL_ENTRY('T', 'M', 'A', 0x2094, &delay_200_500_e50_d100, "TL140VDMS03-01"),
|
||||
EDP_PANEL_ENTRY('T', 'M', 'A', 0x2139, &delay_200_500_e50_d100, "TM156VDXP25"),
|
||||
|
||||
{ /* sentinal */ }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,7 +33,9 @@ struct himax {
|
|||
struct drm_dsc_config dsc;
|
||||
struct gpio_desc *reset_gpio;
|
||||
struct regulator_bulk_data *supplies;
|
||||
struct regulator *bl_supply;
|
||||
struct backlight_device *backlight;
|
||||
bool backlight_enabled;
|
||||
};
|
||||
|
||||
struct panel_desc {
|
||||
|
|
@ -192,10 +194,31 @@ static const struct drm_panel_funcs himax_panel_funcs = {
|
|||
|
||||
static int himax_bl_update_status(struct backlight_device *bl)
|
||||
{
|
||||
struct mipi_dsi_device *dsi = bl_get_data(bl);
|
||||
struct himax *ctx = bl_get_data(bl);
|
||||
u16 brightness = backlight_get_brightness(bl);
|
||||
int ret = 0;
|
||||
|
||||
if (!brightness) {
|
||||
if (ctx->backlight_enabled)
|
||||
ret = regulator_disable(ctx->bl_supply);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ctx->backlight_enabled = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* TODO: brightness to raw map table */
|
||||
return mipi_dsi_dcs_set_display_brightness_large(dsi, brightness);
|
||||
if (!ctx->backlight_enabled)
|
||||
ret = regulator_enable(ctx->bl_supply);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ctx->backlight_enabled = true;
|
||||
|
||||
return mipi_dsi_dcs_set_display_brightness_large(to_primary_dsi(ctx),
|
||||
brightness);
|
||||
}
|
||||
|
||||
static const struct backlight_ops himax_bl_ops = {
|
||||
|
|
@ -204,9 +227,9 @@ static const struct backlight_ops himax_bl_ops = {
|
|||
};
|
||||
|
||||
static struct backlight_device *
|
||||
himax_create_backlight(struct mipi_dsi_device *dsi)
|
||||
himax_create_backlight(struct himax *ctx)
|
||||
{
|
||||
struct device *dev = &dsi->dev;
|
||||
struct device *dev = &to_primary_dsi(ctx)->dev;
|
||||
const struct backlight_properties props = {
|
||||
.type = BACKLIGHT_RAW,
|
||||
.brightness = 512,
|
||||
|
|
@ -214,7 +237,7 @@ himax_create_backlight(struct mipi_dsi_device *dsi)
|
|||
.scale = BACKLIGHT_SCALE_NON_LINEAR,
|
||||
};
|
||||
|
||||
return devm_backlight_device_register(dev, dev_name(dev), dev, dsi,
|
||||
return devm_backlight_device_register(dev, dev_name(dev), dev, ctx,
|
||||
&himax_bl_ops, &props);
|
||||
}
|
||||
|
||||
|
|
@ -645,7 +668,11 @@ static int himax_probe(struct mipi_dsi_device *dsi)
|
|||
ctx->panel.prepare_prev_first = true;
|
||||
|
||||
if (desc->has_dcs_backlight) {
|
||||
ctx->backlight = himax_create_backlight(to_primary_dsi(ctx));
|
||||
ctx->bl_supply = devm_regulator_get_optional(dev, "bl");
|
||||
if (IS_ERR(ctx->bl_supply))
|
||||
return dev_err_probe(dev, PTR_ERR(ctx->bl_supply),
|
||||
"Failed to get backlight supply\n");
|
||||
ctx->backlight = himax_create_backlight(ctx);
|
||||
if (IS_ERR(ctx->backlight))
|
||||
return dev_err_probe(dev, PTR_ERR(ctx->backlight),
|
||||
"Failed to create backlight\n");
|
||||
|
|
|
|||
289
drivers/gpu/drm/panel/panel-ilitek-ili9488.c
Normal file
289
drivers/gpu/drm/panel/panel-ilitek-ili9488.c
Normal file
|
|
@ -0,0 +1,289 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
|
||||
#include <drm/drm_mipi_dsi.h>
|
||||
#include <drm/drm_modes.h>
|
||||
#include <drm/drm_panel.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
|
||||
#include <video/mipi_display.h>
|
||||
|
||||
struct ili9488_desc {
|
||||
const struct drm_display_mode *display_mode;
|
||||
unsigned long mode_flags;
|
||||
enum mipi_dsi_pixel_format format;
|
||||
unsigned int lanes;
|
||||
unsigned int bpc;
|
||||
void (*init_sequence)(struct mipi_dsi_multi_context *ctx);
|
||||
};
|
||||
|
||||
struct ili9488 {
|
||||
struct drm_panel panel;
|
||||
struct mipi_dsi_device *dsi;
|
||||
struct gpio_desc *reset;
|
||||
struct regulator_bulk_data supplies[2];
|
||||
const struct ili9488_desc *desc;
|
||||
enum drm_panel_orientation orientation;
|
||||
};
|
||||
|
||||
static const char * const regulator_names[] = {
|
||||
"vci",
|
||||
"iovcc",
|
||||
};
|
||||
|
||||
static void e35gh_i_mw800cb_init(struct mipi_dsi_multi_context *ctx)
|
||||
{
|
||||
/* Gamma control 1,2 */
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0xE0, 0x00, 0x10, 0x14, 0x01, 0x0E, 0x04, 0x33,
|
||||
0x56, 0x48, 0x03, 0x0C, 0x0B, 0x2B, 0x34, 0x0F);
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0xE1, 0x00, 0x12, 0x18, 0x05, 0x12, 0x06, 0x40,
|
||||
0x34, 0x57, 0x06, 0x10, 0x0C, 0x3B, 0x3F, 0x0F);
|
||||
/* Power control 1,2 */
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0xC0, 0x0F, 0x0C);
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0xC1, 0x41);
|
||||
/* VCOM Control */
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0xC5, 0x00, 0x25, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0x36, 0x48);
|
||||
/* Interface pixel format 18bpp */
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0x3A, 0x66);
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0xB0, 0x00);
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0xB1, 0xA0);
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0xB4, 0x02);
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0xB6, 0x02, 0x02, 0x3B);
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0xE9, 0x00);
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0xF7, 0xA9, 0x51, 0x2C, 0x82);
|
||||
mipi_dsi_dcs_write_seq_multi(ctx, 0x21);
|
||||
}
|
||||
|
||||
static const struct drm_display_mode e35gh_i_mw800cb_display_mode = {
|
||||
.clock = 14400,
|
||||
|
||||
.hdisplay = 320,
|
||||
.hsync_start = 320 + 60,
|
||||
.hsync_end = 320 + 60 + 20,
|
||||
.htotal = 320 + 60 + 20 + 42,
|
||||
|
||||
.vdisplay = 480,
|
||||
.vsync_start = 480 + 20,
|
||||
.vsync_end = 480 + 20 + 10,
|
||||
.vtotal = 480 + 20 + 10 + 33,
|
||||
|
||||
.width_mm = 48,
|
||||
.height_mm = 73,
|
||||
|
||||
.flags = DRM_MODE_FLAG_NHSYNC | DRM_MODE_FLAG_NVSYNC,
|
||||
.type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED,
|
||||
};
|
||||
|
||||
static inline struct ili9488 *panel_to_ili9488(struct drm_panel *panel)
|
||||
{
|
||||
return container_of(panel, struct ili9488, panel);
|
||||
}
|
||||
|
||||
static int ili9488_power_on(struct ili9488 *ili)
|
||||
{
|
||||
struct mipi_dsi_device *dsi = ili->dsi;
|
||||
int ret;
|
||||
|
||||
ret = regulator_bulk_enable(ARRAY_SIZE(ili->supplies), ili->supplies);
|
||||
if (ret < 0) {
|
||||
dev_err(&dsi->dev, "regulator bulk enable failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
gpiod_set_value_cansleep(ili->reset, 0);
|
||||
usleep_range(1000, 5000);
|
||||
gpiod_set_value_cansleep(ili->reset, 1);
|
||||
usleep_range(1000, 5000);
|
||||
gpiod_set_value_cansleep(ili->reset, 0);
|
||||
usleep_range(5000, 10000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ili9488_power_off(struct ili9488 *ili)
|
||||
{
|
||||
struct mipi_dsi_device *dsi = ili->dsi;
|
||||
int ret;
|
||||
|
||||
gpiod_set_value_cansleep(ili->reset, 1);
|
||||
|
||||
ret = regulator_bulk_disable(ARRAY_SIZE(ili->supplies), ili->supplies);
|
||||
if (ret)
|
||||
dev_err(&dsi->dev, "regulator bulk disable failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ili9488_activate(struct ili9488 *ili)
|
||||
{
|
||||
struct mipi_dsi_multi_context ctx = { .dsi = ili->dsi };
|
||||
|
||||
if (ili->desc->init_sequence)
|
||||
ili->desc->init_sequence(&ctx);
|
||||
|
||||
mipi_dsi_dcs_exit_sleep_mode_multi(&ctx);
|
||||
mipi_dsi_msleep(&ctx, 120);
|
||||
mipi_dsi_dcs_set_display_on_multi(&ctx);
|
||||
|
||||
return ctx.accum_err;
|
||||
}
|
||||
|
||||
static int ili9488_prepare(struct drm_panel *panel)
|
||||
{
|
||||
struct ili9488 *ili = panel_to_ili9488(panel);
|
||||
int ret;
|
||||
|
||||
ret = ili9488_power_on(ili);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = ili9488_activate(ili);
|
||||
if (ret) {
|
||||
ili9488_power_off(ili);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ili9488_deactivate(struct ili9488 *ili)
|
||||
{
|
||||
struct mipi_dsi_multi_context ctx = { .dsi = ili->dsi };
|
||||
|
||||
mipi_dsi_dcs_set_display_off_multi(&ctx);
|
||||
mipi_dsi_dcs_enter_sleep_mode_multi(&ctx);
|
||||
mipi_dsi_msleep(&ctx, 120);
|
||||
|
||||
return ctx.accum_err;
|
||||
}
|
||||
|
||||
static int ili9488_unprepare(struct drm_panel *panel)
|
||||
{
|
||||
struct ili9488 *ili = panel_to_ili9488(panel);
|
||||
struct mipi_dsi_device *dsi = ili->dsi;
|
||||
int ret;
|
||||
|
||||
ili9488_deactivate(ili);
|
||||
ret = ili9488_power_off(ili);
|
||||
if (ret < 0)
|
||||
dev_err(&dsi->dev, "power off failed: %d\n", ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int ili9488_get_modes(struct drm_panel *panel, struct drm_connector *connector)
|
||||
{
|
||||
struct ili9488 *ili = panel_to_ili9488(panel);
|
||||
const struct drm_display_mode *mode = ili->desc->display_mode;
|
||||
|
||||
connector->display_info.bpc = ili->desc->bpc;
|
||||
|
||||
return drm_connector_helper_get_modes_fixed(connector, mode);
|
||||
}
|
||||
|
||||
static enum drm_panel_orientation ili9488_get_orientation(struct drm_panel *panel)
|
||||
{
|
||||
struct ili9488 *ili = panel_to_ili9488(panel);
|
||||
|
||||
return ili->orientation;
|
||||
}
|
||||
|
||||
static const struct drm_panel_funcs ili9488_funcs = {
|
||||
.prepare = ili9488_prepare,
|
||||
.unprepare = ili9488_unprepare,
|
||||
.get_modes = ili9488_get_modes,
|
||||
.get_orientation = ili9488_get_orientation,
|
||||
};
|
||||
|
||||
static int ili9488_dsi_probe(struct mipi_dsi_device *dsi)
|
||||
{
|
||||
struct device *dev = &dsi->dev;
|
||||
struct ili9488 *ili;
|
||||
int i, ret;
|
||||
|
||||
ili = devm_drm_panel_alloc(dev, struct ili9488, panel, &ili9488_funcs,
|
||||
DRM_MODE_CONNECTOR_DSI);
|
||||
if (IS_ERR(ili))
|
||||
return PTR_ERR(ili);
|
||||
|
||||
ili->desc = device_get_match_data(dev);
|
||||
ili->dsi = dsi;
|
||||
|
||||
dsi->mode_flags = ili->desc->mode_flags;
|
||||
dsi->format = ili->desc->format;
|
||||
dsi->lanes = ili->desc->lanes;
|
||||
|
||||
ili->reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(ili->reset))
|
||||
return dev_err_probe(dev, PTR_ERR(ili->reset),
|
||||
"failed to get reset-gpios\n");
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ili->supplies); i++)
|
||||
ili->supplies[i].supply = regulator_names[i];
|
||||
|
||||
ret = devm_regulator_bulk_get(dev, ARRAY_SIZE(ili->supplies),
|
||||
ili->supplies);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "failed to get regulators\n");
|
||||
|
||||
ret = of_drm_get_panel_orientation(dev->of_node, &ili->orientation);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to get orientation\n");
|
||||
|
||||
ret = drm_panel_of_backlight(&ili->panel);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to get backlight\n");
|
||||
|
||||
ili->panel.prepare_prev_first = true;
|
||||
|
||||
ret = devm_drm_panel_add(dev, &ili->panel);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_mipi_dsi_attach(dev, dsi);
|
||||
if (ret < 0)
|
||||
return dev_err_probe(dev, ret, "failed to attach to DSI host\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct ili9488_desc e35gh_i_mw800cb_desc = {
|
||||
.init_sequence = e35gh_i_mw800cb_init,
|
||||
.display_mode = &e35gh_i_mw800cb_display_mode,
|
||||
.mode_flags = MIPI_DSI_MODE_VIDEO | MIPI_DSI_MODE_VIDEO_SYNC_PULSE |
|
||||
MIPI_DSI_MODE_LPM | MIPI_DSI_CLOCK_NON_CONTINUOUS,
|
||||
.format = MIPI_DSI_FMT_RGB666_PACKED,
|
||||
.lanes = 1,
|
||||
.bpc = 6,
|
||||
};
|
||||
|
||||
static const struct of_device_id ili9488_of_match[] = {
|
||||
{ .compatible = "focuslcds,e35gh-i-mw800cb", .data = &e35gh_i_mw800cb_desc },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, ili9488_of_match);
|
||||
|
||||
static struct mipi_dsi_driver ili9488_dsi_driver = {
|
||||
.probe = ili9488_dsi_probe,
|
||||
.driver = {
|
||||
.name = "ili9488-dsi",
|
||||
.of_match_table = ili9488_of_match,
|
||||
},
|
||||
};
|
||||
module_mipi_dsi_driver(ili9488_dsi_driver);
|
||||
|
||||
MODULE_AUTHOR("Igor Reznichenko <igor@reznichenko.net>");
|
||||
MODULE_DESCRIPTION("Ilitek ILI9488 Controller Driver");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -28,10 +28,6 @@
|
|||
|
||||
#include <video/mipi_display.h>
|
||||
|
||||
struct nt36672a_panel_cmd {
|
||||
const char data[2];
|
||||
};
|
||||
|
||||
static const char * const nt36672a_regulator_names[] = {
|
||||
"vddio",
|
||||
"vddpos",
|
||||
|
|
@ -55,13 +51,9 @@ struct nt36672a_panel_desc {
|
|||
enum mipi_dsi_pixel_format format;
|
||||
unsigned int lanes;
|
||||
|
||||
unsigned int num_on_cmds_1;
|
||||
const struct nt36672a_panel_cmd *on_cmds_1;
|
||||
unsigned int num_on_cmds_2;
|
||||
const struct nt36672a_panel_cmd *on_cmds_2;
|
||||
|
||||
unsigned int num_off_cmds;
|
||||
const struct nt36672a_panel_cmd *off_cmds;
|
||||
void (*send_init_cmds_1)(struct mipi_dsi_multi_context *dsi_ctx);
|
||||
void (*send_init_cmds_2)(struct mipi_dsi_multi_context *dsi_ctx);
|
||||
void (*send_deinit_cmds)(struct mipi_dsi_multi_context *dsi_ctx);
|
||||
};
|
||||
|
||||
struct nt36672a_panel {
|
||||
|
|
@ -79,19 +71,6 @@ static inline struct nt36672a_panel *to_nt36672a_panel(struct drm_panel *panel)
|
|||
return container_of(panel, struct nt36672a_panel, base);
|
||||
}
|
||||
|
||||
static void nt36672a_send_cmds(struct mipi_dsi_multi_context *dsi_ctx,
|
||||
const struct nt36672a_panel_cmd *cmds, int num)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = 0; i < num; i++) {
|
||||
const struct nt36672a_panel_cmd *cmd = &cmds[i];
|
||||
|
||||
/* cmd->data[0] is the DCS command, cmd->data[1] is the parameter */
|
||||
mipi_dsi_dcs_write_buffer_multi(dsi_ctx, cmd->data, sizeof(cmd->data));
|
||||
}
|
||||
}
|
||||
|
||||
static void nt36672a_panel_power_off(struct drm_panel *panel)
|
||||
{
|
||||
struct nt36672a_panel *pinfo = to_nt36672a_panel(panel);
|
||||
|
|
@ -110,8 +89,8 @@ static int nt36672a_panel_unprepare(struct drm_panel *panel)
|
|||
struct mipi_dsi_multi_context dsi_ctx = { .dsi = pinfo->link };
|
||||
|
||||
/* send off cmds */
|
||||
nt36672a_send_cmds(&dsi_ctx, pinfo->desc->off_cmds,
|
||||
pinfo->desc->num_off_cmds);
|
||||
if (pinfo->desc->send_deinit_cmds)
|
||||
pinfo->desc->send_deinit_cmds(&dsi_ctx);
|
||||
|
||||
/* Reset error to continue with display off even if send_cmds failed */
|
||||
dsi_ctx.accum_err = 0;
|
||||
|
|
@ -162,8 +141,8 @@ static int nt36672a_panel_prepare(struct drm_panel *panel)
|
|||
dsi_ctx.accum_err = nt36672a_panel_power_on(pinfo);
|
||||
|
||||
/* send first part of init cmds */
|
||||
nt36672a_send_cmds(&dsi_ctx, pinfo->desc->on_cmds_1,
|
||||
pinfo->desc->num_on_cmds_1);
|
||||
if (pinfo->desc->send_init_cmds_1)
|
||||
pinfo->desc->send_init_cmds_1(&dsi_ctx);
|
||||
|
||||
mipi_dsi_dcs_exit_sleep_mode_multi(&dsi_ctx);
|
||||
|
||||
|
|
@ -173,8 +152,8 @@ static int nt36672a_panel_prepare(struct drm_panel *panel)
|
|||
mipi_dsi_dcs_set_display_on_multi(&dsi_ctx);
|
||||
|
||||
/* Send rest of the init cmds */
|
||||
nt36672a_send_cmds(&dsi_ctx, pinfo->desc->on_cmds_2,
|
||||
pinfo->desc->num_on_cmds_2);
|
||||
if (pinfo->desc->send_init_cmds_2)
|
||||
pinfo->desc->send_init_cmds_2(&dsi_ctx);
|
||||
|
||||
mipi_dsi_msleep(&dsi_ctx, 120);
|
||||
|
||||
|
|
@ -213,310 +192,184 @@ static const struct drm_panel_funcs panel_funcs = {
|
|||
.get_modes = nt36672a_panel_get_modes,
|
||||
};
|
||||
|
||||
static const struct nt36672a_panel_cmd tianma_fhd_video_on_cmds_1[] = {
|
||||
static void tianma_fhd_video_send_init_cmds_1(struct mipi_dsi_multi_context *dsi_ctx)
|
||||
{
|
||||
u8 reg;
|
||||
|
||||
/* skin enhancement mode */
|
||||
{ .data = {0xFF, 0x22} },
|
||||
{ .data = {0x00, 0x40} },
|
||||
{ .data = {0x01, 0xC0} },
|
||||
{ .data = {0x02, 0x40} },
|
||||
{ .data = {0x03, 0x40} },
|
||||
{ .data = {0x04, 0x40} },
|
||||
{ .data = {0x05, 0x40} },
|
||||
{ .data = {0x06, 0x40} },
|
||||
{ .data = {0x07, 0x40} },
|
||||
{ .data = {0x08, 0x40} },
|
||||
{ .data = {0x09, 0x40} },
|
||||
{ .data = {0x0A, 0x40} },
|
||||
{ .data = {0x0B, 0x40} },
|
||||
{ .data = {0x0C, 0x40} },
|
||||
{ .data = {0x0D, 0x40} },
|
||||
{ .data = {0x0E, 0x40} },
|
||||
{ .data = {0x0F, 0x40} },
|
||||
{ .data = {0x10, 0x40} },
|
||||
{ .data = {0x11, 0x50} },
|
||||
{ .data = {0x12, 0x60} },
|
||||
{ .data = {0x13, 0x70} },
|
||||
{ .data = {0x14, 0x58} },
|
||||
{ .data = {0x15, 0x68} },
|
||||
{ .data = {0x16, 0x78} },
|
||||
{ .data = {0x17, 0x77} },
|
||||
{ .data = {0x18, 0x39} },
|
||||
{ .data = {0x19, 0x2D} },
|
||||
{ .data = {0x1A, 0x2E} },
|
||||
{ .data = {0x1B, 0x32} },
|
||||
{ .data = {0x1C, 0x37} },
|
||||
{ .data = {0x1D, 0x3A} },
|
||||
{ .data = {0x1E, 0x40} },
|
||||
{ .data = {0x1F, 0x40} },
|
||||
{ .data = {0x20, 0x40} },
|
||||
{ .data = {0x21, 0x40} },
|
||||
{ .data = {0x22, 0x40} },
|
||||
{ .data = {0x23, 0x40} },
|
||||
{ .data = {0x24, 0x40} },
|
||||
{ .data = {0x25, 0x40} },
|
||||
{ .data = {0x26, 0x40} },
|
||||
{ .data = {0x27, 0x40} },
|
||||
{ .data = {0x28, 0x40} },
|
||||
{ .data = {0x2D, 0x00} },
|
||||
{ .data = {0x2F, 0x40} },
|
||||
{ .data = {0x30, 0x40} },
|
||||
{ .data = {0x31, 0x40} },
|
||||
{ .data = {0x32, 0x40} },
|
||||
{ .data = {0x33, 0x40} },
|
||||
{ .data = {0x34, 0x40} },
|
||||
{ .data = {0x35, 0x40} },
|
||||
{ .data = {0x36, 0x40} },
|
||||
{ .data = {0x37, 0x40} },
|
||||
{ .data = {0x38, 0x40} },
|
||||
{ .data = {0x39, 0x40} },
|
||||
{ .data = {0x3A, 0x40} },
|
||||
{ .data = {0x3B, 0x40} },
|
||||
{ .data = {0x3D, 0x40} },
|
||||
{ .data = {0x3F, 0x40} },
|
||||
{ .data = {0x40, 0x40} },
|
||||
{ .data = {0x41, 0x40} },
|
||||
{ .data = {0x42, 0x40} },
|
||||
{ .data = {0x43, 0x40} },
|
||||
{ .data = {0x44, 0x40} },
|
||||
{ .data = {0x45, 0x40} },
|
||||
{ .data = {0x46, 0x40} },
|
||||
{ .data = {0x47, 0x40} },
|
||||
{ .data = {0x48, 0x40} },
|
||||
{ .data = {0x49, 0x40} },
|
||||
{ .data = {0x4A, 0x40} },
|
||||
{ .data = {0x4B, 0x40} },
|
||||
{ .data = {0x4C, 0x40} },
|
||||
{ .data = {0x4D, 0x40} },
|
||||
{ .data = {0x4E, 0x40} },
|
||||
{ .data = {0x4F, 0x40} },
|
||||
{ .data = {0x50, 0x40} },
|
||||
{ .data = {0x51, 0x40} },
|
||||
{ .data = {0x52, 0x40} },
|
||||
{ .data = {0x53, 0x01} },
|
||||
{ .data = {0x54, 0x01} },
|
||||
{ .data = {0x55, 0xFE} },
|
||||
{ .data = {0x56, 0x77} },
|
||||
{ .data = {0x58, 0xCD} },
|
||||
{ .data = {0x59, 0xD0} },
|
||||
{ .data = {0x5A, 0xD0} },
|
||||
{ .data = {0x5B, 0x50} },
|
||||
{ .data = {0x5C, 0x50} },
|
||||
{ .data = {0x5D, 0x50} },
|
||||
{ .data = {0x5E, 0x50} },
|
||||
{ .data = {0x5F, 0x50} },
|
||||
{ .data = {0x60, 0x50} },
|
||||
{ .data = {0x61, 0x50} },
|
||||
{ .data = {0x62, 0x50} },
|
||||
{ .data = {0x63, 0x50} },
|
||||
{ .data = {0x64, 0x50} },
|
||||
{ .data = {0x65, 0x50} },
|
||||
{ .data = {0x66, 0x50} },
|
||||
{ .data = {0x67, 0x50} },
|
||||
{ .data = {0x68, 0x50} },
|
||||
{ .data = {0x69, 0x50} },
|
||||
{ .data = {0x6A, 0x50} },
|
||||
{ .data = {0x6B, 0x50} },
|
||||
{ .data = {0x6C, 0x50} },
|
||||
{ .data = {0x6D, 0x50} },
|
||||
{ .data = {0x6E, 0x50} },
|
||||
{ .data = {0x6F, 0x50} },
|
||||
{ .data = {0x70, 0x07} },
|
||||
{ .data = {0x71, 0x00} },
|
||||
{ .data = {0x72, 0x00} },
|
||||
{ .data = {0x73, 0x00} },
|
||||
{ .data = {0x74, 0x06} },
|
||||
{ .data = {0x75, 0x0C} },
|
||||
{ .data = {0x76, 0x03} },
|
||||
{ .data = {0x77, 0x09} },
|
||||
{ .data = {0x78, 0x0F} },
|
||||
{ .data = {0x79, 0x68} },
|
||||
{ .data = {0x7A, 0x88} },
|
||||
{ .data = {0x7C, 0x80} },
|
||||
{ .data = {0x7D, 0x80} },
|
||||
{ .data = {0x7E, 0x80} },
|
||||
{ .data = {0x7F, 0x00} },
|
||||
{ .data = {0x80, 0x00} },
|
||||
{ .data = {0x81, 0x00} },
|
||||
{ .data = {0x83, 0x01} },
|
||||
{ .data = {0x84, 0x00} },
|
||||
{ .data = {0x85, 0x80} },
|
||||
{ .data = {0x86, 0x80} },
|
||||
{ .data = {0x87, 0x80} },
|
||||
{ .data = {0x88, 0x40} },
|
||||
{ .data = {0x89, 0x91} },
|
||||
{ .data = {0x8A, 0x98} },
|
||||
{ .data = {0x8B, 0x80} },
|
||||
{ .data = {0x8C, 0x80} },
|
||||
{ .data = {0x8D, 0x80} },
|
||||
{ .data = {0x8E, 0x80} },
|
||||
{ .data = {0x8F, 0x80} },
|
||||
{ .data = {0x90, 0x80} },
|
||||
{ .data = {0x91, 0x80} },
|
||||
{ .data = {0x92, 0x80} },
|
||||
{ .data = {0x93, 0x80} },
|
||||
{ .data = {0x94, 0x80} },
|
||||
{ .data = {0x95, 0x80} },
|
||||
{ .data = {0x96, 0x80} },
|
||||
{ .data = {0x97, 0x80} },
|
||||
{ .data = {0x98, 0x80} },
|
||||
{ .data = {0x99, 0x80} },
|
||||
{ .data = {0x9A, 0x80} },
|
||||
{ .data = {0x9B, 0x80} },
|
||||
{ .data = {0x9C, 0x80} },
|
||||
{ .data = {0x9D, 0x80} },
|
||||
{ .data = {0x9E, 0x80} },
|
||||
{ .data = {0x9F, 0x80} },
|
||||
{ .data = {0xA0, 0x8A} },
|
||||
{ .data = {0xA2, 0x80} },
|
||||
{ .data = {0xA6, 0x80} },
|
||||
{ .data = {0xA7, 0x80} },
|
||||
{ .data = {0xA9, 0x80} },
|
||||
{ .data = {0xAA, 0x80} },
|
||||
{ .data = {0xAB, 0x80} },
|
||||
{ .data = {0xAC, 0x80} },
|
||||
{ .data = {0xAD, 0x80} },
|
||||
{ .data = {0xAE, 0x80} },
|
||||
{ .data = {0xAF, 0x80} },
|
||||
{ .data = {0xB7, 0x76} },
|
||||
{ .data = {0xB8, 0x76} },
|
||||
{ .data = {0xB9, 0x05} },
|
||||
{ .data = {0xBA, 0x0D} },
|
||||
{ .data = {0xBB, 0x14} },
|
||||
{ .data = {0xBC, 0x0F} },
|
||||
{ .data = {0xBD, 0x18} },
|
||||
{ .data = {0xBE, 0x1F} },
|
||||
{ .data = {0xBF, 0x05} },
|
||||
{ .data = {0xC0, 0x0D} },
|
||||
{ .data = {0xC1, 0x14} },
|
||||
{ .data = {0xC2, 0x03} },
|
||||
{ .data = {0xC3, 0x07} },
|
||||
{ .data = {0xC4, 0x0A} },
|
||||
{ .data = {0xC5, 0xA0} },
|
||||
{ .data = {0xC6, 0x55} },
|
||||
{ .data = {0xC7, 0xFF} },
|
||||
{ .data = {0xC8, 0x39} },
|
||||
{ .data = {0xC9, 0x44} },
|
||||
{ .data = {0xCA, 0x12} },
|
||||
{ .data = {0xCD, 0x80} },
|
||||
{ .data = {0xDB, 0x80} },
|
||||
{ .data = {0xDC, 0x80} },
|
||||
{ .data = {0xDD, 0x80} },
|
||||
{ .data = {0xE0, 0x80} },
|
||||
{ .data = {0xE1, 0x80} },
|
||||
{ .data = {0xE2, 0x80} },
|
||||
{ .data = {0xE3, 0x80} },
|
||||
{ .data = {0xE4, 0x80} },
|
||||
{ .data = {0xE5, 0x40} },
|
||||
{ .data = {0xE6, 0x40} },
|
||||
{ .data = {0xE7, 0x40} },
|
||||
{ .data = {0xE8, 0x40} },
|
||||
{ .data = {0xE9, 0x40} },
|
||||
{ .data = {0xEA, 0x40} },
|
||||
{ .data = {0xEB, 0x40} },
|
||||
{ .data = {0xEC, 0x40} },
|
||||
{ .data = {0xED, 0x40} },
|
||||
{ .data = {0xEE, 0x40} },
|
||||
{ .data = {0xEF, 0x40} },
|
||||
{ .data = {0xF0, 0x40} },
|
||||
{ .data = {0xF1, 0x40} },
|
||||
{ .data = {0xF2, 0x40} },
|
||||
{ .data = {0xF3, 0x40} },
|
||||
{ .data = {0xF4, 0x40} },
|
||||
{ .data = {0xF5, 0x40} },
|
||||
{ .data = {0xF6, 0x40} },
|
||||
{ .data = {0xFB, 0x1} },
|
||||
{ .data = {0xFF, 0x23} },
|
||||
{ .data = {0xFB, 0x01} },
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x22);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x00, 0x40);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x01, 0xc0);
|
||||
for (reg = 0x02; reg <= 0x10; reg++)
|
||||
mipi_dsi_dcs_write_var_seq_multi(dsi_ctx, reg, 0x40);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x11, 0x50);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x12, 0x60);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x13, 0x70);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x14, 0x58);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x15, 0x68);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x16, 0x78);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x17, 0x77);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x18, 0x39);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x19, 0x2d);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x1a, 0x2e);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x1b, 0x32);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x1c, 0x37);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x1d, 0x3a);
|
||||
for (reg = 0x1e; reg <= 0x28; reg++)
|
||||
mipi_dsi_dcs_write_var_seq_multi(dsi_ctx, reg, 0x40);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x2d, 0x00);
|
||||
for (reg = 0x2f; reg <= 0x3b; reg++)
|
||||
mipi_dsi_dcs_write_var_seq_multi(dsi_ctx, reg, 0x40);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x3d, 0x40);
|
||||
for (reg = 0x3f; reg <= 0x52; reg++)
|
||||
mipi_dsi_dcs_write_var_seq_multi(dsi_ctx, reg, 0x40);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x53, 0x01);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x54, 0x01);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x55, 0xfe);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x56, 0x77);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x58, 0xcd);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x59, 0xd0);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x5a, 0xd0);
|
||||
for (reg = 0x5b; reg <= 0x6f; reg++)
|
||||
mipi_dsi_dcs_write_var_seq_multi(dsi_ctx, reg, 0x50);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x70, 0x07);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x71, 0x00);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x72, 0x00);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x73, 0x00);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x74, 0x06);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x75, 0x0c);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x76, 0x03);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x77, 0x09);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x78, 0x0f);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x79, 0x68);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x7a, 0x88);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x7c, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x7d, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x7e, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x7f, 0x00);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x80, 0x00);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x81, 0x00);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x83, 0x01);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x84, 0x00);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x85, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x86, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x87, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x88, 0x40);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x89, 0x91);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x8a, 0x98);
|
||||
for (reg = 0x8b; reg <= 0x9f; reg++)
|
||||
mipi_dsi_dcs_write_var_seq_multi(dsi_ctx, reg, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xa0, 0x8a);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xa2, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xa6, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xa7, 0x80);
|
||||
for (reg = 0xa9; reg <= 0xaf; reg++)
|
||||
mipi_dsi_dcs_write_var_seq_multi(dsi_ctx, reg, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb7, 0x76);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb8, 0x76);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xb9, 0x05);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xba, 0x0d);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbb, 0x14);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbc, 0x0f);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbd, 0x18);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbe, 0x1f);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xbf, 0x05);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc0, 0x0d);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc1, 0x14);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc2, 0x03);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc3, 0x07);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc4, 0x0a);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc5, 0xa0);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc6, 0x55);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc7, 0xff);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc8, 0x39);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc9, 0x44);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xca, 0x12);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xcd, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xdb, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xdc, 0x80);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xdd, 0x80);
|
||||
for (reg = 0xe0; reg <= 0xe4; reg++)
|
||||
mipi_dsi_dcs_write_var_seq_multi(dsi_ctx, reg, 0x80);
|
||||
for (reg = 0xe5; reg <= 0xf6; reg++)
|
||||
mipi_dsi_dcs_write_var_seq_multi(dsi_ctx, reg, 0x40);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x23);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01);
|
||||
/* dimming enable */
|
||||
{ .data = {0x01, 0x84} },
|
||||
{ .data = {0x05, 0x2D} },
|
||||
{ .data = {0x06, 0x00} },
|
||||
/* resolution 1080*2246 */
|
||||
{ .data = {0x11, 0x01} },
|
||||
{ .data = {0x12, 0x7B} },
|
||||
{ .data = {0x15, 0x6F} },
|
||||
{ .data = {0x16, 0x0B} },
|
||||
/* UI mode */
|
||||
{ .data = {0x29, 0x0A} },
|
||||
{ .data = {0x30, 0xFF} },
|
||||
{ .data = {0x31, 0xFF} },
|
||||
{ .data = {0x32, 0xFF} },
|
||||
{ .data = {0x33, 0xFF} },
|
||||
{ .data = {0x34, 0xFF} },
|
||||
{ .data = {0x35, 0xFF} },
|
||||
{ .data = {0x36, 0xFF} },
|
||||
{ .data = {0x37, 0xFF} },
|
||||
{ .data = {0x38, 0xFC} },
|
||||
{ .data = {0x39, 0xF8} },
|
||||
{ .data = {0x3A, 0xF4} },
|
||||
{ .data = {0x3B, 0xF1} },
|
||||
{ .data = {0x3D, 0xEE} },
|
||||
{ .data = {0x3F, 0xEB} },
|
||||
{ .data = {0x40, 0xE8} },
|
||||
{ .data = {0x41, 0xE5} },
|
||||
/* STILL mode */
|
||||
{ .data = {0x2A, 0x13} },
|
||||
{ .data = {0x45, 0xFF} },
|
||||
{ .data = {0x46, 0xFF} },
|
||||
{ .data = {0x47, 0xFF} },
|
||||
{ .data = {0x48, 0xFF} },
|
||||
{ .data = {0x49, 0xFF} },
|
||||
{ .data = {0x4A, 0xFF} },
|
||||
{ .data = {0x4B, 0xFF} },
|
||||
{ .data = {0x4C, 0xFF} },
|
||||
{ .data = {0x4D, 0xED} },
|
||||
{ .data = {0x4E, 0xD5} },
|
||||
{ .data = {0x4F, 0xBF} },
|
||||
{ .data = {0x50, 0xA6} },
|
||||
{ .data = {0x51, 0x96} },
|
||||
{ .data = {0x52, 0x86} },
|
||||
{ .data = {0x53, 0x76} },
|
||||
{ .data = {0x54, 0x66} },
|
||||
/* MOVING mode */
|
||||
{ .data = {0x2B, 0x0E} },
|
||||
{ .data = {0x58, 0xFF} },
|
||||
{ .data = {0x59, 0xFF} },
|
||||
{ .data = {0x5A, 0xFF} },
|
||||
{ .data = {0x5B, 0xFF} },
|
||||
{ .data = {0x5C, 0xFF} },
|
||||
{ .data = {0x5D, 0xFF} },
|
||||
{ .data = {0x5E, 0xFF} },
|
||||
{ .data = {0x5F, 0xFF} },
|
||||
{ .data = {0x60, 0xF6} },
|
||||
{ .data = {0x61, 0xEA} },
|
||||
{ .data = {0x62, 0xE1} },
|
||||
{ .data = {0x63, 0xD8} },
|
||||
{ .data = {0x64, 0xCE} },
|
||||
{ .data = {0x65, 0xC3} },
|
||||
{ .data = {0x66, 0xBA} },
|
||||
{ .data = {0x67, 0xB3} },
|
||||
{ .data = {0xFF, 0x25} },
|
||||
{ .data = {0xFB, 0x01} },
|
||||
{ .data = {0x05, 0x04} },
|
||||
{ .data = {0xFF, 0x26} },
|
||||
{ .data = {0xFB, 0x01} },
|
||||
{ .data = {0x1C, 0xAF} },
|
||||
{ .data = {0xFF, 0x10} },
|
||||
{ .data = {0xFB, 0x01} },
|
||||
{ .data = {0x51, 0xFF} },
|
||||
{ .data = {0x53, 0x24} },
|
||||
{ .data = {0x55, 0x00} },
|
||||
};
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x01, 0x84);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x05, 0x2d);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x06, 0x00);
|
||||
/* resolution 1080*2246 */
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x11, 0x01);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x12, 0x7b);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x15, 0x6f);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x16, 0x0b);
|
||||
/* UI mode */
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x29, 0x0a);
|
||||
for (reg = 0x30; reg <= 0x37; reg++)
|
||||
mipi_dsi_dcs_write_var_seq_multi(dsi_ctx, reg, 0xff);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x38, 0xfc);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x39, 0xf8);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x3a, 0xf4);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x3b, 0xf1);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x3d, 0xee);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x3f, 0xeb);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x40, 0xe8);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x41, 0xe5);
|
||||
/* STILL mode */
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x2a, 0x13);
|
||||
for (reg = 0x45; reg <= 0x4c; reg++)
|
||||
mipi_dsi_dcs_write_var_seq_multi(dsi_ctx, reg, 0xff);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x4d, 0xed);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x4e, 0xd5);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x4f, 0xbf);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x50, 0xa6);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x51, 0x96);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x52, 0x86);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x53, 0x76);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x54, 0x66);
|
||||
/* MOVING mode */
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x2b, 0x0e);
|
||||
for (reg = 0x58; reg <= 0x5f; reg++)
|
||||
mipi_dsi_dcs_write_var_seq_multi(dsi_ctx, reg, 0xff);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x60, 0xf6);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x61, 0xea);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x62, 0xe1);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x63, 0xd8);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x64, 0xce);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x65, 0xc3);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x66, 0xba);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x67, 0xb3);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x25);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x05, 0x04);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x26);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x1c, 0xaf);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x10);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x51, 0xff);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x53, 0x24);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0x55, 0x00);
|
||||
}
|
||||
|
||||
static const struct nt36672a_panel_cmd tianma_fhd_video_on_cmds_2[] = {
|
||||
{ .data = {0xFF, 0x24} },
|
||||
{ .data = {0xFB, 0x01} },
|
||||
{ .data = {0xC3, 0x01} },
|
||||
{ .data = {0xC4, 0x54} },
|
||||
{ .data = {0xFF, 0x10} },
|
||||
};
|
||||
static void tianma_fhd_video_send_init_cmds_2(struct mipi_dsi_multi_context *dsi_ctx)
|
||||
{
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x24);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc3, 0x01);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc4, 0x54);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x10);
|
||||
}
|
||||
|
||||
static const struct nt36672a_panel_cmd tianma_fhd_video_off_cmds[] = {
|
||||
{ .data = {0xFF, 0x24} },
|
||||
{ .data = {0xFB, 0x01} },
|
||||
{ .data = {0xC3, 0x01} },
|
||||
{ .data = {0xFF, 0x10} },
|
||||
};
|
||||
static void tianma_fhd_video_send_deinit_cmds(struct mipi_dsi_multi_context *dsi_ctx)
|
||||
{
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x24);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xfb, 0x01);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xc3, 0x01);
|
||||
mipi_dsi_dcs_write_seq_multi(dsi_ctx, 0xff, 0x10);
|
||||
}
|
||||
|
||||
static const struct drm_display_mode tianma_fhd_video_panel_default_mode = {
|
||||
.clock = 161331,
|
||||
|
|
@ -546,12 +399,9 @@ static const struct nt36672a_panel_desc tianma_fhd_video_panel_desc = {
|
|||
| MIPI_DSI_MODE_VIDEO_BURST,
|
||||
.format = MIPI_DSI_FMT_RGB888,
|
||||
.lanes = 4,
|
||||
.on_cmds_1 = tianma_fhd_video_on_cmds_1,
|
||||
.num_on_cmds_1 = ARRAY_SIZE(tianma_fhd_video_on_cmds_1),
|
||||
.on_cmds_2 = tianma_fhd_video_on_cmds_2,
|
||||
.num_on_cmds_2 = ARRAY_SIZE(tianma_fhd_video_on_cmds_2),
|
||||
.off_cmds = tianma_fhd_video_off_cmds,
|
||||
.num_off_cmds = ARRAY_SIZE(tianma_fhd_video_off_cmds),
|
||||
.send_init_cmds_1 = tianma_fhd_video_send_init_cmds_1,
|
||||
.send_init_cmds_2 = tianma_fhd_video_send_init_cmds_2,
|
||||
.send_deinit_cmds = tianma_fhd_video_send_deinit_cmds,
|
||||
};
|
||||
|
||||
static int nt36672a_panel_add(struct nt36672a_panel *pinfo)
|
||||
|
|
|
|||
|
|
@ -161,6 +161,9 @@ struct panthor_device {
|
|||
/** @csif_info: Command stream interface information. */
|
||||
struct drm_panthor_csif_info csif_info;
|
||||
|
||||
/** @mmu_info: MMU info */
|
||||
struct drm_panthor_mmu_info mmu_info;
|
||||
|
||||
/** @hw: GPU-specific data. */
|
||||
struct panthor_hw *hw;
|
||||
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ panthor_get_uobj_array(const struct drm_panthor_obj_array *in, u32 min_stride,
|
|||
_Generic(_obj_name, \
|
||||
PANTHOR_UOBJ_DECL(struct drm_panthor_gpu_info, tiler_present), \
|
||||
PANTHOR_UOBJ_DECL(struct drm_panthor_csif_info, pad), \
|
||||
PANTHOR_UOBJ_DECL(struct drm_panthor_mmu_info, page_size_bitmap), \
|
||||
PANTHOR_UOBJ_DECL(struct drm_panthor_timestamp_info, current_timestamp), \
|
||||
PANTHOR_UOBJ_DECL(struct drm_panthor_group_priorities_info, pad), \
|
||||
PANTHOR_UOBJ_DECL(struct drm_panthor_sync_op, timeline_value), \
|
||||
|
|
@ -954,6 +955,10 @@ static int panthor_ioctl_dev_query(struct drm_device *ddev, void *data, struct d
|
|||
args->size = sizeof(priorities_info);
|
||||
return 0;
|
||||
|
||||
case DRM_PANTHOR_DEV_QUERY_MMU_INFO:
|
||||
args->size = sizeof(ptdev->mmu_info);
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
@ -984,6 +989,9 @@ static int panthor_ioctl_dev_query(struct drm_device *ddev, void *data, struct d
|
|||
panthor_query_group_priorities_info(file, &priorities_info);
|
||||
return PANTHOR_UOBJ_SET(args->pointer, args->size, priorities_info);
|
||||
|
||||
case DRM_PANTHOR_DEV_QUERY_MMU_INFO:
|
||||
return PANTHOR_UOBJ_SET(args->pointer, args->size, ptdev->mmu_info);
|
||||
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
|
@ -1779,10 +1787,12 @@ static void panthor_debugfs_init(struct drm_minor *minor)
|
|||
* - adds DRM_IOCTL_PANTHOR_BO_QUERY_INFO ioctl
|
||||
* - adds drm_panthor_gpu_info::selected_coherency
|
||||
* - 1.8 - extends DEV_QUERY_TIMESTAMP_INFO with flags
|
||||
* - 1.9 - adds DRM_PANTHOR_DEV_QUERY_MMU_INFO query
|
||||
* - adds DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE flag
|
||||
*/
|
||||
static const struct drm_driver panthor_drm_driver = {
|
||||
.driver_features = DRIVER_RENDER | DRIVER_GEM | DRIVER_SYNCOBJ |
|
||||
DRIVER_SYNCOBJ_TIMELINE | DRIVER_GEM_GPUVA,
|
||||
DRIVER_SYNCOBJ_TIMELINE,
|
||||
.open = panthor_open,
|
||||
.postclose = panthor_postclose,
|
||||
.show_fdinfo = panthor_show_fdinfo,
|
||||
|
|
@ -1792,7 +1802,7 @@ static const struct drm_driver panthor_drm_driver = {
|
|||
.name = "panthor",
|
||||
.desc = "Panthor DRM driver",
|
||||
.major = 1,
|
||||
.minor = 8,
|
||||
.minor = 9,
|
||||
|
||||
.gem_prime_import_sg_table = panthor_gem_prime_import_sg_table,
|
||||
.gem_prime_import = panthor_gem_prime_import,
|
||||
|
|
|
|||
|
|
@ -1347,6 +1347,24 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
|
|||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
/**
|
||||
* panthor_dummy_bo_create() - Create a Panthor BO meant to back sparse bindings.
|
||||
* @ptdev: Device.
|
||||
*
|
||||
* Return: A valid pointer in case of success, an ERR_PTR() otherwise.
|
||||
*/
|
||||
struct panthor_gem_object *
|
||||
panthor_dummy_bo_create(struct panthor_device *ptdev)
|
||||
{
|
||||
/* Since even when the DRM device's mount point has enabled THP we have no guarantee
|
||||
* that drm_gem_get_pages() will return a single 2MiB PMD, and also we cannot be sure
|
||||
* that the 2MiB won't be reclaimed and re-allocated later on as 4KiB chunks, it doesn't
|
||||
* make sense to pre-populate this object's page array, nor to fall back on a BO size
|
||||
* of 4KiB. Sticking to a dummy object size of 2MiB lets us keep things simple for now.
|
||||
*/
|
||||
return panthor_gem_create(&ptdev->base, SZ_2M, DRM_PANTHOR_BO_NO_MMAP, NULL, 0);
|
||||
}
|
||||
|
||||
static bool can_swap(void)
|
||||
{
|
||||
return get_nr_swap_pages() > 0;
|
||||
|
|
|
|||
|
|
@ -325,6 +325,8 @@ panthor_kernel_bo_create(struct panthor_device *ptdev, struct panthor_vm *vm,
|
|||
|
||||
void panthor_kernel_bo_destroy(struct panthor_kernel_bo *bo);
|
||||
|
||||
struct panthor_gem_object *panthor_dummy_bo_create(struct panthor_device *ptdev);
|
||||
|
||||
#ifdef CONFIG_DEBUG_FS
|
||||
void panthor_gem_debugfs_init(struct drm_minor *minor);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -116,6 +116,17 @@ struct panthor_mmu {
|
|||
struct panthor_vm_pool {
|
||||
/** @xa: Array used for VM handle tracking. */
|
||||
struct xarray xa;
|
||||
|
||||
/**
|
||||
* @dummy: Dummy object used for sparse mappings
|
||||
*
|
||||
* Sparse bindings map virtual address ranges onto a dummy
|
||||
* BO in a modulo fashion. Even though sparse writes are meant
|
||||
* to be discarded and reads undefined, writes are still reflected
|
||||
* in the dummy buffer. That means we must keep a dummy object per
|
||||
* file context, to avoid data leaks between them.
|
||||
*/
|
||||
struct panthor_gem_object *dummy;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -199,14 +210,6 @@ struct panthor_vm_op_ctx {
|
|||
/** @map.bo_offset: Offset in the buffer object. */
|
||||
u64 bo_offset;
|
||||
|
||||
/**
|
||||
* @map.sgt: sg-table pointing to pages backing the GEM object.
|
||||
*
|
||||
* This is gathered at job creation time, such that we don't have
|
||||
* to allocate in ::run_job().
|
||||
*/
|
||||
struct sg_table *sgt;
|
||||
|
||||
/** @map.bo: the BO being mapped. */
|
||||
struct panthor_gem_object *bo;
|
||||
} map;
|
||||
|
|
@ -403,6 +406,15 @@ struct panthor_vm {
|
|||
*/
|
||||
struct list_head lru_node;
|
||||
} reclaim;
|
||||
|
||||
/**
|
||||
* @dummy: Dummy object used for sparse mappings.
|
||||
*
|
||||
* VM's must keep a reference to the file context-wide dummy BO because
|
||||
* they can outlive the file context, which includes the VM pool holding
|
||||
* the original dummy BO reference.
|
||||
*/
|
||||
struct panthor_gem_object *dummy;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -1035,6 +1047,30 @@ panthor_vm_map_pages(struct panthor_vm *vm, u64 iova, int prot,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
panthor_vm_map_sparse(struct panthor_vm *vm, u64 iova, int prot,
|
||||
struct sg_table *sgt, u64 size)
|
||||
{
|
||||
u64 mapped = 0;
|
||||
int ret;
|
||||
|
||||
while (mapped < size) {
|
||||
u64 addr = iova + mapped;
|
||||
u32 chunk_size = min(size - mapped, SZ_2M - (addr & (SZ_2M - 1)));
|
||||
|
||||
ret = panthor_vm_map_pages(vm, addr, prot, sgt,
|
||||
addr % SZ_2M, chunk_size);
|
||||
if (ret) {
|
||||
panthor_vm_unmap_pages(vm, iova, mapped);
|
||||
return ret;
|
||||
}
|
||||
|
||||
mapped += chunk_size;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int flags_to_prot(u32 flags)
|
||||
{
|
||||
int prot = 0;
|
||||
|
|
@ -1277,15 +1313,15 @@ static int panthor_vm_op_ctx_prealloc_pts(struct panthor_vm_op_ctx *op_ctx)
|
|||
(DRM_PANTHOR_VM_BIND_OP_MAP_READONLY | \
|
||||
DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC | \
|
||||
DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED | \
|
||||
DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE | \
|
||||
DRM_PANTHOR_VM_BIND_OP_TYPE_MASK)
|
||||
|
||||
static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
|
||||
struct panthor_vm *vm,
|
||||
struct panthor_gem_object *bo,
|
||||
u64 offset,
|
||||
u64 size, u64 va,
|
||||
u32 flags)
|
||||
const struct drm_panthor_vm_bind_op *op)
|
||||
{
|
||||
bool is_sparse = op->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE;
|
||||
struct drm_gpuvm_bo *preallocated_vm_bo;
|
||||
struct sg_table *sgt = NULL;
|
||||
int ret;
|
||||
|
|
@ -1293,12 +1329,25 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
|
|||
if (!bo)
|
||||
return -EINVAL;
|
||||
|
||||
if ((flags & ~PANTHOR_VM_BIND_OP_MAP_FLAGS) ||
|
||||
(flags & DRM_PANTHOR_VM_BIND_OP_TYPE_MASK) != DRM_PANTHOR_VM_BIND_OP_TYPE_MAP)
|
||||
if ((op->flags & ~PANTHOR_VM_BIND_OP_MAP_FLAGS) ||
|
||||
(op->flags & DRM_PANTHOR_VM_BIND_OP_TYPE_MASK) != DRM_PANTHOR_VM_BIND_OP_TYPE_MAP)
|
||||
return -EINVAL;
|
||||
|
||||
/* Make sure the VA and size are in-bounds. */
|
||||
if (size > bo->base.size || offset > bo->base.size - size)
|
||||
/* uAPI mandates sparsely bound regions must not be executable. */
|
||||
if (is_sparse && !(op->flags & DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC))
|
||||
return -EINVAL;
|
||||
|
||||
/* For non-sparse, make sure the VA and size are in-bounds.
|
||||
* For sparse, this is not applicable, because the dummy BO is
|
||||
* repeatedly mapped over a potentially wider VA range.
|
||||
*/
|
||||
if (!is_sparse && (op->size > bo->base.size || op->bo_offset > bo->base.size - op->size))
|
||||
return -EINVAL;
|
||||
|
||||
/* For sparse, we don't expect any user BO, the BO we get passed
|
||||
* is the dummy BO attached to the VM pool.
|
||||
*/
|
||||
if (is_sparse && (op->bo_handle || op->bo_offset))
|
||||
return -EINVAL;
|
||||
|
||||
/* If the BO has an exclusive VM attached, it can't be mapped to other VMs. */
|
||||
|
|
@ -1306,7 +1355,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
|
|||
bo->exclusive_vm_root_gem != panthor_vm_root_gem(vm))
|
||||
return -EINVAL;
|
||||
|
||||
panthor_vm_init_op_ctx(op_ctx, size, va, flags);
|
||||
panthor_vm_init_op_ctx(op_ctx, op->size, op->va, op->flags);
|
||||
|
||||
ret = panthor_vm_op_ctx_prealloc_vmas(op_ctx);
|
||||
if (ret)
|
||||
|
|
@ -1335,7 +1384,7 @@ static int panthor_vm_prepare_map_op_ctx(struct panthor_vm_op_ctx *op_ctx,
|
|||
}
|
||||
|
||||
op_ctx->map.vm_bo = drm_gpuvm_bo_obtain_prealloc(preallocated_vm_bo);
|
||||
op_ctx->map.bo_offset = offset;
|
||||
op_ctx->map.bo_offset = op->bo_offset;
|
||||
|
||||
ret = panthor_vm_op_ctx_prealloc_pts(op_ctx);
|
||||
if (ret)
|
||||
|
|
@ -1446,7 +1495,9 @@ panthor_vm_get_bo_for_va(struct panthor_vm *vm, u64 va, u64 *bo_offset)
|
|||
if (vma && vma->base.gem.obj) {
|
||||
drm_gem_object_get(vma->base.gem.obj);
|
||||
bo = to_panthor_bo(vma->base.gem.obj);
|
||||
*bo_offset = vma->base.gem.offset + (va - vma->base.va.addr);
|
||||
*bo_offset = !(vma->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE) ?
|
||||
vma->base.gem.offset + (va - vma->base.va.addr) :
|
||||
va & (SZ_2M - 1);
|
||||
}
|
||||
mutex_unlock(&vm->op_lock);
|
||||
|
||||
|
|
@ -1551,6 +1602,9 @@ int panthor_vm_pool_create_vm(struct panthor_device *ptdev,
|
|||
if (IS_ERR(vm))
|
||||
return PTR_ERR(vm);
|
||||
|
||||
drm_gem_object_get(&pool->dummy->base);
|
||||
vm->dummy = pool->dummy;
|
||||
|
||||
ret = xa_alloc(&pool->xa, &id, vm,
|
||||
XA_LIMIT(1, PANTHOR_MAX_VMS_PER_FILE), GFP_KERNEL);
|
||||
|
||||
|
|
@ -1650,6 +1704,8 @@ void panthor_vm_pool_destroy(struct panthor_file *pfile)
|
|||
xa_for_each(&pfile->vms->xa, i, vm)
|
||||
panthor_vm_destroy(vm);
|
||||
|
||||
if (pfile->vms->dummy)
|
||||
drm_gem_object_put(&pfile->vms->dummy->base);
|
||||
xa_destroy(&pfile->vms->xa);
|
||||
kfree(pfile->vms);
|
||||
}
|
||||
|
|
@ -1662,12 +1718,28 @@ void panthor_vm_pool_destroy(struct panthor_file *pfile)
|
|||
*/
|
||||
int panthor_vm_pool_create(struct panthor_file *pfile)
|
||||
{
|
||||
struct panthor_gem_object *dummy;
|
||||
int ret;
|
||||
|
||||
pfile->vms = kzalloc_obj(*pfile->vms);
|
||||
if (!pfile->vms)
|
||||
return -ENOMEM;
|
||||
|
||||
xa_init_flags(&pfile->vms->xa, XA_FLAGS_ALLOC1);
|
||||
|
||||
dummy = panthor_dummy_bo_create(pfile->ptdev);
|
||||
if (IS_ERR(dummy)) {
|
||||
ret = PTR_ERR(dummy);
|
||||
goto err_destroy_vm_pool;
|
||||
}
|
||||
|
||||
pfile->vms->dummy = dummy;
|
||||
|
||||
return 0;
|
||||
|
||||
err_destroy_vm_pool:
|
||||
panthor_vm_pool_destroy(pfile);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* dummy TLB ops, the real TLB flush happens in panthor_vm_flush_range() */
|
||||
|
|
@ -2004,6 +2076,9 @@ static void panthor_vm_free(struct drm_gpuvm *gpuvm)
|
|||
|
||||
free_io_pgtable_ops(vm->pgtbl_ops);
|
||||
|
||||
if (vm->dummy)
|
||||
drm_gem_object_put(&vm->dummy->base);
|
||||
|
||||
drm_mm_takedown(&vm->mm);
|
||||
kfree(vm);
|
||||
}
|
||||
|
|
@ -2163,7 +2238,30 @@ static void panthor_vma_init(struct panthor_vma *vma, u32 flags)
|
|||
#define PANTHOR_VM_MAP_FLAGS \
|
||||
(DRM_PANTHOR_VM_BIND_OP_MAP_READONLY | \
|
||||
DRM_PANTHOR_VM_BIND_OP_MAP_NOEXEC | \
|
||||
DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED)
|
||||
DRM_PANTHOR_VM_BIND_OP_MAP_UNCACHED | \
|
||||
DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE)
|
||||
|
||||
static void
|
||||
panthor_fix_sparse_map_offset(struct drm_gpuva_op_map *op, u32 flags)
|
||||
{
|
||||
if (op && (flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE))
|
||||
op->gem.offset = op->va.addr & (SZ_2M - 1);
|
||||
}
|
||||
|
||||
static int
|
||||
panthor_vm_exec_map_op(struct panthor_vm *vm, u32 flags,
|
||||
const struct drm_gpuva_op_map *op)
|
||||
{
|
||||
struct panthor_gem_object *bo = to_panthor_bo(op->gem.obj);
|
||||
int prot = flags_to_prot(flags);
|
||||
|
||||
if (flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE)
|
||||
return panthor_vm_map_sparse(vm, op->va.addr, prot,
|
||||
bo->dmap.sgt, op->va.range);
|
||||
|
||||
return panthor_vm_map_pages(vm, op->va.addr, prot, bo->dmap.sgt,
|
||||
op->gem.offset, op->va.range);
|
||||
}
|
||||
|
||||
static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
|
||||
{
|
||||
|
|
@ -2176,10 +2274,9 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
|
|||
return -EINVAL;
|
||||
|
||||
panthor_vma_init(vma, op_ctx->flags & PANTHOR_VM_MAP_FLAGS);
|
||||
panthor_fix_sparse_map_offset(&op->map, vma->flags);
|
||||
|
||||
ret = panthor_vm_map_pages(vm, op->map.va.addr, flags_to_prot(vma->flags),
|
||||
op_ctx->map.bo->dmap.sgt, op->map.gem.offset,
|
||||
op->map.va.range);
|
||||
ret = panthor_vm_exec_map_op(vm, vma->flags, &op->map);
|
||||
if (ret) {
|
||||
panthor_vm_op_ctx_return_vma(op_ctx, vma);
|
||||
return ret;
|
||||
|
|
@ -2211,6 +2308,8 @@ static void
|
|||
unmap_hugepage_align(const struct drm_gpuva_op_remap *op,
|
||||
u64 *unmap_start, u64 *unmap_range)
|
||||
{
|
||||
struct panthor_vma *unmap_vma = container_of(op->unmap->va, struct panthor_vma, base);
|
||||
bool is_sparse = unmap_vma->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE;
|
||||
u64 aligned_unmap_start, aligned_unmap_end, unmap_end;
|
||||
|
||||
unmap_end = *unmap_start + *unmap_range;
|
||||
|
|
@ -2218,11 +2317,15 @@ unmap_hugepage_align(const struct drm_gpuva_op_remap *op,
|
|||
aligned_unmap_end = ALIGN(unmap_end, SZ_2M);
|
||||
|
||||
/* If we're dealing with a huge page, make sure the unmap region is
|
||||
* aligned on the start of the page.
|
||||
* aligned on the start of the page. If the unmapped VMA stands for
|
||||
* a sparse mapping, always assume the backing storage is a THP, since
|
||||
* the overhead of unmapping 2MiB worth of 4KiB pages and remapping
|
||||
* some of them is offset by the logic of working out whether it's
|
||||
* the opposite case right below. This also holds true for op->next.
|
||||
*/
|
||||
if (op->prev && aligned_unmap_start < *unmap_start &&
|
||||
op->prev->va.addr <= aligned_unmap_start &&
|
||||
iova_mapped_as_huge_page(op->prev, *unmap_start)) {
|
||||
(is_sparse || iova_mapped_as_huge_page(op->prev, *unmap_start))) {
|
||||
*unmap_range += *unmap_start - aligned_unmap_start;
|
||||
*unmap_start = aligned_unmap_start;
|
||||
}
|
||||
|
|
@ -2232,7 +2335,7 @@ unmap_hugepage_align(const struct drm_gpuva_op_remap *op,
|
|||
*/
|
||||
if (op->next && aligned_unmap_end > unmap_end &&
|
||||
op->next->va.addr + op->next->va.range >= aligned_unmap_end &&
|
||||
iova_mapped_as_huge_page(op->next, unmap_end - 1)) {
|
||||
(is_sparse || iova_mapped_as_huge_page(op->next, unmap_end - 1))) {
|
||||
*unmap_range += aligned_unmap_end - unmap_end;
|
||||
}
|
||||
}
|
||||
|
|
@ -2249,6 +2352,11 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
|
|||
|
||||
drm_gpuva_op_remap_to_unmap_range(&op->remap, &unmap_start, &unmap_range);
|
||||
|
||||
/* op->remap.prev's BO offset is always the same as the unmap va's, but
|
||||
* that of op->remap.next must be adjusted so as to remain < SZ_2M
|
||||
*/
|
||||
panthor_fix_sparse_map_offset(op->remap.next, unmap_vma->flags);
|
||||
|
||||
/*
|
||||
* ARM IOMMU page table management code disallows partial unmaps of huge pages,
|
||||
* so when a partial unmap is requested, we must first unmap the entire huge
|
||||
|
|
@ -2268,14 +2376,19 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
|
|||
}
|
||||
|
||||
if (op->remap.prev) {
|
||||
struct panthor_gem_object *bo = to_panthor_bo(op->remap.prev->gem.obj);
|
||||
u64 offset = op->remap.prev->gem.offset + unmap_start - op->remap.prev->va.addr;
|
||||
u64 size = op->remap.prev->va.addr + op->remap.prev->va.range - unmap_start;
|
||||
|
||||
if (!unmap_vma->evicted) {
|
||||
ret = panthor_vm_map_pages(vm, unmap_start,
|
||||
flags_to_prot(unmap_vma->flags),
|
||||
bo->dmap.sgt, offset, size);
|
||||
if (!unmap_vma->evicted && size > 0) {
|
||||
struct drm_gpuva_op_map map_op = {
|
||||
.va.addr = unmap_start,
|
||||
.va.range = size,
|
||||
.gem.obj = op->remap.prev->gem.obj,
|
||||
.gem.offset = offset,
|
||||
};
|
||||
panthor_fix_sparse_map_offset(&map_op, unmap_vma->flags);
|
||||
|
||||
ret = panthor_vm_exec_map_op(vm, unmap_vma->flags, &map_op);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -2286,14 +2399,19 @@ static int panthor_gpuva_sm_step_remap(struct drm_gpuva_op *op,
|
|||
}
|
||||
|
||||
if (op->remap.next) {
|
||||
struct panthor_gem_object *bo = to_panthor_bo(op->remap.next->gem.obj);
|
||||
u64 addr = op->remap.next->va.addr;
|
||||
u64 size = unmap_start + unmap_range - op->remap.next->va.addr;
|
||||
|
||||
if (!unmap_vma->evicted) {
|
||||
ret = panthor_vm_map_pages(vm, addr, flags_to_prot(unmap_vma->flags),
|
||||
bo->dmap.sgt, op->remap.next->gem.offset,
|
||||
size);
|
||||
if (!unmap_vma->evicted && size > 0) {
|
||||
struct drm_gpuva_op_map map_op = {
|
||||
.va.addr = addr,
|
||||
.va.range = size,
|
||||
.gem.obj = op->remap.next->gem.obj,
|
||||
.gem.offset = op->remap.next->gem.offset,
|
||||
};
|
||||
panthor_fix_sparse_map_offset(&map_op, unmap_vma->flags);
|
||||
|
||||
ret = panthor_vm_exec_map_op(vm, unmap_vma->flags, &map_op);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -2490,11 +2608,17 @@ static int remap_evicted_vma(struct drm_gpuvm_bo *vm_bo,
|
|||
ret = panthor_vm_lock_region(vm, evicted_vma->base.va.addr,
|
||||
evicted_vma->base.va.range);
|
||||
if (!ret) {
|
||||
ret = panthor_vm_map_pages(vm, evicted_vma->base.va.addr,
|
||||
flags_to_prot(evicted_vma->flags),
|
||||
bo->dmap.sgt,
|
||||
evicted_vma->base.gem.offset,
|
||||
evicted_vma->base.va.range);
|
||||
struct drm_gpuva_op_map map_op = {
|
||||
.va.addr = evicted_vma->base.va.addr,
|
||||
.va.range = evicted_vma->base.va.range,
|
||||
.gem.obj = &bo->base,
|
||||
.gem.offset = evicted_vma->base.gem.offset,
|
||||
};
|
||||
if (evicted_vma->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE)
|
||||
drm_WARN_ON_ONCE(&vm->ptdev->base, map_op.gem.offset !=
|
||||
(map_op.va.addr & (SZ_2M - 1)));
|
||||
|
||||
ret = panthor_vm_exec_map_op(vm, evicted_vma->flags, &map_op);
|
||||
if (!ret)
|
||||
evicted_vma->evicted = false;
|
||||
|
||||
|
|
@ -2781,7 +2905,7 @@ panthor_vm_create(struct panthor_device *ptdev, bool for_mcu,
|
|||
refcount_set(&vm->as.active_cnt, 0);
|
||||
|
||||
pgtbl_cfg = (struct io_pgtable_cfg) {
|
||||
.pgsize_bitmap = SZ_4K | SZ_2M,
|
||||
.pgsize_bitmap = ptdev->mmu_info.page_size_bitmap,
|
||||
.ias = va_bits,
|
||||
.oas = pa_bits,
|
||||
.coherent_walk = ptdev->coherent,
|
||||
|
|
@ -2858,13 +2982,16 @@ panthor_vm_bind_prepare_op_ctx(struct drm_file *file,
|
|||
|
||||
switch (op->flags & DRM_PANTHOR_VM_BIND_OP_TYPE_MASK) {
|
||||
case DRM_PANTHOR_VM_BIND_OP_TYPE_MAP:
|
||||
gem = drm_gem_object_lookup(file, op->bo_handle);
|
||||
if (!(op->flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE)) {
|
||||
gem = drm_gem_object_lookup(file, op->bo_handle);
|
||||
} else {
|
||||
gem = &vm->dummy->base;
|
||||
drm_gem_object_get(&vm->dummy->base);
|
||||
}
|
||||
|
||||
ret = panthor_vm_prepare_map_op_ctx(op_ctx, vm,
|
||||
gem ? to_panthor_bo(gem) : NULL,
|
||||
op->bo_offset,
|
||||
op->size,
|
||||
op->va,
|
||||
op->flags);
|
||||
op);
|
||||
drm_gem_object_put(gem);
|
||||
return ret;
|
||||
|
||||
|
|
@ -3060,10 +3187,19 @@ int panthor_vm_bind_exec_sync_op(struct drm_file *file,
|
|||
int panthor_vm_map_bo_range(struct panthor_vm *vm, struct panthor_gem_object *bo,
|
||||
u64 offset, u64 size, u64 va, u32 flags)
|
||||
{
|
||||
struct drm_panthor_vm_bind_op op = {
|
||||
.bo_offset = offset,
|
||||
.size = size,
|
||||
.va = va,
|
||||
.flags = flags,
|
||||
};
|
||||
struct panthor_vm_op_ctx op_ctx;
|
||||
int ret;
|
||||
|
||||
ret = panthor_vm_prepare_map_op_ctx(&op_ctx, vm, bo, offset, size, va, flags);
|
||||
if (drm_WARN_ON(&vm->ptdev->base, flags & DRM_PANTHOR_VM_BIND_OP_MAP_SPARSE))
|
||||
return -EINVAL;
|
||||
|
||||
ret = panthor_vm_prepare_map_op_ctx(&op_ctx, vm, bo, &op);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
|
@ -3227,6 +3363,11 @@ static void panthor_mmu_release_wq(struct drm_device *ddev, void *res)
|
|||
destroy_workqueue(res);
|
||||
}
|
||||
|
||||
static void panthor_mmu_info_init(struct panthor_device *ptdev)
|
||||
{
|
||||
ptdev->mmu_info.page_size_bitmap = SZ_4K | SZ_2M;
|
||||
}
|
||||
|
||||
/**
|
||||
* panthor_mmu_init() - Initialize the MMU logic.
|
||||
* @ptdev: Device.
|
||||
|
|
@ -3239,6 +3380,8 @@ int panthor_mmu_init(struct panthor_device *ptdev)
|
|||
struct panthor_mmu *mmu;
|
||||
int ret, irq;
|
||||
|
||||
panthor_mmu_info_init(ptdev);
|
||||
|
||||
mmu = drmm_kzalloc(&ptdev->base, sizeof(*mmu), GFP_KERNEL);
|
||||
if (!mmu)
|
||||
return -ENOMEM;
|
||||
|
|
|
|||
|
|
@ -50,11 +50,16 @@
|
|||
#include "qxl_object.h"
|
||||
|
||||
static const struct pci_device_id pciidlist[] = {
|
||||
{ 0x1b36, 0x100, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_VGA << 8,
|
||||
0xffff00, 0 },
|
||||
{ 0x1b36, 0x100, PCI_ANY_ID, PCI_ANY_ID, PCI_CLASS_DISPLAY_OTHER << 8,
|
||||
0xffff00, 0 },
|
||||
{ 0, 0, 0 },
|
||||
{
|
||||
PCI_DEVICE(0x1b36, 0x0100),
|
||||
.class = PCI_CLASS_DISPLAY_VGA << 8,
|
||||
.class_mask = 0xffff00
|
||||
}, {
|
||||
PCI_DEVICE(0x1b36, 0x0100),
|
||||
.class = PCI_CLASS_DISPLAY_OTHER << 8,
|
||||
.class_mask = 0xffff00
|
||||
},
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(pci, pciidlist);
|
||||
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ static const struct rcar_du_device_info rcar_du_r8a774a1_info = {
|
|||
},
|
||||
.num_lvds = 1,
|
||||
.num_rpf = 5,
|
||||
.dpll_mask = BIT(1),
|
||||
.dpll_mask = BIT(1),
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a774b1_info = {
|
||||
|
|
@ -168,7 +168,7 @@ static const struct rcar_du_device_info rcar_du_r8a774b1_info = {
|
|||
},
|
||||
.num_lvds = 1,
|
||||
.num_rpf = 5,
|
||||
.dpll_mask = BIT(1),
|
||||
.dpll_mask = BIT(1),
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a774c0_info = {
|
||||
|
|
@ -196,7 +196,7 @@ static const struct rcar_du_device_info rcar_du_r8a774c0_info = {
|
|||
},
|
||||
.num_lvds = 2,
|
||||
.num_rpf = 4,
|
||||
.lvds_clk_mask = BIT(1) | BIT(0),
|
||||
.lvds_clk_mask = BIT(1) | BIT(0),
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a774e1_info = {
|
||||
|
|
@ -227,7 +227,7 @@ static const struct rcar_du_device_info rcar_du_r8a774e1_info = {
|
|||
},
|
||||
.num_lvds = 1,
|
||||
.num_rpf = 5,
|
||||
.dpll_mask = BIT(1),
|
||||
.dpll_mask = BIT(1),
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a7779_info = {
|
||||
|
|
@ -385,7 +385,7 @@ static const struct rcar_du_device_info rcar_du_r8a7795_info = {
|
|||
},
|
||||
.num_lvds = 1,
|
||||
.num_rpf = 5,
|
||||
.dpll_mask = BIT(2) | BIT(1),
|
||||
.dpll_mask = BIT(2) | BIT(1),
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a7796_info = {
|
||||
|
|
@ -416,7 +416,7 @@ static const struct rcar_du_device_info rcar_du_r8a7796_info = {
|
|||
},
|
||||
.num_lvds = 1,
|
||||
.num_rpf = 5,
|
||||
.dpll_mask = BIT(1),
|
||||
.dpll_mask = BIT(1),
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a77965_info = {
|
||||
|
|
@ -447,7 +447,7 @@ static const struct rcar_du_device_info rcar_du_r8a77965_info = {
|
|||
},
|
||||
.num_lvds = 1,
|
||||
.num_rpf = 5,
|
||||
.dpll_mask = BIT(1),
|
||||
.dpll_mask = BIT(1),
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a77970_info = {
|
||||
|
|
@ -502,7 +502,7 @@ static const struct rcar_du_device_info rcar_du_r8a7799x_info = {
|
|||
},
|
||||
.num_lvds = 2,
|
||||
.num_rpf = 5,
|
||||
.lvds_clk_mask = BIT(1) | BIT(0),
|
||||
.lvds_clk_mask = BIT(1) | BIT(0),
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a779a0_info = {
|
||||
|
|
@ -523,7 +523,7 @@ static const struct rcar_du_device_info rcar_du_r8a779a0_info = {
|
|||
},
|
||||
},
|
||||
.num_rpf = 5,
|
||||
.dsi_clk_mask = BIT(1) | BIT(0),
|
||||
.dsi_clk_mask = BIT(1) | BIT(0),
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a779g0_info = {
|
||||
|
|
@ -544,7 +544,7 @@ static const struct rcar_du_device_info rcar_du_r8a779g0_info = {
|
|||
},
|
||||
},
|
||||
.num_rpf = 5,
|
||||
.dsi_clk_mask = BIT(1) | BIT(0),
|
||||
.dsi_clk_mask = BIT(1) | BIT(0),
|
||||
};
|
||||
|
||||
static const struct rcar_du_device_info rcar_du_r8a779h0_info = {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
* Jeff Chen <jeff.chen@rock-chips.com>
|
||||
*/
|
||||
|
||||
#include <linux/cleanup.h>
|
||||
#include <linux/component.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/of.h>
|
||||
|
|
@ -206,7 +207,6 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder,
|
|||
struct drm_crtc *crtc;
|
||||
struct drm_crtc_state *old_crtc_state;
|
||||
struct of_endpoint endpoint;
|
||||
struct device_node *remote_port, *remote_port_parent;
|
||||
char name[32];
|
||||
u32 port_id;
|
||||
int ret;
|
||||
|
|
@ -230,18 +230,22 @@ static void rockchip_dp_drm_encoder_enable(struct drm_encoder *encoder,
|
|||
if (ret < 0)
|
||||
return;
|
||||
|
||||
remote_port_parent = of_graph_get_remote_port_parent(endpoint.local_node);
|
||||
struct device_node *remote_port_parent __free(device_node) =
|
||||
of_graph_get_remote_port_parent(endpoint.local_node);
|
||||
if (remote_port_parent) {
|
||||
if (of_get_child_by_name(remote_port_parent, "ports")) {
|
||||
remote_port = of_graph_get_remote_port(endpoint.local_node);
|
||||
struct device_node *ports __free(device_node) =
|
||||
of_get_child_by_name(remote_port_parent, "ports");
|
||||
|
||||
if (ports) {
|
||||
struct device_node *remote_port __free(device_node) =
|
||||
of_graph_get_remote_port(endpoint.local_node);
|
||||
|
||||
of_property_read_u32(remote_port, "reg", &port_id);
|
||||
of_node_put(remote_port);
|
||||
sprintf(name, "%s vp%d", remote_port_parent->full_name, port_id);
|
||||
} else {
|
||||
sprintf(name, "%s %s",
|
||||
remote_port_parent->full_name, endpoint.id ? "vopl" : "vopb");
|
||||
}
|
||||
of_node_put(remote_port_parent);
|
||||
|
||||
DRM_DEV_DEBUG(dp->dev, "vop %s output to dp\n", (ret) ? "LIT" : "BIG");
|
||||
}
|
||||
|
|
@ -311,6 +315,7 @@ static int rockchip_dp_of_probe(struct rockchip_dp_device *dp)
|
|||
{
|
||||
struct device *dev = dp->dev;
|
||||
struct device_node *np = dev->of_node;
|
||||
struct clk *clk;
|
||||
|
||||
dp->grf = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
|
||||
if (IS_ERR(dp->grf))
|
||||
|
|
@ -327,6 +332,11 @@ static int rockchip_dp_of_probe(struct rockchip_dp_device *dp)
|
|||
return dev_err_probe(dev, PTR_ERR(dp->pclk),
|
||||
"failed to get pclk property\n");
|
||||
|
||||
clk = devm_clk_get_optional_enabled(dev, "hclk");
|
||||
if (IS_ERR(clk))
|
||||
return dev_err_probe(dev, PTR_ERR(clk),
|
||||
"failed to get hclk property\n");
|
||||
|
||||
dp->rst = devm_reset_control_get(dev, "dp");
|
||||
if (IS_ERR(dp->rst))
|
||||
return dev_err_probe(dev, PTR_ERR(dp->rst),
|
||||
|
|
@ -516,6 +526,14 @@ static const struct rockchip_dp_chip_data rk3288_dp[] = {
|
|||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static const struct rockchip_dp_chip_data rk3576_edp[] = {
|
||||
{
|
||||
.chip_type = RK3576_EDP,
|
||||
.reg = 0x27dc0000,
|
||||
},
|
||||
{ /* sentinel */ }
|
||||
};
|
||||
|
||||
static const struct rockchip_dp_chip_data rk3588_edp[] = {
|
||||
{
|
||||
.edp_mode = GRF_REG_FIELD(0x0000, 0, 0),
|
||||
|
|
@ -533,6 +551,7 @@ static const struct rockchip_dp_chip_data rk3588_edp[] = {
|
|||
static const struct of_device_id rockchip_dp_dt_ids[] = {
|
||||
{.compatible = "rockchip,rk3288-dp", .data = &rk3288_dp },
|
||||
{.compatible = "rockchip,rk3399-edp", .data = &rk3399_edp },
|
||||
{.compatible = "rockchip,rk3576-edp", .data = &rk3576_edp },
|
||||
{.compatible = "rockchip,rk3588-edp", .data = &rk3588_edp },
|
||||
{}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -107,15 +107,26 @@ static int dw_dp_rockchip_bind(struct device *dev, struct device *master, void *
|
|||
return PTR_ERR(dp->base);
|
||||
|
||||
connector = drm_bridge_connector_init(drm_dev, encoder);
|
||||
if (IS_ERR(connector))
|
||||
if (IS_ERR(connector)) {
|
||||
dw_dp_unbind(dp->base);
|
||||
return dev_err_probe(dev, PTR_ERR(connector),
|
||||
"Failed to init bridge connector");
|
||||
"Failed to init bridge connector\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void dw_dp_rockchip_unbind(struct device *dev, struct device *master,
|
||||
void *data)
|
||||
{
|
||||
struct rockchip_dw_dp *dp = dev_get_drvdata(dev);
|
||||
|
||||
dw_dp_unbind(dp->base);
|
||||
}
|
||||
|
||||
static const struct component_ops dw_dp_rockchip_component_ops = {
|
||||
.bind = dw_dp_rockchip_bind,
|
||||
.unbind = dw_dp_rockchip_unbind,
|
||||
};
|
||||
|
||||
static int dw_dp_probe(struct platform_device *pdev)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include <linux/clk.h>
|
||||
#include <linux/hw_bitfield.h>
|
||||
#include <linux/media-bus-format.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
|
|
@ -14,6 +15,7 @@
|
|||
|
||||
#include <drm/bridge/dw_hdmi.h>
|
||||
#include <drm/drm_edid.h>
|
||||
#include <drm/drm_managed.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include <drm/drm_probe_helper.h>
|
||||
#include <drm/drm_simple_kms_helper.h>
|
||||
|
|
@ -30,8 +32,8 @@
|
|||
|
||||
#define RK3288_GRF_SOC_CON6 0x025C
|
||||
#define RK3288_HDMI_LCDC_SEL BIT(4)
|
||||
#define RK3328_GRF_SOC_CON2 0x0408
|
||||
|
||||
#define RK3328_GRF_SOC_CON2 0x0408
|
||||
#define RK3328_HDMI_SDAIN_MSK BIT(11)
|
||||
#define RK3328_HDMI_SCLIN_MSK BIT(10)
|
||||
#define RK3328_HDMI_HPD_IOE BIT(2)
|
||||
|
|
@ -78,6 +80,7 @@ struct rockchip_hdmi {
|
|||
struct clk *hdmiphy_clk;
|
||||
struct clk *ref_clk;
|
||||
struct clk *grf_clk;
|
||||
struct drm_bridge *bridge;
|
||||
struct dw_hdmi *hdmi;
|
||||
struct phy *phy;
|
||||
};
|
||||
|
|
@ -188,48 +191,13 @@ static const struct dw_hdmi_curr_ctrl rockchip_cur_ctr[] = {
|
|||
|
||||
static const struct dw_hdmi_phy_config rockchip_phy_config[] = {
|
||||
/*pixelclk symbol term vlev*/
|
||||
{ 74250000, 0x8009, 0x0004, 0x0272},
|
||||
{ 165000000, 0x802b, 0x0004, 0x0209},
|
||||
{ 297000000, 0x8039, 0x0005, 0x028d},
|
||||
{ 594000000, 0x8039, 0x0000, 0x019d},
|
||||
{ ~0UL, 0x0000, 0x0000, 0x0000}
|
||||
{ 74250000, 0x8009, 0x0004, 0x0272 },
|
||||
{ 165000000, 0x802b, 0x0004, 0x0209 },
|
||||
{ 297000000, 0x8039, 0x0005, 0x028d },
|
||||
{ 594000000, 0x8039, 0x0000, 0x019d },
|
||||
{ ~0UL, 0x0000, 0x0000, 0x0000 },
|
||||
};
|
||||
|
||||
static int rockchip_hdmi_parse_dt(struct rockchip_hdmi *hdmi)
|
||||
{
|
||||
struct device_node *np = hdmi->dev->of_node;
|
||||
int ret;
|
||||
|
||||
hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
|
||||
if (IS_ERR(hdmi->regmap)) {
|
||||
dev_err(hdmi->dev, "Unable to get rockchip,grf\n");
|
||||
return PTR_ERR(hdmi->regmap);
|
||||
}
|
||||
|
||||
hdmi->ref_clk = devm_clk_get_optional_enabled(hdmi->dev, "ref");
|
||||
if (!hdmi->ref_clk)
|
||||
hdmi->ref_clk = devm_clk_get_optional_enabled(hdmi->dev, "vpll");
|
||||
|
||||
if (IS_ERR(hdmi->ref_clk)) {
|
||||
ret = PTR_ERR(hdmi->ref_clk);
|
||||
return dev_err_probe(hdmi->dev, ret, "failed to get reference clock\n");
|
||||
}
|
||||
|
||||
hdmi->grf_clk = devm_clk_get_optional(hdmi->dev, "grf");
|
||||
if (IS_ERR(hdmi->grf_clk)) {
|
||||
ret = PTR_ERR(hdmi->grf_clk);
|
||||
return dev_err_probe(hdmi->dev, ret, "failed to get grf clock\n");
|
||||
}
|
||||
|
||||
ret = devm_regulator_get_enable(hdmi->dev, "avdd-0v9");
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = devm_regulator_get_enable(hdmi->dev, "avdd-1v8");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static enum drm_mode_status
|
||||
dw_hdmi_rockchip_mode_valid(struct dw_hdmi *dw_hdmi, void *data,
|
||||
const struct drm_display_info *info,
|
||||
|
|
@ -259,23 +227,22 @@ dw_hdmi_rockchip_mode_valid(struct dw_hdmi *dw_hdmi, void *data,
|
|||
return MODE_OK;
|
||||
}
|
||||
|
||||
static void dw_hdmi_rockchip_encoder_disable(struct drm_encoder *encoder)
|
||||
{
|
||||
}
|
||||
|
||||
static bool
|
||||
dw_hdmi_rockchip_encoder_mode_fixup(struct drm_encoder *encoder,
|
||||
const struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adj_mode)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
static void dw_hdmi_rockchip_encoder_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_display_mode *mode,
|
||||
struct drm_display_mode *adj_mode)
|
||||
static void
|
||||
dw_hdmi_rockchip_encoder_atomic_mode_set(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
|
||||
struct drm_display_mode *adj_mode = &crtc_state->adjusted_mode;
|
||||
|
||||
if (hdmi->phy && conn_state->hdmi.tmds_char_rate) {
|
||||
union phy_configure_opts opts = {};
|
||||
|
||||
opts.hdmi.bpc = conn_state->hdmi.output_bpc;
|
||||
opts.hdmi.tmds_char_rate = conn_state->hdmi.tmds_char_rate;
|
||||
|
||||
phy_configure(hdmi->phy, &opts);
|
||||
}
|
||||
|
||||
clk_set_rate(hdmi->ref_clk, adj_mode->clock * 1000);
|
||||
}
|
||||
|
|
@ -309,24 +276,71 @@ static void dw_hdmi_rockchip_encoder_enable(struct drm_encoder *encoder)
|
|||
dev_dbg(hdmi->dev, "vop %s output to hdmi\n", ret ? "LIT" : "BIG");
|
||||
}
|
||||
|
||||
static u32 dw_hdmi_rockchip_get_bus_format(struct drm_encoder *encoder,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct drm_bridge *bridge __free(drm_bridge_put) = NULL;
|
||||
struct drm_bridge_state *bridge_state;
|
||||
|
||||
bridge = drm_bridge_chain_get_first_bridge(encoder);
|
||||
if (!bridge)
|
||||
return 0;
|
||||
|
||||
bridge_state = drm_atomic_get_bridge_state(conn_state->state, bridge);
|
||||
if (!bridge_state)
|
||||
return 0;
|
||||
|
||||
if (bridge_state->input_bus_cfg.format != MEDIA_BUS_FMT_FIXED)
|
||||
return bridge_state->input_bus_cfg.format;
|
||||
|
||||
return bridge_state->output_bus_cfg.format;
|
||||
}
|
||||
|
||||
static int
|
||||
dw_hdmi_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
|
||||
struct rockchip_hdmi *hdmi = to_rockchip_hdmi(encoder);
|
||||
union phy_configure_opts opts = {};
|
||||
u32 bus_format;
|
||||
|
||||
bus_format = dw_hdmi_rockchip_get_bus_format(encoder, conn_state);
|
||||
|
||||
switch (bus_format) {
|
||||
case MEDIA_BUS_FMT_FIXED:
|
||||
bus_format = MEDIA_BUS_FMT_RGB888_1X24;
|
||||
fallthrough;
|
||||
case MEDIA_BUS_FMT_RGB888_1X24:
|
||||
case MEDIA_BUS_FMT_RGB101010_1X30:
|
||||
case MEDIA_BUS_FMT_YUV8_1X24:
|
||||
case MEDIA_BUS_FMT_YUV10_1X30:
|
||||
s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
|
||||
case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
|
||||
s->output_mode = ROCKCHIP_OUT_MODE_YUV420;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
|
||||
s->output_type = DRM_MODE_CONNECTOR_HDMIA;
|
||||
s->bus_format = bus_format;
|
||||
|
||||
return 0;
|
||||
if (!hdmi->phy || !conn_state->hdmi.tmds_char_rate)
|
||||
return 0;
|
||||
|
||||
opts.hdmi.bpc = conn_state->hdmi.output_bpc;
|
||||
opts.hdmi.tmds_char_rate = conn_state->hdmi.tmds_char_rate;
|
||||
|
||||
return phy_validate(hdmi->phy, PHY_MODE_HDMI, PHY_HDMI_MODE_TMDS, &opts);
|
||||
}
|
||||
|
||||
static const struct drm_encoder_helper_funcs dw_hdmi_rockchip_encoder_helper_funcs = {
|
||||
.mode_fixup = dw_hdmi_rockchip_encoder_mode_fixup,
|
||||
.mode_set = dw_hdmi_rockchip_encoder_mode_set,
|
||||
.enable = dw_hdmi_rockchip_encoder_enable,
|
||||
.disable = dw_hdmi_rockchip_encoder_disable,
|
||||
.atomic_mode_set = dw_hdmi_rockchip_encoder_atomic_mode_set,
|
||||
.enable = dw_hdmi_rockchip_encoder_enable,
|
||||
.atomic_check = dw_hdmi_rockchip_encoder_atomic_check,
|
||||
};
|
||||
|
||||
|
|
@ -508,6 +522,7 @@ static const struct dw_hdmi_plat_data rk3568_hdmi_drv_data = {
|
|||
.phy_config = rockchip_phy_config,
|
||||
.phy_data = &rk3568_chip_data,
|
||||
.use_drm_infoframe = true,
|
||||
.output_port = 1,
|
||||
};
|
||||
|
||||
static const struct of_device_id dw_hdmi_rockchip_dt_ids[] = {
|
||||
|
|
@ -538,8 +553,8 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
|
|||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
struct device_node *np = dev_of_node(dev);
|
||||
const struct dw_hdmi_plat_data *drv_data;
|
||||
struct dw_hdmi_plat_data *plat_data;
|
||||
const struct of_device_id *match;
|
||||
struct drm_device *drm = data;
|
||||
struct drm_encoder *encoder;
|
||||
struct rockchip_hdmi *hdmi;
|
||||
|
|
@ -548,17 +563,20 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
|
|||
if (!np)
|
||||
return -ENODEV;
|
||||
|
||||
hdmi = devm_kzalloc(&pdev->dev, sizeof(*hdmi), GFP_KERNEL);
|
||||
drv_data = of_device_get_match_data(dev);
|
||||
if (!drv_data)
|
||||
return -ENODEV;
|
||||
|
||||
hdmi = drmm_kzalloc(drm, sizeof(*hdmi), GFP_KERNEL);
|
||||
if (!hdmi)
|
||||
return -ENOMEM;
|
||||
|
||||
match = of_match_node(dw_hdmi_rockchip_dt_ids, np);
|
||||
plat_data = devm_kmemdup(&pdev->dev, match->data,
|
||||
sizeof(*plat_data), GFP_KERNEL);
|
||||
plat_data = drmm_kzalloc(drm, sizeof(*drv_data), GFP_KERNEL);
|
||||
if (!plat_data)
|
||||
return -ENOMEM;
|
||||
memcpy(plat_data, drv_data, sizeof(*drv_data));
|
||||
|
||||
hdmi->dev = &pdev->dev;
|
||||
hdmi->dev = dev;
|
||||
hdmi->plat_data = plat_data;
|
||||
hdmi->chip_data = plat_data->phy_data;
|
||||
plat_data->phy_data = hdmi;
|
||||
|
|
@ -576,18 +594,39 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
|
|||
* the required CRTC is added later.
|
||||
*/
|
||||
if (encoder->possible_crtcs == 0)
|
||||
return -EPROBE_DEFER;
|
||||
return dev_err_probe(dev, -EPROBE_DEFER,
|
||||
"failed to find possible crtcs\n");
|
||||
|
||||
ret = rockchip_hdmi_parse_dt(hdmi);
|
||||
if (ret) {
|
||||
return dev_err_probe(hdmi->dev, ret, "Unable to parse OF data\n");
|
||||
}
|
||||
hdmi->regmap = syscon_regmap_lookup_by_phandle(np, "rockchip,grf");
|
||||
if (IS_ERR(hdmi->regmap))
|
||||
return dev_err_probe(dev, PTR_ERR(hdmi->regmap),
|
||||
"failed to get rockchip,grf\n");
|
||||
|
||||
hdmi->ref_clk = devm_clk_get_optional_enabled(dev, "ref");
|
||||
if (!hdmi->ref_clk)
|
||||
hdmi->ref_clk = devm_clk_get_optional_enabled(dev, "vpll");
|
||||
|
||||
if (IS_ERR(hdmi->ref_clk))
|
||||
return dev_err_probe(dev, PTR_ERR(hdmi->ref_clk),
|
||||
"failed to get reference clock\n");
|
||||
|
||||
hdmi->grf_clk = devm_clk_get_optional(dev, "grf");
|
||||
if (IS_ERR(hdmi->grf_clk))
|
||||
return dev_err_probe(dev, PTR_ERR(hdmi->grf_clk),
|
||||
"failed to get grf clock\n");
|
||||
|
||||
ret = devm_regulator_get_enable(dev, "avdd-0v9");
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to enable avdd-0v9\n");
|
||||
|
||||
ret = devm_regulator_get_enable(dev, "avdd-1v8");
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to enable avdd-1v8\n");
|
||||
|
||||
hdmi->phy = devm_phy_optional_get(dev, "hdmi");
|
||||
if (IS_ERR(hdmi->phy)) {
|
||||
ret = PTR_ERR(hdmi->phy);
|
||||
return dev_err_probe(hdmi->dev, ret, "failed to get phy\n");
|
||||
}
|
||||
if (IS_ERR(hdmi->phy))
|
||||
return dev_err_probe(dev, PTR_ERR(hdmi->phy),
|
||||
"failed to get phy\n");
|
||||
|
||||
index = of_property_match_string(np, "phy-names", "hdmi");
|
||||
if (index >= 0) {
|
||||
|
|
@ -608,28 +647,27 @@ static int dw_hdmi_rockchip_bind(struct device *dev, struct device *master,
|
|||
FIELD_PREP_WM16(RK3568_HDMI_SCLIN_MSK, 1));
|
||||
}
|
||||
|
||||
ret = drmm_encoder_init(drm, encoder, NULL, DRM_MODE_ENCODER_TMDS, NULL);
|
||||
if (ret)
|
||||
return dev_err_probe(dev, ret, "failed to init encoder\n");
|
||||
|
||||
drm_encoder_helper_add(encoder, &dw_hdmi_rockchip_encoder_helper_funcs);
|
||||
drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
|
||||
|
||||
platform_set_drvdata(pdev, hdmi);
|
||||
|
||||
hdmi->hdmi = dw_hdmi_bind(pdev, encoder, plat_data);
|
||||
if (IS_ERR(hdmi->hdmi))
|
||||
return dev_err_probe(dev, PTR_ERR(hdmi->hdmi),
|
||||
"failed to probe dw-hdmi bridge\n");
|
||||
|
||||
/*
|
||||
* If dw_hdmi_bind() fails we'll never call dw_hdmi_unbind(),
|
||||
* which would have called the encoder cleanup. Do it manually.
|
||||
*/
|
||||
if (IS_ERR(hdmi->hdmi)) {
|
||||
ret = PTR_ERR(hdmi->hdmi);
|
||||
goto err_bind;
|
||||
hdmi->bridge = of_drm_find_and_get_bridge(np);
|
||||
if (!hdmi->bridge) {
|
||||
dw_hdmi_unbind(hdmi->hdmi);
|
||||
return dev_err_probe(dev, -ENODEV,
|
||||
"failed to find dw-hdmi bridge\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_bind:
|
||||
drm_encoder_cleanup(encoder);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
|
||||
|
|
@ -637,8 +675,8 @@ static void dw_hdmi_rockchip_unbind(struct device *dev, struct device *master,
|
|||
{
|
||||
struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
|
||||
|
||||
drm_bridge_put(hdmi->bridge);
|
||||
dw_hdmi_unbind(hdmi->hdmi);
|
||||
drm_encoder_cleanup(&hdmi->encoder.encoder);
|
||||
}
|
||||
|
||||
static const struct component_ops dw_hdmi_rockchip_ops = {
|
||||
|
|
@ -656,17 +694,18 @@ static void dw_hdmi_rockchip_remove(struct platform_device *pdev)
|
|||
component_del(&pdev->dev, &dw_hdmi_rockchip_ops);
|
||||
}
|
||||
|
||||
static int __maybe_unused dw_hdmi_rockchip_resume(struct device *dev)
|
||||
static int __maybe_unused dw_hdmi_rockchip_resume_early(struct device *dev)
|
||||
{
|
||||
struct rockchip_hdmi *hdmi = dev_get_drvdata(dev);
|
||||
|
||||
dw_hdmi_resume(hdmi->hdmi);
|
||||
if (hdmi)
|
||||
dw_hdmi_resume(hdmi->hdmi);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dev_pm_ops dw_hdmi_rockchip_pm = {
|
||||
SET_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_rockchip_resume)
|
||||
SET_LATE_SYSTEM_SLEEP_PM_OPS(NULL, dw_hdmi_rockchip_resume_early)
|
||||
};
|
||||
|
||||
struct platform_driver dw_hdmi_rockchip_pltfm_driver = {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/hw_bitfield.h>
|
||||
#include <linux/mfd/syscon.h>
|
||||
#include <linux/media-bus-format.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/phy/phy.h>
|
||||
|
|
@ -44,10 +45,6 @@
|
|||
#define RK3576_8BPC 0x0
|
||||
#define RK3576_10BPC 0x6
|
||||
#define RK3576_COLOR_FORMAT_MASK GENMASK(7, 4)
|
||||
#define RK3576_RGB 0x9
|
||||
#define RK3576_YUV422 0x1
|
||||
#define RK3576_YUV444 0x2
|
||||
#define RK3576_YUV420 0x3
|
||||
#define RK3576_CECIN_MASK BIT(3)
|
||||
|
||||
#define RK3576_VO0_GRF_SOC_CON14 0x0038
|
||||
|
|
@ -75,8 +72,6 @@
|
|||
#define RK3588_8BPC 0x0
|
||||
#define RK3588_10BPC 0x6
|
||||
#define RK3588_COLOR_FORMAT_MASK GENMASK(3, 0)
|
||||
#define RK3588_RGB 0x0
|
||||
#define RK3588_YUV420 0x3
|
||||
#define RK3588_SCLIN_MASK BIT(9)
|
||||
#define RK3588_SDAIN_MASK BIT(10)
|
||||
#define RK3588_MODE_MASK BIT(11)
|
||||
|
|
@ -88,6 +83,11 @@
|
|||
#define HOTPLUG_DEBOUNCE_MS 150
|
||||
#define MAX_HDMI_PORT_NUM 2
|
||||
|
||||
#define RK_COLOR_FMT_RGB 0x0
|
||||
#define RK_COLOR_FMT_YUV422 0x1
|
||||
#define RK_COLOR_FMT_YUV444 0x2
|
||||
#define RK_COLOR_FMT_YUV420 0x3
|
||||
|
||||
struct rockchip_hdmi_qp {
|
||||
struct device *dev;
|
||||
struct regmap *regmap;
|
||||
|
|
@ -116,6 +116,33 @@ static struct rockchip_hdmi_qp *to_rockchip_hdmi_qp(struct drm_encoder *encoder)
|
|||
return container_of(rkencoder, struct rockchip_hdmi_qp, encoder);
|
||||
}
|
||||
|
||||
/**
|
||||
* dw_hdmi_qp_rockchip_bus_fmt_to_reg - converts a bus format to a GRF reg value
|
||||
* @bus_fmt: One of the MEDIA_BUS_FMT_s allowed by this driver's atomic_check
|
||||
*
|
||||
* Returns: an unshifted value to be written to the COLOR_FORMAT GRF register
|
||||
* on success, or %-EINVAL if the bus format is not supported.
|
||||
*/
|
||||
static int __pure dw_hdmi_qp_rockchip_bus_fmt_to_reg(u32 bus_fmt)
|
||||
{
|
||||
switch (bus_fmt) {
|
||||
case MEDIA_BUS_FMT_RGB888_1X24:
|
||||
case MEDIA_BUS_FMT_RGB101010_1X30:
|
||||
return RK_COLOR_FMT_RGB;
|
||||
case MEDIA_BUS_FMT_UYVY8_1X16:
|
||||
case MEDIA_BUS_FMT_UYVY10_1X20:
|
||||
return RK_COLOR_FMT_YUV422;
|
||||
case MEDIA_BUS_FMT_YUV8_1X24:
|
||||
case MEDIA_BUS_FMT_YUV10_1X30:
|
||||
return RK_COLOR_FMT_YUV444;
|
||||
case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
|
||||
case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
|
||||
return RK_COLOR_FMT_YUV420;
|
||||
}
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
static void dw_hdmi_qp_rockchip_encoder_enable(struct drm_encoder *encoder)
|
||||
{
|
||||
struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder);
|
||||
|
|
@ -131,29 +158,83 @@ static void dw_hdmi_qp_rockchip_encoder_enable(struct drm_encoder *encoder)
|
|||
hdmi->ctrl_ops->enc_init(hdmi, to_rockchip_crtc_state(crtc->state));
|
||||
}
|
||||
|
||||
/**
|
||||
* dw_hdmi_qp_rockchip_get_vop_format - get the bus format VOP should output
|
||||
* @encoder: pointer to a &struct drm_encoder
|
||||
* @conn_state: pointer to the current atomic &struct drm_connector_state
|
||||
*
|
||||
* Determines which bus format the Rockchip video processor should output as
|
||||
* to feed into the bridge chain.
|
||||
*
|
||||
* Returns a MEDIA_BUS_FMT_* on success, or negative errno on error.
|
||||
*/
|
||||
static int dw_hdmi_qp_rockchip_get_vop_format(struct drm_encoder *encoder,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct drm_bridge *bridge __free(drm_bridge_put) = NULL;
|
||||
struct drm_bridge_state *bstate;
|
||||
|
||||
bridge = drm_bridge_chain_get_first_bridge(encoder);
|
||||
if (!bridge)
|
||||
return -ENODEV;
|
||||
|
||||
bstate = drm_atomic_get_bridge_state(conn_state->state, bridge);
|
||||
if (IS_ERR(bstate))
|
||||
return PTR_ERR(bstate);
|
||||
|
||||
if (bstate->input_bus_cfg.format != MEDIA_BUS_FMT_FIXED)
|
||||
return bstate->input_bus_cfg.format;
|
||||
|
||||
return bstate->output_bus_cfg.format;
|
||||
}
|
||||
|
||||
static int
|
||||
dw_hdmi_qp_rockchip_encoder_atomic_check(struct drm_encoder *encoder,
|
||||
struct drm_crtc_state *crtc_state,
|
||||
struct drm_connector_state *conn_state)
|
||||
{
|
||||
struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder);
|
||||
struct rockchip_crtc_state *s = to_rockchip_crtc_state(crtc_state);
|
||||
struct rockchip_hdmi_qp *hdmi = to_rockchip_hdmi_qp(encoder);
|
||||
union phy_configure_opts phy_cfg = {};
|
||||
int ingest_fmt;
|
||||
int ret;
|
||||
|
||||
ingest_fmt = dw_hdmi_qp_rockchip_get_vop_format(encoder, conn_state);
|
||||
if (ingest_fmt < 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (hdmi->tmds_char_rate == conn_state->hdmi.tmds_char_rate &&
|
||||
s->output_bpc == conn_state->hdmi.output_bpc)
|
||||
s->output_bpc == conn_state->hdmi.output_bpc &&
|
||||
s->bus_format == ingest_fmt)
|
||||
return 0;
|
||||
|
||||
switch (ingest_fmt) {
|
||||
case MEDIA_BUS_FMT_RGB888_1X24:
|
||||
case MEDIA_BUS_FMT_RGB101010_1X30:
|
||||
case MEDIA_BUS_FMT_YUV8_1X24:
|
||||
case MEDIA_BUS_FMT_YUV10_1X30:
|
||||
s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_UYVY8_1X16:
|
||||
s->output_mode = ROCKCHIP_OUT_MODE_YUV422;
|
||||
break;
|
||||
case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
|
||||
case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
|
||||
s->output_mode = ROCKCHIP_OUT_MODE_YUV420;
|
||||
break;
|
||||
default:
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
phy_cfg.hdmi.tmds_char_rate = conn_state->hdmi.tmds_char_rate;
|
||||
phy_cfg.hdmi.bpc = conn_state->hdmi.output_bpc;
|
||||
|
||||
ret = phy_configure(hdmi->phy, &phy_cfg);
|
||||
if (!ret) {
|
||||
hdmi->tmds_char_rate = conn_state->hdmi.tmds_char_rate;
|
||||
s->output_mode = ROCKCHIP_OUT_MODE_AAAA;
|
||||
s->output_type = DRM_MODE_CONNECTOR_HDMIA;
|
||||
s->output_bpc = conn_state->hdmi.output_bpc;
|
||||
s->bus_format = ingest_fmt;
|
||||
} else {
|
||||
dev_err(hdmi->dev, "Failed to configure phy: %d\n", ret);
|
||||
}
|
||||
|
|
@ -383,6 +464,7 @@ static void dw_hdmi_qp_rk3588_io_init(struct rockchip_hdmi_qp *hdmi)
|
|||
static void dw_hdmi_qp_rk3576_enc_init(struct rockchip_hdmi_qp *hdmi,
|
||||
struct rockchip_crtc_state *state)
|
||||
{
|
||||
int color = dw_hdmi_qp_rockchip_bus_fmt_to_reg(state->bus_format);
|
||||
u32 val;
|
||||
|
||||
if (state->output_bpc == 10)
|
||||
|
|
@ -390,12 +472,16 @@ static void dw_hdmi_qp_rk3576_enc_init(struct rockchip_hdmi_qp *hdmi,
|
|||
else
|
||||
val = FIELD_PREP_WM16(RK3576_COLOR_DEPTH_MASK, RK3576_8BPC);
|
||||
|
||||
if (likely(color >= 0))
|
||||
val |= FIELD_PREP_WM16(RK3576_COLOR_FORMAT_MASK, color);
|
||||
|
||||
regmap_write(hdmi->vo_regmap, RK3576_VO0_GRF_SOC_CON8, val);
|
||||
}
|
||||
|
||||
static void dw_hdmi_qp_rk3588_enc_init(struct rockchip_hdmi_qp *hdmi,
|
||||
struct rockchip_crtc_state *state)
|
||||
{
|
||||
int color = dw_hdmi_qp_rockchip_bus_fmt_to_reg(state->bus_format);
|
||||
u32 val;
|
||||
|
||||
if (state->output_bpc == 10)
|
||||
|
|
@ -403,6 +489,9 @@ static void dw_hdmi_qp_rk3588_enc_init(struct rockchip_hdmi_qp *hdmi,
|
|||
else
|
||||
val = FIELD_PREP_WM16(RK3588_COLOR_DEPTH_MASK, RK3588_8BPC);
|
||||
|
||||
if (likely(color >= 0))
|
||||
val |= FIELD_PREP_WM16(RK3588_COLOR_FORMAT_MASK, color);
|
||||
|
||||
regmap_write(hdmi->vo_regmap,
|
||||
hdmi->port_id ? RK3588_GRF_VO1_CON6 : RK3588_GRF_VO1_CON3,
|
||||
val);
|
||||
|
|
@ -513,6 +602,10 @@ static int dw_hdmi_qp_rockchip_bind(struct device *dev, struct device *master,
|
|||
plat_data.phy_data = hdmi;
|
||||
plat_data.max_bpc = 10;
|
||||
|
||||
plat_data.supported_formats = BIT(DRM_OUTPUT_COLOR_FORMAT_RGB444) |
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR444) |
|
||||
BIT(DRM_OUTPUT_COLOR_FORMAT_YCBCR422);
|
||||
|
||||
encoder = &hdmi->encoder.encoder;
|
||||
encoder->possible_crtcs = drm_of_find_possible_crtcs(drm, dev->of_node);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,10 +30,14 @@
|
|||
#define ROCKCHIP_OUT_MODE_P565 2
|
||||
#define ROCKCHIP_OUT_MODE_BT656 5
|
||||
#define ROCKCHIP_OUT_MODE_S888 8
|
||||
#define ROCKCHIP_OUT_MODE_YUV422 9
|
||||
#define ROCKCHIP_OUT_MODE_S888_DUMMY 12
|
||||
#define ROCKCHIP_OUT_MODE_YUV420 14
|
||||
/* for use special outface */
|
||||
#define ROCKCHIP_OUT_MODE_AAAA 15
|
||||
/* SoC specific output modes */
|
||||
#define ROCKCHIP_OUT_MODE_YUV422_RK3576_DP 12
|
||||
#define ROCKCHIP_OUT_MODE_YUV422_RK3576_HDMI 13
|
||||
|
||||
/* output flags */
|
||||
#define ROCKCHIP_OUTPUT_DSI_DUAL BIT(0)
|
||||
|
|
|
|||
|
|
@ -337,7 +337,8 @@ static bool vop2_output_uv_swap(u32 bus_format, u32 output_mode)
|
|||
|
||||
static bool vop2_output_rg_swap(struct vop2 *vop2, u32 bus_format)
|
||||
{
|
||||
if (vop2->version == VOP_VERSION_RK3588) {
|
||||
if (vop2->version == VOP_VERSION_RK3588 ||
|
||||
vop2->version == VOP_VERSION_RK3576) {
|
||||
if (bus_format == MEDIA_BUS_FMT_YUV8_1X24 ||
|
||||
bus_format == MEDIA_BUS_FMT_YUV10_1X30)
|
||||
return true;
|
||||
|
|
@ -351,6 +352,8 @@ static bool is_yuv_output(u32 bus_format)
|
|||
switch (bus_format) {
|
||||
case MEDIA_BUS_FMT_YUV8_1X24:
|
||||
case MEDIA_BUS_FMT_YUV10_1X30:
|
||||
case MEDIA_BUS_FMT_YUYV10_1X20:
|
||||
case MEDIA_BUS_FMT_UYVY10_1X20:
|
||||
case MEDIA_BUS_FMT_UYYVYY8_0_5X24:
|
||||
case MEDIA_BUS_FMT_UYYVYY10_0_5X30:
|
||||
case MEDIA_BUS_FMT_YUYV8_2X8:
|
||||
|
|
@ -658,7 +661,7 @@ static int vop2_convert_csc_mode(int csc_mode)
|
|||
case V4L2_COLORSPACE_JPEG:
|
||||
return CSC_BT601F;
|
||||
case V4L2_COLORSPACE_BT2020:
|
||||
return CSC_BT2020;
|
||||
return CSC_BT2020L;
|
||||
default:
|
||||
return CSC_BT709L;
|
||||
}
|
||||
|
|
@ -728,6 +731,89 @@ static void vop2_setup_csc_mode(struct vop2_video_port *vp,
|
|||
vop2_win_write(win, VOP2_WIN_CSC_MODE, csc_mode);
|
||||
}
|
||||
|
||||
/*
|
||||
* RGB-to-YCbCr conversion based on color_to_ycbcr() and rgb2ycbcr() from
|
||||
* drivers/media/common/v4l2-tpg/v4l2-tpg-core.c.
|
||||
*
|
||||
* Limited-range Y offset & chroma midpoint are expressed in 16-bit space.
|
||||
*/
|
||||
#define RGB2YUV_LIMITED_Y_OFFSET (16 << 8)
|
||||
#define RGB2YUV_CHROMA_OFFSET (128 << 8)
|
||||
#define COEFF(v, r) ((s32)(0.5 + (v) * (r) * 256.0))
|
||||
|
||||
struct rgb2yuv_matrix {
|
||||
s32 y_r, y_g, y_b;
|
||||
s32 cb_r, cb_g, cb_b;
|
||||
s32 cr_r, cr_g, cr_b;
|
||||
s32 y_offset;
|
||||
};
|
||||
|
||||
/* BT.601 Limited range */
|
||||
static const struct rgb2yuv_matrix rgb2yuv_bt601l = {
|
||||
.y_r = COEFF(0.299, 219), .y_g = COEFF(0.587, 219), .y_b = COEFF(0.114, 219),
|
||||
.cb_r = COEFF(-0.1687, 224), .cb_g = COEFF(-0.3313, 224), .cb_b = COEFF(0.5, 224),
|
||||
.cr_r = COEFF(0.5, 224), .cr_g = COEFF(-0.4187, 224), .cr_b = COEFF(-0.0813, 224),
|
||||
.y_offset = RGB2YUV_LIMITED_Y_OFFSET,
|
||||
};
|
||||
|
||||
/* BT.601 Full range */
|
||||
static const struct rgb2yuv_matrix rgb2yuv_bt601f = {
|
||||
.y_r = COEFF(0.299, 255), .y_g = COEFF(0.587, 255), .y_b = COEFF(0.114, 255),
|
||||
.cb_r = COEFF(-0.1687, 255), .cb_g = COEFF(-0.3313, 255), .cb_b = COEFF(0.5, 255),
|
||||
.cr_r = COEFF(0.5, 255), .cr_g = COEFF(-0.4187, 255), .cr_b = COEFF(-0.0813, 255),
|
||||
.y_offset = 0,
|
||||
};
|
||||
|
||||
/* BT.709 Limited range */
|
||||
static const struct rgb2yuv_matrix rgb2yuv_bt709l = {
|
||||
.y_r = COEFF(0.2126, 219), .y_g = COEFF(0.7152, 219), .y_b = COEFF(0.0722, 219),
|
||||
.cb_r = COEFF(-0.1146, 224), .cb_g = COEFF(-0.3854, 224), .cb_b = COEFF(0.5, 224),
|
||||
.cr_r = COEFF(0.5, 224), .cr_g = COEFF(-0.4542, 224), .cr_b = COEFF(-0.0458, 224),
|
||||
.y_offset = RGB2YUV_LIMITED_Y_OFFSET,
|
||||
};
|
||||
|
||||
/* BT.2020 Limited range */
|
||||
static const struct rgb2yuv_matrix rgb2yuv_bt2020l = {
|
||||
.y_r = COEFF(0.2627, 219), .y_g = COEFF(0.6780, 219), .y_b = COEFF(0.0593, 219),
|
||||
.cb_r = COEFF(-0.1396, 224), .cb_g = COEFF(-0.3604, 224), .cb_b = COEFF(0.5, 224),
|
||||
.cr_r = COEFF(0.5, 224), .cr_g = COEFF(-0.4598, 224), .cr_b = COEFF(-0.0402, 224),
|
||||
.y_offset = RGB2YUV_LIMITED_Y_OFFSET,
|
||||
};
|
||||
|
||||
static const struct rgb2yuv_matrix *
|
||||
vop2_rgb2yuv_get_matrix(enum vop_csc_format csc)
|
||||
{
|
||||
switch (csc) {
|
||||
case CSC_BT601L:
|
||||
return &rgb2yuv_bt601l;
|
||||
case CSC_BT601F:
|
||||
return &rgb2yuv_bt601f;
|
||||
case CSC_BT2020L:
|
||||
return &rgb2yuv_bt2020l;
|
||||
case CSC_BT709L:
|
||||
default:
|
||||
return &rgb2yuv_bt709l;
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert an RGB (16bpc) to YUV444 (16bpc). */
|
||||
static void vop2_rgb16_to_yuv16(int v4l2_cs, u16 r, u16 g, u16 b,
|
||||
u16 *y, u16 *cb, u16 *cr)
|
||||
{
|
||||
enum vop_csc_format csc = vop2_convert_csc_mode(v4l2_cs);
|
||||
const struct rgb2yuv_matrix *m = vop2_rgb2yuv_get_matrix(csc);
|
||||
s64 rs = r, gs = g, bs = b;
|
||||
s64 ys, cbs, crs;
|
||||
|
||||
ys = m->y_r * rs + m->y_g * gs + m->y_b * bs;
|
||||
cbs = m->cb_r * rs + m->cb_g * gs + m->cb_b * bs;
|
||||
crs = m->cr_r * rs + m->cr_g * gs + m->cr_b * bs;
|
||||
|
||||
*y = (ys >> 16) + m->y_offset;
|
||||
*cb = (cbs >> 16) + RGB2YUV_CHROMA_OFFSET;
|
||||
*cr = (crs >> 16) + RGB2YUV_CHROMA_OFFSET;
|
||||
}
|
||||
|
||||
static void vop2_crtc_enable_irq(struct vop2_video_port *vp, u32 irq)
|
||||
{
|
||||
struct vop2 *vop2 = vp->vop2;
|
||||
|
|
@ -1554,12 +1640,58 @@ static void vop2_dither_setup(struct drm_crtc *crtc, u32 *dsp_ctrl)
|
|||
DITHER_DOWN_ALLEGRO);
|
||||
}
|
||||
|
||||
static void vop2_post_config(struct drm_crtc *crtc)
|
||||
static void vop2_bgcolor_setup(struct drm_crtc *crtc, bool force,
|
||||
struct drm_crtc_state *new_crtc_state,
|
||||
struct drm_crtc_state *old_crtc_state)
|
||||
{
|
||||
struct rockchip_crtc_state *new_vcstate = to_rockchip_crtc_state(new_crtc_state);
|
||||
struct rockchip_crtc_state *old_vcstate = to_rockchip_crtc_state(old_crtc_state);
|
||||
struct vop2_video_port *vp = to_vop2_video_port(crtc);
|
||||
u64 bgcolor = new_crtc_state->background_color;
|
||||
u16 y, cb, cr;
|
||||
u32 val;
|
||||
|
||||
if (!force && old_crtc_state->background_color == bgcolor &&
|
||||
old_vcstate->color_space == new_vcstate->color_space)
|
||||
return;
|
||||
|
||||
/*
|
||||
* Background color is programmed with 10 bits of precision, using YUV
|
||||
* format when operating in YUV overlay mode, and RGB otherwise.
|
||||
*/
|
||||
if (new_vcstate->yuv_overlay) {
|
||||
vop2_rgb16_to_yuv16(new_vcstate->color_space,
|
||||
DRM_ARGB64_GETR(bgcolor),
|
||||
DRM_ARGB64_GETG(bgcolor),
|
||||
DRM_ARGB64_GETB(bgcolor),
|
||||
&y, &cb, &cr);
|
||||
|
||||
val = FIELD_PREP(RK3568_VP_DSP_BG__DSP_BG_RED, cr >> 6);
|
||||
FIELD_MODIFY(RK3568_VP_DSP_BG__DSP_BG_GREEN, &val, y >> 6);
|
||||
FIELD_MODIFY(RK3568_VP_DSP_BG__DSP_BG_BLUE, &val, cb >> 6);
|
||||
} else {
|
||||
/*
|
||||
* Since performance is more important than accuracy here, make
|
||||
* use of the DRM_ARGB64_GET*_BPCS() helpers.
|
||||
*/
|
||||
val = FIELD_PREP(RK3568_VP_DSP_BG__DSP_BG_RED,
|
||||
DRM_ARGB64_GETR_BPCS(bgcolor, 10));
|
||||
FIELD_MODIFY(RK3568_VP_DSP_BG__DSP_BG_GREEN, &val,
|
||||
DRM_ARGB64_GETG_BPCS(bgcolor, 10));
|
||||
FIELD_MODIFY(RK3568_VP_DSP_BG__DSP_BG_BLUE, &val,
|
||||
DRM_ARGB64_GETB_BPCS(bgcolor, 10));
|
||||
}
|
||||
|
||||
vop2_vp_write(vp, RK3568_VP_DSP_BG, val);
|
||||
}
|
||||
|
||||
static void vop2_post_config(struct drm_crtc *crtc, bool force,
|
||||
struct drm_crtc_state *new_crtc_state,
|
||||
struct drm_crtc_state *old_crtc_state)
|
||||
{
|
||||
struct vop2_video_port *vp = to_vop2_video_port(crtc);
|
||||
struct vop2 *vop2 = vp->vop2;
|
||||
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
|
||||
u64 bgcolor = crtc->state->background_color;
|
||||
struct drm_display_mode *mode = &new_crtc_state->adjusted_mode;
|
||||
u16 vtotal = mode->crtc_vtotal;
|
||||
u16 hdisplay = mode->crtc_hdisplay;
|
||||
u16 hact_st = mode->crtc_htotal - mode->crtc_hsync_start;
|
||||
|
|
@ -1605,15 +1737,7 @@ static void vop2_post_config(struct drm_crtc *crtc)
|
|||
vop2_vp_write(vp, RK3568_VP_POST_DSP_VACT_INFO_F1, val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Background color is programmed with 10 bits of precision.
|
||||
* Since performance is more important than accuracy here,
|
||||
* make use of the DRM_ARGB64_GET*_BPCS() helpers.
|
||||
*/
|
||||
val = FIELD_PREP(RK3568_VP_DSP_BG__DSP_BG_RED, DRM_ARGB64_GETR_BPCS(bgcolor, 10));
|
||||
FIELD_MODIFY(RK3568_VP_DSP_BG__DSP_BG_GREEN, &val, DRM_ARGB64_GETG_BPCS(bgcolor, 10));
|
||||
FIELD_MODIFY(RK3568_VP_DSP_BG__DSP_BG_BLUE, &val, DRM_ARGB64_GETB_BPCS(bgcolor, 10));
|
||||
vop2_vp_write(vp, RK3568_VP_DSP_BG, val);
|
||||
vop2_bgcolor_setup(crtc, force, new_crtc_state, old_crtc_state);
|
||||
}
|
||||
|
||||
static int us_to_vertical_line(struct drm_display_mode *mode, int us)
|
||||
|
|
@ -1628,8 +1752,9 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
|
|||
struct vop2 *vop2 = vp->vop2;
|
||||
const struct vop2_data *vop2_data = vop2->data;
|
||||
const struct vop2_video_port_data *vp_data = &vop2_data->vp[vp->id];
|
||||
struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
|
||||
struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
|
||||
struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc->state);
|
||||
struct rockchip_crtc_state *vcstate = to_rockchip_crtc_state(crtc_state);
|
||||
struct drm_display_mode *mode = &crtc->state->adjusted_mode;
|
||||
unsigned long clock = mode->crtc_clock * 1000;
|
||||
u16 hsync_len = mode->crtc_hsync_end - mode->crtc_hsync_start;
|
||||
|
|
@ -1699,6 +1824,22 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
|
|||
if (vcstate->output_mode == ROCKCHIP_OUT_MODE_AAAA &&
|
||||
!(vp_data->feature & VOP2_VP_FEATURE_OUTPUT_10BIT))
|
||||
out_mode = ROCKCHIP_OUT_MODE_P888;
|
||||
else if (vcstate->output_mode == ROCKCHIP_OUT_MODE_YUV422 &&
|
||||
vop2->version == VOP_VERSION_RK3576)
|
||||
switch (vcstate->output_type) {
|
||||
case DRM_MODE_CONNECTOR_DisplayPort:
|
||||
case DRM_MODE_CONNECTOR_eDP:
|
||||
out_mode = ROCKCHIP_OUT_MODE_YUV422_RK3576_DP;
|
||||
break;
|
||||
case DRM_MODE_CONNECTOR_HDMIA:
|
||||
out_mode = ROCKCHIP_OUT_MODE_YUV422_RK3576_HDMI;
|
||||
break;
|
||||
default:
|
||||
drm_err(vop2->drm, "Unknown DRM_MODE_CONNECTOR %d\n",
|
||||
vcstate->output_type);
|
||||
vop2_unlock(vop2);
|
||||
return;
|
||||
}
|
||||
else
|
||||
out_mode = vcstate->output_mode;
|
||||
|
||||
|
|
@ -1799,7 +1940,7 @@ static void vop2_crtc_atomic_enable(struct drm_crtc *crtc,
|
|||
|
||||
clk_set_rate(vp->dclk, clock);
|
||||
|
||||
vop2_post_config(crtc);
|
||||
vop2_post_config(crtc, true, crtc_state, old_crtc_state);
|
||||
|
||||
vop2_cfg_done(vp);
|
||||
|
||||
|
|
@ -1874,6 +2015,7 @@ static void vop2_crtc_atomic_flush(struct drm_crtc *crtc,
|
|||
struct drm_atomic_commit *state)
|
||||
{
|
||||
struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
|
||||
struct drm_crtc_state *old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
|
||||
struct vop2_video_port *vp = to_vop2_video_port(crtc);
|
||||
struct vop2 *vop2 = vp->vop2;
|
||||
|
||||
|
|
@ -1881,7 +2023,7 @@ static void vop2_crtc_atomic_flush(struct drm_crtc *crtc,
|
|||
if (!drm_atomic_crtc_needs_modeset(crtc_state) && crtc_state->color_mgmt_changed)
|
||||
vop2_crtc_atomic_try_set_gamma_locked(vop2, vp, crtc, crtc_state);
|
||||
|
||||
vop2_post_config(crtc);
|
||||
vop2_post_config(crtc, false, crtc_state, old_crtc_state);
|
||||
|
||||
vop2_cfg_done(vp);
|
||||
|
||||
|
|
|
|||
|
|
@ -373,7 +373,7 @@ enum vop_csc_format {
|
|||
CSC_BT601L,
|
||||
CSC_BT709L,
|
||||
CSC_BT601F,
|
||||
CSC_BT2020,
|
||||
CSC_BT2020L,
|
||||
};
|
||||
|
||||
enum src_factor_mode {
|
||||
|
|
|
|||
|
|
@ -2114,7 +2114,7 @@ static u32 rk3568_vop2_read_layer_cfg(struct vop2 *vop2)
|
|||
return vop2_readl(vop2, RK3568_OVL_LAYER_SEL);
|
||||
}
|
||||
|
||||
static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2, u32 cfg)
|
||||
static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2)
|
||||
{
|
||||
u32 atv_layer_cfg;
|
||||
int ret;
|
||||
|
|
@ -2123,21 +2123,19 @@ static void rk3568_vop2_wait_for_layer_cfg_done(struct vop2 *vop2, u32 cfg)
|
|||
* Spin until the previous layer configuration is done.
|
||||
*/
|
||||
ret = readx_poll_timeout_atomic(rk3568_vop2_read_layer_cfg, vop2, atv_layer_cfg,
|
||||
atv_layer_cfg == cfg, 10, 50 * 1000);
|
||||
atv_layer_cfg == vop2->old_layer_sel, 10, 50 * 1000);
|
||||
if (ret)
|
||||
drm_err_ratelimited(vop2->drm, "wait layer cfg done timeout: 0x%x--0x%x\n",
|
||||
atv_layer_cfg, cfg);
|
||||
atv_layer_cfg, vop2->old_layer_sel);
|
||||
}
|
||||
|
||||
static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp)
|
||||
{
|
||||
struct vop2 *vop2 = vp->vop2;
|
||||
struct drm_plane *plane;
|
||||
u32 layer_sel = 0;
|
||||
u32 layer_sel;
|
||||
u32 port_sel;
|
||||
u32 old_layer_sel = 0;
|
||||
u32 atv_layer_sel = 0;
|
||||
u32 old_port_sel = 0;
|
||||
u32 atv_layer_sel;
|
||||
u8 layer_id;
|
||||
u8 old_layer_id;
|
||||
u8 layer_sel_id;
|
||||
|
|
@ -2160,8 +2158,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp)
|
|||
else
|
||||
ovl_ctrl &= ~RK3568_OVL_CTRL__YUV_MODE(vp->id);
|
||||
|
||||
old_port_sel = vop2->old_port_sel;
|
||||
port_sel = old_port_sel;
|
||||
port_sel = vop2->old_port_sel;
|
||||
port_sel &= RK3568_OVL_PORT_SEL__SEL_PORT;
|
||||
|
||||
if (vp0->nlayers)
|
||||
|
|
@ -2187,8 +2184,7 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp)
|
|||
port_sel |= FIELD_PREP(RK3588_OVL_PORT_SET__PORT3_MUX, 7);
|
||||
|
||||
atv_layer_sel = vop2_readl(vop2, RK3568_OVL_LAYER_SEL);
|
||||
old_layer_sel = vop2->old_layer_sel;
|
||||
layer_sel = old_layer_sel;
|
||||
layer_sel = vop2->old_layer_sel;
|
||||
|
||||
ofs = 0;
|
||||
for (i = 0; i < vp->id; i++)
|
||||
|
|
@ -2272,8 +2268,6 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp)
|
|||
old_win->data->layer_sel_id[vp->id]);
|
||||
}
|
||||
|
||||
vop2->old_layer_sel = layer_sel;
|
||||
vop2->old_port_sel = port_sel;
|
||||
/*
|
||||
* As the RK3568_OVL_LAYER_SEL and RK3568_OVL_PORT_SEL are shared by all Video Ports,
|
||||
* and the configuration take effect by one Video Port's vsync.
|
||||
|
|
@ -2288,17 +2282,8 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp)
|
|||
* lead to the configuration of the previous VP being take effect along with the VSYNC
|
||||
* of the new VP.
|
||||
*/
|
||||
if (layer_sel != old_layer_sel || port_sel != old_port_sel)
|
||||
ovl_ctrl |= FIELD_PREP(RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL, vp->id);
|
||||
vop2_writel(vop2, RK3568_OVL_CTRL, ovl_ctrl);
|
||||
|
||||
if (port_sel != old_port_sel) {
|
||||
vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel);
|
||||
vop2_cfg_done(vp);
|
||||
rk3568_vop2_wait_for_port_mux_done(vop2);
|
||||
}
|
||||
|
||||
if (layer_sel != old_layer_sel && atv_layer_sel != old_layer_sel) {
|
||||
if (layer_sel != vop2->old_layer_sel && atv_layer_sel != vop2->old_layer_sel) {
|
||||
cfg_done = vop2_readl(vop2, RK3568_REG_CFG_DONE);
|
||||
cfg_done &= (BIT(vop2->data->nr_vps) - 1);
|
||||
cfg_done &= ~BIT(vp->id);
|
||||
|
|
@ -2306,10 +2291,23 @@ static void rk3568_vop2_setup_layer_mixer(struct vop2_video_port *vp)
|
|||
* Changes of other VPs' overlays have not taken effect
|
||||
*/
|
||||
if (cfg_done)
|
||||
rk3568_vop2_wait_for_layer_cfg_done(vop2, vop2->old_layer_sel);
|
||||
rk3568_vop2_wait_for_layer_cfg_done(vop2);
|
||||
}
|
||||
|
||||
if (layer_sel != vop2->old_layer_sel || port_sel != vop2->old_port_sel)
|
||||
ovl_ctrl |= FIELD_PREP(RK3568_OVL_CTRL__LAYERSEL_REGDONE_SEL, vp->id);
|
||||
vop2_writel(vop2, RK3568_OVL_CTRL, ovl_ctrl);
|
||||
|
||||
if (port_sel != vop2->old_port_sel) {
|
||||
vop2->old_port_sel = port_sel;
|
||||
vop2_writel(vop2, RK3568_OVL_PORT_SEL, port_sel);
|
||||
vop2_cfg_done(vp);
|
||||
rk3568_vop2_wait_for_port_mux_done(vop2);
|
||||
}
|
||||
|
||||
vop2->old_layer_sel = layer_sel;
|
||||
vop2_writel(vop2, RK3568_OVL_LAYER_SEL, layer_sel);
|
||||
|
||||
mutex_unlock(&vop2->ovl_lock);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -129,7 +129,6 @@ int drm_sched_entity_init(struct drm_sched_entity *entity,
|
|||
return -ENOMEM;
|
||||
|
||||
INIT_LIST_HEAD(&entity->list);
|
||||
entity->rq = NULL;
|
||||
entity->guilty = guilty;
|
||||
entity->priority = priority;
|
||||
entity->last_user = current->group_leader;
|
||||
|
|
@ -280,9 +279,6 @@ void drm_sched_entity_kill(struct drm_sched_entity *entity)
|
|||
struct drm_sched_job *job;
|
||||
struct dma_fence *prev;
|
||||
|
||||
if (!entity->rq)
|
||||
return;
|
||||
|
||||
spin_lock(&entity->lock);
|
||||
entity->stopped = true;
|
||||
drm_sched_rq_remove_entity(entity->rq, entity);
|
||||
|
|
@ -329,14 +325,11 @@ EXPORT_SYMBOL(drm_sched_entity_kill);
|
|||
*/
|
||||
long drm_sched_entity_flush(struct drm_sched_entity *entity, long timeout)
|
||||
{
|
||||
struct drm_gpu_scheduler *sched;
|
||||
struct drm_gpu_scheduler *sched =
|
||||
container_of(entity->rq, typeof(*sched), rq);
|
||||
struct task_struct *last_user;
|
||||
long ret = timeout;
|
||||
|
||||
if (!entity->rq)
|
||||
return 0;
|
||||
|
||||
sched = container_of(entity->rq, typeof(*sched), rq);
|
||||
/*
|
||||
* The client will not queue more jobs during this fini - consume
|
||||
* existing queued ones, or discard them on SIGKILL.
|
||||
|
|
|
|||
|
|
@ -588,15 +588,6 @@ int drm_sched_job_init(struct drm_sched_job *job,
|
|||
u32 credits, void *owner,
|
||||
uint64_t drm_client_id)
|
||||
{
|
||||
if (!entity->rq) {
|
||||
/* This will most likely be followed by missing frames
|
||||
* or worse--a blank screen--leave a trail in the
|
||||
* logs, so this can be debugged easier.
|
||||
*/
|
||||
dev_err(job->sched->dev, "%s: entity has no rq!\n", __func__);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (unlikely(!credits)) {
|
||||
pr_err("*ERROR* %s: credits cannot be 0!\n", __func__);
|
||||
return -EINVAL;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright (c) 2025 Valve Corporation */
|
||||
|
||||
#include <linux/completion.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/minmax.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
#include "sched_tests.h"
|
||||
|
||||
|
|
@ -235,6 +239,360 @@ static void drm_sched_basic_cancel(struct kunit *test)
|
|||
KUNIT_ASSERT_EQ(test, job->hw_fence.error, -ECANCELED);
|
||||
}
|
||||
|
||||
struct sched_concurrent_context {
|
||||
struct drm_mock_scheduler *sched;
|
||||
struct workqueue_struct *sub_wq;
|
||||
struct kunit *test;
|
||||
struct completion wait_go;
|
||||
};
|
||||
|
||||
KUNIT_DEFINE_ACTION_WRAPPER(drm_mock_sched_fini_wrap, drm_mock_sched_fini,
|
||||
struct drm_mock_scheduler *);
|
||||
|
||||
KUNIT_DEFINE_ACTION_WRAPPER(drm_mock_sched_entity_free_wrap, drm_mock_sched_entity_free,
|
||||
struct drm_mock_sched_entity *);
|
||||
|
||||
static void complete_destroy_workqueue(void *context)
|
||||
{
|
||||
struct sched_concurrent_context *ctx = context;
|
||||
|
||||
complete_all(&ctx->wait_go);
|
||||
|
||||
destroy_workqueue(ctx->sub_wq);
|
||||
}
|
||||
|
||||
static int drm_sched_concurrent_init(struct kunit *test)
|
||||
{
|
||||
struct sched_concurrent_context *ctx;
|
||||
int ret;
|
||||
|
||||
ctx = kunit_kzalloc(test, sizeof(*ctx), GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_NULL(test, ctx);
|
||||
|
||||
init_completion(&ctx->wait_go);
|
||||
|
||||
ctx->sched = drm_mock_sched_new(test, MAX_SCHEDULE_TIMEOUT);
|
||||
|
||||
ret = kunit_add_action_or_reset(test, drm_mock_sched_fini_wrap, ctx->sched);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
/* Use an unbounded workqueue to maximize job submission concurrency */
|
||||
ctx->sub_wq = alloc_workqueue("drm-sched-submitters-wq", WQ_UNBOUND,
|
||||
WQ_UNBOUND_MAX_ACTIVE);
|
||||
KUNIT_ASSERT_NOT_NULL(test, ctx->sub_wq);
|
||||
|
||||
ret = kunit_add_action_or_reset(test, complete_destroy_workqueue, ctx);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
|
||||
ctx->test = test;
|
||||
test->priv = ctx;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct drm_sched_parallel_params {
|
||||
const char *description;
|
||||
unsigned int num_jobs;
|
||||
unsigned int num_workers;
|
||||
};
|
||||
|
||||
static const struct drm_sched_parallel_params drm_sched_parallel_cases[] = {
|
||||
{
|
||||
.description = "Parallel submission of multiple jobs per worker",
|
||||
.num_jobs = 8,
|
||||
.num_workers = 16,
|
||||
},
|
||||
};
|
||||
|
||||
static void
|
||||
drm_sched_parallel_desc(const struct drm_sched_parallel_params *params, char *desc)
|
||||
{
|
||||
strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(drm_sched_parallel, drm_sched_parallel_cases, drm_sched_parallel_desc);
|
||||
|
||||
struct parallel_worker {
|
||||
struct work_struct work;
|
||||
struct sched_concurrent_context *ctx;
|
||||
struct drm_mock_sched_entity *entity;
|
||||
struct drm_mock_sched_job **jobs;
|
||||
unsigned int id;
|
||||
};
|
||||
|
||||
static void drm_sched_parallel_worker(struct work_struct *work)
|
||||
{
|
||||
const struct drm_sched_parallel_params *params;
|
||||
struct sched_concurrent_context *test_ctx;
|
||||
struct parallel_worker *worker;
|
||||
unsigned int i;
|
||||
|
||||
worker = container_of(work, struct parallel_worker, work);
|
||||
test_ctx = worker->ctx;
|
||||
params = test_ctx->test->param_value;
|
||||
|
||||
wait_for_completion(&test_ctx->wait_go);
|
||||
|
||||
kunit_info(test_ctx->test, "Parallel worker %u submitting %u jobs started\n",
|
||||
worker->id, params->num_jobs);
|
||||
|
||||
for (i = 0; i < params->num_jobs; i++)
|
||||
drm_mock_sched_job_submit(worker->jobs[i]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Spawns workers that submit a sequence of jobs to the mock scheduler.
|
||||
* Once all jobs are submitted, the timeline is manually advanced.
|
||||
*/
|
||||
static void drm_sched_parallel_submit_test(struct kunit *test)
|
||||
{
|
||||
struct sched_concurrent_context *ctx = test->priv;
|
||||
const struct drm_sched_parallel_params *params = test->param_value;
|
||||
struct parallel_worker *workers, *worker;
|
||||
struct drm_mock_sched_job *job;
|
||||
unsigned int i, j, completed_jobs, total_jobs;
|
||||
bool done;
|
||||
int ret;
|
||||
|
||||
KUNIT_ASSERT_GT(test, params->num_workers, 0);
|
||||
KUNIT_ASSERT_GT(test, params->num_jobs, 0);
|
||||
|
||||
workers = kunit_kcalloc(test, params->num_workers, sizeof(*workers),
|
||||
GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_NULL(test, workers);
|
||||
|
||||
/*
|
||||
* Init workers only after all jobs and entities have been successfully
|
||||
* allocated. In this way, the cleanup logic for when an assertion fail
|
||||
* can be simplified.
|
||||
*/
|
||||
for (i = 0; i < params->num_workers; i++) {
|
||||
worker = &workers[i];
|
||||
worker->id = i;
|
||||
worker->ctx = ctx;
|
||||
worker->entity = drm_mock_sched_entity_new(test,
|
||||
DRM_SCHED_PRIORITY_NORMAL,
|
||||
ctx->sched);
|
||||
|
||||
worker->jobs = kunit_kcalloc(test, params->num_jobs,
|
||||
sizeof(*worker->jobs), GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_NULL(test, worker->jobs);
|
||||
|
||||
for (j = 0; j < params->num_jobs; j++) {
|
||||
job = drm_mock_sched_job_new(test, worker->entity);
|
||||
worker->jobs[j] = job;
|
||||
}
|
||||
|
||||
ret = kunit_add_action_or_reset(test, drm_mock_sched_entity_free_wrap,
|
||||
worker->entity);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
}
|
||||
|
||||
for (i = 0; i < params->num_workers; i++) {
|
||||
worker = &workers[i];
|
||||
INIT_WORK(&worker->work, drm_sched_parallel_worker);
|
||||
queue_work(ctx->sub_wq, &worker->work);
|
||||
}
|
||||
|
||||
complete_all(&ctx->wait_go);
|
||||
flush_workqueue(ctx->sub_wq);
|
||||
|
||||
for (i = 0; i < params->num_workers; i++) {
|
||||
worker = &workers[i];
|
||||
for (j = 0; j < params->num_jobs; j++) {
|
||||
job = worker->jobs[j];
|
||||
done = drm_mock_sched_job_wait_scheduled(job, HZ);
|
||||
KUNIT_EXPECT_TRUE(test, done);
|
||||
}
|
||||
}
|
||||
|
||||
total_jobs = params->num_workers * params->num_jobs;
|
||||
completed_jobs = drm_mock_sched_advance(ctx->sched, total_jobs);
|
||||
KUNIT_EXPECT_EQ(test, completed_jobs, total_jobs);
|
||||
|
||||
for (i = 0; i < params->num_workers; i++) {
|
||||
worker = &workers[i];
|
||||
for (j = 0; j < params->num_jobs; j++) {
|
||||
job = worker->jobs[j];
|
||||
done = drm_mock_sched_job_wait_finished(job, HZ);
|
||||
KUNIT_EXPECT_TRUE(test, done);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct drm_sched_interleaved_params {
|
||||
const char *description;
|
||||
unsigned int test_duration_ms;
|
||||
unsigned int job_base_duration_us;
|
||||
unsigned int num_workers;
|
||||
unsigned int num_in_flight_jobs;
|
||||
};
|
||||
|
||||
static const struct drm_sched_interleaved_params drm_sched_interleaved_cases[] = {
|
||||
{
|
||||
.description = "Interleaved submission of multiple jobs per worker",
|
||||
.test_duration_ms = 1000,
|
||||
.job_base_duration_us = 100,
|
||||
.num_workers = 16,
|
||||
.num_in_flight_jobs = 8,
|
||||
},
|
||||
};
|
||||
|
||||
static void
|
||||
drm_sched_interleaved_desc(const struct drm_sched_interleaved_params *params, char *desc)
|
||||
{
|
||||
strscpy(desc, params->description, KUNIT_PARAM_DESC_SIZE);
|
||||
}
|
||||
|
||||
KUNIT_ARRAY_PARAM(drm_sched_interleaved, drm_sched_interleaved_cases,
|
||||
drm_sched_interleaved_desc);
|
||||
|
||||
struct interleaved_worker {
|
||||
struct work_struct work;
|
||||
struct sched_concurrent_context *ctx;
|
||||
struct drm_mock_sched_entity *entity;
|
||||
struct drm_mock_sched_job **jobs;
|
||||
unsigned int id;
|
||||
unsigned int job_count;
|
||||
unsigned int job_duration_us;
|
||||
};
|
||||
|
||||
static void drm_sched_interleaved_worker(struct work_struct *work)
|
||||
{
|
||||
struct sched_concurrent_context *test_ctx;
|
||||
const struct drm_sched_interleaved_params *params;
|
||||
struct interleaved_worker *worker;
|
||||
unsigned int i, j, max_in_flight_job;
|
||||
unsigned long timeout;
|
||||
bool done;
|
||||
|
||||
worker = container_of(work, struct interleaved_worker, work);
|
||||
test_ctx = worker->ctx;
|
||||
params = test_ctx->test->param_value;
|
||||
|
||||
wait_for_completion(&test_ctx->wait_go);
|
||||
|
||||
kunit_info(test_ctx->test, "Worker %u submitting %u jobs of %u us started\n",
|
||||
worker->id, worker->job_count, worker->job_duration_us);
|
||||
|
||||
timeout = msecs_to_jiffies(params->test_duration_ms * 2);
|
||||
|
||||
/* Fill the submission window */
|
||||
max_in_flight_job = min(worker->job_count, params->num_in_flight_jobs);
|
||||
for (i = 0; i < max_in_flight_job; i++)
|
||||
drm_mock_sched_job_submit(worker->jobs[i]);
|
||||
|
||||
/* Keep the window full by submitting a new job at once until done */
|
||||
for (i = 0; i < worker->job_count; i++) {
|
||||
done = drm_mock_sched_job_wait_finished(worker->jobs[i], timeout);
|
||||
if (!done)
|
||||
kunit_info(test_ctx->test, "Job %u of worker %u timed out\n",
|
||||
i, worker->id);
|
||||
|
||||
j = i + max_in_flight_job;
|
||||
if (j < worker->job_count)
|
||||
drm_mock_sched_job_submit(worker->jobs[j]);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Spawns workers that submit a sequence of jobs to the mock scheduler. Job
|
||||
* durations are chosen as multiples of a base duration value specified as
|
||||
* a test parameter. Since the scheduler serializes jobs from all workers,
|
||||
* the total test duration budget is divided into equal shares among workers.
|
||||
* These shares are then used to compute the number of jobs that each worker
|
||||
* can submit.
|
||||
*/
|
||||
static void drm_sched_interleaved_submit_test(struct kunit *test)
|
||||
{
|
||||
const struct drm_sched_interleaved_params *params = test->param_value;
|
||||
struct sched_concurrent_context *ctx = test->priv;
|
||||
struct interleaved_worker *workers, *worker;
|
||||
struct drm_mock_sched_job *job;
|
||||
unsigned int worker_share_us;
|
||||
unsigned int i, j;
|
||||
bool done;
|
||||
int ret;
|
||||
|
||||
KUNIT_ASSERT_GT(test, params->num_workers, 0);
|
||||
KUNIT_ASSERT_GT(test, params->job_base_duration_us, 0);
|
||||
|
||||
workers = kunit_kcalloc(test, params->num_workers, sizeof(*workers),
|
||||
GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_NULL(test, workers);
|
||||
|
||||
/* Divide the available test time into equal shares among the workers */
|
||||
worker_share_us = (params->test_duration_ms * USEC_PER_MSEC) /
|
||||
params->num_workers;
|
||||
|
||||
/*
|
||||
* Init workers only after all jobs and entities have been successfully
|
||||
* allocated. In this way, the cleanup logic for when an assertion fails
|
||||
* can be simplified.
|
||||
*/
|
||||
for (i = 0; i < params->num_workers; i++) {
|
||||
worker = &workers[i];
|
||||
worker->id = i;
|
||||
worker->ctx = ctx;
|
||||
|
||||
worker->job_duration_us = params->job_base_duration_us * (i + 1);
|
||||
worker->job_count = worker_share_us / worker->job_duration_us;
|
||||
worker->job_count = max(1U, worker->job_count);
|
||||
|
||||
worker->entity = drm_mock_sched_entity_new(test,
|
||||
DRM_SCHED_PRIORITY_NORMAL,
|
||||
ctx->sched);
|
||||
|
||||
worker->jobs = kunit_kcalloc(test, worker->job_count,
|
||||
sizeof(*worker->jobs), GFP_KERNEL);
|
||||
KUNIT_ASSERT_NOT_NULL(test, worker->jobs);
|
||||
|
||||
for (j = 0; j < worker->job_count; j++) {
|
||||
job = drm_mock_sched_job_new(test, worker->entity);
|
||||
drm_mock_sched_job_set_duration_us(job, worker->job_duration_us);
|
||||
|
||||
worker->jobs[j] = job;
|
||||
}
|
||||
|
||||
ret = kunit_add_action_or_reset(test, drm_mock_sched_entity_free_wrap,
|
||||
worker->entity);
|
||||
KUNIT_ASSERT_EQ(test, ret, 0);
|
||||
}
|
||||
|
||||
for (i = 0; i < params->num_workers; i++) {
|
||||
worker = &workers[i];
|
||||
INIT_WORK(&worker->work, drm_sched_interleaved_worker);
|
||||
queue_work(ctx->sub_wq, &worker->work);
|
||||
}
|
||||
|
||||
complete_all(&ctx->wait_go);
|
||||
flush_workqueue(ctx->sub_wq);
|
||||
|
||||
for (i = 0; i < params->num_workers; i++) {
|
||||
worker = &workers[i];
|
||||
for (j = 0; j < worker->job_count; j++) {
|
||||
job = worker->jobs[j];
|
||||
done = drm_mock_sched_job_is_finished(job);
|
||||
KUNIT_EXPECT_TRUE(test, done);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static struct kunit_case drm_sched_concurrent_tests[] = {
|
||||
KUNIT_CASE_PARAM(drm_sched_parallel_submit_test, drm_sched_parallel_gen_params),
|
||||
KUNIT_CASE_PARAM(drm_sched_interleaved_submit_test, drm_sched_interleaved_gen_params),
|
||||
{}
|
||||
};
|
||||
|
||||
static struct kunit_suite drm_sched_concurrent = {
|
||||
.name = "drm_sched_concurrent_tests",
|
||||
.init = drm_sched_concurrent_init,
|
||||
.test_cases = drm_sched_concurrent_tests,
|
||||
.attr = {
|
||||
.speed = KUNIT_SPEED_SLOW,
|
||||
},
|
||||
};
|
||||
|
||||
static struct kunit_case drm_sched_cancel_tests[] = {
|
||||
KUNIT_CASE(drm_sched_basic_cancel),
|
||||
{}
|
||||
|
|
@ -556,6 +914,7 @@ static struct kunit_suite drm_sched_credits = {
|
|||
};
|
||||
|
||||
kunit_test_suites(&drm_sched_basic,
|
||||
&drm_sched_concurrent,
|
||||
&drm_sched_timeout,
|
||||
&drm_sched_cancel,
|
||||
&drm_sched_priority,
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user