diff options
Diffstat (limited to 'drivers/gpu/imx/dpu/dpu-common.c')
-rw-r--r-- | drivers/gpu/imx/dpu/dpu-common.c | 1809 |
1 files changed, 1809 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..4e31c1ec5b0b --- /dev/null +++ b/drivers/gpu/imx/dpu/dpu-common.c @@ -0,0 +1,1809 @@ +/* + * Copyright (C) 2016 Freescale Semiconductor, Inc. + * Copyright 2017-2018 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/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/of_graph.h> +#include <linux/platform_device.h> +#include <linux/regmap.h> +#include <soc/imx8/sc/sci.h> +#include <video/dpu.h> +#include <video/imx8-prefetch.h> +#include "dpu-prv.h" + +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(IPIDENTIFIER, ipidentifier); + +#define DESIGNDELIVERYID_MASK 0xF0U +#define DESIGNDELIVERYID_SHIFT 4U + +#define DESIGNMATURITYLEVEL_MASK 0xF00U +#define DESIGNMATURITYLEVEL_SHIFT 8U +enum design_maturity_level { + /* Pre feasibility study. */ + DESIGNMATURITYLEVEL__PREFS = 1 << DESIGNMATURITYLEVEL_SHIFT, + /* Feasibility study. */ + DESIGNMATURITYLEVEL__FS = 2 << DESIGNMATURITYLEVEL_SHIFT, + /* Functionality complete. */ + DESIGNMATURITYLEVEL__R0 = 3 << DESIGNMATURITYLEVEL_SHIFT, + /* Verification complete. */ + DESIGNMATURITYLEVEL__R1 = 4 << DESIGNMATURITYLEVEL_SHIFT, +}; + +#define IPEVOLUTION_MASK 0xF000U +#define IPEVOLUTION_SHIFT 12U + +#define IPFEATURESET_MASK 0xF0000U +#define IPFEATURESET_SHIFT 16U +enum ip_feature_set { + /* Minimal functionality (Eco). */ + IPFEATURESET__E = 1 << IPFEATURESET_SHIFT, + /* Reduced functionality (Light). */ + IPFEATURESET__L = 2 << IPFEATURESET_SHIFT, + /* Advanced functionality (Plus). */ + IPFEATURESET__P = 4 << IPFEATURESET_SHIFT, + /* Extensive functionality (eXtensive). */ + IPFEATURESET__X = 5 << IPFEATURESET_SHIFT, +}; + +#define IPAPPLICATION_MASK 0xF00000U +#define IPAPPLICATION_SHIFT 20U +enum ip_application { + /* Blit Engine only. */ + IPAPPLICATION__B = 1 << IPAPPLICATION_SHIFT, + /* Blit Engine and Display Controller. */ + IPAPPLICATION__D = 2 << IPAPPLICATION_SHIFT, + /* Display Controller only (with direct capture). */ + IPAPPLICATION__V = 3 << IPAPPLICATION_SHIFT, + /* + * Blit Engine, Display Controller (with direct capture), + * Capture Controller (buffered capture) and Drawing Engine. + */ + IPAPPLICATION__G = 4 << IPAPPLICATION_SHIFT, + /* Display Controller only. */ + IPAPPLICATION__C = 5 << IPAPPLICATION_SHIFT, +}; + +#define IPCONFIGURATION_MASK 0xF000000U +#define IPCONFIGURATION_SHIFT 24U +enum ip_configuration { + /* Graphics core only (Module). */ + IPCONFIGURATION__M = 1 << IPCONFIGURATION_SHIFT, + /* Subsystem including a graphics core (System). */ + IPCONFIGURATION__S = 2 << IPCONFIGURATION_SHIFT, +}; + +#define IPFAMILY_MASK 0xF0000000U +#define IPFAMILY_SHIFT 28U +enum ip_family { + /* IMXDPU building block generation 2010. */ + IPFAMILY__IMXDPU2010 = 0, + /* IMXDPU building block generation 2012. */ + IPFAMILY__IMXDPU2012 = 1 << IPFAMILY_SHIFT, + /* IMXDPU building block generation 2013. */ + IPFAMILY__IMXDPU2013 = 2 << IPFAMILY_SHIFT, +}; + +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, u32 value, + unsigned int offset) +{ + 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_v1[] = {0x980, 0xa00, 0x9c0, 0xa40}; +static const unsigned long cf_pec_ofss_v2[] = {0x960, 0x9e0, 0x9a0, 0xa20}; + +/* Display Engine Configuration Unit */ +static const unsigned long dec_ofss_v1[] = {0x10000, 0x10020}; +static const unsigned long dec_ofss_v2[] = {0xb400, 0xb420}; + +/* External Destination Unit */ +static const unsigned long ed_ofss[] = {0x4800, 0x5800, 0x5000, 0x6000}; +static const unsigned long ed_pec_ofss_v1[] = {0x9a0, 0xa20, 0x9e0, 0xa60}; +static const unsigned long ed_pec_ofss_v2[] = {0x980, 0xa00, 0x9c0, 0xa40}; + +/* Fetch Decode Unit */ +static const unsigned long fd_ofss_v1[] = {0x8c00, 0x9800, 0x7400, 0x7c00}; +static const unsigned long fd_ofss_v2[] = {0x6c00, 0x7800}; +static const unsigned long fd_pec_ofss_v1[] = {0xb60, 0xb80, 0xb00, 0xb20}; +static const unsigned long fd_pec_ofss_v2[] = {0xa80, 0xaa0}; + +/* Fetch ECO Unit */ +static const unsigned long fe_ofss_v1[] = {0x9400, 0xa000, 0x8800, 0x1c00}; +static const unsigned long fe_ofss_v2[] = {0x7400, 0x8000, 0x6800, 0x1c00}; +static const unsigned long fe_pec_ofss_v1[] = {0xb70, 0xb90, 0xb50, 0x870}; +static const unsigned long fe_pec_ofss_v2[] = {0xa90, 0xab0, 0xa70, 0x850}; + +/* Frame Generator Unit */ +static const unsigned long fg_ofss_v1[] = {0x10c00, 0x12800}; +static const unsigned long fg_ofss_v2[] = {0xb800, 0xd400}; + +/* Fetch Layer Unit */ +static const unsigned long fl_ofss_v1[] = {0xa400, 0xac00}; +static const unsigned long fl_ofss_v2[] = {0x8400}; +static const unsigned long fl_pec_ofss_v1[] = {0xba0, 0xbb0}; +static const unsigned long fl_pec_ofss_v2[] = {0xac0}; + +/* Fetch Warp Unit */ +static const unsigned long fw_ofss_v1[] = {0x8400}; +static const unsigned long fw_ofss_v2[] = {0x6400}; +static const unsigned long fw_pec_ofss_v1[] = {0xb40}; +static const unsigned long fw_pec_ofss_v2[] = {0xa60}; + +/* Horizontal Scaler Unit */ +static const unsigned long hs_ofss_v1[] = {0xbc00, 0xd000, 0x3000}; +static const unsigned long hs_ofss_v2[] = {0x9000, 0x9c00, 0x3000}; +static const unsigned long hs_pec_ofss_v1[] = {0xc00, 0xca0, 0x8e0}; +static const unsigned long hs_pec_ofss_v2[] = {0xb00, 0xb60, 0x8c0}; + +/* Layer Blend Unit */ +static const unsigned long lb_ofss_v1[] = {0xdc00, 0xe000, 0xe400, 0xe800, + 0xec00, 0xf000, 0xf400}; +static const unsigned long lb_ofss_v2[] = {0xa400, 0xa800, 0xac00, 0xb000}; +static const unsigned long lb_pec_ofss_v1[] = {0xd00, 0xd20, 0xd40, 0xd60, + 0xd80, 0xda0, 0xdc0}; +static const unsigned long lb_pec_ofss_v2[] = {0xba0, 0xbc0, 0xbe0, 0xc00}; + +/* Timing Controller Unit */ +static const unsigned long tcon_ofss_v1[] = {0x12000, 0x13c00}; +static const unsigned long tcon_ofss_v2[] = {0xcc00, 0xe800}; + +/* Vertical Scaler Unit */ +static const unsigned long vs_ofss_v1[] = {0xc000, 0xd400, 0x3400}; +static const unsigned long vs_ofss_v2[] = {0x9400, 0xa000, 0x3400}; +static const unsigned long vs_pec_ofss_v1[] = {0xc20, 0xcc0, 0x900}; +static const unsigned long vs_pec_ofss_v2[] = {0xb20, 0xb80, 0x8e0}; + +static const struct dpu_unit cfs_v1 = { + .name = "ConstFrame", + .num = ARRAY_SIZE(cf_ids), + .ids = cf_ids, + .pec_ofss = cf_pec_ofss_v1, + .ofss = cf_ofss, +}; + +static const struct dpu_unit cfs_v2 = { + .name = "ConstFrame", + .num = ARRAY_SIZE(cf_ids), + .ids = cf_ids, + .pec_ofss = cf_pec_ofss_v2, + .ofss = cf_ofss, +}; + +static const struct dpu_unit decs_v1 = { + .name = "DisEngCfg", + .num = ARRAY_SIZE(dec_ids), + .ids = dec_ids, + .pec_ofss = NULL, + .ofss = dec_ofss_v1, +}; + +static const struct dpu_unit decs_v2 = { + .name = "DisEngCfg", + .num = ARRAY_SIZE(dec_ids), + .ids = dec_ids, + .pec_ofss = NULL, + .ofss = dec_ofss_v2, +}; + +static const struct dpu_unit eds_v1 = { + .name = "ExtDst", + .num = ARRAY_SIZE(ed_ids), + .ids = ed_ids, + .pec_ofss = ed_pec_ofss_v1, + .ofss = ed_ofss, +}; + +static const struct dpu_unit eds_v2 = { + .name = "ExtDst", + .num = ARRAY_SIZE(ed_ids), + .ids = ed_ids, + .pec_ofss = ed_pec_ofss_v2, + .ofss = ed_ofss, +}; + +static const struct dpu_unit fds_v1 = { + .name = "FetchDecode", + .num = ARRAY_SIZE(fd_ids), + .ids = fd_ids, + .pec_ofss = fd_pec_ofss_v1, + .ofss = fd_ofss_v1, +}; + +static const struct dpu_unit fds_v2 = { + .name = "FetchDecode", + .num = 2, + .ids = fd_ids, + .pec_ofss = fd_pec_ofss_v2, + .ofss = fd_ofss_v2, + .dprc_ids = fd_dprc_ids, +}; + +static const struct dpu_unit fes_v1 = { + .name = "FetchECO", + .num = ARRAY_SIZE(fe_ids), + .ids = fe_ids, + .pec_ofss = fe_pec_ofss_v1, + .ofss = fe_ofss_v1, +}; + +static const struct dpu_unit fes_v2 = { + .name = "FetchECO", + .num = ARRAY_SIZE(fe_ids), + .ids = fe_ids, + .pec_ofss = fe_pec_ofss_v2, + .ofss = fe_ofss_v2, +}; + +static const struct dpu_unit fgs_v1 = { + .name = "FrameGen", + .num = ARRAY_SIZE(fg_ids), + .ids = fg_ids, + .pec_ofss = NULL, + .ofss = fg_ofss_v1, +}; + +static const struct dpu_unit fgs_v2 = { + .name = "FrameGen", + .num = ARRAY_SIZE(fg_ids), + .ids = fg_ids, + .pec_ofss = NULL, + .ofss = fg_ofss_v2, +}; + +static const struct dpu_unit fls_v1 = { + .name = "FetchLayer", + .num = ARRAY_SIZE(fl_ids), + .ids = fl_ids, + .pec_ofss = fl_pec_ofss_v1, + .ofss = fl_ofss_v1, +}; + +static const struct dpu_unit fls_v2 = { + .name = "FetchLayer", + .num = 1, + .ids = fl_ids, + .pec_ofss = fl_pec_ofss_v2, + .ofss = fl_ofss_v2, + .dprc_ids = fl_dprc_ids, +}; + +static const struct dpu_unit fws_v1 = { + .name = "FetchWarp", + .num = ARRAY_SIZE(fw_ids), + .ids = fw_ids, + .pec_ofss = fw_pec_ofss_v1, + .ofss = fw_ofss_v1, +}; + +static const struct dpu_unit fws_v2 = { + .name = "FetchWarp", + .num = ARRAY_SIZE(fw_ids), + .ids = fw_ids, + .pec_ofss = fw_pec_ofss_v2, + .ofss = fw_ofss_v2, + .dprc_ids = fw_dprc_ids, +}; + +static const struct dpu_unit hss_v1 = { + .name = "HScaler", + .num = ARRAY_SIZE(hs_ids), + .ids = hs_ids, + .pec_ofss = hs_pec_ofss_v1, + .ofss = hs_ofss_v1, +}; + +static const struct dpu_unit hss_v2 = { + .name = "HScaler", + .num = ARRAY_SIZE(hs_ids), + .ids = hs_ids, + .pec_ofss = hs_pec_ofss_v2, + .ofss = hs_ofss_v2, +}; + +static const struct dpu_unit lbs_v1 = { + .name = "LayerBlend", + .num = ARRAY_SIZE(lb_ids), + .ids = lb_ids, + .pec_ofss = lb_pec_ofss_v1, + .ofss = lb_ofss_v1, +}; + +static const struct dpu_unit lbs_v2 = { + .name = "LayerBlend", + .num = 4, + .ids = lb_ids, + .pec_ofss = lb_pec_ofss_v2, + .ofss = lb_ofss_v2, +}; + +static const struct dpu_unit tcons_v1 = { + .name = "TCon", + .num = ARRAY_SIZE(tcon_ids), + .ids = tcon_ids, + .pec_ofss = NULL, + .ofss = tcon_ofss_v1, +}; + +static const struct dpu_unit tcons_v2 = { + .name = "TCon", + .num = ARRAY_SIZE(tcon_ids), + .ids = tcon_ids, + .pec_ofss = NULL, + .ofss = tcon_ofss_v2, +}; + +static const struct dpu_unit vss_v1 = { + .name = "VScaler", + .num = ARRAY_SIZE(vs_ids), + .ids = vs_ids, + .pec_ofss = vs_pec_ofss_v1, + .ofss = vs_ofss_v1, +}; + +static const struct dpu_unit vss_v2 = { + .name = "VScaler", + .num = ARRAY_SIZE(vs_ids), + .ids = vs_ids, + .pec_ofss = vs_pec_ofss_v2, + .ofss = vs_ofss_v2, +}; + +static const struct cm_reg_ofs cm_reg_ofs_v1 = { + .ipidentifier = 0, + .lockunlock = 0x80, + .lockstatus = 0x84, + .userinterruptmask = 0x88, + .interruptenable = 0x94, + .interruptpreset = 0xa0, + .interruptclear = 0xac, + .interruptstatus = 0xb8, + .userinterruptenable = 0x100, + .userinterruptpreset = 0x10c, + .userinterruptclear = 0x118, + .userinterruptstatus = 0x124, + .generalpurpose = 0x200, +}; + +static const struct cm_reg_ofs cm_reg_ofs_v2 = { + .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 int intsteer_map_v1[] = { + /* 0 1 2 3 4 5 6 7 */ /* 0~31: int0 */ + 448, 449, 450, 64, 65, 66, 67, 68, + /* 8 9 10 11 12 13 14 15 */ + 69, 70, 193, 194, 195, 196, 197, 320, + /* 16 17 18 19 20 21 22 23 */ + 321, 322, 384, 385, 386, NA, 323, NA, + /* 24 25 26 27 28 29 30 31 */ + 387, 71, 198, 72, 73, 74, 75, 76, + /* 32 33 34 35 36 37 38 39 */ /* 32~63: int1 */ + 77, 78, 79, 80, 81, 199, 200, 201, + /* 40 41 42 43 44 45 46 47 */ + 202, 203, 204, 205, 206, 207, 208, 324, + /* 48 49 50 51 52 53 54 55 */ + 389, NA, 0, 1, 2, 3, 4, 82, + /* 56 57 58 59 60 61 62 63 */ + 83, 84, 85, 209, 210, 211, 212, 325, + /* 64 65 66 */ /* 64+: int2 */ + 326, 390, 391, +}; +static const unsigned long unused_irq_v1[] = {0x00a00000, 0x00020000, + 0xfffffff8}; + +static const unsigned int intsteer_map_v2[] = { + /* 0 1 2 3 4 5 6 7 */ /* 0~31: int0 */ + 448, 449, 450, 64, 65, 66, 67, 68, + /* 8 9 10 11 12 13 14 15 */ + 69, 70, 193, 194, 195, 196, 197, 72, + /* 16 17 18 19 20 21 22 23 */ + 73, 74, 75, 76, 77, 78, 79, 80, + /* 24 25 26 27 28 29 30 31 */ + 81, 199, 200, 201, 202, 203, 204, 205, + /* 32 33 34 35 36 37 38 39 */ /* 32+: int1 */ + 206, 207, 208, NA, 0, 1, 2, 3, + /* 40 41 42 43 44 45 46 47 */ + 4, 82, 83, 84, 85, 209, 210, 211, + /* 48 */ + 212, +}; +static const unsigned long unused_irq_v2[] = {0x00000000, 0xfffe0008}; + +static const unsigned int sw2hw_irq_map_v2[] = { + /* 0 1 2 3 4 5 6 7 */ + 0, 1, 2, 3, 4, 5, 6, 7, + /* 8 9 10 11 12 13 14 15 */ + 8, 9, 10, 11, 12, 13, 14, NA, + /* 16 17 18 19 20 21 22 23 */ + NA, NA, NA, NA, NA, NA, NA, NA, + /* 24 25 26 27 28 29 30 31 */ + NA, NA, NA, 15, 16, 17, 18, 19, + /* 32 33 34 35 36 37 38 39 */ + 20, 21, 22, 23, 24, 25, 26, 27, + /* 40 41 42 43 44 45 46 47 */ + 28, 29, 30, 31, 32, 33, 34, NA, + /* 48 49 50 51 52 53 54 55 */ + NA, NA, 36, 37, 38, 39, 40, 41, + /* 56 57 58 59 60 61 62 63 */ + 42, 43, 44, 45, 46, 47, 48, NA, + /* 64 65 66 */ + NA, NA, NA, +}; + +/* FIXME: overkill for some N/As, revive them when needed */ +static const unsigned int sw2hw_block_id_map_v2[] = { + /* 0 1 2 3 4 5 6 7 */ + 0x00, NA, NA, 0x03, NA, NA, NA, 0x07, + /* 8 9 10 11 12 13 14 15 */ + 0x08, NA, 0x0a, NA, 0x0c, NA, 0x0e, NA, + /* 16 17 18 19 20 21 22 23 */ + 0x10, NA, 0x12, NA, NA, NA, NA, NA, + /* 24 25 26 27 28 29 30 31 */ + NA, NA, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, + /* 32 33 34 35 36 37 38 39 */ + 0x1a, NA, NA, 0x1b, 0x1c, 0x1d, NA, NA, + /* 40 41 42 43 44 45 46 47 */ + 0x1e, 0x1f, 0x20, NA, 0x21, 0x22, 0x23, 0x24, + /* 48 49 50 51 52 53 54 55 */ + NA, NA, NA, NA, NA, NA, NA, NA, + /* 56 57 58 59 60 61 62 63 */ + NA, NA, NA, NA, NA, NA, NA, NA, + /* 64 65 66 67 */ + NA, NA, NA, NA, +}; + +static const struct dpu_devtype dpu_type_v1 = { + .cm_ofs = 0x0, + .cfs = &cfs_v1, + .decs = &decs_v1, + .eds = &eds_v1, + .fds = &fds_v1, + .fes = &fes_v1, + .fgs = &fgs_v1, + .fls = &fls_v1, + .fws = &fws_v1, + .hss = &hss_v1, + .lbs = &lbs_v1, + .tcons = &tcons_v1, + .vss = &vss_v1, + .cm_reg_ofs = &cm_reg_ofs_v1, + .intsteer_map = intsteer_map_v1, + .intsteer_map_size = ARRAY_SIZE(intsteer_map_v1), + .unused_irq = unused_irq_v1, + .plane_src_na_mask = 0xffffff80, + .has_capture = true, + .has_prefetch = false, + .has_disp_sel_clk = false, + .has_dual_ldb = false, + .pixel_link_quirks = false, + .pixel_link_nhvsync = false, + .version = DPU_V1, +}; + +static const struct dpu_devtype dpu_type_v2_qm = { + .cm_ofs = 0x0, + .cfs = &cfs_v2, + .decs = &decs_v2, + .eds = &eds_v2, + .fds = &fds_v2, + .fes = &fes_v2, + .fgs = &fgs_v2, + .fls = &fls_v2, + .fws = &fws_v2, + .hss = &hss_v2, + .lbs = &lbs_v2, + .tcons = &tcons_v2, + .vss = &vss_v2, + .cm_reg_ofs = &cm_reg_ofs_v2, + .intsteer_map = intsteer_map_v2, + .intsteer_map_size = ARRAY_SIZE(intsteer_map_v2), + .unused_irq = unused_irq_v2, + .sw2hw_irq_map = sw2hw_irq_map_v2, + .sw2hw_block_id_map = sw2hw_block_id_map_v2, + .plane_src_na_mask = 0xffffffe2, + .has_capture = false, + .has_prefetch = true, + .has_disp_sel_clk = true, + .has_dual_ldb = false, + .pixel_link_quirks = true, + .pixel_link_nhvsync = true, + .version = DPU_V2, +}; + +static const struct dpu_devtype dpu_type_v2_qxp = { + .cm_ofs = 0x0, + .cfs = &cfs_v2, + .decs = &decs_v2, + .eds = &eds_v2, + .fds = &fds_v2, + .fes = &fes_v2, + .fgs = &fgs_v2, + .fls = &fls_v2, + .fws = &fws_v2, + .hss = &hss_v2, + .lbs = &lbs_v2, + .tcons = &tcons_v2, + .vss = &vss_v2, + .cm_reg_ofs = &cm_reg_ofs_v2, + .intsteer_map = intsteer_map_v2, + .intsteer_map_size = ARRAY_SIZE(intsteer_map_v2), + .unused_irq = unused_irq_v2, + .sw2hw_irq_map = sw2hw_irq_map_v2, + .sw2hw_block_id_map = sw2hw_block_id_map_v2, + .plane_src_na_mask = 0xffffffe2, + .has_capture = false, + .has_prefetch = true, + .has_disp_sel_clk = false, + .has_dual_ldb = true, + .pixel_link_quirks = true, + .pixel_link_nhvsync = true, + .version = DPU_V2, +}; + +static const struct of_device_id dpu_dt_ids[] = { + { + .compatible = "fsl,imx8qm-dpu", + .data = &dpu_type_v2_qm, + }, { + .compatible = "fsl,imx8qxp-dpu", + .data = &dpu_type_v2_qxp, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, dpu_dt_ids); + +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); +} + +#define _DPU_UNITS_INIT(unit) \ +{ \ + const struct dpu_unit *us = devtype->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_devtype *devtype = dpu->devtype; + + _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(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 = devtype->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_devtype *devtype = dpu->devtype; + const struct dpu_unit *fds = devtype->fds; + const struct dpu_unit *fls = devtype->fls; + const struct dpu_unit *fws = devtype->fws; + + 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(tcon); + DPU_UNITS_INIT(vs); + + /* get DPR channel for submodules */ + if (devtype->has_prefetch) { + struct dpu_fetchunit *fu; + struct dprc *dprc; + int i; + + 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); + } + } + + return 0; +} + +#define DPU_UNITS_ADDR_DBG(unit) \ +{ \ + const struct dpu_unit *us = devtype->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]); \ + } \ + } \ +} + +enum dpu_irq_line { + DPU_IRQ_LINE_CM = 0, + DPU_IRQ_LINE_STREAM0A = 1, + DPU_IRQ_LINE_STREAM1A = 3, + DPU_IRQ_LINE_RESERVED0 = 5, + DPU_IRQ_LINE_RESERVED1 = 6, + DPU_IRQ_LINE_BLIT = 7, +}; + +static inline unsigned int dpu_get_max_intsteer_num(enum dpu_irq_line irq_line) +{ + return 64 * (++irq_line) - 1; +} + +static inline unsigned int dpu_get_min_intsteer_num(enum dpu_irq_line irq_line) +{ + return 64 * irq_line; +} + +static void +dpu_inner_irq_handle(struct irq_desc *desc, enum dpu_irq_line irq_line) +{ + struct dpu_soc *dpu = irq_desc_get_handler_data(desc); + const struct dpu_devtype *devtype = dpu->devtype; + const struct cm_reg_ofs *ofs = devtype->cm_reg_ofs; + struct irq_chip *chip = irq_desc_get_chip(desc); + unsigned int i, virq, min_intsteer_num, max_intsteer_num; + u32 status; + + chained_irq_enter(chip, desc); + + min_intsteer_num = dpu_get_min_intsteer_num(irq_line); + max_intsteer_num = dpu_get_max_intsteer_num(irq_line); + + for (i = 0; i < devtype->intsteer_map_size; i++) { + if (devtype->intsteer_map[i] >= min_intsteer_num && + devtype->intsteer_map[i] <= max_intsteer_num) { + status = dpu_cm_read(dpu, + USERINTERRUPTSTATUS(ofs, i / 32)); + status &= dpu_cm_read(dpu, + USERINTERRUPTENABLE(ofs, i / 32)); + + if (status & BIT(i % 32)) { + virq = irq_linear_revmap(dpu->domain, i); + if (virq) { + generic_handle_irq(virq); + } + } + } + } + + chained_irq_exit(chip, desc); +} + +#define DPU_INNER_IRQ_HANDLER_DEFINE(name1, name2) \ +static void dpu_##name1##_irq_handler(struct irq_desc *desc) \ +{ \ + dpu_inner_irq_handle(desc, DPU_IRQ_LINE_##name2); \ +} + +DPU_INNER_IRQ_HANDLER_DEFINE(cm, CM) +DPU_INNER_IRQ_HANDLER_DEFINE(stream0a, STREAM0A) +DPU_INNER_IRQ_HANDLER_DEFINE(stream1a, STREAM1A) +DPU_INNER_IRQ_HANDLER_DEFINE(reserved0, RESERVED0) +DPU_INNER_IRQ_HANDLER_DEFINE(reserved1, RESERVED1) +DPU_INNER_IRQ_HANDLER_DEFINE(blit, BLIT) + +int dpu_map_inner_irq(struct dpu_soc *dpu, int irq) +{ + const unsigned int *sw2hw_irq_map = dpu->devtype->sw2hw_irq_map; + int virq, mapped_irq; + + mapped_irq = sw2hw_irq_map ? sw2hw_irq_map[irq] : irq; + if (WARN_ON(mapped_irq == NA)) + return -EINVAL; + + virq = irq_linear_revmap(dpu->domain, mapped_irq); + if (!virq) + virq = irq_create_mapping(dpu->domain, mapped_irq); + + return virq; +} +EXPORT_SYMBOL_GPL(dpu_map_inner_irq); + +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[] = { + { + /* placeholder */ + .pdata = { }, + .name = "imx-dpu-csi", + }, { + /* placeholder */ + .pdata = { }, + .name = "imx-dpu-csi", + }, { + .pdata = { + .stream_id = 0, + }, + .name = "imx-dpu-crtc", + }, { + .pdata = { + .stream_id = 1, + }, + .name = "imx-dpu-crtc", + }, { + .pdata = { }, + .name = "imx-drm-dpu-bliteng", + }, +}; + +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->devtype->fds; + const struct dpu_unit *fls = dpu->devtype->fls; + const struct dpu_unit *fws = dpu->devtype->fws; + const struct dpu_unit *lbs = dpu->devtype->lbs; + struct dpu_plane_grp *grp = plane_res_to_grp(res); + int i; + + for (i = 0; i < ARRAY_SIZE(res->cf); i++) { + res->cf[i] = dpu_cf_get(dpu, i); + if (IS_ERR(res->cf[i])) + return PTR_ERR(res->cf[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->cf); i++) { + if (!IS_ERR_OR_NULL(res->cf[i])) + dpu_cf_put(res->cf[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_devtype *devtype = dpu->devtype; + struct device *dev = dpu->dev; + struct dpu_platform_reg *reg; + struct dpu_plane_grp *plane_grp; + size_t client_num, reg_size; + int i, id, ret; + + client_num = ARRAY_SIZE(client_reg); + if (!devtype->has_capture) + client_num -= 2; + + 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); + if (devtype->has_capture) + memcpy(reg, client_reg, reg_size); + else + memcpy(reg, &client_reg[2], reg_size); + + plane_grp->src_na_mask = devtype->plane_src_na_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; + + for (i = 0; i < client_num; i++) { + struct platform_device *pdev; + struct device_node *of_node = NULL; + bool is_disp, is_bliteng; + + if (devtype->has_capture) { + is_bliteng = (i == 4) ? true : false; + is_disp = (!is_bliteng) && ((i / 2) ? true : false); + } else { + is_bliteng = (i == 2) ? true : false; + is_disp = !is_bliteng; + } + + if (is_bliteng) { + /* 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 %s%d\n", + i, dev->of_node->full_name, + is_disp ? "DISP" : "CSI", i % 2); + continue; + } + } + + if (is_disp) + reg[i].pdata.plane_grp = plane_grp; + + 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, ®[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)); +err_get_plane_res: + dpu_put_plane_resource(&plane_grp->res); + + return ret; +} + +#define IRQSTEER_CHANnCTL 0x0 +#define IRQSTEER_CHANnCTL_CH(n) BIT(n) +#define IRQSTEER_CHANnMASK(n) ((n) + 4) +#define LINE_TO_MASK_OFFSET(n) ((15 - ((n) / 32)) * 4) +#define LINE_TO_MASK_SHIFT(n) ((n) % 32) + +static void dpu_inner_irq_gc_mask_set_bit(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct irq_chip_type *ct = irq_data_get_chip_type(d); + struct dpu_soc *dpu = gc->domain->host_data; + unsigned long flags; + u32 mask = d->mask; + + irq_gc_lock(gc); + spin_lock_irqsave(&dpu->intsteer_lock, flags); + if (++dpu->intsteer_usecount == 1) + /* assuming fast I/O regmap */ + regmap_write(dpu->intsteer_regmap, IRQSTEER_CHANnCTL, + IRQSTEER_CHANnCTL_CH(0)); + spin_unlock_irqrestore(&dpu->intsteer_lock, flags); + *ct->mask_cache |= mask; + irq_reg_writel(gc, *ct->mask_cache, ct->regs.mask); + irq_gc_unlock(gc); +} + +static void dpu_inner_irq_gc_mask_clr_bit(struct irq_data *d) +{ + struct irq_chip_generic *gc = irq_data_get_irq_chip_data(d); + struct irq_chip_type *ct = irq_data_get_chip_type(d); + struct dpu_soc *dpu = gc->domain->host_data; + unsigned long flags; + u32 mask = d->mask; + + irq_gc_lock(gc); + spin_lock_irqsave(&dpu->intsteer_lock, flags); + if (!--dpu->intsteer_usecount) { + WARN(dpu->intsteer_usecount < 0, + "intsteer usecount %d is less than zero", + dpu->intsteer_usecount); + regmap_write(dpu->intsteer_regmap, IRQSTEER_CHANnCTL, 0); + } + spin_unlock_irqrestore(&dpu->intsteer_lock, flags); + *ct->mask_cache &= ~mask; + irq_reg_writel(gc, *ct->mask_cache, ct->regs.mask); + irq_gc_unlock(gc); +} + +static void +dpu_inner_intsteer_enable_line(struct dpu_soc *dpu, unsigned int line) +{ + unsigned int offset = LINE_TO_MASK_OFFSET(line); + unsigned int shift = LINE_TO_MASK_SHIFT(line); + + regmap_update_bits(dpu->intsteer_regmap, IRQSTEER_CHANnMASK(offset), + BIT(shift), BIT(shift)); +} + +static void dpu_inner_intsteer_enable_lines(struct dpu_soc *dpu) +{ + const struct dpu_devtype *devtype = dpu->devtype; + int i; + + for (i = 0; i < devtype->intsteer_map_size; i++) { + if (devtype->intsteer_map[i] == NA) + continue; + + dpu_inner_intsteer_enable_line(dpu, devtype->intsteer_map[i]); + } +} + +static int dpu_inner_irq_init(struct dpu_soc *dpu) +{ + const struct dpu_devtype *devtype = dpu->devtype; + const struct cm_reg_ofs *ofs = devtype->cm_reg_ofs; + struct irq_chip_generic *gc; + struct irq_chip_type *ct; + int ret, i; + + dpu_inner_intsteer_enable_lines(dpu); + + dpu->domain = irq_domain_add_linear(dpu->dev->of_node, + devtype->intsteer_map_size, + &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 < devtype->intsteer_map_size; i += 32) { + /* Mask and clear all interrupts */ + dpu_cm_write(dpu, 0, + USERINTERRUPTENABLE(ofs, i / 32)); + dpu_cm_write(dpu, ~devtype->unused_irq[i / 32], + USERINTERRUPTCLEAR(ofs, i / 32)); + dpu_cm_write(dpu, 0, + INTERRUPTENABLE(ofs, i / 32)); + dpu_cm_write(dpu, ~devtype->unused_irq[i / 32], + INTERRUPTCLEAR(ofs, i / 32)); + + /* Set all interrupts to user mode */ + dpu_cm_write(dpu, ~devtype->unused_irq[i / 32], + USERINTERRUPTMASK(ofs, i / 32)); + + gc = irq_get_domain_generic_chip(dpu->domain, i); + gc->reg_base = dpu->cm_reg; + gc->unused = devtype->unused_irq[i / 32]; + ct = gc->chip_types; + ct->chip.irq_ack = irq_gc_ack_set_bit; + ct->chip.irq_mask = dpu_inner_irq_gc_mask_clr_bit; + ct->chip.irq_unmask = dpu_inner_irq_gc_mask_set_bit; + ct->regs.ack = USERINTERRUPTCLEAR(ofs, i / 32); + ct->regs.mask = USERINTERRUPTENABLE(ofs, i / 32); + } + +#define DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA1(name) \ +irq_set_chained_handler_and_data(dpu->irq_##name, dpu_##name##_irq_handler, dpu) + + DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA1(cm); + DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA1(stream0a); + DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA1(stream1a); + DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA1(reserved0); + DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA1(reserved1); + DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA1(blit); + + return 0; +} + +static void dpu_inner_irq_exit(struct dpu_soc *dpu) +{ + const struct dpu_devtype *devtype = dpu->devtype; + unsigned int i, irq; + +#define DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA2(name) \ +irq_set_chained_handler_and_data(dpu->irq_##name, NULL, NULL) + + DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA2(cm); + DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA2(stream0a); + DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA2(stream1a); + DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA2(reserved0); + DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA2(reserved1); + DPU_INNER_IRQ_SET_CHAINED_HANDLER_AND_DATA2(blit); + + for (i = 0; i < devtype->intsteer_map_size; i++) { + irq = irq_linear_revmap(dpu->domain, i); + if (irq) + irq_dispose_mapping(irq); + } + + irq_domain_remove(dpu->domain); +} + +static irqreturn_t dpu_dpr0_irq_handler(int irq, void *desc) +{ + struct dpu_soc *dpu = desc; + const struct dpu_unit *fls = dpu->devtype->fls; + struct dpu_fetchunit *fu; + int i; + + for (i = 0; i < fls->num; i++) { + fu = dpu->fl_priv[i]; + dprc_irq_handle(fu->dprc); + } + + return IRQ_HANDLED; +} + +static irqreturn_t dpu_dpr1_irq_handler(int irq, void *desc) +{ + struct dpu_soc *dpu = desc; + const struct dpu_unit *fds = dpu->devtype->fds; + const struct dpu_unit *fws = dpu->devtype->fws; + struct dpu_fetchunit *fu; + int i; + + for (i = 0; i < fds->num; i++) { + fu = dpu->fd_priv[i]; + dprc_irq_handle(fu->dprc); + } + + for (i = 0; i < fws->num; i++) { + fu = dpu->fw_priv[i]; + dprc_irq_handle(fu->dprc); + } + + return IRQ_HANDLED; +} + +static void dpu_debug_ip_identity(struct dpu_soc *dpu) +{ + struct device *dev = dpu->dev; + const struct cm_reg_ofs *ofs = dpu->devtype->cm_reg_ofs; + u32 reg; + int id = 0; + + reg = dpu_cm_read(dpu, IPIDENTIFIER(ofs)); + + dev_dbg(dev, "%d) Maturatiy level:\n", ++id); + switch (reg & DESIGNMATURITYLEVEL_MASK) { + case DESIGNMATURITYLEVEL__PREFS: + dev_dbg(dev, "\tPre feasibility study.\n"); + break; + case DESIGNMATURITYLEVEL__FS: + dev_dbg(dev, "\tFeasibility study.\n"); + break; + case DESIGNMATURITYLEVEL__R0: + dev_dbg(dev, "\tFunctionality complete.\n"); + break; + case DESIGNMATURITYLEVEL__R1: + dev_dbg(dev, "\tVerification complete.\n"); + break; + default: + dev_dbg(dev, "\tUnknown.\n"); + break; + } + + dev_dbg(dev, "%d) IP feature set:\n", ++id); + switch (reg & IPFEATURESET_MASK) { + case IPFEATURESET__E: + dev_dbg(dev, "\tMinimal functionality (Eco).\n"); + break; + case IPFEATURESET__L: + dev_dbg(dev, "\tReduced functionality (Light).\n"); + break; + case IPFEATURESET__P: + dev_dbg(dev, "\tAdvanced functionality (Plus).\n"); + break; + case IPFEATURESET__X: + dev_dbg(dev, "\tExtensive functionality (eXtensive).\n"); + break; + default: + dev_dbg(dev, "\tUnknown.\n"); + break; + } + + dev_dbg(dev, "%d) IP application:\n", ++id); + switch (reg & IPAPPLICATION_MASK) { + case IPAPPLICATION__B: + dev_dbg(dev, "\tBlit engine only.\n"); + break; + case IPAPPLICATION__D: + dev_dbg(dev, "\tBlit engine and display controller.\n"); + break; + case IPAPPLICATION__V: + dev_dbg(dev, "\tDisplay controller only " + "(with direct capture).\n"); + break; + case IPAPPLICATION__G: + dev_dbg(dev, "\tBlit engine, display controller " + "(with direct capture),\n" + "\tcapture controller (buffered capture) " + "and drawing engine.\n"); + break; + case IPAPPLICATION__C: + dev_dbg(dev, "\tDisplay controller only.\n"); + break; + default: + dev_dbg(dev, "\tUnknown.\n"); + break; + } + + dev_dbg(dev, "%d) IP configuration:\n", ++id); + switch (reg & IPCONFIGURATION_MASK) { + case IPCONFIGURATION__M: + dev_dbg(dev, "\tGraphics core only (Module).\n"); + break; + case IPCONFIGURATION__S: + dev_dbg(dev, "\tSubsystem including a graphics core " + "(System).\n"); + break; + default: + dev_dbg(dev, "\tUnknown.\n"); + break; + } + + dev_dbg(dev, "%d) IP family:\n", ++id); + switch (reg & IPFAMILY_MASK) { + case IPFAMILY__IMXDPU2010: + dev_dbg(dev, "\tBuilding block generation 2010.\n"); + break; + case IPFAMILY__IMXDPU2012: + dev_dbg(dev, "\tBuilding block generation 2012.\n"); + break; + case IPFAMILY__IMXDPU2013: + dev_dbg(dev, "\tBuilding block generation 2013.\n"); + break; + default: + dev_dbg(dev, "\tUnknown.\n"); + break; + } +} + +/* FIXME: initialize pixel link in a proper manner */ +static void dpu_pixel_link_init(int id) +{ + sc_err_t sciErr; + sc_ipc_t ipcHndl = 0; + u32 mu_id; + + sciErr = sc_ipc_getMuID(&mu_id); + if (sciErr != SC_ERR_NONE) { + pr_err("Cannot obtain MU ID\n"); + return; + } + + sciErr = sc_ipc_open(&ipcHndl, mu_id); + if (sciErr != SC_ERR_NONE) { + pr_err("sc_ipc_open failed! (sciError = %d)\n", sciErr); + return; + } + + if (id == 0) { + /* SC_C_KACHUNK_CNT is for blit */ + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_0, SC_C_KACHUNK_CNT, 32); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_0:SC_C_KACHUNK_CNT sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_0, SC_C_PXL_LINK_MST1_ADDR, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_0:SC_C_PXL_LINK_MST1_ADDR sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_0, SC_C_PXL_LINK_MST1_ENB, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_0:SC_C_PXL_LINK_MST1_ENB sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_0, SC_C_PXL_LINK_MST1_VLD, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_0:SC_C_PXL_LINK_MST1_VLD sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_0, SC_C_PXL_LINK_MST2_ADDR, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_0:SC_C_PXL_LINK_MST2_ADDR sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_0, SC_C_PXL_LINK_MST2_ENB, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_0:SC_C_PXL_LINK_MST2_ENB sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_0, SC_C_PXL_LINK_MST2_VLD, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_0:SC_C_PXL_LINK_MST2_VLD sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_0, SC_C_SYNC_CTRL0, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_0:SC_C_SYNC_CTRL0 sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_0, SC_C_SYNC_CTRL1, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_0:SC_C_SYNC_CTRL1 sc_misc_set_control failed! (sciError = %d)\n", sciErr); + } else if (id == 1) { + /* SC_C_KACHUNK_CNT is for blit */ + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_1, SC_C_KACHUNK_CNT, 32); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_1:SC_C_KACHUNK_CNT sc_misc_set_control failed! (sciError = %d)\n", sciErr); + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_1, SC_C_PXL_LINK_MST1_ADDR, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_1:SC_C_PXL_LINK_MST1_ADDR sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_1, SC_C_PXL_LINK_MST1_ENB, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_1:SC_C_PXL_LINK_MST1_ENB sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_1, SC_C_PXL_LINK_MST1_VLD, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_1:SC_C_PXL_LINK_MST1_VLD sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_1, SC_C_PXL_LINK_MST2_ADDR, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_1:SC_C_PXL_LINK_MST2_ADDR sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_1, SC_C_PXL_LINK_MST2_ENB, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_1:SC_C_PXL_LINK_MST2_ENB sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_1, SC_C_PXL_LINK_MST2_VLD, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_1:SC_C_PXL_LINK_MST2_VLD sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_1, SC_C_SYNC_CTRL0, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_1:SC_C_SYNC_CTRL0 sc_misc_set_control failed! (sciError = %d)\n", sciErr); + + sciErr = sc_misc_set_control(ipcHndl, SC_R_DC_1, SC_C_SYNC_CTRL1, 0); + if (sciErr != SC_ERR_NONE) + pr_err("SC_R_DC_1:SC_C_SYNC_CTRL1 sc_misc_set_control failed! (sciError = %d)\n", sciErr); + } + + sc_ipc_close(mu_id); +} + +static int dpu_probe(struct platform_device *pdev) +{ + const struct of_device_id *of_id = + of_match_device(dpu_dt_ids, &pdev->dev); + struct device_node *np = pdev->dev.of_node; + struct dpu_soc *dpu; + struct resource *res; + unsigned long dpu_base; + const struct dpu_devtype *devtype; + int ret; + + devtype = 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->devtype = devtype; + dpu->id = of_alias_get_id(np, "dpu"); + + /* inner irqs */ + dpu->irq_cm = platform_get_irq(pdev, 0); + dpu->irq_stream0a = platform_get_irq(pdev, 1); + dpu->irq_stream1a = platform_get_irq(pdev, 3); + dpu->irq_reserved0 = platform_get_irq(pdev, 5); + dpu->irq_reserved1 = platform_get_irq(pdev, 6); + dpu->irq_blit = platform_get_irq(pdev, 7); + + dev_dbg(dpu->dev, "irq_cm: %d\n", dpu->irq_cm); + dev_dbg(dpu->dev, "irq_stream0a: %d, irq_stream1a: %d\n", + dpu->irq_stream0a, dpu->irq_stream1a); + dev_dbg(dpu->dev, "irq_reserved0: %d, irq_reserved1: %d\n", + dpu->irq_reserved0, dpu->irq_reserved1); + dev_dbg(dpu->dev, "irq_blit: %d\n", dpu->irq_blit); + + if (dpu->irq_cm < 0 || + dpu->irq_stream0a < 0 || dpu->irq_stream1a < 0 || + dpu->irq_reserved0 < 0 || dpu->irq_reserved1 < 0 || + dpu->irq_blit < 0) + return -ENODEV; + + dpu->intsteer_regmap = syscon_regmap_lookup_by_phandle(np, "intsteer"); + if (IS_ERR(dpu->intsteer_regmap)) { + dev_err(dpu->dev, "failed to get intsteer regmap\n"); + return PTR_ERR(dpu->intsteer_regmap); + } + + /* DPR irqs */ + if (dpu->devtype->has_prefetch) { + dpu->irq_dpr0 = platform_get_irq(pdev, 8); + dpu->irq_dpr1 = platform_get_irq(pdev, 9); + + dev_dbg(dpu->dev, "irq_dpr0: %d\n", dpu->irq_dpr0); + dev_dbg(dpu->dev, "irq_dpr1: %d\n", dpu->irq_dpr1); + + if (dpu->irq_dpr0 < 0 || dpu->irq_dpr1 < 0) + return -ENODEV; + + ret = devm_request_irq(dpu->dev, dpu->irq_dpr0, + dpu_dpr0_irq_handler, 0, pdev->name, dpu); + if (ret) { + dev_err(dpu->dev, "request dpr0 interrupt failed\n"); + return ret; + } + + ret = devm_request_irq(dpu->dev, dpu->irq_dpr1, + dpu_dpr1_irq_handler, 0, pdev->name, dpu); + if (ret) { + dev_err(dpu->dev, "request dpr1 interrupt failed\n"); + return ret; + } + } + + spin_lock_init(&dpu->lock); + spin_lock_init(&dpu->intsteer_lock); + + dev_dbg(dpu->dev, "Common: 0x%08lx\n", dpu_base + devtype->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(tcon); + DPU_UNITS_ADDR_DBG(vs); + + dpu->cm_reg = devm_ioremap(dpu->dev, dpu_base + devtype->cm_ofs, SZ_1K); + if (!dpu->cm_reg) + return -ENOMEM; + + platform_set_drvdata(pdev, dpu); + + ret = dpu_inner_irq_init(dpu); + if (ret) + goto failed_inner_irq; + + ret = dpu_submodules_init(dpu, pdev, dpu_base); + if (ret) + goto failed_submodules_init; + + ret = dpu_add_client_devices(dpu); + if (ret) { + dev_err(dpu->dev, "adding client devices failed with %d\n", + ret); + goto failed_add_clients; + } + + dpu_debug_ip_identity(dpu); + + if (devtype->pixel_link_quirks) + dpu_pixel_link_init(dpu->id); + + dev_info(dpu->dev, "driver probed\n"); + + return 0; + +failed_add_clients: +failed_submodules_init: + dpu_inner_irq_exit(dpu); +failed_inner_irq: + return ret; +} + +static int dpu_remove(struct platform_device *pdev) +{ + struct dpu_soc *dpu = platform_get_drvdata(pdev); + + platform_device_unregister_children(pdev); + dpu_inner_irq_exit(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_inner_intsteer_enable_lines(dpu); + + if (dpu->devtype->pixel_link_quirks) + dpu_pixel_link_init(dpu->id); + + _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"); |