[PATCH v10 3/4] drm/lsdc: add drm driver for loongson display controller

Maxime Ripard maxime at cerno.tech
Tue Feb 22 08:27:47 UTC 2022


Hi,

On Sun, Feb 20, 2022 at 10:55:53PM +0800, Sui Jingfeng wrote:
> +/* lsdc_get_display_timings_from_dtb - Get display timings from the device tree
> + *
> + * @np: point to the device node contain the display timings
> + * @pptim: point to where the pointer of struct display_timings is store to
> + */
> +static void lsdc_get_display_timings_from_dtb(struct device_node *np,
> +					      struct display_timings **pptim)
> +{
> +	struct display_timings *timings;
> +
> +	if (!np)
> +		return;
> +
> +	timings = of_get_display_timings(np);
> +	if (timings)
> +		*pptim = timings;
> +}

This is not documented in your binding.

> +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;
> +
> +		ret = DRM_MODE_CONNECTOR_HDMIA;
> +
> +		res = of_property_read_string(output, "type", &hdmi_type);
> +		if (res == 0 && !strcmp(hdmi_type, "b"))
> +			ret = DRM_MODE_CONNECTOR_HDMIB;
> +
> +		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;
> +}

Your ports and that you're using the connectors bindings either.

> +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;
> +	unsigned int connector_type = DRM_MODE_CONNECTOR_Unknown;
> +	struct device_node *disp_tims_np;
> +	struct lsdc_connector *lconn;
> +	struct drm_connector *connector;
> +	int ret;
> +
> +	lconn = devm_kzalloc(ddev->dev, sizeof(*lconn), GFP_KERNEL);
> +	if (!lconn)
> +		return ERR_PTR(-ENOMEM);
> +
> +	lconn->index = index;
> +	lconn->has_disp_tim = false;
> +	lconn->ddc = NULL;
> +
> +	output = of_parse_phandle(np, "output-ports", index);
> +	if (!output) {
> +		drm_warn(ddev, "no output-ports property, please update dtb\n");
> +		/*
> +		 * Providing a blindly support even though no output-ports
> +		 * property is provided in the dtb.
> +		 */
> +		goto DT_SKIPED;
> +	}

output-ports is not documented either.

> +	if (!of_device_is_available(output)) {
> +		of_node_put(output);
> +		drm_info(ddev, "connector%d is not available\n", index);
> +		return NULL;
> +	}
> +
> +	disp_tims_np = of_get_child_by_name(output, "display-timings");
> +	if (disp_tims_np) {
> +		lsdc_get_display_timings_from_dtb(output, &lconn->disp_tim);
> +		lconn->has_disp_tim = true;
> +		of_node_put(disp_tims_np);
> +		drm_info(ddev, "Found display timings provided by connector%d\n", index);
> +	}
> +
> +	connector_type = lsdc_get_connector_type(ddev, output, index);
> +
> +	if (output) {
> +		of_node_put(output);
> +		output = NULL;
> +	}
> +
> +DT_SKIPED:
> +
> +	/* Only create the i2c channel if display timing is not provided */
> +	if (!lconn->has_disp_tim) {
> +		const struct lsdc_chip_desc * const desc = ldev->desc;
> +
> +		if (desc->have_builtin_i2c)
> +			lconn->ddc = lsdc_create_i2c_chan(ddev, index);
> +		else
> +			lconn->ddc = lsdc_get_i2c_adapter(ddev, index);

This looks weird: the connector bindings have a property to store the
i2c controller connected to the DDC lines, so you should use that
instead.

> +		if (IS_ERR(lconn->ddc)) {
> +			lconn->ddc = NULL;
> +
> +			drm_err(ddev, "Get i2c adapter failed: %ld\n",
> +				PTR_ERR(lconn->ddc));
> +		} else if (lconn->ddc)
> +			drm_info(ddev, "i2c%d for connector%d created\n",
> +				 i2c_adapter_id(lconn->ddc), index);
> +	}
> +
> +	connector = &lconn->base;
> +	connector->polled = DRM_CONNECTOR_POLL_CONNECT | DRM_CONNECTOR_POLL_DISCONNECT;
> +
> +	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_CONNECTOR_INIT;
> +	}
> +
> +	drm_connector_helper_add(connector, &lsdc_connector_helpers);
> +
> +	return lconn;
> +
> +ERR_CONNECTOR_INIT:
> +	if (!IS_ERR_OR_NULL(lconn->ddc))
> +		lsdc_destroy_i2c(ddev, lconn->ddc);
> +
> +	return ERR_PTR(ret);
> +}
> diff --git a/drivers/gpu/drm/lsdc/lsdc_connector.h b/drivers/gpu/drm/lsdc/lsdc_connector.h
> new file mode 100644
> index 000000000000..fff64398b984
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_connector.h
> @@ -0,0 +1,35 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * KMS driver for Loongson display controller
> + */
> +
> +/*
> + * 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;
> +
> +	/* Read 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..8e07c2e4b606
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_crtc.c
> @@ -0,0 +1,338 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * KMS driver for Loongson display controller
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +#include <drm/drm_vblank.h>
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +
> +#include "lsdc_drv.h"
> +#include "lsdc_regs.h"
> +#include "lsdc_pll.h"
> +
> +static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc)
> +{
> +	struct lsdc_device *ldev = to_lsdc(crtc->dev);
> +	unsigned int index = drm_crtc_index(crtc);
> +	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;
> +
> +	__drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base);
> +
> +	drm_dbg(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));
> +
> +	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,
> +	 * it is a limitation of the CRTC.
> +	 * 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;
> +}
> +
> +static int lsdc_crtc_helper_atomic_check(struct drm_crtc *crtc,
> +					 struct drm_atomic_state *state)
> +{
> +	struct drm_crtc_state *crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
> +
> +	if (!crtc_state->enable)
> +		return 0; /* no mode checks if CRTC is being disabled */
> +
> +	return 0;
> +}

You can just remove that function if  you're returning 0 in both cases.

> +
> +static void lsdc_update_pixclk(struct drm_crtc *crtc, unsigned int pixclk)
> +{
> +	struct lsdc_display_pipe *dispipe = container_of(crtc, struct lsdc_display_pipe, crtc);
> +	struct lsdc_pll *pixpll = &dispipe->pixpll;
> +	const struct lsdc_pixpll_funcs *clkfun = pixpll->funcs;
> +	struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(crtc->state);
> +
> +	clkfun->update(pixpll, &priv_state->pparams);

It looks like pparams isn't set anywhere in atomic_check though, where
is it supposed to come from?

> +}
> +
> +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 pixclock = mode->clock;
> +	unsigned int index = drm_crtc_index(crtc);
> +	u32 h_sync, v_sync, h_val, v_val;
> +
> +	/* 26:16 total pixels, 10:0 visiable pixels, in horizontal */
> +	h_val = (mode->crtc_htotal << 16) | mode->crtc_hdisplay;
> +	/* 26:16 total pixels, 10:0 visiable pixels, in vertical */
> +	v_val = (mode->crtc_vtotal << 16) | mode->crtc_vdisplay;
> +	/* 26:16 hsync end, 10:0 hsync start, bit 30 is hsync enable */
> +	h_sync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start | EN_HSYNC_BIT;
> +	if (mode->flags & DRM_MODE_FLAG_NHSYNC)
> +		h_sync |= INV_HSYNC_BIT;
> +
> +	/* 26:16 vsync end, 10:0 vsync start, bit 30 is vsync enable */
> +	v_sync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start | EN_VSYNC_BIT;
> +	if (mode->flags & DRM_MODE_FLAG_NVSYNC)
> +		v_sync |= INV_VSYNC_BIT;
> +
> +	if (index == 0) {
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_FB_ORIGIN_REG, 0);
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_HDISPLAY_REG, h_val);
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_VDISPLAY_REG, v_val);
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_HSYNC_REG, h_sync);
> +		lsdc_reg_write32(ldev, LSDC_CRTC0_VSYNC_REG, v_sync);
> +	} else if (index == 1) {
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_FB_ORIGIN_REG, 0);
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_HDISPLAY_REG, h_val);
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_VDISPLAY_REG, v_val);
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_HSYNC_REG, h_sync);
> +		lsdc_reg_write32(ldev, LSDC_CRTC1_VSYNC_REG, v_sync);
> +	}
> +
> +	drm_dbg(ddev, "%s modeset: %ux%u\n", crtc->name, mode->hdisplay, mode->vdisplay);
> +
> +	lsdc_update_pixclk(crtc, pixclock);

So it's the mode clock? But then, the argument to lsdc_update_pixclk is
entirely ignored, so it's probably not what you'd want either.

> +}
> +
> +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(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(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_check = lsdc_crtc_helper_atomic_check,
> +	.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..8d6735a0c72e
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.c
> @@ -0,0 +1,672 @@
> +// SPDX-License-Identifier: GPL-2.0+
> +/*
> + * KMS driver for Loongson display controller
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#include <linux/module.h>
> +#include <linux/pci.h>
> +#include <linux/of_reserved_mem.h>
> +
> +#include <drm/drm_aperture.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_vblank.h>
> +#include <drm/drm_debugfs.h>
> +#include <drm/drm_fb_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_gem_vram_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_atomic_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_use_vram_helper = -1;
> +MODULE_PARM_DESC(use_vram_helper, "Using vram helper based driver(0 = disabled)");
> +module_param_named(use_vram_helper, lsdc_use_vram_helper, int, 0644);

Sigh... We've been over this a couple of times already.

> +static const struct lsdc_chip_desc dc_in_ls2k1000 = {
> +	.chip = LSDC_CHIP_2K1000,
> +	.num_of_crtc = LSDC_NUM_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,
> +	.stride_alignment = 256,
> +	.have_builtin_i2c = false,
> +	.has_vram = false,
> +};
> +
> +static const struct lsdc_chip_desc dc_in_ls2k0500 = {
> +	.chip = LSDC_CHIP_2K0500,
> +	.num_of_crtc = LSDC_NUM_CRTC,
> +	.max_pixel_clk = 200000,
> +	.max_width = 2048,
> +	.max_height = 2048,
> +	.num_of_hw_cursor = 1,
> +	.hw_cursor_w = 32,
> +	.hw_cursor_h = 32,
> +	.stride_alignment = 256,
> +	.have_builtin_i2c = false,
> +	.has_vram = false,
> +};
> +
> +static const struct lsdc_chip_desc dc_in_ls7a1000 = {
> +	.chip = LSDC_CHIP_7A1000,
> +	.num_of_crtc = LSDC_NUM_CRTC,
> +	.max_pixel_clk = 200000,
> +	.max_width = 2048,
> +	.max_height = 2048,
> +	.num_of_hw_cursor = 1,
> +	.hw_cursor_w = 32,
> +	.hw_cursor_h = 32,
> +	.stride_alignment = 256,
> +	.have_builtin_i2c = true,
> +	.has_vram = true,
> +};
> +
> +static enum drm_mode_status
> +lsdc_device_mode_valid(struct drm_device *ddev, const struct drm_display_mode *mode)
> +{
> +	struct lsdc_device *ldev = to_lsdc(ddev);
> +
> +	if (ldev->use_vram_helper)
> +		return drm_vram_helper_mode_valid(ddev, mode);
> +
> +	return MODE_OK;
> +}
> +
> +static const struct drm_mode_config_funcs lsdc_mode_config_funcs = {
> +	.fb_create = drm_gem_fb_create,
> +	.output_poll_changed = drm_fb_helper_output_poll_changed,
> +	.atomic_check = drm_atomic_helper_check,
> +	.atomic_commit = drm_atomic_helper_commit,
> +	.mode_valid = lsdc_device_mode_valid,
> +};
> +
> +#ifdef CONFIG_DEBUG_FS
> +static int lsdc_show_clock(struct seq_file *m, void *arg)
> +{
> +	struct drm_info_node *node = (struct drm_info_node *)m->private;
> +	struct drm_device *ddev = node->minor->dev;
> +	struct drm_crtc *crtc;
> +
> +	drm_for_each_crtc(crtc, ddev) {
> +		struct lsdc_display_pipe *pipe;
> +		struct lsdc_pll *pixpll;
> +		const struct lsdc_pixpll_funcs *funcs;
> +		struct lsdc_pll_core_values params;
> +		unsigned int out_khz;
> +		struct drm_display_mode *adj;
> +
> +		pipe = container_of(crtc, struct lsdc_display_pipe, crtc);
> +		if (!pipe->available)
> +			continue;
> +
> +		adj = &crtc->state->adjusted_mode;
> +
> +		pixpll = &pipe->pixpll;
> +		funcs = pixpll->funcs;
> +		out_khz = funcs->get_clock_rate(pixpll, &params);
> +
> +		seq_printf(m, "Display pipe %u: %dx%d\n",
> +			   pipe->index, adj->hdisplay, adj->vdisplay);
> +
> +		seq_printf(m, "Frequency actually output: %u kHz\n", out_khz);
> +		seq_printf(m, "Pixel clock required: %d kHz\n", adj->clock);
> +		seq_printf(m, "diff: %d kHz\n", adj->clock);
> +
> +		seq_printf(m, "div_ref=%u, loopc=%u, div_out=%u\n",
> +			   params.div_ref, params.loopc, params.div_out);
> +
> +		seq_printf(m, "hsync_start=%d, hsync_end=%d, htotal=%d\n",
> +			   adj->hsync_start, adj->hsync_end, adj->htotal);
> +		seq_printf(m, "vsync_start=%d, vsync_end=%d, vtotal=%d\n\n",
> +			   adj->vsync_start, adj->vsync_end, adj->vtotal);
> +	}
> +
> +	return 0;
> +}
> +
> +static int lsdc_show_mm(struct seq_file *m, void *arg)
> +{
> +	struct drm_info_node *node = (struct drm_info_node *)m->private;
> +	struct drm_device *ddev = node->minor->dev;
> +	struct drm_printer p = drm_seq_file_printer(m);
> +
> +	drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p);
> +
> +	return 0;
> +}
> +
> +static struct drm_info_list lsdc_debugfs_list[] = {
> +	{ "clocks", lsdc_show_clock, 0 },
> +	{ "mm",     lsdc_show_mm,   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 drm_gem_cma_object *obj = kzalloc(sizeof(*obj), GFP_KERNEL);
> +
> +	if (!obj)
> +		return ERR_PTR(-ENOMEM);
> +
> +	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_gem_fops);
> +
> +static const struct drm_driver lsdc_vram_driver_stub = {
> +	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
> +	.fops = &lsdc_gem_fops,
> +
> +	.name = DRIVER_NAME,
> +	.desc = DRIVER_DESC,
> +	.date = DRIVER_DATE,
> +	.major = DRIVER_MAJOR,
> +	.minor = DRIVER_MINOR,
> +	.patchlevel = DRIVER_PATCHLEVEL,
> +
> +	DRM_GEM_VRAM_DRIVER,
> +};
> +
> +static int lsdc_modeset_init(struct lsdc_device *ldev, const uint32_t num_crtc)
> +{
> +	struct drm_device *ddev = &ldev->drm;
> +	unsigned int i;
> +	int ret;
> +
> +	/* first, find all available connector, and take a record */
> +	for (i = 0; i < num_crtc; i++) {
> +		struct lsdc_display_pipe *const dispipe = &ldev->disp_pipe[i];
> +		struct lsdc_connector *lconn = lsdc_connector_init(ldev, i);
> +		/* Fail if the connector could not be initialized */
> +		if (IS_ERR(lconn))
> +			return PTR_ERR(lconn);
> +
> +		if (!lconn) {
> +			dispipe->lconn = NULL;
> +			dispipe->available = false;
> +			continue;
> +		}
> +
> +		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 = ldev->use_vram_helper;
> +
> +	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);
> +
> +	drm_kms_helper_poll_fini(ddev);
> +
> +	drm_dev_unregister(ddev);
> +
> +	devm_free_irq(ddev->dev, ldev->irq, ddev);
> +
> +	drm_atomic_helper_shutdown(ddev);
> +
> +	drm_mode_config_cleanup(ddev);
> +}
> +
> +/*
> + * lsdc_determine_chip - a function to tell different chips apart.
> + */
> +static const struct lsdc_chip_desc *
> +lsdc_determine_chip(struct pci_dev *pdev, int *has)
> +{
> +	static const struct lsdc_match {
> +		char name[128];
> +		const void *data;
> +	} compat[] = {
> +		{ .name = "loongson,ls7a1000-dc", .data = &dc_in_ls7a1000 },
> +		{ .name = "loongson,ls2k1000-dc", .data = &dc_in_ls2k1000 },
> +		{ .name = "loongson,ls2k0500-dc", .data = &dc_in_ls2k0500 },
> +		{ .name = "loongson,loongson64c-4core-ls7a", .data = &dc_in_ls7a1000 },
> +		{ .name = "loongson,loongson64g-4core-ls7a", .data = &dc_in_ls7a1000 },
> +		{ .name = "loongson,loongson64-2core-2k1000", .data = &dc_in_ls2k1000 },
> +		{ .name = "loongson,loongson2k1000", .data = &dc_in_ls2k1000 },
> +		{ /* sentinel */ },
> +	};
> +
> +	struct device_node *np;
> +	unsigned int i;
> +
> +	/* Deduce DC variant information from the DC device node */
> +	for (i = 0; i < ARRAY_SIZE(compat); ++i) {
> +		np = of_find_compatible_node(NULL, NULL, compat[i].name);
> +		if (!np)
> +			continue;
> +
> +		of_node_put(np);
> +
> +		return compat[i].data;
> +	}
> +
> +	return NULL;
> +}
> +
> +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 Loongson
> +	 * 2H series SoC 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", (u64)base, (u32)(size >> 20));
> +
> +	if (!request_mem_region(base, size, "lsdc_vram")) {
> +		drm_err(ddev, "can't reserve VRAM memory region\n");
> +		return -ENXIO;
> +	}
> +
> +	ret = drmm_vram_helper_init(ddev, base, size);
> +	if (ret) {
> +		drm_err(ddev, "can't init vram helper\n");
> +		return ret;
> +	}
> +
> +	ldev->vram_base = base;
> +	ldev->vram_size = size;
> +
> +	return 0;
> +}
> +
> +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;
> +	int has_dedicated_vram = 0;
> +	struct lsdc_device *ldev;
> +	struct drm_device *ddev;
> +	const struct lsdc_chip_desc *descp;
> +	int ret;
> +
> +	descp = lsdc_determine_chip(pdev, &has_dedicated_vram);
> +	if (!descp) {
> +		dev_err(&pdev->dev, "unknown dc ip core, abort\n");
> +		return -ENOENT;
> +	}
> +
> +	lsdc_remove_conflicting_framebuffers(driver);
> +
> +	ret = pcim_enable_device(pdev);
> +	if (ret)
> +		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;
> +
> +	if (lsdc_use_vram_helper > 0) {
> +		driver = &lsdc_vram_driver_stub;
> +	} else if ((lsdc_use_vram_helper < 0) && descp->has_vram) {
> +		lsdc_use_vram_helper = 1;
> +		driver = &lsdc_vram_driver_stub;
> +	} else {
> +		driver = &lsdc_drm_driver_cma_stub;
> +	}
> +
> +	ldev = devm_drm_dev_alloc(&pdev->dev, driver, struct lsdc_device, drm);
> +	if (IS_ERR(ldev))
> +		return PTR_ERR(ldev);
> +
> +	ldev->use_vram_helper = lsdc_use_vram_helper;
> +	ldev->desc = descp;
> +
> +	/* 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);
> +
> +	if (descp->has_vram && ldev->use_vram_helper)
> +		lsdc_vram_init(ldev);
> +
> +	ddev = &ldev->drm;
> +	pci_set_drvdata(pdev, ddev);
> +
> +	ret = lsdc_mode_config_init(ldev);
> +	if (ret)
> +		goto err_out;
> +
> +	ldev->irq = pdev->irq;
> +
> +	drm_info(&ldev->drm, "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, ldev->desc->num_of_crtc);
> +	if (ret) {
> +		dev_err(&pdev->dev, "Fatal error during vblank init: %d\n", ret);
> +		goto err_out;
> +	}
> +
> +	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;
> +
> +	while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) {
> +		/*
> +		 * Multiple video card workaround
> +		 *
> +		 * This integrated video card will always be selected as
> +		 * default boot device by vgaarb subsystem.
> +		 */
> +		if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) {
> +			pr_info("Discrete graphic card detected, abort\n");
> +			return 0;
> +		}
> +	}
> +
> +	return pci_register_driver(&lsdc_pci_driver);
> +}
> +module_init(lsdc_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..6382283c5e7e
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_drv.h
> @@ -0,0 +1,205 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * KMS driver for Loongson display controller
> + */
> +
> +/*
> + * 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>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_atomic.h>
> +
> +#include "lsdc_regs.h"
> +#include "lsdc_pll.h"
> +
> +#define LSDC_NUM_CRTC           2
> +
> +/* There is only a 1:1 mapping of encoders and connectors for lsdc,
> + * Each CRTC have two FB address registers.
> + *
> + * The display controller in LS2K1000/LS2K0500.
> + *       ___________________                                     _________
> + *      |            -------|                                   |         |
> + *      |  CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | Monitor |
> + *      |  _   _     -------|        ^              ^           |_________|
> + *      | | | | |           |        |              |
> + *      | |_| |_|           |     +------+          |
> + *      |                   <---->| i2c0 |<---------+
> + *      |          LSDC     |     +------+
> + *      |  _   _            |     +------+
> + *      | | | | |           <---->| i2c1 |----------+
> + *      | |_| |_|           |     +------+          |            _________
> + *      |            -------|        |              |           |         |
> + *      |  CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> |  Panel  |
> + *      |            -------|                                   |_________|
> + *      |___________________|
> + *
> + *
> + * The display controller in LS7A1000.
> + *       ___________________                                     _________
> + *      |            -------|                                   |         |
> + *      |  CRTC0 --> | DVO0 ----> Encoder0 ---> Connector0 ---> | Monitor |
> + *      |  _   _     -------|        ^             ^            |_________|
> + *      | | | | |    -------|        |             |
> + *      | |_| |_|    | i2c0 <--------+-------------+
> + *      |            -------|
> + *      |  _   _     -------|
> + *      | | | | |    | i2c1 <--------+-------------+
> + *      | |_| |_|    -------|        |             |             _________
> + *      |            -------|        |             |            |         |
> + *      |  CRTC1 --> | DVO1 ----> Encoder1 ---> Connector1 ---> |  Panel  |
> + *      |            -------|                                   |_________|
> + *      |___________________|
> + */
> +
> +enum loongson_dc_family {
> +	LSDC_CHIP_UNKNOWN = 0,
> +	LSDC_CHIP_2K1000 = 1,  /* 2-Core Mips64r2 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_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;
> +	u32 num_of_crtc;
> +	u32 max_pixel_clk;
> +	u32 max_width;
> +	u32 max_height;
> +	u32 num_of_hw_cursor;
> +	u32 hw_cursor_w;
> +	u32 hw_cursor_h;
> +	u32 stride_alignment;
> +	bool have_builtin_i2c;
> +	bool has_vram;
> +};
> +
> +/*
> + * struct lsdc_display_pipe - Abstraction of hardware display pipeline.
> + * @crtc: CRTC control structure
> + * @plane: Plane control structure
> + * @encoder: Encoder control structure
> + * @pixpll: Pll control structure
> + * @connector: point to connector control structure this display pipe bind
> + * @index: the index corresponding to the hardware display pipe
> + * @available: is this display pipe is available on the motherboard, The
> + *  downstream mother board manufacturer may use only one of them.
> + *  For example, LEMOTE LX-6901 board just has only one VGA output.
> + *
> + * Display pipeline with plane, crtc, encoder, PLL collapsed into one entity.
> + */
> +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;
> +};
> +
> +struct lsdc_device {
> +	struct drm_device drm;
> +
> +	/* LS7A1000 has a dediacted video RAM, typically 64 MB or more */
> +	void __iomem *reg_base;
> +	void __iomem *vram;
> +	resource_size_t vram_base;
> +	resource_size_t vram_size;
> +
> +	struct lsdc_display_pipe disp_pipe[LSDC_NUM_CRTC];
> +
> +	/*
> +	 * @num_output: count the number of active display pipe.
> +	 */
> +	unsigned int num_output;
> +
> +	/* @desc: device dependent data and feature descriptions */
> +	const struct lsdc_chip_desc *desc;
> +
> +	/* @reglock: protects concurrent register access */
> +	spinlock_t reglock;
> +
> +	/*
> +	 * @use_vram_helper: using vram helper base solution instead of
> +	 * CMA helper based solution. The DC scanout from the VRAM is
> +	 * proved to be more reliable, but graphic application is may
> +	 * become slow when using this driver mode.
> +	 */
> +	bool use_vram_helper;
> +
> +	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);
> +}

I'm not sure what you try to protect against, but only protecting a
single read or a single write won't really help.

Assuming you have two concurrent, typical, read-modify-write, you'll
have given back the lock during the modification, so you end up with
exactly the same issue than without the lock.

> +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..8130d4baee78
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_encoder.c
> @@ -0,0 +1,51 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * KMS driver for Loongson display controller
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#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..35e30bf3829a
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c
> @@ -0,0 +1,195 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * KMS driver for Loongson display controller
> + */
> +
> +/*
> + * 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"
> +
> +/*
> + * ls7a_gpio_i2c_set - 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);
> +}
> +
> +/*
> + * ls7a_gpio_i2c_get - 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)
> +		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);
> +	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;
> +}
> +
> +/*
> + * lsdc_get_i2c_adapter - 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..69d0b8f571d3
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.h
> @@ -0,0 +1,37 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * KMS driver for Loongson display controller
> + */
> +
> +/*
> + * 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..1588b7bd444f
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_irq.c
> @@ -0,0 +1,57 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * KMS driver for Loongson display controller
> + */
> +
> +/*
> + * 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..3a9eab02823c
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_irq.h
> @@ -0,0 +1,17 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * KMS driver for Loongson display controller
> + */
> +
> +/*
> + * 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..e44910364934
> --- /dev/null
> +++ b/drivers/gpu/drm/lsdc/lsdc_plane.c
> @@ -0,0 +1,517 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * KMS driver for Loongson display controller
> + */
> +
> +/*
> + * Authors:
> + *      Sui Jingfeng <suijingfeng at loongson.cn>
> + */
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_vblank.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_gem_vram_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 u32 lsdc_primary_formats[] = {
> +	DRM_FORMAT_RGB565,
> +	DRM_FORMAT_XRGB8888,
> +	DRM_FORMAT_ARGB8888,
> +};
> +
> +static const u32 lsdc_cursor_formats[] = {
> +	DRM_FORMAT_ARGB8888,
> +};
> +
> +static const u64 lsdc_fb_format_modifiers[] = {
> +	DRM_FORMAT_MOD_LINEAR,
> +	DRM_FORMAT_MOD_INVALID
> +};
> +
> +static void lsdc_update_fb_format(struct lsdc_device *ldev,
> +				  struct drm_crtc *crtc,
> +				  const struct drm_format_info *fmt_info)
> +{
> +	unsigned int index = drm_crtc_index(crtc);
> +	u32 val = 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 (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);
> +	} 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);
> +	}
> +}
> +
> +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(&ldev->drm, "CRTC0 FB0 will be use\n");
> +		} else {
> +			addr_reg = LSDC_CRTC0_FB_ADDR1_REG;
> +			drm_dbg(&ldev->drm, "CRTC0 FB1 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(&ldev->drm, "CRTC1 FB0 will be use\n");
> +		} else {
> +			addr_reg = LSDC_CRTC1_FB_ADDR1_REG;
> +			drm_dbg(&ldev->drm, "CRTC1 FB1 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(&ldev->drm, "crtc%u scantout from 0x%llx\n", index, paddr);
> +}
> +
> +static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb,
> +				       struct drm_plane_state *state,
> +				       unsigned int plane)
> +{
> +	unsigned int offset = fb->offsets[plane];
> +
> +	offset += fb->format->cpp[plane] * (state->src_x >> 16);
> +	offset += fb->pitches[plane] * (state->src_y >> 16);
> +
> +	return offset;
> +}
> +
> +static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb)
> +{
> +	struct drm_gem_vram_object *gbo;
> +	s64 gpu_addr;
> +
> +	gbo = drm_gem_vram_of_gem(fb->obj[0]);
> +	gpu_addr = drm_gem_vram_offset(gbo);
> +
> +	return gpu_addr;
> +}
> +
> +static int lsdc_pixpll_atomic_check(struct drm_crtc *crtc,
> +				    struct drm_crtc_state *state)
> +{
> +	struct lsdc_display_pipe *dispipe = container_of(crtc, struct lsdc_display_pipe, crtc);
> +	struct lsdc_pll *pixpll = &dispipe->pixpll;
> +	const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs;
> +	struct lsdc_crtc_state *priv_state = to_lsdc_crtc_state(state);
> +	bool ret = pfuncs->compute(pixpll, state->mode.clock, &priv_state->pparams);
> +
> +	if (ret)
> +		return 0;
> +
> +	drm_warn(crtc->dev, "failed find PLL parameters for %u\n", state->mode.clock);
> +
> +	return -EINVAL;
> +}

This should be in your CRTC atomic_check hook, not called by your plane
code.

> +static int lsdc_primary_plane_atomic_check(struct drm_plane *plane,
> +					   struct drm_atomic_state *state)
> +{
> +	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;
> +	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;
> +
> +	/*
> +	 * 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;

This is covered by the framework already.

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/20220222/5c06f840/attachment-0001.sig>


More information about the dri-devel mailing list