Merge branch 'for-upstream/mali-dp' of git://linux-arm.org/linux-ld into drm-next
This pull includes the new Arm "komeda" DRM driver. It is currently hosted in the same repo as the other "mali-dp" driver because it is the next iteration of the IP. Signed-off-by: Dave Airlie <airlied@redhat.com> From: Liviu Dudau <Liviu.Dudau@arm.com> Link: https://patchwork.freedesktop.org/patch/msgid/20190131173600.GN25147@e110455-lin.cambridge.arm.com
This commit is contained in:
commit
74b7d6a913
|
@ -0,0 +1,73 @@
|
|||
Device Tree bindings for Arm Komeda display driver
|
||||
|
||||
Required properties:
|
||||
- compatible: Should be "arm,mali-d71"
|
||||
- reg: Physical base address and length of the registers in the system
|
||||
- interrupts: the interrupt line number of the device in the system
|
||||
- clocks: A list of phandle + clock-specifier pairs, one for each entry
|
||||
in 'clock-names'
|
||||
- clock-names: A list of clock names. It should contain:
|
||||
- "mclk": for the main processor clock
|
||||
- "pclk": for the APB interface clock
|
||||
- #address-cells: Must be 1
|
||||
- #size-cells: Must be 0
|
||||
|
||||
Required properties for sub-node: pipeline@nq
|
||||
Each device contains one or two pipeline sub-nodes (at least one), each
|
||||
pipeline node should provide properties:
|
||||
- reg: Zero-indexed identifier for the pipeline
|
||||
- clocks: A list of phandle + clock-specifier pairs, one for each entry
|
||||
in 'clock-names'
|
||||
- clock-names: should contain:
|
||||
- "pxclk": pixel clock
|
||||
- "aclk": AXI interface clock
|
||||
|
||||
- port: each pipeline connect to an encoder input port. The connection is
|
||||
modeled using the OF graph bindings specified in
|
||||
Documentation/devicetree/bindings/graph.txt
|
||||
|
||||
Optional properties:
|
||||
- memory-region: phandle to a node describing memory (see
|
||||
Documentation/devicetree/bindings/reserved-memory/reserved-memory.txt)
|
||||
to be used for the framebuffer; if not present, the framebuffer may
|
||||
be located anywhere in memory.
|
||||
|
||||
Example:
|
||||
/ {
|
||||
...
|
||||
|
||||
dp0: display@c00000 {
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
compatible = "arm,mali-d71";
|
||||
reg = <0xc00000 0x20000>;
|
||||
interrupts = <0 168 4>;
|
||||
clocks = <&dpu_mclk>, <&dpu_aclk>;
|
||||
clock-names = "mclk", "pclk";
|
||||
|
||||
dp0_pipe0: pipeline@0 {
|
||||
clocks = <&fpgaosc2>, <&dpu_aclk>;
|
||||
clock-names = "pxclk", "aclk";
|
||||
reg = <0>;
|
||||
|
||||
port {
|
||||
dp0_pipe0_out: endpoint {
|
||||
remote-endpoint = <&db_dvi0_in>;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
dp0_pipe1: pipeline@1 {
|
||||
clocks = <&fpgaosc2>, <&dpu_aclk>;
|
||||
clock-names = "pxclk", "aclk";
|
||||
reg = <1>;
|
||||
|
||||
port {
|
||||
dp0_pipe1_out: endpoint {
|
||||
remote-endpoint = <&db_dvi1_in>;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
...
|
||||
};
|
|
@ -0,0 +1,235 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0+
|
||||
|
||||
===================================
|
||||
Arm Framebuffer Compression (AFBC)
|
||||
===================================
|
||||
|
||||
AFBC is a proprietary lossless image compression protocol and format.
|
||||
It provides fine-grained random access and minimizes the amount of
|
||||
data transferred between IP blocks.
|
||||
|
||||
AFBC can be enabled on drivers which support it via use of the AFBC
|
||||
format modifiers defined in drm_fourcc.h. See DRM_FORMAT_MOD_ARM_AFBC(*).
|
||||
|
||||
All users of the AFBC modifiers must follow the usage guidelines laid
|
||||
out in this document, to ensure compatibility across different AFBC
|
||||
producers and consumers.
|
||||
|
||||
Components and Ordering
|
||||
=======================
|
||||
|
||||
AFBC streams can contain several components - where a component
|
||||
corresponds to a color channel (i.e. R, G, B, X, A, Y, Cb, Cr).
|
||||
The assignment of input/output color channels must be consistent
|
||||
between the encoder and the decoder for correct operation, otherwise
|
||||
the consumer will interpret the decoded data incorrectly.
|
||||
|
||||
Furthermore, when the lossless colorspace transform is used
|
||||
(AFBC_FORMAT_MOD_YTR, which should be enabled for RGB buffers for
|
||||
maximum compression efficiency), the component order must be:
|
||||
|
||||
* Component 0: R
|
||||
* Component 1: G
|
||||
* Component 2: B
|
||||
|
||||
The component ordering is communicated via the fourcc code in the
|
||||
fourcc:modifier pair. In general, component '0' is considered to
|
||||
reside in the least-significant bits of the corresponding linear
|
||||
format. For example, COMP(bits):
|
||||
|
||||
* DRM_FORMAT_ABGR8888
|
||||
|
||||
* Component 0: R(8)
|
||||
* Component 1: G(8)
|
||||
* Component 2: B(8)
|
||||
* Component 3: A(8)
|
||||
|
||||
* DRM_FORMAT_BGR888
|
||||
|
||||
* Component 0: R(8)
|
||||
* Component 1: G(8)
|
||||
* Component 2: B(8)
|
||||
|
||||
* DRM_FORMAT_YUYV
|
||||
|
||||
* Component 0: Y(8)
|
||||
* Component 1: Cb(8, 2x1 subsampled)
|
||||
* Component 2: Cr(8, 2x1 subsampled)
|
||||
|
||||
In AFBC, 'X' components are not treated any differently from any other
|
||||
component. Therefore, an AFBC buffer with fourcc DRM_FORMAT_XBGR8888
|
||||
encodes with 4 components, like so:
|
||||
|
||||
* DRM_FORMAT_XBGR8888
|
||||
|
||||
* Component 0: R(8)
|
||||
* Component 1: G(8)
|
||||
* Component 2: B(8)
|
||||
* Component 3: X(8)
|
||||
|
||||
Please note, however, that the inclusion of a "wasted" 'X' channel is
|
||||
bad for compression efficiency, and so it's recommended to avoid
|
||||
formats containing 'X' bits. If a fourth component is
|
||||
required/expected by the encoder/decoder, then it is recommended to
|
||||
instead use an equivalent format with alpha, setting all alpha bits to
|
||||
'1'. If there is no requirement for a fourth component, then a format
|
||||
which doesn't include alpha can be used, e.g. DRM_FORMAT_BGR888.
|
||||
|
||||
Number of Planes
|
||||
================
|
||||
|
||||
Formats which are typically multi-planar in linear layouts (e.g. YUV
|
||||
420), can be encoded into one, or multiple, AFBC planes. As with
|
||||
component order, the encoder and decoder must agree about the number
|
||||
of planes in order to correctly decode the buffer. The fourcc code is
|
||||
used to determine the number of encoded planes in an AFBC buffer,
|
||||
matching the number of planes for the linear (unmodified) format.
|
||||
Within each plane, the component ordering also follows the fourcc
|
||||
code:
|
||||
|
||||
For example:
|
||||
|
||||
* DRM_FORMAT_YUYV: nplanes = 1
|
||||
|
||||
* Plane 0:
|
||||
|
||||
* Component 0: Y(8)
|
||||
* Component 1: Cb(8, 2x1 subsampled)
|
||||
* Component 2: Cr(8, 2x1 subsampled)
|
||||
|
||||
* DRM_FORMAT_NV12: nplanes = 2
|
||||
|
||||
* Plane 0:
|
||||
|
||||
* Component 0: Y(8)
|
||||
|
||||
* Plane 1:
|
||||
|
||||
* Component 0: Cb(8, 2x1 subsampled)
|
||||
* Component 1: Cr(8, 2x1 subsampled)
|
||||
|
||||
Cross-device interoperability
|
||||
=============================
|
||||
|
||||
For maximum compatibility across devices, the table below defines
|
||||
canonical formats for use between AFBC-enabled devices. Formats which
|
||||
are listed here must be used exactly as specified when using the AFBC
|
||||
modifiers. Formats which are not listed should be avoided.
|
||||
|
||||
.. flat-table:: AFBC formats
|
||||
|
||||
* - Fourcc code
|
||||
- Description
|
||||
- Planes/Components
|
||||
|
||||
* - DRM_FORMAT_ABGR2101010
|
||||
- 10-bit per component RGB, with 2-bit alpha
|
||||
- Plane 0: 4 components
|
||||
* Component 0: R(10)
|
||||
* Component 1: G(10)
|
||||
* Component 2: B(10)
|
||||
* Component 3: A(2)
|
||||
|
||||
* - DRM_FORMAT_ABGR8888
|
||||
- 8-bit per component RGB, with 8-bit alpha
|
||||
- Plane 0: 4 components
|
||||
* Component 0: R(8)
|
||||
* Component 1: G(8)
|
||||
* Component 2: B(8)
|
||||
* Component 3: A(8)
|
||||
|
||||
* - DRM_FORMAT_BGR888
|
||||
- 8-bit per component RGB
|
||||
- Plane 0: 3 components
|
||||
* Component 0: R(8)
|
||||
* Component 1: G(8)
|
||||
* Component 2: B(8)
|
||||
|
||||
* - DRM_FORMAT_BGR565
|
||||
- 5/6-bit per component RGB
|
||||
- Plane 0: 3 components
|
||||
* Component 0: R(5)
|
||||
* Component 1: G(6)
|
||||
* Component 2: B(5)
|
||||
|
||||
* - DRM_FORMAT_ABGR1555
|
||||
- 5-bit per component RGB, with 1-bit alpha
|
||||
- Plane 0: 4 components
|
||||
* Component 0: R(5)
|
||||
* Component 1: G(5)
|
||||
* Component 2: B(5)
|
||||
* Component 3: A(1)
|
||||
|
||||
* - DRM_FORMAT_VUY888
|
||||
- 8-bit per component YCbCr 444, single plane
|
||||
- Plane 0: 3 components
|
||||
* Component 0: Y(8)
|
||||
* Component 1: Cb(8)
|
||||
* Component 2: Cr(8)
|
||||
|
||||
* - DRM_FORMAT_VUY101010
|
||||
- 10-bit per component YCbCr 444, single plane
|
||||
- Plane 0: 3 components
|
||||
* Component 0: Y(10)
|
||||
* Component 1: Cb(10)
|
||||
* Component 2: Cr(10)
|
||||
|
||||
* - DRM_FORMAT_YUYV
|
||||
- 8-bit per component YCbCr 422, single plane
|
||||
- Plane 0: 3 components
|
||||
* Component 0: Y(8)
|
||||
* Component 1: Cb(8, 2x1 subsampled)
|
||||
* Component 2: Cr(8, 2x1 subsampled)
|
||||
|
||||
* - DRM_FORMAT_NV16
|
||||
- 8-bit per component YCbCr 422, two plane
|
||||
- Plane 0: 1 component
|
||||
* Component 0: Y(8)
|
||||
Plane 1: 2 components
|
||||
* Component 0: Cb(8, 2x1 subsampled)
|
||||
* Component 1: Cr(8, 2x1 subsampled)
|
||||
|
||||
* - DRM_FORMAT_Y210
|
||||
- 10-bit per component YCbCr 422, single plane
|
||||
- Plane 0: 3 components
|
||||
* Component 0: Y(10)
|
||||
* Component 1: Cb(10, 2x1 subsampled)
|
||||
* Component 2: Cr(10, 2x1 subsampled)
|
||||
|
||||
* - DRM_FORMAT_P210
|
||||
- 10-bit per component YCbCr 422, two plane
|
||||
- Plane 0: 1 component
|
||||
* Component 0: Y(10)
|
||||
Plane 1: 2 components
|
||||
* Component 0: Cb(10, 2x1 subsampled)
|
||||
* Component 1: Cr(10, 2x1 subsampled)
|
||||
|
||||
* - DRM_FORMAT_YUV420_8BIT
|
||||
- 8-bit per component YCbCr 420, single plane
|
||||
- Plane 0: 3 components
|
||||
* Component 0: Y(8)
|
||||
* Component 1: Cb(8, 2x2 subsampled)
|
||||
* Component 2: Cr(8, 2x2 subsampled)
|
||||
|
||||
* - DRM_FORMAT_YUV420_10BIT
|
||||
- 10-bit per component YCbCr 420, single plane
|
||||
- Plane 0: 3 components
|
||||
* Component 0: Y(10)
|
||||
* Component 1: Cb(10, 2x2 subsampled)
|
||||
* Component 2: Cr(10, 2x2 subsampled)
|
||||
|
||||
* - DRM_FORMAT_NV12
|
||||
- 8-bit per component YCbCr 420, two plane
|
||||
- Plane 0: 1 component
|
||||
* Component 0: Y(8)
|
||||
Plane 1: 2 components
|
||||
* Component 0: Cb(8, 2x2 subsampled)
|
||||
* Component 1: Cr(8, 2x2 subsampled)
|
||||
|
||||
* - DRM_FORMAT_P010
|
||||
- 10-bit per component YCbCr 420, two plane
|
||||
- Plane 0: 1 component
|
||||
* Component 0: Y(10)
|
||||
Plane 1: 2 components
|
||||
* Component 0: Cb(10, 2x2 subsampled)
|
||||
* Component 1: Cr(10, 2x2 subsampled)
|
|
@ -17,6 +17,8 @@ GPU Driver Documentation
|
|||
vkms
|
||||
bridge/dw-hdmi
|
||||
xen-front
|
||||
afbc
|
||||
komeda-kms
|
||||
|
||||
.. only:: subproject and html
|
||||
|
||||
|
|
|
@ -0,0 +1,488 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
==============================
|
||||
drm/komeda Arm display driver
|
||||
==============================
|
||||
|
||||
The drm/komeda driver supports the Arm display processor D71 and later products,
|
||||
this document gives a brief overview of driver design: how it works and why
|
||||
design it like that.
|
||||
|
||||
Overview of D71 like display IPs
|
||||
================================
|
||||
|
||||
From D71, Arm display IP begins to adopt a flexible and modularized
|
||||
architecture. A display pipeline is made up of multiple individual and
|
||||
functional pipeline stages called components, and every component has some
|
||||
specific capabilities that can give the flowed pipeline pixel data a
|
||||
particular processing.
|
||||
|
||||
Typical D71 components:
|
||||
|
||||
Layer
|
||||
-----
|
||||
Layer is the first pipeline stage, which prepares the pixel data for the next
|
||||
stage. It fetches the pixel from memory, decodes it if it's AFBC, rotates the
|
||||
source image, unpacks or converts YUV pixels to the device internal RGB pixels,
|
||||
then adjusts the color_space of pixels if needed.
|
||||
|
||||
Scaler
|
||||
------
|
||||
As its name suggests, scaler takes responsibility for scaling, and D71 also
|
||||
supports image enhancements by scaler.
|
||||
The usage of scaler is very flexible and can be connected to layer output
|
||||
for layer scaling, or connected to compositor and scale the whole display
|
||||
frame and then feed the output data into wb_layer which will then write it
|
||||
into memory.
|
||||
|
||||
Compositor (compiz)
|
||||
-------------------
|
||||
Compositor blends multiple layers or pixel data flows into one single display
|
||||
frame. its output frame can be fed into post image processor for showing it on
|
||||
the monitor or fed into wb_layer and written to memory at the same time.
|
||||
user can also insert a scaler between compositor and wb_layer to down scale
|
||||
the display frame first and and then write to memory.
|
||||
|
||||
Writeback Layer (wb_layer)
|
||||
--------------------------
|
||||
Writeback layer does the opposite things of Layer, which connects to compiz
|
||||
and writes the composition result to memory.
|
||||
|
||||
Post image processor (improc)
|
||||
-----------------------------
|
||||
Post image processor adjusts frame data like gamma and color space to fit the
|
||||
requirements of the monitor.
|
||||
|
||||
Timing controller (timing_ctrlr)
|
||||
--------------------------------
|
||||
Final stage of display pipeline, Timing controller is not for the pixel
|
||||
handling, but only for controlling the display timing.
|
||||
|
||||
Merger
|
||||
------
|
||||
D71 scaler mostly only has the half horizontal input/output capabilities
|
||||
compared with Layer, like if Layer supports 4K input size, the scaler only can
|
||||
support 2K input/output in the same time. To achieve the ful frame scaling, D71
|
||||
introduces Layer Split, which splits the whole image to two half parts and feeds
|
||||
them to two Layers A and B, and does the scaling independently. After scaling
|
||||
the result need to be fed to merger to merge two part images together, and then
|
||||
output merged result to compiz.
|
||||
|
||||
Splitter
|
||||
--------
|
||||
Similar to Layer Split, but Splitter is used for writeback, which splits the
|
||||
compiz result to two parts and then feed them to two scalers.
|
||||
|
||||
Possible D71 Pipeline usage
|
||||
===========================
|
||||
|
||||
Benefitting from the modularized architecture, D71 pipelines can be easily
|
||||
adjusted to fit different usages. And D71 has two pipelines, which support two
|
||||
types of working mode:
|
||||
|
||||
- Dual display mode
|
||||
Two pipelines work independently and separately to drive two display outputs.
|
||||
|
||||
- Single display mode
|
||||
Two pipelines work together to drive only one display output.
|
||||
|
||||
On this mode, pipeline_B doesn't work indenpendently, but outputs its
|
||||
composition result into pipeline_A, and its pixel timing also derived from
|
||||
pipeline_A.timing_ctrlr. The pipeline_B works just like a "slave" of
|
||||
pipeline_A(master)
|
||||
|
||||
Single pipeline data flow
|
||||
-------------------------
|
||||
|
||||
.. kernel-render:: DOT
|
||||
:alt: Single pipeline digraph
|
||||
:caption: Single pipeline data flow
|
||||
|
||||
digraph single_ppl {
|
||||
rankdir=LR;
|
||||
|
||||
subgraph {
|
||||
"Memory";
|
||||
"Monitor";
|
||||
}
|
||||
|
||||
subgraph cluster_pipeline {
|
||||
style=dashed
|
||||
node [shape=box]
|
||||
{
|
||||
node [bgcolor=grey style=dashed]
|
||||
"Scaler-0";
|
||||
"Scaler-1";
|
||||
"Scaler-0/1"
|
||||
}
|
||||
|
||||
node [bgcolor=grey style=filled]
|
||||
"Layer-0" -> "Scaler-0"
|
||||
"Layer-1" -> "Scaler-0"
|
||||
"Layer-2" -> "Scaler-1"
|
||||
"Layer-3" -> "Scaler-1"
|
||||
|
||||
"Layer-0" -> "Compiz"
|
||||
"Layer-1" -> "Compiz"
|
||||
"Layer-2" -> "Compiz"
|
||||
"Layer-3" -> "Compiz"
|
||||
"Scaler-0" -> "Compiz"
|
||||
"Scaler-1" -> "Compiz"
|
||||
|
||||
"Compiz" -> "Scaler-0/1" -> "Wb_layer"
|
||||
"Compiz" -> "Improc" -> "Timing Controller"
|
||||
}
|
||||
|
||||
"Wb_layer" -> "Memory"
|
||||
"Timing Controller" -> "Monitor"
|
||||
}
|
||||
|
||||
Dual pipeline with Slave enabled
|
||||
--------------------------------
|
||||
|
||||
.. kernel-render:: DOT
|
||||
:alt: Slave pipeline digraph
|
||||
:caption: Slave pipeline enabled data flow
|
||||
|
||||
digraph slave_ppl {
|
||||
rankdir=LR;
|
||||
|
||||
subgraph {
|
||||
"Memory";
|
||||
"Monitor";
|
||||
}
|
||||
node [shape=box]
|
||||
subgraph cluster_pipeline_slave {
|
||||
style=dashed
|
||||
label="Slave Pipeline_B"
|
||||
node [shape=box]
|
||||
{
|
||||
node [bgcolor=grey style=dashed]
|
||||
"Slave.Scaler-0";
|
||||
"Slave.Scaler-1";
|
||||
}
|
||||
|
||||
node [bgcolor=grey style=filled]
|
||||
"Slave.Layer-0" -> "Slave.Scaler-0"
|
||||
"Slave.Layer-1" -> "Slave.Scaler-0"
|
||||
"Slave.Layer-2" -> "Slave.Scaler-1"
|
||||
"Slave.Layer-3" -> "Slave.Scaler-1"
|
||||
|
||||
"Slave.Layer-0" -> "Slave.Compiz"
|
||||
"Slave.Layer-1" -> "Slave.Compiz"
|
||||
"Slave.Layer-2" -> "Slave.Compiz"
|
||||
"Slave.Layer-3" -> "Slave.Compiz"
|
||||
"Slave.Scaler-0" -> "Slave.Compiz"
|
||||
"Slave.Scaler-1" -> "Slave.Compiz"
|
||||
}
|
||||
|
||||
subgraph cluster_pipeline_master {
|
||||
style=dashed
|
||||
label="Master Pipeline_A"
|
||||
node [shape=box]
|
||||
{
|
||||
node [bgcolor=grey style=dashed]
|
||||
"Scaler-0";
|
||||
"Scaler-1";
|
||||
"Scaler-0/1"
|
||||
}
|
||||
|
||||
node [bgcolor=grey style=filled]
|
||||
"Layer-0" -> "Scaler-0"
|
||||
"Layer-1" -> "Scaler-0"
|
||||
"Layer-2" -> "Scaler-1"
|
||||
"Layer-3" -> "Scaler-1"
|
||||
|
||||
"Slave.Compiz" -> "Compiz"
|
||||
"Layer-0" -> "Compiz"
|
||||
"Layer-1" -> "Compiz"
|
||||
"Layer-2" -> "Compiz"
|
||||
"Layer-3" -> "Compiz"
|
||||
"Scaler-0" -> "Compiz"
|
||||
"Scaler-1" -> "Compiz"
|
||||
|
||||
"Compiz" -> "Scaler-0/1" -> "Wb_layer"
|
||||
"Compiz" -> "Improc" -> "Timing Controller"
|
||||
}
|
||||
|
||||
"Wb_layer" -> "Memory"
|
||||
"Timing Controller" -> "Monitor"
|
||||
}
|
||||
|
||||
Sub-pipelines for input and output
|
||||
----------------------------------
|
||||
|
||||
A complete display pipeline can be easily divided into three sub-pipelines
|
||||
according to the in/out usage.
|
||||
|
||||
Layer(input) pipeline
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
.. kernel-render:: DOT
|
||||
:alt: Layer data digraph
|
||||
:caption: Layer (input) data flow
|
||||
|
||||
digraph layer_data_flow {
|
||||
rankdir=LR;
|
||||
node [shape=box]
|
||||
|
||||
{
|
||||
node [bgcolor=grey style=dashed]
|
||||
"Scaler-n";
|
||||
}
|
||||
|
||||
"Layer-n" -> "Scaler-n" -> "Compiz"
|
||||
}
|
||||
|
||||
.. kernel-render:: DOT
|
||||
:alt: Layer Split digraph
|
||||
:caption: Layer Split pipeline
|
||||
|
||||
digraph layer_data_flow {
|
||||
rankdir=LR;
|
||||
node [shape=box]
|
||||
|
||||
"Layer-0/1" -> "Scaler-0" -> "Merger"
|
||||
"Layer-2/3" -> "Scaler-1" -> "Merger"
|
||||
"Merger" -> "Compiz"
|
||||
}
|
||||
|
||||
Writeback(output) pipeline
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.. kernel-render:: DOT
|
||||
:alt: writeback digraph
|
||||
:caption: Writeback(output) data flow
|
||||
|
||||
digraph writeback_data_flow {
|
||||
rankdir=LR;
|
||||
node [shape=box]
|
||||
|
||||
{
|
||||
node [bgcolor=grey style=dashed]
|
||||
"Scaler-n";
|
||||
}
|
||||
|
||||
"Compiz" -> "Scaler-n" -> "Wb_layer"
|
||||
}
|
||||
|
||||
.. kernel-render:: DOT
|
||||
:alt: split writeback digraph
|
||||
:caption: Writeback(output) Split data flow
|
||||
|
||||
digraph writeback_data_flow {
|
||||
rankdir=LR;
|
||||
node [shape=box]
|
||||
|
||||
"Compiz" -> "Splitter"
|
||||
"Splitter" -> "Scaler-0" -> "Merger"
|
||||
"Splitter" -> "Scaler-1" -> "Merger"
|
||||
"Merger" -> "Wb_layer"
|
||||
}
|
||||
|
||||
Display output pipeline
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
.. kernel-render:: DOT
|
||||
:alt: display digraph
|
||||
:caption: display output data flow
|
||||
|
||||
digraph single_ppl {
|
||||
rankdir=LR;
|
||||
node [shape=box]
|
||||
|
||||
"Compiz" -> "Improc" -> "Timing Controller"
|
||||
}
|
||||
|
||||
In the following section we'll see these three sub-pipelines will be handled
|
||||
by KMS-plane/wb_conn/crtc respectively.
|
||||
|
||||
Komeda Resource abstraction
|
||||
===========================
|
||||
|
||||
struct komeda_pipeline/component
|
||||
--------------------------------
|
||||
|
||||
To fully utilize and easily access/configure the HW, the driver side also uses
|
||||
a similar architecture: Pipeline/Component to describe the HW features and
|
||||
capabilities, and a specific component includes two parts:
|
||||
|
||||
- Data flow controlling.
|
||||
- Specific component capabilities and features.
|
||||
|
||||
So the driver defines a common header struct komeda_component to describe the
|
||||
data flow control and all specific components are a subclass of this base
|
||||
structure.
|
||||
|
||||
.. kernel-doc:: drivers/gpu/drm/arm/display/komeda/komeda_pipeline.h
|
||||
:internal:
|
||||
|
||||
Resource discovery and initialization
|
||||
=====================================
|
||||
|
||||
Pipeline and component are used to describe how to handle the pixel data. We
|
||||
still need a @struct komeda_dev to describe the whole view of the device, and
|
||||
the control-abilites of device.
|
||||
|
||||
We have &komeda_dev, &komeda_pipeline, &komeda_component. Now fill devices with
|
||||
pipelines. Since komeda is not for D71 only but also intended for later products,
|
||||
of course we’d better share as much as possible between different products. To
|
||||
achieve this, split the komeda device into two layers: CORE and CHIP.
|
||||
|
||||
- CORE: for common features and capabilities handling.
|
||||
- CHIP: for register programing and HW specific feature (limitation) handling.
|
||||
|
||||
CORE can access CHIP by three chip function structures:
|
||||
|
||||
- struct komeda_dev_funcs
|
||||
- struct komeda_pipeline_funcs
|
||||
- struct komeda_component_funcs
|
||||
|
||||
.. kernel-doc:: drivers/gpu/drm/arm/display/komeda/komeda_dev.h
|
||||
:internal:
|
||||
|
||||
Format handling
|
||||
===============
|
||||
|
||||
.. kernel-doc:: drivers/gpu/drm/arm/display/komeda/komeda_format_caps.h
|
||||
:internal:
|
||||
.. kernel-doc:: drivers/gpu/drm/arm/display/komeda/komeda_framebuffer.h
|
||||
:internal:
|
||||
|
||||
Attach komeda_dev to DRM-KMS
|
||||
============================
|
||||
|
||||
Komeda abstracts resources by pipeline/component, but DRM-KMS uses
|
||||
crtc/plane/connector. One KMS-obj cannot represent only one single component,
|
||||
since the requirements of a single KMS object cannot simply be achieved by a
|
||||
single component, usually that needs multiple components to fit the requirement.
|
||||
Like set mode, gamma, ctm for KMS all target on CRTC-obj, but komeda needs
|
||||
compiz, improc and timing_ctrlr to work together to fit these requirements.
|
||||
And a KMS-Plane may require multiple komeda resources: layer/scaler/compiz.
|
||||
|
||||
So, one KMS-Obj represents a sub-pipeline of komeda resources.
|
||||
|
||||
- Plane: `Layer(input) pipeline`_
|
||||
- Wb_connector: `Writeback(output) pipeline`_
|
||||
- Crtc: `Display output pipeline`_
|
||||
|
||||
So, for komeda, we treat KMS crtc/plane/connector as users of pipeline and
|
||||
component, and at any one time a pipeline/component only can be used by one
|
||||
user. And pipeline/component will be treated as private object of DRM-KMS; the
|
||||
state will be managed by drm_atomic_state as well.
|
||||
|
||||
How to map plane to Layer(input) pipeline
|
||||
-----------------------------------------
|
||||
|
||||
Komeda has multiple Layer input pipelines, see:
|
||||
- `Single pipeline data flow`_
|
||||
- `Dual pipeline with Slave enabled`_
|
||||
|
||||
The easiest way is binding a plane to a fixed Layer pipeline, but consider the
|
||||
komeda capabilities:
|
||||
|
||||
- Layer Split, See `Layer(input) pipeline`_
|
||||
|
||||
Layer_Split is quite complicated feature, which splits a big image into two
|
||||
parts and handles it by two layers and two scalers individually. But it
|
||||
imports an edge problem or effect in the middle of the image after the split.
|
||||
To avoid such a problem, it needs a complicated Split calculation and some
|
||||
special configurations to the layer and scaler. We'd better hide such HW
|
||||
related complexity to user mode.
|
||||
|
||||
- Slave pipeline, See `Dual pipeline with Slave enabled`_
|
||||
|
||||
Since the compiz component doesn't output alpha value, the slave pipeline
|
||||
only can be used for bottom layers composition. The komeda driver wants to
|
||||
hide this limitation to the user. The way to do this is to pick a suitable
|
||||
Layer according to plane_state->zpos.
|
||||
|
||||
So for komeda, the KMS-plane doesn't represent a fixed komeda layer pipeline,
|
||||
but multiple Layers with same capabilities. Komeda will select one or more
|
||||
Layers to fit the requirement of one KMS-plane.
|
||||
|
||||
Make component/pipeline to be drm_private_obj
|
||||
---------------------------------------------
|
||||
|
||||
Add :c:type:`drm_private_obj` to :c:type:`komeda_component`, :c:type:`komeda_pipeline`
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct komeda_component {
|
||||
struct drm_private_obj obj;
|
||||
...
|
||||
}
|
||||
|
||||
struct komeda_pipeline {
|
||||
struct drm_private_obj obj;
|
||||
...
|
||||
}
|
||||
|
||||
Tracking component_state/pipeline_state by drm_atomic_state
|
||||
-----------------------------------------------------------
|
||||
|
||||
Add :c:type:`drm_private_state` and user to :c:type:`komeda_component_state`,
|
||||
:c:type:`komeda_pipeline_state`
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
struct komeda_component_state {
|
||||
struct drm_private_state obj;
|
||||
void *binding_user;
|
||||
...
|
||||
}
|
||||
|
||||
struct komeda_pipeline_state {
|
||||
struct drm_private_state obj;
|
||||
struct drm_crtc *crtc;
|
||||
...
|
||||
}
|
||||
|
||||
komeda component validation
|
||||
---------------------------
|
||||
|
||||
Komeda has multiple types of components, but the process of validation are
|
||||
similar, usually including the following steps:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
int komeda_xxxx_validate(struct komeda_component_xxx xxx_comp,
|
||||
struct komeda_component_output *input_dflow,
|
||||
struct drm_plane/crtc/connector *user,
|
||||
struct drm_plane/crtc/connector_state, *user_state)
|
||||
{
|
||||
setup 1: check if component is needed, like the scaler is optional depending
|
||||
on the user_state; if unneeded, just return, and the caller will
|
||||
put the data flow into next stage.
|
||||
Setup 2: check user_state with component features and capabilities to see
|
||||
if requirements can be met; if not, return fail.
|
||||
Setup 3: get component_state from drm_atomic_state, and try set to set
|
||||
user to component; fail if component has been assigned to another
|
||||
user already.
|
||||
Setup 3: configure the component_state, like set its input component,
|
||||
convert user_state to component specific state.
|
||||
Setup 4: adjust the input_dflow and prepare it for the next stage.
|
||||
}
|
||||
|
||||
komeda_kms Abstraction
|
||||
----------------------
|
||||
|
||||
.. kernel-doc:: drivers/gpu/drm/arm/display/komeda/komeda_kms.h
|
||||
:internal:
|
||||
|
||||
komde_kms Functions
|
||||
-------------------
|
||||
.. kernel-doc:: drivers/gpu/drm/arm/display/komeda/komeda_crtc.c
|
||||
:internal:
|
||||
.. kernel-doc:: drivers/gpu/drm/arm/display/komeda/komeda_plane.c
|
||||
:internal:
|
||||
|
||||
Build komeda to be a Linux module driver
|
||||
========================================
|
||||
|
||||
Now we have two level devices:
|
||||
|
||||
- komeda_dev: describes the real display hardware.
|
||||
- komeda_kms_dev: attachs or connects komeda_dev to DRM-KMS.
|
||||
|
||||
All komeda operations are supplied or operated by komeda_dev or komeda_kms_dev,
|
||||
the module driver is only a simple wrapper to pass the Linux command
|
||||
(probe/remove/pm) into komeda_dev or komeda_kms_dev.
|
15
MAINTAINERS
15
MAINTAINERS
|
@ -1133,13 +1133,26 @@ S: Supported
|
|||
F: drivers/gpu/drm/arm/hdlcd_*
|
||||
F: Documentation/devicetree/bindings/display/arm,hdlcd.txt
|
||||
|
||||
ARM KOMEDA DRM-KMS DRIVER
|
||||
M: James (Qian) Wang <james.qian.wang@arm.com>
|
||||
M: Liviu Dudau <liviu.dudau@arm.com>
|
||||
L: Mali DP Maintainers <malidp@foss.arm.com>
|
||||
S: Supported
|
||||
T: git git://linux-arm.org/linux-ld.git for-upstream/mali-dp
|
||||
F: drivers/gpu/drm/arm/display/include/
|
||||
F: drivers/gpu/drm/arm/display/komeda/
|
||||
F: Documentation/devicetree/bindings/display/arm/arm,komeda.txt
|
||||
F: Documentation/gpu/komeda-kms.rst
|
||||
|
||||
ARM MALI-DP DRM DRIVER
|
||||
M: Liviu Dudau <liviu.dudau@arm.com>
|
||||
M: Brian Starkey <brian.starkey@arm.com>
|
||||
M: Mali DP Maintainers <malidp@foss.arm.com>
|
||||
L: Mali DP Maintainers <malidp@foss.arm.com>
|
||||
S: Supported
|
||||
T: git git://linux-arm.org/linux-ld.git for-upstream/mali-dp
|
||||
F: drivers/gpu/drm/arm/
|
||||
F: Documentation/devicetree/bindings/display/arm,malidp.txt
|
||||
F: Documentation/gpu/afbc.rst
|
||||
|
||||
ARM MFM AND FLOPPY DRIVERS
|
||||
M: Ian Molton <spyro@f2s.com>
|
||||
|
|
|
@ -51,7 +51,7 @@ obj-$(CONFIG_DRM_DEBUG_SELFTEST) += selftests/
|
|||
obj-$(CONFIG_DRM) += drm.o
|
||||
obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o
|
||||
obj-$(CONFIG_DRM_PANEL_ORIENTATION_QUIRKS) += drm_panel_orientation_quirks.o
|
||||
obj-$(CONFIG_DRM_ARM) += arm/
|
||||
obj-y += arm/
|
||||
obj-$(CONFIG_DRM_TTM) += ttm/
|
||||
obj-$(CONFIG_DRM_SCHED) += scheduler/
|
||||
obj-$(CONFIG_DRM_TDFX) += tdfx/
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
config DRM_ARM
|
||||
bool
|
||||
help
|
||||
Choose this option to select drivers for ARM's devices
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
menu "ARM devices"
|
||||
|
||||
config DRM_HDLCD
|
||||
tristate "ARM HDLCD"
|
||||
depends on DRM && OF && (ARM || ARM64)
|
||||
depends on COMMON_CLK
|
||||
select DRM_ARM
|
||||
select DRM_KMS_HELPER
|
||||
select DRM_KMS_CMA_HELPER
|
||||
help
|
||||
|
@ -29,7 +26,6 @@ config DRM_MALI_DISPLAY
|
|||
tristate "ARM Mali Display Processor"
|
||||
depends on DRM && OF && (ARM || ARM64)
|
||||
depends on COMMON_CLK
|
||||
select DRM_ARM
|
||||
select DRM_KMS_HELPER
|
||||
select DRM_KMS_CMA_HELPER
|
||||
select DRM_GEM_CMA_HELPER
|
||||
|
@ -40,3 +36,7 @@ config DRM_MALI_DISPLAY
|
|||
of the hardware.
|
||||
|
||||
If compiled as a module it will be called mali-dp.
|
||||
|
||||
source "drivers/gpu/drm/arm/display/Kconfig"
|
||||
|
||||
endmenu
|
||||
|
|
|
@ -3,3 +3,4 @@ obj-$(CONFIG_DRM_HDLCD) += hdlcd.o
|
|||
mali-dp-y := malidp_drv.o malidp_hw.o malidp_planes.o malidp_crtc.o
|
||||
mali-dp-y += malidp_mw.o
|
||||
obj-$(CONFIG_DRM_MALI_DISPLAY) += mali-dp.o
|
||||
obj-$(CONFIG_DRM_KOMEDA) += display/
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_DRM_KOMEDA) += komeda/
|
|
@ -0,0 +1,14 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
config DRM_KOMEDA
|
||||
tristate "ARM Komeda display driver"
|
||||
depends on DRM && OF
|
||||
depends on COMMON_CLK
|
||||
select DRM_KMS_HELPER
|
||||
select DRM_KMS_CMA_HELPER
|
||||
select DRM_GEM_CMA_HELPER
|
||||
select VIDEOMODE_HELPERS
|
||||
help
|
||||
Choose this option if you want to compile the ARM Komeda display
|
||||
Processor driver. It supports the D71 variants of the hardware.
|
||||
|
||||
If compiled as a module it will be called komeda.
|
|
@ -0,0 +1,42 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#ifndef _MALIDP_IO_H_
|
||||
#define _MALIDP_IO_H_
|
||||
|
||||
#include <linux/io.h>
|
||||
|
||||
static inline u32
|
||||
malidp_read32(u32 __iomem *base, u32 offset)
|
||||
{
|
||||
return readl((base + (offset >> 2)));
|
||||
}
|
||||
|
||||
static inline void
|
||||
malidp_write32(u32 __iomem *base, u32 offset, u32 v)
|
||||
{
|
||||
writel(v, (base + (offset >> 2)));
|
||||
}
|
||||
|
||||
static inline void
|
||||
malidp_write32_mask(u32 __iomem *base, u32 offset, u32 m, u32 v)
|
||||
{
|
||||
u32 tmp = malidp_read32(base, offset);
|
||||
|
||||
tmp &= (~m);
|
||||
malidp_write32(base, offset, v | tmp);
|
||||
}
|
||||
|
||||
static inline void
|
||||
malidp_write_group(u32 __iomem *base, u32 offset, int num, const u32 *values)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < num; i++)
|
||||
malidp_write32(base, offset + i * 4, values[i]);
|
||||
}
|
||||
|
||||
#endif /*_MALIDP_IO_H_*/
|
|
@ -0,0 +1,23 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#ifndef _MALIDP_PRODUCT_H_
|
||||
#define _MALIDP_PRODUCT_H_
|
||||
|
||||
/* Product identification */
|
||||
#define MALIDP_CORE_ID(__product, __major, __minor, __status) \
|
||||
((((__product) & 0xFFFF) << 16) | (((__major) & 0xF) << 12) | \
|
||||
(((__minor) & 0xF) << 8) | ((__status) & 0xFF))
|
||||
|
||||
#define MALIDP_CORE_ID_PRODUCT_ID(__core_id) ((__u32)(__core_id) >> 16)
|
||||
#define MALIDP_CORE_ID_MAJOR(__core_id) (((__u32)(__core_id) >> 12) & 0xF)
|
||||
#define MALIDP_CORE_ID_MINOR(__core_id) (((__u32)(__core_id) >> 8) & 0xF)
|
||||
#define MALIDP_CORE_ID_STATUS(__core_id) (((__u32)(__core_id)) & 0xFF)
|
||||
|
||||
/* Mali-display product IDs */
|
||||
#define MALIDP_D71_PRODUCT_ID 0x0071
|
||||
|
||||
#endif /* _MALIDP_PRODUCT_H_ */
|
|
@ -0,0 +1,16 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#ifndef _MALIDP_UTILS_
|
||||
#define _MALIDP_UTILS_
|
||||
|
||||
#define has_bit(nr, mask) (BIT(nr) & (mask))
|
||||
#define has_bits(bits, mask) (((bits) & (mask)) == (bits))
|
||||
|
||||
#define dp_for_each_set_bit(bit, mask) \
|
||||
for_each_set_bit((bit), ((unsigned long *)&(mask)), sizeof(mask) * 8)
|
||||
|
||||
#endif /* _MALIDP_UTILS_ */
|
|
@ -0,0 +1,21 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
ccflags-y := \
|
||||
-I$(src)/../include \
|
||||
-I$(src)
|
||||
|
||||
komeda-y := \
|
||||
komeda_drv.o \
|
||||
komeda_dev.o \
|
||||
komeda_format_caps.o \
|
||||
komeda_pipeline.o \
|
||||
komeda_framebuffer.o \
|
||||
komeda_kms.o \
|
||||
komeda_crtc.o \
|
||||
komeda_plane.o \
|
||||
komeda_private_obj.o
|
||||
|
||||
komeda-y += \
|
||||
d71/d71_dev.o
|
||||
|
||||
obj-$(CONFIG_DRM_KOMEDA) += komeda.o
|
|
@ -0,0 +1,111 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#include "malidp_io.h"
|
||||
#include "komeda_dev.h"
|
||||
|
||||
static int d71_enum_resources(struct komeda_dev *mdev)
|
||||
{
|
||||
/* TODO add enum resources */
|
||||
return -1;
|
||||
}
|
||||
|
||||
#define __HW_ID(__group, __format) \
|
||||
((((__group) & 0x7) << 3) | ((__format) & 0x7))
|
||||
|
||||
#define RICH KOMEDA_FMT_RICH_LAYER
|
||||
#define SIMPLE KOMEDA_FMT_SIMPLE_LAYER
|
||||
#define RICH_SIMPLE (KOMEDA_FMT_RICH_LAYER | KOMEDA_FMT_SIMPLE_LAYER)
|
||||
#define RICH_WB (KOMEDA_FMT_RICH_LAYER | KOMEDA_FMT_WB_LAYER)
|
||||
#define RICH_SIMPLE_WB (RICH_SIMPLE | KOMEDA_FMT_WB_LAYER)
|
||||
|
||||
#define Rot_0 DRM_MODE_ROTATE_0
|
||||
#define Flip_H_V (DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y | Rot_0)
|
||||
#define Rot_ALL_H_V (DRM_MODE_ROTATE_MASK | Flip_H_V)
|
||||
|
||||
#define LYT_NM BIT(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16)
|
||||
#define LYT_WB BIT(AFBC_FORMAT_MOD_BLOCK_SIZE_32x8)
|
||||
#define LYT_NM_WB (LYT_NM | LYT_WB)
|
||||
|
||||
#define AFB_TH AFBC(_TILED | _SPARSE)
|
||||
#define AFB_TH_SC_YTR AFBC(_TILED | _SC | _SPARSE | _YTR)
|
||||
#define AFB_TH_SC_YTR_BS AFBC(_TILED | _SC | _SPARSE | _YTR | _SPLIT)
|
||||
|
||||
static struct komeda_format_caps d71_format_caps_table[] = {
|
||||
/* HW_ID | fourcc | tile_sz | layer_types | rots | afbc_layouts | afbc_features */
|
||||
/* ABGR_2101010*/
|
||||
{__HW_ID(0, 0), DRM_FORMAT_ARGB2101010, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0},
|
||||
{__HW_ID(0, 1), DRM_FORMAT_ABGR2101010, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0},
|
||||
{__HW_ID(0, 1), DRM_FORMAT_ABGR2101010, 1, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR_BS}, /* afbc */
|
||||
{__HW_ID(0, 2), DRM_FORMAT_RGBA1010102, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0},
|
||||
{__HW_ID(0, 3), DRM_FORMAT_BGRA1010102, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0},
|
||||
/* ABGR_8888*/
|
||||
{__HW_ID(1, 0), DRM_FORMAT_ARGB8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0},
|
||||
{__HW_ID(1, 1), DRM_FORMAT_ABGR8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0},
|
||||
{__HW_ID(1, 1), DRM_FORMAT_ABGR8888, 1, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR_BS}, /* afbc */
|
||||
{__HW_ID(1, 2), DRM_FORMAT_RGBA8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0},
|
||||
{__HW_ID(1, 3), DRM_FORMAT_BGRA8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0},
|
||||
/* XBGB_8888 */
|
||||
{__HW_ID(2, 0), DRM_FORMAT_XRGB8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0},
|
||||
{__HW_ID(2, 1), DRM_FORMAT_XBGR8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0},
|
||||
{__HW_ID(2, 2), DRM_FORMAT_RGBX8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0},
|
||||
{__HW_ID(2, 3), DRM_FORMAT_BGRX8888, 1, RICH_SIMPLE_WB, Flip_H_V, 0, 0},
|
||||
/* BGR_888 */ /* none-afbc RGB888 doesn't support rotation and flip */
|
||||
{__HW_ID(3, 0), DRM_FORMAT_RGB888, 1, RICH_SIMPLE_WB, Rot_0, 0, 0},
|
||||
{__HW_ID(3, 1), DRM_FORMAT_BGR888, 1, RICH_SIMPLE_WB, Rot_0, 0, 0},
|
||||
{__HW_ID(3, 1), DRM_FORMAT_BGR888, 1, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR_BS}, /* afbc */
|
||||
/* BGR 16bpp */
|
||||
{__HW_ID(4, 0), DRM_FORMAT_RGBA5551, 1, RICH_SIMPLE, Flip_H_V, 0, 0},
|
||||
{__HW_ID(4, 1), DRM_FORMAT_ABGR1555, 1, RICH_SIMPLE, Flip_H_V, 0, 0},
|
||||
{__HW_ID(4, 1), DRM_FORMAT_ABGR1555, 1, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR}, /* afbc */
|
||||
{__HW_ID(4, 2), DRM_FORMAT_RGB565, 1, RICH_SIMPLE, Flip_H_V, 0, 0},
|
||||
{__HW_ID(4, 3), DRM_FORMAT_BGR565, 1, RICH_SIMPLE, Flip_H_V, 0, 0},
|
||||
{__HW_ID(4, 3), DRM_FORMAT_BGR565, 1, RICH_SIMPLE, Rot_ALL_H_V, LYT_NM_WB, AFB_TH_SC_YTR}, /* afbc */
|
||||
{__HW_ID(4, 4), DRM_FORMAT_R8, 1, SIMPLE, Rot_0, 0, 0},
|
||||
/* YUV 444/422/420 8bit */
|
||||
{__HW_ID(5, 0), 0 /*XYUV8888*/, 1, 0, 0, 0, 0},
|
||||
/* XYUV unsupported*/
|
||||
{__HW_ID(5, 1), DRM_FORMAT_YUYV, 1, RICH, Rot_ALL_H_V, LYT_NM, AFB_TH}, /* afbc */
|
||||
{__HW_ID(5, 2), DRM_FORMAT_YUYV, 1, RICH, Flip_H_V, 0, 0},
|
||||
{__HW_ID(5, 3), DRM_FORMAT_UYVY, 1, RICH, Flip_H_V, 0, 0},
|
||||
{__HW_ID(5, 4), 0, /*X0L0 */ 2, 0, 0, 0}, /* Y0L0 unsupported */
|
||||
{__HW_ID(5, 6), DRM_FORMAT_NV12, 1, RICH, Flip_H_V, 0, 0},
|
||||
{__HW_ID(5, 6), 0/*DRM_FORMAT_YUV420_8BIT*/, 1, RICH, Rot_ALL_H_V, LYT_NM, AFB_TH}, /* afbc */
|
||||
{__HW_ID(5, 7), DRM_FORMAT_YUV420, 1, RICH, Flip_H_V, 0, 0},
|
||||
/* YUV 10bit*/
|
||||
{__HW_ID(6, 0), 0,/*XVYU2101010*/ 1, 0, 0, 0, 0},/* VYV30 unsupported */
|
||||
{__HW_ID(6, 6), 0/*DRM_FORMAT_X0L2*/, 2, RICH, Flip_H_V, 0, 0},
|
||||
{__HW_ID(6, 7), 0/*DRM_FORMAT_P010*/, 1, RICH, Flip_H_V, 0, 0},
|
||||
{__HW_ID(6, 7), 0/*DRM_FORMAT_YUV420_10BIT*/, 1, RICH, Rot_ALL_H_V, LYT_NM, AFB_TH},
|
||||
};
|
||||
|
||||
static void d71_init_fmt_tbl(struct komeda_dev *mdev)
|
||||
{
|
||||
struct komeda_format_caps_table *table = &mdev->fmt_tbl;
|
||||
|
||||
table->format_caps = d71_format_caps_table;
|
||||
table->n_formats = ARRAY_SIZE(d71_format_caps_table);
|
||||
}
|
||||
|
||||
static struct komeda_dev_funcs d71_chip_funcs = {
|
||||
.init_format_table = d71_init_fmt_tbl,
|
||||
.enum_resources = d71_enum_resources,
|
||||
.cleanup = NULL,
|
||||
};
|
||||
|
||||
#define GLB_ARCH_ID 0x000
|
||||
#define GLB_CORE_ID 0x004
|
||||
#define GLB_CORE_INFO 0x008
|
||||
|
||||
struct komeda_dev_funcs *
|
||||
d71_identify(u32 __iomem *reg_base, struct komeda_chip_info *chip)
|
||||
{
|
||||
chip->arch_id = malidp_read32(reg_base, GLB_ARCH_ID);
|
||||
chip->core_id = malidp_read32(reg_base, GLB_CORE_ID);
|
||||
chip->core_info = malidp_read32(reg_base, GLB_CORE_INFO);
|
||||
|
||||
return &d71_chip_funcs;
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#include <linux/clk.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_plane_helper.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include "komeda_dev.h"
|
||||
#include "komeda_kms.h"
|
||||
|
||||
struct drm_crtc_helper_funcs komeda_crtc_helper_funcs = {
|
||||
};
|
||||
|
||||
static const struct drm_crtc_funcs komeda_crtc_funcs = {
|
||||
};
|
||||
|
||||
int komeda_kms_setup_crtcs(struct komeda_kms_dev *kms,
|
||||
struct komeda_dev *mdev)
|
||||
{
|
||||
struct komeda_crtc *crtc;
|
||||
struct komeda_pipeline *master;
|
||||
char str[16];
|
||||
int i;
|
||||
|
||||
kms->n_crtcs = 0;
|
||||
|
||||
for (i = 0; i < mdev->n_pipelines; i++) {
|
||||
crtc = &kms->crtcs[kms->n_crtcs];
|
||||
master = mdev->pipelines[i];
|
||||
|
||||
crtc->master = master;
|
||||
crtc->slave = NULL;
|
||||
|
||||
if (crtc->slave)
|
||||
sprintf(str, "pipe-%d", crtc->slave->id);
|
||||
else
|
||||
sprintf(str, "None");
|
||||
|
||||
DRM_INFO("crtc%d: master(pipe-%d) slave(%s) output: %s.\n",
|
||||
kms->n_crtcs, master->id, str,
|
||||
master->of_output_dev ?
|
||||
master->of_output_dev->full_name : "None");
|
||||
|
||||
kms->n_crtcs++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct drm_plane *
|
||||
get_crtc_primary(struct komeda_kms_dev *kms, struct komeda_crtc *crtc)
|
||||
{
|
||||
struct komeda_plane *kplane;
|
||||
struct drm_plane *plane;
|
||||
|
||||
drm_for_each_plane(plane, &kms->base) {
|
||||
if (plane->type != DRM_PLANE_TYPE_PRIMARY)
|
||||
continue;
|
||||
|
||||
kplane = to_kplane(plane);
|
||||
/* only master can be primary */
|
||||
if (kplane->layer->base.pipeline == crtc->master)
|
||||
return plane;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int komeda_crtc_add(struct komeda_kms_dev *kms,
|
||||
struct komeda_crtc *kcrtc)
|
||||
{
|
||||
struct drm_crtc *crtc = &kcrtc->base;
|
||||
int err;
|
||||
|
||||
err = drm_crtc_init_with_planes(&kms->base, crtc,
|
||||
get_crtc_primary(kms, kcrtc), NULL,
|
||||
&komeda_crtc_funcs, NULL);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
drm_crtc_helper_add(crtc, &komeda_crtc_helper_funcs);
|
||||
drm_crtc_vblank_reset(crtc);
|
||||
|
||||
crtc->port = kcrtc->master->of_output_port;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int komeda_kms_add_crtcs(struct komeda_kms_dev *kms, struct komeda_dev *mdev)
|
||||
{
|
||||
int i, err;
|
||||
|
||||
for (i = 0; i < kms->n_crtcs; i++) {
|
||||
err = komeda_crtc_add(kms, &kms->crtcs[i]);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,186 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/of_device.h>
|
||||
#include <linux/of_graph.h>
|
||||
#include "komeda_dev.h"
|
||||
|
||||
static int komeda_parse_pipe_dt(struct komeda_dev *mdev, struct device_node *np)
|
||||
{
|
||||
struct komeda_pipeline *pipe;
|
||||
struct clk *clk;
|
||||
u32 pipe_id;
|
||||
int ret = 0;
|
||||
|
||||
ret = of_property_read_u32(np, "reg", &pipe_id);
|
||||
if (ret != 0 || pipe_id >= mdev->n_pipelines)
|
||||
return -EINVAL;
|
||||
|
||||
pipe = mdev->pipelines[pipe_id];
|
||||
|
||||
clk = of_clk_get_by_name(np, "aclk");
|
||||
if (IS_ERR(clk)) {
|
||||
DRM_ERROR("get aclk for pipeline %d failed!\n", pipe_id);
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
pipe->aclk = clk;
|
||||
|
||||
clk = of_clk_get_by_name(np, "pxclk");
|
||||
if (IS_ERR(clk)) {
|
||||
DRM_ERROR("get pxclk for pipeline %d failed!\n", pipe_id);
|
||||
return PTR_ERR(clk);
|
||||
}
|
||||
pipe->pxlclk = clk;
|
||||
|
||||
/* enum ports */
|
||||
pipe->of_output_dev =
|
||||
of_graph_get_remote_node(np, KOMEDA_OF_PORT_OUTPUT, 0);
|
||||
pipe->of_output_port =
|
||||
of_graph_get_port_by_id(np, KOMEDA_OF_PORT_OUTPUT);
|
||||
|
||||
pipe->of_node = np;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int komeda_parse_dt(struct device *dev, struct komeda_dev *mdev)
|
||||
{
|
||||
struct device_node *child, *np = dev->of_node;
|
||||
struct clk *clk;
|
||||
int ret;
|
||||
|
||||
clk = devm_clk_get(dev, "mclk");
|
||||
if (IS_ERR(clk))
|
||||
return PTR_ERR(clk);
|
||||
|
||||
mdev->mclk = clk;
|
||||
|
||||
for_each_available_child_of_node(np, child) {
|
||||
if (of_node_cmp(child->name, "pipeline") == 0) {
|
||||
ret = komeda_parse_pipe_dt(mdev, child);
|
||||
if (ret) {
|
||||
DRM_ERROR("parse pipeline dt error!\n");
|
||||
of_node_put(child);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct komeda_dev *komeda_dev_create(struct device *dev)
|
||||
{
|
||||
struct platform_device *pdev = to_platform_device(dev);
|
||||
const struct komeda_product_data *product;
|
||||
struct komeda_dev *mdev;
|
||||
struct resource *io_res;
|
||||
int err = 0;
|
||||
|
||||
product = of_device_get_match_data(dev);
|
||||
if (!product)
|
||||
return ERR_PTR(-ENODEV);
|
||||
|
||||
io_res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
||||
if (!io_res) {
|
||||
DRM_ERROR("No registers defined.\n");
|
||||
return ERR_PTR(-ENODEV);
|
||||
}
|
||||
|
||||
mdev = devm_kzalloc(dev, sizeof(*mdev), GFP_KERNEL);
|
||||
if (!mdev)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
mdev->dev = dev;
|
||||
mdev->reg_base = devm_ioremap_resource(dev, io_res);
|
||||
if (IS_ERR(mdev->reg_base)) {
|
||||
DRM_ERROR("Map register space failed.\n");
|
||||
err = PTR_ERR(mdev->reg_base);
|
||||
mdev->reg_base = NULL;
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
mdev->pclk = devm_clk_get(dev, "pclk");
|
||||
if (IS_ERR(mdev->pclk)) {
|
||||
DRM_ERROR("Get APB clk failed.\n");
|
||||
err = PTR_ERR(mdev->pclk);
|
||||
mdev->pclk = NULL;
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
/* Enable APB clock to access the registers */
|
||||
clk_prepare_enable(mdev->pclk);
|
||||
|
||||
mdev->funcs = product->identify(mdev->reg_base, &mdev->chip);
|
||||
if (!komeda_product_match(mdev, product->product_id)) {
|
||||
DRM_ERROR("DT configured %x mismatch with real HW %x.\n",
|
||||
product->product_id,
|
||||
MALIDP_CORE_ID_PRODUCT_ID(mdev->chip.core_id));
|
||||
err = -ENODEV;
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
DRM_INFO("Found ARM Mali-D%x version r%dp%d\n",
|
||||
MALIDP_CORE_ID_PRODUCT_ID(mdev->chip.core_id),
|
||||
MALIDP_CORE_ID_MAJOR(mdev->chip.core_id),
|
||||
MALIDP_CORE_ID_MINOR(mdev->chip.core_id));
|
||||
|
||||
mdev->funcs->init_format_table(mdev);
|
||||
|
||||
err = mdev->funcs->enum_resources(mdev);
|
||||
if (err) {
|
||||
DRM_ERROR("enumerate display resource failed.\n");
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
err = komeda_parse_dt(dev, mdev);
|
||||
if (err) {
|
||||
DRM_ERROR("parse device tree failed.\n");
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
return mdev;
|
||||
|
||||
err_cleanup:
|
||||
komeda_dev_destroy(mdev);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
void komeda_dev_destroy(struct komeda_dev *mdev)
|
||||
{
|
||||
struct device *dev = mdev->dev;
|
||||
struct komeda_dev_funcs *funcs = mdev->funcs;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < mdev->n_pipelines; i++) {
|
||||
komeda_pipeline_destroy(mdev, mdev->pipelines[i]);
|
||||
mdev->pipelines[i] = NULL;
|
||||
}
|
||||
|
||||
mdev->n_pipelines = 0;
|
||||
|
||||
if (funcs && funcs->cleanup)
|
||||
funcs->cleanup(mdev);
|
||||
|
||||
if (mdev->reg_base) {
|
||||
devm_iounmap(dev, mdev->reg_base);
|
||||
mdev->reg_base = NULL;
|
||||
}
|
||||
|
||||
if (mdev->mclk) {
|
||||
devm_clk_put(dev, mdev->mclk);
|
||||
mdev->mclk = NULL;
|
||||
}
|
||||
|
||||
if (mdev->pclk) {
|
||||
clk_disable_unprepare(mdev->pclk);
|
||||
devm_clk_put(dev, mdev->pclk);
|
||||
mdev->pclk = NULL;
|
||||
}
|
||||
|
||||
devm_kfree(dev, mdev);
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#ifndef _KOMEDA_DEV_H_
|
||||
#define _KOMEDA_DEV_H_
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/clk.h>
|
||||
#include "komeda_pipeline.h"
|
||||
#include "malidp_product.h"
|
||||
#include "komeda_format_caps.h"
|
||||
|
||||
/* malidp device id */
|
||||
enum {
|
||||
MALI_D71 = 0,
|
||||
};
|
||||
|
||||
/* pipeline DT ports */
|
||||
enum {
|
||||
KOMEDA_OF_PORT_OUTPUT = 0,
|
||||
KOMEDA_OF_PORT_COPROC = 1,
|
||||
};
|
||||
|
||||
struct komeda_chip_info {
|
||||
u32 arch_id;
|
||||
u32 core_id;
|
||||
u32 core_info;
|
||||
u32 bus_width;
|
||||
};
|
||||
|
||||
struct komeda_product_data {
|
||||
u32 product_id;
|
||||
struct komeda_dev_funcs *(*identify)(u32 __iomem *reg,
|
||||
struct komeda_chip_info *info);
|
||||
};
|
||||
|
||||
struct komeda_dev;
|
||||
|
||||
/**
|
||||
* struct komeda_dev_funcs
|
||||
*
|
||||
* Supplied by chip level and returned by the chip entry function xxx_identify,
|
||||
*/
|
||||
struct komeda_dev_funcs {
|
||||
/**
|
||||
* @init_format_table:
|
||||
*
|
||||
* initialize &komeda_dev->format_table, this function should be called
|
||||
* before the &enum_resource
|
||||
*/
|
||||
void (*init_format_table)(struct komeda_dev *mdev);
|
||||
/**
|
||||
* @enum_resources:
|
||||
*
|
||||
* for CHIP to report or add pipeline and component resources to CORE
|
||||
*/
|
||||
int (*enum_resources)(struct komeda_dev *mdev);
|
||||
/** @cleanup: call to chip to cleanup komeda_dev->chip data */
|
||||
void (*cleanup)(struct komeda_dev *mdev);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct komeda_dev
|
||||
*
|
||||
* Pipeline and component are used to describe how to handle the pixel data.
|
||||
* komeda_device is for describing the whole view of the device, and the
|
||||
* control-abilites of device.
|
||||
*/
|
||||
struct komeda_dev {
|
||||
struct device *dev;
|
||||
u32 __iomem *reg_base;
|
||||
|
||||
struct komeda_chip_info chip;
|
||||
/** @fmt_tbl: initialized by &komeda_dev_funcs->init_format_table */
|
||||
struct komeda_format_caps_table fmt_tbl;
|
||||
/** @pclk: APB clock for register access */
|
||||
struct clk *pclk;
|
||||
/** @mck: HW main engine clk */
|
||||
struct clk *mclk;
|
||||
|
||||
int n_pipelines;
|
||||
struct komeda_pipeline *pipelines[KOMEDA_MAX_PIPELINES];
|
||||
|
||||
/** @funcs: chip funcs to access to HW */
|
||||
struct komeda_dev_funcs *funcs;
|
||||
/**
|
||||
* @chip_data:
|
||||
*
|
||||
* chip data will be added by &komeda_dev_funcs.enum_resources() and
|
||||
* destroyed by &komeda_dev_funcs.cleanup()
|
||||
*/
|
||||
void *chip_data;
|
||||
};
|
||||
|
||||
static inline bool
|
||||
komeda_product_match(struct komeda_dev *mdev, u32 target)
|
||||
{
|
||||
return MALIDP_CORE_ID_PRODUCT_ID(mdev->chip.core_id) == target;
|
||||
}
|
||||
|
||||
struct komeda_dev_funcs *
|
||||
d71_identify(u32 __iomem *reg, struct komeda_chip_info *chip);
|
||||
|
||||
struct komeda_dev *komeda_dev_create(struct device *dev);
|
||||
void komeda_dev_destroy(struct komeda_dev *mdev);
|
||||
|
||||
#endif /*_KOMEDA_DEV_H_*/
|
|
@ -0,0 +1,144 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#include <linux/module.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/component.h>
|
||||
#include <drm/drm_of.h>
|
||||
#include "komeda_dev.h"
|
||||
#include "komeda_kms.h"
|
||||
|
||||
struct komeda_drv {
|
||||
struct komeda_dev *mdev;
|
||||
struct komeda_kms_dev *kms;
|
||||
};
|
||||
|
||||
static void komeda_unbind(struct device *dev)
|
||||
{
|
||||
struct komeda_drv *mdrv = dev_get_drvdata(dev);
|
||||
|
||||
if (!mdrv)
|
||||
return;
|
||||
|
||||
komeda_kms_detach(mdrv->kms);
|
||||
komeda_dev_destroy(mdrv->mdev);
|
||||
|
||||
dev_set_drvdata(dev, NULL);
|
||||
devm_kfree(dev, mdrv);
|
||||
}
|
||||
|
||||
static int komeda_bind(struct device *dev)
|
||||
{
|
||||
struct komeda_drv *mdrv;
|
||||
int err;
|
||||
|
||||
mdrv = devm_kzalloc(dev, sizeof(*mdrv), GFP_KERNEL);
|
||||
if (!mdrv)
|
||||
return -ENOMEM;
|
||||
|
||||
mdrv->mdev = komeda_dev_create(dev);
|
||||
if (IS_ERR(mdrv->mdev)) {
|
||||
err = PTR_ERR(mdrv->mdev);
|
||||
goto free_mdrv;
|
||||
}
|
||||
|
||||
mdrv->kms = komeda_kms_attach(mdrv->mdev);
|
||||
if (IS_ERR(mdrv->kms)) {
|
||||
err = PTR_ERR(mdrv->kms);
|
||||
goto destroy_mdev;
|
||||
}
|
||||
|
||||
dev_set_drvdata(dev, mdrv);
|
||||
|
||||
return 0;
|
||||
|
||||
destroy_mdev:
|
||||
komeda_dev_destroy(mdrv->mdev);
|
||||
|
||||
free_mdrv:
|
||||
devm_kfree(dev, mdrv);
|
||||
return err;
|
||||
}
|
||||
|
||||
static const struct component_master_ops komeda_master_ops = {
|
||||
.bind = komeda_bind,
|
||||
.unbind = komeda_unbind,
|
||||
};
|
||||
|
||||
static int compare_of(struct device *dev, void *data)
|
||||
{
|
||||
return dev->of_node == data;
|
||||
}
|
||||
|
||||
static void komeda_add_slave(struct device *master,
|
||||
struct component_match **match,
|
||||
struct device_node *np, int port)
|
||||
{
|
||||
struct device_node *remote;
|
||||
|
||||
remote = of_graph_get_remote_node(np, port, 0);
|
||||
if (remote) {
|
||||
drm_of_component_match_add(master, match, compare_of, remote);
|
||||
of_node_put(remote);
|
||||
}
|
||||
}
|
||||
|
||||
static int komeda_platform_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct device *dev = &pdev->dev;
|
||||
struct component_match *match = NULL;
|
||||
struct device_node *child;
|
||||
|
||||
if (!dev->of_node)
|
||||
return -ENODEV;
|
||||
|
||||
for_each_available_child_of_node(dev->of_node, child) {
|
||||
if (of_node_cmp(child->name, "pipeline") != 0)
|
||||
continue;
|
||||
|
||||
/* add connector */
|
||||
komeda_add_slave(dev, &match, child, KOMEDA_OF_PORT_OUTPUT);
|
||||
}
|
||||
|
||||
return component_master_add_with_match(dev, &komeda_master_ops, match);
|
||||
}
|
||||
|
||||
static int komeda_platform_remove(struct platform_device *pdev)
|
||||
{
|
||||
component_master_del(&pdev->dev, &komeda_master_ops);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct komeda_product_data komeda_products[] = {
|
||||
[MALI_D71] = {
|
||||
.product_id = MALIDP_D71_PRODUCT_ID,
|
||||
.identify = d71_identify,
|
||||
},
|
||||
};
|
||||
|
||||
const struct of_device_id komeda_of_match[] = {
|
||||
{ .compatible = "arm,mali-d71", .data = &komeda_products[MALI_D71], },
|
||||
{},
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, komeda_of_match);
|
||||
|
||||
static struct platform_driver komeda_platform_driver = {
|
||||
.probe = komeda_platform_probe,
|
||||
.remove = komeda_platform_remove,
|
||||
.driver = {
|
||||
.name = "komeda",
|
||||
.of_match_table = komeda_of_match,
|
||||
.pm = NULL,
|
||||
},
|
||||
};
|
||||
|
||||
module_platform_driver(komeda_platform_driver);
|
||||
|
||||
MODULE_AUTHOR("James.Qian.Wang <james.qian.wang@arm.com>");
|
||||
MODULE_DESCRIPTION("Komeda KMS driver");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -0,0 +1,75 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include "komeda_format_caps.h"
|
||||
#include "malidp_utils.h"
|
||||
|
||||
const struct komeda_format_caps *
|
||||
komeda_get_format_caps(struct komeda_format_caps_table *table,
|
||||
u32 fourcc, u64 modifier)
|
||||
{
|
||||
const struct komeda_format_caps *caps;
|
||||
u64 afbc_features = modifier & ~(AFBC_FORMAT_MOD_BLOCK_SIZE_MASK);
|
||||
u32 afbc_layout = modifier & AFBC_FORMAT_MOD_BLOCK_SIZE_MASK;
|
||||
int id;
|
||||
|
||||
for (id = 0; id < table->n_formats; id++) {
|
||||
caps = &table->format_caps[id];
|
||||
|
||||
if (fourcc != caps->fourcc)
|
||||
continue;
|
||||
|
||||
if ((modifier == 0ULL) && (caps->supported_afbc_layouts == 0))
|
||||
return caps;
|
||||
|
||||
if (has_bits(afbc_features, caps->supported_afbc_features) &&
|
||||
has_bit(afbc_layout, caps->supported_afbc_layouts))
|
||||
return caps;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
u32 *komeda_get_layer_fourcc_list(struct komeda_format_caps_table *table,
|
||||
u32 layer_type, u32 *n_fmts)
|
||||
{
|
||||
const struct komeda_format_caps *cap;
|
||||
u32 *fmts;
|
||||
int i, j, n = 0;
|
||||
|
||||
fmts = kcalloc(table->n_formats, sizeof(u32), GFP_KERNEL);
|
||||
if (!fmts)
|
||||
return NULL;
|
||||
|
||||
for (i = 0; i < table->n_formats; i++) {
|
||||
cap = &table->format_caps[i];
|
||||
if (!(layer_type & cap->supported_layer_types) ||
|
||||
(cap->fourcc == 0))
|
||||
continue;
|
||||
|
||||
/* one fourcc may has two caps items in table (afbc/none-afbc),
|
||||
* so check the existing list to avoid adding a duplicated one.
|
||||
*/
|
||||
for (j = n - 1; j >= 0; j--)
|
||||
if (fmts[j] == cap->fourcc)
|
||||
break;
|
||||
|
||||
if (j < 0)
|
||||
fmts[n++] = cap->fourcc;
|
||||
}
|
||||
|
||||
if (n_fmts)
|
||||
*n_fmts = n;
|
||||
|
||||
return fmts;
|
||||
}
|
||||
|
||||
void komeda_put_fourcc_list(u32 *fourcc_list)
|
||||
{
|
||||
kfree(fourcc_list);
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef _KOMEDA_FORMAT_CAPS_H_
|
||||
#define _KOMEDA_FORMAT_CAPS_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <uapi/drm/drm_fourcc.h>
|
||||
#include <drm/drm_fourcc.h>
|
||||
|
||||
#define AFBC(x) DRM_FORMAT_MOD_ARM_AFBC(x)
|
||||
|
||||
/* afbc layerout */
|
||||
#define AFBC_16x16(x) AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_16x16 | (x))
|
||||
#define AFBC_32x8(x) AFBC(AFBC_FORMAT_MOD_BLOCK_SIZE_32x8 | (x))
|
||||
|
||||
/* afbc features */
|
||||
#define _YTR AFBC_FORMAT_MOD_YTR
|
||||
#define _SPLIT AFBC_FORMAT_MOD_SPLIT
|
||||
#define _SPARSE AFBC_FORMAT_MOD_SPARSE
|
||||
#define _CBR AFBC_FORMAT_MOD_CBR
|
||||
#define _TILED AFBC_FORMAT_MOD_TILED
|
||||
#define _SC AFBC_FORMAT_MOD_SC
|
||||
|
||||
/* layer_type */
|
||||
#define KOMEDA_FMT_RICH_LAYER BIT(0)
|
||||
#define KOMEDA_FMT_SIMPLE_LAYER BIT(1)
|
||||
#define KOMEDA_FMT_WB_LAYER BIT(2)
|
||||
|
||||
#define AFBC_TH_LAYOUT_ALIGNMENT 8
|
||||
#define AFBC_HEADER_SIZE 16
|
||||
#define AFBC_SUPERBLK_ALIGNMENT 128
|
||||
#define AFBC_SUPERBLK_PIXELS 256
|
||||
#define AFBC_BODY_START_ALIGNMENT 1024
|
||||
#define AFBC_TH_BODY_START_ALIGNMENT 4096
|
||||
|
||||
/**
|
||||
* struct komeda_format_caps
|
||||
*
|
||||
* komeda_format_caps is for describing ARM display specific features and
|
||||
* limitations for a specific format, and format_caps will be linked into
|
||||
* &komeda_framebuffer like a extension of &drm_format_info.
|
||||
*
|
||||
* NOTE: one fourcc may has two different format_caps items for fourcc and
|
||||
* fourcc+modifier
|
||||
*
|
||||
* @hw_id: hw format id, hw specific value.
|
||||
* @fourcc: drm fourcc format.
|
||||
* @tile_size: format tiled size, used by ARM format X0L0/X0L2
|
||||
* @supported_layer_types: indicate which layer supports this format
|
||||
* @supported_rots: allowed rotations for this format
|
||||
* @supported_afbc_layouts: supported afbc layerout
|
||||
* @supported_afbc_features: supported afbc features
|
||||
*/
|
||||
struct komeda_format_caps {
|
||||
u32 hw_id;
|
||||
u32 fourcc;
|
||||
u32 tile_size;
|
||||
u32 supported_layer_types;
|
||||
u32 supported_rots;
|
||||
u32 supported_afbc_layouts;
|
||||
u64 supported_afbc_features;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct komeda_format_caps_table - format_caps mananger
|
||||
*
|
||||
* @n_formats: the size of format_caps list.
|
||||
* @format_caps: format_caps list.
|
||||
*/
|
||||
struct komeda_format_caps_table {
|
||||
u32 n_formats;
|
||||
const struct komeda_format_caps *format_caps;
|
||||
};
|
||||
|
||||
const struct komeda_format_caps *
|
||||
komeda_get_format_caps(struct komeda_format_caps_table *table,
|
||||
u32 fourcc, u64 modifier);
|
||||
|
||||
u32 *komeda_get_layer_fourcc_list(struct komeda_format_caps_table *table,
|
||||
u32 layer_type, u32 *n_fmts);
|
||||
|
||||
void komeda_put_fourcc_list(u32 *fourcc_list);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,165 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#include <drm/drm_gem.h>
|
||||
#include <drm/drm_gem_framebuffer_helper.h>
|
||||
#include <drm/drm_fb_cma_helper.h>
|
||||
#include <drm/drm_gem_cma_helper.h>
|
||||
#include "komeda_framebuffer.h"
|
||||
#include "komeda_dev.h"
|
||||
|
||||
static void komeda_fb_destroy(struct drm_framebuffer *fb)
|
||||
{
|
||||
struct komeda_fb *kfb = to_kfb(fb);
|
||||
u32 i;
|
||||
|
||||
for (i = 0; i < fb->format->num_planes; i++)
|
||||
drm_gem_object_put_unlocked(fb->obj[i]);
|
||||
|
||||
drm_framebuffer_cleanup(fb);
|
||||
kfree(kfb);
|
||||
}
|
||||
|
||||
static int komeda_fb_create_handle(struct drm_framebuffer *fb,
|
||||
struct drm_file *file, u32 *handle)
|
||||
{
|
||||
return drm_gem_handle_create(file, fb->obj[0], handle);
|
||||
}
|
||||
|
||||
static const struct drm_framebuffer_funcs komeda_fb_funcs = {
|
||||
.destroy = komeda_fb_destroy,
|
||||
.create_handle = komeda_fb_create_handle,
|
||||
};
|
||||
|
||||
static int
|
||||
komeda_fb_none_afbc_size_check(struct komeda_dev *mdev, struct komeda_fb *kfb,
|
||||
struct drm_file *file,
|
||||
const struct drm_mode_fb_cmd2 *mode_cmd)
|
||||
{
|
||||
struct drm_framebuffer *fb = &kfb->base;
|
||||
struct drm_gem_object *obj;
|
||||
u32 min_size = 0;
|
||||
u32 i;
|
||||
|
||||
for (i = 0; i < fb->format->num_planes; i++) {
|
||||
obj = drm_gem_object_lookup(file, mode_cmd->handles[i]);
|
||||
if (!obj) {
|
||||
DRM_DEBUG_KMS("Failed to lookup GEM object\n");
|
||||
fb->obj[i] = NULL;
|
||||
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
kfb->aligned_w = fb->width / (i ? fb->format->hsub : 1);
|
||||
kfb->aligned_h = fb->height / (i ? fb->format->vsub : 1);
|
||||
|
||||
if (fb->pitches[i] % mdev->chip.bus_width) {
|
||||
DRM_DEBUG_KMS("Pitch[%d]: 0x%x doesn't align to 0x%x\n",
|
||||
i, fb->pitches[i], mdev->chip.bus_width);
|
||||
drm_gem_object_put_unlocked(obj);
|
||||
fb->obj[i] = NULL;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
min_size = ((kfb->aligned_h / kfb->format_caps->tile_size - 1)
|
||||
* fb->pitches[i])
|
||||
+ (kfb->aligned_w * fb->format->cpp[i]
|
||||
* kfb->format_caps->tile_size)
|
||||
+ fb->offsets[i];
|
||||
|
||||
if (obj->size < min_size) {
|
||||
DRM_DEBUG_KMS("Fail to check none afbc fb size.\n");
|
||||
drm_gem_object_put_unlocked(obj);
|
||||
fb->obj[i] = NULL;
|
||||
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
fb->obj[i] = obj;
|
||||
}
|
||||
|
||||
if (fb->format->num_planes == 3) {
|
||||
if (fb->pitches[1] != fb->pitches[2]) {
|
||||
DRM_DEBUG_KMS("The pitch[1] and [2] are not same\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct drm_framebuffer *
|
||||
komeda_fb_create(struct drm_device *dev, struct drm_file *file,
|
||||
const struct drm_mode_fb_cmd2 *mode_cmd)
|
||||
{
|
||||
struct komeda_dev *mdev = dev->dev_private;
|
||||
struct komeda_fb *kfb;
|
||||
int ret = 0, i;
|
||||
|
||||
kfb = kzalloc(sizeof(*kfb), GFP_KERNEL);
|
||||
if (!kfb)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
kfb->format_caps = komeda_get_format_caps(&mdev->fmt_tbl,
|
||||
mode_cmd->pixel_format,
|
||||
mode_cmd->modifier[0]);
|
||||
if (!kfb->format_caps) {
|
||||
DRM_DEBUG_KMS("FMT %x is not supported.\n",
|
||||
mode_cmd->pixel_format);
|
||||
kfree(kfb);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
|
||||
drm_helper_mode_fill_fb_struct(dev, &kfb->base, mode_cmd);
|
||||
|
||||
ret = komeda_fb_none_afbc_size_check(mdev, kfb, file, mode_cmd);
|
||||
if (ret < 0)
|
||||
goto err_cleanup;
|
||||
|
||||
ret = drm_framebuffer_init(dev, &kfb->base, &komeda_fb_funcs);
|
||||
if (ret < 0) {
|
||||
DRM_DEBUG_KMS("failed to initialize fb\n");
|
||||
|
||||
goto err_cleanup;
|
||||
}
|
||||
|
||||
return &kfb->base;
|
||||
|
||||
err_cleanup:
|
||||
for (i = 0; i < kfb->base.format->num_planes; i++)
|
||||
drm_gem_object_put_unlocked(kfb->base.obj[i]);
|
||||
|
||||
kfree(kfb);
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
dma_addr_t
|
||||
komeda_fb_get_pixel_addr(struct komeda_fb *kfb, int x, int y, int plane)
|
||||
{
|
||||
struct drm_framebuffer *fb = &kfb->base;
|
||||
const struct drm_gem_cma_object *obj;
|
||||
u32 plane_x, plane_y, cpp, pitch, offset;
|
||||
|
||||
if (plane >= fb->format->num_planes) {
|
||||
DRM_DEBUG_KMS("Out of max plane num.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
obj = drm_fb_cma_get_gem_obj(fb, plane);
|
||||
|
||||
offset = fb->offsets[plane];
|
||||
if (!fb->modifier) {
|
||||
plane_x = x / (plane ? fb->format->hsub : 1);
|
||||
plane_y = y / (plane ? fb->format->vsub : 1);
|
||||
cpp = fb->format->cpp[plane];
|
||||
pitch = fb->pitches[plane];
|
||||
offset += plane_x * cpp * kfb->format_caps->tile_size +
|
||||
(plane_y * pitch) / kfb->format_caps->tile_size;
|
||||
}
|
||||
|
||||
return obj->paddr + offset;
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#ifndef _KOMEDA_FRAMEBUFFER_H_
|
||||
#define _KOMEDA_FRAMEBUFFER_H_
|
||||
|
||||
#include <drm/drm_framebuffer.h>
|
||||
#include "komeda_format_caps.h"
|
||||
|
||||
/** struct komeda_fb - entend drm_framebuffer with komeda attribute */
|
||||
struct komeda_fb {
|
||||
/** @base: &drm_framebuffer */
|
||||
struct drm_framebuffer base;
|
||||
/* @format_caps: &komeda_format_caps */
|
||||
const struct komeda_format_caps *format_caps;
|
||||
/** @aligned_w: aligned frame buffer width */
|
||||
u32 aligned_w;
|
||||
/** @aligned_h: aligned frame buffer height */
|
||||
u32 aligned_h;
|
||||
};
|
||||
|
||||
#define to_kfb(dfb) container_of(dfb, struct komeda_fb, base)
|
||||
|
||||
struct drm_framebuffer *
|
||||
komeda_fb_create(struct drm_device *dev, struct drm_file *file,
|
||||
const struct drm_mode_fb_cmd2 *mode_cmd);
|
||||
dma_addr_t
|
||||
komeda_fb_get_pixel_addr(struct komeda_fb *kfb, int x, int y, int plane);
|
||||
bool komeda_fb_is_layer_supported(struct komeda_fb *kfb, u32 layer_type);
|
||||
|
||||
#endif
|
|
@ -0,0 +1,167 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#include <linux/component.h>
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_gem_framebuffer_helper.h>
|
||||
#include <drm/drm_gem_cma_helper.h>
|
||||
#include <drm/drm_fb_helper.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include "komeda_dev.h"
|
||||
#include "komeda_kms.h"
|
||||
#include "komeda_framebuffer.h"
|
||||
|
||||
DEFINE_DRM_GEM_CMA_FOPS(komeda_cma_fops);
|
||||
|
||||
static int komeda_gem_cma_dumb_create(struct drm_file *file,
|
||||
struct drm_device *dev,
|
||||
struct drm_mode_create_dumb *args)
|
||||
{
|
||||
u32 alignment = 16; /* TODO get alignment from dev */
|
||||
|
||||
args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8),
|
||||
alignment);
|
||||
|
||||
return drm_gem_cma_dumb_create_internal(file, dev, args);
|
||||
}
|
||||
|
||||
static struct drm_driver komeda_kms_driver = {
|
||||
.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC |
|
||||
DRIVER_PRIME,
|
||||
.lastclose = drm_fb_helper_lastclose,
|
||||
.gem_free_object_unlocked = drm_gem_cma_free_object,
|
||||
.gem_vm_ops = &drm_gem_cma_vm_ops,
|
||||
.dumb_create = komeda_gem_cma_dumb_create,
|
||||
.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
|
||||
.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
|
||||
.gem_prime_export = drm_gem_prime_export,
|
||||
.gem_prime_import = drm_gem_prime_import,
|
||||
.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
|
||||
.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
|
||||
.gem_prime_vmap = drm_gem_cma_prime_vmap,
|
||||
.gem_prime_vunmap = drm_gem_cma_prime_vunmap,
|
||||
.gem_prime_mmap = drm_gem_cma_prime_mmap,
|
||||
.fops = &komeda_cma_fops,
|
||||
.name = "komeda",
|
||||
.desc = "Arm Komeda Display Processor driver",
|
||||
.date = "20181101",
|
||||
.major = 0,
|
||||
.minor = 1,
|
||||
};
|
||||
|
||||
static void komeda_kms_commit_tail(struct drm_atomic_state *old_state)
|
||||
{
|
||||
struct drm_device *dev = old_state->dev;
|
||||
|
||||
drm_atomic_helper_commit_modeset_disables(dev, old_state);
|
||||
|
||||
drm_atomic_helper_commit_planes(dev, old_state, 0);
|
||||
|
||||
drm_atomic_helper_commit_modeset_enables(dev, old_state);
|
||||
|
||||
drm_atomic_helper_wait_for_flip_done(dev, old_state);
|
||||
|
||||
drm_atomic_helper_commit_hw_done(old_state);
|
||||
|
||||
drm_atomic_helper_cleanup_planes(dev, old_state);
|
||||
}
|
||||
|
||||
static const struct drm_mode_config_helper_funcs komeda_mode_config_helpers = {
|
||||
.atomic_commit_tail = komeda_kms_commit_tail,
|
||||
};
|
||||
|
||||
static const struct drm_mode_config_funcs komeda_mode_config_funcs = {
|
||||
.fb_create = komeda_fb_create,
|
||||
.atomic_check = drm_atomic_helper_check,
|
||||
.atomic_commit = drm_atomic_helper_commit,
|
||||
};
|
||||
|
||||
static void komeda_kms_mode_config_init(struct komeda_kms_dev *kms,
|
||||
struct komeda_dev *mdev)
|
||||
{
|
||||
struct drm_mode_config *config = &kms->base.mode_config;
|
||||
|
||||
drm_mode_config_init(&kms->base);
|
||||
|
||||
komeda_kms_setup_crtcs(kms, mdev);
|
||||
|
||||
/* Get value from dev */
|
||||
config->min_width = 0;
|
||||
config->min_height = 0;
|
||||
config->max_width = 4096;
|
||||
config->max_height = 4096;
|
||||
config->allow_fb_modifiers = false;
|
||||
|
||||
config->funcs = &komeda_mode_config_funcs;
|
||||
config->helper_private = &komeda_mode_config_helpers;
|
||||
}
|
||||
|
||||
struct komeda_kms_dev *komeda_kms_attach(struct komeda_dev *mdev)
|
||||
{
|
||||
struct komeda_kms_dev *kms = kzalloc(sizeof(*kms), GFP_KERNEL);
|
||||
struct drm_device *drm;
|
||||
int err;
|
||||
|
||||
if (!kms)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
drm = &kms->base;
|
||||
err = drm_dev_init(drm, &komeda_kms_driver, mdev->dev);
|
||||
if (err)
|
||||
goto free_kms;
|
||||
|
||||
drm->dev_private = mdev;
|
||||
|
||||
komeda_kms_mode_config_init(kms, mdev);
|
||||
|
||||
err = komeda_kms_add_private_objs(kms, mdev);
|
||||
if (err)
|
||||
goto cleanup_mode_config;
|
||||
|
||||
err = komeda_kms_add_planes(kms, mdev);
|
||||
if (err)
|
||||
goto cleanup_mode_config;
|
||||
|
||||
err = drm_vblank_init(drm, kms->n_crtcs);
|
||||
if (err)
|
||||
goto cleanup_mode_config;
|
||||
|
||||
err = komeda_kms_add_crtcs(kms, mdev);
|
||||
if (err)
|
||||
goto cleanup_mode_config;
|
||||
|
||||
err = component_bind_all(mdev->dev, kms);
|
||||
if (err)
|
||||
goto cleanup_mode_config;
|
||||
|
||||
drm_mode_config_reset(drm);
|
||||
|
||||
err = drm_dev_register(drm, 0);
|
||||
if (err)
|
||||
goto cleanup_mode_config;
|
||||
|
||||
return kms;
|
||||
|
||||
cleanup_mode_config:
|
||||
drm_mode_config_cleanup(drm);
|
||||
free_kms:
|
||||
kfree(kms);
|
||||
return ERR_PTR(err);
|
||||
}
|
||||
|
||||
void komeda_kms_detach(struct komeda_kms_dev *kms)
|
||||
{
|
||||
struct drm_device *drm = &kms->base;
|
||||
struct komeda_dev *mdev = drm->dev_private;
|
||||
|
||||
drm_dev_unregister(drm);
|
||||
component_unbind_all(mdev->dev, drm);
|
||||
komeda_kms_cleanup_private_objs(mdev);
|
||||
drm_mode_config_cleanup(drm);
|
||||
drm->dev_private = NULL;
|
||||
drm_dev_put(drm);
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#ifndef _KOMEDA_KMS_H_
|
||||
#define _KOMEDA_KMS_H_
|
||||
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_crtc_helper.h>
|
||||
#include <drm/drm_writeback.h>
|
||||
|
||||
/** struct komeda_plane - komeda instance of drm_plane */
|
||||
struct komeda_plane {
|
||||
/** @base: &drm_plane */
|
||||
struct drm_plane base;
|
||||
/**
|
||||
* @layer:
|
||||
*
|
||||
* represents available layer input pipelines for this plane.
|
||||
*
|
||||
* NOTE:
|
||||
* the layer is not for a specific Layer, but indicate a group of
|
||||
* Layers with same capabilities.
|
||||
*/
|
||||
struct komeda_layer *layer;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct komeda_plane_state
|
||||
*
|
||||
* The plane_state can be split into two data flow (left/right) and handled
|
||||
* by two layers &komeda_plane.layer and &komeda_plane.layer.right
|
||||
*/
|
||||
struct komeda_plane_state {
|
||||
/** @base: &drm_plane_state */
|
||||
struct drm_plane_state base;
|
||||
|
||||
/* private properties */
|
||||
};
|
||||
|
||||
/**
|
||||
* struct komeda_wb_connector
|
||||
*/
|
||||
struct komeda_wb_connector {
|
||||
/** @base: &drm_writeback_connector */
|
||||
struct drm_writeback_connector base;
|
||||
|
||||
/** @wb_layer: represents associated writeback pipeline of komeda */
|
||||
struct komeda_layer *wb_layer;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct komeda_crtc
|
||||
*/
|
||||
struct komeda_crtc {
|
||||
/** @base: &drm_crtc */
|
||||
struct drm_crtc base;
|
||||
/** @master: only master has display output */
|
||||
struct komeda_pipeline *master;
|
||||
/**
|
||||
* @slave: optional
|
||||
*
|
||||
* Doesn't have its own display output, the handled data flow will
|
||||
* merge into the master.
|
||||
*/
|
||||
struct komeda_pipeline *slave;
|
||||
};
|
||||
|
||||
/** struct komeda_crtc_state */
|
||||
struct komeda_crtc_state {
|
||||
/** @base: &drm_crtc_state */
|
||||
struct drm_crtc_state base;
|
||||
|
||||
/* private properties */
|
||||
|
||||
/* computed state which are used by validate/check */
|
||||
u32 affected_pipes;
|
||||
u32 active_pipes;
|
||||
};
|
||||
|
||||
/** struct komeda_kms_dev - for gather KMS related things */
|
||||
struct komeda_kms_dev {
|
||||
/** @base: &drm_device */
|
||||
struct drm_device base;
|
||||
|
||||
/** @n_crtcs: valid numbers of crtcs in &komeda_kms_dev.crtcs */
|
||||
int n_crtcs;
|
||||
/** @crtcs: crtcs list */
|
||||
struct komeda_crtc crtcs[KOMEDA_MAX_PIPELINES];
|
||||
};
|
||||
|
||||
#define to_kplane(p) container_of(p, struct komeda_plane, base)
|
||||
#define to_kplane_st(p) container_of(p, struct komeda_plane_state, base)
|
||||
#define to_kconn(p) container_of(p, struct komeda_wb_connector, base)
|
||||
#define to_kcrtc(p) container_of(p, struct komeda_crtc, base)
|
||||
#define to_kcrtc_st(p) container_of(p, struct komeda_crtc_state, base)
|
||||
#define to_kdev(p) container_of(p, struct komeda_kms_dev, base)
|
||||
|
||||
int komeda_kms_setup_crtcs(struct komeda_kms_dev *kms, struct komeda_dev *mdev);
|
||||
|
||||
int komeda_kms_add_crtcs(struct komeda_kms_dev *kms, struct komeda_dev *mdev);
|
||||
int komeda_kms_add_planes(struct komeda_kms_dev *kms, struct komeda_dev *mdev);
|
||||
int komeda_kms_add_private_objs(struct komeda_kms_dev *kms,
|
||||
struct komeda_dev *mdev);
|
||||
void komeda_kms_cleanup_private_objs(struct komeda_dev *mdev);
|
||||
|
||||
struct komeda_kms_dev *komeda_kms_attach(struct komeda_dev *mdev);
|
||||
void komeda_kms_detach(struct komeda_kms_dev *kms);
|
||||
|
||||
#endif /*_KOMEDA_KMS_H_*/
|
|
@ -0,0 +1,200 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#include "komeda_dev.h"
|
||||
#include "komeda_pipeline.h"
|
||||
|
||||
/** komeda_pipeline_add - Add a pipeline to &komeda_dev */
|
||||
struct komeda_pipeline *
|
||||
komeda_pipeline_add(struct komeda_dev *mdev, size_t size,
|
||||
struct komeda_pipeline_funcs *funcs)
|
||||
{
|
||||
struct komeda_pipeline *pipe;
|
||||
|
||||
if (mdev->n_pipelines + 1 > KOMEDA_MAX_PIPELINES) {
|
||||
DRM_ERROR("Exceed max support %d pipelines.\n",
|
||||
KOMEDA_MAX_PIPELINES);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (size < sizeof(*pipe)) {
|
||||
DRM_ERROR("Request pipeline size too small.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pipe = devm_kzalloc(mdev->dev, size, GFP_KERNEL);
|
||||
if (!pipe)
|
||||
return NULL;
|
||||
|
||||
pipe->mdev = mdev;
|
||||
pipe->id = mdev->n_pipelines;
|
||||
pipe->funcs = funcs;
|
||||
|
||||
mdev->pipelines[mdev->n_pipelines] = pipe;
|
||||
mdev->n_pipelines++;
|
||||
|
||||
return pipe;
|
||||
}
|
||||
|
||||
void komeda_pipeline_destroy(struct komeda_dev *mdev,
|
||||
struct komeda_pipeline *pipe)
|
||||
{
|
||||
struct komeda_component *c;
|
||||
int i;
|
||||
|
||||
dp_for_each_set_bit(i, pipe->avail_comps) {
|
||||
c = komeda_pipeline_get_component(pipe, i);
|
||||
komeda_component_destroy(mdev, c);
|
||||
}
|
||||
|
||||
clk_put(pipe->pxlclk);
|
||||
clk_put(pipe->aclk);
|
||||
|
||||
of_node_put(pipe->of_output_dev);
|
||||
of_node_put(pipe->of_output_port);
|
||||
of_node_put(pipe->of_node);
|
||||
|
||||
devm_kfree(mdev->dev, pipe);
|
||||
}
|
||||
|
||||
struct komeda_component **
|
||||
komeda_pipeline_get_component_pos(struct komeda_pipeline *pipe, int id)
|
||||
{
|
||||
struct komeda_dev *mdev = pipe->mdev;
|
||||
struct komeda_pipeline *temp = NULL;
|
||||
struct komeda_component **pos = NULL;
|
||||
|
||||
switch (id) {
|
||||
case KOMEDA_COMPONENT_LAYER0:
|
||||
case KOMEDA_COMPONENT_LAYER1:
|
||||
case KOMEDA_COMPONENT_LAYER2:
|
||||
case KOMEDA_COMPONENT_LAYER3:
|
||||
pos = to_cpos(pipe->layers[id - KOMEDA_COMPONENT_LAYER0]);
|
||||
break;
|
||||
case KOMEDA_COMPONENT_WB_LAYER:
|
||||
pos = to_cpos(pipe->wb_layer);
|
||||
break;
|
||||
case KOMEDA_COMPONENT_COMPIZ0:
|
||||
case KOMEDA_COMPONENT_COMPIZ1:
|
||||
temp = mdev->pipelines[id - KOMEDA_COMPONENT_COMPIZ0];
|
||||
if (!temp) {
|
||||
DRM_ERROR("compiz-%d doesn't exist.\n", id);
|
||||
return NULL;
|
||||
}
|
||||
pos = to_cpos(temp->compiz);
|
||||
break;
|
||||
case KOMEDA_COMPONENT_SCALER0:
|
||||
case KOMEDA_COMPONENT_SCALER1:
|
||||
pos = to_cpos(pipe->scalers[id - KOMEDA_COMPONENT_SCALER0]);
|
||||
break;
|
||||
case KOMEDA_COMPONENT_IPS0:
|
||||
case KOMEDA_COMPONENT_IPS1:
|
||||
temp = mdev->pipelines[id - KOMEDA_COMPONENT_IPS0];
|
||||
if (!temp) {
|
||||
DRM_ERROR("ips-%d doesn't exist.\n", id);
|
||||
return NULL;
|
||||
}
|
||||
pos = to_cpos(temp->improc);
|
||||
break;
|
||||
case KOMEDA_COMPONENT_TIMING_CTRLR:
|
||||
pos = to_cpos(pipe->ctrlr);
|
||||
break;
|
||||
default:
|
||||
pos = NULL;
|
||||
DRM_ERROR("Unknown pipeline resource ID: %d.\n", id);
|
||||
break;
|
||||
}
|
||||
|
||||
return pos;
|
||||
}
|
||||
|
||||
struct komeda_component *
|
||||
komeda_pipeline_get_component(struct komeda_pipeline *pipe, int id)
|
||||
{
|
||||
struct komeda_component **pos = NULL;
|
||||
struct komeda_component *c = NULL;
|
||||
|
||||
pos = komeda_pipeline_get_component_pos(pipe, id);
|
||||
if (pos)
|
||||
c = *pos;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
/** komeda_component_add - Add a component to &komeda_pipeline */
|
||||
struct komeda_component *
|
||||
komeda_component_add(struct komeda_pipeline *pipe,
|
||||
size_t comp_sz, u32 id, u32 hw_id,
|
||||
struct komeda_component_funcs *funcs,
|
||||
u8 max_active_inputs, u32 supported_inputs,
|
||||
u8 max_active_outputs, u32 __iomem *reg,
|
||||
const char *name_fmt, ...)
|
||||
{
|
||||
struct komeda_component **pos;
|
||||
struct komeda_component *c;
|
||||
int idx, *num = NULL;
|
||||
|
||||
if (max_active_inputs > KOMEDA_COMPONENT_N_INPUTS) {
|
||||
WARN(1, "please large KOMEDA_COMPONENT_N_INPUTS to %d.\n",
|
||||
max_active_inputs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
pos = komeda_pipeline_get_component_pos(pipe, id);
|
||||
if (!pos || (*pos))
|
||||
return NULL;
|
||||
|
||||
if (has_bit(id, KOMEDA_PIPELINE_LAYERS)) {
|
||||
idx = id - KOMEDA_COMPONENT_LAYER0;
|
||||
num = &pipe->n_layers;
|
||||
if (idx != pipe->n_layers) {
|
||||
DRM_ERROR("please add Layer by id sequence.\n");
|
||||
return NULL;
|
||||
}
|
||||
} else if (has_bit(id, KOMEDA_PIPELINE_SCALERS)) {
|
||||
idx = id - KOMEDA_COMPONENT_SCALER0;
|
||||
num = &pipe->n_scalers;
|
||||
if (idx != pipe->n_scalers) {
|
||||
DRM_ERROR("please add Scaler by id sequence.\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
c = devm_kzalloc(pipe->mdev->dev, comp_sz, GFP_KERNEL);
|
||||
if (!c)
|
||||
return NULL;
|
||||
|
||||
c->id = id;
|
||||
c->hw_id = hw_id;
|
||||
c->reg = reg;
|
||||
c->pipeline = pipe;
|
||||
c->max_active_inputs = max_active_inputs;
|
||||
c->max_active_outputs = max_active_outputs;
|
||||
c->supported_inputs = supported_inputs;
|
||||
c->funcs = funcs;
|
||||
|
||||
if (name_fmt) {
|
||||
va_list args;
|
||||
|
||||
va_start(args, name_fmt);
|
||||
vsnprintf(c->name, sizeof(c->name), name_fmt, args);
|
||||
va_end(args);
|
||||
}
|
||||
|
||||
if (num)
|
||||
*num = *num + 1;
|
||||
|
||||
pipe->avail_comps |= BIT(c->id);
|
||||
*pos = c;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
void komeda_component_destroy(struct komeda_dev *mdev,
|
||||
struct komeda_component *c)
|
||||
{
|
||||
devm_kfree(mdev->dev, c);
|
||||
}
|
|
@ -0,0 +1,359 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#ifndef _KOMEDA_PIPELINE_H_
|
||||
#define _KOMEDA_PIPELINE_H_
|
||||
|
||||
#include <linux/types.h>
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include "malidp_utils.h"
|
||||
|
||||
#define KOMEDA_MAX_PIPELINES 2
|
||||
#define KOMEDA_PIPELINE_MAX_LAYERS 4
|
||||
#define KOMEDA_PIPELINE_MAX_SCALERS 2
|
||||
#define KOMEDA_COMPONENT_N_INPUTS 5
|
||||
|
||||
/* pipeline component IDs */
|
||||
enum {
|
||||
KOMEDA_COMPONENT_LAYER0 = 0,
|
||||
KOMEDA_COMPONENT_LAYER1 = 1,
|
||||
KOMEDA_COMPONENT_LAYER2 = 2,
|
||||
KOMEDA_COMPONENT_LAYER3 = 3,
|
||||
KOMEDA_COMPONENT_WB_LAYER = 7, /* write back layer */
|
||||
KOMEDA_COMPONENT_SCALER0 = 8,
|
||||
KOMEDA_COMPONENT_SCALER1 = 9,
|
||||
KOMEDA_COMPONENT_SPLITTER = 12,
|
||||
KOMEDA_COMPONENT_MERGER = 14,
|
||||
KOMEDA_COMPONENT_COMPIZ0 = 16, /* compositor */
|
||||
KOMEDA_COMPONENT_COMPIZ1 = 17,
|
||||
KOMEDA_COMPONENT_IPS0 = 20, /* post image processor */
|
||||
KOMEDA_COMPONENT_IPS1 = 21,
|
||||
KOMEDA_COMPONENT_TIMING_CTRLR = 22, /* timing controller */
|
||||
};
|
||||
|
||||
#define KOMEDA_PIPELINE_LAYERS (BIT(KOMEDA_COMPONENT_LAYER0) |\
|
||||
BIT(KOMEDA_COMPONENT_LAYER1) |\
|
||||
BIT(KOMEDA_COMPONENT_LAYER2) |\
|
||||
BIT(KOMEDA_COMPONENT_LAYER3))
|
||||
|
||||
#define KOMEDA_PIPELINE_SCALERS (BIT(KOMEDA_COMPONENT_SCALER0) |\
|
||||
BIT(KOMEDA_COMPONENT_SCALER1))
|
||||
|
||||
#define KOMEDA_PIPELINE_COMPIZS (BIT(KOMEDA_COMPONENT_COMPIZ0) |\
|
||||
BIT(KOMEDA_COMPONENT_COMPIZ1))
|
||||
|
||||
#define KOMEDA_PIPELINE_IMPROCS (BIT(KOMEDA_COMPONENT_IPS0) |\
|
||||
BIT(KOMEDA_COMPONENT_IPS1))
|
||||
struct komeda_component;
|
||||
struct komeda_component_state;
|
||||
|
||||
/** komeda_component_funcs - component control functions */
|
||||
struct komeda_component_funcs {
|
||||
/** @validate: optional,
|
||||
* component may has special requirements or limitations, this function
|
||||
* supply HW the ability to do the further HW specific check.
|
||||
*/
|
||||
int (*validate)(struct komeda_component *c,
|
||||
struct komeda_component_state *state);
|
||||
/** @update: update is a active update */
|
||||
void (*update)(struct komeda_component *c,
|
||||
struct komeda_component_state *state);
|
||||
/** @disable: disable component */
|
||||
void (*disable)(struct komeda_component *c);
|
||||
/** @dump_register: Optional, dump registers to seq_file */
|
||||
void (*dump_register)(struct komeda_component *c, struct seq_file *seq);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct komeda_component
|
||||
*
|
||||
* struct komeda_component describe the data flow capabilities for how to link a
|
||||
* component into the display pipeline.
|
||||
* all specified components are subclass of this structure.
|
||||
*/
|
||||
struct komeda_component {
|
||||
/** @obj: treat component as private obj */
|
||||
struct drm_private_obj obj;
|
||||
/** @pipeline: the komeda pipeline this component belongs to */
|
||||
struct komeda_pipeline *pipeline;
|
||||
/** @name: component name */
|
||||
char name[32];
|
||||
/**
|
||||
* @reg:
|
||||
* component register base,
|
||||
* which is initialized by chip and used by chip only
|
||||
*/
|
||||
u32 __iomem *reg;
|
||||
/** @id: component id */
|
||||
u32 id;
|
||||
/** @hw_ic: component hw id,
|
||||
* which is initialized by chip and used by chip only
|
||||
*/
|
||||
u32 hw_id;
|
||||
|
||||
/**
|
||||
* @max_active_inputs:
|
||||
* @max_active_outpus:
|
||||
*
|
||||
* maximum number of inputs/outputs that can be active in the same time
|
||||
* Note:
|
||||
* the number isn't the bit number of @supported_inputs or
|
||||
* @supported_outputs, but may be less than it, since component may not
|
||||
* support enabling all @supported_inputs/outputs at the same time.
|
||||
*/
|
||||
u8 max_active_inputs;
|
||||
u8 max_active_outputs;
|
||||
/**
|
||||
* @supported_inputs:
|
||||
* @supported_outputs:
|
||||
*
|
||||
* bitmask of BIT(component->id) for the supported inputs/outputs
|
||||
* describes the possibilities of how a component is linked into a
|
||||
* pipeline.
|
||||
*/
|
||||
u32 supported_inputs;
|
||||
u32 supported_outputs;
|
||||
|
||||
/**
|
||||
* @funcs: chip functions to access HW
|
||||
*/
|
||||
struct komeda_component_funcs *funcs;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct komeda_component_output
|
||||
*
|
||||
* a component has multiple outputs, if want to know where the data
|
||||
* comes from, only know the component is not enough, we still need to know
|
||||
* its output port
|
||||
*/
|
||||
struct komeda_component_output {
|
||||
/** @component: indicate which component the data comes from */
|
||||
struct komeda_component *component;
|
||||
/** @output_port:
|
||||
* the output port of the &komeda_component_output.component
|
||||
*/
|
||||
u8 output_port;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct komeda_component_state
|
||||
*
|
||||
* component_state is the data flow configuration of the component, and it's
|
||||
* the superclass of all specific component_state like @komeda_layer_state,
|
||||
* @komeda_scaler_state
|
||||
*/
|
||||
struct komeda_component_state {
|
||||
/** @obj: tracking component_state by drm_atomic_state */
|
||||
struct drm_private_state obj;
|
||||
struct komeda_component *component;
|
||||
/**
|
||||
* @binding_user:
|
||||
* currently bound user, the user can be crtc/plane/wb_conn, which is
|
||||
* valid decided by @component and @inputs
|
||||
*
|
||||
* - Layer: its user always is plane.
|
||||
* - compiz/improc/timing_ctrlr: the user is crtc.
|
||||
* - wb_layer: wb_conn;
|
||||
* - scaler: plane when input is layer, wb_conn if input is compiz.
|
||||
*/
|
||||
union {
|
||||
struct drm_crtc *crtc;
|
||||
struct drm_plane *plane;
|
||||
struct drm_connector *wb_conn;
|
||||
void *binding_user;
|
||||
};
|
||||
/**
|
||||
* @active_inputs:
|
||||
*
|
||||
* active_inputs is bitmask of @inputs index
|
||||
*
|
||||
* - active_inputs = changed_active_inputs + unchanged_active_inputs
|
||||
* - affected_inputs = old->active_inputs + new->active_inputs;
|
||||
* - disabling_inputs = affected_inputs ^ active_inputs;
|
||||
* - changed_inputs = disabling_inputs + changed_active_inputs;
|
||||
*
|
||||
* NOTE:
|
||||
* changed_inputs doesn't include all active_input but only
|
||||
* @changed_active_inputs, and this bitmask can be used in chip
|
||||
* level for dirty update.
|
||||
*/
|
||||
u16 active_inputs;
|
||||
u16 changed_active_inputs;
|
||||
u16 affected_inputs;
|
||||
/**
|
||||
* @inputs:
|
||||
*
|
||||
* the specific inputs[i] only valid on BIT(i) has been set in
|
||||
* @active_inputs, if not the inputs[i] is undefined.
|
||||
*/
|
||||
struct komeda_component_output inputs[KOMEDA_COMPONENT_N_INPUTS];
|
||||
};
|
||||
|
||||
static inline u16 component_disabling_inputs(struct komeda_component_state *st)
|
||||
{
|
||||
return st->affected_inputs ^ st->active_inputs;
|
||||
}
|
||||
|
||||
static inline u16 component_changed_inputs(struct komeda_component_state *st)
|
||||
{
|
||||
return component_disabling_inputs(st) | st->changed_active_inputs;
|
||||
}
|
||||
|
||||
#define to_comp(__c) (((__c) == NULL) ? NULL : &((__c)->base))
|
||||
#define to_cpos(__c) ((struct komeda_component **)&(__c))
|
||||
|
||||
/* these structures are going to be filled in in uture patches */
|
||||
struct komeda_layer {
|
||||
struct komeda_component base;
|
||||
/* layer specific features and caps */
|
||||
int layer_type; /* RICH, SIMPLE or WB */
|
||||
};
|
||||
|
||||
struct komeda_layer_state {
|
||||
struct komeda_component_state base;
|
||||
/* layer specific configuration state */
|
||||
};
|
||||
|
||||
struct komeda_compiz {
|
||||
struct komeda_component base;
|
||||
/* compiz specific features and caps */
|
||||
};
|
||||
|
||||
struct komeda_compiz_state {
|
||||
struct komeda_component_state base;
|
||||
/* compiz specific configuration state */
|
||||
};
|
||||
|
||||
struct komeda_scaler {
|
||||
struct komeda_component base;
|
||||
/* scaler features and caps */
|
||||
};
|
||||
|
||||
struct komeda_scaler_state {
|
||||
struct komeda_component_state base;
|
||||
};
|
||||
|
||||
struct komeda_improc {
|
||||
struct komeda_component base;
|
||||
};
|
||||
|
||||
struct komeda_improc_state {
|
||||
struct komeda_component_state base;
|
||||
};
|
||||
|
||||
/* display timing controller */
|
||||
struct komeda_timing_ctrlr {
|
||||
struct komeda_component base;
|
||||
};
|
||||
|
||||
struct komeda_timing_ctrlr_state {
|
||||
struct komeda_component_state base;
|
||||
};
|
||||
|
||||
/** struct komeda_pipeline_funcs */
|
||||
struct komeda_pipeline_funcs {
|
||||
/* dump_register: Optional, dump registers to seq_file */
|
||||
void (*dump_register)(struct komeda_pipeline *pipe,
|
||||
struct seq_file *sf);
|
||||
};
|
||||
|
||||
/**
|
||||
* struct komeda_pipeline
|
||||
*
|
||||
* Represent a complete display pipeline and hold all functional components.
|
||||
*/
|
||||
struct komeda_pipeline {
|
||||
/** @obj: link pipeline as private obj of drm_atomic_state */
|
||||
struct drm_private_obj obj;
|
||||
/** @mdev: the parent komeda_dev */
|
||||
struct komeda_dev *mdev;
|
||||
/** @pxlclk: pixel clock */
|
||||
struct clk *pxlclk;
|
||||
/** @aclk: AXI clock */
|
||||
struct clk *aclk;
|
||||
/** @id: pipeline id */
|
||||
int id;
|
||||
/** @avail_comps: available components mask of pipeline */
|
||||
u32 avail_comps;
|
||||
int n_layers;
|
||||
struct komeda_layer *layers[KOMEDA_PIPELINE_MAX_LAYERS];
|
||||
int n_scalers;
|
||||
struct komeda_scaler *scalers[KOMEDA_PIPELINE_MAX_SCALERS];
|
||||
struct komeda_compiz *compiz;
|
||||
struct komeda_layer *wb_layer;
|
||||
struct komeda_improc *improc;
|
||||
struct komeda_timing_ctrlr *ctrlr;
|
||||
struct komeda_pipeline_funcs *funcs; /* private pipeline functions */
|
||||
|
||||
/** @of_node: pipeline dt node */
|
||||
struct device_node *of_node;
|
||||
/** @of_output_port: pipeline output port */
|
||||
struct device_node *of_output_port;
|
||||
/** @of_output_dev: output connector device node */
|
||||
struct device_node *of_output_dev;
|
||||
};
|
||||
|
||||
/**
|
||||
* struct komeda_pipeline_state
|
||||
*
|
||||
* NOTE:
|
||||
* Unlike the pipeline, pipeline_state doesn’t gather any component_state
|
||||
* into it. It because all component will be managed by drm_atomic_state.
|
||||
*/
|
||||
struct komeda_pipeline_state {
|
||||
/** @obj: tracking pipeline_state by drm_atomic_state */
|
||||
struct drm_private_state obj;
|
||||
struct komeda_pipeline *pipe;
|
||||
/** @crtc: currently bound crtc */
|
||||
struct drm_crtc *crtc;
|
||||
/**
|
||||
* @active_comps:
|
||||
*
|
||||
* bitmask - BIT(component->id) of active components
|
||||
*/
|
||||
u32 active_comps;
|
||||
};
|
||||
|
||||
#define to_layer(c) container_of(c, struct komeda_layer, base)
|
||||
#define to_compiz(c) container_of(c, struct komeda_compiz, base)
|
||||
#define to_scaler(c) container_of(c, struct komeda_scaler, base)
|
||||
#define to_improc(c) container_of(c, struct komeda_improc, base)
|
||||
#define to_ctrlr(c) container_of(c, struct komeda_timing_ctrlr, base)
|
||||
|
||||
#define to_layer_st(c) container_of(c, struct komeda_layer_state, base)
|
||||
#define to_compiz_st(c) container_of(c, struct komeda_compiz_state, base)
|
||||
#define to_scaler_st(c) container_of(c, struct komeda_scaler_state, base)
|
||||
#define to_improc_st(c) container_of(c, struct komeda_improc_state, base)
|
||||
#define to_ctrlr_st(c) container_of(c, struct komeda_timing_ctrlr_state, base)
|
||||
|
||||
#define priv_to_comp_st(o) container_of(o, struct komeda_component_state, obj)
|
||||
#define priv_to_pipe_st(o) container_of(o, struct komeda_pipeline_state, obj)
|
||||
|
||||
/* pipeline APIs */
|
||||
struct komeda_pipeline *
|
||||
komeda_pipeline_add(struct komeda_dev *mdev, size_t size,
|
||||
struct komeda_pipeline_funcs *funcs);
|
||||
void komeda_pipeline_destroy(struct komeda_dev *mdev,
|
||||
struct komeda_pipeline *pipe);
|
||||
|
||||
struct komeda_component *
|
||||
komeda_pipeline_get_component(struct komeda_pipeline *pipe, int id);
|
||||
|
||||
/* component APIs */
|
||||
struct komeda_component *
|
||||
komeda_component_add(struct komeda_pipeline *pipe,
|
||||
size_t comp_sz, u32 id, u32 hw_id,
|
||||
struct komeda_component_funcs *funcs,
|
||||
u8 max_active_inputs, u32 supported_inputs,
|
||||
u8 max_active_outputs, u32 __iomem *reg,
|
||||
const char *name_fmt, ...);
|
||||
|
||||
void komeda_component_destroy(struct komeda_dev *mdev,
|
||||
struct komeda_component *c);
|
||||
|
||||
#endif /* _KOMEDA_PIPELINE_H_*/
|
|
@ -0,0 +1,109 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#include <drm/drm_atomic.h>
|
||||
#include <drm/drm_atomic_helper.h>
|
||||
#include <drm/drm_plane_helper.h>
|
||||
#include "komeda_dev.h"
|
||||
#include "komeda_kms.h"
|
||||
|
||||
static const struct drm_plane_helper_funcs komeda_plane_helper_funcs = {
|
||||
};
|
||||
|
||||
static void komeda_plane_destroy(struct drm_plane *plane)
|
||||
{
|
||||
drm_plane_cleanup(plane);
|
||||
|
||||
kfree(to_kplane(plane));
|
||||
}
|
||||
|
||||
static const struct drm_plane_funcs komeda_plane_funcs = {
|
||||
};
|
||||
|
||||
/* for komeda, which is pipeline can be share between crtcs */
|
||||
static u32 get_possible_crtcs(struct komeda_kms_dev *kms,
|
||||
struct komeda_pipeline *pipe)
|
||||
{
|
||||
struct komeda_crtc *crtc;
|
||||
u32 possible_crtcs = 0;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < kms->n_crtcs; i++) {
|
||||
crtc = &kms->crtcs[i];
|
||||
|
||||
if ((pipe == crtc->master) || (pipe == crtc->slave))
|
||||
possible_crtcs |= BIT(i);
|
||||
}
|
||||
|
||||
return possible_crtcs;
|
||||
}
|
||||
|
||||
/* use Layer0 as primary */
|
||||
static u32 get_plane_type(struct komeda_kms_dev *kms,
|
||||
struct komeda_component *c)
|
||||
{
|
||||
bool is_primary = (c->id == KOMEDA_COMPONENT_LAYER0);
|
||||
|
||||
return is_primary ? DRM_PLANE_TYPE_PRIMARY : DRM_PLANE_TYPE_OVERLAY;
|
||||
}
|
||||
|
||||
static int komeda_plane_add(struct komeda_kms_dev *kms,
|
||||
struct komeda_layer *layer)
|
||||
{
|
||||
struct komeda_dev *mdev = kms->base.dev_private;
|
||||
struct komeda_component *c = &layer->base;
|
||||
struct komeda_plane *kplane;
|
||||
struct drm_plane *plane;
|
||||
u32 *formats, n_formats = 0;
|
||||
int err;
|
||||
|
||||
kplane = kzalloc(sizeof(*kplane), GFP_KERNEL);
|
||||
if (!kplane)
|
||||
return -ENOMEM;
|
||||
|
||||
plane = &kplane->base;
|
||||
kplane->layer = layer;
|
||||
|
||||
formats = komeda_get_layer_fourcc_list(&mdev->fmt_tbl,
|
||||
layer->layer_type, &n_formats);
|
||||
|
||||
err = drm_universal_plane_init(&kms->base, plane,
|
||||
get_possible_crtcs(kms, c->pipeline),
|
||||
&komeda_plane_funcs,
|
||||
formats, n_formats, NULL,
|
||||
get_plane_type(kms, c),
|
||||
"%s", c->name);
|
||||
|
||||
komeda_put_fourcc_list(formats);
|
||||
|
||||
if (err)
|
||||
goto cleanup;
|
||||
|
||||
drm_plane_helper_add(plane, &komeda_plane_helper_funcs);
|
||||
|
||||
return 0;
|
||||
cleanup:
|
||||
komeda_plane_destroy(plane);
|
||||
return err;
|
||||
}
|
||||
|
||||
int komeda_kms_add_planes(struct komeda_kms_dev *kms, struct komeda_dev *mdev)
|
||||
{
|
||||
struct komeda_pipeline *pipe;
|
||||
int i, j, err;
|
||||
|
||||
for (i = 0; i < mdev->n_pipelines; i++) {
|
||||
pipe = mdev->pipelines[i];
|
||||
|
||||
for (j = 0; j < pipe->n_layers; j++) {
|
||||
err = komeda_plane_add(kms, pipe->layers[j]);
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) COPYRIGHT 2018 ARM Limited. All rights reserved.
|
||||
* Author: James.Qian.Wang <james.qian.wang@arm.com>
|
||||
*
|
||||
*/
|
||||
#include "komeda_dev.h"
|
||||
#include "komeda_kms.h"
|
||||
|
||||
static struct drm_private_state *
|
||||
komeda_pipeline_atomic_duplicate_state(struct drm_private_obj *obj)
|
||||
{
|
||||
struct komeda_pipeline_state *st;
|
||||
|
||||
st = kmemdup(obj->state, sizeof(*st), GFP_KERNEL);
|
||||
if (!st)
|
||||
return NULL;
|
||||
|
||||
st->active_comps = 0;
|
||||
|
||||
__drm_atomic_helper_private_obj_duplicate_state(obj, &st->obj);
|
||||
|
||||
return &st->obj;
|
||||
}
|
||||
|
||||
static void
|
||||
komeda_pipeline_atomic_destroy_state(struct drm_private_obj *obj,
|
||||
struct drm_private_state *state)
|
||||
{
|
||||
kfree(priv_to_pipe_st(state));
|
||||
}
|
||||
|
||||
static const struct drm_private_state_funcs komeda_pipeline_obj_funcs = {
|
||||
.atomic_duplicate_state = komeda_pipeline_atomic_duplicate_state,
|
||||
.atomic_destroy_state = komeda_pipeline_atomic_destroy_state,
|
||||
};
|
||||
|
||||
static int komeda_pipeline_obj_add(struct komeda_kms_dev *kms,
|
||||
struct komeda_pipeline *pipe)
|
||||
{
|
||||
struct komeda_pipeline_state *st;
|
||||
|
||||
st = kzalloc(sizeof(*st), GFP_KERNEL);
|
||||
if (!st)
|
||||
return -ENOMEM;
|
||||
|
||||
st->pipe = pipe;
|
||||
drm_atomic_private_obj_init(&kms->base, &pipe->obj, &st->obj,
|
||||
&komeda_pipeline_obj_funcs);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int komeda_kms_add_private_objs(struct komeda_kms_dev *kms,
|
||||
struct komeda_dev *mdev)
|
||||
{
|
||||
struct komeda_pipeline *pipe;
|
||||
int i, err;
|
||||
|
||||
for (i = 0; i < mdev->n_pipelines; i++) {
|
||||
pipe = mdev->pipelines[i];
|
||||
|
||||
err = komeda_pipeline_obj_add(kms, pipe);
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/* Add component */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void komeda_kms_cleanup_private_objs(struct komeda_dev *mdev)
|
||||
{
|
||||
struct komeda_pipeline *pipe;
|
||||
struct komeda_component *c;
|
||||
int i, id;
|
||||
|
||||
for (i = 0; i < mdev->n_pipelines; i++) {
|
||||
pipe = mdev->pipelines[i];
|
||||
dp_for_each_set_bit(id, pipe->avail_comps) {
|
||||
c = komeda_pipeline_get_component(pipe, id);
|
||||
|
||||
drm_atomic_private_obj_fini(&c->obj);
|
||||
}
|
||||
drm_atomic_private_obj_fini(&pipe->obj);
|
||||
}
|
||||
}
|
|
@ -574,6 +574,9 @@ extern "C" {
|
|||
* AFBC has several features which may be supported and/or used, which are
|
||||
* represented using bits in the modifier. Not all combinations are valid,
|
||||
* and different devices or use-cases may support different combinations.
|
||||
*
|
||||
* Further information on the use of AFBC modifiers can be found in
|
||||
* Documentation/gpu/afbc.rst
|
||||
*/
|
||||
#define DRM_FORMAT_MOD_ARM_AFBC(__afbc_mode) fourcc_mod_code(ARM, __afbc_mode)
|
||||
|
||||
|
|
Loading…
Reference in New Issue