diff mbox

[RFC,3/7] drm: support panels connected on a DPI bus

Message ID 1412609873-1894-4-git-send-email-boris.brezillon@free-electrons.com (mailing list archive)
State New, archived
Headers show

Commit Message

Boris BREZILLON Oct. 6, 2014, 3:37 p.m. UTC
Add support for panels connected on a DPI bus.
This implementation includes DRM encoder/connector creation and attachment
to the DRM device so that the only thing needed to access such a panel is
to implement a DPI host in your driver and define the appropriate node in
your DT (under the DPI node).

Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
---
 drivers/gpu/drm/panel/panel-simple.c | 277 +++++++++++++++++++++++++++++++++++
 1 file changed, 277 insertions(+)
diff mbox

Patch

diff --git a/drivers/gpu/drm/panel/panel-simple.c b/drivers/gpu/drm/panel/panel-simple.c
index 11bff3f..fd448ca 100644
--- a/drivers/gpu/drm/panel/panel-simple.c
+++ b/drivers/gpu/drm/panel/panel-simple.c
@@ -30,7 +30,9 @@ 
 
 #include <drm/drmP.h>
 #include <drm/drm_crtc.h>
+#include <drm/drm_crtc_helper.h>
 #include <drm/drm_mipi_dsi.h>
+#include <drm/drm_mipi_dpi.h>
 #include <drm/drm_panel.h>
 
 struct panel_desc {
@@ -851,6 +853,275 @@  static struct mipi_dsi_driver panel_simple_dsi_driver = {
 	.shutdown = panel_simple_dsi_shutdown,
 };
 
+struct panel_desc_dpi {
+	struct panel_desc desc;
+
+	enum video_bus_format format;
+};
+
+struct panel_dpi {
+	struct drm_encoder encoder;
+	struct drm_connector connector;
+	int dpms_mode;
+
+	struct mipi_dpi_device *dev;
+};
+
+static const struct of_device_id dpi_of_match[] = {
+	{
+		/* sentinel */
+	}
+};
+MODULE_DEVICE_TABLE(of, dsi_of_match);
+
+static inline struct panel_dpi *
+drm_encoder_to_panel_dpi(struct drm_encoder *encoder)
+{
+	return container_of(encoder, struct panel_dpi, encoder);
+}
+
+static void panel_dpi_encoder_dpms(struct drm_encoder *encoder, int mode)
+{
+	struct panel_dpi *dpanel = drm_encoder_to_panel_dpi(encoder);
+	struct panel_simple *panel = mipi_dpi_get_drvdata(dpanel->dev);
+
+	if (mode != DRM_MODE_DPMS_ON)
+		mode = DRM_MODE_DPMS_OFF;
+
+	if (mode == dpanel->dpms_mode)
+		return;
+
+	switch (mode) {
+	case DRM_MODE_DPMS_ON:
+		drm_panel_prepare(&panel->base);
+		drm_panel_enable(&panel->base);
+		mipi_dpi_enable(dpanel->dev);
+		break;
+	case DRM_MODE_DPMS_STANDBY:
+	case DRM_MODE_DPMS_SUSPEND:
+	case DRM_MODE_DPMS_OFF:
+		mipi_dpi_disable(dpanel->dev);
+		drm_panel_disable(&panel->base);
+		drm_panel_unprepare(&panel->base);
+		break;
+	}
+
+	dpanel->dpms_mode = mode;
+}
+
+static bool panel_dpi_encoder_mode_fixup(struct drm_encoder *encoder,
+				 const struct drm_display_mode *mode,
+				 struct drm_display_mode *adjusted_mode)
+{
+	return true;
+}
+
+static void panel_dpi_encoder_mode_set(struct drm_encoder *encoder,
+			       struct drm_display_mode *mode,
+			       struct drm_display_mode *adjusted)
+{
+}
+
+static void panel_dpi_encoder_prepare(struct drm_encoder *encoder)
+{
+	panel_dpi_encoder_dpms(encoder, DRM_MODE_DPMS_OFF);
+}
+
+static void panel_dpi_encoder_commit(struct drm_encoder *encoder)
+{
+	panel_dpi_encoder_dpms(encoder, DRM_MODE_DPMS_ON);
+}
+
+static const struct drm_encoder_helper_funcs panel_dpi_encoder_helper_funcs = {
+	.dpms = panel_dpi_encoder_dpms,
+	.mode_fixup = panel_dpi_encoder_mode_fixup,
+	.mode_set = panel_dpi_encoder_mode_set,
+	.prepare = panel_dpi_encoder_prepare,
+	.commit = panel_dpi_encoder_commit,
+};
+
+static const struct drm_encoder_funcs panel_dpi_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static inline struct panel_dpi *
+drm_connector_to_panel_dpi(struct drm_connector *connector)
+{
+	return container_of(connector, struct panel_dpi, connector);
+}
+
+static int panel_dpi_connector_get_modes(struct drm_connector *connector)
+{
+	struct panel_dpi *dpanel = drm_connector_to_panel_dpi(connector);
+	struct panel_simple *panel = mipi_dpi_get_drvdata(dpanel->dev);
+
+	return drm_panel_get_modes(&panel->base);
+}
+
+static struct drm_encoder *
+panel_dpi_connector_best_encoder(struct drm_connector *connector)
+{
+	struct panel_dpi *dpanel = drm_connector_to_panel_dpi(connector);
+
+	return &dpanel->encoder;
+}
+
+static int panel_dpi_connector_mode_valid(struct drm_connector *connector,
+					  struct drm_display_mode *mode)
+{
+	return MODE_OK;
+}
+
+static const struct drm_connector_helper_funcs panel_dpi_connector_helper_funcs = {
+	.get_modes = panel_dpi_connector_get_modes,
+	.mode_valid = panel_dpi_connector_mode_valid,
+	.best_encoder = panel_dpi_connector_best_encoder,
+};
+
+static enum drm_connector_status
+panel_dpi_connector_detect(struct drm_connector *connector, bool force)
+{
+	return connector_status_connected;
+}
+
+static void
+panel_dpi_connector_destroy(struct drm_connector *connector)
+{
+	drm_connector_unregister(connector);
+	drm_connector_cleanup(connector);
+}
+
+static const struct drm_connector_funcs panel_dpi_connector_funcs = {
+	.dpms = drm_helper_connector_dpms,
+	.detect = panel_dpi_connector_detect,
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = panel_dpi_connector_destroy,
+};
+
+static int panel_simple_dpi_probe(struct mipi_dpi_device *dpi)
+{
+	const struct panel_desc_dpi *desc;
+	const struct of_device_id *id;
+	struct panel_simple *panel;
+	struct panel_dpi *dpanel;
+	int err;
+
+	id = of_match_node(dpi_of_match, dpi->dev.of_node);
+	if (!id)
+		return -ENODEV;
+
+	desc = id->data;
+
+	dpanel = devm_kzalloc(&dpi->dev, sizeof(*dpanel), GFP_KERNEL);
+	if (!dpanel)
+		return -ENOMEM;
+
+	dpanel->dev = dpi;
+
+	err = panel_simple_probe(&dpi->dev, &desc->desc);
+	if (err < 0)
+		return err;
+
+	dpanel->dpms_mode = DRM_MODE_DPMS_OFF;
+	dpi->supported_formats = &desc->format;
+	dpi->num_supported_formats = 1;
+
+	err = mipi_dpi_attach(dpi);
+	if (err)
+		goto err_panel_remove;
+
+	dpanel->encoder.possible_crtcs = dpi->host->possible_crtcs;
+	drm_encoder_helper_add(&dpanel->encoder,
+			       &panel_dpi_encoder_helper_funcs);
+	err = drm_encoder_init(dpi->host->ddev, &dpanel->encoder,
+			       &panel_dpi_encoder_funcs, DRM_MODE_ENCODER_DPI);
+	if (err)
+		goto err_dpi_detach;
+
+	dpanel->connector.status = connector_status_connected;
+	drm_connector_helper_add(&dpanel->connector,
+				 &panel_dpi_connector_helper_funcs);
+	err = drm_connector_init(dpi->host->ddev, &dpanel->connector,
+				 &panel_dpi_connector_funcs,
+				 DRM_MODE_CONNECTOR_DPI);
+	if (err)
+		goto err_encoder_cleanup;
+
+	panel = mipi_dpi_get_drvdata(dpi);
+	err = drm_panel_attach(&panel->base, &dpanel->connector);
+	if (err)
+		goto err_connector_cleanup;
+
+	err = drm_mode_connector_attach_encoder(&dpanel->connector,
+						&dpanel->encoder);
+	if (err)
+		goto err_panel_detach;
+
+	err = drm_connector_register(&dpanel->connector);
+	if (err)
+		goto err_panel_detach;
+
+	drm_reinit_primary_mode_group(dpi->host->ddev);
+	drm_kms_helper_hotplug_event(dpi->host->ddev);
+
+	return 0;
+
+err_panel_detach:
+	drm_panel_detach(&panel->base);
+err_connector_cleanup:
+	drm_connector_cleanup(&dpanel->connector);
+err_encoder_cleanup:
+	drm_encoder_cleanup(&dpanel->encoder);
+err_dpi_detach:
+	mipi_dpi_detach(dpi);
+err_panel_remove:
+	panel_simple_remove(&dpi->dev);
+
+	return err;
+}
+
+static int panel_simple_dpi_remove(struct mipi_dpi_device *dpi)
+{
+	struct panel_simple *panel;
+	struct panel_dpi *dpanel;
+
+	int err;
+
+	panel = mipi_dpi_get_drvdata(dpi);
+	dpanel = drm_connector_to_panel_dpi(panel->base.connector);
+
+	drm_connector_unregister(&dpanel->connector);
+	drm_connector_cleanup(&dpanel->connector);
+
+	drm_panel_detach(&panel->base);
+
+	drm_encoder_cleanup(&dpanel->encoder);
+
+	drm_reinit_primary_mode_group(dpi->host->ddev);
+
+	err = mipi_dpi_detach(dpi);
+	if (err < 0)
+		dev_err(&dpi->dev, "failed to detach from DPI host: %d\n", err);
+
+	return panel_simple_remove(&dpi->dev);
+}
+
+static void panel_simple_dpi_shutdown(struct mipi_dpi_device *dpi)
+{
+	panel_simple_shutdown(&dpi->dev);
+}
+
+static struct mipi_dpi_driver panel_simple_dpi_driver = {
+	.driver = {
+		.name = "panel-simple-dpi",
+		.owner = THIS_MODULE,
+		.of_match_table = dpi_of_match,
+	},
+	.probe = panel_simple_dpi_probe,
+	.remove = panel_simple_dpi_remove,
+	.shutdown = panel_simple_dpi_shutdown,
+};
+
 static int __init panel_simple_init(void)
 {
 	int err;
@@ -865,6 +1136,12 @@  static int __init panel_simple_init(void)
 			return err;
 	}
 
+	if (IS_ENABLED(CONFIG_DRM_MIPI_DPI)) {
+		err = mipi_dpi_driver_register(&panel_simple_dpi_driver);
+		if (err < 0)
+			return err;
+	}
+
 	return 0;
 }
 module_init(panel_simple_init);