This patch is a DRM Driver for Rockchip Socs, driver provides an abstraction for the graphics hardware, such as lcd controller and connector interface. Signed-off-by: mark yao <yzq@xxxxxxxxxxxxxx> --- changes since v1: Adviced by Daniel Vetter: - Switch to universal plane API's --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/rockchip/Kconfig | 40 ++ drivers/gpu/drm/rockchip/Makefile | 12 + drivers/gpu/drm/rockchip/rockchip_drm_connector.c | 412 ++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_connector.h | 36 + drivers/gpu/drm/rockchip/rockchip_drm_drv.c | 600 +++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_drv.h | 128 ++++ drivers/gpu/drm/rockchip/rockchip_drm_fb.c | 48 ++ drivers/gpu/drm/rockchip/rockchip_drm_fb.h | 28 + drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c | 63 ++ drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h | 24 + drivers/gpu/drm/rockchip/rockchip_drm_gem.c | 163 +++++ drivers/gpu/drm/rockchip/rockchip_drm_gem.h | 40 ++ drivers/gpu/drm/rockchip/rockchip_drm_lcdc.c | 722 +++++++++++++++++++++ drivers/gpu/drm/rockchip/rockchip_drm_lcdc.h | 131 ++++ include/uapi/drm/rockchip_drm.h | 110 ++++ 17 files changed, 2560 insertions(+) create mode 100644 drivers/gpu/drm/rockchip/Kconfig create mode 100644 drivers/gpu/drm/rockchip/Makefile create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_connector.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_connector.h create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_drv.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_drv.h create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fb.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fb.h create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_gem.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_gem.h create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_lcdc.c create mode 100644 drivers/gpu/drm/rockchip/rockchip_drm_lcdc.h create mode 100644 include/uapi/drm/rockchip_drm.h diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index f512004..5951c2c 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -170,6 +170,8 @@ config DRM_SAVAGE source "drivers/gpu/drm/exynos/Kconfig" +source "drivers/gpu/drm/rockchip/Kconfig" + source "drivers/gpu/drm/vmwgfx/Kconfig" source "drivers/gpu/drm/gma500/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index dd2ba42..40babd2 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -51,6 +51,7 @@ obj-$(CONFIG_DRM_VMWGFX)+= vmwgfx/ obj-$(CONFIG_DRM_VIA) +=via/ obj-$(CONFIG_DRM_NOUVEAU) +=nouveau/ obj-$(CONFIG_DRM_EXYNOS) +=exynos/ +obj-$(CONFIG_DRM_ROCKCHIP) +=rockchip/ obj-$(CONFIG_DRM_GMA500) += gma500/ obj-$(CONFIG_DRM_UDL) += udl/ obj-$(CONFIG_DRM_AST) += ast/ diff --git a/drivers/gpu/drm/rockchip/Kconfig b/drivers/gpu/drm/rockchip/Kconfig new file mode 100644 index 0000000..592e999 --- /dev/null +++ b/drivers/gpu/drm/rockchip/Kconfig @@ -0,0 +1,40 @@ +config DRM_ROCKCHIP + tristate "DRM Support for Rockchip" + depends on DRM + select DRM_KMS_HELPER + select DRM_KMS_FB_HELPER + select DRM_KMS_CMA_HELPER + select DRM_GEM_CMA_HELPER + select DRM_PANEL + select FB_CFB_FILLRECT + select FB_CFB_COPYAREA + select FB_CFB_IMAGEBLIT + select VT_HW_CONSOLE_BINDING if FRAMEBUFFER_CONSOLE + select VIDEOMODE_HELPERS + select OF + help + Choose this option if you have a Rockchip soc chipset. + This driver provides kernel mode setting and buffer + management to userspace. This driver does not provides + 2D or 3D acceleration; acceleration is performed by other + IP found on the SoC. + +config DRM_ROCKCHIP_LCDC + bool "ROCKCHIP DRM LCDC" + depends on DRM_ROCKCHIP + select FB_MODE_HELPERS + help + Choose this option if you want to use Rockchip lcdc for DRM. + The driver provides an abstraction for Rockchip lcd controller, + lcd controller is the display interface from memory frame buffer + to display device. + +config DRM_ROCKCHIP_CONNECTOR + bool "ROCKCHIP DRM CONNECTOR" + depends on OF && DRM_ROCKCHIP && DRM_ROCKCHIP_LCDC + select FB_MODE_HELPERS + select VIDEOMODE_HELPERS + help + Choose this option if you want to use Rockchip Primary DISPLAY. + The driver provides an abstraction for Rockchip display devices, + such as lcd plane, lvds, edp , mipi, etc. diff --git a/drivers/gpu/drm/rockchip/Makefile b/drivers/gpu/drm/rockchip/Makefile new file mode 100644 index 0000000..45c9d50 --- /dev/null +++ b/drivers/gpu/drm/rockchip/Makefile @@ -0,0 +1,12 @@ +# +# Makefile for the drm device driver. This driver provides support for the +# Direct Rendering Infrastructure (DRI) in XFree86 4.1.0 and higher. + +ccflags-y := -Iinclude/drm -Idrivers/gpu/drm/rockchip + +rockchipdrm-y := rockchip_drm_drv.o rockchip_drm_gem.o \ + rockchip_drm_fb.o rockchip_drm_fbdev.o + +obj-$(CONFIG_DRM_ROCKCHIP_CONNECTOR) += rockchip_drm_connector.o +obj-$(CONFIG_DRM_ROCKCHIP_LCDC) += rockchip_drm_lcdc.o +obj-$(CONFIG_DRM_ROCKCHIP) += rockchipdrm.o diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_connector.c b/drivers/gpu/drm/rockchip/rockchip_drm_connector.c new file mode 100644 index 0000000..337b618 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_connector.c @@ -0,0 +1,412 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:mark yao <mark.yao@xxxxxxxxxxxxxx> + * + * based on exynos_drm_dpi.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_panel.h> + +#include <linux/component.h> + +#include <video/of_videomode.h> +#include <video/videomode.h> + +#include "rockchip_drm_drv.h" +#include "rockchip_drm_connector.h" +#include "rockchip_drm_lcdc.h" + +struct rockchip_conn_context { + struct device *dev; + + struct drm_panel *panel; + struct rockchip_connector *conn; + struct drm_connector connector; + struct drm_encoder encoder; + + struct drm_display_mode mode; + + u32 flags; + int type; + + int dpms_mode; +}; + +#define connector_to_ctx(c) \ + container_of(c, struct rockchip_conn_context, connector) + +#define encoder_to_ctx(c) \ + container_of(c, struct rockchip_conn_context, encoder) + +static inline int rockchip_convert_conn_type(int type) +{ + switch (type) { + case ROCKCHIP_DISPLAY_TYPE_RGB: + case ROCKCHIP_DISPLAY_TYPE_LVDS: + return DRM_MODE_CONNECTOR_LVDS; + case ROCKCHIP_DISPLAY_TYPE_EDP: + return DRM_MODE_CONNECTOR_eDP; + } + + return DRM_MODE_CONNECTOR_Unknown; +} + +static inline int rockchip_convert_encoder_type(int type) +{ + switch (type) { + case ROCKCHIP_DISPLAY_TYPE_RGB: + case ROCKCHIP_DISPLAY_TYPE_LVDS: + case ROCKCHIP_DISPLAY_TYPE_EDP: + return DRM_MODE_ENCODER_LVDS; + } + + return DRM_MODE_ENCODER_NONE; +} + +static enum drm_connector_status +rockchip_conn_detect(struct drm_connector *connector, bool force) +{ + return true; +} + +static void rockchip_connector_destroy(struct drm_connector *connector) +{ + drm_sysfs_connector_remove(connector); + drm_connector_cleanup(connector); +} + +static struct drm_connector_funcs rockchip_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .detect = rockchip_conn_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = rockchip_connector_destroy, +}; + +static int rockchip_conn_get_modes(struct drm_connector *connector) +{ + struct rockchip_conn_context *ctx = connector_to_ctx(connector); + struct drm_panel *panel = ctx->panel; + + return panel->funcs->get_modes(panel); +} + +static struct drm_encoder * + rockchip_conn_best_encoder(struct drm_connector *connector) +{ + struct rockchip_conn_context *ctx = connector_to_ctx(connector); + + return &ctx->encoder; +} + +static struct drm_connector_helper_funcs rockchip_connector_helper_funcs = { + .get_modes = rockchip_conn_get_modes, + .best_encoder = rockchip_conn_best_encoder, +}; + +static void rockchip_drm_encoder_dpms(struct drm_encoder *encoder, int mode) +{ + struct rockchip_conn_context *ctx = encoder_to_ctx(encoder); + struct rockchip_connector *conn = ctx->conn; + + switch (mode) { + case DRM_MODE_DPMS_ON: + if (ctx->dpms_mode != DRM_MODE_DPMS_ON) { + conn->enable(conn); + ctx->panel->funcs->enable(ctx->panel); + } + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + if (ctx->dpms_mode == DRM_MODE_DPMS_ON) { + ctx->panel->funcs->disable(ctx->panel); + conn->disable(conn); + } + break; + default: + break; + } + + ctx->dpms_mode = mode; +} + +static bool +rockchip_drm_encoder_mode_fixup(struct drm_encoder *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct rockchip_panel_special *priv_mode = + (void *)adjusted_mode->private; + struct rockchip_conn_context *ctx = encoder_to_ctx(encoder); + + priv_mode->out_type = ctx->conn->type; + + return true; +} + +static void rockchip_drm_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted) +{ + /* just set dummy now */ +} + +static void rockchip_drm_encoder_prepare(struct drm_encoder *encoder) +{ + /* drm framework doesn't check NULL. */ +} + +static void rockchip_drm_encoder_commit(struct drm_encoder *encoder) +{ + rockchip_drm_encoder_dpms(encoder, DRM_MODE_DPMS_ON); +} + +static void rockchip_drm_encoder_disable(struct drm_encoder *encoder) +{ + struct drm_plane *plane; + struct drm_device *dev = encoder->dev; + + rockchip_drm_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); + + /* all planes connected to this encoder should be also disabled. */ + list_for_each_entry(plane, &dev->mode_config.plane_list, head) { + if (plane->crtc && (plane->crtc == encoder->crtc)) + plane->funcs->disable_plane(plane); + } +} + + +static struct drm_encoder_helper_funcs rockchip_encoder_helper_funcs = { + .dpms = rockchip_drm_encoder_dpms, + .mode_fixup = rockchip_drm_encoder_mode_fixup, + .mode_set = rockchip_drm_encoder_mode_set, + .prepare = rockchip_drm_encoder_prepare, + .commit = rockchip_drm_encoder_commit, + .disable = rockchip_drm_encoder_disable, +}; +static void rockchip_drm_encoder_destroy(struct drm_encoder *encoder) +{ + drm_encoder_cleanup(encoder); +} + +static struct drm_encoder_funcs rockchip_encoder_funcs = { + .destroy = rockchip_drm_encoder_destroy, +}; + +static unsigned int rockchip_drm_encoder_clones(struct drm_encoder *encoder) +{ + struct rockchip_conn_context *ctx = encoder_to_ctx(encoder); + struct drm_encoder *clone; + struct drm_device *dev = encoder->dev; + unsigned int clone_mask = 0; + int cnt = 0; + + list_for_each_entry(clone, &dev->mode_config.encoder_list, head) { + switch (ctx->type) { + case ROCKCHIP_DISPLAY_TYPE_RGB: + case ROCKCHIP_DISPLAY_TYPE_LVDS: + case ROCKCHIP_DISPLAY_TYPE_EDP: + case ROCKCHIP_DISPLAY_TYPE_HDMI: + clone_mask |= (1 << (cnt++)); + break; + default: + continue; + } + } + + return clone_mask; +} + +static int rockchip_conn_bind(struct device *dev, struct device *master, + void *data) +{ + struct rockchip_conn_context *ctx; + unsigned long possible_crtcs = 0; + struct drm_encoder *encoder; + struct drm_connector *connector; + struct device_node *panel_node; + struct drm_device *drm_dev = data; + int ret; + + ctx = rockchip_drm_component_data_get(dev, + ROCKCHIP_DEVICE_TYPE_CONNECTOR); + if (!ctx) { + DRM_ERROR("can't find dp content form component\n"); + return -EINVAL; + } + + ret = rockchip_drm_pipe_get(dev); + if (ret < 0) { + DRM_ERROR("failed to bind display port\n"); + return ret; + } + + possible_crtcs |= 1 << ret; + + encoder = &ctx->encoder; + encoder->possible_crtcs = possible_crtcs; + + DRM_DEBUG_KMS("possible_crtcs = 0x%x\n", encoder->possible_crtcs); + + ret = drm_encoder_init(drm_dev, encoder, &rockchip_encoder_funcs, + rockchip_convert_encoder_type(ctx->type)); + if (ret) { + DRM_ERROR("failed to initialize encoder with drm\n"); + return ret; + } + + drm_encoder_helper_add(encoder, &rockchip_encoder_helper_funcs); + + connector = &ctx->connector; + connector->polled = DRM_CONNECTOR_POLL_HPD; + connector->dpms = DRM_MODE_DPMS_OFF; + + ret = drm_connector_init(drm_dev, connector, + &rockchip_connector_funcs, + rockchip_convert_conn_type(ctx->type)); + if (ret) { + DRM_ERROR("failed to initialize connector with drm\n"); + goto err_free_encoder; + } + + drm_connector_helper_add(connector, + &rockchip_connector_helper_funcs); + + ret = drm_sysfs_connector_add(connector); + if (ret) { + DRM_ERROR("failed to add drm_sysfs\n"); + goto err_free_connector; + } + + ret = drm_mode_connector_attach_encoder(connector, encoder); + if (ret) { + DRM_ERROR("failed to attach connector and encoder\n"); + goto err_free_connector_sysfs; + } + + panel_node = of_parse_phandle(dev->of_node, "rockchip,panel", 0); + if (!panel_node) { + DRM_ERROR("failed to find diaplay panel\n"); + goto err_free_connector_sysfs; + } + ctx->panel = of_drm_find_panel(panel_node); + if (!ctx->panel) { + DRM_ERROR("failed to find diaplay panel\n"); + ret = -ENODEV; + goto err_free_connector_sysfs; + } + + of_node_put(panel_node); + + ret = drm_panel_attach(ctx->panel, connector); + if (ret) { + DRM_ERROR("failed to attach connector and encoder\n"); + goto err_free_connector_sysfs; + } + + return 0; + +err_free_connector_sysfs: + drm_sysfs_connector_remove(connector); +err_free_connector: + drm_connector_cleanup(connector); +err_free_encoder: + drm_encoder_cleanup(encoder); + return ret; +} + +static void rockchip_conn_unbind(struct device *dev, struct device *master, + void *data) +{ + struct rockchip_conn_context *ctx; + struct drm_encoder *encoder; + + ctx = rockchip_drm_component_data_get(dev, + ROCKCHIP_DEVICE_TYPE_CONNECTOR); + encoder = &ctx->encoder; + + drm_panel_detach(ctx->panel); + + rockchip_drm_encoder_dpms(encoder, DRM_MODE_DPMS_OFF); + encoder->funcs->destroy(encoder); + drm_sysfs_connector_remove(&ctx->connector); + drm_connector_cleanup(&ctx->connector); + drm_encoder_cleanup(encoder); + + rockchip_drm_component_del(dev, ROCKCHIP_DEVICE_TYPE_CONNECTOR); +} + +static const struct component_ops rockchip_conn_component_ops = { + .bind = rockchip_conn_bind, + .unbind = rockchip_conn_unbind, +}; + +void *rockchip_connector_register(struct rockchip_connector *conn) +{ + struct rockchip_conn_context *ctx; + struct device *dev = conn->dev; + int ret; + + if (!dev) { + DRM_ERROR("please provide a device at dp register\n"); + return NULL; + } + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return NULL; + + ret = rockchip_drm_component_add(dev, ROCKCHIP_DEVICE_TYPE_CONNECTOR, + conn->type, ctx); + if (ret < 0) { + DRM_ERROR("register connector component fail ret =%d\n", ret); + return NULL; + } + + ctx->conn = conn; + ctx->dev = dev; + ctx->type = conn->type; + ctx->dpms_mode = DRM_MODE_DPMS_OFF; + + ret = component_add(dev, &rockchip_conn_component_ops); + if (ret) + goto err_del_component; + + DRM_DEBUG_KMS("succes register connector type=%d\n", conn->type); + + return ctx; + +err_del_component: + rockchip_drm_component_del(dev, ROCKCHIP_DEVICE_TYPE_CONNECTOR); + return NULL; +} + +void rockchip_connector_unregister(void *data) +{ + struct rockchip_conn_context *ctx = data; + + if (!ctx) + return; + rockchip_drm_component_del(ctx->dev, ROCKCHIP_DEVICE_TYPE_CONNECTOR); + component_del(ctx->dev, &rockchip_conn_component_ops); +} + +void rockchip_drm_encoder_setup(struct drm_device *dev) +{ + struct drm_encoder *encoder; + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) + encoder->possible_clones = + rockchip_drm_encoder_clones(encoder); +} diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_connector.h b/drivers/gpu/drm/rockchip/rockchip_drm_connector.h new file mode 100644 index 0000000..191f9fc --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_connector.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:mark yao <mark.yao@xxxxxxxxxxxxxx> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 _ROCKCHIP_DRM_CONNECTOR_H_ +#define _ROCKCHIP_DRM_CONNECTOR_H_ + +#include <drm/drm_crtc.h> + +#include "rockchip_drm_drv.h" + +struct rockchip_connector { + struct device *dev; + int type; + void *priv; + u32 flags; + + void (*enable)(struct rockchip_connector *conn); + void (*disable)(struct rockchip_connector *conn); + int (*setmode)(struct rockchip_connector *conn, + struct drm_display_mode *mode); +}; + +void *rockchip_connector_register(struct rockchip_connector *conn); +void rockchip_connector_unregister(void *data); +#endif diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.c b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c new file mode 100644 index 0000000..4871867 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.c @@ -0,0 +1,600 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:mark yao <mark.yao@xxxxxxxxxxxxxx> + * + * based on exynos_drm_drv.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <linux/pm_runtime.h> +#include <drm/drmP.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_gem_cma_helper.h> + +#include <linux/anon_inodes.h> +#include <linux/component.h> + +#include <drm/rockchip_drm.h> + +#include "rockchip_drm_drv.h" +#include "rockchip_drm_fb.h" +#include "rockchip_drm_fbdev.h" +#include "rockchip_drm_gem.h" + +#define DRIVER_NAME "rockchip-drm" +#define DRIVER_DESC "RockChip Soc DRM" +#define DRIVER_DATE "20140725" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 + +#define VBLANK_OFF_DELAY 50000 + +static struct platform_device *rockchip_drm_pdev; + +static DEFINE_MUTEX(drm_component_lock); +static LIST_HEAD(drm_component_list); + +struct component_dev { + struct list_head list; + struct device *crtc_dev; + struct device *conn_dev; + unsigned int out_type; + int pipe; + void *crtc_data; + void *conn_data; +}; + +static int rockchip_drm_load(struct drm_device *dev, unsigned long flags) +{ + struct rockchip_drm_private *private; + int ret; + int nr; + + private = kzalloc(sizeof(*private), GFP_KERNEL); + if (!private) + return -ENOMEM; + + dev_set_drvdata(dev->dev, dev); + dev->dev_private = (void *)private; + + drm_mode_config_init(dev); + + rockchip_drm_mode_config_init(dev); + + + /* Try to bind all sub drivers. */ + ret = component_bind_all(dev->dev, dev); + if (ret) + goto err_cleanup_vblank; + + for (nr = 0; nr < MAX_PLANE; nr++) { + struct drm_plane *plane; + unsigned long possible_crtcs = (1 << MAX_CRTC) - 1; + + plane = rockchip_plane_init(dev, possible_crtcs, false); + if (!plane) + goto err_mode_config_cleanup; + } + + /* init kms poll for handling hpd */ + drm_kms_helper_poll_init(dev); + + ret = drm_vblank_init(dev, MAX_CRTC); + if (ret) + goto err_mode_config_cleanup; + + /* setup possible_clones. */ + rockchip_drm_encoder_setup(dev); + + drm_vblank_offdelay = VBLANK_OFF_DELAY; + + platform_set_drvdata(dev->platformdev, dev); + rockchip_drm_fbdev_init(dev); + + /* force connectors detection */ + drm_helper_hpd_irq_event(dev); + + return 0; + +err_cleanup_vblank: + drm_vblank_cleanup(dev); +err_mode_config_cleanup: + drm_mode_config_cleanup(dev); + kfree(private); + + return ret; +} + +static int rockchip_drm_unload(struct drm_device *dev) +{ + rockchip_drm_fbdev_fini(dev); + drm_vblank_cleanup(dev); + drm_kms_helper_poll_fini(dev); + drm_mode_config_cleanup(dev); + + kfree(dev->dev_private); + + component_unbind_all(dev->dev, dev); + dev->dev_private = NULL; + + return 0; +} + +static int rockchip_drm_suspend(struct drm_device *dev, pm_message_t state) +{ + struct drm_connector *connector; + + drm_modeset_lock_all(dev); + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + int old_dpms = connector->dpms; + + if (connector->funcs->dpms) + connector->funcs->dpms(connector, DRM_MODE_DPMS_OFF); + + /* Set the old mode back to the connector for resume */ + connector->dpms = old_dpms; + } + drm_modeset_unlock_all(dev); + + return 0; +} + +static int rockchip_drm_resume(struct drm_device *dev) +{ + struct drm_connector *connector; + + drm_modeset_lock_all(dev); + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->funcs->dpms) + connector->funcs->dpms(connector, connector->dpms); + } + drm_modeset_unlock_all(dev); + + drm_helper_resume_force_mode(dev); + + return 0; +} + +static int rockchip_drm_open(struct drm_device *dev, struct drm_file *file) +{ + return 0; +} + +static void rockchip_drm_postclose(struct drm_device *dev, + struct drm_file *file) +{ + struct drm_pending_event *e, *et; + unsigned long flags; + + if (!file->driver_priv) + return; + + /* Release all events not unhandled by page flip handler. */ + rockchip_drm_crtc_cancel_pending_flip(dev); + + spin_lock_irqsave(&dev->event_lock, flags); + + /* Release all events handled by page flip handler but not freed. */ + list_for_each_entry_safe(e, et, &file->event_list, link) { + list_del(&e->link); + e->destroy(e); + } + + spin_unlock_irqrestore(&dev->event_lock, flags); + + kfree(file->driver_priv); + file->driver_priv = NULL; +} + +static const struct drm_ioctl_desc rockchip_ioctls[] = { + DRM_IOCTL_DEF_DRV(ROCKCHIP_GEM_CREATE, rockchip_drm_gem_create_ioctl, + DRM_UNLOCKED | DRM_AUTH), + DRM_IOCTL_DEF_DRV(ROCKCHIP_GEM_MAP_OFFSET, + rockchip_drm_gem_map_offset_ioctl, DRM_UNLOCKED | + DRM_AUTH), + DRM_IOCTL_DEF_DRV(ROCKCHIP_GEM_MMAP, rockchip_drm_gem_mmap_ioctl, + DRM_UNLOCKED | DRM_AUTH), + DRM_IOCTL_DEF_DRV(ROCKCHIP_GEM_GET, rockchip_drm_gem_get_ioctl, + DRM_UNLOCKED), +}; + +static const struct file_operations rockchip_drm_driver_fops = { + .owner = THIS_MODULE, + .open = drm_open, + .mmap = drm_gem_cma_mmap, + .poll = drm_poll, + .read = drm_read, + .unlocked_ioctl = drm_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = drm_compat_ioctl, +#endif + .release = drm_release, +}; + +static struct drm_driver rockchip_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME, + .load = rockchip_drm_load, + .unload = rockchip_drm_unload, + .suspend = rockchip_drm_suspend, + .resume = rockchip_drm_resume, + .open = rockchip_drm_open, + .postclose = rockchip_drm_postclose, + .get_vblank_counter = drm_vblank_count, + .enable_vblank = rockchip_drm_crtc_enable_vblank, + .disable_vblank = rockchip_drm_crtc_disable_vblank, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + .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, + .ioctls = rockchip_ioctls, + .num_ioctls = ARRAY_SIZE(rockchip_ioctls), + .fops = &rockchip_drm_driver_fops, + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, +}; + +#ifdef CONFIG_PM_SLEEP +static int rockchip_drm_sys_suspend(struct device *dev) +{ + struct drm_device *drm_dev = dev_get_drvdata(dev); + pm_message_t message; + + if (pm_runtime_suspended(dev)) + return 0; + + message.event = PM_EVENT_SUSPEND; + + return rockchip_drm_suspend(drm_dev, message); +} + +static int rockchip_drm_sys_resume(struct device *dev) +{ + struct drm_device *drm_dev = dev_get_drvdata(dev); + + if (pm_runtime_suspended(dev)) + return 0; + + return rockchip_drm_resume(drm_dev); +} +#endif + +static const struct dev_pm_ops rockchip_drm_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(rockchip_drm_sys_suspend, + rockchip_drm_sys_resume) +}; + +int rockchip_drm_pipe_get(struct device *dev) +{ + struct component_dev *cdev, *next; + int pipe = -1; + + mutex_lock(&drm_component_lock); + + list_for_each_entry_safe(cdev, next, &drm_component_list, list) { + if ((cdev->crtc_dev == dev) || (cdev->conn_dev == dev)) { + pipe = cdev->pipe; + break; + } + } + + mutex_unlock(&drm_component_lock); + + return pipe; +} + +int rockchip_drm_out_type_get(struct device *dev) +{ + struct component_dev *cdev, *next; + int type = -1; + + mutex_lock(&drm_component_lock); + + list_for_each_entry_safe(cdev, next, &drm_component_list, list) { + if ((cdev->crtc_dev == dev) || (cdev->conn_dev == dev)) { + type = cdev->out_type; + break; + } + } + + mutex_unlock(&drm_component_lock); + + return type; +} + +void *rockchip_drm_component_data_get(struct device *dev, + enum rockchip_drm_device_type dev_type) +{ + struct component_dev *cdev, *next; + void *data = NULL; + + mutex_lock(&drm_component_lock); + + list_for_each_entry_safe(cdev, next, &drm_component_list, list) { + if ((cdev->crtc_dev == dev) || (cdev->conn_dev == dev)) { + if (dev_type == ROCKCHIP_DEVICE_TYPE_CRTC) + data = cdev->crtc_data; + else if (dev_type == ROCKCHIP_DEVICE_TYPE_CONNECTOR) + data = cdev->conn_data; + break; + } + } + + mutex_unlock(&drm_component_lock); + + return data; +} + +int rockchip_drm_component_add(struct device *dev, + enum rockchip_drm_device_type dev_type, + int out_type, void *data) +{ + struct component_dev *cdev; + int pipe = -1; + + if (dev_type != ROCKCHIP_DEVICE_TYPE_CRTC && + dev_type != ROCKCHIP_DEVICE_TYPE_CONNECTOR) { + DRM_ERROR("invalid device type.\n"); + return -EINVAL; + } + + mutex_lock(&drm_component_lock); + + /* + * Make sure to check if there is a component which has two device + * objects, for connector and for encoder/connector. + * It should make sure that crtc and encoder/connector drivers are + * ready before rockchip drm core binds them. + */ + list_for_each_entry(cdev, &drm_component_list, list) { + pipe++; + /* + * out_type from crtc and display port, crtc set possible + * out_type maskbit at out_type, and display posr set out_type + * directly. and if crtc and display port all register, set + * out_type not maskbit; + */ + if (cdev->out_type & out_type) { + if (cdev->crtc_dev && cdev->conn_dev) { + DRM_ERROR("already register, not allow"); + return -EINVAL; + } + + if (dev_type == ROCKCHIP_DEVICE_TYPE_CRTC) { + cdev->pipe = pipe; + cdev->crtc_dev = dev; + cdev->crtc_data = data; + } else if (dev_type == ROCKCHIP_DEVICE_TYPE_CONNECTOR) { + cdev->conn_dev = dev; + cdev->conn_data = data; + cdev->out_type = out_type; + } + + mutex_unlock(&drm_component_lock); + return 0; + } + } + + mutex_unlock(&drm_component_lock); + + cdev = kzalloc(sizeof(*cdev), GFP_KERNEL); + if (!cdev) + return -ENOMEM; + + if (dev_type == ROCKCHIP_DEVICE_TYPE_CRTC) { + cdev->crtc_dev = dev; + cdev->crtc_data = data; + } else if (dev_type == ROCKCHIP_DEVICE_TYPE_CONNECTOR) { + cdev->conn_dev = dev; + cdev->conn_data = data; + } + + cdev->out_type = out_type; + + mutex_lock(&drm_component_lock); + list_add_tail(&cdev->list, &drm_component_list); + mutex_unlock(&drm_component_lock); + + return 0; +} + +void rockchip_drm_component_del(struct device *dev, + enum rockchip_drm_device_type dev_type) +{ + struct component_dev *cdev, *next; + + mutex_lock(&drm_component_lock); + + list_for_each_entry_safe(cdev, next, &drm_component_list, list) { + if (dev_type == ROCKCHIP_DEVICE_TYPE_CRTC) { + if (cdev->crtc_dev == dev) + cdev->crtc_dev = NULL; + } + + if (dev_type == ROCKCHIP_DEVICE_TYPE_CONNECTOR) { + if (cdev->conn_dev == dev) + cdev->conn_dev = NULL; + } + + /* + * Release cdev object only in case that both of crtc and + * encoder/connector device objects are NULL. + */ + if (!cdev->crtc_dev && !cdev->conn_dev) { + list_del(&cdev->list); + kfree(cdev); + } + + break; + } + + mutex_unlock(&drm_component_lock); +} + +static int compare_of(struct device *dev, void *data) +{ + return dev == (struct device *)data; +} + +static int rockchip_drm_add_components(struct device *dev, struct master *m) +{ + struct component_dev *cdev; + unsigned int attach_cnt = 0; + + mutex_lock(&drm_component_lock); + + list_for_each_entry(cdev, &drm_component_list, list) { + int ret; + + /* + * Add components to master only in case that crtc and + * encoder/connector device objects exist. + */ + if (!cdev->crtc_dev || !cdev->conn_dev) + continue; + + attach_cnt++; + + mutex_unlock(&drm_component_lock); + + /* + * Do not chage below call order. + * crtc device first should be added to master because + * connector/encoder need pipe number of crtc when they + * are created. + */ + ret = component_master_add_child(m, compare_of, cdev->crtc_dev); + ret |= component_master_add_child(m, compare_of, + cdev->conn_dev); + if (ret < 0) + return ret; + + mutex_lock(&drm_component_lock); + } + + mutex_unlock(&drm_component_lock); + + return attach_cnt ? 0 : -ENODEV; +} + +static int rockchip_drm_bind(struct device *dev) +{ + return drm_platform_init(&rockchip_drm_driver, to_platform_device(dev)); +} + +static void rockchip_drm_unbind(struct device *dev) +{ + drm_put_dev(dev_get_drvdata(dev)); +} + +static const struct component_master_ops rockchip_drm_ops = { + .add_components = rockchip_drm_add_components, + .bind = rockchip_drm_bind, + .unbind = rockchip_drm_unbind, +}; + +static int rockchip_drm_platform_probe(struct platform_device *pdev) +{ + int ret; + + pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32); + rockchip_drm_driver.num_ioctls = ARRAY_SIZE(rockchip_ioctls); + + ret = component_master_add(&pdev->dev, &rockchip_drm_ops); + if (ret < 0) + DRM_DEBUG_KMS("re-tried by last sub driver probed later.\n"); + + return 0; +} + +static int rockchip_drm_platform_remove(struct platform_device *pdev) +{ + component_master_del(&pdev->dev, &rockchip_drm_ops); + + return 0; +} + +static struct platform_driver rockchip_drm_platform_driver = { + .probe = rockchip_drm_platform_probe, + .remove = rockchip_drm_platform_remove, + .driver = { + .owner = THIS_MODULE, + .name = "rockchip-drm", + .pm = &rockchip_drm_pm_ops, + }, +}; + +static int rockchip_drm_init(void) +{ + int ret; + + ret = platform_driver_register(&rockchip_panel_platform_driver); + if (ret < 0) + return -ENOMEM; + +#ifdef CONFIG_DRM_ROCKCHIP_LCDC + ret = platform_driver_register(&rockchip_lcdc_platform_driver); + if (ret < 0) + goto out_lcdc; +#endif + + rockchip_drm_pdev = platform_device_register_simple("rockchip-drm", -1, + NULL, 0); + if (IS_ERR(rockchip_drm_pdev)) { + ret = PTR_ERR(rockchip_drm_pdev); + goto out_drm_pdev; + } + + ret = platform_driver_register(&rockchip_drm_platform_driver); + if (ret) + goto out_drm_driver; + + return 0; + +out_drm_driver: + platform_device_unregister(rockchip_drm_pdev); +out_drm_pdev: +#ifdef CONFIG_DRM_ROCKCHIP_LCDC + platform_driver_unregister(&rockchip_lcdc_platform_driver); +out_lcdc: +#endif + platform_driver_unregister(&rockchip_panel_platform_driver); + return ret; +} + +static void rockchip_drm_exit(void) +{ + platform_device_unregister(rockchip_drm_pdev); + platform_driver_unregister(&rockchip_drm_platform_driver); +#ifdef CONFIG_DRM_ROCKCHIP_LCDC + platform_driver_unregister(&rockchip_lcdc_platform_driver); +#endif + platform_driver_unregister(&rockchip_panel_platform_driver); +} + +module_init(rockchip_drm_init); +module_exit(rockchip_drm_exit); + +MODULE_AUTHOR("mark yao <mark.yao@xxxxxxxxxxxxxx>"); +MODULE_DESCRIPTION("ROCKCHIP DRM Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_drv.h b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h new file mode 100644 index 0000000..c0c1d89 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_drv.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:mark yao <mark.yao@xxxxxxxxxxxxxx> + * + * based on exynos_drm_drv.h + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 _ROCKCHIP_DRM_DRV_H_ +#define _ROCKCHIP_DRM_DRV_H_ + +#include <linux/module.h> + +#define MAX_CRTC 3 +#define MAX_PLANE 5 +#define MAX_FB_BUFFER 4 +#define DEFAULT_ZPOS -1 + +struct drm_device; +struct drm_connector; + +/* + * display output interface supported by rockchip lcdc + */ +#define ROCKCHIP_OUTFACE_P888 0 +#define ROCKCHIP_OUTFACE_P666 1 +#define ROCKCHIP_OUTFACE_P565 2 +/* for use special outface */ +#define ROCKCHIP_OUTFACE_AAAA 15 + +#define ROCKCHIP_COLOR_SWAP_RG 0x1 +#define ROCKCHIP_COLOR_SWAP_RB 0x2 +#define ROCKCHIP_COLOR_SWAP_GB 0x4 +/* + * Special panel info for rockchip + * + * @out_type: lcd controller need to know the sceen type. + * @out_face: the output pin interface. + * @color_swap: if want to swap color at output, use this. + * @pwr18: choice the power supply 1.8 or 3.3 mode for lcdc + * @dither: use dither func at lcd output + * @flags: the display flags, now just for pin sync level. + */ +struct rockchip_panel_special { + int out_type; + int out_face; + u32 color_swap; + bool pwr18; + bool dither; + u32 flags; +}; + +/* This enumerates device type. */ +enum rockchip_drm_device_type { + ROCKCHIP_DEVICE_TYPE_NONE, + ROCKCHIP_DEVICE_TYPE_CRTC, + ROCKCHIP_DEVICE_TYPE_CONNECTOR, +}; + +/* this enumerates display type. */ +enum rockchip_drm_output_type { + ROCKCHIP_DISPLAY_TYPE_NONE = 0, + /* RGB Interface. */ + ROCKCHIP_DISPLAY_TYPE_RGB = (1 << 0), + /* LVDS Interface. */ + ROCKCHIP_DISPLAY_TYPE_LVDS = (1 << 1), + /* DUAL LVDS Interface. */ + ROCKCHIP_DISPLAY_TYPE_DUAL_LVDS = (1 << 2), + /* EDP Interface. */ + ROCKCHIP_DISPLAY_TYPE_EDP = (1 << 3), + /* MIPI Interface. */ + ROCKCHIP_DISPLAY_TYPE_MIPI = (1 << 4), + /* HDMI Interface. */ + ROCKCHIP_DISPLAY_TYPE_HDMI = (1 << 5), +}; + +/* + * Rockchip drm private structure. + * + * @pipe: the pipe number for this crtc/manager. + */ +struct rockchip_drm_private { + struct drm_fbdev_cma *fbdev_cma; + /* + * created crtc object would be contained at this array and + * this array is used to be aware of which crtc did it request vblank. + */ + struct drm_crtc *crtc[MAX_CRTC]; + struct drm_property *plane_zpos_property; + struct drm_property *crtc_mode_property; + + unsigned int pipe; +}; + + +void rockchip_drm_crtc_finish_pageflip(struct drm_device *dev, int pipe); +void rockchip_drm_crtc_cancel_pending_flip(struct drm_device *dev); +int rockchip_drm_crtc_enable_vblank(struct drm_device *dev, int pipe); +void rockchip_drm_crtc_disable_vblank(struct drm_device *dev, int pipe); + +struct drm_plane *rockchip_plane_init(struct drm_device *dev, + unsigned long possible_crtcs, bool priv); + +void rockchip_drm_encoder_setup(struct drm_device *dev); + +void *rockchip_drm_component_data_get(struct device *dev, + enum rockchip_drm_device_type dev_type); +int rockchip_drm_pipe_get(struct device *dev); + +int rockchip_drm_component_add(struct device *dev, + enum rockchip_drm_device_type dev_type, + int out_type, void *data); +void rockchip_drm_component_del(struct device *dev, + enum rockchip_drm_device_type dev_type); + +extern struct platform_driver rockchip_panel_platform_driver; +#ifdef CONFIG_DRM_ROCKCHIP_LCDC +extern struct platform_driver rockchip_lcdc_platform_driver; +#endif +#endif /* _ROCKCHIP_DRM_DRV_H_ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.c b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c new file mode 100644 index 0000000..a04024b --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.c @@ -0,0 +1,48 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:mark yao <mark.yao@xxxxxxxxxxxxxx> + * + * based on exynos_drm_fb.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <drm/drmP.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_fb_helper.h> + +#include <uapi/drm/rockchip_drm.h> + +static struct drm_framebuffer * +rockchip_user_fb_create(struct drm_device *dev, struct drm_file *file_priv, + struct drm_mode_fb_cmd2 *mode_cmd) +{ + return drm_fb_cma_create(dev, file_priv, mode_cmd); +} + +static const struct drm_mode_config_funcs rockchip_drm_mode_config_funcs = { + .fb_create = rockchip_user_fb_create, +}; + +void rockchip_drm_mode_config_init(struct drm_device *dev) +{ + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + + /* + * set max width and height as default value(4096x4096). + * this value would be used to check framebuffer size limitation + * at drm_mode_addfb(). + */ + dev->mode_config.max_width = 4096; + dev->mode_config.max_height = 4096; + + dev->mode_config.funcs = &rockchip_drm_mode_config_funcs; +} diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fb.h b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h new file mode 100644 index 0000000..6258de6 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fb.h @@ -0,0 +1,28 @@ +/* + * + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:mark yao <mark.yao@xxxxxxxxxxxxxx> + * + * based on exynos_drm_fb.h + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 _ROCKCHIP_DRM_FB_H_ +#define _ROCKCHIP_DRM_FB_H_ + +struct drm_framebuffer * +rockchip_drm_framebuffer_init(struct drm_device *dev, + struct drm_mode_fb_cmd2 *mode_cmd, + struct drm_gem_object *obj); + +void rockchip_drm_mode_config_init(struct drm_device *dev); + +#endif /* _ROCKCHIP_DRM_FB_H_ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c new file mode 100644 index 0000000..d32fa57 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.c @@ -0,0 +1,63 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:mark yao <mark.yao@xxxxxxxxxxxxxx> + * + * based on exynos_drm_fbdev.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <drm/drmP.h> +#include <drm/drm_fb_cma_helper.h> + +#include <drm/rockchip_drm.h> + +#include "rockchip_drm_drv.h" + +#define MAX_CONNECTOR 4 +#define PREFERRED_BPP 32 + +int rockchip_drm_fbdev_init(struct drm_device *dev) +{ + struct rockchip_drm_private *private = dev->dev_private; + struct drm_fbdev_cma *fbdev_cma; + unsigned int num_crtc; + + if (!dev->mode_config.num_crtc || !dev->mode_config.num_connector) + return 0; + + if (private->fbdev_cma) { + DRM_ERROR("no allow to reinit cma fbdev\n"); + return -EINVAL; + } + + num_crtc = dev->mode_config.num_crtc; + + fbdev_cma = drm_fbdev_cma_init(dev, PREFERRED_BPP, num_crtc, + MAX_CONNECTOR); + if (!fbdev_cma) { + DRM_ERROR("failed to init cma fbdev\n"); + return -ENOMEM; + } + + private->fbdev_cma = fbdev_cma; + + return 0; +} + +void rockchip_drm_fbdev_fini(struct drm_device *dev) +{ + struct rockchip_drm_private *private = dev->dev_private; + + if (!private || !private->fbdev_cma) + return; + + drm_fbdev_cma_fini(private->fbdev_cma); +} diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h new file mode 100644 index 0000000..91cb535 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_fbdev.h @@ -0,0 +1,24 @@ +/* + * + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:mark yao <mark.yao@xxxxxxxxxxxxxx> + * + * based on exynos_drm_fbdev.h + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 _ROCKCHIP_DRM_FBDEV_H_ +#define _ROCKCHIP_DRM_FBDEV_H_ + +int rockchip_drm_fbdev_init(struct drm_device *dev); +void rockchip_drm_fbdev_fini(struct drm_device *dev); + +#endif /* _ROCKCHIP_DRM_FBDEV_H_ */ diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.c b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c new file mode 100644 index 0000000..f0219cd --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.c @@ -0,0 +1,163 @@ +/* + * + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:mark yao <mark.yao@xxxxxxxxxxxxxx> + * + * based on exynos_drm_gem.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include <drm/drmP.h> +#include <drm/drm_vma_manager.h> +#include <drm/drm_gem_cma_helper.h> + +#include <drm/rockchip_drm.h> + +#include "rockchip_drm_drv.h" +#include "rockchip_drm_gem.h" + +int rockchip_drm_gem_create_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_rockchip_gem_create *args = data; + struct drm_gem_cma_object *cma_obj; + struct drm_gem_object *gem_obj; + int ret; + + cma_obj = drm_gem_cma_create(dev, args->size); + if (IS_ERR(cma_obj)) + return PTR_ERR_OR_ZERO(cma_obj); + + gem_obj = &cma_obj->base; + + /* + * allocate a id of idr table where the obj is registered + * and handle has the id what user can see. + */ + ret = drm_gem_handle_create(file_priv, gem_obj, &args->handle); + if (ret) + goto err_handle_create; + + /* drop reference from allocate - handle holds it now. */ + drm_gem_object_unreference_unlocked(gem_obj); + + return PTR_ERR_OR_ZERO(cma_obj); + +err_handle_create: + drm_gem_cma_free_object(gem_obj); + return ret; +} + +int rockchip_drm_gem_map_offset_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_rockchip_gem_map_off *args = data; + + DRM_DEBUG_KMS("handle = 0x%x, offset = 0x%lx\n", + args->handle, (unsigned long)args->offset); + + return drm_gem_cma_dumb_map_offset(file_priv, dev, args->handle, + &args->offset); +} + +int rockchip_drm_gem_mmap_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_rockchip_gem_mmap *args = data; + struct drm_gem_object *obj; + unsigned long addr; + + mutex_lock(&dev->struct_mutex); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) { + DRM_ERROR("failed to lookup gem object.\n"); + mutex_unlock(&dev->struct_mutex); + return -EINVAL; + } + + addr = vm_mmap(obj->filp, 0, args->size, PROT_READ | PROT_WRITE, + MAP_SHARED, 0); + + drm_gem_object_unreference(obj); + + if (IS_ERR_VALUE(addr)) { + mutex_unlock(&dev->struct_mutex); + return (int)addr; + } + + mutex_unlock(&dev->struct_mutex); + + args->mapped = addr; + + DRM_DEBUG_KMS("mapped = 0x%lx\n", (unsigned long)args->mapped); + + return 0; +} + +int rockchip_drm_gem_get_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv) +{ + struct drm_rockchip_gem_info *args = data; + struct drm_gem_object *obj; + + mutex_lock(&dev->struct_mutex); + + obj = drm_gem_object_lookup(dev, file_priv, args->handle); + if (!obj) { + DRM_ERROR("failed to lookup gem object.\n"); + mutex_unlock(&dev->struct_mutex); + return -EINVAL; + } + + args->size = obj->size; + + drm_gem_object_unreference(obj); + mutex_unlock(&dev->struct_mutex); + + return 0; +} + +int rockchip_drm_gem_dumb_map_offset(struct drm_file *file_priv, + struct drm_device *dev, uint32_t handle, + uint64_t *offset) +{ + struct drm_gem_object *obj; + int ret = 0; + + mutex_lock(&dev->struct_mutex); + + /* + * get offset of memory allocated for drm framebuffer. + * - this callback would be called by user application + * with DRM_IOCTL_MODE_MAP_DUMB command. + */ + + obj = drm_gem_object_lookup(dev, file_priv, handle); + if (!obj) { + DRM_ERROR("failed to lookup gem object.\n"); + ret = -EINVAL; + goto unlock; + } + + ret = drm_gem_create_mmap_offset(obj); + if (ret) + goto out; + + *offset = drm_vma_node_offset_addr(&obj->vma_node); + DRM_DEBUG_KMS("offset = 0x%lx\n", (unsigned long)*offset); + +out: + drm_gem_object_unreference(obj); +unlock: + mutex_unlock(&dev->struct_mutex); + return ret; +} diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_gem.h b/drivers/gpu/drm/rockchip/rockchip_drm_gem.h new file mode 100644 index 0000000..fe8285f --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_gem.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:mark yao <mark.yao@xxxxxxxxxxxxxx> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 _ROCKCHIP_DRM_GEM_H_ +#define _ROCKCHIP_DRM_GEM_H_ + +/* + * request gem object creation and buffer allocation as the size + * that it is calculated with framebuffer information such as width, + * height and bpp. + */ +int rockchip_drm_gem_create_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); + +/* get buffer offset to map to user space. */ +int rockchip_drm_gem_map_offset_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); + +/* + * mmap the physically continuous memory that a gem object contains + * to user space. + */ +int rockchip_drm_gem_mmap_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); + +/* get buffer information to memory region allocated by gem. */ +int rockchip_drm_gem_get_ioctl(struct drm_device *dev, void *data, + struct drm_file *file_priv); +#endif diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_lcdc.c b/drivers/gpu/drm/rockchip/rockchip_drm_lcdc.c new file mode 100644 index 0000000..98bfbab --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_lcdc.c @@ -0,0 +1,722 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:mark yao <mark.yao@xxxxxxxxxxxxxx> + * + * based on exynos_drm_fimd.c + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ +#include <drm/drmP.h> +#include <drm/drm_crtc.h> +#include <drm/drm_crtc_helper.h> +#include <drm/drm_fb_cma_helper.h> +#include <drm/drm_gem_cma_helper.h> + +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/component.h> + +#include <drm/rockchip_drm.h> + +#include <video/of_display_timing.h> +#include <video/of_videomode.h> + +#include "rockchip_drm_drv.h" +#include "rockchip_drm_fbdev.h" +#include "rockchip_drm_lcdc.h" + +#define LCDC_DEFAULT_FRAMERATE 60 + +#define ROCKCHIP_DISPLAY_TYPE_LCD (ROCKCHIP_DISPLAY_TYPE_RGB | \ + ROCKCHIP_DISPLAY_TYPE_LVDS | \ + ROCKCHIP_DISPLAY_TYPE_EDP) + +static const uint32_t formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, +}; + +struct rockchip_plane { + int zpos; + struct drm_plane base; +}; + +struct lcdc_context { + struct device *dev; + struct drm_device *drm_dev; + struct drm_crtc crtc; + struct drm_pending_vblank_event *event; + struct drm_display_mode mode; + struct drm_plane *plane; + struct lcdc_driver *drv; + unsigned int default_win; + unsigned int dpms; + int pipe; + wait_queue_head_t wait_vsync_queue; + atomic_t wait_vsync_event; +}; + +#define to_lcdc_data(x) ((x)->drv->data) +#define to_lcdc_ctx(x) container_of(x, struct lcdc_context, crtc) +#define to_rockchip_plane(x) container_of(x, struct rockchip_plane, base) + +const struct of_device_id lcdc_driver_dt_match[] = { +#ifdef CONFIG_LCDC_RK3288 + { .compatible = "rockchip,rk3288-lcdc", + .data = (void *)&rockchip_rk3288_lcdc }, +#endif + {}, +}; + +static inline struct lcdc_driver_data *drm_lcdc_get_driver_data( + struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(lcdc_driver_dt_match, &pdev->dev); + + return (struct lcdc_driver_data *)of_id->data; +} + +static int rockchip_plane_get_size(int start, unsigned length, unsigned last) +{ + int end = start + length; + int size = 0; + + if (start <= 0) { + if (end > 0) + size = min_t(unsigned, end, last); + } else if (start <= last) { + size = min_t(unsigned, last - start, length); + } + + return size; +} + +static int rockchip_update_plane(struct drm_plane *plane, struct drm_crtc *crtc, + struct drm_framebuffer *fb, int crtc_x, + int crtc_y, unsigned int crtc_w, + unsigned int crtc_h, uint32_t src_x, + uint32_t src_y, uint32_t src_w, uint32_t src_h) +{ + struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane); + struct lcdc_context *ctx = to_lcdc_ctx(crtc); + struct lcdc_driver_data *lcdc_data = to_lcdc_data(ctx); + struct drm_gem_cma_object *gem; + struct lcdc_win_data *win_data; + unsigned long offset; + unsigned int actual_w; + unsigned int actual_h; + int win; + + DRM_DEBUG_KMS("LINE[%d]\n", __LINE__); + gem = drm_fb_cma_get_gem_obj(fb, 0); + if (!gem) { + DRM_ERROR("fail to get cma object from framebuffer\n"); + return -EINVAL; + } + + actual_w = rockchip_plane_get_size(crtc_x, + crtc_w, crtc->mode.hdisplay); + actual_h = rockchip_plane_get_size(crtc_y, + crtc_h, crtc->mode.vdisplay); + if (crtc_x < 0) { + if (actual_w) + src_x -= crtc_x; + crtc_x = 0; + } + + if (crtc_y < 0) { + if (actual_h) + src_y -= crtc_y; + crtc_y = 0; + } + + win = rockchip_plane->zpos; + if (win == DEFAULT_ZPOS) + win = ctx->default_win; + + if (win < 0 || win >= ZPOS_MAX_NUM) + return -EINVAL; + + offset = (src_x >> 16) * (fb->bits_per_pixel >> 3); + offset += (src_y >> 16) * fb->pitches[0]; + + DRM_DEBUG_KMS("offset = 0x%lx, pitch = %x\n", offset, fb->pitches[0]); + + win_data = lcdc_data->get_win(ctx->drv, win); + + win_data->xpos = crtc_x; + win_data->ypos = crtc_y; + win_data->xsize = actual_w; + win_data->ysize = actual_h; + win_data->xact = fb->width; + win_data->yact = fb->height; + win_data->y_vir_stride = fb->pitches[0] / (fb->bits_per_pixel >> 3); + win_data->yrgb_addr = gem->paddr + offset; + win_data->uv_addr = 0; + win_data->alpha_en = false; + + switch (fb->pixel_format) { + case DRM_FORMAT_ARGB8888: + win_data->alpha_en = true; + case DRM_FORMAT_XRGB8888: + win_data->format = ARGB888; + break; + case DRM_FORMAT_RGB565: + win_data->format = RGB565; + win_data->y_vir_stride = + ((win_data->y_vir_stride * 3) >> 2) + + win_data->y_vir_stride % 3; + break; + default: + DRM_DEBUG_KMS("invalid pixel size so using unpacked 24bpp.\n"); + win_data->alpha_en = false; + win_data->format = ARGB888; + break; + } + win_data->enabled = true; + DRM_DEBUG_KMS("offset_x = %d, offset_y = %d\n", + win_data->xpos, win_data->ypos); + DRM_DEBUG_KMS("ovl_width = %d, ovl_height = %d\n", + win_data->xsize, win_data->ysize); + DRM_DEBUG_KMS("paddr = 0x%lx\n", (unsigned long)win_data->yrgb_addr); + DRM_DEBUG_KMS("fb_width = %d, actual_w = %d\n", + fb->width, actual_w); + + lcdc_data->win_commit(ctx->drv, win_data); + return 0; +} + +static int rockchip_disable_plane(struct drm_plane *plane) +{ + struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane); + struct lcdc_context *ctx = to_lcdc_ctx(plane->crtc); + struct lcdc_driver_data *lcdc_data = to_lcdc_data(ctx); + struct lcdc_win_data *win_data; + int win = rockchip_plane->zpos; + + if (win == DEFAULT_ZPOS) + win = ctx->default_win; + + if (win < 0 || win >= ZPOS_MAX_NUM) + return -EINVAL; + + win_data = lcdc_data->get_win(ctx->drv, win); + + win_data->enabled = false; + lcdc_data->win_commit(ctx->drv, win_data); + + return 0; +} + +static void rockchip_plane_destroy(struct drm_plane *plane) +{ + struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane); + + DRM_DEBUG_KMS("LINE[%d]\n", __LINE__); + rockchip_disable_plane(plane); + drm_plane_cleanup(plane); + kfree(rockchip_plane); +} + +static int rockchip_plane_set_property(struct drm_plane *plane, + struct drm_property *property, + uint64_t val) +{ + struct drm_device *dev = plane->dev; + struct rockchip_plane *rockchip_plane = to_rockchip_plane(plane); + struct rockchip_drm_private *dev_priv = dev->dev_private; + + DRM_DEBUG_KMS("LINE[%d]\n", __LINE__); + if (property == dev_priv->plane_zpos_property) { + rockchip_plane->zpos = val; + return 0; + } + + return -EINVAL; +} + +static struct drm_plane_funcs rockchip_plane_funcs = { + .update_plane = rockchip_update_plane, + .disable_plane = rockchip_disable_plane, + .destroy = rockchip_plane_destroy, + .set_property = rockchip_plane_set_property, +}; + +static void rockchip_plane_attach_zpos_property(struct drm_plane *plane) +{ + struct drm_device *dev = plane->dev; + struct rockchip_drm_private *dev_priv = dev->dev_private; + struct drm_property *prop; + + DRM_DEBUG_KMS("LINE[%d]\n", __LINE__); + prop = dev_priv->plane_zpos_property; + if (!prop) { + prop = drm_property_create_range(dev, 0, "zpos", 0, + MAX_PLANE - 1); + if (!prop) + return; + + dev_priv->plane_zpos_property = prop; + } + + drm_object_attach_property(&plane->base, prop, 0); +} + +struct drm_plane *rockchip_plane_init(struct drm_device *dev, + unsigned long possible_crtcs, bool priv) +{ + struct rockchip_plane *rockchip_plane; + struct rockchip_drm_private *private = dev->dev_private; + enum drm_plane_type type; + int err; + + DRM_DEBUG_KMS("LINE[%d]\n", __LINE__); + rockchip_plane = kzalloc(sizeof(*rockchip_plane), GFP_KERNEL); + if (!rockchip_plane) + return NULL; + + type = priv ? DRM_PLANE_TYPE_PRIMARY : DRM_PLANE_TYPE_OVERLAY; + err = drm_universal_plane_init(dev, &rockchip_plane->base, + possible_crtcs, &rockchip_plane_funcs, + formats, ARRAY_SIZE(formats), type); + if (err) { + DRM_ERROR("failed to initialize plane\n"); + kfree(rockchip_plane); + return NULL; + } + + if (priv) { + rockchip_plane->base.crtc = private->crtc[0]; + rockchip_plane->zpos = DEFAULT_ZPOS; + } else { + rockchip_plane_attach_zpos_property(&rockchip_plane->base); + } + + return &rockchip_plane->base; +} + +int rockchip_drm_crtc_enable_vblank(struct drm_device *dev, int pipe) +{ + struct rockchip_drm_private *private = dev->dev_private; + struct lcdc_context *ctx = to_lcdc_ctx(private->crtc[pipe]); + struct lcdc_driver_data *lcdc_data = to_lcdc_data(ctx); + + DRM_DEBUG_KMS("LINE[%d]\n", __LINE__); + if (ctx->dpms != DRM_MODE_DPMS_ON) + return -EPERM; + + lcdc_data->enable_vblank(ctx->drv); + + return 0; +} + +void rockchip_drm_crtc_disable_vblank(struct drm_device *dev, int pipe) +{ + struct rockchip_drm_private *private = dev->dev_private; + struct lcdc_context *ctx = to_lcdc_ctx(private->crtc[pipe]); + struct lcdc_driver_data *lcdc_data = to_lcdc_data(ctx); + + DRM_DEBUG_KMS("LINE[%d]\n", __LINE__); + if (ctx->dpms != DRM_MODE_DPMS_ON) + return; + + lcdc_data->disable_vblank(ctx->drv); +} + +static void rockchip_drm_crtc_dpms(struct drm_crtc *crtc, int mode) +{ + struct lcdc_context *ctx = to_lcdc_ctx(crtc); + struct lcdc_driver_data *lcdc_data = to_lcdc_data(ctx); + + DRM_DEBUG_KMS("crtc[%d] mode[%d]\n", crtc->base.id, mode); + + if (ctx->dpms == mode) { + DRM_DEBUG_KMS("desired dpms mode is same as previous one.\n"); + return; + } + + if (mode > DRM_MODE_DPMS_ON) { + /* wait for the completion of page flip. */ + if (!wait_event_timeout(ctx->wait_vsync_queue, + !atomic_read(&ctx->wait_vsync_event), + HZ/20)) + DRM_DEBUG_KMS("vblank wait timed out.\n"); + drm_vblank_off(crtc->dev, ctx->pipe); + } + + switch (mode) { + case DRM_MODE_DPMS_ON: + lcdc_data->dpms(ctx->drv, DRM_MODE_DPMS_ON); + break; + case DRM_MODE_DPMS_STANDBY: + case DRM_MODE_DPMS_SUSPEND: + case DRM_MODE_DPMS_OFF: + lcdc_data->dpms(ctx->drv, DRM_MODE_DPMS_OFF); + break; + default: + DRM_DEBUG_KMS("unspecified mode %d\n", mode); + break; + } + + ctx->dpms = mode; +} + +static void rockchip_drm_crtc_prepare(struct drm_crtc *crtc) +{ + /* drm framework doesn't check NULL. */ +} + +static bool rockchip_drm_crtc_mode_fixup(struct drm_crtc *crtc, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + /* just do dummy now */ + + return true; +} + +static int rockchip_drm_crtc_mode_set(struct drm_crtc *crtc, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode, + int x, int y, + struct drm_framebuffer *fb) +{ + struct lcdc_context *ctx = to_lcdc_ctx(crtc); + struct lcdc_driver_data *lcdc_data = to_lcdc_data(ctx); + + DRM_DEBUG_KMS("LINE[%d]\n", __LINE__); + /* nothing to do if we haven't set the mode yet */ + if (adjusted_mode->htotal == 0 || adjusted_mode->vtotal == 0) + return -EINVAL; + + drm_mode_copy(&ctx->mode, adjusted_mode); + lcdc_data->mode_set(ctx->drv, &ctx->mode); + + return 0; +} +static int rockchip_drm_crtc_mode_set_base(struct drm_crtc *crtc, int x, int y, + struct drm_framebuffer *old_fb) +{ + struct lcdc_context *ctx = to_lcdc_ctx(crtc); + unsigned int crtc_w; + unsigned int crtc_h; + int ret; + + crtc_w = crtc->primary->fb->width - crtc->x; + crtc_h = crtc->primary->fb->height - crtc->y; + + ret = rockchip_update_plane(ctx->plane, crtc, crtc->primary->fb, 0, 0, + crtc_w, crtc_h, crtc->x, crtc->y, crtc_w, + crtc_h); + if (ret < 0) { + DRM_ERROR("fail to update plane\n"); + return -EINVAL; + } + + return 0; +} + +static void rockchip_drm_crtc_commit(struct drm_crtc *crtc) +{ + rockchip_drm_crtc_mode_set_base(crtc, crtc->x, crtc->y, + crtc->primary->fb); +} + +static struct drm_crtc_helper_funcs rockchip_crtc_helper_funcs = { + .dpms = rockchip_drm_crtc_dpms, + .prepare = rockchip_drm_crtc_prepare, + .mode_fixup = rockchip_drm_crtc_mode_fixup, + .mode_set = rockchip_drm_crtc_mode_set, + .mode_set_base = rockchip_drm_crtc_mode_set_base, + .commit = rockchip_drm_crtc_commit, +}; + +static int rockchip_drm_crtc_page_flip(struct drm_crtc *crtc, + struct drm_framebuffer *fb, + struct drm_pending_vblank_event *event, + uint32_t page_flip_flags) +{ + struct drm_device *dev = crtc->dev; + struct lcdc_context *ctx = to_lcdc_ctx(crtc); + struct drm_framebuffer *old_fb = crtc->primary->fb; + unsigned int crtc_w; + unsigned int crtc_h; + int ret = -EINVAL; + + DRM_DEBUG_KMS("LINE[%d]\n", __LINE__); + /* when the page flip is requested, crtc's dpms should be on */ + if (ctx->dpms > DRM_MODE_DPMS_ON) { + DRM_ERROR("failed page flip request.\n"); + return -EINVAL; + } + + mutex_lock(&dev->struct_mutex); + + /* + * the pipe from user always is 0 so we can set pipe number + * of current owner to event. + */ + ret = drm_vblank_get(dev, ctx->pipe); + if (ret) { + DRM_DEBUG("failed to acquire vblank counter\n"); + goto out; + } + + spin_lock_irq(&dev->event_lock); + if (ctx->event) { + spin_unlock_irq(&dev->event_lock); + DRM_ERROR("already pending flip!\n"); + ret = -EBUSY; + goto out; + } + ctx->event = event; + atomic_set(&ctx->wait_vsync_event, 1); + spin_unlock_irq(&dev->event_lock); + + crtc->primary->fb = fb; + crtc_w = crtc->primary->fb->width - crtc->x; + crtc_h = crtc->primary->fb->height - crtc->y; + + ret = rockchip_update_plane(ctx->plane, crtc, fb, 0, 0, crtc_w, crtc_h, + crtc->x, crtc->y, crtc_w, crtc_h); + if (ret) { + crtc->primary->fb = old_fb; + + spin_lock_irq(&dev->event_lock); + drm_vblank_put(dev, ctx->pipe); + atomic_set(&ctx->wait_vsync_event, 0); + ctx->event = NULL; + spin_unlock_irq(&dev->event_lock); + + goto out; + } +out: + mutex_unlock(&dev->struct_mutex); + return ret; +} + +void rockchip_drm_crtc_finish_pageflip(struct drm_device *dev, int pipe) +{ + struct rockchip_drm_private *dev_priv = dev->dev_private; + struct drm_crtc *drm_crtc = dev_priv->crtc[pipe]; + struct lcdc_context *ctx; + struct drm_pending_vblank_event *event; + unsigned long flags; + + DRM_DEBUG_KMS("LINE[%d]\n", __LINE__); + if (!drm_crtc) + return; + + ctx = to_lcdc_ctx(drm_crtc); + event = ctx->event; + + spin_lock_irqsave(&dev->event_lock, flags); + + if (event) { + ctx->event = NULL; + drm_send_vblank_event(dev, -1, event); + drm_vblank_put(dev, pipe); + atomic_set(&ctx->wait_vsync_event, 0); + wake_up(&ctx->wait_vsync_queue); + } + + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +void rockchip_drm_crtc_cancel_pending_flip(struct drm_device *dev) +{ + int i; + + DRM_DEBUG_KMS("cancle pending flip\n"); + for (i = 0; i < dev->num_crtcs; i++) + rockchip_drm_crtc_finish_pageflip(dev, i); +} + +static void rockchip_drm_crtc_destroy(struct drm_crtc *crtc) +{ + struct lcdc_context *ctx = to_lcdc_ctx(crtc); + struct rockchip_drm_private *private = crtc->dev->dev_private; + + DRM_DEBUG_KMS("LINE[%d]\n", __LINE__); + private->crtc[ctx->pipe] = NULL; + + drm_crtc_cleanup(crtc); +} + +static struct drm_crtc_funcs rockchip_crtc_funcs = { + .set_config = drm_crtc_helper_set_config, + .page_flip = rockchip_drm_crtc_page_flip, + .destroy = rockchip_drm_crtc_destroy, +}; + +void lcdc_vsync_event_handler(struct device *dev) +{ + struct drm_pending_vblank_event *event; + struct lcdc_context *ctx = dev_get_drvdata(dev); + struct drm_device *drm_dev; + unsigned long flags; + + DRM_DEBUG_KMS("LINE[%d]\n", __LINE__); + /* check the crtc is detached already from encoder */ + if (ctx && (ctx->pipe < 0 || !ctx->drm_dev)) + return; + + drm_handle_vblank(ctx->drm_dev, ctx->pipe); + + event = ctx->event; + drm_dev = ctx->drm_dev; + + spin_lock_irqsave(&drm_dev->event_lock, flags); + + if (event) { + ctx->event = NULL; + drm_send_vblank_event(drm_dev, -1, event); + drm_vblank_put(drm_dev, ctx->pipe); + atomic_set(&ctx->wait_vsync_event, 0); + wake_up(&ctx->wait_vsync_queue); + } + + spin_unlock_irqrestore(&drm_dev->event_lock, flags); +} + +static int lcdc_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *drm_dev = data; + struct rockchip_drm_private *private = drm_dev->dev_private; + struct lcdc_context *ctx = dev_get_drvdata(dev); + struct drm_crtc *crtc; + + ctx->drm_dev = drm_dev; + + ctx->pipe = rockchip_drm_pipe_get(dev); + ctx->dpms = DRM_MODE_DPMS_OFF; + crtc = &ctx->crtc; + + private->crtc[ctx->pipe] = crtc; + ctx->plane = rockchip_plane_init(drm_dev, 1 << ctx->pipe, true); + drm_crtc_init_with_planes(drm_dev, crtc, ctx->plane, NULL, &rockchip_crtc_funcs); + drm_crtc_helper_add(crtc, &rockchip_crtc_helper_funcs); + + /* + * enable drm irq mode. + * - with irq_enabled = true, we can use the vblank feature. + * + * P.S. note that we wouldn't use drm irq handler but + * just specific driver own one instead because + * drm framework supports only one irq handler. + */ + drm_dev->irq_enabled = true; + + /* + * with vblank_disable_allowed = true, vblank interrupt will be disabled + * by drm timer once a current process gives up ownership of + * vblank event.(after drm_vblank_put function is called) + */ + drm_dev->vblank_disable_allowed = true; + + return 0; +} + +static void lcdc_unbind(struct device *dev, struct device *master, + void *data) +{ struct drm_device *drm_dev = data; + struct rockchip_drm_private *private = drm_dev->dev_private; + struct lcdc_context *ctx = dev_get_drvdata(dev); + struct drm_crtc *crtc = &ctx->crtc; + + drm_crtc_cleanup(crtc); + private->crtc[ctx->pipe] = NULL; +} + +static const struct component_ops lcdc_component_ops = { + .bind = lcdc_bind, + .unbind = lcdc_unbind, +}; + +static int lcdc_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct lcdc_context *ctx; + struct lcdc_driver *lcdc_drv; + struct lcdc_driver_data *lcdc_data = drm_lcdc_get_driver_data(pdev); + int ret = -EINVAL; + + ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + if (!(lcdc_data->num_win && lcdc_data->init && + lcdc_data->deinit && lcdc_data->dpms && + lcdc_data->mode_set && lcdc_data->enable_vblank && + lcdc_data->disable_vblank && + lcdc_data->win_commit)) { + DRM_ERROR("lcdc driver ops is Incomplete\n"); + return -EINVAL; + } + lcdc_drv = lcdc_data->init(pdev); + if (!lcdc_drv) + return -EINVAL; + + lcdc_drv->data = lcdc_data; + ret = rockchip_drm_component_add(&pdev->dev, ROCKCHIP_DEVICE_TYPE_CRTC, + ROCKCHIP_DISPLAY_TYPE_LCD, ctx); + if (ret) + goto err_deinit_lcdc; + + ctx->dev = dev; + ctx->default_win = ZPOS_DEFAULT_WIN; + + ctx->drv = lcdc_drv; + + init_waitqueue_head(&ctx->wait_vsync_queue); + atomic_set(&ctx->wait_vsync_event, 0); + + platform_set_drvdata(pdev, ctx); + + pm_runtime_enable(&pdev->dev); + + ret = component_add(&pdev->dev, &lcdc_component_ops); + if (ret) + goto err_disable_pm_runtime; + + return ret; + +err_disable_pm_runtime: + pm_runtime_disable(dev); + rockchip_drm_component_del(dev, ROCKCHIP_DEVICE_TYPE_CRTC); +err_deinit_lcdc: + lcdc_data->deinit(ctx->drv); + return ret; +} + +static int lcdc_remove(struct platform_device *pdev) +{ + pm_runtime_disable(&pdev->dev); + + component_del(&pdev->dev, &lcdc_component_ops); + rockchip_drm_component_del(&pdev->dev, ROCKCHIP_DEVICE_TYPE_CRTC); + + return 0; +} + +struct platform_driver rockchip_lcdc_platform_driver = { + .probe = lcdc_probe, + .remove = lcdc_remove, + .driver = { + .name = "rockchip-lcdc", + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(lcdc_driver_dt_match), + }, +}; diff --git a/drivers/gpu/drm/rockchip/rockchip_drm_lcdc.h b/drivers/gpu/drm/rockchip/rockchip_drm_lcdc.h new file mode 100644 index 0000000..0a0f9c2 --- /dev/null +++ b/drivers/gpu/drm/rockchip/rockchip_drm_lcdc.h @@ -0,0 +1,131 @@ +/* + * Copyright (C) Fuzhou Rockchip Electronics Co.Ltd + * Author:mark yao <mark.yao@xxxxxxxxxxxxxx> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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 _ROCKCHIP_DRM_LCDC_H_ +#define _ROCKCHIP_DRM_LCDC_H_ +#include <linux/platform_device.h> +#include <drm/drm_crtc.h> + +#include "rockchip_drm_drv.h" + +enum { + ZPOS_DEFAULT_WIN = 0, + ZPOS_CURSOR_WIN, + ZPOS_MAX_NUM, + ZPOS_UNUSED_WIN +}; + +enum data_format { + ARGB888 = 0, + RGB888, + RGB565, + YUV420 = 4, + YUV422, + YUV444, + XRGB888, + XBGR888, + ABGR888, + YUV420_A = 10, + YUV422_A, + YUV444_A +}; + +struct lcdc_win_data { + int zpos; + int id; + enum data_format format; + u32 xact; + u32 yact; + u32 xsize; + u32 ysize; + u32 xpos; + u32 ypos; + u32 y_vir_stride; + u32 uv_vir_stride; + bool alpha_en; + bool enabled; + bool resume; + + dma_addr_t yrgb_addr; + dma_addr_t uv_addr; + + int dsp_stx; + int dsp_sty; + /* win sel layer */ + int z_order; + u8 fmt_cfg; + u8 fmt_10; + u8 swap_rb; + u32 reserved; + u32 area_num; + u32 scale_yrgb_x; + u32 scale_yrgb_y; + u32 scale_cbcr_x; + u32 scale_cbcr_y; + bool support_3d; + + u8 win_lb_mode; + + u8 bic_coe_el; + /* h 01:scale up ;10:down */ + u8 yrgb_hor_scl_mode; + /* v 01:scale up ;10:down */ + u8 yrgb_ver_scl_mode; + /* h scale down mode */ + u8 yrgb_hsd_mode; + /* v scale up mode */ + u8 yrgb_vsu_mode; + /* v scale down mode */ + u8 yrgb_vsd_mode; + u8 cbr_hor_scl_mode; + u8 cbr_ver_scl_mode; + u8 cbr_hsd_mode; + u8 cbr_vsu_mode; + u8 cbr_vsd_mode; + u8 vsd_yrgb_gt4; + u8 vsd_yrgb_gt2; + u8 vsd_cbr_gt4; + u8 vsd_cbr_gt2; + + u32 alpha_mode; + u32 g_alpha_val; + u32 color_key_val; +}; + +struct lcdc_driver_data { + int num_win; + struct lcdc_driver * (*init)(struct platform_device *pdev); + void (*deinit)(struct lcdc_driver *drv); + void (*dpms)(struct lcdc_driver *drv, int mode); + void (*mode_set)(struct lcdc_driver *drv, + struct drm_display_mode *mode); + void (*enable_vblank)(struct lcdc_driver *drv); + void (*disable_vblank)(struct lcdc_driver *drv); + struct lcdc_win_data * (*get_win)(struct lcdc_driver *drv, int zpos); + void (*win_commit)(struct lcdc_driver *drv, + struct lcdc_win_data *win); +}; + +struct lcdc_driver { + int id; + + struct lcdc_driver_data *data; +}; + + +void lcdc_vsync_event_handler(struct device *dev); +#ifdef CONFIG_LCDC_RK3288 +extern struct lcdc_driver_data rockchip_rk3288_lcdc; +#endif +#endif /* _ROCKCHIP_DRM_LCDC_H_ */ diff --git a/include/uapi/drm/rockchip_drm.h b/include/uapi/drm/rockchip_drm.h new file mode 100644 index 0000000..1b567f7 --- /dev/null +++ b/include/uapi/drm/rockchip_drm.h @@ -0,0 +1,110 @@ +/* + * + * Copyright (c) Fuzhou Rockchip Electronics Co.Ltd + * Authors: + * mark yao <yzq@xxxxxxxxxxxxxx> + * + * base on exynos_drm.h + * + * 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 _UAPI_ROCKCHIP_DRM_H_ +#define _UAPI_ROCKCHIP_DRM_H_ + +#include <drm/drm.h> + +/** + * User-desired buffer creation information structure. + * + * @size: user-desired memory allocation size. + * - this size value would be page-aligned internally. + * @flags: user request for setting memory type or cache attributes. + * @handle: returned a handle to created gem object. + * - this handle will be set by gem module of kernel side. + */ +struct drm_rockchip_gem_create { + uint64_t size; + unsigned int flags; + unsigned int handle; +}; + +/** + * A structure for getting buffer offset. + * + * @handle: a pointer to gem object created. + * @pad: just padding to be 64-bit aligned. + * @offset: relatived offset value of the memory region allocated. + * - this value should be set by user. + */ +struct drm_rockchip_gem_map_off { + unsigned int handle; + unsigned int pad; + uint64_t offset; +}; + +/** + * A structure for mapping buffer. + * + * @handle: a handle to gem object created. + * @pad: just padding to be 64-bit aligned. + * @size: memory size to be mapped. + * @mapped: having user virtual address mmaped. + * - this variable would be filled by rockchip gem module + * of kernel side with user virtual address which is allocated + * by do_mmap(). + */ +struct drm_rockchip_gem_mmap { + unsigned int handle; + unsigned int pad; + uint64_t size; + uint64_t mapped; +}; + +/** + * A structure to gem information. + * + * @handle: a handle to gem object created. + * @flags: flag value including memory type and cache attribute and + * this value would be set by driver. + * @size: size to memory region allocated by gem and this size would + * be set by driver. + */ +struct drm_rockchip_gem_info { + unsigned int handle; + unsigned int flags; + uint64_t size; +}; + +/* memory type definitions. */ +enum e_drm_rockchip_gem_mem_type { + /* non-cachable mapping and used as default. */ + ROCKCHIP_BO_NONCACHABLE = 0 << 0, + /* cachable mapping. */ + ROCKCHIP_BO_CACHABLE = 1 << 0, + /* write-combine mapping. */ + ROCKCHIP_BO_WC = 1 << 1, + ROCKCHIP_BO_MASK = ROCKCHIP_BO_CACHABLE | ROCKCHIP_BO_WC +}; + +#define DRM_ROCKCHIP_GEM_CREATE 0x00 +#define DRM_ROCKCHIP_GEM_MAP_OFFSET 0x01 +#define DRM_ROCKCHIP_GEM_MMAP 0x02 +/* Reserved 0x03 ~ 0x05 for rockchip specific gem ioctl */ +#define DRM_ROCKCHIP_GEM_GET 0x04 + +#define DRM_IOCTL_ROCKCHIP_GEM_CREATE DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_GEM_CREATE, struct drm_rockchip_gem_create) + +#define DRM_IOCTL_ROCKCHIP_GEM_MAP_OFFSET DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_GEM_MAP_OFFSET, struct drm_rockchip_gem_map_off) + +#define DRM_IOCTL_ROCKCHIP_GEM_MMAP DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_GEM_MMAP, struct drm_rockchip_gem_mmap) + +#define DRM_IOCTL_ROCKCHIP_GEM_GET DRM_IOWR(DRM_COMMAND_BASE + \ + DRM_ROCKCHIP_GEM_GET, struct drm_rockchip_gem_info) +#endif /* _UAPI_ROCKCHIP_DRM_H_ */ -- 1.7.9.5 -- To unsubscribe from this list: send the line "unsubscribe linux-api" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html