diff options
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/gpu/drm/Kconfig | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/Makefile | 3 | ||||
-rw-r--r-- | drivers/gpu/drm/drm_virtual_encoder.c | 299 |
3 files changed, 311 insertions, 0 deletions
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 9fa8fa8b1b3a..0307359677e8 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -167,6 +167,15 @@ config DRM_SAVAGE Choose this option if you have a Savage3D/4/SuperSavage/Pro/Twister chipset. If M is selected the module will be called savage. +config DRM_VIRT_ENCODER + tristate "Virtual OF-based encoder" + depends on DRM && OF + help + Choose this option to get a virtual encoder and its associated + connector that will use the device tree to read the display + timings information. If M is selected the module will be called + drm_vencoder. + source "drivers/gpu/drm/exynos/Kconfig" source "drivers/gpu/drm/vmwgfx/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 54d07f3139d6..8d444f28a5db 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -32,6 +32,9 @@ obj-$(CONFIG_DRM_KMS_HELPER) += drm_kms_helper.o CFLAGS_drm_trace_points.o := -I$(src) +drm_vencoder-y := drm_virtual_encoder.o +obj-$(CONFIG_DRM_VIRT_ENCODER) += drm_vencoder.o + obj-$(CONFIG_DRM) += drm.o obj-$(CONFIG_DRM_MIPI_DSI) += drm_mipi_dsi.o obj-$(CONFIG_DRM_ARM) += arm/ diff --git a/drivers/gpu/drm/drm_virtual_encoder.c b/drivers/gpu/drm/drm_virtual_encoder.c new file mode 100644 index 000000000000..21522ed9b17a --- /dev/null +++ b/drivers/gpu/drm/drm_virtual_encoder.c @@ -0,0 +1,299 @@ +/* + * Copyright (C) 2016 ARM Limited + * Author: Liviu Dudau <Liviu.Dudau@arm.com> + * + * Dummy encoder and connector that use the OF to "discover" the attached + * display timings. Can be used in situations where the encoder and connector's + * functionality are emulated and no setup steps are needed, or to describe + * attached panels for which no driver exists but can be used without + * additional hardware setup. + * + * The encoder also uses the component framework so that it can be a quick + * replacement for existing drivers when testing in an emulated environment. + * + * 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. + * + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_of.h> +#include <linux/component.h> +#include <video/display_timing.h> +#include <video/of_display_timing.h> +#include <video/videomode.h> + +struct drm_virt_priv { + struct drm_connector connector; + struct drm_encoder encoder; + struct display_timings *timings; +}; + +#define connector_to_drm_virt_priv(x) \ + container_of(x, struct drm_virt_priv, connector) + +#define encoder_to_drm_virt_priv(x) \ + container_of(x, struct drm_virt_priv, encoder) + +static void drm_virtcon_destroy(struct drm_connector *connector) +{ + struct drm_virt_priv *conn = connector_to_drm_virt_priv(connector); + + drm_connector_cleanup(connector); + display_timings_release(conn->timings); +} + +static enum drm_connector_status +drm_virtcon_detect(struct drm_connector *connector, bool force) +{ + return connector_status_connected; +} + +static const struct drm_connector_funcs drm_virtcon_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .detect = drm_virtcon_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_virtcon_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static int drm_virtcon_get_modes(struct drm_connector *connector) +{ + struct drm_virt_priv *conn = connector_to_drm_virt_priv(connector); + struct display_timings *timings = conn->timings; + int i; + + for (i = 0; i < timings->num_timings; i++) { + struct drm_display_mode *mode = drm_mode_create(connector->dev); + struct videomode vm; + + if (videomode_from_timings(timings, &vm, i)) + break; + + drm_display_mode_from_videomode(&vm, mode); + mode->type = DRM_MODE_TYPE_DRIVER; + if (timings->native_mode == i) + mode->type = DRM_MODE_TYPE_PREFERRED; + + drm_mode_set_name(mode); + drm_mode_probed_add(connector, mode); + } + + return i; +} + +static int drm_virtcon_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + return MODE_OK; +} + +struct drm_encoder *drm_virtcon_best_encoder(struct drm_connector *connector) +{ + struct drm_virt_priv *priv = connector_to_drm_virt_priv(connector); + + return &priv->encoder; +} + +struct drm_encoder * +drm_virtcon_atomic_best_encoder(struct drm_connector *connector, + struct drm_connector_state *connector_state) +{ + struct drm_virt_priv *priv = connector_to_drm_virt_priv(connector); + + return &priv->encoder; +} + +static const struct drm_connector_helper_funcs drm_virtcon_helper_funcs = { + .get_modes = drm_virtcon_get_modes, + .mode_valid = drm_virtcon_mode_valid, + .best_encoder = drm_virtcon_best_encoder, + .atomic_best_encoder = drm_virtcon_atomic_best_encoder, +}; + +static void drm_vencoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static const struct drm_encoder_funcs drm_vencoder_funcs = { + .destroy = drm_vencoder_destroy, +}; + +static void drm_vencoder_dpms(struct drm_encoder *encoder, int mode) +{ + /* nothing needed */ +} + +static bool drm_vencoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ + return true; +} + +static void drm_vencoder_prepare(struct drm_encoder *encoder) +{ + drm_vencoder_dpms(encoder, DRM_MODE_DPMS_OFF); +} + +static void drm_vencoder_commit(struct drm_encoder *encoder) +{ + drm_vencoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void drm_vencoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* nothing needed */ +} + +static const struct drm_encoder_helper_funcs drm_vencoder_helper_funcs = { + .dpms = drm_vencoder_dpms, + .mode_fixup = drm_vencoder_mode_fixup, + .prepare = drm_vencoder_prepare, + .commit = drm_vencoder_commit, + .mode_set = drm_vencoder_mode_set, +}; + +static int drm_vencoder_bind(struct device *dev, struct device *master, + void *data) +{ + struct drm_encoder *encoder; + struct drm_virt_priv *con; + struct drm_connector *connector; + struct drm_device *drm = data; + u32 crtcs = 0; + int ret; + + con = devm_kzalloc(dev, sizeof(*con), GFP_KERNEL); + if (!con) + return -ENOMEM; + + dev_set_drvdata(dev, con); + connector = &con->connector; + encoder = &con->encoder; + + if (dev->of_node) { + struct drm_bridge *bridge; + crtcs = drm_of_find_possible_crtcs(drm, dev->of_node); + bridge = of_drm_find_bridge(dev->of_node); + if (bridge) { + ret = drm_bridge_attach(drm, bridge); + if (ret) { + DRM_ERROR("Failed to initialize bridge\n"); + return ret; + } + encoder->bridge = bridge; + } + con->timings = of_get_display_timings(dev->of_node); + if (!con->timings) { + dev_err(dev, "failed to get display panel timings\n"); + return ENXIO; + } + } + + /* If no CRTCs were found, fall back to the old encoder's behaviour */ + if (crtcs == 0) { + dev_warn(dev, "Falling back to first CRTC\n"); + crtcs = 1 << 0; + } + + encoder->possible_crtcs = crtcs ? crtcs : 1; + encoder->possible_clones = 0; + + ret = drm_encoder_init(drm, encoder, &drm_vencoder_funcs, + DRM_MODE_ENCODER_VIRTUAL, "virtual-encoder"); + if (ret) + goto encoder_init_err; + + drm_encoder_helper_add(encoder, &drm_vencoder_helper_funcs); + + /* bogus values, pretend we're a 24" screen for DPI calculations */ + connector->display_info.width_mm = 519; + connector->display_info.height_mm = 324; + connector->interlace_allowed = false; + connector->doublescan_allowed = false; + connector->polled = 0; + + ret = drm_connector_init(drm, connector, &drm_virtcon_funcs, + DRM_MODE_CONNECTOR_VIRTUAL); + if (ret) + goto connector_init_err; + + drm_connector_helper_add(connector, &drm_virtcon_helper_funcs); + + drm_connector_register(connector); + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) + goto attach_err; + + return ret; + +attach_err: + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +connector_init_err: + drm_encoder_cleanup(encoder); +encoder_init_err: + display_timings_release(con->timings); + + return ret; +}; + +static void drm_vencoder_unbind(struct device *dev, struct device *master, + void *data) +{ + struct drm_virt_priv *con = dev_get_drvdata(dev); + + drm_connector_unregister(&con->connector); + drm_connector_cleanup(&con->connector); + drm_encoder_cleanup(&con->encoder); + display_timings_release(con->timings); +} + +static const struct component_ops drm_vencoder_ops = { + .bind = drm_vencoder_bind, + .unbind = drm_vencoder_unbind, +}; + +static int drm_vencoder_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &drm_vencoder_ops); +} + +static int drm_vencoder_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &drm_vencoder_ops); + return 0; +} + +static const struct of_device_id drm_vencoder_of_match[] = { + { .compatible = "drm,virtual-encoder", }, + {}, +}; +MODULE_DEVICE_TABLE(of, drm_vencoder_of_match); + +static struct platform_driver drm_vencoder_driver = { + .probe = drm_vencoder_probe, + .remove = drm_vencoder_remove, + .driver = { + .name = "drm_vencoder", + .of_match_table = drm_vencoder_of_match, + }, +}; + +module_platform_driver(drm_vencoder_driver); + +MODULE_AUTHOR("Liviu Dudau"); +MODULE_DESCRIPTION("Virtual DRM Encoder"); +MODULE_LICENSE("GPL v2"); |