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 <suijingf...@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 <suijingf...@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 <suijingf...@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 <suijingf...@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 <suijingf...@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 <suijingf...@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 <suijingf...@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 <suijingf...@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 <suijingf...@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 <suijingf...@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 <suijingf...@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
signature.asc
Description: PGP signature