@@ -1,6 +1,7 @@
rcar-du-drm-y := rcar_du_crtc.o \
rcar_du_drv.o \
rcar_du_encoder.o \
+ rcar_du_hdmicon.o \
rcar_du_group.o \
rcar_du_kms.o \
rcar_du_lvdscon.o \
@@ -220,6 +220,13 @@ static const struct dev_pm_ops rcar_du_pm_ops = {
static int rcar_du_probe(struct platform_device *pdev)
{
+ struct rcar_du_platform_data *pdata = pdev->dev.platform_data;
+ int i;
+ for (i = 0; i < pdata->num_encoders; i++) {
+ if (pdata->encoders[i].slave && !*pdata->encoders[i].slave) {
+ return -EPROBE_DEFER;
+ }
+ }
return drm_platform_init(&rcar_du_driver, pdev);
}
@@ -76,6 +76,8 @@ struct rcar_du_device {
unsigned int dpad0_source;
struct rcar_du_lvdsenc *lvds[2];
+
+ struct i2c_client *encoder_slave;
};
static inline bool rcar_du_has(struct rcar_du_device *rcdu,
@@ -16,9 +16,11 @@
#include <drm/drmP.h>
#include <drm/drm_crtc.h>
#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
#include "rcar_du_drv.h"
#include "rcar_du_encoder.h"
+#include "rcar_du_hdmicon.h"
#include "rcar_du_kms.h"
#include "rcar_du_lvdscon.h"
#include "rcar_du_lvdsenc.h"
@@ -44,10 +46,22 @@ static void rcar_du_encoder_dpms(struct drm_encoder *encoder, int mode)
{
struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
- if (renc->lvds)
+ if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS)
+ rcar_du_hdmi_encoder_dpms(encoder, mode);
+ else if (renc->lvds)
rcar_du_lvdsenc_dpms(renc->lvds, encoder->crtc, mode);
}
+static inline struct drm_encoder_slave_funcs *
+get_slave_funcs(struct drm_encoder *enc)
+{
+ if (!to_encoder_slave(enc))
+ return NULL;
+
+ return to_encoder_slave(enc)->slave_funcs;
+}
+
+
static bool rcar_du_encoder_mode_fixup(struct drm_encoder *encoder,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
@@ -62,6 +76,14 @@ static bool rcar_du_encoder_mode_fixup(struct drm_encoder *encoder,
if (encoder->encoder_type == DRM_MODE_ENCODER_DAC)
return true;
+ if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS) {
+ struct drm_encoder_slave_funcs *sfuncs = get_slave_funcs(encoder);
+ if (sfuncs && sfuncs->mode_fixup)
+ return sfuncs->mode_fixup(encoder, mode, adjusted_mode);
+
+ return true;
+ }
+
list_for_each_entry(connector, &dev->mode_config.connector_list, head) {
if (connector->encoder == encoder) {
found = true;
@@ -124,6 +146,12 @@ static void rcar_du_encoder_mode_set(struct drm_encoder *encoder,
{
struct rcar_du_encoder *renc = to_rcar_encoder(encoder);
+ if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS) {
+ struct drm_encoder_slave_funcs *sfuncs = get_slave_funcs(encoder);
+ if (sfuncs && sfuncs->mode_set)
+ sfuncs->mode_set(encoder, mode, adjusted_mode);
+ }
+
rcar_du_crtc_route_output(encoder->crtc, renc->output);
}
@@ -135,8 +163,20 @@ static const struct drm_encoder_helper_funcs encoder_helper_funcs = {
.mode_set = rcar_du_encoder_mode_set,
};
+static void rcar_du_encoder_destroy(struct drm_encoder *encoder)
+{
+ if (encoder->encoder_type == DRM_MODE_ENCODER_TMDS) {
+ struct drm_encoder_slave_funcs *sfuncs = get_slave_funcs(encoder);
+
+ if (sfuncs && sfuncs->destroy)
+ sfuncs->destroy(encoder);
+ }
+
+ drm_encoder_cleanup(encoder);
+}
+
static const struct drm_encoder_funcs encoder_funcs = {
- .destroy = drm_encoder_cleanup,
+ .destroy = rcar_du_encoder_destroy,
};
int rcar_du_encoder_init(struct rcar_du_device *rcdu,
@@ -147,6 +187,7 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
struct rcar_du_encoder *renc;
unsigned int encoder_type;
int ret;
+ struct drm_i2c_encoder_driver *encoder_drv;
renc = devm_kzalloc(rcdu->dev, sizeof(*renc), GFP_KERNEL);
if (renc == NULL)
@@ -174,6 +215,9 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
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. */
@@ -196,6 +240,14 @@ int rcar_du_encoder_init(struct rcar_du_device *rcdu,
case DRM_MODE_ENCODER_DAC:
return rcar_du_vga_connector_init(rcdu, renc);
+ case DRM_MODE_ENCODER_TMDS:
+ rcdu->encoder_slave = *data->slave;
+ if (rcdu->encoder_slave) {
+ encoder_drv = to_drm_i2c_encoder_driver(rcdu->encoder_slave->driver);
+ encoder_drv->encoder_init(rcdu->encoder_slave, rcdu->ddev, &renc->encoder_slave);
+ }
+ return rcar_du_hdmi_connector_init(rcdu, renc);
+
default:
return -EINVAL;
}
@@ -17,12 +17,16 @@
#include <linux/platform_data/rcar-du.h>
#include <drm/drm_crtc.h>
+#include <drm/drm_encoder_slave.h>
struct rcar_du_device;
struct rcar_du_lvdsenc;
struct rcar_du_encoder {
- struct drm_encoder encoder;
+ union {
+ struct drm_encoder encoder;
+ struct drm_encoder_slave encoder_slave;
+ };
enum rcar_du_output output;
struct rcar_du_lvdsenc *lvds;
};
new file mode 100644
@@ -0,0 +1,165 @@
+/*
+ * rcar_du_hdmicon.c -- R-Car Display Unit HDMI DAC and Connector
+ *
+ * Copyright (C) 2013 Renesas 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_crtc.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+
+#include "rcar_du_drv.h"
+#include "rcar_du_encoder.h"
+#include "rcar_du_kms.h"
+#include "rcar_du_hdmicon.h"
+
+#include "../i2c/adv7511.h"
+
+/* -----------------------------------------------------------------------------
+ * Connector
+ */
+
+static inline struct drm_encoder_slave_funcs *
+get_slave_funcs(struct drm_encoder *enc)
+{
+ if (!to_encoder_slave(enc))
+ return NULL;
+
+ return to_encoder_slave(enc)->slave_funcs;
+}
+
+static int rcar_du_hdmi_connector_get_modes(struct drm_connector *connector)
+{
+ int count = 0;
+ struct drm_encoder *encoder = &to_rcar_connector(connector)->encoder->encoder;
+ struct drm_encoder_slave_funcs *sfuncs = get_slave_funcs(encoder);
+
+ if (sfuncs && sfuncs->get_modes)
+ count += sfuncs->get_modes(encoder, connector);
+
+ return count;
+}
+
+static int rcar_du_hdmi_connector_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ /* XXX: applicable here? */
+ if (mode->clock > 165000)
+ return MODE_CLOCK_HIGH;
+ if (mode->flags & DRM_MODE_FLAG_INTERLACE)
+ return MODE_NO_INTERLACE;
+
+ return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs connector_helper_funcs = {
+ .get_modes = rcar_du_hdmi_connector_get_modes,
+ .mode_valid = rcar_du_hdmi_connector_mode_valid,
+ .best_encoder = rcar_du_connector_best_encoder,
+};
+
+static void rcar_du_hdmi_connector_destroy(struct drm_connector *connector)
+{
+ drm_sysfs_connector_remove(connector);
+ drm_connector_cleanup(connector);
+}
+
+static enum drm_connector_status
+rcar_du_hdmi_connector_detect(struct drm_connector *connector, bool force)
+{
+ enum drm_connector_status status = connector_status_unknown;
+ struct drm_encoder *encoder = &to_rcar_connector(connector)->encoder->encoder;
+ struct drm_encoder_slave_funcs *sfuncs = get_slave_funcs(encoder);
+
+ if (sfuncs && sfuncs->detect)
+ status = sfuncs->detect(encoder, connector);
+
+ return status;
+}
+
+static const struct drm_connector_funcs connector_funcs = {
+ .dpms = drm_helper_connector_dpms,
+ .detect = rcar_du_hdmi_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+ .destroy = rcar_du_hdmi_connector_destroy,
+};
+
+int rcar_du_hdmi_connector_init(struct rcar_du_device *rcdu,
+ struct rcar_du_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;
+
+ ret = drm_connector_init(rcdu->ddev, connector, &connector_funcs,
+ DRM_MODE_CONNECTOR_HDMIA);
+ if (ret < 0)
+ return ret;
+
+ drm_connector_helper_add(connector, &connector_helper_funcs);
+ ret = drm_sysfs_connector_add(connector);
+ if (ret < 0)
+ return ret;
+
+ drm_helper_connector_dpms(connector, 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, &renc->encoder);
+ if (ret < 0)
+ return ret;
+
+ connector->encoder = &renc->encoder;
+ rcon->encoder = renc;
+
+ return 0;
+}
+
+void rcar_du_hdmi_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+ struct drm_encoder_slave_funcs *sfuncs = get_slave_funcs(encoder);
+ struct adv7511_video_config config;
+ struct edid *edid;
+
+ switch (mode) {
+ case DRM_MODE_DPMS_ON:
+ edid = adv7511_get_edid(encoder);
+ if (edid) {
+ config.hdmi_mode = drm_detect_hdmi_monitor(edid);
+ kfree(edid);
+ } else {
+ config.hdmi_mode = false;
+ }
+
+ hdmi_avi_infoframe_init(&config.avi_infoframe);
+
+ config.avi_infoframe.scan_mode = HDMI_SCAN_MODE_UNDERSCAN;
+
+ /* XXX: does the DU do anything else? */
+ config.csc_enable = false;
+ config.avi_infoframe.colorspace = HDMI_COLORSPACE_RGB;
+
+ sfuncs->set_config(encoder, &config);
+ break;
+ default:
+ break;
+ }
+ if (sfuncs && sfuncs->dpms)
+ sfuncs->dpms(encoder, mode);
+}
new file mode 100644
@@ -0,0 +1,28 @@
+/*
+ * rcar_du_hdmicon.h -- R-Car Display Unit HDMI DAC and Connector
+ *
+ * Copyright (C) 2013 Renesas 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_HDMI_H__
+#define __RCAR_DU_HDMI_H__
+
+struct rcar_du_device;
+struct rcar_du_encoder_hdmi_data;
+struct rcar_du_encoder;
+
+int rcar_du_hdmi_init(struct rcar_du_device *rcdu,
+ const struct rcar_du_encoder_hdmi_data *data,
+ unsigned int output);
+void rcar_du_hdmi_encoder_dpms(struct drm_encoder *encoder, int mode);
+int rcar_du_hdmi_connector_init(struct rcar_du_device *rcdu,
+ struct rcar_du_encoder *renc);
+
+#endif /* __RCAR_DU_HDMI_H__ */
@@ -30,6 +30,7 @@ enum rcar_du_encoder_type {
RCAR_DU_ENCODER_NONE,
RCAR_DU_ENCODER_VGA,
RCAR_DU_ENCODER_LVDS,
+ RCAR_DU_ENCODER_HDMI,
};
struct rcar_du_panel_data {
@@ -46,6 +47,8 @@ struct rcar_du_connector_vga_data {
/* TODO: Add DDC information for EDID retrieval */
};
+struct i2c_client;
+
/*
* struct rcar_du_encoder_data - Encoder platform data
* @type: the encoder type (RCAR_DU_ENCODER_*)
@@ -59,6 +62,7 @@ struct rcar_du_connector_vga_data {
struct rcar_du_encoder_data {
enum rcar_du_encoder_type type;
enum rcar_du_output output;
+ struct i2c_client **slave;
union {
struct rcar_du_connector_lvds_data lvds;
Enables use of i2c HDMI encoders with the rcar-du driver. Signed-off-by: Ulrich Hecht <ulrich.hecht@gmail.com> --- drivers/gpu/drm/rcar-du/Makefile | 1 + drivers/gpu/drm/rcar-du/rcar_du_drv.c | 7 ++ drivers/gpu/drm/rcar-du/rcar_du_drv.h | 2 + drivers/gpu/drm/rcar-du/rcar_du_encoder.c | 56 +++++++++- drivers/gpu/drm/rcar-du/rcar_du_encoder.h | 6 +- drivers/gpu/drm/rcar-du/rcar_du_hdmicon.c | 165 ++++++++++++++++++++++++++++++ drivers/gpu/drm/rcar-du/rcar_du_hdmicon.h | 28 +++++ include/linux/platform_data/rcar-du.h | 4 + 8 files changed, 266 insertions(+), 3 deletions(-) create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_hdmicon.c create mode 100644 drivers/gpu/drm/rcar-du/rcar_du_hdmicon.h