Received: by 2002:a05:6358:9144:b0:117:f937:c515 with SMTP id r4csp1965055rwr; Sat, 6 May 2023 02:12:26 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ6n19bXEq/UT2KLSn+M2F+1yFuT5plw+YuixzdOMZZwRTLz0oJ9zFi3a+HCBqvP1usuLfGL X-Received: by 2002:a17:90b:148d:b0:23f:b609:e707 with SMTP id js13-20020a17090b148d00b0023fb609e707mr4198282pjb.2.1683364345650; Sat, 06 May 2023 02:12:25 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1683364345; cv=none; d=google.com; s=arc-20160816; b=XWSPXJZAo3UIpb9PxZid3Uavj62JBrxk7sWIgAgHbCemlV0HextqjQjaPOIVd8kfiZ 0hgsacuM7HRhDcw+OhD+L7Rt3xvJqqg9LGNEX+Eq6crOK9IfvSDMeXlEdDiU9H9KGp0T j2QYLRYNgXGvacJdbQamxfwCpzYIK6NK7SvewxPVZgkezBN3q2KG4VavWditLA03Fsxg kDigpnZFledCXhe9wz78oCeeINtvfX0by5PkrqM8R4DpOkaGYEApKrNcT5p00JWS7+Sp /LX+BTMI85qxS4LbitLGReE+EdtFlYYPI9At0/uYOl1Xr8fkTW8QryoeffnGJQz1a2nx dHDg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:in-reply-to:from :content-language:references:cc:to:subject:user-agent:mime-version :date:message-id:sender:hmm_source_type:hmm_attache_num :hmm_source_ip; bh=cpXIfqByTb0sUFp/uVF7ePFyawoKUIfyzgWzEIizizM=; b=l/GZIOGX2Hz2c8aGcRBFodvFYPNEgptFM/9IzGUNLcLK0YEhg+DTSwJ0+x8LNtj2Qb CtO7rzFNijLyL0FX4QhvkmeHKNp6PGn4qCL+6MbWSDXvMjb37v22Z0erI16t4Xb017k/ I6S7ZrP4ntUqiww/EzmVgkVi4rjLyMgl/UooAwKzT1iaKpgwDMKrLgIDw+/R8B4CO1O7 qqrhdXDJKrC0z1M/tLxJOWJ34vmLF0C5ndGOavawn/jVCwd1oJ9dgTJh8nLbhkSdh0I9 p4uRoJ0U0xDtjaJDJ9l8hhE9V2Hd7UmtKDixy3X0KiWi6dX8IQa50yiHkk0QIsE9cMQk tMcA== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Return-Path: Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id lb15-20020a17090b4a4f00b0024e4036717csi8708711pjb.100.2023.05.06.02.12.12; Sat, 06 May 2023 02:12:25 -0700 (PDT) Received-SPF: pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) client-ip=2620:137:e000::1:20; Authentication-Results: mx.google.com; spf=pass (google.com: domain of linux-kernel-owner@vger.kernel.org designates 2620:137:e000::1:20 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S231343AbjEFIYJ (ORCPT + 99 others); Sat, 6 May 2023 04:24:09 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:33572 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230085AbjEFIYI (ORCPT ); Sat, 6 May 2023 04:24:08 -0400 Received: from 189.cn (ptr.189.cn [183.61.185.104]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 4D3ADE5D; Sat, 6 May 2023 01:24:00 -0700 (PDT) HMM_SOURCE_IP: 10.64.8.43:54680.2027615738 HMM_ATTACHE_NUM: 0000 HMM_SOURCE_TYPE: SMTP Received: from clientip-114.242.206.180 (unknown [10.64.8.43]) by 189.cn (HERMES) with SMTP id 16710102955; Sat, 6 May 2023 16:23:53 +0800 (CST) Received: from ([114.242.206.180]) by gateway-151646-dep-85667d6c59-fm8l8 with ESMTP id 86c7455d1d7e4253805f470595c1d111 for chenhuacai@kernel.org; Sat, 06 May 2023 16:23:58 CST X-Transaction-ID: 86c7455d1d7e4253805f470595c1d111 X-Real-From: 15330273260@189.cn X-Receive-IP: 114.242.206.180 X-MEDUSA-Status: 0 Sender: 15330273260@189.cn Message-ID: <0056d62c-5d4b-63cb-85ab-4930bfa1327f@189.cn> Date: Sat, 6 May 2023 16:23:51 +0800 MIME-Version: 1.0 User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Thunderbird/102.10.0 Subject: Re: [PATCH v12 2/2] drm: add kms driver for loongson display controller To: Huacai Chen Cc: Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Daniel Vetter , Sumit Semwal , Christian Koenig , Emil Velikov , linaro-mm-sig@lists.linaro.org, loongson-kernel@lists.loongnix.cn, Li Yi , linux-kernel@vger.kernel.org, dri-devel@lists.freedesktop.org, nathan@kernel.org, linux-media@vger.kernel.org References: <20230504080406.1213623-1-suijingfeng@loongson.cn> <20230504080406.1213623-3-suijingfeng@loongson.cn> Content-Language: en-US From: Sui Jingfeng <15330273260@189.cn> In-Reply-To: Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-5.9 required=5.0 tests=BAYES_00, FREEMAIL_ENVFROM_END_DIGIT,FREEMAIL_FROM,FROM_LOCAL_DIGITS, FROM_LOCAL_HEX,NICE_REPLY_A,SPF_HELO_PASS,SPF_PASS, T_SCC_BODY_TEXT_LINE autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on lindbergh.monkeyblade.net Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Hi, So glad receiving your reviews! On 2023/5/6 11:08, Huacai Chen wrote: > Hi, Jingfeng, > > On Thu, May 4, 2023 at 4:04 PM Sui Jingfeng wrote: >> 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 VRAM >> BOs once when 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, we make the S3 support complete finally. With v11, I can't see >> any garbage data after resume. Teste on both ls7a1000 and ls7a2000 >> platform, with single screen and double screen configuration tested. >> 2) Fix vblank wait timeout when disable CRTC. >> 3) Test against IGT, at least fbdev test and kms_flip test of it passed, >> while most tests of it 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) >> >> v11 -> v12: >> none >> >> 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 | 484 +++++++++ >> 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 | 12 + >> 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, 6194 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 > Our regular name is "Loongson Technology Corporation Limited". Personally,  I think "Loongson Corporation" is more compact, the idea behind this is "less is more". But, this is acceptable, if no objections from other people. >> + */ >> + >> +#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); > Don't need split to so many lines, the last three lines can be combined. > Personally,  it is more clean and clear for me. One argument per line,  it is easy to change when debugging. It helps to form a unified coding style.  :/ The word 'unified' here means how many arguments should be put at first line, and  if no space left, how many arguments left should be put at the following line. I don't want a heavy top(head), at least one argument per line is a reasonable rule. Also, there are 80 characters limitation per line in the past, it is 100 now. so there are still cases where I can't put all arguments in one line, and check patch will complain. Sometimes we need back port this to support downstream kernel, say linux-5.10 used by OpenEuler release, then, check patch still bring concerns to us, so I have to adjust coding style. Maybe, you are right, As I see you are the maintainer of LoongArch and  Mips kernel , pretty over-weighting expert.  :-) let's leave this issue one or two weeks,  wait how other reviewers say. If your idea get more supporters, then I will revise the whole patch at v13. >> +} >> + >> +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); > As above, the last two lines can be combined. > >> + 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); > Same problem. > >> + 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); > Same problem. > >> + 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) > Same problem. > >> +{ >> + 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); > Same problem. > >> + 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..a91db7fe33c8 >> --- /dev/null >> +++ b/drivers/gpu/drm/loongson/lsdc_drv.c >> @@ -0,0 +1,484 @@ >> +// SPDX-License-Identifier: GPL-2.0+ >> +/* >> + * Copyright (C) 2023 Loongson Corporation >> + */ >> + >> +#include >> + >> +#include