Received: by 2002:a05:6358:9144:b0:117:f937:c515 with SMTP id r4csp3977637rwr; Mon, 8 May 2023 00:32:17 -0700 (PDT) X-Google-Smtp-Source: ACHHUZ50G9WkHLSehvJm9I0kZnAo3vZuwH0+Gb/lLcE1BaceA8MWegjyPTlqCzsb/fUBg+5fZhAX X-Received: by 2002:a05:6a20:1583:b0:100:9740:78a5 with SMTP id h3-20020a056a20158300b00100974078a5mr1978875pzj.38.1683531136824; Mon, 08 May 2023 00:32:16 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1683531136; cv=none; d=google.com; s=arc-20160816; b=YiFggOrIVV5XpoBK6+B5V/qE/NU66lYk1Gi+x5kbPZqHzjmIVVVu0eRO0VUwUDDDot 4LxCbcSksp3v5OP6ffzkfuc4PAfitPfB9EljVFaZ0nSM12Q3QN7WXqBHuqBIl/xxUXjb Rpp5+2DAAhGl5p5H1++MVgnvq2KHvdHzllMIa6/iPnGWEwhbBmn9NHWD29XzaID9wl00 t2N6LpVhpFuGUj6GBgVwsBP3yXD5eua7koYYCRzqorlLNoo/IGdFmQBWEPb8FYIbN2Ot ZhMkHsp0HljVukC0cJsBGWk6cip5KwcB1J0jMWszNzt475C5z1dEQ0x6lcj/shotiexc GHjw== 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:references :cc:to:from:content-language:subject:user-agent:mime-version:date :message-id:sender:hmm_source_type:hmm_attache_num:hmm_source_ip; bh=vjz8AMbCzlT6X+ily0w/vB7hOet0/H/2n0M3v0X2d/c=; b=LzexeRLhZAeuPFew8M7bYMIRZxxU/xhYGQVZKnzb/cbFKlnRBv7gljaFc3yhKLRXOU SNIE6d/p3zrQoXmSYzCjny0wt32ca0q9JCSVQugwt/K1OcZQSaJXeBiLoJQwa6xYM051 7rArTa3XZiD0+szYcGYVk57IZsdDq4OnCsKA+/NNuKvyHgL0F7UZJ6EsQ00GvnbVoOcZ zH4R1lTIc9EKNuBbO23hKsXrZal/spqJ2ugZHoLy2d3+HbyJPeyBcppDYhD4TuHYDmjL jpp9B6HLTuWPooi1GbMSEll/t9fu6wX+wJWhpND/S+bcjtMtZnAy/MKKACQCvpFPl0Tv nnPg== 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 s17-20020a63af51000000b005142038a8c7si8108880pgo.291.2023.05.08.00.32.04; Mon, 08 May 2023 00:32:16 -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 S233155AbjEHHWu (ORCPT + 99 others); Mon, 8 May 2023 03:22:50 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:37116 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S232717AbjEHHWs (ORCPT ); Mon, 8 May 2023 03:22:48 -0400 Received: from 189.cn (ptr.189.cn [183.61.185.102]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id A940810C4; Mon, 8 May 2023 00:22:40 -0700 (PDT) HMM_SOURCE_IP: 10.64.8.31:38102.471752073 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 1C9AD1002D7; Mon, 8 May 2023 15:22:29 +0800 (CST) Received: from ([114.242.206.180]) by gateway-151646-dep-85667d6c59-6qwzn with ESMTP id 68a9061666bf4e32a37f80d41938cb50 for maarten.lankhorst@linux.intel.com; Mon, 08 May 2023 15:22:39 CST X-Transaction-ID: 68a9061666bf4e32a37f80d41938cb50 X-Real-From: 15330273260@189.cn X-Receive-IP: 114.242.206.180 X-MEDUSA-Status: 0 Sender: 15330273260@189.cn Message-ID: Date: Mon, 8 May 2023 15:22:26 +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 Content-Language: en-US From: Sui Jingfeng <15330273260@189.cn> 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> <2fb024e7-617b-e3c5-fc01-a626132b1d30@189.cn> In-Reply-To: <2fb024e7-617b-e3c5-fc01-a626132b1d30@189.cn> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-4.6 required=5.0 tests=BAYES_00, FREEMAIL_ENVFROM_END_DIGIT,FREEMAIL_FROM,FROM_LOCAL_DIGITS, FROM_LOCAL_HEX,NICE_REPLY_A,RCVD_IN_MSPIKE_H2,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, If there are bugs, We will take the responsibility to fix. Is there any chance to merge this trivial patch? comments and review are also welcome. On 2023/5/4 21:08, Sui Jingfeng wrote: > 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