Hi, On Thu, Feb 03, 2022 at 04:25:44PM +0800, Sui Jingfeng wrote: > From: suijingfeng <suijingfeng@xxxxxxxxxxx> > > There is a display controller in loongson's LS2K1000 SoC and LS7A1000 > bridge, and the DC in those chip is a PCI device. This patch provide > a minimal support for this display controller which is mainly for > graphic environment bring up. > > This display controller has two display pipes but with only one hardware > cursor. Each way has a DVO output interface and the CRTC is able to scanout > from 1920x1080 resolution at 60Hz. The maxmium resolution is 2048x2048@60hz. > > LS2K1000 is a SoC, only system memory is available. Therefore scanout from > system memory is the only choice. We prefer the CMA helper base solution > because the gc1000 gpu can use etnaviv driver, in this case etnaviv and > lsdc could made a compatible pair. Even through it is possible to use VRAM > helper base solution on ls2k1000 by carving out part of system memory as > VRAM. > > For LS7A1000, there are 4 gpios whos control register is located at the dc > register space which is not the geneal purpose GPIO. The 4 gpios can emulate > two way i2c. One for DVO0, another for DVO1. This is the reason the why we > are not using the drm bridge framework. > > LS2K1000 and LS2K0500 SoC don't have such hardware, they use general purpose > GPIO emulated i2c or hardware i2c adapter from other module to serve this > purpose. Drm bridge and drm panel is suitable for those SoC, we have already > implement it on our own downstream kernel. But due to the upstream kernel > don't have gpio, pwm and i2c driver support for LS2K1000. We just can not > upstream our support for the drm bridge subsystem. > > The DC in LS7A1000 can scanout from both the system memory and the dedicate > VRAM attached to the ddr3 memory controller. Sadly, only scanout from the > VRAM is proved to be a reliable solution for massive product. Scanout from > the system memory suffer from some hardware deficiency which cause the > screen flickering under RAM pressure. This is the reason why we integrate > two distict helpers into one piece of device driver. But CMA base helper is > still usable on ls7a1000 for normal usage, expecially on ls7a1000+ bridge > chip. We have also implemented demage update on top of CMA helper which > copy the demaged shadow framebuffer region from system RAM to the real > framebuffer in VRAM manually. Using "lsdc.dirty_update=1" in the commmand > line will enable this driver mode. > > LS7A1000 have a 32x32 harware cursor, we just let the two CRTC share it > simply with the help of universe plane. LS7A2000 have two 64x64 harware > cursor. Surport for LS7A2000 is on the way. > > In short, we have built-in gpio emulated i2c support, we also have hardware > cursor support. The kind of tiny drivers in drm/tiny is not suitable for us, > we are not "tiny". > > +------+ HyperTransport 3.0 > | DDR4 | | > +------+ | +------------------------------------+ > || MC0 | | LS7A1000 +------------| > +----------+ | | | DDR3 | +------+ > | LS3A4000 |<--------->| +--------+ +-------+ | memory |<->| VRAM | > | CPU |<--------->| | GC1000 | | LSDC | | controller | +------+ > +----------+ | +--------+ +-+---+-+ +------------| > || MC1 +---------------|---|----------------+ > +------+ | | > | DDR4 | +-------+ DVO0 | | DVO1 +------+ > +------+ VGA <--|ADV7125|<---------+ +------->|TFP410|--> DVI/HDMI > +-------+ +------+ > > The above picture give a simple usage of LS7A1000, note that the encoder > is not necessary adv7125 or tfp410, it is a choice of the downstream board > manufacturer. Other candicate encoder can be ch7034b, sil9022 and ite66121 > etc. Therefore, we need device tree to provide board specific information. > Besides, the dc in both ls2k1000 and ls7k1000 have the vendor:device id of > 0x0014:0x7a06, the reverison id is also same. We can't tell it apart simply > (this is the firmware engineer's mistake). But firmware already flushed to > the board and borad already sold out, we choose to resolve those issues by > introduing device tree with board specific device support. > > For lsdc, there is only a 1:1 mapping of encoders and connectors. > > +-------------------+ _________ > | | | | > | CRTC0 --> DVO0 ---------> Encoder0 --> Connector0 ---> | Monitor | > | | ^ ^ |_________| > | | | | > | <----- i2c0 ----------------+ > | LSDC IP CORE | > | <----- i2c1 ----------------+ > | | | | _________ > | | | | | | > | CRTC1 --> DVO1 ---------> Encoder1 --> Connector1 ---> | Panel | > | | |_________| > +-------------------+ > > Below is a brief introduction of loongson's CPU, bridge chip and SoC. > LS2K1000 is a double core 1.0Ghz mips64r2 compatible SoC[1]. LS7A1000 is > a bridge chip made by Loongson corporation which act as north and/or south > bridge of loongson's desktop and server level processor. It is equivalent > to AMD RS780E+SB710 or something like that. More details can be read from > its user manual[2]. > > This bridge chip is typically use with LS3A3000, LS3A4000 and LS3A5000 cpu. > LS3A3000 is 4 core 1.45gHz mips64r2 compatible cpu. > LS3A4000 is 4 core 1.8gHz mips64r5 compatible cpu. > LS3A5000 is 4 core 2.5gHz loongarch cpu[3]. > > Nearly all mordern loongson CPU's cache coherency is maintained by hardware, > except for early version of ls2k1000. So we using cached coherent memory by > default, not writecombine. > > v2: fixup warnings reported by kernel test robot > > v3: fix more grammar mistakes in Kconfig reported by Randy Dunlap and give > more details about lsdc. > > v4: > 1) Add dts required and explain why device tree is required. > 2) Give more description about lsdc and vram helper base driver. > 3) Fix warnings reported by kernel test robot. > 4) Introduce stride_alignment member into struct lsdc_chip_desc, the > stride alignment is 256 bytes for ls7a1000, ls2k1000 and ls2k0500 SoC. > But ls7a2000 improve it to 32 bytes, We are prepare for extend the > support for the on coming device. > > v5: > 1) using writel and readl replace writeq and readq, to fix kernel test > robot report build error on other archtecture > 2) set default fb format to XRGB8888 at crtc reset time. > 3) fix typos. > > v6: > 1) Explain why we are not switch to drm dridge subsystem on ls2k1000. > 2) Explain why tiny drm driver is not suitable for us. > 3) Give a short description of the trival dirty uppdate implement based > on CMA helper. > 4) code clean up > > [1] https://wiki.debian.org/InstallingDebianOn/Lemote/Loongson2K1000 > [2] https://loongson.github.io/LoongArch-Documentation/Loongson-7A1000-usermanual-EN.html > [3] https://loongson.github.io/LoongArch-Documentation/Loongson-3A5000-usermanual-EN.html > > Reported-by: Randy Dunlap <rdunlap@xxxxxxxxxxxxx> > Reported-by: kernel test robot > Signed-off-by: suijingfeng <suijingfeng@xxxxxxxxxxx> > Signed-off-by: Sui Jingfeng <15330273260@xxxxxx> > --- > drivers/gpu/drm/Kconfig | 2 + > drivers/gpu/drm/Makefile | 1 + > drivers/gpu/drm/lsdc/Kconfig | 38 ++ > drivers/gpu/drm/lsdc/Makefile | 15 + > drivers/gpu/drm/lsdc/lsdc_connector.c | 443 ++++++++++++++ > drivers/gpu/drm/lsdc/lsdc_connector.h | 60 ++ > drivers/gpu/drm/lsdc/lsdc_crtc.c | 440 ++++++++++++++ > drivers/gpu/drm/lsdc/lsdc_drv.c | 846 ++++++++++++++++++++++++++ > drivers/gpu/drm/lsdc/lsdc_drv.h | 216 +++++++ > drivers/gpu/drm/lsdc/lsdc_encoder.c | 79 +++ > drivers/gpu/drm/lsdc/lsdc_i2c.c | 220 +++++++ > drivers/gpu/drm/lsdc/lsdc_i2c.h | 61 ++ > drivers/gpu/drm/lsdc/lsdc_irq.c | 77 +++ > drivers/gpu/drm/lsdc/lsdc_irq.h | 37 ++ > drivers/gpu/drm/lsdc/lsdc_plane.c | 681 +++++++++++++++++++++ > drivers/gpu/drm/lsdc/lsdc_pll.c | 657 ++++++++++++++++++++ > drivers/gpu/drm/lsdc/lsdc_pll.h | 109 ++++ > drivers/gpu/drm/lsdc/lsdc_regs.h | 246 ++++++++ > 18 files changed, 4228 insertions(+) > create mode 100644 drivers/gpu/drm/lsdc/Kconfig > create mode 100644 drivers/gpu/drm/lsdc/Makefile > create mode 100644 drivers/gpu/drm/lsdc/lsdc_connector.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_connector.h > create mode 100644 drivers/gpu/drm/lsdc/lsdc_crtc.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_drv.h > create mode 100644 drivers/gpu/drm/lsdc/lsdc_encoder.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_i2c.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_i2c.h > create mode 100644 drivers/gpu/drm/lsdc/lsdc_irq.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_irq.h > create mode 100644 drivers/gpu/drm/lsdc/lsdc_plane.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.c > create mode 100644 drivers/gpu/drm/lsdc/lsdc_pll.h > create mode 100644 drivers/gpu/drm/lsdc/lsdc_regs.h > > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig > index dfdd3ec5f793..18de1485e2ed 100644 > --- a/drivers/gpu/drm/Kconfig > +++ b/drivers/gpu/drm/Kconfig > @@ -405,6 +405,8 @@ source "drivers/gpu/drm/gud/Kconfig" > > source "drivers/gpu/drm/sprd/Kconfig" > > +source "drivers/gpu/drm/lsdc/Kconfig" > + > config DRM_HYPERV > tristate "DRM Support for Hyper-V synthetic video device" > depends on DRM && PCI && MMU && HYPERV > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile > index 8675c2af7ae1..2c5a76ced323 100644 > --- a/drivers/gpu/drm/Makefile > +++ b/drivers/gpu/drm/Makefile > @@ -133,3 +133,4 @@ obj-y += xlnx/ > obj-y += gud/ > obj-$(CONFIG_DRM_HYPERV) += hyperv/ > obj-$(CONFIG_DRM_SPRD) += sprd/ > +obj-$(CONFIG_DRM_LSDC) += lsdc/ > diff --git a/drivers/gpu/drm/lsdc/Kconfig b/drivers/gpu/drm/lsdc/Kconfig > new file mode 100644 > index 000000000000..7ed1b0fdbe1b > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/Kconfig > @@ -0,0 +1,38 @@ > +config DRM_LSDC > + tristate "DRM Support for loongson's display controller" > + depends on DRM && PCI > + depends on MACH_LOONGSON64 || LOONGARCH || MIPS || COMPILE_TEST > + select OF > + select CMA if HAVE_DMA_CONTIGUOUS > + select DMA_CMA if HAVE_DMA_CONTIGUOUS > + select DRM_KMS_HELPER > + select DRM_KMS_FB_HELPER > + select DRM_GEM_CMA_HELPER > + select VIDEOMODE_HELPERS > + select BACKLIGHT_PWM if CPU_LOONGSON2K > + select I2C_GPIO if CPU_LOONGSON2K > + select I2C_LS2X if CPU_LOONGSON2K > + default m > + help > + This is a KMS driver for the display controller in the LS7A1000 > + bridge chip and LS2K1000 SoC. The display controller has two > + display pipes and it is a PCI device. > + When using this driver on LS2K1000/LS2K0500 SoC, its framebuffer > + is located at system memory. > + If "M" is selected, the module will be called lsdc. > + > + If in doubt, say "Y". > + > +config DRM_LSDC_VRAM_DRIVER > + bool "vram helper based device driver support" > + depends on DRM_LSDC > + select DRM_VRAM_HELPER > + default y > + help > + When using this driver on LS7A1000 + LS3A3000/LS3A4000/LS3A5000 > + platform, the LS7A1000 bridge chip has dedicated video RAM. Using > + "lsdc.use_vram_helper=1" in the kernel command line will enable > + this driver mode and then the framebuffer will be located at the > + VRAM at the price of losing PRIME support. > + > + If in doubt, say "Y". This doesn't sound right. The driver should make the proper decision depending on the platform, not the user or the distribution. > diff --git a/drivers/gpu/drm/lsdc/Makefile b/drivers/gpu/drm/lsdc/Makefile > new file mode 100644 > index 000000000000..342990654478 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/Makefile > @@ -0,0 +1,15 @@ > +# > +# Makefile for the lsdc drm device driver. > +# > + > +lsdc-y := \ > + lsdc_drv.o \ > + lsdc_crtc.o \ > + lsdc_irq.o \ > + lsdc_plane.o \ > + lsdc_pll.o \ > + lsdc_i2c.o \ > + lsdc_encoder.o \ > + lsdc_connector.o > + > +obj-$(CONFIG_DRM_LSDC) += lsdc.o > diff --git a/drivers/gpu/drm/lsdc/lsdc_connector.c b/drivers/gpu/drm/lsdc/lsdc_connector.c > new file mode 100644 > index 000000000000..ae5fc0c90961 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_connector.c > @@ -0,0 +1,443 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright 2020 Loongson Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the > + * "Software"), to deal in the Software without restriction, including > + * without limitation the rights to use, copy, modify, merge, publish, > + * distribute, sub license, and/or sell copies of the Software, and to > + * permit persons to whom the Software is furnished to do so, subject to > + * the following conditions: > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL > + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, > + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR > + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE > + * USE OR OTHER DEALINGS IN THE SOFTWARE. > + * > + * The above copyright notice and this permission notice (including the > + * next paragraph) shall be included in all copies or substantial portions > + * of the Software. > + */ That's the MIT license, yet you claim the driver to be licensed under the GPLv2 or later? > + > +/* > + * Authors: > + * Sui Jingfeng <suijingfeng@xxxxxxxxxxx> > + */ > + > +#include <drm/drm_print.h> > +#include <drm/drm_edid.h> > +#include <drm/drm_probe_helper.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_connector.h> > + > +#include <video/videomode.h> > +#include <video/of_display_timing.h> > + > +#include "lsdc_drv.h" > +#include "lsdc_i2c.h" > +#include "lsdc_connector.h" > + > + > +static int lsdc_get_modes_from_edid(struct drm_connector *connector) > +{ > + struct drm_device *ddev = connector->dev; > + struct lsdc_connector *lconn = to_lsdc_connector(connector); > + struct edid *edid_p = (struct edid *)lconn->edid_data; > + int num = drm_add_edid_modes(connector, edid_p); > + > + if (num) > + drm_connector_update_edid_property(connector, edid_p); > + > + drm_dbg_kms(ddev, "%d modes added\n", num); > + > + return num; > +} > + > + > +static int lsdc_get_modes_from_timings(struct drm_connector *connector) > +{ > + struct drm_device *ddev = connector->dev; > + struct lsdc_connector *lconn = to_lsdc_connector(connector); > + struct display_timings *disp_tim = lconn->disp_tim; > + unsigned int num = 0; > + unsigned int i; > + > + for (i = 0; i < disp_tim->num_timings; i++) { > + const struct display_timing *dt = disp_tim->timings[i]; > + struct drm_display_mode *mode; > + struct videomode vm; > + > + videomode_from_timing(dt, &vm); > + mode = drm_mode_create(ddev); > + if (!mode) { > + drm_err(ddev, "failed to add mode %ux%u\n", > + dt->hactive.typ, dt->vactive.typ); > + continue; > + } > + > + drm_display_mode_from_videomode(&vm, mode); > + > + mode->type |= DRM_MODE_TYPE_DRIVER; > + > + if (i == disp_tim->native_mode) > + mode->type |= DRM_MODE_TYPE_PREFERRED; > + > + drm_mode_probed_add(connector, mode); > + num++; > + } > + > + drm_dbg_kms(ddev, "%d modes added\n", num); > + > + return num; > +} > + > + > +static int lsdc_get_modes_from_ddc(struct drm_connector *connector, > + struct i2c_adapter *ddc) > +{ > + unsigned int num = 0; > + struct edid *edid; > + > + edid = drm_get_edid(connector, ddc); > + if (edid) { > + drm_connector_update_edid_property(connector, edid); > + num = drm_add_edid_modes(connector, edid); > + kfree(edid); > + } > + > + return num; > +} > + > + > +static int lsdc_get_modes(struct drm_connector *connector) > +{ > + struct lsdc_connector *lconn = to_lsdc_connector(connector); > + unsigned int num = 0; > + > + if (lconn->has_edid) > + return lsdc_get_modes_from_edid(connector); > + > + if (lconn->has_disp_tim) > + return lsdc_get_modes_from_timings(connector); > + > + if (IS_ERR_OR_NULL(lconn->ddc) == false) > + return lsdc_get_modes_from_ddc(connector, lconn->ddc); > + > + if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) { > + num = drm_add_modes_noedid(connector, > + connector->dev->mode_config.max_width, > + connector->dev->mode_config.max_height); > + > + drm_set_preferred_mode(connector, 1024, 768); > + > + return num; > + } > + > + > + /* > + * In case we cannot retrieve the EDIDs (broken or missing i2c > + * bus), fallback on the XGA standards, because we are for board > + * bringup. > + */ > + num = drm_add_modes_noedid(connector, 1920, 1200); > + > + /* And prefer a mode pretty much anyone can handle */ > + drm_set_preferred_mode(connector, 1024, 768); > + > + return num; > + > +} > + > + > +static enum drm_connector_status > +lsdc_connector_detect(struct drm_connector *connector, bool force) > +{ > + struct lsdc_connector *lconn = to_lsdc_connector(connector); > + > + if (lconn->has_edid == true) > + return connector_status_connected; > + > + if (lconn->has_disp_tim == true) > + return connector_status_connected; > + > + if (IS_ERR_OR_NULL(lconn->ddc) == false) > + return drm_probe_ddc(lconn->ddc); > + > + if (lconn->ddc && drm_probe_ddc(lconn->ddc)) > + return connector_status_connected; > + > + if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) > + return connector_status_connected; > + > + if ((connector->connector_type == DRM_MODE_CONNECTOR_DVIA) || > + (connector->connector_type == DRM_MODE_CONNECTOR_DVID) || > + (connector->connector_type == DRM_MODE_CONNECTOR_DVII)) > + return connector_status_disconnected; > + > + if ((connector->connector_type == DRM_MODE_CONNECTOR_HDMIA) || > + (connector->connector_type == DRM_MODE_CONNECTOR_HDMIB)) > + return connector_status_disconnected; > + > + return connector_status_unknown; > +} > + > + > +/* > + * @connector: point to the drm_connector structure > + * > + * Clean up connector resources > + */ > +static void lsdc_connector_destroy(struct drm_connector *connector) > +{ > + struct drm_device *ddev = connector->dev; > + struct lsdc_connector *lconn = to_lsdc_connector(connector); > + > + if (lconn) { > + if (lconn->ddc) > + lsdc_destroy_i2c(connector->dev, lconn->ddc); > + > + drm_info(ddev, "destroying connector%u\n", lconn->index); > + > + devm_kfree(ddev->dev, lconn); > + } > + > + drm_connector_cleanup(connector); > +} > + > + > +static const struct drm_connector_helper_funcs lsdc_connector_helpers = { > + .get_modes = lsdc_get_modes, > +}; > + > +/* > + * These provide the minimum set of functions required to handle a connector > + * > + * Control connectors on a given device. > + * > + * Each CRTC may have one or more connectors attached to it. > + * The functions below allow the core DRM code to control > + * connectors, enumerate available modes, etc. > + */ > +static const struct drm_connector_funcs lsdc_connector_funcs = { > + .dpms = drm_helper_connector_dpms, > + .detect = lsdc_connector_detect, > + .fill_modes = drm_helper_probe_single_connector_modes, > + .destroy = lsdc_connector_destroy, > + .reset = drm_atomic_helper_connector_reset, > + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, > +}; > + > + > +/* Get the simple EDID data from the device tree > + * the length must be EDID_LENGTH, since it is simple. > + * > + * @np: device node contain edid data > + * @edid_data: where the edid data to store to > + */ > +static bool lsdc_get_edid_from_dtb(struct device_node *np, > + unsigned char *edid_data) > +{ > + int length; > + const void *prop; > + > + if (np == NULL) > + return false; > + > + prop = of_get_property(np, "edid", &length); > + if (prop && (length == EDID_LENGTH)) { > + memcpy(edid_data, prop, EDID_LENGTH); > + return true; > + } > + > + return false; > +} You don't have a device tree binding for that driver, this is something that is required. And it's not clear to me why you'd want EDID in the DTB? > + > +/* Get display timings from the device tree > + * > + * @np: device node contain the display timings > + * @pptim: point to where the pointer of struct display_timings store to > + */ > +static void lsdc_get_display_timings_from_dtb(struct device_node *np, > + struct display_timings **pptim) > +{ > + struct display_timings *timings; > + > + if (np == NULL) > + return; > + > + timings = of_get_display_timings(np); > + if (timings) > + *pptim = timings; > +} > + > + > +static int lsdc_get_connector_type(struct drm_device *ddev, > + struct device_node *output, > + unsigned int index) > +{ > + const char *name; > + int ret; > + > + ret = of_property_read_string(output, "connector", &name); > + if (ret < 0) > + return DRM_MODE_CONNECTOR_Unknown; > + > + if (strncmp(name, "vga-connector", 13) == 0) { > + ret = DRM_MODE_CONNECTOR_VGA; > + drm_info(ddev, "connector%d is VGA\n", index); > + } else if (strncmp(name, "dvi-connector", 13) == 0) { > + bool analog, digital; > + > + analog = of_property_read_bool(output, "analog"); > + digital = of_property_read_bool(output, "digital"); > + > + if (analog && !digital) > + ret = DRM_MODE_CONNECTOR_DVIA; > + else if (analog && digital) > + ret = DRM_MODE_CONNECTOR_DVII; > + else > + ret = DRM_MODE_CONNECTOR_DVID; > + > + drm_info(ddev, "connector%d is DVI\n", index); > + } else if (strncmp(name, "virtual-connector", 17) == 0) { > + ret = DRM_MODE_CONNECTOR_VIRTUAL; > + drm_info(ddev, "connector%d is virtual\n", index); > + } else if (strncmp(name, "dpi-connector", 13) == 0) { > + ret = DRM_MODE_CONNECTOR_DPI; > + drm_info(ddev, "connector%d is DPI\n", index); > + } else if (strncmp(name, "hdmi-connector", 14) == 0) { > + int res; > + const char *hdmi_type; > + > + res = of_property_read_string(output, "type", &hdmi_type); > + if (res == 0) { > + if (!strcmp(hdmi_type, "b")) > + ret = DRM_MODE_CONNECTOR_HDMIB; > + else > + ret = DRM_MODE_CONNECTOR_HDMIA; > + } else > + ret = DRM_MODE_CONNECTOR_HDMIA; > + > + drm_info(ddev, "connector%d is HDMI, type is %s\n", > + index, hdmi_type); > + } else { > + ret = DRM_MODE_CONNECTOR_Unknown; > + drm_info(ddev, "The type of connector%d is unknown\n", index); > + } > + > + return ret; > +} > + > + > +struct lsdc_connector *lsdc_connector_init(struct lsdc_device *ldev, > + unsigned int index) > +{ > + struct drm_device *ddev = &ldev->drm; > + struct device_node *np = ddev->dev->of_node; > + struct device_node *output = NULL; > + struct lsdc_connector *lconn; > + struct drm_connector *connector; > + bool available = false; > + unsigned int connector_type; > + int ret; > + > + lconn = devm_kzalloc(ddev->dev, sizeof(*lconn), GFP_KERNEL); > + if (lconn == NULL) > + return ERR_PTR(-ENOMEM); > + > + lconn->index = index; > + > + output = of_parse_phandle(np, "output-ports", index); > + if (output) { > + struct device_node *disp_tims_np; > + > + available = of_device_is_available(output); > + > + if (available == false) { > + drm_info(ddev, "connector%d is not available\n", index); > + of_node_put(output); > + return NULL; > + } > + > + lconn->has_edid = of_property_read_bool(output, "edid"); > + disp_tims_np = of_get_child_by_name(output, "display-timings"); > + if (disp_tims_np) { > + of_node_put(disp_tims_np); > + lconn->has_disp_tim = true; > + } else > + lconn->has_disp_tim = false; > + } else > + drm_warn(ddev, "no output-ports property, please update dtb\n"); > + > + /* > + * Providing a blindly support even through there is > + * no output-ports property in the dtb. > + */ > + if (lconn->has_edid) { > + lsdc_get_edid_from_dtb(output, lconn->edid_data); > + drm_info(ddev, "connector%d provide edid\n", index); > + } > + > + if (lconn->has_disp_tim) { > + lsdc_get_display_timings_from_dtb(output, &lconn->disp_tim); > + drm_info(ddev, "connector%d provide display timings\n", index); > + } > + > + connector_type = lsdc_get_connector_type(ddev, output, index); > + > + if (output) > + of_node_put(output); > + > + connector = &lconn->base; > + > + if (connector_type == DRM_MODE_CONNECTOR_VIRTUAL) > + goto SKIPED_CREATE_DDC; > + > + /* bypass the ddc creation if the edid or display timing is provided */ > + if ((lconn->has_edid == false) && > + (lconn->has_disp_tim == false)) { > + const struct lsdc_chip_desc * const dc = ldev->desc; > + > + if (dc->have_builtin_i2c) > + lconn->ddc = lsdc_create_i2c_chan(ddev, index); > + else > + lconn->ddc = lsdc_get_i2c_adapter(ddev, index); > + > + if (lconn->ddc && (IS_ERR(lconn->ddc) == false)) { > + drm_info(ddev, "i2c%d for connector%d created\n", > + i2c_adapter_id(lconn->ddc), index); > + > + } else > + drm_warn(ddev, "Get i2c adapter failed: %ld\n", > + PTR_ERR(lconn->ddc)); > + } > + > + /* only pull if the connector have a ddc */ > + connector->polled = DRM_CONNECTOR_POLL_CONNECT | > + DRM_CONNECTOR_POLL_DISCONNECT; > + > +SKIPED_CREATE_DDC: > + ret = drm_connector_init_with_ddc(ddev, > + connector, > + &lsdc_connector_funcs, > + connector_type, > + lconn->ddc); > + if (ret) { > + drm_err(ddev, "init connector%d failed\n", index); > + goto err_i2c_destroy; > + } > + > + drm_connector_helper_add(connector, &lsdc_connector_helpers); > + > + return lconn; > + > +err_i2c_destroy: > + lsdc_destroy_i2c(ddev, lconn->ddc); > + return NULL; > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_connector.h b/drivers/gpu/drm/lsdc/lsdc_connector.h > new file mode 100644 > index 000000000000..e9f94a969f74 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_connector.h > @@ -0,0 +1,60 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Copyright 2020 Loongson Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the > + * "Software"), to deal in the Software without restriction, including > + * without limitation the rights to use, copy, modify, merge, publish, > + * distribute, sub license, and/or sell copies of the Software, and to > + * permit persons to whom the Software is furnished to do so, subject to > + * the following conditions: > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL > + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, > + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR > + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE > + * USE OR OTHER DEALINGS IN THE SOFTWARE. > + * > + * The above copyright notice and this permission notice (including the > + * next paragraph) shall be included in all copies or substantial portions > + * of the Software. > + */ > + > +/* > + * Authors: > + * Sui Jingfeng <suijingfeng@xxxxxxxxxxx> > + */ > + > + > +#ifndef __LSDC_CONNECTOR_H__ > +#define __LSDC_CONNECTOR_H__ > + > +#include <drm/drm_device.h> > +#include <drm/drm_connector.h> > + > +struct lsdc_connector { > + struct drm_connector base; > + > + struct i2c_adapter *ddc; > + > + /* pass EDID from dtb support */ > + unsigned char edid_data[EDID_LENGTH]; > + bool has_edid; > + > + /* pass display timmings from dtb support */ > + struct display_timings *disp_tim; > + bool has_disp_tim; > + > + int index; > +}; > + > +#define to_lsdc_connector(x) \ > + container_of(x, struct lsdc_connector, base) > + > +struct lsdc_connector *lsdc_connector_init(struct lsdc_device *ldev, > + unsigned int index); > + > +#endif > diff --git a/drivers/gpu/drm/lsdc/lsdc_crtc.c b/drivers/gpu/drm/lsdc/lsdc_crtc.c > new file mode 100644 > index 000000000000..7531389f4896 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_crtc.c > @@ -0,0 +1,440 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright 2020 Loongson Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the > + * "Software"), to deal in the Software without restriction, including > + * without limitation the rights to use, copy, modify, merge, publish, > + * distribute, sub license, and/or sell copies of the Software, and to > + * permit persons to whom the Software is furnished to do so, subject to > + * the following conditions: > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL > + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, > + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR > + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE > + * USE OR OTHER DEALINGS IN THE SOFTWARE. > + * > + * The above copyright notice and this permission notice (including the > + * next paragraph) shall be included in all copies or substantial portions > + * of the Software. > + */ > + > +/* > + * Authors: > + * Sui Jingfeng <suijingfeng@xxxxxxxxxxx> > + */ > + > +#include <drm/drm_print.h> > +#include <drm/drm_device.h> > +#include <drm/drm_crtc.h> > +#include <drm/drm_plane.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_vblank.h> > +#include <drm/drm_drv.h> > + > + > +#include <drm/drm_fb_cma_helper.h> > +#include <drm/drm_gem_cma_helper.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_gem_framebuffer_helper.h> > +#include <drm/drm_gem_atomic_helper.h> > +#include <drm/drm_damage_helper.h> > + > +#include "lsdc_drv.h" > +#include "lsdc_regs.h" > +#include "lsdc_pll.h" > + > + > +static int lsdc_crtc_enable_vblank(struct drm_crtc *crtc) > +{ > + struct lsdc_device *ldev = to_lsdc(crtc->dev); > + unsigned int index = drm_crtc_index(crtc); > + struct drm_crtc_state *state = crtc->state; > + u32 val; > + > + if (state->enable) { > + val = lsdc_reg_read32(ldev, LSDC_INT_REG); > + > + if (index == 0) > + val |= INT_CRTC0_VS_EN; > + else if (index == 1) > + val |= INT_CRTC1_VS_EN; > + > + lsdc_reg_write32(ldev, LSDC_INT_REG, val); > + } > + > + return 0; > +} > + > + > +static void lsdc_crtc_disable_vblank(struct drm_crtc *crtc) > +{ > + struct lsdc_device *ldev = to_lsdc(crtc->dev); > + unsigned int index = drm_crtc_index(crtc); > + u32 val; > + > + val = lsdc_reg_read32(ldev, LSDC_INT_REG); > + > + if (index == 0) > + val &= ~INT_CRTC0_VS_EN; > + else if (index == 1) > + val &= ~INT_CRTC1_VS_EN; > + > + lsdc_reg_write32(ldev, LSDC_INT_REG, val); > +} > + > + > +static void lsdc_crtc_reset(struct drm_crtc *crtc) > +{ > + struct drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + unsigned int index = drm_crtc_index(crtc); > + struct lsdc_crtc_state *priv_crtc_state; > + u32 val; > + > + /* The crtc get soft reset if bit 20 of CRTC*_CFG_REG > + * is write with falling edge. > + * > + * Doing this to switch from soft reset state to working state > + */ > + if (index == 0) { > + val = CFG_RESET_BIT | CFG_OUTPUT_EN_BIT | LSDC_PF_XRGB8888; > + lsdc_reg_write32(ldev, LSDC_CRTC0_CFG_REG, val); > + } else if (index == 1) { > + val = CFG_RESET_BIT | CFG_OUTPUT_EN_BIT | LSDC_PF_XRGB8888; > + lsdc_reg_write32(ldev, LSDC_CRTC1_CFG_REG, val); > + } > + > + > + if (crtc->state) { > + priv_crtc_state = to_lsdc_crtc_state(crtc->state); > + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); > + kfree(priv_crtc_state); > + } > + > + priv_crtc_state = kzalloc(sizeof(*priv_crtc_state), GFP_KERNEL); > + if (!priv_crtc_state) > + return; > + > + priv_crtc_state->pix_fmt = val & CFG_PIX_FMT_MASK; > + > + __drm_atomic_helper_crtc_reset(crtc, &priv_crtc_state->base); > + > + drm_info(ddev, "crtc%u reset\n", index); > +} > + > + > +static void lsdc_crtc_atomic_destroy_state(struct drm_crtc *crtc, > + struct drm_crtc_state *state) > +{ > + struct lsdc_crtc_state *priv_crtc_state = to_lsdc_crtc_state(state); > + > + __drm_atomic_helper_crtc_destroy_state(&priv_crtc_state->base); > + > + kfree(priv_crtc_state); > +} > + > + > +static struct drm_crtc_state *lsdc_crtc_atomic_duplicate_state(struct drm_crtc *crtc) > +{ > + struct lsdc_crtc_state *new_priv_state; > + struct lsdc_crtc_state *old_priv_state; > + struct drm_device *ddev = crtc->dev; > + > + if (drm_WARN_ON(ddev, !crtc->state)) > + return NULL; > + > + new_priv_state = kmalloc(sizeof(*new_priv_state), GFP_KERNEL); > + if (!new_priv_state) > + return NULL; > + > + __drm_atomic_helper_crtc_duplicate_state(crtc, &new_priv_state->base); > + > + old_priv_state = to_lsdc_crtc_state(crtc->state); > + > + memcpy(&new_priv_state->pparams, &old_priv_state->pparams, > + sizeof(new_priv_state->pparams)); > + > + new_priv_state->pix_fmt = old_priv_state->pix_fmt; > + > + return &new_priv_state->base; > +} > + > + > +static const struct drm_crtc_funcs lsdc_crtc_funcs = { > + .reset = lsdc_crtc_reset, > + .destroy = drm_crtc_cleanup, > + .set_config = drm_atomic_helper_set_config, > + .page_flip = drm_atomic_helper_page_flip, > + .atomic_duplicate_state = lsdc_crtc_atomic_duplicate_state, > + .atomic_destroy_state = lsdc_crtc_atomic_destroy_state, > + .enable_vblank = lsdc_crtc_enable_vblank, > + .disable_vblank = lsdc_crtc_disable_vblank, > +}; > + > + > +static enum drm_mode_status > +lsdc_crtc_helper_mode_valid(struct drm_crtc *crtc, > + const struct drm_display_mode *mode) > +{ > + struct drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + const struct lsdc_chip_desc *desc = ldev->desc; > + > + if (mode->hdisplay > desc->max_width) > + return MODE_BAD_HVALUE; > + if (mode->vdisplay > desc->max_height) > + return MODE_BAD_VVALUE; > + > + if (mode->clock > desc->max_pixel_clk) { > + drm_dbg_kms(ddev, "mode %dx%d, pixel clock=%d is too high\n", > + mode->hdisplay, mode->vdisplay, mode->clock); > + return MODE_CLOCK_HIGH; > + } > + > + /* the crtc hardware dma take 256 bytes once a time > + * TODO: check RGB565 support > + */ > + if ((mode->hdisplay * 4) % desc->stride_alignment) { > + drm_dbg_kms(ddev, "stride is not %u bytes aligned\n", > + desc->stride_alignment); > + return MODE_BAD; > + } > + > + return MODE_OK; > +} mode_valid will only prevent the mode from being advertised to the userspace, but you need atomic_check if you want to prevent those modes to be used by anybody. > + > +static void lsdc_update_pixclk(struct drm_crtc *crtc, unsigned int pixclk, bool verbose) > +{ > + struct lsdc_display_pipe *dispipe; > + struct lsdc_pll *pixpll; > + const struct lsdc_pixpll_funcs *clkfun; > + struct lsdc_crtc_state *priv_crtc_state; > + > + priv_crtc_state = to_lsdc_crtc_state(crtc->state); > + > + dispipe = container_of(crtc, struct lsdc_display_pipe, crtc); > + pixpll = &dispipe->pixpll; > + clkfun = pixpll->funcs; > + > + /* config the pixel pll */ > + clkfun->update(pixpll, &priv_crtc_state->pparams); > + > + if (verbose) > + clkfun->print(pixpll, pixclk); > +} > + > + > +static void lsdc_crtc_helper_mode_set_nofb(struct drm_crtc *crtc) > +{ > + struct drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct drm_display_mode *mode = &crtc->state->adjusted_mode; > + unsigned int hr = mode->hdisplay; > + unsigned int hss = mode->hsync_start; > + unsigned int hse = mode->hsync_end; > + unsigned int hfl = mode->htotal; > + unsigned int vr = mode->vdisplay; > + unsigned int vss = mode->vsync_start; > + unsigned int vse = mode->vsync_end; > + unsigned int vfl = mode->vtotal; > + unsigned int pixclock = mode->clock; > + unsigned int index = drm_crtc_index(crtc); > + > + > + if (index == 0) { > + /* CRTC 0 */ > + u32 hsync, vsync; > + > + lsdc_reg_write32(ldev, LSDC_CRTC0_FB_ORIGIN_REG, 0); > + > + /* 26:16 total pixels, 10:0 visiable pixels, in horizontal */ > + lsdc_reg_write32(ldev, LSDC_CRTC0_HDISPLAY_REG, > + (mode->crtc_htotal << 16) | mode->crtc_hdisplay); > + > + /* 26:16 total pixels, 10:0 visiable pixels, in vertical */ > + lsdc_reg_write32(ldev, LSDC_CRTC0_VDISPLAY_REG, > + (mode->crtc_vtotal << 16) | mode->crtc_vdisplay); > + > + /* 26:16 hsync end, 10:0 hsync start */ > + hsync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start; > + > + if (mode->flags & DRM_MODE_FLAG_NHSYNC) > + hsync |= INV_HSYNC_BIT; > + > + lsdc_reg_write32(ldev, LSDC_CRTC0_HSYNC_REG, EN_HSYNC_BIT | hsync); > + > + /* 26:16 vsync end, 10:0 vsync start */ > + vsync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start; > + > + if (mode->flags & DRM_MODE_FLAG_NVSYNC) > + vsync |= INV_VSYNC_BIT; > + > + lsdc_reg_write32(ldev, LSDC_CRTC0_VSYNC_REG, EN_VSYNC_BIT | vsync); > + > + } else if (index == 1) { > + /* CRTC 1 */ > + u32 hsync, vsync; > + > + lsdc_reg_write32(ldev, LSDC_CRTC1_FB_ORIGIN_REG, 0); > + > + /* 26:16 total pixels, 10:0 visiable pixels, in horizontal */ > + lsdc_reg_write32(ldev, LSDC_CRTC1_HDISPLAY_REG, > + (mode->crtc_htotal << 16) | mode->crtc_hdisplay); > + > + /* 26:16 total pixels, 10:0 visiable pixels, in vertical */ > + lsdc_reg_write32(ldev, LSDC_CRTC1_VDISPLAY_REG, > + (mode->crtc_vtotal << 16) | mode->crtc_vdisplay); > + > + /* 26:16 hsync end, 10:0 hsync start */ > + hsync = (mode->crtc_hsync_end << 16) | mode->crtc_hsync_start; > + > + if (mode->flags & DRM_MODE_FLAG_NHSYNC) > + hsync |= INV_HSYNC_BIT; > + > + lsdc_reg_write32(ldev, LSDC_CRTC1_HSYNC_REG, EN_HSYNC_BIT | hsync); > + > + /* 26:16 vsync end, 10:0 vsync start */ > + vsync = (mode->crtc_vsync_end << 16) | mode->crtc_vsync_start; > + > + if (mode->flags & DRM_MODE_FLAG_NVSYNC) > + vsync |= INV_VSYNC_BIT; > + > + lsdc_reg_write32(ldev, LSDC_CRTC1_VSYNC_REG, EN_VSYNC_BIT | vsync); > + } > + > + drm_dbg_kms(ddev, "hdisplay=%d, hsync_start=%d, hsync_end=%d, htotal=%d\n", > + hr, hss, hse, hfl); > + > + drm_dbg_kms(ddev, "vdisplay=%d, vsync_start=%d, vsync_end=%d, vtotal=%d\n", > + vr, vss, vse, vfl); > + > + drm_dbg_kms(ddev, "%s modeset: %ux%u\n", crtc->name, hr, vr); > + > + lsdc_update_pixclk(crtc, pixclock, ldev->verbose); > +} > + > + > +static void lsdc_enable_display(struct lsdc_device *ldev, unsigned int index) > +{ > + u32 val; > + > + if (index == 0) { > + val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG); > + val |= CFG_OUTPUT_EN_BIT; > + lsdc_reg_write32(ldev, LSDC_CRTC0_CFG_REG, val); > + } else if (index == 1) { > + val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG); > + val |= CFG_OUTPUT_EN_BIT; > + lsdc_reg_write32(ldev, LSDC_CRTC1_CFG_REG, val); > + } > +} > + > + > +static void lsdc_disable_display(struct lsdc_device *ldev, unsigned int index) > +{ > + u32 val; > + > + if (index == 0) { > + val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG); > + val &= ~CFG_OUTPUT_EN_BIT; > + lsdc_reg_write32(ldev, LSDC_CRTC0_CFG_REG, val); > + } else if (index == 1) { > + val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG); > + val &= ~CFG_OUTPUT_EN_BIT; > + lsdc_reg_write32(ldev, LSDC_CRTC1_CFG_REG, val); > + } > +} > + > + > +static void lsdc_crtc_helper_atomic_enable(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + drm_crtc_vblank_on(crtc); > + > + lsdc_enable_display(ldev, drm_crtc_index(crtc)); > + > + drm_dbg_kms(ddev, "%s: enabled\n", crtc->name); > +} > + > + > +static void lsdc_crtc_helper_atomic_disable(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct drm_device *ddev = crtc->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + drm_crtc_vblank_off(crtc); > + > + lsdc_disable_display(ldev, drm_crtc_index(crtc)); > + > + drm_dbg_kms(ddev, "%s: disabled\n", crtc->name); > +} > + > + > +static void lsdc_crtc_atomic_flush(struct drm_crtc *crtc, > + struct drm_atomic_state *state) > +{ > + struct drm_pending_vblank_event *event = crtc->state->event; > + > + if (event) { > + crtc->state->event = NULL; > + > + spin_lock_irq(&crtc->dev->event_lock); > + if (drm_crtc_vblank_get(crtc) == 0) > + drm_crtc_arm_vblank_event(crtc, event); > + else > + drm_crtc_send_vblank_event(crtc, event); > + spin_unlock_irq(&crtc->dev->event_lock); > + } > +} > + > + > +static const struct drm_crtc_helper_funcs lsdc_crtc_helper_funcs = { > + .mode_valid = lsdc_crtc_helper_mode_valid, > + .mode_set_nofb = lsdc_crtc_helper_mode_set_nofb, > + .atomic_enable = lsdc_crtc_helper_atomic_enable, > + .atomic_disable = lsdc_crtc_helper_atomic_disable, > + .atomic_flush = lsdc_crtc_atomic_flush, > +}; > + > + > + > +/** > + * lsdc_crtc_init > + * > + * @ddev: point to the drm_device structure > + * @index: hardware crtc index > + * > + * Init CRTC > + */ > +int lsdc_crtc_init(struct drm_device *ddev, > + struct drm_crtc *crtc, > + unsigned int index, > + struct drm_plane *primary, > + struct drm_plane *cursor) > +{ > + int ret; > + > + drm_crtc_helper_add(crtc, &lsdc_crtc_helper_funcs); > + > + ret = drm_mode_crtc_set_gamma_size(crtc, 256); > + if (ret) > + drm_warn(ddev, "set the gamma table size failed\n"); > + > + return drm_crtc_init_with_planes(ddev, > + crtc, > + primary, > + cursor, > + &lsdc_crtc_funcs, > + "crtc%d", > + index); > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.c b/drivers/gpu/drm/lsdc/lsdc_drv.c > new file mode 100644 > index 000000000000..aac8901c3431 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_drv.c > @@ -0,0 +1,846 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright 2020 Loongson Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the > + * "Software"), to deal in the Software without restriction, including > + * without limitation the rights to use, copy, modify, merge, publish, > + * distribute, sub license, and/or sell copies of the Software, and to > + * permit persons to whom the Software is furnished to do so, subject to > + * the following conditions: > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL > + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, > + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR > + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE > + * USE OR OTHER DEALINGS IN THE SOFTWARE. > + * > + * The above copyright notice and this permission notice (including the > + * next paragraph) shall be included in all copies or substantial portions > + * of the Software. > + */ > + > +/* > + * Authors: > + * Sui Jingfeng <suijingfeng@xxxxxxxxxxx> > + */ > + > +#include <linux/errno.h> > +#include <linux/string.h> > +#include <linux/module.h> > +#include <linux/pci.h> > +#include <linux/of_reserved_mem.h> > + > +#include <drm/drm_drv.h> > +#include <drm/drm_aperture.h> > +#include <drm/drm_of.h> > +#include <drm/drm_plane.h> > +#include <drm/drm_vblank.h> > +#include <drm/drm_debugfs.h> > +#include <drm/drm_fb_helper.h> > +#include <drm/drm_crtc_helper.h> > +#include <drm/drm_gem_cma_helper.h> > +#include <drm/drm_fb_cma_helper.h> > +#include <drm/drm_gem_framebuffer_helper.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_damage_helper.h> > +#include <drm/drm_probe_helper.h> > + > +#include "lsdc_drv.h" > +#include "lsdc_irq.h" > +#include "lsdc_regs.h" > +#include "lsdc_connector.h" > +#include "lsdc_pll.h" > + > + > +#define DRIVER_AUTHOR "Sui Jingfeng <suijingfeng@xxxxxxxxxxx>" > +#define DRIVER_NAME "lsdc" > +#define DRIVER_DESC "drm driver for loongson's display controller" > +#define DRIVER_DATE "20200701" > +#define DRIVER_MAJOR 1 > +#define DRIVER_MINOR 0 > +#define DRIVER_PATCHLEVEL 0 > + > +static int lsdc_modeset = 1; > +MODULE_PARM_DESC(modeset, "Enable/disable CMA-based KMS(1 = enabled(default), 0 = disabled)"); > +module_param_named(modeset, lsdc_modeset, int, 0644); > + > +static int lsdc_cached_coherent = 1; > +MODULE_PARM_DESC(cached_coherent, "uss cached coherent mapping(1 = enabled(default), 0 = disabled)"); > +module_param_named(cached_coherent, lsdc_cached_coherent, int, 0644); > + > +static int lsdc_dirty_update = -1; > +MODULE_PARM_DESC(dirty_update, "enable dirty update(1 = enabled, 0 = disabled(default))"); > +module_param_named(dirty_update, lsdc_dirty_update, int, 0644); > + > +static int lsdc_use_vram_helper = -1; > +MODULE_PARM_DESC(use_vram_helper, "use vram helper based solution(1 = enabled, 0 = disabled(default))"); > +module_param_named(use_vram_helper, lsdc_use_vram_helper, int, 0644); > + > +static int lsdc_verbose = -1; > +MODULE_PARM_DESC(verbose, "Enable/disable print some key information"); > +module_param_named(verbose, lsdc_verbose, int, 0644); It's not really clear to me why you need any of those parameters. Why would a user want to use a non coherent mapping for example? > + > +static const struct lsdc_chip_desc dc_in_ls2k1000 = { > + .chip = LSDC_CHIP_2K1000, > + .num_of_crtc = LSDC_MAX_CRTC, > + /* ls2k1000 user manual say the max pixel clock can be about 200MHz */ > + .max_pixel_clk = 200000, > + .max_width = 2560, > + .max_height = 2048, > + .num_of_hw_cursor = 1, > + .hw_cursor_w = 32, > + .hw_cursor_h = 32, > + .have_builtin_i2c = false, > + .stride_alignment = 256, > +}; > + > +static const struct lsdc_chip_desc dc_in_ls2k0500 = { > + .chip = LSDC_CHIP_2K0500, > + .num_of_crtc = LSDC_MAX_CRTC, > + .max_pixel_clk = 200000, > + .max_width = 2048, > + .max_height = 2048, > + .num_of_hw_cursor = 1, > + .hw_cursor_w = 32, > + .hw_cursor_h = 32, > + .have_builtin_i2c = false, > + .stride_alignment = 256, > +}; > + > +static const struct lsdc_chip_desc dc_in_ls7a1000 = { > + .chip = LSDC_CHIP_7A1000, > + .num_of_crtc = LSDC_MAX_CRTC, > + .max_pixel_clk = 200000, > + .max_width = 2048, > + .max_height = 2048, > + .num_of_hw_cursor = 1, > + .hw_cursor_w = 32, > + .hw_cursor_h = 32, > + .have_builtin_i2c = true, > + .stride_alignment = 256, > +}; > + > +/* > + * We need the device tree provided additional information to tell the DC > + * in different chip apart. > + */ > +static const struct of_device_id lsdc_drm_of_match[] = { > + { .compatible = "loongson,loongson64c-4core-ls7a", .data = &dc_in_ls7a1000 }, > + { .compatible = "loongson,loongson64g-4core-ls7a", .data = &dc_in_ls7a1000 }, > + { .compatible = "loongson,loongson64-2core-2k1000", .data = &dc_in_ls2k1000 }, > + { .compatible = "loongson,loongson2k1000", .data = &dc_in_ls2k1000 }, > + { .compatible = "loongson,ls2k1000", .data = &dc_in_ls2k1000 }, > + { .compatible = "loongson,display-subsystem", .data = &dc_in_ls2k1000 }, > + { .compatible = "loongson,ls-fb", .data = &dc_in_ls2k1000 }, > + { .compatible = "loongson,loongson2k0500", .data = &dc_in_ls2k0500 }, > + { /* sentinel */ }, > +}; > + > + > +static struct drm_framebuffer * > +lsdc_drm_gem_fb_create(struct drm_device *ddev, struct drm_file *file, > + const struct drm_mode_fb_cmd2 *mode_cmd) > +{ > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + if (ldev->dirty_update) > + return drm_gem_fb_create_with_dirty(ddev, file, mode_cmd); > + > + return drm_gem_fb_create(ddev, file, mode_cmd); > +} > + > + > +static enum drm_mode_status lsdc_device_mode_valid(struct drm_device *ddev, > + const struct drm_display_mode *mode) > +{ > +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + if (ldev->use_vram_helper == true) > + return drm_vram_helper_mode_valid(ddev, mode); > +#endif > + > + return MODE_OK; > +} > + > + > +static const struct drm_mode_config_funcs lsdc_mode_config_funcs = { > + .fb_create = lsdc_drm_gem_fb_create, > + .output_poll_changed = drm_fb_helper_output_poll_changed, > + .atomic_check = drm_atomic_helper_check, > + .atomic_commit = drm_atomic_helper_commit, > + .mode_valid = lsdc_device_mode_valid, > +}; > + > + > +#ifdef CONFIG_DEBUG_FS > +static int lsdc_show_pxlclock(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *)m->private; > + struct drm_device *ddev = node->minor->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct drm_crtc *crtc; > + > + drm_for_each_crtc(crtc, ddev) { > + struct drm_crtc_state *state = crtc->state; > + int index = drm_crtc_index(crtc); > + struct lsdc_display_pipe *pipe = &ldev->disp_pipe[index]; > + struct lsdc_pll *pixpll = &pipe->pixpll; > + const struct lsdc_pixpll_funcs *clkfun = pixpll->funcs; > + unsigned int clkrate = clkfun->get_clock_rate(pixpll); > + unsigned int mode_clock = crtc->mode.crtc_clock; > + > + seq_printf(m, "\n"); > + seq_printf(m, "CRTC%u, %s, %s\n", index, > + state->active ? "active" : "non-active", > + state->enable ? "enabled" : "disabled"); > + seq_printf(m, "hw clock: %u kHz\n", clkrate); > + seq_printf(m, "mode: %u kHz\n", mode_clock); > + > + seq_printf(m, "div_out=%u, loopc=%u, div_ref=%u\n", > + pixpll->core_params.div_out, > + pixpll->core_params.loopc, > + pixpll->core_params.div_ref); > + } > + > + return 0; > +} > + > + > +static int lsdc_mm_show(struct seq_file *m, void *arg) > +{ > + struct drm_info_node *node = (struct drm_info_node *) m->private; > + struct drm_device *ddev = node->minor->dev; > + struct drm_printer p = drm_seq_file_printer(m); > + > + drm_mm_print(&ddev->vma_offset_manager->vm_addr_space_mm, &p); > + return 0; > +} > + > +static struct drm_info_list lsdc_debugfs_list[] = { > + { "clocks", lsdc_show_pxlclock, 0 }, > + { "mm", lsdc_mm_show, 0, NULL }, > +}; > + > +static void lsdc_debugfs_init(struct drm_minor *minor) > +{ > + drm_debugfs_create_files(lsdc_debugfs_list, > + ARRAY_SIZE(lsdc_debugfs_list), > + minor->debugfs_root, > + minor); > +} > +#endif > + > + > +static struct drm_gem_object * > +lsdc_drm_gem_create_object(struct drm_device *ddev, size_t size) > +{ > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct drm_gem_cma_object *obj = kzalloc(sizeof(*obj), GFP_KERNEL); > + > + if (!obj) > + return ERR_PTR(-ENOMEM); > + > + if (ldev->cached_coherent) > + obj->map_noncoherent = true; > + > + return &obj->base; > +} > + > + > +static int lsdc_gem_cma_dumb_create(struct drm_file *file, > + struct drm_device *ddev, > + struct drm_mode_create_dumb *args) > +{ > + struct lsdc_device *ldev = to_lsdc(ddev); > + const struct lsdc_chip_desc *desc = ldev->desc; > + unsigned int bytes_per_pixel = (args->bpp + 7) / 8; > + unsigned int pitch = bytes_per_pixel * args->width; > + > + /* > + * The dc in ls7a1000/ls2k1000/ls2k0500 require the stride be a > + * multiple of 256 bytes, which is for sake of optimize dma data > + * transfer. > + */ > + args->pitch = roundup(pitch, desc->stride_alignment); > + > + return drm_gem_cma_dumb_create_internal(file, ddev, args); > +} > + > + > +DEFINE_DRM_GEM_CMA_FOPS(lsdc_drv_fops); > + > + > +static const struct drm_driver lsdc_drm_driver_cma_stub = { > + .driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC, > + > + .lastclose = drm_fb_helper_lastclose, > + .fops = &lsdc_drv_fops, > + .gem_create_object = lsdc_drm_gem_create_object, > + > + .name = DRIVER_NAME, > + .desc = DRIVER_DESC, > + .date = DRIVER_DATE, > + .major = DRIVER_MAJOR, > + .minor = DRIVER_MINOR, > + .patchlevel = DRIVER_PATCHLEVEL, > + > + DRM_GEM_CMA_DRIVER_OPS_WITH_DUMB_CREATE(lsdc_gem_cma_dumb_create), > + > +#ifdef CONFIG_DEBUG_FS > + .debugfs_init = lsdc_debugfs_init, > +#endif > +}; > + > + > +DEFINE_DRM_GEM_FOPS(lsdc_fops); > + > + > +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER > +static const struct drm_driver lsdc_vram_driver_stub = { > + .driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC, > + .fops = &lsdc_fops, > + > + .name = DRIVER_NAME, > + .desc = DRIVER_DESC, > + .date = DRIVER_DATE, > + .major = DRIVER_MAJOR, > + .minor = DRIVER_MINOR, > + .patchlevel = DRIVER_PATCHLEVEL, > + > + DRM_GEM_VRAM_DRIVER, > +}; > +#endif > + > + > +static int lsdc_modeset_init(struct lsdc_device *ldev, const uint32_t num_crtc) > +{ > + struct drm_device *ddev = &ldev->drm; > + struct lsdc_display_pipe *dispipe; > + struct lsdc_connector *lconn; > + unsigned int i; > + int ret; > + > + /* first find all of connector available */ > + for (i = 0; i < num_crtc; i++) { > + lconn = lsdc_connector_init(ldev, i); > + dispipe = &ldev->disp_pipe[i]; > + > + if (IS_ERR(lconn)) > + return PTR_ERR(lconn); > + else if (lconn == NULL) { > + dispipe->lconn = NULL; > + dispipe->available = false; > + continue; > + } > + > + /* take a record */ > + dispipe->available = true; > + dispipe->lconn = lconn; > + ldev->num_output++; > + } > + > + drm_info(ddev, "number of outputs: %u\n", ldev->num_output); > + > + for (i = 0; i < num_crtc; i++) { > + struct lsdc_display_pipe * const dispipe = &ldev->disp_pipe[i]; > + struct drm_plane * const primary = &dispipe->primary; > + struct drm_plane * const cursor = &dispipe->cursor; > + struct drm_encoder * const encoder = &dispipe->encoder; > + struct drm_crtc * const crtc = &dispipe->crtc; > + struct lsdc_pll * const pixpll = &dispipe->pixpll; > + > + dispipe->index = i; > + > + ret = lsdc_pixpll_init(pixpll, ddev, i); > + if (ret) > + return ret; > + > + ret = lsdc_plane_init(ldev, primary, DRM_PLANE_TYPE_PRIMARY, i); > + if (ret) > + return ret; > + > + ret = lsdc_plane_init(ldev, cursor, DRM_PLANE_TYPE_CURSOR, i); > + if (ret) > + return ret; > + > + /* > + * Initial all of the CRTC available, in this way the crtc > + * index is equal to the hardware crtc index. That is: > + * display pipe 0 = crtc0 + dvo0 + encoder0 > + * display pipe 1 = crtc1 + dvo1 + encoder1 > + */ > + ret = lsdc_crtc_init(ddev, crtc, i, primary, cursor); > + if (ret) > + return ret; > + > + if (dispipe->available) { > + ret = lsdc_encoder_init(encoder, > + &dispipe->lconn->base, > + ddev, > + i, > + ldev->num_output); > + if (ret) > + return ret; > + } > + > + drm_info(ddev, "display pipe %u initialized\n", i); > + } > + > + return 0; > +} > + > + > +static int lsdc_mode_config_init(struct lsdc_device *ldev) > +{ > + const struct lsdc_chip_desc * const descp = ldev->desc; > + struct drm_device *ddev = &ldev->drm; > + int ret; > + > + spin_lock_init(&ldev->reglock); > + > + drm_mode_config_init(ddev); > + > + ddev->mode_config.funcs = &lsdc_mode_config_funcs; > + ddev->mode_config.min_width = 1; > + ddev->mode_config.min_height = 1; > + ddev->mode_config.preferred_depth = 24; > + ddev->mode_config.prefer_shadow = 0; > + > + ddev->mode_config.max_width = 4096; > + ddev->mode_config.max_height = 4096; > + > + ddev->mode_config.cursor_width = descp->hw_cursor_h; > + ddev->mode_config.cursor_height = descp->hw_cursor_h; > + > + if (ldev->vram_base) > + ddev->mode_config.fb_base = ldev->vram_base; > + > + ret = lsdc_modeset_init(ldev, descp->num_of_crtc); > + if (ret) > + goto out_mode_config; > + > + drm_mode_config_reset(ddev); > + > + return 0; > + > +out_mode_config: > + drm_mode_config_cleanup(ddev); > + > + return ret; > +} > + > + > +static void lsdc_mode_config_fini(struct drm_device *ddev) > +{ > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + /* disable output polling */ > + drm_kms_helper_poll_fini(ddev); > + > + drm_dev_unregister(ddev); > + > + devm_free_irq(ddev->dev, ldev->irq, ddev); > + > + /* shutdown all CRTC, for driver unloading */ > + drm_atomic_helper_shutdown(ddev); > + > + drm_mode_config_cleanup(ddev); > +} > + > +/* > + * We need a function to tell different chips apart. > + * Because there are difference between the dc in ls7a1000 and the dc in > + * ls2k1000, ls7a1000 have two gpio emulated i2c built-in, but ls2k1000 > + * don't have such hardware feature. ls2k1000 grab i2c adapter from other > + * module, eithor hardware i2c or external gpio-emulated i2c. > + * > + * Beside, the hardware pixel pll unit is also different. > + */ > +static int lsdc_determine_chip(struct lsdc_device *ldev) > +{ > + struct device_node *np; > + const char *model; > + const char *compat; > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(lsdc_drm_of_match); ++i) { > + compat = lsdc_drm_of_match[i].compatible; > + > + np = of_find_compatible_node(NULL, NULL, compat); > + if (np) { > + > + /* get additional information */ > + of_property_read_string(np, "model", &model); > + > + of_node_put(np); > + > + ldev->desc = lsdc_drm_of_match[i].data; > + > + break; > + } > + } > + > + if (ldev->desc == NULL) { > + drm_err(&ldev->drm, "unknown dc ip core, abort\n"); > + return -ENOENT; > + } > + > + drm_info(&ldev->drm, "%s found, model: %s\n", compat, model); > + > + return 0; > +} > + > + > +static int lsdc_drm_suspend(struct device *dev) > +{ > + struct drm_device *ddev = dev_get_drvdata(dev); > + > + return drm_mode_config_helper_suspend(ddev); > +} > + > + > +static int lsdc_drm_resume(struct device *dev) > +{ > + struct drm_device *ddev = dev_get_drvdata(dev); > + > + return drm_mode_config_helper_resume(ddev); > +} > + > + > +static int lsdc_pm_freeze(struct device *dev) > +{ > + return lsdc_drm_suspend(dev); > +} > + > + > +static int lsdc_pm_thaw(struct device *dev) > +{ > + return lsdc_drm_resume(dev); > +} > + > + > +static int lsdc_pm_suspend(struct device *dev) > +{ > + struct pci_dev *pdev = to_pci_dev(dev); > + int error; > + > + error = lsdc_pm_freeze(dev); > + if (error) > + return error; > + > + pci_save_state(pdev); > + /* Shut down the device */ > + pci_disable_device(pdev); > + pci_set_power_state(pdev, PCI_D3hot); > + > + return 0; > +} > + > + > +static int lsdc_pm_resume(struct device *dev) > +{ > + struct pci_dev *pdev = to_pci_dev(dev); > + > + if (pcim_enable_device(pdev)) > + return -EIO; > + > + pci_set_power_state(pdev, PCI_D0); > + > + pci_restore_state(pdev); > + > + return lsdc_pm_thaw(dev); > +} > + > + > +static const struct dev_pm_ops lsdc_pm_ops = { > + .suspend = lsdc_pm_suspend, > + .resume = lsdc_pm_resume, > + .freeze = lsdc_pm_freeze, > + .thaw = lsdc_pm_thaw, > + .poweroff = lsdc_pm_freeze, > + .restore = lsdc_pm_resume, > +}; > + > + > +static int lsdc_remove_conflicting_framebuffers(const struct drm_driver *drv) > +{ > + /* lsdc is a pci device, but it don't have a dedicate vram bar because > + * of historic reason(The display controller is ported from SoC product > + * of Loongson, Loongson 2H series which date back to 2012) > + * And simplefb node may have been located anywhere in memory. > + */ > + return drm_aperture_remove_conflicting_framebuffers(0, ~0, false, drv); > +} > + > + > +static int lsdc_vram_init(struct lsdc_device *ldev) > +{ > + struct drm_device *ddev = &ldev->drm; > + struct pci_dev *gpu; > + resource_size_t base, size; > + int ret; > + > + /* BAR 2 of LS7A1000's GPU contain VRAM, It's GC1000 */ > + gpu = pci_get_device(PCI_VENDOR_ID_LOONGSON, 0x7a15, NULL); > + base = pci_resource_start(gpu, 2); > + size = pci_resource_len(gpu, 2); > + > + drm_info(ddev, "vram start: 0x%llx, size: %uMB\n", > + base, (unsigned int)(size >> 20)); > + > + if (!request_mem_region(base, size, "lsdc_vram")) { > + drm_err(ddev, "can't reserve VRAM memory region\n"); > + return -ENXIO; > + } > + > + if (ldev->use_vram_helper) { > +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER > + ret = drmm_vram_helper_init(ddev, base, size); > + if (ret) { > + drm_err(ddev, "can't init vram helper\n"); > + return ret; > + } > +#endif > + } else if (ldev->dirty_update) { > + ldev->vram = devm_ioremap_wc(ddev->dev, base, size); > + if (ldev->vram == NULL) > + return -ENOMEM; > + > + drm_info(ddev, "vram virtual addr: 0x%llx\n", (u64)ldev->vram); > + } > + > + ldev->vram_base = base; > + ldev->vram_size = size; > + > + return 0; > +} > + > +/* > + * For the dc in ls7a1000, it is more reliable scanout from the VRAM. > + * scanout from the system memory suffer from some hardware deficiency > + * which cause the screen flickering under RAM pressure. > + */ > +static bool lsdc_should_vram_helper_based(void) > +{ > + static const char * const dc_compat[] = { "pci0014,7a06.0", > + "pci0014,7a06" }; > + bool ret = false; > + struct device_node *np; > + unsigned int i; > + > + for (i = 0; i < ARRAY_SIZE(dc_compat); ++i) { > + np = of_find_compatible_node(NULL, NULL, dc_compat[i]); > + if (!np) > + continue; > + > + ret = of_property_read_bool(np, "use_vram_helper"); > + of_node_put(np); > + break; > + } > + > + if (ret) > + DRM_INFO("using vram base solution dictated by device tree\n"); > + > + return ret; > +} > + > + > +static int lsdc_pci_probe(struct pci_dev *pdev, > + const struct pci_device_id * const ent) > +{ > + const struct drm_driver *driver = &lsdc_drm_driver_cma_stub; > + struct lsdc_device *ldev; > + struct drm_device *ddev; > + int ret; > + > + lsdc_remove_conflicting_framebuffers(driver); > + > + ret = pci_set_dma_mask(pdev, DMA_BIT_MASK(32)); > + if (ret) { > + dev_err(&pdev->dev, "Set DMA Mask failed\n"); > + return ret; > + } > + > + ret = pcim_enable_device(pdev); > + if (ret) { > + dev_err(&pdev->dev, "Enable pci devive failed\n"); > + return ret; > + } > + > + pci_set_master(pdev); > + > + /* Get the optional framebuffer memory resource */ > + ret = of_reserved_mem_device_init(&pdev->dev); > + if (ret && ret != -ENODEV) > + return ret; > + > +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER > + if (lsdc_use_vram_helper && lsdc_should_vram_helper_based()) { > + driver = &lsdc_vram_driver_stub; > + lsdc_use_vram_helper = 1; > + DRM_INFO("using vram helper based solution\n"); > + } > +#endif > + > + ldev = devm_drm_dev_alloc(&pdev->dev, > + driver, > + struct lsdc_device, > + drm); > + if (IS_ERR(ldev)) > + return PTR_ERR(ldev); > + > + ddev = &ldev->drm; > + > + pci_set_drvdata(pdev, ddev); > + > + if (lsdc_use_vram_helper > 0) > + ldev->use_vram_helper = true; > + else { > + if (lsdc_cached_coherent > 0) { > + ldev->cached_coherent = true; > + drm_info(ddev, "cache coherency is maintained by hardware\n"); > + } > + > + if (lsdc_dirty_update > 0) { > + ldev->dirty_update = true; > + drm_info(ddev, "dirty update enabled\n"); > + } > + } > + > + if (lsdc_verbose > 0) > + ldev->verbose = true; > + > + ret = lsdc_determine_chip(ldev); > + if (ret) > + return ret; > + > + /* BAR 0 contains registers */ > + ldev->reg_base = devm_ioremap_resource(&pdev->dev, &pdev->resource[0]); > + if (IS_ERR(ldev->reg_base)) > + return PTR_ERR(ldev->reg_base); > + > + /* LS2K1000/LS2K0500 is SoC, don't have a VRAM */ > + if ((ldev->desc->chip == LSDC_CHIP_7A1000) && > + (ldev->use_vram_helper || ldev->dirty_update)) > + lsdc_vram_init(ldev); > + > + ret = lsdc_mode_config_init(ldev); > + if (ret) > + goto err_out; > + > + > + ldev->irq = pdev->irq; > + > + dev_info(&pdev->dev, "irq = %d\n", ldev->irq); > + > + ret = devm_request_threaded_irq(&pdev->dev, > + pdev->irq, > + lsdc_irq_handler_cb, > + lsdc_irq_thread_cb, > + IRQF_ONESHOT, > + dev_name(&pdev->dev), > + ddev); > + > + if (ret) { > + dev_err(&pdev->dev, "Failed to register lsdc interrupt\n"); > + goto err_out; > + } > + > + ret = drm_vblank_init(ddev, LSDC_MAX_CRTC); > + if (ret) { > + dev_err(&pdev->dev, > + "Fatal error during vblank init: %d\n", ret); > + goto err_out; > + } > + > + /* Initialize and enable output polling */ > + drm_kms_helper_poll_init(ddev); > + > + ret = drm_dev_register(ddev, 0); > + if (ret) > + goto err_out; > + > + drm_fbdev_generic_setup(ddev, 32); > + > + return 0; > + > +err_out: > + drm_dev_put(ddev); > + > + return ret; > +} > + > + > +static void lsdc_pci_remove(struct pci_dev *pdev) > +{ > + struct drm_device *ddev = pci_get_drvdata(pdev); > + > + lsdc_mode_config_fini(ddev); > + > + drm_dev_put(ddev); > + > + pci_clear_master(pdev); > + > + pci_release_regions(pdev); > +} > + > + > +static const struct pci_device_id lsdc_pciid_list[] = { > + {PCI_VENDOR_ID_LOONGSON, 0x7a06, PCI_ANY_ID, PCI_ANY_ID, 0, 0, 0}, > + {0, 0, 0, 0, 0, 0, 0} > +}; > + > + > +static struct pci_driver lsdc_pci_driver = { > + .name = DRIVER_NAME, > + .id_table = lsdc_pciid_list, > + .probe = lsdc_pci_probe, > + .remove = lsdc_pci_remove, > + .driver.pm = &lsdc_pm_ops, > +}; > + > + > +static int __init lsdc_drm_init(void) > +{ > + struct pci_dev *pdev = NULL; > + > + if (drm_firmware_drivers_only()) > + return -EINVAL; > + > + if (lsdc_modeset == 0) > + return -ENOENT; > + > + while ((pdev = pci_get_class(PCI_CLASS_DISPLAY_VGA << 8, pdev))) { > + /* > + * Multiple video card workaround > + * > + * This integrated video driver will always be selected as > + * default boot device by vgaarb system. > + */ > + if (pdev->vendor != PCI_VENDOR_ID_LOONGSON) { > + DRM_INFO("Discrete graphic card detected, abort\n"); > + return 0; > + } > + } > + > + return pci_register_driver(&lsdc_pci_driver); > +} > +module_init(lsdc_drm_init); > + > +static void __exit lsdc_drm_exit(void) > +{ > + pci_unregister_driver(&lsdc_pci_driver); > +} > +module_exit(lsdc_drm_exit); > + > + > +MODULE_DEVICE_TABLE(pci, lsdc_pciid_list); > +MODULE_AUTHOR(DRIVER_AUTHOR); > +MODULE_DESCRIPTION(DRIVER_DESC); > +MODULE_LICENSE("GPL v2"); > diff --git a/drivers/gpu/drm/lsdc/lsdc_drv.h b/drivers/gpu/drm/lsdc/lsdc_drv.h > new file mode 100644 > index 000000000000..89cf15248c3a > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_drv.h > @@ -0,0 +1,216 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Copyright 2020 Loongson Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the > + * "Software"), to deal in the Software without restriction, including > + * without limitation the rights to use, copy, modify, merge, publish, > + * distribute, sub license, and/or sell copies of the Software, and to > + * permit persons to whom the Software is furnished to do so, subject to > + * the following conditions: > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL > + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, > + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR > + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE > + * USE OR OTHER DEALINGS IN THE SOFTWARE. > + * > + * The above copyright notice and this permission notice (including the > + * next paragraph) shall be included in all copies or substantial portions > + * of the Software. > + */ > + > +/* > + * Authors: > + * Sui Jingfeng <suijingfeng@xxxxxxxxxxx> > + */ > + > +#ifndef __LSDC_DRV_H__ > +#define __LSDC_DRV_H__ > + > +#include <drm/drm_print.h> > +#include <drm/drm_device.h> > +#include <drm/drm_crtc.h> > +#include <drm/drm_plane.h> > +#include <drm/drm_encoder.h> > + > +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER > +#include <drm/drm_gem_vram_helper.h> > +#endif > + > +#include "lsdc_regs.h" > +#include "lsdc_pll.h" > + > +#define LSDC_MAX_CRTC 2 > + > +/* There is only a 1:1 mapping of encoders and connectors for lsdc */ > +/* > + * +-------------------+ _________ > + * | | | | > + * | CRTC0 --> DVO0 ---------> Encoder0 --> Connector0 ---> | Monotor | > + * | | ^ ^ |_________| > + * | | | | > + * | <----- i2c0 ----------------+ > + * | LSDC IP CORE | > + * | <----- i2c1 ----------------+ > + * | | | | _________ > + * | | | | | | > + * | CRTC1 --> DVO1 ---------> Encoder1 --> Connector1 ---> | Panel | > + * | | |_________| > + * +-------------------+ > + */ > + > +enum loongson_dc_family { > + LSDC_CHIP_UNKNOWN = 0, > + LSDC_CHIP_2K1000 = 1, /* 2-Core SoC, 64-bit */ > + LSDC_CHIP_7A1000 = 2, /* North bridge of LS3A3000/LS3A4000/LS3A5000 */ > + LSDC_CHIP_2K0500 = 3, /* Reduced version of LS2K1000, single core */ > + LSDC_CHIP_7A2000 = 4, /* Enhancement version of 7A1000 */ > + LSDC_CHIP_LAST, > +}; > + > +enum lsdc_pixel_format { > + LSDC_PF_NONE = 0, > + LSDC_PF_ARGB4444 = 1, /* ARGB A:4 bits R/G/B: 4 bits each [16 bits] */ > + LSDC_PF_ARGB1555 = 2, /* ARGB A:1 bit RGB:15 bits [16 bits] */ > + LSDC_PF_RGB565 = 3, /* RGB [16 bits] */ > + LSDC_PF_XRGB8888 = 4, /* XRGB [32 bits] */ > +}; > + > +struct lsdc_chip_desc { > + enum loongson_dc_family chip; > + uint32_t num_of_crtc; > + > + uint32_t max_pixel_clk; > + > + uint32_t max_width; > + uint32_t max_height; > + > + uint32_t num_of_hw_cursor; > + uint32_t hw_cursor_w; > + uint32_t hw_cursor_h; > + bool have_builtin_i2c; > + uint32_t stride_alignment; > +}; > + > + > +/** > + * struct lsdc_display_pipe - simple display pipeline > + * @crtc: CRTC control structure > + * @plane: Plane control structure > + * @encoder: Encoder control structure > + * @pixpll: Pll control structure > + * @connector: point to connector control structure > + * > + * display pipeline with plane, crtc, encoder, pll collapsed into one > + * entity. It should be initialized by calling drm_simple_display_pipe_init(). > + */ > +struct lsdc_display_pipe { > + struct drm_crtc crtc; > + struct drm_plane primary; > + struct drm_plane cursor; > + struct drm_encoder encoder; > + struct lsdc_pll pixpll; > + struct lsdc_connector *lconn; > + > + int index; > + bool available; > +}; > + > + > +struct lsdc_crtc_state { > + struct drm_crtc_state base; > + struct lsdc_pll_core_values pparams; > + unsigned int pix_fmt; > +}; > + > + > +struct lsdc_device { > + struct drm_device drm; > + > + void __iomem *reg_base; > + void __iomem *vram; > + resource_size_t vram_base; > + resource_size_t vram_size; > + > + struct lsdc_display_pipe disp_pipe[LSDC_MAX_CRTC]; > + > + unsigned int num_output; > + > + /* platform specific data */ > + const struct lsdc_chip_desc *desc; > + > + /* @reglock: protects concurrent register access */ > + spinlock_t reglock; > + > + /* > + * @dirty_update: true if manual dirty update is wantted > + */ > + bool dirty_update; > + /* > + * @cached_coherent: true if the host platform is hardware maintained > + * cached coherent. > + */ > + bool cached_coherent; > + /* > + * @use_vram_helper: using vram helper instead of cma helper base > + * solution. As ls7a1000 has a dediacted video ram, the dc scanout > + * from the vram is more reliable. > + */ > + bool use_vram_helper; > + > + /* > + * @verbose: set it to true if print useful information is wantted > + */ > + bool verbose; > + > + int irq; > + u32 irq_status; > +}; > + > +#define to_lsdc(x) container_of(x, struct lsdc_device, drm) > + > +static inline struct lsdc_crtc_state * > +to_lsdc_crtc_state(struct drm_crtc_state *base) > +{ > + return container_of(base, struct lsdc_crtc_state, base); > +} > + > +static inline u32 lsdc_reg_read32(struct lsdc_device * const ldev, u32 offset) > +{ > + u32 val; > + unsigned long flags; > + > + spin_lock_irqsave(&ldev->reglock, flags); > + val = readl(ldev->reg_base + offset); > + spin_unlock_irqrestore(&ldev->reglock, flags); > + > + return val; > +} > + > +static inline void lsdc_reg_write32(struct lsdc_device * const ldev, u32 offset, u32 val) > +{ > + unsigned long flags; > + > + spin_lock_irqsave(&ldev->reglock, flags); > + writel(val, ldev->reg_base + offset); > + spin_unlock_irqrestore(&ldev->reglock, flags); > +} > + > +int lsdc_crtc_init(struct drm_device *ddev, struct drm_crtc *crtc, > + unsigned int index, struct drm_plane *primary, > + struct drm_plane *cursor); > + > +int lsdc_plane_init(struct lsdc_device *ldev, struct drm_plane *plane, > + enum drm_plane_type type, unsigned int index); > + > +int lsdc_encoder_init(struct drm_encoder * const encoder, > + struct drm_connector *connector, > + struct drm_device *ddev, > + unsigned int index, > + unsigned int total); > + > +#endif > diff --git a/drivers/gpu/drm/lsdc/lsdc_encoder.c b/drivers/gpu/drm/lsdc/lsdc_encoder.c > new file mode 100644 > index 000000000000..0cdd95f5bc37 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_encoder.c > @@ -0,0 +1,79 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright (C) 2020 Loongson Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the > + * "Software"), to deal in the Software without restriction, including > + * without limitation the rights to use, copy, modify, merge, publish, > + * distribute, sub license, and/or sell copies of the Software, and to > + * permit persons to whom the Software is furnished to do so, subject to > + * the following conditions: > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL > + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, > + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR > + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE > + * USE OR OTHER DEALINGS IN THE SOFTWARE. > + * > + * The above copyright notice and this permission notice (including the > + * next paragraph) shall be included in all copies or substantial portions > + * of the Software. > + */ > + > +/* > + * Authors: > + * Sui Jingfeng <suijingfeng@xxxxxxxxxxx> > + */ > +#include <drm/drm_print.h> > +#include <drm/drm_crtc_helper.h> > + > +#include "lsdc_drv.h" > + > +static const struct drm_encoder_funcs lsdc_encoder_funcs = { > + .destroy = drm_encoder_cleanup, > +}; > + > + > +int lsdc_encoder_init(struct drm_encoder * const encoder, > + struct drm_connector *connector, > + struct drm_device *ddev, > + unsigned int index, > + unsigned int total) > +{ > + int ret; > + int type; > + > + encoder->possible_crtcs = BIT(index); > + > + if (total == 2) > + encoder->possible_clones = BIT(1) | BIT(0); > + else if (total < 2) > + encoder->possible_clones = 0; > + > + if (connector->connector_type == DRM_MODE_CONNECTOR_VGA) > + type = DRM_MODE_ENCODER_DAC; > + else if ((connector->connector_type == DRM_MODE_CONNECTOR_HDMIA) || > + (connector->connector_type == DRM_MODE_CONNECTOR_HDMIB) || > + (connector->connector_type == DRM_MODE_CONNECTOR_DVID)) > + type = DRM_MODE_ENCODER_TMDS; > + else if (connector->connector_type == DRM_MODE_CONNECTOR_DPI) > + type = DRM_MODE_ENCODER_DPI; > + else if (connector->connector_type == DRM_MODE_CONNECTOR_VIRTUAL) > + type = DRM_MODE_ENCODER_VIRTUAL; > + else > + type = DRM_MODE_ENCODER_NONE; > + > + ret = drm_encoder_init(ddev, > + encoder, > + &lsdc_encoder_funcs, > + type, > + "encoder%d", > + index); > + if (ret) > + return ret; > + > + return drm_connector_attach_encoder(connector, encoder); > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.c b/drivers/gpu/drm/lsdc/lsdc_i2c.c > new file mode 100644 > index 000000000000..895e94ae6827 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.c > @@ -0,0 +1,220 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright 2020 Loongson Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the > + * "Software"), to deal in the Software without restriction, including > + * without limitation the rights to use, copy, modify, merge, publish, > + * distribute, sub license, and/or sell copies of the Software, and to > + * permit persons to whom the Software is furnished to do so, subject to > + * the following conditions: > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL > + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, > + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR > + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE > + * USE OR OTHER DEALINGS IN THE SOFTWARE. > + * > + * The above copyright notice and this permission notice (including the > + * next paragraph) shall be included in all copies or substantial portions > + * of the Software. > + */ > + > +/* > + * Authors: > + * Sui Jingfeng <suijingfeng@xxxxxxxxxxx> > + */ > + > +#include <linux/string.h> > +#include <linux/i2c.h> > + > +#include "lsdc_drv.h" > +#include "lsdc_regs.h" > +#include "lsdc_i2c.h" > + > +/* > + * set the state of a gpio pin indicated by mask > + * @mask: gpio pin mask > + */ > +static void ls7a_gpio_i2c_set(struct lsdc_i2c * const i2c, int mask, int state) > +{ > + struct lsdc_device *ldev = to_lsdc(i2c->ddev); > + u8 val; > + unsigned long flags; > + > + spin_lock_irqsave(&ldev->reglock, flags); > + > + if (state) { > + val = readb(i2c->dir_reg); > + val |= mask; > + writeb(val, i2c->dir_reg); > + } else { > + val = readb(i2c->dir_reg); > + val &= ~mask; > + writeb(val, i2c->dir_reg); > + > + val = readb(i2c->dat_reg); > + if (state) > + val |= mask; > + else > + val &= ~mask; > + writeb(val, i2c->dat_reg); > + } > + > + spin_unlock_irqrestore(&ldev->reglock, flags); > +} > + > +/* > + * read value back from gpio pin > + * @mask: gpio pin mask > + */ > +static int ls7a_gpio_i2c_get(struct lsdc_i2c * const i2c, int mask) > +{ > + struct lsdc_device *ldev = to_lsdc(i2c->ddev); > + u8 val; > + unsigned long flags; > + > + spin_lock_irqsave(&ldev->reglock, flags); > + > + /* first set this pin as input */ > + val = readb(i2c->dir_reg); > + val |= mask; > + writeb(val, i2c->dir_reg); > + > + /* then get level state from this pin */ > + val = readb(i2c->dat_reg); > + > + spin_unlock_irqrestore(&ldev->reglock, flags); > + > + return (val & mask) ? 1 : 0; > +} > + > + > +/* set the state on the i2c->sda pin */ > +static void ls7a_i2c_set_sda(void *i2c, int state) > +{ > + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; > + > + return ls7a_gpio_i2c_set(li2c, li2c->sda, state); > +} > + > +/* set the state on the i2c->scl pin */ > +static void ls7a_i2c_set_scl(void *i2c, int state) > +{ > + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; > + > + return ls7a_gpio_i2c_set(li2c, li2c->scl, state); > +} > + > +/* read the value from the i2c->sda pin */ > +static int ls7a_i2c_get_sda(void *i2c) > +{ > + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; > + > + return ls7a_gpio_i2c_get(li2c, li2c->sda); > +} > + > +/* read the value from the i2c->scl pin */ > +static int ls7a_i2c_get_scl(void *i2c) > +{ > + struct lsdc_i2c * const li2c = (struct lsdc_i2c *)i2c; > + > + return ls7a_gpio_i2c_get(li2c, li2c->scl); > +} > + > +/* > + * Get i2c id from connector id > + * > + * TODO: get it from dtb > + */ > +static int lsdc_get_i2c_id(struct drm_device *ddev, unsigned int index) > +{ > + return index; > +} > + > +/* > + * mainly for dc in ls7a1000 which have builtin gpio emulated i2c > + * > + * @index : output channel index, 0 for DVO0, 1 for DVO1 > + */ > +struct i2c_adapter *lsdc_create_i2c_chan(struct drm_device *ddev, > + unsigned int index) > +{ > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct i2c_adapter *adapter; > + struct lsdc_i2c *li2c; > + int ret; > + > + li2c = devm_kzalloc(ddev->dev, sizeof(*li2c), GFP_KERNEL); > + if (li2c == NULL) > + return ERR_PTR(-ENOMEM); > + > + li2c->ddev = ddev; > + > + if (index == 0) { > + li2c->sda = 0x01; > + li2c->scl = 0x02; > + } else if (index == 1) { > + li2c->sda = 0x04; > + li2c->scl = 0x08; > + } > + > + li2c->dir_reg = ldev->reg_base + LS7A_DC_GPIO_DIR_REG; > + li2c->dat_reg = ldev->reg_base + LS7A_DC_GPIO_DAT_REG; > + > + li2c->bit.setsda = ls7a_i2c_set_sda; > + li2c->bit.setscl = ls7a_i2c_set_scl; > + li2c->bit.getsda = ls7a_i2c_get_sda; > + li2c->bit.getscl = ls7a_i2c_get_scl; > + li2c->bit.udelay = 5; > + li2c->bit.timeout = usecs_to_jiffies(2200); /* from VESA */ > + li2c->bit.data = li2c; > + > + adapter = &li2c->adapter; > + > + adapter->algo_data = &li2c->bit; > + adapter->owner = THIS_MODULE; > + adapter->class = I2C_CLASS_DDC; > + adapter->dev.parent = ddev->dev; > + adapter->nr = -1; > + > + snprintf(adapter->name, sizeof(adapter->name), > + "%s-%d", "lsdc_gpio_i2c", index); > + > + i2c_set_adapdata(adapter, li2c); > + > + ret = i2c_bit_add_numbered_bus(adapter); > + if (ret) { > + devm_kfree(ddev->dev, li2c); > + return ERR_PTR(ret); > + } > + > + return adapter; > +} > + > + > +/* > + * Mainly for dc in ls2k1000, ls2k0500 SoC which should get a i2c adapter > + * from i2c susystem. > + * > + * @index : output channel index, 0 for DVO0, 1 for DVO1 > + */ > +struct i2c_adapter *lsdc_get_i2c_adapter(struct drm_device *ddev, > + unsigned int index) > +{ > + unsigned int i2c_id; > + > + /* find mapping between i2c id and connector id */ > + i2c_id = lsdc_get_i2c_id(ddev, index); > + > + return i2c_get_adapter(i2c_id); > +} > + > + > +void lsdc_destroy_i2c(struct drm_device *ddev, struct i2c_adapter *adapter) > +{ > + i2c_put_adapter(adapter); > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_i2c.h b/drivers/gpu/drm/lsdc/lsdc_i2c.h > new file mode 100644 > index 000000000000..f4560db3694a > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_i2c.h > @@ -0,0 +1,61 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Copyright 2020 Loongson Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the > + * "Software"), to deal in the Software without restriction, including > + * without limitation the rights to use, copy, modify, merge, publish, > + * distribute, sub license, and/or sell copies of the Software, and to > + * permit persons to whom the Software is furnished to do so, subject to > + * the following conditions: > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL > + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, > + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR > + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE > + * USE OR OTHER DEALINGS IN THE SOFTWARE. > + * > + * The above copyright notice and this permission notice (including the > + * next paragraph) shall be included in all copies or substantial portions > + * of the Software. > + */ > + > +/* > + * Authors: > + * Sui Jingfeng <suijingfeng@xxxxxxxxxxx> > + */ > + > + > +#ifndef __LSDC_I2C__ > +#define __LSDC_I2C__ > + > +#include <linux/i2c.h> > +#include <linux/i2c-algo-bit.h> > + > + > +struct lsdc_i2c { > + struct drm_device *ddev; > + struct i2c_adapter adapter; > + struct i2c_algo_bit_data bit; > + /* pin bit mask */ > + u8 sda; > + u8 scl; > + > + void __iomem *dir_reg; > + void __iomem *dat_reg; > +}; > + > + > +void lsdc_destroy_i2c(struct drm_device *ddev, struct i2c_adapter *i2c); > + > +struct i2c_adapter *lsdc_create_i2c_chan(struct drm_device *ddev, > + unsigned int con_id); > + > +struct i2c_adapter *lsdc_get_i2c_adapter(struct drm_device *ddev, > + unsigned int con_id); > + > + > +#endif > diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.c b/drivers/gpu/drm/lsdc/lsdc_irq.c > new file mode 100644 > index 000000000000..7620de54aae4 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_irq.c > @@ -0,0 +1,77 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright 2020 Loongson Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the > + * "Software"), to deal in the Software without restriction, including > + * without limitation the rights to use, copy, modify, merge, publish, > + * distribute, sub license, and/or sell copies of the Software, and to > + * permit persons to whom the Software is furnished to do so, subject to > + * the following conditions: > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL > + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, > + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR > + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE > + * USE OR OTHER DEALINGS IN THE SOFTWARE. > + * > + * The above copyright notice and this permission notice (including the > + * next paragraph) shall be included in all copies or substantial portions > + * of the Software. > + */ > + > +/* > + * Authors: > + * Sui Jingfeng <suijingfeng@xxxxxxxxxxx> > + */ > +#include <drm/drm_vblank.h> > + > +#include "lsdc_drv.h" > +#include "lsdc_regs.h" > +#include "lsdc_irq.h" > + > +/* Function to be called in a threaded interrupt context. */ > +irqreturn_t lsdc_irq_thread_cb(int irq, void *arg) > +{ > + struct drm_device *ddev = arg; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct drm_crtc *crtc; > + > + /* trigger the vblank event */ > + if (ldev->irq_status & INT_CRTC0_VS) { > + crtc = drm_crtc_from_index(ddev, 0); > + drm_crtc_handle_vblank(crtc); > + } > + > + if (ldev->irq_status & INT_CRTC1_VS) { > + crtc = drm_crtc_from_index(ddev, 1); > + drm_crtc_handle_vblank(crtc); > + } > + > + lsdc_reg_write32(ldev, LSDC_INT_REG, INT_CRTC0_VS_EN | INT_CRTC1_VS_EN); > + > + return IRQ_HANDLED; > +} > + > + > +/* Function to be called when the IRQ occurs */ > +irqreturn_t lsdc_irq_handler_cb(int irq, void *arg) > +{ > + struct drm_device *ddev = arg; > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + /* Read & Clear the interrupt status */ > + ldev->irq_status = lsdc_reg_read32(ldev, LSDC_INT_REG); > + if ((ldev->irq_status & INT_STATUS_MASK) == 0) { > + drm_warn(ddev, "no interrupt occurs\n"); > + return IRQ_NONE; > + } > + > + /* clear all interrupt */ > + lsdc_reg_write32(ldev, LSDC_INT_REG, ldev->irq_status); > + > + return IRQ_WAKE_THREAD; > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_irq.h b/drivers/gpu/drm/lsdc/lsdc_irq.h > new file mode 100644 > index 000000000000..ac187538d746 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_irq.h > @@ -0,0 +1,37 @@ > +/* SPDX-License-Identifier: GPL-2.0+ */ > +/* > + * Copyright 2020 Loongson Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the > + * "Software"), to deal in the Software without restriction, including > + * without limitation the rights to use, copy, modify, merge, publish, > + * distribute, sub license, and/or sell copies of the Software, and to > + * permit persons to whom the Software is furnished to do so, subject to > + * the following conditions: > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL > + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, > + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR > + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE > + * USE OR OTHER DEALINGS IN THE SOFTWARE. > + * > + * The above copyright notice and this permission notice (including the > + * next paragraph) shall be included in all copies or substantial portions > + * of the Software. > + */ > + > +/* > + * Authors: > + * Sui Jingfeng <suijingfeng@xxxxxxxxxxx> > + */ > + > +#ifndef __LSDC_IRQ_H__ > +#define __LSDC_IRQ_H__ > + > +irqreturn_t lsdc_irq_thread_cb(int irq, void *arg); > +irqreturn_t lsdc_irq_handler_cb(int irq, void *arg); > + > +#endif > diff --git a/drivers/gpu/drm/lsdc/lsdc_plane.c b/drivers/gpu/drm/lsdc/lsdc_plane.c > new file mode 100644 > index 000000000000..62801e100a13 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_plane.c > @@ -0,0 +1,681 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright 2020 Loongson Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the > + * "Software"), to deal in the Software without restriction, including > + * without limitation the rights to use, copy, modify, merge, publish, > + * distribute, sub license, and/or sell copies of the Software, and to > + * permit persons to whom the Software is furnished to do so, subject to > + * the following conditions: > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL > + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, > + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR > + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE > + * USE OR OTHER DEALINGS IN THE SOFTWARE. > + * > + * The above copyright notice and this permission notice (including the > + * next paragraph) shall be included in all copies or substantial portions > + * of the Software. > + */ > + > +/* > + * Authors: > + * Sui Jingfeng <suijingfeng@xxxxxxxxxxx> > + */ > + > +#include <drm/drm_print.h> > +#include <drm/drm_device.h> > +#include <drm/drm_crtc.h> > +#include <drm/drm_plane.h> > +#include <drm/drm_atomic.h> > +#include <drm/drm_vblank.h> > +#include <drm/drm_drv.h> > + > +#include <drm/drm_format_helper.h> > +#include <drm/drm_plane_helper.h> > +#include <drm/drm_fb_cma_helper.h> > +#include <drm/drm_gem_cma_helper.h> > +#include <drm/drm_atomic_helper.h> > +#include <drm/drm_gem_framebuffer_helper.h> > +#include <drm/drm_gem_atomic_helper.h> > +#include <drm/drm_damage_helper.h> > + > + > +#include "lsdc_drv.h" > +#include "lsdc_regs.h" > +#include "lsdc_pll.h" > + > + > +static const uint32_t lsdc_primary_formats[] = { > + DRM_FORMAT_RGB565, > + DRM_FORMAT_XRGB8888, > + DRM_FORMAT_ARGB8888, > +}; > + > +static const uint32_t lsdc_cursor_formats[] = { > + DRM_FORMAT_ARGB8888, > +}; > + > +static const u64 lsdc_fb_format_modifiers[] = { > + DRM_FORMAT_MOD_LINEAR, > + DRM_FORMAT_MOD_INVALID > +}; > + > + > +static u32 lsdc_pixfmt_to_drm_pixfmt(enum lsdc_pixel_format pf) > +{ > + switch (pf) { > + case LSDC_PF_XRGB8888: > + return DRM_FORMAT_XRGB8888; > + case LSDC_PF_RGB565: > + return DRM_FORMAT_RGB565; > + case LSDC_PF_ARGB1555: > + return DRM_FORMAT_ARGB1555; > + case LSDC_PF_ARGB4444: > + return DRM_FORMAT_ARGB4444; > + case LSDC_PF_NONE: > + default: > + return 0; > + } > +} > + > +static const char *lsdc_pixfmt_to_string(u32 reg) > +{ > + switch (reg & CFG_PIX_FMT_MASK) { > + case LSDC_PF_XRGB8888: > + return "XRGB8888"; > + case LSDC_PF_RGB565: > + return "RGB565"; > + case LSDC_PF_ARGB1555: > + return "ARGB1555"; > + case LSDC_PF_ARGB4444: > + return "ARGB4444"; > + case LSDC_PF_NONE: > + return "NONE"; > + default: > + return "unknown"; > + } > +} > + > + > + > +static void lsdc_update_fb_format(struct lsdc_device *ldev, > + struct drm_crtc *crtc, > + const struct drm_format_info *fmt_info) > +{ > + unsigned int index = drm_crtc_index(crtc); > + u32 val = 0; > + u32 fmt; > + > + switch (fmt_info->format) { > + case DRM_FORMAT_RGB565: > + fmt = LSDC_PF_RGB565; > + break; > + case DRM_FORMAT_XRGB8888: > + fmt = LSDC_PF_XRGB8888; > + break; > + case DRM_FORMAT_ARGB8888: > + fmt = LSDC_PF_XRGB8888; > + break; > + default: > + fmt = LSDC_PF_XRGB8888; > + break; > + } > + > + if (ldev->verbose) > + drm_info(&ldev->drm, "fmt wanted is %s\n", > + lsdc_pixfmt_to_string(fmt)); > + > + if (index == 0) { > + val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG); > + val = (val & ~CFG_PIX_FMT_MASK) | fmt; > + lsdc_reg_write32(ldev, LSDC_CRTC0_CFG_REG, val); > + val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG); > + } else if (index == 1) { > + val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG); > + val = (val & ~CFG_PIX_FMT_MASK) | fmt; > + lsdc_reg_write32(ldev, LSDC_CRTC1_CFG_REG, val); > + val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG); > + } > + > + if (ldev->verbose) > + drm_info(&ldev->drm, "after update fb%d format is %s\n", > + index, lsdc_pixfmt_to_string(val)); > +} > + > + > +static u32 lsdc_primary_get_default_format(struct drm_crtc *crtc) > +{ > + struct lsdc_device *ldev = to_lsdc(crtc->dev); > + unsigned int index = drm_crtc_index(crtc); > + u32 val; > + > + if (index == 0) > + val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG); > + else if (index == 1) > + val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG); > + > + return val & CFG_PIX_FMT_MASK; > +} > + > + > +static void lsdc_update_fb_start_addr(struct lsdc_device *ldev, > + struct drm_crtc *crtc, > + u64 paddr) > +{ > + unsigned int index = drm_crtc_index(crtc); > + u32 addr_reg; > + u32 cfg_reg; > + u32 val; > + > + /* > + * Find which framebuffer address register should update. > + * if FB_ADDR0_REG is in using, we write the addr to FB_ADDR1_REG, > + * if FB_ADDR1_REG is in using, we write the addr to FB_ADDR0_REG > + */ > + if (index == 0) { > + /* CRTC0 */ > + val = lsdc_reg_read32(ldev, LSDC_CRTC0_CFG_REG); > + > + cfg_reg = LSDC_CRTC0_CFG_REG; > + > + if (val & CFG_FB_IDX_BIT) { > + addr_reg = LSDC_CRTC0_FB_ADDR0_REG; > + drm_dbg_kms(&ldev->drm, "CRTC0 FB0 will be use\n"); > + } else { > + addr_reg = LSDC_CRTC0_FB_ADDR1_REG; > + drm_dbg_kms(&ldev->drm, "CRTC0 FBq will be use\n"); > + } > + } else if (index == 1) { > + /* CRTC1 */ > + val = lsdc_reg_read32(ldev, LSDC_CRTC1_CFG_REG); > + > + cfg_reg = LSDC_CRTC1_CFG_REG; > + > + if (val & CFG_FB_IDX_BIT) { > + addr_reg = LSDC_CRTC1_FB_ADDR0_REG; > + drm_dbg_kms(&ldev->drm, "CRTC1 FB0 will be use\n"); > + } else { > + addr_reg = LSDC_CRTC1_FB_ADDR1_REG; > + drm_dbg_kms(&ldev->drm, "CRTC1 FBq will be use\n"); > + } > + } > + > + lsdc_reg_write32(ldev, addr_reg, paddr); > + > + /* > + * Then, we triger the fb switch, the switch of the framebuffer > + * to be scanout will complete at the next vblank. > + */ > + lsdc_reg_write32(ldev, cfg_reg, val | CFG_PAGE_FLIP_BIT); > + > + drm_dbg_kms(&ldev->drm, "crtc%u scantout from 0x%llx\n", index, paddr); > +} > + > + > +static void lsdc_handle_damage(struct lsdc_device *ldev, > + struct drm_framebuffer *fb, > + struct drm_rect *clip, > + void *src) > +{ > + unsigned int offset; > + void __iomem *dst; > + > + offset = drm_fb_clip_offset(fb->pitches[0], fb->format, clip); > + dst = ldev->vram + offset; > + drm_fb_memcpy_toio(dst, fb->pitches[0], src, fb, clip); > +} > + > + > +static unsigned int lsdc_get_fb_offset(struct drm_framebuffer *fb, > + struct drm_plane_state *state, > + unsigned int plane) > +{ > + unsigned int offset; > + > + offset = fb->offsets[plane]; > + offset += fb->format->cpp[plane] * (state->src_x >> 16); > + offset += fb->pitches[plane] * (state->src_y >> 16); > + > + return offset; > +} > + > +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER > +static s64 lsdc_get_vram_bo_offset(struct drm_framebuffer *fb) > +{ > + struct drm_gem_vram_object *gbo; > + s64 gpu_addr; > + > + gbo = drm_gem_vram_of_gem(fb->obj[0]); > + gpu_addr = drm_gem_vram_offset(gbo); > + > + return gpu_addr; > +} > +#endif > + > + > +static int lsdc_primary_plane_atomic_check(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct drm_device *ddev = plane->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); > + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); > + struct drm_framebuffer *new_fb = new_plane_state->fb; > + struct drm_framebuffer *old_fb = old_plane_state->fb; > + struct drm_crtc *crtc = new_plane_state->crtc; > + u32 new_format = new_fb->format->format; > + struct drm_crtc_state *new_crtc_state; > + struct lsdc_crtc_state *priv_crtc_state; > + int ret; > + > + if (!crtc) > + return 0; > + > + new_crtc_state = drm_atomic_get_new_crtc_state(state, crtc); > + if (WARN_ON(!new_crtc_state)) > + return -EINVAL; > + > + priv_crtc_state = to_lsdc_crtc_state(new_crtc_state); > + > + ret = drm_atomic_helper_check_plane_state(new_plane_state, > + new_crtc_state, > + DRM_PLANE_HELPER_NO_SCALING, > + DRM_PLANE_HELPER_NO_SCALING, > + false, > + true); > + if (ret) > + return ret; > + > + /* > + * Require full modeset if enabling or disabling a plane, > + * or changing its position, size, depth or format. > + */ > + if ((!new_fb || !old_fb || > + old_plane_state->crtc_x != new_plane_state->crtc_x || > + old_plane_state->crtc_y != new_plane_state->crtc_y || > + old_plane_state->crtc_w != new_plane_state->crtc_w || > + old_plane_state->crtc_h != new_plane_state->crtc_h || > + old_fb->format->format != new_format)) > + new_crtc_state->mode_changed = true; > + > + > + priv_crtc_state->pix_fmt = lsdc_primary_get_default_format(crtc); Storing the pixel format in the CRTC state is weird? What would happen if you have a primary plane and a cursor in different formats? Also, reading the default format from a register doesn't look right. atomic_check can occur at any time, including before a previous commit, or while the hardware is disabled. You should rely on either a constant or the previous state here. > + if (lsdc_pixfmt_to_drm_pixfmt(priv_crtc_state->pix_fmt) != new_format) { > + drm_info(&ldev->drm, "mode changed\n"); > + new_crtc_state->mode_changed = true; > + } > + > + if (new_crtc_state->mode_changed) { > + struct lsdc_display_pipe *dispipe = container_of(plane, struct lsdc_display_pipe, primary); > + struct lsdc_pll *pixpll = &dispipe->pixpll; > + const struct lsdc_pixpll_funcs *pfuncs = pixpll->funcs; > + > + ret = pfuncs->compute(pixpll, > + new_crtc_state->mode.clock, > + true, > + &priv_crtc_state->pparams); > + if (ret == false) { > + drm_warn(plane->dev, > + "failed find a set of pll param for mode %u\n", > + new_crtc_state->mode.clock); > + return -EINVAL; > + } > + } > + > + if (ldev->dirty_update) > + drm_atomic_helper_check_plane_damage(state, new_plane_state); > + > + return 0; > +} > + > +static void lsdc_update_stride(struct lsdc_device *ldev, > + struct drm_crtc *crtc, > + unsigned int stride) > +{ > + unsigned int index = drm_crtc_index(crtc); > + > + if (index == 0) > + lsdc_reg_write32(ldev, LSDC_CRTC0_STRIDE_REG, stride); > + else if (index == 1) > + lsdc_reg_write32(ldev, LSDC_CRTC1_STRIDE_REG, stride); > + > + drm_dbg_kms(&ldev->drm, "update stride to %u\n", stride); > +} > + > + > + > +static void lsdc_primary_plane_atomic_update(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct lsdc_device *ldev = to_lsdc(plane->dev); > + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); > + struct drm_crtc *crtc = new_plane_state->crtc; > + struct drm_framebuffer *fb = new_plane_state->fb; > + u32 fb_offset = lsdc_get_fb_offset(fb, new_plane_state, 0); > + struct drm_gem_cma_object *obj; > + dma_addr_t fb_addr; > + > + if (ldev->use_vram_helper) { > +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER > + s64 gpu_addr; > + > + gpu_addr = lsdc_get_vram_bo_offset(fb); > + if (gpu_addr < 0) > + return; > + > + fb_addr = ldev->vram_base + gpu_addr + fb_offset; > +#endif > + } else { > + obj = drm_fb_cma_get_gem_obj(fb, 0); > + > + if (ldev->dirty_update) > + fb_addr = ldev->vram_base + fb_offset; > + else > + fb_addr = obj->paddr + fb_offset; > + } > + > + lsdc_update_fb_start_addr(ldev, crtc, fb_addr); > + > + lsdc_update_stride(ldev, crtc, fb->pitches[0]); > + > + if (drm_atomic_crtc_needs_modeset(crtc->state)) > + lsdc_update_fb_format(ldev, crtc, fb->format); > + > + if (ldev->dirty_update) { > + struct drm_plane_state *old_plane_state; > + struct drm_rect damage; > + bool valid; > + > + old_plane_state = drm_atomic_get_old_plane_state(state, plane); > + > + valid = drm_atomic_helper_damage_merged(old_plane_state, > + new_plane_state, > + &damage); > + if (valid) > + lsdc_handle_damage(ldev, fb, &damage, obj->vaddr); > + } > +} > + > + > +static void lsdc_primary_plane_atomic_disable(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + drm_dbg_kms(plane->dev, "%s disabled\n", plane->name); > +} > + > + > +static int lsdc_plane_prepare_fb(struct drm_plane *plane, > + struct drm_plane_state *new_state) > +{ > +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER > + struct lsdc_device *ldev = to_lsdc(plane->dev); > + > + if (ldev->use_vram_helper) > + return drm_gem_vram_plane_helper_prepare_fb(plane, new_state); > +#endif > + return drm_gem_plane_helper_prepare_fb(plane, new_state); > +} > + > + > +static void lsdc_plane_cleanup_fb(struct drm_plane *plane, > + struct drm_plane_state *old_state) > +{ > +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER > + struct drm_device *ddev = plane->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + > + if (ldev->use_vram_helper) > + return drm_gem_vram_plane_helper_cleanup_fb(plane, old_state); > +#endif > +} > + > +static const struct drm_plane_helper_funcs lsdc_primary_plane_helpers = { > + .prepare_fb = lsdc_plane_prepare_fb, > + .cleanup_fb = lsdc_plane_cleanup_fb, > + .atomic_check = lsdc_primary_plane_atomic_check, > + .atomic_update = lsdc_primary_plane_atomic_update, > + .atomic_disable = lsdc_primary_plane_atomic_disable, > +}; > + > + > + Don't use multiple blank lines. Generally speaking, there's a lot of checkpatch warnings to fix. Make sure to run checkpatch.pl --strict and fix whatever comes up. > +static int lsdc_cursor_atomic_check(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); > + struct drm_crtc *crtc = new_plane_state->crtc; > + struct drm_framebuffer *fb = new_plane_state->fb; > + struct drm_crtc_state *crtc_state; > + int ret; > + > + /* no need for further checks if the plane is being disabled */ > + if (!crtc || !fb) > + return 0; > + > + if (!new_plane_state->visible) > + return 0; > + > + crtc_state = drm_atomic_get_new_crtc_state(state, > + new_plane_state->crtc); > + > + ret = drm_atomic_helper_check_plane_state(new_plane_state, > + crtc_state, > + DRM_PLANE_HELPER_NO_SCALING, > + DRM_PLANE_HELPER_NO_SCALING, > + true, > + true); > + if (ret) > + return ret; > + > + if ((fb->width < LSDC_CURS_MIN_SIZE) || > + (fb->height < LSDC_CURS_MIN_SIZE) || > + (fb->width > LSDC_CURS_MAX_SIZE) || > + (fb->height > LSDC_CURS_MAX_SIZE)) { > + drm_err(plane->dev, "Invalid cursor size: %dx%d\n", > + fb->width, fb->height); > + return -EINVAL; > + } > + > + return 0; > +} > + > + > +/* Update the location of the cursor */ > +static void lsdc_cursor_update_location(struct lsdc_device *ldev, > + struct drm_crtc *crtc) > +{ > + u32 val; > + > + val = lsdc_reg_read32(ldev, LSDC_CURSOR_CFG_REG); > + > + if ((val & CURSOR_FORMAT_MASK) == 0) > + val |= CURSOR_FORMAT_ARGB8888; > + > + /* Update the location of the cursor */ > + if (drm_crtc_index(crtc)) > + val |= CURSOR_LOCATION_BIT; > + > + lsdc_reg_write32(ldev, LSDC_CURSOR_CFG_REG, val); > +} > + > +/* update the position of the cursor */ > +static void lsdc_cursor_update_position(struct lsdc_device *ldev, int x, int y) > +{ > + if (x < 0) > + x = 0; > + > + if (y < 0) > + y = 0; > + > + lsdc_reg_write32(ldev, LSDC_CURSOR_POSITION_REG, (y << 16) | x); > +} > + > + > +static void lsdc_cursor_atomic_update(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct drm_device *ddev = plane->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state, plane); > + struct drm_plane_state *old_plane_state = drm_atomic_get_old_plane_state(state, plane); > + struct drm_framebuffer *new_fb = new_plane_state->fb; > + struct drm_framebuffer *old_fb = old_plane_state->fb; > + > + if (new_fb != old_fb) { > + u64 cursor_addr; > + > + if (ldev->use_vram_helper) { > +#ifdef CONFIG_DRM_LSDC_VRAM_DRIVER > + s64 offset; > + > + offset = lsdc_get_vram_bo_offset(new_fb); > + cursor_addr = ldev->vram_base + offset; > + > + drm_dbg_kms(ddev, "%s offset: %llx\n", > + plane->name, offset); > +#endif > + } else { > + struct drm_gem_cma_object *cursor_obj; > + > + cursor_obj = drm_fb_cma_get_gem_obj(new_fb, 0); > + if (!cursor_obj) > + return; > + > + cursor_addr = cursor_obj->paddr; > + } > + > + lsdc_reg_write32(ldev, LSDC_CURSOR_ADDR_REG, cursor_addr); > + } > + > + lsdc_cursor_update_position(ldev, new_plane_state->crtc_x, > + new_plane_state->crtc_y); > + > + lsdc_cursor_update_location(ldev, new_plane_state->crtc); > +} > + > + > +static void lsdc_cursor_atomic_disable(struct drm_plane *plane, > + struct drm_atomic_state *state) > +{ > + struct drm_device *ddev = plane->dev; > + struct lsdc_device *ldev = to_lsdc(ddev); > + struct drm_plane_state *old_plane_state; > + struct drm_crtc *crtc; > + > + old_plane_state = drm_atomic_get_old_plane_state(state, plane); > + > + if (old_plane_state) > + crtc = old_plane_state->crtc; > + > + lsdc_reg_write32(ldev, LSDC_CURSOR_CFG_REG, 0); > + > + drm_dbg_kms(ddev, "%s disable\n", plane->name); > +} > + > + > +static const struct drm_plane_helper_funcs lsdc_cursor_plane_helpers = { > + .prepare_fb = lsdc_plane_prepare_fb, > + .cleanup_fb = lsdc_plane_cleanup_fb, > + .atomic_check = lsdc_cursor_atomic_check, > + .atomic_update = lsdc_cursor_atomic_update, > + .atomic_disable = lsdc_cursor_atomic_disable, > +}; > + > + > +static int lsdc_plane_get_default_zpos(enum drm_plane_type type) > +{ > + switch (type) { > + case DRM_PLANE_TYPE_PRIMARY: > + return 0; > + case DRM_PLANE_TYPE_OVERLAY: > + return 1; > + case DRM_PLANE_TYPE_CURSOR: > + return 7; > + } > + return 0; > +} > + > + > +static void lsdc_plane_reset(struct drm_plane *plane) > +{ > + drm_atomic_helper_plane_reset(plane); > + > + plane->state->zpos = lsdc_plane_get_default_zpos(plane->type); > + > + drm_dbg_kms(plane->dev, "%s reset\n", plane->name); > +} > + > + > +static const struct drm_plane_funcs lsdc_plane_funcs = { > + .update_plane = drm_atomic_helper_update_plane, > + .disable_plane = drm_atomic_helper_disable_plane, > + .destroy = drm_plane_cleanup, > + .reset = lsdc_plane_reset, > + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, > + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, > +}; > + > + > +int lsdc_plane_init(struct lsdc_device *ldev, struct drm_plane *plane, > + enum drm_plane_type type, unsigned int index) > +{ > + struct drm_device *ddev = &ldev->drm; > + int zpos = lsdc_plane_get_default_zpos(type); > + unsigned int format_count; > + const uint32_t *formats; > + const char *name; > + int ret; > + > + switch (type) { > + case DRM_PLANE_TYPE_PRIMARY: > + formats = lsdc_primary_formats; > + format_count = ARRAY_SIZE(lsdc_primary_formats); > + name = "primary-%u"; > + break; > + case DRM_PLANE_TYPE_CURSOR: > + formats = lsdc_cursor_formats; > + format_count = ARRAY_SIZE(lsdc_cursor_formats); > + name = "cursor-%u"; > + break; > + case DRM_PLANE_TYPE_OVERLAY: > + drm_err(ddev, "overlay plane is not supported\n"); > + break; > + } > + > + ret = drm_universal_plane_init(ddev, plane, 1 << index, > + &lsdc_plane_funcs, > + formats, format_count, > + lsdc_fb_format_modifiers, > + type, name, index); > + if (ret) { > + drm_err(ddev, "%s failed: %d\n", __func__, ret); > + return ret; > + } > + > + switch (type) { > + case DRM_PLANE_TYPE_PRIMARY: > + drm_plane_helper_add(plane, &lsdc_primary_plane_helpers); > + drm_plane_create_zpos_property(plane, zpos, 0, 6); > + if (ldev->dirty_update) > + drm_plane_enable_fb_damage_clips(plane); > + break; > + case DRM_PLANE_TYPE_CURSOR: > + drm_plane_helper_add(plane, &lsdc_cursor_plane_helpers); > + drm_plane_create_zpos_immutable_property(plane, zpos); > + break; > + case DRM_PLANE_TYPE_OVERLAY: > + drm_err(ddev, "overlay plane is not supported\n"); > + break; > + } > + > + drm_plane_create_alpha_property(plane); > + > + return 0; > +} > diff --git a/drivers/gpu/drm/lsdc/lsdc_pll.c b/drivers/gpu/drm/lsdc/lsdc_pll.c > new file mode 100644 > index 000000000000..fcf3728a9067 > --- /dev/null > +++ b/drivers/gpu/drm/lsdc/lsdc_pll.c > @@ -0,0 +1,657 @@ > +// SPDX-License-Identifier: GPL-2.0+ > +/* > + * Copyright 2020 Loongson Corporation > + * > + * Permission is hereby granted, free of charge, to any person obtaining a > + * copy of this software and associated documentation files (the > + * "Software"), to deal in the Software without restriction, including > + * without limitation the rights to use, copy, modify, merge, publish, > + * distribute, sub license, and/or sell copies of the Software, and to > + * permit persons to whom the Software is furnished to do so, subject to > + * the following conditions: > + * > + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR > + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, > + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL > + * THE COPYRIGHT HOLDERS, AUTHORS AND/OR ITS SUPPLIERS BE LIABLE FOR ANY CLAIM, > + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR > + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE > + * USE OR OTHER DEALINGS IN THE SOFTWARE. > + * > + * The above copyright notice and this permission notice (including the > + * next paragraph) shall be included in all copies or substantial portions > + * of the Software. > + */ > + > +/* > + * Authors: > + * Sui Jingfeng <suijingfeng@xxxxxxxxxxx> > + */ > + > +#include <drm/drm_print.h> > + > +#include "lsdc_drv.h" > +#include "lsdc_regs.h" > +#include "lsdc_pll.h" > + > +/* device dependent pixpll regs */ > + > +/* u64 */ > +struct ls7a1000_pixpll_bitmap { > + /* Byte 0 ~ Byte 3 */ > + unsigned div_out : 7; /* 0 : 6 output clock divider */ > + unsigned reserved_1 : 14; /* 7 : 20 */ > + unsigned loopc : 9; /* 21 : 29 */ > + unsigned reserved_2 : 2; /* 30 : 31 */ > + > + /* Byte 4 ~ Byte 7 */ > + unsigned div_ref : 7; /* 0 : 6 input clock divider */ > + unsigned locked : 1; /* 7 PLL locked flag */ > + unsigned sel_out : 1; /* 8 output clk selector */ > + unsigned reserved_3 : 2; /* 9 : 10 reserved */ > + unsigned set_param : 1; /* 11 set pll param */ > + unsigned bypass : 1; /* 12 */ > + unsigned powerdown : 1; /* 13 */ > + unsigned reserved_4 : 18; /* 14 : 31 */ > +}; > + > + > +/* u128 */ > +struct ls2k1000_pixpll_bitmap { > + /* Byte 0 ~ Byte 3 */ > + unsigned sel_out : 1; /* 0 select this PLL */ > + unsigned reserved_1 : 1; /* 1 */ > + unsigned sw_adj_en : 1; /* 2 allow software adjust */ > + unsigned bypass : 1; /* 3 bypass L1 PLL */ > + unsigned reserved_2 : 3; /* 4:6 */ > + unsigned lock_en : 1; /* 7 enable lock L1 PLL */ > + unsigned reserved_3 : 2; /* 8:9 */ > + unsigned lock_check : 2; /* 10:11 precision check */ > + unsigned reserved_4 : 4; /* 12:15 */ > + > + unsigned locked : 1; /* 16 PLL locked flag bit */ > + unsigned reserved_5 : 2; /* 17:18 */ > + unsigned powerdown : 1; /* 19 powerdown the pll if set */ > + unsigned reserved_6 : 6; /* 20:25 */ > + unsigned div_ref : 6; /* 26:31 L1 Prescaler */ > + > + /* Byte 4 ~ Byte 7 */ > + unsigned loopc : 10; /* 32:41 Clock Multiplier */ > + unsigned l1_div : 6; /* 42:47 not used */ > + unsigned reserved_7 : 16; /* 48:63 */ > + > + /* Byte 8 ~ Byte 15 */ > + unsigned div_out : 6; /* 0 : 5 output clock divider */ > + unsigned reserved_8 : 26; /* 6 : 31 */ > + unsigned reserved_9 : 32; /* 70: 127 */ > +}; > + > + > +/* u32 */ > +struct ls2k0500_pixpll_bitmap { > + /* Byte 0 ~ Byte 1 */ > + unsigned sel_out : 1; > + unsigned reserved_1 : 2; > + unsigned sw_adj_en : 1; /* allow software adjust */ > + unsigned bypass : 1; /* bypass L1 PLL */ > + unsigned powerdown : 1; /* write 1 to powerdown the PLL */ > + unsigned reserved_2 : 1; > + unsigned locked : 1; /* 7 Is L1 PLL locked, read only */ > + unsigned div_ref : 6; /* 8:13 ref clock divider */ > + unsigned reserved_3 : 2; /* 14:15 */ > + /* Byte 2 ~ Byte 3 */ > + unsigned loopc : 8; /* 16:23 Clock Multiplier */ > + unsigned div_out : 6; /* 24:29 output clock divider */ > + unsigned reserved_4 : 2; /* 30:31 */ > +}; > + > + > +/* > + * NOTE: All loongson's cpu is little endian. > + */ > +union lsdc_pix_pll_param { > + struct ls7a1000_pixpll_bitmap ls7a1000; > + struct ls2k1000_pixpll_bitmap ls2k1000; > + struct ls2k0500_pixpll_bitmap ls2k0500; > + > + u32 dword[4]; > +}; > + > +/* > + * pixel clock to pll parameters translation table > + */ > +struct pixclk_to_pll_parm { > + /* kHz */ > + unsigned int clock; > + > + /* unrelated information */ > + unsigned short width; > + unsigned short height; > + unsigned short vrefresh; > + > + /* Stores parameters for programming the Hardware PLLs */ > + unsigned short div_out; > + unsigned short loopc; > + unsigned short div_ref; > +}; > + > + > +/* > + * Small cached value to speed up pll parameter calculation > + */ > +static const struct pixclk_to_pll_parm pll_param_table[] = { > + {148500, 1920, 1080, 60, 11, 49, 3}, /* 1920x1080@60Hz */ > + /* 1920x1080@50Hz */ > + {174500, 1920, 1080, 75, 17, 89, 3}, /* 1920x1080@75Hz */ > + {181250, 2560, 1080, 75, 8, 58, 4}, /* 2560x1080@75Hz */ > + {146250, 1680, 1050, 60, 16, 117, 5}, /* 1680x1050@60Hz */ > + {135000, 1280, 1024, 75, 10, 54, 4}, /* 1280x1024@75Hz */ > + > + {108000, 1600, 900, 60, 15, 81, 5}, /* 1600x900@60Hz */ > + /* 1280x1024@60Hz */ > + /* 1280x960@60Hz */ > + /* 1152x864@75Hz */ > + > + {106500, 1440, 900, 60, 19, 81, 4}, /* 1440x900@60Hz */ > + {88750, 1440, 900, 60, 16, 71, 5}, /* 1440x900@60Hz */ > + {83500, 1280, 800, 60, 17, 71, 5}, /* 1280x800@60Hz */ > + {71000, 1280, 800, 60, 20, 71, 5}, /* 1280x800@60Hz */ > + > + {74250, 1280, 720, 60, 22, 49, 3}, /* 1280x720@60Hz */ > + /* 1280x720@50Hz */ > + > + {78750, 1024, 768, 75, 16, 63, 5}, /* 1024x768@75Hz */ > + {75000, 1024, 768, 70, 29, 87, 4}, /* 1024x768@70Hz */ > + {65000, 1024, 768, 60, 20, 39, 3}, /* 1024x768@60Hz */ > + > + {51200, 1024, 600, 60, 25, 64, 5}, /* 1024x600@60Hz */ > + > + {57284, 832, 624, 75, 24, 55, 4}, /* 832x624@75Hz */ > + {49500, 800, 600, 75, 40, 99, 5}, /* 800x600@75Hz */ > + {50000, 800, 600, 72, 44, 88, 4}, /* 800x600@72Hz */ > + {40000, 800, 600, 60, 30, 36, 3}, /* 800x600@60Hz */ > + {36000, 800, 600, 56, 50, 72, 4}, /* 800x600@56Hz */ > + {31500, 640, 480, 75, 40, 63, 5}, /* 640x480@75Hz */ > + /* 640x480@73Hz */ > + > + {30240, 640, 480, 67, 62, 75, 4}, /* 640x480@67Hz */ > + {27000, 720, 576, 50, 50, 54, 4}, /* 720x576@60Hz */ > + {25175, 640, 480, 60, 85, 107, 5}, /* 640x480@60Hz */ > + {25200, 640, 480, 60, 50, 63, 5}, /* 640x480@60Hz */ > + /* 720x480@60Hz */ > +}; > + > +/** > + * lsdc_pixpll_setup > + * > + * @this: point to the object which calling this function > + * > + * ioremap the device dependent register before using it > + */ > +static int lsdc_pixpll_setup(struct lsdc_pll * const this) > +{ > + this->mmio = ioremap(this->reg_base, this->reg_size); > + > + drm_info(this->ddev, "PIXPLL%u REG[%x, %u] map to %llx\n", > + this->index, this->reg_base, this->reg_size, (u64)this->mmio); > + > + return 0; > +} > + > + > + > +/* > + * Find a set of pll parameters (to generate pixel clock) from a static > + * local table, which avoid to compute the pll parameter everytime a > + * modeset is triggered. > + * > + * @this: point to the object which calling this function > + * @clock: the desired pixel clock wanted to generate, the unit is kHz > + * @pout: pointer to where hardware pll parameters(if found) to save > + * > + * Return true if a parameter is found, otherwise return false. > + */ > +static bool lsdc_pixpll_find(struct lsdc_pll * const this, > + unsigned int clock, > + struct lsdc_pll_core_values * const pout) > +{ > + unsigned int num = ARRAY_SIZE(pll_param_table); > + unsigned int i; > + > + for (i = 0; i < num; i++) { > + if (clock != pll_param_table[i].clock) > + continue; > + > + pout->div_ref = pll_param_table[i].div_ref; > + pout->loopc = pll_param_table[i].loopc; > + pout->div_out = pll_param_table[i].div_out; > + > + return true; > + } > + > + drm_dbg(this->ddev, "pixel clock %u: miss\n", clock); > + > + return false; > +} > + > +/* > + * Find a set of pll parameters which have minimal difference with desired > + * clock frequency. It does that by computing the all of pll parameters > + * combines possible and compare the diff and find the minimal. > + * > + * clock_out = refclk / div_ref * loopc / div_out > + * > + * refclk is fixed as 100MHz in ls7a1000, ls2k1000 and ls2k0500 > + * > + * @this: point to the object which calling this function > + * @clk: the desired pixel clock wanted to generate, the unit is kHz > + * @verbose: print the pll parameter and the actual pixel clock. > + * @pout: pointer to where hardware pll parameters(if found) to save > + * > + * Return true if a parameter is found, otherwise return false. > + */ > +static bool lsdc_pixpll_compute(struct lsdc_pll * const this, > + unsigned int clk, > + bool verbose, > + struct lsdc_pll_core_values * const pout) > +{ > + unsigned int refclk = this->ref_clock; > + const unsigned int tolerance = 1000; > + unsigned int min = tolerance; > + unsigned int div_out, loopc, div_ref; > + > + if (lsdc_pixpll_find(this, clk, pout)) > + return true; > + > + for (div_out = 6; div_out < 64; div_out++) { > + for (div_ref = 3; div_ref < 6; div_ref++) { > + for (loopc = 6; loopc < 161; loopc++) { > + int diff; > + > + if (loopc < 12 * div_ref) > + continue; > + if (loopc > 32 * div_ref) > + continue; > + > + diff = clk * div_out - refclk * loopc / div_ref; > + > + if (diff < 0) > + diff = -diff; > + > + if (diff < min) { > + min = diff; > + pout->div_ref = div_ref; > + pout->div_out = div_out; > + pout->loopc = loopc; > + > + if (diff == 0) > + return true; > + } > + } > + } > + } > + > + if (verbose) { > + unsigned int clk_out; > + > + clk_out = refclk / pout->div_ref * pout->loopc / pout->div_out; > + > + drm_info(this->ddev, "pixpll%u\n", this->index); > + > + drm_info(this->ddev, "div_ref=%u, loopc=%u, div_out=%u\n", > + pout->div_ref, pout->loopc, pout->div_out); > + > + drm_info(this->ddev, "desired clk=%u, actual out=%u, diff=%d\n", > + clk, clk_out, clk_out - clk); > + } > + > + return min < tolerance; > +} > + > +/* > + * Update the pll parameters to hardware, target to the pixpll in ls7a1000 > + * > + * @this: point to the object which calling this function > + * @param: pointer to where the parameters passed in > + * > + * Return true if a parameter is found, otherwise return false. > + */ > +static int ls7a1000_pixpll_param_update(struct lsdc_pll * const this, > + const struct lsdc_pll_core_values * const param) > +{ > + u32 val; > + unsigned int counter = 0; > + void __iomem *reg = this->mmio; > + bool locked; > + > + > + /* clear sel_pll_out0 */ > + val = readl(reg + 0x4); > + val &= ~(1 << 8); > + writel(val, reg + 0x4); > + > + /* set pll_pd */ > + val = readl(reg + 0x4); > + val |= (1 << 13); > + writel(val, reg + 0x4); > + > + /* clear set_pll_param */ > + val = readl(reg + 0x4); > + val &= ~(1 << 11); > + writel(val, reg + 0x4); > + > + /* clear old value & config new value */ > + val = readl(reg + 0x04); > + val &= ~0x7F; > + > + val |= param->div_ref; /* div_ref */ > + writel(val, reg + 0x4); > + > + val = readl(reg); > + val &= ~(0x7f << 0); > + val |= param->div_out; /* div_out */ > + val &= ~(0x1ffUL << 21); > + val |= param->loopc << 21; /* loopc */ > + writel(val, reg); > + > + /* set set_pll_param */ > + val = readl(reg + 0x4); > + val |= (1 << 11); > + writel(val, reg + 0x4); > + > + /* clear pll_pd */ > + val = readl(reg + 0x4); > + val &= ~(1 << 13); > + writel(val, reg + 0x4); > + > + /* wait pll lock */ > + do { > + val = readl(reg + 0x4); > + locked = val & 0x80; > + counter++; > + } while (locked == false); > + > + drm_dbg_kms(this->ddev, "%u loop waited\n", counter); > + > + /* set sel_pll_out0 */ > + val = readl(reg + 0x4); > + val |= (1UL << 8); > + writel(val, reg + 0x4); > + > + return 0; > +} > + > + > +/* > + * Update the pll parameters to hardware, target to the pixpll in ls2k1000 > + * > + * @this: point to the object which calling this function > + * @param: pointer to where the parameters passed in > + * > + * Return true if a parameter is found, otherwise return false. > + */ > +static int ls2k1000_pixpll_param_update(struct lsdc_pll * const this, > + const struct lsdc_pll_core_values * const param) > +{ > + void __iomem *reg = this->mmio; > + unsigned int counter = 0; > + bool locked = false; > + u32 val; > + > + val = readl(reg); > + /* Bypass the software configured PLL, using refclk directly */ > + val &= ~(1 << 0); > + writel(val, reg); > + > + /* powerdown the PLL */ > + val |= (1 << 19); > + writel(val, reg); > + > + /* Allow the software configuration */ > + val &= ~(1 << 2); > + writel(val, reg); > + > + /* allow L1 PLL lock */ > + val = (1L << 7) | (3L << 10); > + writel(val, reg); > + > + /* clear div_ref bit field */ > + val &= ~(0x3f << 26); > + /* set div_ref bit field */ > + val = val | (param->div_ref << 26); > + writel(val, reg); > + > + val = readl(reg + 4); > + /* clear loopc bit field */ > + val &= ~0x0fff; > + /* set loopc bit field */ > + val |= param->loopc; > + writel(val, reg + 4); > + > + /* set div_out */ > + writel(param->div_out, reg + 8); > + > + val = readl(reg); > + /* use the software configure param */ > + val |= (1 << 2); > + /* powerup the PLL */ > + val &= ~(1 << 19); > + writel(val, reg); > + > + /* wait pll setup and locked */ > + do { > + val = readl(reg); > + locked = val & 0x10000; > + counter++; > + } while (locked == false); > + > + drm_dbg_kms(this->ddev, "%u loop waited\n", counter); > + > + /* Switch to the above software configured PLL instead of refclk */ > + val |= 1; > + writel(val, reg); > + > + return 0; > +} > + > +/* > + * Update the pll parameters to hardware, target to the pixpll in ls2k0500 > + * > + * @this: point to the object which calling this function > + * @param: pointer to where the parameters passed in > + * > + * Return true if a parameter is found, otherwise return false. > + */ > + > +static int ls2k0500_pixpll_param_update(struct lsdc_pll * const this, > + const struct lsdc_pll_core_values * const param) > +{ > + void __iomem *reg = this->mmio; > + unsigned int counter = 0; > + bool locked = false; > + u32 val; > + > + /* Bypass the software configured PLL, using refclk directly */ > + val = readl(reg); > + val &= ~(1 << 0); > + writel(val, reg); > + > + /* Powerdown the PLL */ > + val = readl(reg); > + val |= (1 << 5); > + writel(val, reg); > + > + /* Allow the software configuration */ > + val |= (1 << 3); > + writel(val, reg); > + > + /* Update the pll params */ > + val = (param->div_out << 24) | > + (param->loopc << 16) | > + (param->div_ref << 8); > + > + writel(val, reg); > + > + /* Powerup the PLL */ > + val = readl(reg); > + val &= ~(1 << 5); > + writel(val, reg); > + > + /* wait pll setup and locked */ > + do { > + val = readl(reg); > + locked = val & 0x80; > + counter++; > + } while (locked == false); > + > + drm_dbg_kms(this->ddev, "%u loop waited\n", counter); > + > + /* Switch to the above software configured PLL instead of refclk */ > + writel((val | 1), reg); > + > + return 0; > +} > + > + > +#define LSDC_PIXPLL_BITMAP(type,var,parms) \ > + struct type ## _pixpll_bitmap *var = &parms.type > + > +#define LSDC_PIXPLL_PRINT_CODE_BLOCK(ddev,var,index,refclk) \ > +{ \ > + out_clk = refclk / var->div_ref * var->loopc / var->div_out; \ > + drm_info(ddev, "div_ref=%u, loopc=%u, div_out=%u\n", \ > + var->div_ref, var->loopc, var->div_out); \ > + drm_info(ddev, "locked: %s\n", var->locked ? "Yes" : "No"); \ > + drm_info(ddev, "bypass: %s\n", var->bypass ? "Yes" : "No"); \ > + drm_info(ddev, "powerdown: %s\n", var->powerdown ? "Yes" : "No"); \ > + drm_info(ddev, "set_out: %s\n", var->sel_out ? "Yes" : "No"); \ > + drm_info(ddev, "pixpll%u generate %ukHz\n", index, out_clk); \ > + drm_info(ddev, "\n"); \ > +} This should be a debug-level trace Maxime
Attachment:
signature.asc
Description: PGP signature