162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
262306a36Sopenharmony_ci// SPDX-FileCopyrightText: 2020 Marian Cichy <M.Cichy@pengutronix.de>
362306a36Sopenharmony_ci
462306a36Sopenharmony_ci#include <drm/drm_bridge.h>
562306a36Sopenharmony_ci#include <drm/drm_bridge_connector.h>
662306a36Sopenharmony_ci#include <drm/drm_damage_helper.h>
762306a36Sopenharmony_ci#include <drm/drm_drv.h>
862306a36Sopenharmony_ci#include <drm/drm_fbdev_generic.h>
962306a36Sopenharmony_ci#include <drm/drm_fb_dma_helper.h>
1062306a36Sopenharmony_ci#include <drm/drm_fourcc.h>
1162306a36Sopenharmony_ci#include <drm/drm_framebuffer.h>
1262306a36Sopenharmony_ci#include <drm/drm_gem_atomic_helper.h>
1362306a36Sopenharmony_ci#include <drm/drm_gem_dma_helper.h>
1462306a36Sopenharmony_ci#include <drm/drm_gem_framebuffer_helper.h>
1562306a36Sopenharmony_ci#include <drm/drm_of.h>
1662306a36Sopenharmony_ci#include <drm/drm_probe_helper.h>
1762306a36Sopenharmony_ci#include <drm/drm_simple_kms_helper.h>
1862306a36Sopenharmony_ci#include <drm/drm_vblank.h>
1962306a36Sopenharmony_ci#include <linux/bitfield.h>
2062306a36Sopenharmony_ci#include <linux/clk.h>
2162306a36Sopenharmony_ci#include <linux/dma-mapping.h>
2262306a36Sopenharmony_ci#include <linux/mod_devicetable.h>
2362306a36Sopenharmony_ci#include <linux/module.h>
2462306a36Sopenharmony_ci#include <linux/platform_device.h>
2562306a36Sopenharmony_ci
2662306a36Sopenharmony_ci#define IMX21LCDC_LSSAR         0x0000 /* LCDC Screen Start Address Register */
2762306a36Sopenharmony_ci#define IMX21LCDC_LSR           0x0004 /* LCDC Size Register */
2862306a36Sopenharmony_ci#define IMX21LCDC_LVPWR         0x0008 /* LCDC Virtual Page Width Register */
2962306a36Sopenharmony_ci#define IMX21LCDC_LCPR          0x000C /* LCDC Cursor Position Register */
3062306a36Sopenharmony_ci#define IMX21LCDC_LCWHB         0x0010 /* LCDC Cursor Width Height and Blink Register*/
3162306a36Sopenharmony_ci#define IMX21LCDC_LCCMR         0x0014 /* LCDC Color Cursor Mapping Register */
3262306a36Sopenharmony_ci#define IMX21LCDC_LPCR          0x0018 /* LCDC Panel Configuration Register */
3362306a36Sopenharmony_ci#define IMX21LCDC_LHCR          0x001C /* LCDC Horizontal Configuration Register */
3462306a36Sopenharmony_ci#define IMX21LCDC_LVCR          0x0020 /* LCDC Vertical Configuration Register */
3562306a36Sopenharmony_ci#define IMX21LCDC_LPOR          0x0024 /* LCDC Panning Offset Register */
3662306a36Sopenharmony_ci#define IMX21LCDC_LSCR          0x0028 /* LCDC Sharp Configuration Register */
3762306a36Sopenharmony_ci#define IMX21LCDC_LPCCR         0x002C /* LCDC PWM Contrast Control Register */
3862306a36Sopenharmony_ci#define IMX21LCDC_LDCR          0x0030 /* LCDC DMA Control Register */
3962306a36Sopenharmony_ci#define IMX21LCDC_LRMCR         0x0034 /* LCDC Refresh Mode Control Register */
4062306a36Sopenharmony_ci#define IMX21LCDC_LICR          0x0038 /* LCDC Interrupt Configuration Register */
4162306a36Sopenharmony_ci#define IMX21LCDC_LIER          0x003C /* LCDC Interrupt Enable Register */
4262306a36Sopenharmony_ci#define IMX21LCDC_LISR          0x0040 /* LCDC Interrupt Status Register */
4362306a36Sopenharmony_ci#define IMX21LCDC_LGWSAR        0x0050 /* LCDC Graphic Window Start Address Register */
4462306a36Sopenharmony_ci#define IMX21LCDC_LGWSR         0x0054 /* LCDC Graph Window Size Register */
4562306a36Sopenharmony_ci#define IMX21LCDC_LGWVPWR       0x0058 /* LCDC Graphic Window Virtual Page Width Register */
4662306a36Sopenharmony_ci#define IMX21LCDC_LGWPOR        0x005C /* LCDC Graphic Window Panning Offset Register */
4762306a36Sopenharmony_ci#define IMX21LCDC_LGWPR         0x0060 /* LCDC Graphic Window Position Register */
4862306a36Sopenharmony_ci#define IMX21LCDC_LGWCR         0x0064 /* LCDC Graphic Window Control Register */
4962306a36Sopenharmony_ci#define IMX21LCDC_LGWDCR        0x0068 /* LCDC Graphic Window DMA Control Register */
5062306a36Sopenharmony_ci#define IMX21LCDC_LAUSCR        0x0080 /* LCDC AUS Mode Control Register */
5162306a36Sopenharmony_ci#define IMX21LCDC_LAUSCCR       0x0084 /* LCDC AUS Mode Cursor Control Register */
5262306a36Sopenharmony_ci#define IMX21LCDC_BGLUT         0x0800 /* Background Lookup Table */
5362306a36Sopenharmony_ci#define IMX21LCDC_GWLUT         0x0C00 /* Graphic Window Lookup Table */
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci#define IMX21LCDC_LCPR_CC0 BIT(30) /* Cursor Control Bit 0 */
5662306a36Sopenharmony_ci#define IMX21LCDC_LCPR_CC1 BIT(31) /* Cursor Control Bit 1 */
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_ci/* Values HSYNC, VSYNC and Framesize Register */
5962306a36Sopenharmony_ci#define IMX21LCDC_LHCR_HWIDTH		GENMASK(31, 26)
6062306a36Sopenharmony_ci#define IMX21LCDC_LHCR_HFPORCH		GENMASK(15, 8)		/* H_WAIT_1 in the i.MX25 Reference manual */
6162306a36Sopenharmony_ci#define IMX21LCDC_LHCR_HBPORCH		GENMASK(7, 0)		/* H_WAIT_2 in the i.MX25 Reference manual */
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci#define IMX21LCDC_LVCR_VWIDTH		GENMASK(31, 26)
6462306a36Sopenharmony_ci#define IMX21LCDC_LVCR_VFPORCH		GENMASK(15, 8)		/* V_WAIT_1 in the i.MX25 Reference manual */
6562306a36Sopenharmony_ci#define IMX21LCDC_LVCR_VBPORCH		GENMASK(7, 0)		/* V_WAIT_2 in the i.MX25 Reference manual */
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci#define IMX21LCDC_LSR_XMAX		GENMASK(25, 20)
6862306a36Sopenharmony_ci#define IMX21LCDC_LSR_YMAX		GENMASK(9, 0)
6962306a36Sopenharmony_ci
7062306a36Sopenharmony_ci/* Values for LPCR Register */
7162306a36Sopenharmony_ci#define IMX21LCDC_LPCR_PCD		GENMASK(5, 0)
7262306a36Sopenharmony_ci#define IMX21LCDC_LPCR_SHARP		BIT(6)
7362306a36Sopenharmony_ci#define IMX21LCDC_LPCR_SCLKSEL		BIT(7)
7462306a36Sopenharmony_ci#define IMX21LCDC_LPCR_ACD		GENMASK(14, 8)
7562306a36Sopenharmony_ci#define IMX21LCDC_LPCR_ACDSEL		BIT(15)
7662306a36Sopenharmony_ci#define IMX21LCDC_LPCR_REV_VS		BIT(16)
7762306a36Sopenharmony_ci#define IMX21LCDC_LPCR_SWAP_SEL		BIT(17)
7862306a36Sopenharmony_ci#define IMX21LCDC_LPCR_END_SEL		BIT(18)
7962306a36Sopenharmony_ci#define IMX21LCDC_LPCR_SCLKIDLE		BIT(19)
8062306a36Sopenharmony_ci#define IMX21LCDC_LPCR_OEPOL		BIT(20)
8162306a36Sopenharmony_ci#define IMX21LCDC_LPCR_CLKPOL		BIT(21)
8262306a36Sopenharmony_ci#define IMX21LCDC_LPCR_LPPOL		BIT(22)
8362306a36Sopenharmony_ci#define IMX21LCDC_LPCR_FLMPOL		BIT(23)
8462306a36Sopenharmony_ci#define IMX21LCDC_LPCR_PIXPOL		BIT(24)
8562306a36Sopenharmony_ci#define IMX21LCDC_LPCR_BPIX		GENMASK(27, 25)
8662306a36Sopenharmony_ci#define IMX21LCDC_LPCR_PBSIZ		GENMASK(29, 28)
8762306a36Sopenharmony_ci#define IMX21LCDC_LPCR_COLOR		BIT(30)
8862306a36Sopenharmony_ci#define IMX21LCDC_LPCR_TFT		BIT(31)
8962306a36Sopenharmony_ci
9062306a36Sopenharmony_ci#define INTR_EOF BIT(1) /* VBLANK Interrupt Bit */
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci#define BPP_RGB565	0x05
9362306a36Sopenharmony_ci#define BPP_XRGB8888	0x07
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci#define LCDC_MIN_XRES 64
9662306a36Sopenharmony_ci#define LCDC_MIN_YRES 64
9762306a36Sopenharmony_ci
9862306a36Sopenharmony_ci#define LCDC_MAX_XRES 1024
9962306a36Sopenharmony_ci#define LCDC_MAX_YRES 1024
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_cistruct imx_lcdc {
10262306a36Sopenharmony_ci	struct drm_device drm;
10362306a36Sopenharmony_ci	struct drm_simple_display_pipe pipe;
10462306a36Sopenharmony_ci	struct drm_connector *connector;
10562306a36Sopenharmony_ci	void __iomem *base;
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_ci	struct clk *clk_ipg;
10862306a36Sopenharmony_ci	struct clk *clk_ahb;
10962306a36Sopenharmony_ci	struct clk *clk_per;
11062306a36Sopenharmony_ci};
11162306a36Sopenharmony_ci
11262306a36Sopenharmony_cistatic const u32 imx_lcdc_formats[] = {
11362306a36Sopenharmony_ci	DRM_FORMAT_RGB565, DRM_FORMAT_XRGB8888,
11462306a36Sopenharmony_ci};
11562306a36Sopenharmony_ci
11662306a36Sopenharmony_cistatic inline struct imx_lcdc *imx_lcdc_from_drmdev(struct drm_device *drm)
11762306a36Sopenharmony_ci{
11862306a36Sopenharmony_ci	return container_of(drm, struct imx_lcdc, drm);
11962306a36Sopenharmony_ci}
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_cistatic unsigned int imx_lcdc_get_format(unsigned int drm_format)
12262306a36Sopenharmony_ci{
12362306a36Sopenharmony_ci	switch (drm_format) {
12462306a36Sopenharmony_ci	default:
12562306a36Sopenharmony_ci		DRM_WARN("Format not supported - fallback to XRGB8888\n");
12662306a36Sopenharmony_ci		fallthrough;
12762306a36Sopenharmony_ci
12862306a36Sopenharmony_ci	case DRM_FORMAT_XRGB8888:
12962306a36Sopenharmony_ci		return BPP_XRGB8888;
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci	case DRM_FORMAT_RGB565:
13262306a36Sopenharmony_ci		return BPP_RGB565;
13362306a36Sopenharmony_ci	}
13462306a36Sopenharmony_ci}
13562306a36Sopenharmony_ci
13662306a36Sopenharmony_cistatic void imx_lcdc_update_hw_registers(struct drm_simple_display_pipe *pipe,
13762306a36Sopenharmony_ci					 struct drm_plane_state *old_state,
13862306a36Sopenharmony_ci					 bool mode_set)
13962306a36Sopenharmony_ci{
14062306a36Sopenharmony_ci	struct drm_crtc *crtc = &pipe->crtc;
14162306a36Sopenharmony_ci	struct drm_plane_state *new_state = pipe->plane.state;
14262306a36Sopenharmony_ci	struct drm_framebuffer *fb = new_state->fb;
14362306a36Sopenharmony_ci	struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(pipe->crtc.dev);
14462306a36Sopenharmony_ci	u32 lpcr, lvcr, lhcr;
14562306a36Sopenharmony_ci	u32 framesize;
14662306a36Sopenharmony_ci	dma_addr_t addr;
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	addr = drm_fb_dma_get_gem_addr(fb, new_state, 0);
14962306a36Sopenharmony_ci	/* The LSSAR register specifies the LCD screen start address (SSA). */
15062306a36Sopenharmony_ci	writel(addr, lcdc->base + IMX21LCDC_LSSAR);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	if (!mode_set)
15362306a36Sopenharmony_ci		return;
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	/* Disable PER clock to make register write possible */
15662306a36Sopenharmony_ci	if (old_state && old_state->crtc && old_state->crtc->enabled)
15762306a36Sopenharmony_ci		clk_disable_unprepare(lcdc->clk_per);
15862306a36Sopenharmony_ci
15962306a36Sopenharmony_ci	/* Framesize */
16062306a36Sopenharmony_ci	framesize = FIELD_PREP(IMX21LCDC_LSR_XMAX, crtc->mode.hdisplay >> 4) |
16162306a36Sopenharmony_ci		FIELD_PREP(IMX21LCDC_LSR_YMAX, crtc->mode.vdisplay);
16262306a36Sopenharmony_ci	writel(framesize, lcdc->base + IMX21LCDC_LSR);
16362306a36Sopenharmony_ci
16462306a36Sopenharmony_ci	/* HSYNC */
16562306a36Sopenharmony_ci	lhcr = FIELD_PREP(IMX21LCDC_LHCR_HFPORCH, crtc->mode.hsync_start - crtc->mode.hdisplay - 1) |
16662306a36Sopenharmony_ci		FIELD_PREP(IMX21LCDC_LHCR_HWIDTH, crtc->mode.hsync_end - crtc->mode.hsync_start - 1) |
16762306a36Sopenharmony_ci		FIELD_PREP(IMX21LCDC_LHCR_HBPORCH, crtc->mode.htotal - crtc->mode.hsync_end - 3);
16862306a36Sopenharmony_ci	writel(lhcr, lcdc->base + IMX21LCDC_LHCR);
16962306a36Sopenharmony_ci
17062306a36Sopenharmony_ci	/* VSYNC */
17162306a36Sopenharmony_ci	lvcr = FIELD_PREP(IMX21LCDC_LVCR_VFPORCH, crtc->mode.vsync_start - crtc->mode.vdisplay) |
17262306a36Sopenharmony_ci		FIELD_PREP(IMX21LCDC_LVCR_VWIDTH, crtc->mode.vsync_end - crtc->mode.vsync_start) |
17362306a36Sopenharmony_ci		FIELD_PREP(IMX21LCDC_LVCR_VBPORCH, crtc->mode.vtotal - crtc->mode.vsync_end);
17462306a36Sopenharmony_ci	writel(lvcr, lcdc->base + IMX21LCDC_LVCR);
17562306a36Sopenharmony_ci
17662306a36Sopenharmony_ci	lpcr = readl(lcdc->base + IMX21LCDC_LPCR);
17762306a36Sopenharmony_ci	lpcr &= ~IMX21LCDC_LPCR_BPIX;
17862306a36Sopenharmony_ci	lpcr |= FIELD_PREP(IMX21LCDC_LPCR_BPIX, imx_lcdc_get_format(fb->format->format));
17962306a36Sopenharmony_ci	writel(lpcr, lcdc->base + IMX21LCDC_LPCR);
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	/* Virtual Page Width */
18262306a36Sopenharmony_ci	writel(new_state->fb->pitches[0] / 4, lcdc->base + IMX21LCDC_LVPWR);
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci	/* Enable PER clock */
18562306a36Sopenharmony_ci	if (new_state->crtc->enabled)
18662306a36Sopenharmony_ci		clk_prepare_enable(lcdc->clk_per);
18762306a36Sopenharmony_ci}
18862306a36Sopenharmony_ci
18962306a36Sopenharmony_cistatic void imx_lcdc_pipe_enable(struct drm_simple_display_pipe *pipe,
19062306a36Sopenharmony_ci				 struct drm_crtc_state *crtc_state,
19162306a36Sopenharmony_ci				 struct drm_plane_state *plane_state)
19262306a36Sopenharmony_ci{
19362306a36Sopenharmony_ci	int ret;
19462306a36Sopenharmony_ci	int clk_div;
19562306a36Sopenharmony_ci	int bpp;
19662306a36Sopenharmony_ci	struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(pipe->crtc.dev);
19762306a36Sopenharmony_ci	struct drm_display_mode *mode = &pipe->crtc.mode;
19862306a36Sopenharmony_ci	struct drm_display_info *disp_info = &lcdc->connector->display_info;
19962306a36Sopenharmony_ci	const int hsync_pol = (mode->flags & DRM_MODE_FLAG_PHSYNC) ? 0 : 1;
20062306a36Sopenharmony_ci	const int vsync_pol = (mode->flags & DRM_MODE_FLAG_PVSYNC) ? 0 : 1;
20162306a36Sopenharmony_ci	const int data_enable_pol =
20262306a36Sopenharmony_ci		(disp_info->bus_flags & DRM_BUS_FLAG_DE_HIGH) ? 0 : 1;
20362306a36Sopenharmony_ci	const int clk_pol =
20462306a36Sopenharmony_ci		(disp_info->bus_flags & DRM_BUS_FLAG_PIXDATA_DRIVE_POSEDGE) ? 0 : 1;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	clk_div = DIV_ROUND_CLOSEST_ULL(clk_get_rate(lcdc->clk_per),
20762306a36Sopenharmony_ci					mode->clock * 1000);
20862306a36Sopenharmony_ci	bpp = imx_lcdc_get_format(plane_state->fb->format->format);
20962306a36Sopenharmony_ci
21062306a36Sopenharmony_ci	writel(FIELD_PREP(IMX21LCDC_LPCR_PCD, clk_div - 1) |
21162306a36Sopenharmony_ci	       FIELD_PREP(IMX21LCDC_LPCR_LPPOL, hsync_pol) |
21262306a36Sopenharmony_ci	       FIELD_PREP(IMX21LCDC_LPCR_FLMPOL, vsync_pol) |
21362306a36Sopenharmony_ci	       FIELD_PREP(IMX21LCDC_LPCR_OEPOL, data_enable_pol) |
21462306a36Sopenharmony_ci	       FIELD_PREP(IMX21LCDC_LPCR_TFT, 1) |
21562306a36Sopenharmony_ci	       FIELD_PREP(IMX21LCDC_LPCR_COLOR, 1) |
21662306a36Sopenharmony_ci	       FIELD_PREP(IMX21LCDC_LPCR_PBSIZ, 3) |
21762306a36Sopenharmony_ci	       FIELD_PREP(IMX21LCDC_LPCR_BPIX, bpp) |
21862306a36Sopenharmony_ci	       FIELD_PREP(IMX21LCDC_LPCR_SCLKSEL, 1) |
21962306a36Sopenharmony_ci	       FIELD_PREP(IMX21LCDC_LPCR_PIXPOL, 0) |
22062306a36Sopenharmony_ci	       FIELD_PREP(IMX21LCDC_LPCR_CLKPOL, clk_pol),
22162306a36Sopenharmony_ci	       lcdc->base + IMX21LCDC_LPCR);
22262306a36Sopenharmony_ci
22362306a36Sopenharmony_ci	/* 0px panning offset */
22462306a36Sopenharmony_ci	writel(0x00000000, lcdc->base + IMX21LCDC_LPOR);
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	/* disable hardware cursor */
22762306a36Sopenharmony_ci	writel(readl(lcdc->base + IMX21LCDC_LCPR) & ~(IMX21LCDC_LCPR_CC0 | IMX21LCDC_LCPR_CC1),
22862306a36Sopenharmony_ci	       lcdc->base + IMX21LCDC_LCPR);
22962306a36Sopenharmony_ci
23062306a36Sopenharmony_ci	ret = clk_prepare_enable(lcdc->clk_ipg);
23162306a36Sopenharmony_ci	if (ret) {
23262306a36Sopenharmony_ci		dev_err(pipe->crtc.dev->dev, "Cannot enable ipg clock: %pe\n", ERR_PTR(ret));
23362306a36Sopenharmony_ci		return;
23462306a36Sopenharmony_ci	}
23562306a36Sopenharmony_ci	ret = clk_prepare_enable(lcdc->clk_ahb);
23662306a36Sopenharmony_ci	if (ret) {
23762306a36Sopenharmony_ci		dev_err(pipe->crtc.dev->dev, "Cannot enable ahb clock: %pe\n", ERR_PTR(ret));
23862306a36Sopenharmony_ci
23962306a36Sopenharmony_ci		clk_disable_unprepare(lcdc->clk_ipg);
24062306a36Sopenharmony_ci
24162306a36Sopenharmony_ci		return;
24262306a36Sopenharmony_ci	}
24362306a36Sopenharmony_ci
24462306a36Sopenharmony_ci	imx_lcdc_update_hw_registers(pipe, NULL, true);
24562306a36Sopenharmony_ci
24662306a36Sopenharmony_ci	/* Enable VBLANK Interrupt */
24762306a36Sopenharmony_ci	writel(INTR_EOF, lcdc->base + IMX21LCDC_LIER);
24862306a36Sopenharmony_ci}
24962306a36Sopenharmony_ci
25062306a36Sopenharmony_cistatic void imx_lcdc_pipe_disable(struct drm_simple_display_pipe *pipe)
25162306a36Sopenharmony_ci{
25262306a36Sopenharmony_ci	struct imx_lcdc *lcdc = imx_lcdc_from_drmdev(pipe->crtc.dev);
25362306a36Sopenharmony_ci	struct drm_crtc *crtc = &lcdc->pipe.crtc;
25462306a36Sopenharmony_ci	struct drm_pending_vblank_event *event;
25562306a36Sopenharmony_ci
25662306a36Sopenharmony_ci	clk_disable_unprepare(lcdc->clk_ahb);
25762306a36Sopenharmony_ci	clk_disable_unprepare(lcdc->clk_ipg);
25862306a36Sopenharmony_ci
25962306a36Sopenharmony_ci	if (pipe->crtc.enabled)
26062306a36Sopenharmony_ci		clk_disable_unprepare(lcdc->clk_per);
26162306a36Sopenharmony_ci
26262306a36Sopenharmony_ci	spin_lock_irq(&lcdc->drm.event_lock);
26362306a36Sopenharmony_ci	event = crtc->state->event;
26462306a36Sopenharmony_ci	if (event) {
26562306a36Sopenharmony_ci		crtc->state->event = NULL;
26662306a36Sopenharmony_ci		drm_crtc_send_vblank_event(crtc, event);
26762306a36Sopenharmony_ci	}
26862306a36Sopenharmony_ci	spin_unlock_irq(&lcdc->drm.event_lock);
26962306a36Sopenharmony_ci
27062306a36Sopenharmony_ci	/* Disable VBLANK Interrupt */
27162306a36Sopenharmony_ci	writel(0, lcdc->base + IMX21LCDC_LIER);
27262306a36Sopenharmony_ci}
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cistatic int imx_lcdc_pipe_check(struct drm_simple_display_pipe *pipe,
27562306a36Sopenharmony_ci			       struct drm_plane_state *plane_state,
27662306a36Sopenharmony_ci			       struct drm_crtc_state *crtc_state)
27762306a36Sopenharmony_ci{
27862306a36Sopenharmony_ci	const struct drm_display_mode *mode = &crtc_state->mode;
27962306a36Sopenharmony_ci	const struct drm_display_mode *old_mode = &pipe->crtc.state->mode;
28062306a36Sopenharmony_ci
28162306a36Sopenharmony_ci	if (mode->hdisplay < LCDC_MIN_XRES || mode->hdisplay > LCDC_MAX_XRES ||
28262306a36Sopenharmony_ci	    mode->vdisplay < LCDC_MIN_YRES || mode->vdisplay > LCDC_MAX_YRES ||
28362306a36Sopenharmony_ci	    mode->hdisplay % 0x10) { /* must be multiple of 16 */
28462306a36Sopenharmony_ci		drm_err(pipe->crtc.dev, "unsupported display mode (%u x %u)\n",
28562306a36Sopenharmony_ci			mode->hdisplay, mode->vdisplay);
28662306a36Sopenharmony_ci		return -EINVAL;
28762306a36Sopenharmony_ci	}
28862306a36Sopenharmony_ci
28962306a36Sopenharmony_ci	crtc_state->mode_changed =
29062306a36Sopenharmony_ci		old_mode->hdisplay != mode->hdisplay ||
29162306a36Sopenharmony_ci		old_mode->vdisplay != mode->vdisplay;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_ci	return 0;
29462306a36Sopenharmony_ci}
29562306a36Sopenharmony_ci
29662306a36Sopenharmony_cistatic void imx_lcdc_pipe_update(struct drm_simple_display_pipe *pipe,
29762306a36Sopenharmony_ci				 struct drm_plane_state *old_state)
29862306a36Sopenharmony_ci{
29962306a36Sopenharmony_ci	struct drm_crtc *crtc = &pipe->crtc;
30062306a36Sopenharmony_ci	struct drm_pending_vblank_event *event = crtc->state->event;
30162306a36Sopenharmony_ci	struct drm_plane_state *new_state = pipe->plane.state;
30262306a36Sopenharmony_ci	struct drm_framebuffer *fb = new_state->fb;
30362306a36Sopenharmony_ci	struct drm_framebuffer *old_fb = old_state->fb;
30462306a36Sopenharmony_ci	struct drm_crtc *old_crtc = old_state->crtc;
30562306a36Sopenharmony_ci	bool mode_changed = false;
30662306a36Sopenharmony_ci
30762306a36Sopenharmony_ci	if (old_fb && old_fb->format != fb->format)
30862306a36Sopenharmony_ci		mode_changed = true;
30962306a36Sopenharmony_ci	else if (old_crtc != crtc)
31062306a36Sopenharmony_ci		mode_changed = true;
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	imx_lcdc_update_hw_registers(pipe, old_state, mode_changed);
31362306a36Sopenharmony_ci
31462306a36Sopenharmony_ci	if (event) {
31562306a36Sopenharmony_ci		crtc->state->event = NULL;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci		spin_lock_irq(&crtc->dev->event_lock);
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci		if (crtc->state->active && drm_crtc_vblank_get(crtc) == 0)
32062306a36Sopenharmony_ci			drm_crtc_arm_vblank_event(crtc, event);
32162306a36Sopenharmony_ci		else
32262306a36Sopenharmony_ci			drm_crtc_send_vblank_event(crtc, event);
32362306a36Sopenharmony_ci
32462306a36Sopenharmony_ci		spin_unlock_irq(&crtc->dev->event_lock);
32562306a36Sopenharmony_ci	}
32662306a36Sopenharmony_ci}
32762306a36Sopenharmony_ci
32862306a36Sopenharmony_cistatic const struct drm_simple_display_pipe_funcs imx_lcdc_pipe_funcs = {
32962306a36Sopenharmony_ci	.enable = imx_lcdc_pipe_enable,
33062306a36Sopenharmony_ci	.disable = imx_lcdc_pipe_disable,
33162306a36Sopenharmony_ci	.check = imx_lcdc_pipe_check,
33262306a36Sopenharmony_ci	.update = imx_lcdc_pipe_update,
33362306a36Sopenharmony_ci};
33462306a36Sopenharmony_ci
33562306a36Sopenharmony_cistatic const struct drm_mode_config_funcs imx_lcdc_mode_config_funcs = {
33662306a36Sopenharmony_ci	.fb_create = drm_gem_fb_create_with_dirty,
33762306a36Sopenharmony_ci	.atomic_check = drm_atomic_helper_check,
33862306a36Sopenharmony_ci	.atomic_commit = drm_atomic_helper_commit,
33962306a36Sopenharmony_ci};
34062306a36Sopenharmony_ci
34162306a36Sopenharmony_cistatic const struct drm_mode_config_helper_funcs imx_lcdc_mode_config_helpers = {
34262306a36Sopenharmony_ci	.atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
34362306a36Sopenharmony_ci};
34462306a36Sopenharmony_ci
34562306a36Sopenharmony_ciDEFINE_DRM_GEM_DMA_FOPS(imx_lcdc_drm_fops);
34662306a36Sopenharmony_ci
34762306a36Sopenharmony_cistatic struct drm_driver imx_lcdc_drm_driver = {
34862306a36Sopenharmony_ci	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
34962306a36Sopenharmony_ci	.fops = &imx_lcdc_drm_fops,
35062306a36Sopenharmony_ci	DRM_GEM_DMA_DRIVER_OPS_VMAP,
35162306a36Sopenharmony_ci	.name = "imx-lcdc",
35262306a36Sopenharmony_ci	.desc = "i.MX LCDC driver",
35362306a36Sopenharmony_ci	.date = "20200716",
35462306a36Sopenharmony_ci};
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_cistatic const struct of_device_id imx_lcdc_of_dev_id[] = {
35762306a36Sopenharmony_ci	{
35862306a36Sopenharmony_ci		.compatible = "fsl,imx21-lcdc",
35962306a36Sopenharmony_ci	},
36062306a36Sopenharmony_ci	{
36162306a36Sopenharmony_ci		.compatible = "fsl,imx25-lcdc",
36262306a36Sopenharmony_ci	},
36362306a36Sopenharmony_ci	{ /* sentinel */ }
36462306a36Sopenharmony_ci};
36562306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, imx_lcdc_of_dev_id);
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_cistatic irqreturn_t imx_lcdc_irq_handler(int irq, void *arg)
36862306a36Sopenharmony_ci{
36962306a36Sopenharmony_ci	struct imx_lcdc *lcdc = arg;
37062306a36Sopenharmony_ci	struct drm_crtc *crtc = &lcdc->pipe.crtc;
37162306a36Sopenharmony_ci	unsigned int status;
37262306a36Sopenharmony_ci
37362306a36Sopenharmony_ci	status = readl(lcdc->base + IMX21LCDC_LISR);
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	if (status & INTR_EOF) {
37662306a36Sopenharmony_ci		drm_crtc_handle_vblank(crtc);
37762306a36Sopenharmony_ci		return IRQ_HANDLED;
37862306a36Sopenharmony_ci	}
37962306a36Sopenharmony_ci
38062306a36Sopenharmony_ci	return IRQ_NONE;
38162306a36Sopenharmony_ci}
38262306a36Sopenharmony_ci
38362306a36Sopenharmony_cistatic int imx_lcdc_probe(struct platform_device *pdev)
38462306a36Sopenharmony_ci{
38562306a36Sopenharmony_ci	struct imx_lcdc *lcdc;
38662306a36Sopenharmony_ci	struct drm_device *drm;
38762306a36Sopenharmony_ci	struct drm_bridge *bridge;
38862306a36Sopenharmony_ci	int irq;
38962306a36Sopenharmony_ci	int ret;
39062306a36Sopenharmony_ci	struct device *dev = &pdev->dev;
39162306a36Sopenharmony_ci
39262306a36Sopenharmony_ci	lcdc = devm_drm_dev_alloc(dev, &imx_lcdc_drm_driver,
39362306a36Sopenharmony_ci				  struct imx_lcdc, drm);
39462306a36Sopenharmony_ci	if (IS_ERR(lcdc))
39562306a36Sopenharmony_ci		return PTR_ERR(lcdc);
39662306a36Sopenharmony_ci
39762306a36Sopenharmony_ci	drm = &lcdc->drm;
39862306a36Sopenharmony_ci
39962306a36Sopenharmony_ci	lcdc->base = devm_platform_ioremap_resource(pdev, 0);
40062306a36Sopenharmony_ci	if (IS_ERR(lcdc->base))
40162306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(lcdc->base), "Cannot get IO memory\n");
40262306a36Sopenharmony_ci
40362306a36Sopenharmony_ci	bridge = devm_drm_of_get_bridge(dev, dev->of_node, 0, 0);
40462306a36Sopenharmony_ci	if (IS_ERR(bridge))
40562306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(bridge), "Failed to find bridge\n");
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	/* Get Clocks */
40862306a36Sopenharmony_ci	lcdc->clk_ipg = devm_clk_get(dev, "ipg");
40962306a36Sopenharmony_ci	if (IS_ERR(lcdc->clk_ipg))
41062306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(lcdc->clk_ipg), "Failed to get %s clk\n", "ipg");
41162306a36Sopenharmony_ci
41262306a36Sopenharmony_ci	lcdc->clk_ahb = devm_clk_get(dev, "ahb");
41362306a36Sopenharmony_ci	if (IS_ERR(lcdc->clk_ahb))
41462306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(lcdc->clk_ahb), "Failed to get %s clk\n", "ahb");
41562306a36Sopenharmony_ci
41662306a36Sopenharmony_ci	lcdc->clk_per = devm_clk_get(dev, "per");
41762306a36Sopenharmony_ci	if (IS_ERR(lcdc->clk_per))
41862306a36Sopenharmony_ci		return dev_err_probe(dev, PTR_ERR(lcdc->clk_per), "Failed to get %s clk\n", "per");
41962306a36Sopenharmony_ci
42062306a36Sopenharmony_ci	ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32));
42162306a36Sopenharmony_ci	if (ret)
42262306a36Sopenharmony_ci		return dev_err_probe(dev, ret, "Cannot set DMA Mask\n");
42362306a36Sopenharmony_ci
42462306a36Sopenharmony_ci	/* Modeset init */
42562306a36Sopenharmony_ci	ret = drmm_mode_config_init(drm);
42662306a36Sopenharmony_ci	if (ret)
42762306a36Sopenharmony_ci		return dev_err_probe(dev, ret, "Cannot initialize mode configuration structure\n");
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci	/* CRTC, Plane, Encoder */
43062306a36Sopenharmony_ci	ret = drm_simple_display_pipe_init(drm, &lcdc->pipe,
43162306a36Sopenharmony_ci					   &imx_lcdc_pipe_funcs,
43262306a36Sopenharmony_ci					   imx_lcdc_formats,
43362306a36Sopenharmony_ci					   ARRAY_SIZE(imx_lcdc_formats), NULL, NULL);
43462306a36Sopenharmony_ci	if (ret < 0)
43562306a36Sopenharmony_ci		return dev_err_probe(drm->dev, ret, "Cannot setup simple display pipe\n");
43662306a36Sopenharmony_ci
43762306a36Sopenharmony_ci	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
43862306a36Sopenharmony_ci	if (ret < 0)
43962306a36Sopenharmony_ci		return dev_err_probe(drm->dev, ret, "Failed to initialize vblank\n");
44062306a36Sopenharmony_ci
44162306a36Sopenharmony_ci	ret = drm_bridge_attach(&lcdc->pipe.encoder, bridge, NULL, DRM_BRIDGE_ATTACH_NO_CONNECTOR);
44262306a36Sopenharmony_ci	if (ret)
44362306a36Sopenharmony_ci		return dev_err_probe(drm->dev, ret, "Cannot attach bridge\n");
44462306a36Sopenharmony_ci
44562306a36Sopenharmony_ci	lcdc->connector = drm_bridge_connector_init(drm, &lcdc->pipe.encoder);
44662306a36Sopenharmony_ci	if (IS_ERR(lcdc->connector))
44762306a36Sopenharmony_ci		return dev_err_probe(drm->dev, PTR_ERR(lcdc->connector), "Cannot init bridge connector\n");
44862306a36Sopenharmony_ci
44962306a36Sopenharmony_ci	drm_connector_attach_encoder(lcdc->connector, &lcdc->pipe.encoder);
45062306a36Sopenharmony_ci
45162306a36Sopenharmony_ci	/*
45262306a36Sopenharmony_ci	 * The LCDC controller does not have an enable bit. The
45362306a36Sopenharmony_ci	 * controller starts directly when the clocks are enabled.
45462306a36Sopenharmony_ci	 * If the clocks are enabled when the controller is not yet
45562306a36Sopenharmony_ci	 * programmed with proper register values (enabled at the
45662306a36Sopenharmony_ci	 * bootloader, for example) then it just goes into some undefined
45762306a36Sopenharmony_ci	 * state.
45862306a36Sopenharmony_ci	 * To avoid this issue, let's enable and disable LCDC IPG,
45962306a36Sopenharmony_ci	 * PER and AHB clock so that we force some kind of 'reset'
46062306a36Sopenharmony_ci	 * to the LCDC block.
46162306a36Sopenharmony_ci	 */
46262306a36Sopenharmony_ci
46362306a36Sopenharmony_ci	ret = clk_prepare_enable(lcdc->clk_ipg);
46462306a36Sopenharmony_ci	if (ret)
46562306a36Sopenharmony_ci		return dev_err_probe(dev, ret, "Cannot enable ipg clock\n");
46662306a36Sopenharmony_ci	clk_disable_unprepare(lcdc->clk_ipg);
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	ret = clk_prepare_enable(lcdc->clk_per);
46962306a36Sopenharmony_ci	if (ret)
47062306a36Sopenharmony_ci		return dev_err_probe(dev, ret, "Cannot enable per clock\n");
47162306a36Sopenharmony_ci	clk_disable_unprepare(lcdc->clk_per);
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	ret = clk_prepare_enable(lcdc->clk_ahb);
47462306a36Sopenharmony_ci	if (ret)
47562306a36Sopenharmony_ci		return dev_err_probe(dev, ret, "Cannot enable ahb clock\n");
47662306a36Sopenharmony_ci	clk_disable_unprepare(lcdc->clk_ahb);
47762306a36Sopenharmony_ci
47862306a36Sopenharmony_ci	drm->mode_config.min_width = LCDC_MIN_XRES;
47962306a36Sopenharmony_ci	drm->mode_config.max_width = LCDC_MAX_XRES;
48062306a36Sopenharmony_ci	drm->mode_config.min_height = LCDC_MIN_YRES;
48162306a36Sopenharmony_ci	drm->mode_config.max_height = LCDC_MAX_YRES;
48262306a36Sopenharmony_ci	drm->mode_config.preferred_depth = 16;
48362306a36Sopenharmony_ci	drm->mode_config.funcs = &imx_lcdc_mode_config_funcs;
48462306a36Sopenharmony_ci	drm->mode_config.helper_private = &imx_lcdc_mode_config_helpers;
48562306a36Sopenharmony_ci
48662306a36Sopenharmony_ci	drm_mode_config_reset(drm);
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_ci	irq = platform_get_irq(pdev, 0);
48962306a36Sopenharmony_ci	if (irq < 0) {
49062306a36Sopenharmony_ci		ret = irq;
49162306a36Sopenharmony_ci		return ret;
49262306a36Sopenharmony_ci	}
49362306a36Sopenharmony_ci
49462306a36Sopenharmony_ci	ret = devm_request_irq(dev, irq, imx_lcdc_irq_handler, 0, "imx-lcdc", lcdc);
49562306a36Sopenharmony_ci	if (ret < 0)
49662306a36Sopenharmony_ci		return dev_err_probe(drm->dev, ret, "Failed to install IRQ handler\n");
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	platform_set_drvdata(pdev, drm);
49962306a36Sopenharmony_ci
50062306a36Sopenharmony_ci	ret = drm_dev_register(&lcdc->drm, 0);
50162306a36Sopenharmony_ci	if (ret)
50262306a36Sopenharmony_ci		return dev_err_probe(dev, ret, "Cannot register device\n");
50362306a36Sopenharmony_ci
50462306a36Sopenharmony_ci	drm_fbdev_generic_setup(drm, 0);
50562306a36Sopenharmony_ci
50662306a36Sopenharmony_ci	return 0;
50762306a36Sopenharmony_ci}
50862306a36Sopenharmony_ci
50962306a36Sopenharmony_cistatic int imx_lcdc_remove(struct platform_device *pdev)
51062306a36Sopenharmony_ci{
51162306a36Sopenharmony_ci	struct drm_device *drm = platform_get_drvdata(pdev);
51262306a36Sopenharmony_ci
51362306a36Sopenharmony_ci	drm_dev_unregister(drm);
51462306a36Sopenharmony_ci	drm_atomic_helper_shutdown(drm);
51562306a36Sopenharmony_ci
51662306a36Sopenharmony_ci	return 0;
51762306a36Sopenharmony_ci}
51862306a36Sopenharmony_ci
51962306a36Sopenharmony_cistatic void imx_lcdc_shutdown(struct platform_device *pdev)
52062306a36Sopenharmony_ci{
52162306a36Sopenharmony_ci	drm_atomic_helper_shutdown(platform_get_drvdata(pdev));
52262306a36Sopenharmony_ci}
52362306a36Sopenharmony_ci
52462306a36Sopenharmony_cistatic struct platform_driver imx_lcdc_driver = {
52562306a36Sopenharmony_ci	.driver = {
52662306a36Sopenharmony_ci		.name = "imx-lcdc",
52762306a36Sopenharmony_ci		.of_match_table = imx_lcdc_of_dev_id,
52862306a36Sopenharmony_ci	},
52962306a36Sopenharmony_ci	.probe = imx_lcdc_probe,
53062306a36Sopenharmony_ci	.remove = imx_lcdc_remove,
53162306a36Sopenharmony_ci	.shutdown = imx_lcdc_shutdown,
53262306a36Sopenharmony_ci};
53362306a36Sopenharmony_cimodule_platform_driver(imx_lcdc_driver);
53462306a36Sopenharmony_ci
53562306a36Sopenharmony_ciMODULE_AUTHOR("Marian Cichy <M.Cichy@pengutronix.de>");
53662306a36Sopenharmony_ciMODULE_DESCRIPTION("Freescale i.MX LCDC driver");
53762306a36Sopenharmony_ciMODULE_LICENSE("GPL");
538