summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/rcar-du
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/rcar-du')
-rw-r--r--drivers/gpu/drm/rcar-du/Kconfig10
-rw-r--r--drivers/gpu/drm/rcar-du/Makefile6
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_crtc.c94
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_crtc.h4
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_drv.c32
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_drv.h8
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_encoder.c187
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_encoder.h14
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_hdmienc.c134
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_hdmienc.h35
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_kms.c143
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_lvdscon.c68
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.c11
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_lvdsenc.h13
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_regs.h23
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_vgacon.c82
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_vgacon.h23
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_du_vsp.h2
-rw-r--r--drivers/gpu/drm/rcar-du/rcar_dw_hdmi.c100
19 files changed, 424 insertions, 565 deletions
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");