| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright 2019 NXP. | |
| */ | |
| #include <linux/delay.h> | |
| #include <linux/dma-mapping.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/platform_device.h> | |
| #include <linux/slab.h> | |
| #include "dcss-dev.h" | |
| #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_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 context loader 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) | |
| /* 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 { | |
| struct device *dev; | |
| 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 */ | |
| }; | |
| static irqreturn_t dcss_ctxld_irq_handler(int irq, void *data) | |
| { | |
| struct dcss_ctxld *ctxld = data; | |
| struct dcss_dev *dcss = dcss_drv_dev_to_dcss(ctxld->dev); | |
| u32 irq_status; | |
| irq_status = dcss_readl(ctxld->ctxld_reg + DCSS_CTXLD_CONTROL_STATUS); | |
| if (irq_status & CTXLD_IRQ_COMPLETION && | |
| !(irq_status & CTXLD_ENABLE) && ctxld->in_use) { | |
| ctxld->in_use = false; | |
| if (dcss && dcss->disable_callback) | |
| dcss->disable_callback(dcss); | |
| } 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(ctxld->dev, "ctxld: error encountered: %08x\n", | |
| irq_status); | |
| dev_err(ctxld->dev, "ctxld: db=%d, sb_hp=%d, sb_lp=%d\n", | |
| ctxld->ctx_size[ctxld->current_ctx ^ 1][CTX_DB], | |
| ctxld->ctx_size[ctxld->current_ctx ^ 1][CTX_SB_HP], | |
| ctxld->ctx_size[ctxld->current_ctx ^ 1][CTX_SB_LP]); | |
| } | |
| dcss_clr(irq_status & (CTXLD_IRQ_ERROR | CTXLD_IRQ_COMPLETION), | |
| ctxld->ctxld_reg + DCSS_CTXLD_CONTROL_STATUS); | |
| return IRQ_HANDLED; | |
| } | |
| static int dcss_ctxld_irq_config(struct dcss_ctxld *ctxld, | |
| struct platform_device *pdev) | |
| { | |
| int ret; | |
| ctxld->irq = platform_get_irq_byname(pdev, "ctxld"); | |
| if (ctxld->irq < 0) | |
| return ctxld->irq; | |
| ret = request_irq(ctxld->irq, dcss_ctxld_irq_handler, | |
| 0, "dcss_ctxld", ctxld); | |
| if (ret) { | |
| dev_err(ctxld->dev, "ctxld: irq request failed.\n"); | |
| return ret; | |
| } | |
| ctxld->irq_en = true; | |
| return 0; | |
| } | |
| static void dcss_ctxld_hw_cfg(struct dcss_ctxld *ctxld) | |
| { | |
| 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); | |
| } | |
| static void dcss_ctxld_free_ctx(struct dcss_ctxld *ctxld) | |
| { | |
| struct dcss_ctxld_item *ctx; | |
| int i; | |
| for (i = 0; i < 2; i++) { | |
| if (ctxld->db[i]) { | |
| dma_free_coherent(ctxld->dev, | |
| CTXLD_DB_CTX_ENTRIES * sizeof(*ctx), | |
| ctxld->db[i], ctxld->db_paddr[i]); | |
| ctxld->db[i] = NULL; | |
| ctxld->db_paddr[i] = 0; | |
| } | |
| if (ctxld->sb_hp[i]) { | |
| dma_free_coherent(ctxld->dev, | |
| CTXLD_SB_CTX_ENTRIES * sizeof(*ctx), | |
| ctxld->sb_hp[i], ctxld->sb_paddr[i]); | |
| ctxld->sb_hp[i] = NULL; | |
| ctxld->sb_paddr[i] = 0; | |
| } | |
| } | |
| } | |
| static int dcss_ctxld_alloc_ctx(struct dcss_ctxld *ctxld) | |
| { | |
| struct dcss_ctxld_item *ctx; | |
| int i; | |
| for (i = 0; i < 2; i++) { | |
| ctx = dma_alloc_coherent(ctxld->dev, | |
| CTXLD_DB_CTX_ENTRIES * sizeof(*ctx), | |
| &ctxld->db_paddr[i], GFP_KERNEL); | |
| if (!ctx) | |
| return -ENOMEM; | |
| ctxld->db[i] = ctx; | |
| ctx = dma_alloc_coherent(ctxld->dev, | |
| CTXLD_SB_CTX_ENTRIES * sizeof(*ctx), | |
| &ctxld->sb_paddr[i], GFP_KERNEL); | |
| if (!ctx) | |
| return -ENOMEM; | |
| ctxld->sb_hp[i] = ctx; | |
| ctxld->sb_lp[i] = ctx + CTXLD_SB_HP_CTX_ENTRIES; | |
| } | |
| return 0; | |
| } | |
| int dcss_ctxld_init(struct dcss_dev *dcss, unsigned long ctxld_base) | |
| { | |
| struct dcss_ctxld *ctxld; | |
| int ret; | |
| ctxld = devm_kzalloc(dcss->dev, sizeof(*ctxld), GFP_KERNEL); | |
| if (!ctxld) | |
| return -ENOMEM; | |
| dcss->ctxld = ctxld; | |
| ctxld->dev = dcss->dev; | |
| spin_lock_init(&ctxld->lock); | |
| ret = dcss_ctxld_alloc_ctx(ctxld); | |
| if (ret) { | |
| dev_err(dcss->dev, "ctxld: cannot allocate context memory.\n"); | |
| goto err; | |
| } | |
| ctxld->ctxld_reg = devm_ioremap(dcss->dev, ctxld_base, SZ_4K); | |
| if (!ctxld->ctxld_reg) { | |
| dev_err(dcss->dev, "ctxld: unable to remap ctxld base\n"); | |
| ret = -ENOMEM; | |
| goto err; | |
| } | |
| ret = dcss_ctxld_irq_config(ctxld, to_platform_device(dcss->dev)); | |
| if (ret) | |
| goto err; | |
| dcss_ctxld_hw_cfg(ctxld); | |
| return 0; | |
| err: | |
| dcss_ctxld_free_ctx(ctxld); | |
| return ret; | |
| } | |
| void dcss_ctxld_exit(struct dcss_ctxld *ctxld) | |
| { | |
| free_irq(ctxld->irq, ctxld); | |
| dcss_ctxld_free_ctx(ctxld); | |
| } | |
| static int dcss_ctxld_enable_locked(struct dcss_ctxld *ctxld) | |
| { | |
| int curr_ctx = ctxld->current_ctx; | |
| u32 db_base, sb_base, sb_count; | |
| u32 sb_hp_cnt, sb_lp_cnt, db_cnt; | |
| struct dcss_dev *dcss = dcss_drv_dev_to_dcss(ctxld->dev); | |
| if (!dcss) | |
| return 0; | |
| dcss_dpr_write_sysctrl(dcss->dpr); | |
| dcss_scaler_write_sclctrl(dcss->scaler); | |
| 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); | |
| /* 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; | |
| } | |
| int dcss_ctxld_enable(struct dcss_ctxld *ctxld) | |
| { | |
| spin_lock_irq(&ctxld->lock); | |
| ctxld->armed = true; | |
| spin_unlock_irq(&ctxld->lock); | |
| return 0; | |
| } | |
| void dcss_ctxld_kick(struct dcss_ctxld *ctxld) | |
| { | |
| unsigned long flags; | |
| spin_lock_irqsave(&ctxld->lock, flags); | |
| if (ctxld->armed && !ctxld->in_use) { | |
| ctxld->armed = false; | |
| dcss_ctxld_enable_locked(ctxld); | |
| } | |
| spin_unlock_irqrestore(&ctxld->lock, flags); | |
| } | |
| void dcss_ctxld_write_irqsafe(struct dcss_ctxld *ctxld, u32 ctx_id, u32 val, | |
| u32 reg_ofs) | |
| { | |
| 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 (item_idx + 1 > dcss_ctxld_ctx_size[ctx_id]) { | |
| WARN_ON(1); | |
| return; | |
| } | |
| 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_ctxld *ctxld, u32 ctx_id, | |
| u32 val, u32 reg_ofs) | |
| { | |
| spin_lock_irq(&ctxld->lock); | |
| dcss_ctxld_write_irqsafe(ctxld, ctx_id, val, reg_ofs); | |
| spin_unlock_irq(&ctxld->lock); | |
| } | |
| bool dcss_ctxld_is_flushed(struct dcss_ctxld *ctxld) | |
| { | |
| 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; | |
| } | |
| int dcss_ctxld_resume(struct dcss_ctxld *ctxld) | |
| { | |
| dcss_ctxld_hw_cfg(ctxld); | |
| if (!ctxld->irq_en) { | |
| enable_irq(ctxld->irq); | |
| ctxld->irq_en = true; | |
| } | |
| return 0; | |
| } | |
| int dcss_ctxld_suspend(struct dcss_ctxld *ctxld) | |
| { | |
| int ret = 0; | |
| unsigned long timeout = jiffies + msecs_to_jiffies(500); | |
| if (!dcss_ctxld_is_flushed(ctxld)) { | |
| dcss_ctxld_kick(ctxld); | |
| while (!time_after(jiffies, timeout) && ctxld->in_use) | |
| msleep(20); | |
| if (time_after(jiffies, timeout)) | |
| return -ETIMEDOUT; | |
| } | |
| spin_lock_irq(&ctxld->lock); | |
| if (ctxld->irq_en) { | |
| disable_irq_nosync(ctxld->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_irq(&ctxld->lock); | |
| return ret; | |
| } | |
| void dcss_ctxld_assert_locked(struct dcss_ctxld *ctxld) | |
| { | |
| lockdep_assert_held(&ctxld->lock); | |
| } |