From patchwork Wed May 3 23:14:00 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sui Jingfeng X-Patchwork-Id: 13230657 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id 46CCAC77B75 for ; Wed, 3 May 2023 23:14:20 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S229807AbjECXOT (ORCPT ); Wed, 3 May 2023 19:14:19 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:55634 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229659AbjECXOO (ORCPT ); Wed, 3 May 2023 19:14:14 -0400 Received: from loongson.cn (mail.loongson.cn [114.242.206.163]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 55F417AB8; Wed, 3 May 2023 16:14:06 -0700 (PDT) Received: from loongson.cn (unknown [10.20.42.43]) by gateway (Coremail) with SMTP id _____8CxVPC96lJkBk8EAA--.7144S3; Thu, 04 May 2023 07:14:05 +0800 (CST) Received: from openarena.loongson.cn (unknown [10.20.42.43]) by localhost.localdomain (Coremail) with SMTP id AQAAf8Dx_7O46lJk5dZIAA--.3684S4; Thu, 04 May 2023 07:14:03 +0800 (CST) From: Sui Jingfeng To: Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Daniel Vetter , suijingfeng , Sumit Semwal , Christian Koenig , emil.l.velikov@gmail.com Cc: nathan@kernel.org, linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, linux-media@vger.kernel.org, linaro-mm-sig@lists.linaro.org, Li Yi Subject: [PATCH v11 2/2] drm: add kms driver for loongson display controller Date: Thu, 4 May 2023 07:14:00 +0800 Message-Id: <20230503231400.995613-3-suijingfeng@loongson.cn> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230503231400.995613-1-suijingfeng@loongson.cn> References: <20230503231400.995613-1-suijingfeng@loongson.cn> MIME-Version: 1.0 X-CM-TRANSID: AQAAf8Dx_7O46lJk5dZIAA--.3684S4 X-CM-SenderInfo: xvxlyxpqjiv03j6o00pqjv00gofq/ X-Coremail-Antispam: 1Uk129KBjvtXoWkAr4kur1UAF48tr1UCFyDWrg_yoW8CryrAF X_CFy2vF1DGw1Du34Fqw1UGw1xtF97Cry7XF4DWFWIyFZ7tr4DX345ArW3Xr1xuFyUu3W8 tF98J34kCry0yF1IgjkaLaAFLSUrUUUU5b8apTn2vfkv8UJUUUU8wcxFpf9Il3svdxBIda Vrn0xqx4xG64xvF2IEw4CE5I8CrVC2j2Jv73VFW2AGmfu7bjvjm3AaLaJ3UjIYCTnIWjp_ UUUYX7kC6x804xWl14x267AKxVWUJVW8JwAFc2x0x2IEx4CE42xK8VAvwI8IcIk0rVWrJV Cq3wAFIxvE14AKwVWUXVWUAwA2ocxC64kIII0Yj41l84x0c7CEw4AK67xGY2AK021l84AC jcxK6xIIjxv20xvE14v26ryj6F1UM28EF7xvwVC0I7IYx2IY6xkF7I0E14v26r4j6F4UM2 8EF7xvwVC2z280aVAFwI0_Gr0_Cr1l84ACjcxK6I8E87Iv6xkF7I0E14v26r4j6r4UJwAa w2AFwI0_JF0_Jw1le2I262IYc4CY6c8Ij28IcVAaY2xG8wAqjxCEc2xF0cIa020Ex4CE44 I27wAqx4xG64xvF2IEw4CE5I8CrVC2j2WlYx0E2Ix0cI8IcVAFwI0_Jw0_WrylYx0Ex4A2 jsIE14v26r4j6F4UMcvjeVCFs4IE7xkEbVWUJVW8JwACjcxG0xvY0x0EwIxGrwCY1x0262 kKe7AKxVWUtVW8ZwCF04k20xvY0x0EwIxGrwCFx2IqxVCFs4IE7xkEbVWUJVW8JwCFI7km 07C267AKxVWUAVWUtwC20s026c02F40E14v26r1j6r18MI8I3I0E7480Y4vE14v26r106r 1rMI8E67AF67kF1VAFwI0_Jw0_GFylIxkGc2Ij64vIr41lIxAIcVC0I7IYx2IY67AKxVW5 JVW7JwCI42IY6xIIjxv20xvEc7CjxVAFwI0_Gr0_Cr1lIxAIcVCF04k26cxKx2IYs7xG6r 1j6r1xMIIF0xvEx4A2jsIE14v26r4j6F4UMIIF0xvEx4A2jsIEc7CjxVAFwI0_Gr0_Gr1U YxBIdaVFxhVjvjDU0xZFpf9x07jr9NsUUUUU= Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org Loongson display controller IP has been integrated in both Loongson north bridge chipset(ls7a1000/ls7a2000) and Loongson SoCs(ls2k1000/ls2k2000), it has been even included in Loongson self-made BMC products. This display controller is a PCI device. It has two display pipes and each display pipe support a primary plane and a cursor plane. For the DC in the ls7a1000 and ls2k1000, each display pipe has a DVO output interface which provide RGB888 signals, vertical & horizontal synchronisations and pixel clock. Each CRTC is able to support 1920x1080@60Hz, the maximum resolution of each display pipe is 2048x2048 according to the hardware spec. For the DC in LS7A2000, each display pipe is equipped with a built-in HDMI encoder which is compliant with the HDMI 1.4 specification, thus it support 3840x2160@30Hz. The first display pipe is also equipped with a transparent vga encoder which is parallel with the HDMI encoder. The DC in LS7A2000 is more complete compare with the one in old chips, besides above feature, it has two hardware cursors, two hardware vblank counter and two scanout position recorders unit. It also support tiled framebuffer format which can be scanout the tiled framebuffer rendered by the LoongGPU directly. v1 -> v2: 1) Use hpd status reg when polling for ls7a2000 2) Fix all warnings emerged when compile with W=1 v2 -> v3: 1) Add COMPILE_TEST in Kconfig and make the driver off by default 2) Alphabetical sorting headers (Thomas) 3) Untangle register access functions as much as possible (Thomas) 4) Switch to TTM based memory manager and prefer cached mapping for Loongson SoC (Thomas) 5) Add chip id detection method, now all models are distinguishable. 6) Revise builtin HDMI phy driver, nearly all main stream mode below 4K@30Hz is tested, this driver supported these mode very well including clone display mode and extend display mode. v3 -> v4: 1) Quickly fix a small mistake. v4 -> v5: 1) Drop potential support for Loongson 2K series SoC temporary, this part should be resend with the DT binding patch in the future. 2) Add per display pipe debugfs support to the builtin HDMI encoder. 3) Rewrite atomic_update() for hardware cursors plane(Thomas) 4) Rewrite encoder and connector initialization part, untangle it according to the chip(Thomas). v5 -> v6: 1) Remove stray code which didn't get used, say lsdc_of_get_reserved_ram 2) Fix all typos I could found, make sentences and code more readable 3) Untangle lsdc_hdmi*_connector_detect() function according to the pipe 4) After a serious consideration, we rename this driver as loongson. Because we also have drivers toward the LoongGPU IP in LS7A2000 and LS2K2000. Besides, there are also drivers about the external encoder, HDMI audio driver and vbios support etc. This patch only provide DC driver part, my teammate Li Yi believe that loongson will be more suitable for loongson graphics than lsdc in the long run. loongson.ko = LSDC + LoongGPU + encoders driver + vbios/DT ... v6 -> v7: 1) Add prime support, self-sharing is works. sharing buffer with etnaviv is also tested, and its works with limitation. 2) Implement buffer objects tracking with list_head. 3) S3(sleep to RAM) is tested on ls3a5000+ls7a2000 evb and it works. 4) Rewrite lsdc_bo_move, since ttm core stop allocating resources during BO creation. Patch V1 ~ V6 of this series no longer works on latest kernel. Thus, we send V7 to revival them. v7 -> v8: 1) Zero a compile warnnings on 32-bit platform, compile with W=1 2) Revise lsdc_bo_gpu_offset() and minor cleanup 3) Pageflip tested on the virtual terminal with following commands modetest -M loongson -s 32:1920x1080 -v modetest -M loongson -s 34:1920x1080 -v -F tiles It works like a charm, when running pageflip test with dual screnn configuration, another two additional bo created by the modetest emerged, VRAM usage up to 40+MB, well we have at least 64MB, still enough. # cat bos bo[0000]: size: 8112kB VRAM bo[0001]: size: 16kB VRAM bo[0002]: size: 16kB VRAM bo[0003]: size: 16208kB VRAM bo[0004]: size: 8112kB VRAM bo[0005]: size: 8112kB VRAM v8 -> v9: 1) Select I2C and I2C_ALGOBIT in Kconfig and should depend on MMU. 2) Using pci_get_domain_bus_and_slot to get the GPU device. 3) Other minor improvements. Those patches are tested on ls3a5000 + ls7a1000 CRB, ls3a5000 + ls7a2000 evb, and lemote a1901 board(ls3a4000 + ls7a1000). On loongson mips CPU, the write combine support should be enabled, to get a decent performance for writing framebuffer data to the VRAM. v9 -> v10: 1) Revise lsdc_drm_freeze() to implement S3 completely and correctly. I suddenly realized that pinned buffer can not move and VRAM lost power when sleep to RAM. Thus, the data in the buffer who is pinned in VRAM will get lost when resume. Yet it's not big problem because we are software rendering solution which relay on the CPU update the front framebuffer. We can see the garbage data when resume from S3, but the screen will show correct image as I move the cursor. This is due to the cpu repaint. v10 of this patch make S3 perfect by unpin all of BOs in VRAM, evict them all to system RAM. v10 -> v11 1) On double screen case, the single giant framebuffer is referenced by two GEM object, hence, it will be pinned by prepare_fb() at lease two times. This cause its pin count > 1. V10 of this patch only unpin BOs in VRAM once before suspend, which is not correct on double screen case. V11 of this patch unpin BOs until its pin count reach to zero when suspend. Then, resume with double screen works very well. Teste on both ls7a1000 and ls7a2000 platform. V10 only works on single screen case. 2) Fix vblank wait timeout when disable CRTC and run kms_flip test. 3) Test against, at least fbdev test and kms_flip test of IGT passed, while most of the tests passed. 4) Rewrite pixel PLL update function, magic numbers eliminated (Emil) 5) Drop a few common hardware features description in lsdc_desc (Emil) 6) Drop lsdc_mode_config_mode_valid(), instead add restrictions in dumb create function. (Emil) 7) Untangle the ls7a1000 case and ls7a2000 case completely. (Thomas) Signed-off-by: Li Yi Signed-off-by: Sui Jingfeng --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/loongson/Kconfig | 17 + drivers/gpu/drm/loongson/Makefile | 19 + drivers/gpu/drm/loongson/ls7a1000_outputs.c | 160 +++ drivers/gpu/drm/loongson/ls7a2000_outputs.c | 534 ++++++++++ drivers/gpu/drm/loongson/lsdc_crtc.c | 1064 +++++++++++++++++++ drivers/gpu/drm/loongson/lsdc_debugfs.c | 78 ++ drivers/gpu/drm/loongson/lsdc_device.c | 104 ++ drivers/gpu/drm/loongson/lsdc_drv.c | 485 +++++++++ drivers/gpu/drm/loongson/lsdc_drv.h | 485 +++++++++ drivers/gpu/drm/loongson/lsdc_gem.c | 319 ++++++ drivers/gpu/drm/loongson/lsdc_gem.h | 37 + drivers/gpu/drm/loongson/lsdc_gfxpll.c | 199 ++++ drivers/gpu/drm/loongson/lsdc_gfxpll.h | 52 + drivers/gpu/drm/loongson/lsdc_i2c.c | 179 ++++ drivers/gpu/drm/loongson/lsdc_i2c.h | 29 + drivers/gpu/drm/loongson/lsdc_irq.c | 81 ++ drivers/gpu/drm/loongson/lsdc_irq.h | 16 + drivers/gpu/drm/loongson/lsdc_output.h | 21 + drivers/gpu/drm/loongson/lsdc_pixpll.c | 485 +++++++++ drivers/gpu/drm/loongson/lsdc_pixpll.h | 86 ++ drivers/gpu/drm/loongson/lsdc_plane.c | 639 +++++++++++ drivers/gpu/drm/loongson/lsdc_probe.c | 56 + drivers/gpu/drm/loongson/lsdc_probe.h | 14 + drivers/gpu/drm/loongson/lsdc_regs.h | 400 +++++++ drivers/gpu/drm/loongson/lsdc_ttm.c | 547 ++++++++++ drivers/gpu/drm/loongson/lsdc_ttm.h | 88 ++ 28 files changed, 6197 insertions(+) create mode 100644 drivers/gpu/drm/loongson/Kconfig create mode 100644 drivers/gpu/drm/loongson/Makefile create mode 100644 drivers/gpu/drm/loongson/ls7a1000_outputs.c create mode 100644 drivers/gpu/drm/loongson/ls7a2000_outputs.c create mode 100644 drivers/gpu/drm/loongson/lsdc_crtc.c create mode 100644 drivers/gpu/drm/loongson/lsdc_debugfs.c create mode 100644 drivers/gpu/drm/loongson/lsdc_device.c create mode 100644 drivers/gpu/drm/loongson/lsdc_drv.c create mode 100644 drivers/gpu/drm/loongson/lsdc_drv.h create mode 100644 drivers/gpu/drm/loongson/lsdc_gem.c create mode 100644 drivers/gpu/drm/loongson/lsdc_gem.h create mode 100644 drivers/gpu/drm/loongson/lsdc_gfxpll.c create mode 100644 drivers/gpu/drm/loongson/lsdc_gfxpll.h create mode 100644 drivers/gpu/drm/loongson/lsdc_i2c.c create mode 100644 drivers/gpu/drm/loongson/lsdc_i2c.h create mode 100644 drivers/gpu/drm/loongson/lsdc_irq.c create mode 100644 drivers/gpu/drm/loongson/lsdc_irq.h create mode 100644 drivers/gpu/drm/loongson/lsdc_output.h create mode 100644 drivers/gpu/drm/loongson/lsdc_pixpll.c create mode 100644 drivers/gpu/drm/loongson/lsdc_pixpll.h create mode 100644 drivers/gpu/drm/loongson/lsdc_plane.c create mode 100644 drivers/gpu/drm/loongson/lsdc_probe.c create mode 100644 drivers/gpu/drm/loongson/lsdc_probe.h create mode 100644 drivers/gpu/drm/loongson/lsdc_regs.h create mode 100644 drivers/gpu/drm/loongson/lsdc_ttm.c create mode 100644 drivers/gpu/drm/loongson/lsdc_ttm.h diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index ba3fb04bb691..d1fa87d2acb7 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -331,6 +331,8 @@ source "drivers/gpu/drm/v3d/Kconfig" source "drivers/gpu/drm/vc4/Kconfig" +source "drivers/gpu/drm/loongson/Kconfig" + source "drivers/gpu/drm/etnaviv/Kconfig" source "drivers/gpu/drm/hisilicon/Kconfig" diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index a33257d2bc7f..131531453b8e 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -194,3 +194,4 @@ obj-y += gud/ obj-$(CONFIG_DRM_HYPERV) += hyperv/ obj-y += solomon/ obj-$(CONFIG_DRM_SPRD) += sprd/ +obj-$(CONFIG_DRM_LOONGSON) += loongson/ diff --git a/drivers/gpu/drm/loongson/Kconfig b/drivers/gpu/drm/loongson/Kconfig new file mode 100644 index 000000000000..df6946d505fa --- /dev/null +++ b/drivers/gpu/drm/loongson/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 + +config DRM_LOONGSON + tristate "DRM support for Loongson Graphics" + depends on DRM && PCI && MMU + select DRM_KMS_HELPER + select DRM_TTM + select I2C + select I2C_ALGOBIT + help + This is a DRM driver for Loongson Graphics, it may including + LS7A2000, LS7A1000, LS2K2000 and LS2K1000 etc. Loongson LS7A + series are bridge chipset, while Loongson LS2K series are SoC. + + If "M" is selected, the module will be called loongson. + + If in doubt, say "N". diff --git a/drivers/gpu/drm/loongson/Makefile b/drivers/gpu/drm/loongson/Makefile new file mode 100644 index 000000000000..b9f5f7c2ecfd --- /dev/null +++ b/drivers/gpu/drm/loongson/Makefile @@ -0,0 +1,19 @@ +# SPDX-License-Identifier: GPL-2.0 + +loongson-y := \ + lsdc_crtc.o \ + lsdc_debugfs.o \ + lsdc_device.o \ + lsdc_drv.o \ + lsdc_gem.o \ + lsdc_gfxpll.o \ + lsdc_i2c.o \ + lsdc_irq.o \ + lsdc_plane.o \ + lsdc_pixpll.o \ + lsdc_probe.o \ + lsdc_ttm.o + +loongson-y += ls7a1000_outputs.o ls7a2000_outputs.o + +obj-$(CONFIG_DRM_LOONGSON) += loongson.o diff --git a/drivers/gpu/drm/loongson/ls7a1000_outputs.c b/drivers/gpu/drm/loongson/ls7a1000_outputs.c new file mode 100644 index 000000000000..8bd3259700f6 --- /dev/null +++ b/drivers/gpu/drm/loongson/ls7a1000_outputs.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Loongson Corporation + */ + +#include +#include +#include + +#include "lsdc_drv.h" +#include "lsdc_output.h" + +/* + * Currently, we assume the external encoders connected with the DVO is + * transparent. Loongson DVO interface can directly drive RGB888 panels. + * + * TODO: Add support for non-transparent encoders ... + */ + +static int ls7a1000_generic_connector_get_modes(struct drm_connector *conn) +{ + unsigned int num = 0; + struct edid *edid; + + if (conn->ddc) { + edid = drm_get_edid(conn, conn->ddc); + if (edid) { + drm_connector_update_edid_property(conn, edid); + num = drm_add_edid_modes(conn, edid); + kfree(edid); + } + + return num; + } + + num = drm_add_modes_noedid(conn, 1920, 1200); + + drm_set_preferred_mode(conn, 1024, 768); + + return num; +} + +static struct drm_encoder * +ls7a1000_generic_connector_get_best_encoder(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct lsdc_display_pipe *pipe = connector_to_display_pipe(connector); + + return &pipe->encoder; +} + +static const struct drm_connector_helper_funcs +ls7a1000_generic_connector_helpers = { + .atomic_best_encoder = ls7a1000_generic_connector_get_best_encoder, + .get_modes = ls7a1000_generic_connector_get_modes, +}; + +static enum drm_connector_status +ls7a1000_generic_connector_detect(struct drm_connector *connector, bool force) +{ + struct i2c_adapter *ddc = connector->ddc; + + if (ddc) { + if (drm_probe_ddc(ddc)) + return connector_status_connected; + + return connector_status_disconnected; + } + + return connector_status_unknown; +} + +static const struct drm_connector_funcs ls7a1000_generic_connector_funcs = { + .detect = ls7a1000_generic_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state +}; + +static void ls7a1000_pipe0_encoder_reset(struct drm_encoder *encoder) +{ + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + + /* + * We need this, for S3 resume, screen will not lightup if don't set + * correctly. + */ + lsdc_wreg32(ldev, LSDC_CRTC0_PANEL_CONF_REG, + PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN); +} + +static void ls7a1000_pipe1_encoder_reset(struct drm_encoder *encoder) +{ + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + + /* + * We need this, for S3 resume, screen will not lightup if don't set + * correctly. + */ + lsdc_wreg32(ldev, LSDC_CRTC1_PANEL_CONF_REG, + PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN); +} + +static const struct drm_encoder_funcs ls7a1000_encoder_funcs[2] = { + { + .reset = ls7a1000_pipe0_encoder_reset, + .destroy = drm_encoder_cleanup, + }, + { + .reset = ls7a1000_pipe1_encoder_reset, + .destroy = drm_encoder_cleanup, + }, +}; + +int ls7a1000_output_init(struct drm_device *ddev, + struct lsdc_display_pipe *dispipe, + struct i2c_adapter *ddc, + unsigned int pipe) +{ + struct drm_encoder *encoder = &dispipe->encoder; + struct drm_connector *connector = &dispipe->connector; + int ret; + + ret = drm_encoder_init(ddev, + encoder, + &ls7a1000_encoder_funcs[pipe], + DRM_MODE_ENCODER_TMDS, + "encoder-%u", + dispipe->index); + if (ret) + return ret; + + encoder->possible_crtcs = BIT(pipe); + + ret = drm_connector_init_with_ddc(ddev, + connector, + &ls7a1000_generic_connector_funcs, + DRM_MODE_CONNECTOR_DPI, + ddc); + if (ret) + return ret; + + drm_info(ddev, "display pipe-%u has a DVO\n", pipe); + + drm_connector_helper_add(connector, &ls7a1000_generic_connector_helpers); + + drm_connector_attach_encoder(connector, encoder); + + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + return 0; +} diff --git a/drivers/gpu/drm/loongson/ls7a2000_outputs.c b/drivers/gpu/drm/loongson/ls7a2000_outputs.c new file mode 100644 index 000000000000..5ee37da3b88e --- /dev/null +++ b/drivers/gpu/drm/loongson/ls7a2000_outputs.c @@ -0,0 +1,534 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Loongson Corporation + */ + +#include + +#include +#include +#include +#include +#include + +#include "lsdc_drv.h" +#include "lsdc_output.h" + +static int ls7a2000_connector_get_modes(struct drm_connector *connector) +{ + unsigned int num = 0; + struct edid *edid; + + if (connector->ddc) { + edid = drm_get_edid(connector, connector->ddc); + if (edid) { + drm_connector_update_edid_property(connector, edid); + num = drm_add_edid_modes(connector, edid); + kfree(edid); + } + + return num; + } + + num = drm_add_modes_noedid(connector, 1920, 1200); + + drm_set_preferred_mode(connector, 1024, 768); + + return num; +} + +static struct drm_encoder * +ls7a2000_connector_get_best_encoder(struct drm_connector *connector, + struct drm_atomic_state *state) +{ + struct lsdc_display_pipe *dispipe; + + dispipe = connector_to_display_pipe(connector); + + return &dispipe->encoder; +} + +static const struct drm_connector_helper_funcs ls7a2000_connector_helpers = { + .atomic_best_encoder = ls7a2000_connector_get_best_encoder, + .get_modes = ls7a2000_connector_get_modes, +}; + +/* debugfs */ + +#define LSDC_HDMI_REG(i, reg) { \ + .name = __stringify_1(LSDC_HDMI##i##_##reg##_REG), \ + .offset = LSDC_HDMI##i##_##reg##_REG, \ +} + +static const struct lsdc_reg32 ls7a2000_hdmi0_encoder_regs[] = { + LSDC_HDMI_REG(0, ZONE), + LSDC_HDMI_REG(0, INTF_CTRL), + LSDC_HDMI_REG(0, PHY_CTRL), + LSDC_HDMI_REG(0, PHY_PLL), + LSDC_HDMI_REG(0, AVI_INFO_CRTL), + LSDC_HDMI_REG(0, PHY_CAL), + LSDC_HDMI_REG(0, AUDIO_PLL_LO), + LSDC_HDMI_REG(0, AUDIO_PLL_HI), + {NULL, 0}, /* MUST be {NULL, 0} terminated */ +}; + +static const struct lsdc_reg32 ls7a2000_hdmi1_encoder_regs[] = { + LSDC_HDMI_REG(1, ZONE), + LSDC_HDMI_REG(1, INTF_CTRL), + LSDC_HDMI_REG(1, PHY_CTRL), + LSDC_HDMI_REG(1, PHY_PLL), + LSDC_HDMI_REG(1, AVI_INFO_CRTL), + LSDC_HDMI_REG(1, PHY_CAL), + LSDC_HDMI_REG(1, AUDIO_PLL_LO), + LSDC_HDMI_REG(1, AUDIO_PLL_HI), + {NULL, 0}, /* MUST be {NULL, 0} terminated */ +}; + +static int ls7a2000_hdmi_encoder_regs_show(struct seq_file *m, void *data) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *ddev = node->minor->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + const struct lsdc_reg32 *preg; + + preg = (const struct lsdc_reg32 *)node->info_ent->data; + + while (preg->name) { + u32 offset = preg->offset; + + seq_printf(m, "%s (0x%04x): 0x%08x\n", + preg->name, offset, lsdc_rreg32(ldev, offset)); + ++preg; + } + + return 0; +} + +static const struct drm_info_list ls7a2000_hdmi0_debugfs_files[] = { + { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi0_encoder_regs }, +}; + +static const struct drm_info_list ls7a2000_hdmi1_debugfs_files[] = { + { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi1_encoder_regs }, +}; + +static void ls7a2000_hdmi0_late_register(struct drm_connector *connector, + struct dentry *root) +{ + struct drm_device *ddev = connector->dev; + struct drm_minor *minor = ddev->primary; + + drm_debugfs_create_files(ls7a2000_hdmi0_debugfs_files, + ARRAY_SIZE(ls7a2000_hdmi0_debugfs_files), + root, + minor); +} + +static void ls7a2000_hdmi1_late_register(struct drm_connector *connector, + struct dentry *root) +{ + struct drm_device *ddev = connector->dev; + struct drm_minor *minor = ddev->primary; + + drm_debugfs_create_files(ls7a2000_hdmi1_debugfs_files, + ARRAY_SIZE(ls7a2000_hdmi1_debugfs_files), + root, + minor); +} + +/* monitor present detection */ + +static enum drm_connector_status +ls7a2000_hdmi0_vga_connector_detect(struct drm_connector *connector, bool force) +{ + struct drm_device *ddev = connector->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 val; + + val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); + + if (val & HDMI0_HPD_FLAG) + return connector_status_connected; + + if (connector->ddc) { + if (drm_probe_ddc(connector->ddc)) + return connector_status_connected; + + return connector_status_disconnected; + } + + return connector_status_unknown; +} + +static enum drm_connector_status +ls7a2000_hdmi1_connector_detect(struct drm_connector *connector, bool force) +{ + struct lsdc_device *ldev = to_lsdc(connector->dev); + u32 val; + + val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); + + if (val & HDMI1_HPD_FLAG) + return connector_status_connected; + + return connector_status_disconnected; +} + +static const struct drm_connector_funcs ls7a2000_hdmi_connector_funcs[2] = { + { + .detect = ls7a2000_hdmi0_vga_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .debugfs_init = ls7a2000_hdmi0_late_register, + }, + { + .detect = ls7a2000_hdmi1_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = drm_connector_cleanup, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, + .debugfs_init = ls7a2000_hdmi1_late_register, + }, +}; + +/* Even though some board has only one hdmi on display pipe 1, + * We still need hook lsdc_encoder_funcs up on display pipe 0, + * This is because we need its reset() callback get called, to + * set the LSDC_HDMIx_CTRL_REG using software gpio emulated i2c. + * Otherwise, the firmware may set LSDC_HDMIx_CTRL_REG blindly. + */ +static void ls7a2000_hdmi0_encoder_reset(struct drm_encoder *encoder) +{ + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 val; + + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN; + lsdc_wreg32(ldev, LSDC_CRTC0_PANEL_CONF_REG, val); + + /* using software gpio emulated i2c */ + val = lsdc_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG); + val &= ~HW_I2C_EN; + val |= HDMI_INTERFACE_EN | HDMI_PACKET_EN; + lsdc_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, val); + + /* get out of reset state */ + val |= HDMI_PHY_RESET_N; + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); + + mdelay(20); + + drm_dbg(ddev, "HDMI-0 Reset\n"); +} + +static void ls7a2000_hdmi1_encoder_reset(struct drm_encoder *encoder) +{ + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 val; + + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN; + lsdc_wreg32(ldev, LSDC_CRTC1_PANEL_CONF_REG, val); + + /* using software gpio emulated i2c */ + val = lsdc_rreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG); + val &= ~HW_I2C_EN; + val |= HDMI_INTERFACE_EN | HDMI_PACKET_EN; + lsdc_wreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG, val); + + /* reset */ + + /* get out of reset state */ + val = HDMI_PHY_RESET_N; + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); + + mdelay(20); + + drm_dbg(ddev, "HDMI-1 Reset\n"); +} + +static const struct drm_encoder_funcs ls7a2000_encoder_funcs[2] = { + { + .reset = ls7a2000_hdmi0_encoder_reset, + .destroy = drm_encoder_cleanup, + }, + { + .reset = ls7a2000_hdmi1_encoder_reset, + .destroy = drm_encoder_cleanup, + } +}; + +static int ls7a2000_hdmi_set_avi_infoframe(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + struct lsdc_display_pipe *dispipe = encoder_to_display_pipe(encoder); + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + unsigned int index = dispipe->index; + struct hdmi_avi_infoframe infoframe; + u8 buffer[HDMI_INFOFRAME_SIZE(AVI)]; + unsigned char *ptr = &buffer[HDMI_INFOFRAME_HEADER_SIZE]; + unsigned int content0, content1, content2, content3; + int err; + + err = drm_hdmi_avi_infoframe_from_display_mode(&infoframe, + &dispipe->connector, + mode); + if (err < 0) { + drm_err(ddev, "failed to setup AVI infoframe: %d\n", err); + return err; + } + + /* Fixed infoframe configuration not linked to the mode */ + infoframe.colorspace = HDMI_COLORSPACE_RGB; + infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; + infoframe.colorimetry = HDMI_COLORIMETRY_NONE; + + err = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer)); + if (err < 0) { + drm_err(ddev, "failed to pack AVI infoframe: %d\n", err); + return err; + } + + content0 = *(unsigned int *)ptr; + content1 = *(ptr + 4); + content2 = *(unsigned int *)(ptr + 5); + content3 = *(unsigned int *)(ptr + 9); + + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT0, index, content0); + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT1, index, content1); + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT2, index, content2); + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT3, index, content3); + + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_AVI_INFO_CRTL_REG, index, + AVI_PKT_ENABLE | AVI_PKT_UPDATE); + + drm_dbg(ddev, "Update HDMI-%u avi infoframe\n", index); + + return 0; +} + +static void ls7a2000_hdmi_atomic_disable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct lsdc_display_pipe *dispipe = encoder_to_display_pipe(encoder); + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + unsigned int index = dispipe->index; + u32 val; + + /* Disable the hdmi phy */ + val = lsdc_hdmi_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index); + val &= ~HDMI_PHY_EN; + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val); + + /* Disable the hdmi interface */ + val = lsdc_hdmi_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index); + val &= ~HDMI_INTERFACE_EN; + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index, val); + + drm_dbg(ddev, "HDMI-%u disabled\n", index); +} + +static void ls7a2000_hdmi_atomic_enable(struct drm_encoder *encoder, + struct drm_atomic_state *state) +{ + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + struct lsdc_display_pipe *dispipe = encoder_to_display_pipe(encoder); + unsigned int index = dispipe->index; + u32 val; + + /* datasheet say it should larger than 48 */ + val = 64 << HDMI_H_ZONE_IDLE_SHIFT | 64 << HDMI_V_ZONE_IDLE_SHIFT; + + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_ZONE_REG, index, val); + + val = HDMI_PHY_TERM_STATUS | + HDMI_PHY_TERM_DET_EN | + HDMI_PHY_TERM_H_EN | + HDMI_PHY_TERM_L_EN | + HDMI_PHY_RESET_N | + HDMI_PHY_EN; + + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val); + + udelay(2); + + val = HDMI_CTL_PERIOD_MODE | + HDMI_AUDIO_EN | + HDMI_PACKET_EN | + HDMI_INTERFACE_EN | + (8 << HDMI_VIDEO_PREAMBLE_SHIFT); + + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index, val); + + drm_dbg(ddev, "HDMI-%u enabled\n", index); +} + +/* + * Fout = M * Fin + * + * M = (4 * LF) / (IDF * ODF) + * + * IDF: Input Division Factor + * ODF: Output Division Factor + * LF: Loop Factor + * M: Required Mult + * + * +--------------------------------------------------------+ + * | Fin (kHZ) | M | IDF | LF | ODF | Fout(Mhz) | + * |-------------------+----+-----+----+-----+--------------| + * | 170000 ~ 340000 | 10 | 16 | 40 | 1 | 1700 ~ 3400 | + * | 85000 ~ 170000 | 10 | 8 | 40 | 2 | 850 ~ 1700 | + * | 42500 ~ 85000 | 10 | 4 | 40 | 4 | 425 ~ 850 | + * | 21250 ~ 42500 | 10 | 2 | 40 | 8 | 212.5 ~ 425 | + * | 20000 ~ 21250 | 10 | 1 | 40 | 16 | 200 ~ 212.5 | + * +--------------------------------------------------------+ + */ +static void ls7a2000_hdmi_phy_pll_config(struct lsdc_device *ldev, + int fin, + unsigned int index) +{ + struct drm_device *ddev = &ldev->base; + int count = 0; + u32 val; + + /* Firstly, disable phy pll */ + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, 0x0); + + /* + * Most of time, loongson HDMI require M = 10 + * for example, 10 = (4 * 40) / (8 * 2) + * here, write "1" to the ODF will get "2" + */ + + if (fin >= 170000) + val = (16 << HDMI_PLL_IDF_SHIFT) | + (40 << HDMI_PLL_LF_SHIFT) | + (0 << HDMI_PLL_ODF_SHIFT); + else if (fin >= 85000) + val = (8 << HDMI_PLL_IDF_SHIFT) | + (40 << HDMI_PLL_LF_SHIFT) | + (1 << HDMI_PLL_ODF_SHIFT); + else if (fin >= 42500) + val = (4 << HDMI_PLL_IDF_SHIFT) | + (40 << HDMI_PLL_LF_SHIFT) | + (2 << HDMI_PLL_ODF_SHIFT); + else if (fin >= 21250) + val = (2 << HDMI_PLL_IDF_SHIFT) | + (40 << HDMI_PLL_LF_SHIFT) | + (3 << HDMI_PLL_ODF_SHIFT); + else + val = (1 << HDMI_PLL_IDF_SHIFT) | + (40 << HDMI_PLL_LF_SHIFT) | + (4 << HDMI_PLL_ODF_SHIFT); + + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, val); + + val |= HDMI_PLL_ENABLE; + + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, val); + + udelay(2); + + drm_dbg(ddev, "Fin of HDMI-%u: %d kHz\n", index, fin); + + /* Wait hdmi phy pll lock */ + do { + val = lsdc_hdmi_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index); + + if (val & HDMI_PLL_LOCKED) { + drm_dbg(ddev, "Setting HDMI-%u PLL take %d cycles\n", + index, count); + break; + } + ++count; + } while (count < 1000); + + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_CAL_REG, index, 0x0f000ff0); + + if (count >= 1000) + drm_err(ddev, "Setting HDMI-%u PLL failed\n", index); +} + +static void ls7a2000_hdmi_atomic_mode_set(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct lsdc_display_pipe *dispipe = encoder_to_display_pipe(encoder); + struct drm_device *ddev = encoder->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + struct drm_display_mode *mode = &crtc_state->mode; + + ls7a2000_hdmi_phy_pll_config(ldev, mode->clock, dispipe->index); + + ls7a2000_hdmi_set_avi_infoframe(encoder, mode); + + drm_dbg(ddev, "HDMI-%u modeset finished\n", dispipe->index); +} + +static const struct drm_encoder_helper_funcs ls7a2000_encoder_helper_funcs = { + .atomic_disable = ls7a2000_hdmi_atomic_disable, + .atomic_enable = ls7a2000_hdmi_atomic_enable, + .atomic_mode_set = ls7a2000_hdmi_atomic_mode_set, +}; + +/* + * For LS7A2000: + * + * 1) Most of board export one vga + hdmi output interface. + * 2) Yet, Some boards export double hdmi output interface. + * 3) Still have boards export three output(2 hdmi + 1 vga). + * + * So let's hook hdmi helper funcs to all display pipe, don't miss. + * writing hdmi register do no harms. + */ +int ls7a2000_output_init(struct drm_device *ddev, + struct lsdc_display_pipe *dispipe, + struct i2c_adapter *ddc, + unsigned int pipe) +{ + struct drm_encoder *encoder = &dispipe->encoder; + struct drm_connector *connector = &dispipe->connector; + int ret; + + ret = drm_encoder_init(ddev, + encoder, + &ls7a2000_encoder_funcs[pipe], + DRM_MODE_ENCODER_TMDS, + "encoder-%u", + pipe); + if (ret) + return ret; + + encoder->possible_crtcs = BIT(pipe); + + drm_encoder_helper_add(encoder, &ls7a2000_encoder_helper_funcs); + + ret = drm_connector_init_with_ddc(ddev, + connector, + &ls7a2000_hdmi_connector_funcs[pipe], + DRM_MODE_CONNECTOR_HDMIA, + ddc); + if (ret) + return ret; + + drm_info(ddev, "display pipe-%u has HDMI %s\n", pipe, pipe ? "" : "and/or VGA"); + + drm_connector_helper_add(connector, &ls7a2000_connector_helpers); + + drm_connector_attach_encoder(connector, encoder); + + connector->polled = DRM_CONNECTOR_POLL_CONNECT | + DRM_CONNECTOR_POLL_DISCONNECT; + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + return 0; +} diff --git a/drivers/gpu/drm/loongson/lsdc_crtc.c b/drivers/gpu/drm/loongson/lsdc_crtc.c new file mode 100644 index 000000000000..9c23fa569dd5 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_crtc.c @@ -0,0 +1,1064 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2023 Loongson Corporation + */ + +#include + +#include +#include +#include +#include + +#include "lsdc_drv.h" + +/* + * The soft reset cause the vblank counter reset to zero, but the address + * and other settings in the crtc register remains. + */ + +static void lsdc_crtc0_soft_reset(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + + val &= CFG_VALID_BITS_MASK; + + /* soft reset bit, active low */ + val &= ~CFG_RESET_N; + + val &= ~CFG_PIX_FMT_MASK; + + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); + + udelay(5); + + val |= CFG_RESET_N | LSDC_PF_XRGB8888 | CFG_OUTPUT_ENABLE; + + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); + + mdelay(20); +} + +static void lsdc_crtc1_soft_reset(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + + val &= CFG_VALID_BITS_MASK; + + /* soft reset bit, active low */ + val &= ~CFG_RESET_N; + + val &= ~CFG_PIX_FMT_MASK; + + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); + + udelay(5); + + val |= CFG_RESET_N | LSDC_PF_XRGB8888 | CFG_OUTPUT_ENABLE; + + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); + + msleep(20); +} + +static void lsdc_crtc0_enable(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + + /* + * If bit 24 of LSDC_CRTC0_CFG_REG is set, it say that the DC hardware + * stop working anymore, anchored. This maybe caused by improper + * operation sequence, may happens on extreme rarely case. + * + * Luckily, a soft reset helps bring it to normal on such a case. + * So we add a warn here, hope to catch something if it happens. + */ + + if (val & BIT(24)) { + drm_warn(&ldev->base, "%s anchored\n", lcrtc->base.name); + return lsdc_crtc0_soft_reset(lcrtc); + } + + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val | CFG_OUTPUT_ENABLE); +} + +static void lsdc_crtc0_disable(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_clr(ldev, LSDC_CRTC0_CFG_REG, CFG_OUTPUT_ENABLE); + + udelay(9); +} + +static void lsdc_crtc1_enable(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + if (val & BIT(24)) { + drm_warn(&ldev->base, "%s anchored\n", lcrtc->base.name); + return lsdc_crtc1_soft_reset(lcrtc); + } + + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val | CFG_OUTPUT_ENABLE); +} + +static void lsdc_crtc1_disable(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_clr(ldev, LSDC_CRTC1_CFG_REG, CFG_OUTPUT_ENABLE); + + udelay(9); +} + +/* All loongson display controller support scanout position hardware */ + +static void lsdc_crtc0_scan_pos(struct lsdc_crtc *lcrtc, int *hpos, int *vpos) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); + + *hpos = val >> 16; + *vpos = val & 0xffff; +} + +static void lsdc_crtc1_scan_pos(struct lsdc_crtc *lcrtc, int *hpos, int *vpos) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); + + *hpos = val >> 16; + *vpos = val & 0xffff; +} + +static void lsdc_crtc0_enable_vblank(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_set(ldev, LSDC_INT_REG, INT_CRTC0_VSYNC_EN); +} + +static void lsdc_crtc0_disable_vblank(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_clr(ldev, LSDC_INT_REG, INT_CRTC0_VSYNC_EN); +} + +static void lsdc_crtc1_enable_vblank(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_set(ldev, LSDC_INT_REG, INT_CRTC1_VSYNC_EN); +} + +static void lsdc_crtc1_disable_vblank(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_clr(ldev, LSDC_INT_REG, INT_CRTC1_VSYNC_EN); +} + +static void lsdc_crtc0_flip(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_set(ldev, LSDC_CRTC0_CFG_REG, CFG_PAGE_FLIP); +} + +static void lsdc_crtc1_flip(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_set(ldev, LSDC_CRTC1_CFG_REG, CFG_PAGE_FLIP); +} + +/* + * CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware logic. + * Hardware engineer say this would help to saving bandwidth on clone mode. + * + * This may useful on custom clone application. + */ + +static void lsdc_crtc0_clone(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_set(ldev, LSDC_CRTC0_CFG_REG, CFG_HW_CLONE); +} + +static void lsdc_crtc1_clone(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_ureg32_set(ldev, LSDC_CRTC1_CFG_REG, CFG_HW_CLONE); +} + +static void lsdc_crtc0_set_mode(struct lsdc_crtc *lcrtc, + const struct drm_display_mode *mode) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG, + (mode->crtc_htotal << 16) | mode->crtc_hdisplay); + + lsdc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG, + (mode->crtc_vtotal << 16) | mode->crtc_vdisplay); + + lsdc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG, + (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | HSYNC_EN); + + lsdc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG, + (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | VSYNC_EN); +} + +static void lsdc_crtc1_set_mode(struct lsdc_crtc *lcrtc, + const struct drm_display_mode *mode) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + lsdc_wreg32(ldev, LSDC_CRTC1_HDISPLAY_REG, + (mode->crtc_htotal << 16) | mode->crtc_hdisplay); + + lsdc_wreg32(ldev, LSDC_CRTC1_VDISPLAY_REG, + (mode->crtc_vtotal << 16) | mode->crtc_vdisplay); + + lsdc_wreg32(ldev, LSDC_CRTC1_HSYNC_REG, + (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | HSYNC_EN); + + lsdc_wreg32(ldev, LSDC_CRTC1_VSYNC_REG, + (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | VSYNC_EN); +} + +/* + * This is required for S3 support. + * + * After resume from suspend, LSDC_CRTCx_CFG_REG (x=0 or 1)is filled with + * garbarge value which may cause the CRTC completely hang. This function + * give a minimal setting to the affected registers. This also override + * the firmware's setting on startup, eliminate potential blinding setting. + * + * Making the CRTC works on our own now, this is similar with the functional + * of GPU POST(Power On Self Test). Only touch CRTC hardware related part. + */ + +static void lsdc_crtc0_reset(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + struct drm_crtc *crtc = &lcrtc->base; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + + /* This help to see what is it */ + drm_dbg(&ldev->base, "value of %s configure register: %x\n", + crtc->name, val); + + /* + * Help the CRTC get out of soft reset sate, as the CRTC is completely + * halt on soft reset mode (BIT(20) = 0). It does not event generate + * vblank, cause vblank wait timeout. This happends when resume from + * S3. + * + * Also give a sane format, after resume from suspend S3, this + * register is filled with garbarge value. A meaningless value may + * also cause the CRTC halt or crash. + */ + + val = CFG_RESET_N | LSDC_PF_XRGB8888; + + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); +} + +static void lsdc_crtc1_reset(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + struct drm_crtc *crtc = &lcrtc->base; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + + drm_dbg(&ldev->base, "value of %s configue register: %x\n", + crtc->name, val); + + /* + * Help the CRTC get out of soft reset sate, give a sane format, + * Otherwise it will complete halt there. + */ + val = CFG_RESET_N | LSDC_PF_XRGB8888; + + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); +} + +static const struct lsdc_crtc_hw_ops ls7a1000_crtc_hw_ops[2] = { + { + .enable = lsdc_crtc0_enable, + .disable = lsdc_crtc0_disable, + .enable_vblank = lsdc_crtc0_enable_vblank, + .disable_vblank = lsdc_crtc0_disable_vblank, + .flip = lsdc_crtc0_flip, + .clone = lsdc_crtc0_clone, + .set_mode = lsdc_crtc0_set_mode, + .get_scan_pos = lsdc_crtc0_scan_pos, + .soft_reset = lsdc_crtc0_soft_reset, + .reset = lsdc_crtc0_reset, + }, + { + .enable = lsdc_crtc1_enable, + .disable = lsdc_crtc1_disable, + .enable_vblank = lsdc_crtc1_enable_vblank, + .disable_vblank = lsdc_crtc1_disable_vblank, + .flip = lsdc_crtc1_flip, + .clone = lsdc_crtc1_clone, + .set_mode = lsdc_crtc1_set_mode, + .get_scan_pos = lsdc_crtc1_scan_pos, + .soft_reset = lsdc_crtc1_soft_reset, + .reset = lsdc_crtc1_reset, + }, +}; + +/* + * The 32-bit hardware vblank counter is available since ls7a2000/ls2k2000, + * The counter grow up even the CRTC is being disabled, it will got reset + * if the crtc is being soft reset. + * + * Those registers are also readable for ls7a1000, but its value does not + * change. + */ + +static u32 lsdc_crtc0_get_vblank_count(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + return lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG); +} + +static u32 lsdc_crtc1_get_vblank_count(struct lsdc_crtc *lcrtc) +{ + struct lsdc_device *ldev = lcrtc->ldev; + + return lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG); +} + +/* + * The DMA step register is available since ls7a2000/ls2k2000, for support + * odd resolutions. But a large DMA step may save bandwidth. Behavior of + * writing thoes bits field on ls7a1000/ls2k1000 is underfined. + */ + +static void lsdc_crtc0_set_dma_step(struct lsdc_crtc *lcrtc, + enum lsdc_dma_steps dma_step) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + + val &= ~CFG_DMA_STEP_MASK; + val |= dma_step << CFG_DMA_STEP_SHIFT; + + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); +} + +static void lsdc_crtc1_set_dma_step(struct lsdc_crtc *lcrtc, + enum lsdc_dma_steps dma_step) +{ + struct lsdc_device *ldev = lcrtc->ldev; + u32 val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + + val &= ~CFG_DMA_STEP_MASK; + val |= dma_step << CFG_DMA_STEP_SHIFT; + + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); +} + +static const struct lsdc_crtc_hw_ops ls7a2000_crtc_hw_ops[2] = { + { + .enable = lsdc_crtc0_enable, + .disable = lsdc_crtc0_disable, + .enable_vblank = lsdc_crtc0_enable_vblank, + .disable_vblank = lsdc_crtc0_disable_vblank, + .flip = lsdc_crtc0_flip, + .clone = lsdc_crtc0_clone, + .set_mode = lsdc_crtc0_set_mode, + .soft_reset = lsdc_crtc0_soft_reset, + .get_scan_pos = lsdc_crtc0_scan_pos, + .set_dma_step = lsdc_crtc0_set_dma_step, + .get_vblank_counter = lsdc_crtc0_get_vblank_count, + .reset = lsdc_crtc0_reset, + }, + { + .enable = lsdc_crtc1_enable, + .disable = lsdc_crtc1_disable, + .enable_vblank = lsdc_crtc1_enable_vblank, + .disable_vblank = lsdc_crtc1_disable_vblank, + .flip = lsdc_crtc1_flip, + .clone = lsdc_crtc1_clone, + .set_mode = lsdc_crtc1_set_mode, + .get_scan_pos = lsdc_crtc1_scan_pos, + .soft_reset = lsdc_crtc1_soft_reset, + .set_dma_step = lsdc_crtc1_set_dma_step, + .get_vblank_counter = lsdc_crtc1_get_vblank_count, + .reset = lsdc_crtc1_reset, + }, +}; + +static void lsdc_crtc_reset(struct drm_crtc *crtc) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + struct lsdc_crtc_state *priv_crtc_state; + + if (crtc->state) + crtc->funcs->atomic_destroy_state(crtc, crtc->state); + + priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL); + + if (!priv_crtc_state) + __drm_atomic_helper_crtc_reset(crtc, NULL); + else + __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); + + /* + * Reset the crtc hardware, this is need for S3 support, + * otherwise, wait for vblank timeout + */ + ops->reset(lcrtc); +} + +static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); + + __drm_atomic_helper_crtc_destroy_state(&priv_state->base); + + kfree(priv_state); +} + +static struct drm_crtc_state * +lsdc_crtc_atomic_duplicate_state(struct drm_crtc *crtc) +{ + struct lsdc_crtc_state *new_priv_state; + struct lsdc_crtc_state *old_priv_state; + + new_priv_state = kzalloc(sizeof(*new_priv_state), GFP_KERNEL); + if (!new_priv_state) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &new_priv_state->base); + + old_priv_state = to_lsdc_crtc_state(crtc->state); + + memcpy(&new_priv_state->pparms, &old_priv_state->pparms, + sizeof(new_priv_state->pparms)); + + return &new_priv_state->base; +} + +static u32 lsdc_crtc_get_vblank_counter(struct drm_crtc *crtc) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + + return ops->get_vblank_counter(lcrtc); +} + +static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + + ops->enable_vblank(lcrtc); + + return 0; +} + +static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + + ops->disable_vblank(lcrtc); +} + +/* CRTC related debugfs + * + * Primary planes and cursor planes are also belong to the CRTC for our case, + * so also append other registers to here, for sake of convient. + */ + +#define REG_DEF(reg) { .name = __stringify_1(LSDC_##reg##_REG), .offset = LSDC_##reg##_REG } + +static const struct lsdc_reg32 lsdc_crtc_regs_array[2][21] = { + [0] = { + REG_DEF(CRTC0_CFG), + REG_DEF(CRTC0_FB_ORIGIN), + REG_DEF(CRTC0_PANEL_CONF), + REG_DEF(CRTC0_HDISPLAY), + REG_DEF(CRTC0_HSYNC), + REG_DEF(CRTC0_VDISPLAY), + REG_DEF(CRTC0_VSYNC), + REG_DEF(CRTC0_GAMMA_INDEX), + REG_DEF(CRTC0_GAMMA_DATA), + REG_DEF(CRTC0_SYNC_DEVIATION), + REG_DEF(CRTC0_VSYNC_COUNTER), + REG_DEF(CRTC0_SCAN_POS), + REG_DEF(CRTC0_STRIDE), + REG_DEF(CRTC0_FB1_ADDR_HI), + REG_DEF(CRTC0_FB1_ADDR_LO), + REG_DEF(CRTC0_FB0_ADDR_HI), + REG_DEF(CRTC0_FB0_ADDR_LO), + REG_DEF(CURSOR0_CFG), + REG_DEF(CURSOR0_POSITION), + REG_DEF(CURSOR0_BG_COLOR), + REG_DEF(CURSOR0_FG_COLOR), + }, + [1] = { + REG_DEF(CRTC1_CFG), + REG_DEF(CRTC1_FB_ORIGIN), + REG_DEF(CRTC1_PANEL_CONF), + REG_DEF(CRTC1_HDISPLAY), + REG_DEF(CRTC1_HSYNC), + REG_DEF(CRTC1_VDISPLAY), + REG_DEF(CRTC1_VSYNC), + REG_DEF(CRTC1_GAMMA_INDEX), + REG_DEF(CRTC1_GAMMA_DATA), + REG_DEF(CRTC1_SYNC_DEVIATION), + REG_DEF(CRTC1_VSYNC_COUNTER), + REG_DEF(CRTC1_SCAN_POS), + REG_DEF(CRTC1_STRIDE), + REG_DEF(CRTC1_FB1_ADDR_HI), + REG_DEF(CRTC1_FB1_ADDR_LO), + REG_DEF(CRTC1_FB0_ADDR_HI), + REG_DEF(CRTC1_FB0_ADDR_LO), + REG_DEF(CURSOR1_CFG), + REG_DEF(CURSOR1_POSITION), + REG_DEF(CURSOR1_BG_COLOR), + REG_DEF(CURSOR1_FG_COLOR), + }, +}; + +static int lsdc_crtc_show_regs(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data; + struct lsdc_device *ldev = lcrtc->ldev; + unsigned int i; + + for (i = 0; i < lcrtc->nreg; i++) { + const struct lsdc_reg32 *preg = &lcrtc->preg[i]; + u32 offset = preg->offset; + + seq_printf(m, "%s (0x%04x): 0x%08x\n", + preg->name, offset, lsdc_rreg32(ldev, offset)); + } + + return 0; +} + +static int lsdc_crtc_show_scan_position(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data; + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + int x, y; + + ops->get_scan_pos(lcrtc, &x, &y); + + seq_printf(m, "scanout position: x: %08u, y: %08u\n", x, y); + + return 0; +} + +static int lsdc_crtc_show_vblank_counter(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data; + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + + seq_printf(m, "CRTC-0 vblank counter: %08u\n\n", + ops->get_vblank_counter(lcrtc)); + + return 0; +} + +static int lsdc_pixpll_show_clock(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data; + struct lsdc_pixpll *pixpll = &lcrtc->pixpll; + const struct lsdc_pixpll_funcs *funcs = pixpll->funcs; + struct drm_crtc *crtc = &lcrtc->base; + struct drm_display_mode *mode = &crtc->state->mode; + struct drm_printer printer = drm_seq_file_printer(m); + unsigned int out_khz; + + out_khz = funcs->get_rate(pixpll); + + seq_printf(m, "%s: %dx%d@%d\n", crtc->name, + mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode)); + + seq_printf(m, "Pixel clock required: %d kHz\n", mode->clock); + seq_printf(m, "Actual frequency output: %u kHz\n", out_khz); + seq_printf(m, "Diff: %d kHz\n", out_khz - mode->clock); + + funcs->print(pixpll, &printer); + + return 0; +} + +static struct drm_info_list lsdc_crtc_debugfs_list[2][4] = { + [0] = { + { "regs", lsdc_crtc_show_regs, 0, NULL }, + { "pixclk", lsdc_pixpll_show_clock, 0, NULL }, + { "scanpos", lsdc_crtc_show_scan_position, 0, NULL }, + { "vblanks", lsdc_crtc_show_vblank_counter, 0, NULL }, + }, + [1] = { + { "regs", lsdc_crtc_show_regs, 0, NULL }, + { "pixclk", lsdc_pixpll_show_clock, 0, NULL }, + { "scanpos", lsdc_crtc_show_scan_position, 0, NULL }, + { "vblanks", lsdc_crtc_show_vblank_counter, 0, NULL }, + }, +}; + +/* operate manually */ + +static int lsdc_crtc_man_op_show(struct seq_file *m, void *data) +{ + seq_puts(m, "soft_reset: soft reset this CRTC\n"); + seq_puts(m, "enable: enable this CRTC\n"); + seq_puts(m, "disable: disable this CRTC\n"); + seq_puts(m, "flip: trigger the page flip\n"); + seq_puts(m, "clone: clone the another crtc with hardware logic\n"); + + return 0; +} + +static int lsdc_crtc_man_op_open(struct inode *inode, struct file *file) +{ + struct drm_crtc *crtc = inode->i_private; + + return single_open(file, lsdc_crtc_man_op_show, crtc); +} + +static ssize_t lsdc_crtc_man_op_write(struct file *file, + const char __user *ubuf, + size_t len, + loff_t *offp) +{ + struct seq_file *m = file->private_data; + struct drm_crtc *crtc = m->private; + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + + char buf[16]; + + if (len > sizeof(buf) - 1) + return -EINVAL; + + if (copy_from_user(buf, ubuf, len)) + return -EFAULT; + + buf[len] = '\0'; + + if (sysfs_streq(buf, "soft_reset")) + ops->soft_reset(lcrtc); + else if (sysfs_streq(buf, "enable")) + ops->enable(lcrtc); + else if (sysfs_streq(buf, "disable")) + ops->disable(lcrtc); + else if (sysfs_streq(buf, "flip")) + ops->flip(lcrtc); + else if (sysfs_streq(buf, "clone")) + ops->clone(lcrtc); + + return len; +} + +static const struct file_operations lsdc_crtc_man_op_fops = { + .owner = THIS_MODULE, + .open = lsdc_crtc_man_op_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = lsdc_crtc_man_op_write, +}; + +static int lsdc_crtc_late_register(struct drm_crtc *crtc) +{ + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + struct drm_minor *minor = crtc->dev->primary; + unsigned int index = dispipe->index; + unsigned int i; + + lcrtc->preg = lsdc_crtc_regs_array[index]; + lcrtc->nreg = ARRAY_SIZE(lsdc_crtc_regs_array[index]); + lcrtc->p_info_list = lsdc_crtc_debugfs_list[index]; + lcrtc->n_info_list = ARRAY_SIZE(lsdc_crtc_debugfs_list[index]); + + for (i = 0; i < lcrtc->n_info_list; ++i) + lcrtc->p_info_list[i].data = lcrtc; + + drm_debugfs_create_files(lcrtc->p_info_list, + lcrtc->n_info_list, + crtc->debugfs_entry, + minor); + + /* supported manual operations */ + debugfs_create_file("ops", 0644, crtc->debugfs_entry, crtc, + &lsdc_crtc_man_op_fops); + + return 0; +} + +static void lsdc_crtc_atomic_print_state(struct drm_printer *p, + const struct drm_crtc_state *state) +{ + const struct lsdc_crtc_state *priv_state; + const struct lsdc_pixpll_parms *pparms; + + priv_state = container_of_const(state, struct lsdc_crtc_state, base); + pparms = &priv_state->pparms; + + drm_printf(p, "\tInput clock divider = %u\n", pparms->div_ref); + drm_printf(p, "\tMedium clock Multiplier = %u\n", pparms->loopc); + drm_printf(p, "\tOutput clock divider = %u\n", pparms->div_out); +} + +static const struct drm_crtc_funcs ls7a1000_crtc_funcs = { + .reset = lsdc_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, + .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, + .late_register = lsdc_crtc_late_register, + .enable_vblank = lsdc_crtc_enable_vblank, + .disable_vblank = lsdc_crtc_disable_vblank, + .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, + .atomic_print_state = lsdc_crtc_atomic_print_state, +}; + +static const struct drm_crtc_funcs ls7a2000_crtc_funcs = { + .reset = lsdc_crtc_reset, + .destroy = drm_crtc_cleanup, + .set_config = drm_atomic_helper_set_config, + .page_flip = drm_atomic_helper_page_flip, + .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, + .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, + .late_register = lsdc_crtc_late_register, + .get_vblank_counter = lsdc_crtc_get_vblank_counter, + .enable_vblank = lsdc_crtc_enable_vblank, + .disable_vblank = lsdc_crtc_disable_vblank, + .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, + .atomic_print_state = lsdc_crtc_atomic_print_state, +}; + +static enum drm_mode_status +lsdc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode) +{ + struct drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + const struct lsdc_desc *descp = ldev->descp; + unsigned int pitch; + + if (mode->hdisplay > descp->max_width) + return MODE_BAD_HVALUE; + + if (mode->vdisplay > descp->max_height) + return MODE_BAD_VVALUE; + + if (mode->clock > descp->max_pixel_clk) { + drm_dbg_kms(ddev, "mode %dx%d, pixel clock=%d is too high\n", + mode->hdisplay, mode->vdisplay, mode->clock); + return MODE_CLOCK_HIGH; + } + + /* 4 for DRM_FORMAT_XRGB8888 */ + pitch = mode->hdisplay * 4; + + if (pitch % descp->pitch_align) { + drm_dbg_kms(ddev, "aligned to %u bytes is required: %u\n", + descp->pitch_align, pitch); + return MODE_BAD_WIDTH; + } + + return MODE_OK; +} + +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + struct lsdc_pixpll *pixpll = &lcrtc->pixpll; + const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); + unsigned int clock = state->mode.clock; + int ret; + + ret = pfuncs->compute(pixpll, clock, &priv_state->pparms); + if (ret) { + drm_warn(crtc->dev, "find PLL parms for %ukHz failed\n", clock); + return -EINVAL; + } + + return 0; +} + +static int lsdc_crtc_helper_atomic_check(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + if (!crtc_state->enable) + return 0; + + return lsdc_pixpll_atomic_check(crtc, crtc_state); +} + +static void lsdc_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + const struct lsdc_crtc_hw_ops *crtc_hw_ops = lcrtc->hw_ops; + struct lsdc_pixpll *pixpll = &lcrtc->pixpll; + const struct lsdc_pixpll_funcs *pixpll_funcs = pixpll->funcs; + struct drm_crtc_state *state = crtc->state; + struct drm_display_mode *mode = &state->mode; + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); + + pixpll_funcs->update(pixpll, &priv_state->pparms); + + if (crtc_hw_ops->set_dma_step) { + unsigned int width_in_bytes = mode->hdisplay * 4; + enum lsdc_dma_steps dma_step; + + /* + * Using large dma step as much as possible, for improving + * hardware DMA efficiency. + */ + if (width_in_bytes % 256 == 0) + dma_step = LSDC_DMA_STEP_256_BYTES; + else if (width_in_bytes % 128 == 0) + dma_step = LSDC_DMA_STEP_128_BYTES; + else if (width_in_bytes % 64 == 0) + dma_step = LSDC_DMA_STEP_64_BYTES; + else /* width_in_bytes % 32 == 0 */ + dma_step = LSDC_DMA_STEP_32_BYTES; + + crtc_hw_ops->set_dma_step(lcrtc, dma_step); + } + + crtc_hw_ops->set_mode(lcrtc, mode); +} + +static void lsdc_crtc_send_vblank(struct drm_crtc *crtc) +{ + struct drm_device *ddev = crtc->dev; + unsigned long flags; + + if (!crtc->state || !crtc->state->event) + return; + + drm_dbg(ddev, "send vblank manually\n"); + + spin_lock_irqsave(&ddev->event_lock, flags); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + spin_unlock_irqrestore(&ddev->event_lock, flags); +} + +static void lsdc_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + + drm_crtc_vblank_on(crtc); + + drm_dbg(crtc->dev, "%s enable\n", crtc->name); + + ops->enable(lcrtc); +} + +static void lsdc_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + + drm_crtc_vblank_off(crtc); + + ops->disable(lcrtc); + + drm_dbg(crtc->dev, "%s disable\n", crtc->name); + + /* + * Make sure we issue a vblank event after disabling the CRTC if + * someone was waiting it. + */ + lsdc_crtc_send_vblank(crtc); +} + +static void lsdc_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + spin_lock_irq(&crtc->dev->event_lock); + if (crtc->state->event) { + if (drm_crtc_vblank_get(crtc) == 0) + drm_crtc_arm_vblank_event(crtc, crtc->state->event); + else + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + } + spin_unlock_irq(&crtc->dev->event_lock); +} + +static bool lsdc_crtc_get_scanout_position(struct drm_crtc *crtc, + bool in_vblank_irq, + int *vpos, + int *hpos, + ktime_t *stime, + ktime_t *etime, + const struct drm_display_mode *mode) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; + int vsw, vbp, vactive_start, vactive_end, vfp_end; + int x, y; + + vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; + vbp = mode->crtc_vtotal - mode->crtc_vsync_end; + + vactive_start = vsw + vbp + 1; + vactive_end = vactive_start + mode->crtc_vdisplay; + + /* last scan line before VSYNC */ + vfp_end = mode->crtc_vtotal; + + if (stime) + *stime = ktime_get(); + + ops->get_scan_pos(lcrtc, &x, &y); + + if (y > vactive_end) + y = y - vfp_end - vactive_start; + else + y -= vactive_start; + + *vpos = y; + *hpos = x; + + if (etime) + *etime = ktime_get(); + + return true; +} + +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { + .mode_valid = lsdc_crtc_mode_valid, + .mode_set_nofb = lsdc_crtc_mode_set_nofb, + .atomic_enable = lsdc_crtc_atomic_enable, + .atomic_disable = lsdc_crtc_atomic_disable, + .atomic_check = lsdc_crtc_helper_atomic_check, + .atomic_flush = lsdc_crtc_atomic_flush, + .get_scanout_position = lsdc_crtc_get_scanout_position, +}; + +int ls7a1000_crtc_init(struct drm_device *ddev, + struct drm_crtc *crtc, + struct drm_plane *primary, + struct drm_plane *cursor, + unsigned int index) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + int ret; + + ret = lsdc_pixpll_init(&lcrtc->pixpll, ddev, index); + if (ret) { + drm_err(ddev, "crtc init with pll failed: %d\n", ret); + return ret; + } + + lcrtc->ldev = to_lsdc(ddev); + lcrtc->hw_ops = &ls7a1000_crtc_hw_ops[index]; + + ret = drm_crtc_init_with_planes(ddev, + crtc, + primary, + cursor, + &ls7a1000_crtc_funcs, + "CRTC-%d", + index); + if (ret) { + drm_err(ddev, "crtc init with planes failed: %d\n", ret); + return ret; + } + + drm_crtc_helper_add(crtc, &lsdc_crtc_helper_funcs); + + ret = drm_mode_crtc_set_gamma_size(crtc, 256); + if (ret) + return ret; + + drm_crtc_enable_color_mgmt(crtc, 0, false, 256); + + return 0; +} + +int ls7a2000_crtc_init(struct drm_device *ddev, + struct drm_crtc *crtc, + struct drm_plane *primary, + struct drm_plane *cursor, + unsigned int index) +{ + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); + int ret; + + ret = lsdc_pixpll_init(&lcrtc->pixpll, ddev, index); + if (ret) { + drm_err(ddev, "crtc init with pll failed: %d\n", ret); + return ret; + } + + lcrtc->ldev = to_lsdc(ddev); + lcrtc->hw_ops = &ls7a2000_crtc_hw_ops[index]; + + ret = drm_crtc_init_with_planes(ddev, + crtc, + primary, + cursor, + &ls7a2000_crtc_funcs, + "CRTC-%d", + index); + if (ret) { + drm_err(ddev, "crtc init with planes failed: %d\n", ret); + return ret; + } + + drm_crtc_helper_add(crtc, &lsdc_crtc_helper_funcs); + + ret = drm_mode_crtc_set_gamma_size(crtc, 256); + if (ret) + return ret; + + drm_crtc_enable_color_mgmt(crtc, 0, false, 256); + + return 0; +} diff --git a/drivers/gpu/drm/loongson/lsdc_debugfs.c b/drivers/gpu/drm/loongson/lsdc_debugfs.c new file mode 100644 index 000000000000..30d70a5dc7d5 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_debugfs.c @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Corporation + */ + +#include + +#include "lsdc_drv.h" +#include "lsdc_gem.h" +#include "lsdc_probe.h" +#include "lsdc_ttm.h" + +/* device level debugfs */ + +static int lsdc_identify(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_device *ldev = (struct lsdc_device *)node->info_ent->data; + const struct loongson_gfx_desc *gfx = to_loongson_gfx(ldev->descp); + u8 impl, rev; + + loongson_cpu_get_prid(&impl, &rev); + + seq_printf(m, "Running on cpu 0x%x, cpu revision: 0x%x\n", + impl, rev); + + seq_printf(m, "Contained in: %s\n", gfx->model); + + return 0; +} + +static int lsdc_show_mm(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *ddev = node->minor->dev; + struct drm_printer p = drm_seq_file_printer(m); + + drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p); + + return 0; +} + +static int loongson_gfxpll_show_clock(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct lsdc_device *ldev = (struct lsdc_device *)node->info_ent->data; + struct drm_printer printer = drm_seq_file_printer(m); + struct loongson_gfxpll *gfxpll = ldev->gfxpll; + + gfxpll->funcs->print(gfxpll, &printer, true); + + return 0; +} + +static struct drm_info_list lsdc_debugfs_list[] = { + { "chips", lsdc_identify, 0, NULL }, + { "clocks", loongson_gfxpll_show_clock, 0, NULL }, + { "mm", lsdc_show_mm, 0, NULL }, + { "bos", lsdc_show_buffer_object, 0, NULL }, +}; + +void lsdc_debugfs_init(struct drm_minor *minor) +{ + struct drm_device *ddev = minor->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + unsigned int N = ARRAY_SIZE(lsdc_debugfs_list); + unsigned int i; + + for (i = 0; i < N; ++i) + lsdc_debugfs_list[i].data = ldev; + + drm_debugfs_create_files(lsdc_debugfs_list, + N, + minor->debugfs_root, + minor); + + lsdc_ttm_debugfs_init(ldev); +} diff --git a/drivers/gpu/drm/loongson/lsdc_device.c b/drivers/gpu/drm/loongson/lsdc_device.c new file mode 100644 index 000000000000..e2dd0e357fa2 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_device.c @@ -0,0 +1,104 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Corporation + */ + +#include + +#include "lsdc_drv.h" + +static const struct lsdc_kms_funcs ls7a1000_kms_funcs = { + .create_i2c = lsdc_create_i2c_chan, + .irq_handler = ls7a1000_dc_irq_handler, + .output_init = ls7a1000_output_init, + .cursor_plane_init = ls7a1000_cursor_plane_init, + .primary_plane_init = lsdc_primary_plane_init, + .crtc_init = ls7a1000_crtc_init, +}; + +static const struct lsdc_kms_funcs ls7a2000_kms_funcs = { + .create_i2c = lsdc_create_i2c_chan, + .irq_handler = ls7a2000_dc_irq_handler, + .output_init = ls7a2000_output_init, + .cursor_plane_init = ls7a2000_cursor_plane_init, + .primary_plane_init = lsdc_primary_plane_init, + .crtc_init = ls7a2000_crtc_init, +}; + +static const struct loongson_gfx_desc ls7a1000_gfx = { + .dc = { + .num_of_crtc = 2, + .max_pixel_clk = 200000, + .max_width = 2048, + .max_height = 2048, + .num_of_hw_cursor = 1, + .hw_cursor_w = 32, + .hw_cursor_h = 32, + .pitch_align = 256, + .mc_bits = 40, + .has_vblank_counter = false, + .funcs = &ls7a1000_kms_funcs, + }, + .conf_reg_base = LS7A1000_CONF_REG_BASE, + .gfxpll = { + .reg_offset = LS7A1000_PLL_GFX_REG, + .reg_size = 8, + }, + .pixpll = { + [0] = { + .reg_offset = LS7A1000_PIXPLL0_REG, + .reg_size = 8, + }, + [1] = { + .reg_offset = LS7A1000_PIXPLL1_REG, + .reg_size = 8, + }, + }, + .chip_id = CHIP_LS7A1000, + .model = "LS7A1000 bridge chipset", +}; + +static const struct loongson_gfx_desc ls7a2000_gfx = { + .dc = { + .num_of_crtc = 2, + .max_pixel_clk = 350000, + .max_width = 4096, + .max_height = 4096, + .num_of_hw_cursor = 2, + .hw_cursor_w = 64, + .hw_cursor_h = 64, + .pitch_align = 64, + .mc_bits = 40, /* Support 48 but using 40 for backward compatibility */ + .has_vblank_counter = true, + .funcs = &ls7a2000_kms_funcs, + }, + .conf_reg_base = LS7A2000_CONF_REG_BASE, + .gfxpll = { + .reg_offset = LS7A2000_PLL_GFX_REG, + .reg_size = 8, + }, + .pixpll = { + [0] = { + .reg_offset = LS7A2000_PIXPLL0_REG, + .reg_size = 8, + }, + [1] = { + .reg_offset = LS7A2000_PIXPLL1_REG, + .reg_size = 8, + }, + }, + .chip_id = CHIP_LS7A2000, + .model = "LS7A2000 bridge chipset", +}; + +const struct lsdc_desc * +lsdc_device_probe(struct pci_dev *pdev, enum loongson_chip_id chip_id) +{ + if (chip_id == CHIP_LS7A1000) + return &ls7a1000_gfx.dc; + + if (chip_id == CHIP_LS7A2000) + return &ls7a2000_gfx.dc; + + return ERR_PTR(-ENODEV); +} diff --git a/drivers/gpu/drm/loongson/lsdc_drv.c b/drivers/gpu/drm/loongson/lsdc_drv.c new file mode 100644 index 000000000000..7f93b7e25b14 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_drv.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2023 Loongson Corporation + */ + +#include + +#include