Received: by 2002:a05:6358:9144:b0:117:f937:c515 with SMTP id r4csp1718531rwr; Fri, 5 May 2023 20:17:46 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ5upAT/+a4Ko5eAvEObpOePiSKLQr93jTUWH1fuxneB10UzuMPYAuTuiLrPVS3dN3ufxgbO X-Received: by 2002:a17:90b:1243:b0:24e:1df5:103f with SMTP id gx3-20020a17090b124300b0024e1df5103fmr3685362pjb.42.1683343066194; Fri, 05 May 2023 20:17:46 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1683343066; cv=none; d=google.com; s=arc-20160816; b=mNg6MhCJkL2sEdRntcFT8XXXF16boFN/V3S2go+uMgfStY67bOc78VfBai+IPaUc9Q Jp0EcntK01yh2VQuO+iO79eKWp5+rS49SmREL6dxta/Ejd6787cQ+TqVDzSYyOhc6nvW IU9+/H7srzivBKvwg+qrOLSE3jSWWJOoHXrzp39lgUB93hkaYCbdYAbc57tR52m1yF0a 28r1VvJZiYXajQiPf1KctsloJtb3EAiPuh0HvqYRzVJd3fZ7vR98qRYRG2pWw/v7HNjZ WwU5QmO2s1WxEsckwmS7n9icZOYmKKWZ8iNH3fH0feZAaw88PSDiPHHIHA6hfhIz51Ln NbaQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:cc:to:subject :message-id:date:from:in-reply-to:references:mime-version :dkim-signature; bh=llqrh3f97Lw6dKAUa5hp3dRnEprQQGpra6ad7gKMS+s=; b=csTW8gmpvJlxYSQ7WAMpbXiSCxmQyPOWH1QOphpJ5wS9ip99d06zKxVT1wSLUcuOI5 F9VIpk97BtFFF/4t+ypZjYiCQjQst37GPbPH18dY2tXgf4Cp/M+wjZrEWO1cSNSmr5rn LX9OiiKp20lD2OVcBUNTCeG5r9t93n0IFQUt28exMCAXtj87w+F5acvBcV8F8weMX+AN Bh3222U9pPQRx1SHRe+jAnLllpIkDUoUeuBpuxh2/yiMRrl1x9MW2RsX+7xtw0dzrKkB IgtdhkWUk9hR5BMmrHCw/MWu/i5DxPyt099+jGQXEUeCXeejquY+fOom04CHAkCSkpPj dPGg== ARC-Authentication-Results: i=1; mx.google.com; dkim=pass header.i=@kernel.org header.s=k20201202 header.b=Ujs0pwVD; 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; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=kernel.org Return-Path: Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id rj2-20020a17090b3e8200b002503ba697e9si1833294pjb.131.2023.05.05.20.17.33; Fri, 05 May 2023 20:17:46 -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; dkim=pass header.i=@kernel.org header.s=k20201202 header.b=Ujs0pwVD; 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; dmarc=pass (p=NONE sp=NONE dis=NONE) header.from=kernel.org Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230301AbjEFDI5 (ORCPT + 99 others); Fri, 5 May 2023 23:08:57 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:34236 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229602AbjEFDIy (ORCPT ); Fri, 5 May 2023 23:08:54 -0400 Received: from dfw.source.kernel.org (dfw.source.kernel.org [IPv6:2604:1380:4641:c500::1]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id 06C00E7D; Fri, 5 May 2023 20:08:49 -0700 (PDT) Received: from smtp.kernel.org (relay.kernel.org [52.25.139.140]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by dfw.source.kernel.org (Postfix) with ESMTPS id 5A8BA6135B; Sat, 6 May 2023 03:08:48 +0000 (UTC) Received: by smtp.kernel.org (Postfix) with ESMTPSA id EAB7CC4339C; Sat, 6 May 2023 03:08:46 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1683342527; bh=dGCgPIxsqfTmRReXWzxIpkTa6lrCo7wDw2fuNYKCYPg=; h=References:In-Reply-To:From:Date:Subject:To:Cc:From; b=Ujs0pwVDwyq7wvDvnl1goq7PKZgpm0c9vxhn8lbF0qUFn7/n77FQ9rDACFHnyXY1w xEJRcqTvzWiFuPhdmOoRVBBwATpd0hlgr9WTvBQJ81hwM0oYcBJoWBqAC/zDmSrnnl WIiqUd2oi8zyqtQil5keCaJ+rI2xe+vGgxiZh0AnZvW9h17mcmplnnloOCNlLgFcrn MkOZeW5xRjdh3sdL7/+N6WvKCwVYR02d/dk178DjLK1NMAzQAB8VS7dSU2hQ9vX08V gbVb7aPeFgMhOCqU+FnSSFdRUCeFzNJPyTpuxL4NF2OK+G1t4pGxoxcFNeJoRIKV7C F0bHe84HX2CKQ== Received: by mail-ed1-f43.google.com with SMTP id 4fb4d7f45d1cf-50be17a1eceso4806256a12.2; Fri, 05 May 2023 20:08:46 -0700 (PDT) X-Gm-Message-State: AC+VfDzamDhmwQ3q7VXMQW3tkIqi17E08zqaE8E6UrHs+8FV1qlRFrsQ rkggmzob49GtpoGikW17NOzW9gAywfaBJKv/spw= X-Received: by 2002:a17:907:6d0b:b0:965:5cba:4a16 with SMTP id sa11-20020a1709076d0b00b009655cba4a16mr3042550ejc.77.1683342524695; Fri, 05 May 2023 20:08:44 -0700 (PDT) MIME-Version: 1.0 References: <20230504080406.1213623-1-suijingfeng@loongson.cn> <20230504080406.1213623-3-suijingfeng@loongson.cn> In-Reply-To: <20230504080406.1213623-3-suijingfeng@loongson.cn> From: Huacai Chen Date: Sat, 6 May 2023 11:08:32 +0800 X-Gmail-Original-Message-ID: Message-ID: Subject: Re: [PATCH v12 2/2] drm: add kms driver for loongson display controller To: Sui Jingfeng 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 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable X-Spam-Status: No, score=-4.6 required=5.0 tests=BAYES_00,DKIMWL_WL_HIGH, DKIM_SIGNED,DKIM_VALID,DKIM_VALID_AU,DKIM_VALID_EF,RCVD_IN_DNSWL_MED, SPF_HELO_NONE,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, Jingfeng, On Thu, May 4, 2023 at 4:04=E2=80=AFPM Sui Jingfeng wrote: > > Loongson display controller IP has been integrated in both Loongson north > bridge chipset(ls7a1000/ls7a2000) and Loongson SoCs(ls2k1000/ls2k2000), i= t > has been even included in Loongson self-made BMC products. > > This display controller is a PCI device. It has two display pipes and eac= h > display pipe support a primary plane and a cursor plane. For the DC in th= e > 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 resolutio= n > 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 HDM= I > encoder which is compliant with the HDMI 1.4 specification, thus it suppo= rt > 3840x2160@30Hz. The first display pipe is also equipped with a transparen= t > vga encoder which is parallel with the HDMI encoder. The DC in LS7A2000 i= s > more complete compare with the one in old chips, besides above feature, i= t > 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=3D1 > > 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 =3D 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=3D1 > 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 VRA= M > BOs once when suspend, which is not correct on double screen case. V1= 1 > of this patch unpin BOs until its pin count reach to zero when suspen= d. > 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 +=3D gud/ > obj-$(CONFIG_DRM_HYPERV) +=3D hyperv/ > obj-y +=3D solomon/ > obj-$(CONFIG_DRM_SPRD) +=3D sprd/ > +obj-$(CONFIG_DRM_LOONGSON) +=3D 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 :=3D \ > + 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 +=3D ls7a1000_outputs.o ls7a2000_outputs.o > + > +obj-$(CONFIG_DRM_LOONGSON) +=3D loongson.o > diff --git a/drivers/gpu/drm/loongson/ls7a1000_outputs.c b/drivers/gpu/dr= m/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". > + */ > + > +#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 *co= nn) > +{ > + unsigned int num =3D 0; > + struct edid *edid; > + > + if (conn->ddc) { > + edid =3D drm_get_edid(conn, conn->ddc); > + if (edid) { > + drm_connector_update_edid_property(conn, edid); > + num =3D drm_add_edid_modes(conn, edid); > + kfree(edid); > + } > + > + return num; > + } > + > + num =3D 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 *connec= tor, > + struct drm_atomic_state *stat= e) > +{ > + struct lsdc_display_pipe *pipe =3D connector_to_display_pipe(conn= ector); > + > + return &pipe->encoder; > +} > + > +static const struct drm_connector_helper_funcs > +ls7a1000_generic_connector_helpers =3D { > + .atomic_best_encoder =3D ls7a1000_generic_connector_get_best_enco= der, > + .get_modes =3D ls7a1000_generic_connector_get_modes, > +}; > + > +static enum drm_connector_status > +ls7a1000_generic_connector_detect(struct drm_connector *connector, bool = force) > +{ > + struct i2c_adapter *ddc =3D 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= =3D { > + .detect =3D ls7a1000_generic_connector_detect, > + .fill_modes =3D drm_helper_probe_single_connector_modes, > + .destroy =3D drm_connector_cleanup, > + .reset =3D drm_atomic_helper_connector_reset, > + .atomic_duplicate_state =3D drm_atomic_helper_connector_duplicate= _state, > + .atomic_destroy_state =3D drm_atomic_helper_connector_destroy_sta= te > +}; > + > +static void ls7a1000_pipe0_encoder_reset(struct drm_encoder *encoder) > +{ > + struct drm_device *ddev =3D encoder->dev; > + struct lsdc_device *ldev =3D 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 =3D encoder->dev; > + struct lsdc_device *ldev =3D 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] =3D { > + { > + .reset =3D ls7a1000_pipe0_encoder_reset, > + .destroy =3D drm_encoder_cleanup, > + }, > + { > + .reset =3D ls7a1000_pipe1_encoder_reset, > + .destroy =3D 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 =3D &dispipe->encoder; > + struct drm_connector *connector =3D &dispipe->connector; > + int ret; > + > + ret =3D drm_encoder_init(ddev, > + encoder, > + &ls7a1000_encoder_funcs[pipe], > + DRM_MODE_ENCODER_TMDS, > + "encoder-%u", > + dispipe->index); > + if (ret) > + return ret; > + > + encoder->possible_crtcs =3D BIT(pipe); > + > + ret =3D drm_connector_init_with_ddc(ddev, > + connector, > + &ls7a1000_generic_connector_fun= cs, > + 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_h= elpers); > + > + drm_connector_attach_encoder(connector, encoder); > + > + connector->polled =3D DRM_CONNECTOR_POLL_CONNECT | > + DRM_CONNECTOR_POLL_DISCONNECT; > + > + connector->interlace_allowed =3D 0; > + connector->doublescan_allowed =3D 0; > + > + return 0; > +} > diff --git a/drivers/gpu/drm/loongson/ls7a2000_outputs.c b/drivers/gpu/dr= m/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 =3D 0; > + struct edid *edid; > + > + if (connector->ddc) { > + edid =3D drm_get_edid(connector, connector->ddc); > + if (edid) { > + drm_connector_update_edid_property(connector, edi= d); > + num =3D drm_add_edid_modes(connector, edid); > + kfree(edid); > + } > + > + return num; > + } > + > + num =3D 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 =3D connector_to_display_pipe(connector); > + > + return &dispipe->encoder; > +} > + > +static const struct drm_connector_helper_funcs ls7a2000_connector_helper= s =3D { > + .atomic_best_encoder =3D ls7a2000_connector_get_best_encoder, > + .get_modes =3D ls7a2000_connector_get_modes, > +}; > + > +/* debugfs */ > + > +#define LSDC_HDMI_REG(i, reg) { \ > + .name =3D __stringify_1(LSDC_HDMI##i##_##reg##_REG), \ > + .offset =3D LSDC_HDMI##i##_##reg##_REG, \ > +} > + > +static const struct lsdc_reg32 ls7a2000_hdmi0_encoder_regs[] =3D { > + 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[] =3D { > + 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 *dat= a) > +{ > + struct drm_info_node *node =3D (struct drm_info_node *)m->private= ; > + struct drm_device *ddev =3D node->minor->dev; > + struct lsdc_device *ldev =3D to_lsdc(ddev); > + const struct lsdc_reg32 *preg; > + > + preg =3D (const struct lsdc_reg32 *)node->info_ent->data; > + > + while (preg->name) { > + u32 offset =3D 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[] =3D { > + { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hd= mi0_encoder_regs }, > +}; > + > +static const struct drm_info_list ls7a2000_hdmi1_debugfs_files[] =3D { > + { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hd= mi1_encoder_regs }, > +}; > + > +static void ls7a2000_hdmi0_late_register(struct drm_connector *connector= , > + struct dentry *root) > +{ > + struct drm_device *ddev =3D connector->dev; > + struct drm_minor *minor =3D 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. > +} > + > +static void ls7a2000_hdmi1_late_register(struct drm_connector *connector= , > + struct dentry *root) > +{ > + struct drm_device *ddev =3D connector->dev; > + struct drm_minor *minor =3D 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, boo= l force) > +{ > + struct drm_device *ddev =3D connector->dev; > + struct lsdc_device *ldev =3D to_lsdc(ddev); > + u32 val; > + > + val =3D 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 fo= rce) > +{ > + struct lsdc_device *ldev =3D to_lsdc(connector->dev); > + u32 val; > + > + val =3D 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]= =3D { > + { > + .detect =3D ls7a2000_hdmi0_vga_connector_detect, > + .fill_modes =3D drm_helper_probe_single_connector_modes, > + .destroy =3D drm_connector_cleanup, > + .reset =3D drm_atomic_helper_connector_reset, > + .atomic_duplicate_state =3D drm_atomic_helper_connector_d= uplicate_state, > + .atomic_destroy_state =3D drm_atomic_helper_connector_des= troy_state, > + .debugfs_init =3D ls7a2000_hdmi0_late_register, > + }, > + { > + .detect =3D ls7a2000_hdmi1_connector_detect, > + .fill_modes =3D drm_helper_probe_single_connector_modes, > + .destroy =3D drm_connector_cleanup, > + .reset =3D drm_atomic_helper_connector_reset, > + .atomic_duplicate_state =3D drm_atomic_helper_connector_d= uplicate_state, > + .atomic_destroy_state =3D drm_atomic_helper_connector_des= troy_state, > + .debugfs_init =3D 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 =3D encoder->dev; > + struct lsdc_device *ldev =3D to_lsdc(ddev); > + u32 val; > + > + val =3D PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN; > + lsdc_wreg32(ldev, LSDC_CRTC0_PANEL_CONF_REG, val); > + > + /* using software gpio emulated i2c */ > + val =3D lsdc_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG); > + val &=3D ~HW_I2C_EN; > + val |=3D HDMI_INTERFACE_EN | HDMI_PACKET_EN; > + lsdc_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, val); > + > + /* get out of reset state */ > + val |=3D 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 =3D encoder->dev; > + struct lsdc_device *ldev =3D to_lsdc(ddev); > + u32 val; > + > + val =3D PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN; > + lsdc_wreg32(ldev, LSDC_CRTC1_PANEL_CONF_REG, val); > + > + /* using software gpio emulated i2c */ > + val =3D lsdc_rreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG); > + val &=3D ~HW_I2C_EN; > + val |=3D HDMI_INTERFACE_EN | HDMI_PACKET_EN; > + lsdc_wreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG, val); > + > + /* reset */ > + > + /* get out of reset state */ > + val =3D 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] =3D { > + { > + .reset =3D ls7a2000_hdmi0_encoder_reset, > + .destroy =3D drm_encoder_cleanup, > + }, > + { > + .reset =3D ls7a2000_hdmi1_encoder_reset, > + .destroy =3D drm_encoder_cleanup, > + } > +}; > + > +static int ls7a2000_hdmi_set_avi_infoframe(struct drm_encoder *encoder, > + struct drm_display_mode *mode) > +{ > + struct lsdc_display_pipe *dispipe =3D encoder_to_display_pipe(enc= oder); > + struct drm_device *ddev =3D encoder->dev; > + struct lsdc_device *ldev =3D to_lsdc(ddev); > + unsigned int index =3D dispipe->index; > + struct hdmi_avi_infoframe infoframe; > + u8 buffer[HDMI_INFOFRAME_SIZE(AVI)]; > + unsigned char *ptr =3D &buffer[HDMI_INFOFRAME_HEADER_SIZE]; > + unsigned int content0, content1, content2, content3; > + int err; > + > + err =3D drm_hdmi_avi_infoframe_from_display_mode(&infoframe, > + &dispipe->connecto= r, > + 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 =3D HDMI_COLORSPACE_RGB; > + infoframe.quantization_range =3D HDMI_QUANTIZATION_RANGE_DEFAULT; > + infoframe.colorimetry =3D HDMI_COLORIMETRY_NONE; > + > + err =3D 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 =3D *(unsigned int *)ptr; > + content1 =3D *(ptr + 4); > + content2 =3D *(unsigned int *)(ptr + 5); > + content3 =3D *(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 =3D encoder_to_display_pipe(enc= oder); > + struct drm_device *ddev =3D encoder->dev; > + struct lsdc_device *ldev =3D to_lsdc(ddev); > + unsigned int index =3D dispipe->index; > + u32 val; > + > + /* Disable the hdmi phy */ > + val =3D lsdc_hdmi_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index); > + val &=3D ~HDMI_PHY_EN; > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val); > + > + /* Disable the hdmi interface */ > + val =3D lsdc_hdmi_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index); > + val &=3D ~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 =3D encoder->dev; > + struct lsdc_device *ldev =3D to_lsdc(ddev); > + struct lsdc_display_pipe *dispipe =3D encoder_to_display_pipe(enc= oder); > + unsigned int index =3D dispipe->index; > + u32 val; > + > + /* datasheet say it should larger than 48 */ > + val =3D 64 << HDMI_H_ZONE_IDLE_SHIFT | 64 << HDMI_V_ZONE_IDLE_SHI= FT; > + > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_ZONE_REG, index, val); > + > + val =3D 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 =3D 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 =3D M * Fin > + * > + * M =3D (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 =3D &ldev->base; > + int count =3D 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 =3D 10 > + * for example, 10 =3D (4 * 40) / (8 * 2) > + * here, write "1" to the ODF will get "2" > + */ > + > + if (fin >=3D 170000) > + val =3D (16 << HDMI_PLL_IDF_SHIFT) | > + (40 << HDMI_PLL_LF_SHIFT) | > + (0 << HDMI_PLL_ODF_SHIFT); > + else if (fin >=3D 85000) > + val =3D (8 << HDMI_PLL_IDF_SHIFT) | > + (40 << HDMI_PLL_LF_SHIFT) | > + (1 << HDMI_PLL_ODF_SHIFT); > + else if (fin >=3D 42500) > + val =3D (4 << HDMI_PLL_IDF_SHIFT) | > + (40 << HDMI_PLL_LF_SHIFT) | > + (2 << HDMI_PLL_ODF_SHIFT); > + else if (fin >=3D 21250) > + val =3D (2 << HDMI_PLL_IDF_SHIFT) | > + (40 << HDMI_PLL_LF_SHIFT) | > + (3 << HDMI_PLL_ODF_SHIFT); > + else > + val =3D (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 |=3D 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 =3D lsdc_hdmi_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, in= dex); > + > + 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 >=3D 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_sta= te, > + struct drm_connector_state *con= n_state) > +{ > + struct lsdc_display_pipe *dispipe =3D encoder_to_display_pipe(enc= oder); > + struct drm_device *ddev =3D encoder->dev; > + struct lsdc_device *ldev =3D to_lsdc(ddev); > + struct drm_display_mode *mode =3D &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_fun= cs =3D { > + .atomic_disable =3D ls7a2000_hdmi_atomic_disable, > + .atomic_enable =3D ls7a2000_hdmi_atomic_enable, > + .atomic_mode_set =3D 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 =3D &dispipe->encoder; > + struct drm_connector *connector =3D &dispipe->connector; > + int ret; > + > + ret =3D 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 =3D BIT(pipe); > + > + drm_encoder_helper_add(encoder, &ls7a2000_encoder_helper_funcs); > + > + ret =3D 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 =3D DRM_CONNECTOR_POLL_CONNECT | > + DRM_CONNECTOR_POLL_DISCONNECT; > + > + connector->interlace_allowed =3D 0; > + connector->doublescan_allowed =3D 0; > + > + return 0; > +} > diff --git a/drivers/gpu/drm/loongson/lsdc_crtc.c b/drivers/gpu/drm/loong= son/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 addres= s > + * and other settings in the crtc register remains. > + */ > + > +static void lsdc_crtc0_soft_reset(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev =3D lcrtc->ldev; > + u32 val; > + > + val =3D lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + > + val &=3D CFG_VALID_BITS_MASK; > + > + /* soft reset bit, active low */ > + val &=3D ~CFG_RESET_N; > + > + val &=3D ~CFG_PIX_FMT_MASK; > + > + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); > + > + udelay(5); > + > + val |=3D 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 =3D lcrtc->ldev; > + u32 val; > + > + val =3D lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); > + > + val &=3D CFG_VALID_BITS_MASK; > + > + /* soft reset bit, active low */ > + val &=3D ~CFG_RESET_N; > + > + val &=3D ~CFG_PIX_FMT_MASK; > + > + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); > + > + udelay(5); > + > + val |=3D 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 =3D lcrtc->ldev; > + u32 val; > + > + val =3D lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + > + /* > + * If bit 24 of LSDC_CRTC0_CFG_REG is set, it say that the DC har= dware > + * 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 =3D 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 =3D lcrtc->ldev; > + u32 val; > + > + val =3D 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 =3D 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 =3D lcrtc->ldev; > + u32 val; > + > + val =3D lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); > + > + *hpos =3D val >> 16; > + *vpos =3D val & 0xffff; > +} > + > +static void lsdc_crtc1_scan_pos(struct lsdc_crtc *lcrtc, int *hpos, int = *vpos) > +{ > + struct lsdc_device *ldev =3D lcrtc->ldev; > + u32 val; > + > + val =3D lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); > + > + *hpos =3D val >> 16; > + *vpos =3D val & 0xffff; > +} > + > +static void lsdc_crtc0_enable_vblank(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev =3D 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 =3D 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 =3D 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 =3D 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 =3D 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 =3D 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 mo= de. > + * > + * This may useful on custom clone application. > + */ > + > +static void lsdc_crtc0_clone(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev =3D 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 =3D 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 =3D 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 =3D 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=3D0 or 1)is filled w= ith > + * garbarge value which may cause the CRTC completely hang. This functio= n > + * give a minimal setting to the affected registers. This also override > + * the firmware's setting on startup, eliminate potential blinding setti= ng. > + * > + * Making the CRTC works on our own now, this is similar with the functi= onal > + * of GPU POST(Power On Self Test). Only touch CRTC hardware related par= t. > + */ > + > +static void lsdc_crtc0_reset(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev =3D lcrtc->ldev; > + struct drm_crtc *crtc =3D &lcrtc->base; > + u32 val; > + > + val =3D 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 compl= etely > + * halt on soft reset mode (BIT(20) =3D 0). It does not event gen= erate > + * vblank, cause vblank wait timeout. This happends when resume f= rom > + * S3. > + * > + * Also give a sane format, after resume from suspend S3, this > + * register is filled with garbarge value. A meaningless value ma= y > + * also cause the CRTC halt or crash. > + */ > + > + val =3D 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 =3D lcrtc->ldev; > + struct drm_crtc *crtc =3D &lcrtc->base; > + u32 val; > + > + val =3D 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 =3D 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] =3D { > + { > + .enable =3D lsdc_crtc0_enable, > + .disable =3D lsdc_crtc0_disable, > + .enable_vblank =3D lsdc_crtc0_enable_vblank, > + .disable_vblank =3D lsdc_crtc0_disable_vblank, > + .flip =3D lsdc_crtc0_flip, > + .clone =3D lsdc_crtc0_clone, > + .set_mode =3D lsdc_crtc0_set_mode, > + .get_scan_pos =3D lsdc_crtc0_scan_pos, > + .soft_reset =3D lsdc_crtc0_soft_reset, > + .reset =3D lsdc_crtc0_reset, > + }, > + { > + .enable =3D lsdc_crtc1_enable, > + .disable =3D lsdc_crtc1_disable, > + .enable_vblank =3D lsdc_crtc1_enable_vblank, > + .disable_vblank =3D lsdc_crtc1_disable_vblank, > + .flip =3D lsdc_crtc1_flip, > + .clone =3D lsdc_crtc1_clone, > + .set_mode =3D lsdc_crtc1_set_mode, > + .get_scan_pos =3D lsdc_crtc1_scan_pos, > + .soft_reset =3D lsdc_crtc1_soft_reset, > + .reset =3D lsdc_crtc1_reset, > + }, > +}; > + > +/* > + * The 32-bit hardware vblank counter is available since ls7a2000/ls2k20= 00, > + * The counter grow up even the CRTC is being disabled, it will got rese= t > + * if the crtc is being soft reset. > + * > + * Those registers are also readable for ls7a1000, but its value does no= t > + * change. > + */ > + > +static u32 lsdc_crtc0_get_vblank_count(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev =3D 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 =3D lcrtc->ldev; > + > + return lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG); > +} > + > +/* > + * The DMA step register is available since ls7a2000/ls2k2000, for suppo= rt > + * 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 =3D lcrtc->ldev; > + u32 val =3D lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + > + val &=3D ~CFG_DMA_STEP_MASK; > + val |=3D 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 =3D lcrtc->ldev; > + u32 val =3D lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); > + > + val &=3D ~CFG_DMA_STEP_MASK; > + val |=3D 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] =3D { > + { > + .enable =3D lsdc_crtc0_enable, > + .disable =3D lsdc_crtc0_disable, > + .enable_vblank =3D lsdc_crtc0_enable_vblank, > + .disable_vblank =3D lsdc_crtc0_disable_vblank, > + .flip =3D lsdc_crtc0_flip, > + .clone =3D lsdc_crtc0_clone, > + .set_mode =3D lsdc_crtc0_set_mode, > + .soft_reset =3D lsdc_crtc0_soft_reset, > + .get_scan_pos =3D lsdc_crtc0_scan_pos, > + .set_dma_step =3D lsdc_crtc0_set_dma_step, > + .get_vblank_counter =3D lsdc_crtc0_get_vblank_count, > + .reset =3D lsdc_crtc0_reset, > + }, > + { > + .enable =3D lsdc_crtc1_enable, > + .disable =3D lsdc_crtc1_disable, > + .enable_vblank =3D lsdc_crtc1_enable_vblank, > + .disable_vblank =3D lsdc_crtc1_disable_vblank, > + .flip =3D lsdc_crtc1_flip, > + .clone =3D lsdc_crtc1_clone, > + .set_mode =3D lsdc_crtc1_set_mode, > + .get_scan_pos =3D lsdc_crtc1_scan_pos, > + .soft_reset =3D lsdc_crtc1_soft_reset, > + .set_dma_step =3D lsdc_crtc1_set_dma_step, > + .get_vblank_counter =3D lsdc_crtc1_get_vblank_count, > + .reset =3D lsdc_crtc1_reset, > + }, > +}; > + > +static void lsdc_crtc_reset(struct drm_crtc *crtc) > +{ > + struct lsdc_crtc *lcrtc =3D to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops =3D lcrtc->hw_ops; > + struct lsdc_crtc_state *priv_crtc_state; > + > + if (crtc->state) > + crtc->funcs->atomic_destroy_state(crtc, crtc->state); > + > + priv_crtc_state =3D 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->ba= se); > + > + /* > + * 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 =3D 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 =3D kzalloc(sizeof(*new_priv_state), GFP_KERNEL); > + if (!new_priv_state) > + return NULL; > + > + __drm_atomic_helper_crtc_duplicate_state(crtc, &new_priv_state->b= ase); > + > + old_priv_state =3D 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 =3D to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops =3D lcrtc->hw_ops; > + > + return ops->get_vblank_counter(lcrtc); > +} > + > +static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc) > +{ > + struct lsdc_crtc *lcrtc =3D to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops =3D lcrtc->hw_ops; > + > + ops->enable_vblank(lcrtc); > + > + return 0; > +} > + > +static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc) > +{ > + struct lsdc_crtc *lcrtc =3D to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops =3D 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 =3D __stringify_1(LSDC_##reg##_REG), .offse= t =3D LSDC_##reg##_REG } > + > +static const struct lsdc_reg32 lsdc_crtc_regs_array[2][21] =3D { > + [0] =3D { > + 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] =3D { > + 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 =3D (struct drm_info_node *)m->private= ; > + struct lsdc_crtc *lcrtc =3D (struct lsdc_crtc *)node->info_ent->d= ata; > + struct lsdc_device *ldev =3D lcrtc->ldev; > + unsigned int i; > + > + for (i =3D 0; i < lcrtc->nreg; i++) { > + const struct lsdc_reg32 *preg =3D &lcrtc->preg[i]; > + u32 offset =3D 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 =3D (struct drm_info_node *)m->private= ; > + struct lsdc_crtc *lcrtc =3D (struct lsdc_crtc *)node->info_ent->d= ata; > + const struct lsdc_crtc_hw_ops *ops =3D 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 =3D (struct drm_info_node *)m->private= ; > + struct lsdc_crtc *lcrtc =3D (struct lsdc_crtc *)node->info_ent->d= ata; > + const struct lsdc_crtc_hw_ops *ops =3D 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 =3D (struct drm_info_node *)m->private= ; > + struct lsdc_crtc *lcrtc =3D (struct lsdc_crtc *)node->info_ent->d= ata; > + struct lsdc_pixpll *pixpll =3D &lcrtc->pixpll; > + const struct lsdc_pixpll_funcs *funcs =3D pixpll->funcs; > + struct drm_crtc *crtc =3D &lcrtc->base; > + struct drm_display_mode *mode =3D &crtc->state->mode; > + struct drm_printer printer =3D drm_seq_file_printer(m); > + unsigned int out_khz; > + > + out_khz =3D 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] =3D { > + [0] =3D { > + { "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] =3D { > + { "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 =3D 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 =3D file->private_data; > + struct drm_crtc *crtc =3D m->private; > + struct lsdc_crtc *lcrtc =3D to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops =3D lcrtc->hw_ops; > + > + char buf[16]; > + > + if (len > sizeof(buf) - 1) > + return -EINVAL; > + > + if (copy_from_user(buf, ubuf, len)) > + return -EFAULT; > + > + buf[len] =3D '\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 =3D { > + .owner =3D THIS_MODULE, > + .open =3D lsdc_crtc_man_op_open, > + .read =3D seq_read, > + .llseek =3D seq_lseek, > + .release =3D single_release, > + .write =3D lsdc_crtc_man_op_write, > +}; > + > +static int lsdc_crtc_late_register(struct drm_crtc *crtc) > +{ > + struct lsdc_display_pipe *dispipe =3D crtc_to_display_pipe(crtc); > + struct lsdc_crtc *lcrtc =3D to_lsdc_crtc(crtc); > + struct drm_minor *minor =3D crtc->dev->primary; > + unsigned int index =3D dispipe->index; > + unsigned int i; > + > + lcrtc->preg =3D lsdc_crtc_regs_array[index]; > + lcrtc->nreg =3D ARRAY_SIZE(lsdc_crtc_regs_array[index]); > + lcrtc->p_info_list =3D lsdc_crtc_debugfs_list[index]; > + lcrtc->n_info_list =3D ARRAY_SIZE(lsdc_crtc_debugfs_list[index]); > + > + for (i =3D 0; i < lcrtc->n_info_list; ++i) > + lcrtc->p_info_list[i].data =3D 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 *sta= te) > +{ > + const struct lsdc_crtc_state *priv_state; > + const struct lsdc_pixpll_parms *pparms; > + > + priv_state =3D container_of_const(state, struct lsdc_crtc_state, = base); > + pparms =3D &priv_state->pparms; > + > + drm_printf(p, "\tInput clock divider =3D %u\n", pparms->div_ref); > + drm_printf(p, "\tMedium clock Multiplier =3D %u\n", pparms->loopc= ); > + drm_printf(p, "\tOutput clock divider =3D %u\n", pparms->div_out)= ; > +} > + > +static const struct drm_crtc_funcs ls7a1000_crtc_funcs =3D { > + .reset =3D lsdc_crtc_reset, > + .destroy =3D drm_crtc_cleanup, > + .set_config =3D drm_atomic_helper_set_config, > + .page_flip =3D drm_atomic_helper_page_flip, > + .atomic_duplicate_state =3D lsdc_crtc_atomic_duplicate_state, > + .atomic_destroy_state =3D lsdc_crtc_atomic_destroy_state, > + .late_register =3D lsdc_crtc_late_register, > + .enable_vblank =3D lsdc_crtc_enable_vblank, > + .disable_vblank =3D lsdc_crtc_disable_vblank, > + .get_vblank_timestamp =3D drm_crtc_vblank_helper_get_vblank_times= tamp, > + .atomic_print_state =3D lsdc_crtc_atomic_print_state, > +}; > + > +static const struct drm_crtc_funcs ls7a2000_crtc_funcs =3D { > + .reset =3D lsdc_crtc_reset, > + .destroy =3D drm_crtc_cleanup, > + .set_config =3D drm_atomic_helper_set_config, > + .page_flip =3D drm_atomic_helper_page_flip, > + .atomic_duplicate_state =3D lsdc_crtc_atomic_duplicate_state, > + .atomic_destroy_state =3D lsdc_crtc_atomic_destroy_state, > + .late_register =3D lsdc_crtc_late_register, > + .get_vblank_counter =3D lsdc_crtc_get_vblank_counter, > + .enable_vblank =3D lsdc_crtc_enable_vblank, > + .disable_vblank =3D lsdc_crtc_disable_vblank, > + .get_vblank_timestamp =3D drm_crtc_vblank_helper_get_vblank_times= tamp, > + .atomic_print_state =3D lsdc_crtc_atomic_print_state, > +}; > + > +static enum drm_mode_status > +lsdc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mod= e *mode) > +{ > + struct drm_device *ddev =3D crtc->dev; > + struct lsdc_device *ldev =3D to_lsdc(ddev); > + const struct lsdc_desc *descp =3D 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=3D%d is too hi= gh\n", > + mode->hdisplay, mode->vdisplay, mode->clock); > + return MODE_CLOCK_HIGH; > + } > + > + /* 4 for DRM_FORMAT_XRGB8888 */ > + pitch =3D 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 =3D to_lsdc_crtc(crtc); > + struct lsdc_pixpll *pixpll =3D &lcrtc->pixpll; > + const struct lsdc_pixpll_funcs *pfuncs =3D pixpll->funcs; > + struct lsdc_crtc_state *priv_state =3D to_lsdc_crtc_state(state); > + unsigned int clock =3D state->mode.clock; > + int ret; > + > + ret =3D 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 =3D drm_atomic_get_new_crtc_sta= te(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 =3D to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *crtc_hw_ops =3D lcrtc->hw_ops; > + struct lsdc_pixpll *pixpll =3D &lcrtc->pixpll; > + const struct lsdc_pixpll_funcs *pixpll_funcs =3D pixpll->funcs; > + struct drm_crtc_state *state =3D crtc->state; > + struct drm_display_mode *mode =3D &state->mode; > + struct lsdc_crtc_state *priv_state =3D to_lsdc_crtc_state(state); > + > + pixpll_funcs->update(pixpll, &priv_state->pparms); > + > + if (crtc_hw_ops->set_dma_step) { > + unsigned int width_in_bytes =3D mode->hdisplay * 4; > + enum lsdc_dma_steps dma_step; > + > + /* > + * Using large dma step as much as possible, for improvin= g > + * hardware DMA efficiency. > + */ > + if (width_in_bytes % 256 =3D=3D 0) > + dma_step =3D LSDC_DMA_STEP_256_BYTES; > + else if (width_in_bytes % 128 =3D=3D 0) > + dma_step =3D LSDC_DMA_STEP_128_BYTES; > + else if (width_in_bytes % 64 =3D=3D 0) > + dma_step =3D LSDC_DMA_STEP_64_BYTES; > + else /* width_in_bytes % 32 =3D=3D 0 */ > + dma_step =3D 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 =3D 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 =3D 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 =3D to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops =3D 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 =3D to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops =3D 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) =3D=3D 0) > + drm_crtc_arm_vblank_event(crtc, crtc->state->even= t); > + else > + drm_crtc_send_vblank_event(crtc, crtc->state->eve= nt); > + crtc->state->event =3D 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 =3D to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops =3D lcrtc->hw_ops; > + int vsw, vbp, vactive_start, vactive_end, vfp_end; > + int x, y; > + > + vsw =3D mode->crtc_vsync_end - mode->crtc_vsync_start; > + vbp =3D mode->crtc_vtotal - mode->crtc_vsync_end; > + > + vactive_start =3D vsw + vbp + 1; > + vactive_end =3D vactive_start + mode->crtc_vdisplay; > + > + /* last scan line before VSYNC */ > + vfp_end =3D mode->crtc_vtotal; > + > + if (stime) > + *stime =3D ktime_get(); > + > + ops->get_scan_pos(lcrtc, &x, &y); > + > + if (y > vactive_end) > + y =3D y - vfp_end - vactive_start; > + else > + y -=3D vactive_start; > + > + *vpos =3D y; > + *hpos =3D x; > + > + if (etime) > + *etime =3D ktime_get(); > + > + return true; > +} > + > +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs =3D { > + .mode_valid =3D lsdc_crtc_mode_valid, > + .mode_set_nofb =3D lsdc_crtc_mode_set_nofb, > + .atomic_enable =3D lsdc_crtc_atomic_enable, > + .atomic_disable =3D lsdc_crtc_atomic_disable, > + .atomic_check =3D lsdc_crtc_helper_atomic_check, > + .atomic_flush =3D lsdc_crtc_atomic_flush, > + .get_scanout_position =3D 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 =3D to_lsdc_crtc(crtc); > + int ret; > + > + ret =3D lsdc_pixpll_init(&lcrtc->pixpll, ddev, index); > + if (ret) { > + drm_err(ddev, "crtc init with pll failed: %d\n", ret); > + return ret; > + } > + > + lcrtc->ldev =3D to_lsdc(ddev); > + lcrtc->hw_ops =3D &ls7a1000_crtc_hw_ops[index]; > + > + ret =3D 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 =3D 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 =3D to_lsdc_crtc(crtc); > + int ret; > + > + ret =3D lsdc_pixpll_init(&lcrtc->pixpll, ddev, index); > + if (ret) { > + drm_err(ddev, "crtc init with pll failed: %d\n", ret); > + return ret; > + } > + > + lcrtc->ldev =3D to_lsdc(ddev); > + lcrtc->hw_ops =3D &ls7a2000_crtc_hw_ops[index]; > + > + ret =3D 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 =3D 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/lo= ongson/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 =3D (struct drm_info_node *)m->private= ; > + struct lsdc_device *ldev =3D (struct lsdc_device *)node->info_ent= ->data; > + const struct loongson_gfx_desc *gfx =3D to_loongson_gfx(ldev->des= cp); > + 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 =3D (struct drm_info_node *)m->private= ; > + struct drm_device *ddev =3D node->minor->dev; > + struct drm_printer p =3D 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 =3D (struct drm_info_node *)m->private= ; > + struct lsdc_device *ldev =3D (struct lsdc_device *)node->info_ent= ->data; > + struct drm_printer printer =3D drm_seq_file_printer(m); > + struct loongson_gfxpll *gfxpll =3D ldev->gfxpll; > + > + gfxpll->funcs->print(gfxpll, &printer, true); > + > + return 0; > +} > + > +static struct drm_info_list lsdc_debugfs_list[] =3D { > + { "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 =3D minor->dev; > + struct lsdc_device *ldev =3D to_lsdc(ddev); > + unsigned int N =3D ARRAY_SIZE(lsdc_debugfs_list); > + unsigned int i; > + > + for (i =3D 0; i < N; ++i) > + lsdc_debugfs_list[i].data =3D 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/loo= ngson/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 =3D { > + .create_i2c =3D lsdc_create_i2c_chan, > + .irq_handler =3D ls7a1000_dc_irq_handler, > + .output_init =3D ls7a1000_output_init, > + .cursor_plane_init =3D ls7a1000_cursor_plane_init, > + .primary_plane_init =3D lsdc_primary_plane_init, > + .crtc_init =3D ls7a1000_crtc_init, > +}; > + > +static const struct lsdc_kms_funcs ls7a2000_kms_funcs =3D { > + .create_i2c =3D lsdc_create_i2c_chan, > + .irq_handler =3D ls7a2000_dc_irq_handler, > + .output_init =3D ls7a2000_output_init, > + .cursor_plane_init =3D ls7a2000_cursor_plane_init, > + .primary_plane_init =3D lsdc_primary_plane_init, > + .crtc_init =3D ls7a2000_crtc_init, > +}; > + > +static const struct loongson_gfx_desc ls7a1000_gfx =3D { > + .dc =3D { > + .num_of_crtc =3D 2, > + .max_pixel_clk =3D 200000, > + .max_width =3D 2048, > + .max_height =3D 2048, > + .num_of_hw_cursor =3D 1, > + .hw_cursor_w =3D 32, > + .hw_cursor_h =3D 32, > + .pitch_align =3D 256, > + .mc_bits =3D 40, > + .has_vblank_counter =3D false, > + .funcs =3D &ls7a1000_kms_funcs, > + }, > + .conf_reg_base =3D LS7A1000_CONF_REG_BASE, > + .gfxpll =3D { > + .reg_offset =3D LS7A1000_PLL_GFX_REG, > + .reg_size =3D 8, > + }, > + .pixpll =3D { > + [0] =3D { > + .reg_offset =3D LS7A1000_PIXPLL0_REG, > + .reg_size =3D 8, > + }, > + [1] =3D { > + .reg_offset =3D LS7A1000_PIXPLL1_REG, > + .reg_size =3D 8, > + }, > + }, > + .chip_id =3D CHIP_LS7A1000, > + .model =3D "LS7A1000 bridge chipset", > +}; > + > +static const struct loongson_gfx_desc ls7a2000_gfx =3D { > + .dc =3D { > + .num_of_crtc =3D 2, > + .max_pixel_clk =3D 350000, > + .max_width =3D 4096, > + .max_height =3D 4096, > + .num_of_hw_cursor =3D 2, > + .hw_cursor_w =3D 64, > + .hw_cursor_h =3D 64, > + .pitch_align =3D 64, > + .mc_bits =3D 40, /* Support 48 but using 40 for backward = compatibility */ > + .has_vblank_counter =3D true, > + .funcs =3D &ls7a2000_kms_funcs, > + }, > + .conf_reg_base =3D LS7A2000_CONF_REG_BASE, > + .gfxpll =3D { > + .reg_offset =3D LS7A2000_PLL_GFX_REG, > + .reg_size =3D 8, > + }, > + .pixpll =3D { > + [0] =3D { > + .reg_offset =3D LS7A2000_PIXPLL0_REG, > + .reg_size =3D 8, > + }, > + [1] =3D { > + .reg_offset =3D LS7A2000_PIXPLL1_REG, > + .reg_size =3D 8, > + }, > + }, > + .chip_id =3D CHIP_LS7A2000, > + .model =3D "LS7A2000 bridge chipset", > +}; > + > +const struct lsdc_desc * > +lsdc_device_probe(struct pci_dev *pdev, enum loongson_chip_id chip_id) > +{ > + if (chip_id =3D=3D CHIP_LS7A1000) > + return &ls7a1000_gfx.dc; > + > + if (chip_id =3D=3D CHIP_LS7A2000) > + return &ls7a2000_gfx.dc; > + > + return ERR_PTR(-ENODEV); > +} > diff --git a/drivers/gpu/drm/loongson/lsdc_drv.c b/drivers/gpu/drm/loongs= on/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