Commit 66af4a9d authored by Daniel Vetter's avatar Daniel Vetter

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: default avatarDaniel Vetter <daniel.vetter@ffwll.ch>
From: Laurent Pinchart <laurent.pinchart@ideasonboard.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20191218151710.GA13830@pendragon.ideasonboard.com
parents be452c4e c267782c
# 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>;
};
...@@ -41,10 +41,14 @@ Required Properties: ...@@ -41,10 +41,14 @@ Required Properties:
supplied they must be named "dclkin.x" with "x" being the input clock supplied they must be named "dclkin.x" with "x" being the input clock
numerical index. numerical index.
- vsps: A list of phandle and channel index tuples to the VSPs that handle - renesas,cmms: A list of phandles to the CMM instances present in the SoC,
the memory interfaces for the DU channels. The phandle identifies the VSP one for each available DU channel. The property shall not be specified for
instance that serves the DU channel, and the channel index identifies the SoCs that do not provide any CMM (such as V3M and V3H).
LIF instance in that VSP.
- 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: Required nodes:
...@@ -92,7 +96,8 @@ Example: R8A7795 (R-Car H3) ES2.0 DU ...@@ -92,7 +96,8 @@ Example: R8A7795 (R-Car H3) ES2.0 DU
<&cpg CPG_MOD 722>, <&cpg CPG_MOD 722>,
<&cpg CPG_MOD 721>; <&cpg CPG_MOD 721>;
clock-names = "du.0", "du.1", "du.2", "du.3"; 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 { ports {
#address-cells = <1>; #address-cells = <1>;
......
...@@ -274,3 +274,119 @@ int drm_of_find_panel_or_bridge(const struct device_node *np, ...@@ -274,3 +274,119 @@ int drm_of_find_panel_or_bridge(const struct device_node *np,
return ret; return ret;
} }
EXPORT_SYMBOL_GPL(drm_of_find_panel_or_bridge); 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);
...@@ -4,6 +4,7 @@ config DRM_RCAR_DU ...@@ -4,6 +4,7 @@ config DRM_RCAR_DU
depends on DRM && OF depends on DRM && OF
depends on ARM || ARM64 depends on ARM || ARM64
depends on ARCH_RENESAS || COMPILE_TEST depends on ARCH_RENESAS || COMPILE_TEST
imply DRM_RCAR_CMM
imply DRM_RCAR_LVDS imply DRM_RCAR_LVDS
select DRM_KMS_HELPER select DRM_KMS_HELPER
select DRM_KMS_CMA_HELPER select DRM_KMS_CMA_HELPER
...@@ -13,6 +14,13 @@ config DRM_RCAR_DU ...@@ -13,6 +14,13 @@ config DRM_RCAR_DU
Choose this option if you have an R-Car chipset. Choose this option if you have an R-Car chipset.
If M is selected the module will be called rcar-du-drm. 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 config DRM_RCAR_DW_HDMI
tristate "R-Car DU Gen3 HDMI Encoder Support" tristate "R-Car DU Gen3 HDMI Encoder Support"
depends on DRM && OF depends on DRM && OF
......
...@@ -15,6 +15,7 @@ rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_of.o \ ...@@ -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_VSP) += rcar_du_vsp.o
rcar-du-drm-$(CONFIG_DRM_RCAR_WRITEBACK) += rcar_du_writeback.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_DU) += rcar-du-drm.o
obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o obj-$(CONFIG_DRM_RCAR_DW_HDMI) += rcar_dw_hdmi.o
obj-$(CONFIG_DRM_RCAR_LVDS) += rcar_lvds.o obj-$(CONFIG_DRM_RCAR_LVDS) += rcar_lvds.o
......
// 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");
/* 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__ */
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <drm/drm_plane_helper.h> #include <drm/drm_plane_helper.h>
#include <drm/drm_vblank.h> #include <drm/drm_vblank.h>
#include "rcar_cmm.h"
#include "rcar_du_crtc.h" #include "rcar_du_crtc.h"
#include "rcar_du_drv.h" #include "rcar_du_drv.h"
#include "rcar_du_encoder.h" #include "rcar_du_encoder.h"
...@@ -475,6 +476,45 @@ static void rcar_du_crtc_wait_page_flip(struct rcar_du_crtc *rcrtc) ...@@ -475,6 +476,45 @@ static void rcar_du_crtc_wait_page_flip(struct rcar_du_crtc *rcrtc)
rcar_du_crtc_finish_page_flip(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 * Start/Stop and Suspend/Resume
*/ */
...@@ -620,6 +660,9 @@ static void rcar_du_crtc_stop(struct rcar_du_crtc *rcrtc) ...@@ -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)) if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
rcar_du_vsp_disable(rcrtc); rcar_du_vsp_disable(rcrtc);
if (rcrtc->cmm)
rcar_cmm_disable(rcrtc->cmm);
/* /*
* Select switch sync mode. This stops display operation and configures * Select switch sync mode. This stops display operation and configures
* the HSYNC and VSYNC signals as inputs. * the HSYNC and VSYNC signals as inputs.
...@@ -643,6 +686,11 @@ static int rcar_du_crtc_atomic_check(struct drm_crtc *crtc, ...@@ -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 rcar_du_crtc_state *rstate = to_rcar_crtc_state(state);
struct drm_encoder *encoder; 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. */ /* Store the routes from the CRTC output to the DU outputs. */
rstate->outputs = 0; rstate->outputs = 0;
...@@ -668,6 +716,8 @@ static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc, ...@@ -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_crtc_state *rstate = to_rcar_crtc_state(crtc->state);
struct rcar_du_device *rcdu = rcrtc->dev; struct rcar_du_device *rcdu = rcrtc->dev;
if (rcrtc->cmm)
rcar_cmm_enable(rcrtc->cmm);
rcar_du_crtc_get(rcrtc); rcar_du_crtc_get(rcrtc);
/* /*
...@@ -688,6 +738,13 @@ static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc, ...@@ -688,6 +738,13 @@ static void rcar_du_crtc_atomic_enable(struct drm_crtc *crtc,
} }
rcar_du_crtc_start(rcrtc); 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, 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, ...@@ -743,6 +800,10 @@ static void rcar_du_crtc_atomic_begin(struct drm_crtc *crtc,
*/ */
rcar_du_crtc_get(rcrtc); 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)) if (rcar_du_has(rcrtc->dev, RCAR_DU_FEATURE_VSP1_SOURCE))
rcar_du_vsp_atomic_begin(rcrtc); rcar_du_vsp_atomic_begin(rcrtc);
} }
...@@ -1079,6 +1140,7 @@ static const struct drm_crtc_funcs crtc_funcs_gen3 = { ...@@ -1079,6 +1140,7 @@ static const struct drm_crtc_funcs crtc_funcs_gen3 = {
.set_crc_source = rcar_du_crtc_set_crc_source, .set_crc_source = rcar_du_crtc_set_crc_source,
.verify_crc_source = rcar_du_crtc_verify_crc_source, .verify_crc_source = rcar_du_crtc_verify_crc_source,
.get_crc_sources = rcar_du_crtc_get_crc_sources, .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, ...@@ -1198,6 +1260,15 @@ int rcar_du_crtc_create(struct rcar_du_group *rgrp, unsigned int swindex,
if (ret < 0) if (ret < 0)
return ret; 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); drm_crtc_helper_add(crtc, &crtc_helper_funcs);
/* Start with vertical blanking interrupt reporting disabled. */ /* Start with vertical blanking interrupt reporting disabled. */
......
...@@ -39,6 +39,7 @@ struct rcar_du_vsp; ...@@ -39,6 +39,7 @@ struct rcar_du_vsp;
* @vblank_wait: wait queue used to signal vertical blanking * @vblank_wait: wait queue used to signal vertical blanking
* @vblank_count: number of vertical blanking interrupts to wait for * @vblank_count: number of vertical blanking interrupts to wait for
* @group: CRTC group this CRTC belongs to * @group: CRTC group this CRTC belongs to
* @cmm: CMM associated with this CRTC
* @vsp: VSP feeding video to this CRTC * @vsp: VSP feeding video to this CRTC
* @vsp_pipe: index of the VSP pipeline feeding video to this CRTC * @vsp_pipe: index of the VSP pipeline feeding video to this CRTC
* @writeback: the writeback connector * @writeback: the writeback connector
...@@ -64,6 +65,7 @@ struct rcar_du_crtc { ...@@ -64,6 +65,7 @@ struct rcar_du_crtc {
unsigned int vblank_count; unsigned int vblank_count;
struct rcar_du_group *group; struct rcar_du_group *group;
struct platform_device *cmm;
struct rcar_du_vsp *vsp; struct rcar_du_vsp *vsp;
unsigned int vsp_pipe; unsigned int vsp_pipe;
......
...@@ -399,7 +399,10 @@ static const struct rcar_du_device_info rcar_du_r8a77970_info = { ...@@ -399,7 +399,10 @@ static const struct rcar_du_device_info rcar_du_r8a77970_info = {
| RCAR_DU_FEATURE_TVM_SYNC, | RCAR_DU_FEATURE_TVM_SYNC,
.channels_mask = BIT(0), .channels_mask = BIT(0),
.routes = { .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] = { [RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0), .possible_crtcs = BIT(0),
.port = 0, .port = 0,
...@@ -457,6 +460,7 @@ static const struct of_device_id rcar_du_of_table[] = { ...@@ -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-r8a7796", .data = &rcar_du_r8a7796_info },
{ .compatible = "renesas,du-r8a77965", .data = &rcar_du_r8a77965_info }, { .compatible = "renesas,du-r8a77965", .data = &rcar_du_r8a77965_info },
{ .compatible = "renesas,du-r8a77970", .data = &rcar_du_r8a77970_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-r8a77990", .data = &rcar_du_r8a7799x_info },
{ .compatible = "renesas,du-r8a77995", .data = &rcar_du_r8a7799x_info }, { .compatible = "renesas,du-r8a77995", .data = &rcar_du_r8a7799x_info },
{ } { }
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/wait.h> #include <linux/wait.h>
#include "rcar_cmm.h"
#include "rcar_du_crtc.h" #include "rcar_du_crtc.h"
#include "rcar_du_group.h" #include "rcar_du_group.h"
#include "rcar_du_vsp.h" #include "rcar_du_vsp.h"
...@@ -85,6 +86,7 @@ struct rcar_du_device { ...@@ -85,6 +86,7 @@ struct rcar_du_device {
struct rcar_du_encoder *encoders[RCAR_DU_OUTPUT_MAX]; struct rcar_du_encoder *encoders[RCAR_DU_OUTPUT_MAX];
struct rcar_du_group groups[RCAR_DU_MAX_GROUPS]; 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 rcar_du_vsp vsps[RCAR_DU_MAX_VSPS];
struct { struct {
......
...@@ -135,6 +135,7 @@ static void rcar_du_group_setup_didsr(struct rcar_du_group *rgrp) ...@@ -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) static void rcar_du_group_setup(struct rcar_du_group *rgrp)
{ {
struct rcar_du_device *rcdu = rgrp->dev; struct rcar_du_device *rcdu = rgrp->dev;
u32 defr7 = DEFR7_CODE;
/* Enable extended features */ /* Enable extended features */
rcar_du_group_write(rgrp, DEFR, DEFR_CODE | DEFR_DEFE); 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) ...@@ -147,6 +148,15 @@ static void rcar_du_group_setup(struct rcar_du_group *rgrp)
rcar_du_group_setup_pins(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) { if (rcdu->info->gen >= 2) {
rcar_du_group_setup_defr8(rgrp); rcar_du_group_setup_defr8(rgrp);
rcar_du_group_setup_didsr(rgrp); rcar_du_group_setup_didsr(rgrp);
......
...@@ -22,6 +22,7 @@ struct rcar_du_device; ...@@ -22,6 +22,7 @@ struct rcar_du_device;
* @mmio_offset: registers offset in the device memory map * @mmio_offset: registers offset in the device memory map
* @index: group index * @index: group index
* @channels_mask: bitmask of populated DU channels in this group * @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) * @num_crtcs: number of CRTCs in this group (1 or 2)
* @use_count: number of users of the group (rcar_du_group_(get|put)) * @use_count: number of users of the group (rcar_du_group_(get|put))
* @used_crtcs: number of CRTCs currently in use * @used_crtcs: number of CRTCs currently in use
...@@ -37,6 +38,7 @@ struct rcar_du_group { ...@@ -37,6 +38,7 @@ struct rcar_du_group {
unsigned int index; unsigned int index;
unsigned int channels_mask; unsigned int channels_mask;
unsigned int cmms_mask;
unsigned int num_crtcs; unsigned int num_crtcs;
unsigned int use_count; unsigned int use_count;
unsigned int used_crtcs; unsigned int used_crtcs;
......
...@@ -17,7 +17,9 @@ ...@@ -17,7 +17,9 @@
#include <drm/drm_probe_helper.h> #include <drm/drm_probe_helper.h>
#include <drm/drm_vblank.h> #include <drm/drm_vblank.h>
#include <linux/device.h>
#include <linux/of_graph.h> #include <linux/of_graph.h>
#include <linux/of_platform.h>
#include <linux/wait.h> #include <linux/wait.h>
#include "rcar_du_crtc.h" #include "rcar_du_crtc.h"
...@@ -542,6 +544,7 @@ static int rcar_du_properties_init(struct rcar_du_device *rcdu) ...@@ -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) static int rcar_du_vsps_init(struct rcar_du_device *rcdu)
{ {
const struct device_node *np = rcdu->dev->of_node; const struct device_node *np = rcdu->dev->of_node;
const char *vsps_prop_name = "renesas,vsps";
struct of_phandle_args args; struct of_phandle_args args;
struct { struct {
struct device_node *np; struct device_node *np;
...@@ -557,15 +560,21 @@ static int rcar_du_vsps_init(struct rcar_du_device *rcdu) ...@@ -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 * entry contains a pointer to the VSP DT node and a bitmask of the
* connected DU CRTCs. * 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) if (cells > 1)
return -EINVAL; return -EINVAL;
for (i = 0; i < rcdu->num_crtcs; ++i) { for (i = 0; i < rcdu->num_crtcs; ++i) {
unsigned int j; unsigned int j;
ret = of_parse_phandle_with_fixed_args(np, "vsps", cells, i, ret = of_parse_phandle_with_fixed_args(np, vsps_prop_name,
&args); cells, i, &args);
if (ret < 0) if (ret < 0)
goto error; goto error;
...@@ -587,8 +596,8 @@ static int rcar_du_vsps_init(struct rcar_du_device *rcdu) ...@@ -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 * Store the VSP pointer and pipe index in the CRTC. If the
* second cell of the 'vsps' specifier isn't present, default * second cell of the 'renesas,vsps' specifier isn't present,
* to 0 to remain compatible with older DT bindings. * default to 0 to remain compatible with older DT bindings.
*/ */
rcdu->crtcs[i].vsp = &rcdu->vsps[j]; rcdu->crtcs[i].vsp = &rcdu->vsps[j];
rcdu->crtcs[i].vsp_pipe = cells >= 1 ? args.args[0] : 0; rcdu->crtcs[i].vsp_pipe = cells >= 1 ? args.args[0] : 0;
...@@ -618,6 +627,75 @@ static int rcar_du_vsps_init(struct rcar_du_device *rcdu) ...@@ -618,6 +627,75 @@ static int rcar_du_vsps_init(struct rcar_du_device *rcdu)
return ret; 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) int rcar_du_modeset_init(struct rcar_du_device *rcdu)
{ {
static const unsigned int mmio_offsets[] = { static const unsigned int mmio_offsets[] = {
...@@ -708,6 +786,11 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu) ...@@ -708,6 +786,11 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
return ret; return ret;
} }
/* Initialize the Color Management Modules. */
ret = rcar_du_cmm_init(rcdu);
if (ret)
return ret;
/* Create the CRTCs. */ /* Create the CRTCs. */
for (swindex = 0, hwindex = 0; swindex < rcdu->num_crtcs; ++hwindex) { for (swindex = 0, hwindex = 0; swindex < rcdu->num_crtcs; ++hwindex) {
struct rcar_du_group *rgrp; struct rcar_du_group *rgrp;
......
...@@ -197,6 +197,11 @@ ...@@ -197,6 +197,11 @@
#define DEFR6_MLOS1 (1 << 2) #define DEFR6_MLOS1 (1 << 2)
#define DEFR6_DEFAULT (DEFR6_CODE | DEFR6_TCNE1) #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 * R8A7790-only Control Registers
*/ */
......
This diff is collapsed.
...@@ -16,6 +16,18 @@ struct drm_panel; ...@@ -16,6 +16,18 @@ struct drm_panel;
struct drm_bridge; struct drm_bridge;
struct device_node; 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 #ifdef CONFIG_OF
uint32_t drm_of_crtc_port_mask(struct drm_device *dev, uint32_t drm_of_crtc_port_mask(struct drm_device *dev,
struct device_node *port); struct device_node *port);
...@@ -35,6 +47,8 @@ int drm_of_find_panel_or_bridge(const struct device_node *np, ...@@ -35,6 +47,8 @@ int drm_of_find_panel_or_bridge(const struct device_node *np,
int port, int endpoint, int port, int endpoint,
struct drm_panel **panel, struct drm_panel **panel,
struct drm_bridge **bridge); struct drm_bridge **bridge);
int drm_of_lvds_get_dual_link_pixel_order(const struct device_node *port1,
const struct device_node *port2);
#else #else
static inline uint32_t drm_of_crtc_port_mask(struct drm_device *dev, static inline uint32_t drm_of_crtc_port_mask(struct drm_device *dev,
struct device_node *port) struct device_node *port)
...@@ -77,6 +91,12 @@ static inline int drm_of_find_panel_or_bridge(const struct device_node *np, ...@@ -77,6 +91,12 @@ static inline int drm_of_find_panel_or_bridge(const struct device_node *np,
{ {
return -EINVAL; return -EINVAL;
} }
int drm_of_lvds_get_dual_link_pixel_order(const struct device_node *port1,
const struct device_node *port2)
{
return -EINVAL;
}
#endif #endif
/* /*
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment