summaryrefslogtreecommitdiff
path: root/drivers/gpu/imx/dpu/dpu-common.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/gpu/imx/dpu/dpu-common.c')
-rw-r--r--drivers/gpu/imx/dpu/dpu-common.c1333
1 files changed, 1333 insertions, 0 deletions
diff --git a/drivers/gpu/imx/dpu/dpu-common.c b/drivers/gpu/imx/dpu/dpu-common.c
new file mode 100644
index 000000000000..e7ce8363f7f4
--- /dev/null
+++ b/drivers/gpu/imx/dpu/dpu-common.c
@@ -0,0 +1,1333 @@
+/*
+ * Copyright (C) 2016 Freescale Semiconductor, Inc.
+ * Copyright 2017-2020 NXP
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+#include <linux/clk.h>
+#include <linux/fb.h>
+#include <linux/io.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_graph.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <video/dpu.h>
+#include <video/imx8-pc.h>
+#include <video/imx8-prefetch.h>
+#include "dpu-prv.h"
+
+#define IMX_DPU_BLITENG_NAME "imx-drm-dpu-bliteng"
+
+static bool display_plane_video_proc = true;
+module_param(display_plane_video_proc, bool, 0444);
+MODULE_PARM_DESC(display_plane_video_proc,
+ "Enable video processing for display [default=true]");
+
+#define DPU_CM_REG_DEFINE1(name1, name2) \
+static inline u32 name1(const struct cm_reg_ofs *ofs) \
+{ \
+ return ofs->name2; \
+}
+
+#define DPU_CM_REG_DEFINE2(name1, name2) \
+static inline u32 name1(const struct cm_reg_ofs *ofs, \
+ unsigned int n) \
+{ \
+ return ofs->name2 + (4 * n); \
+}
+
+DPU_CM_REG_DEFINE1(LOCKUNLOCK, lockunlock);
+DPU_CM_REG_DEFINE1(LOCKSTATUS, lockstatus);
+DPU_CM_REG_DEFINE2(USERINTERRUPTMASK, userinterruptmask);
+DPU_CM_REG_DEFINE2(INTERRUPTENABLE, interruptenable);
+DPU_CM_REG_DEFINE2(INTERRUPTPRESET, interruptpreset);
+DPU_CM_REG_DEFINE2(INTERRUPTCLEAR, interruptclear);
+DPU_CM_REG_DEFINE2(INTERRUPTSTATUS, interruptstatus);
+DPU_CM_REG_DEFINE2(USERINTERRUPTENABLE, userinterruptenable);
+DPU_CM_REG_DEFINE2(USERINTERRUPTPRESET, userinterruptpreset);
+DPU_CM_REG_DEFINE2(USERINTERRUPTCLEAR, userinterruptclear);
+DPU_CM_REG_DEFINE2(USERINTERRUPTSTATUS, userinterruptstatus);
+DPU_CM_REG_DEFINE1(GENERALPURPOSE, generalpurpose);
+
+static inline u32 dpu_cm_read(struct dpu_soc *dpu, unsigned int offset)
+{
+ return readl(dpu->cm_reg + offset);
+}
+
+static inline void dpu_cm_write(struct dpu_soc *dpu,
+ unsigned int offset, u32 value)
+{
+ writel(value, dpu->cm_reg + offset);
+}
+
+/* Constant Frame Unit */
+static const unsigned long cf_ofss[] = {0x4400, 0x5400, 0x4c00, 0x5c00};
+static const unsigned long cf_pec_ofss[] = {0x960, 0x9e0, 0x9a0, 0xa20};
+
+/* Display Engine Configuration Unit */
+static const unsigned long dec_ofss[] = {0xb400, 0xb420};
+
+/* External Destination Unit */
+static const unsigned long ed_ofss[] = {0x4800, 0x5800, 0x5000, 0x6000};
+static const unsigned long ed_pec_ofss[] = {0x980, 0xa00, 0x9c0, 0xa40};
+
+/* Fetch Decode Unit */
+static const unsigned long fd_ofss[] = {0x6c00, 0x7800};
+static const unsigned long fd_pec_ofss[] = {0xa80, 0xaa0};
+
+/* Fetch ECO Unit */
+static const unsigned long fe_ofss[] = {0x7400, 0x8000, 0x6800, 0x1c00};
+static const unsigned long fe_pec_ofss[] = {0xa90, 0xab0, 0xa70, 0x850};
+
+/* Frame Generator Unit */
+static const unsigned long fg_ofss[] = {0xb800, 0xd400};
+
+/* Fetch Layer Unit */
+static const unsigned long fl_ofss[] = {0x8400};
+static const unsigned long fl_pec_ofss[] = {0xac0};
+
+/* Fetch Warp Unit */
+static const unsigned long fw_ofss[] = {0x6400};
+static const unsigned long fw_pec_ofss[] = {0xa60};
+
+/* Horizontal Scaler Unit */
+static const unsigned long hs_ofss[] = {0x9000, 0x9c00, 0x3000};
+static const unsigned long hs_pec_ofss[] = {0xb00, 0xb60, 0x8c0};
+
+/* Layer Blend Unit */
+static const unsigned long lb_ofss[] = {0xa400, 0xa800, 0xac00, 0xb000};
+static const unsigned long lb_pec_ofss[] = {0xba0, 0xbc0, 0xbe0, 0xc00};
+
+/* Signature Unit */
+static const unsigned long sig_ofss[] = {0xd000, 0xec00};
+
+/* Store Unit */
+static const unsigned long st_ofss[] = {0x4000};
+static const unsigned long st_pec_ofss[] = {0x940};
+
+/* Timing Controller Unit */
+static const unsigned long tcon_ofss[] = {0xcc00, 0xe800};
+
+/* Vertical Scaler Unit */
+static const unsigned long vs_ofss[] = {0x9400, 0xa000, 0x3400};
+static const unsigned long vs_pec_ofss[] = {0xb20, 0xb80, 0x8e0};
+
+static const struct dpu_unit _cfs = {
+ .name = "ConstFrame",
+ .num = ARRAY_SIZE(cf_ids),
+ .ids = cf_ids,
+ .pec_ofss = cf_pec_ofss,
+ .ofss = cf_ofss,
+};
+
+static const struct dpu_unit _decs = {
+ .name = "DisEngCfg",
+ .num = ARRAY_SIZE(dec_ids),
+ .ids = dec_ids,
+ .pec_ofss = NULL,
+ .ofss = dec_ofss,
+};
+
+static const struct dpu_unit _eds = {
+ .name = "ExtDst",
+ .num = ARRAY_SIZE(ed_ids),
+ .ids = ed_ids,
+ .pec_ofss = ed_pec_ofss,
+ .ofss = ed_ofss,
+};
+
+static const struct dpu_unit _fds = {
+ .name = "FetchDecode",
+ .num = ARRAY_SIZE(fd_ids),
+ .ids = fd_ids,
+ .pec_ofss = fd_pec_ofss,
+ .ofss = fd_ofss,
+ .dprc_ids = fd_dprc_ids,
+};
+
+static const struct dpu_unit _fes = {
+ .name = "FetchECO",
+ .num = ARRAY_SIZE(fe_ids),
+ .ids = fe_ids,
+ .pec_ofss = fe_pec_ofss,
+ .ofss = fe_ofss,
+};
+
+static const struct dpu_unit _fgs = {
+ .name = "FrameGen",
+ .num = ARRAY_SIZE(fg_ids),
+ .ids = fg_ids,
+ .pec_ofss = NULL,
+ .ofss = fg_ofss,
+};
+
+static const struct dpu_unit _fls = {
+ .name = "FetchLayer",
+ .num = ARRAY_SIZE(fl_ids),
+ .ids = fl_ids,
+ .pec_ofss = fl_pec_ofss,
+ .ofss = fl_ofss,
+ .dprc_ids = fl_dprc_ids,
+};
+
+static const struct dpu_unit _fws = {
+ .name = "FetchWarp",
+ .num = ARRAY_SIZE(fw_ids),
+ .ids = fw_ids,
+ .pec_ofss = fw_pec_ofss,
+ .ofss = fw_ofss,
+ .dprc_ids = fw_dprc_ids,
+};
+
+static const struct dpu_unit _hss = {
+ .name = "HScaler",
+ .num = ARRAY_SIZE(hs_ids),
+ .ids = hs_ids,
+ .pec_ofss = hs_pec_ofss,
+ .ofss = hs_ofss,
+};
+
+static const struct dpu_unit _lbs = {
+ .name = "LayerBlend",
+ .num = ARRAY_SIZE(lb_ids),
+ .ids = lb_ids,
+ .pec_ofss = lb_pec_ofss,
+ .ofss = lb_ofss,
+};
+
+static const struct dpu_unit _sigs = {
+ .name = "Signature",
+ .num = ARRAY_SIZE(sig_ids),
+ .ids = sig_ids,
+ .pec_ofss = NULL,
+ .ofss = sig_ofss,
+};
+
+static const struct dpu_unit _sts = {
+ .name = "Store",
+ .num = ARRAY_SIZE(st_ids),
+ .ids = st_ids,
+ .pec_ofss = st_pec_ofss,
+ .ofss = st_ofss,
+};
+
+static const struct dpu_unit _tcons = {
+ .name = "TCon",
+ .num = ARRAY_SIZE(tcon_ids),
+ .ids = tcon_ids,
+ .pec_ofss = NULL,
+ .ofss = tcon_ofss,
+};
+
+static const struct dpu_unit _vss = {
+ .name = "VScaler",
+ .num = ARRAY_SIZE(vs_ids),
+ .ids = vs_ids,
+ .pec_ofss = vs_pec_ofss,
+ .ofss = vs_ofss,
+};
+
+static const struct cm_reg_ofs _cm_reg_ofs = {
+ .ipidentifier = 0,
+ .lockunlock = 0x40,
+ .lockstatus = 0x44,
+ .userinterruptmask = 0x48,
+ .interruptenable = 0x50,
+ .interruptpreset = 0x58,
+ .interruptclear = 0x60,
+ .interruptstatus = 0x68,
+ .userinterruptenable = 0x80,
+ .userinterruptpreset = 0x88,
+ .userinterruptclear = 0x90,
+ .userinterruptstatus = 0x98,
+ .generalpurpose = 0x100,
+};
+
+static const unsigned long unused_irq[] = {0x00000000, 0xfffe0008};
+
+static const struct dpu_data dpu_data_qxp = {
+ .cm_ofs = 0x0,
+ .cfs = &_cfs,
+ .decs = &_decs,
+ .eds = &_eds,
+ .fds = &_fds,
+ .fes = &_fes,
+ .fgs = &_fgs,
+ .fls = &_fls,
+ .fws = &_fws,
+ .hss = &_hss,
+ .lbs = &_lbs,
+ .sigs = &_sigs,
+ .sts = &_sts,
+ .tcons = &_tcons,
+ .vss = &_vss,
+ .cm_reg_ofs = &_cm_reg_ofs,
+ .unused_irq = unused_irq,
+ .plane_src_mask = DPU_PLANE_SRC_FL0_ID | DPU_PLANE_SRC_FW2_ID |
+ DPU_PLANE_SRC_FD0_ID | DPU_PLANE_SRC_FD1_ID,
+ .has_dual_ldb = true,
+ .syncmode_min_prate = UINT_MAX, /* pc is unused */
+ .singlemode_max_width = UINT_MAX, /* pc is unused */
+};
+
+static const struct dpu_data dpu_data_qm = {
+ .cm_ofs = 0x0,
+ .cfs = &_cfs,
+ .decs = &_decs,
+ .eds = &_eds,
+ .fds = &_fds,
+ .fes = &_fes,
+ .fgs = &_fgs,
+ .fls = &_fls,
+ .fws = &_fws,
+ .hss = &_hss,
+ .lbs = &_lbs,
+ .sigs = &_sigs,
+ .sts = &_sts,
+ .tcons = &_tcons,
+ .vss = &_vss,
+ .cm_reg_ofs = &_cm_reg_ofs,
+ .unused_irq = unused_irq,
+ .plane_src_mask = DPU_PLANE_SRC_FL0_ID | DPU_PLANE_SRC_FW2_ID |
+ DPU_PLANE_SRC_FD0_ID | DPU_PLANE_SRC_FD1_ID,
+ .has_dual_ldb = false,
+ .syncmode_min_prate = 300000,
+ .singlemode_max_width = 2560,
+ .master_stream_id = 1,
+};
+
+static const struct of_device_id dpu_dt_ids[] = {
+ {
+ .compatible = "fsl,imx8qxp-dpu",
+ .data = &dpu_data_qxp,
+ }, {
+ .compatible = "fsl,imx8qm-dpu",
+ .data = &dpu_data_qm,
+ }, {
+ /* sentinel */
+ }
+};
+MODULE_DEVICE_TABLE(of, dpu_dt_ids);
+
+unsigned int dpu_get_syncmode_min_prate(struct dpu_soc *dpu)
+{
+ return dpu->data->syncmode_min_prate;
+}
+EXPORT_SYMBOL_GPL(dpu_get_syncmode_min_prate);
+
+unsigned int dpu_get_singlemode_max_width(struct dpu_soc *dpu)
+{
+ return dpu->data->singlemode_max_width;
+}
+EXPORT_SYMBOL_GPL(dpu_get_singlemode_max_width);
+
+unsigned int dpu_get_master_stream_id(struct dpu_soc *dpu)
+{
+ return dpu->data->master_stream_id;
+}
+EXPORT_SYMBOL_GPL(dpu_get_master_stream_id);
+
+bool dpu_vproc_has_fetcheco_cap(u32 cap_mask)
+{
+ return !!(cap_mask & DPU_VPROC_CAP_FETCHECO);
+}
+EXPORT_SYMBOL_GPL(dpu_vproc_has_fetcheco_cap);
+
+bool dpu_vproc_has_hscale_cap(u32 cap_mask)
+{
+ return !!(cap_mask & DPU_VPROC_CAP_HSCALE);
+}
+EXPORT_SYMBOL_GPL(dpu_vproc_has_hscale_cap);
+
+bool dpu_vproc_has_vscale_cap(u32 cap_mask)
+{
+ return !!(cap_mask & DPU_VPROC_CAP_VSCALE);
+}
+EXPORT_SYMBOL_GPL(dpu_vproc_has_vscale_cap);
+
+u32 dpu_vproc_get_fetcheco_cap(u32 cap_mask)
+{
+ return cap_mask & DPU_VPROC_CAP_FETCHECO;
+}
+EXPORT_SYMBOL_GPL(dpu_vproc_get_fetcheco_cap);
+
+u32 dpu_vproc_get_hscale_cap(u32 cap_mask)
+{
+ return cap_mask & DPU_VPROC_CAP_HSCALE;
+}
+EXPORT_SYMBOL_GPL(dpu_vproc_get_hscale_cap);
+
+u32 dpu_vproc_get_vscale_cap(u32 cap_mask)
+{
+ return cap_mask & DPU_VPROC_CAP_VSCALE;
+}
+EXPORT_SYMBOL_GPL(dpu_vproc_get_vscale_cap);
+
+int dpu_format_horz_chroma_subsampling(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_YUYV:
+ case DRM_FORMAT_UYVY:
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
+ return 2;
+ default:
+ return 1;
+ }
+}
+
+int dpu_format_vert_chroma_subsampling(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ return 2;
+ default:
+ return 1;
+ }
+}
+
+int dpu_format_num_planes(u32 format)
+{
+ switch (format) {
+ case DRM_FORMAT_NV12:
+ case DRM_FORMAT_NV21:
+ case DRM_FORMAT_NV16:
+ case DRM_FORMAT_NV61:
+ case DRM_FORMAT_NV24:
+ case DRM_FORMAT_NV42:
+ return 2;
+ default:
+ return 1;
+ }
+}
+
+int dpu_format_plane_width(int width, u32 format, int plane)
+{
+ if (plane >= dpu_format_num_planes(format))
+ return 0;
+
+ if (plane == 0)
+ return width;
+
+ return width / dpu_format_horz_chroma_subsampling(format);
+}
+
+int dpu_format_plane_height(int height, u32 format, int plane)
+{
+ if (plane >= dpu_format_num_planes(format))
+ return 0;
+
+ if (plane == 0)
+ return height;
+
+ return height / dpu_format_vert_chroma_subsampling(format);
+}
+
+static void dpu_detach_pm_domains(struct dpu_soc *dpu)
+{
+ if (dpu->pd_pll1_link && !IS_ERR(dpu->pd_pll1_link))
+ device_link_del(dpu->pd_pll1_link);
+ if (dpu->pd_pll1_dev && !IS_ERR(dpu->pd_pll1_dev))
+ dev_pm_domain_detach(dpu->pd_pll1_dev, true);
+
+ if (dpu->pd_pll0_link && !IS_ERR(dpu->pd_pll0_link))
+ device_link_del(dpu->pd_pll0_link);
+ if (dpu->pd_pll0_dev && !IS_ERR(dpu->pd_pll0_dev))
+ dev_pm_domain_detach(dpu->pd_pll0_dev, true);
+
+ if (dpu->pd_dc_link && !IS_ERR(dpu->pd_dc_link))
+ device_link_del(dpu->pd_dc_link);
+ if (dpu->pd_dc_dev && !IS_ERR(dpu->pd_dc_dev))
+ dev_pm_domain_detach(dpu->pd_dc_dev, true);
+
+ dpu->pd_dc_dev = NULL;
+ dpu->pd_dc_link = NULL;
+ dpu->pd_pll0_dev = NULL;
+ dpu->pd_pll0_link = NULL;
+ dpu->pd_pll1_dev = NULL;
+ dpu->pd_pll1_link = NULL;
+}
+
+static int dpu_attach_pm_domains(struct dpu_soc *dpu)
+{
+ struct device *dev = dpu->dev;
+ u32 flags = DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE;
+ int ret = 0;
+
+ dpu->pd_dc_dev = dev_pm_domain_attach_by_name(dev, "dc");
+ if (IS_ERR(dpu->pd_dc_dev)) {
+ ret = PTR_ERR(dpu->pd_dc_dev);
+ dev_err(dev, "Failed to attach dc pd dev: %d\n", ret);
+ goto fail;
+ }
+ dpu->pd_dc_link = device_link_add(dev, dpu->pd_dc_dev, flags);
+ if (IS_ERR(dpu->pd_dc_link)) {
+ ret = PTR_ERR(dpu->pd_dc_link);
+ dev_err(dev, "Failed to add device link to dc pd dev: %d\n",
+ ret);
+ goto fail;
+ }
+
+ dpu->pd_pll0_dev = dev_pm_domain_attach_by_name(dev, "pll0");
+ if (IS_ERR(dpu->pd_pll0_dev)) {
+ ret = PTR_ERR(dpu->pd_pll0_dev);
+ dev_err(dev, "Failed to attach pll0 pd dev: %d\n", ret);
+ goto fail;
+ }
+ dpu->pd_pll0_link = device_link_add(dev, dpu->pd_pll0_dev, flags);
+ if (IS_ERR(dpu->pd_pll0_link)) {
+ ret = PTR_ERR(dpu->pd_pll0_link);
+ dev_err(dev, "Failed to add device link to pll0 pd dev: %d\n",
+ ret);
+ goto fail;
+ }
+
+ dpu->pd_pll1_dev = dev_pm_domain_attach_by_name(dev, "pll1");
+ if (IS_ERR(dpu->pd_pll1_dev)) {
+ ret = PTR_ERR(dpu->pd_pll1_dev);
+ dev_err(dev, "Failed to attach pll0 pd dev: %d\n", ret);
+ goto fail;
+ }
+ dpu->pd_pll1_link = device_link_add(dev, dpu->pd_pll1_dev, flags);
+ if (IS_ERR(dpu->pd_pll1_link)) {
+ ret = PTR_ERR(dpu->pd_pll1_link);
+ dev_err(dev, "Failed to add device link to pll1 pd dev: %d\n",
+ ret);
+ goto fail;
+ }
+
+ return ret;
+fail:
+ dpu_detach_pm_domains(dpu);
+ return ret;
+}
+
+#define DPU_UNITS_ADDR_DBG(unit) \
+{ \
+ const struct dpu_unit *us = data->unit##s; \
+ int i; \
+ for (i = 0; i < us->num; i++) { \
+ if (us->pec_ofss) { \
+ dev_dbg(&pdev->dev, "%s%d: pixengcfg @ 0x%08lx,"\
+ " unit @ 0x%08lx\n", us->name, \
+ us->ids[i], \
+ dpu_base + us->pec_ofss[i], \
+ dpu_base + us->ofss[i]); \
+ } else { \
+ dev_dbg(&pdev->dev, \
+ "%s%d: unit @ 0x%08lx\n", us->name, \
+ us->ids[i], dpu_base + us->ofss[i]); \
+ } \
+ } \
+}
+
+static void dpu_units_addr_dbg(struct dpu_soc *dpu,
+ struct platform_device *pdev, unsigned long dpu_base)
+{
+ const struct dpu_data *data = dpu->data;
+
+ dev_dbg(dpu->dev, "Common: 0x%08lx\n", dpu_base + data->cm_ofs);
+ DPU_UNITS_ADDR_DBG(cf);
+ DPU_UNITS_ADDR_DBG(dec);
+ DPU_UNITS_ADDR_DBG(ed);
+ DPU_UNITS_ADDR_DBG(fd);
+ DPU_UNITS_ADDR_DBG(fe);
+ DPU_UNITS_ADDR_DBG(fg);
+ DPU_UNITS_ADDR_DBG(fl);
+ DPU_UNITS_ADDR_DBG(fw);
+ DPU_UNITS_ADDR_DBG(hs);
+ DPU_UNITS_ADDR_DBG(lb);
+ DPU_UNITS_ADDR_DBG(sig);
+ DPU_UNITS_ADDR_DBG(st);
+ DPU_UNITS_ADDR_DBG(tcon);
+ DPU_UNITS_ADDR_DBG(vs);
+}
+
+static int dpu_get_irq(struct platform_device *pdev, struct dpu_soc *dpu)
+{
+#define DPU_GET_IRQ(name) \
+{ \
+ dpu->irq_##name = platform_get_irq_byname(pdev, "" #name ""); \
+ dev_dbg(dpu->dev, "irq_" #name ": %d\n", dpu->irq_##name); \
+ if (dpu->irq_##name < 0) { \
+ dev_err(dpu->dev, "failed to get irq " #name "\n"); \
+ return dpu->irq_##name; \
+ } \
+}
+
+ DPU_GET_IRQ(extdst0_shdload);
+ DPU_GET_IRQ(extdst4_shdload);
+ DPU_GET_IRQ(extdst1_shdload);
+ DPU_GET_IRQ(extdst5_shdload);
+ DPU_GET_IRQ(disengcfg_shdload0);
+ DPU_GET_IRQ(disengcfg_framecomplete0);
+ DPU_GET_IRQ(sig0_shdload);
+ DPU_GET_IRQ(sig0_valid);
+ DPU_GET_IRQ(disengcfg_shdload1);
+ DPU_GET_IRQ(disengcfg_framecomplete1);
+ DPU_GET_IRQ(sig1_shdload);
+ DPU_GET_IRQ(sig1_valid);
+
+ return 0;
+}
+
+static void dpu_irq_handle(struct irq_desc *desc, enum dpu_irq irq)
+{
+ struct dpu_soc *dpu = irq_desc_get_handler_data(desc);
+ const struct dpu_data *data = dpu->data;
+ const struct cm_reg_ofs *ofs = data->cm_reg_ofs;
+ struct irq_chip *chip = irq_desc_get_chip(desc);
+ unsigned int virq;
+ u32 status;
+
+ chained_irq_enter(chip, desc);
+
+ status = dpu_cm_read(dpu, USERINTERRUPTSTATUS(ofs, irq / 32));
+ status &= dpu_cm_read(dpu, USERINTERRUPTENABLE(ofs, irq / 32));
+
+ if (status & BIT(irq % 32)) {
+ virq = irq_linear_revmap(dpu->domain, irq);
+ if (virq)
+ generic_handle_irq(virq);
+ }
+
+ chained_irq_exit(chip, desc);
+}
+
+#define DPU_IRQ_HANDLER_DEFINE(name1, name2) \
+static void dpu_##name1##_irq_handler(struct irq_desc *desc) \
+{ \
+ dpu_irq_handle(desc, IRQ_##name2); \
+}
+
+DPU_IRQ_HANDLER_DEFINE(extdst0_shdload, EXTDST0_SHDLOAD)
+DPU_IRQ_HANDLER_DEFINE(extdst4_shdload, EXTDST4_SHDLOAD)
+DPU_IRQ_HANDLER_DEFINE(extdst1_shdload, EXTDST1_SHDLOAD)
+DPU_IRQ_HANDLER_DEFINE(extdst5_shdload, EXTDST5_SHDLOAD)
+DPU_IRQ_HANDLER_DEFINE(disengcfg_shdload0, DISENGCFG_SHDLOAD0)
+DPU_IRQ_HANDLER_DEFINE(disengcfg_framecomplete0, DISENGCFG_FRAMECOMPLETE0)
+DPU_IRQ_HANDLER_DEFINE(sig0_shdload, SIG0_SHDLOAD);
+DPU_IRQ_HANDLER_DEFINE(sig0_valid, SIG0_VALID);
+DPU_IRQ_HANDLER_DEFINE(disengcfg_shdload1, DISENGCFG_SHDLOAD1)
+DPU_IRQ_HANDLER_DEFINE(disengcfg_framecomplete1, DISENGCFG_FRAMECOMPLETE1)
+DPU_IRQ_HANDLER_DEFINE(sig1_shdload, SIG1_SHDLOAD);
+DPU_IRQ_HANDLER_DEFINE(sig1_valid, SIG1_VALID);
+
+int dpu_map_irq(struct dpu_soc *dpu, int irq)
+{
+ int virq = irq_linear_revmap(dpu->domain, irq);
+
+ if (!virq)
+ virq = irq_create_mapping(dpu->domain, irq);
+
+ return virq;
+}
+EXPORT_SYMBOL_GPL(dpu_map_irq);
+
+static int dpu_irq_init(struct dpu_soc *dpu)
+{
+ const struct dpu_data *data = dpu->data;
+ const struct cm_reg_ofs *ofs = data->cm_reg_ofs;
+ struct irq_chip_generic *gc;
+ struct irq_chip_type *ct;
+ int ret, i;
+
+ dpu->domain = irq_domain_add_linear(dpu->dev->of_node,
+ dpu->irq_line_num,
+ &irq_generic_chip_ops, dpu);
+ if (!dpu->domain) {
+ dev_err(dpu->dev, "failed to add irq domain\n");
+ return -ENODEV;
+ }
+
+ ret = irq_alloc_domain_generic_chips(dpu->domain, 32, 1, "DPU",
+ handle_level_irq, 0, 0, 0);
+ if (ret < 0) {
+ dev_err(dpu->dev, "failed to alloc generic irq chips\n");
+ irq_domain_remove(dpu->domain);
+ return ret;
+ }
+
+ for (i = 0; i < dpu->irq_line_num; i += 32) {
+ /* Mask and clear all interrupts */
+ dpu_cm_write(dpu, USERINTERRUPTENABLE(ofs, i / 32), 0);
+ dpu_cm_write(dpu, USERINTERRUPTCLEAR(ofs, i / 32),
+ ~data->unused_irq[i / 32]);
+ dpu_cm_write(dpu, INTERRUPTENABLE(ofs, i / 32), 0);
+ dpu_cm_write(dpu, INTERRUPTCLEAR(ofs, i / 32),
+ ~data->unused_irq[i / 32]);
+
+ /* Set all interrupts to user mode */
+ dpu_cm_write(dpu, USERINTERRUPTMASK(ofs, i / 32),
+ ~data->unused_irq[i / 32]);
+
+ gc = irq_get_domain_generic_chip(dpu->domain, i);
+ gc->reg_base = dpu->cm_reg;
+ gc->unused = data->unused_irq[i / 32];
+ ct = gc->chip_types;
+ ct->chip.irq_ack = irq_gc_ack_set_bit;
+ ct->chip.irq_mask = irq_gc_mask_clr_bit;
+ ct->chip.irq_unmask = irq_gc_mask_set_bit;
+ ct->regs.ack = USERINTERRUPTCLEAR(ofs, i / 32);
+ ct->regs.mask = USERINTERRUPTENABLE(ofs, i / 32);
+ }
+
+#define DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(name) \
+irq_set_chained_handler_and_data(dpu->irq_##name, dpu_##name##_irq_handler, dpu)
+
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(extdst0_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(extdst4_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(extdst1_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(extdst5_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(disengcfg_shdload0);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(disengcfg_framecomplete0);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(sig0_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(sig0_valid);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(disengcfg_shdload1);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(disengcfg_framecomplete1);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(sig1_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA1(sig1_valid);
+
+#define DPU_IRQ_CHIP_PM_GET(name) \
+{ \
+ ret = irq_chip_pm_get(irq_get_irq_data(dpu->irq_##name)); \
+ if (ret < 0) { \
+ dev_err(dpu->dev, \
+ "failed to get irq chip PM for irq%d %d\n", \
+ dpu->irq_##name, ret); \
+ goto pm_get_rollback; \
+ } \
+ dpu->irq_chip_pm_get_##name = true; \
+}
+
+#define DPU_IRQ_CHIP_PM_PUT_CHECK(name) \
+{ \
+ if (dpu->irq_chip_pm_get_##name) { \
+ irq_chip_pm_put(irq_get_irq_data(dpu->irq_##name)); \
+ dpu->irq_chip_pm_get_##name = false; \
+ } \
+}
+
+ DPU_IRQ_CHIP_PM_GET(extdst0_shdload);
+ DPU_IRQ_CHIP_PM_GET(extdst4_shdload);
+ DPU_IRQ_CHIP_PM_GET(extdst1_shdload);
+ DPU_IRQ_CHIP_PM_GET(extdst5_shdload);
+ DPU_IRQ_CHIP_PM_GET(disengcfg_shdload0);
+ DPU_IRQ_CHIP_PM_GET(disengcfg_framecomplete0);
+ DPU_IRQ_CHIP_PM_GET(sig0_shdload);
+ DPU_IRQ_CHIP_PM_GET(sig0_valid);
+ DPU_IRQ_CHIP_PM_GET(disengcfg_shdload1);
+ DPU_IRQ_CHIP_PM_GET(disengcfg_framecomplete1);
+ DPU_IRQ_CHIP_PM_GET(sig1_shdload);
+ DPU_IRQ_CHIP_PM_GET(sig1_valid);
+
+ return 0;
+
+pm_get_rollback:
+ DPU_IRQ_CHIP_PM_PUT_CHECK(extdst0_shdload);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(extdst4_shdload);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(extdst1_shdload);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(extdst5_shdload);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(disengcfg_shdload0);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(disengcfg_framecomplete0);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(sig0_shdload);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(sig0_valid);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(disengcfg_shdload1);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(disengcfg_framecomplete1);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(sig1_shdload);
+ DPU_IRQ_CHIP_PM_PUT_CHECK(sig1_valid);
+
+ return ret;
+}
+
+static void dpu_irq_exit(struct dpu_soc *dpu)
+{
+ unsigned int i, irq;
+
+#define DPU_IRQ_CHIP_PM_PUT(name) \
+{ \
+ irq_chip_pm_put(irq_get_irq_data(dpu->irq_##name)); \
+ dpu->irq_chip_pm_get_##name = false; \
+}
+
+ DPU_IRQ_CHIP_PM_PUT(extdst0_shdload);
+ DPU_IRQ_CHIP_PM_PUT(extdst4_shdload);
+ DPU_IRQ_CHIP_PM_PUT(extdst1_shdload);
+ DPU_IRQ_CHIP_PM_PUT(extdst5_shdload);
+ DPU_IRQ_CHIP_PM_PUT(disengcfg_shdload0);
+ DPU_IRQ_CHIP_PM_PUT(disengcfg_framecomplete0);
+ DPU_IRQ_CHIP_PM_PUT(sig0_shdload);
+ DPU_IRQ_CHIP_PM_PUT(sig0_valid);
+ DPU_IRQ_CHIP_PM_PUT(disengcfg_shdload1);
+ DPU_IRQ_CHIP_PM_PUT(disengcfg_framecomplete1);
+ DPU_IRQ_CHIP_PM_PUT(sig1_shdload);
+ DPU_IRQ_CHIP_PM_PUT(sig1_valid);
+
+#define DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(name) \
+irq_set_chained_handler_and_data(dpu->irq_##name, NULL, NULL)
+
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(extdst0_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(extdst4_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(extdst1_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(extdst5_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(disengcfg_shdload0);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(disengcfg_framecomplete0);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(sig0_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(sig0_valid);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(disengcfg_shdload1);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(disengcfg_framecomplete1);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(sig1_shdload);
+ DPU_IRQ_SET_CHAINED_HANDLER_AND_DATA2(sig1_valid);
+
+ for (i = 0; i < dpu->irq_line_num; i++) {
+ irq = irq_linear_revmap(dpu->domain, i);
+ if (irq)
+ irq_dispose_mapping(irq);
+ }
+
+ irq_domain_remove(dpu->domain);
+}
+
+#define _DPU_UNITS_INIT(unit) \
+{ \
+ const struct dpu_unit *us = data->unit##s; \
+ int i; \
+ \
+ /* software check */ \
+ if (WARN_ON(us->num > ARRAY_SIZE(unit##_ids))) \
+ return -EINVAL; \
+ \
+ for (i = 0; i < us->num; i++) \
+ _dpu_##unit##_init(dpu, us->ids[i]); \
+}
+
+static int
+_dpu_submodules_init(struct dpu_soc *dpu, struct platform_device *pdev)
+{
+ const struct dpu_data *data = dpu->data;
+
+ _DPU_UNITS_INIT(cf);
+ _DPU_UNITS_INIT(dec);
+ _DPU_UNITS_INIT(ed);
+ _DPU_UNITS_INIT(fd);
+ _DPU_UNITS_INIT(fe);
+ _DPU_UNITS_INIT(fg);
+ _DPU_UNITS_INIT(fl);
+ _DPU_UNITS_INIT(fw);
+ _DPU_UNITS_INIT(hs);
+ _DPU_UNITS_INIT(lb);
+ _DPU_UNITS_INIT(sig);
+ _DPU_UNITS_INIT(st);
+ _DPU_UNITS_INIT(tcon);
+ _DPU_UNITS_INIT(vs);
+
+ return 0;
+}
+
+#define DPU_UNIT_INIT(dpu, base, unit, name, id, pec_ofs, ofs) \
+{ \
+ int ret; \
+ ret = dpu_##unit##_init((dpu), (id), \
+ (pec_ofs) ? (base) + (pec_ofs) : 0, \
+ (base) + (ofs)); \
+ if (ret) { \
+ dev_err((dpu)->dev, "init %s%d failed with %d\n", \
+ (name), (id), ret); \
+ return ret; \
+ } \
+}
+
+#define DPU_UNITS_INIT(unit) \
+{ \
+ const struct dpu_unit *us = data->unit##s; \
+ int i; \
+ \
+ /* software check */ \
+ if (WARN_ON(us->num > ARRAY_SIZE(unit##_ids))) \
+ return -EINVAL; \
+ \
+ for (i = 0; i < us->num; i++) \
+ DPU_UNIT_INIT(dpu, dpu_base, unit, us->name, \
+ us->ids[i], \
+ us->pec_ofss ? us->pec_ofss[i] : 0, \
+ us->ofss[i]); \
+}
+
+static int dpu_submodules_init(struct dpu_soc *dpu,
+ struct platform_device *pdev, unsigned long dpu_base)
+{
+ const struct dpu_data *data = dpu->data;
+ const struct dpu_unit *fds = data->fds;
+ const struct dpu_unit *fls = data->fls;
+ const struct dpu_unit *fws = data->fws;
+ const struct dpu_unit *tcons = data->tcons;
+ struct dpu_fetchunit *fu;
+ struct dprc *dprc;
+ struct dpu_tcon *tcon;
+ struct pc *pc;
+ int i;
+
+ DPU_UNITS_INIT(cf);
+ DPU_UNITS_INIT(dec);
+ DPU_UNITS_INIT(ed);
+ DPU_UNITS_INIT(fd);
+ DPU_UNITS_INIT(fe);
+ DPU_UNITS_INIT(fg);
+ DPU_UNITS_INIT(fl);
+ DPU_UNITS_INIT(fw);
+ DPU_UNITS_INIT(hs);
+ DPU_UNITS_INIT(lb);
+ DPU_UNITS_INIT(sig);
+ DPU_UNITS_INIT(st);
+ DPU_UNITS_INIT(tcon);
+ DPU_UNITS_INIT(vs);
+
+ for (i = 0; i < fds->num; i++) {
+ dprc = dprc_lookup_by_phandle(dpu->dev, "fsl,dpr-channels",
+ fds->dprc_ids[i]);
+ if (!dprc)
+ return -EPROBE_DEFER;
+
+ fu = dpu_fd_get(dpu, i);
+ fetchunit_get_dprc(fu, dprc);
+ dpu_fd_put(fu);
+ }
+
+ for (i = 0; i < fls->num; i++) {
+ dprc = dprc_lookup_by_phandle(dpu->dev, "fsl,dpr-channels",
+ fls->dprc_ids[i]);
+ if (!dprc)
+ return -EPROBE_DEFER;
+
+ fu = dpu_fl_get(dpu, i);
+ fetchunit_get_dprc(fu, dprc);
+ dpu_fl_put(fu);
+ }
+
+ for (i = 0; i < fws->num; i++) {
+ dprc = dprc_lookup_by_phandle(dpu->dev, "fsl,dpr-channels",
+ fws->dprc_ids[i]);
+ if (!dprc)
+ return -EPROBE_DEFER;
+
+ fu = dpu_fw_get(dpu, fw_ids[i]);
+ fetchunit_get_dprc(fu, dprc);
+ dpu_fw_put(fu);
+ }
+
+ pc = pc_lookup_by_phandle(dpu->dev, "fsl,pixel-combiner");
+ if (!pc)
+ return -EPROBE_DEFER;
+
+ for (i = 0; i < tcons->num; i++) {
+ tcon = dpu_tcon_get(dpu, i);
+ tcon_get_pc(tcon, pc);
+ dpu_tcon_put(tcon);
+ }
+
+ return 0;
+}
+
+static int platform_remove_devices_fn(struct device *dev, void *unused)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+
+ platform_device_unregister(pdev);
+
+ return 0;
+}
+
+static void platform_device_unregister_children(struct platform_device *pdev)
+{
+ device_for_each_child(&pdev->dev, NULL, platform_remove_devices_fn);
+}
+
+struct dpu_platform_reg {
+ struct dpu_client_platformdata pdata;
+ const char *name;
+};
+
+static struct dpu_platform_reg client_reg[] = {
+ {
+ .pdata = {
+ .stream_id = 0,
+ },
+ .name = "imx-dpu-crtc",
+ }, {
+ .pdata = {
+ .stream_id = 1,
+ },
+ .name = "imx-dpu-crtc",
+ }, {
+ .pdata = { },
+ .name = IMX_DPU_BLITENG_NAME,
+ }
+};
+
+static DEFINE_MUTEX(dpu_client_id_mutex);
+static int dpu_client_id;
+
+static int dpu_get_plane_resource(struct dpu_soc *dpu,
+ struct dpu_plane_res *res)
+{
+ const struct dpu_unit *fds = dpu->data->fds;
+ const struct dpu_unit *fls = dpu->data->fls;
+ const struct dpu_unit *fws = dpu->data->fws;
+ const struct dpu_unit *lbs = dpu->data->lbs;
+ struct dpu_plane_grp *grp = plane_res_to_grp(res);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(res->ed); i++) {
+ res->ed[i] = dpu_ed_get(dpu, i);
+ if (IS_ERR(res->ed[i]))
+ return PTR_ERR(res->ed[i]);
+ }
+ for (i = 0; i < fds->num; i++) {
+ res->fd[i] = dpu_fd_get(dpu, i);
+ if (IS_ERR(res->fd[i]))
+ return PTR_ERR(res->fd[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->fe); i++) {
+ res->fe[i] = dpu_fe_get(dpu, i);
+ if (IS_ERR(res->fe[i]))
+ return PTR_ERR(res->fe[i]);
+ grp->hw_plane_fetcheco_num = ARRAY_SIZE(res->fe);
+ }
+ for (i = 0; i < fls->num; i++) {
+ res->fl[i] = dpu_fl_get(dpu, i);
+ if (IS_ERR(res->fl[i]))
+ return PTR_ERR(res->fl[i]);
+ }
+ for (i = 0; i < fws->num; i++) {
+ res->fw[i] = dpu_fw_get(dpu, fw_ids[i]);
+ if (IS_ERR(res->fw[i]))
+ return PTR_ERR(res->fw[i]);
+ }
+ /* HScaler could be shared with capture. */
+ if (display_plane_video_proc) {
+ for (i = 0; i < ARRAY_SIZE(res->hs); i++) {
+ res->hs[i] = dpu_hs_get(dpu, hs_ids[i]);
+ if (IS_ERR(res->hs[i]))
+ return PTR_ERR(res->hs[i]);
+ }
+ grp->hw_plane_hscaler_num = ARRAY_SIZE(res->hs);
+ }
+ for (i = 0; i < lbs->num; i++) {
+ res->lb[i] = dpu_lb_get(dpu, i);
+ if (IS_ERR(res->lb[i]))
+ return PTR_ERR(res->lb[i]);
+ }
+ /* VScaler could be shared with capture. */
+ if (display_plane_video_proc) {
+ for (i = 0; i < ARRAY_SIZE(res->vs); i++) {
+ res->vs[i] = dpu_vs_get(dpu, vs_ids[i]);
+ if (IS_ERR(res->vs[i]))
+ return PTR_ERR(res->vs[i]);
+ }
+ grp->hw_plane_vscaler_num = ARRAY_SIZE(res->vs);
+ }
+
+ grp->hw_plane_num = fds->num + fls->num + fws->num;
+
+ return 0;
+}
+
+static void dpu_put_plane_resource(struct dpu_plane_res *res)
+{
+ struct dpu_plane_grp *grp = plane_res_to_grp(res);
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(res->ed); i++) {
+ if (!IS_ERR_OR_NULL(res->ed[i]))
+ dpu_ed_put(res->ed[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->fd); i++) {
+ if (!IS_ERR_OR_NULL(res->fd[i]))
+ dpu_fd_put(res->fd[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->fe); i++) {
+ if (!IS_ERR_OR_NULL(res->fe[i]))
+ dpu_fe_put(res->fe[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->fl); i++) {
+ if (!IS_ERR_OR_NULL(res->fl[i]))
+ dpu_fl_put(res->fl[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->fw); i++) {
+ if (!IS_ERR_OR_NULL(res->fw[i]))
+ dpu_fw_put(res->fw[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->hs); i++) {
+ if (!IS_ERR_OR_NULL(res->hs[i]))
+ dpu_hs_put(res->hs[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->lb); i++) {
+ if (!IS_ERR_OR_NULL(res->lb[i]))
+ dpu_lb_put(res->lb[i]);
+ }
+ for (i = 0; i < ARRAY_SIZE(res->vs); i++) {
+ if (!IS_ERR_OR_NULL(res->vs[i]))
+ dpu_vs_put(res->vs[i]);
+ }
+
+ grp->hw_plane_num = 0;
+}
+
+static int dpu_add_client_devices(struct dpu_soc *dpu)
+{
+ const struct dpu_data *data = dpu->data;
+ struct device *dev = dpu->dev;
+ struct dpu_platform_reg *reg;
+ struct dpu_plane_grp *plane_grp;
+ struct dpu_store *st9 = NULL;
+ size_t client_num, reg_size;
+ int i, id, ret;
+
+ client_num = ARRAY_SIZE(client_reg);
+
+ reg = devm_kcalloc(dev, client_num, sizeof(*reg), GFP_KERNEL);
+ if (!reg)
+ return -ENODEV;
+
+ plane_grp = devm_kzalloc(dev, sizeof(*plane_grp), GFP_KERNEL);
+ if (!plane_grp)
+ return -ENODEV;
+
+ mutex_init(&plane_grp->mutex);
+
+ mutex_lock(&dpu_client_id_mutex);
+ id = dpu_client_id;
+ dpu_client_id += client_num;
+ mutex_unlock(&dpu_client_id_mutex);
+
+ reg_size = client_num * sizeof(struct dpu_platform_reg);
+ memcpy(reg, &client_reg[0], reg_size);
+
+ plane_grp->src_mask = data->plane_src_mask;
+ plane_grp->id = id / client_num;
+ plane_grp->has_vproc = display_plane_video_proc;
+
+ ret = dpu_get_plane_resource(dpu, &plane_grp->res);
+ if (ret)
+ goto err_get_plane_res;
+
+ st9 = dpu_st_get(dpu, 9);
+ if (IS_ERR(st9)) {
+ ret = PTR_ERR(st9);
+ goto err_get_plane_res;
+ }
+
+ for (i = 0; i < client_num; i++) {
+ struct platform_device *pdev;
+ struct device_node *of_node = NULL;
+
+ if (!strcmp(reg[i].name, IMX_DPU_BLITENG_NAME)) {
+ /* As bliteng has no of_node, so to use dpu's. */
+ of_node = dev->of_node;
+ } else {
+ /* Associate subdevice with the corresponding port node. */
+ of_node = of_graph_get_port_by_id(dev->of_node, i);
+ if (!of_node) {
+ dev_info(dev,
+ "no port@%d node in %s, not using DISP%d\n",
+ i, dev->of_node->full_name, i);
+ continue;
+ }
+ }
+
+ reg[i].pdata.plane_grp = plane_grp;
+ reg[i].pdata.di_grp_id = plane_grp->id;
+ reg[i].pdata.st9 = st9;
+
+ pdev = platform_device_alloc(reg[i].name, id++);
+ if (!pdev) {
+ ret = -ENOMEM;
+ goto err_register;
+ }
+
+ pdev->dev.parent = dev;
+
+ reg[i].pdata.of_node = of_node;
+ ret = platform_device_add_data(pdev, &reg[i].pdata,
+ sizeof(reg[i].pdata));
+ if (!ret)
+ ret = platform_device_add(pdev);
+ if (ret) {
+ platform_device_put(pdev);
+ goto err_register;
+ }
+ }
+
+ return 0;
+
+err_register:
+ platform_device_unregister_children(to_platform_device(dev));
+ dpu_st_put(st9);
+err_get_plane_res:
+ dpu_put_plane_resource(&plane_grp->res);
+
+ return ret;
+}
+
+static int dpu_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *of_id;
+ struct device_node *np = pdev->dev.of_node;
+ struct dpu_soc *dpu;
+ struct resource *res;
+ unsigned long dpu_base;
+ const struct dpu_data *data;
+ int ret;
+
+ of_id = of_match_device(dpu_dt_ids, &pdev->dev);
+ if (!of_id)
+ return -ENODEV;
+
+ data = of_id->data;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return -ENODEV;
+
+ dpu_base = res->start;
+
+ dpu = devm_kzalloc(&pdev->dev, sizeof(*dpu), GFP_KERNEL);
+ if (!dpu)
+ return -ENODEV;
+
+ dpu->dev = &pdev->dev;
+ dpu->data = data;
+ dpu->id = of_alias_get_id(np, "dpu");
+ dpu->irq_line_num = platform_irq_count(pdev);
+ if (dpu->irq_line_num < 0)
+ return dpu->irq_line_num;
+
+ dpu_units_addr_dbg(dpu, pdev, dpu_base);
+
+ ret = dpu_get_irq(pdev, dpu);
+ if (ret < 0)
+ return ret;
+
+ ret = dpu_sc_misc_get_handle(dpu);
+ if (ret < 0)
+ return ret;
+
+ spin_lock_init(&dpu->lock);
+
+ dpu->cm_reg = devm_ioremap(dpu->dev, dpu_base + data->cm_ofs, SZ_1K);
+ if (!dpu->cm_reg)
+ return -ENOMEM;
+
+ ret = dpu_attach_pm_domains(dpu);
+ if (ret)
+ return ret;
+
+ ret = dpu_irq_init(dpu);
+ if (ret)
+ goto failed_irq;
+
+ ret = dpu_submodules_init(dpu, pdev, dpu_base);
+ if (ret)
+ goto failed_submodules_init;
+
+ ret = dpu_sc_misc_init(dpu);
+ if (ret < 0) {
+ dev_err(dpu->dev,
+ "failed to initialize pixel link %d\n", ret);
+ goto failed_sc_misc_init;
+ }
+
+ platform_set_drvdata(pdev, dpu);
+
+ ret = dpu_add_client_devices(dpu);
+ if (ret) {
+ dev_err(dpu->dev,
+ "adding client devices failed with %d\n", ret);
+ goto failed_add_clients;
+ }
+
+ dev_info(dpu->dev, "driver probed\n");
+
+ return 0;
+
+failed_add_clients:
+ platform_set_drvdata(pdev, NULL);
+failed_sc_misc_init:
+failed_submodules_init:
+ dpu_irq_exit(dpu);
+failed_irq:
+ dpu_detach_pm_domains(dpu);
+ return ret;
+}
+
+static int dpu_remove(struct platform_device *pdev)
+{
+ struct dpu_soc *dpu = platform_get_drvdata(pdev);
+
+ platform_device_unregister_children(pdev);
+
+ dpu_irq_exit(dpu);
+ dpu_detach_pm_domains(dpu);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int dpu_suspend(struct device *dev)
+{
+ /*
+ * The dpu core driver currently depends on the client drivers
+ * to do suspend operations to leave dpu a cleaned up state
+ * machine status before the system enters sleep mode.
+ */
+ return 0;
+}
+
+static int dpu_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct dpu_soc *dpu = platform_get_drvdata(pdev);
+
+ dpu_sc_misc_init(dpu);
+
+ _dpu_submodules_init(dpu, pdev);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops dpu_pm_ops = {
+ SET_LATE_SYSTEM_SLEEP_PM_OPS(dpu_suspend, dpu_resume)
+};
+
+static struct platform_driver dpu_driver = {
+ .driver = {
+ .pm = &dpu_pm_ops,
+ .name = "dpu-core",
+ .of_match_table = dpu_dt_ids,
+ },
+ .probe = dpu_probe,
+ .remove = dpu_remove,
+};
+
+module_platform_driver(dpu_driver);
+
+MODULE_DESCRIPTION("i.MX DPU driver");
+MODULE_AUTHOR("Freescale Semiconductor, Inc.");
+MODULE_LICENSE("GPL");