Message ID | 20230205161724.488421-1-15330273260@189.cn |
---|---|
State | New |
Headers |
Return-Path: <linux-kernel-owner@vger.kernel.org> Delivered-To: ouuuleilei@gmail.com Received: by 2002:adf:eb09:0:0:0:0:0 with SMTP id s9csp1842776wrn; Sun, 5 Feb 2023 08:35:58 -0800 (PST) X-Google-Smtp-Source: AK7set8P9y8Q20N3j+XwAr6LJJwSRti/5SjJjOnR+PDnyTdD2W1riLjj/1ACrJkSsAK240D6Uo9V X-Received: by 2002:a05:6402:1505:b0:4a2:254f:6b1a with SMTP id f5-20020a056402150500b004a2254f6b1amr15869169edw.11.1675614958169; Sun, 05 Feb 2023 08:35:58 -0800 (PST) ARC-Seal: i=1; a=rsa-sha256; t=1675614958; cv=none; d=google.com; s=arc-20160816; b=qIFquXfU+WdJiY9pwMYQA5Hfea+mxhlMWfn+caar+qbr3mgUb5WeQgs6tHZeX5xLr8 QMcEib1sODqZBHgr1oGuwi3tOL2L+12kjX+Gn4kTv3ntUlNZzTCefWiTak8x/FOAS4Rb TdpelpMUqN0sdpV9ovhxoej9p+sHRL/XQxrkUxw4Sgy8fa5/QcCJ/N6AZZUhKCsEe27v HEtbK86+QBSnlC7WNSvH+PsVjzIvY99PqCijnbmhfTe+59j734+4cLhsTA983RJBrkLa aVqk0iWh4VKk8WRNcORGcDB/3p5P6Jm6s07eFqbD5/jfbv7wLxu9h+iusxLULIi5YS97 oU8g== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:content-transfer-encoding:mime-version :message-id:date:subject:cc:to:from:sender:hmm_source_type :hmm_attache_num:hmm_source_ip; bh=rQQRBY1lbcj2wWVyhaUQofKYPUUcIZ89m2zg2K1fGz0=; b=beZxbGgPLd3siU22S5hTastPDHAaszJV17IWPnmI5k9MVTyqnKfTozQutMg5ZHDGzW Y2iE+bjvaXZTta0On2ZdKM/5l3zdqjMxhRyjDEizP27DZl6y9iDsp8ji9w9GvlkeR3eu qlL3z76sPZrP9jI0yGxwfpIyOHIp38rYEr+zv6qFRaSAfop77Dfn0KYSkOwWSz28xzkz KWELpVweHeef968mN7l0f43jmh1YM/7mn2Zy1kzQyi8zu8qFiAulgxWJJiqzRu//5ah9 3Yk7y3gNDbsPbhG2/syiSyjRR6zfORpGv4KTN7CbxOCjseuqRqolRoGQVqOKs1fPYyDC k6Tw== 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 Received: from out1.vger.email (out1.vger.email. [2620:137:e000::1:20]) by mx.google.com with ESMTP id fx17-20020a1709069e9100b00883258d9023si8729100ejc.514.2023.02.05.08.35.33; Sun, 05 Feb 2023 08:35:57 -0800 (PST) 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 S229541AbjBEQRy (ORCPT <rfc822;nicolai.engesland@gmail.com> + 99 others); Sun, 5 Feb 2023 11:17:54 -0500 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:54360 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S229494AbjBEQRv (ORCPT <rfc822;linux-kernel@vger.kernel.org>); Sun, 5 Feb 2023 11:17:51 -0500 Received: from 189.cn (ptr.189.cn [183.61.185.102]) by lindbergh.monkeyblade.net (Postfix) with ESMTP id CDD7B1CF54 for <linux-kernel@vger.kernel.org>; Sun, 5 Feb 2023 08:17:42 -0800 (PST) HMM_SOURCE_IP: 10.64.8.31:39964.735971084 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 D3A42100132; Mon, 6 Feb 2023 00:17:39 +0800 (CST) Received: from ([114.242.206.180]) by gateway-151646-dep-698c9d7bb7-c7zbh with ESMTP id a3f78084c27d444baafcf461ed30c648 for maarten.lankhorst@linux.intel.com; Mon, 06 Feb 2023 00:17:40 CST X-Transaction-ID: a3f78084c27d444baafcf461ed30c648 X-Real-From: 15330273260@189.cn X-Receive-IP: 114.242.206.180 X-MEDUSA-Status: 0 Sender: 15330273260@189.cn From: suijingfeng <15330273260@189.cn> To: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>, Maxime Ripard <mripard@kernel.org>, Thomas Zimmermann <tzimmermann@suse.de>, David Airlie <airlied@linux.ie>, Daniel Vetter <daniel@ffwll.ch>, suijingfeng <suijingfeng@loongson.cn> Cc: dri-devel@lists.freedesktop.org, linux-kernel@vger.kernel.org Subject: [PATCH v2] drm: add kms driver for loongson display controller Date: Mon, 6 Feb 2023 00:17:24 +0800 Message-Id: <20230205161724.488421-1-15330273260@189.cn> X-Mailer: git-send-email 2.25.1 MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Spam-Status: No, score=-1.6 required=5.0 tests=BAYES_00, FREEMAIL_ENVFROM_END_DIGIT,FREEMAIL_FROM,FROM_LOCAL_DIGITS, FROM_LOCAL_HEX,RCVD_IN_MSPIKE_H2,SPF_HELO_PASS,SPF_PASS autolearn=no 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: <linux-kernel.vger.kernel.org> X-Mailing-List: linux-kernel@vger.kernel.org X-getmail-retrieved-from-mailbox: =?utf-8?q?INBOX?= X-GMAIL-THRID: =?utf-8?q?1757009630468291010?= X-GMAIL-MSGID: =?utf-8?q?1757009630468291010?= |
Series |
[v2] drm: add kms driver for loongson display controller
|
|
Commit Message
Sui Jingfeng
Feb. 5, 2023, 4:17 p.m. UTC
From: suijingfeng <suijingfeng@loongson.cn> Loongson display controller IP has been integrated in both Loongson North Bridge chipset(ls7a1000 and ls7a2000) and Loongson SoCs(ls2k1000 and ls2k2000 etc), it even been included in Loongson BMC products. The display controller is a PCI device, it has two display pipe. For the DC in LS7A1000 and LS2K1000 each way has a DVO output interface which provide RGB888 signals, vertical & horizontal synchronisations, and the pixel clock. Each CRTC is able to support 1920x1080@60Hz, the maximum resolution is 2048x2048 according to the hardware spec. For the DC in LS7A2000, each display pipe is connected with a built-in HDMI encoder which support 3840x2160@30Hz. the first display pipe is also equipped with a transparent vga encoder which is parallel with the hdmi encoder. The DC in LS7A2000 is more complete, besides above feature, it has two hardware cursors, two hardware vblank counter and two scanout position recoders. It also support hdmi audio and tiled format etc. v2: 1) Use hpd status reg when polling for ls7a2000 2) Fix all warnings emerged when compile with W=1 Signed-off-by: suijingfeng <suijingfeng@loongson.cn> Signed-off-by: suijingfeng <15330273260@189.cn> --- drivers/gpu/drm/Kconfig | 2 + drivers/gpu/drm/Makefile | 1 + drivers/gpu/drm/lsdc/Kconfig | 17 + drivers/gpu/drm/lsdc/Makefile | 14 + drivers/gpu/drm/lsdc/lsdc_crtc.c | 421 ++++++++++++++++++ drivers/gpu/drm/lsdc/lsdc_debugfs.c | 191 +++++++++ drivers/gpu/drm/lsdc/lsdc_drv.c | 635 ++++++++++++++++++++++++++++ drivers/gpu/drm/lsdc/lsdc_drv.h | 274 ++++++++++++ drivers/gpu/drm/lsdc/lsdc_i2c.c | 201 +++++++++ drivers/gpu/drm/lsdc/lsdc_irq.c | 86 ++++ drivers/gpu/drm/lsdc/lsdc_output.c | 452 ++++++++++++++++++++ drivers/gpu/drm/lsdc/lsdc_plane.c | 443 +++++++++++++++++++ drivers/gpu/drm/lsdc/lsdc_pll.c | 569 +++++++++++++++++++++++++ drivers/gpu/drm/lsdc/lsdc_pll.h | 78 ++++ drivers/gpu/drm/lsdc/lsdc_regs.c | 29 ++ drivers/gpu/drm/lsdc/lsdc_regs.h | 296 +++++++++++++ 16 files changed, 3709 insertions(+) create mode 100644 drivers/gpu/drm/lsdc/Kconfig create mode 100644 drivers/gpu/drm/lsdc/Makefile create mode 100644 drivers/gpu/drm/lsdc/lsdc_crtc.c create mode 100644 drivers/gpu/drm/lsdc/lsdc_debugfs.c create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.c create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.h create mode 100644 drivers/gpu/drm/lsdc/lsdc_i2c.c create mode 100644 drivers/gpu/drm/lsdc/lsdc_irq.c create mode 100644 drivers/gpu/drm/lsdc/lsdc_output.c create mode 100644 drivers/gpu/drm/lsdc/lsdc_plane.c create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.c create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.h create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.c create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.h
Comments
Hi Am 05.02.23 um 17:17 schrieb suijingfeng: > From: suijingfeng <suijingfeng@loongson.cn> > > Loongson display controller IP has been integrated in both Loongson > North Bridge chipset(ls7a1000 and ls7a2000) and Loongson SoCs(ls2k1000 > and ls2k2000 etc), it even been included in Loongson BMC products. > > The display controller is a PCI device, it has two display pipe. > For the DC in LS7A1000 and LS2K1000 each way has a DVO output interface > which provide RGB888 signals, vertical & horizontal synchronisations, > and the pixel clock. Each CRTC is able to support 1920x1080@60Hz, > the maximum resolution is 2048x2048 according to the hardware spec. > > For the DC in LS7A2000, each display pipe is connected with a built-in > HDMI encoder which support 3840x2160@30Hz. the first display pipe is > also equipped with a transparent vga encoder which is parallel with > the hdmi encoder. The DC in LS7A2000 is more complete, besides above > feature, it has two hardware cursors, two hardware vblank counter and > two scanout position recoders. It also support hdmi audio and tiled > format etc. > > v2: > 1) Use hpd status reg when polling for ls7a2000 > 2) Fix all warnings emerged when compile with W=1 > > Signed-off-by: suijingfeng <suijingfeng@loongson.cn> > Signed-off-by: suijingfeng <15330273260@189.cn> > --- > drivers/gpu/drm/Kconfig | 2 + > drivers/gpu/drm/Makefile | 1 + > drivers/gpu/drm/lsdc/Kconfig | 17 + > drivers/gpu/drm/lsdc/Makefile | 14 + > drivers/gpu/drm/lsdc/lsdc_crtc.c | 421 ++++++++++++++++++ > drivers/gpu/drm/lsdc/lsdc_debugfs.c | 191 +++++++++ > drivers/gpu/drm/lsdc/lsdc_drv.c | 635 ++++++++++++++++++++++++++++ > drivers/gpu/drm/lsdc/lsdc_drv.h | 274 ++++++++++++ > drivers/gpu/drm/lsdc/lsdc_i2c.c | 201 +++++++++ > drivers/gpu/drm/lsdc/lsdc_irq.c | 86 ++++ > drivers/gpu/drm/lsdc/lsdc_output.c | 452 ++++++++++++++++++++ > drivers/gpu/drm/lsdc/lsdc_plane.c | 443 +++++++++++++++++++ > drivers/gpu/drm/lsdc/lsdc_pll.c | 569 +++++++++++++++++++++++++ > drivers/gpu/drm/lsdc/lsdc_pll.h | 78 ++++ > drivers/gpu/drm/lsdc/lsdc_regs.c | 29 ++ > drivers/gpu/drm/lsdc/lsdc_regs.h | 296 +++++++++++++ > 16 files changed, 3709 insertions(+) > create mode 100644 drivers/gpu/drm/lsdc/Kconfig > create mode 100644 drivers/gpu/drm/lsdc/Makefile > create mode 100644 drivers/gpu/drm/lsdc/lsdc_crtc.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_debugfs.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.h > create mode 100644 drivers/gpu/drm/lsdc/lsdc_i2c.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_irq.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_output.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_plane.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.h > create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.h > > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig > index dc0f94f02a82..3baecd48930b 100644 > --- a/drivers/gpu/drm/Kconfig > +++ b/drivers/gpu/drm/Kconfig > @@ -367,6 +367,8 @@ source "drivers/gpu/drm/solomon/Kconfig" > > source "drivers/gpu/drm/sprd/Kconfig" > > +source "drivers/gpu/drm/lsdc/Kconfig" > + > config DRM_HYPERV > tristate "DRM Support for Hyper-V synthetic video device" > depends on DRM && PCI && MMU && HYPERV > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile > index ab4460fcd63f..5a8e2fc8dd5d 100644 > --- a/drivers/gpu/drm/Makefile > +++ b/drivers/gpu/drm/Makefile > @@ -190,3 +190,4 @@ obj-y += gud/ > obj-$(CONFIG_DRM_HYPERV) += hyperv/ > obj-y += solomon/ > obj-$(CONFIG_DRM_SPRD) += sprd/ > +obj-$(CONFIG_DRM_LSDC) += lsdc/ > diff --git a/drivers/gpu/drm/lsdc/Kconfig b/drivers/gpu/drm/lsdc/Kconfig > new file mode 100644 > index 000000000000..8b9bb6996877 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/Kconfig > @@ -0,0 +1,17 @@ > +# SPDX-License-Identifier: GPL-2.0 > + > +config DRM_LSDC > + tristate "DRM support for loongson display controller" > + depends on DRM && PCI && (MACH_LOONGSON64 || LOONGARCH || MIPS) If possible, I'd add a single 'depends on' statement for each AND condition. Like this depends on DRM depends on PCI depends on (MACH_LOONGSON64 || LOONGARCH || MIPS) Does it build on other architectures if you add COMPILE_TEST to that list of architectures? If so, it would simplify testing a lot. > + select OF I don't think that it is allowed to select OF. Rather use depends on OF I think that general rule is that it is not allowed to auto-select options that are selectable by users. That's the case for OF. > + select DRM_KMS_HELPER > + select DRM_TTM > + select DRM_TTM_HELPER > + select DRM_VRAM_HELPER > + default m No default, especially not 'm'. Everything should be off by default. > + help > + This is a KMS driver for loongson display controller in the > + LS7A1000/LS7A2000 bridge chip and LS2K1000/LS2K0500 SoC. > + If "M" is selected, the module will be called lsdc. > + > + If in doubt, say "Y". Always say "N" by default. There are a few exceptions where "yes" is advised, but regular drivers are not one of them. > diff --git a/drivers/gpu/drm/lsdc/Makefile b/drivers/gpu/drm/lsdc/Makefile > new file mode 100644 > index 000000000000..d4b901ec09cf > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/Makefile > @@ -0,0 +1,14 @@ > +# SPDX-License-Identifier: GPL-2.0 > + > +lsdc-y := \ > + lsdc_drv.o \ > + lsdc_crtc.o \ > + lsdc_irq.o \ > + lsdc_plane.o \ > + lsdc_pll.o \ > + lsdc_i2c.o \ > + lsdc_output.o \ > + lsdc_regs.o \ > + lsdc_debugfs.o Please always sort such lists alphabetically. > + > +obj-$(CONFIG_DRM_LSDC) += lsdc.o > diff --git a/drivers/gpu/drm/lsdc/lsdc_crtc.c b/drivers/gpu/drm/lsdc/lsdc_crtc.c > new file mode 100644 > index 000000000000..e23e6085f418 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_crtc.c > @@ -0,0 +1,421 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include <drm/drm_vblank.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> Same here, alphabetical sorting. > +#include "lsdc_drv.h" > +#include "lsdc_regs.h" > +#include "lsdc_pll.h" Sorting. > + > +static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc) > +{ > + struct lsdc_device *ldev = to_lsdc(crtc->dev); > + unsigned int index = drm_crtc_index(crtc); > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_INT_REG); > + > + if (index == 0) > + val |= INT_CRTC0_VS_EN; > + else if (index == 1) > + val |= INT_CRTC1_VS_EN; > + > + lsdc_wreg32(ldev, LSDC_INT_REG, val); > + > + return 0; > +} > + > +static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc) > +{ > + struct lsdc_device *ldev = to_lsdc(crtc->dev); > + unsigned int index = drm_crtc_index(crtc); > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_INT_REG); > + > + if (index == 0) > + val &= ~INT_CRTC0_VS_EN; > + else if (index == 1) > + val &= ~INT_CRTC1_VS_EN; > + > + lsdc_wreg32(ldev, LSDC_INT_REG, val); > +} > + > +static void lsdc_crtc_reset(struct drm_crtc *crtc) > +{ > + struct drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + unsigned int index = drm_crtc_index(crtc); > + struct lsdc_crtc_state *priv_crtc_state; > + u32 val = CFG_RESET_N | LSDC_PF_XRGB8888; > + > + if (index == 0) > + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); > + else if (index == 1) > + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); > + > + if (crtc->state) { > + priv_crtc_state = to_lsdc_crtc_state(crtc->state); > + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); > + kfree(priv_crtc_state); > + } > + > + priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL); > + if (!priv_crtc_state) > + return; > + > + __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); > + > + drm_info(ddev, "CRTC-%u reset\n", index); No drm_info() here. You could use drm_dbg() if you really want to print something. I'd advise to simply remove that line. > +} > + > +static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, > + struct drm_crtc_state *state) > +{ > + struct lsdc_crtc_state *priv_crtc_state = to_lsdc_crtc_state(state); > + > + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); > + > + kfree(priv_crtc_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 = kmalloc(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_get_vblank_counter(struct drm_crtc *crtc) > +{ > + struct drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + const struct lsdc_desc *descp = ldev->descp; > + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); > + unsigned int index = dispipe->index; > + > + /* fallback to software emulated VBLANK counter */ > + if (!descp->has_vblank_counter) > + goto fallback; > + > + if (index == 0) > + return lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG); > + > + if (index == 1) > + return lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG); > + > +fallback: > + return (u32)drm_crtc_vblank_count(crtc); > +} > + > +static const struct drm_crtc_funcs lsdc_crtc_funcs = { My advise for the design is to untangle the different cases into smaller helpers as much as possible. You have several chips with multiple pipelines. I would advise to provide a funcs array for each possible case. For example: u32 lsdc_crtc0_get_vblank_counter(struct drm_crtc *crtc) { // return LSDC_CRTC0_VSYNC_COUNTER_REG } u32 lsdc_crtc1_get_vblank_counter(struct drm_crtc *crtc) { // return LSDC_CRTC1_VSYNC_COUNTER_REG } and use a different funcs instance for each pipeline struct lsdc_crtc0_funcs_ls2k2000 = { .get_vblank_counter = lsdc_crtc0_get_vblank_counter, }; struct lsdc_crtc1_funcs_ls2k2000 = { .get_vblank_counter = lsdc_crtc1_get_vblank_counter, }; For hardware without vblank counter, use another instance struct lsdc_crtc0_funcs_ls2k0500 = { // no }; At first, this looks like a lot of code. But in the long term, this pays of, because it's so much more maintainable. If you have functions that work with any CRTC, you can still share the implementation. All this goes for other components as well. You may want to study mgag200. It supports ~10 chips. Handling this in single functions was unmaintainable. So we split everything into smaller helpers that each handle a single case. And then we build a dedicated modesetting pipeline for each chip from various helpers. Common code is shared among the chips. Here's where we select the chip: https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_drv.c#L227 There's a source file for each chip and the common source code is in https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_mode.c > + .reset = lsdc_crtc_reset, > + .destroy = drm_crtc_cleanup, > + .set_config = drm_atomic_helper_set_config, > + .page_flip = drm_atomic_helper_page_flip, > + .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, > + .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, > + .get_vblank_counter = lsdc_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, > +}; > + > +static enum drm_mode_status > +lsdc_crtc_mode_valid(struct drm_crtc *crtc, > + const struct drm_display_mode *mode) > +{ > + struct drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + const struct lsdc_desc *descp = ldev->descp; > + > + if (mode->hdisplay > descp->max_width) > + return MODE_BAD_HVALUE; > + if (mode->vdisplay > descp->max_height) > + return MODE_BAD_VVALUE; > + > + if (mode->clock > descp->max_pixel_clk) { > + drm_dbg(ddev, "mode %dx%d, pixel clock=%d is too high\n", > + mode->hdisplay, mode->vdisplay, mode->clock); > + return MODE_CLOCK_HIGH; > + } > + > + if ((mode->hdisplay * 4) % descp->pitch_align) { > + drm_dbg(ddev, "stride is require to not %u bytes aligned\n", > + descp->pitch_align); > + return MODE_BAD; > + } > + > + return MODE_OK; > +} > + > +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc, > + struct drm_crtc_state *state) > +{ > + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); > + struct lsdc_pll *pixpll = &dispipe->pixpll; > + const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; > + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); > + bool ret; > + > + ret = pfuncs->compute(pixpll, state->mode.clock, &priv_state->pparms); > + if (ret) > + return 0; > + > + drm_warn(crtc->dev, "failed find PLL parameters for %u\n", state->mode.clock); > + > + return -EINVAL; > +} > + > +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; /* no mode checks if CRTC is being disabled */ > + > + return lsdc_pixpll_atomic_check(crtc, crtc_state); > +} > + > +static void lsdc_update_pixclk(struct lsdc_display_pipe *dispipe, > + struct lsdc_crtc_state *priv_state) > +{ > + struct lsdc_pll *pixpll = &dispipe->pixpll; > + const struct lsdc_pixpll_funcs *clkfunc = pixpll->funcs; > + > + clkfunc->update(pixpll, &priv_state->pparms); > +} > + > +static void lsdc_crtc_helper_mode_set_nofb(struct drm_crtc *crtc) > +{ > + struct drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct drm_display_mode *mode = &crtc->state->mode; > + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); > + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(crtc->state); > + unsigned int index = dispipe->index; > + u32 h_sync, v_sync, h_val, v_val; > + u32 val; > + > + /* 26:16 total pixels, 10:0 visiable pixels, in horizontal */ > + h_val = (mode->crtc_htotal << 16) | mode->crtc_hdisplay; > + > + /* 26:16 total pixels, 10:0 visiable pixels, in vertical */ > + v_val = (mode->crtc_vtotal << 16) | mode->crtc_vdisplay; > + /* 26:16 hsync end, 10:0 hsync start, bit 30 is hsync enable */ > + h_sync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | CFG_HSYNC_EN; > + if (mode->flags & DRM_MODE_FLAG_NHSYNC) > + h_sync |= CFG_HSYNC_INV; > + > + /* 26:16 vsync end, 10:0 vsync start, bit 30 is vsync enable */ > + v_sync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | CFG_VSYNC_EN; > + if (mode->flags & DRM_MODE_FLAG_NVSYNC) > + v_sync |= CFG_VSYNC_INV; > + > + if (index == 0) { > + lsdc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG, h_val); > + lsdc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG, v_val); > + lsdc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG, h_sync); > + lsdc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG, v_sync); > + > + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; > + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); > + } else if (index == 1) { > + lsdc_wreg32(ldev, LSDC_CRTC1_HDISPLAY_REG, h_val); > + lsdc_wreg32(ldev, LSDC_CRTC1_VDISPLAY_REG, v_val); > + lsdc_wreg32(ldev, LSDC_CRTC1_HSYNC_REG, h_sync); > + lsdc_wreg32(ldev, LSDC_CRTC1_VSYNC_REG, v_sync); > + > + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); > + val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; > + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); > + } Same here as with vblank: have one helper for each index and use different _funcs structures for each pipeline. > + > + drm_dbg(ddev, "%s modeset: %ux%u\n", > + crtc->name, mode->hdisplay, mode->vdisplay); > + > + lsdc_update_pixclk(dispipe, priv_state); > +} > + > +static void lsdc_enable_display(struct lsdc_device *ldev, unsigned int index) > +{ > + u32 val; > + > + if (index == 0) { > + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + val |= CFG_OUTPUT_EN; > + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); > + return; > + } > + > + if (index == 1) { > + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); > + val |= CFG_OUTPUT_EN; > + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); > + return; > + } > +} > + > +static void lsdc_disable_display(struct lsdc_device *ldev, unsigned int index) > +{ > + u32 val; > + > + if (index == 0) { > + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + val &= ~CFG_OUTPUT_EN; > + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); > + return; > + } > + > + if (index == 1) { > + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); > + val &= ~CFG_OUTPUT_EN; > + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); > + return; > + } > +} > + > +static void lsdc_crtc_helper_atomic_enable(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + drm_crtc_vblank_on(crtc); > + > + lsdc_enable_display(ldev, drm_crtc_index(crtc)); > + > + drm_dbg(ddev, "%s: enabled\n", crtc->name); > +} > + > +static void lsdc_crtc_helper_atomic_disable(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + lsdc_disable_display(ldev, drm_crtc_index(crtc)); > + > + drm_crtc_wait_one_vblank(crtc); > + > + drm_crtc_vblank_off(crtc); > + > + drm_dbg(ddev, "%s: disabled\n", crtc->name); > +} > + > +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 drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + unsigned int index = drm_crtc_index(crtc); > + int line, vsw, vbp, vactive_start, vactive_end, vfp_end; > + u32 val = 0; > + > + vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; > + vbp = mode->crtc_vtotal - mode->crtc_vsync_end; > + > + vactive_start = vsw + vbp + 1; > + > + vactive_end = vactive_start + mode->crtc_vdisplay; > + > + /* last scan line before VSYNC */ > + vfp_end = mode->crtc_vtotal; > + > + if (stime) > + *stime = ktime_get(); > + > + if (index == 0) > + val = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); > + else if (index == 1) > + val = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); > + > + line = (val & 0xffff); > + > + if (line < vactive_start) > + line -= vactive_start; > + else if (line > vactive_end) > + line = line - vfp_end - vactive_start; > + else > + line -= vactive_start; > + > + *vpos = line; > + *hpos = val >> 16; > + > + if (etime) > + *etime = ktime_get(); > + > + return true; > +} > + > +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { > + .mode_valid = lsdc_crtc_mode_valid, > + .mode_set_nofb = lsdc_crtc_helper_mode_set_nofb, > + .atomic_enable = lsdc_crtc_helper_atomic_enable, > + .atomic_disable = lsdc_crtc_helper_atomic_disable, > + .atomic_check = lsdc_crtc_helper_atomic_check, > + .atomic_flush = lsdc_crtc_atomic_flush, > + .get_scanout_position = lsdc_crtc_get_scanout_position, > +}; > + > +int lsdc_crtc_init(struct drm_device *ddev, > + struct drm_crtc *crtc, > + unsigned int index, > + struct drm_plane *primary, > + struct drm_plane *cursor) > +{ > + int ret; > + > + ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor, > + &lsdc_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) > + drm_warn(ddev, "set the gamma table size failed\n"); > + > + drm_crtc_enable_color_mgmt(crtc, 0, false, 256); Maybe leave this out for now. You need to handle the property in your driver. I don't see code for that. Color management should probably go into a separate patch. > + > + return 0; > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_debugfs.c b/drivers/gpu/drm/lsdc/lsdc_debugfs.c > new file mode 100644 > index 000000000000..176ae4e11d1c > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_debugfs.c > @@ -0,0 +1,191 @@ > +// SPDX-License-Identifier: GPL-2.0+ > + > +#include <drm/drm_debugfs.h> > +#include <drm/drm_managed.h> > +#include <drm/drm_gem_vram_helper.h> > +#include "lsdc_drv.h" > +#include "lsdc_regs.h" > +#include "lsdc_pll.h" > + > +#ifdef CONFIG_DEBUG_FS > +static int lsdc_show_clock(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct drm_device *ddev = node->minor->dev; > + struct drm_crtc *crtc; > + > + drm_for_each_crtc(crtc, ddev) { > + struct lsdc_display_pipe *pipe = crtc_to_display_pipe(crtc); > + struct lsdc_pll *pixpll = &pipe->pixpll; > + const struct lsdc_pixpll_funcs *funcs = pixpll->funcs; > + struct drm_display_mode *adj = &crtc->state->mode; > + struct lsdc_pll_parms parms; > + unsigned int out_khz; > + > + out_khz = funcs->get_clock_rate(pixpll, &parms); > + > + seq_printf(m, "Display pipe %u: %dx%d\n", > + pipe->index, adj->hdisplay, adj->vdisplay); > + > + seq_printf(m, "Frequency actually output: %u kHz\n", out_khz); > + seq_printf(m, "Pixel clock required: %d kHz\n", adj->clock); > + seq_printf(m, "diff: %d kHz\n", adj->clock); > + > + seq_printf(m, "div_ref=%u, loopc=%u, div_out=%u\n", > + parms.div_ref, parms.loopc, parms.div_out); > + > + seq_printf(m, "hsync_start=%d, hsync_end=%d, htotal=%d\n", > + adj->hsync_start, adj->hsync_end, adj->htotal); > + seq_printf(m, "vsync_start=%d, vsync_end=%d, vtotal=%d\n\n", > + adj->vsync_start, adj->vsync_end, adj->vtotal); > + } > + > + return 0; > +} > + > +static int lsdc_show_mm(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct drm_device *ddev = node->minor->dev; > + struct drm_printer p = drm_seq_file_printer(m); > + > + drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p); > + > + return 0; > +} > + > +#define REGDEF(reg) { __stringify_1(LSDC_##reg##_REG), LSDC_##reg##_REG } > +static const struct { > + const char *name; > + u32 reg_offset; > +} lsdc_regs_array[] = { > + REGDEF(CURSOR0_CFG), > + REGDEF(CURSOR0_ADDR_LO), > + REGDEF(CURSOR0_ADDR_HI), > + REGDEF(CURSOR0_POSITION), > + REGDEF(CURSOR0_BG_COLOR), > + REGDEF(CURSOR0_FG_COLOR), > + REGDEF(INT), > + REGDEF(CRTC0_CFG), > + REGDEF(CRTC0_STRIDE), > + REGDEF(CRTC0_FB_ORIGIN), > + REGDEF(CRTC0_HDISPLAY), > + REGDEF(CRTC0_HSYNC), > + REGDEF(CRTC0_VDISPLAY), > + REGDEF(CRTC0_VSYNC), > + REGDEF(CRTC0_GAMMA_INDEX), > + REGDEF(CRTC0_GAMMA_DATA), > + REGDEF(CRTC1_CFG), > + REGDEF(CRTC1_STRIDE), > + REGDEF(CRTC1_FB_ORIGIN), > + REGDEF(CRTC1_HDISPLAY), > + REGDEF(CRTC1_HSYNC), > + REGDEF(CRTC1_VDISPLAY), > + REGDEF(CRTC1_VSYNC), > + REGDEF(CRTC1_GAMMA_INDEX), > + REGDEF(CRTC1_GAMMA_DATA), > +}; > + > +static int lsdc_show_regs(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct drm_device *ddev = node->minor->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + int i; > + > + for (i = 0; i < ARRAY_SIZE(lsdc_regs_array); i++) { > + u32 offset = lsdc_regs_array[i].reg_offset; > + const char *name = lsdc_regs_array[i].name; > + > + seq_printf(m, "%s (0x%04x): 0x%08x\n", > + name, offset, lsdc_rreg32(ldev, offset)); > + } > + > + return 0; > +} > + > +static int lsdc_show_vblank_counter(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct drm_device *ddev = node->minor->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + seq_printf(m, "CRTC-0 vblank counter: %08u\n", > + lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG)); > + > + seq_printf(m, "CRTC-1 vblank counter: %08u\n", > + lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG)); > + > + return 0; > +} > + > +static int lsdc_show_scan_position(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct drm_device *ddev = node->minor->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + u32 val0 = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); > + u32 val1 = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); > + > + seq_printf(m, "CRTC-0: x: %08u, y: %08u\n", > + val0 >> 16, val0 & 0xFFFF); > + > + seq_printf(m, "CRTC-1: x: %08u, y: %08u\n", > + val1 >> 16, val1 & 0xFFFF); > + > + return 0; > +} > + > +static int lsdc_show_fb_addr(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct drm_device *ddev = node->minor->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + u32 lo, hi; > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + if (val & CFG_FB_IN_USING) { > + lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_LO_ADDR_REG); > + hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_HI_ADDR_REG); > + seq_printf(m, "CRTC-0 using fb1: 0x%x:%x\n", hi, lo); > + } else { > + lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_LO_ADDR_REG); > + hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_HI_ADDR_REG); > + seq_printf(m, "CRTC-0 using fb0: 0x%x:%x\n", hi, lo); > + } > + > + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); > + if (val & CFG_FB_IN_USING) { > + lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_LO_ADDR_REG); > + hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_HI_ADDR_REG); > + seq_printf(m, "CRTC-1 using fb1: 0x%x:%x\n", hi, lo); > + } else { > + lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_LO_ADDR_REG); > + hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_HI_ADDR_REG); > + seq_printf(m, "CRTC-1 using fb0: 0x%x:%x\n", hi, lo); > + } > + > + return 0; > +} > + > +static struct drm_info_list lsdc_debugfs_list[] = { > + { "clocks", lsdc_show_clock, 0 }, > + { "mm", lsdc_show_mm, 0, NULL }, > + { "regs", lsdc_show_regs, 0 }, > + { "vblanks", lsdc_show_vblank_counter, 0, NULL }, > + { "scan_pos", lsdc_show_scan_position, 0, NULL }, > + { "fb_addr", lsdc_show_fb_addr, 0, NULL }, > +}; > + > +#endif > + > +void lsdc_debugfs_init(struct drm_minor *minor) > +{ > +#ifdef CONFIG_DEBUG_FS > + drm_debugfs_create_files(lsdc_debugfs_list, > + ARRAY_SIZE(lsdc_debugfs_list), > + minor->debugfs_root, > + minor); > +#endif > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.c b/drivers/gpu/drm/lsdc/lsdc_drv.c > new file mode 100644 > index 000000000000..3bd457e6e980 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_drv.c > @@ -0,0 +1,635 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * KMS driver for Loongson display controller > + * Copyright (C) 2022 Loongson Corporation > + * > + * Authors: > + * Li Yi <liyi@loongson.cn> > + * Li Chenyang <lichenyang@loongson.cn> > + * Sui Jingfeng <suijingfeng@loongson.cn> > + */ > + > +#include <linux/module.h> > +#include <linux/pci.h> > +#include <linux/of_address.h> > +#include <drm/drm_aperture.h> > +#include <drm/drm_vblank.h> > +#include <drm/drm_fb_helper.h> > +#include <drm/drm_gem_vram_helper.h> > +#include <drm/drm_gem_framebuffer_helper.h> > +#include <drm/drm_modeset_helper.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_probe_helper.h> > +#include <drm/drm_fbdev_generic.h> > +#include "lsdc_drv.h" Alphebetical order. > + > +static int lsdc_chip = -1; > +MODULE_PARM_DESC(chip, "override chip id (-1 = probed by driver"); > +module_param_named(chip, lsdc_chip, int, 0644); That's a development option. Better not add it to the published driver. > + > +/* > + * ls7a1000 and ls7a2000 is not only used with loongson desktop class CPU > + * but also loongson server class CPUs (LS3C5000@2.2GHz 16 core). Loongson > + * server products generally don't has a dedicated vram mounted for cost > + * reason, it will use aspeed bmc as its display on its final and formal > + * product. lsdc.has_vram=0 is mainly be use in developing phase of the > + * product. When a board don't has a dedicated vram mounted, we can put > + * the framebuffer on system RAM. > + */ > +static int lsdc_has_vram = -1; > +MODULE_PARM_DESC(has_vram, "has on-board gpu memory in reality (0 = no, -1 = default"); > +module_param_named(has_vram, lsdc_has_vram, int, 0644); Same here. That's for development. > + > +#define DRIVER_AUTHOR "Sui Jingfeng <suijingfeng@loongson.cn>" > +#define DRIVER_NAME "lsdc" > +#define DRIVER_DESC "drm driver for loongson's display controller" > +#define DRIVER_DATE "20220701" > +#define DRIVER_MAJOR 1 > +#define DRIVER_MINOR 0 > +#define DRIVER_PATCHLEVEL 0 > + > +static const struct lsdc_desc dc_in_ls2k0500 = { Those description structs are a two-edged sword IMHO. > + .chip = CHIP_LS2K0500, > + .num_of_crtc = LSDC_NUM_CRTC, > + .max_pixel_clk = 200000, > + .max_width = 2048, > + .max_height = 2048, > + .hw_cursor_w = 32, > + .hw_cursor_h = 32, > + .pitch_align = 256, > + .mc_bits = 40, These are device constants are probably fine. > + .num_of_hw_cursor = 1, > + .has_vblank_counter = false, > + .has_builtin_i2c = false, > + .has_vram = false, > + .has_hpd_reg = false, > + .is_soc = true, But from a quick look through the code, these settings affect control flow and/or modesetting pipelines. They are often better implemented by dedicated helpers as outlined in my comments about vblank and mgag200. > +}; > + > +static const struct lsdc_desc dc_in_ls2k1000 = { > + .chip = CHIP_LS2K1000, > + .num_of_crtc = LSDC_NUM_CRTC, > + .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, > + .has_builtin_i2c = false, > + .has_vram = false, > + .has_hpd_reg = false, > + .is_soc = true, > +}; > + > +static const struct lsdc_desc dc_in_ls2k2000 = { > + .chip = CHIP_LS2K2000, > + .num_of_crtc = LSDC_NUM_CRTC, > + .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 = 256, > + .mc_bits = 40, > + .has_vblank_counter = true, > + .has_builtin_i2c = true, > + .has_vram = false, > + .has_hpd_reg = true, > + .is_soc = true, > +}; > + > +static const struct lsdc_desc dc_in_ls7a1000 = { > + .chip = CHIP_LS7A1000, > + .num_of_crtc = LSDC_NUM_CRTC, > + .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, > + .has_builtin_i2c = true, > + .has_vram = true, > + .has_hpd_reg = false, > + .is_soc = false, > +}; > + > +static const struct lsdc_desc dc_in_ls7a2000 = { > + .chip = CHIP_LS7A2000, > + .num_of_crtc = LSDC_NUM_CRTC, > + .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 = 256, > + .mc_bits = 40, /* support 48, but use 40 for backward compatibility */ > + .has_vblank_counter = true, > + .has_builtin_i2c = true, > + .has_vram = true, > + .has_hpd_reg = true, > + .is_soc = false, > +}; > + > +static int lsdc_gem_dumb_create(struct drm_file *file, > + struct drm_device *ddev, > + struct drm_mode_create_dumb *args) > +{ > + struct lsdc_device *ldev = to_lsdc(ddev); > + const struct lsdc_desc *descp = ldev->descp; > + > + return drm_gem_vram_fill_create_dumb(file, ddev, 0, descp->pitch_align, args); > +} > + > +DEFINE_DRM_GEM_FOPS(lsdc_gem_fops); > + > +static const struct drm_driver lsdc_drm_driver = { > + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, > + .fops = &lsdc_gem_fops, > + > + .name = DRIVER_NAME, > + .desc = DRIVER_DESC, > + .date = DRIVER_DATE, > + .major = DRIVER_MAJOR, > + .minor = DRIVER_MINOR, > + .patchlevel = DRIVER_PATCHLEVEL, > + > + .debugfs_init = lsdc_debugfs_init, > + .dumb_create = lsdc_gem_dumb_create, > + .dumb_map_offset = drm_gem_ttm_dumb_map_offset, > + .gem_prime_mmap = drm_gem_prime_mmap, > +}; > + > +static const struct drm_mode_config_funcs lsdc_mode_config_funcs = { > + .fb_create = drm_gem_fb_create, > + .output_poll_changed = drm_fb_helper_output_poll_changed, > + .atomic_check = drm_atomic_helper_check, > + .atomic_commit = drm_atomic_helper_commit, > + .mode_valid = drm_vram_helper_mode_valid, > +}; > + > +static int lsdc_modeset_init(struct lsdc_device *ldev, > + const struct lsdc_desc *descp) > +{ > + struct drm_device *ddev = &ldev->base; > + unsigned int num_crtc = descp->num_of_crtc; > + unsigned int i; > + int ret; > + > + for (i = 0; i < num_crtc; i++) { > + ret = lsdc_create_output(ldev, i); > + if (ret) > + return ret; > + } > + > + for (i = 0; i < num_crtc; i++) { > + struct lsdc_display_pipe *dispipe = &ldev->dispipe[i]; > + struct lsdc_pll *pixpll = &dispipe->pixpll; > + struct drm_plane *primary = &dispipe->primary; > + struct drm_plane *cursor = &dispipe->cursor; > + struct drm_crtc *crtc = &dispipe->crtc; > + > + dispipe->index = i; > + > + ret = lsdc_pixpll_init(pixpll, ddev, i); > + if (ret) > + return ret; > + > + ret = lsdc_plane_init(ldev, primary, DRM_PLANE_TYPE_PRIMARY, i); > + if (ret) > + return ret; > + > + ret = lsdc_plane_init(ldev, cursor, DRM_PLANE_TYPE_CURSOR, i); > + if (ret) > + return ret; > + > + /* > + * Initial all of the CRTC available, in this way the logical > + * crtc index is equal to the hardware crtc index. > + * > + * display pipe 0 = crtc0 + dvo0 + encoder0 + connector0 > + * display pipe 1 = crtc1 + dvo1 + encoder1 + connector1 > + */ > + ret = lsdc_crtc_init(ddev, crtc, i, primary, cursor); > + if (ret) > + return ret; > + > + drm_info(ddev, "display pipe %u initialized\n", i); > + } > + > + drm_mode_config_reset(ddev); > + > + return 0; > +} > + > +static int lsdc_mode_config_init(struct drm_device *ddev, > + const struct lsdc_desc *descp) > +{ > + int ret; > + > + ret = drmm_mode_config_init(ddev); > + if (ret) > + return ret; > + > + ddev->mode_config.funcs = &lsdc_mode_config_funcs; > + ddev->mode_config.min_width = 1; > + ddev->mode_config.min_height = 1; > + ddev->mode_config.max_width = descp->max_width * LSDC_NUM_CRTC; > + ddev->mode_config.max_height = descp->max_height * LSDC_NUM_CRTC; > + ddev->mode_config.preferred_depth = 24; > + ddev->mode_config.prefer_shadow = descp->has_vram; > + > + ddev->mode_config.cursor_width = descp->hw_cursor_h; > + ddev->mode_config.cursor_height = descp->hw_cursor_h; > + > + /* max_vblank_count is set on each CRTC */ > + ddev->max_vblank_count = 0xffffffff; > + > + return ret; > +} > + > +/* > + * lsdc_detect_chip - a function to tell lsdc variant apart. > + */ > +static const struct lsdc_desc * > +lsdc_detect_chip(struct pci_dev *pdev, const struct pci_device_id *ent) > +{ > + /* > + * Provide a way to false the driver running on new model > + * before we have a proper way to tell them apart > + */ > + if (lsdc_chip == CHIP_LS2K2000) { > + dev_info(&pdev->dev, "Override as LS2K2000\n"); > + return &dc_in_ls2k2000; > + } > + > + if (lsdc_chip == CHIP_LS2K1000) { > + dev_info(&pdev->dev, "Override as LS2K1000\n"); > + return &dc_in_ls2k1000; > + } > + > + if (lsdc_chip == CHIP_LS2K0500) { > + dev_info(&pdev->dev, "Override as LS2K0500\n"); > + return &dc_in_ls2k0500; > + } > + > + if (ent) { > + struct lsdc_desc *dc_descp; > + > + dc_descp = (struct lsdc_desc *)ent->driver_data; Is that legal? driver_data is of kernel_ulong_t. Maybe store the CHIP_ constant here and then lookup the lsdc_desc. https://elixir.bootlin.com/linux/latest/source/include/linux/mod_devicetable.h#L36 > + if (dc_descp->chip == CHIP_LS7A1000) > + dev_info(&pdev->dev, "LS7A1000 Found revision: %u\n", pdev->revision); > + else if (dc_descp->chip == CHIP_LS7A2000) > + dev_info(&pdev->dev, "LS7A2000 Found revison: %u\n", pdev->revision); > + > + return dc_descp; > + } > + > + dev_err(&pdev->dev, "Not known device\n"); > + > + return NULL; > +} > + > +static int lsdc_get_dedicated_vram(struct lsdc_device *ldev, > + const struct lsdc_desc *descp) > +{ > + struct drm_device *ddev = &ldev->base; > + struct pci_dev *gpu; > + resource_size_t base, size; > + > + /* > + * The GPU and display controller in LS7A1000/LS7A2000 are separated > + * PCIE devices, they are two devices not one. The DC is a pci device, > + * but it don't have a dedicate VRAM bar, the BIOS engineer choose to > + * assign the VRAM to the gpu device. Sadly, after years application, > + * this decision form as a convention for loongson integrate graphics. > + * For LS7A1000 and LS7A2000, bar 2 of GPU device contain the VRAM, > + * both the GPU and the DC can make use of the VRAM depend on how DRM > + * device driver is written. Therefore, we have to do some tricks here. > + */ > + if (descp->chip == CHIP_LS7A1000) > + gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A15, NULL); > + else if (descp->chip == CHIP_LS7A2000) > + gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A25, NULL); > + > + if (!gpu) { > + drm_warn(ddev, "No GPU device found\n"); > + return -ENODEV; > + } > + > + base = pci_resource_start(gpu, 2); > + size = pci_resource_len(gpu, 2); > + > + ldev->vram_base = base; > + ldev->vram_size = size; > + > + drm_info(ddev, "dedicated vram start: 0x%llx, size: %uMB\n", > + (u64)base, (u32)(size >> 20)); > + > + return 0; > +} > + > +/* > + * Loongson display controller is not equipped with IOMMU. > + * In order to make this driver work with Loongson SoC, > + * you need to either carveout part RAM as VRAM or reserve > + * part system ram as vram. Pass the start address and size > + * via DT or ACPI. > + */ > +static int lsdc_of_get_reserved_ram(struct lsdc_device *ldev) > +{ > + struct drm_device *ddev = &ldev->base; > + unsigned long size = 0; > + struct device_node *node; > + struct resource r; > + int ret; > + > + node = of_parse_phandle(ddev->dev->of_node, "memory-region", 0); > + if (!node) { > + drm_err(ddev, "No memory-region property or no DT, abort\n"); > + return -ENOENT; > + } > + > + ret = of_address_to_resource(node, 0, &r); > + of_node_put(node); > + if (ret) > + return ret; > + > + size = r.end - r.start + 1; > + > + ldev->vram_base = r.start; > + ldev->vram_size = size; > + > + drm_info(ddev, "using VRAM carveout: %lx@%pa\n", size, &r.start); > + > + return 0; > +} > + > +static int > +lsdc_remove_conflicting_framebuffers(const struct drm_driver *pdriver, > + resource_size_t base, > + resource_size_t size) > +{ > + return drm_aperture_remove_conflicting_framebuffers(base, > + size, > + false, > + pdriver); > +} No need to wrap this single function. > + > +static struct lsdc_device * > +lsdc_create_device(struct pci_dev *pdev, > + const struct pci_device_id *ent, > + const struct drm_driver *drv) > +{ > + struct lsdc_device *ldev; > + struct drm_device *ddev; > + const struct lsdc_desc *descp; > + int ret; > + > + ldev = devm_drm_dev_alloc(&pdev->dev, drv, struct lsdc_device, base); > + if (IS_ERR(ldev)) > + return ldev; > + > + ddev = &ldev->base; > + > + pci_set_drvdata(pdev, ddev); > + > + descp = lsdc_detect_chip(pdev, ent); > + if (!descp) > + return NULL; > + > + ldev->descp = descp; > + > + spin_lock_init(&ldev->reglock); > + > + /* BAR 0 the DC device contains registers */ > + ldev->reg_base = pcim_iomap(pdev, 0, 0); > + if (!ldev->reg_base) > + return ERR_PTR(-EIO); > + > + if (descp->has_vram && lsdc_has_vram) > + ret = lsdc_get_dedicated_vram(ldev, descp); > + else > + ret = lsdc_of_get_reserved_ram(ldev); > + > + if (ret) { > + drm_err(ddev, "Init VRAM failed: %d\n", ret); > + return ERR_PTR(ret); > + } > + > + ret = lsdc_remove_conflicting_framebuffers(drv, > + ldev->vram_base, > + ldev->vram_size); > + if (ret) { > + drm_err(ddev, "remove firmware framebuffers failed: %d\n", ret); > + return ERR_PTR(ret); > + } > + > + ret = drmm_vram_helper_init(ddev, ldev->vram_base, ldev->vram_size); Using GEM VRAM helpers could become a problem and I'd advise against them. First of all, how much video memory is there? VRAM helpers need at least 3 times the maximum consumption to be reliable. So if you have a FullHD display that consumes ~8 Mib, you'd need 24 MiB of VRAM. Hardware cursors can further interfere. VRAM helpers also don't support buffer sharing with other devices. That's a problem for some compositors. For those reasons, VRAM helpers are deprecated. I'd rather remove them entirely. As a better alternative for the DC, you could use GEM SHMEM helpers. See mgag200 or ast for how to do that. The comments mention that there's also a GPU. Does the GPU use VRAM? Or do GPU and DC share memory ranges? > + if (ret) { > + drm_err(ddev, "vram helper init failed: %d\n", ret); > + goto err; > + } > + > + ret = lsdc_mode_config_init(ddev, descp); > + if (ret) { > + drm_dbg(ddev, "%s: %d\n", __func__, ret); > + goto err; > + } > + > + ret = lsdc_modeset_init(ldev, descp); > + if (ret) > + goto err; > + > + ret = drm_vblank_init(ddev, descp->num_of_crtc); > + if (ret) > + goto err; > + > + ret = request_threaded_irq(pdev->irq, > + lsdc_get_irq_handler(ldev), > + lsdc_irq_thread_handler, > + IRQF_ONESHOT, > + dev_name(ddev->dev), > + ddev); > + if (ret) { > + drm_err(ddev, "Failed to register lsdc interrupt\n"); > + goto err; > + } > + > + drm_kms_helper_poll_init(ddev); > + > + return ldev; > + > +err: > + return ERR_PTR(ret); > +} > + > +static int lsdc_pci_probe(struct pci_dev *pdev, > + const struct pci_device_id *ent) > +{ > + struct drm_device *ddev; > + struct lsdc_device *ldev; > + int ret; > + > + ret = pcim_enable_device(pdev); > + if (ret) > + return ret; > + > + pci_set_master(pdev); > + > + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); > + if (ret) > + return ret; > + > + ldev = lsdc_create_device(pdev, ent, &lsdc_drm_driver); > + if (IS_ERR(ldev)) > + return PTR_ERR(ldev); > + > + ddev = &ldev->base; > + > + ret = drm_dev_register(ddev, 0); > + if (ret) > + return ret; > + > + drm_fbdev_generic_setup(ddev, 32); > + > + return 0; > +} > + > +static void lsdc_pci_remove(struct pci_dev *pdev) > +{ > + struct drm_device *ddev = pci_get_drvdata(pdev); > + > + drm_dev_unregister(ddev); > + drm_atomic_helper_shutdown(ddev); > +} > + > +static int lsdc_drm_freeze(struct drm_device *ddev) > +{ > + int error; > + > + error = drm_mode_config_helper_suspend(ddev); > + if (error) > + return error; > + > + pci_save_state(to_pci_dev(ddev->dev)); > + > + return 0; > +} > + > +static int lsdc_drm_resume(struct device *dev) > +{ > + struct pci_dev *pdev = to_pci_dev(dev); > + struct drm_device *ddev = pci_get_drvdata(pdev); > + > + return drm_mode_config_helper_resume(ddev); > +} > + > +static int lsdc_pm_freeze(struct device *dev) > +{ > + struct pci_dev *pdev = to_pci_dev(dev); > + struct drm_device *ddev = pci_get_drvdata(pdev); > + > + return lsdc_drm_freeze(ddev); > +} > + > +static int lsdc_pm_thaw(struct device *dev) > +{ > + return lsdc_drm_resume(dev); > +} > + > +static int lsdc_pm_suspend(struct device *dev) > +{ > + struct pci_dev *pdev = to_pci_dev(dev); > + int error; > + > + error = lsdc_pm_freeze(dev); > + if (error) > + return error; > + > + pci_save_state(pdev); > + /* Shut down the device */ > + pci_disable_device(pdev); > + pci_set_power_state(pdev, PCI_D3hot); > + > + return 0; > +} > + > +static int lsdc_pm_resume(struct device *dev) > +{ > + struct pci_dev *pdev = to_pci_dev(dev); > + > + if (pcim_enable_device(pdev)) > + return -EIO; > + > + pci_set_power_state(pdev, PCI_D0); > + > + pci_restore_state(pdev); > + > + return lsdc_pm_thaw(dev); > +} > + > +static const struct dev_pm_ops lsdc_pm_ops = { > + .suspend = lsdc_pm_suspend, > + .resume = lsdc_pm_resume, > + .freeze = lsdc_pm_freeze, > + .thaw = lsdc_pm_thaw, > + .poweroff = lsdc_pm_freeze, > + .restore = lsdc_pm_resume, > +}; > + > +static const struct pci_device_id lsdc_pciid_list[] = { > + {PCI_VENDOR_ID_LOONGSON, 0x7a06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, > + (kernel_ulong_t)&dc_in_ls7a1000}, > + {PCI_VENDOR_ID_LOONGSON, 0x7a36, PCI_ANY_ID, PCI_ANY_ID, 0, 0, > + (kernel_ulong_t)&dc_in_ls7a2000}, > + {0, 0, 0, 0, 0, 0, 0} > +}; > + > +static struct pci_driver lsdc_pci_driver = { > + .name = DRIVER_NAME, > + .id_table = lsdc_pciid_list, > + .probe = lsdc_pci_probe, > + .remove = lsdc_pci_remove, > + .driver.pm = &lsdc_pm_ops, > +}; > + > +static int __init lsdc_module_init(void) > +{ > + struct pci_dev *pdev = NULL; > + > + if (drm_firmware_drivers_only()) > + return -EINVAL; return -ENODEV; > + > + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) { > + /* > + * Multiple video card workaround: lsdc will always be > + * selected as the default boot device by vgaarb subsystem. > + */ > + if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) { > + pr_info("Discrete graphic card detected, abort\n"); > + return 0; > + } > + } I don't understand this code. You are not registering your driver if there's another graphics card? Best regards Thomas > + > + return pci_register_driver(&lsdc_pci_driver); > +} > +module_init(lsdc_module_init); > + > +static void __exit lsdc_module_exit(void) > +{ > + pci_unregister_driver(&lsdc_pci_driver); > +} > +module_exit(lsdc_module_exit); > + > +MODULE_DEVICE_TABLE(pci, lsdc_pciid_list); > +MODULE_AUTHOR(DRIVER_AUTHOR); > +MODULE_DESCRIPTION(DRIVER_DESC); > +MODULE_LICENSE("GPL"); > diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.h b/drivers/gpu/drm/lsdc/lsdc_drv.h > new file mode 100644 > index 000000000000..d67dae0414ca > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_drv.h > @@ -0,0 +1,274 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * KMS driver for Loongson display controller > + * Copyright (C) 2022 Loongson Corporation > + * > + * Authors: > + * Li Yi <liyi@loongson.cn> > + * Li Chenyang <lichenyang@loongson.cn> > + * Sui Jingfeng <suijingfeng@loongson.cn> > + */ > + > +#ifndef __LSDC_DRV_H__ > +#define __LSDC_DRV_H__ > + > +#include <linux/i2c.h> > +#include <linux/i2c-algo-bit.h> > + > +#include <drm/drm_print.h> > +#include <drm/drm_device.h> > +#include <drm/drm_crtc.h> > +#include <drm/drm_plane.h> > +#include <drm/drm_connector.h> > +#include <drm/drm_encoder.h> > +#include <drm/drm_drv.h> > +#include <drm/drm_atomic.h> > + > +#include "lsdc_regs.h" > +#include "lsdc_pll.h" > + > +#define LSDC_NUM_CRTC 2 > + > +/* > + * The display controller in LS7A2000 integrate three loongson self-made > + * encoder. Display pipe 0 has a transparent VGA encoder and a HDMI phy, > + * they are parallel. Display pipe 1 has only one HDMI phy. > + * ______________________ _____________ > + * | +-----+ | | | > + * | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA Monitor |<---+ > + * | | +-----+ | |_____________| | > + * | | | ______________ | > + * | | +------+ | | | | > + * | +--> | HDMI | ----> HDMI Connector --> | HDMI Monitor |<--+ > + * | +------+ | |______________| | > + * | +------+ | | > + * | | i2c6 | <-------------------------------------------+ > + * | +------+ | > + * | | > + * | DC in LS7A2000 | > + * | | > + * | +------+ | > + * | | i2c7 | <--------------------------------+ > + * | +------+ | | > + * | | ______|_______ > + * | +------+ | | | > + * | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI Monitor | > + * | +------+ | |______________| > + * |______________________| > + * > + * > + * The display controller in LS7A1000 integrate two-way DVO, external > + * encoder is required except use directly with dpi(rgb888) panel. > + * ___________________ _________ > + * | -------| | | > + * | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | Display | > + * | _ _ -------| ^ ^ |_________| > + * | | | | | -------| | | > + * | |_| |_| | i2c6 <--------+-------------+ > + * | -------| > + * | _ _ -------| > + * | | | | | | i2c7 <--------+-------------+ > + * | |_| |_| -------| | | _________ > + * | -------| | | | | > + * | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | Panel | > + * | -------| |_________| > + * |___________________| > + * > + * > + * The display controller in LS2K1000. > + * ___________________ _________ > + * | -------| | | > + * | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | Display | > + * | _ _ -------| ^ ^ |_________| > + * | | | | | | | | > + * | |_| |_| | +------+ | > + * | <---->| i2c0 |<---------+ > + * | DC in LS2K1000 | +------+ > + * | _ _ | +------+ > + * | | | | | <---->| i2c1 |----------+ > + * | |_| |_| | +------+ | _________ > + * | -------| | | | | > + * | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | Panel | > + * | -------| |_________| > + * |___________________| > + * > + * > + * The display controller in LS2K0500, LS2K0500 has a built-in transparent > + * VGA encoder which is connected to display pipe 1(CRTC1). > + * ___________________ _________ > + * | -------| | | > + * | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | Display | > + * | _ _ -------| ^ ^ |_________| > + * | | | | | | | | > + * | |_| |_| | +------+ | > + * | <---->| i2c4 |<---------+ > + * | DC in LS2K0500 | +------+ > + * | _ _ | +------+ > + * | | | | | <---->| i2c5 |-------------------+ > + * | |_| |_| | +------+ ______|______ > + * | +-----+ | | | > + * | CRTC1 --> | VGA | ------------------------> | VGA Monitor | > + * | +-----+ | |_____________| > + * |___________________| > + * > + * LS7A1000 and LS7A2000 are bridge chips, has dedicated Video RAM. > + * while LS2K2000/LS2K1000/LS2K0500 are SoC, they don't have dediacated > + * Video RAM. The dc in LS2K2000 is basicly same with the dc in LS7A1000 > + * except that LS2K2000 don't have a video RAM and have only one built-in > + * hdmi encoder. > + * > + * There is only a 1:1 mapping of encoders and connectors for the DC, > + * each CRTC have two FB address registers. > + */ > + > +enum loongson_chip_family { > + CHIP_UNKNOWN = 0, > + CHIP_LS7A1000 = 1, /* North bridge of LS3A3000/LS3A4000/LS3A5000 */ > + CHIP_LS7A2000 = 2, /* Update version of LS7A1000, with built-in HDMI encoder */ > + CHIP_LS2K0500 = 3, /* Reduced version of LS2K1000, single core */ > + CHIP_LS2K1000 = 4, /* 2-Core Mips64r2/LA264 SoC, 64-bit, 1.0 Ghz */ > + CHIP_LS2K2000 = 5, /* 2-Core 64-bit LA364 SoC, 1.2 ~ 1.5 Ghz */ > + CHIP_LAST, > +}; > + > +struct lsdc_desc { > + enum loongson_chip_family chip; > + u32 num_of_crtc; > + u32 max_pixel_clk; > + u32 max_width; > + u32 max_height; > + u32 num_of_hw_cursor; > + u32 hw_cursor_w; > + u32 hw_cursor_h; > + u32 pitch_align; /* DMA alignment constraint */ > + u64 mc_bits; /* physical address bus bit width */ > + bool has_vblank_counter; > + bool has_builtin_i2c; > + bool has_vram; > + bool has_hpd_reg; > + bool is_soc; > +}; > + > +struct lsdc_i2c { > + struct i2c_adapter adapter; > + struct i2c_algo_bit_data bit; > + struct drm_device *ddev; > + void __iomem *reg_base; > + void __iomem *dir_reg; > + void __iomem *dat_reg; > + /* pin bit mask */ > + u8 sda; > + u8 scl; > +}; > + > +/* > + * struct lsdc_display_pipe - Abstraction of hardware display pipeline. > + * @crtc: CRTC control structure > + * @primary: framebuffer plane control structure > + * @cursor: cursor plane control structure > + * @output: output control structure of this display pipe bind > + * @pixpll: pixel pll control structure > + * @index: the index corresponding to the hardware display pipe > + */ > +struct lsdc_display_pipe { > + struct drm_crtc crtc; > + struct drm_plane primary; > + struct drm_plane cursor; > + struct drm_encoder encoder; > + struct drm_connector connector; > + struct lsdc_pll pixpll; > + struct lsdc_i2c *li2c; > + int index; > +}; > + > +static inline struct lsdc_display_pipe * > +crtc_to_display_pipe(struct drm_crtc *crtc) > +{ > + return container_of(crtc, struct lsdc_display_pipe, crtc); > +} > + > +static inline struct lsdc_display_pipe * > +cursor_to_display_pipe(struct drm_plane *cursor) > +{ > + return container_of(cursor, struct lsdc_display_pipe, cursor); > +} > + > +static inline struct lsdc_display_pipe * > +connector_to_display_pipe(struct drm_connector *conn) > +{ > + return container_of(conn, struct lsdc_display_pipe, connector); > +} > + > +static inline struct lsdc_display_pipe * > +encoder_to_display_pipe(struct drm_encoder *enc) > +{ > + return container_of(enc, struct lsdc_display_pipe, encoder); > +} > + > +struct lsdc_crtc_state { > + struct drm_crtc_state base; > + struct lsdc_pll_parms pparms; > +}; > + > +struct lsdc_device { > + struct drm_device base; > + > + /* @reglock: protects concurrent register access */ > + spinlock_t reglock; > + void __iomem *reg_base; > + void __iomem *vram; > + resource_size_t vram_base; > + resource_size_t vram_size; > + u64 mc_mask; > + > + struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC]; > + > + /* @num_output: count the number of active display pipe */ > + unsigned int num_output; > + > + /* @descp: features description of the DC variant */ > + const struct lsdc_desc *descp; > + > + u32 irq_status; > +}; > + > +static inline struct lsdc_device * > +to_lsdc(struct drm_device *ddev) > +{ > + return container_of(ddev, struct lsdc_device, base); > +} > + > +static inline struct lsdc_crtc_state * > +to_lsdc_crtc_state(struct drm_crtc_state *base) > +{ > + return container_of(base, struct lsdc_crtc_state, base); > +} > + > +void lsdc_debugfs_init(struct drm_minor *minor); > + > +int lsdc_crtc_init(struct drm_device *ddev, > + struct drm_crtc *crtc, > + unsigned int index, > + struct drm_plane *primary, > + struct drm_plane *cursor); > + > +int lsdc_plane_init(struct lsdc_device *ldev, > + struct drm_plane *plane, > + enum drm_plane_type type, > + unsigned int index); > + > +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index); > + > +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, > + void *base, > + unsigned int index); > + > +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, int index); > + > +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg); > +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev); > + > +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset); > +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 val); > + > +#endif > diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c b/drivers/gpu/drm/lsdc/lsdc_i2c.c > new file mode 100644 > index 000000000000..b380098409ca > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c > @@ -0,0 +1,201 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include <linux/i2c.h> > +#include <drm/drm_managed.h> > +#include "lsdc_drv.h" > +#include "lsdc_regs.h" > + > +/* > + * ls7a_gpio_i2c_set - set the state of a gpio pin indicated by mask > + * @mask: gpio pin mask > + * @state: "0" for low, "1" for high > + */ > +static void ls7a_gpio_i2c_set(struct lsdc_i2c * const li2c, int mask, int state) > +{ > + struct lsdc_device *ldev = to_lsdc(li2c->ddev); > + unsigned long flags; > + u8 val; > + > + spin_lock_irqsave(&ldev->reglock, flags); > + > + if (state) { > + /* > + * Setting this pin as input directly, write 1 for Input. > + * The external pull-up resistor will pull the level up > + */ > + val = readb(li2c->dir_reg); > + val |= mask; > + writeb(val, li2c->dir_reg); > + } else { > + /* First set this pin as output, write 0 for Output */ > + val = readb(li2c->dir_reg); > + val &= ~mask; > + writeb(val, li2c->dir_reg); > + > + /* Then, make this pin output 0 */ > + val = readb(li2c->dat_reg); > + val &= ~mask; > + writeb(val, li2c->dat_reg); > + } > + > + spin_unlock_irqrestore(&ldev->reglock, flags); > +} > + > +/* > + * ls7a_gpio_i2c_get - read value back from the gpio pin indicated by mask > + * @mask: gpio pin mask > + * return "0" for low, "1" for high > + */ > +static int ls7a_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask) > +{ > + struct lsdc_device *ldev = to_lsdc(li2c->ddev); > + unsigned long flags; > + u8 val; > + > + spin_lock_irqsave(&ldev->reglock, flags); > + > + /* First set this pin as input */ > + val = readb(li2c->dir_reg); > + val |= mask; > + writeb(val, li2c->dir_reg); > + > + /* Then get level state from this pin */ > + val = readb(li2c->dat_reg); > + > + spin_unlock_irqrestore(&ldev->reglock, flags); > + > + return (val & mask) ? 1 : 0; > +} > + > +static void ls7a_i2c_set_sda(void *i2c, int state) > +{ > + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; > + /* set state on the li2c->sda pin */ > + return ls7a_gpio_i2c_set(li2c, li2c->sda, state); > +} > + > +static void ls7a_i2c_set_scl(void *i2c, int state) > +{ > + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; > + /* set state on the li2c->scl pin */ > + return ls7a_gpio_i2c_set(li2c, li2c->scl, state); > +} > + > +static int ls7a_i2c_get_sda(void *i2c) > +{ > + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; > + /* read value from the li2c->sda pin */ > + return ls7a_gpio_i2c_get(li2c, li2c->sda); > +} > + > +static int ls7a_i2c_get_scl(void *i2c) > +{ > + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; > + /* read the value from the li2c->scl pin */ > + return ls7a_gpio_i2c_get(li2c, li2c->scl); > +} > + > +static void lsdc_destroy_i2c(struct drm_device *ddev, void *data) > +{ > + struct lsdc_i2c *li2c = (struct lsdc_i2c *)data; > + > + if (li2c) { > + i2c_del_adapter(&li2c->adapter); > + kfree(li2c); > + } > +} > + > +/* > + * The DC in ls7a1000/ls7a2000 have builtin gpio hardware > + * > + * @base: gpio reg base > + * @index: output channel index, 0 for DVO0, 1 for DVO1 > + */ > +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, > + void *base, > + unsigned int index) > +{ > + struct i2c_adapter *adapter; > + struct lsdc_i2c *li2c; > + int ret; > + > + li2c = kzalloc(sizeof(*li2c), GFP_KERNEL); > + if (!li2c) > + return ERR_PTR(-ENOMEM); > + > + if (index == 0) { > + li2c->sda = 0x01; /* pin 0 */ > + li2c->scl = 0x02; /* pin 1 */ > + } else if (index == 1) { > + li2c->sda = 0x04; /* pin 2 */ > + li2c->scl = 0x08; /* pin 3 */ > + } > + > + li2c->reg_base = base; > + li2c->ddev = ddev; > + li2c->dir_reg = li2c->reg_base + LS7A_DC_GPIO_DIR_REG; > + li2c->dat_reg = li2c->reg_base + LS7A_DC_GPIO_DAT_REG; > + > + li2c->bit.setsda = ls7a_i2c_set_sda; > + li2c->bit.setscl = ls7a_i2c_set_scl; > + li2c->bit.getsda = ls7a_i2c_get_sda; > + li2c->bit.getscl = ls7a_i2c_get_scl; > + li2c->bit.udelay = 5; > + li2c->bit.timeout = usecs_to_jiffies(2200); > + li2c->bit.data = li2c; > + > + adapter = &li2c->adapter; > + adapter->algo_data = &li2c->bit; > + adapter->owner = THIS_MODULE; > + adapter->class = I2C_CLASS_DDC; > + adapter->dev.parent = ddev->dev; > + adapter->nr = -1; > + > + snprintf(adapter->name, sizeof(adapter->name), "lsdc-i2c%u", index); > + > + i2c_set_adapdata(adapter, li2c); > + > + ret = i2c_bit_add_bus(adapter); > + if (ret) { > + kfree(li2c); > + return ERR_PTR(ret); > + } > + > + ret = drmm_add_action_or_reset(ddev, lsdc_destroy_i2c, li2c); > + if (ret) > + return NULL; > + > + drm_info(ddev, "%s(sda=%u, scl=%u) created for connector-%u\n", > + adapter->name, li2c->sda, li2c->scl, index); > + > + return li2c; > +} > + > +static int lsdc_get_i2c_id(struct lsdc_device *ldev, int index) > +{ > + const struct lsdc_desc *descp = ldev->descp; > + > + if (descp->chip == CHIP_LS2K1000) > + return index; > + > + if (descp->chip == CHIP_LS2K0500) > + return index + 2; > + > + return index; > +} > + > +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, > + int index) > +{ > + const struct lsdc_desc *descp = ldev->descp; > + struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; > + > + if (descp->has_builtin_i2c) { > + struct lsdc_i2c *li2c = dispipe->li2c; > + > + if (li2c) > + return &li2c->adapter; > + } > + > + return i2c_get_adapter(lsdc_get_i2c_id(ldev, index)); > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.c b/drivers/gpu/drm/lsdc/lsdc_irq.c > new file mode 100644 > index 000000000000..71c85f09bb6f > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_irq.c > @@ -0,0 +1,86 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include <drm/drm_vblank.h> > +#include "lsdc_drv.h" > +#include "lsdc_regs.h" > + > +/* > + * For the DC in ls7a2000, clearing interrupt status is achieved by > + * write "1" to LSDC_INT_REG, For the DC in ls7a1000, ls2k1000 and > + * ls2k0500, clearing interrupt status is achieved by write "0" to > + * LSDC_INT_REG. Two different hardware engineer of Loongson modify > + * it as their will. > + */ > + > +/* For the DC in ls7a2000 */ > +static irqreturn_t lsdc_irq_handler(int irq, void *arg) > +{ > + struct drm_device *ddev = arg; > + struct lsdc_device *ldev = to_lsdc(ddev); > + u32 val; > + > + /* Read the interrupt status */ > + val = lsdc_rreg32(ldev, LSDC_INT_REG); > + if ((val & INT_STATUS_MASK) == 0) { > + drm_warn(ddev, "no interrupt occurs\n"); > + return IRQ_NONE; > + } > + > + ldev->irq_status = val; > + > + /* write "1" to clear the interrupt status */ > + lsdc_wreg32(ldev, LSDC_INT_REG, val); > + > + return IRQ_WAKE_THREAD; > +} > + > +/* For the DC in ls7a1000, ls2k1000 and ls2k0500 */ > +static irqreturn_t lsdc_irq_handler_legacy(int irq, void *arg) > +{ > + struct drm_device *ddev = arg; > + struct lsdc_device *ldev = to_lsdc(ddev); > + u32 val; > + > + /* Read the interrupt status */ > + val = lsdc_rreg32(ldev, LSDC_INT_REG); > + if ((val & INT_STATUS_MASK) == 0) { > + drm_warn(ddev, "no interrupt occurs\n"); > + return IRQ_NONE; > + } > + > + ldev->irq_status = val; > + > + /* write "0" to clear the interrupt status */ > + lsdc_wreg32(ldev, LSDC_INT_REG, val & ~INT_STATUS_MASK); > + > + return IRQ_WAKE_THREAD; > +} > + > +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev) > +{ > + const struct lsdc_desc *descp = ldev->descp; > + > + if (descp->chip == CHIP_LS7A2000) > + return lsdc_irq_handler; > + > + return lsdc_irq_handler_legacy; > +} > + > +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg) > +{ > + struct drm_device *ddev = arg; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct drm_crtc *crtc; > + > + if (ldev->irq_status & INT_CRTC0_VSYNC) { > + crtc = drm_crtc_from_index(ddev, 0); > + drm_crtc_handle_vblank(crtc); > + } > + > + if (ldev->irq_status & INT_CRTC1_VSYNC) { > + crtc = drm_crtc_from_index(ddev, 1); > + drm_crtc_handle_vblank(crtc); > + } > + > + return IRQ_HANDLED; > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_output.c b/drivers/gpu/drm/lsdc/lsdc_output.c > new file mode 100644 > index 000000000000..d4bc7666d9ea > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_output.c > @@ -0,0 +1,452 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include <drm/drm_edid.h> > +#include <drm/drm_probe_helper.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_connector.h> > +#include "lsdc_drv.h" > + > +static int lsdc_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 enum drm_connector_status > +lsdc_unknown_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; > + } else { > + if (connector->connector_type == DRM_MODE_CONNECTOR_DPI) > + return connector_status_connected; > + > + if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) > + return connector_status_connected; > + } > + > + return connector_status_unknown; > +} > + > +static enum drm_connector_status > +lsdc_hdmi_connector_detect(struct drm_connector *connector, bool force) > +{ > + struct lsdc_display_pipe *pipe = connector_to_display_pipe(connector); > + struct lsdc_device *ldev = to_lsdc(connector->dev); > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); > + > + if (pipe->index == 0) { > + if (val & HDMI0_HPD_FLAG) > + return connector_status_connected; > + } > + > + if (pipe->index == 1) { > + if (val & HDMI1_HPD_FLAG) > + return connector_status_connected; > + } > + > + return connector_status_disconnected; > +} > + > +static enum drm_connector_status > +lsdc_hdmi_vga_connector_detect(struct drm_connector *connector, bool force) > +{ > + struct lsdc_display_pipe *pipe = connector_to_display_pipe(connector); > + struct drm_device *ddev = connector->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct i2c_adapter *ddc; > + u32 val; > + > + val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); > + > + if (pipe->index == 1) { > + if (val & HDMI1_HPD_FLAG) > + return connector_status_connected; > + > + return connector_status_disconnected; > + } > + > + if (pipe->index == 0) { > + if (val & HDMI0_HPD_FLAG) > + return connector_status_connected; > + > + ddc = connector->ddc; > + if (ddc) { > + if (drm_probe_ddc(ddc)) > + return connector_status_connected; > + > + return connector_status_disconnected; > + } > + } > + > + return connector_status_unknown; > +} > + > +static struct drm_encoder * > +lsdc_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 lsdc_connector_helpers = { > + .atomic_best_encoder = lsdc_connector_get_best_encoder, > + .get_modes = lsdc_get_modes, > +}; > + > +static const struct drm_connector_funcs lsdc_unknown_connector_funcs = { > + .detect = lsdc_unknown_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 const struct drm_connector_funcs lsdc_hdmi_vga_connector_funcs = { > + .detect = lsdc_hdmi_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, > +}; > + > +static const struct drm_connector_funcs lsdc_hdmi_connector_funcs = { > + .detect = lsdc_hdmi_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 const struct drm_connector_funcs * > +lsdc_get_connector_func(struct lsdc_device *ldev, unsigned int index) > +{ > + const struct lsdc_desc *descp = ldev->descp; > + > + if (descp->chip == CHIP_LS7A2000) { > + if (index == 0) > + return &lsdc_hdmi_vga_connector_funcs; > + > + if (index == 1) > + return &lsdc_hdmi_connector_funcs; > + } > + > + return &lsdc_unknown_connector_funcs; > +} > + > +/* > + * we provide a default support before DT/VBIOS is supported > + */ > +static int lsdc_get_encoder_type(struct lsdc_device *ldev, > + unsigned int index) > +{ > + const struct lsdc_desc *descp = ldev->descp; > + > + if (descp->chip == CHIP_LS7A2000) { > + if (index == 0) > + return DRM_MODE_ENCODER_DAC; > + if (index == 1) > + return DRM_MODE_ENCODER_TMDS; > + } > + > + if (descp->chip == CHIP_LS7A1000 || descp->chip == CHIP_LS2K1000) { > + if (index == 0) > + return DRM_MODE_ENCODER_DPI; > + if (index == 1) > + return DRM_MODE_ENCODER_DPI; > + } > + > + if (descp->chip == CHIP_LS2K0500) { > + if (index == 0) > + return DRM_MODE_ENCODER_DPI; > + if (index == 1) > + return DRM_MODE_ENCODER_DAC; > + } > + > + return DRM_MODE_ENCODER_NONE; > +} > + > +/* > + * provide a default before DT/VBIOS support is upstreamed > + */ > +static int lsdc_get_connector_type(struct lsdc_device *ldev, > + unsigned int index) > +{ > + const struct lsdc_desc *descp = ldev->descp; > + > + if (descp->chip == CHIP_LS7A2000) { > + if (index == 0) > + return DRM_MODE_CONNECTOR_VGA; > + if (index == 1) > + return DRM_MODE_CONNECTOR_HDMIA; > + } > + > + if (descp->chip == CHIP_LS7A1000 || descp->chip == CHIP_LS2K1000) { > + if (index == 0) > + return DRM_MODE_CONNECTOR_DPI; > + if (index == 1) > + return DRM_MODE_CONNECTOR_DPI; > + } > + > + if (descp->chip == CHIP_LS2K0500) { > + if (index == 0) > + return DRM_MODE_CONNECTOR_DPI; > + if (index == 1) > + return DRM_MODE_CONNECTOR_VGA; > + } > + > + return DRM_MODE_CONNECTOR_Unknown; > +} > + > +static void lsdc_hdmi_disable(struct drm_encoder *encoder) > +{ > + 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; > + > + if (index == 0) > + val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG); > + else if (index == 1) > + val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG); > + > + val &= ~HDMI_PHY_EN; > + > + if (index == 0) > + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); > + else if (index == 1) > + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); > + > + drm_dbg(ddev, "HDMI-%u disabled\n", index); > +} > + > +static void lsdc_hdmi_enable(struct drm_encoder *encoder) > +{ > + 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; > + > + /* we are using software gpio emulated i2c */ > + val = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | > + HDMI_PACKET_EN | HDMI_INTERFACE_EN; > + > + if (index == 0) { > + /* Enable HDMI-0 */ > + lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); > + > + lsdc_wreg32(ldev, LSDC_HDMI0_ZONE_REG, 0x00400040); > + } else if (index == 1) { > + /* Enable HDMI-1 */ > + lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); > + > + lsdc_wreg32(ldev, LSDC_HDMI1_ZONE_REG, 0x00400040); > + } > + > + 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; > + > + if (index == 0) > + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); > + else if (index == 1) > + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); > + > + drm_dbg(ddev, "HDMI-%u enabled\n", index); > +} > + > +/* > + * Fout = M * Fin > + * > + * M = (4 * LF) / (IDF * ODF) > + * > + * Loongson HDMI require M = 10 > + */ > +static void lsdc_hdmi_phy_pll_config(struct lsdc_device *ldev, > + int index, > + int clock) > +{ > + struct drm_device *ddev = &ldev->base; > + int count = 0; > + u32 val; > + > + /* disable phy pll */ > + if (index == 0) > + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, 0x0); > + else if (index == 1) > + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, 0x0); > + > + /* > + * 10 = (4 * 40) / (8 * 2) > + */ > + val = (8 << HDMI_PLL_IDF_SHIFT) | > + (40 << HDMI_PLL_LF_SHIFT) | > + (1 << HDMI_PLL_ODF_SHIFT) | > + HDMI_PLL_ENABLE; > + > + drm_dbg(ddev, "HDMI-%u clock: %d\n", index, clock); > + > + if (index == 0) > + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, val); > + else if (index == 1) > + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, val); > + > + /* wait hdmi phy pll lock */ > + do { > + if (index == 0) > + val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG); > + else if (index == 1) > + val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_PLL_REG); > + > + ++count; > + > + if (val & HDMI_PLL_LOCKED) { > + drm_dbg(ddev, "Setting HDMI-%u PLL success(take %d cycles)\n", > + index, count); > + break; > + } > + } while (count < 1000); > +} > + > +static void lsdc_hdmi_atomic_mode_set(struct drm_encoder *encoder, > + struct drm_crtc_state *crtc_state, > + struct drm_connector_state *conn_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; > + struct drm_display_mode *mode = &crtc_state->mode; > + > + lsdc_hdmi_phy_pll_config(ldev, index, mode->clock); > + > + drm_dbg(ddev, "HDMI-%u modeset\n", index); > +} > + > +static const struct drm_encoder_helper_funcs lsdc_hdmi_helper_funcs = { > + .disable = lsdc_hdmi_disable, > + .enable = lsdc_hdmi_enable, > + .atomic_mode_set = lsdc_hdmi_atomic_mode_set, > +}; > + > +static void lsdc_hdmi_reset(struct drm_encoder *encoder) > +{ > + 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 = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | > + HDMI_PACKET_EN | HDMI_INTERFACE_EN; > + > + lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); > + lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); > + > + drm_dbg(ddev, "HDMI-%u Reset\n", index); > +} > + > +static const struct drm_encoder_funcs lsdc_encoder_funcs = { > + .reset = lsdc_hdmi_reset, > + .destroy = drm_encoder_cleanup, > +}; > + > +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index) > +{ > + const struct lsdc_desc *descp = ldev->descp; > + struct drm_device *ddev = &ldev->base; > + struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; > + struct drm_encoder *encoder = &dispipe->encoder; > + struct drm_connector *connector = &dispipe->connector; > + struct i2c_adapter *ddc = NULL; > + struct lsdc_i2c *li2c; > + int ret; > + > + ret = drm_encoder_init(ddev, > + encoder, > + &lsdc_encoder_funcs, > + lsdc_get_encoder_type(ldev, index), > + "encoder-%u", > + index); > + if (ret) { > + drm_err(ddev, "Failed to init encoder: %d\n", ret); > + return ret; > + } > + > + encoder->possible_crtcs = BIT(index); > + > + if (descp->chip == CHIP_LS7A2000) > + drm_encoder_helper_add(encoder, &lsdc_hdmi_helper_funcs); > + > + if (descp->has_builtin_i2c) { > + li2c = lsdc_create_i2c_chan(ddev, ldev->reg_base, index); > + if (IS_ERR(li2c)) > + return PTR_ERR(li2c); > + > + dispipe->li2c = li2c; > + > + ddc = &li2c->adapter; > + } else { > + ddc = lsdc_get_i2c_adapter(ldev, index); > + if (IS_ERR(ddc)) { > + drm_err(ddev, "Error get ddc for output-%u\n", index); > + return PTR_ERR(ddc); > + } > + } > + > + ret = drm_connector_init_with_ddc(ddev, > + connector, > + lsdc_get_connector_func(ldev, index), > + lsdc_get_connector_type(ldev, index), > + ddc); > + if (ret) { > + drm_err(ddev, "Init connector-%d failed\n", index); > + return ret; > + } > + > + drm_connector_helper_add(connector, &lsdc_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; > + > + ldev->num_output++; > + > + drm_info(ddev, "output-%u initialized\n", index); > + > + return 0; > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_plane.c b/drivers/gpu/drm/lsdc/lsdc_plane.c > new file mode 100644 > index 000000000000..0f779c97d53b > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_plane.c > @@ -0,0 +1,443 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include <drm/drm_atomic.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_framebuffer.h> > +#include <drm/drm_plane_helper.h> > +#include <drm/drm_gem_vram_helper.h> > +#include "lsdc_drv.h" > +#include "lsdc_regs.h" > +#include "lsdc_pll.h" > + > +static const u32 lsdc_primary_formats[] = { > + DRM_FORMAT_XRGB8888, > + DRM_FORMAT_ARGB8888, > +}; > + > +static const u32 lsdc_cursor_formats[] = { > + DRM_FORMAT_ARGB8888, > +}; > + > +static const u64 lsdc_fb_format_modifiers[] = { > + DRM_FORMAT_MOD_LINEAR, > + DRM_FORMAT_MOD_INVALID > +}; > + > +static void lsdc_update_fb_format(struct lsdc_device *ldev, > + struct drm_crtc *crtc, > + const struct drm_format_info *fmt_info) > +{ > + unsigned int index = drm_crtc_index(crtc); > + u32 val; > + u32 fmt; > + > + switch (fmt_info->format) { > + case DRM_FORMAT_XRGB8888: > + fmt = LSDC_PF_XRGB8888; > + break; > + case DRM_FORMAT_ARGB8888: > + fmt = LSDC_PF_XRGB8888; > + break; > + default: > + fmt = LSDC_PF_XRGB8888; > + break; > + } > + > + if (index == 0) { > + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + val = (val & ~CFG_PIX_FMT_MASK) | fmt; > + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); > + } else if (index == 1) { > + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); > + val = (val & ~CFG_PIX_FMT_MASK) | fmt; > + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); > + } > +} > + > +static void lsdc_update_fb_start_addr(struct lsdc_device *ldev, > + struct drm_crtc *crtc, > + u64 paddr) > +{ > + struct drm_device *ddev = &ldev->base; > + unsigned int index = drm_crtc_index(crtc); > + u32 lo_addr_reg; > + u32 hi_addr_reg; > + u32 val; > + > + /* > + * Find which framebuffer address register should update. > + * if FB_ADDR0_REG is in using, we write the address to FB_ADDR0_REG, > + * if FB_ADDR1_REG is in using, we write the address to FB_ADDR1_REG > + * for each CRTC, the switch using one fb register to another is > + * trigger by triggered by set CFG_PAGE_FLIP bit of LSDC_CRTCx_CFG_REG > + */ > + if (index == 0) { > + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); > + if (val & CFG_FB_IN_USING) { > + lo_addr_reg = LSDC_CRTC0_FB1_LO_ADDR_REG; > + hi_addr_reg = LSDC_CRTC0_FB1_HI_ADDR_REG; > + drm_dbg(ddev, "Currently, FB1 is in using by CRTC-0\n"); > + } else { > + lo_addr_reg = LSDC_CRTC0_FB0_LO_ADDR_REG; > + hi_addr_reg = LSDC_CRTC0_FB0_HI_ADDR_REG; > + drm_dbg(ddev, "Currently, FB0 is in using by CRTC-0\n"); > + } > + } else if (index == 1) { > + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); > + if (val & CFG_FB_IN_USING) { > + lo_addr_reg = LSDC_CRTC1_FB1_LO_ADDR_REG; > + hi_addr_reg = LSDC_CRTC1_FB1_HI_ADDR_REG; > + drm_dbg(ddev, "Currently, FB1 is in using by CRTC-1\n"); > + } else { > + lo_addr_reg = LSDC_CRTC1_FB0_LO_ADDR_REG; > + hi_addr_reg = LSDC_CRTC1_FB0_HI_ADDR_REG; > + drm_dbg(ddev, "Currently, FB0 is in using by CRTC-1\n"); > + } > + } > + > + /* 40-bit width physical address bus */ > + lsdc_wreg32(ldev, lo_addr_reg, paddr); > + lsdc_wreg32(ldev, hi_addr_reg, (paddr >> 32) & 0xFF); > + > + drm_dbg(ddev, "CRTC-%u scantout from 0x%llx\n", index, paddr); > +} > + > +static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb, > + struct drm_plane_state *state, > + unsigned int plane) > +{ > + unsigned int offset = fb->offsets[plane]; > + > + offset += fb->format->cpp[plane] * (state->src_x >> 16); > + offset += fb->pitches[plane] * (state->src_y >> 16); > + > + return offset; > +} > + > +static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb) > +{ > + struct drm_gem_vram_object *gbo; > + s64 gpu_addr; > + > + gbo = drm_gem_vram_of_gem(fb->obj[0]); > + gpu_addr = drm_gem_vram_offset(gbo); > + > + return gpu_addr; > +} > + > +static int lsdc_primary_plane_atomic_check(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); > + struct drm_crtc *crtc = plane_state->crtc; > + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); > + > + if (!crtc) > + return 0; > + > + return drm_atomic_helper_check_plane_state(plane_state, > + crtc_state, > + DRM_PLANE_NO_SCALING, > + DRM_PLANE_NO_SCALING, > + false, > + true); > +} > + > +static void lsdc_update_fb_stride(struct lsdc_device *ldev, > + struct drm_crtc *crtc, > + unsigned int stride) > +{ > + unsigned int index = drm_crtc_index(crtc); > + > + if (index == 0) > + lsdc_wreg32(ldev, LSDC_CRTC0_STRIDE_REG, stride); > + else if (index == 1) > + lsdc_wreg32(ldev, LSDC_CRTC1_STRIDE_REG, stride); > + > + drm_dbg(crtc->dev, "update stride to %u\n", stride); > +} > + > +static void lsdc_primary_plane_atomic_update(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct lsdc_device *ldev = to_lsdc(plane->dev); > + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); > + struct drm_crtc *crtc = new_plane_state->crtc; > + struct drm_framebuffer *fb = new_plane_state->fb; > + u32 fb_offset = lsdc_get_fb_offset(fb, new_plane_state, 0); > + dma_addr_t fb_addr; > + s64 gpu_addr; > + > + gpu_addr = lsdc_get_vram_bo_offset(fb); > + if (gpu_addr < 0) > + return; > + > + fb_addr = ldev->vram_base + gpu_addr + fb_offset; > + > + lsdc_update_fb_start_addr(ldev, crtc, fb_addr); > + > + lsdc_update_fb_stride(ldev, crtc, fb->pitches[0]); > + > + lsdc_update_fb_format(ldev, crtc, fb->format); > +} > + > +static void lsdc_primary_plane_atomic_disable(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + drm_dbg(plane->dev, "%s disabled\n", plane->name); > +} > + > +static const struct drm_plane_helper_funcs lsdc_primary_plane_helpers = { > + .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, > + .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, > + .atomic_check = lsdc_primary_plane_atomic_check, > + .atomic_update = lsdc_primary_plane_atomic_update, > + .atomic_disable = lsdc_primary_plane_atomic_disable, > +}; > + > +static int lsdc_cursor_atomic_check(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); > + struct drm_framebuffer *fb = new_plane_state->fb; > + struct drm_crtc *crtc = new_plane_state->crtc; > + struct drm_crtc_state *crtc_state; > + int ret; > + > + /* no need for further checks if the plane is being disabled */ > + if (!crtc || !fb) > + return 0; > + > + if (!new_plane_state->visible) > + return 0; > + > + crtc_state = drm_atomic_get_new_crtc_state(state, > + new_plane_state->crtc); > + > + ret = drm_atomic_helper_check_plane_state(new_plane_state, > + crtc_state, > + DRM_PLANE_NO_SCALING, > + DRM_PLANE_NO_SCALING, > + true, > + true); > + > + return ret; > +} > + > +/* > + * There is only one hardware cursor in ls7a1000, ls2k1000 and ls2k0500. > + * we made it shared by the two CRTC, which can satisfy peoples who use > + * double screen extend mode only. On clone screen usage case, the cursor > + * on display pipe 1 will not be able to display. > + * > + * Update location of the cursor, attach it to CRTC0 or CRTC1 on the runtime. > + */ > +static void lsdc_cursor_update_location_quirks(struct lsdc_device *ldev, > + struct drm_crtc *crtc) > +{ > + u32 val = CURSOR_FORMAT_ARGB8888; > + > + /* > + * If bit 4 of LSDC_CURSOR0_CFG_REG is 1, then the cursor will be > + * locate at CRTC-1, if bit 4 of LSDC_CURSOR0_CFG_REG is 0, then > + * the cursor will be locate at CRTC-0. The cursor is alway on the > + * top of the primary. Compositing the primary plane and cursor > + * plane is automatically done by hardware. > + */ > + if (drm_crtc_index(crtc)) > + val |= CURSOR_LOCATION; > + > + lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, val); > +} > + > +/* update the position of the cursor */ > +static void lsdc_cursor_update_position_quirks(struct lsdc_device *ldev, > + int x, > + int y) > +{ > + if (x < 0) > + x = 0; > + > + if (y < 0) > + y = 0; > + > + lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); > +} > + > +static void lsdc_cursor_atomic_update_quirks(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct drm_device *ddev = plane->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); > + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); > + struct drm_framebuffer *new_fb = new_plane_state->fb; > + struct drm_framebuffer *old_fb = old_plane_state->fb; > + > + if (new_fb != old_fb) { > + s64 offset = lsdc_get_vram_bo_offset(new_fb); > + u64 cursor_addr = ldev->vram_base + offset; > + > + drm_dbg_kms(ddev, "%s offset: %llx\n", plane->name, offset); > + > + lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); > + lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >> 32) & 0xFF); > + } > + > + lsdc_cursor_update_position_quirks(ldev, new_plane_state->crtc_x, new_plane_state->crtc_y); > + > + lsdc_cursor_update_location_quirks(ldev, new_plane_state->crtc); > +} > + > +/* update the format, size and location of the cursor */ > +static void lsdc_cursor_atomic_update(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct drm_device *ddev = plane->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct lsdc_display_pipe *dispipe = cursor_to_display_pipe(plane); > + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); > + struct drm_framebuffer *new_fb = new_plane_state->fb; > + int x = new_plane_state->crtc_x; > + int y = new_plane_state->crtc_y; > + u32 conf = CURSOR_FORMAT_ARGB8888 | CURSOR_SIZE_64X64; > + u64 cursor_addr = ldev->vram_base + lsdc_get_vram_bo_offset(new_fb); > + > + if (x < 0) > + x = 0; > + > + if (y < 0) > + y = 0; > + > + if (dispipe->index == 0) { > + lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >> 32) & 0xFF); > + lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); > + /* Attach Cursor-0 to CRTC-0 */ > + lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, conf & ~CURSOR_LOCATION); > + lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); > + return; > + } > + > + if (dispipe->index == 1) { > + lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_HI_REG, (cursor_addr >> 32) & 0xFF); > + lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_LO_REG, cursor_addr); > + /* Attach Cursor-1 to CRTC-1 */ > + lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, conf | CURSOR_LOCATION); > + lsdc_wreg32(ldev, LSDC_CURSOR1_POSITION_REG, (y << 16) | x); > + return; > + } > +} > + > +static void lsdc_cursor_atomic_disable_quirks(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct drm_device *ddev = plane->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + /* Set the format to 0 actually display the cursor */ > + lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); > + > + drm_dbg(ddev, "%s disabled\n", plane->name); > +} > + > +static void lsdc_cursor_atomic_disable(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct drm_device *ddev = plane->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct lsdc_display_pipe *dispipe = cursor_to_display_pipe(plane); > + > + if (dispipe->index == 0) > + lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); > + else if (dispipe->index == 1) > + lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, 0); > + > + drm_dbg(ddev, "%s disabled\n", plane->name); > +} > + > +static const struct drm_plane_helper_funcs lsdc_cursor_helpers_quirk = { > + .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, > + .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, > + .atomic_check = lsdc_cursor_atomic_check, > + .atomic_update = lsdc_cursor_atomic_update_quirks, > + .atomic_disable = lsdc_cursor_atomic_disable_quirks, > +}; > + > +static const struct drm_plane_helper_funcs lsdc_cursor_plane_helpers = { > + .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, > + .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, > + .atomic_check = lsdc_cursor_atomic_check, > + .atomic_update = lsdc_cursor_atomic_update, > + .atomic_disable = lsdc_cursor_atomic_disable, > +}; > + > +static const struct drm_plane_funcs lsdc_plane_funcs = { > + .update_plane = drm_atomic_helper_update_plane, > + .disable_plane = drm_atomic_helper_disable_plane, > + .destroy = drm_plane_cleanup, > + .reset = drm_atomic_helper_plane_reset, > + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, > +}; > + > +static const struct drm_plane_helper_funcs * > +lsdc_get_cursor_helper_funcs(const struct lsdc_desc *descp) > +{ > + if (descp->chip == CHIP_LS7A2000) > + return &lsdc_cursor_plane_helpers; > + > + return &lsdc_cursor_helpers_quirk; > +} > + > +int lsdc_plane_init(struct lsdc_device *ldev, > + struct drm_plane *plane, > + enum drm_plane_type type, > + unsigned int index) > +{ > + const struct lsdc_desc *descp = ldev->descp; > + struct drm_device *ddev = &ldev->base; > + unsigned int format_count; > + const u32 *formats; > + const char *name; > + int ret; > + > + switch (type) { > + case DRM_PLANE_TYPE_PRIMARY: > + formats = lsdc_primary_formats; > + format_count = ARRAY_SIZE(lsdc_primary_formats); > + name = "primary-%u"; > + break; > + case DRM_PLANE_TYPE_CURSOR: > + formats = lsdc_cursor_formats; > + format_count = ARRAY_SIZE(lsdc_cursor_formats); > + name = "cursor-%u"; > + break; > + case DRM_PLANE_TYPE_OVERLAY: > + drm_err(ddev, "overlay plane is not supported\n"); > + break; > + } > + > + ret = drm_universal_plane_init(ddev, plane, 1 << index, > + &lsdc_plane_funcs, > + formats, format_count, > + lsdc_fb_format_modifiers, > + type, name, index); > + if (ret) { > + drm_err(ddev, "%s failed: %d\n", __func__, ret); > + return ret; > + } > + > + switch (type) { > + case DRM_PLANE_TYPE_PRIMARY: > + drm_plane_helper_add(plane, &lsdc_primary_plane_helpers); > + break; > + case DRM_PLANE_TYPE_CURSOR: > + drm_plane_helper_add(plane, lsdc_get_cursor_helper_funcs(descp)); > + break; > + case DRM_PLANE_TYPE_OVERLAY: > + drm_err(ddev, "overlay plane is not supported\n"); > + break; > + } > + > + return 0; > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.c b/drivers/gpu/drm/lsdc/lsdc_pll.c > new file mode 100644 > index 000000000000..6ed74989a6f5 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_pll.c > @@ -0,0 +1,569 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include "lsdc_drv.h" > +#include "lsdc_regs.h" > +#include "lsdc_pll.h" > + > +/* > + * The structure of the pixel PLL register is evolved with times. > + * All loongson's cpu is little endian. > + */ > + > +/* u64 */ > +struct ls7a1000_pixpll_bitmap { > + /* Byte 0 ~ Byte 3 */ > + unsigned div_out : 7; /* 0 : 6 output clock divider */ > + unsigned __1 : 14; /* 7 : 20 */ > + unsigned loopc : 9; /* 21 : 29 clock multiplier */ > + unsigned __2 : 2; /* 30 : 31 */ > + > + /* Byte 4 ~ Byte 7 */ > + unsigned div_ref : 7; /* 0 : 6 input clock divider */ > + unsigned locked : 1; /* 7 PLL locked status */ > + unsigned sel_out : 1; /* 8 output clk selector */ > + unsigned __3 : 2; /* 9 : 10 reserved */ > + unsigned set_param : 1; /* 11 trigger the update */ > + unsigned bypass : 1; /* 12 */ > + unsigned powerdown : 1; /* 13 */ > + unsigned __4 : 18; /* 14 : 31 */ > +}; > + > +/* u128 */ > +struct ls2k1000_pixpll_bitmap { > + /* Byte 0 ~ Byte 3 */ > + unsigned sel_out : 1; /* 0 select this PLL */ > + unsigned __1 : 1; /* 1 */ > + unsigned sw_adj_en : 1; /* 2 allow software adjust */ > + unsigned bypass : 1; /* 3 bypass L1 PLL */ > + unsigned __2 : 3; /* 4:6 */ > + unsigned lock_en : 1; /* 7 enable lock L1 PLL */ > + unsigned __3 : 2; /* 8:9 */ > + unsigned lock_check : 2; /* 10:11 precision check */ > + unsigned __4 : 4; /* 12:15 */ > + > + unsigned locked : 1; /* 16 PLL locked flag bit */ > + unsigned __5 : 2; /* 17:18 */ > + unsigned powerdown : 1; /* 19 powerdown the pll if set */ > + unsigned __6 : 6; /* 20:25 */ > + unsigned div_ref : 6; /* 26:31 L1 Prescaler */ > + > + /* Byte 4 ~ Byte 7 */ > + unsigned loopc : 10; /* 32:41 Clock Multiplier */ > + unsigned l1_div : 6; /* 42:47 not used */ > + unsigned __7 : 16; /* 48:63 */ > + > + /* Byte 8 ~ Byte 15 */ > + unsigned div_out : 6; /* 0 : 5 output clock divider */ > + unsigned __8 : 26; /* 6 : 31 */ > + unsigned __9 : 32; /* 70: 127 */ > +}; > + > +/* u32 */ > +struct ls2k0500_pixpll_bitmap { > + /* Byte 0 ~ Byte 1 */ > + unsigned sel_out : 1; > + unsigned __1 : 2; > + unsigned sw_adj_en : 1; /* allow software adjust */ > + unsigned bypass : 1; /* bypass L1 PLL */ > + unsigned powerdown : 1; /* write 1 to powerdown the PLL */ > + unsigned __2 : 1; > + unsigned locked : 1; /* 7 Is L1 PLL locked, read only */ > + unsigned div_ref : 6; /* 8:13 ref clock divider */ > + unsigned __3 : 2; /* 14:15 */ > + /* Byte 2 ~ Byte 3 */ > + unsigned loopc : 8; /* 16:23 Clock Multiplier */ > + unsigned div_out : 6; /* 24:29 output clock divider */ > + unsigned __4 : 2; /* 30:31 */ > +}; > + > +union lsdc_pixpll_bitmap { > + struct ls7a1000_pixpll_bitmap ls7a1000; > + struct ls2k1000_pixpll_bitmap ls2k1000; > + struct ls2k0500_pixpll_bitmap ls2k0500; > + > + u32 dword[4]; > +}; > + > +struct pixclk_to_pll_parm { > + /* kHz */ > + unsigned int clock; > + > + unsigned short width; > + unsigned short height; > + unsigned short vrefresh; > + > + /* Stores parameters for programming the Hardware PLLs */ > + unsigned short div_out; > + unsigned short loopc; > + unsigned short div_ref; > +}; > + > +/* > + * Pixel clock to PLL parameters translation table. > + * Small static cached value to speed up PLL parameters calculation. > + */ > +static const struct pixclk_to_pll_parm pll_param_table[] = { > + {148500, 1920, 1080, 60, 11, 49, 3}, /* 1920x1080@60Hz */ > + /* 1920x1080@50Hz */ > + {174500, 1920, 1080, 75, 17, 89, 3}, /* 1920x1080@75Hz */ > + {181250, 2560, 1080, 75, 8, 58, 4}, /* 2560x1080@75Hz */ > + {146250, 1680, 1050, 60, 16, 117, 5}, /* 1680x1050@60Hz */ > + {135000, 1280, 1024, 75, 10, 54, 4}, /* 1280x1024@75Hz */ > + > + {108000, 1600, 900, 60, 15, 81, 5}, /* 1600x900@60Hz */ > + /* 1280x1024@60Hz */ > + /* 1280x960@60Hz */ > + /* 1152x864@75Hz */ > + > + {106500, 1440, 900, 60, 19, 81, 4}, /* 1440x900@60Hz */ > + {88750, 1440, 900, 60, 16, 71, 5}, /* 1440x900@60Hz */ > + {83500, 1280, 800, 60, 17, 71, 5}, /* 1280x800@60Hz */ > + {71000, 1280, 800, 60, 20, 71, 5}, /* 1280x800@60Hz */ > + > + {74250, 1280, 720, 60, 22, 49, 3}, /* 1280x720@60Hz */ > + /* 1280x720@50Hz */ > + > + {78750, 1024, 768, 75, 16, 63, 5}, /* 1024x768@75Hz */ > + {75000, 1024, 768, 70, 29, 87, 4}, /* 1024x768@70Hz */ > + {65000, 1024, 768, 60, 20, 39, 3}, /* 1024x768@60Hz */ > + > + {51200, 1024, 600, 60, 25, 64, 5}, /* 1024x600@60Hz */ > + > + {57284, 832, 624, 75, 24, 55, 4}, /* 832x624@75Hz */ > + {49500, 800, 600, 75, 40, 99, 5}, /* 800x600@75Hz */ > + {50000, 800, 600, 72, 44, 88, 4}, /* 800x600@72Hz */ > + {40000, 800, 600, 60, 30, 36, 3}, /* 800x600@60Hz */ > + {36000, 800, 600, 56, 50, 72, 4}, /* 800x600@56Hz */ > + {31500, 640, 480, 75, 40, 63, 5}, /* 640x480@75Hz */ > + /* 640x480@73Hz */ > + > + {30240, 640, 480, 67, 62, 75, 4}, /* 640x480@67Hz */ > + {27000, 720, 576, 50, 50, 54, 4}, /* 720x576@60Hz */ > + {25175, 640, 480, 60, 85, 107, 5}, /* 640x480@60Hz */ > + {25200, 640, 480, 60, 50, 63, 5}, /* 640x480@60Hz */ > + /* 720x480@60Hz */ > +}; > + > +/* > + * lsdc_pixpll_setup - ioremap the device dependent PLL registers > + * > + * @this: point to the object where this function is called from > + */ > +static int lsdc_pixpll_setup(struct lsdc_pll * const this) > +{ > + this->mmio = ioremap(this->reg_base, this->reg_size); > + > + return 0; > +} > + > +/* > + * Find a set of pll parameters (to generate pixel clock) from a static > + * local table, which avoid to compute the pll parameter eachtime a > + * modeset is triggered. > + * > + * @this: point to the object which this function is called from > + * @clock: the desired output pixel clock, the unit is kHz > + * @pout: point to where the parameters to store if found > + * > + * Return true if hit, otherwise return false. > + */ > +static bool lsdc_pixpll_find(struct lsdc_pll * const this, > + unsigned int clock, > + struct lsdc_pll_parms * const pout) > +{ > + unsigned int num = ARRAY_SIZE(pll_param_table); > + unsigned int i; > + > + for (i = 0; i < num; i++) { > + if (clock != pll_param_table[i].clock) > + continue; > + > + pout->div_ref = pll_param_table[i].div_ref; > + pout->loopc = pll_param_table[i].loopc; > + pout->div_out = pll_param_table[i].div_out; > + > + return true; > + } > + > + drm_dbg(this->ddev, "pixel clock %u: miss\n", clock); > + > + return false; > +} > + > +/* > + * Find a set of pll parameters which have minimal difference with the desired > + * pixel clock frequency. It does that by computing all of the possible > + * combination. Compute the diff and find the combination with minimal diff. > + * > + * clock_out = refclk / div_ref * loopc / div_out > + * > + * refclk is determined by the oscillator mounted board(Here is 100MHz in > + * almost all case) > + * > + * @this: point to the object from which this function is called > + * @clock_khz: the desired output pixel clock, the unit is kHz > + * @pout: point to the out struct of lsdc_pll_parms > + * > + * Return true if a parameter is found, otherwise return false > + */ > +static bool lsdc_pixpll_compute(struct lsdc_pll * const this, > + unsigned int clock_khz, > + struct lsdc_pll_parms *pout) > +{ > + unsigned int refclk = this->ref_clock; > + const unsigned int tolerance = 1000; > + unsigned int min = tolerance; > + unsigned int div_out, loopc, div_ref; > + unsigned int computed; > + > + if (lsdc_pixpll_find(this, clock_khz, pout)) > + return true; > + > + for (div_out = 6; div_out < 64; div_out++) { > + for (div_ref = 3; div_ref < 6; div_ref++) { > + for (loopc = 6; loopc < 161; loopc++) { > + int diff; > + > + if (loopc < 12 * div_ref) > + continue; > + if (loopc > 32 * div_ref) > + continue; > + > + computed = refclk * loopc / div_ref / div_out; > + > + if (clock_khz > computed) > + diff = clock_khz - computed; > + else if (clock_khz < computed) > + diff = computed - clock_khz; > + > + if (diff < min) { > + min = diff; > + pout->div_ref = div_ref; > + pout->div_out = div_out; > + pout->loopc = loopc; > + > + if (diff == 0) > + return true; > + } > + } > + } > + } > + > + return min < tolerance; > +} > + > +/* > + * Update the pll parameters to hardware, target to the pixpll in ls7a1000 > + * > + * @this: point to the object from which this function is called > + * @pin: point to the struct of lsdc_pll_parms passed in > + * > + * return 0 if successful. > + */ > +static int ls7a1000_pixpll_param_update(struct lsdc_pll * const this, > + struct lsdc_pll_parms const *pin) > +{ > + void __iomem *reg = this->mmio; > + unsigned int counter = 0; > + bool locked; > + u32 val; > + > + /* Bypass the software configured PLL, using refclk directly */ > + val = readl(reg + 0x4); > + val &= ~(1 << 8); > + writel(val, reg + 0x4); > + > + /* Powerdown the PLL */ > + val = readl(reg + 0x4); > + val |= (1 << 13); > + writel(val, reg + 0x4); > + > + /* Clear the pll parameters */ > + val = readl(reg + 0x4); > + val &= ~(1 << 11); > + writel(val, reg + 0x4); > + > + /* clear old value & config new value */ > + val = readl(reg + 0x04); > + val &= ~0x7F; > + val |= pin->div_ref; /* div_ref */ > + writel(val, reg + 0x4); > + > + val = readl(reg); > + val &= ~0x7f; > + val |= pin->div_out; /* div_out */ > + > + val &= ~(0x1ff << 21); > + val |= pin->loopc << 21; /* loopc */ > + writel(val, reg); > + > + /* Set the pll the parameters */ > + val = readl(reg + 0x4); > + val |= (1 << 11); > + writel(val, reg + 0x4); > + > + /* Powerup the PLL */ > + val = readl(reg + 0x4); > + val &= ~(1 << 13); > + writel(val, reg + 0x4); > + > + /* Wait the PLL lock */ > + do { > + val = readl(reg + 0x4); > + locked = val & 0x80; > + counter++; > + } while (!locked && (counter < 2000)); > + > + drm_dbg(this->ddev, "%u loop waited\n", counter); > + > + /* Switch to the software configured pll */ > + val = readl(reg + 0x4); > + val |= (1UL << 8); > + writel(val, reg + 0x4); > + > + return 0; > +} > + > +/* > + * Update the pll parameters to hardware, target to the pixpll in ls2k1000 > + * > + * @this: point to the object from which this function is called > + * @pin: point to the struct of lsdc_pll_parms passed in > + * > + * return 0 if successful. > + */ > +static int ls2k1000_pixpll_param_update(struct lsdc_pll * const this, > + struct lsdc_pll_parms const *pin) > +{ > + void __iomem *reg = this->mmio; > + unsigned int counter = 0; > + bool locked = false; > + u32 val; > + > + val = readl(reg); > + /* Bypass the software configured PLL, using refclk directly */ > + val &= ~(1 << 0); > + writel(val, reg); > + > + /* Powerdown the PLL */ > + val |= (1 << 19); > + writel(val, reg); > + > + /* Allow the software configuration */ > + val &= ~(1 << 2); > + writel(val, reg); > + > + /* allow L1 PLL lock */ > + val = (1L << 7) | (3L << 10); > + writel(val, reg); > + > + /* clear div_ref bit field */ > + val &= ~(0x3f << 26); > + /* set div_ref bit field */ > + val |= pin->div_ref << 26; > + writel(val, reg); > + > + val = readl(reg + 4); > + /* clear loopc bit field */ > + val &= ~0x0fff; > + /* set loopc bit field */ > + val |= pin->loopc; > + writel(val, reg + 4); > + > + /* set div_out */ > + writel(pin->div_out, reg + 8); > + > + val = readl(reg); > + /* use this parms configured just now */ > + val |= (1 << 2); > + /* powerup the PLL */ > + val &= ~(1 << 19); > + writel(val, reg); > + > + /* wait pll setup and locked */ > + do { > + val = readl(reg); > + locked = val & 0x10000; > + counter++; > + } while (!locked && (counter < 2000)); > + > + drm_dbg(this->ddev, "%u loop waited\n", counter); > + > + /* Switch to software configured PLL instead of refclk */ > + val |= 1; > + writel(val, reg); > + > + return 0; > +} > + > +/* > + * Update the pll parameters to hardware, target to the pixpll in ls2k0500 > + * > + * @this: point to the object which calling this function > + * @param: pointer to where the parameters passed in > + * > + * return 0 if successful. > + */ > +static int ls2k0500_pixpll_param_update(struct lsdc_pll * const this, > + struct lsdc_pll_parms const *param) > +{ > + void __iomem *reg = this->mmio; > + unsigned int counter = 0; > + bool locked = false; > + u32 val; > + > + /* Bypass the software configured PLL, using refclk directly */ > + val = readl(reg); > + val &= ~(1 << 0); > + writel(val, reg); > + > + /* Powerdown the PLL */ > + val = readl(reg); > + val |= (1 << 5); > + writel(val, reg); > + > + /* Allow the software configuration */ > + val |= (1 << 3); > + writel(val, reg); > + > + /* Update the pll params */ > + val = (param->div_out << 24) | > + (param->loopc << 16) | > + (param->div_ref << 8); > + > + writel(val, reg); > + > + /* Powerup the PLL */ > + val = readl(reg); > + val &= ~(1 << 5); > + writel(val, reg); > + > + /* wait pll setup and locked */ > + do { > + val = readl(reg); > + locked = val & 0x80; > + counter++; > + } while (!locked && (counter < 10000)); > + > + drm_dbg(this->ddev, "%u loop waited\n", counter); > + > + /* Switch to software configured PLL instead of refclk */ > + writel((val | 1), reg); > + > + return 0; > +} > + > +static unsigned int lsdc_get_clock_rate(struct lsdc_pll * const this, > + struct lsdc_pll_parms *pout) > +{ > + struct drm_device *ddev = this->ddev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + const struct lsdc_desc * const descp = ldev->descp; > + unsigned int out; > + union lsdc_pixpll_bitmap parms; > + > + if (descp->chip == CHIP_LS7A1000 || descp->chip == CHIP_LS7A2000) { > + struct ls7a1000_pixpll_bitmap *obj = &parms.ls7a1000; > + > + parms.dword[0] = readl(this->mmio); > + parms.dword[1] = readl(this->mmio + 4); > + out = this->ref_clock / obj->div_ref * obj->loopc / obj->div_out; > + if (pout) { > + pout->div_ref = obj->div_ref; > + pout->loopc = obj->loopc; > + pout->div_out = obj->div_out; > + } > + } else if (descp->chip == CHIP_LS2K1000) { > + struct ls2k1000_pixpll_bitmap *obj = &parms.ls2k1000; > + > + parms.dword[0] = readl(this->mmio); > + parms.dword[1] = readl(this->mmio + 4); > + parms.dword[2] = readl(this->mmio + 8); > + parms.dword[3] = readl(this->mmio + 12); > + out = this->ref_clock / obj->div_ref * obj->loopc / obj->div_out; > + if (pout) { > + pout->div_ref = obj->div_ref; > + pout->loopc = obj->loopc; > + pout->div_out = obj->div_out; > + } > + } else if (descp->chip == CHIP_LS2K0500) { > + struct ls2k0500_pixpll_bitmap *obj = &parms.ls2k0500; > + > + parms.dword[0] = readl(this->mmio); > + out = this->ref_clock / obj->div_ref * obj->loopc / obj->div_out; > + if (pout) { > + pout->div_ref = obj->div_ref; > + pout->loopc = obj->loopc; > + pout->div_out = obj->div_out; > + } > + } else { > + drm_err(ddev, "unknown chip, the driver need update\n"); > + return 0; > + } > + > + return out; > +} > + > +static const struct lsdc_pixpll_funcs ls7a1000_pixpll_funcs = { > + .setup = lsdc_pixpll_setup, > + .compute = lsdc_pixpll_compute, > + .update = ls7a1000_pixpll_param_update, > + .get_clock_rate = lsdc_get_clock_rate, > +}; > + > +static const struct lsdc_pixpll_funcs ls2k1000_pixpll_funcs = { > + .setup = lsdc_pixpll_setup, > + .compute = lsdc_pixpll_compute, > + .update = ls2k1000_pixpll_param_update, > + .get_clock_rate = lsdc_get_clock_rate, > +}; > + > +static const struct lsdc_pixpll_funcs ls2k0500_pixpll_funcs = { > + .setup = lsdc_pixpll_setup, > + .compute = lsdc_pixpll_compute, > + .update = ls2k0500_pixpll_param_update, > + .get_clock_rate = lsdc_get_clock_rate, > +}; > + > +int lsdc_pixpll_init(struct lsdc_pll * const this, > + struct drm_device *ddev, > + unsigned int index) > +{ > + struct lsdc_device *ldev = to_lsdc(ddev); > + const struct lsdc_desc *descp = ldev->descp; > + > + this->ddev = ddev; > + this->index = index; > + this->ref_clock = LSDC_PLL_REF_CLK; > + > + /* LS7A1000 and LS7A2000's pixpll setting registers is same */ > + if (descp->chip == CHIP_LS7A2000 || descp->chip == CHIP_LS7A1000) { > + if (index == 0) > + this->reg_base = LS7A1000_CFG_REG_BASE + LS7A1000_PIX_PLL0_REG; > + else if (index == 1) > + this->reg_base = LS7A1000_CFG_REG_BASE + LS7A1000_PIX_PLL1_REG; > + this->reg_size = 8; > + this->funcs = &ls7a1000_pixpll_funcs; > + } else if (descp->chip == CHIP_LS2K1000) { > + if (index == 0) > + this->reg_base = LS2K1000_CFG_REG_BASE + LS2K1000_PIX_PLL0_REG; > + else if (index == 1) > + this->reg_base = LS2K1000_CFG_REG_BASE + LS2K1000_PIX_PLL1_REG; > + > + this->reg_size = 16; > + this->funcs = &ls2k1000_pixpll_funcs; > + } else if (descp->chip == CHIP_LS2K0500) { > + if (index == 0) > + this->reg_base = LS2K0500_CFG_REG_BASE + LS2K0500_PIX_PLL0_REG; > + else if (index == 1) > + this->reg_base = LS2K0500_CFG_REG_BASE + LS2K0500_PIX_PLL1_REG; > + > + this->reg_size = 4; > + this->funcs = &ls2k0500_pixpll_funcs; > + } else { > + drm_err(this->ddev, "unknown chip, the driver need update\n"); > + return -ENOENT; > + } > + > + return this->funcs->setup(this); > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.h b/drivers/gpu/drm/lsdc/lsdc_pll.h > new file mode 100644 > index 000000000000..538739f461f2 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_pll.h > @@ -0,0 +1,78 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > + > +#ifndef __LSDC_PLL_H__ > +#define __LSDC_PLL_H__ > + > +#include <drm/drm_device.h> > + > +/* > + * Loongson Pixel PLL hardware structure > + * > + * refclk: reference frequency, 100 MHz from external oscillator > + * outclk: output frequency desired. > + * > + * > + * L1 Fref Fvco L2 > + * refclk +-----------+ +------------------+ +---------+ outclk > + * ---+---> | Prescaler | ---> | Clock Multiplier | ---> | divider | --------> > + * | +-----------+ +------------------+ +---------+ ^ > + * | ^ ^ ^ | > + * | | | | | > + * | | | | | > + * | div_ref loopc div_out | > + * | | > + * +--- sel_out (bypass above software configurable clock if set) ----+ > + * > + * sel_out: PLL clock output selector (for debug purpose only). > + * > + * If sel_out == 1, it will take refclk as output directly, > + * the L1 Prescaler and the out divider will be bypassed. > + * > + * If sel_out == 0, then outclk = refclk / div_ref * loopc / div_out; > + * > + * PLL working requirements: > + * > + * 1) 20 MHz <= refclk / div_ref <= 40Mhz > + * 2) 1.2 GHz <= refclk /div_out * loopc <= 3.2 Ghz > + */ > + > +struct lsdc_pll_parms { > + unsigned int div_ref; > + unsigned int loopc; > + unsigned int div_out; > +}; > + > +struct lsdc_pll; > + > +struct lsdc_pixpll_funcs { > + int (*setup)(struct lsdc_pll * const this); > + bool (*compute)(struct lsdc_pll * const this, > + unsigned int clock, > + struct lsdc_pll_parms *pout); > + int (*update)(struct lsdc_pll * const this, > + struct lsdc_pll_parms const *pin); > + unsigned int (*get_clock_rate)(struct lsdc_pll * const this, > + struct lsdc_pll_parms *pout); > +}; > + > +struct lsdc_pll { > + const struct lsdc_pixpll_funcs *funcs; > + struct drm_device *ddev; > + void __iomem *mmio; > + > + /* PLL register offset */ > + u32 reg_base; > + /* PLL register size in bytes */ > + u32 reg_size; > + > + /* 100000kHz, fixed on all board found */ > + unsigned int ref_clock; > + > + unsigned int index; > +}; > + > +int lsdc_pixpll_init(struct lsdc_pll * const this, > + struct drm_device *ddev, > + unsigned int index); > + > +#endif > diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.c b/drivers/gpu/drm/lsdc/lsdc_regs.c > new file mode 100644 > index 000000000000..c7950c43968f > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_regs.c > @@ -0,0 +1,29 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +#include "lsdc_drv.h" > +#include "lsdc_regs.h" > + > +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset) > +{ > + unsigned long flags; > + u32 ret; > + > + spin_lock_irqsave(&ldev->reglock, flags); > + > + ret = readl(ldev->reg_base + offset); > + > + spin_unlock_irqrestore(&ldev->reglock, flags); > + > + return ret; > +} > + > +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 val) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&ldev->reglock, flags); > + > + writel(val, ldev->reg_base + offset); > + > + spin_unlock_irqrestore(&ldev->reglock, flags); > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.h b/drivers/gpu/drm/lsdc/lsdc_regs.h > new file mode 100644 > index 000000000000..828956633137 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_regs.h > @@ -0,0 +1,296 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > + > +#ifndef __LSDC_REGS_H__ > +#define __LSDC_REGS_H__ > + > +#include <linux/bitops.h> > +#include <linux/types.h> > + > +/* > + * PIXEL PLL Reference clock > + */ > +#define LSDC_PLL_REF_CLK 100000 /* kHz */ > + > +/* > + * Those PLL registers are not located at DC reg bar space, > + * there are relative to LSXXXXX_CFG_REG_BASE. > + * XXXXX = 7A1000, 2K1000, 2K0500 > + */ > + > +/* LS2K1000 */ > +#define LS2K1000_PIX_PLL0_REG 0x04B0 > +#define LS2K1000_PIX_PLL1_REG 0x04C0 > +#define LS2K1000_CFG_REG_BASE 0x1fe10000 > + > +/* LS7A1000 and LS2K2000 */ > +#define LS7A1000_PIX_PLL0_REG 0x04B0 > +#define LS7A1000_PIX_PLL1_REG 0x04C0 > +#define LS7A1000_CFG_REG_BASE 0x10010000 > + > +/* LS2K0500 */ > +#define LS2K0500_PIX_PLL0_REG 0x0418 > +#define LS2K0500_PIX_PLL1_REG 0x0420 > +#define LS2K0500_CFG_REG_BASE 0x1fe10000 > + > +/* > + * CRTC CFG REG > + */ > +#define CFG_PIX_FMT_MASK GENMASK(2, 0) > + > +enum lsdc_pixel_format { > + LSDC_PF_NONE = 0, > + LSDC_PF_ARGB4444 = 1, /* ARGB A:4 bits R/G/B: 4 bits each [16 bits] */ > + LSDC_PF_ARGB1555 = 2, /* ARGB A:1 bit RGB:15 bits [16 bits] */ > + LSDC_PF_RGB565 = 3, /* RGB [16 bits] */ > + LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */ > + LSDC_PF_RGBA8888 = 5, /* ARGB [32 bits] */ > +}; > + > +/* > + * Each crtc has two set fb address registers usable, CFG_FB_IN_USING of > + * LSDC_CRTCx_CFG_REG specify which fb address register is currently > + * in using by the CRTC. CFG_PAGE_FLIP of LSDC_CRTCx_CFG_REG is used to > + * trigger the switch which will be finished at the very vblank. If you > + * want it switch to another again, you must set CFG_PAGE_FLIP again. > + */ > +#define CFG_PAGE_FLIP BIT(7) > +#define CFG_OUTPUT_EN BIT(8) > +/* CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware logic */ > +#define CFG_PANEL_SWITCH BIT(9) > +/* Indicate witch fb addr reg is in using, currently */ > +#define CFG_FB_IN_USING BIT(11) > +#define CFG_GAMMA_EN BIT(12) > + > +/* CRTC get soft reset if voltage level change from 1 -> 0 */ > +#define CFG_RESET_N BIT(20) > + > +#define CFG_HSYNC_EN BIT(30) > +#define CFG_HSYNC_INV BIT(31) > +#define CFG_VSYNC_EN BIT(30) > +#define CFG_VSYNC_INV BIT(31) > + > +/* THE DMA step of the DC in LS7A2000 is configurable */ > +#define LSDC_DMA_STEP_MASK GENMASK(17, 16) > +enum lsdc_dma_steps_supported { > + LSDC_DMA_STEP_256_BYTES = 0, > + LSDC_DMA_STEP_128_BYTES = 1, > + LSDC_DMA_STEP_64_BYTES = 2, > + LSDC_DMA_STEP_32_BYTES = 3, > +}; > + > +/******** CRTC0 & DVO0 ********/ > +#define LSDC_CRTC0_CFG_REG 0x1240 > +#define LSDC_CRTC0_FB0_LO_ADDR_REG 0x1260 > +#define LSDC_CRTC0_FB0_HI_ADDR_REG 0x15A0 > +#define LSDC_CRTC0_FB1_LO_ADDR_REG 0x1580 > +#define LSDC_CRTC0_FB1_HI_ADDR_REG 0x15C0 > +#define LSDC_CRTC0_STRIDE_REG 0x1280 > +#define LSDC_CRTC0_FB_ORIGIN_REG 0x1300 > +#define LSDC_CRTC0_HDISPLAY_REG 0x1400 > +#define LSDC_CRTC0_HSYNC_REG 0x1420 > +#define LSDC_CRTC0_VDISPLAY_REG 0x1480 > +#define LSDC_CRTC0_VSYNC_REG 0x14A0 > +#define LSDC_CRTC0_GAMMA_INDEX_REG 0x14E0 > +#define LSDC_CRTC0_GAMMA_DATA_REG 0x1500 > + > +/******** CTRC1 & DVO1 ********/ > +#define LSDC_CRTC1_CFG_REG 0x1250 > +#define LSDC_CRTC1_FB0_LO_ADDR_REG 0x1270 > +#define LSDC_CRTC1_FB0_HI_ADDR_REG 0x15B0 > +#define LSDC_CRTC1_FB1_LO_ADDR_REG 0x1590 > +#define LSDC_CRTC1_FB1_HI_ADDR_REG 0x15D0 > +#define LSDC_CRTC1_STRIDE_REG 0x1290 > +#define LSDC_CRTC1_FB_ORIGIN_REG 0x1310 > +#define LSDC_CRTC1_HDISPLAY_REG 0x1410 > +#define LSDC_CRTC1_HSYNC_REG 0x1430 > +#define LSDC_CRTC1_VDISPLAY_REG 0x1490 > +#define LSDC_CRTC1_VSYNC_REG 0x14B0 > +#define LSDC_CRTC1_GAMMA_INDEX_REG 0x14F0 > +#define LSDC_CRTC1_GAMMA_DATA_REG 0x1510 > + > +/* > + * LS7A2000 has the hardware which record the scan position of the CRTC > + * [31:16] : current X position, [15:0] : current Y position > + */ > +#define LSDC_CRTC0_SCAN_POS_REG 0x14C0 > +#define LSDC_CRTC1_SCAN_POS_REG 0x14D0 > + > +/* > + * LS7A2000 has the hardware which count the number of vblank generated > + */ > +#define LSDC_CRTC0_VSYNC_COUNTER_REG 0x1A00 > +#define LSDC_CRTC1_VSYNC_COUNTER_REG 0x1A10 > + > +/* In all, LSDC_CRTC1_XXX_REG - LSDC_CRTC0_XXX_REG = 0x10 */ > + > +/* > + * There is only one hardware cursor unit in ls7a1000, ls2k1000 and ls2k0500. > + * well, ls7a2000 has two hardware cursor unit. > + */ > +#define CURSOR_FORMAT_MASK GENMASK(1, 0) > +enum lsdc_cursor_format { > + CURSOR_FORMAT_DISABLE = 0, > + CURSOR_FORMAT_MONOCHROME = 1, > + CURSOR_FORMAT_ARGB8888 = 2, > +}; > + > +#define CURSOR_SIZE_64X64 BIT(2) > +#define CURSOR_LOCATION BIT(4) > + > +#define LSDC_CURSOR0_CFG_REG 0x1520 > +#define LSDC_CURSOR0_ADDR_LO_REG 0x1530 > +#define LSDC_CURSOR0_ADDR_HI_REG 0x15e0 > +#define LSDC_CURSOR0_POSITION_REG 0x1540 > +#define LSDC_CURSOR0_BG_COLOR_REG 0x1550 /* background color */ > +#define LSDC_CURSOR0_FG_COLOR_REG 0x1560 /* foreground color */ > + > +#define LSDC_CURSOR1_CFG_REG 0x1670 > +#define LSDC_CURSOR1_ADDR_LO_REG 0x1680 > +#define LSDC_CURSOR1_ADDR_HI_REG 0x16e0 > +#define LSDC_CURSOR1_POSITION_REG 0x1690 /* [31:16] Y, [15:0] X */ > +#define LSDC_CURSOR1_BG_COLOR_REG 0x16A0 /* background color */ > +#define LSDC_CURSOR1_FG_COLOR_REG 0x16B0 /* foreground color */ > + > +/* > + * DC Interrupt Control Register, 32bit, Address Offset: 1570 > + * > + * Bits 15:0 inidicate the interrupt status > + * Bits 31:16 control enable interrupts corresponding to bit 15:0 or not > + * Write 1 to enable, write 0 to disable > + * > + * RF: Read Finished > + * IDBU: Internal Data Buffer Underflow > + * IDBFU: Internal Data Buffer Fatal Underflow > + * CBRF: Cursor Buffer Read Finished Flag, no use. > + * > + * +-------+--------------------------+-------+--------+--------+-------+ > + * | 31:27 | 26:16 | 15:11 | 10 | 9 | 8 | > + * +-------+--------------------------+-------+--------+--------+-------+ > + * | N/A | Interrupt Enable Control | N/A | IDBFU0 | IDBFU1 | IDBU0 | > + * +-------+--------------------------+-------+--------+--------+-------+ > + * > + * +-------+-----+-----+------+--------+--------+--------+--------+ > + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | > + * +-------+-----+-----+------+--------+--------+--------+--------+ > + * | IDBU1 | RF0 | RF1 | CRRF | HSYNC0 | VSYNC0 | HSYNC1 | VSYNC1 | > + * +-------+-----+-----+------+--------+--------+--------+--------+ > + */ > + > +#define LSDC_INT_REG 0x1570 > + > +#define INT_CRTC0_VSYNC BIT(2) > +#define INT_CRTC0_HSYNC BIT(3) > +#define INT_CRTC0_RF BIT(6) > +#define INT_CRTC0_IDBU BIT(8) > +#define INT_CRTC0_IDBFU BIT(10) > + > +#define INT_CRTC1_VSYNC BIT(0) > +#define INT_CRTC1_HSYNC BIT(1) > +#define INT_CRTC1_RF BIT(5) > +#define INT_CRTC1_IDBU BIT(7) > +#define INT_CRTC1_IDBFU BIT(9) > + > +#define INT_CRTC0_VS_EN BIT(18) > +#define INT_CRTC0_HS_EN BIT(19) > +#define INT_CRTC0_RF_EN BIT(22) > +#define INT_CRTC0_IDBU_EN BIT(24) > +#define INT_CRTC0_IDBFU_EN BIT(26) > + > +#define INT_CRTC1_VS_EN BIT(16) > +#define INT_CRTC1_HS_EN BIT(17) > +#define INT_CRTC1_RF_EN BIT(21) > +#define INT_CRTC1_IDBU_EN BIT(23) > +#define INT_CRTC1_IDBFU_EN BIT(25) > + > +#define INT_STATUS_MASK GENMASK(15, 0) > + > +/* > + * LS7A1000/LS7A2000 have 4 gpios which are used to emulated I2C. > + * They are under control of the LS7A_DC_GPIO_DAT_REG and LS7A_DC_GPIO_DIR_REG > + * register, Those GPIOs has no relationship whth the GPIO hardware on the > + * bridge chip itself. Those offsets are relative to DC register base address > + * > + * LS2k1000 and LS2K0500 don't have those registers, they use hardware i2c > + * or general GPIO emulated i2c from linux i2c subsystem. > + * > + * GPIO data register, address offset: 0x1650 > + * +---------------+-----------+-----------+ > + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | > + * +---------------+-----------+-----------+ > + * | | DVO1 | DVO0 | > + * + N/A +-----------+-----------+ > + * | | SCL | SDA | SCL | SDA | > + * +---------------+-----------+-----------+ > + */ > +#define LS7A_DC_GPIO_DAT_REG 0x1650 > + > +/* > + * GPIO Input/Output direction control register, address offset: 0x1660 > + */ > +#define LS7A_DC_GPIO_DIR_REG 0x1660 > + > +/* > + * LS7A2000 has two built-in HDMI Encoder and one VGA encoder > + */ > + > +/* > + * Number of continuous packets may be present > + * in HDMI hblank and vblank zone, should >= 48 > + */ > +#define LSDC_HDMI0_ZONE_REG 0x1700 > +#define LSDC_HDMI1_ZONE_REG 0x1710 > + > +#define HDMI_INTERFACE_EN BIT(0) > +#define HDMI_PACKET_EN BIT(1) > +#define HDMI_AUDIO_EN BIT(2) > +#define HDMI_VIDEO_PREAMBLE_MASK GENMASK(7, 4) > +#define HDMI_HW_I2C_EN BIT(8) > +#define HDMI_CTL_PERIOD_MODE BIT(9) > +#define LSDC_HDMI0_CTRL_REG 0x1720 > +#define LSDC_HDMI1_CTRL_REG 0x1730 > + > +#define HDMI_PHY_EN BIT(0) > +#define HDMI_PHY_RESET_N BIT(1) > +#define HDMI_PHY_TERM_L_EN BIT(8) > +#define HDMI_PHY_TERM_H_EN BIT(9) > +#define HDMI_PHY_TERM_DET_EN BIT(10) > +#define HDMI_PHY_TERM_STATUS BIT(11) > +#define LSDC_HDMI0_PHY_CTRL_REG 0x1800 > +#define LSDC_HDMI1_PHY_CTRL_REG 0x1810 > + > +/* > + * 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 | > + * +--------------------------------------------------------+ > + */ > +#define LSDC_HDMI0_PHY_PLL_REG 0x1820 > +#define LSDC_HDMI1_PHY_PLL_REG 0x1830 > + > +#define HDMI_PLL_ENABLE BIT(0) > +#define HDMI_PLL_LOCKED BIT(16) > +#define HDMI_PLL_BYPASS BIT(17) > + > +#define HDMI_PLL_IDF_SHIFT 1 > +#define HDMI_PLL_IDF_MASK GENMASK(5, 1) > +#define HDMI_PLL_LF_SHIFT 6 > +#define HDMI_PLL_LF_MASK GENMASK(12, 6) > +#define HDMI_PLL_ODF_SHIFT 13 > +#define HDMI_PLL_ODF_MASK GENMASK(15, 13) > + > +/* LS7A2000 have hpd support */ > +#define LSDC_HDMI_HPD_STATUS_REG 0x1BA0 > +#define HDMI0_HPD_FLAG BIT(0) > +#define HDMI1_HPD_FLAG BIT(1) > + > +#endif -- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Ivo Totev
On 2023/2/10 23:04, Thomas Zimmermann wrote: > Hi > Hello, > Am 05.02.23 um 17:17 schrieb suijingfeng: >> From: suijingfeng <suijingfeng@loongson.cn> >> >> Loongson display controller IP has been integrated in both Loongson >> North Bridge chipset(ls7a1000 and ls7a2000) and Loongson SoCs(ls2k1000 >> and ls2k2000 etc), it even been included in Loongson BMC products. >> >> The display controller is a PCI device, it has two display pipe. >> For the DC in LS7A1000 and LS2K1000 each way has a DVO output interface >> which provide RGB888 signals, vertical & horizontal synchronisations, >> and the pixel clock. Each CRTC is able to support 1920x1080@60Hz, >> the maximum resolution is 2048x2048 according to the hardware spec. >> >> For the DC in LS7A2000, each display pipe is connected with a built-in >> HDMI encoder which support 3840x2160@30Hz. the first display pipe is >> also equipped with a transparent vga encoder which is parallel with >> the hdmi encoder. The DC in LS7A2000 is more complete, besides above >> feature, it has two hardware cursors, two hardware vblank counter and >> two scanout position recoders. It also support hdmi audio and tiled >> format etc. >> >>  v2: >>   1) Use hpd status reg when polling for ls7a2000 >>   2) Fix all warnings emerged when compile with W=1 >> >> Signed-off-by: suijingfeng <suijingfeng@loongson.cn> >> Signed-off-by: suijingfeng <15330273260@189.cn> >> --- >>  drivers/gpu/drm/Kconfig            |  2 + >>  drivers/gpu/drm/Makefile           |  1 + >>  drivers/gpu/drm/lsdc/Kconfig       | 17 + >>  drivers/gpu/drm/lsdc/Makefile      | 14 + >>  drivers/gpu/drm/lsdc/lsdc_crtc.c   | 421 ++++++++++++++++++ >>  drivers/gpu/drm/lsdc/lsdc_debugfs.c | 191 +++++++++ >>  drivers/gpu/drm/lsdc/lsdc_drv.c    | 635 ++++++++++++++++++++++++++++ >>  drivers/gpu/drm/lsdc/lsdc_drv.h    | 274 ++++++++++++ >>  drivers/gpu/drm/lsdc/lsdc_i2c.c    | 201 +++++++++ >>  drivers/gpu/drm/lsdc/lsdc_irq.c    | 86 ++++ >>  drivers/gpu/drm/lsdc/lsdc_output.c | 452 ++++++++++++++++++++ >>  drivers/gpu/drm/lsdc/lsdc_plane.c  | 443 +++++++++++++++++++ >>  drivers/gpu/drm/lsdc/lsdc_pll.c    | 569 +++++++++++++++++++++++++ >>  drivers/gpu/drm/lsdc/lsdc_pll.h    | 78 ++++ >>  drivers/gpu/drm/lsdc/lsdc_regs.c   | 29 ++ >>  drivers/gpu/drm/lsdc/lsdc_regs.h   | 296 +++++++++++++ >>  16 files changed, 3709 insertions(+) >>  create mode 100644 drivers/gpu/drm/lsdc/Kconfig >>  create mode 100644 drivers/gpu/drm/lsdc/Makefile >>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_crtc.c >>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_debugfs.c >>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.c >>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.h >>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_i2c.c >>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_irq.c >>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_output.c >>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_plane.c >>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.c >>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.h >>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.c >>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.h >> >> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig >> index dc0f94f02a82..3baecd48930b 100644 >> --- a/drivers/gpu/drm/Kconfig >> +++ b/drivers/gpu/drm/Kconfig >> @@ -367,6 +367,8 @@ source "drivers/gpu/drm/solomon/Kconfig" >>   source "drivers/gpu/drm/sprd/Kconfig" >>  +source "drivers/gpu/drm/lsdc/Kconfig" >> + >>  config DRM_HYPERV >>      tristate "DRM Support for Hyper-V synthetic video device" >>      depends on DRM && PCI && MMU && HYPERV >> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile >> index ab4460fcd63f..5a8e2fc8dd5d 100644 >> --- a/drivers/gpu/drm/Makefile >> +++ b/drivers/gpu/drm/Makefile >> @@ -190,3 +190,4 @@ obj-y           += gud/ >>  obj-$(CONFIG_DRM_HYPERV) += hyperv/ >>  obj-y           += solomon/ >>  obj-$(CONFIG_DRM_SPRD) += sprd/ >> +obj-$(CONFIG_DRM_LSDC) += lsdc/ >> diff --git a/drivers/gpu/drm/lsdc/Kconfig b/drivers/gpu/drm/lsdc/Kconfig >> new file mode 100644 >> index 000000000000..8b9bb6996877 >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/Kconfig >> @@ -0,0 +1,17 @@ >> +# SPDX-License-Identifier: GPL-2.0 >> + >> +config DRM_LSDC >> +   tristate "DRM support for loongson display controller" >> +   depends on DRM && PCI && (MACH_LOONGSON64 || LOONGARCH || MIPS) > > If possible, I'd add a single 'depends on' statement for each AND > condition. Like this > >  depends on DRM >  depends on PCI >  depends on (MACH_LOONGSON64 || LOONGARCH || MIPS) > > Does it build on other architectures if you add COMPILE_TEST to that > list of architectures? If so, it would simplify testing a lot. > >> +   select OF > > I don't think that it is allowed to select OF. Rather use > >  depends on OF > > I think that general rule is that it is not allowed to auto-select > options that are selectable by users. That's the case for OF. > > Ok, depends on OF is acceptable. >> +   select DRM_KMS_HELPER >> +   select DRM_TTM >> +   select DRM_TTM_HELPER >> +   select DRM_VRAM_HELPER >> +   default m > > No default, especially not 'm'. Everything should be off by default. > >> +   help >> +     This is a KMS driver for loongson display controller in the >> +     LS7A1000/LS7A2000 bridge chip and LS2K1000/LS2K0500 SoC. >> +     If "M" is selected, the module will be called lsdc. >> + >> +     If in doubt, say "Y". > > Always say "N" by default. There are a few exceptions where "yes" is > advised, but regular drivers are not one of them. > >> diff --git a/drivers/gpu/drm/lsdc/Makefile >> b/drivers/gpu/drm/lsdc/Makefile >> new file mode 100644 >> index 000000000000..d4b901ec09cf >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/Makefile >> @@ -0,0 +1,14 @@ >> +# SPDX-License-Identifier: GPL-2.0 >> + >> +lsdc-y := \ >> +   lsdc_drv.o \ >> +   lsdc_crtc.o \ >> +   lsdc_irq.o \ >> +   lsdc_plane.o \ >> +   lsdc_pll.o \ >> +   lsdc_i2c.o \ >> +   lsdc_output.o \ >> +   lsdc_regs.o \ >> +   lsdc_debugfs.o > > Please always sort such lists alphabetically. > >> + >> +obj-$(CONFIG_DRM_LSDC) += lsdc.o >> diff --git a/drivers/gpu/drm/lsdc/lsdc_crtc.c >> b/drivers/gpu/drm/lsdc/lsdc_crtc.c >> new file mode 100644 >> index 000000000000..e23e6085f418 >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/lsdc_crtc.c >> @@ -0,0 +1,421 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> + >> +#include <drm/drm_vblank.h> >> +#include <drm/drm_atomic.h> >> +#include <drm/drm_atomic_helper.h> > > Same here, alphabetical sorting. > >> +#include "lsdc_drv.h" >> +#include "lsdc_regs.h" >> +#include "lsdc_pll.h" > > Sorting. > All of problem mentioned above will be solved at next version, thinks. >> + >> +static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc) >> +{ >> +   struct lsdc_device *ldev = to_lsdc(crtc->dev); >> +   unsigned int index = drm_crtc_index(crtc); >> +   u32 val; >> + >> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >> + >> +   if (index == 0) >> +       val |= INT_CRTC0_VS_EN; >> +   else if (index == 1) >> +       val |= INT_CRTC1_VS_EN; >> + >> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >> + >> +   return 0; >> +} >> + >> +static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc) >> +{ >> +   struct lsdc_device *ldev = to_lsdc(crtc->dev); >> +   unsigned int index = drm_crtc_index(crtc); >> +   u32 val; >> + >> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >> + >> +   if (index == 0) >> +       val &= ~INT_CRTC0_VS_EN; >> +   else if (index == 1) >> +       val &= ~INT_CRTC1_VS_EN; >> + >> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >> +} >> + >> +static void lsdc_crtc_reset(struct drm_crtc *crtc) >> +{ >> +   struct drm_device *ddev = crtc->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   unsigned int index = drm_crtc_index(crtc); >> +   struct lsdc_crtc_state *priv_crtc_state; >> +   u32 val = CFG_RESET_N | LSDC_PF_XRGB8888; >> + >> +   if (index == 0) >> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >> +   else if (index == 1) >> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >> + >> +   if (crtc->state) { >> +       priv_crtc_state = to_lsdc_crtc_state(crtc->state); >> + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); >> +       kfree(priv_crtc_state); >> +   } >> + >> +   priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL); >> +   if (!priv_crtc_state) >> +       return; >> + >> +   __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); >> + >> +   drm_info(ddev, "CRTC-%u reset\n", index); > > No drm_info() here. You could use drm_dbg() if you really want to > print something. I'd advise to simply remove that line. > >> +} >> + >> +static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, >> +                      struct drm_crtc_state *state) >> +{ >> +   struct lsdc_crtc_state *priv_crtc_state = >> to_lsdc_crtc_state(state); >> + >> + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); >> + >> +   kfree(priv_crtc_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 = kmalloc(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_get_vblank_counter(struct drm_crtc *crtc) >> +{ >> +   struct drm_device *ddev = crtc->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   const struct lsdc_desc *descp = ldev->descp; >> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >> +   unsigned int index = dispipe->index; >> + >> +   /* fallback to software emulated VBLANK counter */ >> +   if (!descp->has_vblank_counter) >> +       goto fallback; >> + >> +   if (index == 0) >> +       return lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG); >> + >> +   if (index == 1) >> +       return lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG); >> + >> +fallback: >> +   return (u32)drm_crtc_vblank_count(crtc); >> +} >> + >> +static const struct drm_crtc_funcs lsdc_crtc_funcs = { > > My advise for the design is to untangle the different cases into > smaller helpers as much as possible. > > You have several chips with multiple pipelines. I would advise to > provide a funcs array for each possible case. For example: > >  u32 lsdc_crtc0_get_vblank_counter(struct drm_crtc *crtc) >  { >     // return LSDC_CRTC0_VSYNC_COUNTER_REG >  } > >  u32 lsdc_crtc1_get_vblank_counter(struct drm_crtc *crtc) >  { >     // return LSDC_CRTC1_VSYNC_COUNTER_REG >  } > > and use a different funcs instance for each pipeline > >  struct lsdc_crtc0_funcs_ls2k2000 = { >     .get_vblank_counter = lsdc_crtc0_get_vblank_counter, >  }; > >  struct lsdc_crtc1_funcs_ls2k2000 = { >     .get_vblank_counter = lsdc_crtc1_get_vblank_counter, >  }; > > For hardware without vblank counter, use another instance > >  struct lsdc_crtc0_funcs_ls2k0500 = { >     // no >  }; > > At first, this looks like a lot of code. But in the long term, this > pays of, because it's so much more maintainable. If you have functions > that work with any CRTC, you can still share the implementation. I am agree. > > All this goes for other components as well. > > You may want to study mgag200. It supports ~10 chips. Handling this in > single functions was unmaintainable. So we split everything into > smaller helpers that each handle a single case. And then we build a > dedicated modesetting pipeline for each chip from various helpers. > Common code is shared among the chips. > I guess you may want divide and conquer them, mgag200 driver's structure is basic same with ast. they have only one display pipe and they are both standard pci device. I'm already absorb many merits from mgag200 and ast. And i buy two mga video card, sadly they can boot on loongson platform, x86emu will let them pass, loongson uefi/pmon firmware need adapting. I can only study it by reading the code. > Here's where we select the chip: > > > https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_drv.c#L227 > > > There's a source file for each chip and the common source code is in > I will follow this method at next version, but our hardware and bios engineers is ugly, they often made the chip indistinguishable. ls7a1000 and ls2k1000 have the same pci device id, ls7a2000 and ls2k2000 have the same pci device id, even the revision id is same. I have no choice but to rely on DT or kernel cmd passing parameters. many reviewers and maintainer seem not happy about this. > https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_mode.c > > > >> +   .reset = lsdc_crtc_reset, >> +   .destroy = drm_crtc_cleanup, >> +   .set_config = drm_atomic_helper_set_config, >> +   .page_flip = drm_atomic_helper_page_flip, >> +   .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, >> +   .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, >> +   .get_vblank_counter = lsdc_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, >> +}; >> + >> +static enum drm_mode_status >> +lsdc_crtc_mode_valid(struct drm_crtc *crtc, >> +            const struct drm_display_mode *mode) >> +{ >> +   struct drm_device *ddev = crtc->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   const struct lsdc_desc *descp = ldev->descp; >> + >> +   if (mode->hdisplay > descp->max_width) >> +       return MODE_BAD_HVALUE; >> +   if (mode->vdisplay > descp->max_height) >> +       return MODE_BAD_VVALUE; >> + >> +   if (mode->clock > descp->max_pixel_clk) { >> +       drm_dbg(ddev, "mode %dx%d, pixel clock=%d is too high\n", >> +           mode->hdisplay, mode->vdisplay, mode->clock); >> +       return MODE_CLOCK_HIGH; >> +   } >> + >> +   if ((mode->hdisplay * 4) % descp->pitch_align) { >> +       drm_dbg(ddev, "stride is require to not %u bytes aligned\n", >> +           descp->pitch_align); >> +       return MODE_BAD; >> +   } >> + >> +   return MODE_OK; >> +} >> + >> +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc, >> +                   struct drm_crtc_state *state) >> +{ >> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >> +   struct lsdc_pll *pixpll = &dispipe->pixpll; >> +   const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; >> +   struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); >> +   bool ret; >> + >> +   ret = pfuncs->compute(pixpll, state->mode.clock, >> &priv_state->pparms); >> +   if (ret) >> +       return 0; >> + >> +   drm_warn(crtc->dev, "failed find PLL parameters for %u\n", >> state->mode.clock); >> + >> +   return -EINVAL; >> +} >> + >> +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; /* no mode checks if CRTC is being disabled */ >> + >> +   return lsdc_pixpll_atomic_check(crtc, crtc_state); >> +} >> + >> +static void lsdc_update_pixclk(struct lsdc_display_pipe *dispipe, >> +                  struct lsdc_crtc_state *priv_state) >> +{ >> +   struct lsdc_pll *pixpll = &dispipe->pixpll; >> +   const struct lsdc_pixpll_funcs *clkfunc = pixpll->funcs; >> + >> +   clkfunc->update(pixpll, &priv_state->pparms); >> +} >> + >> +static void lsdc_crtc_helper_mode_set_nofb(struct drm_crtc *crtc) >> +{ >> +   struct drm_device *ddev = crtc->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   struct drm_display_mode *mode = &crtc->state->mode; >> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >> +   struct lsdc_crtc_state *priv_state = >> to_lsdc_crtc_state(crtc->state); >> +   unsigned int index = dispipe->index; >> +   u32 h_sync, v_sync, h_val, v_val; >> +   u32 val; >> + >> +   /* 26:16 total pixels, 10:0 visiable pixels, in horizontal */ >> +   h_val = (mode->crtc_htotal << 16) | mode->crtc_hdisplay; >> + >> +   /* 26:16 total pixels, 10:0 visiable pixels, in vertical */ >> +   v_val = (mode->crtc_vtotal << 16) | mode->crtc_vdisplay; >> +   /* 26:16 hsync end, 10:0 hsync start, bit 30 is hsync enable */ >> +   h_sync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | >> CFG_HSYNC_EN; >> +   if (mode->flags & DRM_MODE_FLAG_NHSYNC) >> +       h_sync |= CFG_HSYNC_INV; >> + >> +   /* 26:16 vsync end, 10:0 vsync start, bit 30 is vsync enable */ >> +   v_sync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | >> CFG_VSYNC_EN; >> +   if (mode->flags & DRM_MODE_FLAG_NVSYNC) >> +       v_sync |= CFG_VSYNC_INV; >> + >> +   if (index == 0) { >> +       lsdc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG, h_val); >> +       lsdc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG, v_val); >> +       lsdc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG, h_sync); >> +       lsdc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG, v_sync); >> + >> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >> +       val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; >> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >> +   } else if (index == 1) { >> +       lsdc_wreg32(ldev, LSDC_CRTC1_HDISPLAY_REG, h_val); >> +       lsdc_wreg32(ldev, LSDC_CRTC1_VDISPLAY_REG, v_val); >> +       lsdc_wreg32(ldev, LSDC_CRTC1_HSYNC_REG, h_sync); >> +       lsdc_wreg32(ldev, LSDC_CRTC1_VSYNC_REG, v_sync); >> + >> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >> +       val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; >> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >> +   } > > Same here as with vblank: have one helper for each index and use > different _funcs structures for each pipeline. > Well, I will revise lsdc driver with this idea in mind. >> + >> +   drm_dbg(ddev, "%s modeset: %ux%u\n", >> +       crtc->name, mode->hdisplay, mode->vdisplay); >> + >> +   lsdc_update_pixclk(dispipe, priv_state); >> +} >> + >> +static void lsdc_enable_display(struct lsdc_device *ldev, unsigned >> int index) >> +{ >> +   u32 val; >> + >> +   if (index == 0) { >> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >> +       val |= CFG_OUTPUT_EN; >> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >> +       return; >> +   } >> + >> +   if (index == 1) { >> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >> +       val |= CFG_OUTPUT_EN; >> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >> +       return; >> +   } >> +} >> + >> +static void lsdc_disable_display(struct lsdc_device *ldev, unsigned >> int index) >> +{ >> +   u32 val; >> + >> +   if (index == 0) { >> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >> +       val &= ~CFG_OUTPUT_EN; >> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >> +       return; >> +   } >> + >> +   if (index == 1) { >> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >> +       val &= ~CFG_OUTPUT_EN; >> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >> +       return; >> +   } >> +} >> + >> +static void lsdc_crtc_helper_atomic_enable(struct drm_crtc *crtc, >> +                      struct drm_atomic_state *state) >> +{ >> +   struct drm_device *ddev = crtc->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> + >> +   drm_crtc_vblank_on(crtc); >> + >> +   lsdc_enable_display(ldev, drm_crtc_index(crtc)); >> + >> +   drm_dbg(ddev, "%s: enabled\n", crtc->name); >> +} >> + >> +static void lsdc_crtc_helper_atomic_disable(struct drm_crtc *crtc, >> +                       struct drm_atomic_state *state) >> +{ >> +   struct drm_device *ddev = crtc->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> + >> +   lsdc_disable_display(ldev, drm_crtc_index(crtc)); >> + >> +   drm_crtc_wait_one_vblank(crtc); >> + >> +   drm_crtc_vblank_off(crtc); >> + >> +   drm_dbg(ddev, "%s: disabled\n", crtc->name); >> +} >> + >> +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 drm_device *ddev = crtc->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   unsigned int index = drm_crtc_index(crtc); >> +   int line, vsw, vbp, vactive_start, vactive_end, vfp_end; >> +   u32 val = 0; >> + >> +   vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; >> +   vbp = mode->crtc_vtotal - mode->crtc_vsync_end; >> + >> +   vactive_start = vsw + vbp + 1; >> + >> +   vactive_end = vactive_start + mode->crtc_vdisplay; >> + >> +   /* last scan line before VSYNC */ >> +   vfp_end = mode->crtc_vtotal; >> + >> +   if (stime) >> +       *stime = ktime_get(); >> + >> +   if (index == 0) >> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); >> +   else if (index == 1) >> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); >> + >> +   line = (val & 0xffff); >> + >> +   if (line < vactive_start) >> +       line -= vactive_start; >> +   else if (line > vactive_end) >> +       line = line - vfp_end - vactive_start; >> +   else >> +       line -= vactive_start; >> + >> +   *vpos = line; >> +   *hpos = val >> 16; >> + >> +   if (etime) >> +       *etime = ktime_get(); >> + >> +   return true; >> +} >> + >> +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { >> +   .mode_valid = lsdc_crtc_mode_valid, >> +   .mode_set_nofb = lsdc_crtc_helper_mode_set_nofb, >> +   .atomic_enable = lsdc_crtc_helper_atomic_enable, >> +   .atomic_disable = lsdc_crtc_helper_atomic_disable, >> +   .atomic_check = lsdc_crtc_helper_atomic_check, >> +   .atomic_flush = lsdc_crtc_atomic_flush, >> +   .get_scanout_position = lsdc_crtc_get_scanout_position, >> +}; >> + >> +int lsdc_crtc_init(struct drm_device *ddev, >> +          struct drm_crtc *crtc, >> +          unsigned int index, >> +          struct drm_plane *primary, >> +          struct drm_plane *cursor) >> +{ >> +   int ret; >> + >> +   ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor, >> +                   &lsdc_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) >> +       drm_warn(ddev, "set the gamma table size failed\n"); >> + >> +   drm_crtc_enable_color_mgmt(crtc, 0, false, 256); > > Maybe leave this out for now. You need to handle the property in your > driver. I don't see code for that. > > Color management should probably go into a separate patch. > Ok, I'm agree to leave gamma support to a separate patch >> + >> +   return 0; >> +} >> diff --git a/drivers/gpu/drm/lsdc/lsdc_debugfs.c >> b/drivers/gpu/drm/lsdc/lsdc_debugfs.c >> new file mode 100644 >> index 000000000000..176ae4e11d1c >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/lsdc_debugfs.c >> @@ -0,0 +1,191 @@ >> +// SPDX-License-Identifier: GPL-2.0+ >> + >> +#include <drm/drm_debugfs.h> >> +#include <drm/drm_managed.h> >> +#include <drm/drm_gem_vram_helper.h> >> +#include "lsdc_drv.h" >> +#include "lsdc_regs.h" >> +#include "lsdc_pll.h" >> + >> +#ifdef CONFIG_DEBUG_FS >> +static int lsdc_show_clock(struct seq_file *m, void *arg) >> +{ >> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >> +   struct drm_device *ddev = node->minor->dev; >> +   struct drm_crtc *crtc; >> + >> +   drm_for_each_crtc(crtc, ddev) { >> +       struct lsdc_display_pipe *pipe = crtc_to_display_pipe(crtc); >> +       struct lsdc_pll *pixpll = &pipe->pixpll; >> +       const struct lsdc_pixpll_funcs *funcs = pixpll->funcs; >> +       struct drm_display_mode *adj = &crtc->state->mode; >> +       struct lsdc_pll_parms parms; >> +       unsigned int out_khz; >> + >> +       out_khz = funcs->get_clock_rate(pixpll, &parms); >> + >> +       seq_printf(m, "Display pipe %u: %dx%d\n", >> +              pipe->index, adj->hdisplay, adj->vdisplay); >> + >> +       seq_printf(m, "Frequency actually output: %u kHz\n", out_khz); >> +       seq_printf(m, "Pixel clock required: %d kHz\n", adj->clock); >> +       seq_printf(m, "diff: %d kHz\n", adj->clock); >> + >> +       seq_printf(m, "div_ref=%u, loopc=%u, div_out=%u\n", >> +              parms.div_ref, parms.loopc, parms.div_out); >> + >> +       seq_printf(m, "hsync_start=%d, hsync_end=%d, htotal=%d\n", >> +              adj->hsync_start, adj->hsync_end, adj->htotal); >> +       seq_printf(m, "vsync_start=%d, vsync_end=%d, vtotal=%d\n\n", >> +              adj->vsync_start, adj->vsync_end, adj->vtotal); >> +   } >> + >> +   return 0; >> +} >> + >> +static int lsdc_show_mm(struct seq_file *m, void *arg) >> +{ >> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >> +   struct drm_device *ddev = node->minor->dev; >> +   struct drm_printer p = drm_seq_file_printer(m); >> + >> + drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p); >> + >> +   return 0; >> +} >> + >> +#define REGDEF(reg) { __stringify_1(LSDC_##reg##_REG), >> LSDC_##reg##_REG } >> +static const struct { >> +   const char *name; >> +   u32 reg_offset; >> +} lsdc_regs_array[] = { >> +   REGDEF(CURSOR0_CFG), >> +   REGDEF(CURSOR0_ADDR_LO), >> +   REGDEF(CURSOR0_ADDR_HI), >> +   REGDEF(CURSOR0_POSITION), >> +   REGDEF(CURSOR0_BG_COLOR), >> +   REGDEF(CURSOR0_FG_COLOR), >> +   REGDEF(INT), >> +   REGDEF(CRTC0_CFG), >> +   REGDEF(CRTC0_STRIDE), >> +   REGDEF(CRTC0_FB_ORIGIN), >> +   REGDEF(CRTC0_HDISPLAY), >> +   REGDEF(CRTC0_HSYNC), >> +   REGDEF(CRTC0_VDISPLAY), >> +   REGDEF(CRTC0_VSYNC), >> +   REGDEF(CRTC0_GAMMA_INDEX), >> +   REGDEF(CRTC0_GAMMA_DATA), >> +   REGDEF(CRTC1_CFG), >> +   REGDEF(CRTC1_STRIDE), >> +   REGDEF(CRTC1_FB_ORIGIN), >> +   REGDEF(CRTC1_HDISPLAY), >> +   REGDEF(CRTC1_HSYNC), >> +   REGDEF(CRTC1_VDISPLAY), >> +   REGDEF(CRTC1_VSYNC), >> +   REGDEF(CRTC1_GAMMA_INDEX), >> +   REGDEF(CRTC1_GAMMA_DATA), >> +}; >> + >> +static int lsdc_show_regs(struct seq_file *m, void *arg) >> +{ >> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >> +   struct drm_device *ddev = node->minor->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   int i; >> + >> +   for (i = 0; i < ARRAY_SIZE(lsdc_regs_array); i++) { >> +       u32 offset = lsdc_regs_array[i].reg_offset; >> +       const char *name = lsdc_regs_array[i].name; >> + >> +       seq_printf(m, "%s (0x%04x): 0x%08x\n", >> +              name, offset, lsdc_rreg32(ldev, offset)); >> +   } >> + >> +   return 0; >> +} >> + >> +static int lsdc_show_vblank_counter(struct seq_file *m, void *arg) >> +{ >> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >> +   struct drm_device *ddev = node->minor->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> + >> +   seq_printf(m, "CRTC-0 vblank counter: %08u\n", >> +          lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG)); >> + >> +   seq_printf(m, "CRTC-1 vblank counter: %08u\n", >> +          lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG)); >> + >> +   return 0; >> +} >> + >> +static int lsdc_show_scan_position(struct seq_file *m, void *arg) >> +{ >> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >> +   struct drm_device *ddev = node->minor->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   u32 val0 = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); >> +   u32 val1 = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); >> + >> +   seq_printf(m, "CRTC-0: x: %08u, y: %08u\n", >> +          val0 >> 16, val0 & 0xFFFF); >> + >> +   seq_printf(m, "CRTC-1: x: %08u, y: %08u\n", >> +          val1 >> 16, val1 & 0xFFFF); >> + >> +   return 0; >> +} >> + >> +static int lsdc_show_fb_addr(struct seq_file *m, void *arg) >> +{ >> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >> +   struct drm_device *ddev = node->minor->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   u32 lo, hi; >> +   u32 val; >> + >> +   val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >> +   if (val & CFG_FB_IN_USING) { >> +       lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_LO_ADDR_REG); >> +       hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_HI_ADDR_REG); >> +       seq_printf(m, "CRTC-0 using fb1: 0x%x:%x\n", hi, lo); >> +   } else { >> +       lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_LO_ADDR_REG); >> +       hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_HI_ADDR_REG); >> +       seq_printf(m, "CRTC-0 using fb0: 0x%x:%x\n", hi, lo); >> +   } >> + >> +   val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >> +   if (val & CFG_FB_IN_USING) { >> +       lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_LO_ADDR_REG); >> +       hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_HI_ADDR_REG); >> +       seq_printf(m, "CRTC-1 using fb1: 0x%x:%x\n", hi, lo); >> +   } else { >> +       lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_LO_ADDR_REG); >> +       hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_HI_ADDR_REG); >> +       seq_printf(m, "CRTC-1 using fb0: 0x%x:%x\n", hi, lo); >> +   } >> + >> +   return 0; >> +} >> + >> +static struct drm_info_list lsdc_debugfs_list[] = { >> +   { "clocks",  lsdc_show_clock, 0 }, >> +   { "mm",      lsdc_show_mm, 0, NULL }, >> +   { "regs",    lsdc_show_regs, 0 }, >> +   { "vblanks", lsdc_show_vblank_counter, 0, NULL }, >> +   { "scan_pos", lsdc_show_scan_position, 0, NULL }, >> +   { "fb_addr", lsdc_show_fb_addr, 0, NULL }, >> +}; >> + >> +#endif >> + >> +void lsdc_debugfs_init(struct drm_minor *minor) >> +{ >> +#ifdef CONFIG_DEBUG_FS >> +   drm_debugfs_create_files(lsdc_debugfs_list, >> +                ARRAY_SIZE(lsdc_debugfs_list), >> +                minor->debugfs_root, >> +                minor); >> +#endif >> +} >> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.c >> b/drivers/gpu/drm/lsdc/lsdc_drv.c >> new file mode 100644 >> index 000000000000..3bd457e6e980 >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.c >> @@ -0,0 +1,635 @@ >> +// SPDX-License-Identifier: GPL-2.0+ >> +/* >> + * KMS driver for Loongson display controller >> + * Copyright (C) 2022 Loongson Corporation >> + * >> + * Authors: >> + *     Li Yi <liyi@loongson.cn> >> + *     Li Chenyang <lichenyang@loongson.cn> >> + *     Sui Jingfeng <suijingfeng@loongson.cn> >> + */ >> + >> +#include <linux/module.h> >> +#include <linux/pci.h> >> +#include <linux/of_address.h> >> +#include <drm/drm_aperture.h> >> +#include <drm/drm_vblank.h> >> +#include <drm/drm_fb_helper.h> >> +#include <drm/drm_gem_vram_helper.h> >> +#include <drm/drm_gem_framebuffer_helper.h> >> +#include <drm/drm_modeset_helper.h> >> +#include <drm/drm_atomic_helper.h> >> +#include <drm/drm_probe_helper.h> >> +#include <drm/drm_fbdev_generic.h> >> +#include "lsdc_drv.h" > > Alphebetical order. I will fix this problem at next version > >> + >> +static int lsdc_chip = -1; >> +MODULE_PARM_DESC(chip, "override chip id (-1 = probed by driver"); >> +module_param_named(chip, lsdc_chip, int, 0644); > > That's a development option. Better not add it to the published driver. Without this, compile with W=1 generate warnings. and removing chip descriptions for Loongson SoC is not wanted either. this option can be removed if DT support is upstream in the future. It allow us to developing driver before DT or ACPI support is ready. Otherwise we have no way developing drivers for loongson SoC beforehand. >> + >> +/* >> + * ls7a1000 and ls7a2000 is not only used with loongson desktop >> class CPU >> + * but also loongson server class CPUs (LS3C5000@2.2GHz 16 core). >> Loongson >> + * server products generally don't has a dedicated vram mounted for >> cost >> + * reason, it will use aspeed bmc as its display on its final and >> formal >> + * product. lsdc.has_vram=0 is mainly be use in developing phase of the >> + * product. When a board don't has a dedicated vram mounted, we can put >> + * the framebuffer on system RAM. >> + */ >> +static int lsdc_has_vram = -1; >> +MODULE_PARM_DESC(has_vram, "has on-board gpu memory in reality (0 = >> no, -1 = default"); >> +module_param_named(has_vram, lsdc_has_vram, int, 0644); > > Same here. That's for development. Err, we truly very need this feature, 1) ASPEED BMC card is removable, it can be unplug from the mother board of Loongson Server. leaving this option there, we still have change enter X server graphic environment. 2) We need developing drivers for loongson SoC on loongson PC platform, we may deliberately don't use on-board vram. there a need to let TTM manage cached reserved RAM, because such device don't have vram. We have achieve this goal on ls2k2000 SoC. Otherwise we have to write two piece of driver, one is DMA helper base, the other is TTM based, we doing so at downstream kernel. > >> + >> +#define DRIVER_AUTHOR       "Sui Jingfeng <suijingfeng@loongson.cn>" >> +#define DRIVER_NAME       "lsdc" >> +#define DRIVER_DESC       "drm driver for loongson's display >> controller" >> +#define DRIVER_DATE       "20220701" >> +#define DRIVER_MAJOR       1 >> +#define DRIVER_MINOR       0 >> +#define DRIVER_PATCHLEVEL   0 >> + >> +static const struct lsdc_desc dc_in_ls2k0500 = { > > Those description structs are a two-edged sword IMHO. > >> +   .chip = CHIP_LS2K0500, >> +   .num_of_crtc = LSDC_NUM_CRTC, >> +   .max_pixel_clk = 200000, >> +   .max_width = 2048, >> +   .max_height = 2048, >> +   .hw_cursor_w = 32, >> +   .hw_cursor_h = 32, >> +   .pitch_align = 256, >> +   .mc_bits = 40, > > These are device constants are probably fine. > >> +   .num_of_hw_cursor = 1, >> +   .has_vblank_counter = false, >> +   .has_builtin_i2c = false, >> +   .has_vram = false, >> +   .has_hpd_reg = false, >> +   .is_soc = true, > > But from a quick look through the code, these settings affect control > flow and/or modesetting pipelines. They are often better implemented > by dedicated helpers as outlined in my comments about vblank and mgag200. Ok, mgag200 is for server mainly, its variant are not so different as loongson does, at least they all have vram, we have to put more attention to turn drm memory manager. I will following your advice by introducing more helpers at next version. > > >> +}; >> + >> +static const struct lsdc_desc dc_in_ls2k1000 = { >> +   .chip = CHIP_LS2K1000, >> +   .num_of_crtc = LSDC_NUM_CRTC, >> +   .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, >> +   .has_builtin_i2c = false, >> +   .has_vram = false, >> +   .has_hpd_reg = false, >> +   .is_soc = true, >> +}; >> + >> +static const struct lsdc_desc dc_in_ls2k2000 = { >> +   .chip = CHIP_LS2K2000, >> +   .num_of_crtc = LSDC_NUM_CRTC, >> +   .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 = 256, >> +   .mc_bits = 40, >> +   .has_vblank_counter = true, >> +   .has_builtin_i2c = true, >> +   .has_vram = false, >> +   .has_hpd_reg = true, >> +   .is_soc = true, >> +}; >> + >> +static const struct lsdc_desc dc_in_ls7a1000 = { >> +   .chip = CHIP_LS7A1000, >> +   .num_of_crtc = LSDC_NUM_CRTC, >> +   .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, >> +   .has_builtin_i2c = true, >> +   .has_vram = true, >> +   .has_hpd_reg = false, >> +   .is_soc = false, >> +}; >> + >> +static const struct lsdc_desc dc_in_ls7a2000 = { >> +   .chip = CHIP_LS7A2000, >> +   .num_of_crtc = LSDC_NUM_CRTC, >> +   .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 = 256, >> +   .mc_bits = 40, /* support 48, but use 40 for backward >> compatibility */ >> +   .has_vblank_counter = true, >> +   .has_builtin_i2c = true, >> +   .has_vram = true, >> +   .has_hpd_reg = true, >> +   .is_soc = false, >> +}; >> + >> +static int lsdc_gem_dumb_create(struct drm_file *file, >> +               struct drm_device *ddev, >> +               struct drm_mode_create_dumb *args) >> +{ >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   const struct lsdc_desc *descp = ldev->descp; >> + >> +   return drm_gem_vram_fill_create_dumb(file, ddev, 0, >> descp->pitch_align, args); >> +} >> + >> +DEFINE_DRM_GEM_FOPS(lsdc_gem_fops); >> + >> +static const struct drm_driver lsdc_drm_driver = { >> +   .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, >> +   .fops = &lsdc_gem_fops, >> + >> +   .name = DRIVER_NAME, >> +   .desc = DRIVER_DESC, >> +   .date = DRIVER_DATE, >> +   .major = DRIVER_MAJOR, >> +   .minor = DRIVER_MINOR, >> +   .patchlevel = DRIVER_PATCHLEVEL, >> + >> +   .debugfs_init = lsdc_debugfs_init, >> +   .dumb_create = lsdc_gem_dumb_create, >> +   .dumb_map_offset = drm_gem_ttm_dumb_map_offset, >> +   .gem_prime_mmap = drm_gem_prime_mmap, >> +}; >> + >> +static const struct drm_mode_config_funcs lsdc_mode_config_funcs = { >> +   .fb_create = drm_gem_fb_create, >> +   .output_poll_changed = drm_fb_helper_output_poll_changed, >> +   .atomic_check = drm_atomic_helper_check, >> +   .atomic_commit = drm_atomic_helper_commit, >> +   .mode_valid = drm_vram_helper_mode_valid, >> +}; >> + >> +static int lsdc_modeset_init(struct lsdc_device *ldev, >> +                const struct lsdc_desc *descp) >> +{ >> +   struct drm_device *ddev = &ldev->base; >> +   unsigned int num_crtc = descp->num_of_crtc; >> +   unsigned int i; >> +   int ret; >> + >> +   for (i = 0; i < num_crtc; i++) { >> +       ret = lsdc_create_output(ldev, i); >> +       if (ret) >> +           return ret; >> +   } >> + >> +   for (i = 0; i < num_crtc; i++) { >> +       struct lsdc_display_pipe *dispipe = &ldev->dispipe[i]; >> +       struct lsdc_pll *pixpll = &dispipe->pixpll; >> +       struct drm_plane *primary = &dispipe->primary; >> +       struct drm_plane *cursor = &dispipe->cursor; >> +       struct drm_crtc *crtc = &dispipe->crtc; >> + >> +       dispipe->index = i; >> + >> +       ret = lsdc_pixpll_init(pixpll, ddev, i); >> +       if (ret) >> +           return ret; >> + >> +       ret = lsdc_plane_init(ldev, primary, DRM_PLANE_TYPE_PRIMARY, >> i); >> +       if (ret) >> +           return ret; >> + >> +       ret = lsdc_plane_init(ldev, cursor, DRM_PLANE_TYPE_CURSOR, i); >> +       if (ret) >> +           return ret; >> + >> +       /* >> +        * Initial all of the CRTC available, in this way the logical >> +        * crtc index is equal to the hardware crtc index. >> +        * >> +        * display pipe 0 = crtc0 + dvo0 + encoder0 + connector0 >> +        * display pipe 1 = crtc1 + dvo1 + encoder1 + connector1 >> +        */ >> +       ret = lsdc_crtc_init(ddev, crtc, i, primary, cursor); >> +       if (ret) >> +           return ret; >> + >> +       drm_info(ddev, "display pipe %u initialized\n", i); >> +   } >> + >> +   drm_mode_config_reset(ddev); >> + >> +   return 0; >> +} >> + >> +static int lsdc_mode_config_init(struct drm_device *ddev, >> +                const struct lsdc_desc *descp) >> +{ >> +   int ret; >> + >> +   ret = drmm_mode_config_init(ddev); >> +   if (ret) >> +       return ret; >> + >> +   ddev->mode_config.funcs = &lsdc_mode_config_funcs; >> +   ddev->mode_config.min_width = 1; >> +   ddev->mode_config.min_height = 1; >> +   ddev->mode_config.max_width = descp->max_width * LSDC_NUM_CRTC; >> +   ddev->mode_config.max_height = descp->max_height * LSDC_NUM_CRTC; >> +   ddev->mode_config.preferred_depth = 24; >> +   ddev->mode_config.prefer_shadow = descp->has_vram; >> + >> +   ddev->mode_config.cursor_width = descp->hw_cursor_h; >> +   ddev->mode_config.cursor_height = descp->hw_cursor_h; >> + >> +   /* max_vblank_count is set on each CRTC */ >> +   ddev->max_vblank_count = 0xffffffff; >> + >> +   return ret; >> +} >> + >> +/* >> + * lsdc_detect_chip - a function to tell lsdc variant apart. >> + */ >> +static const struct lsdc_desc * >> +lsdc_detect_chip(struct pci_dev *pdev, const struct pci_device_id *ent) >> +{ >> +   /* >> +    * Provide a way to false the driver running on new model >> +    * before we have a proper way to tell them apart >> +    */ >> +   if (lsdc_chip == CHIP_LS2K2000) { >> +       dev_info(&pdev->dev, "Override as LS2K2000\n"); >> +       return &dc_in_ls2k2000; >> +   } >> + >> +   if (lsdc_chip == CHIP_LS2K1000) { >> +       dev_info(&pdev->dev, "Override as LS2K1000\n"); >> +       return &dc_in_ls2k1000; >> +   } >> + >> +   if (lsdc_chip == CHIP_LS2K0500) { >> +       dev_info(&pdev->dev, "Override as LS2K0500\n"); >> +       return &dc_in_ls2k0500; >> +   } >> + >> +   if (ent) { >> +       struct lsdc_desc *dc_descp; >> + >> +       dc_descp = (struct lsdc_desc *)ent->driver_data; > > Is that legal? driver_data is of kernel_ulong_t. Maybe store the CHIP_ > constant here and then lookup the lsdc_desc. > It works in practice but it seems dangerous doing so, I will using CHIP_ LS7AXXXX and CHIP_LS2KXXXX at new version. i don't notice it before, thank you. > > https://elixir.bootlin.com/linux/latest/source/include/linux/mod_devicetable.h#L36 > > >> +       if (dc_descp->chip == CHIP_LS7A1000) >> +           dev_info(&pdev->dev, "LS7A1000 Found revision: %u\n", >> pdev->revision); >> +       else if (dc_descp->chip == CHIP_LS7A2000) >> +           dev_info(&pdev->dev, "LS7A2000 Found revison: %u\n", >> pdev->revision); >> + >> +       return dc_descp; >> +   } >> + >> +   dev_err(&pdev->dev, "Not known device\n"); >> + >> +   return NULL; >> +} >> + >> +static int lsdc_get_dedicated_vram(struct lsdc_device *ldev, >> +                  const struct lsdc_desc *descp) >> +{ >> +   struct drm_device *ddev = &ldev->base; >> +   struct pci_dev *gpu; >> +   resource_size_t base, size; >> + >> +   /* >> +    * The GPU and display controller in LS7A1000/LS7A2000 are >> separated >> +    * PCIE devices, they are two devices not one. The DC is a pci >> device, >> +    * but it don't have a dedicate VRAM bar, the BIOS engineer >> choose to >> +    * assign the VRAM to the gpu device. Sadly, after years >> application, >> +    * this decision form as a convention for loongson integrate >> graphics. >> +    * For LS7A1000 and LS7A2000, bar 2 of GPU device contain the VRAM, >> +    * both the GPU and the DC can make use of the VRAM depend on >> how DRM >> +    * device driver is written. Therefore, we have to do some >> tricks here. >> +    */ >> +   if (descp->chip == CHIP_LS7A1000) >> +       gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A15, NULL); >> +   else if (descp->chip == CHIP_LS7A2000) >> +       gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A25, NULL); >> + >> +   if (!gpu) { >> +       drm_warn(ddev, "No GPU device found\n"); >> +       return -ENODEV; >> +   } >> + >> +   base = pci_resource_start(gpu, 2); >> +   size = pci_resource_len(gpu, 2); >> + >> +   ldev->vram_base = base; >> +   ldev->vram_size = size; >> + >> +   drm_info(ddev, "dedicated vram start: 0x%llx, size: %uMB\n", >> +        (u64)base, (u32)(size >> 20)); >> + >> +   return 0; >> +} >> + >> +/* >> + * Loongson display controller is not equipped with IOMMU. >> + * In order to make this driver work with Loongson SoC, >> + * you need to either carveout part RAM as VRAM or reserve >> + * part system ram as vram. Pass the start address and size >> + * via DT or ACPI. >> + */ >> +static int lsdc_of_get_reserved_ram(struct lsdc_device *ldev) >> +{ >> +   struct drm_device *ddev = &ldev->base; >> +   unsigned long size = 0; >> +   struct device_node *node; >> +   struct resource r; >> +   int ret; >> + >> +   node = of_parse_phandle(ddev->dev->of_node, "memory-region", 0); >> +   if (!node) { >> +       drm_err(ddev, "No memory-region property or no DT, abort\n"); >> +       return -ENOENT; >> +   } >> + >> +   ret = of_address_to_resource(node, 0, &r); >> +   of_node_put(node); >> +   if (ret) >> +       return ret; >> + >> +   size = r.end - r.start + 1; >> + >> +   ldev->vram_base = r.start; >> +   ldev->vram_size = size; >> + >> +   drm_info(ddev, "using VRAM carveout: %lx@%pa\n", size, &r.start); >> + >> +   return 0; >> +} >> + >> +static int >> +lsdc_remove_conflicting_framebuffers(const struct drm_driver *pdriver, >> +                    resource_size_t base, >> +                    resource_size_t size) >> +{ >> +   return drm_aperture_remove_conflicting_framebuffers(base, >> +                               size, >> +                               false, >> +                               pdriver); >> +} > > No need to wrap this single function. Ok, Loongson upstream kernel support efifb and it backing memory is located at this range. we must properly remove it, otherwise they will have two fb node(/dev/fb0 and /dev/fb1) > >> + >> +static struct lsdc_device * >> +lsdc_create_device(struct pci_dev *pdev, >> +          const struct pci_device_id *ent, >> +          const struct drm_driver *drv) >> +{ >> +   struct lsdc_device *ldev; >> +   struct drm_device *ddev; >> +   const struct lsdc_desc *descp; >> +   int ret; >> + >> +   ldev = devm_drm_dev_alloc(&pdev->dev, drv, struct lsdc_device, >> base); >> +   if (IS_ERR(ldev)) >> +       return ldev; >> + >> +   ddev = &ldev->base; >> + >> +   pci_set_drvdata(pdev, ddev); >> + >> +   descp = lsdc_detect_chip(pdev, ent); >> +   if (!descp) >> +       return NULL; >> + >> +   ldev->descp = descp; >> + >> +   spin_lock_init(&ldev->reglock); >> + >> +   /* BAR 0 the DC device contains registers */ >> +   ldev->reg_base = pcim_iomap(pdev, 0, 0); >> +   if (!ldev->reg_base) >> +       return ERR_PTR(-EIO); >> + >> +   if (descp->has_vram && lsdc_has_vram) >> +       ret = lsdc_get_dedicated_vram(ldev, descp); >> +   else >> +       ret = lsdc_of_get_reserved_ram(ldev); >> + >> +   if (ret) { >> +       drm_err(ddev, "Init VRAM failed: %d\n", ret); >> +       return ERR_PTR(ret); >> +   } >> + >> +   ret = lsdc_remove_conflicting_framebuffers(drv, >> +                          ldev->vram_base, >> +                          ldev->vram_size); >> +   if (ret) { >> +       drm_err(ddev, "remove firmware framebuffers failed: %d\n", >> ret); >> +       return ERR_PTR(ret); >> +   } >> + >> +   ret = drmm_vram_helper_init(ddev, ldev->vram_base, >> ldev->vram_size); > > Using GEM VRAM helpers could become a problem and I'd advise against > them. > Err, I'm not want to agree this... > First of all, how much video memory is there? VRAM helpers need at > least 3 times the maximum consumption to be reliable.So if you have a > FullHD display that consumes ~8 Mib, you'd need 24 MiB of VRAM. > Hardware cursors can further interfere. Part of ls3a5000/ls3a4000+ls7a1000 machine have 64MB, another part of ls3a5000/ls3a4000+ls7a1000 have 128 MB. For ls7a1000(1080p) the maximum usage actually is two framebuffer(1920x1080x4x2) + two cursor (64x64x4x 2) + one fbcon (1920x1080x4) and LS7A2000 board has at lease 256MB VRAM. Therefore I can guarantee that we have at least 64MB VRAM,enough for double screen(1920x1080) + double cursor. I guess you means that there are some device(aspeed bmc) with limited VRAM size, but it is more a problem of the device itself(It is not vram helper's fault). Early version of ast driver would evict fbcon to system RAM to save VRAM usage, would this idea be a solution for the device with limited vram capacity today? > VRAM helpers also don't support buffer sharing with other devices. > That's a problem for some compositors. > > For those reasons, VRAM helpers are deprecated. I'd rather remove them > entirely. Its not that bad, VRAM helpers is good enough for our case as a first step. Our colleague and customers mainly using MATE and XFCE under X server graphic environment, they need basic 2D graphics environment most. We have tested our driver against macro and xfwm4, it actually works very well. Individual compositor may have a few problems, but it isn't a thing for our case currently. Can we solve those problem by other means? We could give up cross device buffer sharing support for chasing 2D graphic performance , we intend to introducing buffer sharing with the help of the GPU with another patch in the future. We possibly could switch to ttm-based KMS driver under your guiding then. For ls7a1000, I imagine to merge lsdc with evnaviv into a single drm driver.May I suggest to postpone the decision of removing VRAM helpers, its performance is better than shmem helper and have better support for double screen. > As a better alternative for the DC, you could use GEM SHMEM helpers. > See mgag200 or ast for how to do that. We have two display pipe thus support double screen, therefore we have two framebuffer BO and two cursor BO, we need to take clone screen, extend screen and rotate and resize case into consideration, on clone screen case the two CRTC should scanout from the same address, while on extend screen case, the frame buffer itself is grant one buffer if using modesetting driver, but we have two primary plane thus two fb offset. Arrange VRAM bo offset for 4 different drm BO object manually is little bit hack and cumbersome. I have already finish the developing work of shmem helper support, see: https://github.com/loongson-gfx/drm-tip/compare/drm-tip...shmem But it is NOT as good as VRAM helper in the perspective of performance. I think copy from the shadow to the front frame buffer should be done at user space, this can save the overhead of reporting damage rectangle whenever have a damage. > > The comments mention that there's also a GPU. Does the GPU use VRAM? > Or do GPU and DC share memory ranges? > LS7A2000 integrate Loongson self-made GPU, driver is developed by my colleagues, it already works but my mature to upstream yet. LS7A1000 have a VIVANTE GC1000 v5037 (20120617 etc), a ten years ago render-only GPU. the VRAM is connected with the ls7a1000 bridge chip, the memory controller is also in ls7a1000 bridge chip itself. ATI call this type vram as sideport, my company call it as GMEM, I think they are similar. etnaviv don't use VRAM currently, all BOs of etnaviv is in system ram. I think merge etnaviv and lsdc into a single drm driver would be a solution, what do you think? On downstream environment, we hacking make ttm-based lsdc work with etnaviv, by copying(sw resolve) the rendered buffer to vram backed framebuffer, glmark2 get 220 point, quake3 arena can play, haha :) It is achieved by developing xf86-video-loongson which using etnaviv to share buffer between xserver and glmark2, lsdc(or downstream loongson-drm) is only need to implement a self-sharing for userspace process . see https://github.com/loongson-gfx/xf86-video-loongson. The shared rendering buffer is copy to front buffer with loongson LSX and LASX(256-bit) SIMD optimization. Report dirty to kernel space is not need. we can even let suppertiled buffer rendered by etnaviv get exported, xf86-video-loongson get the fd and prime it to handle, then we are using CPU to do the resolve work which directly resolve to the front framebuffer. No damage rectangle reporting is need. Hence, the 2D performance of graphics is the more important. GEM SHMEM helpers is not better than vram helpers in the perspective of performance. >> +   if (ret) { >> +       drm_err(ddev, "vram helper init failed: %d\n", ret); >> +       goto err; >> +   } >> + >> +   ret = lsdc_mode_config_init(ddev, descp); >> +   if (ret) { >> +       drm_dbg(ddev, "%s: %d\n", __func__, ret); >> +       goto err; >> +   } >> + >> +   ret = lsdc_modeset_init(ldev, descp); >> +   if (ret) >> +       goto err; >> + >> +   ret = drm_vblank_init(ddev, descp->num_of_crtc); >> +   if (ret) >> +       goto err; >> + >> +   ret = request_threaded_irq(pdev->irq, >> +                  lsdc_get_irq_handler(ldev), >> +                  lsdc_irq_thread_handler, >> +                  IRQF_ONESHOT, >> +                  dev_name(ddev->dev), >> +                  ddev); >> +   if (ret) { >> +       drm_err(ddev, "Failed to register lsdc interrupt\n"); >> +       goto err; >> +   } >> + >> +   drm_kms_helper_poll_init(ddev); >> + >> +   return ldev; >> + >> +err: >> +   return ERR_PTR(ret); >> +} >> + >> +static int lsdc_pci_probe(struct pci_dev *pdev, >> +             const struct pci_device_id *ent) >> +{ >> +   struct drm_device *ddev; >> +   struct lsdc_device *ldev; >> +   int ret; >> + >> +   ret = pcim_enable_device(pdev); >> +   if (ret) >> +       return ret; >> + >> +   pci_set_master(pdev); >> + >> +   ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); >> +   if (ret) >> +       return ret; >> + >> +   ldev = lsdc_create_device(pdev, ent, &lsdc_drm_driver); >> +   if (IS_ERR(ldev)) >> +       return PTR_ERR(ldev); >> + >> +   ddev = &ldev->base; >> + >> +   ret = drm_dev_register(ddev, 0); >> +   if (ret) >> +       return ret; >> + >> +   drm_fbdev_generic_setup(ddev, 32); >> + >> +   return 0; >> +} >> + >> +static void lsdc_pci_remove(struct pci_dev *pdev) >> +{ >> +   struct drm_device *ddev = pci_get_drvdata(pdev); >> + >> +   drm_dev_unregister(ddev); >> +   drm_atomic_helper_shutdown(ddev); >> +} >> + >> +static int lsdc_drm_freeze(struct drm_device *ddev) >> +{ >> +   int error; >> + >> +   error = drm_mode_config_helper_suspend(ddev); >> +   if (error) >> +       return error; >> + >> +   pci_save_state(to_pci_dev(ddev->dev)); >> + >> +   return 0; >> +} >> + >> +static int lsdc_drm_resume(struct device *dev) >> +{ >> +   struct pci_dev *pdev = to_pci_dev(dev); >> +   struct drm_device *ddev = pci_get_drvdata(pdev); >> + >> +   return drm_mode_config_helper_resume(ddev); >> +} >> + >> +static int lsdc_pm_freeze(struct device *dev) >> +{ >> +   struct pci_dev *pdev = to_pci_dev(dev); >> +   struct drm_device *ddev = pci_get_drvdata(pdev); >> + >> +   return lsdc_drm_freeze(ddev); >> +} >> + >> +static int lsdc_pm_thaw(struct device *dev) >> +{ >> +   return lsdc_drm_resume(dev); >> +} >> + >> +static int lsdc_pm_suspend(struct device *dev) >> +{ >> +   struct pci_dev *pdev = to_pci_dev(dev); >> +   int error; >> + >> +   error = lsdc_pm_freeze(dev); >> +   if (error) >> +       return error; >> + >> +   pci_save_state(pdev); >> +   /* Shut down the device */ >> +   pci_disable_device(pdev); >> +   pci_set_power_state(pdev, PCI_D3hot); >> + >> +   return 0; >> +} >> + >> +static int lsdc_pm_resume(struct device *dev) >> +{ >> +   struct pci_dev *pdev = to_pci_dev(dev); >> + >> +   if (pcim_enable_device(pdev)) >> +       return -EIO; >> + >> +   pci_set_power_state(pdev, PCI_D0); >> + >> +   pci_restore_state(pdev); >> + >> +   return lsdc_pm_thaw(dev); >> +} >> + >> +static const struct dev_pm_ops lsdc_pm_ops = { >> +   .suspend = lsdc_pm_suspend, >> +   .resume = lsdc_pm_resume, >> +   .freeze = lsdc_pm_freeze, >> +   .thaw = lsdc_pm_thaw, >> +   .poweroff = lsdc_pm_freeze, >> +   .restore = lsdc_pm_resume, >> +}; >> + >> +static const struct pci_device_id lsdc_pciid_list[] = { >> +   {PCI_VENDOR_ID_LOONGSON, 0x7a06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, >> +    (kernel_ulong_t)&dc_in_ls7a1000}, >> +   {PCI_VENDOR_ID_LOONGSON, 0x7a36, PCI_ANY_ID, PCI_ANY_ID, 0, 0, >> +    (kernel_ulong_t)&dc_in_ls7a2000}, >> +   {0, 0, 0, 0, 0, 0, 0} >> +}; >> + >> +static struct pci_driver lsdc_pci_driver = { >> +   .name = DRIVER_NAME, >> +   .id_table = lsdc_pciid_list, >> +   .probe = lsdc_pci_probe, >> +   .remove = lsdc_pci_remove, >> +   .driver.pm = &lsdc_pm_ops, >> +}; >> + >> +static int __init lsdc_module_init(void) >> +{ >> +   struct pci_dev *pdev = NULL; >> + >> +   if (drm_firmware_drivers_only()) >> +       return -EINVAL; > >  return -ENODEV; OK, no problem, I will fix it at new version. I remember i already fix this, sorry. > >> + >> +   while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) { >> +       /* >> +        * Multiple video card workaround: lsdc will always be >> +        * selected as the default boot device by vgaarb subsystem. >> +        */ >> +       if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) { >> +           pr_info("Discrete graphic card detected, abort\n"); >> +           return 0; >> +       } >> +   } > > I don't understand this code. You are not registering your driver if > there's another graphics card? > Yes, we do not want to registering this driver if there's discrete graphics card or BMC in the system. for example: 1) Loongson desktop computer(ls3a5000 + ls7a2000 with amdgpu rx580 mounted) On such a configuration, loongson display controller and rx580 co-exist in the system. As discrete GPU is more powerful, it seems that there no need to using LSDC driver anymore. The user is most likely want to using the rx580 as primary GPU and Using rx580 alone is enough for user in such a case. It is introduce unnecessary problems and confusion If we leave lsdc driver live on the system together with amdgpu/ati drm driver. I have just tested the case which leave lsdc driver live on the system together with radeon drm driver. the monitor connected with lsdc is black, not working as a output slave. 2) Loongson ls3c5000 server with aspeed 2400/25000 BMC card mounted. The BMC provide remote access and control besides the graphics, use it alone is enough, we have solve 4 ~ 5 bugs on our product side. we don't want make a mess. our tester get confused, than find a amdgpu and plug in, then he get three video co-exist in the sistem. lsdc, ast and amdgpu, there /dev/dri/card0, /dev/dri/car1 and /dev/dri/card2. if i bring etnaviv up, then i can get four. > Best regards > Thomas > Thinks your valuable advice and guiding, we will take time to fix all the problems your mentioned, and prepare next version. >> + >> +   return pci_register_driver(&lsdc_pci_driver); >> +} >> +module_init(lsdc_module_init); >> + >> +static void __exit lsdc_module_exit(void) >> +{ >> +   pci_unregister_driver(&lsdc_pci_driver); >> +} >> +module_exit(lsdc_module_exit); >> + >> +MODULE_DEVICE_TABLE(pci, lsdc_pciid_list); >> +MODULE_AUTHOR(DRIVER_AUTHOR); >> +MODULE_DESCRIPTION(DRIVER_DESC); >> +MODULE_LICENSE("GPL"); >> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.h >> b/drivers/gpu/drm/lsdc/lsdc_drv.h >> new file mode 100644 >> index 000000000000..d67dae0414ca >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.h >> @@ -0,0 +1,274 @@ >> +/* SPDX-License-Identifier: GPL-2.0 */ >> +/* >> + * KMS driver for Loongson display controller >> + * Copyright (C) 2022 Loongson Corporation >> + * >> + * Authors: >> + *     Li Yi <liyi@loongson.cn> >> + *     Li Chenyang <lichenyang@loongson.cn> >> + *     Sui Jingfeng <suijingfeng@loongson.cn> >> + */ >> + >> +#ifndef __LSDC_DRV_H__ >> +#define __LSDC_DRV_H__ >> + >> +#include <linux/i2c.h> >> +#include <linux/i2c-algo-bit.h> >> + >> +#include <drm/drm_print.h> >> +#include <drm/drm_device.h> >> +#include <drm/drm_crtc.h> >> +#include <drm/drm_plane.h> >> +#include <drm/drm_connector.h> >> +#include <drm/drm_encoder.h> >> +#include <drm/drm_drv.h> >> +#include <drm/drm_atomic.h> >> + >> +#include "lsdc_regs.h" >> +#include "lsdc_pll.h" >> + >> +#define LSDC_NUM_CRTC          2 >> + >> +/* >> + * The display controller in LS7A2000 integrate three loongson >> self-made >> + * encoder. Display pipe 0 has a transparent VGA encoder and a HDMI >> phy, >> + * they are parallel. Display pipe 1 has only one HDMI phy. >> + *      ______________________ _____________ >> + *     |            +-----+ | |            | >> + *     | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA >> Monitor |<---+ >> + *     |       |   +-----+ | |_____________|   | >> + *     |       |            | ______________   | >> + *     |       |   +------+ | |             |  | >> + *     |       +--> | HDMI | ----> HDMI Connector --> | HDMI >> Monitor |<--+ >> + *     |            +------+ | |______________|  | >> + *     |           +------+ >> |                                          | >> + *     |           | i2c6 | >> <-------------------------------------------+ >> + *     |           +------+ | >> + *     |                     | >> + *     |   DC in LS7A2000   | >> + *     |                     | >> + *     |           +------+ | >> + *     |           | i2c7 | <--------------------------------+ >> + *     |           +------+ | | >> + *     |                     | ______|_______ >> + *     |           +------+ | |             | >> + *     | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI >> Monitor | >> + *     |           +------+ | |______________| >> + *     |______________________| >> + * >> + * >> + * The display controller in LS7A1000 integrate two-way DVO, external >> + * encoder is required except use directly with dpi(rgb888) panel. >> + * ___________________ _________ >> + *     |           -------| |        | >> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >> Display | >> + *     | _  _    -------|       ^            ^ |_________| >> + *     | | | | |   -------|       |            | >> + *     | |_| |_|   | i2c6 <--------+-------------+ >> + *     |           -------| >> + *     | _  _    -------| >> + *     | | | | |   | i2c7 <--------+-------------+ >> + *     | |_| |_|   -------|       | |            _________ >> + *     |           -------|       |            | |        | >> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | >> Panel | >> + *     |           -------| |_________| >> + *     |___________________| >> + * >> + * >> + * The display controller in LS2K1000. >> + * ___________________ _________ >> + *     |           -------| |        | >> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >> Display | >> + *     | _  _    -------|       ^             ^ |_________| >> + *     | | | | |          |       |             | >> + *     | |_| |_|          |    +------+         | >> + *     |                  <---->| i2c0 |<---------+ >> + *     | DC in LS2K1000  |    +------+ >> + *     | _  _           |    +------+ >> + *     | | | | |          <---->| i2c1 |----------+ >> + *     | |_| |_|          |    +------+ |           _________ >> + *     |           -------|       |             | |        | >> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | >> Panel | >> + *     |           -------| |_________| >> + *     |___________________| >> + * >> + * >> + * The display controller in LS2K0500, LS2K0500 has a built-in >> transparent >> + * VGA encoder which is connected to display pipe 1(CRTC1). >> + * ___________________ _________ >> + *     |           -------| |        | >> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >> Display | >> + *     | _  _    -------|       ^             ^ |_________| >> + *     | | | | |          |       |             | >> + *     | |_| |_|          |    +------+         | >> + *     |                  <---->| i2c4 |<---------+ >> + *     | DC in LS2K0500  |    +------+ >> + *     | _  _           |    +------+ >> + *     | | | | |          <---->| i2c5 |-------------------+ >> + *     | |_| |_|          |    +------+ ______|______ >> + *     |          +-----+ | |            | >> + *     | CRTC1 --> | VGA | ------------------------> | VGA Monitor | >> + *     |          +-----+ | |_____________| >> + *     |___________________| >> + * >> + * LS7A1000 and LS7A2000 are bridge chips, has dedicated Video RAM. >> + * while LS2K2000/LS2K1000/LS2K0500 are SoC, they don't have dediacated >> + * Video RAM. The dc in LS2K2000 is basicly same with the dc in >> LS7A1000 >> + * except that LS2K2000 don't have a video RAM and have only one >> built-in >> + * hdmi encoder. >> + * >> + * There is only a 1:1 mapping of encoders and connectors for the DC, >> + * each CRTC have two FB address registers. >> + */ >> + >> +enum loongson_chip_family { >> +   CHIP_UNKNOWN = 0, >> +   CHIP_LS7A1000 = 1, /* North bridge of >> LS3A3000/LS3A4000/LS3A5000 */ >> +   CHIP_LS7A2000 = 2, /* Update version of LS7A1000, with built-in >> HDMI encoder */ >> +   CHIP_LS2K0500 = 3, /* Reduced version of LS2K1000, single core */ >> +   CHIP_LS2K1000 = 4, /* 2-Core Mips64r2/LA264 SoC, 64-bit, 1.0 >> Ghz */ >> +   CHIP_LS2K2000 = 5, /* 2-Core 64-bit LA364 SoC, 1.2 ~ 1.5 Ghz */ >> +   CHIP_LAST, >> +}; >> + >> +struct lsdc_desc { >> +   enum loongson_chip_family chip; >> +   u32 num_of_crtc; >> +   u32 max_pixel_clk; >> +   u32 max_width; >> +   u32 max_height; >> +   u32 num_of_hw_cursor; >> +   u32 hw_cursor_w; >> +   u32 hw_cursor_h; >> +   u32 pitch_align; /* DMA alignment constraint */ >> +   u64 mc_bits;     /* physical address bus bit width */ >> +   bool has_vblank_counter; >> +   bool has_builtin_i2c; >> +   bool has_vram; >> +   bool has_hpd_reg; >> +   bool is_soc; >> +}; >> + >> +struct lsdc_i2c { >> +   struct i2c_adapter adapter; >> +   struct i2c_algo_bit_data bit; >> +   struct drm_device *ddev; >> +   void __iomem *reg_base; >> +   void __iomem *dir_reg; >> +   void __iomem *dat_reg; >> +   /* pin bit mask */ >> +   u8 sda; >> +   u8 scl; >> +}; >> + >> +/* >> + * struct lsdc_display_pipe - Abstraction of hardware display pipeline. >> + * @crtc: CRTC control structure >> + * @primary: framebuffer plane control structure >> + * @cursor: cursor plane control structure >> + * @output: output control structure of this display pipe bind >> + * @pixpll: pixel pll control structure >> + * @index: the index corresponding to the hardware display pipe >> + */ >> +struct lsdc_display_pipe { >> +   struct drm_crtc crtc; >> +   struct drm_plane primary; >> +   struct drm_plane cursor; >> +   struct drm_encoder encoder; >> +   struct drm_connector connector; >> +   struct lsdc_pll pixpll; >> +   struct lsdc_i2c *li2c; >> +   int index; >> +}; >> + >> +static inline struct lsdc_display_pipe * >> +crtc_to_display_pipe(struct drm_crtc *crtc) >> +{ >> +   return container_of(crtc, struct lsdc_display_pipe, crtc); >> +} >> + >> +static inline struct lsdc_display_pipe * >> +cursor_to_display_pipe(struct drm_plane *cursor) >> +{ >> +   return container_of(cursor, struct lsdc_display_pipe, cursor); >> +} >> + >> +static inline struct lsdc_display_pipe * >> +connector_to_display_pipe(struct drm_connector *conn) >> +{ >> +   return container_of(conn, struct lsdc_display_pipe, connector); >> +} >> + >> +static inline struct lsdc_display_pipe * >> +encoder_to_display_pipe(struct drm_encoder *enc) >> +{ >> +   return container_of(enc, struct lsdc_display_pipe, encoder); >> +} >> + >> +struct lsdc_crtc_state { >> +   struct drm_crtc_state base; >> +   struct lsdc_pll_parms pparms; >> +}; >> + >> +struct lsdc_device { >> +   struct drm_device base; >> + >> +   /* @reglock: protects concurrent register access */ >> +   spinlock_t reglock; >> +   void __iomem *reg_base; >> +   void __iomem *vram; >> +   resource_size_t vram_base; >> +   resource_size_t vram_size; >> +   u64 mc_mask; >> + >> +   struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC]; >> + >> +   /* @num_output: count the number of active display pipe */ >> +   unsigned int num_output; >> + >> +   /* @descp: features description of the DC variant */ >> +   const struct lsdc_desc *descp; >> + >> +   u32 irq_status; >> +}; >> + >> +static inline struct lsdc_device * >> +to_lsdc(struct drm_device *ddev) >> +{ >> +   return container_of(ddev, struct lsdc_device, base); >> +} >> + >> +static inline struct lsdc_crtc_state * >> +to_lsdc_crtc_state(struct drm_crtc_state *base) >> +{ >> +   return container_of(base, struct lsdc_crtc_state, base); >> +} >> + >> +void lsdc_debugfs_init(struct drm_minor *minor); >> + >> +int lsdc_crtc_init(struct drm_device *ddev, >> +          struct drm_crtc *crtc, >> +          unsigned int index, >> +          struct drm_plane *primary, >> +          struct drm_plane *cursor); >> + >> +int lsdc_plane_init(struct lsdc_device *ldev, >> +           struct drm_plane *plane, >> +           enum drm_plane_type type, >> +           unsigned int index); >> + >> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index); >> + >> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >> +                     void *base, >> +                     unsigned int index); >> + >> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >> int index); >> + >> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg); >> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev); >> + >> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset); >> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 val); >> + >> +#endif >> diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c >> b/drivers/gpu/drm/lsdc/lsdc_i2c.c >> new file mode 100644 >> index 000000000000..b380098409ca >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c >> @@ -0,0 +1,201 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> + >> +#include <linux/i2c.h> >> +#include <drm/drm_managed.h> >> +#include "lsdc_drv.h" >> +#include "lsdc_regs.h" >> + >> +/* >> + * ls7a_gpio_i2c_set - set the state of a gpio pin indicated by mask >> + * @mask: gpio pin mask >> + * @state: "0" for low, "1" for high >> + */ >> +static void ls7a_gpio_i2c_set(struct lsdc_i2c * const li2c, int >> mask, int state) >> +{ >> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >> +   unsigned long flags; >> +   u8 val; >> + >> +   spin_lock_irqsave(&ldev->reglock, flags); >> + >> +   if (state) { >> +       /* >> +        * Setting this pin as input directly, write 1 for Input. >> +        * The external pull-up resistor will pull the level up >> +        */ >> +       val = readb(li2c->dir_reg); >> +       val |= mask; >> +       writeb(val, li2c->dir_reg); >> +   } else { >> +       /* First set this pin as output, write 0 for Output */ >> +       val = readb(li2c->dir_reg); >> +       val &= ~mask; >> +       writeb(val, li2c->dir_reg); >> + >> +       /* Then, make this pin output 0 */ >> +       val = readb(li2c->dat_reg); >> +       val &= ~mask; >> +       writeb(val, li2c->dat_reg); >> +   } >> + >> +   spin_unlock_irqrestore(&ldev->reglock, flags); >> +} >> + >> +/* >> + * ls7a_gpio_i2c_get - read value back from the gpio pin indicated >> by mask >> + * @mask: gpio pin mask >> + * return "0" for low, "1" for high >> + */ >> +static int ls7a_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask) >> +{ >> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >> +   unsigned long flags; >> +   u8 val; >> + >> +   spin_lock_irqsave(&ldev->reglock, flags); >> + >> +   /* First set this pin as input */ >> +   val = readb(li2c->dir_reg); >> +   val |= mask; >> +   writeb(val, li2c->dir_reg); >> + >> +   /* Then get level state from this pin */ >> +   val = readb(li2c->dat_reg); >> + >> +   spin_unlock_irqrestore(&ldev->reglock, flags); >> + >> +   return (val & mask) ? 1 : 0; >> +} >> + >> +static void ls7a_i2c_set_sda(void *i2c, int state) >> +{ >> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >> +   /* set state on the li2c->sda pin */ >> +   return ls7a_gpio_i2c_set(li2c, li2c->sda, state); >> +} >> + >> +static void ls7a_i2c_set_scl(void *i2c, int state) >> +{ >> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >> +   /* set state on the li2c->scl pin */ >> +   return ls7a_gpio_i2c_set(li2c, li2c->scl, state); >> +} >> + >> +static int ls7a_i2c_get_sda(void *i2c) >> +{ >> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >> +   /* read value from the li2c->sda pin */ >> +   return ls7a_gpio_i2c_get(li2c, li2c->sda); >> +} >> + >> +static int ls7a_i2c_get_scl(void *i2c) >> +{ >> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >> +   /* read the value from the li2c->scl pin */ >> +   return ls7a_gpio_i2c_get(li2c, li2c->scl); >> +} >> + >> +static void lsdc_destroy_i2c(struct drm_device *ddev, void *data) >> +{ >> +   struct lsdc_i2c *li2c = (struct lsdc_i2c *)data; >> + >> +   if (li2c) { >> +       i2c_del_adapter(&li2c->adapter); >> +       kfree(li2c); >> +   } >> +} >> + >> +/* >> + * The DC in ls7a1000/ls7a2000 have builtin gpio hardware >> + * >> + * @base: gpio reg base >> + * @index: output channel index, 0 for DVO0, 1 for DVO1 >> + */ >> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >> +                     void *base, >> +                     unsigned int index) >> +{ >> +   struct i2c_adapter *adapter; >> +   struct lsdc_i2c *li2c; >> +   int ret; >> + >> +   li2c = kzalloc(sizeof(*li2c), GFP_KERNEL); >> +   if (!li2c) >> +       return ERR_PTR(-ENOMEM); >> + >> +   if (index == 0) { >> +       li2c->sda = 0x01; /* pin 0 */ >> +       li2c->scl = 0x02; /* pin 1 */ >> +   } else if (index == 1) { >> +       li2c->sda = 0x04; /* pin 2 */ >> +       li2c->scl = 0x08; /* pin 3 */ >> +   } >> + >> +   li2c->reg_base = base; >> +   li2c->ddev = ddev; >> +   li2c->dir_reg = li2c->reg_base + LS7A_DC_GPIO_DIR_REG; >> +   li2c->dat_reg = li2c->reg_base + LS7A_DC_GPIO_DAT_REG; >> + >> +   li2c->bit.setsda = ls7a_i2c_set_sda; >> +   li2c->bit.setscl = ls7a_i2c_set_scl; >> +   li2c->bit.getsda = ls7a_i2c_get_sda; >> +   li2c->bit.getscl = ls7a_i2c_get_scl; >> +   li2c->bit.udelay = 5; >> +   li2c->bit.timeout = usecs_to_jiffies(2200); >> +   li2c->bit.data = li2c; >> + >> +   adapter = &li2c->adapter; >> +   adapter->algo_data = &li2c->bit; >> +   adapter->owner = THIS_MODULE; >> +   adapter->class = I2C_CLASS_DDC; >> +   adapter->dev.parent = ddev->dev; >> +   adapter->nr = -1; >> + >> +   snprintf(adapter->name, sizeof(adapter->name), "lsdc-i2c%u", >> index); >> + >> +   i2c_set_adapdata(adapter, li2c); >> + >> +   ret = i2c_bit_add_bus(adapter); >> +   if (ret) { >> +       kfree(li2c); >> +       return ERR_PTR(ret); >> +   } >> + >> +   ret = drmm_add_action_or_reset(ddev, lsdc_destroy_i2c, li2c); >> +   if (ret) >> +       return NULL; >> + >> +   drm_info(ddev, "%s(sda=%u, scl=%u) created for connector-%u\n", >> +        adapter->name, li2c->sda, li2c->scl, index); >> + >> +   return li2c; >> +} >> + >> +static int lsdc_get_i2c_id(struct lsdc_device *ldev, int index) >> +{ >> +   const struct lsdc_desc *descp = ldev->descp; >> + >> +   if (descp->chip == CHIP_LS2K1000) >> +       return index; >> + >> +   if (descp->chip == CHIP_LS2K0500) >> +       return index + 2; >> + >> +   return index; >> +} >> + >> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >> +                    int index) >> +{ >> +   const struct lsdc_desc *descp = ldev->descp; >> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >> + >> +   if (descp->has_builtin_i2c) { >> +       struct lsdc_i2c *li2c = dispipe->li2c; >> + >> +       if (li2c) >> +           return &li2c->adapter; >> +   } >> + >> +   return i2c_get_adapter(lsdc_get_i2c_id(ldev, index)); >> +} >> diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.c >> b/drivers/gpu/drm/lsdc/lsdc_irq.c >> new file mode 100644 >> index 000000000000..71c85f09bb6f >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/lsdc_irq.c >> @@ -0,0 +1,86 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> + >> +#include <drm/drm_vblank.h> >> +#include "lsdc_drv.h" >> +#include "lsdc_regs.h" >> + >> +/* >> + * For the DC in ls7a2000, clearing interrupt status is achieved by >> + * write "1" to LSDC_INT_REG, For the DC in ls7a1000, ls2k1000 and >> + * ls2k0500, clearing interrupt status is achieved by write "0" to >> + * LSDC_INT_REG. Two different hardware engineer of Loongson modify >> + * it as their will. >> + */ >> + >> +/* For the DC in ls7a2000 */ >> +static irqreturn_t lsdc_irq_handler(int irq, void *arg) >> +{ >> +   struct drm_device *ddev = arg; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   u32 val; >> + >> +   /* Read the interrupt status */ >> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >> +   if ((val & INT_STATUS_MASK) == 0) { >> +       drm_warn(ddev, "no interrupt occurs\n"); >> +       return IRQ_NONE; >> +   } >> + >> +   ldev->irq_status = val; >> + >> +   /* write "1" to clear the interrupt status */ >> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >> + >> +   return IRQ_WAKE_THREAD; >> +} >> + >> +/* For the DC in ls7a1000, ls2k1000 and ls2k0500 */ >> +static irqreturn_t lsdc_irq_handler_legacy(int irq, void *arg) >> +{ >> +   struct drm_device *ddev = arg; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   u32 val; >> + >> +   /* Read the interrupt status */ >> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >> +   if ((val & INT_STATUS_MASK) == 0) { >> +       drm_warn(ddev, "no interrupt occurs\n"); >> +       return IRQ_NONE; >> +   } >> + >> +   ldev->irq_status = val; >> + >> +   /* write "0" to clear the interrupt status */ >> +   lsdc_wreg32(ldev, LSDC_INT_REG, val & ~INT_STATUS_MASK); >> + >> +   return IRQ_WAKE_THREAD; >> +} >> + >> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev) >> +{ >> +   const struct lsdc_desc *descp = ldev->descp; >> + >> +   if (descp->chip == CHIP_LS7A2000) >> +       return lsdc_irq_handler; >> + >> +   return lsdc_irq_handler_legacy; >> +} >> + >> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg) >> +{ >> +   struct drm_device *ddev = arg; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   struct drm_crtc *crtc; >> + >> +   if (ldev->irq_status & INT_CRTC0_VSYNC) { >> +       crtc = drm_crtc_from_index(ddev, 0); >> +       drm_crtc_handle_vblank(crtc); >> +   } >> + >> +   if (ldev->irq_status & INT_CRTC1_VSYNC) { >> +       crtc = drm_crtc_from_index(ddev, 1); >> +       drm_crtc_handle_vblank(crtc); >> +   } >> + >> +   return IRQ_HANDLED; >> +} >> diff --git a/drivers/gpu/drm/lsdc/lsdc_output.c >> b/drivers/gpu/drm/lsdc/lsdc_output.c >> new file mode 100644 >> index 000000000000..d4bc7666d9ea >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/lsdc_output.c >> @@ -0,0 +1,452 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> + >> +#include <drm/drm_edid.h> >> +#include <drm/drm_probe_helper.h> >> +#include <drm/drm_atomic_helper.h> >> +#include <drm/drm_connector.h> >> +#include "lsdc_drv.h" >> + >> +static int lsdc_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 enum drm_connector_status >> +lsdc_unknown_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; >> +   } else { >> +       if (connector->connector_type == DRM_MODE_CONNECTOR_DPI) >> +           return connector_status_connected; >> + >> +       if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) >> +           return connector_status_connected; >> +   } >> + >> +   return connector_status_unknown; >> +} >> + >> +static enum drm_connector_status >> +lsdc_hdmi_connector_detect(struct drm_connector *connector, bool force) >> +{ >> +   struct lsdc_display_pipe *pipe = >> connector_to_display_pipe(connector); >> +   struct lsdc_device *ldev = to_lsdc(connector->dev); >> +   u32 val; >> + >> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >> + >> +   if (pipe->index == 0) { >> +       if (val & HDMI0_HPD_FLAG) >> +           return connector_status_connected; >> +   } >> + >> +   if (pipe->index == 1) { >> +       if (val & HDMI1_HPD_FLAG) >> +           return connector_status_connected; >> +   } >> + >> +   return connector_status_disconnected; >> +} >> + >> +static enum drm_connector_status >> +lsdc_hdmi_vga_connector_detect(struct drm_connector *connector, bool >> force) >> +{ >> +   struct lsdc_display_pipe *pipe = >> connector_to_display_pipe(connector); >> +   struct drm_device *ddev = connector->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   struct i2c_adapter *ddc; >> +   u32 val; >> + >> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >> + >> +   if (pipe->index == 1) { >> +       if (val & HDMI1_HPD_FLAG) >> +           return connector_status_connected; >> + >> +       return connector_status_disconnected; >> +   } >> + >> +   if (pipe->index == 0) { >> +       if (val & HDMI0_HPD_FLAG) >> +           return connector_status_connected; >> + >> +       ddc = connector->ddc; >> +       if (ddc) { >> +           if (drm_probe_ddc(ddc)) >> +               return connector_status_connected; >> + >> +           return connector_status_disconnected; >> +       } >> +   } >> + >> +   return connector_status_unknown; >> +} >> + >> +static struct drm_encoder * >> +lsdc_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 >> lsdc_connector_helpers = { >> +   .atomic_best_encoder = lsdc_connector_get_best_encoder, >> +   .get_modes = lsdc_get_modes, >> +}; >> + >> +static const struct drm_connector_funcs lsdc_unknown_connector_funcs >> = { >> +   .detect = lsdc_unknown_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 const struct drm_connector_funcs >> lsdc_hdmi_vga_connector_funcs = { >> +   .detect = lsdc_hdmi_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, >> +}; >> + >> +static const struct drm_connector_funcs lsdc_hdmi_connector_funcs = { >> +   .detect = lsdc_hdmi_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 const struct drm_connector_funcs * >> +lsdc_get_connector_func(struct lsdc_device *ldev, unsigned int index) >> +{ >> +   const struct lsdc_desc *descp = ldev->descp; >> + >> +   if (descp->chip == CHIP_LS7A2000) { >> +       if (index == 0) >> +           return &lsdc_hdmi_vga_connector_funcs; >> + >> +       if (index == 1) >> +           return &lsdc_hdmi_connector_funcs; >> +   } >> + >> +   return &lsdc_unknown_connector_funcs; >> +} >> + >> +/* >> + * we provide a default support before DT/VBIOS is supported >> + */ >> +static int lsdc_get_encoder_type(struct lsdc_device *ldev, >> +                unsigned int index) >> +{ >> +   const struct lsdc_desc *descp = ldev->descp; >> + >> +   if (descp->chip == CHIP_LS7A2000) { >> +       if (index == 0) >> +           return DRM_MODE_ENCODER_DAC; >> +       if (index == 1) >> +           return DRM_MODE_ENCODER_TMDS; >> +   } >> + >> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == CHIP_LS2K1000) { >> +       if (index == 0) >> +           return DRM_MODE_ENCODER_DPI; >> +       if (index == 1) >> +           return DRM_MODE_ENCODER_DPI; >> +   } >> + >> +   if (descp->chip == CHIP_LS2K0500) { >> +       if (index == 0) >> +           return DRM_MODE_ENCODER_DPI; >> +       if (index == 1) >> +           return DRM_MODE_ENCODER_DAC; >> +   } >> + >> +   return DRM_MODE_ENCODER_NONE; >> +} >> + >> +/* >> + * provide a default before DT/VBIOS support is upstreamed >> + */ >> +static int lsdc_get_connector_type(struct lsdc_device *ldev, >> +                  unsigned int index) >> +{ >> +   const struct lsdc_desc *descp = ldev->descp; >> + >> +   if (descp->chip == CHIP_LS7A2000) { >> +       if (index == 0) >> +           return DRM_MODE_CONNECTOR_VGA; >> +       if (index == 1) >> +           return DRM_MODE_CONNECTOR_HDMIA; >> +   } >> + >> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == CHIP_LS2K1000) { >> +       if (index == 0) >> +           return DRM_MODE_CONNECTOR_DPI; >> +       if (index == 1) >> +           return DRM_MODE_CONNECTOR_DPI; >> +   } >> + >> +   if (descp->chip == CHIP_LS2K0500) { >> +       if (index == 0) >> +           return DRM_MODE_CONNECTOR_DPI; >> +       if (index == 1) >> +           return DRM_MODE_CONNECTOR_VGA; >> +   } >> + >> +   return DRM_MODE_CONNECTOR_Unknown; >> +} >> + >> +static void lsdc_hdmi_disable(struct drm_encoder *encoder) >> +{ >> +   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; >> + >> +   if (index == 0) >> +       val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG); >> +   else if (index == 1) >> +       val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG); >> + >> +   val &= ~HDMI_PHY_EN; >> + >> +   if (index == 0) >> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >> +   else if (index == 1) >> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >> + >> +   drm_dbg(ddev, "HDMI-%u disabled\n", index); >> +} >> + >> +static void lsdc_hdmi_enable(struct drm_encoder *encoder) >> +{ >> +   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; >> + >> +   /* we are using software gpio emulated i2c */ >> +   val = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >> + >> +   if (index == 0) { >> +       /* Enable HDMI-0 */ >> +       lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >> + >> +       lsdc_wreg32(ldev, LSDC_HDMI0_ZONE_REG, 0x00400040); >> +   } else if (index == 1) { >> +       /* Enable HDMI-1 */ >> +       lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >> + >> +       lsdc_wreg32(ldev, LSDC_HDMI1_ZONE_REG, 0x00400040); >> +   } >> + >> +   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; >> + >> +   if (index == 0) >> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >> +   else if (index == 1) >> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >> + >> +   drm_dbg(ddev, "HDMI-%u enabled\n", index); >> +} >> + >> +/* >> + * Fout = M * Fin >> + * >> + * M = (4 * LF) / (IDF * ODF) >> + * >> + * Loongson HDMI require M = 10 >> + */ >> +static void lsdc_hdmi_phy_pll_config(struct lsdc_device *ldev, >> +                    int index, >> +                    int clock) >> +{ >> +   struct drm_device *ddev = &ldev->base; >> +   int count = 0; >> +   u32 val; >> + >> +   /* disable phy pll */ >> +   if (index == 0) >> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, 0x0); >> +   else if (index == 1) >> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, 0x0); >> + >> +   /* >> +    * 10 = (4 * 40) / (8 * 2) >> +    */ >> +   val = (8 << HDMI_PLL_IDF_SHIFT) | >> +         (40 << HDMI_PLL_LF_SHIFT) | >> +         (1 << HDMI_PLL_ODF_SHIFT) | >> +         HDMI_PLL_ENABLE; >> + >> +   drm_dbg(ddev, "HDMI-%u clock: %d\n", index, clock); >> + >> +   if (index == 0) >> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, val); >> +   else if (index == 1) >> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, val); >> + >> +   /* wait hdmi phy pll lock */ >> +   do { >> +       if (index == 0) >> +           val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG); >> +       else if (index == 1) >> +           val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_PLL_REG); >> + >> +       ++count; >> + >> +       if (val & HDMI_PLL_LOCKED) { >> +           drm_dbg(ddev, "Setting HDMI-%u PLL success(take %d >> cycles)\n", >> +               index, count); >> +           break; >> +       } >> +   } while (count < 1000); >> +} >> + >> +static void lsdc_hdmi_atomic_mode_set(struct drm_encoder *encoder, >> +                     struct drm_crtc_state *crtc_state, >> +                     struct drm_connector_state *conn_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; >> +   struct drm_display_mode *mode = &crtc_state->mode; >> + >> +   lsdc_hdmi_phy_pll_config(ldev, index, mode->clock); >> + >> +   drm_dbg(ddev, "HDMI-%u modeset\n", index); >> +} >> + >> +static const struct drm_encoder_helper_funcs lsdc_hdmi_helper_funcs = { >> +   .disable = lsdc_hdmi_disable, >> +   .enable = lsdc_hdmi_enable, >> +   .atomic_mode_set = lsdc_hdmi_atomic_mode_set, >> +}; >> + >> +static void lsdc_hdmi_reset(struct drm_encoder *encoder) >> +{ >> +   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 = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >> + >> +   lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >> +   lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >> + >> +   drm_dbg(ddev, "HDMI-%u Reset\n", index); >> +} >> + >> +static const struct drm_encoder_funcs lsdc_encoder_funcs = { >> +   .reset = lsdc_hdmi_reset, >> +   .destroy = drm_encoder_cleanup, >> +}; >> + >> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index) >> +{ >> +   const struct lsdc_desc *descp = ldev->descp; >> +   struct drm_device *ddev = &ldev->base; >> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >> +   struct drm_encoder *encoder = &dispipe->encoder; >> +   struct drm_connector *connector = &dispipe->connector; >> +   struct i2c_adapter *ddc = NULL; >> +   struct lsdc_i2c *li2c; >> +   int ret; >> + >> +   ret = drm_encoder_init(ddev, >> +                  encoder, >> +                  &lsdc_encoder_funcs, >> +                  lsdc_get_encoder_type(ldev, index), >> +                  "encoder-%u", >> +                  index); >> +   if (ret) { >> +       drm_err(ddev, "Failed to init encoder: %d\n", ret); >> +       return ret; >> +   } >> + >> +   encoder->possible_crtcs = BIT(index); >> + >> +   if (descp->chip == CHIP_LS7A2000) >> +       drm_encoder_helper_add(encoder, &lsdc_hdmi_helper_funcs); >> + >> +   if (descp->has_builtin_i2c) { >> +       li2c = lsdc_create_i2c_chan(ddev, ldev->reg_base, index); >> +       if (IS_ERR(li2c)) >> +           return PTR_ERR(li2c); >> + >> +       dispipe->li2c = li2c; >> + >> +       ddc = &li2c->adapter; >> +   } else { >> +       ddc = lsdc_get_i2c_adapter(ldev, index); >> +       if (IS_ERR(ddc)) { >> +           drm_err(ddev, "Error get ddc for output-%u\n", index); >> +           return PTR_ERR(ddc); >> +       } >> +   } >> + >> +   ret = drm_connector_init_with_ddc(ddev, >> +                     connector, >> +                     lsdc_get_connector_func(ldev, index), >> +                     lsdc_get_connector_type(ldev, index), >> +                     ddc); >> +   if (ret) { >> +       drm_err(ddev, "Init connector-%d failed\n", index); >> +       return ret; >> +   } >> + >> +   drm_connector_helper_add(connector, &lsdc_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; >> + >> +   ldev->num_output++; >> + >> +   drm_info(ddev, "output-%u initialized\n", index); >> + >> +   return 0; >> +} >> diff --git a/drivers/gpu/drm/lsdc/lsdc_plane.c >> b/drivers/gpu/drm/lsdc/lsdc_plane.c >> new file mode 100644 >> index 000000000000..0f779c97d53b >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/lsdc_plane.c >> @@ -0,0 +1,443 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> + >> +#include <drm/drm_atomic.h> >> +#include <drm/drm_atomic_helper.h> >> +#include <drm/drm_framebuffer.h> >> +#include <drm/drm_plane_helper.h> >> +#include <drm/drm_gem_vram_helper.h> >> +#include "lsdc_drv.h" >> +#include "lsdc_regs.h" >> +#include "lsdc_pll.h" >> + >> +static const u32 lsdc_primary_formats[] = { >> +   DRM_FORMAT_XRGB8888, >> +   DRM_FORMAT_ARGB8888, >> +}; >> + >> +static const u32 lsdc_cursor_formats[] = { >> +   DRM_FORMAT_ARGB8888, >> +}; >> + >> +static const u64 lsdc_fb_format_modifiers[] = { >> +   DRM_FORMAT_MOD_LINEAR, >> +   DRM_FORMAT_MOD_INVALID >> +}; >> + >> +static void lsdc_update_fb_format(struct lsdc_device *ldev, >> +                 struct drm_crtc *crtc, >> +                 const struct drm_format_info *fmt_info) >> +{ >> +   unsigned int index = drm_crtc_index(crtc); >> +   u32 val; >> +   u32 fmt; >> + >> +   switch (fmt_info->format) { >> +   case DRM_FORMAT_XRGB8888: >> +       fmt = LSDC_PF_XRGB8888; >> +       break; >> +   case DRM_FORMAT_ARGB8888: >> +       fmt = LSDC_PF_XRGB8888; >> +       break; >> +   default: >> +       fmt = LSDC_PF_XRGB8888; >> +       break; >> +   } >> + >> +   if (index == 0) { >> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >> +   } else if (index == 1) { >> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >> +   } >> +} >> + >> +static void lsdc_update_fb_start_addr(struct lsdc_device *ldev, >> +                     struct drm_crtc *crtc, >> +                     u64 paddr) >> +{ >> +   struct drm_device *ddev = &ldev->base; >> +   unsigned int index = drm_crtc_index(crtc); >> +   u32 lo_addr_reg; >> +   u32 hi_addr_reg; >> +   u32 val; >> + >> +   /* >> +    * Find which framebuffer address register should update. >> +    * if FB_ADDR0_REG is in using, we write the address to >> FB_ADDR0_REG, >> +    * if FB_ADDR1_REG is in using, we write the address to >> FB_ADDR1_REG >> +    * for each CRTC, the switch using one fb register to another is >> +    * trigger by triggered by set CFG_PAGE_FLIP bit of >> LSDC_CRTCx_CFG_REG >> +    */ >> +   if (index == 0) { >> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >> +       if (val & CFG_FB_IN_USING) { >> +           lo_addr_reg = LSDC_CRTC0_FB1_LO_ADDR_REG; >> +           hi_addr_reg = LSDC_CRTC0_FB1_HI_ADDR_REG; >> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-0\n"); >> +       } else { >> +           lo_addr_reg = LSDC_CRTC0_FB0_LO_ADDR_REG; >> +           hi_addr_reg = LSDC_CRTC0_FB0_HI_ADDR_REG; >> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-0\n"); >> +       } >> +   } else if (index == 1) { >> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >> +       if (val & CFG_FB_IN_USING) { >> +           lo_addr_reg = LSDC_CRTC1_FB1_LO_ADDR_REG; >> +           hi_addr_reg = LSDC_CRTC1_FB1_HI_ADDR_REG; >> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-1\n"); >> +       } else { >> +           lo_addr_reg = LSDC_CRTC1_FB0_LO_ADDR_REG; >> +           hi_addr_reg = LSDC_CRTC1_FB0_HI_ADDR_REG; >> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-1\n"); >> +       } >> +   } >> + >> +   /* 40-bit width physical address bus */ >> +   lsdc_wreg32(ldev, lo_addr_reg, paddr); >> +   lsdc_wreg32(ldev, hi_addr_reg, (paddr >> 32) & 0xFF); >> + >> +   drm_dbg(ddev, "CRTC-%u scantout from 0x%llx\n", index, paddr); >> +} >> + >> +static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb, >> +                      struct drm_plane_state *state, >> +                      unsigned int plane) >> +{ >> +   unsigned int offset = fb->offsets[plane]; >> + >> +   offset += fb->format->cpp[plane] * (state->src_x >> 16); >> +   offset += fb->pitches[plane] * (state->src_y >> 16); >> + >> +   return offset; >> +} >> + >> +static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb) >> +{ >> +   struct drm_gem_vram_object *gbo; >> +   s64 gpu_addr; >> + >> +   gbo = drm_gem_vram_of_gem(fb->obj[0]); >> +   gpu_addr = drm_gem_vram_offset(gbo); >> + >> +   return gpu_addr; >> +} >> + >> +static int lsdc_primary_plane_atomic_check(struct drm_plane *plane, >> +                      struct drm_atomic_state *state) >> +{ >> +   struct drm_plane_state *plane_state = >> drm_atomic_get_new_plane_state(state, plane); >> +   struct drm_crtc *crtc = plane_state->crtc; >> +   struct drm_crtc_state *crtc_state = >> drm_atomic_get_new_crtc_state(state, crtc); >> + >> +   if (!crtc) >> +       return 0; >> + >> +   return drm_atomic_helper_check_plane_state(plane_state, >> +                          crtc_state, >> +                          DRM_PLANE_NO_SCALING, >> +                          DRM_PLANE_NO_SCALING, >> +                          false, >> +                          true); >> +} >> + >> +static void lsdc_update_fb_stride(struct lsdc_device *ldev, >> +                 struct drm_crtc *crtc, >> +                 unsigned int stride) >> +{ >> +   unsigned int index = drm_crtc_index(crtc); >> + >> +   if (index == 0) >> +       lsdc_wreg32(ldev, LSDC_CRTC0_STRIDE_REG, stride); >> +   else if (index == 1) >> +       lsdc_wreg32(ldev, LSDC_CRTC1_STRIDE_REG, stride); >> + >> +   drm_dbg(crtc->dev, "update stride to %u\n", stride); >> +} >> + >> +static void lsdc_primary_plane_atomic_update(struct drm_plane *plane, >> +                        struct drm_atomic_state *state) >> +{ >> +   struct lsdc_device *ldev = to_lsdc(plane->dev); >> +   struct drm_plane_state *new_plane_state = >> drm_atomic_get_new_plane_state(state, plane); >> +   struct drm_crtc *crtc = new_plane_state->crtc; >> +   struct drm_framebuffer *fb = new_plane_state->fb; >> +   u32 fb_offset = lsdc_get_fb_offset(fb, new_plane_state, 0); >> +   dma_addr_t fb_addr; >> +   s64 gpu_addr; >> + >> +   gpu_addr = lsdc_get_vram_bo_offset(fb); >> +   if (gpu_addr < 0) >> +       return; >> + >> +   fb_addr = ldev->vram_base + gpu_addr + fb_offset; >> + >> +   lsdc_update_fb_start_addr(ldev, crtc, fb_addr); >> + >> +   lsdc_update_fb_stride(ldev, crtc, fb->pitches[0]); >> + >> +   lsdc_update_fb_format(ldev, crtc, fb->format); >> +} >> + >> +static void lsdc_primary_plane_atomic_disable(struct drm_plane *plane, >> +                         struct drm_atomic_state *state) >> +{ >> +   drm_dbg(plane->dev, "%s disabled\n", plane->name); >> +} >> + >> +static const struct drm_plane_helper_funcs >> lsdc_primary_plane_helpers = { >> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >> +   .atomic_check = lsdc_primary_plane_atomic_check, >> +   .atomic_update = lsdc_primary_plane_atomic_update, >> +   .atomic_disable = lsdc_primary_plane_atomic_disable, >> +}; >> + >> +static int lsdc_cursor_atomic_check(struct drm_plane *plane, >> +                   struct drm_atomic_state *state) >> +{ >> +   struct drm_plane_state *new_plane_state = >> drm_atomic_get_new_plane_state(state, plane); >> +   struct drm_framebuffer *fb = new_plane_state->fb; >> +   struct drm_crtc *crtc = new_plane_state->crtc; >> +   struct drm_crtc_state *crtc_state; >> +   int ret; >> + >> +   /* no need for further checks if the plane is being disabled */ >> +   if (!crtc || !fb) >> +       return 0; >> + >> +   if (!new_plane_state->visible) >> +       return 0; >> + >> +   crtc_state = drm_atomic_get_new_crtc_state(state, >> +                          new_plane_state->crtc); >> + >> +   ret = drm_atomic_helper_check_plane_state(new_plane_state, >> +                         crtc_state, >> +                         DRM_PLANE_NO_SCALING, >> +                         DRM_PLANE_NO_SCALING, >> +                         true, >> +                         true); >> + >> +   return ret; >> +} >> + >> +/* >> + * There is only one hardware cursor in ls7a1000, ls2k1000 and >> ls2k0500. >> + * we made it shared by the two CRTC, which can satisfy peoples who use >> + * double screen extend mode only. On clone screen usage case, the >> cursor >> + * on display pipe 1 will not be able to display. >> + * >> + * Update location of the cursor, attach it to CRTC0 or CRTC1 on the >> runtime. >> + */ >> +static void lsdc_cursor_update_location_quirks(struct lsdc_device >> *ldev, >> +                          struct drm_crtc *crtc) >> +{ >> +   u32 val = CURSOR_FORMAT_ARGB8888; >> + >> +   /* >> +    * If bit 4 of LSDC_CURSOR0_CFG_REG is 1, then the cursor will be >> +    * locate at CRTC-1, if bit 4 of LSDC_CURSOR0_CFG_REG is 0, then >> +    * the cursor will be locate at CRTC-0. The cursor is alway on the >> +    * top of the primary. Compositing the primary plane and cursor >> +    * plane is automatically done by hardware. >> +    */ >> +   if (drm_crtc_index(crtc)) >> +       val |= CURSOR_LOCATION; >> + >> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, val); >> +} >> + >> +/* update the position of the cursor */ >> +static void lsdc_cursor_update_position_quirks(struct lsdc_device >> *ldev, >> +                          int x, >> +                          int y) >> +{ >> +   if (x < 0) >> +       x = 0; >> + >> +   if (y < 0) >> +       y = 0; >> + >> +   lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >> +} >> + >> +static void lsdc_cursor_atomic_update_quirks(struct drm_plane *plane, >> +                        struct drm_atomic_state *state) >> +{ >> +   struct drm_device *ddev = plane->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   struct drm_plane_state *new_plane_state = >> drm_atomic_get_new_plane_state(state, plane); >> +   struct drm_plane_state *old_plane_state = >> drm_atomic_get_old_plane_state(state, plane); >> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >> +   struct drm_framebuffer *old_fb = old_plane_state->fb; >> + >> +   if (new_fb != old_fb) { >> +       s64 offset = lsdc_get_vram_bo_offset(new_fb); >> +       u64 cursor_addr = ldev->vram_base + offset; >> + >> +       drm_dbg_kms(ddev, "%s offset: %llx\n", plane->name, offset); >> + >> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >> >> 32) & 0xFF); >> +   } >> + >> +   lsdc_cursor_update_position_quirks(ldev, >> new_plane_state->crtc_x, new_plane_state->crtc_y); >> + >> +   lsdc_cursor_update_location_quirks(ldev, new_plane_state->crtc); >> +} >> + >> +/* update the format, size and location of the cursor */ >> +static void lsdc_cursor_atomic_update(struct drm_plane *plane, >> +                     struct drm_atomic_state *state) >> +{ >> +   struct drm_device *ddev = plane->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   struct lsdc_display_pipe *dispipe = cursor_to_display_pipe(plane); >> +   struct drm_plane_state *new_plane_state = >> drm_atomic_get_new_plane_state(state, plane); >> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >> +   int x = new_plane_state->crtc_x; >> +   int y = new_plane_state->crtc_y; >> +   u32 conf = CURSOR_FORMAT_ARGB8888 | CURSOR_SIZE_64X64; >> +   u64 cursor_addr = ldev->vram_base + >> lsdc_get_vram_bo_offset(new_fb); >> + >> +   if (x < 0) >> +       x = 0; >> + >> +   if (y < 0) >> +       y = 0; >> + >> +   if (dispipe->index == 0) { >> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >> >> 32) & 0xFF); >> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >> +       /* Attach Cursor-0 to CRTC-0 */ >> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, conf & >> ~CURSOR_LOCATION); >> +       lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >> +       return; >> +   } >> + >> +   if (dispipe->index == 1) { >> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_HI_REG, (cursor_addr >> >> 32) & 0xFF); >> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_LO_REG, cursor_addr); >> +       /* Attach Cursor-1 to CRTC-1 */ >> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, conf | >> CURSOR_LOCATION); >> +       lsdc_wreg32(ldev, LSDC_CURSOR1_POSITION_REG, (y << 16) | x); >> +       return; >> +   } >> +} >> + >> +static void lsdc_cursor_atomic_disable_quirks(struct drm_plane *plane, >> +                         struct drm_atomic_state *state) >> +{ >> +   struct drm_device *ddev = plane->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> + >> +   /* Set the format to 0 actually display the cursor */ >> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >> + >> +   drm_dbg(ddev, "%s disabled\n", plane->name); >> +} >> + >> +static void lsdc_cursor_atomic_disable(struct drm_plane *plane, >> +                      struct drm_atomic_state *state) >> +{ >> +   struct drm_device *ddev = plane->dev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   struct lsdc_display_pipe *dispipe = cursor_to_display_pipe(plane); >> + >> +   if (dispipe->index == 0) >> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >> +   else if (dispipe->index == 1) >> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, 0); >> + >> +   drm_dbg(ddev, "%s disabled\n", plane->name); >> +} >> + >> +static const struct drm_plane_helper_funcs lsdc_cursor_helpers_quirk >> = { >> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >> +   .atomic_check = lsdc_cursor_atomic_check, >> +   .atomic_update = lsdc_cursor_atomic_update_quirks, >> +   .atomic_disable = lsdc_cursor_atomic_disable_quirks, >> +}; >> + >> +static const struct drm_plane_helper_funcs lsdc_cursor_plane_helpers >> = { >> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >> +   .atomic_check = lsdc_cursor_atomic_check, >> +   .atomic_update = lsdc_cursor_atomic_update, >> +   .atomic_disable = lsdc_cursor_atomic_disable, >> +}; >> + >> +static const struct drm_plane_funcs lsdc_plane_funcs = { >> +   .update_plane = drm_atomic_helper_update_plane, >> +   .disable_plane = drm_atomic_helper_disable_plane, >> +   .destroy = drm_plane_cleanup, >> +   .reset = drm_atomic_helper_plane_reset, >> +   .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, >> +   .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, >> +}; >> + >> +static const struct drm_plane_helper_funcs * >> +lsdc_get_cursor_helper_funcs(const struct lsdc_desc *descp) >> +{ >> +   if (descp->chip == CHIP_LS7A2000) >> +       return &lsdc_cursor_plane_helpers; >> + >> +   return &lsdc_cursor_helpers_quirk; >> +} >> + >> +int lsdc_plane_init(struct lsdc_device *ldev, >> +           struct drm_plane *plane, >> +           enum drm_plane_type type, >> +           unsigned int index) >> +{ >> +   const struct lsdc_desc *descp = ldev->descp; >> +   struct drm_device *ddev = &ldev->base; >> +   unsigned int format_count; >> +   const u32 *formats; >> +   const char *name; >> +   int ret; >> + >> +   switch (type) { >> +   case DRM_PLANE_TYPE_PRIMARY: >> +       formats = lsdc_primary_formats; >> +       format_count = ARRAY_SIZE(lsdc_primary_formats); >> +       name = "primary-%u"; >> +       break; >> +   case DRM_PLANE_TYPE_CURSOR: >> +       formats = lsdc_cursor_formats; >> +       format_count = ARRAY_SIZE(lsdc_cursor_formats); >> +       name = "cursor-%u"; >> +       break; >> +   case DRM_PLANE_TYPE_OVERLAY: >> +       drm_err(ddev, "overlay plane is not supported\n"); >> +       break; >> +   } >> + >> +   ret = drm_universal_plane_init(ddev, plane, 1 << index, >> +                      &lsdc_plane_funcs, >> +                      formats, format_count, >> +                      lsdc_fb_format_modifiers, >> +                      type, name, index); >> +   if (ret) { >> +       drm_err(ddev, "%s failed: %d\n", __func__, ret); >> +       return ret; >> +   } >> + >> +   switch (type) { >> +   case DRM_PLANE_TYPE_PRIMARY: >> +       drm_plane_helper_add(plane, &lsdc_primary_plane_helpers); >> +       break; >> +   case DRM_PLANE_TYPE_CURSOR: >> +       drm_plane_helper_add(plane, >> lsdc_get_cursor_helper_funcs(descp)); >> +       break; >> +   case DRM_PLANE_TYPE_OVERLAY: >> +       drm_err(ddev, "overlay plane is not supported\n"); >> +       break; >> +   } >> + >> +   return 0; >> +} >> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.c >> b/drivers/gpu/drm/lsdc/lsdc_pll.c >> new file mode 100644 >> index 000000000000..6ed74989a6f5 >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.c >> @@ -0,0 +1,569 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> + >> +#include "lsdc_drv.h" >> +#include "lsdc_regs.h" >> +#include "lsdc_pll.h" >> + >> +/* >> + * The structure of the pixel PLL register is evolved with times. >> + * All loongson's cpu is little endian. >> + */ >> + >> +/* u64 */ >> +struct ls7a1000_pixpll_bitmap { >> +   /* Byte 0 ~ Byte 3 */ >> +   unsigned div_out     : 7;  /* 0 : 6    output clock divider */ >> +   unsigned __1         : 14; /* 7 : 20                         */ >> +   unsigned loopc       : 9;  /* 21 : 29   clock multiplier     */ >> +   unsigned __2         : 2;  /* 30 : 31                         */ >> + >> +   /* Byte 4 ~ Byte 7 */ >> +   unsigned div_ref     : 7;  /* 0 : 6    input clock divider  */ >> +   unsigned locked      : 1;  /* 7        PLL locked status    */ >> +   unsigned sel_out     : 1;  /* 8        output clk selector  */ >> +   unsigned __3         : 2;  /* 9 : 10 reserved             */ >> +   unsigned set_param   : 1;  /* 11       trigger the update   */ >> +   unsigned bypass      : 1;  /* 12                             */ >> +   unsigned powerdown   : 1;  /* 13                             */ >> +   unsigned __4         : 18; /* 14 : 31                        */ >> +}; >> + >> +/* u128 */ >> +struct ls2k1000_pixpll_bitmap { >> +   /* Byte 0 ~ Byte 3 */ >> +   unsigned sel_out     : 1; /* 0     select this PLL         */ >> +   unsigned __1         : 1; /* 1                              */ >> +   unsigned sw_adj_en   : 1; /* 2     allow software adjust   */ >> +   unsigned bypass      : 1; /* 3     bypass L1 PLL           */ >> +   unsigned __2         : 3; /* 4:6                            */ >> +   unsigned lock_en     : 1; /* 7     enable lock L1 PLL      */ >> +   unsigned __3         : 2; /* 8:9                            */ >> +   unsigned lock_check  : 2; /* 10:11  precision check         */ >> +   unsigned __4         : 4; /* 12:15                           */ >> + >> +   unsigned locked      : 1; /* 16     PLL locked flag bit     */ >> +   unsigned __5         : 2; /* 17:18                           */ >> +   unsigned powerdown   : 1; /* 19     powerdown the pll if set */ >> +   unsigned __6         : 6; /* 20:25                           */ >> +   unsigned div_ref     : 6; /* 26:31  L1 Prescaler            */ >> + >> +   /* Byte 4 ~ Byte 7 */ >> +   unsigned loopc       : 10; /* 32:41  Clock Multiplier        */ >> +   unsigned l1_div      : 6; /* 42:47  not used                */ >> +   unsigned __7         : 16; /* 48:63                           */ >> + >> +   /* Byte 8 ~ Byte 15 */ >> +   unsigned div_out     : 6; /* 0 : 5  output clock divider    */ >> +   unsigned __8         : 26; /* 6 : 31                          */ >> +   unsigned __9         : 32; /* 70: 127                         */ >> +}; >> + >> +/* u32 */ >> +struct ls2k0500_pixpll_bitmap { >> +   /* Byte 0 ~ Byte 1 */ >> +   unsigned sel_out     : 1; >> +   unsigned __1         : 2; >> +   unsigned sw_adj_en   : 1;  /* allow software >> adjust             */ >> +   unsigned bypass      : 1;  /* bypass L1 >> PLL                     */ >> +   unsigned powerdown   : 1;  /* write 1 to powerdown the >> PLL      */ >> +   unsigned __2         : 1; >> +   unsigned locked      : 1;  /* 7    Is L1 PLL locked, read >> only */ >> +   unsigned div_ref     : 6;  /* 8:13 ref clock >> divider          */ >> +   unsigned __3         : 2;  /* >> 14:15                             */ >> +   /* Byte 2 ~ Byte 3 */ >> +   unsigned loopc       : 8;  /* 16:23  Clock >> Multiplier          */ >> +   unsigned div_out     : 6;  /* 24:29  output clock >> divider      */ >> +   unsigned __4         : 2;  /* >> 30:31                             */ >> +}; >> + >> +union lsdc_pixpll_bitmap { >> +   struct ls7a1000_pixpll_bitmap ls7a1000; >> +   struct ls2k1000_pixpll_bitmap ls2k1000; >> +   struct ls2k0500_pixpll_bitmap ls2k0500; >> + >> +   u32 dword[4]; >> +}; >> + >> +struct pixclk_to_pll_parm { >> +   /* kHz */ >> +   unsigned int clock; >> + >> +   unsigned short width; >> +   unsigned short height; >> +   unsigned short vrefresh; >> + >> +   /* Stores parameters for programming the Hardware PLLs */ >> +   unsigned short div_out; >> +   unsigned short loopc; >> +   unsigned short div_ref; >> +}; >> + >> +/* >> + * Pixel clock to PLL parameters translation table. >> + * Small static cached value to speed up PLL parameters calculation. >> + */ >> +static const struct pixclk_to_pll_parm pll_param_table[] = { >> +   {148500, 1920, 1080, 60, 11, 49, 3},  /* 1920x1080@60Hz */ >> +                       /* 1920x1080@50Hz */ >> +   {174500, 1920, 1080, 75, 17, 89, 3},  /* 1920x1080@75Hz */ >> +   {181250, 2560, 1080, 75, 8, 58, 4},  /* 2560x1080@75Hz */ >> +   {146250, 1680, 1050, 60, 16, 117, 5},  /* 1680x1050@60Hz */ >> +   {135000, 1280, 1024, 75, 10, 54, 4},  /* 1280x1024@75Hz */ >> + >> +   {108000, 1600, 900, 60, 15, 81, 5},  /* 1600x900@60Hz */ >> +                       /* 1280x1024@60Hz */ >> +                       /* 1280x960@60Hz */ >> +                       /* 1152x864@75Hz */ >> + >> +   {106500, 1440, 900, 60, 19, 81, 4},  /* 1440x900@60Hz */ >> +   {88750, 1440, 900, 60, 16, 71, 5},  /* 1440x900@60Hz */ >> +   {83500, 1280, 800, 60, 17, 71, 5},  /* 1280x800@60Hz */ >> +   {71000, 1280, 800, 60, 20, 71, 5},  /* 1280x800@60Hz */ >> + >> +   {74250, 1280, 720, 60, 22, 49, 3},  /* 1280x720@60Hz */ >> +                       /* 1280x720@50Hz */ >> + >> +   {78750, 1024, 768, 75, 16, 63, 5},  /* 1024x768@75Hz */ >> +   {75000, 1024, 768, 70, 29, 87, 4},  /* 1024x768@70Hz */ >> +   {65000, 1024, 768, 60, 20, 39, 3},  /* 1024x768@60Hz */ >> + >> +   {51200, 1024, 600, 60, 25, 64, 5},  /* 1024x600@60Hz */ >> + >> +   {57284, 832, 624, 75, 24, 55, 4},  /* 832x624@75Hz */ >> +   {49500, 800, 600, 75, 40, 99, 5},  /* 800x600@75Hz */ >> +   {50000, 800, 600, 72, 44, 88, 4},  /* 800x600@72Hz */ >> +   {40000, 800, 600, 60, 30, 36, 3},  /* 800x600@60Hz */ >> +   {36000, 800, 600, 56, 50, 72, 4},  /* 800x600@56Hz */ >> +   {31500, 640, 480, 75, 40, 63, 5},  /* 640x480@75Hz */ >> +                       /* 640x480@73Hz */ >> + >> +   {30240, 640, 480, 67, 62, 75, 4},  /* 640x480@67Hz */ >> +   {27000, 720, 576, 50, 50, 54, 4},  /* 720x576@60Hz */ >> +   {25175, 640, 480, 60, 85, 107, 5},  /* 640x480@60Hz */ >> +   {25200, 640, 480, 60, 50, 63, 5},  /* 640x480@60Hz */ >> +                       /* 720x480@60Hz */ >> +}; >> + >> +/* >> + * lsdc_pixpll_setup - ioremap the device dependent PLL registers >> + * >> + * @this: point to the object where this function is called from >> + */ >> +static int lsdc_pixpll_setup(struct lsdc_pll * const this) >> +{ >> +   this->mmio = ioremap(this->reg_base, this->reg_size); >> + >> +   return 0; >> +} >> + >> +/* >> + * Find a set of pll parameters (to generate pixel clock) from a static >> + * local table, which avoid to compute the pll parameter eachtime a >> + * modeset is triggered. >> + * >> + * @this: point to the object which this function is called from >> + * @clock: the desired output pixel clock, the unit is kHz >> + * @pout: point to where the parameters to store if found >> + * >> + * Return true if hit, otherwise return false. >> + */ >> +static bool lsdc_pixpll_find(struct lsdc_pll * const this, >> +                unsigned int clock, >> +                struct lsdc_pll_parms * const pout) >> +{ >> +   unsigned int num = ARRAY_SIZE(pll_param_table); >> +   unsigned int i; >> + >> +   for (i = 0; i < num; i++) { >> +       if (clock != pll_param_table[i].clock) >> +           continue; >> + >> +       pout->div_ref = pll_param_table[i].div_ref; >> +       pout->loopc  = pll_param_table[i].loopc; >> +       pout->div_out = pll_param_table[i].div_out; >> + >> +       return true; >> +   } >> + >> +   drm_dbg(this->ddev, "pixel clock %u: miss\n", clock); >> + >> +   return false; >> +} >> + >> +/* >> + * Find a set of pll parameters which have minimal difference with >> the desired >> + * pixel clock frequency. It does that by computing all of the possible >> + * combination. Compute the diff and find the combination with >> minimal diff. >> + * >> + * clock_out = refclk / div_ref * loopc / div_out >> + * >> + * refclk is determined by the oscillator mounted board(Here is >> 100MHz in >> + * almost all case) >> + * >> + * @this: point to the object from which this function is called >> + * @clock_khz: the desired output pixel clock, the unit is kHz >> + * @pout: point to the out struct of lsdc_pll_parms >> + * >> + * Return true if a parameter is found, otherwise return false >> + */ >> +static bool lsdc_pixpll_compute(struct lsdc_pll * const this, >> +               unsigned int clock_khz, >> +               struct lsdc_pll_parms *pout) >> +{ >> +   unsigned int refclk = this->ref_clock; >> +   const unsigned int tolerance = 1000; >> +   unsigned int min = tolerance; >> +   unsigned int div_out, loopc, div_ref; >> +   unsigned int computed; >> + >> +   if (lsdc_pixpll_find(this, clock_khz, pout)) >> +       return true; >> + >> +   for (div_out = 6; div_out < 64; div_out++) { >> +       for (div_ref = 3; div_ref < 6; div_ref++) { >> +           for (loopc = 6; loopc < 161; loopc++) { >> +               int diff; >> + >> +               if (loopc < 12 * div_ref) >> +                   continue; >> +               if (loopc > 32 * div_ref) >> +                   continue; >> + >> +               computed = refclk * loopc / div_ref / div_out; >> + >> +               if (clock_khz > computed) >> +                   diff = clock_khz - computed; >> +               else if (clock_khz < computed) >> +                   diff = computed - clock_khz; >> + >> +               if (diff < min) { >> +                   min = diff; >> +                   pout->div_ref = div_ref; >> +                   pout->div_out = div_out; >> +                   pout->loopc = loopc; >> + >> +                   if (diff == 0) >> +                       return true; >> +               } >> +           } >> +       } >> +   } >> + >> +   return min < tolerance; >> +} >> + >> +/* >> + * Update the pll parameters to hardware, target to the pixpll in >> ls7a1000 >> + * >> + * @this: point to the object from which this function is called >> + * @pin: point to the struct of lsdc_pll_parms passed in >> + * >> + * return 0 if successful. >> + */ >> +static int ls7a1000_pixpll_param_update(struct lsdc_pll * const this, >> +                   struct lsdc_pll_parms const *pin) >> +{ >> +   void __iomem *reg = this->mmio; >> +   unsigned int counter = 0; >> +   bool locked; >> +   u32 val; >> + >> +   /* Bypass the software configured PLL, using refclk directly */ >> +   val = readl(reg + 0x4); >> +   val &= ~(1 << 8); >> +   writel(val, reg + 0x4); >> + >> +   /* Powerdown the PLL */ >> +   val = readl(reg + 0x4); >> +   val |= (1 << 13); >> +   writel(val, reg + 0x4); >> + >> +   /* Clear the pll parameters */ >> +   val = readl(reg + 0x4); >> +   val &= ~(1 << 11); >> +   writel(val, reg + 0x4); >> + >> +   /* clear old value & config new value */ >> +   val = readl(reg + 0x04); >> +   val &= ~0x7F; >> +   val |= pin->div_ref;       /* div_ref */ >> +   writel(val, reg + 0x4); >> + >> +   val = readl(reg); >> +   val &= ~0x7f; >> +   val |= pin->div_out;       /* div_out */ >> + >> +   val &= ~(0x1ff << 21); >> +   val |= pin->loopc << 21;   /* loopc */ >> +   writel(val, reg); >> + >> +   /* Set the pll the parameters */ >> +   val = readl(reg + 0x4); >> +   val |= (1 << 11); >> +   writel(val, reg + 0x4); >> + >> +   /* Powerup the PLL */ >> +   val = readl(reg + 0x4); >> +   val &= ~(1 << 13); >> +   writel(val, reg + 0x4); >> + >> +   /* Wait the PLL lock */ >> +   do { >> +       val = readl(reg + 0x4); >> +       locked = val & 0x80; >> +       counter++; >> +   } while (!locked && (counter < 2000)); >> + >> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >> + >> +   /* Switch to the software configured pll */ >> +   val = readl(reg + 0x4); >> +   val |= (1UL << 8); >> +   writel(val, reg + 0x4); >> + >> +   return 0; >> +} >> + >> +/* >> + * Update the pll parameters to hardware, target to the pixpll in >> ls2k1000 >> + * >> + * @this: point to the object from which this function is called >> + * @pin: point to the struct of lsdc_pll_parms passed in >> + * >> + * return 0 if successful. >> + */ >> +static int ls2k1000_pixpll_param_update(struct lsdc_pll * const this, >> +                   struct lsdc_pll_parms const *pin) >> +{ >> +   void __iomem *reg = this->mmio; >> +   unsigned int counter = 0; >> +   bool locked = false; >> +   u32 val; >> + >> +   val = readl(reg); >> +   /* Bypass the software configured PLL, using refclk directly */ >> +   val &= ~(1 << 0); >> +   writel(val, reg); >> + >> +   /* Powerdown the PLL */ >> +   val |= (1 << 19); >> +   writel(val, reg); >> + >> +   /* Allow the software configuration */ >> +   val &= ~(1 << 2); >> +   writel(val, reg); >> + >> +   /* allow L1 PLL lock */ >> +   val = (1L << 7) | (3L << 10); >> +   writel(val, reg); >> + >> +   /* clear div_ref bit field */ >> +   val &= ~(0x3f << 26); >> +   /* set div_ref bit field */ >> +   val |= pin->div_ref << 26; >> +   writel(val, reg); >> + >> +   val = readl(reg + 4); >> +   /* clear loopc bit field */ >> +   val &= ~0x0fff; >> +   /* set loopc bit field */ >> +   val |= pin->loopc; >> +   writel(val, reg + 4); >> + >> +   /* set div_out */ >> +   writel(pin->div_out, reg + 8); >> + >> +   val = readl(reg); >> +   /* use this parms configured just now */ >> +   val |= (1 << 2); >> +   /* powerup the PLL */ >> +   val &= ~(1 << 19); >> +   writel(val, reg); >> + >> +   /* wait pll setup and locked */ >> +   do { >> +       val = readl(reg); >> +       locked = val & 0x10000; >> +       counter++; >> +   } while (!locked && (counter < 2000)); >> + >> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >> + >> +   /* Switch to software configured PLL instead of refclk */ >> +   val |= 1; >> +   writel(val, reg); >> + >> +   return 0; >> +} >> + >> +/* >> + * Update the pll parameters to hardware, target to the pixpll in >> ls2k0500 >> + * >> + * @this: point to the object which calling this function >> + * @param: pointer to where the parameters passed in >> + * >> + * return 0 if successful. >> + */ >> +static int ls2k0500_pixpll_param_update(struct lsdc_pll * const this, >> +                   struct lsdc_pll_parms const *param) >> +{ >> +   void __iomem *reg = this->mmio; >> +   unsigned int counter = 0; >> +   bool locked = false; >> +   u32 val; >> + >> +   /* Bypass the software configured PLL, using refclk directly */ >> +   val = readl(reg); >> +   val &= ~(1 << 0); >> +   writel(val, reg); >> + >> +   /* Powerdown the PLL */ >> +   val = readl(reg); >> +   val |= (1 << 5); >> +   writel(val, reg); >> + >> +   /* Allow the software configuration */ >> +   val |= (1 << 3); >> +   writel(val, reg); >> + >> +   /* Update the pll params */ >> +   val = (param->div_out << 24) | >> +         (param->loopc << 16) | >> +         (param->div_ref << 8); >> + >> +   writel(val, reg); >> + >> +   /* Powerup the PLL */ >> +   val = readl(reg); >> +   val &= ~(1 << 5); >> +   writel(val, reg); >> + >> +   /* wait pll setup and locked */ >> +   do { >> +       val = readl(reg); >> +       locked = val & 0x80; >> +       counter++; >> +   } while (!locked && (counter < 10000)); >> + >> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >> + >> +   /* Switch to software configured PLL instead of refclk */ >> +   writel((val | 1), reg); >> + >> +   return 0; >> +} >> + >> +static unsigned int lsdc_get_clock_rate(struct lsdc_pll * const this, >> +                   struct lsdc_pll_parms *pout) >> +{ >> +   struct drm_device *ddev = this->ddev; >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   const struct lsdc_desc * const descp = ldev->descp; >> +   unsigned int out; >> +   union lsdc_pixpll_bitmap parms; >> + >> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == CHIP_LS7A2000) { >> +       struct ls7a1000_pixpll_bitmap *obj = &parms.ls7a1000; >> + >> +       parms.dword[0] = readl(this->mmio); >> +       parms.dword[1] = readl(this->mmio + 4); >> +       out = this->ref_clock / obj->div_ref * obj->loopc / >> obj->div_out; >> +       if (pout) { >> +           pout->div_ref = obj->div_ref; >> +           pout->loopc = obj->loopc; >> +           pout->div_out = obj->div_out; >> +       } >> +   } else if (descp->chip == CHIP_LS2K1000) { >> +       struct ls2k1000_pixpll_bitmap *obj = &parms.ls2k1000; >> + >> +       parms.dword[0] = readl(this->mmio); >> +       parms.dword[1] = readl(this->mmio + 4); >> +       parms.dword[2] = readl(this->mmio + 8); >> +       parms.dword[3] = readl(this->mmio + 12); >> +       out = this->ref_clock / obj->div_ref * obj->loopc / >> obj->div_out; >> +       if (pout) { >> +           pout->div_ref = obj->div_ref; >> +           pout->loopc = obj->loopc; >> +           pout->div_out = obj->div_out; >> +       } >> +   } else if (descp->chip == CHIP_LS2K0500) { >> +       struct ls2k0500_pixpll_bitmap *obj = &parms.ls2k0500; >> + >> +       parms.dword[0] = readl(this->mmio); >> +       out = this->ref_clock / obj->div_ref * obj->loopc / >> obj->div_out; >> +       if (pout) { >> +           pout->div_ref = obj->div_ref; >> +           pout->loopc = obj->loopc; >> +           pout->div_out = obj->div_out; >> +       } >> +   } else { >> +       drm_err(ddev, "unknown chip, the driver need update\n"); >> +       return 0; >> +   } >> + >> +   return out; >> +} >> + >> +static const struct lsdc_pixpll_funcs ls7a1000_pixpll_funcs = { >> +   .setup = lsdc_pixpll_setup, >> +   .compute = lsdc_pixpll_compute, >> +   .update = ls7a1000_pixpll_param_update, >> +   .get_clock_rate = lsdc_get_clock_rate, >> +}; >> + >> +static const struct lsdc_pixpll_funcs ls2k1000_pixpll_funcs = { >> +   .setup = lsdc_pixpll_setup, >> +   .compute = lsdc_pixpll_compute, >> +   .update = ls2k1000_pixpll_param_update, >> +   .get_clock_rate = lsdc_get_clock_rate, >> +}; >> + >> +static const struct lsdc_pixpll_funcs ls2k0500_pixpll_funcs = { >> +   .setup = lsdc_pixpll_setup, >> +   .compute = lsdc_pixpll_compute, >> +   .update = ls2k0500_pixpll_param_update, >> +   .get_clock_rate = lsdc_get_clock_rate, >> +}; >> + >> +int lsdc_pixpll_init(struct lsdc_pll * const this, >> +            struct drm_device *ddev, >> +            unsigned int index) >> +{ >> +   struct lsdc_device *ldev = to_lsdc(ddev); >> +   const struct lsdc_desc *descp = ldev->descp; >> + >> +   this->ddev = ddev; >> +   this->index = index; >> +   this->ref_clock = LSDC_PLL_REF_CLK; >> + >> +   /* LS7A1000 and LS7A2000's pixpll setting registers is same */ >> +   if (descp->chip == CHIP_LS7A2000 || descp->chip == CHIP_LS7A1000) { >> +       if (index == 0) >> +           this->reg_base = LS7A1000_CFG_REG_BASE + >> LS7A1000_PIX_PLL0_REG; >> +       else if (index == 1) >> +           this->reg_base = LS7A1000_CFG_REG_BASE + >> LS7A1000_PIX_PLL1_REG; >> +       this->reg_size = 8; >> +       this->funcs = &ls7a1000_pixpll_funcs; >> +   } else if (descp->chip == CHIP_LS2K1000) { >> +       if (index == 0) >> +           this->reg_base = LS2K1000_CFG_REG_BASE + >> LS2K1000_PIX_PLL0_REG; >> +       else if (index == 1) >> +           this->reg_base = LS2K1000_CFG_REG_BASE + >> LS2K1000_PIX_PLL1_REG; >> + >> +       this->reg_size = 16; >> +       this->funcs = &ls2k1000_pixpll_funcs; >> +   } else if (descp->chip == CHIP_LS2K0500) { >> +       if (index == 0) >> +           this->reg_base = LS2K0500_CFG_REG_BASE + >> LS2K0500_PIX_PLL0_REG; >> +       else if (index == 1) >> +           this->reg_base = LS2K0500_CFG_REG_BASE + >> LS2K0500_PIX_PLL1_REG; >> + >> +       this->reg_size = 4; >> +       this->funcs = &ls2k0500_pixpll_funcs; >> +   } else { >> +       drm_err(this->ddev, "unknown chip, the driver need update\n"); >> +       return -ENOENT; >> +   } >> + >> +   return this->funcs->setup(this); >> +} >> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.h >> b/drivers/gpu/drm/lsdc/lsdc_pll.h >> new file mode 100644 >> index 000000000000..538739f461f2 >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.h >> @@ -0,0 +1,78 @@ >> +/* SPDX-License-Identifier: GPL-2.0 */ >> + >> +#ifndef __LSDC_PLL_H__ >> +#define __LSDC_PLL_H__ >> + >> +#include <drm/drm_device.h> >> + >> +/* >> + * Loongson Pixel PLL hardware structure >> + * >> + * refclk: reference frequency, 100 MHz from external oscillator >> + * outclk: output frequency desired. >> + * >> + * >> + *              L1      Fref                     Fvco    L2 >> + * refclk  +-----------+     +------------------+ +---------+  >> outclk >> + * ---+---> | Prescaler | ---> | Clock Multiplier | ---> | divider | >> --------> >> + *   |    +-----------+     +------------------+ +---------+    ^ >> + *   |          ^                     ^ ^         | >> + *   |          |                     | |         | >> + *   |          |                     | |         | >> + *   |       div_ref                loopc div_out      | >> + * | | >> + *   +--- sel_out (bypass above software configurable clock if set) >> ----+ >> + * >> + * sel_out: PLL clock output selector (for debug purpose only). >> + * >> + * If sel_out == 1, it will take refclk as output directly, >> + * the L1 Prescaler and the out divider will be bypassed. >> + * >> + * If sel_out == 0, then outclk = refclk / div_ref * loopc / div_out; >> + * >> + * PLL working requirements: >> + * >> + * 1) 20 MHz <= refclk / div_ref <= 40Mhz >> + * 2) 1.2 GHz <= refclk /div_out * loopc <= 3.2 Ghz >> + */ >> + >> +struct lsdc_pll_parms { >> +   unsigned int div_ref; >> +   unsigned int loopc; >> +   unsigned int div_out; >> +}; >> + >> +struct lsdc_pll; >> + >> +struct lsdc_pixpll_funcs { >> +   int (*setup)(struct lsdc_pll * const this); >> +   bool (*compute)(struct lsdc_pll * const this, >> +           unsigned int clock, >> +           struct lsdc_pll_parms *pout); >> +   int (*update)(struct lsdc_pll * const this, >> +             struct lsdc_pll_parms const *pin); >> +   unsigned int (*get_clock_rate)(struct lsdc_pll * const this, >> +                      struct lsdc_pll_parms *pout); >> +}; >> + >> +struct lsdc_pll { >> +   const struct lsdc_pixpll_funcs *funcs; >> +   struct drm_device *ddev; >> +   void __iomem *mmio; >> + >> +   /* PLL register offset */ >> +   u32 reg_base; >> +   /* PLL register size in bytes */ >> +   u32 reg_size; >> + >> +   /* 100000kHz, fixed on all board found */ >> +   unsigned int ref_clock; >> + >> +   unsigned int index; >> +}; >> + >> +int lsdc_pixpll_init(struct lsdc_pll * const this, >> +            struct drm_device *ddev, >> +            unsigned int index); >> + >> +#endif >> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.c >> b/drivers/gpu/drm/lsdc/lsdc_regs.c >> new file mode 100644 >> index 000000000000..c7950c43968f >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.c >> @@ -0,0 +1,29 @@ >> +// SPDX-License-Identifier: GPL-2.0 >> + >> +#include "lsdc_drv.h" >> +#include "lsdc_regs.h" >> + >> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset) >> +{ >> +   unsigned long flags; >> +   u32 ret; >> + >> +   spin_lock_irqsave(&ldev->reglock, flags); >> + >> +   ret = readl(ldev->reg_base + offset); >> + >> +   spin_unlock_irqrestore(&ldev->reglock, flags); >> + >> +   return ret; >> +} >> + >> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 val) >> +{ >> +   unsigned long flags; >> + >> +   spin_lock_irqsave(&ldev->reglock, flags); >> + >> +   writel(val, ldev->reg_base + offset); >> + >> +   spin_unlock_irqrestore(&ldev->reglock, flags); >> +} >> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.h >> b/drivers/gpu/drm/lsdc/lsdc_regs.h >> new file mode 100644 >> index 000000000000..828956633137 >> --- /dev/null >> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.h >> @@ -0,0 +1,296 @@ >> +/* SPDX-License-Identifier: GPL-2.0 */ >> + >> +#ifndef __LSDC_REGS_H__ >> +#define __LSDC_REGS_H__ >> + >> +#include <linux/bitops.h> >> +#include <linux/types.h> >> + >> +/* >> + * PIXEL PLL Reference clock >> + */ >> +#define LSDC_PLL_REF_CLK               100000          /* kHz */ >> + >> +/* >> + * Those PLL registers are not located at DC reg bar space, >> + * there are relative to LSXXXXX_CFG_REG_BASE. >> + * XXXXX = 7A1000, 2K1000, 2K0500 >> + */ >> + >> +/* LS2K1000 */ >> +#define LS2K1000_PIX_PLL0_REG          0x04B0 >> +#define LS2K1000_PIX_PLL1_REG          0x04C0 >> +#define LS2K1000_CFG_REG_BASE          0x1fe10000 >> + >> +/* LS7A1000 and LS2K2000 */ >> +#define LS7A1000_PIX_PLL0_REG          0x04B0 >> +#define LS7A1000_PIX_PLL1_REG          0x04C0 >> +#define LS7A1000_CFG_REG_BASE          0x10010000 >> + >> +/* LS2K0500 */ >> +#define LS2K0500_PIX_PLL0_REG          0x0418 >> +#define LS2K0500_PIX_PLL1_REG          0x0420 >> +#define LS2K0500_CFG_REG_BASE          0x1fe10000 >> + >> +/* >> + * CRTC CFG REG >> + */ >> +#define CFG_PIX_FMT_MASK               GENMASK(2, 0) >> + >> +enum lsdc_pixel_format { >> +   LSDC_PF_NONE = 0, >> +   LSDC_PF_ARGB4444 = 1, /* ARGB A:4 bits R/G/B: 4 bits each [16 >> bits] */ >> +   LSDC_PF_ARGB1555 = 2, /* ARGB A:1 bit RGB:15 bits [16 bits] */ >> +   LSDC_PF_RGB565 = 3,   /* RGB [16 bits] */ >> +   LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */ >> +   LSDC_PF_RGBA8888 = 5, /* ARGB [32 bits] */ >> +}; >> + >> +/* >> + * Each crtc has two set fb address registers usable, >> CFG_FB_IN_USING of >> + * LSDC_CRTCx_CFG_REG specify which fb address register is currently >> + * in using by the CRTC. CFG_PAGE_FLIP of LSDC_CRTCx_CFG_REG is used to >> + * trigger the switch which will be finished at the very vblank. If you >> + * want it switch to another again, you must set CFG_PAGE_FLIP again. >> + */ >> +#define CFG_PAGE_FLIP                  BIT(7) >> +#define CFG_OUTPUT_EN                  BIT(8) >> +/* CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware >> logic */ >> +#define CFG_PANEL_SWITCH               BIT(9) >> +/* Indicate witch fb addr reg is in using, currently */ >> +#define CFG_FB_IN_USING                BIT(11) >> +#define CFG_GAMMA_EN                   BIT(12) >> + >> +/* CRTC get soft reset if voltage level change from 1 -> 0 */ >> +#define CFG_RESET_N                    BIT(20) >> + >> +#define CFG_HSYNC_EN                   BIT(30) >> +#define CFG_HSYNC_INV                  BIT(31) >> +#define CFG_VSYNC_EN                   BIT(30) >> +#define CFG_VSYNC_INV                  BIT(31) >> + >> +/* THE DMA step of the DC in LS7A2000 is configurable */ >> +#define LSDC_DMA_STEP_MASK             GENMASK(17, 16) >> +enum lsdc_dma_steps_supported { >> +   LSDC_DMA_STEP_256_BYTES = 0, >> +   LSDC_DMA_STEP_128_BYTES = 1, >> +   LSDC_DMA_STEP_64_BYTES = 2, >> +   LSDC_DMA_STEP_32_BYTES = 3, >> +}; >> + >> +/******** CRTC0 & DVO0 ********/ >> +#define LSDC_CRTC0_CFG_REG             0x1240 >> +#define LSDC_CRTC0_FB0_LO_ADDR_REG     0x1260 >> +#define LSDC_CRTC0_FB0_HI_ADDR_REG     0x15A0 >> +#define LSDC_CRTC0_FB1_LO_ADDR_REG     0x1580 >> +#define LSDC_CRTC0_FB1_HI_ADDR_REG     0x15C0 >> +#define LSDC_CRTC0_STRIDE_REG          0x1280 >> +#define LSDC_CRTC0_FB_ORIGIN_REG       0x1300 >> +#define LSDC_CRTC0_HDISPLAY_REG        0x1400 >> +#define LSDC_CRTC0_HSYNC_REG           0x1420 >> +#define LSDC_CRTC0_VDISPLAY_REG        0x1480 >> +#define LSDC_CRTC0_VSYNC_REG           0x14A0 >> +#define LSDC_CRTC0_GAMMA_INDEX_REG     0x14E0 >> +#define LSDC_CRTC0_GAMMA_DATA_REG      0x1500 >> + >> +/******** CTRC1 & DVO1 ********/ >> +#define LSDC_CRTC1_CFG_REG             0x1250 >> +#define LSDC_CRTC1_FB0_LO_ADDR_REG     0x1270 >> +#define LSDC_CRTC1_FB0_HI_ADDR_REG     0x15B0 >> +#define LSDC_CRTC1_FB1_LO_ADDR_REG     0x1590 >> +#define LSDC_CRTC1_FB1_HI_ADDR_REG     0x15D0 >> +#define LSDC_CRTC1_STRIDE_REG          0x1290 >> +#define LSDC_CRTC1_FB_ORIGIN_REG       0x1310 >> +#define LSDC_CRTC1_HDISPLAY_REG        0x1410 >> +#define LSDC_CRTC1_HSYNC_REG           0x1430 >> +#define LSDC_CRTC1_VDISPLAY_REG        0x1490 >> +#define LSDC_CRTC1_VSYNC_REG           0x14B0 >> +#define LSDC_CRTC1_GAMMA_INDEX_REG     0x14F0 >> +#define LSDC_CRTC1_GAMMA_DATA_REG      0x1510 >> + >> +/* >> + * LS7A2000 has the hardware which record the scan position of the CRTC >> + * [31:16] : current X position, [15:0] : current Y position >> + */ >> +#define LSDC_CRTC0_SCAN_POS_REG        0x14C0 >> +#define LSDC_CRTC1_SCAN_POS_REG        0x14D0 >> + >> +/* >> + * LS7A2000 has the hardware which count the number of vblank generated >> + */ >> +#define LSDC_CRTC0_VSYNC_COUNTER_REG   0x1A00 >> +#define LSDC_CRTC1_VSYNC_COUNTER_REG   0x1A10 >> + >> +/* In all, LSDC_CRTC1_XXX_REG - LSDC_CRTC0_XXX_REG = 0x10 */ >> + >> +/* >> + * There is only one hardware cursor unit in ls7a1000, ls2k1000 and >> ls2k0500. >> + * well, ls7a2000 has two hardware cursor unit. >> + */ >> +#define CURSOR_FORMAT_MASK             GENMASK(1, 0) >> +enum lsdc_cursor_format { >> +   CURSOR_FORMAT_DISABLE = 0, >> +   CURSOR_FORMAT_MONOCHROME = 1, >> +   CURSOR_FORMAT_ARGB8888 = 2, >> +}; >> + >> +#define CURSOR_SIZE_64X64              BIT(2) >> +#define CURSOR_LOCATION                BIT(4) >> + >> +#define LSDC_CURSOR0_CFG_REG           0x1520 >> +#define LSDC_CURSOR0_ADDR_LO_REG       0x1530 >> +#define LSDC_CURSOR0_ADDR_HI_REG       0x15e0 >> +#define LSDC_CURSOR0_POSITION_REG      0x1540 >> +#define LSDC_CURSOR0_BG_COLOR_REG      0x1550 /* background color */ >> +#define LSDC_CURSOR0_FG_COLOR_REG      0x1560 /* foreground color */ >> + >> +#define LSDC_CURSOR1_CFG_REG           0x1670 >> +#define LSDC_CURSOR1_ADDR_LO_REG       0x1680 >> +#define LSDC_CURSOR1_ADDR_HI_REG       0x16e0 >> +#define LSDC_CURSOR1_POSITION_REG      0x1690 /* [31:16] Y, [15:0] >> X */ >> +#define LSDC_CURSOR1_BG_COLOR_REG      0x16A0 /* background color */ >> +#define LSDC_CURSOR1_FG_COLOR_REG      0x16B0 /* foreground color */ >> + >> +/* >> + * DC Interrupt Control Register, 32bit, Address Offset: 1570 >> + * >> + * Bits 15:0 inidicate the interrupt status >> + * Bits 31:16 control enable interrupts corresponding to bit 15:0 or >> not >> + * Write 1 to enable, write 0 to disable >> + * >> + * RF: Read Finished >> + * IDBU: Internal Data Buffer Underflow >> + * IDBFU: Internal Data Buffer Fatal Underflow >> + * CBRF: Cursor Buffer Read Finished Flag, no use. >> + * >> + * >> +-------+--------------------------+-------+--------+--------+-------+ >> + * | 31:27 |        26:16           | 15:11 |  10  |  9 |  8  | >> + * >> +-------+--------------------------+-------+--------+--------+-------+ >> + * | N/A | Interrupt Enable Control | N/A | IDBFU0 | IDBFU1 | >> IDBU0 | >> + * >> +-------+--------------------------+-------+--------+--------+-------+ >> + * >> + * +-------+-----+-----+------+--------+--------+--------+--------+ >> + * |  7  | 6 | 5 | 4  |  3   |  2   |  1   | 0   | >> + * +-------+-----+-----+------+--------+--------+--------+--------+ >> + * | IDBU1 | RF0 | RF1 | CRRF | HSYNC0 | VSYNC0 | HSYNC1 | VSYNC1 | >> + * +-------+-----+-----+------+--------+--------+--------+--------+ >> + */ >> + >> +#define LSDC_INT_REG                   0x1570 >> + >> +#define INT_CRTC0_VSYNC                BIT(2) >> +#define INT_CRTC0_HSYNC                BIT(3) >> +#define INT_CRTC0_RF                   BIT(6) >> +#define INT_CRTC0_IDBU                 BIT(8) >> +#define INT_CRTC0_IDBFU                BIT(10) >> + >> +#define INT_CRTC1_VSYNC                BIT(0) >> +#define INT_CRTC1_HSYNC                BIT(1) >> +#define INT_CRTC1_RF                   BIT(5) >> +#define INT_CRTC1_IDBU                 BIT(7) >> +#define INT_CRTC1_IDBFU                BIT(9) >> + >> +#define INT_CRTC0_VS_EN                BIT(18) >> +#define INT_CRTC0_HS_EN                BIT(19) >> +#define INT_CRTC0_RF_EN                BIT(22) >> +#define INT_CRTC0_IDBU_EN              BIT(24) >> +#define INT_CRTC0_IDBFU_EN             BIT(26) >> + >> +#define INT_CRTC1_VS_EN                BIT(16) >> +#define INT_CRTC1_HS_EN                BIT(17) >> +#define INT_CRTC1_RF_EN                BIT(21) >> +#define INT_CRTC1_IDBU_EN              BIT(23) >> +#define INT_CRTC1_IDBFU_EN             BIT(25) >> + >> +#define INT_STATUS_MASK                GENMASK(15, 0) >> + >> +/* >> + * LS7A1000/LS7A2000 have 4 gpios which are used to emulated I2C. >> + * They are under control of the LS7A_DC_GPIO_DAT_REG and >> LS7A_DC_GPIO_DIR_REG >> + * register, Those GPIOs has no relationship whth the GPIO hardware >> on the >> + * bridge chip itself. Those offsets are relative to DC register >> base address >> + * >> + * LS2k1000 and LS2K0500 don't have those registers, they use >> hardware i2c >> + * or general GPIO emulated i2c from linux i2c subsystem. >> + * >> + * GPIO data register, address offset: 0x1650 >> + *  +---------------+-----------+-----------+ >> + *  | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | >> + *  +---------------+-----------+-----------+ >> + *  |              |   DVO1  |   DVO0  | >> + *  +     N/A     +-----------+-----------+ >> + *  |              | SCL | SDA | SCL | SDA | >> + *  +---------------+-----------+-----------+ >> + */ >> +#define LS7A_DC_GPIO_DAT_REG           0x1650 >> + >> +/* >> + * GPIO Input/Output direction control register, address offset: >> 0x1660 >> + */ >> +#define LS7A_DC_GPIO_DIR_REG           0x1660 >> + >> +/* >> + * LS7A2000 has two built-in HDMI Encoder and one VGA encoder >> + */ >> + >> +/* >> + * Number of continuous packets may be present >> + * in HDMI hblank and vblank zone, should >= 48 >> + */ >> +#define LSDC_HDMI0_ZONE_REG            0x1700 >> +#define LSDC_HDMI1_ZONE_REG            0x1710 >> + >> +#define HDMI_INTERFACE_EN              BIT(0) >> +#define HDMI_PACKET_EN                 BIT(1) >> +#define HDMI_AUDIO_EN                  BIT(2) >> +#define HDMI_VIDEO_PREAMBLE_MASK       GENMASK(7, 4) >> +#define HDMI_HW_I2C_EN                 BIT(8) >> +#define HDMI_CTL_PERIOD_MODE           BIT(9) >> +#define LSDC_HDMI0_CTRL_REG            0x1720 >> +#define LSDC_HDMI1_CTRL_REG            0x1730 >> + >> +#define HDMI_PHY_EN                    BIT(0) >> +#define HDMI_PHY_RESET_N               BIT(1) >> +#define HDMI_PHY_TERM_L_EN             BIT(8) >> +#define HDMI_PHY_TERM_H_EN             BIT(9) >> +#define HDMI_PHY_TERM_DET_EN           BIT(10) >> +#define HDMI_PHY_TERM_STATUS           BIT(11) >> +#define LSDC_HDMI0_PHY_CTRL_REG        0x1800 >> +#define LSDC_HDMI1_PHY_CTRL_REG        0x1810 >> + >> +/* >> + * 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 | >> + * +--------------------------------------------------------+ >> + */ >> +#define LSDC_HDMI0_PHY_PLL_REG         0x1820 >> +#define LSDC_HDMI1_PHY_PLL_REG         0x1830 >> + >> +#define HDMI_PLL_ENABLE                BIT(0) >> +#define HDMI_PLL_LOCKED                BIT(16) >> +#define HDMI_PLL_BYPASS                BIT(17) >> + >> +#define HDMI_PLL_IDF_SHIFT             1 >> +#define HDMI_PLL_IDF_MASK              GENMASK(5, 1) >> +#define HDMI_PLL_LF_SHIFT              6 >> +#define HDMI_PLL_LF_MASK               GENMASK(12, 6) >> +#define HDMI_PLL_ODF_SHIFT             13 >> +#define HDMI_PLL_ODF_MASK              GENMASK(15, 13) >> + >> +/* LS7A2000 have hpd support */ >> +#define LSDC_HDMI_HPD_STATUS_REG       0x1BA0 >> +#define HDMI0_HPD_FLAG                 BIT(0) >> +#define HDMI1_HPD_FLAG                 BIT(1) >> + >> +#endif >
On 2023/2/13 12:24, Sui jingfeng wrote: > > On 2023/2/10 23:04, Thomas Zimmermann wrote: >> Hi >> > Hello, >> Am 05.02.23 um 17:17 schrieb suijingfeng: >>> From: suijingfeng <suijingfeng@loongson.cn> >>> >>> Loongson display controller IP has been integrated in both Loongson >>> North Bridge chipset(ls7a1000 and ls7a2000) and Loongson SoCs(ls2k1000 >>> and ls2k2000 etc), it even been included in Loongson BMC products. >>> >>> The display controller is a PCI device, it has two display pipe. >>> For the DC in LS7A1000 and LS2K1000 each way has a DVO output interface >>> which provide RGB888 signals, vertical & horizontal synchronisations, >>> and the pixel clock. Each CRTC is able to support 1920x1080@60Hz, >>> the maximum resolution is 2048x2048 according to the hardware spec. >>> >>> For the DC in LS7A2000, each display pipe is connected with a built-in >>> HDMI encoder which support 3840x2160@30Hz. the first display pipe is >>> also equipped with a transparent vga encoder which is parallel with >>> the hdmi encoder. The DC in LS7A2000 is more complete, besides above >>> feature, it has two hardware cursors, two hardware vblank counter and >>> two scanout position recoders. It also support hdmi audio and tiled >>> format etc. >>> >>>  v2: >>>   1) Use hpd status reg when polling for ls7a2000 >>>   2) Fix all warnings emerged when compile with W=1 >>> >>> Signed-off-by: suijingfeng <suijingfeng@loongson.cn> >>> Signed-off-by: suijingfeng <15330273260@189.cn> >>> --- >>>  drivers/gpu/drm/Kconfig            |  2 + >>>  drivers/gpu/drm/Makefile           |  1 + >>>  drivers/gpu/drm/lsdc/Kconfig       | 17 + >>>  drivers/gpu/drm/lsdc/Makefile      | 14 + >>>  drivers/gpu/drm/lsdc/lsdc_crtc.c   | 421 ++++++++++++++++++ >>>  drivers/gpu/drm/lsdc/lsdc_debugfs.c | 191 +++++++++ >>>  drivers/gpu/drm/lsdc/lsdc_drv.c    | 635 >>> ++++++++++++++++++++++++++++ >>>  drivers/gpu/drm/lsdc/lsdc_drv.h    | 274 ++++++++++++ >>>  drivers/gpu/drm/lsdc/lsdc_i2c.c    | 201 +++++++++ >>>  drivers/gpu/drm/lsdc/lsdc_irq.c    | 86 ++++ >>>  drivers/gpu/drm/lsdc/lsdc_output.c | 452 ++++++++++++++++++++ >>>  drivers/gpu/drm/lsdc/lsdc_plane.c  | 443 +++++++++++++++++++ >>>  drivers/gpu/drm/lsdc/lsdc_pll.c    | 569 +++++++++++++++++++++++++ >>>  drivers/gpu/drm/lsdc/lsdc_pll.h    | 78 ++++ >>>  drivers/gpu/drm/lsdc/lsdc_regs.c   | 29 ++ >>>  drivers/gpu/drm/lsdc/lsdc_regs.h   | 296 +++++++++++++ >>>  16 files changed, 3709 insertions(+) >>>  create mode 100644 drivers/gpu/drm/lsdc/Kconfig >>>  create mode 100644 drivers/gpu/drm/lsdc/Makefile >>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_crtc.c >>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_debugfs.c >>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.c >>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.h >>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_i2c.c >>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_irq.c >>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_output.c >>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_plane.c >>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.c >>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.h >>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.c >>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.h >>> >>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig >>> index dc0f94f02a82..3baecd48930b 100644 >>> --- a/drivers/gpu/drm/Kconfig >>> +++ b/drivers/gpu/drm/Kconfig >>> @@ -367,6 +367,8 @@ source "drivers/gpu/drm/solomon/Kconfig" >>>   source "drivers/gpu/drm/sprd/Kconfig" >>>  +source "drivers/gpu/drm/lsdc/Kconfig" >>> + >>>  config DRM_HYPERV >>>      tristate "DRM Support for Hyper-V synthetic video device" >>>      depends on DRM && PCI && MMU && HYPERV >>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile >>> index ab4460fcd63f..5a8e2fc8dd5d 100644 >>> --- a/drivers/gpu/drm/Makefile >>> +++ b/drivers/gpu/drm/Makefile >>> @@ -190,3 +190,4 @@ obj-y           += gud/ >>>  obj-$(CONFIG_DRM_HYPERV) += hyperv/ >>>  obj-y           += solomon/ >>>  obj-$(CONFIG_DRM_SPRD) += sprd/ >>> +obj-$(CONFIG_DRM_LSDC) += lsdc/ >>> diff --git a/drivers/gpu/drm/lsdc/Kconfig >>> b/drivers/gpu/drm/lsdc/Kconfig >>> new file mode 100644 >>> index 000000000000..8b9bb6996877 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/Kconfig >>> @@ -0,0 +1,17 @@ >>> +# SPDX-License-Identifier: GPL-2.0 >>> + >>> +config DRM_LSDC >>> +   tristate "DRM support for loongson display controller" >>> +   depends on DRM && PCI && (MACH_LOONGSON64 || LOONGARCH || MIPS) >> >> If possible, I'd add a single 'depends on' statement for each AND >> condition. Like this >> >>  depends on DRM >>  depends on PCI >>  depends on (MACH_LOONGSON64 || LOONGARCH || MIPS) >> >> Does it build on other architectures if you add COMPILE_TEST to that >> list of architectures? If so, it would simplify testing a lot. >> Hi, Should I add COMPILE_TEST to the list of architectures on next version? >>> +   select OF >> >> I don't think that it is allowed to select OF. Rather use >> >>  depends on OF >> >> I think that general rule is that it is not allowed to auto-select >> options that are selectable by users. That's the case for OF. >> >> > Ok, depends on OF is acceptable. > >>> +   select DRM_KMS_HELPER >>> +   select DRM_TTM >>> +   select DRM_TTM_HELPER >>> +   select DRM_VRAM_HELPER >>> +   default m >> >> No default, especially not 'm'. Everything should be off by default. >> >>> +   help >>> +     This is a KMS driver for loongson display controller in the >>> +     LS7A1000/LS7A2000 bridge chip and LS2K1000/LS2K0500 SoC. >>> +     If "M" is selected, the module will be called lsdc. >>> + >>> +     If in doubt, say "Y". >> >> Always say "N" by default. There are a few exceptions where "yes" is >> advised, but regular drivers are not one of them. >> >>> diff --git a/drivers/gpu/drm/lsdc/Makefile >>> b/drivers/gpu/drm/lsdc/Makefile >>> new file mode 100644 >>> index 000000000000..d4b901ec09cf >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/Makefile >>> @@ -0,0 +1,14 @@ >>> +# SPDX-License-Identifier: GPL-2.0 >>> + >>> +lsdc-y := \ >>> +   lsdc_drv.o \ >>> +   lsdc_crtc.o \ >>> +   lsdc_irq.o \ >>> +   lsdc_plane.o \ >>> +   lsdc_pll.o \ >>> +   lsdc_i2c.o \ >>> +   lsdc_output.o \ >>> +   lsdc_regs.o \ >>> +   lsdc_debugfs.o >> >> Please always sort such lists alphabetically. >> >>> + >>> +obj-$(CONFIG_DRM_LSDC) += lsdc.o >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_crtc.c >>> b/drivers/gpu/drm/lsdc/lsdc_crtc.c >>> new file mode 100644 >>> index 000000000000..e23e6085f418 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_crtc.c >>> @@ -0,0 +1,421 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include <drm/drm_vblank.h> >>> +#include <drm/drm_atomic.h> >>> +#include <drm/drm_atomic_helper.h> >> >> Same here, alphabetical sorting. >> >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> +#include "lsdc_pll.h" >> >> Sorting. >> > All of problem mentioned above will be solved at next version, thinks. >>> + >>> +static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc) >>> +{ >>> +   struct lsdc_device *ldev = to_lsdc(crtc->dev); >>> +   unsigned int index = drm_crtc_index(crtc); >>> +   u32 val; >>> + >>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>> + >>> +   if (index == 0) >>> +       val |= INT_CRTC0_VS_EN; >>> +   else if (index == 1) >>> +       val |= INT_CRTC1_VS_EN; >>> + >>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >>> + >>> +   return 0; >>> +} >>> + >>> +static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc) >>> +{ >>> +   struct lsdc_device *ldev = to_lsdc(crtc->dev); >>> +   unsigned int index = drm_crtc_index(crtc); >>> +   u32 val; >>> + >>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>> + >>> +   if (index == 0) >>> +       val &= ~INT_CRTC0_VS_EN; >>> +   else if (index == 1) >>> +       val &= ~INT_CRTC1_VS_EN; >>> + >>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >>> +} >>> + >>> +static void lsdc_crtc_reset(struct drm_crtc *crtc) >>> +{ >>> +   struct drm_device *ddev = crtc->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   unsigned int index = drm_crtc_index(crtc); >>> +   struct lsdc_crtc_state *priv_crtc_state; >>> +   u32 val = CFG_RESET_N | LSDC_PF_XRGB8888; >>> + >>> +   if (index == 0) >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>> +   else if (index == 1) >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>> + >>> +   if (crtc->state) { >>> +       priv_crtc_state = to_lsdc_crtc_state(crtc->state); >>> + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); >>> +       kfree(priv_crtc_state); >>> +   } >>> + >>> +   priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL); >>> +   if (!priv_crtc_state) >>> +       return; >>> + >>> +   __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); >>> + >>> +   drm_info(ddev, "CRTC-%u reset\n", index); >> >> No drm_info() here. You could use drm_dbg() if you really want to >> print something. I'd advise to simply remove that line. >> >>> +} >>> + >>> +static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, >>> +                      struct drm_crtc_state *state) >>> +{ >>> +   struct lsdc_crtc_state *priv_crtc_state = >>> to_lsdc_crtc_state(state); >>> + >>> + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); >>> + >>> +   kfree(priv_crtc_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 = kmalloc(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_get_vblank_counter(struct drm_crtc *crtc) >>> +{ >>> +   struct drm_device *ddev = crtc->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   const struct lsdc_desc *descp = ldev->descp; >>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>> +   unsigned int index = dispipe->index; >>> + >>> +   /* fallback to software emulated VBLANK counter */ >>> +   if (!descp->has_vblank_counter) >>> +       goto fallback; >>> + >>> +   if (index == 0) >>> +       return lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG); >>> + >>> +   if (index == 1) >>> +       return lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG); >>> + >>> +fallback: >>> +   return (u32)drm_crtc_vblank_count(crtc); >>> +} >>> + >>> +static const struct drm_crtc_funcs lsdc_crtc_funcs = { >> >> My advise for the design is to untangle the different cases into >> smaller helpers as much as possible. >> >> You have several chips with multiple pipelines. I would advise to >> provide a funcs array for each possible case. For example: >> >>  u32 lsdc_crtc0_get_vblank_counter(struct drm_crtc *crtc) >>  { >>     // return LSDC_CRTC0_VSYNC_COUNTER_REG >>  } >> >>  u32 lsdc_crtc1_get_vblank_counter(struct drm_crtc *crtc) >>  { >>     // return LSDC_CRTC1_VSYNC_COUNTER_REG >>  } >> >> and use a different funcs instance for each pipeline >> >>  struct lsdc_crtc0_funcs_ls2k2000 = { >>     .get_vblank_counter = lsdc_crtc0_get_vblank_counter, >>  }; >> >>  struct lsdc_crtc1_funcs_ls2k2000 = { >>     .get_vblank_counter = lsdc_crtc1_get_vblank_counter, >>  }; >> >> For hardware without vblank counter, use another instance >> >>  struct lsdc_crtc0_funcs_ls2k0500 = { >>     // no >>  }; >> >> At first, this looks like a lot of code. But in the long term, this >> pays of, because it's so much more maintainable. If you have >> functions that work with any CRTC, you can still share the >> implementation. > I am agree. >> >> All this goes for other components as well. >> >> You may want to study mgag200. It supports ~10 chips. Handling this >> in single functions was unmaintainable. So we split everything into >> smaller helpers that each handle a single case. And then we build a >> dedicated modesetting pipeline for each chip from various helpers. >> Common code is shared among the chips. >> > I guess you may want divide and conquer them, mgag200 driver's > structure is basic same with ast. > > they have only one display pipe and they are both standard pci > device. I'm already absorb many merits from mgag200 and ast. > > And i buy two mga video card, sadly they can boot on loongson > platform, x86emu will let them pass, loongson uefi/pmon firmware > > need adapting. I can only study it by reading the code. > Sorry, there have typos here: i buy two MGA video cards, sadly they can't boot on loongson platform, x86emu in firmware will block them from working. Study it on x86 platform is still possible i think. I will focus on its decision seriously, and improve device driver design skill. But i found it is so different from the display controller in ls7a1000/ls7a2000, the display controller in ls7a1000/ls7a2000 seem come from embedded world and walking toward pc, and it don't support vga address hole and don't compatible with IBM vga standard. mgag200 seems come from standard old pc vga card. >> Here's where we select the chip: >> >> >> https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_drv.c#L227 >> >> >> There's a source file for each chip and the common source code is in >> > I will follow this method at next version, but our hardware and bios > engineers is ugly, they often made the chip indistinguishable. > > ls7a1000 and ls2k1000 have the same pci device id, ls7a2000 and > ls2k2000 have the same pci device id, > > even the revision id is same. I have no choice but to rely on DT or > kernel cmd passing parameters. > > many reviewers and maintainer seem not happy about this. > >> https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_mode.c >> >> >> >>> +   .reset = lsdc_crtc_reset, >>> +   .destroy = drm_crtc_cleanup, >>> +   .set_config = drm_atomic_helper_set_config, >>> +   .page_flip = drm_atomic_helper_page_flip, >>> +   .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, >>> +   .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, >>> +   .get_vblank_counter = lsdc_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, >>> +}; >>> + >>> +static enum drm_mode_status >>> +lsdc_crtc_mode_valid(struct drm_crtc *crtc, >>> +            const struct drm_display_mode *mode) >>> +{ >>> +   struct drm_device *ddev = crtc->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   if (mode->hdisplay > descp->max_width) >>> +       return MODE_BAD_HVALUE; >>> +   if (mode->vdisplay > descp->max_height) >>> +       return MODE_BAD_VVALUE; >>> + >>> +   if (mode->clock > descp->max_pixel_clk) { >>> +       drm_dbg(ddev, "mode %dx%d, pixel clock=%d is too high\n", >>> +           mode->hdisplay, mode->vdisplay, mode->clock); >>> +       return MODE_CLOCK_HIGH; >>> +   } >>> + >>> +   if ((mode->hdisplay * 4) % descp->pitch_align) { >>> +       drm_dbg(ddev, "stride is require to not %u bytes aligned\n", >>> +           descp->pitch_align); >>> +       return MODE_BAD; >>> +   } >>> + >>> +   return MODE_OK; >>> +} >>> + >>> +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc, >>> +                   struct drm_crtc_state *state) >>> +{ >>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>> +   struct lsdc_pll *pixpll = &dispipe->pixpll; >>> +   const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; >>> +   struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); >>> +   bool ret; >>> + >>> +   ret = pfuncs->compute(pixpll, state->mode.clock, >>> &priv_state->pparms); >>> +   if (ret) >>> +       return 0; >>> + >>> +   drm_warn(crtc->dev, "failed find PLL parameters for %u\n", >>> state->mode.clock); >>> + >>> +   return -EINVAL; >>> +} >>> + >>> +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; /* no mode checks if CRTC is being disabled */ >>> + >>> +   return lsdc_pixpll_atomic_check(crtc, crtc_state); >>> +} >>> + >>> +static void lsdc_update_pixclk(struct lsdc_display_pipe *dispipe, >>> +                  struct lsdc_crtc_state *priv_state) >>> +{ >>> +   struct lsdc_pll *pixpll = &dispipe->pixpll; >>> +   const struct lsdc_pixpll_funcs *clkfunc = pixpll->funcs; >>> + >>> +   clkfunc->update(pixpll, &priv_state->pparms); >>> +} >>> + >>> +static void lsdc_crtc_helper_mode_set_nofb(struct drm_crtc *crtc) >>> +{ >>> +   struct drm_device *ddev = crtc->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   struct drm_display_mode *mode = &crtc->state->mode; >>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>> +   struct lsdc_crtc_state *priv_state = >>> to_lsdc_crtc_state(crtc->state); >>> +   unsigned int index = dispipe->index; >>> +   u32 h_sync, v_sync, h_val, v_val; >>> +   u32 val; >>> + >>> +   /* 26:16 total pixels, 10:0 visiable pixels, in horizontal */ >>> +   h_val = (mode->crtc_htotal << 16) | mode->crtc_hdisplay; >>> + >>> +   /* 26:16 total pixels, 10:0 visiable pixels, in vertical */ >>> +   v_val = (mode->crtc_vtotal << 16) | mode->crtc_vdisplay; >>> +   /* 26:16 hsync end, 10:0 hsync start, bit 30 is hsync enable */ >>> +   h_sync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start >>> | CFG_HSYNC_EN; >>> +   if (mode->flags & DRM_MODE_FLAG_NHSYNC) >>> +       h_sync |= CFG_HSYNC_INV; >>> + >>> +   /* 26:16 vsync end, 10:0 vsync start, bit 30 is vsync enable */ >>> +   v_sync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start >>> | CFG_VSYNC_EN; >>> +   if (mode->flags & DRM_MODE_FLAG_NVSYNC) >>> +       v_sync |= CFG_VSYNC_INV; >>> + >>> +   if (index == 0) { >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG, h_val); >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG, v_val); >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG, h_sync); >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG, v_sync); >>> + >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>> +       val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>> +   } else if (index == 1) { >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_HDISPLAY_REG, h_val); >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_VDISPLAY_REG, v_val); >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_HSYNC_REG, h_sync); >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_VSYNC_REG, v_sync); >>> + >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>> +       val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>> +   } >> >> Same here as with vblank: have one helper for each index and use >> different _funcs structures for each pipeline. >> > Well, I will revise lsdc driver with this idea in mind. >>> + >>> +   drm_dbg(ddev, "%s modeset: %ux%u\n", >>> +       crtc->name, mode->hdisplay, mode->vdisplay); >>> + >>> +   lsdc_update_pixclk(dispipe, priv_state); >>> +} >>> + >>> +static void lsdc_enable_display(struct lsdc_device *ldev, unsigned >>> int index) >>> +{ >>> +   u32 val; >>> + >>> +   if (index == 0) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>> +       val |= CFG_OUTPUT_EN; >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>> +       return; >>> +   } >>> + >>> +   if (index == 1) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>> +       val |= CFG_OUTPUT_EN; >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>> +       return; >>> +   } >>> +} >>> + >>> +static void lsdc_disable_display(struct lsdc_device *ldev, unsigned >>> int index) >>> +{ >>> +   u32 val; >>> + >>> +   if (index == 0) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>> +       val &= ~CFG_OUTPUT_EN; >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>> +       return; >>> +   } >>> + >>> +   if (index == 1) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>> +       val &= ~CFG_OUTPUT_EN; >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>> +       return; >>> +   } >>> +} >>> + >>> +static void lsdc_crtc_helper_atomic_enable(struct drm_crtc *crtc, >>> +                      struct drm_atomic_state *state) >>> +{ >>> +   struct drm_device *ddev = crtc->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> + >>> +   drm_crtc_vblank_on(crtc); >>> + >>> +   lsdc_enable_display(ldev, drm_crtc_index(crtc)); >>> + >>> +   drm_dbg(ddev, "%s: enabled\n", crtc->name); >>> +} >>> + >>> +static void lsdc_crtc_helper_atomic_disable(struct drm_crtc *crtc, >>> +                       struct drm_atomic_state *state) >>> +{ >>> +   struct drm_device *ddev = crtc->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> + >>> +   lsdc_disable_display(ldev, drm_crtc_index(crtc)); >>> + >>> +   drm_crtc_wait_one_vblank(crtc); >>> + >>> +   drm_crtc_vblank_off(crtc); >>> + >>> +   drm_dbg(ddev, "%s: disabled\n", crtc->name); >>> +} >>> + >>> +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 drm_device *ddev = crtc->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   unsigned int index = drm_crtc_index(crtc); >>> +   int line, vsw, vbp, vactive_start, vactive_end, vfp_end; >>> +   u32 val = 0; >>> + >>> +   vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; >>> +   vbp = mode->crtc_vtotal - mode->crtc_vsync_end; >>> + >>> +   vactive_start = vsw + vbp + 1; >>> + >>> +   vactive_end = vactive_start + mode->crtc_vdisplay; >>> + >>> +   /* last scan line before VSYNC */ >>> +   vfp_end = mode->crtc_vtotal; >>> + >>> +   if (stime) >>> +       *stime = ktime_get(); >>> + >>> +   if (index == 0) >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); >>> +   else if (index == 1) >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); >>> + >>> +   line = (val & 0xffff); >>> + >>> +   if (line < vactive_start) >>> +       line -= vactive_start; >>> +   else if (line > vactive_end) >>> +       line = line - vfp_end - vactive_start; >>> +   else >>> +       line -= vactive_start; >>> + >>> +   *vpos = line; >>> +   *hpos = val >> 16; >>> + >>> +   if (etime) >>> +       *etime = ktime_get(); >>> + >>> +   return true; >>> +} >>> + >>> +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { >>> +   .mode_valid = lsdc_crtc_mode_valid, >>> +   .mode_set_nofb = lsdc_crtc_helper_mode_set_nofb, >>> +   .atomic_enable = lsdc_crtc_helper_atomic_enable, >>> +   .atomic_disable = lsdc_crtc_helper_atomic_disable, >>> +   .atomic_check = lsdc_crtc_helper_atomic_check, >>> +   .atomic_flush = lsdc_crtc_atomic_flush, >>> +   .get_scanout_position = lsdc_crtc_get_scanout_position, >>> +}; >>> + >>> +int lsdc_crtc_init(struct drm_device *ddev, >>> +          struct drm_crtc *crtc, >>> +          unsigned int index, >>> +          struct drm_plane *primary, >>> +          struct drm_plane *cursor) >>> +{ >>> +   int ret; >>> + >>> +   ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor, >>> +                   &lsdc_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) >>> +       drm_warn(ddev, "set the gamma table size failed\n"); >>> + >>> +   drm_crtc_enable_color_mgmt(crtc, 0, false, 256); >> >> Maybe leave this out for now. You need to handle the property in your >> driver. I don't see code for that. >> >> Color management should probably go into a separate patch. >> > Ok, I'm agree to leave gamma support to a separate patch >>> + >>> +   return 0; >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>> b/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>> new file mode 100644 >>> index 000000000000..176ae4e11d1c >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>> @@ -0,0 +1,191 @@ >>> +// SPDX-License-Identifier: GPL-2.0+ >>> + >>> +#include <drm/drm_debugfs.h> >>> +#include <drm/drm_managed.h> >>> +#include <drm/drm_gem_vram_helper.h> >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> +#include "lsdc_pll.h" >>> + >>> +#ifdef CONFIG_DEBUG_FS >>> +static int lsdc_show_clock(struct seq_file *m, void *arg) >>> +{ >>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>> +   struct drm_device *ddev = node->minor->dev; >>> +   struct drm_crtc *crtc; >>> + >>> +   drm_for_each_crtc(crtc, ddev) { >>> +       struct lsdc_display_pipe *pipe = crtc_to_display_pipe(crtc); >>> +       struct lsdc_pll *pixpll = &pipe->pixpll; >>> +       const struct lsdc_pixpll_funcs *funcs = pixpll->funcs; >>> +       struct drm_display_mode *adj = &crtc->state->mode; >>> +       struct lsdc_pll_parms parms; >>> +       unsigned int out_khz; >>> + >>> +       out_khz = funcs->get_clock_rate(pixpll, &parms); >>> + >>> +       seq_printf(m, "Display pipe %u: %dx%d\n", >>> +              pipe->index, adj->hdisplay, adj->vdisplay); >>> + >>> +       seq_printf(m, "Frequency actually output: %u kHz\n", out_khz); >>> +       seq_printf(m, "Pixel clock required: %d kHz\n", adj->clock); >>> +       seq_printf(m, "diff: %d kHz\n", adj->clock); >>> + >>> +       seq_printf(m, "div_ref=%u, loopc=%u, div_out=%u\n", >>> +              parms.div_ref, parms.loopc, parms.div_out); >>> + >>> +       seq_printf(m, "hsync_start=%d, hsync_end=%d, htotal=%d\n", >>> +              adj->hsync_start, adj->hsync_end, adj->htotal); >>> +       seq_printf(m, "vsync_start=%d, vsync_end=%d, vtotal=%d\n\n", >>> +              adj->vsync_start, adj->vsync_end, adj->vtotal); >>> +   } >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_show_mm(struct seq_file *m, void *arg) >>> +{ >>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>> +   struct drm_device *ddev = node->minor->dev; >>> +   struct drm_printer p = drm_seq_file_printer(m); >>> + >>> + drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p); >>> + >>> +   return 0; >>> +} >>> + >>> +#define REGDEF(reg) { __stringify_1(LSDC_##reg##_REG), >>> LSDC_##reg##_REG } >>> +static const struct { >>> +   const char *name; >>> +   u32 reg_offset; >>> +} lsdc_regs_array[] = { >>> +   REGDEF(CURSOR0_CFG), >>> +   REGDEF(CURSOR0_ADDR_LO), >>> +   REGDEF(CURSOR0_ADDR_HI), >>> +   REGDEF(CURSOR0_POSITION), >>> +   REGDEF(CURSOR0_BG_COLOR), >>> +   REGDEF(CURSOR0_FG_COLOR), >>> +   REGDEF(INT), >>> +   REGDEF(CRTC0_CFG), >>> +   REGDEF(CRTC0_STRIDE), >>> +   REGDEF(CRTC0_FB_ORIGIN), >>> +   REGDEF(CRTC0_HDISPLAY), >>> +   REGDEF(CRTC0_HSYNC), >>> +   REGDEF(CRTC0_VDISPLAY), >>> +   REGDEF(CRTC0_VSYNC), >>> +   REGDEF(CRTC0_GAMMA_INDEX), >>> +   REGDEF(CRTC0_GAMMA_DATA), >>> +   REGDEF(CRTC1_CFG), >>> +   REGDEF(CRTC1_STRIDE), >>> +   REGDEF(CRTC1_FB_ORIGIN), >>> +   REGDEF(CRTC1_HDISPLAY), >>> +   REGDEF(CRTC1_HSYNC), >>> +   REGDEF(CRTC1_VDISPLAY), >>> +   REGDEF(CRTC1_VSYNC), >>> +   REGDEF(CRTC1_GAMMA_INDEX), >>> +   REGDEF(CRTC1_GAMMA_DATA), >>> +}; >>> + >>> +static int lsdc_show_regs(struct seq_file *m, void *arg) >>> +{ >>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>> +   struct drm_device *ddev = node->minor->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   int i; >>> + >>> +   for (i = 0; i < ARRAY_SIZE(lsdc_regs_array); i++) { >>> +       u32 offset = lsdc_regs_array[i].reg_offset; >>> +       const char *name = lsdc_regs_array[i].name; >>> + >>> +       seq_printf(m, "%s (0x%04x): 0x%08x\n", >>> +              name, offset, lsdc_rreg32(ldev, offset)); >>> +   } >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_show_vblank_counter(struct seq_file *m, void *arg) >>> +{ >>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>> +   struct drm_device *ddev = node->minor->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> + >>> +   seq_printf(m, "CRTC-0 vblank counter: %08u\n", >>> +          lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG)); >>> + >>> +   seq_printf(m, "CRTC-1 vblank counter: %08u\n", >>> +          lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG)); >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_show_scan_position(struct seq_file *m, void *arg) >>> +{ >>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>> +   struct drm_device *ddev = node->minor->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   u32 val0 = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); >>> +   u32 val1 = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); >>> + >>> +   seq_printf(m, "CRTC-0: x: %08u, y: %08u\n", >>> +          val0 >> 16, val0 & 0xFFFF); >>> + >>> +   seq_printf(m, "CRTC-1: x: %08u, y: %08u\n", >>> +          val1 >> 16, val1 & 0xFFFF); >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_show_fb_addr(struct seq_file *m, void *arg) >>> +{ >>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>> +   struct drm_device *ddev = node->minor->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   u32 lo, hi; >>> +   u32 val; >>> + >>> +   val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>> +   if (val & CFG_FB_IN_USING) { >>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_LO_ADDR_REG); >>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_HI_ADDR_REG); >>> +       seq_printf(m, "CRTC-0 using fb1: 0x%x:%x\n", hi, lo); >>> +   } else { >>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_LO_ADDR_REG); >>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_HI_ADDR_REG); >>> +       seq_printf(m, "CRTC-0 using fb0: 0x%x:%x\n", hi, lo); >>> +   } >>> + >>> +   val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>> +   if (val & CFG_FB_IN_USING) { >>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_LO_ADDR_REG); >>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_HI_ADDR_REG); >>> +       seq_printf(m, "CRTC-1 using fb1: 0x%x:%x\n", hi, lo); >>> +   } else { >>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_LO_ADDR_REG); >>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_HI_ADDR_REG); >>> +       seq_printf(m, "CRTC-1 using fb0: 0x%x:%x\n", hi, lo); >>> +   } >>> + >>> +   return 0; >>> +} >>> + >>> +static struct drm_info_list lsdc_debugfs_list[] = { >>> +   { "clocks",  lsdc_show_clock, 0 }, >>> +   { "mm",      lsdc_show_mm, 0, NULL }, >>> +   { "regs",    lsdc_show_regs, 0 }, >>> +   { "vblanks", lsdc_show_vblank_counter, 0, NULL }, >>> +   { "scan_pos", lsdc_show_scan_position, 0, NULL }, >>> +   { "fb_addr", lsdc_show_fb_addr, 0, NULL }, >>> +}; >>> + >>> +#endif >>> + >>> +void lsdc_debugfs_init(struct drm_minor *minor) >>> +{ >>> +#ifdef CONFIG_DEBUG_FS >>> +   drm_debugfs_create_files(lsdc_debugfs_list, >>> +                ARRAY_SIZE(lsdc_debugfs_list), >>> +                minor->debugfs_root, >>> +                minor); >>> +#endif >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.c >>> b/drivers/gpu/drm/lsdc/lsdc_drv.c >>> new file mode 100644 >>> index 000000000000..3bd457e6e980 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.c >>> @@ -0,0 +1,635 @@ >>> +// SPDX-License-Identifier: GPL-2.0+ >>> +/* >>> + * KMS driver for Loongson display controller >>> + * Copyright (C) 2022 Loongson Corporation >>> + * >>> + * Authors: >>> + *     Li Yi <liyi@loongson.cn> >>> + *     Li Chenyang <lichenyang@loongson.cn> >>> + *     Sui Jingfeng <suijingfeng@loongson.cn> >>> + */ >>> + >>> +#include <linux/module.h> >>> +#include <linux/pci.h> >>> +#include <linux/of_address.h> >>> +#include <drm/drm_aperture.h> >>> +#include <drm/drm_vblank.h> >>> +#include <drm/drm_fb_helper.h> >>> +#include <drm/drm_gem_vram_helper.h> >>> +#include <drm/drm_gem_framebuffer_helper.h> >>> +#include <drm/drm_modeset_helper.h> >>> +#include <drm/drm_atomic_helper.h> >>> +#include <drm/drm_probe_helper.h> >>> +#include <drm/drm_fbdev_generic.h> >>> +#include "lsdc_drv.h" >> >> Alphebetical order. > I will fix this problem at next version >> >>> + >>> +static int lsdc_chip = -1; >>> +MODULE_PARM_DESC(chip, "override chip id (-1 = probed by driver"); >>> +module_param_named(chip, lsdc_chip, int, 0644); >> >> That's a development option. Better not add it to the published driver. > > Without this, compile with W=1 generate warnings. and removing chip > descriptions for Loongson SoC > > is not wanted either. this option can be removed if DT support is > upstream in the future. > > It allow us to developing driver before DT or ACPI support is ready. > Otherwise we have no way developing > > drivers for loongson SoC beforehand. > >>> + >>> +/* >>> + * ls7a1000 and ls7a2000 is not only used with loongson desktop >>> class CPU >>> + * but also loongson server class CPUs (LS3C5000@2.2GHz 16 core). >>> Loongson >>> + * server products generally don't has a dedicated vram mounted for >>> cost >>> + * reason, it will use aspeed bmc as its display on its final and >>> formal >>> + * product. lsdc.has_vram=0 is mainly be use in developing phase of >>> the >>> + * product. When a board don't has a dedicated vram mounted, we can >>> put >>> + * the framebuffer on system RAM. >>> + */ >>> +static int lsdc_has_vram = -1; >>> +MODULE_PARM_DESC(has_vram, "has on-board gpu memory in reality (0 = >>> no, -1 = default"); >>> +module_param_named(has_vram, lsdc_has_vram, int, 0644); >> >> Same here. That's for development. > > Err, we truly very need this feature, > > 1) ASPEED BMC card is removable, it can be unplug from the mother > board of Loongson Server. > > leaving this option there, we still have change enter X server graphic > environment. > > > 2) We need developing drivers for loongson SoC on loongson PC > platform, we may deliberately don't > > use on-board vram. there a need to let TTM manage cached reserved RAM, > because such device don't > > have vram. We have achieve this goal on ls2k2000 SoC. > > Otherwise we have to write two piece of driver, one is DMA helper > base, the other is TTM based, > > we doing so at downstream kernel. > >> >>> + >>> +#define DRIVER_AUTHOR       "Sui Jingfeng <suijingfeng@loongson.cn>" >>> +#define DRIVER_NAME       "lsdc" >>> +#define DRIVER_DESC       "drm driver for loongson's display >>> controller" >>> +#define DRIVER_DATE       "20220701" >>> +#define DRIVER_MAJOR       1 >>> +#define DRIVER_MINOR       0 >>> +#define DRIVER_PATCHLEVEL   0 >>> + >>> +static const struct lsdc_desc dc_in_ls2k0500 = { >> >> Those description structs are a two-edged sword IMHO. >> >>> +   .chip = CHIP_LS2K0500, >>> +   .num_of_crtc = LSDC_NUM_CRTC, >>> +   .max_pixel_clk = 200000, >>> +   .max_width = 2048, >>> +   .max_height = 2048, >>> +   .hw_cursor_w = 32, >>> +   .hw_cursor_h = 32, >>> +   .pitch_align = 256, >>> +   .mc_bits = 40, >> >> These are device constants are probably fine. >> >>> +   .num_of_hw_cursor = 1, >>> +   .has_vblank_counter = false, >>> +   .has_builtin_i2c = false, >>> +   .has_vram = false, >>> +   .has_hpd_reg = false, >>> +   .is_soc = true, >> >> But from a quick look through the code, these settings affect control >> flow and/or modesetting pipelines. They are often better implemented >> by dedicated helpers as outlined in my comments about vblank and >> mgag200. > > Ok, mgag200 is for server mainly, its variant are not so different as > loongson does, > > at least they all have vram, we have to put more attention to turn > drm memory manager. > > I will following your advice by introducing more helpers at next version. > >> >> >>> +}; >>> + >>> +static const struct lsdc_desc dc_in_ls2k1000 = { >>> +   .chip = CHIP_LS2K1000, >>> +   .num_of_crtc = LSDC_NUM_CRTC, >>> +   .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, >>> +   .has_builtin_i2c = false, >>> +   .has_vram = false, >>> +   .has_hpd_reg = false, >>> +   .is_soc = true, >>> +}; >>> + >>> +static const struct lsdc_desc dc_in_ls2k2000 = { >>> +   .chip = CHIP_LS2K2000, >>> +   .num_of_crtc = LSDC_NUM_CRTC, >>> +   .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 = 256, >>> +   .mc_bits = 40, >>> +   .has_vblank_counter = true, >>> +   .has_builtin_i2c = true, >>> +   .has_vram = false, >>> +   .has_hpd_reg = true, >>> +   .is_soc = true, >>> +}; >>> + >>> +static const struct lsdc_desc dc_in_ls7a1000 = { >>> +   .chip = CHIP_LS7A1000, >>> +   .num_of_crtc = LSDC_NUM_CRTC, >>> +   .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, >>> +   .has_builtin_i2c = true, >>> +   .has_vram = true, >>> +   .has_hpd_reg = false, >>> +   .is_soc = false, >>> +}; >>> + >>> +static const struct lsdc_desc dc_in_ls7a2000 = { >>> +   .chip = CHIP_LS7A2000, >>> +   .num_of_crtc = LSDC_NUM_CRTC, >>> +   .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 = 256, >>> +   .mc_bits = 40, /* support 48, but use 40 for backward >>> compatibility */ >>> +   .has_vblank_counter = true, >>> +   .has_builtin_i2c = true, >>> +   .has_vram = true, >>> +   .has_hpd_reg = true, >>> +   .is_soc = false, >>> +}; >>> + >>> +static int lsdc_gem_dumb_create(struct drm_file *file, >>> +               struct drm_device *ddev, >>> +               struct drm_mode_create_dumb *args) >>> +{ >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   return drm_gem_vram_fill_create_dumb(file, ddev, 0, >>> descp->pitch_align, args); >>> +} >>> + >>> +DEFINE_DRM_GEM_FOPS(lsdc_gem_fops); >>> + >>> +static const struct drm_driver lsdc_drm_driver = { >>> +   .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, >>> +   .fops = &lsdc_gem_fops, >>> + >>> +   .name = DRIVER_NAME, >>> +   .desc = DRIVER_DESC, >>> +   .date = DRIVER_DATE, >>> +   .major = DRIVER_MAJOR, >>> +   .minor = DRIVER_MINOR, >>> +   .patchlevel = DRIVER_PATCHLEVEL, >>> + >>> +   .debugfs_init = lsdc_debugfs_init, >>> +   .dumb_create = lsdc_gem_dumb_create, >>> +   .dumb_map_offset = drm_gem_ttm_dumb_map_offset, >>> +   .gem_prime_mmap = drm_gem_prime_mmap, >>> +}; >>> + >>> +static const struct drm_mode_config_funcs lsdc_mode_config_funcs = { >>> +   .fb_create = drm_gem_fb_create, >>> +   .output_poll_changed = drm_fb_helper_output_poll_changed, >>> +   .atomic_check = drm_atomic_helper_check, >>> +   .atomic_commit = drm_atomic_helper_commit, >>> +   .mode_valid = drm_vram_helper_mode_valid, >>> +}; >>> + >>> +static int lsdc_modeset_init(struct lsdc_device *ldev, >>> +                const struct lsdc_desc *descp) >>> +{ >>> +   struct drm_device *ddev = &ldev->base; >>> +   unsigned int num_crtc = descp->num_of_crtc; >>> +   unsigned int i; >>> +   int ret; >>> + >>> +   for (i = 0; i < num_crtc; i++) { >>> +       ret = lsdc_create_output(ldev, i); >>> +       if (ret) >>> +           return ret; >>> +   } >>> + >>> +   for (i = 0; i < num_crtc; i++) { >>> +       struct lsdc_display_pipe *dispipe = &ldev->dispipe[i]; >>> +       struct lsdc_pll *pixpll = &dispipe->pixpll; >>> +       struct drm_plane *primary = &dispipe->primary; >>> +       struct drm_plane *cursor = &dispipe->cursor; >>> +       struct drm_crtc *crtc = &dispipe->crtc; >>> + >>> +       dispipe->index = i; >>> + >>> +       ret = lsdc_pixpll_init(pixpll, ddev, i); >>> +       if (ret) >>> +           return ret; >>> + >>> +       ret = lsdc_plane_init(ldev, primary, >>> DRM_PLANE_TYPE_PRIMARY, i); >>> +       if (ret) >>> +           return ret; >>> + >>> +       ret = lsdc_plane_init(ldev, cursor, DRM_PLANE_TYPE_CURSOR, i); >>> +       if (ret) >>> +           return ret; >>> + >>> +       /* >>> +        * Initial all of the CRTC available, in this way the logical >>> +        * crtc index is equal to the hardware crtc index. >>> +        * >>> +        * display pipe 0 = crtc0 + dvo0 + encoder0 + connector0 >>> +        * display pipe 1 = crtc1 + dvo1 + encoder1 + connector1 >>> +        */ >>> +       ret = lsdc_crtc_init(ddev, crtc, i, primary, cursor); >>> +       if (ret) >>> +           return ret; >>> + >>> +       drm_info(ddev, "display pipe %u initialized\n", i); >>> +   } >>> + >>> +   drm_mode_config_reset(ddev); >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_mode_config_init(struct drm_device *ddev, >>> +                const struct lsdc_desc *descp) >>> +{ >>> +   int ret; >>> + >>> +   ret = drmm_mode_config_init(ddev); >>> +   if (ret) >>> +       return ret; >>> + >>> +   ddev->mode_config.funcs = &lsdc_mode_config_funcs; >>> +   ddev->mode_config.min_width = 1; >>> +   ddev->mode_config.min_height = 1; >>> +   ddev->mode_config.max_width = descp->max_width * LSDC_NUM_CRTC; >>> +   ddev->mode_config.max_height = descp->max_height * LSDC_NUM_CRTC; >>> +   ddev->mode_config.preferred_depth = 24; >>> +   ddev->mode_config.prefer_shadow = descp->has_vram; >>> + >>> +   ddev->mode_config.cursor_width = descp->hw_cursor_h; >>> +   ddev->mode_config.cursor_height = descp->hw_cursor_h; >>> + >>> +   /* max_vblank_count is set on each CRTC */ >>> +   ddev->max_vblank_count = 0xffffffff; >>> + >>> +   return ret; >>> +} >>> + >>> +/* >>> + * lsdc_detect_chip - a function to tell lsdc variant apart. >>> + */ >>> +static const struct lsdc_desc * >>> +lsdc_detect_chip(struct pci_dev *pdev, const struct pci_device_id >>> *ent) >>> +{ >>> +   /* >>> +    * Provide a way to false the driver running on new model >>> +    * before we have a proper way to tell them apart >>> +    */ >>> +   if (lsdc_chip == CHIP_LS2K2000) { >>> +       dev_info(&pdev->dev, "Override as LS2K2000\n"); >>> +       return &dc_in_ls2k2000; >>> +   } >>> + >>> +   if (lsdc_chip == CHIP_LS2K1000) { >>> +       dev_info(&pdev->dev, "Override as LS2K1000\n"); >>> +       return &dc_in_ls2k1000; >>> +   } >>> + >>> +   if (lsdc_chip == CHIP_LS2K0500) { >>> +       dev_info(&pdev->dev, "Override as LS2K0500\n"); >>> +       return &dc_in_ls2k0500; >>> +   } >>> + >>> +   if (ent) { >>> +       struct lsdc_desc *dc_descp; >>> + >>> +       dc_descp = (struct lsdc_desc *)ent->driver_data; >> >> Is that legal? driver_data is of kernel_ulong_t. Maybe store the >> CHIP_ constant here and then lookup the lsdc_desc. >> > It works in practice but it seems dangerous doing so, I will using > CHIP_ LS7AXXXX and CHIP_LS2KXXXX at new version. > > i don't notice it before, thank you. > >> >> https://elixir.bootlin.com/linux/latest/source/include/linux/mod_devicetable.h#L36 >> >> >>> +       if (dc_descp->chip == CHIP_LS7A1000) >>> +           dev_info(&pdev->dev, "LS7A1000 Found revision: %u\n", >>> pdev->revision); >>> +       else if (dc_descp->chip == CHIP_LS7A2000) >>> +           dev_info(&pdev->dev, "LS7A2000 Found revison: %u\n", >>> pdev->revision); >>> + >>> +       return dc_descp; >>> +   } >>> + >>> +   dev_err(&pdev->dev, "Not known device\n"); >>> + >>> +   return NULL; >>> +} >>> + >>> +static int lsdc_get_dedicated_vram(struct lsdc_device *ldev, >>> +                  const struct lsdc_desc *descp) >>> +{ >>> +   struct drm_device *ddev = &ldev->base; >>> +   struct pci_dev *gpu; >>> +   resource_size_t base, size; >>> + >>> +   /* >>> +    * The GPU and display controller in LS7A1000/LS7A2000 are >>> separated >>> +    * PCIE devices, they are two devices not one. The DC is a pci >>> device, >>> +    * but it don't have a dedicate VRAM bar, the BIOS engineer >>> choose to >>> +    * assign the VRAM to the gpu device. Sadly, after years >>> application, >>> +    * this decision form as a convention for loongson integrate >>> graphics. >>> +    * For LS7A1000 and LS7A2000, bar 2 of GPU device contain the >>> VRAM, >>> +    * both the GPU and the DC can make use of the VRAM depend on >>> how DRM >>> +    * device driver is written. Therefore, we have to do some >>> tricks here. >>> +    */ >>> +   if (descp->chip == CHIP_LS7A1000) >>> +       gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A15, NULL); >>> +   else if (descp->chip == CHIP_LS7A2000) >>> +       gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A25, NULL); >>> + >>> +   if (!gpu) { >>> +       drm_warn(ddev, "No GPU device found\n"); >>> +       return -ENODEV; >>> +   } >>> + >>> +   base = pci_resource_start(gpu, 2); >>> +   size = pci_resource_len(gpu, 2); >>> + >>> +   ldev->vram_base = base; >>> +   ldev->vram_size = size; >>> + >>> +   drm_info(ddev, "dedicated vram start: 0x%llx, size: %uMB\n", >>> +        (u64)base, (u32)(size >> 20)); >>> + >>> +   return 0; >>> +} >>> + >>> +/* >>> + * Loongson display controller is not equipped with IOMMU. >>> + * In order to make this driver work with Loongson SoC, >>> + * you need to either carveout part RAM as VRAM or reserve >>> + * part system ram as vram. Pass the start address and size >>> + * via DT or ACPI. >>> + */ >>> +static int lsdc_of_get_reserved_ram(struct lsdc_device *ldev) >>> +{ >>> +   struct drm_device *ddev = &ldev->base; >>> +   unsigned long size = 0; >>> +   struct device_node *node; >>> +   struct resource r; >>> +   int ret; >>> + >>> +   node = of_parse_phandle(ddev->dev->of_node, "memory-region", 0); >>> +   if (!node) { >>> +       drm_err(ddev, "No memory-region property or no DT, abort\n"); >>> +       return -ENOENT; >>> +   } >>> + >>> +   ret = of_address_to_resource(node, 0, &r); >>> +   of_node_put(node); >>> +   if (ret) >>> +       return ret; >>> + >>> +   size = r.end - r.start + 1; >>> + >>> +   ldev->vram_base = r.start; >>> +   ldev->vram_size = size; >>> + >>> +   drm_info(ddev, "using VRAM carveout: %lx@%pa\n", size, &r.start); >>> + >>> +   return 0; >>> +} >>> + >>> +static int >>> +lsdc_remove_conflicting_framebuffers(const struct drm_driver *pdriver, >>> +                    resource_size_t base, >>> +                    resource_size_t size) >>> +{ >>> +   return drm_aperture_remove_conflicting_framebuffers(base, >>> +                               size, >>> +                               false, >>> +                               pdriver); >>> +} >> >> No need to wrap this single function. > > Ok, Loongson upstream kernel support efifb and it backing memory is > located at this range. > > we must properly remove it, otherwise they will have two fb > node(/dev/fb0 and /dev/fb1) > >> >>> + >>> +static struct lsdc_device * >>> +lsdc_create_device(struct pci_dev *pdev, >>> +          const struct pci_device_id *ent, >>> +          const struct drm_driver *drv) >>> +{ >>> +   struct lsdc_device *ldev; >>> +   struct drm_device *ddev; >>> +   const struct lsdc_desc *descp; >>> +   int ret; >>> + >>> +   ldev = devm_drm_dev_alloc(&pdev->dev, drv, struct lsdc_device, >>> base); >>> +   if (IS_ERR(ldev)) >>> +       return ldev; >>> + >>> +   ddev = &ldev->base; >>> + >>> +   pci_set_drvdata(pdev, ddev); >>> + >>> +   descp = lsdc_detect_chip(pdev, ent); >>> +   if (!descp) >>> +       return NULL; >>> + >>> +   ldev->descp = descp; >>> + >>> +   spin_lock_init(&ldev->reglock); >>> + >>> +   /* BAR 0 the DC device contains registers */ >>> +   ldev->reg_base = pcim_iomap(pdev, 0, 0); >>> +   if (!ldev->reg_base) >>> +       return ERR_PTR(-EIO); >>> + >>> +   if (descp->has_vram && lsdc_has_vram) >>> +       ret = lsdc_get_dedicated_vram(ldev, descp); >>> +   else >>> +       ret = lsdc_of_get_reserved_ram(ldev); >>> + >>> +   if (ret) { >>> +       drm_err(ddev, "Init VRAM failed: %d\n", ret); >>> +       return ERR_PTR(ret); >>> +   } >>> + >>> +   ret = lsdc_remove_conflicting_framebuffers(drv, >>> +                          ldev->vram_base, >>> +                          ldev->vram_size); >>> +   if (ret) { >>> +       drm_err(ddev, "remove firmware framebuffers failed: %d\n", >>> ret); >>> +       return ERR_PTR(ret); >>> +   } >>> + >>> +   ret = drmm_vram_helper_init(ddev, ldev->vram_base, >>> ldev->vram_size); >> >> Using GEM VRAM helpers could become a problem and I'd advise against >> them. >> > Err, I'm not want to agree this... >> First of all, how much video memory is there? VRAM helpers need at >> least 3 times the maximum consumption to be reliable.So if you have a >> FullHD display that consumes ~8 Mib, you'd need 24 MiB of VRAM. >> Hardware cursors can further interfere. > > Part of ls3a5000/ls3a4000+ls7a1000 machine have 64MB, another part of > ls3a5000/ls3a4000+ls7a1000 have 128 MB. > > For ls7a1000(1080p) the maximum usage actually is two > framebuffer(1920x1080x4x2) + two cursor (64x64x4x 2) + one fbcon > (1920x1080x4) and LS7A2000 board has at lease 256MB VRAM. > > Therefore I can guarantee that we have at least 64MB VRAM,enough for > double screen(1920x1080) + double cursor. > > I guess you means that there are some device(aspeed bmc) with limited > VRAM size, but it is more a problem > > of the device itself(It is not vram helper's fault). Early version of > ast driver would evict fbcon to system RAM > > to save VRAM usage, would this idea be a solution for the device with > limited vram capacity today? > > >> VRAM helpers also don't support buffer sharing with other devices. >> That's a problem for some compositors. >> >> For those reasons, VRAM helpers are deprecated. I'd rather remove >> them entirely. > > Its not that bad, VRAM helpers is good enough for our case as a first > step. Our colleague and customers mainly > > using MATE and XFCE under X server graphic environment, they need > basic 2D graphics environment most. > > We have tested our driver against macro and xfwm4, it actually works > very well. Individual compositor may > > have a few problems, but it isn't a thing for our case currently. Can > we solve those problem by other means? > > We could give up cross device buffer sharing support for chasing 2D > graphic performance , we intend to > > introducing buffer sharing with the help of the GPU with another patch > in the future. We possibly could > > switch to ttm-based KMS driver under your guiding then. For ls7a1000, > I imagine to merge lsdc with evnaviv > > into a single drm driver.May I suggest to postpone the decision of > removing VRAM helpers, its performance > > is better than shmem helper and have better support for double screen. > > >> As a better alternative for the DC, you could use GEM SHMEM helpers. >> See mgag200 or ast for how to do that. > > We have two display pipe thus support double screen, therefore we have > two framebuffer BO and two cursor BO, > > we need to take clone screen, extend screen and rotate and resize > case into consideration, on clone screen case > > the two CRTC should scanout from the same address, while on extend > screen case, the frame buffer itself is grant > > one buffer if using modesetting driver, but we have two primary plane > thus two fb offset. Arrange VRAM bo offset > > for 4 different drm BO object manually is little bit hack and > cumbersome. I have already finish the developing > > work of shmem helper support, see: > https://github.com/loongson-gfx/drm-tip/compare/drm-tip...shmem > > But it is NOT as good as VRAM helper in the perspective of > performance. I think copy from the shadow to the > > front frame buffer should be done at user space, this can save the > overhead of reporting damage rectangle whenever > > have a damage. > > >> >> The comments mention that there's also a GPU. Does the GPU use VRAM? >> Or do GPU and DC share memory ranges? >> > LS7A2000 integrate Loongson self-made GPU, driver is developed by my > colleagues, it already works but my mature > > to upstream yet. LS7A1000 have a VIVANTE GC1000 v5037 (20120617 etc), > a ten years ago render-only GPU. > > the VRAM is connected with the ls7a1000 bridge chip, the memory > controller is also in ls7a1000 bridge chip itself. > > ATI call this type vram as sideport, my company call it as GMEM, I > think they are similar. > > etnaviv don't use VRAM currently, all BOs of etnaviv is in system ram. > > I think merge etnaviv and lsdc into a single drm driver would be a > solution, what do you think? > > > On downstream environment, we hacking make ttm-based lsdc work with > etnaviv, by copying(sw resolve) > > the rendered buffer to vram backed framebuffer, glmark2 get 220 point, > quake3 arena can play, haha :) > > It is achieved by developing xf86-video-loongson which using etnaviv > to share buffer between xserver and glmark2, > > lsdc(or downstream loongson-drm) is only need to implement a > self-sharing for userspace process . > > see https://github.com/loongson-gfx/xf86-video-loongson. The shared > rendering buffer is copy > > to front buffer with loongson LSX and LASX(256-bit) SIMD > optimization. Report dirty to kernel space is not need. > > we can even let suppertiled buffer rendered by etnaviv get exported, > xf86-video-loongson get the fd and prime > > it to handle, then we are using CPU to do the resolve work which > directly resolve to the front framebuffer. No damage > > rectangle reporting is need. > > > Hence, the 2D performance of graphics is the more important. GEM > SHMEM helpers is not better than vram helpers > > in the perspective of performance. > >>> +   if (ret) { >>> +       drm_err(ddev, "vram helper init failed: %d\n", ret); >>> +       goto err; >>> +   } >>> + >>> +   ret = lsdc_mode_config_init(ddev, descp); >>> +   if (ret) { >>> +       drm_dbg(ddev, "%s: %d\n", __func__, ret); >>> +       goto err; >>> +   } >>> + >>> +   ret = lsdc_modeset_init(ldev, descp); >>> +   if (ret) >>> +       goto err; >>> + >>> +   ret = drm_vblank_init(ddev, descp->num_of_crtc); >>> +   if (ret) >>> +       goto err; >>> + >>> +   ret = request_threaded_irq(pdev->irq, >>> +                  lsdc_get_irq_handler(ldev), >>> +                  lsdc_irq_thread_handler, >>> +                  IRQF_ONESHOT, >>> +                  dev_name(ddev->dev), >>> +                  ddev); >>> +   if (ret) { >>> +       drm_err(ddev, "Failed to register lsdc interrupt\n"); >>> +       goto err; >>> +   } >>> + >>> +   drm_kms_helper_poll_init(ddev); >>> + >>> +   return ldev; >>> + >>> +err: >>> +   return ERR_PTR(ret); >>> +} >>> + >>> +static int lsdc_pci_probe(struct pci_dev *pdev, >>> +             const struct pci_device_id *ent) >>> +{ >>> +   struct drm_device *ddev; >>> +   struct lsdc_device *ldev; >>> +   int ret; >>> + >>> +   ret = pcim_enable_device(pdev); >>> +   if (ret) >>> +       return ret; >>> + >>> +   pci_set_master(pdev); >>> + >>> +   ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); >>> +   if (ret) >>> +       return ret; >>> + >>> +   ldev = lsdc_create_device(pdev, ent, &lsdc_drm_driver); >>> +   if (IS_ERR(ldev)) >>> +       return PTR_ERR(ldev); >>> + >>> +   ddev = &ldev->base; >>> + >>> +   ret = drm_dev_register(ddev, 0); >>> +   if (ret) >>> +       return ret; >>> + >>> +   drm_fbdev_generic_setup(ddev, 32); >>> + >>> +   return 0; >>> +} >>> + >>> +static void lsdc_pci_remove(struct pci_dev *pdev) >>> +{ >>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>> + >>> +   drm_dev_unregister(ddev); >>> +   drm_atomic_helper_shutdown(ddev); >>> +} >>> + >>> +static int lsdc_drm_freeze(struct drm_device *ddev) >>> +{ >>> +   int error; >>> + >>> +   error = drm_mode_config_helper_suspend(ddev); >>> +   if (error) >>> +       return error; >>> + >>> +   pci_save_state(to_pci_dev(ddev->dev)); >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_drm_resume(struct device *dev) >>> +{ >>> +   struct pci_dev *pdev = to_pci_dev(dev); >>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>> + >>> +   return drm_mode_config_helper_resume(ddev); >>> +} >>> + >>> +static int lsdc_pm_freeze(struct device *dev) >>> +{ >>> +   struct pci_dev *pdev = to_pci_dev(dev); >>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>> + >>> +   return lsdc_drm_freeze(ddev); >>> +} >>> + >>> +static int lsdc_pm_thaw(struct device *dev) >>> +{ >>> +   return lsdc_drm_resume(dev); >>> +} >>> + >>> +static int lsdc_pm_suspend(struct device *dev) >>> +{ >>> +   struct pci_dev *pdev = to_pci_dev(dev); >>> +   int error; >>> + >>> +   error = lsdc_pm_freeze(dev); >>> +   if (error) >>> +       return error; >>> + >>> +   pci_save_state(pdev); >>> +   /* Shut down the device */ >>> +   pci_disable_device(pdev); >>> +   pci_set_power_state(pdev, PCI_D3hot); >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_pm_resume(struct device *dev) >>> +{ >>> +   struct pci_dev *pdev = to_pci_dev(dev); >>> + >>> +   if (pcim_enable_device(pdev)) >>> +       return -EIO; >>> + >>> +   pci_set_power_state(pdev, PCI_D0); >>> + >>> +   pci_restore_state(pdev); >>> + >>> +   return lsdc_pm_thaw(dev); >>> +} >>> + >>> +static const struct dev_pm_ops lsdc_pm_ops = { >>> +   .suspend = lsdc_pm_suspend, >>> +   .resume = lsdc_pm_resume, >>> +   .freeze = lsdc_pm_freeze, >>> +   .thaw = lsdc_pm_thaw, >>> +   .poweroff = lsdc_pm_freeze, >>> +   .restore = lsdc_pm_resume, >>> +}; >>> + >>> +static const struct pci_device_id lsdc_pciid_list[] = { >>> +   {PCI_VENDOR_ID_LOONGSON, 0x7a06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, >>> +    (kernel_ulong_t)&dc_in_ls7a1000}, >>> +   {PCI_VENDOR_ID_LOONGSON, 0x7a36, PCI_ANY_ID, PCI_ANY_ID, 0, 0, >>> +    (kernel_ulong_t)&dc_in_ls7a2000}, >>> +   {0, 0, 0, 0, 0, 0, 0} >>> +}; >>> + >>> +static struct pci_driver lsdc_pci_driver = { >>> +   .name = DRIVER_NAME, >>> +   .id_table = lsdc_pciid_list, >>> +   .probe = lsdc_pci_probe, >>> +   .remove = lsdc_pci_remove, >>> +   .driver.pm = &lsdc_pm_ops, >>> +}; >>> + >>> +static int __init lsdc_module_init(void) >>> +{ >>> +   struct pci_dev *pdev = NULL; >>> + >>> +   if (drm_firmware_drivers_only()) >>> +       return -EINVAL; >> >>  return -ENODEV; > > OK, no problem, I will fix it at new version. I remember i already fix > this, sorry. > >> >>> + >>> +   while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) { >>> +       /* >>> +        * Multiple video card workaround: lsdc will always be >>> +        * selected as the default boot device by vgaarb subsystem. >>> +        */ >>> +       if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) { >>> +           pr_info("Discrete graphic card detected, abort\n"); >>> +           return 0; >>> +       } >>> +   } >> >> I don't understand this code. You are not registering your driver if >> there's another graphics card? >> > Yes, we do not want to registering this driver if there's discrete > graphics card or BMC in the system. > > > for example: > > 1) Loongson desktop computer(ls3a5000 + ls7a2000 with amdgpu rx580 > mounted) > > On such a configuration, loongson display controller and rx580 > co-exist in the system. > > As discrete GPU is more powerful, it seems that there no need to using > LSDC driver anymore. > > The user is most likely want to using the rx580 as primary GPU and > Using rx580 alone is enough for user in such a case. > > It is introduce unnecessary problems and confusion If we leave lsdc > driver live on the system together with amdgpu/ati drm driver. > > > I have just tested the case which leave lsdc driver live on the > system together with radeon drm driver. > > the monitor connected with lsdc is black, not working as a output slave. > > > 2) Loongson ls3c5000 server with aspeed 2400/25000 BMC card mounted. > > The BMC provide remote access and control besides the graphics, use it > alone is enough, we have solve > > 4 ~ 5 bugs on our product side. we don't want make a mess. > > our tester get confused, than find a amdgpu and plug in, then he get > three video co-exist in the sistem. > > lsdc, ast and amdgpu, there /dev/dri/card0, /dev/dri/car1 and > /dev/dri/card2. > > if i bring etnaviv up, then i can get four. > > >> Best regards >> Thomas >> > Thinks your valuable advice and guiding, we will take time to fix all > the problems your mentioned, > > and prepare next version. The driver still have a few problem, i will fix it if i can found, but it already can suit the need for daily usage for programmer. hope i can help to merge this driver, i holding it on my hand more than one years . If i miss anything, please remind me, any discussion is welcome. > >>> + >>> +   return pci_register_driver(&lsdc_pci_driver); >>> +} >>> +module_init(lsdc_module_init); >>> + >>> +static void __exit lsdc_module_exit(void) >>> +{ >>> +   pci_unregister_driver(&lsdc_pci_driver); >>> +} >>> +module_exit(lsdc_module_exit); >>> + >>> +MODULE_DEVICE_TABLE(pci, lsdc_pciid_list); >>> +MODULE_AUTHOR(DRIVER_AUTHOR); >>> +MODULE_DESCRIPTION(DRIVER_DESC); >>> +MODULE_LICENSE("GPL"); >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.h >>> b/drivers/gpu/drm/lsdc/lsdc_drv.h >>> new file mode 100644 >>> index 000000000000..d67dae0414ca >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.h >>> @@ -0,0 +1,274 @@ >>> +/* SPDX-License-Identifier: GPL-2.0 */ >>> +/* >>> + * KMS driver for Loongson display controller >>> + * Copyright (C) 2022 Loongson Corporation >>> + * >>> + * Authors: >>> + *     Li Yi <liyi@loongson.cn> >>> + *     Li Chenyang <lichenyang@loongson.cn> >>> + *     Sui Jingfeng <suijingfeng@loongson.cn> >>> + */ >>> + >>> +#ifndef __LSDC_DRV_H__ >>> +#define __LSDC_DRV_H__ >>> + >>> +#include <linux/i2c.h> >>> +#include <linux/i2c-algo-bit.h> >>> + >>> +#include <drm/drm_print.h> >>> +#include <drm/drm_device.h> >>> +#include <drm/drm_crtc.h> >>> +#include <drm/drm_plane.h> >>> +#include <drm/drm_connector.h> >>> +#include <drm/drm_encoder.h> >>> +#include <drm/drm_drv.h> >>> +#include <drm/drm_atomic.h> >>> + >>> +#include "lsdc_regs.h" >>> +#include "lsdc_pll.h" >>> + >>> +#define LSDC_NUM_CRTC          2 >>> + >>> +/* >>> + * The display controller in LS7A2000 integrate three loongson >>> self-made >>> + * encoder. Display pipe 0 has a transparent VGA encoder and a HDMI >>> phy, >>> + * they are parallel. Display pipe 1 has only one HDMI phy. >>> + *      ______________________ _____________ >>> + *     |            +-----+ | |            | >>> + *     | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA >>> Monitor |<---+ >>> + *     |       |   +-----+ | |_____________|   | >>> + *     |       |            | ______________   | >>> + *     |       |   +------+ | |             |  | >>> + *     |       +--> | HDMI | ----> HDMI Connector --> | HDMI >>> Monitor |<--+ >>> + *     |            +------+ | |______________|  | >>> + *     |           +------+ >>> |                                          | >>> + *     |           | i2c6 | >>> <-------------------------------------------+ >>> + *     |           +------+ | >>> + *     |                     | >>> + *     |   DC in LS7A2000   | >>> + *     |                     | >>> + *     |           +------+ | >>> + *     |           | i2c7 | <--------------------------------+ >>> + *     |           +------+ | | >>> + *     |                     | ______|_______ >>> + *     |           +------+ | |             | >>> + *     | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI >>> Monitor | >>> + *     |           +------+ | |______________| >>> + *     |______________________| >>> + * >>> + * >>> + * The display controller in LS7A1000 integrate two-way DVO, external >>> + * encoder is required except use directly with dpi(rgb888) panel. >>> + * ___________________ _________ >>> + *     |           -------| |        | >>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>> Display | >>> + *     | _  _    -------|       ^            ^ |_________| >>> + *     | | | | |   -------|       |            | >>> + *     | |_| |_|   | i2c6 <--------+-------------+ >>> + *     |           -------| >>> + *     | _  _    -------| >>> + *     | | | | |   | i2c7 <--------+-------------+ >>> + *     | |_| |_|   -------|       | | _________ >>> + *     |           -------|       |            | |        | >>> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | >>> Panel | >>> + *     |           -------| |_________| >>> + *     |___________________| >>> + * >>> + * >>> + * The display controller in LS2K1000. >>> + * ___________________ _________ >>> + *     |           -------| |        | >>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>> Display | >>> + *     | _  _    -------|       ^             ^ |_________| >>> + *     | | | | |          |       |             | >>> + *     | |_| |_|          |    +------+         | >>> + *     |                  <---->| i2c0 |<---------+ >>> + *     | DC in LS2K1000  |    +------+ >>> + *     | _  _           |    +------+ >>> + *     | | | | |          <---->| i2c1 |----------+ >>> + *     | |_| |_|          |    +------+ | _________ >>> + *     |           -------|       |             | |        | >>> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | >>> Panel | >>> + *     |           -------| |_________| >>> + *     |___________________| >>> + * >>> + * >>> + * The display controller in LS2K0500, LS2K0500 has a built-in >>> transparent >>> + * VGA encoder which is connected to display pipe 1(CRTC1). >>> + * ___________________ _________ >>> + *     |           -------| |        | >>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>> Display | >>> + *     | _  _    -------|       ^             ^ |_________| >>> + *     | | | | |          |       |             | >>> + *     | |_| |_|          |    +------+         | >>> + *     |                  <---->| i2c4 |<---------+ >>> + *     | DC in LS2K0500  |    +------+ >>> + *     | _  _           |    +------+ >>> + *     | | | | |          <---->| i2c5 |-------------------+ >>> + *     | |_| |_|          |    +------+ ______|______ >>> + *     |          +-----+ | |            | >>> + *     | CRTC1 --> | VGA | ------------------------> | VGA Monitor | >>> + *     |          +-----+ | |_____________| >>> + *     |___________________| >>> + * >>> + * LS7A1000 and LS7A2000 are bridge chips, has dedicated Video RAM. >>> + * while LS2K2000/LS2K1000/LS2K0500 are SoC, they don't have >>> dediacated >>> + * Video RAM. The dc in LS2K2000 is basicly same with the dc in >>> LS7A1000 >>> + * except that LS2K2000 don't have a video RAM and have only one >>> built-in >>> + * hdmi encoder. >>> + * >>> + * There is only a 1:1 mapping of encoders and connectors for the DC, >>> + * each CRTC have two FB address registers. >>> + */ >>> + >>> +enum loongson_chip_family { >>> +   CHIP_UNKNOWN = 0, >>> +   CHIP_LS7A1000 = 1, /* North bridge of >>> LS3A3000/LS3A4000/LS3A5000 */ >>> +   CHIP_LS7A2000 = 2, /* Update version of LS7A1000, with >>> built-in HDMI encoder */ >>> +   CHIP_LS2K0500 = 3, /* Reduced version of LS2K1000, single core */ >>> +   CHIP_LS2K1000 = 4, /* 2-Core Mips64r2/LA264 SoC, 64-bit, 1.0 >>> Ghz */ >>> +   CHIP_LS2K2000 = 5, /* 2-Core 64-bit LA364 SoC, 1.2 ~ 1.5 Ghz */ >>> +   CHIP_LAST, >>> +}; >>> + >>> +struct lsdc_desc { >>> +   enum loongson_chip_family chip; >>> +   u32 num_of_crtc; >>> +   u32 max_pixel_clk; >>> +   u32 max_width; >>> +   u32 max_height; >>> +   u32 num_of_hw_cursor; >>> +   u32 hw_cursor_w; >>> +   u32 hw_cursor_h; >>> +   u32 pitch_align; /* DMA alignment constraint */ >>> +   u64 mc_bits;     /* physical address bus bit width */ >>> +   bool has_vblank_counter; >>> +   bool has_builtin_i2c; >>> +   bool has_vram; >>> +   bool has_hpd_reg; >>> +   bool is_soc; >>> +}; >>> + >>> +struct lsdc_i2c { >>> +   struct i2c_adapter adapter; >>> +   struct i2c_algo_bit_data bit; >>> +   struct drm_device *ddev; >>> +   void __iomem *reg_base; >>> +   void __iomem *dir_reg; >>> +   void __iomem *dat_reg; >>> +   /* pin bit mask */ >>> +   u8 sda; >>> +   u8 scl; >>> +}; >>> + >>> +/* >>> + * struct lsdc_display_pipe - Abstraction of hardware display >>> pipeline. >>> + * @crtc: CRTC control structure >>> + * @primary: framebuffer plane control structure >>> + * @cursor: cursor plane control structure >>> + * @output: output control structure of this display pipe bind >>> + * @pixpll: pixel pll control structure >>> + * @index: the index corresponding to the hardware display pipe >>> + */ >>> +struct lsdc_display_pipe { >>> +   struct drm_crtc crtc; >>> +   struct drm_plane primary; >>> +   struct drm_plane cursor; >>> +   struct drm_encoder encoder; >>> +   struct drm_connector connector; >>> +   struct lsdc_pll pixpll; >>> +   struct lsdc_i2c *li2c; >>> +   int index; >>> +}; >>> + >>> +static inline struct lsdc_display_pipe * >>> +crtc_to_display_pipe(struct drm_crtc *crtc) >>> +{ >>> +   return container_of(crtc, struct lsdc_display_pipe, crtc); >>> +} >>> + >>> +static inline struct lsdc_display_pipe * >>> +cursor_to_display_pipe(struct drm_plane *cursor) >>> +{ >>> +   return container_of(cursor, struct lsdc_display_pipe, cursor); >>> +} >>> + >>> +static inline struct lsdc_display_pipe * >>> +connector_to_display_pipe(struct drm_connector *conn) >>> +{ >>> +   return container_of(conn, struct lsdc_display_pipe, connector); >>> +} >>> + >>> +static inline struct lsdc_display_pipe * >>> +encoder_to_display_pipe(struct drm_encoder *enc) >>> +{ >>> +   return container_of(enc, struct lsdc_display_pipe, encoder); >>> +} >>> + >>> +struct lsdc_crtc_state { >>> +   struct drm_crtc_state base; >>> +   struct lsdc_pll_parms pparms; >>> +}; >>> + >>> +struct lsdc_device { >>> +   struct drm_device base; >>> + >>> +   /* @reglock: protects concurrent register access */ >>> +   spinlock_t reglock; >>> +   void __iomem *reg_base; >>> +   void __iomem *vram; >>> +   resource_size_t vram_base; >>> +   resource_size_t vram_size; >>> +   u64 mc_mask; >>> + >>> +   struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC]; >>> + >>> +   /* @num_output: count the number of active display pipe */ >>> +   unsigned int num_output; >>> + >>> +   /* @descp: features description of the DC variant */ >>> +   const struct lsdc_desc *descp; >>> + >>> +   u32 irq_status; >>> +}; >>> + >>> +static inline struct lsdc_device * >>> +to_lsdc(struct drm_device *ddev) >>> +{ >>> +   return container_of(ddev, struct lsdc_device, base); >>> +} >>> + >>> +static inline struct lsdc_crtc_state * >>> +to_lsdc_crtc_state(struct drm_crtc_state *base) >>> +{ >>> +   return container_of(base, struct lsdc_crtc_state, base); >>> +} >>> + >>> +void lsdc_debugfs_init(struct drm_minor *minor); >>> + >>> +int lsdc_crtc_init(struct drm_device *ddev, >>> +          struct drm_crtc *crtc, >>> +          unsigned int index, >>> +          struct drm_plane *primary, >>> +          struct drm_plane *cursor); >>> + >>> +int lsdc_plane_init(struct lsdc_device *ldev, >>> +           struct drm_plane *plane, >>> +           enum drm_plane_type type, >>> +           unsigned int index); >>> + >>> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index); >>> + >>> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >>> +                     void *base, >>> +                     unsigned int index); >>> + >>> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >>> int index); >>> + >>> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg); >>> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev); >>> + >>> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset); >>> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 >>> val); >>> + >>> +#endif >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c >>> b/drivers/gpu/drm/lsdc/lsdc_i2c.c >>> new file mode 100644 >>> index 000000000000..b380098409ca >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c >>> @@ -0,0 +1,201 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include <linux/i2c.h> >>> +#include <drm/drm_managed.h> >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> + >>> +/* >>> + * ls7a_gpio_i2c_set - set the state of a gpio pin indicated by mask >>> + * @mask: gpio pin mask >>> + * @state: "0" for low, "1" for high >>> + */ >>> +static void ls7a_gpio_i2c_set(struct lsdc_i2c * const li2c, int >>> mask, int state) >>> +{ >>> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >>> +   unsigned long flags; >>> +   u8 val; >>> + >>> +   spin_lock_irqsave(&ldev->reglock, flags); >>> + >>> +   if (state) { >>> +       /* >>> +        * Setting this pin as input directly, write 1 for Input. >>> +        * The external pull-up resistor will pull the level up >>> +        */ >>> +       val = readb(li2c->dir_reg); >>> +       val |= mask; >>> +       writeb(val, li2c->dir_reg); >>> +   } else { >>> +       /* First set this pin as output, write 0 for Output */ >>> +       val = readb(li2c->dir_reg); >>> +       val &= ~mask; >>> +       writeb(val, li2c->dir_reg); >>> + >>> +       /* Then, make this pin output 0 */ >>> +       val = readb(li2c->dat_reg); >>> +       val &= ~mask; >>> +       writeb(val, li2c->dat_reg); >>> +   } >>> + >>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>> +} >>> + >>> +/* >>> + * ls7a_gpio_i2c_get - read value back from the gpio pin indicated >>> by mask >>> + * @mask: gpio pin mask >>> + * return "0" for low, "1" for high >>> + */ >>> +static int ls7a_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask) >>> +{ >>> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >>> +   unsigned long flags; >>> +   u8 val; >>> + >>> +   spin_lock_irqsave(&ldev->reglock, flags); >>> + >>> +   /* First set this pin as input */ >>> +   val = readb(li2c->dir_reg); >>> +   val |= mask; >>> +   writeb(val, li2c->dir_reg); >>> + >>> +   /* Then get level state from this pin */ >>> +   val = readb(li2c->dat_reg); >>> + >>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>> + >>> +   return (val & mask) ? 1 : 0; >>> +} >>> + >>> +static void ls7a_i2c_set_sda(void *i2c, int state) >>> +{ >>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>> +   /* set state on the li2c->sda pin */ >>> +   return ls7a_gpio_i2c_set(li2c, li2c->sda, state); >>> +} >>> + >>> +static void ls7a_i2c_set_scl(void *i2c, int state) >>> +{ >>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>> +   /* set state on the li2c->scl pin */ >>> +   return ls7a_gpio_i2c_set(li2c, li2c->scl, state); >>> +} >>> + >>> +static int ls7a_i2c_get_sda(void *i2c) >>> +{ >>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>> +   /* read value from the li2c->sda pin */ >>> +   return ls7a_gpio_i2c_get(li2c, li2c->sda); >>> +} >>> + >>> +static int ls7a_i2c_get_scl(void *i2c) >>> +{ >>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>> +   /* read the value from the li2c->scl pin */ >>> +   return ls7a_gpio_i2c_get(li2c, li2c->scl); >>> +} >>> + >>> +static void lsdc_destroy_i2c(struct drm_device *ddev, void *data) >>> +{ >>> +   struct lsdc_i2c *li2c = (struct lsdc_i2c *)data; >>> + >>> +   if (li2c) { >>> +       i2c_del_adapter(&li2c->adapter); >>> +       kfree(li2c); >>> +   } >>> +} >>> + >>> +/* >>> + * The DC in ls7a1000/ls7a2000 have builtin gpio hardware >>> + * >>> + * @base: gpio reg base >>> + * @index: output channel index, 0 for DVO0, 1 for DVO1 >>> + */ >>> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >>> +                     void *base, >>> +                     unsigned int index) >>> +{ >>> +   struct i2c_adapter *adapter; >>> +   struct lsdc_i2c *li2c; >>> +   int ret; >>> + >>> +   li2c = kzalloc(sizeof(*li2c), GFP_KERNEL); >>> +   if (!li2c) >>> +       return ERR_PTR(-ENOMEM); >>> + >>> +   if (index == 0) { >>> +       li2c->sda = 0x01; /* pin 0 */ >>> +       li2c->scl = 0x02; /* pin 1 */ >>> +   } else if (index == 1) { >>> +       li2c->sda = 0x04; /* pin 2 */ >>> +       li2c->scl = 0x08; /* pin 3 */ >>> +   } >>> + >>> +   li2c->reg_base = base; >>> +   li2c->ddev = ddev; >>> +   li2c->dir_reg = li2c->reg_base + LS7A_DC_GPIO_DIR_REG; >>> +   li2c->dat_reg = li2c->reg_base + LS7A_DC_GPIO_DAT_REG; >>> + >>> +   li2c->bit.setsda = ls7a_i2c_set_sda; >>> +   li2c->bit.setscl = ls7a_i2c_set_scl; >>> +   li2c->bit.getsda = ls7a_i2c_get_sda; >>> +   li2c->bit.getscl = ls7a_i2c_get_scl; >>> +   li2c->bit.udelay = 5; >>> +   li2c->bit.timeout = usecs_to_jiffies(2200); >>> +   li2c->bit.data = li2c; >>> + >>> +   adapter = &li2c->adapter; >>> +   adapter->algo_data = &li2c->bit; >>> +   adapter->owner = THIS_MODULE; >>> +   adapter->class = I2C_CLASS_DDC; >>> +   adapter->dev.parent = ddev->dev; >>> +   adapter->nr = -1; >>> + >>> +   snprintf(adapter->name, sizeof(adapter->name), "lsdc-i2c%u", >>> index); >>> + >>> +   i2c_set_adapdata(adapter, li2c); >>> + >>> +   ret = i2c_bit_add_bus(adapter); >>> +   if (ret) { >>> +       kfree(li2c); >>> +       return ERR_PTR(ret); >>> +   } >>> + >>> +   ret = drmm_add_action_or_reset(ddev, lsdc_destroy_i2c, li2c); >>> +   if (ret) >>> +       return NULL; >>> + >>> +   drm_info(ddev, "%s(sda=%u, scl=%u) created for connector-%u\n", >>> +        adapter->name, li2c->sda, li2c->scl, index); >>> + >>> +   return li2c; >>> +} >>> + >>> +static int lsdc_get_i2c_id(struct lsdc_device *ldev, int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   if (descp->chip == CHIP_LS2K1000) >>> +       return index; >>> + >>> +   if (descp->chip == CHIP_LS2K0500) >>> +       return index + 2; >>> + >>> +   return index; >>> +} >>> + >>> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >>> +                    int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >>> + >>> +   if (descp->has_builtin_i2c) { >>> +       struct lsdc_i2c *li2c = dispipe->li2c; >>> + >>> +       if (li2c) >>> +           return &li2c->adapter; >>> +   } >>> + >>> +   return i2c_get_adapter(lsdc_get_i2c_id(ldev, index)); >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.c >>> b/drivers/gpu/drm/lsdc/lsdc_irq.c >>> new file mode 100644 >>> index 000000000000..71c85f09bb6f >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_irq.c >>> @@ -0,0 +1,86 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include <drm/drm_vblank.h> >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> + >>> +/* >>> + * For the DC in ls7a2000, clearing interrupt status is achieved by >>> + * write "1" to LSDC_INT_REG, For the DC in ls7a1000, ls2k1000 and >>> + * ls2k0500, clearing interrupt status is achieved by write "0" to >>> + * LSDC_INT_REG. Two different hardware engineer of Loongson modify >>> + * it as their will. >>> + */ >>> + >>> +/* For the DC in ls7a2000 */ >>> +static irqreturn_t lsdc_irq_handler(int irq, void *arg) >>> +{ >>> +   struct drm_device *ddev = arg; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   u32 val; >>> + >>> +   /* Read the interrupt status */ >>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>> +   if ((val & INT_STATUS_MASK) == 0) { >>> +       drm_warn(ddev, "no interrupt occurs\n"); >>> +       return IRQ_NONE; >>> +   } >>> + >>> +   ldev->irq_status = val; >>> + >>> +   /* write "1" to clear the interrupt status */ >>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >>> + >>> +   return IRQ_WAKE_THREAD; >>> +} >>> + >>> +/* For the DC in ls7a1000, ls2k1000 and ls2k0500 */ >>> +static irqreturn_t lsdc_irq_handler_legacy(int irq, void *arg) >>> +{ >>> +   struct drm_device *ddev = arg; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   u32 val; >>> + >>> +   /* Read the interrupt status */ >>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>> +   if ((val & INT_STATUS_MASK) == 0) { >>> +       drm_warn(ddev, "no interrupt occurs\n"); >>> +       return IRQ_NONE; >>> +   } >>> + >>> +   ldev->irq_status = val; >>> + >>> +   /* write "0" to clear the interrupt status */ >>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val & ~INT_STATUS_MASK); >>> + >>> +   return IRQ_WAKE_THREAD; >>> +} >>> + >>> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   if (descp->chip == CHIP_LS7A2000) >>> +       return lsdc_irq_handler; >>> + >>> +   return lsdc_irq_handler_legacy; >>> +} >>> + >>> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg) >>> +{ >>> +   struct drm_device *ddev = arg; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   struct drm_crtc *crtc; >>> + >>> +   if (ldev->irq_status & INT_CRTC0_VSYNC) { >>> +       crtc = drm_crtc_from_index(ddev, 0); >>> +       drm_crtc_handle_vblank(crtc); >>> +   } >>> + >>> +   if (ldev->irq_status & INT_CRTC1_VSYNC) { >>> +       crtc = drm_crtc_from_index(ddev, 1); >>> +       drm_crtc_handle_vblank(crtc); >>> +   } >>> + >>> +   return IRQ_HANDLED; >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_output.c >>> b/drivers/gpu/drm/lsdc/lsdc_output.c >>> new file mode 100644 >>> index 000000000000..d4bc7666d9ea >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_output.c >>> @@ -0,0 +1,452 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include <drm/drm_edid.h> >>> +#include <drm/drm_probe_helper.h> >>> +#include <drm/drm_atomic_helper.h> >>> +#include <drm/drm_connector.h> >>> +#include "lsdc_drv.h" >>> + >>> +static int lsdc_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 enum drm_connector_status >>> +lsdc_unknown_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; >>> +   } else { >>> +       if (connector->connector_type == DRM_MODE_CONNECTOR_DPI) >>> +           return connector_status_connected; >>> + >>> +       if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) >>> +           return connector_status_connected; >>> +   } >>> + >>> +   return connector_status_unknown; >>> +} >>> + >>> +static enum drm_connector_status >>> +lsdc_hdmi_connector_detect(struct drm_connector *connector, bool >>> force) >>> +{ >>> +   struct lsdc_display_pipe *pipe = >>> connector_to_display_pipe(connector); >>> +   struct lsdc_device *ldev = to_lsdc(connector->dev); >>> +   u32 val; >>> + >>> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >>> + >>> +   if (pipe->index == 0) { >>> +       if (val & HDMI0_HPD_FLAG) >>> +           return connector_status_connected; >>> +   } >>> + >>> +   if (pipe->index == 1) { >>> +       if (val & HDMI1_HPD_FLAG) >>> +           return connector_status_connected; >>> +   } >>> + >>> +   return connector_status_disconnected; >>> +} >>> + >>> +static enum drm_connector_status >>> +lsdc_hdmi_vga_connector_detect(struct drm_connector *connector, >>> bool force) >>> +{ >>> +   struct lsdc_display_pipe *pipe = >>> connector_to_display_pipe(connector); >>> +   struct drm_device *ddev = connector->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   struct i2c_adapter *ddc; >>> +   u32 val; >>> + >>> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >>> + >>> +   if (pipe->index == 1) { >>> +       if (val & HDMI1_HPD_FLAG) >>> +           return connector_status_connected; >>> + >>> +       return connector_status_disconnected; >>> +   } >>> + >>> +   if (pipe->index == 0) { >>> +       if (val & HDMI0_HPD_FLAG) >>> +           return connector_status_connected; >>> + >>> +       ddc = connector->ddc; >>> +       if (ddc) { >>> +           if (drm_probe_ddc(ddc)) >>> +               return connector_status_connected; >>> + >>> +           return connector_status_disconnected; >>> +       } >>> +   } >>> + >>> +   return connector_status_unknown; >>> +} >>> + >>> +static struct drm_encoder * >>> +lsdc_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 >>> lsdc_connector_helpers = { >>> +   .atomic_best_encoder = lsdc_connector_get_best_encoder, >>> +   .get_modes = lsdc_get_modes, >>> +}; >>> + >>> +static const struct drm_connector_funcs >>> lsdc_unknown_connector_funcs = { >>> +   .detect = lsdc_unknown_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 const struct drm_connector_funcs >>> lsdc_hdmi_vga_connector_funcs = { >>> +   .detect = lsdc_hdmi_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, >>> +}; >>> + >>> +static const struct drm_connector_funcs lsdc_hdmi_connector_funcs = { >>> +   .detect = lsdc_hdmi_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 const struct drm_connector_funcs * >>> +lsdc_get_connector_func(struct lsdc_device *ldev, unsigned int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   if (descp->chip == CHIP_LS7A2000) { >>> +       if (index == 0) >>> +           return &lsdc_hdmi_vga_connector_funcs; >>> + >>> +       if (index == 1) >>> +           return &lsdc_hdmi_connector_funcs; >>> +   } >>> + >>> +   return &lsdc_unknown_connector_funcs; >>> +} >>> + >>> +/* >>> + * we provide a default support before DT/VBIOS is supported >>> + */ >>> +static int lsdc_get_encoder_type(struct lsdc_device *ldev, >>> +                unsigned int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   if (descp->chip == CHIP_LS7A2000) { >>> +       if (index == 0) >>> +           return DRM_MODE_ENCODER_DAC; >>> +       if (index == 1) >>> +           return DRM_MODE_ENCODER_TMDS; >>> +   } >>> + >>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>> CHIP_LS2K1000) { >>> +       if (index == 0) >>> +           return DRM_MODE_ENCODER_DPI; >>> +       if (index == 1) >>> +           return DRM_MODE_ENCODER_DPI; >>> +   } >>> + >>> +   if (descp->chip == CHIP_LS2K0500) { >>> +       if (index == 0) >>> +           return DRM_MODE_ENCODER_DPI; >>> +       if (index == 1) >>> +           return DRM_MODE_ENCODER_DAC; >>> +   } >>> + >>> +   return DRM_MODE_ENCODER_NONE; >>> +} >>> + >>> +/* >>> + * provide a default before DT/VBIOS support is upstreamed >>> + */ >>> +static int lsdc_get_connector_type(struct lsdc_device *ldev, >>> +                  unsigned int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   if (descp->chip == CHIP_LS7A2000) { >>> +       if (index == 0) >>> +           return DRM_MODE_CONNECTOR_VGA; >>> +       if (index == 1) >>> +           return DRM_MODE_CONNECTOR_HDMIA; >>> +   } >>> + >>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>> CHIP_LS2K1000) { >>> +       if (index == 0) >>> +           return DRM_MODE_CONNECTOR_DPI; >>> +       if (index == 1) >>> +           return DRM_MODE_CONNECTOR_DPI; >>> +   } >>> + >>> +   if (descp->chip == CHIP_LS2K0500) { >>> +       if (index == 0) >>> +           return DRM_MODE_CONNECTOR_DPI; >>> +       if (index == 1) >>> +           return DRM_MODE_CONNECTOR_VGA; >>> +   } >>> + >>> +   return DRM_MODE_CONNECTOR_Unknown; >>> +} >>> + >>> +static void lsdc_hdmi_disable(struct drm_encoder *encoder) >>> +{ >>> +   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; >>> + >>> +   if (index == 0) >>> +       val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG); >>> +   else if (index == 1) >>> +       val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG); >>> + >>> +   val &= ~HDMI_PHY_EN; >>> + >>> +   if (index == 0) >>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >>> +   else if (index == 1) >>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >>> + >>> +   drm_dbg(ddev, "HDMI-%u disabled\n", index); >>> +} >>> + >>> +static void lsdc_hdmi_enable(struct drm_encoder *encoder) >>> +{ >>> +   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; >>> + >>> +   /* we are using software gpio emulated i2c */ >>> +   val = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >>> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >>> + >>> +   if (index == 0) { >>> +       /* Enable HDMI-0 */ >>> +       lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >>> + >>> +       lsdc_wreg32(ldev, LSDC_HDMI0_ZONE_REG, 0x00400040); >>> +   } else if (index == 1) { >>> +       /* Enable HDMI-1 */ >>> +       lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >>> + >>> +       lsdc_wreg32(ldev, LSDC_HDMI1_ZONE_REG, 0x00400040); >>> +   } >>> + >>> +   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; >>> + >>> +   if (index == 0) >>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >>> +   else if (index == 1) >>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >>> + >>> +   drm_dbg(ddev, "HDMI-%u enabled\n", index); >>> +} >>> + >>> +/* >>> + * Fout = M * Fin >>> + * >>> + * M = (4 * LF) / (IDF * ODF) >>> + * >>> + * Loongson HDMI require M = 10 >>> + */ >>> +static void lsdc_hdmi_phy_pll_config(struct lsdc_device *ldev, >>> +                    int index, >>> +                    int clock) >>> +{ >>> +   struct drm_device *ddev = &ldev->base; >>> +   int count = 0; >>> +   u32 val; >>> + >>> +   /* disable phy pll */ >>> +   if (index == 0) >>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, 0x0); >>> +   else if (index == 1) >>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, 0x0); >>> + >>> +   /* >>> +    * 10 = (4 * 40) / (8 * 2) >>> +    */ >>> +   val = (8 << HDMI_PLL_IDF_SHIFT) | >>> +         (40 << HDMI_PLL_LF_SHIFT) | >>> +         (1 << HDMI_PLL_ODF_SHIFT) | >>> +         HDMI_PLL_ENABLE; >>> + >>> +   drm_dbg(ddev, "HDMI-%u clock: %d\n", index, clock); >>> + >>> +   if (index == 0) >>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, val); >>> +   else if (index == 1) >>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, val); >>> + >>> +   /* wait hdmi phy pll lock */ >>> +   do { >>> +       if (index == 0) >>> +           val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG); >>> +       else if (index == 1) >>> +           val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_PLL_REG); >>> + >>> +       ++count; >>> + >>> +       if (val & HDMI_PLL_LOCKED) { >>> +           drm_dbg(ddev, "Setting HDMI-%u PLL success(take %d >>> cycles)\n", >>> +               index, count); >>> +           break; >>> +       } >>> +   } while (count < 1000); >>> +} >>> + >>> +static void lsdc_hdmi_atomic_mode_set(struct drm_encoder *encoder, >>> +                     struct drm_crtc_state *crtc_state, >>> +                     struct drm_connector_state *conn_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; >>> +   struct drm_display_mode *mode = &crtc_state->mode; >>> + >>> +   lsdc_hdmi_phy_pll_config(ldev, index, mode->clock); >>> + >>> +   drm_dbg(ddev, "HDMI-%u modeset\n", index); >>> +} >>> + >>> +static const struct drm_encoder_helper_funcs lsdc_hdmi_helper_funcs >>> = { >>> +   .disable = lsdc_hdmi_disable, >>> +   .enable = lsdc_hdmi_enable, >>> +   .atomic_mode_set = lsdc_hdmi_atomic_mode_set, >>> +}; >>> + >>> +static void lsdc_hdmi_reset(struct drm_encoder *encoder) >>> +{ >>> +   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 = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >>> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >>> + >>> +   lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >>> +   lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >>> + >>> +   drm_dbg(ddev, "HDMI-%u Reset\n", index); >>> +} >>> + >>> +static const struct drm_encoder_funcs lsdc_encoder_funcs = { >>> +   .reset = lsdc_hdmi_reset, >>> +   .destroy = drm_encoder_cleanup, >>> +}; >>> + >>> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> +   struct drm_device *ddev = &ldev->base; >>> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >>> +   struct drm_encoder *encoder = &dispipe->encoder; >>> +   struct drm_connector *connector = &dispipe->connector; >>> +   struct i2c_adapter *ddc = NULL; >>> +   struct lsdc_i2c *li2c; >>> +   int ret; >>> + >>> +   ret = drm_encoder_init(ddev, >>> +                  encoder, >>> +                  &lsdc_encoder_funcs, >>> +                  lsdc_get_encoder_type(ldev, index), >>> +                  "encoder-%u", >>> +                  index); >>> +   if (ret) { >>> +       drm_err(ddev, "Failed to init encoder: %d\n", ret); >>> +       return ret; >>> +   } >>> + >>> +   encoder->possible_crtcs = BIT(index); >>> + >>> +   if (descp->chip == CHIP_LS7A2000) >>> +       drm_encoder_helper_add(encoder, &lsdc_hdmi_helper_funcs); >>> + >>> +   if (descp->has_builtin_i2c) { >>> +       li2c = lsdc_create_i2c_chan(ddev, ldev->reg_base, index); >>> +       if (IS_ERR(li2c)) >>> +           return PTR_ERR(li2c); >>> + >>> +       dispipe->li2c = li2c; >>> + >>> +       ddc = &li2c->adapter; >>> +   } else { >>> +       ddc = lsdc_get_i2c_adapter(ldev, index); >>> +       if (IS_ERR(ddc)) { >>> +           drm_err(ddev, "Error get ddc for output-%u\n", index); >>> +           return PTR_ERR(ddc); >>> +       } >>> +   } >>> + >>> +   ret = drm_connector_init_with_ddc(ddev, >>> +                     connector, >>> +                     lsdc_get_connector_func(ldev, index), >>> +                     lsdc_get_connector_type(ldev, index), >>> +                     ddc); >>> +   if (ret) { >>> +       drm_err(ddev, "Init connector-%d failed\n", index); >>> +       return ret; >>> +   } >>> + >>> +   drm_connector_helper_add(connector, &lsdc_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; >>> + >>> +   ldev->num_output++; >>> + >>> +   drm_info(ddev, "output-%u initialized\n", index); >>> + >>> +   return 0; >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_plane.c >>> b/drivers/gpu/drm/lsdc/lsdc_plane.c >>> new file mode 100644 >>> index 000000000000..0f779c97d53b >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_plane.c >>> @@ -0,0 +1,443 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include <drm/drm_atomic.h> >>> +#include <drm/drm_atomic_helper.h> >>> +#include <drm/drm_framebuffer.h> >>> +#include <drm/drm_plane_helper.h> >>> +#include <drm/drm_gem_vram_helper.h> >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> +#include "lsdc_pll.h" >>> + >>> +static const u32 lsdc_primary_formats[] = { >>> +   DRM_FORMAT_XRGB8888, >>> +   DRM_FORMAT_ARGB8888, >>> +}; >>> + >>> +static const u32 lsdc_cursor_formats[] = { >>> +   DRM_FORMAT_ARGB8888, >>> +}; >>> + >>> +static const u64 lsdc_fb_format_modifiers[] = { >>> +   DRM_FORMAT_MOD_LINEAR, >>> +   DRM_FORMAT_MOD_INVALID >>> +}; >>> + >>> +static void lsdc_update_fb_format(struct lsdc_device *ldev, >>> +                 struct drm_crtc *crtc, >>> +                 const struct drm_format_info *fmt_info) >>> +{ >>> +   unsigned int index = drm_crtc_index(crtc); >>> +   u32 val; >>> +   u32 fmt; >>> + >>> +   switch (fmt_info->format) { >>> +   case DRM_FORMAT_XRGB8888: >>> +       fmt = LSDC_PF_XRGB8888; >>> +       break; >>> +   case DRM_FORMAT_ARGB8888: >>> +       fmt = LSDC_PF_XRGB8888; >>> +       break; >>> +   default: >>> +       fmt = LSDC_PF_XRGB8888; >>> +       break; >>> +   } >>> + >>> +   if (index == 0) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>> +   } else if (index == 1) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>> +   } >>> +} >>> + >>> +static void lsdc_update_fb_start_addr(struct lsdc_device *ldev, >>> +                     struct drm_crtc *crtc, >>> +                     u64 paddr) >>> +{ >>> +   struct drm_device *ddev = &ldev->base; >>> +   unsigned int index = drm_crtc_index(crtc); >>> +   u32 lo_addr_reg; >>> +   u32 hi_addr_reg; >>> +   u32 val; >>> + >>> +   /* >>> +    * Find which framebuffer address register should update. >>> +    * if FB_ADDR0_REG is in using, we write the address to >>> FB_ADDR0_REG, >>> +    * if FB_ADDR1_REG is in using, we write the address to >>> FB_ADDR1_REG >>> +    * for each CRTC, the switch using one fb register to another is >>> +    * trigger by triggered by set CFG_PAGE_FLIP bit of >>> LSDC_CRTCx_CFG_REG >>> +    */ >>> +   if (index == 0) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>> +       if (val & CFG_FB_IN_USING) { >>> +           lo_addr_reg = LSDC_CRTC0_FB1_LO_ADDR_REG; >>> +           hi_addr_reg = LSDC_CRTC0_FB1_HI_ADDR_REG; >>> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-0\n"); >>> +       } else { >>> +           lo_addr_reg = LSDC_CRTC0_FB0_LO_ADDR_REG; >>> +           hi_addr_reg = LSDC_CRTC0_FB0_HI_ADDR_REG; >>> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-0\n"); >>> +       } >>> +   } else if (index == 1) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>> +       if (val & CFG_FB_IN_USING) { >>> +           lo_addr_reg = LSDC_CRTC1_FB1_LO_ADDR_REG; >>> +           hi_addr_reg = LSDC_CRTC1_FB1_HI_ADDR_REG; >>> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-1\n"); >>> +       } else { >>> +           lo_addr_reg = LSDC_CRTC1_FB0_LO_ADDR_REG; >>> +           hi_addr_reg = LSDC_CRTC1_FB0_HI_ADDR_REG; >>> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-1\n"); >>> +       } >>> +   } >>> + >>> +   /* 40-bit width physical address bus */ >>> +   lsdc_wreg32(ldev, lo_addr_reg, paddr); >>> +   lsdc_wreg32(ldev, hi_addr_reg, (paddr >> 32) & 0xFF); >>> + >>> +   drm_dbg(ddev, "CRTC-%u scantout from 0x%llx\n", index, paddr); >>> +} >>> + >>> +static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb, >>> +                      struct drm_plane_state *state, >>> +                      unsigned int plane) >>> +{ >>> +   unsigned int offset = fb->offsets[plane]; >>> + >>> +   offset += fb->format->cpp[plane] * (state->src_x >> 16); >>> +   offset += fb->pitches[plane] * (state->src_y >> 16); >>> + >>> +   return offset; >>> +} >>> + >>> +static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb) >>> +{ >>> +   struct drm_gem_vram_object *gbo; >>> +   s64 gpu_addr; >>> + >>> +   gbo = drm_gem_vram_of_gem(fb->obj[0]); >>> +   gpu_addr = drm_gem_vram_offset(gbo); >>> + >>> +   return gpu_addr; >>> +} >>> + >>> +static int lsdc_primary_plane_atomic_check(struct drm_plane *plane, >>> +                      struct drm_atomic_state *state) >>> +{ >>> +   struct drm_plane_state *plane_state = >>> drm_atomic_get_new_plane_state(state, plane); >>> +   struct drm_crtc *crtc = plane_state->crtc; >>> +   struct drm_crtc_state *crtc_state = >>> drm_atomic_get_new_crtc_state(state, crtc); >>> + >>> +   if (!crtc) >>> +       return 0; >>> + >>> +   return drm_atomic_helper_check_plane_state(plane_state, >>> +                          crtc_state, >>> +                          DRM_PLANE_NO_SCALING, >>> +                          DRM_PLANE_NO_SCALING, >>> +                          false, >>> +                          true); >>> +} >>> + >>> +static void lsdc_update_fb_stride(struct lsdc_device *ldev, >>> +                 struct drm_crtc *crtc, >>> +                 unsigned int stride) >>> +{ >>> +   unsigned int index = drm_crtc_index(crtc); >>> + >>> +   if (index == 0) >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_STRIDE_REG, stride); >>> +   else if (index == 1) >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_STRIDE_REG, stride); >>> + >>> +   drm_dbg(crtc->dev, "update stride to %u\n", stride); >>> +} >>> + >>> +static void lsdc_primary_plane_atomic_update(struct drm_plane *plane, >>> +                        struct drm_atomic_state *state) >>> +{ >>> +   struct lsdc_device *ldev = to_lsdc(plane->dev); >>> +   struct drm_plane_state *new_plane_state = >>> drm_atomic_get_new_plane_state(state, plane); >>> +   struct drm_crtc *crtc = new_plane_state->crtc; >>> +   struct drm_framebuffer *fb = new_plane_state->fb; >>> +   u32 fb_offset = lsdc_get_fb_offset(fb, new_plane_state, 0); >>> +   dma_addr_t fb_addr; >>> +   s64 gpu_addr; >>> + >>> +   gpu_addr = lsdc_get_vram_bo_offset(fb); >>> +   if (gpu_addr < 0) >>> +       return; >>> + >>> +   fb_addr = ldev->vram_base + gpu_addr + fb_offset; >>> + >>> +   lsdc_update_fb_start_addr(ldev, crtc, fb_addr); >>> + >>> +   lsdc_update_fb_stride(ldev, crtc, fb->pitches[0]); >>> + >>> +   lsdc_update_fb_format(ldev, crtc, fb->format); >>> +} >>> + >>> +static void lsdc_primary_plane_atomic_disable(struct drm_plane *plane, >>> +                         struct drm_atomic_state *state) >>> +{ >>> +   drm_dbg(plane->dev, "%s disabled\n", plane->name); >>> +} >>> + >>> +static const struct drm_plane_helper_funcs >>> lsdc_primary_plane_helpers = { >>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>> +   .atomic_check = lsdc_primary_plane_atomic_check, >>> +   .atomic_update = lsdc_primary_plane_atomic_update, >>> +   .atomic_disable = lsdc_primary_plane_atomic_disable, >>> +}; >>> + >>> +static int lsdc_cursor_atomic_check(struct drm_plane *plane, >>> +                   struct drm_atomic_state *state) >>> +{ >>> +   struct drm_plane_state *new_plane_state = >>> drm_atomic_get_new_plane_state(state, plane); >>> +   struct drm_framebuffer *fb = new_plane_state->fb; >>> +   struct drm_crtc *crtc = new_plane_state->crtc; >>> +   struct drm_crtc_state *crtc_state; >>> +   int ret; >>> + >>> +   /* no need for further checks if the plane is being disabled */ >>> +   if (!crtc || !fb) >>> +       return 0; >>> + >>> +   if (!new_plane_state->visible) >>> +       return 0; >>> + >>> +   crtc_state = drm_atomic_get_new_crtc_state(state, >>> +                          new_plane_state->crtc); >>> + >>> +   ret = drm_atomic_helper_check_plane_state(new_plane_state, >>> +                         crtc_state, >>> +                         DRM_PLANE_NO_SCALING, >>> +                         DRM_PLANE_NO_SCALING, >>> +                         true, >>> +                         true); >>> + >>> +   return ret; >>> +} >>> + >>> +/* >>> + * There is only one hardware cursor in ls7a1000, ls2k1000 and >>> ls2k0500. >>> + * we made it shared by the two CRTC, which can satisfy peoples who >>> use >>> + * double screen extend mode only. On clone screen usage case, the >>> cursor >>> + * on display pipe 1 will not be able to display. >>> + * >>> + * Update location of the cursor, attach it to CRTC0 or CRTC1 on >>> the runtime. >>> + */ >>> +static void lsdc_cursor_update_location_quirks(struct lsdc_device >>> *ldev, >>> +                          struct drm_crtc *crtc) >>> +{ >>> +   u32 val = CURSOR_FORMAT_ARGB8888; >>> + >>> +   /* >>> +    * If bit 4 of LSDC_CURSOR0_CFG_REG is 1, then the cursor will be >>> +    * locate at CRTC-1, if bit 4 of LSDC_CURSOR0_CFG_REG is 0, then >>> +    * the cursor will be locate at CRTC-0. The cursor is alway on the >>> +    * top of the primary. Compositing the primary plane and cursor >>> +    * plane is automatically done by hardware. >>> +    */ >>> +   if (drm_crtc_index(crtc)) >>> +       val |= CURSOR_LOCATION; >>> + >>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, val); >>> +} >>> + >>> +/* update the position of the cursor */ >>> +static void lsdc_cursor_update_position_quirks(struct lsdc_device >>> *ldev, >>> +                          int x, >>> +                          int y) >>> +{ >>> +   if (x < 0) >>> +       x = 0; >>> + >>> +   if (y < 0) >>> +       y = 0; >>> + >>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >>> +} >>> + >>> +static void lsdc_cursor_atomic_update_quirks(struct drm_plane *plane, >>> +                        struct drm_atomic_state *state) >>> +{ >>> +   struct drm_device *ddev = plane->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   struct drm_plane_state *new_plane_state = >>> drm_atomic_get_new_plane_state(state, plane); >>> +   struct drm_plane_state *old_plane_state = >>> drm_atomic_get_old_plane_state(state, plane); >>> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >>> +   struct drm_framebuffer *old_fb = old_plane_state->fb; >>> + >>> +   if (new_fb != old_fb) { >>> +       s64 offset = lsdc_get_vram_bo_offset(new_fb); >>> +       u64 cursor_addr = ldev->vram_base + offset; >>> + >>> +       drm_dbg_kms(ddev, "%s offset: %llx\n", plane->name, offset); >>> + >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >> >>> 32) & 0xFF); >>> +   } >>> + >>> +   lsdc_cursor_update_position_quirks(ldev, >>> new_plane_state->crtc_x, new_plane_state->crtc_y); >>> + >>> +   lsdc_cursor_update_location_quirks(ldev, new_plane_state->crtc); >>> +} >>> + >>> +/* update the format, size and location of the cursor */ >>> +static void lsdc_cursor_atomic_update(struct drm_plane *plane, >>> +                     struct drm_atomic_state *state) >>> +{ >>> +   struct drm_device *ddev = plane->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   struct lsdc_display_pipe *dispipe = cursor_to_display_pipe(plane); >>> +   struct drm_plane_state *new_plane_state = >>> drm_atomic_get_new_plane_state(state, plane); >>> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >>> +   int x = new_plane_state->crtc_x; >>> +   int y = new_plane_state->crtc_y; >>> +   u32 conf = CURSOR_FORMAT_ARGB8888 | CURSOR_SIZE_64X64; >>> +   u64 cursor_addr = ldev->vram_base + >>> lsdc_get_vram_bo_offset(new_fb); >>> + >>> +   if (x < 0) >>> +       x = 0; >>> + >>> +   if (y < 0) >>> +       y = 0; >>> + >>> +   if (dispipe->index == 0) { >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >> >>> 32) & 0xFF); >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >>> +       /* Attach Cursor-0 to CRTC-0 */ >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, conf & >>> ~CURSOR_LOCATION); >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >>> +       return; >>> +   } >>> + >>> +   if (dispipe->index == 1) { >>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_HI_REG, (cursor_addr >> >>> 32) & 0xFF); >>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_LO_REG, cursor_addr); >>> +       /* Attach Cursor-1 to CRTC-1 */ >>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, conf | >>> CURSOR_LOCATION); >>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_POSITION_REG, (y << 16) | x); >>> +       return; >>> +   } >>> +} >>> + >>> +static void lsdc_cursor_atomic_disable_quirks(struct drm_plane *plane, >>> +                         struct drm_atomic_state *state) >>> +{ >>> +   struct drm_device *ddev = plane->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> + >>> +   /* Set the format to 0 actually display the cursor */ >>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >>> + >>> +   drm_dbg(ddev, "%s disabled\n", plane->name); >>> +} >>> + >>> +static void lsdc_cursor_atomic_disable(struct drm_plane *plane, >>> +                      struct drm_atomic_state *state) >>> +{ >>> +   struct drm_device *ddev = plane->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   struct lsdc_display_pipe *dispipe = cursor_to_display_pipe(plane); >>> + >>> +   if (dispipe->index == 0) >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >>> +   else if (dispipe->index == 1) >>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, 0); >>> + >>> +   drm_dbg(ddev, "%s disabled\n", plane->name); >>> +} >>> + >>> +static const struct drm_plane_helper_funcs >>> lsdc_cursor_helpers_quirk = { >>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>> +   .atomic_check = lsdc_cursor_atomic_check, >>> +   .atomic_update = lsdc_cursor_atomic_update_quirks, >>> +   .atomic_disable = lsdc_cursor_atomic_disable_quirks, >>> +}; >>> + >>> +static const struct drm_plane_helper_funcs >>> lsdc_cursor_plane_helpers = { >>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>> +   .atomic_check = lsdc_cursor_atomic_check, >>> +   .atomic_update = lsdc_cursor_atomic_update, >>> +   .atomic_disable = lsdc_cursor_atomic_disable, >>> +}; >>> + >>> +static const struct drm_plane_funcs lsdc_plane_funcs = { >>> +   .update_plane = drm_atomic_helper_update_plane, >>> +   .disable_plane = drm_atomic_helper_disable_plane, >>> +   .destroy = drm_plane_cleanup, >>> +   .reset = drm_atomic_helper_plane_reset, >>> +   .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, >>> +   .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, >>> +}; >>> + >>> +static const struct drm_plane_helper_funcs * >>> +lsdc_get_cursor_helper_funcs(const struct lsdc_desc *descp) >>> +{ >>> +   if (descp->chip == CHIP_LS7A2000) >>> +       return &lsdc_cursor_plane_helpers; >>> + >>> +   return &lsdc_cursor_helpers_quirk; >>> +} >>> + >>> +int lsdc_plane_init(struct lsdc_device *ldev, >>> +           struct drm_plane *plane, >>> +           enum drm_plane_type type, >>> +           unsigned int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> +   struct drm_device *ddev = &ldev->base; >>> +   unsigned int format_count; >>> +   const u32 *formats; >>> +   const char *name; >>> +   int ret; >>> + >>> +   switch (type) { >>> +   case DRM_PLANE_TYPE_PRIMARY: >>> +       formats = lsdc_primary_formats; >>> +       format_count = ARRAY_SIZE(lsdc_primary_formats); >>> +       name = "primary-%u"; >>> +       break; >>> +   case DRM_PLANE_TYPE_CURSOR: >>> +       formats = lsdc_cursor_formats; >>> +       format_count = ARRAY_SIZE(lsdc_cursor_formats); >>> +       name = "cursor-%u"; >>> +       break; >>> +   case DRM_PLANE_TYPE_OVERLAY: >>> +       drm_err(ddev, "overlay plane is not supported\n"); >>> +       break; >>> +   } >>> + >>> +   ret = drm_universal_plane_init(ddev, plane, 1 << index, >>> +                      &lsdc_plane_funcs, >>> +                      formats, format_count, >>> +                      lsdc_fb_format_modifiers, >>> +                      type, name, index); >>> +   if (ret) { >>> +       drm_err(ddev, "%s failed: %d\n", __func__, ret); >>> +       return ret; >>> +   } >>> + >>> +   switch (type) { >>> +   case DRM_PLANE_TYPE_PRIMARY: >>> +       drm_plane_helper_add(plane, &lsdc_primary_plane_helpers); >>> +       break; >>> +   case DRM_PLANE_TYPE_CURSOR: >>> +       drm_plane_helper_add(plane, >>> lsdc_get_cursor_helper_funcs(descp)); >>> +       break; >>> +   case DRM_PLANE_TYPE_OVERLAY: >>> +       drm_err(ddev, "overlay plane is not supported\n"); >>> +       break; >>> +   } >>> + >>> +   return 0; >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.c >>> b/drivers/gpu/drm/lsdc/lsdc_pll.c >>> new file mode 100644 >>> index 000000000000..6ed74989a6f5 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.c >>> @@ -0,0 +1,569 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> +#include "lsdc_pll.h" >>> + >>> +/* >>> + * The structure of the pixel PLL register is evolved with times. >>> + * All loongson's cpu is little endian. >>> + */ >>> + >>> +/* u64 */ >>> +struct ls7a1000_pixpll_bitmap { >>> +   /* Byte 0 ~ Byte 3 */ >>> +   unsigned div_out     : 7;  /* 0 : 6    output clock >>> divider */ >>> +   unsigned __1         : 14; /* 7 : >>> 20                         */ >>> +   unsigned loopc       : 9;  /* 21 : 29   clock >>> multiplier     */ >>> +   unsigned __2         : 2;  /* 30 : >>> 31                         */ >>> + >>> +   /* Byte 4 ~ Byte 7 */ >>> +   unsigned div_ref     : 7;  /* 0 : 6    input clock >>> divider  */ >>> +   unsigned locked      : 1;  /* 7        PLL locked >>> status    */ >>> +   unsigned sel_out     : 1;  /* 8        output clk >>> selector  */ >>> +   unsigned __3         : 2;  /* 9 : 10 reserved             */ >>> +   unsigned set_param   : 1;  /* 11       trigger the >>> update   */ >>> +   unsigned bypass      : 1;  /* 12                             */ >>> +   unsigned powerdown   : 1;  /* 13                             */ >>> +   unsigned __4         : 18; /* 14 : >>> 31                        */ >>> +}; >>> + >>> +/* u128 */ >>> +struct ls2k1000_pixpll_bitmap { >>> +   /* Byte 0 ~ Byte 3 */ >>> +   unsigned sel_out     : 1; /* 0     select this >>> PLL         */ >>> +   unsigned __1         : 1; /* 1                              */ >>> +   unsigned sw_adj_en   : 1; /* 2     allow software >>> adjust   */ >>> +   unsigned bypass      : 1; /* 3     bypass L1 >>> PLL           */ >>> +   unsigned __2         : 3; /* 4:6                            */ >>> +   unsigned lock_en     : 1; /* 7     enable lock L1 >>> PLL      */ >>> +   unsigned __3         : 2; /* 8:9                            */ >>> +   unsigned lock_check  : 2; /* 10:11  precision >>> check         */ >>> +   unsigned __4         : 4; /* >>> 12:15                           */ >>> + >>> +   unsigned locked      : 1; /* 16     PLL locked flag >>> bit     */ >>> +   unsigned __5         : 2; /* >>> 17:18                           */ >>> +   unsigned powerdown   : 1; /* 19     powerdown the pll if >>> set */ >>> +   unsigned __6         : 6; /* >>> 20:25                           */ >>> +   unsigned div_ref     : 6; /* 26:31  L1 >>> Prescaler            */ >>> + >>> +   /* Byte 4 ~ Byte 7 */ >>> +   unsigned loopc       : 10; /* 32:41  Clock >>> Multiplier        */ >>> +   unsigned l1_div      : 6; /* 42:47  not >>> used                */ >>> +   unsigned __7         : 16; /* >>> 48:63                           */ >>> + >>> +   /* Byte 8 ~ Byte 15 */ >>> +   unsigned div_out     : 6; /* 0 : 5  output clock >>> divider    */ >>> +   unsigned __8         : 26; /* 6 : >>> 31                          */ >>> +   unsigned __9         : 32; /* 70: >>> 127                         */ >>> +}; >>> + >>> +/* u32 */ >>> +struct ls2k0500_pixpll_bitmap { >>> +   /* Byte 0 ~ Byte 1 */ >>> +   unsigned sel_out     : 1; >>> +   unsigned __1         : 2; >>> +   unsigned sw_adj_en   : 1;  /* allow software >>> adjust             */ >>> +   unsigned bypass      : 1;  /* bypass L1 >>> PLL                     */ >>> +   unsigned powerdown   : 1;  /* write 1 to powerdown the >>> PLL      */ >>> +   unsigned __2         : 1; >>> +   unsigned locked      : 1;  /* 7    Is L1 PLL locked, read >>> only */ >>> +   unsigned div_ref     : 6;  /* 8:13 ref clock >>> divider          */ >>> +   unsigned __3         : 2;  /* >>> 14:15                             */ >>> +   /* Byte 2 ~ Byte 3 */ >>> +   unsigned loopc       : 8;  /* 16:23  Clock >>> Multiplier          */ >>> +   unsigned div_out     : 6;  /* 24:29  output clock >>> divider      */ >>> +   unsigned __4         : 2;  /* >>> 30:31                             */ >>> +}; >>> + >>> +union lsdc_pixpll_bitmap { >>> +   struct ls7a1000_pixpll_bitmap ls7a1000; >>> +   struct ls2k1000_pixpll_bitmap ls2k1000; >>> +   struct ls2k0500_pixpll_bitmap ls2k0500; >>> + >>> +   u32 dword[4]; >>> +}; >>> + >>> +struct pixclk_to_pll_parm { >>> +   /* kHz */ >>> +   unsigned int clock; >>> + >>> +   unsigned short width; >>> +   unsigned short height; >>> +   unsigned short vrefresh; >>> + >>> +   /* Stores parameters for programming the Hardware PLLs */ >>> +   unsigned short div_out; >>> +   unsigned short loopc; >>> +   unsigned short div_ref; >>> +}; >>> + >>> +/* >>> + * Pixel clock to PLL parameters translation table. >>> + * Small static cached value to speed up PLL parameters calculation. >>> + */ >>> +static const struct pixclk_to_pll_parm pll_param_table[] = { >>> +   {148500, 1920, 1080, 60, 11, 49, 3},  /* 1920x1080@60Hz */ >>> +                       /* 1920x1080@50Hz */ >>> +   {174500, 1920, 1080, 75, 17, 89, 3},  /* 1920x1080@75Hz */ >>> +   {181250, 2560, 1080, 75, 8, 58, 4},  /* 2560x1080@75Hz */ >>> +   {146250, 1680, 1050, 60, 16, 117, 5},  /* 1680x1050@60Hz */ >>> +   {135000, 1280, 1024, 75, 10, 54, 4},  /* 1280x1024@75Hz */ >>> + >>> +   {108000, 1600, 900, 60, 15, 81, 5},  /* 1600x900@60Hz */ >>> +                       /* 1280x1024@60Hz */ >>> +                       /* 1280x960@60Hz */ >>> +                       /* 1152x864@75Hz */ >>> + >>> +   {106500, 1440, 900, 60, 19, 81, 4},  /* 1440x900@60Hz */ >>> +   {88750, 1440, 900, 60, 16, 71, 5},  /* 1440x900@60Hz */ >>> +   {83500, 1280, 800, 60, 17, 71, 5},  /* 1280x800@60Hz */ >>> +   {71000, 1280, 800, 60, 20, 71, 5},  /* 1280x800@60Hz */ >>> + >>> +   {74250, 1280, 720, 60, 22, 49, 3},  /* 1280x720@60Hz */ >>> +                       /* 1280x720@50Hz */ >>> + >>> +   {78750, 1024, 768, 75, 16, 63, 5},  /* 1024x768@75Hz */ >>> +   {75000, 1024, 768, 70, 29, 87, 4},  /* 1024x768@70Hz */ >>> +   {65000, 1024, 768, 60, 20, 39, 3},  /* 1024x768@60Hz */ >>> + >>> +   {51200, 1024, 600, 60, 25, 64, 5},  /* 1024x600@60Hz */ >>> + >>> +   {57284, 832, 624, 75, 24, 55, 4},  /* 832x624@75Hz */ >>> +   {49500, 800, 600, 75, 40, 99, 5},  /* 800x600@75Hz */ >>> +   {50000, 800, 600, 72, 44, 88, 4},  /* 800x600@72Hz */ >>> +   {40000, 800, 600, 60, 30, 36, 3},  /* 800x600@60Hz */ >>> +   {36000, 800, 600, 56, 50, 72, 4},  /* 800x600@56Hz */ >>> +   {31500, 640, 480, 75, 40, 63, 5},  /* 640x480@75Hz */ >>> +                       /* 640x480@73Hz */ >>> + >>> +   {30240, 640, 480, 67, 62, 75, 4},  /* 640x480@67Hz */ >>> +   {27000, 720, 576, 50, 50, 54, 4},  /* 720x576@60Hz */ >>> +   {25175, 640, 480, 60, 85, 107, 5},  /* 640x480@60Hz */ >>> +   {25200, 640, 480, 60, 50, 63, 5},  /* 640x480@60Hz */ >>> +                       /* 720x480@60Hz */ >>> +}; >>> + >>> +/* >>> + * lsdc_pixpll_setup - ioremap the device dependent PLL registers >>> + * >>> + * @this: point to the object where this function is called from >>> + */ >>> +static int lsdc_pixpll_setup(struct lsdc_pll * const this) >>> +{ >>> +   this->mmio = ioremap(this->reg_base, this->reg_size); >>> + >>> +   return 0; >>> +} >>> + >>> +/* >>> + * Find a set of pll parameters (to generate pixel clock) from a >>> static >>> + * local table, which avoid to compute the pll parameter eachtime a >>> + * modeset is triggered. >>> + * >>> + * @this: point to the object which this function is called from >>> + * @clock: the desired output pixel clock, the unit is kHz >>> + * @pout: point to where the parameters to store if found >>> + * >>> + * Return true if hit, otherwise return false. >>> + */ >>> +static bool lsdc_pixpll_find(struct lsdc_pll * const this, >>> +                unsigned int clock, >>> +                struct lsdc_pll_parms * const pout) >>> +{ >>> +   unsigned int num = ARRAY_SIZE(pll_param_table); >>> +   unsigned int i; >>> + >>> +   for (i = 0; i < num; i++) { >>> +       if (clock != pll_param_table[i].clock) >>> +           continue; >>> + >>> +       pout->div_ref = pll_param_table[i].div_ref; >>> +       pout->loopc  = pll_param_table[i].loopc; >>> +       pout->div_out = pll_param_table[i].div_out; >>> + >>> +       return true; >>> +   } >>> + >>> +   drm_dbg(this->ddev, "pixel clock %u: miss\n", clock); >>> + >>> +   return false; >>> +} >>> + >>> +/* >>> + * Find a set of pll parameters which have minimal difference with >>> the desired >>> + * pixel clock frequency. It does that by computing all of the >>> possible >>> + * combination. Compute the diff and find the combination with >>> minimal diff. >>> + * >>> + * clock_out = refclk / div_ref * loopc / div_out >>> + * >>> + * refclk is determined by the oscillator mounted board(Here is >>> 100MHz in >>> + * almost all case) >>> + * >>> + * @this: point to the object from which this function is called >>> + * @clock_khz: the desired output pixel clock, the unit is kHz >>> + * @pout: point to the out struct of lsdc_pll_parms >>> + * >>> + * Return true if a parameter is found, otherwise return false >>> + */ >>> +static bool lsdc_pixpll_compute(struct lsdc_pll * const this, >>> +               unsigned int clock_khz, >>> +               struct lsdc_pll_parms *pout) >>> +{ >>> +   unsigned int refclk = this->ref_clock; >>> +   const unsigned int tolerance = 1000; >>> +   unsigned int min = tolerance; >>> +   unsigned int div_out, loopc, div_ref; >>> +   unsigned int computed; >>> + >>> +   if (lsdc_pixpll_find(this, clock_khz, pout)) >>> +       return true; >>> + >>> +   for (div_out = 6; div_out < 64; div_out++) { >>> +       for (div_ref = 3; div_ref < 6; div_ref++) { >>> +           for (loopc = 6; loopc < 161; loopc++) { >>> +               int diff; >>> + >>> +               if (loopc < 12 * div_ref) >>> +                   continue; >>> +               if (loopc > 32 * div_ref) >>> +                   continue; >>> + >>> +               computed = refclk * loopc / div_ref / div_out; >>> + >>> +               if (clock_khz > computed) >>> +                   diff = clock_khz - computed; >>> +               else if (clock_khz < computed) >>> +                   diff = computed - clock_khz; >>> + >>> +               if (diff < min) { >>> +                   min = diff; >>> +                   pout->div_ref = div_ref; >>> +                   pout->div_out = div_out; >>> +                   pout->loopc = loopc; >>> + >>> +                   if (diff == 0) >>> +                       return true; >>> +               } >>> +           } >>> +       } >>> +   } >>> + >>> +   return min < tolerance; >>> +} >>> + >>> +/* >>> + * Update the pll parameters to hardware, target to the pixpll in >>> ls7a1000 >>> + * >>> + * @this: point to the object from which this function is called >>> + * @pin: point to the struct of lsdc_pll_parms passed in >>> + * >>> + * return 0 if successful. >>> + */ >>> +static int ls7a1000_pixpll_param_update(struct lsdc_pll * const this, >>> +                   struct lsdc_pll_parms const *pin) >>> +{ >>> +   void __iomem *reg = this->mmio; >>> +   unsigned int counter = 0; >>> +   bool locked; >>> +   u32 val; >>> + >>> +   /* Bypass the software configured PLL, using refclk directly */ >>> +   val = readl(reg + 0x4); >>> +   val &= ~(1 << 8); >>> +   writel(val, reg + 0x4); >>> + >>> +   /* Powerdown the PLL */ >>> +   val = readl(reg + 0x4); >>> +   val |= (1 << 13); >>> +   writel(val, reg + 0x4); >>> + >>> +   /* Clear the pll parameters */ >>> +   val = readl(reg + 0x4); >>> +   val &= ~(1 << 11); >>> +   writel(val, reg + 0x4); >>> + >>> +   /* clear old value & config new value */ >>> +   val = readl(reg + 0x04); >>> +   val &= ~0x7F; >>> +   val |= pin->div_ref;       /* div_ref */ >>> +   writel(val, reg + 0x4); >>> + >>> +   val = readl(reg); >>> +   val &= ~0x7f; >>> +   val |= pin->div_out;       /* div_out */ >>> + >>> +   val &= ~(0x1ff << 21); >>> +   val |= pin->loopc << 21;   /* loopc */ >>> +   writel(val, reg); >>> + >>> +   /* Set the pll the parameters */ >>> +   val = readl(reg + 0x4); >>> +   val |= (1 << 11); >>> +   writel(val, reg + 0x4); >>> + >>> +   /* Powerup the PLL */ >>> +   val = readl(reg + 0x4); >>> +   val &= ~(1 << 13); >>> +   writel(val, reg + 0x4); >>> + >>> +   /* Wait the PLL lock */ >>> +   do { >>> +       val = readl(reg + 0x4); >>> +       locked = val & 0x80; >>> +       counter++; >>> +   } while (!locked && (counter < 2000)); >>> + >>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>> + >>> +   /* Switch to the software configured pll */ >>> +   val = readl(reg + 0x4); >>> +   val |= (1UL << 8); >>> +   writel(val, reg + 0x4); >>> + >>> +   return 0; >>> +} >>> + >>> +/* >>> + * Update the pll parameters to hardware, target to the pixpll in >>> ls2k1000 >>> + * >>> + * @this: point to the object from which this function is called >>> + * @pin: point to the struct of lsdc_pll_parms passed in >>> + * >>> + * return 0 if successful. >>> + */ >>> +static int ls2k1000_pixpll_param_update(struct lsdc_pll * const this, >>> +                   struct lsdc_pll_parms const *pin) >>> +{ >>> +   void __iomem *reg = this->mmio; >>> +   unsigned int counter = 0; >>> +   bool locked = false; >>> +   u32 val; >>> + >>> +   val = readl(reg); >>> +   /* Bypass the software configured PLL, using refclk directly */ >>> +   val &= ~(1 << 0); >>> +   writel(val, reg); >>> + >>> +   /* Powerdown the PLL */ >>> +   val |= (1 << 19); >>> +   writel(val, reg); >>> + >>> +   /* Allow the software configuration */ >>> +   val &= ~(1 << 2); >>> +   writel(val, reg); >>> + >>> +   /* allow L1 PLL lock */ >>> +   val = (1L << 7) | (3L << 10); >>> +   writel(val, reg); >>> + >>> +   /* clear div_ref bit field */ >>> +   val &= ~(0x3f << 26); >>> +   /* set div_ref bit field */ >>> +   val |= pin->div_ref << 26; >>> +   writel(val, reg); >>> + >>> +   val = readl(reg + 4); >>> +   /* clear loopc bit field */ >>> +   val &= ~0x0fff; >>> +   /* set loopc bit field */ >>> +   val |= pin->loopc; >>> +   writel(val, reg + 4); >>> + >>> +   /* set div_out */ >>> +   writel(pin->div_out, reg + 8); >>> + >>> +   val = readl(reg); >>> +   /* use this parms configured just now */ >>> +   val |= (1 << 2); >>> +   /* powerup the PLL */ >>> +   val &= ~(1 << 19); >>> +   writel(val, reg); >>> + >>> +   /* wait pll setup and locked */ >>> +   do { >>> +       val = readl(reg); >>> +       locked = val & 0x10000; >>> +       counter++; >>> +   } while (!locked && (counter < 2000)); >>> + >>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>> + >>> +   /* Switch to software configured PLL instead of refclk */ >>> +   val |= 1; >>> +   writel(val, reg); >>> + >>> +   return 0; >>> +} >>> + >>> +/* >>> + * Update the pll parameters to hardware, target to the pixpll in >>> ls2k0500 >>> + * >>> + * @this: point to the object which calling this function >>> + * @param: pointer to where the parameters passed in >>> + * >>> + * return 0 if successful. >>> + */ >>> +static int ls2k0500_pixpll_param_update(struct lsdc_pll * const this, >>> +                   struct lsdc_pll_parms const *param) >>> +{ >>> +   void __iomem *reg = this->mmio; >>> +   unsigned int counter = 0; >>> +   bool locked = false; >>> +   u32 val; >>> + >>> +   /* Bypass the software configured PLL, using refclk directly */ >>> +   val = readl(reg); >>> +   val &= ~(1 << 0); >>> +   writel(val, reg); >>> + >>> +   /* Powerdown the PLL */ >>> +   val = readl(reg); >>> +   val |= (1 << 5); >>> +   writel(val, reg); >>> + >>> +   /* Allow the software configuration */ >>> +   val |= (1 << 3); >>> +   writel(val, reg); >>> + >>> +   /* Update the pll params */ >>> +   val = (param->div_out << 24) | >>> +         (param->loopc << 16) | >>> +         (param->div_ref << 8); >>> + >>> +   writel(val, reg); >>> + >>> +   /* Powerup the PLL */ >>> +   val = readl(reg); >>> +   val &= ~(1 << 5); >>> +   writel(val, reg); >>> + >>> +   /* wait pll setup and locked */ >>> +   do { >>> +       val = readl(reg); >>> +       locked = val & 0x80; >>> +       counter++; >>> +   } while (!locked && (counter < 10000)); >>> + >>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>> + >>> +   /* Switch to software configured PLL instead of refclk */ >>> +   writel((val | 1), reg); >>> + >>> +   return 0; >>> +} >>> + >>> +static unsigned int lsdc_get_clock_rate(struct lsdc_pll * const this, >>> +                   struct lsdc_pll_parms *pout) >>> +{ >>> +   struct drm_device *ddev = this->ddev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   const struct lsdc_desc * const descp = ldev->descp; >>> +   unsigned int out; >>> +   union lsdc_pixpll_bitmap parms; >>> + >>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>> CHIP_LS7A2000) { >>> +       struct ls7a1000_pixpll_bitmap *obj = &parms.ls7a1000; >>> + >>> +       parms.dword[0] = readl(this->mmio); >>> +       parms.dword[1] = readl(this->mmio + 4); >>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>> obj->div_out; >>> +       if (pout) { >>> +           pout->div_ref = obj->div_ref; >>> +           pout->loopc = obj->loopc; >>> +           pout->div_out = obj->div_out; >>> +       } >>> +   } else if (descp->chip == CHIP_LS2K1000) { >>> +       struct ls2k1000_pixpll_bitmap *obj = &parms.ls2k1000; >>> + >>> +       parms.dword[0] = readl(this->mmio); >>> +       parms.dword[1] = readl(this->mmio + 4); >>> +       parms.dword[2] = readl(this->mmio + 8); >>> +       parms.dword[3] = readl(this->mmio + 12); >>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>> obj->div_out; >>> +       if (pout) { >>> +           pout->div_ref = obj->div_ref; >>> +           pout->loopc = obj->loopc; >>> +           pout->div_out = obj->div_out; >>> +       } >>> +   } else if (descp->chip == CHIP_LS2K0500) { >>> +       struct ls2k0500_pixpll_bitmap *obj = &parms.ls2k0500; >>> + >>> +       parms.dword[0] = readl(this->mmio); >>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>> obj->div_out; >>> +       if (pout) { >>> +           pout->div_ref = obj->div_ref; >>> +           pout->loopc = obj->loopc; >>> +           pout->div_out = obj->div_out; >>> +       } >>> +   } else { >>> +       drm_err(ddev, "unknown chip, the driver need update\n"); >>> +       return 0; >>> +   } >>> + >>> +   return out; >>> +} >>> + >>> +static const struct lsdc_pixpll_funcs ls7a1000_pixpll_funcs = { >>> +   .setup = lsdc_pixpll_setup, >>> +   .compute = lsdc_pixpll_compute, >>> +   .update = ls7a1000_pixpll_param_update, >>> +   .get_clock_rate = lsdc_get_clock_rate, >>> +}; >>> + >>> +static const struct lsdc_pixpll_funcs ls2k1000_pixpll_funcs = { >>> +   .setup = lsdc_pixpll_setup, >>> +   .compute = lsdc_pixpll_compute, >>> +   .update = ls2k1000_pixpll_param_update, >>> +   .get_clock_rate = lsdc_get_clock_rate, >>> +}; >>> + >>> +static const struct lsdc_pixpll_funcs ls2k0500_pixpll_funcs = { >>> +   .setup = lsdc_pixpll_setup, >>> +   .compute = lsdc_pixpll_compute, >>> +   .update = ls2k0500_pixpll_param_update, >>> +   .get_clock_rate = lsdc_get_clock_rate, >>> +}; >>> + >>> +int lsdc_pixpll_init(struct lsdc_pll * const this, >>> +            struct drm_device *ddev, >>> +            unsigned int index) >>> +{ >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   this->ddev = ddev; >>> +   this->index = index; >>> +   this->ref_clock = LSDC_PLL_REF_CLK; >>> + >>> +   /* LS7A1000 and LS7A2000's pixpll setting registers is same */ >>> +   if (descp->chip == CHIP_LS7A2000 || descp->chip == >>> CHIP_LS7A1000) { >>> +       if (index == 0) >>> +           this->reg_base = LS7A1000_CFG_REG_BASE + >>> LS7A1000_PIX_PLL0_REG; >>> +       else if (index == 1) >>> +           this->reg_base = LS7A1000_CFG_REG_BASE + >>> LS7A1000_PIX_PLL1_REG; >>> +       this->reg_size = 8; >>> +       this->funcs = &ls7a1000_pixpll_funcs; >>> +   } else if (descp->chip == CHIP_LS2K1000) { >>> +       if (index == 0) >>> +           this->reg_base = LS2K1000_CFG_REG_BASE + >>> LS2K1000_PIX_PLL0_REG; >>> +       else if (index == 1) >>> +           this->reg_base = LS2K1000_CFG_REG_BASE + >>> LS2K1000_PIX_PLL1_REG; >>> + >>> +       this->reg_size = 16; >>> +       this->funcs = &ls2k1000_pixpll_funcs; >>> +   } else if (descp->chip == CHIP_LS2K0500) { >>> +       if (index == 0) >>> +           this->reg_base = LS2K0500_CFG_REG_BASE + >>> LS2K0500_PIX_PLL0_REG; >>> +       else if (index == 1) >>> +           this->reg_base = LS2K0500_CFG_REG_BASE + >>> LS2K0500_PIX_PLL1_REG; >>> + >>> +       this->reg_size = 4; >>> +       this->funcs = &ls2k0500_pixpll_funcs; >>> +   } else { >>> +       drm_err(this->ddev, "unknown chip, the driver need update\n"); >>> +       return -ENOENT; >>> +   } >>> + >>> +   return this->funcs->setup(this); >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.h >>> b/drivers/gpu/drm/lsdc/lsdc_pll.h >>> new file mode 100644 >>> index 000000000000..538739f461f2 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.h >>> @@ -0,0 +1,78 @@ >>> +/* SPDX-License-Identifier: GPL-2.0 */ >>> + >>> +#ifndef __LSDC_PLL_H__ >>> +#define __LSDC_PLL_H__ >>> + >>> +#include <drm/drm_device.h> >>> + >>> +/* >>> + * Loongson Pixel PLL hardware structure >>> + * >>> + * refclk: reference frequency, 100 MHz from external oscillator >>> + * outclk: output frequency desired. >>> + * >>> + * >>> + *              L1      Fref                     Fvco L2 >>> + * refclk  +-----------+     +------------------+ +---------+  >>> outclk >>> + * ---+---> | Prescaler | ---> | Clock Multiplier | ---> | divider >>> | --------> >>> + *   |    +-----------+     +------------------+ +---------+    ^ >>> + *   |          ^                     ^ ^         | >>> + *   |          |                     | |         | >>> + *   |          |                     | |         | >>> + *   |       div_ref                loopc div_out      | >>> + * | | >>> + *   +--- sel_out (bypass above software configurable clock if >>> set) ----+ >>> + * >>> + * sel_out: PLL clock output selector (for debug purpose only). >>> + * >>> + * If sel_out == 1, it will take refclk as output directly, >>> + * the L1 Prescaler and the out divider will be bypassed. >>> + * >>> + * If sel_out == 0, then outclk = refclk / div_ref * loopc / div_out; >>> + * >>> + * PLL working requirements: >>> + * >>> + * 1) 20 MHz <= refclk / div_ref <= 40Mhz >>> + * 2) 1.2 GHz <= refclk /div_out * loopc <= 3.2 Ghz >>> + */ >>> + >>> +struct lsdc_pll_parms { >>> +   unsigned int div_ref; >>> +   unsigned int loopc; >>> +   unsigned int div_out; >>> +}; >>> + >>> +struct lsdc_pll; >>> + >>> +struct lsdc_pixpll_funcs { >>> +   int (*setup)(struct lsdc_pll * const this); >>> +   bool (*compute)(struct lsdc_pll * const this, >>> +           unsigned int clock, >>> +           struct lsdc_pll_parms *pout); >>> +   int (*update)(struct lsdc_pll * const this, >>> +             struct lsdc_pll_parms const *pin); >>> +   unsigned int (*get_clock_rate)(struct lsdc_pll * const this, >>> +                      struct lsdc_pll_parms *pout); >>> +}; >>> + >>> +struct lsdc_pll { >>> +   const struct lsdc_pixpll_funcs *funcs; >>> +   struct drm_device *ddev; >>> +   void __iomem *mmio; >>> + >>> +   /* PLL register offset */ >>> +   u32 reg_base; >>> +   /* PLL register size in bytes */ >>> +   u32 reg_size; >>> + >>> +   /* 100000kHz, fixed on all board found */ >>> +   unsigned int ref_clock; >>> + >>> +   unsigned int index; >>> +}; >>> + >>> +int lsdc_pixpll_init(struct lsdc_pll * const this, >>> +            struct drm_device *ddev, >>> +            unsigned int index); >>> + >>> +#endif >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.c >>> b/drivers/gpu/drm/lsdc/lsdc_regs.c >>> new file mode 100644 >>> index 000000000000..c7950c43968f >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.c >>> @@ -0,0 +1,29 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> + >>> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset) >>> +{ >>> +   unsigned long flags; >>> +   u32 ret; >>> + >>> +   spin_lock_irqsave(&ldev->reglock, flags); >>> + >>> +   ret = readl(ldev->reg_base + offset); >>> + >>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>> + >>> +   return ret; >>> +} >>> + >>> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 val) >>> +{ >>> +   unsigned long flags; >>> + >>> +   spin_lock_irqsave(&ldev->reglock, flags); >>> + >>> +   writel(val, ldev->reg_base + offset); >>> + >>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.h >>> b/drivers/gpu/drm/lsdc/lsdc_regs.h >>> new file mode 100644 >>> index 000000000000..828956633137 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.h >>> @@ -0,0 +1,296 @@ >>> +/* SPDX-License-Identifier: GPL-2.0 */ >>> + >>> +#ifndef __LSDC_REGS_H__ >>> +#define __LSDC_REGS_H__ >>> + >>> +#include <linux/bitops.h> >>> +#include <linux/types.h> >>> + >>> +/* >>> + * PIXEL PLL Reference clock >>> + */ >>> +#define LSDC_PLL_REF_CLK               100000          /* kHz */ >>> + >>> +/* >>> + * Those PLL registers are not located at DC reg bar space, >>> + * there are relative to LSXXXXX_CFG_REG_BASE. >>> + * XXXXX = 7A1000, 2K1000, 2K0500 >>> + */ >>> + >>> +/* LS2K1000 */ >>> +#define LS2K1000_PIX_PLL0_REG          0x04B0 >>> +#define LS2K1000_PIX_PLL1_REG          0x04C0 >>> +#define LS2K1000_CFG_REG_BASE          0x1fe10000 >>> + >>> +/* LS7A1000 and LS2K2000 */ >>> +#define LS7A1000_PIX_PLL0_REG          0x04B0 >>> +#define LS7A1000_PIX_PLL1_REG          0x04C0 >>> +#define LS7A1000_CFG_REG_BASE          0x10010000 >>> + >>> +/* LS2K0500 */ >>> +#define LS2K0500_PIX_PLL0_REG          0x0418 >>> +#define LS2K0500_PIX_PLL1_REG          0x0420 >>> +#define LS2K0500_CFG_REG_BASE          0x1fe10000 >>> + >>> +/* >>> + * CRTC CFG REG >>> + */ >>> +#define CFG_PIX_FMT_MASK               GENMASK(2, 0) >>> + >>> +enum lsdc_pixel_format { >>> +   LSDC_PF_NONE = 0, >>> +   LSDC_PF_ARGB4444 = 1, /* ARGB A:4 bits R/G/B: 4 bits each [16 >>> bits] */ >>> +   LSDC_PF_ARGB1555 = 2, /* ARGB A:1 bit RGB:15 bits [16 bits] */ >>> +   LSDC_PF_RGB565 = 3,   /* RGB [16 bits] */ >>> +   LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */ >>> +   LSDC_PF_RGBA8888 = 5, /* ARGB [32 bits] */ >>> +}; >>> + >>> +/* >>> + * Each crtc has two set fb address registers usable, >>> CFG_FB_IN_USING of >>> + * LSDC_CRTCx_CFG_REG specify which fb address register is currently >>> + * in using by the CRTC. CFG_PAGE_FLIP of LSDC_CRTCx_CFG_REG is >>> used to >>> + * trigger the switch which will be finished at the very vblank. If >>> you >>> + * want it switch to another again, you must set CFG_PAGE_FLIP again. >>> + */ >>> +#define CFG_PAGE_FLIP                  BIT(7) >>> +#define CFG_OUTPUT_EN                  BIT(8) >>> +/* CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware >>> logic */ >>> +#define CFG_PANEL_SWITCH               BIT(9) >>> +/* Indicate witch fb addr reg is in using, currently */ >>> +#define CFG_FB_IN_USING                BIT(11) >>> +#define CFG_GAMMA_EN                   BIT(12) >>> + >>> +/* CRTC get soft reset if voltage level change from 1 -> 0 */ >>> +#define CFG_RESET_N                    BIT(20) >>> + >>> +#define CFG_HSYNC_EN                   BIT(30) >>> +#define CFG_HSYNC_INV                  BIT(31) >>> +#define CFG_VSYNC_EN                   BIT(30) >>> +#define CFG_VSYNC_INV                  BIT(31) >>> + >>> +/* THE DMA step of the DC in LS7A2000 is configurable */ >>> +#define LSDC_DMA_STEP_MASK             GENMASK(17, 16) >>> +enum lsdc_dma_steps_supported { >>> +   LSDC_DMA_STEP_256_BYTES = 0, >>> +   LSDC_DMA_STEP_128_BYTES = 1, >>> +   LSDC_DMA_STEP_64_BYTES = 2, >>> +   LSDC_DMA_STEP_32_BYTES = 3, >>> +}; >>> + >>> +/******** CRTC0 & DVO0 ********/ >>> +#define LSDC_CRTC0_CFG_REG             0x1240 >>> +#define LSDC_CRTC0_FB0_LO_ADDR_REG     0x1260 >>> +#define LSDC_CRTC0_FB0_HI_ADDR_REG     0x15A0 >>> +#define LSDC_CRTC0_FB1_LO_ADDR_REG     0x1580 >>> +#define LSDC_CRTC0_FB1_HI_ADDR_REG     0x15C0 >>> +#define LSDC_CRTC0_STRIDE_REG          0x1280 >>> +#define LSDC_CRTC0_FB_ORIGIN_REG       0x1300 >>> +#define LSDC_CRTC0_HDISPLAY_REG        0x1400 >>> +#define LSDC_CRTC0_HSYNC_REG           0x1420 >>> +#define LSDC_CRTC0_VDISPLAY_REG        0x1480 >>> +#define LSDC_CRTC0_VSYNC_REG           0x14A0 >>> +#define LSDC_CRTC0_GAMMA_INDEX_REG     0x14E0 >>> +#define LSDC_CRTC0_GAMMA_DATA_REG      0x1500 >>> + >>> +/******** CTRC1 & DVO1 ********/ >>> +#define LSDC_CRTC1_CFG_REG             0x1250 >>> +#define LSDC_CRTC1_FB0_LO_ADDR_REG     0x1270 >>> +#define LSDC_CRTC1_FB0_HI_ADDR_REG     0x15B0 >>> +#define LSDC_CRTC1_FB1_LO_ADDR_REG     0x1590 >>> +#define LSDC_CRTC1_FB1_HI_ADDR_REG     0x15D0 >>> +#define LSDC_CRTC1_STRIDE_REG          0x1290 >>> +#define LSDC_CRTC1_FB_ORIGIN_REG       0x1310 >>> +#define LSDC_CRTC1_HDISPLAY_REG        0x1410 >>> +#define LSDC_CRTC1_HSYNC_REG           0x1430 >>> +#define LSDC_CRTC1_VDISPLAY_REG        0x1490 >>> +#define LSDC_CRTC1_VSYNC_REG           0x14B0 >>> +#define LSDC_CRTC1_GAMMA_INDEX_REG     0x14F0 >>> +#define LSDC_CRTC1_GAMMA_DATA_REG      0x1510 >>> + >>> +/* >>> + * LS7A2000 has the hardware which record the scan position of the >>> CRTC >>> + * [31:16] : current X position, [15:0] : current Y position >>> + */ >>> +#define LSDC_CRTC0_SCAN_POS_REG        0x14C0 >>> +#define LSDC_CRTC1_SCAN_POS_REG        0x14D0 >>> + >>> +/* >>> + * LS7A2000 has the hardware which count the number of vblank >>> generated >>> + */ >>> +#define LSDC_CRTC0_VSYNC_COUNTER_REG   0x1A00 >>> +#define LSDC_CRTC1_VSYNC_COUNTER_REG   0x1A10 >>> + >>> +/* In all, LSDC_CRTC1_XXX_REG - LSDC_CRTC0_XXX_REG = 0x10 */ >>> + >>> +/* >>> + * There is only one hardware cursor unit in ls7a1000, ls2k1000 and >>> ls2k0500. >>> + * well, ls7a2000 has two hardware cursor unit. >>> + */ >>> +#define CURSOR_FORMAT_MASK             GENMASK(1, 0) >>> +enum lsdc_cursor_format { >>> +   CURSOR_FORMAT_DISABLE = 0, >>> +   CURSOR_FORMAT_MONOCHROME = 1, >>> +   CURSOR_FORMAT_ARGB8888 = 2, >>> +}; >>> + >>> +#define CURSOR_SIZE_64X64              BIT(2) >>> +#define CURSOR_LOCATION                BIT(4) >>> + >>> +#define LSDC_CURSOR0_CFG_REG           0x1520 >>> +#define LSDC_CURSOR0_ADDR_LO_REG       0x1530 >>> +#define LSDC_CURSOR0_ADDR_HI_REG       0x15e0 >>> +#define LSDC_CURSOR0_POSITION_REG      0x1540 >>> +#define LSDC_CURSOR0_BG_COLOR_REG      0x1550 /* background color */ >>> +#define LSDC_CURSOR0_FG_COLOR_REG      0x1560 /* foreground color */ >>> + >>> +#define LSDC_CURSOR1_CFG_REG           0x1670 >>> +#define LSDC_CURSOR1_ADDR_LO_REG       0x1680 >>> +#define LSDC_CURSOR1_ADDR_HI_REG       0x16e0 >>> +#define LSDC_CURSOR1_POSITION_REG      0x1690 /* [31:16] Y, >>> [15:0] X */ >>> +#define LSDC_CURSOR1_BG_COLOR_REG      0x16A0 /* background color */ >>> +#define LSDC_CURSOR1_FG_COLOR_REG      0x16B0 /* foreground color */ >>> + >>> +/* >>> + * DC Interrupt Control Register, 32bit, Address Offset: 1570 >>> + * >>> + * Bits 15:0 inidicate the interrupt status >>> + * Bits 31:16 control enable interrupts corresponding to bit 15:0 >>> or not >>> + * Write 1 to enable, write 0 to disable >>> + * >>> + * RF: Read Finished >>> + * IDBU: Internal Data Buffer Underflow >>> + * IDBFU: Internal Data Buffer Fatal Underflow >>> + * CBRF: Cursor Buffer Read Finished Flag, no use. >>> + * >>> + * >>> +-------+--------------------------+-------+--------+--------+-------+ >>> + * | 31:27 |        26:16           | 15:11 |  10  |  9 |  8  | >>> + * >>> +-------+--------------------------+-------+--------+--------+-------+ >>> + * | N/A | Interrupt Enable Control | N/A | IDBFU0 | IDBFU1 | >>> IDBU0 | >>> + * >>> +-------+--------------------------+-------+--------+--------+-------+ >>> + * >>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>> + * |  7  | 6 | 5 | 4  |  3   |  2   |  1   | 0   | >>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>> + * | IDBU1 | RF0 | RF1 | CRRF | HSYNC0 | VSYNC0 | HSYNC1 | VSYNC1 | >>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>> + */ >>> + >>> +#define LSDC_INT_REG                   0x1570 >>> + >>> +#define INT_CRTC0_VSYNC                BIT(2) >>> +#define INT_CRTC0_HSYNC                BIT(3) >>> +#define INT_CRTC0_RF                   BIT(6) >>> +#define INT_CRTC0_IDBU                 BIT(8) >>> +#define INT_CRTC0_IDBFU                BIT(10) >>> + >>> +#define INT_CRTC1_VSYNC                BIT(0) >>> +#define INT_CRTC1_HSYNC                BIT(1) >>> +#define INT_CRTC1_RF                   BIT(5) >>> +#define INT_CRTC1_IDBU                 BIT(7) >>> +#define INT_CRTC1_IDBFU                BIT(9) >>> + >>> +#define INT_CRTC0_VS_EN                BIT(18) >>> +#define INT_CRTC0_HS_EN                BIT(19) >>> +#define INT_CRTC0_RF_EN                BIT(22) >>> +#define INT_CRTC0_IDBU_EN              BIT(24) >>> +#define INT_CRTC0_IDBFU_EN             BIT(26) >>> + >>> +#define INT_CRTC1_VS_EN                BIT(16) >>> +#define INT_CRTC1_HS_EN                BIT(17) >>> +#define INT_CRTC1_RF_EN                BIT(21) >>> +#define INT_CRTC1_IDBU_EN              BIT(23) >>> +#define INT_CRTC1_IDBFU_EN             BIT(25) >>> + >>> +#define INT_STATUS_MASK                GENMASK(15, 0) >>> + >>> +/* >>> + * LS7A1000/LS7A2000 have 4 gpios which are used to emulated I2C. >>> + * They are under control of the LS7A_DC_GPIO_DAT_REG and >>> LS7A_DC_GPIO_DIR_REG >>> + * register, Those GPIOs has no relationship whth the GPIO hardware >>> on the >>> + * bridge chip itself. Those offsets are relative to DC register >>> base address >>> + * >>> + * LS2k1000 and LS2K0500 don't have those registers, they use >>> hardware i2c >>> + * or general GPIO emulated i2c from linux i2c subsystem. >>> + * >>> + * GPIO data register, address offset: 0x1650 >>> + *  +---------------+-----------+-----------+ >>> + *  | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | >>> + *  +---------------+-----------+-----------+ >>> + *  |              |   DVO1  |   DVO0  | >>> + *  +     N/A     +-----------+-----------+ >>> + *  |              | SCL | SDA | SCL | SDA | >>> + *  +---------------+-----------+-----------+ >>> + */ >>> +#define LS7A_DC_GPIO_DAT_REG           0x1650 >>> + >>> +/* >>> + * GPIO Input/Output direction control register, address offset: >>> 0x1660 >>> + */ >>> +#define LS7A_DC_GPIO_DIR_REG           0x1660 >>> + >>> +/* >>> + * LS7A2000 has two built-in HDMI Encoder and one VGA encoder >>> + */ >>> + >>> +/* >>> + * Number of continuous packets may be present >>> + * in HDMI hblank and vblank zone, should >= 48 >>> + */ >>> +#define LSDC_HDMI0_ZONE_REG            0x1700 >>> +#define LSDC_HDMI1_ZONE_REG            0x1710 >>> + >>> +#define HDMI_INTERFACE_EN              BIT(0) >>> +#define HDMI_PACKET_EN                 BIT(1) >>> +#define HDMI_AUDIO_EN                  BIT(2) >>> +#define HDMI_VIDEO_PREAMBLE_MASK       GENMASK(7, 4) >>> +#define HDMI_HW_I2C_EN                 BIT(8) >>> +#define HDMI_CTL_PERIOD_MODE           BIT(9) >>> +#define LSDC_HDMI0_CTRL_REG            0x1720 >>> +#define LSDC_HDMI1_CTRL_REG            0x1730 >>> + >>> +#define HDMI_PHY_EN                    BIT(0) >>> +#define HDMI_PHY_RESET_N               BIT(1) >>> +#define HDMI_PHY_TERM_L_EN             BIT(8) >>> +#define HDMI_PHY_TERM_H_EN             BIT(9) >>> +#define HDMI_PHY_TERM_DET_EN           BIT(10) >>> +#define HDMI_PHY_TERM_STATUS           BIT(11) >>> +#define LSDC_HDMI0_PHY_CTRL_REG        0x1800 >>> +#define LSDC_HDMI1_PHY_CTRL_REG        0x1810 >>> + >>> +/* >>> + * 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 | >>> + * +--------------------------------------------------------+ >>> + */ >>> +#define LSDC_HDMI0_PHY_PLL_REG         0x1820 >>> +#define LSDC_HDMI1_PHY_PLL_REG         0x1830 >>> + >>> +#define HDMI_PLL_ENABLE                BIT(0) >>> +#define HDMI_PLL_LOCKED                BIT(16) >>> +#define HDMI_PLL_BYPASS                BIT(17) >>> + >>> +#define HDMI_PLL_IDF_SHIFT             1 >>> +#define HDMI_PLL_IDF_MASK              GENMASK(5, 1) >>> +#define HDMI_PLL_LF_SHIFT              6 >>> +#define HDMI_PLL_LF_MASK               GENMASK(12, 6) >>> +#define HDMI_PLL_ODF_SHIFT             13 >>> +#define HDMI_PLL_ODF_MASK              GENMASK(15, 13) >>> + >>> +/* LS7A2000 have hpd support */ >>> +#define LSDC_HDMI_HPD_STATUS_REG       0x1BA0 >>> +#define HDMI0_HPD_FLAG                 BIT(0) >>> +#define HDMI1_HPD_FLAG                 BIT(1) >>> + >>> +#endif >> >
On 2023/2/13 16:35, Sui Jingfeng wrote: > > On 2023/2/13 12:24, Sui jingfeng wrote: >> >> On 2023/2/10 23:04, Thomas Zimmermann wrote: >>> Hi >>> >> Hello, >>> Am 05.02.23 um 17:17 schrieb suijingfeng: >>>> From: suijingfeng <suijingfeng@loongson.cn> >>>> >>>> Loongson display controller IP has been integrated in both Loongson >>>> North Bridge chipset(ls7a1000 and ls7a2000) and Loongson SoCs(ls2k1000 >>>> and ls2k2000 etc), it even been included in Loongson BMC products. >>>> >>>> The display controller is a PCI device, it has two display pipe. >>>> For the DC in LS7A1000 and LS2K1000 each way has a DVO output >>>> interface >>>> which provide RGB888 signals, vertical & horizontal synchronisations, >>>> and the pixel clock. Each CRTC is able to support 1920x1080@60Hz, >>>> the maximum resolution is 2048x2048 according to the hardware spec. >>>> >>>> For the DC in LS7A2000, each display pipe is connected with a built-in >>>> HDMI encoder which support 3840x2160@30Hz. the first display pipe is >>>> also equipped with a transparent vga encoder which is parallel with >>>> the hdmi encoder. The DC in LS7A2000 is more complete, besides above >>>> feature, it has two hardware cursors, two hardware vblank counter and >>>> two scanout position recoders. It also support hdmi audio and tiled >>>> format etc. >>>> >>>>  v2: >>>>   1) Use hpd status reg when polling for ls7a2000 >>>>   2) Fix all warnings emerged when compile with W=1 >>>> >>>> Signed-off-by: suijingfeng <suijingfeng@loongson.cn> >>>> Signed-off-by: suijingfeng <15330273260@189.cn> >>>> --- >>>>  drivers/gpu/drm/Kconfig            |  2 + >>>>  drivers/gpu/drm/Makefile           |  1 + >>>>  drivers/gpu/drm/lsdc/Kconfig       | 17 + >>>>  drivers/gpu/drm/lsdc/Makefile      | 14 + >>>>  drivers/gpu/drm/lsdc/lsdc_crtc.c   | 421 ++++++++++++++++++ >>>>  drivers/gpu/drm/lsdc/lsdc_debugfs.c | 191 +++++++++ >>>>  drivers/gpu/drm/lsdc/lsdc_drv.c    | 635 >>>> ++++++++++++++++++++++++++++ >>>>  drivers/gpu/drm/lsdc/lsdc_drv.h    | 274 ++++++++++++ >>>>  drivers/gpu/drm/lsdc/lsdc_i2c.c    | 201 +++++++++ >>>>  drivers/gpu/drm/lsdc/lsdc_irq.c    | 86 ++++ >>>>  drivers/gpu/drm/lsdc/lsdc_output.c | 452 ++++++++++++++++++++ >>>>  drivers/gpu/drm/lsdc/lsdc_plane.c  | 443 +++++++++++++++++++ >>>>  drivers/gpu/drm/lsdc/lsdc_pll.c    | 569 +++++++++++++++++++++++++ >>>>  drivers/gpu/drm/lsdc/lsdc_pll.h    | 78 ++++ >>>>  drivers/gpu/drm/lsdc/lsdc_regs.c   | 29 ++ >>>>  drivers/gpu/drm/lsdc/lsdc_regs.h   | 296 +++++++++++++ >>>>  16 files changed, 3709 insertions(+) >>>>  create mode 100644 drivers/gpu/drm/lsdc/Kconfig >>>>  create mode 100644 drivers/gpu/drm/lsdc/Makefile >>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_crtc.c >>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_debugfs.c >>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.c >>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.h >>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_i2c.c >>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_irq.c >>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_output.c >>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_plane.c >>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.c >>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.h >>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.c >>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.h >>>> >>>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig >>>> index dc0f94f02a82..3baecd48930b 100644 >>>> --- a/drivers/gpu/drm/Kconfig >>>> +++ b/drivers/gpu/drm/Kconfig >>>> @@ -367,6 +367,8 @@ source "drivers/gpu/drm/solomon/Kconfig" >>>>   source "drivers/gpu/drm/sprd/Kconfig" >>>>  +source "drivers/gpu/drm/lsdc/Kconfig" >>>> + >>>>  config DRM_HYPERV >>>>      tristate "DRM Support for Hyper-V synthetic video device" >>>>      depends on DRM && PCI && MMU && HYPERV >>>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile >>>> index ab4460fcd63f..5a8e2fc8dd5d 100644 >>>> --- a/drivers/gpu/drm/Makefile >>>> +++ b/drivers/gpu/drm/Makefile >>>> @@ -190,3 +190,4 @@ obj-y           += gud/ >>>>  obj-$(CONFIG_DRM_HYPERV) += hyperv/ >>>>  obj-y           += solomon/ >>>>  obj-$(CONFIG_DRM_SPRD) += sprd/ >>>> +obj-$(CONFIG_DRM_LSDC) += lsdc/ >>>> diff --git a/drivers/gpu/drm/lsdc/Kconfig >>>> b/drivers/gpu/drm/lsdc/Kconfig >>>> new file mode 100644 >>>> index 000000000000..8b9bb6996877 >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/Kconfig >>>> @@ -0,0 +1,17 @@ >>>> +# SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +config DRM_LSDC >>>> +   tristate "DRM support for loongson display controller" >>>> +   depends on DRM && PCI && (MACH_LOONGSON64 || LOONGARCH || MIPS) >>> >>> If possible, I'd add a single 'depends on' statement for each AND >>> condition. Like this >>> >>>  depends on DRM >>>  depends on PCI >>>  depends on (MACH_LOONGSON64 || LOONGARCH || MIPS) >>> >>> Does it build on other architectures if you add COMPILE_TEST to that >>> list of architectures? If so, it would simplify testing a lot. >>> > Hi, Should I add COMPILE_TEST to the list of architectures on next > version? >>>> +   select OF >>> >>> I don't think that it is allowed to select OF. Rather use >>> >>>  depends on OF >>> >>> I think that general rule is that it is not allowed to auto-select >>> options that are selectable by users. That's the case for OF. >>> >>> >> Ok, depends on OF is acceptable. >> >>>> +   select DRM_KMS_HELPER >>>> +   select DRM_TTM >>>> +   select DRM_TTM_HELPER >>>> +   select DRM_VRAM_HELPER >>>> +   default m >>> >>> No default, especially not 'm'. Everything should be off by default. >>> >>>> +   help >>>> +     This is a KMS driver for loongson display controller in the >>>> +     LS7A1000/LS7A2000 bridge chip and LS2K1000/LS2K0500 SoC. >>>> +     If "M" is selected, the module will be called lsdc. >>>> + >>>> +     If in doubt, say "Y". >>> >>> Always say "N" by default. There are a few exceptions where "yes" is >>> advised, but regular drivers are not one of them. >>> >>>> diff --git a/drivers/gpu/drm/lsdc/Makefile >>>> b/drivers/gpu/drm/lsdc/Makefile >>>> new file mode 100644 >>>> index 000000000000..d4b901ec09cf >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/Makefile >>>> @@ -0,0 +1,14 @@ >>>> +# SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +lsdc-y := \ >>>> +   lsdc_drv.o \ >>>> +   lsdc_crtc.o \ >>>> +   lsdc_irq.o \ >>>> +   lsdc_plane.o \ >>>> +   lsdc_pll.o \ >>>> +   lsdc_i2c.o \ >>>> +   lsdc_output.o \ >>>> +   lsdc_regs.o \ >>>> +   lsdc_debugfs.o >>> >>> Please always sort such lists alphabetically. >>> >>>> + >>>> +obj-$(CONFIG_DRM_LSDC) += lsdc.o >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_crtc.c >>>> b/drivers/gpu/drm/lsdc/lsdc_crtc.c >>>> new file mode 100644 >>>> index 000000000000..e23e6085f418 >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_crtc.c >>>> @@ -0,0 +1,421 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include <drm/drm_vblank.h> >>>> +#include <drm/drm_atomic.h> >>>> +#include <drm/drm_atomic_helper.h> >>> >>> Same here, alphabetical sorting. >>> >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> +#include "lsdc_pll.h" >>> >>> Sorting. >>> >> All of problem mentioned above will be solved at next version, thinks. >>>> + >>>> +static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc) >>>> +{ >>>> +   struct lsdc_device *ldev = to_lsdc(crtc->dev); >>>> +   unsigned int index = drm_crtc_index(crtc); >>>> +   u32 val; >>>> + >>>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>>> + >>>> +   if (index == 0) >>>> +       val |= INT_CRTC0_VS_EN; >>>> +   else if (index == 1) >>>> +       val |= INT_CRTC1_VS_EN; >>>> + >>>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc) >>>> +{ >>>> +   struct lsdc_device *ldev = to_lsdc(crtc->dev); >>>> +   unsigned int index = drm_crtc_index(crtc); >>>> +   u32 val; >>>> + >>>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>>> + >>>> +   if (index == 0) >>>> +       val &= ~INT_CRTC0_VS_EN; >>>> +   else if (index == 1) >>>> +       val &= ~INT_CRTC1_VS_EN; >>>> + >>>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >>>> +} >>>> + >>>> +static void lsdc_crtc_reset(struct drm_crtc *crtc) >>>> +{ >>>> +   struct drm_device *ddev = crtc->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   unsigned int index = drm_crtc_index(crtc); >>>> +   struct lsdc_crtc_state *priv_crtc_state; >>>> +   u32 val = CFG_RESET_N | LSDC_PF_XRGB8888; >>>> + >>>> +   if (index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>> +   else if (index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>> + >>>> +   if (crtc->state) { >>>> +       priv_crtc_state = to_lsdc_crtc_state(crtc->state); >>>> + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); >>>> +       kfree(priv_crtc_state); >>>> +   } >>>> + >>>> +   priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL); >>>> +   if (!priv_crtc_state) >>>> +       return; >>>> + >>>> +   __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); >>>> + >>>> +   drm_info(ddev, "CRTC-%u reset\n", index); >>> >>> No drm_info() here. You could use drm_dbg() if you really want to >>> print something. I'd advise to simply remove that line. >>> >>>> +} >>>> + >>>> +static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, >>>> +                      struct drm_crtc_state *state) >>>> +{ >>>> +   struct lsdc_crtc_state *priv_crtc_state = >>>> to_lsdc_crtc_state(state); >>>> + >>>> + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); >>>> + >>>> +   kfree(priv_crtc_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 = kmalloc(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_get_vblank_counter(struct drm_crtc *crtc) >>>> +{ >>>> +   struct drm_device *ddev = crtc->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>>> +   unsigned int index = dispipe->index; >>>> + >>>> +   /* fallback to software emulated VBLANK counter */ >>>> +   if (!descp->has_vblank_counter) >>>> +       goto fallback; >>>> + >>>> +   if (index == 0) >>>> +       return lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG); >>>> + >>>> +   if (index == 1) >>>> +       return lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG); >>>> + >>>> +fallback: >>>> +   return (u32)drm_crtc_vblank_count(crtc); >>>> +} >>>> + >>>> +static const struct drm_crtc_funcs lsdc_crtc_funcs = { >>> >>> My advise for the design is to untangle the different cases into >>> smaller helpers as much as possible. >>> >>> You have several chips with multiple pipelines. I would advise to >>> provide a funcs array for each possible case. For example: >>> >>>  u32 lsdc_crtc0_get_vblank_counter(struct drm_crtc *crtc) >>>  { >>>     // return LSDC_CRTC0_VSYNC_COUNTER_REG >>>  } >>> >>>  u32 lsdc_crtc1_get_vblank_counter(struct drm_crtc *crtc) >>>  { >>>     // return LSDC_CRTC1_VSYNC_COUNTER_REG >>>  } >>> >>> and use a different funcs instance for each pipeline >>> >>>  struct lsdc_crtc0_funcs_ls2k2000 = { >>>     .get_vblank_counter = lsdc_crtc0_get_vblank_counter, >>>  }; >>> >>>  struct lsdc_crtc1_funcs_ls2k2000 = { >>>     .get_vblank_counter = lsdc_crtc1_get_vblank_counter, >>>  }; >>> >>> For hardware without vblank counter, use another instance >>> >>>  struct lsdc_crtc0_funcs_ls2k0500 = { >>>     // no >>>  }; >>> >>> At first, this looks like a lot of code. But in the long term, this >>> pays of, because it's so much more maintainable. If you have >>> functions that work with any CRTC, you can still share the >>> implementation. >> I am agree. >>> >>> All this goes for other components as well. >>> >>> You may want to study mgag200. It supports ~10 chips. Handling this >>> in single functions was unmaintainable. So we split everything into >>> smaller helpers that each handle a single case. And then we build a >>> dedicated modesetting pipeline for each chip from various helpers. >>> Common code is shared among the chips. >>> >> I guess you may want divide and conquer them, mgag200 driver's >> structure is basic same with ast. >> >> they have only one display pipe and they are both standard pci >> device. I'm already absorb many merits from mgag200 and ast. >> >> And i buy two mga video card, sadly they can boot on loongson >> platform, x86emu will let them pass, loongson uefi/pmon firmware >> >> need adapting. I can only study it by reading the code. >> > Sorry, there have typos here: > > i buy two MGA video cards, sadly they can't boot on loongson platform, > x86emu in firmware will block them > > from working. Study it on x86 platform is still possible i think. > > I will focus on its decision seriously, and improve device driver > design skill. But i found it is so different > > from the display controller in ls7a1000/ls7a2000, the display > controller in ls7a1000/ls7a2000 seem come from > > embedded world and walking toward pc, and it don't support vga > address hole and don't compatible > > with IBM vga standard. mgag200 seems come from standard old pc vga card. > >>> Here's where we select the chip: >>> >>> >>> https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_drv.c#L227 >>> >>> >>> There's a source file for each chip and the common source code is in >>> >> I will follow this method at next version, but our hardware and bios >> engineers is ugly, they often made the chip indistinguishable. >> >> ls7a1000 and ls2k1000 have the same pci device id, ls7a2000 and >> ls2k2000 have the same pci device id, >> >> even the revision id is same. I have no choice but to rely on DT or >> kernel cmd passing parameters. >> >> many reviewers and maintainer seem not happy about this. >> >>> https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_mode.c >>> >>> >>> >>>> +   .reset = lsdc_crtc_reset, >>>> +   .destroy = drm_crtc_cleanup, >>>> +   .set_config = drm_atomic_helper_set_config, >>>> +   .page_flip = drm_atomic_helper_page_flip, >>>> +   .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, >>>> +   .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, >>>> +   .get_vblank_counter = lsdc_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, >>>> +}; >>>> + >>>> +static enum drm_mode_status >>>> +lsdc_crtc_mode_valid(struct drm_crtc *crtc, >>>> +            const struct drm_display_mode *mode) >>>> +{ >>>> +   struct drm_device *ddev = crtc->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   if (mode->hdisplay > descp->max_width) >>>> +       return MODE_BAD_HVALUE; >>>> +   if (mode->vdisplay > descp->max_height) >>>> +       return MODE_BAD_VVALUE; >>>> + >>>> +   if (mode->clock > descp->max_pixel_clk) { >>>> +       drm_dbg(ddev, "mode %dx%d, pixel clock=%d is too high\n", >>>> +           mode->hdisplay, mode->vdisplay, mode->clock); >>>> +       return MODE_CLOCK_HIGH; >>>> +   } >>>> + >>>> +   if ((mode->hdisplay * 4) % descp->pitch_align) { >>>> +       drm_dbg(ddev, "stride is require to not %u bytes aligned\n", >>>> +           descp->pitch_align); >>>> +       return MODE_BAD; >>>> +   } >>>> + >>>> +   return MODE_OK; >>>> +} >>>> + >>>> +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc, >>>> +                   struct drm_crtc_state *state) >>>> +{ >>>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>>> +   struct lsdc_pll *pixpll = &dispipe->pixpll; >>>> +   const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; >>>> +   struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); >>>> +   bool ret; >>>> + >>>> +   ret = pfuncs->compute(pixpll, state->mode.clock, >>>> &priv_state->pparms); >>>> +   if (ret) >>>> +       return 0; >>>> + >>>> +   drm_warn(crtc->dev, "failed find PLL parameters for %u\n", >>>> state->mode.clock); >>>> + >>>> +   return -EINVAL; >>>> +} >>>> + >>>> +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; /* no mode checks if CRTC is being disabled */ >>>> + >>>> +   return lsdc_pixpll_atomic_check(crtc, crtc_state); >>>> +} >>>> + >>>> +static void lsdc_update_pixclk(struct lsdc_display_pipe *dispipe, >>>> +                  struct lsdc_crtc_state *priv_state) >>>> +{ >>>> +   struct lsdc_pll *pixpll = &dispipe->pixpll; >>>> +   const struct lsdc_pixpll_funcs *clkfunc = pixpll->funcs; >>>> + >>>> +   clkfunc->update(pixpll, &priv_state->pparms); >>>> +} >>>> + >>>> +static void lsdc_crtc_helper_mode_set_nofb(struct drm_crtc *crtc) >>>> +{ >>>> +   struct drm_device *ddev = crtc->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   struct drm_display_mode *mode = &crtc->state->mode; >>>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>>> +   struct lsdc_crtc_state *priv_state = >>>> to_lsdc_crtc_state(crtc->state); >>>> +   unsigned int index = dispipe->index; >>>> +   u32 h_sync, v_sync, h_val, v_val; >>>> +   u32 val; >>>> + >>>> +   /* 26:16 total pixels, 10:0 visiable pixels, in horizontal */ >>>> +   h_val = (mode->crtc_htotal << 16) | mode->crtc_hdisplay; >>>> + >>>> +   /* 26:16 total pixels, 10:0 visiable pixels, in vertical */ >>>> +   v_val = (mode->crtc_vtotal << 16) | mode->crtc_vdisplay; >>>> +   /* 26:16 hsync end, 10:0 hsync start, bit 30 is hsync enable */ >>>> +   h_sync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start >>>> | CFG_HSYNC_EN; >>>> +   if (mode->flags & DRM_MODE_FLAG_NHSYNC) >>>> +       h_sync |= CFG_HSYNC_INV; >>>> + >>>> +   /* 26:16 vsync end, 10:0 vsync start, bit 30 is vsync enable */ >>>> +   v_sync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start >>>> | CFG_VSYNC_EN; >>>> +   if (mode->flags & DRM_MODE_FLAG_NVSYNC) >>>> +       v_sync |= CFG_VSYNC_INV; >>>> + >>>> +   if (index == 0) { >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG, h_val); >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG, v_val); >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG, h_sync); >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG, v_sync); >>>> + >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>> +       val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>> +   } else if (index == 1) { >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_HDISPLAY_REG, h_val); >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_VDISPLAY_REG, v_val); >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_HSYNC_REG, h_sync); >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_VSYNC_REG, v_sync); >>>> + >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>> +       val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>> +   } >>> >>> Same here as with vblank: have one helper for each index and use >>> different _funcs structures for each pipeline. >>> >> Well, I will revise lsdc driver with this idea in mind. >>>> + >>>> +   drm_dbg(ddev, "%s modeset: %ux%u\n", >>>> +       crtc->name, mode->hdisplay, mode->vdisplay); >>>> + >>>> +   lsdc_update_pixclk(dispipe, priv_state); >>>> +} >>>> + >>>> +static void lsdc_enable_display(struct lsdc_device *ldev, unsigned >>>> int index) >>>> +{ >>>> +   u32 val; >>>> + >>>> +   if (index == 0) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>> +       val |= CFG_OUTPUT_EN; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>> +       return; >>>> +   } >>>> + >>>> +   if (index == 1) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>> +       val |= CFG_OUTPUT_EN; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>> +       return; >>>> +   } >>>> +} >>>> + >>>> +static void lsdc_disable_display(struct lsdc_device *ldev, >>>> unsigned int index) >>>> +{ >>>> +   u32 val; >>>> + >>>> +   if (index == 0) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>> +       val &= ~CFG_OUTPUT_EN; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>> +       return; >>>> +   } >>>> + >>>> +   if (index == 1) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>> +       val &= ~CFG_OUTPUT_EN; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>> +       return; >>>> +   } >>>> +} >>>> + >>>> +static void lsdc_crtc_helper_atomic_enable(struct drm_crtc *crtc, >>>> +                      struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_device *ddev = crtc->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> + >>>> +   drm_crtc_vblank_on(crtc); >>>> + >>>> +   lsdc_enable_display(ldev, drm_crtc_index(crtc)); >>>> + >>>> +   drm_dbg(ddev, "%s: enabled\n", crtc->name); >>>> +} >>>> + >>>> +static void lsdc_crtc_helper_atomic_disable(struct drm_crtc *crtc, >>>> +                       struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_device *ddev = crtc->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> + >>>> +   lsdc_disable_display(ldev, drm_crtc_index(crtc)); >>>> + >>>> +   drm_crtc_wait_one_vblank(crtc); >>>> + >>>> +   drm_crtc_vblank_off(crtc); >>>> + >>>> +   drm_dbg(ddev, "%s: disabled\n", crtc->name); >>>> +} >>>> + >>>> +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 drm_device *ddev = crtc->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   unsigned int index = drm_crtc_index(crtc); >>>> +   int line, vsw, vbp, vactive_start, vactive_end, vfp_end; >>>> +   u32 val = 0; >>>> + >>>> +   vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; >>>> +   vbp = mode->crtc_vtotal - mode->crtc_vsync_end; >>>> + >>>> +   vactive_start = vsw + vbp + 1; >>>> + >>>> +   vactive_end = vactive_start + mode->crtc_vdisplay; >>>> + >>>> +   /* last scan line before VSYNC */ >>>> +   vfp_end = mode->crtc_vtotal; >>>> + >>>> +   if (stime) >>>> +       *stime = ktime_get(); >>>> + >>>> +   if (index == 0) >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); >>>> +   else if (index == 1) >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); >>>> + >>>> +   line = (val & 0xffff); >>>> + >>>> +   if (line < vactive_start) >>>> +       line -= vactive_start; >>>> +   else if (line > vactive_end) >>>> +       line = line - vfp_end - vactive_start; >>>> +   else >>>> +       line -= vactive_start; >>>> + >>>> +   *vpos = line; >>>> +   *hpos = val >> 16; >>>> + >>>> +   if (etime) >>>> +       *etime = ktime_get(); >>>> + >>>> +   return true; >>>> +} >>>> + >>>> +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { >>>> +   .mode_valid = lsdc_crtc_mode_valid, >>>> +   .mode_set_nofb = lsdc_crtc_helper_mode_set_nofb, >>>> +   .atomic_enable = lsdc_crtc_helper_atomic_enable, >>>> +   .atomic_disable = lsdc_crtc_helper_atomic_disable, >>>> +   .atomic_check = lsdc_crtc_helper_atomic_check, >>>> +   .atomic_flush = lsdc_crtc_atomic_flush, >>>> +   .get_scanout_position = lsdc_crtc_get_scanout_position, >>>> +}; >>>> + >>>> +int lsdc_crtc_init(struct drm_device *ddev, >>>> +          struct drm_crtc *crtc, >>>> +          unsigned int index, >>>> +          struct drm_plane *primary, >>>> +          struct drm_plane *cursor) >>>> +{ >>>> +   int ret; >>>> + >>>> +   ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor, >>>> +                   &lsdc_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) >>>> +       drm_warn(ddev, "set the gamma table size failed\n"); >>>> + >>>> +   drm_crtc_enable_color_mgmt(crtc, 0, false, 256); >>> >>> Maybe leave this out for now. You need to handle the property in >>> your driver. I don't see code for that. >>> >>> Color management should probably go into a separate patch. >>> >> Ok, I'm agree to leave gamma support to a separate patch >>>> + >>>> +   return 0; >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>>> b/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>>> new file mode 100644 >>>> index 000000000000..176ae4e11d1c >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>>> @@ -0,0 +1,191 @@ >>>> +// SPDX-License-Identifier: GPL-2.0+ >>>> + >>>> +#include <drm/drm_debugfs.h> >>>> +#include <drm/drm_managed.h> >>>> +#include <drm/drm_gem_vram_helper.h> >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> +#include "lsdc_pll.h" >>>> + >>>> +#ifdef CONFIG_DEBUG_FS >>>> +static int lsdc_show_clock(struct seq_file *m, void *arg) >>>> +{ >>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>> +   struct drm_device *ddev = node->minor->dev; >>>> +   struct drm_crtc *crtc; >>>> + >>>> +   drm_for_each_crtc(crtc, ddev) { >>>> +       struct lsdc_display_pipe *pipe = crtc_to_display_pipe(crtc); >>>> +       struct lsdc_pll *pixpll = &pipe->pixpll; >>>> +       const struct lsdc_pixpll_funcs *funcs = pixpll->funcs; >>>> +       struct drm_display_mode *adj = &crtc->state->mode; >>>> +       struct lsdc_pll_parms parms; >>>> +       unsigned int out_khz; >>>> + >>>> +       out_khz = funcs->get_clock_rate(pixpll, &parms); >>>> + >>>> +       seq_printf(m, "Display pipe %u: %dx%d\n", >>>> +              pipe->index, adj->hdisplay, adj->vdisplay); >>>> + >>>> +       seq_printf(m, "Frequency actually output: %u kHz\n", >>>> out_khz); >>>> +       seq_printf(m, "Pixel clock required: %d kHz\n", adj->clock); >>>> +       seq_printf(m, "diff: %d kHz\n", adj->clock); >>>> + >>>> +       seq_printf(m, "div_ref=%u, loopc=%u, div_out=%u\n", >>>> +              parms.div_ref, parms.loopc, parms.div_out); >>>> + >>>> +       seq_printf(m, "hsync_start=%d, hsync_end=%d, htotal=%d\n", >>>> +              adj->hsync_start, adj->hsync_end, adj->htotal); >>>> +       seq_printf(m, "vsync_start=%d, vsync_end=%d, vtotal=%d\n\n", >>>> +              adj->vsync_start, adj->vsync_end, adj->vtotal); >>>> +   } >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_show_mm(struct seq_file *m, void *arg) >>>> +{ >>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>> +   struct drm_device *ddev = node->minor->dev; >>>> +   struct drm_printer p = drm_seq_file_printer(m); >>>> + >>>> + drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +#define REGDEF(reg) { __stringify_1(LSDC_##reg##_REG), >>>> LSDC_##reg##_REG } >>>> +static const struct { >>>> +   const char *name; >>>> +   u32 reg_offset; >>>> +} lsdc_regs_array[] = { >>>> +   REGDEF(CURSOR0_CFG), >>>> +   REGDEF(CURSOR0_ADDR_LO), >>>> +   REGDEF(CURSOR0_ADDR_HI), >>>> +   REGDEF(CURSOR0_POSITION), >>>> +   REGDEF(CURSOR0_BG_COLOR), >>>> +   REGDEF(CURSOR0_FG_COLOR), >>>> +   REGDEF(INT), >>>> +   REGDEF(CRTC0_CFG), >>>> +   REGDEF(CRTC0_STRIDE), >>>> +   REGDEF(CRTC0_FB_ORIGIN), >>>> +   REGDEF(CRTC0_HDISPLAY), >>>> +   REGDEF(CRTC0_HSYNC), >>>> +   REGDEF(CRTC0_VDISPLAY), >>>> +   REGDEF(CRTC0_VSYNC), >>>> +   REGDEF(CRTC0_GAMMA_INDEX), >>>> +   REGDEF(CRTC0_GAMMA_DATA), >>>> +   REGDEF(CRTC1_CFG), >>>> +   REGDEF(CRTC1_STRIDE), >>>> +   REGDEF(CRTC1_FB_ORIGIN), >>>> +   REGDEF(CRTC1_HDISPLAY), >>>> +   REGDEF(CRTC1_HSYNC), >>>> +   REGDEF(CRTC1_VDISPLAY), >>>> +   REGDEF(CRTC1_VSYNC), >>>> +   REGDEF(CRTC1_GAMMA_INDEX), >>>> +   REGDEF(CRTC1_GAMMA_DATA), >>>> +}; >>>> + >>>> +static int lsdc_show_regs(struct seq_file *m, void *arg) >>>> +{ >>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>> +   struct drm_device *ddev = node->minor->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   int i; >>>> + >>>> +   for (i = 0; i < ARRAY_SIZE(lsdc_regs_array); i++) { >>>> +       u32 offset = lsdc_regs_array[i].reg_offset; >>>> +       const char *name = lsdc_regs_array[i].name; >>>> + >>>> +       seq_printf(m, "%s (0x%04x): 0x%08x\n", >>>> +              name, offset, lsdc_rreg32(ldev, offset)); >>>> +   } >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_show_vblank_counter(struct seq_file *m, void *arg) >>>> +{ >>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>> +   struct drm_device *ddev = node->minor->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> + >>>> +   seq_printf(m, "CRTC-0 vblank counter: %08u\n", >>>> +          lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG)); >>>> + >>>> +   seq_printf(m, "CRTC-1 vblank counter: %08u\n", >>>> +          lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG)); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_show_scan_position(struct seq_file *m, void *arg) >>>> +{ >>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>> +   struct drm_device *ddev = node->minor->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   u32 val0 = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); >>>> +   u32 val1 = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); >>>> + >>>> +   seq_printf(m, "CRTC-0: x: %08u, y: %08u\n", >>>> +          val0 >> 16, val0 & 0xFFFF); >>>> + >>>> +   seq_printf(m, "CRTC-1: x: %08u, y: %08u\n", >>>> +          val1 >> 16, val1 & 0xFFFF); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_show_fb_addr(struct seq_file *m, void *arg) >>>> +{ >>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>> +   struct drm_device *ddev = node->minor->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   u32 lo, hi; >>>> +   u32 val; >>>> + >>>> +   val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>> +   if (val & CFG_FB_IN_USING) { >>>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_LO_ADDR_REG); >>>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_HI_ADDR_REG); >>>> +       seq_printf(m, "CRTC-0 using fb1: 0x%x:%x\n", hi, lo); >>>> +   } else { >>>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_LO_ADDR_REG); >>>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_HI_ADDR_REG); >>>> +       seq_printf(m, "CRTC-0 using fb0: 0x%x:%x\n", hi, lo); >>>> +   } >>>> + >>>> +   val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>> +   if (val & CFG_FB_IN_USING) { >>>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_LO_ADDR_REG); >>>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_HI_ADDR_REG); >>>> +       seq_printf(m, "CRTC-1 using fb1: 0x%x:%x\n", hi, lo); >>>> +   } else { >>>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_LO_ADDR_REG); >>>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_HI_ADDR_REG); >>>> +       seq_printf(m, "CRTC-1 using fb0: 0x%x:%x\n", hi, lo); >>>> +   } >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static struct drm_info_list lsdc_debugfs_list[] = { >>>> +   { "clocks",  lsdc_show_clock, 0 }, >>>> +   { "mm",      lsdc_show_mm, 0, NULL }, >>>> +   { "regs",    lsdc_show_regs, 0 }, >>>> +   { "vblanks", lsdc_show_vblank_counter, 0, NULL }, >>>> +   { "scan_pos", lsdc_show_scan_position, 0, NULL }, >>>> +   { "fb_addr", lsdc_show_fb_addr, 0, NULL }, >>>> +}; >>>> + >>>> +#endif >>>> + >>>> +void lsdc_debugfs_init(struct drm_minor *minor) >>>> +{ >>>> +#ifdef CONFIG_DEBUG_FS >>>> +   drm_debugfs_create_files(lsdc_debugfs_list, >>>> +                ARRAY_SIZE(lsdc_debugfs_list), >>>> +                minor->debugfs_root, >>>> +                minor); >>>> +#endif >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.c >>>> b/drivers/gpu/drm/lsdc/lsdc_drv.c >>>> new file mode 100644 >>>> index 000000000000..3bd457e6e980 >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.c >>>> @@ -0,0 +1,635 @@ >>>> +// SPDX-License-Identifier: GPL-2.0+ >>>> +/* >>>> + * KMS driver for Loongson display controller >>>> + * Copyright (C) 2022 Loongson Corporation >>>> + * >>>> + * Authors: >>>> + *     Li Yi <liyi@loongson.cn> >>>> + *     Li Chenyang <lichenyang@loongson.cn> >>>> + *     Sui Jingfeng <suijingfeng@loongson.cn> >>>> + */ >>>> + >>>> +#include <linux/module.h> >>>> +#include <linux/pci.h> >>>> +#include <linux/of_address.h> >>>> +#include <drm/drm_aperture.h> >>>> +#include <drm/drm_vblank.h> >>>> +#include <drm/drm_fb_helper.h> >>>> +#include <drm/drm_gem_vram_helper.h> >>>> +#include <drm/drm_gem_framebuffer_helper.h> >>>> +#include <drm/drm_modeset_helper.h> >>>> +#include <drm/drm_atomic_helper.h> >>>> +#include <drm/drm_probe_helper.h> >>>> +#include <drm/drm_fbdev_generic.h> >>>> +#include "lsdc_drv.h" >>> >>> Alphebetical order. >> I will fix this problem at next version >>> >>>> + >>>> +static int lsdc_chip = -1; >>>> +MODULE_PARM_DESC(chip, "override chip id (-1 = probed by driver"); >>>> +module_param_named(chip, lsdc_chip, int, 0644); >>> >>> That's a development option. Better not add it to the published driver. >> >> Without this, compile with W=1 generate warnings. and removing chip >> descriptions for Loongson SoC >> >> is not wanted either. this option can be removed if DT support is >> upstream in the future. >> >> It allow us to developing driver before DT or ACPI support is ready. >> Otherwise we have no way developing >> >> drivers for loongson SoC beforehand. >> >>>> + >>>> +/* >>>> + * ls7a1000 and ls7a2000 is not only used with loongson desktop >>>> class CPU >>>> + * but also loongson server class CPUs (LS3C5000@2.2GHz 16 core). >>>> Loongson >>>> + * server products generally don't has a dedicated vram mounted >>>> for cost >>>> + * reason, it will use aspeed bmc as its display on its final and >>>> formal >>>> + * product. lsdc.has_vram=0 is mainly be use in developing phase >>>> of the >>>> + * product. When a board don't has a dedicated vram mounted, we >>>> can put >>>> + * the framebuffer on system RAM. >>>> + */ >>>> +static int lsdc_has_vram = -1; >>>> +MODULE_PARM_DESC(has_vram, "has on-board gpu memory in reality (0 >>>> = no, -1 = default"); >>>> +module_param_named(has_vram, lsdc_has_vram, int, 0644); >>> >>> Same here. That's for development. >> >> Err, we truly very need this feature, >> >> 1) ASPEED BMC card is removable, it can be unplug from the mother >> board of Loongson Server. >> >> leaving this option there, we still have change enter X server >> graphic environment. >> >> Hi, "change" is typos, we still have chance enter X... With this option, we have the opportunity to let VRAM helper manage system ram, so that we can use VRAM helper based driver for loongson SoC devices. We may potentially have chance to improve VRAM helper, for example using ttm_cached caching property for system ram. Would this feasible? >> 2) We need developing drivers for loongson SoC on loongson PC >> platform, we may deliberately don't >> >> use on-board vram. there a need to let TTM manage cached reserved >> RAM, because such device don't >> >> have vram. We have achieve this goal on ls2k2000 SoC. >> >> Otherwise we have to write two piece of driver, one is DMA helper >> base, the other is TTM based, >> >> we doing so at downstream kernel. >> >>> >>>> + >>>> +#define DRIVER_AUTHOR       "Sui Jingfeng <suijingfeng@loongson.cn>" >>>> +#define DRIVER_NAME       "lsdc" >>>> +#define DRIVER_DESC       "drm driver for loongson's display >>>> controller" >>>> +#define DRIVER_DATE       "20220701" >>>> +#define DRIVER_MAJOR       1 >>>> +#define DRIVER_MINOR       0 >>>> +#define DRIVER_PATCHLEVEL   0 >>>> + >>>> +static const struct lsdc_desc dc_in_ls2k0500 = { >>> >>> Those description structs are a two-edged sword IMHO. >>> >>>> +   .chip = CHIP_LS2K0500, >>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>> +   .max_pixel_clk = 200000, >>>> +   .max_width = 2048, >>>> +   .max_height = 2048, >>>> +   .hw_cursor_w = 32, >>>> +   .hw_cursor_h = 32, >>>> +   .pitch_align = 256, >>>> +   .mc_bits = 40, >>> >>> These are device constants are probably fine. >>> >>>> +   .num_of_hw_cursor = 1, >>>> +   .has_vblank_counter = false, >>>> +   .has_builtin_i2c = false, >>>> +   .has_vram = false, >>>> +   .has_hpd_reg = false, >>>> +   .is_soc = true, >>> >>> But from a quick look through the code, these settings affect >>> control flow and/or modesetting pipelines. They are often better >>> implemented by dedicated helpers as outlined in my comments about >>> vblank and mgag200. >> >> Ok, mgag200 is for server mainly, its variant are not so different as >> loongson does, >> >> at least they all have vram, we have to put more attention to turn >> drm memory manager. >> >> I will following your advice by introducing more helpers at next >> version. >> >>> >>> >>>> +}; >>>> + >>>> +static const struct lsdc_desc dc_in_ls2k1000 = { >>>> +   .chip = CHIP_LS2K1000, >>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>> +   .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, >>>> +   .has_builtin_i2c = false, >>>> +   .has_vram = false, >>>> +   .has_hpd_reg = false, >>>> +   .is_soc = true, >>>> +}; >>>> + >>>> +static const struct lsdc_desc dc_in_ls2k2000 = { >>>> +   .chip = CHIP_LS2K2000, >>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>> +   .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 = 256, >>>> +   .mc_bits = 40, >>>> +   .has_vblank_counter = true, >>>> +   .has_builtin_i2c = true, >>>> +   .has_vram = false, >>>> +   .has_hpd_reg = true, >>>> +   .is_soc = true, >>>> +}; >>>> + >>>> +static const struct lsdc_desc dc_in_ls7a1000 = { >>>> +   .chip = CHIP_LS7A1000, >>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>> +   .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, >>>> +   .has_builtin_i2c = true, >>>> +   .has_vram = true, >>>> +   .has_hpd_reg = false, >>>> +   .is_soc = false, >>>> +}; >>>> + >>>> +static const struct lsdc_desc dc_in_ls7a2000 = { >>>> +   .chip = CHIP_LS7A2000, >>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>> +   .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 = 256, >>>> +   .mc_bits = 40, /* support 48, but use 40 for backward >>>> compatibility */ >>>> +   .has_vblank_counter = true, >>>> +   .has_builtin_i2c = true, >>>> +   .has_vram = true, >>>> +   .has_hpd_reg = true, >>>> +   .is_soc = false, >>>> +}; >>>> + >>>> +static int lsdc_gem_dumb_create(struct drm_file *file, >>>> +               struct drm_device *ddev, >>>> +               struct drm_mode_create_dumb *args) >>>> +{ >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   return drm_gem_vram_fill_create_dumb(file, ddev, 0, >>>> descp->pitch_align, args); >>>> +} >>>> + >>>> +DEFINE_DRM_GEM_FOPS(lsdc_gem_fops); >>>> + >>>> +static const struct drm_driver lsdc_drm_driver = { >>>> +   .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, >>>> +   .fops = &lsdc_gem_fops, >>>> + >>>> +   .name = DRIVER_NAME, >>>> +   .desc = DRIVER_DESC, >>>> +   .date = DRIVER_DATE, >>>> +   .major = DRIVER_MAJOR, >>>> +   .minor = DRIVER_MINOR, >>>> +   .patchlevel = DRIVER_PATCHLEVEL, >>>> + >>>> +   .debugfs_init = lsdc_debugfs_init, >>>> +   .dumb_create = lsdc_gem_dumb_create, >>>> +   .dumb_map_offset = drm_gem_ttm_dumb_map_offset, >>>> +   .gem_prime_mmap = drm_gem_prime_mmap, >>>> +}; >>>> + >>>> +static const struct drm_mode_config_funcs lsdc_mode_config_funcs = { >>>> +   .fb_create = drm_gem_fb_create, >>>> +   .output_poll_changed = drm_fb_helper_output_poll_changed, >>>> +   .atomic_check = drm_atomic_helper_check, >>>> +   .atomic_commit = drm_atomic_helper_commit, >>>> +   .mode_valid = drm_vram_helper_mode_valid, >>>> +}; >>>> + >>>> +static int lsdc_modeset_init(struct lsdc_device *ldev, >>>> +                const struct lsdc_desc *descp) >>>> +{ >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   unsigned int num_crtc = descp->num_of_crtc; >>>> +   unsigned int i; >>>> +   int ret; >>>> + >>>> +   for (i = 0; i < num_crtc; i++) { >>>> +       ret = lsdc_create_output(ldev, i); >>>> +       if (ret) >>>> +           return ret; >>>> +   } >>>> + >>>> +   for (i = 0; i < num_crtc; i++) { >>>> +       struct lsdc_display_pipe *dispipe = &ldev->dispipe[i]; >>>> +       struct lsdc_pll *pixpll = &dispipe->pixpll; >>>> +       struct drm_plane *primary = &dispipe->primary; >>>> +       struct drm_plane *cursor = &dispipe->cursor; >>>> +       struct drm_crtc *crtc = &dispipe->crtc; >>>> + >>>> +       dispipe->index = i; >>>> + >>>> +       ret = lsdc_pixpll_init(pixpll, ddev, i); >>>> +       if (ret) >>>> +           return ret; >>>> + >>>> +       ret = lsdc_plane_init(ldev, primary, >>>> DRM_PLANE_TYPE_PRIMARY, i); >>>> +       if (ret) >>>> +           return ret; >>>> + >>>> +       ret = lsdc_plane_init(ldev, cursor, DRM_PLANE_TYPE_CURSOR, >>>> i); >>>> +       if (ret) >>>> +           return ret; >>>> + >>>> +       /* >>>> +        * Initial all of the CRTC available, in this way the logical >>>> +        * crtc index is equal to the hardware crtc index. >>>> +        * >>>> +        * display pipe 0 = crtc0 + dvo0 + encoder0 + connector0 >>>> +        * display pipe 1 = crtc1 + dvo1 + encoder1 + connector1 >>>> +        */ >>>> +       ret = lsdc_crtc_init(ddev, crtc, i, primary, cursor); >>>> +       if (ret) >>>> +           return ret; >>>> + >>>> +       drm_info(ddev, "display pipe %u initialized\n", i); >>>> +   } >>>> + >>>> +   drm_mode_config_reset(ddev); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_mode_config_init(struct drm_device *ddev, >>>> +                const struct lsdc_desc *descp) >>>> +{ >>>> +   int ret; >>>> + >>>> +   ret = drmm_mode_config_init(ddev); >>>> +   if (ret) >>>> +       return ret; >>>> + >>>> +   ddev->mode_config.funcs = &lsdc_mode_config_funcs; >>>> +   ddev->mode_config.min_width = 1; >>>> +   ddev->mode_config.min_height = 1; >>>> +   ddev->mode_config.max_width = descp->max_width * LSDC_NUM_CRTC; >>>> +   ddev->mode_config.max_height = descp->max_height * LSDC_NUM_CRTC; >>>> +   ddev->mode_config.preferred_depth = 24; >>>> +   ddev->mode_config.prefer_shadow = descp->has_vram; >>>> + >>>> +   ddev->mode_config.cursor_width = descp->hw_cursor_h; >>>> +   ddev->mode_config.cursor_height = descp->hw_cursor_h; >>>> + >>>> +   /* max_vblank_count is set on each CRTC */ >>>> +   ddev->max_vblank_count = 0xffffffff; >>>> + >>>> +   return ret; >>>> +} >>>> + >>>> +/* >>>> + * lsdc_detect_chip - a function to tell lsdc variant apart. >>>> + */ >>>> +static const struct lsdc_desc * >>>> +lsdc_detect_chip(struct pci_dev *pdev, const struct pci_device_id >>>> *ent) >>>> +{ >>>> +   /* >>>> +    * Provide a way to false the driver running on new model >>>> +    * before we have a proper way to tell them apart >>>> +    */ >>>> +   if (lsdc_chip == CHIP_LS2K2000) { >>>> +       dev_info(&pdev->dev, "Override as LS2K2000\n"); >>>> +       return &dc_in_ls2k2000; >>>> +   } >>>> + >>>> +   if (lsdc_chip == CHIP_LS2K1000) { >>>> +       dev_info(&pdev->dev, "Override as LS2K1000\n"); >>>> +       return &dc_in_ls2k1000; >>>> +   } >>>> + >>>> +   if (lsdc_chip == CHIP_LS2K0500) { >>>> +       dev_info(&pdev->dev, "Override as LS2K0500\n"); >>>> +       return &dc_in_ls2k0500; >>>> +   } >>>> + >>>> +   if (ent) { >>>> +       struct lsdc_desc *dc_descp; >>>> + >>>> +       dc_descp = (struct lsdc_desc *)ent->driver_data; >>> >>> Is that legal? driver_data is of kernel_ulong_t. Maybe store the >>> CHIP_ constant here and then lookup the lsdc_desc. >>> >> It works in practice but it seems dangerous doing so, I will using >> CHIP_ LS7AXXXX and CHIP_LS2KXXXX at new version. >> >> i don't notice it before, thank you. >> >>> >>> https://elixir.bootlin.com/linux/latest/source/include/linux/mod_devicetable.h#L36 >>> >>> >>>> +       if (dc_descp->chip == CHIP_LS7A1000) >>>> +           dev_info(&pdev->dev, "LS7A1000 Found revision: %u\n", >>>> pdev->revision); >>>> +       else if (dc_descp->chip == CHIP_LS7A2000) >>>> +           dev_info(&pdev->dev, "LS7A2000 Found revison: %u\n", >>>> pdev->revision); >>>> + >>>> +       return dc_descp; >>>> +   } >>>> + >>>> +   dev_err(&pdev->dev, "Not known device\n"); >>>> + >>>> +   return NULL; >>>> +} >>>> + >>>> +static int lsdc_get_dedicated_vram(struct lsdc_device *ldev, >>>> +                  const struct lsdc_desc *descp) >>>> +{ >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   struct pci_dev *gpu; >>>> +   resource_size_t base, size; >>>> + >>>> +   /* >>>> +    * The GPU and display controller in LS7A1000/LS7A2000 are >>>> separated >>>> +    * PCIE devices, they are two devices not one. The DC is a pci >>>> device, >>>> +    * but it don't have a dedicate VRAM bar, the BIOS engineer >>>> choose to >>>> +    * assign the VRAM to the gpu device. Sadly, after years >>>> application, >>>> +    * this decision form as a convention for loongson integrate >>>> graphics. >>>> +    * For LS7A1000 and LS7A2000, bar 2 of GPU device contain the >>>> VRAM, >>>> +    * both the GPU and the DC can make use of the VRAM depend on >>>> how DRM >>>> +    * device driver is written. Therefore, we have to do some >>>> tricks here. >>>> +    */ >>>> +   if (descp->chip == CHIP_LS7A1000) >>>> +       gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A15, NULL); >>>> +   else if (descp->chip == CHIP_LS7A2000) >>>> +       gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A25, NULL); >>>> + >>>> +   if (!gpu) { >>>> +       drm_warn(ddev, "No GPU device found\n"); >>>> +       return -ENODEV; >>>> +   } >>>> + >>>> +   base = pci_resource_start(gpu, 2); >>>> +   size = pci_resource_len(gpu, 2); >>>> + >>>> +   ldev->vram_base = base; >>>> +   ldev->vram_size = size; >>>> + >>>> +   drm_info(ddev, "dedicated vram start: 0x%llx, size: %uMB\n", >>>> +        (u64)base, (u32)(size >> 20)); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +/* >>>> + * Loongson display controller is not equipped with IOMMU. >>>> + * In order to make this driver work with Loongson SoC, >>>> + * you need to either carveout part RAM as VRAM or reserve >>>> + * part system ram as vram. Pass the start address and size >>>> + * via DT or ACPI. >>>> + */ >>>> +static int lsdc_of_get_reserved_ram(struct lsdc_device *ldev) >>>> +{ >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   unsigned long size = 0; >>>> +   struct device_node *node; >>>> +   struct resource r; >>>> +   int ret; >>>> + >>>> +   node = of_parse_phandle(ddev->dev->of_node, "memory-region", 0); >>>> +   if (!node) { >>>> +       drm_err(ddev, "No memory-region property or no DT, abort\n"); >>>> +       return -ENOENT; >>>> +   } >>>> + >>>> +   ret = of_address_to_resource(node, 0, &r); >>>> +   of_node_put(node); >>>> +   if (ret) >>>> +       return ret; >>>> + >>>> +   size = r.end - r.start + 1; >>>> + >>>> +   ldev->vram_base = r.start; >>>> +   ldev->vram_size = size; >>>> + >>>> +   drm_info(ddev, "using VRAM carveout: %lx@%pa\n", size, &r.start); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int >>>> +lsdc_remove_conflicting_framebuffers(const struct drm_driver >>>> *pdriver, >>>> +                    resource_size_t base, >>>> +                    resource_size_t size) >>>> +{ >>>> +   return drm_aperture_remove_conflicting_framebuffers(base, >>>> +                               size, >>>> +                               false, >>>> +                               pdriver); >>>> +} >>> >>> No need to wrap this single function. >> >> Ok, Loongson upstream kernel support efifb and it backing memory is >> located at this range. >> >> we must properly remove it, otherwise they will have two fb >> node(/dev/fb0 and /dev/fb1) >> >>> >>>> + >>>> +static struct lsdc_device * >>>> +lsdc_create_device(struct pci_dev *pdev, >>>> +          const struct pci_device_id *ent, >>>> +          const struct drm_driver *drv) >>>> +{ >>>> +   struct lsdc_device *ldev; >>>> +   struct drm_device *ddev; >>>> +   const struct lsdc_desc *descp; >>>> +   int ret; >>>> + >>>> +   ldev = devm_drm_dev_alloc(&pdev->dev, drv, struct lsdc_device, >>>> base); >>>> +   if (IS_ERR(ldev)) >>>> +       return ldev; >>>> + >>>> +   ddev = &ldev->base; >>>> + >>>> +   pci_set_drvdata(pdev, ddev); >>>> + >>>> +   descp = lsdc_detect_chip(pdev, ent); >>>> +   if (!descp) >>>> +       return NULL; >>>> + >>>> +   ldev->descp = descp; >>>> + >>>> +   spin_lock_init(&ldev->reglock); >>>> + >>>> +   /* BAR 0 the DC device contains registers */ >>>> +   ldev->reg_base = pcim_iomap(pdev, 0, 0); >>>> +   if (!ldev->reg_base) >>>> +       return ERR_PTR(-EIO); >>>> + >>>> +   if (descp->has_vram && lsdc_has_vram) >>>> +       ret = lsdc_get_dedicated_vram(ldev, descp); >>>> +   else >>>> +       ret = lsdc_of_get_reserved_ram(ldev); >>>> + >>>> +   if (ret) { >>>> +       drm_err(ddev, "Init VRAM failed: %d\n", ret); >>>> +       return ERR_PTR(ret); >>>> +   } >>>> + >>>> +   ret = lsdc_remove_conflicting_framebuffers(drv, >>>> +                          ldev->vram_base, >>>> +                          ldev->vram_size); >>>> +   if (ret) { >>>> +       drm_err(ddev, "remove firmware framebuffers failed: %d\n", >>>> ret); >>>> +       return ERR_PTR(ret); >>>> +   } >>>> + >>>> +   ret = drmm_vram_helper_init(ddev, ldev->vram_base, >>>> ldev->vram_size); >>> >>> Using GEM VRAM helpers could become a problem and I'd advise against >>> them. >>> >> Err, I'm not want to agree this... >>> First of all, how much video memory is there? VRAM helpers need at >>> least 3 times the maximum consumption to be reliable.So if you have >>> a FullHD display that consumes ~8 Mib, you'd need 24 MiB of VRAM. >>> Hardware cursors can further interfere. >> >> Part of ls3a5000/ls3a4000+ls7a1000 machine have 64MB, another part of >> ls3a5000/ls3a4000+ls7a1000 have 128 MB. >> >> For ls7a1000(1080p) the maximum usage actually is two >> framebuffer(1920x1080x4x2) + two cursor (64x64x4x 2) + one fbcon >> (1920x1080x4) and LS7A2000 board has at lease 256MB VRAM. >> >> Therefore I can guarantee that we have at least 64MB VRAM,enough for >> double screen(1920x1080) + double cursor. >> >> I guess you means that there are some device(aspeed bmc) with limited >> VRAM size, but it is more a problem >> >> of the device itself(It is not vram helper's fault). Early version >> of ast driver would evict fbcon to system RAM >> >> to save VRAM usage, would this idea be a solution for the device with >> limited vram capacity today? >> >> >>> VRAM helpers also don't support buffer sharing with other devices. >>> That's a problem for some compositors. >>> >>> For those reasons, VRAM helpers are deprecated. I'd rather remove >>> them entirely. >> >> Its not that bad, VRAM helpers is good enough for our case as a first >> step. Our colleague and customers mainly >> >> using MATE and XFCE under X server graphic environment, they need >> basic 2D graphics environment most. >> >> We have tested our driver against macro and xfwm4, it actually works >> very well. Individual compositor may >> >> have a few problems, but it isn't a thing for our case currently. Can >> we solve those problem by other means? >> >> We could give up cross device buffer sharing support for chasing 2D >> graphic performance , we intend to >> >> introducing buffer sharing with the help of the GPU with another >> patch in the future. We possibly could >> >> switch to ttm-based KMS driver under your guiding then. For >> ls7a1000, I imagine to merge lsdc with evnaviv >> >> into a single drm driver.May I suggest to postpone the decision of >> removing VRAM helpers, its performance >> >> is better than shmem helper and have better support for double screen. >> >> >>> As a better alternative for the DC, you could use GEM SHMEM helpers. >>> See mgag200 or ast for how to do that. >> >> We have two display pipe thus support double screen, therefore we >> have two framebuffer BO and two cursor BO, >> >> we need to take clone screen, extend screen and rotate and resize >> case into consideration, on clone screen case >> >> the two CRTC should scanout from the same address, while on extend >> screen case, the frame buffer itself is grant >> >> one buffer if using modesetting driver, but we have two primary plane >> thus two fb offset. Arrange VRAM bo offset >> >> for 4 different drm BO object manually is little bit hack and >> cumbersome. I have already finish the developing >> >> work of shmem helper support, see: >> https://github.com/loongson-gfx/drm-tip/compare/drm-tip...shmem >> >> But it is NOT as good as VRAM helper in the perspective of >> performance. I think copy from the shadow to the >> >> front frame buffer should be done at user space, this can save the >> overhead of reporting damage rectangle whenever >> >> have a damage. >> >> >>> >>> The comments mention that there's also a GPU. Does the GPU use VRAM? >>> Or do GPU and DC share memory ranges? >>> >> LS7A2000 integrate Loongson self-made GPU, driver is developed by my >> colleagues, it already works but my mature >> >> to upstream yet. LS7A1000 have a VIVANTE GC1000 v5037 (20120617 >> etc), a ten years ago render-only GPU. >> >> the VRAM is connected with the ls7a1000 bridge chip, the memory >> controller is also in ls7a1000 bridge chip itself. >> >> ATI call this type vram as sideport, my company call it as GMEM, I >> think they are similar. >> >> etnaviv don't use VRAM currently, all BOs of etnaviv is in system ram. >> >> I think merge etnaviv and lsdc into a single drm driver would be a >> solution, what do you think? >> >> >> On downstream environment, we hacking make ttm-based lsdc work with >> etnaviv, by copying(sw resolve) >> >> the rendered buffer to vram backed framebuffer, glmark2 get 220 >> point, quake3 arena can play, haha :) >> >> It is achieved by developing xf86-video-loongson which using etnaviv >> to share buffer between xserver and glmark2, >> >> lsdc(or downstream loongson-drm) is only need to implement a >> self-sharing for userspace process . >> >> see https://github.com/loongson-gfx/xf86-video-loongson. The shared >> rendering buffer is copy >> >> to front buffer with loongson LSX and LASX(256-bit) SIMD >> optimization. Report dirty to kernel space is not need. >> >> we can even let suppertiled buffer rendered by etnaviv get exported, >> xf86-video-loongson get the fd and prime >> >> it to handle, then we are using CPU to do the resolve work which >> directly resolve to the front framebuffer. No damage >> >> rectangle reporting is need. >> >> >> Hence, the 2D performance of graphics is the more important. GEM >> SHMEM helpers is not better than vram helpers >> >> in the perspective of performance. >> >>>> +   if (ret) { >>>> +       drm_err(ddev, "vram helper init failed: %d\n", ret); >>>> +       goto err; >>>> +   } >>>> + >>>> +   ret = lsdc_mode_config_init(ddev, descp); >>>> +   if (ret) { >>>> +       drm_dbg(ddev, "%s: %d\n", __func__, ret); >>>> +       goto err; >>>> +   } >>>> + >>>> +   ret = lsdc_modeset_init(ldev, descp); >>>> +   if (ret) >>>> +       goto err; >>>> + >>>> +   ret = drm_vblank_init(ddev, descp->num_of_crtc); >>>> +   if (ret) >>>> +       goto err; >>>> + >>>> +   ret = request_threaded_irq(pdev->irq, >>>> +                  lsdc_get_irq_handler(ldev), >>>> +                  lsdc_irq_thread_handler, >>>> +                  IRQF_ONESHOT, >>>> +                  dev_name(ddev->dev), >>>> +                  ddev); >>>> +   if (ret) { >>>> +       drm_err(ddev, "Failed to register lsdc interrupt\n"); >>>> +       goto err; >>>> +   } >>>> + >>>> +   drm_kms_helper_poll_init(ddev); >>>> + >>>> +   return ldev; >>>> + >>>> +err: >>>> +   return ERR_PTR(ret); >>>> +} >>>> + >>>> +static int lsdc_pci_probe(struct pci_dev *pdev, >>>> +             const struct pci_device_id *ent) >>>> +{ >>>> +   struct drm_device *ddev; >>>> +   struct lsdc_device *ldev; >>>> +   int ret; >>>> + >>>> +   ret = pcim_enable_device(pdev); >>>> +   if (ret) >>>> +       return ret; >>>> + >>>> +   pci_set_master(pdev); >>>> + >>>> +   ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); >>>> +   if (ret) >>>> +       return ret; >>>> + >>>> +   ldev = lsdc_create_device(pdev, ent, &lsdc_drm_driver); >>>> +   if (IS_ERR(ldev)) >>>> +       return PTR_ERR(ldev); >>>> + >>>> +   ddev = &ldev->base; >>>> + >>>> +   ret = drm_dev_register(ddev, 0); >>>> +   if (ret) >>>> +       return ret; >>>> + >>>> +   drm_fbdev_generic_setup(ddev, 32); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static void lsdc_pci_remove(struct pci_dev *pdev) >>>> +{ >>>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>>> + >>>> +   drm_dev_unregister(ddev); >>>> +   drm_atomic_helper_shutdown(ddev); >>>> +} >>>> + >>>> +static int lsdc_drm_freeze(struct drm_device *ddev) >>>> +{ >>>> +   int error; >>>> + >>>> +   error = drm_mode_config_helper_suspend(ddev); >>>> +   if (error) >>>> +       return error; >>>> + >>>> +   pci_save_state(to_pci_dev(ddev->dev)); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_drm_resume(struct device *dev) >>>> +{ >>>> +   struct pci_dev *pdev = to_pci_dev(dev); >>>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>>> + >>>> +   return drm_mode_config_helper_resume(ddev); >>>> +} >>>> + >>>> +static int lsdc_pm_freeze(struct device *dev) >>>> +{ >>>> +   struct pci_dev *pdev = to_pci_dev(dev); >>>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>>> + >>>> +   return lsdc_drm_freeze(ddev); >>>> +} >>>> + >>>> +static int lsdc_pm_thaw(struct device *dev) >>>> +{ >>>> +   return lsdc_drm_resume(dev); >>>> +} >>>> + >>>> +static int lsdc_pm_suspend(struct device *dev) >>>> +{ >>>> +   struct pci_dev *pdev = to_pci_dev(dev); >>>> +   int error; >>>> + >>>> +   error = lsdc_pm_freeze(dev); >>>> +   if (error) >>>> +       return error; >>>> + >>>> +   pci_save_state(pdev); >>>> +   /* Shut down the device */ >>>> +   pci_disable_device(pdev); >>>> +   pci_set_power_state(pdev, PCI_D3hot); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_pm_resume(struct device *dev) >>>> +{ >>>> +   struct pci_dev *pdev = to_pci_dev(dev); >>>> + >>>> +   if (pcim_enable_device(pdev)) >>>> +       return -EIO; >>>> + >>>> +   pci_set_power_state(pdev, PCI_D0); >>>> + >>>> +   pci_restore_state(pdev); >>>> + >>>> +   return lsdc_pm_thaw(dev); >>>> +} >>>> + >>>> +static const struct dev_pm_ops lsdc_pm_ops = { >>>> +   .suspend = lsdc_pm_suspend, >>>> +   .resume = lsdc_pm_resume, >>>> +   .freeze = lsdc_pm_freeze, >>>> +   .thaw = lsdc_pm_thaw, >>>> +   .poweroff = lsdc_pm_freeze, >>>> +   .restore = lsdc_pm_resume, >>>> +}; >>>> + >>>> +static const struct pci_device_id lsdc_pciid_list[] = { >>>> +   {PCI_VENDOR_ID_LOONGSON, 0x7a06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, >>>> +    (kernel_ulong_t)&dc_in_ls7a1000}, >>>> +   {PCI_VENDOR_ID_LOONGSON, 0x7a36, PCI_ANY_ID, PCI_ANY_ID, 0, 0, >>>> +    (kernel_ulong_t)&dc_in_ls7a2000}, >>>> +   {0, 0, 0, 0, 0, 0, 0} >>>> +}; >>>> + >>>> +static struct pci_driver lsdc_pci_driver = { >>>> +   .name = DRIVER_NAME, >>>> +   .id_table = lsdc_pciid_list, >>>> +   .probe = lsdc_pci_probe, >>>> +   .remove = lsdc_pci_remove, >>>> +   .driver.pm = &lsdc_pm_ops, >>>> +}; >>>> + >>>> +static int __init lsdc_module_init(void) >>>> +{ >>>> +   struct pci_dev *pdev = NULL; >>>> + >>>> +   if (drm_firmware_drivers_only()) >>>> +       return -EINVAL; >>> >>>  return -ENODEV; >> >> OK, no problem, I will fix it at new version. I remember i already >> fix this, sorry. >> >>> >>>> + >>>> +   while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, >>>> pdev))) { >>>> +       /* >>>> +        * Multiple video card workaround: lsdc will always be >>>> +        * selected as the default boot device by vgaarb subsystem. >>>> +        */ >>>> +       if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) { >>>> +           pr_info("Discrete graphic card detected, abort\n"); >>>> +           return 0; >>>> +       } >>>> +   } >>> >>> I don't understand this code. You are not registering your driver if >>> there's another graphics card? >>> >> Yes, we do not want to registering this driver if there's discrete >> graphics card or BMC in the system. >> >> >> for example: >> >> 1) Loongson desktop computer(ls3a5000 + ls7a2000 with amdgpu rx580 >> mounted) >> >> On such a configuration, loongson display controller and rx580 >> co-exist in the system. >> >> As discrete GPU is more powerful, it seems that there no need to >> using LSDC driver anymore. >> >> The user is most likely want to using the rx580 as primary GPU and >> Using rx580 alone is enough for user in such a case. >> >> It is introduce unnecessary problems and confusion If we leave lsdc >> driver live on the system together with amdgpu/ati drm driver. >> >> >> I have just tested the case which leave lsdc driver live on the >> system together with radeon drm driver. >> >> the monitor connected with lsdc is black, not working as a output slave. >> >> >> 2) Loongson ls3c5000 server with aspeed 2400/25000 BMC card mounted. >> >> The BMC provide remote access and control besides the graphics, use >> it alone is enough, we have solve >> >> 4 ~ 5 bugs on our product side. we don't want make a mess. >> >> our tester get confused, than find a amdgpu and plug in, then he get >> three video co-exist in the sistem. >> >> lsdc, ast and amdgpu, there /dev/dri/card0, /dev/dri/car1 and >> /dev/dri/card2. >> >> if i bring etnaviv up, then i can get four. >> >> >>> Best regards >>> Thomas >>> >> Thinks your valuable advice and guiding, we will take time to fix all >> the problems your mentioned, >> >> and prepare next version. > > The driver still have a few problem, i will fix it if i can found, but > it already can suit the need for > > daily usage for programmer. hope i can help to merge this driver, i > holding it on my hand > > more than one years . If i miss anything, please remind me, any > discussion is welcome. > >> >>>> + >>>> +   return pci_register_driver(&lsdc_pci_driver); >>>> +} >>>> +module_init(lsdc_module_init); >>>> + >>>> +static void __exit lsdc_module_exit(void) >>>> +{ >>>> +   pci_unregister_driver(&lsdc_pci_driver); >>>> +} >>>> +module_exit(lsdc_module_exit); >>>> + >>>> +MODULE_DEVICE_TABLE(pci, lsdc_pciid_list); >>>> +MODULE_AUTHOR(DRIVER_AUTHOR); >>>> +MODULE_DESCRIPTION(DRIVER_DESC); >>>> +MODULE_LICENSE("GPL"); >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.h >>>> b/drivers/gpu/drm/lsdc/lsdc_drv.h >>>> new file mode 100644 >>>> index 000000000000..d67dae0414ca >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.h >>>> @@ -0,0 +1,274 @@ >>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>> +/* >>>> + * KMS driver for Loongson display controller >>>> + * Copyright (C) 2022 Loongson Corporation >>>> + * >>>> + * Authors: >>>> + *     Li Yi <liyi@loongson.cn> >>>> + *     Li Chenyang <lichenyang@loongson.cn> >>>> + *     Sui Jingfeng <suijingfeng@loongson.cn> >>>> + */ >>>> + >>>> +#ifndef __LSDC_DRV_H__ >>>> +#define __LSDC_DRV_H__ >>>> + >>>> +#include <linux/i2c.h> >>>> +#include <linux/i2c-algo-bit.h> >>>> + >>>> +#include <drm/drm_print.h> >>>> +#include <drm/drm_device.h> >>>> +#include <drm/drm_crtc.h> >>>> +#include <drm/drm_plane.h> >>>> +#include <drm/drm_connector.h> >>>> +#include <drm/drm_encoder.h> >>>> +#include <drm/drm_drv.h> >>>> +#include <drm/drm_atomic.h> >>>> + >>>> +#include "lsdc_regs.h" >>>> +#include "lsdc_pll.h" >>>> + >>>> +#define LSDC_NUM_CRTC          2 >>>> + >>>> +/* >>>> + * The display controller in LS7A2000 integrate three loongson >>>> self-made >>>> + * encoder. Display pipe 0 has a transparent VGA encoder and a >>>> HDMI phy, >>>> + * they are parallel. Display pipe 1 has only one HDMI phy. >>>> + *      ______________________ _____________ >>>> + *     |            +-----+ | |            | >>>> + *     | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA >>>> Monitor |<---+ >>>> + *     |       |   +-----+ | |_____________|   | >>>> + *     |       |            | ______________   | >>>> + *     |       |   +------+ | |             |  | >>>> + *     |       +--> | HDMI | ----> HDMI Connector --> | HDMI >>>> Monitor |<--+ >>>> + *     |            +------+ | |______________|  | >>>> + *     |           +------+ >>>> |                                          | >>>> + *     |           | i2c6 | >>>> <-------------------------------------------+ >>>> + *     |           +------+ | >>>> + *     |                     | >>>> + *     |   DC in LS7A2000   | >>>> + *     |                     | >>>> + *     |           +------+ | >>>> + *     |           | i2c7 | <--------------------------------+ >>>> + *     |           +------+ | | >>>> + *     |                     | ______|_______ >>>> + *     |           +------+ | |             | >>>> + *     | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI >>>> Monitor | >>>> + *     |           +------+ | |______________| >>>> + *     |______________________| >>>> + * >>>> + * >>>> + * The display controller in LS7A1000 integrate two-way DVO, external >>>> + * encoder is required except use directly with dpi(rgb888) panel. >>>> + * ___________________ _________ >>>> + *     |           -------| |        | >>>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>>> Display | >>>> + *     | _  _    -------|       ^            ^ |_________| >>>> + *     | | | | |   -------|       |            | >>>> + *     | |_| |_|   | i2c6 <--------+-------------+ >>>> + *     |           -------| >>>> + *     | _  _    -------| >>>> + *     | | | | |   | i2c7 <--------+-------------+ >>>> + *     | |_| |_|   -------|       | | _________ >>>> + *     |           -------|       |            | |        | >>>> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | >>>> Panel | >>>> + *     |           -------| |_________| >>>> + *     |___________________| >>>> + * >>>> + * >>>> + * The display controller in LS2K1000. >>>> + * ___________________ _________ >>>> + *     |           -------| |        | >>>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>>> Display | >>>> + *     | _  _    -------|       ^             ^ |_________| >>>> + *     | | | | |          |       |             | >>>> + *     | |_| |_|          |    +------+         | >>>> + *     |                  <---->| i2c0 |<---------+ >>>> + *     | DC in LS2K1000  |    +------+ >>>> + *     | _  _           |    +------+ >>>> + *     | | | | |          <---->| i2c1 |----------+ >>>> + *     | |_| |_|          |    +------+ | _________ >>>> + *     |           -------|       |             | |        | >>>> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | >>>> Panel | >>>> + *     |           -------| |_________| >>>> + *     |___________________| >>>> + * >>>> + * >>>> + * The display controller in LS2K0500, LS2K0500 has a built-in >>>> transparent >>>> + * VGA encoder which is connected to display pipe 1(CRTC1). >>>> + * ___________________ _________ >>>> + *     |           -------| |        | >>>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>>> Display | >>>> + *     | _  _    -------|       ^             ^ |_________| >>>> + *     | | | | |          |       |             | >>>> + *     | |_| |_|          |    +------+         | >>>> + *     |                  <---->| i2c4 |<---------+ >>>> + *     | DC in LS2K0500  |    +------+ >>>> + *     | _  _           |    +------+ >>>> + *     | | | | |          <---->| i2c5 |-------------------+ >>>> + *     | |_| |_|          |    +------+ ______|______ >>>> + *     |          +-----+ | |            | >>>> + *     | CRTC1 --> | VGA | ------------------------> | VGA Monitor | >>>> + *     |          +-----+ | |_____________| >>>> + *     |___________________| >>>> + * >>>> + * LS7A1000 and LS7A2000 are bridge chips, has dedicated Video RAM. >>>> + * while LS2K2000/LS2K1000/LS2K0500 are SoC, they don't have >>>> dediacated >>>> + * Video RAM. The dc in LS2K2000 is basicly same with the dc in >>>> LS7A1000 >>>> + * except that LS2K2000 don't have a video RAM and have only one >>>> built-in >>>> + * hdmi encoder. >>>> + * >>>> + * There is only a 1:1 mapping of encoders and connectors for the DC, >>>> + * each CRTC have two FB address registers. >>>> + */ >>>> + >>>> +enum loongson_chip_family { >>>> +   CHIP_UNKNOWN = 0, >>>> +   CHIP_LS7A1000 = 1, /* North bridge of >>>> LS3A3000/LS3A4000/LS3A5000 */ >>>> +   CHIP_LS7A2000 = 2, /* Update version of LS7A1000, with >>>> built-in HDMI encoder */ >>>> +   CHIP_LS2K0500 = 3, /* Reduced version of LS2K1000, single >>>> core */ >>>> +   CHIP_LS2K1000 = 4, /* 2-Core Mips64r2/LA264 SoC, 64-bit, 1.0 >>>> Ghz */ >>>> +   CHIP_LS2K2000 = 5, /* 2-Core 64-bit LA364 SoC, 1.2 ~ 1.5 Ghz */ >>>> +   CHIP_LAST, >>>> +}; >>>> + >>>> +struct lsdc_desc { >>>> +   enum loongson_chip_family chip; >>>> +   u32 num_of_crtc; >>>> +   u32 max_pixel_clk; >>>> +   u32 max_width; >>>> +   u32 max_height; >>>> +   u32 num_of_hw_cursor; >>>> +   u32 hw_cursor_w; >>>> +   u32 hw_cursor_h; >>>> +   u32 pitch_align; /* DMA alignment constraint */ >>>> +   u64 mc_bits;     /* physical address bus bit width */ >>>> +   bool has_vblank_counter; >>>> +   bool has_builtin_i2c; >>>> +   bool has_vram; >>>> +   bool has_hpd_reg; >>>> +   bool is_soc; >>>> +}; >>>> + >>>> +struct lsdc_i2c { >>>> +   struct i2c_adapter adapter; >>>> +   struct i2c_algo_bit_data bit; >>>> +   struct drm_device *ddev; >>>> +   void __iomem *reg_base; >>>> +   void __iomem *dir_reg; >>>> +   void __iomem *dat_reg; >>>> +   /* pin bit mask */ >>>> +   u8 sda; >>>> +   u8 scl; >>>> +}; >>>> + >>>> +/* >>>> + * struct lsdc_display_pipe - Abstraction of hardware display >>>> pipeline. >>>> + * @crtc: CRTC control structure >>>> + * @primary: framebuffer plane control structure >>>> + * @cursor: cursor plane control structure >>>> + * @output: output control structure of this display pipe bind >>>> + * @pixpll: pixel pll control structure >>>> + * @index: the index corresponding to the hardware display pipe >>>> + */ >>>> +struct lsdc_display_pipe { >>>> +   struct drm_crtc crtc; >>>> +   struct drm_plane primary; >>>> +   struct drm_plane cursor; >>>> +   struct drm_encoder encoder; >>>> +   struct drm_connector connector; >>>> +   struct lsdc_pll pixpll; >>>> +   struct lsdc_i2c *li2c; >>>> +   int index; >>>> +}; >>>> + >>>> +static inline struct lsdc_display_pipe * >>>> +crtc_to_display_pipe(struct drm_crtc *crtc) >>>> +{ >>>> +   return container_of(crtc, struct lsdc_display_pipe, crtc); >>>> +} >>>> + >>>> +static inline struct lsdc_display_pipe * >>>> +cursor_to_display_pipe(struct drm_plane *cursor) >>>> +{ >>>> +   return container_of(cursor, struct lsdc_display_pipe, cursor); >>>> +} >>>> + >>>> +static inline struct lsdc_display_pipe * >>>> +connector_to_display_pipe(struct drm_connector *conn) >>>> +{ >>>> +   return container_of(conn, struct lsdc_display_pipe, connector); >>>> +} >>>> + >>>> +static inline struct lsdc_display_pipe * >>>> +encoder_to_display_pipe(struct drm_encoder *enc) >>>> +{ >>>> +   return container_of(enc, struct lsdc_display_pipe, encoder); >>>> +} >>>> + >>>> +struct lsdc_crtc_state { >>>> +   struct drm_crtc_state base; >>>> +   struct lsdc_pll_parms pparms; >>>> +}; >>>> + >>>> +struct lsdc_device { >>>> +   struct drm_device base; >>>> + >>>> +   /* @reglock: protects concurrent register access */ >>>> +   spinlock_t reglock; >>>> +   void __iomem *reg_base; >>>> +   void __iomem *vram; >>>> +   resource_size_t vram_base; >>>> +   resource_size_t vram_size; >>>> +   u64 mc_mask; >>>> + >>>> +   struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC]; >>>> + >>>> +   /* @num_output: count the number of active display pipe */ >>>> +   unsigned int num_output; >>>> + >>>> +   /* @descp: features description of the DC variant */ >>>> +   const struct lsdc_desc *descp; >>>> + >>>> +   u32 irq_status; >>>> +}; >>>> + >>>> +static inline struct lsdc_device * >>>> +to_lsdc(struct drm_device *ddev) >>>> +{ >>>> +   return container_of(ddev, struct lsdc_device, base); >>>> +} >>>> + >>>> +static inline struct lsdc_crtc_state * >>>> +to_lsdc_crtc_state(struct drm_crtc_state *base) >>>> +{ >>>> +   return container_of(base, struct lsdc_crtc_state, base); >>>> +} >>>> + >>>> +void lsdc_debugfs_init(struct drm_minor *minor); >>>> + >>>> +int lsdc_crtc_init(struct drm_device *ddev, >>>> +          struct drm_crtc *crtc, >>>> +          unsigned int index, >>>> +          struct drm_plane *primary, >>>> +          struct drm_plane *cursor); >>>> + >>>> +int lsdc_plane_init(struct lsdc_device *ldev, >>>> +           struct drm_plane *plane, >>>> +           enum drm_plane_type type, >>>> +           unsigned int index); >>>> + >>>> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index); >>>> + >>>> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >>>> +                     void *base, >>>> +                     unsigned int index); >>>> + >>>> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >>>> int index); >>>> + >>>> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg); >>>> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev); >>>> + >>>> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset); >>>> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 >>>> val); >>>> + >>>> +#endif >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c >>>> b/drivers/gpu/drm/lsdc/lsdc_i2c.c >>>> new file mode 100644 >>>> index 000000000000..b380098409ca >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c >>>> @@ -0,0 +1,201 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include <linux/i2c.h> >>>> +#include <drm/drm_managed.h> >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> + >>>> +/* >>>> + * ls7a_gpio_i2c_set - set the state of a gpio pin indicated by mask >>>> + * @mask: gpio pin mask >>>> + * @state: "0" for low, "1" for high >>>> + */ >>>> +static void ls7a_gpio_i2c_set(struct lsdc_i2c * const li2c, int >>>> mask, int state) >>>> +{ >>>> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >>>> +   unsigned long flags; >>>> +   u8 val; >>>> + >>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>> + >>>> +   if (state) { >>>> +       /* >>>> +        * Setting this pin as input directly, write 1 for Input. >>>> +        * The external pull-up resistor will pull the level up >>>> +        */ >>>> +       val = readb(li2c->dir_reg); >>>> +       val |= mask; >>>> +       writeb(val, li2c->dir_reg); >>>> +   } else { >>>> +       /* First set this pin as output, write 0 for Output */ >>>> +       val = readb(li2c->dir_reg); >>>> +       val &= ~mask; >>>> +       writeb(val, li2c->dir_reg); >>>> + >>>> +       /* Then, make this pin output 0 */ >>>> +       val = readb(li2c->dat_reg); >>>> +       val &= ~mask; >>>> +       writeb(val, li2c->dat_reg); >>>> +   } >>>> + >>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>> +} >>>> + >>>> +/* >>>> + * ls7a_gpio_i2c_get - read value back from the gpio pin indicated >>>> by mask >>>> + * @mask: gpio pin mask >>>> + * return "0" for low, "1" for high >>>> + */ >>>> +static int ls7a_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask) >>>> +{ >>>> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >>>> +   unsigned long flags; >>>> +   u8 val; >>>> + >>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>> + >>>> +   /* First set this pin as input */ >>>> +   val = readb(li2c->dir_reg); >>>> +   val |= mask; >>>> +   writeb(val, li2c->dir_reg); >>>> + >>>> +   /* Then get level state from this pin */ >>>> +   val = readb(li2c->dat_reg); >>>> + >>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>> + >>>> +   return (val & mask) ? 1 : 0; >>>> +} >>>> + >>>> +static void ls7a_i2c_set_sda(void *i2c, int state) >>>> +{ >>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>> +   /* set state on the li2c->sda pin */ >>>> +   return ls7a_gpio_i2c_set(li2c, li2c->sda, state); >>>> +} >>>> + >>>> +static void ls7a_i2c_set_scl(void *i2c, int state) >>>> +{ >>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>> +   /* set state on the li2c->scl pin */ >>>> +   return ls7a_gpio_i2c_set(li2c, li2c->scl, state); >>>> +} >>>> + >>>> +static int ls7a_i2c_get_sda(void *i2c) >>>> +{ >>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>> +   /* read value from the li2c->sda pin */ >>>> +   return ls7a_gpio_i2c_get(li2c, li2c->sda); >>>> +} >>>> + >>>> +static int ls7a_i2c_get_scl(void *i2c) >>>> +{ >>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>> +   /* read the value from the li2c->scl pin */ >>>> +   return ls7a_gpio_i2c_get(li2c, li2c->scl); >>>> +} >>>> + >>>> +static void lsdc_destroy_i2c(struct drm_device *ddev, void *data) >>>> +{ >>>> +   struct lsdc_i2c *li2c = (struct lsdc_i2c *)data; >>>> + >>>> +   if (li2c) { >>>> +       i2c_del_adapter(&li2c->adapter); >>>> +       kfree(li2c); >>>> +   } >>>> +} >>>> + >>>> +/* >>>> + * The DC in ls7a1000/ls7a2000 have builtin gpio hardware >>>> + * >>>> + * @base: gpio reg base >>>> + * @index: output channel index, 0 for DVO0, 1 for DVO1 >>>> + */ >>>> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >>>> +                     void *base, >>>> +                     unsigned int index) >>>> +{ >>>> +   struct i2c_adapter *adapter; >>>> +   struct lsdc_i2c *li2c; >>>> +   int ret; >>>> + >>>> +   li2c = kzalloc(sizeof(*li2c), GFP_KERNEL); >>>> +   if (!li2c) >>>> +       return ERR_PTR(-ENOMEM); >>>> + >>>> +   if (index == 0) { >>>> +       li2c->sda = 0x01; /* pin 0 */ >>>> +       li2c->scl = 0x02; /* pin 1 */ >>>> +   } else if (index == 1) { >>>> +       li2c->sda = 0x04; /* pin 2 */ >>>> +       li2c->scl = 0x08; /* pin 3 */ >>>> +   } >>>> + >>>> +   li2c->reg_base = base; >>>> +   li2c->ddev = ddev; >>>> +   li2c->dir_reg = li2c->reg_base + LS7A_DC_GPIO_DIR_REG; >>>> +   li2c->dat_reg = li2c->reg_base + LS7A_DC_GPIO_DAT_REG; >>>> + >>>> +   li2c->bit.setsda = ls7a_i2c_set_sda; >>>> +   li2c->bit.setscl = ls7a_i2c_set_scl; >>>> +   li2c->bit.getsda = ls7a_i2c_get_sda; >>>> +   li2c->bit.getscl = ls7a_i2c_get_scl; >>>> +   li2c->bit.udelay = 5; >>>> +   li2c->bit.timeout = usecs_to_jiffies(2200); >>>> +   li2c->bit.data = li2c; >>>> + >>>> +   adapter = &li2c->adapter; >>>> +   adapter->algo_data = &li2c->bit; >>>> +   adapter->owner = THIS_MODULE; >>>> +   adapter->class = I2C_CLASS_DDC; >>>> +   adapter->dev.parent = ddev->dev; >>>> +   adapter->nr = -1; >>>> + >>>> +   snprintf(adapter->name, sizeof(adapter->name), "lsdc-i2c%u", >>>> index); >>>> + >>>> +   i2c_set_adapdata(adapter, li2c); >>>> + >>>> +   ret = i2c_bit_add_bus(adapter); >>>> +   if (ret) { >>>> +       kfree(li2c); >>>> +       return ERR_PTR(ret); >>>> +   } >>>> + >>>> +   ret = drmm_add_action_or_reset(ddev, lsdc_destroy_i2c, li2c); >>>> +   if (ret) >>>> +       return NULL; >>>> + >>>> +   drm_info(ddev, "%s(sda=%u, scl=%u) created for connector-%u\n", >>>> +        adapter->name, li2c->sda, li2c->scl, index); >>>> + >>>> +   return li2c; >>>> +} >>>> + >>>> +static int lsdc_get_i2c_id(struct lsdc_device *ldev, int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   if (descp->chip == CHIP_LS2K1000) >>>> +       return index; >>>> + >>>> +   if (descp->chip == CHIP_LS2K0500) >>>> +       return index + 2; >>>> + >>>> +   return index; >>>> +} >>>> + >>>> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >>>> +                    int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >>>> + >>>> +   if (descp->has_builtin_i2c) { >>>> +       struct lsdc_i2c *li2c = dispipe->li2c; >>>> + >>>> +       if (li2c) >>>> +           return &li2c->adapter; >>>> +   } >>>> + >>>> +   return i2c_get_adapter(lsdc_get_i2c_id(ldev, index)); >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.c >>>> b/drivers/gpu/drm/lsdc/lsdc_irq.c >>>> new file mode 100644 >>>> index 000000000000..71c85f09bb6f >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_irq.c >>>> @@ -0,0 +1,86 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include <drm/drm_vblank.h> >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> + >>>> +/* >>>> + * For the DC in ls7a2000, clearing interrupt status is achieved by >>>> + * write "1" to LSDC_INT_REG, For the DC in ls7a1000, ls2k1000 and >>>> + * ls2k0500, clearing interrupt status is achieved by write "0" to >>>> + * LSDC_INT_REG. Two different hardware engineer of Loongson modify >>>> + * it as their will. >>>> + */ >>>> + >>>> +/* For the DC in ls7a2000 */ >>>> +static irqreturn_t lsdc_irq_handler(int irq, void *arg) >>>> +{ >>>> +   struct drm_device *ddev = arg; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   u32 val; >>>> + >>>> +   /* Read the interrupt status */ >>>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>>> +   if ((val & INT_STATUS_MASK) == 0) { >>>> +       drm_warn(ddev, "no interrupt occurs\n"); >>>> +       return IRQ_NONE; >>>> +   } >>>> + >>>> +   ldev->irq_status = val; >>>> + >>>> +   /* write "1" to clear the interrupt status */ >>>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >>>> + >>>> +   return IRQ_WAKE_THREAD; >>>> +} >>>> + >>>> +/* For the DC in ls7a1000, ls2k1000 and ls2k0500 */ >>>> +static irqreturn_t lsdc_irq_handler_legacy(int irq, void *arg) >>>> +{ >>>> +   struct drm_device *ddev = arg; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   u32 val; >>>> + >>>> +   /* Read the interrupt status */ >>>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>>> +   if ((val & INT_STATUS_MASK) == 0) { >>>> +       drm_warn(ddev, "no interrupt occurs\n"); >>>> +       return IRQ_NONE; >>>> +   } >>>> + >>>> +   ldev->irq_status = val; >>>> + >>>> +   /* write "0" to clear the interrupt status */ >>>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val & ~INT_STATUS_MASK); >>>> + >>>> +   return IRQ_WAKE_THREAD; >>>> +} >>>> + >>>> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   if (descp->chip == CHIP_LS7A2000) >>>> +       return lsdc_irq_handler; >>>> + >>>> +   return lsdc_irq_handler_legacy; >>>> +} >>>> + >>>> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg) >>>> +{ >>>> +   struct drm_device *ddev = arg; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   struct drm_crtc *crtc; >>>> + >>>> +   if (ldev->irq_status & INT_CRTC0_VSYNC) { >>>> +       crtc = drm_crtc_from_index(ddev, 0); >>>> +       drm_crtc_handle_vblank(crtc); >>>> +   } >>>> + >>>> +   if (ldev->irq_status & INT_CRTC1_VSYNC) { >>>> +       crtc = drm_crtc_from_index(ddev, 1); >>>> +       drm_crtc_handle_vblank(crtc); >>>> +   } >>>> + >>>> +   return IRQ_HANDLED; >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_output.c >>>> b/drivers/gpu/drm/lsdc/lsdc_output.c >>>> new file mode 100644 >>>> index 000000000000..d4bc7666d9ea >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_output.c >>>> @@ -0,0 +1,452 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include <drm/drm_edid.h> >>>> +#include <drm/drm_probe_helper.h> >>>> +#include <drm/drm_atomic_helper.h> >>>> +#include <drm/drm_connector.h> >>>> +#include "lsdc_drv.h" >>>> + >>>> +static int lsdc_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 enum drm_connector_status >>>> +lsdc_unknown_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; >>>> +   } else { >>>> +       if (connector->connector_type == DRM_MODE_CONNECTOR_DPI) >>>> +           return connector_status_connected; >>>> + >>>> +       if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) >>>> +           return connector_status_connected; >>>> +   } >>>> + >>>> +   return connector_status_unknown; >>>> +} >>>> + >>>> +static enum drm_connector_status >>>> +lsdc_hdmi_connector_detect(struct drm_connector *connector, bool >>>> force) >>>> +{ >>>> +   struct lsdc_display_pipe *pipe = >>>> connector_to_display_pipe(connector); >>>> +   struct lsdc_device *ldev = to_lsdc(connector->dev); >>>> +   u32 val; >>>> + >>>> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >>>> + >>>> +   if (pipe->index == 0) { >>>> +       if (val & HDMI0_HPD_FLAG) >>>> +           return connector_status_connected; >>>> +   } >>>> + >>>> +   if (pipe->index == 1) { >>>> +       if (val & HDMI1_HPD_FLAG) >>>> +           return connector_status_connected; >>>> +   } >>>> + >>>> +   return connector_status_disconnected; >>>> +} >>>> + >>>> +static enum drm_connector_status >>>> +lsdc_hdmi_vga_connector_detect(struct drm_connector *connector, >>>> bool force) >>>> +{ >>>> +   struct lsdc_display_pipe *pipe = >>>> connector_to_display_pipe(connector); >>>> +   struct drm_device *ddev = connector->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   struct i2c_adapter *ddc; >>>> +   u32 val; >>>> + >>>> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >>>> + >>>> +   if (pipe->index == 1) { >>>> +       if (val & HDMI1_HPD_FLAG) >>>> +           return connector_status_connected; >>>> + >>>> +       return connector_status_disconnected; >>>> +   } >>>> + >>>> +   if (pipe->index == 0) { >>>> +       if (val & HDMI0_HPD_FLAG) >>>> +           return connector_status_connected; >>>> + >>>> +       ddc = connector->ddc; >>>> +       if (ddc) { >>>> +           if (drm_probe_ddc(ddc)) >>>> +               return connector_status_connected; >>>> + >>>> +           return connector_status_disconnected; >>>> +       } >>>> +   } >>>> + >>>> +   return connector_status_unknown; >>>> +} >>>> + >>>> +static struct drm_encoder * >>>> +lsdc_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 >>>> lsdc_connector_helpers = { >>>> +   .atomic_best_encoder = lsdc_connector_get_best_encoder, >>>> +   .get_modes = lsdc_get_modes, >>>> +}; >>>> + >>>> +static const struct drm_connector_funcs >>>> lsdc_unknown_connector_funcs = { >>>> +   .detect = lsdc_unknown_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 const struct drm_connector_funcs >>>> lsdc_hdmi_vga_connector_funcs = { >>>> +   .detect = lsdc_hdmi_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, >>>> +}; >>>> + >>>> +static const struct drm_connector_funcs lsdc_hdmi_connector_funcs = { >>>> +   .detect = lsdc_hdmi_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 const struct drm_connector_funcs * >>>> +lsdc_get_connector_func(struct lsdc_device *ldev, unsigned int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   if (descp->chip == CHIP_LS7A2000) { >>>> +       if (index == 0) >>>> +           return &lsdc_hdmi_vga_connector_funcs; >>>> + >>>> +       if (index == 1) >>>> +           return &lsdc_hdmi_connector_funcs; >>>> +   } >>>> + >>>> +   return &lsdc_unknown_connector_funcs; >>>> +} >>>> + >>>> +/* >>>> + * we provide a default support before DT/VBIOS is supported >>>> + */ >>>> +static int lsdc_get_encoder_type(struct lsdc_device *ldev, >>>> +                unsigned int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   if (descp->chip == CHIP_LS7A2000) { >>>> +       if (index == 0) >>>> +           return DRM_MODE_ENCODER_DAC; >>>> +       if (index == 1) >>>> +           return DRM_MODE_ENCODER_TMDS; >>>> +   } >>>> + >>>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>>> CHIP_LS2K1000) { >>>> +       if (index == 0) >>>> +           return DRM_MODE_ENCODER_DPI; >>>> +       if (index == 1) >>>> +           return DRM_MODE_ENCODER_DPI; >>>> +   } >>>> + >>>> +   if (descp->chip == CHIP_LS2K0500) { >>>> +       if (index == 0) >>>> +           return DRM_MODE_ENCODER_DPI; >>>> +       if (index == 1) >>>> +           return DRM_MODE_ENCODER_DAC; >>>> +   } >>>> + >>>> +   return DRM_MODE_ENCODER_NONE; >>>> +} >>>> + >>>> +/* >>>> + * provide a default before DT/VBIOS support is upstreamed >>>> + */ >>>> +static int lsdc_get_connector_type(struct lsdc_device *ldev, >>>> +                  unsigned int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   if (descp->chip == CHIP_LS7A2000) { >>>> +       if (index == 0) >>>> +           return DRM_MODE_CONNECTOR_VGA; >>>> +       if (index == 1) >>>> +           return DRM_MODE_CONNECTOR_HDMIA; >>>> +   } >>>> + >>>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>>> CHIP_LS2K1000) { >>>> +       if (index == 0) >>>> +           return DRM_MODE_CONNECTOR_DPI; >>>> +       if (index == 1) >>>> +           return DRM_MODE_CONNECTOR_DPI; >>>> +   } >>>> + >>>> +   if (descp->chip == CHIP_LS2K0500) { >>>> +       if (index == 0) >>>> +           return DRM_MODE_CONNECTOR_DPI; >>>> +       if (index == 1) >>>> +           return DRM_MODE_CONNECTOR_VGA; >>>> +   } >>>> + >>>> +   return DRM_MODE_CONNECTOR_Unknown; >>>> +} >>>> + >>>> +static void lsdc_hdmi_disable(struct drm_encoder *encoder) >>>> +{ >>>> +   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; >>>> + >>>> +   if (index == 0) >>>> +       val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG); >>>> +   else if (index == 1) >>>> +       val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG); >>>> + >>>> +   val &= ~HDMI_PHY_EN; >>>> + >>>> +   if (index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >>>> +   else if (index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >>>> + >>>> +   drm_dbg(ddev, "HDMI-%u disabled\n", index); >>>> +} >>>> + >>>> +static void lsdc_hdmi_enable(struct drm_encoder *encoder) >>>> +{ >>>> +   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; >>>> + >>>> +   /* we are using software gpio emulated i2c */ >>>> +   val = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >>>> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >>>> + >>>> +   if (index == 0) { >>>> +       /* Enable HDMI-0 */ >>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >>>> + >>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_ZONE_REG, 0x00400040); >>>> +   } else if (index == 1) { >>>> +       /* Enable HDMI-1 */ >>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >>>> + >>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_ZONE_REG, 0x00400040); >>>> +   } >>>> + >>>> +   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; >>>> + >>>> +   if (index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >>>> +   else if (index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >>>> + >>>> +   drm_dbg(ddev, "HDMI-%u enabled\n", index); >>>> +} >>>> + >>>> +/* >>>> + * Fout = M * Fin >>>> + * >>>> + * M = (4 * LF) / (IDF * ODF) >>>> + * >>>> + * Loongson HDMI require M = 10 >>>> + */ >>>> +static void lsdc_hdmi_phy_pll_config(struct lsdc_device *ldev, >>>> +                    int index, >>>> +                    int clock) >>>> +{ >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   int count = 0; >>>> +   u32 val; >>>> + >>>> +   /* disable phy pll */ >>>> +   if (index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, 0x0); >>>> +   else if (index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, 0x0); >>>> + >>>> +   /* >>>> +    * 10 = (4 * 40) / (8 * 2) >>>> +    */ >>>> +   val = (8 << HDMI_PLL_IDF_SHIFT) | >>>> +         (40 << HDMI_PLL_LF_SHIFT) | >>>> +         (1 << HDMI_PLL_ODF_SHIFT) | >>>> +         HDMI_PLL_ENABLE; >>>> + >>>> +   drm_dbg(ddev, "HDMI-%u clock: %d\n", index, clock); >>>> + >>>> +   if (index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, val); >>>> +   else if (index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, val); >>>> + >>>> +   /* wait hdmi phy pll lock */ >>>> +   do { >>>> +       if (index == 0) >>>> +           val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG); >>>> +       else if (index == 1) >>>> +           val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_PLL_REG); >>>> + >>>> +       ++count; >>>> + >>>> +       if (val & HDMI_PLL_LOCKED) { >>>> +           drm_dbg(ddev, "Setting HDMI-%u PLL success(take %d >>>> cycles)\n", >>>> +               index, count); >>>> +           break; >>>> +       } >>>> +   } while (count < 1000); >>>> +} >>>> + >>>> +static void lsdc_hdmi_atomic_mode_set(struct drm_encoder *encoder, >>>> +                     struct drm_crtc_state *crtc_state, >>>> +                     struct drm_connector_state *conn_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; >>>> +   struct drm_display_mode *mode = &crtc_state->mode; >>>> + >>>> +   lsdc_hdmi_phy_pll_config(ldev, index, mode->clock); >>>> + >>>> +   drm_dbg(ddev, "HDMI-%u modeset\n", index); >>>> +} >>>> + >>>> +static const struct drm_encoder_helper_funcs >>>> lsdc_hdmi_helper_funcs = { >>>> +   .disable = lsdc_hdmi_disable, >>>> +   .enable = lsdc_hdmi_enable, >>>> +   .atomic_mode_set = lsdc_hdmi_atomic_mode_set, >>>> +}; >>>> + >>>> +static void lsdc_hdmi_reset(struct drm_encoder *encoder) >>>> +{ >>>> +   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 = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >>>> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >>>> + >>>> +   lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >>>> +   lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >>>> + >>>> +   drm_dbg(ddev, "HDMI-%u Reset\n", index); >>>> +} >>>> + >>>> +static const struct drm_encoder_funcs lsdc_encoder_funcs = { >>>> +   .reset = lsdc_hdmi_reset, >>>> +   .destroy = drm_encoder_cleanup, >>>> +}; >>>> + >>>> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >>>> +   struct drm_encoder *encoder = &dispipe->encoder; >>>> +   struct drm_connector *connector = &dispipe->connector; >>>> +   struct i2c_adapter *ddc = NULL; >>>> +   struct lsdc_i2c *li2c; >>>> +   int ret; >>>> + >>>> +   ret = drm_encoder_init(ddev, >>>> +                  encoder, >>>> +                  &lsdc_encoder_funcs, >>>> +                  lsdc_get_encoder_type(ldev, index), >>>> +                  "encoder-%u", >>>> +                  index); >>>> +   if (ret) { >>>> +       drm_err(ddev, "Failed to init encoder: %d\n", ret); >>>> +       return ret; >>>> +   } >>>> + >>>> +   encoder->possible_crtcs = BIT(index); >>>> + >>>> +   if (descp->chip == CHIP_LS7A2000) >>>> +       drm_encoder_helper_add(encoder, &lsdc_hdmi_helper_funcs); >>>> + >>>> +   if (descp->has_builtin_i2c) { >>>> +       li2c = lsdc_create_i2c_chan(ddev, ldev->reg_base, index); >>>> +       if (IS_ERR(li2c)) >>>> +           return PTR_ERR(li2c); >>>> + >>>> +       dispipe->li2c = li2c; >>>> + >>>> +       ddc = &li2c->adapter; >>>> +   } else { >>>> +       ddc = lsdc_get_i2c_adapter(ldev, index); >>>> +       if (IS_ERR(ddc)) { >>>> +           drm_err(ddev, "Error get ddc for output-%u\n", index); >>>> +           return PTR_ERR(ddc); >>>> +       } >>>> +   } >>>> + >>>> +   ret = drm_connector_init_with_ddc(ddev, >>>> +                     connector, >>>> +                     lsdc_get_connector_func(ldev, index), >>>> +                     lsdc_get_connector_type(ldev, index), >>>> +                     ddc); >>>> +   if (ret) { >>>> +       drm_err(ddev, "Init connector-%d failed\n", index); >>>> +       return ret; >>>> +   } >>>> + >>>> +   drm_connector_helper_add(connector, &lsdc_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; >>>> + >>>> +   ldev->num_output++; >>>> + >>>> +   drm_info(ddev, "output-%u initialized\n", index); >>>> + >>>> +   return 0; >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_plane.c >>>> b/drivers/gpu/drm/lsdc/lsdc_plane.c >>>> new file mode 100644 >>>> index 000000000000..0f779c97d53b >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_plane.c >>>> @@ -0,0 +1,443 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include <drm/drm_atomic.h> >>>> +#include <drm/drm_atomic_helper.h> >>>> +#include <drm/drm_framebuffer.h> >>>> +#include <drm/drm_plane_helper.h> >>>> +#include <drm/drm_gem_vram_helper.h> >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> +#include "lsdc_pll.h" >>>> + >>>> +static const u32 lsdc_primary_formats[] = { >>>> +   DRM_FORMAT_XRGB8888, >>>> +   DRM_FORMAT_ARGB8888, >>>> +}; >>>> + >>>> +static const u32 lsdc_cursor_formats[] = { >>>> +   DRM_FORMAT_ARGB8888, >>>> +}; >>>> + >>>> +static const u64 lsdc_fb_format_modifiers[] = { >>>> +   DRM_FORMAT_MOD_LINEAR, >>>> +   DRM_FORMAT_MOD_INVALID >>>> +}; >>>> + >>>> +static void lsdc_update_fb_format(struct lsdc_device *ldev, >>>> +                 struct drm_crtc *crtc, >>>> +                 const struct drm_format_info *fmt_info) >>>> +{ >>>> +   unsigned int index = drm_crtc_index(crtc); >>>> +   u32 val; >>>> +   u32 fmt; >>>> + >>>> +   switch (fmt_info->format) { >>>> +   case DRM_FORMAT_XRGB8888: >>>> +       fmt = LSDC_PF_XRGB8888; >>>> +       break; >>>> +   case DRM_FORMAT_ARGB8888: >>>> +       fmt = LSDC_PF_XRGB8888; >>>> +       break; >>>> +   default: >>>> +       fmt = LSDC_PF_XRGB8888; >>>> +       break; >>>> +   } >>>> + >>>> +   if (index == 0) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>> +   } else if (index == 1) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>> +   } >>>> +} >>>> + >>>> +static void lsdc_update_fb_start_addr(struct lsdc_device *ldev, >>>> +                     struct drm_crtc *crtc, >>>> +                     u64 paddr) >>>> +{ >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   unsigned int index = drm_crtc_index(crtc); >>>> +   u32 lo_addr_reg; >>>> +   u32 hi_addr_reg; >>>> +   u32 val; >>>> + >>>> +   /* >>>> +    * Find which framebuffer address register should update. >>>> +    * if FB_ADDR0_REG is in using, we write the address to >>>> FB_ADDR0_REG, >>>> +    * if FB_ADDR1_REG is in using, we write the address to >>>> FB_ADDR1_REG >>>> +    * for each CRTC, the switch using one fb register to another is >>>> +    * trigger by triggered by set CFG_PAGE_FLIP bit of >>>> LSDC_CRTCx_CFG_REG >>>> +    */ >>>> +   if (index == 0) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>> +       if (val & CFG_FB_IN_USING) { >>>> +           lo_addr_reg = LSDC_CRTC0_FB1_LO_ADDR_REG; >>>> +           hi_addr_reg = LSDC_CRTC0_FB1_HI_ADDR_REG; >>>> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-0\n"); >>>> +       } else { >>>> +           lo_addr_reg = LSDC_CRTC0_FB0_LO_ADDR_REG; >>>> +           hi_addr_reg = LSDC_CRTC0_FB0_HI_ADDR_REG; >>>> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-0\n"); >>>> +       } >>>> +   } else if (index == 1) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>> +       if (val & CFG_FB_IN_USING) { >>>> +           lo_addr_reg = LSDC_CRTC1_FB1_LO_ADDR_REG; >>>> +           hi_addr_reg = LSDC_CRTC1_FB1_HI_ADDR_REG; >>>> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-1\n"); >>>> +       } else { >>>> +           lo_addr_reg = LSDC_CRTC1_FB0_LO_ADDR_REG; >>>> +           hi_addr_reg = LSDC_CRTC1_FB0_HI_ADDR_REG; >>>> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-1\n"); >>>> +       } >>>> +   } >>>> + >>>> +   /* 40-bit width physical address bus */ >>>> +   lsdc_wreg32(ldev, lo_addr_reg, paddr); >>>> +   lsdc_wreg32(ldev, hi_addr_reg, (paddr >> 32) & 0xFF); >>>> + >>>> +   drm_dbg(ddev, "CRTC-%u scantout from 0x%llx\n", index, paddr); >>>> +} >>>> + >>>> +static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb, >>>> +                      struct drm_plane_state *state, >>>> +                      unsigned int plane) >>>> +{ >>>> +   unsigned int offset = fb->offsets[plane]; >>>> + >>>> +   offset += fb->format->cpp[plane] * (state->src_x >> 16); >>>> +   offset += fb->pitches[plane] * (state->src_y >> 16); >>>> + >>>> +   return offset; >>>> +} >>>> + >>>> +static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb) >>>> +{ >>>> +   struct drm_gem_vram_object *gbo; >>>> +   s64 gpu_addr; >>>> + >>>> +   gbo = drm_gem_vram_of_gem(fb->obj[0]); >>>> +   gpu_addr = drm_gem_vram_offset(gbo); >>>> + >>>> +   return gpu_addr; >>>> +} >>>> + >>>> +static int lsdc_primary_plane_atomic_check(struct drm_plane *plane, >>>> +                      struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_plane_state *plane_state = >>>> drm_atomic_get_new_plane_state(state, plane); >>>> +   struct drm_crtc *crtc = plane_state->crtc; >>>> +   struct drm_crtc_state *crtc_state = >>>> drm_atomic_get_new_crtc_state(state, crtc); >>>> + >>>> +   if (!crtc) >>>> +       return 0; >>>> + >>>> +   return drm_atomic_helper_check_plane_state(plane_state, >>>> +                          crtc_state, >>>> +                          DRM_PLANE_NO_SCALING, >>>> +                          DRM_PLANE_NO_SCALING, >>>> +                          false, >>>> +                          true); >>>> +} >>>> + >>>> +static void lsdc_update_fb_stride(struct lsdc_device *ldev, >>>> +                 struct drm_crtc *crtc, >>>> +                 unsigned int stride) >>>> +{ >>>> +   unsigned int index = drm_crtc_index(crtc); >>>> + >>>> +   if (index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_STRIDE_REG, stride); >>>> +   else if (index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_STRIDE_REG, stride); >>>> + >>>> +   drm_dbg(crtc->dev, "update stride to %u\n", stride); >>>> +} >>>> + >>>> +static void lsdc_primary_plane_atomic_update(struct drm_plane *plane, >>>> +                        struct drm_atomic_state *state) >>>> +{ >>>> +   struct lsdc_device *ldev = to_lsdc(plane->dev); >>>> +   struct drm_plane_state *new_plane_state = >>>> drm_atomic_get_new_plane_state(state, plane); >>>> +   struct drm_crtc *crtc = new_plane_state->crtc; >>>> +   struct drm_framebuffer *fb = new_plane_state->fb; >>>> +   u32 fb_offset = lsdc_get_fb_offset(fb, new_plane_state, 0); >>>> +   dma_addr_t fb_addr; >>>> +   s64 gpu_addr; >>>> + >>>> +   gpu_addr = lsdc_get_vram_bo_offset(fb); >>>> +   if (gpu_addr < 0) >>>> +       return; >>>> + >>>> +   fb_addr = ldev->vram_base + gpu_addr + fb_offset; >>>> + >>>> +   lsdc_update_fb_start_addr(ldev, crtc, fb_addr); >>>> + >>>> +   lsdc_update_fb_stride(ldev, crtc, fb->pitches[0]); >>>> + >>>> +   lsdc_update_fb_format(ldev, crtc, fb->format); >>>> +} >>>> + >>>> +static void lsdc_primary_plane_atomic_disable(struct drm_plane >>>> *plane, >>>> +                         struct drm_atomic_state *state) >>>> +{ >>>> +   drm_dbg(plane->dev, "%s disabled\n", plane->name); >>>> +} >>>> + >>>> +static const struct drm_plane_helper_funcs >>>> lsdc_primary_plane_helpers = { >>>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>>> +   .atomic_check = lsdc_primary_plane_atomic_check, >>>> +   .atomic_update = lsdc_primary_plane_atomic_update, >>>> +   .atomic_disable = lsdc_primary_plane_atomic_disable, >>>> +}; >>>> + >>>> +static int lsdc_cursor_atomic_check(struct drm_plane *plane, >>>> +                   struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_plane_state *new_plane_state = >>>> drm_atomic_get_new_plane_state(state, plane); >>>> +   struct drm_framebuffer *fb = new_plane_state->fb; >>>> +   struct drm_crtc *crtc = new_plane_state->crtc; >>>> +   struct drm_crtc_state *crtc_state; >>>> +   int ret; >>>> + >>>> +   /* no need for further checks if the plane is being disabled */ >>>> +   if (!crtc || !fb) >>>> +       return 0; >>>> + >>>> +   if (!new_plane_state->visible) >>>> +       return 0; >>>> + >>>> +   crtc_state = drm_atomic_get_new_crtc_state(state, >>>> +                          new_plane_state->crtc); >>>> + >>>> +   ret = drm_atomic_helper_check_plane_state(new_plane_state, >>>> +                         crtc_state, >>>> +                         DRM_PLANE_NO_SCALING, >>>> +                         DRM_PLANE_NO_SCALING, >>>> +                         true, >>>> +                         true); >>>> + >>>> +   return ret; >>>> +} >>>> + >>>> +/* >>>> + * There is only one hardware cursor in ls7a1000, ls2k1000 and >>>> ls2k0500. >>>> + * we made it shared by the two CRTC, which can satisfy peoples >>>> who use >>>> + * double screen extend mode only. On clone screen usage case, the >>>> cursor >>>> + * on display pipe 1 will not be able to display. >>>> + * >>>> + * Update location of the cursor, attach it to CRTC0 or CRTC1 on >>>> the runtime. >>>> + */ >>>> +static void lsdc_cursor_update_location_quirks(struct lsdc_device >>>> *ldev, >>>> +                          struct drm_crtc *crtc) >>>> +{ >>>> +   u32 val = CURSOR_FORMAT_ARGB8888; >>>> + >>>> +   /* >>>> +    * If bit 4 of LSDC_CURSOR0_CFG_REG is 1, then the cursor will be >>>> +    * locate at CRTC-1, if bit 4 of LSDC_CURSOR0_CFG_REG is 0, then >>>> +    * the cursor will be locate at CRTC-0. The cursor is alway on >>>> the >>>> +    * top of the primary. Compositing the primary plane and cursor >>>> +    * plane is automatically done by hardware. >>>> +    */ >>>> +   if (drm_crtc_index(crtc)) >>>> +       val |= CURSOR_LOCATION; >>>> + >>>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, val); >>>> +} >>>> + >>>> +/* update the position of the cursor */ >>>> +static void lsdc_cursor_update_position_quirks(struct lsdc_device >>>> *ldev, >>>> +                          int x, >>>> +                          int y) >>>> +{ >>>> +   if (x < 0) >>>> +       x = 0; >>>> + >>>> +   if (y < 0) >>>> +       y = 0; >>>> + >>>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >>>> +} >>>> + >>>> +static void lsdc_cursor_atomic_update_quirks(struct drm_plane *plane, >>>> +                        struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_device *ddev = plane->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   struct drm_plane_state *new_plane_state = >>>> drm_atomic_get_new_plane_state(state, plane); >>>> +   struct drm_plane_state *old_plane_state = >>>> drm_atomic_get_old_plane_state(state, plane); >>>> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >>>> +   struct drm_framebuffer *old_fb = old_plane_state->fb; >>>> + >>>> +   if (new_fb != old_fb) { >>>> +       s64 offset = lsdc_get_vram_bo_offset(new_fb); >>>> +       u64 cursor_addr = ldev->vram_base + offset; >>>> + >>>> +       drm_dbg_kms(ddev, "%s offset: %llx\n", plane->name, offset); >>>> + >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >>>> >> 32) & 0xFF); >>>> +   } >>>> + >>>> +   lsdc_cursor_update_position_quirks(ldev, >>>> new_plane_state->crtc_x, new_plane_state->crtc_y); >>>> + >>>> +   lsdc_cursor_update_location_quirks(ldev, new_plane_state->crtc); >>>> +} >>>> + >>>> +/* update the format, size and location of the cursor */ >>>> +static void lsdc_cursor_atomic_update(struct drm_plane *plane, >>>> +                     struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_device *ddev = plane->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   struct lsdc_display_pipe *dispipe = >>>> cursor_to_display_pipe(plane); >>>> +   struct drm_plane_state *new_plane_state = >>>> drm_atomic_get_new_plane_state(state, plane); >>>> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >>>> +   int x = new_plane_state->crtc_x; >>>> +   int y = new_plane_state->crtc_y; >>>> +   u32 conf = CURSOR_FORMAT_ARGB8888 | CURSOR_SIZE_64X64; >>>> +   u64 cursor_addr = ldev->vram_base + >>>> lsdc_get_vram_bo_offset(new_fb); >>>> + >>>> +   if (x < 0) >>>> +       x = 0; >>>> + >>>> +   if (y < 0) >>>> +       y = 0; >>>> + >>>> +   if (dispipe->index == 0) { >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >>>> >> 32) & 0xFF); >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >>>> +       /* Attach Cursor-0 to CRTC-0 */ >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, conf & >>>> ~CURSOR_LOCATION); >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >>>> +       return; >>>> +   } >>>> + >>>> +   if (dispipe->index == 1) { >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_HI_REG, (cursor_addr >>>> >> 32) & 0xFF); >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_LO_REG, cursor_addr); >>>> +       /* Attach Cursor-1 to CRTC-1 */ >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, conf | >>>> CURSOR_LOCATION); >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_POSITION_REG, (y << 16) | x); >>>> +       return; >>>> +   } >>>> +} >>>> + >>>> +static void lsdc_cursor_atomic_disable_quirks(struct drm_plane >>>> *plane, >>>> +                         struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_device *ddev = plane->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> + >>>> +   /* Set the format to 0 actually display the cursor */ >>>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >>>> + >>>> +   drm_dbg(ddev, "%s disabled\n", plane->name); >>>> +} >>>> + >>>> +static void lsdc_cursor_atomic_disable(struct drm_plane *plane, >>>> +                      struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_device *ddev = plane->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   struct lsdc_display_pipe *dispipe = >>>> cursor_to_display_pipe(plane); >>>> + >>>> +   if (dispipe->index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >>>> +   else if (dispipe->index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, 0); >>>> + >>>> +   drm_dbg(ddev, "%s disabled\n", plane->name); >>>> +} >>>> + >>>> +static const struct drm_plane_helper_funcs >>>> lsdc_cursor_helpers_quirk = { >>>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>>> +   .atomic_check = lsdc_cursor_atomic_check, >>>> +   .atomic_update = lsdc_cursor_atomic_update_quirks, >>>> +   .atomic_disable = lsdc_cursor_atomic_disable_quirks, >>>> +}; >>>> + >>>> +static const struct drm_plane_helper_funcs >>>> lsdc_cursor_plane_helpers = { >>>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>>> +   .atomic_check = lsdc_cursor_atomic_check, >>>> +   .atomic_update = lsdc_cursor_atomic_update, >>>> +   .atomic_disable = lsdc_cursor_atomic_disable, >>>> +}; >>>> + >>>> +static const struct drm_plane_funcs lsdc_plane_funcs = { >>>> +   .update_plane = drm_atomic_helper_update_plane, >>>> +   .disable_plane = drm_atomic_helper_disable_plane, >>>> +   .destroy = drm_plane_cleanup, >>>> +   .reset = drm_atomic_helper_plane_reset, >>>> +   .atomic_duplicate_state = >>>> drm_atomic_helper_plane_duplicate_state, >>>> +   .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, >>>> +}; >>>> + >>>> +static const struct drm_plane_helper_funcs * >>>> +lsdc_get_cursor_helper_funcs(const struct lsdc_desc *descp) >>>> +{ >>>> +   if (descp->chip == CHIP_LS7A2000) >>>> +       return &lsdc_cursor_plane_helpers; >>>> + >>>> +   return &lsdc_cursor_helpers_quirk; >>>> +} >>>> + >>>> +int lsdc_plane_init(struct lsdc_device *ldev, >>>> +           struct drm_plane *plane, >>>> +           enum drm_plane_type type, >>>> +           unsigned int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   unsigned int format_count; >>>> +   const u32 *formats; >>>> +   const char *name; >>>> +   int ret; >>>> + >>>> +   switch (type) { >>>> +   case DRM_PLANE_TYPE_PRIMARY: >>>> +       formats = lsdc_primary_formats; >>>> +       format_count = ARRAY_SIZE(lsdc_primary_formats); >>>> +       name = "primary-%u"; >>>> +       break; >>>> +   case DRM_PLANE_TYPE_CURSOR: >>>> +       formats = lsdc_cursor_formats; >>>> +       format_count = ARRAY_SIZE(lsdc_cursor_formats); >>>> +       name = "cursor-%u"; >>>> +       break; >>>> +   case DRM_PLANE_TYPE_OVERLAY: >>>> +       drm_err(ddev, "overlay plane is not supported\n"); >>>> +       break; >>>> +   } >>>> + >>>> +   ret = drm_universal_plane_init(ddev, plane, 1 << index, >>>> +                      &lsdc_plane_funcs, >>>> +                      formats, format_count, >>>> +                      lsdc_fb_format_modifiers, >>>> +                      type, name, index); >>>> +   if (ret) { >>>> +       drm_err(ddev, "%s failed: %d\n", __func__, ret); >>>> +       return ret; >>>> +   } >>>> + >>>> +   switch (type) { >>>> +   case DRM_PLANE_TYPE_PRIMARY: >>>> +       drm_plane_helper_add(plane, &lsdc_primary_plane_helpers); >>>> +       break; >>>> +   case DRM_PLANE_TYPE_CURSOR: >>>> +       drm_plane_helper_add(plane, >>>> lsdc_get_cursor_helper_funcs(descp)); >>>> +       break; >>>> +   case DRM_PLANE_TYPE_OVERLAY: >>>> +       drm_err(ddev, "overlay plane is not supported\n"); >>>> +       break; >>>> +   } >>>> + >>>> +   return 0; >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.c >>>> b/drivers/gpu/drm/lsdc/lsdc_pll.c >>>> new file mode 100644 >>>> index 000000000000..6ed74989a6f5 >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.c >>>> @@ -0,0 +1,569 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> +#include "lsdc_pll.h" >>>> + >>>> +/* >>>> + * The structure of the pixel PLL register is evolved with times. >>>> + * All loongson's cpu is little endian. >>>> + */ >>>> + >>>> +/* u64 */ >>>> +struct ls7a1000_pixpll_bitmap { >>>> +   /* Byte 0 ~ Byte 3 */ >>>> +   unsigned div_out     : 7;  /* 0 : 6    output clock >>>> divider */ >>>> +   unsigned __1         : 14; /* 7 : >>>> 20                         */ >>>> +   unsigned loopc       : 9;  /* 21 : 29   clock >>>> multiplier     */ >>>> +   unsigned __2         : 2;  /* 30 : >>>> 31                         */ >>>> + >>>> +   /* Byte 4 ~ Byte 7 */ >>>> +   unsigned div_ref     : 7;  /* 0 : 6    input clock >>>> divider  */ >>>> +   unsigned locked      : 1;  /* 7        PLL locked >>>> status    */ >>>> +   unsigned sel_out     : 1;  /* 8        output clk >>>> selector  */ >>>> +   unsigned __3         : 2;  /* 9 : 10 reserved             */ >>>> +   unsigned set_param   : 1;  /* 11       trigger the >>>> update   */ >>>> +   unsigned bypass      : 1;  /* >>>> 12                             */ >>>> +   unsigned powerdown   : 1;  /* >>>> 13                             */ >>>> +   unsigned __4         : 18; /* 14 : >>>> 31                        */ >>>> +}; >>>> + >>>> +/* u128 */ >>>> +struct ls2k1000_pixpll_bitmap { >>>> +   /* Byte 0 ~ Byte 3 */ >>>> +   unsigned sel_out     : 1; /* 0     select this >>>> PLL         */ >>>> +   unsigned __1         : 1; /* >>>> 1                              */ >>>> +   unsigned sw_adj_en   : 1; /* 2     allow software >>>> adjust   */ >>>> +   unsigned bypass      : 1; /* 3     bypass L1 >>>> PLL           */ >>>> +   unsigned __2         : 3; /* >>>> 4:6                            */ >>>> +   unsigned lock_en     : 1; /* 7     enable lock L1 >>>> PLL      */ >>>> +   unsigned __3         : 2; /* >>>> 8:9                            */ >>>> +   unsigned lock_check  : 2; /* 10:11  precision >>>> check         */ >>>> +   unsigned __4         : 4; /* >>>> 12:15                           */ >>>> + >>>> +   unsigned locked      : 1; /* 16     PLL locked flag >>>> bit     */ >>>> +   unsigned __5         : 2; /* >>>> 17:18                           */ >>>> +   unsigned powerdown   : 1; /* 19     powerdown the pll if >>>> set */ >>>> +   unsigned __6         : 6; /* >>>> 20:25                           */ >>>> +   unsigned div_ref     : 6; /* 26:31  L1 >>>> Prescaler            */ >>>> + >>>> +   /* Byte 4 ~ Byte 7 */ >>>> +   unsigned loopc       : 10; /* 32:41  Clock >>>> Multiplier        */ >>>> +   unsigned l1_div      : 6; /* 42:47  not >>>> used                */ >>>> +   unsigned __7         : 16; /* >>>> 48:63                           */ >>>> + >>>> +   /* Byte 8 ~ Byte 15 */ >>>> +   unsigned div_out     : 6; /* 0 : 5  output clock >>>> divider    */ >>>> +   unsigned __8         : 26; /* 6 : >>>> 31                          */ >>>> +   unsigned __9         : 32; /* 70: >>>> 127                         */ >>>> +}; >>>> + >>>> +/* u32 */ >>>> +struct ls2k0500_pixpll_bitmap { >>>> +   /* Byte 0 ~ Byte 1 */ >>>> +   unsigned sel_out     : 1; >>>> +   unsigned __1         : 2; >>>> +   unsigned sw_adj_en   : 1;  /* allow software >>>> adjust             */ >>>> +   unsigned bypass      : 1;  /* bypass L1 >>>> PLL                     */ >>>> +   unsigned powerdown   : 1;  /* write 1 to powerdown the >>>> PLL      */ >>>> +   unsigned __2         : 1; >>>> +   unsigned locked      : 1;  /* 7    Is L1 PLL locked, read >>>> only */ >>>> +   unsigned div_ref     : 6;  /* 8:13 ref clock >>>> divider          */ >>>> +   unsigned __3         : 2;  /* >>>> 14:15                             */ >>>> +   /* Byte 2 ~ Byte 3 */ >>>> +   unsigned loopc       : 8;  /* 16:23  Clock >>>> Multiplier          */ >>>> +   unsigned div_out     : 6;  /* 24:29  output clock >>>> divider      */ >>>> +   unsigned __4         : 2;  /* >>>> 30:31                             */ >>>> +}; >>>> + >>>> +union lsdc_pixpll_bitmap { >>>> +   struct ls7a1000_pixpll_bitmap ls7a1000; >>>> +   struct ls2k1000_pixpll_bitmap ls2k1000; >>>> +   struct ls2k0500_pixpll_bitmap ls2k0500; >>>> + >>>> +   u32 dword[4]; >>>> +}; >>>> + >>>> +struct pixclk_to_pll_parm { >>>> +   /* kHz */ >>>> +   unsigned int clock; >>>> + >>>> +   unsigned short width; >>>> +   unsigned short height; >>>> +   unsigned short vrefresh; >>>> + >>>> +   /* Stores parameters for programming the Hardware PLLs */ >>>> +   unsigned short div_out; >>>> +   unsigned short loopc; >>>> +   unsigned short div_ref; >>>> +}; >>>> + >>>> +/* >>>> + * Pixel clock to PLL parameters translation table. >>>> + * Small static cached value to speed up PLL parameters calculation. >>>> + */ >>>> +static const struct pixclk_to_pll_parm pll_param_table[] = { >>>> +   {148500, 1920, 1080, 60, 11, 49, 3},  /* 1920x1080@60Hz */ >>>> +                       /* 1920x1080@50Hz */ >>>> +   {174500, 1920, 1080, 75, 17, 89, 3},  /* 1920x1080@75Hz */ >>>> +   {181250, 2560, 1080, 75, 8, 58, 4},  /* 2560x1080@75Hz */ >>>> +   {146250, 1680, 1050, 60, 16, 117, 5},  /* 1680x1050@60Hz */ >>>> +   {135000, 1280, 1024, 75, 10, 54, 4},  /* 1280x1024@75Hz */ >>>> + >>>> +   {108000, 1600, 900, 60, 15, 81, 5},  /* 1600x900@60Hz */ >>>> +                       /* 1280x1024@60Hz */ >>>> +                       /* 1280x960@60Hz */ >>>> +                       /* 1152x864@75Hz */ >>>> + >>>> +   {106500, 1440, 900, 60, 19, 81, 4},  /* 1440x900@60Hz */ >>>> +   {88750, 1440, 900, 60, 16, 71, 5},  /* 1440x900@60Hz */ >>>> +   {83500, 1280, 800, 60, 17, 71, 5},  /* 1280x800@60Hz */ >>>> +   {71000, 1280, 800, 60, 20, 71, 5},  /* 1280x800@60Hz */ >>>> + >>>> +   {74250, 1280, 720, 60, 22, 49, 3},  /* 1280x720@60Hz */ >>>> +                       /* 1280x720@50Hz */ >>>> + >>>> +   {78750, 1024, 768, 75, 16, 63, 5},  /* 1024x768@75Hz */ >>>> +   {75000, 1024, 768, 70, 29, 87, 4},  /* 1024x768@70Hz */ >>>> +   {65000, 1024, 768, 60, 20, 39, 3},  /* 1024x768@60Hz */ >>>> + >>>> +   {51200, 1024, 600, 60, 25, 64, 5},  /* 1024x600@60Hz */ >>>> + >>>> +   {57284, 832, 624, 75, 24, 55, 4},  /* 832x624@75Hz */ >>>> +   {49500, 800, 600, 75, 40, 99, 5},  /* 800x600@75Hz */ >>>> +   {50000, 800, 600, 72, 44, 88, 4},  /* 800x600@72Hz */ >>>> +   {40000, 800, 600, 60, 30, 36, 3},  /* 800x600@60Hz */ >>>> +   {36000, 800, 600, 56, 50, 72, 4},  /* 800x600@56Hz */ >>>> +   {31500, 640, 480, 75, 40, 63, 5},  /* 640x480@75Hz */ >>>> +                       /* 640x480@73Hz */ >>>> + >>>> +   {30240, 640, 480, 67, 62, 75, 4},  /* 640x480@67Hz */ >>>> +   {27000, 720, 576, 50, 50, 54, 4},  /* 720x576@60Hz */ >>>> +   {25175, 640, 480, 60, 85, 107, 5},  /* 640x480@60Hz */ >>>> +   {25200, 640, 480, 60, 50, 63, 5},  /* 640x480@60Hz */ >>>> +                       /* 720x480@60Hz */ >>>> +}; >>>> + >>>> +/* >>>> + * lsdc_pixpll_setup - ioremap the device dependent PLL registers >>>> + * >>>> + * @this: point to the object where this function is called from >>>> + */ >>>> +static int lsdc_pixpll_setup(struct lsdc_pll * const this) >>>> +{ >>>> +   this->mmio = ioremap(this->reg_base, this->reg_size); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +/* >>>> + * Find a set of pll parameters (to generate pixel clock) from a >>>> static >>>> + * local table, which avoid to compute the pll parameter eachtime a >>>> + * modeset is triggered. >>>> + * >>>> + * @this: point to the object which this function is called from >>>> + * @clock: the desired output pixel clock, the unit is kHz >>>> + * @pout: point to where the parameters to store if found >>>> + * >>>> + * Return true if hit, otherwise return false. >>>> + */ >>>> +static bool lsdc_pixpll_find(struct lsdc_pll * const this, >>>> +                unsigned int clock, >>>> +                struct lsdc_pll_parms * const pout) >>>> +{ >>>> +   unsigned int num = ARRAY_SIZE(pll_param_table); >>>> +   unsigned int i; >>>> + >>>> +   for (i = 0; i < num; i++) { >>>> +       if (clock != pll_param_table[i].clock) >>>> +           continue; >>>> + >>>> +       pout->div_ref = pll_param_table[i].div_ref; >>>> +       pout->loopc  = pll_param_table[i].loopc; >>>> +       pout->div_out = pll_param_table[i].div_out; >>>> + >>>> +       return true; >>>> +   } >>>> + >>>> +   drm_dbg(this->ddev, "pixel clock %u: miss\n", clock); >>>> + >>>> +   return false; >>>> +} >>>> + >>>> +/* >>>> + * Find a set of pll parameters which have minimal difference with >>>> the desired >>>> + * pixel clock frequency. It does that by computing all of the >>>> possible >>>> + * combination. Compute the diff and find the combination with >>>> minimal diff. >>>> + * >>>> + * clock_out = refclk / div_ref * loopc / div_out >>>> + * >>>> + * refclk is determined by the oscillator mounted board(Here is >>>> 100MHz in >>>> + * almost all case) >>>> + * >>>> + * @this: point to the object from which this function is called >>>> + * @clock_khz: the desired output pixel clock, the unit is kHz >>>> + * @pout: point to the out struct of lsdc_pll_parms >>>> + * >>>> + * Return true if a parameter is found, otherwise return false >>>> + */ >>>> +static bool lsdc_pixpll_compute(struct lsdc_pll * const this, >>>> +               unsigned int clock_khz, >>>> +               struct lsdc_pll_parms *pout) >>>> +{ >>>> +   unsigned int refclk = this->ref_clock; >>>> +   const unsigned int tolerance = 1000; >>>> +   unsigned int min = tolerance; >>>> +   unsigned int div_out, loopc, div_ref; >>>> +   unsigned int computed; >>>> + >>>> +   if (lsdc_pixpll_find(this, clock_khz, pout)) >>>> +       return true; >>>> + >>>> +   for (div_out = 6; div_out < 64; div_out++) { >>>> +       for (div_ref = 3; div_ref < 6; div_ref++) { >>>> +           for (loopc = 6; loopc < 161; loopc++) { >>>> +               int diff; >>>> + >>>> +               if (loopc < 12 * div_ref) >>>> +                   continue; >>>> +               if (loopc > 32 * div_ref) >>>> +                   continue; >>>> + >>>> +               computed = refclk * loopc / div_ref / div_out; >>>> + >>>> +               if (clock_khz > computed) >>>> +                   diff = clock_khz - computed; >>>> +               else if (clock_khz < computed) >>>> +                   diff = computed - clock_khz; >>>> + >>>> +               if (diff < min) { >>>> +                   min = diff; >>>> +                   pout->div_ref = div_ref; >>>> +                   pout->div_out = div_out; >>>> +                   pout->loopc = loopc; >>>> + >>>> +                   if (diff == 0) >>>> +                       return true; >>>> +               } >>>> +           } >>>> +       } >>>> +   } >>>> + >>>> +   return min < tolerance; >>>> +} >>>> + >>>> +/* >>>> + * Update the pll parameters to hardware, target to the pixpll in >>>> ls7a1000 >>>> + * >>>> + * @this: point to the object from which this function is called >>>> + * @pin: point to the struct of lsdc_pll_parms passed in >>>> + * >>>> + * return 0 if successful. >>>> + */ >>>> +static int ls7a1000_pixpll_param_update(struct lsdc_pll * const this, >>>> +                   struct lsdc_pll_parms const *pin) >>>> +{ >>>> +   void __iomem *reg = this->mmio; >>>> +   unsigned int counter = 0; >>>> +   bool locked; >>>> +   u32 val; >>>> + >>>> +   /* Bypass the software configured PLL, using refclk directly */ >>>> +   val = readl(reg + 0x4); >>>> +   val &= ~(1 << 8); >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   /* Powerdown the PLL */ >>>> +   val = readl(reg + 0x4); >>>> +   val |= (1 << 13); >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   /* Clear the pll parameters */ >>>> +   val = readl(reg + 0x4); >>>> +   val &= ~(1 << 11); >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   /* clear old value & config new value */ >>>> +   val = readl(reg + 0x04); >>>> +   val &= ~0x7F; >>>> +   val |= pin->div_ref;       /* div_ref */ >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   val = readl(reg); >>>> +   val &= ~0x7f; >>>> +   val |= pin->div_out;       /* div_out */ >>>> + >>>> +   val &= ~(0x1ff << 21); >>>> +   val |= pin->loopc << 21;   /* loopc */ >>>> +   writel(val, reg); >>>> + >>>> +   /* Set the pll the parameters */ >>>> +   val = readl(reg + 0x4); >>>> +   val |= (1 << 11); >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   /* Powerup the PLL */ >>>> +   val = readl(reg + 0x4); >>>> +   val &= ~(1 << 13); >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   /* Wait the PLL lock */ >>>> +   do { >>>> +       val = readl(reg + 0x4); >>>> +       locked = val & 0x80; >>>> +       counter++; >>>> +   } while (!locked && (counter < 2000)); >>>> + >>>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>>> + >>>> +   /* Switch to the software configured pll */ >>>> +   val = readl(reg + 0x4); >>>> +   val |= (1UL << 8); >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +/* >>>> + * Update the pll parameters to hardware, target to the pixpll in >>>> ls2k1000 >>>> + * >>>> + * @this: point to the object from which this function is called >>>> + * @pin: point to the struct of lsdc_pll_parms passed in >>>> + * >>>> + * return 0 if successful. >>>> + */ >>>> +static int ls2k1000_pixpll_param_update(struct lsdc_pll * const this, >>>> +                   struct lsdc_pll_parms const *pin) >>>> +{ >>>> +   void __iomem *reg = this->mmio; >>>> +   unsigned int counter = 0; >>>> +   bool locked = false; >>>> +   u32 val; >>>> + >>>> +   val = readl(reg); >>>> +   /* Bypass the software configured PLL, using refclk directly */ >>>> +   val &= ~(1 << 0); >>>> +   writel(val, reg); >>>> + >>>> +   /* Powerdown the PLL */ >>>> +   val |= (1 << 19); >>>> +   writel(val, reg); >>>> + >>>> +   /* Allow the software configuration */ >>>> +   val &= ~(1 << 2); >>>> +   writel(val, reg); >>>> + >>>> +   /* allow L1 PLL lock */ >>>> +   val = (1L << 7) | (3L << 10); >>>> +   writel(val, reg); >>>> + >>>> +   /* clear div_ref bit field */ >>>> +   val &= ~(0x3f << 26); >>>> +   /* set div_ref bit field */ >>>> +   val |= pin->div_ref << 26; >>>> +   writel(val, reg); >>>> + >>>> +   val = readl(reg + 4); >>>> +   /* clear loopc bit field */ >>>> +   val &= ~0x0fff; >>>> +   /* set loopc bit field */ >>>> +   val |= pin->loopc; >>>> +   writel(val, reg + 4); >>>> + >>>> +   /* set div_out */ >>>> +   writel(pin->div_out, reg + 8); >>>> + >>>> +   val = readl(reg); >>>> +   /* use this parms configured just now */ >>>> +   val |= (1 << 2); >>>> +   /* powerup the PLL */ >>>> +   val &= ~(1 << 19); >>>> +   writel(val, reg); >>>> + >>>> +   /* wait pll setup and locked */ >>>> +   do { >>>> +       val = readl(reg); >>>> +       locked = val & 0x10000; >>>> +       counter++; >>>> +   } while (!locked && (counter < 2000)); >>>> + >>>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>>> + >>>> +   /* Switch to software configured PLL instead of refclk */ >>>> +   val |= 1; >>>> +   writel(val, reg); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +/* >>>> + * Update the pll parameters to hardware, target to the pixpll in >>>> ls2k0500 >>>> + * >>>> + * @this: point to the object which calling this function >>>> + * @param: pointer to where the parameters passed in >>>> + * >>>> + * return 0 if successful. >>>> + */ >>>> +static int ls2k0500_pixpll_param_update(struct lsdc_pll * const this, >>>> +                   struct lsdc_pll_parms const *param) >>>> +{ >>>> +   void __iomem *reg = this->mmio; >>>> +   unsigned int counter = 0; >>>> +   bool locked = false; >>>> +   u32 val; >>>> + >>>> +   /* Bypass the software configured PLL, using refclk directly */ >>>> +   val = readl(reg); >>>> +   val &= ~(1 << 0); >>>> +   writel(val, reg); >>>> + >>>> +   /* Powerdown the PLL */ >>>> +   val = readl(reg); >>>> +   val |= (1 << 5); >>>> +   writel(val, reg); >>>> + >>>> +   /* Allow the software configuration */ >>>> +   val |= (1 << 3); >>>> +   writel(val, reg); >>>> + >>>> +   /* Update the pll params */ >>>> +   val = (param->div_out << 24) | >>>> +         (param->loopc << 16) | >>>> +         (param->div_ref << 8); >>>> + >>>> +   writel(val, reg); >>>> + >>>> +   /* Powerup the PLL */ >>>> +   val = readl(reg); >>>> +   val &= ~(1 << 5); >>>> +   writel(val, reg); >>>> + >>>> +   /* wait pll setup and locked */ >>>> +   do { >>>> +       val = readl(reg); >>>> +       locked = val & 0x80; >>>> +       counter++; >>>> +   } while (!locked && (counter < 10000)); >>>> + >>>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>>> + >>>> +   /* Switch to software configured PLL instead of refclk */ >>>> +   writel((val | 1), reg); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static unsigned int lsdc_get_clock_rate(struct lsdc_pll * const this, >>>> +                   struct lsdc_pll_parms *pout) >>>> +{ >>>> +   struct drm_device *ddev = this->ddev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   const struct lsdc_desc * const descp = ldev->descp; >>>> +   unsigned int out; >>>> +   union lsdc_pixpll_bitmap parms; >>>> + >>>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>>> CHIP_LS7A2000) { >>>> +       struct ls7a1000_pixpll_bitmap *obj = &parms.ls7a1000; >>>> + >>>> +       parms.dword[0] = readl(this->mmio); >>>> +       parms.dword[1] = readl(this->mmio + 4); >>>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>>> obj->div_out; >>>> +       if (pout) { >>>> +           pout->div_ref = obj->div_ref; >>>> +           pout->loopc = obj->loopc; >>>> +           pout->div_out = obj->div_out; >>>> +       } >>>> +   } else if (descp->chip == CHIP_LS2K1000) { >>>> +       struct ls2k1000_pixpll_bitmap *obj = &parms.ls2k1000; >>>> + >>>> +       parms.dword[0] = readl(this->mmio); >>>> +       parms.dword[1] = readl(this->mmio + 4); >>>> +       parms.dword[2] = readl(this->mmio + 8); >>>> +       parms.dword[3] = readl(this->mmio + 12); >>>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>>> obj->div_out; >>>> +       if (pout) { >>>> +           pout->div_ref = obj->div_ref; >>>> +           pout->loopc = obj->loopc; >>>> +           pout->div_out = obj->div_out; >>>> +       } >>>> +   } else if (descp->chip == CHIP_LS2K0500) { >>>> +       struct ls2k0500_pixpll_bitmap *obj = &parms.ls2k0500; >>>> + >>>> +       parms.dword[0] = readl(this->mmio); >>>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>>> obj->div_out; >>>> +       if (pout) { >>>> +           pout->div_ref = obj->div_ref; >>>> +           pout->loopc = obj->loopc; >>>> +           pout->div_out = obj->div_out; >>>> +       } >>>> +   } else { >>>> +       drm_err(ddev, "unknown chip, the driver need update\n"); >>>> +       return 0; >>>> +   } >>>> + >>>> +   return out; >>>> +} >>>> + >>>> +static const struct lsdc_pixpll_funcs ls7a1000_pixpll_funcs = { >>>> +   .setup = lsdc_pixpll_setup, >>>> +   .compute = lsdc_pixpll_compute, >>>> +   .update = ls7a1000_pixpll_param_update, >>>> +   .get_clock_rate = lsdc_get_clock_rate, >>>> +}; >>>> + >>>> +static const struct lsdc_pixpll_funcs ls2k1000_pixpll_funcs = { >>>> +   .setup = lsdc_pixpll_setup, >>>> +   .compute = lsdc_pixpll_compute, >>>> +   .update = ls2k1000_pixpll_param_update, >>>> +   .get_clock_rate = lsdc_get_clock_rate, >>>> +}; >>>> + >>>> +static const struct lsdc_pixpll_funcs ls2k0500_pixpll_funcs = { >>>> +   .setup = lsdc_pixpll_setup, >>>> +   .compute = lsdc_pixpll_compute, >>>> +   .update = ls2k0500_pixpll_param_update, >>>> +   .get_clock_rate = lsdc_get_clock_rate, >>>> +}; >>>> + >>>> +int lsdc_pixpll_init(struct lsdc_pll * const this, >>>> +            struct drm_device *ddev, >>>> +            unsigned int index) >>>> +{ >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   this->ddev = ddev; >>>> +   this->index = index; >>>> +   this->ref_clock = LSDC_PLL_REF_CLK; >>>> + >>>> +   /* LS7A1000 and LS7A2000's pixpll setting registers is same */ >>>> +   if (descp->chip == CHIP_LS7A2000 || descp->chip == >>>> CHIP_LS7A1000) { >>>> +       if (index == 0) >>>> +           this->reg_base = LS7A1000_CFG_REG_BASE + >>>> LS7A1000_PIX_PLL0_REG; >>>> +       else if (index == 1) >>>> +           this->reg_base = LS7A1000_CFG_REG_BASE + >>>> LS7A1000_PIX_PLL1_REG; >>>> +       this->reg_size = 8; >>>> +       this->funcs = &ls7a1000_pixpll_funcs; >>>> +   } else if (descp->chip == CHIP_LS2K1000) { >>>> +       if (index == 0) >>>> +           this->reg_base = LS2K1000_CFG_REG_BASE + >>>> LS2K1000_PIX_PLL0_REG; >>>> +       else if (index == 1) >>>> +           this->reg_base = LS2K1000_CFG_REG_BASE + >>>> LS2K1000_PIX_PLL1_REG; >>>> + >>>> +       this->reg_size = 16; >>>> +       this->funcs = &ls2k1000_pixpll_funcs; >>>> +   } else if (descp->chip == CHIP_LS2K0500) { >>>> +       if (index == 0) >>>> +           this->reg_base = LS2K0500_CFG_REG_BASE + >>>> LS2K0500_PIX_PLL0_REG; >>>> +       else if (index == 1) >>>> +           this->reg_base = LS2K0500_CFG_REG_BASE + >>>> LS2K0500_PIX_PLL1_REG; >>>> + >>>> +       this->reg_size = 4; >>>> +       this->funcs = &ls2k0500_pixpll_funcs; >>>> +   } else { >>>> +       drm_err(this->ddev, "unknown chip, the driver need >>>> update\n"); >>>> +       return -ENOENT; >>>> +   } >>>> + >>>> +   return this->funcs->setup(this); >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.h >>>> b/drivers/gpu/drm/lsdc/lsdc_pll.h >>>> new file mode 100644 >>>> index 000000000000..538739f461f2 >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.h >>>> @@ -0,0 +1,78 @@ >>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>> + >>>> +#ifndef __LSDC_PLL_H__ >>>> +#define __LSDC_PLL_H__ >>>> + >>>> +#include <drm/drm_device.h> >>>> + >>>> +/* >>>> + * Loongson Pixel PLL hardware structure >>>> + * >>>> + * refclk: reference frequency, 100 MHz from external oscillator >>>> + * outclk: output frequency desired. >>>> + * >>>> + * >>>> + *              L1      Fref                     Fvco L2 >>>> + * refclk  +-----------+     +------------------+ +---------+  >>>> outclk >>>> + * ---+---> | Prescaler | ---> | Clock Multiplier | ---> | divider >>>> | --------> >>>> + *   |    +-----------+     +------------------+ +---------+    ^ >>>> + *   |          ^                     ^ ^         | >>>> + *   |          |                     | |         | >>>> + *   |          |                     | |         | >>>> + *   |       div_ref                loopc div_out | >>>> + * | | >>>> + *   +--- sel_out (bypass above software configurable clock if >>>> set) ----+ >>>> + * >>>> + * sel_out: PLL clock output selector (for debug purpose only). >>>> + * >>>> + * If sel_out == 1, it will take refclk as output directly, >>>> + * the L1 Prescaler and the out divider will be bypassed. >>>> + * >>>> + * If sel_out == 0, then outclk = refclk / div_ref * loopc / >>>> div_out; >>>> + * >>>> + * PLL working requirements: >>>> + * >>>> + * 1) 20 MHz <= refclk / div_ref <= 40Mhz >>>> + * 2) 1.2 GHz <= refclk /div_out * loopc <= 3.2 Ghz >>>> + */ >>>> + >>>> +struct lsdc_pll_parms { >>>> +   unsigned int div_ref; >>>> +   unsigned int loopc; >>>> +   unsigned int div_out; >>>> +}; >>>> + >>>> +struct lsdc_pll; >>>> + >>>> +struct lsdc_pixpll_funcs { >>>> +   int (*setup)(struct lsdc_pll * const this); >>>> +   bool (*compute)(struct lsdc_pll * const this, >>>> +           unsigned int clock, >>>> +           struct lsdc_pll_parms *pout); >>>> +   int (*update)(struct lsdc_pll * const this, >>>> +             struct lsdc_pll_parms const *pin); >>>> +   unsigned int (*get_clock_rate)(struct lsdc_pll * const this, >>>> +                      struct lsdc_pll_parms *pout); >>>> +}; >>>> + >>>> +struct lsdc_pll { >>>> +   const struct lsdc_pixpll_funcs *funcs; >>>> +   struct drm_device *ddev; >>>> +   void __iomem *mmio; >>>> + >>>> +   /* PLL register offset */ >>>> +   u32 reg_base; >>>> +   /* PLL register size in bytes */ >>>> +   u32 reg_size; >>>> + >>>> +   /* 100000kHz, fixed on all board found */ >>>> +   unsigned int ref_clock; >>>> + >>>> +   unsigned int index; >>>> +}; >>>> + >>>> +int lsdc_pixpll_init(struct lsdc_pll * const this, >>>> +            struct drm_device *ddev, >>>> +            unsigned int index); >>>> + >>>> +#endif >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.c >>>> b/drivers/gpu/drm/lsdc/lsdc_regs.c >>>> new file mode 100644 >>>> index 000000000000..c7950c43968f >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.c >>>> @@ -0,0 +1,29 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> + >>>> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset) >>>> +{ >>>> +   unsigned long flags; >>>> +   u32 ret; >>>> + >>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>> + >>>> +   ret = readl(ldev->reg_base + offset); >>>> + >>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>> + >>>> +   return ret; >>>> +} >>>> + >>>> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 >>>> val) >>>> +{ >>>> +   unsigned long flags; >>>> + >>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>> + >>>> +   writel(val, ldev->reg_base + offset); >>>> + >>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.h >>>> b/drivers/gpu/drm/lsdc/lsdc_regs.h >>>> new file mode 100644 >>>> index 000000000000..828956633137 >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.h >>>> @@ -0,0 +1,296 @@ >>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>> + >>>> +#ifndef __LSDC_REGS_H__ >>>> +#define __LSDC_REGS_H__ >>>> + >>>> +#include <linux/bitops.h> >>>> +#include <linux/types.h> >>>> + >>>> +/* >>>> + * PIXEL PLL Reference clock >>>> + */ >>>> +#define LSDC_PLL_REF_CLK               100000          /* kHz */ >>>> + >>>> +/* >>>> + * Those PLL registers are not located at DC reg bar space, >>>> + * there are relative to LSXXXXX_CFG_REG_BASE. >>>> + * XXXXX = 7A1000, 2K1000, 2K0500 >>>> + */ >>>> + >>>> +/* LS2K1000 */ >>>> +#define LS2K1000_PIX_PLL0_REG          0x04B0 >>>> +#define LS2K1000_PIX_PLL1_REG          0x04C0 >>>> +#define LS2K1000_CFG_REG_BASE          0x1fe10000 >>>> + >>>> +/* LS7A1000 and LS2K2000 */ >>>> +#define LS7A1000_PIX_PLL0_REG          0x04B0 >>>> +#define LS7A1000_PIX_PLL1_REG          0x04C0 >>>> +#define LS7A1000_CFG_REG_BASE          0x10010000 >>>> + >>>> +/* LS2K0500 */ >>>> +#define LS2K0500_PIX_PLL0_REG          0x0418 >>>> +#define LS2K0500_PIX_PLL1_REG          0x0420 >>>> +#define LS2K0500_CFG_REG_BASE          0x1fe10000 >>>> + >>>> +/* >>>> + * CRTC CFG REG >>>> + */ >>>> +#define CFG_PIX_FMT_MASK               GENMASK(2, 0) >>>> + >>>> +enum lsdc_pixel_format { >>>> +   LSDC_PF_NONE = 0, >>>> +   LSDC_PF_ARGB4444 = 1, /* ARGB A:4 bits R/G/B: 4 bits each [16 >>>> bits] */ >>>> +   LSDC_PF_ARGB1555 = 2, /* ARGB A:1 bit RGB:15 bits [16 bits] */ >>>> +   LSDC_PF_RGB565 = 3,   /* RGB [16 bits] */ >>>> +   LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */ >>>> +   LSDC_PF_RGBA8888 = 5, /* ARGB [32 bits] */ >>>> +}; >>>> + >>>> +/* >>>> + * Each crtc has two set fb address registers usable, >>>> CFG_FB_IN_USING of >>>> + * LSDC_CRTCx_CFG_REG specify which fb address register is currently >>>> + * in using by the CRTC. CFG_PAGE_FLIP of LSDC_CRTCx_CFG_REG is >>>> used to >>>> + * trigger the switch which will be finished at the very vblank. >>>> If you >>>> + * want it switch to another again, you must set CFG_PAGE_FLIP again. >>>> + */ >>>> +#define CFG_PAGE_FLIP                  BIT(7) >>>> +#define CFG_OUTPUT_EN                  BIT(8) >>>> +/* CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware >>>> logic */ >>>> +#define CFG_PANEL_SWITCH               BIT(9) >>>> +/* Indicate witch fb addr reg is in using, currently */ >>>> +#define CFG_FB_IN_USING                BIT(11) >>>> +#define CFG_GAMMA_EN                   BIT(12) >>>> + >>>> +/* CRTC get soft reset if voltage level change from 1 -> 0 */ >>>> +#define CFG_RESET_N                    BIT(20) >>>> + >>>> +#define CFG_HSYNC_EN                   BIT(30) >>>> +#define CFG_HSYNC_INV                  BIT(31) >>>> +#define CFG_VSYNC_EN                   BIT(30) >>>> +#define CFG_VSYNC_INV                  BIT(31) >>>> + >>>> +/* THE DMA step of the DC in LS7A2000 is configurable */ >>>> +#define LSDC_DMA_STEP_MASK             GENMASK(17, 16) >>>> +enum lsdc_dma_steps_supported { >>>> +   LSDC_DMA_STEP_256_BYTES = 0, >>>> +   LSDC_DMA_STEP_128_BYTES = 1, >>>> +   LSDC_DMA_STEP_64_BYTES = 2, >>>> +   LSDC_DMA_STEP_32_BYTES = 3, >>>> +}; >>>> + >>>> +/******** CRTC0 & DVO0 ********/ >>>> +#define LSDC_CRTC0_CFG_REG             0x1240 >>>> +#define LSDC_CRTC0_FB0_LO_ADDR_REG     0x1260 >>>> +#define LSDC_CRTC0_FB0_HI_ADDR_REG     0x15A0 >>>> +#define LSDC_CRTC0_FB1_LO_ADDR_REG     0x1580 >>>> +#define LSDC_CRTC0_FB1_HI_ADDR_REG     0x15C0 >>>> +#define LSDC_CRTC0_STRIDE_REG          0x1280 >>>> +#define LSDC_CRTC0_FB_ORIGIN_REG       0x1300 >>>> +#define LSDC_CRTC0_HDISPLAY_REG        0x1400 >>>> +#define LSDC_CRTC0_HSYNC_REG           0x1420 >>>> +#define LSDC_CRTC0_VDISPLAY_REG        0x1480 >>>> +#define LSDC_CRTC0_VSYNC_REG           0x14A0 >>>> +#define LSDC_CRTC0_GAMMA_INDEX_REG     0x14E0 >>>> +#define LSDC_CRTC0_GAMMA_DATA_REG      0x1500 >>>> + >>>> +/******** CTRC1 & DVO1 ********/ >>>> +#define LSDC_CRTC1_CFG_REG             0x1250 >>>> +#define LSDC_CRTC1_FB0_LO_ADDR_REG     0x1270 >>>> +#define LSDC_CRTC1_FB0_HI_ADDR_REG     0x15B0 >>>> +#define LSDC_CRTC1_FB1_LO_ADDR_REG     0x1590 >>>> +#define LSDC_CRTC1_FB1_HI_ADDR_REG     0x15D0 >>>> +#define LSDC_CRTC1_STRIDE_REG          0x1290 >>>> +#define LSDC_CRTC1_FB_ORIGIN_REG       0x1310 >>>> +#define LSDC_CRTC1_HDISPLAY_REG        0x1410 >>>> +#define LSDC_CRTC1_HSYNC_REG           0x1430 >>>> +#define LSDC_CRTC1_VDISPLAY_REG        0x1490 >>>> +#define LSDC_CRTC1_VSYNC_REG           0x14B0 >>>> +#define LSDC_CRTC1_GAMMA_INDEX_REG     0x14F0 >>>> +#define LSDC_CRTC1_GAMMA_DATA_REG      0x1510 >>>> + >>>> +/* >>>> + * LS7A2000 has the hardware which record the scan position of the >>>> CRTC >>>> + * [31:16] : current X position, [15:0] : current Y position >>>> + */ >>>> +#define LSDC_CRTC0_SCAN_POS_REG        0x14C0 >>>> +#define LSDC_CRTC1_SCAN_POS_REG        0x14D0 >>>> + >>>> +/* >>>> + * LS7A2000 has the hardware which count the number of vblank >>>> generated >>>> + */ >>>> +#define LSDC_CRTC0_VSYNC_COUNTER_REG   0x1A00 >>>> +#define LSDC_CRTC1_VSYNC_COUNTER_REG   0x1A10 >>>> + >>>> +/* In all, LSDC_CRTC1_XXX_REG - LSDC_CRTC0_XXX_REG = 0x10 */ >>>> + >>>> +/* >>>> + * There is only one hardware cursor unit in ls7a1000, ls2k1000 >>>> and ls2k0500. >>>> + * well, ls7a2000 has two hardware cursor unit. >>>> + */ >>>> +#define CURSOR_FORMAT_MASK             GENMASK(1, 0) >>>> +enum lsdc_cursor_format { >>>> +   CURSOR_FORMAT_DISABLE = 0, >>>> +   CURSOR_FORMAT_MONOCHROME = 1, >>>> +   CURSOR_FORMAT_ARGB8888 = 2, >>>> +}; >>>> + >>>> +#define CURSOR_SIZE_64X64              BIT(2) >>>> +#define CURSOR_LOCATION                BIT(4) >>>> + >>>> +#define LSDC_CURSOR0_CFG_REG           0x1520 >>>> +#define LSDC_CURSOR0_ADDR_LO_REG       0x1530 >>>> +#define LSDC_CURSOR0_ADDR_HI_REG       0x15e0 >>>> +#define LSDC_CURSOR0_POSITION_REG      0x1540 >>>> +#define LSDC_CURSOR0_BG_COLOR_REG      0x1550 /* background >>>> color */ >>>> +#define LSDC_CURSOR0_FG_COLOR_REG      0x1560 /* foreground >>>> color */ >>>> + >>>> +#define LSDC_CURSOR1_CFG_REG           0x1670 >>>> +#define LSDC_CURSOR1_ADDR_LO_REG       0x1680 >>>> +#define LSDC_CURSOR1_ADDR_HI_REG       0x16e0 >>>> +#define LSDC_CURSOR1_POSITION_REG      0x1690 /* [31:16] Y, >>>> [15:0] X */ >>>> +#define LSDC_CURSOR1_BG_COLOR_REG      0x16A0 /* background >>>> color */ >>>> +#define LSDC_CURSOR1_FG_COLOR_REG      0x16B0 /* foreground >>>> color */ >>>> + >>>> +/* >>>> + * DC Interrupt Control Register, 32bit, Address Offset: 1570 >>>> + * >>>> + * Bits 15:0 inidicate the interrupt status >>>> + * Bits 31:16 control enable interrupts corresponding to bit 15:0 >>>> or not >>>> + * Write 1 to enable, write 0 to disable >>>> + * >>>> + * RF: Read Finished >>>> + * IDBU: Internal Data Buffer Underflow >>>> + * IDBFU: Internal Data Buffer Fatal Underflow >>>> + * CBRF: Cursor Buffer Read Finished Flag, no use. >>>> + * >>>> + * >>>> +-------+--------------------------+-------+--------+--------+-------+ >>>> + * | 31:27 |        26:16           | 15:11 |  10  | 9 |  8  | >>>> + * >>>> +-------+--------------------------+-------+--------+--------+-------+ >>>> + * | N/A | Interrupt Enable Control | N/A | IDBFU0 | IDBFU1 | >>>> IDBU0 | >>>> + * >>>> +-------+--------------------------+-------+--------+--------+-------+ >>>> + * >>>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>>> + * |  7  | 6 | 5 | 4  |  3   |  2   |  1   | 0   | >>>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>>> + * | IDBU1 | RF0 | RF1 | CRRF | HSYNC0 | VSYNC0 | HSYNC1 | VSYNC1 | >>>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>>> + */ >>>> + >>>> +#define LSDC_INT_REG                   0x1570 >>>> + >>>> +#define INT_CRTC0_VSYNC                BIT(2) >>>> +#define INT_CRTC0_HSYNC                BIT(3) >>>> +#define INT_CRTC0_RF                   BIT(6) >>>> +#define INT_CRTC0_IDBU                 BIT(8) >>>> +#define INT_CRTC0_IDBFU                BIT(10) >>>> + >>>> +#define INT_CRTC1_VSYNC                BIT(0) >>>> +#define INT_CRTC1_HSYNC                BIT(1) >>>> +#define INT_CRTC1_RF                   BIT(5) >>>> +#define INT_CRTC1_IDBU                 BIT(7) >>>> +#define INT_CRTC1_IDBFU                BIT(9) >>>> + >>>> +#define INT_CRTC0_VS_EN                BIT(18) >>>> +#define INT_CRTC0_HS_EN                BIT(19) >>>> +#define INT_CRTC0_RF_EN                BIT(22) >>>> +#define INT_CRTC0_IDBU_EN              BIT(24) >>>> +#define INT_CRTC0_IDBFU_EN             BIT(26) >>>> + >>>> +#define INT_CRTC1_VS_EN                BIT(16) >>>> +#define INT_CRTC1_HS_EN                BIT(17) >>>> +#define INT_CRTC1_RF_EN                BIT(21) >>>> +#define INT_CRTC1_IDBU_EN              BIT(23) >>>> +#define INT_CRTC1_IDBFU_EN             BIT(25) >>>> + >>>> +#define INT_STATUS_MASK                GENMASK(15, 0) >>>> + >>>> +/* >>>> + * LS7A1000/LS7A2000 have 4 gpios which are used to emulated I2C. >>>> + * They are under control of the LS7A_DC_GPIO_DAT_REG and >>>> LS7A_DC_GPIO_DIR_REG >>>> + * register, Those GPIOs has no relationship whth the GPIO >>>> hardware on the >>>> + * bridge chip itself. Those offsets are relative to DC register >>>> base address >>>> + * >>>> + * LS2k1000 and LS2K0500 don't have those registers, they use >>>> hardware i2c >>>> + * or general GPIO emulated i2c from linux i2c subsystem. >>>> + * >>>> + * GPIO data register, address offset: 0x1650 >>>> + *  +---------------+-----------+-----------+ >>>> + *  | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | >>>> + *  +---------------+-----------+-----------+ >>>> + *  |              |   DVO1  |   DVO0  | >>>> + *  +     N/A     +-----------+-----------+ >>>> + *  |              | SCL | SDA | SCL | SDA | >>>> + *  +---------------+-----------+-----------+ >>>> + */ >>>> +#define LS7A_DC_GPIO_DAT_REG           0x1650 >>>> + >>>> +/* >>>> + * GPIO Input/Output direction control register, address offset: >>>> 0x1660 >>>> + */ >>>> +#define LS7A_DC_GPIO_DIR_REG           0x1660 >>>> + >>>> +/* >>>> + * LS7A2000 has two built-in HDMI Encoder and one VGA encoder >>>> + */ >>>> + >>>> +/* >>>> + * Number of continuous packets may be present >>>> + * in HDMI hblank and vblank zone, should >= 48 >>>> + */ >>>> +#define LSDC_HDMI0_ZONE_REG            0x1700 >>>> +#define LSDC_HDMI1_ZONE_REG            0x1710 >>>> + >>>> +#define HDMI_INTERFACE_EN              BIT(0) >>>> +#define HDMI_PACKET_EN                 BIT(1) >>>> +#define HDMI_AUDIO_EN                  BIT(2) >>>> +#define HDMI_VIDEO_PREAMBLE_MASK       GENMASK(7, 4) >>>> +#define HDMI_HW_I2C_EN                 BIT(8) >>>> +#define HDMI_CTL_PERIOD_MODE           BIT(9) >>>> +#define LSDC_HDMI0_CTRL_REG            0x1720 >>>> +#define LSDC_HDMI1_CTRL_REG            0x1730 >>>> + >>>> +#define HDMI_PHY_EN                    BIT(0) >>>> +#define HDMI_PHY_RESET_N               BIT(1) >>>> +#define HDMI_PHY_TERM_L_EN             BIT(8) >>>> +#define HDMI_PHY_TERM_H_EN             BIT(9) >>>> +#define HDMI_PHY_TERM_DET_EN           BIT(10) >>>> +#define HDMI_PHY_TERM_STATUS           BIT(11) >>>> +#define LSDC_HDMI0_PHY_CTRL_REG        0x1800 >>>> +#define LSDC_HDMI1_PHY_CTRL_REG        0x1810 >>>> + >>>> +/* >>>> + * 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 | >>>> + * +--------------------------------------------------------+ >>>> + */ >>>> +#define LSDC_HDMI0_PHY_PLL_REG         0x1820 >>>> +#define LSDC_HDMI1_PHY_PLL_REG         0x1830 >>>> + >>>> +#define HDMI_PLL_ENABLE                BIT(0) >>>> +#define HDMI_PLL_LOCKED                BIT(16) >>>> +#define HDMI_PLL_BYPASS                BIT(17) >>>> + >>>> +#define HDMI_PLL_IDF_SHIFT             1 >>>> +#define HDMI_PLL_IDF_MASK              GENMASK(5, 1) >>>> +#define HDMI_PLL_LF_SHIFT              6 >>>> +#define HDMI_PLL_LF_MASK               GENMASK(12, 6) >>>> +#define HDMI_PLL_ODF_SHIFT             13 >>>> +#define HDMI_PLL_ODF_MASK              GENMASK(15, 13) >>>> + >>>> +/* LS7A2000 have hpd support */ >>>> +#define LSDC_HDMI_HPD_STATUS_REG       0x1BA0 >>>> +#define HDMI0_HPD_FLAG                 BIT(0) >>>> +#define HDMI1_HPD_FLAG                 BIT(1) >>>> + >>>> +#endif >>> >>
On 2023/2/15 09:17, suijingfeng wrote: > > On 2023/2/13 16:35, Sui Jingfeng wrote: >> >> On 2023/2/13 12:24, Sui jingfeng wrote: >>> >>> On 2023/2/10 23:04, Thomas Zimmermann wrote: >>>> Hi >>>> >>> Hello, >>>> Am 05.02.23 um 17:17 schrieb suijingfeng: >>>>> From: suijingfeng <suijingfeng@loongson.cn> >>>>> >>>>> Loongson display controller IP has been integrated in both Loongson >>>>> North Bridge chipset(ls7a1000 and ls7a2000) and Loongson >>>>> SoCs(ls2k1000 >>>>> and ls2k2000 etc), it even been included in Loongson BMC products. >>>>> >>>>> The display controller is a PCI device, it has two display pipe. >>>>> For the DC in LS7A1000 and LS2K1000 each way has a DVO output >>>>> interface >>>>> which provide RGB888 signals, vertical & horizontal synchronisations, >>>>> and the pixel clock. Each CRTC is able to support 1920x1080@60Hz, >>>>> the maximum resolution is 2048x2048 according to the hardware spec. >>>>> >>>>> For the DC in LS7A2000, each display pipe is connected with a >>>>> built-in >>>>> HDMI encoder which support 3840x2160@30Hz. the first display pipe is >>>>> also equipped with a transparent vga encoder which is parallel with >>>>> the hdmi encoder. The DC in LS7A2000 is more complete, besides above >>>>> feature, it has two hardware cursors, two hardware vblank counter and >>>>> two scanout position recoders. It also support hdmi audio and tiled >>>>> format etc. >>>>> >>>>>  v2: >>>>>   1) Use hpd status reg when polling for ls7a2000 >>>>>   2) Fix all warnings emerged when compile with W=1 >>>>> >>>>> Signed-off-by: suijingfeng <suijingfeng@loongson.cn> >>>>> Signed-off-by: suijingfeng <15330273260@189.cn> >>>>> --- >>>>>  drivers/gpu/drm/Kconfig            |  2 + >>>>>  drivers/gpu/drm/Makefile           |  1 + >>>>>  drivers/gpu/drm/lsdc/Kconfig       | 17 + >>>>>  drivers/gpu/drm/lsdc/Makefile      | 14 + >>>>>  drivers/gpu/drm/lsdc/lsdc_crtc.c   | 421 ++++++++++++++++++ >>>>>  drivers/gpu/drm/lsdc/lsdc_debugfs.c | 191 +++++++++ >>>>>  drivers/gpu/drm/lsdc/lsdc_drv.c    | 635 >>>>> ++++++++++++++++++++++++++++ >>>>>  drivers/gpu/drm/lsdc/lsdc_drv.h    | 274 ++++++++++++ >>>>>  drivers/gpu/drm/lsdc/lsdc_i2c.c    | 201 +++++++++ >>>>>  drivers/gpu/drm/lsdc/lsdc_irq.c    | 86 ++++ >>>>>  drivers/gpu/drm/lsdc/lsdc_output.c | 452 ++++++++++++++++++++ >>>>>  drivers/gpu/drm/lsdc/lsdc_plane.c  | 443 +++++++++++++++++++ >>>>>  drivers/gpu/drm/lsdc/lsdc_pll.c    | 569 +++++++++++++++++++++++++ >>>>>  drivers/gpu/drm/lsdc/lsdc_pll.h    | 78 ++++ >>>>>  drivers/gpu/drm/lsdc/lsdc_regs.c   | 29 ++ >>>>>  drivers/gpu/drm/lsdc/lsdc_regs.h   | 296 +++++++++++++ >>>>>  16 files changed, 3709 insertions(+) >>>>>  create mode 100644 drivers/gpu/drm/lsdc/Kconfig >>>>>  create mode 100644 drivers/gpu/drm/lsdc/Makefile >>>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_crtc.c >>>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_debugfs.c >>>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.c >>>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.h >>>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_i2c.c >>>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_irq.c >>>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_output.c >>>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_plane.c >>>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.c >>>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.h >>>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.c >>>>>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.h >>>>> >>>>> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig >>>>> index dc0f94f02a82..3baecd48930b 100644 >>>>> --- a/drivers/gpu/drm/Kconfig >>>>> +++ b/drivers/gpu/drm/Kconfig >>>>> @@ -367,6 +367,8 @@ source "drivers/gpu/drm/solomon/Kconfig" >>>>>   source "drivers/gpu/drm/sprd/Kconfig" >>>>>  +source "drivers/gpu/drm/lsdc/Kconfig" >>>>> + >>>>>  config DRM_HYPERV >>>>>      tristate "DRM Support for Hyper-V synthetic video device" >>>>>      depends on DRM && PCI && MMU && HYPERV >>>>> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile >>>>> index ab4460fcd63f..5a8e2fc8dd5d 100644 >>>>> --- a/drivers/gpu/drm/Makefile >>>>> +++ b/drivers/gpu/drm/Makefile >>>>> @@ -190,3 +190,4 @@ obj-y           += gud/ >>>>>  obj-$(CONFIG_DRM_HYPERV) += hyperv/ >>>>>  obj-y           += solomon/ >>>>>  obj-$(CONFIG_DRM_SPRD) += sprd/ >>>>> +obj-$(CONFIG_DRM_LSDC) += lsdc/ >>>>> diff --git a/drivers/gpu/drm/lsdc/Kconfig >>>>> b/drivers/gpu/drm/lsdc/Kconfig >>>>> new file mode 100644 >>>>> index 000000000000..8b9bb6996877 >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/Kconfig >>>>> @@ -0,0 +1,17 @@ >>>>> +# SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +config DRM_LSDC >>>>> +   tristate "DRM support for loongson display controller" >>>>> +   depends on DRM && PCI && (MACH_LOONGSON64 || LOONGARCH || MIPS) >>>> >>>> If possible, I'd add a single 'depends on' statement for each AND >>>> condition. Like this >>>> >>>>  depends on DRM >>>>  depends on PCI >>>>  depends on (MACH_LOONGSON64 || LOONGARCH || MIPS) >>>> >>>> Does it build on other architectures if you add COMPILE_TEST to >>>> that list of architectures? If so, it would simplify testing a lot. >>>> >> Hi, Should I add COMPILE_TEST to the list of architectures on next >> version? >>>>> +   select OF >>>> >>>> I don't think that it is allowed to select OF. Rather use >>>> >>>>  depends on OF >>>> >>>> I think that general rule is that it is not allowed to auto-select >>>> options that are selectable by users. That's the case for OF. >>>> >>>> >>> Ok, depends on OF is acceptable. >>> >>>>> +   select DRM_KMS_HELPER >>>>> +   select DRM_TTM >>>>> +   select DRM_TTM_HELPER >>>>> +   select DRM_VRAM_HELPER >>>>> +   default m >>>> >>>> No default, especially not 'm'. Everything should be off by default. >>>> >>>>> +   help >>>>> +     This is a KMS driver for loongson display controller in the >>>>> +     LS7A1000/LS7A2000 bridge chip and LS2K1000/LS2K0500 SoC. >>>>> +     If "M" is selected, the module will be called lsdc. >>>>> + >>>>> +     If in doubt, say "Y". >>>> >>>> Always say "N" by default. There are a few exceptions where "yes" >>>> is advised, but regular drivers are not one of them. >>>> >>>>> diff --git a/drivers/gpu/drm/lsdc/Makefile >>>>> b/drivers/gpu/drm/lsdc/Makefile >>>>> new file mode 100644 >>>>> index 000000000000..d4b901ec09cf >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/Makefile >>>>> @@ -0,0 +1,14 @@ >>>>> +# SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +lsdc-y := \ >>>>> +   lsdc_drv.o \ >>>>> +   lsdc_crtc.o \ >>>>> +   lsdc_irq.o \ >>>>> +   lsdc_plane.o \ >>>>> +   lsdc_pll.o \ >>>>> +   lsdc_i2c.o \ >>>>> +   lsdc_output.o \ >>>>> +   lsdc_regs.o \ >>>>> +   lsdc_debugfs.o >>>> >>>> Please always sort such lists alphabetically. >>>> >>>>> + >>>>> +obj-$(CONFIG_DRM_LSDC) += lsdc.o >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_crtc.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_crtc.c >>>>> new file mode 100644 >>>>> index 000000000000..e23e6085f418 >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_crtc.c >>>>> @@ -0,0 +1,421 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include <drm/drm_vblank.h> >>>>> +#include <drm/drm_atomic.h> >>>>> +#include <drm/drm_atomic_helper.h> >>>> >>>> Same here, alphabetical sorting. >>>> >>>>> +#include "lsdc_drv.h" >>>>> +#include "lsdc_regs.h" >>>>> +#include "lsdc_pll.h" >>>> >>>> Sorting. >>>> >>> All of problem mentioned above will be solved at next version, thinks. >>>>> + >>>>> +static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc) >>>>> +{ >>>>> +   struct lsdc_device *ldev = to_lsdc(crtc->dev); >>>>> +   unsigned int index = drm_crtc_index(crtc); >>>>> +   u32 val; >>>>> + >>>>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>>>> + >>>>> +   if (index == 0) >>>>> +       val |= INT_CRTC0_VS_EN; >>>>> +   else if (index == 1) >>>>> +       val |= INT_CRTC1_VS_EN; >>>>> + >>>>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc) >>>>> +{ >>>>> +   struct lsdc_device *ldev = to_lsdc(crtc->dev); >>>>> +   unsigned int index = drm_crtc_index(crtc); >>>>> +   u32 val; >>>>> + >>>>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>>>> + >>>>> +   if (index == 0) >>>>> +       val &= ~INT_CRTC0_VS_EN; >>>>> +   else if (index == 1) >>>>> +       val &= ~INT_CRTC1_VS_EN; >>>>> + >>>>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >>>>> +} >>>>> + >>>>> +static void lsdc_crtc_reset(struct drm_crtc *crtc) >>>>> +{ >>>>> +   struct drm_device *ddev = crtc->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   unsigned int index = drm_crtc_index(crtc); >>>>> +   struct lsdc_crtc_state *priv_crtc_state; >>>>> +   u32 val = CFG_RESET_N | LSDC_PF_XRGB8888; >>>>> + >>>>> +   if (index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>>> +   else if (index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>>> + >>>>> +   if (crtc->state) { >>>>> +       priv_crtc_state = to_lsdc_crtc_state(crtc->state); >>>>> + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); >>>>> +       kfree(priv_crtc_state); >>>>> +   } >>>>> + >>>>> +   priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL); >>>>> +   if (!priv_crtc_state) >>>>> +       return; >>>>> + >>>>> +   __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); >>>>> + >>>>> +   drm_info(ddev, "CRTC-%u reset\n", index); >>>> >>>> No drm_info() here. You could use drm_dbg() if you really want to >>>> print something. I'd advise to simply remove that line. >>>> >>>>> +} >>>>> + >>>>> +static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, >>>>> +                      struct drm_crtc_state *state) >>>>> +{ >>>>> +   struct lsdc_crtc_state *priv_crtc_state = >>>>> to_lsdc_crtc_state(state); >>>>> + >>>>> + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); >>>>> + >>>>> +   kfree(priv_crtc_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 = kmalloc(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_get_vblank_counter(struct drm_crtc *crtc) >>>>> +{ >>>>> +   struct drm_device *ddev = crtc->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>>>> +   unsigned int index = dispipe->index; >>>>> + >>>>> +   /* fallback to software emulated VBLANK counter */ >>>>> +   if (!descp->has_vblank_counter) >>>>> +       goto fallback; >>>>> + >>>>> +   if (index == 0) >>>>> +       return lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG); >>>>> + >>>>> +   if (index == 1) >>>>> +       return lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG); >>>>> + >>>>> +fallback: >>>>> +   return (u32)drm_crtc_vblank_count(crtc); >>>>> +} >>>>> + >>>>> +static const struct drm_crtc_funcs lsdc_crtc_funcs = { >>>> >>>> My advise for the design is to untangle the different cases into >>>> smaller helpers as much as possible. >>>> >>>> You have several chips with multiple pipelines. I would advise to >>>> provide a funcs array for each possible case. For example: >>>> >>>>  u32 lsdc_crtc0_get_vblank_counter(struct drm_crtc *crtc) >>>>  { >>>>     // return LSDC_CRTC0_VSYNC_COUNTER_REG >>>>  } >>>> >>>>  u32 lsdc_crtc1_get_vblank_counter(struct drm_crtc *crtc) >>>>  { >>>>     // return LSDC_CRTC1_VSYNC_COUNTER_REG >>>>  } >>>> >>>> and use a different funcs instance for each pipeline >>>> >>>>  struct lsdc_crtc0_funcs_ls2k2000 = { >>>>     .get_vblank_counter = lsdc_crtc0_get_vblank_counter, >>>>  }; >>>> >>>>  struct lsdc_crtc1_funcs_ls2k2000 = { >>>>     .get_vblank_counter = lsdc_crtc1_get_vblank_counter, >>>>  }; >>>> >>>> For hardware without vblank counter, use another instance >>>> >>>>  struct lsdc_crtc0_funcs_ls2k0500 = { >>>>     // no >>>>  }; >>>> >>>> At first, this looks like a lot of code. But in the long term, this >>>> pays of, because it's so much more maintainable. If you have >>>> functions that work with any CRTC, you can still share the >>>> implementation. >>> I am agree. >>>> >>>> All this goes for other components as well. >>>> >>>> You may want to study mgag200. It supports ~10 chips. Handling this >>>> in single functions was unmaintainable. So we split everything into >>>> smaller helpers that each handle a single case. And then we build a >>>> dedicated modesetting pipeline for each chip from various helpers. >>>> Common code is shared among the chips. >>>> >>> I guess you may want divide and conquer them, mgag200 driver's >>> structure is basic same with ast. >>> >>> they have only one display pipe and they are both standard pci >>> device. I'm already absorb many merits from mgag200 and ast. >>> >>> And i buy two mga video card, sadly they can boot on loongson >>> platform, x86emu will let them pass, loongson uefi/pmon firmware >>> >>> need adapting. I can only study it by reading the code. >>> >> Sorry, there have typos here: >> >> i buy two MGA video cards, sadly they can't boot on loongson >> platform, x86emu in firmware will block them >> >> from working. Study it on x86 platform is still possible i think. >> >> I will focus on its decision seriously, and improve device driver >> design skill. But i found it is so different >> >> from the display controller in ls7a1000/ls7a2000, the display >> controller in ls7a1000/ls7a2000 seem come from >> >> embedded world and walking toward pc, and it don't support vga >> address hole and don't compatible >> >> with IBM vga standard. mgag200 seems come from standard old pc vga >> card. >> >>>> Here's where we select the chip: >>>> >>>> >>>> https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_drv.c#L227 >>>> >>>> >>>> There's a source file for each chip and the common source code is in >>>> >>> I will follow this method at next version, but our hardware and bios >>> engineers is ugly, they often made the chip indistinguishable. >>> >>> ls7a1000 and ls2k1000 have the same pci device id, ls7a2000 and >>> ls2k2000 have the same pci device id, >>> >>> even the revision id is same. I have no choice but to rely on DT or >>> kernel cmd passing parameters. >>> >>> many reviewers and maintainer seem not happy about this. >>> >>>> https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_mode.c >>>> >>>> >>>> >>>>> +   .reset = lsdc_crtc_reset, >>>>> +   .destroy = drm_crtc_cleanup, >>>>> +   .set_config = drm_atomic_helper_set_config, >>>>> +   .page_flip = drm_atomic_helper_page_flip, >>>>> +   .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, >>>>> +   .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, >>>>> +   .get_vblank_counter = lsdc_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, >>>>> +}; >>>>> + >>>>> +static enum drm_mode_status >>>>> +lsdc_crtc_mode_valid(struct drm_crtc *crtc, >>>>> +            const struct drm_display_mode *mode) >>>>> +{ >>>>> +   struct drm_device *ddev = crtc->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   if (mode->hdisplay > descp->max_width) >>>>> +       return MODE_BAD_HVALUE; >>>>> +   if (mode->vdisplay > descp->max_height) >>>>> +       return MODE_BAD_VVALUE; >>>>> + >>>>> +   if (mode->clock > descp->max_pixel_clk) { >>>>> +       drm_dbg(ddev, "mode %dx%d, pixel clock=%d is too high\n", >>>>> +           mode->hdisplay, mode->vdisplay, mode->clock); >>>>> +       return MODE_CLOCK_HIGH; >>>>> +   } >>>>> + >>>>> +   if ((mode->hdisplay * 4) % descp->pitch_align) { >>>>> +       drm_dbg(ddev, "stride is require to not %u bytes aligned\n", >>>>> +           descp->pitch_align); >>>>> +       return MODE_BAD; >>>>> +   } >>>>> + >>>>> +   return MODE_OK; >>>>> +} >>>>> + >>>>> +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc, >>>>> +                   struct drm_crtc_state *state) >>>>> +{ >>>>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>>>> +   struct lsdc_pll *pixpll = &dispipe->pixpll; >>>>> +   const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; >>>>> +   struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); >>>>> +   bool ret; >>>>> + >>>>> +   ret = pfuncs->compute(pixpll, state->mode.clock, >>>>> &priv_state->pparms); >>>>> +   if (ret) >>>>> +       return 0; >>>>> + >>>>> +   drm_warn(crtc->dev, "failed find PLL parameters for %u\n", >>>>> state->mode.clock); >>>>> + >>>>> +   return -EINVAL; >>>>> +} >>>>> + >>>>> +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; /* no mode checks if CRTC is being disabled */ >>>>> + >>>>> +   return lsdc_pixpll_atomic_check(crtc, crtc_state); >>>>> +} >>>>> + >>>>> +static void lsdc_update_pixclk(struct lsdc_display_pipe *dispipe, >>>>> +                  struct lsdc_crtc_state *priv_state) >>>>> +{ >>>>> +   struct lsdc_pll *pixpll = &dispipe->pixpll; >>>>> +   const struct lsdc_pixpll_funcs *clkfunc = pixpll->funcs; >>>>> + >>>>> +   clkfunc->update(pixpll, &priv_state->pparms); >>>>> +} >>>>> + >>>>> +static void lsdc_crtc_helper_mode_set_nofb(struct drm_crtc *crtc) >>>>> +{ >>>>> +   struct drm_device *ddev = crtc->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   struct drm_display_mode *mode = &crtc->state->mode; >>>>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>>>> +   struct lsdc_crtc_state *priv_state = >>>>> to_lsdc_crtc_state(crtc->state); >>>>> +   unsigned int index = dispipe->index; >>>>> +   u32 h_sync, v_sync, h_val, v_val; >>>>> +   u32 val; >>>>> + >>>>> +   /* 26:16 total pixels, 10:0 visiable pixels, in horizontal */ >>>>> +   h_val = (mode->crtc_htotal << 16) | mode->crtc_hdisplay; >>>>> + >>>>> +   /* 26:16 total pixels, 10:0 visiable pixels, in vertical */ >>>>> +   v_val = (mode->crtc_vtotal << 16) | mode->crtc_vdisplay; >>>>> +   /* 26:16 hsync end, 10:0 hsync start, bit 30 is hsync enable */ >>>>> +   h_sync = (mode->crtc_hsync_end << 16) | >>>>> mode->crtc_hsync_start | CFG_HSYNC_EN; >>>>> +   if (mode->flags & DRM_MODE_FLAG_NHSYNC) >>>>> +       h_sync |= CFG_HSYNC_INV; >>>>> + >>>>> +   /* 26:16 vsync end, 10:0 vsync start, bit 30 is vsync enable */ >>>>> +   v_sync = (mode->crtc_vsync_end << 16) | >>>>> mode->crtc_vsync_start | CFG_VSYNC_EN; >>>>> +   if (mode->flags & DRM_MODE_FLAG_NVSYNC) >>>>> +       v_sync |= CFG_VSYNC_INV; >>>>> + >>>>> +   if (index == 0) { >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG, h_val); >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG, v_val); >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG, h_sync); >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG, v_sync); >>>>> + >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>>> +       val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>>> +   } else if (index == 1) { >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_HDISPLAY_REG, h_val); >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_VDISPLAY_REG, v_val); >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_HSYNC_REG, h_sync); >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_VSYNC_REG, v_sync); >>>>> + >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>>> +       val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>>> +   } >>>> >>>> Same here as with vblank: have one helper for each index and use >>>> different _funcs structures for each pipeline. >>>> >>> Well, I will revise lsdc driver with this idea in mind. >>>>> + >>>>> +   drm_dbg(ddev, "%s modeset: %ux%u\n", >>>>> +       crtc->name, mode->hdisplay, mode->vdisplay); >>>>> + >>>>> +   lsdc_update_pixclk(dispipe, priv_state); >>>>> +} >>>>> + >>>>> +static void lsdc_enable_display(struct lsdc_device *ldev, >>>>> unsigned int index) >>>>> +{ >>>>> +   u32 val; >>>>> + >>>>> +   if (index == 0) { >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>>> +       val |= CFG_OUTPUT_EN; >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>>> +       return; >>>>> +   } >>>>> + >>>>> +   if (index == 1) { >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>>> +       val |= CFG_OUTPUT_EN; >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>>> +       return; >>>>> +   } >>>>> +} >>>>> + >>>>> +static void lsdc_disable_display(struct lsdc_device *ldev, >>>>> unsigned int index) >>>>> +{ >>>>> +   u32 val; >>>>> + >>>>> +   if (index == 0) { >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>>> +       val &= ~CFG_OUTPUT_EN; >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>>> +       return; >>>>> +   } >>>>> + >>>>> +   if (index == 1) { >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>>> +       val &= ~CFG_OUTPUT_EN; >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>>> +       return; >>>>> +   } >>>>> +} >>>>> + >>>>> +static void lsdc_crtc_helper_atomic_enable(struct drm_crtc *crtc, >>>>> +                      struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_device *ddev = crtc->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> + >>>>> +   drm_crtc_vblank_on(crtc); >>>>> + >>>>> +   lsdc_enable_display(ldev, drm_crtc_index(crtc)); >>>>> + >>>>> +   drm_dbg(ddev, "%s: enabled\n", crtc->name); >>>>> +} >>>>> + >>>>> +static void lsdc_crtc_helper_atomic_disable(struct drm_crtc *crtc, >>>>> +                       struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_device *ddev = crtc->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> + >>>>> +   lsdc_disable_display(ldev, drm_crtc_index(crtc)); >>>>> + >>>>> +   drm_crtc_wait_one_vblank(crtc); >>>>> + >>>>> +   drm_crtc_vblank_off(crtc); >>>>> + >>>>> +   drm_dbg(ddev, "%s: disabled\n", crtc->name); >>>>> +} >>>>> + >>>>> +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 drm_device *ddev = crtc->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   unsigned int index = drm_crtc_index(crtc); >>>>> +   int line, vsw, vbp, vactive_start, vactive_end, vfp_end; >>>>> +   u32 val = 0; >>>>> + >>>>> +   vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; >>>>> +   vbp = mode->crtc_vtotal - mode->crtc_vsync_end; >>>>> + >>>>> +   vactive_start = vsw + vbp + 1; >>>>> + >>>>> +   vactive_end = vactive_start + mode->crtc_vdisplay; >>>>> + >>>>> +   /* last scan line before VSYNC */ >>>>> +   vfp_end = mode->crtc_vtotal; >>>>> + >>>>> +   if (stime) >>>>> +       *stime = ktime_get(); >>>>> + >>>>> +   if (index == 0) >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); >>>>> +   else if (index == 1) >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); >>>>> + >>>>> +   line = (val & 0xffff); >>>>> + >>>>> +   if (line < vactive_start) >>>>> +       line -= vactive_start; >>>>> +   else if (line > vactive_end) >>>>> +       line = line - vfp_end - vactive_start; >>>>> +   else >>>>> +       line -= vactive_start; >>>>> + >>>>> +   *vpos = line; >>>>> +   *hpos = val >> 16; >>>>> + >>>>> +   if (etime) >>>>> +       *etime = ktime_get(); >>>>> + >>>>> +   return true; >>>>> +} >>>>> + >>>>> +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { >>>>> +   .mode_valid = lsdc_crtc_mode_valid, >>>>> +   .mode_set_nofb = lsdc_crtc_helper_mode_set_nofb, >>>>> +   .atomic_enable = lsdc_crtc_helper_atomic_enable, >>>>> +   .atomic_disable = lsdc_crtc_helper_atomic_disable, >>>>> +   .atomic_check = lsdc_crtc_helper_atomic_check, >>>>> +   .atomic_flush = lsdc_crtc_atomic_flush, >>>>> +   .get_scanout_position = lsdc_crtc_get_scanout_position, >>>>> +}; >>>>> + >>>>> +int lsdc_crtc_init(struct drm_device *ddev, >>>>> +          struct drm_crtc *crtc, >>>>> +          unsigned int index, >>>>> +          struct drm_plane *primary, >>>>> +          struct drm_plane *cursor) >>>>> +{ >>>>> +   int ret; >>>>> + >>>>> +   ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor, >>>>> +                   &lsdc_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) >>>>> +       drm_warn(ddev, "set the gamma table size failed\n"); >>>>> + >>>>> +   drm_crtc_enable_color_mgmt(crtc, 0, false, 256); >>>> >>>> Maybe leave this out for now. You need to handle the property in >>>> your driver. I don't see code for that. >>>> >>>> Color management should probably go into a separate patch. >>>> >>> Ok, I'm agree to leave gamma support to a separate patch >>>>> + >>>>> +   return 0; >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>>>> new file mode 100644 >>>>> index 000000000000..176ae4e11d1c >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>>>> @@ -0,0 +1,191 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0+ >>>>> + >>>>> +#include <drm/drm_debugfs.h> >>>>> +#include <drm/drm_managed.h> >>>>> +#include <drm/drm_gem_vram_helper.h> >>>>> +#include "lsdc_drv.h" >>>>> +#include "lsdc_regs.h" >>>>> +#include "lsdc_pll.h" >>>>> + >>>>> +#ifdef CONFIG_DEBUG_FS >>>>> +static int lsdc_show_clock(struct seq_file *m, void *arg) >>>>> +{ >>>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>>> +   struct drm_device *ddev = node->minor->dev; >>>>> +   struct drm_crtc *crtc; >>>>> + >>>>> +   drm_for_each_crtc(crtc, ddev) { >>>>> +       struct lsdc_display_pipe *pipe = crtc_to_display_pipe(crtc); >>>>> +       struct lsdc_pll *pixpll = &pipe->pixpll; >>>>> +       const struct lsdc_pixpll_funcs *funcs = pixpll->funcs; >>>>> +       struct drm_display_mode *adj = &crtc->state->mode; >>>>> +       struct lsdc_pll_parms parms; >>>>> +       unsigned int out_khz; >>>>> + >>>>> +       out_khz = funcs->get_clock_rate(pixpll, &parms); >>>>> + >>>>> +       seq_printf(m, "Display pipe %u: %dx%d\n", >>>>> +              pipe->index, adj->hdisplay, adj->vdisplay); >>>>> + >>>>> +       seq_printf(m, "Frequency actually output: %u kHz\n", >>>>> out_khz); >>>>> +       seq_printf(m, "Pixel clock required: %d kHz\n", adj->clock); >>>>> +       seq_printf(m, "diff: %d kHz\n", adj->clock); >>>>> + >>>>> +       seq_printf(m, "div_ref=%u, loopc=%u, div_out=%u\n", >>>>> +              parms.div_ref, parms.loopc, parms.div_out); >>>>> + >>>>> +       seq_printf(m, "hsync_start=%d, hsync_end=%d, htotal=%d\n", >>>>> +              adj->hsync_start, adj->hsync_end, adj->htotal); >>>>> +       seq_printf(m, "vsync_start=%d, vsync_end=%d, vtotal=%d\n\n", >>>>> +              adj->vsync_start, adj->vsync_end, adj->vtotal); >>>>> +   } >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static int lsdc_show_mm(struct seq_file *m, void *arg) >>>>> +{ >>>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>>> +   struct drm_device *ddev = node->minor->dev; >>>>> +   struct drm_printer p = drm_seq_file_printer(m); >>>>> + >>>>> + drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +#define REGDEF(reg) { __stringify_1(LSDC_##reg##_REG), >>>>> LSDC_##reg##_REG } >>>>> +static const struct { >>>>> +   const char *name; >>>>> +   u32 reg_offset; >>>>> +} lsdc_regs_array[] = { >>>>> +   REGDEF(CURSOR0_CFG), >>>>> +   REGDEF(CURSOR0_ADDR_LO), >>>>> +   REGDEF(CURSOR0_ADDR_HI), >>>>> +   REGDEF(CURSOR0_POSITION), >>>>> +   REGDEF(CURSOR0_BG_COLOR), >>>>> +   REGDEF(CURSOR0_FG_COLOR), >>>>> +   REGDEF(INT), >>>>> +   REGDEF(CRTC0_CFG), >>>>> +   REGDEF(CRTC0_STRIDE), >>>>> +   REGDEF(CRTC0_FB_ORIGIN), >>>>> +   REGDEF(CRTC0_HDISPLAY), >>>>> +   REGDEF(CRTC0_HSYNC), >>>>> +   REGDEF(CRTC0_VDISPLAY), >>>>> +   REGDEF(CRTC0_VSYNC), >>>>> +   REGDEF(CRTC0_GAMMA_INDEX), >>>>> +   REGDEF(CRTC0_GAMMA_DATA), >>>>> +   REGDEF(CRTC1_CFG), >>>>> +   REGDEF(CRTC1_STRIDE), >>>>> +   REGDEF(CRTC1_FB_ORIGIN), >>>>> +   REGDEF(CRTC1_HDISPLAY), >>>>> +   REGDEF(CRTC1_HSYNC), >>>>> +   REGDEF(CRTC1_VDISPLAY), >>>>> +   REGDEF(CRTC1_VSYNC), >>>>> +   REGDEF(CRTC1_GAMMA_INDEX), >>>>> +   REGDEF(CRTC1_GAMMA_DATA), >>>>> +}; >>>>> + >>>>> +static int lsdc_show_regs(struct seq_file *m, void *arg) >>>>> +{ >>>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>>> +   struct drm_device *ddev = node->minor->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   int i; >>>>> + >>>>> +   for (i = 0; i < ARRAY_SIZE(lsdc_regs_array); i++) { >>>>> +       u32 offset = lsdc_regs_array[i].reg_offset; >>>>> +       const char *name = lsdc_regs_array[i].name; >>>>> + >>>>> +       seq_printf(m, "%s (0x%04x): 0x%08x\n", >>>>> +              name, offset, lsdc_rreg32(ldev, offset)); >>>>> +   } >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static int lsdc_show_vblank_counter(struct seq_file *m, void *arg) >>>>> +{ >>>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>>> +   struct drm_device *ddev = node->minor->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> + >>>>> +   seq_printf(m, "CRTC-0 vblank counter: %08u\n", >>>>> +          lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG)); >>>>> + >>>>> +   seq_printf(m, "CRTC-1 vblank counter: %08u\n", >>>>> +          lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG)); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static int lsdc_show_scan_position(struct seq_file *m, void *arg) >>>>> +{ >>>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>>> +   struct drm_device *ddev = node->minor->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   u32 val0 = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); >>>>> +   u32 val1 = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); >>>>> + >>>>> +   seq_printf(m, "CRTC-0: x: %08u, y: %08u\n", >>>>> +          val0 >> 16, val0 & 0xFFFF); >>>>> + >>>>> +   seq_printf(m, "CRTC-1: x: %08u, y: %08u\n", >>>>> +          val1 >> 16, val1 & 0xFFFF); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static int lsdc_show_fb_addr(struct seq_file *m, void *arg) >>>>> +{ >>>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>>> +   struct drm_device *ddev = node->minor->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   u32 lo, hi; >>>>> +   u32 val; >>>>> + >>>>> +   val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>>> +   if (val & CFG_FB_IN_USING) { >>>>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_LO_ADDR_REG); >>>>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_HI_ADDR_REG); >>>>> +       seq_printf(m, "CRTC-0 using fb1: 0x%x:%x\n", hi, lo); >>>>> +   } else { >>>>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_LO_ADDR_REG); >>>>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_HI_ADDR_REG); >>>>> +       seq_printf(m, "CRTC-0 using fb0: 0x%x:%x\n", hi, lo); >>>>> +   } >>>>> + >>>>> +   val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>>> +   if (val & CFG_FB_IN_USING) { >>>>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_LO_ADDR_REG); >>>>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_HI_ADDR_REG); >>>>> +       seq_printf(m, "CRTC-1 using fb1: 0x%x:%x\n", hi, lo); >>>>> +   } else { >>>>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_LO_ADDR_REG); >>>>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_HI_ADDR_REG); >>>>> +       seq_printf(m, "CRTC-1 using fb0: 0x%x:%x\n", hi, lo); >>>>> +   } >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static struct drm_info_list lsdc_debugfs_list[] = { >>>>> +   { "clocks",  lsdc_show_clock, 0 }, >>>>> +   { "mm",      lsdc_show_mm, 0, NULL }, >>>>> +   { "regs",    lsdc_show_regs, 0 }, >>>>> +   { "vblanks", lsdc_show_vblank_counter, 0, NULL }, >>>>> +   { "scan_pos", lsdc_show_scan_position, 0, NULL }, >>>>> +   { "fb_addr", lsdc_show_fb_addr, 0, NULL }, >>>>> +}; >>>>> + >>>>> +#endif >>>>> + >>>>> +void lsdc_debugfs_init(struct drm_minor *minor) >>>>> +{ >>>>> +#ifdef CONFIG_DEBUG_FS >>>>> +   drm_debugfs_create_files(lsdc_debugfs_list, >>>>> +                ARRAY_SIZE(lsdc_debugfs_list), >>>>> +                minor->debugfs_root, >>>>> +                minor); >>>>> +#endif >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_drv.c >>>>> new file mode 100644 >>>>> index 000000000000..3bd457e6e980 >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.c >>>>> @@ -0,0 +1,635 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0+ >>>>> +/* >>>>> + * KMS driver for Loongson display controller >>>>> + * Copyright (C) 2022 Loongson Corporation >>>>> + * >>>>> + * Authors: >>>>> + *     Li Yi <liyi@loongson.cn> >>>>> + *     Li Chenyang <lichenyang@loongson.cn> >>>>> + *     Sui Jingfeng <suijingfeng@loongson.cn> >>>>> + */ >>>>> + >>>>> +#include <linux/module.h> >>>>> +#include <linux/pci.h> >>>>> +#include <linux/of_address.h> >>>>> +#include <drm/drm_aperture.h> >>>>> +#include <drm/drm_vblank.h> >>>>> +#include <drm/drm_fb_helper.h> >>>>> +#include <drm/drm_gem_vram_helper.h> >>>>> +#include <drm/drm_gem_framebuffer_helper.h> >>>>> +#include <drm/drm_modeset_helper.h> >>>>> +#include <drm/drm_atomic_helper.h> >>>>> +#include <drm/drm_probe_helper.h> >>>>> +#include <drm/drm_fbdev_generic.h> >>>>> +#include "lsdc_drv.h" >>>> >>>> Alphebetical order. >>> I will fix this problem at next version >>>> >>>>> + >>>>> +static int lsdc_chip = -1; >>>>> +MODULE_PARM_DESC(chip, "override chip id (-1 = probed by driver"); >>>>> +module_param_named(chip, lsdc_chip, int, 0644); >>>> >>>> That's a development option. Better not add it to the published >>>> driver. >>> >>> Without this, compile with W=1 generate warnings. and removing >>> chip descriptions for Loongson SoC >>> >>> is not wanted either. this option can be removed if DT support is >>> upstream in the future. >>> >>> It allow us to developing driver before DT or ACPI support is ready. >>> Otherwise we have no way developing >>> >>> drivers for loongson SoC beforehand. >>> >>>>> + >>>>> +/* >>>>> + * ls7a1000 and ls7a2000 is not only used with loongson desktop >>>>> class CPU >>>>> + * but also loongson server class CPUs (LS3C5000@2.2GHz 16 core). >>>>> Loongson >>>>> + * server products generally don't has a dedicated vram mounted >>>>> for cost >>>>> + * reason, it will use aspeed bmc as its display on its final and >>>>> formal >>>>> + * product. lsdc.has_vram=0 is mainly be use in developing phase >>>>> of the >>>>> + * product. When a board don't has a dedicated vram mounted, we >>>>> can put >>>>> + * the framebuffer on system RAM. >>>>> + */ >>>>> +static int lsdc_has_vram = -1; >>>>> +MODULE_PARM_DESC(has_vram, "has on-board gpu memory in reality (0 >>>>> = no, -1 = default"); >>>>> +module_param_named(has_vram, lsdc_has_vram, int, 0644); >>>> >>>> Same here. That's for development. >>> >>> Err, we truly very need this feature, >>> >>> 1) ASPEED BMC card is removable, it can be unplug from the mother >>> board of Loongson Server. >>> >>> leaving this option there, we still have change enter X server >>> graphic environment. >>> >>> > Hi, > > "change" is typos, we still have chance enter X... > > > With this option, we have the opportunity to let VRAM helper manage > system ram, > > so that we can use VRAM helper based driver for loongson SoC devices. > > We may potentially have chance to improve VRAM helper, > > for example using ttm_cached caching property for system ram. Would > this feasible? > > >>> 2) We need developing drivers for loongson SoC on loongson PC >>> platform, we may deliberately don't >>> >>> use on-board vram. there a need to let TTM manage cached reserved >>> RAM, because such device don't >>> >>> have vram. We have achieve this goal on ls2k2000 SoC. >>> >>> Otherwise we have to write two piece of driver, one is DMA helper >>> base, the other is TTM based, >>> >>> we doing so at downstream kernel. >>> >>>> >>>>> + >>>>> +#define DRIVER_AUTHOR       "Sui Jingfeng >>>>> <suijingfeng@loongson.cn>" >>>>> +#define DRIVER_NAME       "lsdc" >>>>> +#define DRIVER_DESC       "drm driver for loongson's display >>>>> controller" >>>>> +#define DRIVER_DATE       "20220701" >>>>> +#define DRIVER_MAJOR       1 >>>>> +#define DRIVER_MINOR       0 >>>>> +#define DRIVER_PATCHLEVEL   0 >>>>> + >>>>> +static const struct lsdc_desc dc_in_ls2k0500 = { >>>> >>>> Those description structs are a two-edged sword IMHO. >>>> >>>>> +   .chip = CHIP_LS2K0500, >>>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>>> +   .max_pixel_clk = 200000, >>>>> +   .max_width = 2048, >>>>> +   .max_height = 2048, >>>>> +   .hw_cursor_w = 32, >>>>> +   .hw_cursor_h = 32, >>>>> +   .pitch_align = 256, >>>>> +   .mc_bits = 40, >>>> >>>> These are device constants are probably fine. >>>> >>>>> +   .num_of_hw_cursor = 1, >>>>> +   .has_vblank_counter = false, >>>>> +   .has_builtin_i2c = false, >>>>> +   .has_vram = false, >>>>> +   .has_hpd_reg = false, >>>>> +   .is_soc = true, >>>> >>>> But from a quick look through the code, these settings affect >>>> control flow and/or modesetting pipelines. They are often better >>>> implemented by dedicated helpers as outlined in my comments about >>>> vblank and mgag200. >>> >>> Ok, mgag200 is for server mainly, its variant are not so different >>> as loongson does, >>> >>> at least they all have vram, we have to put more attention to turn >>> drm memory manager. >>> >>> I will following your advice by introducing more helpers at next >>> version. >>> Sorry, "turn" is a typo, -> "tune" I means, all mgag200 have VRAM, but our SoC don't have vram, Should i keeping the support for those SoC chip? or simply drop the support for the chip without vram? >>>> >>>> >>>>> +}; >>>>> + >>>>> +static const struct lsdc_desc dc_in_ls2k1000 = { >>>>> +   .chip = CHIP_LS2K1000, >>>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>>> +   .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, >>>>> +   .has_builtin_i2c = false, >>>>> +   .has_vram = false, >>>>> +   .has_hpd_reg = false, >>>>> +   .is_soc = true, >>>>> +}; >>>>> + >>>>> +static const struct lsdc_desc dc_in_ls2k2000 = { >>>>> +   .chip = CHIP_LS2K2000, >>>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>>> +   .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 = 256, >>>>> +   .mc_bits = 40, >>>>> +   .has_vblank_counter = true, >>>>> +   .has_builtin_i2c = true, >>>>> +   .has_vram = false, >>>>> +   .has_hpd_reg = true, >>>>> +   .is_soc = true, >>>>> +}; >>>>> + >>>>> +static const struct lsdc_desc dc_in_ls7a1000 = { >>>>> +   .chip = CHIP_LS7A1000, >>>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>>> +   .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, >>>>> +   .has_builtin_i2c = true, >>>>> +   .has_vram = true, >>>>> +   .has_hpd_reg = false, >>>>> +   .is_soc = false, >>>>> +}; >>>>> + >>>>> +static const struct lsdc_desc dc_in_ls7a2000 = { >>>>> +   .chip = CHIP_LS7A2000, >>>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>>> +   .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 = 256, >>>>> +   .mc_bits = 40, /* support 48, but use 40 for backward >>>>> compatibility */ >>>>> +   .has_vblank_counter = true, >>>>> +   .has_builtin_i2c = true, >>>>> +   .has_vram = true, >>>>> +   .has_hpd_reg = true, >>>>> +   .is_soc = false, >>>>> +}; >>>>> + >>>>> +static int lsdc_gem_dumb_create(struct drm_file *file, >>>>> +               struct drm_device *ddev, >>>>> +               struct drm_mode_create_dumb *args) >>>>> +{ >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   return drm_gem_vram_fill_create_dumb(file, ddev, 0, >>>>> descp->pitch_align, args); >>>>> +} >>>>> + >>>>> +DEFINE_DRM_GEM_FOPS(lsdc_gem_fops); >>>>> + >>>>> +static const struct drm_driver lsdc_drm_driver = { >>>>> +   .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, >>>>> +   .fops = &lsdc_gem_fops, >>>>> + >>>>> +   .name = DRIVER_NAME, >>>>> +   .desc = DRIVER_DESC, >>>>> +   .date = DRIVER_DATE, >>>>> +   .major = DRIVER_MAJOR, >>>>> +   .minor = DRIVER_MINOR, >>>>> +   .patchlevel = DRIVER_PATCHLEVEL, >>>>> + >>>>> +   .debugfs_init = lsdc_debugfs_init, >>>>> +   .dumb_create = lsdc_gem_dumb_create, >>>>> +   .dumb_map_offset = drm_gem_ttm_dumb_map_offset, >>>>> +   .gem_prime_mmap = drm_gem_prime_mmap, >>>>> +}; >>>>> + >>>>> +static const struct drm_mode_config_funcs lsdc_mode_config_funcs = { >>>>> +   .fb_create = drm_gem_fb_create, >>>>> +   .output_poll_changed = drm_fb_helper_output_poll_changed, >>>>> +   .atomic_check = drm_atomic_helper_check, >>>>> +   .atomic_commit = drm_atomic_helper_commit, >>>>> +   .mode_valid = drm_vram_helper_mode_valid, >>>>> +}; >>>>> + >>>>> +static int lsdc_modeset_init(struct lsdc_device *ldev, >>>>> +                const struct lsdc_desc *descp) >>>>> +{ >>>>> +   struct drm_device *ddev = &ldev->base; >>>>> +   unsigned int num_crtc = descp->num_of_crtc; >>>>> +   unsigned int i; >>>>> +   int ret; >>>>> + >>>>> +   for (i = 0; i < num_crtc; i++) { >>>>> +       ret = lsdc_create_output(ldev, i); >>>>> +       if (ret) >>>>> +           return ret; >>>>> +   } >>>>> + >>>>> +   for (i = 0; i < num_crtc; i++) { >>>>> +       struct lsdc_display_pipe *dispipe = &ldev->dispipe[i]; >>>>> +       struct lsdc_pll *pixpll = &dispipe->pixpll; >>>>> +       struct drm_plane *primary = &dispipe->primary; >>>>> +       struct drm_plane *cursor = &dispipe->cursor; >>>>> +       struct drm_crtc *crtc = &dispipe->crtc; >>>>> + >>>>> +       dispipe->index = i; >>>>> + >>>>> +       ret = lsdc_pixpll_init(pixpll, ddev, i); >>>>> +       if (ret) >>>>> +           return ret; >>>>> + >>>>> +       ret = lsdc_plane_init(ldev, primary, >>>>> DRM_PLANE_TYPE_PRIMARY, i); >>>>> +       if (ret) >>>>> +           return ret; >>>>> + >>>>> +       ret = lsdc_plane_init(ldev, cursor, >>>>> DRM_PLANE_TYPE_CURSOR, i); >>>>> +       if (ret) >>>>> +           return ret; >>>>> + >>>>> +       /* >>>>> +        * Initial all of the CRTC available, in this way the >>>>> logical >>>>> +        * crtc index is equal to the hardware crtc index. >>>>> +        * >>>>> +        * display pipe 0 = crtc0 + dvo0 + encoder0 + connector0 >>>>> +        * display pipe 1 = crtc1 + dvo1 + encoder1 + connector1 >>>>> +        */ >>>>> +       ret = lsdc_crtc_init(ddev, crtc, i, primary, cursor); >>>>> +       if (ret) >>>>> +           return ret; >>>>> + >>>>> +       drm_info(ddev, "display pipe %u initialized\n", i); >>>>> +   } >>>>> + >>>>> +   drm_mode_config_reset(ddev); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static int lsdc_mode_config_init(struct drm_device *ddev, >>>>> +                const struct lsdc_desc *descp) >>>>> +{ >>>>> +   int ret; >>>>> + >>>>> +   ret = drmm_mode_config_init(ddev); >>>>> +   if (ret) >>>>> +       return ret; >>>>> + >>>>> +   ddev->mode_config.funcs = &lsdc_mode_config_funcs; >>>>> +   ddev->mode_config.min_width = 1; >>>>> +   ddev->mode_config.min_height = 1; >>>>> +   ddev->mode_config.max_width = descp->max_width * LSDC_NUM_CRTC; >>>>> +   ddev->mode_config.max_height = descp->max_height * >>>>> LSDC_NUM_CRTC; >>>>> +   ddev->mode_config.preferred_depth = 24; >>>>> +   ddev->mode_config.prefer_shadow = descp->has_vram; >>>>> + >>>>> +   ddev->mode_config.cursor_width = descp->hw_cursor_h; >>>>> +   ddev->mode_config.cursor_height = descp->hw_cursor_h; >>>>> + >>>>> +   /* max_vblank_count is set on each CRTC */ >>>>> +   ddev->max_vblank_count = 0xffffffff; >>>>> + >>>>> +   return ret; >>>>> +} >>>>> + >>>>> +/* >>>>> + * lsdc_detect_chip - a function to tell lsdc variant apart. >>>>> + */ >>>>> +static const struct lsdc_desc * >>>>> +lsdc_detect_chip(struct pci_dev *pdev, const struct pci_device_id >>>>> *ent) >>>>> +{ >>>>> +   /* >>>>> +    * Provide a way to false the driver running on new model >>>>> +    * before we have a proper way to tell them apart >>>>> +    */ >>>>> +   if (lsdc_chip == CHIP_LS2K2000) { >>>>> +       dev_info(&pdev->dev, "Override as LS2K2000\n"); >>>>> +       return &dc_in_ls2k2000; >>>>> +   } >>>>> + >>>>> +   if (lsdc_chip == CHIP_LS2K1000) { >>>>> +       dev_info(&pdev->dev, "Override as LS2K1000\n"); >>>>> +       return &dc_in_ls2k1000; >>>>> +   } >>>>> + >>>>> +   if (lsdc_chip == CHIP_LS2K0500) { >>>>> +       dev_info(&pdev->dev, "Override as LS2K0500\n"); >>>>> +       return &dc_in_ls2k0500; >>>>> +   } >>>>> + >>>>> +   if (ent) { >>>>> +       struct lsdc_desc *dc_descp; >>>>> + >>>>> +       dc_descp = (struct lsdc_desc *)ent->driver_data; >>>> >>>> Is that legal? driver_data is of kernel_ulong_t. Maybe store the >>>> CHIP_ constant here and then lookup the lsdc_desc. >>>> >>> It works in practice but it seems dangerous doing so, I will using >>> CHIP_ LS7AXXXX and CHIP_LS2KXXXX at new version. >>> >>> i don't notice it before, thank you. >>> >>>> >>>> https://elixir.bootlin.com/linux/latest/source/include/linux/mod_devicetable.h#L36 >>>> >>>> >>>>> +       if (dc_descp->chip == CHIP_LS7A1000) >>>>> +           dev_info(&pdev->dev, "LS7A1000 Found revision: %u\n", >>>>> pdev->revision); >>>>> +       else if (dc_descp->chip == CHIP_LS7A2000) >>>>> +           dev_info(&pdev->dev, "LS7A2000 Found revison: %u\n", >>>>> pdev->revision); >>>>> + >>>>> +       return dc_descp; >>>>> +   } >>>>> + >>>>> +   dev_err(&pdev->dev, "Not known device\n"); >>>>> + >>>>> +   return NULL; >>>>> +} >>>>> + >>>>> +static int lsdc_get_dedicated_vram(struct lsdc_device *ldev, >>>>> +                  const struct lsdc_desc *descp) >>>>> +{ >>>>> +   struct drm_device *ddev = &ldev->base; >>>>> +   struct pci_dev *gpu; >>>>> +   resource_size_t base, size; >>>>> + >>>>> +   /* >>>>> +    * The GPU and display controller in LS7A1000/LS7A2000 are >>>>> separated >>>>> +    * PCIE devices, they are two devices not one. The DC is a >>>>> pci device, >>>>> +    * but it don't have a dedicate VRAM bar, the BIOS engineer >>>>> choose to >>>>> +    * assign the VRAM to the gpu device. Sadly, after years >>>>> application, >>>>> +    * this decision form as a convention for loongson integrate >>>>> graphics. >>>>> +    * For LS7A1000 and LS7A2000, bar 2 of GPU device contain the >>>>> VRAM, >>>>> +    * both the GPU and the DC can make use of the VRAM depend on >>>>> how DRM >>>>> +    * device driver is written. Therefore, we have to do some >>>>> tricks here. >>>>> +    */ >>>>> +   if (descp->chip == CHIP_LS7A1000) >>>>> +       gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A15, NULL); >>>>> +   else if (descp->chip == CHIP_LS7A2000) >>>>> +       gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A25, NULL); >>>>> + >>>>> +   if (!gpu) { >>>>> +       drm_warn(ddev, "No GPU device found\n"); >>>>> +       return -ENODEV; >>>>> +   } >>>>> + >>>>> +   base = pci_resource_start(gpu, 2); >>>>> +   size = pci_resource_len(gpu, 2); >>>>> + >>>>> +   ldev->vram_base = base; >>>>> +   ldev->vram_size = size; >>>>> + >>>>> +   drm_info(ddev, "dedicated vram start: 0x%llx, size: %uMB\n", >>>>> +        (u64)base, (u32)(size >> 20)); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +/* >>>>> + * Loongson display controller is not equipped with IOMMU. >>>>> + * In order to make this driver work with Loongson SoC, >>>>> + * you need to either carveout part RAM as VRAM or reserve >>>>> + * part system ram as vram. Pass the start address and size >>>>> + * via DT or ACPI. >>>>> + */ >>>>> +static int lsdc_of_get_reserved_ram(struct lsdc_device *ldev) >>>>> +{ >>>>> +   struct drm_device *ddev = &ldev->base; >>>>> +   unsigned long size = 0; >>>>> +   struct device_node *node; >>>>> +   struct resource r; >>>>> +   int ret; >>>>> + >>>>> +   node = of_parse_phandle(ddev->dev->of_node, "memory-region", 0); >>>>> +   if (!node) { >>>>> +       drm_err(ddev, "No memory-region property or no DT, >>>>> abort\n"); >>>>> +       return -ENOENT; >>>>> +   } >>>>> + >>>>> +   ret = of_address_to_resource(node, 0, &r); >>>>> +   of_node_put(node); >>>>> +   if (ret) >>>>> +       return ret; >>>>> + >>>>> +   size = r.end - r.start + 1; >>>>> + >>>>> +   ldev->vram_base = r.start; >>>>> +   ldev->vram_size = size; >>>>> + >>>>> +   drm_info(ddev, "using VRAM carveout: %lx@%pa\n", size, >>>>> &r.start); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static int >>>>> +lsdc_remove_conflicting_framebuffers(const struct drm_driver >>>>> *pdriver, >>>>> +                    resource_size_t base, >>>>> +                    resource_size_t size) >>>>> +{ >>>>> +   return drm_aperture_remove_conflicting_framebuffers(base, >>>>> +                               size, >>>>> +                               false, >>>>> +                               pdriver); >>>>> +} >>>> >>>> No need to wrap this single function. >>> >>> Ok, Loongson upstream kernel support efifb and it backing memory is >>> located at this range. >>> >>> we must properly remove it, otherwise they will have two fb >>> node(/dev/fb0 and /dev/fb1) >>> >>>> >>>>> + >>>>> +static struct lsdc_device * >>>>> +lsdc_create_device(struct pci_dev *pdev, >>>>> +          const struct pci_device_id *ent, >>>>> +          const struct drm_driver *drv) >>>>> +{ >>>>> +   struct lsdc_device *ldev; >>>>> +   struct drm_device *ddev; >>>>> +   const struct lsdc_desc *descp; >>>>> +   int ret; >>>>> + >>>>> +   ldev = devm_drm_dev_alloc(&pdev->dev, drv, struct >>>>> lsdc_device, base); >>>>> +   if (IS_ERR(ldev)) >>>>> +       return ldev; >>>>> + >>>>> +   ddev = &ldev->base; >>>>> + >>>>> +   pci_set_drvdata(pdev, ddev); >>>>> + >>>>> +   descp = lsdc_detect_chip(pdev, ent); >>>>> +   if (!descp) >>>>> +       return NULL; >>>>> + >>>>> +   ldev->descp = descp; >>>>> + >>>>> +   spin_lock_init(&ldev->reglock); >>>>> + >>>>> +   /* BAR 0 the DC device contains registers */ >>>>> +   ldev->reg_base = pcim_iomap(pdev, 0, 0); >>>>> +   if (!ldev->reg_base) >>>>> +       return ERR_PTR(-EIO); >>>>> + >>>>> +   if (descp->has_vram && lsdc_has_vram) >>>>> +       ret = lsdc_get_dedicated_vram(ldev, descp); >>>>> +   else >>>>> +       ret = lsdc_of_get_reserved_ram(ldev); >>>>> + >>>>> +   if (ret) { >>>>> +       drm_err(ddev, "Init VRAM failed: %d\n", ret); >>>>> +       return ERR_PTR(ret); >>>>> +   } >>>>> + >>>>> +   ret = lsdc_remove_conflicting_framebuffers(drv, >>>>> +                          ldev->vram_base, >>>>> +                          ldev->vram_size); >>>>> +   if (ret) { >>>>> +       drm_err(ddev, "remove firmware framebuffers failed: >>>>> %d\n", ret); >>>>> +       return ERR_PTR(ret); >>>>> +   } >>>>> + >>>>> +   ret = drmm_vram_helper_init(ddev, ldev->vram_base, >>>>> ldev->vram_size); >>>> >>>> Using GEM VRAM helpers could become a problem and I'd advise >>>> against them. >>>> >>> Err, I'm not want to agree this... >>>> First of all, how much video memory is there? VRAM helpers need at >>>> least 3 times the maximum consumption to be reliable.So if you have >>>> a FullHD display that consumes ~8 Mib, you'd need 24 MiB of VRAM. >>>> Hardware cursors can further interfere. >>> >>> Part of ls3a5000/ls3a4000+ls7a1000 machine have 64MB, another part >>> of ls3a5000/ls3a4000+ls7a1000 have 128 MB. >>> >>> For ls7a1000(1080p) the maximum usage actually is two >>> framebuffer(1920x1080x4x2) + two cursor (64x64x4x 2) + one fbcon >>> (1920x1080x4) and LS7A2000 board has at lease 256MB VRAM. >>> >>> Therefore I can guarantee that we have at least 64MB VRAM,enough for >>> double screen(1920x1080) + double cursor. >>> >>> I guess you means that there are some device(aspeed bmc) with >>> limited VRAM size, but it is more a problem >>> >>> of the device itself(It is not vram helper's fault). Early version >>> of ast driver would evict fbcon to system RAM >>> >>> to save VRAM usage, would this idea be a solution for the device >>> with limited vram capacity today? >>> >>> >>>> VRAM helpers also don't support buffer sharing with other devices. >>>> That's a problem for some compositors. >>>> >>>> For those reasons, VRAM helpers are deprecated. I'd rather remove >>>> them entirely. >>> >>> Its not that bad, VRAM helpers is good enough for our case as a >>> first step. Our colleague and customers mainly >>> >>> using MATE and XFCE under X server graphic environment, they need >>> basic 2D graphics environment most. >>> >>> We have tested our driver against macro and xfwm4, it actually works >>> very well. Individual compositor may >>> >>> have a few problems, but it isn't a thing for our case currently. >>> Can we solve those problem by other means? >>> >>> We could give up cross device buffer sharing support for chasing 2D >>> graphic performance , we intend to >>> >>> introducing buffer sharing with the help of the GPU with another >>> patch in the future. We possibly could >>> >>> switch to ttm-based KMS driver under your guiding then. For >>> ls7a1000, I imagine to merge lsdc with evnaviv >>> >>> into a single drm driver.May I suggest to postpone the decision of >>> removing VRAM helpers, its performance >>> >>> is better than shmem helper and have better support for double screen. >>> >>> >>>> As a better alternative for the DC, you could use GEM SHMEM >>>> helpers. See mgag200 or ast for how to do that. >>> >>> We have two display pipe thus support double screen, therefore we >>> have two framebuffer BO and two cursor BO, >>> >>> we need to take clone screen, extend screen and rotate and resize >>> case into consideration, on clone screen case >>> >>> the two CRTC should scanout from the same address, while on extend >>> screen case, the frame buffer itself is grant >>> >>> one buffer if using modesetting driver, but we have two primary >>> plane thus two fb offset. Arrange VRAM bo offset >>> >>> for 4 different drm BO object manually is little bit hack and >>> cumbersome. I have already finish the developing >>> >>> work of shmem helper support, see: >>> https://github.com/loongson-gfx/drm-tip/compare/drm-tip...shmem >>> >>> But it is NOT as good as VRAM helper in the perspective of >>> performance. I think copy from the shadow to the >>> >>> front frame buffer should be done at user space, this can save the >>> overhead of reporting damage rectangle whenever >>> >>> have a damage. >>> >>> >>>> >>>> The comments mention that there's also a GPU. Does the GPU use >>>> VRAM? Or do GPU and DC share memory ranges? >>>> >>> LS7A2000 integrate Loongson self-made GPU, driver is developed by my >>> colleagues, it already works but my mature >>> >>> to upstream yet. LS7A1000 have a VIVANTE GC1000 v5037 (20120617 >>> etc), a ten years ago render-only GPU. >>> >>> the VRAM is connected with the ls7a1000 bridge chip, the memory >>> controller is also in ls7a1000 bridge chip itself. >>> >>> ATI call this type vram as sideport, my company call it as GMEM, I >>> think they are similar. >>> >>> etnaviv don't use VRAM currently, all BOs of etnaviv is in system ram. >>> >>> I think merge etnaviv and lsdc into a single drm driver would be a >>> solution, what do you think? >>> >>> >>> On downstream environment, we hacking make ttm-based lsdc work with >>> etnaviv, by copying(sw resolve) >>> >>> the rendered buffer to vram backed framebuffer, glmark2 get 220 >>> point, quake3 arena can play, haha :) >>> >>> It is achieved by developing xf86-video-loongson which using etnaviv >>> to share buffer between xserver and glmark2, >>> >>> lsdc(or downstream loongson-drm) is only need to implement a >>> self-sharing for userspace process . >>> >>> see https://github.com/loongson-gfx/xf86-video-loongson. The shared >>> rendering buffer is copy >>> >>> to front buffer with loongson LSX and LASX(256-bit) SIMD >>> optimization. Report dirty to kernel space is not need. >>> >>> we can even let suppertiled buffer rendered by etnaviv get exported, >>> xf86-video-loongson get the fd and prime >>> >>> it to handle, then we are using CPU to do the resolve work which >>> directly resolve to the front framebuffer. No damage >>> >>> rectangle reporting is need. >>> >>> >>> Hence, the 2D performance of graphics is the more important. GEM >>> SHMEM helpers is not better than vram helpers >>> >>> in the perspective of performance. >>> >>>>> +   if (ret) { >>>>> +       drm_err(ddev, "vram helper init failed: %d\n", ret); >>>>> +       goto err; >>>>> +   } >>>>> + >>>>> +   ret = lsdc_mode_config_init(ddev, descp); >>>>> +   if (ret) { >>>>> +       drm_dbg(ddev, "%s: %d\n", __func__, ret); >>>>> +       goto err; >>>>> +   } >>>>> + >>>>> +   ret = lsdc_modeset_init(ldev, descp); >>>>> +   if (ret) >>>>> +       goto err; >>>>> + >>>>> +   ret = drm_vblank_init(ddev, descp->num_of_crtc); >>>>> +   if (ret) >>>>> +       goto err; >>>>> + >>>>> +   ret = request_threaded_irq(pdev->irq, >>>>> +                  lsdc_get_irq_handler(ldev), >>>>> +                  lsdc_irq_thread_handler, >>>>> +                  IRQF_ONESHOT, >>>>> +                  dev_name(ddev->dev), >>>>> +                  ddev); >>>>> +   if (ret) { >>>>> +       drm_err(ddev, "Failed to register lsdc interrupt\n"); >>>>> +       goto err; >>>>> +   } >>>>> + >>>>> +   drm_kms_helper_poll_init(ddev); >>>>> + >>>>> +   return ldev; >>>>> + >>>>> +err: >>>>> +   return ERR_PTR(ret); >>>>> +} >>>>> + >>>>> +static int lsdc_pci_probe(struct pci_dev *pdev, >>>>> +             const struct pci_device_id *ent) >>>>> +{ >>>>> +   struct drm_device *ddev; >>>>> +   struct lsdc_device *ldev; >>>>> +   int ret; >>>>> + >>>>> +   ret = pcim_enable_device(pdev); >>>>> +   if (ret) >>>>> +       return ret; >>>>> + >>>>> +   pci_set_master(pdev); >>>>> + >>>>> +   ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); >>>>> +   if (ret) >>>>> +       return ret; >>>>> + >>>>> +   ldev = lsdc_create_device(pdev, ent, &lsdc_drm_driver); >>>>> +   if (IS_ERR(ldev)) >>>>> +       return PTR_ERR(ldev); >>>>> + >>>>> +   ddev = &ldev->base; >>>>> + >>>>> +   ret = drm_dev_register(ddev, 0); >>>>> +   if (ret) >>>>> +       return ret; >>>>> + >>>>> +   drm_fbdev_generic_setup(ddev, 32); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static void lsdc_pci_remove(struct pci_dev *pdev) >>>>> +{ >>>>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>>>> + >>>>> +   drm_dev_unregister(ddev); >>>>> +   drm_atomic_helper_shutdown(ddev); >>>>> +} >>>>> + >>>>> +static int lsdc_drm_freeze(struct drm_device *ddev) >>>>> +{ >>>>> +   int error; >>>>> + >>>>> +   error = drm_mode_config_helper_suspend(ddev); >>>>> +   if (error) >>>>> +       return error; >>>>> + >>>>> +   pci_save_state(to_pci_dev(ddev->dev)); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static int lsdc_drm_resume(struct device *dev) >>>>> +{ >>>>> +   struct pci_dev *pdev = to_pci_dev(dev); >>>>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>>>> + >>>>> +   return drm_mode_config_helper_resume(ddev); >>>>> +} >>>>> + >>>>> +static int lsdc_pm_freeze(struct device *dev) >>>>> +{ >>>>> +   struct pci_dev *pdev = to_pci_dev(dev); >>>>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>>>> + >>>>> +   return lsdc_drm_freeze(ddev); >>>>> +} >>>>> + >>>>> +static int lsdc_pm_thaw(struct device *dev) >>>>> +{ >>>>> +   return lsdc_drm_resume(dev); >>>>> +} >>>>> + >>>>> +static int lsdc_pm_suspend(struct device *dev) >>>>> +{ >>>>> +   struct pci_dev *pdev = to_pci_dev(dev); >>>>> +   int error; >>>>> + >>>>> +   error = lsdc_pm_freeze(dev); >>>>> +   if (error) >>>>> +       return error; >>>>> + >>>>> +   pci_save_state(pdev); >>>>> +   /* Shut down the device */ >>>>> +   pci_disable_device(pdev); >>>>> +   pci_set_power_state(pdev, PCI_D3hot); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static int lsdc_pm_resume(struct device *dev) >>>>> +{ >>>>> +   struct pci_dev *pdev = to_pci_dev(dev); >>>>> + >>>>> +   if (pcim_enable_device(pdev)) >>>>> +       return -EIO; >>>>> + >>>>> +   pci_set_power_state(pdev, PCI_D0); >>>>> + >>>>> +   pci_restore_state(pdev); >>>>> + >>>>> +   return lsdc_pm_thaw(dev); >>>>> +} >>>>> + >>>>> +static const struct dev_pm_ops lsdc_pm_ops = { >>>>> +   .suspend = lsdc_pm_suspend, >>>>> +   .resume = lsdc_pm_resume, >>>>> +   .freeze = lsdc_pm_freeze, >>>>> +   .thaw = lsdc_pm_thaw, >>>>> +   .poweroff = lsdc_pm_freeze, >>>>> +   .restore = lsdc_pm_resume, >>>>> +}; >>>>> + >>>>> +static const struct pci_device_id lsdc_pciid_list[] = { >>>>> +   {PCI_VENDOR_ID_LOONGSON, 0x7a06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, >>>>> +    (kernel_ulong_t)&dc_in_ls7a1000}, >>>>> +   {PCI_VENDOR_ID_LOONGSON, 0x7a36, PCI_ANY_ID, PCI_ANY_ID, 0, 0, >>>>> +    (kernel_ulong_t)&dc_in_ls7a2000}, >>>>> +   {0, 0, 0, 0, 0, 0, 0} >>>>> +}; >>>>> + >>>>> +static struct pci_driver lsdc_pci_driver = { >>>>> +   .name = DRIVER_NAME, >>>>> +   .id_table = lsdc_pciid_list, >>>>> +   .probe = lsdc_pci_probe, >>>>> +   .remove = lsdc_pci_remove, >>>>> +   .driver.pm = &lsdc_pm_ops, >>>>> +}; >>>>> + >>>>> +static int __init lsdc_module_init(void) >>>>> +{ >>>>> +   struct pci_dev *pdev = NULL; >>>>> + >>>>> +   if (drm_firmware_drivers_only()) >>>>> +       return -EINVAL; >>>> >>>>  return -ENODEV; >>> >>> OK, no problem, I will fix it at new version. I remember i already >>> fix this, sorry. >>> >>>> >>>>> + >>>>> +   while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, >>>>> pdev))) { >>>>> +       /* >>>>> +        * Multiple video card workaround: lsdc will always be >>>>> +        * selected as the default boot device by vgaarb subsystem. >>>>> +        */ >>>>> +       if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) { >>>>> +           pr_info("Discrete graphic card detected, abort\n"); >>>>> +           return 0; >>>>> +       } >>>>> +   } >>>> >>>> I don't understand this code. You are not registering your driver >>>> if there's another graphics card? >>>> >>> Yes, we do not want to registering this driver if there's discrete >>> graphics card or BMC in the system. >>> >>> >>> for example: >>> >>> 1) Loongson desktop computer(ls3a5000 + ls7a2000 with amdgpu rx580 >>> mounted) >>> >>> On such a configuration, loongson display controller and rx580 >>> co-exist in the system. >>> >>> As discrete GPU is more powerful, it seems that there no need to >>> using LSDC driver anymore. >>> >>> The user is most likely want to using the rx580 as primary GPU and >>> Using rx580 alone is enough for user in such a case. >>> >>> It is introduce unnecessary problems and confusion If we leave lsdc >>> driver live on the system together with amdgpu/ati drm driver. >>> >>> >>> I have just tested the case which leave lsdc driver live on the >>> system together with radeon drm driver. >>> >>> the monitor connected with lsdc is black, not working as a output >>> slave. >>> >>> >>> 2) Loongson ls3c5000 server with aspeed 2400/25000 BMC card mounted. >>> >>> The BMC provide remote access and control besides the graphics, use >>> it alone is enough, we have solve >>> >>> 4 ~ 5 bugs on our product side. we don't want make a mess. >>> >>> our tester get confused, than find a amdgpu and plug in, then he get >>> three video co-exist in the sistem. >>> >>> lsdc, ast and amdgpu, there /dev/dri/card0, /dev/dri/car1 and >>> /dev/dri/card2. >>> >>> if i bring etnaviv up, then i can get four. >>> >>> >>>> Best regards >>>> Thomas >>>> >>> Thinks your valuable advice and guiding, we will take time to fix >>> all the problems your mentioned, >>> >>> and prepare next version. >> >> The driver still have a few problem, i will fix it if i can found, >> but it already can suit the need for >> >> daily usage for programmer. hope i can help to merge this driver, i >> holding it on my hand >> >> more than one years . If i miss anything, please remind me, any >> discussion is welcome. >> >>> >>>>> + >>>>> +   return pci_register_driver(&lsdc_pci_driver); >>>>> +} >>>>> +module_init(lsdc_module_init); >>>>> + >>>>> +static void __exit lsdc_module_exit(void) >>>>> +{ >>>>> +   pci_unregister_driver(&lsdc_pci_driver); >>>>> +} >>>>> +module_exit(lsdc_module_exit); >>>>> + >>>>> +MODULE_DEVICE_TABLE(pci, lsdc_pciid_list); >>>>> +MODULE_AUTHOR(DRIVER_AUTHOR); >>>>> +MODULE_DESCRIPTION(DRIVER_DESC); >>>>> +MODULE_LICENSE("GPL"); >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.h >>>>> b/drivers/gpu/drm/lsdc/lsdc_drv.h >>>>> new file mode 100644 >>>>> index 000000000000..d67dae0414ca >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.h >>>>> @@ -0,0 +1,274 @@ >>>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>>> +/* >>>>> + * KMS driver for Loongson display controller >>>>> + * Copyright (C) 2022 Loongson Corporation >>>>> + * >>>>> + * Authors: >>>>> + *     Li Yi <liyi@loongson.cn> >>>>> + *     Li Chenyang <lichenyang@loongson.cn> >>>>> + *     Sui Jingfeng <suijingfeng@loongson.cn> >>>>> + */ >>>>> + >>>>> +#ifndef __LSDC_DRV_H__ >>>>> +#define __LSDC_DRV_H__ >>>>> + >>>>> +#include <linux/i2c.h> >>>>> +#include <linux/i2c-algo-bit.h> >>>>> + >>>>> +#include <drm/drm_print.h> >>>>> +#include <drm/drm_device.h> >>>>> +#include <drm/drm_crtc.h> >>>>> +#include <drm/drm_plane.h> >>>>> +#include <drm/drm_connector.h> >>>>> +#include <drm/drm_encoder.h> >>>>> +#include <drm/drm_drv.h> >>>>> +#include <drm/drm_atomic.h> >>>>> + >>>>> +#include "lsdc_regs.h" >>>>> +#include "lsdc_pll.h" >>>>> + >>>>> +#define LSDC_NUM_CRTC          2 >>>>> + >>>>> +/* >>>>> + * The display controller in LS7A2000 integrate three loongson >>>>> self-made >>>>> + * encoder. Display pipe 0 has a transparent VGA encoder and a >>>>> HDMI phy, >>>>> + * they are parallel. Display pipe 1 has only one HDMI phy. >>>>> + *      ______________________ _____________ >>>>> + *     |            +-----+ | |            | >>>>> + *     | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA >>>>> Monitor |<---+ >>>>> + *     |       |   +-----+ | |_____________|   | >>>>> + *     |       |            | ______________   | >>>>> + *     |       |   +------+ | |             |  | >>>>> + *     |       +--> | HDMI | ----> HDMI Connector --> | HDMI >>>>> Monitor |<--+ >>>>> + *     |            +------+ | |______________|  | >>>>> + *     |           +------+ >>>>> |                                          | >>>>> + *     |           | i2c6 | >>>>> <-------------------------------------------+ >>>>> + *     |           +------+ | >>>>> + *     |                     | >>>>> + *     |   DC in LS7A2000   | >>>>> + *     |                     | >>>>> + *     |           +------+ | >>>>> + *     |           | i2c7 | <--------------------------------+ >>>>> + *     |           +------+ | | >>>>> + *     |                     | ______|_______ >>>>> + *     |           +------+ | |             | >>>>> + *     | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI >>>>> Monitor | >>>>> + *     |           +------+ | |______________| >>>>> + *     |______________________| >>>>> + * >>>>> + * >>>>> + * The display controller in LS7A1000 integrate two-way DVO, >>>>> external >>>>> + * encoder is required except use directly with dpi(rgb888) panel. >>>>> + * ___________________ _________ >>>>> + *     |           -------| |        | >>>>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>>>> Display | >>>>> + *     | _  _    -------|       ^            ^ |_________| >>>>> + *     | | | | |   -------|       |            | >>>>> + *     | |_| |_|   | i2c6 <--------+-------------+ >>>>> + *     |           -------| >>>>> + *     | _  _    -------| >>>>> + *     | | | | |   | i2c7 <--------+-------------+ >>>>> + *     | |_| |_|   -------|       | | _________ >>>>> + *     |           -------|       |            | |        | >>>>> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> >>>>> | Panel | >>>>> + *     |           -------| |_________| >>>>> + *     |___________________| >>>>> + * >>>>> + * >>>>> + * The display controller in LS2K1000. >>>>> + * ___________________ _________ >>>>> + *     |           -------| |        | >>>>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>>>> Display | >>>>> + *     | _  _    -------|       ^             ^ |_________| >>>>> + *     | | | | |          |       |             | >>>>> + *     | |_| |_|          |    +------+         | >>>>> + *     |                  <---->| i2c0 |<---------+ >>>>> + *     | DC in LS2K1000  |    +------+ >>>>> + *     | _  _           |    +------+ >>>>> + *     | | | | |          <---->| i2c1 |----------+ >>>>> + *     | |_| |_|          |    +------+ | _________ >>>>> + *     |           -------|       |             | |        | >>>>> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> >>>>> | Panel | >>>>> + *     |           -------| |_________| >>>>> + *     |___________________| >>>>> + * >>>>> + * >>>>> + * The display controller in LS2K0500, LS2K0500 has a built-in >>>>> transparent >>>>> + * VGA encoder which is connected to display pipe 1(CRTC1). >>>>> + * ___________________ _________ >>>>> + *     |           -------| |        | >>>>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>>>> Display | >>>>> + *     | _  _    -------|       ^             ^ |_________| >>>>> + *     | | | | |          |       |             | >>>>> + *     | |_| |_|          |    +------+         | >>>>> + *     |                  <---->| i2c4 |<---------+ >>>>> + *     | DC in LS2K0500  |    +------+ >>>>> + *     | _  _           |    +------+ >>>>> + *     | | | | |          <---->| i2c5 |-------------------+ >>>>> + *     | |_| |_|          |    +------+ ______|______ >>>>> + *     |          +-----+ | |            | >>>>> + *     | CRTC1 --> | VGA | ------------------------> | VGA >>>>> Monitor | >>>>> + *     |          +-----+ | |_____________| >>>>> + *     |___________________| >>>>> + * >>>>> + * LS7A1000 and LS7A2000 are bridge chips, has dedicated Video RAM. >>>>> + * while LS2K2000/LS2K1000/LS2K0500 are SoC, they don't have >>>>> dediacated >>>>> + * Video RAM. The dc in LS2K2000 is basicly same with the dc in >>>>> LS7A1000 >>>>> + * except that LS2K2000 don't have a video RAM and have only one >>>>> built-in >>>>> + * hdmi encoder. >>>>> + * >>>>> + * There is only a 1:1 mapping of encoders and connectors for the >>>>> DC, >>>>> + * each CRTC have two FB address registers. >>>>> + */ >>>>> + >>>>> +enum loongson_chip_family { >>>>> +   CHIP_UNKNOWN = 0, >>>>> +   CHIP_LS7A1000 = 1, /* North bridge of >>>>> LS3A3000/LS3A4000/LS3A5000 */ >>>>> +   CHIP_LS7A2000 = 2, /* Update version of LS7A1000, with >>>>> built-in HDMI encoder */ >>>>> +   CHIP_LS2K0500 = 3, /* Reduced version of LS2K1000, single >>>>> core */ >>>>> +   CHIP_LS2K1000 = 4, /* 2-Core Mips64r2/LA264 SoC, 64-bit, 1.0 >>>>> Ghz */ >>>>> +   CHIP_LS2K2000 = 5, /* 2-Core 64-bit LA364 SoC, 1.2 ~ 1.5 Ghz */ >>>>> +   CHIP_LAST, >>>>> +}; >>>>> + >>>>> +struct lsdc_desc { >>>>> +   enum loongson_chip_family chip; >>>>> +   u32 num_of_crtc; >>>>> +   u32 max_pixel_clk; >>>>> +   u32 max_width; >>>>> +   u32 max_height; >>>>> +   u32 num_of_hw_cursor; >>>>> +   u32 hw_cursor_w; >>>>> +   u32 hw_cursor_h; >>>>> +   u32 pitch_align; /* DMA alignment constraint */ >>>>> +   u64 mc_bits;     /* physical address bus bit width */ >>>>> +   bool has_vblank_counter; >>>>> +   bool has_builtin_i2c; >>>>> +   bool has_vram; >>>>> +   bool has_hpd_reg; >>>>> +   bool is_soc; >>>>> +}; >>>>> + >>>>> +struct lsdc_i2c { >>>>> +   struct i2c_adapter adapter; >>>>> +   struct i2c_algo_bit_data bit; >>>>> +   struct drm_device *ddev; >>>>> +   void __iomem *reg_base; >>>>> +   void __iomem *dir_reg; >>>>> +   void __iomem *dat_reg; >>>>> +   /* pin bit mask */ >>>>> +   u8 sda; >>>>> +   u8 scl; >>>>> +}; >>>>> + >>>>> +/* >>>>> + * struct lsdc_display_pipe - Abstraction of hardware display >>>>> pipeline. >>>>> + * @crtc: CRTC control structure >>>>> + * @primary: framebuffer plane control structure >>>>> + * @cursor: cursor plane control structure >>>>> + * @output: output control structure of this display pipe bind >>>>> + * @pixpll: pixel pll control structure >>>>> + * @index: the index corresponding to the hardware display pipe >>>>> + */ >>>>> +struct lsdc_display_pipe { >>>>> +   struct drm_crtc crtc; >>>>> +   struct drm_plane primary; >>>>> +   struct drm_plane cursor; >>>>> +   struct drm_encoder encoder; >>>>> +   struct drm_connector connector; >>>>> +   struct lsdc_pll pixpll; >>>>> +   struct lsdc_i2c *li2c; >>>>> +   int index; >>>>> +}; >>>>> + >>>>> +static inline struct lsdc_display_pipe * >>>>> +crtc_to_display_pipe(struct drm_crtc *crtc) >>>>> +{ >>>>> +   return container_of(crtc, struct lsdc_display_pipe, crtc); >>>>> +} >>>>> + >>>>> +static inline struct lsdc_display_pipe * >>>>> +cursor_to_display_pipe(struct drm_plane *cursor) >>>>> +{ >>>>> +   return container_of(cursor, struct lsdc_display_pipe, cursor); >>>>> +} >>>>> + >>>>> +static inline struct lsdc_display_pipe * >>>>> +connector_to_display_pipe(struct drm_connector *conn) >>>>> +{ >>>>> +   return container_of(conn, struct lsdc_display_pipe, connector); >>>>> +} >>>>> + >>>>> +static inline struct lsdc_display_pipe * >>>>> +encoder_to_display_pipe(struct drm_encoder *enc) >>>>> +{ >>>>> +   return container_of(enc, struct lsdc_display_pipe, encoder); >>>>> +} >>>>> + >>>>> +struct lsdc_crtc_state { >>>>> +   struct drm_crtc_state base; >>>>> +   struct lsdc_pll_parms pparms; >>>>> +}; >>>>> + >>>>> +struct lsdc_device { >>>>> +   struct drm_device base; >>>>> + >>>>> +   /* @reglock: protects concurrent register access */ >>>>> +   spinlock_t reglock; >>>>> +   void __iomem *reg_base; >>>>> +   void __iomem *vram; >>>>> +   resource_size_t vram_base; >>>>> +   resource_size_t vram_size; >>>>> +   u64 mc_mask; >>>>> + >>>>> +   struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC]; >>>>> + >>>>> +   /* @num_output: count the number of active display pipe */ >>>>> +   unsigned int num_output; >>>>> + >>>>> +   /* @descp: features description of the DC variant */ >>>>> +   const struct lsdc_desc *descp; >>>>> + >>>>> +   u32 irq_status; >>>>> +}; >>>>> + >>>>> +static inline struct lsdc_device * >>>>> +to_lsdc(struct drm_device *ddev) >>>>> +{ >>>>> +   return container_of(ddev, struct lsdc_device, base); >>>>> +} >>>>> + >>>>> +static inline struct lsdc_crtc_state * >>>>> +to_lsdc_crtc_state(struct drm_crtc_state *base) >>>>> +{ >>>>> +   return container_of(base, struct lsdc_crtc_state, base); >>>>> +} >>>>> + >>>>> +void lsdc_debugfs_init(struct drm_minor *minor); >>>>> + >>>>> +int lsdc_crtc_init(struct drm_device *ddev, >>>>> +          struct drm_crtc *crtc, >>>>> +          unsigned int index, >>>>> +          struct drm_plane *primary, >>>>> +          struct drm_plane *cursor); >>>>> + >>>>> +int lsdc_plane_init(struct lsdc_device *ldev, >>>>> +           struct drm_plane *plane, >>>>> +           enum drm_plane_type type, >>>>> +           unsigned int index); >>>>> + >>>>> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int >>>>> index); >>>>> + >>>>> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >>>>> +                     void *base, >>>>> +                     unsigned int index); >>>>> + >>>>> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device >>>>> *ldev, int index); >>>>> + >>>>> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg); >>>>> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev); >>>>> + >>>>> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset); >>>>> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 >>>>> val); >>>>> + >>>>> +#endif >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_i2c.c >>>>> new file mode 100644 >>>>> index 000000000000..b380098409ca >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c >>>>> @@ -0,0 +1,201 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include <linux/i2c.h> >>>>> +#include <drm/drm_managed.h> >>>>> +#include "lsdc_drv.h" >>>>> +#include "lsdc_regs.h" >>>>> + >>>>> +/* >>>>> + * ls7a_gpio_i2c_set - set the state of a gpio pin indicated by mask >>>>> + * @mask: gpio pin mask >>>>> + * @state: "0" for low, "1" for high >>>>> + */ >>>>> +static void ls7a_gpio_i2c_set(struct lsdc_i2c * const li2c, int >>>>> mask, int state) >>>>> +{ >>>>> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >>>>> +   unsigned long flags; >>>>> +   u8 val; >>>>> + >>>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>>> + >>>>> +   if (state) { >>>>> +       /* >>>>> +        * Setting this pin as input directly, write 1 for Input. >>>>> +        * The external pull-up resistor will pull the level up >>>>> +        */ >>>>> +       val = readb(li2c->dir_reg); >>>>> +       val |= mask; >>>>> +       writeb(val, li2c->dir_reg); >>>>> +   } else { >>>>> +       /* First set this pin as output, write 0 for Output */ >>>>> +       val = readb(li2c->dir_reg); >>>>> +       val &= ~mask; >>>>> +       writeb(val, li2c->dir_reg); >>>>> + >>>>> +       /* Then, make this pin output 0 */ >>>>> +       val = readb(li2c->dat_reg); >>>>> +       val &= ~mask; >>>>> +       writeb(val, li2c->dat_reg); >>>>> +   } >>>>> + >>>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>>> +} >>>>> + >>>>> +/* >>>>> + * ls7a_gpio_i2c_get - read value back from the gpio pin >>>>> indicated by mask >>>>> + * @mask: gpio pin mask >>>>> + * return "0" for low, "1" for high >>>>> + */ >>>>> +static int ls7a_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask) >>>>> +{ >>>>> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >>>>> +   unsigned long flags; >>>>> +   u8 val; >>>>> + >>>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>>> + >>>>> +   /* First set this pin as input */ >>>>> +   val = readb(li2c->dir_reg); >>>>> +   val |= mask; >>>>> +   writeb(val, li2c->dir_reg); >>>>> + >>>>> +   /* Then get level state from this pin */ >>>>> +   val = readb(li2c->dat_reg); >>>>> + >>>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>>> + >>>>> +   return (val & mask) ? 1 : 0; >>>>> +} >>>>> + >>>>> +static void ls7a_i2c_set_sda(void *i2c, int state) >>>>> +{ >>>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>>> +   /* set state on the li2c->sda pin */ >>>>> +   return ls7a_gpio_i2c_set(li2c, li2c->sda, state); >>>>> +} >>>>> + >>>>> +static void ls7a_i2c_set_scl(void *i2c, int state) >>>>> +{ >>>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>>> +   /* set state on the li2c->scl pin */ >>>>> +   return ls7a_gpio_i2c_set(li2c, li2c->scl, state); >>>>> +} >>>>> + >>>>> +static int ls7a_i2c_get_sda(void *i2c) >>>>> +{ >>>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>>> +   /* read value from the li2c->sda pin */ >>>>> +   return ls7a_gpio_i2c_get(li2c, li2c->sda); >>>>> +} >>>>> + >>>>> +static int ls7a_i2c_get_scl(void *i2c) >>>>> +{ >>>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>>> +   /* read the value from the li2c->scl pin */ >>>>> +   return ls7a_gpio_i2c_get(li2c, li2c->scl); >>>>> +} >>>>> + >>>>> +static void lsdc_destroy_i2c(struct drm_device *ddev, void *data) >>>>> +{ >>>>> +   struct lsdc_i2c *li2c = (struct lsdc_i2c *)data; >>>>> + >>>>> +   if (li2c) { >>>>> +       i2c_del_adapter(&li2c->adapter); >>>>> +       kfree(li2c); >>>>> +   } >>>>> +} >>>>> + >>>>> +/* >>>>> + * The DC in ls7a1000/ls7a2000 have builtin gpio hardware >>>>> + * >>>>> + * @base: gpio reg base >>>>> + * @index: output channel index, 0 for DVO0, 1 for DVO1 >>>>> + */ >>>>> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >>>>> +                     void *base, >>>>> +                     unsigned int index) >>>>> +{ >>>>> +   struct i2c_adapter *adapter; >>>>> +   struct lsdc_i2c *li2c; >>>>> +   int ret; >>>>> + >>>>> +   li2c = kzalloc(sizeof(*li2c), GFP_KERNEL); >>>>> +   if (!li2c) >>>>> +       return ERR_PTR(-ENOMEM); >>>>> + >>>>> +   if (index == 0) { >>>>> +       li2c->sda = 0x01; /* pin 0 */ >>>>> +       li2c->scl = 0x02; /* pin 1 */ >>>>> +   } else if (index == 1) { >>>>> +       li2c->sda = 0x04; /* pin 2 */ >>>>> +       li2c->scl = 0x08; /* pin 3 */ >>>>> +   } >>>>> + >>>>> +   li2c->reg_base = base; >>>>> +   li2c->ddev = ddev; >>>>> +   li2c->dir_reg = li2c->reg_base + LS7A_DC_GPIO_DIR_REG; >>>>> +   li2c->dat_reg = li2c->reg_base + LS7A_DC_GPIO_DAT_REG; >>>>> + >>>>> +   li2c->bit.setsda = ls7a_i2c_set_sda; >>>>> +   li2c->bit.setscl = ls7a_i2c_set_scl; >>>>> +   li2c->bit.getsda = ls7a_i2c_get_sda; >>>>> +   li2c->bit.getscl = ls7a_i2c_get_scl; >>>>> +   li2c->bit.udelay = 5; >>>>> +   li2c->bit.timeout = usecs_to_jiffies(2200); >>>>> +   li2c->bit.data = li2c; >>>>> + >>>>> +   adapter = &li2c->adapter; >>>>> +   adapter->algo_data = &li2c->bit; >>>>> +   adapter->owner = THIS_MODULE; >>>>> +   adapter->class = I2C_CLASS_DDC; >>>>> +   adapter->dev.parent = ddev->dev; >>>>> +   adapter->nr = -1; >>>>> + >>>>> +   snprintf(adapter->name, sizeof(adapter->name), "lsdc-i2c%u", >>>>> index); >>>>> + >>>>> +   i2c_set_adapdata(adapter, li2c); >>>>> + >>>>> +   ret = i2c_bit_add_bus(adapter); >>>>> +   if (ret) { >>>>> +       kfree(li2c); >>>>> +       return ERR_PTR(ret); >>>>> +   } >>>>> + >>>>> +   ret = drmm_add_action_or_reset(ddev, lsdc_destroy_i2c, li2c); >>>>> +   if (ret) >>>>> +       return NULL; >>>>> + >>>>> +   drm_info(ddev, "%s(sda=%u, scl=%u) created for connector-%u\n", >>>>> +        adapter->name, li2c->sda, li2c->scl, index); >>>>> + >>>>> +   return li2c; >>>>> +} >>>>> + >>>>> +static int lsdc_get_i2c_id(struct lsdc_device *ldev, int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   if (descp->chip == CHIP_LS2K1000) >>>>> +       return index; >>>>> + >>>>> +   if (descp->chip == CHIP_LS2K0500) >>>>> +       return index + 2; >>>>> + >>>>> +   return index; >>>>> +} >>>>> + >>>>> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >>>>> +                    int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >>>>> + >>>>> +   if (descp->has_builtin_i2c) { >>>>> +       struct lsdc_i2c *li2c = dispipe->li2c; >>>>> + >>>>> +       if (li2c) >>>>> +           return &li2c->adapter; >>>>> +   } >>>>> + >>>>> +   return i2c_get_adapter(lsdc_get_i2c_id(ldev, index)); >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_irq.c >>>>> new file mode 100644 >>>>> index 000000000000..71c85f09bb6f >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_irq.c >>>>> @@ -0,0 +1,86 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include <drm/drm_vblank.h> >>>>> +#include "lsdc_drv.h" >>>>> +#include "lsdc_regs.h" >>>>> + >>>>> +/* >>>>> + * For the DC in ls7a2000, clearing interrupt status is achieved by >>>>> + * write "1" to LSDC_INT_REG, For the DC in ls7a1000, ls2k1000 and >>>>> + * ls2k0500, clearing interrupt status is achieved by write "0" to >>>>> + * LSDC_INT_REG. Two different hardware engineer of Loongson modify >>>>> + * it as their will. >>>>> + */ >>>>> + >>>>> +/* For the DC in ls7a2000 */ >>>>> +static irqreturn_t lsdc_irq_handler(int irq, void *arg) >>>>> +{ >>>>> +   struct drm_device *ddev = arg; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   u32 val; >>>>> + >>>>> +   /* Read the interrupt status */ >>>>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>>>> +   if ((val & INT_STATUS_MASK) == 0) { >>>>> +       drm_warn(ddev, "no interrupt occurs\n"); >>>>> +       return IRQ_NONE; >>>>> +   } >>>>> + >>>>> +   ldev->irq_status = val; >>>>> + >>>>> +   /* write "1" to clear the interrupt status */ >>>>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >>>>> + >>>>> +   return IRQ_WAKE_THREAD; >>>>> +} >>>>> + >>>>> +/* For the DC in ls7a1000, ls2k1000 and ls2k0500 */ >>>>> +static irqreturn_t lsdc_irq_handler_legacy(int irq, void *arg) >>>>> +{ >>>>> +   struct drm_device *ddev = arg; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   u32 val; >>>>> + >>>>> +   /* Read the interrupt status */ >>>>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>>>> +   if ((val & INT_STATUS_MASK) == 0) { >>>>> +       drm_warn(ddev, "no interrupt occurs\n"); >>>>> +       return IRQ_NONE; >>>>> +   } >>>>> + >>>>> +   ldev->irq_status = val; >>>>> + >>>>> +   /* write "0" to clear the interrupt status */ >>>>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val & ~INT_STATUS_MASK); >>>>> + >>>>> +   return IRQ_WAKE_THREAD; >>>>> +} >>>>> + >>>>> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A2000) >>>>> +       return lsdc_irq_handler; >>>>> + >>>>> +   return lsdc_irq_handler_legacy; >>>>> +} >>>>> + >>>>> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg) >>>>> +{ >>>>> +   struct drm_device *ddev = arg; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   struct drm_crtc *crtc; >>>>> + >>>>> +   if (ldev->irq_status & INT_CRTC0_VSYNC) { >>>>> +       crtc = drm_crtc_from_index(ddev, 0); >>>>> +       drm_crtc_handle_vblank(crtc); >>>>> +   } >>>>> + >>>>> +   if (ldev->irq_status & INT_CRTC1_VSYNC) { >>>>> +       crtc = drm_crtc_from_index(ddev, 1); >>>>> +       drm_crtc_handle_vblank(crtc); >>>>> +   } >>>>> + >>>>> +   return IRQ_HANDLED; >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_output.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_output.c >>>>> new file mode 100644 >>>>> index 000000000000..d4bc7666d9ea >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_output.c >>>>> @@ -0,0 +1,452 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include <drm/drm_edid.h> >>>>> +#include <drm/drm_probe_helper.h> >>>>> +#include <drm/drm_atomic_helper.h> >>>>> +#include <drm/drm_connector.h> >>>>> +#include "lsdc_drv.h" >>>>> + >>>>> +static int lsdc_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 enum drm_connector_status >>>>> +lsdc_unknown_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; >>>>> +   } else { >>>>> +       if (connector->connector_type == DRM_MODE_CONNECTOR_DPI) >>>>> +           return connector_status_connected; >>>>> + >>>>> +       if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) >>>>> +           return connector_status_connected; >>>>> +   } >>>>> + >>>>> +   return connector_status_unknown; >>>>> +} >>>>> + >>>>> +static enum drm_connector_status >>>>> +lsdc_hdmi_connector_detect(struct drm_connector *connector, bool >>>>> force) >>>>> +{ >>>>> +   struct lsdc_display_pipe *pipe = >>>>> connector_to_display_pipe(connector); >>>>> +   struct lsdc_device *ldev = to_lsdc(connector->dev); >>>>> +   u32 val; >>>>> + >>>>> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >>>>> + >>>>> +   if (pipe->index == 0) { >>>>> +       if (val & HDMI0_HPD_FLAG) >>>>> +           return connector_status_connected; >>>>> +   } >>>>> + >>>>> +   if (pipe->index == 1) { >>>>> +       if (val & HDMI1_HPD_FLAG) >>>>> +           return connector_status_connected; >>>>> +   } >>>>> + >>>>> +   return connector_status_disconnected; >>>>> +} >>>>> + >>>>> +static enum drm_connector_status >>>>> +lsdc_hdmi_vga_connector_detect(struct drm_connector *connector, >>>>> bool force) >>>>> +{ >>>>> +   struct lsdc_display_pipe *pipe = >>>>> connector_to_display_pipe(connector); >>>>> +   struct drm_device *ddev = connector->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   struct i2c_adapter *ddc; >>>>> +   u32 val; >>>>> + >>>>> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >>>>> + >>>>> +   if (pipe->index == 1) { >>>>> +       if (val & HDMI1_HPD_FLAG) >>>>> +           return connector_status_connected; >>>>> + >>>>> +       return connector_status_disconnected; >>>>> +   } >>>>> + >>>>> +   if (pipe->index == 0) { >>>>> +       if (val & HDMI0_HPD_FLAG) >>>>> +           return connector_status_connected; >>>>> + >>>>> +       ddc = connector->ddc; >>>>> +       if (ddc) { >>>>> +           if (drm_probe_ddc(ddc)) >>>>> +               return connector_status_connected; >>>>> + >>>>> +           return connector_status_disconnected; >>>>> +       } >>>>> +   } >>>>> + >>>>> +   return connector_status_unknown; >>>>> +} >>>>> + >>>>> +static struct drm_encoder * >>>>> +lsdc_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 >>>>> lsdc_connector_helpers = { >>>>> +   .atomic_best_encoder = lsdc_connector_get_best_encoder, >>>>> +   .get_modes = lsdc_get_modes, >>>>> +}; >>>>> + >>>>> +static const struct drm_connector_funcs >>>>> lsdc_unknown_connector_funcs = { >>>>> +   .detect = lsdc_unknown_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 const struct drm_connector_funcs >>>>> lsdc_hdmi_vga_connector_funcs = { >>>>> +   .detect = lsdc_hdmi_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, >>>>> +}; >>>>> + >>>>> +static const struct drm_connector_funcs lsdc_hdmi_connector_funcs >>>>> = { >>>>> +   .detect = lsdc_hdmi_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 const struct drm_connector_funcs * >>>>> +lsdc_get_connector_func(struct lsdc_device *ldev, unsigned int >>>>> index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A2000) { >>>>> +       if (index == 0) >>>>> +           return &lsdc_hdmi_vga_connector_funcs; >>>>> + >>>>> +       if (index == 1) >>>>> +           return &lsdc_hdmi_connector_funcs; >>>>> +   } >>>>> + >>>>> +   return &lsdc_unknown_connector_funcs; >>>>> +} >>>>> + >>>>> +/* >>>>> + * we provide a default support before DT/VBIOS is supported >>>>> + */ >>>>> +static int lsdc_get_encoder_type(struct lsdc_device *ldev, >>>>> +                unsigned int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A2000) { >>>>> +       if (index == 0) >>>>> +           return DRM_MODE_ENCODER_DAC; >>>>> +       if (index == 1) >>>>> +           return DRM_MODE_ENCODER_TMDS; >>>>> +   } >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>>>> CHIP_LS2K1000) { >>>>> +       if (index == 0) >>>>> +           return DRM_MODE_ENCODER_DPI; >>>>> +       if (index == 1) >>>>> +           return DRM_MODE_ENCODER_DPI; >>>>> +   } >>>>> + >>>>> +   if (descp->chip == CHIP_LS2K0500) { >>>>> +       if (index == 0) >>>>> +           return DRM_MODE_ENCODER_DPI; >>>>> +       if (index == 1) >>>>> +           return DRM_MODE_ENCODER_DAC; >>>>> +   } >>>>> + >>>>> +   return DRM_MODE_ENCODER_NONE; >>>>> +} >>>>> + >>>>> +/* >>>>> + * provide a default before DT/VBIOS support is upstreamed >>>>> + */ >>>>> +static int lsdc_get_connector_type(struct lsdc_device *ldev, >>>>> +                  unsigned int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A2000) { >>>>> +       if (index == 0) >>>>> +           return DRM_MODE_CONNECTOR_VGA; >>>>> +       if (index == 1) >>>>> +           return DRM_MODE_CONNECTOR_HDMIA; >>>>> +   } >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>>>> CHIP_LS2K1000) { >>>>> +       if (index == 0) >>>>> +           return DRM_MODE_CONNECTOR_DPI; >>>>> +       if (index == 1) >>>>> +           return DRM_MODE_CONNECTOR_DPI; >>>>> +   } >>>>> + >>>>> +   if (descp->chip == CHIP_LS2K0500) { >>>>> +       if (index == 0) >>>>> +           return DRM_MODE_CONNECTOR_DPI; >>>>> +       if (index == 1) >>>>> +           return DRM_MODE_CONNECTOR_VGA; >>>>> +   } >>>>> + >>>>> +   return DRM_MODE_CONNECTOR_Unknown; >>>>> +} >>>>> + >>>>> +static void lsdc_hdmi_disable(struct drm_encoder *encoder) >>>>> +{ >>>>> +   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; >>>>> + >>>>> +   if (index == 0) >>>>> +       val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG); >>>>> +   else if (index == 1) >>>>> +       val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG); >>>>> + >>>>> +   val &= ~HDMI_PHY_EN; >>>>> + >>>>> +   if (index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >>>>> +   else if (index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >>>>> + >>>>> +   drm_dbg(ddev, "HDMI-%u disabled\n", index); >>>>> +} >>>>> + >>>>> +static void lsdc_hdmi_enable(struct drm_encoder *encoder) >>>>> +{ >>>>> +   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; >>>>> + >>>>> +   /* we are using software gpio emulated i2c */ >>>>> +   val = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >>>>> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >>>>> + >>>>> +   if (index == 0) { >>>>> +       /* Enable HDMI-0 */ >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >>>>> + >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_ZONE_REG, 0x00400040); >>>>> +   } else if (index == 1) { >>>>> +       /* Enable HDMI-1 */ >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >>>>> + >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_ZONE_REG, 0x00400040); >>>>> +   } >>>>> + >>>>> +   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; >>>>> + >>>>> +   if (index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >>>>> +   else if (index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >>>>> + >>>>> +   drm_dbg(ddev, "HDMI-%u enabled\n", index); >>>>> +} >>>>> + >>>>> +/* >>>>> + * Fout = M * Fin >>>>> + * >>>>> + * M = (4 * LF) / (IDF * ODF) >>>>> + * >>>>> + * Loongson HDMI require M = 10 >>>>> + */ >>>>> +static void lsdc_hdmi_phy_pll_config(struct lsdc_device *ldev, >>>>> +                    int index, >>>>> +                    int clock) >>>>> +{ >>>>> +   struct drm_device *ddev = &ldev->base; >>>>> +   int count = 0; >>>>> +   u32 val; >>>>> + >>>>> +   /* disable phy pll */ >>>>> +   if (index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, 0x0); >>>>> +   else if (index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, 0x0); >>>>> + >>>>> +   /* >>>>> +    * 10 = (4 * 40) / (8 * 2) >>>>> +    */ >>>>> +   val = (8 << HDMI_PLL_IDF_SHIFT) | >>>>> +         (40 << HDMI_PLL_LF_SHIFT) | >>>>> +         (1 << HDMI_PLL_ODF_SHIFT) | >>>>> +         HDMI_PLL_ENABLE; >>>>> + >>>>> +   drm_dbg(ddev, "HDMI-%u clock: %d\n", index, clock); >>>>> + >>>>> +   if (index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, val); >>>>> +   else if (index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, val); >>>>> + >>>>> +   /* wait hdmi phy pll lock */ >>>>> +   do { >>>>> +       if (index == 0) >>>>> +           val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG); >>>>> +       else if (index == 1) >>>>> +           val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_PLL_REG); >>>>> + >>>>> +       ++count; >>>>> + >>>>> +       if (val & HDMI_PLL_LOCKED) { >>>>> +           drm_dbg(ddev, "Setting HDMI-%u PLL success(take %d >>>>> cycles)\n", >>>>> +               index, count); >>>>> +           break; >>>>> +       } >>>>> +   } while (count < 1000); >>>>> +} >>>>> + >>>>> +static void lsdc_hdmi_atomic_mode_set(struct drm_encoder *encoder, >>>>> +                     struct drm_crtc_state *crtc_state, >>>>> +                     struct drm_connector_state *conn_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; >>>>> +   struct drm_display_mode *mode = &crtc_state->mode; >>>>> + >>>>> +   lsdc_hdmi_phy_pll_config(ldev, index, mode->clock); >>>>> + >>>>> +   drm_dbg(ddev, "HDMI-%u modeset\n", index); >>>>> +} >>>>> + >>>>> +static const struct drm_encoder_helper_funcs >>>>> lsdc_hdmi_helper_funcs = { >>>>> +   .disable = lsdc_hdmi_disable, >>>>> +   .enable = lsdc_hdmi_enable, >>>>> +   .atomic_mode_set = lsdc_hdmi_atomic_mode_set, >>>>> +}; >>>>> + >>>>> +static void lsdc_hdmi_reset(struct drm_encoder *encoder) >>>>> +{ >>>>> +   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 = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >>>>> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >>>>> + >>>>> +   lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >>>>> +   lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >>>>> + >>>>> +   drm_dbg(ddev, "HDMI-%u Reset\n", index); >>>>> +} >>>>> + >>>>> +static const struct drm_encoder_funcs lsdc_encoder_funcs = { >>>>> +   .reset = lsdc_hdmi_reset, >>>>> +   .destroy = drm_encoder_cleanup, >>>>> +}; >>>>> + >>>>> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> +   struct drm_device *ddev = &ldev->base; >>>>> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >>>>> +   struct drm_encoder *encoder = &dispipe->encoder; >>>>> +   struct drm_connector *connector = &dispipe->connector; >>>>> +   struct i2c_adapter *ddc = NULL; >>>>> +   struct lsdc_i2c *li2c; >>>>> +   int ret; >>>>> + >>>>> +   ret = drm_encoder_init(ddev, >>>>> +                  encoder, >>>>> +                  &lsdc_encoder_funcs, >>>>> +                  lsdc_get_encoder_type(ldev, index), >>>>> +                  "encoder-%u", >>>>> +                  index); >>>>> +   if (ret) { >>>>> +       drm_err(ddev, "Failed to init encoder: %d\n", ret); >>>>> +       return ret; >>>>> +   } >>>>> + >>>>> +   encoder->possible_crtcs = BIT(index); >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A2000) >>>>> +       drm_encoder_helper_add(encoder, &lsdc_hdmi_helper_funcs); >>>>> + >>>>> +   if (descp->has_builtin_i2c) { >>>>> +       li2c = lsdc_create_i2c_chan(ddev, ldev->reg_base, index); >>>>> +       if (IS_ERR(li2c)) >>>>> +           return PTR_ERR(li2c); >>>>> + >>>>> +       dispipe->li2c = li2c; >>>>> + >>>>> +       ddc = &li2c->adapter; >>>>> +   } else { >>>>> +       ddc = lsdc_get_i2c_adapter(ldev, index); >>>>> +       if (IS_ERR(ddc)) { >>>>> +           drm_err(ddev, "Error get ddc for output-%u\n", index); >>>>> +           return PTR_ERR(ddc); >>>>> +       } >>>>> +   } >>>>> + >>>>> +   ret = drm_connector_init_with_ddc(ddev, >>>>> +                     connector, >>>>> +                     lsdc_get_connector_func(ldev, index), >>>>> +                     lsdc_get_connector_type(ldev, index), >>>>> +                     ddc); >>>>> +   if (ret) { >>>>> +       drm_err(ddev, "Init connector-%d failed\n", index); >>>>> +       return ret; >>>>> +   } >>>>> + >>>>> +   drm_connector_helper_add(connector, &lsdc_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; >>>>> + >>>>> +   ldev->num_output++; >>>>> + >>>>> +   drm_info(ddev, "output-%u initialized\n", index); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_plane.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_plane.c >>>>> new file mode 100644 >>>>> index 000000000000..0f779c97d53b >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_plane.c >>>>> @@ -0,0 +1,443 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include <drm/drm_atomic.h> >>>>> +#include <drm/drm_atomic_helper.h> >>>>> +#include <drm/drm_framebuffer.h> >>>>> +#include <drm/drm_plane_helper.h> >>>>> +#include <drm/drm_gem_vram_helper.h> >>>>> +#include "lsdc_drv.h" >>>>> +#include "lsdc_regs.h" >>>>> +#include "lsdc_pll.h" >>>>> + >>>>> +static const u32 lsdc_primary_formats[] = { >>>>> +   DRM_FORMAT_XRGB8888, >>>>> +   DRM_FORMAT_ARGB8888, >>>>> +}; >>>>> + >>>>> +static const u32 lsdc_cursor_formats[] = { >>>>> +   DRM_FORMAT_ARGB8888, >>>>> +}; >>>>> + >>>>> +static const u64 lsdc_fb_format_modifiers[] = { >>>>> +   DRM_FORMAT_MOD_LINEAR, >>>>> +   DRM_FORMAT_MOD_INVALID >>>>> +}; >>>>> + >>>>> +static void lsdc_update_fb_format(struct lsdc_device *ldev, >>>>> +                 struct drm_crtc *crtc, >>>>> +                 const struct drm_format_info *fmt_info) >>>>> +{ >>>>> +   unsigned int index = drm_crtc_index(crtc); >>>>> +   u32 val; >>>>> +   u32 fmt; >>>>> + >>>>> +   switch (fmt_info->format) { >>>>> +   case DRM_FORMAT_XRGB8888: >>>>> +       fmt = LSDC_PF_XRGB8888; >>>>> +       break; >>>>> +   case DRM_FORMAT_ARGB8888: >>>>> +       fmt = LSDC_PF_XRGB8888; >>>>> +       break; >>>>> +   default: >>>>> +       fmt = LSDC_PF_XRGB8888; >>>>> +       break; >>>>> +   } >>>>> + >>>>> +   if (index == 0) { >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>>> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>>> +   } else if (index == 1) { >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>>> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>>> +   } >>>>> +} >>>>> + >>>>> +static void lsdc_update_fb_start_addr(struct lsdc_device *ldev, >>>>> +                     struct drm_crtc *crtc, >>>>> +                     u64 paddr) >>>>> +{ >>>>> +   struct drm_device *ddev = &ldev->base; >>>>> +   unsigned int index = drm_crtc_index(crtc); >>>>> +   u32 lo_addr_reg; >>>>> +   u32 hi_addr_reg; >>>>> +   u32 val; >>>>> + >>>>> +   /* >>>>> +    * Find which framebuffer address register should update. >>>>> +    * if FB_ADDR0_REG is in using, we write the address to >>>>> FB_ADDR0_REG, >>>>> +    * if FB_ADDR1_REG is in using, we write the address to >>>>> FB_ADDR1_REG >>>>> +    * for each CRTC, the switch using one fb register to another is >>>>> +    * trigger by triggered by set CFG_PAGE_FLIP bit of >>>>> LSDC_CRTCx_CFG_REG >>>>> +    */ >>>>> +   if (index == 0) { >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>>> +       if (val & CFG_FB_IN_USING) { >>>>> +           lo_addr_reg = LSDC_CRTC0_FB1_LO_ADDR_REG; >>>>> +           hi_addr_reg = LSDC_CRTC0_FB1_HI_ADDR_REG; >>>>> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-0\n"); >>>>> +       } else { >>>>> +           lo_addr_reg = LSDC_CRTC0_FB0_LO_ADDR_REG; >>>>> +           hi_addr_reg = LSDC_CRTC0_FB0_HI_ADDR_REG; >>>>> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-0\n"); >>>>> +       } >>>>> +   } else if (index == 1) { >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>>> +       if (val & CFG_FB_IN_USING) { >>>>> +           lo_addr_reg = LSDC_CRTC1_FB1_LO_ADDR_REG; >>>>> +           hi_addr_reg = LSDC_CRTC1_FB1_HI_ADDR_REG; >>>>> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-1\n"); >>>>> +       } else { >>>>> +           lo_addr_reg = LSDC_CRTC1_FB0_LO_ADDR_REG; >>>>> +           hi_addr_reg = LSDC_CRTC1_FB0_HI_ADDR_REG; >>>>> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-1\n"); >>>>> +       } >>>>> +   } >>>>> + >>>>> +   /* 40-bit width physical address bus */ >>>>> +   lsdc_wreg32(ldev, lo_addr_reg, paddr); >>>>> +   lsdc_wreg32(ldev, hi_addr_reg, (paddr >> 32) & 0xFF); >>>>> + >>>>> +   drm_dbg(ddev, "CRTC-%u scantout from 0x%llx\n", index, paddr); >>>>> +} >>>>> + >>>>> +static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb, >>>>> +                      struct drm_plane_state *state, >>>>> +                      unsigned int plane) >>>>> +{ >>>>> +   unsigned int offset = fb->offsets[plane]; >>>>> + >>>>> +   offset += fb->format->cpp[plane] * (state->src_x >> 16); >>>>> +   offset += fb->pitches[plane] * (state->src_y >> 16); >>>>> + >>>>> +   return offset; >>>>> +} >>>>> + >>>>> +static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb) >>>>> +{ >>>>> +   struct drm_gem_vram_object *gbo; >>>>> +   s64 gpu_addr; >>>>> + >>>>> +   gbo = drm_gem_vram_of_gem(fb->obj[0]); >>>>> +   gpu_addr = drm_gem_vram_offset(gbo); >>>>> + >>>>> +   return gpu_addr; >>>>> +} >>>>> + >>>>> +static int lsdc_primary_plane_atomic_check(struct drm_plane *plane, >>>>> +                      struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_plane_state *plane_state = >>>>> drm_atomic_get_new_plane_state(state, plane); >>>>> +   struct drm_crtc *crtc = plane_state->crtc; >>>>> +   struct drm_crtc_state *crtc_state = >>>>> drm_atomic_get_new_crtc_state(state, crtc); >>>>> + >>>>> +   if (!crtc) >>>>> +       return 0; >>>>> + >>>>> +   return drm_atomic_helper_check_plane_state(plane_state, >>>>> +                          crtc_state, >>>>> +                          DRM_PLANE_NO_SCALING, >>>>> +                          DRM_PLANE_NO_SCALING, >>>>> +                          false, >>>>> +                          true); >>>>> +} >>>>> + >>>>> +static void lsdc_update_fb_stride(struct lsdc_device *ldev, >>>>> +                 struct drm_crtc *crtc, >>>>> +                 unsigned int stride) >>>>> +{ >>>>> +   unsigned int index = drm_crtc_index(crtc); >>>>> + >>>>> +   if (index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_STRIDE_REG, stride); >>>>> +   else if (index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_STRIDE_REG, stride); >>>>> + >>>>> +   drm_dbg(crtc->dev, "update stride to %u\n", stride); >>>>> +} >>>>> + >>>>> +static void lsdc_primary_plane_atomic_update(struct drm_plane >>>>> *plane, >>>>> +                        struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct lsdc_device *ldev = to_lsdc(plane->dev); >>>>> +   struct drm_plane_state *new_plane_state = >>>>> drm_atomic_get_new_plane_state(state, plane); >>>>> +   struct drm_crtc *crtc = new_plane_state->crtc; >>>>> +   struct drm_framebuffer *fb = new_plane_state->fb; >>>>> +   u32 fb_offset = lsdc_get_fb_offset(fb, new_plane_state, 0); >>>>> +   dma_addr_t fb_addr; >>>>> +   s64 gpu_addr; >>>>> + >>>>> +   gpu_addr = lsdc_get_vram_bo_offset(fb); >>>>> +   if (gpu_addr < 0) >>>>> +       return; >>>>> + >>>>> +   fb_addr = ldev->vram_base + gpu_addr + fb_offset; >>>>> + >>>>> +   lsdc_update_fb_start_addr(ldev, crtc, fb_addr); >>>>> + >>>>> +   lsdc_update_fb_stride(ldev, crtc, fb->pitches[0]); >>>>> + >>>>> +   lsdc_update_fb_format(ldev, crtc, fb->format); >>>>> +} >>>>> + >>>>> +static void lsdc_primary_plane_atomic_disable(struct drm_plane >>>>> *plane, >>>>> +                         struct drm_atomic_state *state) >>>>> +{ >>>>> +   drm_dbg(plane->dev, "%s disabled\n", plane->name); >>>>> +} >>>>> + >>>>> +static const struct drm_plane_helper_funcs >>>>> lsdc_primary_plane_helpers = { >>>>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>>>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>>>> +   .atomic_check = lsdc_primary_plane_atomic_check, >>>>> +   .atomic_update = lsdc_primary_plane_atomic_update, >>>>> +   .atomic_disable = lsdc_primary_plane_atomic_disable, >>>>> +}; >>>>> + >>>>> +static int lsdc_cursor_atomic_check(struct drm_plane *plane, >>>>> +                   struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_plane_state *new_plane_state = >>>>> drm_atomic_get_new_plane_state(state, plane); >>>>> +   struct drm_framebuffer *fb = new_plane_state->fb; >>>>> +   struct drm_crtc *crtc = new_plane_state->crtc; >>>>> +   struct drm_crtc_state *crtc_state; >>>>> +   int ret; >>>>> + >>>>> +   /* no need for further checks if the plane is being disabled */ >>>>> +   if (!crtc || !fb) >>>>> +       return 0; >>>>> + >>>>> +   if (!new_plane_state->visible) >>>>> +       return 0; >>>>> + >>>>> +   crtc_state = drm_atomic_get_new_crtc_state(state, >>>>> +                          new_plane_state->crtc); >>>>> + >>>>> +   ret = drm_atomic_helper_check_plane_state(new_plane_state, >>>>> +                         crtc_state, >>>>> +                         DRM_PLANE_NO_SCALING, >>>>> +                         DRM_PLANE_NO_SCALING, >>>>> +                         true, >>>>> +                         true); >>>>> + >>>>> +   return ret; >>>>> +} >>>>> + >>>>> +/* >>>>> + * There is only one hardware cursor in ls7a1000, ls2k1000 and >>>>> ls2k0500. >>>>> + * we made it shared by the two CRTC, which can satisfy peoples >>>>> who use >>>>> + * double screen extend mode only. On clone screen usage case, >>>>> the cursor >>>>> + * on display pipe 1 will not be able to display. >>>>> + * >>>>> + * Update location of the cursor, attach it to CRTC0 or CRTC1 on >>>>> the runtime. >>>>> + */ >>>>> +static void lsdc_cursor_update_location_quirks(struct lsdc_device >>>>> *ldev, >>>>> +                          struct drm_crtc *crtc) >>>>> +{ >>>>> +   u32 val = CURSOR_FORMAT_ARGB8888; >>>>> + >>>>> +   /* >>>>> +    * If bit 4 of LSDC_CURSOR0_CFG_REG is 1, then the cursor >>>>> will be >>>>> +    * locate at CRTC-1, if bit 4 of LSDC_CURSOR0_CFG_REG is 0, then >>>>> +    * the cursor will be locate at CRTC-0. The cursor is alway >>>>> on the >>>>> +    * top of the primary. Compositing the primary plane and cursor >>>>> +    * plane is automatically done by hardware. >>>>> +    */ >>>>> +   if (drm_crtc_index(crtc)) >>>>> +       val |= CURSOR_LOCATION; >>>>> + >>>>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, val); >>>>> +} >>>>> + >>>>> +/* update the position of the cursor */ >>>>> +static void lsdc_cursor_update_position_quirks(struct lsdc_device >>>>> *ldev, >>>>> +                          int x, >>>>> +                          int y) >>>>> +{ >>>>> +   if (x < 0) >>>>> +       x = 0; >>>>> + >>>>> +   if (y < 0) >>>>> +       y = 0; >>>>> + >>>>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >>>>> +} >>>>> + >>>>> +static void lsdc_cursor_atomic_update_quirks(struct drm_plane >>>>> *plane, >>>>> +                        struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_device *ddev = plane->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   struct drm_plane_state *new_plane_state = >>>>> drm_atomic_get_new_plane_state(state, plane); >>>>> +   struct drm_plane_state *old_plane_state = >>>>> drm_atomic_get_old_plane_state(state, plane); >>>>> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >>>>> +   struct drm_framebuffer *old_fb = old_plane_state->fb; >>>>> + >>>>> +   if (new_fb != old_fb) { >>>>> +       s64 offset = lsdc_get_vram_bo_offset(new_fb); >>>>> +       u64 cursor_addr = ldev->vram_base + offset; >>>>> + >>>>> +       drm_dbg_kms(ddev, "%s offset: %llx\n", plane->name, offset); >>>>> + >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >>>>> >> 32) & 0xFF); >>>>> +   } >>>>> + >>>>> +   lsdc_cursor_update_position_quirks(ldev, >>>>> new_plane_state->crtc_x, new_plane_state->crtc_y); >>>>> + >>>>> +   lsdc_cursor_update_location_quirks(ldev, new_plane_state->crtc); >>>>> +} >>>>> + >>>>> +/* update the format, size and location of the cursor */ >>>>> +static void lsdc_cursor_atomic_update(struct drm_plane *plane, >>>>> +                     struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_device *ddev = plane->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   struct lsdc_display_pipe *dispipe = >>>>> cursor_to_display_pipe(plane); >>>>> +   struct drm_plane_state *new_plane_state = >>>>> drm_atomic_get_new_plane_state(state, plane); >>>>> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >>>>> +   int x = new_plane_state->crtc_x; >>>>> +   int y = new_plane_state->crtc_y; >>>>> +   u32 conf = CURSOR_FORMAT_ARGB8888 | CURSOR_SIZE_64X64; >>>>> +   u64 cursor_addr = ldev->vram_base + >>>>> lsdc_get_vram_bo_offset(new_fb); >>>>> + >>>>> +   if (x < 0) >>>>> +       x = 0; >>>>> + >>>>> +   if (y < 0) >>>>> +       y = 0; >>>>> + >>>>> +   if (dispipe->index == 0) { >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >>>>> >> 32) & 0xFF); >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >>>>> +       /* Attach Cursor-0 to CRTC-0 */ >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, conf & >>>>> ~CURSOR_LOCATION); >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >>>>> +       return; >>>>> +   } >>>>> + >>>>> +   if (dispipe->index == 1) { >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_HI_REG, (cursor_addr >>>>> >> 32) & 0xFF); >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_LO_REG, cursor_addr); >>>>> +       /* Attach Cursor-1 to CRTC-1 */ >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, conf | >>>>> CURSOR_LOCATION); >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_POSITION_REG, (y << 16) | x); >>>>> +       return; >>>>> +   } >>>>> +} >>>>> + >>>>> +static void lsdc_cursor_atomic_disable_quirks(struct drm_plane >>>>> *plane, >>>>> +                         struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_device *ddev = plane->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> + >>>>> +   /* Set the format to 0 actually display the cursor */ >>>>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >>>>> + >>>>> +   drm_dbg(ddev, "%s disabled\n", plane->name); >>>>> +} >>>>> + >>>>> +static void lsdc_cursor_atomic_disable(struct drm_plane *plane, >>>>> +                      struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_device *ddev = plane->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   struct lsdc_display_pipe *dispipe = >>>>> cursor_to_display_pipe(plane); >>>>> + >>>>> +   if (dispipe->index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >>>>> +   else if (dispipe->index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, 0); >>>>> + >>>>> +   drm_dbg(ddev, "%s disabled\n", plane->name); >>>>> +} >>>>> + >>>>> +static const struct drm_plane_helper_funcs >>>>> lsdc_cursor_helpers_quirk = { >>>>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>>>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>>>> +   .atomic_check = lsdc_cursor_atomic_check, >>>>> +   .atomic_update = lsdc_cursor_atomic_update_quirks, >>>>> +   .atomic_disable = lsdc_cursor_atomic_disable_quirks, >>>>> +}; >>>>> + >>>>> +static const struct drm_plane_helper_funcs >>>>> lsdc_cursor_plane_helpers = { >>>>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>>>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>>>> +   .atomic_check = lsdc_cursor_atomic_check, >>>>> +   .atomic_update = lsdc_cursor_atomic_update, >>>>> +   .atomic_disable = lsdc_cursor_atomic_disable, >>>>> +}; >>>>> + >>>>> +static const struct drm_plane_funcs lsdc_plane_funcs = { >>>>> +   .update_plane = drm_atomic_helper_update_plane, >>>>> +   .disable_plane = drm_atomic_helper_disable_plane, >>>>> +   .destroy = drm_plane_cleanup, >>>>> +   .reset = drm_atomic_helper_plane_reset, >>>>> +   .atomic_duplicate_state = >>>>> drm_atomic_helper_plane_duplicate_state, >>>>> +   .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, >>>>> +}; >>>>> + >>>>> +static const struct drm_plane_helper_funcs * >>>>> +lsdc_get_cursor_helper_funcs(const struct lsdc_desc *descp) >>>>> +{ >>>>> +   if (descp->chip == CHIP_LS7A2000) >>>>> +       return &lsdc_cursor_plane_helpers; >>>>> + >>>>> +   return &lsdc_cursor_helpers_quirk; >>>>> +} >>>>> + >>>>> +int lsdc_plane_init(struct lsdc_device *ldev, >>>>> +           struct drm_plane *plane, >>>>> +           enum drm_plane_type type, >>>>> +           unsigned int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> +   struct drm_device *ddev = &ldev->base; >>>>> +   unsigned int format_count; >>>>> +   const u32 *formats; >>>>> +   const char *name; >>>>> +   int ret; >>>>> + >>>>> +   switch (type) { >>>>> +   case DRM_PLANE_TYPE_PRIMARY: >>>>> +       formats = lsdc_primary_formats; >>>>> +       format_count = ARRAY_SIZE(lsdc_primary_formats); >>>>> +       name = "primary-%u"; >>>>> +       break; >>>>> +   case DRM_PLANE_TYPE_CURSOR: >>>>> +       formats = lsdc_cursor_formats; >>>>> +       format_count = ARRAY_SIZE(lsdc_cursor_formats); >>>>> +       name = "cursor-%u"; >>>>> +       break; >>>>> +   case DRM_PLANE_TYPE_OVERLAY: >>>>> +       drm_err(ddev, "overlay plane is not supported\n"); >>>>> +       break; >>>>> +   } >>>>> + >>>>> +   ret = drm_universal_plane_init(ddev, plane, 1 << index, >>>>> +                      &lsdc_plane_funcs, >>>>> +                      formats, format_count, >>>>> +                      lsdc_fb_format_modifiers, >>>>> +                      type, name, index); >>>>> +   if (ret) { >>>>> +       drm_err(ddev, "%s failed: %d\n", __func__, ret); >>>>> +       return ret; >>>>> +   } >>>>> + >>>>> +   switch (type) { >>>>> +   case DRM_PLANE_TYPE_PRIMARY: >>>>> +       drm_plane_helper_add(plane, &lsdc_primary_plane_helpers); >>>>> +       break; >>>>> +   case DRM_PLANE_TYPE_CURSOR: >>>>> +       drm_plane_helper_add(plane, >>>>> lsdc_get_cursor_helper_funcs(descp)); >>>>> +       break; >>>>> +   case DRM_PLANE_TYPE_OVERLAY: >>>>> +       drm_err(ddev, "overlay plane is not supported\n"); >>>>> +       break; >>>>> +   } >>>>> + >>>>> +   return 0; >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_pll.c >>>>> new file mode 100644 >>>>> index 000000000000..6ed74989a6f5 >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.c >>>>> @@ -0,0 +1,569 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include "lsdc_drv.h" >>>>> +#include "lsdc_regs.h" >>>>> +#include "lsdc_pll.h" >>>>> + >>>>> +/* >>>>> + * The structure of the pixel PLL register is evolved with times. >>>>> + * All loongson's cpu is little endian. >>>>> + */ >>>>> + >>>>> +/* u64 */ >>>>> +struct ls7a1000_pixpll_bitmap { >>>>> +   /* Byte 0 ~ Byte 3 */ >>>>> +   unsigned div_out     : 7;  /* 0 : 6    output clock >>>>> divider */ >>>>> +   unsigned __1         : 14; /* 7 : >>>>> 20                         */ >>>>> +   unsigned loopc       : 9;  /* 21 : 29   clock >>>>> multiplier     */ >>>>> +   unsigned __2         : 2;  /* 30 : >>>>> 31                         */ >>>>> + >>>>> +   /* Byte 4 ~ Byte 7 */ >>>>> +   unsigned div_ref     : 7;  /* 0 : 6    input clock >>>>> divider  */ >>>>> +   unsigned locked      : 1;  /* 7        PLL locked >>>>> status    */ >>>>> +   unsigned sel_out     : 1;  /* 8        output clk >>>>> selector  */ >>>>> +   unsigned __3         : 2;  /* 9 : 10 reserved             */ >>>>> +   unsigned set_param   : 1;  /* 11       trigger the >>>>> update   */ >>>>> +   unsigned bypass      : 1;  /* >>>>> 12                             */ >>>>> +   unsigned powerdown   : 1;  /* >>>>> 13                             */ >>>>> +   unsigned __4         : 18; /* 14 : >>>>> 31                        */ >>>>> +}; >>>>> + >>>>> +/* u128 */ >>>>> +struct ls2k1000_pixpll_bitmap { >>>>> +   /* Byte 0 ~ Byte 3 */ >>>>> +   unsigned sel_out     : 1; /* 0     select this >>>>> PLL         */ >>>>> +   unsigned __1         : 1; /* >>>>> 1                              */ >>>>> +   unsigned sw_adj_en   : 1; /* 2     allow software >>>>> adjust   */ >>>>> +   unsigned bypass      : 1; /* 3     bypass L1 >>>>> PLL           */ >>>>> +   unsigned __2         : 3; /* >>>>> 4:6                            */ >>>>> +   unsigned lock_en     : 1; /* 7     enable lock L1 >>>>> PLL      */ >>>>> +   unsigned __3         : 2; /* >>>>> 8:9                            */ >>>>> +   unsigned lock_check  : 2; /* 10:11  precision >>>>> check         */ >>>>> +   unsigned __4         : 4; /* >>>>> 12:15                           */ >>>>> + >>>>> +   unsigned locked      : 1; /* 16     PLL locked flag >>>>> bit     */ >>>>> +   unsigned __5         : 2; /* >>>>> 17:18                           */ >>>>> +   unsigned powerdown   : 1; /* 19     powerdown the pll if >>>>> set */ >>>>> +   unsigned __6         : 6; /* >>>>> 20:25                           */ >>>>> +   unsigned div_ref     : 6; /* 26:31  L1 >>>>> Prescaler            */ >>>>> + >>>>> +   /* Byte 4 ~ Byte 7 */ >>>>> +   unsigned loopc       : 10; /* 32:41  Clock >>>>> Multiplier        */ >>>>> +   unsigned l1_div      : 6; /* 42:47  not >>>>> used                */ >>>>> +   unsigned __7         : 16; /* >>>>> 48:63                           */ >>>>> + >>>>> +   /* Byte 8 ~ Byte 15 */ >>>>> +   unsigned div_out     : 6; /* 0 : 5  output clock >>>>> divider    */ >>>>> +   unsigned __8         : 26; /* 6 : >>>>> 31                          */ >>>>> +   unsigned __9         : 32; /* 70: >>>>> 127                         */ >>>>> +}; >>>>> + >>>>> +/* u32 */ >>>>> +struct ls2k0500_pixpll_bitmap { >>>>> +   /* Byte 0 ~ Byte 1 */ >>>>> +   unsigned sel_out     : 1; >>>>> +   unsigned __1         : 2; >>>>> +   unsigned sw_adj_en   : 1;  /* allow software >>>>> adjust             */ >>>>> +   unsigned bypass      : 1;  /* bypass L1 >>>>> PLL                     */ >>>>> +   unsigned powerdown   : 1;  /* write 1 to powerdown the >>>>> PLL      */ >>>>> +   unsigned __2         : 1; >>>>> +   unsigned locked      : 1;  /* 7    Is L1 PLL locked, read >>>>> only */ >>>>> +   unsigned div_ref     : 6;  /* 8:13 ref clock >>>>> divider          */ >>>>> +   unsigned __3         : 2;  /* >>>>> 14:15                             */ >>>>> +   /* Byte 2 ~ Byte 3 */ >>>>> +   unsigned loopc       : 8;  /* 16:23  Clock >>>>> Multiplier          */ >>>>> +   unsigned div_out     : 6;  /* 24:29  output clock >>>>> divider      */ >>>>> +   unsigned __4         : 2;  /* >>>>> 30:31                             */ >>>>> +}; >>>>> + >>>>> +union lsdc_pixpll_bitmap { >>>>> +   struct ls7a1000_pixpll_bitmap ls7a1000; >>>>> +   struct ls2k1000_pixpll_bitmap ls2k1000; >>>>> +   struct ls2k0500_pixpll_bitmap ls2k0500; >>>>> + >>>>> +   u32 dword[4]; >>>>> +}; >>>>> + >>>>> +struct pixclk_to_pll_parm { >>>>> +   /* kHz */ >>>>> +   unsigned int clock; >>>>> + >>>>> +   unsigned short width; >>>>> +   unsigned short height; >>>>> +   unsigned short vrefresh; >>>>> + >>>>> +   /* Stores parameters for programming the Hardware PLLs */ >>>>> +   unsigned short div_out; >>>>> +   unsigned short loopc; >>>>> +   unsigned short div_ref; >>>>> +}; >>>>> + >>>>> +/* >>>>> + * Pixel clock to PLL parameters translation table. >>>>> + * Small static cached value to speed up PLL parameters calculation. >>>>> + */ >>>>> +static const struct pixclk_to_pll_parm pll_param_table[] = { >>>>> +   {148500, 1920, 1080, 60, 11, 49, 3},  /* 1920x1080@60Hz */ >>>>> +                       /* 1920x1080@50Hz */ >>>>> +   {174500, 1920, 1080, 75, 17, 89, 3},  /* 1920x1080@75Hz */ >>>>> +   {181250, 2560, 1080, 75, 8, 58, 4},  /* 2560x1080@75Hz */ >>>>> +   {146250, 1680, 1050, 60, 16, 117, 5},  /* 1680x1050@60Hz */ >>>>> +   {135000, 1280, 1024, 75, 10, 54, 4},  /* 1280x1024@75Hz */ >>>>> + >>>>> +   {108000, 1600, 900, 60, 15, 81, 5},  /* 1600x900@60Hz */ >>>>> +                       /* 1280x1024@60Hz */ >>>>> +                       /* 1280x960@60Hz */ >>>>> +                       /* 1152x864@75Hz */ >>>>> + >>>>> +   {106500, 1440, 900, 60, 19, 81, 4},  /* 1440x900@60Hz */ >>>>> +   {88750, 1440, 900, 60, 16, 71, 5},  /* 1440x900@60Hz */ >>>>> +   {83500, 1280, 800, 60, 17, 71, 5},  /* 1280x800@60Hz */ >>>>> +   {71000, 1280, 800, 60, 20, 71, 5},  /* 1280x800@60Hz */ >>>>> + >>>>> +   {74250, 1280, 720, 60, 22, 49, 3},  /* 1280x720@60Hz */ >>>>> +                       /* 1280x720@50Hz */ >>>>> + >>>>> +   {78750, 1024, 768, 75, 16, 63, 5},  /* 1024x768@75Hz */ >>>>> +   {75000, 1024, 768, 70, 29, 87, 4},  /* 1024x768@70Hz */ >>>>> +   {65000, 1024, 768, 60, 20, 39, 3},  /* 1024x768@60Hz */ >>>>> + >>>>> +   {51200, 1024, 600, 60, 25, 64, 5},  /* 1024x600@60Hz */ >>>>> + >>>>> +   {57284, 832, 624, 75, 24, 55, 4},  /* 832x624@75Hz */ >>>>> +   {49500, 800, 600, 75, 40, 99, 5},  /* 800x600@75Hz */ >>>>> +   {50000, 800, 600, 72, 44, 88, 4},  /* 800x600@72Hz */ >>>>> +   {40000, 800, 600, 60, 30, 36, 3},  /* 800x600@60Hz */ >>>>> +   {36000, 800, 600, 56, 50, 72, 4},  /* 800x600@56Hz */ >>>>> +   {31500, 640, 480, 75, 40, 63, 5},  /* 640x480@75Hz */ >>>>> +                       /* 640x480@73Hz */ >>>>> + >>>>> +   {30240, 640, 480, 67, 62, 75, 4},  /* 640x480@67Hz */ >>>>> +   {27000, 720, 576, 50, 50, 54, 4},  /* 720x576@60Hz */ >>>>> +   {25175, 640, 480, 60, 85, 107, 5},  /* 640x480@60Hz */ >>>>> +   {25200, 640, 480, 60, 50, 63, 5},  /* 640x480@60Hz */ >>>>> +                       /* 720x480@60Hz */ >>>>> +}; >>>>> + >>>>> +/* >>>>> + * lsdc_pixpll_setup - ioremap the device dependent PLL registers >>>>> + * >>>>> + * @this: point to the object where this function is called from >>>>> + */ >>>>> +static int lsdc_pixpll_setup(struct lsdc_pll * const this) >>>>> +{ >>>>> +   this->mmio = ioremap(this->reg_base, this->reg_size); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +/* >>>>> + * Find a set of pll parameters (to generate pixel clock) from a >>>>> static >>>>> + * local table, which avoid to compute the pll parameter eachtime a >>>>> + * modeset is triggered. >>>>> + * >>>>> + * @this: point to the object which this function is called from >>>>> + * @clock: the desired output pixel clock, the unit is kHz >>>>> + * @pout: point to where the parameters to store if found >>>>> + * >>>>> + * Return true if hit, otherwise return false. >>>>> + */ >>>>> +static bool lsdc_pixpll_find(struct lsdc_pll * const this, >>>>> +                unsigned int clock, >>>>> +                struct lsdc_pll_parms * const pout) >>>>> +{ >>>>> +   unsigned int num = ARRAY_SIZE(pll_param_table); >>>>> +   unsigned int i; >>>>> + >>>>> +   for (i = 0; i < num; i++) { >>>>> +       if (clock != pll_param_table[i].clock) >>>>> +           continue; >>>>> + >>>>> +       pout->div_ref = pll_param_table[i].div_ref; >>>>> +       pout->loopc  = pll_param_table[i].loopc; >>>>> +       pout->div_out = pll_param_table[i].div_out; >>>>> + >>>>> +       return true; >>>>> +   } >>>>> + >>>>> +   drm_dbg(this->ddev, "pixel clock %u: miss\n", clock); >>>>> + >>>>> +   return false; >>>>> +} >>>>> + >>>>> +/* >>>>> + * Find a set of pll parameters which have minimal difference >>>>> with the desired >>>>> + * pixel clock frequency. It does that by computing all of the >>>>> possible >>>>> + * combination. Compute the diff and find the combination with >>>>> minimal diff. >>>>> + * >>>>> + * clock_out = refclk / div_ref * loopc / div_out >>>>> + * >>>>> + * refclk is determined by the oscillator mounted board(Here is >>>>> 100MHz in >>>>> + * almost all case) >>>>> + * >>>>> + * @this: point to the object from which this function is called >>>>> + * @clock_khz: the desired output pixel clock, the unit is kHz >>>>> + * @pout: point to the out struct of lsdc_pll_parms >>>>> + * >>>>> + * Return true if a parameter is found, otherwise return false >>>>> + */ >>>>> +static bool lsdc_pixpll_compute(struct lsdc_pll * const this, >>>>> +               unsigned int clock_khz, >>>>> +               struct lsdc_pll_parms *pout) >>>>> +{ >>>>> +   unsigned int refclk = this->ref_clock; >>>>> +   const unsigned int tolerance = 1000; >>>>> +   unsigned int min = tolerance; >>>>> +   unsigned int div_out, loopc, div_ref; >>>>> +   unsigned int computed; >>>>> + >>>>> +   if (lsdc_pixpll_find(this, clock_khz, pout)) >>>>> +       return true; >>>>> + >>>>> +   for (div_out = 6; div_out < 64; div_out++) { >>>>> +       for (div_ref = 3; div_ref < 6; div_ref++) { >>>>> +           for (loopc = 6; loopc < 161; loopc++) { >>>>> +               int diff; >>>>> + >>>>> +               if (loopc < 12 * div_ref) >>>>> +                   continue; >>>>> +               if (loopc > 32 * div_ref) >>>>> +                   continue; >>>>> + >>>>> +               computed = refclk * loopc / div_ref / div_out; >>>>> + >>>>> +               if (clock_khz > computed) >>>>> +                   diff = clock_khz - computed; >>>>> +               else if (clock_khz < computed) >>>>> +                   diff = computed - clock_khz; >>>>> + >>>>> +               if (diff < min) { >>>>> +                   min = diff; >>>>> +                   pout->div_ref = div_ref; >>>>> +                   pout->div_out = div_out; >>>>> +                   pout->loopc = loopc; >>>>> + >>>>> +                   if (diff == 0) >>>>> +                       return true; >>>>> +               } >>>>> +           } >>>>> +       } >>>>> +   } >>>>> + >>>>> +   return min < tolerance; >>>>> +} >>>>> + >>>>> +/* >>>>> + * Update the pll parameters to hardware, target to the pixpll in >>>>> ls7a1000 >>>>> + * >>>>> + * @this: point to the object from which this function is called >>>>> + * @pin: point to the struct of lsdc_pll_parms passed in >>>>> + * >>>>> + * return 0 if successful. >>>>> + */ >>>>> +static int ls7a1000_pixpll_param_update(struct lsdc_pll * const >>>>> this, >>>>> +                   struct lsdc_pll_parms const *pin) >>>>> +{ >>>>> +   void __iomem *reg = this->mmio; >>>>> +   unsigned int counter = 0; >>>>> +   bool locked; >>>>> +   u32 val; >>>>> + >>>>> +   /* Bypass the software configured PLL, using refclk directly */ >>>>> +   val = readl(reg + 0x4); >>>>> +   val &= ~(1 << 8); >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   /* Powerdown the PLL */ >>>>> +   val = readl(reg + 0x4); >>>>> +   val |= (1 << 13); >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   /* Clear the pll parameters */ >>>>> +   val = readl(reg + 0x4); >>>>> +   val &= ~(1 << 11); >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   /* clear old value & config new value */ >>>>> +   val = readl(reg + 0x04); >>>>> +   val &= ~0x7F; >>>>> +   val |= pin->div_ref;       /* div_ref */ >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   val = readl(reg); >>>>> +   val &= ~0x7f; >>>>> +   val |= pin->div_out;       /* div_out */ >>>>> + >>>>> +   val &= ~(0x1ff << 21); >>>>> +   val |= pin->loopc << 21;   /* loopc */ >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Set the pll the parameters */ >>>>> +   val = readl(reg + 0x4); >>>>> +   val |= (1 << 11); >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   /* Powerup the PLL */ >>>>> +   val = readl(reg + 0x4); >>>>> +   val &= ~(1 << 13); >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   /* Wait the PLL lock */ >>>>> +   do { >>>>> +       val = readl(reg + 0x4); >>>>> +       locked = val & 0x80; >>>>> +       counter++; >>>>> +   } while (!locked && (counter < 2000)); >>>>> + >>>>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>>>> + >>>>> +   /* Switch to the software configured pll */ >>>>> +   val = readl(reg + 0x4); >>>>> +   val |= (1UL << 8); >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +/* >>>>> + * Update the pll parameters to hardware, target to the pixpll in >>>>> ls2k1000 >>>>> + * >>>>> + * @this: point to the object from which this function is called >>>>> + * @pin: point to the struct of lsdc_pll_parms passed in >>>>> + * >>>>> + * return 0 if successful. >>>>> + */ >>>>> +static int ls2k1000_pixpll_param_update(struct lsdc_pll * const >>>>> this, >>>>> +                   struct lsdc_pll_parms const *pin) >>>>> +{ >>>>> +   void __iomem *reg = this->mmio; >>>>> +   unsigned int counter = 0; >>>>> +   bool locked = false; >>>>> +   u32 val; >>>>> + >>>>> +   val = readl(reg); >>>>> +   /* Bypass the software configured PLL, using refclk directly */ >>>>> +   val &= ~(1 << 0); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Powerdown the PLL */ >>>>> +   val |= (1 << 19); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Allow the software configuration */ >>>>> +   val &= ~(1 << 2); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* allow L1 PLL lock */ >>>>> +   val = (1L << 7) | (3L << 10); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* clear div_ref bit field */ >>>>> +   val &= ~(0x3f << 26); >>>>> +   /* set div_ref bit field */ >>>>> +   val |= pin->div_ref << 26; >>>>> +   writel(val, reg); >>>>> + >>>>> +   val = readl(reg + 4); >>>>> +   /* clear loopc bit field */ >>>>> +   val &= ~0x0fff; >>>>> +   /* set loopc bit field */ >>>>> +   val |= pin->loopc; >>>>> +   writel(val, reg + 4); >>>>> + >>>>> +   /* set div_out */ >>>>> +   writel(pin->div_out, reg + 8); >>>>> + >>>>> +   val = readl(reg); >>>>> +   /* use this parms configured just now */ >>>>> +   val |= (1 << 2); >>>>> +   /* powerup the PLL */ >>>>> +   val &= ~(1 << 19); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* wait pll setup and locked */ >>>>> +   do { >>>>> +       val = readl(reg); >>>>> +       locked = val & 0x10000; >>>>> +       counter++; >>>>> +   } while (!locked && (counter < 2000)); >>>>> + >>>>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>>>> + >>>>> +   /* Switch to software configured PLL instead of refclk */ >>>>> +   val |= 1; >>>>> +   writel(val, reg); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +/* >>>>> + * Update the pll parameters to hardware, target to the pixpll in >>>>> ls2k0500 >>>>> + * >>>>> + * @this: point to the object which calling this function >>>>> + * @param: pointer to where the parameters passed in >>>>> + * >>>>> + * return 0 if successful. >>>>> + */ >>>>> +static int ls2k0500_pixpll_param_update(struct lsdc_pll * const >>>>> this, >>>>> +                   struct lsdc_pll_parms const *param) >>>>> +{ >>>>> +   void __iomem *reg = this->mmio; >>>>> +   unsigned int counter = 0; >>>>> +   bool locked = false; >>>>> +   u32 val; >>>>> + >>>>> +   /* Bypass the software configured PLL, using refclk directly */ >>>>> +   val = readl(reg); >>>>> +   val &= ~(1 << 0); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Powerdown the PLL */ >>>>> +   val = readl(reg); >>>>> +   val |= (1 << 5); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Allow the software configuration */ >>>>> +   val |= (1 << 3); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Update the pll params */ >>>>> +   val = (param->div_out << 24) | >>>>> +         (param->loopc << 16) | >>>>> +         (param->div_ref << 8); >>>>> + >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Powerup the PLL */ >>>>> +   val = readl(reg); >>>>> +   val &= ~(1 << 5); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* wait pll setup and locked */ >>>>> +   do { >>>>> +       val = readl(reg); >>>>> +       locked = val & 0x80; >>>>> +       counter++; >>>>> +   } while (!locked && (counter < 10000)); >>>>> + >>>>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>>>> + >>>>> +   /* Switch to software configured PLL instead of refclk */ >>>>> +   writel((val | 1), reg); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static unsigned int lsdc_get_clock_rate(struct lsdc_pll * const >>>>> this, >>>>> +                   struct lsdc_pll_parms *pout) >>>>> +{ >>>>> +   struct drm_device *ddev = this->ddev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   const struct lsdc_desc * const descp = ldev->descp; >>>>> +   unsigned int out; >>>>> +   union lsdc_pixpll_bitmap parms; >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>>>> CHIP_LS7A2000) { >>>>> +       struct ls7a1000_pixpll_bitmap *obj = &parms.ls7a1000; >>>>> + >>>>> +       parms.dword[0] = readl(this->mmio); >>>>> +       parms.dword[1] = readl(this->mmio + 4); >>>>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>>>> obj->div_out; >>>>> +       if (pout) { >>>>> +           pout->div_ref = obj->div_ref; >>>>> +           pout->loopc = obj->loopc; >>>>> +           pout->div_out = obj->div_out; >>>>> +       } >>>>> +   } else if (descp->chip == CHIP_LS2K1000) { >>>>> +       struct ls2k1000_pixpll_bitmap *obj = &parms.ls2k1000; >>>>> + >>>>> +       parms.dword[0] = readl(this->mmio); >>>>> +       parms.dword[1] = readl(this->mmio + 4); >>>>> +       parms.dword[2] = readl(this->mmio + 8); >>>>> +       parms.dword[3] = readl(this->mmio + 12); >>>>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>>>> obj->div_out; >>>>> +       if (pout) { >>>>> +           pout->div_ref = obj->div_ref; >>>>> +           pout->loopc = obj->loopc; >>>>> +           pout->div_out = obj->div_out; >>>>> +       } >>>>> +   } else if (descp->chip == CHIP_LS2K0500) { >>>>> +       struct ls2k0500_pixpll_bitmap *obj = &parms.ls2k0500; >>>>> + >>>>> +       parms.dword[0] = readl(this->mmio); >>>>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>>>> obj->div_out; >>>>> +       if (pout) { >>>>> +           pout->div_ref = obj->div_ref; >>>>> +           pout->loopc = obj->loopc; >>>>> +           pout->div_out = obj->div_out; >>>>> +       } >>>>> +   } else { >>>>> +       drm_err(ddev, "unknown chip, the driver need update\n"); >>>>> +       return 0; >>>>> +   } >>>>> + >>>>> +   return out; >>>>> +} >>>>> + >>>>> +static const struct lsdc_pixpll_funcs ls7a1000_pixpll_funcs = { >>>>> +   .setup = lsdc_pixpll_setup, >>>>> +   .compute = lsdc_pixpll_compute, >>>>> +   .update = ls7a1000_pixpll_param_update, >>>>> +   .get_clock_rate = lsdc_get_clock_rate, >>>>> +}; >>>>> + >>>>> +static const struct lsdc_pixpll_funcs ls2k1000_pixpll_funcs = { >>>>> +   .setup = lsdc_pixpll_setup, >>>>> +   .compute = lsdc_pixpll_compute, >>>>> +   .update = ls2k1000_pixpll_param_update, >>>>> +   .get_clock_rate = lsdc_get_clock_rate, >>>>> +}; >>>>> + >>>>> +static const struct lsdc_pixpll_funcs ls2k0500_pixpll_funcs = { >>>>> +   .setup = lsdc_pixpll_setup, >>>>> +   .compute = lsdc_pixpll_compute, >>>>> +   .update = ls2k0500_pixpll_param_update, >>>>> +   .get_clock_rate = lsdc_get_clock_rate, >>>>> +}; >>>>> + >>>>> +int lsdc_pixpll_init(struct lsdc_pll * const this, >>>>> +            struct drm_device *ddev, >>>>> +            unsigned int index) >>>>> +{ >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   this->ddev = ddev; >>>>> +   this->index = index; >>>>> +   this->ref_clock = LSDC_PLL_REF_CLK; >>>>> + >>>>> +   /* LS7A1000 and LS7A2000's pixpll setting registers is same */ >>>>> +   if (descp->chip == CHIP_LS7A2000 || descp->chip == >>>>> CHIP_LS7A1000) { >>>>> +       if (index == 0) >>>>> +           this->reg_base = LS7A1000_CFG_REG_BASE + >>>>> LS7A1000_PIX_PLL0_REG; >>>>> +       else if (index == 1) >>>>> +           this->reg_base = LS7A1000_CFG_REG_BASE + >>>>> LS7A1000_PIX_PLL1_REG; >>>>> +       this->reg_size = 8; >>>>> +       this->funcs = &ls7a1000_pixpll_funcs; >>>>> +   } else if (descp->chip == CHIP_LS2K1000) { >>>>> +       if (index == 0) >>>>> +           this->reg_base = LS2K1000_CFG_REG_BASE + >>>>> LS2K1000_PIX_PLL0_REG; >>>>> +       else if (index == 1) >>>>> +           this->reg_base = LS2K1000_CFG_REG_BASE + >>>>> LS2K1000_PIX_PLL1_REG; >>>>> + >>>>> +       this->reg_size = 16; >>>>> +       this->funcs = &ls2k1000_pixpll_funcs; >>>>> +   } else if (descp->chip == CHIP_LS2K0500) { >>>>> +       if (index == 0) >>>>> +           this->reg_base = LS2K0500_CFG_REG_BASE + >>>>> LS2K0500_PIX_PLL0_REG; >>>>> +       else if (index == 1) >>>>> +           this->reg_base = LS2K0500_CFG_REG_BASE + >>>>> LS2K0500_PIX_PLL1_REG; >>>>> + >>>>> +       this->reg_size = 4; >>>>> +       this->funcs = &ls2k0500_pixpll_funcs; >>>>> +   } else { >>>>> +       drm_err(this->ddev, "unknown chip, the driver need >>>>> update\n"); >>>>> +       return -ENOENT; >>>>> +   } >>>>> + >>>>> +   return this->funcs->setup(this); >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.h >>>>> b/drivers/gpu/drm/lsdc/lsdc_pll.h >>>>> new file mode 100644 >>>>> index 000000000000..538739f461f2 >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.h >>>>> @@ -0,0 +1,78 @@ >>>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>>> + >>>>> +#ifndef __LSDC_PLL_H__ >>>>> +#define __LSDC_PLL_H__ >>>>> + >>>>> +#include <drm/drm_device.h> >>>>> + >>>>> +/* >>>>> + * Loongson Pixel PLL hardware structure >>>>> + * >>>>> + * refclk: reference frequency, 100 MHz from external oscillator >>>>> + * outclk: output frequency desired. >>>>> + * >>>>> + * >>>>> + *              L1      Fref                     Fvco L2 >>>>> + * refclk  +-----------+     +------------------+ +---------+  >>>>> outclk >>>>> + * ---+---> | Prescaler | ---> | Clock Multiplier | ---> | >>>>> divider | --------> >>>>> + *   |    +-----------+     +------------------+ >>>>> +---------+    ^ >>>>> + *   |          ^                     ^ ^         | >>>>> + *   |          |                     | |         | >>>>> + *   |          |                     | |         | >>>>> + *   |       div_ref                loopc div_out | >>>>> + * | | >>>>> + *   +--- sel_out (bypass above software configurable clock if >>>>> set) ----+ >>>>> + * >>>>> + * sel_out: PLL clock output selector (for debug purpose only). >>>>> + * >>>>> + * If sel_out == 1, it will take refclk as output directly, >>>>> + * the L1 Prescaler and the out divider will be bypassed. >>>>> + * >>>>> + * If sel_out == 0, then outclk = refclk / div_ref * loopc / >>>>> div_out; >>>>> + * >>>>> + * PLL working requirements: >>>>> + * >>>>> + * 1) 20 MHz <= refclk / div_ref <= 40Mhz >>>>> + * 2) 1.2 GHz <= refclk /div_out * loopc <= 3.2 Ghz >>>>> + */ >>>>> + >>>>> +struct lsdc_pll_parms { >>>>> +   unsigned int div_ref; >>>>> +   unsigned int loopc; >>>>> +   unsigned int div_out; >>>>> +}; >>>>> + >>>>> +struct lsdc_pll; >>>>> + >>>>> +struct lsdc_pixpll_funcs { >>>>> +   int (*setup)(struct lsdc_pll * const this); >>>>> +   bool (*compute)(struct lsdc_pll * const this, >>>>> +           unsigned int clock, >>>>> +           struct lsdc_pll_parms *pout); >>>>> +   int (*update)(struct lsdc_pll * const this, >>>>> +             struct lsdc_pll_parms const *pin); >>>>> +   unsigned int (*get_clock_rate)(struct lsdc_pll * const this, >>>>> +                      struct lsdc_pll_parms *pout); >>>>> +}; >>>>> + >>>>> +struct lsdc_pll { >>>>> +   const struct lsdc_pixpll_funcs *funcs; >>>>> +   struct drm_device *ddev; >>>>> +   void __iomem *mmio; >>>>> + >>>>> +   /* PLL register offset */ >>>>> +   u32 reg_base; >>>>> +   /* PLL register size in bytes */ >>>>> +   u32 reg_size; >>>>> + >>>>> +   /* 100000kHz, fixed on all board found */ >>>>> +   unsigned int ref_clock; >>>>> + >>>>> +   unsigned int index; >>>>> +}; >>>>> + >>>>> +int lsdc_pixpll_init(struct lsdc_pll * const this, >>>>> +            struct drm_device *ddev, >>>>> +            unsigned int index); >>>>> + >>>>> +#endif >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_regs.c >>>>> new file mode 100644 >>>>> index 000000000000..c7950c43968f >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.c >>>>> @@ -0,0 +1,29 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include "lsdc_drv.h" >>>>> +#include "lsdc_regs.h" >>>>> + >>>>> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset) >>>>> +{ >>>>> +   unsigned long flags; >>>>> +   u32 ret; >>>>> + >>>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>>> + >>>>> +   ret = readl(ldev->reg_base + offset); >>>>> + >>>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>>> + >>>>> +   return ret; >>>>> +} >>>>> + >>>>> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 >>>>> val) >>>>> +{ >>>>> +   unsigned long flags; >>>>> + >>>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>>> + >>>>> +   writel(val, ldev->reg_base + offset); >>>>> + >>>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.h >>>>> b/drivers/gpu/drm/lsdc/lsdc_regs.h >>>>> new file mode 100644 >>>>> index 000000000000..828956633137 >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.h >>>>> @@ -0,0 +1,296 @@ >>>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>>> + >>>>> +#ifndef __LSDC_REGS_H__ >>>>> +#define __LSDC_REGS_H__ >>>>> + >>>>> +#include <linux/bitops.h> >>>>> +#include <linux/types.h> >>>>> + >>>>> +/* >>>>> + * PIXEL PLL Reference clock >>>>> + */ >>>>> +#define LSDC_PLL_REF_CLK               100000 /* kHz */ >>>>> + >>>>> +/* >>>>> + * Those PLL registers are not located at DC reg bar space, >>>>> + * there are relative to LSXXXXX_CFG_REG_BASE. >>>>> + * XXXXX = 7A1000, 2K1000, 2K0500 >>>>> + */ >>>>> + >>>>> +/* LS2K1000 */ >>>>> +#define LS2K1000_PIX_PLL0_REG          0x04B0 >>>>> +#define LS2K1000_PIX_PLL1_REG          0x04C0 >>>>> +#define LS2K1000_CFG_REG_BASE          0x1fe10000 >>>>> + >>>>> +/* LS7A1000 and LS2K2000 */ >>>>> +#define LS7A1000_PIX_PLL0_REG          0x04B0 >>>>> +#define LS7A1000_PIX_PLL1_REG          0x04C0 >>>>> +#define LS7A1000_CFG_REG_BASE          0x10010000 >>>>> + >>>>> +/* LS2K0500 */ >>>>> +#define LS2K0500_PIX_PLL0_REG          0x0418 >>>>> +#define LS2K0500_PIX_PLL1_REG          0x0420 >>>>> +#define LS2K0500_CFG_REG_BASE          0x1fe10000 >>>>> + >>>>> +/* >>>>> + * CRTC CFG REG >>>>> + */ >>>>> +#define CFG_PIX_FMT_MASK               GENMASK(2, 0) >>>>> + >>>>> +enum lsdc_pixel_format { >>>>> +   LSDC_PF_NONE = 0, >>>>> +   LSDC_PF_ARGB4444 = 1, /* ARGB A:4 bits R/G/B: 4 bits each >>>>> [16 bits] */ >>>>> +   LSDC_PF_ARGB1555 = 2, /* ARGB A:1 bit RGB:15 bits [16 bits] */ >>>>> +   LSDC_PF_RGB565 = 3,   /* RGB [16 bits] */ >>>>> +   LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */ >>>>> +   LSDC_PF_RGBA8888 = 5, /* ARGB [32 bits] */ >>>>> +}; >>>>> + >>>>> +/* >>>>> + * Each crtc has two set fb address registers usable, >>>>> CFG_FB_IN_USING of >>>>> + * LSDC_CRTCx_CFG_REG specify which fb address register is currently >>>>> + * in using by the CRTC. CFG_PAGE_FLIP of LSDC_CRTCx_CFG_REG is >>>>> used to >>>>> + * trigger the switch which will be finished at the very vblank. >>>>> If you >>>>> + * want it switch to another again, you must set CFG_PAGE_FLIP >>>>> again. >>>>> + */ >>>>> +#define CFG_PAGE_FLIP                  BIT(7) >>>>> +#define CFG_OUTPUT_EN                  BIT(8) >>>>> +/* CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using >>>>> hardware logic */ >>>>> +#define CFG_PANEL_SWITCH               BIT(9) >>>>> +/* Indicate witch fb addr reg is in using, currently */ >>>>> +#define CFG_FB_IN_USING                BIT(11) >>>>> +#define CFG_GAMMA_EN                   BIT(12) >>>>> + >>>>> +/* CRTC get soft reset if voltage level change from 1 -> 0 */ >>>>> +#define CFG_RESET_N                    BIT(20) >>>>> + >>>>> +#define CFG_HSYNC_EN                   BIT(30) >>>>> +#define CFG_HSYNC_INV                  BIT(31) >>>>> +#define CFG_VSYNC_EN                   BIT(30) >>>>> +#define CFG_VSYNC_INV                  BIT(31) >>>>> + >>>>> +/* THE DMA step of the DC in LS7A2000 is configurable */ >>>>> +#define LSDC_DMA_STEP_MASK             GENMASK(17, 16) >>>>> +enum lsdc_dma_steps_supported { >>>>> +   LSDC_DMA_STEP_256_BYTES = 0, >>>>> +   LSDC_DMA_STEP_128_BYTES = 1, >>>>> +   LSDC_DMA_STEP_64_BYTES = 2, >>>>> +   LSDC_DMA_STEP_32_BYTES = 3, >>>>> +}; >>>>> + >>>>> +/******** CRTC0 & DVO0 ********/ >>>>> +#define LSDC_CRTC0_CFG_REG             0x1240 >>>>> +#define LSDC_CRTC0_FB0_LO_ADDR_REG     0x1260 >>>>> +#define LSDC_CRTC0_FB0_HI_ADDR_REG     0x15A0 >>>>> +#define LSDC_CRTC0_FB1_LO_ADDR_REG     0x1580 >>>>> +#define LSDC_CRTC0_FB1_HI_ADDR_REG     0x15C0 >>>>> +#define LSDC_CRTC0_STRIDE_REG          0x1280 >>>>> +#define LSDC_CRTC0_FB_ORIGIN_REG       0x1300 >>>>> +#define LSDC_CRTC0_HDISPLAY_REG        0x1400 >>>>> +#define LSDC_CRTC0_HSYNC_REG           0x1420 >>>>> +#define LSDC_CRTC0_VDISPLAY_REG        0x1480 >>>>> +#define LSDC_CRTC0_VSYNC_REG           0x14A0 >>>>> +#define LSDC_CRTC0_GAMMA_INDEX_REG     0x14E0 >>>>> +#define LSDC_CRTC0_GAMMA_DATA_REG      0x1500 >>>>> + >>>>> +/******** CTRC1 & DVO1 ********/ >>>>> +#define LSDC_CRTC1_CFG_REG             0x1250 >>>>> +#define LSDC_CRTC1_FB0_LO_ADDR_REG     0x1270 >>>>> +#define LSDC_CRTC1_FB0_HI_ADDR_REG     0x15B0 >>>>> +#define LSDC_CRTC1_FB1_LO_ADDR_REG     0x1590 >>>>> +#define LSDC_CRTC1_FB1_HI_ADDR_REG     0x15D0 >>>>> +#define LSDC_CRTC1_STRIDE_REG          0x1290 >>>>> +#define LSDC_CRTC1_FB_ORIGIN_REG       0x1310 >>>>> +#define LSDC_CRTC1_HDISPLAY_REG        0x1410 >>>>> +#define LSDC_CRTC1_HSYNC_REG           0x1430 >>>>> +#define LSDC_CRTC1_VDISPLAY_REG        0x1490 >>>>> +#define LSDC_CRTC1_VSYNC_REG           0x14B0 >>>>> +#define LSDC_CRTC1_GAMMA_INDEX_REG     0x14F0 >>>>> +#define LSDC_CRTC1_GAMMA_DATA_REG      0x1510 >>>>> + >>>>> +/* >>>>> + * LS7A2000 has the hardware which record the scan position of >>>>> the CRTC >>>>> + * [31:16] : current X position, [15:0] : current Y position >>>>> + */ >>>>> +#define LSDC_CRTC0_SCAN_POS_REG        0x14C0 >>>>> +#define LSDC_CRTC1_SCAN_POS_REG        0x14D0 >>>>> + >>>>> +/* >>>>> + * LS7A2000 has the hardware which count the number of vblank >>>>> generated >>>>> + */ >>>>> +#define LSDC_CRTC0_VSYNC_COUNTER_REG   0x1A00 >>>>> +#define LSDC_CRTC1_VSYNC_COUNTER_REG   0x1A10 >>>>> + >>>>> +/* In all, LSDC_CRTC1_XXX_REG - LSDC_CRTC0_XXX_REG = 0x10 */ >>>>> + >>>>> +/* >>>>> + * There is only one hardware cursor unit in ls7a1000, ls2k1000 >>>>> and ls2k0500. >>>>> + * well, ls7a2000 has two hardware cursor unit. >>>>> + */ >>>>> +#define CURSOR_FORMAT_MASK             GENMASK(1, 0) >>>>> +enum lsdc_cursor_format { >>>>> +   CURSOR_FORMAT_DISABLE = 0, >>>>> +   CURSOR_FORMAT_MONOCHROME = 1, >>>>> +   CURSOR_FORMAT_ARGB8888 = 2, >>>>> +}; >>>>> + >>>>> +#define CURSOR_SIZE_64X64              BIT(2) >>>>> +#define CURSOR_LOCATION                BIT(4) >>>>> + >>>>> +#define LSDC_CURSOR0_CFG_REG           0x1520 >>>>> +#define LSDC_CURSOR0_ADDR_LO_REG       0x1530 >>>>> +#define LSDC_CURSOR0_ADDR_HI_REG       0x15e0 >>>>> +#define LSDC_CURSOR0_POSITION_REG      0x1540 >>>>> +#define LSDC_CURSOR0_BG_COLOR_REG      0x1550 /* background >>>>> color */ >>>>> +#define LSDC_CURSOR0_FG_COLOR_REG      0x1560 /* foreground >>>>> color */ >>>>> + >>>>> +#define LSDC_CURSOR1_CFG_REG           0x1670 >>>>> +#define LSDC_CURSOR1_ADDR_LO_REG       0x1680 >>>>> +#define LSDC_CURSOR1_ADDR_HI_REG       0x16e0 >>>>> +#define LSDC_CURSOR1_POSITION_REG      0x1690 /* [31:16] Y, >>>>> [15:0] X */ >>>>> +#define LSDC_CURSOR1_BG_COLOR_REG      0x16A0 /* background >>>>> color */ >>>>> +#define LSDC_CURSOR1_FG_COLOR_REG      0x16B0 /* foreground >>>>> color */ >>>>> + >>>>> +/* >>>>> + * DC Interrupt Control Register, 32bit, Address Offset: 1570 >>>>> + * >>>>> + * Bits 15:0 inidicate the interrupt status >>>>> + * Bits 31:16 control enable interrupts corresponding to bit 15:0 >>>>> or not >>>>> + * Write 1 to enable, write 0 to disable >>>>> + * >>>>> + * RF: Read Finished >>>>> + * IDBU: Internal Data Buffer Underflow >>>>> + * IDBFU: Internal Data Buffer Fatal Underflow >>>>> + * CBRF: Cursor Buffer Read Finished Flag, no use. >>>>> + * >>>>> + * >>>>> +-------+--------------------------+-------+--------+--------+-------+ >>>>> >>>>> + * | 31:27 |        26:16           | 15:11 |  10  | 9 |  8  | >>>>> + * >>>>> +-------+--------------------------+-------+--------+--------+-------+ >>>>> >>>>> + * | N/A | Interrupt Enable Control | N/A | IDBFU0 | IDBFU1 | >>>>> IDBU0 | >>>>> + * >>>>> +-------+--------------------------+-------+--------+--------+-------+ >>>>> >>>>> + * >>>>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>>>> + * |  7  | 6 | 5 | 4  |  3   |  2   |  1 | 0   | >>>>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>>>> + * | IDBU1 | RF0 | RF1 | CRRF | HSYNC0 | VSYNC0 | HSYNC1 | VSYNC1 | >>>>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>>>> + */ >>>>> + >>>>> +#define LSDC_INT_REG                   0x1570 >>>>> + >>>>> +#define INT_CRTC0_VSYNC                BIT(2) >>>>> +#define INT_CRTC0_HSYNC                BIT(3) >>>>> +#define INT_CRTC0_RF                   BIT(6) >>>>> +#define INT_CRTC0_IDBU                 BIT(8) >>>>> +#define INT_CRTC0_IDBFU                BIT(10) >>>>> + >>>>> +#define INT_CRTC1_VSYNC                BIT(0) >>>>> +#define INT_CRTC1_HSYNC                BIT(1) >>>>> +#define INT_CRTC1_RF                   BIT(5) >>>>> +#define INT_CRTC1_IDBU                 BIT(7) >>>>> +#define INT_CRTC1_IDBFU                BIT(9) >>>>> + >>>>> +#define INT_CRTC0_VS_EN                BIT(18) >>>>> +#define INT_CRTC0_HS_EN                BIT(19) >>>>> +#define INT_CRTC0_RF_EN                BIT(22) >>>>> +#define INT_CRTC0_IDBU_EN              BIT(24) >>>>> +#define INT_CRTC0_IDBFU_EN             BIT(26) >>>>> + >>>>> +#define INT_CRTC1_VS_EN                BIT(16) >>>>> +#define INT_CRTC1_HS_EN                BIT(17) >>>>> +#define INT_CRTC1_RF_EN                BIT(21) >>>>> +#define INT_CRTC1_IDBU_EN              BIT(23) >>>>> +#define INT_CRTC1_IDBFU_EN             BIT(25) >>>>> + >>>>> +#define INT_STATUS_MASK                GENMASK(15, 0) >>>>> + >>>>> +/* >>>>> + * LS7A1000/LS7A2000 have 4 gpios which are used to emulated I2C. >>>>> + * They are under control of the LS7A_DC_GPIO_DAT_REG and >>>>> LS7A_DC_GPIO_DIR_REG >>>>> + * register, Those GPIOs has no relationship whth the GPIO >>>>> hardware on the >>>>> + * bridge chip itself. Those offsets are relative to DC register >>>>> base address >>>>> + * >>>>> + * LS2k1000 and LS2K0500 don't have those registers, they use >>>>> hardware i2c >>>>> + * or general GPIO emulated i2c from linux i2c subsystem. >>>>> + * >>>>> + * GPIO data register, address offset: 0x1650 >>>>> + *  +---------------+-----------+-----------+ >>>>> + *  | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | >>>>> + *  +---------------+-----------+-----------+ >>>>> + *  |              |   DVO1  |   DVO0  | >>>>> + *  +     N/A     +-----------+-----------+ >>>>> + *  |              | SCL | SDA | SCL | SDA | >>>>> + *  +---------------+-----------+-----------+ >>>>> + */ >>>>> +#define LS7A_DC_GPIO_DAT_REG           0x1650 >>>>> + >>>>> +/* >>>>> + * GPIO Input/Output direction control register, address offset: >>>>> 0x1660 >>>>> + */ >>>>> +#define LS7A_DC_GPIO_DIR_REG           0x1660 >>>>> + >>>>> +/* >>>>> + * LS7A2000 has two built-in HDMI Encoder and one VGA encoder >>>>> + */ >>>>> + >>>>> +/* >>>>> + * Number of continuous packets may be present >>>>> + * in HDMI hblank and vblank zone, should >= 48 >>>>> + */ >>>>> +#define LSDC_HDMI0_ZONE_REG            0x1700 >>>>> +#define LSDC_HDMI1_ZONE_REG            0x1710 >>>>> + >>>>> +#define HDMI_INTERFACE_EN              BIT(0) >>>>> +#define HDMI_PACKET_EN                 BIT(1) >>>>> +#define HDMI_AUDIO_EN                  BIT(2) >>>>> +#define HDMI_VIDEO_PREAMBLE_MASK       GENMASK(7, 4) >>>>> +#define HDMI_HW_I2C_EN                 BIT(8) >>>>> +#define HDMI_CTL_PERIOD_MODE           BIT(9) >>>>> +#define LSDC_HDMI0_CTRL_REG            0x1720 >>>>> +#define LSDC_HDMI1_CTRL_REG            0x1730 >>>>> + >>>>> +#define HDMI_PHY_EN                    BIT(0) >>>>> +#define HDMI_PHY_RESET_N               BIT(1) >>>>> +#define HDMI_PHY_TERM_L_EN             BIT(8) >>>>> +#define HDMI_PHY_TERM_H_EN             BIT(9) >>>>> +#define HDMI_PHY_TERM_DET_EN           BIT(10) >>>>> +#define HDMI_PHY_TERM_STATUS           BIT(11) >>>>> +#define LSDC_HDMI0_PHY_CTRL_REG        0x1800 >>>>> +#define LSDC_HDMI1_PHY_CTRL_REG        0x1810 >>>>> + >>>>> +/* >>>>> + * 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 | >>>>> + * +--------------------------------------------------------+ >>>>> + */ >>>>> +#define LSDC_HDMI0_PHY_PLL_REG         0x1820 >>>>> +#define LSDC_HDMI1_PHY_PLL_REG         0x1830 >>>>> + >>>>> +#define HDMI_PLL_ENABLE                BIT(0) >>>>> +#define HDMI_PLL_LOCKED                BIT(16) >>>>> +#define HDMI_PLL_BYPASS                BIT(17) >>>>> + >>>>> +#define HDMI_PLL_IDF_SHIFT             1 >>>>> +#define HDMI_PLL_IDF_MASK              GENMASK(5, 1) >>>>> +#define HDMI_PLL_LF_SHIFT              6 >>>>> +#define HDMI_PLL_LF_MASK               GENMASK(12, 6) >>>>> +#define HDMI_PLL_ODF_SHIFT             13 >>>>> +#define HDMI_PLL_ODF_MASK              GENMASK(15, 13) >>>>> + >>>>> +/* LS7A2000 have hpd support */ >>>>> +#define LSDC_HDMI_HPD_STATUS_REG       0x1BA0 >>>>> +#define HDMI0_HPD_FLAG                 BIT(0) >>>>> +#define HDMI1_HPD_FLAG                 BIT(1) >>>>> + >>>>> +#endif >>>> >>> >
Hi Am 13.02.23 um 05:24 schrieb Sui jingfeng: [...] > >> Here's where we select the chip: >> >> >> https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_drv.c#L227 >> >> There's a source file for each chip and the common source code is in >> > I will follow this method at next version, but our hardware and bios > engineers is ugly, they often made the chip indistinguishable. > > ls7a1000 and ls2k1000 have the same pci device id, ls7a2000 and > ls2k2000 have the same pci device id, > > even the revision id is same. I have no choice but to rely on DT or Relying on DT is not a problem. Many drivers do that. > kernel cmd passing parameters. It's also ok to to read version information from a status register. If you really have models that cannot be detect, that's more of a problem. > > many reviewers and maintainer seem not happy about this. By maintainers, you mean maintainers in other parts of the Linux kernel? Did they offer a solution on how to deal with this situation? Do you have a link to sucha discussion? > >> https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_mode.c >> >> >>> +   .reset = lsdc_crtc_reset, >>> +   .destroy = drm_crtc_cleanup, >>> +   .set_config = drm_atomic_helper_set_config, >>> +   .page_flip = drm_atomic_helper_page_flip, >>> +   .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, >>> +   .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, >>> +   .get_vblank_counter = lsdc_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, >>> +}; >>> + >>> +static enum drm_mode_status >>> +lsdc_crtc_mode_valid(struct drm_crtc *crtc, >>> +            const struct drm_display_mode *mode) >>> +{ >>> +   struct drm_device *ddev = crtc->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   if (mode->hdisplay > descp->max_width) >>> +       return MODE_BAD_HVALUE; >>> +   if (mode->vdisplay > descp->max_height) >>> +       return MODE_BAD_VVALUE; >>> + >>> +   if (mode->clock > descp->max_pixel_clk) { >>> +       drm_dbg(ddev, "mode %dx%d, pixel clock=%d is too high\n", >>> +           mode->hdisplay, mode->vdisplay, mode->clock); >>> +       return MODE_CLOCK_HIGH; >>> +   } >>> + >>> +   if ((mode->hdisplay * 4) % descp->pitch_align) { >>> +       drm_dbg(ddev, "stride is require to not %u bytes aligned\n", >>> +           descp->pitch_align); >>> +       return MODE_BAD; >>> +   } >>> + >>> +   return MODE_OK; >>> +} >>> + >>> +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc, >>> +                   struct drm_crtc_state *state) >>> +{ >>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>> +   struct lsdc_pll *pixpll = &dispipe->pixpll; >>> +   const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; >>> +   struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); >>> +   bool ret; >>> + >>> +   ret = pfuncs->compute(pixpll, state->mode.clock, >>> &priv_state->pparms); >>> +   if (ret) >>> +       return 0; >>> + >>> +   drm_warn(crtc->dev, "failed find PLL parameters for %u\n", >>> state->mode.clock); >>> + >>> +   return -EINVAL; >>> +} >>> + >>> +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; /* no mode checks if CRTC is being disabled */ >>> + >>> +   return lsdc_pixpll_atomic_check(crtc, crtc_state); >>> +} >>> + >>> +static void lsdc_update_pixclk(struct lsdc_display_pipe *dispipe, >>> +                  struct lsdc_crtc_state *priv_state) >>> +{ >>> +   struct lsdc_pll *pixpll = &dispipe->pixpll; >>> +   const struct lsdc_pixpll_funcs *clkfunc = pixpll->funcs; >>> + >>> +   clkfunc->update(pixpll, &priv_state->pparms); >>> +} >>> + >>> +static void lsdc_crtc_helper_mode_set_nofb(struct drm_crtc *crtc) >>> +{ >>> +   struct drm_device *ddev = crtc->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   struct drm_display_mode *mode = &crtc->state->mode; >>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>> +   struct lsdc_crtc_state *priv_state = >>> to_lsdc_crtc_state(crtc->state); >>> +   unsigned int index = dispipe->index; >>> +   u32 h_sync, v_sync, h_val, v_val; >>> +   u32 val; >>> + >>> +   /* 26:16 total pixels, 10:0 visiable pixels, in horizontal */ >>> +   h_val = (mode->crtc_htotal << 16) | mode->crtc_hdisplay; >>> + >>> +   /* 26:16 total pixels, 10:0 visiable pixels, in vertical */ >>> +   v_val = (mode->crtc_vtotal << 16) | mode->crtc_vdisplay; >>> +   /* 26:16 hsync end, 10:0 hsync start, bit 30 is hsync enable */ >>> +   h_sync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | >>> CFG_HSYNC_EN; >>> +   if (mode->flags & DRM_MODE_FLAG_NHSYNC) >>> +       h_sync |= CFG_HSYNC_INV; >>> + >>> +   /* 26:16 vsync end, 10:0 vsync start, bit 30 is vsync enable */ >>> +   v_sync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | >>> CFG_VSYNC_EN; >>> +   if (mode->flags & DRM_MODE_FLAG_NVSYNC) >>> +       v_sync |= CFG_VSYNC_INV; >>> + >>> +   if (index == 0) { >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG, h_val); >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG, v_val); >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG, h_sync); >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG, v_sync); >>> + >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>> +       val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>> +   } else if (index == 1) { >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_HDISPLAY_REG, h_val); >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_VDISPLAY_REG, v_val); >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_HSYNC_REG, h_sync); >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_VSYNC_REG, v_sync); >>> + >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>> +       val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>> +   } >> >> Same here as with vblank: have one helper for each index and use >> different _funcs structures for each pipeline. >> > Well, I will revise lsdc driver with this idea in mind. >>> + >>> +   drm_dbg(ddev, "%s modeset: %ux%u\n", >>> +       crtc->name, mode->hdisplay, mode->vdisplay); >>> + >>> +   lsdc_update_pixclk(dispipe, priv_state); >>> +} >>> + >>> +static void lsdc_enable_display(struct lsdc_device *ldev, unsigned >>> int index) >>> +{ >>> +   u32 val; >>> + >>> +   if (index == 0) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>> +       val |= CFG_OUTPUT_EN; >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>> +       return; >>> +   } >>> + >>> +   if (index == 1) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>> +       val |= CFG_OUTPUT_EN; >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>> +       return; >>> +   } >>> +} >>> + >>> +static void lsdc_disable_display(struct lsdc_device *ldev, unsigned >>> int index) >>> +{ >>> +   u32 val; >>> + >>> +   if (index == 0) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>> +       val &= ~CFG_OUTPUT_EN; >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>> +       return; >>> +   } >>> + >>> +   if (index == 1) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>> +       val &= ~CFG_OUTPUT_EN; >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>> +       return; >>> +   } >>> +} >>> + >>> +static void lsdc_crtc_helper_atomic_enable(struct drm_crtc *crtc, >>> +                      struct drm_atomic_state *state) >>> +{ >>> +   struct drm_device *ddev = crtc->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> + >>> +   drm_crtc_vblank_on(crtc); >>> + >>> +   lsdc_enable_display(ldev, drm_crtc_index(crtc)); >>> + >>> +   drm_dbg(ddev, "%s: enabled\n", crtc->name); >>> +} >>> + >>> +static void lsdc_crtc_helper_atomic_disable(struct drm_crtc *crtc, >>> +                       struct drm_atomic_state *state) >>> +{ >>> +   struct drm_device *ddev = crtc->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> + >>> +   lsdc_disable_display(ldev, drm_crtc_index(crtc)); >>> + >>> +   drm_crtc_wait_one_vblank(crtc); >>> + >>> +   drm_crtc_vblank_off(crtc); >>> + >>> +   drm_dbg(ddev, "%s: disabled\n", crtc->name); >>> +} >>> + >>> +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 drm_device *ddev = crtc->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   unsigned int index = drm_crtc_index(crtc); >>> +   int line, vsw, vbp, vactive_start, vactive_end, vfp_end; >>> +   u32 val = 0; >>> + >>> +   vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; >>> +   vbp = mode->crtc_vtotal - mode->crtc_vsync_end; >>> + >>> +   vactive_start = vsw + vbp + 1; >>> + >>> +   vactive_end = vactive_start + mode->crtc_vdisplay; >>> + >>> +   /* last scan line before VSYNC */ >>> +   vfp_end = mode->crtc_vtotal; >>> + >>> +   if (stime) >>> +       *stime = ktime_get(); >>> + >>> +   if (index == 0) >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); >>> +   else if (index == 1) >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); >>> + >>> +   line = (val & 0xffff); >>> + >>> +   if (line < vactive_start) >>> +       line -= vactive_start; >>> +   else if (line > vactive_end) >>> +       line = line - vfp_end - vactive_start; >>> +   else >>> +       line -= vactive_start; >>> + >>> +   *vpos = line; >>> +   *hpos = val >> 16; >>> + >>> +   if (etime) >>> +       *etime = ktime_get(); >>> + >>> +   return true; >>> +} >>> + >>> +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { >>> +   .mode_valid = lsdc_crtc_mode_valid, >>> +   .mode_set_nofb = lsdc_crtc_helper_mode_set_nofb, >>> +   .atomic_enable = lsdc_crtc_helper_atomic_enable, >>> +   .atomic_disable = lsdc_crtc_helper_atomic_disable, >>> +   .atomic_check = lsdc_crtc_helper_atomic_check, >>> +   .atomic_flush = lsdc_crtc_atomic_flush, >>> +   .get_scanout_position = lsdc_crtc_get_scanout_position, >>> +}; >>> + >>> +int lsdc_crtc_init(struct drm_device *ddev, >>> +          struct drm_crtc *crtc, >>> +          unsigned int index, >>> +          struct drm_plane *primary, >>> +          struct drm_plane *cursor) >>> +{ >>> +   int ret; >>> + >>> +   ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor, >>> +                   &lsdc_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) >>> +       drm_warn(ddev, "set the gamma table size failed\n"); >>> + >>> +   drm_crtc_enable_color_mgmt(crtc, 0, false, 256); >> >> Maybe leave this out for now. You need to handle the property in your >> driver. I don't see code for that. >> >> Color management should probably go into a separate patch. >> > Ok, I'm agree to leave gamma support to a separate patch >>> + >>> +   return 0; >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>> b/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>> new file mode 100644 >>> index 000000000000..176ae4e11d1c >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>> @@ -0,0 +1,191 @@ >>> +// SPDX-License-Identifier: GPL-2.0+ >>> + >>> +#include <drm/drm_debugfs.h> >>> +#include <drm/drm_managed.h> >>> +#include <drm/drm_gem_vram_helper.h> >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> +#include "lsdc_pll.h" >>> + >>> +#ifdef CONFIG_DEBUG_FS >>> +static int lsdc_show_clock(struct seq_file *m, void *arg) >>> +{ >>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>> +   struct drm_device *ddev = node->minor->dev; >>> +   struct drm_crtc *crtc; >>> + >>> +   drm_for_each_crtc(crtc, ddev) { >>> +       struct lsdc_display_pipe *pipe = crtc_to_display_pipe(crtc); >>> +       struct lsdc_pll *pixpll = &pipe->pixpll; >>> +       const struct lsdc_pixpll_funcs *funcs = pixpll->funcs; >>> +       struct drm_display_mode *adj = &crtc->state->mode; >>> +       struct lsdc_pll_parms parms; >>> +       unsigned int out_khz; >>> + >>> +       out_khz = funcs->get_clock_rate(pixpll, &parms); >>> + >>> +       seq_printf(m, "Display pipe %u: %dx%d\n", >>> +              pipe->index, adj->hdisplay, adj->vdisplay); >>> + >>> +       seq_printf(m, "Frequency actually output: %u kHz\n", out_khz); >>> +       seq_printf(m, "Pixel clock required: %d kHz\n", adj->clock); >>> +       seq_printf(m, "diff: %d kHz\n", adj->clock); >>> + >>> +       seq_printf(m, "div_ref=%u, loopc=%u, div_out=%u\n", >>> +              parms.div_ref, parms.loopc, parms.div_out); >>> + >>> +       seq_printf(m, "hsync_start=%d, hsync_end=%d, htotal=%d\n", >>> +              adj->hsync_start, adj->hsync_end, adj->htotal); >>> +       seq_printf(m, "vsync_start=%d, vsync_end=%d, vtotal=%d\n\n", >>> +              adj->vsync_start, adj->vsync_end, adj->vtotal); >>> +   } >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_show_mm(struct seq_file *m, void *arg) >>> +{ >>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>> +   struct drm_device *ddev = node->minor->dev; >>> +   struct drm_printer p = drm_seq_file_printer(m); >>> + >>> + drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p); >>> + >>> +   return 0; >>> +} >>> + >>> +#define REGDEF(reg) { __stringify_1(LSDC_##reg##_REG), >>> LSDC_##reg##_REG } >>> +static const struct { >>> +   const char *name; >>> +   u32 reg_offset; >>> +} lsdc_regs_array[] = { >>> +   REGDEF(CURSOR0_CFG), >>> +   REGDEF(CURSOR0_ADDR_LO), >>> +   REGDEF(CURSOR0_ADDR_HI), >>> +   REGDEF(CURSOR0_POSITION), >>> +   REGDEF(CURSOR0_BG_COLOR), >>> +   REGDEF(CURSOR0_FG_COLOR), >>> +   REGDEF(INT), >>> +   REGDEF(CRTC0_CFG), >>> +   REGDEF(CRTC0_STRIDE), >>> +   REGDEF(CRTC0_FB_ORIGIN), >>> +   REGDEF(CRTC0_HDISPLAY), >>> +   REGDEF(CRTC0_HSYNC), >>> +   REGDEF(CRTC0_VDISPLAY), >>> +   REGDEF(CRTC0_VSYNC), >>> +   REGDEF(CRTC0_GAMMA_INDEX), >>> +   REGDEF(CRTC0_GAMMA_DATA), >>> +   REGDEF(CRTC1_CFG), >>> +   REGDEF(CRTC1_STRIDE), >>> +   REGDEF(CRTC1_FB_ORIGIN), >>> +   REGDEF(CRTC1_HDISPLAY), >>> +   REGDEF(CRTC1_HSYNC), >>> +   REGDEF(CRTC1_VDISPLAY), >>> +   REGDEF(CRTC1_VSYNC), >>> +   REGDEF(CRTC1_GAMMA_INDEX), >>> +   REGDEF(CRTC1_GAMMA_DATA), >>> +}; >>> + >>> +static int lsdc_show_regs(struct seq_file *m, void *arg) >>> +{ >>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>> +   struct drm_device *ddev = node->minor->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   int i; >>> + >>> +   for (i = 0; i < ARRAY_SIZE(lsdc_regs_array); i++) { >>> +       u32 offset = lsdc_regs_array[i].reg_offset; >>> +       const char *name = lsdc_regs_array[i].name; >>> + >>> +       seq_printf(m, "%s (0x%04x): 0x%08x\n", >>> +              name, offset, lsdc_rreg32(ldev, offset)); >>> +   } >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_show_vblank_counter(struct seq_file *m, void *arg) >>> +{ >>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>> +   struct drm_device *ddev = node->minor->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> + >>> +   seq_printf(m, "CRTC-0 vblank counter: %08u\n", >>> +          lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG)); >>> + >>> +   seq_printf(m, "CRTC-1 vblank counter: %08u\n", >>> +          lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG)); >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_show_scan_position(struct seq_file *m, void *arg) >>> +{ >>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>> +   struct drm_device *ddev = node->minor->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   u32 val0 = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); >>> +   u32 val1 = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); >>> + >>> +   seq_printf(m, "CRTC-0: x: %08u, y: %08u\n", >>> +          val0 >> 16, val0 & 0xFFFF); >>> + >>> +   seq_printf(m, "CRTC-1: x: %08u, y: %08u\n", >>> +          val1 >> 16, val1 & 0xFFFF); >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_show_fb_addr(struct seq_file *m, void *arg) >>> +{ >>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>> +   struct drm_device *ddev = node->minor->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   u32 lo, hi; >>> +   u32 val; >>> + >>> +   val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>> +   if (val & CFG_FB_IN_USING) { >>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_LO_ADDR_REG); >>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_HI_ADDR_REG); >>> +       seq_printf(m, "CRTC-0 using fb1: 0x%x:%x\n", hi, lo); >>> +   } else { >>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_LO_ADDR_REG); >>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_HI_ADDR_REG); >>> +       seq_printf(m, "CRTC-0 using fb0: 0x%x:%x\n", hi, lo); >>> +   } >>> + >>> +   val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>> +   if (val & CFG_FB_IN_USING) { >>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_LO_ADDR_REG); >>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_HI_ADDR_REG); >>> +       seq_printf(m, "CRTC-1 using fb1: 0x%x:%x\n", hi, lo); >>> +   } else { >>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_LO_ADDR_REG); >>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_HI_ADDR_REG); >>> +       seq_printf(m, "CRTC-1 using fb0: 0x%x:%x\n", hi, lo); >>> +   } >>> + >>> +   return 0; >>> +} >>> + >>> +static struct drm_info_list lsdc_debugfs_list[] = { >>> +   { "clocks",  lsdc_show_clock, 0 }, >>> +   { "mm",      lsdc_show_mm, 0, NULL }, >>> +   { "regs",    lsdc_show_regs, 0 }, >>> +   { "vblanks", lsdc_show_vblank_counter, 0, NULL }, >>> +   { "scan_pos", lsdc_show_scan_position, 0, NULL }, >>> +   { "fb_addr", lsdc_show_fb_addr, 0, NULL }, >>> +}; >>> + >>> +#endif >>> + >>> +void lsdc_debugfs_init(struct drm_minor *minor) >>> +{ >>> +#ifdef CONFIG_DEBUG_FS >>> +   drm_debugfs_create_files(lsdc_debugfs_list, >>> +                ARRAY_SIZE(lsdc_debugfs_list), >>> +                minor->debugfs_root, >>> +                minor); >>> +#endif >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.c >>> b/drivers/gpu/drm/lsdc/lsdc_drv.c >>> new file mode 100644 >>> index 000000000000..3bd457e6e980 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.c >>> @@ -0,0 +1,635 @@ >>> +// SPDX-License-Identifier: GPL-2.0+ >>> +/* >>> + * KMS driver for Loongson display controller >>> + * Copyright (C) 2022 Loongson Corporation >>> + * >>> + * Authors: >>> + *     Li Yi <liyi@loongson.cn> >>> + *     Li Chenyang <lichenyang@loongson.cn> >>> + *     Sui Jingfeng <suijingfeng@loongson.cn> >>> + */ >>> + >>> +#include <linux/module.h> >>> +#include <linux/pci.h> >>> +#include <linux/of_address.h> >>> +#include <drm/drm_aperture.h> >>> +#include <drm/drm_vblank.h> >>> +#include <drm/drm_fb_helper.h> >>> +#include <drm/drm_gem_vram_helper.h> >>> +#include <drm/drm_gem_framebuffer_helper.h> >>> +#include <drm/drm_modeset_helper.h> >>> +#include <drm/drm_atomic_helper.h> >>> +#include <drm/drm_probe_helper.h> >>> +#include <drm/drm_fbdev_generic.h> >>> +#include "lsdc_drv.h" >> >> Alphebetical order. > I will fix this problem at next version >> >>> + >>> +static int lsdc_chip = -1; >>> +MODULE_PARM_DESC(chip, "override chip id (-1 = probed by driver"); >>> +module_param_named(chip, lsdc_chip, int, 0644); >> >> That's a development option. Better not add it to the published driver. > > Without this, compile with W=1 generate warnings. and removing chip > descriptions for Loongson SoC > > is not wanted either. this option can be removed if DT support is > upstream in the future. I'd strongly prefer to rely on DT for this. Because this setting is nothing that we want users to modify. > > It allow us to developing driver before DT or ACPI support is ready. > Otherwise we have no way developing > > drivers for loongson SoC beforehand. I understand, I just don't think that the parameter should be in the official Linux kernel. It's an option for your internal development team. It does not make sense to merge the DRM driver before the DT or ACPI support is ready. > >>> + >>> +/* >>> + * ls7a1000 and ls7a2000 is not only used with loongson desktop >>> class CPU >>> + * but also loongson server class CPUs (LS3C5000@2.2GHz 16 core). >>> Loongson >>> + * server products generally don't has a dedicated vram mounted for >>> cost >>> + * reason, it will use aspeed bmc as its display on its final and >>> formal >>> + * product. lsdc.has_vram=0 is mainly be use in developing phase of the >>> + * product. When a board don't has a dedicated vram mounted, we can put >>> + * the framebuffer on system RAM. >>> + */ >>> +static int lsdc_has_vram = -1; >>> +MODULE_PARM_DESC(has_vram, "has on-board gpu memory in reality (0 = >>> no, -1 = default"); >>> +module_param_named(has_vram, lsdc_has_vram, int, 0644); >> >> Same here. That's for development. > > Err, we truly very need this feature, > > 1) ASPEED BMC card is removable, it can be unplug from the mother board > of Loongson Server. > > leaving this option there, we still have change enter X server graphic > environment. Why don't you detect the presence of the BMC card at runtime. > > > 2) We need developing drivers for loongson SoC on loongson PC platform, > we may deliberately don't > > use on-board vram. there a need to let TTM manage cached reserved RAM, > because such device don't > > have vram. We have achieve this goal on ls2k2000 SoC. > > Otherwise we have to write two piece of driver, one is DMA helper base, > the other is TTM based, > > we doing so at downstream kernel. I don't understand. You can still have an internal patch that provides this option to your development team. The problem with module options is that they are hard to remove. Users start relying on them and complain when you remove them. That happens even if you provide a better solution. And some options, such as the chip setting, can possibly damage the user's hardware. So it's best to not provide options in the first place. > >> >>> + >>> +#define DRIVER_AUTHOR       "Sui Jingfeng <suijingfeng@loongson.cn>" >>> +#define DRIVER_NAME       "lsdc" >>> +#define DRIVER_DESC       "drm driver for loongson's display >>> controller" >>> +#define DRIVER_DATE       "20220701" >>> +#define DRIVER_MAJOR       1 >>> +#define DRIVER_MINOR       0 >>> +#define DRIVER_PATCHLEVEL   0 >>> + >>> +static const struct lsdc_desc dc_in_ls2k0500 = { >> >> Those description structs are a two-edged sword IMHO. >> >>> +   .chip = CHIP_LS2K0500, >>> +   .num_of_crtc = LSDC_NUM_CRTC, >>> +   .max_pixel_clk = 200000, >>> +   .max_width = 2048, >>> +   .max_height = 2048, >>> +   .hw_cursor_w = 32, >>> +   .hw_cursor_h = 32, >>> +   .pitch_align = 256, >>> +   .mc_bits = 40, >> >> These are device constants are probably fine. >> >>> +   .num_of_hw_cursor = 1, >>> +   .has_vblank_counter = false, >>> +   .has_builtin_i2c = false, >>> +   .has_vram = false, >>> +   .has_hpd_reg = false, >>> +   .is_soc = true, >> >> But from a quick look through the code, these settings affect control >> flow and/or modesetting pipelines. They are often better implemented >> by dedicated helpers as outlined in my comments about vblank and mgag200. > > Ok, mgag200 is for server mainly, its variant are not so different as > loongson does, > > at least they all have vram, we have to put more attention to turn drm > memory manager. > > I will following your advice by introducing more helpers at next version. > >> >> >>> +}; >>> + >>> +static const struct lsdc_desc dc_in_ls2k1000 = { >>> +   .chip = CHIP_LS2K1000, >>> +   .num_of_crtc = LSDC_NUM_CRTC, >>> +   .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, >>> +   .has_builtin_i2c = false, >>> +   .has_vram = false, >>> +   .has_hpd_reg = false, >>> +   .is_soc = true, >>> +}; >>> + >>> +static const struct lsdc_desc dc_in_ls2k2000 = { >>> +   .chip = CHIP_LS2K2000, >>> +   .num_of_crtc = LSDC_NUM_CRTC, >>> +   .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 = 256, >>> +   .mc_bits = 40, >>> +   .has_vblank_counter = true, >>> +   .has_builtin_i2c = true, >>> +   .has_vram = false, >>> +   .has_hpd_reg = true, >>> +   .is_soc = true, >>> +}; >>> + >>> +static const struct lsdc_desc dc_in_ls7a1000 = { >>> +   .chip = CHIP_LS7A1000, >>> +   .num_of_crtc = LSDC_NUM_CRTC, >>> +   .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, >>> +   .has_builtin_i2c = true, >>> +   .has_vram = true, >>> +   .has_hpd_reg = false, >>> +   .is_soc = false, >>> +}; >>> + >>> +static const struct lsdc_desc dc_in_ls7a2000 = { >>> +   .chip = CHIP_LS7A2000, >>> +   .num_of_crtc = LSDC_NUM_CRTC, >>> +   .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 = 256, >>> +   .mc_bits = 40, /* support 48, but use 40 for backward >>> compatibility */ >>> +   .has_vblank_counter = true, >>> +   .has_builtin_i2c = true, >>> +   .has_vram = true, >>> +   .has_hpd_reg = true, >>> +   .is_soc = false, >>> +}; >>> + >>> +static int lsdc_gem_dumb_create(struct drm_file *file, >>> +               struct drm_device *ddev, >>> +               struct drm_mode_create_dumb *args) >>> +{ >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   return drm_gem_vram_fill_create_dumb(file, ddev, 0, >>> descp->pitch_align, args); >>> +} >>> + >>> +DEFINE_DRM_GEM_FOPS(lsdc_gem_fops); >>> + >>> +static const struct drm_driver lsdc_drm_driver = { >>> +   .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, >>> +   .fops = &lsdc_gem_fops, >>> + >>> +   .name = DRIVER_NAME, >>> +   .desc = DRIVER_DESC, >>> +   .date = DRIVER_DATE, >>> +   .major = DRIVER_MAJOR, >>> +   .minor = DRIVER_MINOR, >>> +   .patchlevel = DRIVER_PATCHLEVEL, >>> + >>> +   .debugfs_init = lsdc_debugfs_init, >>> +   .dumb_create = lsdc_gem_dumb_create, >>> +   .dumb_map_offset = drm_gem_ttm_dumb_map_offset, >>> +   .gem_prime_mmap = drm_gem_prime_mmap, >>> +}; >>> + >>> +static const struct drm_mode_config_funcs lsdc_mode_config_funcs = { >>> +   .fb_create = drm_gem_fb_create, >>> +   .output_poll_changed = drm_fb_helper_output_poll_changed, >>> +   .atomic_check = drm_atomic_helper_check, >>> +   .atomic_commit = drm_atomic_helper_commit, >>> +   .mode_valid = drm_vram_helper_mode_valid, >>> +}; >>> + >>> +static int lsdc_modeset_init(struct lsdc_device *ldev, >>> +                const struct lsdc_desc *descp) >>> +{ >>> +   struct drm_device *ddev = &ldev->base; >>> +   unsigned int num_crtc = descp->num_of_crtc; >>> +   unsigned int i; >>> +   int ret; >>> + >>> +   for (i = 0; i < num_crtc; i++) { >>> +       ret = lsdc_create_output(ldev, i); >>> +       if (ret) >>> +           return ret; >>> +   } >>> + >>> +   for (i = 0; i < num_crtc; i++) { >>> +       struct lsdc_display_pipe *dispipe = &ldev->dispipe[i]; >>> +       struct lsdc_pll *pixpll = &dispipe->pixpll; >>> +       struct drm_plane *primary = &dispipe->primary; >>> +       struct drm_plane *cursor = &dispipe->cursor; >>> +       struct drm_crtc *crtc = &dispipe->crtc; >>> + >>> +       dispipe->index = i; >>> + >>> +       ret = lsdc_pixpll_init(pixpll, ddev, i); >>> +       if (ret) >>> +           return ret; >>> + >>> +       ret = lsdc_plane_init(ldev, primary, DRM_PLANE_TYPE_PRIMARY, >>> i); >>> +       if (ret) >>> +           return ret; >>> + >>> +       ret = lsdc_plane_init(ldev, cursor, DRM_PLANE_TYPE_CURSOR, i); >>> +       if (ret) >>> +           return ret; >>> + >>> +       /* >>> +        * Initial all of the CRTC available, in this way the logical >>> +        * crtc index is equal to the hardware crtc index. >>> +        * >>> +        * display pipe 0 = crtc0 + dvo0 + encoder0 + connector0 >>> +        * display pipe 1 = crtc1 + dvo1 + encoder1 + connector1 >>> +        */ >>> +       ret = lsdc_crtc_init(ddev, crtc, i, primary, cursor); >>> +       if (ret) >>> +           return ret; >>> + >>> +       drm_info(ddev, "display pipe %u initialized\n", i); >>> +   } >>> + >>> +   drm_mode_config_reset(ddev); >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_mode_config_init(struct drm_device *ddev, >>> +                const struct lsdc_desc *descp) >>> +{ >>> +   int ret; >>> + >>> +   ret = drmm_mode_config_init(ddev); >>> +   if (ret) >>> +       return ret; >>> + >>> +   ddev->mode_config.funcs = &lsdc_mode_config_funcs; >>> +   ddev->mode_config.min_width = 1; >>> +   ddev->mode_config.min_height = 1; >>> +   ddev->mode_config.max_width = descp->max_width * LSDC_NUM_CRTC; >>> +   ddev->mode_config.max_height = descp->max_height * LSDC_NUM_CRTC; >>> +   ddev->mode_config.preferred_depth = 24; >>> +   ddev->mode_config.prefer_shadow = descp->has_vram; >>> + >>> +   ddev->mode_config.cursor_width = descp->hw_cursor_h; >>> +   ddev->mode_config.cursor_height = descp->hw_cursor_h; >>> + >>> +   /* max_vblank_count is set on each CRTC */ >>> +   ddev->max_vblank_count = 0xffffffff; >>> + >>> +   return ret; >>> +} >>> + >>> +/* >>> + * lsdc_detect_chip - a function to tell lsdc variant apart. >>> + */ >>> +static const struct lsdc_desc * >>> +lsdc_detect_chip(struct pci_dev *pdev, const struct pci_device_id *ent) >>> +{ >>> +   /* >>> +    * Provide a way to false the driver running on new model >>> +    * before we have a proper way to tell them apart >>> +    */ >>> +   if (lsdc_chip == CHIP_LS2K2000) { >>> +       dev_info(&pdev->dev, "Override as LS2K2000\n"); >>> +       return &dc_in_ls2k2000; >>> +   } >>> + >>> +   if (lsdc_chip == CHIP_LS2K1000) { >>> +       dev_info(&pdev->dev, "Override as LS2K1000\n"); >>> +       return &dc_in_ls2k1000; >>> +   } >>> + >>> +   if (lsdc_chip == CHIP_LS2K0500) { >>> +       dev_info(&pdev->dev, "Override as LS2K0500\n"); >>> +       return &dc_in_ls2k0500; >>> +   } >>> + >>> +   if (ent) { >>> +       struct lsdc_desc *dc_descp; >>> + >>> +       dc_descp = (struct lsdc_desc *)ent->driver_data; >> >> Is that legal? driver_data is of kernel_ulong_t. Maybe store the CHIP_ >> constant here and then lookup the lsdc_desc. >> > It works in practice but it seems dangerous doing so, I will using > CHIP_ LS7AXXXX and CHIP_LS2KXXXX at new version. > > i don't notice it before, thank you. > >> >> https://elixir.bootlin.com/linux/latest/source/include/linux/mod_devicetable.h#L36 >> >>> +       if (dc_descp->chip == CHIP_LS7A1000) >>> +           dev_info(&pdev->dev, "LS7A1000 Found revision: %u\n", >>> pdev->revision); >>> +       else if (dc_descp->chip == CHIP_LS7A2000) >>> +           dev_info(&pdev->dev, "LS7A2000 Found revison: %u\n", >>> pdev->revision); >>> + >>> +       return dc_descp; >>> +   } >>> + >>> +   dev_err(&pdev->dev, "Not known device\n"); >>> + >>> +   return NULL; >>> +} >>> + >>> +static int lsdc_get_dedicated_vram(struct lsdc_device *ldev, >>> +                  const struct lsdc_desc *descp) >>> +{ >>> +   struct drm_device *ddev = &ldev->base; >>> +   struct pci_dev *gpu; >>> +   resource_size_t base, size; >>> + >>> +   /* >>> +    * The GPU and display controller in LS7A1000/LS7A2000 are >>> separated >>> +    * PCIE devices, they are two devices not one. The DC is a pci >>> device, >>> +    * but it don't have a dedicate VRAM bar, the BIOS engineer >>> choose to >>> +    * assign the VRAM to the gpu device. Sadly, after years >>> application, >>> +    * this decision form as a convention for loongson integrate >>> graphics. >>> +    * For LS7A1000 and LS7A2000, bar 2 of GPU device contain the VRAM, >>> +    * both the GPU and the DC can make use of the VRAM depend on >>> how DRM >>> +    * device driver is written. Therefore, we have to do some >>> tricks here. >>> +    */ >>> +   if (descp->chip == CHIP_LS7A1000) >>> +       gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A15, NULL); >>> +   else if (descp->chip == CHIP_LS7A2000) >>> +       gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A25, NULL); >>> + >>> +   if (!gpu) { >>> +       drm_warn(ddev, "No GPU device found\n"); >>> +       return -ENODEV; >>> +   } >>> + >>> +   base = pci_resource_start(gpu, 2); >>> +   size = pci_resource_len(gpu, 2); >>> + >>> +   ldev->vram_base = base; >>> +   ldev->vram_size = size; >>> + >>> +   drm_info(ddev, "dedicated vram start: 0x%llx, size: %uMB\n", >>> +        (u64)base, (u32)(size >> 20)); >>> + >>> +   return 0; >>> +} >>> + >>> +/* >>> + * Loongson display controller is not equipped with IOMMU. >>> + * In order to make this driver work with Loongson SoC, >>> + * you need to either carveout part RAM as VRAM or reserve >>> + * part system ram as vram. Pass the start address and size >>> + * via DT or ACPI. >>> + */ >>> +static int lsdc_of_get_reserved_ram(struct lsdc_device *ldev) >>> +{ >>> +   struct drm_device *ddev = &ldev->base; >>> +   unsigned long size = 0; >>> +   struct device_node *node; >>> +   struct resource r; >>> +   int ret; >>> + >>> +   node = of_parse_phandle(ddev->dev->of_node, "memory-region", 0); >>> +   if (!node) { >>> +       drm_err(ddev, "No memory-region property or no DT, abort\n"); >>> +       return -ENOENT; >>> +   } >>> + >>> +   ret = of_address_to_resource(node, 0, &r); >>> +   of_node_put(node); >>> +   if (ret) >>> +       return ret; >>> + >>> +   size = r.end - r.start + 1; >>> + >>> +   ldev->vram_base = r.start; >>> +   ldev->vram_size = size; >>> + >>> +   drm_info(ddev, "using VRAM carveout: %lx@%pa\n", size, &r.start); >>> + >>> +   return 0; >>> +} >>> + >>> +static int >>> +lsdc_remove_conflicting_framebuffers(const struct drm_driver *pdriver, >>> +                    resource_size_t base, >>> +                    resource_size_t size) >>> +{ >>> +   return drm_aperture_remove_conflicting_framebuffers(base, >>> +                               size, >>> +                               false, >>> +                               pdriver); >>> +} >> >> No need to wrap this single function. > > Ok, Loongson upstream kernel support efifb and it backing memory is > located at this range. > > we must properly remove it, otherwise they will have two fb > node(/dev/fb0 and /dev/fb1) Right, but you can call drm_aperture_remove_conflicting_framebuffers() directly. Best practice is to detect your device's video-memory range and clear use it as base and size values. In cases where you don't know, drm_aperture_remove_framebuffers() remove all conflicting drivers. > >> >>> + >>> +static struct lsdc_device * >>> +lsdc_create_device(struct pci_dev *pdev, >>> +          const struct pci_device_id *ent, >>> +          const struct drm_driver *drv) >>> +{ >>> +   struct lsdc_device *ldev; >>> +   struct drm_device *ddev; >>> +   const struct lsdc_desc *descp; >>> +   int ret; >>> + >>> +   ldev = devm_drm_dev_alloc(&pdev->dev, drv, struct lsdc_device, >>> base); >>> +   if (IS_ERR(ldev)) >>> +       return ldev; >>> + >>> +   ddev = &ldev->base; >>> + >>> +   pci_set_drvdata(pdev, ddev); >>> + >>> +   descp = lsdc_detect_chip(pdev, ent); >>> +   if (!descp) >>> +       return NULL; >>> + >>> +   ldev->descp = descp; >>> + >>> +   spin_lock_init(&ldev->reglock); >>> + >>> +   /* BAR 0 the DC device contains registers */ >>> +   ldev->reg_base = pcim_iomap(pdev, 0, 0); >>> +   if (!ldev->reg_base) >>> +       return ERR_PTR(-EIO); >>> + >>> +   if (descp->has_vram && lsdc_has_vram) >>> +       ret = lsdc_get_dedicated_vram(ldev, descp); >>> +   else >>> +       ret = lsdc_of_get_reserved_ram(ldev); >>> + >>> +   if (ret) { >>> +       drm_err(ddev, "Init VRAM failed: %d\n", ret); >>> +       return ERR_PTR(ret); >>> +   } >>> + >>> +   ret = lsdc_remove_conflicting_framebuffers(drv, >>> +                          ldev->vram_base, >>> +                          ldev->vram_size); >>> +   if (ret) { >>> +       drm_err(ddev, "remove firmware framebuffers failed: %d\n", >>> ret); >>> +       return ERR_PTR(ret); >>> +   } >>> + >>> +   ret = drmm_vram_helper_init(ddev, ldev->vram_base, >>> ldev->vram_size); >> >> Using GEM VRAM helpers could become a problem and I'd advise against >> them. >> > Err, I'm not want to agree this... >> First of all, how much video memory is there? VRAM helpers need at >> least 3 times the maximum consumption to be reliable.So if you have a >> FullHD display that consumes ~8 Mib, you'd need 24 MiB of VRAM. >> Hardware cursors can further interfere. > > Part of ls3a5000/ls3a4000+ls7a1000 machine have 64MB, another part of > ls3a5000/ls3a4000+ls7a1000 have 128 MB. > > For ls7a1000(1080p) the maximum usage actually is two > framebuffer(1920x1080x4x2) + two cursor (64x64x4x 2) + one fbcon > (1920x1080x4) and LS7A2000 board has at lease 256MB VRAM. > > Therefore I can guarantee that we have at least 64MB VRAM,enough for > double screen(1920x1080) + double cursor. Ok, sounds good. I expected some low-end devices, but 64 MiB should be fine. I still want to remove those VRAM helpers in the long term. My suggestion is that you simply reimplement the code in drm_gem_vram_helper.c in your driver. It's not that much and you can probably duplicate most of it. Please name everything according to your driver's convention. You should end up with a self-contained memory manager based on TTM, which you can adapt to your hardware's needs. > > I guess you means that there are some device(aspeed bmc) with limited > VRAM size, but it is more a problem > > of the device itself(It is not vram helper's fault). Early version of > ast driver would evict fbcon to system RAM > > to save VRAM usage, would this idea be a solution for the device with > limited vram capacity today? > > >> VRAM helpers also don't support buffer sharing with other devices. >> That's a problem for some compositors. >> >> For those reasons, VRAM helpers are deprecated. I'd rather remove them >> entirely. > > Its not that bad, VRAM helpers is good enough for our case as a first > step. Our colleague and customers mainly > > using MATE and XFCE under X server graphic environment, they need > basic 2D graphics environment most. > > We have tested our driver against macro and xfwm4, it actually works > very well. Individual compositor may > > have a few problems, but it isn't a thing for our case currently. Can we > solve those problem by other means? > > We could give up cross device buffer sharing support for chasing 2D > graphic performance , we intend to > > introducing buffer sharing with the help of the GPU with another patch > in the future. We possibly could > > switch to ttm-based KMS driver under your guiding then. For ls7a1000, I > imagine to merge lsdc with evnaviv > > into a single drm driver.May I suggest to postpone the decision of > removing VRAM helpers, its performance > > is better than shmem helper and have better support for double screen. As I outlined, please duplicate the VRAM helpers in you driver and adapt it to your needs. I really just want to avoid more users of the VRAM helpers. Building your driver upon TTM is perfectly fine. Since you have devices with VRAM and devices without VRAM, you should be able to handle both cases with TTM. TTM is flexible enough. You just need to configure it in the right way. For fbcon, using drm_fbdev_generic_setup() should be fine for now. If you implement your own TTM-based memory manager, you will also have the posibility of implementing a faster console. That's maybe something for a later patch. > > >> As a better alternative for the DC, you could use GEM SHMEM helpers. >> See mgag200 or ast for how to do that. > > We have two display pipe thus support double screen, therefore we have > two framebuffer BO and two cursor BO, > > we need to take clone screen, extend screen and rotate and resize case > into consideration, on clone screen case > > the two CRTC should scanout from the same address, while on extend > screen case, the frame buffer itself is grant > > one buffer if using modesetting driver, but we have two primary plane > thus two fb offset. Arrange VRAM bo offset > > for 4 different drm BO object manually is little bit hack and > cumbersome. I have already finish the developing > > work of shmem helper support, see: > https://github.com/loongson-gfx/drm-tip/compare/drm-tip...shmem > > But it is NOT as good as VRAM helper in the perspective of performance. > I think copy from the shadow to the > > front frame buffer should be done at user space, this can save the > overhead of reporting damage rectangle whenever > > have a damage. > > >> >> The comments mention that there's also a GPU. Does the GPU use VRAM? >> Or do GPU and DC share memory ranges? >> > LS7A2000 integrate Loongson self-made GPU, driver is developed by my > colleagues, it already works but my mature > > to upstream yet. LS7A1000 have a VIVANTE GC1000 v5037 (20120617 etc), a > ten years ago render-only GPU. > > the VRAM is connected with the ls7a1000 bridge chip, the memory > controller is also in ls7a1000 bridge chip itself. > > ATI call this type vram as sideport, my company call it as GMEM, I > think they are similar. > > etnaviv don't use VRAM currently, all BOs of etnaviv is in system ram. > > I think merge etnaviv and lsdc into a single drm driver would be a > solution, what do you think? I don't think etnaviv developers would like such mergers. We usually keep each driver separate from others and if we have multiple drivers with similar code, we try to build a helper library that covers all of them. Drivers can select from the helpers what they need. > > > On downstream environment, we hacking make ttm-based lsdc work with > etnaviv, by copying(sw resolve) > > the rendered buffer to vram backed framebuffer, glmark2 get 220 point, > quake3 arena can play, haha :) That's the important use case. :D > > It is achieved by developing xf86-video-loongson which using etnaviv to > share buffer between xserver and glmark2, > > lsdc(or downstream loongson-drm) is only need to implement a > self-sharing for userspace process . > > see https://github.com/loongson-gfx/xf86-video-loongson. The shared > rendering buffer is copy > > to front buffer with loongson LSX and LASX(256-bit) SIMD optimization. > Report dirty to kernel space is not need. > > we can even let suppertiled buffer rendered by etnaviv get exported, > xf86-video-loongson get the fd and prime > > it to handle, then we are using CPU to do the resolve work which > directly resolve to the front framebuffer. No damage > > rectangle reporting is need. > > > Hence, the 2D performance of graphics is the more important. GEM SHMEM > helpers is not better than vram helpers > > in the perspective of performance. > >>> +   if (ret) { >>> +       drm_err(ddev, "vram helper init failed: %d\n", ret); >>> +       goto err; >>> +   } >>> + >>> +   ret = lsdc_mode_config_init(ddev, descp); >>> +   if (ret) { >>> +       drm_dbg(ddev, "%s: %d\n", __func__, ret); >>> +       goto err; >>> +   } >>> + >>> +   ret = lsdc_modeset_init(ldev, descp); >>> +   if (ret) >>> +       goto err; >>> + >>> +   ret = drm_vblank_init(ddev, descp->num_of_crtc); >>> +   if (ret) >>> +       goto err; >>> + >>> +   ret = request_threaded_irq(pdev->irq, >>> +                  lsdc_get_irq_handler(ldev), >>> +                  lsdc_irq_thread_handler, >>> +                  IRQF_ONESHOT, >>> +                  dev_name(ddev->dev), >>> +                  ddev); >>> +   if (ret) { >>> +       drm_err(ddev, "Failed to register lsdc interrupt\n"); >>> +       goto err; >>> +   } >>> + >>> +   drm_kms_helper_poll_init(ddev); >>> + >>> +   return ldev; >>> + >>> +err: >>> +   return ERR_PTR(ret); >>> +} >>> + >>> +static int lsdc_pci_probe(struct pci_dev *pdev, >>> +             const struct pci_device_id *ent) >>> +{ >>> +   struct drm_device *ddev; >>> +   struct lsdc_device *ldev; >>> +   int ret; >>> + >>> +   ret = pcim_enable_device(pdev); >>> +   if (ret) >>> +       return ret; >>> + >>> +   pci_set_master(pdev); >>> + >>> +   ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); >>> +   if (ret) >>> +       return ret; >>> + >>> +   ldev = lsdc_create_device(pdev, ent, &lsdc_drm_driver); >>> +   if (IS_ERR(ldev)) >>> +       return PTR_ERR(ldev); >>> + >>> +   ddev = &ldev->base; >>> + >>> +   ret = drm_dev_register(ddev, 0); >>> +   if (ret) >>> +       return ret; >>> + >>> +   drm_fbdev_generic_setup(ddev, 32); >>> + >>> +   return 0; >>> +} >>> + >>> +static void lsdc_pci_remove(struct pci_dev *pdev) >>> +{ >>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>> + >>> +   drm_dev_unregister(ddev); >>> +   drm_atomic_helper_shutdown(ddev); >>> +} >>> + >>> +static int lsdc_drm_freeze(struct drm_device *ddev) >>> +{ >>> +   int error; >>> + >>> +   error = drm_mode_config_helper_suspend(ddev); >>> +   if (error) >>> +       return error; >>> + >>> +   pci_save_state(to_pci_dev(ddev->dev)); >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_drm_resume(struct device *dev) >>> +{ >>> +   struct pci_dev *pdev = to_pci_dev(dev); >>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>> + >>> +   return drm_mode_config_helper_resume(ddev); >>> +} >>> + >>> +static int lsdc_pm_freeze(struct device *dev) >>> +{ >>> +   struct pci_dev *pdev = to_pci_dev(dev); >>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>> + >>> +   return lsdc_drm_freeze(ddev); >>> +} >>> + >>> +static int lsdc_pm_thaw(struct device *dev) >>> +{ >>> +   return lsdc_drm_resume(dev); >>> +} >>> + >>> +static int lsdc_pm_suspend(struct device *dev) >>> +{ >>> +   struct pci_dev *pdev = to_pci_dev(dev); >>> +   int error; >>> + >>> +   error = lsdc_pm_freeze(dev); >>> +   if (error) >>> +       return error; >>> + >>> +   pci_save_state(pdev); >>> +   /* Shut down the device */ >>> +   pci_disable_device(pdev); >>> +   pci_set_power_state(pdev, PCI_D3hot); >>> + >>> +   return 0; >>> +} >>> + >>> +static int lsdc_pm_resume(struct device *dev) >>> +{ >>> +   struct pci_dev *pdev = to_pci_dev(dev); >>> + >>> +   if (pcim_enable_device(pdev)) >>> +       return -EIO; >>> + >>> +   pci_set_power_state(pdev, PCI_D0); >>> + >>> +   pci_restore_state(pdev); >>> + >>> +   return lsdc_pm_thaw(dev); >>> +} >>> + >>> +static const struct dev_pm_ops lsdc_pm_ops = { >>> +   .suspend = lsdc_pm_suspend, >>> +   .resume = lsdc_pm_resume, >>> +   .freeze = lsdc_pm_freeze, >>> +   .thaw = lsdc_pm_thaw, >>> +   .poweroff = lsdc_pm_freeze, >>> +   .restore = lsdc_pm_resume, >>> +}; >>> + >>> +static const struct pci_device_id lsdc_pciid_list[] = { >>> +   {PCI_VENDOR_ID_LOONGSON, 0x7a06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, >>> +    (kernel_ulong_t)&dc_in_ls7a1000}, >>> +   {PCI_VENDOR_ID_LOONGSON, 0x7a36, PCI_ANY_ID, PCI_ANY_ID, 0, 0, >>> +    (kernel_ulong_t)&dc_in_ls7a2000}, >>> +   {0, 0, 0, 0, 0, 0, 0} >>> +}; >>> + >>> +static struct pci_driver lsdc_pci_driver = { >>> +   .name = DRIVER_NAME, >>> +   .id_table = lsdc_pciid_list, >>> +   .probe = lsdc_pci_probe, >>> +   .remove = lsdc_pci_remove, >>> +   .driver.pm = &lsdc_pm_ops, >>> +}; >>> + >>> +static int __init lsdc_module_init(void) >>> +{ >>> +   struct pci_dev *pdev = NULL; >>> + >>> +   if (drm_firmware_drivers_only()) >>> +       return -EINVAL; >> >>  return -ENODEV; > > OK, no problem, I will fix it at new version. I remember i already fix > this, sorry. > >> >>> + >>> +   while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) { >>> +       /* >>> +        * Multiple video card workaround: lsdc will always be >>> +        * selected as the default boot device by vgaarb subsystem. >>> +        */ >>> +       if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) { >>> +           pr_info("Discrete graphic card detected, abort\n"); >>> +           return 0; >>> +       } >>> +   } >> >> I don't understand this code. You are not registering your driver if >> there's another graphics card? >> > Yes, we do not want to registering this driver if there's discrete > graphics card or BMC in the system. > > > for example: I read through the examples, but still find it strange. No other driver does this. Maybe keep the code for now, but I'd like to hear the opinion of other DRM developers on this. I'd expect that there's a better solution to this. > > 1) Loongson desktop computer(ls3a5000 + ls7a2000 with amdgpu rx580 > mounted) > > On such a configuration, loongson display controller and rx580 co-exist > in the system. > > As discrete GPU is more powerful, it seems that there no need to using > LSDC driver anymore. > > The user is most likely want to using the rx580 as primary GPU and > Using rx580 alone is enough for user in such a case. > > It is introduce unnecessary problems and confusion If we leave lsdc > driver live on the system together with amdgpu/ati drm driver. > > > I have just tested the case which leave lsdc driver live on the system > together with radeon drm driver. > > the monitor connected with lsdc is black, not working as a output slave. > > > 2) Loongson ls3c5000 server with aspeed 2400/25000 BMC card mounted. > > The BMC provide remote access and control besides the graphics, use it > alone is enough, we have solve > > 4 ~ 5 bugs on our product side. we don't want make a mess. > > our tester get confused, than find a amdgpu and plug in, then he get > three video co-exist in the sistem. > > lsdc, ast and amdgpu, there /dev/dri/card0, /dev/dri/car1 and > /dev/dri/card2. > > if i bring etnaviv up, then i can get four. > > >> Best regards >> Thomas >> > Thinks your valuable advice and guiding, we will take time to fix all > the problems your mentioned, No problem. I think I mentioned that it wouldn't make sense to merge the DRM driver before ACPI and/or DT are ready. So we have time to get the driver into good shape. Best regards Thomas > > and prepare next version. > >>> + >>> +   return pci_register_driver(&lsdc_pci_driver); >>> +} >>> +module_init(lsdc_module_init); >>> + >>> +static void __exit lsdc_module_exit(void) >>> +{ >>> +   pci_unregister_driver(&lsdc_pci_driver); >>> +} >>> +module_exit(lsdc_module_exit); >>> + >>> +MODULE_DEVICE_TABLE(pci, lsdc_pciid_list); >>> +MODULE_AUTHOR(DRIVER_AUTHOR); >>> +MODULE_DESCRIPTION(DRIVER_DESC); >>> +MODULE_LICENSE("GPL"); >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.h >>> b/drivers/gpu/drm/lsdc/lsdc_drv.h >>> new file mode 100644 >>> index 000000000000..d67dae0414ca >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.h >>> @@ -0,0 +1,274 @@ >>> +/* SPDX-License-Identifier: GPL-2.0 */ >>> +/* >>> + * KMS driver for Loongson display controller >>> + * Copyright (C) 2022 Loongson Corporation >>> + * >>> + * Authors: >>> + *     Li Yi <liyi@loongson.cn> >>> + *     Li Chenyang <lichenyang@loongson.cn> >>> + *     Sui Jingfeng <suijingfeng@loongson.cn> >>> + */ >>> + >>> +#ifndef __LSDC_DRV_H__ >>> +#define __LSDC_DRV_H__ >>> + >>> +#include <linux/i2c.h> >>> +#include <linux/i2c-algo-bit.h> >>> + >>> +#include <drm/drm_print.h> >>> +#include <drm/drm_device.h> >>> +#include <drm/drm_crtc.h> >>> +#include <drm/drm_plane.h> >>> +#include <drm/drm_connector.h> >>> +#include <drm/drm_encoder.h> >>> +#include <drm/drm_drv.h> >>> +#include <drm/drm_atomic.h> >>> + >>> +#include "lsdc_regs.h" >>> +#include "lsdc_pll.h" >>> + >>> +#define LSDC_NUM_CRTC          2 >>> + >>> +/* >>> + * The display controller in LS7A2000 integrate three loongson >>> self-made >>> + * encoder. Display pipe 0 has a transparent VGA encoder and a HDMI >>> phy, >>> + * they are parallel. Display pipe 1 has only one HDMI phy. >>> + *      ______________________ _____________ >>> + *     |            +-----+ | |            | >>> + *     | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA >>> Monitor |<---+ >>> + *     |       |   +-----+ | |_____________|   | >>> + *     |       |            | ______________   | >>> + *     |       |   +------+ | |             |  | >>> + *     |       +--> | HDMI | ----> HDMI Connector --> | HDMI >>> Monitor |<--+ >>> + *     |            +------+ | |______________|  | >>> + *     |           +------+ >>> |                                          | >>> + *     |           | i2c6 | >>> <-------------------------------------------+ >>> + *     |           +------+ | >>> + *     |                     | >>> + *     |   DC in LS7A2000   | >>> + *     |                     | >>> + *     |           +------+ | >>> + *     |           | i2c7 | <--------------------------------+ >>> + *     |           +------+ | | >>> + *     |                     | ______|_______ >>> + *     |           +------+ | |             | >>> + *     | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI >>> Monitor | >>> + *     |           +------+ | |______________| >>> + *     |______________________| >>> + * >>> + * >>> + * The display controller in LS7A1000 integrate two-way DVO, external >>> + * encoder is required except use directly with dpi(rgb888) panel. >>> + * ___________________ _________ >>> + *     |           -------| |        | >>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>> Display | >>> + *     | _  _    -------|       ^            ^ |_________| >>> + *     | | | | |   -------|       |            | >>> + *     | |_| |_|   | i2c6 <--------+-------------+ >>> + *     |           -------| >>> + *     | _  _    -------| >>> + *     | | | | |   | i2c7 <--------+-------------+ >>> + *     | |_| |_|   -------|       | |            _________ >>> + *     |           -------|       |            | |        | >>> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | >>> Panel | >>> + *     |           -------| |_________| >>> + *     |___________________| >>> + * >>> + * >>> + * The display controller in LS2K1000. >>> + * ___________________ _________ >>> + *     |           -------| |        | >>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>> Display | >>> + *     | _  _    -------|       ^             ^ |_________| >>> + *     | | | | |          |       |             | >>> + *     | |_| |_|          |    +------+         | >>> + *     |                  <---->| i2c0 |<---------+ >>> + *     | DC in LS2K1000  |    +------+ >>> + *     | _  _           |    +------+ >>> + *     | | | | |          <---->| i2c1 |----------+ >>> + *     | |_| |_|          |    +------+ |           _________ >>> + *     |           -------|       |             | |        | >>> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | >>> Panel | >>> + *     |           -------| |_________| >>> + *     |___________________| >>> + * >>> + * >>> + * The display controller in LS2K0500, LS2K0500 has a built-in >>> transparent >>> + * VGA encoder which is connected to display pipe 1(CRTC1). >>> + * ___________________ _________ >>> + *     |           -------| |        | >>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>> Display | >>> + *     | _  _    -------|       ^             ^ |_________| >>> + *     | | | | |          |       |             | >>> + *     | |_| |_|          |    +------+         | >>> + *     |                  <---->| i2c4 |<---------+ >>> + *     | DC in LS2K0500  |    +------+ >>> + *     | _  _           |    +------+ >>> + *     | | | | |          <---->| i2c5 |-------------------+ >>> + *     | |_| |_|          |    +------+ ______|______ >>> + *     |          +-----+ | |            | >>> + *     | CRTC1 --> | VGA | ------------------------> | VGA Monitor | >>> + *     |          +-----+ | |_____________| >>> + *     |___________________| >>> + * >>> + * LS7A1000 and LS7A2000 are bridge chips, has dedicated Video RAM. >>> + * while LS2K2000/LS2K1000/LS2K0500 are SoC, they don't have dediacated >>> + * Video RAM. The dc in LS2K2000 is basicly same with the dc in >>> LS7A1000 >>> + * except that LS2K2000 don't have a video RAM and have only one >>> built-in >>> + * hdmi encoder. >>> + * >>> + * There is only a 1:1 mapping of encoders and connectors for the DC, >>> + * each CRTC have two FB address registers. >>> + */ >>> + >>> +enum loongson_chip_family { >>> +   CHIP_UNKNOWN = 0, >>> +   CHIP_LS7A1000 = 1, /* North bridge of >>> LS3A3000/LS3A4000/LS3A5000 */ >>> +   CHIP_LS7A2000 = 2, /* Update version of LS7A1000, with built-in >>> HDMI encoder */ >>> +   CHIP_LS2K0500 = 3, /* Reduced version of LS2K1000, single core */ >>> +   CHIP_LS2K1000 = 4, /* 2-Core Mips64r2/LA264 SoC, 64-bit, 1.0 >>> Ghz */ >>> +   CHIP_LS2K2000 = 5, /* 2-Core 64-bit LA364 SoC, 1.2 ~ 1.5 Ghz */ >>> +   CHIP_LAST, >>> +}; >>> + >>> +struct lsdc_desc { >>> +   enum loongson_chip_family chip; >>> +   u32 num_of_crtc; >>> +   u32 max_pixel_clk; >>> +   u32 max_width; >>> +   u32 max_height; >>> +   u32 num_of_hw_cursor; >>> +   u32 hw_cursor_w; >>> +   u32 hw_cursor_h; >>> +   u32 pitch_align; /* DMA alignment constraint */ >>> +   u64 mc_bits;     /* physical address bus bit width */ >>> +   bool has_vblank_counter; >>> +   bool has_builtin_i2c; >>> +   bool has_vram; >>> +   bool has_hpd_reg; >>> +   bool is_soc; >>> +}; >>> + >>> +struct lsdc_i2c { >>> +   struct i2c_adapter adapter; >>> +   struct i2c_algo_bit_data bit; >>> +   struct drm_device *ddev; >>> +   void __iomem *reg_base; >>> +   void __iomem *dir_reg; >>> +   void __iomem *dat_reg; >>> +   /* pin bit mask */ >>> +   u8 sda; >>> +   u8 scl; >>> +}; >>> + >>> +/* >>> + * struct lsdc_display_pipe - Abstraction of hardware display pipeline. >>> + * @crtc: CRTC control structure >>> + * @primary: framebuffer plane control structure >>> + * @cursor: cursor plane control structure >>> + * @output: output control structure of this display pipe bind >>> + * @pixpll: pixel pll control structure >>> + * @index: the index corresponding to the hardware display pipe >>> + */ >>> +struct lsdc_display_pipe { >>> +   struct drm_crtc crtc; >>> +   struct drm_plane primary; >>> +   struct drm_plane cursor; >>> +   struct drm_encoder encoder; >>> +   struct drm_connector connector; >>> +   struct lsdc_pll pixpll; >>> +   struct lsdc_i2c *li2c; >>> +   int index; >>> +}; >>> + >>> +static inline struct lsdc_display_pipe * >>> +crtc_to_display_pipe(struct drm_crtc *crtc) >>> +{ >>> +   return container_of(crtc, struct lsdc_display_pipe, crtc); >>> +} >>> + >>> +static inline struct lsdc_display_pipe * >>> +cursor_to_display_pipe(struct drm_plane *cursor) >>> +{ >>> +   return container_of(cursor, struct lsdc_display_pipe, cursor); >>> +} >>> + >>> +static inline struct lsdc_display_pipe * >>> +connector_to_display_pipe(struct drm_connector *conn) >>> +{ >>> +   return container_of(conn, struct lsdc_display_pipe, connector); >>> +} >>> + >>> +static inline struct lsdc_display_pipe * >>> +encoder_to_display_pipe(struct drm_encoder *enc) >>> +{ >>> +   return container_of(enc, struct lsdc_display_pipe, encoder); >>> +} >>> + >>> +struct lsdc_crtc_state { >>> +   struct drm_crtc_state base; >>> +   struct lsdc_pll_parms pparms; >>> +}; >>> + >>> +struct lsdc_device { >>> +   struct drm_device base; >>> + >>> +   /* @reglock: protects concurrent register access */ >>> +   spinlock_t reglock; >>> +   void __iomem *reg_base; >>> +   void __iomem *vram; >>> +   resource_size_t vram_base; >>> +   resource_size_t vram_size; >>> +   u64 mc_mask; >>> + >>> +   struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC]; >>> + >>> +   /* @num_output: count the number of active display pipe */ >>> +   unsigned int num_output; >>> + >>> +   /* @descp: features description of the DC variant */ >>> +   const struct lsdc_desc *descp; >>> + >>> +   u32 irq_status; >>> +}; >>> + >>> +static inline struct lsdc_device * >>> +to_lsdc(struct drm_device *ddev) >>> +{ >>> +   return container_of(ddev, struct lsdc_device, base); >>> +} >>> + >>> +static inline struct lsdc_crtc_state * >>> +to_lsdc_crtc_state(struct drm_crtc_state *base) >>> +{ >>> +   return container_of(base, struct lsdc_crtc_state, base); >>> +} >>> + >>> +void lsdc_debugfs_init(struct drm_minor *minor); >>> + >>> +int lsdc_crtc_init(struct drm_device *ddev, >>> +          struct drm_crtc *crtc, >>> +          unsigned int index, >>> +          struct drm_plane *primary, >>> +          struct drm_plane *cursor); >>> + >>> +int lsdc_plane_init(struct lsdc_device *ldev, >>> +           struct drm_plane *plane, >>> +           enum drm_plane_type type, >>> +           unsigned int index); >>> + >>> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index); >>> + >>> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >>> +                     void *base, >>> +                     unsigned int index); >>> + >>> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >>> int index); >>> + >>> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg); >>> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev); >>> + >>> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset); >>> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 val); >>> + >>> +#endif >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c >>> b/drivers/gpu/drm/lsdc/lsdc_i2c.c >>> new file mode 100644 >>> index 000000000000..b380098409ca >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c >>> @@ -0,0 +1,201 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include <linux/i2c.h> >>> +#include <drm/drm_managed.h> >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> + >>> +/* >>> + * ls7a_gpio_i2c_set - set the state of a gpio pin indicated by mask >>> + * @mask: gpio pin mask >>> + * @state: "0" for low, "1" for high >>> + */ >>> +static void ls7a_gpio_i2c_set(struct lsdc_i2c * const li2c, int >>> mask, int state) >>> +{ >>> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >>> +   unsigned long flags; >>> +   u8 val; >>> + >>> +   spin_lock_irqsave(&ldev->reglock, flags); >>> + >>> +   if (state) { >>> +       /* >>> +        * Setting this pin as input directly, write 1 for Input. >>> +        * The external pull-up resistor will pull the level up >>> +        */ >>> +       val = readb(li2c->dir_reg); >>> +       val |= mask; >>> +       writeb(val, li2c->dir_reg); >>> +   } else { >>> +       /* First set this pin as output, write 0 for Output */ >>> +       val = readb(li2c->dir_reg); >>> +       val &= ~mask; >>> +       writeb(val, li2c->dir_reg); >>> + >>> +       /* Then, make this pin output 0 */ >>> +       val = readb(li2c->dat_reg); >>> +       val &= ~mask; >>> +       writeb(val, li2c->dat_reg); >>> +   } >>> + >>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>> +} >>> + >>> +/* >>> + * ls7a_gpio_i2c_get - read value back from the gpio pin indicated >>> by mask >>> + * @mask: gpio pin mask >>> + * return "0" for low, "1" for high >>> + */ >>> +static int ls7a_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask) >>> +{ >>> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >>> +   unsigned long flags; >>> +   u8 val; >>> + >>> +   spin_lock_irqsave(&ldev->reglock, flags); >>> + >>> +   /* First set this pin as input */ >>> +   val = readb(li2c->dir_reg); >>> +   val |= mask; >>> +   writeb(val, li2c->dir_reg); >>> + >>> +   /* Then get level state from this pin */ >>> +   val = readb(li2c->dat_reg); >>> + >>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>> + >>> +   return (val & mask) ? 1 : 0; >>> +} >>> + >>> +static void ls7a_i2c_set_sda(void *i2c, int state) >>> +{ >>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>> +   /* set state on the li2c->sda pin */ >>> +   return ls7a_gpio_i2c_set(li2c, li2c->sda, state); >>> +} >>> + >>> +static void ls7a_i2c_set_scl(void *i2c, int state) >>> +{ >>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>> +   /* set state on the li2c->scl pin */ >>> +   return ls7a_gpio_i2c_set(li2c, li2c->scl, state); >>> +} >>> + >>> +static int ls7a_i2c_get_sda(void *i2c) >>> +{ >>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>> +   /* read value from the li2c->sda pin */ >>> +   return ls7a_gpio_i2c_get(li2c, li2c->sda); >>> +} >>> + >>> +static int ls7a_i2c_get_scl(void *i2c) >>> +{ >>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>> +   /* read the value from the li2c->scl pin */ >>> +   return ls7a_gpio_i2c_get(li2c, li2c->scl); >>> +} >>> + >>> +static void lsdc_destroy_i2c(struct drm_device *ddev, void *data) >>> +{ >>> +   struct lsdc_i2c *li2c = (struct lsdc_i2c *)data; >>> + >>> +   if (li2c) { >>> +       i2c_del_adapter(&li2c->adapter); >>> +       kfree(li2c); >>> +   } >>> +} >>> + >>> +/* >>> + * The DC in ls7a1000/ls7a2000 have builtin gpio hardware >>> + * >>> + * @base: gpio reg base >>> + * @index: output channel index, 0 for DVO0, 1 for DVO1 >>> + */ >>> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >>> +                     void *base, >>> +                     unsigned int index) >>> +{ >>> +   struct i2c_adapter *adapter; >>> +   struct lsdc_i2c *li2c; >>> +   int ret; >>> + >>> +   li2c = kzalloc(sizeof(*li2c), GFP_KERNEL); >>> +   if (!li2c) >>> +       return ERR_PTR(-ENOMEM); >>> + >>> +   if (index == 0) { >>> +       li2c->sda = 0x01; /* pin 0 */ >>> +       li2c->scl = 0x02; /* pin 1 */ >>> +   } else if (index == 1) { >>> +       li2c->sda = 0x04; /* pin 2 */ >>> +       li2c->scl = 0x08; /* pin 3 */ >>> +   } >>> + >>> +   li2c->reg_base = base; >>> +   li2c->ddev = ddev; >>> +   li2c->dir_reg = li2c->reg_base + LS7A_DC_GPIO_DIR_REG; >>> +   li2c->dat_reg = li2c->reg_base + LS7A_DC_GPIO_DAT_REG; >>> + >>> +   li2c->bit.setsda = ls7a_i2c_set_sda; >>> +   li2c->bit.setscl = ls7a_i2c_set_scl; >>> +   li2c->bit.getsda = ls7a_i2c_get_sda; >>> +   li2c->bit.getscl = ls7a_i2c_get_scl; >>> +   li2c->bit.udelay = 5; >>> +   li2c->bit.timeout = usecs_to_jiffies(2200); >>> +   li2c->bit.data = li2c; >>> + >>> +   adapter = &li2c->adapter; >>> +   adapter->algo_data = &li2c->bit; >>> +   adapter->owner = THIS_MODULE; >>> +   adapter->class = I2C_CLASS_DDC; >>> +   adapter->dev.parent = ddev->dev; >>> +   adapter->nr = -1; >>> + >>> +   snprintf(adapter->name, sizeof(adapter->name), "lsdc-i2c%u", >>> index); >>> + >>> +   i2c_set_adapdata(adapter, li2c); >>> + >>> +   ret = i2c_bit_add_bus(adapter); >>> +   if (ret) { >>> +       kfree(li2c); >>> +       return ERR_PTR(ret); >>> +   } >>> + >>> +   ret = drmm_add_action_or_reset(ddev, lsdc_destroy_i2c, li2c); >>> +   if (ret) >>> +       return NULL; >>> + >>> +   drm_info(ddev, "%s(sda=%u, scl=%u) created for connector-%u\n", >>> +        adapter->name, li2c->sda, li2c->scl, index); >>> + >>> +   return li2c; >>> +} >>> + >>> +static int lsdc_get_i2c_id(struct lsdc_device *ldev, int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   if (descp->chip == CHIP_LS2K1000) >>> +       return index; >>> + >>> +   if (descp->chip == CHIP_LS2K0500) >>> +       return index + 2; >>> + >>> +   return index; >>> +} >>> + >>> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >>> +                    int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >>> + >>> +   if (descp->has_builtin_i2c) { >>> +       struct lsdc_i2c *li2c = dispipe->li2c; >>> + >>> +       if (li2c) >>> +           return &li2c->adapter; >>> +   } >>> + >>> +   return i2c_get_adapter(lsdc_get_i2c_id(ldev, index)); >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.c >>> b/drivers/gpu/drm/lsdc/lsdc_irq.c >>> new file mode 100644 >>> index 000000000000..71c85f09bb6f >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_irq.c >>> @@ -0,0 +1,86 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include <drm/drm_vblank.h> >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> + >>> +/* >>> + * For the DC in ls7a2000, clearing interrupt status is achieved by >>> + * write "1" to LSDC_INT_REG, For the DC in ls7a1000, ls2k1000 and >>> + * ls2k0500, clearing interrupt status is achieved by write "0" to >>> + * LSDC_INT_REG. Two different hardware engineer of Loongson modify >>> + * it as their will. >>> + */ >>> + >>> +/* For the DC in ls7a2000 */ >>> +static irqreturn_t lsdc_irq_handler(int irq, void *arg) >>> +{ >>> +   struct drm_device *ddev = arg; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   u32 val; >>> + >>> +   /* Read the interrupt status */ >>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>> +   if ((val & INT_STATUS_MASK) == 0) { >>> +       drm_warn(ddev, "no interrupt occurs\n"); >>> +       return IRQ_NONE; >>> +   } >>> + >>> +   ldev->irq_status = val; >>> + >>> +   /* write "1" to clear the interrupt status */ >>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >>> + >>> +   return IRQ_WAKE_THREAD; >>> +} >>> + >>> +/* For the DC in ls7a1000, ls2k1000 and ls2k0500 */ >>> +static irqreturn_t lsdc_irq_handler_legacy(int irq, void *arg) >>> +{ >>> +   struct drm_device *ddev = arg; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   u32 val; >>> + >>> +   /* Read the interrupt status */ >>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>> +   if ((val & INT_STATUS_MASK) == 0) { >>> +       drm_warn(ddev, "no interrupt occurs\n"); >>> +       return IRQ_NONE; >>> +   } >>> + >>> +   ldev->irq_status = val; >>> + >>> +   /* write "0" to clear the interrupt status */ >>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val & ~INT_STATUS_MASK); >>> + >>> +   return IRQ_WAKE_THREAD; >>> +} >>> + >>> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   if (descp->chip == CHIP_LS7A2000) >>> +       return lsdc_irq_handler; >>> + >>> +   return lsdc_irq_handler_legacy; >>> +} >>> + >>> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg) >>> +{ >>> +   struct drm_device *ddev = arg; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   struct drm_crtc *crtc; >>> + >>> +   if (ldev->irq_status & INT_CRTC0_VSYNC) { >>> +       crtc = drm_crtc_from_index(ddev, 0); >>> +       drm_crtc_handle_vblank(crtc); >>> +   } >>> + >>> +   if (ldev->irq_status & INT_CRTC1_VSYNC) { >>> +       crtc = drm_crtc_from_index(ddev, 1); >>> +       drm_crtc_handle_vblank(crtc); >>> +   } >>> + >>> +   return IRQ_HANDLED; >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_output.c >>> b/drivers/gpu/drm/lsdc/lsdc_output.c >>> new file mode 100644 >>> index 000000000000..d4bc7666d9ea >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_output.c >>> @@ -0,0 +1,452 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include <drm/drm_edid.h> >>> +#include <drm/drm_probe_helper.h> >>> +#include <drm/drm_atomic_helper.h> >>> +#include <drm/drm_connector.h> >>> +#include "lsdc_drv.h" >>> + >>> +static int lsdc_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 enum drm_connector_status >>> +lsdc_unknown_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; >>> +   } else { >>> +       if (connector->connector_type == DRM_MODE_CONNECTOR_DPI) >>> +           return connector_status_connected; >>> + >>> +       if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) >>> +           return connector_status_connected; >>> +   } >>> + >>> +   return connector_status_unknown; >>> +} >>> + >>> +static enum drm_connector_status >>> +lsdc_hdmi_connector_detect(struct drm_connector *connector, bool force) >>> +{ >>> +   struct lsdc_display_pipe *pipe = >>> connector_to_display_pipe(connector); >>> +   struct lsdc_device *ldev = to_lsdc(connector->dev); >>> +   u32 val; >>> + >>> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >>> + >>> +   if (pipe->index == 0) { >>> +       if (val & HDMI0_HPD_FLAG) >>> +           return connector_status_connected; >>> +   } >>> + >>> +   if (pipe->index == 1) { >>> +       if (val & HDMI1_HPD_FLAG) >>> +           return connector_status_connected; >>> +   } >>> + >>> +   return connector_status_disconnected; >>> +} >>> + >>> +static enum drm_connector_status >>> +lsdc_hdmi_vga_connector_detect(struct drm_connector *connector, bool >>> force) >>> +{ >>> +   struct lsdc_display_pipe *pipe = >>> connector_to_display_pipe(connector); >>> +   struct drm_device *ddev = connector->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   struct i2c_adapter *ddc; >>> +   u32 val; >>> + >>> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >>> + >>> +   if (pipe->index == 1) { >>> +       if (val & HDMI1_HPD_FLAG) >>> +           return connector_status_connected; >>> + >>> +       return connector_status_disconnected; >>> +   } >>> + >>> +   if (pipe->index == 0) { >>> +       if (val & HDMI0_HPD_FLAG) >>> +           return connector_status_connected; >>> + >>> +       ddc = connector->ddc; >>> +       if (ddc) { >>> +           if (drm_probe_ddc(ddc)) >>> +               return connector_status_connected; >>> + >>> +           return connector_status_disconnected; >>> +       } >>> +   } >>> + >>> +   return connector_status_unknown; >>> +} >>> + >>> +static struct drm_encoder * >>> +lsdc_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 >>> lsdc_connector_helpers = { >>> +   .atomic_best_encoder = lsdc_connector_get_best_encoder, >>> +   .get_modes = lsdc_get_modes, >>> +}; >>> + >>> +static const struct drm_connector_funcs lsdc_unknown_connector_funcs >>> = { >>> +   .detect = lsdc_unknown_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 const struct drm_connector_funcs >>> lsdc_hdmi_vga_connector_funcs = { >>> +   .detect = lsdc_hdmi_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, >>> +}; >>> + >>> +static const struct drm_connector_funcs lsdc_hdmi_connector_funcs = { >>> +   .detect = lsdc_hdmi_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 const struct drm_connector_funcs * >>> +lsdc_get_connector_func(struct lsdc_device *ldev, unsigned int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   if (descp->chip == CHIP_LS7A2000) { >>> +       if (index == 0) >>> +           return &lsdc_hdmi_vga_connector_funcs; >>> + >>> +       if (index == 1) >>> +           return &lsdc_hdmi_connector_funcs; >>> +   } >>> + >>> +   return &lsdc_unknown_connector_funcs; >>> +} >>> + >>> +/* >>> + * we provide a default support before DT/VBIOS is supported >>> + */ >>> +static int lsdc_get_encoder_type(struct lsdc_device *ldev, >>> +                unsigned int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   if (descp->chip == CHIP_LS7A2000) { >>> +       if (index == 0) >>> +           return DRM_MODE_ENCODER_DAC; >>> +       if (index == 1) >>> +           return DRM_MODE_ENCODER_TMDS; >>> +   } >>> + >>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == CHIP_LS2K1000) { >>> +       if (index == 0) >>> +           return DRM_MODE_ENCODER_DPI; >>> +       if (index == 1) >>> +           return DRM_MODE_ENCODER_DPI; >>> +   } >>> + >>> +   if (descp->chip == CHIP_LS2K0500) { >>> +       if (index == 0) >>> +           return DRM_MODE_ENCODER_DPI; >>> +       if (index == 1) >>> +           return DRM_MODE_ENCODER_DAC; >>> +   } >>> + >>> +   return DRM_MODE_ENCODER_NONE; >>> +} >>> + >>> +/* >>> + * provide a default before DT/VBIOS support is upstreamed >>> + */ >>> +static int lsdc_get_connector_type(struct lsdc_device *ldev, >>> +                  unsigned int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   if (descp->chip == CHIP_LS7A2000) { >>> +       if (index == 0) >>> +           return DRM_MODE_CONNECTOR_VGA; >>> +       if (index == 1) >>> +           return DRM_MODE_CONNECTOR_HDMIA; >>> +   } >>> + >>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == CHIP_LS2K1000) { >>> +       if (index == 0) >>> +           return DRM_MODE_CONNECTOR_DPI; >>> +       if (index == 1) >>> +           return DRM_MODE_CONNECTOR_DPI; >>> +   } >>> + >>> +   if (descp->chip == CHIP_LS2K0500) { >>> +       if (index == 0) >>> +           return DRM_MODE_CONNECTOR_DPI; >>> +       if (index == 1) >>> +           return DRM_MODE_CONNECTOR_VGA; >>> +   } >>> + >>> +   return DRM_MODE_CONNECTOR_Unknown; >>> +} >>> + >>> +static void lsdc_hdmi_disable(struct drm_encoder *encoder) >>> +{ >>> +   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; >>> + >>> +   if (index == 0) >>> +       val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG); >>> +   else if (index == 1) >>> +       val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG); >>> + >>> +   val &= ~HDMI_PHY_EN; >>> + >>> +   if (index == 0) >>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >>> +   else if (index == 1) >>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >>> + >>> +   drm_dbg(ddev, "HDMI-%u disabled\n", index); >>> +} >>> + >>> +static void lsdc_hdmi_enable(struct drm_encoder *encoder) >>> +{ >>> +   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; >>> + >>> +   /* we are using software gpio emulated i2c */ >>> +   val = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >>> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >>> + >>> +   if (index == 0) { >>> +       /* Enable HDMI-0 */ >>> +       lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >>> + >>> +       lsdc_wreg32(ldev, LSDC_HDMI0_ZONE_REG, 0x00400040); >>> +   } else if (index == 1) { >>> +       /* Enable HDMI-1 */ >>> +       lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >>> + >>> +       lsdc_wreg32(ldev, LSDC_HDMI1_ZONE_REG, 0x00400040); >>> +   } >>> + >>> +   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; >>> + >>> +   if (index == 0) >>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >>> +   else if (index == 1) >>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >>> + >>> +   drm_dbg(ddev, "HDMI-%u enabled\n", index); >>> +} >>> + >>> +/* >>> + * Fout = M * Fin >>> + * >>> + * M = (4 * LF) / (IDF * ODF) >>> + * >>> + * Loongson HDMI require M = 10 >>> + */ >>> +static void lsdc_hdmi_phy_pll_config(struct lsdc_device *ldev, >>> +                    int index, >>> +                    int clock) >>> +{ >>> +   struct drm_device *ddev = &ldev->base; >>> +   int count = 0; >>> +   u32 val; >>> + >>> +   /* disable phy pll */ >>> +   if (index == 0) >>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, 0x0); >>> +   else if (index == 1) >>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, 0x0); >>> + >>> +   /* >>> +    * 10 = (4 * 40) / (8 * 2) >>> +    */ >>> +   val = (8 << HDMI_PLL_IDF_SHIFT) | >>> +         (40 << HDMI_PLL_LF_SHIFT) | >>> +         (1 << HDMI_PLL_ODF_SHIFT) | >>> +         HDMI_PLL_ENABLE; >>> + >>> +   drm_dbg(ddev, "HDMI-%u clock: %d\n", index, clock); >>> + >>> +   if (index == 0) >>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, val); >>> +   else if (index == 1) >>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, val); >>> + >>> +   /* wait hdmi phy pll lock */ >>> +   do { >>> +       if (index == 0) >>> +           val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG); >>> +       else if (index == 1) >>> +           val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_PLL_REG); >>> + >>> +       ++count; >>> + >>> +       if (val & HDMI_PLL_LOCKED) { >>> +           drm_dbg(ddev, "Setting HDMI-%u PLL success(take %d >>> cycles)\n", >>> +               index, count); >>> +           break; >>> +       } >>> +   } while (count < 1000); >>> +} >>> + >>> +static void lsdc_hdmi_atomic_mode_set(struct drm_encoder *encoder, >>> +                     struct drm_crtc_state *crtc_state, >>> +                     struct drm_connector_state *conn_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; >>> +   struct drm_display_mode *mode = &crtc_state->mode; >>> + >>> +   lsdc_hdmi_phy_pll_config(ldev, index, mode->clock); >>> + >>> +   drm_dbg(ddev, "HDMI-%u modeset\n", index); >>> +} >>> + >>> +static const struct drm_encoder_helper_funcs lsdc_hdmi_helper_funcs = { >>> +   .disable = lsdc_hdmi_disable, >>> +   .enable = lsdc_hdmi_enable, >>> +   .atomic_mode_set = lsdc_hdmi_atomic_mode_set, >>> +}; >>> + >>> +static void lsdc_hdmi_reset(struct drm_encoder *encoder) >>> +{ >>> +   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 = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >>> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >>> + >>> +   lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >>> +   lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >>> + >>> +   drm_dbg(ddev, "HDMI-%u Reset\n", index); >>> +} >>> + >>> +static const struct drm_encoder_funcs lsdc_encoder_funcs = { >>> +   .reset = lsdc_hdmi_reset, >>> +   .destroy = drm_encoder_cleanup, >>> +}; >>> + >>> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> +   struct drm_device *ddev = &ldev->base; >>> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >>> +   struct drm_encoder *encoder = &dispipe->encoder; >>> +   struct drm_connector *connector = &dispipe->connector; >>> +   struct i2c_adapter *ddc = NULL; >>> +   struct lsdc_i2c *li2c; >>> +   int ret; >>> + >>> +   ret = drm_encoder_init(ddev, >>> +                  encoder, >>> +                  &lsdc_encoder_funcs, >>> +                  lsdc_get_encoder_type(ldev, index), >>> +                  "encoder-%u", >>> +                  index); >>> +   if (ret) { >>> +       drm_err(ddev, "Failed to init encoder: %d\n", ret); >>> +       return ret; >>> +   } >>> + >>> +   encoder->possible_crtcs = BIT(index); >>> + >>> +   if (descp->chip == CHIP_LS7A2000) >>> +       drm_encoder_helper_add(encoder, &lsdc_hdmi_helper_funcs); >>> + >>> +   if (descp->has_builtin_i2c) { >>> +       li2c = lsdc_create_i2c_chan(ddev, ldev->reg_base, index); >>> +       if (IS_ERR(li2c)) >>> +           return PTR_ERR(li2c); >>> + >>> +       dispipe->li2c = li2c; >>> + >>> +       ddc = &li2c->adapter; >>> +   } else { >>> +       ddc = lsdc_get_i2c_adapter(ldev, index); >>> +       if (IS_ERR(ddc)) { >>> +           drm_err(ddev, "Error get ddc for output-%u\n", index); >>> +           return PTR_ERR(ddc); >>> +       } >>> +   } >>> + >>> +   ret = drm_connector_init_with_ddc(ddev, >>> +                     connector, >>> +                     lsdc_get_connector_func(ldev, index), >>> +                     lsdc_get_connector_type(ldev, index), >>> +                     ddc); >>> +   if (ret) { >>> +       drm_err(ddev, "Init connector-%d failed\n", index); >>> +       return ret; >>> +   } >>> + >>> +   drm_connector_helper_add(connector, &lsdc_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; >>> + >>> +   ldev->num_output++; >>> + >>> +   drm_info(ddev, "output-%u initialized\n", index); >>> + >>> +   return 0; >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_plane.c >>> b/drivers/gpu/drm/lsdc/lsdc_plane.c >>> new file mode 100644 >>> index 000000000000..0f779c97d53b >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_plane.c >>> @@ -0,0 +1,443 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include <drm/drm_atomic.h> >>> +#include <drm/drm_atomic_helper.h> >>> +#include <drm/drm_framebuffer.h> >>> +#include <drm/drm_plane_helper.h> >>> +#include <drm/drm_gem_vram_helper.h> >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> +#include "lsdc_pll.h" >>> + >>> +static const u32 lsdc_primary_formats[] = { >>> +   DRM_FORMAT_XRGB8888, >>> +   DRM_FORMAT_ARGB8888, >>> +}; >>> + >>> +static const u32 lsdc_cursor_formats[] = { >>> +   DRM_FORMAT_ARGB8888, >>> +}; >>> + >>> +static const u64 lsdc_fb_format_modifiers[] = { >>> +   DRM_FORMAT_MOD_LINEAR, >>> +   DRM_FORMAT_MOD_INVALID >>> +}; >>> + >>> +static void lsdc_update_fb_format(struct lsdc_device *ldev, >>> +                 struct drm_crtc *crtc, >>> +                 const struct drm_format_info *fmt_info) >>> +{ >>> +   unsigned int index = drm_crtc_index(crtc); >>> +   u32 val; >>> +   u32 fmt; >>> + >>> +   switch (fmt_info->format) { >>> +   case DRM_FORMAT_XRGB8888: >>> +       fmt = LSDC_PF_XRGB8888; >>> +       break; >>> +   case DRM_FORMAT_ARGB8888: >>> +       fmt = LSDC_PF_XRGB8888; >>> +       break; >>> +   default: >>> +       fmt = LSDC_PF_XRGB8888; >>> +       break; >>> +   } >>> + >>> +   if (index == 0) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>> +   } else if (index == 1) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>> +   } >>> +} >>> + >>> +static void lsdc_update_fb_start_addr(struct lsdc_device *ldev, >>> +                     struct drm_crtc *crtc, >>> +                     u64 paddr) >>> +{ >>> +   struct drm_device *ddev = &ldev->base; >>> +   unsigned int index = drm_crtc_index(crtc); >>> +   u32 lo_addr_reg; >>> +   u32 hi_addr_reg; >>> +   u32 val; >>> + >>> +   /* >>> +    * Find which framebuffer address register should update. >>> +    * if FB_ADDR0_REG is in using, we write the address to >>> FB_ADDR0_REG, >>> +    * if FB_ADDR1_REG is in using, we write the address to >>> FB_ADDR1_REG >>> +    * for each CRTC, the switch using one fb register to another is >>> +    * trigger by triggered by set CFG_PAGE_FLIP bit of >>> LSDC_CRTCx_CFG_REG >>> +    */ >>> +   if (index == 0) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>> +       if (val & CFG_FB_IN_USING) { >>> +           lo_addr_reg = LSDC_CRTC0_FB1_LO_ADDR_REG; >>> +           hi_addr_reg = LSDC_CRTC0_FB1_HI_ADDR_REG; >>> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-0\n"); >>> +       } else { >>> +           lo_addr_reg = LSDC_CRTC0_FB0_LO_ADDR_REG; >>> +           hi_addr_reg = LSDC_CRTC0_FB0_HI_ADDR_REG; >>> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-0\n"); >>> +       } >>> +   } else if (index == 1) { >>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>> +       if (val & CFG_FB_IN_USING) { >>> +           lo_addr_reg = LSDC_CRTC1_FB1_LO_ADDR_REG; >>> +           hi_addr_reg = LSDC_CRTC1_FB1_HI_ADDR_REG; >>> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-1\n"); >>> +       } else { >>> +           lo_addr_reg = LSDC_CRTC1_FB0_LO_ADDR_REG; >>> +           hi_addr_reg = LSDC_CRTC1_FB0_HI_ADDR_REG; >>> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-1\n"); >>> +       } >>> +   } >>> + >>> +   /* 40-bit width physical address bus */ >>> +   lsdc_wreg32(ldev, lo_addr_reg, paddr); >>> +   lsdc_wreg32(ldev, hi_addr_reg, (paddr >> 32) & 0xFF); >>> + >>> +   drm_dbg(ddev, "CRTC-%u scantout from 0x%llx\n", index, paddr); >>> +} >>> + >>> +static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb, >>> +                      struct drm_plane_state *state, >>> +                      unsigned int plane) >>> +{ >>> +   unsigned int offset = fb->offsets[plane]; >>> + >>> +   offset += fb->format->cpp[plane] * (state->src_x >> 16); >>> +   offset += fb->pitches[plane] * (state->src_y >> 16); >>> + >>> +   return offset; >>> +} >>> + >>> +static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb) >>> +{ >>> +   struct drm_gem_vram_object *gbo; >>> +   s64 gpu_addr; >>> + >>> +   gbo = drm_gem_vram_of_gem(fb->obj[0]); >>> +   gpu_addr = drm_gem_vram_offset(gbo); >>> + >>> +   return gpu_addr; >>> +} >>> + >>> +static int lsdc_primary_plane_atomic_check(struct drm_plane *plane, >>> +                      struct drm_atomic_state *state) >>> +{ >>> +   struct drm_plane_state *plane_state = >>> drm_atomic_get_new_plane_state(state, plane); >>> +   struct drm_crtc *crtc = plane_state->crtc; >>> +   struct drm_crtc_state *crtc_state = >>> drm_atomic_get_new_crtc_state(state, crtc); >>> + >>> +   if (!crtc) >>> +       return 0; >>> + >>> +   return drm_atomic_helper_check_plane_state(plane_state, >>> +                          crtc_state, >>> +                          DRM_PLANE_NO_SCALING, >>> +                          DRM_PLANE_NO_SCALING, >>> +                          false, >>> +                          true); >>> +} >>> + >>> +static void lsdc_update_fb_stride(struct lsdc_device *ldev, >>> +                 struct drm_crtc *crtc, >>> +                 unsigned int stride) >>> +{ >>> +   unsigned int index = drm_crtc_index(crtc); >>> + >>> +   if (index == 0) >>> +       lsdc_wreg32(ldev, LSDC_CRTC0_STRIDE_REG, stride); >>> +   else if (index == 1) >>> +       lsdc_wreg32(ldev, LSDC_CRTC1_STRIDE_REG, stride); >>> + >>> +   drm_dbg(crtc->dev, "update stride to %u\n", stride); >>> +} >>> + >>> +static void lsdc_primary_plane_atomic_update(struct drm_plane *plane, >>> +                        struct drm_atomic_state *state) >>> +{ >>> +   struct lsdc_device *ldev = to_lsdc(plane->dev); >>> +   struct drm_plane_state *new_plane_state = >>> drm_atomic_get_new_plane_state(state, plane); >>> +   struct drm_crtc *crtc = new_plane_state->crtc; >>> +   struct drm_framebuffer *fb = new_plane_state->fb; >>> +   u32 fb_offset = lsdc_get_fb_offset(fb, new_plane_state, 0); >>> +   dma_addr_t fb_addr; >>> +   s64 gpu_addr; >>> + >>> +   gpu_addr = lsdc_get_vram_bo_offset(fb); >>> +   if (gpu_addr < 0) >>> +       return; >>> + >>> +   fb_addr = ldev->vram_base + gpu_addr + fb_offset; >>> + >>> +   lsdc_update_fb_start_addr(ldev, crtc, fb_addr); >>> + >>> +   lsdc_update_fb_stride(ldev, crtc, fb->pitches[0]); >>> + >>> +   lsdc_update_fb_format(ldev, crtc, fb->format); >>> +} >>> + >>> +static void lsdc_primary_plane_atomic_disable(struct drm_plane *plane, >>> +                         struct drm_atomic_state *state) >>> +{ >>> +   drm_dbg(plane->dev, "%s disabled\n", plane->name); >>> +} >>> + >>> +static const struct drm_plane_helper_funcs >>> lsdc_primary_plane_helpers = { >>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>> +   .atomic_check = lsdc_primary_plane_atomic_check, >>> +   .atomic_update = lsdc_primary_plane_atomic_update, >>> +   .atomic_disable = lsdc_primary_plane_atomic_disable, >>> +}; >>> + >>> +static int lsdc_cursor_atomic_check(struct drm_plane *plane, >>> +                   struct drm_atomic_state *state) >>> +{ >>> +   struct drm_plane_state *new_plane_state = >>> drm_atomic_get_new_plane_state(state, plane); >>> +   struct drm_framebuffer *fb = new_plane_state->fb; >>> +   struct drm_crtc *crtc = new_plane_state->crtc; >>> +   struct drm_crtc_state *crtc_state; >>> +   int ret; >>> + >>> +   /* no need for further checks if the plane is being disabled */ >>> +   if (!crtc || !fb) >>> +       return 0; >>> + >>> +   if (!new_plane_state->visible) >>> +       return 0; >>> + >>> +   crtc_state = drm_atomic_get_new_crtc_state(state, >>> +                          new_plane_state->crtc); >>> + >>> +   ret = drm_atomic_helper_check_plane_state(new_plane_state, >>> +                         crtc_state, >>> +                         DRM_PLANE_NO_SCALING, >>> +                         DRM_PLANE_NO_SCALING, >>> +                         true, >>> +                         true); >>> + >>> +   return ret; >>> +} >>> + >>> +/* >>> + * There is only one hardware cursor in ls7a1000, ls2k1000 and >>> ls2k0500. >>> + * we made it shared by the two CRTC, which can satisfy peoples who use >>> + * double screen extend mode only. On clone screen usage case, the >>> cursor >>> + * on display pipe 1 will not be able to display. >>> + * >>> + * Update location of the cursor, attach it to CRTC0 or CRTC1 on the >>> runtime. >>> + */ >>> +static void lsdc_cursor_update_location_quirks(struct lsdc_device >>> *ldev, >>> +                          struct drm_crtc *crtc) >>> +{ >>> +   u32 val = CURSOR_FORMAT_ARGB8888; >>> + >>> +   /* >>> +    * If bit 4 of LSDC_CURSOR0_CFG_REG is 1, then the cursor will be >>> +    * locate at CRTC-1, if bit 4 of LSDC_CURSOR0_CFG_REG is 0, then >>> +    * the cursor will be locate at CRTC-0. The cursor is alway on the >>> +    * top of the primary. Compositing the primary plane and cursor >>> +    * plane is automatically done by hardware. >>> +    */ >>> +   if (drm_crtc_index(crtc)) >>> +       val |= CURSOR_LOCATION; >>> + >>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, val); >>> +} >>> + >>> +/* update the position of the cursor */ >>> +static void lsdc_cursor_update_position_quirks(struct lsdc_device >>> *ldev, >>> +                          int x, >>> +                          int y) >>> +{ >>> +   if (x < 0) >>> +       x = 0; >>> + >>> +   if (y < 0) >>> +       y = 0; >>> + >>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >>> +} >>> + >>> +static void lsdc_cursor_atomic_update_quirks(struct drm_plane *plane, >>> +                        struct drm_atomic_state *state) >>> +{ >>> +   struct drm_device *ddev = plane->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   struct drm_plane_state *new_plane_state = >>> drm_atomic_get_new_plane_state(state, plane); >>> +   struct drm_plane_state *old_plane_state = >>> drm_atomic_get_old_plane_state(state, plane); >>> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >>> +   struct drm_framebuffer *old_fb = old_plane_state->fb; >>> + >>> +   if (new_fb != old_fb) { >>> +       s64 offset = lsdc_get_vram_bo_offset(new_fb); >>> +       u64 cursor_addr = ldev->vram_base + offset; >>> + >>> +       drm_dbg_kms(ddev, "%s offset: %llx\n", plane->name, offset); >>> + >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >> >>> 32) & 0xFF); >>> +   } >>> + >>> +   lsdc_cursor_update_position_quirks(ldev, >>> new_plane_state->crtc_x, new_plane_state->crtc_y); >>> + >>> +   lsdc_cursor_update_location_quirks(ldev, new_plane_state->crtc); >>> +} >>> + >>> +/* update the format, size and location of the cursor */ >>> +static void lsdc_cursor_atomic_update(struct drm_plane *plane, >>> +                     struct drm_atomic_state *state) >>> +{ >>> +   struct drm_device *ddev = plane->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   struct lsdc_display_pipe *dispipe = cursor_to_display_pipe(plane); >>> +   struct drm_plane_state *new_plane_state = >>> drm_atomic_get_new_plane_state(state, plane); >>> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >>> +   int x = new_plane_state->crtc_x; >>> +   int y = new_plane_state->crtc_y; >>> +   u32 conf = CURSOR_FORMAT_ARGB8888 | CURSOR_SIZE_64X64; >>> +   u64 cursor_addr = ldev->vram_base + >>> lsdc_get_vram_bo_offset(new_fb); >>> + >>> +   if (x < 0) >>> +       x = 0; >>> + >>> +   if (y < 0) >>> +       y = 0; >>> + >>> +   if (dispipe->index == 0) { >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >> >>> 32) & 0xFF); >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >>> +       /* Attach Cursor-0 to CRTC-0 */ >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, conf & >>> ~CURSOR_LOCATION); >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >>> +       return; >>> +   } >>> + >>> +   if (dispipe->index == 1) { >>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_HI_REG, (cursor_addr >> >>> 32) & 0xFF); >>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_LO_REG, cursor_addr); >>> +       /* Attach Cursor-1 to CRTC-1 */ >>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, conf | >>> CURSOR_LOCATION); >>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_POSITION_REG, (y << 16) | x); >>> +       return; >>> +   } >>> +} >>> + >>> +static void lsdc_cursor_atomic_disable_quirks(struct drm_plane *plane, >>> +                         struct drm_atomic_state *state) >>> +{ >>> +   struct drm_device *ddev = plane->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> + >>> +   /* Set the format to 0 actually display the cursor */ >>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >>> + >>> +   drm_dbg(ddev, "%s disabled\n", plane->name); >>> +} >>> + >>> +static void lsdc_cursor_atomic_disable(struct drm_plane *plane, >>> +                      struct drm_atomic_state *state) >>> +{ >>> +   struct drm_device *ddev = plane->dev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   struct lsdc_display_pipe *dispipe = cursor_to_display_pipe(plane); >>> + >>> +   if (dispipe->index == 0) >>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >>> +   else if (dispipe->index == 1) >>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, 0); >>> + >>> +   drm_dbg(ddev, "%s disabled\n", plane->name); >>> +} >>> + >>> +static const struct drm_plane_helper_funcs lsdc_cursor_helpers_quirk >>> = { >>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>> +   .atomic_check = lsdc_cursor_atomic_check, >>> +   .atomic_update = lsdc_cursor_atomic_update_quirks, >>> +   .atomic_disable = lsdc_cursor_atomic_disable_quirks, >>> +}; >>> + >>> +static const struct drm_plane_helper_funcs lsdc_cursor_plane_helpers >>> = { >>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>> +   .atomic_check = lsdc_cursor_atomic_check, >>> +   .atomic_update = lsdc_cursor_atomic_update, >>> +   .atomic_disable = lsdc_cursor_atomic_disable, >>> +}; >>> + >>> +static const struct drm_plane_funcs lsdc_plane_funcs = { >>> +   .update_plane = drm_atomic_helper_update_plane, >>> +   .disable_plane = drm_atomic_helper_disable_plane, >>> +   .destroy = drm_plane_cleanup, >>> +   .reset = drm_atomic_helper_plane_reset, >>> +   .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, >>> +   .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, >>> +}; >>> + >>> +static const struct drm_plane_helper_funcs * >>> +lsdc_get_cursor_helper_funcs(const struct lsdc_desc *descp) >>> +{ >>> +   if (descp->chip == CHIP_LS7A2000) >>> +       return &lsdc_cursor_plane_helpers; >>> + >>> +   return &lsdc_cursor_helpers_quirk; >>> +} >>> + >>> +int lsdc_plane_init(struct lsdc_device *ldev, >>> +           struct drm_plane *plane, >>> +           enum drm_plane_type type, >>> +           unsigned int index) >>> +{ >>> +   const struct lsdc_desc *descp = ldev->descp; >>> +   struct drm_device *ddev = &ldev->base; >>> +   unsigned int format_count; >>> +   const u32 *formats; >>> +   const char *name; >>> +   int ret; >>> + >>> +   switch (type) { >>> +   case DRM_PLANE_TYPE_PRIMARY: >>> +       formats = lsdc_primary_formats; >>> +       format_count = ARRAY_SIZE(lsdc_primary_formats); >>> +       name = "primary-%u"; >>> +       break; >>> +   case DRM_PLANE_TYPE_CURSOR: >>> +       formats = lsdc_cursor_formats; >>> +       format_count = ARRAY_SIZE(lsdc_cursor_formats); >>> +       name = "cursor-%u"; >>> +       break; >>> +   case DRM_PLANE_TYPE_OVERLAY: >>> +       drm_err(ddev, "overlay plane is not supported\n"); >>> +       break; >>> +   } >>> + >>> +   ret = drm_universal_plane_init(ddev, plane, 1 << index, >>> +                      &lsdc_plane_funcs, >>> +                      formats, format_count, >>> +                      lsdc_fb_format_modifiers, >>> +                      type, name, index); >>> +   if (ret) { >>> +       drm_err(ddev, "%s failed: %d\n", __func__, ret); >>> +       return ret; >>> +   } >>> + >>> +   switch (type) { >>> +   case DRM_PLANE_TYPE_PRIMARY: >>> +       drm_plane_helper_add(plane, &lsdc_primary_plane_helpers); >>> +       break; >>> +   case DRM_PLANE_TYPE_CURSOR: >>> +       drm_plane_helper_add(plane, >>> lsdc_get_cursor_helper_funcs(descp)); >>> +       break; >>> +   case DRM_PLANE_TYPE_OVERLAY: >>> +       drm_err(ddev, "overlay plane is not supported\n"); >>> +       break; >>> +   } >>> + >>> +   return 0; >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.c >>> b/drivers/gpu/drm/lsdc/lsdc_pll.c >>> new file mode 100644 >>> index 000000000000..6ed74989a6f5 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.c >>> @@ -0,0 +1,569 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> +#include "lsdc_pll.h" >>> + >>> +/* >>> + * The structure of the pixel PLL register is evolved with times. >>> + * All loongson's cpu is little endian. >>> + */ >>> + >>> +/* u64 */ >>> +struct ls7a1000_pixpll_bitmap { >>> +   /* Byte 0 ~ Byte 3 */ >>> +   unsigned div_out     : 7;  /* 0 : 6    output clock divider */ >>> +   unsigned __1         : 14; /* 7 : 20                         */ >>> +   unsigned loopc       : 9;  /* 21 : 29   clock multiplier     */ >>> +   unsigned __2         : 2;  /* 30 : 31                         */ >>> + >>> +   /* Byte 4 ~ Byte 7 */ >>> +   unsigned div_ref     : 7;  /* 0 : 6    input clock divider  */ >>> +   unsigned locked      : 1;  /* 7        PLL locked status    */ >>> +   unsigned sel_out     : 1;  /* 8        output clk selector  */ >>> +   unsigned __3         : 2;  /* 9 : 10 reserved             */ >>> +   unsigned set_param   : 1;  /* 11       trigger the update   */ >>> +   unsigned bypass      : 1;  /* 12                             */ >>> +   unsigned powerdown   : 1;  /* 13                             */ >>> +   unsigned __4         : 18; /* 14 : 31                        */ >>> +}; >>> + >>> +/* u128 */ >>> +struct ls2k1000_pixpll_bitmap { >>> +   /* Byte 0 ~ Byte 3 */ >>> +   unsigned sel_out     : 1; /* 0     select this PLL         */ >>> +   unsigned __1         : 1; /* 1                              */ >>> +   unsigned sw_adj_en   : 1; /* 2     allow software adjust   */ >>> +   unsigned bypass      : 1; /* 3     bypass L1 PLL           */ >>> +   unsigned __2         : 3; /* 4:6                            */ >>> +   unsigned lock_en     : 1; /* 7     enable lock L1 PLL      */ >>> +   unsigned __3         : 2; /* 8:9                            */ >>> +   unsigned lock_check  : 2; /* 10:11  precision check         */ >>> +   unsigned __4         : 4; /* 12:15                           */ >>> + >>> +   unsigned locked      : 1; /* 16     PLL locked flag bit     */ >>> +   unsigned __5         : 2; /* 17:18                           */ >>> +   unsigned powerdown   : 1; /* 19     powerdown the pll if set */ >>> +   unsigned __6         : 6; /* 20:25                           */ >>> +   unsigned div_ref     : 6; /* 26:31  L1 Prescaler            */ >>> + >>> +   /* Byte 4 ~ Byte 7 */ >>> +   unsigned loopc       : 10; /* 32:41  Clock Multiplier        */ >>> +   unsigned l1_div      : 6; /* 42:47  not used                */ >>> +   unsigned __7         : 16; /* 48:63                           */ >>> + >>> +   /* Byte 8 ~ Byte 15 */ >>> +   unsigned div_out     : 6; /* 0 : 5  output clock divider    */ >>> +   unsigned __8         : 26; /* 6 : 31                          */ >>> +   unsigned __9         : 32; /* 70: 127                         */ >>> +}; >>> + >>> +/* u32 */ >>> +struct ls2k0500_pixpll_bitmap { >>> +   /* Byte 0 ~ Byte 1 */ >>> +   unsigned sel_out     : 1; >>> +   unsigned __1         : 2; >>> +   unsigned sw_adj_en   : 1;  /* allow software >>> adjust             */ >>> +   unsigned bypass      : 1;  /* bypass L1 >>> PLL                     */ >>> +   unsigned powerdown   : 1;  /* write 1 to powerdown the >>> PLL      */ >>> +   unsigned __2         : 1; >>> +   unsigned locked      : 1;  /* 7    Is L1 PLL locked, read >>> only */ >>> +   unsigned div_ref     : 6;  /* 8:13 ref clock >>> divider          */ >>> +   unsigned __3         : 2;  /* >>> 14:15                             */ >>> +   /* Byte 2 ~ Byte 3 */ >>> +   unsigned loopc       : 8;  /* 16:23  Clock >>> Multiplier          */ >>> +   unsigned div_out     : 6;  /* 24:29  output clock >>> divider      */ >>> +   unsigned __4         : 2;  /* >>> 30:31                             */ >>> +}; >>> + >>> +union lsdc_pixpll_bitmap { >>> +   struct ls7a1000_pixpll_bitmap ls7a1000; >>> +   struct ls2k1000_pixpll_bitmap ls2k1000; >>> +   struct ls2k0500_pixpll_bitmap ls2k0500; >>> + >>> +   u32 dword[4]; >>> +}; >>> + >>> +struct pixclk_to_pll_parm { >>> +   /* kHz */ >>> +   unsigned int clock; >>> + >>> +   unsigned short width; >>> +   unsigned short height; >>> +   unsigned short vrefresh; >>> + >>> +   /* Stores parameters for programming the Hardware PLLs */ >>> +   unsigned short div_out; >>> +   unsigned short loopc; >>> +   unsigned short div_ref; >>> +}; >>> + >>> +/* >>> + * Pixel clock to PLL parameters translation table. >>> + * Small static cached value to speed up PLL parameters calculation. >>> + */ >>> +static const struct pixclk_to_pll_parm pll_param_table[] = { >>> +   {148500, 1920, 1080, 60, 11, 49, 3},  /* 1920x1080@60Hz */ >>> +                       /* 1920x1080@50Hz */ >>> +   {174500, 1920, 1080, 75, 17, 89, 3},  /* 1920x1080@75Hz */ >>> +   {181250, 2560, 1080, 75, 8, 58, 4},  /* 2560x1080@75Hz */ >>> +   {146250, 1680, 1050, 60, 16, 117, 5},  /* 1680x1050@60Hz */ >>> +   {135000, 1280, 1024, 75, 10, 54, 4},  /* 1280x1024@75Hz */ >>> + >>> +   {108000, 1600, 900, 60, 15, 81, 5},  /* 1600x900@60Hz */ >>> +                       /* 1280x1024@60Hz */ >>> +                       /* 1280x960@60Hz */ >>> +                       /* 1152x864@75Hz */ >>> + >>> +   {106500, 1440, 900, 60, 19, 81, 4},  /* 1440x900@60Hz */ >>> +   {88750, 1440, 900, 60, 16, 71, 5},  /* 1440x900@60Hz */ >>> +   {83500, 1280, 800, 60, 17, 71, 5},  /* 1280x800@60Hz */ >>> +   {71000, 1280, 800, 60, 20, 71, 5},  /* 1280x800@60Hz */ >>> + >>> +   {74250, 1280, 720, 60, 22, 49, 3},  /* 1280x720@60Hz */ >>> +                       /* 1280x720@50Hz */ >>> + >>> +   {78750, 1024, 768, 75, 16, 63, 5},  /* 1024x768@75Hz */ >>> +   {75000, 1024, 768, 70, 29, 87, 4},  /* 1024x768@70Hz */ >>> +   {65000, 1024, 768, 60, 20, 39, 3},  /* 1024x768@60Hz */ >>> + >>> +   {51200, 1024, 600, 60, 25, 64, 5},  /* 1024x600@60Hz */ >>> + >>> +   {57284, 832, 624, 75, 24, 55, 4},  /* 832x624@75Hz */ >>> +   {49500, 800, 600, 75, 40, 99, 5},  /* 800x600@75Hz */ >>> +   {50000, 800, 600, 72, 44, 88, 4},  /* 800x600@72Hz */ >>> +   {40000, 800, 600, 60, 30, 36, 3},  /* 800x600@60Hz */ >>> +   {36000, 800, 600, 56, 50, 72, 4},  /* 800x600@56Hz */ >>> +   {31500, 640, 480, 75, 40, 63, 5},  /* 640x480@75Hz */ >>> +                       /* 640x480@73Hz */ >>> + >>> +   {30240, 640, 480, 67, 62, 75, 4},  /* 640x480@67Hz */ >>> +   {27000, 720, 576, 50, 50, 54, 4},  /* 720x576@60Hz */ >>> +   {25175, 640, 480, 60, 85, 107, 5},  /* 640x480@60Hz */ >>> +   {25200, 640, 480, 60, 50, 63, 5},  /* 640x480@60Hz */ >>> +                       /* 720x480@60Hz */ >>> +}; >>> + >>> +/* >>> + * lsdc_pixpll_setup - ioremap the device dependent PLL registers >>> + * >>> + * @this: point to the object where this function is called from >>> + */ >>> +static int lsdc_pixpll_setup(struct lsdc_pll * const this) >>> +{ >>> +   this->mmio = ioremap(this->reg_base, this->reg_size); >>> + >>> +   return 0; >>> +} >>> + >>> +/* >>> + * Find a set of pll parameters (to generate pixel clock) from a static >>> + * local table, which avoid to compute the pll parameter eachtime a >>> + * modeset is triggered. >>> + * >>> + * @this: point to the object which this function is called from >>> + * @clock: the desired output pixel clock, the unit is kHz >>> + * @pout: point to where the parameters to store if found >>> + * >>> + * Return true if hit, otherwise return false. >>> + */ >>> +static bool lsdc_pixpll_find(struct lsdc_pll * const this, >>> +                unsigned int clock, >>> +                struct lsdc_pll_parms * const pout) >>> +{ >>> +   unsigned int num = ARRAY_SIZE(pll_param_table); >>> +   unsigned int i; >>> + >>> +   for (i = 0; i < num; i++) { >>> +       if (clock != pll_param_table[i].clock) >>> +           continue; >>> + >>> +       pout->div_ref = pll_param_table[i].div_ref; >>> +       pout->loopc  = pll_param_table[i].loopc; >>> +       pout->div_out = pll_param_table[i].div_out; >>> + >>> +       return true; >>> +   } >>> + >>> +   drm_dbg(this->ddev, "pixel clock %u: miss\n", clock); >>> + >>> +   return false; >>> +} >>> + >>> +/* >>> + * Find a set of pll parameters which have minimal difference with >>> the desired >>> + * pixel clock frequency. It does that by computing all of the possible >>> + * combination. Compute the diff and find the combination with >>> minimal diff. >>> + * >>> + * clock_out = refclk / div_ref * loopc / div_out >>> + * >>> + * refclk is determined by the oscillator mounted board(Here is >>> 100MHz in >>> + * almost all case) >>> + * >>> + * @this: point to the object from which this function is called >>> + * @clock_khz: the desired output pixel clock, the unit is kHz >>> + * @pout: point to the out struct of lsdc_pll_parms >>> + * >>> + * Return true if a parameter is found, otherwise return false >>> + */ >>> +static bool lsdc_pixpll_compute(struct lsdc_pll * const this, >>> +               unsigned int clock_khz, >>> +               struct lsdc_pll_parms *pout) >>> +{ >>> +   unsigned int refclk = this->ref_clock; >>> +   const unsigned int tolerance = 1000; >>> +   unsigned int min = tolerance; >>> +   unsigned int div_out, loopc, div_ref; >>> +   unsigned int computed; >>> + >>> +   if (lsdc_pixpll_find(this, clock_khz, pout)) >>> +       return true; >>> + >>> +   for (div_out = 6; div_out < 64; div_out++) { >>> +       for (div_ref = 3; div_ref < 6; div_ref++) { >>> +           for (loopc = 6; loopc < 161; loopc++) { >>> +               int diff; >>> + >>> +               if (loopc < 12 * div_ref) >>> +                   continue; >>> +               if (loopc > 32 * div_ref) >>> +                   continue; >>> + >>> +               computed = refclk * loopc / div_ref / div_out; >>> + >>> +               if (clock_khz > computed) >>> +                   diff = clock_khz - computed; >>> +               else if (clock_khz < computed) >>> +                   diff = computed - clock_khz; >>> + >>> +               if (diff < min) { >>> +                   min = diff; >>> +                   pout->div_ref = div_ref; >>> +                   pout->div_out = div_out; >>> +                   pout->loopc = loopc; >>> + >>> +                   if (diff == 0) >>> +                       return true; >>> +               } >>> +           } >>> +       } >>> +   } >>> + >>> +   return min < tolerance; >>> +} >>> + >>> +/* >>> + * Update the pll parameters to hardware, target to the pixpll in >>> ls7a1000 >>> + * >>> + * @this: point to the object from which this function is called >>> + * @pin: point to the struct of lsdc_pll_parms passed in >>> + * >>> + * return 0 if successful. >>> + */ >>> +static int ls7a1000_pixpll_param_update(struct lsdc_pll * const this, >>> +                   struct lsdc_pll_parms const *pin) >>> +{ >>> +   void __iomem *reg = this->mmio; >>> +   unsigned int counter = 0; >>> +   bool locked; >>> +   u32 val; >>> + >>> +   /* Bypass the software configured PLL, using refclk directly */ >>> +   val = readl(reg + 0x4); >>> +   val &= ~(1 << 8); >>> +   writel(val, reg + 0x4); >>> + >>> +   /* Powerdown the PLL */ >>> +   val = readl(reg + 0x4); >>> +   val |= (1 << 13); >>> +   writel(val, reg + 0x4); >>> + >>> +   /* Clear the pll parameters */ >>> +   val = readl(reg + 0x4); >>> +   val &= ~(1 << 11); >>> +   writel(val, reg + 0x4); >>> + >>> +   /* clear old value & config new value */ >>> +   val = readl(reg + 0x04); >>> +   val &= ~0x7F; >>> +   val |= pin->div_ref;       /* div_ref */ >>> +   writel(val, reg + 0x4); >>> + >>> +   val = readl(reg); >>> +   val &= ~0x7f; >>> +   val |= pin->div_out;       /* div_out */ >>> + >>> +   val &= ~(0x1ff << 21); >>> +   val |= pin->loopc << 21;   /* loopc */ >>> +   writel(val, reg); >>> + >>> +   /* Set the pll the parameters */ >>> +   val = readl(reg + 0x4); >>> +   val |= (1 << 11); >>> +   writel(val, reg + 0x4); >>> + >>> +   /* Powerup the PLL */ >>> +   val = readl(reg + 0x4); >>> +   val &= ~(1 << 13); >>> +   writel(val, reg + 0x4); >>> + >>> +   /* Wait the PLL lock */ >>> +   do { >>> +       val = readl(reg + 0x4); >>> +       locked = val & 0x80; >>> +       counter++; >>> +   } while (!locked && (counter < 2000)); >>> + >>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>> + >>> +   /* Switch to the software configured pll */ >>> +   val = readl(reg + 0x4); >>> +   val |= (1UL << 8); >>> +   writel(val, reg + 0x4); >>> + >>> +   return 0; >>> +} >>> + >>> +/* >>> + * Update the pll parameters to hardware, target to the pixpll in >>> ls2k1000 >>> + * >>> + * @this: point to the object from which this function is called >>> + * @pin: point to the struct of lsdc_pll_parms passed in >>> + * >>> + * return 0 if successful. >>> + */ >>> +static int ls2k1000_pixpll_param_update(struct lsdc_pll * const this, >>> +                   struct lsdc_pll_parms const *pin) >>> +{ >>> +   void __iomem *reg = this->mmio; >>> +   unsigned int counter = 0; >>> +   bool locked = false; >>> +   u32 val; >>> + >>> +   val = readl(reg); >>> +   /* Bypass the software configured PLL, using refclk directly */ >>> +   val &= ~(1 << 0); >>> +   writel(val, reg); >>> + >>> +   /* Powerdown the PLL */ >>> +   val |= (1 << 19); >>> +   writel(val, reg); >>> + >>> +   /* Allow the software configuration */ >>> +   val &= ~(1 << 2); >>> +   writel(val, reg); >>> + >>> +   /* allow L1 PLL lock */ >>> +   val = (1L << 7) | (3L << 10); >>> +   writel(val, reg); >>> + >>> +   /* clear div_ref bit field */ >>> +   val &= ~(0x3f << 26); >>> +   /* set div_ref bit field */ >>> +   val |= pin->div_ref << 26; >>> +   writel(val, reg); >>> + >>> +   val = readl(reg + 4); >>> +   /* clear loopc bit field */ >>> +   val &= ~0x0fff; >>> +   /* set loopc bit field */ >>> +   val |= pin->loopc; >>> +   writel(val, reg + 4); >>> + >>> +   /* set div_out */ >>> +   writel(pin->div_out, reg + 8); >>> + >>> +   val = readl(reg); >>> +   /* use this parms configured just now */ >>> +   val |= (1 << 2); >>> +   /* powerup the PLL */ >>> +   val &= ~(1 << 19); >>> +   writel(val, reg); >>> + >>> +   /* wait pll setup and locked */ >>> +   do { >>> +       val = readl(reg); >>> +       locked = val & 0x10000; >>> +       counter++; >>> +   } while (!locked && (counter < 2000)); >>> + >>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>> + >>> +   /* Switch to software configured PLL instead of refclk */ >>> +   val |= 1; >>> +   writel(val, reg); >>> + >>> +   return 0; >>> +} >>> + >>> +/* >>> + * Update the pll parameters to hardware, target to the pixpll in >>> ls2k0500 >>> + * >>> + * @this: point to the object which calling this function >>> + * @param: pointer to where the parameters passed in >>> + * >>> + * return 0 if successful. >>> + */ >>> +static int ls2k0500_pixpll_param_update(struct lsdc_pll * const this, >>> +                   struct lsdc_pll_parms const *param) >>> +{ >>> +   void __iomem *reg = this->mmio; >>> +   unsigned int counter = 0; >>> +   bool locked = false; >>> +   u32 val; >>> + >>> +   /* Bypass the software configured PLL, using refclk directly */ >>> +   val = readl(reg); >>> +   val &= ~(1 << 0); >>> +   writel(val, reg); >>> + >>> +   /* Powerdown the PLL */ >>> +   val = readl(reg); >>> +   val |= (1 << 5); >>> +   writel(val, reg); >>> + >>> +   /* Allow the software configuration */ >>> +   val |= (1 << 3); >>> +   writel(val, reg); >>> + >>> +   /* Update the pll params */ >>> +   val = (param->div_out << 24) | >>> +         (param->loopc << 16) | >>> +         (param->div_ref << 8); >>> + >>> +   writel(val, reg); >>> + >>> +   /* Powerup the PLL */ >>> +   val = readl(reg); >>> +   val &= ~(1 << 5); >>> +   writel(val, reg); >>> + >>> +   /* wait pll setup and locked */ >>> +   do { >>> +       val = readl(reg); >>> +       locked = val & 0x80; >>> +       counter++; >>> +   } while (!locked && (counter < 10000)); >>> + >>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>> + >>> +   /* Switch to software configured PLL instead of refclk */ >>> +   writel((val | 1), reg); >>> + >>> +   return 0; >>> +} >>> + >>> +static unsigned int lsdc_get_clock_rate(struct lsdc_pll * const this, >>> +                   struct lsdc_pll_parms *pout) >>> +{ >>> +   struct drm_device *ddev = this->ddev; >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   const struct lsdc_desc * const descp = ldev->descp; >>> +   unsigned int out; >>> +   union lsdc_pixpll_bitmap parms; >>> + >>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == CHIP_LS7A2000) { >>> +       struct ls7a1000_pixpll_bitmap *obj = &parms.ls7a1000; >>> + >>> +       parms.dword[0] = readl(this->mmio); >>> +       parms.dword[1] = readl(this->mmio + 4); >>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>> obj->div_out; >>> +       if (pout) { >>> +           pout->div_ref = obj->div_ref; >>> +           pout->loopc = obj->loopc; >>> +           pout->div_out = obj->div_out; >>> +       } >>> +   } else if (descp->chip == CHIP_LS2K1000) { >>> +       struct ls2k1000_pixpll_bitmap *obj = &parms.ls2k1000; >>> + >>> +       parms.dword[0] = readl(this->mmio); >>> +       parms.dword[1] = readl(this->mmio + 4); >>> +       parms.dword[2] = readl(this->mmio + 8); >>> +       parms.dword[3] = readl(this->mmio + 12); >>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>> obj->div_out; >>> +       if (pout) { >>> +           pout->div_ref = obj->div_ref; >>> +           pout->loopc = obj->loopc; >>> +           pout->div_out = obj->div_out; >>> +       } >>> +   } else if (descp->chip == CHIP_LS2K0500) { >>> +       struct ls2k0500_pixpll_bitmap *obj = &parms.ls2k0500; >>> + >>> +       parms.dword[0] = readl(this->mmio); >>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>> obj->div_out; >>> +       if (pout) { >>> +           pout->div_ref = obj->div_ref; >>> +           pout->loopc = obj->loopc; >>> +           pout->div_out = obj->div_out; >>> +       } >>> +   } else { >>> +       drm_err(ddev, "unknown chip, the driver need update\n"); >>> +       return 0; >>> +   } >>> + >>> +   return out; >>> +} >>> + >>> +static const struct lsdc_pixpll_funcs ls7a1000_pixpll_funcs = { >>> +   .setup = lsdc_pixpll_setup, >>> +   .compute = lsdc_pixpll_compute, >>> +   .update = ls7a1000_pixpll_param_update, >>> +   .get_clock_rate = lsdc_get_clock_rate, >>> +}; >>> + >>> +static const struct lsdc_pixpll_funcs ls2k1000_pixpll_funcs = { >>> +   .setup = lsdc_pixpll_setup, >>> +   .compute = lsdc_pixpll_compute, >>> +   .update = ls2k1000_pixpll_param_update, >>> +   .get_clock_rate = lsdc_get_clock_rate, >>> +}; >>> + >>> +static const struct lsdc_pixpll_funcs ls2k0500_pixpll_funcs = { >>> +   .setup = lsdc_pixpll_setup, >>> +   .compute = lsdc_pixpll_compute, >>> +   .update = ls2k0500_pixpll_param_update, >>> +   .get_clock_rate = lsdc_get_clock_rate, >>> +}; >>> + >>> +int lsdc_pixpll_init(struct lsdc_pll * const this, >>> +            struct drm_device *ddev, >>> +            unsigned int index) >>> +{ >>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>> +   const struct lsdc_desc *descp = ldev->descp; >>> + >>> +   this->ddev = ddev; >>> +   this->index = index; >>> +   this->ref_clock = LSDC_PLL_REF_CLK; >>> + >>> +   /* LS7A1000 and LS7A2000's pixpll setting registers is same */ >>> +   if (descp->chip == CHIP_LS7A2000 || descp->chip == CHIP_LS7A1000) { >>> +       if (index == 0) >>> +           this->reg_base = LS7A1000_CFG_REG_BASE + >>> LS7A1000_PIX_PLL0_REG; >>> +       else if (index == 1) >>> +           this->reg_base = LS7A1000_CFG_REG_BASE + >>> LS7A1000_PIX_PLL1_REG; >>> +       this->reg_size = 8; >>> +       this->funcs = &ls7a1000_pixpll_funcs; >>> +   } else if (descp->chip == CHIP_LS2K1000) { >>> +       if (index == 0) >>> +           this->reg_base = LS2K1000_CFG_REG_BASE + >>> LS2K1000_PIX_PLL0_REG; >>> +       else if (index == 1) >>> +           this->reg_base = LS2K1000_CFG_REG_BASE + >>> LS2K1000_PIX_PLL1_REG; >>> + >>> +       this->reg_size = 16; >>> +       this->funcs = &ls2k1000_pixpll_funcs; >>> +   } else if (descp->chip == CHIP_LS2K0500) { >>> +       if (index == 0) >>> +           this->reg_base = LS2K0500_CFG_REG_BASE + >>> LS2K0500_PIX_PLL0_REG; >>> +       else if (index == 1) >>> +           this->reg_base = LS2K0500_CFG_REG_BASE + >>> LS2K0500_PIX_PLL1_REG; >>> + >>> +       this->reg_size = 4; >>> +       this->funcs = &ls2k0500_pixpll_funcs; >>> +   } else { >>> +       drm_err(this->ddev, "unknown chip, the driver need update\n"); >>> +       return -ENOENT; >>> +   } >>> + >>> +   return this->funcs->setup(this); >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.h >>> b/drivers/gpu/drm/lsdc/lsdc_pll.h >>> new file mode 100644 >>> index 000000000000..538739f461f2 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.h >>> @@ -0,0 +1,78 @@ >>> +/* SPDX-License-Identifier: GPL-2.0 */ >>> + >>> +#ifndef __LSDC_PLL_H__ >>> +#define __LSDC_PLL_H__ >>> + >>> +#include <drm/drm_device.h> >>> + >>> +/* >>> + * Loongson Pixel PLL hardware structure >>> + * >>> + * refclk: reference frequency, 100 MHz from external oscillator >>> + * outclk: output frequency desired. >>> + * >>> + * >>> + *              L1      Fref                     Fvco    L2 >>> + * refclk  +-----------+     +------------------+ +---------+ outclk >>> + * ---+---> | Prescaler | ---> | Clock Multiplier | ---> | divider | >>> --------> >>> + *   |    +-----------+     +------------------+ +---------+    ^ >>> + *   |          ^                     ^ ^         | >>> + *   |          |                     | |         | >>> + *   |          |                     | |         | >>> + *   |       div_ref                loopc div_out      | >>> + * | | >>> + *   +--- sel_out (bypass above software configurable clock if set) >>> ----+ >>> + * >>> + * sel_out: PLL clock output selector (for debug purpose only). >>> + * >>> + * If sel_out == 1, it will take refclk as output directly, >>> + * the L1 Prescaler and the out divider will be bypassed. >>> + * >>> + * If sel_out == 0, then outclk = refclk / div_ref * loopc / div_out; >>> + * >>> + * PLL working requirements: >>> + * >>> + * 1) 20 MHz <= refclk / div_ref <= 40Mhz >>> + * 2) 1.2 GHz <= refclk /div_out * loopc <= 3.2 Ghz >>> + */ >>> + >>> +struct lsdc_pll_parms { >>> +   unsigned int div_ref; >>> +   unsigned int loopc; >>> +   unsigned int div_out; >>> +}; >>> + >>> +struct lsdc_pll; >>> + >>> +struct lsdc_pixpll_funcs { >>> +   int (*setup)(struct lsdc_pll * const this); >>> +   bool (*compute)(struct lsdc_pll * const this, >>> +           unsigned int clock, >>> +           struct lsdc_pll_parms *pout); >>> +   int (*update)(struct lsdc_pll * const this, >>> +             struct lsdc_pll_parms const *pin); >>> +   unsigned int (*get_clock_rate)(struct lsdc_pll * const this, >>> +                      struct lsdc_pll_parms *pout); >>> +}; >>> + >>> +struct lsdc_pll { >>> +   const struct lsdc_pixpll_funcs *funcs; >>> +   struct drm_device *ddev; >>> +   void __iomem *mmio; >>> + >>> +   /* PLL register offset */ >>> +   u32 reg_base; >>> +   /* PLL register size in bytes */ >>> +   u32 reg_size; >>> + >>> +   /* 100000kHz, fixed on all board found */ >>> +   unsigned int ref_clock; >>> + >>> +   unsigned int index; >>> +}; >>> + >>> +int lsdc_pixpll_init(struct lsdc_pll * const this, >>> +            struct drm_device *ddev, >>> +            unsigned int index); >>> + >>> +#endif >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.c >>> b/drivers/gpu/drm/lsdc/lsdc_regs.c >>> new file mode 100644 >>> index 000000000000..c7950c43968f >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.c >>> @@ -0,0 +1,29 @@ >>> +// SPDX-License-Identifier: GPL-2.0 >>> + >>> +#include "lsdc_drv.h" >>> +#include "lsdc_regs.h" >>> + >>> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset) >>> +{ >>> +   unsigned long flags; >>> +   u32 ret; >>> + >>> +   spin_lock_irqsave(&ldev->reglock, flags); >>> + >>> +   ret = readl(ldev->reg_base + offset); >>> + >>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>> + >>> +   return ret; >>> +} >>> + >>> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 val) >>> +{ >>> +   unsigned long flags; >>> + >>> +   spin_lock_irqsave(&ldev->reglock, flags); >>> + >>> +   writel(val, ldev->reg_base + offset); >>> + >>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>> +} >>> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.h >>> b/drivers/gpu/drm/lsdc/lsdc_regs.h >>> new file mode 100644 >>> index 000000000000..828956633137 >>> --- /dev/null >>> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.h >>> @@ -0,0 +1,296 @@ >>> +/* SPDX-License-Identifier: GPL-2.0 */ >>> + >>> +#ifndef __LSDC_REGS_H__ >>> +#define __LSDC_REGS_H__ >>> + >>> +#include <linux/bitops.h> >>> +#include <linux/types.h> >>> + >>> +/* >>> + * PIXEL PLL Reference clock >>> + */ >>> +#define LSDC_PLL_REF_CLK               100000          /* kHz */ >>> + >>> +/* >>> + * Those PLL registers are not located at DC reg bar space, >>> + * there are relative to LSXXXXX_CFG_REG_BASE. >>> + * XXXXX = 7A1000, 2K1000, 2K0500 >>> + */ >>> + >>> +/* LS2K1000 */ >>> +#define LS2K1000_PIX_PLL0_REG          0x04B0 >>> +#define LS2K1000_PIX_PLL1_REG          0x04C0 >>> +#define LS2K1000_CFG_REG_BASE          0x1fe10000 >>> + >>> +/* LS7A1000 and LS2K2000 */ >>> +#define LS7A1000_PIX_PLL0_REG          0x04B0 >>> +#define LS7A1000_PIX_PLL1_REG          0x04C0 >>> +#define LS7A1000_CFG_REG_BASE          0x10010000 >>> + >>> +/* LS2K0500 */ >>> +#define LS2K0500_PIX_PLL0_REG          0x0418 >>> +#define LS2K0500_PIX_PLL1_REG          0x0420 >>> +#define LS2K0500_CFG_REG_BASE          0x1fe10000 >>> + >>> +/* >>> + * CRTC CFG REG >>> + */ >>> +#define CFG_PIX_FMT_MASK               GENMASK(2, 0) >>> + >>> +enum lsdc_pixel_format { >>> +   LSDC_PF_NONE = 0, >>> +   LSDC_PF_ARGB4444 = 1, /* ARGB A:4 bits R/G/B: 4 bits each [16 >>> bits] */ >>> +   LSDC_PF_ARGB1555 = 2, /* ARGB A:1 bit RGB:15 bits [16 bits] */ >>> +   LSDC_PF_RGB565 = 3,   /* RGB [16 bits] */ >>> +   LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */ >>> +   LSDC_PF_RGBA8888 = 5, /* ARGB [32 bits] */ >>> +}; >>> + >>> +/* >>> + * Each crtc has two set fb address registers usable, >>> CFG_FB_IN_USING of >>> + * LSDC_CRTCx_CFG_REG specify which fb address register is currently >>> + * in using by the CRTC. CFG_PAGE_FLIP of LSDC_CRTCx_CFG_REG is used to >>> + * trigger the switch which will be finished at the very vblank. If you >>> + * want it switch to another again, you must set CFG_PAGE_FLIP again. >>> + */ >>> +#define CFG_PAGE_FLIP                  BIT(7) >>> +#define CFG_OUTPUT_EN                  BIT(8) >>> +/* CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware >>> logic */ >>> +#define CFG_PANEL_SWITCH               BIT(9) >>> +/* Indicate witch fb addr reg is in using, currently */ >>> +#define CFG_FB_IN_USING                BIT(11) >>> +#define CFG_GAMMA_EN                   BIT(12) >>> + >>> +/* CRTC get soft reset if voltage level change from 1 -> 0 */ >>> +#define CFG_RESET_N                    BIT(20) >>> + >>> +#define CFG_HSYNC_EN                   BIT(30) >>> +#define CFG_HSYNC_INV                  BIT(31) >>> +#define CFG_VSYNC_EN                   BIT(30) >>> +#define CFG_VSYNC_INV                  BIT(31) >>> + >>> +/* THE DMA step of the DC in LS7A2000 is configurable */ >>> +#define LSDC_DMA_STEP_MASK             GENMASK(17, 16) >>> +enum lsdc_dma_steps_supported { >>> +   LSDC_DMA_STEP_256_BYTES = 0, >>> +   LSDC_DMA_STEP_128_BYTES = 1, >>> +   LSDC_DMA_STEP_64_BYTES = 2, >>> +   LSDC_DMA_STEP_32_BYTES = 3, >>> +}; >>> + >>> +/******** CRTC0 & DVO0 ********/ >>> +#define LSDC_CRTC0_CFG_REG             0x1240 >>> +#define LSDC_CRTC0_FB0_LO_ADDR_REG     0x1260 >>> +#define LSDC_CRTC0_FB0_HI_ADDR_REG     0x15A0 >>> +#define LSDC_CRTC0_FB1_LO_ADDR_REG     0x1580 >>> +#define LSDC_CRTC0_FB1_HI_ADDR_REG     0x15C0 >>> +#define LSDC_CRTC0_STRIDE_REG          0x1280 >>> +#define LSDC_CRTC0_FB_ORIGIN_REG       0x1300 >>> +#define LSDC_CRTC0_HDISPLAY_REG        0x1400 >>> +#define LSDC_CRTC0_HSYNC_REG           0x1420 >>> +#define LSDC_CRTC0_VDISPLAY_REG        0x1480 >>> +#define LSDC_CRTC0_VSYNC_REG           0x14A0 >>> +#define LSDC_CRTC0_GAMMA_INDEX_REG     0x14E0 >>> +#define LSDC_CRTC0_GAMMA_DATA_REG      0x1500 >>> + >>> +/******** CTRC1 & DVO1 ********/ >>> +#define LSDC_CRTC1_CFG_REG             0x1250 >>> +#define LSDC_CRTC1_FB0_LO_ADDR_REG     0x1270 >>> +#define LSDC_CRTC1_FB0_HI_ADDR_REG     0x15B0 >>> +#define LSDC_CRTC1_FB1_LO_ADDR_REG     0x1590 >>> +#define LSDC_CRTC1_FB1_HI_ADDR_REG     0x15D0 >>> +#define LSDC_CRTC1_STRIDE_REG          0x1290 >>> +#define LSDC_CRTC1_FB_ORIGIN_REG       0x1310 >>> +#define LSDC_CRTC1_HDISPLAY_REG        0x1410 >>> +#define LSDC_CRTC1_HSYNC_REG           0x1430 >>> +#define LSDC_CRTC1_VDISPLAY_REG        0x1490 >>> +#define LSDC_CRTC1_VSYNC_REG           0x14B0 >>> +#define LSDC_CRTC1_GAMMA_INDEX_REG     0x14F0 >>> +#define LSDC_CRTC1_GAMMA_DATA_REG      0x1510 >>> + >>> +/* >>> + * LS7A2000 has the hardware which record the scan position of the CRTC >>> + * [31:16] : current X position, [15:0] : current Y position >>> + */ >>> +#define LSDC_CRTC0_SCAN_POS_REG        0x14C0 >>> +#define LSDC_CRTC1_SCAN_POS_REG        0x14D0 >>> + >>> +/* >>> + * LS7A2000 has the hardware which count the number of vblank generated >>> + */ >>> +#define LSDC_CRTC0_VSYNC_COUNTER_REG   0x1A00 >>> +#define LSDC_CRTC1_VSYNC_COUNTER_REG   0x1A10 >>> + >>> +/* In all, LSDC_CRTC1_XXX_REG - LSDC_CRTC0_XXX_REG = 0x10 */ >>> + >>> +/* >>> + * There is only one hardware cursor unit in ls7a1000, ls2k1000 and >>> ls2k0500. >>> + * well, ls7a2000 has two hardware cursor unit. >>> + */ >>> +#define CURSOR_FORMAT_MASK             GENMASK(1, 0) >>> +enum lsdc_cursor_format { >>> +   CURSOR_FORMAT_DISABLE = 0, >>> +   CURSOR_FORMAT_MONOCHROME = 1, >>> +   CURSOR_FORMAT_ARGB8888 = 2, >>> +}; >>> + >>> +#define CURSOR_SIZE_64X64              BIT(2) >>> +#define CURSOR_LOCATION                BIT(4) >>> + >>> +#define LSDC_CURSOR0_CFG_REG           0x1520 >>> +#define LSDC_CURSOR0_ADDR_LO_REG       0x1530 >>> +#define LSDC_CURSOR0_ADDR_HI_REG       0x15e0 >>> +#define LSDC_CURSOR0_POSITION_REG      0x1540 >>> +#define LSDC_CURSOR0_BG_COLOR_REG      0x1550 /* background color */ >>> +#define LSDC_CURSOR0_FG_COLOR_REG      0x1560 /* foreground color */ >>> + >>> +#define LSDC_CURSOR1_CFG_REG           0x1670 >>> +#define LSDC_CURSOR1_ADDR_LO_REG       0x1680 >>> +#define LSDC_CURSOR1_ADDR_HI_REG       0x16e0 >>> +#define LSDC_CURSOR1_POSITION_REG      0x1690 /* [31:16] Y, [15:0] >>> X */ >>> +#define LSDC_CURSOR1_BG_COLOR_REG      0x16A0 /* background color */ >>> +#define LSDC_CURSOR1_FG_COLOR_REG      0x16B0 /* foreground color */ >>> + >>> +/* >>> + * DC Interrupt Control Register, 32bit, Address Offset: 1570 >>> + * >>> + * Bits 15:0 inidicate the interrupt status >>> + * Bits 31:16 control enable interrupts corresponding to bit 15:0 or >>> not >>> + * Write 1 to enable, write 0 to disable >>> + * >>> + * RF: Read Finished >>> + * IDBU: Internal Data Buffer Underflow >>> + * IDBFU: Internal Data Buffer Fatal Underflow >>> + * CBRF: Cursor Buffer Read Finished Flag, no use. >>> + * >>> + * >>> +-------+--------------------------+-------+--------+--------+-------+ >>> + * | 31:27 |        26:16           | 15:11 |  10  |  9 |  8  | >>> + * >>> +-------+--------------------------+-------+--------+--------+-------+ >>> + * | N/A | Interrupt Enable Control | N/A | IDBFU0 | IDBFU1 | >>> IDBU0 | >>> + * >>> +-------+--------------------------+-------+--------+--------+-------+ >>> + * >>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>> + * |  7  | 6 | 5 | 4  |  3   |  2   |  1   | 0   | >>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>> + * | IDBU1 | RF0 | RF1 | CRRF | HSYNC0 | VSYNC0 | HSYNC1 | VSYNC1 | >>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>> + */ >>> + >>> +#define LSDC_INT_REG                   0x1570 >>> + >>> +#define INT_CRTC0_VSYNC                BIT(2) >>> +#define INT_CRTC0_HSYNC                BIT(3) >>> +#define INT_CRTC0_RF                   BIT(6) >>> +#define INT_CRTC0_IDBU                 BIT(8) >>> +#define INT_CRTC0_IDBFU                BIT(10) >>> + >>> +#define INT_CRTC1_VSYNC                BIT(0) >>> +#define INT_CRTC1_HSYNC                BIT(1) >>> +#define INT_CRTC1_RF                   BIT(5) >>> +#define INT_CRTC1_IDBU                 BIT(7) >>> +#define INT_CRTC1_IDBFU                BIT(9) >>> + >>> +#define INT_CRTC0_VS_EN                BIT(18) >>> +#define INT_CRTC0_HS_EN                BIT(19) >>> +#define INT_CRTC0_RF_EN                BIT(22) >>> +#define INT_CRTC0_IDBU_EN              BIT(24) >>> +#define INT_CRTC0_IDBFU_EN             BIT(26) >>> + >>> +#define INT_CRTC1_VS_EN                BIT(16) >>> +#define INT_CRTC1_HS_EN                BIT(17) >>> +#define INT_CRTC1_RF_EN                BIT(21) >>> +#define INT_CRTC1_IDBU_EN              BIT(23) >>> +#define INT_CRTC1_IDBFU_EN             BIT(25) >>> + >>> +#define INT_STATUS_MASK                GENMASK(15, 0) >>> + >>> +/* >>> + * LS7A1000/LS7A2000 have 4 gpios which are used to emulated I2C. >>> + * They are under control of the LS7A_DC_GPIO_DAT_REG and >>> LS7A_DC_GPIO_DIR_REG >>> + * register, Those GPIOs has no relationship whth the GPIO hardware >>> on the >>> + * bridge chip itself. Those offsets are relative to DC register >>> base address >>> + * >>> + * LS2k1000 and LS2K0500 don't have those registers, they use >>> hardware i2c >>> + * or general GPIO emulated i2c from linux i2c subsystem. >>> + * >>> + * GPIO data register, address offset: 0x1650 >>> + *  +---------------+-----------+-----------+ >>> + *  | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | >>> + *  +---------------+-----------+-----------+ >>> + *  |              |   DVO1  |   DVO0  | >>> + *  +     N/A     +-----------+-----------+ >>> + *  |              | SCL | SDA | SCL | SDA | >>> + *  +---------------+-----------+-----------+ >>> + */ >>> +#define LS7A_DC_GPIO_DAT_REG           0x1650 >>> + >>> +/* >>> + * GPIO Input/Output direction control register, address offset: >>> 0x1660 >>> + */ >>> +#define LS7A_DC_GPIO_DIR_REG           0x1660 >>> + >>> +/* >>> + * LS7A2000 has two built-in HDMI Encoder and one VGA encoder >>> + */ >>> + >>> +/* >>> + * Number of continuous packets may be present >>> + * in HDMI hblank and vblank zone, should >= 48 >>> + */ >>> +#define LSDC_HDMI0_ZONE_REG            0x1700 >>> +#define LSDC_HDMI1_ZONE_REG            0x1710 >>> + >>> +#define HDMI_INTERFACE_EN              BIT(0) >>> +#define HDMI_PACKET_EN                 BIT(1) >>> +#define HDMI_AUDIO_EN                  BIT(2) >>> +#define HDMI_VIDEO_PREAMBLE_MASK       GENMASK(7, 4) >>> +#define HDMI_HW_I2C_EN                 BIT(8) >>> +#define HDMI_CTL_PERIOD_MODE           BIT(9) >>> +#define LSDC_HDMI0_CTRL_REG            0x1720 >>> +#define LSDC_HDMI1_CTRL_REG            0x1730 >>> + >>> +#define HDMI_PHY_EN                    BIT(0) >>> +#define HDMI_PHY_RESET_N               BIT(1) >>> +#define HDMI_PHY_TERM_L_EN             BIT(8) >>> +#define HDMI_PHY_TERM_H_EN             BIT(9) >>> +#define HDMI_PHY_TERM_DET_EN           BIT(10) >>> +#define HDMI_PHY_TERM_STATUS           BIT(11) >>> +#define LSDC_HDMI0_PHY_CTRL_REG        0x1800 >>> +#define LSDC_HDMI1_PHY_CTRL_REG        0x1810 >>> + >>> +/* >>> + * 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 | >>> + * +--------------------------------------------------------+ >>> + */ >>> +#define LSDC_HDMI0_PHY_PLL_REG         0x1820 >>> +#define LSDC_HDMI1_PHY_PLL_REG         0x1830 >>> + >>> +#define HDMI_PLL_ENABLE                BIT(0) >>> +#define HDMI_PLL_LOCKED                BIT(16) >>> +#define HDMI_PLL_BYPASS                BIT(17) >>> + >>> +#define HDMI_PLL_IDF_SHIFT             1 >>> +#define HDMI_PLL_IDF_MASK              GENMASK(5, 1) >>> +#define HDMI_PLL_LF_SHIFT              6 >>> +#define HDMI_PLL_LF_MASK               GENMASK(12, 6) >>> +#define HDMI_PLL_ODF_SHIFT             13 >>> +#define HDMI_PLL_ODF_MASK              GENMASK(15, 13) >>> + >>> +/* LS7A2000 have hpd support */ >>> +#define LSDC_HDMI_HPD_STATUS_REG       0x1BA0 >>> +#define HDMI0_HPD_FLAG                 BIT(0) >>> +#define HDMI1_HPD_FLAG                 BIT(1) >>> + >>> +#endif >> > -- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Ivo Totev
On 2023/2/17 17:20, Thomas Zimmermann wrote: > Hi > > Am 13.02.23 um 05:24 schrieb Sui jingfeng: > [...] >> >>> Here's where we select the chip: >>> >>> >>> https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_drv.c#L227 >>> >>> >>> There's a source file for each chip and the common source code is in >>> >> I will follow this method at next version, but our hardware and bios >> engineers is ugly, they often made the chip indistinguishable. >> >> ls7a1000 and ls2k1000 have the same pci device id, ls7a2000 and >> ls2k2000 have the same pci device id, >> >> even the revision id is same. I have no choice but to rely on DT or > > Relying on DT is not a problem. Many drivers do that. > >> kernel cmd passing parameters. > > It's also ok to to read version information from a status register. If > you really have models that cannot be detect, that's more of a problem. > >> >> many reviewers and maintainer seem not happy about this. > > By maintainers, you mean maintainers in other parts of the Linux > kernel? Did they offer a solution on how to deal with this situation? > Do you have a link to sucha discussion? > >> >>> https://elixir.bootlin.com/linux/latest/source/drivers/gpu/drm/mgag200/mgag200_mode.c >>> >>> >>> >>>> +   .reset = lsdc_crtc_reset, >>>> +   .destroy = drm_crtc_cleanup, >>>> +   .set_config = drm_atomic_helper_set_config, >>>> +   .page_flip = drm_atomic_helper_page_flip, >>>> +   .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, >>>> +   .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, >>>> +   .get_vblank_counter = lsdc_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, >>>> +}; >>>> + >>>> +static enum drm_mode_status >>>> +lsdc_crtc_mode_valid(struct drm_crtc *crtc, >>>> +            const struct drm_display_mode *mode) >>>> +{ >>>> +   struct drm_device *ddev = crtc->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   if (mode->hdisplay > descp->max_width) >>>> +       return MODE_BAD_HVALUE; >>>> +   if (mode->vdisplay > descp->max_height) >>>> +       return MODE_BAD_VVALUE; >>>> + >>>> +   if (mode->clock > descp->max_pixel_clk) { >>>> +       drm_dbg(ddev, "mode %dx%d, pixel clock=%d is too high\n", >>>> +           mode->hdisplay, mode->vdisplay, mode->clock); >>>> +       return MODE_CLOCK_HIGH; >>>> +   } >>>> + >>>> +   if ((mode->hdisplay * 4) % descp->pitch_align) { >>>> +       drm_dbg(ddev, "stride is require to not %u bytes aligned\n", >>>> +           descp->pitch_align); >>>> +       return MODE_BAD; >>>> +   } >>>> + >>>> +   return MODE_OK; >>>> +} >>>> + >>>> +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc, >>>> +                   struct drm_crtc_state *state) >>>> +{ >>>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>>> +   struct lsdc_pll *pixpll = &dispipe->pixpll; >>>> +   const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; >>>> +   struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); >>>> +   bool ret; >>>> + >>>> +   ret = pfuncs->compute(pixpll, state->mode.clock, >>>> &priv_state->pparms); >>>> +   if (ret) >>>> +       return 0; >>>> + >>>> +   drm_warn(crtc->dev, "failed find PLL parameters for %u\n", >>>> state->mode.clock); >>>> + >>>> +   return -EINVAL; >>>> +} >>>> + >>>> +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; /* no mode checks if CRTC is being disabled */ >>>> + >>>> +   return lsdc_pixpll_atomic_check(crtc, crtc_state); >>>> +} >>>> + >>>> +static void lsdc_update_pixclk(struct lsdc_display_pipe *dispipe, >>>> +                  struct lsdc_crtc_state *priv_state) >>>> +{ >>>> +   struct lsdc_pll *pixpll = &dispipe->pixpll; >>>> +   const struct lsdc_pixpll_funcs *clkfunc = pixpll->funcs; >>>> + >>>> +   clkfunc->update(pixpll, &priv_state->pparms); >>>> +} >>>> + >>>> +static void lsdc_crtc_helper_mode_set_nofb(struct drm_crtc *crtc) >>>> +{ >>>> +   struct drm_device *ddev = crtc->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   struct drm_display_mode *mode = &crtc->state->mode; >>>> +   struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); >>>> +   struct lsdc_crtc_state *priv_state = >>>> to_lsdc_crtc_state(crtc->state); >>>> +   unsigned int index = dispipe->index; >>>> +   u32 h_sync, v_sync, h_val, v_val; >>>> +   u32 val; >>>> + >>>> +   /* 26:16 total pixels, 10:0 visiable pixels, in horizontal */ >>>> +   h_val = (mode->crtc_htotal << 16) | mode->crtc_hdisplay; >>>> + >>>> +   /* 26:16 total pixels, 10:0 visiable pixels, in vertical */ >>>> +   v_val = (mode->crtc_vtotal << 16) | mode->crtc_vdisplay; >>>> +   /* 26:16 hsync end, 10:0 hsync start, bit 30 is hsync enable */ >>>> +   h_sync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start >>>> | CFG_HSYNC_EN; >>>> +   if (mode->flags & DRM_MODE_FLAG_NHSYNC) >>>> +       h_sync |= CFG_HSYNC_INV; >>>> + >>>> +   /* 26:16 vsync end, 10:0 vsync start, bit 30 is vsync enable */ >>>> +   v_sync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start >>>> | CFG_VSYNC_EN; >>>> +   if (mode->flags & DRM_MODE_FLAG_NVSYNC) >>>> +       v_sync |= CFG_VSYNC_INV; >>>> + >>>> +   if (index == 0) { >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG, h_val); >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG, v_val); >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG, h_sync); >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG, v_sync); >>>> + >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>> +       val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>> +   } else if (index == 1) { >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_HDISPLAY_REG, h_val); >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_VDISPLAY_REG, v_val); >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_HSYNC_REG, h_sync); >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_VSYNC_REG, v_sync); >>>> + >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>> +       val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>> +   } >>> >>> Same here as with vblank: have one helper for each index and use >>> different _funcs structures for each pipeline. >>> >> Well, I will revise lsdc driver with this idea in mind. >>>> + >>>> +   drm_dbg(ddev, "%s modeset: %ux%u\n", >>>> +       crtc->name, mode->hdisplay, mode->vdisplay); >>>> + >>>> +   lsdc_update_pixclk(dispipe, priv_state); >>>> +} >>>> + >>>> +static void lsdc_enable_display(struct lsdc_device *ldev, unsigned >>>> int index) >>>> +{ >>>> +   u32 val; >>>> + >>>> +   if (index == 0) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>> +       val |= CFG_OUTPUT_EN; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>> +       return; >>>> +   } >>>> + >>>> +   if (index == 1) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>> +       val |= CFG_OUTPUT_EN; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>> +       return; >>>> +   } >>>> +} >>>> + >>>> +static void lsdc_disable_display(struct lsdc_device *ldev, >>>> unsigned int index) >>>> +{ >>>> +   u32 val; >>>> + >>>> +   if (index == 0) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>> +       val &= ~CFG_OUTPUT_EN; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>> +       return; >>>> +   } >>>> + >>>> +   if (index == 1) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>> +       val &= ~CFG_OUTPUT_EN; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>> +       return; >>>> +   } >>>> +} >>>> + >>>> +static void lsdc_crtc_helper_atomic_enable(struct drm_crtc *crtc, >>>> +                      struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_device *ddev = crtc->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> + >>>> +   drm_crtc_vblank_on(crtc); >>>> + >>>> +   lsdc_enable_display(ldev, drm_crtc_index(crtc)); >>>> + >>>> +   drm_dbg(ddev, "%s: enabled\n", crtc->name); >>>> +} >>>> + >>>> +static void lsdc_crtc_helper_atomic_disable(struct drm_crtc *crtc, >>>> +                       struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_device *ddev = crtc->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> + >>>> +   lsdc_disable_display(ldev, drm_crtc_index(crtc)); >>>> + >>>> +   drm_crtc_wait_one_vblank(crtc); >>>> + >>>> +   drm_crtc_vblank_off(crtc); >>>> + >>>> +   drm_dbg(ddev, "%s: disabled\n", crtc->name); >>>> +} >>>> + >>>> +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 drm_device *ddev = crtc->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   unsigned int index = drm_crtc_index(crtc); >>>> +   int line, vsw, vbp, vactive_start, vactive_end, vfp_end; >>>> +   u32 val = 0; >>>> + >>>> +   vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; >>>> +   vbp = mode->crtc_vtotal - mode->crtc_vsync_end; >>>> + >>>> +   vactive_start = vsw + vbp + 1; >>>> + >>>> +   vactive_end = vactive_start + mode->crtc_vdisplay; >>>> + >>>> +   /* last scan line before VSYNC */ >>>> +   vfp_end = mode->crtc_vtotal; >>>> + >>>> +   if (stime) >>>> +       *stime = ktime_get(); >>>> + >>>> +   if (index == 0) >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); >>>> +   else if (index == 1) >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); >>>> + >>>> +   line = (val & 0xffff); >>>> + >>>> +   if (line < vactive_start) >>>> +       line -= vactive_start; >>>> +   else if (line > vactive_end) >>>> +       line = line - vfp_end - vactive_start; >>>> +   else >>>> +       line -= vactive_start; >>>> + >>>> +   *vpos = line; >>>> +   *hpos = val >> 16; >>>> + >>>> +   if (etime) >>>> +       *etime = ktime_get(); >>>> + >>>> +   return true; >>>> +} >>>> + >>>> +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { >>>> +   .mode_valid = lsdc_crtc_mode_valid, >>>> +   .mode_set_nofb = lsdc_crtc_helper_mode_set_nofb, >>>> +   .atomic_enable = lsdc_crtc_helper_atomic_enable, >>>> +   .atomic_disable = lsdc_crtc_helper_atomic_disable, >>>> +   .atomic_check = lsdc_crtc_helper_atomic_check, >>>> +   .atomic_flush = lsdc_crtc_atomic_flush, >>>> +   .get_scanout_position = lsdc_crtc_get_scanout_position, >>>> +}; >>>> + >>>> +int lsdc_crtc_init(struct drm_device *ddev, >>>> +          struct drm_crtc *crtc, >>>> +          unsigned int index, >>>> +          struct drm_plane *primary, >>>> +          struct drm_plane *cursor) >>>> +{ >>>> +   int ret; >>>> + >>>> +   ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor, >>>> +                   &lsdc_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) >>>> +       drm_warn(ddev, "set the gamma table size failed\n"); >>>> + >>>> +   drm_crtc_enable_color_mgmt(crtc, 0, false, 256); >>> >>> Maybe leave this out for now. You need to handle the property in >>> your driver. I don't see code for that. >>> >>> Color management should probably go into a separate patch. >>> >> Ok, I'm agree to leave gamma support to a separate patch >>>> + >>>> +   return 0; >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>>> b/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>>> new file mode 100644 >>>> index 000000000000..176ae4e11d1c >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_debugfs.c >>>> @@ -0,0 +1,191 @@ >>>> +// SPDX-License-Identifier: GPL-2.0+ >>>> + >>>> +#include <drm/drm_debugfs.h> >>>> +#include <drm/drm_managed.h> >>>> +#include <drm/drm_gem_vram_helper.h> >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> +#include "lsdc_pll.h" >>>> + >>>> +#ifdef CONFIG_DEBUG_FS >>>> +static int lsdc_show_clock(struct seq_file *m, void *arg) >>>> +{ >>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>> +   struct drm_device *ddev = node->minor->dev; >>>> +   struct drm_crtc *crtc; >>>> + >>>> +   drm_for_each_crtc(crtc, ddev) { >>>> +       struct lsdc_display_pipe *pipe = crtc_to_display_pipe(crtc); >>>> +       struct lsdc_pll *pixpll = &pipe->pixpll; >>>> +       const struct lsdc_pixpll_funcs *funcs = pixpll->funcs; >>>> +       struct drm_display_mode *adj = &crtc->state->mode; >>>> +       struct lsdc_pll_parms parms; >>>> +       unsigned int out_khz; >>>> + >>>> +       out_khz = funcs->get_clock_rate(pixpll, &parms); >>>> + >>>> +       seq_printf(m, "Display pipe %u: %dx%d\n", >>>> +              pipe->index, adj->hdisplay, adj->vdisplay); >>>> + >>>> +       seq_printf(m, "Frequency actually output: %u kHz\n", >>>> out_khz); >>>> +       seq_printf(m, "Pixel clock required: %d kHz\n", adj->clock); >>>> +       seq_printf(m, "diff: %d kHz\n", adj->clock); >>>> + >>>> +       seq_printf(m, "div_ref=%u, loopc=%u, div_out=%u\n", >>>> +              parms.div_ref, parms.loopc, parms.div_out); >>>> + >>>> +       seq_printf(m, "hsync_start=%d, hsync_end=%d, htotal=%d\n", >>>> +              adj->hsync_start, adj->hsync_end, adj->htotal); >>>> +       seq_printf(m, "vsync_start=%d, vsync_end=%d, vtotal=%d\n\n", >>>> +              adj->vsync_start, adj->vsync_end, adj->vtotal); >>>> +   } >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_show_mm(struct seq_file *m, void *arg) >>>> +{ >>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>> +   struct drm_device *ddev = node->minor->dev; >>>> +   struct drm_printer p = drm_seq_file_printer(m); >>>> + >>>> + drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +#define REGDEF(reg) { __stringify_1(LSDC_##reg##_REG), >>>> LSDC_##reg##_REG } >>>> +static const struct { >>>> +   const char *name; >>>> +   u32 reg_offset; >>>> +} lsdc_regs_array[] = { >>>> +   REGDEF(CURSOR0_CFG), >>>> +   REGDEF(CURSOR0_ADDR_LO), >>>> +   REGDEF(CURSOR0_ADDR_HI), >>>> +   REGDEF(CURSOR0_POSITION), >>>> +   REGDEF(CURSOR0_BG_COLOR), >>>> +   REGDEF(CURSOR0_FG_COLOR), >>>> +   REGDEF(INT), >>>> +   REGDEF(CRTC0_CFG), >>>> +   REGDEF(CRTC0_STRIDE), >>>> +   REGDEF(CRTC0_FB_ORIGIN), >>>> +   REGDEF(CRTC0_HDISPLAY), >>>> +   REGDEF(CRTC0_HSYNC), >>>> +   REGDEF(CRTC0_VDISPLAY), >>>> +   REGDEF(CRTC0_VSYNC), >>>> +   REGDEF(CRTC0_GAMMA_INDEX), >>>> +   REGDEF(CRTC0_GAMMA_DATA), >>>> +   REGDEF(CRTC1_CFG), >>>> +   REGDEF(CRTC1_STRIDE), >>>> +   REGDEF(CRTC1_FB_ORIGIN), >>>> +   REGDEF(CRTC1_HDISPLAY), >>>> +   REGDEF(CRTC1_HSYNC), >>>> +   REGDEF(CRTC1_VDISPLAY), >>>> +   REGDEF(CRTC1_VSYNC), >>>> +   REGDEF(CRTC1_GAMMA_INDEX), >>>> +   REGDEF(CRTC1_GAMMA_DATA), >>>> +}; >>>> + >>>> +static int lsdc_show_regs(struct seq_file *m, void *arg) >>>> +{ >>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>> +   struct drm_device *ddev = node->minor->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   int i; >>>> + >>>> +   for (i = 0; i < ARRAY_SIZE(lsdc_regs_array); i++) { >>>> +       u32 offset = lsdc_regs_array[i].reg_offset; >>>> +       const char *name = lsdc_regs_array[i].name; >>>> + >>>> +       seq_printf(m, "%s (0x%04x): 0x%08x\n", >>>> +              name, offset, lsdc_rreg32(ldev, offset)); >>>> +   } >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_show_vblank_counter(struct seq_file *m, void *arg) >>>> +{ >>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>> +   struct drm_device *ddev = node->minor->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> + >>>> +   seq_printf(m, "CRTC-0 vblank counter: %08u\n", >>>> +          lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG)); >>>> + >>>> +   seq_printf(m, "CRTC-1 vblank counter: %08u\n", >>>> +          lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG)); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_show_scan_position(struct seq_file *m, void *arg) >>>> +{ >>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>> +   struct drm_device *ddev = node->minor->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   u32 val0 = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); >>>> +   u32 val1 = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); >>>> + >>>> +   seq_printf(m, "CRTC-0: x: %08u, y: %08u\n", >>>> +          val0 >> 16, val0 & 0xFFFF); >>>> + >>>> +   seq_printf(m, "CRTC-1: x: %08u, y: %08u\n", >>>> +          val1 >> 16, val1 & 0xFFFF); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_show_fb_addr(struct seq_file *m, void *arg) >>>> +{ >>>> +   struct drm_info_node *node = (struct drm_info_node *)m->private; >>>> +   struct drm_device *ddev = node->minor->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   u32 lo, hi; >>>> +   u32 val; >>>> + >>>> +   val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>> +   if (val & CFG_FB_IN_USING) { >>>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_LO_ADDR_REG); >>>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_HI_ADDR_REG); >>>> +       seq_printf(m, "CRTC-0 using fb1: 0x%x:%x\n", hi, lo); >>>> +   } else { >>>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_LO_ADDR_REG); >>>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_HI_ADDR_REG); >>>> +       seq_printf(m, "CRTC-0 using fb0: 0x%x:%x\n", hi, lo); >>>> +   } >>>> + >>>> +   val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>> +   if (val & CFG_FB_IN_USING) { >>>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_LO_ADDR_REG); >>>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_HI_ADDR_REG); >>>> +       seq_printf(m, "CRTC-1 using fb1: 0x%x:%x\n", hi, lo); >>>> +   } else { >>>> +       lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_LO_ADDR_REG); >>>> +       hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_HI_ADDR_REG); >>>> +       seq_printf(m, "CRTC-1 using fb0: 0x%x:%x\n", hi, lo); >>>> +   } >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static struct drm_info_list lsdc_debugfs_list[] = { >>>> +   { "clocks",  lsdc_show_clock, 0 }, >>>> +   { "mm",      lsdc_show_mm, 0, NULL }, >>>> +   { "regs",    lsdc_show_regs, 0 }, >>>> +   { "vblanks", lsdc_show_vblank_counter, 0, NULL }, >>>> +   { "scan_pos", lsdc_show_scan_position, 0, NULL }, >>>> +   { "fb_addr", lsdc_show_fb_addr, 0, NULL }, >>>> +}; >>>> + >>>> +#endif >>>> + >>>> +void lsdc_debugfs_init(struct drm_minor *minor) >>>> +{ >>>> +#ifdef CONFIG_DEBUG_FS >>>> +   drm_debugfs_create_files(lsdc_debugfs_list, >>>> +                ARRAY_SIZE(lsdc_debugfs_list), >>>> +                minor->debugfs_root, >>>> +                minor); >>>> +#endif >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.c >>>> b/drivers/gpu/drm/lsdc/lsdc_drv.c >>>> new file mode 100644 >>>> index 000000000000..3bd457e6e980 >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.c >>>> @@ -0,0 +1,635 @@ >>>> +// SPDX-License-Identifier: GPL-2.0+ >>>> +/* >>>> + * KMS driver for Loongson display controller >>>> + * Copyright (C) 2022 Loongson Corporation >>>> + * >>>> + * Authors: >>>> + *     Li Yi <liyi@loongson.cn> >>>> + *     Li Chenyang <lichenyang@loongson.cn> >>>> + *     Sui Jingfeng <suijingfeng@loongson.cn> >>>> + */ >>>> + >>>> +#include <linux/module.h> >>>> +#include <linux/pci.h> >>>> +#include <linux/of_address.h> >>>> +#include <drm/drm_aperture.h> >>>> +#include <drm/drm_vblank.h> >>>> +#include <drm/drm_fb_helper.h> >>>> +#include <drm/drm_gem_vram_helper.h> >>>> +#include <drm/drm_gem_framebuffer_helper.h> >>>> +#include <drm/drm_modeset_helper.h> >>>> +#include <drm/drm_atomic_helper.h> >>>> +#include <drm/drm_probe_helper.h> >>>> +#include <drm/drm_fbdev_generic.h> >>>> +#include "lsdc_drv.h" >>> >>> Alphebetical order. >> I will fix this problem at next version >>> >>>> + >>>> +static int lsdc_chip = -1; >>>> +MODULE_PARM_DESC(chip, "override chip id (-1 = probed by driver"); >>>> +module_param_named(chip, lsdc_chip, int, 0644); >>> >>> That's a development option. Better not add it to the published driver. >> >> Without this, compile with W=1 generate warnings. and removing chip >> descriptions for Loongson SoC >> >> is not wanted either. this option can be removed if DT support is >> upstream in the future. > > I'd strongly prefer to rely on DT for this. Because this setting is > nothing that we want users to modify. > >> >> It allow us to developing driver before DT or ACPI support is ready. >> Otherwise we have no way developing >> >> drivers for loongson SoC beforehand. > > I understand, I just don't think that the parameter should be in the > official Linux kernel. It's an option for your internal development team. > > It does not make sense to merge the DRM driver before the DT or ACPI > support is ready. > >> >>>> + >>>> +/* >>>> + * ls7a1000 and ls7a2000 is not only used with loongson desktop >>>> class CPU >>>> + * but also loongson server class CPUs (LS3C5000@2.2GHz 16 core). >>>> Loongson >>>> + * server products generally don't has a dedicated vram mounted >>>> for cost >>>> + * reason, it will use aspeed bmc as its display on its final and >>>> formal >>>> + * product. lsdc.has_vram=0 is mainly be use in developing phase >>>> of the >>>> + * product. When a board don't has a dedicated vram mounted, we >>>> can put >>>> + * the framebuffer on system RAM. >>>> + */ >>>> +static int lsdc_has_vram = -1; >>>> +MODULE_PARM_DESC(has_vram, "has on-board gpu memory in reality (0 >>>> = no, -1 = default"); >>>> +module_param_named(has_vram, lsdc_has_vram, int, 0644); >>> >>> Same here. That's for development. >> >> Err, we truly very need this feature, >> >> 1) ASPEED BMC card is removable, it can be unplug from the mother >> board of Loongson Server. >> >> leaving this option there, we still have change enter X server >> graphic environment. > > Why don't you detect the presence of the BMC card at runtime. > >> >> >> 2) We need developing drivers for loongson SoC on loongson PC >> platform, we may deliberately don't >> >> use on-board vram. there a need to let TTM manage cached reserved >> RAM, because such device don't >> >> have vram. We have achieve this goal on ls2k2000 SoC. >> >> Otherwise we have to write two piece of driver, one is DMA helper >> base, the other is TTM based, >> >> we doing so at downstream kernel. > > I don't understand. You can still have an internal patch that provides > this option to your development team. > > The problem with module options is that they are hard to remove. Users > start relying on them and complain when you remove them. That happens > even if you provide a better solution. And some options, such as the > chip setting, can possibly damage the user's hardware. So it's best to > not provide options in the first place. > Ok, I understand. As a compromise, we will drop this two module options at next version. thanks you point out the reason. >> >>> >>>> + >>>> +#define DRIVER_AUTHOR       "Sui Jingfeng <suijingfeng@loongson.cn>" >>>> +#define DRIVER_NAME       "lsdc" >>>> +#define DRIVER_DESC       "drm driver for loongson's display >>>> controller" >>>> +#define DRIVER_DATE       "20220701" >>>> +#define DRIVER_MAJOR       1 >>>> +#define DRIVER_MINOR       0 >>>> +#define DRIVER_PATCHLEVEL   0 >>>> + >>>> +static const struct lsdc_desc dc_in_ls2k0500 = { >>> >>> Those description structs are a two-edged sword IMHO. >>> >>>> +   .chip = CHIP_LS2K0500, >>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>> +   .max_pixel_clk = 200000, >>>> +   .max_width = 2048, >>>> +   .max_height = 2048, >>>> +   .hw_cursor_w = 32, >>>> +   .hw_cursor_h = 32, >>>> +   .pitch_align = 256, >>>> +   .mc_bits = 40, >>> >>> These are device constants are probably fine. >>> >>>> +   .num_of_hw_cursor = 1, >>>> +   .has_vblank_counter = false, >>>> +   .has_builtin_i2c = false, >>>> +   .has_vram = false, >>>> +   .has_hpd_reg = false, >>>> +   .is_soc = true, >>> >>> But from a quick look through the code, these settings affect >>> control flow and/or modesetting pipelines. They are often better >>> implemented by dedicated helpers as outlined in my comments about >>> vblank and mgag200. >> >> Ok, mgag200 is for server mainly, its variant are not so different as >> loongson does, >> >> at least they all have vram, we have to put more attention to turn >> drm memory manager. >> >> I will following your advice by introducing more helpers at next >> version. >> >>> >>> >>>> +}; >>>> + >>>> +static const struct lsdc_desc dc_in_ls2k1000 = { >>>> +   .chip = CHIP_LS2K1000, >>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>> +   .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, >>>> +   .has_builtin_i2c = false, >>>> +   .has_vram = false, >>>> +   .has_hpd_reg = false, >>>> +   .is_soc = true, >>>> +}; >>>> + >>>> +static const struct lsdc_desc dc_in_ls2k2000 = { >>>> +   .chip = CHIP_LS2K2000, >>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>> +   .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 = 256, >>>> +   .mc_bits = 40, >>>> +   .has_vblank_counter = true, >>>> +   .has_builtin_i2c = true, >>>> +   .has_vram = false, >>>> +   .has_hpd_reg = true, >>>> +   .is_soc = true, >>>> +}; >>>> + >>>> +static const struct lsdc_desc dc_in_ls7a1000 = { >>>> +   .chip = CHIP_LS7A1000, >>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>> +   .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, >>>> +   .has_builtin_i2c = true, >>>> +   .has_vram = true, >>>> +   .has_hpd_reg = false, >>>> +   .is_soc = false, >>>> +}; >>>> + >>>> +static const struct lsdc_desc dc_in_ls7a2000 = { >>>> +   .chip = CHIP_LS7A2000, >>>> +   .num_of_crtc = LSDC_NUM_CRTC, >>>> +   .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 = 256, >>>> +   .mc_bits = 40, /* support 48, but use 40 for backward >>>> compatibility */ >>>> +   .has_vblank_counter = true, >>>> +   .has_builtin_i2c = true, >>>> +   .has_vram = true, >>>> +   .has_hpd_reg = true, >>>> +   .is_soc = false, >>>> +}; >>>> + >>>> +static int lsdc_gem_dumb_create(struct drm_file *file, >>>> +               struct drm_device *ddev, >>>> +               struct drm_mode_create_dumb *args) >>>> +{ >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   return drm_gem_vram_fill_create_dumb(file, ddev, 0, >>>> descp->pitch_align, args); >>>> +} >>>> + >>>> +DEFINE_DRM_GEM_FOPS(lsdc_gem_fops); >>>> + >>>> +static const struct drm_driver lsdc_drm_driver = { >>>> +   .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, >>>> +   .fops = &lsdc_gem_fops, >>>> + >>>> +   .name = DRIVER_NAME, >>>> +   .desc = DRIVER_DESC, >>>> +   .date = DRIVER_DATE, >>>> +   .major = DRIVER_MAJOR, >>>> +   .minor = DRIVER_MINOR, >>>> +   .patchlevel = DRIVER_PATCHLEVEL, >>>> + >>>> +   .debugfs_init = lsdc_debugfs_init, >>>> +   .dumb_create = lsdc_gem_dumb_create, >>>> +   .dumb_map_offset = drm_gem_ttm_dumb_map_offset, >>>> +   .gem_prime_mmap = drm_gem_prime_mmap, >>>> +}; >>>> + >>>> +static const struct drm_mode_config_funcs lsdc_mode_config_funcs = { >>>> +   .fb_create = drm_gem_fb_create, >>>> +   .output_poll_changed = drm_fb_helper_output_poll_changed, >>>> +   .atomic_check = drm_atomic_helper_check, >>>> +   .atomic_commit = drm_atomic_helper_commit, >>>> +   .mode_valid = drm_vram_helper_mode_valid, >>>> +}; >>>> + >>>> +static int lsdc_modeset_init(struct lsdc_device *ldev, >>>> +                const struct lsdc_desc *descp) >>>> +{ >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   unsigned int num_crtc = descp->num_of_crtc; >>>> +   unsigned int i; >>>> +   int ret; >>>> + >>>> +   for (i = 0; i < num_crtc; i++) { >>>> +       ret = lsdc_create_output(ldev, i); >>>> +       if (ret) >>>> +           return ret; >>>> +   } >>>> + >>>> +   for (i = 0; i < num_crtc; i++) { >>>> +       struct lsdc_display_pipe *dispipe = &ldev->dispipe[i]; >>>> +       struct lsdc_pll *pixpll = &dispipe->pixpll; >>>> +       struct drm_plane *primary = &dispipe->primary; >>>> +       struct drm_plane *cursor = &dispipe->cursor; >>>> +       struct drm_crtc *crtc = &dispipe->crtc; >>>> + >>>> +       dispipe->index = i; >>>> + >>>> +       ret = lsdc_pixpll_init(pixpll, ddev, i); >>>> +       if (ret) >>>> +           return ret; >>>> + >>>> +       ret = lsdc_plane_init(ldev, primary, >>>> DRM_PLANE_TYPE_PRIMARY, i); >>>> +       if (ret) >>>> +           return ret; >>>> + >>>> +       ret = lsdc_plane_init(ldev, cursor, DRM_PLANE_TYPE_CURSOR, >>>> i); >>>> +       if (ret) >>>> +           return ret; >>>> + >>>> +       /* >>>> +        * Initial all of the CRTC available, in this way the logical >>>> +        * crtc index is equal to the hardware crtc index. >>>> +        * >>>> +        * display pipe 0 = crtc0 + dvo0 + encoder0 + connector0 >>>> +        * display pipe 1 = crtc1 + dvo1 + encoder1 + connector1 >>>> +        */ >>>> +       ret = lsdc_crtc_init(ddev, crtc, i, primary, cursor); >>>> +       if (ret) >>>> +           return ret; >>>> + >>>> +       drm_info(ddev, "display pipe %u initialized\n", i); >>>> +   } >>>> + >>>> +   drm_mode_config_reset(ddev); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_mode_config_init(struct drm_device *ddev, >>>> +                const struct lsdc_desc *descp) >>>> +{ >>>> +   int ret; >>>> + >>>> +   ret = drmm_mode_config_init(ddev); >>>> +   if (ret) >>>> +       return ret; >>>> + >>>> +   ddev->mode_config.funcs = &lsdc_mode_config_funcs; >>>> +   ddev->mode_config.min_width = 1; >>>> +   ddev->mode_config.min_height = 1; >>>> +   ddev->mode_config.max_width = descp->max_width * LSDC_NUM_CRTC; >>>> +   ddev->mode_config.max_height = descp->max_height * LSDC_NUM_CRTC; >>>> +   ddev->mode_config.preferred_depth = 24; >>>> +   ddev->mode_config.prefer_shadow = descp->has_vram; >>>> + >>>> +   ddev->mode_config.cursor_width = descp->hw_cursor_h; >>>> +   ddev->mode_config.cursor_height = descp->hw_cursor_h; >>>> + >>>> +   /* max_vblank_count is set on each CRTC */ >>>> +   ddev->max_vblank_count = 0xffffffff; >>>> + >>>> +   return ret; >>>> +} >>>> + >>>> +/* >>>> + * lsdc_detect_chip - a function to tell lsdc variant apart. >>>> + */ >>>> +static const struct lsdc_desc * >>>> +lsdc_detect_chip(struct pci_dev *pdev, const struct pci_device_id >>>> *ent) >>>> +{ >>>> +   /* >>>> +    * Provide a way to false the driver running on new model >>>> +    * before we have a proper way to tell them apart >>>> +    */ >>>> +   if (lsdc_chip == CHIP_LS2K2000) { >>>> +       dev_info(&pdev->dev, "Override as LS2K2000\n"); >>>> +       return &dc_in_ls2k2000; >>>> +   } >>>> + >>>> +   if (lsdc_chip == CHIP_LS2K1000) { >>>> +       dev_info(&pdev->dev, "Override as LS2K1000\n"); >>>> +       return &dc_in_ls2k1000; >>>> +   } >>>> + >>>> +   if (lsdc_chip == CHIP_LS2K0500) { >>>> +       dev_info(&pdev->dev, "Override as LS2K0500\n"); >>>> +       return &dc_in_ls2k0500; >>>> +   } >>>> + >>>> +   if (ent) { >>>> +       struct lsdc_desc *dc_descp; >>>> + >>>> +       dc_descp = (struct lsdc_desc *)ent->driver_data; >>> >>> Is that legal? driver_data is of kernel_ulong_t. Maybe store the >>> CHIP_ constant here and then lookup the lsdc_desc. >>> >> It works in practice but it seems dangerous doing so, I will using >> CHIP_ LS7AXXXX and CHIP_LS2KXXXX at new version. >> >> i don't notice it before, thank you. >> >>> >>> https://elixir.bootlin.com/linux/latest/source/include/linux/mod_devicetable.h#L36 >>> >>> >>>> +       if (dc_descp->chip == CHIP_LS7A1000) >>>> +           dev_info(&pdev->dev, "LS7A1000 Found revision: %u\n", >>>> pdev->revision); >>>> +       else if (dc_descp->chip == CHIP_LS7A2000) >>>> +           dev_info(&pdev->dev, "LS7A2000 Found revison: %u\n", >>>> pdev->revision); >>>> + >>>> +       return dc_descp; >>>> +   } >>>> + >>>> +   dev_err(&pdev->dev, "Not known device\n"); >>>> + >>>> +   return NULL; >>>> +} >>>> + >>>> +static int lsdc_get_dedicated_vram(struct lsdc_device *ldev, >>>> +                  const struct lsdc_desc *descp) >>>> +{ >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   struct pci_dev *gpu; >>>> +   resource_size_t base, size; >>>> + >>>> +   /* >>>> +    * The GPU and display controller in LS7A1000/LS7A2000 are >>>> separated >>>> +    * PCIE devices, they are two devices not one. The DC is a pci >>>> device, >>>> +    * but it don't have a dedicate VRAM bar, the BIOS engineer >>>> choose to >>>> +    * assign the VRAM to the gpu device. Sadly, after years >>>> application, >>>> +    * this decision form as a convention for loongson integrate >>>> graphics. >>>> +    * For LS7A1000 and LS7A2000, bar 2 of GPU device contain the >>>> VRAM, >>>> +    * both the GPU and the DC can make use of the VRAM depend on >>>> how DRM >>>> +    * device driver is written. Therefore, we have to do some >>>> tricks here. >>>> +    */ >>>> +   if (descp->chip == CHIP_LS7A1000) >>>> +       gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A15, NULL); >>>> +   else if (descp->chip == CHIP_LS7A2000) >>>> +       gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A25, NULL); >>>> + >>>> +   if (!gpu) { >>>> +       drm_warn(ddev, "No GPU device found\n"); >>>> +       return -ENODEV; >>>> +   } >>>> + >>>> +   base = pci_resource_start(gpu, 2); >>>> +   size = pci_resource_len(gpu, 2); >>>> + >>>> +   ldev->vram_base = base; >>>> +   ldev->vram_size = size; >>>> + >>>> +   drm_info(ddev, "dedicated vram start: 0x%llx, size: %uMB\n", >>>> +        (u64)base, (u32)(size >> 20)); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +/* >>>> + * Loongson display controller is not equipped with IOMMU. >>>> + * In order to make this driver work with Loongson SoC, >>>> + * you need to either carveout part RAM as VRAM or reserve >>>> + * part system ram as vram. Pass the start address and size >>>> + * via DT or ACPI. >>>> + */ >>>> +static int lsdc_of_get_reserved_ram(struct lsdc_device *ldev) >>>> +{ >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   unsigned long size = 0; >>>> +   struct device_node *node; >>>> +   struct resource r; >>>> +   int ret; >>>> + >>>> +   node = of_parse_phandle(ddev->dev->of_node, "memory-region", 0); >>>> +   if (!node) { >>>> +       drm_err(ddev, "No memory-region property or no DT, abort\n"); >>>> +       return -ENOENT; >>>> +   } >>>> + >>>> +   ret = of_address_to_resource(node, 0, &r); >>>> +   of_node_put(node); >>>> +   if (ret) >>>> +       return ret; >>>> + >>>> +   size = r.end - r.start + 1; >>>> + >>>> +   ldev->vram_base = r.start; >>>> +   ldev->vram_size = size; >>>> + >>>> +   drm_info(ddev, "using VRAM carveout: %lx@%pa\n", size, &r.start); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int >>>> +lsdc_remove_conflicting_framebuffers(const struct drm_driver >>>> *pdriver, >>>> +                    resource_size_t base, >>>> +                    resource_size_t size) >>>> +{ >>>> +   return drm_aperture_remove_conflicting_framebuffers(base, >>>> +                               size, >>>> +                               false, >>>> +                               pdriver); >>>> +} >>> >>> No need to wrap this single function. >> >> Ok, Loongson upstream kernel support efifb and it backing memory is >> located at this range. >> >> we must properly remove it, otherwise they will have two fb >> node(/dev/fb0 and /dev/fb1) > > Right, but you can call drm_aperture_remove_conflicting_framebuffers() > directly. > > Best practice is to detect your device's video-memory range and clear > use it as base and size values. In cases where you don't know, > drm_aperture_remove_framebuffers() remove all conflicting drivers. > >> >>> >>>> + >>>> +static struct lsdc_device * >>>> +lsdc_create_device(struct pci_dev *pdev, >>>> +          const struct pci_device_id *ent, >>>> +          const struct drm_driver *drv) >>>> +{ >>>> +   struct lsdc_device *ldev; >>>> +   struct drm_device *ddev; >>>> +   const struct lsdc_desc *descp; >>>> +   int ret; >>>> + >>>> +   ldev = devm_drm_dev_alloc(&pdev->dev, drv, struct lsdc_device, >>>> base); >>>> +   if (IS_ERR(ldev)) >>>> +       return ldev; >>>> + >>>> +   ddev = &ldev->base; >>>> + >>>> +   pci_set_drvdata(pdev, ddev); >>>> + >>>> +   descp = lsdc_detect_chip(pdev, ent); >>>> +   if (!descp) >>>> +       return NULL; >>>> + >>>> +   ldev->descp = descp; >>>> + >>>> +   spin_lock_init(&ldev->reglock); >>>> + >>>> +   /* BAR 0 the DC device contains registers */ >>>> +   ldev->reg_base = pcim_iomap(pdev, 0, 0); >>>> +   if (!ldev->reg_base) >>>> +       return ERR_PTR(-EIO); >>>> + >>>> +   if (descp->has_vram && lsdc_has_vram) >>>> +       ret = lsdc_get_dedicated_vram(ldev, descp); >>>> +   else >>>> +       ret = lsdc_of_get_reserved_ram(ldev); >>>> + >>>> +   if (ret) { >>>> +       drm_err(ddev, "Init VRAM failed: %d\n", ret); >>>> +       return ERR_PTR(ret); >>>> +   } >>>> + >>>> +   ret = lsdc_remove_conflicting_framebuffers(drv, >>>> +                          ldev->vram_base, >>>> +                          ldev->vram_size); >>>> +   if (ret) { >>>> +       drm_err(ddev, "remove firmware framebuffers failed: %d\n", >>>> ret); >>>> +       return ERR_PTR(ret); >>>> +   } >>>> + >>>> +   ret = drmm_vram_helper_init(ddev, ldev->vram_base, >>>> ldev->vram_size); >>> >>> Using GEM VRAM helpers could become a problem and I'd advise against >>> them. >>> >> Err, I'm not want to agree this... >>> First of all, how much video memory is there? VRAM helpers need at >>> least 3 times the maximum consumption to be reliable.So if you have >>> a FullHD display that consumes ~8 Mib, you'd need 24 MiB of VRAM. >>> Hardware cursors can further interfere. >> >> Part of ls3a5000/ls3a4000+ls7a1000 machine have 64MB, another part of >> ls3a5000/ls3a4000+ls7a1000 have 128 MB. >> >> For ls7a1000(1080p) the maximum usage actually is two >> framebuffer(1920x1080x4x2) + two cursor (64x64x4x 2) + one fbcon >> (1920x1080x4) and LS7A2000 board has at lease 256MB VRAM. >> >> Therefore I can guarantee that we have at least 64MB VRAM,enough for >> double screen(1920x1080) + double cursor. > > Ok, sounds good. I expected some low-end devices, but 64 MiB should be > fine. > > I still want to remove those VRAM helpers in the long term. My > suggestion is that you simply reimplement the code in > drm_gem_vram_helper.c in your driver. It's not that much and you can > probably duplicate most of it. Please name everything according to > your driver's convention. > > You should end up with a self-contained memory manager based on TTM, > which you can adapt to your hardware's needs. > >> >> I guess you means that there are some device(aspeed bmc) with limited >> VRAM size, but it is more a problem >> >> of the device itself(It is not vram helper's fault). Early version >> of ast driver would evict fbcon to system RAM >> >> to save VRAM usage, would this idea be a solution for the device with >> limited vram capacity today? >> >> >>> VRAM helpers also don't support buffer sharing with other devices. >>> That's a problem for some compositors. >>> >>> For those reasons, VRAM helpers are deprecated. I'd rather remove >>> them entirely. >> >> Its not that bad, VRAM helpers is good enough for our case as a first >> step. Our colleague and customers mainly >> >> using MATE and XFCE under X server graphic environment, they need >> basic 2D graphics environment most. >> >> We have tested our driver against macro and xfwm4, it actually works >> very well. Individual compositor may >> >> have a few problems, but it isn't a thing for our case currently. Can >> we solve those problem by other means? >> >> We could give up cross device buffer sharing support for chasing 2D >> graphic performance , we intend to >> >> introducing buffer sharing with the help of the GPU with another >> patch in the future. We possibly could >> >> switch to ttm-based KMS driver under your guiding then. For >> ls7a1000, I imagine to merge lsdc with evnaviv >> >> into a single drm driver.May I suggest to postpone the decision of >> removing VRAM helpers, its performance >> >> is better than shmem helper and have better support for double screen. > > As I outlined, please duplicate the VRAM helpers in you driver and > adapt it to your needs. I really just want to avoid more users of the > VRAM helpers. Building your driver upon TTM is perfectly fine. > > Since you have devices with VRAM and devices without VRAM, you should > be able to handle both cases with TTM. TTM is flexible enough. You > just need to configure it in the right way. > > For fbcon, using drm_fbdev_generic_setup() should be fine for now. If > you implement your own TTM-based memory manager, you will also have > the posibility of implementing a faster console. That's maybe > something for a later patch. > >> >> >>> As a better alternative for the DC, you could use GEM SHMEM helpers. >>> See mgag200 or ast for how to do that. >> >> We have two display pipe thus support double screen, therefore we >> have two framebuffer BO and two cursor BO, >> >> we need to take clone screen, extend screen and rotate and resize >> case into consideration, on clone screen case >> >> the two CRTC should scanout from the same address, while on extend >> screen case, the frame buffer itself is grant >> >> one buffer if using modesetting driver, but we have two primary plane >> thus two fb offset. Arrange VRAM bo offset >> >> for 4 different drm BO object manually is little bit hack and >> cumbersome. I have already finish the developing >> >> work of shmem helper support, see: >> https://github.com/loongson-gfx/drm-tip/compare/drm-tip...shmem >> >> But it is NOT as good as VRAM helper in the perspective of >> performance. I think copy from the shadow to the >> >> front frame buffer should be done at user space, this can save the >> overhead of reporting damage rectangle whenever >> >> have a damage. >> >> >>> >>> The comments mention that there's also a GPU. Does the GPU use VRAM? >>> Or do GPU and DC share memory ranges? >>> >> LS7A2000 integrate Loongson self-made GPU, driver is developed by my >> colleagues, it already works but my mature >> >> to upstream yet. LS7A1000 have a VIVANTE GC1000 v5037 (20120617 >> etc), a ten years ago render-only GPU. >> >> the VRAM is connected with the ls7a1000 bridge chip, the memory >> controller is also in ls7a1000 bridge chip itself. >> >> ATI call this type vram as sideport, my company call it as GMEM, I >> think they are similar. >> >> etnaviv don't use VRAM currently, all BOs of etnaviv is in system ram. >> >> I think merge etnaviv and lsdc into a single drm driver would be a >> solution, what do you think? > > I don't think etnaviv developers would like such mergers. We usually > keep each driver separate from others and if we have multiple drivers > with similar code, we try to build a helper library that covers all of > them. Drivers can select from the helpers what they need. > >> >> >> On downstream environment, we hacking make ttm-based lsdc work with >> etnaviv, by copying(sw resolve) >> >> the rendered buffer to vram backed framebuffer, glmark2 get 220 point, > > >> quake3 arena can play, haha :) > > That's the important use case. :D > >> >> It is achieved by developing xf86-video-loongson which using etnaviv >> to share buffer between xserver and glmark2, >> >> lsdc(or downstream loongson-drm) is only need to implement a >> self-sharing for userspace process . >> >> see https://github.com/loongson-gfx/xf86-video-loongson. The shared >> rendering buffer is copy >> >> to front buffer with loongson LSX and LASX(256-bit) SIMD >> optimization. Report dirty to kernel space is not need. >> >> we can even let suppertiled buffer rendered by etnaviv get exported, >> xf86-video-loongson get the fd and prime >> >> it to handle, then we are using CPU to do the resolve work which >> directly resolve to the front framebuffer. No damage >> >> rectangle reporting is need. >> >> >> Hence, the 2D performance of graphics is the more important. GEM >> SHMEM helpers is not better than vram helpers >> >> in the perspective of performance. >> >>>> +   if (ret) { >>>> +       drm_err(ddev, "vram helper init failed: %d\n", ret); >>>> +       goto err; >>>> +   } >>>> + >>>> +   ret = lsdc_mode_config_init(ddev, descp); >>>> +   if (ret) { >>>> +       drm_dbg(ddev, "%s: %d\n", __func__, ret); >>>> +       goto err; >>>> +   } >>>> + >>>> +   ret = lsdc_modeset_init(ldev, descp); >>>> +   if (ret) >>>> +       goto err; >>>> + >>>> +   ret = drm_vblank_init(ddev, descp->num_of_crtc); >>>> +   if (ret) >>>> +       goto err; >>>> + >>>> +   ret = request_threaded_irq(pdev->irq, >>>> +                  lsdc_get_irq_handler(ldev), >>>> +                  lsdc_irq_thread_handler, >>>> +                  IRQF_ONESHOT, >>>> +                  dev_name(ddev->dev), >>>> +                  ddev); >>>> +   if (ret) { >>>> +       drm_err(ddev, "Failed to register lsdc interrupt\n"); >>>> +       goto err; >>>> +   } >>>> + >>>> +   drm_kms_helper_poll_init(ddev); >>>> + >>>> +   return ldev; >>>> + >>>> +err: >>>> +   return ERR_PTR(ret); >>>> +} >>>> + >>>> +static int lsdc_pci_probe(struct pci_dev *pdev, >>>> +             const struct pci_device_id *ent) >>>> +{ >>>> +   struct drm_device *ddev; >>>> +   struct lsdc_device *ldev; >>>> +   int ret; >>>> + >>>> +   ret = pcim_enable_device(pdev); >>>> +   if (ret) >>>> +       return ret; >>>> + >>>> +   pci_set_master(pdev); >>>> + >>>> +   ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); >>>> +   if (ret) >>>> +       return ret; >>>> + >>>> +   ldev = lsdc_create_device(pdev, ent, &lsdc_drm_driver); >>>> +   if (IS_ERR(ldev)) >>>> +       return PTR_ERR(ldev); >>>> + >>>> +   ddev = &ldev->base; >>>> + >>>> +   ret = drm_dev_register(ddev, 0); >>>> +   if (ret) >>>> +       return ret; >>>> + >>>> +   drm_fbdev_generic_setup(ddev, 32); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static void lsdc_pci_remove(struct pci_dev *pdev) >>>> +{ >>>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>>> + >>>> +   drm_dev_unregister(ddev); >>>> +   drm_atomic_helper_shutdown(ddev); >>>> +} >>>> + >>>> +static int lsdc_drm_freeze(struct drm_device *ddev) >>>> +{ >>>> +   int error; >>>> + >>>> +   error = drm_mode_config_helper_suspend(ddev); >>>> +   if (error) >>>> +       return error; >>>> + >>>> +   pci_save_state(to_pci_dev(ddev->dev)); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_drm_resume(struct device *dev) >>>> +{ >>>> +   struct pci_dev *pdev = to_pci_dev(dev); >>>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>>> + >>>> +   return drm_mode_config_helper_resume(ddev); >>>> +} >>>> + >>>> +static int lsdc_pm_freeze(struct device *dev) >>>> +{ >>>> +   struct pci_dev *pdev = to_pci_dev(dev); >>>> +   struct drm_device *ddev = pci_get_drvdata(pdev); >>>> + >>>> +   return lsdc_drm_freeze(ddev); >>>> +} >>>> + >>>> +static int lsdc_pm_thaw(struct device *dev) >>>> +{ >>>> +   return lsdc_drm_resume(dev); >>>> +} >>>> + >>>> +static int lsdc_pm_suspend(struct device *dev) >>>> +{ >>>> +   struct pci_dev *pdev = to_pci_dev(dev); >>>> +   int error; >>>> + >>>> +   error = lsdc_pm_freeze(dev); >>>> +   if (error) >>>> +       return error; >>>> + >>>> +   pci_save_state(pdev); >>>> +   /* Shut down the device */ >>>> +   pci_disable_device(pdev); >>>> +   pci_set_power_state(pdev, PCI_D3hot); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static int lsdc_pm_resume(struct device *dev) >>>> +{ >>>> +   struct pci_dev *pdev = to_pci_dev(dev); >>>> + >>>> +   if (pcim_enable_device(pdev)) >>>> +       return -EIO; >>>> + >>>> +   pci_set_power_state(pdev, PCI_D0); >>>> + >>>> +   pci_restore_state(pdev); >>>> + >>>> +   return lsdc_pm_thaw(dev); >>>> +} >>>> + >>>> +static const struct dev_pm_ops lsdc_pm_ops = { >>>> +   .suspend = lsdc_pm_suspend, >>>> +   .resume = lsdc_pm_resume, >>>> +   .freeze = lsdc_pm_freeze, >>>> +   .thaw = lsdc_pm_thaw, >>>> +   .poweroff = lsdc_pm_freeze, >>>> +   .restore = lsdc_pm_resume, >>>> +}; >>>> + >>>> +static const struct pci_device_id lsdc_pciid_list[] = { >>>> +   {PCI_VENDOR_ID_LOONGSON, 0x7a06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, >>>> +    (kernel_ulong_t)&dc_in_ls7a1000}, >>>> +   {PCI_VENDOR_ID_LOONGSON, 0x7a36, PCI_ANY_ID, PCI_ANY_ID, 0, 0, >>>> +    (kernel_ulong_t)&dc_in_ls7a2000}, >>>> +   {0, 0, 0, 0, 0, 0, 0} >>>> +}; >>>> + >>>> +static struct pci_driver lsdc_pci_driver = { >>>> +   .name = DRIVER_NAME, >>>> +   .id_table = lsdc_pciid_list, >>>> +   .probe = lsdc_pci_probe, >>>> +   .remove = lsdc_pci_remove, >>>> +   .driver.pm = &lsdc_pm_ops, >>>> +}; >>>> + >>>> +static int __init lsdc_module_init(void) >>>> +{ >>>> +   struct pci_dev *pdev = NULL; >>>> + >>>> +   if (drm_firmware_drivers_only()) >>>> +       return -EINVAL; >>> >>>  return -ENODEV; >> >> OK, no problem, I will fix it at new version. I remember i already >> fix this, sorry. >> >>> >>>> + >>>> +   while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, >>>> pdev))) { >>>> +       /* >>>> +        * Multiple video card workaround: lsdc will always be >>>> +        * selected as the default boot device by vgaarb subsystem. >>>> +        */ >>>> +       if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) { >>>> +           pr_info("Discrete graphic card detected, abort\n"); >>>> +           return 0; >>>> +       } >>>> +   } >>> >>> I don't understand this code. You are not registering your driver if >>> there's another graphics card? >>> >> Yes, we do not want to registering this driver if there's discrete >> graphics card or BMC in the system. >> >> >> for example: > > I read through the examples, but still find it strange. No other > driver does this. Maybe keep the code for now, but I'd like to hear > the opinion of other DRM developers on this. I'd expect that there's a > better solution to this. > >> >> 1) Loongson desktop computer(ls3a5000 + ls7a2000 with amdgpu rx580 >> mounted) >> >> On such a configuration, loongson display controller and rx580 >> co-exist in the system. >> >> As discrete GPU is more powerful, it seems that there no need to >> using LSDC driver anymore. >> >> The user is most likely want to using the rx580 as primary GPU and >> Using rx580 alone is enough for user in such a case. >> >> It is introduce unnecessary problems and confusion If we leave lsdc >> driver live on the system together with amdgpu/ati drm driver. >> >> >> I have just tested the case which leave lsdc driver live on the >> system together with radeon drm driver. >> >> the monitor connected with lsdc is black, not working as a output slave. >> >> >> 2) Loongson ls3c5000 server with aspeed 2400/25000 BMC card mounted. >> >> The BMC provide remote access and control besides the graphics, use >> it alone is enough, we have solve >> >> 4 ~ 5 bugs on our product side. we don't want make a mess. >> >> our tester get confused, than find a amdgpu and plug in, then he get >> three video co-exist in the sistem. >> >> lsdc, ast and amdgpu, there /dev/dri/card0, /dev/dri/car1 and >> /dev/dri/card2. >> >> if i bring etnaviv up, then i can get four. >> >> >>> Best regards >>> Thomas >>> >> Thinks your valuable advice and guiding, we will take time to fix all >> the problems your mentioned, > > No problem. I think I mentioned that it wouldn't make sense to merge > the DRM driver before ACPI and/or DT are ready. So we have time to get > the driver into good shape. > Sigh, :( Then can we drop the support for SoC chips temporary, and provide DT support at latter time? this patch is target to providing the generic part of device driver support for Loongson integrated graphics. I see the double mention :(, but still want to make sure if it is negotiable. Because for the DC in our bridge chip, this driver can be probed reliably. The code size of this patch and the technical description needed is actually large enough for me to carry on.  If this is not negotiable, then it is ok to provide DT support at the next version of this patch? it(dt support patch) tend to incurring more criticize. I'm scared and not want to face it. We can leave the support for our SoC as internal, those SoC are really target for low end application. keep them back is actually ok. > Best regards > Thomas > >> >> and prepare next version. >> >>>> + >>>> +   return pci_register_driver(&lsdc_pci_driver); >>>> +} >>>> +module_init(lsdc_module_init); >>>> + >>>> +static void __exit lsdc_module_exit(void) >>>> +{ >>>> +   pci_unregister_driver(&lsdc_pci_driver); >>>> +} >>>> +module_exit(lsdc_module_exit); >>>> + >>>> +MODULE_DEVICE_TABLE(pci, lsdc_pciid_list); >>>> +MODULE_AUTHOR(DRIVER_AUTHOR); >>>> +MODULE_DESCRIPTION(DRIVER_DESC); >>>> +MODULE_LICENSE("GPL"); >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.h >>>> b/drivers/gpu/drm/lsdc/lsdc_drv.h >>>> new file mode 100644 >>>> index 000000000000..d67dae0414ca >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.h >>>> @@ -0,0 +1,274 @@ >>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>> +/* >>>> + * KMS driver for Loongson display controller >>>> + * Copyright (C) 2022 Loongson Corporation >>>> + * >>>> + * Authors: >>>> + *     Li Yi <liyi@loongson.cn> >>>> + *     Li Chenyang <lichenyang@loongson.cn> >>>> + *     Sui Jingfeng <suijingfeng@loongson.cn> >>>> + */ >>>> + >>>> +#ifndef __LSDC_DRV_H__ >>>> +#define __LSDC_DRV_H__ >>>> + >>>> +#include <linux/i2c.h> >>>> +#include <linux/i2c-algo-bit.h> >>>> + >>>> +#include <drm/drm_print.h> >>>> +#include <drm/drm_device.h> >>>> +#include <drm/drm_crtc.h> >>>> +#include <drm/drm_plane.h> >>>> +#include <drm/drm_connector.h> >>>> +#include <drm/drm_encoder.h> >>>> +#include <drm/drm_drv.h> >>>> +#include <drm/drm_atomic.h> >>>> + >>>> +#include "lsdc_regs.h" >>>> +#include "lsdc_pll.h" >>>> + >>>> +#define LSDC_NUM_CRTC          2 >>>> + >>>> +/* >>>> + * The display controller in LS7A2000 integrate three loongson >>>> self-made >>>> + * encoder. Display pipe 0 has a transparent VGA encoder and a >>>> HDMI phy, >>>> + * they are parallel. Display pipe 1 has only one HDMI phy. >>>> + *      ______________________ _____________ >>>> + *     |            +-----+ | |            | >>>> + *     | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA >>>> Monitor |<---+ >>>> + *     |       |   +-----+ | |_____________|   | >>>> + *     |       |            | ______________   | >>>> + *     |       |   +------+ | |             |  | >>>> + *     |       +--> | HDMI | ----> HDMI Connector --> | HDMI >>>> Monitor |<--+ >>>> + *     |            +------+ | |______________|  | >>>> + *     |           +------+ >>>> |                                          | >>>> + *     |           | i2c6 | >>>> <-------------------------------------------+ >>>> + *     |           +------+ | >>>> + *     |                     | >>>> + *     |   DC in LS7A2000   | >>>> + *     |                     | >>>> + *     |           +------+ | >>>> + *     |           | i2c7 | <--------------------------------+ >>>> + *     |           +------+ | | >>>> + *     |                     | ______|_______ >>>> + *     |           +------+ | |             | >>>> + *     | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI >>>> Monitor | >>>> + *     |           +------+ | |______________| >>>> + *     |______________________| >>>> + * >>>> + * >>>> + * The display controller in LS7A1000 integrate two-way DVO, external >>>> + * encoder is required except use directly with dpi(rgb888) panel. >>>> + * ___________________ _________ >>>> + *     |           -------| |        | >>>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>>> Display | >>>> + *     | _  _    -------|       ^            ^ |_________| >>>> + *     | | | | |   -------|       |            | >>>> + *     | |_| |_|   | i2c6 <--------+-------------+ >>>> + *     |           -------| >>>> + *     | _  _    -------| >>>> + *     | | | | |   | i2c7 <--------+-------------+ >>>> + *     | |_| |_|   -------|       | | _________ >>>> + *     |           -------|       |            | |        | >>>> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | >>>> Panel | >>>> + *     |           -------| |_________| >>>> + *     |___________________| >>>> + * >>>> + * >>>> + * The display controller in LS2K1000. >>>> + * ___________________ _________ >>>> + *     |           -------| |        | >>>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>>> Display | >>>> + *     | _  _    -------|       ^             ^ |_________| >>>> + *     | | | | |          |       |             | >>>> + *     | |_| |_|          |    +------+         | >>>> + *     |                  <---->| i2c0 |<---------+ >>>> + *     | DC in LS2K1000  |    +------+ >>>> + *     | _  _           |    +------+ >>>> + *     | | | | |          <---->| i2c1 |----------+ >>>> + *     | |_| |_|          |    +------+ | _________ >>>> + *     |           -------|       |             | |        | >>>> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | >>>> Panel | >>>> + *     |           -------| |_________| >>>> + *     |___________________| >>>> + * >>>> + * >>>> + * The display controller in LS2K0500, LS2K0500 has a built-in >>>> transparent >>>> + * VGA encoder which is connected to display pipe 1(CRTC1). >>>> + * ___________________ _________ >>>> + *     |           -------| |        | >>>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>>> Display | >>>> + *     | _  _    -------|       ^             ^ |_________| >>>> + *     | | | | |          |       |             | >>>> + *     | |_| |_|          |    +------+         | >>>> + *     |                  <---->| i2c4 |<---------+ >>>> + *     | DC in LS2K0500  |    +------+ >>>> + *     | _  _           |    +------+ >>>> + *     | | | | |          <---->| i2c5 |-------------------+ >>>> + *     | |_| |_|          |    +------+ ______|______ >>>> + *     |          +-----+ | |            | >>>> + *     | CRTC1 --> | VGA | ------------------------> | VGA Monitor | >>>> + *     |          +-----+ | |_____________| >>>> + *     |___________________| >>>> + * >>>> + * LS7A1000 and LS7A2000 are bridge chips, has dedicated Video RAM. >>>> + * while LS2K2000/LS2K1000/LS2K0500 are SoC, they don't have >>>> dediacated >>>> + * Video RAM. The dc in LS2K2000 is basicly same with the dc in >>>> LS7A1000 >>>> + * except that LS2K2000 don't have a video RAM and have only one >>>> built-in >>>> + * hdmi encoder. >>>> + * >>>> + * There is only a 1:1 mapping of encoders and connectors for the DC, >>>> + * each CRTC have two FB address registers. >>>> + */ >>>> + >>>> +enum loongson_chip_family { >>>> +   CHIP_UNKNOWN = 0, >>>> +   CHIP_LS7A1000 = 1, /* North bridge of >>>> LS3A3000/LS3A4000/LS3A5000 */ >>>> +   CHIP_LS7A2000 = 2, /* Update version of LS7A1000, with >>>> built-in HDMI encoder */ >>>> +   CHIP_LS2K0500 = 3, /* Reduced version of LS2K1000, single >>>> core */ >>>> +   CHIP_LS2K1000 = 4, /* 2-Core Mips64r2/LA264 SoC, 64-bit, 1.0 >>>> Ghz */ >>>> +   CHIP_LS2K2000 = 5, /* 2-Core 64-bit LA364 SoC, 1.2 ~ 1.5 Ghz */ >>>> +   CHIP_LAST, >>>> +}; >>>> + >>>> +struct lsdc_desc { >>>> +   enum loongson_chip_family chip; >>>> +   u32 num_of_crtc; >>>> +   u32 max_pixel_clk; >>>> +   u32 max_width; >>>> +   u32 max_height; >>>> +   u32 num_of_hw_cursor; >>>> +   u32 hw_cursor_w; >>>> +   u32 hw_cursor_h; >>>> +   u32 pitch_align; /* DMA alignment constraint */ >>>> +   u64 mc_bits;     /* physical address bus bit width */ >>>> +   bool has_vblank_counter; >>>> +   bool has_builtin_i2c; >>>> +   bool has_vram; >>>> +   bool has_hpd_reg; >>>> +   bool is_soc; >>>> +}; >>>> + >>>> +struct lsdc_i2c { >>>> +   struct i2c_adapter adapter; >>>> +   struct i2c_algo_bit_data bit; >>>> +   struct drm_device *ddev; >>>> +   void __iomem *reg_base; >>>> +   void __iomem *dir_reg; >>>> +   void __iomem *dat_reg; >>>> +   /* pin bit mask */ >>>> +   u8 sda; >>>> +   u8 scl; >>>> +}; >>>> + >>>> +/* >>>> + * struct lsdc_display_pipe - Abstraction of hardware display >>>> pipeline. >>>> + * @crtc: CRTC control structure >>>> + * @primary: framebuffer plane control structure >>>> + * @cursor: cursor plane control structure >>>> + * @output: output control structure of this display pipe bind >>>> + * @pixpll: pixel pll control structure >>>> + * @index: the index corresponding to the hardware display pipe >>>> + */ >>>> +struct lsdc_display_pipe { >>>> +   struct drm_crtc crtc; >>>> +   struct drm_plane primary; >>>> +   struct drm_plane cursor; >>>> +   struct drm_encoder encoder; >>>> +   struct drm_connector connector; >>>> +   struct lsdc_pll pixpll; >>>> +   struct lsdc_i2c *li2c; >>>> +   int index; >>>> +}; >>>> + >>>> +static inline struct lsdc_display_pipe * >>>> +crtc_to_display_pipe(struct drm_crtc *crtc) >>>> +{ >>>> +   return container_of(crtc, struct lsdc_display_pipe, crtc); >>>> +} >>>> + >>>> +static inline struct lsdc_display_pipe * >>>> +cursor_to_display_pipe(struct drm_plane *cursor) >>>> +{ >>>> +   return container_of(cursor, struct lsdc_display_pipe, cursor); >>>> +} >>>> + >>>> +static inline struct lsdc_display_pipe * >>>> +connector_to_display_pipe(struct drm_connector *conn) >>>> +{ >>>> +   return container_of(conn, struct lsdc_display_pipe, connector); >>>> +} >>>> + >>>> +static inline struct lsdc_display_pipe * >>>> +encoder_to_display_pipe(struct drm_encoder *enc) >>>> +{ >>>> +   return container_of(enc, struct lsdc_display_pipe, encoder); >>>> +} >>>> + >>>> +struct lsdc_crtc_state { >>>> +   struct drm_crtc_state base; >>>> +   struct lsdc_pll_parms pparms; >>>> +}; >>>> + >>>> +struct lsdc_device { >>>> +   struct drm_device base; >>>> + >>>> +   /* @reglock: protects concurrent register access */ >>>> +   spinlock_t reglock; >>>> +   void __iomem *reg_base; >>>> +   void __iomem *vram; >>>> +   resource_size_t vram_base; >>>> +   resource_size_t vram_size; >>>> +   u64 mc_mask; >>>> + >>>> +   struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC]; >>>> + >>>> +   /* @num_output: count the number of active display pipe */ >>>> +   unsigned int num_output; >>>> + >>>> +   /* @descp: features description of the DC variant */ >>>> +   const struct lsdc_desc *descp; >>>> + >>>> +   u32 irq_status; >>>> +}; >>>> + >>>> +static inline struct lsdc_device * >>>> +to_lsdc(struct drm_device *ddev) >>>> +{ >>>> +   return container_of(ddev, struct lsdc_device, base); >>>> +} >>>> + >>>> +static inline struct lsdc_crtc_state * >>>> +to_lsdc_crtc_state(struct drm_crtc_state *base) >>>> +{ >>>> +   return container_of(base, struct lsdc_crtc_state, base); >>>> +} >>>> + >>>> +void lsdc_debugfs_init(struct drm_minor *minor); >>>> + >>>> +int lsdc_crtc_init(struct drm_device *ddev, >>>> +          struct drm_crtc *crtc, >>>> +          unsigned int index, >>>> +          struct drm_plane *primary, >>>> +          struct drm_plane *cursor); >>>> + >>>> +int lsdc_plane_init(struct lsdc_device *ldev, >>>> +           struct drm_plane *plane, >>>> +           enum drm_plane_type type, >>>> +           unsigned int index); >>>> + >>>> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index); >>>> + >>>> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >>>> +                     void *base, >>>> +                     unsigned int index); >>>> + >>>> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >>>> int index); >>>> + >>>> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg); >>>> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev); >>>> + >>>> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset); >>>> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 >>>> val); >>>> + >>>> +#endif >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c >>>> b/drivers/gpu/drm/lsdc/lsdc_i2c.c >>>> new file mode 100644 >>>> index 000000000000..b380098409ca >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c >>>> @@ -0,0 +1,201 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include <linux/i2c.h> >>>> +#include <drm/drm_managed.h> >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> + >>>> +/* >>>> + * ls7a_gpio_i2c_set - set the state of a gpio pin indicated by mask >>>> + * @mask: gpio pin mask >>>> + * @state: "0" for low, "1" for high >>>> + */ >>>> +static void ls7a_gpio_i2c_set(struct lsdc_i2c * const li2c, int >>>> mask, int state) >>>> +{ >>>> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >>>> +   unsigned long flags; >>>> +   u8 val; >>>> + >>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>> + >>>> +   if (state) { >>>> +       /* >>>> +        * Setting this pin as input directly, write 1 for Input. >>>> +        * The external pull-up resistor will pull the level up >>>> +        */ >>>> +       val = readb(li2c->dir_reg); >>>> +       val |= mask; >>>> +       writeb(val, li2c->dir_reg); >>>> +   } else { >>>> +       /* First set this pin as output, write 0 for Output */ >>>> +       val = readb(li2c->dir_reg); >>>> +       val &= ~mask; >>>> +       writeb(val, li2c->dir_reg); >>>> + >>>> +       /* Then, make this pin output 0 */ >>>> +       val = readb(li2c->dat_reg); >>>> +       val &= ~mask; >>>> +       writeb(val, li2c->dat_reg); >>>> +   } >>>> + >>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>> +} >>>> + >>>> +/* >>>> + * ls7a_gpio_i2c_get - read value back from the gpio pin indicated >>>> by mask >>>> + * @mask: gpio pin mask >>>> + * return "0" for low, "1" for high >>>> + */ >>>> +static int ls7a_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask) >>>> +{ >>>> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >>>> +   unsigned long flags; >>>> +   u8 val; >>>> + >>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>> + >>>> +   /* First set this pin as input */ >>>> +   val = readb(li2c->dir_reg); >>>> +   val |= mask; >>>> +   writeb(val, li2c->dir_reg); >>>> + >>>> +   /* Then get level state from this pin */ >>>> +   val = readb(li2c->dat_reg); >>>> + >>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>> + >>>> +   return (val & mask) ? 1 : 0; >>>> +} >>>> + >>>> +static void ls7a_i2c_set_sda(void *i2c, int state) >>>> +{ >>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>> +   /* set state on the li2c->sda pin */ >>>> +   return ls7a_gpio_i2c_set(li2c, li2c->sda, state); >>>> +} >>>> + >>>> +static void ls7a_i2c_set_scl(void *i2c, int state) >>>> +{ >>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>> +   /* set state on the li2c->scl pin */ >>>> +   return ls7a_gpio_i2c_set(li2c, li2c->scl, state); >>>> +} >>>> + >>>> +static int ls7a_i2c_get_sda(void *i2c) >>>> +{ >>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>> +   /* read value from the li2c->sda pin */ >>>> +   return ls7a_gpio_i2c_get(li2c, li2c->sda); >>>> +} >>>> + >>>> +static int ls7a_i2c_get_scl(void *i2c) >>>> +{ >>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>> +   /* read the value from the li2c->scl pin */ >>>> +   return ls7a_gpio_i2c_get(li2c, li2c->scl); >>>> +} >>>> + >>>> +static void lsdc_destroy_i2c(struct drm_device *ddev, void *data) >>>> +{ >>>> +   struct lsdc_i2c *li2c = (struct lsdc_i2c *)data; >>>> + >>>> +   if (li2c) { >>>> +       i2c_del_adapter(&li2c->adapter); >>>> +       kfree(li2c); >>>> +   } >>>> +} >>>> + >>>> +/* >>>> + * The DC in ls7a1000/ls7a2000 have builtin gpio hardware >>>> + * >>>> + * @base: gpio reg base >>>> + * @index: output channel index, 0 for DVO0, 1 for DVO1 >>>> + */ >>>> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >>>> +                     void *base, >>>> +                     unsigned int index) >>>> +{ >>>> +   struct i2c_adapter *adapter; >>>> +   struct lsdc_i2c *li2c; >>>> +   int ret; >>>> + >>>> +   li2c = kzalloc(sizeof(*li2c), GFP_KERNEL); >>>> +   if (!li2c) >>>> +       return ERR_PTR(-ENOMEM); >>>> + >>>> +   if (index == 0) { >>>> +       li2c->sda = 0x01; /* pin 0 */ >>>> +       li2c->scl = 0x02; /* pin 1 */ >>>> +   } else if (index == 1) { >>>> +       li2c->sda = 0x04; /* pin 2 */ >>>> +       li2c->scl = 0x08; /* pin 3 */ >>>> +   } >>>> + >>>> +   li2c->reg_base = base; >>>> +   li2c->ddev = ddev; >>>> +   li2c->dir_reg = li2c->reg_base + LS7A_DC_GPIO_DIR_REG; >>>> +   li2c->dat_reg = li2c->reg_base + LS7A_DC_GPIO_DAT_REG; >>>> + >>>> +   li2c->bit.setsda = ls7a_i2c_set_sda; >>>> +   li2c->bit.setscl = ls7a_i2c_set_scl; >>>> +   li2c->bit.getsda = ls7a_i2c_get_sda; >>>> +   li2c->bit.getscl = ls7a_i2c_get_scl; >>>> +   li2c->bit.udelay = 5; >>>> +   li2c->bit.timeout = usecs_to_jiffies(2200); >>>> +   li2c->bit.data = li2c; >>>> + >>>> +   adapter = &li2c->adapter; >>>> +   adapter->algo_data = &li2c->bit; >>>> +   adapter->owner = THIS_MODULE; >>>> +   adapter->class = I2C_CLASS_DDC; >>>> +   adapter->dev.parent = ddev->dev; >>>> +   adapter->nr = -1; >>>> + >>>> +   snprintf(adapter->name, sizeof(adapter->name), "lsdc-i2c%u", >>>> index); >>>> + >>>> +   i2c_set_adapdata(adapter, li2c); >>>> + >>>> +   ret = i2c_bit_add_bus(adapter); >>>> +   if (ret) { >>>> +       kfree(li2c); >>>> +       return ERR_PTR(ret); >>>> +   } >>>> + >>>> +   ret = drmm_add_action_or_reset(ddev, lsdc_destroy_i2c, li2c); >>>> +   if (ret) >>>> +       return NULL; >>>> + >>>> +   drm_info(ddev, "%s(sda=%u, scl=%u) created for connector-%u\n", >>>> +        adapter->name, li2c->sda, li2c->scl, index); >>>> + >>>> +   return li2c; >>>> +} >>>> + >>>> +static int lsdc_get_i2c_id(struct lsdc_device *ldev, int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   if (descp->chip == CHIP_LS2K1000) >>>> +       return index; >>>> + >>>> +   if (descp->chip == CHIP_LS2K0500) >>>> +       return index + 2; >>>> + >>>> +   return index; >>>> +} >>>> + >>>> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >>>> +                    int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >>>> + >>>> +   if (descp->has_builtin_i2c) { >>>> +       struct lsdc_i2c *li2c = dispipe->li2c; >>>> + >>>> +       if (li2c) >>>> +           return &li2c->adapter; >>>> +   } >>>> + >>>> +   return i2c_get_adapter(lsdc_get_i2c_id(ldev, index)); >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.c >>>> b/drivers/gpu/drm/lsdc/lsdc_irq.c >>>> new file mode 100644 >>>> index 000000000000..71c85f09bb6f >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_irq.c >>>> @@ -0,0 +1,86 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include <drm/drm_vblank.h> >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> + >>>> +/* >>>> + * For the DC in ls7a2000, clearing interrupt status is achieved by >>>> + * write "1" to LSDC_INT_REG, For the DC in ls7a1000, ls2k1000 and >>>> + * ls2k0500, clearing interrupt status is achieved by write "0" to >>>> + * LSDC_INT_REG. Two different hardware engineer of Loongson modify >>>> + * it as their will. >>>> + */ >>>> + >>>> +/* For the DC in ls7a2000 */ >>>> +static irqreturn_t lsdc_irq_handler(int irq, void *arg) >>>> +{ >>>> +   struct drm_device *ddev = arg; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   u32 val; >>>> + >>>> +   /* Read the interrupt status */ >>>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>>> +   if ((val & INT_STATUS_MASK) == 0) { >>>> +       drm_warn(ddev, "no interrupt occurs\n"); >>>> +       return IRQ_NONE; >>>> +   } >>>> + >>>> +   ldev->irq_status = val; >>>> + >>>> +   /* write "1" to clear the interrupt status */ >>>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >>>> + >>>> +   return IRQ_WAKE_THREAD; >>>> +} >>>> + >>>> +/* For the DC in ls7a1000, ls2k1000 and ls2k0500 */ >>>> +static irqreturn_t lsdc_irq_handler_legacy(int irq, void *arg) >>>> +{ >>>> +   struct drm_device *ddev = arg; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   u32 val; >>>> + >>>> +   /* Read the interrupt status */ >>>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>>> +   if ((val & INT_STATUS_MASK) == 0) { >>>> +       drm_warn(ddev, "no interrupt occurs\n"); >>>> +       return IRQ_NONE; >>>> +   } >>>> + >>>> +   ldev->irq_status = val; >>>> + >>>> +   /* write "0" to clear the interrupt status */ >>>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val & ~INT_STATUS_MASK); >>>> + >>>> +   return IRQ_WAKE_THREAD; >>>> +} >>>> + >>>> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   if (descp->chip == CHIP_LS7A2000) >>>> +       return lsdc_irq_handler; >>>> + >>>> +   return lsdc_irq_handler_legacy; >>>> +} >>>> + >>>> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg) >>>> +{ >>>> +   struct drm_device *ddev = arg; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   struct drm_crtc *crtc; >>>> + >>>> +   if (ldev->irq_status & INT_CRTC0_VSYNC) { >>>> +       crtc = drm_crtc_from_index(ddev, 0); >>>> +       drm_crtc_handle_vblank(crtc); >>>> +   } >>>> + >>>> +   if (ldev->irq_status & INT_CRTC1_VSYNC) { >>>> +       crtc = drm_crtc_from_index(ddev, 1); >>>> +       drm_crtc_handle_vblank(crtc); >>>> +   } >>>> + >>>> +   return IRQ_HANDLED; >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_output.c >>>> b/drivers/gpu/drm/lsdc/lsdc_output.c >>>> new file mode 100644 >>>> index 000000000000..d4bc7666d9ea >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_output.c >>>> @@ -0,0 +1,452 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include <drm/drm_edid.h> >>>> +#include <drm/drm_probe_helper.h> >>>> +#include <drm/drm_atomic_helper.h> >>>> +#include <drm/drm_connector.h> >>>> +#include "lsdc_drv.h" >>>> + >>>> +static int lsdc_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 enum drm_connector_status >>>> +lsdc_unknown_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; >>>> +   } else { >>>> +       if (connector->connector_type == DRM_MODE_CONNECTOR_DPI) >>>> +           return connector_status_connected; >>>> + >>>> +       if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) >>>> +           return connector_status_connected; >>>> +   } >>>> + >>>> +   return connector_status_unknown; >>>> +} >>>> + >>>> +static enum drm_connector_status >>>> +lsdc_hdmi_connector_detect(struct drm_connector *connector, bool >>>> force) >>>> +{ >>>> +   struct lsdc_display_pipe *pipe = >>>> connector_to_display_pipe(connector); >>>> +   struct lsdc_device *ldev = to_lsdc(connector->dev); >>>> +   u32 val; >>>> + >>>> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >>>> + >>>> +   if (pipe->index == 0) { >>>> +       if (val & HDMI0_HPD_FLAG) >>>> +           return connector_status_connected; >>>> +   } >>>> + >>>> +   if (pipe->index == 1) { >>>> +       if (val & HDMI1_HPD_FLAG) >>>> +           return connector_status_connected; >>>> +   } >>>> + >>>> +   return connector_status_disconnected; >>>> +} >>>> + >>>> +static enum drm_connector_status >>>> +lsdc_hdmi_vga_connector_detect(struct drm_connector *connector, >>>> bool force) >>>> +{ >>>> +   struct lsdc_display_pipe *pipe = >>>> connector_to_display_pipe(connector); >>>> +   struct drm_device *ddev = connector->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   struct i2c_adapter *ddc; >>>> +   u32 val; >>>> + >>>> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >>>> + >>>> +   if (pipe->index == 1) { >>>> +       if (val & HDMI1_HPD_FLAG) >>>> +           return connector_status_connected; >>>> + >>>> +       return connector_status_disconnected; >>>> +   } >>>> + >>>> +   if (pipe->index == 0) { >>>> +       if (val & HDMI0_HPD_FLAG) >>>> +           return connector_status_connected; >>>> + >>>> +       ddc = connector->ddc; >>>> +       if (ddc) { >>>> +           if (drm_probe_ddc(ddc)) >>>> +               return connector_status_connected; >>>> + >>>> +           return connector_status_disconnected; >>>> +       } >>>> +   } >>>> + >>>> +   return connector_status_unknown; >>>> +} >>>> + >>>> +static struct drm_encoder * >>>> +lsdc_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 >>>> lsdc_connector_helpers = { >>>> +   .atomic_best_encoder = lsdc_connector_get_best_encoder, >>>> +   .get_modes = lsdc_get_modes, >>>> +}; >>>> + >>>> +static const struct drm_connector_funcs >>>> lsdc_unknown_connector_funcs = { >>>> +   .detect = lsdc_unknown_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 const struct drm_connector_funcs >>>> lsdc_hdmi_vga_connector_funcs = { >>>> +   .detect = lsdc_hdmi_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, >>>> +}; >>>> + >>>> +static const struct drm_connector_funcs lsdc_hdmi_connector_funcs = { >>>> +   .detect = lsdc_hdmi_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 const struct drm_connector_funcs * >>>> +lsdc_get_connector_func(struct lsdc_device *ldev, unsigned int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   if (descp->chip == CHIP_LS7A2000) { >>>> +       if (index == 0) >>>> +           return &lsdc_hdmi_vga_connector_funcs; >>>> + >>>> +       if (index == 1) >>>> +           return &lsdc_hdmi_connector_funcs; >>>> +   } >>>> + >>>> +   return &lsdc_unknown_connector_funcs; >>>> +} >>>> + >>>> +/* >>>> + * we provide a default support before DT/VBIOS is supported >>>> + */ >>>> +static int lsdc_get_encoder_type(struct lsdc_device *ldev, >>>> +                unsigned int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   if (descp->chip == CHIP_LS7A2000) { >>>> +       if (index == 0) >>>> +           return DRM_MODE_ENCODER_DAC; >>>> +       if (index == 1) >>>> +           return DRM_MODE_ENCODER_TMDS; >>>> +   } >>>> + >>>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>>> CHIP_LS2K1000) { >>>> +       if (index == 0) >>>> +           return DRM_MODE_ENCODER_DPI; >>>> +       if (index == 1) >>>> +           return DRM_MODE_ENCODER_DPI; >>>> +   } >>>> + >>>> +   if (descp->chip == CHIP_LS2K0500) { >>>> +       if (index == 0) >>>> +           return DRM_MODE_ENCODER_DPI; >>>> +       if (index == 1) >>>> +           return DRM_MODE_ENCODER_DAC; >>>> +   } >>>> + >>>> +   return DRM_MODE_ENCODER_NONE; >>>> +} >>>> + >>>> +/* >>>> + * provide a default before DT/VBIOS support is upstreamed >>>> + */ >>>> +static int lsdc_get_connector_type(struct lsdc_device *ldev, >>>> +                  unsigned int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   if (descp->chip == CHIP_LS7A2000) { >>>> +       if (index == 0) >>>> +           return DRM_MODE_CONNECTOR_VGA; >>>> +       if (index == 1) >>>> +           return DRM_MODE_CONNECTOR_HDMIA; >>>> +   } >>>> + >>>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>>> CHIP_LS2K1000) { >>>> +       if (index == 0) >>>> +           return DRM_MODE_CONNECTOR_DPI; >>>> +       if (index == 1) >>>> +           return DRM_MODE_CONNECTOR_DPI; >>>> +   } >>>> + >>>> +   if (descp->chip == CHIP_LS2K0500) { >>>> +       if (index == 0) >>>> +           return DRM_MODE_CONNECTOR_DPI; >>>> +       if (index == 1) >>>> +           return DRM_MODE_CONNECTOR_VGA; >>>> +   } >>>> + >>>> +   return DRM_MODE_CONNECTOR_Unknown; >>>> +} >>>> + >>>> +static void lsdc_hdmi_disable(struct drm_encoder *encoder) >>>> +{ >>>> +   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; >>>> + >>>> +   if (index == 0) >>>> +       val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG); >>>> +   else if (index == 1) >>>> +       val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG); >>>> + >>>> +   val &= ~HDMI_PHY_EN; >>>> + >>>> +   if (index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >>>> +   else if (index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >>>> + >>>> +   drm_dbg(ddev, "HDMI-%u disabled\n", index); >>>> +} >>>> + >>>> +static void lsdc_hdmi_enable(struct drm_encoder *encoder) >>>> +{ >>>> +   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; >>>> + >>>> +   /* we are using software gpio emulated i2c */ >>>> +   val = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >>>> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >>>> + >>>> +   if (index == 0) { >>>> +       /* Enable HDMI-0 */ >>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >>>> + >>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_ZONE_REG, 0x00400040); >>>> +   } else if (index == 1) { >>>> +       /* Enable HDMI-1 */ >>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >>>> + >>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_ZONE_REG, 0x00400040); >>>> +   } >>>> + >>>> +   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; >>>> + >>>> +   if (index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >>>> +   else if (index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >>>> + >>>> +   drm_dbg(ddev, "HDMI-%u enabled\n", index); >>>> +} >>>> + >>>> +/* >>>> + * Fout = M * Fin >>>> + * >>>> + * M = (4 * LF) / (IDF * ODF) >>>> + * >>>> + * Loongson HDMI require M = 10 >>>> + */ >>>> +static void lsdc_hdmi_phy_pll_config(struct lsdc_device *ldev, >>>> +                    int index, >>>> +                    int clock) >>>> +{ >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   int count = 0; >>>> +   u32 val; >>>> + >>>> +   /* disable phy pll */ >>>> +   if (index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, 0x0); >>>> +   else if (index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, 0x0); >>>> + >>>> +   /* >>>> +    * 10 = (4 * 40) / (8 * 2) >>>> +    */ >>>> +   val = (8 << HDMI_PLL_IDF_SHIFT) | >>>> +         (40 << HDMI_PLL_LF_SHIFT) | >>>> +         (1 << HDMI_PLL_ODF_SHIFT) | >>>> +         HDMI_PLL_ENABLE; >>>> + >>>> +   drm_dbg(ddev, "HDMI-%u clock: %d\n", index, clock); >>>> + >>>> +   if (index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, val); >>>> +   else if (index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, val); >>>> + >>>> +   /* wait hdmi phy pll lock */ >>>> +   do { >>>> +       if (index == 0) >>>> +           val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG); >>>> +       else if (index == 1) >>>> +           val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_PLL_REG); >>>> + >>>> +       ++count; >>>> + >>>> +       if (val & HDMI_PLL_LOCKED) { >>>> +           drm_dbg(ddev, "Setting HDMI-%u PLL success(take %d >>>> cycles)\n", >>>> +               index, count); >>>> +           break; >>>> +       } >>>> +   } while (count < 1000); >>>> +} >>>> + >>>> +static void lsdc_hdmi_atomic_mode_set(struct drm_encoder *encoder, >>>> +                     struct drm_crtc_state *crtc_state, >>>> +                     struct drm_connector_state *conn_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; >>>> +   struct drm_display_mode *mode = &crtc_state->mode; >>>> + >>>> +   lsdc_hdmi_phy_pll_config(ldev, index, mode->clock); >>>> + >>>> +   drm_dbg(ddev, "HDMI-%u modeset\n", index); >>>> +} >>>> + >>>> +static const struct drm_encoder_helper_funcs >>>> lsdc_hdmi_helper_funcs = { >>>> +   .disable = lsdc_hdmi_disable, >>>> +   .enable = lsdc_hdmi_enable, >>>> +   .atomic_mode_set = lsdc_hdmi_atomic_mode_set, >>>> +}; >>>> + >>>> +static void lsdc_hdmi_reset(struct drm_encoder *encoder) >>>> +{ >>>> +   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 = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >>>> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >>>> + >>>> +   lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >>>> +   lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >>>> + >>>> +   drm_dbg(ddev, "HDMI-%u Reset\n", index); >>>> +} >>>> + >>>> +static const struct drm_encoder_funcs lsdc_encoder_funcs = { >>>> +   .reset = lsdc_hdmi_reset, >>>> +   .destroy = drm_encoder_cleanup, >>>> +}; >>>> + >>>> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >>>> +   struct drm_encoder *encoder = &dispipe->encoder; >>>> +   struct drm_connector *connector = &dispipe->connector; >>>> +   struct i2c_adapter *ddc = NULL; >>>> +   struct lsdc_i2c *li2c; >>>> +   int ret; >>>> + >>>> +   ret = drm_encoder_init(ddev, >>>> +                  encoder, >>>> +                  &lsdc_encoder_funcs, >>>> +                  lsdc_get_encoder_type(ldev, index), >>>> +                  "encoder-%u", >>>> +                  index); >>>> +   if (ret) { >>>> +       drm_err(ddev, "Failed to init encoder: %d\n", ret); >>>> +       return ret; >>>> +   } >>>> + >>>> +   encoder->possible_crtcs = BIT(index); >>>> + >>>> +   if (descp->chip == CHIP_LS7A2000) >>>> +       drm_encoder_helper_add(encoder, &lsdc_hdmi_helper_funcs); >>>> + >>>> +   if (descp->has_builtin_i2c) { >>>> +       li2c = lsdc_create_i2c_chan(ddev, ldev->reg_base, index); >>>> +       if (IS_ERR(li2c)) >>>> +           return PTR_ERR(li2c); >>>> + >>>> +       dispipe->li2c = li2c; >>>> + >>>> +       ddc = &li2c->adapter; >>>> +   } else { >>>> +       ddc = lsdc_get_i2c_adapter(ldev, index); >>>> +       if (IS_ERR(ddc)) { >>>> +           drm_err(ddev, "Error get ddc for output-%u\n", index); >>>> +           return PTR_ERR(ddc); >>>> +       } >>>> +   } >>>> + >>>> +   ret = drm_connector_init_with_ddc(ddev, >>>> +                     connector, >>>> +                     lsdc_get_connector_func(ldev, index), >>>> +                     lsdc_get_connector_type(ldev, index), >>>> +                     ddc); >>>> +   if (ret) { >>>> +       drm_err(ddev, "Init connector-%d failed\n", index); >>>> +       return ret; >>>> +   } >>>> + >>>> +   drm_connector_helper_add(connector, &lsdc_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; >>>> + >>>> +   ldev->num_output++; >>>> + >>>> +   drm_info(ddev, "output-%u initialized\n", index); >>>> + >>>> +   return 0; >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_plane.c >>>> b/drivers/gpu/drm/lsdc/lsdc_plane.c >>>> new file mode 100644 >>>> index 000000000000..0f779c97d53b >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_plane.c >>>> @@ -0,0 +1,443 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include <drm/drm_atomic.h> >>>> +#include <drm/drm_atomic_helper.h> >>>> +#include <drm/drm_framebuffer.h> >>>> +#include <drm/drm_plane_helper.h> >>>> +#include <drm/drm_gem_vram_helper.h> >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> +#include "lsdc_pll.h" >>>> + >>>> +static const u32 lsdc_primary_formats[] = { >>>> +   DRM_FORMAT_XRGB8888, >>>> +   DRM_FORMAT_ARGB8888, >>>> +}; >>>> + >>>> +static const u32 lsdc_cursor_formats[] = { >>>> +   DRM_FORMAT_ARGB8888, >>>> +}; >>>> + >>>> +static const u64 lsdc_fb_format_modifiers[] = { >>>> +   DRM_FORMAT_MOD_LINEAR, >>>> +   DRM_FORMAT_MOD_INVALID >>>> +}; >>>> + >>>> +static void lsdc_update_fb_format(struct lsdc_device *ldev, >>>> +                 struct drm_crtc *crtc, >>>> +                 const struct drm_format_info *fmt_info) >>>> +{ >>>> +   unsigned int index = drm_crtc_index(crtc); >>>> +   u32 val; >>>> +   u32 fmt; >>>> + >>>> +   switch (fmt_info->format) { >>>> +   case DRM_FORMAT_XRGB8888: >>>> +       fmt = LSDC_PF_XRGB8888; >>>> +       break; >>>> +   case DRM_FORMAT_ARGB8888: >>>> +       fmt = LSDC_PF_XRGB8888; >>>> +       break; >>>> +   default: >>>> +       fmt = LSDC_PF_XRGB8888; >>>> +       break; >>>> +   } >>>> + >>>> +   if (index == 0) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>> +   } else if (index == 1) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>> +   } >>>> +} >>>> + >>>> +static void lsdc_update_fb_start_addr(struct lsdc_device *ldev, >>>> +                     struct drm_crtc *crtc, >>>> +                     u64 paddr) >>>> +{ >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   unsigned int index = drm_crtc_index(crtc); >>>> +   u32 lo_addr_reg; >>>> +   u32 hi_addr_reg; >>>> +   u32 val; >>>> + >>>> +   /* >>>> +    * Find which framebuffer address register should update. >>>> +    * if FB_ADDR0_REG is in using, we write the address to >>>> FB_ADDR0_REG, >>>> +    * if FB_ADDR1_REG is in using, we write the address to >>>> FB_ADDR1_REG >>>> +    * for each CRTC, the switch using one fb register to another is >>>> +    * trigger by triggered by set CFG_PAGE_FLIP bit of >>>> LSDC_CRTCx_CFG_REG >>>> +    */ >>>> +   if (index == 0) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>> +       if (val & CFG_FB_IN_USING) { >>>> +           lo_addr_reg = LSDC_CRTC0_FB1_LO_ADDR_REG; >>>> +           hi_addr_reg = LSDC_CRTC0_FB1_HI_ADDR_REG; >>>> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-0\n"); >>>> +       } else { >>>> +           lo_addr_reg = LSDC_CRTC0_FB0_LO_ADDR_REG; >>>> +           hi_addr_reg = LSDC_CRTC0_FB0_HI_ADDR_REG; >>>> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-0\n"); >>>> +       } >>>> +   } else if (index == 1) { >>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>> +       if (val & CFG_FB_IN_USING) { >>>> +           lo_addr_reg = LSDC_CRTC1_FB1_LO_ADDR_REG; >>>> +           hi_addr_reg = LSDC_CRTC1_FB1_HI_ADDR_REG; >>>> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-1\n"); >>>> +       } else { >>>> +           lo_addr_reg = LSDC_CRTC1_FB0_LO_ADDR_REG; >>>> +           hi_addr_reg = LSDC_CRTC1_FB0_HI_ADDR_REG; >>>> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-1\n"); >>>> +       } >>>> +   } >>>> + >>>> +   /* 40-bit width physical address bus */ >>>> +   lsdc_wreg32(ldev, lo_addr_reg, paddr); >>>> +   lsdc_wreg32(ldev, hi_addr_reg, (paddr >> 32) & 0xFF); >>>> + >>>> +   drm_dbg(ddev, "CRTC-%u scantout from 0x%llx\n", index, paddr); >>>> +} >>>> + >>>> +static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb, >>>> +                      struct drm_plane_state *state, >>>> +                      unsigned int plane) >>>> +{ >>>> +   unsigned int offset = fb->offsets[plane]; >>>> + >>>> +   offset += fb->format->cpp[plane] * (state->src_x >> 16); >>>> +   offset += fb->pitches[plane] * (state->src_y >> 16); >>>> + >>>> +   return offset; >>>> +} >>>> + >>>> +static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb) >>>> +{ >>>> +   struct drm_gem_vram_object *gbo; >>>> +   s64 gpu_addr; >>>> + >>>> +   gbo = drm_gem_vram_of_gem(fb->obj[0]); >>>> +   gpu_addr = drm_gem_vram_offset(gbo); >>>> + >>>> +   return gpu_addr; >>>> +} >>>> + >>>> +static int lsdc_primary_plane_atomic_check(struct drm_plane *plane, >>>> +                      struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_plane_state *plane_state = >>>> drm_atomic_get_new_plane_state(state, plane); >>>> +   struct drm_crtc *crtc = plane_state->crtc; >>>> +   struct drm_crtc_state *crtc_state = >>>> drm_atomic_get_new_crtc_state(state, crtc); >>>> + >>>> +   if (!crtc) >>>> +       return 0; >>>> + >>>> +   return drm_atomic_helper_check_plane_state(plane_state, >>>> +                          crtc_state, >>>> +                          DRM_PLANE_NO_SCALING, >>>> +                          DRM_PLANE_NO_SCALING, >>>> +                          false, >>>> +                          true); >>>> +} >>>> + >>>> +static void lsdc_update_fb_stride(struct lsdc_device *ldev, >>>> +                 struct drm_crtc *crtc, >>>> +                 unsigned int stride) >>>> +{ >>>> +   unsigned int index = drm_crtc_index(crtc); >>>> + >>>> +   if (index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_STRIDE_REG, stride); >>>> +   else if (index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_STRIDE_REG, stride); >>>> + >>>> +   drm_dbg(crtc->dev, "update stride to %u\n", stride); >>>> +} >>>> + >>>> +static void lsdc_primary_plane_atomic_update(struct drm_plane *plane, >>>> +                        struct drm_atomic_state *state) >>>> +{ >>>> +   struct lsdc_device *ldev = to_lsdc(plane->dev); >>>> +   struct drm_plane_state *new_plane_state = >>>> drm_atomic_get_new_plane_state(state, plane); >>>> +   struct drm_crtc *crtc = new_plane_state->crtc; >>>> +   struct drm_framebuffer *fb = new_plane_state->fb; >>>> +   u32 fb_offset = lsdc_get_fb_offset(fb, new_plane_state, 0); >>>> +   dma_addr_t fb_addr; >>>> +   s64 gpu_addr; >>>> + >>>> +   gpu_addr = lsdc_get_vram_bo_offset(fb); >>>> +   if (gpu_addr < 0) >>>> +       return; >>>> + >>>> +   fb_addr = ldev->vram_base + gpu_addr + fb_offset; >>>> + >>>> +   lsdc_update_fb_start_addr(ldev, crtc, fb_addr); >>>> + >>>> +   lsdc_update_fb_stride(ldev, crtc, fb->pitches[0]); >>>> + >>>> +   lsdc_update_fb_format(ldev, crtc, fb->format); >>>> +} >>>> + >>>> +static void lsdc_primary_plane_atomic_disable(struct drm_plane >>>> *plane, >>>> +                         struct drm_atomic_state *state) >>>> +{ >>>> +   drm_dbg(plane->dev, "%s disabled\n", plane->name); >>>> +} >>>> + >>>> +static const struct drm_plane_helper_funcs >>>> lsdc_primary_plane_helpers = { >>>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>>> +   .atomic_check = lsdc_primary_plane_atomic_check, >>>> +   .atomic_update = lsdc_primary_plane_atomic_update, >>>> +   .atomic_disable = lsdc_primary_plane_atomic_disable, >>>> +}; >>>> + >>>> +static int lsdc_cursor_atomic_check(struct drm_plane *plane, >>>> +                   struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_plane_state *new_plane_state = >>>> drm_atomic_get_new_plane_state(state, plane); >>>> +   struct drm_framebuffer *fb = new_plane_state->fb; >>>> +   struct drm_crtc *crtc = new_plane_state->crtc; >>>> +   struct drm_crtc_state *crtc_state; >>>> +   int ret; >>>> + >>>> +   /* no need for further checks if the plane is being disabled */ >>>> +   if (!crtc || !fb) >>>> +       return 0; >>>> + >>>> +   if (!new_plane_state->visible) >>>> +       return 0; >>>> + >>>> +   crtc_state = drm_atomic_get_new_crtc_state(state, >>>> +                          new_plane_state->crtc); >>>> + >>>> +   ret = drm_atomic_helper_check_plane_state(new_plane_state, >>>> +                         crtc_state, >>>> +                         DRM_PLANE_NO_SCALING, >>>> +                         DRM_PLANE_NO_SCALING, >>>> +                         true, >>>> +                         true); >>>> + >>>> +   return ret; >>>> +} >>>> + >>>> +/* >>>> + * There is only one hardware cursor in ls7a1000, ls2k1000 and >>>> ls2k0500. >>>> + * we made it shared by the two CRTC, which can satisfy peoples >>>> who use >>>> + * double screen extend mode only. On clone screen usage case, the >>>> cursor >>>> + * on display pipe 1 will not be able to display. >>>> + * >>>> + * Update location of the cursor, attach it to CRTC0 or CRTC1 on >>>> the runtime. >>>> + */ >>>> +static void lsdc_cursor_update_location_quirks(struct lsdc_device >>>> *ldev, >>>> +                          struct drm_crtc *crtc) >>>> +{ >>>> +   u32 val = CURSOR_FORMAT_ARGB8888; >>>> + >>>> +   /* >>>> +    * If bit 4 of LSDC_CURSOR0_CFG_REG is 1, then the cursor will be >>>> +    * locate at CRTC-1, if bit 4 of LSDC_CURSOR0_CFG_REG is 0, then >>>> +    * the cursor will be locate at CRTC-0. The cursor is alway on >>>> the >>>> +    * top of the primary. Compositing the primary plane and cursor >>>> +    * plane is automatically done by hardware. >>>> +    */ >>>> +   if (drm_crtc_index(crtc)) >>>> +       val |= CURSOR_LOCATION; >>>> + >>>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, val); >>>> +} >>>> + >>>> +/* update the position of the cursor */ >>>> +static void lsdc_cursor_update_position_quirks(struct lsdc_device >>>> *ldev, >>>> +                          int x, >>>> +                          int y) >>>> +{ >>>> +   if (x < 0) >>>> +       x = 0; >>>> + >>>> +   if (y < 0) >>>> +       y = 0; >>>> + >>>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >>>> +} >>>> + >>>> +static void lsdc_cursor_atomic_update_quirks(struct drm_plane *plane, >>>> +                        struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_device *ddev = plane->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   struct drm_plane_state *new_plane_state = >>>> drm_atomic_get_new_plane_state(state, plane); >>>> +   struct drm_plane_state *old_plane_state = >>>> drm_atomic_get_old_plane_state(state, plane); >>>> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >>>> +   struct drm_framebuffer *old_fb = old_plane_state->fb; >>>> + >>>> +   if (new_fb != old_fb) { >>>> +       s64 offset = lsdc_get_vram_bo_offset(new_fb); >>>> +       u64 cursor_addr = ldev->vram_base + offset; >>>> + >>>> +       drm_dbg_kms(ddev, "%s offset: %llx\n", plane->name, offset); >>>> + >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >>>> >> 32) & 0xFF); >>>> +   } >>>> + >>>> +   lsdc_cursor_update_position_quirks(ldev, >>>> new_plane_state->crtc_x, new_plane_state->crtc_y); >>>> + >>>> +   lsdc_cursor_update_location_quirks(ldev, new_plane_state->crtc); >>>> +} >>>> + >>>> +/* update the format, size and location of the cursor */ >>>> +static void lsdc_cursor_atomic_update(struct drm_plane *plane, >>>> +                     struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_device *ddev = plane->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   struct lsdc_display_pipe *dispipe = >>>> cursor_to_display_pipe(plane); >>>> +   struct drm_plane_state *new_plane_state = >>>> drm_atomic_get_new_plane_state(state, plane); >>>> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >>>> +   int x = new_plane_state->crtc_x; >>>> +   int y = new_plane_state->crtc_y; >>>> +   u32 conf = CURSOR_FORMAT_ARGB8888 | CURSOR_SIZE_64X64; >>>> +   u64 cursor_addr = ldev->vram_base + >>>> lsdc_get_vram_bo_offset(new_fb); >>>> + >>>> +   if (x < 0) >>>> +       x = 0; >>>> + >>>> +   if (y < 0) >>>> +       y = 0; >>>> + >>>> +   if (dispipe->index == 0) { >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >>>> >> 32) & 0xFF); >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >>>> +       /* Attach Cursor-0 to CRTC-0 */ >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, conf & >>>> ~CURSOR_LOCATION); >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >>>> +       return; >>>> +   } >>>> + >>>> +   if (dispipe->index == 1) { >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_HI_REG, (cursor_addr >>>> >> 32) & 0xFF); >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_LO_REG, cursor_addr); >>>> +       /* Attach Cursor-1 to CRTC-1 */ >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, conf | >>>> CURSOR_LOCATION); >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_POSITION_REG, (y << 16) | x); >>>> +       return; >>>> +   } >>>> +} >>>> + >>>> +static void lsdc_cursor_atomic_disable_quirks(struct drm_plane >>>> *plane, >>>> +                         struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_device *ddev = plane->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> + >>>> +   /* Set the format to 0 actually display the cursor */ >>>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >>>> + >>>> +   drm_dbg(ddev, "%s disabled\n", plane->name); >>>> +} >>>> + >>>> +static void lsdc_cursor_atomic_disable(struct drm_plane *plane, >>>> +                      struct drm_atomic_state *state) >>>> +{ >>>> +   struct drm_device *ddev = plane->dev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   struct lsdc_display_pipe *dispipe = >>>> cursor_to_display_pipe(plane); >>>> + >>>> +   if (dispipe->index == 0) >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >>>> +   else if (dispipe->index == 1) >>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, 0); >>>> + >>>> +   drm_dbg(ddev, "%s disabled\n", plane->name); >>>> +} >>>> + >>>> +static const struct drm_plane_helper_funcs >>>> lsdc_cursor_helpers_quirk = { >>>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>>> +   .atomic_check = lsdc_cursor_atomic_check, >>>> +   .atomic_update = lsdc_cursor_atomic_update_quirks, >>>> +   .atomic_disable = lsdc_cursor_atomic_disable_quirks, >>>> +}; >>>> + >>>> +static const struct drm_plane_helper_funcs >>>> lsdc_cursor_plane_helpers = { >>>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>>> +   .atomic_check = lsdc_cursor_atomic_check, >>>> +   .atomic_update = lsdc_cursor_atomic_update, >>>> +   .atomic_disable = lsdc_cursor_atomic_disable, >>>> +}; >>>> + >>>> +static const struct drm_plane_funcs lsdc_plane_funcs = { >>>> +   .update_plane = drm_atomic_helper_update_plane, >>>> +   .disable_plane = drm_atomic_helper_disable_plane, >>>> +   .destroy = drm_plane_cleanup, >>>> +   .reset = drm_atomic_helper_plane_reset, >>>> +   .atomic_duplicate_state = >>>> drm_atomic_helper_plane_duplicate_state, >>>> +   .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, >>>> +}; >>>> + >>>> +static const struct drm_plane_helper_funcs * >>>> +lsdc_get_cursor_helper_funcs(const struct lsdc_desc *descp) >>>> +{ >>>> +   if (descp->chip == CHIP_LS7A2000) >>>> +       return &lsdc_cursor_plane_helpers; >>>> + >>>> +   return &lsdc_cursor_helpers_quirk; >>>> +} >>>> + >>>> +int lsdc_plane_init(struct lsdc_device *ldev, >>>> +           struct drm_plane *plane, >>>> +           enum drm_plane_type type, >>>> +           unsigned int index) >>>> +{ >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> +   struct drm_device *ddev = &ldev->base; >>>> +   unsigned int format_count; >>>> +   const u32 *formats; >>>> +   const char *name; >>>> +   int ret; >>>> + >>>> +   switch (type) { >>>> +   case DRM_PLANE_TYPE_PRIMARY: >>>> +       formats = lsdc_primary_formats; >>>> +       format_count = ARRAY_SIZE(lsdc_primary_formats); >>>> +       name = "primary-%u"; >>>> +       break; >>>> +   case DRM_PLANE_TYPE_CURSOR: >>>> +       formats = lsdc_cursor_formats; >>>> +       format_count = ARRAY_SIZE(lsdc_cursor_formats); >>>> +       name = "cursor-%u"; >>>> +       break; >>>> +   case DRM_PLANE_TYPE_OVERLAY: >>>> +       drm_err(ddev, "overlay plane is not supported\n"); >>>> +       break; >>>> +   } >>>> + >>>> +   ret = drm_universal_plane_init(ddev, plane, 1 << index, >>>> +                      &lsdc_plane_funcs, >>>> +                      formats, format_count, >>>> +                      lsdc_fb_format_modifiers, >>>> +                      type, name, index); >>>> +   if (ret) { >>>> +       drm_err(ddev, "%s failed: %d\n", __func__, ret); >>>> +       return ret; >>>> +   } >>>> + >>>> +   switch (type) { >>>> +   case DRM_PLANE_TYPE_PRIMARY: >>>> +       drm_plane_helper_add(plane, &lsdc_primary_plane_helpers); >>>> +       break; >>>> +   case DRM_PLANE_TYPE_CURSOR: >>>> +       drm_plane_helper_add(plane, >>>> lsdc_get_cursor_helper_funcs(descp)); >>>> +       break; >>>> +   case DRM_PLANE_TYPE_OVERLAY: >>>> +       drm_err(ddev, "overlay plane is not supported\n"); >>>> +       break; >>>> +   } >>>> + >>>> +   return 0; >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.c >>>> b/drivers/gpu/drm/lsdc/lsdc_pll.c >>>> new file mode 100644 >>>> index 000000000000..6ed74989a6f5 >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.c >>>> @@ -0,0 +1,569 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> +#include "lsdc_pll.h" >>>> + >>>> +/* >>>> + * The structure of the pixel PLL register is evolved with times. >>>> + * All loongson's cpu is little endian. >>>> + */ >>>> + >>>> +/* u64 */ >>>> +struct ls7a1000_pixpll_bitmap { >>>> +   /* Byte 0 ~ Byte 3 */ >>>> +   unsigned div_out     : 7;  /* 0 : 6    output clock >>>> divider */ >>>> +   unsigned __1         : 14; /* 7 : >>>> 20                         */ >>>> +   unsigned loopc       : 9;  /* 21 : 29   clock >>>> multiplier     */ >>>> +   unsigned __2         : 2;  /* 30 : >>>> 31                         */ >>>> + >>>> +   /* Byte 4 ~ Byte 7 */ >>>> +   unsigned div_ref     : 7;  /* 0 : 6    input clock >>>> divider  */ >>>> +   unsigned locked      : 1;  /* 7        PLL locked >>>> status    */ >>>> +   unsigned sel_out     : 1;  /* 8        output clk >>>> selector  */ >>>> +   unsigned __3         : 2;  /* 9 : 10 reserved             */ >>>> +   unsigned set_param   : 1;  /* 11       trigger the >>>> update   */ >>>> +   unsigned bypass      : 1;  /* >>>> 12                             */ >>>> +   unsigned powerdown   : 1;  /* >>>> 13                             */ >>>> +   unsigned __4         : 18; /* 14 : >>>> 31                        */ >>>> +}; >>>> + >>>> +/* u128 */ >>>> +struct ls2k1000_pixpll_bitmap { >>>> +   /* Byte 0 ~ Byte 3 */ >>>> +   unsigned sel_out     : 1; /* 0     select this >>>> PLL         */ >>>> +   unsigned __1         : 1; /* >>>> 1                              */ >>>> +   unsigned sw_adj_en   : 1; /* 2     allow software >>>> adjust   */ >>>> +   unsigned bypass      : 1; /* 3     bypass L1 >>>> PLL           */ >>>> +   unsigned __2         : 3; /* >>>> 4:6                            */ >>>> +   unsigned lock_en     : 1; /* 7     enable lock L1 >>>> PLL      */ >>>> +   unsigned __3         : 2; /* >>>> 8:9                            */ >>>> +   unsigned lock_check  : 2; /* 10:11  precision >>>> check         */ >>>> +   unsigned __4         : 4; /* >>>> 12:15                           */ >>>> + >>>> +   unsigned locked      : 1; /* 16     PLL locked flag >>>> bit     */ >>>> +   unsigned __5         : 2; /* >>>> 17:18                           */ >>>> +   unsigned powerdown   : 1; /* 19     powerdown the pll if >>>> set */ >>>> +   unsigned __6         : 6; /* >>>> 20:25                           */ >>>> +   unsigned div_ref     : 6; /* 26:31  L1 >>>> Prescaler            */ >>>> + >>>> +   /* Byte 4 ~ Byte 7 */ >>>> +   unsigned loopc       : 10; /* 32:41  Clock >>>> Multiplier        */ >>>> +   unsigned l1_div      : 6; /* 42:47  not >>>> used                */ >>>> +   unsigned __7         : 16; /* >>>> 48:63                           */ >>>> + >>>> +   /* Byte 8 ~ Byte 15 */ >>>> +   unsigned div_out     : 6; /* 0 : 5  output clock >>>> divider    */ >>>> +   unsigned __8         : 26; /* 6 : >>>> 31                          */ >>>> +   unsigned __9         : 32; /* 70: >>>> 127                         */ >>>> +}; >>>> + >>>> +/* u32 */ >>>> +struct ls2k0500_pixpll_bitmap { >>>> +   /* Byte 0 ~ Byte 1 */ >>>> +   unsigned sel_out     : 1; >>>> +   unsigned __1         : 2; >>>> +   unsigned sw_adj_en   : 1;  /* allow software >>>> adjust             */ >>>> +   unsigned bypass      : 1;  /* bypass L1 >>>> PLL                     */ >>>> +   unsigned powerdown   : 1;  /* write 1 to powerdown the >>>> PLL      */ >>>> +   unsigned __2         : 1; >>>> +   unsigned locked      : 1;  /* 7    Is L1 PLL locked, read >>>> only */ >>>> +   unsigned div_ref     : 6;  /* 8:13 ref clock >>>> divider          */ >>>> +   unsigned __3         : 2;  /* >>>> 14:15                             */ >>>> +   /* Byte 2 ~ Byte 3 */ >>>> +   unsigned loopc       : 8;  /* 16:23  Clock >>>> Multiplier          */ >>>> +   unsigned div_out     : 6;  /* 24:29  output clock >>>> divider      */ >>>> +   unsigned __4         : 2;  /* >>>> 30:31                             */ >>>> +}; >>>> + >>>> +union lsdc_pixpll_bitmap { >>>> +   struct ls7a1000_pixpll_bitmap ls7a1000; >>>> +   struct ls2k1000_pixpll_bitmap ls2k1000; >>>> +   struct ls2k0500_pixpll_bitmap ls2k0500; >>>> + >>>> +   u32 dword[4]; >>>> +}; >>>> + >>>> +struct pixclk_to_pll_parm { >>>> +   /* kHz */ >>>> +   unsigned int clock; >>>> + >>>> +   unsigned short width; >>>> +   unsigned short height; >>>> +   unsigned short vrefresh; >>>> + >>>> +   /* Stores parameters for programming the Hardware PLLs */ >>>> +   unsigned short div_out; >>>> +   unsigned short loopc; >>>> +   unsigned short div_ref; >>>> +}; >>>> + >>>> +/* >>>> + * Pixel clock to PLL parameters translation table. >>>> + * Small static cached value to speed up PLL parameters calculation. >>>> + */ >>>> +static const struct pixclk_to_pll_parm pll_param_table[] = { >>>> +   {148500, 1920, 1080, 60, 11, 49, 3},  /* 1920x1080@60Hz */ >>>> +                       /* 1920x1080@50Hz */ >>>> +   {174500, 1920, 1080, 75, 17, 89, 3},  /* 1920x1080@75Hz */ >>>> +   {181250, 2560, 1080, 75, 8, 58, 4},  /* 2560x1080@75Hz */ >>>> +   {146250, 1680, 1050, 60, 16, 117, 5},  /* 1680x1050@60Hz */ >>>> +   {135000, 1280, 1024, 75, 10, 54, 4},  /* 1280x1024@75Hz */ >>>> + >>>> +   {108000, 1600, 900, 60, 15, 81, 5},  /* 1600x900@60Hz */ >>>> +                       /* 1280x1024@60Hz */ >>>> +                       /* 1280x960@60Hz */ >>>> +                       /* 1152x864@75Hz */ >>>> + >>>> +   {106500, 1440, 900, 60, 19, 81, 4},  /* 1440x900@60Hz */ >>>> +   {88750, 1440, 900, 60, 16, 71, 5},  /* 1440x900@60Hz */ >>>> +   {83500, 1280, 800, 60, 17, 71, 5},  /* 1280x800@60Hz */ >>>> +   {71000, 1280, 800, 60, 20, 71, 5},  /* 1280x800@60Hz */ >>>> + >>>> +   {74250, 1280, 720, 60, 22, 49, 3},  /* 1280x720@60Hz */ >>>> +                       /* 1280x720@50Hz */ >>>> + >>>> +   {78750, 1024, 768, 75, 16, 63, 5},  /* 1024x768@75Hz */ >>>> +   {75000, 1024, 768, 70, 29, 87, 4},  /* 1024x768@70Hz */ >>>> +   {65000, 1024, 768, 60, 20, 39, 3},  /* 1024x768@60Hz */ >>>> + >>>> +   {51200, 1024, 600, 60, 25, 64, 5},  /* 1024x600@60Hz */ >>>> + >>>> +   {57284, 832, 624, 75, 24, 55, 4},  /* 832x624@75Hz */ >>>> +   {49500, 800, 600, 75, 40, 99, 5},  /* 800x600@75Hz */ >>>> +   {50000, 800, 600, 72, 44, 88, 4},  /* 800x600@72Hz */ >>>> +   {40000, 800, 600, 60, 30, 36, 3},  /* 800x600@60Hz */ >>>> +   {36000, 800, 600, 56, 50, 72, 4},  /* 800x600@56Hz */ >>>> +   {31500, 640, 480, 75, 40, 63, 5},  /* 640x480@75Hz */ >>>> +                       /* 640x480@73Hz */ >>>> + >>>> +   {30240, 640, 480, 67, 62, 75, 4},  /* 640x480@67Hz */ >>>> +   {27000, 720, 576, 50, 50, 54, 4},  /* 720x576@60Hz */ >>>> +   {25175, 640, 480, 60, 85, 107, 5},  /* 640x480@60Hz */ >>>> +   {25200, 640, 480, 60, 50, 63, 5},  /* 640x480@60Hz */ >>>> +                       /* 720x480@60Hz */ >>>> +}; >>>> + >>>> +/* >>>> + * lsdc_pixpll_setup - ioremap the device dependent PLL registers >>>> + * >>>> + * @this: point to the object where this function is called from >>>> + */ >>>> +static int lsdc_pixpll_setup(struct lsdc_pll * const this) >>>> +{ >>>> +   this->mmio = ioremap(this->reg_base, this->reg_size); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +/* >>>> + * Find a set of pll parameters (to generate pixel clock) from a >>>> static >>>> + * local table, which avoid to compute the pll parameter eachtime a >>>> + * modeset is triggered. >>>> + * >>>> + * @this: point to the object which this function is called from >>>> + * @clock: the desired output pixel clock, the unit is kHz >>>> + * @pout: point to where the parameters to store if found >>>> + * >>>> + * Return true if hit, otherwise return false. >>>> + */ >>>> +static bool lsdc_pixpll_find(struct lsdc_pll * const this, >>>> +                unsigned int clock, >>>> +                struct lsdc_pll_parms * const pout) >>>> +{ >>>> +   unsigned int num = ARRAY_SIZE(pll_param_table); >>>> +   unsigned int i; >>>> + >>>> +   for (i = 0; i < num; i++) { >>>> +       if (clock != pll_param_table[i].clock) >>>> +           continue; >>>> + >>>> +       pout->div_ref = pll_param_table[i].div_ref; >>>> +       pout->loopc  = pll_param_table[i].loopc; >>>> +       pout->div_out = pll_param_table[i].div_out; >>>> + >>>> +       return true; >>>> +   } >>>> + >>>> +   drm_dbg(this->ddev, "pixel clock %u: miss\n", clock); >>>> + >>>> +   return false; >>>> +} >>>> + >>>> +/* >>>> + * Find a set of pll parameters which have minimal difference with >>>> the desired >>>> + * pixel clock frequency. It does that by computing all of the >>>> possible >>>> + * combination. Compute the diff and find the combination with >>>> minimal diff. >>>> + * >>>> + * clock_out = refclk / div_ref * loopc / div_out >>>> + * >>>> + * refclk is determined by the oscillator mounted board(Here is >>>> 100MHz in >>>> + * almost all case) >>>> + * >>>> + * @this: point to the object from which this function is called >>>> + * @clock_khz: the desired output pixel clock, the unit is kHz >>>> + * @pout: point to the out struct of lsdc_pll_parms >>>> + * >>>> + * Return true if a parameter is found, otherwise return false >>>> + */ >>>> +static bool lsdc_pixpll_compute(struct lsdc_pll * const this, >>>> +               unsigned int clock_khz, >>>> +               struct lsdc_pll_parms *pout) >>>> +{ >>>> +   unsigned int refclk = this->ref_clock; >>>> +   const unsigned int tolerance = 1000; >>>> +   unsigned int min = tolerance; >>>> +   unsigned int div_out, loopc, div_ref; >>>> +   unsigned int computed; >>>> + >>>> +   if (lsdc_pixpll_find(this, clock_khz, pout)) >>>> +       return true; >>>> + >>>> +   for (div_out = 6; div_out < 64; div_out++) { >>>> +       for (div_ref = 3; div_ref < 6; div_ref++) { >>>> +           for (loopc = 6; loopc < 161; loopc++) { >>>> +               int diff; >>>> + >>>> +               if (loopc < 12 * div_ref) >>>> +                   continue; >>>> +               if (loopc > 32 * div_ref) >>>> +                   continue; >>>> + >>>> +               computed = refclk * loopc / div_ref / div_out; >>>> + >>>> +               if (clock_khz > computed) >>>> +                   diff = clock_khz - computed; >>>> +               else if (clock_khz < computed) >>>> +                   diff = computed - clock_khz; >>>> + >>>> +               if (diff < min) { >>>> +                   min = diff; >>>> +                   pout->div_ref = div_ref; >>>> +                   pout->div_out = div_out; >>>> +                   pout->loopc = loopc; >>>> + >>>> +                   if (diff == 0) >>>> +                       return true; >>>> +               } >>>> +           } >>>> +       } >>>> +   } >>>> + >>>> +   return min < tolerance; >>>> +} >>>> + >>>> +/* >>>> + * Update the pll parameters to hardware, target to the pixpll in >>>> ls7a1000 >>>> + * >>>> + * @this: point to the object from which this function is called >>>> + * @pin: point to the struct of lsdc_pll_parms passed in >>>> + * >>>> + * return 0 if successful. >>>> + */ >>>> +static int ls7a1000_pixpll_param_update(struct lsdc_pll * const this, >>>> +                   struct lsdc_pll_parms const *pin) >>>> +{ >>>> +   void __iomem *reg = this->mmio; >>>> +   unsigned int counter = 0; >>>> +   bool locked; >>>> +   u32 val; >>>> + >>>> +   /* Bypass the software configured PLL, using refclk directly */ >>>> +   val = readl(reg + 0x4); >>>> +   val &= ~(1 << 8); >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   /* Powerdown the PLL */ >>>> +   val = readl(reg + 0x4); >>>> +   val |= (1 << 13); >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   /* Clear the pll parameters */ >>>> +   val = readl(reg + 0x4); >>>> +   val &= ~(1 << 11); >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   /* clear old value & config new value */ >>>> +   val = readl(reg + 0x04); >>>> +   val &= ~0x7F; >>>> +   val |= pin->div_ref;       /* div_ref */ >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   val = readl(reg); >>>> +   val &= ~0x7f; >>>> +   val |= pin->div_out;       /* div_out */ >>>> + >>>> +   val &= ~(0x1ff << 21); >>>> +   val |= pin->loopc << 21;   /* loopc */ >>>> +   writel(val, reg); >>>> + >>>> +   /* Set the pll the parameters */ >>>> +   val = readl(reg + 0x4); >>>> +   val |= (1 << 11); >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   /* Powerup the PLL */ >>>> +   val = readl(reg + 0x4); >>>> +   val &= ~(1 << 13); >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   /* Wait the PLL lock */ >>>> +   do { >>>> +       val = readl(reg + 0x4); >>>> +       locked = val & 0x80; >>>> +       counter++; >>>> +   } while (!locked && (counter < 2000)); >>>> + >>>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>>> + >>>> +   /* Switch to the software configured pll */ >>>> +   val = readl(reg + 0x4); >>>> +   val |= (1UL << 8); >>>> +   writel(val, reg + 0x4); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +/* >>>> + * Update the pll parameters to hardware, target to the pixpll in >>>> ls2k1000 >>>> + * >>>> + * @this: point to the object from which this function is called >>>> + * @pin: point to the struct of lsdc_pll_parms passed in >>>> + * >>>> + * return 0 if successful. >>>> + */ >>>> +static int ls2k1000_pixpll_param_update(struct lsdc_pll * const this, >>>> +                   struct lsdc_pll_parms const *pin) >>>> +{ >>>> +   void __iomem *reg = this->mmio; >>>> +   unsigned int counter = 0; >>>> +   bool locked = false; >>>> +   u32 val; >>>> + >>>> +   val = readl(reg); >>>> +   /* Bypass the software configured PLL, using refclk directly */ >>>> +   val &= ~(1 << 0); >>>> +   writel(val, reg); >>>> + >>>> +   /* Powerdown the PLL */ >>>> +   val |= (1 << 19); >>>> +   writel(val, reg); >>>> + >>>> +   /* Allow the software configuration */ >>>> +   val &= ~(1 << 2); >>>> +   writel(val, reg); >>>> + >>>> +   /* allow L1 PLL lock */ >>>> +   val = (1L << 7) | (3L << 10); >>>> +   writel(val, reg); >>>> + >>>> +   /* clear div_ref bit field */ >>>> +   val &= ~(0x3f << 26); >>>> +   /* set div_ref bit field */ >>>> +   val |= pin->div_ref << 26; >>>> +   writel(val, reg); >>>> + >>>> +   val = readl(reg + 4); >>>> +   /* clear loopc bit field */ >>>> +   val &= ~0x0fff; >>>> +   /* set loopc bit field */ >>>> +   val |= pin->loopc; >>>> +   writel(val, reg + 4); >>>> + >>>> +   /* set div_out */ >>>> +   writel(pin->div_out, reg + 8); >>>> + >>>> +   val = readl(reg); >>>> +   /* use this parms configured just now */ >>>> +   val |= (1 << 2); >>>> +   /* powerup the PLL */ >>>> +   val &= ~(1 << 19); >>>> +   writel(val, reg); >>>> + >>>> +   /* wait pll setup and locked */ >>>> +   do { >>>> +       val = readl(reg); >>>> +       locked = val & 0x10000; >>>> +       counter++; >>>> +   } while (!locked && (counter < 2000)); >>>> + >>>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>>> + >>>> +   /* Switch to software configured PLL instead of refclk */ >>>> +   val |= 1; >>>> +   writel(val, reg); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +/* >>>> + * Update the pll parameters to hardware, target to the pixpll in >>>> ls2k0500 >>>> + * >>>> + * @this: point to the object which calling this function >>>> + * @param: pointer to where the parameters passed in >>>> + * >>>> + * return 0 if successful. >>>> + */ >>>> +static int ls2k0500_pixpll_param_update(struct lsdc_pll * const this, >>>> +                   struct lsdc_pll_parms const *param) >>>> +{ >>>> +   void __iomem *reg = this->mmio; >>>> +   unsigned int counter = 0; >>>> +   bool locked = false; >>>> +   u32 val; >>>> + >>>> +   /* Bypass the software configured PLL, using refclk directly */ >>>> +   val = readl(reg); >>>> +   val &= ~(1 << 0); >>>> +   writel(val, reg); >>>> + >>>> +   /* Powerdown the PLL */ >>>> +   val = readl(reg); >>>> +   val |= (1 << 5); >>>> +   writel(val, reg); >>>> + >>>> +   /* Allow the software configuration */ >>>> +   val |= (1 << 3); >>>> +   writel(val, reg); >>>> + >>>> +   /* Update the pll params */ >>>> +   val = (param->div_out << 24) | >>>> +         (param->loopc << 16) | >>>> +         (param->div_ref << 8); >>>> + >>>> +   writel(val, reg); >>>> + >>>> +   /* Powerup the PLL */ >>>> +   val = readl(reg); >>>> +   val &= ~(1 << 5); >>>> +   writel(val, reg); >>>> + >>>> +   /* wait pll setup and locked */ >>>> +   do { >>>> +       val = readl(reg); >>>> +       locked = val & 0x80; >>>> +       counter++; >>>> +   } while (!locked && (counter < 10000)); >>>> + >>>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>>> + >>>> +   /* Switch to software configured PLL instead of refclk */ >>>> +   writel((val | 1), reg); >>>> + >>>> +   return 0; >>>> +} >>>> + >>>> +static unsigned int lsdc_get_clock_rate(struct lsdc_pll * const this, >>>> +                   struct lsdc_pll_parms *pout) >>>> +{ >>>> +   struct drm_device *ddev = this->ddev; >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   const struct lsdc_desc * const descp = ldev->descp; >>>> +   unsigned int out; >>>> +   union lsdc_pixpll_bitmap parms; >>>> + >>>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>>> CHIP_LS7A2000) { >>>> +       struct ls7a1000_pixpll_bitmap *obj = &parms.ls7a1000; >>>> + >>>> +       parms.dword[0] = readl(this->mmio); >>>> +       parms.dword[1] = readl(this->mmio + 4); >>>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>>> obj->div_out; >>>> +       if (pout) { >>>> +           pout->div_ref = obj->div_ref; >>>> +           pout->loopc = obj->loopc; >>>> +           pout->div_out = obj->div_out; >>>> +       } >>>> +   } else if (descp->chip == CHIP_LS2K1000) { >>>> +       struct ls2k1000_pixpll_bitmap *obj = &parms.ls2k1000; >>>> + >>>> +       parms.dword[0] = readl(this->mmio); >>>> +       parms.dword[1] = readl(this->mmio + 4); >>>> +       parms.dword[2] = readl(this->mmio + 8); >>>> +       parms.dword[3] = readl(this->mmio + 12); >>>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>>> obj->div_out; >>>> +       if (pout) { >>>> +           pout->div_ref = obj->div_ref; >>>> +           pout->loopc = obj->loopc; >>>> +           pout->div_out = obj->div_out; >>>> +       } >>>> +   } else if (descp->chip == CHIP_LS2K0500) { >>>> +       struct ls2k0500_pixpll_bitmap *obj = &parms.ls2k0500; >>>> + >>>> +       parms.dword[0] = readl(this->mmio); >>>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>>> obj->div_out; >>>> +       if (pout) { >>>> +           pout->div_ref = obj->div_ref; >>>> +           pout->loopc = obj->loopc; >>>> +           pout->div_out = obj->div_out; >>>> +       } >>>> +   } else { >>>> +       drm_err(ddev, "unknown chip, the driver need update\n"); >>>> +       return 0; >>>> +   } >>>> + >>>> +   return out; >>>> +} >>>> + >>>> +static const struct lsdc_pixpll_funcs ls7a1000_pixpll_funcs = { >>>> +   .setup = lsdc_pixpll_setup, >>>> +   .compute = lsdc_pixpll_compute, >>>> +   .update = ls7a1000_pixpll_param_update, >>>> +   .get_clock_rate = lsdc_get_clock_rate, >>>> +}; >>>> + >>>> +static const struct lsdc_pixpll_funcs ls2k1000_pixpll_funcs = { >>>> +   .setup = lsdc_pixpll_setup, >>>> +   .compute = lsdc_pixpll_compute, >>>> +   .update = ls2k1000_pixpll_param_update, >>>> +   .get_clock_rate = lsdc_get_clock_rate, >>>> +}; >>>> + >>>> +static const struct lsdc_pixpll_funcs ls2k0500_pixpll_funcs = { >>>> +   .setup = lsdc_pixpll_setup, >>>> +   .compute = lsdc_pixpll_compute, >>>> +   .update = ls2k0500_pixpll_param_update, >>>> +   .get_clock_rate = lsdc_get_clock_rate, >>>> +}; >>>> + >>>> +int lsdc_pixpll_init(struct lsdc_pll * const this, >>>> +            struct drm_device *ddev, >>>> +            unsigned int index) >>>> +{ >>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>> +   const struct lsdc_desc *descp = ldev->descp; >>>> + >>>> +   this->ddev = ddev; >>>> +   this->index = index; >>>> +   this->ref_clock = LSDC_PLL_REF_CLK; >>>> + >>>> +   /* LS7A1000 and LS7A2000's pixpll setting registers is same */ >>>> +   if (descp->chip == CHIP_LS7A2000 || descp->chip == >>>> CHIP_LS7A1000) { >>>> +       if (index == 0) >>>> +           this->reg_base = LS7A1000_CFG_REG_BASE + >>>> LS7A1000_PIX_PLL0_REG; >>>> +       else if (index == 1) >>>> +           this->reg_base = LS7A1000_CFG_REG_BASE + >>>> LS7A1000_PIX_PLL1_REG; >>>> +       this->reg_size = 8; >>>> +       this->funcs = &ls7a1000_pixpll_funcs; >>>> +   } else if (descp->chip == CHIP_LS2K1000) { >>>> +       if (index == 0) >>>> +           this->reg_base = LS2K1000_CFG_REG_BASE + >>>> LS2K1000_PIX_PLL0_REG; >>>> +       else if (index == 1) >>>> +           this->reg_base = LS2K1000_CFG_REG_BASE + >>>> LS2K1000_PIX_PLL1_REG; >>>> + >>>> +       this->reg_size = 16; >>>> +       this->funcs = &ls2k1000_pixpll_funcs; >>>> +   } else if (descp->chip == CHIP_LS2K0500) { >>>> +       if (index == 0) >>>> +           this->reg_base = LS2K0500_CFG_REG_BASE + >>>> LS2K0500_PIX_PLL0_REG; >>>> +       else if (index == 1) >>>> +           this->reg_base = LS2K0500_CFG_REG_BASE + >>>> LS2K0500_PIX_PLL1_REG; >>>> + >>>> +       this->reg_size = 4; >>>> +       this->funcs = &ls2k0500_pixpll_funcs; >>>> +   } else { >>>> +       drm_err(this->ddev, "unknown chip, the driver need >>>> update\n"); >>>> +       return -ENOENT; >>>> +   } >>>> + >>>> +   return this->funcs->setup(this); >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.h >>>> b/drivers/gpu/drm/lsdc/lsdc_pll.h >>>> new file mode 100644 >>>> index 000000000000..538739f461f2 >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.h >>>> @@ -0,0 +1,78 @@ >>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>> + >>>> +#ifndef __LSDC_PLL_H__ >>>> +#define __LSDC_PLL_H__ >>>> + >>>> +#include <drm/drm_device.h> >>>> + >>>> +/* >>>> + * Loongson Pixel PLL hardware structure >>>> + * >>>> + * refclk: reference frequency, 100 MHz from external oscillator >>>> + * outclk: output frequency desired. >>>> + * >>>> + * >>>> + *              L1      Fref Fvco    L2 >>>> + * refclk  +-----------+     +------------------+ +---------+ >>>> outclk >>>> + * ---+---> | Prescaler | ---> | Clock Multiplier | ---> | divider >>>> | --------> >>>> + *   |    +-----------+     +------------------+ +---------+    ^ >>>> + *   |          ^                     ^ ^         | >>>> + *   |          |                     | |         | >>>> + *   |          |                     | |         | >>>> + *   |       div_ref                loopc div_out | >>>> + * | | >>>> + *   +--- sel_out (bypass above software configurable clock if >>>> set) ----+ >>>> + * >>>> + * sel_out: PLL clock output selector (for debug purpose only). >>>> + * >>>> + * If sel_out == 1, it will take refclk as output directly, >>>> + * the L1 Prescaler and the out divider will be bypassed. >>>> + * >>>> + * If sel_out == 0, then outclk = refclk / div_ref * loopc / >>>> div_out; >>>> + * >>>> + * PLL working requirements: >>>> + * >>>> + * 1) 20 MHz <= refclk / div_ref <= 40Mhz >>>> + * 2) 1.2 GHz <= refclk /div_out * loopc <= 3.2 Ghz >>>> + */ >>>> + >>>> +struct lsdc_pll_parms { >>>> +   unsigned int div_ref; >>>> +   unsigned int loopc; >>>> +   unsigned int div_out; >>>> +}; >>>> + >>>> +struct lsdc_pll; >>>> + >>>> +struct lsdc_pixpll_funcs { >>>> +   int (*setup)(struct lsdc_pll * const this); >>>> +   bool (*compute)(struct lsdc_pll * const this, >>>> +           unsigned int clock, >>>> +           struct lsdc_pll_parms *pout); >>>> +   int (*update)(struct lsdc_pll * const this, >>>> +             struct lsdc_pll_parms const *pin); >>>> +   unsigned int (*get_clock_rate)(struct lsdc_pll * const this, >>>> +                      struct lsdc_pll_parms *pout); >>>> +}; >>>> + >>>> +struct lsdc_pll { >>>> +   const struct lsdc_pixpll_funcs *funcs; >>>> +   struct drm_device *ddev; >>>> +   void __iomem *mmio; >>>> + >>>> +   /* PLL register offset */ >>>> +   u32 reg_base; >>>> +   /* PLL register size in bytes */ >>>> +   u32 reg_size; >>>> + >>>> +   /* 100000kHz, fixed on all board found */ >>>> +   unsigned int ref_clock; >>>> + >>>> +   unsigned int index; >>>> +}; >>>> + >>>> +int lsdc_pixpll_init(struct lsdc_pll * const this, >>>> +            struct drm_device *ddev, >>>> +            unsigned int index); >>>> + >>>> +#endif >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.c >>>> b/drivers/gpu/drm/lsdc/lsdc_regs.c >>>> new file mode 100644 >>>> index 000000000000..c7950c43968f >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.c >>>> @@ -0,0 +1,29 @@ >>>> +// SPDX-License-Identifier: GPL-2.0 >>>> + >>>> +#include "lsdc_drv.h" >>>> +#include "lsdc_regs.h" >>>> + >>>> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset) >>>> +{ >>>> +   unsigned long flags; >>>> +   u32 ret; >>>> + >>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>> + >>>> +   ret = readl(ldev->reg_base + offset); >>>> + >>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>> + >>>> +   return ret; >>>> +} >>>> + >>>> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 >>>> val) >>>> +{ >>>> +   unsigned long flags; >>>> + >>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>> + >>>> +   writel(val, ldev->reg_base + offset); >>>> + >>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>> +} >>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.h >>>> b/drivers/gpu/drm/lsdc/lsdc_regs.h >>>> new file mode 100644 >>>> index 000000000000..828956633137 >>>> --- /dev/null >>>> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.h >>>> @@ -0,0 +1,296 @@ >>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>> + >>>> +#ifndef __LSDC_REGS_H__ >>>> +#define __LSDC_REGS_H__ >>>> + >>>> +#include <linux/bitops.h> >>>> +#include <linux/types.h> >>>> + >>>> +/* >>>> + * PIXEL PLL Reference clock >>>> + */ >>>> +#define LSDC_PLL_REF_CLK               100000          /* kHz */ >>>> + >>>> +/* >>>> + * Those PLL registers are not located at DC reg bar space, >>>> + * there are relative to LSXXXXX_CFG_REG_BASE. >>>> + * XXXXX = 7A1000, 2K1000, 2K0500 >>>> + */ >>>> + >>>> +/* LS2K1000 */ >>>> +#define LS2K1000_PIX_PLL0_REG          0x04B0 >>>> +#define LS2K1000_PIX_PLL1_REG          0x04C0 >>>> +#define LS2K1000_CFG_REG_BASE          0x1fe10000 >>>> + >>>> +/* LS7A1000 and LS2K2000 */ >>>> +#define LS7A1000_PIX_PLL0_REG          0x04B0 >>>> +#define LS7A1000_PIX_PLL1_REG          0x04C0 >>>> +#define LS7A1000_CFG_REG_BASE          0x10010000 >>>> + >>>> +/* LS2K0500 */ >>>> +#define LS2K0500_PIX_PLL0_REG          0x0418 >>>> +#define LS2K0500_PIX_PLL1_REG          0x0420 >>>> +#define LS2K0500_CFG_REG_BASE          0x1fe10000 >>>> + >>>> +/* >>>> + * CRTC CFG REG >>>> + */ >>>> +#define CFG_PIX_FMT_MASK               GENMASK(2, 0) >>>> + >>>> +enum lsdc_pixel_format { >>>> +   LSDC_PF_NONE = 0, >>>> +   LSDC_PF_ARGB4444 = 1, /* ARGB A:4 bits R/G/B: 4 bits each [16 >>>> bits] */ >>>> +   LSDC_PF_ARGB1555 = 2, /* ARGB A:1 bit RGB:15 bits [16 bits] */ >>>> +   LSDC_PF_RGB565 = 3,   /* RGB [16 bits] */ >>>> +   LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */ >>>> +   LSDC_PF_RGBA8888 = 5, /* ARGB [32 bits] */ >>>> +}; >>>> + >>>> +/* >>>> + * Each crtc has two set fb address registers usable, >>>> CFG_FB_IN_USING of >>>> + * LSDC_CRTCx_CFG_REG specify which fb address register is currently >>>> + * in using by the CRTC. CFG_PAGE_FLIP of LSDC_CRTCx_CFG_REG is >>>> used to >>>> + * trigger the switch which will be finished at the very vblank. >>>> If you >>>> + * want it switch to another again, you must set CFG_PAGE_FLIP again. >>>> + */ >>>> +#define CFG_PAGE_FLIP                  BIT(7) >>>> +#define CFG_OUTPUT_EN                  BIT(8) >>>> +/* CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware >>>> logic */ >>>> +#define CFG_PANEL_SWITCH               BIT(9) >>>> +/* Indicate witch fb addr reg is in using, currently */ >>>> +#define CFG_FB_IN_USING                BIT(11) >>>> +#define CFG_GAMMA_EN                   BIT(12) >>>> + >>>> +/* CRTC get soft reset if voltage level change from 1 -> 0 */ >>>> +#define CFG_RESET_N                    BIT(20) >>>> + >>>> +#define CFG_HSYNC_EN                   BIT(30) >>>> +#define CFG_HSYNC_INV                  BIT(31) >>>> +#define CFG_VSYNC_EN                   BIT(30) >>>> +#define CFG_VSYNC_INV                  BIT(31) >>>> + >>>> +/* THE DMA step of the DC in LS7A2000 is configurable */ >>>> +#define LSDC_DMA_STEP_MASK             GENMASK(17, 16) >>>> +enum lsdc_dma_steps_supported { >>>> +   LSDC_DMA_STEP_256_BYTES = 0, >>>> +   LSDC_DMA_STEP_128_BYTES = 1, >>>> +   LSDC_DMA_STEP_64_BYTES = 2, >>>> +   LSDC_DMA_STEP_32_BYTES = 3, >>>> +}; >>>> + >>>> +/******** CRTC0 & DVO0 ********/ >>>> +#define LSDC_CRTC0_CFG_REG             0x1240 >>>> +#define LSDC_CRTC0_FB0_LO_ADDR_REG     0x1260 >>>> +#define LSDC_CRTC0_FB0_HI_ADDR_REG     0x15A0 >>>> +#define LSDC_CRTC0_FB1_LO_ADDR_REG     0x1580 >>>> +#define LSDC_CRTC0_FB1_HI_ADDR_REG     0x15C0 >>>> +#define LSDC_CRTC0_STRIDE_REG          0x1280 >>>> +#define LSDC_CRTC0_FB_ORIGIN_REG       0x1300 >>>> +#define LSDC_CRTC0_HDISPLAY_REG        0x1400 >>>> +#define LSDC_CRTC0_HSYNC_REG           0x1420 >>>> +#define LSDC_CRTC0_VDISPLAY_REG        0x1480 >>>> +#define LSDC_CRTC0_VSYNC_REG           0x14A0 >>>> +#define LSDC_CRTC0_GAMMA_INDEX_REG     0x14E0 >>>> +#define LSDC_CRTC0_GAMMA_DATA_REG      0x1500 >>>> + >>>> +/******** CTRC1 & DVO1 ********/ >>>> +#define LSDC_CRTC1_CFG_REG             0x1250 >>>> +#define LSDC_CRTC1_FB0_LO_ADDR_REG     0x1270 >>>> +#define LSDC_CRTC1_FB0_HI_ADDR_REG     0x15B0 >>>> +#define LSDC_CRTC1_FB1_LO_ADDR_REG     0x1590 >>>> +#define LSDC_CRTC1_FB1_HI_ADDR_REG     0x15D0 >>>> +#define LSDC_CRTC1_STRIDE_REG          0x1290 >>>> +#define LSDC_CRTC1_FB_ORIGIN_REG       0x1310 >>>> +#define LSDC_CRTC1_HDISPLAY_REG        0x1410 >>>> +#define LSDC_CRTC1_HSYNC_REG           0x1430 >>>> +#define LSDC_CRTC1_VDISPLAY_REG        0x1490 >>>> +#define LSDC_CRTC1_VSYNC_REG           0x14B0 >>>> +#define LSDC_CRTC1_GAMMA_INDEX_REG     0x14F0 >>>> +#define LSDC_CRTC1_GAMMA_DATA_REG      0x1510 >>>> + >>>> +/* >>>> + * LS7A2000 has the hardware which record the scan position of the >>>> CRTC >>>> + * [31:16] : current X position, [15:0] : current Y position >>>> + */ >>>> +#define LSDC_CRTC0_SCAN_POS_REG        0x14C0 >>>> +#define LSDC_CRTC1_SCAN_POS_REG        0x14D0 >>>> + >>>> +/* >>>> + * LS7A2000 has the hardware which count the number of vblank >>>> generated >>>> + */ >>>> +#define LSDC_CRTC0_VSYNC_COUNTER_REG   0x1A00 >>>> +#define LSDC_CRTC1_VSYNC_COUNTER_REG   0x1A10 >>>> + >>>> +/* In all, LSDC_CRTC1_XXX_REG - LSDC_CRTC0_XXX_REG = 0x10 */ >>>> + >>>> +/* >>>> + * There is only one hardware cursor unit in ls7a1000, ls2k1000 >>>> and ls2k0500. >>>> + * well, ls7a2000 has two hardware cursor unit. >>>> + */ >>>> +#define CURSOR_FORMAT_MASK             GENMASK(1, 0) >>>> +enum lsdc_cursor_format { >>>> +   CURSOR_FORMAT_DISABLE = 0, >>>> +   CURSOR_FORMAT_MONOCHROME = 1, >>>> +   CURSOR_FORMAT_ARGB8888 = 2, >>>> +}; >>>> + >>>> +#define CURSOR_SIZE_64X64              BIT(2) >>>> +#define CURSOR_LOCATION                BIT(4) >>>> + >>>> +#define LSDC_CURSOR0_CFG_REG           0x1520 >>>> +#define LSDC_CURSOR0_ADDR_LO_REG       0x1530 >>>> +#define LSDC_CURSOR0_ADDR_HI_REG       0x15e0 >>>> +#define LSDC_CURSOR0_POSITION_REG      0x1540 >>>> +#define LSDC_CURSOR0_BG_COLOR_REG      0x1550 /* background >>>> color */ >>>> +#define LSDC_CURSOR0_FG_COLOR_REG      0x1560 /* foreground >>>> color */ >>>> + >>>> +#define LSDC_CURSOR1_CFG_REG           0x1670 >>>> +#define LSDC_CURSOR1_ADDR_LO_REG       0x1680 >>>> +#define LSDC_CURSOR1_ADDR_HI_REG       0x16e0 >>>> +#define LSDC_CURSOR1_POSITION_REG      0x1690 /* [31:16] Y, >>>> [15:0] X */ >>>> +#define LSDC_CURSOR1_BG_COLOR_REG      0x16A0 /* background >>>> color */ >>>> +#define LSDC_CURSOR1_FG_COLOR_REG      0x16B0 /* foreground >>>> color */ >>>> + >>>> +/* >>>> + * DC Interrupt Control Register, 32bit, Address Offset: 1570 >>>> + * >>>> + * Bits 15:0 inidicate the interrupt status >>>> + * Bits 31:16 control enable interrupts corresponding to bit 15:0 >>>> or not >>>> + * Write 1 to enable, write 0 to disable >>>> + * >>>> + * RF: Read Finished >>>> + * IDBU: Internal Data Buffer Underflow >>>> + * IDBFU: Internal Data Buffer Fatal Underflow >>>> + * CBRF: Cursor Buffer Read Finished Flag, no use. >>>> + * >>>> + * >>>> +-------+--------------------------+-------+--------+--------+-------+ >>>> + * | 31:27 |        26:16           | 15:11 |  10  | 9 |  8  | >>>> + * >>>> +-------+--------------------------+-------+--------+--------+-------+ >>>> + * | N/A | Interrupt Enable Control | N/A | IDBFU0 | IDBFU1 | >>>> IDBU0 | >>>> + * >>>> +-------+--------------------------+-------+--------+--------+-------+ >>>> + * >>>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>>> + * |  7  | 6 | 5 | 4  |  3   |  2   |  1   | 0   | >>>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>>> + * | IDBU1 | RF0 | RF1 | CRRF | HSYNC0 | VSYNC0 | HSYNC1 | VSYNC1 | >>>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>>> + */ >>>> + >>>> +#define LSDC_INT_REG                   0x1570 >>>> + >>>> +#define INT_CRTC0_VSYNC                BIT(2) >>>> +#define INT_CRTC0_HSYNC                BIT(3) >>>> +#define INT_CRTC0_RF                   BIT(6) >>>> +#define INT_CRTC0_IDBU                 BIT(8) >>>> +#define INT_CRTC0_IDBFU                BIT(10) >>>> + >>>> +#define INT_CRTC1_VSYNC                BIT(0) >>>> +#define INT_CRTC1_HSYNC                BIT(1) >>>> +#define INT_CRTC1_RF                   BIT(5) >>>> +#define INT_CRTC1_IDBU                 BIT(7) >>>> +#define INT_CRTC1_IDBFU                BIT(9) >>>> + >>>> +#define INT_CRTC0_VS_EN                BIT(18) >>>> +#define INT_CRTC0_HS_EN                BIT(19) >>>> +#define INT_CRTC0_RF_EN                BIT(22) >>>> +#define INT_CRTC0_IDBU_EN              BIT(24) >>>> +#define INT_CRTC0_IDBFU_EN             BIT(26) >>>> + >>>> +#define INT_CRTC1_VS_EN                BIT(16) >>>> +#define INT_CRTC1_HS_EN                BIT(17) >>>> +#define INT_CRTC1_RF_EN                BIT(21) >>>> +#define INT_CRTC1_IDBU_EN              BIT(23) >>>> +#define INT_CRTC1_IDBFU_EN             BIT(25) >>>> + >>>> +#define INT_STATUS_MASK                GENMASK(15, 0) >>>> + >>>> +/* >>>> + * LS7A1000/LS7A2000 have 4 gpios which are used to emulated I2C. >>>> + * They are under control of the LS7A_DC_GPIO_DAT_REG and >>>> LS7A_DC_GPIO_DIR_REG >>>> + * register, Those GPIOs has no relationship whth the GPIO >>>> hardware on the >>>> + * bridge chip itself. Those offsets are relative to DC register >>>> base address >>>> + * >>>> + * LS2k1000 and LS2K0500 don't have those registers, they use >>>> hardware i2c >>>> + * or general GPIO emulated i2c from linux i2c subsystem. >>>> + * >>>> + * GPIO data register, address offset: 0x1650 >>>> + *  +---------------+-----------+-----------+ >>>> + *  | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | >>>> + *  +---------------+-----------+-----------+ >>>> + *  |              |   DVO1  |   DVO0  | >>>> + *  +     N/A     +-----------+-----------+ >>>> + *  |              | SCL | SDA | SCL | SDA | >>>> + *  +---------------+-----------+-----------+ >>>> + */ >>>> +#define LS7A_DC_GPIO_DAT_REG           0x1650 >>>> + >>>> +/* >>>> + * GPIO Input/Output direction control register, address offset: >>>> 0x1660 >>>> + */ >>>> +#define LS7A_DC_GPIO_DIR_REG           0x1660 >>>> + >>>> +/* >>>> + * LS7A2000 has two built-in HDMI Encoder and one VGA encoder >>>> + */ >>>> + >>>> +/* >>>> + * Number of continuous packets may be present >>>> + * in HDMI hblank and vblank zone, should >= 48 >>>> + */ >>>> +#define LSDC_HDMI0_ZONE_REG            0x1700 >>>> +#define LSDC_HDMI1_ZONE_REG            0x1710 >>>> + >>>> +#define HDMI_INTERFACE_EN              BIT(0) >>>> +#define HDMI_PACKET_EN                 BIT(1) >>>> +#define HDMI_AUDIO_EN                  BIT(2) >>>> +#define HDMI_VIDEO_PREAMBLE_MASK       GENMASK(7, 4) >>>> +#define HDMI_HW_I2C_EN                 BIT(8) >>>> +#define HDMI_CTL_PERIOD_MODE           BIT(9) >>>> +#define LSDC_HDMI0_CTRL_REG            0x1720 >>>> +#define LSDC_HDMI1_CTRL_REG            0x1730 >>>> + >>>> +#define HDMI_PHY_EN                    BIT(0) >>>> +#define HDMI_PHY_RESET_N               BIT(1) >>>> +#define HDMI_PHY_TERM_L_EN             BIT(8) >>>> +#define HDMI_PHY_TERM_H_EN             BIT(9) >>>> +#define HDMI_PHY_TERM_DET_EN           BIT(10) >>>> +#define HDMI_PHY_TERM_STATUS           BIT(11) >>>> +#define LSDC_HDMI0_PHY_CTRL_REG        0x1800 >>>> +#define LSDC_HDMI1_PHY_CTRL_REG        0x1810 >>>> + >>>> +/* >>>> + * 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 | >>>> + * +--------------------------------------------------------+ >>>> + */ >>>> +#define LSDC_HDMI0_PHY_PLL_REG         0x1820 >>>> +#define LSDC_HDMI1_PHY_PLL_REG         0x1830 >>>> + >>>> +#define HDMI_PLL_ENABLE                BIT(0) >>>> +#define HDMI_PLL_LOCKED                BIT(16) >>>> +#define HDMI_PLL_BYPASS                BIT(17) >>>> + >>>> +#define HDMI_PLL_IDF_SHIFT             1 >>>> +#define HDMI_PLL_IDF_MASK              GENMASK(5, 1) >>>> +#define HDMI_PLL_LF_SHIFT              6 >>>> +#define HDMI_PLL_LF_MASK               GENMASK(12, 6) >>>> +#define HDMI_PLL_ODF_SHIFT             13 >>>> +#define HDMI_PLL_ODF_MASK              GENMASK(15, 13) >>>> + >>>> +/* LS7A2000 have hpd support */ >>>> +#define LSDC_HDMI_HPD_STATUS_REG       0x1BA0 >>>> +#define HDMI0_HPD_FLAG                 BIT(0) >>>> +#define HDMI1_HPD_FLAG                 BIT(1) >>>> + >>>> +#endif >>> >> >
Hi Am 18.02.23 um 16:30 schrieb Sui Jingfeng: [...] >> >> No problem. I think I mentioned that it wouldn't make sense to merge >> the DRM driver before ACPI and/or DT are ready. So we have time to get >> the driver into good shape. >> > Sigh, :( > > Then can we drop the support for SoC chips temporary, and provide DT > support at latter time? Yes, of course DT can be provided later when it is ready. If you have something that is currently usable, you can submit it. But if your patch depends on other kernel features, those features should be ready first. And if your driver cannot be used at all without those features, there's no point in merging the driver until the feature is ready. > > this patch is target to providing the generic part of device driver > support for Loongson integrated graphics. > > I see the double mention :(, but still want to make sure if it is > negotiable. Because for the DC in our bridge chip, > > this driver can be probed reliably. The code size of this patch and the > technical description needed is actually > > large enough for me to carry on.  If this is not negotiable, then it is > ok to provide DT support at the next version of > > this patch? it(dt support patch) tend to incurring more criticize. I'm > scared and not want to face it. As I said above, if the driver can already be used with the bridge chip, reduce it to the required code. The DT support can be submitted later on. In fact, you should try to reduce your initial driver to the minimum size that is useable. Try to remove any feature or functionality that is not critical to get something on the screen. The initial driver submission is the hardest. Once the driver has been merged, it is much easier to add new features and get them merged quickly. First-time submitters often think they have to provide a feature-complete driver. So they send huge patches that are hard to understand review. Those drivers then end up in an endless cycle of reviews. It's better to start small and grow the driver over time. Best regards Thomas > > We can leave the support for our SoC as internal, those SoC are really > target for low end application. > > keep them back is actually ok. > > >> Best regards >> Thomas >> >>> >>> and prepare next version. >>> >>>>> + >>>>> +   return pci_register_driver(&lsdc_pci_driver); >>>>> +} >>>>> +module_init(lsdc_module_init); >>>>> + >>>>> +static void __exit lsdc_module_exit(void) >>>>> +{ >>>>> +   pci_unregister_driver(&lsdc_pci_driver); >>>>> +} >>>>> +module_exit(lsdc_module_exit); >>>>> + >>>>> +MODULE_DEVICE_TABLE(pci, lsdc_pciid_list); >>>>> +MODULE_AUTHOR(DRIVER_AUTHOR); >>>>> +MODULE_DESCRIPTION(DRIVER_DESC); >>>>> +MODULE_LICENSE("GPL"); >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.h >>>>> b/drivers/gpu/drm/lsdc/lsdc_drv.h >>>>> new file mode 100644 >>>>> index 000000000000..d67dae0414ca >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.h >>>>> @@ -0,0 +1,274 @@ >>>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>>> +/* >>>>> + * KMS driver for Loongson display controller >>>>> + * Copyright (C) 2022 Loongson Corporation >>>>> + * >>>>> + * Authors: >>>>> + *     Li Yi <liyi@loongson.cn> >>>>> + *     Li Chenyang <lichenyang@loongson.cn> >>>>> + *     Sui Jingfeng <suijingfeng@loongson.cn> >>>>> + */ >>>>> + >>>>> +#ifndef __LSDC_DRV_H__ >>>>> +#define __LSDC_DRV_H__ >>>>> + >>>>> +#include <linux/i2c.h> >>>>> +#include <linux/i2c-algo-bit.h> >>>>> + >>>>> +#include <drm/drm_print.h> >>>>> +#include <drm/drm_device.h> >>>>> +#include <drm/drm_crtc.h> >>>>> +#include <drm/drm_plane.h> >>>>> +#include <drm/drm_connector.h> >>>>> +#include <drm/drm_encoder.h> >>>>> +#include <drm/drm_drv.h> >>>>> +#include <drm/drm_atomic.h> >>>>> + >>>>> +#include "lsdc_regs.h" >>>>> +#include "lsdc_pll.h" >>>>> + >>>>> +#define LSDC_NUM_CRTC          2 >>>>> + >>>>> +/* >>>>> + * The display controller in LS7A2000 integrate three loongson >>>>> self-made >>>>> + * encoder. Display pipe 0 has a transparent VGA encoder and a >>>>> HDMI phy, >>>>> + * they are parallel. Display pipe 1 has only one HDMI phy. >>>>> + *      ______________________ _____________ >>>>> + *     |            +-----+ | |            | >>>>> + *     | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA >>>>> Monitor |<---+ >>>>> + *     |       |   +-----+ | |_____________|   | >>>>> + *     |       |            | ______________   | >>>>> + *     |       |   +------+ | |             |  | >>>>> + *     |       +--> | HDMI | ----> HDMI Connector --> | HDMI >>>>> Monitor |<--+ >>>>> + *     |            +------+ | |______________|  | >>>>> + *     |           +------+ >>>>> |                                          | >>>>> + *     |           | i2c6 | >>>>> <-------------------------------------------+ >>>>> + *     |           +------+ | >>>>> + *     |                     | >>>>> + *     |   DC in LS7A2000   | >>>>> + *     |                     | >>>>> + *     |           +------+ | >>>>> + *     |           | i2c7 | <--------------------------------+ >>>>> + *     |           +------+ | | >>>>> + *     |                     | ______|_______ >>>>> + *     |           +------+ | |             | >>>>> + *     | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI >>>>> Monitor | >>>>> + *     |           +------+ | |______________| >>>>> + *     |______________________| >>>>> + * >>>>> + * >>>>> + * The display controller in LS7A1000 integrate two-way DVO, external >>>>> + * encoder is required except use directly with dpi(rgb888) panel. >>>>> + * ___________________ _________ >>>>> + *     |           -------| |        | >>>>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>>>> Display | >>>>> + *     | _  _    -------|       ^            ^ |_________| >>>>> + *     | | | | |   -------|       |            | >>>>> + *     | |_| |_|   | i2c6 <--------+-------------+ >>>>> + *     |           -------| >>>>> + *     | _  _    -------| >>>>> + *     | | | | |   | i2c7 <--------+-------------+ >>>>> + *     | |_| |_|   -------|       | | _________ >>>>> + *     |           -------|       |            | |        | >>>>> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | >>>>> Panel | >>>>> + *     |           -------| |_________| >>>>> + *     |___________________| >>>>> + * >>>>> + * >>>>> + * The display controller in LS2K1000. >>>>> + * ___________________ _________ >>>>> + *     |           -------| |        | >>>>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>>>> Display | >>>>> + *     | _  _    -------|       ^             ^ |_________| >>>>> + *     | | | | |          |       |             | >>>>> + *     | |_| |_|          |    +------+         | >>>>> + *     |                  <---->| i2c0 |<---------+ >>>>> + *     | DC in LS2K1000  |    +------+ >>>>> + *     | _  _           |    +------+ >>>>> + *     | | | | |          <---->| i2c1 |----------+ >>>>> + *     | |_| |_|          |    +------+ | _________ >>>>> + *     |           -------|       |             | |        | >>>>> + *     | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | >>>>> Panel | >>>>> + *     |           -------| |_________| >>>>> + *     |___________________| >>>>> + * >>>>> + * >>>>> + * The display controller in LS2K0500, LS2K0500 has a built-in >>>>> transparent >>>>> + * VGA encoder which is connected to display pipe 1(CRTC1). >>>>> + * ___________________ _________ >>>>> + *     |           -------| |        | >>>>> + *     | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | >>>>> Display | >>>>> + *     | _  _    -------|       ^             ^ |_________| >>>>> + *     | | | | |          |       |             | >>>>> + *     | |_| |_|          |    +------+         | >>>>> + *     |                  <---->| i2c4 |<---------+ >>>>> + *     | DC in LS2K0500  |    +------+ >>>>> + *     | _  _           |    +------+ >>>>> + *     | | | | |          <---->| i2c5 |-------------------+ >>>>> + *     | |_| |_|          |    +------+ ______|______ >>>>> + *     |          +-----+ | |            | >>>>> + *     | CRTC1 --> | VGA | ------------------------> | VGA Monitor | >>>>> + *     |          +-----+ | |_____________| >>>>> + *     |___________________| >>>>> + * >>>>> + * LS7A1000 and LS7A2000 are bridge chips, has dedicated Video RAM. >>>>> + * while LS2K2000/LS2K1000/LS2K0500 are SoC, they don't have >>>>> dediacated >>>>> + * Video RAM. The dc in LS2K2000 is basicly same with the dc in >>>>> LS7A1000 >>>>> + * except that LS2K2000 don't have a video RAM and have only one >>>>> built-in >>>>> + * hdmi encoder. >>>>> + * >>>>> + * There is only a 1:1 mapping of encoders and connectors for the DC, >>>>> + * each CRTC have two FB address registers. >>>>> + */ >>>>> + >>>>> +enum loongson_chip_family { >>>>> +   CHIP_UNKNOWN = 0, >>>>> +   CHIP_LS7A1000 = 1, /* North bridge of >>>>> LS3A3000/LS3A4000/LS3A5000 */ >>>>> +   CHIP_LS7A2000 = 2, /* Update version of LS7A1000, with >>>>> built-in HDMI encoder */ >>>>> +   CHIP_LS2K0500 = 3, /* Reduced version of LS2K1000, single >>>>> core */ >>>>> +   CHIP_LS2K1000 = 4, /* 2-Core Mips64r2/LA264 SoC, 64-bit, 1.0 >>>>> Ghz */ >>>>> +   CHIP_LS2K2000 = 5, /* 2-Core 64-bit LA364 SoC, 1.2 ~ 1.5 Ghz */ >>>>> +   CHIP_LAST, >>>>> +}; >>>>> + >>>>> +struct lsdc_desc { >>>>> +   enum loongson_chip_family chip; >>>>> +   u32 num_of_crtc; >>>>> +   u32 max_pixel_clk; >>>>> +   u32 max_width; >>>>> +   u32 max_height; >>>>> +   u32 num_of_hw_cursor; >>>>> +   u32 hw_cursor_w; >>>>> +   u32 hw_cursor_h; >>>>> +   u32 pitch_align; /* DMA alignment constraint */ >>>>> +   u64 mc_bits;     /* physical address bus bit width */ >>>>> +   bool has_vblank_counter; >>>>> +   bool has_builtin_i2c; >>>>> +   bool has_vram; >>>>> +   bool has_hpd_reg; >>>>> +   bool is_soc; >>>>> +}; >>>>> + >>>>> +struct lsdc_i2c { >>>>> +   struct i2c_adapter adapter; >>>>> +   struct i2c_algo_bit_data bit; >>>>> +   struct drm_device *ddev; >>>>> +   void __iomem *reg_base; >>>>> +   void __iomem *dir_reg; >>>>> +   void __iomem *dat_reg; >>>>> +   /* pin bit mask */ >>>>> +   u8 sda; >>>>> +   u8 scl; >>>>> +}; >>>>> + >>>>> +/* >>>>> + * struct lsdc_display_pipe - Abstraction of hardware display >>>>> pipeline. >>>>> + * @crtc: CRTC control structure >>>>> + * @primary: framebuffer plane control structure >>>>> + * @cursor: cursor plane control structure >>>>> + * @output: output control structure of this display pipe bind >>>>> + * @pixpll: pixel pll control structure >>>>> + * @index: the index corresponding to the hardware display pipe >>>>> + */ >>>>> +struct lsdc_display_pipe { >>>>> +   struct drm_crtc crtc; >>>>> +   struct drm_plane primary; >>>>> +   struct drm_plane cursor; >>>>> +   struct drm_encoder encoder; >>>>> +   struct drm_connector connector; >>>>> +   struct lsdc_pll pixpll; >>>>> +   struct lsdc_i2c *li2c; >>>>> +   int index; >>>>> +}; >>>>> + >>>>> +static inline struct lsdc_display_pipe * >>>>> +crtc_to_display_pipe(struct drm_crtc *crtc) >>>>> +{ >>>>> +   return container_of(crtc, struct lsdc_display_pipe, crtc); >>>>> +} >>>>> + >>>>> +static inline struct lsdc_display_pipe * >>>>> +cursor_to_display_pipe(struct drm_plane *cursor) >>>>> +{ >>>>> +   return container_of(cursor, struct lsdc_display_pipe, cursor); >>>>> +} >>>>> + >>>>> +static inline struct lsdc_display_pipe * >>>>> +connector_to_display_pipe(struct drm_connector *conn) >>>>> +{ >>>>> +   return container_of(conn, struct lsdc_display_pipe, connector); >>>>> +} >>>>> + >>>>> +static inline struct lsdc_display_pipe * >>>>> +encoder_to_display_pipe(struct drm_encoder *enc) >>>>> +{ >>>>> +   return container_of(enc, struct lsdc_display_pipe, encoder); >>>>> +} >>>>> + >>>>> +struct lsdc_crtc_state { >>>>> +   struct drm_crtc_state base; >>>>> +   struct lsdc_pll_parms pparms; >>>>> +}; >>>>> + >>>>> +struct lsdc_device { >>>>> +   struct drm_device base; >>>>> + >>>>> +   /* @reglock: protects concurrent register access */ >>>>> +   spinlock_t reglock; >>>>> +   void __iomem *reg_base; >>>>> +   void __iomem *vram; >>>>> +   resource_size_t vram_base; >>>>> +   resource_size_t vram_size; >>>>> +   u64 mc_mask; >>>>> + >>>>> +   struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC]; >>>>> + >>>>> +   /* @num_output: count the number of active display pipe */ >>>>> +   unsigned int num_output; >>>>> + >>>>> +   /* @descp: features description of the DC variant */ >>>>> +   const struct lsdc_desc *descp; >>>>> + >>>>> +   u32 irq_status; >>>>> +}; >>>>> + >>>>> +static inline struct lsdc_device * >>>>> +to_lsdc(struct drm_device *ddev) >>>>> +{ >>>>> +   return container_of(ddev, struct lsdc_device, base); >>>>> +} >>>>> + >>>>> +static inline struct lsdc_crtc_state * >>>>> +to_lsdc_crtc_state(struct drm_crtc_state *base) >>>>> +{ >>>>> +   return container_of(base, struct lsdc_crtc_state, base); >>>>> +} >>>>> + >>>>> +void lsdc_debugfs_init(struct drm_minor *minor); >>>>> + >>>>> +int lsdc_crtc_init(struct drm_device *ddev, >>>>> +          struct drm_crtc *crtc, >>>>> +          unsigned int index, >>>>> +          struct drm_plane *primary, >>>>> +          struct drm_plane *cursor); >>>>> + >>>>> +int lsdc_plane_init(struct lsdc_device *ldev, >>>>> +           struct drm_plane *plane, >>>>> +           enum drm_plane_type type, >>>>> +           unsigned int index); >>>>> + >>>>> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index); >>>>> + >>>>> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >>>>> +                     void *base, >>>>> +                     unsigned int index); >>>>> + >>>>> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >>>>> int index); >>>>> + >>>>> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg); >>>>> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev); >>>>> + >>>>> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset); >>>>> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 >>>>> val); >>>>> + >>>>> +#endif >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_i2c.c >>>>> new file mode 100644 >>>>> index 000000000000..b380098409ca >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c >>>>> @@ -0,0 +1,201 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include <linux/i2c.h> >>>>> +#include <drm/drm_managed.h> >>>>> +#include "lsdc_drv.h" >>>>> +#include "lsdc_regs.h" >>>>> + >>>>> +/* >>>>> + * ls7a_gpio_i2c_set - set the state of a gpio pin indicated by mask >>>>> + * @mask: gpio pin mask >>>>> + * @state: "0" for low, "1" for high >>>>> + */ >>>>> +static void ls7a_gpio_i2c_set(struct lsdc_i2c * const li2c, int >>>>> mask, int state) >>>>> +{ >>>>> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >>>>> +   unsigned long flags; >>>>> +   u8 val; >>>>> + >>>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>>> + >>>>> +   if (state) { >>>>> +       /* >>>>> +        * Setting this pin as input directly, write 1 for Input. >>>>> +        * The external pull-up resistor will pull the level up >>>>> +        */ >>>>> +       val = readb(li2c->dir_reg); >>>>> +       val |= mask; >>>>> +       writeb(val, li2c->dir_reg); >>>>> +   } else { >>>>> +       /* First set this pin as output, write 0 for Output */ >>>>> +       val = readb(li2c->dir_reg); >>>>> +       val &= ~mask; >>>>> +       writeb(val, li2c->dir_reg); >>>>> + >>>>> +       /* Then, make this pin output 0 */ >>>>> +       val = readb(li2c->dat_reg); >>>>> +       val &= ~mask; >>>>> +       writeb(val, li2c->dat_reg); >>>>> +   } >>>>> + >>>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>>> +} >>>>> + >>>>> +/* >>>>> + * ls7a_gpio_i2c_get - read value back from the gpio pin indicated >>>>> by mask >>>>> + * @mask: gpio pin mask >>>>> + * return "0" for low, "1" for high >>>>> + */ >>>>> +static int ls7a_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask) >>>>> +{ >>>>> +   struct lsdc_device *ldev = to_lsdc(li2c->ddev); >>>>> +   unsigned long flags; >>>>> +   u8 val; >>>>> + >>>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>>> + >>>>> +   /* First set this pin as input */ >>>>> +   val = readb(li2c->dir_reg); >>>>> +   val |= mask; >>>>> +   writeb(val, li2c->dir_reg); >>>>> + >>>>> +   /* Then get level state from this pin */ >>>>> +   val = readb(li2c->dat_reg); >>>>> + >>>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>>> + >>>>> +   return (val & mask) ? 1 : 0; >>>>> +} >>>>> + >>>>> +static void ls7a_i2c_set_sda(void *i2c, int state) >>>>> +{ >>>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>>> +   /* set state on the li2c->sda pin */ >>>>> +   return ls7a_gpio_i2c_set(li2c, li2c->sda, state); >>>>> +} >>>>> + >>>>> +static void ls7a_i2c_set_scl(void *i2c, int state) >>>>> +{ >>>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>>> +   /* set state on the li2c->scl pin */ >>>>> +   return ls7a_gpio_i2c_set(li2c, li2c->scl, state); >>>>> +} >>>>> + >>>>> +static int ls7a_i2c_get_sda(void *i2c) >>>>> +{ >>>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>>> +   /* read value from the li2c->sda pin */ >>>>> +   return ls7a_gpio_i2c_get(li2c, li2c->sda); >>>>> +} >>>>> + >>>>> +static int ls7a_i2c_get_scl(void *i2c) >>>>> +{ >>>>> +   struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; >>>>> +   /* read the value from the li2c->scl pin */ >>>>> +   return ls7a_gpio_i2c_get(li2c, li2c->scl); >>>>> +} >>>>> + >>>>> +static void lsdc_destroy_i2c(struct drm_device *ddev, void *data) >>>>> +{ >>>>> +   struct lsdc_i2c *li2c = (struct lsdc_i2c *)data; >>>>> + >>>>> +   if (li2c) { >>>>> +       i2c_del_adapter(&li2c->adapter); >>>>> +       kfree(li2c); >>>>> +   } >>>>> +} >>>>> + >>>>> +/* >>>>> + * The DC in ls7a1000/ls7a2000 have builtin gpio hardware >>>>> + * >>>>> + * @base: gpio reg base >>>>> + * @index: output channel index, 0 for DVO0, 1 for DVO1 >>>>> + */ >>>>> +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, >>>>> +                     void *base, >>>>> +                     unsigned int index) >>>>> +{ >>>>> +   struct i2c_adapter *adapter; >>>>> +   struct lsdc_i2c *li2c; >>>>> +   int ret; >>>>> + >>>>> +   li2c = kzalloc(sizeof(*li2c), GFP_KERNEL); >>>>> +   if (!li2c) >>>>> +       return ERR_PTR(-ENOMEM); >>>>> + >>>>> +   if (index == 0) { >>>>> +       li2c->sda = 0x01; /* pin 0 */ >>>>> +       li2c->scl = 0x02; /* pin 1 */ >>>>> +   } else if (index == 1) { >>>>> +       li2c->sda = 0x04; /* pin 2 */ >>>>> +       li2c->scl = 0x08; /* pin 3 */ >>>>> +   } >>>>> + >>>>> +   li2c->reg_base = base; >>>>> +   li2c->ddev = ddev; >>>>> +   li2c->dir_reg = li2c->reg_base + LS7A_DC_GPIO_DIR_REG; >>>>> +   li2c->dat_reg = li2c->reg_base + LS7A_DC_GPIO_DAT_REG; >>>>> + >>>>> +   li2c->bit.setsda = ls7a_i2c_set_sda; >>>>> +   li2c->bit.setscl = ls7a_i2c_set_scl; >>>>> +   li2c->bit.getsda = ls7a_i2c_get_sda; >>>>> +   li2c->bit.getscl = ls7a_i2c_get_scl; >>>>> +   li2c->bit.udelay = 5; >>>>> +   li2c->bit.timeout = usecs_to_jiffies(2200); >>>>> +   li2c->bit.data = li2c; >>>>> + >>>>> +   adapter = &li2c->adapter; >>>>> +   adapter->algo_data = &li2c->bit; >>>>> +   adapter->owner = THIS_MODULE; >>>>> +   adapter->class = I2C_CLASS_DDC; >>>>> +   adapter->dev.parent = ddev->dev; >>>>> +   adapter->nr = -1; >>>>> + >>>>> +   snprintf(adapter->name, sizeof(adapter->name), "lsdc-i2c%u", >>>>> index); >>>>> + >>>>> +   i2c_set_adapdata(adapter, li2c); >>>>> + >>>>> +   ret = i2c_bit_add_bus(adapter); >>>>> +   if (ret) { >>>>> +       kfree(li2c); >>>>> +       return ERR_PTR(ret); >>>>> +   } >>>>> + >>>>> +   ret = drmm_add_action_or_reset(ddev, lsdc_destroy_i2c, li2c); >>>>> +   if (ret) >>>>> +       return NULL; >>>>> + >>>>> +   drm_info(ddev, "%s(sda=%u, scl=%u) created for connector-%u\n", >>>>> +        adapter->name, li2c->sda, li2c->scl, index); >>>>> + >>>>> +   return li2c; >>>>> +} >>>>> + >>>>> +static int lsdc_get_i2c_id(struct lsdc_device *ldev, int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   if (descp->chip == CHIP_LS2K1000) >>>>> +       return index; >>>>> + >>>>> +   if (descp->chip == CHIP_LS2K0500) >>>>> +       return index + 2; >>>>> + >>>>> +   return index; >>>>> +} >>>>> + >>>>> +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, >>>>> +                    int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >>>>> + >>>>> +   if (descp->has_builtin_i2c) { >>>>> +       struct lsdc_i2c *li2c = dispipe->li2c; >>>>> + >>>>> +       if (li2c) >>>>> +           return &li2c->adapter; >>>>> +   } >>>>> + >>>>> +   return i2c_get_adapter(lsdc_get_i2c_id(ldev, index)); >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_irq.c >>>>> new file mode 100644 >>>>> index 000000000000..71c85f09bb6f >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_irq.c >>>>> @@ -0,0 +1,86 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include <drm/drm_vblank.h> >>>>> +#include "lsdc_drv.h" >>>>> +#include "lsdc_regs.h" >>>>> + >>>>> +/* >>>>> + * For the DC in ls7a2000, clearing interrupt status is achieved by >>>>> + * write "1" to LSDC_INT_REG, For the DC in ls7a1000, ls2k1000 and >>>>> + * ls2k0500, clearing interrupt status is achieved by write "0" to >>>>> + * LSDC_INT_REG. Two different hardware engineer of Loongson modify >>>>> + * it as their will. >>>>> + */ >>>>> + >>>>> +/* For the DC in ls7a2000 */ >>>>> +static irqreturn_t lsdc_irq_handler(int irq, void *arg) >>>>> +{ >>>>> +   struct drm_device *ddev = arg; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   u32 val; >>>>> + >>>>> +   /* Read the interrupt status */ >>>>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>>>> +   if ((val & INT_STATUS_MASK) == 0) { >>>>> +       drm_warn(ddev, "no interrupt occurs\n"); >>>>> +       return IRQ_NONE; >>>>> +   } >>>>> + >>>>> +   ldev->irq_status = val; >>>>> + >>>>> +   /* write "1" to clear the interrupt status */ >>>>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val); >>>>> + >>>>> +   return IRQ_WAKE_THREAD; >>>>> +} >>>>> + >>>>> +/* For the DC in ls7a1000, ls2k1000 and ls2k0500 */ >>>>> +static irqreturn_t lsdc_irq_handler_legacy(int irq, void *arg) >>>>> +{ >>>>> +   struct drm_device *ddev = arg; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   u32 val; >>>>> + >>>>> +   /* Read the interrupt status */ >>>>> +   val = lsdc_rreg32(ldev, LSDC_INT_REG); >>>>> +   if ((val & INT_STATUS_MASK) == 0) { >>>>> +       drm_warn(ddev, "no interrupt occurs\n"); >>>>> +       return IRQ_NONE; >>>>> +   } >>>>> + >>>>> +   ldev->irq_status = val; >>>>> + >>>>> +   /* write "0" to clear the interrupt status */ >>>>> +   lsdc_wreg32(ldev, LSDC_INT_REG, val & ~INT_STATUS_MASK); >>>>> + >>>>> +   return IRQ_WAKE_THREAD; >>>>> +} >>>>> + >>>>> +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A2000) >>>>> +       return lsdc_irq_handler; >>>>> + >>>>> +   return lsdc_irq_handler_legacy; >>>>> +} >>>>> + >>>>> +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg) >>>>> +{ >>>>> +   struct drm_device *ddev = arg; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   struct drm_crtc *crtc; >>>>> + >>>>> +   if (ldev->irq_status & INT_CRTC0_VSYNC) { >>>>> +       crtc = drm_crtc_from_index(ddev, 0); >>>>> +       drm_crtc_handle_vblank(crtc); >>>>> +   } >>>>> + >>>>> +   if (ldev->irq_status & INT_CRTC1_VSYNC) { >>>>> +       crtc = drm_crtc_from_index(ddev, 1); >>>>> +       drm_crtc_handle_vblank(crtc); >>>>> +   } >>>>> + >>>>> +   return IRQ_HANDLED; >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_output.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_output.c >>>>> new file mode 100644 >>>>> index 000000000000..d4bc7666d9ea >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_output.c >>>>> @@ -0,0 +1,452 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include <drm/drm_edid.h> >>>>> +#include <drm/drm_probe_helper.h> >>>>> +#include <drm/drm_atomic_helper.h> >>>>> +#include <drm/drm_connector.h> >>>>> +#include "lsdc_drv.h" >>>>> + >>>>> +static int lsdc_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 enum drm_connector_status >>>>> +lsdc_unknown_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; >>>>> +   } else { >>>>> +       if (connector->connector_type == DRM_MODE_CONNECTOR_DPI) >>>>> +           return connector_status_connected; >>>>> + >>>>> +       if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) >>>>> +           return connector_status_connected; >>>>> +   } >>>>> + >>>>> +   return connector_status_unknown; >>>>> +} >>>>> + >>>>> +static enum drm_connector_status >>>>> +lsdc_hdmi_connector_detect(struct drm_connector *connector, bool >>>>> force) >>>>> +{ >>>>> +   struct lsdc_display_pipe *pipe = >>>>> connector_to_display_pipe(connector); >>>>> +   struct lsdc_device *ldev = to_lsdc(connector->dev); >>>>> +   u32 val; >>>>> + >>>>> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >>>>> + >>>>> +   if (pipe->index == 0) { >>>>> +       if (val & HDMI0_HPD_FLAG) >>>>> +           return connector_status_connected; >>>>> +   } >>>>> + >>>>> +   if (pipe->index == 1) { >>>>> +       if (val & HDMI1_HPD_FLAG) >>>>> +           return connector_status_connected; >>>>> +   } >>>>> + >>>>> +   return connector_status_disconnected; >>>>> +} >>>>> + >>>>> +static enum drm_connector_status >>>>> +lsdc_hdmi_vga_connector_detect(struct drm_connector *connector, >>>>> bool force) >>>>> +{ >>>>> +   struct lsdc_display_pipe *pipe = >>>>> connector_to_display_pipe(connector); >>>>> +   struct drm_device *ddev = connector->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   struct i2c_adapter *ddc; >>>>> +   u32 val; >>>>> + >>>>> +   val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); >>>>> + >>>>> +   if (pipe->index == 1) { >>>>> +       if (val & HDMI1_HPD_FLAG) >>>>> +           return connector_status_connected; >>>>> + >>>>> +       return connector_status_disconnected; >>>>> +   } >>>>> + >>>>> +   if (pipe->index == 0) { >>>>> +       if (val & HDMI0_HPD_FLAG) >>>>> +           return connector_status_connected; >>>>> + >>>>> +       ddc = connector->ddc; >>>>> +       if (ddc) { >>>>> +           if (drm_probe_ddc(ddc)) >>>>> +               return connector_status_connected; >>>>> + >>>>> +           return connector_status_disconnected; >>>>> +       } >>>>> +   } >>>>> + >>>>> +   return connector_status_unknown; >>>>> +} >>>>> + >>>>> +static struct drm_encoder * >>>>> +lsdc_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 >>>>> lsdc_connector_helpers = { >>>>> +   .atomic_best_encoder = lsdc_connector_get_best_encoder, >>>>> +   .get_modes = lsdc_get_modes, >>>>> +}; >>>>> + >>>>> +static const struct drm_connector_funcs >>>>> lsdc_unknown_connector_funcs = { >>>>> +   .detect = lsdc_unknown_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 const struct drm_connector_funcs >>>>> lsdc_hdmi_vga_connector_funcs = { >>>>> +   .detect = lsdc_hdmi_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, >>>>> +}; >>>>> + >>>>> +static const struct drm_connector_funcs lsdc_hdmi_connector_funcs = { >>>>> +   .detect = lsdc_hdmi_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 const struct drm_connector_funcs * >>>>> +lsdc_get_connector_func(struct lsdc_device *ldev, unsigned int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A2000) { >>>>> +       if (index == 0) >>>>> +           return &lsdc_hdmi_vga_connector_funcs; >>>>> + >>>>> +       if (index == 1) >>>>> +           return &lsdc_hdmi_connector_funcs; >>>>> +   } >>>>> + >>>>> +   return &lsdc_unknown_connector_funcs; >>>>> +} >>>>> + >>>>> +/* >>>>> + * we provide a default support before DT/VBIOS is supported >>>>> + */ >>>>> +static int lsdc_get_encoder_type(struct lsdc_device *ldev, >>>>> +                unsigned int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A2000) { >>>>> +       if (index == 0) >>>>> +           return DRM_MODE_ENCODER_DAC; >>>>> +       if (index == 1) >>>>> +           return DRM_MODE_ENCODER_TMDS; >>>>> +   } >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>>>> CHIP_LS2K1000) { >>>>> +       if (index == 0) >>>>> +           return DRM_MODE_ENCODER_DPI; >>>>> +       if (index == 1) >>>>> +           return DRM_MODE_ENCODER_DPI; >>>>> +   } >>>>> + >>>>> +   if (descp->chip == CHIP_LS2K0500) { >>>>> +       if (index == 0) >>>>> +           return DRM_MODE_ENCODER_DPI; >>>>> +       if (index == 1) >>>>> +           return DRM_MODE_ENCODER_DAC; >>>>> +   } >>>>> + >>>>> +   return DRM_MODE_ENCODER_NONE; >>>>> +} >>>>> + >>>>> +/* >>>>> + * provide a default before DT/VBIOS support is upstreamed >>>>> + */ >>>>> +static int lsdc_get_connector_type(struct lsdc_device *ldev, >>>>> +                  unsigned int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A2000) { >>>>> +       if (index == 0) >>>>> +           return DRM_MODE_CONNECTOR_VGA; >>>>> +       if (index == 1) >>>>> +           return DRM_MODE_CONNECTOR_HDMIA; >>>>> +   } >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>>>> CHIP_LS2K1000) { >>>>> +       if (index == 0) >>>>> +           return DRM_MODE_CONNECTOR_DPI; >>>>> +       if (index == 1) >>>>> +           return DRM_MODE_CONNECTOR_DPI; >>>>> +   } >>>>> + >>>>> +   if (descp->chip == CHIP_LS2K0500) { >>>>> +       if (index == 0) >>>>> +           return DRM_MODE_CONNECTOR_DPI; >>>>> +       if (index == 1) >>>>> +           return DRM_MODE_CONNECTOR_VGA; >>>>> +   } >>>>> + >>>>> +   return DRM_MODE_CONNECTOR_Unknown; >>>>> +} >>>>> + >>>>> +static void lsdc_hdmi_disable(struct drm_encoder *encoder) >>>>> +{ >>>>> +   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; >>>>> + >>>>> +   if (index == 0) >>>>> +       val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG); >>>>> +   else if (index == 1) >>>>> +       val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG); >>>>> + >>>>> +   val &= ~HDMI_PHY_EN; >>>>> + >>>>> +   if (index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >>>>> +   else if (index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >>>>> + >>>>> +   drm_dbg(ddev, "HDMI-%u disabled\n", index); >>>>> +} >>>>> + >>>>> +static void lsdc_hdmi_enable(struct drm_encoder *encoder) >>>>> +{ >>>>> +   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; >>>>> + >>>>> +   /* we are using software gpio emulated i2c */ >>>>> +   val = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >>>>> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >>>>> + >>>>> +   if (index == 0) { >>>>> +       /* Enable HDMI-0 */ >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >>>>> + >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_ZONE_REG, 0x00400040); >>>>> +   } else if (index == 1) { >>>>> +       /* Enable HDMI-1 */ >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >>>>> + >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_ZONE_REG, 0x00400040); >>>>> +   } >>>>> + >>>>> +   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; >>>>> + >>>>> +   if (index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); >>>>> +   else if (index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); >>>>> + >>>>> +   drm_dbg(ddev, "HDMI-%u enabled\n", index); >>>>> +} >>>>> + >>>>> +/* >>>>> + * Fout = M * Fin >>>>> + * >>>>> + * M = (4 * LF) / (IDF * ODF) >>>>> + * >>>>> + * Loongson HDMI require M = 10 >>>>> + */ >>>>> +static void lsdc_hdmi_phy_pll_config(struct lsdc_device *ldev, >>>>> +                    int index, >>>>> +                    int clock) >>>>> +{ >>>>> +   struct drm_device *ddev = &ldev->base; >>>>> +   int count = 0; >>>>> +   u32 val; >>>>> + >>>>> +   /* disable phy pll */ >>>>> +   if (index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, 0x0); >>>>> +   else if (index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, 0x0); >>>>> + >>>>> +   /* >>>>> +    * 10 = (4 * 40) / (8 * 2) >>>>> +    */ >>>>> +   val = (8 << HDMI_PLL_IDF_SHIFT) | >>>>> +         (40 << HDMI_PLL_LF_SHIFT) | >>>>> +         (1 << HDMI_PLL_ODF_SHIFT) | >>>>> +         HDMI_PLL_ENABLE; >>>>> + >>>>> +   drm_dbg(ddev, "HDMI-%u clock: %d\n", index, clock); >>>>> + >>>>> +   if (index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, val); >>>>> +   else if (index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, val); >>>>> + >>>>> +   /* wait hdmi phy pll lock */ >>>>> +   do { >>>>> +       if (index == 0) >>>>> +           val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG); >>>>> +       else if (index == 1) >>>>> +           val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_PLL_REG); >>>>> + >>>>> +       ++count; >>>>> + >>>>> +       if (val & HDMI_PLL_LOCKED) { >>>>> +           drm_dbg(ddev, "Setting HDMI-%u PLL success(take %d >>>>> cycles)\n", >>>>> +               index, count); >>>>> +           break; >>>>> +       } >>>>> +   } while (count < 1000); >>>>> +} >>>>> + >>>>> +static void lsdc_hdmi_atomic_mode_set(struct drm_encoder *encoder, >>>>> +                     struct drm_crtc_state *crtc_state, >>>>> +                     struct drm_connector_state *conn_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; >>>>> +   struct drm_display_mode *mode = &crtc_state->mode; >>>>> + >>>>> +   lsdc_hdmi_phy_pll_config(ldev, index, mode->clock); >>>>> + >>>>> +   drm_dbg(ddev, "HDMI-%u modeset\n", index); >>>>> +} >>>>> + >>>>> +static const struct drm_encoder_helper_funcs >>>>> lsdc_hdmi_helper_funcs = { >>>>> +   .disable = lsdc_hdmi_disable, >>>>> +   .enable = lsdc_hdmi_enable, >>>>> +   .atomic_mode_set = lsdc_hdmi_atomic_mode_set, >>>>> +}; >>>>> + >>>>> +static void lsdc_hdmi_reset(struct drm_encoder *encoder) >>>>> +{ >>>>> +   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 = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | >>>>> +         HDMI_PACKET_EN | HDMI_INTERFACE_EN; >>>>> + >>>>> +   lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); >>>>> +   lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); >>>>> + >>>>> +   drm_dbg(ddev, "HDMI-%u Reset\n", index); >>>>> +} >>>>> + >>>>> +static const struct drm_encoder_funcs lsdc_encoder_funcs = { >>>>> +   .reset = lsdc_hdmi_reset, >>>>> +   .destroy = drm_encoder_cleanup, >>>>> +}; >>>>> + >>>>> +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> +   struct drm_device *ddev = &ldev->base; >>>>> +   struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; >>>>> +   struct drm_encoder *encoder = &dispipe->encoder; >>>>> +   struct drm_connector *connector = &dispipe->connector; >>>>> +   struct i2c_adapter *ddc = NULL; >>>>> +   struct lsdc_i2c *li2c; >>>>> +   int ret; >>>>> + >>>>> +   ret = drm_encoder_init(ddev, >>>>> +                  encoder, >>>>> +                  &lsdc_encoder_funcs, >>>>> +                  lsdc_get_encoder_type(ldev, index), >>>>> +                  "encoder-%u", >>>>> +                  index); >>>>> +   if (ret) { >>>>> +       drm_err(ddev, "Failed to init encoder: %d\n", ret); >>>>> +       return ret; >>>>> +   } >>>>> + >>>>> +   encoder->possible_crtcs = BIT(index); >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A2000) >>>>> +       drm_encoder_helper_add(encoder, &lsdc_hdmi_helper_funcs); >>>>> + >>>>> +   if (descp->has_builtin_i2c) { >>>>> +       li2c = lsdc_create_i2c_chan(ddev, ldev->reg_base, index); >>>>> +       if (IS_ERR(li2c)) >>>>> +           return PTR_ERR(li2c); >>>>> + >>>>> +       dispipe->li2c = li2c; >>>>> + >>>>> +       ddc = &li2c->adapter; >>>>> +   } else { >>>>> +       ddc = lsdc_get_i2c_adapter(ldev, index); >>>>> +       if (IS_ERR(ddc)) { >>>>> +           drm_err(ddev, "Error get ddc for output-%u\n", index); >>>>> +           return PTR_ERR(ddc); >>>>> +       } >>>>> +   } >>>>> + >>>>> +   ret = drm_connector_init_with_ddc(ddev, >>>>> +                     connector, >>>>> +                     lsdc_get_connector_func(ldev, index), >>>>> +                     lsdc_get_connector_type(ldev, index), >>>>> +                     ddc); >>>>> +   if (ret) { >>>>> +       drm_err(ddev, "Init connector-%d failed\n", index); >>>>> +       return ret; >>>>> +   } >>>>> + >>>>> +   drm_connector_helper_add(connector, &lsdc_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; >>>>> + >>>>> +   ldev->num_output++; >>>>> + >>>>> +   drm_info(ddev, "output-%u initialized\n", index); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_plane.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_plane.c >>>>> new file mode 100644 >>>>> index 000000000000..0f779c97d53b >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_plane.c >>>>> @@ -0,0 +1,443 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include <drm/drm_atomic.h> >>>>> +#include <drm/drm_atomic_helper.h> >>>>> +#include <drm/drm_framebuffer.h> >>>>> +#include <drm/drm_plane_helper.h> >>>>> +#include <drm/drm_gem_vram_helper.h> >>>>> +#include "lsdc_drv.h" >>>>> +#include "lsdc_regs.h" >>>>> +#include "lsdc_pll.h" >>>>> + >>>>> +static const u32 lsdc_primary_formats[] = { >>>>> +   DRM_FORMAT_XRGB8888, >>>>> +   DRM_FORMAT_ARGB8888, >>>>> +}; >>>>> + >>>>> +static const u32 lsdc_cursor_formats[] = { >>>>> +   DRM_FORMAT_ARGB8888, >>>>> +}; >>>>> + >>>>> +static const u64 lsdc_fb_format_modifiers[] = { >>>>> +   DRM_FORMAT_MOD_LINEAR, >>>>> +   DRM_FORMAT_MOD_INVALID >>>>> +}; >>>>> + >>>>> +static void lsdc_update_fb_format(struct lsdc_device *ldev, >>>>> +                 struct drm_crtc *crtc, >>>>> +                 const struct drm_format_info *fmt_info) >>>>> +{ >>>>> +   unsigned int index = drm_crtc_index(crtc); >>>>> +   u32 val; >>>>> +   u32 fmt; >>>>> + >>>>> +   switch (fmt_info->format) { >>>>> +   case DRM_FORMAT_XRGB8888: >>>>> +       fmt = LSDC_PF_XRGB8888; >>>>> +       break; >>>>> +   case DRM_FORMAT_ARGB8888: >>>>> +       fmt = LSDC_PF_XRGB8888; >>>>> +       break; >>>>> +   default: >>>>> +       fmt = LSDC_PF_XRGB8888; >>>>> +       break; >>>>> +   } >>>>> + >>>>> +   if (index == 0) { >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>>> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); >>>>> +   } else if (index == 1) { >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>>> +       val = (val & ~CFG_PIX_FMT_MASK) | fmt; >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); >>>>> +   } >>>>> +} >>>>> + >>>>> +static void lsdc_update_fb_start_addr(struct lsdc_device *ldev, >>>>> +                     struct drm_crtc *crtc, >>>>> +                     u64 paddr) >>>>> +{ >>>>> +   struct drm_device *ddev = &ldev->base; >>>>> +   unsigned int index = drm_crtc_index(crtc); >>>>> +   u32 lo_addr_reg; >>>>> +   u32 hi_addr_reg; >>>>> +   u32 val; >>>>> + >>>>> +   /* >>>>> +    * Find which framebuffer address register should update. >>>>> +    * if FB_ADDR0_REG is in using, we write the address to >>>>> FB_ADDR0_REG, >>>>> +    * if FB_ADDR1_REG is in using, we write the address to >>>>> FB_ADDR1_REG >>>>> +    * for each CRTC, the switch using one fb register to another is >>>>> +    * trigger by triggered by set CFG_PAGE_FLIP bit of >>>>> LSDC_CRTCx_CFG_REG >>>>> +    */ >>>>> +   if (index == 0) { >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); >>>>> +       if (val & CFG_FB_IN_USING) { >>>>> +           lo_addr_reg = LSDC_CRTC0_FB1_LO_ADDR_REG; >>>>> +           hi_addr_reg = LSDC_CRTC0_FB1_HI_ADDR_REG; >>>>> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-0\n"); >>>>> +       } else { >>>>> +           lo_addr_reg = LSDC_CRTC0_FB0_LO_ADDR_REG; >>>>> +           hi_addr_reg = LSDC_CRTC0_FB0_HI_ADDR_REG; >>>>> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-0\n"); >>>>> +       } >>>>> +   } else if (index == 1) { >>>>> +       val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); >>>>> +       if (val & CFG_FB_IN_USING) { >>>>> +           lo_addr_reg = LSDC_CRTC1_FB1_LO_ADDR_REG; >>>>> +           hi_addr_reg = LSDC_CRTC1_FB1_HI_ADDR_REG; >>>>> +           drm_dbg(ddev, "Currently, FB1 is in using by CRTC-1\n"); >>>>> +       } else { >>>>> +           lo_addr_reg = LSDC_CRTC1_FB0_LO_ADDR_REG; >>>>> +           hi_addr_reg = LSDC_CRTC1_FB0_HI_ADDR_REG; >>>>> +           drm_dbg(ddev, "Currently, FB0 is in using by CRTC-1\n"); >>>>> +       } >>>>> +   } >>>>> + >>>>> +   /* 40-bit width physical address bus */ >>>>> +   lsdc_wreg32(ldev, lo_addr_reg, paddr); >>>>> +   lsdc_wreg32(ldev, hi_addr_reg, (paddr >> 32) & 0xFF); >>>>> + >>>>> +   drm_dbg(ddev, "CRTC-%u scantout from 0x%llx\n", index, paddr); >>>>> +} >>>>> + >>>>> +static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb, >>>>> +                      struct drm_plane_state *state, >>>>> +                      unsigned int plane) >>>>> +{ >>>>> +   unsigned int offset = fb->offsets[plane]; >>>>> + >>>>> +   offset += fb->format->cpp[plane] * (state->src_x >> 16); >>>>> +   offset += fb->pitches[plane] * (state->src_y >> 16); >>>>> + >>>>> +   return offset; >>>>> +} >>>>> + >>>>> +static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb) >>>>> +{ >>>>> +   struct drm_gem_vram_object *gbo; >>>>> +   s64 gpu_addr; >>>>> + >>>>> +   gbo = drm_gem_vram_of_gem(fb->obj[0]); >>>>> +   gpu_addr = drm_gem_vram_offset(gbo); >>>>> + >>>>> +   return gpu_addr; >>>>> +} >>>>> + >>>>> +static int lsdc_primary_plane_atomic_check(struct drm_plane *plane, >>>>> +                      struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_plane_state *plane_state = >>>>> drm_atomic_get_new_plane_state(state, plane); >>>>> +   struct drm_crtc *crtc = plane_state->crtc; >>>>> +   struct drm_crtc_state *crtc_state = >>>>> drm_atomic_get_new_crtc_state(state, crtc); >>>>> + >>>>> +   if (!crtc) >>>>> +       return 0; >>>>> + >>>>> +   return drm_atomic_helper_check_plane_state(plane_state, >>>>> +                          crtc_state, >>>>> +                          DRM_PLANE_NO_SCALING, >>>>> +                          DRM_PLANE_NO_SCALING, >>>>> +                          false, >>>>> +                          true); >>>>> +} >>>>> + >>>>> +static void lsdc_update_fb_stride(struct lsdc_device *ldev, >>>>> +                 struct drm_crtc *crtc, >>>>> +                 unsigned int stride) >>>>> +{ >>>>> +   unsigned int index = drm_crtc_index(crtc); >>>>> + >>>>> +   if (index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC0_STRIDE_REG, stride); >>>>> +   else if (index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_CRTC1_STRIDE_REG, stride); >>>>> + >>>>> +   drm_dbg(crtc->dev, "update stride to %u\n", stride); >>>>> +} >>>>> + >>>>> +static void lsdc_primary_plane_atomic_update(struct drm_plane *plane, >>>>> +                        struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct lsdc_device *ldev = to_lsdc(plane->dev); >>>>> +   struct drm_plane_state *new_plane_state = >>>>> drm_atomic_get_new_plane_state(state, plane); >>>>> +   struct drm_crtc *crtc = new_plane_state->crtc; >>>>> +   struct drm_framebuffer *fb = new_plane_state->fb; >>>>> +   u32 fb_offset = lsdc_get_fb_offset(fb, new_plane_state, 0); >>>>> +   dma_addr_t fb_addr; >>>>> +   s64 gpu_addr; >>>>> + >>>>> +   gpu_addr = lsdc_get_vram_bo_offset(fb); >>>>> +   if (gpu_addr < 0) >>>>> +       return; >>>>> + >>>>> +   fb_addr = ldev->vram_base + gpu_addr + fb_offset; >>>>> + >>>>> +   lsdc_update_fb_start_addr(ldev, crtc, fb_addr); >>>>> + >>>>> +   lsdc_update_fb_stride(ldev, crtc, fb->pitches[0]); >>>>> + >>>>> +   lsdc_update_fb_format(ldev, crtc, fb->format); >>>>> +} >>>>> + >>>>> +static void lsdc_primary_plane_atomic_disable(struct drm_plane >>>>> *plane, >>>>> +                         struct drm_atomic_state *state) >>>>> +{ >>>>> +   drm_dbg(plane->dev, "%s disabled\n", plane->name); >>>>> +} >>>>> + >>>>> +static const struct drm_plane_helper_funcs >>>>> lsdc_primary_plane_helpers = { >>>>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>>>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>>>> +   .atomic_check = lsdc_primary_plane_atomic_check, >>>>> +   .atomic_update = lsdc_primary_plane_atomic_update, >>>>> +   .atomic_disable = lsdc_primary_plane_atomic_disable, >>>>> +}; >>>>> + >>>>> +static int lsdc_cursor_atomic_check(struct drm_plane *plane, >>>>> +                   struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_plane_state *new_plane_state = >>>>> drm_atomic_get_new_plane_state(state, plane); >>>>> +   struct drm_framebuffer *fb = new_plane_state->fb; >>>>> +   struct drm_crtc *crtc = new_plane_state->crtc; >>>>> +   struct drm_crtc_state *crtc_state; >>>>> +   int ret; >>>>> + >>>>> +   /* no need for further checks if the plane is being disabled */ >>>>> +   if (!crtc || !fb) >>>>> +       return 0; >>>>> + >>>>> +   if (!new_plane_state->visible) >>>>> +       return 0; >>>>> + >>>>> +   crtc_state = drm_atomic_get_new_crtc_state(state, >>>>> +                          new_plane_state->crtc); >>>>> + >>>>> +   ret = drm_atomic_helper_check_plane_state(new_plane_state, >>>>> +                         crtc_state, >>>>> +                         DRM_PLANE_NO_SCALING, >>>>> +                         DRM_PLANE_NO_SCALING, >>>>> +                         true, >>>>> +                         true); >>>>> + >>>>> +   return ret; >>>>> +} >>>>> + >>>>> +/* >>>>> + * There is only one hardware cursor in ls7a1000, ls2k1000 and >>>>> ls2k0500. >>>>> + * we made it shared by the two CRTC, which can satisfy peoples >>>>> who use >>>>> + * double screen extend mode only. On clone screen usage case, the >>>>> cursor >>>>> + * on display pipe 1 will not be able to display. >>>>> + * >>>>> + * Update location of the cursor, attach it to CRTC0 or CRTC1 on >>>>> the runtime. >>>>> + */ >>>>> +static void lsdc_cursor_update_location_quirks(struct lsdc_device >>>>> *ldev, >>>>> +                          struct drm_crtc *crtc) >>>>> +{ >>>>> +   u32 val = CURSOR_FORMAT_ARGB8888; >>>>> + >>>>> +   /* >>>>> +    * If bit 4 of LSDC_CURSOR0_CFG_REG is 1, then the cursor will be >>>>> +    * locate at CRTC-1, if bit 4 of LSDC_CURSOR0_CFG_REG is 0, then >>>>> +    * the cursor will be locate at CRTC-0. The cursor is alway on >>>>> the >>>>> +    * top of the primary. Compositing the primary plane and cursor >>>>> +    * plane is automatically done by hardware. >>>>> +    */ >>>>> +   if (drm_crtc_index(crtc)) >>>>> +       val |= CURSOR_LOCATION; >>>>> + >>>>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, val); >>>>> +} >>>>> + >>>>> +/* update the position of the cursor */ >>>>> +static void lsdc_cursor_update_position_quirks(struct lsdc_device >>>>> *ldev, >>>>> +                          int x, >>>>> +                          int y) >>>>> +{ >>>>> +   if (x < 0) >>>>> +       x = 0; >>>>> + >>>>> +   if (y < 0) >>>>> +       y = 0; >>>>> + >>>>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >>>>> +} >>>>> + >>>>> +static void lsdc_cursor_atomic_update_quirks(struct drm_plane *plane, >>>>> +                        struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_device *ddev = plane->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   struct drm_plane_state *new_plane_state = >>>>> drm_atomic_get_new_plane_state(state, plane); >>>>> +   struct drm_plane_state *old_plane_state = >>>>> drm_atomic_get_old_plane_state(state, plane); >>>>> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >>>>> +   struct drm_framebuffer *old_fb = old_plane_state->fb; >>>>> + >>>>> +   if (new_fb != old_fb) { >>>>> +       s64 offset = lsdc_get_vram_bo_offset(new_fb); >>>>> +       u64 cursor_addr = ldev->vram_base + offset; >>>>> + >>>>> +       drm_dbg_kms(ddev, "%s offset: %llx\n", plane->name, offset); >>>>> + >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >>>>> >> 32) & 0xFF); >>>>> +   } >>>>> + >>>>> +   lsdc_cursor_update_position_quirks(ldev, >>>>> new_plane_state->crtc_x, new_plane_state->crtc_y); >>>>> + >>>>> +   lsdc_cursor_update_location_quirks(ldev, new_plane_state->crtc); >>>>> +} >>>>> + >>>>> +/* update the format, size and location of the cursor */ >>>>> +static void lsdc_cursor_atomic_update(struct drm_plane *plane, >>>>> +                     struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_device *ddev = plane->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   struct lsdc_display_pipe *dispipe = >>>>> cursor_to_display_pipe(plane); >>>>> +   struct drm_plane_state *new_plane_state = >>>>> drm_atomic_get_new_plane_state(state, plane); >>>>> +   struct drm_framebuffer *new_fb = new_plane_state->fb; >>>>> +   int x = new_plane_state->crtc_x; >>>>> +   int y = new_plane_state->crtc_y; >>>>> +   u32 conf = CURSOR_FORMAT_ARGB8888 | CURSOR_SIZE_64X64; >>>>> +   u64 cursor_addr = ldev->vram_base + >>>>> lsdc_get_vram_bo_offset(new_fb); >>>>> + >>>>> +   if (x < 0) >>>>> +       x = 0; >>>>> + >>>>> +   if (y < 0) >>>>> +       y = 0; >>>>> + >>>>> +   if (dispipe->index == 0) { >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >>>>> >> 32) & 0xFF); >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); >>>>> +       /* Attach Cursor-0 to CRTC-0 */ >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, conf & >>>>> ~CURSOR_LOCATION); >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); >>>>> +       return; >>>>> +   } >>>>> + >>>>> +   if (dispipe->index == 1) { >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_HI_REG, (cursor_addr >>>>> >> 32) & 0xFF); >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_LO_REG, cursor_addr); >>>>> +       /* Attach Cursor-1 to CRTC-1 */ >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, conf | >>>>> CURSOR_LOCATION); >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_POSITION_REG, (y << 16) | x); >>>>> +       return; >>>>> +   } >>>>> +} >>>>> + >>>>> +static void lsdc_cursor_atomic_disable_quirks(struct drm_plane >>>>> *plane, >>>>> +                         struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_device *ddev = plane->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> + >>>>> +   /* Set the format to 0 actually display the cursor */ >>>>> +   lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >>>>> + >>>>> +   drm_dbg(ddev, "%s disabled\n", plane->name); >>>>> +} >>>>> + >>>>> +static void lsdc_cursor_atomic_disable(struct drm_plane *plane, >>>>> +                      struct drm_atomic_state *state) >>>>> +{ >>>>> +   struct drm_device *ddev = plane->dev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   struct lsdc_display_pipe *dispipe = >>>>> cursor_to_display_pipe(plane); >>>>> + >>>>> +   if (dispipe->index == 0) >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); >>>>> +   else if (dispipe->index == 1) >>>>> +       lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, 0); >>>>> + >>>>> +   drm_dbg(ddev, "%s disabled\n", plane->name); >>>>> +} >>>>> + >>>>> +static const struct drm_plane_helper_funcs >>>>> lsdc_cursor_helpers_quirk = { >>>>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>>>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>>>> +   .atomic_check = lsdc_cursor_atomic_check, >>>>> +   .atomic_update = lsdc_cursor_atomic_update_quirks, >>>>> +   .atomic_disable = lsdc_cursor_atomic_disable_quirks, >>>>> +}; >>>>> + >>>>> +static const struct drm_plane_helper_funcs >>>>> lsdc_cursor_plane_helpers = { >>>>> +   .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, >>>>> +   .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, >>>>> +   .atomic_check = lsdc_cursor_atomic_check, >>>>> +   .atomic_update = lsdc_cursor_atomic_update, >>>>> +   .atomic_disable = lsdc_cursor_atomic_disable, >>>>> +}; >>>>> + >>>>> +static const struct drm_plane_funcs lsdc_plane_funcs = { >>>>> +   .update_plane = drm_atomic_helper_update_plane, >>>>> +   .disable_plane = drm_atomic_helper_disable_plane, >>>>> +   .destroy = drm_plane_cleanup, >>>>> +   .reset = drm_atomic_helper_plane_reset, >>>>> +   .atomic_duplicate_state = >>>>> drm_atomic_helper_plane_duplicate_state, >>>>> +   .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, >>>>> +}; >>>>> + >>>>> +static const struct drm_plane_helper_funcs * >>>>> +lsdc_get_cursor_helper_funcs(const struct lsdc_desc *descp) >>>>> +{ >>>>> +   if (descp->chip == CHIP_LS7A2000) >>>>> +       return &lsdc_cursor_plane_helpers; >>>>> + >>>>> +   return &lsdc_cursor_helpers_quirk; >>>>> +} >>>>> + >>>>> +int lsdc_plane_init(struct lsdc_device *ldev, >>>>> +           struct drm_plane *plane, >>>>> +           enum drm_plane_type type, >>>>> +           unsigned int index) >>>>> +{ >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> +   struct drm_device *ddev = &ldev->base; >>>>> +   unsigned int format_count; >>>>> +   const u32 *formats; >>>>> +   const char *name; >>>>> +   int ret; >>>>> + >>>>> +   switch (type) { >>>>> +   case DRM_PLANE_TYPE_PRIMARY: >>>>> +       formats = lsdc_primary_formats; >>>>> +       format_count = ARRAY_SIZE(lsdc_primary_formats); >>>>> +       name = "primary-%u"; >>>>> +       break; >>>>> +   case DRM_PLANE_TYPE_CURSOR: >>>>> +       formats = lsdc_cursor_formats; >>>>> +       format_count = ARRAY_SIZE(lsdc_cursor_formats); >>>>> +       name = "cursor-%u"; >>>>> +       break; >>>>> +   case DRM_PLANE_TYPE_OVERLAY: >>>>> +       drm_err(ddev, "overlay plane is not supported\n"); >>>>> +       break; >>>>> +   } >>>>> + >>>>> +   ret = drm_universal_plane_init(ddev, plane, 1 << index, >>>>> +                      &lsdc_plane_funcs, >>>>> +                      formats, format_count, >>>>> +                      lsdc_fb_format_modifiers, >>>>> +                      type, name, index); >>>>> +   if (ret) { >>>>> +       drm_err(ddev, "%s failed: %d\n", __func__, ret); >>>>> +       return ret; >>>>> +   } >>>>> + >>>>> +   switch (type) { >>>>> +   case DRM_PLANE_TYPE_PRIMARY: >>>>> +       drm_plane_helper_add(plane, &lsdc_primary_plane_helpers); >>>>> +       break; >>>>> +   case DRM_PLANE_TYPE_CURSOR: >>>>> +       drm_plane_helper_add(plane, >>>>> lsdc_get_cursor_helper_funcs(descp)); >>>>> +       break; >>>>> +   case DRM_PLANE_TYPE_OVERLAY: >>>>> +       drm_err(ddev, "overlay plane is not supported\n"); >>>>> +       break; >>>>> +   } >>>>> + >>>>> +   return 0; >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_pll.c >>>>> new file mode 100644 >>>>> index 000000000000..6ed74989a6f5 >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.c >>>>> @@ -0,0 +1,569 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include "lsdc_drv.h" >>>>> +#include "lsdc_regs.h" >>>>> +#include "lsdc_pll.h" >>>>> + >>>>> +/* >>>>> + * The structure of the pixel PLL register is evolved with times. >>>>> + * All loongson's cpu is little endian. >>>>> + */ >>>>> + >>>>> +/* u64 */ >>>>> +struct ls7a1000_pixpll_bitmap { >>>>> +   /* Byte 0 ~ Byte 3 */ >>>>> +   unsigned div_out     : 7;  /* 0 : 6    output clock >>>>> divider */ >>>>> +   unsigned __1         : 14; /* 7 : >>>>> 20                         */ >>>>> +   unsigned loopc       : 9;  /* 21 : 29   clock >>>>> multiplier     */ >>>>> +   unsigned __2         : 2;  /* 30 : >>>>> 31                         */ >>>>> + >>>>> +   /* Byte 4 ~ Byte 7 */ >>>>> +   unsigned div_ref     : 7;  /* 0 : 6    input clock >>>>> divider  */ >>>>> +   unsigned locked      : 1;  /* 7        PLL locked >>>>> status    */ >>>>> +   unsigned sel_out     : 1;  /* 8        output clk >>>>> selector  */ >>>>> +   unsigned __3         : 2;  /* 9 : 10 reserved             */ >>>>> +   unsigned set_param   : 1;  /* 11       trigger the >>>>> update   */ >>>>> +   unsigned bypass      : 1;  /* >>>>> 12                             */ >>>>> +   unsigned powerdown   : 1;  /* >>>>> 13                             */ >>>>> +   unsigned __4         : 18; /* 14 : >>>>> 31                        */ >>>>> +}; >>>>> + >>>>> +/* u128 */ >>>>> +struct ls2k1000_pixpll_bitmap { >>>>> +   /* Byte 0 ~ Byte 3 */ >>>>> +   unsigned sel_out     : 1; /* 0     select this >>>>> PLL         */ >>>>> +   unsigned __1         : 1; /* >>>>> 1                              */ >>>>> +   unsigned sw_adj_en   : 1; /* 2     allow software >>>>> adjust   */ >>>>> +   unsigned bypass      : 1; /* 3     bypass L1 >>>>> PLL           */ >>>>> +   unsigned __2         : 3; /* >>>>> 4:6                            */ >>>>> +   unsigned lock_en     : 1; /* 7     enable lock L1 >>>>> PLL      */ >>>>> +   unsigned __3         : 2; /* >>>>> 8:9                            */ >>>>> +   unsigned lock_check  : 2; /* 10:11  precision >>>>> check         */ >>>>> +   unsigned __4         : 4; /* >>>>> 12:15                           */ >>>>> + >>>>> +   unsigned locked      : 1; /* 16     PLL locked flag >>>>> bit     */ >>>>> +   unsigned __5         : 2; /* >>>>> 17:18                           */ >>>>> +   unsigned powerdown   : 1; /* 19     powerdown the pll if >>>>> set */ >>>>> +   unsigned __6         : 6; /* >>>>> 20:25                           */ >>>>> +   unsigned div_ref     : 6; /* 26:31  L1 >>>>> Prescaler            */ >>>>> + >>>>> +   /* Byte 4 ~ Byte 7 */ >>>>> +   unsigned loopc       : 10; /* 32:41  Clock >>>>> Multiplier        */ >>>>> +   unsigned l1_div      : 6; /* 42:47  not >>>>> used                */ >>>>> +   unsigned __7         : 16; /* >>>>> 48:63                           */ >>>>> + >>>>> +   /* Byte 8 ~ Byte 15 */ >>>>> +   unsigned div_out     : 6; /* 0 : 5  output clock >>>>> divider    */ >>>>> +   unsigned __8         : 26; /* 6 : >>>>> 31                          */ >>>>> +   unsigned __9         : 32; /* 70: >>>>> 127                         */ >>>>> +}; >>>>> + >>>>> +/* u32 */ >>>>> +struct ls2k0500_pixpll_bitmap { >>>>> +   /* Byte 0 ~ Byte 1 */ >>>>> +   unsigned sel_out     : 1; >>>>> +   unsigned __1         : 2; >>>>> +   unsigned sw_adj_en   : 1;  /* allow software >>>>> adjust             */ >>>>> +   unsigned bypass      : 1;  /* bypass L1 >>>>> PLL                     */ >>>>> +   unsigned powerdown   : 1;  /* write 1 to powerdown the >>>>> PLL      */ >>>>> +   unsigned __2         : 1; >>>>> +   unsigned locked      : 1;  /* 7    Is L1 PLL locked, read >>>>> only */ >>>>> +   unsigned div_ref     : 6;  /* 8:13 ref clock >>>>> divider          */ >>>>> +   unsigned __3         : 2;  /* >>>>> 14:15                             */ >>>>> +   /* Byte 2 ~ Byte 3 */ >>>>> +   unsigned loopc       : 8;  /* 16:23  Clock >>>>> Multiplier          */ >>>>> +   unsigned div_out     : 6;  /* 24:29  output clock >>>>> divider      */ >>>>> +   unsigned __4         : 2;  /* >>>>> 30:31                             */ >>>>> +}; >>>>> + >>>>> +union lsdc_pixpll_bitmap { >>>>> +   struct ls7a1000_pixpll_bitmap ls7a1000; >>>>> +   struct ls2k1000_pixpll_bitmap ls2k1000; >>>>> +   struct ls2k0500_pixpll_bitmap ls2k0500; >>>>> + >>>>> +   u32 dword[4]; >>>>> +}; >>>>> + >>>>> +struct pixclk_to_pll_parm { >>>>> +   /* kHz */ >>>>> +   unsigned int clock; >>>>> + >>>>> +   unsigned short width; >>>>> +   unsigned short height; >>>>> +   unsigned short vrefresh; >>>>> + >>>>> +   /* Stores parameters for programming the Hardware PLLs */ >>>>> +   unsigned short div_out; >>>>> +   unsigned short loopc; >>>>> +   unsigned short div_ref; >>>>> +}; >>>>> + >>>>> +/* >>>>> + * Pixel clock to PLL parameters translation table. >>>>> + * Small static cached value to speed up PLL parameters calculation. >>>>> + */ >>>>> +static const struct pixclk_to_pll_parm pll_param_table[] = { >>>>> +   {148500, 1920, 1080, 60, 11, 49, 3},  /* 1920x1080@60Hz */ >>>>> +                       /* 1920x1080@50Hz */ >>>>> +   {174500, 1920, 1080, 75, 17, 89, 3},  /* 1920x1080@75Hz */ >>>>> +   {181250, 2560, 1080, 75, 8, 58, 4},  /* 2560x1080@75Hz */ >>>>> +   {146250, 1680, 1050, 60, 16, 117, 5},  /* 1680x1050@60Hz */ >>>>> +   {135000, 1280, 1024, 75, 10, 54, 4},  /* 1280x1024@75Hz */ >>>>> + >>>>> +   {108000, 1600, 900, 60, 15, 81, 5},  /* 1600x900@60Hz */ >>>>> +                       /* 1280x1024@60Hz */ >>>>> +                       /* 1280x960@60Hz */ >>>>> +                       /* 1152x864@75Hz */ >>>>> + >>>>> +   {106500, 1440, 900, 60, 19, 81, 4},  /* 1440x900@60Hz */ >>>>> +   {88750, 1440, 900, 60, 16, 71, 5},  /* 1440x900@60Hz */ >>>>> +   {83500, 1280, 800, 60, 17, 71, 5},  /* 1280x800@60Hz */ >>>>> +   {71000, 1280, 800, 60, 20, 71, 5},  /* 1280x800@60Hz */ >>>>> + >>>>> +   {74250, 1280, 720, 60, 22, 49, 3},  /* 1280x720@60Hz */ >>>>> +                       /* 1280x720@50Hz */ >>>>> + >>>>> +   {78750, 1024, 768, 75, 16, 63, 5},  /* 1024x768@75Hz */ >>>>> +   {75000, 1024, 768, 70, 29, 87, 4},  /* 1024x768@70Hz */ >>>>> +   {65000, 1024, 768, 60, 20, 39, 3},  /* 1024x768@60Hz */ >>>>> + >>>>> +   {51200, 1024, 600, 60, 25, 64, 5},  /* 1024x600@60Hz */ >>>>> + >>>>> +   {57284, 832, 624, 75, 24, 55, 4},  /* 832x624@75Hz */ >>>>> +   {49500, 800, 600, 75, 40, 99, 5},  /* 800x600@75Hz */ >>>>> +   {50000, 800, 600, 72, 44, 88, 4},  /* 800x600@72Hz */ >>>>> +   {40000, 800, 600, 60, 30, 36, 3},  /* 800x600@60Hz */ >>>>> +   {36000, 800, 600, 56, 50, 72, 4},  /* 800x600@56Hz */ >>>>> +   {31500, 640, 480, 75, 40, 63, 5},  /* 640x480@75Hz */ >>>>> +                       /* 640x480@73Hz */ >>>>> + >>>>> +   {30240, 640, 480, 67, 62, 75, 4},  /* 640x480@67Hz */ >>>>> +   {27000, 720, 576, 50, 50, 54, 4},  /* 720x576@60Hz */ >>>>> +   {25175, 640, 480, 60, 85, 107, 5},  /* 640x480@60Hz */ >>>>> +   {25200, 640, 480, 60, 50, 63, 5},  /* 640x480@60Hz */ >>>>> +                       /* 720x480@60Hz */ >>>>> +}; >>>>> + >>>>> +/* >>>>> + * lsdc_pixpll_setup - ioremap the device dependent PLL registers >>>>> + * >>>>> + * @this: point to the object where this function is called from >>>>> + */ >>>>> +static int lsdc_pixpll_setup(struct lsdc_pll * const this) >>>>> +{ >>>>> +   this->mmio = ioremap(this->reg_base, this->reg_size); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +/* >>>>> + * Find a set of pll parameters (to generate pixel clock) from a >>>>> static >>>>> + * local table, which avoid to compute the pll parameter eachtime a >>>>> + * modeset is triggered. >>>>> + * >>>>> + * @this: point to the object which this function is called from >>>>> + * @clock: the desired output pixel clock, the unit is kHz >>>>> + * @pout: point to where the parameters to store if found >>>>> + * >>>>> + * Return true if hit, otherwise return false. >>>>> + */ >>>>> +static bool lsdc_pixpll_find(struct lsdc_pll * const this, >>>>> +                unsigned int clock, >>>>> +                struct lsdc_pll_parms * const pout) >>>>> +{ >>>>> +   unsigned int num = ARRAY_SIZE(pll_param_table); >>>>> +   unsigned int i; >>>>> + >>>>> +   for (i = 0; i < num; i++) { >>>>> +       if (clock != pll_param_table[i].clock) >>>>> +           continue; >>>>> + >>>>> +       pout->div_ref = pll_param_table[i].div_ref; >>>>> +       pout->loopc  = pll_param_table[i].loopc; >>>>> +       pout->div_out = pll_param_table[i].div_out; >>>>> + >>>>> +       return true; >>>>> +   } >>>>> + >>>>> +   drm_dbg(this->ddev, "pixel clock %u: miss\n", clock); >>>>> + >>>>> +   return false; >>>>> +} >>>>> + >>>>> +/* >>>>> + * Find a set of pll parameters which have minimal difference with >>>>> the desired >>>>> + * pixel clock frequency. It does that by computing all of the >>>>> possible >>>>> + * combination. Compute the diff and find the combination with >>>>> minimal diff. >>>>> + * >>>>> + * clock_out = refclk / div_ref * loopc / div_out >>>>> + * >>>>> + * refclk is determined by the oscillator mounted board(Here is >>>>> 100MHz in >>>>> + * almost all case) >>>>> + * >>>>> + * @this: point to the object from which this function is called >>>>> + * @clock_khz: the desired output pixel clock, the unit is kHz >>>>> + * @pout: point to the out struct of lsdc_pll_parms >>>>> + * >>>>> + * Return true if a parameter is found, otherwise return false >>>>> + */ >>>>> +static bool lsdc_pixpll_compute(struct lsdc_pll * const this, >>>>> +               unsigned int clock_khz, >>>>> +               struct lsdc_pll_parms *pout) >>>>> +{ >>>>> +   unsigned int refclk = this->ref_clock; >>>>> +   const unsigned int tolerance = 1000; >>>>> +   unsigned int min = tolerance; >>>>> +   unsigned int div_out, loopc, div_ref; >>>>> +   unsigned int computed; >>>>> + >>>>> +   if (lsdc_pixpll_find(this, clock_khz, pout)) >>>>> +       return true; >>>>> + >>>>> +   for (div_out = 6; div_out < 64; div_out++) { >>>>> +       for (div_ref = 3; div_ref < 6; div_ref++) { >>>>> +           for (loopc = 6; loopc < 161; loopc++) { >>>>> +               int diff; >>>>> + >>>>> +               if (loopc < 12 * div_ref) >>>>> +                   continue; >>>>> +               if (loopc > 32 * div_ref) >>>>> +                   continue; >>>>> + >>>>> +               computed = refclk * loopc / div_ref / div_out; >>>>> + >>>>> +               if (clock_khz > computed) >>>>> +                   diff = clock_khz - computed; >>>>> +               else if (clock_khz < computed) >>>>> +                   diff = computed - clock_khz; >>>>> + >>>>> +               if (diff < min) { >>>>> +                   min = diff; >>>>> +                   pout->div_ref = div_ref; >>>>> +                   pout->div_out = div_out; >>>>> +                   pout->loopc = loopc; >>>>> + >>>>> +                   if (diff == 0) >>>>> +                       return true; >>>>> +               } >>>>> +           } >>>>> +       } >>>>> +   } >>>>> + >>>>> +   return min < tolerance; >>>>> +} >>>>> + >>>>> +/* >>>>> + * Update the pll parameters to hardware, target to the pixpll in >>>>> ls7a1000 >>>>> + * >>>>> + * @this: point to the object from which this function is called >>>>> + * @pin: point to the struct of lsdc_pll_parms passed in >>>>> + * >>>>> + * return 0 if successful. >>>>> + */ >>>>> +static int ls7a1000_pixpll_param_update(struct lsdc_pll * const this, >>>>> +                   struct lsdc_pll_parms const *pin) >>>>> +{ >>>>> +   void __iomem *reg = this->mmio; >>>>> +   unsigned int counter = 0; >>>>> +   bool locked; >>>>> +   u32 val; >>>>> + >>>>> +   /* Bypass the software configured PLL, using refclk directly */ >>>>> +   val = readl(reg + 0x4); >>>>> +   val &= ~(1 << 8); >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   /* Powerdown the PLL */ >>>>> +   val = readl(reg + 0x4); >>>>> +   val |= (1 << 13); >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   /* Clear the pll parameters */ >>>>> +   val = readl(reg + 0x4); >>>>> +   val &= ~(1 << 11); >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   /* clear old value & config new value */ >>>>> +   val = readl(reg + 0x04); >>>>> +   val &= ~0x7F; >>>>> +   val |= pin->div_ref;       /* div_ref */ >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   val = readl(reg); >>>>> +   val &= ~0x7f; >>>>> +   val |= pin->div_out;       /* div_out */ >>>>> + >>>>> +   val &= ~(0x1ff << 21); >>>>> +   val |= pin->loopc << 21;   /* loopc */ >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Set the pll the parameters */ >>>>> +   val = readl(reg + 0x4); >>>>> +   val |= (1 << 11); >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   /* Powerup the PLL */ >>>>> +   val = readl(reg + 0x4); >>>>> +   val &= ~(1 << 13); >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   /* Wait the PLL lock */ >>>>> +   do { >>>>> +       val = readl(reg + 0x4); >>>>> +       locked = val & 0x80; >>>>> +       counter++; >>>>> +   } while (!locked && (counter < 2000)); >>>>> + >>>>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>>>> + >>>>> +   /* Switch to the software configured pll */ >>>>> +   val = readl(reg + 0x4); >>>>> +   val |= (1UL << 8); >>>>> +   writel(val, reg + 0x4); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +/* >>>>> + * Update the pll parameters to hardware, target to the pixpll in >>>>> ls2k1000 >>>>> + * >>>>> + * @this: point to the object from which this function is called >>>>> + * @pin: point to the struct of lsdc_pll_parms passed in >>>>> + * >>>>> + * return 0 if successful. >>>>> + */ >>>>> +static int ls2k1000_pixpll_param_update(struct lsdc_pll * const this, >>>>> +                   struct lsdc_pll_parms const *pin) >>>>> +{ >>>>> +   void __iomem *reg = this->mmio; >>>>> +   unsigned int counter = 0; >>>>> +   bool locked = false; >>>>> +   u32 val; >>>>> + >>>>> +   val = readl(reg); >>>>> +   /* Bypass the software configured PLL, using refclk directly */ >>>>> +   val &= ~(1 << 0); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Powerdown the PLL */ >>>>> +   val |= (1 << 19); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Allow the software configuration */ >>>>> +   val &= ~(1 << 2); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* allow L1 PLL lock */ >>>>> +   val = (1L << 7) | (3L << 10); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* clear div_ref bit field */ >>>>> +   val &= ~(0x3f << 26); >>>>> +   /* set div_ref bit field */ >>>>> +   val |= pin->div_ref << 26; >>>>> +   writel(val, reg); >>>>> + >>>>> +   val = readl(reg + 4); >>>>> +   /* clear loopc bit field */ >>>>> +   val &= ~0x0fff; >>>>> +   /* set loopc bit field */ >>>>> +   val |= pin->loopc; >>>>> +   writel(val, reg + 4); >>>>> + >>>>> +   /* set div_out */ >>>>> +   writel(pin->div_out, reg + 8); >>>>> + >>>>> +   val = readl(reg); >>>>> +   /* use this parms configured just now */ >>>>> +   val |= (1 << 2); >>>>> +   /* powerup the PLL */ >>>>> +   val &= ~(1 << 19); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* wait pll setup and locked */ >>>>> +   do { >>>>> +       val = readl(reg); >>>>> +       locked = val & 0x10000; >>>>> +       counter++; >>>>> +   } while (!locked && (counter < 2000)); >>>>> + >>>>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>>>> + >>>>> +   /* Switch to software configured PLL instead of refclk */ >>>>> +   val |= 1; >>>>> +   writel(val, reg); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +/* >>>>> + * Update the pll parameters to hardware, target to the pixpll in >>>>> ls2k0500 >>>>> + * >>>>> + * @this: point to the object which calling this function >>>>> + * @param: pointer to where the parameters passed in >>>>> + * >>>>> + * return 0 if successful. >>>>> + */ >>>>> +static int ls2k0500_pixpll_param_update(struct lsdc_pll * const this, >>>>> +                   struct lsdc_pll_parms const *param) >>>>> +{ >>>>> +   void __iomem *reg = this->mmio; >>>>> +   unsigned int counter = 0; >>>>> +   bool locked = false; >>>>> +   u32 val; >>>>> + >>>>> +   /* Bypass the software configured PLL, using refclk directly */ >>>>> +   val = readl(reg); >>>>> +   val &= ~(1 << 0); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Powerdown the PLL */ >>>>> +   val = readl(reg); >>>>> +   val |= (1 << 5); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Allow the software configuration */ >>>>> +   val |= (1 << 3); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Update the pll params */ >>>>> +   val = (param->div_out << 24) | >>>>> +         (param->loopc << 16) | >>>>> +         (param->div_ref << 8); >>>>> + >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* Powerup the PLL */ >>>>> +   val = readl(reg); >>>>> +   val &= ~(1 << 5); >>>>> +   writel(val, reg); >>>>> + >>>>> +   /* wait pll setup and locked */ >>>>> +   do { >>>>> +       val = readl(reg); >>>>> +       locked = val & 0x80; >>>>> +       counter++; >>>>> +   } while (!locked && (counter < 10000)); >>>>> + >>>>> +   drm_dbg(this->ddev, "%u loop waited\n", counter); >>>>> + >>>>> +   /* Switch to software configured PLL instead of refclk */ >>>>> +   writel((val | 1), reg); >>>>> + >>>>> +   return 0; >>>>> +} >>>>> + >>>>> +static unsigned int lsdc_get_clock_rate(struct lsdc_pll * const this, >>>>> +                   struct lsdc_pll_parms *pout) >>>>> +{ >>>>> +   struct drm_device *ddev = this->ddev; >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   const struct lsdc_desc * const descp = ldev->descp; >>>>> +   unsigned int out; >>>>> +   union lsdc_pixpll_bitmap parms; >>>>> + >>>>> +   if (descp->chip == CHIP_LS7A1000 || descp->chip == >>>>> CHIP_LS7A2000) { >>>>> +       struct ls7a1000_pixpll_bitmap *obj = &parms.ls7a1000; >>>>> + >>>>> +       parms.dword[0] = readl(this->mmio); >>>>> +       parms.dword[1] = readl(this->mmio + 4); >>>>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>>>> obj->div_out; >>>>> +       if (pout) { >>>>> +           pout->div_ref = obj->div_ref; >>>>> +           pout->loopc = obj->loopc; >>>>> +           pout->div_out = obj->div_out; >>>>> +       } >>>>> +   } else if (descp->chip == CHIP_LS2K1000) { >>>>> +       struct ls2k1000_pixpll_bitmap *obj = &parms.ls2k1000; >>>>> + >>>>> +       parms.dword[0] = readl(this->mmio); >>>>> +       parms.dword[1] = readl(this->mmio + 4); >>>>> +       parms.dword[2] = readl(this->mmio + 8); >>>>> +       parms.dword[3] = readl(this->mmio + 12); >>>>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>>>> obj->div_out; >>>>> +       if (pout) { >>>>> +           pout->div_ref = obj->div_ref; >>>>> +           pout->loopc = obj->loopc; >>>>> +           pout->div_out = obj->div_out; >>>>> +       } >>>>> +   } else if (descp->chip == CHIP_LS2K0500) { >>>>> +       struct ls2k0500_pixpll_bitmap *obj = &parms.ls2k0500; >>>>> + >>>>> +       parms.dword[0] = readl(this->mmio); >>>>> +       out = this->ref_clock / obj->div_ref * obj->loopc / >>>>> obj->div_out; >>>>> +       if (pout) { >>>>> +           pout->div_ref = obj->div_ref; >>>>> +           pout->loopc = obj->loopc; >>>>> +           pout->div_out = obj->div_out; >>>>> +       } >>>>> +   } else { >>>>> +       drm_err(ddev, "unknown chip, the driver need update\n"); >>>>> +       return 0; >>>>> +   } >>>>> + >>>>> +   return out; >>>>> +} >>>>> + >>>>> +static const struct lsdc_pixpll_funcs ls7a1000_pixpll_funcs = { >>>>> +   .setup = lsdc_pixpll_setup, >>>>> +   .compute = lsdc_pixpll_compute, >>>>> +   .update = ls7a1000_pixpll_param_update, >>>>> +   .get_clock_rate = lsdc_get_clock_rate, >>>>> +}; >>>>> + >>>>> +static const struct lsdc_pixpll_funcs ls2k1000_pixpll_funcs = { >>>>> +   .setup = lsdc_pixpll_setup, >>>>> +   .compute = lsdc_pixpll_compute, >>>>> +   .update = ls2k1000_pixpll_param_update, >>>>> +   .get_clock_rate = lsdc_get_clock_rate, >>>>> +}; >>>>> + >>>>> +static const struct lsdc_pixpll_funcs ls2k0500_pixpll_funcs = { >>>>> +   .setup = lsdc_pixpll_setup, >>>>> +   .compute = lsdc_pixpll_compute, >>>>> +   .update = ls2k0500_pixpll_param_update, >>>>> +   .get_clock_rate = lsdc_get_clock_rate, >>>>> +}; >>>>> + >>>>> +int lsdc_pixpll_init(struct lsdc_pll * const this, >>>>> +            struct drm_device *ddev, >>>>> +            unsigned int index) >>>>> +{ >>>>> +   struct lsdc_device *ldev = to_lsdc(ddev); >>>>> +   const struct lsdc_desc *descp = ldev->descp; >>>>> + >>>>> +   this->ddev = ddev; >>>>> +   this->index = index; >>>>> +   this->ref_clock = LSDC_PLL_REF_CLK; >>>>> + >>>>> +   /* LS7A1000 and LS7A2000's pixpll setting registers is same */ >>>>> +   if (descp->chip == CHIP_LS7A2000 || descp->chip == >>>>> CHIP_LS7A1000) { >>>>> +       if (index == 0) >>>>> +           this->reg_base = LS7A1000_CFG_REG_BASE + >>>>> LS7A1000_PIX_PLL0_REG; >>>>> +       else if (index == 1) >>>>> +           this->reg_base = LS7A1000_CFG_REG_BASE + >>>>> LS7A1000_PIX_PLL1_REG; >>>>> +       this->reg_size = 8; >>>>> +       this->funcs = &ls7a1000_pixpll_funcs; >>>>> +   } else if (descp->chip == CHIP_LS2K1000) { >>>>> +       if (index == 0) >>>>> +           this->reg_base = LS2K1000_CFG_REG_BASE + >>>>> LS2K1000_PIX_PLL0_REG; >>>>> +       else if (index == 1) >>>>> +           this->reg_base = LS2K1000_CFG_REG_BASE + >>>>> LS2K1000_PIX_PLL1_REG; >>>>> + >>>>> +       this->reg_size = 16; >>>>> +       this->funcs = &ls2k1000_pixpll_funcs; >>>>> +   } else if (descp->chip == CHIP_LS2K0500) { >>>>> +       if (index == 0) >>>>> +           this->reg_base = LS2K0500_CFG_REG_BASE + >>>>> LS2K0500_PIX_PLL0_REG; >>>>> +       else if (index == 1) >>>>> +           this->reg_base = LS2K0500_CFG_REG_BASE + >>>>> LS2K0500_PIX_PLL1_REG; >>>>> + >>>>> +       this->reg_size = 4; >>>>> +       this->funcs = &ls2k0500_pixpll_funcs; >>>>> +   } else { >>>>> +       drm_err(this->ddev, "unknown chip, the driver need >>>>> update\n"); >>>>> +       return -ENOENT; >>>>> +   } >>>>> + >>>>> +   return this->funcs->setup(this); >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.h >>>>> b/drivers/gpu/drm/lsdc/lsdc_pll.h >>>>> new file mode 100644 >>>>> index 000000000000..538739f461f2 >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.h >>>>> @@ -0,0 +1,78 @@ >>>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>>> + >>>>> +#ifndef __LSDC_PLL_H__ >>>>> +#define __LSDC_PLL_H__ >>>>> + >>>>> +#include <drm/drm_device.h> >>>>> + >>>>> +/* >>>>> + * Loongson Pixel PLL hardware structure >>>>> + * >>>>> + * refclk: reference frequency, 100 MHz from external oscillator >>>>> + * outclk: output frequency desired. >>>>> + * >>>>> + * >>>>> + *              L1      Fref Fvco    L2 >>>>> + * refclk  +-----------+     +------------------+ +---------+ >>>>> outclk >>>>> + * ---+---> | Prescaler | ---> | Clock Multiplier | ---> | divider >>>>> | --------> >>>>> + *   |    +-----------+     +------------------+ +---------+    ^ >>>>> + *   |          ^                     ^ ^         | >>>>> + *   |          |                     | |         | >>>>> + *   |          |                     | |         | >>>>> + *   |       div_ref                loopc div_out | >>>>> + * | | >>>>> + *   +--- sel_out (bypass above software configurable clock if >>>>> set) ----+ >>>>> + * >>>>> + * sel_out: PLL clock output selector (for debug purpose only). >>>>> + * >>>>> + * If sel_out == 1, it will take refclk as output directly, >>>>> + * the L1 Prescaler and the out divider will be bypassed. >>>>> + * >>>>> + * If sel_out == 0, then outclk = refclk / div_ref * loopc / >>>>> div_out; >>>>> + * >>>>> + * PLL working requirements: >>>>> + * >>>>> + * 1) 20 MHz <= refclk / div_ref <= 40Mhz >>>>> + * 2) 1.2 GHz <= refclk /div_out * loopc <= 3.2 Ghz >>>>> + */ >>>>> + >>>>> +struct lsdc_pll_parms { >>>>> +   unsigned int div_ref; >>>>> +   unsigned int loopc; >>>>> +   unsigned int div_out; >>>>> +}; >>>>> + >>>>> +struct lsdc_pll; >>>>> + >>>>> +struct lsdc_pixpll_funcs { >>>>> +   int (*setup)(struct lsdc_pll * const this); >>>>> +   bool (*compute)(struct lsdc_pll * const this, >>>>> +           unsigned int clock, >>>>> +           struct lsdc_pll_parms *pout); >>>>> +   int (*update)(struct lsdc_pll * const this, >>>>> +             struct lsdc_pll_parms const *pin); >>>>> +   unsigned int (*get_clock_rate)(struct lsdc_pll * const this, >>>>> +                      struct lsdc_pll_parms *pout); >>>>> +}; >>>>> + >>>>> +struct lsdc_pll { >>>>> +   const struct lsdc_pixpll_funcs *funcs; >>>>> +   struct drm_device *ddev; >>>>> +   void __iomem *mmio; >>>>> + >>>>> +   /* PLL register offset */ >>>>> +   u32 reg_base; >>>>> +   /* PLL register size in bytes */ >>>>> +   u32 reg_size; >>>>> + >>>>> +   /* 100000kHz, fixed on all board found */ >>>>> +   unsigned int ref_clock; >>>>> + >>>>> +   unsigned int index; >>>>> +}; >>>>> + >>>>> +int lsdc_pixpll_init(struct lsdc_pll * const this, >>>>> +            struct drm_device *ddev, >>>>> +            unsigned int index); >>>>> + >>>>> +#endif >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.c >>>>> b/drivers/gpu/drm/lsdc/lsdc_regs.c >>>>> new file mode 100644 >>>>> index 000000000000..c7950c43968f >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.c >>>>> @@ -0,0 +1,29 @@ >>>>> +// SPDX-License-Identifier: GPL-2.0 >>>>> + >>>>> +#include "lsdc_drv.h" >>>>> +#include "lsdc_regs.h" >>>>> + >>>>> +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset) >>>>> +{ >>>>> +   unsigned long flags; >>>>> +   u32 ret; >>>>> + >>>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>>> + >>>>> +   ret = readl(ldev->reg_base + offset); >>>>> + >>>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>>> + >>>>> +   return ret; >>>>> +} >>>>> + >>>>> +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 >>>>> val) >>>>> +{ >>>>> +   unsigned long flags; >>>>> + >>>>> +   spin_lock_irqsave(&ldev->reglock, flags); >>>>> + >>>>> +   writel(val, ldev->reg_base + offset); >>>>> + >>>>> +   spin_unlock_irqrestore(&ldev->reglock, flags); >>>>> +} >>>>> diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.h >>>>> b/drivers/gpu/drm/lsdc/lsdc_regs.h >>>>> new file mode 100644 >>>>> index 000000000000..828956633137 >>>>> --- /dev/null >>>>> +++ b/drivers/gpu/drm/lsdc/lsdc_regs.h >>>>> @@ -0,0 +1,296 @@ >>>>> +/* SPDX-License-Identifier: GPL-2.0 */ >>>>> + >>>>> +#ifndef __LSDC_REGS_H__ >>>>> +#define __LSDC_REGS_H__ >>>>> + >>>>> +#include <linux/bitops.h> >>>>> +#include <linux/types.h> >>>>> + >>>>> +/* >>>>> + * PIXEL PLL Reference clock >>>>> + */ >>>>> +#define LSDC_PLL_REF_CLK               100000          /* kHz */ >>>>> + >>>>> +/* >>>>> + * Those PLL registers are not located at DC reg bar space, >>>>> + * there are relative to LSXXXXX_CFG_REG_BASE. >>>>> + * XXXXX = 7A1000, 2K1000, 2K0500 >>>>> + */ >>>>> + >>>>> +/* LS2K1000 */ >>>>> +#define LS2K1000_PIX_PLL0_REG          0x04B0 >>>>> +#define LS2K1000_PIX_PLL1_REG          0x04C0 >>>>> +#define LS2K1000_CFG_REG_BASE          0x1fe10000 >>>>> + >>>>> +/* LS7A1000 and LS2K2000 */ >>>>> +#define LS7A1000_PIX_PLL0_REG          0x04B0 >>>>> +#define LS7A1000_PIX_PLL1_REG          0x04C0 >>>>> +#define LS7A1000_CFG_REG_BASE          0x10010000 >>>>> + >>>>> +/* LS2K0500 */ >>>>> +#define LS2K0500_PIX_PLL0_REG          0x0418 >>>>> +#define LS2K0500_PIX_PLL1_REG          0x0420 >>>>> +#define LS2K0500_CFG_REG_BASE          0x1fe10000 >>>>> + >>>>> +/* >>>>> + * CRTC CFG REG >>>>> + */ >>>>> +#define CFG_PIX_FMT_MASK               GENMASK(2, 0) >>>>> + >>>>> +enum lsdc_pixel_format { >>>>> +   LSDC_PF_NONE = 0, >>>>> +   LSDC_PF_ARGB4444 = 1, /* ARGB A:4 bits R/G/B: 4 bits each [16 >>>>> bits] */ >>>>> +   LSDC_PF_ARGB1555 = 2, /* ARGB A:1 bit RGB:15 bits [16 bits] */ >>>>> +   LSDC_PF_RGB565 = 3,   /* RGB [16 bits] */ >>>>> +   LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */ >>>>> +   LSDC_PF_RGBA8888 = 5, /* ARGB [32 bits] */ >>>>> +}; >>>>> + >>>>> +/* >>>>> + * Each crtc has two set fb address registers usable, >>>>> CFG_FB_IN_USING of >>>>> + * LSDC_CRTCx_CFG_REG specify which fb address register is currently >>>>> + * in using by the CRTC. CFG_PAGE_FLIP of LSDC_CRTCx_CFG_REG is >>>>> used to >>>>> + * trigger the switch which will be finished at the very vblank. >>>>> If you >>>>> + * want it switch to another again, you must set CFG_PAGE_FLIP again. >>>>> + */ >>>>> +#define CFG_PAGE_FLIP                  BIT(7) >>>>> +#define CFG_OUTPUT_EN                  BIT(8) >>>>> +/* CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware >>>>> logic */ >>>>> +#define CFG_PANEL_SWITCH               BIT(9) >>>>> +/* Indicate witch fb addr reg is in using, currently */ >>>>> +#define CFG_FB_IN_USING                BIT(11) >>>>> +#define CFG_GAMMA_EN                   BIT(12) >>>>> + >>>>> +/* CRTC get soft reset if voltage level change from 1 -> 0 */ >>>>> +#define CFG_RESET_N                    BIT(20) >>>>> + >>>>> +#define CFG_HSYNC_EN                   BIT(30) >>>>> +#define CFG_HSYNC_INV                  BIT(31) >>>>> +#define CFG_VSYNC_EN                   BIT(30) >>>>> +#define CFG_VSYNC_INV                  BIT(31) >>>>> + >>>>> +/* THE DMA step of the DC in LS7A2000 is configurable */ >>>>> +#define LSDC_DMA_STEP_MASK             GENMASK(17, 16) >>>>> +enum lsdc_dma_steps_supported { >>>>> +   LSDC_DMA_STEP_256_BYTES = 0, >>>>> +   LSDC_DMA_STEP_128_BYTES = 1, >>>>> +   LSDC_DMA_STEP_64_BYTES = 2, >>>>> +   LSDC_DMA_STEP_32_BYTES = 3, >>>>> +}; >>>>> + >>>>> +/******** CRTC0 & DVO0 ********/ >>>>> +#define LSDC_CRTC0_CFG_REG             0x1240 >>>>> +#define LSDC_CRTC0_FB0_LO_ADDR_REG     0x1260 >>>>> +#define LSDC_CRTC0_FB0_HI_ADDR_REG     0x15A0 >>>>> +#define LSDC_CRTC0_FB1_LO_ADDR_REG     0x1580 >>>>> +#define LSDC_CRTC0_FB1_HI_ADDR_REG     0x15C0 >>>>> +#define LSDC_CRTC0_STRIDE_REG          0x1280 >>>>> +#define LSDC_CRTC0_FB_ORIGIN_REG       0x1300 >>>>> +#define LSDC_CRTC0_HDISPLAY_REG        0x1400 >>>>> +#define LSDC_CRTC0_HSYNC_REG           0x1420 >>>>> +#define LSDC_CRTC0_VDISPLAY_REG        0x1480 >>>>> +#define LSDC_CRTC0_VSYNC_REG           0x14A0 >>>>> +#define LSDC_CRTC0_GAMMA_INDEX_REG     0x14E0 >>>>> +#define LSDC_CRTC0_GAMMA_DATA_REG      0x1500 >>>>> + >>>>> +/******** CTRC1 & DVO1 ********/ >>>>> +#define LSDC_CRTC1_CFG_REG             0x1250 >>>>> +#define LSDC_CRTC1_FB0_LO_ADDR_REG     0x1270 >>>>> +#define LSDC_CRTC1_FB0_HI_ADDR_REG     0x15B0 >>>>> +#define LSDC_CRTC1_FB1_LO_ADDR_REG     0x1590 >>>>> +#define LSDC_CRTC1_FB1_HI_ADDR_REG     0x15D0 >>>>> +#define LSDC_CRTC1_STRIDE_REG          0x1290 >>>>> +#define LSDC_CRTC1_FB_ORIGIN_REG       0x1310 >>>>> +#define LSDC_CRTC1_HDISPLAY_REG        0x1410 >>>>> +#define LSDC_CRTC1_HSYNC_REG           0x1430 >>>>> +#define LSDC_CRTC1_VDISPLAY_REG        0x1490 >>>>> +#define LSDC_CRTC1_VSYNC_REG           0x14B0 >>>>> +#define LSDC_CRTC1_GAMMA_INDEX_REG     0x14F0 >>>>> +#define LSDC_CRTC1_GAMMA_DATA_REG      0x1510 >>>>> + >>>>> +/* >>>>> + * LS7A2000 has the hardware which record the scan position of the >>>>> CRTC >>>>> + * [31:16] : current X position, [15:0] : current Y position >>>>> + */ >>>>> +#define LSDC_CRTC0_SCAN_POS_REG        0x14C0 >>>>> +#define LSDC_CRTC1_SCAN_POS_REG        0x14D0 >>>>> + >>>>> +/* >>>>> + * LS7A2000 has the hardware which count the number of vblank >>>>> generated >>>>> + */ >>>>> +#define LSDC_CRTC0_VSYNC_COUNTER_REG   0x1A00 >>>>> +#define LSDC_CRTC1_VSYNC_COUNTER_REG   0x1A10 >>>>> + >>>>> +/* In all, LSDC_CRTC1_XXX_REG - LSDC_CRTC0_XXX_REG = 0x10 */ >>>>> + >>>>> +/* >>>>> + * There is only one hardware cursor unit in ls7a1000, ls2k1000 >>>>> and ls2k0500. >>>>> + * well, ls7a2000 has two hardware cursor unit. >>>>> + */ >>>>> +#define CURSOR_FORMAT_MASK             GENMASK(1, 0) >>>>> +enum lsdc_cursor_format { >>>>> +   CURSOR_FORMAT_DISABLE = 0, >>>>> +   CURSOR_FORMAT_MONOCHROME = 1, >>>>> +   CURSOR_FORMAT_ARGB8888 = 2, >>>>> +}; >>>>> + >>>>> +#define CURSOR_SIZE_64X64              BIT(2) >>>>> +#define CURSOR_LOCATION                BIT(4) >>>>> + >>>>> +#define LSDC_CURSOR0_CFG_REG           0x1520 >>>>> +#define LSDC_CURSOR0_ADDR_LO_REG       0x1530 >>>>> +#define LSDC_CURSOR0_ADDR_HI_REG       0x15e0 >>>>> +#define LSDC_CURSOR0_POSITION_REG      0x1540 >>>>> +#define LSDC_CURSOR0_BG_COLOR_REG      0x1550 /* background >>>>> color */ >>>>> +#define LSDC_CURSOR0_FG_COLOR_REG      0x1560 /* foreground >>>>> color */ >>>>> + >>>>> +#define LSDC_CURSOR1_CFG_REG           0x1670 >>>>> +#define LSDC_CURSOR1_ADDR_LO_REG       0x1680 >>>>> +#define LSDC_CURSOR1_ADDR_HI_REG       0x16e0 >>>>> +#define LSDC_CURSOR1_POSITION_REG      0x1690 /* [31:16] Y, >>>>> [15:0] X */ >>>>> +#define LSDC_CURSOR1_BG_COLOR_REG      0x16A0 /* background >>>>> color */ >>>>> +#define LSDC_CURSOR1_FG_COLOR_REG      0x16B0 /* foreground >>>>> color */ >>>>> + >>>>> +/* >>>>> + * DC Interrupt Control Register, 32bit, Address Offset: 1570 >>>>> + * >>>>> + * Bits 15:0 inidicate the interrupt status >>>>> + * Bits 31:16 control enable interrupts corresponding to bit 15:0 >>>>> or not >>>>> + * Write 1 to enable, write 0 to disable >>>>> + * >>>>> + * RF: Read Finished >>>>> + * IDBU: Internal Data Buffer Underflow >>>>> + * IDBFU: Internal Data Buffer Fatal Underflow >>>>> + * CBRF: Cursor Buffer Read Finished Flag, no use. >>>>> + * >>>>> + * >>>>> +-------+--------------------------+-------+--------+--------+-------+ >>>>> + * | 31:27 |        26:16           | 15:11 |  10  | 9 |  8  | >>>>> + * >>>>> +-------+--------------------------+-------+--------+--------+-------+ >>>>> + * | N/A | Interrupt Enable Control | N/A | IDBFU0 | IDBFU1 | >>>>> IDBU0 | >>>>> + * >>>>> +-------+--------------------------+-------+--------+--------+-------+ >>>>> + * >>>>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>>>> + * |  7  | 6 | 5 | 4  |  3   |  2   |  1   | 0   | >>>>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>>>> + * | IDBU1 | RF0 | RF1 | CRRF | HSYNC0 | VSYNC0 | HSYNC1 | VSYNC1 | >>>>> + * +-------+-----+-----+------+--------+--------+--------+--------+ >>>>> + */ >>>>> + >>>>> +#define LSDC_INT_REG                   0x1570 >>>>> + >>>>> +#define INT_CRTC0_VSYNC                BIT(2) >>>>> +#define INT_CRTC0_HSYNC                BIT(3) >>>>> +#define INT_CRTC0_RF                   BIT(6) >>>>> +#define INT_CRTC0_IDBU                 BIT(8) >>>>> +#define INT_CRTC0_IDBFU                BIT(10) >>>>> + >>>>> +#define INT_CRTC1_VSYNC                BIT(0) >>>>> +#define INT_CRTC1_HSYNC                BIT(1) >>>>> +#define INT_CRTC1_RF                   BIT(5) >>>>> +#define INT_CRTC1_IDBU                 BIT(7) >>>>> +#define INT_CRTC1_IDBFU                BIT(9) >>>>> + >>>>> +#define INT_CRTC0_VS_EN                BIT(18) >>>>> +#define INT_CRTC0_HS_EN                BIT(19) >>>>> +#define INT_CRTC0_RF_EN                BIT(22) >>>>> +#define INT_CRTC0_IDBU_EN              BIT(24) >>>>> +#define INT_CRTC0_IDBFU_EN             BIT(26) >>>>> + >>>>> +#define INT_CRTC1_VS_EN                BIT(16) >>>>> +#define INT_CRTC1_HS_EN                BIT(17) >>>>> +#define INT_CRTC1_RF_EN                BIT(21) >>>>> +#define INT_CRTC1_IDBU_EN              BIT(23) >>>>> +#define INT_CRTC1_IDBFU_EN             BIT(25) >>>>> + >>>>> +#define INT_STATUS_MASK                GENMASK(15, 0) >>>>> + >>>>> +/* >>>>> + * LS7A1000/LS7A2000 have 4 gpios which are used to emulated I2C. >>>>> + * They are under control of the LS7A_DC_GPIO_DAT_REG and >>>>> LS7A_DC_GPIO_DIR_REG >>>>> + * register, Those GPIOs has no relationship whth the GPIO >>>>> hardware on the >>>>> + * bridge chip itself. Those offsets are relative to DC register >>>>> base address >>>>> + * >>>>> + * LS2k1000 and LS2K0500 don't have those registers, they use >>>>> hardware i2c >>>>> + * or general GPIO emulated i2c from linux i2c subsystem. >>>>> + * >>>>> + * GPIO data register, address offset: 0x1650 >>>>> + *  +---------------+-----------+-----------+ >>>>> + *  | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | >>>>> + *  +---------------+-----------+-----------+ >>>>> + *  |              |   DVO1  |   DVO0  | >>>>> + *  +     N/A     +-----------+-----------+ >>>>> + *  |              | SCL | SDA | SCL | SDA | >>>>> + *  +---------------+-----------+-----------+ >>>>> + */ >>>>> +#define LS7A_DC_GPIO_DAT_REG           0x1650 >>>>> + >>>>> +/* >>>>> + * GPIO Input/Output direction control register, address offset: >>>>> 0x1660 >>>>> + */ >>>>> +#define LS7A_DC_GPIO_DIR_REG           0x1660 >>>>> + >>>>> +/* >>>>> + * LS7A2000 has two built-in HDMI Encoder and one VGA encoder >>>>> + */ >>>>> + >>>>> +/* >>>>> + * Number of continuous packets may be present >>>>> + * in HDMI hblank and vblank zone, should >= 48 >>>>> + */ >>>>> +#define LSDC_HDMI0_ZONE_REG            0x1700 >>>>> +#define LSDC_HDMI1_ZONE_REG            0x1710 >>>>> + >>>>> +#define HDMI_INTERFACE_EN              BIT(0) >>>>> +#define HDMI_PACKET_EN                 BIT(1) >>>>> +#define HDMI_AUDIO_EN                  BIT(2) >>>>> +#define HDMI_VIDEO_PREAMBLE_MASK       GENMASK(7, 4) >>>>> +#define HDMI_HW_I2C_EN                 BIT(8) >>>>> +#define HDMI_CTL_PERIOD_MODE           BIT(9) >>>>> +#define LSDC_HDMI0_CTRL_REG            0x1720 >>>>> +#define LSDC_HDMI1_CTRL_REG            0x1730 >>>>> + >>>>> +#define HDMI_PHY_EN                    BIT(0) >>>>> +#define HDMI_PHY_RESET_N               BIT(1) >>>>> +#define HDMI_PHY_TERM_L_EN             BIT(8) >>>>> +#define HDMI_PHY_TERM_H_EN             BIT(9) >>>>> +#define HDMI_PHY_TERM_DET_EN           BIT(10) >>>>> +#define HDMI_PHY_TERM_STATUS           BIT(11) >>>>> +#define LSDC_HDMI0_PHY_CTRL_REG        0x1800 >>>>> +#define LSDC_HDMI1_PHY_CTRL_REG        0x1810 >>>>> + >>>>> +/* >>>>> + * 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 | >>>>> + * +--------------------------------------------------------+ >>>>> + */ >>>>> +#define LSDC_HDMI0_PHY_PLL_REG    ��    0x1820 >>>>> +#define LSDC_HDMI1_PHY_PLL_REG         0x1830 >>>>> + >>>>> +#define HDMI_PLL_ENABLE                BIT(0) >>>>> +#define HDMI_PLL_LOCKED                BIT(16) >>>>> +#define HDMI_PLL_BYPASS                BIT(17) >>>>> + >>>>> +#define HDMI_PLL_IDF_SHIFT             1 >>>>> +#define HDMI_PLL_IDF_MASK              GENMASK(5, 1) >>>>> +#define HDMI_PLL_LF_SHIFT              6 >>>>> +#define HDMI_PLL_LF_MASK               GENMASK(12, 6) >>>>> +#define HDMI_PLL_ODF_SHIFT             13 >>>>> +#define HDMI_PLL_ODF_MASK              GENMASK(15, 13) >>>>> + >>>>> +/* LS7A2000 have hpd support */ >>>>> +#define LSDC_HDMI_HPD_STATUS_REG       0x1BA0 >>>>> +#define HDMI0_HPD_FLAG                 BIT(0) >>>>> +#define HDMI1_HPD_FLAG                 BIT(1) >>>>> + >>>>> +#endif >>>> >>> >> -- Thomas Zimmermann Graphics Driver Developer SUSE Software Solutions Germany GmbH Maxfeldstr. 5, 90409 Nürnberg, Germany (HRB 36809, AG Nürnberg) Geschäftsführer: Ivo Totev
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig index dc0f94f02a82..3baecd48930b 100644 --- a/drivers/gpu/drm/Kconfig +++ b/drivers/gpu/drm/Kconfig @@ -367,6 +367,8 @@ source "drivers/gpu/drm/solomon/Kconfig" source "drivers/gpu/drm/sprd/Kconfig" +source "drivers/gpu/drm/lsdc/Kconfig" + config DRM_HYPERV tristate "DRM Support for Hyper-V synthetic video device" depends on DRM && PCI && MMU && HYPERV diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile index ab4460fcd63f..5a8e2fc8dd5d 100644 --- a/drivers/gpu/drm/Makefile +++ b/drivers/gpu/drm/Makefile @@ -190,3 +190,4 @@ obj-y += gud/ obj-$(CONFIG_DRM_HYPERV) += hyperv/ obj-y += solomon/ obj-$(CONFIG_DRM_SPRD) += sprd/ +obj-$(CONFIG_DRM_LSDC) += lsdc/ diff --git a/drivers/gpu/drm/lsdc/Kconfig b/drivers/gpu/drm/lsdc/Kconfig new file mode 100644 index 000000000000..8b9bb6996877 --- /dev/null +++ b/drivers/gpu/drm/lsdc/Kconfig @@ -0,0 +1,17 @@ +# SPDX-License-Identifier: GPL-2.0 + +config DRM_LSDC + tristate "DRM support for loongson display controller" + depends on DRM && PCI && (MACH_LOONGSON64 || LOONGARCH || MIPS) + select OF + select DRM_KMS_HELPER + select DRM_TTM + select DRM_TTM_HELPER + select DRM_VRAM_HELPER + default m + help + This is a KMS driver for loongson display controller in the + LS7A1000/LS7A2000 bridge chip and LS2K1000/LS2K0500 SoC. + If "M" is selected, the module will be called lsdc. + + If in doubt, say "Y". diff --git a/drivers/gpu/drm/lsdc/Makefile b/drivers/gpu/drm/lsdc/Makefile new file mode 100644 index 000000000000..d4b901ec09cf --- /dev/null +++ b/drivers/gpu/drm/lsdc/Makefile @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: GPL-2.0 + +lsdc-y := \ + lsdc_drv.o \ + lsdc_crtc.o \ + lsdc_irq.o \ + lsdc_plane.o \ + lsdc_pll.o \ + lsdc_i2c.o \ + lsdc_output.o \ + lsdc_regs.o \ + lsdc_debugfs.o + +obj-$(CONFIG_DRM_LSDC) += lsdc.o diff --git a/drivers/gpu/drm/lsdc/lsdc_crtc.c b/drivers/gpu/drm/lsdc/lsdc_crtc.c new file mode 100644 index 000000000000..e23e6085f418 --- /dev/null +++ b/drivers/gpu/drm/lsdc/lsdc_crtc.c @@ -0,0 +1,421 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <drm/drm_vblank.h> +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include "lsdc_drv.h" +#include "lsdc_regs.h" +#include "lsdc_pll.h" + +static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc) +{ + struct lsdc_device *ldev = to_lsdc(crtc->dev); + unsigned int index = drm_crtc_index(crtc); + u32 val; + + val = lsdc_rreg32(ldev, LSDC_INT_REG); + + if (index == 0) + val |= INT_CRTC0_VS_EN; + else if (index == 1) + val |= INT_CRTC1_VS_EN; + + lsdc_wreg32(ldev, LSDC_INT_REG, val); + + return 0; +} + +static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc) +{ + struct lsdc_device *ldev = to_lsdc(crtc->dev); + unsigned int index = drm_crtc_index(crtc); + u32 val; + + val = lsdc_rreg32(ldev, LSDC_INT_REG); + + if (index == 0) + val &= ~INT_CRTC0_VS_EN; + else if (index == 1) + val &= ~INT_CRTC1_VS_EN; + + lsdc_wreg32(ldev, LSDC_INT_REG, val); +} + +static void lsdc_crtc_reset(struct drm_crtc *crtc) +{ + struct drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + unsigned int index = drm_crtc_index(crtc); + struct lsdc_crtc_state *priv_crtc_state; + u32 val = CFG_RESET_N | LSDC_PF_XRGB8888; + + if (index == 0) + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); + else if (index == 1) + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); + + if (crtc->state) { + priv_crtc_state = to_lsdc_crtc_state(crtc->state); + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); + kfree(priv_crtc_state); + } + + priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL); + if (!priv_crtc_state) + return; + + __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); + + drm_info(ddev, "CRTC-%u reset\n", index); +} + +static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct lsdc_crtc_state *priv_crtc_state = to_lsdc_crtc_state(state); + + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); + + kfree(priv_crtc_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 = kmalloc(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_get_vblank_counter(struct drm_crtc *crtc) +{ + struct drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + const struct lsdc_desc *descp = ldev->descp; + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); + unsigned int index = dispipe->index; + + /* fallback to software emulated VBLANK counter */ + if (!descp->has_vblank_counter) + goto fallback; + + if (index == 0) + return lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG); + + if (index == 1) + return lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG); + +fallback: + return (u32)drm_crtc_vblank_count(crtc); +} + +static const struct drm_crtc_funcs lsdc_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, + .get_vblank_counter = lsdc_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, +}; + +static enum drm_mode_status +lsdc_crtc_mode_valid(struct drm_crtc *crtc, + const struct drm_display_mode *mode) +{ + struct drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + const struct lsdc_desc *descp = ldev->descp; + + if (mode->hdisplay > descp->max_width) + return MODE_BAD_HVALUE; + if (mode->vdisplay > descp->max_height) + return MODE_BAD_VVALUE; + + if (mode->clock > descp->max_pixel_clk) { + drm_dbg(ddev, "mode %dx%d, pixel clock=%d is too high\n", + mode->hdisplay, mode->vdisplay, mode->clock); + return MODE_CLOCK_HIGH; + } + + if ((mode->hdisplay * 4) % descp->pitch_align) { + drm_dbg(ddev, "stride is require to not %u bytes aligned\n", + descp->pitch_align); + return MODE_BAD; + } + + return MODE_OK; +} + +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); + struct lsdc_pll *pixpll = &dispipe->pixpll; + const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state); + bool ret; + + ret = pfuncs->compute(pixpll, state->mode.clock, &priv_state->pparms); + if (ret) + return 0; + + drm_warn(crtc->dev, "failed find PLL parameters for %u\n", state->mode.clock); + + return -EINVAL; +} + +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; /* no mode checks if CRTC is being disabled */ + + return lsdc_pixpll_atomic_check(crtc, crtc_state); +} + +static void lsdc_update_pixclk(struct lsdc_display_pipe *dispipe, + struct lsdc_crtc_state *priv_state) +{ + struct lsdc_pll *pixpll = &dispipe->pixpll; + const struct lsdc_pixpll_funcs *clkfunc = pixpll->funcs; + + clkfunc->update(pixpll, &priv_state->pparms); +} + +static void lsdc_crtc_helper_mode_set_nofb(struct drm_crtc *crtc) +{ + struct drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + struct drm_display_mode *mode = &crtc->state->mode; + struct lsdc_display_pipe *dispipe = crtc_to_display_pipe(crtc); + struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(crtc->state); + unsigned int index = dispipe->index; + u32 h_sync, v_sync, h_val, v_val; + u32 val; + + /* 26:16 total pixels, 10:0 visiable pixels, in horizontal */ + h_val = (mode->crtc_htotal << 16) | mode->crtc_hdisplay; + + /* 26:16 total pixels, 10:0 visiable pixels, in vertical */ + v_val = (mode->crtc_vtotal << 16) | mode->crtc_vdisplay; + /* 26:16 hsync end, 10:0 hsync start, bit 30 is hsync enable */ + h_sync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | CFG_HSYNC_EN; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + h_sync |= CFG_HSYNC_INV; + + /* 26:16 vsync end, 10:0 vsync start, bit 30 is vsync enable */ + v_sync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | CFG_VSYNC_EN; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + v_sync |= CFG_VSYNC_INV; + + if (index == 0) { + lsdc_wreg32(ldev, LSDC_CRTC0_HDISPLAY_REG, h_val); + lsdc_wreg32(ldev, LSDC_CRTC0_VDISPLAY_REG, v_val); + lsdc_wreg32(ldev, LSDC_CRTC0_HSYNC_REG, h_sync); + lsdc_wreg32(ldev, LSDC_CRTC0_VSYNC_REG, v_sync); + + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); + } else if (index == 1) { + lsdc_wreg32(ldev, LSDC_CRTC1_HDISPLAY_REG, h_val); + lsdc_wreg32(ldev, LSDC_CRTC1_VDISPLAY_REG, v_val); + lsdc_wreg32(ldev, LSDC_CRTC1_HSYNC_REG, h_sync); + lsdc_wreg32(ldev, LSDC_CRTC1_VSYNC_REG, v_sync); + + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + val = (val & ~LSDC_DMA_STEP_MASK) | LSDC_DMA_STEP_256_BYTES; + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); + } + + drm_dbg(ddev, "%s modeset: %ux%u\n", + crtc->name, mode->hdisplay, mode->vdisplay); + + lsdc_update_pixclk(dispipe, priv_state); +} + +static void lsdc_enable_display(struct lsdc_device *ldev, unsigned int index) +{ + u32 val; + + if (index == 0) { + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + val |= CFG_OUTPUT_EN; + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); + return; + } + + if (index == 1) { + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + val |= CFG_OUTPUT_EN; + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); + return; + } +} + +static void lsdc_disable_display(struct lsdc_device *ldev, unsigned int index) +{ + u32 val; + + if (index == 0) { + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + val &= ~CFG_OUTPUT_EN; + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); + return; + } + + if (index == 1) { + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + val &= ~CFG_OUTPUT_EN; + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); + return; + } +} + +static void lsdc_crtc_helper_atomic_enable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + + drm_crtc_vblank_on(crtc); + + lsdc_enable_display(ldev, drm_crtc_index(crtc)); + + drm_dbg(ddev, "%s: enabled\n", crtc->name); +} + +static void lsdc_crtc_helper_atomic_disable(struct drm_crtc *crtc, + struct drm_atomic_state *state) +{ + struct drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + + lsdc_disable_display(ldev, drm_crtc_index(crtc)); + + drm_crtc_wait_one_vblank(crtc); + + drm_crtc_vblank_off(crtc); + + drm_dbg(ddev, "%s: disabled\n", crtc->name); +} + +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 drm_device *ddev = crtc->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + unsigned int index = drm_crtc_index(crtc); + int line, vsw, vbp, vactive_start, vactive_end, vfp_end; + u32 val = 0; + + vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; + vbp = mode->crtc_vtotal - mode->crtc_vsync_end; + + vactive_start = vsw + vbp + 1; + + vactive_end = vactive_start + mode->crtc_vdisplay; + + /* last scan line before VSYNC */ + vfp_end = mode->crtc_vtotal; + + if (stime) + *stime = ktime_get(); + + if (index == 0) + val = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); + else if (index == 1) + val = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); + + line = (val & 0xffff); + + if (line < vactive_start) + line -= vactive_start; + else if (line > vactive_end) + line = line - vfp_end - vactive_start; + else + line -= vactive_start; + + *vpos = line; + *hpos = val >> 16; + + if (etime) + *etime = ktime_get(); + + return true; +} + +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { + .mode_valid = lsdc_crtc_mode_valid, + .mode_set_nofb = lsdc_crtc_helper_mode_set_nofb, + .atomic_enable = lsdc_crtc_helper_atomic_enable, + .atomic_disable = lsdc_crtc_helper_atomic_disable, + .atomic_check = lsdc_crtc_helper_atomic_check, + .atomic_flush = lsdc_crtc_atomic_flush, + .get_scanout_position = lsdc_crtc_get_scanout_position, +}; + +int lsdc_crtc_init(struct drm_device *ddev, + struct drm_crtc *crtc, + unsigned int index, + struct drm_plane *primary, + struct drm_plane *cursor) +{ + int ret; + + ret = drm_crtc_init_with_planes(ddev, crtc, primary, cursor, + &lsdc_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) + drm_warn(ddev, "set the gamma table size failed\n"); + + drm_crtc_enable_color_mgmt(crtc, 0, false, 256); + + return 0; +} diff --git a/drivers/gpu/drm/lsdc/lsdc_debugfs.c b/drivers/gpu/drm/lsdc/lsdc_debugfs.c new file mode 100644 index 000000000000..176ae4e11d1c --- /dev/null +++ b/drivers/gpu/drm/lsdc/lsdc_debugfs.c @@ -0,0 +1,191 @@ +// SPDX-License-Identifier: GPL-2.0+ + +#include <drm/drm_debugfs.h> +#include <drm/drm_managed.h> +#include <drm/drm_gem_vram_helper.h> +#include "lsdc_drv.h" +#include "lsdc_regs.h" +#include "lsdc_pll.h" + +#ifdef CONFIG_DEBUG_FS +static int lsdc_show_clock(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *ddev = node->minor->dev; + struct drm_crtc *crtc; + + drm_for_each_crtc(crtc, ddev) { + struct lsdc_display_pipe *pipe = crtc_to_display_pipe(crtc); + struct lsdc_pll *pixpll = &pipe->pixpll; + const struct lsdc_pixpll_funcs *funcs = pixpll->funcs; + struct drm_display_mode *adj = &crtc->state->mode; + struct lsdc_pll_parms parms; + unsigned int out_khz; + + out_khz = funcs->get_clock_rate(pixpll, &parms); + + seq_printf(m, "Display pipe %u: %dx%d\n", + pipe->index, adj->hdisplay, adj->vdisplay); + + seq_printf(m, "Frequency actually output: %u kHz\n", out_khz); + seq_printf(m, "Pixel clock required: %d kHz\n", adj->clock); + seq_printf(m, "diff: %d kHz\n", adj->clock); + + seq_printf(m, "div_ref=%u, loopc=%u, div_out=%u\n", + parms.div_ref, parms.loopc, parms.div_out); + + seq_printf(m, "hsync_start=%d, hsync_end=%d, htotal=%d\n", + adj->hsync_start, adj->hsync_end, adj->htotal); + seq_printf(m, "vsync_start=%d, vsync_end=%d, vtotal=%d\n\n", + adj->vsync_start, adj->vsync_end, adj->vtotal); + } + + return 0; +} + +static int lsdc_show_mm(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *ddev = node->minor->dev; + struct drm_printer p = drm_seq_file_printer(m); + + drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p); + + return 0; +} + +#define REGDEF(reg) { __stringify_1(LSDC_##reg##_REG), LSDC_##reg##_REG } +static const struct { + const char *name; + u32 reg_offset; +} lsdc_regs_array[] = { + REGDEF(CURSOR0_CFG), + REGDEF(CURSOR0_ADDR_LO), + REGDEF(CURSOR0_ADDR_HI), + REGDEF(CURSOR0_POSITION), + REGDEF(CURSOR0_BG_COLOR), + REGDEF(CURSOR0_FG_COLOR), + REGDEF(INT), + REGDEF(CRTC0_CFG), + REGDEF(CRTC0_STRIDE), + REGDEF(CRTC0_FB_ORIGIN), + REGDEF(CRTC0_HDISPLAY), + REGDEF(CRTC0_HSYNC), + REGDEF(CRTC0_VDISPLAY), + REGDEF(CRTC0_VSYNC), + REGDEF(CRTC0_GAMMA_INDEX), + REGDEF(CRTC0_GAMMA_DATA), + REGDEF(CRTC1_CFG), + REGDEF(CRTC1_STRIDE), + REGDEF(CRTC1_FB_ORIGIN), + REGDEF(CRTC1_HDISPLAY), + REGDEF(CRTC1_HSYNC), + REGDEF(CRTC1_VDISPLAY), + REGDEF(CRTC1_VSYNC), + REGDEF(CRTC1_GAMMA_INDEX), + REGDEF(CRTC1_GAMMA_DATA), +}; + +static int lsdc_show_regs(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *ddev = node->minor->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + int i; + + for (i = 0; i < ARRAY_SIZE(lsdc_regs_array); i++) { + u32 offset = lsdc_regs_array[i].reg_offset; + const char *name = lsdc_regs_array[i].name; + + seq_printf(m, "%s (0x%04x): 0x%08x\n", + name, offset, lsdc_rreg32(ldev, offset)); + } + + return 0; +} + +static int lsdc_show_vblank_counter(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *ddev = node->minor->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + + seq_printf(m, "CRTC-0 vblank counter: %08u\n", + lsdc_rreg32(ldev, LSDC_CRTC0_VSYNC_COUNTER_REG)); + + seq_printf(m, "CRTC-1 vblank counter: %08u\n", + lsdc_rreg32(ldev, LSDC_CRTC1_VSYNC_COUNTER_REG)); + + return 0; +} + +static int lsdc_show_scan_position(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *ddev = node->minor->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 val0 = lsdc_rreg32(ldev, LSDC_CRTC0_SCAN_POS_REG); + u32 val1 = lsdc_rreg32(ldev, LSDC_CRTC1_SCAN_POS_REG); + + seq_printf(m, "CRTC-0: x: %08u, y: %08u\n", + val0 >> 16, val0 & 0xFFFF); + + seq_printf(m, "CRTC-1: x: %08u, y: %08u\n", + val1 >> 16, val1 & 0xFFFF); + + return 0; +} + +static int lsdc_show_fb_addr(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *)m->private; + struct drm_device *ddev = node->minor->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 lo, hi; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + if (val & CFG_FB_IN_USING) { + lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_LO_ADDR_REG); + hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB1_HI_ADDR_REG); + seq_printf(m, "CRTC-0 using fb1: 0x%x:%x\n", hi, lo); + } else { + lo = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_LO_ADDR_REG); + hi = lsdc_rreg32(ldev, LSDC_CRTC0_FB0_HI_ADDR_REG); + seq_printf(m, "CRTC-0 using fb0: 0x%x:%x\n", hi, lo); + } + + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + if (val & CFG_FB_IN_USING) { + lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_LO_ADDR_REG); + hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB1_HI_ADDR_REG); + seq_printf(m, "CRTC-1 using fb1: 0x%x:%x\n", hi, lo); + } else { + lo = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_LO_ADDR_REG); + hi = lsdc_rreg32(ldev, LSDC_CRTC1_FB0_HI_ADDR_REG); + seq_printf(m, "CRTC-1 using fb0: 0x%x:%x\n", hi, lo); + } + + return 0; +} + +static struct drm_info_list lsdc_debugfs_list[] = { + { "clocks", lsdc_show_clock, 0 }, + { "mm", lsdc_show_mm, 0, NULL }, + { "regs", lsdc_show_regs, 0 }, + { "vblanks", lsdc_show_vblank_counter, 0, NULL }, + { "scan_pos", lsdc_show_scan_position, 0, NULL }, + { "fb_addr", lsdc_show_fb_addr, 0, NULL }, +}; + +#endif + +void lsdc_debugfs_init(struct drm_minor *minor) +{ +#ifdef CONFIG_DEBUG_FS + drm_debugfs_create_files(lsdc_debugfs_list, + ARRAY_SIZE(lsdc_debugfs_list), + minor->debugfs_root, + minor); +#endif +} diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.c b/drivers/gpu/drm/lsdc/lsdc_drv.c new file mode 100644 index 000000000000..3bd457e6e980 --- /dev/null +++ b/drivers/gpu/drm/lsdc/lsdc_drv.c @@ -0,0 +1,635 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * KMS driver for Loongson display controller + * Copyright (C) 2022 Loongson Corporation + * + * Authors: + * Li Yi <liyi@loongson.cn> + * Li Chenyang <lichenyang@loongson.cn> + * Sui Jingfeng <suijingfeng@loongson.cn> + */ + +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/of_address.h> +#include <drm/drm_aperture.h> +#include <drm/drm_vblank.h> +#include <drm/drm_fb_helper.h> +#include <drm/drm_gem_vram_helper.h> +#include <drm/drm_gem_framebuffer_helper.h> +#include <drm/drm_modeset_helper.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_fbdev_generic.h> +#include "lsdc_drv.h" + +static int lsdc_chip = -1; +MODULE_PARM_DESC(chip, "override chip id (-1 = probed by driver"); +module_param_named(chip, lsdc_chip, int, 0644); + +/* + * ls7a1000 and ls7a2000 is not only used with loongson desktop class CPU + * but also loongson server class CPUs (LS3C5000@2.2GHz 16 core). Loongson + * server products generally don't has a dedicated vram mounted for cost + * reason, it will use aspeed bmc as its display on its final and formal + * product. lsdc.has_vram=0 is mainly be use in developing phase of the + * product. When a board don't has a dedicated vram mounted, we can put + * the framebuffer on system RAM. + */ +static int lsdc_has_vram = -1; +MODULE_PARM_DESC(has_vram, "has on-board gpu memory in reality (0 = no, -1 = default"); +module_param_named(has_vram, lsdc_has_vram, int, 0644); + +#define DRIVER_AUTHOR "Sui Jingfeng <suijingfeng@loongson.cn>" +#define DRIVER_NAME "lsdc" +#define DRIVER_DESC "drm driver for loongson's display controller" +#define DRIVER_DATE "20220701" +#define DRIVER_MAJOR 1 +#define DRIVER_MINOR 0 +#define DRIVER_PATCHLEVEL 0 + +static const struct lsdc_desc dc_in_ls2k0500 = { + .chip = CHIP_LS2K0500, + .num_of_crtc = LSDC_NUM_CRTC, + .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, + .has_builtin_i2c = false, + .has_vram = false, + .has_hpd_reg = false, + .is_soc = true, +}; + +static const struct lsdc_desc dc_in_ls2k1000 = { + .chip = CHIP_LS2K1000, + .num_of_crtc = LSDC_NUM_CRTC, + .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, + .has_builtin_i2c = false, + .has_vram = false, + .has_hpd_reg = false, + .is_soc = true, +}; + +static const struct lsdc_desc dc_in_ls2k2000 = { + .chip = CHIP_LS2K2000, + .num_of_crtc = LSDC_NUM_CRTC, + .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 = 256, + .mc_bits = 40, + .has_vblank_counter = true, + .has_builtin_i2c = true, + .has_vram = false, + .has_hpd_reg = true, + .is_soc = true, +}; + +static const struct lsdc_desc dc_in_ls7a1000 = { + .chip = CHIP_LS7A1000, + .num_of_crtc = LSDC_NUM_CRTC, + .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, + .has_builtin_i2c = true, + .has_vram = true, + .has_hpd_reg = false, + .is_soc = false, +}; + +static const struct lsdc_desc dc_in_ls7a2000 = { + .chip = CHIP_LS7A2000, + .num_of_crtc = LSDC_NUM_CRTC, + .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 = 256, + .mc_bits = 40, /* support 48, but use 40 for backward compatibility */ + .has_vblank_counter = true, + .has_builtin_i2c = true, + .has_vram = true, + .has_hpd_reg = true, + .is_soc = false, +}; + +static int lsdc_gem_dumb_create(struct drm_file *file, + struct drm_device *ddev, + struct drm_mode_create_dumb *args) +{ + struct lsdc_device *ldev = to_lsdc(ddev); + const struct lsdc_desc *descp = ldev->descp; + + return drm_gem_vram_fill_create_dumb(file, ddev, 0, descp->pitch_align, args); +} + +DEFINE_DRM_GEM_FOPS(lsdc_gem_fops); + +static const struct drm_driver lsdc_drm_driver = { + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, + .fops = &lsdc_gem_fops, + + .name = DRIVER_NAME, + .desc = DRIVER_DESC, + .date = DRIVER_DATE, + .major = DRIVER_MAJOR, + .minor = DRIVER_MINOR, + .patchlevel = DRIVER_PATCHLEVEL, + + .debugfs_init = lsdc_debugfs_init, + .dumb_create = lsdc_gem_dumb_create, + .dumb_map_offset = drm_gem_ttm_dumb_map_offset, + .gem_prime_mmap = drm_gem_prime_mmap, +}; + +static const struct drm_mode_config_funcs lsdc_mode_config_funcs = { + .fb_create = drm_gem_fb_create, + .output_poll_changed = drm_fb_helper_output_poll_changed, + .atomic_check = drm_atomic_helper_check, + .atomic_commit = drm_atomic_helper_commit, + .mode_valid = drm_vram_helper_mode_valid, +}; + +static int lsdc_modeset_init(struct lsdc_device *ldev, + const struct lsdc_desc *descp) +{ + struct drm_device *ddev = &ldev->base; + unsigned int num_crtc = descp->num_of_crtc; + unsigned int i; + int ret; + + for (i = 0; i < num_crtc; i++) { + ret = lsdc_create_output(ldev, i); + if (ret) + return ret; + } + + for (i = 0; i < num_crtc; i++) { + struct lsdc_display_pipe *dispipe = &ldev->dispipe[i]; + struct lsdc_pll *pixpll = &dispipe->pixpll; + struct drm_plane *primary = &dispipe->primary; + struct drm_plane *cursor = &dispipe->cursor; + struct drm_crtc *crtc = &dispipe->crtc; + + dispipe->index = i; + + ret = lsdc_pixpll_init(pixpll, ddev, i); + if (ret) + return ret; + + ret = lsdc_plane_init(ldev, primary, DRM_PLANE_TYPE_PRIMARY, i); + if (ret) + return ret; + + ret = lsdc_plane_init(ldev, cursor, DRM_PLANE_TYPE_CURSOR, i); + if (ret) + return ret; + + /* + * Initial all of the CRTC available, in this way the logical + * crtc index is equal to the hardware crtc index. + * + * display pipe 0 = crtc0 + dvo0 + encoder0 + connector0 + * display pipe 1 = crtc1 + dvo1 + encoder1 + connector1 + */ + ret = lsdc_crtc_init(ddev, crtc, i, primary, cursor); + if (ret) + return ret; + + drm_info(ddev, "display pipe %u initialized\n", i); + } + + drm_mode_config_reset(ddev); + + return 0; +} + +static int lsdc_mode_config_init(struct drm_device *ddev, + const struct lsdc_desc *descp) +{ + int ret; + + ret = drmm_mode_config_init(ddev); + if (ret) + return ret; + + ddev->mode_config.funcs = &lsdc_mode_config_funcs; + ddev->mode_config.min_width = 1; + ddev->mode_config.min_height = 1; + ddev->mode_config.max_width = descp->max_width * LSDC_NUM_CRTC; + ddev->mode_config.max_height = descp->max_height * LSDC_NUM_CRTC; + ddev->mode_config.preferred_depth = 24; + ddev->mode_config.prefer_shadow = descp->has_vram; + + ddev->mode_config.cursor_width = descp->hw_cursor_h; + ddev->mode_config.cursor_height = descp->hw_cursor_h; + + /* max_vblank_count is set on each CRTC */ + ddev->max_vblank_count = 0xffffffff; + + return ret; +} + +/* + * lsdc_detect_chip - a function to tell lsdc variant apart. + */ +static const struct lsdc_desc * +lsdc_detect_chip(struct pci_dev *pdev, const struct pci_device_id *ent) +{ + /* + * Provide a way to false the driver running on new model + * before we have a proper way to tell them apart + */ + if (lsdc_chip == CHIP_LS2K2000) { + dev_info(&pdev->dev, "Override as LS2K2000\n"); + return &dc_in_ls2k2000; + } + + if (lsdc_chip == CHIP_LS2K1000) { + dev_info(&pdev->dev, "Override as LS2K1000\n"); + return &dc_in_ls2k1000; + } + + if (lsdc_chip == CHIP_LS2K0500) { + dev_info(&pdev->dev, "Override as LS2K0500\n"); + return &dc_in_ls2k0500; + } + + if (ent) { + struct lsdc_desc *dc_descp; + + dc_descp = (struct lsdc_desc *)ent->driver_data; + if (dc_descp->chip == CHIP_LS7A1000) + dev_info(&pdev->dev, "LS7A1000 Found revision: %u\n", pdev->revision); + else if (dc_descp->chip == CHIP_LS7A2000) + dev_info(&pdev->dev, "LS7A2000 Found revison: %u\n", pdev->revision); + + return dc_descp; + } + + dev_err(&pdev->dev, "Not known device\n"); + + return NULL; +} + +static int lsdc_get_dedicated_vram(struct lsdc_device *ldev, + const struct lsdc_desc *descp) +{ + struct drm_device *ddev = &ldev->base; + struct pci_dev *gpu; + resource_size_t base, size; + + /* + * The GPU and display controller in LS7A1000/LS7A2000 are separated + * PCIE devices, they are two devices not one. The DC is a pci device, + * but it don't have a dedicate VRAM bar, the BIOS engineer choose to + * assign the VRAM to the gpu device. Sadly, after years application, + * this decision form as a convention for loongson integrate graphics. + * For LS7A1000 and LS7A2000, bar 2 of GPU device contain the VRAM, + * both the GPU and the DC can make use of the VRAM depend on how DRM + * device driver is written. Therefore, we have to do some tricks here. + */ + if (descp->chip == CHIP_LS7A1000) + gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A15, NULL); + else if (descp->chip == CHIP_LS7A2000) + gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7A25, NULL); + + if (!gpu) { + drm_warn(ddev, "No GPU device found\n"); + return -ENODEV; + } + + base = pci_resource_start(gpu, 2); + size = pci_resource_len(gpu, 2); + + ldev->vram_base = base; + ldev->vram_size = size; + + drm_info(ddev, "dedicated vram start: 0x%llx, size: %uMB\n", + (u64)base, (u32)(size >> 20)); + + return 0; +} + +/* + * Loongson display controller is not equipped with IOMMU. + * In order to make this driver work with Loongson SoC, + * you need to either carveout part RAM as VRAM or reserve + * part system ram as vram. Pass the start address and size + * via DT or ACPI. + */ +static int lsdc_of_get_reserved_ram(struct lsdc_device *ldev) +{ + struct drm_device *ddev = &ldev->base; + unsigned long size = 0; + struct device_node *node; + struct resource r; + int ret; + + node = of_parse_phandle(ddev->dev->of_node, "memory-region", 0); + if (!node) { + drm_err(ddev, "No memory-region property or no DT, abort\n"); + return -ENOENT; + } + + ret = of_address_to_resource(node, 0, &r); + of_node_put(node); + if (ret) + return ret; + + size = r.end - r.start + 1; + + ldev->vram_base = r.start; + ldev->vram_size = size; + + drm_info(ddev, "using VRAM carveout: %lx@%pa\n", size, &r.start); + + return 0; +} + +static int +lsdc_remove_conflicting_framebuffers(const struct drm_driver *pdriver, + resource_size_t base, + resource_size_t size) +{ + return drm_aperture_remove_conflicting_framebuffers(base, + size, + false, + pdriver); +} + +static struct lsdc_device * +lsdc_create_device(struct pci_dev *pdev, + const struct pci_device_id *ent, + const struct drm_driver *drv) +{ + struct lsdc_device *ldev; + struct drm_device *ddev; + const struct lsdc_desc *descp; + int ret; + + ldev = devm_drm_dev_alloc(&pdev->dev, drv, struct lsdc_device, base); + if (IS_ERR(ldev)) + return ldev; + + ddev = &ldev->base; + + pci_set_drvdata(pdev, ddev); + + descp = lsdc_detect_chip(pdev, ent); + if (!descp) + return NULL; + + ldev->descp = descp; + + spin_lock_init(&ldev->reglock); + + /* BAR 0 the DC device contains registers */ + ldev->reg_base = pcim_iomap(pdev, 0, 0); + if (!ldev->reg_base) + return ERR_PTR(-EIO); + + if (descp->has_vram && lsdc_has_vram) + ret = lsdc_get_dedicated_vram(ldev, descp); + else + ret = lsdc_of_get_reserved_ram(ldev); + + if (ret) { + drm_err(ddev, "Init VRAM failed: %d\n", ret); + return ERR_PTR(ret); + } + + ret = lsdc_remove_conflicting_framebuffers(drv, + ldev->vram_base, + ldev->vram_size); + if (ret) { + drm_err(ddev, "remove firmware framebuffers failed: %d\n", ret); + return ERR_PTR(ret); + } + + ret = drmm_vram_helper_init(ddev, ldev->vram_base, ldev->vram_size); + if (ret) { + drm_err(ddev, "vram helper init failed: %d\n", ret); + goto err; + } + + ret = lsdc_mode_config_init(ddev, descp); + if (ret) { + drm_dbg(ddev, "%s: %d\n", __func__, ret); + goto err; + } + + ret = lsdc_modeset_init(ldev, descp); + if (ret) + goto err; + + ret = drm_vblank_init(ddev, descp->num_of_crtc); + if (ret) + goto err; + + ret = request_threaded_irq(pdev->irq, + lsdc_get_irq_handler(ldev), + lsdc_irq_thread_handler, + IRQF_ONESHOT, + dev_name(ddev->dev), + ddev); + if (ret) { + drm_err(ddev, "Failed to register lsdc interrupt\n"); + goto err; + } + + drm_kms_helper_poll_init(ddev); + + return ldev; + +err: + return ERR_PTR(ret); +} + +static int lsdc_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *ent) +{ + struct drm_device *ddev; + struct lsdc_device *ldev; + int ret; + + ret = pcim_enable_device(pdev); + if (ret) + return ret; + + pci_set_master(pdev); + + ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(40)); + if (ret) + return ret; + + ldev = lsdc_create_device(pdev, ent, &lsdc_drm_driver); + if (IS_ERR(ldev)) + return PTR_ERR(ldev); + + ddev = &ldev->base; + + ret = drm_dev_register(ddev, 0); + if (ret) + return ret; + + drm_fbdev_generic_setup(ddev, 32); + + return 0; +} + +static void lsdc_pci_remove(struct pci_dev *pdev) +{ + struct drm_device *ddev = pci_get_drvdata(pdev); + + drm_dev_unregister(ddev); + drm_atomic_helper_shutdown(ddev); +} + +static int lsdc_drm_freeze(struct drm_device *ddev) +{ + int error; + + error = drm_mode_config_helper_suspend(ddev); + if (error) + return error; + + pci_save_state(to_pci_dev(ddev->dev)); + + return 0; +} + +static int lsdc_drm_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *ddev = pci_get_drvdata(pdev); + + return drm_mode_config_helper_resume(ddev); +} + +static int lsdc_pm_freeze(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *ddev = pci_get_drvdata(pdev); + + return lsdc_drm_freeze(ddev); +} + +static int lsdc_pm_thaw(struct device *dev) +{ + return lsdc_drm_resume(dev); +} + +static int lsdc_pm_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + int error; + + error = lsdc_pm_freeze(dev); + if (error) + return error; + + pci_save_state(pdev); + /* Shut down the device */ + pci_disable_device(pdev); + pci_set_power_state(pdev, PCI_D3hot); + + return 0; +} + +static int lsdc_pm_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + + if (pcim_enable_device(pdev)) + return -EIO; + + pci_set_power_state(pdev, PCI_D0); + + pci_restore_state(pdev); + + return lsdc_pm_thaw(dev); +} + +static const struct dev_pm_ops lsdc_pm_ops = { + .suspend = lsdc_pm_suspend, + .resume = lsdc_pm_resume, + .freeze = lsdc_pm_freeze, + .thaw = lsdc_pm_thaw, + .poweroff = lsdc_pm_freeze, + .restore = lsdc_pm_resume, +}; + +static const struct pci_device_id lsdc_pciid_list[] = { + {PCI_VENDOR_ID_LOONGSON, 0x7a06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, + (kernel_ulong_t)&dc_in_ls7a1000}, + {PCI_VENDOR_ID_LOONGSON, 0x7a36, PCI_ANY_ID, PCI_ANY_ID, 0, 0, + (kernel_ulong_t)&dc_in_ls7a2000}, + {0, 0, 0, 0, 0, 0, 0} +}; + +static struct pci_driver lsdc_pci_driver = { + .name = DRIVER_NAME, + .id_table = lsdc_pciid_list, + .probe = lsdc_pci_probe, + .remove = lsdc_pci_remove, + .driver.pm = &lsdc_pm_ops, +}; + +static int __init lsdc_module_init(void) +{ + struct pci_dev *pdev = NULL; + + if (drm_firmware_drivers_only()) + return -EINVAL; + + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) { + /* + * Multiple video card workaround: lsdc will always be + * selected as the default boot device by vgaarb subsystem. + */ + if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) { + pr_info("Discrete graphic card detected, abort\n"); + return 0; + } + } + + return pci_register_driver(&lsdc_pci_driver); +} +module_init(lsdc_module_init); + +static void __exit lsdc_module_exit(void) +{ + pci_unregister_driver(&lsdc_pci_driver); +} +module_exit(lsdc_module_exit); + +MODULE_DEVICE_TABLE(pci, lsdc_pciid_list); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.h b/drivers/gpu/drm/lsdc/lsdc_drv.h new file mode 100644 index 000000000000..d67dae0414ca --- /dev/null +++ b/drivers/gpu/drm/lsdc/lsdc_drv.h @@ -0,0 +1,274 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * KMS driver for Loongson display controller + * Copyright (C) 2022 Loongson Corporation + * + * Authors: + * Li Yi <liyi@loongson.cn> + * Li Chenyang <lichenyang@loongson.cn> + * Sui Jingfeng <suijingfeng@loongson.cn> + */ + +#ifndef __LSDC_DRV_H__ +#define __LSDC_DRV_H__ + +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> + +#include <drm/drm_print.h> +#include <drm/drm_device.h> +#include <drm/drm_crtc.h> +#include <drm/drm_plane.h> +#include <drm/drm_connector.h> +#include <drm/drm_encoder.h> +#include <drm/drm_drv.h> +#include <drm/drm_atomic.h> + +#include "lsdc_regs.h" +#include "lsdc_pll.h" + +#define LSDC_NUM_CRTC 2 + +/* + * The display controller in LS7A2000 integrate three loongson self-made + * encoder. Display pipe 0 has a transparent VGA encoder and a HDMI phy, + * they are parallel. Display pipe 1 has only one HDMI phy. + * ______________________ _____________ + * | +-----+ | | | + * | CRTC0 -+--> | VGA | ----> VGA Connector ---> | VGA Monitor |<---+ + * | | +-----+ | |_____________| | + * | | | ______________ | + * | | +------+ | | | | + * | +--> | HDMI | ----> HDMI Connector --> | HDMI Monitor |<--+ + * | +------+ | |______________| | + * | +------+ | | + * | | i2c6 | <-------------------------------------------+ + * | +------+ | + * | | + * | DC in LS7A2000 | + * | | + * | +------+ | + * | | i2c7 | <--------------------------------+ + * | +------+ | | + * | | ______|_______ + * | +------+ | | | + * | CRTC1 ---> | HDMI | ----> HDMI Connector ---> | HDMI Monitor | + * | +------+ | |______________| + * |______________________| + * + * + * The display controller in LS7A1000 integrate two-way DVO, external + * encoder is required except use directly with dpi(rgb888) panel. + * ___________________ _________ + * | -------| | | + * | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | Display | + * | _ _ -------| ^ ^ |_________| + * | | | | | -------| | | + * | |_| |_| | i2c6 <--------+-------------+ + * | -------| + * | _ _ -------| + * | | | | | | i2c7 <--------+-------------+ + * | |_| |_| -------| | | _________ + * | -------| | | | | + * | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | Panel | + * | -------| |_________| + * |___________________| + * + * + * The display controller in LS2K1000. + * ___________________ _________ + * | -------| | | + * | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | Display | + * | _ _ -------| ^ ^ |_________| + * | | | | | | | | + * | |_| |_| | +------+ | + * | <---->| i2c0 |<---------+ + * | DC in LS2K1000 | +------+ + * | _ _ | +------+ + * | | | | | <---->| i2c1 |----------+ + * | |_| |_| | +------+ | _________ + * | -------| | | | | + * | CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> | Panel | + * | -------| |_________| + * |___________________| + * + * + * The display controller in LS2K0500, LS2K0500 has a built-in transparent + * VGA encoder which is connected to display pipe 1(CRTC1). + * ___________________ _________ + * | -------| | | + * | CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | Display | + * | _ _ -------| ^ ^ |_________| + * | | | | | | | | + * | |_| |_| | +------+ | + * | <---->| i2c4 |<---------+ + * | DC in LS2K0500 | +------+ + * | _ _ | +------+ + * | | | | | <---->| i2c5 |-------------------+ + * | |_| |_| | +------+ ______|______ + * | +-----+ | | | + * | CRTC1 --> | VGA | ------------------------> | VGA Monitor | + * | +-----+ | |_____________| + * |___________________| + * + * LS7A1000 and LS7A2000 are bridge chips, has dedicated Video RAM. + * while LS2K2000/LS2K1000/LS2K0500 are SoC, they don't have dediacated + * Video RAM. The dc in LS2K2000 is basicly same with the dc in LS7A1000 + * except that LS2K2000 don't have a video RAM and have only one built-in + * hdmi encoder. + * + * There is only a 1:1 mapping of encoders and connectors for the DC, + * each CRTC have two FB address registers. + */ + +enum loongson_chip_family { + CHIP_UNKNOWN = 0, + CHIP_LS7A1000 = 1, /* North bridge of LS3A3000/LS3A4000/LS3A5000 */ + CHIP_LS7A2000 = 2, /* Update version of LS7A1000, with built-in HDMI encoder */ + CHIP_LS2K0500 = 3, /* Reduced version of LS2K1000, single core */ + CHIP_LS2K1000 = 4, /* 2-Core Mips64r2/LA264 SoC, 64-bit, 1.0 Ghz */ + CHIP_LS2K2000 = 5, /* 2-Core 64-bit LA364 SoC, 1.2 ~ 1.5 Ghz */ + CHIP_LAST, +}; + +struct lsdc_desc { + enum loongson_chip_family chip; + u32 num_of_crtc; + u32 max_pixel_clk; + u32 max_width; + u32 max_height; + u32 num_of_hw_cursor; + u32 hw_cursor_w; + u32 hw_cursor_h; + u32 pitch_align; /* DMA alignment constraint */ + u64 mc_bits; /* physical address bus bit width */ + bool has_vblank_counter; + bool has_builtin_i2c; + bool has_vram; + bool has_hpd_reg; + bool is_soc; +}; + +struct lsdc_i2c { + struct i2c_adapter adapter; + struct i2c_algo_bit_data bit; + struct drm_device *ddev; + void __iomem *reg_base; + void __iomem *dir_reg; + void __iomem *dat_reg; + /* pin bit mask */ + u8 sda; + u8 scl; +}; + +/* + * struct lsdc_display_pipe - Abstraction of hardware display pipeline. + * @crtc: CRTC control structure + * @primary: framebuffer plane control structure + * @cursor: cursor plane control structure + * @output: output control structure of this display pipe bind + * @pixpll: pixel pll control structure + * @index: the index corresponding to the hardware display pipe + */ +struct lsdc_display_pipe { + struct drm_crtc crtc; + struct drm_plane primary; + struct drm_plane cursor; + struct drm_encoder encoder; + struct drm_connector connector; + struct lsdc_pll pixpll; + struct lsdc_i2c *li2c; + int index; +}; + +static inline struct lsdc_display_pipe * +crtc_to_display_pipe(struct drm_crtc *crtc) +{ + return container_of(crtc, struct lsdc_display_pipe, crtc); +} + +static inline struct lsdc_display_pipe * +cursor_to_display_pipe(struct drm_plane *cursor) +{ + return container_of(cursor, struct lsdc_display_pipe, cursor); +} + +static inline struct lsdc_display_pipe * +connector_to_display_pipe(struct drm_connector *conn) +{ + return container_of(conn, struct lsdc_display_pipe, connector); +} + +static inline struct lsdc_display_pipe * +encoder_to_display_pipe(struct drm_encoder *enc) +{ + return container_of(enc, struct lsdc_display_pipe, encoder); +} + +struct lsdc_crtc_state { + struct drm_crtc_state base; + struct lsdc_pll_parms pparms; +}; + +struct lsdc_device { + struct drm_device base; + + /* @reglock: protects concurrent register access */ + spinlock_t reglock; + void __iomem *reg_base; + void __iomem *vram; + resource_size_t vram_base; + resource_size_t vram_size; + u64 mc_mask; + + struct lsdc_display_pipe dispipe[LSDC_NUM_CRTC]; + + /* @num_output: count the number of active display pipe */ + unsigned int num_output; + + /* @descp: features description of the DC variant */ + const struct lsdc_desc *descp; + + u32 irq_status; +}; + +static inline struct lsdc_device * +to_lsdc(struct drm_device *ddev) +{ + return container_of(ddev, struct lsdc_device, base); +} + +static inline struct lsdc_crtc_state * +to_lsdc_crtc_state(struct drm_crtc_state *base) +{ + return container_of(base, struct lsdc_crtc_state, base); +} + +void lsdc_debugfs_init(struct drm_minor *minor); + +int lsdc_crtc_init(struct drm_device *ddev, + struct drm_crtc *crtc, + unsigned int index, + struct drm_plane *primary, + struct drm_plane *cursor); + +int lsdc_plane_init(struct lsdc_device *ldev, + struct drm_plane *plane, + enum drm_plane_type type, + unsigned int index); + +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index); + +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, + void *base, + unsigned int index); + +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, int index); + +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg); +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev); + +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset); +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 val); + +#endif diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c b/drivers/gpu/drm/lsdc/lsdc_i2c.c new file mode 100644 index 000000000000..b380098409ca --- /dev/null +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c @@ -0,0 +1,201 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <linux/i2c.h> +#include <drm/drm_managed.h> +#include "lsdc_drv.h" +#include "lsdc_regs.h" + +/* + * ls7a_gpio_i2c_set - set the state of a gpio pin indicated by mask + * @mask: gpio pin mask + * @state: "0" for low, "1" for high + */ +static void ls7a_gpio_i2c_set(struct lsdc_i2c * const li2c, int mask, int state) +{ + struct lsdc_device *ldev = to_lsdc(li2c->ddev); + unsigned long flags; + u8 val; + + spin_lock_irqsave(&ldev->reglock, flags); + + if (state) { + /* + * Setting this pin as input directly, write 1 for Input. + * The external pull-up resistor will pull the level up + */ + val = readb(li2c->dir_reg); + val |= mask; + writeb(val, li2c->dir_reg); + } else { + /* First set this pin as output, write 0 for Output */ + val = readb(li2c->dir_reg); + val &= ~mask; + writeb(val, li2c->dir_reg); + + /* Then, make this pin output 0 */ + val = readb(li2c->dat_reg); + val &= ~mask; + writeb(val, li2c->dat_reg); + } + + spin_unlock_irqrestore(&ldev->reglock, flags); +} + +/* + * ls7a_gpio_i2c_get - read value back from the gpio pin indicated by mask + * @mask: gpio pin mask + * return "0" for low, "1" for high + */ +static int ls7a_gpio_i2c_get(struct lsdc_i2c * const li2c, int mask) +{ + struct lsdc_device *ldev = to_lsdc(li2c->ddev); + unsigned long flags; + u8 val; + + spin_lock_irqsave(&ldev->reglock, flags); + + /* First set this pin as input */ + val = readb(li2c->dir_reg); + val |= mask; + writeb(val, li2c->dir_reg); + + /* Then get level state from this pin */ + val = readb(li2c->dat_reg); + + spin_unlock_irqrestore(&ldev->reglock, flags); + + return (val & mask) ? 1 : 0; +} + +static void ls7a_i2c_set_sda(void *i2c, int state) +{ + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; + /* set state on the li2c->sda pin */ + return ls7a_gpio_i2c_set(li2c, li2c->sda, state); +} + +static void ls7a_i2c_set_scl(void *i2c, int state) +{ + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; + /* set state on the li2c->scl pin */ + return ls7a_gpio_i2c_set(li2c, li2c->scl, state); +} + +static int ls7a_i2c_get_sda(void *i2c) +{ + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; + /* read value from the li2c->sda pin */ + return ls7a_gpio_i2c_get(li2c, li2c->sda); +} + +static int ls7a_i2c_get_scl(void *i2c) +{ + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; + /* read the value from the li2c->scl pin */ + return ls7a_gpio_i2c_get(li2c, li2c->scl); +} + +static void lsdc_destroy_i2c(struct drm_device *ddev, void *data) +{ + struct lsdc_i2c *li2c = (struct lsdc_i2c *)data; + + if (li2c) { + i2c_del_adapter(&li2c->adapter); + kfree(li2c); + } +} + +/* + * The DC in ls7a1000/ls7a2000 have builtin gpio hardware + * + * @base: gpio reg base + * @index: output channel index, 0 for DVO0, 1 for DVO1 + */ +struct lsdc_i2c *lsdc_create_i2c_chan(struct drm_device *ddev, + void *base, + unsigned int index) +{ + struct i2c_adapter *adapter; + struct lsdc_i2c *li2c; + int ret; + + li2c = kzalloc(sizeof(*li2c), GFP_KERNEL); + if (!li2c) + return ERR_PTR(-ENOMEM); + + if (index == 0) { + li2c->sda = 0x01; /* pin 0 */ + li2c->scl = 0x02; /* pin 1 */ + } else if (index == 1) { + li2c->sda = 0x04; /* pin 2 */ + li2c->scl = 0x08; /* pin 3 */ + } + + li2c->reg_base = base; + li2c->ddev = ddev; + li2c->dir_reg = li2c->reg_base + LS7A_DC_GPIO_DIR_REG; + li2c->dat_reg = li2c->reg_base + LS7A_DC_GPIO_DAT_REG; + + li2c->bit.setsda = ls7a_i2c_set_sda; + li2c->bit.setscl = ls7a_i2c_set_scl; + li2c->bit.getsda = ls7a_i2c_get_sda; + li2c->bit.getscl = ls7a_i2c_get_scl; + li2c->bit.udelay = 5; + li2c->bit.timeout = usecs_to_jiffies(2200); + li2c->bit.data = li2c; + + adapter = &li2c->adapter; + adapter->algo_data = &li2c->bit; + adapter->owner = THIS_MODULE; + adapter->class = I2C_CLASS_DDC; + adapter->dev.parent = ddev->dev; + adapter->nr = -1; + + snprintf(adapter->name, sizeof(adapter->name), "lsdc-i2c%u", index); + + i2c_set_adapdata(adapter, li2c); + + ret = i2c_bit_add_bus(adapter); + if (ret) { + kfree(li2c); + return ERR_PTR(ret); + } + + ret = drmm_add_action_or_reset(ddev, lsdc_destroy_i2c, li2c); + if (ret) + return NULL; + + drm_info(ddev, "%s(sda=%u, scl=%u) created for connector-%u\n", + adapter->name, li2c->sda, li2c->scl, index); + + return li2c; +} + +static int lsdc_get_i2c_id(struct lsdc_device *ldev, int index) +{ + const struct lsdc_desc *descp = ldev->descp; + + if (descp->chip == CHIP_LS2K1000) + return index; + + if (descp->chip == CHIP_LS2K0500) + return index + 2; + + return index; +} + +struct i2c_adapter *lsdc_get_i2c_adapter(struct lsdc_device *ldev, + int index) +{ + const struct lsdc_desc *descp = ldev->descp; + struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; + + if (descp->has_builtin_i2c) { + struct lsdc_i2c *li2c = dispipe->li2c; + + if (li2c) + return &li2c->adapter; + } + + return i2c_get_adapter(lsdc_get_i2c_id(ldev, index)); +} diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.c b/drivers/gpu/drm/lsdc/lsdc_irq.c new file mode 100644 index 000000000000..71c85f09bb6f --- /dev/null +++ b/drivers/gpu/drm/lsdc/lsdc_irq.c @@ -0,0 +1,86 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <drm/drm_vblank.h> +#include "lsdc_drv.h" +#include "lsdc_regs.h" + +/* + * For the DC in ls7a2000, clearing interrupt status is achieved by + * write "1" to LSDC_INT_REG, For the DC in ls7a1000, ls2k1000 and + * ls2k0500, clearing interrupt status is achieved by write "0" to + * LSDC_INT_REG. Two different hardware engineer of Loongson modify + * it as their will. + */ + +/* For the DC in ls7a2000 */ +static irqreturn_t lsdc_irq_handler(int irq, void *arg) +{ + struct drm_device *ddev = arg; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 val; + + /* Read the interrupt status */ + val = lsdc_rreg32(ldev, LSDC_INT_REG); + if ((val & INT_STATUS_MASK) == 0) { + drm_warn(ddev, "no interrupt occurs\n"); + return IRQ_NONE; + } + + ldev->irq_status = val; + + /* write "1" to clear the interrupt status */ + lsdc_wreg32(ldev, LSDC_INT_REG, val); + + return IRQ_WAKE_THREAD; +} + +/* For the DC in ls7a1000, ls2k1000 and ls2k0500 */ +static irqreturn_t lsdc_irq_handler_legacy(int irq, void *arg) +{ + struct drm_device *ddev = arg; + struct lsdc_device *ldev = to_lsdc(ddev); + u32 val; + + /* Read the interrupt status */ + val = lsdc_rreg32(ldev, LSDC_INT_REG); + if ((val & INT_STATUS_MASK) == 0) { + drm_warn(ddev, "no interrupt occurs\n"); + return IRQ_NONE; + } + + ldev->irq_status = val; + + /* write "0" to clear the interrupt status */ + lsdc_wreg32(ldev, LSDC_INT_REG, val & ~INT_STATUS_MASK); + + return IRQ_WAKE_THREAD; +} + +irq_handler_t lsdc_get_irq_handler(struct lsdc_device *ldev) +{ + const struct lsdc_desc *descp = ldev->descp; + + if (descp->chip == CHIP_LS7A2000) + return lsdc_irq_handler; + + return lsdc_irq_handler_legacy; +} + +irqreturn_t lsdc_irq_thread_handler(int irq, void *arg) +{ + struct drm_device *ddev = arg; + struct lsdc_device *ldev = to_lsdc(ddev); + struct drm_crtc *crtc; + + if (ldev->irq_status & INT_CRTC0_VSYNC) { + crtc = drm_crtc_from_index(ddev, 0); + drm_crtc_handle_vblank(crtc); + } + + if (ldev->irq_status & INT_CRTC1_VSYNC) { + crtc = drm_crtc_from_index(ddev, 1); + drm_crtc_handle_vblank(crtc); + } + + return IRQ_HANDLED; +} diff --git a/drivers/gpu/drm/lsdc/lsdc_output.c b/drivers/gpu/drm/lsdc/lsdc_output.c new file mode 100644 index 000000000000..d4bc7666d9ea --- /dev/null +++ b/drivers/gpu/drm/lsdc/lsdc_output.c @@ -0,0 +1,452 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <drm/drm_edid.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_connector.h> +#include "lsdc_drv.h" + +static int lsdc_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 enum drm_connector_status +lsdc_unknown_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; + } else { + if (connector->connector_type == DRM_MODE_CONNECTOR_DPI) + return connector_status_connected; + + if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) + return connector_status_connected; + } + + return connector_status_unknown; +} + +static enum drm_connector_status +lsdc_hdmi_connector_detect(struct drm_connector *connector, bool force) +{ + struct lsdc_display_pipe *pipe = connector_to_display_pipe(connector); + struct lsdc_device *ldev = to_lsdc(connector->dev); + u32 val; + + val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); + + if (pipe->index == 0) { + if (val & HDMI0_HPD_FLAG) + return connector_status_connected; + } + + if (pipe->index == 1) { + if (val & HDMI1_HPD_FLAG) + return connector_status_connected; + } + + return connector_status_disconnected; +} + +static enum drm_connector_status +lsdc_hdmi_vga_connector_detect(struct drm_connector *connector, bool force) +{ + struct lsdc_display_pipe *pipe = connector_to_display_pipe(connector); + struct drm_device *ddev = connector->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + struct i2c_adapter *ddc; + u32 val; + + val = lsdc_rreg32(ldev, LSDC_HDMI_HPD_STATUS_REG); + + if (pipe->index == 1) { + if (val & HDMI1_HPD_FLAG) + return connector_status_connected; + + return connector_status_disconnected; + } + + if (pipe->index == 0) { + if (val & HDMI0_HPD_FLAG) + return connector_status_connected; + + ddc = connector->ddc; + if (ddc) { + if (drm_probe_ddc(ddc)) + return connector_status_connected; + + return connector_status_disconnected; + } + } + + return connector_status_unknown; +} + +static struct drm_encoder * +lsdc_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 lsdc_connector_helpers = { + .atomic_best_encoder = lsdc_connector_get_best_encoder, + .get_modes = lsdc_get_modes, +}; + +static const struct drm_connector_funcs lsdc_unknown_connector_funcs = { + .detect = lsdc_unknown_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 const struct drm_connector_funcs lsdc_hdmi_vga_connector_funcs = { + .detect = lsdc_hdmi_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, +}; + +static const struct drm_connector_funcs lsdc_hdmi_connector_funcs = { + .detect = lsdc_hdmi_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 const struct drm_connector_funcs * +lsdc_get_connector_func(struct lsdc_device *ldev, unsigned int index) +{ + const struct lsdc_desc *descp = ldev->descp; + + if (descp->chip == CHIP_LS7A2000) { + if (index == 0) + return &lsdc_hdmi_vga_connector_funcs; + + if (index == 1) + return &lsdc_hdmi_connector_funcs; + } + + return &lsdc_unknown_connector_funcs; +} + +/* + * we provide a default support before DT/VBIOS is supported + */ +static int lsdc_get_encoder_type(struct lsdc_device *ldev, + unsigned int index) +{ + const struct lsdc_desc *descp = ldev->descp; + + if (descp->chip == CHIP_LS7A2000) { + if (index == 0) + return DRM_MODE_ENCODER_DAC; + if (index == 1) + return DRM_MODE_ENCODER_TMDS; + } + + if (descp->chip == CHIP_LS7A1000 || descp->chip == CHIP_LS2K1000) { + if (index == 0) + return DRM_MODE_ENCODER_DPI; + if (index == 1) + return DRM_MODE_ENCODER_DPI; + } + + if (descp->chip == CHIP_LS2K0500) { + if (index == 0) + return DRM_MODE_ENCODER_DPI; + if (index == 1) + return DRM_MODE_ENCODER_DAC; + } + + return DRM_MODE_ENCODER_NONE; +} + +/* + * provide a default before DT/VBIOS support is upstreamed + */ +static int lsdc_get_connector_type(struct lsdc_device *ldev, + unsigned int index) +{ + const struct lsdc_desc *descp = ldev->descp; + + if (descp->chip == CHIP_LS7A2000) { + if (index == 0) + return DRM_MODE_CONNECTOR_VGA; + if (index == 1) + return DRM_MODE_CONNECTOR_HDMIA; + } + + if (descp->chip == CHIP_LS7A1000 || descp->chip == CHIP_LS2K1000) { + if (index == 0) + return DRM_MODE_CONNECTOR_DPI; + if (index == 1) + return DRM_MODE_CONNECTOR_DPI; + } + + if (descp->chip == CHIP_LS2K0500) { + if (index == 0) + return DRM_MODE_CONNECTOR_DPI; + if (index == 1) + return DRM_MODE_CONNECTOR_VGA; + } + + return DRM_MODE_CONNECTOR_Unknown; +} + +static void lsdc_hdmi_disable(struct drm_encoder *encoder) +{ + 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; + + if (index == 0) + val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG); + else if (index == 1) + val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG); + + val &= ~HDMI_PHY_EN; + + if (index == 0) + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); + else if (index == 1) + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); + + drm_dbg(ddev, "HDMI-%u disabled\n", index); +} + +static void lsdc_hdmi_enable(struct drm_encoder *encoder) +{ + 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; + + /* we are using software gpio emulated i2c */ + val = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | + HDMI_PACKET_EN | HDMI_INTERFACE_EN; + + if (index == 0) { + /* Enable HDMI-0 */ + lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); + + lsdc_wreg32(ldev, LSDC_HDMI0_ZONE_REG, 0x00400040); + } else if (index == 1) { + /* Enable HDMI-1 */ + lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); + + lsdc_wreg32(ldev, LSDC_HDMI1_ZONE_REG, 0x00400040); + } + + 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; + + if (index == 0) + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_CTRL_REG, val); + else if (index == 1) + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_CTRL_REG, val); + + drm_dbg(ddev, "HDMI-%u enabled\n", index); +} + +/* + * Fout = M * Fin + * + * M = (4 * LF) / (IDF * ODF) + * + * Loongson HDMI require M = 10 + */ +static void lsdc_hdmi_phy_pll_config(struct lsdc_device *ldev, + int index, + int clock) +{ + struct drm_device *ddev = &ldev->base; + int count = 0; + u32 val; + + /* disable phy pll */ + if (index == 0) + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, 0x0); + else if (index == 1) + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, 0x0); + + /* + * 10 = (4 * 40) / (8 * 2) + */ + val = (8 << HDMI_PLL_IDF_SHIFT) | + (40 << HDMI_PLL_LF_SHIFT) | + (1 << HDMI_PLL_ODF_SHIFT) | + HDMI_PLL_ENABLE; + + drm_dbg(ddev, "HDMI-%u clock: %d\n", index, clock); + + if (index == 0) + lsdc_wreg32(ldev, LSDC_HDMI0_PHY_PLL_REG, val); + else if (index == 1) + lsdc_wreg32(ldev, LSDC_HDMI1_PHY_PLL_REG, val); + + /* wait hdmi phy pll lock */ + do { + if (index == 0) + val = lsdc_rreg32(ldev, LSDC_HDMI0_PHY_PLL_REG); + else if (index == 1) + val = lsdc_rreg32(ldev, LSDC_HDMI1_PHY_PLL_REG); + + ++count; + + if (val & HDMI_PLL_LOCKED) { + drm_dbg(ddev, "Setting HDMI-%u PLL success(take %d cycles)\n", + index, count); + break; + } + } while (count < 1000); +} + +static void lsdc_hdmi_atomic_mode_set(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_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; + struct drm_display_mode *mode = &crtc_state->mode; + + lsdc_hdmi_phy_pll_config(ldev, index, mode->clock); + + drm_dbg(ddev, "HDMI-%u modeset\n", index); +} + +static const struct drm_encoder_helper_funcs lsdc_hdmi_helper_funcs = { + .disable = lsdc_hdmi_disable, + .enable = lsdc_hdmi_enable, + .atomic_mode_set = lsdc_hdmi_atomic_mode_set, +}; + +static void lsdc_hdmi_reset(struct drm_encoder *encoder) +{ + 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 = HDMI_CTL_PERIOD_MODE | HDMI_AUDIO_EN | + HDMI_PACKET_EN | HDMI_INTERFACE_EN; + + lsdc_wreg32(ldev, LSDC_HDMI0_CTRL_REG, val); + lsdc_wreg32(ldev, LSDC_HDMI1_CTRL_REG, val); + + drm_dbg(ddev, "HDMI-%u Reset\n", index); +} + +static const struct drm_encoder_funcs lsdc_encoder_funcs = { + .reset = lsdc_hdmi_reset, + .destroy = drm_encoder_cleanup, +}; + +int lsdc_create_output(struct lsdc_device *ldev, unsigned int index) +{ + const struct lsdc_desc *descp = ldev->descp; + struct drm_device *ddev = &ldev->base; + struct lsdc_display_pipe *dispipe = &ldev->dispipe[index]; + struct drm_encoder *encoder = &dispipe->encoder; + struct drm_connector *connector = &dispipe->connector; + struct i2c_adapter *ddc = NULL; + struct lsdc_i2c *li2c; + int ret; + + ret = drm_encoder_init(ddev, + encoder, + &lsdc_encoder_funcs, + lsdc_get_encoder_type(ldev, index), + "encoder-%u", + index); + if (ret) { + drm_err(ddev, "Failed to init encoder: %d\n", ret); + return ret; + } + + encoder->possible_crtcs = BIT(index); + + if (descp->chip == CHIP_LS7A2000) + drm_encoder_helper_add(encoder, &lsdc_hdmi_helper_funcs); + + if (descp->has_builtin_i2c) { + li2c = lsdc_create_i2c_chan(ddev, ldev->reg_base, index); + if (IS_ERR(li2c)) + return PTR_ERR(li2c); + + dispipe->li2c = li2c; + + ddc = &li2c->adapter; + } else { + ddc = lsdc_get_i2c_adapter(ldev, index); + if (IS_ERR(ddc)) { + drm_err(ddev, "Error get ddc for output-%u\n", index); + return PTR_ERR(ddc); + } + } + + ret = drm_connector_init_with_ddc(ddev, + connector, + lsdc_get_connector_func(ldev, index), + lsdc_get_connector_type(ldev, index), + ddc); + if (ret) { + drm_err(ddev, "Init connector-%d failed\n", index); + return ret; + } + + drm_connector_helper_add(connector, &lsdc_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; + + ldev->num_output++; + + drm_info(ddev, "output-%u initialized\n", index); + + return 0; +} diff --git a/drivers/gpu/drm/lsdc/lsdc_plane.c b/drivers/gpu/drm/lsdc/lsdc_plane.c new file mode 100644 index 000000000000..0f779c97d53b --- /dev/null +++ b/drivers/gpu/drm/lsdc/lsdc_plane.c @@ -0,0 +1,443 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include <drm/drm_atomic.h> +#include <drm/drm_atomic_helper.h> +#include <drm/drm_framebuffer.h> +#include <drm/drm_plane_helper.h> +#include <drm/drm_gem_vram_helper.h> +#include "lsdc_drv.h" +#include "lsdc_regs.h" +#include "lsdc_pll.h" + +static const u32 lsdc_primary_formats[] = { + DRM_FORMAT_XRGB8888, + DRM_FORMAT_ARGB8888, +}; + +static const u32 lsdc_cursor_formats[] = { + DRM_FORMAT_ARGB8888, +}; + +static const u64 lsdc_fb_format_modifiers[] = { + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +static void lsdc_update_fb_format(struct lsdc_device *ldev, + struct drm_crtc *crtc, + const struct drm_format_info *fmt_info) +{ + unsigned int index = drm_crtc_index(crtc); + u32 val; + u32 fmt; + + switch (fmt_info->format) { + case DRM_FORMAT_XRGB8888: + fmt = LSDC_PF_XRGB8888; + break; + case DRM_FORMAT_ARGB8888: + fmt = LSDC_PF_XRGB8888; + break; + default: + fmt = LSDC_PF_XRGB8888; + break; + } + + if (index == 0) { + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + val = (val & ~CFG_PIX_FMT_MASK) | fmt; + lsdc_wreg32(ldev, LSDC_CRTC0_CFG_REG, val); + } else if (index == 1) { + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + val = (val & ~CFG_PIX_FMT_MASK) | fmt; + lsdc_wreg32(ldev, LSDC_CRTC1_CFG_REG, val); + } +} + +static void lsdc_update_fb_start_addr(struct lsdc_device *ldev, + struct drm_crtc *crtc, + u64 paddr) +{ + struct drm_device *ddev = &ldev->base; + unsigned int index = drm_crtc_index(crtc); + u32 lo_addr_reg; + u32 hi_addr_reg; + u32 val; + + /* + * Find which framebuffer address register should update. + * if FB_ADDR0_REG is in using, we write the address to FB_ADDR0_REG, + * if FB_ADDR1_REG is in using, we write the address to FB_ADDR1_REG + * for each CRTC, the switch using one fb register to another is + * trigger by triggered by set CFG_PAGE_FLIP bit of LSDC_CRTCx_CFG_REG + */ + if (index == 0) { + val = lsdc_rreg32(ldev, LSDC_CRTC0_CFG_REG); + if (val & CFG_FB_IN_USING) { + lo_addr_reg = LSDC_CRTC0_FB1_LO_ADDR_REG; + hi_addr_reg = LSDC_CRTC0_FB1_HI_ADDR_REG; + drm_dbg(ddev, "Currently, FB1 is in using by CRTC-0\n"); + } else { + lo_addr_reg = LSDC_CRTC0_FB0_LO_ADDR_REG; + hi_addr_reg = LSDC_CRTC0_FB0_HI_ADDR_REG; + drm_dbg(ddev, "Currently, FB0 is in using by CRTC-0\n"); + } + } else if (index == 1) { + val = lsdc_rreg32(ldev, LSDC_CRTC1_CFG_REG); + if (val & CFG_FB_IN_USING) { + lo_addr_reg = LSDC_CRTC1_FB1_LO_ADDR_REG; + hi_addr_reg = LSDC_CRTC1_FB1_HI_ADDR_REG; + drm_dbg(ddev, "Currently, FB1 is in using by CRTC-1\n"); + } else { + lo_addr_reg = LSDC_CRTC1_FB0_LO_ADDR_REG; + hi_addr_reg = LSDC_CRTC1_FB0_HI_ADDR_REG; + drm_dbg(ddev, "Currently, FB0 is in using by CRTC-1\n"); + } + } + + /* 40-bit width physical address bus */ + lsdc_wreg32(ldev, lo_addr_reg, paddr); + lsdc_wreg32(ldev, hi_addr_reg, (paddr >> 32) & 0xFF); + + drm_dbg(ddev, "CRTC-%u scantout from 0x%llx\n", index, paddr); +} + +static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb, + struct drm_plane_state *state, + unsigned int plane) +{ + unsigned int offset = fb->offsets[plane]; + + offset += fb->format->cpp[plane] * (state->src_x >> 16); + offset += fb->pitches[plane] * (state->src_y >> 16); + + return offset; +} + +static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb) +{ + struct drm_gem_vram_object *gbo; + s64 gpu_addr; + + gbo = drm_gem_vram_of_gem(fb->obj[0]); + gpu_addr = drm_gem_vram_offset(gbo); + + return gpu_addr; +} + +static int lsdc_primary_plane_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_crtc *crtc = plane_state->crtc; + struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + + if (!crtc) + return 0; + + return drm_atomic_helper_check_plane_state(plane_state, + crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + false, + true); +} + +static void lsdc_update_fb_stride(struct lsdc_device *ldev, + struct drm_crtc *crtc, + unsigned int stride) +{ + unsigned int index = drm_crtc_index(crtc); + + if (index == 0) + lsdc_wreg32(ldev, LSDC_CRTC0_STRIDE_REG, stride); + else if (index == 1) + lsdc_wreg32(ldev, LSDC_CRTC1_STRIDE_REG, stride); + + drm_dbg(crtc->dev, "update stride to %u\n", stride); +} + +static void lsdc_primary_plane_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct lsdc_device *ldev = to_lsdc(plane->dev); + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_crtc *crtc = new_plane_state->crtc; + struct drm_framebuffer *fb = new_plane_state->fb; + u32 fb_offset = lsdc_get_fb_offset(fb, new_plane_state, 0); + dma_addr_t fb_addr; + s64 gpu_addr; + + gpu_addr = lsdc_get_vram_bo_offset(fb); + if (gpu_addr < 0) + return; + + fb_addr = ldev->vram_base + gpu_addr + fb_offset; + + lsdc_update_fb_start_addr(ldev, crtc, fb_addr); + + lsdc_update_fb_stride(ldev, crtc, fb->pitches[0]); + + lsdc_update_fb_format(ldev, crtc, fb->format); +} + +static void lsdc_primary_plane_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + drm_dbg(plane->dev, "%s disabled\n", plane->name); +} + +static const struct drm_plane_helper_funcs lsdc_primary_plane_helpers = { + .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, + .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, + .atomic_check = lsdc_primary_plane_atomic_check, + .atomic_update = lsdc_primary_plane_atomic_update, + .atomic_disable = lsdc_primary_plane_atomic_disable, +}; + +static int lsdc_cursor_atomic_check(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_framebuffer *fb = new_plane_state->fb; + struct drm_crtc *crtc = new_plane_state->crtc; + struct drm_crtc_state *crtc_state; + int ret; + + /* no need for further checks if the plane is being disabled */ + if (!crtc || !fb) + return 0; + + if (!new_plane_state->visible) + return 0; + + crtc_state = drm_atomic_get_new_crtc_state(state, + new_plane_state->crtc); + + ret = drm_atomic_helper_check_plane_state(new_plane_state, + crtc_state, + DRM_PLANE_NO_SCALING, + DRM_PLANE_NO_SCALING, + true, + true); + + return ret; +} + +/* + * There is only one hardware cursor in ls7a1000, ls2k1000 and ls2k0500. + * we made it shared by the two CRTC, which can satisfy peoples who use + * double screen extend mode only. On clone screen usage case, the cursor + * on display pipe 1 will not be able to display. + * + * Update location of the cursor, attach it to CRTC0 or CRTC1 on the runtime. + */ +static void lsdc_cursor_update_location_quirks(struct lsdc_device *ldev, + struct drm_crtc *crtc) +{ + u32 val = CURSOR_FORMAT_ARGB8888; + + /* + * If bit 4 of LSDC_CURSOR0_CFG_REG is 1, then the cursor will be + * locate at CRTC-1, if bit 4 of LSDC_CURSOR0_CFG_REG is 0, then + * the cursor will be locate at CRTC-0. The cursor is alway on the + * top of the primary. Compositing the primary plane and cursor + * plane is automatically done by hardware. + */ + if (drm_crtc_index(crtc)) + val |= CURSOR_LOCATION; + + lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, val); +} + +/* update the position of the cursor */ +static void lsdc_cursor_update_position_quirks(struct lsdc_device *ldev, + int x, + int y) +{ + if (x < 0) + x = 0; + + if (y < 0) + y = 0; + + lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); +} + +static void lsdc_cursor_atomic_update_quirks(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_device *ddev = plane->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); + struct drm_framebuffer *new_fb = new_plane_state->fb; + struct drm_framebuffer *old_fb = old_plane_state->fb; + + if (new_fb != old_fb) { + s64 offset = lsdc_get_vram_bo_offset(new_fb); + u64 cursor_addr = ldev->vram_base + offset; + + drm_dbg_kms(ddev, "%s offset: %llx\n", plane->name, offset); + + lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); + lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >> 32) & 0xFF); + } + + lsdc_cursor_update_position_quirks(ldev, new_plane_state->crtc_x, new_plane_state->crtc_y); + + lsdc_cursor_update_location_quirks(ldev, new_plane_state->crtc); +} + +/* update the format, size and location of the cursor */ +static void lsdc_cursor_atomic_update(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_device *ddev = plane->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + struct lsdc_display_pipe *dispipe = cursor_to_display_pipe(plane); + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); + struct drm_framebuffer *new_fb = new_plane_state->fb; + int x = new_plane_state->crtc_x; + int y = new_plane_state->crtc_y; + u32 conf = CURSOR_FORMAT_ARGB8888 | CURSOR_SIZE_64X64; + u64 cursor_addr = ldev->vram_base + lsdc_get_vram_bo_offset(new_fb); + + if (x < 0) + x = 0; + + if (y < 0) + y = 0; + + if (dispipe->index == 0) { + lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_HI_REG, (cursor_addr >> 32) & 0xFF); + lsdc_wreg32(ldev, LSDC_CURSOR0_ADDR_LO_REG, cursor_addr); + /* Attach Cursor-0 to CRTC-0 */ + lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, conf & ~CURSOR_LOCATION); + lsdc_wreg32(ldev, LSDC_CURSOR0_POSITION_REG, (y << 16) | x); + return; + } + + if (dispipe->index == 1) { + lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_HI_REG, (cursor_addr >> 32) & 0xFF); + lsdc_wreg32(ldev, LSDC_CURSOR1_ADDR_LO_REG, cursor_addr); + /* Attach Cursor-1 to CRTC-1 */ + lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, conf | CURSOR_LOCATION); + lsdc_wreg32(ldev, LSDC_CURSOR1_POSITION_REG, (y << 16) | x); + return; + } +} + +static void lsdc_cursor_atomic_disable_quirks(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_device *ddev = plane->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + + /* Set the format to 0 actually display the cursor */ + lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); + + drm_dbg(ddev, "%s disabled\n", plane->name); +} + +static void lsdc_cursor_atomic_disable(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_device *ddev = plane->dev; + struct lsdc_device *ldev = to_lsdc(ddev); + struct lsdc_display_pipe *dispipe = cursor_to_display_pipe(plane); + + if (dispipe->index == 0) + lsdc_wreg32(ldev, LSDC_CURSOR0_CFG_REG, 0); + else if (dispipe->index == 1) + lsdc_wreg32(ldev, LSDC_CURSOR1_CFG_REG, 0); + + drm_dbg(ddev, "%s disabled\n", plane->name); +} + +static const struct drm_plane_helper_funcs lsdc_cursor_helpers_quirk = { + .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, + .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, + .atomic_check = lsdc_cursor_atomic_check, + .atomic_update = lsdc_cursor_atomic_update_quirks, + .atomic_disable = lsdc_cursor_atomic_disable_quirks, +}; + +static const struct drm_plane_helper_funcs lsdc_cursor_plane_helpers = { + .prepare_fb = drm_gem_vram_plane_helper_prepare_fb, + .cleanup_fb = drm_gem_vram_plane_helper_cleanup_fb, + .atomic_check = lsdc_cursor_atomic_check, + .atomic_update = lsdc_cursor_atomic_update, + .atomic_disable = lsdc_cursor_atomic_disable, +}; + +static const struct drm_plane_funcs lsdc_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = drm_plane_cleanup, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static const struct drm_plane_helper_funcs * +lsdc_get_cursor_helper_funcs(const struct lsdc_desc *descp) +{ + if (descp->chip == CHIP_LS7A2000) + return &lsdc_cursor_plane_helpers; + + return &lsdc_cursor_helpers_quirk; +} + +int lsdc_plane_init(struct lsdc_device *ldev, + struct drm_plane *plane, + enum drm_plane_type type, + unsigned int index) +{ + const struct lsdc_desc *descp = ldev->descp; + struct drm_device *ddev = &ldev->base; + unsigned int format_count; + const u32 *formats; + const char *name; + int ret; + + switch (type) { + case DRM_PLANE_TYPE_PRIMARY: + formats = lsdc_primary_formats; + format_count = ARRAY_SIZE(lsdc_primary_formats); + name = "primary-%u"; + break; + case DRM_PLANE_TYPE_CURSOR: + formats = lsdc_cursor_formats; + format_count = ARRAY_SIZE(lsdc_cursor_formats); + name = "cursor-%u"; + break; + case DRM_PLANE_TYPE_OVERLAY: + drm_err(ddev, "overlay plane is not supported\n"); + break; + } + + ret = drm_universal_plane_init(ddev, plane, 1 << index, + &lsdc_plane_funcs, + formats, format_count, + lsdc_fb_format_modifiers, + type, name, index); + if (ret) { + drm_err(ddev, "%s failed: %d\n", __func__, ret); + return ret; + } + + switch (type) { + case DRM_PLANE_TYPE_PRIMARY: + drm_plane_helper_add(plane, &lsdc_primary_plane_helpers); + break; + case DRM_PLANE_TYPE_CURSOR: + drm_plane_helper_add(plane, lsdc_get_cursor_helper_funcs(descp)); + break; + case DRM_PLANE_TYPE_OVERLAY: + drm_err(ddev, "overlay plane is not supported\n"); + break; + } + + return 0; +} diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.c b/drivers/gpu/drm/lsdc/lsdc_pll.c new file mode 100644 index 000000000000..6ed74989a6f5 --- /dev/null +++ b/drivers/gpu/drm/lsdc/lsdc_pll.c @@ -0,0 +1,569 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "lsdc_drv.h" +#include "lsdc_regs.h" +#include "lsdc_pll.h" + +/* + * The structure of the pixel PLL register is evolved with times. + * All loongson's cpu is little endian. + */ + +/* u64 */ +struct ls7a1000_pixpll_bitmap { + /* Byte 0 ~ Byte 3 */ + unsigned div_out : 7; /* 0 : 6 output clock divider */ + unsigned __1 : 14; /* 7 : 20 */ + unsigned loopc : 9; /* 21 : 29 clock multiplier */ + unsigned __2 : 2; /* 30 : 31 */ + + /* Byte 4 ~ Byte 7 */ + unsigned div_ref : 7; /* 0 : 6 input clock divider */ + unsigned locked : 1; /* 7 PLL locked status */ + unsigned sel_out : 1; /* 8 output clk selector */ + unsigned __3 : 2; /* 9 : 10 reserved */ + unsigned set_param : 1; /* 11 trigger the update */ + unsigned bypass : 1; /* 12 */ + unsigned powerdown : 1; /* 13 */ + unsigned __4 : 18; /* 14 : 31 */ +}; + +/* u128 */ +struct ls2k1000_pixpll_bitmap { + /* Byte 0 ~ Byte 3 */ + unsigned sel_out : 1; /* 0 select this PLL */ + unsigned __1 : 1; /* 1 */ + unsigned sw_adj_en : 1; /* 2 allow software adjust */ + unsigned bypass : 1; /* 3 bypass L1 PLL */ + unsigned __2 : 3; /* 4:6 */ + unsigned lock_en : 1; /* 7 enable lock L1 PLL */ + unsigned __3 : 2; /* 8:9 */ + unsigned lock_check : 2; /* 10:11 precision check */ + unsigned __4 : 4; /* 12:15 */ + + unsigned locked : 1; /* 16 PLL locked flag bit */ + unsigned __5 : 2; /* 17:18 */ + unsigned powerdown : 1; /* 19 powerdown the pll if set */ + unsigned __6 : 6; /* 20:25 */ + unsigned div_ref : 6; /* 26:31 L1 Prescaler */ + + /* Byte 4 ~ Byte 7 */ + unsigned loopc : 10; /* 32:41 Clock Multiplier */ + unsigned l1_div : 6; /* 42:47 not used */ + unsigned __7 : 16; /* 48:63 */ + + /* Byte 8 ~ Byte 15 */ + unsigned div_out : 6; /* 0 : 5 output clock divider */ + unsigned __8 : 26; /* 6 : 31 */ + unsigned __9 : 32; /* 70: 127 */ +}; + +/* u32 */ +struct ls2k0500_pixpll_bitmap { + /* Byte 0 ~ Byte 1 */ + unsigned sel_out : 1; + unsigned __1 : 2; + unsigned sw_adj_en : 1; /* allow software adjust */ + unsigned bypass : 1; /* bypass L1 PLL */ + unsigned powerdown : 1; /* write 1 to powerdown the PLL */ + unsigned __2 : 1; + unsigned locked : 1; /* 7 Is L1 PLL locked, read only */ + unsigned div_ref : 6; /* 8:13 ref clock divider */ + unsigned __3 : 2; /* 14:15 */ + /* Byte 2 ~ Byte 3 */ + unsigned loopc : 8; /* 16:23 Clock Multiplier */ + unsigned div_out : 6; /* 24:29 output clock divider */ + unsigned __4 : 2; /* 30:31 */ +}; + +union lsdc_pixpll_bitmap { + struct ls7a1000_pixpll_bitmap ls7a1000; + struct ls2k1000_pixpll_bitmap ls2k1000; + struct ls2k0500_pixpll_bitmap ls2k0500; + + u32 dword[4]; +}; + +struct pixclk_to_pll_parm { + /* kHz */ + unsigned int clock; + + unsigned short width; + unsigned short height; + unsigned short vrefresh; + + /* Stores parameters for programming the Hardware PLLs */ + unsigned short div_out; + unsigned short loopc; + unsigned short div_ref; +}; + +/* + * Pixel clock to PLL parameters translation table. + * Small static cached value to speed up PLL parameters calculation. + */ +static const struct pixclk_to_pll_parm pll_param_table[] = { + {148500, 1920, 1080, 60, 11, 49, 3}, /* 1920x1080@60Hz */ + /* 1920x1080@50Hz */ + {174500, 1920, 1080, 75, 17, 89, 3}, /* 1920x1080@75Hz */ + {181250, 2560, 1080, 75, 8, 58, 4}, /* 2560x1080@75Hz */ + {146250, 1680, 1050, 60, 16, 117, 5}, /* 1680x1050@60Hz */ + {135000, 1280, 1024, 75, 10, 54, 4}, /* 1280x1024@75Hz */ + + {108000, 1600, 900, 60, 15, 81, 5}, /* 1600x900@60Hz */ + /* 1280x1024@60Hz */ + /* 1280x960@60Hz */ + /* 1152x864@75Hz */ + + {106500, 1440, 900, 60, 19, 81, 4}, /* 1440x900@60Hz */ + {88750, 1440, 900, 60, 16, 71, 5}, /* 1440x900@60Hz */ + {83500, 1280, 800, 60, 17, 71, 5}, /* 1280x800@60Hz */ + {71000, 1280, 800, 60, 20, 71, 5}, /* 1280x800@60Hz */ + + {74250, 1280, 720, 60, 22, 49, 3}, /* 1280x720@60Hz */ + /* 1280x720@50Hz */ + + {78750, 1024, 768, 75, 16, 63, 5}, /* 1024x768@75Hz */ + {75000, 1024, 768, 70, 29, 87, 4}, /* 1024x768@70Hz */ + {65000, 1024, 768, 60, 20, 39, 3}, /* 1024x768@60Hz */ + + {51200, 1024, 600, 60, 25, 64, 5}, /* 1024x600@60Hz */ + + {57284, 832, 624, 75, 24, 55, 4}, /* 832x624@75Hz */ + {49500, 800, 600, 75, 40, 99, 5}, /* 800x600@75Hz */ + {50000, 800, 600, 72, 44, 88, 4}, /* 800x600@72Hz */ + {40000, 800, 600, 60, 30, 36, 3}, /* 800x600@60Hz */ + {36000, 800, 600, 56, 50, 72, 4}, /* 800x600@56Hz */ + {31500, 640, 480, 75, 40, 63, 5}, /* 640x480@75Hz */ + /* 640x480@73Hz */ + + {30240, 640, 480, 67, 62, 75, 4}, /* 640x480@67Hz */ + {27000, 720, 576, 50, 50, 54, 4}, /* 720x576@60Hz */ + {25175, 640, 480, 60, 85, 107, 5}, /* 640x480@60Hz */ + {25200, 640, 480, 60, 50, 63, 5}, /* 640x480@60Hz */ + /* 720x480@60Hz */ +}; + +/* + * lsdc_pixpll_setup - ioremap the device dependent PLL registers + * + * @this: point to the object where this function is called from + */ +static int lsdc_pixpll_setup(struct lsdc_pll * const this) +{ + this->mmio = ioremap(this->reg_base, this->reg_size); + + return 0; +} + +/* + * Find a set of pll parameters (to generate pixel clock) from a static + * local table, which avoid to compute the pll parameter eachtime a + * modeset is triggered. + * + * @this: point to the object which this function is called from + * @clock: the desired output pixel clock, the unit is kHz + * @pout: point to where the parameters to store if found + * + * Return true if hit, otherwise return false. + */ +static bool lsdc_pixpll_find(struct lsdc_pll * const this, + unsigned int clock, + struct lsdc_pll_parms * const pout) +{ + unsigned int num = ARRAY_SIZE(pll_param_table); + unsigned int i; + + for (i = 0; i < num; i++) { + if (clock != pll_param_table[i].clock) + continue; + + pout->div_ref = pll_param_table[i].div_ref; + pout->loopc = pll_param_table[i].loopc; + pout->div_out = pll_param_table[i].div_out; + + return true; + } + + drm_dbg(this->ddev, "pixel clock %u: miss\n", clock); + + return false; +} + +/* + * Find a set of pll parameters which have minimal difference with the desired + * pixel clock frequency. It does that by computing all of the possible + * combination. Compute the diff and find the combination with minimal diff. + * + * clock_out = refclk / div_ref * loopc / div_out + * + * refclk is determined by the oscillator mounted board(Here is 100MHz in + * almost all case) + * + * @this: point to the object from which this function is called + * @clock_khz: the desired output pixel clock, the unit is kHz + * @pout: point to the out struct of lsdc_pll_parms + * + * Return true if a parameter is found, otherwise return false + */ +static bool lsdc_pixpll_compute(struct lsdc_pll * const this, + unsigned int clock_khz, + struct lsdc_pll_parms *pout) +{ + unsigned int refclk = this->ref_clock; + const unsigned int tolerance = 1000; + unsigned int min = tolerance; + unsigned int div_out, loopc, div_ref; + unsigned int computed; + + if (lsdc_pixpll_find(this, clock_khz, pout)) + return true; + + for (div_out = 6; div_out < 64; div_out++) { + for (div_ref = 3; div_ref < 6; div_ref++) { + for (loopc = 6; loopc < 161; loopc++) { + int diff; + + if (loopc < 12 * div_ref) + continue; + if (loopc > 32 * div_ref) + continue; + + computed = refclk * loopc / div_ref / div_out; + + if (clock_khz > computed) + diff = clock_khz - computed; + else if (clock_khz < computed) + diff = computed - clock_khz; + + if (diff < min) { + min = diff; + pout->div_ref = div_ref; + pout->div_out = div_out; + pout->loopc = loopc; + + if (diff == 0) + return true; + } + } + } + } + + return min < tolerance; +} + +/* + * Update the pll parameters to hardware, target to the pixpll in ls7a1000 + * + * @this: point to the object from which this function is called + * @pin: point to the struct of lsdc_pll_parms passed in + * + * return 0 if successful. + */ +static int ls7a1000_pixpll_param_update(struct lsdc_pll * const this, + struct lsdc_pll_parms const *pin) +{ + void __iomem *reg = this->mmio; + unsigned int counter = 0; + bool locked; + u32 val; + + /* Bypass the software configured PLL, using refclk directly */ + val = readl(reg + 0x4); + val &= ~(1 << 8); + writel(val, reg + 0x4); + + /* Powerdown the PLL */ + val = readl(reg + 0x4); + val |= (1 << 13); + writel(val, reg + 0x4); + + /* Clear the pll parameters */ + val = readl(reg + 0x4); + val &= ~(1 << 11); + writel(val, reg + 0x4); + + /* clear old value & config new value */ + val = readl(reg + 0x04); + val &= ~0x7F; + val |= pin->div_ref; /* div_ref */ + writel(val, reg + 0x4); + + val = readl(reg); + val &= ~0x7f; + val |= pin->div_out; /* div_out */ + + val &= ~(0x1ff << 21); + val |= pin->loopc << 21; /* loopc */ + writel(val, reg); + + /* Set the pll the parameters */ + val = readl(reg + 0x4); + val |= (1 << 11); + writel(val, reg + 0x4); + + /* Powerup the PLL */ + val = readl(reg + 0x4); + val &= ~(1 << 13); + writel(val, reg + 0x4); + + /* Wait the PLL lock */ + do { + val = readl(reg + 0x4); + locked = val & 0x80; + counter++; + } while (!locked && (counter < 2000)); + + drm_dbg(this->ddev, "%u loop waited\n", counter); + + /* Switch to the software configured pll */ + val = readl(reg + 0x4); + val |= (1UL << 8); + writel(val, reg + 0x4); + + return 0; +} + +/* + * Update the pll parameters to hardware, target to the pixpll in ls2k1000 + * + * @this: point to the object from which this function is called + * @pin: point to the struct of lsdc_pll_parms passed in + * + * return 0 if successful. + */ +static int ls2k1000_pixpll_param_update(struct lsdc_pll * const this, + struct lsdc_pll_parms const *pin) +{ + void __iomem *reg = this->mmio; + unsigned int counter = 0; + bool locked = false; + u32 val; + + val = readl(reg); + /* Bypass the software configured PLL, using refclk directly */ + val &= ~(1 << 0); + writel(val, reg); + + /* Powerdown the PLL */ + val |= (1 << 19); + writel(val, reg); + + /* Allow the software configuration */ + val &= ~(1 << 2); + writel(val, reg); + + /* allow L1 PLL lock */ + val = (1L << 7) | (3L << 10); + writel(val, reg); + + /* clear div_ref bit field */ + val &= ~(0x3f << 26); + /* set div_ref bit field */ + val |= pin->div_ref << 26; + writel(val, reg); + + val = readl(reg + 4); + /* clear loopc bit field */ + val &= ~0x0fff; + /* set loopc bit field */ + val |= pin->loopc; + writel(val, reg + 4); + + /* set div_out */ + writel(pin->div_out, reg + 8); + + val = readl(reg); + /* use this parms configured just now */ + val |= (1 << 2); + /* powerup the PLL */ + val &= ~(1 << 19); + writel(val, reg); + + /* wait pll setup and locked */ + do { + val = readl(reg); + locked = val & 0x10000; + counter++; + } while (!locked && (counter < 2000)); + + drm_dbg(this->ddev, "%u loop waited\n", counter); + + /* Switch to software configured PLL instead of refclk */ + val |= 1; + writel(val, reg); + + return 0; +} + +/* + * Update the pll parameters to hardware, target to the pixpll in ls2k0500 + * + * @this: point to the object which calling this function + * @param: pointer to where the parameters passed in + * + * return 0 if successful. + */ +static int ls2k0500_pixpll_param_update(struct lsdc_pll * const this, + struct lsdc_pll_parms const *param) +{ + void __iomem *reg = this->mmio; + unsigned int counter = 0; + bool locked = false; + u32 val; + + /* Bypass the software configured PLL, using refclk directly */ + val = readl(reg); + val &= ~(1 << 0); + writel(val, reg); + + /* Powerdown the PLL */ + val = readl(reg); + val |= (1 << 5); + writel(val, reg); + + /* Allow the software configuration */ + val |= (1 << 3); + writel(val, reg); + + /* Update the pll params */ + val = (param->div_out << 24) | + (param->loopc << 16) | + (param->div_ref << 8); + + writel(val, reg); + + /* Powerup the PLL */ + val = readl(reg); + val &= ~(1 << 5); + writel(val, reg); + + /* wait pll setup and locked */ + do { + val = readl(reg); + locked = val & 0x80; + counter++; + } while (!locked && (counter < 10000)); + + drm_dbg(this->ddev, "%u loop waited\n", counter); + + /* Switch to software configured PLL instead of refclk */ + writel((val | 1), reg); + + return 0; +} + +static unsigned int lsdc_get_clock_rate(struct lsdc_pll * const this, + struct lsdc_pll_parms *pout) +{ + struct drm_device *ddev = this->ddev; + struct lsdc_device *ldev = to_lsdc(ddev); + const struct lsdc_desc * const descp = ldev->descp; + unsigned int out; + union lsdc_pixpll_bitmap parms; + + if (descp->chip == CHIP_LS7A1000 || descp->chip == CHIP_LS7A2000) { + struct ls7a1000_pixpll_bitmap *obj = &parms.ls7a1000; + + parms.dword[0] = readl(this->mmio); + parms.dword[1] = readl(this->mmio + 4); + out = this->ref_clock / obj->div_ref * obj->loopc / obj->div_out; + if (pout) { + pout->div_ref = obj->div_ref; + pout->loopc = obj->loopc; + pout->div_out = obj->div_out; + } + } else if (descp->chip == CHIP_LS2K1000) { + struct ls2k1000_pixpll_bitmap *obj = &parms.ls2k1000; + + parms.dword[0] = readl(this->mmio); + parms.dword[1] = readl(this->mmio + 4); + parms.dword[2] = readl(this->mmio + 8); + parms.dword[3] = readl(this->mmio + 12); + out = this->ref_clock / obj->div_ref * obj->loopc / obj->div_out; + if (pout) { + pout->div_ref = obj->div_ref; + pout->loopc = obj->loopc; + pout->div_out = obj->div_out; + } + } else if (descp->chip == CHIP_LS2K0500) { + struct ls2k0500_pixpll_bitmap *obj = &parms.ls2k0500; + + parms.dword[0] = readl(this->mmio); + out = this->ref_clock / obj->div_ref * obj->loopc / obj->div_out; + if (pout) { + pout->div_ref = obj->div_ref; + pout->loopc = obj->loopc; + pout->div_out = obj->div_out; + } + } else { + drm_err(ddev, "unknown chip, the driver need update\n"); + return 0; + } + + return out; +} + +static const struct lsdc_pixpll_funcs ls7a1000_pixpll_funcs = { + .setup = lsdc_pixpll_setup, + .compute = lsdc_pixpll_compute, + .update = ls7a1000_pixpll_param_update, + .get_clock_rate = lsdc_get_clock_rate, +}; + +static const struct lsdc_pixpll_funcs ls2k1000_pixpll_funcs = { + .setup = lsdc_pixpll_setup, + .compute = lsdc_pixpll_compute, + .update = ls2k1000_pixpll_param_update, + .get_clock_rate = lsdc_get_clock_rate, +}; + +static const struct lsdc_pixpll_funcs ls2k0500_pixpll_funcs = { + .setup = lsdc_pixpll_setup, + .compute = lsdc_pixpll_compute, + .update = ls2k0500_pixpll_param_update, + .get_clock_rate = lsdc_get_clock_rate, +}; + +int lsdc_pixpll_init(struct lsdc_pll * const this, + struct drm_device *ddev, + unsigned int index) +{ + struct lsdc_device *ldev = to_lsdc(ddev); + const struct lsdc_desc *descp = ldev->descp; + + this->ddev = ddev; + this->index = index; + this->ref_clock = LSDC_PLL_REF_CLK; + + /* LS7A1000 and LS7A2000's pixpll setting registers is same */ + if (descp->chip == CHIP_LS7A2000 || descp->chip == CHIP_LS7A1000) { + if (index == 0) + this->reg_base = LS7A1000_CFG_REG_BASE + LS7A1000_PIX_PLL0_REG; + else if (index == 1) + this->reg_base = LS7A1000_CFG_REG_BASE + LS7A1000_PIX_PLL1_REG; + this->reg_size = 8; + this->funcs = &ls7a1000_pixpll_funcs; + } else if (descp->chip == CHIP_LS2K1000) { + if (index == 0) + this->reg_base = LS2K1000_CFG_REG_BASE + LS2K1000_PIX_PLL0_REG; + else if (index == 1) + this->reg_base = LS2K1000_CFG_REG_BASE + LS2K1000_PIX_PLL1_REG; + + this->reg_size = 16; + this->funcs = &ls2k1000_pixpll_funcs; + } else if (descp->chip == CHIP_LS2K0500) { + if (index == 0) + this->reg_base = LS2K0500_CFG_REG_BASE + LS2K0500_PIX_PLL0_REG; + else if (index == 1) + this->reg_base = LS2K0500_CFG_REG_BASE + LS2K0500_PIX_PLL1_REG; + + this->reg_size = 4; + this->funcs = &ls2k0500_pixpll_funcs; + } else { + drm_err(this->ddev, "unknown chip, the driver need update\n"); + return -ENOENT; + } + + return this->funcs->setup(this); +} diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.h b/drivers/gpu/drm/lsdc/lsdc_pll.h new file mode 100644 index 000000000000..538739f461f2 --- /dev/null +++ b/drivers/gpu/drm/lsdc/lsdc_pll.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __LSDC_PLL_H__ +#define __LSDC_PLL_H__ + +#include <drm/drm_device.h> + +/* + * Loongson Pixel PLL hardware structure + * + * refclk: reference frequency, 100 MHz from external oscillator + * outclk: output frequency desired. + * + * + * L1 Fref Fvco L2 + * refclk +-----------+ +------------------+ +---------+ outclk + * ---+---> | Prescaler | ---> | Clock Multiplier | ---> | divider | --------> + * | +-----------+ +------------------+ +---------+ ^ + * | ^ ^ ^ | + * | | | | | + * | | | | | + * | div_ref loopc div_out | + * | | + * +--- sel_out (bypass above software configurable clock if set) ----+ + * + * sel_out: PLL clock output selector (for debug purpose only). + * + * If sel_out == 1, it will take refclk as output directly, + * the L1 Prescaler and the out divider will be bypassed. + * + * If sel_out == 0, then outclk = refclk / div_ref * loopc / div_out; + * + * PLL working requirements: + * + * 1) 20 MHz <= refclk / div_ref <= 40Mhz + * 2) 1.2 GHz <= refclk /div_out * loopc <= 3.2 Ghz + */ + +struct lsdc_pll_parms { + unsigned int div_ref; + unsigned int loopc; + unsigned int div_out; +}; + +struct lsdc_pll; + +struct lsdc_pixpll_funcs { + int (*setup)(struct lsdc_pll * const this); + bool (*compute)(struct lsdc_pll * const this, + unsigned int clock, + struct lsdc_pll_parms *pout); + int (*update)(struct lsdc_pll * const this, + struct lsdc_pll_parms const *pin); + unsigned int (*get_clock_rate)(struct lsdc_pll * const this, + struct lsdc_pll_parms *pout); +}; + +struct lsdc_pll { + const struct lsdc_pixpll_funcs *funcs; + struct drm_device *ddev; + void __iomem *mmio; + + /* PLL register offset */ + u32 reg_base; + /* PLL register size in bytes */ + u32 reg_size; + + /* 100000kHz, fixed on all board found */ + unsigned int ref_clock; + + unsigned int index; +}; + +int lsdc_pixpll_init(struct lsdc_pll * const this, + struct drm_device *ddev, + unsigned int index); + +#endif diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.c b/drivers/gpu/drm/lsdc/lsdc_regs.c new file mode 100644 index 000000000000..c7950c43968f --- /dev/null +++ b/drivers/gpu/drm/lsdc/lsdc_regs.c @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: GPL-2.0 + +#include "lsdc_drv.h" +#include "lsdc_regs.h" + +u32 lsdc_rreg32(struct lsdc_device * const ldev, u32 offset) +{ + unsigned long flags; + u32 ret; + + spin_lock_irqsave(&ldev->reglock, flags); + + ret = readl(ldev->reg_base + offset); + + spin_unlock_irqrestore(&ldev->reglock, flags); + + return ret; +} + +void lsdc_wreg32(struct lsdc_device * const ldev, u32 offset, u32 val) +{ + unsigned long flags; + + spin_lock_irqsave(&ldev->reglock, flags); + + writel(val, ldev->reg_base + offset); + + spin_unlock_irqrestore(&ldev->reglock, flags); +} diff --git a/drivers/gpu/drm/lsdc/lsdc_regs.h b/drivers/gpu/drm/lsdc/lsdc_regs.h new file mode 100644 index 000000000000..828956633137 --- /dev/null +++ b/drivers/gpu/drm/lsdc/lsdc_regs.h @@ -0,0 +1,296 @@ +/* SPDX-License-Identifier: GPL-2.0 */ + +#ifndef __LSDC_REGS_H__ +#define __LSDC_REGS_H__ + +#include <linux/bitops.h> +#include <linux/types.h> + +/* + * PIXEL PLL Reference clock + */ +#define LSDC_PLL_REF_CLK 100000 /* kHz */ + +/* + * Those PLL registers are not located at DC reg bar space, + * there are relative to LSXXXXX_CFG_REG_BASE. + * XXXXX = 7A1000, 2K1000, 2K0500 + */ + +/* LS2K1000 */ +#define LS2K1000_PIX_PLL0_REG 0x04B0 +#define LS2K1000_PIX_PLL1_REG 0x04C0 +#define LS2K1000_CFG_REG_BASE 0x1fe10000 + +/* LS7A1000 and LS2K2000 */ +#define LS7A1000_PIX_PLL0_REG 0x04B0 +#define LS7A1000_PIX_PLL1_REG 0x04C0 +#define LS7A1000_CFG_REG_BASE 0x10010000 + +/* LS2K0500 */ +#define LS2K0500_PIX_PLL0_REG 0x0418 +#define LS2K0500_PIX_PLL1_REG 0x0420 +#define LS2K0500_CFG_REG_BASE 0x1fe10000 + +/* + * CRTC CFG REG + */ +#define CFG_PIX_FMT_MASK GENMASK(2, 0) + +enum lsdc_pixel_format { + LSDC_PF_NONE = 0, + LSDC_PF_ARGB4444 = 1, /* ARGB A:4 bits R/G/B: 4 bits each [16 bits] */ + LSDC_PF_ARGB1555 = 2, /* ARGB A:1 bit RGB:15 bits [16 bits] */ + LSDC_PF_RGB565 = 3, /* RGB [16 bits] */ + LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */ + LSDC_PF_RGBA8888 = 5, /* ARGB [32 bits] */ +}; + +/* + * Each crtc has two set fb address registers usable, CFG_FB_IN_USING of + * LSDC_CRTCx_CFG_REG specify which fb address register is currently + * in using by the CRTC. CFG_PAGE_FLIP of LSDC_CRTCx_CFG_REG is used to + * trigger the switch which will be finished at the very vblank. If you + * want it switch to another again, you must set CFG_PAGE_FLIP again. + */ +#define CFG_PAGE_FLIP BIT(7) +#define CFG_OUTPUT_EN BIT(8) +/* CRTC0 clone from CRTC1 or CRTC1 clone from CRTC0 using hardware logic */ +#define CFG_PANEL_SWITCH BIT(9) +/* Indicate witch fb addr reg is in using, currently */ +#define CFG_FB_IN_USING BIT(11) +#define CFG_GAMMA_EN BIT(12) + +/* CRTC get soft reset if voltage level change from 1 -> 0 */ +#define CFG_RESET_N BIT(20) + +#define CFG_HSYNC_EN BIT(30) +#define CFG_HSYNC_INV BIT(31) +#define CFG_VSYNC_EN BIT(30) +#define CFG_VSYNC_INV BIT(31) + +/* THE DMA step of the DC in LS7A2000 is configurable */ +#define LSDC_DMA_STEP_MASK GENMASK(17, 16) +enum lsdc_dma_steps_supported { + LSDC_DMA_STEP_256_BYTES = 0, + LSDC_DMA_STEP_128_BYTES = 1, + LSDC_DMA_STEP_64_BYTES = 2, + LSDC_DMA_STEP_32_BYTES = 3, +}; + +/******** CRTC0 & DVO0 ********/ +#define LSDC_CRTC0_CFG_REG 0x1240 +#define LSDC_CRTC0_FB0_LO_ADDR_REG 0x1260 +#define LSDC_CRTC0_FB0_HI_ADDR_REG 0x15A0 +#define LSDC_CRTC0_FB1_LO_ADDR_REG 0x1580 +#define LSDC_CRTC0_FB1_HI_ADDR_REG 0x15C0 +#define LSDC_CRTC0_STRIDE_REG 0x1280 +#define LSDC_CRTC0_FB_ORIGIN_REG 0x1300 +#define LSDC_CRTC0_HDISPLAY_REG 0x1400 +#define LSDC_CRTC0_HSYNC_REG 0x1420 +#define LSDC_CRTC0_VDISPLAY_REG 0x1480 +#define LSDC_CRTC0_VSYNC_REG 0x14A0 +#define LSDC_CRTC0_GAMMA_INDEX_REG 0x14E0 +#define LSDC_CRTC0_GAMMA_DATA_REG 0x1500 + +/******** CTRC1 & DVO1 ********/ +#define LSDC_CRTC1_CFG_REG 0x1250 +#define LSDC_CRTC1_FB0_LO_ADDR_REG 0x1270 +#define LSDC_CRTC1_FB0_HI_ADDR_REG 0x15B0 +#define LSDC_CRTC1_FB1_LO_ADDR_REG 0x1590 +#define LSDC_CRTC1_FB1_HI_ADDR_REG 0x15D0 +#define LSDC_CRTC1_STRIDE_REG 0x1290 +#define LSDC_CRTC1_FB_ORIGIN_REG 0x1310 +#define LSDC_CRTC1_HDISPLAY_REG 0x1410 +#define LSDC_CRTC1_HSYNC_REG 0x1430 +#define LSDC_CRTC1_VDISPLAY_REG 0x1490 +#define LSDC_CRTC1_VSYNC_REG 0x14B0 +#define LSDC_CRTC1_GAMMA_INDEX_REG 0x14F0 +#define LSDC_CRTC1_GAMMA_DATA_REG 0x1510 + +/* + * LS7A2000 has the hardware which record the scan position of the CRTC + * [31:16] : current X position, [15:0] : current Y position + */ +#define LSDC_CRTC0_SCAN_POS_REG 0x14C0 +#define LSDC_CRTC1_SCAN_POS_REG 0x14D0 + +/* + * LS7A2000 has the hardware which count the number of vblank generated + */ +#define LSDC_CRTC0_VSYNC_COUNTER_REG 0x1A00 +#define LSDC_CRTC1_VSYNC_COUNTER_REG 0x1A10 + +/* In all, LSDC_CRTC1_XXX_REG - LSDC_CRTC0_XXX_REG = 0x10 */ + +/* + * There is only one hardware cursor unit in ls7a1000, ls2k1000 and ls2k0500. + * well, ls7a2000 has two hardware cursor unit. + */ +#define CURSOR_FORMAT_MASK GENMASK(1, 0) +enum lsdc_cursor_format { + CURSOR_FORMAT_DISABLE = 0, + CURSOR_FORMAT_MONOCHROME = 1, + CURSOR_FORMAT_ARGB8888 = 2, +}; + +#define CURSOR_SIZE_64X64 BIT(2) +#define CURSOR_LOCATION BIT(4) + +#define LSDC_CURSOR0_CFG_REG 0x1520 +#define LSDC_CURSOR0_ADDR_LO_REG 0x1530 +#define LSDC_CURSOR0_ADDR_HI_REG 0x15e0 +#define LSDC_CURSOR0_POSITION_REG 0x1540 +#define LSDC_CURSOR0_BG_COLOR_REG 0x1550 /* background color */ +#define LSDC_CURSOR0_FG_COLOR_REG 0x1560 /* foreground color */ + +#define LSDC_CURSOR1_CFG_REG 0x1670 +#define LSDC_CURSOR1_ADDR_LO_REG 0x1680 +#define LSDC_CURSOR1_ADDR_HI_REG 0x16e0 +#define LSDC_CURSOR1_POSITION_REG 0x1690 /* [31:16] Y, [15:0] X */ +#define LSDC_CURSOR1_BG_COLOR_REG 0x16A0 /* background color */ +#define LSDC_CURSOR1_FG_COLOR_REG 0x16B0 /* foreground color */ + +/* + * DC Interrupt Control Register, 32bit, Address Offset: 1570 + * + * Bits 15:0 inidicate the interrupt status + * Bits 31:16 control enable interrupts corresponding to bit 15:0 or not + * Write 1 to enable, write 0 to disable + * + * RF: Read Finished + * IDBU: Internal Data Buffer Underflow + * IDBFU: Internal Data Buffer Fatal Underflow + * CBRF: Cursor Buffer Read Finished Flag, no use. + * + * +-------+--------------------------+-------+--------+--------+-------+ + * | 31:27 | 26:16 | 15:11 | 10 | 9 | 8 | + * +-------+--------------------------+-------+--------+--------+-------+ + * | N/A | Interrupt Enable Control | N/A | IDBFU0 | IDBFU1 | IDBU0 | + * +-------+--------------------------+-------+--------+--------+-------+ + * + * +-------+-----+-----+------+--------+--------+--------+--------+ + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * +-------+-----+-----+------+--------+--------+--------+--------+ + * | IDBU1 | RF0 | RF1 | CRRF | HSYNC0 | VSYNC0 | HSYNC1 | VSYNC1 | + * +-------+-----+-----+------+--------+--------+--------+--------+ + */ + +#define LSDC_INT_REG 0x1570 + +#define INT_CRTC0_VSYNC BIT(2) +#define INT_CRTC0_HSYNC BIT(3) +#define INT_CRTC0_RF BIT(6) +#define INT_CRTC0_IDBU BIT(8) +#define INT_CRTC0_IDBFU BIT(10) + +#define INT_CRTC1_VSYNC BIT(0) +#define INT_CRTC1_HSYNC BIT(1) +#define INT_CRTC1_RF BIT(5) +#define INT_CRTC1_IDBU BIT(7) +#define INT_CRTC1_IDBFU BIT(9) + +#define INT_CRTC0_VS_EN BIT(18) +#define INT_CRTC0_HS_EN BIT(19) +#define INT_CRTC0_RF_EN BIT(22) +#define INT_CRTC0_IDBU_EN BIT(24) +#define INT_CRTC0_IDBFU_EN BIT(26) + +#define INT_CRTC1_VS_EN BIT(16) +#define INT_CRTC1_HS_EN BIT(17) +#define INT_CRTC1_RF_EN BIT(21) +#define INT_CRTC1_IDBU_EN BIT(23) +#define INT_CRTC1_IDBFU_EN BIT(25) + +#define INT_STATUS_MASK GENMASK(15, 0) + +/* + * LS7A1000/LS7A2000 have 4 gpios which are used to emulated I2C. + * They are under control of the LS7A_DC_GPIO_DAT_REG and LS7A_DC_GPIO_DIR_REG + * register, Those GPIOs has no relationship whth the GPIO hardware on the + * bridge chip itself. Those offsets are relative to DC register base address + * + * LS2k1000 and LS2K0500 don't have those registers, they use hardware i2c + * or general GPIO emulated i2c from linux i2c subsystem. + * + * GPIO data register, address offset: 0x1650 + * +---------------+-----------+-----------+ + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * +---------------+-----------+-----------+ + * | | DVO1 | DVO0 | + * + N/A +-----------+-----------+ + * | | SCL | SDA | SCL | SDA | + * +---------------+-----------+-----------+ + */ +#define LS7A_DC_GPIO_DAT_REG 0x1650 + +/* + * GPIO Input/Output direction control register, address offset: 0x1660 + */ +#define LS7A_DC_GPIO_DIR_REG 0x1660 + +/* + * LS7A2000 has two built-in HDMI Encoder and one VGA encoder + */ + +/* + * Number of continuous packets may be present + * in HDMI hblank and vblank zone, should >= 48 + */ +#define LSDC_HDMI0_ZONE_REG 0x1700 +#define LSDC_HDMI1_ZONE_REG 0x1710 + +#define HDMI_INTERFACE_EN BIT(0) +#define HDMI_PACKET_EN BIT(1) +#define HDMI_AUDIO_EN BIT(2) +#define HDMI_VIDEO_PREAMBLE_MASK GENMASK(7, 4) +#define HDMI_HW_I2C_EN BIT(8) +#define HDMI_CTL_PERIOD_MODE BIT(9) +#define LSDC_HDMI0_CTRL_REG 0x1720 +#define LSDC_HDMI1_CTRL_REG 0x1730 + +#define HDMI_PHY_EN BIT(0) +#define HDMI_PHY_RESET_N BIT(1) +#define HDMI_PHY_TERM_L_EN BIT(8) +#define HDMI_PHY_TERM_H_EN BIT(9) +#define HDMI_PHY_TERM_DET_EN BIT(10) +#define HDMI_PHY_TERM_STATUS BIT(11) +#define LSDC_HDMI0_PHY_CTRL_REG 0x1800 +#define LSDC_HDMI1_PHY_CTRL_REG 0x1810 + +/* + * 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 | + * +--------------------------------------------------------+ + */ +#define LSDC_HDMI0_PHY_PLL_REG 0x1820 +#define LSDC_HDMI1_PHY_PLL_REG 0x1830 + +#define HDMI_PLL_ENABLE BIT(0) +#define HDMI_PLL_LOCKED BIT(16) +#define HDMI_PLL_BYPASS BIT(17) + +#define HDMI_PLL_IDF_SHIFT 1 +#define HDMI_PLL_IDF_MASK GENMASK(5, 1) +#define HDMI_PLL_LF_SHIFT 6 +#define HDMI_PLL_LF_MASK GENMASK(12, 6) +#define HDMI_PLL_ODF_SHIFT 13 +#define HDMI_PLL_ODF_MASK GENMASK(15, 13) + +/* LS7A2000 have hpd support */ +#define LSDC_HDMI_HPD_STATUS_REG 0x1BA0 +#define HDMI0_HPD_FLAG BIT(0) +#define HDMI1_HPD_FLAG BIT(1) + +#endif