コード例 #1
0
ファイル: malidp_drv.c プロジェクト: acton393/linux
static void malidp_unbind(struct device *dev)
{
	struct drm_device *drm = dev_get_drvdata(dev);
	struct malidp_drm *malidp = drm->dev_private;
	struct malidp_hw_device *hwdev = malidp->dev;

	if (malidp->fbdev) {
		drm_fbdev_cma_fini(malidp->fbdev);
		malidp->fbdev = NULL;
	}
	drm_kms_helper_poll_fini(drm);
	malidp_se_irq_fini(drm);
	malidp_de_irq_fini(drm);
	drm_vblank_cleanup(drm);
	component_unbind_all(dev, drm);
	of_node_put(malidp->crtc.port);
	malidp->crtc.port = NULL;
	drm_dev_unregister(drm);
	malidp_de_planes_destroy(drm);
	drm_mode_config_cleanup(drm);
	drm->dev_private = NULL;
	dev_set_drvdata(dev, NULL);
	clk_disable_unprepare(hwdev->mclk);
	clk_disable_unprepare(hwdev->aclk);
	clk_disable_unprepare(hwdev->pclk);
	drm_dev_unref(drm);
	of_reserved_mem_device_release(dev);
}
コード例 #2
0
ファイル: sun4i_drv.c プロジェクト: Lyude/linux
static void sun4i_drv_unbind(struct device *dev)
{
	struct drm_device *drm = dev_get_drvdata(dev);

	drm_dev_unregister(drm);
	drm_kms_helper_poll_fini(drm);
	sun4i_framebuffer_free(drm);
	drm_mode_config_cleanup(drm);
	of_reserved_mem_device_release(dev);
	drm_dev_put(drm);
}
コード例 #3
0
ファイル: sun4i_drv.c プロジェクト: grate-driver/linux
static void sun4i_drv_unbind(struct device *dev)
{
	struct drm_device *drm = dev_get_drvdata(dev);

	drm_dev_unregister(drm);
	drm_kms_helper_poll_fini(drm);
	drm_atomic_helper_shutdown(drm);
	drm_mode_config_cleanup(drm);

	component_unbind_all(dev, NULL);
	of_reserved_mem_device_release(dev);

	drm_dev_put(drm);
}
コード例 #4
0
ファイル: st_remoteproc.c プロジェクト: forgivemyheart/linux
static int st_rproc_remove(struct platform_device *pdev)
{
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct st_rproc *ddata = rproc->priv;

	rproc_del(rproc);

	clk_disable_unprepare(ddata->clk);

	of_reserved_mem_device_release(&pdev->dev);

	rproc_free(rproc);

	return 0;
}
コード例 #5
0
ファイル: da8xx_remoteproc.c プロジェクト: krzk/linux
static int da8xx_rproc_remove(struct platform_device *pdev)
{
	struct rproc *rproc = platform_get_drvdata(pdev);
	struct da8xx_rproc *drproc = (struct da8xx_rproc *)rproc->priv;
	struct device *dev = &pdev->dev;

	/*
	 * The devm subsystem might end up releasing things before
	 * freeing the irq, thus allowing an interrupt to sneak in while
	 * the device is being removed.  This should prevent that.
	 */
	disable_irq(drproc->irq);

	rproc_del(rproc);
	rproc_free(rproc);
	if (dev->of_node)
		of_reserved_mem_device_release(dev);

	return 0;
}
コード例 #6
0
ファイル: zynqmp_dpsub.c プロジェクト: Xilinx/linux-xlnx
static int zynqmp_dpsub_remove(struct platform_device *pdev)
{
	struct zynqmp_dpsub *dpsub = platform_get_drvdata(pdev);
	int err, ret = 0;

	xlnx_drm_pipeline_exit(dpsub->master);
	of_platform_depopulate(&pdev->dev);
	of_reserved_mem_device_release(&pdev->dev);
	component_del(&pdev->dev, &zynqmp_dpsub_component_ops);

	err = zynqmp_disp_remove(pdev);
	if (err)
		ret = -EIO;

	err = zynqmp_dp_remove(pdev);
	if (err)
		ret = -EIO;

	pm_runtime_disable(&pdev->dev);

	return err;
}
コード例 #7
0
ファイル: hdlcd_drv.c プロジェクト: acton393/linux
static void hdlcd_drm_unbind(struct device *dev)
{
	struct drm_device *drm = dev_get_drvdata(dev);
	struct hdlcd_drm_private *hdlcd = drm->dev_private;

	if (hdlcd->fbdev) {
		drm_fbdev_cma_fini(hdlcd->fbdev);
		hdlcd->fbdev = NULL;
	}
	drm_kms_helper_poll_fini(drm);
	component_unbind_all(dev, drm);
	drm_vblank_cleanup(drm);
	pm_runtime_get_sync(drm->dev);
	drm_irq_uninstall(drm);
	pm_runtime_put_sync(drm->dev);
	pm_runtime_disable(drm->dev);
	of_reserved_mem_device_release(drm->dev);
	drm_mode_config_cleanup(drm);
	drm_dev_unregister(drm);
	drm_dev_unref(drm);
	drm->dev_private = NULL;
	dev_set_drvdata(dev, NULL);
}
コード例 #8
0
ファイル: zynqmp_dpsub.c プロジェクト: Xilinx/linux-xlnx
static int zynqmp_dpsub_probe(struct platform_device *pdev)
{
	struct zynqmp_dpsub *dpsub;
	int ret;

	dpsub = devm_kzalloc(&pdev->dev, sizeof(*dpsub), GFP_KERNEL);
	if (!dpsub)
		return -ENOMEM;

	/* Sub-driver will access dpsub from drvdata */
	platform_set_drvdata(pdev, dpsub);
	pm_runtime_enable(&pdev->dev);

	/*
	 * DP should be probed first so that the zynqmp_disp can set the output
	 * format accordingly.
	 */
	ret = zynqmp_dp_probe(pdev);
	if (ret)
		goto err_pm;

	ret = zynqmp_disp_probe(pdev);
	if (ret)
		goto err_dp;

	ret = component_add(&pdev->dev, &zynqmp_dpsub_component_ops);
	if (ret)
		goto err_disp;

	/* Try the reserved memory. Proceed if there's none */
	of_reserved_mem_device_init(&pdev->dev);

	/* Populate the sound child nodes */
	ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
	if (ret) {
		dev_err(&pdev->dev, "failed to populate child nodes\n");
		goto err_rmem;
	}

	dpsub->master = xlnx_drm_pipeline_init(pdev);
	if (IS_ERR(dpsub->master)) {
		dev_err(&pdev->dev, "failed to initialize the drm pipeline\n");
		goto err_populate;
	}

	dev_info(&pdev->dev, "ZynqMP DisplayPort Subsystem driver probed");

	return 0;

err_populate:
	of_platform_depopulate(&pdev->dev);
err_rmem:
	of_reserved_mem_device_release(&pdev->dev);
	component_del(&pdev->dev, &zynqmp_dpsub_component_ops);
err_disp:
	zynqmp_disp_remove(pdev);
err_dp:
	zynqmp_dp_remove(pdev);
err_pm:
	pm_runtime_disable(&pdev->dev);
	return ret;
}
コード例 #9
0
ファイル: malidp_drv.c プロジェクト: acton393/linux
static int malidp_bind(struct device *dev)
{
	struct resource *res;
	struct drm_device *drm;
	struct device_node *ep;
	struct malidp_drm *malidp;
	struct malidp_hw_device *hwdev;
	struct platform_device *pdev = to_platform_device(dev);
	/* number of lines for the R, G and B output */
	u8 output_width[MAX_OUTPUT_CHANNELS];
	int ret = 0, i;
	u32 version, out_depth = 0;

	malidp = devm_kzalloc(dev, sizeof(*malidp), GFP_KERNEL);
	if (!malidp)
		return -ENOMEM;

	hwdev = devm_kzalloc(dev, sizeof(*hwdev), GFP_KERNEL);
	if (!hwdev)
		return -ENOMEM;

	/*
	 * copy the associated data from malidp_drm_of_match to avoid
	 * having to keep a reference to the OF node after binding
	 */
	memcpy(hwdev, of_device_get_match_data(dev), sizeof(*hwdev));
	malidp->dev = hwdev;

	INIT_LIST_HEAD(&malidp->event_list);

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	hwdev->regs = devm_ioremap_resource(dev, res);
	if (IS_ERR(hwdev->regs))
		return PTR_ERR(hwdev->regs);

	hwdev->pclk = devm_clk_get(dev, "pclk");
	if (IS_ERR(hwdev->pclk))
		return PTR_ERR(hwdev->pclk);

	hwdev->aclk = devm_clk_get(dev, "aclk");
	if (IS_ERR(hwdev->aclk))
		return PTR_ERR(hwdev->aclk);

	hwdev->mclk = devm_clk_get(dev, "mclk");
	if (IS_ERR(hwdev->mclk))
		return PTR_ERR(hwdev->mclk);

	hwdev->pxlclk = devm_clk_get(dev, "pxlclk");
	if (IS_ERR(hwdev->pxlclk))
		return PTR_ERR(hwdev->pxlclk);

	/* Get the optional framebuffer memory resource */
	ret = of_reserved_mem_device_init(dev);
	if (ret && ret != -ENODEV)
		return ret;

	drm = drm_dev_alloc(&malidp_driver, dev);
	if (IS_ERR(drm)) {
		ret = PTR_ERR(drm);
		goto alloc_fail;
	}

	/* Enable APB clock in order to get access to the registers */
	clk_prepare_enable(hwdev->pclk);
	/*
	 * Enable AXI clock and main clock so that prefetch can start once
	 * the registers are set
	 */
	clk_prepare_enable(hwdev->aclk);
	clk_prepare_enable(hwdev->mclk);

	ret = hwdev->query_hw(hwdev);
	if (ret) {
		DRM_ERROR("Invalid HW configuration\n");
		goto query_hw_fail;
	}

	version = malidp_hw_read(hwdev, hwdev->map.dc_base + MALIDP_DE_CORE_ID);
	DRM_INFO("found ARM Mali-DP%3x version r%dp%d\n", version >> 16,
		 (version >> 12) & 0xf, (version >> 8) & 0xf);

	/* set the number of lines used for output of RGB data */
	ret = of_property_read_u8_array(dev->of_node,
					"arm,malidp-output-port-lines",
					output_width, MAX_OUTPUT_CHANNELS);
	if (ret)
		goto query_hw_fail;

	for (i = 0; i < MAX_OUTPUT_CHANNELS; i++)
		out_depth = (out_depth << 8) | (output_width[i] & 0xf);
	malidp_hw_write(hwdev, out_depth, hwdev->map.out_depth_base);

	drm->dev_private = malidp;
	dev_set_drvdata(dev, drm);
	atomic_set(&malidp->config_valid, 0);
	init_waitqueue_head(&malidp->wq);

	ret = malidp_init(drm);
	if (ret < 0)
		goto init_fail;

	ret = drm_dev_register(drm, 0);
	if (ret)
		goto register_fail;

	/* Set the CRTC's port so that the encoder component can find it */
	ep = of_graph_get_next_endpoint(dev->of_node, NULL);
	if (!ep) {
		ret = -EINVAL;
		goto port_fail;
	}
	malidp->crtc.port = of_get_next_parent(ep);

	ret = component_bind_all(dev, drm);
	if (ret) {
		DRM_ERROR("Failed to bind all components\n");
		goto bind_fail;
	}

	ret = malidp_irq_init(pdev);
	if (ret < 0)
		goto irq_init_fail;

	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
	if (ret < 0) {
		DRM_ERROR("failed to initialise vblank\n");
		goto vblank_fail;
	}

	drm_mode_config_reset(drm);

	malidp->fbdev = drm_fbdev_cma_init(drm, 32, drm->mode_config.num_crtc,
					   drm->mode_config.num_connector);

	if (IS_ERR(malidp->fbdev)) {
		ret = PTR_ERR(malidp->fbdev);
		malidp->fbdev = NULL;
		goto fbdev_fail;
	}

	drm_kms_helper_poll_init(drm);
	return 0;

fbdev_fail:
	drm_vblank_cleanup(drm);
vblank_fail:
	malidp_se_irq_fini(drm);
	malidp_de_irq_fini(drm);
irq_init_fail:
	component_unbind_all(dev, drm);
bind_fail:
	of_node_put(malidp->crtc.port);
	malidp->crtc.port = NULL;
port_fail:
	drm_dev_unregister(drm);
register_fail:
	malidp_de_planes_destroy(drm);
	drm_mode_config_cleanup(drm);
init_fail:
	drm->dev_private = NULL;
	dev_set_drvdata(dev, NULL);
query_hw_fail:
	clk_disable_unprepare(hwdev->mclk);
	clk_disable_unprepare(hwdev->aclk);
	clk_disable_unprepare(hwdev->pclk);
	drm_dev_unref(drm);
alloc_fail:
	of_reserved_mem_device_release(dev);

	return ret;
}
コード例 #10
0
ファイル: sun4i_drv.c プロジェクト: asmalldev/linux
static int sun4i_drv_bind(struct device *dev)
{
	struct drm_device *drm;
	struct sun4i_drv *drv;
	int ret;

	drm = drm_dev_alloc(&sun4i_drv_driver, dev);
	if (IS_ERR(drm))
		return PTR_ERR(drm);

	drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
	if (!drv) {
		ret = -ENOMEM;
		goto free_drm;
	}
	drm->dev_private = drv;

	ret = of_reserved_mem_device_init(dev);
	if (ret && ret != -ENODEV) {
		dev_err(drm->dev, "Couldn't claim our memory region\n");
		goto free_drm;
	}

	/* drm_vblank_init calls kcalloc, which can fail */
	ret = drm_vblank_init(drm, 1);
	if (ret)
		goto free_mem_region;

	drm_mode_config_init(drm);

	ret = component_bind_all(drm->dev, drm);
	if (ret) {
		dev_err(drm->dev, "Couldn't bind all pipelines components\n");
		goto cleanup_mode_config;
	}

	drm->irq_enabled = true;

	/* Remove early framebuffers (ie. simplefb) */
	sun4i_remove_framebuffers();

	/* Create our framebuffer */
	drv->fbdev = sun4i_framebuffer_init(drm);
	if (IS_ERR(drv->fbdev)) {
		dev_err(drm->dev, "Couldn't create our framebuffer\n");
		ret = PTR_ERR(drv->fbdev);
		goto cleanup_mode_config;
	}

	/* Enable connectors polling */
	drm_kms_helper_poll_init(drm);

	ret = drm_dev_register(drm, 0);
	if (ret)
		goto finish_poll;

	return 0;

finish_poll:
	drm_kms_helper_poll_fini(drm);
	sun4i_framebuffer_free(drm);
cleanup_mode_config:
	drm_mode_config_cleanup(drm);
	drm_vblank_cleanup(drm);
free_mem_region:
	of_reserved_mem_device_release(dev);
free_drm:
	drm_dev_unref(drm);
	return ret;
}
コード例 #11
0
ファイル: da8xx_remoteproc.c プロジェクト: krzk/linux
static int da8xx_rproc_probe(struct platform_device *pdev)
{
	struct device *dev = &pdev->dev;
	struct da8xx_rproc *drproc;
	struct rproc *rproc;
	struct irq_data *irq_data;
	struct resource *bootreg_res;
	struct resource *chipsig_res;
	struct clk *dsp_clk;
	void __iomem *chipsig;
	void __iomem *bootreg;
	int irq;
	int ret;

	irq = platform_get_irq(pdev, 0);
	if (irq < 0) {
		dev_err(dev, "platform_get_irq(pdev, 0) error: %d\n", irq);
		return irq;
	}

	irq_data = irq_get_irq_data(irq);
	if (!irq_data) {
		dev_err(dev, "irq_get_irq_data(%d): NULL\n", irq);
		return -EINVAL;
	}

	bootreg_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
						   "host1cfg");
	bootreg = devm_ioremap_resource(dev, bootreg_res);
	if (IS_ERR(bootreg))
		return PTR_ERR(bootreg);

	chipsig_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
						   "chipsig");
	chipsig = devm_ioremap_resource(dev, chipsig_res);
	if (IS_ERR(chipsig))
		return PTR_ERR(chipsig);

	dsp_clk = devm_clk_get(dev, NULL);
	if (IS_ERR(dsp_clk)) {
		dev_err(dev, "clk_get error: %ld\n", PTR_ERR(dsp_clk));

		return PTR_ERR(dsp_clk);
	}

	if (dev->of_node) {
		ret = of_reserved_mem_device_init(dev);
		if (ret) {
			dev_err(dev, "device does not have specific CMA pool: %d\n",
				ret);
			return ret;
		}
	}

	rproc = rproc_alloc(dev, "dsp", &da8xx_rproc_ops, da8xx_fw_name,
		sizeof(*drproc));
	if (!rproc) {
		ret = -ENOMEM;
		goto free_mem;
	}

	drproc = rproc->priv;
	drproc->rproc = rproc;
	drproc->dsp_clk = dsp_clk;
	rproc->has_iommu = false;

	ret = da8xx_rproc_get_internal_memories(pdev, drproc);
	if (ret)
		goto free_rproc;

	platform_set_drvdata(pdev, rproc);

	/* everything the ISR needs is now setup, so hook it up */
	ret = devm_request_threaded_irq(dev, irq, da8xx_rproc_callback,
					handle_event, 0, "da8xx-remoteproc",
					rproc);
	if (ret) {
		dev_err(dev, "devm_request_threaded_irq error: %d\n", ret);
		goto free_rproc;
	}

	/*
	 * rproc_add() can end up enabling the DSP's clk with the DSP
	 * *not* in reset, but da8xx_rproc_start() needs the DSP to be
	 * held in reset at the time it is called.
	 */
	ret = davinci_clk_reset_assert(drproc->dsp_clk);
	if (ret)
		goto free_rproc;

	drproc->chipsig = chipsig;
	drproc->bootreg = bootreg;
	drproc->ack_fxn = irq_data->chip->irq_ack;
	drproc->irq_data = irq_data;
	drproc->irq = irq;

	ret = rproc_add(rproc);
	if (ret) {
		dev_err(dev, "rproc_add failed: %d\n", ret);
		goto free_rproc;
	}

	return 0;

free_rproc:
	rproc_free(rproc);
free_mem:
	if (dev->of_node)
		of_reserved_mem_device_release(dev);
	return ret;
}
コード例 #12
0
ファイル: hdlcd_drv.c プロジェクト: acton393/linux
static int hdlcd_load(struct drm_device *drm, unsigned long flags)
{
	struct hdlcd_drm_private *hdlcd = drm->dev_private;
	struct platform_device *pdev = to_platform_device(drm->dev);
	struct resource *res;
	u32 version;
	int ret;

	hdlcd->clk = devm_clk_get(drm->dev, "pxlclk");
	if (IS_ERR(hdlcd->clk))
		return PTR_ERR(hdlcd->clk);

#ifdef CONFIG_DEBUG_FS
	atomic_set(&hdlcd->buffer_underrun_count, 0);
	atomic_set(&hdlcd->bus_error_count, 0);
	atomic_set(&hdlcd->vsync_count, 0);
	atomic_set(&hdlcd->dma_end_count, 0);
#endif

	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
	hdlcd->mmio = devm_ioremap_resource(drm->dev, res);
	if (IS_ERR(hdlcd->mmio)) {
		DRM_ERROR("failed to map control registers area\n");
		ret = PTR_ERR(hdlcd->mmio);
		hdlcd->mmio = NULL;
		return ret;
	}

	version = hdlcd_read(hdlcd, HDLCD_REG_VERSION);
	if ((version & HDLCD_PRODUCT_MASK) != HDLCD_PRODUCT_ID) {
		DRM_ERROR("unknown product id: 0x%x\n", version);
		return -EINVAL;
	}
	DRM_INFO("found ARM HDLCD version r%dp%d\n",
		(version & HDLCD_VERSION_MAJOR_MASK) >> 8,
		version & HDLCD_VERSION_MINOR_MASK);

	/* Get the optional framebuffer memory resource */
	ret = of_reserved_mem_device_init(drm->dev);
	if (ret && ret != -ENODEV)
		return ret;

	ret = dma_set_mask_and_coherent(drm->dev, DMA_BIT_MASK(32));
	if (ret)
		goto setup_fail;

	ret = hdlcd_setup_crtc(drm);
	if (ret < 0) {
		DRM_ERROR("failed to create crtc\n");
		goto setup_fail;
	}

	ret = drm_irq_install(drm, platform_get_irq(pdev, 0));
	if (ret < 0) {
		DRM_ERROR("failed to install IRQ handler\n");
		goto irq_fail;
	}

	return 0;

irq_fail:
	drm_crtc_cleanup(&hdlcd->crtc);
setup_fail:
	of_reserved_mem_device_release(drm->dev);

	return ret;
}
コード例 #13
0
ファイル: hdlcd_drv.c プロジェクト: acton393/linux
static int hdlcd_drm_bind(struct device *dev)
{
	struct drm_device *drm;
	struct hdlcd_drm_private *hdlcd;
	int ret;

	hdlcd = devm_kzalloc(dev, sizeof(*hdlcd), GFP_KERNEL);
	if (!hdlcd)
		return -ENOMEM;

	drm = drm_dev_alloc(&hdlcd_driver, dev);
	if (IS_ERR(drm))
		return PTR_ERR(drm);

	drm->dev_private = hdlcd;
	dev_set_drvdata(dev, drm);

	hdlcd_setup_mode_config(drm);
	ret = hdlcd_load(drm, 0);
	if (ret)
		goto err_free;

	ret = drm_dev_register(drm, 0);
	if (ret)
		goto err_unload;

	ret = component_bind_all(dev, drm);
	if (ret) {
		DRM_ERROR("Failed to bind all components\n");
		goto err_unregister;
	}

	ret = pm_runtime_set_active(dev);
	if (ret)
		goto err_pm_active;

	pm_runtime_enable(dev);

	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
	if (ret < 0) {
		DRM_ERROR("failed to initialise vblank\n");
		goto err_vblank;
	}

	drm_mode_config_reset(drm);
	drm_kms_helper_poll_init(drm);

	hdlcd->fbdev = drm_fbdev_cma_init(drm, 32, drm->mode_config.num_crtc,
					  drm->mode_config.num_connector);

	if (IS_ERR(hdlcd->fbdev)) {
		ret = PTR_ERR(hdlcd->fbdev);
		hdlcd->fbdev = NULL;
		goto err_fbdev;
	}

	return 0;

err_fbdev:
	drm_kms_helper_poll_fini(drm);
	drm_mode_config_cleanup(drm);
	drm_vblank_cleanup(drm);
err_vblank:
	pm_runtime_disable(drm->dev);
err_pm_active:
	component_unbind_all(dev, drm);
err_unregister:
	drm_dev_unregister(drm);
err_unload:
	drm_irq_uninstall(drm);
	of_reserved_mem_device_release(drm->dev);
err_free:
	dev_set_drvdata(dev, NULL);
	drm_dev_unref(drm);

	return ret;
}
コード例 #14
0
ファイル: sun4i_drv.c プロジェクト: grate-driver/linux
static int sun4i_drv_bind(struct device *dev)
{
	struct drm_device *drm;
	struct sun4i_drv *drv;
	int ret;

	drm = drm_dev_alloc(&sun4i_drv_driver, dev);
	if (IS_ERR(drm))
		return PTR_ERR(drm);

	drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
	if (!drv) {
		ret = -ENOMEM;
		goto free_drm;
	}

	dev_set_drvdata(dev, drm);
	drm->dev_private = drv;
	INIT_LIST_HEAD(&drv->frontend_list);
	INIT_LIST_HEAD(&drv->engine_list);
	INIT_LIST_HEAD(&drv->tcon_list);

	ret = of_reserved_mem_device_init(dev);
	if (ret && ret != -ENODEV) {
		dev_err(drm->dev, "Couldn't claim our memory region\n");
		goto free_drm;
	}

	drm_mode_config_init(drm);
	drm->mode_config.allow_fb_modifiers = true;

	ret = component_bind_all(drm->dev, drm);
	if (ret) {
		dev_err(drm->dev, "Couldn't bind all pipelines components\n");
		goto cleanup_mode_config;
	}

	/* drm_vblank_init calls kcalloc, which can fail */
	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
	if (ret)
		goto cleanup_mode_config;

	drm->irq_enabled = true;

	/* Remove early framebuffers (ie. simplefb) */
	drm_fb_helper_remove_conflicting_framebuffers(NULL, "sun4i-drm-fb", false);

	sun4i_framebuffer_init(drm);

	/* Enable connectors polling */
	drm_kms_helper_poll_init(drm);

	ret = drm_dev_register(drm, 0);
	if (ret)
		goto finish_poll;

	drm_fbdev_generic_setup(drm, 32);

	return 0;

finish_poll:
	drm_kms_helper_poll_fini(drm);
cleanup_mode_config:
	drm_mode_config_cleanup(drm);
	of_reserved_mem_device_release(dev);
free_drm:
	drm_dev_put(drm);
	return ret;
}