From patchwork Wed Mar 29 15:50:33 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Sui Jingfeng <15330273260@189.cn> X-Patchwork-Id: 668757 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 ABFB9C761AF for ; Wed, 29 Mar 2023 15:51:08 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230331AbjC2PvH (ORCPT ); Wed, 29 Mar 2023 11:51:07 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:38032 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230490AbjC2Puu (ORCPT ); Wed, 29 Mar 2023 11:50:50 -0400 Received: from 189.cn (ptr.189.cn [183.61.185.101]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 8ED58468B; Wed, 29 Mar 2023 08:50:43 -0700 (PDT) HMM_SOURCE_IP: 10.64.8.41:58922.1411433373 HMM_ATTACHE_NUM: 0000 HMM_SOURCE_TYPE: SMTP Received: from clientip-114.242.206.180 (unknown [10.64.8.41]) by 189.cn (HERMES) with SMTP id C5AC81002C5; Wed, 29 Mar 2023 23:50:41 +0800 (CST) Received: from ([114.242.206.180]) by gateway-151646-dep-7b48884fd-bkw2h with ESMTP id cb811aa7901841a1af610bb464e7b955 for maarten.lankhorst@linux.intel.com; Wed, 29 Mar 2023 23:50:42 CST X-Transaction-ID: cb811aa7901841a1af610bb464e7b955 X-Real-From: 15330273260@189.cn X-Receive-IP: 114.242.206.180 X-MEDUSA-Status: 0 Sender: 15330273260@189.cn From: Sui Jingfeng <15330273260@189.cn> To: Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Daniel Vetter , suijingfeng , Sumit Semwal , Christian Koenig 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, loongson-kernel@lists.loongnix.cn, Li Yi Subject: [PATCH v9 2/2] drm: add kms driver for loongson display controller Date: Wed, 29 Mar 2023 23:50:33 +0800 Message-Id: <20230329155033.1303550-3-15330273260@189.cn> X-Mailer: git-send-email 2.25.1 In-Reply-To: <20230329155033.1303550-1-15330273260@189.cn> References: <20230329155033.1303550-1-15330273260@189.cn> MIME-Version: 1.0 Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org From: Sui Jingfeng Loongson display controller IP has been integrated in both Loongson North Bridge chipset(ls7a1000 and ls7a2000) and Loongson SoCs(ls2k1000 and ls2k2000 etc), it even has been included in Loongson BMC products. This display controller is a PCI device, it has two display pipe. For the DC in LS7A1000 and LS2K1000 each way has a DVO output interface which provide RGB888 signals, vertical & horizontal synchronisations, and the pixel clock. Each CRTC is able to support 1920x1080@60Hz, the maximum resolution 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 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, besides above feature, it has two hardware cursors, two hardware vblank counter and two scanout position recorders. 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 to the VRAM. Signed-off-by: Li Yi Signed-off-by: Sui Jingfeng Signed-off-by: Sui Jingfeng <15330273260@189.cn> --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/loongson/Kconfig | 17 + drivers/gpu/drm/loongson/Makefile | 16 + drivers/gpu/drm/loongson/lsdc_crtc.c | 381 ++++++++++++++++ drivers/gpu/drm/loongson/lsdc_debugfs.c | 261 +++++++++++ drivers/gpu/drm/loongson/lsdc_drv.c | 508 +++++++++++++++++++++ drivers/gpu/drm/loongson/lsdc_drv.h | 324 ++++++++++++++ drivers/gpu/drm/loongson/lsdc_gem.c | 294 +++++++++++++ drivers/gpu/drm/loongson/lsdc_gem.h | 26 ++ drivers/gpu/drm/loongson/lsdc_i2c.c | 171 +++++++ drivers/gpu/drm/loongson/lsdc_irq.c | 86 ++++ drivers/gpu/drm/loongson/lsdc_irq.h | 12 + drivers/gpu/drm/loongson/lsdc_output.c | 563 ++++++++++++++++++++++++ drivers/gpu/drm/loongson/lsdc_output.h | 14 + drivers/gpu/drm/loongson/lsdc_plane.c | 432 ++++++++++++++++++ drivers/gpu/drm/loongson/lsdc_pll.c | 338 ++++++++++++++ drivers/gpu/drm/loongson/lsdc_pll.h | 76 ++++ drivers/gpu/drm/loongson/lsdc_probe.c | 86 ++++ drivers/gpu/drm/loongson/lsdc_probe.h | 11 + drivers/gpu/drm/loongson/lsdc_regs.h | 370 ++++++++++++++++ drivers/gpu/drm/loongson/lsdc_ttm.c | 426 ++++++++++++++++++ drivers/gpu/drm/loongson/lsdc_ttm.h | 71 +++ 23 files changed, 4486 insertions(+) create mode 100644 drivers/gpu/drm/loongson/Kconfig create mode 100644 drivers/gpu/drm/loongson/Makefile 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_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_i2c.c 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.c create mode 100644 drivers/gpu/drm/loongson/lsdc_output.h create mode 100644 drivers/gpu/drm/loongson/lsdc_plane.c create mode 100644 drivers/gpu/drm/loongson/lsdc_pll.c create mode 100644 drivers/gpu/drm/loongson/lsdc_pll.h 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..c5dcc7c9b348 --- /dev/null +++ b/drivers/gpu/drm/loongson/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0 + +loongson-y := \ + lsdc_crtc.o \ + lsdc_debugfs.o \ + lsdc_drv.o \ + lsdc_gem.o \ + lsdc_i2c.o \ + lsdc_irq.o \ + lsdc_output.o \ + lsdc_plane.o \ + lsdc_pll.o \ + lsdc_probe.o \ + lsdc_ttm.o + +obj-$(CONFIG_DRM_LOONGSON) += loongson.o diff --git a/drivers/gpu/drm/loongson/lsdc_crtc.c b/drivers/gpu/drm/loongson/lsdc_crtc.c new file mode 100644 index 000000000000..48950c476a90 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_crtc.c @@ -0,0 +1,381 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include +#include +#include + +#include "lsdc_drv.h" + +static u32 lsdc_get_vblank_counter_pipe0(struct drm_crtc *crtc) +{ + struct lsdc_device *ldev = to_lsdc(crtc->dev); + + return lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG); +} + +static u32 lsdc_get_vblank_counter_pipe1(struct drm_crtc *crtc) +{ + struct lsdc_device *ldev = to_lsdc(crtc->dev); + + return lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG); +} + +static int lsdc_enable_vblank_pipe0(struct drm_crtc *crtc) +{ + struct lsdc_device *ldev = to_lsdc(crtc->dev); + + lsdc_ureg32_set(ldev, LSDC_INT_REG, INT_CRTC0_VSYNC_EN); + + return 0; +} + +static void lsdc_disable_vblank_pipe0(struct drm_crtc *crtc) +{ + struct lsdc_device *ldev = to_lsdc(crtc->dev); + + lsdc_ureg32_clr(ldev, LSDC_INT_REG, INT_CRTC0_VSYNC_EN); +} + +static int lsdc_enable_vblank_pipe1(struct drm_crtc *crtc) +{ + struct lsdc_device *ldev = to_lsdc(crtc->dev); + + lsdc_ureg32_set(ldev, LSDC_INT_REG, INT_CRTC1_VSYNC_EN); + + return 0; +} + +static void lsdc_disable_vblank_pipe1(struct drm_crtc *crtc) +{ + struct lsdc_device *ldev = to_lsdc(crtc->dev); + + lsdc_ureg32_clr(ldev, LSDC_INT_REG, INT_CRTC1_VSYNC_EN); +} + +static void lsdc_crtc_reset(struct drm_crtc *crtc) +{ + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); + struct drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + struct lsdc_crtc_state *priv_crtc_state; + unsigned int index = dispipe->index; + u32 val; + + val = LSDC_PF_XRGB8888 | CFG_RESET_N; + if (ldev->descp->chip == CHIP_LS7A2000) + val |= LSDC_DMA_STEP_64_BYTES; + + lsdc_crtc_wreg32(ldev, LSDC_CRTC0_CFG_REG, index, val); + + if (ldev->descp->chip == CHIP_LS7A2000) { + val = PHY_CLOCK_EN | PHY_DATA_EN; + lsdc_crtc_wreg32(ldev, LSDC_CRTC0_PANEL_CONF_REG, index, val); + } + + if (crtc->state) { + priv_crtc_state = to_lsdc_crtc_state(crtc->state); + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); + kfree(priv_crtc_state); + } + + priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL); + if (!priv_crtc_state) + return; + + __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); +} + +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; +} + +#define LSDC_CRTC_FUNCS(pipe, pfn_get_vblank_counter) \ + .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, \ + .get_vblank_counter = pfn_get_vblank_counter, \ + .enable_vblank = lsdc_enable_vblank_##pipe, \ + .disable_vblank = lsdc_disable_vblank_##pipe, \ + .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp + +static const struct drm_crtc_funcs lsdc_crtc_funcs_array[2][LSDC_NUM_CRTC] = { + { + { + LSDC_CRTC_FUNCS(pipe0, NULL), + }, + { + LSDC_CRTC_FUNCS(pipe1, NULL), + } + }, + { + { + LSDC_CRTC_FUNCS(pipe0, lsdc_get_vblank_counter_pipe0), + }, + { + LSDC_CRTC_FUNCS(pipe1, lsdc_get_vblank_counter_pipe1), + } + } +}; + +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; + + 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(ddev, "mode %dx%d, pixel clock=%d is too high\n", + mode->hdisplay, mode->vdisplay, mode->clock); + return MODE_CLOCK_HIGH; + } + + /* TODO: check RGB565 support, 4 for DRM_FORMAT_XRGB8888 */ + if ((mode->hdisplay * 4) % descp->pitch_align) { + drm_dbg(ddev, "stride align to %u bytes is required\n", + descp->pitch_align); + return MODE_BAD_WIDTH; + } + + return MODE_OK; +} + +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); + struct lsdc_pll *pixpll = &dispipe->pixpll; + const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); + struct lsdc_pll_parms *pout = &priv_state->pparms; + unsigned int clock = state->mode.clock; + int ret; + + ret = pfuncs->compute(pixpll, clock, pout); + 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_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + const struct lsdc_desc *descp = ldev->descp; + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); + struct lsdc_pll *pixpll = &dispipe->pixpll; + const struct lsdc_pixpll_funcs *clk_func = pixpll->funcs; + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(crtc_state); + struct drm_display_mode *mode = &crtc_state->mode; + unsigned int index = dispipe->index; + unsigned int width_in_bytes = mode->crtc_hdisplay * 4; + u32 val; + + lsdc_crtc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG, index, + (mode->crtc_htotal << 16) | mode->crtc_hdisplay); + + lsdc_crtc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG, index, + (mode->crtc_vtotal << 16) | mode->crtc_vdisplay); + + lsdc_crtc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG, index, + (mode->crtc_hsync_end << 16) | + mode->crtc_hsync_start | + HSYNC_EN); + + lsdc_crtc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG, index, + (mode->crtc_vsync_end << 16) | + mode->crtc_vsync_start | + VSYNC_EN); + + val = lsdc_crtc_rreg32(ldev, LSDC_CRTC0_CFG_REG, index); + /* clear old dma step settings */ + val &= ~CFG_DMA_STEP_MASK; + + if (descp->chip == CHIP_LS7A2000) { + /* + * Using large dma step as much as possible, + * for improve hardware DMA efficiency. + */ + if (width_in_bytes % 256 == 0) + val |= LSDC_DMA_STEP_256_BYTES; + else if (width_in_bytes % 128 == 0) + val |= LSDC_DMA_STEP_128_BYTES; + else if (width_in_bytes % 64 == 0) + val |= LSDC_DMA_STEP_64_BYTES; + else /* width_in_bytes % 32 == 0 */ + val |= LSDC_DMA_STEP_32_BYTES; + } + + clk_func->update(pixpll, &priv_state->pparms); + + lsdc_crtc_wreg32(ldev, LSDC_CRTC0_CFG_REG, index, val | CFG_OUTPUT_EN); + + drm_crtc_vblank_on(crtc); +} + +static void lsdc_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); + struct drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + unsigned int index = dispipe->index; + + lsdc_crtc_ureg32_clr(ldev, LSDC_CRTC0_CFG_REG, index, CFG_OUTPUT_EN); + + drm_crtc_vblank_off(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_display_pipe *dispipe = crtc_to_display_pipe(crtc); + struct drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + unsigned int index = dispipe->index; + int vsw, vbp, vactive_start, vactive_end, vfp_end, y; + u32 val = 0; + + 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(); + + val = lsdc_crtc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG, index); + + y = (val & 0xffff); + + if (y < vactive_start) + y -= vactive_start; + else if (y > vactive_end) + y = y - vfp_end - vactive_start; + else + y -= vactive_start; + + *vpos = y; + *hpos = val >> 16; + + if (etime) + *etime = ktime_get(); + + return true; +} + +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { + .mode_valid = lsdc_crtc_mode_valid, + .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 lsdc_crtc_init(struct drm_device *ddev, + struct drm_crtc *crtc, + struct drm_plane *primary, + struct drm_plane *cursor, + unsigned int index) +{ + struct lsdc_device *ldev = to_lsdc(ddev); + const struct lsdc_desc *descp = ldev->descp; + unsigned int has = descp->has_vblank_counter; + int ret; + + ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor, + &lsdc_crtc_funcs_array[has][index], + "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); + + drm_dbg(ddev, "%s initialized %s vblank counter support\n", + crtc->name, has ? "with" : "without"); + + 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..3eba22e57ee6 --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_debugfs.c @@ -0,0 +1,261 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include +#include +#include + +#include "lsdc_drv.h" +#include "lsdc_gem.h" +#include "lsdc_probe.h" + +#ifdef CONFIG_DEBUG_FS + +static int lsdc_identify(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 lsdc_device *ldev = to_lsdc(ddev); + u8 imp, rev; + + loongson_cpu_get_prid(&imp, &rev); + + seq_printf(m, "I'm in %s, running on cpu 0x%x, cpu revision: 0x%x\n", + chip_to_str(ldev->descp->chip), imp, rev); + + return 0; +} + +static int lsdc_show_clock(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_crtc *crtc; + + drm_for_each_crtc(crtc, ddev) { + struct lsdc_display_pipe *pipe = crtc_to_display_pipe(crtc); + struct lsdc_pll *pixpll = &pipe->pixpll; + const struct lsdc_pixpll_funcs *funcs = pixpll->funcs; + struct drm_display_mode *mode = &crtc->state->mode; + struct lsdc_pll_parms parms; + unsigned int out_khz; + + out_khz = funcs->get_clock_rate(pixpll, &parms); + + seq_printf(m, "Display pipe %u: %dx%d\n", + pipe->index, mode->hdisplay, mode->vdisplay); + + seq_printf(m, "Frequency actually output: %u kHz\n", out_khz); + seq_printf(m, "Pixel clock required: %d kHz\n", mode->clock); + seq_printf(m, "diff: %d kHz\n", out_khz - mode->clock); + + seq_printf(m, "div_ref=%u, loopc=%u, div_out=%u\n", + parms.div_ref, parms.loopc, parms.div_out); + + seq_printf(m, "hsync_start=%d, hsync_end=%d, htotal=%d\n", + mode->hsync_start, mode->hsync_end, mode->htotal); + seq_printf(m, "vsync_start=%d, vsync_end=%d, vtotal=%d\n\n", + mode->vsync_start, mode->vsync_end, mode->vtotal); + } + + 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; +} + +#define REG_DEF(reg) { __stringify_1(LSDC_##reg##_REG), LSDC_##reg##_REG } +static const struct { + const char *name; + u32 reg_offset; +} lsdc_regs_array[] = { + REG_DEF(CURSOR0_CFG), + REG_DEF(CURSOR0_POSITION), + REG_DEF(CURSOR0_BG_COLOR), + REG_DEF(CURSOR0_FG_COLOR), + 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(INT), + 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), +}; + +static int lsdc_show_regs(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 lsdc_device *ldev = to_lsdc(ddev); + int i; + + for (i = 0; i < ARRAY_SIZE(lsdc_regs_array); i++) { + u32 offset = lsdc_regs_array[i].reg_offset; + const char *name = lsdc_regs_array[i].name; + + seq_printf(m, "%s (0x%04x): 0x%08x\n", + name, offset, lsdc_rreg32(ldev, offset)); + } + + return 0; +} + +static int lsdc_show_vblank_counter(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 lsdc_device *ldev = to_lsdc(ddev); + + seq_printf(m, "CRTC-0 vblank counter: %08u\n", + lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG)); + + seq_printf(m, "CRTC-1 vblank counter: %08u\n", + lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG)); + + return 0; +} + +static int lsdc_show_scan_position(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 lsdc_device *ldev = to_lsdc(ddev); + u32 p0 = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); + u32 p1 = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); + + seq_printf(m, "CRTC-0: x: %08u, y: %08u\n", p0 >> 16, p0 & 0xFFFF); + seq_printf(m, "CRTC-1: x: %08u, y: %08u\n", p1 >> 16, p1 & 0xFFFF); + + return 0; +} + +static int lsdc_show_primary_addr(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 lsdc_device *ldev = to_lsdc(ddev); + u32 lo, hi; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + if (val & CFG_FB_IN_USING) { + lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_LO_ADDR_REG); + hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_HI_ADDR_REG); + seq_printf(m, "CRTC-0 using fb1: 0x%x:%x\n", hi, lo); + } else { + lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_LO_ADDR_REG); + hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_HI_ADDR_REG); + seq_printf(m, "CRTC-0 using fb0: 0x%x:%x\n", hi, lo); + } + + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + if (val & CFG_FB_IN_USING) { + lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_LO_ADDR_REG); + hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_HI_ADDR_REG); + seq_printf(m, "CRTC-1 using fb1: 0x%x:%x\n", hi, lo); + } else { + lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_LO_ADDR_REG); + hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_HI_ADDR_REG); + seq_printf(m, "CRTC-1 using fb0: 0x%x:%x\n", hi, lo); + } + + return 0; +} + +/* To verify if TTM_PL_FLAG_TOPDOWN works */ +static int lsdc_show_cursor_addr(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 lsdc_device *ldev = to_lsdc(ddev); + u32 lo, hi; + + lo = lsdc_rreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG); + hi = lsdc_rreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG); + seq_printf(m, "CURSOR0: 0x%x:%x\n", hi, lo); + + lo = lsdc_rreg32(ldev, LSDC_CURSOR1_ADDR_LO_REG); + hi = lsdc_rreg32(ldev, LSDC_CURSOR1_ADDR_HI_REG); + seq_printf(m, "CURSOR1: 0x%x:%x\n", hi, lo); + + return 0; +} + +static int lsdc_show_stride(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 lsdc_device *ldev = to_lsdc(ddev); + u32 stride0 = lsdc_rreg32(ldev, LSDC_CRTC0_STRIDE_REG); + u32 stride1 = lsdc_rreg32(ldev, LSDC_CRTC1_STRIDE_REG); + + seq_printf(m, "PIPE-0 stride: %u\n", stride0); + seq_printf(m, "PIPE-1 stride: %u\n", stride1); + + return 0; +} + +static int lsdc_trigger_flip_fb(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 lsdc_device *ldev = to_lsdc(ddev); + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val | CFG_PAGE_FLIP); + seq_puts(m, "CRTC-0 flip triggered\n"); + + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val | CFG_PAGE_FLIP); + seq_puts(m, "CRTC-1 flip triggered\n"); + + return 0; +} + +static struct drm_info_list lsdc_debugfs_list[] = { + { "chip", lsdc_identify, 0, NULL }, + { "clocks", lsdc_show_clock, 0 }, + { "mm", lsdc_show_mm, 0, NULL }, + { "regs", lsdc_show_regs, 0 }, + { "vblanks", lsdc_show_vblank_counter, 0, NULL }, + { "scan_pos", lsdc_show_scan_position, 0, NULL }, + { "fb_addr", lsdc_show_primary_addr, 0, NULL }, + { "cursor_addr", lsdc_show_cursor_addr, 0, NULL }, + { "flip", lsdc_trigger_flip_fb, 0, NULL }, + { "stride", lsdc_show_stride, 0, NULL }, + { "bos", lsdc_show_buffer_object, 0, NULL }, +}; + +#endif + +void lsdc_debugfs_init(struct drm_minor *minor) +{ +#ifdef CONFIG_DEBUG_FS + drm_debugfs_create_files(lsdc_debugfs_list, + ARRAY_SIZE(lsdc_debugfs_list), + minor->debugfs_root, + minor); +#endif +} diff --git a/drivers/gpu/drm/loongson/lsdc_drv.c b/drivers/gpu/drm/loongson/lsdc_drv.c new file mode 100644 index 000000000000..348f48903d4d --- /dev/null +++ b/drivers/gpu/drm/loongson/lsdc_drv.c @@ -0,0 +1,508 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include + +#include