From patchwork Fri Aug 30 12:37:43 2013 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Ulrich Hecht X-Patchwork-Id: 2851970 Return-Path: X-Original-To: patchwork-linux-sh@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork1.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.19.201]) by patchwork1.web.kernel.org (Postfix) with ESMTP id 4FFF89F313 for ; Fri, 30 Aug 2013 12:40:10 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 83294203E3 for ; Fri, 30 Aug 2013 12:40:08 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id CBE03203E6 for ; Fri, 30 Aug 2013 12:40:05 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1755624Ab3H3MkE (ORCPT ); Fri, 30 Aug 2013 08:40:04 -0400 Received: from mail-ea0-f175.google.com ([209.85.215.175]:37242 "EHLO mail-ea0-f175.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753921Ab3H3MjT (ORCPT ); Fri, 30 Aug 2013 08:39:19 -0400 Received: by mail-ea0-f175.google.com with SMTP id m14so887970eaj.34 for ; Fri, 30 Aug 2013 05:39:18 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20120113; h=from:to:cc:subject:date:message-id:in-reply-to:references; bh=2HqdXJZTUkk/LkW+gaU0GDKf3BNHvsLc7nCgvx+z2eE=; b=ooRqHLQ/EZHzy5OoDPEUm5kZtxIoQN4RrUl6v/glQNE3Q6jrWj5bojW0GvilaC/OMN aYgi3aSFQL7WyN9qHO876ukr9f+dA4q5WPuyorCLn+kFPtD8l1TuSs7mpeZd5ZpJekkn Uxp8OX4FjN5gm9/kh9dc9KXOpCBCKKJasTJIpdoZHdCniIHsGJVq4rMat3uXcF7tcBxn wfDaXK3EO/R69pjy+QYcUP+/ZTu2t9uXY4VYoE45coBL45LHvDAH7FIfwXe/qKbwHmv8 +XySXbbWibgjWZAwhEPYTiKub3zbYk2L0d9dR0M9GhEaafbiD8rw26K2vPapMVLcR2x7 l/IQ== X-Received: by 10.14.37.4 with SMTP id x4mr12560989eea.16.1377866358574; Fri, 30 Aug 2013 05:39:18 -0700 (PDT) Received: from groucho.site ([109.201.152.242]) by mx.google.com with ESMTPSA id j7sm54444384eeo.15.1969.12.31.16.00.00 (version=TLSv1.2 cipher=ECDHE-RSA-RC4-SHA bits=128/128); Fri, 30 Aug 2013 05:39:17 -0700 (PDT) From: Ulrich Hecht To: linux-sh@vger.kernel.org Cc: magnus.damm@gmail.com, Ulrich Hecht Subject: [RFC 09/10] drm: rcar-du: support for i2c HDMI encoders Date: Fri, 30 Aug 2013 14:37:43 +0200 Message-Id: <1377866264-21110-10-git-send-email-ulrich.hecht@gmail.com> X-Mailer: git-send-email 1.8.1.4 In-Reply-To: <1377866264-21110-1-git-send-email-ulrich.hecht@gmail.com> References: <1377866264-21110-1-git-send-email-ulrich.hecht@gmail.com> Sender: linux-sh-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-sh@vger.kernel.org X-Spam-Status: No, score=-8.9 required=5.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, RCVD_IN_DNSWL_HI, RP_MATCHES_RCVD, T_DKIM_INVALID, UNPARSEABLE_RELAY autolearn=ham version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP Enables use of i2c HDMI encoders with the rcar-du driver. Signed-off-by: Ulrich Hecht --- 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 diff --git a/drivers/gpu/drm/rcar-du/Makefile b/drivers/gpu/drm/rcar-du/Makefile index 12b8d44..1d7d4c5a 100644 --- a/drivers/gpu/drm/rcar-du/Makefile +++ b/drivers/gpu/drm/rcar-du/Makefile @@ -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 \ diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.c b/drivers/gpu/drm/rcar-du/rcar_du_drv.c index e113352..81c7676 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.c @@ -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); } diff --git a/drivers/gpu/drm/rcar-du/rcar_du_drv.h b/drivers/gpu/drm/rcar-du/rcar_du_drv.h index 65d2d63..72d44de 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_drv.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_drv.h @@ -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, diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c index 3daa7a1..16e0f87 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.c +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.c @@ -16,9 +16,11 @@ #include #include #include +#include #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; } diff --git a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h index 0e5a65e..066c45c 100644 --- a/drivers/gpu/drm/rcar-du/rcar_du_encoder.h +++ b/drivers/gpu/drm/rcar-du/rcar_du_encoder.h @@ -17,12 +17,16 @@ #include #include +#include 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; }; diff --git a/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.c b/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.c new file mode 100644 index 0000000..ffe8098 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.c @@ -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 +#include +#include +#include + +#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); +} diff --git a/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.h b/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.h new file mode 100644 index 0000000..4fee757 --- /dev/null +++ b/drivers/gpu/drm/rcar-du/rcar_du_hdmicon.h @@ -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__ */ diff --git a/include/linux/platform_data/rcar-du.h b/include/linux/platform_data/rcar-du.h index 1a2e990..fc4a87c 100644 --- a/include/linux/platform_data/rcar-du.h +++ b/include/linux/platform_data/rcar-du.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;