diff mbox series

[v2,2/4] drm: ipk: Add DRM driver for DesignWare IPK DSI

Message ID 488ff0f31581967517607e6860ab520839e29635.1586174459.git.angelo.ribeiro@synopsys.com (mailing list archive)
State New, archived
Headers show
Series drm: Add support for IPK DSI Host Driver | expand

Commit Message

Angelo Ribeiro April 6, 2020, 1:24 p.m. UTC
Add support for Synopsys DesignWare VPG (Video Pattern Generator) and
DRM driver for Synopsys DesignWare DSI Host IPK solution.

Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: Maxime Ripard <mripard@kernel.org>
Cc: David Airlie <airlied@linux.ie>
Cc: Daniel Vetter <daniel@ffwll.ch>
Cc: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
Cc: Joao Pinto <jpinto@synopsys.com>
Signed-off-by: Angelo Ribeiro <angelo.ribeiro@synopsys.com>
---
 drivers/gpu/drm/Kconfig      |   2 +
 drivers/gpu/drm/Makefile     |   1 +
 drivers/gpu/drm/ipk/Kconfig  |  13 +
 drivers/gpu/drm/ipk/Makefile |   6 +
 drivers/gpu/drm/ipk/dw-drv.c | 189 +++++++++++++++
 drivers/gpu/drm/ipk/dw-ipk.h |  30 +++
 drivers/gpu/drm/ipk/dw-vpg.c | 559 +++++++++++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/ipk/dw-vpg.h |  55 +++++
 8 files changed, 855 insertions(+)
 create mode 100644 drivers/gpu/drm/ipk/Kconfig
 create mode 100644 drivers/gpu/drm/ipk/Makefile
 create mode 100644 drivers/gpu/drm/ipk/dw-drv.c
 create mode 100644 drivers/gpu/drm/ipk/dw-ipk.h
 create mode 100644 drivers/gpu/drm/ipk/dw-vpg.c
 create mode 100644 drivers/gpu/drm/ipk/dw-vpg.h

Comments

Daniel Vetter April 7, 2020, 8:13 a.m. UTC | #1
On Mon, Apr 06, 2020 at 03:24:12PM +0200, Angelo Ribeiro wrote:
> Add support for Synopsys DesignWare VPG (Video Pattern Generator) and
> DRM driver for Synopsys DesignWare DSI Host IPK solution.
> 
> Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
> Cc: Maxime Ripard <mripard@kernel.org>
> Cc: David Airlie <airlied@linux.ie>
> Cc: Daniel Vetter <daniel@ffwll.ch>
> Cc: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
> Cc: Joao Pinto <jpinto@synopsys.com>
> Signed-off-by: Angelo Ribeiro <angelo.ribeiro@synopsys.com>
> ---
>  drivers/gpu/drm/Kconfig      |   2 +
>  drivers/gpu/drm/Makefile     |   1 +
>  drivers/gpu/drm/ipk/Kconfig  |  13 +
>  drivers/gpu/drm/ipk/Makefile |   6 +
>  drivers/gpu/drm/ipk/dw-drv.c | 189 +++++++++++++++
>  drivers/gpu/drm/ipk/dw-ipk.h |  30 +++
>  drivers/gpu/drm/ipk/dw-vpg.c | 559 +++++++++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/ipk/dw-vpg.h |  55 +++++
>  8 files changed, 855 insertions(+)
>  create mode 100644 drivers/gpu/drm/ipk/Kconfig
>  create mode 100644 drivers/gpu/drm/ipk/Makefile
>  create mode 100644 drivers/gpu/drm/ipk/dw-drv.c
>  create mode 100644 drivers/gpu/drm/ipk/dw-ipk.h
>  create mode 100644 drivers/gpu/drm/ipk/dw-vpg.c
>  create mode 100644 drivers/gpu/drm/ipk/dw-vpg.h
> 
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index 4359497..29ea1d1 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -388,6 +388,8 @@ source "drivers/gpu/drm/mcde/Kconfig"
>  
>  source "drivers/gpu/drm/tidss/Kconfig"
>  
> +source "drivers/gpu/drm/ipk/Kconfig"
> +
>  # Keep legacy drivers last
>  
>  menuconfig DRM_LEGACY
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 183c600..5bcc1c1 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -125,3 +125,4 @@ obj-$(CONFIG_DRM_PANFROST) += panfrost/
>  obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
>  obj-$(CONFIG_DRM_MCDE) += mcde/
>  obj-$(CONFIG_DRM_TIDSS) += tidss/
> +obj-$(CONFIG_DRM_IPK) += ipk/
> diff --git a/drivers/gpu/drm/ipk/Kconfig b/drivers/gpu/drm/ipk/Kconfig
> new file mode 100644
> index 0000000..1f87444
> --- /dev/null
> +++ b/drivers/gpu/drm/ipk/Kconfig
> @@ -0,0 +1,13 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +config DRM_IPK
> +	tristate "DRM Support for Synopsys DesignWare IPK DSI"
> +	depends on DRM
> +	select DRM_KMS_HELPER
> +	select DRM_GEM_CMA_HELPER
> +	select DRM_KMS_CMA_HELPER
> +	select DRM_PANEL_BRIDGE
> +	select VIDEOMODE_HELPERS
> +	help
> +	  Enable support for the Synopsys DesignWare DRM DSI.
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called ipk-drm.
> diff --git a/drivers/gpu/drm/ipk/Makefile b/drivers/gpu/drm/ipk/Makefile
> new file mode 100644
> index 0000000..51d2774
> --- /dev/null
> +++ b/drivers/gpu/drm/ipk/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +ipk-drm-y := \
> +	dw-drv.o \
> +	dw-vpg.o
> +
> +obj-$(CONFIG_DRM_IPK) += ipk-drm.o
> diff --git a/drivers/gpu/drm/ipk/dw-drv.c b/drivers/gpu/drm/ipk/dw-drv.c
> new file mode 100644
> index 0000000..6205f1c
> --- /dev/null
> +++ b/drivers/gpu/drm/ipk/dw-drv.c
> @@ -0,0 +1,189 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
> + * Synopsys DesignWare MIPI DSI DRM driver
> + *
> + * Author: Angelo Ribeiro <angelo.ribeiro@synopsys.com>
> + */
> +
> +#include <linux/component.h>
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
> +#include <linux/clk.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_fb_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_probe_helper.h>
> +
> +#include "dw-ipk.h"
> +#include "dw-vpg.h"
> +
> +static const struct drm_mode_config_funcs dw_ipk_drm_modecfg_funcs = {
> +	.fb_create = drm_gem_fb_create_with_dirty,
> +	.atomic_check = drm_atomic_helper_check,
> +	.atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static int dw_ipk_load(struct drm_device *drm)
> +{
> +	int ret;
> +
> +	drm_mode_config_init(drm);
> +
> +	drm->mode_config.min_width = 0;
> +	drm->mode_config.min_height = 0;
> +
> +	/* To handle orientation */
> +	drm->mode_config.max_width = 2048;
> +	drm->mode_config.max_height = 2048;
> +
> +	drm->mode_config.funcs = &dw_ipk_drm_modecfg_funcs;
> +
> +	/* TODO
> +	 * Optional framebuffer memory resources allocation
> +	 */
> +
> +	ret = vpg_load(drm);
> +	if (ret)
> +		return ret;
> +
> +	/* Calls all the crtc's, encoder's and connector's reset */
> +	drm_mode_config_reset(drm);
> +
> +	/* Initialize and enable output polling */
> +	drm_kms_helper_poll_init(drm);
> +
> +	return ret;
> +}
> +
> +static void dw_ipk_unload(struct drm_device *drm)
> +{
> +	DRM_DEBUG_DRIVER("\n");
> +
> +	drm_kms_helper_poll_fini(drm);
> +	vpg_unload(drm);
> +}
> +
> +DEFINE_DRM_GEM_CMA_FOPS(ipk_drm_driver_fops);
> +
> +static int ipk_gem_cma_dumb_create(struct drm_file *file,
> +				   struct drm_device *dev,
> +				   struct drm_mode_create_dumb *args)
> +{
> +	unsigned int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
> +	int err;
> +
> +	/*
> +	 * In order to optimize data transfer, pitch is aligned on
> +	 * 128 bytes, height is aligned on 4 bytes
> +	 */
> +	args->pitch = roundup(min_pitch, 128);
> +	args->height = roundup(args->height, 4);
> +
> +	err = drm_gem_cma_dumb_create_internal(file, dev, args);
> +	if (err < 0)
> +		drm_err(dev, "dumb_create failed %d\n", err);
> +
> +	return err;
> +}
> +
> +static struct drm_driver dw_ipk_drm_driver = {
> +	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
> +	.name = "dw_ipk",
> +	.desc = "DW IPK DSI Host Controller",
> +	.date = "20190725",
> +	.major = 1,
> +	.minor = 0,
> +	.patchlevel = 0,
> +	.fops = &ipk_drm_driver_fops,
> +	.dumb_create = ipk_gem_cma_dumb_create,
> +	.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
> +	.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
> +	.gem_free_object_unlocked = drm_gem_cma_free_object,
> +	.gem_vm_ops = &drm_gem_cma_vm_ops,
> +	.gem_prime_export = drm_gem_prime_export,
> +	.gem_prime_import = drm_gem_prime_import,
> +	.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
> +	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
> +	.gem_prime_vmap = drm_gem_cma_prime_vmap,
> +	.gem_prime_vunmap = drm_gem_cma_prime_vunmap,
> +	.gem_prime_mmap = drm_gem_cma_prime_mmap,
> +};
> +
> +static int dw_ipk_drm_platform_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct drm_device *drm;
> +	struct ipk_device *ipk;
> +	int ret;
> +
> +	DRM_DEBUG_DRIVER("\n");
> +
> +	ipk = kzalloc(sizeof(*ipk), GFP_KERNEL);
> +	if (!ipk)
> +		return -ENOMEM;
> +
> +	ipk->platform = pdev;
> +	drm = &ipk->drm;
> +
> +	ret = drm_dev_init(&ipk->drm, &dw_ipk_drm_driver, dev);
> +	if (ret) {
> +		kfree(ipk);
> +		return ret;
> +	}

I'm reworking this right now, you're missing a call to
drmm_add_final_kfree here. But I think even better would be to rebase on
top of my patch here:

https://patchwork.freedesktop.org/patch/359897/

Which would allow you to drop a bunch of code from your load/unload code.
See that patch series for a lot of example conversions. I plan to land
that patch pretty soon in drm-misc-next so shouldn't hold up merging your
driver.
-Daniel

> +
> +	platform_set_drvdata(pdev, drm);
> +
> +	ret = dw_ipk_load(drm);
> +	if (ret)
> +		goto err_put;
> +
> +	ret = drm_dev_register(drm, 0);
> +	if (ret)
> +		goto err_put;
> +
> +	drm_fbdev_generic_setup(drm, 24);
> +
> +	return ret;
> +
> +err_put:
> +	drm_dev_put(drm);
> +	return ret;
> +}
> +
> +static int dw_ipk_drm_platform_remove(struct platform_device *pdev)
> +{
> +	struct drm_device *drm = platform_get_drvdata(pdev);
> +
> +	drm_dev_unregister(drm);
> +	dw_ipk_unload(drm);
> +	drm_dev_put(drm);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id dw_ipk_dt_ids[] = {
> +	{.compatible = "snps,dw-ipk-vpg"},
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, dw_ipk_dt_ids);
> +
> +static struct platform_driver dw_ipk_drm_platform_driver = {
> +	.probe = dw_ipk_drm_platform_probe,
> +	.remove = dw_ipk_drm_platform_remove,
> +	.driver = {
> +		.name = "dw-ipk-drm",
> +		.of_match_table = dw_ipk_dt_ids,
> +	},
> +};
> +
> +module_platform_driver(dw_ipk_drm_platform_driver);
> +
> +MODULE_DESCRIPTION("Synopsys DesignWare IPK DRM driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Angelo Ribeiro <angelo.ribeiro@synopsys.com>");
> diff --git a/drivers/gpu/drm/ipk/dw-ipk.h b/drivers/gpu/drm/ipk/dw-ipk.h
> new file mode 100644
> index 0000000..4abb6dd
> --- /dev/null
> +++ b/drivers/gpu/drm/ipk/dw-ipk.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
> + * Synopsys DesignWare MIPI DSI Controller
> + */
> +
> +#ifndef _DW_IPK_H
> +#define _DW_IPK_H
> +
> +#include "drm/drm_device.h"
> +#include <drm/bridge/dw_mipi_dsi.h>
> +
> +struct ipk_pipeline {
> +	struct drm_framebuffer	*fb;
> +	struct drm_crtc		crtc;
> +	struct drm_plane	*plane;
> +	struct drm_bridge	*bridge;
> +};
> +
> +struct ipk_device {
> +	struct drm_device drm;
> +	struct platform_device *platform;
> +	struct ipk_pipeline pipeline;
> +	struct vpg_device *vpg;
> +};
> +
> +#define drm_device_to_ipk_device(target) \
> +	container_of(target, struct ipk_device, drm)
> +
> +#endif /* _DW_IPK_H */
> diff --git a/drivers/gpu/drm/ipk/dw-vpg.c b/drivers/gpu/drm/ipk/dw-vpg.c
> new file mode 100644
> index 0000000..feb3e90
> --- /dev/null
> +++ b/drivers/gpu/drm/ipk/dw-vpg.c
> @@ -0,0 +1,559 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
> + * Synopsys DesignWare MIPI DSI controller
> + *
> + * Author: Angelo Ribeiro <angelor@synopsys.com>
> + * Author: Luis Oliveira <luis.oliveira@synopsys.com>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/of_address.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/reset.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_device.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_vblank.h>
> +#include <linux/platform_data/simplefb.h>
> +#include <video/videomode.h>
> +
> +#include "dw-ipk.h"
> +#include "dw-vpg.h"
> +
> +#define NB_CRTC 1
> +#define CRTC_MASK GENMASK(NB_CRTC - 1, 0)
> +#define MAX_ENDPOINTS 1
> +
> +struct vpg_device {
> +	void __iomem *base;
> +	void __iomem *base_mmcm;
> +};
> +
> +enum vpg_pixel_fmt {
> +	VIF_COLOR_CODE_16BIT_CONFIG1 = 0,
> +	VIF_COLOR_CODE_16BIT_CONFIG2,
> +	VIF_COLOR_CODE_16BIT_CONFIG3,
> +	VIF_COLOR_CODE_18BIT_CONFIG1,
> +	VIF_COLOR_CODE_18BIT_CONFIG2,
> +	VIF_COLOR_CODE_24BIT,
> +	VIF_COLOR_CODE_MAX
> +};
> +
> +static enum vpg_pixel_fmt to_vpg_pixel_format(u32 drm_fmt)
> +{
> +	enum vpg_pixel_fmt pf;
> +
> +	switch (drm_fmt) {
> +	case DRM_FORMAT_RGB888:
> +		pf = VIF_COLOR_CODE_24BIT;
> +		break;
> +	case DRM_FORMAT_RGB565:
> +		pf = VIF_COLOR_CODE_16BIT_CONFIG1;
> +		break;
> +	default:
> +		pf = VIF_COLOR_CODE_MAX;
> +		break;
> +	}
> +	return pf;
> +}
> +
> +static struct simplefb_format supported_formats[] = {
> +	{"r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, DRM_FORMAT_RGB565},
> +	{"r8g8b8", 24, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_RGB888},
> +	{"x8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_XRGB8888},
> +};
> +
> +static const u64 vpg_format_modifiers[] = {
> +	DRM_FORMAT_MOD_LINEAR,
> +	DRM_FORMAT_MOD_INVALID
> +};
> +
> +static const struct drm_encoder_funcs ipk_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
> +
> +static int ipk_encoder_init(struct drm_device *drm,
> +			    struct drm_bridge *bridge)
> +{
> +	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
> +	struct drm_encoder *encoder;
> +	int ret;
> +
> +	encoder = devm_kzalloc(drm->dev, sizeof(*encoder), GFP_KERNEL);
> +	if (!encoder)
> +		return -ENOMEM;
> +
> +	/* Only one CRTC can be attached in IPK_DSI */
> +	encoder->possible_crtcs = CRTC_MASK;
> +	encoder->possible_clones = 0;
> +
> +	ret = drm_encoder_init(drm, encoder, &ipk_encoder_funcs,
> +			       DRM_MODE_ENCODER_DPI, NULL);
> +	if (ret)
> +		return ret;
> +
> +	ret = drm_bridge_attach(encoder, bridge, NULL, 0);
> +	if (ret)
> +		goto err_attach;
> +
> +	/* Add to the internal struct */
> +	ipk->pipeline.bridge = bridge;
> +
> +	DRM_DEBUG_DRIVER("Num of connectors = %d\n",
> +			 drm->mode_config.num_connector);
> +	DRM_DEBUG_DRIVER("Bridge encoder:%d created\n", encoder->base.id);
> +
> +	return ret;
> +
> +err_attach:
> +	drm_encoder_cleanup(encoder);
> +	return ret;
> +}
> +
> +/*
> + * CRTC
> + */
> +static u32 vpg_read(struct vpg_device *dev, u32 reg)
> +{
> +	return readl(dev->base + reg);
> +}
> +
> +static void vpg_write(struct vpg_device *dev, u32 reg, u32 val)
> +{
> +	writel(val, dev->base + reg);
> +}
> +
> +static void vpg_write_part(struct vpg_device *dev, u32 reg,
> +			   u32 val, u8 shift, u8 width)
> +{
> +	u32 mask = (1 << width) - 1;
> +
> +	vpg_write(dev, reg, (vpg_read(dev, reg) &
> +		  ~(mask << shift)) | ((val & mask) << shift));
> +}
> +
> +static u32 mmcm_read(struct vpg_device *dev, u32 reg)
> +{
> +	return readl(dev->base_mmcm + reg);
> +}
> +
> +static void mmcm_write(struct vpg_device *dev, u32 reg, u32 value)
> +{
> +	writel(value, dev->base_mmcm + reg);
> +}
> +
> +static void mmcm_configure(struct vpg_device *vpg, struct videomode *v_mode)
> +{
> +	int div,  mul, cur_freq, cur_deviat, temp;
> +	int out_freq = v_mode->pixelclock / 1000;
> +	int in_freq = 100000;
> +	int deviat = 1000;	/* Deviation from desired master clock */
> +	int best_div = 0;	/* Divider for PLL */
> +	int best_mul = 0;	/* Multiplier for PLL */
> +
> +	DRM_DEBUG_DRIVER("out_freq = %d\n", out_freq);
> +
> +	for (div = 1; div < 150 && deviat > 50; div++) {
> +		for (mul = 1; mul <= 10 && deviat > 50; mul++) {
> +			cur_freq = in_freq * mul / div;
> +			cur_deviat = abs(out_freq - cur_freq);
> +			if (cur_deviat < deviat) {
> +				best_div = div;
> +				best_mul = mul;
> +				deviat = cur_deviat;
> +			}
> +		}
> +	}
> +
> +	DRM_DEBUG_DRIVER("deviat = %d\n best_div = %d\n best_mul = %d\n",
> +			 deviat, best_div, best_mul);
> +
> +	temp = mmcm_read(vpg, DW_MMCM_CLKOUT0_REG_1) & DW_MMCM_MASK;
> +	mmcm_write(vpg, DW_MMCM_CLKOUT0_REG_1, temp | (best_div << 6) |
> +		   best_div);
> +
> +	temp = mmcm_read(vpg, DW_MMCM_CLKFBOUT_REG_1) & DW_MMCM_MASK;
> +	mmcm_write(vpg, DW_MMCM_CLKFBOUT_REG_1, temp | (best_mul << 6) |
> +		   best_mul);
> +}
> +
> +static void vpg_set_fps(struct vpg_device *vpg,
> +			const struct drm_display_mode *mode)
> +{
> +	/* IF_Freq is in KHz */
> +	u32 line_time = (1000 * DW_VPG_FREQ) / (mode->vrefresh * mode->vtotal);
> +
> +	vpg_write(vpg, DW_VPG_LINE_TIME, line_time);
> +}
> +
> +static void vpg_select_pattern(struct vpg_device *vpg, unsigned int value)
> +{
> +	vpg_write(vpg, DW_VPG_TESTMODE, value);
> +}
> +
> +static void vpg_vertical_set(struct vpg_device *vpg, struct videomode *v_mode)
> +{
> +	vpg_write_part(vpg, DW_VPG_CANVAS, v_mode->vactive, 16, 16);
> +	vpg_write_part(vpg, DW_VPG_VBP_VFP_VSA, v_mode->vsync_len, 0, 8);
> +	vpg_write_part(vpg, DW_VPG_VBP_VFP_VSA, v_mode->vback_porch, 20, 12);
> +	vpg_write_part(vpg, DW_VPG_VBP_VFP_VSA, v_mode->vfront_porch, 8, 11);
> +}
> +
> +static void vpg_horizontal_set(struct vpg_device *vpg, struct videomode *v_mode)
> +{
> +	vpg_write_part(vpg, DW_VPG_CANVAS, v_mode->hactive, 0, 16);
> +	vpg_write_part(vpg, DW_VPG_HBP_HSA, v_mode->hsync_len, 0, 16);
> +	vpg_write_part(vpg, DW_VPG_HBP_HSA, v_mode->hback_porch, 16, 16);
> +}
> +
> +static ssize_t show_pattern(struct device *dev,
> +			    struct device_attribute *attr, char *buf)
> +{
> +	struct drm_device *drm = dev_get_drvdata(dev);
> +	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
> +	struct vpg_device *vpg = ipk->vpg;
> +
> +	return sprintf(buf, "%d\n", vpg_read(vpg, DW_VPG_TESTMODE));
> +}
> +
> +static ssize_t store_pattern(struct device *dev, struct device_attribute *attr,
> +			     const char *buf, size_t count)
> +{
> +	struct drm_device *drm = dev_get_drvdata(dev);
> +	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
> +	struct vpg_device *vpg = ipk->vpg;
> +	long pattern;
> +	int ret;
> +
> +	ret = kstrtol(buf, 0, &pattern);
> +	if (ret < 0)
> +		return ret;
> +
> +	vpg_select_pattern(vpg, pattern);
> +
> +	return count;
> +}
> +
> +static struct kobj_attribute vpg_pattern = __ATTR(pattern, 0660,
> +						  (void *)show_pattern,
> +						  (void *)store_pattern);
> +
> +static struct attribute *vpg_attr[] = {
> +	&vpg_pattern.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group vpg_attr_group = {
> +	.attrs = vpg_attr,
> +};
> +
> +static void vpg_crtc_mode_set_nofb(struct drm_crtc *crtc)
> +{
> +	struct ipk_device *ipk = drm_device_to_ipk_device(crtc->dev);
> +	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
> +	struct vpg_device *vpg = ipk->vpg;
> +	struct videomode v_mode;
> +
> +	DRM_DEBUG_DRIVER("\n");
> +
> +	drm_display_mode_to_videomode(mode, &v_mode);
> +
> +	DRM_DEBUG_DRIVER("CRTC:%d mode:%s\n", crtc->base.id, mode->name);
> +	DRM_DEBUG_DRIVER("Video mode: %dx%d", v_mode.hactive, v_mode.vactive);
> +	DRM_DEBUG_DRIVER(" hfp %d hbp %d hsl %d vfp %d vbp %d vsl %d\n",
> +			 v_mode.hfront_porch, v_mode.hback_porch,
> +			 v_mode.hsync_len, v_mode.vfront_porch,
> +			 v_mode.vback_porch, v_mode.vsync_len);
> +
> +	mmcm_configure(vpg, &v_mode);
> +
> +	vpg_write(vpg, DW_VPG_SEL_DATA, 0x01);
> +	vpg_write(vpg, DW_VPG_SEL_DATA, 0x03);
> +
> +	vpg_horizontal_set(vpg, &v_mode);
> +
> +	vpg_vertical_set(vpg, &v_mode);
> +
> +	vpg_set_fps(vpg, mode);
> +
> +	vpg_select_pattern(vpg, 0);
> +}
> +
> +static void vpg_crtc_atomic_begin(struct drm_crtc *crtc,
> +				  struct drm_crtc_state *state)
> +{
> +	struct drm_pending_vblank_event *event = crtc->state->event;
> +
> +	if (event) {
> +		crtc->state->event = NULL;
> +		spin_lock_irq(&crtc->dev->event_lock);
> +		drm_crtc_send_vblank_event(crtc, event);
> +		spin_unlock_irq(&crtc->dev->event_lock);
> +	}
> +}
> +
> +static struct drm_crtc_helper_funcs vpg_crtc_helper_funcs = {
> +	.mode_set_nofb = vpg_crtc_mode_set_nofb,
> +	.atomic_begin = vpg_crtc_atomic_begin,
> +};
> +
> +static int vpg_crtc_late_register(struct drm_crtc *crtc)
> +{
> +	struct ipk_device *ipk = drm_device_to_ipk_device(crtc->dev);
> +	struct platform_device *pdev = ipk->platform;
> +	int ret;
> +
> +	ret = sysfs_create_group(&pdev->dev.kobj, &vpg_attr_group);
> +	if (ret)
> +		drm_err(crtc->dev, "Failed to create sysfs entries");
> +
> +	return ret;
> +}
> +
> +static void vpg_crtc_early_unregister(struct drm_crtc *crtc)
> +{
> +	struct ipk_device *ipk = drm_device_to_ipk_device(crtc->dev);
> +	struct platform_device *pdev = ipk->platform;
> +
> +	sysfs_remove_group(&pdev->dev.kobj, &vpg_attr_group);
> +}
> +
> +static const struct drm_crtc_funcs vpg_crtc_funcs = {
> +	.reset = drm_atomic_helper_crtc_reset,
> +	.destroy = drm_crtc_cleanup,
> +	.set_config = drm_atomic_helper_set_config,
> +	.page_flip = drm_atomic_helper_page_flip,
> +	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> +	.late_register =  vpg_crtc_late_register,
> +	.early_unregister = vpg_crtc_early_unregister
> +};
> +
> +/*
> + * Plane
> + */
> +static void vpg_plane_atomic_update(struct drm_plane *plane,
> +				    struct drm_plane_state *old_state)
> +{
> +	struct ipk_device *ipk = drm_device_to_ipk_device(plane->dev);
> +	struct drm_plane_state *state = plane->state;
> +	struct drm_framebuffer *fb = state->fb;
> +	struct vpg_device *vpg = ipk->vpg;
> +	enum vpg_pixel_fmt pixel_format;
> +
> +	pixel_format = to_vpg_pixel_format(fb->format->format);
> +
> +	vpg_write(vpg, DW_VPG_DPICOLORMODE, pixel_format);
> +}
> +
> +static void vpg_plane_atomic_disable(struct drm_plane *plane,
> +				     struct drm_plane_state *oldstate)
> +{
> +	DRM_DEBUG_DRIVER("\n");
> +}
> +
> +static const struct drm_plane_helper_funcs vpg_plane_helper_funcs = {
> +	.atomic_update = vpg_plane_atomic_update,
> +	.atomic_disable = vpg_plane_atomic_disable,
> +};
> +
> +static const struct drm_plane_funcs vpg_plane_funcs = {
> +	.update_plane = drm_atomic_helper_update_plane,
> +	.disable_plane = drm_atomic_helper_disable_plane,
> +	.destroy = drm_plane_cleanup,
> +	.reset = drm_atomic_helper_plane_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
> +};
> +
> +static void vpg_plane_destroy_all(struct drm_device *drm)
> +{
> +	struct drm_plane *plane, *plane_temp;
> +
> +	list_for_each_entry_safe(plane, plane_temp,
> +				 &drm->mode_config.plane_list, head)
> +		drm_plane_cleanup(plane);
> +}
> +
> +static int vpg_crtc_init(struct drm_device *drm, struct drm_crtc *crtc)
> +{
> +	u32 formats[ARRAY_SIZE(supported_formats)], i;
> +	const u64 *modifiers = vpg_format_modifiers;
> +	unsigned long possible_crtcs = CRTC_MASK;
> +	struct device *drm_dev = drm->dev;
> +	struct drm_plane *plane;
> +	int ret;
> +
> +	plane = devm_kzalloc(drm_dev, sizeof(*plane), GFP_KERNEL);
> +	if (!plane)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < ARRAY_SIZE(supported_formats); i++)
> +		formats[i] = supported_formats[i].fourcc;
> +
> +	ret = drm_universal_plane_init(drm, plane, possible_crtcs,
> +				       &vpg_plane_funcs, formats,
> +				       ARRAY_SIZE(formats), modifiers,
> +				       DRM_PLANE_TYPE_PRIMARY, NULL);
> +	if (ret)
> +		goto cleanup_planes;
> +
> +	drm_plane_helper_add(plane, &vpg_plane_helper_funcs);
> +
> +	DRM_DEBUG_DRIVER("plane:%d created\n", plane->base.id);
> +
> +	ret = drm_crtc_init_with_planes(drm, crtc, plane, NULL,
> +					&vpg_crtc_funcs, "DSI-VPG");
> +	if (ret) {
> +		DRM_DEBUG_DRIVER("Failed initialize CRTC\n");
> +		goto cleanup_crtc;
> +	}
> +
> +	drm_crtc_helper_add(crtc, &vpg_crtc_helper_funcs);
> +
> +	DRM_DEBUG_DRIVER("CRTC:%d created\n", crtc->base.id);
> +
> +	return ret;
> +
> +cleanup_crtc:
> +	drm_crtc_cleanup(crtc);
> +cleanup_planes:
> +	vpg_plane_destroy_all(drm);
> +	return ret;
> +}
> +
> +int vpg_load(struct drm_device *drm)
> +{
> +	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
> +	struct drm_bridge *bridge[MAX_ENDPOINTS] = {NULL};
> +	struct drm_panel *panel[MAX_ENDPOINTS] = {NULL};
> +	struct platform_device *pdev = ipk->platform;
> +	struct device_node *np = drm->dev->of_node;
> +	int ret, i, endpoint_not_ready = -ENODEV;
> +	struct reset_control *vpg_rst, *mmcm_rst;
> +	struct device *dev = drm->dev;
> +	struct vpg_device *vpg;
> +	struct drm_crtc *crtc;
> +	struct resource *res;
> +
> +	DRM_DEBUG_DRIVER("\n");
> +
> +	vpg = devm_kzalloc(&pdev->dev, sizeof(*vpg), GFP_KERNEL);
> +	if (!vpg)
> +		return -ENOMEM;
> +
> +	ipk->vpg = vpg;
> +
> +	/* Get endpoints if any */
> +	for (i = 0; i < MAX_ENDPOINTS; i++) {
> +		ret = drm_of_find_panel_or_bridge(np, 0, i, &panel[i],
> +						  &bridge[i]);
> +		if (ret == -EPROBE_DEFER)
> +			return ret;
> +		else if (!ret)
> +			endpoint_not_ready = 0;
> +	}
> +
> +	if (endpoint_not_ready)
> +		return endpoint_not_ready;
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vpg");
> +	vpg->base = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(vpg->base)) {
> +		drm_err(drm, "Unable to get VPG registers\n");
> +		ret = PTR_ERR(vpg->base);
> +		goto err;
> +	}
> +
> +	vpg_rst = devm_reset_control_get_optional_exclusive(dev, "vpg");
> +	if (IS_ERR(vpg_rst)) {
> +		ret = PTR_ERR(vpg_rst);
> +		if (ret != -EPROBE_DEFER)
> +			drm_err(drm, "Unable to get reset control: %d\n", ret);
> +		goto err;
> +	}
> +
> +	if (vpg_rst) {
> +		reset_control_assert(vpg_rst);
> +		usleep_range(10, 20);
> +		reset_control_deassert(vpg_rst);
> +	}
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mmcm");
> +	vpg->base_mmcm = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(vpg->base)) {
> +		ret = PTR_ERR(vpg->base);
> +		goto err;
> +	}
> +
> +	mmcm_rst = devm_reset_control_get_optional_exclusive(dev, "mmcm");
> +	if (IS_ERR(mmcm_rst)) {
> +		ret = PTR_ERR(mmcm_rst);
> +		if (ret != -EPROBE_DEFER)
> +			drm_err(drm, "Unable to get reset control: %d\n", ret);
> +		goto err;
> +	}
> +
> +	if (mmcm_rst) {
> +		reset_control_assert(mmcm_rst);
> +		usleep_range(10, 20);
> +		reset_control_deassert(mmcm_rst);
> +	}
> +
> +	/* Add endpoints panels or bridges if any */
> +	for (i = 0; i < MAX_ENDPOINTS; i++) {
> +		if (panel[i]) {
> +			bridge[i] = drm_panel_bridge_add_typed(panel[i],
> +							DRM_MODE_CONNECTOR_DSI);
> +			if (IS_ERR(bridge[i])) {
> +				ret = PTR_ERR(bridge[i]);
> +				goto err;
> +			}
> +		}
> +
> +		if (bridge[i]) {
> +			ret = ipk_encoder_init(drm, bridge[i]);
> +			if (ret)
> +				goto err;
> +		}
> +	}
> +
> +	crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL);
> +	if (!crtc) {
> +		ret = -ENOMEM;
> +		goto err;
> +	}
> +
> +	ret = vpg_crtc_init(drm, crtc);
> +	if (ret)
> +		goto err;
> +
> +	return ret;
> +
> +err:
> +	for (i = 0; i < MAX_ENDPOINTS; i++)
> +		drm_panel_bridge_remove(bridge[i]);
> +
> +	return ret;
> +}
> +
> +void vpg_unload(struct drm_device *drm)
> +{
> +	int i;
> +
> +	DRM_DEBUG_DRIVER("\n");
> +	for (i = 0; i < MAX_ENDPOINTS; i++)
> +		drm_of_panel_bridge_remove(drm->dev->of_node, 0, i);
> +}
> diff --git a/drivers/gpu/drm/ipk/dw-vpg.h b/drivers/gpu/drm/ipk/dw-vpg.h
> new file mode 100644
> index 0000000..869ccc4
> --- /dev/null
> +++ b/drivers/gpu/drm/ipk/dw-vpg.h
> @@ -0,0 +1,55 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
> + * Synopsys DesignWare VPG
> + */
> +
> +#ifndef _VPG_H_
> +#define _VPG_H_
> +
> +#define DW_VPG_PANX				0x00
> +#define DW_VPG_PANY				0x04
> +#define DW_VPG_PHASE_SEL			0x08
> +#define DW_VPG_SEL_DATA				0x0C
> +#define DW_VPG_SWAP_BAY				0x10
> +#define DW_VPG_TESTMODE				0x14
> +#define DW_VPG_ZOOM_OUT				0x18
> +#define DW_VPG_EDPICTRL				0x1C
> +#define DW_VPG_CANVAS				0x20
> +#define DW_VPG_HBP_HSA				0x24
> +#define DW_VPG_LINE_TIME			0x28
> +#define DW_VPG_VBP_VFP_VSA			0x2C
> +#define DW_VPG_DPICOLORMODE			0x30
> +#define DW_VPG_VERSION				0x34
> +#define DW_VPG_EXTRNAL_EDPI			0x38
> +#define DW_VPG_PVO_CEAVID			0x3C
> +#define DW_VPG_PAN_VALID			0x40
> +#define DW_VPG_TECONTROL			0x44
> +#define DW_VPG_IMAGE_START_POSITION_X		0x4C
> +#define DW_VPG_IMAGE_START_POSITION_Y		0x50
> +#define DW_VPG_IMAGE_Y				0x54
> +#define DW_VPG_IMAGE_WORD_COUNT			0x58
> +#define DW_VPG_IMAGE_DATA_TYPE			0x5C
> +#define DW_VPG_LINE_PIXS_CNT			0x60
> +#define DW_VPG_FRAME_LINES_CNT			0x64
> +
> +#define DW_MMCM_MASK				0x1000
> +#define DW_MMCM_CLKOUT0_REG_1			0x20
> +#define DW_MMCM_CLKOUT0_REG_2			0x24
> +#define DW_MMCM_CLKFBOUT_REG_1			0x50
> +#define DW_MMCM_CLKFBOUT_REG_2			0x54
> +#define DW_MMCM_POWER_REG			0xA0
> +
> +#define DW_VPG_ZOOM_OUT_STEP	1
> +#define DW_VPG_PAN_STEP	40
> +#define DW_VPG_FREQ	25000	/* [KHz] */
> +#define DW_VPG_V_FRONT_PORCH	2
> +#define DW_VPG_V_BACK_PORCH	2
> +#define DW_VPG_V_SYNC	2
> +#define SCREEN_SIZE_X	800
> +#define SCREEN_SIZE_Y	480
> +
> +int vpg_load(struct drm_device *ddev);
> +void vpg_unload(struct drm_device *ddev);
> +
> +#endif /* _VPG_H_ */
> -- 
> 2.7.4
>
Sam Ravnborg April 7, 2020, 5:47 p.m. UTC | #2
Hi Angelo.

Has it been evaluated if the drm_simple_* stuff can be used?
It looks like this is a single plane, single connector thing so a good
candidate.

Some nits below.

	Sam

On Mon, Apr 06, 2020 at 03:24:12PM +0200, Angelo Ribeiro wrote:
> Add support for Synopsys DesignWare VPG (Video Pattern Generator) and
> DRM driver for Synopsys DesignWare DSI Host IPK solution.
> 
> Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
> Cc: Maxime Ripard <mripard@kernel.org>
> Cc: David Airlie <airlied@linux.ie>
> Cc: Daniel Vetter <daniel@ffwll.ch>
> Cc: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
> Cc: Joao Pinto <jpinto@synopsys.com>
> Signed-off-by: Angelo Ribeiro <angelo.ribeiro@synopsys.com>
> ---
>  drivers/gpu/drm/Kconfig      |   2 +
>  drivers/gpu/drm/Makefile     |   1 +
>  drivers/gpu/drm/ipk/Kconfig  |  13 +
>  drivers/gpu/drm/ipk/Makefile |   6 +
>  drivers/gpu/drm/ipk/dw-drv.c | 189 +++++++++++++++
>  drivers/gpu/drm/ipk/dw-ipk.h |  30 +++
>  drivers/gpu/drm/ipk/dw-vpg.c | 559 +++++++++++++++++++++++++++++++++++++++++++
>  drivers/gpu/drm/ipk/dw-vpg.h |  55 +++++
>  8 files changed, 855 insertions(+)
>  create mode 100644 drivers/gpu/drm/ipk/Kconfig
>  create mode 100644 drivers/gpu/drm/ipk/Makefile
>  create mode 100644 drivers/gpu/drm/ipk/dw-drv.c
>  create mode 100644 drivers/gpu/drm/ipk/dw-ipk.h
>  create mode 100644 drivers/gpu/drm/ipk/dw-vpg.c
>  create mode 100644 drivers/gpu/drm/ipk/dw-vpg.h
> 
> diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> index 4359497..29ea1d1 100644
> --- a/drivers/gpu/drm/Kconfig
> +++ b/drivers/gpu/drm/Kconfig
> @@ -388,6 +388,8 @@ source "drivers/gpu/drm/mcde/Kconfig"
>  
>  source "drivers/gpu/drm/tidss/Kconfig"
>  
> +source "drivers/gpu/drm/ipk/Kconfig"
> +
>  # Keep legacy drivers last
>  
>  menuconfig DRM_LEGACY
> diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> index 183c600..5bcc1c1 100644
> --- a/drivers/gpu/drm/Makefile
> +++ b/drivers/gpu/drm/Makefile
> @@ -125,3 +125,4 @@ obj-$(CONFIG_DRM_PANFROST) += panfrost/
>  obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
>  obj-$(CONFIG_DRM_MCDE) += mcde/
>  obj-$(CONFIG_DRM_TIDSS) += tidss/
> +obj-$(CONFIG_DRM_IPK) += ipk/
> diff --git a/drivers/gpu/drm/ipk/Kconfig b/drivers/gpu/drm/ipk/Kconfig
> new file mode 100644
> index 0000000..1f87444
> --- /dev/null
> +++ b/drivers/gpu/drm/ipk/Kconfig
> @@ -0,0 +1,13 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +config DRM_IPK
> +	tristate "DRM Support for Synopsys DesignWare IPK DSI"
> +	depends on DRM
> +	select DRM_KMS_HELPER
> +	select DRM_GEM_CMA_HELPER
> +	select DRM_KMS_CMA_HELPER
> +	select DRM_PANEL_BRIDGE
> +	select VIDEOMODE_HELPERS
> +	help
> +	  Enable support for the Synopsys DesignWare DRM DSI.
> +	  To compile this driver as a module, choose M here: the module
> +	  will be called ipk-drm.
> diff --git a/drivers/gpu/drm/ipk/Makefile b/drivers/gpu/drm/ipk/Makefile
> new file mode 100644
> index 0000000..51d2774
> --- /dev/null
> +++ b/drivers/gpu/drm/ipk/Makefile
> @@ -0,0 +1,6 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +ipk-drm-y := \
> +	dw-drv.o \
> +	dw-vpg.o
There is no point in adding '\' here.
A simple:

    ipk-drm-y := dw-drv.o dw-vpg.o

Lot's of Makefile uses the ugly '\' where they should
use a += instead :-(

> +obj-$(CONFIG_DRM_IPK) += ipk-drm.o
> diff --git a/drivers/gpu/drm/ipk/dw-drv.c b/drivers/gpu/drm/ipk/dw-drv.c
> new file mode 100644
> index 0000000..6205f1c
> --- /dev/null
> +++ b/drivers/gpu/drm/ipk/dw-drv.c
> @@ -0,0 +1,189 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
> + * Synopsys DesignWare MIPI DSI DRM driver
> + *
> + * Author: Angelo Ribeiro <angelo.ribeiro@synopsys.com>
> + */
> +
> +#include <linux/component.h>
> +#include <linux/module.h>
> +#include <linux/of_platform.h>
> +#include <linux/clk.h>
> +#include <drm/drm_crtc_helper.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_fb_helper.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_gem_framebuffer_helper.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_drv.h>
> +#include <drm/drm_probe_helper.h>
> +
> +#include "dw-ipk.h"
> +#include "dw-vpg.h"

Please order thei ncludes like this:

#include <linux/*>
[empty line]
#include <drm/*>
[empty line]
#include "dw*.h"

Within each block - sort alphabetically.
Drop unused includes - drm_probe_helper is IIRC for legacy stuff
mostly/only.

> +
> +static const struct drm_mode_config_funcs dw_ipk_drm_modecfg_funcs = {
> +	.fb_create = drm_gem_fb_create_with_dirty,
> +	.atomic_check = drm_atomic_helper_check,
> +	.atomic_commit = drm_atomic_helper_commit,
> +};
> +
> +static int dw_ipk_load(struct drm_device *drm)
> +{
> +	int ret;
> +
> +	drm_mode_config_init(drm);
drmm_mode_config_init(drm);
And remember to check return value.

> +
> +	drm->mode_config.min_width = 0;
> +	drm->mode_config.min_height = 0;
> +
> +	/* To handle orientation */
> +	drm->mode_config.max_width = 2048;
> +	drm->mode_config.max_height = 2048;
> +
> +	drm->mode_config.funcs = &dw_ipk_drm_modecfg_funcs;
> +
> +	/* TODO
> +	 * Optional framebuffer memory resources allocation
> +	 */
> +
> +	ret = vpg_load(drm);
> +	if (ret)
> +		return ret;
> +
> +	/* Calls all the crtc's, encoder's and connector's reset */
> +	drm_mode_config_reset(drm);
> +
> +	/* Initialize and enable output polling */
> +	drm_kms_helper_poll_init(drm);
> +
> +	return ret;
> +}
> +
> +static void dw_ipk_unload(struct drm_device *drm)
> +{
> +	DRM_DEBUG_DRIVER("\n");
when you have a drm_device - then please use drm_xxx for
debug/warnings/errors - and also drm_WARN as applicable.
This goes for everywhere that DRM_XXX is used.
In this case use:
	drm_dbg(drm, "\n");

> +
> +	drm_kms_helper_poll_fini(drm);
> +	vpg_unload(drm);
> +}
> +
> +DEFINE_DRM_GEM_CMA_FOPS(ipk_drm_driver_fops);
> +
> +static int ipk_gem_cma_dumb_create(struct drm_file *file,
> +				   struct drm_device *dev,
> +				   struct drm_mode_create_dumb *args)
> +{
> +	unsigned int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
> +	int err;
> +
> +	/*
> +	 * In order to optimize data transfer, pitch is aligned on
> +	 * 128 bytes, height is aligned on 4 bytes
> +	 */
> +	args->pitch = roundup(min_pitch, 128);
> +	args->height = roundup(args->height, 4);
> +
> +	err = drm_gem_cma_dumb_create_internal(file, dev, args);
> +	if (err < 0)
> +		drm_err(dev, "dumb_create failed %d\n", err);
> +
> +	return err;
> +}
> +
> +static struct drm_driver dw_ipk_drm_driver = {
> +	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
> +	.name = "dw_ipk",
> +	.desc = "DW IPK DSI Host Controller",
> +	.date = "20190725",
> +	.major = 1,
> +	.minor = 0,
> +	.patchlevel = 0,
> +	.fops = &ipk_drm_driver_fops,
> +	.dumb_create = ipk_gem_cma_dumb_create,
> +	.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
> +	.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
> +	.gem_free_object_unlocked = drm_gem_cma_free_object,
From the documentation:

This is deprecated and should not be used by new drivers. Use
&drm_gem_object_funcs.free instead.


> +	.gem_vm_ops = &drm_gem_cma_vm_ops,
> +	.gem_prime_export = drm_gem_prime_export,
From documentation:

Export hook for GEM drivers. Deprecated in favour of
&drm_gem_object_funcs.export.


> +	.gem_prime_import = drm_gem_prime_import,
From documentation:
This defaults to drm_gem_prime_import() if not set.


> +	.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
> +	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
> +	.gem_prime_vmap = drm_gem_cma_prime_vmap,
> +	.gem_prime_vunmap = drm_gem_cma_prime_vunmap,
> +	.gem_prime_mmap = drm_gem_cma_prime_mmap,
> +};
Did not check all - please go through drm_drv.h - and fix so the driver
do not use any deprecated stuff.


> +
> +static int dw_ipk_drm_platform_probe(struct platform_device *pdev)
> +{
> +	struct device *dev = &pdev->dev;
> +	struct drm_device *drm;
> +	struct ipk_device *ipk;
> +	int ret;
> +
> +	DRM_DEBUG_DRIVER("\n");
> +
> +	ipk = kzalloc(sizeof(*ipk), GFP_KERNEL);
drmm_kzalloc() - but see feedback from Daniel too.
> +	if (!ipk)
> +		return -ENOMEM;
> +
> +	ipk->platform = pdev;
> +	drm = &ipk->drm;
> +
> +	ret = drm_dev_init(&ipk->drm, &dw_ipk_drm_driver, dev);
> +	if (ret) {
> +		kfree(ipk);
> +		return ret;
> +	}
> +
> +	platform_set_drvdata(pdev, drm);
> +
> +	ret = dw_ipk_load(drm);
> +	if (ret)
> +		goto err_put;
> +
> +	ret = drm_dev_register(drm, 0);
> +	if (ret)
> +		goto err_put;
> +
> +	drm_fbdev_generic_setup(drm, 24);
> +
> +	return ret;
> +
> +err_put:
> +	drm_dev_put(drm);
> +	return ret;
> +}
> +
> +static int dw_ipk_drm_platform_remove(struct platform_device *pdev)
> +{
> +	struct drm_device *drm = platform_get_drvdata(pdev);
> +
> +	drm_dev_unregister(drm);
> +	dw_ipk_unload(drm);
> +	drm_dev_put(drm);
> +
> +	return 0;
> +}
> +
> +static const struct of_device_id dw_ipk_dt_ids[] = {
> +	{.compatible = "snps,dw-ipk-vpg"},
> +	{ /* sentinel */ }
> +};
> +MODULE_DEVICE_TABLE(of, dw_ipk_dt_ids);
> +
> +static struct platform_driver dw_ipk_drm_platform_driver = {
> +	.probe = dw_ipk_drm_platform_probe,
> +	.remove = dw_ipk_drm_platform_remove,
> +	.driver = {
> +		.name = "dw-ipk-drm",
> +		.of_match_table = dw_ipk_dt_ids,
> +	},
> +};
> +
> +module_platform_driver(dw_ipk_drm_platform_driver);
> +
> +MODULE_DESCRIPTION("Synopsys DesignWare IPK DRM driver");
> +MODULE_LICENSE("GPL v2");
> +MODULE_AUTHOR("Angelo Ribeiro <angelo.ribeiro@synopsys.com>");
> diff --git a/drivers/gpu/drm/ipk/dw-ipk.h b/drivers/gpu/drm/ipk/dw-ipk.h
> new file mode 100644
> index 0000000..4abb6dd
> --- /dev/null
> +++ b/drivers/gpu/drm/ipk/dw-ipk.h
> @@ -0,0 +1,30 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
> + * Synopsys DesignWare MIPI DSI Controller
> + */
> +
> +#ifndef _DW_IPK_H
> +#define _DW_IPK_H
> +
> +#include "drm/drm_device.h"
> +#include <drm/bridge/dw_mipi_dsi.h>
Add the inlcudes so the header file is standalone . the header file
should not relay on other files being includes.
Use forward as appropriate - like for example:

struct drm_bridge;
struct drm_framebuffer;
stuct drm_plane;

Keep the forwards alphabetically sorted.

> +
> +struct ipk_pipeline {
> +	struct drm_framebuffer	*fb;
> +	struct drm_crtc		crtc;
> +	struct drm_plane	*plane;
> +	struct drm_bridge	*bridge;
> +};
> +
> +struct ipk_device {
> +	struct drm_device drm;
> +	struct platform_device *platform;
> +	struct ipk_pipeline pipeline;
> +	struct vpg_device *vpg;
> +};
> +
> +#define drm_device_to_ipk_device(target) \
> +	container_of(target, struct ipk_device, drm)
> +
> +#endif /* _DW_IPK_H */
> diff --git a/drivers/gpu/drm/ipk/dw-vpg.c b/drivers/gpu/drm/ipk/dw-vpg.c
> new file mode 100644
> index 0000000..feb3e90
> --- /dev/null
> +++ b/drivers/gpu/drm/ipk/dw-vpg.c
> @@ -0,0 +1,559 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
> + * Synopsys DesignWare MIPI DSI controller
> + *
> + * Author: Angelo Ribeiro <angelor@synopsys.com>
> + * Author: Luis Oliveira <luis.oliveira@synopsys.com>
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/component.h>
> +#include <linux/delay.h>
> +#include <linux/interrupt.h>
> +#include <linux/module.h>
> +#include <linux/of_address.h>
> +#include <linux/of_graph.h>
> +#include <linux/platform_device.h>
> +#include <linux/reset.h>
> +
> +#include <drm/drm_atomic.h>
> +#include <drm/drm_atomic_helper.h>
> +#include <drm/drm_bridge.h>
> +#include <drm/drm_device.h>
> +#include <drm/drm_fb_cma_helper.h>
> +#include <drm/drm_fourcc.h>
> +#include <drm/drm_gem_cma_helper.h>
> +#include <drm/drm_of.h>
> +#include <drm/drm_vblank.h>
> +#include <linux/platform_data/simplefb.h>
> +#include <video/videomode.h>
> +
> +#include "dw-ipk.h"
> +#include "dw-vpg.h"
See earlier rant on include files.

> +
> +#define NB_CRTC 1
> +#define CRTC_MASK GENMASK(NB_CRTC - 1, 0)
> +#define MAX_ENDPOINTS 1
> +
> +struct vpg_device {
> +	void __iomem *base;
> +	void __iomem *base_mmcm;
> +};
> +
> +enum vpg_pixel_fmt {
> +	VIF_COLOR_CODE_16BIT_CONFIG1 = 0,
> +	VIF_COLOR_CODE_16BIT_CONFIG2,
> +	VIF_COLOR_CODE_16BIT_CONFIG3,
> +	VIF_COLOR_CODE_18BIT_CONFIG1,
> +	VIF_COLOR_CODE_18BIT_CONFIG2,
> +	VIF_COLOR_CODE_24BIT,
> +	VIF_COLOR_CODE_MAX
> +};
> +
> +static enum vpg_pixel_fmt to_vpg_pixel_format(u32 drm_fmt)
> +{
> +	enum vpg_pixel_fmt pf;
> +
> +	switch (drm_fmt) {
> +	case DRM_FORMAT_RGB888:
> +		pf = VIF_COLOR_CODE_24BIT;
> +		break;
> +	case DRM_FORMAT_RGB565:
> +		pf = VIF_COLOR_CODE_16BIT_CONFIG1;
> +		break;
> +	default:
> +		pf = VIF_COLOR_CODE_MAX;
> +		break;
> +	}
> +	return pf;
> +}
> +
> +static struct simplefb_format supported_formats[] = {
> +	{"r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, DRM_FORMAT_RGB565},
> +	{"r8g8b8", 24, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_RGB888},
> +	{"x8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_XRGB8888},
> +};
> +
> +static const u64 vpg_format_modifiers[] = {
> +	DRM_FORMAT_MOD_LINEAR,
> +	DRM_FORMAT_MOD_INVALID
> +};
> +
> +static const struct drm_encoder_funcs ipk_encoder_funcs = {
> +	.destroy = drm_encoder_cleanup,
> +};
This driver is a candidate to use drm_simple_encoder_init()
This would get rid of the above ipk_encoder_funcs variable.

> +
> +static int ipk_encoder_init(struct drm_device *drm,
> +			    struct drm_bridge *bridge)
> +{
> +	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
> +	struct drm_encoder *encoder;
> +	int ret;
> +
> +	encoder = devm_kzalloc(drm->dev, sizeof(*encoder), GFP_KERNEL);
> +	if (!encoder)
> +		return -ENOMEM;
> +
> +	/* Only one CRTC can be attached in IPK_DSI */
> +	encoder->possible_crtcs = CRTC_MASK;
> +	encoder->possible_clones = 0;
> +
> +	ret = drm_encoder_init(drm, encoder, &ipk_encoder_funcs,
> +			       DRM_MODE_ENCODER_DPI, NULL);
> +	if (ret)
> +		return ret;
> +
> +	ret = drm_bridge_attach(encoder, bridge, NULL, 0);
> +	if (ret)
> +		goto err_attach;
> +
> +	/* Add to the internal struct */
> +	ipk->pipeline.bridge = bridge;
> +
> +	DRM_DEBUG_DRIVER("Num of connectors = %d\n",
> +			 drm->mode_config.num_connector);
> +	DRM_DEBUG_DRIVER("Bridge encoder:%d created\n", encoder->base.id);
drm_dbg(drm, ...)
> +
> +	return ret;
> +
> +err_attach:
> +	drm_encoder_cleanup(encoder);
> +	return ret;
> +}
> +
> +/*
> + * CRTC
> + */
> +static u32 vpg_read(struct vpg_device *dev, u32 reg)
> +{
> +	return readl(dev->base + reg);
> +}
> +
> +static void vpg_write(struct vpg_device *dev, u32 reg, u32 val)
> +{
> +	writel(val, dev->base + reg);
> +}
> +
> +static void vpg_write_part(struct vpg_device *dev, u32 reg,
> +			   u32 val, u8 shift, u8 width)
> +{
> +	u32 mask = (1 << width) - 1;
> +
> +	vpg_write(dev, reg, (vpg_read(dev, reg) &
> +		  ~(mask << shift)) | ((val & mask) << shift));
> +}
> +
> +static u32 mmcm_read(struct vpg_device *dev, u32 reg)
> +{
> +	return readl(dev->base_mmcm + reg);
> +}
> +
> +static void mmcm_write(struct vpg_device *dev, u32 reg, u32 value)
> +{
> +	writel(value, dev->base_mmcm + reg);
> +}
> +
> +static void mmcm_configure(struct vpg_device *vpg, struct videomode *v_mode)
> +{
> +	int div,  mul, cur_freq, cur_deviat, temp;
> +	int out_freq = v_mode->pixelclock / 1000;
> +	int in_freq = 100000;
> +	int deviat = 1000;	/* Deviation from desired master clock */
> +	int best_div = 0;	/* Divider for PLL */
> +	int best_mul = 0;	/* Multiplier for PLL */
> +
> +	DRM_DEBUG_DRIVER("out_freq = %d\n", out_freq);
> +
> +	for (div = 1; div < 150 && deviat > 50; div++) {
> +		for (mul = 1; mul <= 10 && deviat > 50; mul++) {
> +			cur_freq = in_freq * mul / div;
> +			cur_deviat = abs(out_freq - cur_freq);
> +			if (cur_deviat < deviat) {
> +				best_div = div;
> +				best_mul = mul;
> +				deviat = cur_deviat;
> +			}
> +		}
> +	}
> +
> +	DRM_DEBUG_DRIVER("deviat = %d\n best_div = %d\n best_mul = %d\n",
> +			 deviat, best_div, best_mul);
> +
> +	temp = mmcm_read(vpg, DW_MMCM_CLKOUT0_REG_1) & DW_MMCM_MASK;
> +	mmcm_write(vpg, DW_MMCM_CLKOUT0_REG_1, temp | (best_div << 6) |
> +		   best_div);
> +
> +	temp = mmcm_read(vpg, DW_MMCM_CLKFBOUT_REG_1) & DW_MMCM_MASK;
> +	mmcm_write(vpg, DW_MMCM_CLKFBOUT_REG_1, temp | (best_mul << 6) |
> +		   best_mul);
> +}
> +
> +static void vpg_set_fps(struct vpg_device *vpg,
> +			const struct drm_display_mode *mode)
> +{
> +	/* IF_Freq is in KHz */
> +	u32 line_time = (1000 * DW_VPG_FREQ) / (mode->vrefresh * mode->vtotal);
> +
> +	vpg_write(vpg, DW_VPG_LINE_TIME, line_time);
> +}
> +
> +static void vpg_select_pattern(struct vpg_device *vpg, unsigned int value)
> +{
> +	vpg_write(vpg, DW_VPG_TESTMODE, value);
> +}
> +
> +static void vpg_vertical_set(struct vpg_device *vpg, struct videomode *v_mode)
> +{
> +	vpg_write_part(vpg, DW_VPG_CANVAS, v_mode->vactive, 16, 16);
> +	vpg_write_part(vpg, DW_VPG_VBP_VFP_VSA, v_mode->vsync_len, 0, 8);
> +	vpg_write_part(vpg, DW_VPG_VBP_VFP_VSA, v_mode->vback_porch, 20, 12);
> +	vpg_write_part(vpg, DW_VPG_VBP_VFP_VSA, v_mode->vfront_porch, 8, 11);
> +}
> +
> +static void vpg_horizontal_set(struct vpg_device *vpg, struct videomode *v_mode)
> +{
> +	vpg_write_part(vpg, DW_VPG_CANVAS, v_mode->hactive, 0, 16);
> +	vpg_write_part(vpg, DW_VPG_HBP_HSA, v_mode->hsync_len, 0, 16);
> +	vpg_write_part(vpg, DW_VPG_HBP_HSA, v_mode->hback_porch, 16, 16);
> +}
> +
> +static ssize_t show_pattern(struct device *dev,
> +			    struct device_attribute *attr, char *buf)
> +{
> +	struct drm_device *drm = dev_get_drvdata(dev);
> +	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
> +	struct vpg_device *vpg = ipk->vpg;
> +
> +	return sprintf(buf, "%d\n", vpg_read(vpg, DW_VPG_TESTMODE));
> +}
> +
> +static ssize_t store_pattern(struct device *dev, struct device_attribute *attr,
> +			     const char *buf, size_t count)
> +{
> +	struct drm_device *drm = dev_get_drvdata(dev);
> +	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
> +	struct vpg_device *vpg = ipk->vpg;
> +	long pattern;
> +	int ret;
> +
> +	ret = kstrtol(buf, 0, &pattern);
> +	if (ret < 0)
> +		return ret;
> +
> +	vpg_select_pattern(vpg, pattern);
> +
> +	return count;
> +}
> +
> +static struct kobj_attribute vpg_pattern = __ATTR(pattern, 0660,
> +						  (void *)show_pattern,
> +						  (void *)store_pattern);
> +
> +static struct attribute *vpg_attr[] = {
> +	&vpg_pattern.attr,
> +	NULL,
> +};
> +
> +static const struct attribute_group vpg_attr_group = {
> +	.attrs = vpg_attr,
> +};
> +
> +static void vpg_crtc_mode_set_nofb(struct drm_crtc *crtc)
> +{
> +	struct ipk_device *ipk = drm_device_to_ipk_device(crtc->dev);
> +	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
> +	struct vpg_device *vpg = ipk->vpg;
> +	struct videomode v_mode;
> +
> +	DRM_DEBUG_DRIVER("\n");
> +
> +	drm_display_mode_to_videomode(mode, &v_mode);
Could be dropped, and all users of videomode could use drm_display_mode.
One less indirection.

> +
> +	DRM_DEBUG_DRIVER("CRTC:%d mode:%s\n", crtc->base.id, mode->name);
> +	DRM_DEBUG_DRIVER("Video mode: %dx%d", v_mode.hactive, v_mode.vactive);
> +	DRM_DEBUG_DRIVER(" hfp %d hbp %d hsl %d vfp %d vbp %d vsl %d\n",
> +			 v_mode.hfront_porch, v_mode.hback_porch,
> +			 v_mode.hsync_len, v_mode.vfront_porch,
> +			 v_mode.vback_porch, v_mode.vsync_len);
> +
> +	mmcm_configure(vpg, &v_mode);
> +
> +	vpg_write(vpg, DW_VPG_SEL_DATA, 0x01);
> +	vpg_write(vpg, DW_VPG_SEL_DATA, 0x03);
> +
> +	vpg_horizontal_set(vpg, &v_mode);
> +
> +	vpg_vertical_set(vpg, &v_mode);
> +
> +	vpg_set_fps(vpg, mode);
> +
> +	vpg_select_pattern(vpg, 0);
> +}
> +
> +static void vpg_crtc_atomic_begin(struct drm_crtc *crtc,
> +				  struct drm_crtc_state *state)
> +{
> +	struct drm_pending_vblank_event *event = crtc->state->event;
> +
> +	if (event) {
> +		crtc->state->event = NULL;
> +		spin_lock_irq(&crtc->dev->event_lock);
> +		drm_crtc_send_vblank_event(crtc, event);
> +		spin_unlock_irq(&crtc->dev->event_lock);
> +	}
> +}
> +
> +static struct drm_crtc_helper_funcs vpg_crtc_helper_funcs = {
> +	.mode_set_nofb = vpg_crtc_mode_set_nofb,
> +	.atomic_begin = vpg_crtc_atomic_begin,
> +};
> +
> +static int vpg_crtc_late_register(struct drm_crtc *crtc)
> +{
> +	struct ipk_device *ipk = drm_device_to_ipk_device(crtc->dev);
> +	struct platform_device *pdev = ipk->platform;
> +	int ret;
> +
> +	ret = sysfs_create_group(&pdev->dev.kobj, &vpg_attr_group);
> +	if (ret)
> +		drm_err(crtc->dev, "Failed to create sysfs entries");
> +
> +	return ret;
> +}
> +
> +static void vpg_crtc_early_unregister(struct drm_crtc *crtc)
> +{
> +	struct ipk_device *ipk = drm_device_to_ipk_device(crtc->dev);
> +	struct platform_device *pdev = ipk->platform;
> +
> +	sysfs_remove_group(&pdev->dev.kobj, &vpg_attr_group);
> +}
> +
> +static const struct drm_crtc_funcs vpg_crtc_funcs = {
> +	.reset = drm_atomic_helper_crtc_reset,
> +	.destroy = drm_crtc_cleanup,
> +	.set_config = drm_atomic_helper_set_config,
> +	.page_flip = drm_atomic_helper_page_flip,
> +	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> +	.late_register =  vpg_crtc_late_register,
> +	.early_unregister = vpg_crtc_early_unregister
> +};
> +
> +/*
> + * Plane
> + */
> +static void vpg_plane_atomic_update(struct drm_plane *plane,
> +				    struct drm_plane_state *old_state)
> +{
> +	struct ipk_device *ipk = drm_device_to_ipk_device(plane->dev);
> +	struct drm_plane_state *state = plane->state;
> +	struct drm_framebuffer *fb = state->fb;
> +	struct vpg_device *vpg = ipk->vpg;
> +	enum vpg_pixel_fmt pixel_format;
> +
> +	pixel_format = to_vpg_pixel_format(fb->format->format);
> +
> +	vpg_write(vpg, DW_VPG_DPICOLORMODE, pixel_format);
> +}
> +
> +static void vpg_plane_atomic_disable(struct drm_plane *plane,
> +				     struct drm_plane_state *oldstate)
> +{
> +	DRM_DEBUG_DRIVER("\n");
> +}
> +
> +static const struct drm_plane_helper_funcs vpg_plane_helper_funcs = {
> +	.atomic_update = vpg_plane_atomic_update,
> +	.atomic_disable = vpg_plane_atomic_disable,
> +};
> +
> +static const struct drm_plane_funcs vpg_plane_funcs = {
> +	.update_plane = drm_atomic_helper_update_plane,
> +	.disable_plane = drm_atomic_helper_disable_plane,
> +	.destroy = drm_plane_cleanup,
> +	.reset = drm_atomic_helper_plane_reset,
> +	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
> +	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
> +};
> +
> +static void vpg_plane_destroy_all(struct drm_device *drm)
> +{
> +	struct drm_plane *plane, *plane_temp;
> +
> +	list_for_each_entry_safe(plane, plane_temp,
> +				 &drm->mode_config.plane_list, head)
> +		drm_plane_cleanup(plane);
> +}
> +
> +static int vpg_crtc_init(struct drm_device *drm, struct drm_crtc *crtc)
> +{
> +	u32 formats[ARRAY_SIZE(supported_formats)], i;
> +	const u64 *modifiers = vpg_format_modifiers;
> +	unsigned long possible_crtcs = CRTC_MASK;
> +	struct device *drm_dev = drm->dev;
> +	struct drm_plane *plane;
> +	int ret;
> +
> +	plane = devm_kzalloc(drm_dev, sizeof(*plane), GFP_KERNEL);
> +	if (!plane)
> +		return -ENOMEM;
> +
> +	for (i = 0; i < ARRAY_SIZE(supported_formats); i++)
> +		formats[i] = supported_formats[i].fourcc;
> +
> +	ret = drm_universal_plane_init(drm, plane, possible_crtcs,
> +				       &vpg_plane_funcs, formats,
> +				       ARRAY_SIZE(formats), modifiers,
> +				       DRM_PLANE_TYPE_PRIMARY, NULL);
> +	if (ret)
> +		goto cleanup_planes;
> +
> +	drm_plane_helper_add(plane, &vpg_plane_helper_funcs);
> +
> +	DRM_DEBUG_DRIVER("plane:%d created\n", plane->base.id);
> +
> +	ret = drm_crtc_init_with_planes(drm, crtc, plane, NULL,
> +					&vpg_crtc_funcs, "DSI-VPG");
> +	if (ret) {
> +		DRM_DEBUG_DRIVER("Failed initialize CRTC\n");
> +		goto cleanup_crtc;
> +	}
> +
> +	drm_crtc_helper_add(crtc, &vpg_crtc_helper_funcs);
> +
> +	DRM_DEBUG_DRIVER("CRTC:%d created\n", crtc->base.id);
> +
> +	return ret;
> +
> +cleanup_crtc:
> +	drm_crtc_cleanup(crtc);
> +cleanup_planes:
> +	vpg_plane_destroy_all(drm);
> +	return ret;
> +}
> +
> +int vpg_load(struct drm_device *drm)
> +{
> +	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
> +	struct drm_bridge *bridge[MAX_ENDPOINTS] = {NULL};
> +	struct drm_panel *panel[MAX_ENDPOINTS] = {NULL};
> +	struct platform_device *pdev = ipk->platform;
> +	struct device_node *np = drm->dev->of_node;
> +	int ret, i, endpoint_not_ready = -ENODEV;
> +	struct reset_control *vpg_rst, *mmcm_rst;
> +	struct device *dev = drm->dev;
> +	struct vpg_device *vpg;
> +	struct drm_crtc *crtc;
> +	struct resource *res;
> +
> +	DRM_DEBUG_DRIVER("\n");
> +
> +	vpg = devm_kzalloc(&pdev->dev, sizeof(*vpg), GFP_KERNEL);
> +	if (!vpg)
> +		return -ENOMEM;
> +
> +	ipk->vpg = vpg;
> +
> +	/* Get endpoints if any */
> +	for (i = 0; i < MAX_ENDPOINTS; i++) {
> +		ret = drm_of_find_panel_or_bridge(np, 0, i, &panel[i],
> +						  &bridge[i]);
> +		if (ret == -EPROBE_DEFER)
> +			return ret;
> +		else if (!ret)
> +			endpoint_not_ready = 0;
> +	}
> +
> +	if (endpoint_not_ready)
> +		return endpoint_not_ready;
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vpg");
> +	vpg->base = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(vpg->base)) {
> +		drm_err(drm, "Unable to get VPG registers\n");
> +		ret = PTR_ERR(vpg->base);
> +		goto err;
> +	}
> +
> +	vpg_rst = devm_reset_control_get_optional_exclusive(dev, "vpg");
> +	if (IS_ERR(vpg_rst)) {
> +		ret = PTR_ERR(vpg_rst);
> +		if (ret != -EPROBE_DEFER)
> +			drm_err(drm, "Unable to get reset control: %d\n", ret);
> +		goto err;
> +	}
> +
> +	if (vpg_rst) {
> +		reset_control_assert(vpg_rst);
> +		usleep_range(10, 20);
> +		reset_control_deassert(vpg_rst);
> +	}
> +
> +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mmcm");
> +	vpg->base_mmcm = devm_ioremap_resource(dev, res);
> +	if (IS_ERR(vpg->base)) {
> +		ret = PTR_ERR(vpg->base);
> +		goto err;
> +	}
> +
> +	mmcm_rst = devm_reset_control_get_optional_exclusive(dev, "mmcm");
> +	if (IS_ERR(mmcm_rst)) {
> +		ret = PTR_ERR(mmcm_rst);
> +		if (ret != -EPROBE_DEFER)
> +			drm_err(drm, "Unable to get reset control: %d\n", ret);
> +		goto err;
> +	}
> +
> +	if (mmcm_rst) {
> +		reset_control_assert(mmcm_rst);
> +		usleep_range(10, 20);
> +		reset_control_deassert(mmcm_rst);
> +	}
> +
> +	/* Add endpoints panels or bridges if any */
> +	for (i = 0; i < MAX_ENDPOINTS; i++) {
> +		if (panel[i]) {
> +			bridge[i] = drm_panel_bridge_add_typed(panel[i],
> +							DRM_MODE_CONNECTOR_DSI);
> +			if (IS_ERR(bridge[i])) {
> +				ret = PTR_ERR(bridge[i]);
> +				goto err;
> +			}
> +		}
> +
> +		if (bridge[i]) {
> +			ret = ipk_encoder_init(drm, bridge[i]);
> +			if (ret)
> +				goto err;
> +		}
> +	}
> +
> +	crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL);
> +	if (!crtc) {
> +		ret = -ENOMEM;
> +		goto err;
> +	}
> +
> +	ret = vpg_crtc_init(drm, crtc);
> +	if (ret)
> +		goto err;
> +
> +	return ret;
> +
> +err:
> +	for (i = 0; i < MAX_ENDPOINTS; i++)
> +		drm_panel_bridge_remove(bridge[i]);
> +
> +	return ret;
> +}
> +
> +void vpg_unload(struct drm_device *drm)
> +{
> +	int i;
> +
> +	DRM_DEBUG_DRIVER("\n");
> +	for (i = 0; i < MAX_ENDPOINTS; i++)
> +		drm_of_panel_bridge_remove(drm->dev->of_node, 0, i);
> +}
> diff --git a/drivers/gpu/drm/ipk/dw-vpg.h b/drivers/gpu/drm/ipk/dw-vpg.h
> new file mode 100644
> index 0000000..869ccc4
> --- /dev/null
> +++ b/drivers/gpu/drm/ipk/dw-vpg.h
> @@ -0,0 +1,55 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
> + * Synopsys DesignWare VPG
> + */
> +
> +#ifndef _VPG_H_
> +#define _VPG_H_
> +
> +#define DW_VPG_PANX				0x00
> +#define DW_VPG_PANY				0x04
> +#define DW_VPG_PHASE_SEL			0x08
> +#define DW_VPG_SEL_DATA				0x0C
> +#define DW_VPG_SWAP_BAY				0x10
> +#define DW_VPG_TESTMODE				0x14
> +#define DW_VPG_ZOOM_OUT				0x18
> +#define DW_VPG_EDPICTRL				0x1C
> +#define DW_VPG_CANVAS				0x20
> +#define DW_VPG_HBP_HSA				0x24
> +#define DW_VPG_LINE_TIME			0x28
> +#define DW_VPG_VBP_VFP_VSA			0x2C
> +#define DW_VPG_DPICOLORMODE			0x30
> +#define DW_VPG_VERSION				0x34
> +#define DW_VPG_EXTRNAL_EDPI			0x38
> +#define DW_VPG_PVO_CEAVID			0x3C
> +#define DW_VPG_PAN_VALID			0x40
> +#define DW_VPG_TECONTROL			0x44
> +#define DW_VPG_IMAGE_START_POSITION_X		0x4C
> +#define DW_VPG_IMAGE_START_POSITION_Y		0x50
> +#define DW_VPG_IMAGE_Y				0x54
> +#define DW_VPG_IMAGE_WORD_COUNT			0x58
> +#define DW_VPG_IMAGE_DATA_TYPE			0x5C
> +#define DW_VPG_LINE_PIXS_CNT			0x60
> +#define DW_VPG_FRAME_LINES_CNT			0x64
> +
> +#define DW_MMCM_MASK				0x1000
> +#define DW_MMCM_CLKOUT0_REG_1			0x20
> +#define DW_MMCM_CLKOUT0_REG_2			0x24
> +#define DW_MMCM_CLKFBOUT_REG_1			0x50
> +#define DW_MMCM_CLKFBOUT_REG_2			0x54
> +#define DW_MMCM_POWER_REG			0xA0
> +
> +#define DW_VPG_ZOOM_OUT_STEP	1
> +#define DW_VPG_PAN_STEP	40
> +#define DW_VPG_FREQ	25000	/* [KHz] */
> +#define DW_VPG_V_FRONT_PORCH	2
> +#define DW_VPG_V_BACK_PORCH	2
> +#define DW_VPG_V_SYNC	2
> +#define SCREEN_SIZE_X	800
> +#define SCREEN_SIZE_Y	480
> +
> +int vpg_load(struct drm_device *ddev);
> +void vpg_unload(struct drm_device *ddev);
> +
> +#endif /* _VPG_H_ */
> -- 
> 2.7.4
> 
> _______________________________________________
> dri-devel mailing list
> dri-devel@lists.freedesktop.org
> https://lists.freedesktop.org/mailman/listinfo/dri-devel
Angelo Ribeiro April 8, 2020, 9:56 a.m. UTC | #3
Hi Daniel,

From: Daniel Vetter <daniel@ffwll.ch>
Date: Tue, Apr 07, 2020 at 09:13:33

> On Mon, Apr 06, 2020 at 03:24:12PM +0200, Angelo Ribeiro wrote:
> > Add support for Synopsys DesignWare VPG (Video Pattern Generator) and
> > DRM driver for Synopsys DesignWare DSI Host IPK solution.
> > 
> > Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
> > Cc: Maxime Ripard <mripard@kernel.org>
> > Cc: David Airlie <airlied@linux.ie>
> > Cc: Daniel Vetter <daniel@ffwll.ch>
> > Cc: Gustavo Pimentel <gustavo.pimentel@synopsys.com>
> > Cc: Joao Pinto <jpinto@synopsys.com>
> > Signed-off-by: Angelo Ribeiro <angelo.ribeiro@synopsys.com>
> > ---
> >  drivers/gpu/drm/Kconfig      |   2 +
> >  drivers/gpu/drm/Makefile     |   1 +
> >  drivers/gpu/drm/ipk/Kconfig  |  13 +
> >  drivers/gpu/drm/ipk/Makefile |   6 +
> >  drivers/gpu/drm/ipk/dw-drv.c | 189 +++++++++++++++
> >  drivers/gpu/drm/ipk/dw-ipk.h |  30 +++
> >  drivers/gpu/drm/ipk/dw-vpg.c | 559 +++++++++++++++++++++++++++++++++++++++++++
> >  drivers/gpu/drm/ipk/dw-vpg.h |  55 +++++
> >  8 files changed, 855 insertions(+)
> >  create mode 100644 drivers/gpu/drm/ipk/Kconfig
> >  create mode 100644 drivers/gpu/drm/ipk/Makefile
> >  create mode 100644 drivers/gpu/drm/ipk/dw-drv.c
> >  create mode 100644 drivers/gpu/drm/ipk/dw-ipk.h
> >  create mode 100644 drivers/gpu/drm/ipk/dw-vpg.c
> >  create mode 100644 drivers/gpu/drm/ipk/dw-vpg.h
> > 
> > diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
> > index 4359497..29ea1d1 100644
> > --- a/drivers/gpu/drm/Kconfig
> > +++ b/drivers/gpu/drm/Kconfig
> > @@ -388,6 +388,8 @@ source "drivers/gpu/drm/mcde/Kconfig"
> >  
> >  source "drivers/gpu/drm/tidss/Kconfig"
> >  
> > +source "drivers/gpu/drm/ipk/Kconfig"
> > +
> >  # Keep legacy drivers last
> >  
> >  menuconfig DRM_LEGACY
> > diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
> > index 183c600..5bcc1c1 100644
> > --- a/drivers/gpu/drm/Makefile
> > +++ b/drivers/gpu/drm/Makefile
> > @@ -125,3 +125,4 @@ obj-$(CONFIG_DRM_PANFROST) += panfrost/
> >  obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
> >  obj-$(CONFIG_DRM_MCDE) += mcde/
> >  obj-$(CONFIG_DRM_TIDSS) += tidss/
> > +obj-$(CONFIG_DRM_IPK) += ipk/
> > diff --git a/drivers/gpu/drm/ipk/Kconfig b/drivers/gpu/drm/ipk/Kconfig
> > new file mode 100644
> > index 0000000..1f87444
> > --- /dev/null
> > +++ b/drivers/gpu/drm/ipk/Kconfig
> > @@ -0,0 +1,13 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +config DRM_IPK
> > +	tristate "DRM Support for Synopsys DesignWare IPK DSI"
> > +	depends on DRM
> > +	select DRM_KMS_HELPER
> > +	select DRM_GEM_CMA_HELPER
> > +	select DRM_KMS_CMA_HELPER
> > +	select DRM_PANEL_BRIDGE
> > +	select VIDEOMODE_HELPERS
> > +	help
> > +	  Enable support for the Synopsys DesignWare DRM DSI.
> > +	  To compile this driver as a module, choose M here: the module
> > +	  will be called ipk-drm.
> > diff --git a/drivers/gpu/drm/ipk/Makefile b/drivers/gpu/drm/ipk/Makefile
> > new file mode 100644
> > index 0000000..51d2774
> > --- /dev/null
> > +++ b/drivers/gpu/drm/ipk/Makefile
> > @@ -0,0 +1,6 @@
> > +# SPDX-License-Identifier: GPL-2.0-only
> > +ipk-drm-y := \
> > +	dw-drv.o \
> > +	dw-vpg.o
> > +
> > +obj-$(CONFIG_DRM_IPK) += ipk-drm.o
> > diff --git a/drivers/gpu/drm/ipk/dw-drv.c b/drivers/gpu/drm/ipk/dw-drv.c
> > new file mode 100644
> > index 0000000..6205f1c
> > --- /dev/null
> > +++ b/drivers/gpu/drm/ipk/dw-drv.c
> > @@ -0,0 +1,189 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
> > + * Synopsys DesignWare MIPI DSI DRM driver
> > + *
> > + * Author: Angelo Ribeiro <angelo.ribeiro@synopsys.com>
> > + */
> > +
> > +#include <linux/component.h>
> > +#include <linux/module.h>
> > +#include <linux/of_platform.h>
> > +#include <linux/clk.h>
> > +#include <drm/drm_crtc_helper.h>
> > +#include <drm/drm_fb_cma_helper.h>
> > +#include <drm/drm_fb_helper.h>
> > +#include <drm/drm_gem_cma_helper.h>
> > +#include <drm/drm_gem_framebuffer_helper.h>
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_bridge.h>
> > +#include <drm/drm_drv.h>
> > +#include <drm/drm_probe_helper.h>
> > +
> > +#include "dw-ipk.h"
> > +#include "dw-vpg.h"
> > +
> > +static const struct drm_mode_config_funcs dw_ipk_drm_modecfg_funcs = {
> > +	.fb_create = drm_gem_fb_create_with_dirty,
> > +	.atomic_check = drm_atomic_helper_check,
> > +	.atomic_commit = drm_atomic_helper_commit,
> > +};
> > +
> > +static int dw_ipk_load(struct drm_device *drm)
> > +{
> > +	int ret;
> > +
> > +	drm_mode_config_init(drm);
> > +
> > +	drm->mode_config.min_width = 0;
> > +	drm->mode_config.min_height = 0;
> > +
> > +	/* To handle orientation */
> > +	drm->mode_config.max_width = 2048;
> > +	drm->mode_config.max_height = 2048;
> > +
> > +	drm->mode_config.funcs = &dw_ipk_drm_modecfg_funcs;
> > +
> > +	/* TODO
> > +	 * Optional framebuffer memory resources allocation
> > +	 */
> > +
> > +	ret = vpg_load(drm);
> > +	if (ret)
> > +		return ret;
> > +
> > +	/* Calls all the crtc's, encoder's and connector's reset */
> > +	drm_mode_config_reset(drm);
> > +
> > +	/* Initialize and enable output polling */
> > +	drm_kms_helper_poll_init(drm);
> > +
> > +	return ret;
> > +}
> > +
> > +static void dw_ipk_unload(struct drm_device *drm)
> > +{
> > +	DRM_DEBUG_DRIVER("\n");
> > +
> > +	drm_kms_helper_poll_fini(drm);
> > +	vpg_unload(drm);
> > +}
> > +
> > +DEFINE_DRM_GEM_CMA_FOPS(ipk_drm_driver_fops);
> > +
> > +static int ipk_gem_cma_dumb_create(struct drm_file *file,
> > +				   struct drm_device *dev,
> > +				   struct drm_mode_create_dumb *args)
> > +{
> > +	unsigned int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
> > +	int err;
> > +
> > +	/*
> > +	 * In order to optimize data transfer, pitch is aligned on
> > +	 * 128 bytes, height is aligned on 4 bytes
> > +	 */
> > +	args->pitch = roundup(min_pitch, 128);
> > +	args->height = roundup(args->height, 4);
> > +
> > +	err = drm_gem_cma_dumb_create_internal(file, dev, args);
> > +	if (err < 0)
> > +		drm_err(dev, "dumb_create failed %d\n", err);
> > +
> > +	return err;
> > +}
> > +
> > +static struct drm_driver dw_ipk_drm_driver = {
> > +	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
> > +	.name = "dw_ipk",
> > +	.desc = "DW IPK DSI Host Controller",
> > +	.date = "20190725",
> > +	.major = 1,
> > +	.minor = 0,
> > +	.patchlevel = 0,
> > +	.fops = &ipk_drm_driver_fops,
> > +	.dumb_create = ipk_gem_cma_dumb_create,
> > +	.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
> > +	.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
> > +	.gem_free_object_unlocked = drm_gem_cma_free_object,
> > +	.gem_vm_ops = &drm_gem_cma_vm_ops,
> > +	.gem_prime_export = drm_gem_prime_export,
> > +	.gem_prime_import = drm_gem_prime_import,
> > +	.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
> > +	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
> > +	.gem_prime_vmap = drm_gem_cma_prime_vmap,
> > +	.gem_prime_vunmap = drm_gem_cma_prime_vunmap,
> > +	.gem_prime_mmap = drm_gem_cma_prime_mmap,
> > +};
> > +
> > +static int dw_ipk_drm_platform_probe(struct platform_device *pdev)
> > +{
> > +	struct device *dev = &pdev->dev;
> > +	struct drm_device *drm;
> > +	struct ipk_device *ipk;
> > +	int ret;
> > +
> > +	DRM_DEBUG_DRIVER("\n");
> > +
> > +	ipk = kzalloc(sizeof(*ipk), GFP_KERNEL);
> > +	if (!ipk)
> > +		return -ENOMEM;
> > +
> > +	ipk->platform = pdev;
> > +	drm = &ipk->drm;
> > +
> > +	ret = drm_dev_init(&ipk->drm, &dw_ipk_drm_driver, dev);
> > +	if (ret) {
> > +		kfree(ipk);
> > +		return ret;
> > +	}
> 
> I'm reworking this right now, you're missing a call to
> drmm_add_final_kfree here. But I think even better would be to rebase on
> top of my patch here:
> 
> https://urldefense.proofpoint.com/v2/url?u=https-3A__patchwork.freedesktop.org_patch_359897_&d=DwIBAg&c=DPL6_X_6JkXFx7AXWqB0tg&r=-1lJkMi2j8IE8jpsNWO99o9zJFwyv1Mbjj3vJBR__i0&m=knxu5BXoiypjzq6gobgbIqbrpnkIlWKYlpnGtKMLvnw&s=sUMf2MJZh_cU3VOmu_TXUwCftlGMtMzkJgkPTExhq8I&e= 
> 
> Which would allow you to drop a bunch of code from your load/unload code.
> See that patch series for a lot of example conversions. I plan to land
> that patch pretty soon in drm-misc-next so shouldn't hold up merging your
> driver.
> -Daniel

Thank you for the revision, I will do it.

> 
> > +
> > +	platform_set_drvdata(pdev, drm);
> > +
> > +	ret = dw_ipk_load(drm);
> > +	if (ret)
> > +		goto err_put;
> > +
> > +	ret = drm_dev_register(drm, 0);
> > +	if (ret)
> > +		goto err_put;
> > +
> > +	drm_fbdev_generic_setup(drm, 24);
> > +
> > +	return ret;
> > +
> > +err_put:
> > +	drm_dev_put(drm);
> > +	return ret;
> > +}
> > +
> > +static int dw_ipk_drm_platform_remove(struct platform_device *pdev)
> > +{
> > +	struct drm_device *drm = platform_get_drvdata(pdev);
> > +
> > +	drm_dev_unregister(drm);
> > +	dw_ipk_unload(drm);
> > +	drm_dev_put(drm);
> > +
> > +	return 0;
> > +}
> > +
> > +static const struct of_device_id dw_ipk_dt_ids[] = {
> > +	{.compatible = "snps,dw-ipk-vpg"},
> > +	{ /* sentinel */ }
> > +};
> > +MODULE_DEVICE_TABLE(of, dw_ipk_dt_ids);
> > +
> > +static struct platform_driver dw_ipk_drm_platform_driver = {
> > +	.probe = dw_ipk_drm_platform_probe,
> > +	.remove = dw_ipk_drm_platform_remove,
> > +	.driver = {
> > +		.name = "dw-ipk-drm",
> > +		.of_match_table = dw_ipk_dt_ids,
> > +	},
> > +};
> > +
> > +module_platform_driver(dw_ipk_drm_platform_driver);
> > +
> > +MODULE_DESCRIPTION("Synopsys DesignWare IPK DRM driver");
> > +MODULE_LICENSE("GPL v2");
> > +MODULE_AUTHOR("Angelo Ribeiro <angelo.ribeiro@synopsys.com>");
> > diff --git a/drivers/gpu/drm/ipk/dw-ipk.h b/drivers/gpu/drm/ipk/dw-ipk.h
> > new file mode 100644
> > index 0000000..4abb6dd
> > --- /dev/null
> > +++ b/drivers/gpu/drm/ipk/dw-ipk.h
> > @@ -0,0 +1,30 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
> > + * Synopsys DesignWare MIPI DSI Controller
> > + */
> > +
> > +#ifndef _DW_IPK_H
> > +#define _DW_IPK_H
> > +
> > +#include "drm/drm_device.h"
> > +#include <drm/bridge/dw_mipi_dsi.h>
> > +
> > +struct ipk_pipeline {
> > +	struct drm_framebuffer	*fb;
> > +	struct drm_crtc		crtc;
> > +	struct drm_plane	*plane;
> > +	struct drm_bridge	*bridge;
> > +};
> > +
> > +struct ipk_device {
> > +	struct drm_device drm;
> > +	struct platform_device *platform;
> > +	struct ipk_pipeline pipeline;
> > +	struct vpg_device *vpg;
> > +};
> > +
> > +#define drm_device_to_ipk_device(target) \
> > +	container_of(target, struct ipk_device, drm)
> > +
> > +#endif /* _DW_IPK_H */
> > diff --git a/drivers/gpu/drm/ipk/dw-vpg.c b/drivers/gpu/drm/ipk/dw-vpg.c
> > new file mode 100644
> > index 0000000..feb3e90
> > --- /dev/null
> > +++ b/drivers/gpu/drm/ipk/dw-vpg.c
> > @@ -0,0 +1,559 @@
> > +// SPDX-License-Identifier: GPL-2.0
> > +/*
> > + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
> > + * Synopsys DesignWare MIPI DSI controller
> > + *
> > + * Author: Angelo Ribeiro <angelor@synopsys.com>
> > + * Author: Luis Oliveira <luis.oliveira@synopsys.com>
> > + */
> > +
> > +#include <linux/clk.h>
> > +#include <linux/component.h>
> > +#include <linux/delay.h>
> > +#include <linux/interrupt.h>
> > +#include <linux/module.h>
> > +#include <linux/of_address.h>
> > +#include <linux/of_graph.h>
> > +#include <linux/platform_device.h>
> > +#include <linux/reset.h>
> > +
> > +#include <drm/drm_atomic.h>
> > +#include <drm/drm_atomic_helper.h>
> > +#include <drm/drm_bridge.h>
> > +#include <drm/drm_device.h>
> > +#include <drm/drm_fb_cma_helper.h>
> > +#include <drm/drm_fourcc.h>
> > +#include <drm/drm_gem_cma_helper.h>
> > +#include <drm/drm_of.h>
> > +#include <drm/drm_vblank.h>
> > +#include <linux/platform_data/simplefb.h>
> > +#include <video/videomode.h>
> > +
> > +#include "dw-ipk.h"
> > +#include "dw-vpg.h"
> > +
> > +#define NB_CRTC 1
> > +#define CRTC_MASK GENMASK(NB_CRTC - 1, 0)
> > +#define MAX_ENDPOINTS 1
> > +
> > +struct vpg_device {
> > +	void __iomem *base;
> > +	void __iomem *base_mmcm;
> > +};
> > +
> > +enum vpg_pixel_fmt {
> > +	VIF_COLOR_CODE_16BIT_CONFIG1 = 0,
> > +	VIF_COLOR_CODE_16BIT_CONFIG2,
> > +	VIF_COLOR_CODE_16BIT_CONFIG3,
> > +	VIF_COLOR_CODE_18BIT_CONFIG1,
> > +	VIF_COLOR_CODE_18BIT_CONFIG2,
> > +	VIF_COLOR_CODE_24BIT,
> > +	VIF_COLOR_CODE_MAX
> > +};
> > +
> > +static enum vpg_pixel_fmt to_vpg_pixel_format(u32 drm_fmt)
> > +{
> > +	enum vpg_pixel_fmt pf;
> > +
> > +	switch (drm_fmt) {
> > +	case DRM_FORMAT_RGB888:
> > +		pf = VIF_COLOR_CODE_24BIT;
> > +		break;
> > +	case DRM_FORMAT_RGB565:
> > +		pf = VIF_COLOR_CODE_16BIT_CONFIG1;
> > +		break;
> > +	default:
> > +		pf = VIF_COLOR_CODE_MAX;
> > +		break;
> > +	}
> > +	return pf;
> > +}
> > +
> > +static struct simplefb_format supported_formats[] = {
> > +	{"r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, DRM_FORMAT_RGB565},
> > +	{"r8g8b8", 24, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_RGB888},
> > +	{"x8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_XRGB8888},
> > +};
> > +
> > +static const u64 vpg_format_modifiers[] = {
> > +	DRM_FORMAT_MOD_LINEAR,
> > +	DRM_FORMAT_MOD_INVALID
> > +};
> > +
> > +static const struct drm_encoder_funcs ipk_encoder_funcs = {
> > +	.destroy = drm_encoder_cleanup,
> > +};
> > +
> > +static int ipk_encoder_init(struct drm_device *drm,
> > +			    struct drm_bridge *bridge)
> > +{
> > +	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
> > +	struct drm_encoder *encoder;
> > +	int ret;
> > +
> > +	encoder = devm_kzalloc(drm->dev, sizeof(*encoder), GFP_KERNEL);
> > +	if (!encoder)
> > +		return -ENOMEM;
> > +
> > +	/* Only one CRTC can be attached in IPK_DSI */
> > +	encoder->possible_crtcs = CRTC_MASK;
> > +	encoder->possible_clones = 0;
> > +
> > +	ret = drm_encoder_init(drm, encoder, &ipk_encoder_funcs,
> > +			       DRM_MODE_ENCODER_DPI, NULL);
> > +	if (ret)
> > +		return ret;
> > +
> > +	ret = drm_bridge_attach(encoder, bridge, NULL, 0);
> > +	if (ret)
> > +		goto err_attach;
> > +
> > +	/* Add to the internal struct */
> > +	ipk->pipeline.bridge = bridge;
> > +
> > +	DRM_DEBUG_DRIVER("Num of connectors = %d\n",
> > +			 drm->mode_config.num_connector);
> > +	DRM_DEBUG_DRIVER("Bridge encoder:%d created\n", encoder->base.id);
> > +
> > +	return ret;
> > +
> > +err_attach:
> > +	drm_encoder_cleanup(encoder);
> > +	return ret;
> > +}
> > +
> > +/*
> > + * CRTC
> > + */
> > +static u32 vpg_read(struct vpg_device *dev, u32 reg)
> > +{
> > +	return readl(dev->base + reg);
> > +}
> > +
> > +static void vpg_write(struct vpg_device *dev, u32 reg, u32 val)
> > +{
> > +	writel(val, dev->base + reg);
> > +}
> > +
> > +static void vpg_write_part(struct vpg_device *dev, u32 reg,
> > +			   u32 val, u8 shift, u8 width)
> > +{
> > +	u32 mask = (1 << width) - 1;
> > +
> > +	vpg_write(dev, reg, (vpg_read(dev, reg) &
> > +		  ~(mask << shift)) | ((val & mask) << shift));
> > +}
> > +
> > +static u32 mmcm_read(struct vpg_device *dev, u32 reg)
> > +{
> > +	return readl(dev->base_mmcm + reg);
> > +}
> > +
> > +static void mmcm_write(struct vpg_device *dev, u32 reg, u32 value)
> > +{
> > +	writel(value, dev->base_mmcm + reg);
> > +}
> > +
> > +static void mmcm_configure(struct vpg_device *vpg, struct videomode *v_mode)
> > +{
> > +	int div,  mul, cur_freq, cur_deviat, temp;
> > +	int out_freq = v_mode->pixelclock / 1000;
> > +	int in_freq = 100000;
> > +	int deviat = 1000;	/* Deviation from desired master clock */
> > +	int best_div = 0;	/* Divider for PLL */
> > +	int best_mul = 0;	/* Multiplier for PLL */
> > +
> > +	DRM_DEBUG_DRIVER("out_freq = %d\n", out_freq);
> > +
> > +	for (div = 1; div < 150 && deviat > 50; div++) {
> > +		for (mul = 1; mul <= 10 && deviat > 50; mul++) {
> > +			cur_freq = in_freq * mul / div;
> > +			cur_deviat = abs(out_freq - cur_freq);
> > +			if (cur_deviat < deviat) {
> > +				best_div = div;
> > +				best_mul = mul;
> > +				deviat = cur_deviat;
> > +			}
> > +		}
> > +	}
> > +
> > +	DRM_DEBUG_DRIVER("deviat = %d\n best_div = %d\n best_mul = %d\n",
> > +			 deviat, best_div, best_mul);
> > +
> > +	temp = mmcm_read(vpg, DW_MMCM_CLKOUT0_REG_1) & DW_MMCM_MASK;
> > +	mmcm_write(vpg, DW_MMCM_CLKOUT0_REG_1, temp | (best_div << 6) |
> > +		   best_div);
> > +
> > +	temp = mmcm_read(vpg, DW_MMCM_CLKFBOUT_REG_1) & DW_MMCM_MASK;
> > +	mmcm_write(vpg, DW_MMCM_CLKFBOUT_REG_1, temp | (best_mul << 6) |
> > +		   best_mul);
> > +}
> > +
> > +static void vpg_set_fps(struct vpg_device *vpg,
> > +			const struct drm_display_mode *mode)
> > +{
> > +	/* IF_Freq is in KHz */
> > +	u32 line_time = (1000 * DW_VPG_FREQ) / (mode->vrefresh * mode->vtotal);
> > +
> > +	vpg_write(vpg, DW_VPG_LINE_TIME, line_time);
> > +}
> > +
> > +static void vpg_select_pattern(struct vpg_device *vpg, unsigned int value)
> > +{
> > +	vpg_write(vpg, DW_VPG_TESTMODE, value);
> > +}
> > +
> > +static void vpg_vertical_set(struct vpg_device *vpg, struct videomode *v_mode)
> > +{
> > +	vpg_write_part(vpg, DW_VPG_CANVAS, v_mode->vactive, 16, 16);
> > +	vpg_write_part(vpg, DW_VPG_VBP_VFP_VSA, v_mode->vsync_len, 0, 8);
> > +	vpg_write_part(vpg, DW_VPG_VBP_VFP_VSA, v_mode->vback_porch, 20, 12);
> > +	vpg_write_part(vpg, DW_VPG_VBP_VFP_VSA, v_mode->vfront_porch, 8, 11);
> > +}
> > +
> > +static void vpg_horizontal_set(struct vpg_device *vpg, struct videomode *v_mode)
> > +{
> > +	vpg_write_part(vpg, DW_VPG_CANVAS, v_mode->hactive, 0, 16);
> > +	vpg_write_part(vpg, DW_VPG_HBP_HSA, v_mode->hsync_len, 0, 16);
> > +	vpg_write_part(vpg, DW_VPG_HBP_HSA, v_mode->hback_porch, 16, 16);
> > +}
> > +
> > +static ssize_t show_pattern(struct device *dev,
> > +			    struct device_attribute *attr, char *buf)
> > +{
> > +	struct drm_device *drm = dev_get_drvdata(dev);
> > +	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
> > +	struct vpg_device *vpg = ipk->vpg;
> > +
> > +	return sprintf(buf, "%d\n", vpg_read(vpg, DW_VPG_TESTMODE));
> > +}
> > +
> > +static ssize_t store_pattern(struct device *dev, struct device_attribute *attr,
> > +			     const char *buf, size_t count)
> > +{
> > +	struct drm_device *drm = dev_get_drvdata(dev);
> > +	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
> > +	struct vpg_device *vpg = ipk->vpg;
> > +	long pattern;
> > +	int ret;
> > +
> > +	ret = kstrtol(buf, 0, &pattern);
> > +	if (ret < 0)
> > +		return ret;
> > +
> > +	vpg_select_pattern(vpg, pattern);
> > +
> > +	return count;
> > +}
> > +
> > +static struct kobj_attribute vpg_pattern = __ATTR(pattern, 0660,
> > +						  (void *)show_pattern,
> > +						  (void *)store_pattern);
> > +
> > +static struct attribute *vpg_attr[] = {
> > +	&vpg_pattern.attr,
> > +	NULL,
> > +};
> > +
> > +static const struct attribute_group vpg_attr_group = {
> > +	.attrs = vpg_attr,
> > +};
> > +
> > +static void vpg_crtc_mode_set_nofb(struct drm_crtc *crtc)
> > +{
> > +	struct ipk_device *ipk = drm_device_to_ipk_device(crtc->dev);
> > +	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
> > +	struct vpg_device *vpg = ipk->vpg;
> > +	struct videomode v_mode;
> > +
> > +	DRM_DEBUG_DRIVER("\n");
> > +
> > +	drm_display_mode_to_videomode(mode, &v_mode);
> > +
> > +	DRM_DEBUG_DRIVER("CRTC:%d mode:%s\n", crtc->base.id, mode->name);
> > +	DRM_DEBUG_DRIVER("Video mode: %dx%d", v_mode.hactive, v_mode.vactive);
> > +	DRM_DEBUG_DRIVER(" hfp %d hbp %d hsl %d vfp %d vbp %d vsl %d\n",
> > +			 v_mode.hfront_porch, v_mode.hback_porch,
> > +			 v_mode.hsync_len, v_mode.vfront_porch,
> > +			 v_mode.vback_porch, v_mode.vsync_len);
> > +
> > +	mmcm_configure(vpg, &v_mode);
> > +
> > +	vpg_write(vpg, DW_VPG_SEL_DATA, 0x01);
> > +	vpg_write(vpg, DW_VPG_SEL_DATA, 0x03);
> > +
> > +	vpg_horizontal_set(vpg, &v_mode);
> > +
> > +	vpg_vertical_set(vpg, &v_mode);
> > +
> > +	vpg_set_fps(vpg, mode);
> > +
> > +	vpg_select_pattern(vpg, 0);
> > +}
> > +
> > +static void vpg_crtc_atomic_begin(struct drm_crtc *crtc,
> > +				  struct drm_crtc_state *state)
> > +{
> > +	struct drm_pending_vblank_event *event = crtc->state->event;
> > +
> > +	if (event) {
> > +		crtc->state->event = NULL;
> > +		spin_lock_irq(&crtc->dev->event_lock);
> > +		drm_crtc_send_vblank_event(crtc, event);
> > +		spin_unlock_irq(&crtc->dev->event_lock);
> > +	}
> > +}
> > +
> > +static struct drm_crtc_helper_funcs vpg_crtc_helper_funcs = {
> > +	.mode_set_nofb = vpg_crtc_mode_set_nofb,
> > +	.atomic_begin = vpg_crtc_atomic_begin,
> > +};
> > +
> > +static int vpg_crtc_late_register(struct drm_crtc *crtc)
> > +{
> > +	struct ipk_device *ipk = drm_device_to_ipk_device(crtc->dev);
> > +	struct platform_device *pdev = ipk->platform;
> > +	int ret;
> > +
> > +	ret = sysfs_create_group(&pdev->dev.kobj, &vpg_attr_group);
> > +	if (ret)
> > +		drm_err(crtc->dev, "Failed to create sysfs entries");
> > +
> > +	return ret;
> > +}
> > +
> > +static void vpg_crtc_early_unregister(struct drm_crtc *crtc)
> > +{
> > +	struct ipk_device *ipk = drm_device_to_ipk_device(crtc->dev);
> > +	struct platform_device *pdev = ipk->platform;
> > +
> > +	sysfs_remove_group(&pdev->dev.kobj, &vpg_attr_group);
> > +}
> > +
> > +static const struct drm_crtc_funcs vpg_crtc_funcs = {
> > +	.reset = drm_atomic_helper_crtc_reset,
> > +	.destroy = drm_crtc_cleanup,
> > +	.set_config = drm_atomic_helper_set_config,
> > +	.page_flip = drm_atomic_helper_page_flip,
> > +	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
> > +	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
> > +	.late_register =  vpg_crtc_late_register,
> > +	.early_unregister = vpg_crtc_early_unregister
> > +};
> > +
> > +/*
> > + * Plane
> > + */
> > +static void vpg_plane_atomic_update(struct drm_plane *plane,
> > +				    struct drm_plane_state *old_state)
> > +{
> > +	struct ipk_device *ipk = drm_device_to_ipk_device(plane->dev);
> > +	struct drm_plane_state *state = plane->state;
> > +	struct drm_framebuffer *fb = state->fb;
> > +	struct vpg_device *vpg = ipk->vpg;
> > +	enum vpg_pixel_fmt pixel_format;
> > +
> > +	pixel_format = to_vpg_pixel_format(fb->format->format);
> > +
> > +	vpg_write(vpg, DW_VPG_DPICOLORMODE, pixel_format);
> > +}
> > +
> > +static void vpg_plane_atomic_disable(struct drm_plane *plane,
> > +				     struct drm_plane_state *oldstate)
> > +{
> > +	DRM_DEBUG_DRIVER("\n");
> > +}
> > +
> > +static const struct drm_plane_helper_funcs vpg_plane_helper_funcs = {
> > +	.atomic_update = vpg_plane_atomic_update,
> > +	.atomic_disable = vpg_plane_atomic_disable,
> > +};
> > +
> > +static const struct drm_plane_funcs vpg_plane_funcs = {
> > +	.update_plane = drm_atomic_helper_update_plane,
> > +	.disable_plane = drm_atomic_helper_disable_plane,
> > +	.destroy = drm_plane_cleanup,
> > +	.reset = drm_atomic_helper_plane_reset,
> > +	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
> > +	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
> > +};
> > +
> > +static void vpg_plane_destroy_all(struct drm_device *drm)
> > +{
> > +	struct drm_plane *plane, *plane_temp;
> > +
> > +	list_for_each_entry_safe(plane, plane_temp,
> > +				 &drm->mode_config.plane_list, head)
> > +		drm_plane_cleanup(plane);
> > +}
> > +
> > +static int vpg_crtc_init(struct drm_device *drm, struct drm_crtc *crtc)
> > +{
> > +	u32 formats[ARRAY_SIZE(supported_formats)], i;
> > +	const u64 *modifiers = vpg_format_modifiers;
> > +	unsigned long possible_crtcs = CRTC_MASK;
> > +	struct device *drm_dev = drm->dev;
> > +	struct drm_plane *plane;
> > +	int ret;
> > +
> > +	plane = devm_kzalloc(drm_dev, sizeof(*plane), GFP_KERNEL);
> > +	if (!plane)
> > +		return -ENOMEM;
> > +
> > +	for (i = 0; i < ARRAY_SIZE(supported_formats); i++)
> > +		formats[i] = supported_formats[i].fourcc;
> > +
> > +	ret = drm_universal_plane_init(drm, plane, possible_crtcs,
> > +				       &vpg_plane_funcs, formats,
> > +				       ARRAY_SIZE(formats), modifiers,
> > +				       DRM_PLANE_TYPE_PRIMARY, NULL);
> > +	if (ret)
> > +		goto cleanup_planes;
> > +
> > +	drm_plane_helper_add(plane, &vpg_plane_helper_funcs);
> > +
> > +	DRM_DEBUG_DRIVER("plane:%d created\n", plane->base.id);
> > +
> > +	ret = drm_crtc_init_with_planes(drm, crtc, plane, NULL,
> > +					&vpg_crtc_funcs, "DSI-VPG");
> > +	if (ret) {
> > +		DRM_DEBUG_DRIVER("Failed initialize CRTC\n");
> > +		goto cleanup_crtc;
> > +	}
> > +
> > +	drm_crtc_helper_add(crtc, &vpg_crtc_helper_funcs);
> > +
> > +	DRM_DEBUG_DRIVER("CRTC:%d created\n", crtc->base.id);
> > +
> > +	return ret;
> > +
> > +cleanup_crtc:
> > +	drm_crtc_cleanup(crtc);
> > +cleanup_planes:
> > +	vpg_plane_destroy_all(drm);
> > +	return ret;
> > +}
> > +
> > +int vpg_load(struct drm_device *drm)
> > +{
> > +	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
> > +	struct drm_bridge *bridge[MAX_ENDPOINTS] = {NULL};
> > +	struct drm_panel *panel[MAX_ENDPOINTS] = {NULL};
> > +	struct platform_device *pdev = ipk->platform;
> > +	struct device_node *np = drm->dev->of_node;
> > +	int ret, i, endpoint_not_ready = -ENODEV;
> > +	struct reset_control *vpg_rst, *mmcm_rst;
> > +	struct device *dev = drm->dev;
> > +	struct vpg_device *vpg;
> > +	struct drm_crtc *crtc;
> > +	struct resource *res;
> > +
> > +	DRM_DEBUG_DRIVER("\n");
> > +
> > +	vpg = devm_kzalloc(&pdev->dev, sizeof(*vpg), GFP_KERNEL);
> > +	if (!vpg)
> > +		return -ENOMEM;
> > +
> > +	ipk->vpg = vpg;
> > +
> > +	/* Get endpoints if any */
> > +	for (i = 0; i < MAX_ENDPOINTS; i++) {
> > +		ret = drm_of_find_panel_or_bridge(np, 0, i, &panel[i],
> > +						  &bridge[i]);
> > +		if (ret == -EPROBE_DEFER)
> > +			return ret;
> > +		else if (!ret)
> > +			endpoint_not_ready = 0;
> > +	}
> > +
> > +	if (endpoint_not_ready)
> > +		return endpoint_not_ready;
> > +
> > +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vpg");
> > +	vpg->base = devm_ioremap_resource(dev, res);
> > +	if (IS_ERR(vpg->base)) {
> > +		drm_err(drm, "Unable to get VPG registers\n");
> > +		ret = PTR_ERR(vpg->base);
> > +		goto err;
> > +	}
> > +
> > +	vpg_rst = devm_reset_control_get_optional_exclusive(dev, "vpg");
> > +	if (IS_ERR(vpg_rst)) {
> > +		ret = PTR_ERR(vpg_rst);
> > +		if (ret != -EPROBE_DEFER)
> > +			drm_err(drm, "Unable to get reset control: %d\n", ret);
> > +		goto err;
> > +	}
> > +
> > +	if (vpg_rst) {
> > +		reset_control_assert(vpg_rst);
> > +		usleep_range(10, 20);
> > +		reset_control_deassert(vpg_rst);
> > +	}
> > +
> > +	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mmcm");
> > +	vpg->base_mmcm = devm_ioremap_resource(dev, res);
> > +	if (IS_ERR(vpg->base)) {
> > +		ret = PTR_ERR(vpg->base);
> > +		goto err;
> > +	}
> > +
> > +	mmcm_rst = devm_reset_control_get_optional_exclusive(dev, "mmcm");
> > +	if (IS_ERR(mmcm_rst)) {
> > +		ret = PTR_ERR(mmcm_rst);
> > +		if (ret != -EPROBE_DEFER)
> > +			drm_err(drm, "Unable to get reset control: %d\n", ret);
> > +		goto err;
> > +	}
> > +
> > +	if (mmcm_rst) {
> > +		reset_control_assert(mmcm_rst);
> > +		usleep_range(10, 20);
> > +		reset_control_deassert(mmcm_rst);
> > +	}
> > +
> > +	/* Add endpoints panels or bridges if any */
> > +	for (i = 0; i < MAX_ENDPOINTS; i++) {
> > +		if (panel[i]) {
> > +			bridge[i] = drm_panel_bridge_add_typed(panel[i],
> > +							DRM_MODE_CONNECTOR_DSI);
> > +			if (IS_ERR(bridge[i])) {
> > +				ret = PTR_ERR(bridge[i]);
> > +				goto err;
> > +			}
> > +		}
> > +
> > +		if (bridge[i]) {
> > +			ret = ipk_encoder_init(drm, bridge[i]);
> > +			if (ret)
> > +				goto err;
> > +		}
> > +	}
> > +
> > +	crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL);
> > +	if (!crtc) {
> > +		ret = -ENOMEM;
> > +		goto err;
> > +	}
> > +
> > +	ret = vpg_crtc_init(drm, crtc);
> > +	if (ret)
> > +		goto err;
> > +
> > +	return ret;
> > +
> > +err:
> > +	for (i = 0; i < MAX_ENDPOINTS; i++)
> > +		drm_panel_bridge_remove(bridge[i]);
> > +
> > +	return ret;
> > +}
> > +
> > +void vpg_unload(struct drm_device *drm)
> > +{
> > +	int i;
> > +
> > +	DRM_DEBUG_DRIVER("\n");
> > +	for (i = 0; i < MAX_ENDPOINTS; i++)
> > +		drm_of_panel_bridge_remove(drm->dev->of_node, 0, i);
> > +}
> > diff --git a/drivers/gpu/drm/ipk/dw-vpg.h b/drivers/gpu/drm/ipk/dw-vpg.h
> > new file mode 100644
> > index 0000000..869ccc4
> > --- /dev/null
> > +++ b/drivers/gpu/drm/ipk/dw-vpg.h
> > @@ -0,0 +1,55 @@
> > +/* SPDX-License-Identifier: GPL-2.0 */
> > +/*
> > + * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
> > + * Synopsys DesignWare VPG
> > + */
> > +
> > +#ifndef _VPG_H_
> > +#define _VPG_H_
> > +
> > +#define DW_VPG_PANX				0x00
> > +#define DW_VPG_PANY				0x04
> > +#define DW_VPG_PHASE_SEL			0x08
> > +#define DW_VPG_SEL_DATA				0x0C
> > +#define DW_VPG_SWAP_BAY				0x10
> > +#define DW_VPG_TESTMODE				0x14
> > +#define DW_VPG_ZOOM_OUT				0x18
> > +#define DW_VPG_EDPICTRL				0x1C
> > +#define DW_VPG_CANVAS				0x20
> > +#define DW_VPG_HBP_HSA				0x24
> > +#define DW_VPG_LINE_TIME			0x28
> > +#define DW_VPG_VBP_VFP_VSA			0x2C
> > +#define DW_VPG_DPICOLORMODE			0x30
> > +#define DW_VPG_VERSION				0x34
> > +#define DW_VPG_EXTRNAL_EDPI			0x38
> > +#define DW_VPG_PVO_CEAVID			0x3C
> > +#define DW_VPG_PAN_VALID			0x40
> > +#define DW_VPG_TECONTROL			0x44
> > +#define DW_VPG_IMAGE_START_POSITION_X		0x4C
> > +#define DW_VPG_IMAGE_START_POSITION_Y		0x50
> > +#define DW_VPG_IMAGE_Y				0x54
> > +#define DW_VPG_IMAGE_WORD_COUNT			0x58
> > +#define DW_VPG_IMAGE_DATA_TYPE			0x5C
> > +#define DW_VPG_LINE_PIXS_CNT			0x60
> > +#define DW_VPG_FRAME_LINES_CNT			0x64
> > +
> > +#define DW_MMCM_MASK				0x1000
> > +#define DW_MMCM_CLKOUT0_REG_1			0x20
> > +#define DW_MMCM_CLKOUT0_REG_2			0x24
> > +#define DW_MMCM_CLKFBOUT_REG_1			0x50
> > +#define DW_MMCM_CLKFBOUT_REG_2			0x54
> > +#define DW_MMCM_POWER_REG			0xA0
> > +
> > +#define DW_VPG_ZOOM_OUT_STEP	1
> > +#define DW_VPG_PAN_STEP	40
> > +#define DW_VPG_FREQ	25000	/* [KHz] */
> > +#define DW_VPG_V_FRONT_PORCH	2
> > +#define DW_VPG_V_BACK_PORCH	2
> > +#define DW_VPG_V_SYNC	2
> > +#define SCREEN_SIZE_X	800
> > +#define SCREEN_SIZE_Y	480
> > +
> > +int vpg_load(struct drm_device *ddev);
> > +void vpg_unload(struct drm_device *ddev);
> > +
> > +#endif /* _VPG_H_ */
> > -- 
> > 2.7.4
> > 
> 
> -- 
> Daniel Vetter
> Software Engineer, Intel Corporation
> https://urldefense.proofpoint.com/v2/url?u=http-3A__blog.ffwll.ch&d=DwIBAg&c=DPL6_X_6JkXFx7AXWqB0tg&r=-1lJkMi2j8IE8jpsNWO99o9zJFwyv1Mbjj3vJBR__i0&m=knxu5BXoiypjzq6gobgbIqbrpnkIlWKYlpnGtKMLvnw&s=obntSCr3Ev8khEsp1VLKqczn-yc69G5IuHbSBcRS24k&e= 

Thanks,
Angelo Ribeiro
diff mbox series

Patch

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 4359497..29ea1d1 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -388,6 +388,8 @@  source "drivers/gpu/drm/mcde/Kconfig"
 
 source "drivers/gpu/drm/tidss/Kconfig"
 
+source "drivers/gpu/drm/ipk/Kconfig"
+
 # Keep legacy drivers last
 
 menuconfig DRM_LEGACY
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index 183c600..5bcc1c1 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -125,3 +125,4 @@  obj-$(CONFIG_DRM_PANFROST) += panfrost/
 obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
 obj-$(CONFIG_DRM_MCDE) += mcde/
 obj-$(CONFIG_DRM_TIDSS) += tidss/
+obj-$(CONFIG_DRM_IPK) += ipk/
diff --git a/drivers/gpu/drm/ipk/Kconfig b/drivers/gpu/drm/ipk/Kconfig
new file mode 100644
index 0000000..1f87444
--- /dev/null
+++ b/drivers/gpu/drm/ipk/Kconfig
@@ -0,0 +1,13 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_IPK
+	tristate "DRM Support for Synopsys DesignWare IPK DSI"
+	depends on DRM
+	select DRM_KMS_HELPER
+	select DRM_GEM_CMA_HELPER
+	select DRM_KMS_CMA_HELPER
+	select DRM_PANEL_BRIDGE
+	select VIDEOMODE_HELPERS
+	help
+	  Enable support for the Synopsys DesignWare DRM DSI.
+	  To compile this driver as a module, choose M here: the module
+	  will be called ipk-drm.
diff --git a/drivers/gpu/drm/ipk/Makefile b/drivers/gpu/drm/ipk/Makefile
new file mode 100644
index 0000000..51d2774
--- /dev/null
+++ b/drivers/gpu/drm/ipk/Makefile
@@ -0,0 +1,6 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+ipk-drm-y := \
+	dw-drv.o \
+	dw-vpg.o
+
+obj-$(CONFIG_DRM_IPK) += ipk-drm.o
diff --git a/drivers/gpu/drm/ipk/dw-drv.c b/drivers/gpu/drm/ipk/dw-drv.c
new file mode 100644
index 0000000..6205f1c
--- /dev/null
+++ b/drivers/gpu/drm/ipk/dw-drv.c
@@ -0,0 +1,189 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare MIPI DSI DRM driver
+ *
+ * Author: Angelo Ribeiro <angelo.ribeiro@synopsys.com>
+ */
+
+#include <linux/component.h>
+#include <linux/module.h>
+#include <linux/of_platform.h>
+#include <linux/clk.h>
+#include <drm/drm_crtc_helper.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_probe_helper.h>
+
+#include "dw-ipk.h"
+#include "dw-vpg.h"
+
+static const struct drm_mode_config_funcs dw_ipk_drm_modecfg_funcs = {
+	.fb_create = drm_gem_fb_create_with_dirty,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static int dw_ipk_load(struct drm_device *drm)
+{
+	int ret;
+
+	drm_mode_config_init(drm);
+
+	drm->mode_config.min_width = 0;
+	drm->mode_config.min_height = 0;
+
+	/* To handle orientation */
+	drm->mode_config.max_width = 2048;
+	drm->mode_config.max_height = 2048;
+
+	drm->mode_config.funcs = &dw_ipk_drm_modecfg_funcs;
+
+	/* TODO
+	 * Optional framebuffer memory resources allocation
+	 */
+
+	ret = vpg_load(drm);
+	if (ret)
+		return ret;
+
+	/* Calls all the crtc's, encoder's and connector's reset */
+	drm_mode_config_reset(drm);
+
+	/* Initialize and enable output polling */
+	drm_kms_helper_poll_init(drm);
+
+	return ret;
+}
+
+static void dw_ipk_unload(struct drm_device *drm)
+{
+	DRM_DEBUG_DRIVER("\n");
+
+	drm_kms_helper_poll_fini(drm);
+	vpg_unload(drm);
+}
+
+DEFINE_DRM_GEM_CMA_FOPS(ipk_drm_driver_fops);
+
+static int ipk_gem_cma_dumb_create(struct drm_file *file,
+				   struct drm_device *dev,
+				   struct drm_mode_create_dumb *args)
+{
+	unsigned int min_pitch = DIV_ROUND_UP(args->width * args->bpp, 8);
+	int err;
+
+	/*
+	 * In order to optimize data transfer, pitch is aligned on
+	 * 128 bytes, height is aligned on 4 bytes
+	 */
+	args->pitch = roundup(min_pitch, 128);
+	args->height = roundup(args->height, 4);
+
+	err = drm_gem_cma_dumb_create_internal(file, dev, args);
+	if (err < 0)
+		drm_err(dev, "dumb_create failed %d\n", err);
+
+	return err;
+}
+
+static struct drm_driver dw_ipk_drm_driver = {
+	.driver_features = DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+	.name = "dw_ipk",
+	.desc = "DW IPK DSI Host Controller",
+	.date = "20190725",
+	.major = 1,
+	.minor = 0,
+	.patchlevel = 0,
+	.fops = &ipk_drm_driver_fops,
+	.dumb_create = ipk_gem_cma_dumb_create,
+	.prime_handle_to_fd = drm_gem_prime_handle_to_fd,
+	.prime_fd_to_handle = drm_gem_prime_fd_to_handle,
+	.gem_free_object_unlocked = drm_gem_cma_free_object,
+	.gem_vm_ops = &drm_gem_cma_vm_ops,
+	.gem_prime_export = drm_gem_prime_export,
+	.gem_prime_import = drm_gem_prime_import,
+	.gem_prime_get_sg_table = drm_gem_cma_prime_get_sg_table,
+	.gem_prime_import_sg_table = drm_gem_cma_prime_import_sg_table,
+	.gem_prime_vmap = drm_gem_cma_prime_vmap,
+	.gem_prime_vunmap = drm_gem_cma_prime_vunmap,
+	.gem_prime_mmap = drm_gem_cma_prime_mmap,
+};
+
+static int dw_ipk_drm_platform_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct drm_device *drm;
+	struct ipk_device *ipk;
+	int ret;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	ipk = kzalloc(sizeof(*ipk), GFP_KERNEL);
+	if (!ipk)
+		return -ENOMEM;
+
+	ipk->platform = pdev;
+	drm = &ipk->drm;
+
+	ret = drm_dev_init(&ipk->drm, &dw_ipk_drm_driver, dev);
+	if (ret) {
+		kfree(ipk);
+		return ret;
+	}
+
+	platform_set_drvdata(pdev, drm);
+
+	ret = dw_ipk_load(drm);
+	if (ret)
+		goto err_put;
+
+	ret = drm_dev_register(drm, 0);
+	if (ret)
+		goto err_put;
+
+	drm_fbdev_generic_setup(drm, 24);
+
+	return ret;
+
+err_put:
+	drm_dev_put(drm);
+	return ret;
+}
+
+static int dw_ipk_drm_platform_remove(struct platform_device *pdev)
+{
+	struct drm_device *drm = platform_get_drvdata(pdev);
+
+	drm_dev_unregister(drm);
+	dw_ipk_unload(drm);
+	drm_dev_put(drm);
+
+	return 0;
+}
+
+static const struct of_device_id dw_ipk_dt_ids[] = {
+	{.compatible = "snps,dw-ipk-vpg"},
+	{ /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, dw_ipk_dt_ids);
+
+static struct platform_driver dw_ipk_drm_platform_driver = {
+	.probe = dw_ipk_drm_platform_probe,
+	.remove = dw_ipk_drm_platform_remove,
+	.driver = {
+		.name = "dw-ipk-drm",
+		.of_match_table = dw_ipk_dt_ids,
+	},
+};
+
+module_platform_driver(dw_ipk_drm_platform_driver);
+
+MODULE_DESCRIPTION("Synopsys DesignWare IPK DRM driver");
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Angelo Ribeiro <angelo.ribeiro@synopsys.com>");
diff --git a/drivers/gpu/drm/ipk/dw-ipk.h b/drivers/gpu/drm/ipk/dw-ipk.h
new file mode 100644
index 0000000..4abb6dd
--- /dev/null
+++ b/drivers/gpu/drm/ipk/dw-ipk.h
@@ -0,0 +1,30 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare MIPI DSI Controller
+ */
+
+#ifndef _DW_IPK_H
+#define _DW_IPK_H
+
+#include "drm/drm_device.h"
+#include <drm/bridge/dw_mipi_dsi.h>
+
+struct ipk_pipeline {
+	struct drm_framebuffer	*fb;
+	struct drm_crtc		crtc;
+	struct drm_plane	*plane;
+	struct drm_bridge	*bridge;
+};
+
+struct ipk_device {
+	struct drm_device drm;
+	struct platform_device *platform;
+	struct ipk_pipeline pipeline;
+	struct vpg_device *vpg;
+};
+
+#define drm_device_to_ipk_device(target) \
+	container_of(target, struct ipk_device, drm)
+
+#endif /* _DW_IPK_H */
diff --git a/drivers/gpu/drm/ipk/dw-vpg.c b/drivers/gpu/drm/ipk/dw-vpg.c
new file mode 100644
index 0000000..feb3e90
--- /dev/null
+++ b/drivers/gpu/drm/ipk/dw-vpg.c
@@ -0,0 +1,559 @@ 
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare MIPI DSI controller
+ *
+ * Author: Angelo Ribeiro <angelor@synopsys.com>
+ * Author: Luis Oliveira <luis.oliveira@synopsys.com>
+ */
+
+#include <linux/clk.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/reset.h>
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_bridge.h>
+#include <drm/drm_device.h>
+#include <drm/drm_fb_cma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_gem_cma_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_vblank.h>
+#include <linux/platform_data/simplefb.h>
+#include <video/videomode.h>
+
+#include "dw-ipk.h"
+#include "dw-vpg.h"
+
+#define NB_CRTC 1
+#define CRTC_MASK GENMASK(NB_CRTC - 1, 0)
+#define MAX_ENDPOINTS 1
+
+struct vpg_device {
+	void __iomem *base;
+	void __iomem *base_mmcm;
+};
+
+enum vpg_pixel_fmt {
+	VIF_COLOR_CODE_16BIT_CONFIG1 = 0,
+	VIF_COLOR_CODE_16BIT_CONFIG2,
+	VIF_COLOR_CODE_16BIT_CONFIG3,
+	VIF_COLOR_CODE_18BIT_CONFIG1,
+	VIF_COLOR_CODE_18BIT_CONFIG2,
+	VIF_COLOR_CODE_24BIT,
+	VIF_COLOR_CODE_MAX
+};
+
+static enum vpg_pixel_fmt to_vpg_pixel_format(u32 drm_fmt)
+{
+	enum vpg_pixel_fmt pf;
+
+	switch (drm_fmt) {
+	case DRM_FORMAT_RGB888:
+		pf = VIF_COLOR_CODE_24BIT;
+		break;
+	case DRM_FORMAT_RGB565:
+		pf = VIF_COLOR_CODE_16BIT_CONFIG1;
+		break;
+	default:
+		pf = VIF_COLOR_CODE_MAX;
+		break;
+	}
+	return pf;
+}
+
+static struct simplefb_format supported_formats[] = {
+	{"r5g6b5", 16, {11, 5}, {5, 6}, {0, 5}, {0, 0}, DRM_FORMAT_RGB565},
+	{"r8g8b8", 24, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_RGB888},
+	{"x8r8g8b8", 32, {16, 8}, {8, 8}, {0, 8}, {0, 0}, DRM_FORMAT_XRGB8888},
+};
+
+static const u64 vpg_format_modifiers[] = {
+	DRM_FORMAT_MOD_LINEAR,
+	DRM_FORMAT_MOD_INVALID
+};
+
+static const struct drm_encoder_funcs ipk_encoder_funcs = {
+	.destroy = drm_encoder_cleanup,
+};
+
+static int ipk_encoder_init(struct drm_device *drm,
+			    struct drm_bridge *bridge)
+{
+	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
+	struct drm_encoder *encoder;
+	int ret;
+
+	encoder = devm_kzalloc(drm->dev, sizeof(*encoder), GFP_KERNEL);
+	if (!encoder)
+		return -ENOMEM;
+
+	/* Only one CRTC can be attached in IPK_DSI */
+	encoder->possible_crtcs = CRTC_MASK;
+	encoder->possible_clones = 0;
+
+	ret = drm_encoder_init(drm, encoder, &ipk_encoder_funcs,
+			       DRM_MODE_ENCODER_DPI, NULL);
+	if (ret)
+		return ret;
+
+	ret = drm_bridge_attach(encoder, bridge, NULL, 0);
+	if (ret)
+		goto err_attach;
+
+	/* Add to the internal struct */
+	ipk->pipeline.bridge = bridge;
+
+	DRM_DEBUG_DRIVER("Num of connectors = %d\n",
+			 drm->mode_config.num_connector);
+	DRM_DEBUG_DRIVER("Bridge encoder:%d created\n", encoder->base.id);
+
+	return ret;
+
+err_attach:
+	drm_encoder_cleanup(encoder);
+	return ret;
+}
+
+/*
+ * CRTC
+ */
+static u32 vpg_read(struct vpg_device *dev, u32 reg)
+{
+	return readl(dev->base + reg);
+}
+
+static void vpg_write(struct vpg_device *dev, u32 reg, u32 val)
+{
+	writel(val, dev->base + reg);
+}
+
+static void vpg_write_part(struct vpg_device *dev, u32 reg,
+			   u32 val, u8 shift, u8 width)
+{
+	u32 mask = (1 << width) - 1;
+
+	vpg_write(dev, reg, (vpg_read(dev, reg) &
+		  ~(mask << shift)) | ((val & mask) << shift));
+}
+
+static u32 mmcm_read(struct vpg_device *dev, u32 reg)
+{
+	return readl(dev->base_mmcm + reg);
+}
+
+static void mmcm_write(struct vpg_device *dev, u32 reg, u32 value)
+{
+	writel(value, dev->base_mmcm + reg);
+}
+
+static void mmcm_configure(struct vpg_device *vpg, struct videomode *v_mode)
+{
+	int div,  mul, cur_freq, cur_deviat, temp;
+	int out_freq = v_mode->pixelclock / 1000;
+	int in_freq = 100000;
+	int deviat = 1000;	/* Deviation from desired master clock */
+	int best_div = 0;	/* Divider for PLL */
+	int best_mul = 0;	/* Multiplier for PLL */
+
+	DRM_DEBUG_DRIVER("out_freq = %d\n", out_freq);
+
+	for (div = 1; div < 150 && deviat > 50; div++) {
+		for (mul = 1; mul <= 10 && deviat > 50; mul++) {
+			cur_freq = in_freq * mul / div;
+			cur_deviat = abs(out_freq - cur_freq);
+			if (cur_deviat < deviat) {
+				best_div = div;
+				best_mul = mul;
+				deviat = cur_deviat;
+			}
+		}
+	}
+
+	DRM_DEBUG_DRIVER("deviat = %d\n best_div = %d\n best_mul = %d\n",
+			 deviat, best_div, best_mul);
+
+	temp = mmcm_read(vpg, DW_MMCM_CLKOUT0_REG_1) & DW_MMCM_MASK;
+	mmcm_write(vpg, DW_MMCM_CLKOUT0_REG_1, temp | (best_div << 6) |
+		   best_div);
+
+	temp = mmcm_read(vpg, DW_MMCM_CLKFBOUT_REG_1) & DW_MMCM_MASK;
+	mmcm_write(vpg, DW_MMCM_CLKFBOUT_REG_1, temp | (best_mul << 6) |
+		   best_mul);
+}
+
+static void vpg_set_fps(struct vpg_device *vpg,
+			const struct drm_display_mode *mode)
+{
+	/* IF_Freq is in KHz */
+	u32 line_time = (1000 * DW_VPG_FREQ) / (mode->vrefresh * mode->vtotal);
+
+	vpg_write(vpg, DW_VPG_LINE_TIME, line_time);
+}
+
+static void vpg_select_pattern(struct vpg_device *vpg, unsigned int value)
+{
+	vpg_write(vpg, DW_VPG_TESTMODE, value);
+}
+
+static void vpg_vertical_set(struct vpg_device *vpg, struct videomode *v_mode)
+{
+	vpg_write_part(vpg, DW_VPG_CANVAS, v_mode->vactive, 16, 16);
+	vpg_write_part(vpg, DW_VPG_VBP_VFP_VSA, v_mode->vsync_len, 0, 8);
+	vpg_write_part(vpg, DW_VPG_VBP_VFP_VSA, v_mode->vback_porch, 20, 12);
+	vpg_write_part(vpg, DW_VPG_VBP_VFP_VSA, v_mode->vfront_porch, 8, 11);
+}
+
+static void vpg_horizontal_set(struct vpg_device *vpg, struct videomode *v_mode)
+{
+	vpg_write_part(vpg, DW_VPG_CANVAS, v_mode->hactive, 0, 16);
+	vpg_write_part(vpg, DW_VPG_HBP_HSA, v_mode->hsync_len, 0, 16);
+	vpg_write_part(vpg, DW_VPG_HBP_HSA, v_mode->hback_porch, 16, 16);
+}
+
+static ssize_t show_pattern(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
+	struct vpg_device *vpg = ipk->vpg;
+
+	return sprintf(buf, "%d\n", vpg_read(vpg, DW_VPG_TESTMODE));
+}
+
+static ssize_t store_pattern(struct device *dev, struct device_attribute *attr,
+			     const char *buf, size_t count)
+{
+	struct drm_device *drm = dev_get_drvdata(dev);
+	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
+	struct vpg_device *vpg = ipk->vpg;
+	long pattern;
+	int ret;
+
+	ret = kstrtol(buf, 0, &pattern);
+	if (ret < 0)
+		return ret;
+
+	vpg_select_pattern(vpg, pattern);
+
+	return count;
+}
+
+static struct kobj_attribute vpg_pattern = __ATTR(pattern, 0660,
+						  (void *)show_pattern,
+						  (void *)store_pattern);
+
+static struct attribute *vpg_attr[] = {
+	&vpg_pattern.attr,
+	NULL,
+};
+
+static const struct attribute_group vpg_attr_group = {
+	.attrs = vpg_attr,
+};
+
+static void vpg_crtc_mode_set_nofb(struct drm_crtc *crtc)
+{
+	struct ipk_device *ipk = drm_device_to_ipk_device(crtc->dev);
+	struct drm_display_mode *mode = &crtc->state->adjusted_mode;
+	struct vpg_device *vpg = ipk->vpg;
+	struct videomode v_mode;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	drm_display_mode_to_videomode(mode, &v_mode);
+
+	DRM_DEBUG_DRIVER("CRTC:%d mode:%s\n", crtc->base.id, mode->name);
+	DRM_DEBUG_DRIVER("Video mode: %dx%d", v_mode.hactive, v_mode.vactive);
+	DRM_DEBUG_DRIVER(" hfp %d hbp %d hsl %d vfp %d vbp %d vsl %d\n",
+			 v_mode.hfront_porch, v_mode.hback_porch,
+			 v_mode.hsync_len, v_mode.vfront_porch,
+			 v_mode.vback_porch, v_mode.vsync_len);
+
+	mmcm_configure(vpg, &v_mode);
+
+	vpg_write(vpg, DW_VPG_SEL_DATA, 0x01);
+	vpg_write(vpg, DW_VPG_SEL_DATA, 0x03);
+
+	vpg_horizontal_set(vpg, &v_mode);
+
+	vpg_vertical_set(vpg, &v_mode);
+
+	vpg_set_fps(vpg, mode);
+
+	vpg_select_pattern(vpg, 0);
+}
+
+static void vpg_crtc_atomic_begin(struct drm_crtc *crtc,
+				  struct drm_crtc_state *state)
+{
+	struct drm_pending_vblank_event *event = crtc->state->event;
+
+	if (event) {
+		crtc->state->event = NULL;
+		spin_lock_irq(&crtc->dev->event_lock);
+		drm_crtc_send_vblank_event(crtc, event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+	}
+}
+
+static struct drm_crtc_helper_funcs vpg_crtc_helper_funcs = {
+	.mode_set_nofb = vpg_crtc_mode_set_nofb,
+	.atomic_begin = vpg_crtc_atomic_begin,
+};
+
+static int vpg_crtc_late_register(struct drm_crtc *crtc)
+{
+	struct ipk_device *ipk = drm_device_to_ipk_device(crtc->dev);
+	struct platform_device *pdev = ipk->platform;
+	int ret;
+
+	ret = sysfs_create_group(&pdev->dev.kobj, &vpg_attr_group);
+	if (ret)
+		drm_err(crtc->dev, "Failed to create sysfs entries");
+
+	return ret;
+}
+
+static void vpg_crtc_early_unregister(struct drm_crtc *crtc)
+{
+	struct ipk_device *ipk = drm_device_to_ipk_device(crtc->dev);
+	struct platform_device *pdev = ipk->platform;
+
+	sysfs_remove_group(&pdev->dev.kobj, &vpg_attr_group);
+}
+
+static const struct drm_crtc_funcs vpg_crtc_funcs = {
+	.reset = drm_atomic_helper_crtc_reset,
+	.destroy = drm_crtc_cleanup,
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.late_register =  vpg_crtc_late_register,
+	.early_unregister = vpg_crtc_early_unregister
+};
+
+/*
+ * Plane
+ */
+static void vpg_plane_atomic_update(struct drm_plane *plane,
+				    struct drm_plane_state *old_state)
+{
+	struct ipk_device *ipk = drm_device_to_ipk_device(plane->dev);
+	struct drm_plane_state *state = plane->state;
+	struct drm_framebuffer *fb = state->fb;
+	struct vpg_device *vpg = ipk->vpg;
+	enum vpg_pixel_fmt pixel_format;
+
+	pixel_format = to_vpg_pixel_format(fb->format->format);
+
+	vpg_write(vpg, DW_VPG_DPICOLORMODE, pixel_format);
+}
+
+static void vpg_plane_atomic_disable(struct drm_plane *plane,
+				     struct drm_plane_state *oldstate)
+{
+	DRM_DEBUG_DRIVER("\n");
+}
+
+static const struct drm_plane_helper_funcs vpg_plane_helper_funcs = {
+	.atomic_update = vpg_plane_atomic_update,
+	.atomic_disable = vpg_plane_atomic_disable,
+};
+
+static const struct drm_plane_funcs vpg_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	.destroy = drm_plane_cleanup,
+	.reset = drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_plane_destroy_state,
+};
+
+static void vpg_plane_destroy_all(struct drm_device *drm)
+{
+	struct drm_plane *plane, *plane_temp;
+
+	list_for_each_entry_safe(plane, plane_temp,
+				 &drm->mode_config.plane_list, head)
+		drm_plane_cleanup(plane);
+}
+
+static int vpg_crtc_init(struct drm_device *drm, struct drm_crtc *crtc)
+{
+	u32 formats[ARRAY_SIZE(supported_formats)], i;
+	const u64 *modifiers = vpg_format_modifiers;
+	unsigned long possible_crtcs = CRTC_MASK;
+	struct device *drm_dev = drm->dev;
+	struct drm_plane *plane;
+	int ret;
+
+	plane = devm_kzalloc(drm_dev, sizeof(*plane), GFP_KERNEL);
+	if (!plane)
+		return -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(supported_formats); i++)
+		formats[i] = supported_formats[i].fourcc;
+
+	ret = drm_universal_plane_init(drm, plane, possible_crtcs,
+				       &vpg_plane_funcs, formats,
+				       ARRAY_SIZE(formats), modifiers,
+				       DRM_PLANE_TYPE_PRIMARY, NULL);
+	if (ret)
+		goto cleanup_planes;
+
+	drm_plane_helper_add(plane, &vpg_plane_helper_funcs);
+
+	DRM_DEBUG_DRIVER("plane:%d created\n", plane->base.id);
+
+	ret = drm_crtc_init_with_planes(drm, crtc, plane, NULL,
+					&vpg_crtc_funcs, "DSI-VPG");
+	if (ret) {
+		DRM_DEBUG_DRIVER("Failed initialize CRTC\n");
+		goto cleanup_crtc;
+	}
+
+	drm_crtc_helper_add(crtc, &vpg_crtc_helper_funcs);
+
+	DRM_DEBUG_DRIVER("CRTC:%d created\n", crtc->base.id);
+
+	return ret;
+
+cleanup_crtc:
+	drm_crtc_cleanup(crtc);
+cleanup_planes:
+	vpg_plane_destroy_all(drm);
+	return ret;
+}
+
+int vpg_load(struct drm_device *drm)
+{
+	struct ipk_device *ipk = drm_device_to_ipk_device(drm);
+	struct drm_bridge *bridge[MAX_ENDPOINTS] = {NULL};
+	struct drm_panel *panel[MAX_ENDPOINTS] = {NULL};
+	struct platform_device *pdev = ipk->platform;
+	struct device_node *np = drm->dev->of_node;
+	int ret, i, endpoint_not_ready = -ENODEV;
+	struct reset_control *vpg_rst, *mmcm_rst;
+	struct device *dev = drm->dev;
+	struct vpg_device *vpg;
+	struct drm_crtc *crtc;
+	struct resource *res;
+
+	DRM_DEBUG_DRIVER("\n");
+
+	vpg = devm_kzalloc(&pdev->dev, sizeof(*vpg), GFP_KERNEL);
+	if (!vpg)
+		return -ENOMEM;
+
+	ipk->vpg = vpg;
+
+	/* Get endpoints if any */
+	for (i = 0; i < MAX_ENDPOINTS; i++) {
+		ret = drm_of_find_panel_or_bridge(np, 0, i, &panel[i],
+						  &bridge[i]);
+		if (ret == -EPROBE_DEFER)
+			return ret;
+		else if (!ret)
+			endpoint_not_ready = 0;
+	}
+
+	if (endpoint_not_ready)
+		return endpoint_not_ready;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "vpg");
+	vpg->base = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vpg->base)) {
+		drm_err(drm, "Unable to get VPG registers\n");
+		ret = PTR_ERR(vpg->base);
+		goto err;
+	}
+
+	vpg_rst = devm_reset_control_get_optional_exclusive(dev, "vpg");
+	if (IS_ERR(vpg_rst)) {
+		ret = PTR_ERR(vpg_rst);
+		if (ret != -EPROBE_DEFER)
+			drm_err(drm, "Unable to get reset control: %d\n", ret);
+		goto err;
+	}
+
+	if (vpg_rst) {
+		reset_control_assert(vpg_rst);
+		usleep_range(10, 20);
+		reset_control_deassert(vpg_rst);
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "mmcm");
+	vpg->base_mmcm = devm_ioremap_resource(dev, res);
+	if (IS_ERR(vpg->base)) {
+		ret = PTR_ERR(vpg->base);
+		goto err;
+	}
+
+	mmcm_rst = devm_reset_control_get_optional_exclusive(dev, "mmcm");
+	if (IS_ERR(mmcm_rst)) {
+		ret = PTR_ERR(mmcm_rst);
+		if (ret != -EPROBE_DEFER)
+			drm_err(drm, "Unable to get reset control: %d\n", ret);
+		goto err;
+	}
+
+	if (mmcm_rst) {
+		reset_control_assert(mmcm_rst);
+		usleep_range(10, 20);
+		reset_control_deassert(mmcm_rst);
+	}
+
+	/* Add endpoints panels or bridges if any */
+	for (i = 0; i < MAX_ENDPOINTS; i++) {
+		if (panel[i]) {
+			bridge[i] = drm_panel_bridge_add_typed(panel[i],
+							DRM_MODE_CONNECTOR_DSI);
+			if (IS_ERR(bridge[i])) {
+				ret = PTR_ERR(bridge[i]);
+				goto err;
+			}
+		}
+
+		if (bridge[i]) {
+			ret = ipk_encoder_init(drm, bridge[i]);
+			if (ret)
+				goto err;
+		}
+	}
+
+	crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL);
+	if (!crtc) {
+		ret = -ENOMEM;
+		goto err;
+	}
+
+	ret = vpg_crtc_init(drm, crtc);
+	if (ret)
+		goto err;
+
+	return ret;
+
+err:
+	for (i = 0; i < MAX_ENDPOINTS; i++)
+		drm_panel_bridge_remove(bridge[i]);
+
+	return ret;
+}
+
+void vpg_unload(struct drm_device *drm)
+{
+	int i;
+
+	DRM_DEBUG_DRIVER("\n");
+	for (i = 0; i < MAX_ENDPOINTS; i++)
+		drm_of_panel_bridge_remove(drm->dev->of_node, 0, i);
+}
diff --git a/drivers/gpu/drm/ipk/dw-vpg.h b/drivers/gpu/drm/ipk/dw-vpg.h
new file mode 100644
index 0000000..869ccc4
--- /dev/null
+++ b/drivers/gpu/drm/ipk/dw-vpg.h
@@ -0,0 +1,55 @@ 
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2019-2020 Synopsys, Inc. and/or its affiliates.
+ * Synopsys DesignWare VPG
+ */
+
+#ifndef _VPG_H_
+#define _VPG_H_
+
+#define DW_VPG_PANX				0x00
+#define DW_VPG_PANY				0x04
+#define DW_VPG_PHASE_SEL			0x08
+#define DW_VPG_SEL_DATA				0x0C
+#define DW_VPG_SWAP_BAY				0x10
+#define DW_VPG_TESTMODE				0x14
+#define DW_VPG_ZOOM_OUT				0x18
+#define DW_VPG_EDPICTRL				0x1C
+#define DW_VPG_CANVAS				0x20
+#define DW_VPG_HBP_HSA				0x24
+#define DW_VPG_LINE_TIME			0x28
+#define DW_VPG_VBP_VFP_VSA			0x2C
+#define DW_VPG_DPICOLORMODE			0x30
+#define DW_VPG_VERSION				0x34
+#define DW_VPG_EXTRNAL_EDPI			0x38
+#define DW_VPG_PVO_CEAVID			0x3C
+#define DW_VPG_PAN_VALID			0x40
+#define DW_VPG_TECONTROL			0x44
+#define DW_VPG_IMAGE_START_POSITION_X		0x4C
+#define DW_VPG_IMAGE_START_POSITION_Y		0x50
+#define DW_VPG_IMAGE_Y				0x54
+#define DW_VPG_IMAGE_WORD_COUNT			0x58
+#define DW_VPG_IMAGE_DATA_TYPE			0x5C
+#define DW_VPG_LINE_PIXS_CNT			0x60
+#define DW_VPG_FRAME_LINES_CNT			0x64
+
+#define DW_MMCM_MASK				0x1000
+#define DW_MMCM_CLKOUT0_REG_1			0x20
+#define DW_MMCM_CLKOUT0_REG_2			0x24
+#define DW_MMCM_CLKFBOUT_REG_1			0x50
+#define DW_MMCM_CLKFBOUT_REG_2			0x54
+#define DW_MMCM_POWER_REG			0xA0
+
+#define DW_VPG_ZOOM_OUT_STEP	1
+#define DW_VPG_PAN_STEP	40
+#define DW_VPG_FREQ	25000	/* [KHz] */
+#define DW_VPG_V_FRONT_PORCH	2
+#define DW_VPG_V_BACK_PORCH	2
+#define DW_VPG_V_SYNC	2
+#define SCREEN_SIZE_X	800
+#define SCREEN_SIZE_Y	480
+
+int vpg_load(struct drm_device *ddev);
+void vpg_unload(struct drm_device *ddev);
+
+#endif /* _VPG_H_ */