xref: /openbmc/linux/drivers/gpu/drm/mxsfb/mxsfb_drv.c (revision bd296a59)
1c942fddfSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later
245d59d70SMarek Vasut /*
345d59d70SMarek Vasut  * Copyright (C) 2016 Marek Vasut <marex@denx.de>
445d59d70SMarek Vasut  *
545d59d70SMarek Vasut  * This code is based on drivers/video/fbdev/mxsfb.c :
645d59d70SMarek Vasut  * Copyright (C) 2010 Juergen Beisert, Pengutronix
745d59d70SMarek Vasut  * Copyright (C) 2008-2009 Freescale Semiconductor, Inc. All Rights Reserved.
845d59d70SMarek Vasut  * Copyright (C) 2008 Embedded Alley Solutions, Inc All Rights Reserved.
945d59d70SMarek Vasut  */
1045d59d70SMarek Vasut 
1145d59d70SMarek Vasut #include <linux/clk.h>
12d5742c6cSSam Ravnborg #include <linux/dma-mapping.h>
13ae1ed009SLaurent Pinchart #include <linux/io.h>
14d5742c6cSSam Ravnborg #include <linux/module.h>
1545d59d70SMarek Vasut #include <linux/of_device.h>
16ae1ed009SLaurent Pinchart #include <linux/platform_device.h>
1745d59d70SMarek Vasut #include <linux/pm_runtime.h>
1845d59d70SMarek Vasut 
1945d59d70SMarek Vasut #include <drm/drm_atomic_helper.h>
20ae1ed009SLaurent Pinchart #include <drm/drm_bridge.h>
21ae1ed009SLaurent Pinchart #include <drm/drm_connector.h>
22d5742c6cSSam Ravnborg #include <drm/drm_drv.h>
235fe96f6aSThomas Zimmermann #include <drm/drm_fbdev_dma.h>
24d5a0c816SStefan Agner #include <drm/drm_fourcc.h>
254a83c26aSDanilo Krummrich #include <drm/drm_gem_dma_helper.h>
2698f3eac5SNoralf Trønnes #include <drm/drm_gem_framebuffer_helper.h>
27ae1ed009SLaurent Pinchart #include <drm/drm_mode_config.h>
28d405054dSJavier Martinez Canillas #include <drm/drm_module.h>
2945d59d70SMarek Vasut #include <drm/drm_of.h>
30fcd70cd3SDaniel Vetter #include <drm/drm_probe_helper.h>
31d5742c6cSSam Ravnborg #include <drm/drm_vblank.h>
3245d59d70SMarek Vasut 
3345d59d70SMarek Vasut #include "mxsfb_drv.h"
3445d59d70SMarek Vasut #include "mxsfb_regs.h"
3545d59d70SMarek Vasut 
3645d59d70SMarek Vasut enum mxsfb_devtype {
3745d59d70SMarek Vasut 	MXSFB_V3,
3845d59d70SMarek Vasut 	MXSFB_V4,
39f6d94e71SLaurent Pinchart 	/*
40f6d94e71SLaurent Pinchart 	 * Starting at i.MX6 the hardware version register is gone, use the
41f6d94e71SLaurent Pinchart 	 * i.MX family number as the version.
42f6d94e71SLaurent Pinchart 	 */
43f6d94e71SLaurent Pinchart 	MXSFB_V6,
4445d59d70SMarek Vasut };
4545d59d70SMarek Vasut 
4645d59d70SMarek Vasut static const struct mxsfb_devdata mxsfb_devdata[] = {
4745d59d70SMarek Vasut 	[MXSFB_V3] = {
4845d59d70SMarek Vasut 		.transfer_count	= LCDC_V3_TRANSFER_COUNT,
4945d59d70SMarek Vasut 		.cur_buf	= LCDC_V3_CUR_BUF,
5045d59d70SMarek Vasut 		.next_buf	= LCDC_V3_NEXT_BUF,
5145d59d70SMarek Vasut 		.hs_wdth_mask	= 0xff,
5245d59d70SMarek Vasut 		.hs_wdth_shift	= 24,
5363aa581cSLaurent Pinchart 		.has_overlay	= false,
549891cb54SMarek Vasut 		.has_ctrl2	= false,
5505ecc678SMarek Vasut 		.has_crc32	= false,
5645d59d70SMarek Vasut 	},
5745d59d70SMarek Vasut 	[MXSFB_V4] = {
5845d59d70SMarek Vasut 		.transfer_count	= LCDC_V4_TRANSFER_COUNT,
5945d59d70SMarek Vasut 		.cur_buf	= LCDC_V4_CUR_BUF,
6045d59d70SMarek Vasut 		.next_buf	= LCDC_V4_NEXT_BUF,
6145d59d70SMarek Vasut 		.hs_wdth_mask	= 0x3fff,
6245d59d70SMarek Vasut 		.hs_wdth_shift	= 18,
6363aa581cSLaurent Pinchart 		.has_overlay	= false,
649891cb54SMarek Vasut 		.has_ctrl2	= true,
6505ecc678SMarek Vasut 		.has_crc32	= true,
6645d59d70SMarek Vasut 	},
67f6d94e71SLaurent Pinchart 	[MXSFB_V6] = {
68f6d94e71SLaurent Pinchart 		.transfer_count	= LCDC_V4_TRANSFER_COUNT,
69f6d94e71SLaurent Pinchart 		.cur_buf	= LCDC_V4_CUR_BUF,
70f6d94e71SLaurent Pinchart 		.next_buf	= LCDC_V4_NEXT_BUF,
71f6d94e71SLaurent Pinchart 		.hs_wdth_mask	= 0x3fff,
72f6d94e71SLaurent Pinchart 		.hs_wdth_shift	= 18,
7363aa581cSLaurent Pinchart 		.has_overlay	= true,
749891cb54SMarek Vasut 		.has_ctrl2	= true,
7505ecc678SMarek Vasut 		.has_crc32	= true,
76f6d94e71SLaurent Pinchart 	},
7745d59d70SMarek Vasut };
7845d59d70SMarek Vasut 
mxsfb_enable_axi_clk(struct mxsfb_drm_private * mxsfb)7945d59d70SMarek Vasut void mxsfb_enable_axi_clk(struct mxsfb_drm_private *mxsfb)
8045d59d70SMarek Vasut {
8145d59d70SMarek Vasut 	clk_prepare_enable(mxsfb->clk_axi);
8245d59d70SMarek Vasut }
8345d59d70SMarek Vasut 
mxsfb_disable_axi_clk(struct mxsfb_drm_private * mxsfb)8445d59d70SMarek Vasut void mxsfb_disable_axi_clk(struct mxsfb_drm_private *mxsfb)
8545d59d70SMarek Vasut {
8645d59d70SMarek Vasut 	clk_disable_unprepare(mxsfb->clk_axi);
8745d59d70SMarek Vasut }
8845d59d70SMarek Vasut 
89d5a0c816SStefan Agner static struct drm_framebuffer *
mxsfb_fb_create(struct drm_device * dev,struct drm_file * file_priv,const struct drm_mode_fb_cmd2 * mode_cmd)90d5a0c816SStefan Agner mxsfb_fb_create(struct drm_device *dev, struct drm_file *file_priv,
91d5a0c816SStefan Agner 		const struct drm_mode_fb_cmd2 *mode_cmd)
92d5a0c816SStefan Agner {
93d5a0c816SStefan Agner 	const struct drm_format_info *info;
94d5a0c816SStefan Agner 
95d5a0c816SStefan Agner 	info = drm_get_format_info(dev, mode_cmd);
96d5a0c816SStefan Agner 	if (!info)
97d5a0c816SStefan Agner 		return ERR_PTR(-EINVAL);
98d5a0c816SStefan Agner 
99d5a0c816SStefan Agner 	if (mode_cmd->width * info->cpp[0] != mode_cmd->pitches[0]) {
100d5a0c816SStefan Agner 		dev_dbg(dev->dev, "Invalid pitch: fb width must match pitch\n");
101d5a0c816SStefan Agner 		return ERR_PTR(-EINVAL);
102d5a0c816SStefan Agner 	}
103d5a0c816SStefan Agner 
104d5a0c816SStefan Agner 	return drm_gem_fb_create(dev, file_priv, mode_cmd);
105d5a0c816SStefan Agner }
106d5a0c816SStefan Agner 
10745d59d70SMarek Vasut static const struct drm_mode_config_funcs mxsfb_mode_config_funcs = {
108d5a0c816SStefan Agner 	.fb_create		= mxsfb_fb_create,
10945d59d70SMarek Vasut 	.atomic_check		= drm_atomic_helper_check,
11045d59d70SMarek Vasut 	.atomic_commit		= drm_atomic_helper_commit,
11145d59d70SMarek Vasut };
11245d59d70SMarek Vasut 
1139f19fd3bSLeonard Crestez static const struct drm_mode_config_helper_funcs mxsfb_mode_config_helpers = {
1149f19fd3bSLeonard Crestez 	.atomic_commit_tail = drm_atomic_helper_commit_tail_rpm,
1159f19fd3bSLeonard Crestez };
1169f19fd3bSLeonard Crestez 
mxsfb_attach_bridge(struct mxsfb_drm_private * mxsfb)117c42001e3SLaurent Pinchart static int mxsfb_attach_bridge(struct mxsfb_drm_private *mxsfb)
118c42001e3SLaurent Pinchart {
119c42001e3SLaurent Pinchart 	struct drm_device *drm = mxsfb->drm;
120c42001e3SLaurent Pinchart 	struct drm_connector_list_iter iter;
121c42001e3SLaurent Pinchart 	struct drm_panel *panel;
122c42001e3SLaurent Pinchart 	struct drm_bridge *bridge;
123c42001e3SLaurent Pinchart 	int ret;
124c42001e3SLaurent Pinchart 
125c42001e3SLaurent Pinchart 	ret = drm_of_find_panel_or_bridge(drm->dev->of_node, 0, 0, &panel,
126c42001e3SLaurent Pinchart 					  &bridge);
127c42001e3SLaurent Pinchart 	if (ret)
128c42001e3SLaurent Pinchart 		return ret;
129c42001e3SLaurent Pinchart 
130c42001e3SLaurent Pinchart 	if (panel) {
131c42001e3SLaurent Pinchart 		bridge = devm_drm_panel_bridge_add_typed(drm->dev, panel,
132c42001e3SLaurent Pinchart 							 DRM_MODE_CONNECTOR_DPI);
133c42001e3SLaurent Pinchart 		if (IS_ERR(bridge))
134c42001e3SLaurent Pinchart 			return PTR_ERR(bridge);
135c42001e3SLaurent Pinchart 	}
136c42001e3SLaurent Pinchart 
137c42001e3SLaurent Pinchart 	if (!bridge)
138c42001e3SLaurent Pinchart 		return -ENODEV;
139c42001e3SLaurent Pinchart 
140ae1ed009SLaurent Pinchart 	ret = drm_bridge_attach(&mxsfb->encoder, bridge, NULL, 0);
141ee46d16dSGuido Günther 	if (ret)
142ee46d16dSGuido Günther 		return dev_err_probe(drm->dev, ret, "Failed to attach bridge\n");
143c42001e3SLaurent Pinchart 
144c42001e3SLaurent Pinchart 	mxsfb->bridge = bridge;
145c42001e3SLaurent Pinchart 
146c42001e3SLaurent Pinchart 	/*
147c42001e3SLaurent Pinchart 	 * Get hold of the connector. This is a bit of a hack, until the bridge
148c42001e3SLaurent Pinchart 	 * API gives us bus flags and formats.
149c42001e3SLaurent Pinchart 	 */
150c42001e3SLaurent Pinchart 	drm_connector_list_iter_begin(drm, &iter);
151c42001e3SLaurent Pinchart 	mxsfb->connector = drm_connector_list_iter_next(&iter);
152c42001e3SLaurent Pinchart 	drm_connector_list_iter_end(&iter);
153c42001e3SLaurent Pinchart 
154c42001e3SLaurent Pinchart 	return 0;
155c42001e3SLaurent Pinchart }
156c42001e3SLaurent Pinchart 
mxsfb_irq_handler(int irq,void * data)1575fc40f41SThomas Zimmermann static irqreturn_t mxsfb_irq_handler(int irq, void *data)
1585fc40f41SThomas Zimmermann {
1595fc40f41SThomas Zimmermann 	struct drm_device *drm = data;
1605fc40f41SThomas Zimmermann 	struct mxsfb_drm_private *mxsfb = drm->dev_private;
16105ecc678SMarek Vasut 	u32 reg, crc;
16205ecc678SMarek Vasut 	u64 vbc;
1635fc40f41SThomas Zimmermann 
1645fc40f41SThomas Zimmermann 	reg = readl(mxsfb->base + LCDC_CTRL1);
1655fc40f41SThomas Zimmermann 
16605ecc678SMarek Vasut 	if (reg & CTRL1_CUR_FRAME_DONE_IRQ) {
1675fc40f41SThomas Zimmermann 		drm_crtc_handle_vblank(&mxsfb->crtc);
16805ecc678SMarek Vasut 		if (mxsfb->crc_active) {
16905ecc678SMarek Vasut 			crc = readl(mxsfb->base + LCDC_V4_CRC_STAT);
17005ecc678SMarek Vasut 			vbc = drm_crtc_accurate_vblank_count(&mxsfb->crtc);
17105ecc678SMarek Vasut 			drm_crtc_add_crc_entry(&mxsfb->crtc, true, vbc, &crc);
17205ecc678SMarek Vasut 		}
17305ecc678SMarek Vasut 	}
1745fc40f41SThomas Zimmermann 
1755fc40f41SThomas Zimmermann 	writel(CTRL1_CUR_FRAME_DONE_IRQ, mxsfb->base + LCDC_CTRL1 + REG_CLR);
1765fc40f41SThomas Zimmermann 
1775fc40f41SThomas Zimmermann 	return IRQ_HANDLED;
1785fc40f41SThomas Zimmermann }
1795fc40f41SThomas Zimmermann 
mxsfb_irq_disable(struct drm_device * drm)1805fc40f41SThomas Zimmermann static void mxsfb_irq_disable(struct drm_device *drm)
1815fc40f41SThomas Zimmermann {
1825fc40f41SThomas Zimmermann 	struct mxsfb_drm_private *mxsfb = drm->dev_private;
1835fc40f41SThomas Zimmermann 
1845fc40f41SThomas Zimmermann 	mxsfb_enable_axi_clk(mxsfb);
1853cfc1830SMarek Vasut 
1863cfc1830SMarek Vasut 	/* Disable and clear VBLANK IRQ */
1873cfc1830SMarek Vasut 	writel(CTRL1_CUR_FRAME_DONE_IRQ_EN, mxsfb->base + LCDC_CTRL1 + REG_CLR);
1883cfc1830SMarek Vasut 	writel(CTRL1_CUR_FRAME_DONE_IRQ, mxsfb->base + LCDC_CTRL1 + REG_CLR);
1893cfc1830SMarek Vasut 
1905fc40f41SThomas Zimmermann 	mxsfb_disable_axi_clk(mxsfb);
1915fc40f41SThomas Zimmermann }
1925fc40f41SThomas Zimmermann 
mxsfb_irq_install(struct drm_device * dev,int irq)1935fc40f41SThomas Zimmermann static int mxsfb_irq_install(struct drm_device *dev, int irq)
1945fc40f41SThomas Zimmermann {
1955fc40f41SThomas Zimmermann 	if (irq == IRQ_NOTCONNECTED)
1965fc40f41SThomas Zimmermann 		return -ENOTCONN;
1975fc40f41SThomas Zimmermann 
1985fc40f41SThomas Zimmermann 	mxsfb_irq_disable(dev);
1995fc40f41SThomas Zimmermann 
2005fc40f41SThomas Zimmermann 	return request_irq(irq, mxsfb_irq_handler, 0,  dev->driver->name, dev);
2015fc40f41SThomas Zimmermann }
2025fc40f41SThomas Zimmermann 
mxsfb_irq_uninstall(struct drm_device * dev)2035fc40f41SThomas Zimmermann static void mxsfb_irq_uninstall(struct drm_device *dev)
2045fc40f41SThomas Zimmermann {
2055fc40f41SThomas Zimmermann 	struct mxsfb_drm_private *mxsfb = dev->dev_private;
2065fc40f41SThomas Zimmermann 
2075fc40f41SThomas Zimmermann 	mxsfb_irq_disable(dev);
2085fc40f41SThomas Zimmermann 	free_irq(mxsfb->irq, dev);
2095fc40f41SThomas Zimmermann }
2105fc40f41SThomas Zimmermann 
mxsfb_load(struct drm_device * drm,const struct mxsfb_devdata * devdata)211c6ddee82SLaurent Pinchart static int mxsfb_load(struct drm_device *drm,
212c6ddee82SLaurent Pinchart 		      const struct mxsfb_devdata *devdata)
21345d59d70SMarek Vasut {
21445d59d70SMarek Vasut 	struct platform_device *pdev = to_platform_device(drm->dev);
21545d59d70SMarek Vasut 	struct mxsfb_drm_private *mxsfb;
21645d59d70SMarek Vasut 	struct resource *res;
21745d59d70SMarek Vasut 	int ret;
21845d59d70SMarek Vasut 
21945d59d70SMarek Vasut 	mxsfb = devm_kzalloc(&pdev->dev, sizeof(*mxsfb), GFP_KERNEL);
22045d59d70SMarek Vasut 	if (!mxsfb)
22145d59d70SMarek Vasut 		return -ENOMEM;
22245d59d70SMarek Vasut 
223c42001e3SLaurent Pinchart 	mxsfb->drm = drm;
22445d59d70SMarek Vasut 	drm->dev_private = mxsfb;
225c6ddee82SLaurent Pinchart 	mxsfb->devdata = devdata;
22645d59d70SMarek Vasut 
22745d59d70SMarek Vasut 	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
22845d59d70SMarek Vasut 	mxsfb->base = devm_ioremap_resource(drm->dev, res);
22945d59d70SMarek Vasut 	if (IS_ERR(mxsfb->base))
23045d59d70SMarek Vasut 		return PTR_ERR(mxsfb->base);
23145d59d70SMarek Vasut 
23245d59d70SMarek Vasut 	mxsfb->clk = devm_clk_get(drm->dev, NULL);
23345d59d70SMarek Vasut 	if (IS_ERR(mxsfb->clk))
23445d59d70SMarek Vasut 		return PTR_ERR(mxsfb->clk);
23545d59d70SMarek Vasut 
2366d6defd4SUwe Kleine-König 	mxsfb->clk_axi = devm_clk_get_optional(drm->dev, "axi");
23745d59d70SMarek Vasut 	if (IS_ERR(mxsfb->clk_axi))
2386d6defd4SUwe Kleine-König 		return PTR_ERR(mxsfb->clk_axi);
23945d59d70SMarek Vasut 
24045d59d70SMarek Vasut 	mxsfb->clk_disp_axi = devm_clk_get(drm->dev, "disp_axi");
24145d59d70SMarek Vasut 	if (IS_ERR(mxsfb->clk_disp_axi))
24245d59d70SMarek Vasut 		mxsfb->clk_disp_axi = NULL;
24345d59d70SMarek Vasut 
24445d59d70SMarek Vasut 	ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32));
24545d59d70SMarek Vasut 	if (ret)
24645d59d70SMarek Vasut 		return ret;
24745d59d70SMarek Vasut 
24845d59d70SMarek Vasut 	pm_runtime_enable(drm->dev);
24945d59d70SMarek Vasut 
25045d59d70SMarek Vasut 	/* Modeset init */
25145d59d70SMarek Vasut 	drm_mode_config_init(drm);
25245d59d70SMarek Vasut 
253ae1ed009SLaurent Pinchart 	ret = mxsfb_kms_init(mxsfb);
25445d59d70SMarek Vasut 	if (ret < 0) {
255ae1ed009SLaurent Pinchart 		dev_err(drm->dev, "Failed to initialize KMS pipeline\n");
25645d59d70SMarek Vasut 		goto err_vblank;
25745d59d70SMarek Vasut 	}
25845d59d70SMarek Vasut 
259b9f59376SLaurent Pinchart 	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
260b9f59376SLaurent Pinchart 	if (ret < 0) {
261b9f59376SLaurent Pinchart 		dev_err(drm->dev, "Failed to initialise vblank\n");
262b9f59376SLaurent Pinchart 		goto err_vblank;
263b9f59376SLaurent Pinchart 	}
264b9f59376SLaurent Pinchart 
265b9f59376SLaurent Pinchart 	/* Start with vertical blanking interrupt reporting disabled. */
266b9f59376SLaurent Pinchart 	drm_crtc_vblank_off(&mxsfb->crtc);
267b9f59376SLaurent Pinchart 
268c42001e3SLaurent Pinchart 	ret = mxsfb_attach_bridge(mxsfb);
269d0234043SRobert Chiras 	if (ret) {
270c5e804baSAlexander Stein 		dev_err_probe(drm->dev, ret, "Cannot connect bridge\n");
271d0234043SRobert Chiras 		goto err_vblank;
272d0234043SRobert Chiras 	}
27345d59d70SMarek Vasut 
27445d59d70SMarek Vasut 	drm->mode_config.min_width	= MXSFB_MIN_XRES;
27545d59d70SMarek Vasut 	drm->mode_config.min_height	= MXSFB_MIN_YRES;
27645d59d70SMarek Vasut 	drm->mode_config.max_width	= MXSFB_MAX_XRES;
27745d59d70SMarek Vasut 	drm->mode_config.max_height	= MXSFB_MAX_YRES;
27845d59d70SMarek Vasut 	drm->mode_config.funcs		= &mxsfb_mode_config_funcs;
2799f19fd3bSLeonard Crestez 	drm->mode_config.helper_private	= &mxsfb_mode_config_helpers;
28045d59d70SMarek Vasut 
28145d59d70SMarek Vasut 	drm_mode_config_reset(drm);
28245d59d70SMarek Vasut 
2835fc40f41SThomas Zimmermann 	ret = platform_get_irq(pdev, 0);
2845fc40f41SThomas Zimmermann 	if (ret < 0)
2855fc40f41SThomas Zimmermann 		goto err_vblank;
2865fc40f41SThomas Zimmermann 	mxsfb->irq = ret;
2875fc40f41SThomas Zimmermann 
28845d59d70SMarek Vasut 	pm_runtime_get_sync(drm->dev);
2895fc40f41SThomas Zimmermann 	ret = mxsfb_irq_install(drm, mxsfb->irq);
29045d59d70SMarek Vasut 	pm_runtime_put_sync(drm->dev);
29145d59d70SMarek Vasut 
29245d59d70SMarek Vasut 	if (ret < 0) {
29345d59d70SMarek Vasut 		dev_err(drm->dev, "Failed to install IRQ handler\n");
294c42001e3SLaurent Pinchart 		goto err_vblank;
29545d59d70SMarek Vasut 	}
29645d59d70SMarek Vasut 
29745d59d70SMarek Vasut 	drm_kms_helper_poll_init(drm);
29845d59d70SMarek Vasut 
29945d59d70SMarek Vasut 	platform_set_drvdata(pdev, drm);
30045d59d70SMarek Vasut 
30145d59d70SMarek Vasut 	drm_helper_hpd_irq_event(drm);
30245d59d70SMarek Vasut 
30345d59d70SMarek Vasut 	return 0;
30445d59d70SMarek Vasut 
30545d59d70SMarek Vasut err_vblank:
30645d59d70SMarek Vasut 	pm_runtime_disable(drm->dev);
30745d59d70SMarek Vasut 
30845d59d70SMarek Vasut 	return ret;
30945d59d70SMarek Vasut }
31045d59d70SMarek Vasut 
mxsfb_unload(struct drm_device * drm)31145d59d70SMarek Vasut static void mxsfb_unload(struct drm_device *drm)
31245d59d70SMarek Vasut {
31345d59d70SMarek Vasut 	drm_kms_helper_poll_fini(drm);
31445d59d70SMarek Vasut 	drm_mode_config_cleanup(drm);
31545d59d70SMarek Vasut 
31645d59d70SMarek Vasut 	pm_runtime_get_sync(drm->dev);
3175fc40f41SThomas Zimmermann 	mxsfb_irq_uninstall(drm);
31845d59d70SMarek Vasut 	pm_runtime_put_sync(drm->dev);
31945d59d70SMarek Vasut 
32045d59d70SMarek Vasut 	drm->dev_private = NULL;
32145d59d70SMarek Vasut 
32245d59d70SMarek Vasut 	pm_runtime_disable(drm->dev);
32345d59d70SMarek Vasut }
32445d59d70SMarek Vasut 
3254a83c26aSDanilo Krummrich DEFINE_DRM_GEM_DMA_FOPS(fops);
32645d59d70SMarek Vasut 
32770a59dd8SDaniel Vetter static const struct drm_driver mxsfb_driver = {
3280424fdafSDaniel Vetter 	.driver_features	= DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
3294a83c26aSDanilo Krummrich 	DRM_GEM_DMA_DRIVER_OPS,
33045d59d70SMarek Vasut 	.fops	= &fops,
33145d59d70SMarek Vasut 	.name	= "mxsfb-drm",
33245d59d70SMarek Vasut 	.desc	= "MXSFB Controller DRM",
33345d59d70SMarek Vasut 	.date	= "20160824",
33445d59d70SMarek Vasut 	.major	= 1,
33545d59d70SMarek Vasut 	.minor	= 0,
33645d59d70SMarek Vasut };
33745d59d70SMarek Vasut 
33845d59d70SMarek Vasut static const struct of_device_id mxsfb_dt_ids[] = {
339c6ddee82SLaurent Pinchart 	{ .compatible = "fsl,imx23-lcdif", .data = &mxsfb_devdata[MXSFB_V3], },
340c6ddee82SLaurent Pinchart 	{ .compatible = "fsl,imx28-lcdif", .data = &mxsfb_devdata[MXSFB_V4], },
341c6ddee82SLaurent Pinchart 	{ .compatible = "fsl,imx6sx-lcdif", .data = &mxsfb_devdata[MXSFB_V6], },
34245d59d70SMarek Vasut 	{ /* sentinel */ }
34345d59d70SMarek Vasut };
34445d59d70SMarek Vasut MODULE_DEVICE_TABLE(of, mxsfb_dt_ids);
34545d59d70SMarek Vasut 
mxsfb_probe(struct platform_device * pdev)34645d59d70SMarek Vasut static int mxsfb_probe(struct platform_device *pdev)
34745d59d70SMarek Vasut {
34845d59d70SMarek Vasut 	struct drm_device *drm;
34945d59d70SMarek Vasut 	const struct of_device_id *of_id =
35045d59d70SMarek Vasut 			of_match_device(mxsfb_dt_ids, &pdev->dev);
35145d59d70SMarek Vasut 	int ret;
35245d59d70SMarek Vasut 
35345d59d70SMarek Vasut 	if (!pdev->dev.of_node)
35445d59d70SMarek Vasut 		return -ENODEV;
35545d59d70SMarek Vasut 
35645d59d70SMarek Vasut 	drm = drm_dev_alloc(&mxsfb_driver, &pdev->dev);
357e89e50acSDan Carpenter 	if (IS_ERR(drm))
358e89e50acSDan Carpenter 		return PTR_ERR(drm);
35945d59d70SMarek Vasut 
360c6ddee82SLaurent Pinchart 	ret = mxsfb_load(drm, of_id->data);
36145d59d70SMarek Vasut 	if (ret)
36245d59d70SMarek Vasut 		goto err_free;
36345d59d70SMarek Vasut 
36445d59d70SMarek Vasut 	ret = drm_dev_register(drm, 0);
36545d59d70SMarek Vasut 	if (ret)
36645d59d70SMarek Vasut 		goto err_unload;
36745d59d70SMarek Vasut 
3685fe96f6aSThomas Zimmermann 	drm_fbdev_dma_setup(drm, 32);
3698e93f102SNoralf Trønnes 
37045d59d70SMarek Vasut 	return 0;
37145d59d70SMarek Vasut 
37245d59d70SMarek Vasut err_unload:
37345d59d70SMarek Vasut 	mxsfb_unload(drm);
37445d59d70SMarek Vasut err_free:
375808bad32SFernando Ramos 	drm_dev_put(drm);
37645d59d70SMarek Vasut 
37745d59d70SMarek Vasut 	return ret;
37845d59d70SMarek Vasut }
37945d59d70SMarek Vasut 
mxsfb_remove(struct platform_device * pdev)380*bd296a59SUwe Kleine-König static void mxsfb_remove(struct platform_device *pdev)
38145d59d70SMarek Vasut {
38245d59d70SMarek Vasut 	struct drm_device *drm = platform_get_drvdata(pdev);
38345d59d70SMarek Vasut 
38445d59d70SMarek Vasut 	drm_dev_unregister(drm);
38572f6c033SMarek Vasut 	drm_atomic_helper_shutdown(drm);
38645d59d70SMarek Vasut 	mxsfb_unload(drm);
387808bad32SFernando Ramos 	drm_dev_put(drm);
38845d59d70SMarek Vasut }
38945d59d70SMarek Vasut 
mxsfb_shutdown(struct platform_device * pdev)390653af51cSMarek Vasut static void mxsfb_shutdown(struct platform_device *pdev)
391653af51cSMarek Vasut {
392653af51cSMarek Vasut 	struct drm_device *drm = platform_get_drvdata(pdev);
393653af51cSMarek Vasut 
394653af51cSMarek Vasut 	drm_atomic_helper_shutdown(drm);
395653af51cSMarek Vasut }
396653af51cSMarek Vasut 
397f0525a1cSLeonard Crestez #ifdef CONFIG_PM_SLEEP
mxsfb_suspend(struct device * dev)398f0525a1cSLeonard Crestez static int mxsfb_suspend(struct device *dev)
399f0525a1cSLeonard Crestez {
400f0525a1cSLeonard Crestez 	struct drm_device *drm = dev_get_drvdata(dev);
401f0525a1cSLeonard Crestez 
402f0525a1cSLeonard Crestez 	return drm_mode_config_helper_suspend(drm);
403f0525a1cSLeonard Crestez }
404f0525a1cSLeonard Crestez 
mxsfb_resume(struct device * dev)405f0525a1cSLeonard Crestez static int mxsfb_resume(struct device *dev)
406f0525a1cSLeonard Crestez {
407f0525a1cSLeonard Crestez 	struct drm_device *drm = dev_get_drvdata(dev);
408f0525a1cSLeonard Crestez 
409f0525a1cSLeonard Crestez 	return drm_mode_config_helper_resume(drm);
410f0525a1cSLeonard Crestez }
411f0525a1cSLeonard Crestez #endif
412f0525a1cSLeonard Crestez 
413f0525a1cSLeonard Crestez static const struct dev_pm_ops mxsfb_pm_ops = {
414f0525a1cSLeonard Crestez 	SET_SYSTEM_SLEEP_PM_OPS(mxsfb_suspend, mxsfb_resume)
415f0525a1cSLeonard Crestez };
416f0525a1cSLeonard Crestez 
41745d59d70SMarek Vasut static struct platform_driver mxsfb_platform_driver = {
41845d59d70SMarek Vasut 	.probe		= mxsfb_probe,
419*bd296a59SUwe Kleine-König 	.remove_new	= mxsfb_remove,
420653af51cSMarek Vasut 	.shutdown	= mxsfb_shutdown,
42145d59d70SMarek Vasut 	.driver	= {
42245d59d70SMarek Vasut 		.name		= "mxsfb",
42345d59d70SMarek Vasut 		.of_match_table	= mxsfb_dt_ids,
424f0525a1cSLeonard Crestez 		.pm		= &mxsfb_pm_ops,
42545d59d70SMarek Vasut 	},
42645d59d70SMarek Vasut };
42745d59d70SMarek Vasut 
428d405054dSJavier Martinez Canillas drm_module_platform_driver(mxsfb_platform_driver);
42945d59d70SMarek Vasut 
43045d59d70SMarek Vasut MODULE_AUTHOR("Marek Vasut <marex@denx.de>");
43145d59d70SMarek Vasut MODULE_DESCRIPTION("Freescale MXS DRM/KMS driver");
43245d59d70SMarek Vasut MODULE_LICENSE("GPL");
433