From patchwork Mon Mar 28 11:36:35 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Alexey Brodkin X-Patchwork-Id: 8680881 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 B9099C0553 for ; Tue, 29 Mar 2016 00:39:32 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 998AE20225 for ; Tue, 29 Mar 2016 00:39:30 +0000 (UTC) Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) by mail.kernel.org (Postfix) with ESMTP id 7DB612020F for ; Tue, 29 Mar 2016 00:39:28 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id 9D9036E5EE; Tue, 29 Mar 2016 00:39:09 +0000 (UTC) X-Original-To: dri-devel@lists.freedesktop.org Delivered-To: dri-devel@lists.freedesktop.org Received: from smtprelay.synopsys.com (smtprelay.synopsys.com [198.182.47.9]) by gabe.freedesktop.org (Postfix) with ESMTPS id CDF716E396 for ; Mon, 28 Mar 2016 11:37:00 +0000 (UTC) Received: from dc8secmta1.synopsys.com (dc8secmta1.synopsys.com [10.13.218.200]) by smtprelay.synopsys.com (Postfix) with ESMTP id 7B1C724E16F9; Mon, 28 Mar 2016 04:37:00 -0700 (PDT) Received: from dc8secmta1.internal.synopsys.com (dc8secmta1.internal.synopsys.com [127.0.0.1]) by dc8secmta1.internal.synopsys.com (Service) with ESMTP id 6FAA027115; Mon, 28 Mar 2016 04:37:00 -0700 (PDT) Received: from mailhost.synopsys.com (unknown [10.13.184.66]) by dc8secmta1.internal.synopsys.com (Service) with ESMTP id 130BE27114; Mon, 28 Mar 2016 04:37:00 -0700 (PDT) Received: from mailhost.synopsys.com (localhost [127.0.0.1]) by mailhost.synopsys.com (Postfix) with ESMTP id EBBE0E21; Mon, 28 Mar 2016 04:36:59 -0700 (PDT) Received: from abrodkin-7440l.internal.synopsys.com (unknown [10.225.15.82]) by mailhost.synopsys.com (Postfix) with ESMTP id 63603E1E; Mon, 28 Mar 2016 04:36:57 -0700 (PDT) From: Alexey Brodkin To: dri-devel@lists.freedesktop.org Subject: [PATCH 1/5 v5] drm: Add support of ARC PGU display controller Date: Mon, 28 Mar 2016 14:36:35 +0300 Message-Id: <1459164999-11850-2-git-send-email-abrodkin@synopsys.com> X-Mailer: git-send-email 2.5.0 In-Reply-To: <1459164999-11850-1-git-send-email-abrodkin@synopsys.com> References: <1459164999-11850-1-git-send-email-abrodkin@synopsys.com> X-Mailman-Approved-At: Tue, 29 Mar 2016 00:38:17 +0000 Cc: linux-snps-arc@lists.infradead.org, Alexey Brodkin , linux-kernel@vger.kernel.org, Carlos Palminha 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: , MIME-Version: 1.0 Errors-To: dri-devel-bounces@lists.freedesktop.org Sender: "dri-devel" X-Spam-Status: No, score=-5.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 From: Carlos Palminha ARC PGU could be found on some development boards from Synopsys. This is a simple byte streamer that reads data from a framebuffer and sends data to the single encoder. Signed-off-by: Carlos Palminha Signed-off-by: Alexey Brodkin Cc: David Airlie Cc: dri-devel@lists.freedesktop.org Cc: linux-snps-arc@lists.infradead.org --- Note following series that introduces drm_connector_register_all()/drm_connector_unregister_all() is a prerequisite now: https://lkml.org/lkml/2016/3/23/61 No changes v4 -> v5. Changes v3 -> v4: * The driver author is now set properly (thanks Carlos for all your efforts) * Implemented correct hsync and vsync setup (thanks Jose) * Dummy call-backs were removed (as suggested by Daniel) * Obsolete load()/unload() call-backs were removed (as suggested by Daniel) * With above in mind we were able to adopt recently introduced drm_connector_register_all()/drm_connector_unregister_all() Changes v2 -> v3: * Improved failure path if arcpgu_connector wasn't allocated (thanks Jose). * Fixed driver building as module (reported by 0-DAY kernel test infrastruct.) * Implemented uncached mapping of user-space FB pages. No changes v1 -> v2. drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/arc/Kconfig | 10 ++ drivers/gpu/drm/arc/Makefile | 2 + drivers/gpu/drm/arc/arcpgu.h | 50 +++++++ drivers/gpu/drm/arc/arcpgu_crtc.c | 257 ++++++++++++++++++++++++++++++++++ drivers/gpu/drm/arc/arcpgu_drv.c | 282 ++++++++++++++++++++++++++++++++++++++ drivers/gpu/drm/arc/arcpgu_hdmi.c | 201 +++++++++++++++++++++++++++ drivers/gpu/drm/arc/arcpgu_regs.h | 40 ++++++ 9 files changed, 845 insertions(+) create mode 100644 drivers/gpu/drm/arc/Kconfig create mode 100644 drivers/gpu/drm/arc/Makefile create mode 100644 drivers/gpu/drm/arc/arcpgu.h create mode 100644 drivers/gpu/drm/arc/arcpgu_crtc.c create mode 100644 drivers/gpu/drm/arc/arcpgu_drv.c create mode 100644 drivers/gpu/drm/arc/arcpgu_hdmi.c create mode 100644 drivers/gpu/drm/arc/arcpgu_regs.h diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index f2a74d0..9e4f2f1 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -281,3 +281,5 @@ source "drivers/gpu/drm/imx/Kconfig" source "drivers/gpu/drm/vc4/Kconfig" source "drivers/gpu/drm/etnaviv/Kconfig" + +source "drivers/gpu/drm/arc/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index 6eb94fc..c338d04 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -78,3 +78,4 @@ obj-y += panel/ obj-y += bridge/ obj-$(CONFIG_DRM_FSL_DCU) += fsl-dcu/ obj-$(CONFIG_DRM_ETNAVIV) += etnaviv/ +obj-$(CONFIG_DRM_ARCPGU)+= arc/ diff --git a/drivers/gpu/drm/arc/Kconfig b/drivers/gpu/drm/arc/Kconfig new file mode 100644 index 0000000..f9a13b6 --- /dev/null +++ b/drivers/gpu/drm/arc/Kconfig @@ -0,0 +1,10 @@ +config DRM_ARCPGU + tristate "ARC PGU" + depends on DRM && OF + select DRM_KMS_CMA_HELPER + select DRM_KMS_FB_HELPER + select DRM_KMS_HELPER + help + Choose this option if you have an ARC PGU controller. + + If M is selected the module will be called arcpgu. diff --git a/drivers/gpu/drm/arc/Makefile b/drivers/gpu/drm/arc/Makefile new file mode 100644 index 0000000..d48fda7 --- /dev/null +++ b/drivers/gpu/drm/arc/Makefile @@ -0,0 +1,2 @@ +arcpgu-y := arcpgu_crtc.o arcpgu_hdmi.o arcpgu_drv.o +obj-$(CONFIG_DRM_ARCPGU) += arcpgu.o diff --git a/drivers/gpu/drm/arc/arcpgu.h b/drivers/gpu/drm/arc/arcpgu.h new file mode 100644 index 0000000..86574b6 --- /dev/null +++ b/drivers/gpu/drm/arc/arcpgu.h @@ -0,0 +1,50 @@ +/* + * ARC PGU DRM driver. + * + * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARCPGU_H_ +#define _ARCPGU_H_ + +struct arcpgu_drm_private { + void __iomem *regs; + struct clk *clk; + struct drm_fbdev_cma *fbdev; + struct drm_framebuffer *fb; + struct list_head event_list; + struct drm_crtc crtc; + struct drm_plane *plane; +}; + +#define crtc_to_arcpgu_priv(x) container_of(x, struct arcpgu_drm_private, crtc) + +static inline void arc_pgu_write(struct arcpgu_drm_private *arcpgu, + unsigned int reg, u32 value) +{ + iowrite32(value, arcpgu->regs + reg); +} + +static inline u32 arc_pgu_read(struct arcpgu_drm_private *arcpgu, + unsigned int reg) +{ + return ioread32(arcpgu->regs + reg); +} + +int arc_pgu_setup_crtc(struct drm_device *dev); +int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np); +struct drm_fbdev_cma *arcpgu_fbdev_cma_init(struct drm_device *dev, + unsigned int preferred_bpp, unsigned int num_crtc, + unsigned int max_conn_count); + +#endif diff --git a/drivers/gpu/drm/arc/arcpgu_crtc.c b/drivers/gpu/drm/arc/arcpgu_crtc.c new file mode 100644 index 0000000..92f8bef --- /dev/null +++ b/drivers/gpu/drm/arc/arcpgu_crtc.c @@ -0,0 +1,257 @@ +/* + * ARC PGU DRM driver. + * + * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "arcpgu.h" +#include "arcpgu_regs.h" + +#define ENCODE_PGU_XY(x, y) ((((x) - 1) << 16) | ((y) - 1)) + +static struct simplefb_format supported_formats[] = { + { "r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, DRM_FORMAT_RGB565 }, + { "r8g8b8", 24, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_RGB888 }, +}; + +static void arc_pgu_set_pxl_fmt(struct drm_crtc *crtc) +{ + struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc); + uint32_t pixel_format = crtc->primary->state->fb->pixel_format; + struct simplefb_format *format = NULL; + int i; + + for (i = 0; i < ARRAY_SIZE(supported_formats); i++) { + if (supported_formats[i].fourcc == pixel_format) + format = &supported_formats[i]; + } + + if (WARN_ON(!format)) + return; + + if (format->fourcc == DRM_FORMAT_RGB888) + arc_pgu_write(arcpgu, ARCPGU_REG_CTRL, + arc_pgu_read(arcpgu, ARCPGU_REG_CTRL) | + ARCPGU_MODE_RGB888_MASK); + +} + +static const struct drm_crtc_funcs arc_pgu_crtc_funcs = { + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, +}; + +static void arc_pgu_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc); + struct drm_display_mode *m = &crtc->state->adjusted_mode; + u32 val; + + arc_pgu_write(arcpgu, ARCPGU_REG_FMT, + ENCODE_PGU_XY(m->crtc_htotal, m->crtc_vtotal)); + + arc_pgu_write(arcpgu, ARCPGU_REG_HSYNC, + ENCODE_PGU_XY(m->crtc_hsync_start - m->crtc_hdisplay, + m->crtc_hsync_end - m->crtc_hdisplay)); + + arc_pgu_write(arcpgu, ARCPGU_REG_VSYNC, + ENCODE_PGU_XY(m->crtc_vsync_start - m->crtc_vdisplay, + m->crtc_vsync_end - m->crtc_vdisplay)); + + arc_pgu_write(arcpgu, ARCPGU_REG_ACTIVE, + ENCODE_PGU_XY(m->crtc_hblank_end - m->crtc_hblank_start, + m->crtc_vblank_end - m->crtc_vblank_start)); + + val = arc_pgu_read(arcpgu, ARCPGU_REG_CTRL); + + if (m->flags & DRM_MODE_FLAG_PVSYNC) + val |= ARCPGU_CTRL_VS_POL_MASK << ARCPGU_CTRL_VS_POL_OFST; + else + val &= ~(ARCPGU_CTRL_VS_POL_MASK << ARCPGU_CTRL_VS_POL_OFST); + + if (m->flags & DRM_MODE_FLAG_PHSYNC) + val |= ARCPGU_CTRL_HS_POL_MASK << ARCPGU_CTRL_HS_POL_OFST; + else + val &= ~(ARCPGU_CTRL_HS_POL_MASK << ARCPGU_CTRL_HS_POL_OFST); + + arc_pgu_write(arcpgu, ARCPGU_REG_CTRL, val); + arc_pgu_write(arcpgu, ARCPGU_REG_STRIDE, 0); + arc_pgu_write(arcpgu, ARCPGU_REG_START_SET, 1); + + arc_pgu_set_pxl_fmt(crtc); + + clk_set_rate(arcpgu->clk, m->crtc_clock * 1000); +} + +static void arc_pgu_crtc_enable(struct drm_crtc *crtc) +{ + struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc); + + clk_prepare_enable(arcpgu->clk); + arc_pgu_write(arcpgu, ARCPGU_REG_CTRL, + arc_pgu_read(arcpgu, ARCPGU_REG_CTRL) | + ARCPGU_CTRL_ENABLE_MASK); +} + +static void arc_pgu_crtc_disable(struct drm_crtc *crtc) +{ + struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc); + + if (!crtc->primary->fb) + return; + + clk_disable_unprepare(arcpgu->clk); + arc_pgu_write(arcpgu, ARCPGU_REG_CTRL, + arc_pgu_read(arcpgu, ARCPGU_REG_CTRL) & + ~ARCPGU_CTRL_ENABLE_MASK); +} + +static int arc_pgu_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc); + struct drm_display_mode *mode = &state->adjusted_mode; + long rate, clk_rate = mode->clock * 1000; + + rate = clk_round_rate(arcpgu->clk, clk_rate); + if (rate != clk_rate) + return -EINVAL; + + return 0; +} + +static void arc_pgu_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct arcpgu_drm_private *arcpgu = crtc_to_arcpgu_priv(crtc); + unsigned long flags; + + if (crtc->state->event) { + struct drm_pending_vblank_event *event = crtc->state->event; + + crtc->state->event = NULL; + event->pipe = drm_crtc_index(crtc); + + WARN_ON(drm_crtc_vblank_get(crtc) != 0); + + spin_lock_irqsave(&crtc->dev->event_lock, flags); + list_add_tail(&event->base.link, &arcpgu->event_list); + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + } +} + +static const struct drm_crtc_helper_funcs arc_pgu_crtc_helper_funcs = { + .mode_set = drm_helper_crtc_mode_set, + .mode_set_base = drm_helper_crtc_mode_set_base, + .mode_set_nofb = arc_pgu_crtc_mode_set_nofb, + .enable = arc_pgu_crtc_enable, + .disable = arc_pgu_crtc_disable, + .prepare = arc_pgu_crtc_disable, + .commit = arc_pgu_crtc_enable, + .atomic_check = arc_pgu_crtc_atomic_check, + .atomic_begin = arc_pgu_crtc_atomic_begin, +}; + +static void arc_pgu_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct arcpgu_drm_private *arcpgu; + struct drm_gem_cma_object *gem; + + if (!plane->state->crtc || !plane->state->fb) + return; + + arcpgu = crtc_to_arcpgu_priv(plane->state->crtc); + gem = drm_fb_cma_get_gem_obj(plane->state->fb, 0); + arc_pgu_write(arcpgu, ARCPGU_REG_BUF0_ADDR, gem->paddr); +} + +static const struct drm_plane_helper_funcs arc_pgu_plane_helper_funcs = { + .prepare_fb = NULL, + .cleanup_fb = NULL, + .atomic_update = arc_pgu_plane_atomic_update, +}; + +static void arc_pgu_plane_destroy(struct drm_plane *plane) +{ + drm_plane_helper_disable(plane); + drm_plane_cleanup(plane); +} + +static const struct drm_plane_funcs arc_pgu_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = arc_pgu_plane_destroy, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static struct drm_plane *arc_pgu_plane_init(struct drm_device *drm) +{ + struct arcpgu_drm_private *arcpgu = drm->dev_private; + struct drm_plane *plane = NULL; + u32 formats[ARRAY_SIZE(supported_formats)], i; + int ret; + + plane = devm_kzalloc(drm->dev, sizeof(*plane), GFP_KERNEL); + if (!plane) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < ARRAY_SIZE(supported_formats); i++) + formats[i] = supported_formats[i].fourcc; + + ret = drm_universal_plane_init(drm, plane, 0xff, &arc_pgu_plane_funcs, + formats, ARRAY_SIZE(formats), + DRM_PLANE_TYPE_PRIMARY, NULL); + if (ret) + return ERR_PTR(ret); + + drm_plane_helper_add(plane, &arc_pgu_plane_helper_funcs); + arcpgu->plane = plane; + + return plane; +} + +int arc_pgu_setup_crtc(struct drm_device *drm) +{ + struct arcpgu_drm_private *arcpgu = drm->dev_private; + struct drm_plane *primary; + int ret; + + primary = arc_pgu_plane_init(drm); + if (IS_ERR(primary)) + return PTR_ERR(primary); + + ret = drm_crtc_init_with_planes(drm, &arcpgu->crtc, primary, NULL, + &arc_pgu_crtc_funcs, NULL); + if (ret) { + arc_pgu_plane_destroy(primary); + return ret; + } + + drm_crtc_helper_add(&arcpgu->crtc, &arc_pgu_crtc_helper_funcs); + return 0; +} diff --git a/drivers/gpu/drm/arc/arcpgu_drv.c b/drivers/gpu/drm/arc/arcpgu_drv.c new file mode 100644 index 0000000..5b35e5db --- /dev/null +++ b/drivers/gpu/drm/arc/arcpgu_drv.c @@ -0,0 +1,282 @@ +/* + * ARC PGU DRM driver. + * + * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include + +#include "arcpgu.h" +#include "arcpgu_regs.h" + +static void arcpgu_fb_output_poll_changed(struct drm_device *dev) +{ + struct arcpgu_drm_private *arcpgu = dev->dev_private; + + if (arcpgu->fbdev) + drm_fbdev_cma_hotplug_event(arcpgu->fbdev); +} + +static int arcpgu_atomic_commit(struct drm_device *dev, + struct drm_atomic_state *state, bool async) +{ + return drm_atomic_helper_commit(dev, state, false); +} + +static struct drm_mode_config_funcs arcpgu_drm_modecfg_funcs = { + .fb_create = drm_fb_cma_create, + .output_poll_changed = arcpgu_fb_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = arcpgu_atomic_commit, +}; + +static void arcpgu_setup_mode_config(struct drm_device *drm) +{ + drm_mode_config_init(drm); + drm->mode_config.min_width = 0; + drm->mode_config.min_height = 0; + drm->mode_config.max_width = 1920; + drm->mode_config.max_height = 1080; + drm->mode_config.funcs = &arcpgu_drm_modecfg_funcs; +} + +int arcpgu_gem_mmap(struct file *filp, struct vm_area_struct *vma) +{ + int ret; + + ret = drm_gem_mmap(filp, vma); + if (ret) + return ret; + + vma->vm_page_prot = pgprot_noncached(vm_get_page_prot(vma->vm_flags)); + return 0; +} + +static const struct file_operations arcpgu_drm_ops = { + .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 = arcpgu_gem_mmap, +}; + +static void arcpgu_preclose(struct drm_device *drm, struct drm_file *file) +{ + struct arcpgu_drm_private *arcpgu = drm->dev_private; + struct drm_pending_vblank_event *e, *t; + unsigned long flags; + + spin_lock_irqsave(&drm->event_lock, flags); + list_for_each_entry_safe(e, t, &arcpgu->event_list, base.link) { + if (e->base.file_priv != file) + continue; + list_del(&e->base.link); + e->base.destroy(&e->base); + } + spin_unlock_irqrestore(&drm->event_lock, flags); +} + +static void arcpgu_lastclose(struct drm_device *drm) +{ + struct arcpgu_drm_private *arcpgu = drm->dev_private; + + drm_fbdev_cma_restore_mode(arcpgu->fbdev); +} + +static int arcpgu_load(struct drm_device *drm) +{ + struct platform_device *pdev = to_platform_device(drm->dev); + struct arcpgu_drm_private *arcpgu; + struct device_node *encoder_node; + struct resource *res; + int ret; + + arcpgu = devm_kzalloc(&pdev->dev, sizeof(*arcpgu), GFP_KERNEL); + if (arcpgu == NULL) + return -ENOMEM; + + drm->dev_private = arcpgu; + + arcpgu->clk = devm_clk_get(drm->dev, "pxlclk"); + if (IS_ERR(arcpgu->clk)) + return PTR_ERR(arcpgu->clk); + + INIT_LIST_HEAD(&arcpgu->event_list); + + arcpgu_setup_mode_config(drm); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + arcpgu->regs = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(arcpgu->regs)) { + dev_err(drm->dev, "Could not remap IO mem\n"); + return PTR_ERR(arcpgu->regs); + } + + dev_info(drm->dev, "arc_pgu ID: 0x%x\n", + arc_pgu_read(arcpgu, ARCPGU_REG_ID)); + + if (dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32))) + return -ENODEV; + + if (arc_pgu_setup_crtc(drm) < 0) + return -ENODEV; + + /* find the encoder node and initialize it */ + encoder_node = of_parse_phandle(drm->dev->of_node, "encoder-slave", 0); + if (!encoder_node) { + dev_err(drm->dev, "failed to get an encoder slave node\n"); + return -ENODEV; + } + + ret = arcpgu_drm_hdmi_init(drm, encoder_node); + if (ret < 0) + return ret; + + drm_mode_config_reset(drm); + drm_kms_helper_poll_init(drm); + + arcpgu->fbdev = drm_fbdev_cma_init(drm, 16, + drm->mode_config.num_crtc, + drm->mode_config.num_connector); + if (IS_ERR(arcpgu->fbdev)) { + ret = PTR_ERR(arcpgu->fbdev); + arcpgu->fbdev = NULL; + return -ENODEV; + } + + platform_set_drvdata(pdev, arcpgu); + return 0; +} + +int arcpgu_unload(struct drm_device *drm) +{ + struct arcpgu_drm_private *arcpgu = drm->dev_private; + + if (arcpgu->fbdev) { + drm_fbdev_cma_fini(arcpgu->fbdev); + arcpgu->fbdev = NULL; + } + drm_kms_helper_poll_fini(drm); + drm_vblank_cleanup(drm); + drm_mode_config_cleanup(drm); + + return 0; +} + +static struct drm_driver arcpgu_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_PRIME | + DRIVER_ATOMIC, + .preclose = arcpgu_preclose, + .lastclose = arcpgu_lastclose, + .name = "drm-arcpgu", + .desc = "ARC PGU Controller", + .date = "20160219", + .major = 1, + .minor = 0, + .patchlevel = 0, + .fops = &arcpgu_drm_ops, + .dumb_create = drm_gem_cma_dumb_create, + .dumb_map_offset = drm_gem_cma_dumb_map_offset, + .dumb_destroy = drm_gem_dumb_destroy, + .get_vblank_counter = drm_vblank_no_hw_counter, + .prime_handle_to_fd = drm_gem_prime_handle_to_fd, + .prime_fd_to_handle = drm_gem_prime_fd_to_handle, + .gem_free_object = drm_gem_cma_free_object, + .gem_vm_ops = &drm_gem_cma_vm_ops, + .gem_prime_export = drm_gem_prime_export, + .gem_prime_import = drm_gem_prime_import, + .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, +}; + +static int arcpgu_probe(struct platform_device *pdev) +{ + struct drm_device *drm; + int ret; + + drm = drm_dev_alloc(&arcpgu_drm_driver, &pdev->dev); + if (!drm) + return -ENOMEM; + + ret = arcpgu_load(drm); + if (ret) + goto err_unref; + + ret = drm_dev_register(drm, 0); + if (ret) + goto err_unload; + + ret = drm_connector_register_all(drm); + if (ret) + goto err_unregister; + + return 0; + +err_unregister: + drm_dev_unregister(drm); + +err_unload: + arcpgu_unload(drm); + +err_unref: + drm_dev_unref(drm); + + return ret; +} + +static int arcpgu_remove(struct platform_device *pdev) +{ + struct drm_device *drm = platform_get_drvdata(pdev); + + drm_connector_unregister_all(drm); + drm_dev_unregister(drm); + arcpgu_unload(drm); + drm_dev_unref(drm); + + return 0; +} + +static const struct of_device_id arcpgu_of_table[] = { + {.compatible = "snps,arcpgu"}, + {} +}; + +MODULE_DEVICE_TABLE(of, arcpgu_of_table); + +static struct platform_driver arcpgu_platform_driver = { + .probe = arcpgu_probe, + .remove = arcpgu_remove, + .driver = { + .name = "arcpgu", + .of_match_table = arcpgu_of_table, + }, +}; + +module_platform_driver(arcpgu_platform_driver); + +MODULE_AUTHOR("Carlos Palminha "); +MODULE_DESCRIPTION("ARC PGU DRM driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/arc/arcpgu_hdmi.c b/drivers/gpu/drm/arc/arcpgu_hdmi.c new file mode 100644 index 0000000..08b6bae --- /dev/null +++ b/drivers/gpu/drm/arc/arcpgu_hdmi.c @@ -0,0 +1,201 @@ +/* + * ARC PGU DRM driver. + * + * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include + +#include "arcpgu.h" + +struct arcpgu_drm_connector { + struct drm_connector connector; + struct drm_encoder_slave *encoder_slave; +}; + +static int arcpgu_drm_connector_get_modes(struct drm_connector *connector) +{ + const struct drm_encoder_slave_funcs *sfuncs; + struct drm_encoder_slave *slave; + struct arcpgu_drm_connector *con = + container_of(connector, struct arcpgu_drm_connector, connector); + + slave = con->encoder_slave; + if (slave == NULL) { + dev_err(connector->dev->dev, + "connector_get_modes: cannot find slave encoder for connector\n"); + return 0; + } + + sfuncs = slave->slave_funcs; + if (sfuncs->get_modes == NULL) + return 0; + + return sfuncs->get_modes(&slave->base, connector); +} + +struct drm_encoder * +arcpgu_drm_connector_best_encoder(struct drm_connector *connector) +{ + struct drm_encoder_slave *slave; + struct arcpgu_drm_connector *con = + container_of(connector, struct arcpgu_drm_connector, connector); + + slave = con->encoder_slave; + if (slave == NULL) { + dev_err(connector->dev->dev, + "connector_best_encoder: cannot find slave encoder for connector\n"); + return NULL; + } + + return &slave->base; +} + +static enum drm_connector_status +arcpgu_drm_connector_detect(struct drm_connector *connector, bool force) +{ + enum drm_connector_status status = connector_status_unknown; + const struct drm_encoder_slave_funcs *sfuncs; + struct drm_encoder_slave *slave; + + struct arcpgu_drm_connector *con = + container_of(connector, struct arcpgu_drm_connector, connector); + + slave = con->encoder_slave; + if (slave == NULL) { + dev_err(connector->dev->dev, + "connector_detect: cannot find slave encoder for connector\n"); + return status; + } + + sfuncs = slave->slave_funcs; + if (sfuncs && sfuncs->detect) + return sfuncs->detect(&slave->base, connector); + + dev_err(connector->dev->dev, "connector_detect: could not detect slave funcs\n"); + return status; +} + +static void arcpgu_drm_connector_destroy(struct drm_connector *connector) +{ + drm_connector_unregister(connector); + drm_connector_cleanup(connector); +} + +static const struct drm_connector_helper_funcs +arcpgu_drm_connector_helper_funcs = { + .get_modes = arcpgu_drm_connector_get_modes, + .best_encoder = arcpgu_drm_connector_best_encoder, +}; + +static const struct drm_connector_funcs arcpgu_drm_connector_funcs = { + .dpms = drm_helper_connector_dpms, + .reset = drm_atomic_helper_connector_reset, + .detect = arcpgu_drm_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = arcpgu_drm_connector_destroy, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static struct drm_encoder_helper_funcs arcpgu_drm_encoder_helper_funcs = { + .dpms = drm_i2c_encoder_dpms, + .mode_fixup = drm_i2c_encoder_mode_fixup, + .mode_set = drm_i2c_encoder_mode_set, + .prepare = drm_i2c_encoder_prepare, + .commit = drm_i2c_encoder_commit, + .detect = drm_i2c_encoder_detect, +}; + +static struct drm_encoder_funcs arcpgu_drm_encoder_funcs = { + .destroy = drm_encoder_cleanup, +}; + +int arcpgu_drm_hdmi_init(struct drm_device *drm, struct device_node *np) +{ + struct arcpgu_drm_connector *arcpgu_connector; + struct drm_i2c_encoder_driver *driver; + struct drm_encoder_slave *encoder; + struct drm_connector *connector; + struct i2c_client *i2c_slave; + int ret; + + encoder = devm_kzalloc(drm->dev, sizeof(*encoder), GFP_KERNEL); + if (encoder == NULL) + return -ENOMEM; + + i2c_slave = of_find_i2c_device_by_node(np); + if (!i2c_slave || !i2c_get_clientdata(i2c_slave)) { + dev_err(drm->dev, "failed to find i2c slave encoder\n"); + return -EPROBE_DEFER; + } + + if (i2c_slave->dev.driver == NULL) { + dev_err(drm->dev, "failed to find i2c slave driver\n"); + return -EPROBE_DEFER; + } + + driver = + to_drm_i2c_encoder_driver(to_i2c_driver(i2c_slave->dev.driver)); + ret = driver->encoder_init(i2c_slave, drm, encoder); + if (ret) { + dev_err(drm->dev, "failed to initialize i2c encoder slave\n"); + return ret; + } + + encoder->base.possible_crtcs = 1; + encoder->base.possible_clones = 0; + ret = drm_encoder_init(drm, &encoder->base, &arcpgu_drm_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + if (ret) + return ret; + + drm_encoder_helper_add(&encoder->base, + &arcpgu_drm_encoder_helper_funcs); + + arcpgu_connector = devm_kzalloc(drm->dev, sizeof(*arcpgu_connector), + GFP_KERNEL); + if (!arcpgu_connector) { + ret = -ENOMEM; + goto error_encoder_cleanup; + } + + connector = &arcpgu_connector->connector; + drm_connector_helper_add(connector, &arcpgu_drm_connector_helper_funcs); + ret = drm_connector_init(drm, connector, &arcpgu_drm_connector_funcs, + DRM_MODE_CONNECTOR_HDMIA); + if (ret < 0) { + dev_err(drm->dev, "failed to initialize drm connector\n"); + goto error_encoder_cleanup; + } + + ret = drm_mode_connector_attach_encoder(connector, &encoder->base); + if (ret < 0) { + dev_err(drm->dev, "could not attach connector to encoder\n"); + drm_connector_unregister(connector); + goto error_connector_cleanup; + } + + arcpgu_connector->encoder_slave = encoder; + + return 0; + +error_connector_cleanup: + drm_connector_cleanup(connector); + +error_encoder_cleanup: + drm_encoder_cleanup(&encoder->base); + return ret; +} diff --git a/drivers/gpu/drm/arc/arcpgu_regs.h b/drivers/gpu/drm/arc/arcpgu_regs.h new file mode 100644 index 0000000..95a13a8 --- /dev/null +++ b/drivers/gpu/drm/arc/arcpgu_regs.h @@ -0,0 +1,40 @@ +/* + * ARC PGU DRM driver. + * + * Copyright (C) 2016 Synopsys, Inc. (www.synopsys.com) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#ifndef _ARC_PGU_REGS_H_ +#define _ARC_PGU_REGS_H_ + +#define ARCPGU_REG_CTRL 0x00 +#define ARCPGU_REG_STAT 0x04 +#define ARCPGU_REG_FMT 0x10 +#define ARCPGU_REG_HSYNC 0x14 +#define ARCPGU_REG_VSYNC 0x18 +#define ARCPGU_REG_ACTIVE 0x1c +#define ARCPGU_REG_BUF0_ADDR 0x40 +#define ARCPGU_REG_STRIDE 0x50 +#define ARCPGU_REG_START_SET 0x84 + +#define ARCPGU_REG_ID 0x3FC + +#define ARCPGU_CTRL_ENABLE_MASK 0x02 +#define ARCPGU_CTRL_VS_POL_MASK 0x1 +#define ARCPGU_CTRL_VS_POL_OFST 0x3 +#define ARCPGU_CTRL_HS_POL_MASK 0x1 +#define ARCPGU_CTRL_HS_POL_OFST 0x4 +#define ARCPGU_MODE_RGB888_MASK 0x04 +#define ARCPGU_STAT_BUSY_MASK 0x02 + +#endif