R-Car Display Unit changes:

- Color Management Module support
 - LVDS encoder dual-link support enhancements
 - R8A77980 support
 -----BEGIN PGP SIGNATURE-----
 
 iQJWBAABCgBAFiEEvZRkio5H7O2/GZsYYiVdKZ4oCyQFAl35dcIiHGxhdXJlbnQu
 cGluY2hhcnRAaWRlYXNvbmJvYXJkLmNvbQAKCRBiJV0pnigLJP65D/9NOZrf69n0
 ziqv0Te4CJb1S/uZYm+wdPw/J982idKDcq38eANgfo9EVZVYQr5svyG4RycGyDj6
 77tmbBvc7tZCydEDY4D2Ojk1sYVMZwq0MpB4BQWKwnmgfUgQBynmUD0zKvRNtBEd
 tW9kpqTLLzs2zyrM7yGtzJWBoMQj+Dy8u17SzlPsCcJI7rBFCzxmg5ipvAjBWNAL
 Oetq8wkUOLKnO7brybjKhHLXSinbPdHoGR3YY9EHm0U1Rr8kUovQJP7dO57Gy7dl
 y3+/AAqP00W7zobzEqkx5bY4l0TwmmSdAGAR0WVaGeVEJRstWbmX4LcWYovgf8fR
 YgIXK6L0abHDtVP/09kLUT+PlcSR3cHBzkM1XNNbJWroEpytqTFvY0e66Zvtt/nm
 RJKGQcwe++AMeiZYcqQRstuax3rZleZE4QDbwQkZcGyEwRBF/ZWEX/ZIivpSThcx
 //kk47ujuTjWrYtSSOEKK8KHgHCLwzd+a7Vl/KXyKyPgKb7S8LlA8mk/Vo3NKv2O
 oCx+VTqKBuwga16JPWcyNDcG2rC109z7KNp0YJuk7sC9S1t5cSXxazBUAKg1epFG
 85Pvf9ZHR3zvZ1srBv5Hcd43AqYdOypaHN56fArH1YdtCLd08huRemtgan59mjTX
 1rV5rVNhLb34Y0iCxQ9F5Gt/CA4LhSEcnw==
 =XAKY
 -----END PGP SIGNATURE-----

Merge tag 'du-next-20191218' of git://linuxtv.org/pinchartl/media into drm-next

R-Car Display Unit changes:

- Color Management Module support
- LVDS encoder dual-link support enhancements
- R8A77980 support

Signed-off-by: Daniel Vetter <daniel.vetter@ffwll.ch>
From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20191218151710.GA13830@pendragon.ideasonboard.com
This commit is contained in:
Daniel Vetter 2019-12-18 16:19:26 +01:00
commit 66af4a9dda
17 changed files with 856 additions and 155 deletions

View File

@ -0,0 +1,67 @@
# SPDX-License-Identifier: GPL-2.0-only
%YAML 1.2
---
$id: http://devicetree.org/schemas/display/renesas,cmm.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Renesas R-Car Color Management Module (CMM)
maintainers:
- Laurent Pinchart <laurent.pinchart@ideasonboard.com>
- Kieran Bingham <kieran.bingham+renesas@ideasonboard.com>
- Jacopo Mondi <jacopo+renesas@jmondi.org>
description: |+
Renesas R-Car color management module connected to R-Car DU video channels.
It provides image enhancement functions such as 1-D look-up tables (LUT),
3-D look-up tables (CLU), 1D-histogram generation (HGO), and color
space conversion (CSC).
properties:
compatible:
oneOf:
- items:
- enum:
- renesas,r8a7795-cmm
- renesas,r8a7796-cmm
- renesas,r8a77965-cmm
- renesas,r8a77990-cmm
- renesas,r8a77995-cmm
- const: renesas,rcar-gen3-cmm
- items:
- const: renesas,rcar-gen2-cmm
reg:
maxItems: 1
clocks:
maxItems: 1
resets:
maxItems: 1
power-domains:
maxItems: 1
required:
- compatible
- reg
- clocks
- resets
- power-domains
additionalProperties: false
examples:
- |
#include <dt-bindings/clock/r8a7796-cpg-mssr.h>
#include <dt-bindings/power/r8a7796-sysc.h>
cmm0: cmm@fea40000 {
compatible = "renesas,r8a7796-cmm",
"renesas,rcar-gen3-cmm";
reg = <0 0xfea40000 0 0x1000>;
power-domains = <&sysc R8A7796_PD_ALWAYS_ON>;
clocks = <&cpg CPG_MOD 711>;
resets = <&cpg 711>;
};

View File

@ -41,10 +41,14 @@ Required Properties:
supplied they must be named "dclkin.x" with "x" being the input clock
numerical index.
- vsps: A list of phandle and channel index tuples to the VSPs that handle
the memory interfaces for the DU channels. The phandle identifies the VSP
instance that serves the DU channel, and the channel index identifies the
LIF instance in that VSP.
- renesas,cmms: A list of phandles to the CMM instances present in the SoC,
one for each available DU channel. The property shall not be specified for
SoCs that do not provide any CMM (such as V3M and V3H).
- renesas,vsps: A list of phandle and channel index tuples to the VSPs that
handle the memory interfaces for the DU channels. The phandle identifies the
VSP instance that serves the DU channel, and the channel index identifies
the LIF instance in that VSP.
Required nodes:
@ -92,7 +96,8 @@ Example: R8A7795 (R-Car H3) ES2.0 DU
<&cpg CPG_MOD 722>,
<&cpg CPG_MOD 721>;
clock-names = "du.0", "du.1", "du.2", "du.3";
vsps = <&vspd0 0>, <&vspd1 0>, <&vspd2 0>, <&vspd0 1>;
renesas,cmms = <&cmm0>, <&cmm1>, <&cmm2>, <&cmm3>;
renesas,vsps = <&vspd0 0>, <&vspd1 0>, <&vspd2 0>, <&vspd0 1>;
ports {
#address-cells = <1>;

View File

@ -274,3 +274,119 @@ int drm_of_find_panel_or_bridge(const struct device_node *np,
return ret;
}
EXPORT_SYMBOL_GPL(drm_of_find_panel_or_bridge);
enum drm_of_lvds_pixels {
DRM_OF_LVDS_EVEN = BIT(0),
DRM_OF_LVDS_ODD = BIT(1),
};
static int drm_of_lvds_get_port_pixels_type(struct device_node *port_node)
{
bool even_pixels =
of_property_read_bool(port_node, "dual-lvds-even-pixels");
bool odd_pixels =
of_property_read_bool(port_node, "dual-lvds-odd-pixels");
return (even_pixels ? DRM_OF_LVDS_EVEN : 0) |
(odd_pixels ? DRM_OF_LVDS_ODD : 0);
}
static int drm_of_lvds_get_remote_pixels_type(
const struct device_node *port_node)
{
struct device_node *endpoint = NULL;
int pixels_type = -EPIPE;
for_each_child_of_node(port_node, endpoint) {
struct device_node *remote_port;
int current_pt;
if (!of_node_name_eq(endpoint, "endpoint"))
continue;
remote_port = of_graph_get_remote_port(endpoint);
if (!remote_port) {
of_node_put(remote_port);
return -EPIPE;
}
current_pt = drm_of_lvds_get_port_pixels_type(remote_port);
of_node_put(remote_port);
if (pixels_type < 0)
pixels_type = current_pt;
/*
* Sanity check, ensure that all remote endpoints have the same
* pixel type. We may lift this restriction later if we need to
* support multiple sinks with different dual-link
* configurations by passing the endpoints explicitly to
* drm_of_lvds_get_dual_link_pixel_order().
*/
if (!current_pt || pixels_type != current_pt) {
of_node_put(remote_port);
return -EINVAL;
}
}
return pixels_type;
}
/**
* drm_of_lvds_get_dual_link_pixel_order - Get LVDS dual-link pixel order
* @port1: First DT port node of the Dual-link LVDS source
* @port2: Second DT port node of the Dual-link LVDS source
*
* An LVDS dual-link connection is made of two links, with even pixels
* transitting on one link, and odd pixels on the other link. This function
* returns, for two ports of an LVDS dual-link source, which port shall transmit
* the even and odd pixels, based on the requirements of the connected sink.
*
* The pixel order is determined from the dual-lvds-even-pixels and
* dual-lvds-odd-pixels properties in the sink's DT port nodes. If those
* properties are not present, or if their usage is not valid, this function
* returns -EINVAL.
*
* If either port is not connected, this function returns -EPIPE.
*
* @port1 and @port2 are typically DT sibling nodes, but may have different
* parents when, for instance, two separate LVDS encoders carry the even and odd
* pixels.
*
* Return:
* * DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS - @port1 carries even pixels and @port2
* carries odd pixels
* * DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS - @port1 carries odd pixels and @port2
* carries even pixels
* * -EINVAL - @port1 and @port2 are not connected to a dual-link LVDS sink, or
* the sink configuration is invalid
* * -EPIPE - when @port1 or @port2 are not connected
*/
int drm_of_lvds_get_dual_link_pixel_order(const struct device_node *port1,
const struct device_node *port2)
{
int remote_p1_pt, remote_p2_pt;
if (!port1 || !port2)
return -EINVAL;
remote_p1_pt = drm_of_lvds_get_remote_pixels_type(port1);
if (remote_p1_pt < 0)
return remote_p1_pt;
remote_p2_pt = drm_of_lvds_get_remote_pixels_type(port2);
if (remote_p2_pt < 0)
return remote_p2_pt;
/*
* A valid dual-lVDS bus is found when one remote port is marked with
* "dual-lvds-even-pixels", and the other remote port is marked with
* "dual-lvds-odd-pixels", bail out if the markers are not right.
*/
if (remote_p1_pt + remote_p2_pt != DRM_OF_LVDS_EVEN + DRM_OF_LVDS_ODD)
return -EINVAL;
return remote_p1_pt == DRM_OF_LVDS_EVEN ?
DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS :
DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS;
}
EXPORT_SYMBOL_GPL(drm_of_lvds_get_dual_link_pixel_order);

View File

@ -4,6 +4,7 @@ config DRM_RCAR_DU
depends on DRM && OF
depends on ARM || ARM64
depends on ARCH_RENESAS || COMPILE_TEST
imply DRM_RCAR_CMM
imply DRM_RCAR_LVDS
select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER
@ -13,6 +14,13 @@ config DRM_RCAR_DU
Choose this option if you have an R-Car chipset.
If M is selected the module will be called rcar-du-drm.
config DRM_RCAR_CMM
tristate "R-Car DU Color Management Module (CMM) Support"
depends on DRM && OF
depends on DRM_RCAR_DU
help
Enable support for R-Car Color Management Module (CMM).
config DRM_RCAR_DW_HDMI
tristate "R-Car DU Gen3 HDMI Encoder Support"
depends on DRM && OF

View File

@ -15,6 +15,7 @@ rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_of.o \
rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o
rcar-du-drm-$(CONFIG_DRM_RCAR_WRITEBACK) += rcar_du_writeback.o
obj-$(CONFIG_DRM_RCAR_CMM) += rcar_cmm.o
obj-$(CONFIG_DRM_RCAR_DU) += rcar-du-drm.o
obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o
obj-$(CONFIG_DRM_RCAR_LVDS) += rcar_lvds.o

View File

@ -0,0 +1,217 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* rcar_cmm.c -- R-Car Display Unit Color Management Module
*
* Copyright (C) 2019 Jacopo Mondi <jacopo+renesas@jmondi.org>
*/
#include <linux/io.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/pm_runtime.h>
#include <drm/drm_color_mgmt.h>
#include "rcar_cmm.h"
#define CM2_LUT_CTRL 0x0000
#define CM2_LUT_CTRL_LUT_EN BIT(0)
#define CM2_LUT_TBL_BASE 0x0600
#define CM2_LUT_TBL(__i) (CM2_LUT_TBL_BASE + (__i) * 4)
struct rcar_cmm {
void __iomem *base;
/*
* @lut: 1D-LUT state
* @lut.enabled: 1D-LUT enabled flag
*/
struct {
bool enabled;
} lut;
};
static inline int rcar_cmm_read(struct rcar_cmm *rcmm, u32 reg)
{
return ioread32(rcmm->base + reg);
}
static inline void rcar_cmm_write(struct rcar_cmm *rcmm, u32 reg, u32 data)
{
iowrite32(data, rcmm->base + reg);
}
/*
* rcar_cmm_lut_write() - Scale the DRM LUT table entries to hardware precision
* and write to the CMM registers
* @rcmm: Pointer to the CMM device
* @drm_lut: Pointer to the DRM LUT table
*/
static void rcar_cmm_lut_write(struct rcar_cmm *rcmm,
const struct drm_color_lut *drm_lut)
{
unsigned int i;
for (i = 0; i < CM2_LUT_SIZE; ++i) {
u32 entry = drm_color_lut_extract(drm_lut[i].red, 8) << 16
| drm_color_lut_extract(drm_lut[i].green, 8) << 8
| drm_color_lut_extract(drm_lut[i].blue, 8);
rcar_cmm_write(rcmm, CM2_LUT_TBL(i), entry);
}
}
/*
* rcar_cmm_setup() - Configure the CMM unit
* @pdev: The platform device associated with the CMM instance
* @config: The CMM unit configuration
*
* Configure the CMM unit with the given configuration. Currently enabling,
* disabling and programming of the 1-D LUT unit is supported.
*
* As rcar_cmm_setup() accesses the CMM registers the unit should be powered
* and its functional clock enabled. To guarantee this, before any call to
* this function is made, the CMM unit has to be enabled by calling
* rcar_cmm_enable() first.
*
* TODO: Add support for LUT double buffer operations to avoid updating the
* LUT table entries while a frame is being displayed.
*/
int rcar_cmm_setup(struct platform_device *pdev,
const struct rcar_cmm_config *config)
{
struct rcar_cmm *rcmm = platform_get_drvdata(pdev);
/* Disable LUT if no table is provided. */
if (!config->lut.table) {
if (rcmm->lut.enabled) {
rcar_cmm_write(rcmm, CM2_LUT_CTRL, 0);
rcmm->lut.enabled = false;
}
return 0;
}
/* Enable LUT and program the new gamma table values. */
if (!rcmm->lut.enabled) {
rcar_cmm_write(rcmm, CM2_LUT_CTRL, CM2_LUT_CTRL_LUT_EN);
rcmm->lut.enabled = true;
}
rcar_cmm_lut_write(rcmm, config->lut.table);
return 0;
}
EXPORT_SYMBOL_GPL(rcar_cmm_setup);
/*
* rcar_cmm_enable() - Enable the CMM unit
* @pdev: The platform device associated with the CMM instance
*
* When the output of the corresponding DU channel is routed to the CMM unit,
* the unit shall be enabled before the DU channel is started, and remain
* enabled until the channel is stopped. The CMM unit shall be disabled with
* rcar_cmm_disable().
*
* Calls to rcar_cmm_enable() and rcar_cmm_disable() are not reference-counted.
* It is an error to attempt to enable an already enabled CMM unit, or to
* attempt to disable a disabled unit.
*/
int rcar_cmm_enable(struct platform_device *pdev)
{
int ret;
ret = pm_runtime_get_sync(&pdev->dev);
if (ret < 0)
return ret;
return 0;
}
EXPORT_SYMBOL_GPL(rcar_cmm_enable);
/*
* rcar_cmm_disable() - Disable the CMM unit
* @pdev: The platform device associated with the CMM instance
*
* See rcar_cmm_enable() for usage information.
*
* Disabling the CMM unit disable all the internal processing blocks. The CMM
* state shall thus be restored with rcar_cmm_setup() when re-enabling the CMM
* unit after the next rcar_cmm_enable() call.
*/
void rcar_cmm_disable(struct platform_device *pdev)
{
struct rcar_cmm *rcmm = platform_get_drvdata(pdev);
rcar_cmm_write(rcmm, CM2_LUT_CTRL, 0);
rcmm->lut.enabled = false;
pm_runtime_put(&pdev->dev);
}
EXPORT_SYMBOL_GPL(rcar_cmm_disable);
/*
* rcar_cmm_init() - Initialize the CMM unit
* @pdev: The platform device associated with the CMM instance
*
* Return: 0 on success, -EPROBE_DEFER if the CMM is not available yet,
* -ENODEV if the DRM_RCAR_CMM config option is disabled
*/
int rcar_cmm_init(struct platform_device *pdev)
{
struct rcar_cmm *rcmm = platform_get_drvdata(pdev);
if (!rcmm)
return -EPROBE_DEFER;
return 0;
}
EXPORT_SYMBOL_GPL(rcar_cmm_init);
static int rcar_cmm_probe(struct platform_device *pdev)
{
struct rcar_cmm *rcmm;
rcmm = devm_kzalloc(&pdev->dev, sizeof(*rcmm), GFP_KERNEL);
if (!rcmm)
return -ENOMEM;
platform_set_drvdata(pdev, rcmm);
rcmm->base = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(rcmm->base))
return PTR_ERR(rcmm->base);
pm_runtime_enable(&pdev->dev);
return 0;
}
static int rcar_cmm_remove(struct platform_device *pdev)
{
pm_runtime_disable(&pdev->dev);
return 0;
}
static const struct of_device_id rcar_cmm_of_table[] = {
{ .compatible = "renesas,rcar-gen3-cmm", },
{ .compatible = "renesas,rcar-gen2-cmm", },
{ },
};
MODULE_DEVICE_TABLE(of, rcar_cmm_of_table);
static struct platform_driver rcar_cmm_platform_driver = {
.probe = rcar_cmm_probe,
.remove = rcar_cmm_remove,
.driver = {
.name = "rcar-cmm",
.of_match_table = rcar_cmm_of_table,
},
};
module_platform_driver(rcar_cmm_platform_driver);
MODULE_AUTHOR("Jacopo Mondi <jacopo+renesas@jmondi.org>");
MODULE_DESCRIPTION("Renesas R-Car CMM Driver");
MODULE_LICENSE("GPL v2");

View File

@ -0,0 +1,58 @@
/* SPDX-License-Identifier: GPL-2.0+ */
/*
* rcar_cmm.h -- R-Car Display Unit Color Management Module
*
* Copyright (C) 2019 Jacopo Mondi <jacopo+renesas@jmondi.org>
*/
#ifndef __RCAR_CMM_H__
#define __RCAR_CMM_H__
#define CM2_LUT_SIZE 256
struct drm_color_lut;
struct platform_device;
/**
* struct rcar_cmm_config - CMM configuration
*
* @lut: 1D-LUT configuration
* @lut.table: 1D-LUT table entries. Disable LUT operations when NULL
*/
struct rcar_cmm_config {
struct {
struct drm_color_lut *table;
} lut;
};
#if IS_ENABLED(CONFIG_DRM_RCAR_CMM)
int rcar_cmm_init(struct platform_device *pdev);
int rcar_cmm_enable(struct platform_device *pdev);
void rcar_cmm_disable(struct platform_device *pdev);
int rcar_cmm_setup(struct platform_device *pdev,
const struct rcar_cmm_config *config);
#else
static inline int rcar_cmm_init(struct platform_device *pdev)
{
return -ENODEV;
}
static inline int rcar_cmm_enable(struct platform_device *pdev)
{
return 0;
}
static inline void rcar_cmm_disable(struct platform_device *pdev)
{
}
static inline int rcar_cmm_setup(struct platform_device *pdev,
const struct rcar_cmm_config *config)
{
return 0;
}
#endif /* IS_ENABLED(CONFIG_DRM_RCAR_CMM) */
#endif /* __RCAR_CMM_H__ */

View File

@ -22,6 +22,7 @@
#include <drm/drm_plane_helper.h>
#include <drm/drm_vblank.h>
#include "rcar_cmm.h"
#include "rcar_du_crtc.h"
#include "rcar_du_drv.h"
#include "rcar_du_encoder.h"
@ -475,6 +476,45 @@ static void rcar_du_crtc_wait_page_flip(struct rcar_du_crtc *rcrtc)
rcar_du_crtc_finish_page_flip(rcrtc);
}
/* -----------------------------------------------------------------------------
* Color Management Module (CMM)
*/
static int rcar_du_cmm_check(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct drm_property_blob *drm_lut = state->gamma_lut;
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
struct device *dev = rcrtc->dev->dev;
if (!drm_lut)
return 0;
/* We only accept fully populated LUT tables. */
if (drm_color_lut_size(drm_lut) != CM2_LUT_SIZE) {
dev_err(dev, "invalid gamma lut size: %zu bytes\n",
drm_lut->length);
return -EINVAL;
}
return 0;
}
static void rcar_du_cmm_setup(struct drm_crtc *crtc)
{
struct drm_property_blob *drm_lut = crtc->state->gamma_lut;
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
struct rcar_cmm_config cmm_config = {};
if (!rcrtc->cmm)
return;
if (drm_lut)
cmm_config.lut.table = (struct drm_color_lut *)drm_lut->data;
rcar_cmm_setup(rcrtc->cmm, &cmm_config);
}
/* -----------------------------------------------------------------------------
* Start/Stop and Suspend/Resume
*/
@ -620,6 +660,9 @@ static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc)
if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
rcar_du_vsp_disable(rcrtc);
if (rcrtc->cmm)
rcar_cmm_disable(rcrtc->cmm);
/*
* Select switch sync mode. This stops display operation and configures
* the HSYNC and VSYNC signals as inputs.
@ -643,6 +686,11 @@ static int rcar_du_crtc_atomic_check(struct drm_crtc *crtc,
{
struct rcar_du_crtc_state *rstate = to_rcar_crtc_state(state);
struct drm_encoder *encoder;
int ret;
ret = rcar_du_cmm_check(crtc, state);
if (ret)
return ret;
/* Store the routes from the CRTC output to the DU outputs. */
rstate->outputs = 0;
@ -668,6 +716,8 @@ static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc,
struct rcar_du_crtc_state *rstate = to_rcar_crtc_state(crtc->state);
struct rcar_du_device *rcdu = rcrtc->dev;
if (rcrtc->cmm)
rcar_cmm_enable(rcrtc->cmm);
rcar_du_crtc_get(rcrtc);
/*
@ -688,6 +738,13 @@ static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc,
}
rcar_du_crtc_start(rcrtc);
/*
* TODO: The chip manual indicates that CMM tables should be written
* after the DU channel has been activated. Investigate the impact
* of this restriction on the first displayed frame.
*/
rcar_du_cmm_setup(crtc);
}
static void rcar_du_crtc_atomic_disable(struct drm_crtc *crtc,
@ -743,6 +800,10 @@ static void rcar_du_crtc_atomic_begin(struct drm_crtc *crtc,
*/
rcar_du_crtc_get(rcrtc);
/* If the active state changed, we let .atomic_enable handle CMM. */
if (crtc->state->color_mgmt_changed && !crtc->state->active_changed)
rcar_du_cmm_setup(crtc);
if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
rcar_du_vsp_atomic_begin(rcrtc);
}
@ -1079,6 +1140,7 @@ static const struct drm_crtc_funcs crtc_funcs_gen3 = {
.set_crc_source = rcar_du_crtc_set_crc_source,
.verify_crc_source = rcar_du_crtc_verify_crc_source,
.get_crc_sources = rcar_du_crtc_get_crc_sources,
.gamma_set = drm_atomic_helper_legacy_gamma_set,
};
/* -----------------------------------------------------------------------------
@ -1198,6 +1260,15 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex,
if (ret < 0)
return ret;
/* CMM might be disabled for this CRTC. */
if (rcdu->cmms[swindex]) {
rcrtc->cmm = rcdu->cmms[swindex];
rgrp->cmms_mask |= BIT(hwindex % 2);
drm_mode_crtc_set_gamma_size(crtc, CM2_LUT_SIZE);
drm_crtc_enable_color_mgmt(crtc, 0, false, CM2_LUT_SIZE);
}
drm_crtc_helper_add(crtc, &crtc_helper_funcs);
/* Start with vertical blanking interrupt reporting disabled. */

View File

@ -39,6 +39,7 @@ struct rcar_du_vsp;
* @vblank_wait: wait queue used to signal vertical blanking
* @vblank_count: number of vertical blanking interrupts to wait for
* @group: CRTC group this CRTC belongs to
* @cmm: CMM associated with this CRTC
* @vsp: VSP feeding video to this CRTC
* @vsp_pipe: index of the VSP pipeline feeding video to this CRTC
* @writeback: the writeback connector
@ -64,6 +65,7 @@ struct rcar_du_crtc {
unsigned int vblank_count;
struct rcar_du_group *group;
struct platform_device *cmm;
struct rcar_du_vsp *vsp;
unsigned int vsp_pipe;

View File

@ -399,7 +399,10 @@ static const struct rcar_du_device_info rcar_du_r8a77970_info = {
| RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(0),
.routes = {
/* R8A77970 has one RGB output and one LVDS output. */
/*
* R8A77970 and R8A77980 have one RGB output and one LVDS
* output.
*/
[RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0),
.port = 0,
@ -457,6 +460,7 @@ static const struct of_device_id rcar_du_of_table[] = {
{ .compatible = "renesas,du-r8a7796", .data = &rcar_du_r8a7796_info },
{ .compatible = "renesas,du-r8a77965", .data = &rcar_du_r8a77965_info },
{ .compatible = "renesas,du-r8a77970", .data = &rcar_du_r8a77970_info },
{ .compatible = "renesas,du-r8a77980", .data = &rcar_du_r8a77970_info },
{ .compatible = "renesas,du-r8a77990", .data = &rcar_du_r8a7799x_info },
{ .compatible = "renesas,du-r8a77995", .data = &rcar_du_r8a7799x_info },
{ }

View File

@ -13,6 +13,7 @@
#include <linux/kernel.h>
#include <linux/wait.h>
#include "rcar_cmm.h"
#include "rcar_du_crtc.h"
#include "rcar_du_group.h"
#include "rcar_du_vsp.h"
@ -85,6 +86,7 @@ struct rcar_du_device {
struct rcar_du_encoder *encoders[RCAR_DU_OUTPUT_MAX];
struct rcar_du_group groups[RCAR_DU_MAX_GROUPS];
struct platform_device *cmms[RCAR_DU_MAX_CRTCS];
struct rcar_du_vsp vsps[RCAR_DU_MAX_VSPS];
struct {

View File

@ -135,6 +135,7 @@ static void rcar_du_group_setup_didsr(struct rcar_du_group *rgrp)
static void rcar_du_group_setup(struct rcar_du_group *rgrp)
{
struct rcar_du_device *rcdu = rgrp->dev;
u32 defr7 = DEFR7_CODE;
/* Enable extended features */
rcar_du_group_write(rgrp, DEFR, DEFR_CODE | DEFR_DEFE);
@ -147,6 +148,15 @@ static void rcar_du_group_setup(struct rcar_du_group *rgrp)
rcar_du_group_setup_pins(rgrp);
/*
* TODO: Handle routing of the DU output to CMM dynamically, as we
* should bypass CMM completely when no color management feature is
* used.
*/
defr7 |= (rgrp->cmms_mask & BIT(1) ? DEFR7_CMME1 : 0) |
(rgrp->cmms_mask & BIT(0) ? DEFR7_CMME0 : 0);
rcar_du_group_write(rgrp, DEFR7, defr7);
if (rcdu->info->gen >= 2) {
rcar_du_group_setup_defr8(rgrp);
rcar_du_group_setup_didsr(rgrp);

View File

@ -22,6 +22,7 @@ struct rcar_du_device;
* @mmio_offset: registers offset in the device memory map
* @index: group index
* @channels_mask: bitmask of populated DU channels in this group
* @cmms_mask: bitmask of available CMMs in this group
* @num_crtcs: number of CRTCs in this group (1 or 2)
* @use_count: number of users of the group (rcar_du_group_(get|put))
* @used_crtcs: number of CRTCs currently in use
@ -37,6 +38,7 @@ struct rcar_du_group {
unsigned int index;
unsigned int channels_mask;
unsigned int cmms_mask;
unsigned int num_crtcs;
unsigned int use_count;
unsigned int used_crtcs;

View File

@ -17,7 +17,9 @@
#include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h>
#include <linux/device.h>
#include <linux/of_graph.h>
#include <linux/of_platform.h>
#include <linux/wait.h>
#include "rcar_du_crtc.h"
@ -542,6 +544,7 @@ static int rcar_du_properties_init(struct rcar_du_device *rcdu)
static int rcar_du_vsps_init(struct rcar_du_device *rcdu)
{
const struct device_node *np = rcdu->dev->of_node;
const char *vsps_prop_name = "renesas,vsps";
struct of_phandle_args args;
struct {
struct device_node *np;
@ -557,15 +560,21 @@ static int rcar_du_vsps_init(struct rcar_du_device *rcdu)
* entry contains a pointer to the VSP DT node and a bitmask of the
* connected DU CRTCs.
*/
cells = of_property_count_u32_elems(np, "vsps") / rcdu->num_crtcs - 1;
ret = of_property_count_u32_elems(np, vsps_prop_name);
if (ret < 0) {
/* Backward compatibility with old DTBs. */
vsps_prop_name = "vsps";
ret = of_property_count_u32_elems(np, vsps_prop_name);
}
cells = ret / rcdu->num_crtcs - 1;
if (cells > 1)
return -EINVAL;
for (i = 0; i < rcdu->num_crtcs; ++i) {
unsigned int j;
ret = of_parse_phandle_with_fixed_args(np, "vsps", cells, i,
&args);
ret = of_parse_phandle_with_fixed_args(np, vsps_prop_name,
cells, i, &args);
if (ret < 0)
goto error;
@ -587,8 +596,8 @@ static int rcar_du_vsps_init(struct rcar_du_device *rcdu)
/*
* Store the VSP pointer and pipe index in the CRTC. If the
* second cell of the 'vsps' specifier isn't present, default
* to 0 to remain compatible with older DT bindings.
* second cell of the 'renesas,vsps' specifier isn't present,
* default to 0 to remain compatible with older DT bindings.
*/
rcdu->crtcs[i].vsp = &rcdu->vsps[j];
rcdu->crtcs[i].vsp_pipe = cells >= 1 ? args.args[0] : 0;
@ -618,6 +627,75 @@ error:
return ret;
}
static int rcar_du_cmm_init(struct rcar_du_device *rcdu)
{
const struct device_node *np = rcdu->dev->of_node;
unsigned int i;
int cells;
cells = of_property_count_u32_elems(np, "renesas,cmms");
if (cells == -EINVAL)
return 0;
if (cells > rcdu->num_crtcs) {
dev_err(rcdu->dev,
"Invalid number of entries in 'renesas,cmms'\n");
return -EINVAL;
}
for (i = 0; i < cells; ++i) {
struct platform_device *pdev;
struct device_link *link;
struct device_node *cmm;
int ret;
cmm = of_parse_phandle(np, "renesas,cmms", i);
if (IS_ERR(cmm)) {
dev_err(rcdu->dev,
"Failed to parse 'renesas,cmms' property\n");
return PTR_ERR(cmm);
}
if (!of_device_is_available(cmm)) {
/* It's fine to have a phandle to a non-enabled CMM. */
of_node_put(cmm);
continue;
}
pdev = of_find_device_by_node(cmm);
if (IS_ERR(pdev)) {
dev_err(rcdu->dev, "No device found for CMM%u\n", i);
of_node_put(cmm);
return PTR_ERR(pdev);
}
of_node_put(cmm);
/*
* -ENODEV is used to report that the CMM config option is
* disabled: return 0 and let the DU continue probing.
*/
ret = rcar_cmm_init(pdev);
if (ret)
return ret == -ENODEV ? 0 : ret;
/*
* Enforce suspend/resume ordering by making the CMM a provider
* of the DU: CMM is suspended after and resumed before the DU.
*/
link = device_link_add(rcdu->dev, &pdev->dev, DL_FLAG_STATELESS);
if (!link) {
dev_err(rcdu->dev,
"Failed to create device link to CMM%u\n", i);
return -EINVAL;
}
rcdu->cmms[i] = pdev;
}
return 0;
}
int rcar_du_modeset_init(struct rcar_du_device *rcdu)
{
static const unsigned int mmio_offsets[] = {
@ -708,6 +786,11 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
return ret;
}
/* Initialize the Color Management Modules. */
ret = rcar_du_cmm_init(rcdu);
if (ret)
return ret;
/* Create the CRTCs. */
for (swindex = 0, hwindex = 0; swindex < rcdu->num_crtcs; ++hwindex) {
struct rcar_du_group *rgrp;

View File

@ -197,6 +197,11 @@
#define DEFR6_MLOS1 (1 << 2)
#define DEFR6_DEFAULT (DEFR6_CODE | DEFR6_TCNE1)
#define DEFR7 0x000ec
#define DEFR7_CODE (0x7779 << 16)
#define DEFR7_CMME1 BIT(6)
#define DEFR7_CMME0 BIT(4)
/* -----------------------------------------------------------------------------
* R8A7790-only Control Registers
*/

View File

@ -21,6 +21,7 @@
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_bridge.h>
#include <drm/drm_of.h>
#include <drm/drm_panel.h>
#include <drm/drm_probe_helper.h>
@ -36,6 +37,12 @@ enum rcar_lvds_mode {
RCAR_LVDS_MODE_VESA = 4,
};
enum rcar_lvds_link_type {
RCAR_LVDS_SINGLE_LINK = 0,
RCAR_LVDS_DUAL_LINK_EVEN_ODD_PIXELS = 1,
RCAR_LVDS_DUAL_LINK_ODD_EVEN_PIXELS = 2,
};
#define RCAR_LVDS_QUIRK_LANES BIT(0) /* LVDS lanes 1 and 3 inverted */
#define RCAR_LVDS_QUIRK_GEN3_LVEN BIT(1) /* LVEN bit needs to be set on R8A77970/R8A7799x */
#define RCAR_LVDS_QUIRK_PWD BIT(2) /* PWD bit available (all of Gen3 but E3) */
@ -65,11 +72,8 @@ struct rcar_lvds {
struct clk *dotclkin[2]; /* External DU clocks */
} clocks;
struct drm_display_mode display_mode;
enum rcar_lvds_mode mode;
struct drm_bridge *companion;
bool dual_link;
enum rcar_lvds_link_type link_type;
};
#define bridge_to_rcar_lvds(b) \
@ -402,10 +406,53 @@ EXPORT_SYMBOL_GPL(rcar_lvds_clk_disable);
* Bridge
*/
static void rcar_lvds_enable(struct drm_bridge *bridge)
static enum rcar_lvds_mode rcar_lvds_get_lvds_mode(struct rcar_lvds *lvds,
const struct drm_connector *connector)
{
const struct drm_display_info *info;
enum rcar_lvds_mode mode;
/*
* There is no API yet to retrieve LVDS mode from a bridge, only panels
* are supported.
*/
if (!lvds->panel)
return RCAR_LVDS_MODE_JEIDA;
info = &connector->display_info;
if (!info->num_bus_formats || !info->bus_formats) {
dev_warn(lvds->dev,
"no LVDS bus format reported, using JEIDA\n");
return RCAR_LVDS_MODE_JEIDA;
}
switch (info->bus_formats[0]) {
case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
mode = RCAR_LVDS_MODE_JEIDA;
break;
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
mode = RCAR_LVDS_MODE_VESA;
break;
default:
dev_warn(lvds->dev,
"unsupported LVDS bus format 0x%04x, using JEIDA\n",
info->bus_formats[0]);
return RCAR_LVDS_MODE_JEIDA;
}
if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
mode |= RCAR_LVDS_MODE_MIRROR;
return mode;
}
static void __rcar_lvds_atomic_enable(struct drm_bridge *bridge,
struct drm_atomic_state *state,
struct drm_crtc *crtc,
struct drm_connector *connector)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
const struct drm_display_mode *mode = &lvds->display_mode;
u32 lvdhcr;
u32 lvdcr0;
int ret;
@ -415,8 +462,9 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
return;
/* Enable the companion LVDS encoder in dual-link mode. */
if (lvds->dual_link && lvds->companion)
lvds->companion->funcs->enable(lvds->companion);
if (lvds->link_type != RCAR_LVDS_SINGLE_LINK && lvds->companion)
__rcar_lvds_atomic_enable(lvds->companion, state, crtc,
connector);
/*
* Hardcode the channels and control signals routing for now.
@ -440,30 +488,51 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
rcar_lvds_write(lvds, LVDCHCR, lvdhcr);
if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK) {
/*
* Configure vertical stripe based on the mode of operation of
* the connected device.
*/
rcar_lvds_write(lvds, LVDSTRIPE,
lvds->dual_link ? LVDSTRIPE_ST_ON : 0);
u32 lvdstripe = 0;
if (lvds->link_type != RCAR_LVDS_SINGLE_LINK) {
/*
* By default we generate even pixels from the primary
* encoder and odd pixels from the companion encoder.
* Swap pixels around if the sink requires odd pixels
* from the primary encoder and even pixels from the
* companion encoder.
*/
bool swap_pixels = lvds->link_type ==
RCAR_LVDS_DUAL_LINK_ODD_EVEN_PIXELS;
/*
* Configure vertical stripe since we are dealing with
* an LVDS dual-link connection.
*
* ST_SWAP is reserved for the companion encoder, only
* set it in the primary encoder.
*/
lvdstripe = LVDSTRIPE_ST_ON
| (lvds->companion && swap_pixels ?
LVDSTRIPE_ST_SWAP : 0);
}
rcar_lvds_write(lvds, LVDSTRIPE, lvdstripe);
}
/*
* PLL clock configuration on all instances but the companion in
* dual-link mode.
*/
if (!lvds->dual_link || lvds->companion)
if (lvds->link_type == RCAR_LVDS_SINGLE_LINK || lvds->companion) {
const struct drm_crtc_state *crtc_state =
drm_atomic_get_new_crtc_state(state, crtc);
const struct drm_display_mode *mode =
&crtc_state->adjusted_mode;
lvds->info->pll_setup(lvds, mode->clock * 1000);
}
/* Set the LVDS mode and select the input. */
lvdcr0 = lvds->mode << LVDCR0_LVMD_SHIFT;
lvdcr0 = rcar_lvds_get_lvds_mode(lvds, connector) << LVDCR0_LVMD_SHIFT;
if (lvds->bridge.encoder) {
/*
* FIXME: We should really retrieve the CRTC through the state,
* but how do we get a state pointer?
*/
if (drm_crtc_index(lvds->bridge.encoder->crtc) == 2)
if (drm_crtc_index(crtc) == 2)
lvdcr0 |= LVDCR0_DUSEL;
}
@ -520,7 +589,21 @@ static void rcar_lvds_enable(struct drm_bridge *bridge)
}
}
static void rcar_lvds_disable(struct drm_bridge *bridge)
static void rcar_lvds_atomic_enable(struct drm_bridge *bridge,
struct drm_atomic_state *state)
{
struct drm_connector *connector;
struct drm_crtc *crtc;
connector = drm_atomic_get_new_connector_for_encoder(state,
bridge->encoder);
crtc = drm_atomic_get_new_connector_state(state, connector)->crtc;
__rcar_lvds_atomic_enable(bridge, state, crtc, connector);
}
static void rcar_lvds_atomic_disable(struct drm_bridge *bridge,
struct drm_atomic_state *state)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
@ -534,8 +617,8 @@ static void rcar_lvds_disable(struct drm_bridge *bridge)
rcar_lvds_write(lvds, LVDPLLCR, 0);
/* Disable the companion LVDS encoder in dual-link mode. */
if (lvds->dual_link && lvds->companion)
lvds->companion->funcs->disable(lvds->companion);
if (lvds->link_type != RCAR_LVDS_SINGLE_LINK && lvds->companion)
lvds->companion->funcs->atomic_disable(lvds->companion, state);
clk_disable_unprepare(lvds->clocks.mod);
}
@ -558,54 +641,6 @@ static bool rcar_lvds_mode_fixup(struct drm_bridge *bridge,
return true;
}
static void rcar_lvds_get_lvds_mode(struct rcar_lvds *lvds)
{
struct drm_display_info *info = &lvds->connector.display_info;
enum rcar_lvds_mode mode;
/*
* There is no API yet to retrieve LVDS mode from a bridge, only panels
* are supported.
*/
if (!lvds->panel)
return;
if (!info->num_bus_formats || !info->bus_formats) {
dev_err(lvds->dev, "no LVDS bus format reported\n");
return;
}
switch (info->bus_formats[0]) {
case MEDIA_BUS_FMT_RGB666_1X7X3_SPWG:
case MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA:
mode = RCAR_LVDS_MODE_JEIDA;
break;
case MEDIA_BUS_FMT_RGB888_1X7X4_SPWG:
mode = RCAR_LVDS_MODE_VESA;
break;
default:
dev_err(lvds->dev, "unsupported LVDS bus format 0x%04x\n",
info->bus_formats[0]);
return;
}
if (info->bus_flags & DRM_BUS_FLAG_DATA_LSB_TO_MSB)
mode |= RCAR_LVDS_MODE_MIRROR;
lvds->mode = mode;
}
static void rcar_lvds_mode_set(struct drm_bridge *bridge,
const struct drm_display_mode *mode,
const struct drm_display_mode *adjusted_mode)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
lvds->display_mode = *adjusted_mode;
rcar_lvds_get_lvds_mode(lvds);
}
static int rcar_lvds_attach(struct drm_bridge *bridge)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
@ -647,17 +682,16 @@ static void rcar_lvds_detach(struct drm_bridge *bridge)
static const struct drm_bridge_funcs rcar_lvds_bridge_ops = {
.attach = rcar_lvds_attach,
.detach = rcar_lvds_detach,
.enable = rcar_lvds_enable,
.disable = rcar_lvds_disable,
.atomic_enable = rcar_lvds_atomic_enable,
.atomic_disable = rcar_lvds_atomic_disable,
.mode_fixup = rcar_lvds_mode_fixup,
.mode_set = rcar_lvds_mode_set,
};
bool rcar_lvds_dual_link(struct drm_bridge *bridge)
{
struct rcar_lvds *lvds = bridge_to_rcar_lvds(bridge);
return lvds->dual_link;
return lvds->link_type != RCAR_LVDS_SINGLE_LINK;
}
EXPORT_SYMBOL_GPL(rcar_lvds_dual_link);
@ -669,7 +703,10 @@ static int rcar_lvds_parse_dt_companion(struct rcar_lvds *lvds)
{
const struct of_device_id *match;
struct device_node *companion;
struct device_node *port0, *port1;
struct rcar_lvds *companion_lvds;
struct device *dev = lvds->dev;
int dual_link;
int ret = 0;
/* Locate the companion LVDS encoder for dual-link operation, if any. */
@ -688,13 +725,68 @@ static int rcar_lvds_parse_dt_companion(struct rcar_lvds *lvds)
goto done;
}
/*
* We need to work out if the sink is expecting us to function in
* dual-link mode. We do this by looking at the DT port nodes we are
* connected to, if they are marked as expecting even pixels and
* odd pixels than we need to enable vertical stripe output.
*/
port0 = of_graph_get_port_by_id(dev->of_node, 1);
port1 = of_graph_get_port_by_id(companion, 1);
dual_link = drm_of_lvds_get_dual_link_pixel_order(port0, port1);
of_node_put(port0);
of_node_put(port1);
switch (dual_link) {
case DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS:
lvds->link_type = RCAR_LVDS_DUAL_LINK_ODD_EVEN_PIXELS;
break;
case DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS:
lvds->link_type = RCAR_LVDS_DUAL_LINK_EVEN_ODD_PIXELS;
break;
default:
/*
* Early dual-link bridge specific implementations populate the
* timings field of drm_bridge. If the flag is set, we assume
* that we are expected to generate even pixels from the primary
* encoder, and odd pixels from the companion encoder.
*/
if (lvds->next_bridge && lvds->next_bridge->timings &&
lvds->next_bridge->timings->dual_link)
lvds->link_type = RCAR_LVDS_DUAL_LINK_EVEN_ODD_PIXELS;
else
lvds->link_type = RCAR_LVDS_SINGLE_LINK;
}
if (lvds->link_type == RCAR_LVDS_SINGLE_LINK) {
dev_dbg(dev, "Single-link configuration detected\n");
goto done;
}
lvds->companion = of_drm_find_bridge(companion);
if (!lvds->companion) {
ret = -EPROBE_DEFER;
goto done;
}
dev_dbg(dev, "Found companion encoder %pOF\n", companion);
dev_dbg(dev,
"Dual-link configuration detected (companion encoder %pOF)\n",
companion);
if (lvds->link_type == RCAR_LVDS_DUAL_LINK_ODD_EVEN_PIXELS)
dev_dbg(dev, "Data swapping required\n");
/*
* FIXME: We should not be messing with the companion encoder private
* data from the primary encoder, we should rather let the companion
* encoder work things out on its own. However, the companion encoder
* doesn't hold a reference to the primary encoder, and
* drm_of_lvds_get_dual_link_pixel_order needs to be given references
* to the output ports of both encoders, therefore leave it like this
* for the time being.
*/
companion_lvds = bridge_to_rcar_lvds(lvds->companion);
companion_lvds->link_type = lvds->link_type;
done:
of_node_put(companion);
@ -704,79 +796,17 @@ done:
static int rcar_lvds_parse_dt(struct rcar_lvds *lvds)
{
struct device_node *local_output = NULL;
struct device_node *remote_input = NULL;
struct device_node *remote = NULL;
struct device_node *node;
bool is_bridge = false;
int ret = 0;
int ret;
local_output = of_graph_get_endpoint_by_regs(lvds->dev->of_node, 1, 0);
if (!local_output) {
dev_dbg(lvds->dev, "unconnected port@1\n");
ret = -ENODEV;
ret = drm_of_find_panel_or_bridge(lvds->dev->of_node, 1, 0,
&lvds->panel, &lvds->next_bridge);
if (ret)
goto done;
}
/*
* Locate the connected entity and infer its type from the number of
* endpoints.
*/
remote = of_graph_get_remote_port_parent(local_output);
if (!remote) {
dev_dbg(lvds->dev, "unconnected endpoint %pOF\n", local_output);
ret = -ENODEV;
goto done;
}
if (!of_device_is_available(remote)) {
dev_dbg(lvds->dev, "connected entity %pOF is disabled\n",
remote);
ret = -ENODEV;
goto done;
}
remote_input = of_graph_get_remote_endpoint(local_output);
for_each_endpoint_of_node(remote, node) {
if (node != remote_input) {
/*
* We've found one endpoint other than the input, this
* must be a bridge.
*/
is_bridge = true;
of_node_put(node);
break;
}
}
if (is_bridge) {
lvds->next_bridge = of_drm_find_bridge(remote);
if (!lvds->next_bridge) {
ret = -EPROBE_DEFER;
goto done;
}
if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK)
lvds->dual_link = lvds->next_bridge->timings
? lvds->next_bridge->timings->dual_link
: false;
} else {
lvds->panel = of_drm_find_panel(remote);
if (IS_ERR(lvds->panel)) {
ret = PTR_ERR(lvds->panel);
goto done;
}
}
if (lvds->dual_link)
if (lvds->info->quirks & RCAR_LVDS_QUIRK_DUAL_LINK)
ret = rcar_lvds_parse_dt_companion(lvds);
done:
of_node_put(local_output);
of_node_put(remote_input);
of_node_put(remote);
/*
* On D3/E3 the LVDS encoder provides a clock to the DU, which can be
* used for the DPAD output even when the LVDS output is not connected.

View File

@ -16,6 +16,18 @@ struct drm_panel;
struct drm_bridge;
struct device_node;
/**
* enum drm_lvds_dual_link_pixels - Pixel order of an LVDS dual-link connection
* @DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS: Even pixels are expected to be generated
* from the first port, odd pixels from the second port
* @DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS: Odd pixels are expected to be generated
* from the first port, even pixels from the second port
*/
enum drm_lvds_dual_link_pixels {
DRM_LVDS_DUAL_LINK_EVEN_ODD_PIXELS = 0,
DRM_LVDS_DUAL_LINK_ODD_EVEN_PIXELS = 1,
};
#ifdef CONFIG_OF
uint32_t drm_of_crtc_port_mask(struct drm_device *dev,
struct device_node *port);
@ -35,6 +47,8 @@ int drm_of_find_panel_or_bridge(const struct device_node *np,
int port, int endpoint,
struct drm_panel **panel,
struct drm_bridge **bridge);
int drm_of_lvds_get_dual_link_pixel_order(const struct device_node *port1,
const struct device_node *port2);
#else
static inline uint32_t drm_of_crtc_port_mask(struct drm_device *dev,
struct device_node *port)
@ -77,6 +91,12 @@ static inline int drm_of_find_panel_or_bridge(const struct device_node *np,
{
return -EINVAL;
}
int drm_of_lvds_get_dual_link_pixel_order(const struct device_node *port1,
const struct device_node *port2)
{
return -EINVAL;
}
#endif
/*