diff options
Diffstat (limited to 'drivers/gpu/imx/dcss/dcss-ctxld.c')
-rw-r--r-- | drivers/gpu/imx/dcss/dcss-ctxld.c | 513 |
1 files changed, 513 insertions, 0 deletions
diff --git a/drivers/gpu/imx/dcss/dcss-ctxld.c b/drivers/gpu/imx/dcss/dcss-ctxld.c new file mode 100644 index 000000000000..076c4279c4d6 --- /dev/null +++ b/drivers/gpu/imx/dcss/dcss-ctxld.c @@ -0,0 +1,513 @@ +/* + * Copyright (C) 2017 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/device.h> +#include <linux/bitops.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/sizes.h> +#include <linux/io.h> +#include <linux/dma-mapping.h> +#include <linux/delay.h> +#include <asm/cacheflush.h> + +#include "video/imx-dcss.h" +#include "dcss-prv.h" + +#define DCSS_CTXLD_DEVNAME "dcss_ctxld" + +#define DCSS_CTXLD_CONTROL_STATUS 0x0 +#define CTXLD_ENABLE BIT(0) +#define ARB_SEL BIT(1) +#define RD_ERR_EN BIT(2) +#define DB_COMP_EN BIT(3) +#define SB_HP_COMP_EN BIT(4) +#define SB_LP_COMP_EN BIT(5) +#define DB_PEND_SB_REC_EN BIT(6) +#define SB_PEND_DISP_ACTIVE_EN BIT(7) +#define AHB_ERR_EN BIT(8) +#define RD_ERR BIT(16) +#define DB_COMP BIT(17) +#define SB_HP_COMP BIT(18) +#define SB_LP_COMP BIT(19) +#define DB_PEND_SB_REC BIT(20) +#define SB_PEND_DISP_ACTIVE BIT(21) +#define AHB_ERR BIT(22) +#define DCSS_CTXLD_DB_BASE_ADDR 0x10 +#define DCSS_CTXLD_DB_COUNT 0x14 +#define DCSS_CTXLD_SB_BASE_ADDR 0x18 +#define DCSS_CTXLD_SB_COUNT 0x1C +#define SB_HP_COUNT_POS 0 +#define SB_HP_COUNT_MASK 0xffff +#define SB_LP_COUNT_POS 16 +#define SB_LP_COUNT_MASK 0xffff0000 +#define DCSS_AHB_ERR_ADDR 0x20 + +#define CTXLD_IRQ_NAME "ctx_ld" /* irq steer irq name */ +#define CTXLD_IRQ_COMPLETION (DB_COMP | SB_HP_COMP | SB_LP_COMP) +#define CTXLD_IRQ_ERROR (RD_ERR | DB_PEND_SB_REC | AHB_ERR) + +/* The following sizes are in entries, 8 bytes each */ +#define CTXLD_DB_CTX_ENTRIES 1024 /* max 65536 */ +#define CTXLD_SB_LP_CTX_ENTRIES 10240 /* max 65536 */ +#define CTXLD_SB_HP_CTX_ENTRIES 20000 /* max 65536 */ +#define CTXLD_SB_CTX_ENTRIES (CTXLD_SB_LP_CTX_ENTRIES + \ + CTXLD_SB_HP_CTX_ENTRIES) + +#define TRACE_ARM (1LL << 48) +#define TRACE_IRQ (2LL << 48) +#define TRACE_KICK (3LL << 48) + +static struct dcss_debug_reg ctxld_debug_reg[] = { + DCSS_DBG_REG(DCSS_CTXLD_CONTROL_STATUS), + DCSS_DBG_REG(DCSS_CTXLD_DB_BASE_ADDR), + DCSS_DBG_REG(DCSS_CTXLD_DB_COUNT), + DCSS_DBG_REG(DCSS_CTXLD_SB_BASE_ADDR), + DCSS_DBG_REG(DCSS_CTXLD_SB_COUNT), + DCSS_DBG_REG(DCSS_AHB_ERR_ADDR), +}; + +/* Sizes, in entries, of the DB, SB_HP and SB_LP context regions. */ +static u16 dcss_ctxld_ctx_size[3] = { + CTXLD_DB_CTX_ENTRIES, + CTXLD_SB_HP_CTX_ENTRIES, + CTXLD_SB_LP_CTX_ENTRIES +}; + +/* this represents an entry in the context loader map */ +struct dcss_ctxld_item { + u32 val; + u32 ofs; +}; + +#define CTX_ITEM_SIZE sizeof(struct dcss_ctxld_item) + +struct dcss_ctxld_priv { + struct dcss_soc *dcss; + void __iomem *ctxld_reg; + int irq; + bool irq_en; + + struct dcss_ctxld_item *db[2]; + struct dcss_ctxld_item *sb_hp[2]; + struct dcss_ctxld_item *sb_lp[2]; + + dma_addr_t db_paddr[2]; + dma_addr_t sb_paddr[2]; + + u16 ctx_size[2][3]; /* holds the sizes of DB, SB_HP and SB_LP ctx */ + u8 current_ctx; + + bool in_use; + bool armed; + + spinlock_t lock; /* protects concurent access to private data */ +}; + +#ifdef CONFIG_DEBUG_FS +void dcss_ctxld_dump_regs(struct seq_file *s, void *data) +{ + struct dcss_soc *dcss = data; + int j; + + seq_puts(s, ">> Dumping CTXLD:\n"); + for (j = 0; j < ARRAY_SIZE(ctxld_debug_reg); j++) { + seq_printf(s, "%-35s(0x%04x) -> 0x%08x\n", + ctxld_debug_reg[j].name, + ctxld_debug_reg[j].ofs, + dcss_readl(dcss->ctxld_priv->ctxld_reg + + ctxld_debug_reg[j].ofs)); + } +} +#endif + +static int __dcss_ctxld_enable(struct dcss_ctxld_priv *ctxld); + +static irqreturn_t dcss_ctxld_irq_handler(int irq, void *data) +{ + struct dcss_ctxld_priv *priv = data; + u32 irq_status; + + irq_status = dcss_readl(priv->ctxld_reg + DCSS_CTXLD_CONTROL_STATUS); + + if (irq_status & CTXLD_IRQ_COMPLETION && + !(irq_status & CTXLD_ENABLE) && priv->in_use) { + priv->in_use = false; + + dcss_trace_module(TRACE_CTXLD, + TRACE_IRQ | (priv->current_ctx ^ 1)); + + if (priv->dcss->dcss_disable_callback) { + struct dcss_dtg_priv *dtg = priv->dcss->dtg_priv; + + priv->dcss->dcss_disable_callback(dtg); + } + } else if (irq_status & CTXLD_IRQ_ERROR) { + /* + * Except for throwing an error message and clearing the status + * register, there's not much we can do here. + */ + dev_err(priv->dcss->dev, "ctxld: error encountered: %08x\n", + irq_status); + dev_err(priv->dcss->dev, "ctxld: db=%d, sb_hp=%d, sb_lp=%d\n", + priv->ctx_size[priv->current_ctx ^ 1][CTX_DB], + priv->ctx_size[priv->current_ctx ^ 1][CTX_SB_HP], + priv->ctx_size[priv->current_ctx ^ 1][CTX_SB_LP]); + } + + dcss_clr(irq_status & (CTXLD_IRQ_ERROR | CTXLD_IRQ_COMPLETION), + priv->ctxld_reg + DCSS_CTXLD_CONTROL_STATUS); + + return IRQ_HANDLED; +} + +static int dcss_ctxld_irq_config(struct dcss_ctxld_priv *ctxld) +{ + struct dcss_soc *dcss = ctxld->dcss; + struct platform_device *pdev = to_platform_device(dcss->dev); + int ret; + + ctxld->irq = platform_get_irq_byname(pdev, CTXLD_IRQ_NAME); + if (ctxld->irq < 0) { + dev_err(dcss->dev, "ctxld: can't get irq number\n"); + return ctxld->irq; + } + + ret = devm_request_irq(dcss->dev, ctxld->irq, + dcss_ctxld_irq_handler, + IRQF_ONESHOT | IRQF_TRIGGER_RISING, + DCSS_CTXLD_DEVNAME, ctxld); + if (ret) { + dev_err(dcss->dev, "ctxld: irq request failed.\n"); + return ret; + } + + ctxld->irq_en = true; + + return 0; +} + +void dcss_ctxld_hw_cfg(struct dcss_soc *dcss) +{ + struct dcss_ctxld_priv *ctxld = dcss->ctxld_priv; + + dcss_writel(RD_ERR_EN | SB_HP_COMP_EN | + DB_PEND_SB_REC_EN | AHB_ERR_EN | RD_ERR | AHB_ERR, + ctxld->ctxld_reg + DCSS_CTXLD_CONTROL_STATUS); +} + +/** + * dcss_ctxld_alloc_ctx - Allocate context memory. + * + * @ctxld: Pointer to ctxld. + * + * Returns: + * Zeron on success, negative errno on failure. + */ +static int dcss_ctxld_alloc_ctx(struct dcss_ctxld_priv *ctxld) +{ + struct dcss_soc *dcss = ctxld->dcss; + struct dcss_ctxld_item *ctx; + int i; + dma_addr_t dma_handle; + + for (i = 0; i < 2; i++) { + ctx = dmam_alloc_coherent(dcss->dev, + CTXLD_DB_CTX_ENTRIES * sizeof(*ctx), + &dma_handle, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctxld->db[i] = ctx; + ctxld->db_paddr[i] = dma_handle; + + ctx = dmam_alloc_coherent(dcss->dev, + CTXLD_SB_CTX_ENTRIES * sizeof(*ctx), + &dma_handle, GFP_KERNEL); + if (!ctx) + return -ENOMEM; + + ctxld->sb_hp[i] = ctx; + ctxld->sb_lp[i] = ctx + CTXLD_SB_HP_CTX_ENTRIES; + + ctxld->sb_paddr[i] = dma_handle; + } + + return 0; +} + +int dcss_ctxld_init(struct dcss_soc *dcss, unsigned long ctxld_base) +{ + struct dcss_ctxld_priv *priv; + int ret; + + priv = devm_kzalloc(dcss->dev, sizeof(struct dcss_ctxld_priv), + GFP_KERNEL); + if (!priv) + return -ENOMEM; + + dcss->ctxld_priv = priv; + priv->dcss = dcss; + + ret = dcss_ctxld_alloc_ctx(priv); + if (ret) { + dev_err(dcss->dev, "ctxld: cannot allocate context memory.\n"); + return ret; + } + + priv->ctxld_reg = devm_ioremap(dcss->dev, ctxld_base, SZ_4K); + if (!priv->ctxld_reg) { + dev_err(dcss->dev, "ctxld: unable to remap ctxld base\n"); + return -ENOMEM; + } + + ret = dcss_ctxld_irq_config(priv); + if (!ret) + return ret; + + dcss_ctxld_hw_cfg(dcss); + + return 0; +} + +void dcss_ctxld_exit(struct dcss_soc *dcss) +{ +} + +static int __dcss_ctxld_enable(struct dcss_ctxld_priv *ctxld) +{ + int curr_ctx = ctxld->current_ctx; + u32 db_base, sb_base, sb_count; + u32 sb_hp_cnt, sb_lp_cnt, db_cnt; + + dcss_dpr_write_sysctrl(ctxld->dcss); + dcss_scaler_write_sclctrl(ctxld->dcss); + + if (dcss_dtrc_is_running(ctxld->dcss, 1) || + dcss_dtrc_is_running(ctxld->dcss, 2)) { + dcss_dtrc_switch_banks(ctxld->dcss); + ctxld->armed = true; + } + + sb_hp_cnt = ctxld->ctx_size[curr_ctx][CTX_SB_HP]; + sb_lp_cnt = ctxld->ctx_size[curr_ctx][CTX_SB_LP]; + db_cnt = ctxld->ctx_size[curr_ctx][CTX_DB]; + + /* make sure SB_LP context area comes after SB_HP */ + if (sb_lp_cnt && + ctxld->sb_lp[curr_ctx] != ctxld->sb_hp[curr_ctx] + sb_hp_cnt) { + struct dcss_ctxld_item *sb_lp_adjusted; + + sb_lp_adjusted = ctxld->sb_hp[curr_ctx] + sb_hp_cnt; + + memcpy(sb_lp_adjusted, ctxld->sb_lp[curr_ctx], + sb_lp_cnt * CTX_ITEM_SIZE); + } + + db_base = db_cnt ? ctxld->db_paddr[curr_ctx] : 0; + + dcss_writel(db_base, ctxld->ctxld_reg + DCSS_CTXLD_DB_BASE_ADDR); + dcss_writel(db_cnt, ctxld->ctxld_reg + DCSS_CTXLD_DB_COUNT); + + if (sb_hp_cnt) + sb_count = ((sb_hp_cnt << SB_HP_COUNT_POS) & SB_HP_COUNT_MASK) | + ((sb_lp_cnt << SB_LP_COUNT_POS) & SB_LP_COUNT_MASK); + else + sb_count = (sb_lp_cnt << SB_HP_COUNT_POS) & SB_HP_COUNT_MASK; + + sb_base = sb_count ? ctxld->sb_paddr[curr_ctx] : 0; + + dcss_writel(sb_base, ctxld->ctxld_reg + DCSS_CTXLD_SB_BASE_ADDR); + dcss_writel(sb_count, ctxld->ctxld_reg + DCSS_CTXLD_SB_COUNT); + + dcss_trace_module(TRACE_CTXLD, + TRACE_ARM | db_cnt | (sb_count << 16) | + ((u64)ctxld->current_ctx << 32)); + + /* enable the context loader */ + dcss_set(CTXLD_ENABLE, ctxld->ctxld_reg + DCSS_CTXLD_CONTROL_STATUS); + + ctxld->in_use = true; + + /* + * Toggle the current context to the alternate one so that any updates + * in the modules' settings take place there. + */ + ctxld->current_ctx ^= 1; + + ctxld->ctx_size[ctxld->current_ctx][CTX_DB] = 0; + ctxld->ctx_size[ctxld->current_ctx][CTX_SB_HP] = 0; + ctxld->ctx_size[ctxld->current_ctx][CTX_SB_LP] = 0; + + return 0; +} + +/** + * dcss_ctxld_enable - Enable context loader module. + * + * @dcss: pointer to dcss_soc. + * + * Returns: + * Zero on success, negative errno on failure. + */ +int dcss_ctxld_enable(struct dcss_soc *dcss) +{ + struct dcss_ctxld_priv *ctxld = dcss->ctxld_priv; + unsigned long flags; + + spin_lock_irqsave(&ctxld->lock, flags); + ctxld->armed = true; + spin_unlock_irqrestore(&ctxld->lock, flags); + + return 0; +} +EXPORT_SYMBOL(dcss_ctxld_enable); + +void dcss_ctxld_kick(struct dcss_soc *dcss) +{ + struct dcss_ctxld_priv *ctxld = dcss->ctxld_priv; + unsigned long flags; + + dcss_trace_module(TRACE_CTXLD, TRACE_KICK); + + spin_lock_irqsave(&ctxld->lock, flags); + if (ctxld->armed) { + ctxld->armed = false; + __dcss_ctxld_enable(dcss->ctxld_priv); + } + spin_unlock_irqrestore(&ctxld->lock, flags); +} +EXPORT_SYMBOL(dcss_ctxld_kick); + +void dcss_ctxld_write_irqsafe(struct dcss_soc *dcss, u32 ctx_id, u32 val, + u32 reg_ofs) +{ + struct dcss_ctxld_priv *ctxld = dcss->ctxld_priv; + int curr_ctx = ctxld->current_ctx; + struct dcss_ctxld_item *ctx[] = { + [CTX_DB] = ctxld->db[curr_ctx], + [CTX_SB_HP] = ctxld->sb_hp[curr_ctx], + [CTX_SB_LP] = ctxld->sb_lp[curr_ctx] + }; + int item_idx = ctxld->ctx_size[curr_ctx][ctx_id]; + + /* if we hit this, we've got to increase the maximum context size */ + BUG_ON(dcss_ctxld_ctx_size[ctx_id] - 1 < item_idx); + + ctx[ctx_id][item_idx].val = val; + ctx[ctx_id][item_idx].ofs = reg_ofs; + ctxld->ctx_size[curr_ctx][ctx_id] += 1; +} + +void dcss_ctxld_write(struct dcss_soc *dcss, u32 ctx_id, u32 val, u32 reg_ofs) +{ + struct dcss_ctxld_priv *ctxld = dcss->ctxld_priv; + unsigned long flags; + + spin_lock_irqsave(&ctxld->lock, flags); + dcss_ctxld_write_irqsafe(dcss, ctx_id, val, reg_ofs); + spin_unlock_irqrestore(&ctxld->lock, flags); +} + +bool dcss_ctxld_is_flushed(struct dcss_soc *dcss) +{ + struct dcss_ctxld_priv *ctxld = dcss->ctxld_priv; + + return ctxld->ctx_size[ctxld->current_ctx][CTX_DB] == 0 && + ctxld->ctx_size[ctxld->current_ctx][CTX_SB_HP] == 0 && + ctxld->ctx_size[ctxld->current_ctx][CTX_SB_LP] == 0; +} +EXPORT_SYMBOL(dcss_ctxld_is_flushed); + +int dcss_ctxld_resume(struct dcss_soc *dcss) +{ + struct dcss_ctxld_priv *ctxld = dcss->ctxld_priv; + + dcss_ctxld_hw_cfg(dcss); + + if (!ctxld->irq_en) { + enable_irq(dcss->ctxld_priv->irq); + ctxld->irq_en = true; + } + + return 0; +} + +int dcss_ctxld_suspend(struct dcss_soc *dcss) +{ + int ret = 0; + struct dcss_ctxld_priv *ctxld = dcss->ctxld_priv; + int wait_time_ms = 0; + unsigned long flags; + + while (ctxld->in_use && wait_time_ms < 500) { + msleep(20); + wait_time_ms += 20; + } + + if (wait_time_ms > 500) + return -ETIMEDOUT; + + spin_lock_irqsave(&ctxld->lock, flags); + + if (ctxld->irq_en) { + disable_irq_nosync(dcss->ctxld_priv->irq); + ctxld->irq_en = false; + } + + /* reset context region and sizes */ + ctxld->current_ctx = 0; + ctxld->ctx_size[0][CTX_DB] = 0; + ctxld->ctx_size[0][CTX_SB_HP] = 0; + ctxld->ctx_size[0][CTX_SB_LP] = 0; + + spin_unlock_irqrestore(&ctxld->lock, flags); + return ret; +} + +#ifdef CONFIG_DEBUG_FS +void dcss_ctxld_dump(struct seq_file *s, void *data) +{ + struct dcss_soc *dcss = data; + struct dcss_ctxld_priv *ctxld = dcss->ctxld_priv; + int curr_ctx = ctxld->current_ctx; + int i; + struct dcss_ctxld_item *ctx_db, *ctx_sb_hp, *ctx_sb_lp; + u32 ctx_db_size, ctx_sb_hp_size, ctx_sb_lp_size; + + ctx_db_size = ctxld->ctx_size[curr_ctx ^ 1][CTX_DB]; + ctx_sb_hp_size = ctxld->ctx_size[curr_ctx ^ 1][CTX_SB_HP]; + ctx_sb_lp_size = ctxld->ctx_size[curr_ctx ^ 1][CTX_SB_LP]; + + ctx_db = ctxld->db[curr_ctx ^ 1]; + ctx_sb_hp = ctxld->sb_hp[curr_ctx ^ 1]; + ctx_sb_lp = ctxld->sb_hp[curr_ctx ^ 1] + ctx_sb_hp_size; + + seq_puts(s, ">> Dumping loaded context:\n"); + seq_puts(s, "\t>>Dumping CTX_DB:\n"); + for (i = 0; i < ctx_db_size; i++) + seq_printf(s, "\t0x%16llx -> 0x%08x : 0x%08x\n", + (u64)&ctx_db[i], ctx_db[i].ofs, ctx_db[i].val); + seq_puts(s, "\t>>Dumping CTX_SB_HP:\n"); + for (i = 0; i < ctx_sb_hp_size; i++) + seq_printf(s, "\t0x%16llx -> 0x%08x : 0x%08x\n", + (u64)&ctx_sb_hp[i], ctx_sb_hp[i].ofs, + ctx_sb_hp[i].val); + seq_puts(s, "\t>>Dumping CTX_SB_LP:\n"); + for (i = 0; i < ctx_sb_lp_size; i++) + seq_printf(s, "\t0x%16llx -> 0x%08x : 0x%08x\n", + (u64)&ctx_sb_lp[i], ctx_sb_lp[i].ofs, + ctx_sb_lp[i].val); +} +#endif |