aboutsummaryrefslogtreecommitdiff
path: root/drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c')
-rw-r--r--drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c236
1 files changed, 236 insertions, 0 deletions
diff --git a/drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c b/drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c
new file mode 100644
index 000000000000..5f6a4b4ff9f6
--- /dev/null
+++ b/drivers/gpu/drm/arm/hdlcd_hdmi_encoder.c
@@ -0,0 +1,236 @@
+/*
+ * Copyright (C) 2013,2014 ARM Limited
+ * Author: Liviu Dudau <Liviu.Dudau@arm.com>
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive
+ * for more details.
+ *
+ */
+
+/*
+ * Theory of operation:
+ *
+ * The DRM framework expects the CRTC -> Encoder -> Connector chain,
+ * where the CRTC is reading from framebuffer, passes data to the
+ * encoder and that formats the signals to something usable by the
+ * attached connector(s). Connectors can use i2c links to talk with
+ * attached monitors.
+ *
+ * The HDMI transmitter is a different beast: it is both and encoder
+ * and a connector in DRM parlance *and* can only be reached via i2c.
+ * It implements an i2c pass through mode for the situation where one
+ * wants to talk with the attached monitor. To complicate things
+ * even further, the VExpress boards that have the SiI9022 chip share
+ * the i2c line between the on-board microcontroller and the CoreTiles.
+ * This leads to a situation where the microcontroller might be able to
+ * talk with the SiI9022 transmitter, but not the CoreTile. And the
+ * micro has a very small brain and a list of hardcoded modes that
+ * it can program into the HDMI transmitter, so only a limited set
+ * of resolutions will be valid.
+ *
+ * This file handles only the case where the i2c connection is available
+ * to the kernel. For the case where we have to ask the microcontroller
+ * to do the modesetting for us see the hdlcd_vexpress_encoder.c file.
+ */
+
+#include <linux/i2c.h>
+#include <drm/drmP.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_encoder_slave.h>
+#include <drm/i2c/tda998x.h>
+#include <video/display_timing.h>
+#include <video/of_display_timing.h>
+#include <video/videomode.h>
+
+#include "hdlcd_drv.h"
+
+static inline struct drm_encoder_slave *
+hdlcd_get_slave_encoder(struct drm_connector * connector)
+{
+ return to_encoder_slave(hdlcd_connector_best_encoder(connector));
+}
+
+static void hdlcd_connector_destroy(struct drm_connector *connector)
+{
+}
+
+static enum drm_connector_status
+hdlcd_connector_detect(struct drm_connector *connector, bool force)
+{
+ struct drm_encoder_slave *slave;
+ if (!connector->encoder)
+ connector->encoder = hdlcd_connector_best_encoder(connector);
+
+ slave = hdlcd_get_slave_encoder(connector);
+ if (!slave || !slave->slave_funcs)
+ return connector_status_unknown;
+
+ return slave->slave_funcs->detect(connector->encoder, connector);
+}
+
+static const struct drm_connector_funcs hdlcd_connector_funcs = {
+ .destroy = hdlcd_connector_destroy,
+ .dpms = drm_helper_connector_dpms,
+ .detect = hdlcd_connector_detect,
+ .fill_modes = drm_helper_probe_single_connector_modes,
+};
+
+struct drm_encoder *
+hdlcd_connector_best_encoder(struct drm_connector *connector)
+{
+ int enc_id = connector->encoder_ids[0];
+ struct drm_mode_object *obj;
+ struct drm_encoder *encoder;
+
+ if (connector->encoder)
+ return connector->encoder;
+
+ if (enc_id) {
+ obj = drm_mode_object_find(connector->dev, enc_id,
+ DRM_MODE_OBJECT_ENCODER);
+ if (obj) {
+ encoder = obj_to_encoder(obj);
+ return encoder;
+ }
+ }
+ return NULL;
+
+}
+
+static int hdlcd_hdmi_con_get_modes(struct drm_connector *connector)
+{
+ struct drm_encoder_slave *slave = hdlcd_get_slave_encoder(connector);
+
+ if (slave && slave->slave_funcs)
+ return slave->slave_funcs->get_modes(&slave->base, connector);
+
+ return 0;
+}
+
+static int hdlcd_hdmi_con_mode_valid(struct drm_connector *connector,
+ struct drm_display_mode *mode)
+{
+ struct drm_encoder_slave *slave = hdlcd_get_slave_encoder(connector);
+
+ if (slave && slave->slave_funcs)
+ return slave->slave_funcs->mode_valid(connector->encoder, mode);
+
+ return MODE_ERROR;
+}
+
+static const struct drm_connector_helper_funcs hdlcd_hdmi_con_helper_funcs = {
+ .get_modes = hdlcd_hdmi_con_get_modes,
+ .mode_valid = hdlcd_hdmi_con_mode_valid,
+ .best_encoder = hdlcd_connector_best_encoder,
+};
+
+
+static struct drm_encoder_funcs hdlcd_encoder_funcs = {
+ .destroy = drm_i2c_encoder_destroy,
+};
+
+static void hdlcd_hdmi_encoder_disable(struct drm_encoder *encoder)
+{
+ drm_i2c_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+static struct drm_encoder_helper_funcs hdlcd_encoder_helper_funcs = {
+ .dpms = drm_i2c_encoder_dpms,
+ .save = drm_i2c_encoder_save,
+ .restore = drm_i2c_encoder_restore,
+ .mode_fixup = drm_i2c_encoder_mode_fixup,
+ .prepare = drm_i2c_encoder_prepare,
+ .commit = drm_i2c_encoder_commit,
+ .mode_set = drm_i2c_encoder_mode_set,
+ .detect = drm_i2c_encoder_detect,
+ .disable = hdlcd_hdmi_encoder_disable,
+};
+
+static struct tda998x_encoder_params tda998x_params = {
+ .swap_a = 2,
+ .swap_b = 3,
+ .swap_c = 4,
+ .swap_d = 5,
+ .swap_e = 0,
+ .swap_f = 1,
+};
+
+int hdlcd_create_digital_connector(struct drm_device *dev,
+ struct hdlcd_drm_private *hdlcd)
+{
+ int err;
+ struct i2c_board_info i2c_info;
+ struct drm_encoder_slave *slave;
+ struct drm_connector *connector;
+ struct device_node *node = hdlcd->slave_node;
+
+ slave = kzalloc(sizeof(*slave), GFP_KERNEL);
+ if (!slave)
+ return -ENOMEM;
+
+ slave->base.possible_crtcs = 1;
+ slave->base.possible_clones = 0;
+
+ err = drm_encoder_init(dev, &slave->base, &hdlcd_encoder_funcs,
+ DRM_MODE_ENCODER_TMDS);
+ if (err)
+ goto encoder_init_err;
+
+ drm_encoder_helper_add(&slave->base, &hdlcd_encoder_helper_funcs);
+
+ /* get the driver for the i2c slave node */
+ i2c_info.of_node = node;
+ err = of_modalias_node(node, i2c_info.type, sizeof(i2c_info.type));
+ if (err < 0) {
+ dev_err(dev->dev, "failed to get a module alias for node %s\n",
+ node->full_name);
+ }
+
+ /* Hack: this needs to be specified in the device tree */
+ i2c_info.platform_data = &tda998x_params;
+
+ err = drm_i2c_encoder_init(dev, slave, NULL, &i2c_info);
+ of_node_put(node);
+ if (err)
+ goto connector_alloc_err;
+
+ connector = kzalloc(sizeof(*connector), GFP_KERNEL);
+ if (!connector) {
+ err = -ENOMEM;
+ goto connector_alloc_err;
+ }
+
+ connector->interlace_allowed = false;
+ connector->doublescan_allowed = false;
+ connector->polled = DRM_CONNECTOR_POLL_CONNECT |
+ DRM_CONNECTOR_POLL_DISCONNECT;
+ err = drm_connector_init(dev, connector, &hdlcd_connector_funcs,
+ DRM_MODE_CONNECTOR_DVID);
+ if (err)
+ goto connector_init_err;
+
+ drm_connector_helper_add(connector, &hdlcd_hdmi_con_helper_funcs);
+
+ connector->encoder = &slave->base;
+ err = drm_mode_connector_attach_encoder(connector, &slave->base);
+ if (err) {
+ goto connector_attach_err;
+ }
+
+ drm_sysfs_connector_add(connector);
+
+ slave->base.dev->dev->platform_data = hdlcd;
+ return err;
+
+connector_attach_err:
+ drm_connector_cleanup(connector);
+connector_init_err:
+ kfree(connector);
+connector_alloc_err:
+ drm_encoder_cleanup(&slave->base);
+encoder_init_err:
+ kfree(slave);
+
+ return err;
+}