[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, ¶ms);
> +
> + 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