Commit fabe2be1 authored by Dave Airlie's avatar Dave Airlie

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

RCAR GEN3 DU HDMI support.

* 'drm/next/du' of git://linuxtv.org/pinchartl/media: (22 commits)
  drm: rcar-du: Add HDMI outputs to R8A7795 device description
  drm: rcar-du: Add DPLL support
  drm: rcar-du: Skip disabled outputs
  drm: rcar-du: Add Gen3 HDMI encoder support
  dt-bindings: display: renesas: Add R-Car Gen3 HDMI TX DT bindings
  drm: rcar-du: Hardcode encoders types to DRM_MODE_ENCODER_NONE
  drm: rcar-du: Replace manual bridge implementation with DRM bridge
  drm: rcar-du: Add support for LVDS mode selection
  drm: rcar-du: Use the DRM panel API
  drm: rcar-du: Document the vsps property in the DT bindings
  drm: rcar-du: Remove wait field from rcar_du_device structure
  drm: rcar-du: Make sure the VSP is initialized on platforms that need it
  drm: rcar-du: Use DRM core's atomic commit helper
  drm: rcar-du: Clear handled event pointer in CRTC state
  drm: rcar-du: Handle event when disabling CRTCs
  drm: rcar-du: Don't open code of_device_get_match_data()
  drm: rcar-du: Switch to encoder .atomic_mode_set() helper function
  drm: panels: Add LVDS panel driver
  drm: Add data transmission order bus flag
  devicetree/bindings: display: Add bindings for two Mitsubishi panels
  ...
parents e1b489d2 0dda563e
Renesas Gen3 DWC HDMI TX Encoder
================================
The HDMI transmitter is a Synopsys DesignWare HDMI 1.4 TX controller IP
with a companion PHY IP.
These DT bindings follow the Synopsys DWC HDMI TX bindings defined in
Documentation/devicetree/bindings/display/bridge/dw_hdmi.txt with the
following device-specific properties.
Required properties:
- compatible : Shall contain one or more of
- "renesas,r8a7795-hdmi" for R8A7795 (R-Car H3) compatible HDMI TX
- "renesas,rcar-gen3-hdmi" for the generic R-Car Gen3 compatible HDMI TX
When compatible with generic versions, nodes must list the SoC-specific
version corresponding to the platform first, followed by the
family-specific version.
- reg: See dw_hdmi.txt.
- interrupts: HDMI interrupt number
- clocks: See dw_hdmi.txt.
- clock-names: Shall contain "iahb" and "isfr" as defined in dw_hdmi.txt.
- ports: See dw_hdmi.txt. The DWC HDMI shall have one port numbered 0
corresponding to the video input of the controller and one port numbered 1
corresponding to its HDMI output. Each port shall have a single endpoint.
Optional properties:
- power-domains: Shall reference the power domain that contains the DWC HDMI,
if any.
Example:
hdmi0: hdmi0@fead0000 {
compatible = "renesas,r8a7795-dw-hdmi";
reg = <0 0xfead0000 0 0x10000>;
interrupts = <0 389 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&cpg CPG_CORE R8A7795_CLK_S0D4>, <&cpg CPG_MOD 729>;
clock-names = "iahb", "isfr";
power-domains = <&sysc R8A7795_PD_ALWAYS_ON>;
status = "disabled";
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
dw_hdmi0_in: endpoint {
remote-endpoint = <&du_out_hdmi0>;
};
};
port@1 {
reg = <1>;
rcar_dw_hdmi0_out: endpoint {
remote-endpoint = <&hdmi0_con>;
};
};
};
};
hdmi0-out {
compatible = "hdmi-connector";
label = "HDMI0 OUT";
type = "a";
port {
hdmi0_con: endpoint {
remote-endpoint = <&rcar_dw_hdmi0_out>;
};
};
};
Mitsubishi AA204XD12 LVDS Display Panel
=======================================
The AA104XD12 is a 10.4" XGA TFT-LCD display panel.
These DT bindings follow the LVDS panel bindings defined in panel-lvds.txt
with the following device-specific properties.
Required properties:
- compatible: Shall contain "mitsubishi,aa121td01" and "panel-lvds", in that
order.
- vcc-supply: Reference to the regulator powering the panel VCC pins.
Example
-------
panel {
compatible = "mitsubishi,aa104xd12", "panel-lvds";
vcc-supply = <&vcc_3v3>;
width-mm = <210>;
height-mm = <158>;
data-mapping = "jeida-24";
panel-timing {
/* 1024x768 @65Hz */
clock-frequency = <65000000>;
hactive = <1024>;
vactive = <768>;
hsync-len = <136>;
hfront-porch = <20>;
hback-porch = <160>;
vfront-porch = <3>;
vback-porch = <29>;
vsync-len = <6>;
};
port {
panel_in: endpoint {
remote-endpoint = <&lvds_encoder>;
};
};
};
Mitsubishi AA121TD01 LVDS Display Panel
=======================================
The AA121TD01 is a 12.1" WXGA TFT-LCD display panel.
These DT bindings follow the LVDS panel bindings defined in panel-lvds.txt
with the following device-specific properties.
Required properties:
- compatible: Shall contain "mitsubishi,aa121td01" and "panel-lvds", in that
order.
- vcc-supply: Reference to the regulator powering the panel VCC pins.
Example
-------
panel {
compatible = "mitsubishi,aa121td01", "panel-lvds";
vcc-supply = <&vcc_3v3>;
width-mm = <261>;
height-mm = <163>;
data-mapping = "jeida-24";
panel-timing {
/* 1280x800 @60Hz */
clock-frequency = <71000000>;
hactive = <1280>;
vactive = <800>;
hsync-len = <70>;
hfront-porch = <20>;
hback-porch = <70>;
vsync-len = <5>;
vfront-porch = <3>;
vback-porch = <15>;
};
port {
panel_in: endpoint {
remote-endpoint = <&lvds_encoder>;
};
};
};
Common Properties for Display Panel
===================================
This document defines device tree properties common to several classes of
display panels. It doesn't constitue a device tree binding specification by
itself but is meant to be referenced by device tree bindings.
When referenced from panel device tree bindings the properties defined in this
document are defined as follows. The panel device tree bindings are
responsible for defining whether each property is required or optional.
Descriptive Properties
----------------------
- width-mm,
- height-mm: The width-mm and height-mm specify the width and height of the
physical area where images are displayed. These properties are expressed in
millimeters and rounded to the closest unit.
- label: The label property specifies a symbolic name for the panel as a
string suitable for use by humans. It typically contains a name inscribed on
the system (e.g. as an affixed label) or specified in the system's
documentation (e.g. in the user's manual).
If no such name exists, and unless the property is mandatory according to
device tree bindings, it shall rather be omitted than constructed of
non-descriptive information. For instance an LCD panel in a system that
contains a single panel shall not be labelled "LCD" if that name is not
inscribed on the system or used in a descriptive fashion in system
documentation.
Display Timings
---------------
- panel-timing: Most display panels are restricted to a single resolution and
require specific display timings. The panel-timing subnode expresses those
timings as specified in the timing subnode section of the display timing
bindings defined in
Documentation/devicetree/bindings/display/display-timing.txt.
Connectivity
------------
- ports: Panels receive video data through one or multiple connections. While
the nature of those connections is specific to the panel type, the
connectivity is expressed in a standard fashion using ports as specified in
the device graph bindings defined in
Documentation/devicetree/bindings/graph.txt.
- ddc-i2c-bus: Some panels expose EDID information through an I2C-compatible
bus such as DDC2 or E-DDC. For such panels the ddc-i2c-bus contains a
phandle to the system I2C controller connected to that bus.
Control I/Os
------------
Many display panels can be controlled through pins driven by GPIOs. The nature
and timing of those control signals are device-specific and left for panel
device tree bindings to specify. The following GPIO specifiers can however be
used for panels that implement compatible control signals.
- enable-gpios: Specifier for a GPIO connected to the panel enable control
signal. The enable signal is active high and enables operation of the panel.
This property can also be used for panels implementing an active low power
down signal, which is a negated version of the enable signal. Active low
enable signals (or active high power down signals) can be supported by
inverting the GPIO specifier polarity flag.
Note that the enable signal control panel operation only and must not be
confused with a backlight enable signal.
- reset-gpios: Specifier for a GPIO coonnected to the panel reset control
signal. The reset signal is active low and resets the panel internal logic
while active. Active high reset signals can be supported by inverting the
GPIO specifier polarity flag.
Backlight
---------
Most display panels include a backlight. Some of them also include a backlight
controller exposed through a control bus such as I2C or DSI. Others expose
backlight control through GPIO, PWM or other signals connected to an external
backlight controller.
- backlight: For panels whose backlight is controlled by an external backlight
controller, this property contains a phandle that references the controller.
LVDS Display Panel
==================
LVDS is a physical layer specification defined in ANSI/TIA/EIA-644-A. Multiple
incompatible data link layers have been used over time to transmit image data
to LVDS panels. This bindings supports display panels compatible with the
following specifications.
[JEIDA] "Digital Interface Standards for Monitor", JEIDA-59-1999, February
1999 (Version 1.0), Japan Electronic Industry Development Association (JEIDA)
[LDI] "Open LVDS Display Interface", May 1999 (Version 0.95), National
Semiconductor
[VESA] "VESA Notebook Panel Standard", October 2007 (Version 1.0), Video
Electronics Standards Association (VESA)
Device compatible with those specifications have been marketed under the
FPD-Link and FlatLink brands.
Required properties:
- compatible: Shall contain "panel-lvds" in addition to a mandatory
panel-specific compatible string defined in individual panel bindings. The
"panel-lvds" value shall never be used on its own.
- width-mm: See panel-common.txt.
- height-mm: See panel-common.txt.
- data-mapping: The color signals mapping order, "jeida-18", "jeida-24"
or "vesa-24".
Optional properties:
- label: See panel-common.txt.
- gpios: See panel-common.txt.
- backlight: See panel-common.txt.
- data-mirror: If set, reverse the bit order described in the data mappings
below on all data lanes, transmitting bits for slots 6 to 0 instead of
0 to 6.
Required nodes:
- panel-timing: See panel-common.txt.
- ports: See panel-common.txt. These bindings require a single port subnode
corresponding to the panel LVDS input.
LVDS data mappings are defined as follows.
- "jeida-18" - 18-bit data mapping compatible with the [JEIDA], [LDI] and
[VESA] specifications. Data are transferred as follows on 3 LVDS lanes.
Slot 0 1 2 3 4 5 6
________________ _________________
Clock \_______________________/
______ ______ ______ ______ ______ ______ ______
DATA0 ><__G0__><__R5__><__R4__><__R3__><__R2__><__R1__><__R0__><
DATA1 ><__B1__><__B0__><__G5__><__G4__><__G3__><__G2__><__G1__><
DATA2 ><_CTL2_><_CTL1_><_CTL0_><__B5__><__B4__><__B3__><__B2__><
- "jeida-24" - 24-bit data mapping compatible with the [DSIM] and [LDI]
specifications. Data are transferred as follows on 4 LVDS lanes.
Slot 0 1 2 3 4 5 6
________________ _________________
Clock \_______________________/
______ ______ ______ ______ ______ ______ ______
DATA0 ><__G2__><__R7__><__R6__><__R5__><__R4__><__R3__><__R2__><
DATA1 ><__B3__><__B2__><__G7__><__G6__><__G5__><__G4__><__G3__><
DATA2 ><_CTL2_><_CTL1_><_CTL0_><__B7__><__B6__><__B5__><__B4__><
DATA3 ><_CTL3_><__B1__><__B0__><__G1__><__G0__><__R1__><__R0__><
- "vesa-24" - 24-bit data mapping compatible with the [VESA] specification.
Data are transferred as follows on 4 LVDS lanes.
Slot 0 1 2 3 4 5 6
________________ _________________
Clock \_______________________/
______ ______ ______ ______ ______ ______ ______
DATA0 ><__G0__><__R5__><__R4__><__R3__><__R2__><__R1__><__R0__><
DATA1 ><__B1__><__B0__><__G5__><__G4__><__G3__><__G2__><__G1__><
DATA2 ><_CTL2_><_CTL1_><_CTL0_><__B5__><__B4__><__B3__><__B2__><
DATA3 ><_CTL3_><__B7__><__B6__><__G7__><__G6__><__R7__><__R6__><
Control signals are mapped as follows.
CTL0: HSync
CTL1: VSync
CTL2: Data Enable
CTL3: 0
Example
-------
panel {
compatible = "mitsubishi,aa121td01", "panel-lvds";
width-mm = <261>;
height-mm = <163>;
data-mapping = "jeida-24";
panel-timing {
/* 1280x800 @60Hz */
clock-frequency = <71000000>;
hactive = <1280>;
vactive = <800>;
hsync-len = <70>;
hfront-porch = <20>;
hback-porch = <70>;
vsync-len = <5>;
vfront-porch = <3>;
vback-porch = <15>;
};
port {
panel_in: endpoint {
remote-endpoint = <&lvds_encoder>;
};
};
};
...@@ -36,6 +36,9 @@ Required Properties: ...@@ -36,6 +36,9 @@ Required Properties:
When supplied they must be named "dclkin.x" with "x" being the input When supplied they must be named "dclkin.x" with "x" being the input
clock numerical index. clock numerical index.
- vsps: A list of phandles to the VSP nodes that handle the memory
interfaces for the DU channels.
Required nodes: Required nodes:
The connections to the DU output video ports are modeled using the OF graph The connections to the DU output video ports are modeled using the OF graph
......
...@@ -4383,6 +4383,7 @@ S: Supported ...@@ -4383,6 +4383,7 @@ S: Supported
F: drivers/gpu/drm/rcar-du/ F: drivers/gpu/drm/rcar-du/
F: drivers/gpu/drm/shmobile/ F: drivers/gpu/drm/shmobile/
F: include/linux/platform_data/shmob_drm.h F: include/linux/platform_data/shmob_drm.h
F: Documentation/devicetree/bindings/display/bridge/renesas,dw-hdmi.txt
F: Documentation/devicetree/bindings/display/renesas,du.txt F: Documentation/devicetree/bindings/display/renesas,du.txt
DRM DRIVER FOR QXL VIRTUAL GPU DRM DRIVER FOR QXL VIRTUAL GPU
......
...@@ -7,6 +7,16 @@ config DRM_PANEL ...@@ -7,6 +7,16 @@ config DRM_PANEL
menu "Display Panels" menu "Display Panels"
depends on DRM && DRM_PANEL depends on DRM && DRM_PANEL
config DRM_PANEL_LVDS
tristate "Generic LVDS panel driver"
depends on OF
depends on BACKLIGHT_CLASS_DEVICE
select VIDEOMODE_HELPERS
help
This driver supports LVDS panels that don't require device-specific
handling of power supplies or control signals. It implements automatic
backlight handling if the panel is attached to a backlight controller.
config DRM_PANEL_SIMPLE config DRM_PANEL_SIMPLE
tristate "support for simple panels" tristate "support for simple panels"
depends on OF depends on OF
......
obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o
obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o
obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o
obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o
......
/*
* rcar_du_crtc.c -- R-Car Display Unit CRTCs
*
* Copyright (C) 2016 Laurent Pinchart
* Copyright (C) 2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/backlight.h>
#include <linux/gpio/consumer.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_panel.h>
#include <video/display_timing.h>
#include <video/of_display_timing.h>
#include <video/videomode.h>
struct panel_lvds {
struct drm_panel panel;
struct device *dev;
const char *label;
unsigned int width;
unsigned int height;
struct videomode video_mode;
unsigned int bus_format;
bool data_mirror;
struct backlight_device *backlight;
struct gpio_desc *enable_gpio;
struct gpio_desc *reset_gpio;
};
static inline struct panel_lvds *to_panel_lvds(struct drm_panel *panel)
{
return container_of(panel, struct panel_lvds, panel);
}
static int panel_lvds_disable(struct drm_panel *panel)
{
struct panel_lvds *lvds = to_panel_lvds(panel);
if (lvds->backlight) {
lvds->backlight->props.power = FB_BLANK_POWERDOWN;
lvds->backlight->props.state |= BL_CORE_FBBLANK;
backlight_update_status(lvds->backlight);
}
return 0;
}
static int panel_lvds_unprepare(struct drm_panel *panel)
{
struct panel_lvds *lvds = to_panel_lvds(panel);
if (lvds->enable_gpio)
gpiod_set_value_cansleep(lvds->enable_gpio, 0);
return 0;
}
static int panel_lvds_prepare(struct drm_panel *panel)
{
struct panel_lvds *lvds = to_panel_lvds(panel);
if (lvds->enable_gpio)
gpiod_set_value_cansleep(lvds->enable_gpio, 1);
return 0;
}
static int panel_lvds_enable(struct drm_panel *panel)
{
struct panel_lvds *lvds = to_panel_lvds(panel);
if (lvds->backlight) {
lvds->backlight->props.state &= ~BL_CORE_FBBLANK;
lvds->backlight->props.power = FB_BLANK_UNBLANK;
backlight_update_status(lvds->backlight);
}
return 0;
}
static int panel_lvds_get_modes(struct drm_panel *panel)
{
struct panel_lvds *lvds = to_panel_lvds(panel);
struct drm_connector *connector = lvds->panel.connector;
struct drm_display_mode *mode;
mode = drm_mode_create(lvds->panel.drm);
if (!mode)
return 0;
drm_display_mode_from_videomode(&lvds->video_mode, mode);
mode->type |= DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
drm_mode_probed_add(connector, mode);
connector->display_info.width_mm = lvds->width;
connector->display_info.height_mm = lvds->height;
drm_display_info_set_bus_formats(&connector->display_info,
&lvds->bus_format, 1);
connector->display_info.bus_flags = lvds->data_mirror
? DRM_BUS_FLAG_DATA_LSB_TO_MSB
: DRM_BUS_FLAG_DATA_MSB_TO_LSB;
return 1;
}
static const struct drm_panel_funcs panel_lvds_funcs = {
.disable = panel_lvds_disable,
.unprepare = panel_lvds_unprepare,
.prepare = panel_lvds_prepare,
.enable = panel_lvds_enable,
.get_modes = panel_lvds_get_modes,
};
static int panel_lvds_parse_dt(struct panel_lvds *lvds)
{
struct device_node *np = lvds->dev->of_node;
struct display_timing timing;
const char *mapping;
int ret;
ret = of_get_display_timing(np, "panel-timing", &timing);
if (ret < 0)
return ret;
videomode_from_timing(&timing, &lvds->video_mode);
ret = of_property_read_u32(np, "width-mm", &lvds->width);
if (ret < 0) {
dev_err(lvds->dev, "%s: invalid or missing %s DT property\n",
of_node_full_name(np), "width-mm");
return -ENODEV;
}
ret = of_property_read_u32(np, "height-mm", &lvds->height);
if (ret < 0) {
dev_err(lvds->dev, "%s: invalid or missing %s DT property\n",
of_node_full_name(np), "height-mm");
return -ENODEV;
}
of_property_read_string(np, "label", &lvds->label);
ret = of_property_read_string(np, "data-mapping", &mapping);
if (ret < 0) {
dev_err(lvds->dev, "%s: invalid or missing %s DT property\n",
of_node_full_name(np), "data-mapping");
return -ENODEV;
}
if (!strcmp(mapping, "jeida-18")) {
lvds->bus_format = MEDIA_BUS_FMT_RGB666_1X7X3_SPWG;
} else if (!strcmp(mapping, "jeida-24")) {
lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_JEIDA;
} else if (!strcmp(mapping, "vesa-24")) {
lvds->bus_format = MEDIA_BUS_FMT_RGB888_1X7X4_SPWG;
} else {
dev_err(lvds->dev, "%s: invalid or missing %s DT property\n",
of_node_full_name(np), "data-mapping");
return -EINVAL;
}
lvds->data_mirror = of_property_read_bool(np, "data-mirror");
return 0;
}
static int panel_lvds_probe(struct platform_device *pdev)
{
struct panel_lvds *lvds;
struct device_node *np;
int ret;
lvds = devm_kzalloc(&pdev->dev, sizeof(*lvds), GFP_KERNEL);
if (!lvds)
return -ENOMEM;
lvds->dev = &pdev->dev;
ret = panel_lvds_parse_dt(lvds);
if (ret < 0)
return ret;
/* Get GPIOs and backlight controller. */
lvds->enable_gpio = devm_gpiod_get_optional(lvds->dev, "enable",
GPIOD_OUT_LOW);
if (IS_ERR(lvds->enable_gpio)) {
ret = PTR_ERR(lvds->enable_gpio);
dev_err(lvds->dev, "failed to request %s GPIO: %d\n",
"enable", ret);
return ret;
}
lvds->reset_gpio = devm_gpiod_get_optional(lvds->dev, "reset",
GPIOD_OUT_HIGH);
if (IS_ERR(lvds->reset_gpio)) {
ret = PTR_ERR(lvds->reset_gpio);
dev_err(lvds->dev, "failed to request %s GPIO: %d\n",
"reset", ret);
return ret;
}
np = of_parse_phandle(lvds->dev->of_node, "backlight", 0);
if (np) {
lvds->backlight = of_find_backlight_by_node(np);
of_node_put(np);
if (!lvds->backlight)
return -EPROBE_DEFER;
}
/*
* TODO: Handle all power supplies specified in the DT node in a generic
* way for panels that don't care about power supply ordering. LVDS
* panels that require a specific power sequence will need a dedicated
* driver.
*/
/* Register the panel. */
drm_panel_init(&lvds->panel);
lvds->panel.dev = lvds->dev;
lvds->panel.funcs = &panel_lvds_funcs;
ret = drm_panel_add(&lvds->panel);
if (ret < 0)
goto error;
dev_set_drvdata(lvds->dev, lvds);
return 0;
error:
put_device(&lvds->backlight->dev);
return ret;
}
static int panel_lvds_remove(struct platform_device *pdev)
{
struct panel_lvds *lvds = dev_get_drvdata(&pdev->dev);
drm_panel_detach(&lvds->panel);
drm_panel_remove(&lvds->panel);
panel_lvds_disable(&lvds->panel);
if (lvds->backlight)
put_device(&lvds->backlight->dev);
return 0;
}
static const struct of_device_id panel_lvds_of_table[] = {
{ .compatible = "panel-lvds", },
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, panel_lvds_of_table);
static struct platform_driver panel_lvds_driver = {
.probe = panel_lvds_probe,
.remove = panel_lvds_remove,
.driver = {
.name = "panel-lvds",
.of_match_table = panel_lvds_of_table,
},
};
module_platform_driver(panel_lvds_driver);
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
MODULE_DESCRIPTION("LVDS Panel Driver");
MODULE_LICENSE("GPL");
...@@ -11,15 +11,17 @@ config DRM_RCAR_DU ...@@ -11,15 +11,17 @@ 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_HDMI config DRM_RCAR_DW_HDMI
bool "R-Car DU HDMI Encoder Support" tristate "R-Car DU Gen3 HDMI Encoder Support"
depends on DRM_RCAR_DU depends on DRM && OF
select DRM_DW_HDMI
help help
Enable support for external HDMI encoders. Enable support for R-Car Gen3 internal HDMI encoder.
config DRM_RCAR_LVDS config DRM_RCAR_LVDS
bool "R-Car DU LVDS Encoder Support" bool "R-Car DU LVDS Encoder Support"
depends on DRM_RCAR_DU depends on DRM_RCAR_DU
select DRM_PANEL
help help
Enable support for the R-Car Display Unit embedded LVDS encoders. Enable support for the R-Car Display Unit embedded LVDS encoders.
......
...@@ -4,13 +4,11 @@ rcar-du-drm-y := rcar_du_crtc.o \ ...@@ -4,13 +4,11 @@ rcar-du-drm-y := rcar_du_crtc.o \
rcar_du_group.o \ rcar_du_group.o \
rcar_du_kms.o \ rcar_du_kms.o \
rcar_du_lvdscon.o \ rcar_du_lvdscon.o \
rcar_du_plane.o \ rcar_du_plane.o
rcar_du_vgacon.o
rcar-du-drm-$(CONFIG_DRM_RCAR_HDMI) += rcar_du_hdmienc.o
rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.o
rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.o rcar-du-drm-$(CONFIG_DRM_RCAR_VSP) += rcar_du_vsp.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
...@@ -106,9 +106,62 @@ static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc) ...@@ -106,9 +106,62 @@ static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc)
* Hardware Setup * Hardware Setup
*/ */
struct dpll_info {
unsigned int output;
unsigned int fdpll;
unsigned int n;
unsigned int m;
};
static void rcar_du_dpll_divider(struct rcar_du_crtc *rcrtc,
struct dpll_info *dpll,
unsigned long input,
unsigned long target)
{
unsigned long best_diff = (unsigned long)-1;
unsigned long diff;
unsigned int fdpll;
unsigned int m;
unsigned int n;
for (n = 39; n < 120; n++) {
for (m = 0; m < 4; m++) {
for (fdpll = 1; fdpll < 32; fdpll++) {
unsigned long output;
/* 1/2 (FRQSEL=1) for duty rate 50% */
output = input * (n + 1) / (m + 1)
/ (fdpll + 1) / 2;
if (output >= 400000000)
continue;
diff = abs((long)output - (long)target);
if (best_diff > diff) {
best_diff = diff;
dpll->n = n;
dpll->m = m;
dpll->fdpll = fdpll;
dpll->output = output;
}
if (diff == 0)
goto done;
}
}
}
done:
dev_dbg(rcrtc->group->dev->dev,
"output:%u, fdpll:%u, n:%u, m:%u, diff:%lu\n",
dpll->output, dpll->fdpll, dpll->n, dpll->m,
best_diff);
}
static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
{ {
const struct drm_display_mode *mode = &rcrtc->crtc.state->adjusted_mode; const struct drm_display_mode *mode = &rcrtc->crtc.state->adjusted_mode;
struct rcar_du_device *rcdu = rcrtc->group->dev;
unsigned long mode_clock = mode->clock * 1000; unsigned long mode_clock = mode->clock * 1000;
unsigned long clk; unsigned long clk;
u32 value; u32 value;
...@@ -124,12 +177,18 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) ...@@ -124,12 +177,18 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
escr = div | ESCR_DCLKSEL_CLKS; escr = div | ESCR_DCLKSEL_CLKS;
if (rcrtc->extclock) { if (rcrtc->extclock) {
struct dpll_info dpll = { 0 };
unsigned long extclk; unsigned long extclk;
unsigned long extrate; unsigned long extrate;
unsigned long rate; unsigned long rate;
u32 extdiv; u32 extdiv;
extclk = clk_get_rate(rcrtc->extclock); extclk = clk_get_rate(rcrtc->extclock);
if (rcdu->info->dpll_ch & (1 << rcrtc->index)) {
rcar_du_dpll_divider(rcrtc, &dpll, extclk, mode_clock);
extclk = dpll.output;
}
extdiv = DIV_ROUND_CLOSEST(extclk, mode_clock); extdiv = DIV_ROUND_CLOSEST(extclk, mode_clock);
extdiv = clamp(extdiv, 1U, 64U) - 1; extdiv = clamp(extdiv, 1U, 64U) - 1;
...@@ -140,7 +199,27 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) ...@@ -140,7 +199,27 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc)
abs((long)rate - (long)mode_clock)) { abs((long)rate - (long)mode_clock)) {
dev_dbg(rcrtc->group->dev->dev, dev_dbg(rcrtc->group->dev->dev,
"crtc%u: using external clock\n", rcrtc->index); "crtc%u: using external clock\n", rcrtc->index);
escr = extdiv | ESCR_DCLKSEL_DCLKIN;
if (rcdu->info->dpll_ch & (1 << rcrtc->index)) {
u32 dpllcr = DPLLCR_CODE | DPLLCR_CLKE
| DPLLCR_FDPLL(dpll.fdpll)
| DPLLCR_N(dpll.n) | DPLLCR_M(dpll.m)
| DPLLCR_STBY;
if (rcrtc->index == 1)
dpllcr |= DPLLCR_PLCS1
| DPLLCR_INCS_DOTCLKIN1;
else
dpllcr |= DPLLCR_PLCS0
| DPLLCR_INCS_DOTCLKIN0;
rcar_du_group_write(rcrtc->group, DPLLCR,
dpllcr);
escr = ESCR_DCLKSEL_DCLKIN | 1;
} else {
escr = ESCR_DCLKSEL_DCLKIN | extdiv;
}
} }
} }
...@@ -488,22 +567,29 @@ static void rcar_du_crtc_disable(struct drm_crtc *crtc) ...@@ -488,22 +567,29 @@ static void rcar_du_crtc_disable(struct drm_crtc *crtc)
rcar_du_crtc_stop(rcrtc); rcar_du_crtc_stop(rcrtc);
rcar_du_crtc_put(rcrtc); rcar_du_crtc_put(rcrtc);
spin_lock_irq(&crtc->dev->event_lock);
if (crtc->state->event) {
drm_crtc_send_vblank_event(crtc, crtc->state->event);
crtc->state->event = NULL;
}
spin_unlock_irq(&crtc->dev->event_lock);
rcrtc->outputs = 0; rcrtc->outputs = 0;
} }
static void rcar_du_crtc_atomic_begin(struct drm_crtc *crtc, static void rcar_du_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state) struct drm_crtc_state *old_crtc_state)
{ {
struct drm_pending_vblank_event *event = crtc->state->event;
struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc); struct rcar_du_crtc *rcrtc = to_rcar_crtc(crtc);
struct drm_device *dev = rcrtc->crtc.dev; struct drm_device *dev = rcrtc->crtc.dev;
unsigned long flags; unsigned long flags;
if (event) { if (crtc->state->event) {
WARN_ON(drm_crtc_vblank_get(crtc) != 0); WARN_ON(drm_crtc_vblank_get(crtc) != 0);
spin_lock_irqsave(&dev->event_lock, flags); spin_lock_irqsave(&dev->event_lock, flags);
rcrtc->event = event; rcrtc->event = crtc->state->event;
crtc->state->event = NULL;
spin_unlock_irqrestore(&dev->event_lock, flags); spin_unlock_irqrestore(&dev->event_lock, flags);
} }
......
/* /*
* rcar_du_crtc.h -- R-Car Display Unit CRTCs * rcar_du_crtc.h -- R-Car Display Unit CRTCs
* *
* Copyright (C) 2013-2014 Renesas Electronics Corporation * Copyright (C) 2013-2015 Renesas Electronics Corporation
* *
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com) * Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
* *
...@@ -61,6 +61,8 @@ enum rcar_du_output { ...@@ -61,6 +61,8 @@ enum rcar_du_output {
RCAR_DU_OUTPUT_DPAD1, RCAR_DU_OUTPUT_DPAD1,
RCAR_DU_OUTPUT_LVDS0, RCAR_DU_OUTPUT_LVDS0,
RCAR_DU_OUTPUT_LVDS1, RCAR_DU_OUTPUT_LVDS1,
RCAR_DU_OUTPUT_HDMI0,
RCAR_DU_OUTPUT_HDMI1,
RCAR_DU_OUTPUT_TCON, RCAR_DU_OUTPUT_TCON,
RCAR_DU_OUTPUT_MAX, RCAR_DU_OUTPUT_MAX,
}; };
......
...@@ -44,12 +44,10 @@ static const struct rcar_du_device_info rcar_du_r8a7779_info = { ...@@ -44,12 +44,10 @@ static const struct rcar_du_device_info rcar_du_r8a7779_info = {
*/ */
[RCAR_DU_OUTPUT_DPAD0] = { [RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0), .possible_crtcs = BIT(0),
.encoder_type = DRM_MODE_ENCODER_NONE,
.port = 0, .port = 0,
}, },
[RCAR_DU_OUTPUT_DPAD1] = { [RCAR_DU_OUTPUT_DPAD1] = {
.possible_crtcs = BIT(1) | BIT(0), .possible_crtcs = BIT(1) | BIT(0),
.encoder_type = DRM_MODE_ENCODER_NONE,
.port = 1, .port = 1,
}, },
}, },
...@@ -68,17 +66,14 @@ static const struct rcar_du_device_info rcar_du_r8a7790_info = { ...@@ -68,17 +66,14 @@ static const struct rcar_du_device_info rcar_du_r8a7790_info = {
*/ */
[RCAR_DU_OUTPUT_DPAD0] = { [RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(2) | BIT(1) | BIT(0), .possible_crtcs = BIT(2) | BIT(1) | BIT(0),
.encoder_type = DRM_MODE_ENCODER_NONE,
.port = 0, .port = 0,
}, },
[RCAR_DU_OUTPUT_LVDS0] = { [RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0), .possible_crtcs = BIT(0),
.encoder_type = DRM_MODE_ENCODER_LVDS,
.port = 1, .port = 1,
}, },
[RCAR_DU_OUTPUT_LVDS1] = { [RCAR_DU_OUTPUT_LVDS1] = {
.possible_crtcs = BIT(2) | BIT(1), .possible_crtcs = BIT(2) | BIT(1),
.encoder_type = DRM_MODE_ENCODER_LVDS,
.port = 2, .port = 2,
}, },
}, },
...@@ -97,12 +92,10 @@ static const struct rcar_du_device_info rcar_du_r8a7791_info = { ...@@ -97,12 +92,10 @@ static const struct rcar_du_device_info rcar_du_r8a7791_info = {
*/ */
[RCAR_DU_OUTPUT_DPAD0] = { [RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(1) | BIT(0), .possible_crtcs = BIT(1) | BIT(0),
.encoder_type = DRM_MODE_ENCODER_NONE,
.port = 0, .port = 0,
}, },
[RCAR_DU_OUTPUT_LVDS0] = { [RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0), .possible_crtcs = BIT(0),
.encoder_type = DRM_MODE_ENCODER_LVDS,
.port = 1, .port = 1,
}, },
}, },
...@@ -118,12 +111,10 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = { ...@@ -118,12 +111,10 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = {
/* R8A7792 has two RGB outputs. */ /* R8A7792 has two RGB outputs. */
[RCAR_DU_OUTPUT_DPAD0] = { [RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0), .possible_crtcs = BIT(0),
.encoder_type = DRM_MODE_ENCODER_NONE,
.port = 0, .port = 0,
}, },
[RCAR_DU_OUTPUT_DPAD1] = { [RCAR_DU_OUTPUT_DPAD1] = {
.possible_crtcs = BIT(1), .possible_crtcs = BIT(1),
.encoder_type = DRM_MODE_ENCODER_NONE,
.port = 1, .port = 1,
}, },
}, },
...@@ -141,12 +132,10 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = { ...@@ -141,12 +132,10 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = {
*/ */
[RCAR_DU_OUTPUT_DPAD0] = { [RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(0), .possible_crtcs = BIT(0),
.encoder_type = DRM_MODE_ENCODER_NONE,
.port = 0, .port = 0,
}, },
[RCAR_DU_OUTPUT_DPAD1] = { [RCAR_DU_OUTPUT_DPAD1] = {
.possible_crtcs = BIT(1), .possible_crtcs = BIT(1),
.encoder_type = DRM_MODE_ENCODER_NONE,
.port = 1, .port = 1,
}, },
}, },
...@@ -160,21 +149,28 @@ static const struct rcar_du_device_info rcar_du_r8a7795_info = { ...@@ -160,21 +149,28 @@ static const struct rcar_du_device_info rcar_du_r8a7795_info = {
| RCAR_DU_FEATURE_VSP1_SOURCE, | RCAR_DU_FEATURE_VSP1_SOURCE,
.num_crtcs = 4, .num_crtcs = 4,
.routes = { .routes = {
/* R8A7795 has one RGB output, one LVDS output and two /* R8A7795 has one RGB output, two HDMI outputs and one
* (currently unsupported) HDMI outputs. * LVDS output.
*/ */
[RCAR_DU_OUTPUT_DPAD0] = { [RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(3), .possible_crtcs = BIT(3),
.encoder_type = DRM_MODE_ENCODER_NONE,
.port = 0, .port = 0,
}, },
[RCAR_DU_OUTPUT_HDMI0] = {
.possible_crtcs = BIT(1),
.port = 1,
},
[RCAR_DU_OUTPUT_HDMI1] = {
.possible_crtcs = BIT(2),
.port = 2,
},
[RCAR_DU_OUTPUT_LVDS0] = { [RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0), .possible_crtcs = BIT(0),
.encoder_type = DRM_MODE_ENCODER_LVDS,
.port = 3, .port = 3,
}, },
}, },
.num_lvds = 1, .num_lvds = 1,
.dpll_ch = BIT(1) | BIT(2),
}; };
static const struct rcar_du_device_info rcar_du_r8a7796_info = { static const struct rcar_du_device_info rcar_du_r8a7796_info = {
...@@ -189,12 +185,10 @@ static const struct rcar_du_device_info rcar_du_r8a7796_info = { ...@@ -189,12 +185,10 @@ static const struct rcar_du_device_info rcar_du_r8a7796_info = {
*/ */
[RCAR_DU_OUTPUT_DPAD0] = { [RCAR_DU_OUTPUT_DPAD0] = {
.possible_crtcs = BIT(2), .possible_crtcs = BIT(2),
.encoder_type = DRM_MODE_ENCODER_NONE,
.port = 0, .port = 0,
}, },
[RCAR_DU_OUTPUT_LVDS0] = { [RCAR_DU_OUTPUT_LVDS0] = {
.possible_crtcs = BIT(0), .possible_crtcs = BIT(0),
.encoder_type = DRM_MODE_ENCODER_LVDS,
.port = 2, .port = 2,
}, },
}, },
...@@ -318,10 +312,8 @@ static int rcar_du_probe(struct platform_device *pdev) ...@@ -318,10 +312,8 @@ static int rcar_du_probe(struct platform_device *pdev)
if (rcdu == NULL) if (rcdu == NULL)
return -ENOMEM; return -ENOMEM;
init_waitqueue_head(&rcdu->commit.wait);
rcdu->dev = &pdev->dev; rcdu->dev = &pdev->dev;
rcdu->info = of_match_device(rcar_du_of_table, rcdu->dev)->data; rcdu->info = of_device_get_match_data(rcdu->dev);
platform_set_drvdata(pdev, rcdu); platform_set_drvdata(pdev, rcdu);
......
...@@ -38,7 +38,6 @@ struct rcar_du_lvdsenc; ...@@ -38,7 +38,6 @@ struct rcar_du_lvdsenc;
/* /*
* struct rcar_du_output_routing - Output routing specification * struct rcar_du_output_routing - Output routing specification
* @possible_crtcs: bitmask of possible CRTCs for the output * @possible_crtcs: bitmask of possible CRTCs for the output
* @encoder_type: DRM type of the internal encoder associated with the output
* @port: device tree port number corresponding to this output route * @port: device tree port number corresponding to this output route
* *
* The DU has 5 possible outputs (DPAD0/1, LVDS0/1, TCON). Output routing data * The DU has 5 possible outputs (DPAD0/1, LVDS0/1, TCON). Output routing data
...@@ -47,7 +46,6 @@ struct rcar_du_lvdsenc; ...@@ -47,7 +46,6 @@ struct rcar_du_lvdsenc;
*/ */
struct rcar_du_output_routing { struct rcar_du_output_routing {
unsigned int possible_crtcs; unsigned int possible_crtcs;
unsigned int encoder_type;
unsigned int port; unsigned int port;
}; };
...@@ -67,6 +65,7 @@ struct rcar_du_device_info { ...@@ -67,6 +65,7 @@ struct rcar_du_device_info {
unsigned int num_crtcs; unsigned int num_crtcs;
struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX]; struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX];
unsigned int num_lvds; unsigned int num_lvds;
unsigned int dpll_ch;
}; };
#define RCAR_DU_MAX_CRTCS 4 #define RCAR_DU_MAX_CRTCS 4
...@@ -98,11 +97,6 @@ struct rcar_du_device { ...@@ -98,11 +97,6 @@ struct rcar_du_device {
unsigned int vspd1_sink; unsigned int vspd1_sink;
struct rcar_du_lvdsenc *lvds[RCAR_DU_MAX_LVDS]; struct rcar_du_lvdsenc *lvds[RCAR_DU_MAX_LVDS];
struct {
wait_queue_head_t wait;
u32 pending;
} commit;
}; };
static inline bool rcar_du_has(struct rcar_du_device *rcdu, static inline bool rcar_du_has(struct rcar_du_device *rcdu,
......
...@@ -16,14 +16,13 @@ ...@@ -16,14 +16,13 @@
#include <drm/drmP.h> #include <drm/drmP.h>
#include <drm/drm_crtc.h> #include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h> #include <drm/drm_crtc_helper.h>
#include <drm/drm_panel.h>
#include "rcar_du_drv.h" #include "rcar_du_drv.h"
#include "rcar_du_encoder.h" #include "rcar_du_encoder.h"
#include "rcar_du_hdmienc.h"
#include "rcar_du_kms.h" #include "rcar_du_kms.h"
#include "rcar_du_lvdscon.h" #include "rcar_du_lvdscon.h"
#include "rcar_du_lvdsenc.h" #include "rcar_du_lvdsenc.h"
#include "rcar_du_vgacon.h"
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Encoder * Encoder
...@@ -33,6 +32,11 @@ static void rcar_du_encoder_disable(struct drm_encoder *encoder) ...@@ -33,6 +32,11 @@ static void rcar_du_encoder_disable(struct drm_encoder *encoder)
{ {
struct rcar_du_encoder *renc = to_rcar_encoder(encoder); struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
if (renc->connector && renc->connector->panel) {
drm_panel_disable(renc->connector->panel);
drm_panel_unprepare(renc->connector->panel);
}
if (renc->lvds) if (renc->lvds)
rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, false); rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, false);
} }
...@@ -43,6 +47,11 @@ static void rcar_du_encoder_enable(struct drm_encoder *encoder) ...@@ -43,6 +47,11 @@ static void rcar_du_encoder_enable(struct drm_encoder *encoder)
if (renc->lvds) if (renc->lvds)
rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, true); rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, true);
if (renc->connector && renc->connector->panel) {
drm_panel_prepare(renc->connector->panel);
drm_panel_enable(renc->connector->panel);
}
} }
static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder, static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder,
...@@ -52,30 +61,36 @@ static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder, ...@@ -52,30 +61,36 @@ static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder,
struct rcar_du_encoder *renc = to_rcar_encoder(encoder); struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode; struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
const struct drm_display_mode *mode = &crtc_state->mode; const struct drm_display_mode *mode = &crtc_state->mode;
const struct drm_display_mode *panel_mode;
struct drm_connector *connector = conn_state->connector; struct drm_connector *connector = conn_state->connector;
struct drm_device *dev = encoder->dev; struct drm_device *dev = encoder->dev;
/* DAC encoders have currently no restriction on the mode. */ /*
if (encoder->encoder_type == DRM_MODE_ENCODER_DAC) * Only panel-related encoder types require validation here, everything
return 0; * else is handled by the bridge drivers.
*/
if (list_empty(&connector->modes)) { if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) {
dev_dbg(dev->dev, "encoder: empty modes list\n"); const struct drm_display_mode *panel_mode;
return -EINVAL;
if (list_empty(&connector->modes)) {
dev_dbg(dev->dev, "encoder: empty modes list\n");
return -EINVAL;
}
panel_mode = list_first_entry(&connector->modes,
struct drm_display_mode, head);
/* We're not allowed to modify the resolution. */
if (mode->hdisplay != panel_mode->hdisplay ||
mode->vdisplay != panel_mode->vdisplay)
return -EINVAL;
/*
* The flat panel mode is fixed, just copy it to the adjusted
* mode.
*/
drm_mode_copy(adjusted_mode, panel_mode);
} }
panel_mode = list_first_entry(&connector->modes,
struct drm_display_mode, head);
/* We're not allowed to modify the resolution. */
if (mode->hdisplay != panel_mode->hdisplay ||
mode->vdisplay != panel_mode->vdisplay)
return -EINVAL;
/* The flat panel mode is fixed, just copy it to the adjusted mode. */
drm_mode_copy(adjusted_mode, panel_mode);
if (renc->lvds) if (renc->lvds)
rcar_du_lvdsenc_atomic_check(renc->lvds, adjusted_mode); rcar_du_lvdsenc_atomic_check(renc->lvds, adjusted_mode);
...@@ -83,16 +98,54 @@ static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder, ...@@ -83,16 +98,54 @@ static int rcar_du_encoder_atomic_check(struct drm_encoder *encoder,
} }
static void rcar_du_encoder_mode_set(struct drm_encoder *encoder, static void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode, struct drm_crtc_state *crtc_state,
struct drm_display_mode *adjusted_mode) struct drm_connector_state *conn_state)
{ {
struct rcar_du_encoder *renc = to_rcar_encoder(encoder); struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
struct drm_display_info *info = &conn_state->connector->display_info;
enum rcar_lvds_mode mode;
rcar_du_crtc_route_output(crtc_state->crtc, renc->output);
if (!renc->lvds) {
/*
* The DU driver creates connectors only for the outputs of the
* internal LVDS encoders.
*/
renc->connector = NULL;
return;
}
renc->connector = to_rcar_connector(conn_state->connector);
if (!info->num_bus_formats || !info->bus_formats) {
dev_err(encoder->dev->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(encoder->dev->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;
rcar_du_crtc_route_output(encoder->crtc, renc->output); rcar_du_lvdsenc_set_mode(renc->lvds, mode);
} }
static const struct drm_encoder_helper_funcs encoder_helper_funcs = { static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
.mode_set = rcar_du_encoder_mode_set, .atomic_mode_set = rcar_du_encoder_mode_set,
.disable = rcar_du_encoder_disable, .disable = rcar_du_encoder_disable,
.enable = rcar_du_encoder_enable, .enable = rcar_du_encoder_enable,
.atomic_check = rcar_du_encoder_atomic_check, .atomic_check = rcar_du_encoder_atomic_check,
...@@ -103,14 +156,13 @@ static const struct drm_encoder_funcs encoder_funcs = { ...@@ -103,14 +156,13 @@ static const struct drm_encoder_funcs encoder_funcs = {
}; };
int rcar_du_encoder_init(struct rcar_du_device *rcdu, int rcar_du_encoder_init(struct rcar_du_device *rcdu,
enum rcar_du_encoder_type type,
enum rcar_du_output output, enum rcar_du_output output,
struct device_node *enc_node, struct device_node *enc_node,
struct device_node *con_node) struct device_node *con_node)
{ {
struct rcar_du_encoder *renc; struct rcar_du_encoder *renc;
struct drm_encoder *encoder; struct drm_encoder *encoder;
unsigned int encoder_type; struct drm_bridge *bridge = NULL;
int ret; int ret;
renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL); renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL);
...@@ -133,52 +185,51 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, ...@@ -133,52 +185,51 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
break; break;
} }
switch (type) { if (enc_node) {
case RCAR_DU_ENCODER_VGA: dev_dbg(rcdu->dev, "initializing encoder %s for output %u\n",
encoder_type = DRM_MODE_ENCODER_DAC; of_node_full_name(enc_node), output);
break;
case RCAR_DU_ENCODER_LVDS:
encoder_type = DRM_MODE_ENCODER_LVDS;
break;
case RCAR_DU_ENCODER_HDMI:
encoder_type = DRM_MODE_ENCODER_TMDS;
break;
case RCAR_DU_ENCODER_NONE:
default:
/* No external encoder, use the internal encoder type. */
encoder_type = rcdu->info->routes[output].encoder_type;
break;
}
if (type == RCAR_DU_ENCODER_HDMI) { /* Locate the DRM bridge from the encoder DT node. */
ret = rcar_du_hdmienc_init(rcdu, renc, enc_node); bridge = of_drm_find_bridge(enc_node);
if (ret < 0) if (!bridge) {
ret = -EPROBE_DEFER;
goto done; goto done;
}
} else { } else {
ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs, dev_dbg(rcdu->dev,
encoder_type, NULL); "initializing internal encoder for output %u\n",
if (ret < 0) output);
goto done;
drm_encoder_helper_add(encoder, &encoder_helper_funcs);
} }
switch (encoder_type) { ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
case DRM_MODE_ENCODER_LVDS: DRM_MODE_ENCODER_NONE, NULL);
ret = rcar_du_lvds_connector_init(rcdu, renc, con_node); if (ret < 0)
break; goto done;
case DRM_MODE_ENCODER_DAC: drm_encoder_helper_add(encoder, &encoder_helper_funcs);
ret = rcar_du_vga_connector_init(rcdu, renc);
break; if (bridge) {
/*
case DRM_MODE_ENCODER_TMDS: * Attach the bridge to the encoder. The bridge will create the
/* connector managed by the bridge driver */ * connector.
break; */
ret = drm_bridge_attach(encoder, bridge, NULL);
default: if (ret) {
ret = -EINVAL; drm_encoder_cleanup(encoder);
break; return ret;
}
} else {
/* There's no bridge, create the connector manually. */
switch (output) {
case RCAR_DU_OUTPUT_LVDS0:
case RCAR_DU_OUTPUT_LVDS1:
ret = rcar_du_lvds_connector_init(rcdu, renc, con_node);
break;
default:
ret = -EINVAL;
break;
}
} }
done: done:
......
...@@ -17,22 +17,14 @@ ...@@ -17,22 +17,14 @@
#include <drm/drm_crtc.h> #include <drm/drm_crtc.h>
#include <drm/drm_encoder.h> #include <drm/drm_encoder.h>
struct drm_panel;
struct rcar_du_device; struct rcar_du_device;
struct rcar_du_hdmienc;
struct rcar_du_lvdsenc; struct rcar_du_lvdsenc;
enum rcar_du_encoder_type {
RCAR_DU_ENCODER_UNUSED = 0,
RCAR_DU_ENCODER_NONE,
RCAR_DU_ENCODER_VGA,
RCAR_DU_ENCODER_LVDS,
RCAR_DU_ENCODER_HDMI,
};
struct rcar_du_encoder { struct rcar_du_encoder {
struct drm_encoder base; struct drm_encoder base;
enum rcar_du_output output; enum rcar_du_output output;
struct rcar_du_hdmienc *hdmi; struct rcar_du_connector *connector;
struct rcar_du_lvdsenc *lvds; struct rcar_du_lvdsenc *lvds;
}; };
...@@ -44,13 +36,13 @@ struct rcar_du_encoder { ...@@ -44,13 +36,13 @@ struct rcar_du_encoder {
struct rcar_du_connector { struct rcar_du_connector {
struct drm_connector connector; struct drm_connector connector;
struct rcar_du_encoder *encoder; struct rcar_du_encoder *encoder;
struct drm_panel *panel;
}; };
#define to_rcar_connector(c) \ #define to_rcar_connector(c) \
container_of(c, struct rcar_du_connector, connector) container_of(c, struct rcar_du_connector, connector)
int rcar_du_encoder_init(struct rcar_du_device *rcdu, int rcar_du_encoder_init(struct rcar_du_device *rcdu,
enum rcar_du_encoder_type type,
enum rcar_du_output output, enum rcar_du_output output,
struct device_node *enc_node, struct device_node *enc_node,
struct device_node *con_node); struct device_node *con_node);
......
/*
* R-Car Display Unit HDMI Encoder
*
* Copyright (C) 2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/slab.h>
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include "rcar_du_drv.h"
#include "rcar_du_encoder.h"
#include "rcar_du_hdmienc.h"
#include "rcar_du_lvdsenc.h"
struct rcar_du_hdmienc {
struct rcar_du_encoder *renc;
bool enabled;
};
#define to_rcar_hdmienc(e) (to_rcar_encoder(e)->hdmi)
static void rcar_du_hdmienc_disable(struct drm_encoder *encoder)
{
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
if (hdmienc->renc->lvds)
rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc,
false);
hdmienc->enabled = false;
}
static void rcar_du_hdmienc_enable(struct drm_encoder *encoder)
{
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
if (hdmienc->renc->lvds)
rcar_du_lvdsenc_enable(hdmienc->renc->lvds, encoder->crtc,
true);
hdmienc->enabled = true;
}
static int rcar_du_hdmienc_atomic_check(struct drm_encoder *encoder,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
struct drm_display_mode *adjusted_mode = &crtc_state->adjusted_mode;
if (hdmienc->renc->lvds)
rcar_du_lvdsenc_atomic_check(hdmienc->renc->lvds,
adjusted_mode);
return 0;
}
static void rcar_du_hdmienc_mode_set(struct drm_encoder *encoder,
struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
rcar_du_crtc_route_output(encoder->crtc, hdmienc->renc->output);
}
static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
.mode_set = rcar_du_hdmienc_mode_set,
.disable = rcar_du_hdmienc_disable,
.enable = rcar_du_hdmienc_enable,
.atomic_check = rcar_du_hdmienc_atomic_check,
};
static void rcar_du_hdmienc_cleanup(struct drm_encoder *encoder)
{
struct rcar_du_hdmienc *hdmienc = to_rcar_hdmienc(encoder);
if (hdmienc->enabled)
rcar_du_hdmienc_disable(encoder);
drm_encoder_cleanup(encoder);
}
static const struct drm_encoder_funcs encoder_funcs = {
.destroy = rcar_du_hdmienc_cleanup,
};
int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc, struct device_node *np)
{
struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
struct drm_bridge *bridge;
struct rcar_du_hdmienc *hdmienc;
int ret;
hdmienc = devm_kzalloc(rcdu->dev, sizeof(*hdmienc), GFP_KERNEL);
if (hdmienc == NULL)
return -ENOMEM;
/* Locate the DRM bridge from the HDMI encoder DT node. */
bridge = of_drm_find_bridge(np);
if (!bridge)
return -EPROBE_DEFER;
ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs,
DRM_MODE_ENCODER_TMDS, NULL);
if (ret < 0)
return ret;
drm_encoder_helper_add(encoder, &encoder_helper_funcs);
renc->hdmi = hdmienc;
hdmienc->renc = renc;
/* Link the bridge to the encoder. */
ret = drm_bridge_attach(encoder, bridge, NULL);
if (ret) {
drm_encoder_cleanup(encoder);
return ret;
}
return 0;
}
/*
* R-Car Display Unit HDMI Encoder
*
* Copyright (C) 2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef __RCAR_DU_HDMIENC_H__
#define __RCAR_DU_HDMIENC_H__
#include <linux/module.h>
struct device_node;
struct rcar_du_device;
struct rcar_du_encoder;
#if IS_ENABLED(CONFIG_DRM_RCAR_HDMI)
int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc, struct device_node *np);
#else
static inline int rcar_du_hdmienc_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc,
struct device_node *np)
{
return -ENOSYS;
}
#endif
#endif /* __RCAR_DU_HDMIENC_H__ */
...@@ -249,18 +249,9 @@ static int rcar_du_atomic_check(struct drm_device *dev, ...@@ -249,18 +249,9 @@ static int rcar_du_atomic_check(struct drm_device *dev,
return rcar_du_atomic_check_planes(dev, state); return rcar_du_atomic_check_planes(dev, state);
} }
struct rcar_du_commit { static void rcar_du_atomic_commit_tail(struct drm_atomic_state *old_state)
struct work_struct work;
struct drm_device *dev;
struct drm_atomic_state *state;
u32 crtcs;
};
static void rcar_du_atomic_complete(struct rcar_du_commit *commit)
{ {
struct drm_device *dev = commit->dev; struct drm_device *dev = old_state->dev;
struct rcar_du_device *rcdu = dev->dev_private;
struct drm_atomic_state *old_state = commit->state;
/* Apply the atomic update. */ /* Apply the atomic update. */
drm_atomic_helper_commit_modeset_disables(dev, old_state); drm_atomic_helper_commit_modeset_disables(dev, old_state);
...@@ -268,114 +259,31 @@ static void rcar_du_atomic_complete(struct rcar_du_commit *commit) ...@@ -268,114 +259,31 @@ static void rcar_du_atomic_complete(struct rcar_du_commit *commit)
drm_atomic_helper_commit_planes(dev, old_state, drm_atomic_helper_commit_planes(dev, old_state,
DRM_PLANE_COMMIT_ACTIVE_ONLY); DRM_PLANE_COMMIT_ACTIVE_ONLY);
drm_atomic_helper_commit_hw_done(old_state);
drm_atomic_helper_wait_for_vblanks(dev, old_state); drm_atomic_helper_wait_for_vblanks(dev, old_state);
drm_atomic_helper_cleanup_planes(dev, old_state); drm_atomic_helper_cleanup_planes(dev, old_state);
drm_atomic_state_put(old_state);
/* Complete the commit, wake up any waiter. */
spin_lock(&rcdu->commit.wait.lock);
rcdu->commit.pending &= ~commit->crtcs;
wake_up_all_locked(&rcdu->commit.wait);
spin_unlock(&rcdu->commit.wait.lock);
kfree(commit);
}
static void rcar_du_atomic_work(struct work_struct *work)
{
struct rcar_du_commit *commit =
container_of(work, struct rcar_du_commit, work);
rcar_du_atomic_complete(commit);
}
static int rcar_du_atomic_commit(struct drm_device *dev,
struct drm_atomic_state *state,
bool nonblock)
{
struct rcar_du_device *rcdu = dev->dev_private;
struct rcar_du_commit *commit;
struct drm_crtc *crtc;
struct drm_crtc_state *crtc_state;
unsigned int i;
int ret;
ret = drm_atomic_helper_prepare_planes(dev, state);
if (ret)
return ret;
/* Allocate the commit object. */
commit = kzalloc(sizeof(*commit), GFP_KERNEL);
if (commit == NULL) {
ret = -ENOMEM;
goto error;
}
INIT_WORK(&commit->work, rcar_du_atomic_work);
commit->dev = dev;
commit->state = state;
/* Wait until all affected CRTCs have completed previous commits and
* mark them as pending.
*/
for_each_crtc_in_state(state, crtc, crtc_state, i)
commit->crtcs |= drm_crtc_mask(crtc);
spin_lock(&rcdu->commit.wait.lock);
ret = wait_event_interruptible_locked(rcdu->commit.wait,
!(rcdu->commit.pending & commit->crtcs));
if (ret == 0)
rcdu->commit.pending |= commit->crtcs;
spin_unlock(&rcdu->commit.wait.lock);
if (ret) {
kfree(commit);
goto error;
}
/* Swap the state, this is the point of no return. */
drm_atomic_helper_swap_state(state, true);
drm_atomic_state_get(state);
if (nonblock)
schedule_work(&commit->work);
else
rcar_du_atomic_complete(commit);
return 0;
error:
drm_atomic_helper_cleanup_planes(dev, state);
return ret;
} }
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Initialization * Initialization
*/ */
static const struct drm_mode_config_helper_funcs rcar_du_mode_config_helper = {
.atomic_commit_tail = rcar_du_atomic_commit_tail,
};
static const struct drm_mode_config_funcs rcar_du_mode_config_funcs = { static const struct drm_mode_config_funcs rcar_du_mode_config_funcs = {
.fb_create = rcar_du_fb_create, .fb_create = rcar_du_fb_create,
.output_poll_changed = rcar_du_output_poll_changed, .output_poll_changed = rcar_du_output_poll_changed,
.atomic_check = rcar_du_atomic_check, .atomic_check = rcar_du_atomic_check,
.atomic_commit = rcar_du_atomic_commit, .atomic_commit = drm_atomic_helper_commit,
}; };
static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu, static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
enum rcar_du_output output, enum rcar_du_output output,
struct of_endpoint *ep) struct of_endpoint *ep)
{ {
static const struct {
const char *compatible;
enum rcar_du_encoder_type type;
} encoders[] = {
{ "adi,adv7123", RCAR_DU_ENCODER_VGA },
{ "adi,adv7511w", RCAR_DU_ENCODER_HDMI },
{ "thine,thc63lvdm83d", RCAR_DU_ENCODER_LVDS },
};
enum rcar_du_encoder_type enc_type = RCAR_DU_ENCODER_NONE;
struct device_node *connector = NULL; struct device_node *connector = NULL;
struct device_node *encoder = NULL; struct device_node *encoder = NULL;
struct device_node *ep_node = NULL; struct device_node *ep_node = NULL;
...@@ -394,6 +302,13 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu, ...@@ -394,6 +302,13 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
return -ENODEV; return -ENODEV;
} }
if (!of_device_is_available(entity)) {
dev_dbg(rcdu->dev,
"connected entity %s is disabled, skipping\n",
entity->full_name);
return -ENODEV;
}
entity_ep_node = of_parse_phandle(ep->local_node, "remote-endpoint", 0); entity_ep_node = of_parse_phandle(ep->local_node, "remote-endpoint", 0);
for_each_endpoint_of_node(entity, ep_node) { for_each_endpoint_of_node(entity, ep_node) {
...@@ -422,30 +337,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu, ...@@ -422,30 +337,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
of_node_put(entity_ep_node); of_node_put(entity_ep_node);
if (encoder) { if (!encoder) {
/*
* If an encoder has been found, get its type based on its
* compatible string.
*/
unsigned int i;
for (i = 0; i < ARRAY_SIZE(encoders); ++i) {
if (of_device_is_compatible(encoder,
encoders[i].compatible)) {
enc_type = encoders[i].type;
break;
}
}
if (i == ARRAY_SIZE(encoders)) {
dev_warn(rcdu->dev,
"unknown encoder type for %s, skipping\n",
encoder->full_name);
of_node_put(encoder);
of_node_put(connector);
return -EINVAL;
}
} else {
/* /*
* If no encoder has been found the entity must be the * If no encoder has been found the entity must be the
* connector. * connector.
...@@ -453,7 +345,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu, ...@@ -453,7 +345,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu,
connector = entity; connector = entity;
} }
ret = rcar_du_encoder_init(rcdu, enc_type, output, encoder, connector); ret = rcar_du_encoder_init(rcdu, output, encoder, connector);
if (ret && ret != -EPROBE_DEFER) if (ret && ret != -EPROBE_DEFER)
dev_warn(rcdu->dev, dev_warn(rcdu->dev,
"failed to initialize encoder %s on output %u (%d), skipping\n", "failed to initialize encoder %s on output %u (%d), skipping\n",
...@@ -561,6 +453,7 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu) ...@@ -561,6 +453,7 @@ int rcar_du_modeset_init(struct rcar_du_device *rcdu)
dev->mode_config.max_width = 4095; dev->mode_config.max_width = 4095;
dev->mode_config.max_height = 2047; dev->mode_config.max_height = 2047;
dev->mode_config.funcs = &rcar_du_mode_config_funcs; dev->mode_config.funcs = &rcar_du_mode_config_funcs;
dev->mode_config.helper_private = &rcar_du_mode_config_helper;
rcdu->num_crtcs = rcdu->info->num_crtcs; rcdu->num_crtcs = rcdu->info->num_crtcs;
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
#include <drm/drm_atomic_helper.h> #include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h> #include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h> #include <drm/drm_crtc_helper.h>
#include <drm/drm_panel.h>
#include <video/display_timing.h> #include <video/display_timing.h>
#include <video/of_display_timing.h> #include <video/of_display_timing.h>
...@@ -25,47 +26,30 @@ ...@@ -25,47 +26,30 @@
#include "rcar_du_kms.h" #include "rcar_du_kms.h"
#include "rcar_du_lvdscon.h" #include "rcar_du_lvdscon.h"
struct rcar_du_lvds_connector {
struct rcar_du_connector connector;
struct {
unsigned int width_mm; /* Panel width in mm */
unsigned int height_mm; /* Panel height in mm */
struct videomode mode;
} panel;
};
#define to_rcar_lvds_connector(c) \
container_of(c, struct rcar_du_lvds_connector, connector.connector)
static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector) static int rcar_du_lvds_connector_get_modes(struct drm_connector *connector)
{ {
struct rcar_du_lvds_connector *lvdscon = struct rcar_du_connector *rcon = to_rcar_connector(connector);
to_rcar_lvds_connector(connector);
struct drm_display_mode *mode;
mode = drm_mode_create(connector->dev);
if (mode == NULL)
return 0;
mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; return drm_panel_get_modes(rcon->panel);
drm_display_mode_from_videomode(&lvdscon->panel.mode, mode);
drm_mode_probed_add(connector, mode);
return 1;
} }
static const struct drm_connector_helper_funcs connector_helper_funcs = { static const struct drm_connector_helper_funcs connector_helper_funcs = {
.get_modes = rcar_du_lvds_connector_get_modes, .get_modes = rcar_du_lvds_connector_get_modes,
}; };
static void rcar_du_lvds_connector_destroy(struct drm_connector *connector)
{
struct rcar_du_connector *rcon = to_rcar_connector(connector);
drm_panel_detach(rcon->panel);
drm_connector_cleanup(connector);
}
static const struct drm_connector_funcs connector_funcs = { static const struct drm_connector_funcs connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms, .dpms = drm_atomic_helper_connector_dpms,
.reset = drm_atomic_helper_connector_reset, .reset = drm_atomic_helper_connector_reset,
.fill_modes = drm_helper_probe_single_connector_modes, .fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup, .destroy = rcar_du_lvds_connector_destroy,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state, .atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
}; };
...@@ -75,27 +59,19 @@ int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu, ...@@ -75,27 +59,19 @@ int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
const struct device_node *np) const struct device_node *np)
{ {
struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc); struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
struct rcar_du_lvds_connector *lvdscon; struct rcar_du_connector *rcon;
struct drm_connector *connector; struct drm_connector *connector;
struct display_timing timing;
int ret; int ret;
lvdscon = devm_kzalloc(rcdu->dev, sizeof(*lvdscon), GFP_KERNEL); rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL);
if (lvdscon == NULL) if (rcon == NULL)
return -ENOMEM; return -ENOMEM;
ret = of_get_display_timing(np, "panel-timing", &timing); connector = &rcon->connector;
if (ret < 0)
return ret;
videomode_from_timing(&timing, &lvdscon->panel.mode);
of_property_read_u32(np, "width-mm", &lvdscon->panel.width_mm); rcon->panel = of_drm_find_panel(np);
of_property_read_u32(np, "height-mm", &lvdscon->panel.height_mm); if (!rcon->panel)
return -EPROBE_DEFER;
connector = &lvdscon->connector.connector;
connector->display_info.width_mm = lvdscon->panel.width_mm;
connector->display_info.height_mm = lvdscon->panel.height_mm;
ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs, ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
DRM_MODE_CONNECTOR_LVDS); DRM_MODE_CONNECTOR_LVDS);
...@@ -112,7 +88,11 @@ int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu, ...@@ -112,7 +88,11 @@ int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu,
if (ret < 0) if (ret < 0)
return ret; return ret;
lvdscon->connector.encoder = renc; ret = drm_panel_attach(rcon->panel, connector);
if (ret < 0)
return ret;
rcon->encoder = renc;
return 0; return 0;
} }
...@@ -31,6 +31,7 @@ struct rcar_du_lvdsenc { ...@@ -31,6 +31,7 @@ struct rcar_du_lvdsenc {
bool enabled; bool enabled;
enum rcar_lvds_input input; enum rcar_lvds_input input;
enum rcar_lvds_mode mode;
}; };
static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data) static void rcar_lvds_write(struct rcar_du_lvdsenc *lvds, u32 reg, u32 data)
...@@ -61,7 +62,7 @@ static void rcar_du_lvdsenc_start_gen2(struct rcar_du_lvdsenc *lvds, ...@@ -61,7 +62,7 @@ static void rcar_du_lvdsenc_start_gen2(struct rcar_du_lvdsenc *lvds,
/* Select the input, hardcode mode 0, enable LVDS operation and turn /* Select the input, hardcode mode 0, enable LVDS operation and turn
* bias circuitry on. * bias circuitry on.
*/ */
lvdcr0 = LVDCR0_BEN | LVDCR0_LVEN; lvdcr0 = (lvds->mode << LVDCR0_LVMD_SHIFT) | LVDCR0_BEN | LVDCR0_LVEN;
if (rcrtc->index == 2) if (rcrtc->index == 2)
lvdcr0 |= LVDCR0_DUSEL; lvdcr0 |= LVDCR0_DUSEL;
rcar_lvds_write(lvds, LVDCR0, lvdcr0); rcar_lvds_write(lvds, LVDCR0, lvdcr0);
...@@ -114,7 +115,7 @@ static void rcar_du_lvdsenc_start_gen3(struct rcar_du_lvdsenc *lvds, ...@@ -114,7 +115,7 @@ static void rcar_du_lvdsenc_start_gen3(struct rcar_du_lvdsenc *lvds,
* Turn the PLL on, set it to LVDS normal mode, wait for the startup * Turn the PLL on, set it to LVDS normal mode, wait for the startup
* delay and turn the output on. * delay and turn the output on.
*/ */
lvdcr0 = LVDCR0_PLLON; lvdcr0 = (lvds->mode << LVDCR0_LVMD_SHIFT) | LVDCR0_PLLON;
rcar_lvds_write(lvds, LVDCR0, lvdcr0); rcar_lvds_write(lvds, LVDCR0, lvdcr0);
lvdcr0 |= LVDCR0_PWD; lvdcr0 |= LVDCR0_PWD;
...@@ -211,6 +212,12 @@ void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds, ...@@ -211,6 +212,12 @@ void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
mode->clock = clamp(mode->clock, 25175, 148500); mode->clock = clamp(mode->clock, 25175, 148500);
} }
void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
enum rcar_lvds_mode mode)
{
lvds->mode = mode;
}
static int rcar_du_lvdsenc_get_resources(struct rcar_du_lvdsenc *lvds, static int rcar_du_lvdsenc_get_resources(struct rcar_du_lvdsenc *lvds,
struct platform_device *pdev) struct platform_device *pdev)
{ {
......
...@@ -26,8 +26,17 @@ enum rcar_lvds_input { ...@@ -26,8 +26,17 @@ enum rcar_lvds_input {
RCAR_LVDS_INPUT_DU2, RCAR_LVDS_INPUT_DU2,
}; };
/* Keep in sync with the LVDCR0.LVMD hardware register values. */
enum rcar_lvds_mode {
RCAR_LVDS_MODE_JEIDA = 0,
RCAR_LVDS_MODE_MIRROR = 1,
RCAR_LVDS_MODE_VESA = 4,
};
#if IS_ENABLED(CONFIG_DRM_RCAR_LVDS) #if IS_ENABLED(CONFIG_DRM_RCAR_LVDS)
int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu); int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu);
void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
enum rcar_lvds_mode mode);
int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
struct drm_crtc *crtc, bool enable); struct drm_crtc *crtc, bool enable);
void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds, void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds,
...@@ -37,6 +46,10 @@ static inline int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu) ...@@ -37,6 +46,10 @@ static inline int rcar_du_lvdsenc_init(struct rcar_du_device *rcdu)
{ {
return 0; return 0;
} }
static inline void rcar_du_lvdsenc_set_mode(struct rcar_du_lvdsenc *lvds,
enum rcar_lvds_mode mode)
{
}
static inline int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds, static inline int rcar_du_lvdsenc_enable(struct rcar_du_lvdsenc *lvds,
struct drm_crtc *crtc, bool enable) struct drm_crtc *crtc, bool enable)
{ {
......
...@@ -277,6 +277,29 @@ ...@@ -277,6 +277,29 @@
#define DEFR10_TSEL_H3_TCON1 (0 << 1) /* DEFR102 register only (DU2/DU3) */ #define DEFR10_TSEL_H3_TCON1 (0 << 1) /* DEFR102 register only (DU2/DU3) */
#define DEFR10_DEFE10 (1 << 0) #define DEFR10_DEFE10 (1 << 0)
#define DPLLCR 0x20044
#define DPLLCR_CODE (0x95 << 24)
#define DPLLCR_PLCS1 (1 << 23)
/*
* PLCS0 is bit 21, but H3 ES1.x requires bit 20 to be set as well. As bit 20
* isn't implemented by other SoC in the Gen3 family it can safely be set
* unconditionally.
*/
#define DPLLCR_PLCS0 (3 << 20)
#define DPLLCR_CLKE (1 << 18)
#define DPLLCR_FDPLL(n) ((n) << 12)
#define DPLLCR_N(n) ((n) << 5)
#define DPLLCR_M(n) ((n) << 3)
#define DPLLCR_STBY (1 << 2)
#define DPLLCR_INCS_DOTCLKIN0 (0 << 0)
#define DPLLCR_INCS_DOTCLKIN1 (1 << 1)
#define DPLLC2R 0x20048
#define DPLLC2R_CODE (0x95 << 24)
#define DPLLC2R_SELC (1 << 12)
#define DPLLC2R_M(n) ((n) << 8)
#define DPLLC2R_FDPLL(n) ((n) << 0)
/* ----------------------------------------------------------------------------- /* -----------------------------------------------------------------------------
* Display Timing Generation Registers * Display Timing Generation Registers
*/ */
......
/*
* rcar_du_vgacon.c -- R-Car Display Unit VGA Connector
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <drm/drmP.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
#include "rcar_du_drv.h"
#include "rcar_du_encoder.h"
#include "rcar_du_kms.h"
#include "rcar_du_vgacon.h"
static int rcar_du_vga_connector_get_modes(struct drm_connector *connector)
{
return 0;
}
static const struct drm_connector_helper_funcs connector_helper_funcs = {
.get_modes = rcar_du_vga_connector_get_modes,
};
static enum drm_connector_status
rcar_du_vga_connector_detect(struct drm_connector *connector, bool force)
{
return connector_status_connected;
}
static const struct drm_connector_funcs connector_funcs = {
.dpms = drm_atomic_helper_connector_dpms,
.reset = drm_atomic_helper_connector_reset,
.detect = rcar_du_vga_connector_detect,
.fill_modes = drm_helper_probe_single_connector_modes,
.destroy = drm_connector_cleanup,
.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
};
int rcar_du_vga_connector_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc)
{
struct drm_encoder *encoder = rcar_encoder_to_drm_encoder(renc);
struct rcar_du_connector *rcon;
struct drm_connector *connector;
int ret;
rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL);
if (rcon == NULL)
return -ENOMEM;
connector = &rcon->connector;
connector->display_info.width_mm = 0;
connector->display_info.height_mm = 0;
connector->interlace_allowed = true;
ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
DRM_MODE_CONNECTOR_VGA);
if (ret < 0)
return ret;
drm_connector_helper_add(connector, &connector_helper_funcs);
connector->dpms = DRM_MODE_DPMS_OFF;
drm_object_property_set_value(&connector->base,
rcdu->ddev->mode_config.dpms_property, DRM_MODE_DPMS_OFF);
ret = drm_mode_connector_attach_encoder(connector, encoder);
if (ret < 0)
return ret;
return 0;
}
/*
* rcar_du_vgacon.h -- R-Car Display Unit VGA Connector
*
* Copyright (C) 2013-2014 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#ifndef __RCAR_DU_VGACON_H__
#define __RCAR_DU_VGACON_H__
struct rcar_du_device;
struct rcar_du_encoder;
int rcar_du_vga_connector_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc);
#endif /* __RCAR_DU_VGACON_H__ */
...@@ -68,7 +68,7 @@ void rcar_du_vsp_disable(struct rcar_du_crtc *crtc); ...@@ -68,7 +68,7 @@ void rcar_du_vsp_disable(struct rcar_du_crtc *crtc);
void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc); void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc);
void rcar_du_vsp_atomic_flush(struct rcar_du_crtc *crtc); void rcar_du_vsp_atomic_flush(struct rcar_du_crtc *crtc);
#else #else
static inline int rcar_du_vsp_init(struct rcar_du_vsp *vsp) { return 0; }; static inline int rcar_du_vsp_init(struct rcar_du_vsp *vsp) { return -ENXIO; };
static inline void rcar_du_vsp_enable(struct rcar_du_crtc *crtc) { }; static inline void rcar_du_vsp_enable(struct rcar_du_crtc *crtc) { };
static inline void rcar_du_vsp_disable(struct rcar_du_crtc *crtc) { }; static inline void rcar_du_vsp_disable(struct rcar_du_crtc *crtc) { };
static inline void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc) { }; static inline void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc) { };
......
/*
* R-Car Gen3 HDMI PHY
*
* Copyright (C) 2016 Renesas Electronics Corporation
*
* Contact: Laurent Pinchart (laurent.pinchart@ideasonboard.com)
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <drm/bridge/dw_hdmi.h>
#define RCAR_HDMI_PHY_OPMODE_PLLCFG 0x06 /* Mode of operation and PLL dividers */
#define RCAR_HDMI_PHY_PLLCURRGMPCTRL 0x10 /* PLL current and Gmp (conductance) */
#define RCAR_HDMI_PHY_PLLDIVCTRL 0x11 /* PLL dividers */
struct rcar_hdmi_phy_params {
unsigned long mpixelclock;
u16 opmode_div; /* Mode of operation and PLL dividers */
u16 curr_gmp; /* PLL current and Gmp (conductance) */
u16 div; /* PLL dividers */
};
static const struct rcar_hdmi_phy_params rcar_hdmi_phy_params[] = {
{ 35500000, 0x0003, 0x0344, 0x0328 },
{ 44900000, 0x0003, 0x0285, 0x0128 },
{ 71000000, 0x0002, 0x1184, 0x0314 },
{ 90000000, 0x0002, 0x1144, 0x0114 },
{ 140250000, 0x0001, 0x20c4, 0x030a },
{ 182750000, 0x0001, 0x2084, 0x010a },
{ 281250000, 0x0000, 0x0084, 0x0305 },
{ 297000000, 0x0000, 0x0084, 0x0105 },
{ ~0UL, 0x0000, 0x0000, 0x0000 },
};
static int rcar_hdmi_phy_configure(struct dw_hdmi *hdmi,
const struct dw_hdmi_plat_data *pdata,
unsigned long mpixelclock)
{
const struct rcar_hdmi_phy_params *params = rcar_hdmi_phy_params;
for (; params && params->mpixelclock != ~0UL; ++params) {
if (mpixelclock <= params->mpixelclock)
break;
}
if (params->mpixelclock == ~0UL)
return -EINVAL;
dw_hdmi_phy_i2c_write(hdmi, params->opmode_div,
RCAR_HDMI_PHY_OPMODE_PLLCFG);
dw_hdmi_phy_i2c_write(hdmi, params->curr_gmp,
RCAR_HDMI_PHY_PLLCURRGMPCTRL);
dw_hdmi_phy_i2c_write(hdmi, params->div, RCAR_HDMI_PHY_PLLDIVCTRL);
return 0;
}
static const struct dw_hdmi_plat_data rcar_dw_hdmi_plat_data = {
.configure_phy = rcar_hdmi_phy_configure,
};
static int rcar_dw_hdmi_probe(struct platform_device *pdev)
{
return dw_hdmi_probe(pdev, &rcar_dw_hdmi_plat_data);
}
static int rcar_dw_hdmi_remove(struct platform_device *pdev)
{
dw_hdmi_remove(pdev);
return 0;
}
static const struct of_device_id rcar_dw_hdmi_of_table[] = {
{ .compatible = "renesas,rcar-gen3-hdmi" },
{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, rcar_dw_hdmi_of_table);
static struct platform_driver rcar_dw_hdmi_platform_driver = {
.probe = rcar_dw_hdmi_probe,
.remove = rcar_dw_hdmi_remove,
.driver = {
.name = "rcar-dw-hdmi",
.of_match_table = rcar_dw_hdmi_of_table,
},
};
module_platform_driver(rcar_dw_hdmi_platform_driver);
MODULE_AUTHOR("Laurent Pinchart <laurent.pinchart@ideasonboard.com>");
MODULE_DESCRIPTION("Renesas R-Car Gen3 HDMI Encoder Driver");
MODULE_LICENSE("GPL");
...@@ -224,6 +224,10 @@ struct drm_display_info { ...@@ -224,6 +224,10 @@ struct drm_display_info {
#define DRM_BUS_FLAG_PIXDATA_POSEDGE (1<<2) #define DRM_BUS_FLAG_PIXDATA_POSEDGE (1<<2)
/* drive data on neg. edge */ /* drive data on neg. edge */
#define DRM_BUS_FLAG_PIXDATA_NEGEDGE (1<<3) #define DRM_BUS_FLAG_PIXDATA_NEGEDGE (1<<3)
/* data is transmitted MSB to LSB on the bus */
#define DRM_BUS_FLAG_DATA_MSB_TO_LSB (1<<4)
/* data is transmitted LSB to MSB on the bus */
#define DRM_BUS_FLAG_DATA_LSB_TO_MSB (1<<5)
/** /**
* @bus_flags: Additional information (like pixel signal polarity) for * @bus_flags: Additional information (like pixel signal polarity) for
......
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