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:
Dave Airlie 2026-07-06 17:38:33 +10:00
commit 00422c79d0
154 changed files with 8309 additions and 2752 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = <&reg_vci_panel>;
iovcc-supply = <&reg_iovcc_panel>;
reset-gpios = <&gpio3 6 GPIO_ACTIVE_LOW>;
backlight = <&pwm_bl>;
port {
panel_in: endpoint {
remote-endpoint = <&dsi_out>;
};
};
};
};
...

View File

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

View File

@ -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:

View File

@ -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,.*":

View File

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

View File

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

View File

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

View File

@ -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
===========

View File

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

View File

@ -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 = {

View File

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

View File

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

View File

@ -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, &ethosudev->clks);
if (ethosudev->num_clks < 0)
return ethosudev->num_clks;
ret = drmm_mutex_init(&ethosudev->base, &ethosudev->perfmon_state.lock);
if (ret)
return ret;
ret = ethosu_job_init(ethosudev);
if (ret)
return ret;

View File

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

View File

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

View File

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

View 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(&ethosu->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(&ethosu->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)(&ethosu->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(&ethosu_priv->perfmons);
perfmon = xa_load(&ethosu_priv->perfmons, id);
ethosu_perfmon_get(perfmon);
xa_unlock(&ethosu_priv->perfmons);
return perfmon;
}
void ethosu_perfmon_open_file(struct ethosu_file_priv *ethosu_priv)
{
xa_init_flags(&ethosu_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, &ethosu->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(&ethosu_priv->perfmons, id, perfmon)
ethosu_perfmon_delete(ethosu_priv, perfmon);
xa_destroy(&ethosu_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(&ethosu_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(&ethosu_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, &ethosu->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, &ethosu->perfmon_state.lock) {
if (ethosu->perfmon_state.active || ethosu->global_perfmon) {
ethosu_perfmon_put(perfmon);
return -EBUSY;
}
ethosu->global_perfmon = perfmon;
}
return 0;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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",

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

@ -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,
};
/* -----------------------------------------------------------------------------

View File

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

View File

@ -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 =

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,7 +32,6 @@
#include <drm/drm_print.h>
#include "drm_crtc_internal.h"
#include "drm_internal.h"
/**
* DOC: overview

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View 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");

View 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");

View File

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

View File

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

View 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");

View File

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

View File

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

View File

@ -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,

View File

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

View File

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

View File

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

View File

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

View File

@ -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 = {

View File

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

View File

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

View File

@ -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 = {

View File

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

View File

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

View File

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

View File

@ -373,7 +373,7 @@ enum vop_csc_format {
CSC_BT601L,
CSC_BT709L,
CSC_BT601F,
CSC_BT2020,
CSC_BT2020L,
};
enum src_factor_mode {

View File

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

View File

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

View File

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

View File

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