From patchwork Wed Mar 16 13:34:15 2016 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: 8600281 Return-Path: X-Original-To: patchwork-dri-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id CD37DC0553 for ; Wed, 16 Mar 2016 13:41:25 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 2F5E62034F for ; Wed, 16 Mar 2016 13:41:23 +0000 (UTC) Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) by mail.kernel.org (Postfix) with ESMTP id 85D9F20304 for ; Wed, 16 Mar 2016 13:41:20 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 54A356E92F; Wed, 16 Mar 2016 13:41:11 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from asav22.altibox.net (asav22.altibox.net [109.247.116.9]) by gabe.freedesktop.org (Postfix) with ESMTPS id 0752F6E931 for ; Wed, 16 Mar 2016 13:40:57 +0000 (UTC) Received: from localhost.localdomain (151.79-160-56.customer.lyse.net [79.160.56.151]) by asav22.altibox.net (Postfix) with ESMTP id 0FAB5201FB; Wed, 16 Mar 2016 14:34:42 +0100 (CET) From: =?UTF-8?q?Noralf=20Tr=C3=B8nnes?= To: dri-devel@lists.freedesktop.org Subject: [RFC 1/5] drm: Add DRM support for tiny LCD displays Date: Wed, 16 Mar 2016 14:34:15 +0100 Message-Id: <1458135259-31050-2-git-send-email-noralf@tronnes.org> X-Mailer: git-send-email 2.2.2 In-Reply-To: <1458135259-31050-1-git-send-email-noralf@tronnes.org> References: <1458135259-31050-1-git-send-email-noralf@tronnes.org> MIME-Version: 1.0 X-CMAE-Score: 0 X-CMAE-Analysis: v=2.1 cv=WpKLSYrv c=1 sm=1 tr=0 a=gFHx44SYZz5JQKQKbGEAEQ==:117 a=gFHx44SYZz5JQKQKbGEAEQ==:17 a=L9H7d07YOLsA:10 a=9cW_t1CCXrUA:10 a=s5jvgZ67dGcA:10 a=IkcTkHD0fZMA:10 a=SJz97ENfAAAA:8 a=OuFvlF8OHERUV-2niCIA:9 a=vA_YXf9F83wG6q3w:21 a=_TsrOVvYBc28fuP2:21 a=QEXdDO2ut3YA:10 Cc: thomas.petazzoni@free-electrons.com 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-Spam-Status: No, score=-4.2 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_MED, RP_MATCHES_RCVD, UNPARSEABLE_RELAY autolearn=unavailable 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 tinydrm provides a very simplified view of DRM for displays that has onboard video memory and is connected through a slow bus like SPI/I2C. Signed-off-by: Noralf Trønnes --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/tinydrm/Kconfig | 11 + drivers/gpu/drm/tinydrm/Makefile | 1 + drivers/gpu/drm/tinydrm/core/Makefile | 8 + drivers/gpu/drm/tinydrm/core/internal.h | 43 +++ drivers/gpu/drm/tinydrm/core/tinydrm-core.c | 194 ++++++++++++ drivers/gpu/drm/tinydrm/core/tinydrm-crtc.c | 203 ++++++++++++ drivers/gpu/drm/tinydrm/core/tinydrm-deferred.c | 116 +++++++ drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c | 345 +++++++++++++++++++++ drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c | 112 +++++++ drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c | 97 ++++++ drivers/gpu/drm/tinydrm/core/tinydrm-plane.c | 50 +++ include/drm/tinydrm/tinydrm.h | 142 +++++++++ 14 files changed, 1325 insertions(+) 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/internal.h create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-core.c create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-crtc.c create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-deferred.c create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c create mode 100644 drivers/gpu/drm/tinydrm/core/tinydrm-plane.c create mode 100644 include/drm/tinydrm/tinydrm.h diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index c4bf9a1..3f8ede0 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -266,3 +266,5 @@ source "drivers/gpu/drm/amd/amdkfd/Kconfig" source "drivers/gpu/drm/imx/Kconfig" source "drivers/gpu/drm/vc4/Kconfig" + +source "drivers/gpu/drm/tinydrm/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 1e9ff4c..c7c5c16 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -75,3 +75,4 @@ obj-y += i2c/ obj-y += panel/ obj-y += bridge/ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/ +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..f290045 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/Kconfig @@ -0,0 +1,11 @@ +menuconfig DRM_TINYDRM + tristate "Support for small TFT LCD display modules" + depends on DRM + select DRM_KMS_HELPER + select DRM_KMS_CMA_HELPER + select DRM_GEM_CMA_HELPER + select DRM_PANEL + select VIDEOMODE_HELPERS + 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..03309f4 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/Makefile @@ -0,0 +1,8 @@ +obj-$(CONFIG_DRM_TINYDRM) += tinydrm.o +tinydrm-y += tinydrm-core.o +tinydrm-y += tinydrm-crtc.o +tinydrm-y += tinydrm-framebuffer.o +tinydrm-y += tinydrm-plane.o +tinydrm-y += tinydrm-helpers.o +tinydrm-y += tinydrm-deferred.o +tinydrm-$(CONFIG_DRM_KMS_FB_HELPER) += tinydrm-fbdev.o diff --git a/drivers/gpu/drm/tinydrm/core/internal.h b/drivers/gpu/drm/tinydrm/core/internal.h new file mode 100644 index 0000000..a126658 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/internal.h @@ -0,0 +1,43 @@ +/* + * 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. + */ + +int tinydrm_crtc_create(struct tinydrm_device *tdev); + +static inline bool tinydrm_active(struct tinydrm_device *tdev) +{ + struct drm_crtc *crtc; + + drm_for_each_crtc(crtc, tdev->base) + return crtc->state && crtc->state->active; + + return false; +} + +void tinydrm_mode_config_init(struct tinydrm_device *tdev); + +int tinydrm_plane_init(struct tinydrm_device *tdev); + +#ifdef CONFIG_DRM_KMS_FB_HELPER +int tinydrm_fbdev_init(struct tinydrm_device *tdev); +void tinydrm_fbdev_fini(struct tinydrm_device *tdev); +void tinydrm_fbdev_restore_mode(struct tinydrm_fbdev *fbdev); +#else +static inline int tinydrm_fbdev_init(struct tinydrm_device *tdev) +{ + return 0; +} + +static inline void tinydrm_fbdev_fini(struct tinydrm_device *tdev) +{ +} + +static inline void tinydrm_fbdev_restore_mode(struct tinydrm_fbdev *fbdev) +{ +} +#endif 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..cb3cf71 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-core.c @@ -0,0 +1,194 @@ +//#define DEBUG +/* + * 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 "internal.h" + +static int tinydrm_load(struct drm_device *ddev, unsigned long flags) +{ + struct tinydrm_device *tdev = ddev->dev_private; + struct drm_connector *connector; + int ret; + + DRM_DEBUG_KMS("\n"); + + tinydrm_mode_config_init(tdev); + + ret = tinydrm_plane_init(tdev); + if (ret) + return ret; + + ret = tinydrm_crtc_create(tdev); + if (ret) + return ret; + + connector = list_first_entry(&ddev->mode_config.connector_list, + typeof(*connector), head); + connector->status = connector_status_connected; + + drm_panel_init(&tdev->panel); + drm_panel_add(&tdev->panel); + drm_panel_attach(&tdev->panel, connector); + + drm_mode_config_reset(ddev); + + ret = tinydrm_fbdev_init(tdev); + if (ret) + return ret; + + return 0; +} + +static void tinydrm_lastclose(struct drm_device *ddev) +{ + struct tinydrm_device *tdev = ddev->dev_private; + + DRM_DEBUG_KMS("\n"); + tinydrm_fbdev_restore_mode(tdev->fbdev); +} + +static 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, +}; + +static struct drm_driver tinydrm_driver = { + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_PRIME + | DRIVER_ATOMIC, + .load = tinydrm_load, + .lastclose = tinydrm_lastclose, +// .unload = tinydrm_unload, + .get_vblank_counter = drm_vblank_count, +// .enable_vblank = tinydrm_enable_vblank, +// .disable_vblank = tinydrm_disable_vblank, + .gem_free_object = drm_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 = drm_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, + .name = "tinydrm", + .desc = "tinydrm", + .date = "20150916", + .major = 1, + .minor = 0, +}; + +void tinydrm_release(struct tinydrm_device *tdev) +{ + DRM_DEBUG_KMS("\n"); + + if (tdev->deferred) + cancel_delayed_work_sync(&tdev->deferred->dwork); + + tinydrm_fbdev_fini(tdev); + + drm_panel_detach(&tdev->panel); + drm_panel_remove(&tdev->panel); + + drm_mode_config_cleanup(tdev->base); + drm_dev_unregister(tdev->base); + drm_dev_unref(tdev->base); +} +EXPORT_SYMBOL(tinydrm_release); + +int tinydrm_register(struct device *dev, struct tinydrm_device *tdev) +{ + struct drm_driver *driver = &tinydrm_driver; + struct drm_device *ddev; + int ret; + + dev_info(dev, "%s\n", __func__); + +dev->coherent_dma_mask = DMA_BIT_MASK(32); + + if (WARN_ON(!tdev->dirtyfb)) + return -EINVAL; + + ddev = drm_dev_alloc(driver, dev); + if (!ddev) + return -ENOMEM; + + tdev->base = ddev; + ddev->dev_private = tdev; + + ret = drm_dev_set_unique(ddev, dev_name(ddev->dev)); + if (ret) + goto err_free; + + ret = drm_dev_register(ddev, 0); + if (ret) + goto err_free; + + DRM_INFO("Device: %s\n", dev_name(dev)); + DRM_INFO("Initialized %s %d.%d.%d on minor %d\n", + driver->name, driver->major, driver->minor, driver->patchlevel, + ddev->primary->index); + + return 0; + +err_free: + drm_dev_unref(ddev); + + return ret; +} +EXPORT_SYMBOL(tinydrm_register); + +static void devm_tinydrm_release(struct device *dev, void *res) +{ + tinydrm_release(*(struct tinydrm_device **)res); +} + +int devm_tinydrm_register(struct device *dev, struct tinydrm_device *tdev) +{ + struct tinydrm_device **ptr; + int ret; + + ptr = devres_alloc(devm_tinydrm_release, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return -ENOMEM; + + ret = tinydrm_register(dev, tdev); + if (ret) { + devres_free(ptr); + return ret; + } + + *ptr = tdev; + devres_add(dev, ptr); + + return 0; +} +EXPORT_SYMBOL(devm_tinydrm_register); + +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-crtc.c b/drivers/gpu/drm/tinydrm/core/tinydrm-crtc.c new file mode 100644 index 0000000..65b3426 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-crtc.c @@ -0,0 +1,203 @@ +/* + * 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 "internal.h" + +static int tinydrm_connector_get_modes(struct drm_connector *connector) +{ + struct tinydrm_device *tdev = connector->dev->dev_private; + struct drm_display_mode *mode; + int ret; + + DRM_DEBUG_KMS("\n"); + ret = drm_panel_get_modes(&tdev->panel); + if (ret > 0) + return ret; + + mode = drm_cvt_mode(connector->dev, tdev->width, tdev->height, 60, false, false, false); + if (!mode) + return 0; + + mode->type |= DRM_MODE_TYPE_PREFERRED; + drm_mode_probed_add(connector, mode); + + return 1; +} + +static struct drm_encoder * +tinydrm_connector_best_encoder(struct drm_connector *connector) +{ + return drm_encoder_find(connector->dev, connector->encoder_ids[0]); +} + +static const struct drm_connector_helper_funcs tinydrm_connector_helper_funcs = { + .get_modes = tinydrm_connector_get_modes, + .best_encoder = tinydrm_connector_best_encoder, +}; + +static enum drm_connector_status +tinydrm_connector_detect(struct drm_connector *connector, bool force) +{ + DRM_DEBUG_KMS("status = %d\n", connector->status); + + if (drm_device_is_unplugged(connector->dev)) + return connector_status_disconnected; + + return connector->status; +} + +static void tinydrm_connector_destroy(struct drm_connector *connector) +{ + DRM_DEBUG_KMS("\n"); + drm_connector_unregister(connector); + drm_connector_cleanup(connector); + kfree(connector); +} + +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, +}; + +static void tinydrm_encoder_disable(struct drm_encoder *encoder) +{ +} + +static void tinydrm_encoder_enable(struct drm_encoder *encoder) +{ +} + +static int tinydrm_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + return 0; +} + +static const struct drm_encoder_helper_funcs tinydrm_encoder_helper_funcs = { + .disable = tinydrm_encoder_disable, + .enable = tinydrm_encoder_enable, + .atomic_check = tinydrm_encoder_atomic_check, +}; + +static void tinydrm_encoder_cleanup(struct drm_encoder *encoder) +{ + DRM_DEBUG_KMS("\n"); + drm_encoder_cleanup(encoder); + kfree(encoder); +} + +static const struct drm_encoder_funcs tinydrm_encoder_funcs = { + .destroy = tinydrm_encoder_cleanup, +}; + +static void tinydrm_crtc_enable(struct drm_crtc *crtc) +{ + struct tinydrm_device *tdev = crtc->dev->dev_private; + + DRM_DEBUG_KMS("prepared=%u, enabled=%u\n", tdev->prepared, tdev->enabled); + + /* The panel must be prepared on the first crtc enable after probe */ + tinydrm_prepare(tdev); + /* The panel is enabled after the first display update */ +} + +static void tinydrm_crtc_disable(struct drm_crtc *crtc) +{ + struct tinydrm_device *tdev = crtc->dev->dev_private; + + DRM_DEBUG_KMS("prepared=%u, enabled=%u\n", tdev->prepared, tdev->enabled); + + tinydrm_disable(tdev); +} + +static const struct drm_crtc_helper_funcs tinydrm_crtc_helper_funcs = { + .disable = tinydrm_crtc_disable, + .enable = tinydrm_crtc_enable, +}; + +static void tinydrm_crtc_cleanup(struct drm_crtc *crtc) +{ + DRM_DEBUG_KMS("\n"); + drm_crtc_cleanup(crtc); + kfree(crtc); +} + +static const struct drm_crtc_funcs tinydrm_crtc_funcs = { + .reset = drm_atomic_helper_crtc_reset, + .destroy = tinydrm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +int tinydrm_crtc_create(struct tinydrm_device *tdev) +{ + struct drm_device *dev = tdev->base; + struct drm_connector *connector; + struct drm_encoder *encoder; + struct drm_crtc *crtc; + int ret; + + connector = kzalloc(sizeof(*connector), GFP_KERNEL); + encoder = kzalloc(sizeof(*encoder), GFP_KERNEL); + crtc = kzalloc(sizeof(*crtc), GFP_KERNEL); + if (!connector || !encoder || !crtc) { + ret = -ENOMEM; + goto error_free; + } + + drm_crtc_helper_add(crtc, &tinydrm_crtc_helper_funcs); + ret = drm_crtc_init_with_planes(dev, crtc, &tdev->plane, NULL, + &tinydrm_crtc_funcs); + if (ret) + goto error_free; + + encoder->possible_crtcs = 1 << drm_crtc_index(crtc); + drm_encoder_helper_add(encoder, &tinydrm_encoder_helper_funcs); + ret = drm_encoder_init(dev, encoder, &tinydrm_encoder_funcs, + DRM_MODE_ENCODER_NONE); + if (ret) + goto error_free; + + drm_connector_helper_add(connector, &tinydrm_connector_helper_funcs); + ret = drm_connector_init(dev, connector, &tinydrm_connector_funcs, + DRM_MODE_CONNECTOR_VIRTUAL); + if (ret) + goto error_free; + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) + goto error_free; + + ret = drm_connector_register(connector); + if (ret) + goto error_free; + + return 0; + +error_free: + kfree(crtc); + kfree(encoder); + kfree(connector); + + return ret; +} diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-deferred.c b/drivers/gpu/drm/tinydrm/core/tinydrm-deferred.c new file mode 100644 index 0000000..16553a6 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-deferred.c @@ -0,0 +1,116 @@ +#include + +#include "internal.h" + +bool tinydrm_deferred_begin(struct tinydrm_device *tdev, + struct tinydrm_fb_clip *fb_clip) +{ + struct tinydrm_deferred *deferred = tdev->deferred; + + spin_lock(&deferred->lock); + *fb_clip = deferred->fb_clip; + tinydrm_reset_clip(&deferred->fb_clip.clip); + deferred->fb_clip.fb = NULL; + deferred->fb_clip.vmem = NULL; + spin_unlock(&deferred->lock); + + /* The crtc might have been disabled by the time we get here */ + if (!tinydrm_active(tdev)) + return false; + + /* On first update make sure to do the entire framebuffer */ + if (!tdev->enabled) { + fb_clip->clip.x1 = 0; + fb_clip->clip.x2 = fb_clip->fb->width - 1; + fb_clip->clip.y1 = 0; + fb_clip->clip.y2 = fb_clip->fb->height - 1; + } + + /* TODO: support partial updates */ + fb_clip->clip.x1 = 0; + fb_clip->clip.x2 = fb_clip->fb->width - 1; + fb_clip->clip.y1 = 0; + fb_clip->clip.y2 = fb_clip->fb->height - 1; + + return true; +} +EXPORT_SYMBOL(tinydrm_deferred_begin); + +void tinydrm_deferred_end(struct tinydrm_device *tdev) +{ + if (tdev->prepared && !tdev->enabled) { + drm_panel_enable(&tdev->panel); + tdev->enabled = true; + } +} +EXPORT_SYMBOL(tinydrm_deferred_end); + +int tinydrm_dirtyfb(struct drm_framebuffer *fb, void *vmem, unsigned flags, + unsigned color, struct drm_clip_rect *clips, + unsigned num_clips) +{ + struct tinydrm_device *tdev = fb->dev->dev_private; + + struct tinydrm_deferred *deferred = tdev->deferred; + struct tinydrm_fb_clip *fb_clip = &tdev->deferred->fb_clip; + + bool no_delay = deferred->no_delay; + unsigned long delay; + + dev_dbg(tdev->base->dev, "%s(fb = %p, vmem = %p, clips = %p, num_clips = %u, no_delay = %u)\n", __func__, fb, vmem, clips, num_clips, no_delay); + + if (!vmem || !fb) + return -EINVAL; + + spin_lock(&deferred->lock); + fb_clip->fb = fb; + fb_clip->vmem = vmem; + tinydrm_merge_clips(&fb_clip->clip, clips, num_clips, flags, + fb->width, fb->height); + if (tinydrm_is_full_clip(&fb_clip->clip, fb->width, fb->height)) + no_delay = true; + spin_unlock(&deferred->lock); + + delay = no_delay ? 0 : msecs_to_jiffies(deferred->defer_ms); + + if (schedule_delayed_work(&deferred->dwork, delay)) + dev_dbg(tdev->base->dev, "%s: Already scheduled\n", __func__); + + return 0; +} +EXPORT_SYMBOL(tinydrm_dirtyfb); + +void tinydrm_merge_clips(struct drm_clip_rect *dst, + struct drm_clip_rect *clips, unsigned num_clips, + unsigned flags, u32 width, u32 height) +{ + struct drm_clip_rect full_clip = { + .x1 = 0, + .x2 = width - 1, + .y1 = 0, + .y2 = height - 1, + }; + int i; + + if (!clips) { + clips = &full_clip; + num_clips = 1; + } + + for (i = 0; i < num_clips; i++) { + dst->x1 = min(dst->x1, clips[i].x1); + dst->x2 = max(dst->x2, clips[i].x2); + dst->y1 = min(dst->y1, clips[i].y1); + dst->y2 = max(dst->y2, clips[i].y2); + + if (flags & DRM_MODE_FB_DIRTY_ANNOTATE_COPY) { + i++; + dst->x2 = max(dst->x2, clips[i].x2); + dst->y2 = max(dst->y2, clips[i].y2); + } + } + + dst->x2 = min_t(u32, dst->x2, width - 1); + dst->y2 = min_t(u32, dst->y2, height - 1); +} +EXPORT_SYMBOL(tinydrm_merge_clips); diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c b/drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c new file mode 100644 index 0000000..44b6a95 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-fbdev.c @@ -0,0 +1,345 @@ +//#define DEBUG +/* + * 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 + +#include "internal.h" + +#define DEFAULT_DEFIO_DELAY HZ/30 + +struct tinydrm_fbdev { + struct drm_fb_helper fb_helper; + struct drm_framebuffer fb; + void *vmem; +}; + +static inline struct tinydrm_fbdev *helper_to_fbdev(struct drm_fb_helper *helper) +{ + return container_of(helper, struct tinydrm_fbdev, fb_helper); +} + +static inline struct tinydrm_fbdev *fb_to_fbdev(struct drm_framebuffer *fb) +{ + return container_of(fb, struct tinydrm_fbdev, fb); +} + +static void tinydrm_fbdev_dirty(struct fb_info *info, + struct drm_clip_rect *clip, bool run_now) +{ + struct drm_fb_helper *helper = info->par; + struct tinydrm_device *tdev = helper->dev->dev_private; + struct drm_framebuffer *fb = helper->fb; + + if (tdev->plane.fb != fb) + return; + + if (tdev->deferred) + tdev->deferred->no_delay = run_now; + tdev->dirtyfb(fb, info->screen_buffer, 0, 0, clip, 1); +} + +static void tinydrm_fbdev_deferred_io(struct fb_info *info, + struct list_head *pagelist) +{ + unsigned long start, end, next, min, max; + struct drm_clip_rect clip; + struct page *page; +int count = 0; + + min = ULONG_MAX; + max = 0; + next = 0; + list_for_each_entry(page, pagelist, lru) { + start = page->index << PAGE_SHIFT; + end = start + PAGE_SIZE - 1; + min = min(min, start); + max = max(max, end); +count++; + } + + if (min < max) { + clip.x1 = 0; + clip.x2 = info->var.xres - 1; + clip.y1 = min / info->fix.line_length; + clip.y2 = min_t(u32, max / info->fix.line_length, + info->var.yres - 1); + pr_debug("%s: x1=%u, x2=%u, y1=%u, y2=%u, count=%d\n", __func__, clip.x1, clip.x2, clip.y1, clip.y2, count); + tinydrm_fbdev_dirty(info, &clip, true); + } +} + +static void tinydrm_fbdev_fb_fillrect(struct fb_info *info, + const struct fb_fillrect *rect) +{ + struct drm_clip_rect clip = { + .x1 = rect->dx, + .x2 = rect->dx + rect->width - 1, + .y1 = rect->dy, + .y2 = rect->dy + rect->height - 1, + }; + + dev_dbg(info->dev, "%s: dx=%d, dy=%d, width=%d, height=%d\n", + __func__, rect->dx, rect->dy, rect->width, rect->height); + sys_fillrect(info, rect); + tinydrm_fbdev_dirty(info, &clip, false); +} + +static void tinydrm_fbdev_fb_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + struct drm_clip_rect clip = { + .x1 = area->dx, + .x2 = area->dx + area->width - 1, + .y1 = area->dy, + .y2 = area->dy + area->height - 1, + }; + + dev_dbg(info->dev, "%s: dx=%d, dy=%d, width=%d, height=%d\n", + __func__, area->dx, area->dy, area->width, area->height); + sys_copyarea(info, area); + tinydrm_fbdev_dirty(info, &clip, false); +} + +static void tinydrm_fbdev_fb_imageblit(struct fb_info *info, + const struct fb_image *image) +{ + struct drm_clip_rect clip = { + .x1 = image->dx, + .x2 = image->dx + image->width - 1, + .y1 = image->dy, + .y2 = image->dy + image->height - 1, + }; + + dev_dbg(info->dev, "%s: dx=%d, dy=%d, width=%d, height=%d\n", + __func__, image->dx, image->dy, image->width, image->height); + sys_imageblit(info, image); + tinydrm_fbdev_dirty(info, &clip, false); +} + +static ssize_t tinydrm_fbdev_fb_write(struct fb_info *info, + const char __user *buf, size_t count, + loff_t *ppos) +{ + struct drm_clip_rect clip = { + .x1 = 0, + .x2 = info->var.xres - 1, + .y1 = 0, + .y2 = info->var.yres - 1, + }; + ssize_t ret; + + dev_dbg(info->dev, "%s:\n", __func__); + ret = fb_sys_write(info, buf, count, ppos); + tinydrm_fbdev_dirty(info, &clip, false); + + return ret; +} + +static void tinydrm_fbdev_fb_destroy(struct drm_framebuffer *fb) +{ +} + +static struct drm_framebuffer_funcs tinydrm_fbdev_fb_funcs = { + .destroy = tinydrm_fbdev_fb_destroy, +}; + +static int tinydrm_fbdev_create(struct drm_fb_helper *helper, + struct drm_fb_helper_surface_size *sizes) +{ + struct tinydrm_fbdev *fbdev = helper_to_fbdev(helper); + struct drm_mode_fb_cmd2 mode_cmd = { 0 }; + struct drm_device *dev = helper->dev; + struct tinydrm_device *tdev = dev->dev_private; + struct fb_deferred_io *fbdefio; + struct drm_framebuffer *fb; + unsigned int bytes_per_pixel = DIV_ROUND_UP(sizes->surface_bpp, 8); + struct fb_ops *fbops; + struct fb_info *fbi; + size_t size; + char *screen_buffer; + int ret; + + mode_cmd.width = sizes->surface_width; + mode_cmd.height = sizes->surface_height; + mode_cmd.pitches[0] = sizes->surface_width * bytes_per_pixel; + mode_cmd.pixel_format = drm_mode_legacy_fb_format(sizes->surface_bpp, + sizes->surface_depth); + size = mode_cmd.pitches[0] * mode_cmd.height; + + /* + * A per device fbops structure is needed because + * fb_deferred_io_cleanup() clears fbops.fb_mmap + */ + fbops = devm_kzalloc(dev->dev, sizeof(*fbops), GFP_KERNEL); + if (!fbops) { + dev_err(dev->dev, "Failed to allocate fbops\n"); + return -ENOMEM; + } + + /* A per device structure is needed for individual delays */ + fbdefio = devm_kzalloc(dev->dev, sizeof(*fbdefio), GFP_KERNEL); + if (!fbdefio) { + dev_err(dev->dev, "Could not allocate fbdefio\n"); + return -ENOMEM; + } + + fbi = drm_fb_helper_alloc_fbi(helper); + if (IS_ERR(fbi)) { + dev_err(dev->dev, "Could not allocate fbi\n"); + return PTR_ERR(fbi); + } + + screen_buffer = vzalloc(size); + if (!screen_buffer) { + dev_err(dev->dev, "Failed to allocate fbdev screen buffer.\n"); + ret = -ENOMEM; + goto err_fb_info_destroy; + } + + fb = &fbdev->fb; + helper->fb = fb; + drm_helper_mode_fill_fb_struct(fb, &mode_cmd); + ret = drm_framebuffer_init(dev, fb, &tinydrm_fbdev_fb_funcs); + if (ret) { + dev_err(dev->dev, "failed to init framebuffer: %d\n", ret); + vfree(screen_buffer); + goto err_fb_info_destroy; + } + + DRM_DEBUG_KMS("fbdev FB ID: %d, vmem = %p\n", fb->base.id, fbdev->vmem); + + fbi->par = helper; + fbi->flags = FBINFO_FLAG_DEFAULT | FBINFO_VIRTFB; + strcpy(fbi->fix.id, "tinydrm"); + + fbops->owner = THIS_MODULE, + fbops->fb_fillrect = tinydrm_fbdev_fb_fillrect, + fbops->fb_copyarea = tinydrm_fbdev_fb_copyarea, + fbops->fb_imageblit = tinydrm_fbdev_fb_imageblit, + fbops->fb_write = tinydrm_fbdev_fb_write, + fbops->fb_check_var = drm_fb_helper_check_var, + fbops->fb_set_par = drm_fb_helper_set_par, + fbops->fb_blank = drm_fb_helper_blank, + fbops->fb_setcmap = drm_fb_helper_setcmap, + fbi->fbops = fbops; + + drm_fb_helper_fill_fix(fbi, fb->pitches[0], fb->depth); + drm_fb_helper_fill_var(fbi, helper, sizes->fb_width, sizes->fb_height); + + fbdev->vmem = screen_buffer; + fbi->screen_buffer = screen_buffer; + fbi->screen_size = size; + fbi->fix.smem_len = size; + + if (tdev->deferred) + fbdefio->delay = msecs_to_jiffies(tdev->deferred->defer_ms); + else + fbdefio->delay = DEFAULT_DEFIO_DELAY; + /* delay=0 is turned into delay=HZ, so use 1 as a minimum */ + if (!fbdefio->delay) + fbdefio->delay = 1; + fbdefio->deferred_io = tinydrm_fbdev_deferred_io; + fbi->fbdefio = fbdefio; + fb_deferred_io_init(fbi); + + return 0; + +err_fb_info_destroy: + drm_fb_helper_release_fbi(helper); + + return ret; +} + +static const struct drm_fb_helper_funcs tinydrm_fb_helper_funcs = { + .fb_probe = tinydrm_fbdev_create, +}; + +int tinydrm_fbdev_init(struct tinydrm_device *tdev) +{ + struct drm_device *dev = tdev->base; + struct drm_fb_helper *helper; + struct tinydrm_fbdev *fbdev; + int ret; + + DRM_DEBUG_KMS("IN\n"); + + fbdev = devm_kzalloc(dev->dev, sizeof(*fbdev), GFP_KERNEL); + if (!fbdev) { + dev_err(dev->dev, "Failed to allocate drm fbdev.\n"); + return -ENOMEM; + } + + helper = &fbdev->fb_helper; + + drm_fb_helper_prepare(dev, helper, &tinydrm_fb_helper_funcs); + + ret = drm_fb_helper_init(dev, helper, 1, 1); + if (ret < 0) { + dev_err(dev->dev, "Failed to initialize drm fb helper.\n"); + return ret; + } + + ret = drm_fb_helper_single_add_all_connectors(helper); + if (ret < 0) { + dev_err(dev->dev, "Failed to add connectors.\n"); + goto err_drm_fb_helper_fini; + + } + + ret = drm_fb_helper_initial_config(helper, 16); + if (ret < 0) { + dev_err(dev->dev, "Failed to set initial hw configuration.\n"); + goto err_drm_fb_helper_fini; + } + + tdev->fbdev = fbdev; + DRM_DEBUG_KMS("OUT\n"); + + return 0; + +err_drm_fb_helper_fini: + drm_fb_helper_fini(helper); + + return ret; +} + +void tinydrm_fbdev_fini(struct tinydrm_device *tdev) +{ + struct tinydrm_fbdev *fbdev = tdev->fbdev; + struct drm_fb_helper *fb_helper = &fbdev->fb_helper; + + DRM_DEBUG_KMS("IN\n"); + + drm_fb_helper_unregister_fbi(fb_helper); + fb_deferred_io_cleanup(fb_helper->fbdev); + drm_fb_helper_release_fbi(fb_helper); + drm_fb_helper_fini(fb_helper); + + drm_framebuffer_unregister_private(&fbdev->fb); + drm_framebuffer_cleanup(&fbdev->fb); + + vfree(fbdev->vmem); + + tdev->fbdev = NULL; + DRM_DEBUG_KMS("OUT\n"); +} + +/* TODO: pass tdev instead ? */ +void tinydrm_fbdev_restore_mode(struct tinydrm_fbdev *fbdev) +{ + if (fbdev) + drm_fb_helper_restore_fbdev_mode_unlocked(&fbdev->fb_helper); +} +EXPORT_SYMBOL(tinydrm_fbdev_restore_mode); diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c b/drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c new file mode 100644 index 0000000..1056bc6 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-framebuffer.c @@ -0,0 +1,112 @@ +/* + * 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 + +static inline struct tinydrm_framebuffer *to_tinydrm_framebuffer(struct drm_framebuffer *fb) +{ + return container_of(fb, struct tinydrm_framebuffer, base); +} + +static void tinydrm_framebuffer_destroy(struct drm_framebuffer *fb) +{ + struct tinydrm_framebuffer *tinydrm_fb = to_tinydrm_framebuffer(fb); + struct tinydrm_device *tdev = fb->dev->dev_private; + + DRM_DEBUG_KMS("fb = %p, cma_obj = %p\n", fb, tinydrm_fb->cma_obj); + + if (tdev->deferred) + flush_delayed_work(&tdev->deferred->dwork); + + if (tinydrm_fb->cma_obj) + drm_gem_object_unreference_unlocked(&tinydrm_fb->cma_obj->base); + + drm_framebuffer_cleanup(fb); + kfree(tinydrm_fb); +} + +static int tinydrm_framebuffer_dirty(struct drm_framebuffer *fb, + struct drm_file *file_priv, + unsigned flags, unsigned color, + struct drm_clip_rect *clips, + unsigned num_clips) +{ + struct tinydrm_framebuffer *tfb = to_tinydrm_framebuffer(fb); + struct tinydrm_device *tdev = fb->dev->dev_private; + + dev_dbg(fb->dev->dev, "%s\n", __func__); + + return tdev->dirtyfb(fb, tfb->cma_obj->vaddr, flags, color, clips, num_clips); +} + +static const struct drm_framebuffer_funcs tinydrm_fb_funcs = { + .destroy = tinydrm_framebuffer_destroy, + .dirty = tinydrm_framebuffer_dirty, +/* TODO? + * .create_handle = tinydrm_framebuffer_create_handle, */ +}; + +static struct drm_framebuffer * +tinydrm_fb_create(struct drm_device *ddev, struct drm_file *file_priv, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + struct tinydrm_framebuffer *tinydrm_fb; + struct drm_gem_object *obj; + int ret; + + /* TODO? Validate the pixel format, size and pitches */ + DRM_DEBUG_KMS("pixel_format=%s\n", drm_get_format_name(mode_cmd->pixel_format)); + DRM_DEBUG_KMS("width=%u\n", mode_cmd->width); + DRM_DEBUG_KMS("height=%u\n", mode_cmd->height); + DRM_DEBUG_KMS("pitches[0]=%u\n", mode_cmd->pitches[0]); + + obj = drm_gem_object_lookup(ddev, file_priv, mode_cmd->handles[0]); + if (!obj) + return NULL; + + tinydrm_fb = kzalloc(sizeof(*tinydrm_fb), GFP_KERNEL); + if (!tinydrm_fb) + return NULL; + + tinydrm_fb->cma_obj = to_drm_gem_cma_obj(obj); + + ret = drm_framebuffer_init(ddev, &tinydrm_fb->base, &tinydrm_fb_funcs); + if (ret) { + kfree(tinydrm_fb); + drm_gem_object_unreference_unlocked(obj); + return NULL; + } + + drm_helper_mode_fill_fb_struct(&tinydrm_fb->base, mode_cmd); + + return &tinydrm_fb->base; +} + +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, +}; + +void tinydrm_mode_config_init(struct tinydrm_device *tdev) +{ + struct drm_device *ddev = tdev->base; + + drm_mode_config_init(ddev); + + ddev->mode_config.min_width = tdev->width; + ddev->mode_config.min_height = tdev->height; + ddev->mode_config.max_width = tdev->width; + ddev->mode_config.max_height = tdev->height; + ddev->mode_config.funcs = &tinydrm_mode_config_funcs; +} diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c new file mode 100644 index 0000000..8ed9a15 --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-helpers.c @@ -0,0 +1,97 @@ +/* + * 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 "internal.h" + +struct backlight_device *tinydrm_of_find_backlight(struct device *dev) +{ + struct backlight_device *backlight; + struct device_node *np; + + np = of_parse_phandle(dev->of_node, "backlight", 0); + if (!np) + return NULL; + + backlight = of_find_backlight_by_node(np); + of_node_put(np); + + if (!backlight) + return ERR_PTR(-EPROBE_DEFER); + + return backlight; +} +EXPORT_SYMBOL(tinydrm_of_find_backlight); + +int tinydrm_panel_enable_backlight(struct drm_panel *panel) +{ + struct tinydrm_device *tdev = tinydrm_from_panel(panel); + + if (tdev->backlight) { + if (tdev->backlight->props.brightness == 0) + tdev->backlight->props.brightness = + tdev->backlight->props.max_brightness; + tdev->backlight->props.state &= ~BL_CORE_SUSPENDED; + backlight_update_status(tdev->backlight); + } + + return 0; +} +EXPORT_SYMBOL(tinydrm_panel_enable_backlight); + +int tinydrm_panel_disable_backlight(struct drm_panel *panel) +{ + struct tinydrm_device *tdev = tinydrm_from_panel(panel); + + if (tdev->backlight) { + tdev->backlight->props.state |= BL_CORE_SUSPENDED; + backlight_update_status(tdev->backlight); + } + + return 0; +} +EXPORT_SYMBOL(tinydrm_panel_disable_backlight); + +static int __maybe_unused tinydrm_pm_suspend(struct device *dev) +{ + struct tinydrm_device *tdev = dev_get_drvdata(dev); + + tinydrm_disable(tdev); + tinydrm_unprepare(tdev); + + return 0; +} + +static int __maybe_unused tinydrm_pm_resume(struct device *dev) +{ + struct tinydrm_device *tdev = dev_get_drvdata(dev); + + tinydrm_prepare(tdev); + /* The panel is enabled after the first display update */ + + return 0; +} + +const struct dev_pm_ops tinydrm_simple_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(tinydrm_pm_suspend, tinydrm_pm_resume) +}; +EXPORT_SYMBOL(tinydrm_simple_pm_ops); + +void tinydrm_spi_shutdown(struct spi_device *spi) +{ + struct tinydrm_device *tdev = spi_get_drvdata(spi); + + tinydrm_disable(tdev); + tinydrm_unprepare(tdev); +} +EXPORT_SYMBOL(tinydrm_spi_shutdown); diff --git a/drivers/gpu/drm/tinydrm/core/tinydrm-plane.c b/drivers/gpu/drm/tinydrm/core/tinydrm-plane.c new file mode 100644 index 0000000..7774e8c --- /dev/null +++ b/drivers/gpu/drm/tinydrm/core/tinydrm-plane.c @@ -0,0 +1,50 @@ +/* + * 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 + +/* TODO: Configurable */ +static const uint32_t tinydrm_formats[] = { + DRM_FORMAT_RGB565, + DRM_FORMAT_XRGB8888, +}; + +static void tinydrm_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + DRM_DEBUG("handle 0x%x, crtc %dx%d+%d+%d\n", 0, + plane->state->crtc_w, plane->state->crtc_h, + plane->state->crtc_x, plane->state->crtc_y); +} + +static const struct drm_plane_helper_funcs tinydrm_plane_helper_funcs = { + .atomic_update = tinydrm_plane_atomic_update, +}; + +static const struct drm_plane_funcs tinydrm_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +int tinydrm_plane_init(struct tinydrm_device *tdev) +{ + drm_plane_helper_add(&tdev->plane, &tinydrm_plane_helper_funcs); + return drm_universal_plane_init(tdev->base, &tdev->plane, 0, + &tinydrm_plane_funcs, tinydrm_formats, + ARRAY_SIZE(tinydrm_formats), + DRM_PLANE_TYPE_PRIMARY); +} diff --git a/include/drm/tinydrm/tinydrm.h b/include/drm/tinydrm/tinydrm.h new file mode 100644 index 0000000..695e483 --- /dev/null +++ b/include/drm/tinydrm/tinydrm.h @@ -0,0 +1,142 @@ +/* + * 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_deferred; +struct tinydrm_fbdev; +struct spi_device; +struct regulator; +struct lcdreg; + +struct tinydrm_framebuffer { + struct drm_framebuffer base; + struct drm_gem_cma_object *cma_obj; +}; + +struct tinydrm_device { + struct drm_device *base; + u32 width, height; + struct drm_panel panel; + struct drm_plane plane; + struct tinydrm_fbdev *fbdev; + struct tinydrm_deferred *deferred; + struct backlight_device *backlight; + struct regulator *regulator; + struct lcdreg *lcdreg; + bool prepared; + bool enabled; + void *dev_private; + + int (*dirtyfb)(struct drm_framebuffer *fb, void *vmem, unsigned flags, + unsigned color, struct drm_clip_rect *clips, + unsigned num_clips); +}; + +int devm_tinydrm_register(struct device *dev, struct tinydrm_device *tdev); +int tinydrm_register(struct device *dev, struct tinydrm_device *tdev); +void tinydrm_release(struct tinydrm_device *tdev); + +static inline struct tinydrm_device *tinydrm_from_panel(struct drm_panel *panel) +{ + return panel->connector->dev->dev_private; +} + +static inline void tinydrm_prepare(struct tinydrm_device *tdev) +{ + if (!tdev->prepared) { + drm_panel_prepare(&tdev->panel); + tdev->prepared = true; + } +} + +static inline void tinydrm_unprepare(struct tinydrm_device *tdev) +{ + if (tdev->prepared) { + drm_panel_unprepare(&tdev->panel); + tdev->prepared = false; + } +} + +static inline void tinydrm_enable(struct tinydrm_device *tdev) +{ + if (!tdev->enabled) { + drm_panel_enable(&tdev->panel); + tdev->enabled = true; + } +} + +static inline void tinydrm_disable(struct tinydrm_device *tdev) +{ + if (tdev->enabled) { + drm_panel_disable(&tdev->panel); + tdev->enabled = false; + } +} + +struct backlight_device *tinydrm_of_find_backlight(struct device *dev); +int tinydrm_panel_enable_backlight(struct drm_panel *panel); +int tinydrm_panel_disable_backlight(struct drm_panel *panel); +extern const struct dev_pm_ops tinydrm_simple_pm_ops; +void tinydrm_spi_shutdown(struct spi_device *spi); + +struct tinydrm_fb_clip { + struct drm_framebuffer *fb; + struct drm_clip_rect clip; + void *vmem; +}; + +struct tinydrm_deferred { + struct delayed_work dwork; + struct tinydrm_fb_clip fb_clip; + unsigned defer_ms; + spinlock_t lock; + bool no_delay; +}; + +static inline struct tinydrm_device *work_to_tinydrm(struct work_struct *work) +{ + struct tinydrm_deferred *deferred; + + deferred = container_of(work, struct tinydrm_deferred, dwork.work); + return deferred->fb_clip.fb->dev->dev_private; +} + +bool tinydrm_deferred_begin(struct tinydrm_device *tdev, + struct tinydrm_fb_clip *fb_clip); +void tinydrm_deferred_end(struct tinydrm_device *tdev); +int tinydrm_dirtyfb(struct drm_framebuffer *fb, void *vmem, unsigned flags, + unsigned color, struct drm_clip_rect *clips, + unsigned num_clips); + +static inline bool tinydrm_is_full_clip(struct drm_clip_rect *clip, u32 width, u32 height) +{ + return clip->x1 == 0 && clip->x2 >= (width - 1) && + clip->y1 == 0 && clip->y2 >= (height -1); +} + +static inline void tinydrm_reset_clip(struct drm_clip_rect *clip) +{ + clip->x1 = ~0; + clip->x2 = 0; + clip->y1 = ~0; + clip->y2 = 0; +} + +void tinydrm_merge_clips(struct drm_clip_rect *dst, + struct drm_clip_rect *clips, unsigned num_clips, + unsigned flags, u32 width, u32 height); + +#endif /* __LINUX_TINYDRM_H */