| .. 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 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 independently, 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 programming 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: attaches 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. |