From patchwork Sat Feb 11 18:48:52 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Noralf_Tr=C3=B8nnes?= X-Patchwork-Id: 9568083 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 0236F601C3 for ; Sat, 11 Feb 2017 18:49:15 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id D1DDE2845D for ; Sat, 11 Feb 2017 18:49:14 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id B38A5284E9; Sat, 11 Feb 2017 18:49:14 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-4.2 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_MED autolearn=ham version=3.3.1 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher DHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.wl.linuxfoundation.org (Postfix) with ESMTPS id 02E762845D for ; Sat, 11 Feb 2017 18:49:12 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id AF7F16E2DC; Sat, 11 Feb 2017 18:49:10 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from smtp.domeneshop.no (smtp.domeneshop.no [IPv6:2a01:5b40:0:3005::1]) by gabe.freedesktop.org (Postfix) with ESMTPS id 88A2B6E2DB for ; Sat, 11 Feb 2017 18:49:09 +0000 (UTC) Received: from 211.81-166-168.customer.lyse.net ([81.166.168.211]:59196 helo=localhost.localdomain) by smtp.domeneshop.no with esmtpsa (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.84_2) (envelope-from ) id 1cccjL-0007Li-II; Sat, 11 Feb 2017 19:49:07 +0100 From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= To: dri-devel@lists.freedesktop.org, devicetree@vger.kernel.org Subject: [PATCH v4 1/7] drm: Add DRM support for tiny LCD displays Date: Sat, 11 Feb 2017 19:48:52 +0100 Message-Id: <20170211184858.26421-2-noralf@tronnes.org> X-Mailer: git-send-email 2.10.2 In-Reply-To: <20170211184858.26421-1-noralf@tronnes.org> References: <20170211184858.26421-1-noralf@tronnes.org> MIME-Version: 1.0 Cc: thomas.petazzoni@free-electrons.com, linux-kernel@vger.kernel.org X-BeenThere: dri-devel@lists.freedesktop.org X-Mailman-Version: 2.1.18 Precedence: list List-Id: Direct Rendering Infrastructure - Development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Virus-Scanned: ClamAV using ClamSMTP tinydrm provides helpers for very simple displays that can use CMA backed framebuffers and need flushing on changes. Signed-off-by: Noralf Trønnes Acked-by: Daniel Vetter Acked-by: Thierry Reding --- Changes since version 3: - Rebase on new drm_fbdev_cma_init_with_funcs() signature Documentation/gpu/index.rst | 1 + Documentation/gpu/tinydrm.rst | 21 ++ drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/tinydrm/Kconfig | 8 + drivers/gpu/drm/tinydrm/Makefile | 1 + drivers/gpu/drm/tinydrm/core/Makefile | 3 + drivers/gpu/drm/tinydrm/core/tinydrm-core.c | 376 ++++++++++++++++++++++++++++ drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c | 234 +++++++++++++++++ include/drm/tinydrm/tinydrm.h | 115 +++++++++ 10 files changed, 762 insertions(+) create mode 100644 Documentation/gpu/tinydrm.rst create mode 100644 drivers/gpu/drm/tinydrm/Kconfig create mode 100644 drivers/gpu/drm/tinydrm/Makefile create mode 100644 drivers/gpu/drm/tinydrm/core/Makefile create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-core.c create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c create mode 100644 include/drm/tinydrm/tinydrm.h -- 2.10.2 diff --git a/Documentation/gpu/index.rst b/Documentation/gpu/index.rst index 367d7c3..f81278a 100644 --- a/Documentation/gpu/index.rst +++ b/Documentation/gpu/index.rst @@ -11,6 +11,7 @@ Linux GPU Driver Developer's Guide drm-kms-helpers drm-uapi i915 + tinydrm vga-switcheroo vgaarbiter diff --git a/Documentation/gpu/tinydrm.rst b/Documentation/gpu/tinydrm.rst new file mode 100644 index 0000000..ec4a20d --- /dev/null +++ b/Documentation/gpu/tinydrm.rst @@ -0,0 +1,21 @@ +========================== +drm/tinydrm Driver library +========================== + +.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c + :doc: overview + +Core functionality +================== + +.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c + :doc: core + +.. kernel-doc:: include/drm/tinydrm/tinydrm.h + :internal: + +.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-core.c + :export: + +.. kernel-doc:: drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c + :export: diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index 90bc65d..88e01e08e 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -263,6 +263,8 @@ source "drivers/gpu/drm/mxsfb/Kconfig" source "drivers/gpu/drm/meson/Kconfig" +source "drivers/gpu/drm/tinydrm/Kconfig" + # Keep legacy drivers last menuconfig DRM_LEGACY diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 92de399..3ee9579 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -94,3 +94,4 @@ obj-$(CONFIG_DRM_ARCPGU)+= arc/ obj-y += hisilicon/ obj-$(CONFIG_DRM_ZTE) += zte/ obj-$(CONFIG_DRM_MXSFB) += mxsfb/ +obj-$(CONFIG_DRM_TINYDRM) += tinydrm/ diff --git a/drivers/gpu/drm/tinydrm/Kconfig b/drivers/gpu/drm/tinydrm/Kconfig new file mode 100644 index 0000000..ffb873f --- /dev/null +++ b/drivers/gpu/drm/tinydrm/Kconfig @@ -0,0 +1,8 @@ +menuconfig DRM_TINYDRM + tristate "Support for simple displays" + depends on DRM + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + help + Choose this option if you have a tinydrm supported display. + If M is selected the module will be called tinydrm. diff --git a/drivers/gpu/drm/tinydrm/Makefile b/drivers/gpu/drm/tinydrm/Makefile new file mode 100644 index 0000000..7476ed1 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_DRM_TINYDRM) += core/ diff --git a/drivers/gpu/drm/tinydrm/core/Makefile b/drivers/gpu/drm/tinydrm/core/Makefile new file mode 100644 index 0000000..4f14a0f --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/Makefile @@ -0,0 +1,3 @@ +tinydrm-y := tinydrm-core.o tinydrm-pipe.o + +obj-$(CONFIG_DRM_TINYDRM) += tinydrm.o diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-core.c b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c new file mode 100644 index 0000000..6a257dd --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2016 Noralf Trønnes + * + * 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 +#include + +/** + * DOC: overview + * + * This library provides driver helpers for very simple display hardware. + * + * It is based on &drm_simple_display_pipe coupled with a &drm_connector which + * has only one fixed &drm_display_mode. The framebuffers are backed by the + * cma helper and have support for framebuffer flushing (dirty). + * fbdev support is also included. + * + */ + +/** + * DOC: core + * + * The driver allocates &tinydrm_device, initializes it using + * devm_tinydrm_init(), sets up the pipeline using tinydrm_display_pipe_init() + * and registers the DRM device using devm_tinydrm_register(). + */ + +/** + * tinydrm_lastclose - DRM lastclose helper + * @drm: DRM device + * + * This function ensures that fbdev is restored when drm_lastclose() is called + * on the last drm_release(). Drivers can use this as their + * &drm_driver->lastclose callback. + */ +void tinydrm_lastclose(struct drm_device *drm) +{ + struct tinydrm_device *tdev = drm->dev_private; + + DRM_DEBUG_KMS("\n"); + drm_fbdev_cma_restore_mode(tdev->fbdev_cma); +} +EXPORT_SYMBOL(tinydrm_lastclose); + +/** + * tinydrm_gem_cma_prime_import_sg_table - Produce a CMA GEM object from + * another driver's scatter/gather table of pinned pages + * @drm: DRM device to import into + * @attach: DMA-BUF attachment + * @sgt: Scatter/gather table of pinned pages + * + * This function imports a scatter/gather table exported via DMA-BUF by + * another driver using drm_gem_cma_prime_import_sg_table(). It sets the + * kernel virtual address on the CMA object. Drivers should use this as their + * &drm_driver->gem_prime_import_sg_table callback if they need the virtual + * address. tinydrm_gem_cma_free_object() should be used in combination with + * this function. + * + * Returns: + * A pointer to a newly created GEM object or an ERR_PTR-encoded negative + * error code on failure. + */ +struct drm_gem_object * +tinydrm_gem_cma_prime_import_sg_table(struct drm_device *drm, + struct dma_buf_attachment *attach, + struct sg_table *sgt) +{ + struct drm_gem_cma_object *cma_obj; + struct drm_gem_object *obj; + void *vaddr; + + vaddr = dma_buf_vmap(attach->dmabuf); + if (!vaddr) { + DRM_ERROR("Failed to vmap PRIME buffer\n"); + return ERR_PTR(-ENOMEM); + } + + obj = drm_gem_cma_prime_import_sg_table(drm, attach, sgt); + if (IS_ERR(obj)) { + dma_buf_vunmap(attach->dmabuf, vaddr); + return obj; + } + + cma_obj = to_drm_gem_cma_obj(obj); + cma_obj->vaddr = vaddr; + + return obj; +} +EXPORT_SYMBOL(tinydrm_gem_cma_prime_import_sg_table); + +/** + * tinydrm_gem_cma_free_object - Free resources associated with a CMA GEM + * object + * @gem_obj: GEM object to free + * + * This function frees the backing memory of the CMA GEM object, cleans up the + * GEM object state and frees the memory used to store the object itself using + * drm_gem_cma_free_object(). It also handles PRIME buffers which has the kernel + * virtual address set by tinydrm_gem_cma_prime_import_sg_table(). Drivers + * can use this as their &drm_driver->gem_free_object callback. + */ +void tinydrm_gem_cma_free_object(struct drm_gem_object *gem_obj) +{ + if (gem_obj->import_attach) { + struct drm_gem_cma_object *cma_obj; + + cma_obj = to_drm_gem_cma_obj(gem_obj); + dma_buf_vunmap(gem_obj->import_attach->dmabuf, cma_obj->vaddr); + cma_obj->vaddr = NULL; + } + + drm_gem_cma_free_object(gem_obj); +} +EXPORT_SYMBOL_GPL(tinydrm_gem_cma_free_object); + +const struct file_operations tinydrm_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .release = drm_release, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .poll = drm_poll, + .read = drm_read, + .llseek = no_llseek, + .mmap = drm_gem_cma_mmap, +}; +EXPORT_SYMBOL(tinydrm_fops); + +static struct drm_framebuffer * +tinydrm_fb_create(struct drm_device *drm, struct drm_file *file_priv, + const struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct tinydrm_device *tdev = drm->dev_private; + + return drm_fb_cma_create_with_funcs(drm, file_priv, mode_cmd, + tdev->fb_funcs); +} + +static const struct drm_mode_config_funcs tinydrm_mode_config_funcs = { + .fb_create = tinydrm_fb_create, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, +}; + +static int tinydrm_init(struct device *parent, struct tinydrm_device *tdev, + const struct drm_framebuffer_funcs *fb_funcs, + struct drm_driver *driver) +{ + struct drm_device *drm; + + mutex_init(&tdev->dirty_lock); + tdev->fb_funcs = fb_funcs; + + /* + * We don't embed drm_device, because that prevent us from using + * devm_kzalloc() to allocate tinydrm_device in the driver since + * drm_dev_unref() frees the structure. The devm_ functions provide + * for easy error handling. + */ + drm = drm_dev_alloc(driver, parent); + if (IS_ERR(drm)) + return PTR_ERR(drm); + + tdev->drm = drm; + drm->dev_private = tdev; + drm_mode_config_init(drm); + drm->mode_config.funcs = &tinydrm_mode_config_funcs; + + return 0; +} + +static void tinydrm_fini(struct tinydrm_device *tdev) +{ + drm_mode_config_cleanup(tdev->drm); + mutex_destroy(&tdev->dirty_lock); + tdev->drm->dev_private = NULL; + drm_dev_unref(tdev->drm); +} + +static void devm_tinydrm_release(void *data) +{ + tinydrm_fini(data); +} + +/** + * devm_tinydrm_init - Initialize tinydrm device + * @parent: Parent device object + * @tdev: tinydrm device + * @fb_funcs: Framebuffer functions + * @driver: DRM driver + * + * This function initializes @tdev, the underlying DRM device and it's + * mode_config. Resources will be automatically freed on driver detach (devres) + * using drm_mode_config_cleanup() and drm_dev_unref(). + * + * Returns: + * Zero on success, negative error code on failure. + */ +int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev, + const struct drm_framebuffer_funcs *fb_funcs, + struct drm_driver *driver) +{ + int ret; + + ret = tinydrm_init(parent, tdev, fb_funcs, driver); + if (ret) + return ret; + + ret = devm_add_action(parent, devm_tinydrm_release, tdev); + if (ret) + tinydrm_fini(tdev); + + return ret; +} +EXPORT_SYMBOL(devm_tinydrm_init); + +static int tinydrm_register(struct tinydrm_device *tdev) +{ + struct drm_device *drm = tdev->drm; + int bpp = drm->mode_config.preferred_depth; + struct drm_fbdev_cma *fbdev; + int ret; + + ret = drm_dev_register(tdev->drm, 0); + if (ret) + return ret; + + fbdev = drm_fbdev_cma_init_with_funcs(drm, bpp ? bpp : 32, + drm->mode_config.num_connector, + tdev->fb_funcs); + if (IS_ERR(fbdev)) + DRM_ERROR("Failed to initialize fbdev: %ld\n", PTR_ERR(fbdev)); + else + tdev->fbdev_cma = fbdev; + + return 0; +} + +static void tinydrm_unregister(struct tinydrm_device *tdev) +{ + struct drm_fbdev_cma *fbdev_cma = tdev->fbdev_cma; + + drm_crtc_force_disable_all(tdev->drm); + /* don't restore fbdev in lastclose, keep pipeline disabled */ + tdev->fbdev_cma = NULL; + drm_dev_unregister(tdev->drm); + if (fbdev_cma) + drm_fbdev_cma_fini(fbdev_cma); +} + +static void devm_tinydrm_register_release(void *data) +{ + tinydrm_unregister(data); +} + +/** + * devm_tinydrm_register - Register tinydrm device + * @tdev: tinydrm device + * + * This function registers the underlying DRM device and fbdev. + * These resources will be automatically unregistered on driver detach (devres) + * and the display pipeline will be disabled. + * + * Returns: + * Zero on success, negative error code on failure. + */ +int devm_tinydrm_register(struct tinydrm_device *tdev) +{ + struct device *dev = tdev->drm->dev; + int ret; + + ret = tinydrm_register(tdev); + if (ret) + return ret; + + ret = devm_add_action(dev, devm_tinydrm_register_release, tdev); + if (ret) + tinydrm_unregister(tdev); + + return ret; +} +EXPORT_SYMBOL(devm_tinydrm_register); + +/** + * tinydrm_shutdown - Shutdown tinydrm + * @tdev: tinydrm device + * + * This function makes sure that the display pipeline is disabled. + * Used by drivers in their shutdown callback to turn off the display + * on machine shutdown and reboot. + */ +void tinydrm_shutdown(struct tinydrm_device *tdev) +{ + drm_crtc_force_disable_all(tdev->drm); +} +EXPORT_SYMBOL(tinydrm_shutdown); + +/** + * tinydrm_suspend - Suspend tinydrm + * @tdev: tinydrm device + * + * Used in driver PM operations to suspend tinydrm. + * Suspends fbdev and DRM. + * Resume with tinydrm_resume(). + * + * Returns: + * Zero on success, negative error code on failure. + */ +int tinydrm_suspend(struct tinydrm_device *tdev) +{ + struct drm_atomic_state *state; + + if (tdev->suspend_state) { + DRM_ERROR("Failed to suspend: state already set\n"); + return -EINVAL; + } + + drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 1); + state = drm_atomic_helper_suspend(tdev->drm); + if (IS_ERR(state)) { + drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 0); + return PTR_ERR(state); + } + + tdev->suspend_state = state; + + return 0; +} +EXPORT_SYMBOL(tinydrm_suspend); + +/** + * tinydrm_resume - Resume tinydrm + * @tdev: tinydrm device + * + * Used in driver PM operations to resume tinydrm. + * Suspend with tinydrm_suspend(). + * + * Returns: + * Zero on success, negative error code on failure. + */ +int tinydrm_resume(struct tinydrm_device *tdev) +{ + struct drm_atomic_state *state = tdev->suspend_state; + int ret; + + if (!state) { + DRM_ERROR("Failed to resume: state is not set\n"); + return -EINVAL; + } + + tdev->suspend_state = NULL; + + ret = drm_atomic_helper_resume(tdev->drm, state); + if (ret) { + DRM_ERROR("Error resuming state: %d\n", ret); + return ret; + } + + drm_fbdev_cma_set_suspend_unlocked(tdev->fbdev_cma, 0); + + return 0; +} +EXPORT_SYMBOL(tinydrm_resume); + +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c new file mode 100644 index 0000000..ec43fb7 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-pipe.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2016 Noralf Trønnes + * + * 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 + +struct tinydrm_connector { + struct drm_connector base; + const struct drm_display_mode *mode; +}; + +static inline struct tinydrm_connector * +to_tinydrm_connector(struct drm_connector *connector) +{ + return container_of(connector, struct tinydrm_connector, base); +} + +static int tinydrm_connector_get_modes(struct drm_connector *connector) +{ + struct tinydrm_connector *tconn = to_tinydrm_connector(connector); + struct drm_display_mode *mode; + + mode = drm_mode_duplicate(connector->dev, tconn->mode); + if (!mode) { + DRM_ERROR("Failed to duplicate mode\n"); + return 0; + } + + if (mode->name[0] == '\0') + drm_mode_set_name(mode); + + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + if (mode->width_mm) { + connector->display_info.width_mm = mode->width_mm; + connector->display_info.height_mm = mode->height_mm; + } + + return 1; +} + +static const struct drm_connector_helper_funcs tinydrm_connector_hfuncs = { + .get_modes = tinydrm_connector_get_modes, + .best_encoder = drm_atomic_helper_best_encoder, +}; + +static enum drm_connector_status +tinydrm_connector_detect(struct drm_connector *connector, bool force) +{ + if (drm_device_is_unplugged(connector->dev)) + return connector_status_disconnected; + + return connector->status; +} + +static void tinydrm_connector_destroy(struct drm_connector *connector) +{ + struct tinydrm_connector *tconn = to_tinydrm_connector(connector); + + drm_connector_cleanup(connector); + kfree(tconn); +} + +static const struct drm_connector_funcs tinydrm_connector_funcs = { + .dpms = drm_atomic_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .detect = tinydrm_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = tinydrm_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +struct drm_connector * +tinydrm_connector_create(struct drm_device *drm, + const struct drm_display_mode *mode, + int connector_type) +{ + struct tinydrm_connector *tconn; + struct drm_connector *connector; + int ret; + + tconn = kzalloc(sizeof(*tconn), GFP_KERNEL); + if (!tconn) + return ERR_PTR(-ENOMEM); + + tconn->mode = mode; + connector = &tconn->base; + + drm_connector_helper_add(connector, &tinydrm_connector_hfuncs); + ret = drm_connector_init(drm, connector, &tinydrm_connector_funcs, + connector_type); + if (ret) { + kfree(tconn); + return ERR_PTR(ret); + } + + connector->status = connector_status_connected; + + return connector; +} + +/** + * tinydrm_display_pipe_update - Display pipe update helper + * @pipe: Simple display pipe + * @old_state: Old plane state + * + * This function does a full framebuffer flush if the plane framebuffer + * has changed. It also handles vblank events. Drivers can use this as their + * &drm_simple_display_pipe_funcs->update callback. + */ +void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_state) +{ + struct tinydrm_device *tdev = pipe_to_tinydrm(pipe); + struct drm_framebuffer *fb = pipe->plane.state->fb; + struct drm_crtc *crtc = &tdev->pipe.crtc; + + if (fb && (fb != old_state->fb)) { + pipe->plane.fb = fb; + if (fb->funcs->dirty) + fb->funcs->dirty(fb, NULL, 0, 0, NULL, 0); + } + + if (crtc->state->event) { + spin_lock_irq(&crtc->dev->event_lock); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + spin_unlock_irq(&crtc->dev->event_lock); + crtc->state->event = NULL; + } +} +EXPORT_SYMBOL(tinydrm_display_pipe_update); + +/** + * tinydrm_display_pipe_prepare_fb - Display pipe prepare_fb helper + * @pipe: Simple display pipe + * @plane_state: Plane state + * + * This function uses drm_fb_cma_prepare_fb() to check if the plane FB has an + * dma-buf attached, extracts the exclusive fence and attaches it to plane + * state for the atomic helper to wait on. Drivers can use this as their + * &drm_simple_display_pipe_funcs->prepare_fb callback. + */ +int tinydrm_display_pipe_prepare_fb(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *plane_state) +{ + return drm_fb_cma_prepare_fb(&pipe->plane, plane_state); +} +EXPORT_SYMBOL(tinydrm_display_pipe_prepare_fb); + +static int tinydrm_rotate_mode(struct drm_display_mode *mode, + unsigned int rotation) +{ + if (rotation == 0 || rotation == 180) { + return 0; + } else if (rotation == 90 || rotation == 270) { + swap(mode->hdisplay, mode->vdisplay); + swap(mode->hsync_start, mode->vsync_start); + swap(mode->hsync_end, mode->vsync_end); + swap(mode->htotal, mode->vtotal); + swap(mode->width_mm, mode->height_mm); + return 0; + } else { + return -EINVAL; + } +} + +/** + * tinydrm_display_pipe_init - Initialize display pipe + * @tdev: tinydrm device + * @funcs: Display pipe functions + * @connector_type: Connector type + * @formats: Array of supported formats (DRM_FORMAT\_\*) + * @format_count: Number of elements in @formats + * @mode: Supported mode + * @rotation: Initial @mode rotation in degrees Counter Clock Wise + * + * This function sets up a &drm_simple_display_pipe with a &drm_connector that + * has one fixed &drm_display_mode which is rotated according to @rotation. + * + * Returns: + * Zero on success, negative error code on failure. + */ +int +tinydrm_display_pipe_init(struct tinydrm_device *tdev, + const struct drm_simple_display_pipe_funcs *funcs, + int connector_type, + const uint32_t *formats, + unsigned int format_count, + const struct drm_display_mode *mode, + unsigned int rotation) +{ + struct drm_device *drm = tdev->drm; + struct drm_display_mode *mode_copy; + struct drm_connector *connector; + int ret; + + mode_copy = devm_kmalloc(drm->dev, sizeof(*mode_copy), GFP_KERNEL); + if (!mode_copy) + return -ENOMEM; + + *mode_copy = *mode; + ret = tinydrm_rotate_mode(mode_copy, rotation); + if (ret) { + DRM_ERROR("Illegal rotation value %u\n", rotation); + return -EINVAL; + } + + drm->mode_config.min_width = mode_copy->hdisplay; + drm->mode_config.max_width = mode_copy->hdisplay; + drm->mode_config.min_height = mode_copy->vdisplay; + drm->mode_config.max_height = mode_copy->vdisplay; + + connector = tinydrm_connector_create(drm, mode_copy, connector_type); + if (IS_ERR(connector)) + return PTR_ERR(connector); + + ret = drm_simple_display_pipe_init(drm, &tdev->pipe, funcs, formats, + format_count, connector); + if (ret) + return ret; + + return 0; +} +EXPORT_SYMBOL(tinydrm_display_pipe_init); diff --git a/include/drm/tinydrm/tinydrm.h b/include/drm/tinydrm/tinydrm.h new file mode 100644 index 0000000..cf9ca20 --- /dev/null +++ b/include/drm/tinydrm/tinydrm.h @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2016 Noralf Trønnes + * + * 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 __LINUX_TINYDRM_H +#define __LINUX_TINYDRM_H + +#include +#include +#include + +/** + * struct tinydrm_device - tinydrm device + * @drm: DRM device + * @pipe: Display pipe structure + * @dirty_lock: Serializes framebuffer flushing + * @fbdev_cma: CMA fbdev structure + * @suspend_state: Atomic state when suspended + * @fb_funcs: Framebuffer functions used when creating framebuffers + */ +struct tinydrm_device { + struct drm_device *drm; + struct drm_simple_display_pipe pipe; + struct mutex dirty_lock; + struct drm_fbdev_cma *fbdev_cma; + struct drm_atomic_state *suspend_state; + const struct drm_framebuffer_funcs *fb_funcs; +}; + +static inline struct tinydrm_device * +pipe_to_tinydrm(struct drm_simple_display_pipe *pipe) +{ + return container_of(pipe, struct tinydrm_device, pipe); +} + +/** + * TINYDRM_GEM_DRIVER_OPS - default tinydrm gem operations + * + * This macro provides a shortcut for setting the tinydrm GEM operations in + * the &drm_driver structure. + */ +#define TINYDRM_GEM_DRIVER_OPS \ + .gem_free_object = tinydrm_gem_cma_free_object, \ + .gem_vm_ops = &drm_gem_cma_vm_ops, \ + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, \ + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, \ + .gem_prime_import = drm_gem_prime_import, \ + .gem_prime_export = drm_gem_prime_export, \ + .gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table, \ + .gem_prime_import_sg_table = tinydrm_gem_cma_prime_import_sg_table, \ + .gem_prime_vmap = drm_gem_cma_prime_vmap, \ + .gem_prime_vunmap = drm_gem_cma_prime_vunmap, \ + .gem_prime_mmap = drm_gem_cma_prime_mmap, \ + .dumb_create = drm_gem_cma_dumb_create, \ + .dumb_map_offset = drm_gem_cma_dumb_map_offset, \ + .dumb_destroy = drm_gem_dumb_destroy, \ + .fops = &tinydrm_fops + +/** + * TINYDRM_MODE - tinydrm display mode + * @hd: Horizontal resolution, width + * @vd: Vertical resolution, height + * @hd_mm: Display width in millimeters + * @vd_mm: Display height in millimeters + * + * This macro creates a &drm_display_mode for use with tinydrm. + */ +#define TINYDRM_MODE(hd, vd, hd_mm, vd_mm) \ + .hdisplay = (hd), \ + .hsync_start = (hd), \ + .hsync_end = (hd), \ + .htotal = (hd), \ + .vdisplay = (vd), \ + .vsync_start = (vd), \ + .vsync_end = (vd), \ + .vtotal = (vd), \ + .width_mm = (hd_mm), \ + .height_mm = (vd_mm), \ + .type = DRM_MODE_TYPE_DRIVER, \ + .clock = 1 /* pass validation */ + +extern const struct file_operations tinydrm_fops; +void tinydrm_lastclose(struct drm_device *drm); +void tinydrm_gem_cma_free_object(struct drm_gem_object *gem_obj); +struct drm_gem_object * +tinydrm_gem_cma_prime_import_sg_table(struct drm_device *drm, + struct dma_buf_attachment *attach, + struct sg_table *sgt); +int devm_tinydrm_init(struct device *parent, struct tinydrm_device *tdev, + const struct drm_framebuffer_funcs *fb_funcs, + struct drm_driver *driver); +int devm_tinydrm_register(struct tinydrm_device *tdev); +void tinydrm_shutdown(struct tinydrm_device *tdev); +int tinydrm_suspend(struct tinydrm_device *tdev); +int tinydrm_resume(struct tinydrm_device *tdev); + +void tinydrm_display_pipe_update(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *old_state); +int tinydrm_display_pipe_prepare_fb(struct drm_simple_display_pipe *pipe, + struct drm_plane_state *plane_state); +int +tinydrm_display_pipe_init(struct tinydrm_device *tdev, + const struct drm_simple_display_pipe_funcs *funcs, + int connector_type, + const uint32_t *formats, + unsigned int format_count, + const struct drm_display_mode *mode, + unsigned int rotation); + +#endif /* __LINUX_TINYDRM_H */