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:
commit
66af4a9dda
|
@ -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>;
|
||||
};
|
|
@ -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>;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
|
@ -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__ */
|
|
@ -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. */
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 },
|
||||
{ }
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
/*
|
||||
|
|
Loading…
Reference in New Issue