diff options
| author | Ajay Singh Parmar <aparmar@codeaurora.org> | 2016-05-16 17:56:30 -0700 |
|---|---|---|
| committer | Dhaval Patel <pdhaval@codeaurora.org> | 2016-08-01 11:58:13 -0700 |
| commit | bee52f9067ad433e82f6d2b8c7c195ca95745002 (patch) | |
| tree | 2645041ea5dcbfe56578d93c01dd622249cb113e /drivers/gpu | |
| parent | 7b778d492026b62b96dbfc145a70b8af50dfab41 (diff) | |
drm/msm/dsi-staging: add dsi drm interface
Add DSI DRM component to interface with the DRM framework.
Configure DSI bridge and connector and hook up with DRM
framework.
Change-Id: I7c4c91047fece87e24955ffc35bf6681a8235cab
Signed-off-by: Ajay Singh Parmar <aparmar@codeaurora.org>
Diffstat (limited to 'drivers/gpu')
| -rw-r--r-- | drivers/gpu/drm/msm/dsi-staging/dsi_drm.c | 561 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/dsi-staging/dsi_drm.h | 67 |
2 files changed, 628 insertions, 0 deletions
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c new file mode 100644 index 000000000000..26d1155a7298 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.c @@ -0,0 +1,561 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + + +#define pr_fmt(fmt) "dsi-drm:[%s] " fmt, __func__ +#include <drm/drm_atomic_helper.h> +#include <drm/drm_atomic.h> + +#include "dsi_drm.h" + +#define to_dsi_bridge(x) container_of((x), struct dsi_bridge, base) +#define to_dsi_connector(x) container_of((x), struct dsi_connector, base) +#define to_dsi_state(x) container_of((x), struct dsi_connector_state, base) + +static void convert_to_dsi_mode(const struct drm_display_mode *drm_mode, + struct dsi_display_mode *dsi_mode) +{ + dsi_mode->timing.h_active = drm_mode->hdisplay; + dsi_mode->timing.h_back_porch = drm_mode->htotal - drm_mode->hsync_end; + dsi_mode->timing.h_sync_width = drm_mode->htotal - + (drm_mode->hsync_start + dsi_mode->timing.h_back_porch); + dsi_mode->timing.h_front_porch = drm_mode->hsync_start - + drm_mode->hdisplay; + dsi_mode->timing.h_skew = drm_mode->hskew; + + dsi_mode->timing.v_active = drm_mode->vdisplay; + dsi_mode->timing.v_back_porch = drm_mode->vtotal - drm_mode->vsync_end; + dsi_mode->timing.v_sync_width = drm_mode->vtotal - + (drm_mode->vsync_start + dsi_mode->timing.v_back_porch); + + dsi_mode->timing.v_front_porch = drm_mode->vsync_start - + drm_mode->vdisplay; + + dsi_mode->timing.refresh_rate = drm_mode->vrefresh; + + dsi_mode->pixel_clk_khz = drm_mode->clock; +} + +static void convert_to_drm_mode(const struct dsi_display_mode *dsi_mode, + struct drm_display_mode *drm_mode) +{ + drm_mode->hdisplay = dsi_mode->timing.h_active; + drm_mode->hsync_start = drm_mode->hdisplay + + dsi_mode->timing.h_front_porch; + drm_mode->hsync_end = drm_mode->hsync_start + + dsi_mode->timing.h_sync_width; + drm_mode->htotal = drm_mode->hsync_end + dsi_mode->timing.h_back_porch; + drm_mode->hskew = dsi_mode->timing.h_skew; + + drm_mode->vdisplay = dsi_mode->timing.v_active; + drm_mode->vsync_start = drm_mode->vdisplay + + dsi_mode->timing.v_front_porch; + drm_mode->vsync_end = drm_mode->vsync_start + + dsi_mode->timing.v_sync_width; + drm_mode->vtotal = drm_mode->vsync_end + dsi_mode->timing.v_back_porch; + + drm_mode->vrefresh = dsi_mode->timing.refresh_rate; + drm_mode->clock = dsi_mode->pixel_clk_khz; + + drm_mode_set_name(drm_mode); +} + +static int dsi_bridge_attach(struct drm_bridge *bridge) +{ + struct dsi_bridge *c_bridge = to_dsi_bridge(bridge); + + if (!c_bridge) { + pr_err("Invalid params\n"); + return -EINVAL; + } + + pr_debug("Bridge %d attached\n", c_bridge->id); + + return 0; + +} + +static void dsi_bridge_pre_enable(struct drm_bridge *bridge) +{ + int rc = 0; + struct dsi_bridge *c_bridge = to_dsi_bridge(bridge); + + if (!c_bridge) { + pr_err("Invalid params\n"); + rc = -EINVAL; + } + + rc = dsi_display_prepare(c_bridge->display); + if (rc) { + pr_err("[%d]DSI display prepare failed, rc=%d\n", + c_bridge->id, rc); + goto end; + } + + rc = dsi_display_enable(c_bridge->display); + if (rc) { + pr_err("[%d]DSI display enable failed, rc=%d\n", + c_bridge->id, rc); + goto error_unprep_display; + } + + return; +error_unprep_display: + (void)dsi_display_unprepare(c_bridge->display); +end: + pr_debug(""); +} + +static void dsi_bridge_enable(struct drm_bridge *bridge) +{ + int rc = 0; + struct dsi_bridge *c_bridge = to_dsi_bridge(bridge); + + if (!c_bridge) { + pr_err("Invalid params\n"); + return; + } + + rc = dsi_display_post_enable(c_bridge->display); + if (rc) + pr_err("[%d]DSI display post enabled failed, rc=%d\n", + c_bridge->id, rc); +} + +static void dsi_bridge_disable(struct drm_bridge *bridge) +{ + int rc = 0; + struct dsi_bridge *c_bridge = to_dsi_bridge(bridge); + + if (!c_bridge) { + pr_err("Invalid params\n"); + rc = -EINVAL; + goto error; + } + + rc = dsi_display_pre_disable(c_bridge->display); + if (rc) { + pr_err("[%d] dsi display pre disable failed, rc=%d\n", + c_bridge->id, rc); + goto error; + } +error: + pr_debug(""); +} + +static void dsi_bridge_post_disable(struct drm_bridge *bridge) +{ + int rc = 0; + struct dsi_bridge *c_bridge = to_dsi_bridge(bridge); + + if (!c_bridge) { + pr_err("Invalid params\n"); + return; + } + + rc = dsi_display_disable(c_bridge->display); + if (rc) { + pr_err("[%d] DSI display disable failed, rc=%d\n", + c_bridge->id, rc); + goto error; + } + + rc = dsi_display_unprepare(c_bridge->display); + if (rc) { + pr_err("[%d] DSI display unprepare failed, rc=%d\n", + c_bridge->id, rc); + goto error; + } + +error: + pr_debug("[%d] DSI bridge post disable done\n", c_bridge->id); +} + +static void dsi_bridge_mode_set(struct drm_bridge *bridge, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + int rc = 0; + struct dsi_bridge *c_bridge = to_dsi_bridge(bridge); + struct dsi_display_mode dsi_mode; + + if (!c_bridge || !mode || !adjusted_mode) { + pr_err("Invalid params\n"); + rc = -EINVAL; + } + + memset(&dsi_mode, 0x0, sizeof(dsi_mode)); + + convert_to_dsi_mode(adjusted_mode, &dsi_mode); + + /* By this point mode should have been validated through mode_fixup */ + rc = dsi_display_set_mode(c_bridge->display, &dsi_mode, 0x0); + if (rc) + pr_err("[%d] failed to perform a mode set, rc=%d\n", + c_bridge->id, rc); +} + +static bool dsi_bridge_mode_fixup(struct drm_bridge *bridge, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + int rc = 0; + bool ret = true; + struct dsi_bridge *c_bridge = to_dsi_bridge(bridge); + struct dsi_display_mode dsi_mode; + + if (!c_bridge || !mode || !adjusted_mode) { + pr_err("Invalid params\n"); + return false; + } + + convert_to_dsi_mode(mode, &dsi_mode); + + rc = dsi_display_validate_mode(c_bridge->display, &dsi_mode); + if (rc) { + pr_err("[%d] Mode is not valid, rc=%d\n", c_bridge->id, rc); + ret = false; + } + + return ret; +} + +static const struct drm_bridge_funcs dsi_bridge_ops = { + .attach = dsi_bridge_attach, + .mode_fixup = dsi_bridge_mode_fixup, + .pre_enable = dsi_bridge_pre_enable, + .enable = dsi_bridge_enable, + .disable = dsi_bridge_disable, + .post_disable = dsi_bridge_post_disable, + .mode_set = dsi_bridge_mode_set, +}; + +static enum drm_connector_status dsi_conn_detect(struct drm_connector *conn, + bool force) +{ + int rc = 0; + enum drm_connector_status status = connector_status_connected; + struct dsi_connector *c_conn = to_dsi_connector(conn); + struct dsi_display_info info; + struct drm_property_blob *blob; + size_t len; + + memset(&info, 0x0, sizeof(info)); + rc = dsi_display_get_info(c_conn->display, &info); + if (rc) { + pr_err("[%d] failed to get display info, rc=%d\n", + c_conn->id, rc); + status = connector_status_disconnected; + goto error; + } + + if (info.is_hot_pluggable) { + status = (info.is_connected ? connector_status_connected : + connector_status_disconnected); + } + + conn->display_info.width_mm = info.width_mm; + conn->display_info.height_mm = info.height_mm; + + len = strnlen(info.display_type, sizeof(info.display_type)); + + blob = drm_property_create_blob(conn->dev, + len, + info.display_type); + if (IS_ERR_OR_NULL(blob)) { + rc = PTR_ERR(blob); + pr_err("failed to create blob, rc=%d\n", rc); + status = connector_status_connected; + goto error; + } + + rc = drm_object_property_set_value(&conn->base, + c_conn->display_type, + blob->base.id); + if (rc) { + pr_err("failed to update display_type prop, rc=%d\n", rc); + status = connector_status_disconnected; + drm_property_unreference_blob(blob); + goto error; + } + + c_conn->display_type_blob = blob; +error: + return status; +} + +static void dsi_connector_destroy(struct drm_connector *connector) +{ + struct dsi_connector *c_conn = to_dsi_connector(connector); + + drm_connector_unregister(connector); + drm_connector_cleanup(connector); + kfree(c_conn); +} + +static struct drm_connector_state * +dsi_connector_atomic_dup_state(struct drm_connector *connector) +{ + struct dsi_connector_state *state = to_dsi_state(connector->state); + struct dsi_connector_state *duplicate; + + duplicate = kmemdup(state, sizeof(*state), GFP_KERNEL); + if (!duplicate) + return NULL; + + return &duplicate->base; +} + +static void dsi_connector_atomic_destroy_state(struct drm_connector *conn, + struct drm_connector_state *state) +{ + struct dsi_connector_state *c_state = to_dsi_state(state); + + kfree(c_state); +} + +static int dsi_connector_get_modes(struct drm_connector *connector) +{ + int rc = 0; + u32 count = 0; + u32 size = 0; + int i = 0; + struct dsi_connector *c_conn = to_dsi_connector(connector); + struct dsi_display_mode *modes; + struct drm_display_mode drm_mode; + + if (c_conn->panel) { + /* + * TODO: If drm_panel is attached, query modes from the panel. + * This is complicated in split dsi cases because panel is not + * attached to both connectors. + */ + goto end; + } + + rc = dsi_display_get_modes(c_conn->display, NULL, &count); + if (rc) { + pr_err("[%d] failed to get num of modes, rc=%d\n", + c_conn->id, rc); + goto error; + } + + size = count * sizeof(*modes); + modes = kzalloc(size, GFP_KERNEL); + if (!modes) { + count = 0; + goto end; + } + + rc = dsi_display_get_modes(c_conn->display, modes, &count); + if (rc) { + pr_err("[%d] failed to get modes, rc=%d\n", + c_conn->id, rc); + count = 0; + goto error; + } + + for (i = 0; i < count; i++) { + struct drm_display_mode *m; + + memset(&drm_mode, 0x0, sizeof(drm_mode)); + convert_to_drm_mode(&modes[i], &drm_mode); + m = drm_mode_duplicate(connector->dev, &drm_mode); + if (!m) { + pr_err("[%d] failed to add mode %ux%u\n", + c_conn->id, drm_mode.hdisplay, + drm_mode.vdisplay); + count = -ENOMEM; + goto error; + } + m->width_mm = connector->display_info.width_mm; + m->height_mm = connector->display_info.height_mm; + drm_mode_probed_add(connector, m); + } +error: + kfree(modes); +end: + pr_debug("MODE COUNT =%d\n\n", count); + return count; +} + +static enum drm_mode_status dsi_conn_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + int rc = 0; + struct dsi_display_mode dsi_mode; + struct dsi_connector *c_conn = to_dsi_connector(connector); + + if (!connector || !mode) { + pr_err("Invalid params\n"); + return MODE_ERROR; + } + + convert_to_dsi_mode(mode, &dsi_mode); + + rc = dsi_display_validate_mode(c_conn->display, &dsi_mode); + if (rc) { + pr_err("[%d] mode not supported, rc=%d\n", c_conn->id, rc); + return MODE_BAD; + } + + return MODE_OK; +} + +static struct drm_encoder *dsi_conn_best_encoder(struct drm_connector *conn) +{ + struct dsi_connector *connector = to_dsi_connector(conn); + /* + * This is true for now, revisit this code when multiple encoders are + * supported. + */ + return connector->encoder; +} + + +static const struct drm_connector_funcs dsi_conn_ops = { + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + + .detect = dsi_conn_detect, + .destroy = dsi_connector_destroy, + .fill_modes = drm_helper_probe_single_connector_modes, + .atomic_duplicate_state = dsi_connector_atomic_dup_state, + .atomic_destroy_state = dsi_connector_atomic_destroy_state, +}; + +static const struct dsi_connector_helper_funcs dsi_conn_helper_ops = { + .base = { + .get_modes = dsi_connector_get_modes, + .mode_valid = dsi_conn_mode_valid, + .best_encoder = dsi_conn_best_encoder, + }, +}; + +struct dsi_connector *dsi_drm_connector_init(struct dsi_display *display, + struct drm_device *dev, + struct dsi_bridge *bridge) +{ + int rc = 0; + struct dsi_connector *conn; + struct drm_property *blob; + + conn = kzalloc(sizeof(*conn), GFP_KERNEL); + if (!conn) { + rc = -ENOMEM; + goto error; + } + + conn->display = display; + + rc = drm_connector_init(dev, + &conn->base, + &dsi_conn_ops, + DRM_MODE_CONNECTOR_DSI); + if (rc) { + pr_err("failed to initialize drm connector, rc=%d\n", rc); + goto error_free_conn; + } + + conn->base.helper_private = &dsi_conn_helper_ops; + + conn->base.polled = DRM_CONNECTOR_POLL_HPD; + conn->base.interlace_allowed = 0; + conn->base.doublescan_allowed = 0; + + rc = drm_connector_register(&conn->base); + if (rc) { + pr_err("failed to register drm connector, rc=%d\n", rc); + goto error_cleanup_conn; + } + + rc = drm_mode_connector_attach_encoder(&conn->base, + bridge->base.encoder); + if (rc) { + pr_err("failed to attach encoder to connector, rc=%d\n", rc); + goto error_unregister_conn; + } + + conn->encoder = bridge->base.encoder; + + blob = drm_property_create(dev, + DRM_MODE_PROP_BLOB | DRM_MODE_PROP_IMMUTABLE, + "DISPLAY_TYPE", 0); + if (!blob) { + pr_err("Failed to create DISPLAY_TYPE property\n"); + goto error_unregister_conn; + } + + drm_object_attach_property(&conn->base.base, blob, 0); + conn->display_type = blob; + + return conn; + +error_unregister_conn: + drm_connector_unregister(&conn->base); +error_cleanup_conn: + drm_connector_cleanup(&conn->base); +error_free_conn: + kfree(conn); +error: + return ERR_PTR(rc); +} + +void dsi_drm_connector_cleanup(struct dsi_connector *conn) +{ + drm_connector_unregister(&conn->base); + drm_connector_cleanup(&conn->base); + kfree(conn); +} + +struct dsi_bridge *dsi_drm_bridge_init(struct dsi_display *display, + struct drm_device *dev, + struct drm_encoder *encoder) +{ + int rc = 0; + struct dsi_bridge *bridge; + + bridge = kzalloc(sizeof(*bridge), GFP_KERNEL); + if (!bridge) { + rc = -ENOMEM; + goto error; + } + + bridge->display = display; + bridge->base.funcs = &dsi_bridge_ops; + bridge->base.encoder = encoder; + + rc = drm_bridge_attach(dev, &bridge->base); + if (rc) { + pr_err("failed to attach bridge, rc=%d\n", rc); + goto error_free_bridge; + } + + encoder->bridge = &bridge->base; + return bridge; +error_free_bridge: + kfree(bridge); +error: + return ERR_PTR(rc); +} + +void dsi_drm_bridge_cleanup(struct dsi_bridge *bridge) +{ + struct drm_encoder *encoder = bridge->base.encoder; + + if (!encoder) + encoder->bridge = NULL; + + kfree(bridge); +} diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_drm.h b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.h new file mode 100644 index 000000000000..905341fed8e6 --- /dev/null +++ b/drivers/gpu/drm/msm/dsi-staging/dsi_drm.h @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _DSI_DRM_H_ +#define _DSI_DRM_H_ + +#include <linux/types.h> +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> + +#include "dsi_display.h" + +struct dsi_connector_state { + struct drm_connector_state base; +}; + +struct dsi_connector_helper_funcs { + struct drm_connector_helper_funcs base; +}; + +struct dsi_connector { + struct drm_connector base; + u32 id; + + struct dsi_display *display; + struct drm_panel *panel; + struct drm_encoder *encoder; + + struct drm_property *display_type; + struct drm_property_blob *display_type_blob; + + enum dsi_display_type type; + bool has_tile; + u32 h_tile_id; + bool is_master; +}; + +struct dsi_bridge { + struct drm_bridge base; + u32 id; + + struct dsi_display *display; +}; + +struct dsi_connector *dsi_drm_connector_init(struct dsi_display *display, + struct drm_device *dev, + struct dsi_bridge *bridge); +void dsi_drm_connector_cleanup(struct dsi_connector *conn); + +struct dsi_bridge *dsi_drm_bridge_init(struct dsi_display *display, + struct drm_device *dev, + struct drm_encoder *encoder); + +void dsi_drm_bridge_cleanup(struct dsi_bridge *bridge); +#endif /* _DSI_DRM_H_ */ |
