Received: by 2002:a05:6358:9144:b0:117:f937:c515 with SMTP id r4csp505031rwr; Thu, 4 May 2023 06:15:26 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ7ezi6jFoBS9zXNdZR2hLHqVjrv6HnfoaMy5cbX3pqv71bnC85ku995iW3NDliSqOHEETgL X-Received: by 2002:a05:6a00:130a:b0:63a:75a4:b2d4 with SMTP id j10-20020a056a00130a00b0063a75a4b2d4mr2203433pfu.24.1683206125455; Thu, 04 May 2023 06:15:25 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1683206125; cv=none; d=google.com; s=arc-20160816; b=RiYhAZmdkehAw2Unn0ntB/2NQnSfQ/ZRrqZehA/3biADuVIrMQI0g4l6aPofR5bzlz 1MVscPvEROgHt500VaWVfJMo8hkGN9/ElvmMsgRczrHCs8geIBn7/DEsfqR6rTHp84wM KkHxnKmVgrYKh84jOYwn91tDJGNwuucvSLf0O6PJia9dzjYpGjlYvsK/NoYfHxEDthTQ Sa7kL9Dhcz95xSb6IZactTzsv2bROjfn8LIVaTTUtzbMtcF17eF4BuWJnvlqowJNIzmh HM809r4gxhVlxfbAnV3US2bwcPhYKHGbtSMa2Vp88nXgekqkBrwpLr3Jq7J769Yjvog0 0DiA== 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=hTFtAt9UhpohiyKtbOfsV0sG43POOOy8/b+A59bccCs=; b=GwYzJsDmTAcZE+Cz7LDBmhf8N1dVGq3LQsGybTDY0qOtGTDW1NVAL1B+vT75XwuZzF /oix9IfBmBUfFmH03Ye1pD1wTRcZ4e2cru5dMadYLSZQbdFJ47FLpT9Hnv1CzxmrPlE7 qlyY8C4QaJDnKNMVm4PTQwBirvcAC8v4jgZs7NXVfen7GoS6owLMBv6UZ5Xlnr2GvxAi 6anfCIKYTIayGl1SpgruRkFCZg0kZ9tGPNuh/GYZ4hGaQHFRNwPkBdRqniNSHgg3wOgG 81RlS0Ed33VkqHJF7nnKNpyb/bXYF/qzkAmxRGlurV9Q2R0/MZ0CNUCFl/DVzJMknrGc xc3g== 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 b2-20020a621b02000000b0063d3867ecf1si3473990pfb.89.2023.05.04.06.15.13; Thu, 04 May 2023 06:15: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 S230435AbjEDNJG (ORCPT + 99 others); Thu, 4 May 2023 09:09:06 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:48156 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229873AbjEDNJA (ORCPT ); Thu, 4 May 2023 09:09:00 -0400 Received: from 189.cn (ptr.189.cn [183.61.185.101]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id 36582F2; Thu, 4 May 2023 06:08:50 -0700 (PDT) HMM_SOURCE_IP: 10.64.8.31:60512.146472093 HMM_ATTACHE_NUM: 0000 HMM_SOURCE_TYPE: SMTP Received: from clientip-114.242.206.180 (unknown [10.64.8.31]) by 189.cn (HERMES) with SMTP id 45CEA100210; Thu, 4 May 2023 21:08:44 +0800 (CST) Received: from ([114.242.206.180]) by gateway-151646-dep-85667d6c59-lhcrq with ESMTP id 2a392ab896854b408d343068ac8d0256 for maarten.lankhorst@linux.intel.com; Thu, 04 May 2023 21:08:49 CST X-Transaction-ID: 2a392ab896854b408d343068ac8d0256 X-Real-From: 15330273260@189.cn X-Receive-IP: 114.242.206.180 X-MEDUSA-Status: 0 Sender: 15330273260@189.cn Message-ID: <2fb024e7-617b-e3c5-fc01-a626132b1d30@189.cn> Date: Thu, 4 May 2023 21:08:43 +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: [v12,2/2] drm: add kms driver for loongson display controller To: Maarten Lankhorst , Maxime Ripard , Thomas Zimmermann , David Airlie , Daniel Vetter , Sumit Semwal , Christian Koenig , Emil Velikov Cc: 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-3-suijingfeng@loongson.cn> Content-Language: en-US From: Sui Jingfeng <15330273260@189.cn> In-Reply-To: <20230504080406.1213623-3-suijingfeng@loongson.cn> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit 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, Welcome review, thanks. On 2023/5/4 16:04, 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 > + */ > + > +#include > +#include > +#include > + > +#include "lsdc_drv.h" > +#include "lsdc_output.h" > + > +/* > + * Currently, we assume the external encoders connected with the DVO is > + * transparent. Loongson DVO interface can directly drive RGB888 panels. > + * > + * TODO: Add support for non-transparent encoders ... > + */ > + > +static int ls7a1000_generic_connector_get_modes(struct drm_connector *conn) > +{ > + unsigned int num = 0; > + struct edid *edid; > + > + if (conn->ddc) { > + edid = drm_get_edid(conn, conn->ddc); > + if (edid) { > + drm_connector_update_edid_property(conn, edid); > + num = drm_add_edid_modes(conn, edid); > + kfree(edid); > + } > + > + return num; > + } > + > + num = drm_add_modes_noedid(conn, 1920, 1200); > + > + drm_set_preferred_mode(conn, 1024, 768); > + > + return num; > +} > + > +static struct drm_encoder * > +ls7a1000_generic_connector_get_best_encoder(struct drm_connector *connector, > + struct drm_atomic_state *state) > +{ > + struct lsdc_display_pipe *pipe = connector_to_display_pipe(connector); > + > + return &pipe->encoder; > +} > + > +static const struct drm_connector_helper_funcs > +ls7a1000_generic_connector_helpers = { > + .atomic_best_encoder = ls7a1000_generic_connector_get_best_encoder, > + .get_modes = ls7a1000_generic_connector_get_modes, > +}; > + > +static enum drm_connector_status > +ls7a1000_generic_connector_detect(struct drm_connector *connector, bool force) > +{ > + struct i2c_adapter *ddc = connector->ddc; > + > + if (ddc) { > + if (drm_probe_ddc(ddc)) > + return connector_status_connected; > + > + return connector_status_disconnected; > + } > + > + return connector_status_unknown; > +} > + > +static const struct drm_connector_funcs ls7a1000_generic_connector_funcs = { > + .detect = ls7a1000_generic_connector_detect, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .destroy = drm_connector_cleanup, > + .reset = drm_atomic_helper_connector_reset, > + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state > +}; > + > +static void ls7a1000_pipe0_encoder_reset(struct drm_encoder *encoder) > +{ > + struct drm_device *ddev = encoder->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + /* > + * We need this, for S3 resume, screen will not lightup if don't set > + * correctly. > + */ > + lsdc_wreg32(ldev, LSDC_CRTC0_PANEL_CONF_REG, > + PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN); > +} > + > +static void ls7a1000_pipe1_encoder_reset(struct drm_encoder *encoder) > +{ > + struct drm_device *ddev = encoder->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + /* > + * We need this, for S3 resume, screen will not lightup if don't set > + * correctly. > + */ > + lsdc_wreg32(ldev, LSDC_CRTC1_PANEL_CONF_REG, > + PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN); > +} > + > +static const struct drm_encoder_funcs ls7a1000_encoder_funcs[2] = { > + { > + .reset = ls7a1000_pipe0_encoder_reset, > + .destroy = drm_encoder_cleanup, > + }, > + { > + .reset = ls7a1000_pipe1_encoder_reset, > + .destroy = drm_encoder_cleanup, > + }, > +}; > + > +int ls7a1000_output_init(struct drm_device *ddev, > + struct lsdc_display_pipe *dispipe, > + struct i2c_adapter *ddc, > + unsigned int pipe) > +{ > + struct drm_encoder *encoder = &dispipe->encoder; > + struct drm_connector *connector = &dispipe->connector; > + int ret; > + > + ret = drm_encoder_init(ddev, > + encoder, > + &ls7a1000_encoder_funcs[pipe], > + DRM_MODE_ENCODER_TMDS, > + "encoder-%u", > + dispipe->index); > + if (ret) > + return ret; > + > + encoder->possible_crtcs = BIT(pipe); > + > + ret = drm_connector_init_with_ddc(ddev, > + connector, > + &ls7a1000_generic_connector_funcs, > + DRM_MODE_CONNECTOR_DPI, > + ddc); > + if (ret) > + return ret; > + > + drm_info(ddev, "display pipe-%u has a DVO\n", pipe); > + > + drm_connector_helper_add(connector, &ls7a1000_generic_connector_helpers); > + > + drm_connector_attach_encoder(connector, encoder); > + > + connector->polled = DRM_CONNECTOR_POLL_CONNECT | > + DRM_CONNECTOR_POLL_DISCONNECT; > + > + connector->interlace_allowed = 0; > + connector->doublescan_allowed = 0; > + > + return 0; > +} > diff --git a/drivers/gpu/drm/loongson/ls7a2000_outputs.c b/drivers/gpu/drm/loongson/ls7a2000_outputs.c > new file mode 100644 > index 000000000000..5ee37da3b88e > --- /dev/null > +++ b/drivers/gpu/drm/loongson/ls7a2000_outputs.c > @@ -0,0 +1,534 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2023 Loongson Corporation > + */ > + > +#include > + > +#include > +#include > +#include > +#include > +#include > + > +#include "lsdc_drv.h" > +#include "lsdc_output.h" > + > +static int ls7a2000_connector_get_modes(struct drm_connector *connector) > +{ > + unsigned int num = 0; > + struct edid *edid; > + > + if (connector->ddc) { > + edid = drm_get_edid(connector, connector->ddc); > + if (edid) { > + drm_connector_update_edid_property(connector, edid); > + num = drm_add_edid_modes(connector, edid); > + kfree(edid); > + } > + > + return num; > + } > + > + num = drm_add_modes_noedid(connector, 1920, 1200); > + > + drm_set_preferred_mode(connector, 1024, 768); > + > + return num; > +} > + > +static struct drm_encoder * > +ls7a2000_connector_get_best_encoder(struct drm_connector *connector, > + struct drm_atomic_state *state) > +{ > + struct lsdc_display_pipe *dispipe; > + > + dispipe = connector_to_display_pipe(connector); > + > + return &dispipe->encoder; > +} > + > +static const struct drm_connector_helper_funcs ls7a2000_connector_helpers = { > + .atomic_best_encoder = ls7a2000_connector_get_best_encoder, > + .get_modes = ls7a2000_connector_get_modes, > +}; > + > +/* debugfs */ > + > +#define LSDC_HDMI_REG(i, reg) { \ > + .name = __stringify_1(LSDC_HDMI##i##_##reg##_REG), \ > + .offset = LSDC_HDMI##i##_##reg##_REG, \ > +} > + > +static const struct lsdc_reg32 ls7a2000_hdmi0_encoder_regs[] = { > + LSDC_HDMI_REG(0, ZONE), > + LSDC_HDMI_REG(0, INTF_CTRL), > + LSDC_HDMI_REG(0, PHY_CTRL), > + LSDC_HDMI_REG(0, PHY_PLL), > + LSDC_HDMI_REG(0, AVI_INFO_CRTL), > + LSDC_HDMI_REG(0, PHY_CAL), > + LSDC_HDMI_REG(0, AUDIO_PLL_LO), > + LSDC_HDMI_REG(0, AUDIO_PLL_HI), > + {NULL, 0}, /* MUST be {NULL, 0} terminated */ > +}; > + > +static const struct lsdc_reg32 ls7a2000_hdmi1_encoder_regs[] = { > + LSDC_HDMI_REG(1, ZONE), > + LSDC_HDMI_REG(1, INTF_CTRL), > + LSDC_HDMI_REG(1, PHY_CTRL), > + LSDC_HDMI_REG(1, PHY_PLL), > + LSDC_HDMI_REG(1, AVI_INFO_CRTL), > + LSDC_HDMI_REG(1, PHY_CAL), > + LSDC_HDMI_REG(1, AUDIO_PLL_LO), > + LSDC_HDMI_REG(1, AUDIO_PLL_HI), > + {NULL, 0}, /* MUST be {NULL, 0} terminated */ > +}; > + > +static int ls7a2000_hdmi_encoder_regs_show(struct seq_file *m, void *data) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct drm_device *ddev = node->minor->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + const struct lsdc_reg32 *preg; > + > + preg = (const struct lsdc_reg32 *)node->info_ent->data; > + > + while (preg->name) { > + u32 offset = preg->offset; > + > + seq_printf(m, "%s (0x%04x): 0x%08x\n", > + preg->name, offset, lsdc_rreg32(ldev, offset)); > + ++preg; > + } > + > + return 0; > +} > + > +static const struct drm_info_list ls7a2000_hdmi0_debugfs_files[] = { > + { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi0_encoder_regs }, > +}; > + > +static const struct drm_info_list ls7a2000_hdmi1_debugfs_files[] = { > + { "regs", ls7a2000_hdmi_encoder_regs_show, 0, (void *)ls7a2000_hdmi1_encoder_regs }, > +}; > + > +static void ls7a2000_hdmi0_late_register(struct drm_connector *connector, > + struct dentry *root) > +{ > + struct drm_device *ddev = connector->dev; > + struct drm_minor *minor = ddev->primary; > + > + drm_debugfs_create_files(ls7a2000_hdmi0_debugfs_files, > + ARRAY_SIZE(ls7a2000_hdmi0_debugfs_files), > + root, > + minor); > +} > + > +static void ls7a2000_hdmi1_late_register(struct drm_connector *connector, > + struct dentry *root) > +{ > + struct drm_device *ddev = connector->dev; > + struct drm_minor *minor = ddev->primary; > + > + drm_debugfs_create_files(ls7a2000_hdmi1_debugfs_files, > + ARRAY_SIZE(ls7a2000_hdmi1_debugfs_files), > + root, > + minor); > +} > + > +/* monitor present detection */ > + > +static enum drm_connector_status > +ls7a2000_hdmi0_vga_connector_detect(struct drm_connector *connector, bool force) > +{ > + struct drm_device *ddev = connector->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); > + > + if (val & HDMI0_HPD_FLAG) > + return connector_status_connected; > + > + if (connector->ddc) { > + if (drm_probe_ddc(connector->ddc)) > + return connector_status_connected; > + > + return connector_status_disconnected; > + } > + > + return connector_status_unknown; > +} > + > +static enum drm_connector_status > +ls7a2000_hdmi1_connector_detect(struct drm_connector *connector, bool force) > +{ > + struct lsdc_device *ldev = to_lsdc(connector->dev); > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); > + > + if (val & HDMI1_HPD_FLAG) > + return connector_status_connected; > + > + return connector_status_disconnected; > +} > + > +static const struct drm_connector_funcs ls7a2000_hdmi_connector_funcs[2] = { > + { > + .detect = ls7a2000_hdmi0_vga_connector_detect, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .destroy = drm_connector_cleanup, > + .reset = drm_atomic_helper_connector_reset, > + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, > + .debugfs_init = ls7a2000_hdmi0_late_register, > + }, > + { > + .detect = ls7a2000_hdmi1_connector_detect, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .destroy = drm_connector_cleanup, > + .reset = drm_atomic_helper_connector_reset, > + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, > + .debugfs_init = ls7a2000_hdmi1_late_register, > + }, > +}; > + > +/* Even though some board has only one hdmi on display pipe 1, > + * We still need hook lsdc_encoder_funcs up on display pipe 0, > + * This is because we need its reset() callback get called, to > + * set the LSDC_HDMIx_CTRL_REG using software gpio emulated i2c. > + * Otherwise, the firmware may set LSDC_HDMIx_CTRL_REG blindly. > + */ > +static void ls7a2000_hdmi0_encoder_reset(struct drm_encoder *encoder) > +{ > + struct drm_device *ddev = encoder->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + u32 val; > + > + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN; > + lsdc_wreg32(ldev, LSDC_CRTC0_PANEL_CONF_REG, val); > + > + /* using software gpio emulated i2c */ > + val = lsdc_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG); > + val &= ~HW_I2C_EN; > + val |= HDMI_INTERFACE_EN | HDMI_PACKET_EN; > + lsdc_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, val); > + > + /* get out of reset state */ > + val |= HDMI_PHY_RESET_N; > + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); > + > + mdelay(20); > + > + drm_dbg(ddev, "HDMI-0 Reset\n"); > +} > + > +static void ls7a2000_hdmi1_encoder_reset(struct drm_encoder *encoder) > +{ > + struct drm_device *ddev = encoder->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + u32 val; > + > + val = PHY_CLOCK_POL | PHY_CLOCK_EN | PHY_DATA_EN; > + lsdc_wreg32(ldev, LSDC_CRTC1_PANEL_CONF_REG, val); > + > + /* using software gpio emulated i2c */ > + val = lsdc_rreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG); > + val &= ~HW_I2C_EN; > + val |= HDMI_INTERFACE_EN | HDMI_PACKET_EN; > + lsdc_wreg32(ldev, LSDC_HDMI1_INTF_CTRL_REG, val); > + > + /* reset */ > + > + /* get out of reset state */ > + val = HDMI_PHY_RESET_N; > + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); > + > + mdelay(20); > + > + drm_dbg(ddev, "HDMI-1 Reset\n"); > +} > + > +static const struct drm_encoder_funcs ls7a2000_encoder_funcs[2] = { > + { > + .reset = ls7a2000_hdmi0_encoder_reset, > + .destroy = drm_encoder_cleanup, > + }, > + { > + .reset = ls7a2000_hdmi1_encoder_reset, > + .destroy = drm_encoder_cleanup, > + } > +}; > + > +static int ls7a2000_hdmi_set_avi_infoframe(struct drm_encoder *encoder, > + struct drm_display_mode *mode) > +{ > + struct lsdc_display_pipe *dispipe = encoder_to_display_pipe(encoder); > + struct drm_device *ddev = encoder->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + unsigned int index = dispipe->index; > + struct hdmi_avi_infoframe infoframe; > + u8 buffer[HDMI_INFOFRAME_SIZE(AVI)]; > + unsigned char *ptr = &buffer[HDMI_INFOFRAME_HEADER_SIZE]; > + unsigned int content0, content1, content2, content3; > + int err; > + > + err = drm_hdmi_avi_infoframe_from_display_mode(&infoframe, > + &dispipe->connector, > + mode); > + if (err < 0) { > + drm_err(ddev, "failed to setup AVI infoframe: %d\n", err); > + return err; > + } > + > + /* Fixed infoframe configuration not linked to the mode */ > + infoframe.colorspace = HDMI_COLORSPACE_RGB; > + infoframe.quantization_range = HDMI_QUANTIZATION_RANGE_DEFAULT; > + infoframe.colorimetry = HDMI_COLORIMETRY_NONE; > + > + err = hdmi_avi_infoframe_pack(&infoframe, buffer, sizeof(buffer)); > + if (err < 0) { > + drm_err(ddev, "failed to pack AVI infoframe: %d\n", err); > + return err; > + } > + > + content0 = *(unsigned int *)ptr; > + content1 = *(ptr + 4); > + content2 = *(unsigned int *)(ptr + 5); > + content3 = *(unsigned int *)(ptr + 9); > + > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT0, index, content0); > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT1, index, content1); > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT2, index, content2); > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_AVI_CONTENT3, index, content3); > + > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_AVI_INFO_CRTL_REG, index, > + AVI_PKT_ENABLE | AVI_PKT_UPDATE); > + > + drm_dbg(ddev, "Update HDMI-%u avi infoframe\n", index); > + > + return 0; > +} > + > +static void ls7a2000_hdmi_atomic_disable(struct drm_encoder *encoder, > + struct drm_atomic_state *state) > +{ > + struct lsdc_display_pipe *dispipe = encoder_to_display_pipe(encoder); > + struct drm_device *ddev = encoder->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + unsigned int index = dispipe->index; > + u32 val; > + > + /* Disable the hdmi phy */ > + val = lsdc_hdmi_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index); > + val &= ~HDMI_PHY_EN; > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val); > + > + /* Disable the hdmi interface */ > + val = lsdc_hdmi_rreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index); > + val &= ~HDMI_INTERFACE_EN; > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index, val); > + > + drm_dbg(ddev, "HDMI-%u disabled\n", index); > +} > + > +static void ls7a2000_hdmi_atomic_enable(struct drm_encoder *encoder, > + struct drm_atomic_state *state) > +{ > + struct drm_device *ddev = encoder->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct lsdc_display_pipe *dispipe = encoder_to_display_pipe(encoder); > + unsigned int index = dispipe->index; > + u32 val; > + > + /* datasheet say it should larger than 48 */ > + val = 64 << HDMI_H_ZONE_IDLE_SHIFT | 64 << HDMI_V_ZONE_IDLE_SHIFT; > + > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_ZONE_REG, index, val); > + > + val = HDMI_PHY_TERM_STATUS | > + HDMI_PHY_TERM_DET_EN | > + HDMI_PHY_TERM_H_EN | > + HDMI_PHY_TERM_L_EN | > + HDMI_PHY_RESET_N | > + HDMI_PHY_EN; > + > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, index, val); > + > + udelay(2); > + > + val = HDMI_CTL_PERIOD_MODE | > + HDMI_AUDIO_EN | > + HDMI_PACKET_EN | > + HDMI_INTERFACE_EN | > + (8 << HDMI_VIDEO_PREAMBLE_SHIFT); > + > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_INTF_CTRL_REG, index, val); > + > + drm_dbg(ddev, "HDMI-%u enabled\n", index); > +} > + > +/* > + * Fout = M * Fin > + * > + * M = (4 * LF) / (IDF * ODF) > + * > + * IDF: Input Division Factor > + * ODF: Output Division Factor > + * LF: Loop Factor > + * M: Required Mult > + * > + * +--------------------------------------------------------+ > + * | Fin (kHZ) | M | IDF | LF | ODF | Fout(Mhz) | > + * |-------------------+----+-----+----+-----+--------------| > + * | 170000 ~ 340000 | 10 | 16 | 40 | 1 | 1700 ~ 3400 | > + * | 85000 ~ 170000 | 10 | 8 | 40 | 2 | 850 ~ 1700 | > + * | 42500 ~ 85000 | 10 | 4 | 40 | 4 | 425 ~ 850 | > + * | 21250 ~ 42500 | 10 | 2 | 40 | 8 | 212.5 ~ 425 | > + * | 20000 ~ 21250 | 10 | 1 | 40 | 16 | 200 ~ 212.5 | > + * +--------------------------------------------------------+ > + */ > +static void ls7a2000_hdmi_phy_pll_config(struct lsdc_device *ldev, > + int fin, > + unsigned int index) > +{ > + struct drm_device *ddev = &ldev->base; > + int count = 0; > + u32 val; > + > + /* Firstly, disable phy pll */ > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, 0x0); > + > + /* > + * Most of time, loongson HDMI require M = 10 > + * for example, 10 = (4 * 40) / (8 * 2) > + * here, write "1" to the ODF will get "2" > + */ > + > + if (fin >= 170000) > + val = (16 << HDMI_PLL_IDF_SHIFT) | > + (40 << HDMI_PLL_LF_SHIFT) | > + (0 << HDMI_PLL_ODF_SHIFT); > + else if (fin >= 85000) > + val = (8 << HDMI_PLL_IDF_SHIFT) | > + (40 << HDMI_PLL_LF_SHIFT) | > + (1 << HDMI_PLL_ODF_SHIFT); > + else if (fin >= 42500) > + val = (4 << HDMI_PLL_IDF_SHIFT) | > + (40 << HDMI_PLL_LF_SHIFT) | > + (2 << HDMI_PLL_ODF_SHIFT); > + else if (fin >= 21250) > + val = (2 << HDMI_PLL_IDF_SHIFT) | > + (40 << HDMI_PLL_LF_SHIFT) | > + (3 << HDMI_PLL_ODF_SHIFT); > + else > + val = (1 << HDMI_PLL_IDF_SHIFT) | > + (40 << HDMI_PLL_LF_SHIFT) | > + (4 << HDMI_PLL_ODF_SHIFT); > + > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, val); > + > + val |= HDMI_PLL_ENABLE; > + > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index, val); > + > + udelay(2); > + > + drm_dbg(ddev, "Fin of HDMI-%u: %d kHz\n", index, fin); > + > + /* Wait hdmi phy pll lock */ > + do { > + val = lsdc_hdmi_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, index); > + > + if (val & HDMI_PLL_LOCKED) { > + drm_dbg(ddev, "Setting HDMI-%u PLL take %d cycles\n", > + index, count); > + break; > + } > + ++count; > + } while (count < 1000); > + > + lsdc_hdmi_wreg32(ldev, LSDC_HDMI0_PHY_CAL_REG, index, 0x0f000ff0); > + > + if (count >= 1000) > + drm_err(ddev, "Setting HDMI-%u PLL failed\n", index); > +} > + > +static void ls7a2000_hdmi_atomic_mode_set(struct drm_encoder *encoder, > + struct drm_crtc_state *crtc_state, > + struct drm_connector_state *conn_state) > +{ > + struct lsdc_display_pipe *dispipe = encoder_to_display_pipe(encoder); > + struct drm_device *ddev = encoder->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct drm_display_mode *mode = &crtc_state->mode; > + > + ls7a2000_hdmi_phy_pll_config(ldev, mode->clock, dispipe->index); > + > + ls7a2000_hdmi_set_avi_infoframe(encoder, mode); > + > + drm_dbg(ddev, "HDMI-%u modeset finished\n", dispipe->index); > +} > + > +static const struct drm_encoder_helper_funcs ls7a2000_encoder_helper_funcs = { > + .atomic_disable = ls7a2000_hdmi_atomic_disable, > + .atomic_enable = ls7a2000_hdmi_atomic_enable, > + .atomic_mode_set = ls7a2000_hdmi_atomic_mode_set, > +}; > + > +/* > + * For LS7A2000: > + * > + * 1) Most of board export one vga + hdmi output interface. > + * 2) Yet, Some boards export double hdmi output interface. > + * 3) Still have boards export three output(2 hdmi + 1 vga). > + * > + * So let's hook hdmi helper funcs to all display pipe, don't miss. > + * writing hdmi register do no harms. > + */ > +int ls7a2000_output_init(struct drm_device *ddev, > + struct lsdc_display_pipe *dispipe, > + struct i2c_adapter *ddc, > + unsigned int pipe) > +{ > + struct drm_encoder *encoder = &dispipe->encoder; > + struct drm_connector *connector = &dispipe->connector; > + int ret; > + > + ret = drm_encoder_init(ddev, > + encoder, > + &ls7a2000_encoder_funcs[pipe], > + DRM_MODE_ENCODER_TMDS, > + "encoder-%u", > + pipe); > + if (ret) > + return ret; > + > + encoder->possible_crtcs = BIT(pipe); > + > + drm_encoder_helper_add(encoder, &ls7a2000_encoder_helper_funcs); > + > + ret = drm_connector_init_with_ddc(ddev, > + connector, > + &ls7a2000_hdmi_connector_funcs[pipe], > + DRM_MODE_CONNECTOR_HDMIA, > + ddc); > + if (ret) > + return ret; > + > + drm_info(ddev, "display pipe-%u has HDMI %s\n", pipe, pipe ? "" : "and/or VGA"); > + > + drm_connector_helper_add(connector, &ls7a2000_connector_helpers); > + > + drm_connector_attach_encoder(connector, encoder); > + > + connector->polled = DRM_CONNECTOR_POLL_CONNECT | > + DRM_CONNECTOR_POLL_DISCONNECT; > + > + connector->interlace_allowed = 0; > + connector->doublescan_allowed = 0; > + > + return 0; > +} > diff --git a/drivers/gpu/drm/loongson/lsdc_crtc.c b/drivers/gpu/drm/loongson/lsdc_crtc.c > new file mode 100644 > index 000000000000..9c23fa569dd5 > --- /dev/null > +++ b/drivers/gpu/drm/loongson/lsdc_crtc.c > @@ -0,0 +1,1064 @@ > +// SPDX-License-Identifier: GPL-2.0 > +/* > + * Copyright (C) 2023 Loongson Corporation > + */ > + > +#include > + > +#include > +#include > +#include > +#include > + > +#include "lsdc_drv.h" > + > +/* > + * The soft reset cause the vblank counter reset to zero, but the address > + * and other settings in the crtc register remains. > + */ > + > +static void lsdc_crtc0_soft_reset(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + > + val &= CFG_VALID_BITS_MASK; > + > + /* soft reset bit, active low */ > + val &= ~CFG_RESET_N; > + > + val &= ~CFG_PIX_FMT_MASK; > + > + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); > + > + udelay(5); > + > + val |= CFG_RESET_N | LSDC_PF_XRGB8888 | CFG_OUTPUT_ENABLE; > + > + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); > + > + mdelay(20); > +} > + > +static void lsdc_crtc1_soft_reset(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); > + > + val &= CFG_VALID_BITS_MASK; > + > + /* soft reset bit, active low */ > + val &= ~CFG_RESET_N; > + > + val &= ~CFG_PIX_FMT_MASK; > + > + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); > + > + udelay(5); > + > + val |= CFG_RESET_N | LSDC_PF_XRGB8888 | CFG_OUTPUT_ENABLE; > + > + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); > + > + msleep(20); > +} > + > +static void lsdc_crtc0_enable(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + > + /* > + * If bit 24 of LSDC_CRTC0_CFG_REG is set, it say that the DC hardware > + * stop working anymore, anchored. This maybe caused by improper > + * operation sequence, may happens on extreme rarely case. > + * > + * Luckily, a soft reset helps bring it to normal on such a case. > + * So we add a warn here, hope to catch something if it happens. > + */ > + > + if (val & BIT(24)) { > + drm_warn(&ldev->base, "%s anchored\n", lcrtc->base.name); > + return lsdc_crtc0_soft_reset(lcrtc); > + } > + > + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val | CFG_OUTPUT_ENABLE); > +} > + > +static void lsdc_crtc0_disable(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + lsdc_ureg32_clr(ldev, LSDC_CRTC0_CFG_REG, CFG_OUTPUT_ENABLE); > + > + udelay(9); > +} > + > +static void lsdc_crtc1_enable(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); > + if (val & BIT(24)) { > + drm_warn(&ldev->base, "%s anchored\n", lcrtc->base.name); > + return lsdc_crtc1_soft_reset(lcrtc); > + } > + > + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val | CFG_OUTPUT_ENABLE); > +} > + > +static void lsdc_crtc1_disable(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + lsdc_ureg32_clr(ldev, LSDC_CRTC1_CFG_REG, CFG_OUTPUT_ENABLE); > + > + udelay(9); > +} > + > +/* All loongson display controller support scanout position hardware */ > + > +static void lsdc_crtc0_scan_pos(struct lsdc_crtc *lcrtc, int *hpos, int *vpos) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); > + > + *hpos = val >> 16; > + *vpos = val & 0xffff; > +} > + > +static void lsdc_crtc1_scan_pos(struct lsdc_crtc *lcrtc, int *hpos, int *vpos) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); > + > + *hpos = val >> 16; > + *vpos = val & 0xffff; > +} > + > +static void lsdc_crtc0_enable_vblank(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + lsdc_ureg32_set(ldev, LSDC_INT_REG, INT_CRTC0_VSYNC_EN); > +} > + > +static void lsdc_crtc0_disable_vblank(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + lsdc_ureg32_clr(ldev, LSDC_INT_REG, INT_CRTC0_VSYNC_EN); > +} > + > +static void lsdc_crtc1_enable_vblank(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + lsdc_ureg32_set(ldev, LSDC_INT_REG, INT_CRTC1_VSYNC_EN); > +} > + > +static void lsdc_crtc1_disable_vblank(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + lsdc_ureg32_clr(ldev, LSDC_INT_REG, INT_CRTC1_VSYNC_EN); > +} > + > +static void lsdc_crtc0_flip(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + lsdc_ureg32_set(ldev, LSDC_CRTC0_CFG_REG, CFG_PAGE_FLIP); > +} > + > +static void lsdc_crtc1_flip(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + lsdc_ureg32_set(ldev, LSDC_CRTC1_CFG_REG, CFG_PAGE_FLIP); > +} > + > +/* > + * CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware logic. > + * Hardware engineer say this would help to saving bandwidth on clone mode. > + * > + * This may useful on custom clone application. > + */ > + > +static void lsdc_crtc0_clone(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + lsdc_ureg32_set(ldev, LSDC_CRTC0_CFG_REG, CFG_HW_CLONE); > +} > + > +static void lsdc_crtc1_clone(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + lsdc_ureg32_set(ldev, LSDC_CRTC1_CFG_REG, CFG_HW_CLONE); > +} > + > +static void lsdc_crtc0_set_mode(struct lsdc_crtc *lcrtc, > + const struct drm_display_mode *mode) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + lsdc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG, > + (mode->crtc_htotal << 16) | mode->crtc_hdisplay); > + > + lsdc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG, > + (mode->crtc_vtotal << 16) | mode->crtc_vdisplay); > + > + lsdc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG, > + (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | HSYNC_EN); > + > + lsdc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG, > + (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | VSYNC_EN); > +} > + > +static void lsdc_crtc1_set_mode(struct lsdc_crtc *lcrtc, > + const struct drm_display_mode *mode) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + lsdc_wreg32(ldev, LSDC_CRTC1_HDISPLAY_REG, > + (mode->crtc_htotal << 16) | mode->crtc_hdisplay); > + > + lsdc_wreg32(ldev, LSDC_CRTC1_VDISPLAY_REG, > + (mode->crtc_vtotal << 16) | mode->crtc_vdisplay); > + > + lsdc_wreg32(ldev, LSDC_CRTC1_HSYNC_REG, > + (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | HSYNC_EN); > + > + lsdc_wreg32(ldev, LSDC_CRTC1_VSYNC_REG, > + (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | VSYNC_EN); > +} > + > +/* > + * This is required for S3 support. > + * > + * After resume from suspend, LSDC_CRTCx_CFG_REG (x=0 or 1)is filled with > + * garbarge value which may cause the CRTC completely hang. This function > + * give a minimal setting to the affected registers. This also override > + * the firmware's setting on startup, eliminate potential blinding setting. > + * > + * Making the CRTC works on our own now, this is similar with the functional > + * of GPU POST(Power On Self Test). Only touch CRTC hardware related part. > + */ > + > +static void lsdc_crtc0_reset(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + struct drm_crtc *crtc = &lcrtc->base; > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + > + /* This help to see what is it */ > + drm_dbg(&ldev->base, "value of %s configure register: %x\n", > + crtc->name, val); > + > + /* > + * Help the CRTC get out of soft reset sate, as the CRTC is completely > + * halt on soft reset mode (BIT(20) = 0). It does not event generate > + * vblank, cause vblank wait timeout. This happends when resume from > + * S3. > + * > + * Also give a sane format, after resume from suspend S3, this > + * register is filled with garbarge value. A meaningless value may > + * also cause the CRTC halt or crash. > + */ > + > + val = CFG_RESET_N | LSDC_PF_XRGB8888; > + > + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); > +} > + > +static void lsdc_crtc1_reset(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + struct drm_crtc *crtc = &lcrtc->base; > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); > + > + drm_dbg(&ldev->base, "value of %s configue register: %x\n", > + crtc->name, val); > + > + /* > + * Help the CRTC get out of soft reset sate, give a sane format, > + * Otherwise it will complete halt there. > + */ > + val = CFG_RESET_N | LSDC_PF_XRGB8888; > + > + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); > +} > + > +static const struct lsdc_crtc_hw_ops ls7a1000_crtc_hw_ops[2] = { > + { > + .enable = lsdc_crtc0_enable, > + .disable = lsdc_crtc0_disable, > + .enable_vblank = lsdc_crtc0_enable_vblank, > + .disable_vblank = lsdc_crtc0_disable_vblank, > + .flip = lsdc_crtc0_flip, > + .clone = lsdc_crtc0_clone, > + .set_mode = lsdc_crtc0_set_mode, > + .get_scan_pos = lsdc_crtc0_scan_pos, > + .soft_reset = lsdc_crtc0_soft_reset, > + .reset = lsdc_crtc0_reset, > + }, > + { > + .enable = lsdc_crtc1_enable, > + .disable = lsdc_crtc1_disable, > + .enable_vblank = lsdc_crtc1_enable_vblank, > + .disable_vblank = lsdc_crtc1_disable_vblank, > + .flip = lsdc_crtc1_flip, > + .clone = lsdc_crtc1_clone, > + .set_mode = lsdc_crtc1_set_mode, > + .get_scan_pos = lsdc_crtc1_scan_pos, > + .soft_reset = lsdc_crtc1_soft_reset, > + .reset = lsdc_crtc1_reset, > + }, > +}; > + > +/* > + * The 32-bit hardware vblank counter is available since ls7a2000/ls2k2000, > + * The counter grow up even the CRTC is being disabled, it will got reset > + * if the crtc is being soft reset. > + * > + * Those registers are also readable for ls7a1000, but its value does not > + * change. > + */ > + > +static u32 lsdc_crtc0_get_vblank_count(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + return lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG); > +} > + > +static u32 lsdc_crtc1_get_vblank_count(struct lsdc_crtc *lcrtc) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + > + return lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG); > +} > + > +/* > + * The DMA step register is available since ls7a2000/ls2k2000, for support > + * odd resolutions. But a large DMA step may save bandwidth. Behavior of > + * writing thoes bits field on ls7a1000/ls2k1000 is underfined. > + */ > + > +static void lsdc_crtc0_set_dma_step(struct lsdc_crtc *lcrtc, > + enum lsdc_dma_steps dma_step) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + u32 val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + > + val &= ~CFG_DMA_STEP_MASK; > + val |= dma_step << CFG_DMA_STEP_SHIFT; > + > + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); > +} > + > +static void lsdc_crtc1_set_dma_step(struct lsdc_crtc *lcrtc, > + enum lsdc_dma_steps dma_step) > +{ > + struct lsdc_device *ldev = lcrtc->ldev; > + u32 val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); > + > + val &= ~CFG_DMA_STEP_MASK; > + val |= dma_step << CFG_DMA_STEP_SHIFT; > + > + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); > +} > + > +static const struct lsdc_crtc_hw_ops ls7a2000_crtc_hw_ops[2] = { > + { > + .enable = lsdc_crtc0_enable, > + .disable = lsdc_crtc0_disable, > + .enable_vblank = lsdc_crtc0_enable_vblank, > + .disable_vblank = lsdc_crtc0_disable_vblank, > + .flip = lsdc_crtc0_flip, > + .clone = lsdc_crtc0_clone, > + .set_mode = lsdc_crtc0_set_mode, > + .soft_reset = lsdc_crtc0_soft_reset, > + .get_scan_pos = lsdc_crtc0_scan_pos, > + .set_dma_step = lsdc_crtc0_set_dma_step, > + .get_vblank_counter = lsdc_crtc0_get_vblank_count, > + .reset = lsdc_crtc0_reset, > + }, > + { > + .enable = lsdc_crtc1_enable, > + .disable = lsdc_crtc1_disable, > + .enable_vblank = lsdc_crtc1_enable_vblank, > + .disable_vblank = lsdc_crtc1_disable_vblank, > + .flip = lsdc_crtc1_flip, > + .clone = lsdc_crtc1_clone, > + .set_mode = lsdc_crtc1_set_mode, > + .get_scan_pos = lsdc_crtc1_scan_pos, > + .soft_reset = lsdc_crtc1_soft_reset, > + .set_dma_step = lsdc_crtc1_set_dma_step, > + .get_vblank_counter = lsdc_crtc1_get_vblank_count, > + .reset = lsdc_crtc1_reset, > + }, > +}; > + > +static void lsdc_crtc_reset(struct drm_crtc *crtc) > +{ > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; > + struct lsdc_crtc_state *priv_crtc_state; > + > + if (crtc->state) > + crtc->funcs->atomic_destroy_state(crtc, crtc->state); > + > + priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL); > + > + if (!priv_crtc_state) > + __drm_atomic_helper_crtc_reset(crtc, NULL); > + else > + __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); > + > + /* > + * Reset the crtc hardware, this is need for S3 support, > + * otherwise, wait for vblank timeout > + */ > + ops->reset(lcrtc); > +} > + > +static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, > + struct drm_crtc_state *state) > +{ > + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); > + > + __drm_atomic_helper_crtc_destroy_state(&priv_state->base); > + > + kfree(priv_state); > +} > + > +static struct drm_crtc_state * > +lsdc_crtc_atomic_duplicate_state(struct drm_crtc *crtc) > +{ > + struct lsdc_crtc_state *new_priv_state; > + struct lsdc_crtc_state *old_priv_state; > + > + new_priv_state = kzalloc(sizeof(*new_priv_state), GFP_KERNEL); > + if (!new_priv_state) > + return NULL; > + > + __drm_atomic_helper_crtc_duplicate_state(crtc, &new_priv_state->base); > + > + old_priv_state = to_lsdc_crtc_state(crtc->state); > + > + memcpy(&new_priv_state->pparms, &old_priv_state->pparms, > + sizeof(new_priv_state->pparms)); > + > + return &new_priv_state->base; > +} > + > +static u32 lsdc_crtc_get_vblank_counter(struct drm_crtc *crtc) > +{ > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; > + > + return ops->get_vblank_counter(lcrtc); > +} > + > +static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc) > +{ > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; > + > + ops->enable_vblank(lcrtc); > + > + return 0; > +} > + > +static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc) > +{ > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; > + > + ops->disable_vblank(lcrtc); > +} > + > +/* CRTC related debugfs > + * > + * Primary planes and cursor planes are also belong to the CRTC for our case, > + * so also append other registers to here, for sake of convient. > + */ > + > +#define REG_DEF(reg) { .name = __stringify_1(LSDC_##reg##_REG), .offset = LSDC_##reg##_REG } > + > +static const struct lsdc_reg32 lsdc_crtc_regs_array[2][21] = { > + [0] = { > + REG_DEF(CRTC0_CFG), > + REG_DEF(CRTC0_FB_ORIGIN), > + REG_DEF(CRTC0_PANEL_CONF), > + REG_DEF(CRTC0_HDISPLAY), > + REG_DEF(CRTC0_HSYNC), > + REG_DEF(CRTC0_VDISPLAY), > + REG_DEF(CRTC0_VSYNC), > + REG_DEF(CRTC0_GAMMA_INDEX), > + REG_DEF(CRTC0_GAMMA_DATA), > + REG_DEF(CRTC0_SYNC_DEVIATION), > + REG_DEF(CRTC0_VSYNC_COUNTER), > + REG_DEF(CRTC0_SCAN_POS), > + REG_DEF(CRTC0_STRIDE), > + REG_DEF(CRTC0_FB1_ADDR_HI), > + REG_DEF(CRTC0_FB1_ADDR_LO), > + REG_DEF(CRTC0_FB0_ADDR_HI), > + REG_DEF(CRTC0_FB0_ADDR_LO), > + REG_DEF(CURSOR0_CFG), > + REG_DEF(CURSOR0_POSITION), > + REG_DEF(CURSOR0_BG_COLOR), > + REG_DEF(CURSOR0_FG_COLOR), > + }, > + [1] = { > + REG_DEF(CRTC1_CFG), > + REG_DEF(CRTC1_FB_ORIGIN), > + REG_DEF(CRTC1_PANEL_CONF), > + REG_DEF(CRTC1_HDISPLAY), > + REG_DEF(CRTC1_HSYNC), > + REG_DEF(CRTC1_VDISPLAY), > + REG_DEF(CRTC1_VSYNC), > + REG_DEF(CRTC1_GAMMA_INDEX), > + REG_DEF(CRTC1_GAMMA_DATA), > + REG_DEF(CRTC1_SYNC_DEVIATION), > + REG_DEF(CRTC1_VSYNC_COUNTER), > + REG_DEF(CRTC1_SCAN_POS), > + REG_DEF(CRTC1_STRIDE), > + REG_DEF(CRTC1_FB1_ADDR_HI), > + REG_DEF(CRTC1_FB1_ADDR_LO), > + REG_DEF(CRTC1_FB0_ADDR_HI), > + REG_DEF(CRTC1_FB0_ADDR_LO), > + REG_DEF(CURSOR1_CFG), > + REG_DEF(CURSOR1_POSITION), > + REG_DEF(CURSOR1_BG_COLOR), > + REG_DEF(CURSOR1_FG_COLOR), > + }, > +}; > + > +static int lsdc_crtc_show_regs(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data; > + struct lsdc_device *ldev = lcrtc->ldev; > + unsigned int i; > + > + for (i = 0; i < lcrtc->nreg; i++) { > + const struct lsdc_reg32 *preg = &lcrtc->preg[i]; > + u32 offset = preg->offset; > + > + seq_printf(m, "%s (0x%04x): 0x%08x\n", > + preg->name, offset, lsdc_rreg32(ldev, offset)); > + } > + > + return 0; > +} > + > +static int lsdc_crtc_show_scan_position(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data; > + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; > + int x, y; > + > + ops->get_scan_pos(lcrtc, &x, &y); > + > + seq_printf(m, "scanout position: x: %08u, y: %08u\n", x, y); > + > + return 0; > +} > + > +static int lsdc_crtc_show_vblank_counter(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data; > + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; > + > + seq_printf(m, "CRTC-0 vblank counter: %08u\n\n", > + ops->get_vblank_counter(lcrtc)); > + > + return 0; > +} > + > +static int lsdc_pixpll_show_clock(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct lsdc_crtc *lcrtc = (struct lsdc_crtc *)node->info_ent->data; > + struct lsdc_pixpll *pixpll = &lcrtc->pixpll; > + const struct lsdc_pixpll_funcs *funcs = pixpll->funcs; > + struct drm_crtc *crtc = &lcrtc->base; > + struct drm_display_mode *mode = &crtc->state->mode; > + struct drm_printer printer = drm_seq_file_printer(m); > + unsigned int out_khz; > + > + out_khz = funcs->get_rate(pixpll); > + > + seq_printf(m, "%s: %dx%d@%d\n", crtc->name, > + mode->hdisplay, mode->vdisplay, drm_mode_vrefresh(mode)); > + > + seq_printf(m, "Pixel clock required: %d kHz\n", mode->clock); > + seq_printf(m, "Actual frequency output: %u kHz\n", out_khz); > + seq_printf(m, "Diff: %d kHz\n", out_khz - mode->clock); > + > + funcs->print(pixpll, &printer); > + > + return 0; > +} > + > +static struct drm_info_list lsdc_crtc_debugfs_list[2][4] = { > + [0] = { > + { "regs", lsdc_crtc_show_regs, 0, NULL }, > + { "pixclk", lsdc_pixpll_show_clock, 0, NULL }, > + { "scanpos", lsdc_crtc_show_scan_position, 0, NULL }, > + { "vblanks", lsdc_crtc_show_vblank_counter, 0, NULL }, > + }, > + [1] = { > + { "regs", lsdc_crtc_show_regs, 0, NULL }, > + { "pixclk", lsdc_pixpll_show_clock, 0, NULL }, > + { "scanpos", lsdc_crtc_show_scan_position, 0, NULL }, > + { "vblanks", lsdc_crtc_show_vblank_counter, 0, NULL }, > + }, > +}; > + > +/* operate manually */ > + > +static int lsdc_crtc_man_op_show(struct seq_file *m, void *data) > +{ > + seq_puts(m, "soft_reset: soft reset this CRTC\n"); > + seq_puts(m, "enable: enable this CRTC\n"); > + seq_puts(m, "disable: disable this CRTC\n"); > + seq_puts(m, "flip: trigger the page flip\n"); > + seq_puts(m, "clone: clone the another crtc with hardware logic\n"); > + > + return 0; > +} > + > +static int lsdc_crtc_man_op_open(struct inode *inode, struct file *file) > +{ > + struct drm_crtc *crtc = inode->i_private; > + > + return single_open(file, lsdc_crtc_man_op_show, crtc); > +} > + > +static ssize_t lsdc_crtc_man_op_write(struct file *file, > + const char __user *ubuf, > + size_t len, > + loff_t *offp) > +{ > + struct seq_file *m = file->private_data; > + struct drm_crtc *crtc = m->private; > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; > + > + char buf[16]; > + > + if (len > sizeof(buf) - 1) > + return -EINVAL; > + > + if (copy_from_user(buf, ubuf, len)) > + return -EFAULT; > + > + buf[len] = '\0'; > + > + if (sysfs_streq(buf, "soft_reset")) > + ops->soft_reset(lcrtc); > + else if (sysfs_streq(buf, "enable")) > + ops->enable(lcrtc); > + else if (sysfs_streq(buf, "disable")) > + ops->disable(lcrtc); > + else if (sysfs_streq(buf, "flip")) > + ops->flip(lcrtc); > + else if (sysfs_streq(buf, "clone")) > + ops->clone(lcrtc); > + > + return len; > +} > + > +static const struct file_operations lsdc_crtc_man_op_fops = { > + .owner = THIS_MODULE, > + .open = lsdc_crtc_man_op_open, > + .read = seq_read, > + .llseek = seq_lseek, > + .release = single_release, > + .write = lsdc_crtc_man_op_write, > +}; > + > +static int lsdc_crtc_late_register(struct drm_crtc *crtc) > +{ > + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + struct drm_minor *minor = crtc->dev->primary; > + unsigned int index = dispipe->index; > + unsigned int i; > + > + lcrtc->preg = lsdc_crtc_regs_array[index]; > + lcrtc->nreg = ARRAY_SIZE(lsdc_crtc_regs_array[index]); > + lcrtc->p_info_list = lsdc_crtc_debugfs_list[index]; > + lcrtc->n_info_list = ARRAY_SIZE(lsdc_crtc_debugfs_list[index]); > + > + for (i = 0; i < lcrtc->n_info_list; ++i) > + lcrtc->p_info_list[i].data = lcrtc; > + > + drm_debugfs_create_files(lcrtc->p_info_list, > + lcrtc->n_info_list, > + crtc->debugfs_entry, > + minor); > + > + /* supported manual operations */ > + debugfs_create_file("ops", 0644, crtc->debugfs_entry, crtc, > + &lsdc_crtc_man_op_fops); > + > + return 0; > +} > + > +static void lsdc_crtc_atomic_print_state(struct drm_printer *p, > + const struct drm_crtc_state *state) > +{ > + const struct lsdc_crtc_state *priv_state; > + const struct lsdc_pixpll_parms *pparms; > + > + priv_state = container_of_const(state, struct lsdc_crtc_state, base); > + pparms = &priv_state->pparms; > + > + drm_printf(p, "\tInput clock divider = %u\n", pparms->div_ref); > + drm_printf(p, "\tMedium clock Multiplier = %u\n", pparms->loopc); > + drm_printf(p, "\tOutput clock divider = %u\n", pparms->div_out); > +} > + > +static const struct drm_crtc_funcs ls7a1000_crtc_funcs = { > + .reset = lsdc_crtc_reset, > + .destroy = drm_crtc_cleanup, > + .set_config = drm_atomic_helper_set_config, > + .page_flip = drm_atomic_helper_page_flip, > + .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, > + .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, > + .late_register = lsdc_crtc_late_register, > + .enable_vblank = lsdc_crtc_enable_vblank, > + .disable_vblank = lsdc_crtc_disable_vblank, > + .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, > + .atomic_print_state = lsdc_crtc_atomic_print_state, > +}; > + > +static const struct drm_crtc_funcs ls7a2000_crtc_funcs = { > + .reset = lsdc_crtc_reset, > + .destroy = drm_crtc_cleanup, > + .set_config = drm_atomic_helper_set_config, > + .page_flip = drm_atomic_helper_page_flip, > + .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, > + .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, > + .late_register = lsdc_crtc_late_register, > + .get_vblank_counter = lsdc_crtc_get_vblank_counter, > + .enable_vblank = lsdc_crtc_enable_vblank, > + .disable_vblank = lsdc_crtc_disable_vblank, > + .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, > + .atomic_print_state = lsdc_crtc_atomic_print_state, > +}; > + > +static enum drm_mode_status > +lsdc_crtc_mode_valid(struct drm_crtc *crtc, const struct drm_display_mode *mode) > +{ > + struct drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + const struct lsdc_desc *descp = ldev->descp; > + unsigned int pitch; > + > + if (mode->hdisplay > descp->max_width) > + return MODE_BAD_HVALUE; > + > + if (mode->vdisplay > descp->max_height) > + return MODE_BAD_VVALUE; > + > + if (mode->clock > descp->max_pixel_clk) { > + drm_dbg_kms(ddev, "mode %dx%d, pixel clock=%d is too high\n", > + mode->hdisplay, mode->vdisplay, mode->clock); > + return MODE_CLOCK_HIGH; > + } > + > + /* 4 for DRM_FORMAT_XRGB8888 */ > + pitch = mode->hdisplay * 4; > + > + if (pitch % descp->pitch_align) { > + drm_dbg_kms(ddev, "aligned to %u bytes is required: %u\n", > + descp->pitch_align, pitch); > + return MODE_BAD_WIDTH; > + } > + > + return MODE_OK; > +} > + > +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc, > + struct drm_crtc_state *state) > +{ > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + struct lsdc_pixpll *pixpll = &lcrtc->pixpll; > + const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; > + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); > + unsigned int clock = state->mode.clock; > + int ret; > + > + ret = pfuncs->compute(pixpll, clock, &priv_state->pparms); > + if (ret) { > + drm_warn(crtc->dev, "find PLL parms for %ukHz failed\n", clock); > + return -EINVAL; > + } > + > + return 0; > +} > + > +static int lsdc_crtc_helper_atomic_check(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); > + > + if (!crtc_state->enable) > + return 0; > + > + return lsdc_pixpll_atomic_check(crtc, crtc_state); > +} > + > +static void lsdc_crtc_mode_set_nofb(struct drm_crtc *crtc) > +{ > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *crtc_hw_ops = lcrtc->hw_ops; > + struct lsdc_pixpll *pixpll = &lcrtc->pixpll; > + const struct lsdc_pixpll_funcs *pixpll_funcs = pixpll->funcs; > + struct drm_crtc_state *state = crtc->state; > + struct drm_display_mode *mode = &state->mode; > + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); > + > + pixpll_funcs->update(pixpll, &priv_state->pparms); > + > + if (crtc_hw_ops->set_dma_step) { > + unsigned int width_in_bytes = mode->hdisplay * 4; > + enum lsdc_dma_steps dma_step; > + > + /* > + * Using large dma step as much as possible, for improving > + * hardware DMA efficiency. > + */ > + if (width_in_bytes % 256 == 0) > + dma_step = LSDC_DMA_STEP_256_BYTES; > + else if (width_in_bytes % 128 == 0) > + dma_step = LSDC_DMA_STEP_128_BYTES; > + else if (width_in_bytes % 64 == 0) > + dma_step = LSDC_DMA_STEP_64_BYTES; > + else /* width_in_bytes % 32 == 0 */ > + dma_step = LSDC_DMA_STEP_32_BYTES; > + > + crtc_hw_ops->set_dma_step(lcrtc, dma_step); > + } > + > + crtc_hw_ops->set_mode(lcrtc, mode); > +} > + > +static void lsdc_crtc_send_vblank(struct drm_crtc *crtc) > +{ > + struct drm_device *ddev = crtc->dev; > + unsigned long flags; > + > + if (!crtc->state || !crtc->state->event) > + return; > + > + drm_dbg(ddev, "send vblank manually\n"); > + > + spin_lock_irqsave(&ddev->event_lock, flags); > + drm_crtc_send_vblank_event(crtc, crtc->state->event); > + crtc->state->event = NULL; > + spin_unlock_irqrestore(&ddev->event_lock, flags); > +} > + > +static void lsdc_crtc_atomic_enable(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; > + > + drm_crtc_vblank_on(crtc); > + > + drm_dbg(crtc->dev, "%s enable\n", crtc->name); > + > + ops->enable(lcrtc); > +} > + > +static void lsdc_crtc_atomic_disable(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; > + > + drm_crtc_vblank_off(crtc); > + > + ops->disable(lcrtc); > + > + drm_dbg(crtc->dev, "%s disable\n", crtc->name); > + > + /* > + * Make sure we issue a vblank event after disabling the CRTC if > + * someone was waiting it. > + */ > + lsdc_crtc_send_vblank(crtc); > +} > + > +static void lsdc_crtc_atomic_flush(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + spin_lock_irq(&crtc->dev->event_lock); > + if (crtc->state->event) { > + if (drm_crtc_vblank_get(crtc) == 0) > + drm_crtc_arm_vblank_event(crtc, crtc->state->event); > + else > + drm_crtc_send_vblank_event(crtc, crtc->state->event); > + crtc->state->event = NULL; > + } > + spin_unlock_irq(&crtc->dev->event_lock); > +} > + > +static bool lsdc_crtc_get_scanout_position(struct drm_crtc *crtc, > + bool in_vblank_irq, > + int *vpos, > + int *hpos, > + ktime_t *stime, > + ktime_t *etime, > + const struct drm_display_mode *mode) > +{ > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + const struct lsdc_crtc_hw_ops *ops = lcrtc->hw_ops; > + int vsw, vbp, vactive_start, vactive_end, vfp_end; > + int x, y; > + > + vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; > + vbp = mode->crtc_vtotal - mode->crtc_vsync_end; > + > + vactive_start = vsw + vbp + 1; > + vactive_end = vactive_start + mode->crtc_vdisplay; > + > + /* last scan line before VSYNC */ > + vfp_end = mode->crtc_vtotal; > + > + if (stime) > + *stime = ktime_get(); > + > + ops->get_scan_pos(lcrtc, &x, &y); > + > + if (y > vactive_end) > + y = y - vfp_end - vactive_start; > + else > + y -= vactive_start; > + > + *vpos = y; > + *hpos = x; > + > + if (etime) > + *etime = ktime_get(); > + > + return true; > +} > + > +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { > + .mode_valid = lsdc_crtc_mode_valid, > + .mode_set_nofb = lsdc_crtc_mode_set_nofb, > + .atomic_enable = lsdc_crtc_atomic_enable, > + .atomic_disable = lsdc_crtc_atomic_disable, > + .atomic_check = lsdc_crtc_helper_atomic_check, > + .atomic_flush = lsdc_crtc_atomic_flush, > + .get_scanout_position = lsdc_crtc_get_scanout_position, > +}; > + > +int ls7a1000_crtc_init(struct drm_device *ddev, > + struct drm_crtc *crtc, > + struct drm_plane *primary, > + struct drm_plane *cursor, > + unsigned int index) > +{ > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + int ret; > + > + ret = lsdc_pixpll_init(&lcrtc->pixpll, ddev, index); > + if (ret) { > + drm_err(ddev, "crtc init with pll failed: %d\n", ret); > + return ret; > + } > + > + lcrtc->ldev = to_lsdc(ddev); > + lcrtc->hw_ops = &ls7a1000_crtc_hw_ops[index]; > + > + ret = drm_crtc_init_with_planes(ddev, > + crtc, > + primary, > + cursor, > + &ls7a1000_crtc_funcs, > + "CRTC-%d", > + index); > + if (ret) { > + drm_err(ddev, "crtc init with planes failed: %d\n", ret); > + return ret; > + } > + > + drm_crtc_helper_add(crtc, &lsdc_crtc_helper_funcs); > + > + ret = drm_mode_crtc_set_gamma_size(crtc, 256); > + if (ret) > + return ret; > + > + drm_crtc_enable_color_mgmt(crtc, 0, false, 256); > + > + return 0; > +} > + > +int ls7a2000_crtc_init(struct drm_device *ddev, > + struct drm_crtc *crtc, > + struct drm_plane *primary, > + struct drm_plane *cursor, > + unsigned int index) > +{ > + struct lsdc_crtc *lcrtc = to_lsdc_crtc(crtc); > + int ret; > + > + ret = lsdc_pixpll_init(&lcrtc->pixpll, ddev, index); > + if (ret) { > + drm_err(ddev, "crtc init with pll failed: %d\n", ret); > + return ret; > + } > + > + lcrtc->ldev = to_lsdc(ddev); > + lcrtc->hw_ops = &ls7a2000_crtc_hw_ops[index]; > + > + ret = drm_crtc_init_with_planes(ddev, > + crtc, > + primary, > + cursor, > + &ls7a2000_crtc_funcs, > + "CRTC-%d", > + index); > + if (ret) { > + drm_err(ddev, "crtc init with planes failed: %d\n", ret); > + return ret; > + } > + > + drm_crtc_helper_add(crtc, &lsdc_crtc_helper_funcs); > + > + ret = drm_mode_crtc_set_gamma_size(crtc, 256); > + if (ret) > + return ret; > + > + drm_crtc_enable_color_mgmt(crtc, 0, false, 256); > + > + return 0; > +} > diff --git a/drivers/gpu/drm/loongson/lsdc_debugfs.c b/drivers/gpu/drm/loongson/lsdc_debugfs.c > new file mode 100644 > index 000000000000..30d70a5dc7d5 > --- /dev/null > +++ b/drivers/gpu/drm/loongson/lsdc_debugfs.c > @@ -0,0 +1,78 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright (C) 2023 Loongson Corporation > + */ > + > +#include > + > +#include "lsdc_drv.h" > +#include "lsdc_gem.h" > +#include "lsdc_probe.h" > +#include "lsdc_ttm.h" > + > +/* device level debugfs */ > + > +static int lsdc_identify(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct lsdc_device *ldev = (struct lsdc_device *)node->info_ent->data; > + const struct loongson_gfx_desc *gfx = to_loongson_gfx(ldev->descp); > + u8 impl, rev; > + > + loongson_cpu_get_prid(&impl, &rev); > + > + seq_printf(m, "Running on cpu 0x%x, cpu revision: 0x%x\n", > + impl, rev); > + > + seq_printf(m, "Contained in: %s\n", gfx->model); > + > + return 0; > +} > + > +static int lsdc_show_mm(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct drm_device *ddev = node->minor->dev; > + struct drm_printer p = drm_seq_file_printer(m); > + > + drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p); > + > + return 0; > +} > + > +static int loongson_gfxpll_show_clock(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct lsdc_device *ldev = (struct lsdc_device *)node->info_ent->data; > + struct drm_printer printer = drm_seq_file_printer(m); > + struct loongson_gfxpll *gfxpll = ldev->gfxpll; > + > + gfxpll->funcs->print(gfxpll, &printer, true); > + > + return 0; > +} > + > +static struct drm_info_list lsdc_debugfs_list[] = { > + { "chips", lsdc_identify, 0, NULL }, > + { "clocks", loongson_gfxpll_show_clock, 0, NULL }, > + { "mm", lsdc_show_mm, 0, NULL }, > + { "bos", lsdc_show_buffer_object, 0, NULL }, > +}; > + > +void lsdc_debugfs_init(struct drm_minor *minor) > +{ > + struct drm_device *ddev = minor->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + unsigned int N = ARRAY_SIZE(lsdc_debugfs_list); > + unsigned int i; > + > + for (i = 0; i < N; ++i) > + lsdc_debugfs_list[i].data = ldev; > + > + drm_debugfs_create_files(lsdc_debugfs_list, > + N, > + minor->debugfs_root, > + minor); > + > + lsdc_ttm_debugfs_init(ldev); > +} > diff --git a/drivers/gpu/drm/loongson/lsdc_device.c b/drivers/gpu/drm/loongson/lsdc_device.c > new file mode 100644 > index 000000000000..e2dd0e357fa2 > --- /dev/null > +++ b/drivers/gpu/drm/loongson/lsdc_device.c > @@ -0,0 +1,104 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright (C) 2023 Loongson Corporation > + */ > + > +#include > + > +#include "lsdc_drv.h" > + > +static const struct lsdc_kms_funcs ls7a1000_kms_funcs = { > + .create_i2c = lsdc_create_i2c_chan, > + .irq_handler = ls7a1000_dc_irq_handler, > + .output_init = ls7a1000_output_init, > + .cursor_plane_init = ls7a1000_cursor_plane_init, > + .primary_plane_init = lsdc_primary_plane_init, > + .crtc_init = ls7a1000_crtc_init, > +}; > + > +static const struct lsdc_kms_funcs ls7a2000_kms_funcs = { > + .create_i2c = lsdc_create_i2c_chan, > + .irq_handler = ls7a2000_dc_irq_handler, > + .output_init = ls7a2000_output_init, > + .cursor_plane_init = ls7a2000_cursor_plane_init, > + .primary_plane_init = lsdc_primary_plane_init, > + .crtc_init = ls7a2000_crtc_init, > +}; > + > +static const struct loongson_gfx_desc ls7a1000_gfx = { > + .dc = { > + .num_of_crtc = 2, > + .max_pixel_clk = 200000, > + .max_width = 2048, > + .max_height = 2048, > + .num_of_hw_cursor = 1, > + .hw_cursor_w = 32, > + .hw_cursor_h = 32, > + .pitch_align = 256, > + .mc_bits = 40, > + .has_vblank_counter = false, > + .funcs = &ls7a1000_kms_funcs, > + }, > + .conf_reg_base = LS7A1000_CONF_REG_BASE, > + .gfxpll = { > + .reg_offset = LS7A1000_PLL_GFX_REG, > + .reg_size = 8, > + }, > + .pixpll = { > + [0] = { > + .reg_offset = LS7A1000_PIXPLL0_REG, > + .reg_size = 8, > + }, > + [1] = { > + .reg_offset = LS7A1000_PIXPLL1_REG, > + .reg_size = 8, > + }, > + }, > + .chip_id = CHIP_LS7A1000, > + .model = "LS7A1000 bridge chipset", > +}; > + > +static const struct loongson_gfx_desc ls7a2000_gfx = { > + .dc = { > + .num_of_crtc = 2, > + .max_pixel_clk = 350000, > + .max_width = 4096, > + .max_height = 4096, > + .num_of_hw_cursor = 2, > + .hw_cursor_w = 64, > + .hw_cursor_h = 64, > + .pitch_align = 64, > + .mc_bits = 40, /* Support 48 but using 40 for backward compatibility */ > + .has_vblank_counter = true, > + .funcs = &ls7a2000_kms_funcs, > + }, > + .conf_reg_base = LS7A2000_CONF_REG_BASE, > + .gfxpll = { > + .reg_offset = LS7A2000_PLL_GFX_REG, > + .reg_size = 8, > + }, > + .pixpll = { > + [0] = { > + .reg_offset = LS7A2000_PIXPLL0_REG, > + .reg_size = 8, > + }, > + [1] = { > + .reg_offset = LS7A2000_PIXPLL1_REG, > + .reg_size = 8, > + }, > + }, > + .chip_id = CHIP_LS7A2000, > + .model = "LS7A2000 bridge chipset", > +}; > + > +const struct lsdc_desc * > +lsdc_device_probe(struct pci_dev *pdev, enum loongson_chip_id chip_id) > +{ > + if (chip_id == CHIP_LS7A1000) > + return &ls7a1000_gfx.dc; > + > + if (chip_id == CHIP_LS7A2000) > + return &ls7a2000_gfx.dc; > + > + return ERR_PTR(-ENODEV); > +} > diff --git a/drivers/gpu/drm/loongson/lsdc_drv.c b/drivers/gpu/drm/loongson/lsdc_drv.c > new file mode 100644 > index 000000000000..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