[PATCH v6 1/3] drm/lsdc: add drm driver for loongson display controller

Maxime Ripard maxime at cerno.tech
Thu Feb 3 08:58:51 UTC 2022


Hi,

On Thu, Feb 03, 2022 at 04:25:44PM +0800, Sui Jingfeng wrote:
> From: suijingfeng <suijingfeng at loongson.cn>
> 
> There is a display controller in loongson's LS2K1000 SoC and LS7A1000
> bridge, and the DC in those chip is a PCI device. This patch provide
> a minimal support for this display controller which is mainly for
> graphic environment bring up.
> 
> This display controller has two display pipes but with only one hardware
> cursor. Each way has a DVO output interface and the CRTC is able to scanout
> from 1920x1080 resolution at 60Hz. The maxmium resolution is 2048x2048 at 60hz.
> 
> LS2K1000 is a SoC, only system memory is available. Therefore scanout from
> system memory is the only choice. We prefer the CMA helper base solution
> because the gc1000 gpu can use etnaviv driver, in this case etnaviv and
> lsdc could made a compatible pair. Even through it is possible to use VRAM
> helper base solution on ls2k1000 by carving out part of system memory as
> VRAM.
> 
> For LS7A1000, there are 4 gpios whos control register is located at the dc
> register space which is not the geneal purpose GPIO. The 4 gpios can emulate
> two way i2c. One for DVO0, another for DVO1. This is the reason the why we
> are not using the drm bridge framework.
> 
> LS2K1000 and LS2K0500 SoC don't have such hardware, they use general purpose
> GPIO emulated i2c or hardware i2c adapter from other module to serve this
> purpose. Drm bridge and drm panel is suitable for those SoC, we have already
> implement it on our own downstream kernel. But due to the upstream kernel
> don't have gpio, pwm and i2c driver support for LS2K1000. We just can not
> upstream our support for the drm bridge subsystem.
> 
> The DC in LS7A1000 can scanout from both the system memory and the dedicate
> VRAM attached to the ddr3 memory controller. Sadly, only scanout from the
> VRAM is proved to be a reliable solution for massive product. Scanout from
> the system memory suffer from some hardware deficiency which cause the
> screen flickering under RAM pressure. This is the reason why we integrate
> two distict helpers into one piece of device driver. But CMA base helper is
> still usable on ls7a1000 for normal usage, expecially on ls7a1000+ bridge
> chip. We have also implemented demage update on top of CMA helper which
> copy the demaged shadow framebuffer region from system RAM to the real
> framebuffer in VRAM manually. Using "lsdc.dirty_update=1" in the commmand
> line will enable this driver mode.
> 
> LS7A1000 have a 32x32 harware cursor, we just let the two CRTC share it
> simply with the help of universe plane. LS7A2000 have two 64x64 harware
> cursor. Surport for LS7A2000 is on the way.
> 
> In short, we have built-in gpio emulated i2c support, we also have hardware
> cursor support. The kind of tiny drivers in drm/tiny is not suitable for us,
> we are not "tiny".
> 
>     +------+  HyperTransport 3.0
>     | DDR4 |       |
>     +------+       |     +------------------------------------+
>        || MC0      |     |   LS7A1000            +------------|
>   +----------+     |     |                       |    DDR3    |   +------+
>   | LS3A4000 |<--------->| +--------+  +-------+ |   memory   |<->| VRAM |
>   |   CPU    |<--------->| | GC1000 |  |  LSDC | | controller |   +------+
>   +----------+           | +--------+  +-+---+-+ +------------|
>        || MC1            +---------------|---|----------------+
>     +------+                             |   |
>     | DDR4 |          +-------+    DVO0  |   |  DVO1  +------+
>     +------+   VGA <--|ADV7125|<---------+   +------->|TFP410|--> DVI/HDMI
>                       +-------+                       +------+
> 
> The above picture give a simple usage of LS7A1000, note that the encoder
> is not necessary adv7125 or tfp410, it is a choice of the downstream board
> manufacturer. Other candicate encoder can be ch7034b, sil9022 and ite66121
> etc. Therefore, we need device tree to provide board specific information.
> Besides, the dc in both ls2k1000 and ls7k1000 have the vendor:device id of
> 0x0014:0x7a06, the reverison id is also same. We can't tell it apart simply
> (this is the firmware engineer's mistake). But firmware already flushed to
> the board and borad already sold out, we choose to resolve those issues by
> introduing device tree with board specific device support.
> 
> For lsdc, there is only a 1:1 mapping of encoders and connectors.
> 
>      +-------------------+                                      _________
>      |                   |                                     |         |
>      |  CRTC0 --> DVO0 ---------> Encoder0 --> Connector0 ---> | Monitor |
>      |                   |           ^            ^            |_________|
>      |                   |           |            |
>      |                <----- i2c0 ----------------+
>      |   LSDC IP CORE    |
>      |                <----- i2c1 ----------------+
>      |                   |           |            |             _________
>      |                   |           |            |            |         |
>      |  CRTC1 --> DVO1 ---------> Encoder1 --> Connector1 ---> |  Panel  |
>      |                   |                                     |_________|
>      +-------------------+
> 
> Below is a brief introduction of loongson's CPU, bridge chip and SoC.
> LS2K1000 is a double core 1.0Ghz mips64r2 compatible SoC[1]. LS7A1000 is
> a bridge chip made by Loongson corporation which act as north and/or south
> bridge of loongson's desktop and server level processor. It is equivalent
> to AMD RS780E+SB710 or something like that. More details can be read from
> its user manual[2].
> 
> This bridge chip is typically use with LS3A3000, LS3A4000 and LS3A5000 cpu.
> LS3A3000 is 4 core 1.45gHz mips64r2 compatible cpu.
> LS3A4000 is 4 core 1.8gHz mips64r5 compatible cpu.
> LS3A5000 is 4 core 2.5gHz loongarch cpu[3].
> 
> Nearly all mordern loongson CPU's cache coherency is maintained by hardware,
> except for early version of ls2k1000. So we using cached coherent memory by
> default, not writecombine.
> 
> v2: fixup warnings reported by kernel test robot
> 
> v3: fix more grammar mistakes in Kconfig reported by Randy Dunlap and give
>     more details about lsdc.
> 
> v4:
>    1) Add dts required and explain why device tree is required.
>    2) Give more description about lsdc and vram helper base driver.
>    3) Fix warnings reported by kernel test robot.
>    4) Introduce stride_alignment member into struct lsdc_chip_desc, the
>       stride alignment is 256 bytes for ls7a1000, ls2k1000 and ls2k0500 SoC.
>       But ls7a2000 improve it to 32 bytes, We are prepare for extend the
>       support for the on coming device.
> 
> v5:
>    1) using writel and readl replace writeq and readq, to fix kernel test
>       robot report build error on other archtecture
>    2) set default fb format to XRGB8888 at crtc reset time.
>    3) fix typos.
> 
> v6:
>    1) Explain why we are not switch to drm dridge subsystem on ls2k1000.
>    2) Explain why tiny drm driver is not suitable for us.
>    3) Give a short description of the trival dirty uppdate implement based
>       on CMA helper.
>    4) code clean up
> 
> [1] https://wiki.debian.org/InstallingDebianOn/Lemote/Loongson2K1000
> [2] https://loongson.github.io/LoongArch-Documentation/Loongson-7A1000-usermanual-EN.html
> [3] https://loongson.github.io/LoongArch-Documentation/Loongson-3A5000-usermanual-EN.html
> 
> Reported-by: Randy Dunlap <rdunlap at infradead.org>
> Reported-by: kernel test robot
> Signed-off-by: suijingfeng <suijingfeng at loongson.cn>
> Signed-off-by: Sui Jingfeng <15330273260 at 189.cn>
> ---
>  drivers/gpu/drm/Kconfig               |   2 +
>  drivers/gpu/drm/Makefile              |   1 +
>  drivers/gpu/drm/lsdc/Kconfig          |  38 ++
>  drivers/gpu/drm/lsdc/Makefile         |  15 +
>  drivers/gpu/drm/lsdc/lsdc_connector.c | 443 ++++++++++++++
>  drivers/gpu/drm/lsdc/lsdc_connector.h |  60 ++
>  drivers/gpu/drm/lsdc/lsdc_crtc.c      | 440 ++++++++++++++
>  drivers/gpu/drm/lsdc/lsdc_drv.c       | 846 ++++++++++++++++++++++++++
>  drivers/gpu/drm/lsdc/lsdc_drv.h       | 216 +++++++
>  drivers/gpu/drm/lsdc/lsdc_encoder.c   |  79 +++
>  drivers/gpu/drm/lsdc/lsdc_i2c.c       | 220 +++++++
>  drivers/gpu/drm/lsdc/lsdc_i2c.h       |  61 ++
>  drivers/gpu/drm/lsdc/lsdc_irq.c       |  77 +++
>  drivers/gpu/drm/lsdc/lsdc_irq.h       |  37 ++
>  drivers/gpu/drm/lsdc/lsdc_plane.c     | 681 +++++++++++++++++++++
>  drivers/gpu/drm/lsdc/lsdc_pll.c       | 657 ++++++++++++++++++++
>  drivers/gpu/drm/lsdc/lsdc_pll.h       | 109 ++++
>  drivers/gpu/drm/lsdc/lsdc_regs.h      | 246 ++++++++
>  18 files changed, 4228 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_connector.c
>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_connector.h
>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_crtc.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_encoder.c
>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_i2c.c
>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_i2c.h
>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_irq.c
>  create mode 100644 drivers/gpu/drm/lsdc/lsdc_irq.h
>  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.h
> 
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index dfdd3ec5f793..18de1485e2ed 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -405,6 +405,8 @@ source "drivers/gpu/drm/gud/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 8675c2af7ae1..2c5a76ced323 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -133,3 +133,4 @@ obj-y			+= xlnx/
>  obj-y			+= gud/
>  obj-$(CONFIG_DRM_HYPERV) += hyperv/
>  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..7ed1b0fdbe1b
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/Kconfig
> @@ -0,0 +1,38 @@
> +config DRM_LSDC
> +	tristate "DRM Support for loongson's display controller"
> +	depends on DRM && PCI
> +	depends on MACH_LOONGSON64 || LOONGARCH || MIPS || COMPILE_TEST
> +	select OF
> +	select CMA if HAVE_DMA_CONTIGUOUS
> +	select DMA_CMA if HAVE_DMA_CONTIGUOUS
> +	select DRM_KMS_HELPER
> +	select DRM_KMS_FB_HELPER
> +	select DRM_GEM_CMA_HELPER
> +	select VIDEOMODE_HELPERS
> +	select BACKLIGHT_PWM if CPU_LOONGSON2K
> +	select I2C_GPIO if CPU_LOONGSON2K
> +	select I2C_LS2X if CPU_LOONGSON2K
> +	default m
> +	help
> +	  This is a KMS driver for the display controller in the LS7A1000
> +	  bridge chip and LS2K1000 SoC. The display controller has two
> +	  display pipes and it is a PCI device.
> +	  When using this driver on LS2K1000/LS2K0500 SoC, its framebuffer
> +	  is located at system memory.
> +	  If "M" is selected, the module will be called lsdc.
> +
> +	  If in doubt, say "Y".
> +
> +config DRM_LSDC_VRAM_DRIVER
> +	bool "vram helper based device driver support"
> +	depends on DRM_LSDC
> +	select DRM_VRAM_HELPER
> +	default y
> +	help
> +	  When using this driver on LS7A1000 + LS3A3000/LS3A4000/LS3A5000
> +	  platform, the LS7A1000 bridge chip has dedicated video RAM. Using
> +	  "lsdc.use_vram_helper=1" in the kernel command line will enable
> +	  this driver mode and then the framebuffer will be located at the
> +	  VRAM at the price of losing PRIME support.
> +
> +	  If in doubt, say "Y".

This doesn't sound right. The driver should make the proper decision
depending on the platform, not the user or the distribution.

> diff --git a/drivers/gpu/drm/lsdc/Makefile b/drivers/gpu/drm/lsdc/Makefile
> new file mode 100644
> index 000000000000..342990654478
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/Makefile
> @@ -0,0 +1,15 @@
> +#
> +# Makefile for the lsdc drm device driver.
> +#
> +
> +lsdc-y := \
> +	lsdc_drv.o \
> +	lsdc_crtc.o \
> +	lsdc_irq.o \
> +	lsdc_plane.o \
> +	lsdc_pll.o \
> +	lsdc_i2c.o \
> +	lsdc_encoder.o \
> +	lsdc_connector.o
> +
> +obj-$(CONFIG_DRM_LSDC) += lsdc.o
> diff --git a/drivers/gpu/drm/lsdc/lsdc_connector.c b/drivers/gpu/drm/lsdc/lsdc_connector.c
> new file mode 100644
> index 000000000000..ae5fc0c90961
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_connector.c
> @@ -0,0 +1,443 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2020 Loongson Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sub license, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
> + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
> + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
> + * USE OR OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * The above copyright notice and this permission notice (including the
> + * next paragraph) shall be included in all copies or substantial portions
> + * of the Software.
> + */

That's the MIT license, yet you claim the driver to be licensed under
the GPLv2 or later?

> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#include <drm/drm_print.h>
> +#include <drm/drm_edid.h>
> +#include <drm/drm_probe_helper.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_connector.h>
> +
> +#include <video/videomode.h>
> +#include <video/of_display_timing.h>
> +
> +#include "lsdc_drv.h"
> +#include "lsdc_i2c.h"
> +#include "lsdc_connector.h"
> +
> +
> +static int lsdc_get_modes_from_edid(struct drm_connector *connector)
> +{
> +	struct drm_device *ddev = connector->dev;
> +	struct lsdc_connector *lconn = to_lsdc_connector(connector);
> +	struct edid *edid_p = (struct edid *)lconn->edid_data;
> +	int num = drm_add_edid_modes(connector, edid_p);
> +
> +	if (num)
> +		drm_connector_update_edid_property(connector, edid_p);
> +
> +	drm_dbg_kms(ddev, "%d modes added\n", num);
> +
> +	return num;
> +}
> +
> +
> +static int lsdc_get_modes_from_timings(struct drm_connector *connector)
> +{
> +	struct drm_device *ddev = connector->dev;
> +	struct lsdc_connector *lconn = to_lsdc_connector(connector);
> +	struct display_timings *disp_tim = lconn->disp_tim;
> +	unsigned int num = 0;
> +	unsigned int i;
> +
> +	for (i = 0; i < disp_tim->num_timings; i++) {
> +		const struct display_timing *dt = disp_tim->timings[i];
> +		struct drm_display_mode *mode;
> +		struct videomode vm;
> +
> +		videomode_from_timing(dt, &vm);
> +		mode = drm_mode_create(ddev);
> +		if (!mode) {
> +			drm_err(ddev, "failed to add mode %ux%u\n",
> +					dt->hactive.typ, dt->vactive.typ);
> +			continue;
> +		}
> +
> +		drm_display_mode_from_videomode(&vm, mode);
> +
> +		mode->type |= DRM_MODE_TYPE_DRIVER;
> +
> +		if (i == disp_tim->native_mode)
> +			mode->type |= DRM_MODE_TYPE_PREFERRED;
> +
> +		drm_mode_probed_add(connector, mode);
> +		num++;
> +	}
> +
> +	drm_dbg_kms(ddev, "%d modes added\n", num);
> +
> +	return num;
> +}
> +
> +
> +static int lsdc_get_modes_from_ddc(struct drm_connector *connector,
> +				   struct i2c_adapter *ddc)
> +{
> +	unsigned int num = 0;
> +	struct edid *edid;
> +
> +	edid = drm_get_edid(connector, ddc);
> +	if (edid) {
> +		drm_connector_update_edid_property(connector, edid);
> +		num = drm_add_edid_modes(connector, edid);
> +		kfree(edid);
> +	}
> +
> +	return num;
> +}
> +
> +
> +static int lsdc_get_modes(struct drm_connector *connector)
> +{
> +	struct lsdc_connector *lconn = to_lsdc_connector(connector);
> +	unsigned int num = 0;
> +
> +	if (lconn->has_edid)
> +		return lsdc_get_modes_from_edid(connector);
> +
> +	if (lconn->has_disp_tim)
> +		return lsdc_get_modes_from_timings(connector);
> +
> +	if (IS_ERR_OR_NULL(lconn->ddc) == false)
> +		return lsdc_get_modes_from_ddc(connector, lconn->ddc);
> +
> +	if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) {
> +		num = drm_add_modes_noedid(connector,
> +				     connector->dev->mode_config.max_width,
> +				     connector->dev->mode_config.max_height);
> +
> +		drm_set_preferred_mode(connector, 1024, 768);
> +
> +		return num;
> +	}
> +
> +
> +	/*
> +	 * In case we cannot retrieve the EDIDs (broken or missing i2c
> +	 * bus), fallback on the XGA standards, because we are for board
> +	 * bringup.
> +	 */
> +	num = drm_add_modes_noedid(connector, 1920, 1200);
> +
> +	/* And prefer a mode pretty much anyone can handle */
> +	drm_set_preferred_mode(connector, 1024, 768);
> +
> +	return num;
> +
> +}
> +
> +
> +static enum drm_connector_status
> +lsdc_connector_detect(struct drm_connector *connector, bool force)
> +{
> +	struct lsdc_connector *lconn = to_lsdc_connector(connector);
> +
> +	if (lconn->has_edid == true)
> +		return connector_status_connected;
> +
> +	if (lconn->has_disp_tim == true)
> +		return connector_status_connected;
> +
> +	if (IS_ERR_OR_NULL(lconn->ddc) == false)
> +		return drm_probe_ddc(lconn->ddc);
> +
> +	if (lconn->ddc && drm_probe_ddc(lconn->ddc))
> +		return connector_status_connected;
> +
> +	if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL)
> +		return connector_status_connected;
> +
> +	if ((connector->connector_type == DRM_MODE_CONNECTOR_DVIA) ||
> +	    (connector->connector_type == DRM_MODE_CONNECTOR_DVID) ||
> +	    (connector->connector_type == DRM_MODE_CONNECTOR_DVII))
> +		return connector_status_disconnected;
> +
> +	if ((connector->connector_type == DRM_MODE_CONNECTOR_HDMIA) ||
> +	    (connector->connector_type == DRM_MODE_CONNECTOR_HDMIB))
> +		return connector_status_disconnected;
> +
> +	return connector_status_unknown;
> +}
> +
> +
> +/*
> + * @connector: point to the drm_connector structure
> + *
> + * Clean up connector resources
> + */
> +static void lsdc_connector_destroy(struct drm_connector *connector)
> +{
> +	struct drm_device *ddev = connector->dev;
> +	struct lsdc_connector *lconn = to_lsdc_connector(connector);
> +
> +	if (lconn) {
> +		if (lconn->ddc)
> +			lsdc_destroy_i2c(connector->dev, lconn->ddc);
> +
> +		drm_info(ddev, "destroying connector%u\n", lconn->index);
> +
> +		devm_kfree(ddev->dev, lconn);
> +	}
> +
> +	drm_connector_cleanup(connector);
> +}
> +
> +
> +static const struct drm_connector_helper_funcs lsdc_connector_helpers = {
> +	.get_modes = lsdc_get_modes,
> +};
> +
> +/*
> + * These provide the minimum set of functions required to handle a connector
> + *
> + * Control connectors on a given device.
> + *
> + * Each CRTC may have one or more connectors attached to it.
> + * The functions below allow the core DRM code to control
> + * connectors, enumerate available modes, etc.
> + */
> +static const struct drm_connector_funcs lsdc_connector_funcs = {
> +	.dpms = drm_helper_connector_dpms,
> +	.detect = lsdc_connector_detect,
> +	.fill_modes = drm_helper_probe_single_connector_modes,
> +	.destroy = lsdc_connector_destroy,
> +	.reset = drm_atomic_helper_connector_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
> +};
> +
> +
> +/* Get the simple EDID data from the device tree
> + * the length must be EDID_LENGTH, since it is simple.
> + *
> + * @np: device node contain edid data
> + * @edid_data: where the edid data to store to
> + */
> +static bool lsdc_get_edid_from_dtb(struct device_node *np,
> +				   unsigned char *edid_data)
> +{
> +	int length;
> +	const void *prop;
> +
> +	if (np == NULL)
> +		return false;
> +
> +	prop = of_get_property(np, "edid", &length);
> +	if (prop && (length == EDID_LENGTH)) {
> +		memcpy(edid_data, prop, EDID_LENGTH);
> +		return true;
> +	}
> +
> +	return false;
> +}

You don't have a device tree binding for that driver, this is something
that is required. And it's not clear to me why you'd want EDID in the
DTB?

> +
> +/* Get display timings from the device tree
> + *
> + * @np: device node contain the display timings
> + * @pptim: point to where the pointer of struct display_timings store to
> + */
> +static void lsdc_get_display_timings_from_dtb(struct device_node *np,
> +					      struct display_timings **pptim)
> +{
> +	struct display_timings *timings;
> +
> +	if (np == NULL)
> +		return;
> +
> +	timings = of_get_display_timings(np);
> +	if (timings)
> +		*pptim = timings;
> +}
> +
> +
> +static int lsdc_get_connector_type(struct drm_device *ddev,
> +				   struct device_node *output,
> +				   unsigned int index)
> +{
> +	const char *name;
> +	int ret;
> +
> +	ret = of_property_read_string(output, "connector", &name);
> +	if (ret < 0)
> +		return DRM_MODE_CONNECTOR_Unknown;
> +
> +	if (strncmp(name, "vga-connector", 13) == 0) {
> +		ret = DRM_MODE_CONNECTOR_VGA;
> +		drm_info(ddev, "connector%d is VGA\n", index);
> +	} else if (strncmp(name, "dvi-connector", 13) == 0) {
> +		bool analog, digital;
> +
> +		analog = of_property_read_bool(output, "analog");
> +		digital = of_property_read_bool(output, "digital");
> +
> +		if (analog && !digital)
> +			ret = DRM_MODE_CONNECTOR_DVIA;
> +		else if (analog && digital)
> +			ret = DRM_MODE_CONNECTOR_DVII;
> +		else
> +			ret = DRM_MODE_CONNECTOR_DVID;
> +
> +		drm_info(ddev, "connector%d is DVI\n", index);
> +	} else if (strncmp(name, "virtual-connector", 17) == 0) {
> +		ret = DRM_MODE_CONNECTOR_VIRTUAL;
> +		drm_info(ddev, "connector%d is virtual\n", index);
> +	} else if (strncmp(name, "dpi-connector", 13) == 0) {
> +		ret = DRM_MODE_CONNECTOR_DPI;
> +		drm_info(ddev, "connector%d is DPI\n", index);
> +	} else if (strncmp(name, "hdmi-connector", 14) == 0) {
> +		int res;
> +		const char *hdmi_type;
> +
> +		res = of_property_read_string(output, "type", &hdmi_type);
> +		if (res == 0) {
> +			if (!strcmp(hdmi_type, "b"))
> +				ret = DRM_MODE_CONNECTOR_HDMIB;
> +			else
> +				ret = DRM_MODE_CONNECTOR_HDMIA;
> +		} else
> +			ret = DRM_MODE_CONNECTOR_HDMIA;
> +
> +		drm_info(ddev, "connector%d is HDMI, type is %s\n",
> +			index, hdmi_type);
> +	} else {
> +		ret = DRM_MODE_CONNECTOR_Unknown;
> +		drm_info(ddev, "The type of connector%d is unknown\n", index);
> +	}
> +
> +	return ret;
> +}
> +
> +
> +struct lsdc_connector *lsdc_connector_init(struct lsdc_device *ldev,
> +					   unsigned int index)
> +{
> +	struct drm_device *ddev = &ldev->drm;
> +	struct device_node *np = ddev->dev->of_node;
> +	struct device_node *output = NULL;
> +	struct lsdc_connector *lconn;
> +	struct drm_connector *connector;
> +	bool available = false;
> +	unsigned int connector_type;
> +	int ret;
> +
> +	lconn = devm_kzalloc(ddev->dev, sizeof(*lconn), GFP_KERNEL);
> +	if (lconn == NULL)
> +		return ERR_PTR(-ENOMEM);
> +
> +	lconn->index = index;
> +
> +	output = of_parse_phandle(np, "output-ports", index);
> +	if (output) {
> +		struct device_node *disp_tims_np;
> +
> +		available = of_device_is_available(output);
> +
> +		if (available == false) {
> +			drm_info(ddev, "connector%d is not available\n", index);
> +			of_node_put(output);
> +			return NULL;
> +		}
> +
> +		lconn->has_edid = of_property_read_bool(output, "edid");
> +		disp_tims_np = of_get_child_by_name(output, "display-timings");
> +		if (disp_tims_np) {
> +			of_node_put(disp_tims_np);
> +			lconn->has_disp_tim = true;
> +		} else
> +			lconn->has_disp_tim = false;
> +	} else
> +		drm_warn(ddev, "no output-ports property, please update dtb\n");
> +
> +	/*
> +	 * Providing a blindly support even through there is
> +	 * no output-ports property in the dtb.
> +	 */
> +	if (lconn->has_edid) {
> +		lsdc_get_edid_from_dtb(output, lconn->edid_data);
> +		drm_info(ddev, "connector%d provide edid\n", index);
> +	}
> +
> +	if (lconn->has_disp_tim) {
> +		lsdc_get_display_timings_from_dtb(output, &lconn->disp_tim);
> +		drm_info(ddev, "connector%d provide display timings\n", index);
> +	}
> +
> +	connector_type = lsdc_get_connector_type(ddev, output, index);
> +
> +	if (output)
> +		of_node_put(output);
> +
> +	connector = &lconn->base;
> +
> +	if (connector_type == DRM_MODE_CONNECTOR_VIRTUAL)
> +		goto SKIPED_CREATE_DDC;
> +
> +	/* bypass the ddc creation if the edid or display timing is provided */
> +	if ((lconn->has_edid == false) &&
> +	    (lconn->has_disp_tim == false)) {
> +		const struct lsdc_chip_desc * const dc = ldev->desc;
> +
> +		if (dc->have_builtin_i2c)
> +			lconn->ddc = lsdc_create_i2c_chan(ddev, index);
> +		else
> +			lconn->ddc = lsdc_get_i2c_adapter(ddev, index);
> +
> +		if (lconn->ddc && (IS_ERR(lconn->ddc) == false)) {
> +			drm_info(ddev, "i2c%d for connector%d created\n",
> +					i2c_adapter_id(lconn->ddc), index);
> +
> +		} else
> +			drm_warn(ddev, "Get i2c adapter failed: %ld\n",
> +					PTR_ERR(lconn->ddc));
> +	}
> +
> +	/* only pull if the connector have a ddc */
> +	connector->polled = DRM_CONNECTOR_POLL_CONNECT |
> +			    DRM_CONNECTOR_POLL_DISCONNECT;
> +
> +SKIPED_CREATE_DDC:
> +	ret = drm_connector_init_with_ddc(ddev,
> +					  connector,
> +					  &lsdc_connector_funcs,
> +					  connector_type,
> +					  lconn->ddc);
> +	if (ret) {
> +		drm_err(ddev, "init connector%d failed\n", index);
> +		goto err_i2c_destroy;
> +	}
> +
> +	drm_connector_helper_add(connector, &lsdc_connector_helpers);
> +
> +	return lconn;
> +
> +err_i2c_destroy:
> +	lsdc_destroy_i2c(ddev, lconn->ddc);
> +	return NULL;
> +}
> diff --git a/drivers/gpu/drm/lsdc/lsdc_connector.h b/drivers/gpu/drm/lsdc/lsdc_connector.h
> new file mode 100644
> index 000000000000..e9f94a969f74
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_connector.h
> @@ -0,0 +1,60 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2020 Loongson Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sub license, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
> + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
> + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
> + * USE OR OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * The above copyright notice and this permission notice (including the
> + * next paragraph) shall be included in all copies or substantial portions
> + * of the Software.
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +
> +#ifndef __LSDC_CONNECTOR_H__
> +#define __LSDC_CONNECTOR_H__
> +
> +#include <drm/drm_device.h>
> +#include <drm/drm_connector.h>
> +
> +struct lsdc_connector {
> +	struct drm_connector base;
> +
> +	struct i2c_adapter *ddc;
> +
> +	/* pass EDID from dtb support */
> +	unsigned char edid_data[EDID_LENGTH];
> +	bool has_edid;
> +
> +	/* pass display timmings from dtb support */
> +	struct display_timings *disp_tim;
> +	bool has_disp_tim;
> +
> +	int index;
> +};
> +
> +#define to_lsdc_connector(x)        \
> +		container_of(x, struct lsdc_connector, base)
> +
> +struct lsdc_connector *lsdc_connector_init(struct lsdc_device *ldev,
> +					   unsigned int index);
> +
> +#endif
> diff --git a/drivers/gpu/drm/lsdc/lsdc_crtc.c b/drivers/gpu/drm/lsdc/lsdc_crtc.c
> new file mode 100644
> index 000000000000..7531389f4896
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_crtc.c
> @@ -0,0 +1,440 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2020 Loongson Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sub license, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
> + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
> + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
> + * USE OR OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * The above copyright notice and this permission notice (including the
> + * next paragraph) shall be included in all copies or substantial portions
> + * of the Software.
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#include <drm/drm_print.h>
> +#include <drm/drm_device.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_plane.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_vblank.h>
> +#include <drm/drm_drv.h>
> +
> +
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_gem_atomic_helper.h>
> +#include <drm/drm_damage_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);
> +	struct drm_crtc_state *state = crtc->state;
> +	u32 val;
> +
> +	if (state->enable) {
> +		val = lsdc_reg_read32(ldev, LSDC_INT_REG);
> +
> +		if (index == 0)
> +			val |= INT_CRTC0_VS_EN;
> +		else if (index == 1)
> +			val |= INT_CRTC1_VS_EN;
> +
> +		lsdc_reg_write32(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_reg_read32(ldev, LSDC_INT_REG);
> +
> +	if (index == 0)
> +		val &= ~INT_CRTC0_VS_EN;
> +	else if (index == 1)
> +		val &= ~INT_CRTC1_VS_EN;
> +
> +	lsdc_reg_write32(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;
> +
> +	/* The crtc get soft reset if bit 20 of CRTC*_CFG_REG
> +	 * is write with falling edge.
> +	 *
> +	 * Doing this to switch from soft reset state to working state
> +	 */
> +	if (index == 0) {
> +		val = CFG_RESET_BIT | CFG_OUTPUT_EN_BIT | LSDC_PF_XRGB8888;
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_CFG_REG, val);
> +	} else if (index == 1) {
> +		val = CFG_RESET_BIT | CFG_OUTPUT_EN_BIT | LSDC_PF_XRGB8888;
> +		lsdc_reg_write32(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;
> +
> +	priv_crtc_state->pix_fmt = val & CFG_PIX_FMT_MASK;
> +
> +	__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;
> +	struct drm_device *ddev = crtc->dev;
> +
> +	if (drm_WARN_ON(ddev, !crtc->state))
> +		return NULL;
> +
> +	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->pparams, &old_priv_state->pparams,
> +		sizeof(new_priv_state->pparams));
> +
> +	new_priv_state->pix_fmt = old_priv_state->pix_fmt;
> +
> +	return &new_priv_state->base;
> +}
> +
> +
> +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,
> +	.enable_vblank = lsdc_crtc_enable_vblank,
> +	.disable_vblank = lsdc_crtc_disable_vblank,
> +};
> +
> +
> +static enum drm_mode_status
> +lsdc_crtc_helper_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_chip_desc *desc = ldev->desc;
> +
> +	if (mode->hdisplay > desc->max_width)
> +		return MODE_BAD_HVALUE;
> +	if (mode->vdisplay > desc->max_height)
> +		return MODE_BAD_VVALUE;
> +
> +	if (mode->clock > desc->max_pixel_clk) {
> +		drm_dbg_kms(ddev, "mode %dx%d, pixel clock=%d is too high\n",
> +				mode->hdisplay, mode->vdisplay, mode->clock);
> +		return MODE_CLOCK_HIGH;
> +	}
> +
> +	/* the crtc hardware dma take 256 bytes once a time
> +	 * TODO: check RGB565 support
> +	 */
> +	if ((mode->hdisplay * 4) % desc->stride_alignment) {
> +		drm_dbg_kms(ddev, "stride is not %u bytes aligned\n",
> +				desc->stride_alignment);
> +		return MODE_BAD;
> +	}
> +
> +	return MODE_OK;
> +}

mode_valid will only prevent the mode from being advertised to the
userspace, but you need atomic_check if you want to prevent those modes
to be used by anybody.

> +
> +static void lsdc_update_pixclk(struct drm_crtc *crtc, unsigned int pixclk, bool verbose)
> +{
> +	struct lsdc_display_pipe *dispipe;
> +	struct lsdc_pll *pixpll;
> +	const struct lsdc_pixpll_funcs *clkfun;
> +	struct lsdc_crtc_state *priv_crtc_state;
> +
> +	priv_crtc_state = to_lsdc_crtc_state(crtc->state);
> +
> +	dispipe = container_of(crtc, struct lsdc_display_pipe, crtc);
> +	pixpll = &dispipe->pixpll;
> +	clkfun = pixpll->funcs;
> +
> +	/* config the pixel pll */
> +	clkfun->update(pixpll, &priv_crtc_state->pparams);
> +
> +	if (verbose)
> +		clkfun->print(pixpll, pixclk);
> +}
> +
> +
> +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->adjusted_mode;
> +	unsigned int hr = mode->hdisplay;
> +	unsigned int hss = mode->hsync_start;
> +	unsigned int hse = mode->hsync_end;
> +	unsigned int hfl = mode->htotal;
> +	unsigned int vr = mode->vdisplay;
> +	unsigned int vss = mode->vsync_start;
> +	unsigned int vse = mode->vsync_end;
> +	unsigned int vfl = mode->vtotal;
> +	unsigned int pixclock = mode->clock;
> +	unsigned int index = drm_crtc_index(crtc);
> +
> +
> +	if (index == 0) {
> +		/* CRTC 0 */
> +		u32 hsync, vsync;
> +
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_FB_ORIGIN_REG, 0);
> +
> +		/* 26:16 total pixels, 10:0 visiable pixels, in horizontal */
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_HDISPLAY_REG,
> +			(mode->crtc_htotal << 16) | mode->crtc_hdisplay);
> +
> +		/* 26:16 total pixels, 10:0 visiable pixels, in vertical */
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_VDISPLAY_REG,
> +			(mode->crtc_vtotal << 16) | mode->crtc_vdisplay);
> +
> +		/* 26:16 hsync end, 10:0 hsync start */
> +		hsync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start;
> +
> +		if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> +			hsync |= INV_HSYNC_BIT;
> +
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_HSYNC_REG, EN_HSYNC_BIT | hsync);
> +
> +		/* 26:16 vsync end, 10:0 vsync start */
> +		vsync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start;
> +
> +		if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +			vsync |= INV_VSYNC_BIT;
> +
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_VSYNC_REG, EN_VSYNC_BIT | vsync);
> +
> +	} else if (index == 1) {
> +		/* CRTC 1 */
> +		u32 hsync, vsync;
> +
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_FB_ORIGIN_REG, 0);
> +
> +		/* 26:16 total pixels, 10:0 visiable pixels, in horizontal */
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_HDISPLAY_REG,
> +			(mode->crtc_htotal << 16) | mode->crtc_hdisplay);
> +
> +		/* 26:16 total pixels, 10:0 visiable pixels, in vertical */
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_VDISPLAY_REG,
> +			(mode->crtc_vtotal << 16) | mode->crtc_vdisplay);
> +
> +		/* 26:16 hsync end, 10:0 hsync start */
> +		hsync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start;
> +
> +		if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> +			hsync |= INV_HSYNC_BIT;
> +
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_HSYNC_REG, EN_HSYNC_BIT | hsync);
> +
> +		/* 26:16 vsync end, 10:0 vsync start */
> +		vsync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start;
> +
> +		if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +			vsync |= INV_VSYNC_BIT;
> +
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_VSYNC_REG, EN_VSYNC_BIT | vsync);
> +	}
> +
> +	drm_dbg_kms(ddev, "hdisplay=%d, hsync_start=%d, hsync_end=%d, htotal=%d\n",
> +			hr, hss, hse, hfl);
> +
> +	drm_dbg_kms(ddev, "vdisplay=%d, vsync_start=%d, vsync_end=%d, vtotal=%d\n",
> +			vr, vss, vse, vfl);
> +
> +	drm_dbg_kms(ddev, "%s modeset: %ux%u\n", crtc->name, hr, vr);
> +
> +	lsdc_update_pixclk(crtc, pixclock, ldev->verbose);
> +}
> +
> +
> +static void lsdc_enable_display(struct lsdc_device *ldev, unsigned int index)
> +{
> +	u32 val;
> +
> +	if (index == 0) {
> +		val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG);
> +		val |= CFG_OUTPUT_EN_BIT;
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_CFG_REG, val);
> +	} else if (index == 1) {
> +		val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG);
> +		val |= CFG_OUTPUT_EN_BIT;
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_CFG_REG, val);
> +	}
> +}
> +
> +
> +static void lsdc_disable_display(struct lsdc_device *ldev, unsigned int index)
> +{
> +	u32 val;
> +
> +	if (index == 0) {
> +		val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG);
> +		val &= ~CFG_OUTPUT_EN_BIT;
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_CFG_REG, val);
> +	} else if (index == 1) {
> +		val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG);
> +		val &= ~CFG_OUTPUT_EN_BIT;
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_CFG_REG, val);
> +	}
> +}
> +
> +
> +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_kms(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);
> +
> +	drm_crtc_vblank_off(crtc);
> +
> +	lsdc_disable_display(ldev, drm_crtc_index(crtc));
> +
> +	drm_dbg_kms(ddev, "%s: disabled\n", crtc->name);
> +}
> +
> +
> +static void lsdc_crtc_atomic_flush(struct drm_crtc *crtc,
> +				   struct drm_atomic_state *state)
> +{
> +	struct drm_pending_vblank_event *event = crtc->state->event;
> +
> +	if (event) {
> +		crtc->state->event = NULL;
> +
> +		spin_lock_irq(&crtc->dev->event_lock);
> +		if (drm_crtc_vblank_get(crtc) == 0)
> +			drm_crtc_arm_vblank_event(crtc, event);
> +		else
> +			drm_crtc_send_vblank_event(crtc, event);
> +		spin_unlock_irq(&crtc->dev->event_lock);
> +	}
> +}
> +
> +
> +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = {
> +	.mode_valid = lsdc_crtc_helper_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_flush = lsdc_crtc_atomic_flush,
> +};
> +
> +
> +
> +/**
> + * lsdc_crtc_init
> + *
> + * @ddev: point to the drm_device structure
> + * @index: hardware crtc index
> + *
> + * Init CRTC
> + */
> +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;
> +
> +	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");
> +
> +	return drm_crtc_init_with_planes(ddev,
> +					 crtc,
> +					 primary,
> +					 cursor,
> +					 &lsdc_crtc_funcs,
> +					 "crtc%d",
> +					 index);
> +}
> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.c b/drivers/gpu/drm/lsdc/lsdc_drv.c
> new file mode 100644
> index 000000000000..aac8901c3431
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.c
> @@ -0,0 +1,846 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2020 Loongson Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sub license, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
> + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
> + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
> + * USE OR OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * The above copyright notice and this permission notice (including the
> + * next paragraph) shall be included in all copies or substantial portions
> + * of the Software.
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#include <linux/errno.h>
> +#include <linux/string.h>
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/of_reserved_mem.h>
> +
> +#include <drm/drm_drv.h>
> +#include <drm/drm_aperture.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_plane.h>
> +#include <drm/drm_vblank.h>
> +#include <drm/drm_debugfs.h>
> +#include <drm/drm_fb_helper.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_damage_helper.h>
> +#include <drm/drm_probe_helper.h>
> +
> +#include "lsdc_drv.h"
> +#include "lsdc_irq.h"
> +#include "lsdc_regs.h"
> +#include "lsdc_connector.h"
> +#include "lsdc_pll.h"
> +
> +
> +#define DRIVER_AUTHOR		"Sui Jingfeng <suijingfeng at loongson.cn>"
> +#define DRIVER_NAME		"lsdc"
> +#define DRIVER_DESC		"drm driver for loongson's display controller"
> +#define DRIVER_DATE		"20200701"
> +#define DRIVER_MAJOR		1
> +#define DRIVER_MINOR		0
> +#define DRIVER_PATCHLEVEL	0
> +
> +static int lsdc_modeset = 1;
> +MODULE_PARM_DESC(modeset, "Enable/disable CMA-based KMS(1 = enabled(default), 0 = disabled)");
> +module_param_named(modeset, lsdc_modeset, int, 0644);
> +
> +static int lsdc_cached_coherent = 1;
> +MODULE_PARM_DESC(cached_coherent, "uss cached coherent mapping(1 = enabled(default), 0 = disabled)");
> +module_param_named(cached_coherent, lsdc_cached_coherent, int, 0644);
> +
> +static int lsdc_dirty_update = -1;
> +MODULE_PARM_DESC(dirty_update, "enable dirty update(1 = enabled, 0 = disabled(default))");
> +module_param_named(dirty_update, lsdc_dirty_update, int, 0644);
> +
> +static int lsdc_use_vram_helper = -1;
> +MODULE_PARM_DESC(use_vram_helper, "use vram helper based solution(1 = enabled, 0 = disabled(default))");
> +module_param_named(use_vram_helper, lsdc_use_vram_helper, int, 0644);
> +
> +static int lsdc_verbose = -1;
> +MODULE_PARM_DESC(verbose, "Enable/disable print some key information");
> +module_param_named(verbose, lsdc_verbose, int, 0644);

It's not really clear to me why you need any of those parameters. Why
would a user want to use a non coherent mapping for example?

> +
> +static const struct lsdc_chip_desc dc_in_ls2k1000 = {
> +	.chip = LSDC_CHIP_2K1000,
> +	.num_of_crtc = LSDC_MAX_CRTC,
> +	/* ls2k1000 user manual say the max pixel clock can be about 200MHz */
> +	.max_pixel_clk = 200000,
> +	.max_width = 2560,
> +	.max_height = 2048,
> +	.num_of_hw_cursor = 1,
> +	.hw_cursor_w = 32,
> +	.hw_cursor_h = 32,
> +	.have_builtin_i2c = false,
> +	.stride_alignment = 256,
> +};
> +
> +static const struct lsdc_chip_desc dc_in_ls2k0500 = {
> +	.chip = LSDC_CHIP_2K0500,
> +	.num_of_crtc = LSDC_MAX_CRTC,
> +	.max_pixel_clk = 200000,
> +	.max_width = 2048,
> +	.max_height = 2048,
> +	.num_of_hw_cursor = 1,
> +	.hw_cursor_w = 32,
> +	.hw_cursor_h = 32,
> +	.have_builtin_i2c = false,
> +	.stride_alignment = 256,
> +};
> +
> +static const struct lsdc_chip_desc dc_in_ls7a1000 = {
> +	.chip = LSDC_CHIP_7A1000,
> +	.num_of_crtc = LSDC_MAX_CRTC,
> +	.max_pixel_clk = 200000,
> +	.max_width = 2048,
> +	.max_height = 2048,
> +	.num_of_hw_cursor = 1,
> +	.hw_cursor_w = 32,
> +	.hw_cursor_h = 32,
> +	.have_builtin_i2c = true,
> +	.stride_alignment = 256,
> +};
> +
> +/*
> + * We need the device tree provided additional information to tell the DC
> + * in different chip apart.
> + */
> +static const struct of_device_id lsdc_drm_of_match[] = {
> +	{ .compatible = "loongson,loongson64c-4core-ls7a", .data = &dc_in_ls7a1000 },
> +	{ .compatible = "loongson,loongson64g-4core-ls7a", .data = &dc_in_ls7a1000 },
> +	{ .compatible = "loongson,loongson64-2core-2k1000", .data = &dc_in_ls2k1000 },
> +	{ .compatible = "loongson,loongson2k1000", .data = &dc_in_ls2k1000 },
> +	{ .compatible = "loongson,ls2k1000", .data = &dc_in_ls2k1000 },
> +	{ .compatible = "loongson,display-subsystem", .data = &dc_in_ls2k1000 },
> +	{ .compatible = "loongson,ls-fb", .data = &dc_in_ls2k1000 },
> +	{ .compatible = "loongson,loongson2k0500", .data = &dc_in_ls2k0500 },
> +	{ /* sentinel */ },
> +};
> +
> +
> +static struct drm_framebuffer *
> +lsdc_drm_gem_fb_create(struct drm_device *ddev, struct drm_file *file,
> +			  const struct drm_mode_fb_cmd2 *mode_cmd)
> +{
> +	struct lsdc_device *ldev = to_lsdc(ddev);
> +
> +	if (ldev->dirty_update)
> +		return drm_gem_fb_create_with_dirty(ddev, file, mode_cmd);
> +
> +	return drm_gem_fb_create(ddev, file, mode_cmd);
> +}
> +
> +
> +static enum drm_mode_status lsdc_device_mode_valid(struct drm_device *ddev,
> +					const struct drm_display_mode *mode)
> +{
> +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER
> +	struct lsdc_device *ldev = to_lsdc(ddev);
> +
> +	if (ldev->use_vram_helper == true)
> +		return drm_vram_helper_mode_valid(ddev, mode);
> +#endif
> +
> +	return MODE_OK;
> +}
> +
> +
> +static const struct drm_mode_config_funcs lsdc_mode_config_funcs = {
> +	.fb_create = lsdc_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 = lsdc_device_mode_valid,
> +};
> +
> +
> +#ifdef CONFIG_DEBUG_FS
> +static int lsdc_show_pxlclock(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);
> +	struct drm_crtc *crtc;
> +
> +	drm_for_each_crtc(crtc, ddev) {
> +		struct drm_crtc_state *state = crtc->state;
> +		int index = drm_crtc_index(crtc);
> +		struct lsdc_display_pipe *pipe = &ldev->disp_pipe[index];
> +		struct lsdc_pll *pixpll = &pipe->pixpll;
> +		const struct lsdc_pixpll_funcs *clkfun = pixpll->funcs;
> +		unsigned int clkrate = clkfun->get_clock_rate(pixpll);
> +		unsigned int mode_clock = crtc->mode.crtc_clock;
> +
> +		seq_printf(m, "\n");
> +		seq_printf(m, "CRTC%u, %s, %s\n", index,
> +				state->active ? "active" : "non-active",
> +				state->enable ? "enabled" : "disabled");
> +		seq_printf(m, "hw clock: %u kHz\n", clkrate);
> +		seq_printf(m, "mode: %u kHz\n", mode_clock);
> +
> +		seq_printf(m, "div_out=%u, loopc=%u, div_ref=%u\n",
> +				pixpll->core_params.div_out,
> +				pixpll->core_params.loopc,
> +				pixpll->core_params.div_ref);
> +	}
> +
> +	return 0;
> +}
> +
> +
> +static int lsdc_mm_show(struct seq_file *m, void *arg)
> +{
> +	struct drm_info_node *node = (struct drm_info_node *) m->private;
> +	struct drm_device *ddev = node->minor->dev;
> +	struct drm_printer p = drm_seq_file_printer(m);
> +
> +	drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p);
> +	return 0;
> +}
> +
> +static struct drm_info_list lsdc_debugfs_list[] = {
> +	{ "clocks", lsdc_show_pxlclock, 0 },
> +	{ "mm",     lsdc_mm_show,   0, NULL },
> +};
> +
> +static void lsdc_debugfs_init(struct drm_minor *minor)
> +{
> +	drm_debugfs_create_files(lsdc_debugfs_list,
> +				 ARRAY_SIZE(lsdc_debugfs_list),
> +				 minor->debugfs_root,
> +				 minor);
> +}
> +#endif
> +
> +
> +static struct drm_gem_object *
> +lsdc_drm_gem_create_object(struct drm_device *ddev, size_t size)
> +{
> +	struct lsdc_device *ldev = to_lsdc(ddev);
> +	struct drm_gem_cma_object *obj = kzalloc(sizeof(*obj), GFP_KERNEL);
> +
> +	if (!obj)
> +		return ERR_PTR(-ENOMEM);
> +
> +	if (ldev->cached_coherent)
> +		obj->map_noncoherent = true;
> +
> +	return &obj->base;
> +}
> +
> +
> +static int lsdc_gem_cma_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_chip_desc *desc = ldev->desc;
> +	unsigned int bytes_per_pixel = (args->bpp + 7) / 8;
> +	unsigned int pitch = bytes_per_pixel * args->width;
> +
> +	/*
> +	 * The dc in ls7a1000/ls2k1000/ls2k0500 require the stride be a
> +	 * multiple of 256 bytes, which is for sake of optimize dma data
> +	 * transfer.
> +	 */
> +	args->pitch = roundup(pitch, desc->stride_alignment);
> +
> +	return drm_gem_cma_dumb_create_internal(file, ddev, args);
> +}
> +
> +
> +DEFINE_DRM_GEM_CMA_FOPS(lsdc_drv_fops);
> +
> +
> +static const struct drm_driver lsdc_drm_driver_cma_stub = {
> +	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
> +
> +	.lastclose = drm_fb_helper_lastclose,
> +	.fops = &lsdc_drv_fops,
> +	.gem_create_object = lsdc_drm_gem_create_object,
> +
> +	.name = DRIVER_NAME,
> +	.desc = DRIVER_DESC,
> +	.date = DRIVER_DATE,
> +	.major = DRIVER_MAJOR,
> +	.minor = DRIVER_MINOR,
> +	.patchlevel = DRIVER_PATCHLEVEL,
> +
> +	DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(lsdc_gem_cma_dumb_create),
> +
> +#ifdef CONFIG_DEBUG_FS
> +	.debugfs_init = lsdc_debugfs_init,
> +#endif
> +};
> +
> +
> +DEFINE_DRM_GEM_FOPS(lsdc_fops);
> +
> +
> +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER
> +static const struct drm_driver lsdc_vram_driver_stub = {
> +	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
> +	.fops = &lsdc_fops,
> +
> +	.name = DRIVER_NAME,
> +	.desc = DRIVER_DESC,
> +	.date = DRIVER_DATE,
> +	.major = DRIVER_MAJOR,
> +	.minor = DRIVER_MINOR,
> +	.patchlevel = DRIVER_PATCHLEVEL,
> +
> +	DRM_GEM_VRAM_DRIVER,
> +};
> +#endif
> +
> +
> +static int lsdc_modeset_init(struct lsdc_device *ldev, const uint32_t num_crtc)
> +{
> +	struct drm_device *ddev = &ldev->drm;
> +	struct lsdc_display_pipe *dispipe;
> +	struct lsdc_connector *lconn;
> +	unsigned int i;
> +	int ret;
> +
> +	/* first find all of connector available */
> +	for (i = 0; i < num_crtc; i++) {
> +		lconn = lsdc_connector_init(ldev, i);
> +		dispipe = &ldev->disp_pipe[i];
> +
> +		if (IS_ERR(lconn))
> +			return PTR_ERR(lconn);
> +		else if (lconn == NULL) {
> +			dispipe->lconn = NULL;
> +			dispipe->available = false;
> +			continue;
> +		}
> +
> +		/* take a record */
> +		dispipe->available = true;
> +		dispipe->lconn = lconn;
> +		ldev->num_output++;
> +	}
> +
> +	drm_info(ddev, "number of outputs: %u\n", ldev->num_output);
> +
> +	for (i = 0; i < num_crtc; i++) {
> +		struct lsdc_display_pipe * const dispipe = &ldev->disp_pipe[i];
> +		struct drm_plane * const primary = &dispipe->primary;
> +		struct drm_plane * const cursor = &dispipe->cursor;
> +		struct drm_encoder * const encoder = &dispipe->encoder;
> +		struct drm_crtc * const crtc = &dispipe->crtc;
> +		struct lsdc_pll * const pixpll = &dispipe->pixpll;
> +
> +		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 crtc
> +		 * index is equal to the hardware crtc index. That is:
> +		 * display pipe 0 = crtc0 + dvo0 + encoder0
> +		 * display pipe 1 = crtc1 + dvo1 + encoder1
> +		 */
> +		ret = lsdc_crtc_init(ddev, crtc, i, primary, cursor);
> +		if (ret)
> +			return ret;
> +
> +		if (dispipe->available) {
> +			ret = lsdc_encoder_init(encoder,
> +						&dispipe->lconn->base,
> +						ddev,
> +						i,
> +						ldev->num_output);
> +			if (ret)
> +				return ret;
> +		}
> +
> +		drm_info(ddev, "display pipe %u initialized\n", i);
> +	}
> +
> +	return 0;
> +}
> +
> +
> +static int lsdc_mode_config_init(struct lsdc_device *ldev)
> +{
> +	const struct lsdc_chip_desc * const descp = ldev->desc;
> +	struct drm_device *ddev = &ldev->drm;
> +	int ret;
> +
> +	spin_lock_init(&ldev->reglock);
> +
> +	drm_mode_config_init(ddev);
> +
> +	ddev->mode_config.funcs = &lsdc_mode_config_funcs;
> +	ddev->mode_config.min_width = 1;
> +	ddev->mode_config.min_height = 1;
> +	ddev->mode_config.preferred_depth = 24;
> +	ddev->mode_config.prefer_shadow = 0;
> +
> +	ddev->mode_config.max_width = 4096;
> +	ddev->mode_config.max_height = 4096;
> +
> +	ddev->mode_config.cursor_width = descp->hw_cursor_h;
> +	ddev->mode_config.cursor_height = descp->hw_cursor_h;
> +
> +	if (ldev->vram_base)
> +		ddev->mode_config.fb_base = ldev->vram_base;
> +
> +	ret = lsdc_modeset_init(ldev, descp->num_of_crtc);
> +	if (ret)
> +		goto out_mode_config;
> +
> +	drm_mode_config_reset(ddev);
> +
> +	return 0;
> +
> +out_mode_config:
> +	drm_mode_config_cleanup(ddev);
> +
> +	return ret;
> +}
> +
> +
> +static void lsdc_mode_config_fini(struct drm_device *ddev)
> +{
> +	struct lsdc_device *ldev = to_lsdc(ddev);
> +
> +	/* disable output polling */
> +	drm_kms_helper_poll_fini(ddev);
> +
> +	drm_dev_unregister(ddev);
> +
> +	devm_free_irq(ddev->dev, ldev->irq, ddev);
> +
> +	/* shutdown all CRTC, for driver unloading */
> +	drm_atomic_helper_shutdown(ddev);
> +
> +	drm_mode_config_cleanup(ddev);
> +}
> +
> +/*
> + * We need a function to tell different chips apart.
> + * Because there are difference between the dc in ls7a1000 and the dc in
> + * ls2k1000, ls7a1000 have two gpio emulated i2c built-in, but ls2k1000
> + * don't have such hardware feature. ls2k1000 grab i2c adapter from other
> + * module, eithor hardware i2c or external gpio-emulated i2c.
> + *
> + * Beside, the hardware pixel pll unit is also different.
> + */
> +static int lsdc_determine_chip(struct lsdc_device *ldev)
> +{
> +	struct device_node *np;
> +	const char *model;
> +	const char *compat;
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(lsdc_drm_of_match); ++i) {
> +		compat = lsdc_drm_of_match[i].compatible;
> +
> +		np = of_find_compatible_node(NULL, NULL, compat);
> +		if (np) {
> +
> +			/* get additional information */
> +			of_property_read_string(np, "model", &model);
> +
> +			of_node_put(np);
> +
> +			ldev->desc = lsdc_drm_of_match[i].data;
> +
> +			break;
> +		}
> +	}
> +
> +	if (ldev->desc == NULL) {
> +		drm_err(&ldev->drm, "unknown dc ip core, abort\n");
> +		return -ENOENT;
> +	}
> +
> +	drm_info(&ldev->drm, "%s found, model: %s\n", compat, model);
> +
> +	return 0;
> +}
> +
> +
> +static int lsdc_drm_suspend(struct device *dev)
> +{
> +	struct drm_device *ddev = dev_get_drvdata(dev);
> +
> +	return drm_mode_config_helper_suspend(ddev);
> +}
> +
> +
> +static int lsdc_drm_resume(struct device *dev)
> +{
> +	struct drm_device *ddev = dev_get_drvdata(dev);
> +
> +	return drm_mode_config_helper_resume(ddev);
> +}
> +
> +
> +static int lsdc_pm_freeze(struct device *dev)
> +{
> +	return lsdc_drm_suspend(dev);
> +}
> +
> +
> +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 int lsdc_remove_conflicting_framebuffers(const struct drm_driver *drv)
> +{
> +	/* lsdc is a pci device, but it don't have a dedicate vram bar because
> +	 * of historic reason(The display controller is ported from SoC product
> +	 * of Loongson, Loongson 2H series which date back to 2012)
> +	 * And simplefb node may have been located anywhere in memory.
> +	 */
> +	return drm_aperture_remove_conflicting_framebuffers(0, ~0, false, drv);
> +}
> +
> +
> +static int lsdc_vram_init(struct lsdc_device *ldev)
> +{
> +	struct drm_device *ddev = &ldev->drm;
> +	struct pci_dev *gpu;
> +	resource_size_t base, size;
> +	int ret;
> +
> +	/* BAR 2 of LS7A1000's GPU contain VRAM, It's GC1000 */
> +	gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7a15, NULL);
> +	base = pci_resource_start(gpu, 2);
> +	size =  pci_resource_len(gpu, 2);
> +
> +	drm_info(ddev, "vram start: 0x%llx, size: %uMB\n",
> +			base, (unsigned int)(size >> 20));
> +
> +	if (!request_mem_region(base, size, "lsdc_vram")) {
> +		drm_err(ddev, "can't reserve VRAM memory region\n");
> +		return -ENXIO;
> +	}
> +
> +	if (ldev->use_vram_helper) {
> +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER
> +		ret = drmm_vram_helper_init(ddev, base, size);
> +		if (ret) {
> +			drm_err(ddev, "can't init vram helper\n");
> +			return ret;
> +		}
> +#endif
> +	} else if (ldev->dirty_update) {
> +		ldev->vram = devm_ioremap_wc(ddev->dev, base, size);
> +		if (ldev->vram == NULL)
> +			return -ENOMEM;
> +
> +		drm_info(ddev, "vram virtual addr: 0x%llx\n", (u64)ldev->vram);
> +	}
> +
> +	ldev->vram_base = base;
> +	ldev->vram_size = size;
> +
> +	return 0;
> +}
> +
> +/*
> + * For the dc in ls7a1000, it is more reliable scanout from the VRAM.
> + * scanout from the system memory suffer from some hardware deficiency
> + * which cause the screen flickering under RAM pressure.
> + */
> +static bool lsdc_should_vram_helper_based(void)
> +{
> +	static const char * const dc_compat[] = { "pci0014,7a06.0",
> +						  "pci0014,7a06" };
> +	bool ret = false;
> +	struct device_node *np;
> +	unsigned int i;
> +
> +	for (i = 0; i < ARRAY_SIZE(dc_compat); ++i) {
> +		np = of_find_compatible_node(NULL, NULL, dc_compat[i]);
> +		if (!np)
> +			continue;
> +
> +		ret = of_property_read_bool(np, "use_vram_helper");
> +		of_node_put(np);
> +		break;
> +	}
> +
> +	if (ret)
> +		DRM_INFO("using vram base solution dictated by device tree\n");
> +
> +	return ret;
> +}
> +
> +
> +static int lsdc_pci_probe(struct pci_dev *pdev,
> +			  const struct pci_device_id * const ent)
> +{
> +	const struct drm_driver *driver = &lsdc_drm_driver_cma_stub;
> +	struct lsdc_device *ldev;
> +	struct drm_device *ddev;
> +	int ret;
> +
> +	lsdc_remove_conflicting_framebuffers(driver);
> +
> +	ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32));
> +	if (ret) {
> +		dev_err(&pdev->dev, "Set DMA Mask failed\n");
> +		return ret;
> +	}
> +
> +	ret = pcim_enable_device(pdev);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Enable pci devive failed\n");
> +		return ret;
> +	}
> +
> +	pci_set_master(pdev);
> +
> +	/* Get the optional framebuffer memory resource */
> +	ret = of_reserved_mem_device_init(&pdev->dev);
> +	if (ret && ret != -ENODEV)
> +		return ret;
> +
> +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER
> +	if (lsdc_use_vram_helper && lsdc_should_vram_helper_based()) {
> +		driver = &lsdc_vram_driver_stub;
> +		lsdc_use_vram_helper = 1;
> +		DRM_INFO("using vram helper based solution\n");
> +	}
> +#endif
> +
> +	ldev = devm_drm_dev_alloc(&pdev->dev,
> +				  driver,
> +				  struct lsdc_device,
> +				  drm);
> +	if (IS_ERR(ldev))
> +		return PTR_ERR(ldev);
> +
> +	ddev = &ldev->drm;
> +
> +	pci_set_drvdata(pdev, ddev);
> +
> +	if (lsdc_use_vram_helper > 0)
> +		ldev->use_vram_helper = true;
> +	else {
> +		if (lsdc_cached_coherent > 0) {
> +			ldev->cached_coherent = true;
> +			drm_info(ddev, "cache coherency is maintained by hardware\n");
> +		}
> +
> +		if (lsdc_dirty_update > 0) {
> +			ldev->dirty_update = true;
> +			drm_info(ddev, "dirty update enabled\n");
> +		}
> +	}
> +
> +	if (lsdc_verbose > 0)
> +		ldev->verbose = true;
> +
> +	ret = lsdc_determine_chip(ldev);
> +	if (ret)
> +		return ret;
> +
> +	/* BAR 0 contains registers */
> +	ldev->reg_base = devm_ioremap_resource(&pdev->dev, &pdev->resource[0]);
> +	if (IS_ERR(ldev->reg_base))
> +		return PTR_ERR(ldev->reg_base);
> +
> +	/* LS2K1000/LS2K0500 is SoC, don't have a VRAM */
> +	if ((ldev->desc->chip == LSDC_CHIP_7A1000) &&
> +	    (ldev->use_vram_helper || ldev->dirty_update))
> +		lsdc_vram_init(ldev);
> +
> +	ret = lsdc_mode_config_init(ldev);
> +	if (ret)
> +		goto err_out;
> +
> +
> +	ldev->irq = pdev->irq;
> +
> +	dev_info(&pdev->dev, "irq = %d\n", ldev->irq);
> +
> +	ret = devm_request_threaded_irq(&pdev->dev,
> +					pdev->irq,
> +					lsdc_irq_handler_cb,
> +					lsdc_irq_thread_cb,
> +					IRQF_ONESHOT,
> +					dev_name(&pdev->dev),
> +					ddev);
> +
> +	if (ret) {
> +		dev_err(&pdev->dev, "Failed to register lsdc interrupt\n");
> +		goto err_out;
> +	}
> +
> +	ret = drm_vblank_init(ddev, LSDC_MAX_CRTC);
> +	if (ret) {
> +		dev_err(&pdev->dev,
> +				"Fatal error during vblank init: %d\n", ret);
> +		goto err_out;
> +	}
> +
> +	/* Initialize and enable output polling */
> +	drm_kms_helper_poll_init(ddev);
> +
> +	ret = drm_dev_register(ddev, 0);
> +	if (ret)
> +		goto err_out;
> +
> +	drm_fbdev_generic_setup(ddev, 32);
> +
> +	return 0;
> +
> +err_out:
> +	drm_dev_put(ddev);
> +
> +	return ret;
> +}
> +
> +
> +static void lsdc_pci_remove(struct pci_dev *pdev)
> +{
> +	struct drm_device *ddev = pci_get_drvdata(pdev);
> +
> +	lsdc_mode_config_fini(ddev);
> +
> +	drm_dev_put(ddev);
> +
> +	pci_clear_master(pdev);
> +
> +	pci_release_regions(pdev);
> +}
> +
> +
> +static const struct pci_device_id lsdc_pciid_list[] = {
> +	{PCI_VENDOR_ID_LOONGSON, 0x7a06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0},
> +	{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_drm_init(void)
> +{
> +	struct pci_dev *pdev = NULL;
> +
> +	if (drm_firmware_drivers_only())
> +		return -EINVAL;
> +
> +	if (lsdc_modeset == 0)
> +		return -ENOENT;
> +
> +	while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) {
> +		/*
> +		 * Multiple video card workaround
> +		 *
> +		 * This integrated video driver will always be selected as
> +		 * default boot device by vgaarb system.
> +		 */
> +		if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) {
> +			DRM_INFO("Discrete graphic card detected, abort\n");
> +			return 0;
> +		}
> +	}
> +
> +	return pci_register_driver(&lsdc_pci_driver);
> +}
> +module_init(lsdc_drm_init);
> +
> +static void __exit lsdc_drm_exit(void)
> +{
> +	pci_unregister_driver(&lsdc_pci_driver);
> +}
> +module_exit(lsdc_drm_exit);
> +
> +
> +MODULE_DEVICE_TABLE(pci, lsdc_pciid_list);
> +MODULE_AUTHOR(DRIVER_AUTHOR);
> +MODULE_DESCRIPTION(DRIVER_DESC);
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.h b/drivers/gpu/drm/lsdc/lsdc_drv.h
> new file mode 100644
> index 000000000000..89cf15248c3a
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.h
> @@ -0,0 +1,216 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2020 Loongson Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sub license, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
> + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
> + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
> + * USE OR OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * The above copyright notice and this permission notice (including the
> + * next paragraph) shall be included in all copies or substantial portions
> + * of the Software.
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#ifndef __LSDC_DRV_H__
> +#define __LSDC_DRV_H__
> +
> +#include <drm/drm_print.h>
> +#include <drm/drm_device.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_plane.h>
> +#include <drm/drm_encoder.h>
> +
> +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER
> +#include <drm/drm_gem_vram_helper.h>
> +#endif
> +
> +#include "lsdc_regs.h"
> +#include "lsdc_pll.h"
> +
> +#define LSDC_MAX_CRTC           2
> +
> +/* There is only a 1:1 mapping of encoders and connectors for lsdc */
> +/*
> + *      +-------------------+                                      _________
> + *      |                   |                                     |         |
> + *      |  CRTC0 --> DVO0 ---------> Encoder0 --> Connector0 ---> | Monotor |
> + *      |                   |           ^            ^            |_________|
> + *      |                   |           |            |
> + *      |                <----- i2c0 ----------------+
> + *      |   LSDC IP CORE    |
> + *      |                <----- i2c1 ----------------+
> + *      |                   |           |            |             _________
> + *      |                   |           |            |            |         |
> + *      |  CRTC1 --> DVO1 ---------> Encoder1 --> Connector1 ---> |  Panel  |
> + *      |                   |                                     |_________|
> + *      +-------------------+
> + */
> +
> +enum loongson_dc_family {
> +	LSDC_CHIP_UNKNOWN = 0,
> +	LSDC_CHIP_2K1000 = 1, /* 2-Core SoC, 64-bit */
> +	LSDC_CHIP_7A1000 = 2, /* North bridge of LS3A3000/LS3A4000/LS3A5000 */
> +	LSDC_CHIP_2K0500 = 3, /* Reduced version of LS2K1000, single core */
> +	LSDC_CHIP_7A2000 = 4, /* Enhancement version of 7A1000 */
> +	LSDC_CHIP_LAST,
> +};
> +
> +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] */
> +};
> +
> +struct lsdc_chip_desc {
> +	enum loongson_dc_family chip;
> +	uint32_t num_of_crtc;
> +
> +	uint32_t max_pixel_clk;
> +
> +	uint32_t max_width;
> +	uint32_t max_height;
> +
> +	uint32_t num_of_hw_cursor;
> +	uint32_t hw_cursor_w;
> +	uint32_t hw_cursor_h;
> +	bool have_builtin_i2c;
> +	uint32_t stride_alignment;
> +};
> +
> +
> +/**
> + * struct lsdc_display_pipe - simple display pipeline
> + * @crtc: CRTC control structure
> + * @plane: Plane control structure
> + * @encoder: Encoder control structure
> + * @pixpll: Pll control structure
> + * @connector: point to connector control structure
> + *
> + * display pipeline with plane, crtc, encoder, pll collapsed into one
> + * entity. It should be initialized by calling drm_simple_display_pipe_init().
> + */
> +struct lsdc_display_pipe {
> +	struct drm_crtc crtc;
> +	struct drm_plane primary;
> +	struct drm_plane cursor;
> +	struct drm_encoder encoder;
> +	struct lsdc_pll pixpll;
> +	struct lsdc_connector *lconn;
> +
> +	int index;
> +	bool available;
> +};
> +
> +
> +struct lsdc_crtc_state {
> +	struct drm_crtc_state base;
> +	struct lsdc_pll_core_values pparams;
> +	unsigned int pix_fmt;
> +};
> +
> +
> +struct lsdc_device {
> +	struct drm_device drm;
> +
> +	void __iomem *reg_base;
> +	void __iomem *vram;
> +	resource_size_t vram_base;
> +	resource_size_t vram_size;
> +
> +	struct lsdc_display_pipe disp_pipe[LSDC_MAX_CRTC];
> +
> +	unsigned int num_output;
> +
> +	/* platform specific data */
> +	const struct lsdc_chip_desc *desc;
> +
> +	/* @reglock: protects concurrent register access */
> +	spinlock_t reglock;
> +
> +	/*
> +	 * @dirty_update: true if manual dirty update is wantted
> +	 */
> +	bool dirty_update;
> +	/*
> +	 * @cached_coherent: true if the host platform is hardware maintained
> +	 * cached coherent.
> +	 */
> +	bool cached_coherent;
> +	/*
> +	 * @use_vram_helper: using vram helper instead of cma helper base
> +	 * solution. As ls7a1000 has a dediacted video ram, the dc scanout
> +	 * from the vram is more reliable.
> +	 */
> +	bool use_vram_helper;
> +
> +	/*
> +	 * @verbose: set it to true if print useful information is wantted
> +	 */
> +	bool verbose;
> +
> +	int irq;
> +	u32 irq_status;
> +};
> +
> +#define to_lsdc(x) container_of(x, struct lsdc_device, drm)
> +
> +static inline struct lsdc_crtc_state *
> +to_lsdc_crtc_state(struct drm_crtc_state *base)
> +{
> +	return container_of(base, struct lsdc_crtc_state, base);
> +}
> +
> +static inline u32 lsdc_reg_read32(struct lsdc_device * const ldev, u32 offset)
> +{
> +	u32 val;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&ldev->reglock, flags);
> +	val = readl(ldev->reg_base + offset);
> +	spin_unlock_irqrestore(&ldev->reglock, flags);
> +
> +	return val;
> +}
> +
> +static inline void lsdc_reg_write32(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);
> +}
> +
> +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_encoder_init(struct drm_encoder * const encoder,
> +		      struct drm_connector *connector,
> +		      struct drm_device *ddev,
> +		      unsigned int index,
> +		      unsigned int total);
> +
> +#endif
> diff --git a/drivers/gpu/drm/lsdc/lsdc_encoder.c b/drivers/gpu/drm/lsdc/lsdc_encoder.c
> new file mode 100644
> index 000000000000..0cdd95f5bc37
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_encoder.c
> @@ -0,0 +1,79 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright (C) 2020 Loongson Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sub license, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
> + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
> + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
> + * USE OR OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * The above copyright notice and this permission notice (including the
> + * next paragraph) shall be included in all copies or substantial portions
> + * of the Software.
> + */
> +
> +/*
> + * Authors:
> + *	Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +#include <drm/drm_print.h>
> +#include <drm/drm_crtc_helper.h>
> +
> +#include "lsdc_drv.h"
> +
> +static const struct drm_encoder_funcs lsdc_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
> +
> +
> +int lsdc_encoder_init(struct drm_encoder * const encoder,
> +		      struct drm_connector *connector,
> +		      struct drm_device *ddev,
> +		      unsigned int index,
> +		      unsigned int total)
> +{
> +	int ret;
> +	int type;
> +
> +	encoder->possible_crtcs = BIT(index);
> +
> +	if (total == 2)
> +		encoder->possible_clones = BIT(1) | BIT(0);
> +	else if (total < 2)
> +		encoder->possible_clones = 0;
> +
> +	if (connector->connector_type == DRM_MODE_CONNECTOR_VGA)
> +		type = DRM_MODE_ENCODER_DAC;
> +	else if ((connector->connector_type == DRM_MODE_CONNECTOR_HDMIA) ||
> +		 (connector->connector_type == DRM_MODE_CONNECTOR_HDMIB) ||
> +		 (connector->connector_type == DRM_MODE_CONNECTOR_DVID))
> +		type = DRM_MODE_ENCODER_TMDS;
> +	else if (connector->connector_type == DRM_MODE_CONNECTOR_DPI)
> +		type = DRM_MODE_ENCODER_DPI;
> +	else if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL)
> +		type = DRM_MODE_ENCODER_VIRTUAL;
> +	else
> +		type = DRM_MODE_ENCODER_NONE;
> +
> +	ret = drm_encoder_init(ddev,
> +				encoder,
> +				&lsdc_encoder_funcs,
> +				type,
> +				"encoder%d",
> +				index);
> +	if (ret)
> +		return ret;
> +
> +	return drm_connector_attach_encoder(connector, encoder);
> +}
> diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c b/drivers/gpu/drm/lsdc/lsdc_i2c.c
> new file mode 100644
> index 000000000000..895e94ae6827
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c
> @@ -0,0 +1,220 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2020 Loongson Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sub license, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
> + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
> + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
> + * USE OR OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * The above copyright notice and this permission notice (including the
> + * next paragraph) shall be included in all copies or substantial portions
> + * of the Software.
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#include <linux/string.h>
> +#include <linux/i2c.h>
> +
> +#include "lsdc_drv.h"
> +#include "lsdc_regs.h"
> +#include "lsdc_i2c.h"
> +
> +/*
> + * set the state of a gpio pin indicated by mask
> + * @mask: gpio pin mask
> + */
> +static void ls7a_gpio_i2c_set(struct lsdc_i2c * const i2c, int mask, int state)
> +{
> +	struct lsdc_device *ldev = to_lsdc(i2c->ddev);
> +	u8 val;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&ldev->reglock, flags);
> +
> +	if (state) {
> +		val = readb(i2c->dir_reg);
> +		val |= mask;
> +		writeb(val, i2c->dir_reg);
> +	} else {
> +		val = readb(i2c->dir_reg);
> +		val &= ~mask;
> +		writeb(val, i2c->dir_reg);
> +
> +		val = readb(i2c->dat_reg);
> +		if (state)
> +			val |= mask;
> +		else
> +			val &= ~mask;
> +		writeb(val, i2c->dat_reg);
> +	}
> +
> +	spin_unlock_irqrestore(&ldev->reglock, flags);
> +}
> +
> +/*
> + * read value back from gpio pin
> + * @mask: gpio pin mask
> + */
> +static int ls7a_gpio_i2c_get(struct lsdc_i2c * const i2c, int mask)
> +{
> +	struct lsdc_device *ldev = to_lsdc(i2c->ddev);
> +	u8 val;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&ldev->reglock, flags);
> +
> +	/* first set this pin as input */
> +	val = readb(i2c->dir_reg);
> +	val |= mask;
> +	writeb(val, i2c->dir_reg);
> +
> +	/* then get level state from this pin */
> +	val = readb(i2c->dat_reg);
> +
> +	spin_unlock_irqrestore(&ldev->reglock, flags);
> +
> +	return (val & mask) ? 1 : 0;
> +}
> +
> +
> +/* set the state on the i2c->sda pin */
> +static void ls7a_i2c_set_sda(void *i2c, int state)
> +{
> +	struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
> +
> +	return ls7a_gpio_i2c_set(li2c, li2c->sda, state);
> +}
> +
> +/* set the state on the i2c->scl pin */
> +static void ls7a_i2c_set_scl(void *i2c, int state)
> +{
> +	struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
> +
> +	return ls7a_gpio_i2c_set(li2c, li2c->scl, state);
> +}
> +
> +/* read the value from the i2c->sda pin */
> +static int ls7a_i2c_get_sda(void *i2c)
> +{
> +	struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
> +
> +	return ls7a_gpio_i2c_get(li2c, li2c->sda);
> +}
> +
> +/* read the value from the i2c->scl pin */
> +static int ls7a_i2c_get_scl(void *i2c)
> +{
> +	struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c;
> +
> +	return ls7a_gpio_i2c_get(li2c, li2c->scl);
> +}
> +
> +/*
> + * Get i2c id from connector id
> + *
> + * TODO: get it from dtb
> + */
> +static int lsdc_get_i2c_id(struct drm_device *ddev, unsigned int index)
> +{
> +	return index;
> +}
> +
> +/*
> + * mainly for dc in ls7a1000 which have builtin gpio emulated i2c
> + *
> + * @index : output channel index, 0 for DVO0, 1 for DVO1
> + */
> +struct i2c_adapter *lsdc_create_i2c_chan(struct drm_device *ddev,
> +					 unsigned int index)
> +{
> +	struct lsdc_device *ldev = to_lsdc(ddev);
> +	struct i2c_adapter *adapter;
> +	struct lsdc_i2c *li2c;
> +	int ret;
> +
> +	li2c = devm_kzalloc(ddev->dev, sizeof(*li2c), GFP_KERNEL);
> +	if (li2c == NULL)
> +		return ERR_PTR(-ENOMEM);
> +
> +	li2c->ddev = ddev;
> +
> +	if (index == 0) {
> +		li2c->sda = 0x01;
> +		li2c->scl = 0x02;
> +	} else if (index == 1) {
> +		li2c->sda = 0x04;
> +		li2c->scl = 0x08;
> +	}
> +
> +	li2c->dir_reg = ldev->reg_base + LS7A_DC_GPIO_DIR_REG;
> +	li2c->dat_reg = ldev->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); /* from VESA */
> +	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),
> +		"%s-%d", "lsdc_gpio_i2c", index);
> +
> +	i2c_set_adapdata(adapter, li2c);
> +
> +	ret = i2c_bit_add_numbered_bus(adapter);
> +	if (ret) {
> +		devm_kfree(ddev->dev, li2c);
> +		return ERR_PTR(ret);
> +	}
> +
> +	return adapter;
> +}
> +
> +
> +/*
> + * Mainly for dc in ls2k1000, ls2k0500 SoC which should get a i2c adapter
> + * from i2c susystem.
> + *
> + * @index : output channel index, 0 for DVO0, 1 for DVO1
> + */
> +struct i2c_adapter *lsdc_get_i2c_adapter(struct drm_device *ddev,
> +					 unsigned int index)
> +{
> +	unsigned int i2c_id;
> +
> +	/* find mapping between i2c id and connector id */
> +	i2c_id = lsdc_get_i2c_id(ddev, index);
> +
> +	return i2c_get_adapter(i2c_id);
> +}
> +
> +
> +void lsdc_destroy_i2c(struct drm_device *ddev, struct i2c_adapter *adapter)
> +{
> +	i2c_put_adapter(adapter);
> +}
> diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.h b/drivers/gpu/drm/lsdc/lsdc_i2c.h
> new file mode 100644
> index 000000000000..f4560db3694a
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.h
> @@ -0,0 +1,61 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2020 Loongson Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sub license, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
> + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
> + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
> + * USE OR OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * The above copyright notice and this permission notice (including the
> + * next paragraph) shall be included in all copies or substantial portions
> + * of the Software.
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +
> +#ifndef __LSDC_I2C__
> +#define __LSDC_I2C__
> +
> +#include <linux/i2c.h>
> +#include <linux/i2c-algo-bit.h>
> +
> +
> +struct lsdc_i2c {
> +	struct drm_device *ddev;
> +	struct i2c_adapter adapter;
> +	struct i2c_algo_bit_data bit;
> +	/* pin bit mask */
> +	u8 sda;
> +	u8 scl;
> +
> +	void __iomem *dir_reg;
> +	void __iomem *dat_reg;
> +};
> +
> +
> +void lsdc_destroy_i2c(struct drm_device *ddev, struct i2c_adapter *i2c);
> +
> +struct i2c_adapter *lsdc_create_i2c_chan(struct drm_device *ddev,
> +					 unsigned int con_id);
> +
> +struct i2c_adapter *lsdc_get_i2c_adapter(struct drm_device *ddev,
> +					 unsigned int con_id);
> +
> +
> +#endif
> diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.c b/drivers/gpu/drm/lsdc/lsdc_irq.c
> new file mode 100644
> index 000000000000..7620de54aae4
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_irq.c
> @@ -0,0 +1,77 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2020 Loongson Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sub license, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
> + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
> + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
> + * USE OR OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * The above copyright notice and this permission notice (including the
> + * next paragraph) shall be included in all copies or substantial portions
> + * of the Software.
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +#include <drm/drm_vblank.h>
> +
> +#include "lsdc_drv.h"
> +#include "lsdc_regs.h"
> +#include "lsdc_irq.h"
> +
> +/* Function to be called in a threaded interrupt context. */
> +irqreturn_t lsdc_irq_thread_cb(int irq, void *arg)
> +{
> +	struct drm_device *ddev = arg;
> +	struct lsdc_device *ldev = to_lsdc(ddev);
> +	struct drm_crtc *crtc;
> +
> +	/* trigger the vblank event */
> +	if (ldev->irq_status & INT_CRTC0_VS) {
> +		crtc = drm_crtc_from_index(ddev, 0);
> +		drm_crtc_handle_vblank(crtc);
> +	}
> +
> +	if (ldev->irq_status & INT_CRTC1_VS) {
> +		crtc = drm_crtc_from_index(ddev, 1);
> +		drm_crtc_handle_vblank(crtc);
> +	}
> +
> +	lsdc_reg_write32(ldev, LSDC_INT_REG, INT_CRTC0_VS_EN | INT_CRTC1_VS_EN);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +
> +/* Function to be called when the IRQ occurs */
> +irqreturn_t lsdc_irq_handler_cb(int irq, void *arg)
> +{
> +	struct drm_device *ddev = arg;
> +	struct lsdc_device *ldev = to_lsdc(ddev);
> +
> +	/* Read & Clear the interrupt status */
> +	ldev->irq_status = lsdc_reg_read32(ldev, LSDC_INT_REG);
> +	if ((ldev->irq_status & INT_STATUS_MASK) == 0) {
> +		drm_warn(ddev, "no interrupt occurs\n");
> +		return IRQ_NONE;
> +	}
> +
> +	/* clear all interrupt */
> +	lsdc_reg_write32(ldev, LSDC_INT_REG, ldev->irq_status);
> +
> +	return IRQ_WAKE_THREAD;
> +}
> diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.h b/drivers/gpu/drm/lsdc/lsdc_irq.h
> new file mode 100644
> index 000000000000..ac187538d746
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_irq.h
> @@ -0,0 +1,37 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * Copyright 2020 Loongson Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sub license, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
> + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
> + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
> + * USE OR OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * The above copyright notice and this permission notice (including the
> + * next paragraph) shall be included in all copies or substantial portions
> + * of the Software.
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#ifndef __LSDC_IRQ_H__
> +#define __LSDC_IRQ_H__
> +
> +irqreturn_t lsdc_irq_thread_cb(int irq, void *arg);
> +irqreturn_t lsdc_irq_handler_cb(int irq, void *arg);
> +
> +#endif
> diff --git a/drivers/gpu/drm/lsdc/lsdc_plane.c b/drivers/gpu/drm/lsdc/lsdc_plane.c
> new file mode 100644
> index 000000000000..62801e100a13
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_plane.c
> @@ -0,0 +1,681 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2020 Loongson Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sub license, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
> + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
> + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
> + * USE OR OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * The above copyright notice and this permission notice (including the
> + * next paragraph) shall be included in all copies or substantial portions
> + * of the Software.
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#include <drm/drm_print.h>
> +#include <drm/drm_device.h>
> +#include <drm/drm_crtc.h>
> +#include <drm/drm_plane.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_vblank.h>
> +#include <drm/drm_drv.h>
> +
> +#include <drm/drm_format_helper.h>
> +#include <drm/drm_plane_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_gem_atomic_helper.h>
> +#include <drm/drm_damage_helper.h>
> +
> +
> +#include "lsdc_drv.h"
> +#include "lsdc_regs.h"
> +#include "lsdc_pll.h"
> +
> +
> +static const uint32_t lsdc_primary_formats[] = {
> +	DRM_FORMAT_RGB565,
> +	DRM_FORMAT_XRGB8888,
> +	DRM_FORMAT_ARGB8888,
> +};
> +
> +static const uint32_t lsdc_cursor_formats[] = {
> +	DRM_FORMAT_ARGB8888,
> +};
> +
> +static const u64 lsdc_fb_format_modifiers[] = {
> +	DRM_FORMAT_MOD_LINEAR,
> +	DRM_FORMAT_MOD_INVALID
> +};
> +
> +
> +static u32 lsdc_pixfmt_to_drm_pixfmt(enum lsdc_pixel_format pf)
> +{
> +	switch (pf) {
> +	case LSDC_PF_XRGB8888:
> +		return DRM_FORMAT_XRGB8888;
> +	case LSDC_PF_RGB565:
> +		return DRM_FORMAT_RGB565;
> +	case LSDC_PF_ARGB1555:
> +		return DRM_FORMAT_ARGB1555;
> +	case LSDC_PF_ARGB4444:
> +		return DRM_FORMAT_ARGB4444;
> +	case LSDC_PF_NONE:
> +	default:
> +		return 0;
> +	}
> +}
> +
> +static const char *lsdc_pixfmt_to_string(u32 reg)
> +{
> +	switch (reg & CFG_PIX_FMT_MASK) {
> +	case LSDC_PF_XRGB8888:
> +		return "XRGB8888";
> +	case LSDC_PF_RGB565:
> +		return "RGB565";
> +	case LSDC_PF_ARGB1555:
> +		return "ARGB1555";
> +	case LSDC_PF_ARGB4444:
> +		return "ARGB4444";
> +	case LSDC_PF_NONE:
> +		return "NONE";
> +	default:
> +		return "unknown";
> +	}
> +}
> +
> +
> +
> +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 = 0;
> +	u32 fmt;
> +
> +	switch (fmt_info->format) {
> +	case DRM_FORMAT_RGB565:
> +		fmt = LSDC_PF_RGB565;
> +		break;
> +	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 (ldev->verbose)
> +		drm_info(&ldev->drm, "fmt wanted is %s\n",
> +				lsdc_pixfmt_to_string(fmt));
> +
> +	if (index == 0) {
> +		val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG);
> +		val = (val & ~CFG_PIX_FMT_MASK) | fmt;
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_CFG_REG, val);
> +		val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG);
> +	} else if (index == 1) {
> +		val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG);
> +		val = (val & ~CFG_PIX_FMT_MASK) | fmt;
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_CFG_REG, val);
> +		val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG);
> +	}
> +
> +	if (ldev->verbose)
> +		drm_info(&ldev->drm, "after update fb%d format is %s\n",
> +				index, lsdc_pixfmt_to_string(val));
> +}
> +
> +
> +static u32 lsdc_primary_get_default_format(struct drm_crtc *crtc)
> +{
> +	struct lsdc_device *ldev = to_lsdc(crtc->dev);
> +	unsigned int index = drm_crtc_index(crtc);
> +	u32 val;
> +
> +	if (index == 0)
> +		val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG);
> +	else if (index == 1)
> +		val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG);
> +
> +	return val & CFG_PIX_FMT_MASK;
> +}
> +
> +
> +static void lsdc_update_fb_start_addr(struct lsdc_device *ldev,
> +				      struct drm_crtc *crtc,
> +				      u64 paddr)
> +{
> +	unsigned int index = drm_crtc_index(crtc);
> +	u32 addr_reg;
> +	u32 cfg_reg;
> +	u32 val;
> +
> +	/*
> +	 * Find which framebuffer address register should update.
> +	 * if FB_ADDR0_REG is in using, we write the addr to FB_ADDR1_REG,
> +	 * if FB_ADDR1_REG is in using, we write the addr to FB_ADDR0_REG
> +	 */
> +	if (index == 0) {
> +		/* CRTC0 */
> +		val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG);
> +
> +		cfg_reg = LSDC_CRTC0_CFG_REG;
> +
> +		if (val & CFG_FB_IDX_BIT) {
> +			addr_reg = LSDC_CRTC0_FB_ADDR0_REG;
> +			drm_dbg_kms(&ldev->drm, "CRTC0 FB0 will be use\n");
> +		} else {
> +			addr_reg = LSDC_CRTC0_FB_ADDR1_REG;
> +			drm_dbg_kms(&ldev->drm, "CRTC0 FBq will be use\n");
> +		}
> +	} else if (index == 1) {
> +		/* CRTC1 */
> +		val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG);
> +
> +		cfg_reg = LSDC_CRTC1_CFG_REG;
> +
> +		if (val & CFG_FB_IDX_BIT) {
> +			addr_reg = LSDC_CRTC1_FB_ADDR0_REG;
> +			drm_dbg_kms(&ldev->drm, "CRTC1 FB0 will be use\n");
> +		} else {
> +			addr_reg = LSDC_CRTC1_FB_ADDR1_REG;
> +			drm_dbg_kms(&ldev->drm, "CRTC1 FBq will be use\n");
> +		}
> +	}
> +
> +	lsdc_reg_write32(ldev, addr_reg, paddr);
> +
> +	/*
> +	 * Then, we triger the fb switch, the switch of the framebuffer
> +	 * to be scanout will complete at the next vblank.
> +	 */
> +	lsdc_reg_write32(ldev, cfg_reg, val | CFG_PAGE_FLIP_BIT);
> +
> +	drm_dbg_kms(&ldev->drm, "crtc%u scantout from 0x%llx\n", index, paddr);
> +}
> +
> +
> +static void lsdc_handle_damage(struct lsdc_device *ldev,
> +			       struct drm_framebuffer *fb,
> +			       struct drm_rect *clip,
> +			       void *src)
> +{
> +	unsigned int offset;
> +	void __iomem *dst;
> +
> +	offset = drm_fb_clip_offset(fb->pitches[0], fb->format, clip);
> +	dst = ldev->vram + offset;
> +	drm_fb_memcpy_toio(dst, fb->pitches[0], src, fb, clip);
> +}
> +
> +
> +static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb,
> +				       struct drm_plane_state *state,
> +				       unsigned int plane)
> +{
> +	unsigned int offset;
> +
> +	offset = fb->offsets[plane];
> +	offset += fb->format->cpp[plane] * (state->src_x >> 16);
> +	offset += fb->pitches[plane] * (state->src_y >> 16);
> +
> +	return offset;
> +}
> +
> +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER
> +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;
> +}
> +#endif
> +
> +
> +static int lsdc_primary_plane_atomic_check(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 *old_plane_state = drm_atomic_get_old_plane_state(state, 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;
> +	struct drm_framebuffer *old_fb = old_plane_state->fb;
> +	struct drm_crtc *crtc = new_plane_state->crtc;
> +	u32 new_format = new_fb->format->format;
> +	struct drm_crtc_state *new_crtc_state;
> +	struct lsdc_crtc_state *priv_crtc_state;
> +	int ret;
> +
> +	if (!crtc)
> +		return 0;
> +
> +	new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
> +	if (WARN_ON(!new_crtc_state))
> +		return -EINVAL;
> +
> +	priv_crtc_state = to_lsdc_crtc_state(new_crtc_state);
> +
> +	ret = drm_atomic_helper_check_plane_state(new_plane_state,
> +						  new_crtc_state,
> +						  DRM_PLANE_HELPER_NO_SCALING,
> +						  DRM_PLANE_HELPER_NO_SCALING,
> +						  false,
> +						  true);
> +	if (ret)
> +		return ret;
> +
> +	/*
> +	 * Require full modeset if enabling or disabling a plane,
> +	 * or changing its position, size, depth or format.
> +	 */
> +	if ((!new_fb || !old_fb ||
> +	     old_plane_state->crtc_x != new_plane_state->crtc_x ||
> +	     old_plane_state->crtc_y != new_plane_state->crtc_y ||
> +	     old_plane_state->crtc_w != new_plane_state->crtc_w ||
> +	     old_plane_state->crtc_h != new_plane_state->crtc_h ||
> +	     old_fb->format->format != new_format))
> +		new_crtc_state->mode_changed = true;
> +
> +
> +	priv_crtc_state->pix_fmt = lsdc_primary_get_default_format(crtc);

Storing the pixel format in the CRTC state is weird? What would happen
if you have a primary plane and a cursor in different formats?

Also, reading the default format from a register doesn't look right.
atomic_check can occur at any time, including before a previous commit,
or while the hardware is disabled. You should rely on either a constant
or the previous state here.

> +	if (lsdc_pixfmt_to_drm_pixfmt(priv_crtc_state->pix_fmt) != new_format) {
> +		drm_info(&ldev->drm, "mode changed\n");
> +		new_crtc_state->mode_changed = true;
> +	}
> +
> +	if (new_crtc_state->mode_changed) {
> +		struct lsdc_display_pipe *dispipe = container_of(plane, struct lsdc_display_pipe, primary);
> +		struct lsdc_pll *pixpll = &dispipe->pixpll;
> +		const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs;
> +
> +		ret = pfuncs->compute(pixpll,
> +				      new_crtc_state->mode.clock,
> +				      true,
> +				      &priv_crtc_state->pparams);
> +		if (ret == false) {
> +			drm_warn(plane->dev,
> +				"failed find a set of pll param for mode %u\n",
> +				new_crtc_state->mode.clock);
> +			return -EINVAL;
> +		}
> +	}
> +
> +	if (ldev->dirty_update)
> +		drm_atomic_helper_check_plane_damage(state, new_plane_state);
> +
> +	return 0;
> +}
> +
> +static void lsdc_update_stride(struct lsdc_device *ldev,
> +			       struct drm_crtc *crtc,
> +			       unsigned int stride)
> +{
> +	unsigned int index = drm_crtc_index(crtc);
> +
> +	if (index == 0)
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_STRIDE_REG, stride);
> +	else if (index == 1)
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_STRIDE_REG, stride);
> +
> +	drm_dbg_kms(&ldev->drm, "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);
> +	struct drm_gem_cma_object *obj;
> +	dma_addr_t fb_addr;
> +
> +	if (ldev->use_vram_helper) {
> +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER
> +		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;
> +#endif
> +	} else {
> +		obj = drm_fb_cma_get_gem_obj(fb, 0);
> +
> +		if (ldev->dirty_update)
> +			fb_addr = ldev->vram_base + fb_offset;
> +		else
> +			fb_addr = obj->paddr + fb_offset;
> +	}
> +
> +	lsdc_update_fb_start_addr(ldev, crtc, fb_addr);
> +
> +	lsdc_update_stride(ldev, crtc, fb->pitches[0]);
> +
> +	if (drm_atomic_crtc_needs_modeset(crtc->state))
> +		lsdc_update_fb_format(ldev, crtc, fb->format);
> +
> +	if (ldev->dirty_update) {
> +		struct drm_plane_state *old_plane_state;
> +		struct drm_rect damage;
> +		bool valid;
> +
> +		old_plane_state = drm_atomic_get_old_plane_state(state, plane);
> +
> +		valid = drm_atomic_helper_damage_merged(old_plane_state,
> +							new_plane_state,
> +							&damage);
> +		if (valid)
> +			lsdc_handle_damage(ldev, fb, &damage, obj->vaddr);
> +	}
> +}
> +
> +
> +static void lsdc_primary_plane_atomic_disable(struct drm_plane *plane,
> +					struct drm_atomic_state *state)
> +{
> +	drm_dbg_kms(plane->dev, "%s disabled\n", plane->name);
> +}
> +
> +
> +static int lsdc_plane_prepare_fb(struct drm_plane *plane,
> +				   struct drm_plane_state *new_state)
> +{
> +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER
> +	struct lsdc_device *ldev = to_lsdc(plane->dev);
> +
> +	if (ldev->use_vram_helper)
> +		return drm_gem_vram_plane_helper_prepare_fb(plane, new_state);
> +#endif
> +	return drm_gem_plane_helper_prepare_fb(plane, new_state);
> +}
> +
> +
> +static void lsdc_plane_cleanup_fb(struct drm_plane *plane,
> +				    struct drm_plane_state *old_state)
> +{
> +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER
> +	struct drm_device *ddev = plane->dev;
> +	struct lsdc_device *ldev = to_lsdc(ddev);
> +
> +	if (ldev->use_vram_helper)
> +		return drm_gem_vram_plane_helper_cleanup_fb(plane, old_state);
> +#endif
> +}
> +
> +static const struct drm_plane_helper_funcs lsdc_primary_plane_helpers = {
> +	.prepare_fb = lsdc_plane_prepare_fb,
> +	.cleanup_fb = lsdc_plane_cleanup_fb,
> +	.atomic_check = lsdc_primary_plane_atomic_check,
> +	.atomic_update = lsdc_primary_plane_atomic_update,
> +	.atomic_disable = lsdc_primary_plane_atomic_disable,
> +};
> +
> +
> +

Don't use multiple blank lines. Generally speaking, there's a lot of
checkpatch warnings to fix. Make sure to run checkpatch.pl --strict and
fix whatever comes up.

> +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_crtc *crtc = new_plane_state->crtc;
> +	struct drm_framebuffer *fb = new_plane_state->fb;
> +	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_HELPER_NO_SCALING,
> +						  DRM_PLANE_HELPER_NO_SCALING,
> +						  true,
> +						  true);
> +	if (ret)
> +		return ret;
> +
> +	if ((fb->width < LSDC_CURS_MIN_SIZE) ||
> +	    (fb->height < LSDC_CURS_MIN_SIZE) ||
> +	    (fb->width > LSDC_CURS_MAX_SIZE) ||
> +	    (fb->height > LSDC_CURS_MAX_SIZE)) {
> +		drm_err(plane->dev, "Invalid cursor size: %dx%d\n",
> +				fb->width, fb->height);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +
> +/* Update the location of the cursor */
> +static void lsdc_cursor_update_location(struct lsdc_device *ldev,
> +					struct drm_crtc *crtc)
> +{
> +	u32 val;
> +
> +	val = lsdc_reg_read32(ldev, LSDC_CURSOR_CFG_REG);
> +
> +	if ((val & CURSOR_FORMAT_MASK) == 0)
> +		val |= CURSOR_FORMAT_ARGB8888;
> +
> +	/* Update the location of the cursor */
> +	if (drm_crtc_index(crtc))
> +		val |= CURSOR_LOCATION_BIT;
> +
> +	lsdc_reg_write32(ldev, LSDC_CURSOR_CFG_REG, val);
> +}
> +
> +/* update the position of the cursor */
> +static void lsdc_cursor_update_position(struct lsdc_device *ldev, int x, int y)
> +{
> +	if (x < 0)
> +		x = 0;
> +
> +	if (y < 0)
> +		y = 0;
> +
> +	lsdc_reg_write32(ldev, LSDC_CURSOR_POSITION_REG, (y << 16) | x);
> +}
> +
> +
> +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 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) {
> +		u64 cursor_addr;
> +
> +		if (ldev->use_vram_helper) {
> +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER
> +			s64 offset;
> +
> +			offset = lsdc_get_vram_bo_offset(new_fb);
> +			cursor_addr = ldev->vram_base + offset;
> +
> +			drm_dbg_kms(ddev, "%s offset: %llx\n",
> +					plane->name, offset);
> +#endif
> +		} else {
> +			struct drm_gem_cma_object *cursor_obj;
> +
> +			cursor_obj = drm_fb_cma_get_gem_obj(new_fb, 0);
> +			if (!cursor_obj)
> +				return;
> +
> +			cursor_addr = cursor_obj->paddr;
> +		}
> +
> +		lsdc_reg_write32(ldev, LSDC_CURSOR_ADDR_REG, cursor_addr);
> +	}
> +
> +	lsdc_cursor_update_position(ldev, new_plane_state->crtc_x,
> +					  new_plane_state->crtc_y);
> +
> +	lsdc_cursor_update_location(ldev, new_plane_state->crtc);
> +}
> +
> +
> +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 drm_plane_state *old_plane_state;
> +	struct drm_crtc *crtc;
> +
> +	old_plane_state = drm_atomic_get_old_plane_state(state, plane);
> +
> +	if (old_plane_state)
> +		crtc = old_plane_state->crtc;
> +
> +	lsdc_reg_write32(ldev, LSDC_CURSOR_CFG_REG, 0);
> +
> +	drm_dbg_kms(ddev, "%s disable\n", plane->name);
> +}
> +
> +
> +static const struct drm_plane_helper_funcs lsdc_cursor_plane_helpers = {
> +	.prepare_fb = lsdc_plane_prepare_fb,
> +	.cleanup_fb = lsdc_plane_cleanup_fb,
> +	.atomic_check = lsdc_cursor_atomic_check,
> +	.atomic_update = lsdc_cursor_atomic_update,
> +	.atomic_disable = lsdc_cursor_atomic_disable,
> +};
> +
> +
> +static int lsdc_plane_get_default_zpos(enum drm_plane_type type)
> +{
> +	switch (type) {
> +	case DRM_PLANE_TYPE_PRIMARY:
> +		return 0;
> +	case DRM_PLANE_TYPE_OVERLAY:
> +		return 1;
> +	case DRM_PLANE_TYPE_CURSOR:
> +		return 7;
> +	}
> +	return 0;
> +}
> +
> +
> +static void lsdc_plane_reset(struct drm_plane *plane)
> +{
> +	drm_atomic_helper_plane_reset(plane);
> +
> +	plane->state->zpos = lsdc_plane_get_default_zpos(plane->type);
> +
> +	drm_dbg_kms(plane->dev, "%s reset\n", plane->name);
> +}
> +
> +
> +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 = lsdc_plane_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
> +};
> +
> +
> +int lsdc_plane_init(struct lsdc_device *ldev, struct drm_plane *plane,
> +		    enum drm_plane_type type, unsigned int index)
> +{
> +	struct drm_device *ddev = &ldev->drm;
> +	int zpos = lsdc_plane_get_default_zpos(type);
> +	unsigned int format_count;
> +	const uint32_t *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);
> +		drm_plane_create_zpos_property(plane, zpos, 0, 6);
> +		if (ldev->dirty_update)
> +			drm_plane_enable_fb_damage_clips(plane);
> +		break;
> +	case DRM_PLANE_TYPE_CURSOR:
> +		drm_plane_helper_add(plane, &lsdc_cursor_plane_helpers);
> +		drm_plane_create_zpos_immutable_property(plane, zpos);
> +		break;
> +	case DRM_PLANE_TYPE_OVERLAY:
> +		drm_err(ddev, "overlay plane is not supported\n");
> +		break;
> +	}
> +
> +	drm_plane_create_alpha_property(plane);
> +
> +	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..fcf3728a9067
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_pll.c
> @@ -0,0 +1,657 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * Copyright 2020 Loongson Corporation
> + *
> + * Permission is hereby granted, free of charge, to any person obtaining a
> + * copy of this software and associated documentation files (the
> + * "Software"), to deal in the Software without restriction, including
> + * without limitation the rights to use, copy, modify, merge, publish,
> + * distribute, sub license, and/or sell copies of the Software, and to
> + * permit persons to whom the Software is furnished to do so, subject to
> + * the following conditions:
> + *
> + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL
> + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM,
> + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
> + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
> + * USE OR OTHER DEALINGS IN THE SOFTWARE.
> + *
> + * The above copyright notice and this permission notice (including the
> + * next paragraph) shall be included in all copies or substantial portions
> + * of the Software.
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#include <drm/drm_print.h>
> +
> +#include "lsdc_drv.h"
> +#include "lsdc_regs.h"
> +#include "lsdc_pll.h"
> +
> +/* device dependent pixpll regs */
> +
> +/* u64 */
> +struct ls7a1000_pixpll_bitmap {
> +	/* Byte 0 ~ Byte 3 */
> +	unsigned div_out      : 7;   /*  0 : 6     output clock divider  */
> +	unsigned reserved_1   : 14;  /*  7 : 20                          */
> +	unsigned loopc        : 9;   /* 21 : 29                          */
> +	unsigned reserved_2   : 2;   /* 30 : 31                          */
> +
> +	/* Byte 4 ~ Byte 7 */
> +	unsigned div_ref      : 7;   /*  0 : 6     input clock divider   */
> +	unsigned locked       : 1;   /*  7         PLL locked flag       */
> +	unsigned sel_out      : 1;   /*  8         output clk selector   */
> +	unsigned reserved_3   : 2;   /*  9 : 10    reserved              */
> +	unsigned set_param    : 1;   /*  11        set pll param         */
> +	unsigned bypass       : 1;   /*  12                              */
> +	unsigned powerdown    : 1;   /*  13                              */
> +	unsigned reserved_4   : 18;  /*  14 : 31                         */
> +};
> +
> +
> +/* u128 */
> +struct ls2k1000_pixpll_bitmap {
> +	/* Byte 0 ~ Byte 3 */
> +	unsigned sel_out      :  1;  /*  0      select this PLL          */
> +	unsigned reserved_1   :  1;  /*  1                               */
> +	unsigned sw_adj_en    :  1;  /*  2      allow software adjust    */
> +	unsigned bypass       :  1;  /*  3      bypass L1 PLL            */
> +	unsigned reserved_2   :  3;  /*  4:6                             */
> +	unsigned lock_en      :  1;  /*  7      enable lock L1 PLL       */
> +	unsigned reserved_3   :  2;  /*  8:9                             */
> +	unsigned lock_check   :  2;  /* 10:11   precision check          */
> +	unsigned reserved_4   :  4;  /* 12:15                            */
> +
> +	unsigned locked       :  1;  /* 16      PLL locked flag bit      */
> +	unsigned reserved_5   :  2;  /* 17:18                            */
> +	unsigned powerdown    :  1;  /* 19      powerdown the pll if set */
> +	unsigned reserved_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 reserved_7   : 16;  /* 48:63                            */
> +
> +	/* Byte 8 ~ Byte 15 */
> +	unsigned div_out      :  6;  /* 0 : 5   output clock divider     */
> +	unsigned reserved_8   : 26;  /* 6 : 31                           */
> +	unsigned reserved_9   : 32;  /* 70: 127                          */
> +};
> +
> +
> +/* u32 */
> +struct ls2k0500_pixpll_bitmap {
> +	/* Byte 0 ~ Byte 1 */
> +	unsigned sel_out      : 1;
> +	unsigned reserved_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 reserved_2   : 1;
> +	unsigned locked       : 1;   /*  7     Is L1 PLL locked, read only */
> +	unsigned div_ref      : 6;   /*  8:13  ref clock divider           */
> +	unsigned reserved_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 reserved_4   : 2;   /* 30:31                              */
> +};
> +
> +
> +/*
> + * NOTE: All loongson's cpu is little endian.
> + */
> +union lsdc_pix_pll_param {
> +	struct ls7a1000_pixpll_bitmap ls7a1000;
> +	struct ls2k1000_pixpll_bitmap ls2k1000;
> +	struct ls2k0500_pixpll_bitmap ls2k0500;
> +
> +	u32 dword[4];
> +};
> +
> +/*
> + * pixel clock to pll parameters translation table
> + */
> +struct pixclk_to_pll_parm {
> +	/* kHz */
> +	unsigned int clock;
> +
> +	/* unrelated information */
> +	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;
> +};
> +
> +
> +/*
> + * Small cached value to speed up pll parameter calculation
> + */
> +static const struct pixclk_to_pll_parm pll_param_table[] = {
> +	{148500, 1920, 1080, 60, 11, 49,  3},   /* 1920x1080 at 60Hz */
> +						/* 1920x1080 at 50Hz */
> +	{174500, 1920, 1080, 75, 17, 89,  3},   /* 1920x1080 at 75Hz */
> +	{181250, 2560, 1080, 75,  8, 58,  4},   /* 2560x1080 at 75Hz */
> +	{146250, 1680, 1050, 60, 16, 117, 5},   /* 1680x1050 at 60Hz */
> +	{135000, 1280, 1024, 75, 10, 54,  4},   /* 1280x1024 at 75Hz */
> +
> +	{108000, 1600, 900,  60, 15, 81,  5},   /* 1600x900 at 60Hz  */
> +						/* 1280x1024 at 60Hz */
> +						/* 1280x960 at 60Hz */
> +						/* 1152x864 at 75Hz */
> +
> +	{106500, 1440, 900,  60, 19, 81,  4},   /* 1440x900 at 60Hz */
> +	{88750,  1440, 900,  60, 16, 71,  5},   /* 1440x900 at 60Hz */
> +	{83500,  1280, 800,  60, 17, 71,  5},   /* 1280x800 at 60Hz */
> +	{71000,  1280, 800,  60, 20, 71,  5},   /* 1280x800 at 60Hz */
> +
> +	{74250,  1280, 720,  60, 22, 49,  3},   /* 1280x720 at 60Hz */
> +						/* 1280x720 at 50Hz */
> +
> +	{78750,  1024, 768,  75, 16, 63,  5},   /* 1024x768 at 75Hz */
> +	{75000,  1024, 768,  70, 29, 87,  4},   /* 1024x768 at 70Hz */
> +	{65000,  1024, 768,  60, 20, 39,  3},   /* 1024x768 at 60Hz */
> +
> +	{51200,  1024, 600,  60, 25, 64,  5},   /* 1024x600 at 60Hz */
> +
> +	{57284,  832,  624,  75, 24, 55,  4},   /* 832x624 at 75Hz */
> +	{49500,  800,  600,  75, 40, 99,  5},   /* 800x600 at 75Hz */
> +	{50000,  800,  600,  72, 44, 88,  4},   /* 800x600 at 72Hz */
> +	{40000,  800,  600,  60, 30, 36,  3},   /* 800x600 at 60Hz */
> +	{36000,  800,  600,  56, 50, 72,  4},   /* 800x600 at 56Hz */
> +	{31500,  640,  480,  75, 40, 63,  5},   /* 640x480 at 75Hz */
> +						/* 640x480 at 73Hz */
> +
> +	{30240,  640,  480,  67, 62, 75,  4},   /* 640x480 at 67Hz */
> +	{27000,  720,  576,  50, 50, 54,  4},   /* 720x576 at 60Hz */
> +	{25175,  640,  480,  60, 85, 107, 5},   /* 640x480 at 60Hz */
> +	{25200,  640,  480,  60, 50, 63,  5},   /* 640x480 at 60Hz */
> +						/* 720x480 at 60Hz */
> +};
> +
> +/**
> + * lsdc_pixpll_setup
> + *
> + * @this: point to the object which calling this function
> + *
> + * ioremap the device dependent register before using it
> + */
> +static int lsdc_pixpll_setup(struct lsdc_pll * const this)
> +{
> +	this->mmio = ioremap(this->reg_base, this->reg_size);
> +
> +	drm_info(this->ddev, "PIXPLL%u REG[%x, %u] map to %llx\n",
> +		this->index, this->reg_base, this->reg_size, (u64)this->mmio);
> +
> +	return 0;
> +}
> +
> +
> +
> +/*
> + * Find a set of pll parameters (to generate pixel clock) from a static
> + * local table, which avoid to compute the pll parameter everytime a
> + * modeset is triggered.
> + *
> + * @this: point to the object which calling this function
> + * @clock: the desired pixel clock wanted to generate, the unit is kHz
> + * @pout: pointer to where hardware pll parameters(if found) to save
> + *
> + *  Return true if a parameter is found, otherwise return false.
> + */
> +static bool lsdc_pixpll_find(struct lsdc_pll * const this,
> +			     unsigned int clock,
> +			     struct lsdc_pll_core_values * 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 desired
> + * clock frequency. It does that by computing the all of pll parameters
> + * combines possible and compare the diff and find the minimal.
> + *
> + *  clock_out = refclk / div_ref * loopc / div_out
> + *
> + *  refclk is fixed as 100MHz in ls7a1000, ls2k1000 and ls2k0500
> + *
> + * @this: point to the object which calling this function
> + * @clk: the desired pixel clock wanted to generate, the unit is kHz
> + * @verbose: print the pll parameter and the actual pixel clock.
> + * @pout: pointer to where hardware pll parameters(if found) to save
> + *
> + *  Return true if a parameter is found, otherwise return false.
> + */
> +static bool lsdc_pixpll_compute(struct lsdc_pll * const this,
> +				unsigned int clk,
> +				bool verbose,
> +				struct lsdc_pll_core_values * const pout)
> +{
> +	unsigned int refclk = this->ref_clock;
> +	const unsigned int tolerance = 1000;
> +	unsigned int min = tolerance;
> +	unsigned int div_out, loopc, div_ref;
> +
> +	if (lsdc_pixpll_find(this, clk, 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;
> +
> +				diff = clk * div_out - refclk * loopc / div_ref;
> +
> +				if (diff < 0)
> +					diff = -diff;
> +
> +				if (diff < min) {
> +					min = diff;
> +					pout->div_ref = div_ref;
> +					pout->div_out = div_out;
> +					pout->loopc = loopc;
> +
> +					if (diff == 0)
> +						return true;
> +				}
> +			}
> +		}
> +	}
> +
> +	if (verbose) {
> +		unsigned int clk_out;
> +
> +		clk_out = refclk / pout->div_ref * pout->loopc / pout->div_out;
> +
> +		drm_info(this->ddev, "pixpll%u\n", this->index);
> +
> +		drm_info(this->ddev, "div_ref=%u, loopc=%u, div_out=%u\n",
> +				pout->div_ref, pout->loopc, pout->div_out);
> +
> +		drm_info(this->ddev, "desired clk=%u, actual out=%u, diff=%d\n",
> +				clk, clk_out, clk_out - clk);
> +	}
> +
> +	return min < tolerance;
> +}
> +
> +/*
> + * Update the pll parameters to hardware, target to the pixpll in ls7a1000
> + *
> + * @this: point to the object which calling this function
> + * @param: pointer to where the parameters passed in
> + *
> + *  Return true if a parameter is found, otherwise return false.
> + */
> +static int ls7a1000_pixpll_param_update(struct lsdc_pll * const this,
> +			const struct lsdc_pll_core_values * const param)
> +{
> +	u32 val;
> +	unsigned int counter = 0;
> +	void __iomem *reg = this->mmio;
> +	bool locked;
> +
> +
> +	/* clear sel_pll_out0 */
> +	val = readl(reg + 0x4);
> +	val &= ~(1 << 8);
> +	writel(val, reg + 0x4);
> +
> +	/* set pll_pd */
> +	val = readl(reg + 0x4);
> +	val |= (1 << 13);
> +	writel(val, reg + 0x4);
> +
> +	/* clear set_pll_param */
> +	val = readl(reg + 0x4);
> +	val &= ~(1 << 11);
> +	writel(val, reg + 0x4);
> +
> +	/* clear old value & config new value */
> +	val = readl(reg + 0x04);
> +	val &= ~0x7F;
> +
> +	val |= param->div_ref;        /* div_ref */
> +	writel(val, reg + 0x4);
> +
> +	val = readl(reg);
> +	val &= ~(0x7f << 0);
> +	val |= param->div_out;        /* div_out */
> +	val &= ~(0x1ffUL << 21);
> +	val |= param->loopc << 21;    /* loopc */
> +	writel(val, reg);
> +
> +	/* set set_pll_param */
> +	val = readl(reg + 0x4);
> +	val |= (1 << 11);
> +	writel(val, reg + 0x4);
> +
> +	/* clear pll_pd */
> +	val = readl(reg + 0x4);
> +	val &= ~(1 << 13);
> +	writel(val, reg + 0x4);
> +
> +	/* wait pll lock */
> +	do {
> +		val = readl(reg + 0x4);
> +		locked = val & 0x80;
> +		counter++;
> +	} while (locked == false);
> +
> +	drm_dbg_kms(this->ddev, "%u loop waited\n", counter);
> +
> +	/* set sel_pll_out0 */
> +	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 which calling this function
> + * @param: pointer to where the parameters passed in
> + *
> + *  Return true if a parameter is found, otherwise return false.
> + */
> +static int ls2k1000_pixpll_param_update(struct lsdc_pll * const this,
> +			const struct lsdc_pll_core_values * const param)
> +{
> +	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 = val | (param->div_ref << 26);
> +	writel(val, reg);
> +
> +	val = readl(reg + 4);
> +	/* clear loopc bit field */
> +	val &= ~0x0fff;
> +	/* set loopc bit field */
> +	val |= param->loopc;
> +	writel(val, reg + 4);
> +
> +	/* set div_out */
> +	writel(param->div_out, reg + 8);
> +
> +	val = readl(reg);
> +	/* use the software configure param */
> +	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 == false);
> +
> +	drm_dbg_kms(this->ddev, "%u loop waited\n", counter);
> +
> +	/* Switch to the above 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 true if a parameter is found, otherwise return false.
> + */
> +
> +static int ls2k0500_pixpll_param_update(struct lsdc_pll * const this,
> +			const struct lsdc_pll_core_values * 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 == false);
> +
> +	drm_dbg_kms(this->ddev, "%u loop waited\n", counter);
> +
> +	/* Switch to the above software configured PLL instead of refclk */
> +	writel((val | 1), reg);
> +
> +	return 0;
> +}
> +
> +
> +#define LSDC_PIXPLL_BITMAP(type,var,parms) \
> +		struct type ## _pixpll_bitmap *var = &parms.type
> +
> +#define LSDC_PIXPLL_PRINT_CODE_BLOCK(ddev,var,index,refclk)                \
> +{                                                                          \
> +	out_clk = refclk / var->div_ref * var->loopc / var->div_out;       \
> +	drm_info(ddev, "div_ref=%u, loopc=%u, div_out=%u\n",               \
> +			var->div_ref, var->loopc, var->div_out);           \
> +	drm_info(ddev, "locked: %s\n", var->locked ? "Yes" : "No");        \
> +	drm_info(ddev, "bypass: %s\n", var->bypass ? "Yes" : "No");        \
> +	drm_info(ddev, "powerdown: %s\n", var->powerdown ? "Yes" : "No");  \
> +	drm_info(ddev, "set_out: %s\n", var->sel_out ? "Yes" : "No");      \
> +	drm_info(ddev, "pixpll%u generate %ukHz\n", index, out_clk);       \
> +	drm_info(ddev, "\n");                                              \
> +}

This should be a debug-level trace

Maxime
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 228 bytes
Desc: not available
URL: <https://lists.freedesktop.org/archives/dri-devel/attachments/20220203/d9c85860/attachment-0001.sig>


More information about the dri-devel mailing list