diff options
author | Dave Airlie <airlied@redhat.com> | 2017-04-05 07:17:12 +1000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2017-04-05 07:17:12 +1000 |
commit | fabe2be10f5f985340bb5459c7ccc8d90e4d56b8 (patch) | |
tree | 707929779c7087d9986c394ee407ee4523bda64a | |
parent | e1b489d207c73e67810659a88c45b8db4bd62773 (diff) | |
parent | 0dda563e571093f309d597cafaf7dd535496ecfb (diff) | |
download | linux-fabe2be10f5f985340bb5459c7ccc8d90e4d56b8.tar.gz linux-fabe2be10f5f985340bb5459c7ccc8d90e4d56b8.tar.xz |
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
...
30 files changed, 1109 insertions, 565 deletions
diff --git a/Documentation/devicetree/bindings/display/bridge/renesas,dw-hdmi.txt b/Documentation/devicetree/bindings/display/bridge/renesas,dw-hdmi.txt new file mode 100644 index 000000000000..f6b3f36d422b --- /dev/null +++ b/Documentation/devicetree/bindings/display/bridge/renesas,dw-hdmi.txt @@ -0,0 +1,75 @@ +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>; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/display/panel/mitsubishi,aa104xd12.txt b/Documentation/devicetree/bindings/display/panel/mitsubishi,aa104xd12.txt new file mode 100644 index 000000000000..ced0121aed7d --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/mitsubishi,aa104xd12.txt @@ -0,0 +1,47 @@ +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>; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/display/panel/mitsubishi,aa121td01.txt b/Documentation/devicetree/bindings/display/panel/mitsubishi,aa121td01.txt new file mode 100644 index 000000000000..d6e1097504fe --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/mitsubishi,aa121td01.txt @@ -0,0 +1,47 @@ +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>; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/display/panel/panel-common.txt b/Documentation/devicetree/bindings/display/panel/panel-common.txt new file mode 100644 index 000000000000..ec52c472c845 --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/panel-common.txt @@ -0,0 +1,91 @@ +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. diff --git a/Documentation/devicetree/bindings/display/panel/panel-lvds.txt b/Documentation/devicetree/bindings/display/panel/panel-lvds.txt new file mode 100644 index 000000000000..b938269f841e --- /dev/null +++ b/Documentation/devicetree/bindings/display/panel/panel-lvds.txt @@ -0,0 +1,120 @@ +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>; + }; + }; +}; diff --git a/Documentation/devicetree/bindings/display/renesas,du.txt b/Documentation/devicetree/bindings/display/renesas,du.txt index 1a02f099a0ff..c6cb96a4fa93 100644 --- a/Documentation/devicetree/bindings/display/renesas,du.txt +++ b/Documentation/devicetree/bindings/display/renesas,du.txt @@ -36,6 +36,9 @@ Required Properties: When supplied they must be named "dclkin.x" with "x" being the input clock numerical index. + - vsps: A list of phandles to the VSP nodes that handle the memory + interfaces for the DU channels. + Required nodes: The connections to the DU output video ports are modeled using the OF graph diff --git a/MAINTAINERS b/MAINTAINERS index fa8479e1799a..bac1a88cdd4a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4383,6 +4383,7 @@ S: Supported F: drivers/gpu/drm/rcar-du/ F: drivers/gpu/drm/shmobile/ 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 DRM DRIVER FOR QXL VIRTUAL GPU diff --git a/drivers/gpu/drm/panel/Kconfig b/drivers/gpu/drm/panel/Kconfig index 62aba976e744..5dc2106da2bc 100644 --- a/drivers/gpu/drm/panel/Kconfig +++ b/drivers/gpu/drm/panel/Kconfig @@ -7,6 +7,16 @@ config DRM_PANEL menu "Display Panels" 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 tristate "support for simple panels" depends on OF diff --git a/drivers/gpu/drm/panel/Makefile b/drivers/gpu/drm/panel/Makefile index a5c7ec0236e0..20b5060d1f47 100644 --- a/drivers/gpu/drm/panel/Makefile +++ b/drivers/gpu/drm/panel/Makefile @@ -1,3 +1,4 @@ +obj-$(CONFIG_DRM_PANEL_LVDS) += panel-lvds.o obj-$(CONFIG_DRM_PANEL_SIMPLE) += panel-simple.o obj-$(CONFIG_DRM_PANEL_JDI_LT070ME05000) += panel-jdi-lt070me05000.o obj-$(CONFIG_DRM_PANEL_LG_LG4573) += panel-lg-lg4573.o diff --git a/drivers/gpu/drm/panel/panel-lvds.c b/drivers/gpu/drm/panel/panel-lvds.c new file mode 100644 index 000000000000..3216aa9a88d6 --- /dev/null +++ b/drivers/gpu/drm/panel/panel-lvds.c @@ -0,0 +1,286 @@ +/* + * 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"); diff --git a/drivers/gpu/drm/rcar-du/Kconfig b/drivers/gpu/drm/rcar-du/Kconfig index 4c2fd056dd6d..8a50dab19e5c 100644 --- a/drivers/gpu/drm/rcar-du/Kconfig +++ b/drivers/gpu/drm/rcar-du/Kconfig @@ -11,15 +11,17 @@ 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_HDMI - bool "R-Car DU HDMI Encoder Support" - depends on DRM_RCAR_DU +config DRM_RCAR_DW_HDMI + tristate "R-Car DU Gen3 HDMI Encoder Support" + depends on DRM && OF + select DRM_DW_HDMI help - Enable support for external HDMI encoders. + Enable support for R-Car Gen3 internal HDMI encoder. config DRM_RCAR_LVDS bool "R-Car DU LVDS Encoder Support" depends on DRM_RCAR_DU + select DRM_PANEL help Enable support for the R-Car Display Unit embedded LVDS encoders. diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile index d3b44651061a..2131e722de3b 100644 --- a/drivers/gpu/drm/rcar-du/Makefile +++ b/drivers/gpu/drm/rcar-du/Makefile @@ -4,13 +4,11 @@ rcar-du-drm-y := rcar_du_crtc.o \ rcar_du_group.o \ rcar_du_kms.o \ rcar_du_lvdscon.o \ - rcar_du_plane.o \ - rcar_du_vgacon.o - -rcar-du-drm-$(CONFIG_DRM_RCAR_HDMI) += rcar_du_hdmienc.o + rcar_du_plane.o rcar-du-drm-$(CONFIG_DRM_RCAR_LVDS) += rcar_du_lvdsenc.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_DW_HDMI) += rcar_dw_hdmi.o diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c index edcbe2e3625d..4ed6f2340af0 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.c @@ -106,9 +106,62 @@ static void rcar_du_crtc_put(struct rcar_du_crtc *rcrtc) * 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) { 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 clk; u32 value; @@ -124,12 +177,18 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) escr = div | ESCR_DCLKSEL_CLKS; if (rcrtc->extclock) { + struct dpll_info dpll = { 0 }; unsigned long extclk; unsigned long extrate; unsigned long rate; u32 extdiv; 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 = clamp(extdiv, 1U, 64U) - 1; @@ -140,7 +199,27 @@ static void rcar_du_crtc_set_display_timing(struct rcar_du_crtc *rcrtc) abs((long)rate - (long)mode_clock)) { dev_dbg(rcrtc->group->dev->dev, "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) rcar_du_crtc_stop(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; } static void rcar_du_crtc_atomic_begin(struct drm_crtc *crtc, 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 drm_device *dev = rcrtc->crtc.dev; unsigned long flags; - if (event) { + if (crtc->state->event) { WARN_ON(drm_crtc_vblank_get(crtc) != 0); 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); } diff --git a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h index a7194812997e..15871fae7445 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_crtc.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_crtc.h @@ -1,7 +1,7 @@ /* * 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) * @@ -61,6 +61,8 @@ enum rcar_du_output { RCAR_DU_OUTPUT_DPAD1, RCAR_DU_OUTPUT_LVDS0, RCAR_DU_OUTPUT_LVDS1, + RCAR_DU_OUTPUT_HDMI0, + RCAR_DU_OUTPUT_HDMI1, RCAR_DU_OUTPUT_TCON, RCAR_DU_OUTPUT_MAX, }; diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c index 62a3b3e32153..d6a0255181cc 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c @@ -44,12 +44,10 @@ static const struct rcar_du_device_info rcar_du_r8a7779_info = { */ [RCAR_DU_OUTPUT_DPAD0] = { .possible_crtcs = BIT(0), - .encoder_type = DRM_MODE_ENCODER_NONE, .port = 0, }, [RCAR_DU_OUTPUT_DPAD1] = { .possible_crtcs = BIT(1) | BIT(0), - .encoder_type = DRM_MODE_ENCODER_NONE, .port = 1, }, }, @@ -68,17 +66,14 @@ static const struct rcar_du_device_info rcar_du_r8a7790_info = { */ [RCAR_DU_OUTPUT_DPAD0] = { .possible_crtcs = BIT(2) | BIT(1) | BIT(0), - .encoder_type = DRM_MODE_ENCODER_NONE, .port = 0, }, [RCAR_DU_OUTPUT_LVDS0] = { .possible_crtcs = BIT(0), - .encoder_type = DRM_MODE_ENCODER_LVDS, .port = 1, }, [RCAR_DU_OUTPUT_LVDS1] = { .possible_crtcs = BIT(2) | BIT(1), - .encoder_type = DRM_MODE_ENCODER_LVDS, .port = 2, }, }, @@ -97,12 +92,10 @@ static const struct rcar_du_device_info rcar_du_r8a7791_info = { */ [RCAR_DU_OUTPUT_DPAD0] = { .possible_crtcs = BIT(1) | BIT(0), - .encoder_type = DRM_MODE_ENCODER_NONE, .port = 0, }, [RCAR_DU_OUTPUT_LVDS0] = { .possible_crtcs = BIT(0), - .encoder_type = DRM_MODE_ENCODER_LVDS, .port = 1, }, }, @@ -118,12 +111,10 @@ static const struct rcar_du_device_info rcar_du_r8a7792_info = { /* R8A7792 has two RGB outputs. */ [RCAR_DU_OUTPUT_DPAD0] = { .possible_crtcs = BIT(0), - .encoder_type = DRM_MODE_ENCODER_NONE, .port = 0, }, [RCAR_DU_OUTPUT_DPAD1] = { .possible_crtcs = BIT(1), - .encoder_type = DRM_MODE_ENCODER_NONE, .port = 1, }, }, @@ -141,12 +132,10 @@ static const struct rcar_du_device_info rcar_du_r8a7794_info = { */ [RCAR_DU_OUTPUT_DPAD0] = { .possible_crtcs = BIT(0), - .encoder_type = DRM_MODE_ENCODER_NONE, .port = 0, }, [RCAR_DU_OUTPUT_DPAD1] = { .possible_crtcs = BIT(1), - .encoder_type = DRM_MODE_ENCODER_NONE, .port = 1, }, }, @@ -160,21 +149,28 @@ static const struct rcar_du_device_info rcar_du_r8a7795_info = { | RCAR_DU_FEATURE_VSP1_SOURCE, .num_crtcs = 4, .routes = { - /* R8A7795 has one RGB output, one LVDS output and two - * (currently unsupported) HDMI outputs. + /* R8A7795 has one RGB output, two HDMI outputs and one + * LVDS output. */ [RCAR_DU_OUTPUT_DPAD0] = { .possible_crtcs = BIT(3), - .encoder_type = DRM_MODE_ENCODER_NONE, .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] = { .possible_crtcs = BIT(0), - .encoder_type = DRM_MODE_ENCODER_LVDS, .port = 3, }, }, .num_lvds = 1, + .dpll_ch = BIT(1) | BIT(2), }; 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] = { .possible_crtcs = BIT(2), - .encoder_type = DRM_MODE_ENCODER_NONE, .port = 0, }, [RCAR_DU_OUTPUT_LVDS0] = { .possible_crtcs = BIT(0), - .encoder_type = DRM_MODE_ENCODER_LVDS, .port = 2, }, }, @@ -318,10 +312,8 @@ static int rcar_du_probe(struct platform_device *pdev) if (rcdu == NULL) return -ENOMEM; - init_waitqueue_head(&rcdu->commit.wait); - 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); diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h index c843c3134498..f8cd79488ece 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h @@ -38,7 +38,6 @@ struct rcar_du_lvdsenc; /* * struct rcar_du_output_routing - Output routing specification * @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 * * The DU has 5 possible outputs (DPAD0/1, LVDS0/1, TCON). Output routing data @@ -47,7 +46,6 @@ struct rcar_du_lvdsenc; */ struct rcar_du_output_routing { unsigned int possible_crtcs; - unsigned int encoder_type; unsigned int port; }; @@ -67,6 +65,7 @@ struct rcar_du_device_info { unsigned int num_crtcs; struct rcar_du_output_routing routes[RCAR_DU_OUTPUT_MAX]; unsigned int num_lvds; + unsigned int dpll_ch; }; #define RCAR_DU_MAX_CRTCS 4 @@ -98,11 +97,6 @@ struct rcar_du_device { unsigned int vspd1_sink; 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, diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c index ab8645c57e2d..3e048dd98b64 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c @@ -16,14 +16,13 @@ #include <drm/drmP.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_panel.h> #include "rcar_du_drv.h" #include "rcar_du_encoder.h" -#include "rcar_du_hdmienc.h" #include "rcar_du_kms.h" #include "rcar_du_lvdscon.h" #include "rcar_du_lvdsenc.h" -#include "rcar_du_vgacon.h" /* ----------------------------------------------------------------------------- * Encoder @@ -33,6 +32,11 @@ static void rcar_du_encoder_disable(struct drm_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) rcar_du_lvdsenc_enable(renc->lvds, encoder->crtc, false); } @@ -43,6 +47,11 @@ static void rcar_du_encoder_enable(struct drm_encoder *encoder) if (renc->lvds) 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, @@ -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 drm_display_mode *adjusted_mode = &crtc_state->adjusted_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_device *dev = encoder->dev; - /* DAC encoders have currently no restriction on the mode. */ - if (encoder->encoder_type == DRM_MODE_ENCODER_DAC) - return 0; - - if (list_empty(&connector->modes)) { - dev_dbg(dev->dev, "encoder: empty modes list\n"); - return -EINVAL; + /* + * Only panel-related encoder types require validation here, everything + * else is handled by the bridge drivers. + */ + if (connector->connector_type == DRM_MODE_CONNECTOR_LVDS) { + const struct drm_display_mode *panel_mode; + + 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) rcar_du_lvdsenc_atomic_check(renc->lvds, adjusted_mode); @@ -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, - struct drm_display_mode *mode, - struct drm_display_mode *adjusted_mode) + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) { 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 = { - .mode_set = rcar_du_encoder_mode_set, + .atomic_mode_set = rcar_du_encoder_mode_set, .disable = rcar_du_encoder_disable, .enable = rcar_du_encoder_enable, .atomic_check = rcar_du_encoder_atomic_check, @@ -103,14 +156,13 @@ static const struct drm_encoder_funcs encoder_funcs = { }; int rcar_du_encoder_init(struct rcar_du_device *rcdu, - enum rcar_du_encoder_type type, enum rcar_du_output output, struct device_node *enc_node, struct device_node *con_node) { struct rcar_du_encoder *renc; struct drm_encoder *encoder; - unsigned int encoder_type; + struct drm_bridge *bridge = NULL; int ret; renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL); @@ -133,52 +185,51 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu, break; } - switch (type) { - case RCAR_DU_ENCODER_VGA: - encoder_type = DRM_MODE_ENCODER_DAC; - 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 (enc_node) { + dev_dbg(rcdu->dev, "initializing encoder %s for output %u\n", + of_node_full_name(enc_node), output); - if (type == RCAR_DU_ENCODER_HDMI) { - ret = rcar_du_hdmienc_init(rcdu, renc, enc_node); - if (ret < 0) + /* Locate the DRM bridge from the encoder DT node. */ + bridge = of_drm_find_bridge(enc_node); + if (!bridge) { + ret = -EPROBE_DEFER; goto done; + } } else { - ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs, - encoder_type, NULL); - if (ret < 0) - goto done; - - drm_encoder_helper_add(encoder, &encoder_helper_funcs); + dev_dbg(rcdu->dev, + "initializing internal encoder for output %u\n", + output); } - switch (encoder_type) { - case DRM_MODE_ENCODER_LVDS: - ret = rcar_du_lvds_connector_init(rcdu, renc, con_node); - break; - - case DRM_MODE_ENCODER_DAC: - ret = rcar_du_vga_connector_init(rcdu, renc); - break; - - case DRM_MODE_ENCODER_TMDS: - /* connector managed by the bridge driver */ - break; - - default: - ret = -EINVAL; - break; + ret = drm_encoder_init(rcdu->ddev, encoder, &encoder_funcs, + DRM_MODE_ENCODER_NONE, NULL); + if (ret < 0) + goto done; + + drm_encoder_helper_add(encoder, &encoder_helper_funcs); + + if (bridge) { + /* + * Attach the bridge to the encoder. The bridge will create the + * connector. + */ + ret = drm_bridge_attach(encoder, bridge, NULL); + if (ret) { + drm_encoder_cleanup(encoder); + 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: diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h index a050a3699857..5422fa4df272 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h @@ -17,22 +17,14 @@ #include <drm/drm_crtc.h> #include <drm/drm_encoder.h> +struct drm_panel; struct rcar_du_device; -struct rcar_du_hdmienc; 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 drm_encoder base; enum rcar_du_output output; - struct rcar_du_hdmienc *hdmi; + struct rcar_du_connector *connector; struct rcar_du_lvdsenc *lvds; }; @@ -44,13 +36,13 @@ struct rcar_du_encoder { struct rcar_du_connector { struct drm_connector connector; struct rcar_du_encoder *encoder; + struct drm_panel *panel; }; #define to_rcar_connector(c) \ container_of(c, struct rcar_du_connector, connector) int rcar_du_encoder_init(struct rcar_du_device *rcdu, - enum rcar_du_encoder_type type, enum rcar_du_output output, struct device_node *enc_node, struct device_node *con_node); diff --git a/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c b/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c deleted file mode 100644 index c4c5d1abcff8..000000000000 --- a/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c +++ /dev/null @@ -1,134 +0,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. - */ - -#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; -} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h b/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h deleted file mode 100644 index 2ff0128ac8e1..000000000000 --- a/drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h +++ /dev/null @@ -1,35 +0,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__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_kms.c b/drivers/gpu/drm/rcar-du/rcar_du_kms.c index ff61f6032f2c..f4125c8ca902 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_kms.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_kms.c @@ -249,18 +249,9 @@ static int rcar_du_atomic_check(struct drm_device *dev, return rcar_du_atomic_check_planes(dev, state); } -struct rcar_du_commit { - 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) +static void rcar_du_atomic_commit_tail(struct drm_atomic_state *old_state) { - struct drm_device *dev = commit->dev; - struct rcar_du_device *rcdu = dev->dev_private; - struct drm_atomic_state *old_state = commit->state; + struct drm_device *dev = old_state->dev; /* Apply the atomic update. */ drm_atomic_helper_commit_modeset_disables(dev, old_state); @@ -268,114 +259,31 @@ static void rcar_du_atomic_complete(struct rcar_du_commit *commit) drm_atomic_helper_commit_planes(dev, old_state, 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_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 */ +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 = { .fb_create = rcar_du_fb_create, .output_poll_changed = rcar_du_output_poll_changed, .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, enum rcar_du_output output, 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 *encoder = NULL; struct device_node *ep_node = NULL; @@ -394,6 +302,13 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu, 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); for_each_endpoint_of_node(entity, ep_node) { @@ -422,30 +337,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu, of_node_put(entity_ep_node); - 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 (!encoder) { /* * If no encoder has been found the entity must be the * connector. @@ -453,7 +345,7 @@ static int rcar_du_encoders_init_one(struct rcar_du_device *rcdu, 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) dev_warn(rcdu->dev, "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) dev->mode_config.max_width = 4095; dev->mode_config.max_height = 2047; 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; diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c index 3bcfd161c53f..ee91481131ad 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c @@ -15,6 +15,7 @@ #include <drm/drm_atomic_helper.h> #include <drm/drm_crtc.h> #include <drm/drm_crtc_helper.h> +#include <drm/drm_panel.h> #include <video/display_timing.h> #include <video/of_display_timing.h> @@ -25,47 +26,30 @@ #include "rcar_du_kms.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) { - struct rcar_du_lvds_connector *lvdscon = - to_rcar_lvds_connector(connector); - struct drm_display_mode *mode; - - mode = drm_mode_create(connector->dev); - if (mode == NULL) - return 0; + struct rcar_du_connector *rcon = to_rcar_connector(connector); - mode->type = DRM_MODE_TYPE_PREFERRED | DRM_MODE_TYPE_DRIVER; - - drm_display_mode_from_videomode(&lvdscon->panel.mode, mode); - - drm_mode_probed_add(connector, mode); - - return 1; + return drm_panel_get_modes(rcon->panel); } static const struct drm_connector_helper_funcs connector_helper_funcs = { .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 = { .dpms = drm_atomic_helper_connector_dpms, .reset = drm_atomic_helper_connector_reset, .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_destroy_state = drm_atomic_helper_connector_destroy_state, }; @@ -75,27 +59,19 @@ int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu, const struct device_node *np) { 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 display_timing timing; int ret; - lvdscon = devm_kzalloc(rcdu->dev, sizeof(*lvdscon), GFP_KERNEL); - if (lvdscon == NULL) + rcon = devm_kzalloc(rcdu->dev, sizeof(*rcon), GFP_KERNEL); + if (rcon == NULL) return -ENOMEM; - ret = of_get_display_timing(np, "panel-timing", &timing); - if (ret < 0) - return ret; - - videomode_from_timing(&timing, &lvdscon->panel.mode); + connector = &rcon->connector; - of_property_read_u32(np, "width-mm", &lvdscon->panel.width_mm); - of_property_read_u32(np, "height-mm", &lvdscon->panel.height_mm); - - connector = &lvdscon->connector.connector; - connector->display_info.width_mm = lvdscon->panel.width_mm; - connector->display_info.height_mm = lvdscon->panel.height_mm; + rcon->panel = of_drm_find_panel(np); + if (!rcon->panel) + return -EPROBE_DEFER; ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs, DRM_MODE_CONNECTOR_LVDS); @@ -112,7 +88,11 @@ int rcar_du_lvds_connector_init(struct rcar_du_device *rcdu, if (ret < 0) return ret; - lvdscon->connector.encoder = renc; + ret = drm_panel_attach(rcon->panel, connector); + if (ret < 0) + return ret; + + rcon->encoder = renc; return 0; } diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c index e3a4985f6f3f..1661f6201210 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c @@ -31,6 +31,7 @@ struct rcar_du_lvdsenc { bool enabled; enum rcar_lvds_input input; + enum rcar_lvds_mode mode; }; 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, /* Select the input, hardcode mode 0, enable LVDS operation and turn * bias circuitry on. */ - lvdcr0 = LVDCR0_BEN | LVDCR0_LVEN; + lvdcr0 = (lvds->mode << LVDCR0_LVMD_SHIFT) | LVDCR0_BEN | LVDCR0_LVEN; if (rcrtc->index == 2) lvdcr0 |= LVDCR0_DUSEL; rcar_lvds_write(lvds, LVDCR0, lvdcr0); @@ -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 * delay and turn the output on. */ - lvdcr0 = LVDCR0_PLLON; + lvdcr0 = (lvds->mode << LVDCR0_LVMD_SHIFT) | LVDCR0_PLLON; rcar_lvds_write(lvds, LVDCR0, lvdcr0); lvdcr0 |= LVDCR0_PWD; @@ -211,6 +212,12 @@ void rcar_du_lvdsenc_atomic_check(struct rcar_du_lvdsenc *lvds, 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, struct platform_device *pdev) { diff --git a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h index dfdba746edf4..7218ac89333e 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h @@ -26,8 +26,17 @@ enum rcar_lvds_input { 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) 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, struct drm_crtc *crtc, bool enable); 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) { 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, struct drm_crtc *crtc, bool enable) { diff --git a/drivers/gpu/drm/rcar-du/rcar_du_regs.h b/drivers/gpu/drm/rcar-du/rcar_du_regs.h index fedb0161e234..d5bae99d3cfe 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_regs.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_regs.h @@ -277,6 +277,29 @@ #define DEFR10_TSEL_H3_TCON1 (0 << 1) /* DEFR102 register only (DU2/DU3) */ #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 */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c b/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c deleted file mode 100644 index 8d6125c1c0f9..000000000000 --- a/drivers/gpu/drm/rcar-du/rcar_du_vgacon.c +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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; -} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vgacon.h b/drivers/gpu/drm/rcar-du/rcar_du_vgacon.h deleted file mode 100644 index 112f50316e01..000000000000 --- a/drivers/gpu/drm/rcar-du/rcar_du_vgacon.h +++ /dev/null @@ -1,23 +0,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__ */ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_vsp.h b/drivers/gpu/drm/rcar-du/rcar_du_vsp.h index 510dcc9c6816..f1d0f1824528 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_vsp.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_vsp.h @@ -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_flush(struct rcar_du_crtc *crtc); #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_disable(struct rcar_du_crtc *crtc) { }; static inline void rcar_du_vsp_atomic_begin(struct rcar_du_crtc *crtc) { }; diff --git a/drivers/gpu/drm/rcar-du/rcar_dw_hdmi.c b/drivers/gpu/drm/rcar-du/rcar_dw_hdmi.c new file mode 100644 index 000000000000..7539626b8ebd --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_dw_hdmi.c @@ -0,0 +1,100 @@ +/* + * 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"); diff --git a/include/drm/drm_connector.h b/include/drm/drm_connector.h index 941f57f311aa..8250062eb8a5 100644 --- a/include/drm/drm_connector.h +++ b/include/drm/drm_connector.h @@ -224,6 +224,10 @@ struct drm_display_info { #define DRM_BUS_FLAG_PIXDATA_POSEDGE (1<<2) /* drive data on neg. edge */ #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 |