diff options
Diffstat (limited to 'drivers/gpu/drm/msm/disp')
87 files changed, 38899 insertions, 0 deletions
diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_irq.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_irq.c new file mode 100644 index 000000000..84ea09d96 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_irq.c @@ -0,0 +1,402 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ + +#include <linux/debugfs.h> +#include <linux/irqdomain.h> +#include <linux/irq.h> +#include <linux/kthread.h> + +#include "dpu_core_irq.h" +#include "dpu_trace.h" + +/** + * dpu_core_irq_callback_handler - dispatch core interrupts + * @arg: private data of callback handler + * @irq_idx: interrupt index + */ +static void dpu_core_irq_callback_handler(void *arg, int irq_idx) +{ + struct dpu_kms *dpu_kms = arg; + struct dpu_irq *irq_obj = &dpu_kms->irq_obj; + struct dpu_irq_callback *cb; + unsigned long irq_flags; + + pr_debug("irq_idx=%d\n", irq_idx); + + if (list_empty(&irq_obj->irq_cb_tbl[irq_idx])) { + DRM_ERROR("no registered cb, idx:%d enable_count:%d\n", irq_idx, + atomic_read(&dpu_kms->irq_obj.enable_counts[irq_idx])); + } + + atomic_inc(&irq_obj->irq_counts[irq_idx]); + + /* + * Perform registered function callback + */ + spin_lock_irqsave(&dpu_kms->irq_obj.cb_lock, irq_flags); + list_for_each_entry(cb, &irq_obj->irq_cb_tbl[irq_idx], list) + if (cb->func) + cb->func(cb->arg, irq_idx); + spin_unlock_irqrestore(&dpu_kms->irq_obj.cb_lock, irq_flags); + + /* + * Clear pending interrupt status in HW. + * NOTE: dpu_core_irq_callback_handler is protected by top-level + * spinlock, so it is safe to clear any interrupt status here. + */ + dpu_kms->hw_intr->ops.clear_intr_status_nolock( + dpu_kms->hw_intr, + irq_idx); +} + +int dpu_core_irq_idx_lookup(struct dpu_kms *dpu_kms, + enum dpu_intr_type intr_type, u32 instance_idx) +{ + if (!dpu_kms->hw_intr || !dpu_kms->hw_intr->ops.irq_idx_lookup) + return -EINVAL; + + return dpu_kms->hw_intr->ops.irq_idx_lookup(intr_type, + instance_idx); +} + +/** + * _dpu_core_irq_enable - enable core interrupt given by the index + * @dpu_kms: Pointer to dpu kms context + * @irq_idx: interrupt index + */ +static int _dpu_core_irq_enable(struct dpu_kms *dpu_kms, int irq_idx) +{ + unsigned long irq_flags; + int ret = 0, enable_count; + + if (!dpu_kms->hw_intr || + !dpu_kms->irq_obj.enable_counts || + !dpu_kms->irq_obj.irq_counts) { + DPU_ERROR("invalid params\n"); + return -EINVAL; + } + + if (irq_idx < 0 || irq_idx >= dpu_kms->hw_intr->irq_idx_tbl_size) { + DPU_ERROR("invalid IRQ index: [%d]\n", irq_idx); + return -EINVAL; + } + + enable_count = atomic_read(&dpu_kms->irq_obj.enable_counts[irq_idx]); + DRM_DEBUG_KMS("irq_idx=%d enable_count=%d\n", irq_idx, enable_count); + trace_dpu_core_irq_enable_idx(irq_idx, enable_count); + + if (atomic_inc_return(&dpu_kms->irq_obj.enable_counts[irq_idx]) == 1) { + ret = dpu_kms->hw_intr->ops.enable_irq( + dpu_kms->hw_intr, + irq_idx); + if (ret) + DPU_ERROR("Fail to enable IRQ for irq_idx:%d\n", + irq_idx); + + DPU_DEBUG("irq_idx=%d ret=%d\n", irq_idx, ret); + + spin_lock_irqsave(&dpu_kms->irq_obj.cb_lock, irq_flags); + /* empty callback list but interrupt is enabled */ + if (list_empty(&dpu_kms->irq_obj.irq_cb_tbl[irq_idx])) + DPU_ERROR("irq_idx=%d enabled with no callback\n", + irq_idx); + spin_unlock_irqrestore(&dpu_kms->irq_obj.cb_lock, irq_flags); + } + + return ret; +} + +int dpu_core_irq_enable(struct dpu_kms *dpu_kms, int *irq_idxs, u32 irq_count) +{ + int i, ret = 0, counts; + + if (!irq_idxs || !irq_count) { + DPU_ERROR("invalid params\n"); + return -EINVAL; + } + + counts = atomic_read(&dpu_kms->irq_obj.enable_counts[irq_idxs[0]]); + if (counts) + DRM_ERROR("irq_idx=%d enable_count=%d\n", irq_idxs[0], counts); + + for (i = 0; (i < irq_count) && !ret; i++) + ret = _dpu_core_irq_enable(dpu_kms, irq_idxs[i]); + + return ret; +} + +/** + * _dpu_core_irq_disable - disable core interrupt given by the index + * @dpu_kms: Pointer to dpu kms context + * @irq_idx: interrupt index + */ +static int _dpu_core_irq_disable(struct dpu_kms *dpu_kms, int irq_idx) +{ + int ret = 0, enable_count; + + if (!dpu_kms->hw_intr || !dpu_kms->irq_obj.enable_counts) { + DPU_ERROR("invalid params\n"); + return -EINVAL; + } + + if (irq_idx < 0 || irq_idx >= dpu_kms->hw_intr->irq_idx_tbl_size) { + DPU_ERROR("invalid IRQ index: [%d]\n", irq_idx); + return -EINVAL; + } + + enable_count = atomic_read(&dpu_kms->irq_obj.enable_counts[irq_idx]); + DRM_DEBUG_KMS("irq_idx=%d enable_count=%d\n", irq_idx, enable_count); + trace_dpu_core_irq_disable_idx(irq_idx, enable_count); + + if (atomic_dec_return(&dpu_kms->irq_obj.enable_counts[irq_idx]) == 0) { + ret = dpu_kms->hw_intr->ops.disable_irq( + dpu_kms->hw_intr, + irq_idx); + if (ret) + DPU_ERROR("Fail to disable IRQ for irq_idx:%d\n", + irq_idx); + DPU_DEBUG("irq_idx=%d ret=%d\n", irq_idx, ret); + } + + return ret; +} + +int dpu_core_irq_disable(struct dpu_kms *dpu_kms, int *irq_idxs, u32 irq_count) +{ + int i, ret = 0, counts; + + if (!irq_idxs || !irq_count) { + DPU_ERROR("invalid params\n"); + return -EINVAL; + } + + counts = atomic_read(&dpu_kms->irq_obj.enable_counts[irq_idxs[0]]); + if (counts == 2) + DRM_ERROR("irq_idx=%d enable_count=%d\n", irq_idxs[0], counts); + + for (i = 0; (i < irq_count) && !ret; i++) + ret = _dpu_core_irq_disable(dpu_kms, irq_idxs[i]); + + return ret; +} + +u32 dpu_core_irq_read(struct dpu_kms *dpu_kms, int irq_idx, bool clear) +{ + if (!dpu_kms->hw_intr || + !dpu_kms->hw_intr->ops.get_interrupt_status) + return 0; + + if (irq_idx < 0) { + DPU_ERROR("[%pS] invalid irq_idx=%d\n", + __builtin_return_address(0), irq_idx); + return 0; + } + + return dpu_kms->hw_intr->ops.get_interrupt_status(dpu_kms->hw_intr, + irq_idx, clear); +} + +int dpu_core_irq_register_callback(struct dpu_kms *dpu_kms, int irq_idx, + struct dpu_irq_callback *register_irq_cb) +{ + unsigned long irq_flags; + + if (!dpu_kms->irq_obj.irq_cb_tbl) { + DPU_ERROR("invalid params\n"); + return -EINVAL; + } + + if (!register_irq_cb || !register_irq_cb->func) { + DPU_ERROR("invalid irq_cb:%d func:%d\n", + register_irq_cb != NULL, + register_irq_cb ? + register_irq_cb->func != NULL : -1); + return -EINVAL; + } + + if (irq_idx < 0 || irq_idx >= dpu_kms->hw_intr->irq_idx_tbl_size) { + DPU_ERROR("invalid IRQ index: [%d]\n", irq_idx); + return -EINVAL; + } + + DPU_DEBUG("[%pS] irq_idx=%d\n", __builtin_return_address(0), irq_idx); + + spin_lock_irqsave(&dpu_kms->irq_obj.cb_lock, irq_flags); + trace_dpu_core_irq_register_callback(irq_idx, register_irq_cb); + list_del_init(®ister_irq_cb->list); + list_add_tail(®ister_irq_cb->list, + &dpu_kms->irq_obj.irq_cb_tbl[irq_idx]); + spin_unlock_irqrestore(&dpu_kms->irq_obj.cb_lock, irq_flags); + + return 0; +} + +int dpu_core_irq_unregister_callback(struct dpu_kms *dpu_kms, int irq_idx, + struct dpu_irq_callback *register_irq_cb) +{ + unsigned long irq_flags; + + if (!dpu_kms->irq_obj.irq_cb_tbl) { + DPU_ERROR("invalid params\n"); + return -EINVAL; + } + + if (!register_irq_cb || !register_irq_cb->func) { + DPU_ERROR("invalid irq_cb:%d func:%d\n", + register_irq_cb != NULL, + register_irq_cb ? + register_irq_cb->func != NULL : -1); + return -EINVAL; + } + + if (irq_idx < 0 || irq_idx >= dpu_kms->hw_intr->irq_idx_tbl_size) { + DPU_ERROR("invalid IRQ index: [%d]\n", irq_idx); + return -EINVAL; + } + + DPU_DEBUG("[%pS] irq_idx=%d\n", __builtin_return_address(0), irq_idx); + + spin_lock_irqsave(&dpu_kms->irq_obj.cb_lock, irq_flags); + trace_dpu_core_irq_unregister_callback(irq_idx, register_irq_cb); + list_del_init(®ister_irq_cb->list); + /* empty callback list but interrupt is still enabled */ + if (list_empty(&dpu_kms->irq_obj.irq_cb_tbl[irq_idx]) && + atomic_read(&dpu_kms->irq_obj.enable_counts[irq_idx])) + DPU_ERROR("irq_idx=%d enabled with no callback\n", irq_idx); + spin_unlock_irqrestore(&dpu_kms->irq_obj.cb_lock, irq_flags); + + return 0; +} + +static void dpu_clear_all_irqs(struct dpu_kms *dpu_kms) +{ + if (!dpu_kms->hw_intr || !dpu_kms->hw_intr->ops.clear_all_irqs) + return; + + dpu_kms->hw_intr->ops.clear_all_irqs(dpu_kms->hw_intr); +} + +static void dpu_disable_all_irqs(struct dpu_kms *dpu_kms) +{ + if (!dpu_kms->hw_intr || !dpu_kms->hw_intr->ops.disable_all_irqs) + return; + + dpu_kms->hw_intr->ops.disable_all_irqs(dpu_kms->hw_intr); +} + +#ifdef CONFIG_DEBUG_FS +static int dpu_debugfs_core_irq_show(struct seq_file *s, void *v) +{ + struct dpu_irq *irq_obj = s->private; + struct dpu_irq_callback *cb; + unsigned long irq_flags; + int i, irq_count, enable_count, cb_count; + + if (WARN_ON(!irq_obj->enable_counts || !irq_obj->irq_cb_tbl)) + return 0; + + for (i = 0; i < irq_obj->total_irqs; i++) { + spin_lock_irqsave(&irq_obj->cb_lock, irq_flags); + cb_count = 0; + irq_count = atomic_read(&irq_obj->irq_counts[i]); + enable_count = atomic_read(&irq_obj->enable_counts[i]); + list_for_each_entry(cb, &irq_obj->irq_cb_tbl[i], list) + cb_count++; + spin_unlock_irqrestore(&irq_obj->cb_lock, irq_flags); + + if (irq_count || enable_count || cb_count) + seq_printf(s, "idx:%d irq:%d enable:%d cb:%d\n", + i, irq_count, enable_count, cb_count); + } + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(dpu_debugfs_core_irq); + +void dpu_debugfs_core_irq_init(struct dpu_kms *dpu_kms, + struct dentry *parent) +{ + debugfs_create_file("core_irq", 0600, parent, &dpu_kms->irq_obj, + &dpu_debugfs_core_irq_fops); +} +#endif + +void dpu_core_irq_preinstall(struct dpu_kms *dpu_kms) +{ + int i; + + pm_runtime_get_sync(&dpu_kms->pdev->dev); + dpu_clear_all_irqs(dpu_kms); + dpu_disable_all_irqs(dpu_kms); + pm_runtime_put_sync(&dpu_kms->pdev->dev); + + spin_lock_init(&dpu_kms->irq_obj.cb_lock); + + /* Create irq callbacks for all possible irq_idx */ + dpu_kms->irq_obj.total_irqs = dpu_kms->hw_intr->irq_idx_tbl_size; + dpu_kms->irq_obj.irq_cb_tbl = kcalloc(dpu_kms->irq_obj.total_irqs, + sizeof(struct list_head), GFP_KERNEL); + dpu_kms->irq_obj.enable_counts = kcalloc(dpu_kms->irq_obj.total_irqs, + sizeof(atomic_t), GFP_KERNEL); + dpu_kms->irq_obj.irq_counts = kcalloc(dpu_kms->irq_obj.total_irqs, + sizeof(atomic_t), GFP_KERNEL); + for (i = 0; i < dpu_kms->irq_obj.total_irqs; i++) { + INIT_LIST_HEAD(&dpu_kms->irq_obj.irq_cb_tbl[i]); + atomic_set(&dpu_kms->irq_obj.enable_counts[i], 0); + atomic_set(&dpu_kms->irq_obj.irq_counts[i], 0); + } +} + +void dpu_core_irq_uninstall(struct dpu_kms *dpu_kms) +{ + int i; + + pm_runtime_get_sync(&dpu_kms->pdev->dev); + for (i = 0; i < dpu_kms->irq_obj.total_irqs; i++) + if (atomic_read(&dpu_kms->irq_obj.enable_counts[i]) || + !list_empty(&dpu_kms->irq_obj.irq_cb_tbl[i])) + DPU_ERROR("irq_idx=%d still enabled/registered\n", i); + + dpu_clear_all_irqs(dpu_kms); + dpu_disable_all_irqs(dpu_kms); + pm_runtime_put_sync(&dpu_kms->pdev->dev); + + kfree(dpu_kms->irq_obj.irq_cb_tbl); + kfree(dpu_kms->irq_obj.enable_counts); + kfree(dpu_kms->irq_obj.irq_counts); + dpu_kms->irq_obj.irq_cb_tbl = NULL; + dpu_kms->irq_obj.enable_counts = NULL; + dpu_kms->irq_obj.irq_counts = NULL; + dpu_kms->irq_obj.total_irqs = 0; +} + +irqreturn_t dpu_core_irq(struct dpu_kms *dpu_kms) +{ + /* + * Read interrupt status from all sources. Interrupt status are + * stored within hw_intr. + * Function will also clear the interrupt status after reading. + * Individual interrupt status bit will only get stored if it + * is enabled. + */ + dpu_kms->hw_intr->ops.get_interrupt_statuses(dpu_kms->hw_intr); + + /* + * Dispatch to HW driver to handle interrupt lookup that is being + * fired. When matching interrupt is located, HW driver will call to + * dpu_core_irq_callback_handler with the irq_idx from the lookup table. + * dpu_core_irq_callback_handler will perform the registered function + * callback, and do the interrupt status clearing once the registered + * callback is finished. + */ + dpu_kms->hw_intr->ops.dispatch_irqs( + dpu_kms->hw_intr, + dpu_core_irq_callback_handler, + dpu_kms); + + return IRQ_HANDLED; +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_irq.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_irq.h new file mode 100644 index 000000000..e30775e65 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_irq.h @@ -0,0 +1,131 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef __DPU_CORE_IRQ_H__ +#define __DPU_CORE_IRQ_H__ + +#include "dpu_kms.h" +#include "dpu_hw_interrupts.h" + +/** + * dpu_core_irq_preinstall - perform pre-installation of core IRQ handler + * @dpu_kms: DPU handle + * @return: none + */ +void dpu_core_irq_preinstall(struct dpu_kms *dpu_kms); + +/** + * dpu_core_irq_uninstall - uninstall core IRQ handler + * @dpu_kms: DPU handle + * @return: none + */ +void dpu_core_irq_uninstall(struct dpu_kms *dpu_kms); + +/** + * dpu_core_irq - core IRQ handler + * @dpu_kms: DPU handle + * @return: interrupt handling status + */ +irqreturn_t dpu_core_irq(struct dpu_kms *dpu_kms); + +/** + * dpu_core_irq_idx_lookup - IRQ helper function for lookup irq_idx from HW + * interrupt mapping table. + * @dpu_kms: DPU handle + * @intr_type: DPU HW interrupt type for lookup + * @instance_idx: DPU HW block instance defined in dpu_hw_mdss.h + * @return: irq_idx or -EINVAL when fail to lookup + */ +int dpu_core_irq_idx_lookup( + struct dpu_kms *dpu_kms, + enum dpu_intr_type intr_type, + uint32_t instance_idx); + +/** + * dpu_core_irq_enable - IRQ helper function for enabling one or more IRQs + * @dpu_kms: DPU handle + * @irq_idxs: Array of irq index + * @irq_count: Number of irq_idx provided in the array + * @return: 0 for success enabling IRQ, otherwise failure + * + * This function increments count on each enable and decrements on each + * disable. Interrupts is enabled if count is 0 before increment. + */ +int dpu_core_irq_enable( + struct dpu_kms *dpu_kms, + int *irq_idxs, + uint32_t irq_count); + +/** + * dpu_core_irq_disable - IRQ helper function for disabling one of more IRQs + * @dpu_kms: DPU handle + * @irq_idxs: Array of irq index + * @irq_count: Number of irq_idx provided in the array + * @return: 0 for success disabling IRQ, otherwise failure + * + * This function increments count on each enable and decrements on each + * disable. Interrupts is disabled if count is 0 after decrement. + */ +int dpu_core_irq_disable( + struct dpu_kms *dpu_kms, + int *irq_idxs, + uint32_t irq_count); + +/** + * dpu_core_irq_read - IRQ helper function for reading IRQ status + * @dpu_kms: DPU handle + * @irq_idx: irq index + * @clear: True to clear the irq after read + * @return: non-zero if irq detected; otherwise no irq detected + */ +u32 dpu_core_irq_read( + struct dpu_kms *dpu_kms, + int irq_idx, + bool clear); + +/** + * dpu_core_irq_register_callback - For registering callback function on IRQ + * interrupt + * @dpu_kms: DPU handle + * @irq_idx: irq index + * @irq_cb: IRQ callback structure, containing callback function + * and argument. Passing NULL for irq_cb will unregister + * the callback for the given irq_idx + * This must exist until un-registration. + * @return: 0 for success registering callback, otherwise failure + * + * This function supports registration of multiple callbacks for each interrupt. + */ +int dpu_core_irq_register_callback( + struct dpu_kms *dpu_kms, + int irq_idx, + struct dpu_irq_callback *irq_cb); + +/** + * dpu_core_irq_unregister_callback - For unregistering callback function on IRQ + * interrupt + * @dpu_kms: DPU handle + * @irq_idx: irq index + * @irq_cb: IRQ callback structure, containing callback function + * and argument. Passing NULL for irq_cb will unregister + * the callback for the given irq_idx + * This must match with registration. + * @return: 0 for success registering callback, otherwise failure + * + * This function supports registration of multiple callbacks for each interrupt. + */ +int dpu_core_irq_unregister_callback( + struct dpu_kms *dpu_kms, + int irq_idx, + struct dpu_irq_callback *irq_cb); + +/** + * dpu_debugfs_core_irq_init - register core irq debugfs + * @dpu_kms: pointer to kms + * @parent: debugfs directory root + */ +void dpu_debugfs_core_irq_init(struct dpu_kms *dpu_kms, + struct dentry *parent); + +#endif /* __DPU_CORE_IRQ_H__ */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c new file mode 100644 index 000000000..37c827068 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.c @@ -0,0 +1,548 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ + +#include <linux/debugfs.h> +#include <linux/errno.h> +#include <linux/mutex.h> +#include <linux/pm_opp.h> +#include <linux/sort.h> +#include <linux/clk.h> +#include <linux/bitmap.h> + +#include "dpu_kms.h" +#include "dpu_trace.h" +#include "dpu_crtc.h" +#include "dpu_core_perf.h" + +/** + * enum dpu_perf_mode - performance tuning mode + * @DPU_PERF_MODE_NORMAL: performance controlled by user mode client + * @DPU_PERF_MODE_MINIMUM: performance bounded by minimum setting + * @DPU_PERF_MODE_FIXED: performance bounded by fixed setting + */ +enum dpu_perf_mode { + DPU_PERF_MODE_NORMAL, + DPU_PERF_MODE_MINIMUM, + DPU_PERF_MODE_FIXED, + DPU_PERF_MODE_MAX +}; + +/** + * @_dpu_core_perf_calc_bw() - to calculate BW per crtc + * @kms - pointer to the dpu_kms + * @crtc - pointer to a crtc + * Return: returns aggregated BW for all planes in crtc. + */ +static u64 _dpu_core_perf_calc_bw(struct dpu_kms *kms, + struct drm_crtc *crtc) +{ + struct drm_plane *plane; + struct dpu_plane_state *pstate; + u64 crtc_plane_bw = 0; + u32 bw_factor; + + drm_atomic_crtc_for_each_plane(plane, crtc) { + pstate = to_dpu_plane_state(plane->state); + if (!pstate) + continue; + + crtc_plane_bw += pstate->plane_fetch_bw; + } + + bw_factor = kms->catalog->perf.bw_inefficiency_factor; + if (bw_factor) { + crtc_plane_bw *= bw_factor; + do_div(crtc_plane_bw, 100); + } + + return crtc_plane_bw; +} + +/** + * _dpu_core_perf_calc_clk() - to calculate clock per crtc + * @kms - pointer to the dpu_kms + * @crtc - pointer to a crtc + * @state - pointer to a crtc state + * Return: returns max clk for all planes in crtc. + */ +static u64 _dpu_core_perf_calc_clk(struct dpu_kms *kms, + struct drm_crtc *crtc, struct drm_crtc_state *state) +{ + struct drm_plane *plane; + struct dpu_plane_state *pstate; + struct drm_display_mode *mode; + u64 crtc_clk; + u32 clk_factor; + + mode = &state->adjusted_mode; + + crtc_clk = mode->vtotal * mode->hdisplay * drm_mode_vrefresh(mode); + + drm_atomic_crtc_for_each_plane(plane, crtc) { + pstate = to_dpu_plane_state(plane->state); + if (!pstate) + continue; + + crtc_clk = max(pstate->plane_clk, crtc_clk); + } + + clk_factor = kms->catalog->perf.clk_inefficiency_factor; + if (clk_factor) { + crtc_clk *= clk_factor; + do_div(crtc_clk, 100); + } + + return crtc_clk; +} + +static struct dpu_kms *_dpu_crtc_get_kms(struct drm_crtc *crtc) +{ + struct msm_drm_private *priv; + priv = crtc->dev->dev_private; + return to_dpu_kms(priv->kms); +} + +static void _dpu_core_perf_calc_crtc(struct dpu_kms *kms, + struct drm_crtc *crtc, + struct drm_crtc_state *state, + struct dpu_core_perf_params *perf) +{ + struct dpu_crtc_state *dpu_cstate; + + if (!kms || !kms->catalog || !crtc || !state || !perf) { + DPU_ERROR("invalid parameters\n"); + return; + } + + dpu_cstate = to_dpu_crtc_state(state); + memset(perf, 0, sizeof(struct dpu_core_perf_params)); + + if (kms->perf.perf_tune.mode == DPU_PERF_MODE_MINIMUM) { + perf->bw_ctl = 0; + perf->max_per_pipe_ib = 0; + perf->core_clk_rate = 0; + } else if (kms->perf.perf_tune.mode == DPU_PERF_MODE_FIXED) { + perf->bw_ctl = kms->perf.fix_core_ab_vote; + perf->max_per_pipe_ib = kms->perf.fix_core_ib_vote; + perf->core_clk_rate = kms->perf.fix_core_clk_rate; + } else { + perf->bw_ctl = _dpu_core_perf_calc_bw(kms, crtc); + perf->max_per_pipe_ib = kms->catalog->perf.min_dram_ib; + perf->core_clk_rate = _dpu_core_perf_calc_clk(kms, crtc, state); + } + + DPU_DEBUG( + "crtc=%d clk_rate=%llu core_ib=%llu core_ab=%llu\n", + crtc->base.id, perf->core_clk_rate, + perf->max_per_pipe_ib, perf->bw_ctl); +} + +int dpu_core_perf_crtc_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + u32 bw, threshold; + u64 bw_sum_of_intfs = 0; + enum dpu_crtc_client_type curr_client_type; + struct dpu_crtc_state *dpu_cstate; + struct drm_crtc *tmp_crtc; + struct dpu_kms *kms; + + if (!crtc || !state) { + DPU_ERROR("invalid crtc\n"); + return -EINVAL; + } + + kms = _dpu_crtc_get_kms(crtc); + if (!kms->catalog) { + DPU_ERROR("invalid parameters\n"); + return 0; + } + + /* we only need bandwidth check on real-time clients (interfaces) */ + if (dpu_crtc_get_client_type(crtc) == NRT_CLIENT) + return 0; + + dpu_cstate = to_dpu_crtc_state(state); + + /* obtain new values */ + _dpu_core_perf_calc_crtc(kms, crtc, state, &dpu_cstate->new_perf); + + bw_sum_of_intfs = dpu_cstate->new_perf.bw_ctl; + curr_client_type = dpu_crtc_get_client_type(crtc); + + drm_for_each_crtc(tmp_crtc, crtc->dev) { + if (tmp_crtc->enabled && + (dpu_crtc_get_client_type(tmp_crtc) == + curr_client_type) && (tmp_crtc != crtc)) { + struct dpu_crtc_state *tmp_cstate = + to_dpu_crtc_state(tmp_crtc->state); + + DPU_DEBUG("crtc:%d bw:%llu ctrl:%d\n", + tmp_crtc->base.id, tmp_cstate->new_perf.bw_ctl, + tmp_cstate->bw_control); + + bw_sum_of_intfs += tmp_cstate->new_perf.bw_ctl; + } + + /* convert bandwidth to kb */ + bw = DIV_ROUND_UP_ULL(bw_sum_of_intfs, 1000); + DPU_DEBUG("calculated bandwidth=%uk\n", bw); + + threshold = kms->catalog->perf.max_bw_high; + + DPU_DEBUG("final threshold bw limit = %d\n", threshold); + + if (!threshold) { + DPU_ERROR("no bandwidth limits specified\n"); + return -E2BIG; + } else if (bw > threshold) { + DPU_ERROR("exceeds bandwidth: %ukb > %ukb\n", bw, + threshold); + return -E2BIG; + } + } + + return 0; +} + +static int _dpu_core_perf_crtc_update_bus(struct dpu_kms *kms, + struct drm_crtc *crtc) +{ + struct dpu_core_perf_params perf = { 0 }; + enum dpu_crtc_client_type curr_client_type + = dpu_crtc_get_client_type(crtc); + struct drm_crtc *tmp_crtc; + struct dpu_crtc_state *dpu_cstate; + int i, ret = 0; + u64 avg_bw; + + drm_for_each_crtc(tmp_crtc, crtc->dev) { + if (tmp_crtc->enabled && + curr_client_type == + dpu_crtc_get_client_type(tmp_crtc)) { + dpu_cstate = to_dpu_crtc_state(tmp_crtc->state); + + perf.max_per_pipe_ib = max(perf.max_per_pipe_ib, + dpu_cstate->new_perf.max_per_pipe_ib); + + perf.bw_ctl += dpu_cstate->new_perf.bw_ctl; + + DPU_DEBUG("crtc=%d bw=%llu paths:%d\n", + tmp_crtc->base.id, + dpu_cstate->new_perf.bw_ctl, kms->num_paths); + } + } + + if (!kms->num_paths) + return 0; + + avg_bw = perf.bw_ctl; + do_div(avg_bw, (kms->num_paths * 1000)); /*Bps_to_icc*/ + + for (i = 0; i < kms->num_paths; i++) + icc_set_bw(kms->path[i], avg_bw, perf.max_per_pipe_ib); + + return ret; +} + +/** + * @dpu_core_perf_crtc_release_bw() - request zero bandwidth + * @crtc - pointer to a crtc + * + * Function checks a state variable for the crtc, if all pending commit + * requests are done, meaning no more bandwidth is needed, release + * bandwidth request. + */ +void dpu_core_perf_crtc_release_bw(struct drm_crtc *crtc) +{ + struct dpu_crtc *dpu_crtc; + struct dpu_kms *kms; + + if (!crtc) { + DPU_ERROR("invalid crtc\n"); + return; + } + + kms = _dpu_crtc_get_kms(crtc); + if (!kms->catalog) { + DPU_ERROR("invalid kms\n"); + return; + } + + dpu_crtc = to_dpu_crtc(crtc); + + if (atomic_dec_return(&kms->bandwidth_ref) > 0) + return; + + /* Release the bandwidth */ + if (kms->perf.enable_bw_release) { + trace_dpu_cmd_release_bw(crtc->base.id); + DPU_DEBUG("Release BW crtc=%d\n", crtc->base.id); + dpu_crtc->cur_perf.bw_ctl = 0; + _dpu_core_perf_crtc_update_bus(kms, crtc); + } +} + +static int _dpu_core_perf_set_core_clk_rate(struct dpu_kms *kms, u64 rate) +{ + struct dss_clk *core_clk = kms->perf.core_clk; + + if (core_clk->max_rate && (rate > core_clk->max_rate)) + rate = core_clk->max_rate; + + core_clk->rate = rate; + return dev_pm_opp_set_rate(&kms->pdev->dev, core_clk->rate); +} + +static u64 _dpu_core_perf_get_core_clk_rate(struct dpu_kms *kms) +{ + u64 clk_rate = kms->perf.perf_tune.min_core_clk; + struct drm_crtc *crtc; + struct dpu_crtc_state *dpu_cstate; + + drm_for_each_crtc(crtc, kms->dev) { + if (crtc->enabled) { + dpu_cstate = to_dpu_crtc_state(crtc->state); + clk_rate = max(dpu_cstate->new_perf.core_clk_rate, + clk_rate); + clk_rate = clk_round_rate(kms->perf.core_clk->clk, + clk_rate); + } + } + + if (kms->perf.perf_tune.mode == DPU_PERF_MODE_FIXED) + clk_rate = kms->perf.fix_core_clk_rate; + + DPU_DEBUG("clk:%llu\n", clk_rate); + + return clk_rate; +} + +int dpu_core_perf_crtc_update(struct drm_crtc *crtc, + int params_changed, bool stop_req) +{ + struct dpu_core_perf_params *new, *old; + bool update_bus = false, update_clk = false; + u64 clk_rate = 0; + struct dpu_crtc *dpu_crtc; + struct dpu_crtc_state *dpu_cstate; + struct dpu_kms *kms; + int ret; + + if (!crtc) { + DPU_ERROR("invalid crtc\n"); + return -EINVAL; + } + + kms = _dpu_crtc_get_kms(crtc); + if (!kms->catalog) { + DPU_ERROR("invalid kms\n"); + return -EINVAL; + } + + dpu_crtc = to_dpu_crtc(crtc); + dpu_cstate = to_dpu_crtc_state(crtc->state); + + DPU_DEBUG("crtc:%d stop_req:%d core_clk:%llu\n", + crtc->base.id, stop_req, kms->perf.core_clk_rate); + + old = &dpu_crtc->cur_perf; + new = &dpu_cstate->new_perf; + + if (crtc->enabled && !stop_req) { + /* + * cases for bus bandwidth update. + * 1. new bandwidth vote - "ab or ib vote" is higher + * than current vote for update request. + * 2. new bandwidth vote - "ab or ib vote" is lower + * than current vote at end of commit or stop. + */ + if ((params_changed && ((new->bw_ctl > old->bw_ctl) || + (new->max_per_pipe_ib > old->max_per_pipe_ib))) || + (!params_changed && ((new->bw_ctl < old->bw_ctl) || + (new->max_per_pipe_ib < old->max_per_pipe_ib)))) { + DPU_DEBUG("crtc=%d p=%d new_bw=%llu,old_bw=%llu\n", + crtc->base.id, params_changed, + new->bw_ctl, old->bw_ctl); + old->bw_ctl = new->bw_ctl; + old->max_per_pipe_ib = new->max_per_pipe_ib; + update_bus = true; + } + + if ((params_changed && + (new->core_clk_rate > old->core_clk_rate)) || + (!params_changed && + (new->core_clk_rate < old->core_clk_rate))) { + old->core_clk_rate = new->core_clk_rate; + update_clk = true; + } + } else { + DPU_DEBUG("crtc=%d disable\n", crtc->base.id); + memset(old, 0, sizeof(*old)); + memset(new, 0, sizeof(*new)); + update_bus = true; + update_clk = true; + } + + trace_dpu_perf_crtc_update(crtc->base.id, new->bw_ctl, + new->core_clk_rate, stop_req, update_bus, update_clk); + + if (update_bus) { + ret = _dpu_core_perf_crtc_update_bus(kms, crtc); + if (ret) { + DPU_ERROR("crtc-%d: failed to update bus bw vote\n", + crtc->base.id); + return ret; + } + } + + /* + * Update the clock after bandwidth vote to ensure + * bandwidth is available before clock rate is increased. + */ + if (update_clk) { + clk_rate = _dpu_core_perf_get_core_clk_rate(kms); + + trace_dpu_core_perf_update_clk(kms->dev, stop_req, clk_rate); + + ret = _dpu_core_perf_set_core_clk_rate(kms, clk_rate); + if (ret) { + DPU_ERROR("failed to set %s clock rate %llu\n", + kms->perf.core_clk->clk_name, clk_rate); + return ret; + } + + kms->perf.core_clk_rate = clk_rate; + DPU_DEBUG("update clk rate = %lld HZ\n", clk_rate); + } + return 0; +} + +#ifdef CONFIG_DEBUG_FS + +static ssize_t _dpu_core_perf_mode_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct dpu_core_perf *perf = file->private_data; + struct dpu_perf_cfg *cfg = &perf->catalog->perf; + u32 perf_mode = 0; + int ret; + + ret = kstrtouint_from_user(user_buf, count, 0, &perf_mode); + if (ret) + return ret; + + if (perf_mode >= DPU_PERF_MODE_MAX) + return -EINVAL; + + if (perf_mode == DPU_PERF_MODE_FIXED) { + DRM_INFO("fix performance mode\n"); + } else if (perf_mode == DPU_PERF_MODE_MINIMUM) { + /* run the driver with max clk and BW vote */ + perf->perf_tune.min_core_clk = perf->max_core_clk_rate; + perf->perf_tune.min_bus_vote = + (u64) cfg->max_bw_high * 1000; + DRM_INFO("minimum performance mode\n"); + } else if (perf_mode == DPU_PERF_MODE_NORMAL) { + /* reset the perf tune params to 0 */ + perf->perf_tune.min_core_clk = 0; + perf->perf_tune.min_bus_vote = 0; + DRM_INFO("normal performance mode\n"); + } + perf->perf_tune.mode = perf_mode; + + return count; +} + +static ssize_t _dpu_core_perf_mode_read(struct file *file, + char __user *buff, size_t count, loff_t *ppos) +{ + struct dpu_core_perf *perf = file->private_data; + int len; + char buf[128]; + + len = scnprintf(buf, sizeof(buf), + "mode %d min_mdp_clk %llu min_bus_vote %llu\n", + perf->perf_tune.mode, + perf->perf_tune.min_core_clk, + perf->perf_tune.min_bus_vote); + + return simple_read_from_buffer(buff, count, ppos, buf, len); +} + +static const struct file_operations dpu_core_perf_mode_fops = { + .open = simple_open, + .read = _dpu_core_perf_mode_read, + .write = _dpu_core_perf_mode_write, +}; + +int dpu_core_perf_debugfs_init(struct dpu_kms *dpu_kms, struct dentry *parent) +{ + struct dpu_core_perf *perf = &dpu_kms->perf; + struct dpu_mdss_cfg *catalog = perf->catalog; + struct dentry *entry; + + entry = debugfs_create_dir("core_perf", parent); + + debugfs_create_u64("max_core_clk_rate", 0600, entry, + &perf->max_core_clk_rate); + debugfs_create_u64("core_clk_rate", 0600, entry, + &perf->core_clk_rate); + debugfs_create_u32("enable_bw_release", 0600, entry, + (u32 *)&perf->enable_bw_release); + debugfs_create_u32("threshold_low", 0600, entry, + (u32 *)&catalog->perf.max_bw_low); + debugfs_create_u32("threshold_high", 0600, entry, + (u32 *)&catalog->perf.max_bw_high); + debugfs_create_u32("min_core_ib", 0600, entry, + (u32 *)&catalog->perf.min_core_ib); + debugfs_create_u32("min_llcc_ib", 0600, entry, + (u32 *)&catalog->perf.min_llcc_ib); + debugfs_create_u32("min_dram_ib", 0600, entry, + (u32 *)&catalog->perf.min_dram_ib); + debugfs_create_file("perf_mode", 0600, entry, + (u32 *)perf, &dpu_core_perf_mode_fops); + debugfs_create_u64("fix_core_clk_rate", 0600, entry, + &perf->fix_core_clk_rate); + debugfs_create_u64("fix_core_ib_vote", 0600, entry, + &perf->fix_core_ib_vote); + debugfs_create_u64("fix_core_ab_vote", 0600, entry, + &perf->fix_core_ab_vote); + + return 0; +} +#endif + +void dpu_core_perf_destroy(struct dpu_core_perf *perf) +{ + if (!perf) { + DPU_ERROR("invalid parameters\n"); + return; + } + + perf->max_core_clk_rate = 0; + perf->core_clk = NULL; + perf->catalog = NULL; + perf->dev = NULL; +} + +int dpu_core_perf_init(struct dpu_core_perf *perf, + struct drm_device *dev, + struct dpu_mdss_cfg *catalog, + struct dss_clk *core_clk) +{ + perf->dev = dev; + perf->catalog = catalog; + perf->core_clk = core_clk; + + perf->max_core_clk_rate = core_clk->max_rate; + if (!perf->max_core_clk_rate) { + DPU_DEBUG("optional max core clk rate, use default\n"); + perf->max_core_clk_rate = DPU_PERF_DEFAULT_MAX_CORE_CLK_RATE; + } + + return 0; +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.h new file mode 100644 index 000000000..cd6c3518b --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_core_perf.h @@ -0,0 +1,121 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_CORE_PERF_H_ +#define _DPU_CORE_PERF_H_ + +#include <linux/types.h> +#include <linux/dcache.h> +#include <linux/mutex.h> +#include <drm/drm_crtc.h> + +#include "dpu_hw_catalog.h" + +#define DPU_PERF_DEFAULT_MAX_CORE_CLK_RATE 412500000 + +/** + * struct dpu_core_perf_params - definition of performance parameters + * @max_per_pipe_ib: maximum instantaneous bandwidth request + * @bw_ctl: arbitrated bandwidth request + * @core_clk_rate: core clock rate request + */ +struct dpu_core_perf_params { + u64 max_per_pipe_ib; + u64 bw_ctl; + u64 core_clk_rate; +}; + +/** + * struct dpu_core_perf_tune - definition of performance tuning control + * @mode: performance mode + * @min_core_clk: minimum core clock + * @min_bus_vote: minimum bus vote + */ +struct dpu_core_perf_tune { + u32 mode; + u64 min_core_clk; + u64 min_bus_vote; +}; + +/** + * struct dpu_core_perf - definition of core performance context + * @dev: Pointer to drm device + * @debugfs_root: top level debug folder + * @catalog: Pointer to catalog configuration + * @core_clk: Pointer to core clock structure + * @core_clk_rate: current core clock rate + * @max_core_clk_rate: maximum allowable core clock rate + * @perf_tune: debug control for performance tuning + * @enable_bw_release: debug control for bandwidth release + * @fix_core_clk_rate: fixed core clock request in Hz used in mode 2 + * @fix_core_ib_vote: fixed core ib vote in bps used in mode 2 + * @fix_core_ab_vote: fixed core ab vote in bps used in mode 2 + */ +struct dpu_core_perf { + struct drm_device *dev; + struct dentry *debugfs_root; + struct dpu_mdss_cfg *catalog; + struct dss_clk *core_clk; + u64 core_clk_rate; + u64 max_core_clk_rate; + struct dpu_core_perf_tune perf_tune; + u32 enable_bw_release; + u64 fix_core_clk_rate; + u64 fix_core_ib_vote; + u64 fix_core_ab_vote; +}; + +/** + * dpu_core_perf_crtc_check - validate performance of the given crtc state + * @crtc: Pointer to crtc + * @state: Pointer to new crtc state + * return: zero if success, or error code otherwise + */ +int dpu_core_perf_crtc_check(struct drm_crtc *crtc, + struct drm_crtc_state *state); + +/** + * dpu_core_perf_crtc_update - update performance of the given crtc + * @crtc: Pointer to crtc + * @params_changed: true if crtc parameters are modified + * @stop_req: true if this is a stop request + * return: zero if success, or error code otherwise + */ +int dpu_core_perf_crtc_update(struct drm_crtc *crtc, + int params_changed, bool stop_req); + +/** + * dpu_core_perf_crtc_release_bw - release bandwidth of the given crtc + * @crtc: Pointer to crtc + */ +void dpu_core_perf_crtc_release_bw(struct drm_crtc *crtc); + +/** + * dpu_core_perf_destroy - destroy the given core performance context + * @perf: Pointer to core performance context + */ +void dpu_core_perf_destroy(struct dpu_core_perf *perf); + +/** + * dpu_core_perf_init - initialize the given core performance context + * @perf: Pointer to core performance context + * @dev: Pointer to drm device + * @catalog: Pointer to catalog + * @core_clk: pointer to core clock + */ +int dpu_core_perf_init(struct dpu_core_perf *perf, + struct drm_device *dev, + struct dpu_mdss_cfg *catalog, + struct dss_clk *core_clk); + +struct dpu_kms; + +/** + * dpu_core_perf_debugfs_init - initialize debugfs for core performance context + * @dpu_kms: Pointer to the dpu_kms struct + * @debugfs_parent: Pointer to parent debugfs + */ +int dpu_core_perf_debugfs_init(struct dpu_kms *dpu_kms, struct dentry *parent); + +#endif /* _DPU_CORE_PERF_H_ */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c new file mode 100644 index 000000000..4c64e2d4f --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c @@ -0,0 +1,1309 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2018 The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ +#include <linux/sort.h> +#include <linux/debugfs.h> +#include <linux/ktime.h> +#include <linux/bits.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_flip_work.h> +#include <drm/drm_mode.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_rect.h> +#include <drm/drm_vblank.h> + +#include "dpu_kms.h" +#include "dpu_hw_lm.h" +#include "dpu_hw_ctl.h" +#include "dpu_hw_dspp.h" +#include "dpu_crtc.h" +#include "dpu_plane.h" +#include "dpu_encoder.h" +#include "dpu_vbif.h" +#include "dpu_core_perf.h" +#include "dpu_trace.h" + +#define DPU_DRM_BLEND_OP_NOT_DEFINED 0 +#define DPU_DRM_BLEND_OP_OPAQUE 1 +#define DPU_DRM_BLEND_OP_PREMULTIPLIED 2 +#define DPU_DRM_BLEND_OP_COVERAGE 3 +#define DPU_DRM_BLEND_OP_MAX 4 + +/* layer mixer index on dpu_crtc */ +#define LEFT_MIXER 0 +#define RIGHT_MIXER 1 + +/* timeout in ms waiting for frame done */ +#define DPU_CRTC_FRAME_DONE_TIMEOUT_MS 60 + +#define CONVERT_S3_15(val) \ + (((((u64)val) & ~BIT_ULL(63)) >> 17) & GENMASK_ULL(17, 0)) + +static struct dpu_kms *_dpu_crtc_get_kms(struct drm_crtc *crtc) +{ + struct msm_drm_private *priv = crtc->dev->dev_private; + + return to_dpu_kms(priv->kms); +} + +static void dpu_crtc_destroy(struct drm_crtc *crtc) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + + DPU_DEBUG("\n"); + + if (!crtc) + return; + + drm_crtc_cleanup(crtc); + kfree(dpu_crtc); +} + +static void _dpu_crtc_setup_blend_cfg(struct dpu_crtc_mixer *mixer, + struct dpu_plane_state *pstate, struct dpu_format *format) +{ + struct dpu_hw_mixer *lm = mixer->hw_lm; + uint32_t blend_op; + struct drm_format_name_buf format_name; + + /* default to opaque blending */ + blend_op = DPU_BLEND_FG_ALPHA_FG_CONST | + DPU_BLEND_BG_ALPHA_BG_CONST; + + if (format->alpha_enable) { + /* coverage blending */ + blend_op = DPU_BLEND_FG_ALPHA_FG_PIXEL | + DPU_BLEND_BG_ALPHA_FG_PIXEL | + DPU_BLEND_BG_INV_ALPHA; + } + + lm->ops.setup_blend_config(lm, pstate->stage, + 0xFF, 0, blend_op); + + DPU_DEBUG("format:%s, alpha_en:%u blend_op:0x%x\n", + drm_get_format_name(format->base.pixel_format, &format_name), + format->alpha_enable, blend_op); +} + +static void _dpu_crtc_program_lm_output_roi(struct drm_crtc *crtc) +{ + struct dpu_crtc_state *crtc_state; + int lm_idx, lm_horiz_position; + + crtc_state = to_dpu_crtc_state(crtc->state); + + lm_horiz_position = 0; + for (lm_idx = 0; lm_idx < crtc_state->num_mixers; lm_idx++) { + const struct drm_rect *lm_roi = &crtc_state->lm_bounds[lm_idx]; + struct dpu_hw_mixer *hw_lm = crtc_state->mixers[lm_idx].hw_lm; + struct dpu_hw_mixer_cfg cfg; + + if (!lm_roi || !drm_rect_visible(lm_roi)) + continue; + + cfg.out_width = drm_rect_width(lm_roi); + cfg.out_height = drm_rect_height(lm_roi); + cfg.right_mixer = lm_horiz_position++; + cfg.flags = 0; + hw_lm->ops.setup_mixer_out(hw_lm, &cfg); + } +} + +static void _dpu_crtc_blend_setup_mixer(struct drm_crtc *crtc, + struct dpu_crtc *dpu_crtc, struct dpu_crtc_mixer *mixer) +{ + struct drm_plane *plane; + struct drm_framebuffer *fb; + struct drm_plane_state *state; + struct dpu_crtc_state *cstate = to_dpu_crtc_state(crtc->state); + struct dpu_plane_state *pstate = NULL; + struct dpu_format *format; + struct dpu_hw_ctl *ctl = mixer->lm_ctl; + struct dpu_hw_stage_cfg *stage_cfg = &dpu_crtc->stage_cfg; + + u32 flush_mask; + uint32_t stage_idx, lm_idx; + int zpos_cnt[DPU_STAGE_MAX + 1] = { 0 }; + bool bg_alpha_enable = false; + + drm_atomic_crtc_for_each_plane(plane, crtc) { + state = plane->state; + if (!state) + continue; + + pstate = to_dpu_plane_state(state); + fb = state->fb; + + dpu_plane_get_ctl_flush(plane, ctl, &flush_mask); + + DPU_DEBUG("crtc %d stage:%d - plane %d sspp %d fb %d\n", + crtc->base.id, + pstate->stage, + plane->base.id, + dpu_plane_pipe(plane) - SSPP_VIG0, + state->fb ? state->fb->base.id : -1); + + format = to_dpu_format(msm_framebuffer_format(pstate->base.fb)); + + if (pstate->stage == DPU_STAGE_BASE && format->alpha_enable) + bg_alpha_enable = true; + + stage_idx = zpos_cnt[pstate->stage]++; + stage_cfg->stage[pstate->stage][stage_idx] = + dpu_plane_pipe(plane); + stage_cfg->multirect_index[pstate->stage][stage_idx] = + pstate->multirect_index; + + trace_dpu_crtc_setup_mixer(DRMID(crtc), DRMID(plane), + state, pstate, stage_idx, + dpu_plane_pipe(plane) - SSPP_VIG0, + format->base.pixel_format, + fb ? fb->modifier : 0); + + /* blend config update */ + for (lm_idx = 0; lm_idx < cstate->num_mixers; lm_idx++) { + _dpu_crtc_setup_blend_cfg(mixer + lm_idx, + pstate, format); + + mixer[lm_idx].flush_mask |= flush_mask; + + if (bg_alpha_enable && !format->alpha_enable) + mixer[lm_idx].mixer_op_mode = 0; + else + mixer[lm_idx].mixer_op_mode |= + 1 << pstate->stage; + } + } + + _dpu_crtc_program_lm_output_roi(crtc); +} + +/** + * _dpu_crtc_blend_setup - configure crtc mixers + * @crtc: Pointer to drm crtc structure + */ +static void _dpu_crtc_blend_setup(struct drm_crtc *crtc) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct dpu_crtc_state *cstate = to_dpu_crtc_state(crtc->state); + struct dpu_crtc_mixer *mixer = cstate->mixers; + struct dpu_hw_ctl *ctl; + struct dpu_hw_mixer *lm; + int i; + + DPU_DEBUG("%s\n", dpu_crtc->name); + + for (i = 0; i < cstate->num_mixers; i++) { + mixer[i].mixer_op_mode = 0; + mixer[i].flush_mask = 0; + if (mixer[i].lm_ctl->ops.clear_all_blendstages) + mixer[i].lm_ctl->ops.clear_all_blendstages( + mixer[i].lm_ctl); + } + + /* initialize stage cfg */ + memset(&dpu_crtc->stage_cfg, 0, sizeof(struct dpu_hw_stage_cfg)); + + _dpu_crtc_blend_setup_mixer(crtc, dpu_crtc, mixer); + + for (i = 0; i < cstate->num_mixers; i++) { + ctl = mixer[i].lm_ctl; + lm = mixer[i].hw_lm; + + lm->ops.setup_alpha_out(lm, mixer[i].mixer_op_mode); + + mixer[i].flush_mask |= ctl->ops.get_bitmask_mixer(ctl, + mixer[i].hw_lm->idx); + + /* stage config flush mask */ + ctl->ops.update_pending_flush(ctl, mixer[i].flush_mask); + + DPU_DEBUG("lm %d, op_mode 0x%X, ctl %d, flush mask 0x%x\n", + mixer[i].hw_lm->idx - LM_0, + mixer[i].mixer_op_mode, + ctl->idx - CTL_0, + mixer[i].flush_mask); + + ctl->ops.setup_blendstage(ctl, mixer[i].hw_lm->idx, + &dpu_crtc->stage_cfg); + } +} + +/** + * _dpu_crtc_complete_flip - signal pending page_flip events + * Any pending vblank events are added to the vblank_event_list + * so that the next vblank interrupt shall signal them. + * However PAGE_FLIP events are not handled through the vblank_event_list. + * This API signals any pending PAGE_FLIP events requested through + * DRM_IOCTL_MODE_PAGE_FLIP and are cached in the dpu_crtc->event. + * @crtc: Pointer to drm crtc structure + */ +static void _dpu_crtc_complete_flip(struct drm_crtc *crtc) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct drm_device *dev = crtc->dev; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + if (dpu_crtc->event) { + DRM_DEBUG_VBL("%s: send event: %pK\n", dpu_crtc->name, + dpu_crtc->event); + trace_dpu_crtc_complete_flip(DRMID(crtc)); + drm_crtc_send_vblank_event(crtc, dpu_crtc->event); + dpu_crtc->event = NULL; + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +enum dpu_intf_mode dpu_crtc_get_intf_mode(struct drm_crtc *crtc) +{ + struct drm_encoder *encoder; + + /* + * TODO: This function is called from dpu debugfs and as part of atomic + * check. When called from debugfs, the crtc->mutex must be held to + * read crtc->state. However reading crtc->state from atomic check isn't + * allowed (unless you have a good reason, a big comment, and a deep + * understanding of how the atomic/modeset locks work (<- and this is + * probably not possible)). So we'll keep the WARN_ON here for now, but + * really we need to figure out a better way to track our operating mode + */ + WARN_ON(!drm_modeset_is_locked(&crtc->mutex)); + + /* TODO: Returns the first INTF_MODE, could there be multiple values? */ + drm_for_each_encoder_mask(encoder, crtc->dev, crtc->state->encoder_mask) + return dpu_encoder_get_intf_mode(encoder); + + return INTF_MODE_NONE; +} + +void dpu_crtc_vblank_callback(struct drm_crtc *crtc) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + + /* keep statistics on vblank callback - with auto reset via debugfs */ + if (ktime_compare(dpu_crtc->vblank_cb_time, ktime_set(0, 0)) == 0) + dpu_crtc->vblank_cb_time = ktime_get(); + else + dpu_crtc->vblank_cb_count++; + drm_crtc_handle_vblank(crtc); + trace_dpu_crtc_vblank_cb(DRMID(crtc)); +} + +static void dpu_crtc_frame_event_work(struct kthread_work *work) +{ + struct dpu_crtc_frame_event *fevent = container_of(work, + struct dpu_crtc_frame_event, work); + struct drm_crtc *crtc = fevent->crtc; + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + unsigned long flags; + bool frame_done = false; + + DPU_ATRACE_BEGIN("crtc_frame_event"); + + DRM_DEBUG_KMS("crtc%d event:%u ts:%lld\n", crtc->base.id, fevent->event, + ktime_to_ns(fevent->ts)); + + if (fevent->event & (DPU_ENCODER_FRAME_EVENT_DONE + | DPU_ENCODER_FRAME_EVENT_ERROR + | DPU_ENCODER_FRAME_EVENT_PANEL_DEAD)) { + + if (atomic_read(&dpu_crtc->frame_pending) < 1) { + /* ignore vblank when not pending */ + } else if (atomic_dec_return(&dpu_crtc->frame_pending) == 0) { + /* release bandwidth and other resources */ + trace_dpu_crtc_frame_event_done(DRMID(crtc), + fevent->event); + dpu_core_perf_crtc_release_bw(crtc); + } else { + trace_dpu_crtc_frame_event_more_pending(DRMID(crtc), + fevent->event); + } + + if (fevent->event & DPU_ENCODER_FRAME_EVENT_DONE) + dpu_core_perf_crtc_update(crtc, 0, false); + + if (fevent->event & (DPU_ENCODER_FRAME_EVENT_DONE + | DPU_ENCODER_FRAME_EVENT_ERROR)) + frame_done = true; + } + + if (fevent->event & DPU_ENCODER_FRAME_EVENT_PANEL_DEAD) + DPU_ERROR("crtc%d ts:%lld received panel dead event\n", + crtc->base.id, ktime_to_ns(fevent->ts)); + + if (frame_done) + complete_all(&dpu_crtc->frame_done_comp); + + spin_lock_irqsave(&dpu_crtc->spin_lock, flags); + list_add_tail(&fevent->list, &dpu_crtc->frame_event_list); + spin_unlock_irqrestore(&dpu_crtc->spin_lock, flags); + DPU_ATRACE_END("crtc_frame_event"); +} + +/* + * dpu_crtc_frame_event_cb - crtc frame event callback API. CRTC module + * registers this API to encoder for all frame event callbacks like + * frame_error, frame_done, idle_timeout, etc. Encoder may call different events + * from different context - IRQ, user thread, commit_thread, etc. Each event + * should be carefully reviewed and should be processed in proper task context + * to avoid schedulin delay or properly manage the irq context's bottom half + * processing. + */ +static void dpu_crtc_frame_event_cb(void *data, u32 event) +{ + struct drm_crtc *crtc = (struct drm_crtc *)data; + struct dpu_crtc *dpu_crtc; + struct msm_drm_private *priv; + struct dpu_crtc_frame_event *fevent; + unsigned long flags; + u32 crtc_id; + + /* Nothing to do on idle event */ + if (event & DPU_ENCODER_FRAME_EVENT_IDLE) + return; + + dpu_crtc = to_dpu_crtc(crtc); + priv = crtc->dev->dev_private; + crtc_id = drm_crtc_index(crtc); + + trace_dpu_crtc_frame_event_cb(DRMID(crtc), event); + + spin_lock_irqsave(&dpu_crtc->spin_lock, flags); + fevent = list_first_entry_or_null(&dpu_crtc->frame_event_list, + struct dpu_crtc_frame_event, list); + if (fevent) + list_del_init(&fevent->list); + spin_unlock_irqrestore(&dpu_crtc->spin_lock, flags); + + if (!fevent) { + DRM_ERROR_RATELIMITED("crtc%d event %d overflow\n", crtc->base.id, event); + return; + } + + fevent->event = event; + fevent->crtc = crtc; + fevent->ts = ktime_get(); + kthread_queue_work(priv->event_thread[crtc_id].worker, &fevent->work); +} + +void dpu_crtc_complete_commit(struct drm_crtc *crtc) +{ + trace_dpu_crtc_complete_commit(DRMID(crtc)); + _dpu_crtc_complete_flip(crtc); +} + +static void _dpu_crtc_setup_lm_bounds(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct dpu_crtc_state *cstate = to_dpu_crtc_state(state); + struct drm_display_mode *adj_mode = &state->adjusted_mode; + u32 crtc_split_width = adj_mode->hdisplay / cstate->num_mixers; + int i; + + for (i = 0; i < cstate->num_mixers; i++) { + struct drm_rect *r = &cstate->lm_bounds[i]; + r->x1 = crtc_split_width * i; + r->y1 = 0; + r->x2 = r->x1 + crtc_split_width; + r->y2 = adj_mode->vdisplay; + + trace_dpu_crtc_setup_lm_bounds(DRMID(crtc), i, r); + } +} + +static void _dpu_crtc_get_pcc_coeff(struct drm_crtc_state *state, + struct dpu_hw_pcc_cfg *cfg) +{ + struct drm_color_ctm *ctm; + + memset(cfg, 0, sizeof(struct dpu_hw_pcc_cfg)); + + ctm = (struct drm_color_ctm *)state->ctm->data; + + if (!ctm) + return; + + cfg->r.r = CONVERT_S3_15(ctm->matrix[0]); + cfg->g.r = CONVERT_S3_15(ctm->matrix[1]); + cfg->b.r = CONVERT_S3_15(ctm->matrix[2]); + + cfg->r.g = CONVERT_S3_15(ctm->matrix[3]); + cfg->g.g = CONVERT_S3_15(ctm->matrix[4]); + cfg->b.g = CONVERT_S3_15(ctm->matrix[5]); + + cfg->r.b = CONVERT_S3_15(ctm->matrix[6]); + cfg->g.b = CONVERT_S3_15(ctm->matrix[7]); + cfg->b.b = CONVERT_S3_15(ctm->matrix[8]); +} + +static void _dpu_crtc_setup_cp_blocks(struct drm_crtc *crtc) +{ + struct drm_crtc_state *state = crtc->state; + struct dpu_crtc_state *cstate = to_dpu_crtc_state(crtc->state); + struct dpu_crtc_mixer *mixer = cstate->mixers; + struct dpu_hw_pcc_cfg cfg; + struct dpu_hw_ctl *ctl; + struct dpu_hw_dspp *dspp; + int i; + + + if (!state->color_mgmt_changed) + return; + + for (i = 0; i < cstate->num_mixers; i++) { + ctl = mixer[i].lm_ctl; + dspp = mixer[i].hw_dspp; + + if (!dspp || !dspp->ops.setup_pcc) + continue; + + if (!state->ctm) { + dspp->ops.setup_pcc(dspp, NULL); + } else { + _dpu_crtc_get_pcc_coeff(state, &cfg); + dspp->ops.setup_pcc(dspp, &cfg); + } + + mixer[i].flush_mask |= ctl->ops.get_bitmask_dspp(ctl, + mixer[i].hw_dspp->idx); + + /* stage config flush mask */ + ctl->ops.update_pending_flush(ctl, mixer[i].flush_mask); + + DPU_DEBUG("lm %d, ctl %d, flush mask 0x%x\n", + mixer[i].hw_lm->idx - DSPP_0, + ctl->idx - CTL_0, + mixer[i].flush_mask); + } +} + +static void dpu_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct dpu_crtc_state *cstate = to_dpu_crtc_state(crtc->state); + struct drm_encoder *encoder; + + if (!crtc->state->enable) { + DPU_DEBUG("crtc%d -> enable %d, skip atomic_begin\n", + crtc->base.id, crtc->state->enable); + return; + } + + DPU_DEBUG("crtc%d\n", crtc->base.id); + + _dpu_crtc_setup_lm_bounds(crtc, crtc->state); + + /* encoder will trigger pending mask now */ + drm_for_each_encoder_mask(encoder, crtc->dev, crtc->state->encoder_mask) + dpu_encoder_trigger_kickoff_pending(encoder); + + /* + * If no mixers have been allocated in dpu_crtc_atomic_check(), + * it means we are trying to flush a CRTC whose state is disabled: + * nothing else needs to be done. + */ + if (unlikely(!cstate->num_mixers)) + return; + + _dpu_crtc_blend_setup(crtc); + + _dpu_crtc_setup_cp_blocks(crtc); + + /* + * PP_DONE irq is only used by command mode for now. + * It is better to request pending before FLUSH and START trigger + * to make sure no pp_done irq missed. + * This is safe because no pp_done will happen before SW trigger + * in command mode. + */ +} + +static void dpu_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct dpu_crtc *dpu_crtc; + struct drm_device *dev; + struct drm_plane *plane; + struct msm_drm_private *priv; + unsigned long flags; + struct dpu_crtc_state *cstate; + + if (!crtc->state->enable) { + DPU_DEBUG("crtc%d -> enable %d, skip atomic_flush\n", + crtc->base.id, crtc->state->enable); + return; + } + + DPU_DEBUG("crtc%d\n", crtc->base.id); + + dpu_crtc = to_dpu_crtc(crtc); + cstate = to_dpu_crtc_state(crtc->state); + dev = crtc->dev; + priv = dev->dev_private; + + if (crtc->index >= ARRAY_SIZE(priv->event_thread)) { + DPU_ERROR("invalid crtc index[%d]\n", crtc->index); + return; + } + + WARN_ON(dpu_crtc->event); + spin_lock_irqsave(&dev->event_lock, flags); + dpu_crtc->event = crtc->state->event; + crtc->state->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + + /* + * If no mixers has been allocated in dpu_crtc_atomic_check(), + * it means we are trying to flush a CRTC whose state is disabled: + * nothing else needs to be done. + */ + if (unlikely(!cstate->num_mixers)) + return; + + /* + * For planes without commit update, drm framework will not add + * those planes to current state since hardware update is not + * required. However, if those planes were power collapsed since + * last commit cycle, driver has to restore the hardware state + * of those planes explicitly here prior to plane flush. + */ + drm_atomic_crtc_for_each_plane(plane, crtc) + dpu_plane_restore(plane); + + /* update performance setting before crtc kickoff */ + dpu_core_perf_crtc_update(crtc, 1, false); + + /* + * Final plane updates: Give each plane a chance to complete all + * required writes/flushing before crtc's "flush + * everything" call below. + */ + drm_atomic_crtc_for_each_plane(plane, crtc) { + if (dpu_crtc->smmu_state.transition_error) + dpu_plane_set_error(plane, true); + dpu_plane_flush(plane); + } + + /* Kickoff will be scheduled by outer layer */ +} + +/** + * dpu_crtc_destroy_state - state destroy hook + * @crtc: drm CRTC + * @state: CRTC state object to release + */ +static void dpu_crtc_destroy_state(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct dpu_crtc_state *cstate = to_dpu_crtc_state(state); + + DPU_DEBUG("crtc%d\n", crtc->base.id); + + __drm_atomic_helper_crtc_destroy_state(state); + + kfree(cstate); +} + +static int _dpu_crtc_wait_for_frame_done(struct drm_crtc *crtc) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + int ret, rc = 0; + + if (!atomic_read(&dpu_crtc->frame_pending)) { + DPU_DEBUG("no frames pending\n"); + return 0; + } + + DPU_ATRACE_BEGIN("frame done completion wait"); + ret = wait_for_completion_timeout(&dpu_crtc->frame_done_comp, + msecs_to_jiffies(DPU_CRTC_FRAME_DONE_TIMEOUT_MS)); + if (!ret) { + DRM_ERROR("frame done wait timed out, ret:%d\n", ret); + rc = -ETIMEDOUT; + } + DPU_ATRACE_END("frame done completion wait"); + + return rc; +} + +void dpu_crtc_commit_kickoff(struct drm_crtc *crtc) +{ + struct drm_encoder *encoder; + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct dpu_kms *dpu_kms = _dpu_crtc_get_kms(crtc); + struct dpu_crtc_state *cstate = to_dpu_crtc_state(crtc->state); + + /* + * If no mixers has been allocated in dpu_crtc_atomic_check(), + * it means we are trying to start a CRTC whose state is disabled: + * nothing else needs to be done. + */ + if (unlikely(!cstate->num_mixers)) + return; + + DPU_ATRACE_BEGIN("crtc_commit"); + + /* + * Encoder will flush/start now, unless it has a tx pending. If so, it + * may delay and flush at an irq event (e.g. ppdone) + */ + drm_for_each_encoder_mask(encoder, crtc->dev, + crtc->state->encoder_mask) + dpu_encoder_prepare_for_kickoff(encoder); + + if (atomic_inc_return(&dpu_crtc->frame_pending) == 1) { + /* acquire bandwidth and other resources */ + DPU_DEBUG("crtc%d first commit\n", crtc->base.id); + } else + DPU_DEBUG("crtc%d commit\n", crtc->base.id); + + dpu_crtc->play_count++; + + dpu_vbif_clear_errors(dpu_kms); + + drm_for_each_encoder_mask(encoder, crtc->dev, crtc->state->encoder_mask) + dpu_encoder_kickoff(encoder); + + reinit_completion(&dpu_crtc->frame_done_comp); + DPU_ATRACE_END("crtc_commit"); +} + +static void dpu_crtc_reset(struct drm_crtc *crtc) +{ + struct dpu_crtc_state *cstate = kzalloc(sizeof(*cstate), GFP_KERNEL); + + if (crtc->state) + dpu_crtc_destroy_state(crtc, crtc->state); + + if (cstate) + __drm_atomic_helper_crtc_reset(crtc, &cstate->base); + else + __drm_atomic_helper_crtc_reset(crtc, NULL); +} + +/** + * dpu_crtc_duplicate_state - state duplicate hook + * @crtc: Pointer to drm crtc structure + */ +static struct drm_crtc_state *dpu_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct dpu_crtc_state *cstate, *old_cstate = to_dpu_crtc_state(crtc->state); + + cstate = kmemdup(old_cstate, sizeof(*old_cstate), GFP_KERNEL); + if (!cstate) { + DPU_ERROR("failed to allocate state\n"); + return NULL; + } + + /* duplicate base helper */ + __drm_atomic_helper_crtc_duplicate_state(crtc, &cstate->base); + + return &cstate->base; +} + +static void dpu_crtc_disable(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct dpu_crtc_state *cstate = to_dpu_crtc_state(crtc->state); + struct drm_encoder *encoder; + unsigned long flags; + bool release_bandwidth = false; + + DRM_DEBUG_KMS("crtc%d\n", crtc->base.id); + + /* Disable/save vblank irq handling */ + drm_crtc_vblank_off(crtc); + + drm_for_each_encoder_mask(encoder, crtc->dev, + old_crtc_state->encoder_mask) { + /* in video mode, we hold an extra bandwidth reference + * as we cannot drop bandwidth at frame-done if any + * crtc is being used in video mode. + */ + if (dpu_encoder_get_intf_mode(encoder) == INTF_MODE_VIDEO) + release_bandwidth = true; + dpu_encoder_assign_crtc(encoder, NULL); + } + + /* wait for frame_event_done completion */ + if (_dpu_crtc_wait_for_frame_done(crtc)) + DPU_ERROR("crtc%d wait for frame done failed;frame_pending%d\n", + crtc->base.id, + atomic_read(&dpu_crtc->frame_pending)); + + trace_dpu_crtc_disable(DRMID(crtc), false, dpu_crtc); + dpu_crtc->enabled = false; + + if (atomic_read(&dpu_crtc->frame_pending)) { + trace_dpu_crtc_disable_frame_pending(DRMID(crtc), + atomic_read(&dpu_crtc->frame_pending)); + if (release_bandwidth) + dpu_core_perf_crtc_release_bw(crtc); + atomic_set(&dpu_crtc->frame_pending, 0); + } + + dpu_core_perf_crtc_update(crtc, 0, true); + + drm_for_each_encoder_mask(encoder, crtc->dev, crtc->state->encoder_mask) + dpu_encoder_register_frame_event_callback(encoder, NULL, NULL); + + memset(cstate->mixers, 0, sizeof(cstate->mixers)); + cstate->num_mixers = 0; + + /* disable clk & bw control until clk & bw properties are set */ + cstate->bw_control = false; + cstate->bw_split_vote = false; + + if (crtc->state->event && !crtc->state->active) { + spin_lock_irqsave(&crtc->dev->event_lock, flags); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + spin_unlock_irqrestore(&crtc->dev->event_lock, flags); + } + + pm_runtime_put_sync(crtc->dev->dev); +} + +static void dpu_crtc_enable(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct drm_encoder *encoder; + bool request_bandwidth = false; + + pm_runtime_get_sync(crtc->dev->dev); + + DRM_DEBUG_KMS("crtc%d\n", crtc->base.id); + + drm_for_each_encoder_mask(encoder, crtc->dev, crtc->state->encoder_mask) { + /* in video mode, we hold an extra bandwidth reference + * as we cannot drop bandwidth at frame-done if any + * crtc is being used in video mode. + */ + if (dpu_encoder_get_intf_mode(encoder) == INTF_MODE_VIDEO) + request_bandwidth = true; + dpu_encoder_register_frame_event_callback(encoder, + dpu_crtc_frame_event_cb, (void *)crtc); + } + + if (request_bandwidth) + atomic_inc(&_dpu_crtc_get_kms(crtc)->bandwidth_ref); + + trace_dpu_crtc_enable(DRMID(crtc), true, dpu_crtc); + dpu_crtc->enabled = true; + + drm_for_each_encoder_mask(encoder, crtc->dev, crtc->state->encoder_mask) + dpu_encoder_assign_crtc(encoder, crtc); + + /* Enable/restore vblank irq handling */ + drm_crtc_vblank_on(crtc); +} + +struct plane_state { + struct dpu_plane_state *dpu_pstate; + const struct drm_plane_state *drm_pstate; + int stage; + u32 pipe_id; +}; + +static int dpu_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct dpu_crtc_state *cstate = to_dpu_crtc_state(state); + struct plane_state *pstates; + + const struct drm_plane_state *pstate; + struct drm_plane *plane; + struct drm_display_mode *mode; + + int cnt = 0, rc = 0, mixer_width = 0, i, z_pos; + + struct dpu_multirect_plane_states multirect_plane[DPU_STAGE_MAX * 2]; + int multirect_count = 0; + const struct drm_plane_state *pipe_staged[SSPP_MAX]; + int left_zpos_cnt = 0, right_zpos_cnt = 0; + struct drm_rect crtc_rect = { 0 }; + + pstates = kzalloc(sizeof(*pstates) * DPU_STAGE_MAX * 4, GFP_KERNEL); + if (!pstates) + return -ENOMEM; + + if (!state->enable || !state->active) { + DPU_DEBUG("crtc%d -> enable %d, active %d, skip atomic_check\n", + crtc->base.id, state->enable, state->active); + goto end; + } + + mode = &state->adjusted_mode; + DPU_DEBUG("%s: check", dpu_crtc->name); + + /* force a full mode set if active state changed */ + if (state->active_changed) + state->mode_changed = true; + + memset(pipe_staged, 0, sizeof(pipe_staged)); + + if (cstate->num_mixers) { + mixer_width = mode->hdisplay / cstate->num_mixers; + + _dpu_crtc_setup_lm_bounds(crtc, state); + } + + crtc_rect.x2 = mode->hdisplay; + crtc_rect.y2 = mode->vdisplay; + + /* get plane state for all drm planes associated with crtc state */ + drm_atomic_crtc_state_for_each_plane_state(plane, pstate, state) { + struct drm_rect dst, clip = crtc_rect; + + if (IS_ERR_OR_NULL(pstate)) { + rc = PTR_ERR(pstate); + DPU_ERROR("%s: failed to get plane%d state, %d\n", + dpu_crtc->name, plane->base.id, rc); + goto end; + } + if (cnt >= DPU_STAGE_MAX * 4) + continue; + + pstates[cnt].dpu_pstate = to_dpu_plane_state(pstate); + pstates[cnt].drm_pstate = pstate; + pstates[cnt].stage = pstate->normalized_zpos; + pstates[cnt].pipe_id = dpu_plane_pipe(plane); + + if (pipe_staged[pstates[cnt].pipe_id]) { + multirect_plane[multirect_count].r0 = + pipe_staged[pstates[cnt].pipe_id]; + multirect_plane[multirect_count].r1 = pstate; + multirect_count++; + + pipe_staged[pstates[cnt].pipe_id] = NULL; + } else { + pipe_staged[pstates[cnt].pipe_id] = pstate; + } + + cnt++; + + dst = drm_plane_state_dest(pstate); + if (!drm_rect_intersect(&clip, &dst)) { + DPU_ERROR("invalid vertical/horizontal destination\n"); + DPU_ERROR("display: " DRM_RECT_FMT " plane: " + DRM_RECT_FMT "\n", DRM_RECT_ARG(&crtc_rect), + DRM_RECT_ARG(&dst)); + rc = -E2BIG; + goto end; + } + } + + for (i = 1; i < SSPP_MAX; i++) { + if (pipe_staged[i]) { + dpu_plane_clear_multirect(pipe_staged[i]); + + if (is_dpu_plane_virtual(pipe_staged[i]->plane)) { + DPU_ERROR( + "r1 only virt plane:%d not supported\n", + pipe_staged[i]->plane->base.id); + rc = -EINVAL; + goto end; + } + } + } + + z_pos = -1; + for (i = 0; i < cnt; i++) { + /* reset counts at every new blend stage */ + if (pstates[i].stage != z_pos) { + left_zpos_cnt = 0; + right_zpos_cnt = 0; + z_pos = pstates[i].stage; + } + + /* verify z_pos setting before using it */ + if (z_pos >= DPU_STAGE_MAX - DPU_STAGE_0) { + DPU_ERROR("> %d plane stages assigned\n", + DPU_STAGE_MAX - DPU_STAGE_0); + rc = -EINVAL; + goto end; + } else if (pstates[i].drm_pstate->crtc_x < mixer_width) { + if (left_zpos_cnt == 2) { + DPU_ERROR("> 2 planes @ stage %d on left\n", + z_pos); + rc = -EINVAL; + goto end; + } + left_zpos_cnt++; + + } else { + if (right_zpos_cnt == 2) { + DPU_ERROR("> 2 planes @ stage %d on right\n", + z_pos); + rc = -EINVAL; + goto end; + } + right_zpos_cnt++; + } + + pstates[i].dpu_pstate->stage = z_pos + DPU_STAGE_0; + DPU_DEBUG("%s: zpos %d", dpu_crtc->name, z_pos); + } + + for (i = 0; i < multirect_count; i++) { + if (dpu_plane_validate_multirect_v2(&multirect_plane[i])) { + DPU_ERROR( + "multirect validation failed for planes (%d - %d)\n", + multirect_plane[i].r0->plane->base.id, + multirect_plane[i].r1->plane->base.id); + rc = -EINVAL; + goto end; + } + } + + atomic_inc(&_dpu_crtc_get_kms(crtc)->bandwidth_ref); + + rc = dpu_core_perf_crtc_check(crtc, state); + if (rc) { + DPU_ERROR("crtc%d failed performance check %d\n", + crtc->base.id, rc); + goto end; + } + + /* validate source split: + * use pstates sorted by stage to check planes on same stage + * we assume that all pipes are in source split so its valid to compare + * without taking into account left/right mixer placement + */ + for (i = 1; i < cnt; i++) { + struct plane_state *prv_pstate, *cur_pstate; + struct drm_rect left_rect, right_rect; + int32_t left_pid, right_pid; + int32_t stage; + + prv_pstate = &pstates[i - 1]; + cur_pstate = &pstates[i]; + if (prv_pstate->stage != cur_pstate->stage) + continue; + + stage = cur_pstate->stage; + + left_pid = prv_pstate->dpu_pstate->base.plane->base.id; + left_rect = drm_plane_state_dest(prv_pstate->drm_pstate); + + right_pid = cur_pstate->dpu_pstate->base.plane->base.id; + right_rect = drm_plane_state_dest(cur_pstate->drm_pstate); + + if (right_rect.x1 < left_rect.x1) { + swap(left_pid, right_pid); + swap(left_rect, right_rect); + } + + /** + * - planes are enumerated in pipe-priority order such that + * planes with lower drm_id must be left-most in a shared + * blend-stage when using source split. + * - planes in source split must be contiguous in width + * - planes in source split must have same dest yoff and height + */ + if (right_pid < left_pid) { + DPU_ERROR( + "invalid src split cfg. priority mismatch. stage: %d left: %d right: %d\n", + stage, left_pid, right_pid); + rc = -EINVAL; + goto end; + } else if (right_rect.x1 != drm_rect_width(&left_rect)) { + DPU_ERROR("non-contiguous coordinates for src split. " + "stage: %d left: " DRM_RECT_FMT " right: " + DRM_RECT_FMT "\n", stage, + DRM_RECT_ARG(&left_rect), + DRM_RECT_ARG(&right_rect)); + rc = -EINVAL; + goto end; + } else if (left_rect.y1 != right_rect.y1 || + drm_rect_height(&left_rect) != drm_rect_height(&right_rect)) { + DPU_ERROR("source split at stage: %d. invalid " + "yoff/height: left: " DRM_RECT_FMT " right: " + DRM_RECT_FMT "\n", stage, + DRM_RECT_ARG(&left_rect), + DRM_RECT_ARG(&right_rect)); + rc = -EINVAL; + goto end; + } + } + +end: + kfree(pstates); + return rc; +} + +int dpu_crtc_vblank(struct drm_crtc *crtc, bool en) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + struct drm_encoder *enc; + + trace_dpu_crtc_vblank(DRMID(&dpu_crtc->base), en, dpu_crtc); + + /* + * Normally we would iterate through encoder_mask in crtc state to find + * attached encoders. In this case, we might be disabling vblank _after_ + * encoder_mask has been cleared. + * + * Instead, we "assign" a crtc to the encoder in enable and clear it in + * disable (which is also after encoder_mask is cleared). So instead of + * using encoder mask, we'll ask the encoder to toggle itself iff it's + * currently assigned to our crtc. + * + * Note also that this function cannot be called while crtc is disabled + * since we use drm_crtc_vblank_on/off. So we don't need to worry + * about the assigned crtcs being inconsistent with the current state + * (which means no need to worry about modeset locks). + */ + list_for_each_entry(enc, &crtc->dev->mode_config.encoder_list, head) { + trace_dpu_crtc_vblank_enable(DRMID(crtc), DRMID(enc), en, + dpu_crtc); + + dpu_encoder_toggle_vblank_for_crtc(enc, crtc, en); + } + + return 0; +} + +#ifdef CONFIG_DEBUG_FS +static int _dpu_debugfs_status_show(struct seq_file *s, void *data) +{ + struct dpu_crtc *dpu_crtc; + struct dpu_plane_state *pstate = NULL; + struct dpu_crtc_mixer *m; + + struct drm_crtc *crtc; + struct drm_plane *plane; + struct drm_display_mode *mode; + struct drm_framebuffer *fb; + struct drm_plane_state *state; + struct dpu_crtc_state *cstate; + + int i, out_width; + + dpu_crtc = s->private; + crtc = &dpu_crtc->base; + + drm_modeset_lock_all(crtc->dev); + cstate = to_dpu_crtc_state(crtc->state); + + mode = &crtc->state->adjusted_mode; + out_width = mode->hdisplay / cstate->num_mixers; + + seq_printf(s, "crtc:%d width:%d height:%d\n", crtc->base.id, + mode->hdisplay, mode->vdisplay); + + seq_puts(s, "\n"); + + for (i = 0; i < cstate->num_mixers; ++i) { + m = &cstate->mixers[i]; + seq_printf(s, "\tmixer:%d ctl:%d width:%d height:%d\n", + m->hw_lm->idx - LM_0, m->lm_ctl->idx - CTL_0, + out_width, mode->vdisplay); + } + + seq_puts(s, "\n"); + + drm_atomic_crtc_for_each_plane(plane, crtc) { + pstate = to_dpu_plane_state(plane->state); + state = plane->state; + + if (!pstate || !state) + continue; + + seq_printf(s, "\tplane:%u stage:%d\n", plane->base.id, + pstate->stage); + + if (plane->state->fb) { + fb = plane->state->fb; + + seq_printf(s, "\tfb:%d image format:%4.4s wxh:%ux%u ", + fb->base.id, (char *) &fb->format->format, + fb->width, fb->height); + for (i = 0; i < ARRAY_SIZE(fb->format->cpp); ++i) + seq_printf(s, "cpp[%d]:%u ", + i, fb->format->cpp[i]); + seq_puts(s, "\n\t"); + + seq_printf(s, "modifier:%8llu ", fb->modifier); + seq_puts(s, "\n"); + + seq_puts(s, "\t"); + for (i = 0; i < ARRAY_SIZE(fb->pitches); i++) + seq_printf(s, "pitches[%d]:%8u ", i, + fb->pitches[i]); + seq_puts(s, "\n"); + + seq_puts(s, "\t"); + for (i = 0; i < ARRAY_SIZE(fb->offsets); i++) + seq_printf(s, "offsets[%d]:%8u ", i, + fb->offsets[i]); + seq_puts(s, "\n"); + } + + seq_printf(s, "\tsrc_x:%4d src_y:%4d src_w:%4d src_h:%4d\n", + state->src_x, state->src_y, state->src_w, state->src_h); + + seq_printf(s, "\tdst x:%4d dst_y:%4d dst_w:%4d dst_h:%4d\n", + state->crtc_x, state->crtc_y, state->crtc_w, + state->crtc_h); + seq_printf(s, "\tmultirect: mode: %d index: %d\n", + pstate->multirect_mode, pstate->multirect_index); + + seq_puts(s, "\n"); + } + if (dpu_crtc->vblank_cb_count) { + ktime_t diff = ktime_sub(ktime_get(), dpu_crtc->vblank_cb_time); + s64 diff_ms = ktime_to_ms(diff); + s64 fps = diff_ms ? div_s64( + dpu_crtc->vblank_cb_count * 1000, diff_ms) : 0; + + seq_printf(s, + "vblank fps:%lld count:%u total:%llums total_framecount:%llu\n", + fps, dpu_crtc->vblank_cb_count, + ktime_to_ms(diff), dpu_crtc->play_count); + + /* reset time & count for next measurement */ + dpu_crtc->vblank_cb_count = 0; + dpu_crtc->vblank_cb_time = ktime_set(0, 0); + } + + drm_modeset_unlock_all(crtc->dev); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(_dpu_debugfs_status); + +static int dpu_crtc_debugfs_state_show(struct seq_file *s, void *v) +{ + struct drm_crtc *crtc = (struct drm_crtc *) s->private; + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + + seq_printf(s, "client type: %d\n", dpu_crtc_get_client_type(crtc)); + seq_printf(s, "intf_mode: %d\n", dpu_crtc_get_intf_mode(crtc)); + seq_printf(s, "core_clk_rate: %llu\n", + dpu_crtc->cur_perf.core_clk_rate); + seq_printf(s, "bw_ctl: %llu\n", dpu_crtc->cur_perf.bw_ctl); + seq_printf(s, "max_per_pipe_ib: %llu\n", + dpu_crtc->cur_perf.max_per_pipe_ib); + + return 0; +} +DEFINE_SHOW_ATTRIBUTE(dpu_crtc_debugfs_state); + +static int _dpu_crtc_init_debugfs(struct drm_crtc *crtc) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + + dpu_crtc->debugfs_root = debugfs_create_dir(dpu_crtc->name, + crtc->dev->primary->debugfs_root); + + debugfs_create_file("status", 0400, + dpu_crtc->debugfs_root, + dpu_crtc, &_dpu_debugfs_status_fops); + debugfs_create_file("state", 0600, + dpu_crtc->debugfs_root, + &dpu_crtc->base, + &dpu_crtc_debugfs_state_fops); + + return 0; +} +#else +static int _dpu_crtc_init_debugfs(struct drm_crtc *crtc) +{ + return 0; +} +#endif /* CONFIG_DEBUG_FS */ + +static int dpu_crtc_late_register(struct drm_crtc *crtc) +{ + return _dpu_crtc_init_debugfs(crtc); +} + +static void dpu_crtc_early_unregister(struct drm_crtc *crtc) +{ + struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc); + + debugfs_remove_recursive(dpu_crtc->debugfs_root); +} + +static const struct drm_crtc_funcs dpu_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .destroy = dpu_crtc_destroy, + .page_flip = drm_atomic_helper_page_flip, + .reset = dpu_crtc_reset, + .atomic_duplicate_state = dpu_crtc_duplicate_state, + .atomic_destroy_state = dpu_crtc_destroy_state, + .late_register = dpu_crtc_late_register, + .early_unregister = dpu_crtc_early_unregister, + .enable_vblank = msm_crtc_enable_vblank, + .disable_vblank = msm_crtc_disable_vblank, +}; + +static const struct drm_crtc_helper_funcs dpu_crtc_helper_funcs = { + .atomic_disable = dpu_crtc_disable, + .atomic_enable = dpu_crtc_enable, + .atomic_check = dpu_crtc_atomic_check, + .atomic_begin = dpu_crtc_atomic_begin, + .atomic_flush = dpu_crtc_atomic_flush, +}; + +/* initialize crtc */ +struct drm_crtc *dpu_crtc_init(struct drm_device *dev, struct drm_plane *plane, + struct drm_plane *cursor) +{ + struct msm_drm_private *priv = dev->dev_private; + struct dpu_kms *dpu_kms = to_dpu_kms(priv->kms); + struct drm_crtc *crtc = NULL; + struct dpu_crtc *dpu_crtc = NULL; + int i; + + dpu_crtc = kzalloc(sizeof(*dpu_crtc), GFP_KERNEL); + if (!dpu_crtc) + return ERR_PTR(-ENOMEM); + + crtc = &dpu_crtc->base; + crtc->dev = dev; + + spin_lock_init(&dpu_crtc->spin_lock); + atomic_set(&dpu_crtc->frame_pending, 0); + + init_completion(&dpu_crtc->frame_done_comp); + + INIT_LIST_HEAD(&dpu_crtc->frame_event_list); + + for (i = 0; i < ARRAY_SIZE(dpu_crtc->frame_events); i++) { + INIT_LIST_HEAD(&dpu_crtc->frame_events[i].list); + list_add(&dpu_crtc->frame_events[i].list, + &dpu_crtc->frame_event_list); + kthread_init_work(&dpu_crtc->frame_events[i].work, + dpu_crtc_frame_event_work); + } + + drm_crtc_init_with_planes(dev, crtc, plane, cursor, &dpu_crtc_funcs, + NULL); + + drm_crtc_helper_add(crtc, &dpu_crtc_helper_funcs); + + if (dpu_kms->catalog->dspp_count) + drm_crtc_enable_color_mgmt(crtc, 0, true, 0); + + /* save user friendly CRTC name for later */ + snprintf(dpu_crtc->name, DPU_CRTC_NAME_SIZE, "crtc%u", crtc->base.id); + + /* initialize event handling */ + spin_lock_init(&dpu_crtc->event_lock); + + DPU_DEBUG("%s: successfully initialized crtc\n", dpu_crtc->name); + return crtc; +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.h new file mode 100644 index 000000000..cec347434 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.h @@ -0,0 +1,288 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2018 The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#ifndef _DPU_CRTC_H_ +#define _DPU_CRTC_H_ + +#include <linux/kthread.h> +#include <drm/drm_crtc.h> +#include "dpu_kms.h" +#include "dpu_core_perf.h" +#include "dpu_hw_blk.h" + +#define DPU_CRTC_NAME_SIZE 12 + +/* define the maximum number of in-flight frame events */ +#define DPU_CRTC_FRAME_EVENT_SIZE 4 + +/** + * enum dpu_crtc_client_type: crtc client type + * @RT_CLIENT: RealTime client like video/cmd mode display + * voting through apps rsc + * @NRT_CLIENT: Non-RealTime client like WB display + * voting through apps rsc + */ +enum dpu_crtc_client_type { + RT_CLIENT, + NRT_CLIENT, +}; + +/** + * enum dpu_crtc_smmu_state: smmu state + * @ATTACHED: all the context banks are attached. + * @DETACHED: all the context banks are detached. + * @ATTACH_ALL_REQ: transient state of attaching context banks. + * @DETACH_ALL_REQ: transient state of detaching context banks. + */ +enum dpu_crtc_smmu_state { + ATTACHED = 0, + DETACHED, + ATTACH_ALL_REQ, + DETACH_ALL_REQ, +}; + +/** + * enum dpu_crtc_smmu_state_transition_type: state transition type + * @NONE: no pending state transitions + * @PRE_COMMIT: state transitions should be done before processing the commit + * @POST_COMMIT: state transitions to be done after processing the commit. + */ +enum dpu_crtc_smmu_state_transition_type { + NONE, + PRE_COMMIT, + POST_COMMIT +}; + +/** + * struct dpu_crtc_smmu_state_data: stores the smmu state and transition type + * @state: current state of smmu context banks + * @transition_type: transition request type + * @transition_error: whether there is error while transitioning the state + */ +struct dpu_crtc_smmu_state_data { + uint32_t state; + uint32_t transition_type; + uint32_t transition_error; +}; + +/** + * struct dpu_crtc_mixer: stores the map for each virtual pipeline in the CRTC + * @hw_lm: LM HW Driver context + * @lm_ctl: CTL Path HW driver context + * @lm_dspp: DSPP HW driver context + * @mixer_op_mode: mixer blending operation mode + * @flush_mask: mixer flush mask for ctl, mixer and pipe + */ +struct dpu_crtc_mixer { + struct dpu_hw_mixer *hw_lm; + struct dpu_hw_ctl *lm_ctl; + struct dpu_hw_dspp *hw_dspp; + u32 mixer_op_mode; + u32 flush_mask; +}; + +/** + * struct dpu_crtc_frame_event: stores crtc frame event for crtc processing + * @work: base work structure + * @crtc: Pointer to crtc handling this event + * @list: event list + * @ts: timestamp at queue entry + * @event: event identifier + */ +struct dpu_crtc_frame_event { + struct kthread_work work; + struct drm_crtc *crtc; + struct list_head list; + ktime_t ts; + u32 event; +}; + +/* + * Maximum number of free event structures to cache + */ +#define DPU_CRTC_MAX_EVENT_COUNT 16 + +/** + * struct dpu_crtc - virtualized CRTC data structure + * @base : Base drm crtc structure + * @name : ASCII description of this crtc + * @event : Pointer to last received drm vblank event. If there is a + * pending vblank event, this will be non-null. + * @vsync_count : Running count of received vsync events + * @drm_requested_vblank : Whether vblanks have been enabled in the encoder + * @property_info : Opaque structure for generic property support + * @property_defaults : Array of default values for generic property support + * @stage_cfg : H/w mixer stage configuration + * @debugfs_root : Parent of debugfs node + * @vblank_cb_count : count of vblank callback since last reset + * @play_count : frame count between crtc enable and disable + * @vblank_cb_time : ktime at vblank count reset + * @enabled : whether the DPU CRTC is currently enabled. updated in the + * commit-thread, not state-swap time which is earlier, so + * safe to make decisions on during VBLANK on/off work + * @feature_list : list of color processing features supported on a crtc + * @active_list : list of color processing features are active + * @dirty_list : list of color processing features are dirty + * @ad_dirty: list containing ad properties that are dirty + * @ad_active: list containing ad properties that are active + * @frame_pending : Whether or not an update is pending + * @frame_events : static allocation of in-flight frame events + * @frame_event_list : available frame event list + * @spin_lock : spin lock for frame event, transaction status, etc... + * @frame_done_comp : for frame_event_done synchronization + * @event_thread : Pointer to event handler thread + * @event_worker : Event worker queue + * @event_lock : Spinlock around event handling code + * @phandle: Pointer to power handler + * @cur_perf : current performance committed to clock/bandwidth driver + */ +struct dpu_crtc { + struct drm_crtc base; + char name[DPU_CRTC_NAME_SIZE]; + + struct drm_pending_vblank_event *event; + u32 vsync_count; + + struct dpu_hw_stage_cfg stage_cfg; + struct dentry *debugfs_root; + + u32 vblank_cb_count; + u64 play_count; + ktime_t vblank_cb_time; + bool enabled; + + struct list_head feature_list; + struct list_head active_list; + struct list_head dirty_list; + struct list_head ad_dirty; + struct list_head ad_active; + + atomic_t frame_pending; + struct dpu_crtc_frame_event frame_events[DPU_CRTC_FRAME_EVENT_SIZE]; + struct list_head frame_event_list; + spinlock_t spin_lock; + struct completion frame_done_comp; + + /* for handling internal event thread */ + spinlock_t event_lock; + + struct dpu_core_perf_params cur_perf; + + struct dpu_crtc_smmu_state_data smmu_state; +}; + +#define to_dpu_crtc(x) container_of(x, struct dpu_crtc, base) + +/** + * struct dpu_crtc_state - dpu container for atomic crtc state + * @base: Base drm crtc state structure + * @bw_control : true if bw/clk controlled by core bw/clk properties + * @bw_split_vote : true if bw controlled by llcc/dram bw properties + * @lm_bounds : LM boundaries based on current mode full resolution, no ROI. + * Origin top left of CRTC. + * @property_state: Local storage for msm_prop properties + * @property_values: Current crtc property values + * @input_fence_timeout_ns : Cached input fence timeout, in ns + * @new_perf: new performance state being requested + * @num_mixers : Number of mixers in use + * @mixers : List of active mixers + * @num_ctls : Number of ctl paths in use + * @hw_ctls : List of active ctl paths + */ +struct dpu_crtc_state { + struct drm_crtc_state base; + + bool bw_control; + bool bw_split_vote; + struct drm_rect lm_bounds[CRTC_DUAL_MIXERS]; + + uint64_t input_fence_timeout_ns; + + struct dpu_core_perf_params new_perf; + + /* HW Resources reserved for the crtc */ + u32 num_mixers; + struct dpu_crtc_mixer mixers[CRTC_DUAL_MIXERS]; + + u32 num_ctls; + struct dpu_hw_ctl *hw_ctls[CRTC_DUAL_MIXERS]; +}; + +#define to_dpu_crtc_state(x) \ + container_of(x, struct dpu_crtc_state, base) + +/** + * dpu_crtc_frame_pending - retun the number of pending frames + * @crtc: Pointer to drm crtc object + */ +static inline int dpu_crtc_frame_pending(struct drm_crtc *crtc) +{ + return crtc ? atomic_read(&to_dpu_crtc(crtc)->frame_pending) : -EINVAL; +} + +/** + * dpu_crtc_vblank - enable or disable vblanks for this crtc + * @crtc: Pointer to drm crtc object + * @en: true to enable vblanks, false to disable + */ +int dpu_crtc_vblank(struct drm_crtc *crtc, bool en); + +/** + * dpu_crtc_vblank_callback - called on vblank irq, issues completion events + * @crtc: Pointer to drm crtc object + */ +void dpu_crtc_vblank_callback(struct drm_crtc *crtc); + +/** + * dpu_crtc_commit_kickoff - trigger kickoff of the commit for this crtc + * @crtc: Pointer to drm crtc object + */ +void dpu_crtc_commit_kickoff(struct drm_crtc *crtc); + +/** + * dpu_crtc_complete_commit - callback signalling completion of current commit + * @crtc: Pointer to drm crtc object + */ +void dpu_crtc_complete_commit(struct drm_crtc *crtc); + +/** + * dpu_crtc_init - create a new crtc object + * @dev: dpu device + * @plane: base plane + * @cursor: cursor plane + * @Return: new crtc object or error + */ +struct drm_crtc *dpu_crtc_init(struct drm_device *dev, struct drm_plane *plane, + struct drm_plane *cursor); + +/** + * dpu_crtc_register_custom_event - api for enabling/disabling crtc event + * @kms: Pointer to dpu_kms + * @crtc_drm: Pointer to crtc object + * @event: Event that client is interested + * @en: Flag to enable/disable the event + */ +int dpu_crtc_register_custom_event(struct dpu_kms *kms, + struct drm_crtc *crtc_drm, u32 event, bool en); + +/** + * dpu_crtc_get_intf_mode - get interface mode of the given crtc + * @crtc: Pointert to crtc + */ +enum dpu_intf_mode dpu_crtc_get_intf_mode(struct drm_crtc *crtc); + +/** + * dpu_crtc_get_client_type - check the crtc type- rt, nrt etc. + * @crtc: Pointer to crtc + */ +static inline enum dpu_crtc_client_type dpu_crtc_get_client_type( + struct drm_crtc *crtc) +{ + return crtc && crtc->state ? RT_CLIENT : NRT_CLIENT; +} + +#endif /* _DPU_CRTC_H_ */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c new file mode 100644 index 000000000..408fc6c8a --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.c @@ -0,0 +1,2298 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ +#include <linux/debugfs.h> +#include <linux/kthread.h> +#include <linux/seq_file.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_file.h> +#include <drm/drm_probe_helper.h> + +#include "msm_drv.h" +#include "dpu_kms.h" +#include "dpu_hwio.h" +#include "dpu_hw_catalog.h" +#include "dpu_hw_intf.h" +#include "dpu_hw_ctl.h" +#include "dpu_hw_dspp.h" +#include "dpu_formats.h" +#include "dpu_encoder_phys.h" +#include "dpu_crtc.h" +#include "dpu_trace.h" +#include "dpu_core_irq.h" + +#define DPU_DEBUG_ENC(e, fmt, ...) DPU_DEBUG("enc%d " fmt,\ + (e) ? (e)->base.base.id : -1, ##__VA_ARGS__) + +#define DPU_ERROR_ENC(e, fmt, ...) DPU_ERROR("enc%d " fmt,\ + (e) ? (e)->base.base.id : -1, ##__VA_ARGS__) + +#define DPU_DEBUG_PHYS(p, fmt, ...) DPU_DEBUG("enc%d intf%d pp%d " fmt,\ + (p) ? (p)->parent->base.id : -1, \ + (p) ? (p)->intf_idx - INTF_0 : -1, \ + (p) ? ((p)->hw_pp ? (p)->hw_pp->idx - PINGPONG_0 : -1) : -1, \ + ##__VA_ARGS__) + +#define DPU_ERROR_PHYS(p, fmt, ...) DPU_ERROR("enc%d intf%d pp%d " fmt,\ + (p) ? (p)->parent->base.id : -1, \ + (p) ? (p)->intf_idx - INTF_0 : -1, \ + (p) ? ((p)->hw_pp ? (p)->hw_pp->idx - PINGPONG_0 : -1) : -1, \ + ##__VA_ARGS__) + +/* + * Two to anticipate panels that can do cmd/vid dynamic switching + * plan is to create all possible physical encoder types, and switch between + * them at runtime + */ +#define NUM_PHYS_ENCODER_TYPES 2 + +#define MAX_PHYS_ENCODERS_PER_VIRTUAL \ + (MAX_H_TILES_PER_DISPLAY * NUM_PHYS_ENCODER_TYPES) + +#define MAX_CHANNELS_PER_ENC 2 + +#define IDLE_SHORT_TIMEOUT 1 + +#define MAX_HDISPLAY_SPLIT 1080 + +/* timeout in frames waiting for frame done */ +#define DPU_ENCODER_FRAME_DONE_TIMEOUT_FRAMES 5 + +/** + * enum dpu_enc_rc_events - events for resource control state machine + * @DPU_ENC_RC_EVENT_KICKOFF: + * This event happens at NORMAL priority. + * Event that signals the start of the transfer. When this event is + * received, enable MDP/DSI core clocks. Regardless of the previous + * state, the resource should be in ON state at the end of this event. + * @DPU_ENC_RC_EVENT_FRAME_DONE: + * This event happens at INTERRUPT level. + * Event signals the end of the data transfer after the PP FRAME_DONE + * event. At the end of this event, a delayed work is scheduled to go to + * IDLE_PC state after IDLE_TIMEOUT time. + * @DPU_ENC_RC_EVENT_PRE_STOP: + * This event happens at NORMAL priority. + * This event, when received during the ON state, leave the RC STATE + * in the PRE_OFF state. It should be followed by the STOP event as + * part of encoder disable. + * If received during IDLE or OFF states, it will do nothing. + * @DPU_ENC_RC_EVENT_STOP: + * This event happens at NORMAL priority. + * When this event is received, disable all the MDP/DSI core clocks, and + * disable IRQs. It should be called from the PRE_OFF or IDLE states. + * IDLE is expected when IDLE_PC has run, and PRE_OFF did nothing. + * PRE_OFF is expected when PRE_STOP was executed during the ON state. + * Resource state should be in OFF at the end of the event. + * @DPU_ENC_RC_EVENT_ENTER_IDLE: + * This event happens at NORMAL priority from a work item. + * Event signals that there were no frame updates for IDLE_TIMEOUT time. + * This would disable MDP/DSI core clocks and change the resource state + * to IDLE. + */ +enum dpu_enc_rc_events { + DPU_ENC_RC_EVENT_KICKOFF = 1, + DPU_ENC_RC_EVENT_FRAME_DONE, + DPU_ENC_RC_EVENT_PRE_STOP, + DPU_ENC_RC_EVENT_STOP, + DPU_ENC_RC_EVENT_ENTER_IDLE +}; + +/* + * enum dpu_enc_rc_states - states that the resource control maintains + * @DPU_ENC_RC_STATE_OFF: Resource is in OFF state + * @DPU_ENC_RC_STATE_PRE_OFF: Resource is transitioning to OFF state + * @DPU_ENC_RC_STATE_ON: Resource is in ON state + * @DPU_ENC_RC_STATE_MODESET: Resource is in modeset state + * @DPU_ENC_RC_STATE_IDLE: Resource is in IDLE state + */ +enum dpu_enc_rc_states { + DPU_ENC_RC_STATE_OFF, + DPU_ENC_RC_STATE_PRE_OFF, + DPU_ENC_RC_STATE_ON, + DPU_ENC_RC_STATE_IDLE +}; + +/** + * struct dpu_encoder_virt - virtual encoder. Container of one or more physical + * encoders. Virtual encoder manages one "logical" display. Physical + * encoders manage one intf block, tied to a specific panel/sub-panel. + * Virtual encoder defers as much as possible to the physical encoders. + * Virtual encoder registers itself with the DRM Framework as the encoder. + * @base: drm_encoder base class for registration with DRM + * @enc_spinlock: Virtual-Encoder-Wide Spin Lock for IRQ purposes + * @bus_scaling_client: Client handle to the bus scaling interface + * @enabled: True if the encoder is active, protected by enc_lock + * @num_phys_encs: Actual number of physical encoders contained. + * @phys_encs: Container of physical encoders managed. + * @cur_master: Pointer to the current master in this mode. Optimization + * Only valid after enable. Cleared as disable. + * @hw_pp Handle to the pingpong blocks used for the display. No. + * pingpong blocks can be different than num_phys_encs. + * @intfs_swapped Whether or not the phys_enc interfaces have been swapped + * for partial update right-only cases, such as pingpong + * split where virtual pingpong does not generate IRQs + * @crtc: Pointer to the currently assigned crtc. Normally you + * would use crtc->state->encoder_mask to determine the + * link between encoder/crtc. However in this case we need + * to track crtc in the disable() hook which is called + * _after_ encoder_mask is cleared. + * @crtc_kickoff_cb: Callback into CRTC that will flush & start + * all CTL paths + * @crtc_kickoff_cb_data: Opaque user data given to crtc_kickoff_cb + * @debugfs_root: Debug file system root file node + * @enc_lock: Lock around physical encoder + * create/destroy/enable/disable + * @frame_busy_mask: Bitmask tracking which phys_enc we are still + * busy processing current command. + * Bit0 = phys_encs[0] etc. + * @crtc_frame_event_cb: callback handler for frame event + * @crtc_frame_event_cb_data: callback handler private data + * @frame_done_timeout_ms: frame done timeout in ms + * @frame_done_timer: watchdog timer for frame done event + * @vsync_event_timer: vsync timer + * @disp_info: local copy of msm_display_info struct + * @idle_pc_supported: indicate if idle power collaps is supported + * @rc_lock: resource control mutex lock to protect + * virt encoder over various state changes + * @rc_state: resource controller state + * @delayed_off_work: delayed worker to schedule disabling of + * clks and resources after IDLE_TIMEOUT time. + * @vsync_event_work: worker to handle vsync event for autorefresh + * @topology: topology of the display + * @idle_timeout: idle timeout duration in milliseconds + */ +struct dpu_encoder_virt { + struct drm_encoder base; + spinlock_t enc_spinlock; + uint32_t bus_scaling_client; + + bool enabled; + + unsigned int num_phys_encs; + struct dpu_encoder_phys *phys_encs[MAX_PHYS_ENCODERS_PER_VIRTUAL]; + struct dpu_encoder_phys *cur_master; + struct dpu_encoder_phys *cur_slave; + struct dpu_hw_pingpong *hw_pp[MAX_CHANNELS_PER_ENC]; + + bool intfs_swapped; + + struct drm_crtc *crtc; + + struct dentry *debugfs_root; + struct mutex enc_lock; + DECLARE_BITMAP(frame_busy_mask, MAX_PHYS_ENCODERS_PER_VIRTUAL); + void (*crtc_frame_event_cb)(void *, u32 event); + void *crtc_frame_event_cb_data; + + atomic_t frame_done_timeout_ms; + struct timer_list frame_done_timer; + struct timer_list vsync_event_timer; + + struct msm_display_info disp_info; + + bool idle_pc_supported; + struct mutex rc_lock; + enum dpu_enc_rc_states rc_state; + struct delayed_work delayed_off_work; + struct kthread_work vsync_event_work; + struct msm_display_topology topology; + + u32 idle_timeout; +}; + +#define to_dpu_encoder_virt(x) container_of(x, struct dpu_encoder_virt, base) + +static u32 dither_matrix[DITHER_MATRIX_SZ] = { + 15, 7, 13, 5, 3, 11, 1, 9, 12, 4, 14, 6, 0, 8, 2, 10 +}; + +static void _dpu_encoder_setup_dither(struct dpu_hw_pingpong *hw_pp, unsigned bpc) +{ + struct dpu_hw_dither_cfg dither_cfg = { 0 }; + + if (!hw_pp->ops.setup_dither) + return; + + switch (bpc) { + case 6: + dither_cfg.c0_bitdepth = 6; + dither_cfg.c1_bitdepth = 6; + dither_cfg.c2_bitdepth = 6; + dither_cfg.c3_bitdepth = 6; + dither_cfg.temporal_en = 0; + break; + default: + hw_pp->ops.setup_dither(hw_pp, NULL); + return; + } + + memcpy(&dither_cfg.matrix, dither_matrix, + sizeof(u32) * DITHER_MATRIX_SZ); + + hw_pp->ops.setup_dither(hw_pp, &dither_cfg); +} + +void dpu_encoder_helper_report_irq_timeout(struct dpu_encoder_phys *phys_enc, + enum dpu_intr_idx intr_idx) +{ + DRM_ERROR("irq timeout id=%u, intf=%d, pp=%d, intr=%d\n", + DRMID(phys_enc->parent), phys_enc->intf_idx - INTF_0, + phys_enc->hw_pp->idx - PINGPONG_0, intr_idx); + + if (phys_enc->parent_ops->handle_frame_done) + phys_enc->parent_ops->handle_frame_done( + phys_enc->parent, phys_enc, + DPU_ENCODER_FRAME_EVENT_ERROR); +} + +static int dpu_encoder_helper_wait_event_timeout(int32_t drm_id, + int32_t hw_id, struct dpu_encoder_wait_info *info); + +int dpu_encoder_helper_wait_for_irq(struct dpu_encoder_phys *phys_enc, + enum dpu_intr_idx intr_idx, + struct dpu_encoder_wait_info *wait_info) +{ + struct dpu_encoder_irq *irq; + u32 irq_status; + int ret; + + if (!wait_info || intr_idx >= INTR_IDX_MAX) { + DPU_ERROR("invalid params\n"); + return -EINVAL; + } + irq = &phys_enc->irq[intr_idx]; + + /* note: do master / slave checking outside */ + + /* return EWOULDBLOCK since we know the wait isn't necessary */ + if (phys_enc->enable_state == DPU_ENC_DISABLED) { + DRM_ERROR("encoder is disabled id=%u, intr=%d, hw=%d, irq=%d", + DRMID(phys_enc->parent), intr_idx, irq->hw_idx, + irq->irq_idx); + return -EWOULDBLOCK; + } + + if (irq->irq_idx < 0) { + DRM_DEBUG_KMS("skip irq wait id=%u, intr=%d, hw=%d, irq=%s", + DRMID(phys_enc->parent), intr_idx, irq->hw_idx, + irq->name); + return 0; + } + + DRM_DEBUG_KMS("id=%u, intr=%d, hw=%d, irq=%d, pp=%d, pending_cnt=%d", + DRMID(phys_enc->parent), intr_idx, irq->hw_idx, + irq->irq_idx, phys_enc->hw_pp->idx - PINGPONG_0, + atomic_read(wait_info->atomic_cnt)); + + ret = dpu_encoder_helper_wait_event_timeout( + DRMID(phys_enc->parent), + irq->hw_idx, + wait_info); + + if (ret <= 0) { + irq_status = dpu_core_irq_read(phys_enc->dpu_kms, + irq->irq_idx, true); + if (irq_status) { + unsigned long flags; + + DRM_DEBUG_KMS("irq not triggered id=%u, intr=%d, " + "hw=%d, irq=%d, pp=%d, atomic_cnt=%d", + DRMID(phys_enc->parent), intr_idx, + irq->hw_idx, irq->irq_idx, + phys_enc->hw_pp->idx - PINGPONG_0, + atomic_read(wait_info->atomic_cnt)); + local_irq_save(flags); + irq->cb.func(phys_enc, irq->irq_idx); + local_irq_restore(flags); + ret = 0; + } else { + ret = -ETIMEDOUT; + DRM_DEBUG_KMS("irq timeout id=%u, intr=%d, " + "hw=%d, irq=%d, pp=%d, atomic_cnt=%d", + DRMID(phys_enc->parent), intr_idx, + irq->hw_idx, irq->irq_idx, + phys_enc->hw_pp->idx - PINGPONG_0, + atomic_read(wait_info->atomic_cnt)); + } + } else { + ret = 0; + trace_dpu_enc_irq_wait_success(DRMID(phys_enc->parent), + intr_idx, irq->hw_idx, irq->irq_idx, + phys_enc->hw_pp->idx - PINGPONG_0, + atomic_read(wait_info->atomic_cnt)); + } + + return ret; +} + +int dpu_encoder_helper_register_irq(struct dpu_encoder_phys *phys_enc, + enum dpu_intr_idx intr_idx) +{ + struct dpu_encoder_irq *irq; + int ret = 0; + + if (intr_idx >= INTR_IDX_MAX) { + DPU_ERROR("invalid params\n"); + return -EINVAL; + } + irq = &phys_enc->irq[intr_idx]; + + if (irq->irq_idx >= 0) { + DPU_DEBUG_PHYS(phys_enc, + "skipping already registered irq %s type %d\n", + irq->name, irq->intr_type); + return 0; + } + + irq->irq_idx = dpu_core_irq_idx_lookup(phys_enc->dpu_kms, + irq->intr_type, irq->hw_idx); + if (irq->irq_idx < 0) { + DPU_ERROR_PHYS(phys_enc, + "failed to lookup IRQ index for %s type:%d\n", + irq->name, irq->intr_type); + return -EINVAL; + } + + ret = dpu_core_irq_register_callback(phys_enc->dpu_kms, irq->irq_idx, + &irq->cb); + if (ret) { + DPU_ERROR_PHYS(phys_enc, + "failed to register IRQ callback for %s\n", + irq->name); + irq->irq_idx = -EINVAL; + return ret; + } + + ret = dpu_core_irq_enable(phys_enc->dpu_kms, &irq->irq_idx, 1); + if (ret) { + DRM_ERROR("enable failed id=%u, intr=%d, hw=%d, irq=%d", + DRMID(phys_enc->parent), intr_idx, irq->hw_idx, + irq->irq_idx); + dpu_core_irq_unregister_callback(phys_enc->dpu_kms, + irq->irq_idx, &irq->cb); + irq->irq_idx = -EINVAL; + return ret; + } + + trace_dpu_enc_irq_register_success(DRMID(phys_enc->parent), intr_idx, + irq->hw_idx, irq->irq_idx); + + return ret; +} + +int dpu_encoder_helper_unregister_irq(struct dpu_encoder_phys *phys_enc, + enum dpu_intr_idx intr_idx) +{ + struct dpu_encoder_irq *irq; + int ret; + + irq = &phys_enc->irq[intr_idx]; + + /* silently skip irqs that weren't registered */ + if (irq->irq_idx < 0) { + DRM_ERROR("duplicate unregister id=%u, intr=%d, hw=%d, irq=%d", + DRMID(phys_enc->parent), intr_idx, irq->hw_idx, + irq->irq_idx); + return 0; + } + + ret = dpu_core_irq_disable(phys_enc->dpu_kms, &irq->irq_idx, 1); + if (ret) { + DRM_ERROR("disable failed id=%u, intr=%d, hw=%d, irq=%d ret=%d", + DRMID(phys_enc->parent), intr_idx, irq->hw_idx, + irq->irq_idx, ret); + } + + ret = dpu_core_irq_unregister_callback(phys_enc->dpu_kms, irq->irq_idx, + &irq->cb); + if (ret) { + DRM_ERROR("unreg cb fail id=%u, intr=%d, hw=%d, irq=%d ret=%d", + DRMID(phys_enc->parent), intr_idx, irq->hw_idx, + irq->irq_idx, ret); + } + + trace_dpu_enc_irq_unregister_success(DRMID(phys_enc->parent), intr_idx, + irq->hw_idx, irq->irq_idx); + + irq->irq_idx = -EINVAL; + + return 0; +} + +void dpu_encoder_get_hw_resources(struct drm_encoder *drm_enc, + struct dpu_encoder_hw_resources *hw_res) +{ + struct dpu_encoder_virt *dpu_enc = NULL; + int i = 0; + + dpu_enc = to_dpu_encoder_virt(drm_enc); + DPU_DEBUG_ENC(dpu_enc, "\n"); + + /* Query resources used by phys encs, expected to be without overlap */ + memset(hw_res, 0, sizeof(*hw_res)); + + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + struct dpu_encoder_phys *phys = dpu_enc->phys_encs[i]; + + if (phys->ops.get_hw_resources) + phys->ops.get_hw_resources(phys, hw_res); + } +} + +static void dpu_encoder_destroy(struct drm_encoder *drm_enc) +{ + struct dpu_encoder_virt *dpu_enc = NULL; + int i = 0; + + if (!drm_enc) { + DPU_ERROR("invalid encoder\n"); + return; + } + + dpu_enc = to_dpu_encoder_virt(drm_enc); + DPU_DEBUG_ENC(dpu_enc, "\n"); + + mutex_lock(&dpu_enc->enc_lock); + + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + struct dpu_encoder_phys *phys = dpu_enc->phys_encs[i]; + + if (phys->ops.destroy) { + phys->ops.destroy(phys); + --dpu_enc->num_phys_encs; + dpu_enc->phys_encs[i] = NULL; + } + } + + if (dpu_enc->num_phys_encs) + DPU_ERROR_ENC(dpu_enc, "expected 0 num_phys_encs not %d\n", + dpu_enc->num_phys_encs); + dpu_enc->num_phys_encs = 0; + mutex_unlock(&dpu_enc->enc_lock); + + drm_encoder_cleanup(drm_enc); + mutex_destroy(&dpu_enc->enc_lock); +} + +void dpu_encoder_helper_split_config( + struct dpu_encoder_phys *phys_enc, + enum dpu_intf interface) +{ + struct dpu_encoder_virt *dpu_enc; + struct split_pipe_cfg cfg = { 0 }; + struct dpu_hw_mdp *hw_mdptop; + struct msm_display_info *disp_info; + + if (!phys_enc->hw_mdptop || !phys_enc->parent) { + DPU_ERROR("invalid arg(s), encoder %d\n", phys_enc != NULL); + return; + } + + dpu_enc = to_dpu_encoder_virt(phys_enc->parent); + hw_mdptop = phys_enc->hw_mdptop; + disp_info = &dpu_enc->disp_info; + + if (disp_info->intf_type != DRM_MODE_ENCODER_DSI) + return; + + /** + * disable split modes since encoder will be operating in as the only + * encoder, either for the entire use case in the case of, for example, + * single DSI, or for this frame in the case of left/right only partial + * update. + */ + if (phys_enc->split_role == ENC_ROLE_SOLO) { + if (hw_mdptop->ops.setup_split_pipe) + hw_mdptop->ops.setup_split_pipe(hw_mdptop, &cfg); + return; + } + + cfg.en = true; + cfg.mode = phys_enc->intf_mode; + cfg.intf = interface; + + if (cfg.en && phys_enc->ops.needs_single_flush && + phys_enc->ops.needs_single_flush(phys_enc)) + cfg.split_flush_en = true; + + if (phys_enc->split_role == ENC_ROLE_MASTER) { + DPU_DEBUG_ENC(dpu_enc, "enable %d\n", cfg.en); + + if (hw_mdptop->ops.setup_split_pipe) + hw_mdptop->ops.setup_split_pipe(hw_mdptop, &cfg); + } +} + +static struct msm_display_topology dpu_encoder_get_topology( + struct dpu_encoder_virt *dpu_enc, + struct dpu_kms *dpu_kms, + struct drm_display_mode *mode) +{ + struct msm_display_topology topology = {0}; + int i, intf_count = 0; + + for (i = 0; i < MAX_PHYS_ENCODERS_PER_VIRTUAL; i++) + if (dpu_enc->phys_encs[i]) + intf_count++; + + /* Datapath topology selection + * + * Dual display + * 2 LM, 2 INTF ( Split display using 2 interfaces) + * + * Single display + * 1 LM, 1 INTF + * 2 LM, 1 INTF (stream merge to support high resolution interfaces) + * + * Adding color blocks only to primary interface if available in + * sufficient number + */ + if (intf_count == 2) + topology.num_lm = 2; + else if (!dpu_kms->catalog->caps->has_3d_merge) + topology.num_lm = 1; + else + topology.num_lm = (mode->hdisplay > MAX_HDISPLAY_SPLIT) ? 2 : 1; + + if (dpu_enc->disp_info.intf_type == DRM_MODE_ENCODER_DSI) { + if (dpu_kms->catalog->dspp && + (dpu_kms->catalog->dspp_count >= topology.num_lm)) + topology.num_dspp = topology.num_lm; + } + + topology.num_enc = 0; + topology.num_intf = intf_count; + + return topology; +} +static int dpu_encoder_virt_atomic_check( + struct drm_encoder *drm_enc, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct dpu_encoder_virt *dpu_enc; + struct msm_drm_private *priv; + struct dpu_kms *dpu_kms; + const struct drm_display_mode *mode; + struct drm_display_mode *adj_mode; + struct msm_display_topology topology; + struct dpu_global_state *global_state; + int i = 0; + int ret = 0; + + if (!drm_enc || !crtc_state || !conn_state) { + DPU_ERROR("invalid arg(s), drm_enc %d, crtc/conn state %d/%d\n", + drm_enc != NULL, crtc_state != NULL, conn_state != NULL); + return -EINVAL; + } + + dpu_enc = to_dpu_encoder_virt(drm_enc); + DPU_DEBUG_ENC(dpu_enc, "\n"); + + priv = drm_enc->dev->dev_private; + dpu_kms = to_dpu_kms(priv->kms); + mode = &crtc_state->mode; + adj_mode = &crtc_state->adjusted_mode; + global_state = dpu_kms_get_global_state(crtc_state->state); + if (IS_ERR(global_state)) + return PTR_ERR(global_state); + + trace_dpu_enc_atomic_check(DRMID(drm_enc)); + + /* perform atomic check on the first physical encoder (master) */ + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + struct dpu_encoder_phys *phys = dpu_enc->phys_encs[i]; + + if (phys->ops.atomic_check) + ret = phys->ops.atomic_check(phys, crtc_state, + conn_state); + else if (phys->ops.mode_fixup) + if (!phys->ops.mode_fixup(phys, mode, adj_mode)) + ret = -EINVAL; + + if (ret) { + DPU_ERROR_ENC(dpu_enc, + "mode unsupported, phys idx %d\n", i); + break; + } + } + + topology = dpu_encoder_get_topology(dpu_enc, dpu_kms, adj_mode); + + /* Reserve dynamic resources now. */ + if (!ret) { + /* + * Release and Allocate resources on every modeset + * Dont allocate when active is false. + */ + if (drm_atomic_crtc_needs_modeset(crtc_state)) { + dpu_rm_release(global_state, drm_enc); + + if (!crtc_state->active_changed || crtc_state->enable) + ret = dpu_rm_reserve(&dpu_kms->rm, global_state, + drm_enc, crtc_state, topology); + } + } + + trace_dpu_enc_atomic_check_flags(DRMID(drm_enc), adj_mode->flags); + + return ret; +} + +static void _dpu_encoder_update_vsync_source(struct dpu_encoder_virt *dpu_enc, + struct msm_display_info *disp_info) +{ + struct dpu_vsync_source_cfg vsync_cfg = { 0 }; + struct msm_drm_private *priv; + struct dpu_kms *dpu_kms; + struct dpu_hw_mdp *hw_mdptop; + struct drm_encoder *drm_enc; + int i; + + if (!dpu_enc || !disp_info) { + DPU_ERROR("invalid param dpu_enc:%d or disp_info:%d\n", + dpu_enc != NULL, disp_info != NULL); + return; + } else if (dpu_enc->num_phys_encs > ARRAY_SIZE(dpu_enc->hw_pp)) { + DPU_ERROR("invalid num phys enc %d/%d\n", + dpu_enc->num_phys_encs, + (int) ARRAY_SIZE(dpu_enc->hw_pp)); + return; + } + + drm_enc = &dpu_enc->base; + /* this pointers are checked in virt_enable_helper */ + priv = drm_enc->dev->dev_private; + + dpu_kms = to_dpu_kms(priv->kms); + hw_mdptop = dpu_kms->hw_mdp; + if (!hw_mdptop) { + DPU_ERROR("invalid mdptop\n"); + return; + } + + if (hw_mdptop->ops.setup_vsync_source && + disp_info->capabilities & MSM_DISPLAY_CAP_CMD_MODE) { + for (i = 0; i < dpu_enc->num_phys_encs; i++) + vsync_cfg.ppnumber[i] = dpu_enc->hw_pp[i]->idx; + + vsync_cfg.pp_count = dpu_enc->num_phys_encs; + if (disp_info->is_te_using_watchdog_timer) + vsync_cfg.vsync_source = DPU_VSYNC_SOURCE_WD_TIMER_0; + else + vsync_cfg.vsync_source = DPU_VSYNC0_SOURCE_GPIO; + + hw_mdptop->ops.setup_vsync_source(hw_mdptop, &vsync_cfg); + } +} + +static void _dpu_encoder_irq_control(struct drm_encoder *drm_enc, bool enable) +{ + struct dpu_encoder_virt *dpu_enc; + int i; + + if (!drm_enc) { + DPU_ERROR("invalid encoder\n"); + return; + } + + dpu_enc = to_dpu_encoder_virt(drm_enc); + + DPU_DEBUG_ENC(dpu_enc, "enable:%d\n", enable); + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + struct dpu_encoder_phys *phys = dpu_enc->phys_encs[i]; + + if (phys->ops.irq_control) + phys->ops.irq_control(phys, enable); + } + +} + +static void _dpu_encoder_resource_control_helper(struct drm_encoder *drm_enc, + bool enable) +{ + struct msm_drm_private *priv; + struct dpu_kms *dpu_kms; + struct dpu_encoder_virt *dpu_enc; + + dpu_enc = to_dpu_encoder_virt(drm_enc); + priv = drm_enc->dev->dev_private; + dpu_kms = to_dpu_kms(priv->kms); + + trace_dpu_enc_rc_helper(DRMID(drm_enc), enable); + + if (!dpu_enc->cur_master) { + DPU_ERROR("encoder master not set\n"); + return; + } + + if (enable) { + /* enable DPU core clks */ + pm_runtime_get_sync(&dpu_kms->pdev->dev); + + /* enable all the irq */ + _dpu_encoder_irq_control(drm_enc, true); + + } else { + /* disable all the irq */ + _dpu_encoder_irq_control(drm_enc, false); + + /* disable DPU core clks */ + pm_runtime_put_sync(&dpu_kms->pdev->dev); + } + +} + +static int dpu_encoder_resource_control(struct drm_encoder *drm_enc, + u32 sw_event) +{ + struct dpu_encoder_virt *dpu_enc; + struct msm_drm_private *priv; + bool is_vid_mode = false; + + if (!drm_enc || !drm_enc->dev || !drm_enc->crtc) { + DPU_ERROR("invalid parameters\n"); + return -EINVAL; + } + dpu_enc = to_dpu_encoder_virt(drm_enc); + priv = drm_enc->dev->dev_private; + is_vid_mode = dpu_enc->disp_info.capabilities & + MSM_DISPLAY_CAP_VID_MODE; + + /* + * when idle_pc is not supported, process only KICKOFF, STOP and MODESET + * events and return early for other events (ie wb display). + */ + if (!dpu_enc->idle_pc_supported && + (sw_event != DPU_ENC_RC_EVENT_KICKOFF && + sw_event != DPU_ENC_RC_EVENT_STOP && + sw_event != DPU_ENC_RC_EVENT_PRE_STOP)) + return 0; + + trace_dpu_enc_rc(DRMID(drm_enc), sw_event, dpu_enc->idle_pc_supported, + dpu_enc->rc_state, "begin"); + + switch (sw_event) { + case DPU_ENC_RC_EVENT_KICKOFF: + /* cancel delayed off work, if any */ + if (cancel_delayed_work_sync(&dpu_enc->delayed_off_work)) + DPU_DEBUG_ENC(dpu_enc, "sw_event:%d, work cancelled\n", + sw_event); + + mutex_lock(&dpu_enc->rc_lock); + + /* return if the resource control is already in ON state */ + if (dpu_enc->rc_state == DPU_ENC_RC_STATE_ON) { + DRM_DEBUG_KMS("id;%u, sw_event:%d, rc in ON state\n", + DRMID(drm_enc), sw_event); + mutex_unlock(&dpu_enc->rc_lock); + return 0; + } else if (dpu_enc->rc_state != DPU_ENC_RC_STATE_OFF && + dpu_enc->rc_state != DPU_ENC_RC_STATE_IDLE) { + DRM_DEBUG_KMS("id;%u, sw_event:%d, rc in state %d\n", + DRMID(drm_enc), sw_event, + dpu_enc->rc_state); + mutex_unlock(&dpu_enc->rc_lock); + return -EINVAL; + } + + if (is_vid_mode && dpu_enc->rc_state == DPU_ENC_RC_STATE_IDLE) + _dpu_encoder_irq_control(drm_enc, true); + else + _dpu_encoder_resource_control_helper(drm_enc, true); + + dpu_enc->rc_state = DPU_ENC_RC_STATE_ON; + + trace_dpu_enc_rc(DRMID(drm_enc), sw_event, + dpu_enc->idle_pc_supported, dpu_enc->rc_state, + "kickoff"); + + mutex_unlock(&dpu_enc->rc_lock); + break; + + case DPU_ENC_RC_EVENT_FRAME_DONE: + /* + * mutex lock is not used as this event happens at interrupt + * context. And locking is not required as, the other events + * like KICKOFF and STOP does a wait-for-idle before executing + * the resource_control + */ + if (dpu_enc->rc_state != DPU_ENC_RC_STATE_ON) { + DRM_DEBUG_KMS("id:%d, sw_event:%d,rc:%d-unexpected\n", + DRMID(drm_enc), sw_event, + dpu_enc->rc_state); + return -EINVAL; + } + + /* + * schedule off work item only when there are no + * frames pending + */ + if (dpu_crtc_frame_pending(drm_enc->crtc) > 1) { + DRM_DEBUG_KMS("id:%d skip schedule work\n", + DRMID(drm_enc)); + return 0; + } + + queue_delayed_work(priv->wq, &dpu_enc->delayed_off_work, + msecs_to_jiffies(dpu_enc->idle_timeout)); + + trace_dpu_enc_rc(DRMID(drm_enc), sw_event, + dpu_enc->idle_pc_supported, dpu_enc->rc_state, + "frame done"); + break; + + case DPU_ENC_RC_EVENT_PRE_STOP: + /* cancel delayed off work, if any */ + if (cancel_delayed_work_sync(&dpu_enc->delayed_off_work)) + DPU_DEBUG_ENC(dpu_enc, "sw_event:%d, work cancelled\n", + sw_event); + + mutex_lock(&dpu_enc->rc_lock); + + if (is_vid_mode && + dpu_enc->rc_state == DPU_ENC_RC_STATE_IDLE) { + _dpu_encoder_irq_control(drm_enc, true); + } + /* skip if is already OFF or IDLE, resources are off already */ + else if (dpu_enc->rc_state == DPU_ENC_RC_STATE_OFF || + dpu_enc->rc_state == DPU_ENC_RC_STATE_IDLE) { + DRM_DEBUG_KMS("id:%u, sw_event:%d, rc in %d state\n", + DRMID(drm_enc), sw_event, + dpu_enc->rc_state); + mutex_unlock(&dpu_enc->rc_lock); + return 0; + } + + dpu_enc->rc_state = DPU_ENC_RC_STATE_PRE_OFF; + + trace_dpu_enc_rc(DRMID(drm_enc), sw_event, + dpu_enc->idle_pc_supported, dpu_enc->rc_state, + "pre stop"); + + mutex_unlock(&dpu_enc->rc_lock); + break; + + case DPU_ENC_RC_EVENT_STOP: + mutex_lock(&dpu_enc->rc_lock); + + /* return if the resource control is already in OFF state */ + if (dpu_enc->rc_state == DPU_ENC_RC_STATE_OFF) { + DRM_DEBUG_KMS("id: %u, sw_event:%d, rc in OFF state\n", + DRMID(drm_enc), sw_event); + mutex_unlock(&dpu_enc->rc_lock); + return 0; + } else if (dpu_enc->rc_state == DPU_ENC_RC_STATE_ON) { + DRM_ERROR("id: %u, sw_event:%d, rc in state %d\n", + DRMID(drm_enc), sw_event, dpu_enc->rc_state); + mutex_unlock(&dpu_enc->rc_lock); + return -EINVAL; + } + + /** + * expect to arrive here only if in either idle state or pre-off + * and in IDLE state the resources are already disabled + */ + if (dpu_enc->rc_state == DPU_ENC_RC_STATE_PRE_OFF) + _dpu_encoder_resource_control_helper(drm_enc, false); + + dpu_enc->rc_state = DPU_ENC_RC_STATE_OFF; + + trace_dpu_enc_rc(DRMID(drm_enc), sw_event, + dpu_enc->idle_pc_supported, dpu_enc->rc_state, + "stop"); + + mutex_unlock(&dpu_enc->rc_lock); + break; + + case DPU_ENC_RC_EVENT_ENTER_IDLE: + mutex_lock(&dpu_enc->rc_lock); + + if (dpu_enc->rc_state != DPU_ENC_RC_STATE_ON) { + DRM_ERROR("id: %u, sw_event:%d, rc:%d !ON state\n", + DRMID(drm_enc), sw_event, dpu_enc->rc_state); + mutex_unlock(&dpu_enc->rc_lock); + return 0; + } + + /* + * if we are in ON but a frame was just kicked off, + * ignore the IDLE event, it's probably a stale timer event + */ + if (dpu_enc->frame_busy_mask[0]) { + DRM_ERROR("id:%u, sw_event:%d, rc:%d frame pending\n", + DRMID(drm_enc), sw_event, dpu_enc->rc_state); + mutex_unlock(&dpu_enc->rc_lock); + return 0; + } + + if (is_vid_mode) + _dpu_encoder_irq_control(drm_enc, false); + else + _dpu_encoder_resource_control_helper(drm_enc, false); + + dpu_enc->rc_state = DPU_ENC_RC_STATE_IDLE; + + trace_dpu_enc_rc(DRMID(drm_enc), sw_event, + dpu_enc->idle_pc_supported, dpu_enc->rc_state, + "idle"); + + mutex_unlock(&dpu_enc->rc_lock); + break; + + default: + DRM_ERROR("id:%u, unexpected sw_event: %d\n", DRMID(drm_enc), + sw_event); + trace_dpu_enc_rc(DRMID(drm_enc), sw_event, + dpu_enc->idle_pc_supported, dpu_enc->rc_state, + "error"); + break; + } + + trace_dpu_enc_rc(DRMID(drm_enc), sw_event, + dpu_enc->idle_pc_supported, dpu_enc->rc_state, + "end"); + return 0; +} + +static void dpu_encoder_virt_mode_set(struct drm_encoder *drm_enc, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct dpu_encoder_virt *dpu_enc; + struct msm_drm_private *priv; + struct dpu_kms *dpu_kms; + struct list_head *connector_list; + struct drm_connector *conn = NULL, *conn_iter; + struct drm_crtc *drm_crtc; + struct dpu_crtc_state *cstate; + struct dpu_global_state *global_state; + struct msm_display_topology topology; + struct dpu_hw_blk *hw_pp[MAX_CHANNELS_PER_ENC]; + struct dpu_hw_blk *hw_ctl[MAX_CHANNELS_PER_ENC]; + struct dpu_hw_blk *hw_lm[MAX_CHANNELS_PER_ENC]; + struct dpu_hw_blk *hw_dspp[MAX_CHANNELS_PER_ENC] = { NULL }; + int num_lm, num_ctl, num_pp, num_dspp; + int i, j; + + if (!drm_enc) { + DPU_ERROR("invalid encoder\n"); + return; + } + + dpu_enc = to_dpu_encoder_virt(drm_enc); + DPU_DEBUG_ENC(dpu_enc, "\n"); + + priv = drm_enc->dev->dev_private; + dpu_kms = to_dpu_kms(priv->kms); + connector_list = &dpu_kms->dev->mode_config.connector_list; + + global_state = dpu_kms_get_existing_global_state(dpu_kms); + if (IS_ERR_OR_NULL(global_state)) { + DPU_ERROR("Failed to get global state"); + return; + } + + trace_dpu_enc_mode_set(DRMID(drm_enc)); + + if (drm_enc->encoder_type == DRM_MODE_ENCODER_TMDS && priv->dp) + msm_dp_display_mode_set(priv->dp, drm_enc, mode, adj_mode); + + list_for_each_entry(conn_iter, connector_list, head) + if (conn_iter->encoder == drm_enc) + conn = conn_iter; + + if (!conn) { + DPU_ERROR_ENC(dpu_enc, "failed to find attached connector\n"); + return; + } else if (!conn->state) { + DPU_ERROR_ENC(dpu_enc, "invalid connector state\n"); + return; + } + + drm_for_each_crtc(drm_crtc, drm_enc->dev) + if (drm_crtc->state->encoder_mask & drm_encoder_mask(drm_enc)) + break; + + topology = dpu_encoder_get_topology(dpu_enc, dpu_kms, adj_mode); + + /* Query resource that have been reserved in atomic check step. */ + num_pp = dpu_rm_get_assigned_resources(&dpu_kms->rm, global_state, + drm_enc->base.id, DPU_HW_BLK_PINGPONG, hw_pp, + ARRAY_SIZE(hw_pp)); + num_ctl = dpu_rm_get_assigned_resources(&dpu_kms->rm, global_state, + drm_enc->base.id, DPU_HW_BLK_CTL, hw_ctl, ARRAY_SIZE(hw_ctl)); + num_lm = dpu_rm_get_assigned_resources(&dpu_kms->rm, global_state, + drm_enc->base.id, DPU_HW_BLK_LM, hw_lm, ARRAY_SIZE(hw_lm)); + num_dspp = dpu_rm_get_assigned_resources(&dpu_kms->rm, global_state, + drm_enc->base.id, DPU_HW_BLK_DSPP, hw_dspp, + ARRAY_SIZE(hw_dspp)); + + for (i = 0; i < MAX_CHANNELS_PER_ENC; i++) + dpu_enc->hw_pp[i] = i < num_pp ? to_dpu_hw_pingpong(hw_pp[i]) + : NULL; + + cstate = to_dpu_crtc_state(drm_crtc->state); + + for (i = 0; i < num_lm; i++) { + int ctl_idx = (i < num_ctl) ? i : (num_ctl-1); + + cstate->mixers[i].hw_lm = to_dpu_hw_mixer(hw_lm[i]); + cstate->mixers[i].lm_ctl = to_dpu_hw_ctl(hw_ctl[ctl_idx]); + cstate->mixers[i].hw_dspp = to_dpu_hw_dspp(hw_dspp[i]); + } + + cstate->num_mixers = num_lm; + + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + int num_blk; + struct dpu_hw_blk *hw_blk[MAX_CHANNELS_PER_ENC]; + struct dpu_encoder_phys *phys = dpu_enc->phys_encs[i]; + + if (!dpu_enc->hw_pp[i]) { + DPU_ERROR_ENC(dpu_enc, + "no pp block assigned at idx: %d\n", i); + return; + } + + if (!hw_ctl[i]) { + DPU_ERROR_ENC(dpu_enc, + "no ctl block assigned at idx: %d\n", i); + return; + } + + phys->hw_pp = dpu_enc->hw_pp[i]; + phys->hw_ctl = to_dpu_hw_ctl(hw_ctl[i]); + + num_blk = dpu_rm_get_assigned_resources(&dpu_kms->rm, + global_state, drm_enc->base.id, DPU_HW_BLK_INTF, + hw_blk, ARRAY_SIZE(hw_blk)); + for (j = 0; j < num_blk; j++) { + struct dpu_hw_intf *hw_intf; + + hw_intf = to_dpu_hw_intf(hw_blk[i]); + if (hw_intf->idx == phys->intf_idx) + phys->hw_intf = hw_intf; + } + + if (!phys->hw_intf) { + DPU_ERROR_ENC(dpu_enc, + "no intf block assigned at idx: %d\n", i); + return; + } + + phys->connector = conn->state->connector; + if (phys->ops.mode_set) + phys->ops.mode_set(phys, mode, adj_mode); + } +} + +static void _dpu_encoder_virt_enable_helper(struct drm_encoder *drm_enc) +{ + struct dpu_encoder_virt *dpu_enc = NULL; + struct msm_drm_private *priv; + int i; + + if (!drm_enc || !drm_enc->dev) { + DPU_ERROR("invalid parameters\n"); + return; + } + + priv = drm_enc->dev->dev_private; + + dpu_enc = to_dpu_encoder_virt(drm_enc); + if (!dpu_enc || !dpu_enc->cur_master) { + DPU_ERROR("invalid dpu encoder/master\n"); + return; + } + + + if (dpu_enc->disp_info.intf_type == DRM_MODE_ENCODER_TMDS && + dpu_enc->cur_master->hw_mdptop && + dpu_enc->cur_master->hw_mdptop->ops.intf_audio_select) + dpu_enc->cur_master->hw_mdptop->ops.intf_audio_select( + dpu_enc->cur_master->hw_mdptop); + + _dpu_encoder_update_vsync_source(dpu_enc, &dpu_enc->disp_info); + + if (dpu_enc->disp_info.intf_type == DRM_MODE_ENCODER_DSI && + !WARN_ON(dpu_enc->num_phys_encs == 0)) { + unsigned bpc = dpu_enc->phys_encs[0]->connector->display_info.bpc; + for (i = 0; i < MAX_CHANNELS_PER_ENC; i++) { + if (!dpu_enc->hw_pp[i]) + continue; + _dpu_encoder_setup_dither(dpu_enc->hw_pp[i], bpc); + } + } +} + +void dpu_encoder_virt_runtime_resume(struct drm_encoder *drm_enc) +{ + struct dpu_encoder_virt *dpu_enc = to_dpu_encoder_virt(drm_enc); + + mutex_lock(&dpu_enc->enc_lock); + + if (!dpu_enc->enabled) + goto out; + + if (dpu_enc->cur_slave && dpu_enc->cur_slave->ops.restore) + dpu_enc->cur_slave->ops.restore(dpu_enc->cur_slave); + if (dpu_enc->cur_master && dpu_enc->cur_master->ops.restore) + dpu_enc->cur_master->ops.restore(dpu_enc->cur_master); + + _dpu_encoder_virt_enable_helper(drm_enc); + +out: + mutex_unlock(&dpu_enc->enc_lock); +} + +static void dpu_encoder_virt_enable(struct drm_encoder *drm_enc) +{ + struct dpu_encoder_virt *dpu_enc = NULL; + int ret = 0; + struct msm_drm_private *priv; + struct drm_display_mode *cur_mode = NULL; + + if (!drm_enc) { + DPU_ERROR("invalid encoder\n"); + return; + } + dpu_enc = to_dpu_encoder_virt(drm_enc); + + mutex_lock(&dpu_enc->enc_lock); + cur_mode = &dpu_enc->base.crtc->state->adjusted_mode; + priv = drm_enc->dev->dev_private; + + trace_dpu_enc_enable(DRMID(drm_enc), cur_mode->hdisplay, + cur_mode->vdisplay); + + /* always enable slave encoder before master */ + if (dpu_enc->cur_slave && dpu_enc->cur_slave->ops.enable) + dpu_enc->cur_slave->ops.enable(dpu_enc->cur_slave); + + if (dpu_enc->cur_master && dpu_enc->cur_master->ops.enable) + dpu_enc->cur_master->ops.enable(dpu_enc->cur_master); + + ret = dpu_encoder_resource_control(drm_enc, DPU_ENC_RC_EVENT_KICKOFF); + if (ret) { + DPU_ERROR_ENC(dpu_enc, "dpu resource control failed: %d\n", + ret); + goto out; + } + + _dpu_encoder_virt_enable_helper(drm_enc); + + if (drm_enc->encoder_type == DRM_MODE_ENCODER_TMDS && priv->dp) { + ret = msm_dp_display_enable(priv->dp, + drm_enc); + if (ret) { + DPU_ERROR_ENC(dpu_enc, "dp display enable failed: %d\n", + ret); + goto out; + } + } + dpu_enc->enabled = true; + +out: + mutex_unlock(&dpu_enc->enc_lock); +} + +static void dpu_encoder_virt_disable(struct drm_encoder *drm_enc) +{ + struct dpu_encoder_virt *dpu_enc = NULL; + struct msm_drm_private *priv; + struct dpu_kms *dpu_kms; + int i = 0; + + if (!drm_enc) { + DPU_ERROR("invalid encoder\n"); + return; + } else if (!drm_enc->dev) { + DPU_ERROR("invalid dev\n"); + return; + } + + dpu_enc = to_dpu_encoder_virt(drm_enc); + DPU_DEBUG_ENC(dpu_enc, "\n"); + + mutex_lock(&dpu_enc->enc_lock); + dpu_enc->enabled = false; + + priv = drm_enc->dev->dev_private; + dpu_kms = to_dpu_kms(priv->kms); + + trace_dpu_enc_disable(DRMID(drm_enc)); + + /* wait for idle */ + dpu_encoder_wait_for_event(drm_enc, MSM_ENC_TX_COMPLETE); + + if (drm_enc->encoder_type == DRM_MODE_ENCODER_TMDS && priv->dp) { + if (msm_dp_display_pre_disable(priv->dp, drm_enc)) + DPU_ERROR_ENC(dpu_enc, "dp display push idle failed\n"); + } + + dpu_encoder_resource_control(drm_enc, DPU_ENC_RC_EVENT_PRE_STOP); + + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + struct dpu_encoder_phys *phys = dpu_enc->phys_encs[i]; + + if (phys->ops.disable) + phys->ops.disable(phys); + } + + + /* after phys waits for frame-done, should be no more frames pending */ + if (atomic_xchg(&dpu_enc->frame_done_timeout_ms, 0)) { + DPU_ERROR("enc%d timeout pending\n", drm_enc->base.id); + del_timer_sync(&dpu_enc->frame_done_timer); + } + + dpu_encoder_resource_control(drm_enc, DPU_ENC_RC_EVENT_STOP); + + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + dpu_enc->phys_encs[i]->connector = NULL; + } + + DPU_DEBUG_ENC(dpu_enc, "encoder disabled\n"); + + if (drm_enc->encoder_type == DRM_MODE_ENCODER_TMDS && priv->dp) { + if (msm_dp_display_disable(priv->dp, drm_enc)) + DPU_ERROR_ENC(dpu_enc, "dp display disable failed\n"); + } + + mutex_unlock(&dpu_enc->enc_lock); +} + +static enum dpu_intf dpu_encoder_get_intf(struct dpu_mdss_cfg *catalog, + enum dpu_intf_type type, u32 controller_id) +{ + int i = 0; + + for (i = 0; i < catalog->intf_count; i++) { + if (catalog->intf[i].type == type + && catalog->intf[i].controller_id == controller_id) { + return catalog->intf[i].id; + } + } + + return INTF_MAX; +} + +static void dpu_encoder_vblank_callback(struct drm_encoder *drm_enc, + struct dpu_encoder_phys *phy_enc) +{ + struct dpu_encoder_virt *dpu_enc = NULL; + unsigned long lock_flags; + + if (!drm_enc || !phy_enc) + return; + + DPU_ATRACE_BEGIN("encoder_vblank_callback"); + dpu_enc = to_dpu_encoder_virt(drm_enc); + + spin_lock_irqsave(&dpu_enc->enc_spinlock, lock_flags); + if (dpu_enc->crtc) + dpu_crtc_vblank_callback(dpu_enc->crtc); + spin_unlock_irqrestore(&dpu_enc->enc_spinlock, lock_flags); + + atomic_inc(&phy_enc->vsync_cnt); + DPU_ATRACE_END("encoder_vblank_callback"); +} + +static void dpu_encoder_underrun_callback(struct drm_encoder *drm_enc, + struct dpu_encoder_phys *phy_enc) +{ + if (!phy_enc) + return; + + DPU_ATRACE_BEGIN("encoder_underrun_callback"); + atomic_inc(&phy_enc->underrun_cnt); + trace_dpu_enc_underrun_cb(DRMID(drm_enc), + atomic_read(&phy_enc->underrun_cnt)); + DPU_ATRACE_END("encoder_underrun_callback"); +} + +void dpu_encoder_assign_crtc(struct drm_encoder *drm_enc, struct drm_crtc *crtc) +{ + struct dpu_encoder_virt *dpu_enc = to_dpu_encoder_virt(drm_enc); + unsigned long lock_flags; + + spin_lock_irqsave(&dpu_enc->enc_spinlock, lock_flags); + /* crtc should always be cleared before re-assigning */ + WARN_ON(crtc && dpu_enc->crtc); + dpu_enc->crtc = crtc; + spin_unlock_irqrestore(&dpu_enc->enc_spinlock, lock_flags); +} + +void dpu_encoder_toggle_vblank_for_crtc(struct drm_encoder *drm_enc, + struct drm_crtc *crtc, bool enable) +{ + struct dpu_encoder_virt *dpu_enc = to_dpu_encoder_virt(drm_enc); + unsigned long lock_flags; + int i; + + trace_dpu_enc_vblank_cb(DRMID(drm_enc), enable); + + spin_lock_irqsave(&dpu_enc->enc_spinlock, lock_flags); + if (dpu_enc->crtc != crtc) { + spin_unlock_irqrestore(&dpu_enc->enc_spinlock, lock_flags); + return; + } + spin_unlock_irqrestore(&dpu_enc->enc_spinlock, lock_flags); + + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + struct dpu_encoder_phys *phys = dpu_enc->phys_encs[i]; + + if (phys->ops.control_vblank_irq) + phys->ops.control_vblank_irq(phys, enable); + } +} + +void dpu_encoder_register_frame_event_callback(struct drm_encoder *drm_enc, + void (*frame_event_cb)(void *, u32 event), + void *frame_event_cb_data) +{ + struct dpu_encoder_virt *dpu_enc = to_dpu_encoder_virt(drm_enc); + unsigned long lock_flags; + bool enable; + + enable = frame_event_cb ? true : false; + + if (!drm_enc) { + DPU_ERROR("invalid encoder\n"); + return; + } + trace_dpu_enc_frame_event_cb(DRMID(drm_enc), enable); + + spin_lock_irqsave(&dpu_enc->enc_spinlock, lock_flags); + dpu_enc->crtc_frame_event_cb = frame_event_cb; + dpu_enc->crtc_frame_event_cb_data = frame_event_cb_data; + spin_unlock_irqrestore(&dpu_enc->enc_spinlock, lock_flags); +} + +static void dpu_encoder_frame_done_callback( + struct drm_encoder *drm_enc, + struct dpu_encoder_phys *ready_phys, u32 event) +{ + struct dpu_encoder_virt *dpu_enc = to_dpu_encoder_virt(drm_enc); + unsigned int i; + + if (event & (DPU_ENCODER_FRAME_EVENT_DONE + | DPU_ENCODER_FRAME_EVENT_ERROR + | DPU_ENCODER_FRAME_EVENT_PANEL_DEAD)) { + + if (!dpu_enc->frame_busy_mask[0]) { + /** + * suppress frame_done without waiter, + * likely autorefresh + */ + trace_dpu_enc_frame_done_cb_not_busy(DRMID(drm_enc), + event, ready_phys->intf_idx); + return; + } + + /* One of the physical encoders has become idle */ + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + if (dpu_enc->phys_encs[i] == ready_phys) { + trace_dpu_enc_frame_done_cb(DRMID(drm_enc), i, + dpu_enc->frame_busy_mask[0]); + clear_bit(i, dpu_enc->frame_busy_mask); + } + } + + if (!dpu_enc->frame_busy_mask[0]) { + atomic_set(&dpu_enc->frame_done_timeout_ms, 0); + del_timer(&dpu_enc->frame_done_timer); + + dpu_encoder_resource_control(drm_enc, + DPU_ENC_RC_EVENT_FRAME_DONE); + + if (dpu_enc->crtc_frame_event_cb) + dpu_enc->crtc_frame_event_cb( + dpu_enc->crtc_frame_event_cb_data, + event); + } + } else { + if (dpu_enc->crtc_frame_event_cb) + dpu_enc->crtc_frame_event_cb( + dpu_enc->crtc_frame_event_cb_data, event); + } +} + +static void dpu_encoder_off_work(struct work_struct *work) +{ + struct dpu_encoder_virt *dpu_enc = container_of(work, + struct dpu_encoder_virt, delayed_off_work.work); + + if (!dpu_enc) { + DPU_ERROR("invalid dpu encoder\n"); + return; + } + + dpu_encoder_resource_control(&dpu_enc->base, + DPU_ENC_RC_EVENT_ENTER_IDLE); + + dpu_encoder_frame_done_callback(&dpu_enc->base, NULL, + DPU_ENCODER_FRAME_EVENT_IDLE); +} + +/** + * _dpu_encoder_trigger_flush - trigger flush for a physical encoder + * drm_enc: Pointer to drm encoder structure + * phys: Pointer to physical encoder structure + * extra_flush_bits: Additional bit mask to include in flush trigger + */ +static void _dpu_encoder_trigger_flush(struct drm_encoder *drm_enc, + struct dpu_encoder_phys *phys, uint32_t extra_flush_bits) +{ + struct dpu_hw_ctl *ctl; + int pending_kickoff_cnt; + u32 ret = UINT_MAX; + + if (!phys->hw_pp) { + DPU_ERROR("invalid pingpong hw\n"); + return; + } + + ctl = phys->hw_ctl; + if (!ctl->ops.trigger_flush) { + DPU_ERROR("missing trigger cb\n"); + return; + } + + pending_kickoff_cnt = dpu_encoder_phys_inc_pending(phys); + + if (extra_flush_bits && ctl->ops.update_pending_flush) + ctl->ops.update_pending_flush(ctl, extra_flush_bits); + + ctl->ops.trigger_flush(ctl); + + if (ctl->ops.get_pending_flush) + ret = ctl->ops.get_pending_flush(ctl); + + trace_dpu_enc_trigger_flush(DRMID(drm_enc), phys->intf_idx, + pending_kickoff_cnt, ctl->idx, + extra_flush_bits, ret); +} + +/** + * _dpu_encoder_trigger_start - trigger start for a physical encoder + * phys: Pointer to physical encoder structure + */ +static void _dpu_encoder_trigger_start(struct dpu_encoder_phys *phys) +{ + if (!phys) { + DPU_ERROR("invalid argument(s)\n"); + return; + } + + if (!phys->hw_pp) { + DPU_ERROR("invalid pingpong hw\n"); + return; + } + + if (phys->ops.trigger_start && phys->enable_state != DPU_ENC_DISABLED) + phys->ops.trigger_start(phys); +} + +void dpu_encoder_helper_trigger_start(struct dpu_encoder_phys *phys_enc) +{ + struct dpu_hw_ctl *ctl; + + ctl = phys_enc->hw_ctl; + if (ctl->ops.trigger_start) { + ctl->ops.trigger_start(ctl); + trace_dpu_enc_trigger_start(DRMID(phys_enc->parent), ctl->idx); + } +} + +static int dpu_encoder_helper_wait_event_timeout( + int32_t drm_id, + int32_t hw_id, + struct dpu_encoder_wait_info *info) +{ + int rc = 0; + s64 expected_time = ktime_to_ms(ktime_get()) + info->timeout_ms; + s64 jiffies = msecs_to_jiffies(info->timeout_ms); + s64 time; + + do { + rc = wait_event_timeout(*(info->wq), + atomic_read(info->atomic_cnt) == 0, jiffies); + time = ktime_to_ms(ktime_get()); + + trace_dpu_enc_wait_event_timeout(drm_id, hw_id, rc, time, + expected_time, + atomic_read(info->atomic_cnt)); + /* If we timed out, counter is valid and time is less, wait again */ + } while (atomic_read(info->atomic_cnt) && (rc == 0) && + (time < expected_time)); + + return rc; +} + +static void dpu_encoder_helper_hw_reset(struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_virt *dpu_enc; + struct dpu_hw_ctl *ctl; + int rc; + + dpu_enc = to_dpu_encoder_virt(phys_enc->parent); + ctl = phys_enc->hw_ctl; + + if (!ctl->ops.reset) + return; + + DRM_DEBUG_KMS("id:%u ctl %d reset\n", DRMID(phys_enc->parent), + ctl->idx); + + rc = ctl->ops.reset(ctl); + if (rc) + DPU_ERROR_ENC(dpu_enc, "ctl %d reset failure\n", ctl->idx); + + phys_enc->enable_state = DPU_ENC_ENABLED; +} + +/** + * _dpu_encoder_kickoff_phys - handle physical encoder kickoff + * Iterate through the physical encoders and perform consolidated flush + * and/or control start triggering as needed. This is done in the virtual + * encoder rather than the individual physical ones in order to handle + * use cases that require visibility into multiple physical encoders at + * a time. + * dpu_enc: Pointer to virtual encoder structure + */ +static void _dpu_encoder_kickoff_phys(struct dpu_encoder_virt *dpu_enc) +{ + struct dpu_hw_ctl *ctl; + uint32_t i, pending_flush; + unsigned long lock_flags; + + pending_flush = 0x0; + + /* update pending counts and trigger kickoff ctl flush atomically */ + spin_lock_irqsave(&dpu_enc->enc_spinlock, lock_flags); + + /* don't perform flush/start operations for slave encoders */ + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + struct dpu_encoder_phys *phys = dpu_enc->phys_encs[i]; + + if (phys->enable_state == DPU_ENC_DISABLED) + continue; + + ctl = phys->hw_ctl; + + /* + * This is cleared in frame_done worker, which isn't invoked + * for async commits. So don't set this for async, since it'll + * roll over to the next commit. + */ + if (phys->split_role != ENC_ROLE_SLAVE) + set_bit(i, dpu_enc->frame_busy_mask); + + if (!phys->ops.needs_single_flush || + !phys->ops.needs_single_flush(phys)) + _dpu_encoder_trigger_flush(&dpu_enc->base, phys, 0x0); + else if (ctl->ops.get_pending_flush) + pending_flush |= ctl->ops.get_pending_flush(ctl); + } + + /* for split flush, combine pending flush masks and send to master */ + if (pending_flush && dpu_enc->cur_master) { + _dpu_encoder_trigger_flush( + &dpu_enc->base, + dpu_enc->cur_master, + pending_flush); + } + + _dpu_encoder_trigger_start(dpu_enc->cur_master); + + spin_unlock_irqrestore(&dpu_enc->enc_spinlock, lock_flags); +} + +void dpu_encoder_trigger_kickoff_pending(struct drm_encoder *drm_enc) +{ + struct dpu_encoder_virt *dpu_enc; + struct dpu_encoder_phys *phys; + unsigned int i; + struct dpu_hw_ctl *ctl; + struct msm_display_info *disp_info; + + if (!drm_enc) { + DPU_ERROR("invalid encoder\n"); + return; + } + dpu_enc = to_dpu_encoder_virt(drm_enc); + disp_info = &dpu_enc->disp_info; + + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + phys = dpu_enc->phys_encs[i]; + + ctl = phys->hw_ctl; + if (ctl->ops.clear_pending_flush) + ctl->ops.clear_pending_flush(ctl); + + /* update only for command mode primary ctl */ + if ((phys == dpu_enc->cur_master) && + (disp_info->capabilities & MSM_DISPLAY_CAP_CMD_MODE) + && ctl->ops.trigger_pending) + ctl->ops.trigger_pending(ctl); + } +} + +static u32 _dpu_encoder_calculate_linetime(struct dpu_encoder_virt *dpu_enc, + struct drm_display_mode *mode) +{ + u64 pclk_rate; + u32 pclk_period; + u32 line_time; + + /* + * For linetime calculation, only operate on master encoder. + */ + if (!dpu_enc->cur_master) + return 0; + + if (!dpu_enc->cur_master->ops.get_line_count) { + DPU_ERROR("get_line_count function not defined\n"); + return 0; + } + + pclk_rate = mode->clock; /* pixel clock in kHz */ + if (pclk_rate == 0) { + DPU_ERROR("pclk is 0, cannot calculate line time\n"); + return 0; + } + + pclk_period = DIV_ROUND_UP_ULL(1000000000ull, pclk_rate); + if (pclk_period == 0) { + DPU_ERROR("pclk period is 0\n"); + return 0; + } + + /* + * Line time calculation based on Pixel clock and HTOTAL. + * Final unit is in ns. + */ + line_time = (pclk_period * mode->htotal) / 1000; + if (line_time == 0) { + DPU_ERROR("line time calculation is 0\n"); + return 0; + } + + DPU_DEBUG_ENC(dpu_enc, + "clk_rate=%lldkHz, clk_period=%d, linetime=%dns\n", + pclk_rate, pclk_period, line_time); + + return line_time; +} + +int dpu_encoder_vsync_time(struct drm_encoder *drm_enc, ktime_t *wakeup_time) +{ + struct drm_display_mode *mode; + struct dpu_encoder_virt *dpu_enc; + u32 cur_line; + u32 line_time; + u32 vtotal, time_to_vsync; + ktime_t cur_time; + + dpu_enc = to_dpu_encoder_virt(drm_enc); + + if (!drm_enc->crtc || !drm_enc->crtc->state) { + DPU_ERROR("crtc/crtc state object is NULL\n"); + return -EINVAL; + } + mode = &drm_enc->crtc->state->adjusted_mode; + + line_time = _dpu_encoder_calculate_linetime(dpu_enc, mode); + if (!line_time) + return -EINVAL; + + cur_line = dpu_enc->cur_master->ops.get_line_count(dpu_enc->cur_master); + + vtotal = mode->vtotal; + if (cur_line >= vtotal) + time_to_vsync = line_time * vtotal; + else + time_to_vsync = line_time * (vtotal - cur_line); + + if (time_to_vsync == 0) { + DPU_ERROR("time to vsync should not be zero, vtotal=%d\n", + vtotal); + return -EINVAL; + } + + cur_time = ktime_get(); + *wakeup_time = ktime_add_ns(cur_time, time_to_vsync); + + DPU_DEBUG_ENC(dpu_enc, + "cur_line=%u vtotal=%u time_to_vsync=%u, cur_time=%lld, wakeup_time=%lld\n", + cur_line, vtotal, time_to_vsync, + ktime_to_ms(cur_time), + ktime_to_ms(*wakeup_time)); + return 0; +} + +static void dpu_encoder_vsync_event_handler(struct timer_list *t) +{ + struct dpu_encoder_virt *dpu_enc = from_timer(dpu_enc, t, + vsync_event_timer); + struct drm_encoder *drm_enc = &dpu_enc->base; + struct msm_drm_private *priv; + struct msm_drm_thread *event_thread; + + if (!drm_enc->dev || !drm_enc->crtc) { + DPU_ERROR("invalid parameters\n"); + return; + } + + priv = drm_enc->dev->dev_private; + + if (drm_enc->crtc->index >= ARRAY_SIZE(priv->event_thread)) { + DPU_ERROR("invalid crtc index\n"); + return; + } + event_thread = &priv->event_thread[drm_enc->crtc->index]; + if (!event_thread) { + DPU_ERROR("event_thread not found for crtc:%d\n", + drm_enc->crtc->index); + return; + } + + del_timer(&dpu_enc->vsync_event_timer); +} + +static void dpu_encoder_vsync_event_work_handler(struct kthread_work *work) +{ + struct dpu_encoder_virt *dpu_enc = container_of(work, + struct dpu_encoder_virt, vsync_event_work); + ktime_t wakeup_time; + + if (!dpu_enc) { + DPU_ERROR("invalid dpu encoder\n"); + return; + } + + if (dpu_encoder_vsync_time(&dpu_enc->base, &wakeup_time)) + return; + + trace_dpu_enc_vsync_event_work(DRMID(&dpu_enc->base), wakeup_time); + mod_timer(&dpu_enc->vsync_event_timer, + nsecs_to_jiffies(ktime_to_ns(wakeup_time))); +} + +void dpu_encoder_prepare_for_kickoff(struct drm_encoder *drm_enc) +{ + struct dpu_encoder_virt *dpu_enc; + struct dpu_encoder_phys *phys; + bool needs_hw_reset = false; + unsigned int i; + + dpu_enc = to_dpu_encoder_virt(drm_enc); + + trace_dpu_enc_prepare_kickoff(DRMID(drm_enc)); + + /* prepare for next kickoff, may include waiting on previous kickoff */ + DPU_ATRACE_BEGIN("enc_prepare_for_kickoff"); + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + phys = dpu_enc->phys_encs[i]; + if (phys->ops.prepare_for_kickoff) + phys->ops.prepare_for_kickoff(phys); + if (phys->enable_state == DPU_ENC_ERR_NEEDS_HW_RESET) + needs_hw_reset = true; + } + DPU_ATRACE_END("enc_prepare_for_kickoff"); + + dpu_encoder_resource_control(drm_enc, DPU_ENC_RC_EVENT_KICKOFF); + + /* if any phys needs reset, reset all phys, in-order */ + if (needs_hw_reset) { + trace_dpu_enc_prepare_kickoff_reset(DRMID(drm_enc)); + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + dpu_encoder_helper_hw_reset(dpu_enc->phys_encs[i]); + } + } +} + +void dpu_encoder_kickoff(struct drm_encoder *drm_enc) +{ + struct dpu_encoder_virt *dpu_enc; + struct dpu_encoder_phys *phys; + ktime_t wakeup_time; + unsigned long timeout_ms; + unsigned int i; + + DPU_ATRACE_BEGIN("encoder_kickoff"); + dpu_enc = to_dpu_encoder_virt(drm_enc); + + trace_dpu_enc_kickoff(DRMID(drm_enc)); + + timeout_ms = DPU_ENCODER_FRAME_DONE_TIMEOUT_FRAMES * 1000 / + drm_mode_vrefresh(&drm_enc->crtc->state->adjusted_mode); + + atomic_set(&dpu_enc->frame_done_timeout_ms, timeout_ms); + mod_timer(&dpu_enc->frame_done_timer, + jiffies + msecs_to_jiffies(timeout_ms)); + + /* All phys encs are ready to go, trigger the kickoff */ + _dpu_encoder_kickoff_phys(dpu_enc); + + /* allow phys encs to handle any post-kickoff business */ + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + phys = dpu_enc->phys_encs[i]; + if (phys->ops.handle_post_kickoff) + phys->ops.handle_post_kickoff(phys); + } + + if (dpu_enc->disp_info.intf_type == DRM_MODE_ENCODER_DSI && + !dpu_encoder_vsync_time(drm_enc, &wakeup_time)) { + trace_dpu_enc_early_kickoff(DRMID(drm_enc), + ktime_to_ms(wakeup_time)); + mod_timer(&dpu_enc->vsync_event_timer, + nsecs_to_jiffies(ktime_to_ns(wakeup_time))); + } + + DPU_ATRACE_END("encoder_kickoff"); +} + +void dpu_encoder_prepare_commit(struct drm_encoder *drm_enc) +{ + struct dpu_encoder_virt *dpu_enc; + struct dpu_encoder_phys *phys; + int i; + + if (!drm_enc) { + DPU_ERROR("invalid encoder\n"); + return; + } + dpu_enc = to_dpu_encoder_virt(drm_enc); + + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + phys = dpu_enc->phys_encs[i]; + if (phys->ops.prepare_commit) + phys->ops.prepare_commit(phys); + } +} + +#ifdef CONFIG_DEBUG_FS +static int _dpu_encoder_status_show(struct seq_file *s, void *data) +{ + struct dpu_encoder_virt *dpu_enc = s->private; + int i; + + mutex_lock(&dpu_enc->enc_lock); + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + struct dpu_encoder_phys *phys = dpu_enc->phys_encs[i]; + + seq_printf(s, "intf:%d vsync:%8d underrun:%8d ", + phys->intf_idx - INTF_0, + atomic_read(&phys->vsync_cnt), + atomic_read(&phys->underrun_cnt)); + + switch (phys->intf_mode) { + case INTF_MODE_VIDEO: + seq_puts(s, "mode: video\n"); + break; + case INTF_MODE_CMD: + seq_puts(s, "mode: command\n"); + break; + default: + seq_puts(s, "mode: ???\n"); + break; + } + } + mutex_unlock(&dpu_enc->enc_lock); + + return 0; +} + +DEFINE_SHOW_ATTRIBUTE(_dpu_encoder_status); + +static int _dpu_encoder_init_debugfs(struct drm_encoder *drm_enc) +{ + struct dpu_encoder_virt *dpu_enc = to_dpu_encoder_virt(drm_enc); + int i; + + char name[DPU_NAME_SIZE]; + + if (!drm_enc->dev) { + DPU_ERROR("invalid encoder or kms\n"); + return -EINVAL; + } + + snprintf(name, DPU_NAME_SIZE, "encoder%u", drm_enc->base.id); + + /* create overall sub-directory for the encoder */ + dpu_enc->debugfs_root = debugfs_create_dir(name, + drm_enc->dev->primary->debugfs_root); + + /* don't error check these */ + debugfs_create_file("status", 0600, + dpu_enc->debugfs_root, dpu_enc, &_dpu_encoder_status_fops); + + for (i = 0; i < dpu_enc->num_phys_encs; i++) + if (dpu_enc->phys_encs[i]->ops.late_register) + dpu_enc->phys_encs[i]->ops.late_register( + dpu_enc->phys_encs[i], + dpu_enc->debugfs_root); + + return 0; +} +#else +static int _dpu_encoder_init_debugfs(struct drm_encoder *drm_enc) +{ + return 0; +} +#endif + +static int dpu_encoder_late_register(struct drm_encoder *encoder) +{ + return _dpu_encoder_init_debugfs(encoder); +} + +static void dpu_encoder_early_unregister(struct drm_encoder *encoder) +{ + struct dpu_encoder_virt *dpu_enc = to_dpu_encoder_virt(encoder); + + debugfs_remove_recursive(dpu_enc->debugfs_root); +} + +static int dpu_encoder_virt_add_phys_encs( + u32 display_caps, + struct dpu_encoder_virt *dpu_enc, + struct dpu_enc_phys_init_params *params) +{ + struct dpu_encoder_phys *enc = NULL; + + DPU_DEBUG_ENC(dpu_enc, "\n"); + + /* + * We may create up to NUM_PHYS_ENCODER_TYPES physical encoder types + * in this function, check up-front. + */ + if (dpu_enc->num_phys_encs + NUM_PHYS_ENCODER_TYPES >= + ARRAY_SIZE(dpu_enc->phys_encs)) { + DPU_ERROR_ENC(dpu_enc, "too many physical encoders %d\n", + dpu_enc->num_phys_encs); + return -EINVAL; + } + + if (display_caps & MSM_DISPLAY_CAP_VID_MODE) { + enc = dpu_encoder_phys_vid_init(params); + + if (IS_ERR_OR_NULL(enc)) { + DPU_ERROR_ENC(dpu_enc, "failed to init vid enc: %ld\n", + PTR_ERR(enc)); + return enc == NULL ? -EINVAL : PTR_ERR(enc); + } + + dpu_enc->phys_encs[dpu_enc->num_phys_encs] = enc; + ++dpu_enc->num_phys_encs; + } + + if (display_caps & MSM_DISPLAY_CAP_CMD_MODE) { + enc = dpu_encoder_phys_cmd_init(params); + + if (IS_ERR_OR_NULL(enc)) { + DPU_ERROR_ENC(dpu_enc, "failed to init cmd enc: %ld\n", + PTR_ERR(enc)); + return enc == NULL ? -EINVAL : PTR_ERR(enc); + } + + dpu_enc->phys_encs[dpu_enc->num_phys_encs] = enc; + ++dpu_enc->num_phys_encs; + } + + if (params->split_role == ENC_ROLE_SLAVE) + dpu_enc->cur_slave = enc; + else + dpu_enc->cur_master = enc; + + return 0; +} + +static const struct dpu_encoder_virt_ops dpu_encoder_parent_ops = { + .handle_vblank_virt = dpu_encoder_vblank_callback, + .handle_underrun_virt = dpu_encoder_underrun_callback, + .handle_frame_done = dpu_encoder_frame_done_callback, +}; + +static int dpu_encoder_setup_display(struct dpu_encoder_virt *dpu_enc, + struct dpu_kms *dpu_kms, + struct msm_display_info *disp_info) +{ + int ret = 0; + int i = 0; + enum dpu_intf_type intf_type = INTF_NONE; + struct dpu_enc_phys_init_params phys_params; + + if (!dpu_enc) { + DPU_ERROR("invalid arg(s), enc %d\n", dpu_enc != NULL); + return -EINVAL; + } + + dpu_enc->cur_master = NULL; + + memset(&phys_params, 0, sizeof(phys_params)); + phys_params.dpu_kms = dpu_kms; + phys_params.parent = &dpu_enc->base; + phys_params.parent_ops = &dpu_encoder_parent_ops; + phys_params.enc_spinlock = &dpu_enc->enc_spinlock; + + DPU_DEBUG("\n"); + + switch (disp_info->intf_type) { + case DRM_MODE_ENCODER_DSI: + intf_type = INTF_DSI; + break; + case DRM_MODE_ENCODER_TMDS: + intf_type = INTF_DP; + break; + } + + WARN_ON(disp_info->num_of_h_tiles < 1); + + DPU_DEBUG("dsi_info->num_of_h_tiles %d\n", disp_info->num_of_h_tiles); + + if ((disp_info->capabilities & MSM_DISPLAY_CAP_CMD_MODE) || + (disp_info->capabilities & MSM_DISPLAY_CAP_VID_MODE)) + dpu_enc->idle_pc_supported = + dpu_kms->catalog->caps->has_idle_pc; + + mutex_lock(&dpu_enc->enc_lock); + for (i = 0; i < disp_info->num_of_h_tiles && !ret; i++) { + /* + * Left-most tile is at index 0, content is controller id + * h_tile_instance_ids[2] = {0, 1}; DSI0 = left, DSI1 = right + * h_tile_instance_ids[2] = {1, 0}; DSI1 = left, DSI0 = right + */ + u32 controller_id = disp_info->h_tile_instance[i]; + + if (disp_info->num_of_h_tiles > 1) { + if (i == 0) + phys_params.split_role = ENC_ROLE_MASTER; + else + phys_params.split_role = ENC_ROLE_SLAVE; + } else { + phys_params.split_role = ENC_ROLE_SOLO; + } + + DPU_DEBUG("h_tile_instance %d = %d, split_role %d\n", + i, controller_id, phys_params.split_role); + + phys_params.intf_idx = dpu_encoder_get_intf(dpu_kms->catalog, + intf_type, + controller_id); + if (phys_params.intf_idx == INTF_MAX) { + DPU_ERROR_ENC(dpu_enc, "could not get intf: type %d, id %d\n", + intf_type, controller_id); + ret = -EINVAL; + } + + if (!ret) { + ret = dpu_encoder_virt_add_phys_encs(disp_info->capabilities, + dpu_enc, + &phys_params); + if (ret) + DPU_ERROR_ENC(dpu_enc, "failed to add phys encs\n"); + } + } + + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + struct dpu_encoder_phys *phys = dpu_enc->phys_encs[i]; + atomic_set(&phys->vsync_cnt, 0); + atomic_set(&phys->underrun_cnt, 0); + } + mutex_unlock(&dpu_enc->enc_lock); + + return ret; +} + +static void dpu_encoder_frame_done_timeout(struct timer_list *t) +{ + struct dpu_encoder_virt *dpu_enc = from_timer(dpu_enc, t, + frame_done_timer); + struct drm_encoder *drm_enc = &dpu_enc->base; + u32 event; + + if (!drm_enc->dev) { + DPU_ERROR("invalid parameters\n"); + return; + } + + if (!dpu_enc->frame_busy_mask[0] || !dpu_enc->crtc_frame_event_cb) { + DRM_DEBUG_KMS("id:%u invalid timeout frame_busy_mask=%lu\n", + DRMID(drm_enc), dpu_enc->frame_busy_mask[0]); + return; + } else if (!atomic_xchg(&dpu_enc->frame_done_timeout_ms, 0)) { + DRM_DEBUG_KMS("id:%u invalid timeout\n", DRMID(drm_enc)); + return; + } + + DPU_ERROR_ENC(dpu_enc, "frame done timeout\n"); + + event = DPU_ENCODER_FRAME_EVENT_ERROR; + trace_dpu_enc_frame_done_timeout(DRMID(drm_enc), event); + dpu_enc->crtc_frame_event_cb(dpu_enc->crtc_frame_event_cb_data, event); +} + +static const struct drm_encoder_helper_funcs dpu_encoder_helper_funcs = { + .mode_set = dpu_encoder_virt_mode_set, + .disable = dpu_encoder_virt_disable, + .enable = dpu_kms_encoder_enable, + .atomic_check = dpu_encoder_virt_atomic_check, + + /* This is called by dpu_kms_encoder_enable */ + .commit = dpu_encoder_virt_enable, +}; + +static const struct drm_encoder_funcs dpu_encoder_funcs = { + .destroy = dpu_encoder_destroy, + .late_register = dpu_encoder_late_register, + .early_unregister = dpu_encoder_early_unregister, +}; + +int dpu_encoder_setup(struct drm_device *dev, struct drm_encoder *enc, + struct msm_display_info *disp_info) +{ + struct msm_drm_private *priv = dev->dev_private; + struct dpu_kms *dpu_kms = to_dpu_kms(priv->kms); + struct drm_encoder *drm_enc = NULL; + struct dpu_encoder_virt *dpu_enc = NULL; + int ret = 0; + + dpu_enc = to_dpu_encoder_virt(enc); + + ret = dpu_encoder_setup_display(dpu_enc, dpu_kms, disp_info); + if (ret) + goto fail; + + atomic_set(&dpu_enc->frame_done_timeout_ms, 0); + timer_setup(&dpu_enc->frame_done_timer, + dpu_encoder_frame_done_timeout, 0); + + if (disp_info->intf_type == DRM_MODE_ENCODER_DSI) + timer_setup(&dpu_enc->vsync_event_timer, + dpu_encoder_vsync_event_handler, + 0); + + + INIT_DELAYED_WORK(&dpu_enc->delayed_off_work, + dpu_encoder_off_work); + dpu_enc->idle_timeout = IDLE_TIMEOUT; + + kthread_init_work(&dpu_enc->vsync_event_work, + dpu_encoder_vsync_event_work_handler); + + memcpy(&dpu_enc->disp_info, disp_info, sizeof(*disp_info)); + + DPU_DEBUG_ENC(dpu_enc, "created\n"); + + return ret; + +fail: + DPU_ERROR("failed to create encoder\n"); + if (drm_enc) + dpu_encoder_destroy(drm_enc); + + return ret; + + +} + +struct drm_encoder *dpu_encoder_init(struct drm_device *dev, + int drm_enc_mode) +{ + struct dpu_encoder_virt *dpu_enc = NULL; + int rc = 0; + + dpu_enc = devm_kzalloc(dev->dev, sizeof(*dpu_enc), GFP_KERNEL); + if (!dpu_enc) + return ERR_PTR(-ENOMEM); + + rc = drm_encoder_init(dev, &dpu_enc->base, &dpu_encoder_funcs, + drm_enc_mode, NULL); + if (rc) { + devm_kfree(dev->dev, dpu_enc); + return ERR_PTR(rc); + } + + drm_encoder_helper_add(&dpu_enc->base, &dpu_encoder_helper_funcs); + + spin_lock_init(&dpu_enc->enc_spinlock); + dpu_enc->enabled = false; + mutex_init(&dpu_enc->enc_lock); + mutex_init(&dpu_enc->rc_lock); + + return &dpu_enc->base; +} + +int dpu_encoder_wait_for_event(struct drm_encoder *drm_enc, + enum msm_event_wait event) +{ + int (*fn_wait)(struct dpu_encoder_phys *phys_enc) = NULL; + struct dpu_encoder_virt *dpu_enc = NULL; + int i, ret = 0; + + if (!drm_enc) { + DPU_ERROR("invalid encoder\n"); + return -EINVAL; + } + dpu_enc = to_dpu_encoder_virt(drm_enc); + DPU_DEBUG_ENC(dpu_enc, "\n"); + + for (i = 0; i < dpu_enc->num_phys_encs; i++) { + struct dpu_encoder_phys *phys = dpu_enc->phys_encs[i]; + + switch (event) { + case MSM_ENC_COMMIT_DONE: + fn_wait = phys->ops.wait_for_commit_done; + break; + case MSM_ENC_TX_COMPLETE: + fn_wait = phys->ops.wait_for_tx_complete; + break; + case MSM_ENC_VBLANK: + fn_wait = phys->ops.wait_for_vblank; + break; + default: + DPU_ERROR_ENC(dpu_enc, "unknown wait event %d\n", + event); + return -EINVAL; + } + + if (fn_wait) { + DPU_ATRACE_BEGIN("wait_for_completion_event"); + ret = fn_wait(phys); + DPU_ATRACE_END("wait_for_completion_event"); + if (ret) + return ret; + } + } + + return ret; +} + +enum dpu_intf_mode dpu_encoder_get_intf_mode(struct drm_encoder *encoder) +{ + struct dpu_encoder_virt *dpu_enc = NULL; + + if (!encoder) { + DPU_ERROR("invalid encoder\n"); + return INTF_MODE_NONE; + } + dpu_enc = to_dpu_encoder_virt(encoder); + + if (dpu_enc->cur_master) + return dpu_enc->cur_master->intf_mode; + + if (dpu_enc->num_phys_encs) + return dpu_enc->phys_encs[0]->intf_mode; + + return INTF_MODE_NONE; +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h new file mode 100644 index 000000000..b4913465e --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#ifndef __DPU_ENCODER_H__ +#define __DPU_ENCODER_H__ + +#include <drm/drm_crtc.h> +#include "dpu_hw_mdss.h" + +#define DPU_ENCODER_FRAME_EVENT_DONE BIT(0) +#define DPU_ENCODER_FRAME_EVENT_ERROR BIT(1) +#define DPU_ENCODER_FRAME_EVENT_PANEL_DEAD BIT(2) +#define DPU_ENCODER_FRAME_EVENT_IDLE BIT(3) + +#define IDLE_TIMEOUT (66 - 16/2) + +/** + * Encoder functions and data types + * @intfs: Interfaces this encoder is using, INTF_MODE_NONE if unused + */ +struct dpu_encoder_hw_resources { + enum dpu_intf_mode intfs[INTF_MAX]; +}; + +/** + * dpu_encoder_get_hw_resources - Populate table of required hardware resources + * @encoder: encoder pointer + * @hw_res: resource table to populate with encoder required resources + */ +void dpu_encoder_get_hw_resources(struct drm_encoder *encoder, + struct dpu_encoder_hw_resources *hw_res); + +/** + * dpu_encoder_assign_crtc - Link the encoder to the crtc it's assigned to + * @encoder: encoder pointer + * @crtc: crtc pointer + */ +void dpu_encoder_assign_crtc(struct drm_encoder *encoder, + struct drm_crtc *crtc); + +/** + * dpu_encoder_toggle_vblank_for_crtc - Toggles vblank interrupts on or off if + * the encoder is assigned to the given crtc + * @encoder: encoder pointer + * @crtc: crtc pointer + * @enable: true if vblank should be enabled + */ +void dpu_encoder_toggle_vblank_for_crtc(struct drm_encoder *encoder, + struct drm_crtc *crtc, bool enable); + +/** + * dpu_encoder_register_frame_event_callback - provide callback to encoder that + * will be called after the request is complete, or other events. + * @encoder: encoder pointer + * @cb: callback pointer, provide NULL to deregister + * @data: user data provided to callback + */ +void dpu_encoder_register_frame_event_callback(struct drm_encoder *encoder, + void (*cb)(void *, u32), void *data); + +/** + * dpu_encoder_prepare_for_kickoff - schedule double buffer flip of the ctl + * path (i.e. ctl flush and start) at next appropriate time. + * Immediately: if no previous commit is outstanding. + * Delayed: Block until next trigger can be issued. + * @encoder: encoder pointer + */ +void dpu_encoder_prepare_for_kickoff(struct drm_encoder *encoder); + +/** + * dpu_encoder_trigger_kickoff_pending - Clear the flush bits from previous + * kickoff and trigger the ctl prepare progress for command mode display. + * @encoder: encoder pointer + */ +void dpu_encoder_trigger_kickoff_pending(struct drm_encoder *encoder); + +/** + * dpu_encoder_kickoff - trigger a double buffer flip of the ctl path + * (i.e. ctl flush and start) immediately. + * @encoder: encoder pointer + */ +void dpu_encoder_kickoff(struct drm_encoder *encoder); + +/** + * dpu_encoder_wakeup_time - get the time of the next vsync + */ +int dpu_encoder_vsync_time(struct drm_encoder *drm_enc, ktime_t *wakeup_time); + +/** + * dpu_encoder_wait_for_event - Waits for encoder events + * @encoder: encoder pointer + * @event: event to wait for + * MSM_ENC_COMMIT_DONE - Wait for hardware to have flushed the current pending + * frames to hardware at a vblank or ctl_start + * Encoders will map this differently depending on the + * panel type. + * vid mode -> vsync_irq + * cmd mode -> ctl_start + * MSM_ENC_TX_COMPLETE - Wait for the hardware to transfer all the pixels to + * the panel. Encoders will map this differently + * depending on the panel type. + * vid mode -> vsync_irq + * cmd mode -> pp_done + * Returns: 0 on success, -EWOULDBLOCK if already signaled, error otherwise + */ +int dpu_encoder_wait_for_event(struct drm_encoder *drm_encoder, + enum msm_event_wait event); + +/* + * dpu_encoder_get_intf_mode - get interface mode of the given encoder + * @encoder: Pointer to drm encoder object + */ +enum dpu_intf_mode dpu_encoder_get_intf_mode(struct drm_encoder *encoder); + +/** + * dpu_encoder_virt_runtime_resume - pm runtime resume the encoder configs + * @encoder: encoder pointer + */ +void dpu_encoder_virt_runtime_resume(struct drm_encoder *encoder); + +/** + * dpu_encoder_init - initialize virtual encoder object + * @dev: Pointer to drm device structure + * @disp_info: Pointer to display information structure + * Returns: Pointer to newly created drm encoder + */ +struct drm_encoder *dpu_encoder_init( + struct drm_device *dev, + int drm_enc_mode); + +/** + * dpu_encoder_setup - setup dpu_encoder for the display probed + * @dev: Pointer to drm device structure + * @enc: Pointer to the drm_encoder + * @disp_info: Pointer to the display info + */ +int dpu_encoder_setup(struct drm_device *dev, struct drm_encoder *enc, + struct msm_display_info *disp_info); + +/** + * dpu_encoder_prepare_commit - prepare encoder at the very beginning of an + * atomic commit, before any registers are written + * @drm_enc: Pointer to previously created drm encoder structure + */ +void dpu_encoder_prepare_commit(struct drm_encoder *drm_enc); + +/** + * dpu_encoder_set_idle_timeout - set the idle timeout for video + * and command mode encoders. + * @drm_enc: Pointer to previously created drm encoder structure + * @idle_timeout: idle timeout duration in milliseconds + */ +void dpu_encoder_set_idle_timeout(struct drm_encoder *drm_enc, + u32 idle_timeout); + +#endif /* __DPU_ENCODER_H__ */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys.h new file mode 100644 index 000000000..f8f25157f --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys.h @@ -0,0 +1,388 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2018 The Linux Foundation. All rights reserved. + */ + +#ifndef __DPU_ENCODER_PHYS_H__ +#define __DPU_ENCODER_PHYS_H__ + +#include <linux/jiffies.h> + +#include "dpu_kms.h" +#include "dpu_hw_intf.h" +#include "dpu_hw_pingpong.h" +#include "dpu_hw_ctl.h" +#include "dpu_hw_top.h" +#include "dpu_encoder.h" +#include "dpu_crtc.h" + +#define DPU_ENCODER_NAME_MAX 16 + +/* wait for at most 2 vsync for lowest refresh rate (24hz) */ +#define KICKOFF_TIMEOUT_MS 84 +#define KICKOFF_TIMEOUT_JIFFIES msecs_to_jiffies(KICKOFF_TIMEOUT_MS) + +/** + * enum dpu_enc_split_role - Role this physical encoder will play in a + * split-panel configuration, where one panel is master, and others slaves. + * Masters have extra responsibilities, like managing the VBLANK IRQ. + * @ENC_ROLE_SOLO: This is the one and only panel. This encoder is master. + * @ENC_ROLE_MASTER: This encoder is the master of a split panel config. + * @ENC_ROLE_SLAVE: This encoder is not the master of a split panel config. + */ +enum dpu_enc_split_role { + ENC_ROLE_SOLO, + ENC_ROLE_MASTER, + ENC_ROLE_SLAVE, +}; + +/** + * enum dpu_enc_enable_state - current enabled state of the physical encoder + * @DPU_ENC_DISABLING: Encoder transitioning to disable state + * Events bounding transition are encoder type specific + * @DPU_ENC_DISABLED: Encoder is disabled + * @DPU_ENC_ENABLING: Encoder transitioning to enabled + * Events bounding transition are encoder type specific + * @DPU_ENC_ENABLED: Encoder is enabled + * @DPU_ENC_ERR_NEEDS_HW_RESET: Encoder is enabled, but requires a hw_reset + * to recover from a previous error + */ +enum dpu_enc_enable_state { + DPU_ENC_DISABLING, + DPU_ENC_DISABLED, + DPU_ENC_ENABLING, + DPU_ENC_ENABLED, + DPU_ENC_ERR_NEEDS_HW_RESET +}; + +struct dpu_encoder_phys; + +/** + * struct dpu_encoder_virt_ops - Interface the containing virtual encoder + * provides for the physical encoders to use to callback. + * @handle_vblank_virt: Notify virtual encoder of vblank IRQ reception + * Note: This is called from IRQ handler context. + * @handle_underrun_virt: Notify virtual encoder of underrun IRQ reception + * Note: This is called from IRQ handler context. + * @handle_frame_done: Notify virtual encoder that this phys encoder + * completes last request frame. + */ +struct dpu_encoder_virt_ops { + void (*handle_vblank_virt)(struct drm_encoder *, + struct dpu_encoder_phys *phys); + void (*handle_underrun_virt)(struct drm_encoder *, + struct dpu_encoder_phys *phys); + void (*handle_frame_done)(struct drm_encoder *, + struct dpu_encoder_phys *phys, u32 event); +}; + +/** + * struct dpu_encoder_phys_ops - Interface the physical encoders provide to + * the containing virtual encoder. + * @late_register: DRM Call. Add Userspace interfaces, debugfs. + * @prepare_commit: MSM Atomic Call, start of atomic commit sequence + * @is_master: Whether this phys_enc is the current master + * encoder. Can be switched at enable time. Based + * on split_role and current mode (CMD/VID). + * @mode_fixup: DRM Call. Fixup a DRM mode. + * @mode_set: DRM Call. Set a DRM mode. + * This likely caches the mode, for use at enable. + * @enable: DRM Call. Enable a DRM mode. + * @disable: DRM Call. Disable mode. + * @atomic_check: DRM Call. Atomic check new DRM state. + * @destroy: DRM Call. Destroy and release resources. + * @get_hw_resources: Populate the structure with the hardware + * resources that this phys_enc is using. + * Expect no overlap between phys_encs. + * @control_vblank_irq Register/Deregister for VBLANK IRQ + * @wait_for_commit_done: Wait for hardware to have flushed the + * current pending frames to hardware + * @wait_for_tx_complete: Wait for hardware to transfer the pixels + * to the panel + * @wait_for_vblank: Wait for VBLANK, for sub-driver internal use + * @prepare_for_kickoff: Do any work necessary prior to a kickoff + * For CMD encoder, may wait for previous tx done + * @handle_post_kickoff: Do any work necessary post-kickoff work + * @trigger_start: Process start event on physical encoder + * @needs_single_flush: Whether encoder slaves need to be flushed + * @irq_control: Handler to enable/disable all the encoder IRQs + * @prepare_idle_pc: phys encoder can update the vsync_enable status + * on idle power collapse prepare + * @restore: Restore all the encoder configs. + * @get_line_count: Obtain current vertical line count + */ + +struct dpu_encoder_phys_ops { + int (*late_register)(struct dpu_encoder_phys *encoder, + struct dentry *debugfs_root); + void (*prepare_commit)(struct dpu_encoder_phys *encoder); + bool (*is_master)(struct dpu_encoder_phys *encoder); + bool (*mode_fixup)(struct dpu_encoder_phys *encoder, + const struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); + void (*mode_set)(struct dpu_encoder_phys *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); + void (*enable)(struct dpu_encoder_phys *encoder); + void (*disable)(struct dpu_encoder_phys *encoder); + int (*atomic_check)(struct dpu_encoder_phys *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state); + void (*destroy)(struct dpu_encoder_phys *encoder); + void (*get_hw_resources)(struct dpu_encoder_phys *encoder, + struct dpu_encoder_hw_resources *hw_res); + int (*control_vblank_irq)(struct dpu_encoder_phys *enc, bool enable); + int (*wait_for_commit_done)(struct dpu_encoder_phys *phys_enc); + int (*wait_for_tx_complete)(struct dpu_encoder_phys *phys_enc); + int (*wait_for_vblank)(struct dpu_encoder_phys *phys_enc); + void (*prepare_for_kickoff)(struct dpu_encoder_phys *phys_enc); + void (*handle_post_kickoff)(struct dpu_encoder_phys *phys_enc); + void (*trigger_start)(struct dpu_encoder_phys *phys_enc); + bool (*needs_single_flush)(struct dpu_encoder_phys *phys_enc); + void (*irq_control)(struct dpu_encoder_phys *phys, bool enable); + void (*prepare_idle_pc)(struct dpu_encoder_phys *phys_enc); + void (*restore)(struct dpu_encoder_phys *phys); + int (*get_line_count)(struct dpu_encoder_phys *phys); +}; + +/** + * enum dpu_intr_idx - dpu encoder interrupt index + * @INTR_IDX_VSYNC: Vsync interrupt for video mode panel + * @INTR_IDX_PINGPONG: Pingpong done unterrupt for cmd mode panel + * @INTR_IDX_UNDERRUN: Underrun unterrupt for video and cmd mode panel + * @INTR_IDX_RDPTR: Readpointer done unterrupt for cmd mode panel + */ +enum dpu_intr_idx { + INTR_IDX_VSYNC, + INTR_IDX_PINGPONG, + INTR_IDX_UNDERRUN, + INTR_IDX_CTL_START, + INTR_IDX_RDPTR, + INTR_IDX_MAX, +}; + +/** + * dpu_encoder_irq - tracking structure for interrupts + * @name: string name of interrupt + * @intr_type: Encoder interrupt type + * @intr_idx: Encoder interrupt enumeration + * @hw_idx: HW Block ID + * @irq_idx: IRQ interface lookup index from DPU IRQ framework + * will be -EINVAL if IRQ is not registered + * @irq_cb: interrupt callback + */ +struct dpu_encoder_irq { + const char *name; + enum dpu_intr_type intr_type; + enum dpu_intr_idx intr_idx; + int hw_idx; + int irq_idx; + struct dpu_irq_callback cb; +}; + +/** + * struct dpu_encoder_phys - physical encoder that drives a single INTF block + * tied to a specific panel / sub-panel. Abstract type, sub-classed by + * phys_vid or phys_cmd for video mode or command mode encs respectively. + * @parent: Pointer to the containing virtual encoder + * @connector: If a mode is set, cached pointer to the active connector + * @ops: Operations exposed to the virtual encoder + * @parent_ops: Callbacks exposed by the parent to the phys_enc + * @hw_mdptop: Hardware interface to the top registers + * @hw_ctl: Hardware interface to the ctl registers + * @hw_pp: Hardware interface to the ping pong registers + * @hw_intf: Hardware interface to the intf registers + * @dpu_kms: Pointer to the dpu_kms top level + * @cached_mode: DRM mode cached at mode_set time, acted on in enable + * @enabled: Whether the encoder has enabled and running a mode + * @split_role: Role to play in a split-panel configuration + * @intf_mode: Interface mode + * @intf_idx: Interface index on dpu hardware + * @enc_spinlock: Virtual-Encoder-Wide Spin Lock for IRQ purposes + * @enable_state: Enable state tracking + * @vblank_refcount: Reference count of vblank request + * @vsync_cnt: Vsync count for the physical encoder + * @underrun_cnt: Underrun count for the physical encoder + * @pending_kickoff_cnt: Atomic counter tracking the number of kickoffs + * vs. the number of done/vblank irqs. Should hover + * between 0-2 Incremented when a new kickoff is + * scheduled. Decremented in irq handler + * @pending_ctlstart_cnt: Atomic counter tracking the number of ctl start + * pending. + * @pending_kickoff_wq: Wait queue for blocking until kickoff completes + * @irq: IRQ tracking structures + */ +struct dpu_encoder_phys { + struct drm_encoder *parent; + struct drm_connector *connector; + struct dpu_encoder_phys_ops ops; + const struct dpu_encoder_virt_ops *parent_ops; + struct dpu_hw_mdp *hw_mdptop; + struct dpu_hw_ctl *hw_ctl; + struct dpu_hw_pingpong *hw_pp; + struct dpu_hw_intf *hw_intf; + struct dpu_kms *dpu_kms; + struct drm_display_mode cached_mode; + enum dpu_enc_split_role split_role; + enum dpu_intf_mode intf_mode; + enum dpu_intf intf_idx; + spinlock_t *enc_spinlock; + enum dpu_enc_enable_state enable_state; + atomic_t vblank_refcount; + atomic_t vsync_cnt; + atomic_t underrun_cnt; + atomic_t pending_ctlstart_cnt; + atomic_t pending_kickoff_cnt; + wait_queue_head_t pending_kickoff_wq; + struct dpu_encoder_irq irq[INTR_IDX_MAX]; +}; + +static inline int dpu_encoder_phys_inc_pending(struct dpu_encoder_phys *phys) +{ + atomic_inc_return(&phys->pending_ctlstart_cnt); + return atomic_inc_return(&phys->pending_kickoff_cnt); +} + +/** + * struct dpu_encoder_phys_cmd - sub-class of dpu_encoder_phys to handle command + * mode specific operations + * @base: Baseclass physical encoder structure + * @intf_idx: Intf Block index used by this phys encoder + * @stream_sel: Stream selection for multi-stream interfaces + * @serialize_wait4pp: serialize wait4pp feature waits for pp_done interrupt + * after ctl_start instead of before next frame kickoff + * @pp_timeout_report_cnt: number of pingpong done irq timeout errors + * @pending_vblank_cnt: Atomic counter tracking pending wait for VBLANK + * @pending_vblank_wq: Wait queue for blocking until VBLANK received + */ +struct dpu_encoder_phys_cmd { + struct dpu_encoder_phys base; + int stream_sel; + bool serialize_wait4pp; + int pp_timeout_report_cnt; + atomic_t pending_vblank_cnt; + wait_queue_head_t pending_vblank_wq; +}; + +/** + * struct dpu_enc_phys_init_params - initialization parameters for phys encs + * @dpu_kms: Pointer to the dpu_kms top level + * @parent: Pointer to the containing virtual encoder + * @parent_ops: Callbacks exposed by the parent to the phys_enc + * @split_role: Role to play in a split-panel configuration + * @intf_idx: Interface index this phys_enc will control + * @enc_spinlock: Virtual-Encoder-Wide Spin Lock for IRQ purposes + */ +struct dpu_enc_phys_init_params { + struct dpu_kms *dpu_kms; + struct drm_encoder *parent; + const struct dpu_encoder_virt_ops *parent_ops; + enum dpu_enc_split_role split_role; + enum dpu_intf intf_idx; + spinlock_t *enc_spinlock; +}; + +/** + * dpu_encoder_wait_info - container for passing arguments to irq wait functions + * @wq: wait queue structure + * @atomic_cnt: wait until atomic_cnt equals zero + * @timeout_ms: timeout value in milliseconds + */ +struct dpu_encoder_wait_info { + wait_queue_head_t *wq; + atomic_t *atomic_cnt; + s64 timeout_ms; +}; + +/** + * dpu_encoder_phys_vid_init - Construct a new video mode physical encoder + * @p: Pointer to init params structure + * Return: Error code or newly allocated encoder + */ +struct dpu_encoder_phys *dpu_encoder_phys_vid_init( + struct dpu_enc_phys_init_params *p); + +/** + * dpu_encoder_phys_cmd_init - Construct a new command mode physical encoder + * @p: Pointer to init params structure + * Return: Error code or newly allocated encoder + */ +struct dpu_encoder_phys *dpu_encoder_phys_cmd_init( + struct dpu_enc_phys_init_params *p); + +/** + * dpu_encoder_helper_trigger_start - control start helper function + * This helper function may be optionally specified by physical + * encoders if they require ctl_start triggering. + * @phys_enc: Pointer to physical encoder structure + */ +void dpu_encoder_helper_trigger_start(struct dpu_encoder_phys *phys_enc); + +static inline enum dpu_3d_blend_mode dpu_encoder_helper_get_3d_blend_mode( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_crtc_state *dpu_cstate; + + if (!phys_enc || phys_enc->enable_state == DPU_ENC_DISABLING) + return BLEND_3D_NONE; + + dpu_cstate = to_dpu_crtc_state(phys_enc->parent->crtc->state); + + if (phys_enc->split_role == ENC_ROLE_SOLO && + dpu_cstate->num_mixers == CRTC_DUAL_MIXERS) + return BLEND_3D_H_ROW_INT; + + return BLEND_3D_NONE; +} + +/** + * dpu_encoder_helper_split_config - split display configuration helper function + * This helper function may be used by physical encoders to configure + * the split display related registers. + * @phys_enc: Pointer to physical encoder structure + * @interface: enum dpu_intf setting + */ +void dpu_encoder_helper_split_config( + struct dpu_encoder_phys *phys_enc, + enum dpu_intf interface); + +/** + * dpu_encoder_helper_report_irq_timeout - utility to report error that irq has + * timed out, including reporting frame error event to crtc and debug dump + * @phys_enc: Pointer to physical encoder structure + * @intr_idx: Failing interrupt index + */ +void dpu_encoder_helper_report_irq_timeout(struct dpu_encoder_phys *phys_enc, + enum dpu_intr_idx intr_idx); + +/** + * dpu_encoder_helper_wait_for_irq - utility to wait on an irq. + * note: will call dpu_encoder_helper_wait_for_irq on timeout + * @phys_enc: Pointer to physical encoder structure + * @intr_idx: encoder interrupt index + * @wait_info: wait info struct + * @Return: 0 or -ERROR + */ +int dpu_encoder_helper_wait_for_irq(struct dpu_encoder_phys *phys_enc, + enum dpu_intr_idx intr_idx, + struct dpu_encoder_wait_info *wait_info); + +/** + * dpu_encoder_helper_register_irq - register and enable an irq + * @phys_enc: Pointer to physical encoder structure + * @intr_idx: encoder interrupt index + * @Return: 0 or -ERROR + */ +int dpu_encoder_helper_register_irq(struct dpu_encoder_phys *phys_enc, + enum dpu_intr_idx intr_idx); + +/** + * dpu_encoder_helper_unregister_irq - unregister and disable an irq + * @phys_enc: Pointer to physical encoder structure + * @intr_idx: encoder interrupt index + * @Return: 0 or -ERROR + */ +int dpu_encoder_helper_unregister_irq(struct dpu_encoder_phys *phys_enc, + enum dpu_intr_idx intr_idx); + +#endif /* __dpu_encoder_phys_H__ */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c new file mode 100644 index 000000000..8493d68ad --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_cmd.c @@ -0,0 +1,778 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015-2018 The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ +#include "dpu_encoder_phys.h" +#include "dpu_hw_interrupts.h" +#include "dpu_core_irq.h" +#include "dpu_formats.h" +#include "dpu_trace.h" + +#define DPU_DEBUG_CMDENC(e, fmt, ...) DPU_DEBUG("enc%d intf%d " fmt, \ + (e) && (e)->base.parent ? \ + (e)->base.parent->base.id : -1, \ + (e) ? (e)->base.intf_idx - INTF_0 : -1, ##__VA_ARGS__) + +#define DPU_ERROR_CMDENC(e, fmt, ...) DPU_ERROR("enc%d intf%d " fmt, \ + (e) && (e)->base.parent ? \ + (e)->base.parent->base.id : -1, \ + (e) ? (e)->base.intf_idx - INTF_0 : -1, ##__VA_ARGS__) + +#define to_dpu_encoder_phys_cmd(x) \ + container_of(x, struct dpu_encoder_phys_cmd, base) + +#define PP_TIMEOUT_MAX_TRIALS 10 + +/* + * Tearcheck sync start and continue thresholds are empirically found + * based on common panels In the future, may want to allow panels to override + * these default values + */ +#define DEFAULT_TEARCHECK_SYNC_THRESH_START 4 +#define DEFAULT_TEARCHECK_SYNC_THRESH_CONTINUE 4 + +#define DPU_ENC_WR_PTR_START_TIMEOUT_US 20000 + +static bool dpu_encoder_phys_cmd_is_master(struct dpu_encoder_phys *phys_enc) +{ + return (phys_enc->split_role != ENC_ROLE_SLAVE) ? true : false; +} + +static bool dpu_encoder_phys_cmd_mode_fixup( + struct dpu_encoder_phys *phys_enc, + const struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + DPU_DEBUG_CMDENC(to_dpu_encoder_phys_cmd(phys_enc), "\n"); + return true; +} + +static void _dpu_encoder_phys_cmd_update_intf_cfg( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_phys_cmd *cmd_enc = + to_dpu_encoder_phys_cmd(phys_enc); + struct dpu_hw_ctl *ctl; + struct dpu_hw_intf_cfg intf_cfg = { 0 }; + + ctl = phys_enc->hw_ctl; + if (!ctl->ops.setup_intf_cfg) + return; + + intf_cfg.intf = phys_enc->intf_idx; + intf_cfg.intf_mode_sel = DPU_CTL_MODE_SEL_CMD; + intf_cfg.stream_sel = cmd_enc->stream_sel; + intf_cfg.mode_3d = dpu_encoder_helper_get_3d_blend_mode(phys_enc); + ctl->ops.setup_intf_cfg(ctl, &intf_cfg); +} + +static void dpu_encoder_phys_cmd_pp_tx_done_irq(void *arg, int irq_idx) +{ + struct dpu_encoder_phys *phys_enc = arg; + unsigned long lock_flags; + int new_cnt; + u32 event = DPU_ENCODER_FRAME_EVENT_DONE; + + if (!phys_enc->hw_pp) + return; + + DPU_ATRACE_BEGIN("pp_done_irq"); + /* notify all synchronous clients first, then asynchronous clients */ + if (phys_enc->parent_ops->handle_frame_done) + phys_enc->parent_ops->handle_frame_done(phys_enc->parent, + phys_enc, event); + + spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags); + new_cnt = atomic_add_unless(&phys_enc->pending_kickoff_cnt, -1, 0); + spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags); + + trace_dpu_enc_phys_cmd_pp_tx_done(DRMID(phys_enc->parent), + phys_enc->hw_pp->idx - PINGPONG_0, + new_cnt, event); + + /* Signal any waiting atomic commit thread */ + wake_up_all(&phys_enc->pending_kickoff_wq); + DPU_ATRACE_END("pp_done_irq"); +} + +static void dpu_encoder_phys_cmd_pp_rd_ptr_irq(void *arg, int irq_idx) +{ + struct dpu_encoder_phys *phys_enc = arg; + struct dpu_encoder_phys_cmd *cmd_enc; + + if (!phys_enc->hw_pp) + return; + + DPU_ATRACE_BEGIN("rd_ptr_irq"); + cmd_enc = to_dpu_encoder_phys_cmd(phys_enc); + + if (phys_enc->parent_ops->handle_vblank_virt) + phys_enc->parent_ops->handle_vblank_virt(phys_enc->parent, + phys_enc); + + atomic_add_unless(&cmd_enc->pending_vblank_cnt, -1, 0); + wake_up_all(&cmd_enc->pending_vblank_wq); + DPU_ATRACE_END("rd_ptr_irq"); +} + +static void dpu_encoder_phys_cmd_ctl_start_irq(void *arg, int irq_idx) +{ + struct dpu_encoder_phys *phys_enc = arg; + + DPU_ATRACE_BEGIN("ctl_start_irq"); + + atomic_add_unless(&phys_enc->pending_ctlstart_cnt, -1, 0); + + /* Signal any waiting ctl start interrupt */ + wake_up_all(&phys_enc->pending_kickoff_wq); + DPU_ATRACE_END("ctl_start_irq"); +} + +static void dpu_encoder_phys_cmd_underrun_irq(void *arg, int irq_idx) +{ + struct dpu_encoder_phys *phys_enc = arg; + + if (phys_enc->parent_ops->handle_underrun_virt) + phys_enc->parent_ops->handle_underrun_virt(phys_enc->parent, + phys_enc); +} + +static void _dpu_encoder_phys_cmd_setup_irq_hw_idx( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_irq *irq; + + irq = &phys_enc->irq[INTR_IDX_CTL_START]; + irq->hw_idx = phys_enc->hw_ctl->idx; + irq->irq_idx = -EINVAL; + + irq = &phys_enc->irq[INTR_IDX_PINGPONG]; + irq->hw_idx = phys_enc->hw_pp->idx; + irq->irq_idx = -EINVAL; + + irq = &phys_enc->irq[INTR_IDX_RDPTR]; + irq->hw_idx = phys_enc->hw_pp->idx; + irq->irq_idx = -EINVAL; + + irq = &phys_enc->irq[INTR_IDX_UNDERRUN]; + irq->hw_idx = phys_enc->intf_idx; + irq->irq_idx = -EINVAL; +} + +static void dpu_encoder_phys_cmd_mode_set( + struct dpu_encoder_phys *phys_enc, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + struct dpu_encoder_phys_cmd *cmd_enc = + to_dpu_encoder_phys_cmd(phys_enc); + + if (!mode || !adj_mode) { + DPU_ERROR("invalid args\n"); + return; + } + phys_enc->cached_mode = *adj_mode; + DPU_DEBUG_CMDENC(cmd_enc, "caching mode:\n"); + drm_mode_debug_printmodeline(adj_mode); + + _dpu_encoder_phys_cmd_setup_irq_hw_idx(phys_enc); +} + +static int _dpu_encoder_phys_cmd_handle_ppdone_timeout( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_phys_cmd *cmd_enc = + to_dpu_encoder_phys_cmd(phys_enc); + u32 frame_event = DPU_ENCODER_FRAME_EVENT_ERROR; + bool do_log = false; + + if (!phys_enc->hw_pp) + return -EINVAL; + + cmd_enc->pp_timeout_report_cnt++; + if (cmd_enc->pp_timeout_report_cnt == PP_TIMEOUT_MAX_TRIALS) { + frame_event |= DPU_ENCODER_FRAME_EVENT_PANEL_DEAD; + do_log = true; + } else if (cmd_enc->pp_timeout_report_cnt == 1) { + do_log = true; + } + + trace_dpu_enc_phys_cmd_pdone_timeout(DRMID(phys_enc->parent), + phys_enc->hw_pp->idx - PINGPONG_0, + cmd_enc->pp_timeout_report_cnt, + atomic_read(&phys_enc->pending_kickoff_cnt), + frame_event); + + /* to avoid flooding, only log first time, and "dead" time */ + if (do_log) { + DRM_ERROR("id:%d pp:%d kickoff timeout %d cnt %d koff_cnt %d\n", + DRMID(phys_enc->parent), + phys_enc->hw_pp->idx - PINGPONG_0, + phys_enc->hw_ctl->idx - CTL_0, + cmd_enc->pp_timeout_report_cnt, + atomic_read(&phys_enc->pending_kickoff_cnt)); + + dpu_encoder_helper_unregister_irq(phys_enc, INTR_IDX_RDPTR); + } + + atomic_add_unless(&phys_enc->pending_kickoff_cnt, -1, 0); + + /* request a ctl reset before the next kickoff */ + phys_enc->enable_state = DPU_ENC_ERR_NEEDS_HW_RESET; + + if (phys_enc->parent_ops->handle_frame_done) + phys_enc->parent_ops->handle_frame_done( + phys_enc->parent, phys_enc, frame_event); + + return -ETIMEDOUT; +} + +static int _dpu_encoder_phys_cmd_wait_for_idle( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_phys_cmd *cmd_enc = + to_dpu_encoder_phys_cmd(phys_enc); + struct dpu_encoder_wait_info wait_info; + int ret; + + wait_info.wq = &phys_enc->pending_kickoff_wq; + wait_info.atomic_cnt = &phys_enc->pending_kickoff_cnt; + wait_info.timeout_ms = KICKOFF_TIMEOUT_MS; + + ret = dpu_encoder_helper_wait_for_irq(phys_enc, INTR_IDX_PINGPONG, + &wait_info); + if (ret == -ETIMEDOUT) + _dpu_encoder_phys_cmd_handle_ppdone_timeout(phys_enc); + else if (!ret) + cmd_enc->pp_timeout_report_cnt = 0; + + return ret; +} + +static int dpu_encoder_phys_cmd_control_vblank_irq( + struct dpu_encoder_phys *phys_enc, + bool enable) +{ + int ret = 0; + int refcount; + + if (!phys_enc->hw_pp) { + DPU_ERROR("invalid encoder\n"); + return -EINVAL; + } + + refcount = atomic_read(&phys_enc->vblank_refcount); + + /* Slave encoders don't report vblank */ + if (!dpu_encoder_phys_cmd_is_master(phys_enc)) + goto end; + + /* protect against negative */ + if (!enable && refcount == 0) { + ret = -EINVAL; + goto end; + } + + DRM_DEBUG_KMS("id:%u pp:%d enable=%s/%d\n", DRMID(phys_enc->parent), + phys_enc->hw_pp->idx - PINGPONG_0, + enable ? "true" : "false", refcount); + + if (enable && atomic_inc_return(&phys_enc->vblank_refcount) == 1) + ret = dpu_encoder_helper_register_irq(phys_enc, INTR_IDX_RDPTR); + else if (!enable && atomic_dec_return(&phys_enc->vblank_refcount) == 0) + ret = dpu_encoder_helper_unregister_irq(phys_enc, + INTR_IDX_RDPTR); + +end: + if (ret) { + DRM_ERROR("vblank irq err id:%u pp:%d ret:%d, enable %s/%d\n", + DRMID(phys_enc->parent), + phys_enc->hw_pp->idx - PINGPONG_0, ret, + enable ? "true" : "false", refcount); + } + + return ret; +} + +static void dpu_encoder_phys_cmd_irq_control(struct dpu_encoder_phys *phys_enc, + bool enable) +{ + trace_dpu_enc_phys_cmd_irq_ctrl(DRMID(phys_enc->parent), + phys_enc->hw_pp->idx - PINGPONG_0, + enable, atomic_read(&phys_enc->vblank_refcount)); + + if (enable) { + dpu_encoder_helper_register_irq(phys_enc, INTR_IDX_PINGPONG); + dpu_encoder_helper_register_irq(phys_enc, INTR_IDX_UNDERRUN); + dpu_encoder_phys_cmd_control_vblank_irq(phys_enc, true); + + if (dpu_encoder_phys_cmd_is_master(phys_enc)) + dpu_encoder_helper_register_irq(phys_enc, + INTR_IDX_CTL_START); + } else { + if (dpu_encoder_phys_cmd_is_master(phys_enc)) + dpu_encoder_helper_unregister_irq(phys_enc, + INTR_IDX_CTL_START); + + dpu_encoder_helper_unregister_irq(phys_enc, INTR_IDX_UNDERRUN); + dpu_encoder_phys_cmd_control_vblank_irq(phys_enc, false); + dpu_encoder_helper_unregister_irq(phys_enc, INTR_IDX_PINGPONG); + } +} + +static void dpu_encoder_phys_cmd_tearcheck_config( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_phys_cmd *cmd_enc = + to_dpu_encoder_phys_cmd(phys_enc); + struct dpu_hw_tear_check tc_cfg = { 0 }; + struct drm_display_mode *mode; + bool tc_enable = true; + u32 vsync_hz; + struct dpu_kms *dpu_kms; + + if (!phys_enc->hw_pp) { + DPU_ERROR("invalid encoder\n"); + return; + } + mode = &phys_enc->cached_mode; + + DPU_DEBUG_CMDENC(cmd_enc, "pp %d\n", phys_enc->hw_pp->idx - PINGPONG_0); + + if (!phys_enc->hw_pp->ops.setup_tearcheck || + !phys_enc->hw_pp->ops.enable_tearcheck) { + DPU_DEBUG_CMDENC(cmd_enc, "tearcheck not supported\n"); + return; + } + + dpu_kms = phys_enc->dpu_kms; + + /* + * TE default: dsi byte clock calculated base on 70 fps; + * around 14 ms to complete a kickoff cycle if te disabled; + * vclk_line base on 60 fps; write is faster than read; + * init == start == rdptr; + * + * vsync_count is ratio of MDP VSYNC clock frequency to LCD panel + * frequency divided by the no. of rows (lines) in the LCDpanel. + */ + vsync_hz = dpu_kms_get_clk_rate(dpu_kms, "vsync"); + if (vsync_hz <= 0) { + DPU_DEBUG_CMDENC(cmd_enc, "invalid - vsync_hz %u\n", + vsync_hz); + return; + } + + tc_cfg.vsync_count = vsync_hz / + (mode->vtotal * drm_mode_vrefresh(mode)); + + /* enable external TE after kickoff to avoid premature autorefresh */ + tc_cfg.hw_vsync_mode = 0; + + /* + * By setting sync_cfg_height to near max register value, we essentially + * disable dpu hw generated TE signal, since hw TE will arrive first. + * Only caveat is if due to error, we hit wrap-around. + */ + tc_cfg.sync_cfg_height = 0xFFF0; + tc_cfg.vsync_init_val = mode->vdisplay; + tc_cfg.sync_threshold_start = DEFAULT_TEARCHECK_SYNC_THRESH_START; + tc_cfg.sync_threshold_continue = DEFAULT_TEARCHECK_SYNC_THRESH_CONTINUE; + tc_cfg.start_pos = mode->vdisplay; + tc_cfg.rd_ptr_irq = mode->vdisplay + 1; + + DPU_DEBUG_CMDENC(cmd_enc, + "tc %d vsync_clk_speed_hz %u vtotal %u vrefresh %u\n", + phys_enc->hw_pp->idx - PINGPONG_0, vsync_hz, + mode->vtotal, drm_mode_vrefresh(mode)); + DPU_DEBUG_CMDENC(cmd_enc, + "tc %d enable %u start_pos %u rd_ptr_irq %u\n", + phys_enc->hw_pp->idx - PINGPONG_0, tc_enable, tc_cfg.start_pos, + tc_cfg.rd_ptr_irq); + DPU_DEBUG_CMDENC(cmd_enc, + "tc %d hw_vsync_mode %u vsync_count %u vsync_init_val %u\n", + phys_enc->hw_pp->idx - PINGPONG_0, tc_cfg.hw_vsync_mode, + tc_cfg.vsync_count, tc_cfg.vsync_init_val); + DPU_DEBUG_CMDENC(cmd_enc, + "tc %d cfgheight %u thresh_start %u thresh_cont %u\n", + phys_enc->hw_pp->idx - PINGPONG_0, tc_cfg.sync_cfg_height, + tc_cfg.sync_threshold_start, tc_cfg.sync_threshold_continue); + + phys_enc->hw_pp->ops.setup_tearcheck(phys_enc->hw_pp, &tc_cfg); + phys_enc->hw_pp->ops.enable_tearcheck(phys_enc->hw_pp, tc_enable); +} + +static void _dpu_encoder_phys_cmd_pingpong_config( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_phys_cmd *cmd_enc = + to_dpu_encoder_phys_cmd(phys_enc); + + if (!phys_enc->hw_pp || !phys_enc->hw_ctl->ops.setup_intf_cfg) { + DPU_ERROR("invalid arg(s), enc %d\n", phys_enc != NULL); + return; + } + + DPU_DEBUG_CMDENC(cmd_enc, "pp %d, enabling mode:\n", + phys_enc->hw_pp->idx - PINGPONG_0); + drm_mode_debug_printmodeline(&phys_enc->cached_mode); + + _dpu_encoder_phys_cmd_update_intf_cfg(phys_enc); + dpu_encoder_phys_cmd_tearcheck_config(phys_enc); +} + +static bool dpu_encoder_phys_cmd_needs_single_flush( + struct dpu_encoder_phys *phys_enc) +{ + /** + * we do separate flush for each CTL and let + * CTL_START synchronize them + */ + return false; +} + +static void dpu_encoder_phys_cmd_enable_helper( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_hw_ctl *ctl; + u32 flush_mask = 0; + + if (!phys_enc->hw_pp) { + DPU_ERROR("invalid arg(s), encoder %d\n", phys_enc != NULL); + return; + } + + dpu_encoder_helper_split_config(phys_enc, phys_enc->intf_idx); + + _dpu_encoder_phys_cmd_pingpong_config(phys_enc); + + if (!dpu_encoder_phys_cmd_is_master(phys_enc)) + return; + + ctl = phys_enc->hw_ctl; + ctl->ops.get_bitmask_intf(ctl, &flush_mask, phys_enc->intf_idx); + ctl->ops.update_pending_flush(ctl, flush_mask); +} + +static void dpu_encoder_phys_cmd_enable(struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_phys_cmd *cmd_enc = + to_dpu_encoder_phys_cmd(phys_enc); + + if (!phys_enc->hw_pp) { + DPU_ERROR("invalid phys encoder\n"); + return; + } + + DPU_DEBUG_CMDENC(cmd_enc, "pp %d\n", phys_enc->hw_pp->idx - PINGPONG_0); + + if (phys_enc->enable_state == DPU_ENC_ENABLED) { + DPU_ERROR("already enabled\n"); + return; + } + + dpu_encoder_phys_cmd_enable_helper(phys_enc); + phys_enc->enable_state = DPU_ENC_ENABLED; +} + +static void _dpu_encoder_phys_cmd_connect_te( + struct dpu_encoder_phys *phys_enc, bool enable) +{ + if (!phys_enc->hw_pp || !phys_enc->hw_pp->ops.connect_external_te) + return; + + trace_dpu_enc_phys_cmd_connect_te(DRMID(phys_enc->parent), enable); + phys_enc->hw_pp->ops.connect_external_te(phys_enc->hw_pp, enable); +} + +static void dpu_encoder_phys_cmd_prepare_idle_pc( + struct dpu_encoder_phys *phys_enc) +{ + _dpu_encoder_phys_cmd_connect_te(phys_enc, false); +} + +static int dpu_encoder_phys_cmd_get_line_count( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_hw_pingpong *hw_pp; + + if (!phys_enc->hw_pp) + return -EINVAL; + + if (!dpu_encoder_phys_cmd_is_master(phys_enc)) + return -EINVAL; + + hw_pp = phys_enc->hw_pp; + if (!hw_pp->ops.get_line_count) + return -EINVAL; + + return hw_pp->ops.get_line_count(hw_pp); +} + +static void dpu_encoder_phys_cmd_disable(struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_phys_cmd *cmd_enc = + to_dpu_encoder_phys_cmd(phys_enc); + + if (!phys_enc->hw_pp) { + DPU_ERROR("invalid encoder\n"); + return; + } + DRM_DEBUG_KMS("id:%u pp:%d state:%d\n", DRMID(phys_enc->parent), + phys_enc->hw_pp->idx - PINGPONG_0, + phys_enc->enable_state); + + if (phys_enc->enable_state == DPU_ENC_DISABLED) { + DPU_ERROR_CMDENC(cmd_enc, "already disabled\n"); + return; + } + + if (phys_enc->hw_pp->ops.enable_tearcheck) + phys_enc->hw_pp->ops.enable_tearcheck(phys_enc->hw_pp, false); + phys_enc->enable_state = DPU_ENC_DISABLED; +} + +static void dpu_encoder_phys_cmd_destroy(struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_phys_cmd *cmd_enc = + to_dpu_encoder_phys_cmd(phys_enc); + + kfree(cmd_enc); +} + +static void dpu_encoder_phys_cmd_get_hw_resources( + struct dpu_encoder_phys *phys_enc, + struct dpu_encoder_hw_resources *hw_res) +{ + hw_res->intfs[phys_enc->intf_idx - INTF_0] = INTF_MODE_CMD; +} + +static void dpu_encoder_phys_cmd_prepare_for_kickoff( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_phys_cmd *cmd_enc = + to_dpu_encoder_phys_cmd(phys_enc); + int ret; + + if (!phys_enc->hw_pp) { + DPU_ERROR("invalid encoder\n"); + return; + } + DRM_DEBUG_KMS("id:%u pp:%d pending_cnt:%d\n", DRMID(phys_enc->parent), + phys_enc->hw_pp->idx - PINGPONG_0, + atomic_read(&phys_enc->pending_kickoff_cnt)); + + /* + * Mark kickoff request as outstanding. If there are more than one, + * outstanding, then we have to wait for the previous one to complete + */ + ret = _dpu_encoder_phys_cmd_wait_for_idle(phys_enc); + if (ret) { + /* force pending_kickoff_cnt 0 to discard failed kickoff */ + atomic_set(&phys_enc->pending_kickoff_cnt, 0); + DRM_ERROR("failed wait_for_idle: id:%u ret:%d pp:%d\n", + DRMID(phys_enc->parent), ret, + phys_enc->hw_pp->idx - PINGPONG_0); + } + + DPU_DEBUG_CMDENC(cmd_enc, "pp:%d pending_cnt %d\n", + phys_enc->hw_pp->idx - PINGPONG_0, + atomic_read(&phys_enc->pending_kickoff_cnt)); +} + +static int _dpu_encoder_phys_cmd_wait_for_ctl_start( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_phys_cmd *cmd_enc = + to_dpu_encoder_phys_cmd(phys_enc); + struct dpu_encoder_wait_info wait_info; + int ret; + + wait_info.wq = &phys_enc->pending_kickoff_wq; + wait_info.atomic_cnt = &phys_enc->pending_ctlstart_cnt; + wait_info.timeout_ms = KICKOFF_TIMEOUT_MS; + + ret = dpu_encoder_helper_wait_for_irq(phys_enc, INTR_IDX_CTL_START, + &wait_info); + if (ret == -ETIMEDOUT) { + DPU_ERROR_CMDENC(cmd_enc, "ctl start interrupt wait failed\n"); + ret = -EINVAL; + } else if (!ret) + ret = 0; + + return ret; +} + +static int dpu_encoder_phys_cmd_wait_for_tx_complete( + struct dpu_encoder_phys *phys_enc) +{ + int rc; + + rc = _dpu_encoder_phys_cmd_wait_for_idle(phys_enc); + if (rc) { + DRM_ERROR("failed wait_for_idle: id:%u ret:%d intf:%d\n", + DRMID(phys_enc->parent), rc, + phys_enc->intf_idx - INTF_0); + } + + return rc; +} + +static int dpu_encoder_phys_cmd_wait_for_commit_done( + struct dpu_encoder_phys *phys_enc) +{ + int rc = 0; + struct dpu_encoder_phys_cmd *cmd_enc; + + cmd_enc = to_dpu_encoder_phys_cmd(phys_enc); + + /* only required for master controller */ + if (dpu_encoder_phys_cmd_is_master(phys_enc)) + rc = _dpu_encoder_phys_cmd_wait_for_ctl_start(phys_enc); + + /* required for both controllers */ + if (!rc && cmd_enc->serialize_wait4pp) + dpu_encoder_phys_cmd_prepare_for_kickoff(phys_enc); + + return rc; +} + +static int dpu_encoder_phys_cmd_wait_for_vblank( + struct dpu_encoder_phys *phys_enc) +{ + int rc = 0; + struct dpu_encoder_phys_cmd *cmd_enc; + struct dpu_encoder_wait_info wait_info; + + cmd_enc = to_dpu_encoder_phys_cmd(phys_enc); + + /* only required for master controller */ + if (!dpu_encoder_phys_cmd_is_master(phys_enc)) + return rc; + + wait_info.wq = &cmd_enc->pending_vblank_wq; + wait_info.atomic_cnt = &cmd_enc->pending_vblank_cnt; + wait_info.timeout_ms = KICKOFF_TIMEOUT_MS; + + atomic_inc(&cmd_enc->pending_vblank_cnt); + + rc = dpu_encoder_helper_wait_for_irq(phys_enc, INTR_IDX_RDPTR, + &wait_info); + + return rc; +} + +static void dpu_encoder_phys_cmd_handle_post_kickoff( + struct dpu_encoder_phys *phys_enc) +{ + /** + * re-enable external TE, either for the first time after enabling + * or if disabled for Autorefresh + */ + _dpu_encoder_phys_cmd_connect_te(phys_enc, true); +} + +static void dpu_encoder_phys_cmd_trigger_start( + struct dpu_encoder_phys *phys_enc) +{ + dpu_encoder_helper_trigger_start(phys_enc); +} + +static void dpu_encoder_phys_cmd_init_ops( + struct dpu_encoder_phys_ops *ops) +{ + ops->is_master = dpu_encoder_phys_cmd_is_master; + ops->mode_set = dpu_encoder_phys_cmd_mode_set; + ops->mode_fixup = dpu_encoder_phys_cmd_mode_fixup; + ops->enable = dpu_encoder_phys_cmd_enable; + ops->disable = dpu_encoder_phys_cmd_disable; + ops->destroy = dpu_encoder_phys_cmd_destroy; + ops->get_hw_resources = dpu_encoder_phys_cmd_get_hw_resources; + ops->control_vblank_irq = dpu_encoder_phys_cmd_control_vblank_irq; + ops->wait_for_commit_done = dpu_encoder_phys_cmd_wait_for_commit_done; + ops->prepare_for_kickoff = dpu_encoder_phys_cmd_prepare_for_kickoff; + ops->wait_for_tx_complete = dpu_encoder_phys_cmd_wait_for_tx_complete; + ops->wait_for_vblank = dpu_encoder_phys_cmd_wait_for_vblank; + ops->trigger_start = dpu_encoder_phys_cmd_trigger_start; + ops->needs_single_flush = dpu_encoder_phys_cmd_needs_single_flush; + ops->irq_control = dpu_encoder_phys_cmd_irq_control; + ops->restore = dpu_encoder_phys_cmd_enable_helper; + ops->prepare_idle_pc = dpu_encoder_phys_cmd_prepare_idle_pc; + ops->handle_post_kickoff = dpu_encoder_phys_cmd_handle_post_kickoff; + ops->get_line_count = dpu_encoder_phys_cmd_get_line_count; +} + +struct dpu_encoder_phys *dpu_encoder_phys_cmd_init( + struct dpu_enc_phys_init_params *p) +{ + struct dpu_encoder_phys *phys_enc = NULL; + struct dpu_encoder_phys_cmd *cmd_enc = NULL; + struct dpu_encoder_irq *irq; + int i, ret = 0; + + DPU_DEBUG("intf %d\n", p->intf_idx - INTF_0); + + cmd_enc = kzalloc(sizeof(*cmd_enc), GFP_KERNEL); + if (!cmd_enc) { + ret = -ENOMEM; + DPU_ERROR("failed to allocate\n"); + return ERR_PTR(ret); + } + phys_enc = &cmd_enc->base; + phys_enc->hw_mdptop = p->dpu_kms->hw_mdp; + phys_enc->intf_idx = p->intf_idx; + + dpu_encoder_phys_cmd_init_ops(&phys_enc->ops); + phys_enc->parent = p->parent; + phys_enc->parent_ops = p->parent_ops; + phys_enc->dpu_kms = p->dpu_kms; + phys_enc->split_role = p->split_role; + phys_enc->intf_mode = INTF_MODE_CMD; + phys_enc->enc_spinlock = p->enc_spinlock; + cmd_enc->stream_sel = 0; + phys_enc->enable_state = DPU_ENC_DISABLED; + for (i = 0; i < INTR_IDX_MAX; i++) { + irq = &phys_enc->irq[i]; + INIT_LIST_HEAD(&irq->cb.list); + irq->irq_idx = -EINVAL; + irq->hw_idx = -EINVAL; + irq->cb.arg = phys_enc; + } + + irq = &phys_enc->irq[INTR_IDX_CTL_START]; + irq->name = "ctl_start"; + irq->intr_type = DPU_IRQ_TYPE_CTL_START; + irq->intr_idx = INTR_IDX_CTL_START; + irq->cb.func = dpu_encoder_phys_cmd_ctl_start_irq; + + irq = &phys_enc->irq[INTR_IDX_PINGPONG]; + irq->name = "pp_done"; + irq->intr_type = DPU_IRQ_TYPE_PING_PONG_COMP; + irq->intr_idx = INTR_IDX_PINGPONG; + irq->cb.func = dpu_encoder_phys_cmd_pp_tx_done_irq; + + irq = &phys_enc->irq[INTR_IDX_RDPTR]; + irq->name = "pp_rd_ptr"; + irq->intr_type = DPU_IRQ_TYPE_PING_PONG_RD_PTR; + irq->intr_idx = INTR_IDX_RDPTR; + irq->cb.func = dpu_encoder_phys_cmd_pp_rd_ptr_irq; + + irq = &phys_enc->irq[INTR_IDX_UNDERRUN]; + irq->name = "underrun"; + irq->intr_type = DPU_IRQ_TYPE_INTF_UNDER_RUN; + irq->intr_idx = INTR_IDX_UNDERRUN; + irq->cb.func = dpu_encoder_phys_cmd_underrun_irq; + + atomic_set(&phys_enc->vblank_refcount, 0); + atomic_set(&phys_enc->pending_kickoff_cnt, 0); + atomic_set(&phys_enc->pending_ctlstart_cnt, 0); + atomic_set(&cmd_enc->pending_vblank_cnt, 0); + init_waitqueue_head(&phys_enc->pending_kickoff_wq); + init_waitqueue_head(&cmd_enc->pending_vblank_wq); + + DPU_DEBUG_CMDENC(cmd_enc, "created\n"); + + return phys_enc; +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_vid.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_vid.c new file mode 100644 index 000000000..805e059b5 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_encoder_phys_vid.c @@ -0,0 +1,745 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ +#include "dpu_encoder_phys.h" +#include "dpu_hw_interrupts.h" +#include "dpu_core_irq.h" +#include "dpu_formats.h" +#include "dpu_trace.h" + +#define DPU_DEBUG_VIDENC(e, fmt, ...) DPU_DEBUG("enc%d intf%d " fmt, \ + (e) && (e)->parent ? \ + (e)->parent->base.id : -1, \ + (e) && (e)->hw_intf ? \ + (e)->hw_intf->idx - INTF_0 : -1, ##__VA_ARGS__) + +#define DPU_ERROR_VIDENC(e, fmt, ...) DPU_ERROR("enc%d intf%d " fmt, \ + (e) && (e)->parent ? \ + (e)->parent->base.id : -1, \ + (e) && (e)->hw_intf ? \ + (e)->hw_intf->idx - INTF_0 : -1, ##__VA_ARGS__) + +#define to_dpu_encoder_phys_vid(x) \ + container_of(x, struct dpu_encoder_phys_vid, base) + +static bool dpu_encoder_phys_vid_is_master( + struct dpu_encoder_phys *phys_enc) +{ + bool ret = false; + + if (phys_enc->split_role != ENC_ROLE_SLAVE) + ret = true; + + return ret; +} + +static void drm_mode_to_intf_timing_params( + const struct dpu_encoder_phys *phys_enc, + const struct drm_display_mode *mode, + struct intf_timing_params *timing) +{ + memset(timing, 0, sizeof(*timing)); + + if ((mode->htotal < mode->hsync_end) + || (mode->hsync_start < mode->hdisplay) + || (mode->vtotal < mode->vsync_end) + || (mode->vsync_start < mode->vdisplay) + || (mode->hsync_end < mode->hsync_start) + || (mode->vsync_end < mode->vsync_start)) { + DPU_ERROR( + "invalid params - hstart:%d,hend:%d,htot:%d,hdisplay:%d\n", + mode->hsync_start, mode->hsync_end, + mode->htotal, mode->hdisplay); + DPU_ERROR("vstart:%d,vend:%d,vtot:%d,vdisplay:%d\n", + mode->vsync_start, mode->vsync_end, + mode->vtotal, mode->vdisplay); + return; + } + + /* + * https://www.kernel.org/doc/htmldocs/drm/ch02s05.html + * Active Region Front Porch Sync Back Porch + * <-----------------><------------><-----><-----------> + * <- [hv]display ---> + * <--------- [hv]sync_start ------> + * <----------------- [hv]sync_end -------> + * <---------------------------- [hv]total -------------> + */ + timing->width = mode->hdisplay; /* active width */ + timing->height = mode->vdisplay; /* active height */ + timing->xres = timing->width; + timing->yres = timing->height; + timing->h_back_porch = mode->htotal - mode->hsync_end; + timing->h_front_porch = mode->hsync_start - mode->hdisplay; + timing->v_back_porch = mode->vtotal - mode->vsync_end; + timing->v_front_porch = mode->vsync_start - mode->vdisplay; + timing->hsync_pulse_width = mode->hsync_end - mode->hsync_start; + timing->vsync_pulse_width = mode->vsync_end - mode->vsync_start; + timing->hsync_polarity = (mode->flags & DRM_MODE_FLAG_NHSYNC) ? 1 : 0; + timing->vsync_polarity = (mode->flags & DRM_MODE_FLAG_NVSYNC) ? 1 : 0; + timing->border_clr = 0; + timing->underflow_clr = 0xff; + timing->hsync_skew = mode->hskew; + + /* DSI controller cannot handle active-low sync signals. */ + if (phys_enc->hw_intf->cap->type == INTF_DSI) { + timing->hsync_polarity = 0; + timing->vsync_polarity = 0; + } + + /* + * For edp only: + * DISPLAY_V_START = (VBP * HCYCLE) + HBP + * DISPLAY_V_END = (VBP + VACTIVE) * HCYCLE - 1 - HFP + */ + /* + * if (vid_enc->hw->cap->type == INTF_EDP) { + * display_v_start += mode->htotal - mode->hsync_start; + * display_v_end -= mode->hsync_start - mode->hdisplay; + * } + */ + /* for DP/EDP, Shift timings to align it to bottom right */ + if ((phys_enc->hw_intf->cap->type == INTF_DP) || + (phys_enc->hw_intf->cap->type == INTF_EDP)) { + timing->h_back_porch += timing->h_front_porch; + timing->h_front_porch = 0; + timing->v_back_porch += timing->v_front_porch; + timing->v_front_porch = 0; + } +} + +static u32 get_horizontal_total(const struct intf_timing_params *timing) +{ + u32 active = timing->xres; + u32 inactive = + timing->h_back_porch + timing->h_front_porch + + timing->hsync_pulse_width; + return active + inactive; +} + +static u32 get_vertical_total(const struct intf_timing_params *timing) +{ + u32 active = timing->yres; + u32 inactive = + timing->v_back_porch + timing->v_front_porch + + timing->vsync_pulse_width; + return active + inactive; +} + +/* + * programmable_fetch_get_num_lines: + * Number of fetch lines in vertical front porch + * @timing: Pointer to the intf timing information for the requested mode + * + * Returns the number of fetch lines in vertical front porch at which mdp + * can start fetching the next frame. + * + * Number of needed prefetch lines is anything that cannot be absorbed in the + * start of frame time (back porch + vsync pulse width). + * + * Some panels have very large VFP, however we only need a total number of + * lines based on the chip worst case latencies. + */ +static u32 programmable_fetch_get_num_lines( + struct dpu_encoder_phys *phys_enc, + const struct intf_timing_params *timing) +{ + u32 worst_case_needed_lines = + phys_enc->hw_intf->cap->prog_fetch_lines_worst_case; + u32 start_of_frame_lines = + timing->v_back_porch + timing->vsync_pulse_width; + u32 needed_vfp_lines = worst_case_needed_lines - start_of_frame_lines; + u32 actual_vfp_lines = 0; + + /* Fetch must be outside active lines, otherwise undefined. */ + if (start_of_frame_lines >= worst_case_needed_lines) { + DPU_DEBUG_VIDENC(phys_enc, + "prog fetch is not needed, large vbp+vsw\n"); + actual_vfp_lines = 0; + } else if (timing->v_front_porch < needed_vfp_lines) { + /* Warn fetch needed, but not enough porch in panel config */ + pr_warn_once + ("low vbp+vfp may lead to perf issues in some cases\n"); + DPU_DEBUG_VIDENC(phys_enc, + "less vfp than fetch req, using entire vfp\n"); + actual_vfp_lines = timing->v_front_porch; + } else { + DPU_DEBUG_VIDENC(phys_enc, "room in vfp for needed prefetch\n"); + actual_vfp_lines = needed_vfp_lines; + } + + DPU_DEBUG_VIDENC(phys_enc, + "v_front_porch %u v_back_porch %u vsync_pulse_width %u\n", + timing->v_front_porch, timing->v_back_porch, + timing->vsync_pulse_width); + DPU_DEBUG_VIDENC(phys_enc, + "wc_lines %u needed_vfp_lines %u actual_vfp_lines %u\n", + worst_case_needed_lines, needed_vfp_lines, actual_vfp_lines); + + return actual_vfp_lines; +} + +/* + * programmable_fetch_config: Programs HW to prefetch lines by offsetting + * the start of fetch into the vertical front porch for cases where the + * vsync pulse width and vertical back porch time is insufficient + * + * Gets # of lines to pre-fetch, then calculate VSYNC counter value. + * HW layer requires VSYNC counter of first pixel of tgt VFP line. + * + * @timing: Pointer to the intf timing information for the requested mode + */ +static void programmable_fetch_config(struct dpu_encoder_phys *phys_enc, + const struct intf_timing_params *timing) +{ + struct intf_prog_fetch f = { 0 }; + u32 vfp_fetch_lines = 0; + u32 horiz_total = 0; + u32 vert_total = 0; + u32 vfp_fetch_start_vsync_counter = 0; + unsigned long lock_flags; + + if (WARN_ON_ONCE(!phys_enc->hw_intf->ops.setup_prg_fetch)) + return; + + vfp_fetch_lines = programmable_fetch_get_num_lines(phys_enc, timing); + if (vfp_fetch_lines) { + vert_total = get_vertical_total(timing); + horiz_total = get_horizontal_total(timing); + vfp_fetch_start_vsync_counter = + (vert_total - vfp_fetch_lines) * horiz_total + 1; + f.enable = 1; + f.fetch_start = vfp_fetch_start_vsync_counter; + } + + DPU_DEBUG_VIDENC(phys_enc, + "vfp_fetch_lines %u vfp_fetch_start_vsync_counter %u\n", + vfp_fetch_lines, vfp_fetch_start_vsync_counter); + + spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags); + phys_enc->hw_intf->ops.setup_prg_fetch(phys_enc->hw_intf, &f); + spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags); +} + +static bool dpu_encoder_phys_vid_mode_fixup( + struct dpu_encoder_phys *phys_enc, + const struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + DPU_DEBUG_VIDENC(phys_enc, "\n"); + + /* + * Modifying mode has consequences when the mode comes back to us + */ + return true; +} + +static void dpu_encoder_phys_vid_setup_timing_engine( + struct dpu_encoder_phys *phys_enc) +{ + struct drm_display_mode mode; + struct intf_timing_params timing_params = { 0 }; + const struct dpu_format *fmt = NULL; + u32 fmt_fourcc = DRM_FORMAT_RGB888; + unsigned long lock_flags; + struct dpu_hw_intf_cfg intf_cfg = { 0 }; + + if (!phys_enc->hw_ctl->ops.setup_intf_cfg) { + DPU_ERROR("invalid encoder %d\n", phys_enc != NULL); + return; + } + + mode = phys_enc->cached_mode; + if (!phys_enc->hw_intf->ops.setup_timing_gen) { + DPU_ERROR("timing engine setup is not supported\n"); + return; + } + + DPU_DEBUG_VIDENC(phys_enc, "enabling mode:\n"); + drm_mode_debug_printmodeline(&mode); + + if (phys_enc->split_role != ENC_ROLE_SOLO) { + mode.hdisplay >>= 1; + mode.htotal >>= 1; + mode.hsync_start >>= 1; + mode.hsync_end >>= 1; + + DPU_DEBUG_VIDENC(phys_enc, + "split_role %d, halve horizontal %d %d %d %d\n", + phys_enc->split_role, + mode.hdisplay, mode.htotal, + mode.hsync_start, mode.hsync_end); + } + + drm_mode_to_intf_timing_params(phys_enc, &mode, &timing_params); + + fmt = dpu_get_dpu_format(fmt_fourcc); + DPU_DEBUG_VIDENC(phys_enc, "fmt_fourcc 0x%X\n", fmt_fourcc); + + intf_cfg.intf = phys_enc->hw_intf->idx; + intf_cfg.intf_mode_sel = DPU_CTL_MODE_SEL_VID; + intf_cfg.stream_sel = 0; /* Don't care value for video mode */ + intf_cfg.mode_3d = dpu_encoder_helper_get_3d_blend_mode(phys_enc); + + spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags); + phys_enc->hw_intf->ops.setup_timing_gen(phys_enc->hw_intf, + &timing_params, fmt); + phys_enc->hw_ctl->ops.setup_intf_cfg(phys_enc->hw_ctl, &intf_cfg); + + /* setup which pp blk will connect to this intf */ + if (phys_enc->hw_intf->ops.bind_pingpong_blk) + phys_enc->hw_intf->ops.bind_pingpong_blk( + phys_enc->hw_intf, + true, + phys_enc->hw_pp->idx); + + spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags); + + programmable_fetch_config(phys_enc, &timing_params); +} + +static void dpu_encoder_phys_vid_vblank_irq(void *arg, int irq_idx) +{ + struct dpu_encoder_phys *phys_enc = arg; + struct dpu_hw_ctl *hw_ctl; + unsigned long lock_flags; + u32 flush_register = 0; + + hw_ctl = phys_enc->hw_ctl; + + DPU_ATRACE_BEGIN("vblank_irq"); + + if (phys_enc->parent_ops->handle_vblank_virt) + phys_enc->parent_ops->handle_vblank_virt(phys_enc->parent, + phys_enc); + + atomic_read(&phys_enc->pending_kickoff_cnt); + + /* + * only decrement the pending flush count if we've actually flushed + * hardware. due to sw irq latency, vblank may have already happened + * so we need to double-check with hw that it accepted the flush bits + */ + spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags); + if (hw_ctl->ops.get_flush_register) + flush_register = hw_ctl->ops.get_flush_register(hw_ctl); + + if (!(flush_register & hw_ctl->ops.get_pending_flush(hw_ctl))) + atomic_add_unless(&phys_enc->pending_kickoff_cnt, -1, 0); + spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags); + + /* Signal any waiting atomic commit thread */ + wake_up_all(&phys_enc->pending_kickoff_wq); + + phys_enc->parent_ops->handle_frame_done(phys_enc->parent, phys_enc, + DPU_ENCODER_FRAME_EVENT_DONE); + + DPU_ATRACE_END("vblank_irq"); +} + +static void dpu_encoder_phys_vid_underrun_irq(void *arg, int irq_idx) +{ + struct dpu_encoder_phys *phys_enc = arg; + + if (phys_enc->parent_ops->handle_underrun_virt) + phys_enc->parent_ops->handle_underrun_virt(phys_enc->parent, + phys_enc); +} + +static bool dpu_encoder_phys_vid_needs_single_flush( + struct dpu_encoder_phys *phys_enc) +{ + return phys_enc->split_role != ENC_ROLE_SOLO; +} + +static void _dpu_encoder_phys_vid_setup_irq_hw_idx( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_irq *irq; + + /* + * Initialize irq->hw_idx only when irq is not registered. + * Prevent invalidating irq->irq_idx as modeset may be + * called many times during dfps. + */ + + irq = &phys_enc->irq[INTR_IDX_VSYNC]; + if (irq->irq_idx < 0) + irq->hw_idx = phys_enc->intf_idx; + + irq = &phys_enc->irq[INTR_IDX_UNDERRUN]; + if (irq->irq_idx < 0) + irq->hw_idx = phys_enc->intf_idx; +} + +static void dpu_encoder_phys_vid_mode_set( + struct dpu_encoder_phys *phys_enc, + struct drm_display_mode *mode, + struct drm_display_mode *adj_mode) +{ + if (adj_mode) { + phys_enc->cached_mode = *adj_mode; + drm_mode_debug_printmodeline(adj_mode); + DPU_DEBUG_VIDENC(phys_enc, "caching mode:\n"); + } + + _dpu_encoder_phys_vid_setup_irq_hw_idx(phys_enc); +} + +static int dpu_encoder_phys_vid_control_vblank_irq( + struct dpu_encoder_phys *phys_enc, + bool enable) +{ + int ret = 0; + int refcount; + + refcount = atomic_read(&phys_enc->vblank_refcount); + + /* Slave encoders don't report vblank */ + if (!dpu_encoder_phys_vid_is_master(phys_enc)) + goto end; + + /* protect against negative */ + if (!enable && refcount == 0) { + ret = -EINVAL; + goto end; + } + + DRM_DEBUG_KMS("id:%u enable=%d/%d\n", DRMID(phys_enc->parent), enable, + atomic_read(&phys_enc->vblank_refcount)); + + if (enable && atomic_inc_return(&phys_enc->vblank_refcount) == 1) + ret = dpu_encoder_helper_register_irq(phys_enc, INTR_IDX_VSYNC); + else if (!enable && atomic_dec_return(&phys_enc->vblank_refcount) == 0) + ret = dpu_encoder_helper_unregister_irq(phys_enc, + INTR_IDX_VSYNC); + +end: + if (ret) { + DRM_ERROR("failed: id:%u intf:%d ret:%d enable:%d refcnt:%d\n", + DRMID(phys_enc->parent), + phys_enc->hw_intf->idx - INTF_0, ret, enable, + refcount); + } + return ret; +} + +static void dpu_encoder_phys_vid_enable(struct dpu_encoder_phys *phys_enc) +{ + struct dpu_hw_ctl *ctl; + u32 flush_mask = 0; + u32 intf_flush_mask = 0; + + ctl = phys_enc->hw_ctl; + + DPU_DEBUG_VIDENC(phys_enc, "\n"); + + if (WARN_ON(!phys_enc->hw_intf->ops.enable_timing)) + return; + + dpu_encoder_helper_split_config(phys_enc, phys_enc->hw_intf->idx); + + dpu_encoder_phys_vid_setup_timing_engine(phys_enc); + + /* + * For single flush cases (dual-ctl or pp-split), skip setting the + * flush bit for the slave intf, since both intfs use same ctl + * and HW will only flush the master. + */ + if (dpu_encoder_phys_vid_needs_single_flush(phys_enc) && + !dpu_encoder_phys_vid_is_master(phys_enc)) + goto skip_flush; + + ctl->ops.get_bitmask_intf(ctl, &flush_mask, phys_enc->hw_intf->idx); + ctl->ops.update_pending_flush(ctl, flush_mask); + + if (ctl->ops.get_bitmask_active_intf) + ctl->ops.get_bitmask_active_intf(ctl, &intf_flush_mask, + phys_enc->hw_intf->idx); + + if (ctl->ops.update_pending_intf_flush) + ctl->ops.update_pending_intf_flush(ctl, intf_flush_mask); + +skip_flush: + DPU_DEBUG_VIDENC(phys_enc, + "update pending flush ctl %d flush_mask 0%x intf_mask 0x%x\n", + ctl->idx - CTL_0, flush_mask, intf_flush_mask); + + + /* ctl_flush & timing engine enable will be triggered by framework */ + if (phys_enc->enable_state == DPU_ENC_DISABLED) + phys_enc->enable_state = DPU_ENC_ENABLING; +} + +static void dpu_encoder_phys_vid_destroy(struct dpu_encoder_phys *phys_enc) +{ + DPU_DEBUG_VIDENC(phys_enc, "\n"); + kfree(phys_enc); +} + +static void dpu_encoder_phys_vid_get_hw_resources( + struct dpu_encoder_phys *phys_enc, + struct dpu_encoder_hw_resources *hw_res) +{ + hw_res->intfs[phys_enc->intf_idx - INTF_0] = INTF_MODE_VIDEO; +} + +static int dpu_encoder_phys_vid_wait_for_vblank( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_encoder_wait_info wait_info; + int ret; + + wait_info.wq = &phys_enc->pending_kickoff_wq; + wait_info.atomic_cnt = &phys_enc->pending_kickoff_cnt; + wait_info.timeout_ms = KICKOFF_TIMEOUT_MS; + + if (!dpu_encoder_phys_vid_is_master(phys_enc)) { + return 0; + } + + /* Wait for kickoff to complete */ + ret = dpu_encoder_helper_wait_for_irq(phys_enc, INTR_IDX_VSYNC, + &wait_info); + + if (ret == -ETIMEDOUT) { + dpu_encoder_helper_report_irq_timeout(phys_enc, INTR_IDX_VSYNC); + } + + return ret; +} + +static int dpu_encoder_phys_vid_wait_for_commit_done( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_hw_ctl *hw_ctl = phys_enc->hw_ctl; + int ret; + + if (!hw_ctl) + return 0; + + ret = wait_event_timeout(phys_enc->pending_kickoff_wq, + (hw_ctl->ops.get_flush_register(hw_ctl) == 0), + msecs_to_jiffies(50)); + if (ret <= 0) { + DPU_ERROR("vblank timeout\n"); + return -ETIMEDOUT; + } + + return 0; +} + +static void dpu_encoder_phys_vid_prepare_for_kickoff( + struct dpu_encoder_phys *phys_enc) +{ + struct dpu_hw_ctl *ctl; + int rc; + + ctl = phys_enc->hw_ctl; + if (!ctl->ops.wait_reset_status) + return; + + /* + * hw supports hardware initiated ctl reset, so before we kickoff a new + * frame, need to check and wait for hw initiated ctl reset completion + */ + rc = ctl->ops.wait_reset_status(ctl); + if (rc) { + DPU_ERROR_VIDENC(phys_enc, "ctl %d reset failure: %d\n", + ctl->idx, rc); + dpu_encoder_helper_unregister_irq(phys_enc, INTR_IDX_VSYNC); + } +} + +static void dpu_encoder_phys_vid_disable(struct dpu_encoder_phys *phys_enc) +{ + unsigned long lock_flags; + int ret; + + if (!phys_enc->parent || !phys_enc->parent->dev) { + DPU_ERROR("invalid encoder/device\n"); + return; + } + + if (!phys_enc->hw_intf) { + DPU_ERROR("invalid hw_intf %d hw_ctl %d\n", + phys_enc->hw_intf != NULL, phys_enc->hw_ctl != NULL); + return; + } + + if (WARN_ON(!phys_enc->hw_intf->ops.enable_timing)) + return; + + if (phys_enc->enable_state == DPU_ENC_DISABLED) { + DPU_ERROR("already disabled\n"); + return; + } + + spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags); + phys_enc->hw_intf->ops.enable_timing(phys_enc->hw_intf, 0); + if (dpu_encoder_phys_vid_is_master(phys_enc)) + dpu_encoder_phys_inc_pending(phys_enc); + spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags); + + /* + * Wait for a vsync so we know the ENABLE=0 latched before + * the (connector) source of the vsync's gets disabled, + * otherwise we end up in a funny state if we re-enable + * before the disable latches, which results that some of + * the settings changes for the new modeset (like new + * scanout buffer) don't latch properly.. + */ + if (dpu_encoder_phys_vid_is_master(phys_enc)) { + ret = dpu_encoder_phys_vid_wait_for_vblank(phys_enc); + if (ret) { + atomic_set(&phys_enc->pending_kickoff_cnt, 0); + DRM_ERROR("wait disable failed: id:%u intf:%d ret:%d\n", + DRMID(phys_enc->parent), + phys_enc->hw_intf->idx - INTF_0, ret); + } + } + + phys_enc->enable_state = DPU_ENC_DISABLED; +} + +static void dpu_encoder_phys_vid_handle_post_kickoff( + struct dpu_encoder_phys *phys_enc) +{ + unsigned long lock_flags; + + /* + * Video mode must flush CTL before enabling timing engine + * Video encoders need to turn on their interfaces now + */ + if (phys_enc->enable_state == DPU_ENC_ENABLING) { + trace_dpu_enc_phys_vid_post_kickoff(DRMID(phys_enc->parent), + phys_enc->hw_intf->idx - INTF_0); + spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags); + phys_enc->hw_intf->ops.enable_timing(phys_enc->hw_intf, 1); + spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags); + phys_enc->enable_state = DPU_ENC_ENABLED; + } +} + +static void dpu_encoder_phys_vid_irq_control(struct dpu_encoder_phys *phys_enc, + bool enable) +{ + int ret; + + trace_dpu_enc_phys_vid_irq_ctrl(DRMID(phys_enc->parent), + phys_enc->hw_intf->idx - INTF_0, + enable, + atomic_read(&phys_enc->vblank_refcount)); + + if (enable) { + ret = dpu_encoder_phys_vid_control_vblank_irq(phys_enc, true); + if (ret) + return; + + dpu_encoder_helper_register_irq(phys_enc, INTR_IDX_UNDERRUN); + } else { + dpu_encoder_phys_vid_control_vblank_irq(phys_enc, false); + dpu_encoder_helper_unregister_irq(phys_enc, INTR_IDX_UNDERRUN); + } +} + +static int dpu_encoder_phys_vid_get_line_count( + struct dpu_encoder_phys *phys_enc) +{ + if (!dpu_encoder_phys_vid_is_master(phys_enc)) + return -EINVAL; + + if (!phys_enc->hw_intf || !phys_enc->hw_intf->ops.get_line_count) + return -EINVAL; + + return phys_enc->hw_intf->ops.get_line_count(phys_enc->hw_intf); +} + +static void dpu_encoder_phys_vid_init_ops(struct dpu_encoder_phys_ops *ops) +{ + ops->is_master = dpu_encoder_phys_vid_is_master; + ops->mode_set = dpu_encoder_phys_vid_mode_set; + ops->mode_fixup = dpu_encoder_phys_vid_mode_fixup; + ops->enable = dpu_encoder_phys_vid_enable; + ops->disable = dpu_encoder_phys_vid_disable; + ops->destroy = dpu_encoder_phys_vid_destroy; + ops->get_hw_resources = dpu_encoder_phys_vid_get_hw_resources; + ops->control_vblank_irq = dpu_encoder_phys_vid_control_vblank_irq; + ops->wait_for_commit_done = dpu_encoder_phys_vid_wait_for_commit_done; + ops->wait_for_vblank = dpu_encoder_phys_vid_wait_for_vblank; + ops->wait_for_tx_complete = dpu_encoder_phys_vid_wait_for_vblank; + ops->irq_control = dpu_encoder_phys_vid_irq_control; + ops->prepare_for_kickoff = dpu_encoder_phys_vid_prepare_for_kickoff; + ops->handle_post_kickoff = dpu_encoder_phys_vid_handle_post_kickoff; + ops->needs_single_flush = dpu_encoder_phys_vid_needs_single_flush; + ops->get_line_count = dpu_encoder_phys_vid_get_line_count; +} + +struct dpu_encoder_phys *dpu_encoder_phys_vid_init( + struct dpu_enc_phys_init_params *p) +{ + struct dpu_encoder_phys *phys_enc = NULL; + struct dpu_encoder_irq *irq; + int i, ret = 0; + + if (!p) { + ret = -EINVAL; + goto fail; + } + + phys_enc = kzalloc(sizeof(*phys_enc), GFP_KERNEL); + if (!phys_enc) { + ret = -ENOMEM; + goto fail; + } + + phys_enc->hw_mdptop = p->dpu_kms->hw_mdp; + phys_enc->intf_idx = p->intf_idx; + + DPU_DEBUG_VIDENC(phys_enc, "\n"); + + dpu_encoder_phys_vid_init_ops(&phys_enc->ops); + phys_enc->parent = p->parent; + phys_enc->parent_ops = p->parent_ops; + phys_enc->dpu_kms = p->dpu_kms; + phys_enc->split_role = p->split_role; + phys_enc->intf_mode = INTF_MODE_VIDEO; + phys_enc->enc_spinlock = p->enc_spinlock; + for (i = 0; i < INTR_IDX_MAX; i++) { + irq = &phys_enc->irq[i]; + INIT_LIST_HEAD(&irq->cb.list); + irq->irq_idx = -EINVAL; + irq->hw_idx = -EINVAL; + irq->cb.arg = phys_enc; + } + + irq = &phys_enc->irq[INTR_IDX_VSYNC]; + irq->name = "vsync_irq"; + irq->intr_type = DPU_IRQ_TYPE_INTF_VSYNC; + irq->intr_idx = INTR_IDX_VSYNC; + irq->cb.func = dpu_encoder_phys_vid_vblank_irq; + + irq = &phys_enc->irq[INTR_IDX_UNDERRUN]; + irq->name = "underrun"; + irq->intr_type = DPU_IRQ_TYPE_INTF_UNDER_RUN; + irq->intr_idx = INTR_IDX_UNDERRUN; + irq->cb.func = dpu_encoder_phys_vid_underrun_irq; + + atomic_set(&phys_enc->vblank_refcount, 0); + atomic_set(&phys_enc->pending_kickoff_cnt, 0); + init_waitqueue_head(&phys_enc->pending_kickoff_wq); + phys_enc->enable_state = DPU_ENC_DISABLED; + + DPU_DEBUG_VIDENC(phys_enc, "created intf idx:%d\n", p->intf_idx); + + return phys_enc; + +fail: + DPU_ERROR("failed to create encoder\n"); + if (phys_enc) + dpu_encoder_phys_vid_destroy(phys_enc); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_formats.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_formats.c new file mode 100644 index 000000000..a05282ded --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_formats.c @@ -0,0 +1,1042 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ + +#include <uapi/drm/drm_fourcc.h> + +#include "msm_media_info.h" +#include "dpu_kms.h" +#include "dpu_formats.h" + +#define DPU_UBWC_META_MACRO_W_H 16 +#define DPU_UBWC_META_BLOCK_SIZE 256 +#define DPU_UBWC_PLANE_SIZE_ALIGNMENT 4096 + +#define DPU_TILE_HEIGHT_DEFAULT 1 +#define DPU_TILE_HEIGHT_TILED 4 +#define DPU_TILE_HEIGHT_UBWC 4 +#define DPU_TILE_HEIGHT_NV12 8 + +#define DPU_MAX_IMG_WIDTH 0x3FFF +#define DPU_MAX_IMG_HEIGHT 0x3FFF + +/** + * DPU supported format packing, bpp, and other format + * information. + * DPU currently only supports interleaved RGB formats + * UBWC support for a pixel format is indicated by the flag, + * there is additional meta data plane for such formats + */ + +#define INTERLEAVED_RGB_FMT(fmt, a, r, g, b, e0, e1, e2, e3, uc, alpha, \ +bp, flg, fm, np) \ +{ \ + .base.pixel_format = DRM_FORMAT_ ## fmt, \ + .fetch_planes = DPU_PLANE_INTERLEAVED, \ + .alpha_enable = alpha, \ + .element = { (e0), (e1), (e2), (e3) }, \ + .bits = { g, b, r, a }, \ + .chroma_sample = DPU_CHROMA_RGB, \ + .unpack_align_msb = 0, \ + .unpack_tight = 1, \ + .unpack_count = uc, \ + .bpp = bp, \ + .fetch_mode = fm, \ + .flag = {(flg)}, \ + .num_planes = np, \ + .tile_height = DPU_TILE_HEIGHT_DEFAULT \ +} + +#define INTERLEAVED_RGB_FMT_TILED(fmt, a, r, g, b, e0, e1, e2, e3, uc, \ +alpha, bp, flg, fm, np, th) \ +{ \ + .base.pixel_format = DRM_FORMAT_ ## fmt, \ + .fetch_planes = DPU_PLANE_INTERLEAVED, \ + .alpha_enable = alpha, \ + .element = { (e0), (e1), (e2), (e3) }, \ + .bits = { g, b, r, a }, \ + .chroma_sample = DPU_CHROMA_RGB, \ + .unpack_align_msb = 0, \ + .unpack_tight = 1, \ + .unpack_count = uc, \ + .bpp = bp, \ + .fetch_mode = fm, \ + .flag = {(flg)}, \ + .num_planes = np, \ + .tile_height = th \ +} + + +#define INTERLEAVED_YUV_FMT(fmt, a, r, g, b, e0, e1, e2, e3, \ +alpha, chroma, count, bp, flg, fm, np) \ +{ \ + .base.pixel_format = DRM_FORMAT_ ## fmt, \ + .fetch_planes = DPU_PLANE_INTERLEAVED, \ + .alpha_enable = alpha, \ + .element = { (e0), (e1), (e2), (e3)}, \ + .bits = { g, b, r, a }, \ + .chroma_sample = chroma, \ + .unpack_align_msb = 0, \ + .unpack_tight = 1, \ + .unpack_count = count, \ + .bpp = bp, \ + .fetch_mode = fm, \ + .flag = {(flg)}, \ + .num_planes = np, \ + .tile_height = DPU_TILE_HEIGHT_DEFAULT \ +} + +#define PSEUDO_YUV_FMT(fmt, a, r, g, b, e0, e1, chroma, flg, fm, np) \ +{ \ + .base.pixel_format = DRM_FORMAT_ ## fmt, \ + .fetch_planes = DPU_PLANE_PSEUDO_PLANAR, \ + .alpha_enable = false, \ + .element = { (e0), (e1), 0, 0 }, \ + .bits = { g, b, r, a }, \ + .chroma_sample = chroma, \ + .unpack_align_msb = 0, \ + .unpack_tight = 1, \ + .unpack_count = 2, \ + .bpp = 2, \ + .fetch_mode = fm, \ + .flag = {(flg)}, \ + .num_planes = np, \ + .tile_height = DPU_TILE_HEIGHT_DEFAULT \ +} + +#define PSEUDO_YUV_FMT_TILED(fmt, a, r, g, b, e0, e1, chroma, \ +flg, fm, np, th) \ +{ \ + .base.pixel_format = DRM_FORMAT_ ## fmt, \ + .fetch_planes = DPU_PLANE_PSEUDO_PLANAR, \ + .alpha_enable = false, \ + .element = { (e0), (e1), 0, 0 }, \ + .bits = { g, b, r, a }, \ + .chroma_sample = chroma, \ + .unpack_align_msb = 0, \ + .unpack_tight = 1, \ + .unpack_count = 2, \ + .bpp = 2, \ + .fetch_mode = fm, \ + .flag = {(flg)}, \ + .num_planes = np, \ + .tile_height = th \ +} + +#define PSEUDO_YUV_FMT_LOOSE(fmt, a, r, g, b, e0, e1, chroma, flg, fm, np)\ +{ \ + .base.pixel_format = DRM_FORMAT_ ## fmt, \ + .fetch_planes = DPU_PLANE_PSEUDO_PLANAR, \ + .alpha_enable = false, \ + .element = { (e0), (e1), 0, 0 }, \ + .bits = { g, b, r, a }, \ + .chroma_sample = chroma, \ + .unpack_align_msb = 1, \ + .unpack_tight = 0, \ + .unpack_count = 2, \ + .bpp = 2, \ + .fetch_mode = fm, \ + .flag = {(flg)}, \ + .num_planes = np, \ + .tile_height = DPU_TILE_HEIGHT_DEFAULT \ +} + +#define PSEUDO_YUV_FMT_LOOSE_TILED(fmt, a, r, g, b, e0, e1, chroma, \ +flg, fm, np, th) \ +{ \ + .base.pixel_format = DRM_FORMAT_ ## fmt, \ + .fetch_planes = DPU_PLANE_PSEUDO_PLANAR, \ + .alpha_enable = false, \ + .element = { (e0), (e1), 0, 0 }, \ + .bits = { g, b, r, a }, \ + .chroma_sample = chroma, \ + .unpack_align_msb = 1, \ + .unpack_tight = 0, \ + .unpack_count = 2, \ + .bpp = 2, \ + .fetch_mode = fm, \ + .flag = {(flg)}, \ + .num_planes = np, \ + .tile_height = th \ +} + + +#define PLANAR_YUV_FMT(fmt, a, r, g, b, e0, e1, e2, alpha, chroma, bp, \ +flg, fm, np) \ +{ \ + .base.pixel_format = DRM_FORMAT_ ## fmt, \ + .fetch_planes = DPU_PLANE_PLANAR, \ + .alpha_enable = alpha, \ + .element = { (e0), (e1), (e2), 0 }, \ + .bits = { g, b, r, a }, \ + .chroma_sample = chroma, \ + .unpack_align_msb = 0, \ + .unpack_tight = 1, \ + .unpack_count = 1, \ + .bpp = bp, \ + .fetch_mode = fm, \ + .flag = {(flg)}, \ + .num_planes = np, \ + .tile_height = DPU_TILE_HEIGHT_DEFAULT \ +} + +/* + * struct dpu_media_color_map - maps drm format to media format + * @format: DRM base pixel format + * @color: Media API color related to DRM format + */ +struct dpu_media_color_map { + uint32_t format; + uint32_t color; +}; + +static const struct dpu_format dpu_format_map[] = { + INTERLEAVED_RGB_FMT(ARGB8888, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4, + true, 4, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(ABGR8888, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + true, 4, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(XBGR8888, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + false, 4, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(RGBA8888, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4, + true, 4, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(BGRA8888, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4, + true, 4, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(BGRX8888, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4, + false, 4, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(XRGB8888, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4, + false, 4, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(RGBX8888, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4, + false, 4, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(RGB888, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C1_B_Cb, C0_G_Y, C2_R_Cr, 0, 3, + false, 3, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(BGR888, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, 0, 3, + false, 3, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(RGB565, + 0, COLOR_5BIT, COLOR_6BIT, COLOR_5BIT, + C1_B_Cb, C0_G_Y, C2_R_Cr, 0, 3, + false, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(BGR565, + 0, COLOR_5BIT, COLOR_6BIT, COLOR_5BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, 0, 3, + false, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(ARGB1555, + COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT, + C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4, + true, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(ABGR1555, + COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + true, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(RGBA5551, + COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT, + C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4, + true, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(BGRA5551, + COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT, + C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4, + true, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(XRGB1555, + COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT, + C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4, + false, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(XBGR1555, + COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + false, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(RGBX5551, + COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT, + C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4, + false, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(BGRX5551, + COLOR_ALPHA_1BIT, COLOR_5BIT, COLOR_5BIT, COLOR_5BIT, + C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4, + false, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(ARGB4444, + COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT, + C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4, + true, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(ABGR4444, + COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + true, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(RGBA4444, + COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT, + C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4, + true, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(BGRA4444, + COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT, + C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4, + true, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(XRGB4444, + COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT, + C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4, + false, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(XBGR4444, + COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + false, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(RGBX4444, + COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT, + C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4, + false, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(BGRX4444, + COLOR_ALPHA_4BIT, COLOR_4BIT, COLOR_4BIT, COLOR_4BIT, + C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4, + false, 2, 0, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(BGRA1010102, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4, + true, 4, DPU_FORMAT_FLAG_DX, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(RGBA1010102, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4, + true, 4, DPU_FORMAT_FLAG_DX, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(ABGR2101010, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + true, 4, DPU_FORMAT_FLAG_DX, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(ARGB2101010, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4, + true, 4, DPU_FORMAT_FLAG_DX, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(XRGB2101010, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C1_B_Cb, C0_G_Y, C2_R_Cr, C3_ALPHA, 4, + false, 4, DPU_FORMAT_FLAG_DX, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(BGRX1010102, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C3_ALPHA, C2_R_Cr, C0_G_Y, C1_B_Cb, 4, + false, 4, DPU_FORMAT_FLAG_DX, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(XBGR2101010, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + false, 4, DPU_FORMAT_FLAG_DX, + DPU_FETCH_LINEAR, 1), + + INTERLEAVED_RGB_FMT(RGBX1010102, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C3_ALPHA, C1_B_Cb, C0_G_Y, C2_R_Cr, 4, + false, 4, DPU_FORMAT_FLAG_DX, + DPU_FETCH_LINEAR, 1), + + PSEUDO_YUV_FMT(NV12, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C1_B_Cb, C2_R_Cr, + DPU_CHROMA_420, DPU_FORMAT_FLAG_YUV, + DPU_FETCH_LINEAR, 2), + + PSEUDO_YUV_FMT(NV21, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C1_B_Cb, + DPU_CHROMA_420, DPU_FORMAT_FLAG_YUV, + DPU_FETCH_LINEAR, 2), + + PSEUDO_YUV_FMT(NV16, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C1_B_Cb, C2_R_Cr, + DPU_CHROMA_H2V1, DPU_FORMAT_FLAG_YUV, + DPU_FETCH_LINEAR, 2), + + PSEUDO_YUV_FMT(NV61, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C1_B_Cb, + DPU_CHROMA_H2V1, DPU_FORMAT_FLAG_YUV, + DPU_FETCH_LINEAR, 2), + + INTERLEAVED_YUV_FMT(VYUY, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C0_G_Y, + false, DPU_CHROMA_H2V1, 4, 2, DPU_FORMAT_FLAG_YUV, + DPU_FETCH_LINEAR, 2), + + INTERLEAVED_YUV_FMT(UYVY, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C1_B_Cb, C0_G_Y, C2_R_Cr, C0_G_Y, + false, DPU_CHROMA_H2V1, 4, 2, DPU_FORMAT_FLAG_YUV, + DPU_FETCH_LINEAR, 2), + + INTERLEAVED_YUV_FMT(YUYV, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C0_G_Y, C1_B_Cb, C0_G_Y, C2_R_Cr, + false, DPU_CHROMA_H2V1, 4, 2, DPU_FORMAT_FLAG_YUV, + DPU_FETCH_LINEAR, 2), + + INTERLEAVED_YUV_FMT(YVYU, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C0_G_Y, C2_R_Cr, C0_G_Y, C1_B_Cb, + false, DPU_CHROMA_H2V1, 4, 2, DPU_FORMAT_FLAG_YUV, + DPU_FETCH_LINEAR, 2), + + PLANAR_YUV_FMT(YUV420, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C1_B_Cb, C0_G_Y, + false, DPU_CHROMA_420, 1, DPU_FORMAT_FLAG_YUV, + DPU_FETCH_LINEAR, 3), + + PLANAR_YUV_FMT(YVU420, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C1_B_Cb, C2_R_Cr, C0_G_Y, + false, DPU_CHROMA_420, 1, DPU_FORMAT_FLAG_YUV, + DPU_FETCH_LINEAR, 3), +}; + +/* + * UBWC formats table: + * This table holds the UBWC formats supported. + * If a compression ratio needs to be used for this or any other format, + * the data will be passed by user-space. + */ +static const struct dpu_format dpu_format_map_ubwc[] = { + INTERLEAVED_RGB_FMT_TILED(BGR565, + 0, COLOR_5BIT, COLOR_6BIT, COLOR_5BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, 0, 3, + false, 2, DPU_FORMAT_FLAG_COMPRESSED, + DPU_FETCH_UBWC, 2, DPU_TILE_HEIGHT_UBWC), + + INTERLEAVED_RGB_FMT_TILED(ABGR8888, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + true, 4, DPU_FORMAT_FLAG_COMPRESSED, + DPU_FETCH_UBWC, 2, DPU_TILE_HEIGHT_UBWC), + + /* ARGB8888 and ABGR8888 purposely have the same color + * ordering. The hardware only supports ABGR8888 UBWC + * natively. + */ + INTERLEAVED_RGB_FMT_TILED(ARGB8888, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + true, 4, DPU_FORMAT_FLAG_COMPRESSED, + DPU_FETCH_UBWC, 2, DPU_TILE_HEIGHT_UBWC), + + INTERLEAVED_RGB_FMT_TILED(XBGR8888, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + false, 4, DPU_FORMAT_FLAG_COMPRESSED, + DPU_FETCH_UBWC, 2, DPU_TILE_HEIGHT_UBWC), + + INTERLEAVED_RGB_FMT_TILED(XRGB8888, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + false, 4, DPU_FORMAT_FLAG_COMPRESSED, + DPU_FETCH_UBWC, 2, DPU_TILE_HEIGHT_UBWC), + + INTERLEAVED_RGB_FMT_TILED(ABGR2101010, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + true, 4, DPU_FORMAT_FLAG_DX | DPU_FORMAT_FLAG_COMPRESSED, + DPU_FETCH_UBWC, 2, DPU_TILE_HEIGHT_UBWC), + + INTERLEAVED_RGB_FMT_TILED(XBGR2101010, + COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C2_R_Cr, C0_G_Y, C1_B_Cb, C3_ALPHA, 4, + true, 4, DPU_FORMAT_FLAG_DX | DPU_FORMAT_FLAG_COMPRESSED, + DPU_FETCH_UBWC, 2, DPU_TILE_HEIGHT_UBWC), + + PSEUDO_YUV_FMT_TILED(NV12, + 0, COLOR_8BIT, COLOR_8BIT, COLOR_8BIT, + C1_B_Cb, C2_R_Cr, + DPU_CHROMA_420, DPU_FORMAT_FLAG_YUV | + DPU_FORMAT_FLAG_COMPRESSED, + DPU_FETCH_UBWC, 4, DPU_TILE_HEIGHT_NV12), +}; + +/* _dpu_get_v_h_subsample_rate - Get subsample rates for all formats we support + * Note: Not using the drm_format_*_subsampling since we have formats + */ +static void _dpu_get_v_h_subsample_rate( + enum dpu_chroma_samp_type chroma_sample, + uint32_t *v_sample, + uint32_t *h_sample) +{ + if (!v_sample || !h_sample) + return; + + switch (chroma_sample) { + case DPU_CHROMA_H2V1: + *v_sample = 1; + *h_sample = 2; + break; + case DPU_CHROMA_H1V2: + *v_sample = 2; + *h_sample = 1; + break; + case DPU_CHROMA_420: + *v_sample = 2; + *h_sample = 2; + break; + default: + *v_sample = 1; + *h_sample = 1; + break; + } +} + +static int _dpu_format_get_media_color_ubwc(const struct dpu_format *fmt) +{ + static const struct dpu_media_color_map dpu_media_ubwc_map[] = { + {DRM_FORMAT_ABGR8888, COLOR_FMT_RGBA8888_UBWC}, + {DRM_FORMAT_ARGB8888, COLOR_FMT_RGBA8888_UBWC}, + {DRM_FORMAT_XBGR8888, COLOR_FMT_RGBA8888_UBWC}, + {DRM_FORMAT_XRGB8888, COLOR_FMT_RGBA8888_UBWC}, + {DRM_FORMAT_ABGR2101010, COLOR_FMT_RGBA1010102_UBWC}, + {DRM_FORMAT_XBGR2101010, COLOR_FMT_RGBA1010102_UBWC}, + {DRM_FORMAT_BGR565, COLOR_FMT_RGB565_UBWC}, + }; + int color_fmt = -1; + int i; + + if (fmt->base.pixel_format == DRM_FORMAT_NV12) { + if (DPU_FORMAT_IS_DX(fmt)) { + if (fmt->unpack_tight) + color_fmt = COLOR_FMT_NV12_BPP10_UBWC; + else + color_fmt = COLOR_FMT_P010_UBWC; + } else + color_fmt = COLOR_FMT_NV12_UBWC; + return color_fmt; + } + + for (i = 0; i < ARRAY_SIZE(dpu_media_ubwc_map); ++i) + if (fmt->base.pixel_format == dpu_media_ubwc_map[i].format) { + color_fmt = dpu_media_ubwc_map[i].color; + break; + } + return color_fmt; +} + +static int _dpu_format_get_plane_sizes_ubwc( + const struct dpu_format *fmt, + const uint32_t width, + const uint32_t height, + struct dpu_hw_fmt_layout *layout) +{ + int i; + int color; + bool meta = DPU_FORMAT_IS_UBWC(fmt); + + memset(layout, 0, sizeof(struct dpu_hw_fmt_layout)); + layout->format = fmt; + layout->width = width; + layout->height = height; + layout->num_planes = fmt->num_planes; + + color = _dpu_format_get_media_color_ubwc(fmt); + if (color < 0) { + DRM_ERROR("UBWC format not supported for fmt: %4.4s\n", + (char *)&fmt->base.pixel_format); + return -EINVAL; + } + + if (DPU_FORMAT_IS_YUV(layout->format)) { + uint32_t y_sclines, uv_sclines; + uint32_t y_meta_scanlines = 0; + uint32_t uv_meta_scanlines = 0; + + layout->num_planes = 2; + layout->plane_pitch[0] = VENUS_Y_STRIDE(color, width); + y_sclines = VENUS_Y_SCANLINES(color, height); + layout->plane_size[0] = MSM_MEDIA_ALIGN(layout->plane_pitch[0] * + y_sclines, DPU_UBWC_PLANE_SIZE_ALIGNMENT); + + layout->plane_pitch[1] = VENUS_UV_STRIDE(color, width); + uv_sclines = VENUS_UV_SCANLINES(color, height); + layout->plane_size[1] = MSM_MEDIA_ALIGN(layout->plane_pitch[1] * + uv_sclines, DPU_UBWC_PLANE_SIZE_ALIGNMENT); + + if (!meta) + goto done; + + layout->num_planes += 2; + layout->plane_pitch[2] = VENUS_Y_META_STRIDE(color, width); + y_meta_scanlines = VENUS_Y_META_SCANLINES(color, height); + layout->plane_size[2] = MSM_MEDIA_ALIGN(layout->plane_pitch[2] * + y_meta_scanlines, DPU_UBWC_PLANE_SIZE_ALIGNMENT); + + layout->plane_pitch[3] = VENUS_UV_META_STRIDE(color, width); + uv_meta_scanlines = VENUS_UV_META_SCANLINES(color, height); + layout->plane_size[3] = MSM_MEDIA_ALIGN(layout->plane_pitch[3] * + uv_meta_scanlines, DPU_UBWC_PLANE_SIZE_ALIGNMENT); + + } else { + uint32_t rgb_scanlines, rgb_meta_scanlines; + + layout->num_planes = 1; + + layout->plane_pitch[0] = VENUS_RGB_STRIDE(color, width); + rgb_scanlines = VENUS_RGB_SCANLINES(color, height); + layout->plane_size[0] = MSM_MEDIA_ALIGN(layout->plane_pitch[0] * + rgb_scanlines, DPU_UBWC_PLANE_SIZE_ALIGNMENT); + + if (!meta) + goto done; + layout->num_planes += 2; + layout->plane_pitch[2] = VENUS_RGB_META_STRIDE(color, width); + rgb_meta_scanlines = VENUS_RGB_META_SCANLINES(color, height); + layout->plane_size[2] = MSM_MEDIA_ALIGN(layout->plane_pitch[2] * + rgb_meta_scanlines, DPU_UBWC_PLANE_SIZE_ALIGNMENT); + } + +done: + for (i = 0; i < DPU_MAX_PLANES; i++) + layout->total_size += layout->plane_size[i]; + + return 0; +} + +static int _dpu_format_get_plane_sizes_linear( + const struct dpu_format *fmt, + const uint32_t width, + const uint32_t height, + struct dpu_hw_fmt_layout *layout, + const uint32_t *pitches) +{ + int i; + + memset(layout, 0, sizeof(struct dpu_hw_fmt_layout)); + layout->format = fmt; + layout->width = width; + layout->height = height; + layout->num_planes = fmt->num_planes; + + /* Due to memset above, only need to set planes of interest */ + if (fmt->fetch_planes == DPU_PLANE_INTERLEAVED) { + layout->num_planes = 1; + layout->plane_size[0] = width * height * layout->format->bpp; + layout->plane_pitch[0] = width * layout->format->bpp; + } else { + uint32_t v_subsample, h_subsample; + uint32_t chroma_samp; + uint32_t bpp = 1; + + chroma_samp = fmt->chroma_sample; + _dpu_get_v_h_subsample_rate(chroma_samp, &v_subsample, + &h_subsample); + + if (width % h_subsample || height % v_subsample) { + DRM_ERROR("mismatch in subsample vs dimensions\n"); + return -EINVAL; + } + + if ((fmt->base.pixel_format == DRM_FORMAT_NV12) && + (DPU_FORMAT_IS_DX(fmt))) + bpp = 2; + layout->plane_pitch[0] = width * bpp; + layout->plane_pitch[1] = layout->plane_pitch[0] / h_subsample; + layout->plane_size[0] = layout->plane_pitch[0] * height; + layout->plane_size[1] = layout->plane_pitch[1] * + (height / v_subsample); + + if (fmt->fetch_planes == DPU_PLANE_PSEUDO_PLANAR) { + layout->num_planes = 2; + layout->plane_size[1] *= 2; + layout->plane_pitch[1] *= 2; + } else { + /* planar */ + layout->num_planes = 3; + layout->plane_size[2] = layout->plane_size[1]; + layout->plane_pitch[2] = layout->plane_pitch[1]; + } + } + + /* + * linear format: allow user allocated pitches if they are greater than + * the requirement. + * ubwc format: pitch values are computed uniformly across + * all the components based on ubwc specifications. + */ + for (i = 0; i < layout->num_planes && i < DPU_MAX_PLANES; ++i) { + if (pitches && layout->plane_pitch[i] < pitches[i]) + layout->plane_pitch[i] = pitches[i]; + } + + for (i = 0; i < DPU_MAX_PLANES; i++) + layout->total_size += layout->plane_size[i]; + + return 0; +} + +static int dpu_format_get_plane_sizes( + const struct dpu_format *fmt, + const uint32_t w, + const uint32_t h, + struct dpu_hw_fmt_layout *layout, + const uint32_t *pitches) +{ + if (!layout || !fmt) { + DRM_ERROR("invalid pointer\n"); + return -EINVAL; + } + + if ((w > DPU_MAX_IMG_WIDTH) || (h > DPU_MAX_IMG_HEIGHT)) { + DRM_ERROR("image dimensions outside max range\n"); + return -ERANGE; + } + + if (DPU_FORMAT_IS_UBWC(fmt) || DPU_FORMAT_IS_TILE(fmt)) + return _dpu_format_get_plane_sizes_ubwc(fmt, w, h, layout); + + return _dpu_format_get_plane_sizes_linear(fmt, w, h, layout, pitches); +} + +static int _dpu_format_populate_addrs_ubwc( + struct msm_gem_address_space *aspace, + struct drm_framebuffer *fb, + struct dpu_hw_fmt_layout *layout) +{ + uint32_t base_addr = 0; + bool meta; + + if (!fb || !layout) { + DRM_ERROR("invalid pointers\n"); + return -EINVAL; + } + + if (aspace) + base_addr = msm_framebuffer_iova(fb, aspace, 0); + if (!base_addr) { + DRM_ERROR("failed to retrieve base addr\n"); + return -EFAULT; + } + + meta = DPU_FORMAT_IS_UBWC(layout->format); + + /* Per-format logic for verifying active planes */ + if (DPU_FORMAT_IS_YUV(layout->format)) { + /************************************************/ + /* UBWC ** */ + /* buffer ** DPU PLANE */ + /* format ** */ + /************************************************/ + /* ------------------- ** -------------------- */ + /* | Y meta | ** | Y bitstream | */ + /* | data | ** | plane | */ + /* ------------------- ** -------------------- */ + /* | Y bitstream | ** | CbCr bitstream | */ + /* | data | ** | plane | */ + /* ------------------- ** -------------------- */ + /* | Cbcr metadata | ** | Y meta | */ + /* | data | ** | plane | */ + /* ------------------- ** -------------------- */ + /* | CbCr bitstream | ** | CbCr meta | */ + /* | data | ** | plane | */ + /* ------------------- ** -------------------- */ + /************************************************/ + + /* configure Y bitstream plane */ + layout->plane_addr[0] = base_addr + layout->plane_size[2]; + + /* configure CbCr bitstream plane */ + layout->plane_addr[1] = base_addr + layout->plane_size[0] + + layout->plane_size[2] + layout->plane_size[3]; + + if (!meta) + return 0; + + /* configure Y metadata plane */ + layout->plane_addr[2] = base_addr; + + /* configure CbCr metadata plane */ + layout->plane_addr[3] = base_addr + layout->plane_size[0] + + layout->plane_size[2]; + + } else { + /************************************************/ + /* UBWC ** */ + /* buffer ** DPU PLANE */ + /* format ** */ + /************************************************/ + /* ------------------- ** -------------------- */ + /* | RGB meta | ** | RGB bitstream | */ + /* | data | ** | plane | */ + /* ------------------- ** -------------------- */ + /* | RGB bitstream | ** | NONE | */ + /* | data | ** | | */ + /* ------------------- ** -------------------- */ + /* ** | RGB meta | */ + /* ** | plane | */ + /* ** -------------------- */ + /************************************************/ + + layout->plane_addr[0] = base_addr + layout->plane_size[2]; + layout->plane_addr[1] = 0; + + if (!meta) + return 0; + + layout->plane_addr[2] = base_addr; + layout->plane_addr[3] = 0; + } + return 0; +} + +static int _dpu_format_populate_addrs_linear( + struct msm_gem_address_space *aspace, + struct drm_framebuffer *fb, + struct dpu_hw_fmt_layout *layout) +{ + unsigned int i; + + /* Can now check the pitches given vs pitches expected */ + for (i = 0; i < layout->num_planes; ++i) { + if (layout->plane_pitch[i] > fb->pitches[i]) { + DRM_ERROR("plane %u expected pitch %u, fb %u\n", + i, layout->plane_pitch[i], fb->pitches[i]); + return -EINVAL; + } + } + + /* Populate addresses for simple formats here */ + for (i = 0; i < layout->num_planes; ++i) { + if (aspace) + layout->plane_addr[i] = + msm_framebuffer_iova(fb, aspace, i); + if (!layout->plane_addr[i]) { + DRM_ERROR("failed to retrieve base addr\n"); + return -EFAULT; + } + } + + return 0; +} + +int dpu_format_populate_layout( + struct msm_gem_address_space *aspace, + struct drm_framebuffer *fb, + struct dpu_hw_fmt_layout *layout) +{ + uint32_t plane_addr[DPU_MAX_PLANES]; + int i, ret; + + if (!fb || !layout) { + DRM_ERROR("invalid arguments\n"); + return -EINVAL; + } + + if ((fb->width > DPU_MAX_IMG_WIDTH) || + (fb->height > DPU_MAX_IMG_HEIGHT)) { + DRM_ERROR("image dimensions outside max range\n"); + return -ERANGE; + } + + layout->format = to_dpu_format(msm_framebuffer_format(fb)); + + /* Populate the plane sizes etc via get_format */ + ret = dpu_format_get_plane_sizes(layout->format, fb->width, fb->height, + layout, fb->pitches); + if (ret) + return ret; + + for (i = 0; i < DPU_MAX_PLANES; ++i) + plane_addr[i] = layout->plane_addr[i]; + + /* Populate the addresses given the fb */ + if (DPU_FORMAT_IS_UBWC(layout->format) || + DPU_FORMAT_IS_TILE(layout->format)) + ret = _dpu_format_populate_addrs_ubwc(aspace, fb, layout); + else + ret = _dpu_format_populate_addrs_linear(aspace, fb, layout); + + /* check if anything changed */ + if (!ret && !memcmp(plane_addr, layout->plane_addr, sizeof(plane_addr))) + ret = -EAGAIN; + + return ret; +} + +int dpu_format_check_modified_format( + const struct msm_kms *kms, + const struct msm_format *msm_fmt, + const struct drm_mode_fb_cmd2 *cmd, + struct drm_gem_object **bos) +{ + const struct drm_format_info *info; + const struct dpu_format *fmt; + struct dpu_hw_fmt_layout layout; + uint32_t bos_total_size = 0; + int ret, i; + + if (!msm_fmt || !cmd || !bos) { + DRM_ERROR("invalid arguments\n"); + return -EINVAL; + } + + fmt = to_dpu_format(msm_fmt); + info = drm_format_info(fmt->base.pixel_format); + if (!info) + return -EINVAL; + + ret = dpu_format_get_plane_sizes(fmt, cmd->width, cmd->height, + &layout, cmd->pitches); + if (ret) + return ret; + + for (i = 0; i < info->num_planes; i++) { + if (!bos[i]) { + DRM_ERROR("invalid handle for plane %d\n", i); + return -EINVAL; + } + if ((i == 0) || (bos[i] != bos[0])) + bos_total_size += bos[i]->size; + } + + if (bos_total_size < layout.total_size) { + DRM_ERROR("buffers total size too small %u expected %u\n", + bos_total_size, layout.total_size); + return -EINVAL; + } + + return 0; +} + +const struct dpu_format *dpu_get_dpu_format_ext( + const uint32_t format, + const uint64_t modifier) +{ + uint32_t i = 0; + const struct dpu_format *fmt = NULL; + const struct dpu_format *map = NULL; + ssize_t map_size = 0; + + /* + * Currently only support exactly zero or one modifier. + * All planes use the same modifier. + */ + DPU_DEBUG("plane format modifier 0x%llX\n", modifier); + + switch (modifier) { + case 0: + map = dpu_format_map; + map_size = ARRAY_SIZE(dpu_format_map); + break; + case DRM_FORMAT_MOD_QCOM_COMPRESSED: + map = dpu_format_map_ubwc; + map_size = ARRAY_SIZE(dpu_format_map_ubwc); + DPU_DEBUG("found fmt: %4.4s DRM_FORMAT_MOD_QCOM_COMPRESSED\n", + (char *)&format); + break; + default: + DPU_ERROR("unsupported format modifier %llX\n", modifier); + return NULL; + } + + for (i = 0; i < map_size; i++) { + if (format == map[i].base.pixel_format) { + fmt = &map[i]; + break; + } + } + + if (fmt == NULL) + DPU_ERROR("unsupported fmt: %4.4s modifier 0x%llX\n", + (char *)&format, modifier); + else + DPU_DEBUG("fmt %4.4s mod 0x%llX ubwc %d yuv %d\n", + (char *)&format, modifier, + DPU_FORMAT_IS_UBWC(fmt), + DPU_FORMAT_IS_YUV(fmt)); + + return fmt; +} + +const struct msm_format *dpu_get_msm_format( + struct msm_kms *kms, + const uint32_t format, + const uint64_t modifiers) +{ + const struct dpu_format *fmt = dpu_get_dpu_format_ext(format, + modifiers); + if (fmt) + return &fmt->base; + return NULL; +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_formats.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_formats.h new file mode 100644 index 000000000..418f5ae91 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_formats.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_FORMATS_H +#define _DPU_FORMATS_H + +#include <drm/drm_fourcc.h> +#include "msm_gem.h" +#include "dpu_hw_mdss.h" + +/** + * dpu_get_dpu_format_ext() - Returns dpu format structure pointer. + * @format: DRM FourCC Code + * @modifiers: format modifier array from client, one per plane + */ +const struct dpu_format *dpu_get_dpu_format_ext( + const uint32_t format, + const uint64_t modifier); + +#define dpu_get_dpu_format(f) dpu_get_dpu_format_ext(f, 0) + +/** + * dpu_get_msm_format - get an dpu_format by its msm_format base + * callback function registers with the msm_kms layer + * @kms: kms driver + * @format: DRM FourCC Code + * @modifiers: data layout modifier + */ +const struct msm_format *dpu_get_msm_format( + struct msm_kms *kms, + const uint32_t format, + const uint64_t modifiers); + +/** + * dpu_format_check_modified_format - validate format and buffers for + * dpu non-standard, i.e. modified format + * @kms: kms driver + * @msm_fmt: pointer to the msm_fmt base pointer of an dpu_format + * @cmd: fb_cmd2 structure user request + * @bos: gem buffer object list + * + * Return: error code on failure, 0 on success + */ +int dpu_format_check_modified_format( + const struct msm_kms *kms, + const struct msm_format *msm_fmt, + const struct drm_mode_fb_cmd2 *cmd, + struct drm_gem_object **bos); + +/** + * dpu_format_populate_layout - populate the given format layout based on + * mmu, fb, and format found in the fb + * @aspace: address space pointer + * @fb: framebuffer pointer + * @fmtl: format layout structure to populate + * + * Return: error code on failure, -EAGAIN if success but the addresses + * are the same as before or 0 if new addresses were populated + */ +int dpu_format_populate_layout( + struct msm_gem_address_space *aspace, + struct drm_framebuffer *fb, + struct dpu_hw_fmt_layout *fmtl); + +#endif /*_DPU_FORMATS_H */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_blk.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_blk.c new file mode 100644 index 000000000..ca26666d2 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_blk.c @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ + +#include <linux/mutex.h> +#include <linux/errno.h> +#include <linux/slab.h> + +#include "dpu_hw_mdss.h" +#include "dpu_hw_blk.h" + +/* Serialization lock for dpu_hw_blk_list */ +static DEFINE_MUTEX(dpu_hw_blk_lock); + +/* List of all hw block objects */ +static LIST_HEAD(dpu_hw_blk_list); + +/** + * dpu_hw_blk_init - initialize hw block object + * @type: hw block type - enum dpu_hw_blk_type + * @id: instance id of the hw block + * @ops: Pointer to block operations + */ +void dpu_hw_blk_init(struct dpu_hw_blk *hw_blk, u32 type, int id, + struct dpu_hw_blk_ops *ops) +{ + INIT_LIST_HEAD(&hw_blk->list); + hw_blk->type = type; + hw_blk->id = id; + atomic_set(&hw_blk->refcount, 0); + + if (ops) + hw_blk->ops = *ops; + + mutex_lock(&dpu_hw_blk_lock); + list_add(&hw_blk->list, &dpu_hw_blk_list); + mutex_unlock(&dpu_hw_blk_lock); +} + +/** + * dpu_hw_blk_destroy - destroy hw block object. + * @hw_blk: pointer to hw block object + * return: none + */ +void dpu_hw_blk_destroy(struct dpu_hw_blk *hw_blk) +{ + if (!hw_blk) { + pr_err("invalid parameters\n"); + return; + } + + if (atomic_read(&hw_blk->refcount)) + pr_err("hw_blk:%d.%d invalid refcount\n", hw_blk->type, + hw_blk->id); + + mutex_lock(&dpu_hw_blk_lock); + list_del(&hw_blk->list); + mutex_unlock(&dpu_hw_blk_lock); +} + +/** + * dpu_hw_blk_get - get hw_blk from free pool + * @hw_blk: if specified, increment reference count only + * @type: if hw_blk is not specified, allocate the next available of this type + * @id: if specified (>= 0), allocate the given instance of the above type + * return: pointer to hw block object + */ +struct dpu_hw_blk *dpu_hw_blk_get(struct dpu_hw_blk *hw_blk, u32 type, int id) +{ + struct dpu_hw_blk *curr; + int rc, refcount; + + if (!hw_blk) { + mutex_lock(&dpu_hw_blk_lock); + list_for_each_entry(curr, &dpu_hw_blk_list, list) { + if ((curr->type != type) || + (id >= 0 && curr->id != id) || + (id < 0 && + atomic_read(&curr->refcount))) + continue; + + hw_blk = curr; + break; + } + mutex_unlock(&dpu_hw_blk_lock); + } + + if (!hw_blk) { + pr_debug("no hw_blk:%d\n", type); + return NULL; + } + + refcount = atomic_inc_return(&hw_blk->refcount); + + if (refcount == 1 && hw_blk->ops.start) { + rc = hw_blk->ops.start(hw_blk); + if (rc) { + pr_err("failed to start hw_blk:%d rc:%d\n", type, rc); + goto error_start; + } + } + + pr_debug("hw_blk:%d.%d refcount:%d\n", hw_blk->type, + hw_blk->id, refcount); + return hw_blk; + +error_start: + dpu_hw_blk_put(hw_blk); + return ERR_PTR(rc); +} + +/** + * dpu_hw_blk_put - put hw_blk to free pool if decremented refcount is zero + * @hw_blk: hw block to be freed + * @free_blk: function to be called when reference count goes to zero + */ +void dpu_hw_blk_put(struct dpu_hw_blk *hw_blk) +{ + if (!hw_blk) { + pr_err("invalid parameters\n"); + return; + } + + pr_debug("hw_blk:%d.%d refcount:%d\n", hw_blk->type, hw_blk->id, + atomic_read(&hw_blk->refcount)); + + if (!atomic_read(&hw_blk->refcount)) { + pr_err("hw_blk:%d.%d invalid put\n", hw_blk->type, hw_blk->id); + return; + } + + if (atomic_dec_return(&hw_blk->refcount)) + return; + + if (hw_blk->ops.stop) + hw_blk->ops.stop(hw_blk); +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_blk.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_blk.h new file mode 100644 index 000000000..2bf737f8d --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_blk.h @@ -0,0 +1,45 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2017-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_BLK_H +#define _DPU_HW_BLK_H + +#include <linux/types.h> +#include <linux/list.h> +#include <linux/atomic.h> + +struct dpu_hw_blk; + +/** + * struct dpu_hw_blk_ops - common hardware block operations + * @start: start operation on first get + * @stop: stop operation on last put + */ +struct dpu_hw_blk_ops { + int (*start)(struct dpu_hw_blk *); + void (*stop)(struct dpu_hw_blk *); +}; + +/** + * struct dpu_hw_blk - definition of hardware block object + * @list: list of hardware blocks + * @type: hardware block type + * @id: instance id + * @refcount: reference/usage count + */ +struct dpu_hw_blk { + struct list_head list; + u32 type; + int id; + atomic_t refcount; + struct dpu_hw_blk_ops ops; +}; + +void dpu_hw_blk_init(struct dpu_hw_blk *hw_blk, u32 type, int id, + struct dpu_hw_blk_ops *ops); +void dpu_hw_blk_destroy(struct dpu_hw_blk *hw_blk); + +struct dpu_hw_blk *dpu_hw_blk_get(struct dpu_hw_blk *hw_blk, u32 type, int id); +void dpu_hw_blk_put(struct dpu_hw_blk *hw_blk); +#endif /*_DPU_HW_BLK_H */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c new file mode 100644 index 000000000..b39980b9d --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.c @@ -0,0 +1,917 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ +#include <linux/slab.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include "dpu_hw_mdss.h" +#include "dpu_hw_catalog.h" +#include "dpu_hw_catalog_format.h" +#include "dpu_kms.h" + +#define VIG_MASK \ + (BIT(DPU_SSPP_SRC) | BIT(DPU_SSPP_QOS) |\ + BIT(DPU_SSPP_CSC_10BIT) | BIT(DPU_SSPP_CDP) | BIT(DPU_SSPP_QOS_8LVL) |\ + BIT(DPU_SSPP_TS_PREFILL) | BIT(DPU_SSPP_EXCL_RECT)) + +#define VIG_SDM845_MASK \ + (VIG_MASK | BIT(DPU_SSPP_SCALER_QSEED3)) + +#define VIG_SC7180_MASK \ + (VIG_MASK | BIT(DPU_SSPP_SCALER_QSEED4)) + +#define DMA_SDM845_MASK \ + (BIT(DPU_SSPP_SRC) | BIT(DPU_SSPP_QOS) | BIT(DPU_SSPP_QOS_8LVL) |\ + BIT(DPU_SSPP_TS_PREFILL) | BIT(DPU_SSPP_TS_PREFILL_REC1) |\ + BIT(DPU_SSPP_CDP) | BIT(DPU_SSPP_EXCL_RECT)) + +#define DMA_CURSOR_SDM845_MASK \ + (DMA_SDM845_MASK | BIT(DPU_SSPP_CURSOR)) + +#define MIXER_SDM845_MASK \ + (BIT(DPU_MIXER_SOURCESPLIT) | BIT(DPU_DIM_LAYER)) + +#define MIXER_SC7180_MASK \ + (BIT(DPU_DIM_LAYER)) + +#define PINGPONG_SDM845_MASK BIT(DPU_PINGPONG_DITHER) + +#define PINGPONG_SDM845_SPLIT_MASK \ + (PINGPONG_SDM845_MASK | BIT(DPU_PINGPONG_TE2)) + +#define DSPP_SC7180_MASK BIT(DPU_DSPP_PCC) + +#define INTF_SDM845_MASK (0) + +#define INTF_SC7180_MASK BIT(DPU_INTF_INPUT_CTRL) | BIT(DPU_INTF_TE) + +#define DEFAULT_PIXEL_RAM_SIZE (50 * 1024) +#define DEFAULT_DPU_LINE_WIDTH 2048 +#define DEFAULT_DPU_OUTPUT_LINE_WIDTH 2560 + +#define MAX_HORZ_DECIMATION 4 +#define MAX_VERT_DECIMATION 4 + +#define MAX_UPSCALE_RATIO 20 +#define MAX_DOWNSCALE_RATIO 4 +#define SSPP_UNITY_SCALE 1 + +#define STRCAT(X, Y) (X Y) + +/************************************************************* + * DPU sub blocks config + *************************************************************/ +/* DPU top level caps */ +static const struct dpu_caps sdm845_dpu_caps = { + .max_mixer_width = DEFAULT_DPU_OUTPUT_LINE_WIDTH, + .max_mixer_blendstages = 0xb, + .qseed_type = DPU_SSPP_SCALER_QSEED3, + .smart_dma_rev = DPU_SSPP_SMART_DMA_V2, + .ubwc_version = DPU_HW_UBWC_VER_20, + .has_src_split = true, + .has_dim_layer = true, + .has_idle_pc = true, + .has_3d_merge = true, + .max_linewidth = DEFAULT_DPU_OUTPUT_LINE_WIDTH, + .pixel_ram_size = DEFAULT_PIXEL_RAM_SIZE, + .max_hdeci_exp = MAX_HORZ_DECIMATION, + .max_vdeci_exp = MAX_VERT_DECIMATION, +}; + +static const struct dpu_caps sc7180_dpu_caps = { + .max_mixer_width = DEFAULT_DPU_OUTPUT_LINE_WIDTH, + .max_mixer_blendstages = 0x9, + .qseed_type = DPU_SSPP_SCALER_QSEED4, + .smart_dma_rev = DPU_SSPP_SMART_DMA_V2, + .ubwc_version = DPU_HW_UBWC_VER_20, + .has_dim_layer = true, + .has_idle_pc = true, + .max_linewidth = DEFAULT_DPU_OUTPUT_LINE_WIDTH, + .pixel_ram_size = DEFAULT_PIXEL_RAM_SIZE, +}; + +static const struct dpu_caps sm8150_dpu_caps = { + .max_mixer_width = DEFAULT_DPU_OUTPUT_LINE_WIDTH, + .max_mixer_blendstages = 0xb, + .qseed_type = DPU_SSPP_SCALER_QSEED3, + .smart_dma_rev = DPU_SSPP_SMART_DMA_V2, /* TODO: v2.5 */ + .ubwc_version = DPU_HW_UBWC_VER_30, + .has_src_split = true, + .has_dim_layer = true, + .has_idle_pc = true, + .has_3d_merge = true, + .max_linewidth = 4096, + .pixel_ram_size = DEFAULT_PIXEL_RAM_SIZE, + .max_hdeci_exp = MAX_HORZ_DECIMATION, + .max_vdeci_exp = MAX_VERT_DECIMATION, +}; + +static const struct dpu_caps sm8250_dpu_caps = { + .max_mixer_width = DEFAULT_DPU_OUTPUT_LINE_WIDTH, + .max_mixer_blendstages = 0xb, + .max_linewidth = 4096, + .qseed_type = DPU_SSPP_SCALER_QSEED3, /* TODO: qseed3 lite */ + .smart_dma_rev = DPU_SSPP_SMART_DMA_V2, /* TODO: v2.5 */ + .ubwc_version = DPU_HW_UBWC_VER_40, + .has_src_split = true, + .has_dim_layer = true, + .has_idle_pc = true, + .has_3d_merge = true, + .max_linewidth = 4096, + .pixel_ram_size = DEFAULT_PIXEL_RAM_SIZE, +}; + +static const struct dpu_mdp_cfg sdm845_mdp[] = { + { + .name = "top_0", .id = MDP_TOP, + .base = 0x0, .len = 0x45C, + .features = 0, + .highest_bank_bit = 0x2, + .clk_ctrls[DPU_CLK_CTRL_VIG0] = { + .reg_off = 0x2AC, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_VIG1] = { + .reg_off = 0x2B4, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_VIG2] = { + .reg_off = 0x2BC, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_VIG3] = { + .reg_off = 0x2C4, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_DMA0] = { + .reg_off = 0x2AC, .bit_off = 8}, + .clk_ctrls[DPU_CLK_CTRL_DMA1] = { + .reg_off = 0x2B4, .bit_off = 8}, + .clk_ctrls[DPU_CLK_CTRL_CURSOR0] = { + .reg_off = 0x2BC, .bit_off = 8}, + .clk_ctrls[DPU_CLK_CTRL_CURSOR1] = { + .reg_off = 0x2C4, .bit_off = 8}, + }, +}; + +static const struct dpu_mdp_cfg sc7180_mdp[] = { + { + .name = "top_0", .id = MDP_TOP, + .base = 0x0, .len = 0x494, + .features = 0, + .highest_bank_bit = 0x3, + .clk_ctrls[DPU_CLK_CTRL_VIG0] = { + .reg_off = 0x2AC, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_DMA0] = { + .reg_off = 0x2AC, .bit_off = 8}, + .clk_ctrls[DPU_CLK_CTRL_CURSOR0] = { + .reg_off = 0x2B4, .bit_off = 8}, + .clk_ctrls[DPU_CLK_CTRL_CURSOR1] = { + .reg_off = 0x2C4, .bit_off = 8}, + }, +}; + +static const struct dpu_mdp_cfg sm8250_mdp[] = { + { + .name = "top_0", .id = MDP_TOP, + .base = 0x0, .len = 0x494, + .features = 0, + .highest_bank_bit = 0x3, /* TODO: 2 for LP_DDR4 */ + .clk_ctrls[DPU_CLK_CTRL_VIG0] = { + .reg_off = 0x2AC, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_VIG1] = { + .reg_off = 0x2B4, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_VIG2] = { + .reg_off = 0x2BC, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_VIG3] = { + .reg_off = 0x2C4, .bit_off = 0}, + .clk_ctrls[DPU_CLK_CTRL_DMA0] = { + .reg_off = 0x2AC, .bit_off = 8}, + .clk_ctrls[DPU_CLK_CTRL_DMA1] = { + .reg_off = 0x2B4, .bit_off = 8}, + .clk_ctrls[DPU_CLK_CTRL_CURSOR0] = { + .reg_off = 0x2BC, .bit_off = 8}, + .clk_ctrls[DPU_CLK_CTRL_CURSOR1] = { + .reg_off = 0x2C4, .bit_off = 8}, + .clk_ctrls[DPU_CLK_CTRL_REG_DMA] = { + .reg_off = 0x2BC, .bit_off = 20}, + }, +}; + +/************************************************************* + * CTL sub blocks config + *************************************************************/ +static const struct dpu_ctl_cfg sdm845_ctl[] = { + { + .name = "ctl_0", .id = CTL_0, + .base = 0x1000, .len = 0xE4, + .features = BIT(DPU_CTL_SPLIT_DISPLAY) + }, + { + .name = "ctl_1", .id = CTL_1, + .base = 0x1200, .len = 0xE4, + .features = BIT(DPU_CTL_SPLIT_DISPLAY) + }, + { + .name = "ctl_2", .id = CTL_2, + .base = 0x1400, .len = 0xE4, + .features = 0 + }, + { + .name = "ctl_3", .id = CTL_3, + .base = 0x1600, .len = 0xE4, + .features = 0 + }, + { + .name = "ctl_4", .id = CTL_4, + .base = 0x1800, .len = 0xE4, + .features = 0 + }, +}; + +static const struct dpu_ctl_cfg sc7180_ctl[] = { + { + .name = "ctl_0", .id = CTL_0, + .base = 0x1000, .len = 0xE4, + .features = BIT(DPU_CTL_ACTIVE_CFG) + }, + { + .name = "ctl_1", .id = CTL_1, + .base = 0x1200, .len = 0xE4, + .features = BIT(DPU_CTL_ACTIVE_CFG) + }, + { + .name = "ctl_2", .id = CTL_2, + .base = 0x1400, .len = 0xE4, + .features = BIT(DPU_CTL_ACTIVE_CFG) + }, +}; + +static const struct dpu_ctl_cfg sm8150_ctl[] = { + { + .name = "ctl_0", .id = CTL_0, + .base = 0x1000, .len = 0x1e0, + .features = BIT(DPU_CTL_ACTIVE_CFG) | BIT(DPU_CTL_SPLIT_DISPLAY) + }, + { + .name = "ctl_1", .id = CTL_1, + .base = 0x1200, .len = 0x1e0, + .features = BIT(DPU_CTL_ACTIVE_CFG) | BIT(DPU_CTL_SPLIT_DISPLAY) + }, + { + .name = "ctl_2", .id = CTL_2, + .base = 0x1400, .len = 0x1e0, + .features = BIT(DPU_CTL_ACTIVE_CFG) + }, + { + .name = "ctl_3", .id = CTL_3, + .base = 0x1600, .len = 0x1e0, + .features = BIT(DPU_CTL_ACTIVE_CFG) + }, + { + .name = "ctl_4", .id = CTL_4, + .base = 0x1800, .len = 0x1e0, + .features = BIT(DPU_CTL_ACTIVE_CFG) + }, + { + .name = "ctl_5", .id = CTL_5, + .base = 0x1a00, .len = 0x1e0, + .features = BIT(DPU_CTL_ACTIVE_CFG) + }, +}; + +/************************************************************* + * SSPP sub blocks config + *************************************************************/ + +/* SSPP common configuration */ + +#define _VIG_SBLK(num, sdma_pri, qseed_ver) \ + { \ + .maxdwnscale = MAX_DOWNSCALE_RATIO, \ + .maxupscale = MAX_UPSCALE_RATIO, \ + .smart_dma_priority = sdma_pri, \ + .src_blk = {.name = STRCAT("sspp_src_", num), \ + .id = DPU_SSPP_SRC, .base = 0x00, .len = 0x150,}, \ + .scaler_blk = {.name = STRCAT("sspp_scaler", num), \ + .id = qseed_ver, \ + .base = 0xa00, .len = 0xa0,}, \ + .csc_blk = {.name = STRCAT("sspp_csc", num), \ + .id = DPU_SSPP_CSC_10BIT, \ + .base = 0x1a00, .len = 0x100,}, \ + .format_list = plane_formats_yuv, \ + .num_formats = ARRAY_SIZE(plane_formats_yuv), \ + .virt_format_list = plane_formats, \ + .virt_num_formats = ARRAY_SIZE(plane_formats), \ + } + +#define _DMA_SBLK(num, sdma_pri) \ + { \ + .maxdwnscale = SSPP_UNITY_SCALE, \ + .maxupscale = SSPP_UNITY_SCALE, \ + .smart_dma_priority = sdma_pri, \ + .src_blk = {.name = STRCAT("sspp_src_", num), \ + .id = DPU_SSPP_SRC, .base = 0x00, .len = 0x150,}, \ + .format_list = plane_formats, \ + .num_formats = ARRAY_SIZE(plane_formats), \ + .virt_format_list = plane_formats, \ + .virt_num_formats = ARRAY_SIZE(plane_formats), \ + } + +static const struct dpu_sspp_sub_blks sdm845_vig_sblk_0 = + _VIG_SBLK("0", 5, DPU_SSPP_SCALER_QSEED3); +static const struct dpu_sspp_sub_blks sdm845_vig_sblk_1 = + _VIG_SBLK("1", 6, DPU_SSPP_SCALER_QSEED3); +static const struct dpu_sspp_sub_blks sdm845_vig_sblk_2 = + _VIG_SBLK("2", 7, DPU_SSPP_SCALER_QSEED3); +static const struct dpu_sspp_sub_blks sdm845_vig_sblk_3 = + _VIG_SBLK("3", 8, DPU_SSPP_SCALER_QSEED3); + +static const struct dpu_sspp_sub_blks sdm845_dma_sblk_0 = _DMA_SBLK("8", 1); +static const struct dpu_sspp_sub_blks sdm845_dma_sblk_1 = _DMA_SBLK("9", 2); +static const struct dpu_sspp_sub_blks sdm845_dma_sblk_2 = _DMA_SBLK("10", 3); +static const struct dpu_sspp_sub_blks sdm845_dma_sblk_3 = _DMA_SBLK("11", 4); + +#define SSPP_BLK(_name, _id, _base, _features, \ + _sblk, _xinid, _type, _clkctrl) \ + { \ + .name = _name, .id = _id, \ + .base = _base, .len = 0x1c8, \ + .features = _features, \ + .sblk = &_sblk, \ + .xin_id = _xinid, \ + .type = _type, \ + .clk_ctrl = _clkctrl \ + } + +static const struct dpu_sspp_cfg sdm845_sspp[] = { + SSPP_BLK("sspp_0", SSPP_VIG0, 0x4000, VIG_SDM845_MASK, + sdm845_vig_sblk_0, 0, SSPP_TYPE_VIG, DPU_CLK_CTRL_VIG0), + SSPP_BLK("sspp_1", SSPP_VIG1, 0x6000, VIG_SDM845_MASK, + sdm845_vig_sblk_1, 4, SSPP_TYPE_VIG, DPU_CLK_CTRL_VIG1), + SSPP_BLK("sspp_2", SSPP_VIG2, 0x8000, VIG_SDM845_MASK, + sdm845_vig_sblk_2, 8, SSPP_TYPE_VIG, DPU_CLK_CTRL_VIG2), + SSPP_BLK("sspp_3", SSPP_VIG3, 0xa000, VIG_SDM845_MASK, + sdm845_vig_sblk_3, 12, SSPP_TYPE_VIG, DPU_CLK_CTRL_VIG3), + SSPP_BLK("sspp_8", SSPP_DMA0, 0x24000, DMA_SDM845_MASK, + sdm845_dma_sblk_0, 1, SSPP_TYPE_DMA, DPU_CLK_CTRL_DMA0), + SSPP_BLK("sspp_9", SSPP_DMA1, 0x26000, DMA_SDM845_MASK, + sdm845_dma_sblk_1, 5, SSPP_TYPE_DMA, DPU_CLK_CTRL_DMA1), + SSPP_BLK("sspp_10", SSPP_DMA2, 0x28000, DMA_CURSOR_SDM845_MASK, + sdm845_dma_sblk_2, 9, SSPP_TYPE_DMA, DPU_CLK_CTRL_CURSOR0), + SSPP_BLK("sspp_11", SSPP_DMA3, 0x2a000, DMA_CURSOR_SDM845_MASK, + sdm845_dma_sblk_3, 13, SSPP_TYPE_DMA, DPU_CLK_CTRL_CURSOR1), +}; + +static const struct dpu_sspp_sub_blks sc7180_vig_sblk_0 = + _VIG_SBLK("0", 4, DPU_SSPP_SCALER_QSEED4); + +static const struct dpu_sspp_cfg sc7180_sspp[] = { + SSPP_BLK("sspp_0", SSPP_VIG0, 0x4000, VIG_SC7180_MASK, + sc7180_vig_sblk_0, 0, SSPP_TYPE_VIG, DPU_CLK_CTRL_VIG0), + SSPP_BLK("sspp_8", SSPP_DMA0, 0x24000, DMA_SDM845_MASK, + sdm845_dma_sblk_0, 1, SSPP_TYPE_DMA, DPU_CLK_CTRL_DMA0), + SSPP_BLK("sspp_9", SSPP_DMA1, 0x26000, DMA_CURSOR_SDM845_MASK, + sdm845_dma_sblk_1, 5, SSPP_TYPE_DMA, DPU_CLK_CTRL_CURSOR0), + SSPP_BLK("sspp_10", SSPP_DMA2, 0x28000, DMA_CURSOR_SDM845_MASK, + sdm845_dma_sblk_2, 9, SSPP_TYPE_DMA, DPU_CLK_CTRL_CURSOR1), +}; + +/************************************************************* + * MIXER sub blocks config + *************************************************************/ + +/* SDM845 */ + +static const struct dpu_lm_sub_blks sdm845_lm_sblk = { + .maxwidth = DEFAULT_DPU_OUTPUT_LINE_WIDTH, + .maxblendstages = 11, /* excluding base layer */ + .blendstage_base = { /* offsets relative to mixer base */ + 0x20, 0x38, 0x50, 0x68, 0x80, 0x98, + 0xb0, 0xc8, 0xe0, 0xf8, 0x110 + }, +}; + +#define LM_BLK(_name, _id, _base, _fmask, _sblk, _pp, _lmpair, _dspp) \ + { \ + .name = _name, .id = _id, \ + .base = _base, .len = 0x320, \ + .features = _fmask, \ + .sblk = _sblk, \ + .pingpong = _pp, \ + .lm_pair_mask = (1 << _lmpair), \ + .dspp = _dspp \ + } + +static const struct dpu_lm_cfg sdm845_lm[] = { + LM_BLK("lm_0", LM_0, 0x44000, MIXER_SDM845_MASK, + &sdm845_lm_sblk, PINGPONG_0, LM_1, 0), + LM_BLK("lm_1", LM_1, 0x45000, MIXER_SDM845_MASK, + &sdm845_lm_sblk, PINGPONG_1, LM_0, 0), + LM_BLK("lm_2", LM_2, 0x46000, MIXER_SDM845_MASK, + &sdm845_lm_sblk, PINGPONG_2, LM_5, 0), + LM_BLK("lm_3", LM_3, 0x0, MIXER_SDM845_MASK, + &sdm845_lm_sblk, PINGPONG_MAX, 0, 0), + LM_BLK("lm_4", LM_4, 0x0, MIXER_SDM845_MASK, + &sdm845_lm_sblk, PINGPONG_MAX, 0, 0), + LM_BLK("lm_5", LM_5, 0x49000, MIXER_SDM845_MASK, + &sdm845_lm_sblk, PINGPONG_3, LM_2, 0), +}; + +/* SC7180 */ + +static const struct dpu_lm_sub_blks sc7180_lm_sblk = { + .maxwidth = DEFAULT_DPU_OUTPUT_LINE_WIDTH, + .maxblendstages = 7, /* excluding base layer */ + .blendstage_base = { /* offsets relative to mixer base */ + 0x20, 0x38, 0x50, 0x68, 0x80, 0x98, 0xb0 + }, +}; + +static const struct dpu_lm_cfg sc7180_lm[] = { + LM_BLK("lm_0", LM_0, 0x44000, MIXER_SC7180_MASK, + &sc7180_lm_sblk, PINGPONG_0, LM_1, DSPP_0), + LM_BLK("lm_1", LM_1, 0x45000, MIXER_SC7180_MASK, + &sc7180_lm_sblk, PINGPONG_1, LM_0, 0), +}; + +/* SM8150 */ + +static const struct dpu_lm_cfg sm8150_lm[] = { + LM_BLK("lm_0", LM_0, 0x44000, MIXER_SDM845_MASK, + &sdm845_lm_sblk, PINGPONG_0, LM_1, 0), + LM_BLK("lm_1", LM_1, 0x45000, MIXER_SDM845_MASK, + &sdm845_lm_sblk, PINGPONG_1, LM_0, 0), + LM_BLK("lm_2", LM_2, 0x46000, MIXER_SDM845_MASK, + &sdm845_lm_sblk, PINGPONG_2, LM_3, 0), + LM_BLK("lm_3", LM_3, 0x47000, MIXER_SDM845_MASK, + &sdm845_lm_sblk, PINGPONG_3, LM_2, 0), + LM_BLK("lm_4", LM_4, 0x48000, MIXER_SDM845_MASK, + &sdm845_lm_sblk, PINGPONG_4, LM_5, 0), + LM_BLK("lm_5", LM_5, 0x49000, MIXER_SDM845_MASK, + &sdm845_lm_sblk, PINGPONG_5, LM_4, 0), +}; + +/************************************************************* + * DSPP sub blocks config + *************************************************************/ +static const struct dpu_dspp_sub_blks sc7180_dspp_sblk = { + .pcc = {.id = DPU_DSPP_PCC, .base = 0x1700, + .len = 0x90, .version = 0x10000}, +}; + +#define DSPP_BLK(_name, _id, _base) \ + {\ + .name = _name, .id = _id, \ + .base = _base, .len = 0x1800, \ + .features = DSPP_SC7180_MASK, \ + .sblk = &sc7180_dspp_sblk \ + } + +static const struct dpu_dspp_cfg sc7180_dspp[] = { + DSPP_BLK("dspp_0", DSPP_0, 0x54000), +}; + +/************************************************************* + * PINGPONG sub blocks config + *************************************************************/ +static const struct dpu_pingpong_sub_blks sdm845_pp_sblk_te = { + .te2 = {.id = DPU_PINGPONG_TE2, .base = 0x2000, .len = 0x0, + .version = 0x1}, + .dither = {.id = DPU_PINGPONG_DITHER, .base = 0x30e0, + .len = 0x20, .version = 0x10000}, +}; + +static const struct dpu_pingpong_sub_blks sdm845_pp_sblk = { + .dither = {.id = DPU_PINGPONG_DITHER, .base = 0x30e0, + .len = 0x20, .version = 0x10000}, +}; + +#define PP_BLK_TE(_name, _id, _base) \ + {\ + .name = _name, .id = _id, \ + .base = _base, .len = 0xd4, \ + .features = PINGPONG_SDM845_SPLIT_MASK, \ + .sblk = &sdm845_pp_sblk_te \ + } +#define PP_BLK(_name, _id, _base) \ + {\ + .name = _name, .id = _id, \ + .base = _base, .len = 0xd4, \ + .features = PINGPONG_SDM845_MASK, \ + .sblk = &sdm845_pp_sblk \ + } + +static const struct dpu_pingpong_cfg sdm845_pp[] = { + PP_BLK_TE("pingpong_0", PINGPONG_0, 0x70000), + PP_BLK_TE("pingpong_1", PINGPONG_1, 0x70800), + PP_BLK("pingpong_2", PINGPONG_2, 0x71000), + PP_BLK("pingpong_3", PINGPONG_3, 0x71800), +}; + +static struct dpu_pingpong_cfg sc7180_pp[] = { + PP_BLK_TE("pingpong_0", PINGPONG_0, 0x70000), + PP_BLK_TE("pingpong_1", PINGPONG_1, 0x70800), +}; + +static const struct dpu_pingpong_cfg sm8150_pp[] = { + PP_BLK_TE("pingpong_0", PINGPONG_0, 0x70000), + PP_BLK_TE("pingpong_1", PINGPONG_1, 0x70800), + PP_BLK("pingpong_2", PINGPONG_2, 0x71000), + PP_BLK("pingpong_3", PINGPONG_3, 0x71800), + PP_BLK("pingpong_4", PINGPONG_4, 0x72000), + PP_BLK("pingpong_5", PINGPONG_5, 0x72800), +}; + +/************************************************************* + * INTF sub blocks config + *************************************************************/ +#define INTF_BLK(_name, _id, _base, _type, _ctrl_id, _features) \ + {\ + .name = _name, .id = _id, \ + .base = _base, .len = 0x280, \ + .features = _features, \ + .type = _type, \ + .controller_id = _ctrl_id, \ + .prog_fetch_lines_worst_case = 24 \ + } + +static const struct dpu_intf_cfg sdm845_intf[] = { + INTF_BLK("intf_0", INTF_0, 0x6A000, INTF_DP, 0, INTF_SDM845_MASK), + INTF_BLK("intf_1", INTF_1, 0x6A800, INTF_DSI, 0, INTF_SDM845_MASK), + INTF_BLK("intf_2", INTF_2, 0x6B000, INTF_DSI, 1, INTF_SDM845_MASK), + INTF_BLK("intf_3", INTF_3, 0x6B800, INTF_DP, 1, INTF_SDM845_MASK), +}; + +static const struct dpu_intf_cfg sc7180_intf[] = { + INTF_BLK("intf_0", INTF_0, 0x6A000, INTF_DP, 0, INTF_SC7180_MASK), + INTF_BLK("intf_1", INTF_1, 0x6A800, INTF_DSI, 0, INTF_SC7180_MASK), +}; + +static const struct dpu_intf_cfg sm8150_intf[] = { + INTF_BLK("intf_0", INTF_0, 0x6A000, INTF_DP, 0, INTF_SC7180_MASK), + INTF_BLK("intf_1", INTF_1, 0x6A800, INTF_DSI, 0, INTF_SC7180_MASK), + INTF_BLK("intf_2", INTF_2, 0x6B000, INTF_DSI, 1, INTF_SC7180_MASK), + INTF_BLK("intf_3", INTF_3, 0x6B800, INTF_DP, 1, INTF_SC7180_MASK), +}; + +/************************************************************* + * VBIF sub blocks config + *************************************************************/ +/* VBIF QOS remap */ +static const u32 sdm845_rt_pri_lvl[] = {3, 3, 4, 4, 5, 5, 6, 6}; +static const u32 sdm845_nrt_pri_lvl[] = {3, 3, 3, 3, 3, 3, 3, 3}; + +static const struct dpu_vbif_cfg sdm845_vbif[] = { + { + .name = "vbif_0", .id = VBIF_0, + .base = 0, .len = 0x1040, + .features = BIT(DPU_VBIF_QOS_REMAP), + .xin_halt_timeout = 0x4000, + .qos_rt_tbl = { + .npriority_lvl = ARRAY_SIZE(sdm845_rt_pri_lvl), + .priority_lvl = sdm845_rt_pri_lvl, + }, + .qos_nrt_tbl = { + .npriority_lvl = ARRAY_SIZE(sdm845_nrt_pri_lvl), + .priority_lvl = sdm845_nrt_pri_lvl, + }, + .memtype_count = 14, + .memtype = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, + }, +}; + +static const struct dpu_reg_dma_cfg sdm845_regdma = { + .base = 0x0, .version = 0x1, .trigger_sel_off = 0x119c +}; + +static const struct dpu_reg_dma_cfg sm8150_regdma = { + .base = 0x0, .version = 0x00010001, .trigger_sel_off = 0x119c +}; + +static const struct dpu_reg_dma_cfg sm8250_regdma = { + .base = 0x0, + .version = 0x00010002, + .trigger_sel_off = 0x119c, + .xin_id = 7, + .clk_ctrl = DPU_CLK_CTRL_REG_DMA, +}; + +/************************************************************* + * PERF data config + *************************************************************/ + +/* SSPP QOS LUTs */ +static const struct dpu_qos_lut_entry sdm845_qos_linear[] = { + {.fl = 4, .lut = 0x357}, + {.fl = 5, .lut = 0x3357}, + {.fl = 6, .lut = 0x23357}, + {.fl = 7, .lut = 0x223357}, + {.fl = 8, .lut = 0x2223357}, + {.fl = 9, .lut = 0x22223357}, + {.fl = 10, .lut = 0x222223357}, + {.fl = 11, .lut = 0x2222223357}, + {.fl = 12, .lut = 0x22222223357}, + {.fl = 13, .lut = 0x222222223357}, + {.fl = 14, .lut = 0x1222222223357}, + {.fl = 0, .lut = 0x11222222223357} +}; + +static const struct dpu_qos_lut_entry sc7180_qos_linear[] = { + {.fl = 0, .lut = 0x0011222222335777}, +}; + +static const struct dpu_qos_lut_entry sm8150_qos_linear[] = { + {.fl = 0, .lut = 0x0011222222223357 }, +}; + +static const struct dpu_qos_lut_entry sdm845_qos_macrotile[] = { + {.fl = 10, .lut = 0x344556677}, + {.fl = 11, .lut = 0x3344556677}, + {.fl = 12, .lut = 0x23344556677}, + {.fl = 13, .lut = 0x223344556677}, + {.fl = 14, .lut = 0x1223344556677}, + {.fl = 0, .lut = 0x112233344556677}, +}; + +static const struct dpu_qos_lut_entry sc7180_qos_macrotile[] = { + {.fl = 0, .lut = 0x0011223344556677}, +}; + +static const struct dpu_qos_lut_entry sdm845_qos_nrt[] = { + {.fl = 0, .lut = 0x0}, +}; + +static const struct dpu_qos_lut_entry sc7180_qos_nrt[] = { + {.fl = 0, .lut = 0x0}, +}; + +static const struct dpu_perf_cfg sdm845_perf_data = { + .max_bw_low = 6800000, + .max_bw_high = 6800000, + .min_core_ib = 2400000, + .min_llcc_ib = 800000, + .min_dram_ib = 800000, + .core_ib_ff = "6.0", + .core_clk_ff = "1.0", + .comp_ratio_rt = + "NV12/5/1/1.23 AB24/5/1/1.23 XB24/5/1/1.23", + .comp_ratio_nrt = + "NV12/5/1/1.25 AB24/5/1/1.25 XB24/5/1/1.25", + .undersized_prefill_lines = 2, + .xtra_prefill_lines = 2, + .dest_scale_prefill_lines = 3, + .macrotile_prefill_lines = 4, + .yuv_nv12_prefill_lines = 8, + .linear_prefill_lines = 1, + .downscaling_prefill_lines = 1, + .amortizable_threshold = 25, + .min_prefill_lines = 24, + .danger_lut_tbl = {0xf, 0xffff, 0x0}, + .qos_lut_tbl = { + {.nentry = ARRAY_SIZE(sdm845_qos_linear), + .entries = sdm845_qos_linear + }, + {.nentry = ARRAY_SIZE(sdm845_qos_macrotile), + .entries = sdm845_qos_macrotile + }, + {.nentry = ARRAY_SIZE(sdm845_qos_nrt), + .entries = sdm845_qos_nrt + }, + }, + .cdp_cfg = { + {.rd_enable = 1, .wr_enable = 1}, + {.rd_enable = 1, .wr_enable = 0} + }, +}; + +static const struct dpu_perf_cfg sc7180_perf_data = { + .max_bw_low = 6800000, + .max_bw_high = 6800000, + .min_core_ib = 2400000, + .min_llcc_ib = 800000, + .min_dram_ib = 1600000, + .min_prefill_lines = 24, + .danger_lut_tbl = {0xff, 0xffff, 0x0}, + .qos_lut_tbl = { + {.nentry = ARRAY_SIZE(sc7180_qos_linear), + .entries = sc7180_qos_linear + }, + {.nentry = ARRAY_SIZE(sc7180_qos_macrotile), + .entries = sc7180_qos_macrotile + }, + {.nentry = ARRAY_SIZE(sc7180_qos_nrt), + .entries = sc7180_qos_nrt + }, + }, + .cdp_cfg = { + {.rd_enable = 1, .wr_enable = 1}, + {.rd_enable = 1, .wr_enable = 0} + }, + .clk_inefficiency_factor = 105, + .bw_inefficiency_factor = 120, +}; + +static const struct dpu_perf_cfg sm8150_perf_data = { + .max_bw_low = 12800000, + .max_bw_high = 12800000, + .min_core_ib = 2400000, + .min_llcc_ib = 800000, + .min_dram_ib = 800000, + .danger_lut_tbl = {0xf, 0xffff, 0x0}, + .qos_lut_tbl = { + {.nentry = ARRAY_SIZE(sm8150_qos_linear), + .entries = sm8150_qos_linear + }, + {.nentry = ARRAY_SIZE(sc7180_qos_macrotile), + .entries = sc7180_qos_macrotile + }, + {.nentry = ARRAY_SIZE(sc7180_qos_nrt), + .entries = sc7180_qos_nrt + }, + /* TODO: macrotile-qseed is different from macrotile */ + }, + .cdp_cfg = { + {.rd_enable = 1, .wr_enable = 1}, + {.rd_enable = 1, .wr_enable = 0} + }, +}; + +static const struct dpu_perf_cfg sm8250_perf_data = { + .max_bw_low = 13700000, + .max_bw_high = 16600000, + .min_core_ib = 4800000, + .min_llcc_ib = 0, + .min_dram_ib = 800000, + .danger_lut_tbl = {0xf, 0xffff, 0x0}, + .qos_lut_tbl = { + {.nentry = ARRAY_SIZE(sc7180_qos_linear), + .entries = sc7180_qos_linear + }, + {.nentry = ARRAY_SIZE(sc7180_qos_macrotile), + .entries = sc7180_qos_macrotile + }, + {.nentry = ARRAY_SIZE(sc7180_qos_nrt), + .entries = sc7180_qos_nrt + }, + /* TODO: macrotile-qseed is different from macrotile */ + }, + .cdp_cfg = { + {.rd_enable = 1, .wr_enable = 1}, + {.rd_enable = 1, .wr_enable = 0} + }, +}; + +/************************************************************* + * Hardware catalog init + *************************************************************/ + +/* + * sdm845_cfg_init(): populate sdm845 dpu sub-blocks reg offsets + * and instance counts. + */ +static void sdm845_cfg_init(struct dpu_mdss_cfg *dpu_cfg) +{ + *dpu_cfg = (struct dpu_mdss_cfg){ + .caps = &sdm845_dpu_caps, + .mdp_count = ARRAY_SIZE(sdm845_mdp), + .mdp = sdm845_mdp, + .ctl_count = ARRAY_SIZE(sdm845_ctl), + .ctl = sdm845_ctl, + .sspp_count = ARRAY_SIZE(sdm845_sspp), + .sspp = sdm845_sspp, + .mixer_count = ARRAY_SIZE(sdm845_lm), + .mixer = sdm845_lm, + .pingpong_count = ARRAY_SIZE(sdm845_pp), + .pingpong = sdm845_pp, + .intf_count = ARRAY_SIZE(sdm845_intf), + .intf = sdm845_intf, + .vbif_count = ARRAY_SIZE(sdm845_vbif), + .vbif = sdm845_vbif, + .reg_dma_count = 1, + .dma_cfg = sdm845_regdma, + .perf = sdm845_perf_data, + .mdss_irqs = 0x3ff, + }; +} + +/* + * sc7180_cfg_init(): populate sc7180 dpu sub-blocks reg offsets + * and instance counts. + */ +static void sc7180_cfg_init(struct dpu_mdss_cfg *dpu_cfg) +{ + *dpu_cfg = (struct dpu_mdss_cfg){ + .caps = &sc7180_dpu_caps, + .mdp_count = ARRAY_SIZE(sc7180_mdp), + .mdp = sc7180_mdp, + .ctl_count = ARRAY_SIZE(sc7180_ctl), + .ctl = sc7180_ctl, + .sspp_count = ARRAY_SIZE(sc7180_sspp), + .sspp = sc7180_sspp, + .mixer_count = ARRAY_SIZE(sc7180_lm), + .mixer = sc7180_lm, + .dspp_count = ARRAY_SIZE(sc7180_dspp), + .dspp = sc7180_dspp, + .pingpong_count = ARRAY_SIZE(sc7180_pp), + .pingpong = sc7180_pp, + .intf_count = ARRAY_SIZE(sc7180_intf), + .intf = sc7180_intf, + .vbif_count = ARRAY_SIZE(sdm845_vbif), + .vbif = sdm845_vbif, + .reg_dma_count = 1, + .dma_cfg = sdm845_regdma, + .perf = sc7180_perf_data, + .mdss_irqs = 0x3f, + }; +} + +/* + * sm8150_cfg_init(): populate sm8150 dpu sub-blocks reg offsets + * and instance counts. + */ +static void sm8150_cfg_init(struct dpu_mdss_cfg *dpu_cfg) +{ + *dpu_cfg = (struct dpu_mdss_cfg){ + .caps = &sm8150_dpu_caps, + .mdp_count = ARRAY_SIZE(sdm845_mdp), + .mdp = sdm845_mdp, + .ctl_count = ARRAY_SIZE(sm8150_ctl), + .ctl = sm8150_ctl, + .sspp_count = ARRAY_SIZE(sdm845_sspp), + .sspp = sdm845_sspp, + .mixer_count = ARRAY_SIZE(sm8150_lm), + .mixer = sm8150_lm, + .pingpong_count = ARRAY_SIZE(sm8150_pp), + .pingpong = sm8150_pp, + .intf_count = ARRAY_SIZE(sm8150_intf), + .intf = sm8150_intf, + .vbif_count = ARRAY_SIZE(sdm845_vbif), + .vbif = sdm845_vbif, + .reg_dma_count = 1, + .dma_cfg = sm8150_regdma, + .perf = sm8150_perf_data, + .mdss_irqs = 0x3ff, + }; +} + +/* + * sm8250_cfg_init(): populate sm8250 dpu sub-blocks reg offsets + * and instance counts. + */ +static void sm8250_cfg_init(struct dpu_mdss_cfg *dpu_cfg) +{ + *dpu_cfg = (struct dpu_mdss_cfg){ + .caps = &sm8250_dpu_caps, + .mdp_count = ARRAY_SIZE(sm8250_mdp), + .mdp = sm8250_mdp, + .ctl_count = ARRAY_SIZE(sm8150_ctl), + .ctl = sm8150_ctl, + /* TODO: sspp qseed version differs from 845 */ + .sspp_count = ARRAY_SIZE(sdm845_sspp), + .sspp = sdm845_sspp, + .mixer_count = ARRAY_SIZE(sm8150_lm), + .mixer = sm8150_lm, + .pingpong_count = ARRAY_SIZE(sm8150_pp), + .pingpong = sm8150_pp, + .intf_count = ARRAY_SIZE(sm8150_intf), + .intf = sm8150_intf, + .vbif_count = ARRAY_SIZE(sdm845_vbif), + .vbif = sdm845_vbif, + .reg_dma_count = 1, + .dma_cfg = sm8250_regdma, + .perf = sm8250_perf_data, + .mdss_irqs = 0xff, + }; +} + +static const struct dpu_mdss_hw_cfg_handler cfg_handler[] = { + { .hw_rev = DPU_HW_VER_400, .cfg_init = sdm845_cfg_init}, + { .hw_rev = DPU_HW_VER_401, .cfg_init = sdm845_cfg_init}, + { .hw_rev = DPU_HW_VER_500, .cfg_init = sm8150_cfg_init}, + { .hw_rev = DPU_HW_VER_501, .cfg_init = sm8150_cfg_init}, + { .hw_rev = DPU_HW_VER_600, .cfg_init = sm8250_cfg_init}, + { .hw_rev = DPU_HW_VER_620, .cfg_init = sc7180_cfg_init}, +}; + +void dpu_hw_catalog_deinit(struct dpu_mdss_cfg *dpu_cfg) +{ + kfree(dpu_cfg); +} + +struct dpu_mdss_cfg *dpu_hw_catalog_init(u32 hw_rev) +{ + int i; + struct dpu_mdss_cfg *dpu_cfg; + + dpu_cfg = kzalloc(sizeof(*dpu_cfg), GFP_KERNEL); + if (!dpu_cfg) + return ERR_PTR(-ENOMEM); + + for (i = 0; i < ARRAY_SIZE(cfg_handler); i++) { + if (cfg_handler[i].hw_rev == hw_rev) { + cfg_handler[i].cfg_init(dpu_cfg); + dpu_cfg->hwversion = hw_rev; + return dpu_cfg; + } + } + + DPU_ERROR("unsupported chipset id:%X\n", hw_rev); + dpu_hw_catalog_deinit(dpu_cfg); + return ERR_PTR(-ENODEV); +} + diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h new file mode 100644 index 000000000..3544af1a4 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog.h @@ -0,0 +1,786 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_CATALOG_H +#define _DPU_HW_CATALOG_H + +#include <linux/kernel.h> +#include <linux/bug.h> +#include <linux/bitmap.h> +#include <linux/err.h> + +/** + * Max hardware block count: For ex: max 12 SSPP pipes or + * 5 ctl paths. In all cases, it can have max 12 hardware blocks + * based on current design + */ +#define MAX_BLOCKS 12 + +#define DPU_HW_VER(MAJOR, MINOR, STEP) (((MAJOR & 0xF) << 28) |\ + ((MINOR & 0xFFF) << 16) |\ + (STEP & 0xFFFF)) + +#define DPU_HW_MAJOR(rev) ((rev) >> 28) +#define DPU_HW_MINOR(rev) (((rev) >> 16) & 0xFFF) +#define DPU_HW_STEP(rev) ((rev) & 0xFFFF) +#define DPU_HW_MAJOR_MINOR(rev) ((rev) >> 16) + +#define IS_DPU_MAJOR_MINOR_SAME(rev1, rev2) \ + (DPU_HW_MAJOR_MINOR((rev1)) == DPU_HW_MAJOR_MINOR((rev2))) + +#define DPU_HW_VER_170 DPU_HW_VER(1, 7, 0) /* 8996 v1.0 */ +#define DPU_HW_VER_171 DPU_HW_VER(1, 7, 1) /* 8996 v2.0 */ +#define DPU_HW_VER_172 DPU_HW_VER(1, 7, 2) /* 8996 v3.0 */ +#define DPU_HW_VER_300 DPU_HW_VER(3, 0, 0) /* 8998 v1.0 */ +#define DPU_HW_VER_301 DPU_HW_VER(3, 0, 1) /* 8998 v1.1 */ +#define DPU_HW_VER_400 DPU_HW_VER(4, 0, 0) /* sdm845 v1.0 */ +#define DPU_HW_VER_401 DPU_HW_VER(4, 0, 1) /* sdm845 v2.0 */ +#define DPU_HW_VER_410 DPU_HW_VER(4, 1, 0) /* sdm670 v1.0 */ +#define DPU_HW_VER_500 DPU_HW_VER(5, 0, 0) /* sm8150 v1.0 */ +#define DPU_HW_VER_501 DPU_HW_VER(5, 0, 1) /* sm8150 v2.0 */ +#define DPU_HW_VER_600 DPU_HW_VER(6, 0, 0) /* sm8250 */ +#define DPU_HW_VER_620 DPU_HW_VER(6, 2, 0) /* sc7180 v1.0 */ + + +#define IS_MSM8996_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_170) +#define IS_MSM8998_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_300) +#define IS_SDM845_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_400) +#define IS_SDM670_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_410) +#define IS_SDM855_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_500) +#define IS_SC7180_TARGET(rev) IS_DPU_MAJOR_MINOR_SAME((rev), DPU_HW_VER_620) + + +#define DPU_HW_BLK_NAME_LEN 16 + +#define MAX_IMG_WIDTH 0x3fff +#define MAX_IMG_HEIGHT 0x3fff + +#define CRTC_DUAL_MIXERS 2 + +#define MAX_XIN_COUNT 16 + +/** + * Supported UBWC feature versions + */ +enum { + DPU_HW_UBWC_VER_10 = 0x100, + DPU_HW_UBWC_VER_20 = 0x200, + DPU_HW_UBWC_VER_30 = 0x300, + DPU_HW_UBWC_VER_40 = 0x400, +}; + +/** + * MDP TOP BLOCK features + * @DPU_MDP_PANIC_PER_PIPE Panic configuration needs to be be done per pipe + * @DPU_MDP_10BIT_SUPPORT, Chipset supports 10 bit pixel formats + * @DPU_MDP_BWC, MDSS HW supports Bandwidth compression. + * @DPU_MDP_UBWC_1_0, This chipsets supports Universal Bandwidth + * compression initial revision + * @DPU_MDP_UBWC_1_5, Universal Bandwidth compression version 1.5 + * @DPU_MDP_MAX Maximum value + + */ +enum { + DPU_MDP_PANIC_PER_PIPE = 0x1, + DPU_MDP_10BIT_SUPPORT, + DPU_MDP_BWC, + DPU_MDP_UBWC_1_0, + DPU_MDP_UBWC_1_5, + DPU_MDP_MAX +}; + +/** + * SSPP sub-blocks/features + * @DPU_SSPP_SRC Src and fetch part of the pipes, + * @DPU_SSPP_SCALER_QSEED2, QSEED2 algorithm support + * @DPU_SSPP_SCALER_QSEED3, QSEED3 alogorithm support + * @DPU_SSPP_SCALER_QSEED4, QSEED4 algorithm support + * @DPU_SSPP_SCALER_RGB, RGB Scaler, supported by RGB pipes + * @DPU_SSPP_CSC, Support of Color space converion + * @DPU_SSPP_CSC_10BIT, Support of 10-bit Color space conversion + * @DPU_SSPP_CURSOR, SSPP can be used as a cursor layer + * @DPU_SSPP_QOS, SSPP support QoS control, danger/safe/creq + * @DPU_SSPP_QOS_8LVL, SSPP support 8-level QoS control + * @DPU_SSPP_EXCL_RECT, SSPP supports exclusion rect + * @DPU_SSPP_SMART_DMA_V1, SmartDMA 1.0 support + * @DPU_SSPP_SMART_DMA_V2, SmartDMA 2.0 support + * @DPU_SSPP_TS_PREFILL Supports prefill with traffic shaper + * @DPU_SSPP_TS_PREFILL_REC1 Supports prefill with traffic shaper multirec + * @DPU_SSPP_CDP Supports client driven prefetch + * @DPU_SSPP_MAX maximum value + */ +enum { + DPU_SSPP_SRC = 0x1, + DPU_SSPP_SCALER_QSEED2, + DPU_SSPP_SCALER_QSEED3, + DPU_SSPP_SCALER_QSEED4, + DPU_SSPP_SCALER_RGB, + DPU_SSPP_CSC, + DPU_SSPP_CSC_10BIT, + DPU_SSPP_CURSOR, + DPU_SSPP_QOS, + DPU_SSPP_QOS_8LVL, + DPU_SSPP_EXCL_RECT, + DPU_SSPP_SMART_DMA_V1, + DPU_SSPP_SMART_DMA_V2, + DPU_SSPP_TS_PREFILL, + DPU_SSPP_TS_PREFILL_REC1, + DPU_SSPP_CDP, + DPU_SSPP_MAX +}; + +/* + * MIXER sub-blocks/features + * @DPU_MIXER_LAYER Layer mixer layer blend configuration, + * @DPU_MIXER_SOURCESPLIT Layer mixer supports source-split configuration + * @DPU_MIXER_GC Gamma correction block + * @DPU_DIM_LAYER Layer mixer supports dim layer + * @DPU_MIXER_MAX maximum value + */ +enum { + DPU_MIXER_LAYER = 0x1, + DPU_MIXER_SOURCESPLIT, + DPU_MIXER_GC, + DPU_DIM_LAYER, + DPU_MIXER_MAX +}; + +/** + * DSPP sub-blocks + * @DPU_DSPP_PCC Panel color correction block + * @DPU_DSPP_GC Gamma correction block + */ +enum { + DPU_DSPP_PCC = 0x1, + DPU_DSPP_GC, + DPU_DSPP_MAX +}; + +/** + * PINGPONG sub-blocks + * @DPU_PINGPONG_TE Tear check block + * @DPU_PINGPONG_TE2 Additional tear check block for split pipes + * @DPU_PINGPONG_SPLIT PP block supports split fifo + * @DPU_PINGPONG_SLAVE PP block is a suitable slave for split fifo + * @DPU_PINGPONG_DITHER, Dither blocks + * @DPU_PINGPONG_MAX + */ +enum { + DPU_PINGPONG_TE = 0x1, + DPU_PINGPONG_TE2, + DPU_PINGPONG_SPLIT, + DPU_PINGPONG_SLAVE, + DPU_PINGPONG_DITHER, + DPU_PINGPONG_MAX +}; + +/** + * CTL sub-blocks + * @DPU_CTL_SPLIT_DISPLAY CTL supports video mode split display + * @DPU_CTL_MAX + */ +enum { + DPU_CTL_SPLIT_DISPLAY = 0x1, + DPU_CTL_ACTIVE_CFG, + DPU_CTL_MAX +}; + +/** + * INTF sub-blocks + * @DPU_INTF_INPUT_CTRL Supports the setting of pp block from which + * pixel data arrives to this INTF + * @DPU_INTF_TE INTF block has TE configuration support + * @DPU_INTF_MAX + */ +enum { + DPU_INTF_INPUT_CTRL = 0x1, + DPU_INTF_TE, + DPU_INTF_MAX +}; + +/** + * VBIF sub-blocks and features + * @DPU_VBIF_QOS_OTLIM VBIF supports OT Limit + * @DPU_VBIF_QOS_REMAP VBIF supports QoS priority remap + * @DPU_VBIF_MAX maximum value + */ +enum { + DPU_VBIF_QOS_OTLIM = 0x1, + DPU_VBIF_QOS_REMAP, + DPU_VBIF_MAX +}; + +/** + * MACRO DPU_HW_BLK_INFO - information of HW blocks inside DPU + * @name: string name for debug purposes + * @id: enum identifying this block + * @base: register base offset to mdss + * @len: length of hardware block + * @features bit mask identifying sub-blocks/features + */ +#define DPU_HW_BLK_INFO \ + char name[DPU_HW_BLK_NAME_LEN]; \ + u32 id; \ + u32 base; \ + u32 len; \ + unsigned long features + +/** + * MACRO DPU_HW_SUBBLK_INFO - information of HW sub-block inside DPU + * @name: string name for debug purposes + * @id: enum identifying this sub-block + * @base: offset of this sub-block relative to the block + * offset + * @len register block length of this sub-block + */ +#define DPU_HW_SUBBLK_INFO \ + char name[DPU_HW_BLK_NAME_LEN]; \ + u32 id; \ + u32 base; \ + u32 len + +/** + * struct dpu_src_blk: SSPP part of the source pipes + * @info: HW register and features supported by this sub-blk + */ +struct dpu_src_blk { + DPU_HW_SUBBLK_INFO; +}; + +/** + * struct dpu_scaler_blk: Scaler information + * @info: HW register and features supported by this sub-blk + * @version: qseed block revision + */ +struct dpu_scaler_blk { + DPU_HW_SUBBLK_INFO; + u32 version; +}; + +struct dpu_csc_blk { + DPU_HW_SUBBLK_INFO; +}; + +/** + * struct dpu_pp_blk : Pixel processing sub-blk information + * @info: HW register and features supported by this sub-blk + * @version: HW Algorithm version + */ +struct dpu_pp_blk { + DPU_HW_SUBBLK_INFO; + u32 version; +}; + +/** + * enum dpu_qos_lut_usage - define QoS LUT use cases + */ +enum dpu_qos_lut_usage { + DPU_QOS_LUT_USAGE_LINEAR, + DPU_QOS_LUT_USAGE_MACROTILE, + DPU_QOS_LUT_USAGE_NRT, + DPU_QOS_LUT_USAGE_MAX, +}; + +/** + * struct dpu_qos_lut_entry - define QoS LUT table entry + * @fl: fill level, or zero on last entry to indicate default lut + * @lut: lut to use if equal to or less than fill level + */ +struct dpu_qos_lut_entry { + u32 fl; + u64 lut; +}; + +/** + * struct dpu_qos_lut_tbl - define QoS LUT table + * @nentry: number of entry in this table + * @entries: Pointer to table entries + */ +struct dpu_qos_lut_tbl { + u32 nentry; + const struct dpu_qos_lut_entry *entries; +}; + +/** + * struct dpu_caps - define DPU capabilities + * @max_mixer_width max layer mixer line width support. + * @max_mixer_blendstages max layer mixer blend stages or + * supported z order + * @qseed_type qseed2 or qseed3 support. + * @smart_dma_rev Supported version of SmartDMA feature. + * @ubwc_version UBWC feature version (0x0 for not supported) + * @has_src_split source split feature status + * @has_dim_layer dim layer feature status + * @has_idle_pc indicate if idle power collapse feature is supported + * @has_3d_merge indicate if 3D merge is supported + * @max_linewidth max linewidth for sspp + * @pixel_ram_size size of latency hiding and de-tiling buffer in bytes + * @max_hdeci_exp max horizontal decimation supported (max is 2^value) + * @max_vdeci_exp max vertical decimation supported (max is 2^value) + */ +struct dpu_caps { + u32 max_mixer_width; + u32 max_mixer_blendstages; + u32 qseed_type; + u32 smart_dma_rev; + u32 ubwc_version; + bool has_src_split; + bool has_dim_layer; + bool has_idle_pc; + bool has_3d_merge; + /* SSPP limits */ + u32 max_linewidth; + u32 pixel_ram_size; + u32 max_hdeci_exp; + u32 max_vdeci_exp; +}; + +/** + * struct dpu_sspp_sub_blks : SSPP sub-blocks + * common: Pointer to common configurations shared by sub blocks + * @creq_vblank: creq priority during vertical blanking + * @danger_vblank: danger priority during vertical blanking + * @maxdwnscale: max downscale ratio supported(without DECIMATION) + * @maxupscale: maxupscale ratio supported + * @smart_dma_priority: hw priority of rect1 of multirect pipe + * @max_per_pipe_bw: maximum allowable bandwidth of this pipe in kBps + * @qseed_ver: qseed version + * @src_blk: + * @scaler_blk: + * @csc_blk: + * @hsic: + * @memcolor: + * @pcc_blk: + * @igc_blk: + * @format_list: Pointer to list of supported formats + * @num_formats: Number of supported formats + * @virt_format_list: Pointer to list of supported formats for virtual planes + * @virt_num_formats: Number of supported formats for virtual planes + */ +struct dpu_sspp_sub_blks { + u32 creq_vblank; + u32 danger_vblank; + u32 maxdwnscale; + u32 maxupscale; + u32 smart_dma_priority; + u32 max_per_pipe_bw; + u32 qseed_ver; + struct dpu_src_blk src_blk; + struct dpu_scaler_blk scaler_blk; + struct dpu_pp_blk csc_blk; + struct dpu_pp_blk hsic_blk; + struct dpu_pp_blk memcolor_blk; + struct dpu_pp_blk pcc_blk; + struct dpu_pp_blk igc_blk; + + const u32 *format_list; + u32 num_formats; + const u32 *virt_format_list; + u32 virt_num_formats; +}; + +/** + * struct dpu_lm_sub_blks: information of mixer block + * @maxwidth: Max pixel width supported by this mixer + * @maxblendstages: Max number of blend-stages supported + * @blendstage_base: Blend-stage register base offset + * @gc: gamma correction block + */ +struct dpu_lm_sub_blks { + u32 maxwidth; + u32 maxblendstages; + u32 blendstage_base[MAX_BLOCKS]; + struct dpu_pp_blk gc; +}; + +/** + * struct dpu_dspp_sub_blks: Information of DSPP block + * @gc : gamma correction block + * @pcc: pixel color correction block + */ +struct dpu_dspp_sub_blks { + struct dpu_pp_blk gc; + struct dpu_pp_blk pcc; +}; + +struct dpu_pingpong_sub_blks { + struct dpu_pp_blk te; + struct dpu_pp_blk te2; + struct dpu_pp_blk dither; +}; + +/** + * dpu_clk_ctrl_type - Defines top level clock control signals + */ +enum dpu_clk_ctrl_type { + DPU_CLK_CTRL_NONE, + DPU_CLK_CTRL_VIG0, + DPU_CLK_CTRL_VIG1, + DPU_CLK_CTRL_VIG2, + DPU_CLK_CTRL_VIG3, + DPU_CLK_CTRL_VIG4, + DPU_CLK_CTRL_RGB0, + DPU_CLK_CTRL_RGB1, + DPU_CLK_CTRL_RGB2, + DPU_CLK_CTRL_RGB3, + DPU_CLK_CTRL_DMA0, + DPU_CLK_CTRL_DMA1, + DPU_CLK_CTRL_CURSOR0, + DPU_CLK_CTRL_CURSOR1, + DPU_CLK_CTRL_INLINE_ROT0_SSPP, + DPU_CLK_CTRL_REG_DMA, + DPU_CLK_CTRL_MAX, +}; + +/* struct dpu_clk_ctrl_reg : Clock control register + * @reg_off: register offset + * @bit_off: bit offset + */ +struct dpu_clk_ctrl_reg { + u32 reg_off; + u32 bit_off; +}; + +/* struct dpu_mdp_cfg : MDP TOP-BLK instance info + * @id: index identifying this block + * @base: register base offset to mdss + * @features bit mask identifying sub-blocks/features + * @highest_bank_bit: UBWC parameter + * @ubwc_static: ubwc static configuration + * @ubwc_swizzle: ubwc default swizzle setting + * @clk_ctrls clock control register definition + */ +struct dpu_mdp_cfg { + DPU_HW_BLK_INFO; + u32 highest_bank_bit; + u32 ubwc_swizzle; + struct dpu_clk_ctrl_reg clk_ctrls[DPU_CLK_CTRL_MAX]; +}; + +/* struct dpu_mdp_cfg : MDP TOP-BLK instance info + * @id: index identifying this block + * @base: register base offset to mdss + * @features bit mask identifying sub-blocks/features + */ +struct dpu_ctl_cfg { + DPU_HW_BLK_INFO; +}; + +/** + * struct dpu_sspp_cfg - information of source pipes + * @id: index identifying this block + * @base register offset of this block + * @features bit mask identifying sub-blocks/features + * @sblk: SSPP sub-blocks information + * @xin_id: bus client identifier + * @clk_ctrl clock control identifier + * @type sspp type identifier + */ +struct dpu_sspp_cfg { + DPU_HW_BLK_INFO; + const struct dpu_sspp_sub_blks *sblk; + u32 xin_id; + enum dpu_clk_ctrl_type clk_ctrl; + u32 type; +}; + +/** + * struct dpu_lm_cfg - information of layer mixer blocks + * @id: index identifying this block + * @base register offset of this block + * @features bit mask identifying sub-blocks/features + * @sblk: LM Sub-blocks information + * @pingpong: ID of connected PingPong, PINGPONG_MAX if unsupported + * @lm_pair_mask: Bitmask of LMs that can be controlled by same CTL + */ +struct dpu_lm_cfg { + DPU_HW_BLK_INFO; + const struct dpu_lm_sub_blks *sblk; + u32 pingpong; + u32 dspp; + unsigned long lm_pair_mask; +}; + +/** + * struct dpu_dspp_cfg - information of DSPP blocks + * @id enum identifying this block + * @base register offset of this block + * @features bit mask identifying sub-blocks/features + * supported by this block + * @sblk sub-blocks information + */ +struct dpu_dspp_cfg { + DPU_HW_BLK_INFO; + const struct dpu_dspp_sub_blks *sblk; +}; + +/** + * struct dpu_pingpong_cfg - information of PING-PONG blocks + * @id enum identifying this block + * @base register offset of this block + * @features bit mask identifying sub-blocks/features + * @sblk sub-blocks information + */ +struct dpu_pingpong_cfg { + DPU_HW_BLK_INFO; + const struct dpu_pingpong_sub_blks *sblk; +}; + +/** + * struct dpu_intf_cfg - information of timing engine blocks + * @id enum identifying this block + * @base register offset of this block + * @features bit mask identifying sub-blocks/features + * @type: Interface type(DSI, DP, HDMI) + * @controller_id: Controller Instance ID in case of multiple of intf type + * @prog_fetch_lines_worst_case Worst case latency num lines needed to prefetch + */ +struct dpu_intf_cfg { + DPU_HW_BLK_INFO; + u32 type; /* interface type*/ + u32 controller_id; + u32 prog_fetch_lines_worst_case; +}; + +/** + * struct dpu_vbif_dynamic_ot_cfg - dynamic OT setting + * @pps pixel per seconds + * @ot_limit OT limit to use up to specified pixel per second + */ +struct dpu_vbif_dynamic_ot_cfg { + u64 pps; + u32 ot_limit; +}; + +/** + * struct dpu_vbif_dynamic_ot_tbl - dynamic OT setting table + * @count length of cfg + * @cfg pointer to array of configuration settings with + * ascending requirements + */ +struct dpu_vbif_dynamic_ot_tbl { + u32 count; + const struct dpu_vbif_dynamic_ot_cfg *cfg; +}; + +/** + * struct dpu_vbif_qos_tbl - QoS priority table + * @npriority_lvl num of priority level + * @priority_lvl pointer to array of priority level in ascending order + */ +struct dpu_vbif_qos_tbl { + u32 npriority_lvl; + const u32 *priority_lvl; +}; + +/** + * struct dpu_vbif_cfg - information of VBIF blocks + * @id enum identifying this block + * @base register offset of this block + * @features bit mask identifying sub-blocks/features + * @ot_rd_limit default OT read limit + * @ot_wr_limit default OT write limit + * @xin_halt_timeout maximum time (in usec) for xin to halt + * @dynamic_ot_rd_tbl dynamic OT read configuration table + * @dynamic_ot_wr_tbl dynamic OT write configuration table + * @qos_rt_tbl real-time QoS priority table + * @qos_nrt_tbl non-real-time QoS priority table + * @memtype_count number of defined memtypes + * @memtype array of xin memtype definitions + */ +struct dpu_vbif_cfg { + DPU_HW_BLK_INFO; + u32 default_ot_rd_limit; + u32 default_ot_wr_limit; + u32 xin_halt_timeout; + struct dpu_vbif_dynamic_ot_tbl dynamic_ot_rd_tbl; + struct dpu_vbif_dynamic_ot_tbl dynamic_ot_wr_tbl; + struct dpu_vbif_qos_tbl qos_rt_tbl; + struct dpu_vbif_qos_tbl qos_nrt_tbl; + u32 memtype_count; + u32 memtype[MAX_XIN_COUNT]; +}; +/** + * struct dpu_reg_dma_cfg - information of lut dma blocks + * @id enum identifying this block + * @base register offset of this block + * @features bit mask identifying sub-blocks/features + * @version version of lutdma hw block + * @trigger_sel_off offset to trigger select registers of lutdma + */ +struct dpu_reg_dma_cfg { + DPU_HW_BLK_INFO; + u32 version; + u32 trigger_sel_off; + u32 xin_id; + enum dpu_clk_ctrl_type clk_ctrl; +}; + +/** + * Define CDP use cases + * @DPU_PERF_CDP_UDAGE_RT: real-time use cases + * @DPU_PERF_CDP_USAGE_NRT: non real-time use cases such as WFD + */ +enum { + DPU_PERF_CDP_USAGE_RT, + DPU_PERF_CDP_USAGE_NRT, + DPU_PERF_CDP_USAGE_MAX +}; + +/** + * struct dpu_perf_cdp_cfg - define CDP use case configuration + * @rd_enable: true if read pipe CDP is enabled + * @wr_enable: true if write pipe CDP is enabled + */ +struct dpu_perf_cdp_cfg { + bool rd_enable; + bool wr_enable; +}; + +/** + * struct dpu_perf_cfg - performance control settings + * @max_bw_low low threshold of maximum bandwidth (kbps) + * @max_bw_high high threshold of maximum bandwidth (kbps) + * @min_core_ib minimum bandwidth for core (kbps) + * @min_core_ib minimum mnoc ib vote in kbps + * @min_llcc_ib minimum llcc ib vote in kbps + * @min_dram_ib minimum dram ib vote in kbps + * @core_ib_ff core instantaneous bandwidth fudge factor + * @core_clk_ff core clock fudge factor + * @comp_ratio_rt string of 0 or more of <fourcc>/<ven>/<mod>/<comp ratio> + * @comp_ratio_nrt string of 0 or more of <fourcc>/<ven>/<mod>/<comp ratio> + * @undersized_prefill_lines undersized prefill in lines + * @xtra_prefill_lines extra prefill latency in lines + * @dest_scale_prefill_lines destination scaler latency in lines + * @macrotile_perfill_lines macrotile latency in lines + * @yuv_nv12_prefill_lines yuv_nv12 latency in lines + * @linear_prefill_lines linear latency in lines + * @downscaling_prefill_lines downscaling latency in lines + * @amortizable_theshold minimum y position for traffic shaping prefill + * @min_prefill_lines minimum pipeline latency in lines + * @clk_inefficiency_factor DPU src clock inefficiency factor + * @bw_inefficiency_factor DPU axi bus bw inefficiency factor + * @safe_lut_tbl: LUT tables for safe signals + * @danger_lut_tbl: LUT tables for danger signals + * @qos_lut_tbl: LUT tables for QoS signals + * @cdp_cfg cdp use case configurations + */ +struct dpu_perf_cfg { + u32 max_bw_low; + u32 max_bw_high; + u32 min_core_ib; + u32 min_llcc_ib; + u32 min_dram_ib; + const char *core_ib_ff; + const char *core_clk_ff; + const char *comp_ratio_rt; + const char *comp_ratio_nrt; + u32 undersized_prefill_lines; + u32 xtra_prefill_lines; + u32 dest_scale_prefill_lines; + u32 macrotile_prefill_lines; + u32 yuv_nv12_prefill_lines; + u32 linear_prefill_lines; + u32 downscaling_prefill_lines; + u32 amortizable_threshold; + u32 min_prefill_lines; + u32 clk_inefficiency_factor; + u32 bw_inefficiency_factor; + u32 safe_lut_tbl[DPU_QOS_LUT_USAGE_MAX]; + u32 danger_lut_tbl[DPU_QOS_LUT_USAGE_MAX]; + struct dpu_qos_lut_tbl qos_lut_tbl[DPU_QOS_LUT_USAGE_MAX]; + struct dpu_perf_cdp_cfg cdp_cfg[DPU_PERF_CDP_USAGE_MAX]; +}; + +/** + * struct dpu_mdss_cfg - information of MDSS HW + * This is the main catalog data structure representing + * this HW version. Contains number of instances, + * register offsets, capabilities of the all MDSS HW sub-blocks. + * + * @dma_formats Supported formats for dma pipe + * @cursor_formats Supported formats for cursor pipe + * @vig_formats Supported formats for vig pipe + * @mdss_irqs: Bitmap with the irqs supported by the target + */ +struct dpu_mdss_cfg { + u32 hwversion; + + const struct dpu_caps *caps; + + u32 mdp_count; + const struct dpu_mdp_cfg *mdp; + + u32 ctl_count; + const struct dpu_ctl_cfg *ctl; + + u32 sspp_count; + const struct dpu_sspp_cfg *sspp; + + u32 mixer_count; + const struct dpu_lm_cfg *mixer; + + u32 pingpong_count; + const struct dpu_pingpong_cfg *pingpong; + + u32 intf_count; + const struct dpu_intf_cfg *intf; + + u32 vbif_count; + const struct dpu_vbif_cfg *vbif; + + u32 reg_dma_count; + struct dpu_reg_dma_cfg dma_cfg; + + u32 ad_count; + + u32 dspp_count; + const struct dpu_dspp_cfg *dspp; + + /* Add additional block data structures here */ + + struct dpu_perf_cfg perf; + const struct dpu_format_extended *dma_formats; + const struct dpu_format_extended *cursor_formats; + const struct dpu_format_extended *vig_formats; + + unsigned long mdss_irqs; +}; + +struct dpu_mdss_hw_cfg_handler { + u32 hw_rev; + void (*cfg_init)(struct dpu_mdss_cfg *dpu_cfg); +}; + +/* + * Access Macros + */ +#define BLK_MDP(s) ((s)->mdp) +#define BLK_CTL(s) ((s)->ctl) +#define BLK_VIG(s) ((s)->vig) +#define BLK_RGB(s) ((s)->rgb) +#define BLK_DMA(s) ((s)->dma) +#define BLK_CURSOR(s) ((s)->cursor) +#define BLK_MIXER(s) ((s)->mixer) +#define BLK_PINGPONG(s) ((s)->pingpong) +#define BLK_INTF(s) ((s)->intf) +#define BLK_AD(s) ((s)->ad) +#define BLK_DSPP(s) ((s)->dspp) + +/** + * dpu_hw_catalog_init - dpu hardware catalog init API retrieves + * hardcoded target specific catalog information in config structure + * @hw_rev: caller needs provide the hardware revision. + * + * Return: dpu config structure + */ +struct dpu_mdss_cfg *dpu_hw_catalog_init(u32 hw_rev); + +/** + * dpu_hw_catalog_deinit - dpu hardware catalog cleanup + * @dpu_cfg: pointer returned from init function + */ +void dpu_hw_catalog_deinit(struct dpu_mdss_cfg *dpu_cfg); + +#endif /* _DPU_HW_CATALOG_H */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog_format.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog_format.h new file mode 100644 index 000000000..3766f0fd0 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_catalog_format.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#include "dpu_hw_mdss.h" + +static const uint32_t qcom_compressed_supported_formats[] = { + DRM_FORMAT_ABGR8888, + DRM_FORMAT_ARGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_BGR565, + + DRM_FORMAT_NV12, +}; + +static const uint32_t plane_formats[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_BGRA4444, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_RGBX4444, + DRM_FORMAT_BGRX4444, +}; + +static const uint32_t plane_formats_yuv[] = { + DRM_FORMAT_ARGB8888, + DRM_FORMAT_ABGR8888, + DRM_FORMAT_RGBA8888, + DRM_FORMAT_BGRX8888, + DRM_FORMAT_BGRA8888, + DRM_FORMAT_XRGB8888, + DRM_FORMAT_XBGR8888, + DRM_FORMAT_RGBX8888, + DRM_FORMAT_RGB888, + DRM_FORMAT_BGR888, + DRM_FORMAT_RGB565, + DRM_FORMAT_BGR565, + DRM_FORMAT_ARGB1555, + DRM_FORMAT_ABGR1555, + DRM_FORMAT_RGBA5551, + DRM_FORMAT_BGRA5551, + DRM_FORMAT_XRGB1555, + DRM_FORMAT_XBGR1555, + DRM_FORMAT_RGBX5551, + DRM_FORMAT_BGRX5551, + DRM_FORMAT_ARGB4444, + DRM_FORMAT_ABGR4444, + DRM_FORMAT_RGBA4444, + DRM_FORMAT_BGRA4444, + DRM_FORMAT_XRGB4444, + DRM_FORMAT_XBGR4444, + DRM_FORMAT_RGBX4444, + DRM_FORMAT_BGRX4444, + + DRM_FORMAT_NV12, + DRM_FORMAT_NV21, + DRM_FORMAT_NV16, + DRM_FORMAT_NV61, + DRM_FORMAT_VYUY, + DRM_FORMAT_UYVY, + DRM_FORMAT_YUYV, + DRM_FORMAT_YVYU, + DRM_FORMAT_YUV420, + DRM_FORMAT_YVU420, +}; diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c new file mode 100644 index 000000000..f8c7100a8 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.c @@ -0,0 +1,601 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#include <linux/delay.h> +#include "dpu_hwio.h" +#include "dpu_hw_ctl.h" +#include "dpu_kms.h" +#include "dpu_trace.h" + +#define CTL_LAYER(lm) \ + (((lm) == LM_5) ? (0x024) : (((lm) - LM_0) * 0x004)) +#define CTL_LAYER_EXT(lm) \ + (0x40 + (((lm) - LM_0) * 0x004)) +#define CTL_LAYER_EXT2(lm) \ + (0x70 + (((lm) - LM_0) * 0x004)) +#define CTL_LAYER_EXT3(lm) \ + (0xA0 + (((lm) - LM_0) * 0x004)) +#define CTL_TOP 0x014 +#define CTL_FLUSH 0x018 +#define CTL_START 0x01C +#define CTL_PREPARE 0x0d0 +#define CTL_SW_RESET 0x030 +#define CTL_LAYER_EXTN_OFFSET 0x40 +#define CTL_INTF_ACTIVE 0x0F4 +#define CTL_INTF_FLUSH 0x110 +#define CTL_INTF_MASTER 0x134 + +#define CTL_MIXER_BORDER_OUT BIT(24) +#define CTL_FLUSH_MASK_CTL BIT(17) + +#define DPU_REG_RESET_TIMEOUT_US 2000 +#define INTF_IDX 31 + +static const struct dpu_ctl_cfg *_ctl_offset(enum dpu_ctl ctl, + const struct dpu_mdss_cfg *m, + void __iomem *addr, + struct dpu_hw_blk_reg_map *b) +{ + int i; + + for (i = 0; i < m->ctl_count; i++) { + if (ctl == m->ctl[i].id) { + b->base_off = addr; + b->blk_off = m->ctl[i].base; + b->length = m->ctl[i].len; + b->hwversion = m->hwversion; + b->log_mask = DPU_DBG_MASK_CTL; + return &m->ctl[i]; + } + } + return ERR_PTR(-ENOMEM); +} + +static int _mixer_stages(const struct dpu_lm_cfg *mixer, int count, + enum dpu_lm lm) +{ + int i; + int stages = -EINVAL; + + for (i = 0; i < count; i++) { + if (lm == mixer[i].id) { + stages = mixer[i].sblk->maxblendstages; + break; + } + } + + return stages; +} + +static inline u32 dpu_hw_ctl_get_flush_register(struct dpu_hw_ctl *ctx) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + + return DPU_REG_READ(c, CTL_FLUSH); +} + +static inline void dpu_hw_ctl_trigger_start(struct dpu_hw_ctl *ctx) +{ + trace_dpu_hw_ctl_trigger_start(ctx->pending_flush_mask, + dpu_hw_ctl_get_flush_register(ctx)); + DPU_REG_WRITE(&ctx->hw, CTL_START, 0x1); +} + +static inline void dpu_hw_ctl_trigger_pending(struct dpu_hw_ctl *ctx) +{ + trace_dpu_hw_ctl_trigger_prepare(ctx->pending_flush_mask, + dpu_hw_ctl_get_flush_register(ctx)); + DPU_REG_WRITE(&ctx->hw, CTL_PREPARE, 0x1); +} + +static inline void dpu_hw_ctl_clear_pending_flush(struct dpu_hw_ctl *ctx) +{ + trace_dpu_hw_ctl_clear_pending_flush(ctx->pending_flush_mask, + dpu_hw_ctl_get_flush_register(ctx)); + ctx->pending_flush_mask = 0x0; +} + +static inline void dpu_hw_ctl_update_pending_flush(struct dpu_hw_ctl *ctx, + u32 flushbits) +{ + trace_dpu_hw_ctl_update_pending_flush(flushbits, + ctx->pending_flush_mask); + ctx->pending_flush_mask |= flushbits; +} + +static inline void dpu_hw_ctl_update_pending_intf_flush(struct dpu_hw_ctl *ctx, + u32 flushbits) +{ + ctx->pending_intf_flush_mask |= flushbits; +} + +static u32 dpu_hw_ctl_get_pending_flush(struct dpu_hw_ctl *ctx) +{ + return ctx->pending_flush_mask; +} + +static inline void dpu_hw_ctl_trigger_flush_v1(struct dpu_hw_ctl *ctx) +{ + + if (ctx->pending_flush_mask & BIT(INTF_IDX)) + DPU_REG_WRITE(&ctx->hw, CTL_INTF_FLUSH, + ctx->pending_intf_flush_mask); + + DPU_REG_WRITE(&ctx->hw, CTL_FLUSH, ctx->pending_flush_mask); +} + +static inline void dpu_hw_ctl_trigger_flush(struct dpu_hw_ctl *ctx) +{ + trace_dpu_hw_ctl_trigger_pending_flush(ctx->pending_flush_mask, + dpu_hw_ctl_get_flush_register(ctx)); + DPU_REG_WRITE(&ctx->hw, CTL_FLUSH, ctx->pending_flush_mask); +} + +static uint32_t dpu_hw_ctl_get_bitmask_sspp(struct dpu_hw_ctl *ctx, + enum dpu_sspp sspp) +{ + uint32_t flushbits = 0; + + switch (sspp) { + case SSPP_VIG0: + flushbits = BIT(0); + break; + case SSPP_VIG1: + flushbits = BIT(1); + break; + case SSPP_VIG2: + flushbits = BIT(2); + break; + case SSPP_VIG3: + flushbits = BIT(18); + break; + case SSPP_RGB0: + flushbits = BIT(3); + break; + case SSPP_RGB1: + flushbits = BIT(4); + break; + case SSPP_RGB2: + flushbits = BIT(5); + break; + case SSPP_RGB3: + flushbits = BIT(19); + break; + case SSPP_DMA0: + flushbits = BIT(11); + break; + case SSPP_DMA1: + flushbits = BIT(12); + break; + case SSPP_DMA2: + flushbits = BIT(24); + break; + case SSPP_DMA3: + flushbits = BIT(25); + break; + case SSPP_CURSOR0: + flushbits = BIT(22); + break; + case SSPP_CURSOR1: + flushbits = BIT(23); + break; + default: + break; + } + + return flushbits; +} + +static uint32_t dpu_hw_ctl_get_bitmask_mixer(struct dpu_hw_ctl *ctx, + enum dpu_lm lm) +{ + uint32_t flushbits = 0; + + switch (lm) { + case LM_0: + flushbits = BIT(6); + break; + case LM_1: + flushbits = BIT(7); + break; + case LM_2: + flushbits = BIT(8); + break; + case LM_3: + flushbits = BIT(9); + break; + case LM_4: + flushbits = BIT(10); + break; + case LM_5: + flushbits = BIT(20); + break; + default: + return -EINVAL; + } + + flushbits |= CTL_FLUSH_MASK_CTL; + + return flushbits; +} + +static int dpu_hw_ctl_get_bitmask_intf(struct dpu_hw_ctl *ctx, + u32 *flushbits, enum dpu_intf intf) +{ + switch (intf) { + case INTF_0: + *flushbits |= BIT(31); + break; + case INTF_1: + *flushbits |= BIT(30); + break; + case INTF_2: + *flushbits |= BIT(29); + break; + case INTF_3: + *flushbits |= BIT(28); + break; + default: + return -EINVAL; + } + return 0; +} + +static int dpu_hw_ctl_get_bitmask_intf_v1(struct dpu_hw_ctl *ctx, + u32 *flushbits, enum dpu_intf intf) +{ + *flushbits |= BIT(31); + return 0; +} + +static int dpu_hw_ctl_active_get_bitmask_intf(struct dpu_hw_ctl *ctx, + u32 *flushbits, enum dpu_intf intf) +{ + *flushbits |= BIT(intf - INTF_0); + return 0; +} + +static uint32_t dpu_hw_ctl_get_bitmask_dspp(struct dpu_hw_ctl *ctx, + enum dpu_dspp dspp) +{ + uint32_t flushbits = 0; + + switch (dspp) { + case DSPP_0: + flushbits = BIT(13); + break; + case DSPP_1: + flushbits = BIT(14); + break; + case DSPP_2: + flushbits = BIT(15); + break; + case DSPP_3: + flushbits = BIT(21); + break; + default: + return 0; + } + + return flushbits; +} + +static u32 dpu_hw_ctl_poll_reset_status(struct dpu_hw_ctl *ctx, u32 timeout_us) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + ktime_t timeout; + u32 status; + + timeout = ktime_add_us(ktime_get(), timeout_us); + + /* + * it takes around 30us to have mdp finish resetting its ctl path + * poll every 50us so that reset should be completed at 1st poll + */ + do { + status = DPU_REG_READ(c, CTL_SW_RESET); + status &= 0x1; + if (status) + usleep_range(20, 50); + } while (status && ktime_compare_safe(ktime_get(), timeout) < 0); + + return status; +} + +static int dpu_hw_ctl_reset_control(struct dpu_hw_ctl *ctx) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + + pr_debug("issuing hw ctl reset for ctl:%d\n", ctx->idx); + DPU_REG_WRITE(c, CTL_SW_RESET, 0x1); + if (dpu_hw_ctl_poll_reset_status(ctx, DPU_REG_RESET_TIMEOUT_US)) + return -EINVAL; + + return 0; +} + +static int dpu_hw_ctl_wait_reset_status(struct dpu_hw_ctl *ctx) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + u32 status; + + status = DPU_REG_READ(c, CTL_SW_RESET); + status &= 0x01; + if (!status) + return 0; + + pr_debug("hw ctl reset is set for ctl:%d\n", ctx->idx); + if (dpu_hw_ctl_poll_reset_status(ctx, DPU_REG_RESET_TIMEOUT_US)) { + pr_err("hw recovery is not complete for ctl:%d\n", ctx->idx); + return -EINVAL; + } + + return 0; +} + +static void dpu_hw_ctl_clear_all_blendstages(struct dpu_hw_ctl *ctx) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + int i; + + for (i = 0; i < ctx->mixer_count; i++) { + enum dpu_lm mixer_id = ctx->mixer_hw_caps[i].id; + + DPU_REG_WRITE(c, CTL_LAYER(mixer_id), 0); + DPU_REG_WRITE(c, CTL_LAYER_EXT(mixer_id), 0); + DPU_REG_WRITE(c, CTL_LAYER_EXT2(mixer_id), 0); + DPU_REG_WRITE(c, CTL_LAYER_EXT3(mixer_id), 0); + } +} + +static void dpu_hw_ctl_setup_blendstage(struct dpu_hw_ctl *ctx, + enum dpu_lm lm, struct dpu_hw_stage_cfg *stage_cfg) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + u32 mixercfg = 0, mixercfg_ext = 0, mix, ext; + u32 mixercfg_ext2 = 0, mixercfg_ext3 = 0; + int i, j; + int stages; + int pipes_per_stage; + + stages = _mixer_stages(ctx->mixer_hw_caps, ctx->mixer_count, lm); + if (stages < 0) + return; + + if (test_bit(DPU_MIXER_SOURCESPLIT, + &ctx->mixer_hw_caps->features)) + pipes_per_stage = PIPES_PER_STAGE; + else + pipes_per_stage = 1; + + mixercfg = CTL_MIXER_BORDER_OUT; /* always set BORDER_OUT */ + + if (!stage_cfg) + goto exit; + + for (i = 0; i <= stages; i++) { + /* overflow to ext register if 'i + 1 > 7' */ + mix = (i + 1) & 0x7; + ext = i >= 7; + + for (j = 0 ; j < pipes_per_stage; j++) { + enum dpu_sspp_multirect_index rect_index = + stage_cfg->multirect_index[i][j]; + + switch (stage_cfg->stage[i][j]) { + case SSPP_VIG0: + if (rect_index == DPU_SSPP_RECT_1) { + mixercfg_ext3 |= ((i + 1) & 0xF) << 0; + } else { + mixercfg |= mix << 0; + mixercfg_ext |= ext << 0; + } + break; + case SSPP_VIG1: + if (rect_index == DPU_SSPP_RECT_1) { + mixercfg_ext3 |= ((i + 1) & 0xF) << 4; + } else { + mixercfg |= mix << 3; + mixercfg_ext |= ext << 2; + } + break; + case SSPP_VIG2: + if (rect_index == DPU_SSPP_RECT_1) { + mixercfg_ext3 |= ((i + 1) & 0xF) << 8; + } else { + mixercfg |= mix << 6; + mixercfg_ext |= ext << 4; + } + break; + case SSPP_VIG3: + if (rect_index == DPU_SSPP_RECT_1) { + mixercfg_ext3 |= ((i + 1) & 0xF) << 12; + } else { + mixercfg |= mix << 26; + mixercfg_ext |= ext << 6; + } + break; + case SSPP_RGB0: + mixercfg |= mix << 9; + mixercfg_ext |= ext << 8; + break; + case SSPP_RGB1: + mixercfg |= mix << 12; + mixercfg_ext |= ext << 10; + break; + case SSPP_RGB2: + mixercfg |= mix << 15; + mixercfg_ext |= ext << 12; + break; + case SSPP_RGB3: + mixercfg |= mix << 29; + mixercfg_ext |= ext << 14; + break; + case SSPP_DMA0: + if (rect_index == DPU_SSPP_RECT_1) { + mixercfg_ext2 |= ((i + 1) & 0xF) << 8; + } else { + mixercfg |= mix << 18; + mixercfg_ext |= ext << 16; + } + break; + case SSPP_DMA1: + if (rect_index == DPU_SSPP_RECT_1) { + mixercfg_ext2 |= ((i + 1) & 0xF) << 12; + } else { + mixercfg |= mix << 21; + mixercfg_ext |= ext << 18; + } + break; + case SSPP_DMA2: + if (rect_index == DPU_SSPP_RECT_1) { + mixercfg_ext2 |= ((i + 1) & 0xF) << 16; + } else { + mix |= (i + 1) & 0xF; + mixercfg_ext2 |= mix << 0; + } + break; + case SSPP_DMA3: + if (rect_index == DPU_SSPP_RECT_1) { + mixercfg_ext2 |= ((i + 1) & 0xF) << 20; + } else { + mix |= (i + 1) & 0xF; + mixercfg_ext2 |= mix << 4; + } + break; + case SSPP_CURSOR0: + mixercfg_ext |= ((i + 1) & 0xF) << 20; + break; + case SSPP_CURSOR1: + mixercfg_ext |= ((i + 1) & 0xF) << 26; + break; + default: + break; + } + } + } + +exit: + DPU_REG_WRITE(c, CTL_LAYER(lm), mixercfg); + DPU_REG_WRITE(c, CTL_LAYER_EXT(lm), mixercfg_ext); + DPU_REG_WRITE(c, CTL_LAYER_EXT2(lm), mixercfg_ext2); + DPU_REG_WRITE(c, CTL_LAYER_EXT3(lm), mixercfg_ext3); +} + + +static void dpu_hw_ctl_intf_cfg_v1(struct dpu_hw_ctl *ctx, + struct dpu_hw_intf_cfg *cfg) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + u32 intf_active = 0; + u32 mode_sel = 0; + + if (cfg->intf_mode_sel == DPU_CTL_MODE_SEL_CMD) + mode_sel |= BIT(17); + + intf_active = DPU_REG_READ(c, CTL_INTF_ACTIVE); + intf_active |= BIT(cfg->intf - INTF_0); + + DPU_REG_WRITE(c, CTL_TOP, mode_sel); + DPU_REG_WRITE(c, CTL_INTF_ACTIVE, intf_active); +} + +static void dpu_hw_ctl_intf_cfg(struct dpu_hw_ctl *ctx, + struct dpu_hw_intf_cfg *cfg) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + u32 intf_cfg = 0; + + intf_cfg |= (cfg->intf & 0xF) << 4; + + if (cfg->mode_3d) { + intf_cfg |= BIT(19); + intf_cfg |= (cfg->mode_3d - 0x1) << 20; + } + + switch (cfg->intf_mode_sel) { + case DPU_CTL_MODE_SEL_VID: + intf_cfg &= ~BIT(17); + intf_cfg &= ~(0x3 << 15); + break; + case DPU_CTL_MODE_SEL_CMD: + intf_cfg |= BIT(17); + intf_cfg |= ((cfg->stream_sel & 0x3) << 15); + break; + default: + pr_err("unknown interface type %d\n", cfg->intf_mode_sel); + return; + } + + DPU_REG_WRITE(c, CTL_TOP, intf_cfg); +} + +static void _setup_ctl_ops(struct dpu_hw_ctl_ops *ops, + unsigned long cap) +{ + if (cap & BIT(DPU_CTL_ACTIVE_CFG)) { + ops->trigger_flush = dpu_hw_ctl_trigger_flush_v1; + ops->setup_intf_cfg = dpu_hw_ctl_intf_cfg_v1; + ops->get_bitmask_intf = dpu_hw_ctl_get_bitmask_intf_v1; + ops->get_bitmask_active_intf = + dpu_hw_ctl_active_get_bitmask_intf; + ops->update_pending_intf_flush = + dpu_hw_ctl_update_pending_intf_flush; + } else { + ops->trigger_flush = dpu_hw_ctl_trigger_flush; + ops->setup_intf_cfg = dpu_hw_ctl_intf_cfg; + ops->get_bitmask_intf = dpu_hw_ctl_get_bitmask_intf; + } + ops->clear_pending_flush = dpu_hw_ctl_clear_pending_flush; + ops->update_pending_flush = dpu_hw_ctl_update_pending_flush; + ops->get_pending_flush = dpu_hw_ctl_get_pending_flush; + ops->get_flush_register = dpu_hw_ctl_get_flush_register; + ops->trigger_start = dpu_hw_ctl_trigger_start; + ops->trigger_pending = dpu_hw_ctl_trigger_pending; + ops->reset = dpu_hw_ctl_reset_control; + ops->wait_reset_status = dpu_hw_ctl_wait_reset_status; + ops->clear_all_blendstages = dpu_hw_ctl_clear_all_blendstages; + ops->setup_blendstage = dpu_hw_ctl_setup_blendstage; + ops->get_bitmask_sspp = dpu_hw_ctl_get_bitmask_sspp; + ops->get_bitmask_mixer = dpu_hw_ctl_get_bitmask_mixer; + ops->get_bitmask_dspp = dpu_hw_ctl_get_bitmask_dspp; +}; + +static struct dpu_hw_blk_ops dpu_hw_ops; + +struct dpu_hw_ctl *dpu_hw_ctl_init(enum dpu_ctl idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m) +{ + struct dpu_hw_ctl *c; + const struct dpu_ctl_cfg *cfg; + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return ERR_PTR(-ENOMEM); + + cfg = _ctl_offset(idx, m, addr, &c->hw); + if (IS_ERR_OR_NULL(cfg)) { + kfree(c); + pr_err("failed to create dpu_hw_ctl %d\n", idx); + return ERR_PTR(-EINVAL); + } + + c->caps = cfg; + _setup_ctl_ops(&c->ops, c->caps->features); + c->idx = idx; + c->mixer_count = m->mixer_count; + c->mixer_hw_caps = m->mixer; + + dpu_hw_blk_init(&c->base, DPU_HW_BLK_CTL, idx, &dpu_hw_ops); + + return c; +} + +void dpu_hw_ctl_destroy(struct dpu_hw_ctl *ctx) +{ + if (ctx) + dpu_hw_blk_destroy(&ctx->base); + kfree(ctx); +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.h new file mode 100644 index 000000000..ec579b470 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_ctl.h @@ -0,0 +1,233 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_CTL_H +#define _DPU_HW_CTL_H + +#include "dpu_hw_mdss.h" +#include "dpu_hw_util.h" +#include "dpu_hw_catalog.h" +#include "dpu_hw_sspp.h" +#include "dpu_hw_blk.h" + +/** + * dpu_ctl_mode_sel: Interface mode selection + * DPU_CTL_MODE_SEL_VID: Video mode interface + * DPU_CTL_MODE_SEL_CMD: Command mode interface + */ +enum dpu_ctl_mode_sel { + DPU_CTL_MODE_SEL_VID = 0, + DPU_CTL_MODE_SEL_CMD +}; + +struct dpu_hw_ctl; +/** + * struct dpu_hw_stage_cfg - blending stage cfg + * @stage : SSPP_ID at each stage + * @multirect_index: index of the rectangle of SSPP. + */ +struct dpu_hw_stage_cfg { + enum dpu_sspp stage[DPU_STAGE_MAX][PIPES_PER_STAGE]; + enum dpu_sspp_multirect_index multirect_index + [DPU_STAGE_MAX][PIPES_PER_STAGE]; +}; + +/** + * struct dpu_hw_intf_cfg :Describes how the DPU writes data to output interface + * @intf : Interface id + * @mode_3d: 3d mux configuration + * @intf_mode_sel: Interface mode, cmd / vid + * @stream_sel: Stream selection for multi-stream interfaces + */ +struct dpu_hw_intf_cfg { + enum dpu_intf intf; + enum dpu_3d_blend_mode mode_3d; + enum dpu_ctl_mode_sel intf_mode_sel; + int stream_sel; +}; + +/** + * struct dpu_hw_ctl_ops - Interface to the wb Hw driver functions + * Assumption is these functions will be called after clocks are enabled + */ +struct dpu_hw_ctl_ops { + /** + * kickoff hw operation for Sw controlled interfaces + * DSI cmd mode and WB interface are SW controlled + * @ctx : ctl path ctx pointer + */ + void (*trigger_start)(struct dpu_hw_ctl *ctx); + + /** + * kickoff prepare is in progress hw operation for sw + * controlled interfaces: DSI cmd mode and WB interface + * are SW controlled + * @ctx : ctl path ctx pointer + */ + void (*trigger_pending)(struct dpu_hw_ctl *ctx); + + /** + * Clear the value of the cached pending_flush_mask + * No effect on hardware + * @ctx : ctl path ctx pointer + */ + void (*clear_pending_flush)(struct dpu_hw_ctl *ctx); + + /** + * Query the value of the cached pending_flush_mask + * No effect on hardware + * @ctx : ctl path ctx pointer + */ + u32 (*get_pending_flush)(struct dpu_hw_ctl *ctx); + + /** + * OR in the given flushbits to the cached pending_flush_mask + * No effect on hardware + * @ctx : ctl path ctx pointer + * @flushbits : module flushmask + */ + void (*update_pending_flush)(struct dpu_hw_ctl *ctx, + u32 flushbits); + + /** + * OR in the given flushbits to the cached pending_intf_flush_mask + * No effect on hardware + * @ctx : ctl path ctx pointer + * @flushbits : module flushmask + */ + void (*update_pending_intf_flush)(struct dpu_hw_ctl *ctx, + u32 flushbits); + + /** + * Write the value of the pending_flush_mask to hardware + * @ctx : ctl path ctx pointer + */ + void (*trigger_flush)(struct dpu_hw_ctl *ctx); + + /** + * Read the value of the flush register + * @ctx : ctl path ctx pointer + * @Return: value of the ctl flush register. + */ + u32 (*get_flush_register)(struct dpu_hw_ctl *ctx); + + /** + * Setup ctl_path interface config + * @ctx + * @cfg : interface config structure pointer + */ + void (*setup_intf_cfg)(struct dpu_hw_ctl *ctx, + struct dpu_hw_intf_cfg *cfg); + + int (*reset)(struct dpu_hw_ctl *c); + + /* + * wait_reset_status - checks ctl reset status + * @ctx : ctl path ctx pointer + * + * This function checks the ctl reset status bit. + * If the reset bit is set, it keeps polling the status till the hw + * reset is complete. + * Returns: 0 on success or -error if reset incomplete within interval + */ + int (*wait_reset_status)(struct dpu_hw_ctl *ctx); + + uint32_t (*get_bitmask_sspp)(struct dpu_hw_ctl *ctx, + enum dpu_sspp blk); + + uint32_t (*get_bitmask_mixer)(struct dpu_hw_ctl *ctx, + enum dpu_lm blk); + + uint32_t (*get_bitmask_dspp)(struct dpu_hw_ctl *ctx, + enum dpu_dspp blk); + + /** + * Query the value of the intf flush mask + * No effect on hardware + * @ctx : ctl path ctx pointer + */ + int (*get_bitmask_intf)(struct dpu_hw_ctl *ctx, + u32 *flushbits, + enum dpu_intf blk); + + /** + * Query the value of the intf active flush mask + * No effect on hardware + * @ctx : ctl path ctx pointer + */ + int (*get_bitmask_active_intf)(struct dpu_hw_ctl *ctx, + u32 *flushbits, enum dpu_intf blk); + + /** + * Set all blend stages to disabled + * @ctx : ctl path ctx pointer + */ + void (*clear_all_blendstages)(struct dpu_hw_ctl *ctx); + + /** + * Configure layer mixer to pipe configuration + * @ctx : ctl path ctx pointer + * @lm : layer mixer enumeration + * @cfg : blend stage configuration + */ + void (*setup_blendstage)(struct dpu_hw_ctl *ctx, + enum dpu_lm lm, struct dpu_hw_stage_cfg *cfg); +}; + +/** + * struct dpu_hw_ctl : CTL PATH driver object + * @base: hardware block base structure + * @hw: block register map object + * @idx: control path index + * @caps: control path capabilities + * @mixer_count: number of mixers + * @mixer_hw_caps: mixer hardware capabilities + * @pending_flush_mask: storage for pending ctl_flush managed via ops + * @pending_intf_flush_mask: pending INTF flush + * @ops: operation list + */ +struct dpu_hw_ctl { + struct dpu_hw_blk base; + struct dpu_hw_blk_reg_map hw; + + /* ctl path */ + int idx; + const struct dpu_ctl_cfg *caps; + int mixer_count; + const struct dpu_lm_cfg *mixer_hw_caps; + u32 pending_flush_mask; + u32 pending_intf_flush_mask; + + /* ops */ + struct dpu_hw_ctl_ops ops; +}; + +/** + * dpu_hw_ctl - convert base object dpu_hw_base to container + * @hw: Pointer to base hardware block + * return: Pointer to hardware block container + */ +static inline struct dpu_hw_ctl *to_dpu_hw_ctl(struct dpu_hw_blk *hw) +{ + return container_of(hw, struct dpu_hw_ctl, base); +} + +/** + * dpu_hw_ctl_init(): Initializes the ctl_path hw driver object. + * should be called before accessing every ctl path registers. + * @idx: ctl_path index for which driver object is required + * @addr: mapped register io address of MDP + * @m : pointer to mdss catalog data + */ +struct dpu_hw_ctl *dpu_hw_ctl_init(enum dpu_ctl idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m); + +/** + * dpu_hw_ctl_destroy(): Destroys ctl driver context + * should be called to free the context + */ +void dpu_hw_ctl_destroy(struct dpu_hw_ctl *ctx); + +#endif /*_DPU_HW_CTL_H */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_dspp.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_dspp.c new file mode 100644 index 000000000..a6efc11eb --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_dspp.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#include "dpu_hwio.h" +#include "dpu_hw_catalog.h" +#include "dpu_hw_lm.h" +#include "dpu_hw_dspp.h" +#include "dpu_kms.h" + + +/* DSPP_PCC */ +#define PCC_EN BIT(0) +#define PCC_DIS 0 +#define PCC_RED_R_OFF 0x10 +#define PCC_RED_G_OFF 0x1C +#define PCC_RED_B_OFF 0x28 +#define PCC_GREEN_R_OFF 0x14 +#define PCC_GREEN_G_OFF 0x20 +#define PCC_GREEN_B_OFF 0x2C +#define PCC_BLUE_R_OFF 0x18 +#define PCC_BLUE_G_OFF 0x24 +#define PCC_BLUE_B_OFF 0x30 + +static void dpu_setup_dspp_pcc(struct dpu_hw_dspp *ctx, + struct dpu_hw_pcc_cfg *cfg) +{ + + u32 base; + + if (!ctx) { + DRM_ERROR("invalid ctx %pK\n", ctx); + return; + } + + base = ctx->cap->sblk->pcc.base; + + if (!base) { + DRM_ERROR("invalid ctx %pK pcc base 0x%x\n", ctx, base); + return; + } + + if (!cfg) { + DRM_DEBUG_DRIVER("disable pcc feature\n"); + DPU_REG_WRITE(&ctx->hw, base, PCC_DIS); + return; + } + + DPU_REG_WRITE(&ctx->hw, base + PCC_RED_R_OFF, cfg->r.r); + DPU_REG_WRITE(&ctx->hw, base + PCC_RED_G_OFF, cfg->r.g); + DPU_REG_WRITE(&ctx->hw, base + PCC_RED_B_OFF, cfg->r.b); + + DPU_REG_WRITE(&ctx->hw, base + PCC_GREEN_R_OFF, cfg->g.r); + DPU_REG_WRITE(&ctx->hw, base + PCC_GREEN_G_OFF, cfg->g.g); + DPU_REG_WRITE(&ctx->hw, base + PCC_GREEN_B_OFF, cfg->g.b); + + DPU_REG_WRITE(&ctx->hw, base + PCC_BLUE_R_OFF, cfg->b.r); + DPU_REG_WRITE(&ctx->hw, base + PCC_BLUE_G_OFF, cfg->b.g); + DPU_REG_WRITE(&ctx->hw, base + PCC_BLUE_B_OFF, cfg->b.b); + + DPU_REG_WRITE(&ctx->hw, base, PCC_EN); +} + +static void _setup_dspp_ops(struct dpu_hw_dspp *c, + unsigned long features) +{ + if (test_bit(DPU_DSPP_PCC, &features) && + IS_SC7180_TARGET(c->hw.hwversion)) + c->ops.setup_pcc = dpu_setup_dspp_pcc; +} + +static const struct dpu_dspp_cfg *_dspp_offset(enum dpu_dspp dspp, + const struct dpu_mdss_cfg *m, + void __iomem *addr, + struct dpu_hw_blk_reg_map *b) +{ + int i; + + if (!m || !addr || !b) + return ERR_PTR(-EINVAL); + + for (i = 0; i < m->dspp_count; i++) { + if (dspp == m->dspp[i].id) { + b->base_off = addr; + b->blk_off = m->dspp[i].base; + b->length = m->dspp[i].len; + b->hwversion = m->hwversion; + b->log_mask = DPU_DBG_MASK_DSPP; + return &m->dspp[i]; + } + } + + return ERR_PTR(-EINVAL); +} + +static struct dpu_hw_blk_ops dpu_hw_ops; + +struct dpu_hw_dspp *dpu_hw_dspp_init(enum dpu_dspp idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m) +{ + struct dpu_hw_dspp *c; + const struct dpu_dspp_cfg *cfg; + + if (!addr || !m) + return ERR_PTR(-EINVAL); + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return ERR_PTR(-ENOMEM); + + cfg = _dspp_offset(idx, m, addr, &c->hw); + if (IS_ERR_OR_NULL(cfg)) { + kfree(c); + return ERR_PTR(-EINVAL); + } + + /* Assign ops */ + c->idx = idx; + c->cap = cfg; + _setup_dspp_ops(c, c->cap->features); + + dpu_hw_blk_init(&c->base, DPU_HW_BLK_DSPP, idx, &dpu_hw_ops); + + return c; +} + +void dpu_hw_dspp_destroy(struct dpu_hw_dspp *dspp) +{ + if (dspp) + dpu_hw_blk_destroy(&dspp->base); + + kfree(dspp); +} + + diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_dspp.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_dspp.h new file mode 100644 index 000000000..7fa189cfc --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_dspp.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_DSPP_H +#define _DPU_HW_DSPP_H + +#include "dpu_hw_blk.h" + +struct dpu_hw_dspp; + +/** + * struct dpu_hw_pcc_coeff - PCC coefficient structure for each color + * component. + * @r: red coefficient. + * @g: green coefficient. + * @b: blue coefficient. + */ + +struct dpu_hw_pcc_coeff { + __u32 r; + __u32 g; + __u32 b; +}; + +/** + * struct dpu_hw_pcc - pcc feature structure + * @r: red coefficients. + * @g: green coefficients. + * @b: blue coefficients. + */ +struct dpu_hw_pcc_cfg { + struct dpu_hw_pcc_coeff r; + struct dpu_hw_pcc_coeff g; + struct dpu_hw_pcc_coeff b; +}; + +/** + * struct dpu_hw_dspp_ops - interface to the dspp hardware driver functions + * Caller must call the init function to get the dspp context for each dspp + * Assumption is these functions will be called after clocks are enabled + */ +struct dpu_hw_dspp_ops { + /** + * setup_pcc - setup dspp pcc + * @ctx: Pointer to dspp context + * @cfg: Pointer to configuration + */ + void (*setup_pcc)(struct dpu_hw_dspp *ctx, struct dpu_hw_pcc_cfg *cfg); + +}; + +/** + * struct dpu_hw_dspp - dspp description + * @base: Hardware block base structure + * @hw: Block hardware details + * @idx: DSPP index + * @cap: Pointer to layer_cfg + * @ops: Pointer to operations possible for this DSPP + */ +struct dpu_hw_dspp { + struct dpu_hw_blk base; + struct dpu_hw_blk_reg_map hw; + + /* dspp */ + int idx; + const struct dpu_dspp_cfg *cap; + + /* Ops */ + struct dpu_hw_dspp_ops ops; +}; + +/** + * dpu_hw_dspp - convert base object dpu_hw_base to container + * @hw: Pointer to base hardware block + * return: Pointer to hardware block container + */ +static inline struct dpu_hw_dspp *to_dpu_hw_dspp(struct dpu_hw_blk *hw) +{ + return container_of(hw, struct dpu_hw_dspp, base); +} + +/** + * dpu_hw_dspp_init - initializes the dspp hw driver object. + * should be called once before accessing every dspp. + * @idx: DSPP index for which driver object is required + * @addr: Mapped register io address of MDP + * @Return: pointer to structure or ERR_PTR + */ +struct dpu_hw_dspp *dpu_hw_dspp_init(enum dpu_dspp idx, + void __iomem *addr, const struct dpu_mdss_cfg *m); + +/** + * dpu_hw_dspp_destroy(): Destroys DSPP driver context + * @dspp: Pointer to DSPP driver context + */ +void dpu_hw_dspp_destroy(struct dpu_hw_dspp *dspp); + +#endif /*_DPU_HW_DSPP_H */ + diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_interrupts.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_interrupts.c new file mode 100644 index 000000000..d84a84f7f --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_interrupts.c @@ -0,0 +1,1141 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#include <linux/bitops.h> +#include <linux/slab.h> + +#include "dpu_kms.h" +#include "dpu_hw_interrupts.h" +#include "dpu_hw_util.h" +#include "dpu_hw_mdss.h" + +/** + * Register offsets in MDSS register file for the interrupt registers + * w.r.t. to the MDP base + */ +#define MDP_SSPP_TOP0_OFF 0x0 +#define MDP_INTF_0_OFF 0x6A000 +#define MDP_INTF_1_OFF 0x6A800 +#define MDP_INTF_2_OFF 0x6B000 +#define MDP_INTF_3_OFF 0x6B800 +#define MDP_INTF_4_OFF 0x6C000 +#define MDP_AD4_0_OFF 0x7C000 +#define MDP_AD4_1_OFF 0x7D000 +#define MDP_AD4_INTR_EN_OFF 0x41c +#define MDP_AD4_INTR_CLEAR_OFF 0x424 +#define MDP_AD4_INTR_STATUS_OFF 0x420 + +/** + * WB interrupt status bit definitions + */ +#define DPU_INTR_WB_0_DONE BIT(0) +#define DPU_INTR_WB_1_DONE BIT(1) +#define DPU_INTR_WB_2_DONE BIT(4) + +/** + * WDOG timer interrupt status bit definitions + */ +#define DPU_INTR_WD_TIMER_0_DONE BIT(2) +#define DPU_INTR_WD_TIMER_1_DONE BIT(3) +#define DPU_INTR_WD_TIMER_2_DONE BIT(5) +#define DPU_INTR_WD_TIMER_3_DONE BIT(6) +#define DPU_INTR_WD_TIMER_4_DONE BIT(7) + +/** + * Pingpong interrupt status bit definitions + */ +#define DPU_INTR_PING_PONG_0_DONE BIT(8) +#define DPU_INTR_PING_PONG_1_DONE BIT(9) +#define DPU_INTR_PING_PONG_2_DONE BIT(10) +#define DPU_INTR_PING_PONG_3_DONE BIT(11) +#define DPU_INTR_PING_PONG_0_RD_PTR BIT(12) +#define DPU_INTR_PING_PONG_1_RD_PTR BIT(13) +#define DPU_INTR_PING_PONG_2_RD_PTR BIT(14) +#define DPU_INTR_PING_PONG_3_RD_PTR BIT(15) +#define DPU_INTR_PING_PONG_0_WR_PTR BIT(16) +#define DPU_INTR_PING_PONG_1_WR_PTR BIT(17) +#define DPU_INTR_PING_PONG_2_WR_PTR BIT(18) +#define DPU_INTR_PING_PONG_3_WR_PTR BIT(19) +#define DPU_INTR_PING_PONG_0_AUTOREFRESH_DONE BIT(20) +#define DPU_INTR_PING_PONG_1_AUTOREFRESH_DONE BIT(21) +#define DPU_INTR_PING_PONG_2_AUTOREFRESH_DONE BIT(22) +#define DPU_INTR_PING_PONG_3_AUTOREFRESH_DONE BIT(23) + +/** + * Interface interrupt status bit definitions + */ +#define DPU_INTR_INTF_0_UNDERRUN BIT(24) +#define DPU_INTR_INTF_1_UNDERRUN BIT(26) +#define DPU_INTR_INTF_2_UNDERRUN BIT(28) +#define DPU_INTR_INTF_3_UNDERRUN BIT(30) +#define DPU_INTR_INTF_0_VSYNC BIT(25) +#define DPU_INTR_INTF_1_VSYNC BIT(27) +#define DPU_INTR_INTF_2_VSYNC BIT(29) +#define DPU_INTR_INTF_3_VSYNC BIT(31) + +/** + * Pingpong Secondary interrupt status bit definitions + */ +#define DPU_INTR_PING_PONG_S0_AUTOREFRESH_DONE BIT(0) +#define DPU_INTR_PING_PONG_S0_WR_PTR BIT(4) +#define DPU_INTR_PING_PONG_S0_RD_PTR BIT(8) +#define DPU_INTR_PING_PONG_S0_TEAR_DETECTED BIT(22) +#define DPU_INTR_PING_PONG_S0_TE_DETECTED BIT(28) + +/** + * Pingpong TEAR detection interrupt status bit definitions + */ +#define DPU_INTR_PING_PONG_0_TEAR_DETECTED BIT(16) +#define DPU_INTR_PING_PONG_1_TEAR_DETECTED BIT(17) +#define DPU_INTR_PING_PONG_2_TEAR_DETECTED BIT(18) +#define DPU_INTR_PING_PONG_3_TEAR_DETECTED BIT(19) + +/** + * Pingpong TE detection interrupt status bit definitions + */ +#define DPU_INTR_PING_PONG_0_TE_DETECTED BIT(24) +#define DPU_INTR_PING_PONG_1_TE_DETECTED BIT(25) +#define DPU_INTR_PING_PONG_2_TE_DETECTED BIT(26) +#define DPU_INTR_PING_PONG_3_TE_DETECTED BIT(27) + +/** + * Ctl start interrupt status bit definitions + */ +#define DPU_INTR_CTL_0_START BIT(9) +#define DPU_INTR_CTL_1_START BIT(10) +#define DPU_INTR_CTL_2_START BIT(11) +#define DPU_INTR_CTL_3_START BIT(12) +#define DPU_INTR_CTL_4_START BIT(13) + +/** + * Concurrent WB overflow interrupt status bit definitions + */ +#define DPU_INTR_CWB_2_OVERFLOW BIT(14) +#define DPU_INTR_CWB_3_OVERFLOW BIT(15) + +/** + * Histogram VIG done interrupt status bit definitions + */ +#define DPU_INTR_HIST_VIG_0_DONE BIT(0) +#define DPU_INTR_HIST_VIG_1_DONE BIT(4) +#define DPU_INTR_HIST_VIG_2_DONE BIT(8) +#define DPU_INTR_HIST_VIG_3_DONE BIT(10) + +/** + * Histogram VIG reset Sequence done interrupt status bit definitions + */ +#define DPU_INTR_HIST_VIG_0_RSTSEQ_DONE BIT(1) +#define DPU_INTR_HIST_VIG_1_RSTSEQ_DONE BIT(5) +#define DPU_INTR_HIST_VIG_2_RSTSEQ_DONE BIT(9) +#define DPU_INTR_HIST_VIG_3_RSTSEQ_DONE BIT(11) + +/** + * Histogram DSPP done interrupt status bit definitions + */ +#define DPU_INTR_HIST_DSPP_0_DONE BIT(12) +#define DPU_INTR_HIST_DSPP_1_DONE BIT(16) +#define DPU_INTR_HIST_DSPP_2_DONE BIT(20) +#define DPU_INTR_HIST_DSPP_3_DONE BIT(22) + +/** + * Histogram DSPP reset Sequence done interrupt status bit definitions + */ +#define DPU_INTR_HIST_DSPP_0_RSTSEQ_DONE BIT(13) +#define DPU_INTR_HIST_DSPP_1_RSTSEQ_DONE BIT(17) +#define DPU_INTR_HIST_DSPP_2_RSTSEQ_DONE BIT(21) +#define DPU_INTR_HIST_DSPP_3_RSTSEQ_DONE BIT(23) + +/** + * INTF interrupt status bit definitions + */ +#define DPU_INTR_VIDEO_INTO_STATIC BIT(0) +#define DPU_INTR_VIDEO_OUTOF_STATIC BIT(1) +#define DPU_INTR_DSICMD_0_INTO_STATIC BIT(2) +#define DPU_INTR_DSICMD_0_OUTOF_STATIC BIT(3) +#define DPU_INTR_DSICMD_1_INTO_STATIC BIT(4) +#define DPU_INTR_DSICMD_1_OUTOF_STATIC BIT(5) +#define DPU_INTR_DSICMD_2_INTO_STATIC BIT(6) +#define DPU_INTR_DSICMD_2_OUTOF_STATIC BIT(7) +#define DPU_INTR_PROG_LINE BIT(8) + +/** + * AD4 interrupt status bit definitions + */ +#define DPU_INTR_BACKLIGHT_UPDATED BIT(0) +/** + * struct dpu_intr_reg - array of DPU register sets + * @clr_off: offset to CLEAR reg + * @en_off: offset to ENABLE reg + * @status_off: offset to STATUS reg + */ +struct dpu_intr_reg { + u32 clr_off; + u32 en_off; + u32 status_off; +}; + +/** + * struct dpu_irq_type - maps each irq with i/f + * @intr_type: type of interrupt listed in dpu_intr_type + * @instance_idx: instance index of the associated HW block in DPU + * @irq_mask: corresponding bit in the interrupt status reg + * @reg_idx: which reg set to use + */ +struct dpu_irq_type { + u32 intr_type; + u32 instance_idx; + u32 irq_mask; + u32 reg_idx; +}; + +/** + * List of DPU interrupt registers + */ +static const struct dpu_intr_reg dpu_intr_set[] = { + { + MDP_SSPP_TOP0_OFF+INTR_CLEAR, + MDP_SSPP_TOP0_OFF+INTR_EN, + MDP_SSPP_TOP0_OFF+INTR_STATUS + }, + { + MDP_SSPP_TOP0_OFF+INTR2_CLEAR, + MDP_SSPP_TOP0_OFF+INTR2_EN, + MDP_SSPP_TOP0_OFF+INTR2_STATUS + }, + { + MDP_SSPP_TOP0_OFF+HIST_INTR_CLEAR, + MDP_SSPP_TOP0_OFF+HIST_INTR_EN, + MDP_SSPP_TOP0_OFF+HIST_INTR_STATUS + }, + { + MDP_INTF_0_OFF+INTF_INTR_CLEAR, + MDP_INTF_0_OFF+INTF_INTR_EN, + MDP_INTF_0_OFF+INTF_INTR_STATUS + }, + { + MDP_INTF_1_OFF+INTF_INTR_CLEAR, + MDP_INTF_1_OFF+INTF_INTR_EN, + MDP_INTF_1_OFF+INTF_INTR_STATUS + }, + { + MDP_INTF_2_OFF+INTF_INTR_CLEAR, + MDP_INTF_2_OFF+INTF_INTR_EN, + MDP_INTF_2_OFF+INTF_INTR_STATUS + }, + { + MDP_INTF_3_OFF+INTF_INTR_CLEAR, + MDP_INTF_3_OFF+INTF_INTR_EN, + MDP_INTF_3_OFF+INTF_INTR_STATUS + }, + { + MDP_INTF_4_OFF+INTF_INTR_CLEAR, + MDP_INTF_4_OFF+INTF_INTR_EN, + MDP_INTF_4_OFF+INTF_INTR_STATUS + }, + { + MDP_AD4_0_OFF + MDP_AD4_INTR_CLEAR_OFF, + MDP_AD4_0_OFF + MDP_AD4_INTR_EN_OFF, + MDP_AD4_0_OFF + MDP_AD4_INTR_STATUS_OFF, + }, + { + MDP_AD4_1_OFF + MDP_AD4_INTR_CLEAR_OFF, + MDP_AD4_1_OFF + MDP_AD4_INTR_EN_OFF, + MDP_AD4_1_OFF + MDP_AD4_INTR_STATUS_OFF, + } +}; + +/** + * IRQ mapping table - use for lookup an irq_idx in this table that have + * a matching interface type and instance index. + */ +static const struct dpu_irq_type dpu_irq_map[] = { + /* BEGIN MAP_RANGE: 0-31, INTR */ + /* irq_idx: 0-3 */ + { DPU_IRQ_TYPE_WB_ROT_COMP, WB_0, DPU_INTR_WB_0_DONE, 0}, + { DPU_IRQ_TYPE_WB_ROT_COMP, WB_1, DPU_INTR_WB_1_DONE, 0}, + { DPU_IRQ_TYPE_WD_TIMER, WD_TIMER_0, DPU_INTR_WD_TIMER_0_DONE, 0}, + { DPU_IRQ_TYPE_WD_TIMER, WD_TIMER_1, DPU_INTR_WD_TIMER_1_DONE, 0}, + /* irq_idx: 4-7 */ + { DPU_IRQ_TYPE_WB_WFD_COMP, WB_2, DPU_INTR_WB_2_DONE, 0}, + { DPU_IRQ_TYPE_WD_TIMER, WD_TIMER_2, DPU_INTR_WD_TIMER_2_DONE, 0}, + { DPU_IRQ_TYPE_WD_TIMER, WD_TIMER_3, DPU_INTR_WD_TIMER_3_DONE, 0}, + { DPU_IRQ_TYPE_WD_TIMER, WD_TIMER_4, DPU_INTR_WD_TIMER_4_DONE, 0}, + /* irq_idx: 8-11 */ + { DPU_IRQ_TYPE_PING_PONG_COMP, PINGPONG_0, + DPU_INTR_PING_PONG_0_DONE, 0}, + { DPU_IRQ_TYPE_PING_PONG_COMP, PINGPONG_1, + DPU_INTR_PING_PONG_1_DONE, 0}, + { DPU_IRQ_TYPE_PING_PONG_COMP, PINGPONG_2, + DPU_INTR_PING_PONG_2_DONE, 0}, + { DPU_IRQ_TYPE_PING_PONG_COMP, PINGPONG_3, + DPU_INTR_PING_PONG_3_DONE, 0}, + /* irq_idx: 12-15 */ + { DPU_IRQ_TYPE_PING_PONG_RD_PTR, PINGPONG_0, + DPU_INTR_PING_PONG_0_RD_PTR, 0}, + { DPU_IRQ_TYPE_PING_PONG_RD_PTR, PINGPONG_1, + DPU_INTR_PING_PONG_1_RD_PTR, 0}, + { DPU_IRQ_TYPE_PING_PONG_RD_PTR, PINGPONG_2, + DPU_INTR_PING_PONG_2_RD_PTR, 0}, + { DPU_IRQ_TYPE_PING_PONG_RD_PTR, PINGPONG_3, + DPU_INTR_PING_PONG_3_RD_PTR, 0}, + /* irq_idx: 16-19 */ + { DPU_IRQ_TYPE_PING_PONG_WR_PTR, PINGPONG_0, + DPU_INTR_PING_PONG_0_WR_PTR, 0}, + { DPU_IRQ_TYPE_PING_PONG_WR_PTR, PINGPONG_1, + DPU_INTR_PING_PONG_1_WR_PTR, 0}, + { DPU_IRQ_TYPE_PING_PONG_WR_PTR, PINGPONG_2, + DPU_INTR_PING_PONG_2_WR_PTR, 0}, + { DPU_IRQ_TYPE_PING_PONG_WR_PTR, PINGPONG_3, + DPU_INTR_PING_PONG_3_WR_PTR, 0}, + /* irq_idx: 20-23 */ + { DPU_IRQ_TYPE_PING_PONG_AUTO_REF, PINGPONG_0, + DPU_INTR_PING_PONG_0_AUTOREFRESH_DONE, 0}, + { DPU_IRQ_TYPE_PING_PONG_AUTO_REF, PINGPONG_1, + DPU_INTR_PING_PONG_1_AUTOREFRESH_DONE, 0}, + { DPU_IRQ_TYPE_PING_PONG_AUTO_REF, PINGPONG_2, + DPU_INTR_PING_PONG_2_AUTOREFRESH_DONE, 0}, + { DPU_IRQ_TYPE_PING_PONG_AUTO_REF, PINGPONG_3, + DPU_INTR_PING_PONG_3_AUTOREFRESH_DONE, 0}, + /* irq_idx: 24-27 */ + { DPU_IRQ_TYPE_INTF_UNDER_RUN, INTF_0, DPU_INTR_INTF_0_UNDERRUN, 0}, + { DPU_IRQ_TYPE_INTF_VSYNC, INTF_0, DPU_INTR_INTF_0_VSYNC, 0}, + { DPU_IRQ_TYPE_INTF_UNDER_RUN, INTF_1, DPU_INTR_INTF_1_UNDERRUN, 0}, + { DPU_IRQ_TYPE_INTF_VSYNC, INTF_1, DPU_INTR_INTF_1_VSYNC, 0}, + /* irq_idx: 28-31 */ + { DPU_IRQ_TYPE_INTF_UNDER_RUN, INTF_2, DPU_INTR_INTF_2_UNDERRUN, 0}, + { DPU_IRQ_TYPE_INTF_VSYNC, INTF_2, DPU_INTR_INTF_2_VSYNC, 0}, + { DPU_IRQ_TYPE_INTF_UNDER_RUN, INTF_3, DPU_INTR_INTF_3_UNDERRUN, 0}, + { DPU_IRQ_TYPE_INTF_VSYNC, INTF_3, DPU_INTR_INTF_3_VSYNC, 0}, + + /* BEGIN MAP_RANGE: 32-64, INTR2 */ + /* irq_idx: 32-35 */ + { DPU_IRQ_TYPE_PING_PONG_AUTO_REF, PINGPONG_S0, + DPU_INTR_PING_PONG_S0_AUTOREFRESH_DONE, 1}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 1}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 1}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 1}, + /* irq_idx: 36-39 */ + { DPU_IRQ_TYPE_PING_PONG_WR_PTR, PINGPONG_S0, + DPU_INTR_PING_PONG_S0_WR_PTR, 1}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 1}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 1}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 1}, + /* irq_idx: 40 */ + { DPU_IRQ_TYPE_PING_PONG_RD_PTR, PINGPONG_S0, + DPU_INTR_PING_PONG_S0_RD_PTR, 1}, + /* irq_idx: 41-45 */ + { DPU_IRQ_TYPE_CTL_START, CTL_0, + DPU_INTR_CTL_0_START, 1}, + { DPU_IRQ_TYPE_CTL_START, CTL_1, + DPU_INTR_CTL_1_START, 1}, + { DPU_IRQ_TYPE_CTL_START, CTL_2, + DPU_INTR_CTL_2_START, 1}, + { DPU_IRQ_TYPE_CTL_START, CTL_3, + DPU_INTR_CTL_3_START, 1}, + { DPU_IRQ_TYPE_CTL_START, CTL_4, + DPU_INTR_CTL_4_START, 1}, + /* irq_idx: 46-47 */ + { DPU_IRQ_TYPE_CWB_OVERFLOW, CWB_2, DPU_INTR_CWB_2_OVERFLOW, 1}, + { DPU_IRQ_TYPE_CWB_OVERFLOW, CWB_3, DPU_INTR_CWB_3_OVERFLOW, 1}, + /* irq_idx: 48-51 */ + { DPU_IRQ_TYPE_PING_PONG_TEAR_CHECK, PINGPONG_0, + DPU_INTR_PING_PONG_0_TEAR_DETECTED, 1}, + { DPU_IRQ_TYPE_PING_PONG_TEAR_CHECK, PINGPONG_1, + DPU_INTR_PING_PONG_1_TEAR_DETECTED, 1}, + { DPU_IRQ_TYPE_PING_PONG_TEAR_CHECK, PINGPONG_2, + DPU_INTR_PING_PONG_2_TEAR_DETECTED, 1}, + { DPU_IRQ_TYPE_PING_PONG_TEAR_CHECK, PINGPONG_3, + DPU_INTR_PING_PONG_3_TEAR_DETECTED, 1}, + /* irq_idx: 52-55 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 1}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 1}, + { DPU_IRQ_TYPE_PING_PONG_TEAR_CHECK, PINGPONG_S0, + DPU_INTR_PING_PONG_S0_TEAR_DETECTED, 1}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 1}, + /* irq_idx: 56-59 */ + { DPU_IRQ_TYPE_PING_PONG_TE_CHECK, PINGPONG_0, + DPU_INTR_PING_PONG_0_TE_DETECTED, 1}, + { DPU_IRQ_TYPE_PING_PONG_TE_CHECK, PINGPONG_1, + DPU_INTR_PING_PONG_1_TE_DETECTED, 1}, + { DPU_IRQ_TYPE_PING_PONG_TE_CHECK, PINGPONG_2, + DPU_INTR_PING_PONG_2_TE_DETECTED, 1}, + { DPU_IRQ_TYPE_PING_PONG_TE_CHECK, PINGPONG_3, + DPU_INTR_PING_PONG_3_TE_DETECTED, 1}, + /* irq_idx: 60-63 */ + { DPU_IRQ_TYPE_PING_PONG_TE_CHECK, PINGPONG_S0, + DPU_INTR_PING_PONG_S0_TE_DETECTED, 1}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 1}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 1}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 1}, + + /* BEGIN MAP_RANGE: 64-95 HIST */ + /* irq_idx: 64-67 */ + { DPU_IRQ_TYPE_HIST_VIG_DONE, SSPP_VIG0, DPU_INTR_HIST_VIG_0_DONE, 2}, + { DPU_IRQ_TYPE_HIST_VIG_RSTSEQ, SSPP_VIG0, + DPU_INTR_HIST_VIG_0_RSTSEQ_DONE, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + /* irq_idx: 68-71 */ + { DPU_IRQ_TYPE_HIST_VIG_DONE, SSPP_VIG1, DPU_INTR_HIST_VIG_1_DONE, 2}, + { DPU_IRQ_TYPE_HIST_VIG_RSTSEQ, SSPP_VIG1, + DPU_INTR_HIST_VIG_1_RSTSEQ_DONE, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + /* irq_idx: 72-75 */ + { DPU_IRQ_TYPE_HIST_VIG_DONE, SSPP_VIG2, DPU_INTR_HIST_VIG_2_DONE, 2}, + { DPU_IRQ_TYPE_HIST_VIG_RSTSEQ, SSPP_VIG2, + DPU_INTR_HIST_VIG_2_RSTSEQ_DONE, 2}, + { DPU_IRQ_TYPE_HIST_VIG_DONE, SSPP_VIG3, DPU_INTR_HIST_VIG_3_DONE, 2}, + { DPU_IRQ_TYPE_HIST_VIG_RSTSEQ, SSPP_VIG3, + DPU_INTR_HIST_VIG_3_RSTSEQ_DONE, 2}, + /* irq_idx: 76-79 */ + { DPU_IRQ_TYPE_HIST_DSPP_DONE, DSPP_0, DPU_INTR_HIST_DSPP_0_DONE, 2}, + { DPU_IRQ_TYPE_HIST_DSPP_RSTSEQ, DSPP_0, + DPU_INTR_HIST_DSPP_0_RSTSEQ_DONE, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + /* irq_idx: 80-83 */ + { DPU_IRQ_TYPE_HIST_DSPP_DONE, DSPP_1, DPU_INTR_HIST_DSPP_1_DONE, 2}, + { DPU_IRQ_TYPE_HIST_DSPP_RSTSEQ, DSPP_1, + DPU_INTR_HIST_DSPP_1_RSTSEQ_DONE, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + /* irq_idx: 84-87 */ + { DPU_IRQ_TYPE_HIST_DSPP_DONE, DSPP_2, DPU_INTR_HIST_DSPP_2_DONE, 2}, + { DPU_IRQ_TYPE_HIST_DSPP_RSTSEQ, DSPP_2, + DPU_INTR_HIST_DSPP_2_RSTSEQ_DONE, 2}, + { DPU_IRQ_TYPE_HIST_DSPP_DONE, DSPP_3, DPU_INTR_HIST_DSPP_3_DONE, 2}, + { DPU_IRQ_TYPE_HIST_DSPP_RSTSEQ, DSPP_3, + DPU_INTR_HIST_DSPP_3_RSTSEQ_DONE, 2}, + /* irq_idx: 88-91 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + /* irq_idx: 92-95 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 2}, + + /* BEGIN MAP_RANGE: 96-127 INTF_0_INTR */ + /* irq_idx: 96-99 */ + { DPU_IRQ_TYPE_SFI_VIDEO_IN, INTF_0, + DPU_INTR_VIDEO_INTO_STATIC, 3}, + { DPU_IRQ_TYPE_SFI_VIDEO_OUT, INTF_0, + DPU_INTR_VIDEO_OUTOF_STATIC, 3}, + { DPU_IRQ_TYPE_SFI_CMD_0_IN, INTF_0, + DPU_INTR_DSICMD_0_INTO_STATIC, 3}, + { DPU_IRQ_TYPE_SFI_CMD_0_OUT, INTF_0, + DPU_INTR_DSICMD_0_OUTOF_STATIC, 3}, + /* irq_idx: 100-103 */ + { DPU_IRQ_TYPE_SFI_CMD_1_IN, INTF_0, + DPU_INTR_DSICMD_1_INTO_STATIC, 3}, + { DPU_IRQ_TYPE_SFI_CMD_1_OUT, INTF_0, + DPU_INTR_DSICMD_1_OUTOF_STATIC, 3}, + { DPU_IRQ_TYPE_SFI_CMD_2_IN, INTF_0, + DPU_INTR_DSICMD_2_INTO_STATIC, 3}, + { DPU_IRQ_TYPE_SFI_CMD_2_OUT, INTF_0, + DPU_INTR_DSICMD_2_OUTOF_STATIC, 3}, + /* irq_idx: 104-107 */ + { DPU_IRQ_TYPE_PROG_LINE, INTF_0, DPU_INTR_PROG_LINE, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + /* irq_idx: 108-111 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + /* irq_idx: 112-115 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + /* irq_idx: 116-119 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + /* irq_idx: 120-123 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + /* irq_idx: 124-127 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 3}, + + /* BEGIN MAP_RANGE: 128-159 INTF_1_INTR */ + /* irq_idx: 128-131 */ + { DPU_IRQ_TYPE_SFI_VIDEO_IN, INTF_1, + DPU_INTR_VIDEO_INTO_STATIC, 4}, + { DPU_IRQ_TYPE_SFI_VIDEO_OUT, INTF_1, + DPU_INTR_VIDEO_OUTOF_STATIC, 4}, + { DPU_IRQ_TYPE_SFI_CMD_0_IN, INTF_1, + DPU_INTR_DSICMD_0_INTO_STATIC, 4}, + { DPU_IRQ_TYPE_SFI_CMD_0_OUT, INTF_1, + DPU_INTR_DSICMD_0_OUTOF_STATIC, 4}, + /* irq_idx: 132-135 */ + { DPU_IRQ_TYPE_SFI_CMD_1_IN, INTF_1, + DPU_INTR_DSICMD_1_INTO_STATIC, 4}, + { DPU_IRQ_TYPE_SFI_CMD_1_OUT, INTF_1, + DPU_INTR_DSICMD_1_OUTOF_STATIC, 4}, + { DPU_IRQ_TYPE_SFI_CMD_2_IN, INTF_1, + DPU_INTR_DSICMD_2_INTO_STATIC, 4}, + { DPU_IRQ_TYPE_SFI_CMD_2_OUT, INTF_1, + DPU_INTR_DSICMD_2_OUTOF_STATIC, 4}, + /* irq_idx: 136-139 */ + { DPU_IRQ_TYPE_PROG_LINE, INTF_1, DPU_INTR_PROG_LINE, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + /* irq_idx: 140-143 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + /* irq_idx: 144-147 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + /* irq_idx: 148-151 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + /* irq_idx: 152-155 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + /* irq_idx: 156-159 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 4}, + + /* BEGIN MAP_RANGE: 160-191 INTF_2_INTR */ + /* irq_idx: 160-163 */ + { DPU_IRQ_TYPE_SFI_VIDEO_IN, INTF_2, + DPU_INTR_VIDEO_INTO_STATIC, 5}, + { DPU_IRQ_TYPE_SFI_VIDEO_OUT, INTF_2, + DPU_INTR_VIDEO_OUTOF_STATIC, 5}, + { DPU_IRQ_TYPE_SFI_CMD_0_IN, INTF_2, + DPU_INTR_DSICMD_0_INTO_STATIC, 5}, + { DPU_IRQ_TYPE_SFI_CMD_0_OUT, INTF_2, + DPU_INTR_DSICMD_0_OUTOF_STATIC, 5}, + /* irq_idx: 164-167 */ + { DPU_IRQ_TYPE_SFI_CMD_1_IN, INTF_2, + DPU_INTR_DSICMD_1_INTO_STATIC, 5}, + { DPU_IRQ_TYPE_SFI_CMD_1_OUT, INTF_2, + DPU_INTR_DSICMD_1_OUTOF_STATIC, 5}, + { DPU_IRQ_TYPE_SFI_CMD_2_IN, INTF_2, + DPU_INTR_DSICMD_2_INTO_STATIC, 5}, + { DPU_IRQ_TYPE_SFI_CMD_2_OUT, INTF_2, + DPU_INTR_DSICMD_2_OUTOF_STATIC, 5}, + /* irq_idx: 168-171 */ + { DPU_IRQ_TYPE_PROG_LINE, INTF_2, DPU_INTR_PROG_LINE, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + /* irq_idx: 172-175 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + /* irq_idx: 176-179 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + /* irq_idx: 180-183 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + /* irq_idx: 184-187 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + /* irq_idx: 188-191 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 5}, + + /* BEGIN MAP_RANGE: 192-223 INTF_3_INTR */ + /* irq_idx: 192-195 */ + { DPU_IRQ_TYPE_SFI_VIDEO_IN, INTF_3, + DPU_INTR_VIDEO_INTO_STATIC, 6}, + { DPU_IRQ_TYPE_SFI_VIDEO_OUT, INTF_3, + DPU_INTR_VIDEO_OUTOF_STATIC, 6}, + { DPU_IRQ_TYPE_SFI_CMD_0_IN, INTF_3, + DPU_INTR_DSICMD_0_INTO_STATIC, 6}, + { DPU_IRQ_TYPE_SFI_CMD_0_OUT, INTF_3, + DPU_INTR_DSICMD_0_OUTOF_STATIC, 6}, + /* irq_idx: 196-199 */ + { DPU_IRQ_TYPE_SFI_CMD_1_IN, INTF_3, + DPU_INTR_DSICMD_1_INTO_STATIC, 6}, + { DPU_IRQ_TYPE_SFI_CMD_1_OUT, INTF_3, + DPU_INTR_DSICMD_1_OUTOF_STATIC, 6}, + { DPU_IRQ_TYPE_SFI_CMD_2_IN, INTF_3, + DPU_INTR_DSICMD_2_INTO_STATIC, 6}, + { DPU_IRQ_TYPE_SFI_CMD_2_OUT, INTF_3, + DPU_INTR_DSICMD_2_OUTOF_STATIC, 6}, + /* irq_idx: 200-203 */ + { DPU_IRQ_TYPE_PROG_LINE, INTF_3, DPU_INTR_PROG_LINE, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + /* irq_idx: 204-207 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + /* irq_idx: 208-211 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + /* irq_idx: 212-215 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + /* irq_idx: 216-219 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + /* irq_idx: 220-223 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 6}, + + /* BEGIN MAP_RANGE: 224-255 INTF_4_INTR */ + /* irq_idx: 224-227 */ + { DPU_IRQ_TYPE_SFI_VIDEO_IN, INTF_4, + DPU_INTR_VIDEO_INTO_STATIC, 7}, + { DPU_IRQ_TYPE_SFI_VIDEO_OUT, INTF_4, + DPU_INTR_VIDEO_OUTOF_STATIC, 7}, + { DPU_IRQ_TYPE_SFI_CMD_0_IN, INTF_4, + DPU_INTR_DSICMD_0_INTO_STATIC, 7}, + { DPU_IRQ_TYPE_SFI_CMD_0_OUT, INTF_4, + DPU_INTR_DSICMD_0_OUTOF_STATIC, 7}, + /* irq_idx: 228-231 */ + { DPU_IRQ_TYPE_SFI_CMD_1_IN, INTF_4, + DPU_INTR_DSICMD_1_INTO_STATIC, 7}, + { DPU_IRQ_TYPE_SFI_CMD_1_OUT, INTF_4, + DPU_INTR_DSICMD_1_OUTOF_STATIC, 7}, + { DPU_IRQ_TYPE_SFI_CMD_2_IN, INTF_4, + DPU_INTR_DSICMD_2_INTO_STATIC, 7}, + { DPU_IRQ_TYPE_SFI_CMD_2_OUT, INTF_4, + DPU_INTR_DSICMD_2_OUTOF_STATIC, 7}, + /* irq_idx: 232-235 */ + { DPU_IRQ_TYPE_PROG_LINE, INTF_4, DPU_INTR_PROG_LINE, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + /* irq_idx: 236-239 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + /* irq_idx: 240-243 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + /* irq_idx: 244-247 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + /* irq_idx: 248-251 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + /* irq_idx: 252-255 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 7}, + + /* BEGIN MAP_RANGE: 256-287 AD4_0_INTR */ + /* irq_idx: 256-259 */ + { DPU_IRQ_TYPE_AD4_BL_DONE, DSPP_0, DPU_INTR_BACKLIGHT_UPDATED, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + /* irq_idx: 260-263 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + /* irq_idx: 264-267 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + /* irq_idx: 268-271 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + /* irq_idx: 272-275 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + /* irq_idx: 276-279 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + /* irq_idx: 280-283 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + /* irq_idx: 284-287 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 8}, + + /* BEGIN MAP_RANGE: 288-319 AD4_1_INTR */ + /* irq_idx: 288-291 */ + { DPU_IRQ_TYPE_AD4_BL_DONE, DSPP_1, DPU_INTR_BACKLIGHT_UPDATED, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + /* irq_idx: 292-295 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + /* irq_idx: 296-299 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + /* irq_idx: 300-303 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + /* irq_idx: 304-307 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + /* irq_idx: 308-311 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + /* irq_idx: 312-315 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + /* irq_idx: 315-319 */ + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, + { DPU_IRQ_TYPE_RESERVED, 0, 0, 9}, +}; + +static int dpu_hw_intr_irqidx_lookup(enum dpu_intr_type intr_type, + u32 instance_idx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(dpu_irq_map); i++) { + if (intr_type == dpu_irq_map[i].intr_type && + instance_idx == dpu_irq_map[i].instance_idx) + return i; + } + + pr_debug("IRQ lookup fail!! intr_type=%d, instance_idx=%d\n", + intr_type, instance_idx); + return -EINVAL; +} + +static void dpu_hw_intr_dispatch_irq(struct dpu_hw_intr *intr, + void (*cbfunc)(void *, int), + void *arg) +{ + int reg_idx; + int irq_idx; + int start_idx; + int end_idx; + u32 irq_status; + unsigned long irq_flags; + + if (!intr) + return; + + /* + * The dispatcher will save the IRQ status before calling here. + * Now need to go through each IRQ status and find matching + * irq lookup index. + */ + spin_lock_irqsave(&intr->irq_lock, irq_flags); + for (reg_idx = 0; reg_idx < ARRAY_SIZE(dpu_intr_set); reg_idx++) { + irq_status = intr->save_irq_status[reg_idx]; + + /* + * Each Interrupt register has a range of 32 indexes, and + * that is static for dpu_irq_map. + */ + start_idx = reg_idx * 32; + end_idx = start_idx + 32; + + if (!test_bit(reg_idx, &intr->irq_mask) || + start_idx >= ARRAY_SIZE(dpu_irq_map)) + continue; + + /* + * Search through matching intr status from irq map. + * start_idx and end_idx defined the search range in + * the dpu_irq_map. + */ + for (irq_idx = start_idx; + (irq_idx < end_idx) && irq_status; + irq_idx++) + if ((irq_status & dpu_irq_map[irq_idx].irq_mask) && + (dpu_irq_map[irq_idx].reg_idx == reg_idx)) { + /* + * Once a match on irq mask, perform a callback + * to the given cbfunc. cbfunc will take care + * the interrupt status clearing. If cbfunc is + * not provided, then the interrupt clearing + * is here. + */ + if (cbfunc) + cbfunc(arg, irq_idx); + else + intr->ops.clear_intr_status_nolock( + intr, irq_idx); + + /* + * When callback finish, clear the irq_status + * with the matching mask. Once irq_status + * is all cleared, the search can be stopped. + */ + irq_status &= ~dpu_irq_map[irq_idx].irq_mask; + } + } + spin_unlock_irqrestore(&intr->irq_lock, irq_flags); +} + +static int dpu_hw_intr_enable_irq(struct dpu_hw_intr *intr, int irq_idx) +{ + int reg_idx; + unsigned long irq_flags; + const struct dpu_intr_reg *reg; + const struct dpu_irq_type *irq; + const char *dbgstr = NULL; + uint32_t cache_irq_mask; + + if (!intr) + return -EINVAL; + + if (irq_idx < 0 || irq_idx >= ARRAY_SIZE(dpu_irq_map)) { + pr_err("invalid IRQ index: [%d]\n", irq_idx); + return -EINVAL; + } + + irq = &dpu_irq_map[irq_idx]; + reg_idx = irq->reg_idx; + reg = &dpu_intr_set[reg_idx]; + + spin_lock_irqsave(&intr->irq_lock, irq_flags); + cache_irq_mask = intr->cache_irq_mask[reg_idx]; + if (cache_irq_mask & irq->irq_mask) { + dbgstr = "DPU IRQ already set:"; + } else { + dbgstr = "DPU IRQ enabled:"; + + cache_irq_mask |= irq->irq_mask; + /* Cleaning any pending interrupt */ + DPU_REG_WRITE(&intr->hw, reg->clr_off, irq->irq_mask); + /* Enabling interrupts with the new mask */ + DPU_REG_WRITE(&intr->hw, reg->en_off, cache_irq_mask); + + /* ensure register write goes through */ + wmb(); + + intr->cache_irq_mask[reg_idx] = cache_irq_mask; + } + spin_unlock_irqrestore(&intr->irq_lock, irq_flags); + + pr_debug("%s MASK:0x%.8x, CACHE-MASK:0x%.8x\n", dbgstr, + irq->irq_mask, cache_irq_mask); + + return 0; +} + +static int dpu_hw_intr_disable_irq_nolock(struct dpu_hw_intr *intr, int irq_idx) +{ + int reg_idx; + const struct dpu_intr_reg *reg; + const struct dpu_irq_type *irq; + const char *dbgstr = NULL; + uint32_t cache_irq_mask; + + if (!intr) + return -EINVAL; + + if (irq_idx < 0 || irq_idx >= ARRAY_SIZE(dpu_irq_map)) { + pr_err("invalid IRQ index: [%d]\n", irq_idx); + return -EINVAL; + } + + irq = &dpu_irq_map[irq_idx]; + reg_idx = irq->reg_idx; + reg = &dpu_intr_set[reg_idx]; + + cache_irq_mask = intr->cache_irq_mask[reg_idx]; + if ((cache_irq_mask & irq->irq_mask) == 0) { + dbgstr = "DPU IRQ is already cleared:"; + } else { + dbgstr = "DPU IRQ mask disable:"; + + cache_irq_mask &= ~irq->irq_mask; + /* Disable interrupts based on the new mask */ + DPU_REG_WRITE(&intr->hw, reg->en_off, cache_irq_mask); + /* Cleaning any pending interrupt */ + DPU_REG_WRITE(&intr->hw, reg->clr_off, irq->irq_mask); + + /* ensure register write goes through */ + wmb(); + + intr->cache_irq_mask[reg_idx] = cache_irq_mask; + } + + pr_debug("%s MASK:0x%.8x, CACHE-MASK:0x%.8x\n", dbgstr, + irq->irq_mask, cache_irq_mask); + + return 0; +} + +static int dpu_hw_intr_disable_irq(struct dpu_hw_intr *intr, int irq_idx) +{ + unsigned long irq_flags; + + if (!intr) + return -EINVAL; + + if (irq_idx < 0 || irq_idx >= ARRAY_SIZE(dpu_irq_map)) { + pr_err("invalid IRQ index: [%d]\n", irq_idx); + return -EINVAL; + } + + spin_lock_irqsave(&intr->irq_lock, irq_flags); + dpu_hw_intr_disable_irq_nolock(intr, irq_idx); + spin_unlock_irqrestore(&intr->irq_lock, irq_flags); + + return 0; +} + +static int dpu_hw_intr_clear_irqs(struct dpu_hw_intr *intr) +{ + int i; + + if (!intr) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(dpu_intr_set); i++) { + if (test_bit(i, &intr->irq_mask)) + DPU_REG_WRITE(&intr->hw, + dpu_intr_set[i].clr_off, 0xffffffff); + } + + /* ensure register writes go through */ + wmb(); + + return 0; +} + +static int dpu_hw_intr_disable_irqs(struct dpu_hw_intr *intr) +{ + int i; + + if (!intr) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(dpu_intr_set); i++) { + if (test_bit(i, &intr->irq_mask)) + DPU_REG_WRITE(&intr->hw, + dpu_intr_set[i].en_off, 0x00000000); + } + + /* ensure register writes go through */ + wmb(); + + return 0; +} + +static void dpu_hw_intr_get_interrupt_statuses(struct dpu_hw_intr *intr) +{ + int i; + u32 enable_mask; + unsigned long irq_flags; + + if (!intr) + return; + + spin_lock_irqsave(&intr->irq_lock, irq_flags); + for (i = 0; i < ARRAY_SIZE(dpu_intr_set); i++) { + if (!test_bit(i, &intr->irq_mask)) + continue; + + /* Read interrupt status */ + intr->save_irq_status[i] = DPU_REG_READ(&intr->hw, + dpu_intr_set[i].status_off); + + /* Read enable mask */ + enable_mask = DPU_REG_READ(&intr->hw, dpu_intr_set[i].en_off); + + /* and clear the interrupt */ + if (intr->save_irq_status[i]) + DPU_REG_WRITE(&intr->hw, dpu_intr_set[i].clr_off, + intr->save_irq_status[i]); + + /* Finally update IRQ status based on enable mask */ + intr->save_irq_status[i] &= enable_mask; + } + + /* ensure register writes go through */ + wmb(); + + spin_unlock_irqrestore(&intr->irq_lock, irq_flags); +} + +static void dpu_hw_intr_clear_intr_status_nolock(struct dpu_hw_intr *intr, + int irq_idx) +{ + int reg_idx; + + if (!intr) + return; + + reg_idx = dpu_irq_map[irq_idx].reg_idx; + DPU_REG_WRITE(&intr->hw, dpu_intr_set[reg_idx].clr_off, + dpu_irq_map[irq_idx].irq_mask); + + /* ensure register writes go through */ + wmb(); +} + +static u32 dpu_hw_intr_get_interrupt_status(struct dpu_hw_intr *intr, + int irq_idx, bool clear) +{ + int reg_idx; + unsigned long irq_flags; + u32 intr_status; + + if (!intr) + return 0; + + if (irq_idx >= ARRAY_SIZE(dpu_irq_map) || irq_idx < 0) { + pr_err("invalid IRQ index: [%d]\n", irq_idx); + return 0; + } + + spin_lock_irqsave(&intr->irq_lock, irq_flags); + + reg_idx = dpu_irq_map[irq_idx].reg_idx; + intr_status = DPU_REG_READ(&intr->hw, + dpu_intr_set[reg_idx].status_off) & + dpu_irq_map[irq_idx].irq_mask; + if (intr_status && clear) + DPU_REG_WRITE(&intr->hw, dpu_intr_set[reg_idx].clr_off, + intr_status); + + /* ensure register writes go through */ + wmb(); + + spin_unlock_irqrestore(&intr->irq_lock, irq_flags); + + return intr_status; +} + +static void __setup_intr_ops(struct dpu_hw_intr_ops *ops) +{ + ops->irq_idx_lookup = dpu_hw_intr_irqidx_lookup; + ops->enable_irq = dpu_hw_intr_enable_irq; + ops->disable_irq = dpu_hw_intr_disable_irq; + ops->dispatch_irqs = dpu_hw_intr_dispatch_irq; + ops->clear_all_irqs = dpu_hw_intr_clear_irqs; + ops->disable_all_irqs = dpu_hw_intr_disable_irqs; + ops->get_interrupt_statuses = dpu_hw_intr_get_interrupt_statuses; + ops->clear_intr_status_nolock = dpu_hw_intr_clear_intr_status_nolock; + ops->get_interrupt_status = dpu_hw_intr_get_interrupt_status; +} + +static void __intr_offset(struct dpu_mdss_cfg *m, + void __iomem *addr, struct dpu_hw_blk_reg_map *hw) +{ + hw->base_off = addr; + hw->blk_off = m->mdp[0].base; + hw->hwversion = m->hwversion; +} + +struct dpu_hw_intr *dpu_hw_intr_init(void __iomem *addr, + struct dpu_mdss_cfg *m) +{ + struct dpu_hw_intr *intr; + + if (!addr || !m) + return ERR_PTR(-EINVAL); + + intr = kzalloc(sizeof(*intr), GFP_KERNEL); + if (!intr) + return ERR_PTR(-ENOMEM); + + __intr_offset(m, addr, &intr->hw); + __setup_intr_ops(&intr->ops); + + intr->irq_idx_tbl_size = ARRAY_SIZE(dpu_irq_map); + + intr->cache_irq_mask = kcalloc(ARRAY_SIZE(dpu_intr_set), sizeof(u32), + GFP_KERNEL); + if (intr->cache_irq_mask == NULL) { + kfree(intr); + return ERR_PTR(-ENOMEM); + } + + intr->save_irq_status = kcalloc(ARRAY_SIZE(dpu_intr_set), sizeof(u32), + GFP_KERNEL); + if (intr->save_irq_status == NULL) { + kfree(intr->cache_irq_mask); + kfree(intr); + return ERR_PTR(-ENOMEM); + } + + intr->irq_mask = m->mdss_irqs; + spin_lock_init(&intr->irq_lock); + + return intr; +} + +void dpu_hw_intr_destroy(struct dpu_hw_intr *intr) +{ + if (intr) { + kfree(intr->cache_irq_mask); + kfree(intr->save_irq_status); + kfree(intr); + } +} + diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_interrupts.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_interrupts.h new file mode 100644 index 000000000..fc9c98617 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_interrupts.h @@ -0,0 +1,206 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_INTERRUPTS_H +#define _DPU_HW_INTERRUPTS_H + +#include <linux/types.h> + +#include "dpu_hwio.h" +#include "dpu_hw_catalog.h" +#include "dpu_hw_util.h" +#include "dpu_hw_mdss.h" + +/** + * dpu_intr_type - HW Interrupt Type + * @DPU_IRQ_TYPE_WB_ROT_COMP: WB rotator done + * @DPU_IRQ_TYPE_WB_WFD_COMP: WB WFD done + * @DPU_IRQ_TYPE_PING_PONG_COMP: PingPong done + * @DPU_IRQ_TYPE_PING_PONG_RD_PTR: PingPong read pointer + * @DPU_IRQ_TYPE_PING_PONG_WR_PTR: PingPong write pointer + * @DPU_IRQ_TYPE_PING_PONG_AUTO_REF: PingPong auto refresh + * @DPU_IRQ_TYPE_PING_PONG_TEAR_CHECK: PingPong Tear check + * @DPU_IRQ_TYPE_PING_PONG_TE_CHECK: PingPong TE detection + * @DPU_IRQ_TYPE_INTF_UNDER_RUN: INTF underrun + * @DPU_IRQ_TYPE_INTF_VSYNC: INTF VSYNC + * @DPU_IRQ_TYPE_CWB_OVERFLOW: Concurrent WB overflow + * @DPU_IRQ_TYPE_HIST_VIG_DONE: VIG Histogram done + * @DPU_IRQ_TYPE_HIST_VIG_RSTSEQ: VIG Histogram reset + * @DPU_IRQ_TYPE_HIST_DSPP_DONE: DSPP Histogram done + * @DPU_IRQ_TYPE_HIST_DSPP_RSTSEQ: DSPP Histogram reset + * @DPU_IRQ_TYPE_WD_TIMER: Watchdog timer + * @DPU_IRQ_TYPE_SFI_VIDEO_IN: Video static frame INTR into static + * @DPU_IRQ_TYPE_SFI_VIDEO_OUT: Video static frame INTR out-of static + * @DPU_IRQ_TYPE_SFI_CMD_0_IN: DSI CMD0 static frame INTR into static + * @DPU_IRQ_TYPE_SFI_CMD_0_OUT: DSI CMD0 static frame INTR out-of static + * @DPU_IRQ_TYPE_SFI_CMD_1_IN: DSI CMD1 static frame INTR into static + * @DPU_IRQ_TYPE_SFI_CMD_1_OUT: DSI CMD1 static frame INTR out-of static + * @DPU_IRQ_TYPE_SFI_CMD_2_IN: DSI CMD2 static frame INTR into static + * @DPU_IRQ_TYPE_SFI_CMD_2_OUT: DSI CMD2 static frame INTR out-of static + * @DPU_IRQ_TYPE_PROG_LINE: Programmable Line interrupt + * @DPU_IRQ_TYPE_AD4_BL_DONE: AD4 backlight + * @DPU_IRQ_TYPE_CTL_START: Control start + * @DPU_IRQ_TYPE_RESERVED: Reserved for expansion + */ +enum dpu_intr_type { + DPU_IRQ_TYPE_WB_ROT_COMP, + DPU_IRQ_TYPE_WB_WFD_COMP, + DPU_IRQ_TYPE_PING_PONG_COMP, + DPU_IRQ_TYPE_PING_PONG_RD_PTR, + DPU_IRQ_TYPE_PING_PONG_WR_PTR, + DPU_IRQ_TYPE_PING_PONG_AUTO_REF, + DPU_IRQ_TYPE_PING_PONG_TEAR_CHECK, + DPU_IRQ_TYPE_PING_PONG_TE_CHECK, + DPU_IRQ_TYPE_INTF_UNDER_RUN, + DPU_IRQ_TYPE_INTF_VSYNC, + DPU_IRQ_TYPE_CWB_OVERFLOW, + DPU_IRQ_TYPE_HIST_VIG_DONE, + DPU_IRQ_TYPE_HIST_VIG_RSTSEQ, + DPU_IRQ_TYPE_HIST_DSPP_DONE, + DPU_IRQ_TYPE_HIST_DSPP_RSTSEQ, + DPU_IRQ_TYPE_WD_TIMER, + DPU_IRQ_TYPE_SFI_VIDEO_IN, + DPU_IRQ_TYPE_SFI_VIDEO_OUT, + DPU_IRQ_TYPE_SFI_CMD_0_IN, + DPU_IRQ_TYPE_SFI_CMD_0_OUT, + DPU_IRQ_TYPE_SFI_CMD_1_IN, + DPU_IRQ_TYPE_SFI_CMD_1_OUT, + DPU_IRQ_TYPE_SFI_CMD_2_IN, + DPU_IRQ_TYPE_SFI_CMD_2_OUT, + DPU_IRQ_TYPE_PROG_LINE, + DPU_IRQ_TYPE_AD4_BL_DONE, + DPU_IRQ_TYPE_CTL_START, + DPU_IRQ_TYPE_RESERVED, +}; + +struct dpu_hw_intr; + +/** + * Interrupt operations. + */ +struct dpu_hw_intr_ops { + /** + * irq_idx_lookup - Lookup IRQ index on the HW interrupt type + * Used for all irq related ops + * @intr_type: Interrupt type defined in dpu_intr_type + * @instance_idx: HW interrupt block instance + * @return: irq_idx or -EINVAL for lookup fail + */ + int (*irq_idx_lookup)( + enum dpu_intr_type intr_type, + u32 instance_idx); + + /** + * enable_irq - Enable IRQ based on lookup IRQ index + * @intr: HW interrupt handle + * @irq_idx: Lookup irq index return from irq_idx_lookup + * @return: 0 for success, otherwise failure + */ + int (*enable_irq)( + struct dpu_hw_intr *intr, + int irq_idx); + + /** + * disable_irq - Disable IRQ based on lookup IRQ index + * @intr: HW interrupt handle + * @irq_idx: Lookup irq index return from irq_idx_lookup + * @return: 0 for success, otherwise failure + */ + int (*disable_irq)( + struct dpu_hw_intr *intr, + int irq_idx); + + /** + * clear_all_irqs - Clears all the interrupts (i.e. acknowledges + * any asserted IRQs). Useful during reset. + * @intr: HW interrupt handle + * @return: 0 for success, otherwise failure + */ + int (*clear_all_irqs)( + struct dpu_hw_intr *intr); + + /** + * disable_all_irqs - Disables all the interrupts. Useful during reset. + * @intr: HW interrupt handle + * @return: 0 for success, otherwise failure + */ + int (*disable_all_irqs)( + struct dpu_hw_intr *intr); + + /** + * dispatch_irqs - IRQ dispatcher will call the given callback + * function when a matching interrupt status bit is + * found in the irq mapping table. + * @intr: HW interrupt handle + * @cbfunc: Callback function pointer + * @arg: Argument to pass back during callback + */ + void (*dispatch_irqs)( + struct dpu_hw_intr *intr, + void (*cbfunc)(void *arg, int irq_idx), + void *arg); + + /** + * get_interrupt_statuses - Gets and store value from all interrupt + * status registers that are currently fired. + * @intr: HW interrupt handle + */ + void (*get_interrupt_statuses)( + struct dpu_hw_intr *intr); + + /** + * clear_intr_status_nolock() - clears the HW interrupts without lock + * @intr: HW interrupt handle + * @irq_idx: Lookup irq index return from irq_idx_lookup + */ + void (*clear_intr_status_nolock)( + struct dpu_hw_intr *intr, + int irq_idx); + + /** + * get_interrupt_status - Gets HW interrupt status, and clear if set, + * based on given lookup IRQ index. + * @intr: HW interrupt handle + * @irq_idx: Lookup irq index return from irq_idx_lookup + * @clear: True to clear irq after read + */ + u32 (*get_interrupt_status)( + struct dpu_hw_intr *intr, + int irq_idx, + bool clear); +}; + +/** + * struct dpu_hw_intr: hw interrupts handling data structure + * @hw: virtual address mapping + * @ops: function pointer mapping for IRQ handling + * @cache_irq_mask: array of IRQ enable masks reg storage created during init + * @save_irq_status: array of IRQ status reg storage created during init + * @irq_idx_tbl_size: total number of irq_idx mapped in the hw_interrupts + * @irq_lock: spinlock for accessing IRQ resources + */ +struct dpu_hw_intr { + struct dpu_hw_blk_reg_map hw; + struct dpu_hw_intr_ops ops; + u32 *cache_irq_mask; + u32 *save_irq_status; + u32 irq_idx_tbl_size; + spinlock_t irq_lock; + unsigned long irq_mask; +}; + +/** + * dpu_hw_intr_init(): Initializes the interrupts hw object + * @addr: mapped register io address of MDP + * @m : pointer to mdss catalog data + */ +struct dpu_hw_intr *dpu_hw_intr_init(void __iomem *addr, + struct dpu_mdss_cfg *m); + +/** + * dpu_hw_intr_destroy(): Cleanup interrutps hw object + * @intr: pointer to interrupts hw object + */ +void dpu_hw_intr_destroy(struct dpu_hw_intr *intr); +#endif diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.c new file mode 100644 index 000000000..7aa6accb7 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.c @@ -0,0 +1,327 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#include "dpu_hwio.h" +#include "dpu_hw_catalog.h" +#include "dpu_hw_intf.h" +#include "dpu_kms.h" + +#define INTF_TIMING_ENGINE_EN 0x000 +#define INTF_CONFIG 0x004 +#define INTF_HSYNC_CTL 0x008 +#define INTF_VSYNC_PERIOD_F0 0x00C +#define INTF_VSYNC_PERIOD_F1 0x010 +#define INTF_VSYNC_PULSE_WIDTH_F0 0x014 +#define INTF_VSYNC_PULSE_WIDTH_F1 0x018 +#define INTF_DISPLAY_V_START_F0 0x01C +#define INTF_DISPLAY_V_START_F1 0x020 +#define INTF_DISPLAY_V_END_F0 0x024 +#define INTF_DISPLAY_V_END_F1 0x028 +#define INTF_ACTIVE_V_START_F0 0x02C +#define INTF_ACTIVE_V_START_F1 0x030 +#define INTF_ACTIVE_V_END_F0 0x034 +#define INTF_ACTIVE_V_END_F1 0x038 +#define INTF_DISPLAY_HCTL 0x03C +#define INTF_ACTIVE_HCTL 0x040 +#define INTF_BORDER_COLOR 0x044 +#define INTF_UNDERFLOW_COLOR 0x048 +#define INTF_HSYNC_SKEW 0x04C +#define INTF_POLARITY_CTL 0x050 +#define INTF_TEST_CTL 0x054 +#define INTF_TP_COLOR0 0x058 +#define INTF_TP_COLOR1 0x05C +#define INTF_FRAME_LINE_COUNT_EN 0x0A8 +#define INTF_FRAME_COUNT 0x0AC +#define INTF_LINE_COUNT 0x0B0 + +#define INTF_DEFLICKER_CONFIG 0x0F0 +#define INTF_DEFLICKER_STRNG_COEFF 0x0F4 +#define INTF_DEFLICKER_WEAK_COEFF 0x0F8 + +#define INTF_DSI_CMD_MODE_TRIGGER_EN 0x084 +#define INTF_PANEL_FORMAT 0x090 +#define INTF_TPG_ENABLE 0x100 +#define INTF_TPG_MAIN_CONTROL 0x104 +#define INTF_TPG_VIDEO_CONFIG 0x108 +#define INTF_TPG_COMPONENT_LIMITS 0x10C +#define INTF_TPG_RECTANGLE 0x110 +#define INTF_TPG_INITIAL_VALUE 0x114 +#define INTF_TPG_BLK_WHITE_PATTERN_FRAMES 0x118 +#define INTF_TPG_RGB_MAPPING 0x11C +#define INTF_PROG_FETCH_START 0x170 +#define INTF_PROG_ROT_START 0x174 +#define INTF_MUX 0x25C + +static const struct dpu_intf_cfg *_intf_offset(enum dpu_intf intf, + const struct dpu_mdss_cfg *m, + void __iomem *addr, + struct dpu_hw_blk_reg_map *b) +{ + int i; + + for (i = 0; i < m->intf_count; i++) { + if ((intf == m->intf[i].id) && + (m->intf[i].type != INTF_NONE)) { + b->base_off = addr; + b->blk_off = m->intf[i].base; + b->length = m->intf[i].len; + b->hwversion = m->hwversion; + b->log_mask = DPU_DBG_MASK_INTF; + return &m->intf[i]; + } + } + + return ERR_PTR(-EINVAL); +} + +static void dpu_hw_intf_setup_timing_engine(struct dpu_hw_intf *ctx, + const struct intf_timing_params *p, + const struct dpu_format *fmt) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + u32 hsync_period, vsync_period; + u32 display_v_start, display_v_end; + u32 hsync_start_x, hsync_end_x; + u32 active_h_start, active_h_end; + u32 active_v_start, active_v_end; + u32 active_hctl, display_hctl, hsync_ctl; + u32 polarity_ctl, den_polarity, hsync_polarity, vsync_polarity; + u32 panel_format; + u32 intf_cfg; + + /* read interface_cfg */ + intf_cfg = DPU_REG_READ(c, INTF_CONFIG); + hsync_period = p->hsync_pulse_width + p->h_back_porch + p->width + + p->h_front_porch; + vsync_period = p->vsync_pulse_width + p->v_back_porch + p->height + + p->v_front_porch; + + display_v_start = ((p->vsync_pulse_width + p->v_back_porch) * + hsync_period) + p->hsync_skew; + display_v_end = ((vsync_period - p->v_front_porch) * hsync_period) + + p->hsync_skew - 1; + + hsync_start_x = p->h_back_porch + p->hsync_pulse_width; + hsync_end_x = hsync_period - p->h_front_porch - 1; + + if (p->width != p->xres) { + active_h_start = hsync_start_x; + active_h_end = active_h_start + p->xres - 1; + } else { + active_h_start = 0; + active_h_end = 0; + } + + if (p->height != p->yres) { + active_v_start = display_v_start; + active_v_end = active_v_start + (p->yres * hsync_period) - 1; + } else { + active_v_start = 0; + active_v_end = 0; + } + + if (active_h_end) { + active_hctl = (active_h_end << 16) | active_h_start; + intf_cfg |= BIT(29); /* ACTIVE_H_ENABLE */ + } else { + active_hctl = 0; + } + + if (active_v_end) + intf_cfg |= BIT(30); /* ACTIVE_V_ENABLE */ + + hsync_ctl = (hsync_period << 16) | p->hsync_pulse_width; + display_hctl = (hsync_end_x << 16) | hsync_start_x; + + if (ctx->cap->type == INTF_EDP || ctx->cap->type == INTF_DP) { + active_h_start = hsync_start_x; + active_h_end = active_h_start + p->xres - 1; + active_v_start = display_v_start; + active_v_end = active_v_start + (p->yres * hsync_period) - 1; + + display_v_start += p->hsync_pulse_width + p->h_back_porch; + display_v_end -= p->h_front_porch; + + active_hctl = (active_h_end << 16) | active_h_start; + display_hctl = active_hctl; + } + + den_polarity = 0; + if (ctx->cap->type == INTF_HDMI) { + hsync_polarity = p->yres >= 720 ? 0 : 1; + vsync_polarity = p->yres >= 720 ? 0 : 1; + } else if (ctx->cap->type == INTF_DP) { + hsync_polarity = p->hsync_polarity; + vsync_polarity = p->vsync_polarity; + } else { + hsync_polarity = 0; + vsync_polarity = 0; + } + polarity_ctl = (den_polarity << 2) | /* DEN Polarity */ + (vsync_polarity << 1) | /* VSYNC Polarity */ + (hsync_polarity << 0); /* HSYNC Polarity */ + + if (!DPU_FORMAT_IS_YUV(fmt)) + panel_format = (fmt->bits[C0_G_Y] | + (fmt->bits[C1_B_Cb] << 2) | + (fmt->bits[C2_R_Cr] << 4) | + (0x21 << 8)); + else + /* Interface treats all the pixel data in RGB888 format */ + panel_format = (COLOR_8BIT | + (COLOR_8BIT << 2) | + (COLOR_8BIT << 4) | + (0x21 << 8)); + + DPU_REG_WRITE(c, INTF_HSYNC_CTL, hsync_ctl); + DPU_REG_WRITE(c, INTF_VSYNC_PERIOD_F0, vsync_period * hsync_period); + DPU_REG_WRITE(c, INTF_VSYNC_PULSE_WIDTH_F0, + p->vsync_pulse_width * hsync_period); + DPU_REG_WRITE(c, INTF_DISPLAY_HCTL, display_hctl); + DPU_REG_WRITE(c, INTF_DISPLAY_V_START_F0, display_v_start); + DPU_REG_WRITE(c, INTF_DISPLAY_V_END_F0, display_v_end); + DPU_REG_WRITE(c, INTF_ACTIVE_HCTL, active_hctl); + DPU_REG_WRITE(c, INTF_ACTIVE_V_START_F0, active_v_start); + DPU_REG_WRITE(c, INTF_ACTIVE_V_END_F0, active_v_end); + DPU_REG_WRITE(c, INTF_BORDER_COLOR, p->border_clr); + DPU_REG_WRITE(c, INTF_UNDERFLOW_COLOR, p->underflow_clr); + DPU_REG_WRITE(c, INTF_HSYNC_SKEW, p->hsync_skew); + DPU_REG_WRITE(c, INTF_POLARITY_CTL, polarity_ctl); + DPU_REG_WRITE(c, INTF_FRAME_LINE_COUNT_EN, 0x3); + DPU_REG_WRITE(c, INTF_CONFIG, intf_cfg); + DPU_REG_WRITE(c, INTF_PANEL_FORMAT, panel_format); +} + +static void dpu_hw_intf_enable_timing_engine( + struct dpu_hw_intf *intf, + u8 enable) +{ + struct dpu_hw_blk_reg_map *c = &intf->hw; + /* Note: Display interface select is handled in top block hw layer */ + DPU_REG_WRITE(c, INTF_TIMING_ENGINE_EN, enable != 0); +} + +static void dpu_hw_intf_setup_prg_fetch( + struct dpu_hw_intf *intf, + const struct intf_prog_fetch *fetch) +{ + struct dpu_hw_blk_reg_map *c = &intf->hw; + int fetch_enable; + + /* + * Fetch should always be outside the active lines. If the fetching + * is programmed within active region, hardware behavior is unknown. + */ + + fetch_enable = DPU_REG_READ(c, INTF_CONFIG); + if (fetch->enable) { + fetch_enable |= BIT(31); + DPU_REG_WRITE(c, INTF_PROG_FETCH_START, + fetch->fetch_start); + } else { + fetch_enable &= ~BIT(31); + } + + DPU_REG_WRITE(c, INTF_CONFIG, fetch_enable); +} + +static void dpu_hw_intf_bind_pingpong_blk( + struct dpu_hw_intf *intf, + bool enable, + const enum dpu_pingpong pp) +{ + struct dpu_hw_blk_reg_map *c = &intf->hw; + u32 mux_cfg; + + mux_cfg = DPU_REG_READ(c, INTF_MUX); + mux_cfg &= ~0xf; + + if (enable) + mux_cfg |= (pp - PINGPONG_0) & 0x7; + else + mux_cfg |= 0xf; + + DPU_REG_WRITE(c, INTF_MUX, mux_cfg); +} + +static void dpu_hw_intf_get_status( + struct dpu_hw_intf *intf, + struct intf_status *s) +{ + struct dpu_hw_blk_reg_map *c = &intf->hw; + + s->is_en = DPU_REG_READ(c, INTF_TIMING_ENGINE_EN); + if (s->is_en) { + s->frame_count = DPU_REG_READ(c, INTF_FRAME_COUNT); + s->line_count = DPU_REG_READ(c, INTF_LINE_COUNT); + } else { + s->line_count = 0; + s->frame_count = 0; + } +} + +static u32 dpu_hw_intf_get_line_count(struct dpu_hw_intf *intf) +{ + struct dpu_hw_blk_reg_map *c; + + if (!intf) + return 0; + + c = &intf->hw; + + return DPU_REG_READ(c, INTF_LINE_COUNT); +} + +static void _setup_intf_ops(struct dpu_hw_intf_ops *ops, + unsigned long cap) +{ + ops->setup_timing_gen = dpu_hw_intf_setup_timing_engine; + ops->setup_prg_fetch = dpu_hw_intf_setup_prg_fetch; + ops->get_status = dpu_hw_intf_get_status; + ops->enable_timing = dpu_hw_intf_enable_timing_engine; + ops->get_line_count = dpu_hw_intf_get_line_count; + if (cap & BIT(DPU_INTF_INPUT_CTRL)) + ops->bind_pingpong_blk = dpu_hw_intf_bind_pingpong_blk; +} + +static struct dpu_hw_blk_ops dpu_hw_ops; + +struct dpu_hw_intf *dpu_hw_intf_init(enum dpu_intf idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m) +{ + struct dpu_hw_intf *c; + const struct dpu_intf_cfg *cfg; + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return ERR_PTR(-ENOMEM); + + cfg = _intf_offset(idx, m, addr, &c->hw); + if (IS_ERR_OR_NULL(cfg)) { + kfree(c); + pr_err("failed to create dpu_hw_intf %d\n", idx); + return ERR_PTR(-EINVAL); + } + + /* + * Assign ops + */ + c->idx = idx; + c->cap = cfg; + c->mdss = m; + _setup_intf_ops(&c->ops, c->cap->features); + + dpu_hw_blk_init(&c->base, DPU_HW_BLK_INTF, idx, &dpu_hw_ops); + + return c; +} + +void dpu_hw_intf_destroy(struct dpu_hw_intf *intf) +{ + if (intf) + dpu_hw_blk_destroy(&intf->base); + kfree(intf); +} + diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.h new file mode 100644 index 000000000..0ead64d3f --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_intf.h @@ -0,0 +1,119 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_INTF_H +#define _DPU_HW_INTF_H + +#include "dpu_hw_catalog.h" +#include "dpu_hw_mdss.h" +#include "dpu_hw_util.h" +#include "dpu_hw_blk.h" + +struct dpu_hw_intf; + +/* intf timing settings */ +struct intf_timing_params { + u32 width; /* active width */ + u32 height; /* active height */ + u32 xres; /* Display panel width */ + u32 yres; /* Display panel height */ + + u32 h_back_porch; + u32 h_front_porch; + u32 v_back_porch; + u32 v_front_porch; + u32 hsync_pulse_width; + u32 vsync_pulse_width; + u32 hsync_polarity; + u32 vsync_polarity; + u32 border_clr; + u32 underflow_clr; + u32 hsync_skew; +}; + +struct intf_prog_fetch { + u8 enable; + /* vsync counter for the front porch pixel line */ + u32 fetch_start; +}; + +struct intf_status { + u8 is_en; /* interface timing engine is enabled or not */ + u32 frame_count; /* frame count since timing engine enabled */ + u32 line_count; /* current line count including blanking */ +}; + +/** + * struct dpu_hw_intf_ops : Interface to the interface Hw driver functions + * Assumption is these functions will be called after clocks are enabled + * @ setup_timing_gen : programs the timing engine + * @ setup_prog_fetch : enables/disables the programmable fetch logic + * @ enable_timing: enable/disable timing engine + * @ get_status: returns if timing engine is enabled or not + * @ get_line_count: reads current vertical line counter + * @bind_pingpong_blk: enable/disable the connection with pingpong which will + * feed pixels to this interface + */ +struct dpu_hw_intf_ops { + void (*setup_timing_gen)(struct dpu_hw_intf *intf, + const struct intf_timing_params *p, + const struct dpu_format *fmt); + + void (*setup_prg_fetch)(struct dpu_hw_intf *intf, + const struct intf_prog_fetch *fetch); + + void (*enable_timing)(struct dpu_hw_intf *intf, + u8 enable); + + void (*get_status)(struct dpu_hw_intf *intf, + struct intf_status *status); + + u32 (*get_line_count)(struct dpu_hw_intf *intf); + + void (*bind_pingpong_blk)(struct dpu_hw_intf *intf, + bool enable, + const enum dpu_pingpong pp); +}; + +struct dpu_hw_intf { + struct dpu_hw_blk base; + struct dpu_hw_blk_reg_map hw; + + /* intf */ + enum dpu_intf idx; + const struct dpu_intf_cfg *cap; + const struct dpu_mdss_cfg *mdss; + + /* ops */ + struct dpu_hw_intf_ops ops; +}; + +/** + * to_dpu_hw_intf - convert base object dpu_hw_base to container + * @hw: Pointer to base hardware block + * return: Pointer to hardware block container + */ +static inline struct dpu_hw_intf *to_dpu_hw_intf(struct dpu_hw_blk *hw) +{ + return container_of(hw, struct dpu_hw_intf, base); +} + +/** + * dpu_hw_intf_init(): Initializes the intf driver for the passed + * interface idx. + * @idx: interface index for which driver object is required + * @addr: mapped register io address of MDP + * @m : pointer to mdss catalog data + */ +struct dpu_hw_intf *dpu_hw_intf_init(enum dpu_intf idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m); + +/** + * dpu_hw_intf_destroy(): Destroys INTF driver context + * @intf: Pointer to INTF driver context + */ +void dpu_hw_intf_destroy(struct dpu_hw_intf *intf); + +#endif /*_DPU_HW_INTF_H */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.c new file mode 100644 index 000000000..4b8baf714 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.c @@ -0,0 +1,197 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#include "dpu_kms.h" +#include "dpu_hw_catalog.h" +#include "dpu_hwio.h" +#include "dpu_hw_lm.h" +#include "dpu_hw_mdss.h" + +#define LM_OP_MODE 0x00 +#define LM_OUT_SIZE 0x04 +#define LM_BORDER_COLOR_0 0x08 +#define LM_BORDER_COLOR_1 0x010 + +/* These register are offset to mixer base + stage base */ +#define LM_BLEND0_OP 0x00 +#define LM_BLEND0_CONST_ALPHA 0x04 +#define LM_FG_COLOR_FILL_COLOR_0 0x08 +#define LM_FG_COLOR_FILL_COLOR_1 0x0C +#define LM_FG_COLOR_FILL_SIZE 0x10 +#define LM_FG_COLOR_FILL_XY 0x14 + +#define LM_BLEND0_FG_ALPHA 0x04 +#define LM_BLEND0_BG_ALPHA 0x08 + +static const struct dpu_lm_cfg *_lm_offset(enum dpu_lm mixer, + const struct dpu_mdss_cfg *m, + void __iomem *addr, + struct dpu_hw_blk_reg_map *b) +{ + int i; + + for (i = 0; i < m->mixer_count; i++) { + if (mixer == m->mixer[i].id) { + b->base_off = addr; + b->blk_off = m->mixer[i].base; + b->length = m->mixer[i].len; + b->hwversion = m->hwversion; + b->log_mask = DPU_DBG_MASK_LM; + return &m->mixer[i]; + } + } + + return ERR_PTR(-ENOMEM); +} + +/** + * _stage_offset(): returns the relative offset of the blend registers + * for the stage to be setup + * @c: mixer ctx contains the mixer to be programmed + * @stage: stage index to setup + */ +static inline int _stage_offset(struct dpu_hw_mixer *ctx, enum dpu_stage stage) +{ + const struct dpu_lm_sub_blks *sblk = ctx->cap->sblk; + if (stage != DPU_STAGE_BASE && stage <= sblk->maxblendstages) + return sblk->blendstage_base[stage - DPU_STAGE_0]; + + return -EINVAL; +} + +static void dpu_hw_lm_setup_out(struct dpu_hw_mixer *ctx, + struct dpu_hw_mixer_cfg *mixer) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + u32 outsize; + u32 op_mode; + + op_mode = DPU_REG_READ(c, LM_OP_MODE); + + outsize = mixer->out_height << 16 | mixer->out_width; + DPU_REG_WRITE(c, LM_OUT_SIZE, outsize); + + /* SPLIT_LEFT_RIGHT */ + if (mixer->right_mixer) + op_mode |= BIT(31); + else + op_mode &= ~BIT(31); + DPU_REG_WRITE(c, LM_OP_MODE, op_mode); +} + +static void dpu_hw_lm_setup_border_color(struct dpu_hw_mixer *ctx, + struct dpu_mdss_color *color, + u8 border_en) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + + if (border_en) { + DPU_REG_WRITE(c, LM_BORDER_COLOR_0, + (color->color_0 & 0xFFF) | + ((color->color_1 & 0xFFF) << 0x10)); + DPU_REG_WRITE(c, LM_BORDER_COLOR_1, + (color->color_2 & 0xFFF) | + ((color->color_3 & 0xFFF) << 0x10)); + } +} + +static void dpu_hw_lm_setup_blend_config_sdm845(struct dpu_hw_mixer *ctx, + u32 stage, u32 fg_alpha, u32 bg_alpha, u32 blend_op) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + int stage_off; + u32 const_alpha; + + if (stage == DPU_STAGE_BASE) + return; + + stage_off = _stage_offset(ctx, stage); + if (WARN_ON(stage_off < 0)) + return; + + const_alpha = (bg_alpha & 0xFF) | ((fg_alpha & 0xFF) << 16); + DPU_REG_WRITE(c, LM_BLEND0_CONST_ALPHA + stage_off, const_alpha); + DPU_REG_WRITE(c, LM_BLEND0_OP + stage_off, blend_op); +} + +static void dpu_hw_lm_setup_blend_config(struct dpu_hw_mixer *ctx, + u32 stage, u32 fg_alpha, u32 bg_alpha, u32 blend_op) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + int stage_off; + + if (stage == DPU_STAGE_BASE) + return; + + stage_off = _stage_offset(ctx, stage); + if (WARN_ON(stage_off < 0)) + return; + + DPU_REG_WRITE(c, LM_BLEND0_FG_ALPHA + stage_off, fg_alpha); + DPU_REG_WRITE(c, LM_BLEND0_BG_ALPHA + stage_off, bg_alpha); + DPU_REG_WRITE(c, LM_BLEND0_OP + stage_off, blend_op); +} + +static void dpu_hw_lm_setup_color3(struct dpu_hw_mixer *ctx, + uint32_t mixer_op_mode) +{ + struct dpu_hw_blk_reg_map *c = &ctx->hw; + int op_mode; + + /* read the existing op_mode configuration */ + op_mode = DPU_REG_READ(c, LM_OP_MODE); + + op_mode = (op_mode & (BIT(31) | BIT(30))) | mixer_op_mode; + + DPU_REG_WRITE(c, LM_OP_MODE, op_mode); +} + +static void _setup_mixer_ops(const struct dpu_mdss_cfg *m, + struct dpu_hw_lm_ops *ops, + unsigned long features) +{ + ops->setup_mixer_out = dpu_hw_lm_setup_out; + if (m->hwversion >= DPU_HW_VER_400) + ops->setup_blend_config = dpu_hw_lm_setup_blend_config_sdm845; + else + ops->setup_blend_config = dpu_hw_lm_setup_blend_config; + ops->setup_alpha_out = dpu_hw_lm_setup_color3; + ops->setup_border_color = dpu_hw_lm_setup_border_color; +} + +static struct dpu_hw_blk_ops dpu_hw_ops; + +struct dpu_hw_mixer *dpu_hw_lm_init(enum dpu_lm idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m) +{ + struct dpu_hw_mixer *c; + const struct dpu_lm_cfg *cfg; + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return ERR_PTR(-ENOMEM); + + cfg = _lm_offset(idx, m, addr, &c->hw); + if (IS_ERR_OR_NULL(cfg)) { + kfree(c); + return ERR_PTR(-EINVAL); + } + + /* Assign ops */ + c->idx = idx; + c->cap = cfg; + _setup_mixer_ops(m, &c->ops, c->cap->features); + + dpu_hw_blk_init(&c->base, DPU_HW_BLK_LM, idx, &dpu_hw_ops); + + return c; +} + +void dpu_hw_lm_destroy(struct dpu_hw_mixer *lm) +{ + if (lm) + dpu_hw_blk_destroy(&lm->base); + kfree(lm); +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.h new file mode 100644 index 000000000..4a6b2de19 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.h @@ -0,0 +1,102 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_LM_H +#define _DPU_HW_LM_H + +#include "dpu_hw_mdss.h" +#include "dpu_hw_util.h" +#include "dpu_hw_blk.h" + +struct dpu_hw_mixer; + +struct dpu_hw_mixer_cfg { + u32 out_width; + u32 out_height; + bool right_mixer; + int flags; +}; + +struct dpu_hw_color3_cfg { + u8 keep_fg[DPU_STAGE_MAX]; +}; + +/** + * + * struct dpu_hw_lm_ops : Interface to the mixer Hw driver functions + * Assumption is these functions will be called after clocks are enabled + */ +struct dpu_hw_lm_ops { + /* + * Sets up mixer output width and height + * and border color if enabled + */ + void (*setup_mixer_out)(struct dpu_hw_mixer *ctx, + struct dpu_hw_mixer_cfg *cfg); + + /* + * Alpha blending configuration + * for the specified stage + */ + void (*setup_blend_config)(struct dpu_hw_mixer *ctx, uint32_t stage, + uint32_t fg_alpha, uint32_t bg_alpha, uint32_t blend_op); + + /* + * Alpha color component selection from either fg or bg + */ + void (*setup_alpha_out)(struct dpu_hw_mixer *ctx, uint32_t mixer_op); + + /** + * setup_border_color : enable/disable border color + */ + void (*setup_border_color)(struct dpu_hw_mixer *ctx, + struct dpu_mdss_color *color, + u8 border_en); +}; + +struct dpu_hw_mixer { + struct dpu_hw_blk base; + struct dpu_hw_blk_reg_map hw; + + /* lm */ + enum dpu_lm idx; + const struct dpu_lm_cfg *cap; + const struct dpu_mdp_cfg *mdp; + const struct dpu_ctl_cfg *ctl; + + /* ops */ + struct dpu_hw_lm_ops ops; + + /* store mixer info specific to display */ + struct dpu_hw_mixer_cfg cfg; +}; + +/** + * to_dpu_hw_mixer - convert base object dpu_hw_base to container + * @hw: Pointer to base hardware block + * return: Pointer to hardware block container + */ +static inline struct dpu_hw_mixer *to_dpu_hw_mixer(struct dpu_hw_blk *hw) +{ + return container_of(hw, struct dpu_hw_mixer, base); +} + +/** + * dpu_hw_lm_init(): Initializes the mixer hw driver object. + * should be called once before accessing every mixer. + * @idx: mixer index for which driver object is required + * @addr: mapped register io address of MDP + * @m : pointer to mdss catalog data + */ +struct dpu_hw_mixer *dpu_hw_lm_init(enum dpu_lm idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m); + +/** + * dpu_hw_lm_destroy(): Destroys layer mixer driver context + * @lm: Pointer to LM driver context + */ +void dpu_hw_lm_destroy(struct dpu_hw_mixer *lm); + +#endif /*_DPU_HW_LM_H */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_mdss.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_mdss.h new file mode 100644 index 000000000..979fd2c60 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_mdss.h @@ -0,0 +1,433 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_MDSS_H +#define _DPU_HW_MDSS_H + +#include <linux/kernel.h> +#include <linux/err.h> + +#include "msm_drv.h" + +#define DPU_DBG_NAME "dpu" + +#define DPU_NONE 0 + +#ifndef DPU_CSC_MATRIX_COEFF_SIZE +#define DPU_CSC_MATRIX_COEFF_SIZE 9 +#endif + +#ifndef DPU_CSC_CLAMP_SIZE +#define DPU_CSC_CLAMP_SIZE 6 +#endif + +#ifndef DPU_CSC_BIAS_SIZE +#define DPU_CSC_BIAS_SIZE 3 +#endif + +#ifndef DPU_MAX_PLANES +#define DPU_MAX_PLANES 4 +#endif + +#define PIPES_PER_STAGE 2 +#ifndef DPU_MAX_DE_CURVES +#define DPU_MAX_DE_CURVES 3 +#endif + +enum dpu_format_flags { + DPU_FORMAT_FLAG_YUV_BIT, + DPU_FORMAT_FLAG_DX_BIT, + DPU_FORMAT_FLAG_COMPRESSED_BIT, + DPU_FORMAT_FLAG_BIT_MAX, +}; + +#define DPU_FORMAT_FLAG_YUV BIT(DPU_FORMAT_FLAG_YUV_BIT) +#define DPU_FORMAT_FLAG_DX BIT(DPU_FORMAT_FLAG_DX_BIT) +#define DPU_FORMAT_FLAG_COMPRESSED BIT(DPU_FORMAT_FLAG_COMPRESSED_BIT) +#define DPU_FORMAT_IS_YUV(X) \ + (test_bit(DPU_FORMAT_FLAG_YUV_BIT, (X)->flag)) +#define DPU_FORMAT_IS_DX(X) \ + (test_bit(DPU_FORMAT_FLAG_DX_BIT, (X)->flag)) +#define DPU_FORMAT_IS_LINEAR(X) ((X)->fetch_mode == DPU_FETCH_LINEAR) +#define DPU_FORMAT_IS_TILE(X) \ + (((X)->fetch_mode == DPU_FETCH_UBWC) && \ + !test_bit(DPU_FORMAT_FLAG_COMPRESSED_BIT, (X)->flag)) +#define DPU_FORMAT_IS_UBWC(X) \ + (((X)->fetch_mode == DPU_FETCH_UBWC) && \ + test_bit(DPU_FORMAT_FLAG_COMPRESSED_BIT, (X)->flag)) + +#define DPU_BLEND_FG_ALPHA_FG_CONST (0 << 0) +#define DPU_BLEND_FG_ALPHA_BG_CONST (1 << 0) +#define DPU_BLEND_FG_ALPHA_FG_PIXEL (2 << 0) +#define DPU_BLEND_FG_ALPHA_BG_PIXEL (3 << 0) +#define DPU_BLEND_FG_INV_ALPHA (1 << 2) +#define DPU_BLEND_FG_MOD_ALPHA (1 << 3) +#define DPU_BLEND_FG_INV_MOD_ALPHA (1 << 4) +#define DPU_BLEND_FG_TRANSP_EN (1 << 5) +#define DPU_BLEND_BG_ALPHA_FG_CONST (0 << 8) +#define DPU_BLEND_BG_ALPHA_BG_CONST (1 << 8) +#define DPU_BLEND_BG_ALPHA_FG_PIXEL (2 << 8) +#define DPU_BLEND_BG_ALPHA_BG_PIXEL (3 << 8) +#define DPU_BLEND_BG_INV_ALPHA (1 << 10) +#define DPU_BLEND_BG_MOD_ALPHA (1 << 11) +#define DPU_BLEND_BG_INV_MOD_ALPHA (1 << 12) +#define DPU_BLEND_BG_TRANSP_EN (1 << 13) + +#define DPU_VSYNC0_SOURCE_GPIO 0 +#define DPU_VSYNC1_SOURCE_GPIO 1 +#define DPU_VSYNC2_SOURCE_GPIO 2 +#define DPU_VSYNC_SOURCE_INTF_0 3 +#define DPU_VSYNC_SOURCE_INTF_1 4 +#define DPU_VSYNC_SOURCE_INTF_2 5 +#define DPU_VSYNC_SOURCE_INTF_3 6 +#define DPU_VSYNC_SOURCE_WD_TIMER_4 11 +#define DPU_VSYNC_SOURCE_WD_TIMER_3 12 +#define DPU_VSYNC_SOURCE_WD_TIMER_2 13 +#define DPU_VSYNC_SOURCE_WD_TIMER_1 14 +#define DPU_VSYNC_SOURCE_WD_TIMER_0 15 + +enum dpu_hw_blk_type { + DPU_HW_BLK_TOP = 0, + DPU_HW_BLK_SSPP, + DPU_HW_BLK_LM, + DPU_HW_BLK_CTL, + DPU_HW_BLK_PINGPONG, + DPU_HW_BLK_INTF, + DPU_HW_BLK_WB, + DPU_HW_BLK_DSPP, + DPU_HW_BLK_MAX, +}; + +enum dpu_mdp { + MDP_TOP = 0x1, + MDP_MAX, +}; + +enum dpu_sspp { + SSPP_NONE, + SSPP_VIG0, + SSPP_VIG1, + SSPP_VIG2, + SSPP_VIG3, + SSPP_RGB0, + SSPP_RGB1, + SSPP_RGB2, + SSPP_RGB3, + SSPP_DMA0, + SSPP_DMA1, + SSPP_DMA2, + SSPP_DMA3, + SSPP_CURSOR0, + SSPP_CURSOR1, + SSPP_MAX +}; + +enum dpu_sspp_type { + SSPP_TYPE_VIG, + SSPP_TYPE_RGB, + SSPP_TYPE_DMA, + SSPP_TYPE_CURSOR, + SSPP_TYPE_MAX +}; + +enum dpu_lm { + LM_0 = 1, + LM_1, + LM_2, + LM_3, + LM_4, + LM_5, + LM_6, + LM_MAX +}; + +enum dpu_stage { + DPU_STAGE_BASE = 0, + DPU_STAGE_0, + DPU_STAGE_1, + DPU_STAGE_2, + DPU_STAGE_3, + DPU_STAGE_4, + DPU_STAGE_5, + DPU_STAGE_6, + DPU_STAGE_7, + DPU_STAGE_8, + DPU_STAGE_9, + DPU_STAGE_10, + DPU_STAGE_MAX +}; +enum dpu_dspp { + DSPP_0 = 1, + DSPP_1, + DSPP_2, + DSPP_3, + DSPP_MAX +}; + +enum dpu_ctl { + CTL_0 = 1, + CTL_1, + CTL_2, + CTL_3, + CTL_4, + CTL_5, + CTL_MAX +}; + +enum dpu_pingpong { + PINGPONG_0 = 1, + PINGPONG_1, + PINGPONG_2, + PINGPONG_3, + PINGPONG_4, + PINGPONG_5, + PINGPONG_S0, + PINGPONG_MAX +}; + +enum dpu_intf { + INTF_0 = 1, + INTF_1, + INTF_2, + INTF_3, + INTF_4, + INTF_5, + INTF_6, + INTF_MAX +}; + +enum dpu_intf_type { + INTF_NONE = 0x0, + INTF_DSI = 0x1, + INTF_HDMI = 0x3, + INTF_LCDC = 0x5, + INTF_EDP = 0x9, + INTF_DP = 0xa, + INTF_TYPE_MAX, + + /* virtual interfaces */ + INTF_WB = 0x100, +}; + +enum dpu_intf_mode { + INTF_MODE_NONE = 0, + INTF_MODE_CMD, + INTF_MODE_VIDEO, + INTF_MODE_WB_BLOCK, + INTF_MODE_WB_LINE, + INTF_MODE_MAX +}; + +enum dpu_wb { + WB_0 = 1, + WB_1, + WB_2, + WB_3, + WB_MAX +}; + +enum dpu_cwb { + CWB_0 = 0x1, + CWB_1, + CWB_2, + CWB_3, + CWB_MAX +}; + +enum dpu_wd_timer { + WD_TIMER_0 = 0x1, + WD_TIMER_1, + WD_TIMER_2, + WD_TIMER_3, + WD_TIMER_4, + WD_TIMER_5, + WD_TIMER_MAX +}; + +enum dpu_vbif { + VBIF_0, + VBIF_1, + VBIF_MAX, + VBIF_RT = VBIF_0, + VBIF_NRT = VBIF_1 +}; + +/** + * DPU HW,Component order color map + */ +enum { + C0_G_Y = 0, + C1_B_Cb = 1, + C2_R_Cr = 2, + C3_ALPHA = 3 +}; + +/** + * enum dpu_plane_type - defines how the color component pixel packing + * @DPU_PLANE_INTERLEAVED : Color components in single plane + * @DPU_PLANE_PLANAR : Color component in separate planes + * @DPU_PLANE_PSEUDO_PLANAR : Chroma components interleaved in separate plane + */ +enum dpu_plane_type { + DPU_PLANE_INTERLEAVED, + DPU_PLANE_PLANAR, + DPU_PLANE_PSEUDO_PLANAR, +}; + +/** + * enum dpu_chroma_samp_type - chroma sub-samplng type + * @DPU_CHROMA_RGB : No chroma subsampling + * @DPU_CHROMA_H2V1 : Chroma pixels are horizontally subsampled + * @DPU_CHROMA_H1V2 : Chroma pixels are vertically subsampled + * @DPU_CHROMA_420 : 420 subsampling + */ +enum dpu_chroma_samp_type { + DPU_CHROMA_RGB, + DPU_CHROMA_H2V1, + DPU_CHROMA_H1V2, + DPU_CHROMA_420 +}; + +/** + * dpu_fetch_type - Defines How DPU HW fetches data + * @DPU_FETCH_LINEAR : fetch is line by line + * @DPU_FETCH_TILE : fetches data in Z order from a tile + * @DPU_FETCH_UBWC : fetch and decompress data + */ +enum dpu_fetch_type { + DPU_FETCH_LINEAR, + DPU_FETCH_TILE, + DPU_FETCH_UBWC +}; + +/** + * Value of enum chosen to fit the number of bits + * expected by the HW programming. + */ +enum { + COLOR_ALPHA_1BIT = 0, + COLOR_ALPHA_4BIT = 1, + COLOR_4BIT = 0, + COLOR_5BIT = 1, /* No 5-bit Alpha */ + COLOR_6BIT = 2, /* 6-Bit Alpha also = 2 */ + COLOR_8BIT = 3, /* 8-Bit Alpha also = 3 */ +}; + +/** + * enum dpu_3d_blend_mode + * Desribes how the 3d data is blended + * @BLEND_3D_NONE : 3d blending not enabled + * @BLEND_3D_FRAME_INT : Frame interleaving + * @BLEND_3D_H_ROW_INT : Horizontal row interleaving + * @BLEND_3D_V_ROW_INT : vertical row interleaving + * @BLEND_3D_COL_INT : column interleaving + * @BLEND_3D_MAX : + */ +enum dpu_3d_blend_mode { + BLEND_3D_NONE = 0, + BLEND_3D_FRAME_INT, + BLEND_3D_H_ROW_INT, + BLEND_3D_V_ROW_INT, + BLEND_3D_COL_INT, + BLEND_3D_MAX +}; + +/** struct dpu_format - defines the format configuration which + * allows DPU HW to correctly fetch and decode the format + * @base: base msm_format struture containing fourcc code + * @fetch_planes: how the color components are packed in pixel format + * @element: element color ordering + * @bits: element bit widths + * @chroma_sample: chroma sub-samplng type + * @unpack_align_msb: unpack aligned, 0 to LSB, 1 to MSB + * @unpack_tight: 0 for loose, 1 for tight + * @unpack_count: 0 = 1 component, 1 = 2 component + * @bpp: bytes per pixel + * @alpha_enable: whether the format has an alpha channel + * @num_planes: number of planes (including meta data planes) + * @fetch_mode: linear, tiled, or ubwc hw fetch behavior + * @flag: usage bit flags + * @tile_width: format tile width + * @tile_height: format tile height + */ +struct dpu_format { + struct msm_format base; + enum dpu_plane_type fetch_planes; + u8 element[DPU_MAX_PLANES]; + u8 bits[DPU_MAX_PLANES]; + enum dpu_chroma_samp_type chroma_sample; + u8 unpack_align_msb; + u8 unpack_tight; + u8 unpack_count; + u8 bpp; + u8 alpha_enable; + u8 num_planes; + enum dpu_fetch_type fetch_mode; + DECLARE_BITMAP(flag, DPU_FORMAT_FLAG_BIT_MAX); + u16 tile_width; + u16 tile_height; +}; +#define to_dpu_format(x) container_of(x, struct dpu_format, base) + +/** + * struct dpu_hw_fmt_layout - format information of the source pixel data + * @format: pixel format parameters + * @num_planes: number of planes (including meta data planes) + * @width: image width + * @height: image height + * @total_size: total size in bytes + * @plane_addr: address of each plane + * @plane_size: length of each plane + * @plane_pitch: pitch of each plane + */ +struct dpu_hw_fmt_layout { + const struct dpu_format *format; + uint32_t num_planes; + uint32_t width; + uint32_t height; + uint32_t total_size; + uint32_t plane_addr[DPU_MAX_PLANES]; + uint32_t plane_size[DPU_MAX_PLANES]; + uint32_t plane_pitch[DPU_MAX_PLANES]; +}; + +struct dpu_csc_cfg { + /* matrix coefficients in S15.16 format */ + uint32_t csc_mv[DPU_CSC_MATRIX_COEFF_SIZE]; + uint32_t csc_pre_bv[DPU_CSC_BIAS_SIZE]; + uint32_t csc_post_bv[DPU_CSC_BIAS_SIZE]; + uint32_t csc_pre_lv[DPU_CSC_CLAMP_SIZE]; + uint32_t csc_post_lv[DPU_CSC_CLAMP_SIZE]; +}; + +/** + * struct dpu_mdss_color - mdss color description + * color 0 : green + * color 1 : blue + * color 2 : red + * color 3 : alpha + */ +struct dpu_mdss_color { + u32 color_0; + u32 color_1; + u32 color_2; + u32 color_3; +}; + +/* + * Define bit masks for h/w logging. + */ +#define DPU_DBG_MASK_NONE (1 << 0) +#define DPU_DBG_MASK_INTF (1 << 1) +#define DPU_DBG_MASK_LM (1 << 2) +#define DPU_DBG_MASK_CTL (1 << 3) +#define DPU_DBG_MASK_PINGPONG (1 << 4) +#define DPU_DBG_MASK_SSPP (1 << 5) +#define DPU_DBG_MASK_WB (1 << 6) +#define DPU_DBG_MASK_TOP (1 << 7) +#define DPU_DBG_MASK_VBIF (1 << 8) +#define DPU_DBG_MASK_ROT (1 << 9) +#define DPU_DBG_MASK_DSPP (1 << 10) + +#endif /* _DPU_HW_MDSS_H */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_pingpong.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_pingpong.c new file mode 100644 index 000000000..bea4ab5c5 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_pingpong.c @@ -0,0 +1,271 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#include <linux/iopoll.h> + +#include "dpu_hw_mdss.h" +#include "dpu_hwio.h" +#include "dpu_hw_catalog.h" +#include "dpu_hw_pingpong.h" +#include "dpu_kms.h" +#include "dpu_trace.h" + +#define PP_TEAR_CHECK_EN 0x000 +#define PP_SYNC_CONFIG_VSYNC 0x004 +#define PP_SYNC_CONFIG_HEIGHT 0x008 +#define PP_SYNC_WRCOUNT 0x00C +#define PP_VSYNC_INIT_VAL 0x010 +#define PP_INT_COUNT_VAL 0x014 +#define PP_SYNC_THRESH 0x018 +#define PP_START_POS 0x01C +#define PP_RD_PTR_IRQ 0x020 +#define PP_WR_PTR_IRQ 0x024 +#define PP_OUT_LINE_COUNT 0x028 +#define PP_LINE_COUNT 0x02C + +#define PP_FBC_MODE 0x034 +#define PP_FBC_BUDGET_CTL 0x038 +#define PP_FBC_LOSSY_MODE 0x03C + +#define PP_DITHER_EN 0x000 +#define PP_DITHER_BITDEPTH 0x004 +#define PP_DITHER_MATRIX 0x008 + +#define DITHER_DEPTH_MAP_INDEX 9 + +static u32 dither_depth_map[DITHER_DEPTH_MAP_INDEX] = { + 0, 0, 0, 0, 0, 0, 0, 1, 2 +}; + +static const struct dpu_pingpong_cfg *_pingpong_offset(enum dpu_pingpong pp, + const struct dpu_mdss_cfg *m, + void __iomem *addr, + struct dpu_hw_blk_reg_map *b) +{ + int i; + + for (i = 0; i < m->pingpong_count; i++) { + if (pp == m->pingpong[i].id) { + b->base_off = addr; + b->blk_off = m->pingpong[i].base; + b->length = m->pingpong[i].len; + b->hwversion = m->hwversion; + b->log_mask = DPU_DBG_MASK_PINGPONG; + return &m->pingpong[i]; + } + } + + return ERR_PTR(-EINVAL); +} + +static void dpu_hw_pp_setup_dither(struct dpu_hw_pingpong *pp, + struct dpu_hw_dither_cfg *cfg) +{ + struct dpu_hw_blk_reg_map *c; + u32 i, base, data = 0; + + c = &pp->hw; + base = pp->caps->sblk->dither.base; + if (!cfg) { + DPU_REG_WRITE(c, base + PP_DITHER_EN, 0); + return; + } + + data = dither_depth_map[cfg->c0_bitdepth] & REG_MASK(2); + data |= (dither_depth_map[cfg->c1_bitdepth] & REG_MASK(2)) << 2; + data |= (dither_depth_map[cfg->c2_bitdepth] & REG_MASK(2)) << 4; + data |= (dither_depth_map[cfg->c3_bitdepth] & REG_MASK(2)) << 6; + data |= (cfg->temporal_en) ? (1 << 8) : 0; + + DPU_REG_WRITE(c, base + PP_DITHER_BITDEPTH, data); + + for (i = 0; i < DITHER_MATRIX_SZ - 3; i += 4) { + data = (cfg->matrix[i] & REG_MASK(4)) | + ((cfg->matrix[i + 1] & REG_MASK(4)) << 4) | + ((cfg->matrix[i + 2] & REG_MASK(4)) << 8) | + ((cfg->matrix[i + 3] & REG_MASK(4)) << 12); + DPU_REG_WRITE(c, base + PP_DITHER_MATRIX + i, data); + } + DPU_REG_WRITE(c, base + PP_DITHER_EN, 1); +} + +static int dpu_hw_pp_setup_te_config(struct dpu_hw_pingpong *pp, + struct dpu_hw_tear_check *te) +{ + struct dpu_hw_blk_reg_map *c; + int cfg; + + if (!pp || !te) + return -EINVAL; + c = &pp->hw; + + cfg = BIT(19); /*VSYNC_COUNTER_EN */ + if (te->hw_vsync_mode) + cfg |= BIT(20); + + cfg |= te->vsync_count; + + DPU_REG_WRITE(c, PP_SYNC_CONFIG_VSYNC, cfg); + DPU_REG_WRITE(c, PP_SYNC_CONFIG_HEIGHT, te->sync_cfg_height); + DPU_REG_WRITE(c, PP_VSYNC_INIT_VAL, te->vsync_init_val); + DPU_REG_WRITE(c, PP_RD_PTR_IRQ, te->rd_ptr_irq); + DPU_REG_WRITE(c, PP_START_POS, te->start_pos); + DPU_REG_WRITE(c, PP_SYNC_THRESH, + ((te->sync_threshold_continue << 16) | + te->sync_threshold_start)); + DPU_REG_WRITE(c, PP_SYNC_WRCOUNT, + (te->start_pos + te->sync_threshold_start + 1)); + + return 0; +} + +static int dpu_hw_pp_poll_timeout_wr_ptr(struct dpu_hw_pingpong *pp, + u32 timeout_us) +{ + struct dpu_hw_blk_reg_map *c; + u32 val; + int rc; + + if (!pp) + return -EINVAL; + + c = &pp->hw; + rc = readl_poll_timeout(c->base_off + c->blk_off + PP_LINE_COUNT, + val, (val & 0xffff) >= 1, 10, timeout_us); + + return rc; +} + +static int dpu_hw_pp_enable_te(struct dpu_hw_pingpong *pp, bool enable) +{ + struct dpu_hw_blk_reg_map *c; + + if (!pp) + return -EINVAL; + c = &pp->hw; + + DPU_REG_WRITE(c, PP_TEAR_CHECK_EN, enable); + return 0; +} + +static int dpu_hw_pp_connect_external_te(struct dpu_hw_pingpong *pp, + bool enable_external_te) +{ + struct dpu_hw_blk_reg_map *c = &pp->hw; + u32 cfg; + int orig; + + if (!pp) + return -EINVAL; + + c = &pp->hw; + cfg = DPU_REG_READ(c, PP_SYNC_CONFIG_VSYNC); + orig = (bool)(cfg & BIT(20)); + if (enable_external_te) + cfg |= BIT(20); + else + cfg &= ~BIT(20); + DPU_REG_WRITE(c, PP_SYNC_CONFIG_VSYNC, cfg); + trace_dpu_pp_connect_ext_te(pp->idx - PINGPONG_0, cfg); + + return orig; +} + +static int dpu_hw_pp_get_vsync_info(struct dpu_hw_pingpong *pp, + struct dpu_hw_pp_vsync_info *info) +{ + struct dpu_hw_blk_reg_map *c; + u32 val; + + if (!pp || !info) + return -EINVAL; + c = &pp->hw; + + val = DPU_REG_READ(c, PP_VSYNC_INIT_VAL); + info->rd_ptr_init_val = val & 0xffff; + + val = DPU_REG_READ(c, PP_INT_COUNT_VAL); + info->rd_ptr_frame_count = (val & 0xffff0000) >> 16; + info->rd_ptr_line_count = val & 0xffff; + + val = DPU_REG_READ(c, PP_LINE_COUNT); + info->wr_ptr_line_count = val & 0xffff; + + return 0; +} + +static u32 dpu_hw_pp_get_line_count(struct dpu_hw_pingpong *pp) +{ + struct dpu_hw_blk_reg_map *c = &pp->hw; + u32 height, init; + u32 line = 0xFFFF; + + if (!pp) + return 0; + c = &pp->hw; + + init = DPU_REG_READ(c, PP_VSYNC_INIT_VAL) & 0xFFFF; + height = DPU_REG_READ(c, PP_SYNC_CONFIG_HEIGHT) & 0xFFFF; + + if (height < init) + return line; + + line = DPU_REG_READ(c, PP_INT_COUNT_VAL) & 0xFFFF; + + if (line < init) + line += (0xFFFF - init); + else + line -= init; + + return line; +} + +static void _setup_pingpong_ops(struct dpu_hw_pingpong *c, + unsigned long features) +{ + c->ops.setup_tearcheck = dpu_hw_pp_setup_te_config; + c->ops.enable_tearcheck = dpu_hw_pp_enable_te; + c->ops.connect_external_te = dpu_hw_pp_connect_external_te; + c->ops.get_vsync_info = dpu_hw_pp_get_vsync_info; + c->ops.poll_timeout_wr_ptr = dpu_hw_pp_poll_timeout_wr_ptr; + c->ops.get_line_count = dpu_hw_pp_get_line_count; + + if (test_bit(DPU_PINGPONG_DITHER, &features)) + c->ops.setup_dither = dpu_hw_pp_setup_dither; +}; + +static struct dpu_hw_blk_ops dpu_hw_ops; + +struct dpu_hw_pingpong *dpu_hw_pingpong_init(enum dpu_pingpong idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m) +{ + struct dpu_hw_pingpong *c; + const struct dpu_pingpong_cfg *cfg; + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return ERR_PTR(-ENOMEM); + + cfg = _pingpong_offset(idx, m, addr, &c->hw); + if (IS_ERR_OR_NULL(cfg)) { + kfree(c); + return ERR_PTR(-EINVAL); + } + + c->idx = idx; + c->caps = cfg; + _setup_pingpong_ops(c, c->caps->features); + + dpu_hw_blk_init(&c->base, DPU_HW_BLK_PINGPONG, idx, &dpu_hw_ops); + + return c; +} + +void dpu_hw_pingpong_destroy(struct dpu_hw_pingpong *pp) +{ + if (pp) + dpu_hw_blk_destroy(&pp->base); + kfree(pp); +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_pingpong.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_pingpong.h new file mode 100644 index 000000000..065996b3e --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_pingpong.h @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_PINGPONG_H +#define _DPU_HW_PINGPONG_H + +#include "dpu_hw_catalog.h" +#include "dpu_hw_mdss.h" +#include "dpu_hw_util.h" +#include "dpu_hw_blk.h" + +#define DITHER_MATRIX_SZ 16 + +struct dpu_hw_pingpong; + +struct dpu_hw_tear_check { + /* + * This is ratio of MDP VSYNC clk freq(Hz) to + * refresh rate divided by no of lines + */ + u32 vsync_count; + u32 sync_cfg_height; + u32 vsync_init_val; + u32 sync_threshold_start; + u32 sync_threshold_continue; + u32 start_pos; + u32 rd_ptr_irq; + u8 hw_vsync_mode; +}; + +struct dpu_hw_pp_vsync_info { + u32 rd_ptr_init_val; /* value of rd pointer at vsync edge */ + u32 rd_ptr_frame_count; /* num frames sent since enabling interface */ + u32 rd_ptr_line_count; /* current line on panel (rd ptr) */ + u32 wr_ptr_line_count; /* current line within pp fifo (wr ptr) */ +}; + +/** + * struct dpu_hw_dither_cfg - dither feature structure + * @flags: for customizing operations + * @temporal_en: temperal dither enable + * @c0_bitdepth: c0 component bit depth + * @c1_bitdepth: c1 component bit depth + * @c2_bitdepth: c2 component bit depth + * @c3_bitdepth: c2 component bit depth + * @matrix: dither strength matrix + */ +struct dpu_hw_dither_cfg { + u64 flags; + u32 temporal_en; + u32 c0_bitdepth; + u32 c1_bitdepth; + u32 c2_bitdepth; + u32 c3_bitdepth; + u32 matrix[DITHER_MATRIX_SZ]; +}; + +/** + * + * struct dpu_hw_pingpong_ops : Interface to the pingpong Hw driver functions + * Assumption is these functions will be called after clocks are enabled + * @setup_tearcheck : program tear check values + * @enable_tearcheck : enables tear check + * @get_vsync_info : retries timing info of the panel + * @setup_dither : function to program the dither hw block + * @get_line_count: obtain current vertical line counter + */ +struct dpu_hw_pingpong_ops { + /** + * enables vysnc generation and sets up init value of + * read pointer and programs the tear check cofiguration + */ + int (*setup_tearcheck)(struct dpu_hw_pingpong *pp, + struct dpu_hw_tear_check *cfg); + + /** + * enables tear check block + */ + int (*enable_tearcheck)(struct dpu_hw_pingpong *pp, + bool enable); + + /** + * read, modify, write to either set or clear listening to external TE + * @Return: 1 if TE was originally connected, 0 if not, or -ERROR + */ + int (*connect_external_te)(struct dpu_hw_pingpong *pp, + bool enable_external_te); + + /** + * provides the programmed and current + * line_count + */ + int (*get_vsync_info)(struct dpu_hw_pingpong *pp, + struct dpu_hw_pp_vsync_info *info); + + /** + * poll until write pointer transmission starts + * @Return: 0 on success, -ETIMEDOUT on timeout + */ + int (*poll_timeout_wr_ptr)(struct dpu_hw_pingpong *pp, u32 timeout_us); + + /** + * Obtain current vertical line counter + */ + u32 (*get_line_count)(struct dpu_hw_pingpong *pp); + + /** + * Setup dither matix for pingpong block + */ + void (*setup_dither)(struct dpu_hw_pingpong *pp, + struct dpu_hw_dither_cfg *cfg); +}; + +struct dpu_hw_pingpong { + struct dpu_hw_blk base; + struct dpu_hw_blk_reg_map hw; + + /* pingpong */ + enum dpu_pingpong idx; + const struct dpu_pingpong_cfg *caps; + + /* ops */ + struct dpu_hw_pingpong_ops ops; +}; + +/** + * to_dpu_hw_pingpong - convert base object dpu_hw_base to container + * @hw: Pointer to base hardware block + * return: Pointer to hardware block container + */ +static inline struct dpu_hw_pingpong *to_dpu_hw_pingpong(struct dpu_hw_blk *hw) +{ + return container_of(hw, struct dpu_hw_pingpong, base); +} + +/** + * dpu_hw_pingpong_init - initializes the pingpong driver for the passed + * pingpong idx. + * @idx: Pingpong index for which driver object is required + * @addr: Mapped register io address of MDP + * @m: Pointer to mdss catalog data + * Returns: Error code or allocated dpu_hw_pingpong context + */ +struct dpu_hw_pingpong *dpu_hw_pingpong_init(enum dpu_pingpong idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m); + +/** + * dpu_hw_pingpong_destroy - destroys pingpong driver context + * should be called to free the context + * @pp: Pointer to PP driver context returned by dpu_hw_pingpong_init + */ +void dpu_hw_pingpong_destroy(struct dpu_hw_pingpong *pp); + +#endif /*_DPU_HW_PINGPONG_H */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c new file mode 100644 index 000000000..016c462bd --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.c @@ -0,0 +1,750 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#include "dpu_hwio.h" +#include "dpu_hw_catalog.h" +#include "dpu_hw_lm.h" +#include "dpu_hw_sspp.h" +#include "dpu_kms.h" + +#define DPU_FETCH_CONFIG_RESET_VALUE 0x00000087 + +/* DPU_SSPP_SRC */ +#define SSPP_SRC_SIZE 0x00 +#define SSPP_SRC_XY 0x08 +#define SSPP_OUT_SIZE 0x0c +#define SSPP_OUT_XY 0x10 +#define SSPP_SRC0_ADDR 0x14 +#define SSPP_SRC1_ADDR 0x18 +#define SSPP_SRC2_ADDR 0x1C +#define SSPP_SRC3_ADDR 0x20 +#define SSPP_SRC_YSTRIDE0 0x24 +#define SSPP_SRC_YSTRIDE1 0x28 +#define SSPP_SRC_FORMAT 0x30 +#define SSPP_SRC_UNPACK_PATTERN 0x34 +#define SSPP_SRC_OP_MODE 0x38 + +/* SSPP_MULTIRECT*/ +#define SSPP_SRC_SIZE_REC1 0x16C +#define SSPP_SRC_XY_REC1 0x168 +#define SSPP_OUT_SIZE_REC1 0x160 +#define SSPP_OUT_XY_REC1 0x164 +#define SSPP_SRC_FORMAT_REC1 0x174 +#define SSPP_SRC_UNPACK_PATTERN_REC1 0x178 +#define SSPP_SRC_OP_MODE_REC1 0x17C +#define SSPP_MULTIRECT_OPMODE 0x170 +#define SSPP_SRC_CONSTANT_COLOR_REC1 0x180 +#define SSPP_EXCL_REC_SIZE_REC1 0x184 +#define SSPP_EXCL_REC_XY_REC1 0x188 + +#define MDSS_MDP_OP_DEINTERLACE BIT(22) +#define MDSS_MDP_OP_DEINTERLACE_ODD BIT(23) +#define MDSS_MDP_OP_IGC_ROM_1 BIT(18) +#define MDSS_MDP_OP_IGC_ROM_0 BIT(17) +#define MDSS_MDP_OP_IGC_EN BIT(16) +#define MDSS_MDP_OP_FLIP_UD BIT(14) +#define MDSS_MDP_OP_FLIP_LR BIT(13) +#define MDSS_MDP_OP_BWC_EN BIT(0) +#define MDSS_MDP_OP_PE_OVERRIDE BIT(31) +#define MDSS_MDP_OP_BWC_LOSSLESS (0 << 1) +#define MDSS_MDP_OP_BWC_Q_HIGH (1 << 1) +#define MDSS_MDP_OP_BWC_Q_MED (2 << 1) + +#define SSPP_SRC_CONSTANT_COLOR 0x3c +#define SSPP_EXCL_REC_CTL 0x40 +#define SSPP_UBWC_STATIC_CTRL 0x44 +#define SSPP_FETCH_CONFIG 0x048 +#define SSPP_DANGER_LUT 0x60 +#define SSPP_SAFE_LUT 0x64 +#define SSPP_CREQ_LUT 0x68 +#define SSPP_QOS_CTRL 0x6C +#define SSPP_DECIMATION_CONFIG 0xB4 +#define SSPP_SRC_ADDR_SW_STATUS 0x70 +#define SSPP_CREQ_LUT_0 0x74 +#define SSPP_CREQ_LUT_1 0x78 +#define SSPP_SW_PIX_EXT_C0_LR 0x100 +#define SSPP_SW_PIX_EXT_C0_TB 0x104 +#define SSPP_SW_PIX_EXT_C0_REQ_PIXELS 0x108 +#define SSPP_SW_PIX_EXT_C1C2_LR 0x110 +#define SSPP_SW_PIX_EXT_C1C2_TB 0x114 +#define SSPP_SW_PIX_EXT_C1C2_REQ_PIXELS 0x118 +#define SSPP_SW_PIX_EXT_C3_LR 0x120 +#define SSPP_SW_PIX_EXT_C3_TB 0x124 +#define SSPP_SW_PIX_EXT_C3_REQ_PIXELS 0x128 +#define SSPP_TRAFFIC_SHAPER 0x130 +#define SSPP_CDP_CNTL 0x134 +#define SSPP_UBWC_ERROR_STATUS 0x138 +#define SSPP_TRAFFIC_SHAPER_PREFILL 0x150 +#define SSPP_TRAFFIC_SHAPER_REC1_PREFILL 0x154 +#define SSPP_TRAFFIC_SHAPER_REC1 0x158 +#define SSPP_EXCL_REC_SIZE 0x1B4 +#define SSPP_EXCL_REC_XY 0x1B8 +#define SSPP_VIG_OP_MODE 0x0 +#define SSPP_VIG_CSC_10_OP_MODE 0x0 +#define SSPP_TRAFFIC_SHAPER_BPC_MAX 0xFF + +/* SSPP_QOS_CTRL */ +#define SSPP_QOS_CTRL_VBLANK_EN BIT(16) +#define SSPP_QOS_CTRL_DANGER_SAFE_EN BIT(0) +#define SSPP_QOS_CTRL_DANGER_VBLANK_MASK 0x3 +#define SSPP_QOS_CTRL_DANGER_VBLANK_OFF 4 +#define SSPP_QOS_CTRL_CREQ_VBLANK_MASK 0x3 +#define SSPP_QOS_CTRL_CREQ_VBLANK_OFF 20 + +/* DPU_SSPP_SCALER_QSEED2 */ +#define SCALE_CONFIG 0x04 +#define COMP0_3_PHASE_STEP_X 0x10 +#define COMP0_3_PHASE_STEP_Y 0x14 +#define COMP1_2_PHASE_STEP_X 0x18 +#define COMP1_2_PHASE_STEP_Y 0x1c +#define COMP0_3_INIT_PHASE_X 0x20 +#define COMP0_3_INIT_PHASE_Y 0x24 +#define COMP1_2_INIT_PHASE_X 0x28 +#define COMP1_2_INIT_PHASE_Y 0x2C +#define VIG_0_QSEED2_SHARP 0x30 + +/* + * Definitions for ViG op modes + */ +#define VIG_OP_CSC_DST_DATAFMT BIT(19) +#define VIG_OP_CSC_SRC_DATAFMT BIT(18) +#define VIG_OP_CSC_EN BIT(17) +#define VIG_OP_MEM_PROT_CONT BIT(15) +#define VIG_OP_MEM_PROT_VAL BIT(14) +#define VIG_OP_MEM_PROT_SAT BIT(13) +#define VIG_OP_MEM_PROT_HUE BIT(12) +#define VIG_OP_HIST BIT(8) +#define VIG_OP_SKY_COL BIT(7) +#define VIG_OP_FOIL BIT(6) +#define VIG_OP_SKIN_COL BIT(5) +#define VIG_OP_PA_EN BIT(4) +#define VIG_OP_PA_SAT_ZERO_EXP BIT(2) +#define VIG_OP_MEM_PROT_BLEND BIT(1) + +/* + * Definitions for CSC 10 op modes + */ +#define VIG_CSC_10_SRC_DATAFMT BIT(1) +#define VIG_CSC_10_EN BIT(0) +#define CSC_10BIT_OFFSET 4 + +/* traffic shaper clock in Hz */ +#define TS_CLK 19200000 + + +static int _sspp_subblk_offset(struct dpu_hw_pipe *ctx, + int s_id, + u32 *idx) +{ + int rc = 0; + const struct dpu_sspp_sub_blks *sblk; + + if (!ctx || !ctx->cap || !ctx->cap->sblk) + return -EINVAL; + + sblk = ctx->cap->sblk; + + switch (s_id) { + case DPU_SSPP_SRC: + *idx = sblk->src_blk.base; + break; + case DPU_SSPP_SCALER_QSEED2: + case DPU_SSPP_SCALER_QSEED3: + case DPU_SSPP_SCALER_RGB: + *idx = sblk->scaler_blk.base; + break; + case DPU_SSPP_CSC: + case DPU_SSPP_CSC_10BIT: + *idx = sblk->csc_blk.base; + break; + default: + rc = -EINVAL; + } + + return rc; +} + +static void dpu_hw_sspp_setup_multirect(struct dpu_hw_pipe *ctx, + enum dpu_sspp_multirect_index index, + enum dpu_sspp_multirect_mode mode) +{ + u32 mode_mask; + u32 idx; + + if (_sspp_subblk_offset(ctx, DPU_SSPP_SRC, &idx)) + return; + + if (index == DPU_SSPP_RECT_SOLO) { + /** + * if rect index is RECT_SOLO, we cannot expect a + * virtual plane sharing the same SSPP id. So we go + * and disable multirect + */ + mode_mask = 0; + } else { + mode_mask = DPU_REG_READ(&ctx->hw, SSPP_MULTIRECT_OPMODE + idx); + mode_mask |= index; + if (mode == DPU_SSPP_MULTIRECT_TIME_MX) + mode_mask |= BIT(2); + else + mode_mask &= ~BIT(2); + } + + DPU_REG_WRITE(&ctx->hw, SSPP_MULTIRECT_OPMODE + idx, mode_mask); +} + +static void _sspp_setup_opmode(struct dpu_hw_pipe *ctx, + u32 mask, u8 en) +{ + u32 idx; + u32 opmode; + + if (!test_bit(DPU_SSPP_SCALER_QSEED2, &ctx->cap->features) || + _sspp_subblk_offset(ctx, DPU_SSPP_SCALER_QSEED2, &idx) || + !test_bit(DPU_SSPP_CSC, &ctx->cap->features)) + return; + + opmode = DPU_REG_READ(&ctx->hw, SSPP_VIG_OP_MODE + idx); + + if (en) + opmode |= mask; + else + opmode &= ~mask; + + DPU_REG_WRITE(&ctx->hw, SSPP_VIG_OP_MODE + idx, opmode); +} + +static void _sspp_setup_csc10_opmode(struct dpu_hw_pipe *ctx, + u32 mask, u8 en) +{ + u32 idx; + u32 opmode; + + if (_sspp_subblk_offset(ctx, DPU_SSPP_CSC_10BIT, &idx)) + return; + + opmode = DPU_REG_READ(&ctx->hw, SSPP_VIG_CSC_10_OP_MODE + idx); + if (en) + opmode |= mask; + else + opmode &= ~mask; + + DPU_REG_WRITE(&ctx->hw, SSPP_VIG_CSC_10_OP_MODE + idx, opmode); +} + +/** + * Setup source pixel format, flip, + */ +static void dpu_hw_sspp_setup_format(struct dpu_hw_pipe *ctx, + const struct dpu_format *fmt, u32 flags, + enum dpu_sspp_multirect_index rect_mode) +{ + struct dpu_hw_blk_reg_map *c; + u32 chroma_samp, unpack, src_format; + u32 opmode = 0; + u32 fast_clear = 0; + u32 op_mode_off, unpack_pat_off, format_off; + u32 idx; + + if (_sspp_subblk_offset(ctx, DPU_SSPP_SRC, &idx) || !fmt) + return; + + if (rect_mode == DPU_SSPP_RECT_SOLO || rect_mode == DPU_SSPP_RECT_0) { + op_mode_off = SSPP_SRC_OP_MODE; + unpack_pat_off = SSPP_SRC_UNPACK_PATTERN; + format_off = SSPP_SRC_FORMAT; + } else { + op_mode_off = SSPP_SRC_OP_MODE_REC1; + unpack_pat_off = SSPP_SRC_UNPACK_PATTERN_REC1; + format_off = SSPP_SRC_FORMAT_REC1; + } + + c = &ctx->hw; + opmode = DPU_REG_READ(c, op_mode_off + idx); + opmode &= ~(MDSS_MDP_OP_FLIP_LR | MDSS_MDP_OP_FLIP_UD | + MDSS_MDP_OP_BWC_EN | MDSS_MDP_OP_PE_OVERRIDE); + + if (flags & DPU_SSPP_FLIP_LR) + opmode |= MDSS_MDP_OP_FLIP_LR; + if (flags & DPU_SSPP_FLIP_UD) + opmode |= MDSS_MDP_OP_FLIP_UD; + + chroma_samp = fmt->chroma_sample; + if (flags & DPU_SSPP_SOURCE_ROTATED_90) { + if (chroma_samp == DPU_CHROMA_H2V1) + chroma_samp = DPU_CHROMA_H1V2; + else if (chroma_samp == DPU_CHROMA_H1V2) + chroma_samp = DPU_CHROMA_H2V1; + } + + src_format = (chroma_samp << 23) | (fmt->fetch_planes << 19) | + (fmt->bits[C3_ALPHA] << 6) | (fmt->bits[C2_R_Cr] << 4) | + (fmt->bits[C1_B_Cb] << 2) | (fmt->bits[C0_G_Y] << 0); + + if (flags & DPU_SSPP_ROT_90) + src_format |= BIT(11); /* ROT90 */ + + if (fmt->alpha_enable && fmt->fetch_planes == DPU_PLANE_INTERLEAVED) + src_format |= BIT(8); /* SRCC3_EN */ + + if (flags & DPU_SSPP_SOLID_FILL) + src_format |= BIT(22); + + unpack = (fmt->element[3] << 24) | (fmt->element[2] << 16) | + (fmt->element[1] << 8) | (fmt->element[0] << 0); + src_format |= ((fmt->unpack_count - 1) << 12) | + (fmt->unpack_tight << 17) | + (fmt->unpack_align_msb << 18) | + ((fmt->bpp - 1) << 9); + + if (fmt->fetch_mode != DPU_FETCH_LINEAR) { + if (DPU_FORMAT_IS_UBWC(fmt)) + opmode |= MDSS_MDP_OP_BWC_EN; + src_format |= (fmt->fetch_mode & 3) << 30; /*FRAME_FORMAT */ + DPU_REG_WRITE(c, SSPP_FETCH_CONFIG, + DPU_FETCH_CONFIG_RESET_VALUE | + ctx->mdp->highest_bank_bit << 18); + switch (ctx->catalog->caps->ubwc_version) { + case DPU_HW_UBWC_VER_10: + /* TODO: UBWC v1 case */ + break; + case DPU_HW_UBWC_VER_20: + fast_clear = fmt->alpha_enable ? BIT(31) : 0; + DPU_REG_WRITE(c, SSPP_UBWC_STATIC_CTRL, + fast_clear | (ctx->mdp->ubwc_swizzle) | + (ctx->mdp->highest_bank_bit << 4)); + break; + case DPU_HW_UBWC_VER_30: + DPU_REG_WRITE(c, SSPP_UBWC_STATIC_CTRL, + BIT(30) | (ctx->mdp->ubwc_swizzle) | + (ctx->mdp->highest_bank_bit << 4)); + break; + case DPU_HW_UBWC_VER_40: + DPU_REG_WRITE(c, SSPP_UBWC_STATIC_CTRL, + DPU_FORMAT_IS_YUV(fmt) ? 0 : BIT(30)); + break; + } + } + + opmode |= MDSS_MDP_OP_PE_OVERRIDE; + + /* if this is YUV pixel format, enable CSC */ + if (DPU_FORMAT_IS_YUV(fmt)) + src_format |= BIT(15); + + if (DPU_FORMAT_IS_DX(fmt)) + src_format |= BIT(14); + + /* update scaler opmode, if appropriate */ + if (test_bit(DPU_SSPP_CSC, &ctx->cap->features)) + _sspp_setup_opmode(ctx, VIG_OP_CSC_EN | VIG_OP_CSC_SRC_DATAFMT, + DPU_FORMAT_IS_YUV(fmt)); + else if (test_bit(DPU_SSPP_CSC_10BIT, &ctx->cap->features)) + _sspp_setup_csc10_opmode(ctx, + VIG_CSC_10_EN | VIG_CSC_10_SRC_DATAFMT, + DPU_FORMAT_IS_YUV(fmt)); + + DPU_REG_WRITE(c, format_off + idx, src_format); + DPU_REG_WRITE(c, unpack_pat_off + idx, unpack); + DPU_REG_WRITE(c, op_mode_off + idx, opmode); + + /* clear previous UBWC error */ + DPU_REG_WRITE(c, SSPP_UBWC_ERROR_STATUS + idx, BIT(31)); +} + +static void dpu_hw_sspp_setup_pe_config(struct dpu_hw_pipe *ctx, + struct dpu_hw_pixel_ext *pe_ext) +{ + struct dpu_hw_blk_reg_map *c; + u8 color; + u32 lr_pe[4], tb_pe[4], tot_req_pixels[4]; + const u32 bytemask = 0xff; + const u32 shortmask = 0xffff; + u32 idx; + + if (_sspp_subblk_offset(ctx, DPU_SSPP_SRC, &idx) || !pe_ext) + return; + + c = &ctx->hw; + + /* program SW pixel extension override for all pipes*/ + for (color = 0; color < DPU_MAX_PLANES; color++) { + /* color 2 has the same set of registers as color 1 */ + if (color == 2) + continue; + + lr_pe[color] = ((pe_ext->right_ftch[color] & bytemask) << 24)| + ((pe_ext->right_rpt[color] & bytemask) << 16)| + ((pe_ext->left_ftch[color] & bytemask) << 8)| + (pe_ext->left_rpt[color] & bytemask); + + tb_pe[color] = ((pe_ext->btm_ftch[color] & bytemask) << 24)| + ((pe_ext->btm_rpt[color] & bytemask) << 16)| + ((pe_ext->top_ftch[color] & bytemask) << 8)| + (pe_ext->top_rpt[color] & bytemask); + + tot_req_pixels[color] = (((pe_ext->roi_h[color] + + pe_ext->num_ext_pxls_top[color] + + pe_ext->num_ext_pxls_btm[color]) & shortmask) << 16) | + ((pe_ext->roi_w[color] + + pe_ext->num_ext_pxls_left[color] + + pe_ext->num_ext_pxls_right[color]) & shortmask); + } + + /* color 0 */ + DPU_REG_WRITE(c, SSPP_SW_PIX_EXT_C0_LR + idx, lr_pe[0]); + DPU_REG_WRITE(c, SSPP_SW_PIX_EXT_C0_TB + idx, tb_pe[0]); + DPU_REG_WRITE(c, SSPP_SW_PIX_EXT_C0_REQ_PIXELS + idx, + tot_req_pixels[0]); + + /* color 1 and color 2 */ + DPU_REG_WRITE(c, SSPP_SW_PIX_EXT_C1C2_LR + idx, lr_pe[1]); + DPU_REG_WRITE(c, SSPP_SW_PIX_EXT_C1C2_TB + idx, tb_pe[1]); + DPU_REG_WRITE(c, SSPP_SW_PIX_EXT_C1C2_REQ_PIXELS + idx, + tot_req_pixels[1]); + + /* color 3 */ + DPU_REG_WRITE(c, SSPP_SW_PIX_EXT_C3_LR + idx, lr_pe[3]); + DPU_REG_WRITE(c, SSPP_SW_PIX_EXT_C3_TB + idx, lr_pe[3]); + DPU_REG_WRITE(c, SSPP_SW_PIX_EXT_C3_REQ_PIXELS + idx, + tot_req_pixels[3]); +} + +static void _dpu_hw_sspp_setup_scaler3(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_cfg *sspp, + struct dpu_hw_pixel_ext *pe, + void *scaler_cfg) +{ + u32 idx; + struct dpu_hw_scaler3_cfg *scaler3_cfg = scaler_cfg; + + (void)pe; + if (_sspp_subblk_offset(ctx, DPU_SSPP_SCALER_QSEED3, &idx) || !sspp + || !scaler3_cfg) + return; + + dpu_hw_setup_scaler3(&ctx->hw, scaler3_cfg, idx, + ctx->cap->sblk->scaler_blk.version, + sspp->layout.format); +} + +static u32 _dpu_hw_sspp_get_scaler3_ver(struct dpu_hw_pipe *ctx) +{ + u32 idx; + + if (!ctx || _sspp_subblk_offset(ctx, DPU_SSPP_SCALER_QSEED3, &idx)) + return 0; + + return dpu_hw_get_scaler3_ver(&ctx->hw, idx); +} + +/** + * dpu_hw_sspp_setup_rects() + */ +static void dpu_hw_sspp_setup_rects(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_cfg *cfg, + enum dpu_sspp_multirect_index rect_index) +{ + struct dpu_hw_blk_reg_map *c; + u32 src_size, src_xy, dst_size, dst_xy, ystride0, ystride1; + u32 src_size_off, src_xy_off, out_size_off, out_xy_off; + u32 idx; + + if (_sspp_subblk_offset(ctx, DPU_SSPP_SRC, &idx) || !cfg) + return; + + c = &ctx->hw; + + if (rect_index == DPU_SSPP_RECT_SOLO || rect_index == DPU_SSPP_RECT_0) { + src_size_off = SSPP_SRC_SIZE; + src_xy_off = SSPP_SRC_XY; + out_size_off = SSPP_OUT_SIZE; + out_xy_off = SSPP_OUT_XY; + } else { + src_size_off = SSPP_SRC_SIZE_REC1; + src_xy_off = SSPP_SRC_XY_REC1; + out_size_off = SSPP_OUT_SIZE_REC1; + out_xy_off = SSPP_OUT_XY_REC1; + } + + + /* src and dest rect programming */ + src_xy = (cfg->src_rect.y1 << 16) | cfg->src_rect.x1; + src_size = (drm_rect_height(&cfg->src_rect) << 16) | + drm_rect_width(&cfg->src_rect); + dst_xy = (cfg->dst_rect.y1 << 16) | cfg->dst_rect.x1; + dst_size = (drm_rect_height(&cfg->dst_rect) << 16) | + drm_rect_width(&cfg->dst_rect); + + if (rect_index == DPU_SSPP_RECT_SOLO) { + ystride0 = (cfg->layout.plane_pitch[0]) | + (cfg->layout.plane_pitch[1] << 16); + ystride1 = (cfg->layout.plane_pitch[2]) | + (cfg->layout.plane_pitch[3] << 16); + } else { + ystride0 = DPU_REG_READ(c, SSPP_SRC_YSTRIDE0 + idx); + ystride1 = DPU_REG_READ(c, SSPP_SRC_YSTRIDE1 + idx); + + if (rect_index == DPU_SSPP_RECT_0) { + ystride0 = (ystride0 & 0xFFFF0000) | + (cfg->layout.plane_pitch[0] & 0x0000FFFF); + ystride1 = (ystride1 & 0xFFFF0000)| + (cfg->layout.plane_pitch[2] & 0x0000FFFF); + } else { + ystride0 = (ystride0 & 0x0000FFFF) | + ((cfg->layout.plane_pitch[0] << 16) & + 0xFFFF0000); + ystride1 = (ystride1 & 0x0000FFFF) | + ((cfg->layout.plane_pitch[2] << 16) & + 0xFFFF0000); + } + } + + /* rectangle register programming */ + DPU_REG_WRITE(c, src_size_off + idx, src_size); + DPU_REG_WRITE(c, src_xy_off + idx, src_xy); + DPU_REG_WRITE(c, out_size_off + idx, dst_size); + DPU_REG_WRITE(c, out_xy_off + idx, dst_xy); + + DPU_REG_WRITE(c, SSPP_SRC_YSTRIDE0 + idx, ystride0); + DPU_REG_WRITE(c, SSPP_SRC_YSTRIDE1 + idx, ystride1); +} + +static void dpu_hw_sspp_setup_sourceaddress(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_cfg *cfg, + enum dpu_sspp_multirect_index rect_mode) +{ + int i; + u32 idx; + + if (_sspp_subblk_offset(ctx, DPU_SSPP_SRC, &idx)) + return; + + if (rect_mode == DPU_SSPP_RECT_SOLO) { + for (i = 0; i < ARRAY_SIZE(cfg->layout.plane_addr); i++) + DPU_REG_WRITE(&ctx->hw, SSPP_SRC0_ADDR + idx + i * 0x4, + cfg->layout.plane_addr[i]); + } else if (rect_mode == DPU_SSPP_RECT_0) { + DPU_REG_WRITE(&ctx->hw, SSPP_SRC0_ADDR + idx, + cfg->layout.plane_addr[0]); + DPU_REG_WRITE(&ctx->hw, SSPP_SRC2_ADDR + idx, + cfg->layout.plane_addr[2]); + } else { + DPU_REG_WRITE(&ctx->hw, SSPP_SRC1_ADDR + idx, + cfg->layout.plane_addr[0]); + DPU_REG_WRITE(&ctx->hw, SSPP_SRC3_ADDR + idx, + cfg->layout.plane_addr[2]); + } +} + +static void dpu_hw_sspp_setup_csc(struct dpu_hw_pipe *ctx, + struct dpu_csc_cfg *data) +{ + u32 idx; + bool csc10 = false; + + if (_sspp_subblk_offset(ctx, DPU_SSPP_CSC, &idx) || !data) + return; + + if (test_bit(DPU_SSPP_CSC_10BIT, &ctx->cap->features)) { + idx += CSC_10BIT_OFFSET; + csc10 = true; + } + + dpu_hw_csc_setup(&ctx->hw, idx, data, csc10); +} + +static void dpu_hw_sspp_setup_solidfill(struct dpu_hw_pipe *ctx, u32 color, enum + dpu_sspp_multirect_index rect_index) +{ + u32 idx; + + if (_sspp_subblk_offset(ctx, DPU_SSPP_SRC, &idx)) + return; + + if (rect_index == DPU_SSPP_RECT_SOLO || rect_index == DPU_SSPP_RECT_0) + DPU_REG_WRITE(&ctx->hw, SSPP_SRC_CONSTANT_COLOR + idx, color); + else + DPU_REG_WRITE(&ctx->hw, SSPP_SRC_CONSTANT_COLOR_REC1 + idx, + color); +} + +static void dpu_hw_sspp_setup_danger_safe_lut(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_qos_cfg *cfg) +{ + u32 idx; + + if (_sspp_subblk_offset(ctx, DPU_SSPP_SRC, &idx)) + return; + + DPU_REG_WRITE(&ctx->hw, SSPP_DANGER_LUT + idx, cfg->danger_lut); + DPU_REG_WRITE(&ctx->hw, SSPP_SAFE_LUT + idx, cfg->safe_lut); +} + +static void dpu_hw_sspp_setup_creq_lut(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_qos_cfg *cfg) +{ + u32 idx; + + if (_sspp_subblk_offset(ctx, DPU_SSPP_SRC, &idx)) + return; + + if (ctx->cap && test_bit(DPU_SSPP_QOS_8LVL, &ctx->cap->features)) { + DPU_REG_WRITE(&ctx->hw, SSPP_CREQ_LUT_0 + idx, cfg->creq_lut); + DPU_REG_WRITE(&ctx->hw, SSPP_CREQ_LUT_1 + idx, + cfg->creq_lut >> 32); + } else { + DPU_REG_WRITE(&ctx->hw, SSPP_CREQ_LUT + idx, cfg->creq_lut); + } +} + +static void dpu_hw_sspp_setup_qos_ctrl(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_qos_cfg *cfg) +{ + u32 idx; + u32 qos_ctrl = 0; + + if (_sspp_subblk_offset(ctx, DPU_SSPP_SRC, &idx)) + return; + + if (cfg->vblank_en) { + qos_ctrl |= ((cfg->creq_vblank & + SSPP_QOS_CTRL_CREQ_VBLANK_MASK) << + SSPP_QOS_CTRL_CREQ_VBLANK_OFF); + qos_ctrl |= ((cfg->danger_vblank & + SSPP_QOS_CTRL_DANGER_VBLANK_MASK) << + SSPP_QOS_CTRL_DANGER_VBLANK_OFF); + qos_ctrl |= SSPP_QOS_CTRL_VBLANK_EN; + } + + if (cfg->danger_safe_en) + qos_ctrl |= SSPP_QOS_CTRL_DANGER_SAFE_EN; + + DPU_REG_WRITE(&ctx->hw, SSPP_QOS_CTRL + idx, qos_ctrl); +} + +static void dpu_hw_sspp_setup_cdp(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_cdp_cfg *cfg) +{ + u32 idx; + u32 cdp_cntl = 0; + + if (!ctx || !cfg) + return; + + if (_sspp_subblk_offset(ctx, DPU_SSPP_SRC, &idx)) + return; + + if (cfg->enable) + cdp_cntl |= BIT(0); + if (cfg->ubwc_meta_enable) + cdp_cntl |= BIT(1); + if (cfg->tile_amortize_enable) + cdp_cntl |= BIT(2); + if (cfg->preload_ahead == DPU_SSPP_CDP_PRELOAD_AHEAD_64) + cdp_cntl |= BIT(3); + + DPU_REG_WRITE(&ctx->hw, SSPP_CDP_CNTL, cdp_cntl); +} + +static void _setup_layer_ops(struct dpu_hw_pipe *c, + unsigned long features) +{ + if (test_bit(DPU_SSPP_SRC, &features)) { + c->ops.setup_format = dpu_hw_sspp_setup_format; + c->ops.setup_rects = dpu_hw_sspp_setup_rects; + c->ops.setup_sourceaddress = dpu_hw_sspp_setup_sourceaddress; + c->ops.setup_solidfill = dpu_hw_sspp_setup_solidfill; + c->ops.setup_pe = dpu_hw_sspp_setup_pe_config; + } + + if (test_bit(DPU_SSPP_QOS, &features)) { + c->ops.setup_danger_safe_lut = + dpu_hw_sspp_setup_danger_safe_lut; + c->ops.setup_creq_lut = dpu_hw_sspp_setup_creq_lut; + c->ops.setup_qos_ctrl = dpu_hw_sspp_setup_qos_ctrl; + } + + if (test_bit(DPU_SSPP_CSC, &features) || + test_bit(DPU_SSPP_CSC_10BIT, &features)) + c->ops.setup_csc = dpu_hw_sspp_setup_csc; + + if (test_bit(DPU_SSPP_SMART_DMA_V1, &c->cap->features) || + test_bit(DPU_SSPP_SMART_DMA_V2, &c->cap->features)) + c->ops.setup_multirect = dpu_hw_sspp_setup_multirect; + + if (test_bit(DPU_SSPP_SCALER_QSEED3, &features) || + test_bit(DPU_SSPP_SCALER_QSEED4, &features)) { + c->ops.setup_scaler = _dpu_hw_sspp_setup_scaler3; + c->ops.get_scaler_ver = _dpu_hw_sspp_get_scaler3_ver; + } + + if (test_bit(DPU_SSPP_CDP, &features)) + c->ops.setup_cdp = dpu_hw_sspp_setup_cdp; +} + +static const struct dpu_sspp_cfg *_sspp_offset(enum dpu_sspp sspp, + void __iomem *addr, + struct dpu_mdss_cfg *catalog, + struct dpu_hw_blk_reg_map *b) +{ + int i; + + if ((sspp < SSPP_MAX) && catalog && addr && b) { + for (i = 0; i < catalog->sspp_count; i++) { + if (sspp == catalog->sspp[i].id) { + b->base_off = addr; + b->blk_off = catalog->sspp[i].base; + b->length = catalog->sspp[i].len; + b->hwversion = catalog->hwversion; + b->log_mask = DPU_DBG_MASK_SSPP; + return &catalog->sspp[i]; + } + } + } + + return ERR_PTR(-ENOMEM); +} + +static struct dpu_hw_blk_ops dpu_hw_ops; + +struct dpu_hw_pipe *dpu_hw_sspp_init(enum dpu_sspp idx, + void __iomem *addr, struct dpu_mdss_cfg *catalog, + bool is_virtual_pipe) +{ + struct dpu_hw_pipe *hw_pipe; + const struct dpu_sspp_cfg *cfg; + + if (!addr || !catalog) + return ERR_PTR(-EINVAL); + + hw_pipe = kzalloc(sizeof(*hw_pipe), GFP_KERNEL); + if (!hw_pipe) + return ERR_PTR(-ENOMEM); + + cfg = _sspp_offset(idx, addr, catalog, &hw_pipe->hw); + if (IS_ERR_OR_NULL(cfg)) { + kfree(hw_pipe); + return ERR_PTR(-EINVAL); + } + + /* Assign ops */ + hw_pipe->catalog = catalog; + hw_pipe->mdp = &catalog->mdp[0]; + hw_pipe->idx = idx; + hw_pipe->cap = cfg; + _setup_layer_ops(hw_pipe, hw_pipe->cap->features); + + dpu_hw_blk_init(&hw_pipe->base, DPU_HW_BLK_SSPP, idx, &dpu_hw_ops); + + return hw_pipe; +} + +void dpu_hw_sspp_destroy(struct dpu_hw_pipe *ctx) +{ + if (ctx) + dpu_hw_blk_destroy(&ctx->base); + kfree(ctx); +} + diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.h new file mode 100644 index 000000000..85b018a9b --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_sspp.h @@ -0,0 +1,407 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_SSPP_H +#define _DPU_HW_SSPP_H + +#include "dpu_hw_catalog.h" +#include "dpu_hw_mdss.h" +#include "dpu_hw_util.h" +#include "dpu_hw_blk.h" +#include "dpu_formats.h" + +struct dpu_hw_pipe; + +/** + * Flags + */ +#define DPU_SSPP_FLIP_LR BIT(0) +#define DPU_SSPP_FLIP_UD BIT(1) +#define DPU_SSPP_SOURCE_ROTATED_90 BIT(2) +#define DPU_SSPP_ROT_90 BIT(3) +#define DPU_SSPP_SOLID_FILL BIT(4) + +/** + * Define all scaler feature bits in catalog + */ +#define DPU_SSPP_SCALER ((1UL << DPU_SSPP_SCALER_RGB) | \ + (1UL << DPU_SSPP_SCALER_QSEED2) | \ + (1UL << DPU_SSPP_SCALER_QSEED3) | \ + (1UL << DPU_SSPP_SCALER_QSEED4)) + +/** + * Component indices + */ +enum { + DPU_SSPP_COMP_0, + DPU_SSPP_COMP_1_2, + DPU_SSPP_COMP_2, + DPU_SSPP_COMP_3, + + DPU_SSPP_COMP_MAX +}; + +/** + * DPU_SSPP_RECT_SOLO - multirect disabled + * DPU_SSPP_RECT_0 - rect0 of a multirect pipe + * DPU_SSPP_RECT_1 - rect1 of a multirect pipe + * + * Note: HW supports multirect with either RECT0 or + * RECT1. Considering no benefit of such configs over + * SOLO mode and to keep the plane management simple, + * we dont support single rect multirect configs. + */ +enum dpu_sspp_multirect_index { + DPU_SSPP_RECT_SOLO = 0, + DPU_SSPP_RECT_0, + DPU_SSPP_RECT_1, +}; + +enum dpu_sspp_multirect_mode { + DPU_SSPP_MULTIRECT_NONE = 0, + DPU_SSPP_MULTIRECT_PARALLEL, + DPU_SSPP_MULTIRECT_TIME_MX, +}; + +enum { + DPU_FRAME_LINEAR, + DPU_FRAME_TILE_A4X, + DPU_FRAME_TILE_A5X, +}; + +enum dpu_hw_filter { + DPU_SCALE_FILTER_NEAREST = 0, + DPU_SCALE_FILTER_BIL, + DPU_SCALE_FILTER_PCMN, + DPU_SCALE_FILTER_CA, + DPU_SCALE_FILTER_MAX +}; + +enum dpu_hw_filter_alpa { + DPU_SCALE_ALPHA_PIXEL_REP, + DPU_SCALE_ALPHA_BIL +}; + +enum dpu_hw_filter_yuv { + DPU_SCALE_2D_4X4, + DPU_SCALE_2D_CIR, + DPU_SCALE_1D_SEP, + DPU_SCALE_BIL +}; + +struct dpu_hw_sharp_cfg { + u32 strength; + u32 edge_thr; + u32 smooth_thr; + u32 noise_thr; +}; + +struct dpu_hw_pixel_ext { + /* scaling factors are enabled for this input layer */ + uint8_t enable_pxl_ext; + + int init_phase_x[DPU_MAX_PLANES]; + int phase_step_x[DPU_MAX_PLANES]; + int init_phase_y[DPU_MAX_PLANES]; + int phase_step_y[DPU_MAX_PLANES]; + + /* + * Number of pixels extension in left, right, top and bottom direction + * for all color components. This pixel value for each color component + * should be sum of fetch + repeat pixels. + */ + int num_ext_pxls_left[DPU_MAX_PLANES]; + int num_ext_pxls_right[DPU_MAX_PLANES]; + int num_ext_pxls_top[DPU_MAX_PLANES]; + int num_ext_pxls_btm[DPU_MAX_PLANES]; + + /* + * Number of pixels needs to be overfetched in left, right, top and + * bottom directions from source image for scaling. + */ + int left_ftch[DPU_MAX_PLANES]; + int right_ftch[DPU_MAX_PLANES]; + int top_ftch[DPU_MAX_PLANES]; + int btm_ftch[DPU_MAX_PLANES]; + + /* + * Number of pixels needs to be repeated in left, right, top and + * bottom directions for scaling. + */ + int left_rpt[DPU_MAX_PLANES]; + int right_rpt[DPU_MAX_PLANES]; + int top_rpt[DPU_MAX_PLANES]; + int btm_rpt[DPU_MAX_PLANES]; + + uint32_t roi_w[DPU_MAX_PLANES]; + uint32_t roi_h[DPU_MAX_PLANES]; + + /* + * Filter type to be used for scaling in horizontal and vertical + * directions + */ + enum dpu_hw_filter horz_filter[DPU_MAX_PLANES]; + enum dpu_hw_filter vert_filter[DPU_MAX_PLANES]; + +}; + +/** + * struct dpu_hw_pipe_cfg : Pipe description + * @layout: format layout information for programming buffer to hardware + * @src_rect: src ROI, caller takes into account the different operations + * such as decimation, flip etc to program this field + * @dest_rect: destination ROI. + * @index: index of the rectangle of SSPP + * @mode: parallel or time multiplex multirect mode + */ +struct dpu_hw_pipe_cfg { + struct dpu_hw_fmt_layout layout; + struct drm_rect src_rect; + struct drm_rect dst_rect; + enum dpu_sspp_multirect_index index; + enum dpu_sspp_multirect_mode mode; +}; + +/** + * struct dpu_hw_pipe_qos_cfg : Source pipe QoS configuration + * @danger_lut: LUT for generate danger level based on fill level + * @safe_lut: LUT for generate safe level based on fill level + * @creq_lut: LUT for generate creq level based on fill level + * @creq_vblank: creq value generated to vbif during vertical blanking + * @danger_vblank: danger value generated during vertical blanking + * @vblank_en: enable creq_vblank and danger_vblank during vblank + * @danger_safe_en: enable danger safe generation + */ +struct dpu_hw_pipe_qos_cfg { + u32 danger_lut; + u32 safe_lut; + u64 creq_lut; + u32 creq_vblank; + u32 danger_vblank; + bool vblank_en; + bool danger_safe_en; +}; + +/** + * enum CDP preload ahead address size + */ +enum { + DPU_SSPP_CDP_PRELOAD_AHEAD_32, + DPU_SSPP_CDP_PRELOAD_AHEAD_64 +}; + +/** + * struct dpu_hw_pipe_cdp_cfg : CDP configuration + * @enable: true to enable CDP + * @ubwc_meta_enable: true to enable ubwc metadata preload + * @tile_amortize_enable: true to enable amortization control for tile format + * @preload_ahead: number of request to preload ahead + * DPU_SSPP_CDP_PRELOAD_AHEAD_32, + * DPU_SSPP_CDP_PRELOAD_AHEAD_64 + */ +struct dpu_hw_pipe_cdp_cfg { + bool enable; + bool ubwc_meta_enable; + bool tile_amortize_enable; + u32 preload_ahead; +}; + +/** + * struct dpu_hw_pipe_ts_cfg - traffic shaper configuration + * @size: size to prefill in bytes, or zero to disable + * @time: time to prefill in usec, or zero to disable + */ +struct dpu_hw_pipe_ts_cfg { + u64 size; + u64 time; +}; + +/** + * struct dpu_hw_sspp_ops - interface to the SSPP Hw driver functions + * Caller must call the init function to get the pipe context for each pipe + * Assumption is these functions will be called after clocks are enabled + */ +struct dpu_hw_sspp_ops { + /** + * setup_format - setup pixel format cropping rectangle, flip + * @ctx: Pointer to pipe context + * @cfg: Pointer to pipe config structure + * @flags: Extra flags for format config + * @index: rectangle index in multirect + */ + void (*setup_format)(struct dpu_hw_pipe *ctx, + const struct dpu_format *fmt, u32 flags, + enum dpu_sspp_multirect_index index); + + /** + * setup_rects - setup pipe ROI rectangles + * @ctx: Pointer to pipe context + * @cfg: Pointer to pipe config structure + * @index: rectangle index in multirect + */ + void (*setup_rects)(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_cfg *cfg, + enum dpu_sspp_multirect_index index); + + /** + * setup_pe - setup pipe pixel extension + * @ctx: Pointer to pipe context + * @pe_ext: Pointer to pixel ext settings + */ + void (*setup_pe)(struct dpu_hw_pipe *ctx, + struct dpu_hw_pixel_ext *pe_ext); + + /** + * setup_sourceaddress - setup pipe source addresses + * @ctx: Pointer to pipe context + * @cfg: Pointer to pipe config structure + * @index: rectangle index in multirect + */ + void (*setup_sourceaddress)(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_cfg *cfg, + enum dpu_sspp_multirect_index index); + + /** + * setup_csc - setup color space coversion + * @ctx: Pointer to pipe context + * @data: Pointer to config structure + */ + void (*setup_csc)(struct dpu_hw_pipe *ctx, struct dpu_csc_cfg *data); + + /** + * setup_solidfill - enable/disable colorfill + * @ctx: Pointer to pipe context + * @const_color: Fill color value + * @flags: Pipe flags + * @index: rectangle index in multirect + */ + void (*setup_solidfill)(struct dpu_hw_pipe *ctx, u32 color, + enum dpu_sspp_multirect_index index); + + /** + * setup_multirect - setup multirect configuration + * @ctx: Pointer to pipe context + * @index: rectangle index in multirect + * @mode: parallel fetch / time multiplex multirect mode + */ + + void (*setup_multirect)(struct dpu_hw_pipe *ctx, + enum dpu_sspp_multirect_index index, + enum dpu_sspp_multirect_mode mode); + + /** + * setup_sharpening - setup sharpening + * @ctx: Pointer to pipe context + * @cfg: Pointer to config structure + */ + void (*setup_sharpening)(struct dpu_hw_pipe *ctx, + struct dpu_hw_sharp_cfg *cfg); + + /** + * setup_danger_safe_lut - setup danger safe LUTs + * @ctx: Pointer to pipe context + * @cfg: Pointer to pipe QoS configuration + * + */ + void (*setup_danger_safe_lut)(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_qos_cfg *cfg); + + /** + * setup_creq_lut - setup CREQ LUT + * @ctx: Pointer to pipe context + * @cfg: Pointer to pipe QoS configuration + * + */ + void (*setup_creq_lut)(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_qos_cfg *cfg); + + /** + * setup_qos_ctrl - setup QoS control + * @ctx: Pointer to pipe context + * @cfg: Pointer to pipe QoS configuration + * + */ + void (*setup_qos_ctrl)(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_qos_cfg *cfg); + + /** + * setup_histogram - setup histograms + * @ctx: Pointer to pipe context + * @cfg: Pointer to histogram configuration + */ + void (*setup_histogram)(struct dpu_hw_pipe *ctx, + void *cfg); + + /** + * setup_scaler - setup scaler + * @ctx: Pointer to pipe context + * @pipe_cfg: Pointer to pipe configuration + * @pe_cfg: Pointer to pixel extension configuration + * @scaler_cfg: Pointer to scaler configuration + */ + void (*setup_scaler)(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_cfg *pipe_cfg, + struct dpu_hw_pixel_ext *pe_cfg, + void *scaler_cfg); + + /** + * get_scaler_ver - get scaler h/w version + * @ctx: Pointer to pipe context + */ + u32 (*get_scaler_ver)(struct dpu_hw_pipe *ctx); + + /** + * setup_cdp - setup client driven prefetch + * @ctx: Pointer to pipe context + * @cfg: Pointer to cdp configuration + */ + void (*setup_cdp)(struct dpu_hw_pipe *ctx, + struct dpu_hw_pipe_cdp_cfg *cfg); +}; + +/** + * struct dpu_hw_pipe - pipe description + * @base: hardware block base structure + * @hw: block hardware details + * @catalog: back pointer to catalog + * @mdp: pointer to associated mdp portion of the catalog + * @idx: pipe index + * @cap: pointer to layer_cfg + * @ops: pointer to operations possible for this pipe + */ +struct dpu_hw_pipe { + struct dpu_hw_blk base; + struct dpu_hw_blk_reg_map hw; + struct dpu_mdss_cfg *catalog; + const struct dpu_mdp_cfg *mdp; + + /* Pipe */ + enum dpu_sspp idx; + const struct dpu_sspp_cfg *cap; + + /* Ops */ + struct dpu_hw_sspp_ops ops; +}; + +/** + * dpu_hw_sspp_init - initializes the sspp hw driver object. + * Should be called once before accessing every pipe. + * @idx: Pipe index for which driver object is required + * @addr: Mapped register io address of MDP + * @catalog : Pointer to mdss catalog data + * @is_virtual_pipe: is this pipe virtual pipe + */ +struct dpu_hw_pipe *dpu_hw_sspp_init(enum dpu_sspp idx, + void __iomem *addr, struct dpu_mdss_cfg *catalog, + bool is_virtual_pipe); + +/** + * dpu_hw_sspp_destroy(): Destroys SSPP driver context + * should be called during Hw pipe cleanup. + * @ctx: Pointer to SSPP driver context returned by dpu_hw_sspp_init + */ +void dpu_hw_sspp_destroy(struct dpu_hw_pipe *ctx); + +#endif /*_DPU_HW_SSPP_H */ + diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_top.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_top.c new file mode 100644 index 000000000..01b76766a --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_top.c @@ -0,0 +1,338 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#include "dpu_hwio.h" +#include "dpu_hw_catalog.h" +#include "dpu_hw_top.h" +#include "dpu_kms.h" + +#define SSPP_SPARE 0x28 + +#define FLD_SPLIT_DISPLAY_CMD BIT(1) +#define FLD_SMART_PANEL_FREE_RUN BIT(2) +#define FLD_INTF_1_SW_TRG_MUX BIT(4) +#define FLD_INTF_2_SW_TRG_MUX BIT(8) +#define FLD_TE_LINE_INTER_WATERLEVEL_MASK 0xFFFF + +#define DANGER_STATUS 0x360 +#define SAFE_STATUS 0x364 + +#define TE_LINE_INTERVAL 0x3F4 + +#define TRAFFIC_SHAPER_EN BIT(31) +#define TRAFFIC_SHAPER_RD_CLIENT(num) (0x030 + (num * 4)) +#define TRAFFIC_SHAPER_WR_CLIENT(num) (0x060 + (num * 4)) +#define TRAFFIC_SHAPER_FIXPOINT_FACTOR 4 + +#define MDP_WD_TIMER_0_CTL 0x380 +#define MDP_WD_TIMER_0_CTL2 0x384 +#define MDP_WD_TIMER_0_LOAD_VALUE 0x388 +#define MDP_WD_TIMER_1_CTL 0x390 +#define MDP_WD_TIMER_1_CTL2 0x394 +#define MDP_WD_TIMER_1_LOAD_VALUE 0x398 +#define MDP_WD_TIMER_2_CTL 0x420 +#define MDP_WD_TIMER_2_CTL2 0x424 +#define MDP_WD_TIMER_2_LOAD_VALUE 0x428 +#define MDP_WD_TIMER_3_CTL 0x430 +#define MDP_WD_TIMER_3_CTL2 0x434 +#define MDP_WD_TIMER_3_LOAD_VALUE 0x438 +#define MDP_WD_TIMER_4_CTL 0x440 +#define MDP_WD_TIMER_4_CTL2 0x444 +#define MDP_WD_TIMER_4_LOAD_VALUE 0x448 + +#define MDP_TICK_COUNT 16 +#define XO_CLK_RATE 19200 +#define MS_TICKS_IN_SEC 1000 + +#define CALCULATE_WD_LOAD_VALUE(fps) \ + ((uint32_t)((MS_TICKS_IN_SEC * XO_CLK_RATE)/(MDP_TICK_COUNT * fps))) + +#define DCE_SEL 0x450 + +static void dpu_hw_setup_split_pipe(struct dpu_hw_mdp *mdp, + struct split_pipe_cfg *cfg) +{ + struct dpu_hw_blk_reg_map *c; + u32 upper_pipe = 0; + u32 lower_pipe = 0; + + if (!mdp || !cfg) + return; + + c = &mdp->hw; + + if (cfg->en) { + if (cfg->mode == INTF_MODE_CMD) { + lower_pipe = FLD_SPLIT_DISPLAY_CMD; + /* interface controlling sw trigger */ + if (cfg->intf == INTF_2) + lower_pipe |= FLD_INTF_1_SW_TRG_MUX; + else + lower_pipe |= FLD_INTF_2_SW_TRG_MUX; + upper_pipe = lower_pipe; + } else { + if (cfg->intf == INTF_2) { + lower_pipe = FLD_INTF_1_SW_TRG_MUX; + upper_pipe = FLD_INTF_2_SW_TRG_MUX; + } else { + lower_pipe = FLD_INTF_2_SW_TRG_MUX; + upper_pipe = FLD_INTF_1_SW_TRG_MUX; + } + } + } + + DPU_REG_WRITE(c, SSPP_SPARE, cfg->split_flush_en ? 0x1 : 0x0); + DPU_REG_WRITE(c, SPLIT_DISPLAY_LOWER_PIPE_CTRL, lower_pipe); + DPU_REG_WRITE(c, SPLIT_DISPLAY_UPPER_PIPE_CTRL, upper_pipe); + DPU_REG_WRITE(c, SPLIT_DISPLAY_EN, cfg->en & 0x1); +} + +static bool dpu_hw_setup_clk_force_ctrl(struct dpu_hw_mdp *mdp, + enum dpu_clk_ctrl_type clk_ctrl, bool enable) +{ + struct dpu_hw_blk_reg_map *c; + u32 reg_off, bit_off; + u32 reg_val, new_val; + bool clk_forced_on; + + if (!mdp) + return false; + + c = &mdp->hw; + + if (clk_ctrl <= DPU_CLK_CTRL_NONE || clk_ctrl >= DPU_CLK_CTRL_MAX) + return false; + + reg_off = mdp->caps->clk_ctrls[clk_ctrl].reg_off; + bit_off = mdp->caps->clk_ctrls[clk_ctrl].bit_off; + + reg_val = DPU_REG_READ(c, reg_off); + + if (enable) + new_val = reg_val | BIT(bit_off); + else + new_val = reg_val & ~BIT(bit_off); + + DPU_REG_WRITE(c, reg_off, new_val); + + clk_forced_on = !(reg_val & BIT(bit_off)); + + return clk_forced_on; +} + + +static void dpu_hw_get_danger_status(struct dpu_hw_mdp *mdp, + struct dpu_danger_safe_status *status) +{ + struct dpu_hw_blk_reg_map *c; + u32 value; + + if (!mdp || !status) + return; + + c = &mdp->hw; + + value = DPU_REG_READ(c, DANGER_STATUS); + status->mdp = (value >> 0) & 0x3; + status->sspp[SSPP_VIG0] = (value >> 4) & 0x3; + status->sspp[SSPP_VIG1] = (value >> 6) & 0x3; + status->sspp[SSPP_VIG2] = (value >> 8) & 0x3; + status->sspp[SSPP_VIG3] = (value >> 10) & 0x3; + status->sspp[SSPP_RGB0] = (value >> 12) & 0x3; + status->sspp[SSPP_RGB1] = (value >> 14) & 0x3; + status->sspp[SSPP_RGB2] = (value >> 16) & 0x3; + status->sspp[SSPP_RGB3] = (value >> 18) & 0x3; + status->sspp[SSPP_DMA0] = (value >> 20) & 0x3; + status->sspp[SSPP_DMA1] = (value >> 22) & 0x3; + status->sspp[SSPP_DMA2] = (value >> 28) & 0x3; + status->sspp[SSPP_DMA3] = (value >> 30) & 0x3; + status->sspp[SSPP_CURSOR0] = (value >> 24) & 0x3; + status->sspp[SSPP_CURSOR1] = (value >> 26) & 0x3; +} + +static void dpu_hw_setup_vsync_source(struct dpu_hw_mdp *mdp, + struct dpu_vsync_source_cfg *cfg) +{ + struct dpu_hw_blk_reg_map *c; + u32 reg, wd_load_value, wd_ctl, wd_ctl2, i; + static const u32 pp_offset[PINGPONG_MAX] = {0xC, 0x8, 0x4, 0x13, 0x18}; + + if (!mdp || !cfg || (cfg->pp_count > ARRAY_SIZE(cfg->ppnumber))) + return; + + c = &mdp->hw; + reg = DPU_REG_READ(c, MDP_VSYNC_SEL); + for (i = 0; i < cfg->pp_count; i++) { + int pp_idx = cfg->ppnumber[i] - PINGPONG_0; + + if (pp_idx >= ARRAY_SIZE(pp_offset)) + continue; + + reg &= ~(0xf << pp_offset[pp_idx]); + reg |= (cfg->vsync_source & 0xf) << pp_offset[pp_idx]; + } + DPU_REG_WRITE(c, MDP_VSYNC_SEL, reg); + + if (cfg->vsync_source >= DPU_VSYNC_SOURCE_WD_TIMER_4 && + cfg->vsync_source <= DPU_VSYNC_SOURCE_WD_TIMER_0) { + switch (cfg->vsync_source) { + case DPU_VSYNC_SOURCE_WD_TIMER_4: + wd_load_value = MDP_WD_TIMER_4_LOAD_VALUE; + wd_ctl = MDP_WD_TIMER_4_CTL; + wd_ctl2 = MDP_WD_TIMER_4_CTL2; + break; + case DPU_VSYNC_SOURCE_WD_TIMER_3: + wd_load_value = MDP_WD_TIMER_3_LOAD_VALUE; + wd_ctl = MDP_WD_TIMER_3_CTL; + wd_ctl2 = MDP_WD_TIMER_3_CTL2; + break; + case DPU_VSYNC_SOURCE_WD_TIMER_2: + wd_load_value = MDP_WD_TIMER_2_LOAD_VALUE; + wd_ctl = MDP_WD_TIMER_2_CTL; + wd_ctl2 = MDP_WD_TIMER_2_CTL2; + break; + case DPU_VSYNC_SOURCE_WD_TIMER_1: + wd_load_value = MDP_WD_TIMER_1_LOAD_VALUE; + wd_ctl = MDP_WD_TIMER_1_CTL; + wd_ctl2 = MDP_WD_TIMER_1_CTL2; + break; + case DPU_VSYNC_SOURCE_WD_TIMER_0: + default: + wd_load_value = MDP_WD_TIMER_0_LOAD_VALUE; + wd_ctl = MDP_WD_TIMER_0_CTL; + wd_ctl2 = MDP_WD_TIMER_0_CTL2; + break; + } + + DPU_REG_WRITE(c, wd_load_value, + CALCULATE_WD_LOAD_VALUE(cfg->frame_rate)); + + DPU_REG_WRITE(c, wd_ctl, BIT(0)); /* clear timer */ + reg = DPU_REG_READ(c, wd_ctl2); + reg |= BIT(8); /* enable heartbeat timer */ + reg |= BIT(0); /* enable WD timer */ + DPU_REG_WRITE(c, wd_ctl2, reg); + + /* make sure that timers are enabled/disabled for vsync state */ + wmb(); + } +} + +static void dpu_hw_get_safe_status(struct dpu_hw_mdp *mdp, + struct dpu_danger_safe_status *status) +{ + struct dpu_hw_blk_reg_map *c; + u32 value; + + if (!mdp || !status) + return; + + c = &mdp->hw; + + value = DPU_REG_READ(c, SAFE_STATUS); + status->mdp = (value >> 0) & 0x1; + status->sspp[SSPP_VIG0] = (value >> 4) & 0x1; + status->sspp[SSPP_VIG1] = (value >> 6) & 0x1; + status->sspp[SSPP_VIG2] = (value >> 8) & 0x1; + status->sspp[SSPP_VIG3] = (value >> 10) & 0x1; + status->sspp[SSPP_RGB0] = (value >> 12) & 0x1; + status->sspp[SSPP_RGB1] = (value >> 14) & 0x1; + status->sspp[SSPP_RGB2] = (value >> 16) & 0x1; + status->sspp[SSPP_RGB3] = (value >> 18) & 0x1; + status->sspp[SSPP_DMA0] = (value >> 20) & 0x1; + status->sspp[SSPP_DMA1] = (value >> 22) & 0x1; + status->sspp[SSPP_DMA2] = (value >> 28) & 0x1; + status->sspp[SSPP_DMA3] = (value >> 30) & 0x1; + status->sspp[SSPP_CURSOR0] = (value >> 24) & 0x1; + status->sspp[SSPP_CURSOR1] = (value >> 26) & 0x1; +} + +static void dpu_hw_intf_audio_select(struct dpu_hw_mdp *mdp) +{ + struct dpu_hw_blk_reg_map *c; + + if (!mdp) + return; + + c = &mdp->hw; + + DPU_REG_WRITE(c, HDMI_DP_CORE_SELECT, 0x1); +} + +static void _setup_mdp_ops(struct dpu_hw_mdp_ops *ops, + unsigned long cap) +{ + ops->setup_split_pipe = dpu_hw_setup_split_pipe; + ops->setup_clk_force_ctrl = dpu_hw_setup_clk_force_ctrl; + ops->get_danger_status = dpu_hw_get_danger_status; + ops->setup_vsync_source = dpu_hw_setup_vsync_source; + ops->get_safe_status = dpu_hw_get_safe_status; + ops->intf_audio_select = dpu_hw_intf_audio_select; +} + +static const struct dpu_mdp_cfg *_top_offset(enum dpu_mdp mdp, + const struct dpu_mdss_cfg *m, + void __iomem *addr, + struct dpu_hw_blk_reg_map *b) +{ + int i; + + if (!m || !addr || !b) + return ERR_PTR(-EINVAL); + + for (i = 0; i < m->mdp_count; i++) { + if (mdp == m->mdp[i].id) { + b->base_off = addr; + b->blk_off = m->mdp[i].base; + b->length = m->mdp[i].len; + b->hwversion = m->hwversion; + b->log_mask = DPU_DBG_MASK_TOP; + return &m->mdp[i]; + } + } + + return ERR_PTR(-EINVAL); +} + +static struct dpu_hw_blk_ops dpu_hw_ops; + +struct dpu_hw_mdp *dpu_hw_mdptop_init(enum dpu_mdp idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m) +{ + struct dpu_hw_mdp *mdp; + const struct dpu_mdp_cfg *cfg; + + if (!addr || !m) + return ERR_PTR(-EINVAL); + + mdp = kzalloc(sizeof(*mdp), GFP_KERNEL); + if (!mdp) + return ERR_PTR(-ENOMEM); + + cfg = _top_offset(idx, m, addr, &mdp->hw); + if (IS_ERR_OR_NULL(cfg)) { + kfree(mdp); + return ERR_PTR(-EINVAL); + } + + /* + * Assign ops + */ + mdp->idx = idx; + mdp->caps = cfg; + _setup_mdp_ops(&mdp->ops, mdp->caps->features); + + dpu_hw_blk_init(&mdp->base, DPU_HW_BLK_TOP, idx, &dpu_hw_ops); + + return mdp; +} + +void dpu_hw_mdp_destroy(struct dpu_hw_mdp *mdp) +{ + if (mdp) + dpu_hw_blk_destroy(&mdp->base); + kfree(mdp); +} + diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_top.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_top.h new file mode 100644 index 000000000..8018fff56 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_top.h @@ -0,0 +1,160 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_TOP_H +#define _DPU_HW_TOP_H + +#include "dpu_hw_catalog.h" +#include "dpu_hw_mdss.h" +#include "dpu_hw_util.h" +#include "dpu_hw_blk.h" + +struct dpu_hw_mdp; + +/** + * struct traffic_shaper_cfg: traffic shaper configuration + * @en : enable/disable traffic shaper + * @rd_client : true if read client; false if write client + * @client_id : client identifier + * @bpc_denom : denominator of byte per clk + * @bpc_numer : numerator of byte per clk + */ +struct traffic_shaper_cfg { + bool en; + bool rd_client; + u32 client_id; + u32 bpc_denom; + u64 bpc_numer; +}; + +/** + * struct split_pipe_cfg - pipe configuration for dual display panels + * @en : Enable/disable dual pipe confguration + * @mode : Panel interface mode + * @intf : Interface id for main control path + * @split_flush_en: Allows both the paths to be flushed when master path is + * flushed + */ +struct split_pipe_cfg { + bool en; + enum dpu_intf_mode mode; + enum dpu_intf intf; + bool split_flush_en; +}; + +/** + * struct dpu_danger_safe_status: danger and safe status signals + * @mdp: top level status + * @sspp: source pipe status + */ +struct dpu_danger_safe_status { + u8 mdp; + u8 sspp[SSPP_MAX]; +}; + +/** + * struct dpu_vsync_source_cfg - configure vsync source and configure the + * watchdog timers if required. + * @pp_count: number of ping pongs active + * @frame_rate: Display frame rate + * @ppnumber: ping pong index array + * @vsync_source: vsync source selection + */ +struct dpu_vsync_source_cfg { + u32 pp_count; + u32 frame_rate; + u32 ppnumber[PINGPONG_MAX]; + u32 vsync_source; +}; + +/** + * struct dpu_hw_mdp_ops - interface to the MDP TOP Hw driver functions + * Assumption is these functions will be called after clocks are enabled. + * @setup_split_pipe : Programs the pipe control registers + * @setup_pp_split : Programs the pp split control registers + * @setup_traffic_shaper : programs traffic shaper control + */ +struct dpu_hw_mdp_ops { + /** setup_split_pipe() : Regsiters are not double buffered, thisk + * function should be called before timing control enable + * @mdp : mdp top context driver + * @cfg : upper and lower part of pipe configuration + */ + void (*setup_split_pipe)(struct dpu_hw_mdp *mdp, + struct split_pipe_cfg *p); + + /** + * setup_traffic_shaper() : Setup traffic shaper control + * @mdp : mdp top context driver + * @cfg : traffic shaper configuration + */ + void (*setup_traffic_shaper)(struct dpu_hw_mdp *mdp, + struct traffic_shaper_cfg *cfg); + + /** + * setup_clk_force_ctrl - set clock force control + * @mdp: mdp top context driver + * @clk_ctrl: clock to be controlled + * @enable: force on enable + * @return: if the clock is forced-on by this function + */ + bool (*setup_clk_force_ctrl)(struct dpu_hw_mdp *mdp, + enum dpu_clk_ctrl_type clk_ctrl, bool enable); + + /** + * get_danger_status - get danger status + * @mdp: mdp top context driver + * @status: Pointer to danger safe status + */ + void (*get_danger_status)(struct dpu_hw_mdp *mdp, + struct dpu_danger_safe_status *status); + + /** + * setup_vsync_source - setup vsync source configuration details + * @mdp: mdp top context driver + * @cfg: vsync source selection configuration + */ + void (*setup_vsync_source)(struct dpu_hw_mdp *mdp, + struct dpu_vsync_source_cfg *cfg); + + /** + * get_safe_status - get safe status + * @mdp: mdp top context driver + * @status: Pointer to danger safe status + */ + void (*get_safe_status)(struct dpu_hw_mdp *mdp, + struct dpu_danger_safe_status *status); + + /** + * intf_audio_select - select the external interface for audio + * @mdp: mdp top context driver + */ + void (*intf_audio_select)(struct dpu_hw_mdp *mdp); +}; + +struct dpu_hw_mdp { + struct dpu_hw_blk base; + struct dpu_hw_blk_reg_map hw; + + /* top */ + enum dpu_mdp idx; + const struct dpu_mdp_cfg *caps; + + /* ops */ + struct dpu_hw_mdp_ops ops; +}; + +/** + * dpu_hw_mdptop_init - initializes the top driver for the passed idx + * @idx: Interface index for which driver object is required + * @addr: Mapped register io address of MDP + * @m: Pointer to mdss catalog data + */ +struct dpu_hw_mdp *dpu_hw_mdptop_init(enum dpu_mdp idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m); + +void dpu_hw_mdp_destroy(struct dpu_hw_mdp *mdp); + +#endif /*_DPU_HW_TOP_H */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.c new file mode 100644 index 000000000..84e987599 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.c @@ -0,0 +1,357 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ + +#include "msm_drv.h" +#include "dpu_kms.h" +#include "dpu_hw_mdss.h" +#include "dpu_hw_util.h" + +/* using a file static variables for debugfs access */ +static u32 dpu_hw_util_log_mask = DPU_DBG_MASK_NONE; + +/* DPU_SCALER_QSEED3 */ +#define QSEED3_HW_VERSION 0x00 +#define QSEED3_OP_MODE 0x04 +#define QSEED3_RGB2Y_COEFF 0x08 +#define QSEED3_PHASE_INIT 0x0C +#define QSEED3_PHASE_STEP_Y_H 0x10 +#define QSEED3_PHASE_STEP_Y_V 0x14 +#define QSEED3_PHASE_STEP_UV_H 0x18 +#define QSEED3_PHASE_STEP_UV_V 0x1C +#define QSEED3_PRELOAD 0x20 +#define QSEED3_DE_SHARPEN 0x24 +#define QSEED3_DE_SHARPEN_CTL 0x28 +#define QSEED3_DE_SHAPE_CTL 0x2C +#define QSEED3_DE_THRESHOLD 0x30 +#define QSEED3_DE_ADJUST_DATA_0 0x34 +#define QSEED3_DE_ADJUST_DATA_1 0x38 +#define QSEED3_DE_ADJUST_DATA_2 0x3C +#define QSEED3_SRC_SIZE_Y_RGB_A 0x40 +#define QSEED3_SRC_SIZE_UV 0x44 +#define QSEED3_DST_SIZE 0x48 +#define QSEED3_COEF_LUT_CTRL 0x4C +#define QSEED3_COEF_LUT_SWAP_BIT 0 +#define QSEED3_COEF_LUT_DIR_BIT 1 +#define QSEED3_COEF_LUT_Y_CIR_BIT 2 +#define QSEED3_COEF_LUT_UV_CIR_BIT 3 +#define QSEED3_COEF_LUT_Y_SEP_BIT 4 +#define QSEED3_COEF_LUT_UV_SEP_BIT 5 +#define QSEED3_BUFFER_CTRL 0x50 +#define QSEED3_CLK_CTRL0 0x54 +#define QSEED3_CLK_CTRL1 0x58 +#define QSEED3_CLK_STATUS 0x5C +#define QSEED3_PHASE_INIT_Y_H 0x90 +#define QSEED3_PHASE_INIT_Y_V 0x94 +#define QSEED3_PHASE_INIT_UV_H 0x98 +#define QSEED3_PHASE_INIT_UV_V 0x9C +#define QSEED3_COEF_LUT 0x100 +#define QSEED3_FILTERS 5 +#define QSEED3_LUT_REGIONS 4 +#define QSEED3_CIRCULAR_LUTS 9 +#define QSEED3_SEPARABLE_LUTS 10 +#define QSEED3_LUT_SIZE 60 +#define QSEED3_ENABLE 2 +#define QSEED3_DIR_LUT_SIZE (200 * sizeof(u32)) +#define QSEED3_CIR_LUT_SIZE \ + (QSEED3_LUT_SIZE * QSEED3_CIRCULAR_LUTS * sizeof(u32)) +#define QSEED3_SEP_LUT_SIZE \ + (QSEED3_LUT_SIZE * QSEED3_SEPARABLE_LUTS * sizeof(u32)) + +void dpu_reg_write(struct dpu_hw_blk_reg_map *c, + u32 reg_off, + u32 val, + const char *name) +{ + /* don't need to mutex protect this */ + if (c->log_mask & dpu_hw_util_log_mask) + DPU_DEBUG_DRIVER("[%s:0x%X] <= 0x%X\n", + name, c->blk_off + reg_off, val); + writel_relaxed(val, c->base_off + c->blk_off + reg_off); +} + +int dpu_reg_read(struct dpu_hw_blk_reg_map *c, u32 reg_off) +{ + return readl_relaxed(c->base_off + c->blk_off + reg_off); +} + +u32 *dpu_hw_util_get_log_mask_ptr(void) +{ + return &dpu_hw_util_log_mask; +} + +static void _dpu_hw_setup_scaler3_lut(struct dpu_hw_blk_reg_map *c, + struct dpu_hw_scaler3_cfg *scaler3_cfg, u32 offset) +{ + int i, j, filter; + int config_lut = 0x0; + unsigned long lut_flags; + u32 lut_addr, lut_offset, lut_len; + u32 *lut[QSEED3_FILTERS] = {NULL, NULL, NULL, NULL, NULL}; + static const uint32_t off_tbl[QSEED3_FILTERS][QSEED3_LUT_REGIONS][2] = { + {{18, 0x000}, {12, 0x120}, {12, 0x1E0}, {8, 0x2A0} }, + {{6, 0x320}, {3, 0x3E0}, {3, 0x440}, {3, 0x4A0} }, + {{6, 0x500}, {3, 0x5c0}, {3, 0x620}, {3, 0x680} }, + {{6, 0x380}, {3, 0x410}, {3, 0x470}, {3, 0x4d0} }, + {{6, 0x560}, {3, 0x5f0}, {3, 0x650}, {3, 0x6b0} }, + }; + + lut_flags = (unsigned long) scaler3_cfg->lut_flag; + if (test_bit(QSEED3_COEF_LUT_DIR_BIT, &lut_flags) && + (scaler3_cfg->dir_len == QSEED3_DIR_LUT_SIZE)) { + lut[0] = scaler3_cfg->dir_lut; + config_lut = 1; + } + if (test_bit(QSEED3_COEF_LUT_Y_CIR_BIT, &lut_flags) && + (scaler3_cfg->y_rgb_cir_lut_idx < QSEED3_CIRCULAR_LUTS) && + (scaler3_cfg->cir_len == QSEED3_CIR_LUT_SIZE)) { + lut[1] = scaler3_cfg->cir_lut + + scaler3_cfg->y_rgb_cir_lut_idx * QSEED3_LUT_SIZE; + config_lut = 1; + } + if (test_bit(QSEED3_COEF_LUT_UV_CIR_BIT, &lut_flags) && + (scaler3_cfg->uv_cir_lut_idx < QSEED3_CIRCULAR_LUTS) && + (scaler3_cfg->cir_len == QSEED3_CIR_LUT_SIZE)) { + lut[2] = scaler3_cfg->cir_lut + + scaler3_cfg->uv_cir_lut_idx * QSEED3_LUT_SIZE; + config_lut = 1; + } + if (test_bit(QSEED3_COEF_LUT_Y_SEP_BIT, &lut_flags) && + (scaler3_cfg->y_rgb_sep_lut_idx < QSEED3_SEPARABLE_LUTS) && + (scaler3_cfg->sep_len == QSEED3_SEP_LUT_SIZE)) { + lut[3] = scaler3_cfg->sep_lut + + scaler3_cfg->y_rgb_sep_lut_idx * QSEED3_LUT_SIZE; + config_lut = 1; + } + if (test_bit(QSEED3_COEF_LUT_UV_SEP_BIT, &lut_flags) && + (scaler3_cfg->uv_sep_lut_idx < QSEED3_SEPARABLE_LUTS) && + (scaler3_cfg->sep_len == QSEED3_SEP_LUT_SIZE)) { + lut[4] = scaler3_cfg->sep_lut + + scaler3_cfg->uv_sep_lut_idx * QSEED3_LUT_SIZE; + config_lut = 1; + } + + if (config_lut) { + for (filter = 0; filter < QSEED3_FILTERS; filter++) { + if (!lut[filter]) + continue; + lut_offset = 0; + for (i = 0; i < QSEED3_LUT_REGIONS; i++) { + lut_addr = QSEED3_COEF_LUT + offset + + off_tbl[filter][i][1]; + lut_len = off_tbl[filter][i][0] << 2; + for (j = 0; j < lut_len; j++) { + DPU_REG_WRITE(c, + lut_addr, + (lut[filter])[lut_offset++]); + lut_addr += 4; + } + } + } + } + + if (test_bit(QSEED3_COEF_LUT_SWAP_BIT, &lut_flags)) + DPU_REG_WRITE(c, QSEED3_COEF_LUT_CTRL + offset, BIT(0)); + +} + +static void _dpu_hw_setup_scaler3_de(struct dpu_hw_blk_reg_map *c, + struct dpu_hw_scaler3_de_cfg *de_cfg, u32 offset) +{ + u32 sharp_lvl, sharp_ctl, shape_ctl, de_thr; + u32 adjust_a, adjust_b, adjust_c; + + if (!de_cfg->enable) + return; + + sharp_lvl = (de_cfg->sharpen_level1 & 0x1FF) | + ((de_cfg->sharpen_level2 & 0x1FF) << 16); + + sharp_ctl = ((de_cfg->limit & 0xF) << 9) | + ((de_cfg->prec_shift & 0x7) << 13) | + ((de_cfg->clip & 0x7) << 16); + + shape_ctl = (de_cfg->thr_quiet & 0xFF) | + ((de_cfg->thr_dieout & 0x3FF) << 16); + + de_thr = (de_cfg->thr_low & 0x3FF) | + ((de_cfg->thr_high & 0x3FF) << 16); + + adjust_a = (de_cfg->adjust_a[0] & 0x3FF) | + ((de_cfg->adjust_a[1] & 0x3FF) << 10) | + ((de_cfg->adjust_a[2] & 0x3FF) << 20); + + adjust_b = (de_cfg->adjust_b[0] & 0x3FF) | + ((de_cfg->adjust_b[1] & 0x3FF) << 10) | + ((de_cfg->adjust_b[2] & 0x3FF) << 20); + + adjust_c = (de_cfg->adjust_c[0] & 0x3FF) | + ((de_cfg->adjust_c[1] & 0x3FF) << 10) | + ((de_cfg->adjust_c[2] & 0x3FF) << 20); + + DPU_REG_WRITE(c, QSEED3_DE_SHARPEN + offset, sharp_lvl); + DPU_REG_WRITE(c, QSEED3_DE_SHARPEN_CTL + offset, sharp_ctl); + DPU_REG_WRITE(c, QSEED3_DE_SHAPE_CTL + offset, shape_ctl); + DPU_REG_WRITE(c, QSEED3_DE_THRESHOLD + offset, de_thr); + DPU_REG_WRITE(c, QSEED3_DE_ADJUST_DATA_0 + offset, adjust_a); + DPU_REG_WRITE(c, QSEED3_DE_ADJUST_DATA_1 + offset, adjust_b); + DPU_REG_WRITE(c, QSEED3_DE_ADJUST_DATA_2 + offset, adjust_c); + +} + +void dpu_hw_setup_scaler3(struct dpu_hw_blk_reg_map *c, + struct dpu_hw_scaler3_cfg *scaler3_cfg, + u32 scaler_offset, u32 scaler_version, + const struct dpu_format *format) +{ + u32 op_mode = 0; + u32 phase_init, preload, src_y_rgb, src_uv, dst; + + if (!scaler3_cfg->enable) + goto end; + + op_mode |= BIT(0); + op_mode |= (scaler3_cfg->y_rgb_filter_cfg & 0x3) << 16; + + if (format && DPU_FORMAT_IS_YUV(format)) { + op_mode |= BIT(12); + op_mode |= (scaler3_cfg->uv_filter_cfg & 0x3) << 24; + } + + op_mode |= (scaler3_cfg->blend_cfg & 1) << 31; + op_mode |= (scaler3_cfg->dir_en) ? BIT(4) : 0; + + preload = + ((scaler3_cfg->preload_x[0] & 0x7F) << 0) | + ((scaler3_cfg->preload_y[0] & 0x7F) << 8) | + ((scaler3_cfg->preload_x[1] & 0x7F) << 16) | + ((scaler3_cfg->preload_y[1] & 0x7F) << 24); + + src_y_rgb = (scaler3_cfg->src_width[0] & 0x1FFFF) | + ((scaler3_cfg->src_height[0] & 0x1FFFF) << 16); + + src_uv = (scaler3_cfg->src_width[1] & 0x1FFFF) | + ((scaler3_cfg->src_height[1] & 0x1FFFF) << 16); + + dst = (scaler3_cfg->dst_width & 0x1FFFF) | + ((scaler3_cfg->dst_height & 0x1FFFF) << 16); + + if (scaler3_cfg->de.enable) { + _dpu_hw_setup_scaler3_de(c, &scaler3_cfg->de, scaler_offset); + op_mode |= BIT(8); + } + + if (scaler3_cfg->lut_flag) + _dpu_hw_setup_scaler3_lut(c, scaler3_cfg, + scaler_offset); + + if (scaler_version == 0x1002) { + phase_init = + ((scaler3_cfg->init_phase_x[0] & 0x3F) << 0) | + ((scaler3_cfg->init_phase_y[0] & 0x3F) << 8) | + ((scaler3_cfg->init_phase_x[1] & 0x3F) << 16) | + ((scaler3_cfg->init_phase_y[1] & 0x3F) << 24); + DPU_REG_WRITE(c, QSEED3_PHASE_INIT + scaler_offset, phase_init); + } else { + DPU_REG_WRITE(c, QSEED3_PHASE_INIT_Y_H + scaler_offset, + scaler3_cfg->init_phase_x[0] & 0x1FFFFF); + DPU_REG_WRITE(c, QSEED3_PHASE_INIT_Y_V + scaler_offset, + scaler3_cfg->init_phase_y[0] & 0x1FFFFF); + DPU_REG_WRITE(c, QSEED3_PHASE_INIT_UV_H + scaler_offset, + scaler3_cfg->init_phase_x[1] & 0x1FFFFF); + DPU_REG_WRITE(c, QSEED3_PHASE_INIT_UV_V + scaler_offset, + scaler3_cfg->init_phase_y[1] & 0x1FFFFF); + } + + DPU_REG_WRITE(c, QSEED3_PHASE_STEP_Y_H + scaler_offset, + scaler3_cfg->phase_step_x[0] & 0xFFFFFF); + + DPU_REG_WRITE(c, QSEED3_PHASE_STEP_Y_V + scaler_offset, + scaler3_cfg->phase_step_y[0] & 0xFFFFFF); + + DPU_REG_WRITE(c, QSEED3_PHASE_STEP_UV_H + scaler_offset, + scaler3_cfg->phase_step_x[1] & 0xFFFFFF); + + DPU_REG_WRITE(c, QSEED3_PHASE_STEP_UV_V + scaler_offset, + scaler3_cfg->phase_step_y[1] & 0xFFFFFF); + + DPU_REG_WRITE(c, QSEED3_PRELOAD + scaler_offset, preload); + + DPU_REG_WRITE(c, QSEED3_SRC_SIZE_Y_RGB_A + scaler_offset, src_y_rgb); + + DPU_REG_WRITE(c, QSEED3_SRC_SIZE_UV + scaler_offset, src_uv); + + DPU_REG_WRITE(c, QSEED3_DST_SIZE + scaler_offset, dst); + +end: + if (format && !DPU_FORMAT_IS_DX(format)) + op_mode |= BIT(14); + + if (format && format->alpha_enable) { + op_mode |= BIT(10); + if (scaler_version == 0x1002) + op_mode |= (scaler3_cfg->alpha_filter_cfg & 0x1) << 30; + else + op_mode |= (scaler3_cfg->alpha_filter_cfg & 0x3) << 29; + } + + DPU_REG_WRITE(c, QSEED3_OP_MODE + scaler_offset, op_mode); +} + +u32 dpu_hw_get_scaler3_ver(struct dpu_hw_blk_reg_map *c, + u32 scaler_offset) +{ + return DPU_REG_READ(c, QSEED3_HW_VERSION + scaler_offset); +} + +void dpu_hw_csc_setup(struct dpu_hw_blk_reg_map *c, + u32 csc_reg_off, + struct dpu_csc_cfg *data, bool csc10) +{ + static const u32 matrix_shift = 7; + u32 clamp_shift = csc10 ? 16 : 8; + u32 val; + + /* matrix coeff - convert S15.16 to S4.9 */ + val = ((data->csc_mv[0] >> matrix_shift) & 0x1FFF) | + (((data->csc_mv[1] >> matrix_shift) & 0x1FFF) << 16); + DPU_REG_WRITE(c, csc_reg_off, val); + val = ((data->csc_mv[2] >> matrix_shift) & 0x1FFF) | + (((data->csc_mv[3] >> matrix_shift) & 0x1FFF) << 16); + DPU_REG_WRITE(c, csc_reg_off + 0x4, val); + val = ((data->csc_mv[4] >> matrix_shift) & 0x1FFF) | + (((data->csc_mv[5] >> matrix_shift) & 0x1FFF) << 16); + DPU_REG_WRITE(c, csc_reg_off + 0x8, val); + val = ((data->csc_mv[6] >> matrix_shift) & 0x1FFF) | + (((data->csc_mv[7] >> matrix_shift) & 0x1FFF) << 16); + DPU_REG_WRITE(c, csc_reg_off + 0xc, val); + val = (data->csc_mv[8] >> matrix_shift) & 0x1FFF; + DPU_REG_WRITE(c, csc_reg_off + 0x10, val); + + /* Pre clamp */ + val = (data->csc_pre_lv[0] << clamp_shift) | data->csc_pre_lv[1]; + DPU_REG_WRITE(c, csc_reg_off + 0x14, val); + val = (data->csc_pre_lv[2] << clamp_shift) | data->csc_pre_lv[3]; + DPU_REG_WRITE(c, csc_reg_off + 0x18, val); + val = (data->csc_pre_lv[4] << clamp_shift) | data->csc_pre_lv[5]; + DPU_REG_WRITE(c, csc_reg_off + 0x1c, val); + + /* Post clamp */ + val = (data->csc_post_lv[0] << clamp_shift) | data->csc_post_lv[1]; + DPU_REG_WRITE(c, csc_reg_off + 0x20, val); + val = (data->csc_post_lv[2] << clamp_shift) | data->csc_post_lv[3]; + DPU_REG_WRITE(c, csc_reg_off + 0x24, val); + val = (data->csc_post_lv[4] << clamp_shift) | data->csc_post_lv[5]; + DPU_REG_WRITE(c, csc_reg_off + 0x28, val); + + /* Pre-Bias */ + DPU_REG_WRITE(c, csc_reg_off + 0x2c, data->csc_pre_bv[0]); + DPU_REG_WRITE(c, csc_reg_off + 0x30, data->csc_pre_bv[1]); + DPU_REG_WRITE(c, csc_reg_off + 0x34, data->csc_pre_bv[2]); + + /* Post-Bias */ + DPU_REG_WRITE(c, csc_reg_off + 0x38, data->csc_post_bv[0]); + DPU_REG_WRITE(c, csc_reg_off + 0x3c, data->csc_post_bv[1]); + DPU_REG_WRITE(c, csc_reg_off + 0x40, data->csc_post_bv[2]); +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h new file mode 100644 index 000000000..234eb7d65 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h @@ -0,0 +1,323 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_UTIL_H +#define _DPU_HW_UTIL_H + +#include <linux/io.h> +#include <linux/slab.h> +#include "dpu_hw_mdss.h" + +#define REG_MASK(n) ((BIT(n)) - 1) + +/* + * This is the common struct maintained by each sub block + * for mapping the register offsets in this block to the + * absoulute IO address + * @base_off: mdp register mapped offset + * @blk_off: pipe offset relative to mdss offset + * @length length of register block offset + * @xin_id xin id + * @hwversion mdss hw version number + */ +struct dpu_hw_blk_reg_map { + void __iomem *base_off; + u32 blk_off; + u32 length; + u32 xin_id; + u32 hwversion; + u32 log_mask; +}; + +/** + * struct dpu_hw_scaler3_de_cfg : QSEEDv3 detail enhancer configuration + * @enable: detail enhancer enable/disable + * @sharpen_level1: sharpening strength for noise + * @sharpen_level2: sharpening strength for signal + * @ clip: clip shift + * @ limit: limit value + * @ thr_quiet: quiet threshold + * @ thr_dieout: dieout threshold + * @ thr_high: low threshold + * @ thr_high: high threshold + * @ prec_shift: precision shift + * @ adjust_a: A-coefficients for mapping curve + * @ adjust_b: B-coefficients for mapping curve + * @ adjust_c: C-coefficients for mapping curve + */ +struct dpu_hw_scaler3_de_cfg { + u32 enable; + int16_t sharpen_level1; + int16_t sharpen_level2; + uint16_t clip; + uint16_t limit; + uint16_t thr_quiet; + uint16_t thr_dieout; + uint16_t thr_low; + uint16_t thr_high; + uint16_t prec_shift; + int16_t adjust_a[DPU_MAX_DE_CURVES]; + int16_t adjust_b[DPU_MAX_DE_CURVES]; + int16_t adjust_c[DPU_MAX_DE_CURVES]; +}; + + +/** + * struct dpu_hw_scaler3_cfg : QSEEDv3 configuration + * @enable: scaler enable + * @dir_en: direction detection block enable + * @ init_phase_x: horizontal initial phase + * @ phase_step_x: horizontal phase step + * @ init_phase_y: vertical initial phase + * @ phase_step_y: vertical phase step + * @ preload_x: horizontal preload value + * @ preload_y: vertical preload value + * @ src_width: source width + * @ src_height: source height + * @ dst_width: destination width + * @ dst_height: destination height + * @ y_rgb_filter_cfg: y/rgb plane filter configuration + * @ uv_filter_cfg: uv plane filter configuration + * @ alpha_filter_cfg: alpha filter configuration + * @ blend_cfg: blend coefficients configuration + * @ lut_flag: scaler LUT update flags + * 0x1 swap LUT bank + * 0x2 update 2D filter LUT + * 0x4 update y circular filter LUT + * 0x8 update uv circular filter LUT + * 0x10 update y separable filter LUT + * 0x20 update uv separable filter LUT + * @ dir_lut_idx: 2D filter LUT index + * @ y_rgb_cir_lut_idx: y circular filter LUT index + * @ uv_cir_lut_idx: uv circular filter LUT index + * @ y_rgb_sep_lut_idx: y circular filter LUT index + * @ uv_sep_lut_idx: uv separable filter LUT index + * @ dir_lut: pointer to 2D LUT + * @ cir_lut: pointer to circular filter LUT + * @ sep_lut: pointer to separable filter LUT + * @ de: detail enhancer configuration + */ +struct dpu_hw_scaler3_cfg { + u32 enable; + u32 dir_en; + int32_t init_phase_x[DPU_MAX_PLANES]; + int32_t phase_step_x[DPU_MAX_PLANES]; + int32_t init_phase_y[DPU_MAX_PLANES]; + int32_t phase_step_y[DPU_MAX_PLANES]; + + u32 preload_x[DPU_MAX_PLANES]; + u32 preload_y[DPU_MAX_PLANES]; + u32 src_width[DPU_MAX_PLANES]; + u32 src_height[DPU_MAX_PLANES]; + + u32 dst_width; + u32 dst_height; + + u32 y_rgb_filter_cfg; + u32 uv_filter_cfg; + u32 alpha_filter_cfg; + u32 blend_cfg; + + u32 lut_flag; + u32 dir_lut_idx; + + u32 y_rgb_cir_lut_idx; + u32 uv_cir_lut_idx; + u32 y_rgb_sep_lut_idx; + u32 uv_sep_lut_idx; + u32 *dir_lut; + size_t dir_len; + u32 *cir_lut; + size_t cir_len; + u32 *sep_lut; + size_t sep_len; + + /* + * Detail enhancer settings + */ + struct dpu_hw_scaler3_de_cfg de; +}; + +/** + * struct dpu_drm_pix_ext_v1 - version 1 of pixel ext structure + * @num_ext_pxls_lr: Number of total horizontal pixels + * @num_ext_pxls_tb: Number of total vertical lines + * @left_ftch: Number of extra pixels to overfetch from left + * @right_ftch: Number of extra pixels to overfetch from right + * @top_ftch: Number of extra lines to overfetch from top + * @btm_ftch: Number of extra lines to overfetch from bottom + * @left_rpt: Number of extra pixels to repeat from left + * @right_rpt: Number of extra pixels to repeat from right + * @top_rpt: Number of extra lines to repeat from top + * @btm_rpt: Number of extra lines to repeat from bottom + */ +struct dpu_drm_pix_ext_v1 { + /* + * Number of pixels ext in left, right, top and bottom direction + * for all color components. + */ + int32_t num_ext_pxls_lr[DPU_MAX_PLANES]; + int32_t num_ext_pxls_tb[DPU_MAX_PLANES]; + + /* + * Number of pixels needs to be overfetched in left, right, top + * and bottom directions from source image for scaling. + */ + int32_t left_ftch[DPU_MAX_PLANES]; + int32_t right_ftch[DPU_MAX_PLANES]; + int32_t top_ftch[DPU_MAX_PLANES]; + int32_t btm_ftch[DPU_MAX_PLANES]; + /* + * Number of pixels needs to be repeated in left, right, top and + * bottom directions for scaling. + */ + int32_t left_rpt[DPU_MAX_PLANES]; + int32_t right_rpt[DPU_MAX_PLANES]; + int32_t top_rpt[DPU_MAX_PLANES]; + int32_t btm_rpt[DPU_MAX_PLANES]; + +}; + +/** + * struct dpu_drm_de_v1 - version 1 of detail enhancer structure + * @enable: Enables/disables detail enhancer + * @sharpen_level1: Sharpening strength for noise + * @sharpen_level2: Sharpening strength for context + * @clip: Clip coefficient + * @limit: Detail enhancer limit factor + * @thr_quiet: Quite zone threshold + * @thr_dieout: Die-out zone threshold + * @thr_low: Linear zone left threshold + * @thr_high: Linear zone right threshold + * @prec_shift: Detail enhancer precision + * @adjust_a: Mapping curves A coefficients + * @adjust_b: Mapping curves B coefficients + * @adjust_c: Mapping curves C coefficients + */ +struct dpu_drm_de_v1 { + uint32_t enable; + int16_t sharpen_level1; + int16_t sharpen_level2; + uint16_t clip; + uint16_t limit; + uint16_t thr_quiet; + uint16_t thr_dieout; + uint16_t thr_low; + uint16_t thr_high; + uint16_t prec_shift; + int16_t adjust_a[DPU_MAX_DE_CURVES]; + int16_t adjust_b[DPU_MAX_DE_CURVES]; + int16_t adjust_c[DPU_MAX_DE_CURVES]; +}; + +/** + * struct dpu_drm_scaler_v2 - version 2 of struct dpu_drm_scaler + * @enable: Scaler enable + * @dir_en: Detail enhancer enable + * @pe: Pixel extension settings + * @horz_decimate: Horizontal decimation factor + * @vert_decimate: Vertical decimation factor + * @init_phase_x: Initial scaler phase values for x + * @phase_step_x: Phase step values for x + * @init_phase_y: Initial scaler phase values for y + * @phase_step_y: Phase step values for y + * @preload_x: Horizontal preload value + * @preload_y: Vertical preload value + * @src_width: Source width + * @src_height: Source height + * @dst_width: Destination width + * @dst_height: Destination height + * @y_rgb_filter_cfg: Y/RGB plane filter configuration + * @uv_filter_cfg: UV plane filter configuration + * @alpha_filter_cfg: Alpha filter configuration + * @blend_cfg: Selection of blend coefficients + * @lut_flag: LUT configuration flags + * @dir_lut_idx: 2d 4x4 LUT index + * @y_rgb_cir_lut_idx: Y/RGB circular LUT index + * @uv_cir_lut_idx: UV circular LUT index + * @y_rgb_sep_lut_idx: Y/RGB separable LUT index + * @uv_sep_lut_idx: UV separable LUT index + * @de: Detail enhancer settings + */ +struct dpu_drm_scaler_v2 { + /* + * General definitions + */ + uint32_t enable; + uint32_t dir_en; + + /* + * Pix ext settings + */ + struct dpu_drm_pix_ext_v1 pe; + + /* + * Decimation settings + */ + uint32_t horz_decimate; + uint32_t vert_decimate; + + /* + * Phase settings + */ + int32_t init_phase_x[DPU_MAX_PLANES]; + int32_t phase_step_x[DPU_MAX_PLANES]; + int32_t init_phase_y[DPU_MAX_PLANES]; + int32_t phase_step_y[DPU_MAX_PLANES]; + + uint32_t preload_x[DPU_MAX_PLANES]; + uint32_t preload_y[DPU_MAX_PLANES]; + uint32_t src_width[DPU_MAX_PLANES]; + uint32_t src_height[DPU_MAX_PLANES]; + + uint32_t dst_width; + uint32_t dst_height; + + uint32_t y_rgb_filter_cfg; + uint32_t uv_filter_cfg; + uint32_t alpha_filter_cfg; + uint32_t blend_cfg; + + uint32_t lut_flag; + uint32_t dir_lut_idx; + + /* for Y(RGB) and UV planes*/ + uint32_t y_rgb_cir_lut_idx; + uint32_t uv_cir_lut_idx; + uint32_t y_rgb_sep_lut_idx; + uint32_t uv_sep_lut_idx; + + /* + * Detail enhancer settings + */ + struct dpu_drm_de_v1 de; +}; + + +u32 *dpu_hw_util_get_log_mask_ptr(void); + +void dpu_reg_write(struct dpu_hw_blk_reg_map *c, + u32 reg_off, + u32 val, + const char *name); +int dpu_reg_read(struct dpu_hw_blk_reg_map *c, u32 reg_off); + +#define DPU_REG_WRITE(c, off, val) dpu_reg_write(c, off, val, #off) +#define DPU_REG_READ(c, off) dpu_reg_read(c, off) + +void *dpu_hw_util_get_dir(void); + +void dpu_hw_setup_scaler3(struct dpu_hw_blk_reg_map *c, + struct dpu_hw_scaler3_cfg *scaler3_cfg, + u32 scaler_offset, u32 scaler_version, + const struct dpu_format *format); + +u32 dpu_hw_get_scaler3_ver(struct dpu_hw_blk_reg_map *c, + u32 scaler_offset); + +void dpu_hw_csc_setup(struct dpu_hw_blk_reg_map *c, + u32 csc_reg_off, + struct dpu_csc_cfg *data, bool csc10); + +#endif /* _DPU_HW_UTIL_H */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_vbif.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_vbif.c new file mode 100644 index 000000000..cf867f3f7 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_vbif.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#include "dpu_hwio.h" +#include "dpu_hw_catalog.h" +#include "dpu_hw_vbif.h" + +#define VBIF_VERSION 0x0000 +#define VBIF_CLK_FORCE_CTRL0 0x0008 +#define VBIF_CLK_FORCE_CTRL1 0x000C +#define VBIF_QOS_REMAP_00 0x0020 +#define VBIF_QOS_REMAP_01 0x0024 +#define VBIF_QOS_REMAP_10 0x0028 +#define VBIF_QOS_REMAP_11 0x002C +#define VBIF_WRITE_GATHER_EN 0x00AC +#define VBIF_IN_RD_LIM_CONF0 0x00B0 +#define VBIF_IN_RD_LIM_CONF1 0x00B4 +#define VBIF_IN_RD_LIM_CONF2 0x00B8 +#define VBIF_IN_WR_LIM_CONF0 0x00C0 +#define VBIF_IN_WR_LIM_CONF1 0x00C4 +#define VBIF_IN_WR_LIM_CONF2 0x00C8 +#define VBIF_OUT_RD_LIM_CONF0 0x00D0 +#define VBIF_OUT_WR_LIM_CONF0 0x00D4 +#define VBIF_OUT_AXI_AMEMTYPE_CONF0 0x0160 +#define VBIF_OUT_AXI_AMEMTYPE_CONF1 0x0164 +#define VBIF_XIN_PND_ERR 0x0190 +#define VBIF_XIN_SRC_ERR 0x0194 +#define VBIF_XIN_CLR_ERR 0x019C +#define VBIF_XIN_HALT_CTRL0 0x0200 +#define VBIF_XIN_HALT_CTRL1 0x0204 +#define VBIF_XINL_QOS_RP_REMAP_000 0x0550 +#define VBIF_XINL_QOS_LVL_REMAP_000 0x0590 + +static void dpu_hw_clear_errors(struct dpu_hw_vbif *vbif, + u32 *pnd_errors, u32 *src_errors) +{ + struct dpu_hw_blk_reg_map *c; + u32 pnd, src; + + if (!vbif) + return; + c = &vbif->hw; + pnd = DPU_REG_READ(c, VBIF_XIN_PND_ERR); + src = DPU_REG_READ(c, VBIF_XIN_SRC_ERR); + + if (pnd_errors) + *pnd_errors = pnd; + if (src_errors) + *src_errors = src; + + DPU_REG_WRITE(c, VBIF_XIN_CLR_ERR, pnd | src); +} + +static void dpu_hw_set_mem_type(struct dpu_hw_vbif *vbif, + u32 xin_id, u32 value) +{ + struct dpu_hw_blk_reg_map *c; + u32 reg_off; + u32 bit_off; + u32 reg_val; + + /* + * Assume 4 bits per bit field, 8 fields per 32-bit register so + * 16 bit fields maximum across two registers + */ + if (!vbif || xin_id >= MAX_XIN_COUNT || xin_id >= 16) + return; + + c = &vbif->hw; + + if (xin_id >= 8) { + xin_id -= 8; + reg_off = VBIF_OUT_AXI_AMEMTYPE_CONF1; + } else { + reg_off = VBIF_OUT_AXI_AMEMTYPE_CONF0; + } + bit_off = (xin_id & 0x7) * 4; + reg_val = DPU_REG_READ(c, reg_off); + reg_val &= ~(0x7 << bit_off); + reg_val |= (value & 0x7) << bit_off; + DPU_REG_WRITE(c, reg_off, reg_val); +} + +static void dpu_hw_set_limit_conf(struct dpu_hw_vbif *vbif, + u32 xin_id, bool rd, u32 limit) +{ + struct dpu_hw_blk_reg_map *c = &vbif->hw; + u32 reg_val; + u32 reg_off; + u32 bit_off; + + if (rd) + reg_off = VBIF_IN_RD_LIM_CONF0; + else + reg_off = VBIF_IN_WR_LIM_CONF0; + + reg_off += (xin_id / 4) * 4; + bit_off = (xin_id % 4) * 8; + reg_val = DPU_REG_READ(c, reg_off); + reg_val &= ~(0xFF << bit_off); + reg_val |= (limit) << bit_off; + DPU_REG_WRITE(c, reg_off, reg_val); +} + +static u32 dpu_hw_get_limit_conf(struct dpu_hw_vbif *vbif, + u32 xin_id, bool rd) +{ + struct dpu_hw_blk_reg_map *c = &vbif->hw; + u32 reg_val; + u32 reg_off; + u32 bit_off; + u32 limit; + + if (rd) + reg_off = VBIF_IN_RD_LIM_CONF0; + else + reg_off = VBIF_IN_WR_LIM_CONF0; + + reg_off += (xin_id / 4) * 4; + bit_off = (xin_id % 4) * 8; + reg_val = DPU_REG_READ(c, reg_off); + limit = (reg_val >> bit_off) & 0xFF; + + return limit; +} + +static void dpu_hw_set_halt_ctrl(struct dpu_hw_vbif *vbif, + u32 xin_id, bool enable) +{ + struct dpu_hw_blk_reg_map *c = &vbif->hw; + u32 reg_val; + + reg_val = DPU_REG_READ(c, VBIF_XIN_HALT_CTRL0); + + if (enable) + reg_val |= BIT(xin_id); + else + reg_val &= ~BIT(xin_id); + + DPU_REG_WRITE(c, VBIF_XIN_HALT_CTRL0, reg_val); +} + +static bool dpu_hw_get_halt_ctrl(struct dpu_hw_vbif *vbif, + u32 xin_id) +{ + struct dpu_hw_blk_reg_map *c = &vbif->hw; + u32 reg_val; + + reg_val = DPU_REG_READ(c, VBIF_XIN_HALT_CTRL1); + + return (reg_val & BIT(xin_id)) ? true : false; +} + +static void dpu_hw_set_qos_remap(struct dpu_hw_vbif *vbif, + u32 xin_id, u32 level, u32 remap_level) +{ + struct dpu_hw_blk_reg_map *c; + u32 reg_val, reg_val_lvl, mask, reg_high, reg_shift; + + if (!vbif) + return; + + c = &vbif->hw; + + reg_high = ((xin_id & 0x8) >> 3) * 4 + (level * 8); + reg_shift = (xin_id & 0x7) * 4; + + reg_val = DPU_REG_READ(c, VBIF_XINL_QOS_RP_REMAP_000 + reg_high); + reg_val_lvl = DPU_REG_READ(c, VBIF_XINL_QOS_LVL_REMAP_000 + reg_high); + + mask = 0x7 << reg_shift; + + reg_val &= ~mask; + reg_val |= (remap_level << reg_shift) & mask; + + reg_val_lvl &= ~mask; + reg_val_lvl |= (remap_level << reg_shift) & mask; + + DPU_REG_WRITE(c, VBIF_XINL_QOS_RP_REMAP_000 + reg_high, reg_val); + DPU_REG_WRITE(c, VBIF_XINL_QOS_LVL_REMAP_000 + reg_high, reg_val_lvl); +} + +static void dpu_hw_set_write_gather_en(struct dpu_hw_vbif *vbif, u32 xin_id) +{ + struct dpu_hw_blk_reg_map *c; + u32 reg_val; + + if (!vbif || xin_id >= MAX_XIN_COUNT) + return; + + c = &vbif->hw; + + reg_val = DPU_REG_READ(c, VBIF_WRITE_GATHER_EN); + reg_val |= BIT(xin_id); + DPU_REG_WRITE(c, VBIF_WRITE_GATHER_EN, reg_val); +} + +static void _setup_vbif_ops(struct dpu_hw_vbif_ops *ops, + unsigned long cap) +{ + ops->set_limit_conf = dpu_hw_set_limit_conf; + ops->get_limit_conf = dpu_hw_get_limit_conf; + ops->set_halt_ctrl = dpu_hw_set_halt_ctrl; + ops->get_halt_ctrl = dpu_hw_get_halt_ctrl; + if (test_bit(DPU_VBIF_QOS_REMAP, &cap)) + ops->set_qos_remap = dpu_hw_set_qos_remap; + ops->set_mem_type = dpu_hw_set_mem_type; + ops->clear_errors = dpu_hw_clear_errors; + ops->set_write_gather_en = dpu_hw_set_write_gather_en; +} + +static const struct dpu_vbif_cfg *_top_offset(enum dpu_vbif vbif, + const struct dpu_mdss_cfg *m, + void __iomem *addr, + struct dpu_hw_blk_reg_map *b) +{ + int i; + + for (i = 0; i < m->vbif_count; i++) { + if (vbif == m->vbif[i].id) { + b->base_off = addr; + b->blk_off = m->vbif[i].base; + b->length = m->vbif[i].len; + b->hwversion = m->hwversion; + b->log_mask = DPU_DBG_MASK_VBIF; + return &m->vbif[i]; + } + } + + return ERR_PTR(-EINVAL); +} + +struct dpu_hw_vbif *dpu_hw_vbif_init(enum dpu_vbif idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m) +{ + struct dpu_hw_vbif *c; + const struct dpu_vbif_cfg *cfg; + + c = kzalloc(sizeof(*c), GFP_KERNEL); + if (!c) + return ERR_PTR(-ENOMEM); + + cfg = _top_offset(idx, m, addr, &c->hw); + if (IS_ERR_OR_NULL(cfg)) { + kfree(c); + return ERR_PTR(-EINVAL); + } + + /* + * Assign ops + */ + c->idx = idx; + c->cap = cfg; + _setup_vbif_ops(&c->ops, c->cap->features); + + /* no need to register sub-range in dpu dbg, dump entire vbif io base */ + + return c; +} + +void dpu_hw_vbif_destroy(struct dpu_hw_vbif *vbif) +{ + kfree(vbif); +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_vbif.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_vbif.h new file mode 100644 index 000000000..6417aa28d --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_vbif.h @@ -0,0 +1,120 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HW_VBIF_H +#define _DPU_HW_VBIF_H + +#include "dpu_hw_catalog.h" +#include "dpu_hw_mdss.h" +#include "dpu_hw_util.h" + +struct dpu_hw_vbif; + +/** + * struct dpu_hw_vbif_ops : Interface to the VBIF hardware driver functions + * Assumption is these functions will be called after clocks are enabled + */ +struct dpu_hw_vbif_ops { + /** + * set_limit_conf - set transaction limit config + * @vbif: vbif context driver + * @xin_id: client interface identifier + * @rd: true for read limit; false for write limit + * @limit: outstanding transaction limit + */ + void (*set_limit_conf)(struct dpu_hw_vbif *vbif, + u32 xin_id, bool rd, u32 limit); + + /** + * get_limit_conf - get transaction limit config + * @vbif: vbif context driver + * @xin_id: client interface identifier + * @rd: true for read limit; false for write limit + * @return: outstanding transaction limit + */ + u32 (*get_limit_conf)(struct dpu_hw_vbif *vbif, + u32 xin_id, bool rd); + + /** + * set_halt_ctrl - set halt control + * @vbif: vbif context driver + * @xin_id: client interface identifier + * @enable: halt control enable + */ + void (*set_halt_ctrl)(struct dpu_hw_vbif *vbif, + u32 xin_id, bool enable); + + /** + * get_halt_ctrl - get halt control + * @vbif: vbif context driver + * @xin_id: client interface identifier + * @return: halt control enable + */ + bool (*get_halt_ctrl)(struct dpu_hw_vbif *vbif, + u32 xin_id); + + /** + * set_qos_remap - set QoS priority remap + * @vbif: vbif context driver + * @xin_id: client interface identifier + * @level: priority level + * @remap_level: remapped level + */ + void (*set_qos_remap)(struct dpu_hw_vbif *vbif, + u32 xin_id, u32 level, u32 remap_level); + + /** + * set_mem_type - set memory type + * @vbif: vbif context driver + * @xin_id: client interface identifier + * @value: memory type value + */ + void (*set_mem_type)(struct dpu_hw_vbif *vbif, + u32 xin_id, u32 value); + + /** + * clear_errors - clear any vbif errors + * This function clears any detected pending/source errors + * on the VBIF interface, and optionally returns the detected + * error mask(s). + * @vbif: vbif context driver + * @pnd_errors: pointer to pending error reporting variable + * @src_errors: pointer to source error reporting variable + */ + void (*clear_errors)(struct dpu_hw_vbif *vbif, + u32 *pnd_errors, u32 *src_errors); + + /** + * set_write_gather_en - set write_gather enable + * @vbif: vbif context driver + * @xin_id: client interface identifier + */ + void (*set_write_gather_en)(struct dpu_hw_vbif *vbif, u32 xin_id); +}; + +struct dpu_hw_vbif { + /* base */ + struct dpu_hw_blk_reg_map hw; + + /* vbif */ + enum dpu_vbif idx; + const struct dpu_vbif_cfg *cap; + + /* ops */ + struct dpu_hw_vbif_ops ops; +}; + +/** + * dpu_hw_vbif_init - initializes the vbif driver for the passed interface idx + * @idx: Interface index for which driver object is required + * @addr: Mapped register io address of MDSS + * @m: Pointer to mdss catalog data + */ +struct dpu_hw_vbif *dpu_hw_vbif_init(enum dpu_vbif idx, + void __iomem *addr, + const struct dpu_mdss_cfg *m); + +void dpu_hw_vbif_destroy(struct dpu_hw_vbif *vbif); + +#endif /*_DPU_HW_VBIF_H */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hwio.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_hwio.h new file mode 100644 index 000000000..c8156ed4b --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hwio.h @@ -0,0 +1,48 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef _DPU_HWIO_H +#define _DPU_HWIO_H + +#include "dpu_hw_util.h" + +/** + * MDP TOP block Register and bit fields and defines + */ +#define DISP_INTF_SEL 0x004 +#define INTR_EN 0x010 +#define INTR_STATUS 0x014 +#define INTR_CLEAR 0x018 +#define INTR2_EN 0x008 +#define INTR2_STATUS 0x00c +#define INTR2_CLEAR 0x02c +#define HIST_INTR_EN 0x01c +#define HIST_INTR_STATUS 0x020 +#define HIST_INTR_CLEAR 0x024 +#define INTF_INTR_EN 0x1C0 +#define INTF_INTR_STATUS 0x1C4 +#define INTF_INTR_CLEAR 0x1C8 +#define SPLIT_DISPLAY_EN 0x2F4 +#define SPLIT_DISPLAY_UPPER_PIPE_CTRL 0x2F8 +#define DSPP_IGC_COLOR0_RAM_LUTN 0x300 +#define DSPP_IGC_COLOR1_RAM_LUTN 0x304 +#define DSPP_IGC_COLOR2_RAM_LUTN 0x308 +#define HW_EVENTS_CTL 0x37C +#define CLK_CTRL3 0x3A8 +#define CLK_STATUS3 0x3AC +#define CLK_CTRL4 0x3B0 +#define CLK_STATUS4 0x3B4 +#define CLK_CTRL5 0x3B8 +#define CLK_STATUS5 0x3BC +#define CLK_CTRL7 0x3D0 +#define CLK_STATUS7 0x3D4 +#define SPLIT_DISPLAY_LOWER_PIPE_CTRL 0x3F0 +#define SPLIT_DISPLAY_TE_LINE_INTERVAL 0x3F4 +#define INTF_SW_RESET_MASK 0x3FC +#define HDMI_DP_CORE_SELECT 0x408 +#define MDP_OUT_CTL_0 0x410 +#define MDP_VSYNC_SEL 0x414 +#define DCE_SEL 0x450 + +#endif /*_DPU_HWIO_H */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.c new file mode 100644 index 000000000..078afc5f5 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.c @@ -0,0 +1,187 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2012-2015, 2017-2018, The Linux Foundation. + * All rights reserved. + */ + +#include <linux/clk.h> +#include <linux/clk/clk-conf.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/of.h> + +#include <drm/drm_print.h> + +#include "dpu_io_util.h" + +void msm_dss_put_clk(struct dss_clk *clk_arry, int num_clk) +{ + int i; + + for (i = num_clk - 1; i >= 0; i--) { + if (clk_arry[i].clk) + clk_put(clk_arry[i].clk); + clk_arry[i].clk = NULL; + } +} + +int msm_dss_get_clk(struct device *dev, struct dss_clk *clk_arry, int num_clk) +{ + int i, rc = 0; + + for (i = 0; i < num_clk; i++) { + clk_arry[i].clk = clk_get(dev, clk_arry[i].clk_name); + rc = PTR_ERR_OR_ZERO(clk_arry[i].clk); + if (rc) { + DEV_ERR("%pS->%s: '%s' get failed. rc=%d\n", + __builtin_return_address(0), __func__, + clk_arry[i].clk_name, rc); + goto error; + } + } + + return rc; + +error: + for (i--; i >= 0; i--) { + if (clk_arry[i].clk) + clk_put(clk_arry[i].clk); + clk_arry[i].clk = NULL; + } + + return rc; +} + +int msm_dss_clk_set_rate(struct dss_clk *clk_arry, int num_clk) +{ + int i, rc = 0; + + for (i = 0; i < num_clk; i++) { + if (clk_arry[i].clk) { + if (clk_arry[i].type != DSS_CLK_AHB) { + DEV_DBG("%pS->%s: '%s' rate %ld\n", + __builtin_return_address(0), __func__, + clk_arry[i].clk_name, + clk_arry[i].rate); + rc = clk_set_rate(clk_arry[i].clk, + clk_arry[i].rate); + if (rc) { + DEV_ERR("%pS->%s: %s failed. rc=%d\n", + __builtin_return_address(0), + __func__, + clk_arry[i].clk_name, rc); + break; + } + } + } else { + DEV_ERR("%pS->%s: '%s' is not available\n", + __builtin_return_address(0), __func__, + clk_arry[i].clk_name); + rc = -EPERM; + break; + } + } + + return rc; +} + +int msm_dss_enable_clk(struct dss_clk *clk_arry, int num_clk, int enable) +{ + int i, rc = 0; + + if (enable) { + for (i = 0; i < num_clk; i++) { + DEV_DBG("%pS->%s: enable '%s'\n", + __builtin_return_address(0), __func__, + clk_arry[i].clk_name); + rc = clk_prepare_enable(clk_arry[i].clk); + if (rc) + DEV_ERR("%pS->%s: %s en fail. rc=%d\n", + __builtin_return_address(0), + __func__, + clk_arry[i].clk_name, rc); + + if (rc && i) { + msm_dss_enable_clk(&clk_arry[i - 1], + i - 1, false); + break; + } + } + } else { + for (i = num_clk - 1; i >= 0; i--) { + DEV_DBG("%pS->%s: disable '%s'\n", + __builtin_return_address(0), __func__, + clk_arry[i].clk_name); + + clk_disable_unprepare(clk_arry[i].clk); + } + } + + return rc; +} + +int msm_dss_parse_clock(struct platform_device *pdev, + struct dss_module_power *mp) +{ + u32 i, rc = 0; + const char *clock_name; + int num_clk = 0; + + if (!pdev || !mp) + return -EINVAL; + + mp->num_clk = 0; + num_clk = of_property_count_strings(pdev->dev.of_node, "clock-names"); + if (num_clk <= 0) { + pr_debug("clocks are not defined\n"); + return 0; + } + + mp->clk_config = devm_kcalloc(&pdev->dev, + num_clk, sizeof(struct dss_clk), + GFP_KERNEL); + if (!mp->clk_config) + return -ENOMEM; + + for (i = 0; i < num_clk; i++) { + rc = of_property_read_string_index(pdev->dev.of_node, + "clock-names", i, + &clock_name); + if (rc) { + DRM_DEV_ERROR(&pdev->dev, "Failed to get clock name for %d\n", + i); + break; + } + strlcpy(mp->clk_config[i].clk_name, clock_name, + sizeof(mp->clk_config[i].clk_name)); + + mp->clk_config[i].type = DSS_CLK_AHB; + } + + rc = msm_dss_get_clk(&pdev->dev, mp->clk_config, num_clk); + if (rc) { + DRM_DEV_ERROR(&pdev->dev, "Failed to get clock refs %d\n", rc); + goto err; + } + + rc = of_clk_set_defaults(pdev->dev.of_node, false); + if (rc) { + DRM_DEV_ERROR(&pdev->dev, "Failed to set clock defaults %d\n", rc); + goto err; + } + + for (i = 0; i < num_clk; i++) { + u32 rate = clk_get_rate(mp->clk_config[i].clk); + if (!rate) + continue; + mp->clk_config[i].rate = rate; + mp->clk_config[i].type = DSS_CLK_PCLK; + mp->clk_config[i].max_rate = rate; + } + + mp->num_clk = num_clk; + return 0; + +err: + msm_dss_put_clk(mp->clk_config, num_clk); + return rc; +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.h new file mode 100644 index 000000000..e6b5c772f --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_io_util.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2012, 2017-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef __DPU_IO_UTIL_H__ +#define __DPU_IO_UTIL_H__ + +#include <linux/platform_device.h> +#include <linux/types.h> + +#define DEV_DBG(fmt, args...) pr_debug(fmt, ##args) +#define DEV_INFO(fmt, args...) pr_info(fmt, ##args) +#define DEV_WARN(fmt, args...) pr_warn(fmt, ##args) +#define DEV_ERR(fmt, args...) pr_err(fmt, ##args) + +enum dss_clk_type { + DSS_CLK_AHB, /* no set rate. rate controlled through rpm */ + DSS_CLK_PCLK, +}; + +struct dss_clk { + struct clk *clk; /* clk handle */ + char clk_name[32]; + enum dss_clk_type type; + unsigned long rate; + unsigned long max_rate; +}; + +struct dss_module_power { + unsigned int num_clk; + struct dss_clk *clk_config; +}; + +int msm_dss_get_clk(struct device *dev, struct dss_clk *clk_arry, int num_clk); +void msm_dss_put_clk(struct dss_clk *clk_arry, int num_clk); +int msm_dss_clk_set_rate(struct dss_clk *clk_arry, int num_clk); +int msm_dss_enable_clk(struct dss_clk *clk_arry, int num_clk, int enable); +int msm_dss_parse_clock(struct platform_device *pdev, + struct dss_module_power *mp); +#endif /* __DPU_IO_UTIL_H__ */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c new file mode 100644 index 000000000..b7841f7fc --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.c @@ -0,0 +1,1249 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ + +#include <linux/debugfs.h> +#include <linux/dma-buf.h> +#include <linux/of_irq.h> +#include <linux/pm_opp.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_file.h> + +#include "msm_drv.h" +#include "msm_mmu.h" +#include "msm_gem.h" + +#include "dpu_kms.h" +#include "dpu_core_irq.h" +#include "dpu_formats.h" +#include "dpu_hw_vbif.h" +#include "dpu_vbif.h" +#include "dpu_encoder.h" +#include "dpu_plane.h" +#include "dpu_crtc.h" + +#define CREATE_TRACE_POINTS +#include "dpu_trace.h" + +/* + * To enable overall DRM driver logging + * # echo 0x2 > /sys/module/drm/parameters/debug + * + * To enable DRM driver h/w logging + * # echo <mask> > /sys/kernel/debug/dri/0/debug/hw_log_mask + * + * See dpu_hw_mdss.h for h/w logging mask definitions (search for DPU_DBG_MASK_) + */ +#define DPU_DEBUGFS_DIR "msm_dpu" +#define DPU_DEBUGFS_HWMASKNAME "hw_log_mask" + +#define MIN_IB_BW 400000000ULL /* Min ib vote 400MB */ + +static int dpu_kms_hw_init(struct msm_kms *kms); +static void _dpu_kms_mmu_destroy(struct dpu_kms *dpu_kms); + +#ifdef CONFIG_DEBUG_FS +static int _dpu_danger_signal_status(struct seq_file *s, + bool danger_status) +{ + struct dpu_kms *kms = (struct dpu_kms *)s->private; + struct dpu_danger_safe_status status; + int i; + + if (!kms->hw_mdp) { + DPU_ERROR("invalid arg(s)\n"); + return 0; + } + + memset(&status, 0, sizeof(struct dpu_danger_safe_status)); + + pm_runtime_get_sync(&kms->pdev->dev); + if (danger_status) { + seq_puts(s, "\nDanger signal status:\n"); + if (kms->hw_mdp->ops.get_danger_status) + kms->hw_mdp->ops.get_danger_status(kms->hw_mdp, + &status); + } else { + seq_puts(s, "\nSafe signal status:\n"); + if (kms->hw_mdp->ops.get_safe_status) + kms->hw_mdp->ops.get_safe_status(kms->hw_mdp, + &status); + } + pm_runtime_put_sync(&kms->pdev->dev); + + seq_printf(s, "MDP : 0x%x\n", status.mdp); + + for (i = SSPP_VIG0; i < SSPP_MAX; i++) + seq_printf(s, "SSPP%d : 0x%x \t", i - SSPP_VIG0, + status.sspp[i]); + seq_puts(s, "\n"); + + return 0; +} + +static int dpu_debugfs_danger_stats_show(struct seq_file *s, void *v) +{ + return _dpu_danger_signal_status(s, true); +} +DEFINE_SHOW_ATTRIBUTE(dpu_debugfs_danger_stats); + +static int dpu_debugfs_safe_stats_show(struct seq_file *s, void *v) +{ + return _dpu_danger_signal_status(s, false); +} +DEFINE_SHOW_ATTRIBUTE(dpu_debugfs_safe_stats); + +static void dpu_debugfs_danger_init(struct dpu_kms *dpu_kms, + struct dentry *parent) +{ + struct dentry *entry = debugfs_create_dir("danger", parent); + + debugfs_create_file("danger_status", 0600, entry, + dpu_kms, &dpu_debugfs_danger_stats_fops); + debugfs_create_file("safe_status", 0600, entry, + dpu_kms, &dpu_debugfs_safe_stats_fops); +} + +static int _dpu_debugfs_show_regset32(struct seq_file *s, void *data) +{ + struct dpu_debugfs_regset32 *regset = s->private; + struct dpu_kms *dpu_kms = regset->dpu_kms; + void __iomem *base; + uint32_t i, addr; + + if (!dpu_kms->mmio) + return 0; + + base = dpu_kms->mmio + regset->offset; + + /* insert padding spaces, if needed */ + if (regset->offset & 0xF) { + seq_printf(s, "[%x]", regset->offset & ~0xF); + for (i = 0; i < (regset->offset & 0xF); i += 4) + seq_puts(s, " "); + } + + pm_runtime_get_sync(&dpu_kms->pdev->dev); + + /* main register output */ + for (i = 0; i < regset->blk_len; i += 4) { + addr = regset->offset + i; + if ((addr & 0xF) == 0x0) + seq_printf(s, i ? "\n[%x]" : "[%x]", addr); + seq_printf(s, " %08x", readl_relaxed(base + i)); + } + seq_puts(s, "\n"); + pm_runtime_put_sync(&dpu_kms->pdev->dev); + + return 0; +} + +static int dpu_debugfs_open_regset32(struct inode *inode, + struct file *file) +{ + return single_open(file, _dpu_debugfs_show_regset32, inode->i_private); +} + +static const struct file_operations dpu_fops_regset32 = { + .open = dpu_debugfs_open_regset32, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +void dpu_debugfs_setup_regset32(struct dpu_debugfs_regset32 *regset, + uint32_t offset, uint32_t length, struct dpu_kms *dpu_kms) +{ + if (regset) { + regset->offset = offset; + regset->blk_len = length; + regset->dpu_kms = dpu_kms; + } +} + +void dpu_debugfs_create_regset32(const char *name, umode_t mode, + void *parent, struct dpu_debugfs_regset32 *regset) +{ + if (!name || !regset || !regset->dpu_kms || !regset->blk_len) + return; + + /* make sure offset is a multiple of 4 */ + regset->offset = round_down(regset->offset, 4); + + debugfs_create_file(name, mode, parent, regset, &dpu_fops_regset32); +} + +static int dpu_kms_debugfs_init(struct msm_kms *kms, struct drm_minor *minor) +{ + struct dpu_kms *dpu_kms = to_dpu_kms(kms); + void *p = dpu_hw_util_get_log_mask_ptr(); + struct dentry *entry; + struct drm_device *dev; + struct msm_drm_private *priv; + + if (!p) + return -EINVAL; + + dev = dpu_kms->dev; + priv = dev->dev_private; + + entry = debugfs_create_dir("debug", minor->debugfs_root); + + debugfs_create_x32(DPU_DEBUGFS_HWMASKNAME, 0600, entry, p); + + dpu_debugfs_danger_init(dpu_kms, entry); + dpu_debugfs_vbif_init(dpu_kms, entry); + dpu_debugfs_core_irq_init(dpu_kms, entry); + + if (priv->dp) + msm_dp_debugfs_init(priv->dp, minor); + + return dpu_core_perf_debugfs_init(dpu_kms, entry); +} +#endif + +/* Global/shared object state funcs */ + +/* + * This is a helper that returns the private state currently in operation. + * Note that this would return the "old_state" if called in the atomic check + * path, and the "new_state" after the atomic swap has been done. + */ +struct dpu_global_state * +dpu_kms_get_existing_global_state(struct dpu_kms *dpu_kms) +{ + return to_dpu_global_state(dpu_kms->global_state.state); +} + +/* + * This acquires the modeset lock set aside for global state, creates + * a new duplicated private object state. + */ +struct dpu_global_state *dpu_kms_get_global_state(struct drm_atomic_state *s) +{ + struct msm_drm_private *priv = s->dev->dev_private; + struct dpu_kms *dpu_kms = to_dpu_kms(priv->kms); + struct drm_private_state *priv_state; + int ret; + + ret = drm_modeset_lock(&dpu_kms->global_state_lock, s->acquire_ctx); + if (ret) + return ERR_PTR(ret); + + priv_state = drm_atomic_get_private_obj_state(s, + &dpu_kms->global_state); + if (IS_ERR(priv_state)) + return ERR_CAST(priv_state); + + return to_dpu_global_state(priv_state); +} + +static struct drm_private_state * +dpu_kms_global_duplicate_state(struct drm_private_obj *obj) +{ + struct dpu_global_state *state; + + state = kmemdup(obj->state, sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base); + + return &state->base; +} + +static void dpu_kms_global_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) +{ + struct dpu_global_state *dpu_state = to_dpu_global_state(state); + + kfree(dpu_state); +} + +static const struct drm_private_state_funcs dpu_kms_global_state_funcs = { + .atomic_duplicate_state = dpu_kms_global_duplicate_state, + .atomic_destroy_state = dpu_kms_global_destroy_state, +}; + +static int dpu_kms_global_obj_init(struct dpu_kms *dpu_kms) +{ + struct dpu_global_state *state; + + drm_modeset_lock_init(&dpu_kms->global_state_lock); + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + drm_atomic_private_obj_init(dpu_kms->dev, &dpu_kms->global_state, + &state->base, + &dpu_kms_global_state_funcs); + return 0; +} + +static int dpu_kms_parse_data_bus_icc_path(struct dpu_kms *dpu_kms) +{ + struct icc_path *path0; + struct icc_path *path1; + struct drm_device *dev = dpu_kms->dev; + + path0 = of_icc_get(dev->dev, "mdp0-mem"); + path1 = of_icc_get(dev->dev, "mdp1-mem"); + + if (IS_ERR_OR_NULL(path0)) + return PTR_ERR_OR_ZERO(path0); + + dpu_kms->path[0] = path0; + dpu_kms->num_paths = 1; + + if (!IS_ERR_OR_NULL(path1)) { + dpu_kms->path[1] = path1; + dpu_kms->num_paths++; + } + return 0; +} + +static int dpu_kms_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc) +{ + return dpu_crtc_vblank(crtc, true); +} + +static void dpu_kms_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc) +{ + dpu_crtc_vblank(crtc, false); +} + +static void dpu_kms_enable_commit(struct msm_kms *kms) +{ + struct dpu_kms *dpu_kms = to_dpu_kms(kms); + pm_runtime_get_sync(&dpu_kms->pdev->dev); +} + +static void dpu_kms_disable_commit(struct msm_kms *kms) +{ + struct dpu_kms *dpu_kms = to_dpu_kms(kms); + pm_runtime_put_sync(&dpu_kms->pdev->dev); +} + +static ktime_t dpu_kms_vsync_time(struct msm_kms *kms, struct drm_crtc *crtc) +{ + struct drm_encoder *encoder; + + drm_for_each_encoder_mask(encoder, crtc->dev, crtc->state->encoder_mask) { + ktime_t vsync_time; + + if (dpu_encoder_vsync_time(encoder, &vsync_time) == 0) + return vsync_time; + } + + return ktime_get(); +} + +static void dpu_kms_prepare_commit(struct msm_kms *kms, + struct drm_atomic_state *state) +{ + struct drm_crtc *crtc; + struct drm_crtc_state *crtc_state; + struct drm_encoder *encoder; + int i; + + if (!kms) + return; + + /* Call prepare_commit for all affected encoders */ + for_each_new_crtc_in_state(state, crtc, crtc_state, i) { + drm_for_each_encoder_mask(encoder, crtc->dev, + crtc_state->encoder_mask) { + dpu_encoder_prepare_commit(encoder); + } + } +} + +static void dpu_kms_flush_commit(struct msm_kms *kms, unsigned crtc_mask) +{ + struct dpu_kms *dpu_kms = to_dpu_kms(kms); + struct drm_crtc *crtc; + + for_each_crtc_mask(dpu_kms->dev, crtc, crtc_mask) { + if (!crtc->state->active) + continue; + + trace_dpu_kms_commit(DRMID(crtc)); + dpu_crtc_commit_kickoff(crtc); + } +} + +/* + * Override the encoder enable since we need to setup the inline rotator and do + * some crtc magic before enabling any bridge that might be present. + */ +void dpu_kms_encoder_enable(struct drm_encoder *encoder) +{ + const struct drm_encoder_helper_funcs *funcs = encoder->helper_private; + struct drm_device *dev = encoder->dev; + struct drm_crtc *crtc; + + /* Forward this enable call to the commit hook */ + if (funcs && funcs->commit) + funcs->commit(encoder); + + drm_for_each_crtc(crtc, dev) { + if (!(crtc->state->encoder_mask & drm_encoder_mask(encoder))) + continue; + + trace_dpu_kms_enc_enable(DRMID(crtc)); + } +} + +static void dpu_kms_complete_commit(struct msm_kms *kms, unsigned crtc_mask) +{ + struct dpu_kms *dpu_kms = to_dpu_kms(kms); + struct drm_crtc *crtc; + + DPU_ATRACE_BEGIN("kms_complete_commit"); + + for_each_crtc_mask(dpu_kms->dev, crtc, crtc_mask) + dpu_crtc_complete_commit(crtc); + + DPU_ATRACE_END("kms_complete_commit"); +} + +static void dpu_kms_wait_for_commit_done(struct msm_kms *kms, + struct drm_crtc *crtc) +{ + struct drm_encoder *encoder; + struct drm_device *dev; + int ret; + + if (!kms || !crtc || !crtc->state) { + DPU_ERROR("invalid params\n"); + return; + } + + dev = crtc->dev; + + if (!crtc->state->enable) { + DPU_DEBUG("[crtc:%d] not enable\n", crtc->base.id); + return; + } + + if (!crtc->state->active) { + DPU_DEBUG("[crtc:%d] not active\n", crtc->base.id); + return; + } + + list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) { + if (encoder->crtc != crtc) + continue; + /* + * Wait for post-flush if necessary to delay before + * plane_cleanup. For example, wait for vsync in case of video + * mode panels. This may be a no-op for command mode panels. + */ + trace_dpu_kms_wait_for_commit_done(DRMID(crtc)); + ret = dpu_encoder_wait_for_event(encoder, MSM_ENC_COMMIT_DONE); + if (ret && ret != -EWOULDBLOCK) { + DPU_ERROR("wait for commit done returned %d\n", ret); + break; + } + } +} + +static void dpu_kms_wait_flush(struct msm_kms *kms, unsigned crtc_mask) +{ + struct dpu_kms *dpu_kms = to_dpu_kms(kms); + struct drm_crtc *crtc; + + for_each_crtc_mask(dpu_kms->dev, crtc, crtc_mask) + dpu_kms_wait_for_commit_done(kms, crtc); +} + +static int _dpu_kms_initialize_dsi(struct drm_device *dev, + struct msm_drm_private *priv, + struct dpu_kms *dpu_kms) +{ + struct drm_encoder *encoder = NULL; + int i, rc = 0; + + if (!(priv->dsi[0] || priv->dsi[1])) + return rc; + + /*TODO: Support two independent DSI connectors */ + encoder = dpu_encoder_init(dev, DRM_MODE_ENCODER_DSI); + if (IS_ERR(encoder)) { + DPU_ERROR("encoder init failed for dsi display\n"); + return PTR_ERR(encoder); + } + + priv->encoders[priv->num_encoders++] = encoder; + + for (i = 0; i < ARRAY_SIZE(priv->dsi); i++) { + if (!priv->dsi[i]) + continue; + + rc = msm_dsi_modeset_init(priv->dsi[i], dev, encoder); + if (rc) { + DPU_ERROR("modeset_init failed for dsi[%d], rc = %d\n", + i, rc); + break; + } + } + + return rc; +} + +static int _dpu_kms_initialize_displayport(struct drm_device *dev, + struct msm_drm_private *priv, + struct dpu_kms *dpu_kms) +{ + struct drm_encoder *encoder = NULL; + int rc = 0; + + if (!priv->dp) + return rc; + + encoder = dpu_encoder_init(dev, DRM_MODE_ENCODER_TMDS); + if (IS_ERR(encoder)) { + DPU_ERROR("encoder init failed for dsi display\n"); + return PTR_ERR(encoder); + } + + rc = msm_dp_modeset_init(priv->dp, dev, encoder); + if (rc) { + DPU_ERROR("modeset_init failed for DP, rc = %d\n", rc); + drm_encoder_cleanup(encoder); + return rc; + } + + priv->encoders[priv->num_encoders++] = encoder; + return rc; +} + +/** + * _dpu_kms_setup_displays - create encoders, bridges and connectors + * for underlying displays + * @dev: Pointer to drm device structure + * @priv: Pointer to private drm device data + * @dpu_kms: Pointer to dpu kms structure + * Returns: Zero on success + */ +static int _dpu_kms_setup_displays(struct drm_device *dev, + struct msm_drm_private *priv, + struct dpu_kms *dpu_kms) +{ + int rc = 0; + + rc = _dpu_kms_initialize_dsi(dev, priv, dpu_kms); + if (rc) { + DPU_ERROR("initialize_dsi failed, rc = %d\n", rc); + return rc; + } + + rc = _dpu_kms_initialize_displayport(dev, priv, dpu_kms); + if (rc) { + DPU_ERROR("initialize_DP failed, rc = %d\n", rc); + return rc; + } + + return rc; +} + +static void _dpu_kms_drm_obj_destroy(struct dpu_kms *dpu_kms) +{ + struct msm_drm_private *priv; + int i; + + priv = dpu_kms->dev->dev_private; + + for (i = 0; i < priv->num_crtcs; i++) + priv->crtcs[i]->funcs->destroy(priv->crtcs[i]); + priv->num_crtcs = 0; + + for (i = 0; i < priv->num_planes; i++) + priv->planes[i]->funcs->destroy(priv->planes[i]); + priv->num_planes = 0; + + for (i = 0; i < priv->num_connectors; i++) + priv->connectors[i]->funcs->destroy(priv->connectors[i]); + priv->num_connectors = 0; + + for (i = 0; i < priv->num_encoders; i++) + priv->encoders[i]->funcs->destroy(priv->encoders[i]); + priv->num_encoders = 0; +} + +static int _dpu_kms_drm_obj_init(struct dpu_kms *dpu_kms) +{ + struct drm_device *dev; + struct drm_plane *primary_planes[MAX_PLANES], *plane; + struct drm_plane *cursor_planes[MAX_PLANES] = { NULL }; + struct drm_crtc *crtc; + + struct msm_drm_private *priv; + struct dpu_mdss_cfg *catalog; + + int primary_planes_idx = 0, cursor_planes_idx = 0, i, ret; + int max_crtc_count; + dev = dpu_kms->dev; + priv = dev->dev_private; + catalog = dpu_kms->catalog; + + /* + * Create encoder and query display drivers to create + * bridges and connectors + */ + ret = _dpu_kms_setup_displays(dev, priv, dpu_kms); + if (ret) + goto fail; + + max_crtc_count = min(catalog->mixer_count, priv->num_encoders); + + /* Create the planes, keeping track of one primary/cursor per crtc */ + for (i = 0; i < catalog->sspp_count; i++) { + enum drm_plane_type type; + + if ((catalog->sspp[i].features & BIT(DPU_SSPP_CURSOR)) + && cursor_planes_idx < max_crtc_count) + type = DRM_PLANE_TYPE_CURSOR; + else if (primary_planes_idx < max_crtc_count) + type = DRM_PLANE_TYPE_PRIMARY; + else + type = DRM_PLANE_TYPE_OVERLAY; + + DPU_DEBUG("Create plane type %d with features %lx (cur %lx)\n", + type, catalog->sspp[i].features, + catalog->sspp[i].features & BIT(DPU_SSPP_CURSOR)); + + plane = dpu_plane_init(dev, catalog->sspp[i].id, type, + (1UL << max_crtc_count) - 1, 0); + if (IS_ERR(plane)) { + DPU_ERROR("dpu_plane_init failed\n"); + ret = PTR_ERR(plane); + goto fail; + } + priv->planes[priv->num_planes++] = plane; + + if (type == DRM_PLANE_TYPE_CURSOR) + cursor_planes[cursor_planes_idx++] = plane; + else if (type == DRM_PLANE_TYPE_PRIMARY) + primary_planes[primary_planes_idx++] = plane; + } + + max_crtc_count = min(max_crtc_count, primary_planes_idx); + + /* Create one CRTC per encoder */ + for (i = 0; i < max_crtc_count; i++) { + crtc = dpu_crtc_init(dev, primary_planes[i], cursor_planes[i]); + if (IS_ERR(crtc)) { + ret = PTR_ERR(crtc); + goto fail; + } + priv->crtcs[priv->num_crtcs++] = crtc; + } + + /* All CRTCs are compatible with all encoders */ + for (i = 0; i < priv->num_encoders; i++) + priv->encoders[i]->possible_crtcs = (1 << priv->num_crtcs) - 1; + + return 0; +fail: + _dpu_kms_drm_obj_destroy(dpu_kms); + return ret; +} + +static long dpu_kms_round_pixclk(struct msm_kms *kms, unsigned long rate, + struct drm_encoder *encoder) +{ + return rate; +} + +static void _dpu_kms_hw_destroy(struct dpu_kms *dpu_kms) +{ + int i; + + if (dpu_kms->hw_intr) + dpu_hw_intr_destroy(dpu_kms->hw_intr); + dpu_kms->hw_intr = NULL; + + /* safe to call these more than once during shutdown */ + _dpu_kms_mmu_destroy(dpu_kms); + + if (dpu_kms->catalog) { + for (i = 0; i < ARRAY_SIZE(dpu_kms->hw_vbif); i++) { + if (dpu_kms->hw_vbif[i]) { + dpu_hw_vbif_destroy(dpu_kms->hw_vbif[i]); + dpu_kms->hw_vbif[i] = NULL; + } + } + } + + if (dpu_kms->rm_init) + dpu_rm_destroy(&dpu_kms->rm); + dpu_kms->rm_init = false; + + if (dpu_kms->catalog) + dpu_hw_catalog_deinit(dpu_kms->catalog); + dpu_kms->catalog = NULL; + + if (dpu_kms->vbif[VBIF_NRT]) + devm_iounmap(&dpu_kms->pdev->dev, dpu_kms->vbif[VBIF_NRT]); + dpu_kms->vbif[VBIF_NRT] = NULL; + + if (dpu_kms->vbif[VBIF_RT]) + devm_iounmap(&dpu_kms->pdev->dev, dpu_kms->vbif[VBIF_RT]); + dpu_kms->vbif[VBIF_RT] = NULL; + + if (dpu_kms->hw_mdp) + dpu_hw_mdp_destroy(dpu_kms->hw_mdp); + dpu_kms->hw_mdp = NULL; + + if (dpu_kms->mmio) + devm_iounmap(&dpu_kms->pdev->dev, dpu_kms->mmio); + dpu_kms->mmio = NULL; +} + +static void dpu_kms_destroy(struct msm_kms *kms) +{ + struct dpu_kms *dpu_kms; + + if (!kms) { + DPU_ERROR("invalid kms\n"); + return; + } + + dpu_kms = to_dpu_kms(kms); + + _dpu_kms_hw_destroy(dpu_kms); +} + +static void _dpu_kms_set_encoder_mode(struct msm_kms *kms, + struct drm_encoder *encoder, + bool cmd_mode) +{ + struct msm_display_info info; + struct msm_drm_private *priv = encoder->dev->dev_private; + int i, rc = 0; + + memset(&info, 0, sizeof(info)); + + info.intf_type = encoder->encoder_type; + info.capabilities = cmd_mode ? MSM_DISPLAY_CAP_CMD_MODE : + MSM_DISPLAY_CAP_VID_MODE; + + switch (info.intf_type) { + case DRM_MODE_ENCODER_DSI: + /* TODO: No support for DSI swap */ + for (i = 0; i < ARRAY_SIZE(priv->dsi); i++) { + if (priv->dsi[i]) { + info.h_tile_instance[info.num_of_h_tiles] = i; + info.num_of_h_tiles++; + } + } + break; + case DRM_MODE_ENCODER_TMDS: + info.num_of_h_tiles = 1; + break; + }; + + rc = dpu_encoder_setup(encoder->dev, encoder, &info); + if (rc) + DPU_ERROR("failed to setup DPU encoder %d: rc:%d\n", + encoder->base.id, rc); +} + +static irqreturn_t dpu_irq(struct msm_kms *kms) +{ + struct dpu_kms *dpu_kms = to_dpu_kms(kms); + + return dpu_core_irq(dpu_kms); +} + +static void dpu_irq_preinstall(struct msm_kms *kms) +{ + struct dpu_kms *dpu_kms = to_dpu_kms(kms); + + dpu_core_irq_preinstall(dpu_kms); +} + +static int dpu_irq_postinstall(struct msm_kms *kms) +{ + struct msm_drm_private *priv; + struct dpu_kms *dpu_kms = to_dpu_kms(kms); + + if (!dpu_kms || !dpu_kms->dev) + return -EINVAL; + + priv = dpu_kms->dev->dev_private; + if (!priv) + return -EINVAL; + + msm_dp_irq_postinstall(priv->dp); + + return 0; +} + +static void dpu_irq_uninstall(struct msm_kms *kms) +{ + struct dpu_kms *dpu_kms = to_dpu_kms(kms); + + dpu_core_irq_uninstall(dpu_kms); +} + +static const struct msm_kms_funcs kms_funcs = { + .hw_init = dpu_kms_hw_init, + .irq_preinstall = dpu_irq_preinstall, + .irq_postinstall = dpu_irq_postinstall, + .irq_uninstall = dpu_irq_uninstall, + .irq = dpu_irq, + .enable_commit = dpu_kms_enable_commit, + .disable_commit = dpu_kms_disable_commit, + .vsync_time = dpu_kms_vsync_time, + .prepare_commit = dpu_kms_prepare_commit, + .flush_commit = dpu_kms_flush_commit, + .wait_flush = dpu_kms_wait_flush, + .complete_commit = dpu_kms_complete_commit, + .enable_vblank = dpu_kms_enable_vblank, + .disable_vblank = dpu_kms_disable_vblank, + .check_modified_format = dpu_format_check_modified_format, + .get_format = dpu_get_msm_format, + .round_pixclk = dpu_kms_round_pixclk, + .destroy = dpu_kms_destroy, + .set_encoder_mode = _dpu_kms_set_encoder_mode, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = dpu_kms_debugfs_init, +#endif +}; + +static void _dpu_kms_mmu_destroy(struct dpu_kms *dpu_kms) +{ + struct msm_mmu *mmu; + + if (!dpu_kms->base.aspace) + return; + + mmu = dpu_kms->base.aspace->mmu; + + mmu->funcs->detach(mmu); + msm_gem_address_space_put(dpu_kms->base.aspace); + + dpu_kms->base.aspace = NULL; +} + +static int _dpu_kms_mmu_init(struct dpu_kms *dpu_kms) +{ + struct iommu_domain *domain; + struct msm_gem_address_space *aspace; + struct msm_mmu *mmu; + + domain = iommu_domain_alloc(&platform_bus_type); + if (!domain) + return 0; + + mmu = msm_iommu_new(dpu_kms->dev->dev, domain); + if (IS_ERR(mmu)) { + iommu_domain_free(domain); + return PTR_ERR(mmu); + } + aspace = msm_gem_address_space_create(mmu, "dpu1", + 0x1000, 0x100000000 - 0x1000); + + if (IS_ERR(aspace)) { + mmu->funcs->destroy(mmu); + return PTR_ERR(aspace); + } + + dpu_kms->base.aspace = aspace; + return 0; +} + +static struct dss_clk *_dpu_kms_get_clk(struct dpu_kms *dpu_kms, + char *clock_name) +{ + struct dss_module_power *mp = &dpu_kms->mp; + int i; + + for (i = 0; i < mp->num_clk; i++) { + if (!strcmp(mp->clk_config[i].clk_name, clock_name)) + return &mp->clk_config[i]; + } + + return NULL; +} + +u64 dpu_kms_get_clk_rate(struct dpu_kms *dpu_kms, char *clock_name) +{ + struct dss_clk *clk; + + clk = _dpu_kms_get_clk(dpu_kms, clock_name); + if (!clk) + return -EINVAL; + + return clk_get_rate(clk->clk); +} + +static int dpu_kms_hw_init(struct msm_kms *kms) +{ + struct dpu_kms *dpu_kms; + struct drm_device *dev; + int i, rc = -EINVAL; + + if (!kms) { + DPU_ERROR("invalid kms\n"); + return rc; + } + + dpu_kms = to_dpu_kms(kms); + dev = dpu_kms->dev; + + rc = dpu_kms_global_obj_init(dpu_kms); + if (rc) + return rc; + + atomic_set(&dpu_kms->bandwidth_ref, 0); + + dpu_kms->mmio = msm_ioremap(dpu_kms->pdev, "mdp", "mdp"); + if (IS_ERR(dpu_kms->mmio)) { + rc = PTR_ERR(dpu_kms->mmio); + DPU_ERROR("mdp register memory map failed: %d\n", rc); + dpu_kms->mmio = NULL; + goto error; + } + DRM_DEBUG("mapped dpu address space @%pK\n", dpu_kms->mmio); + + dpu_kms->vbif[VBIF_RT] = msm_ioremap(dpu_kms->pdev, "vbif", "vbif"); + if (IS_ERR(dpu_kms->vbif[VBIF_RT])) { + rc = PTR_ERR(dpu_kms->vbif[VBIF_RT]); + DPU_ERROR("vbif register memory map failed: %d\n", rc); + dpu_kms->vbif[VBIF_RT] = NULL; + goto error; + } + dpu_kms->vbif[VBIF_NRT] = msm_ioremap_quiet(dpu_kms->pdev, "vbif_nrt", "vbif_nrt"); + if (IS_ERR(dpu_kms->vbif[VBIF_NRT])) { + dpu_kms->vbif[VBIF_NRT] = NULL; + DPU_DEBUG("VBIF NRT is not defined"); + } + + dpu_kms->reg_dma = msm_ioremap_quiet(dpu_kms->pdev, "regdma", "regdma"); + if (IS_ERR(dpu_kms->reg_dma)) { + dpu_kms->reg_dma = NULL; + DPU_DEBUG("REG_DMA is not defined"); + } + + dpu_kms_parse_data_bus_icc_path(dpu_kms); + + rc = pm_runtime_resume_and_get(&dpu_kms->pdev->dev); + if (rc < 0) + goto error; + + dpu_kms->core_rev = readl_relaxed(dpu_kms->mmio + 0x0); + + pr_info("dpu hardware revision:0x%x\n", dpu_kms->core_rev); + + dpu_kms->catalog = dpu_hw_catalog_init(dpu_kms->core_rev); + if (IS_ERR_OR_NULL(dpu_kms->catalog)) { + rc = PTR_ERR(dpu_kms->catalog); + if (!dpu_kms->catalog) + rc = -EINVAL; + DPU_ERROR("catalog init failed: %d\n", rc); + dpu_kms->catalog = NULL; + goto power_error; + } + + /* + * Now we need to read the HW catalog and initialize resources such as + * clocks, regulators, GDSC/MMAGIC, ioremap the register ranges etc + */ + rc = _dpu_kms_mmu_init(dpu_kms); + if (rc) { + DPU_ERROR("dpu_kms_mmu_init failed: %d\n", rc); + goto power_error; + } + + rc = dpu_rm_init(&dpu_kms->rm, dpu_kms->catalog, dpu_kms->mmio); + if (rc) { + DPU_ERROR("rm init failed: %d\n", rc); + goto power_error; + } + + dpu_kms->rm_init = true; + + dpu_kms->hw_mdp = dpu_hw_mdptop_init(MDP_TOP, dpu_kms->mmio, + dpu_kms->catalog); + if (IS_ERR(dpu_kms->hw_mdp)) { + rc = PTR_ERR(dpu_kms->hw_mdp); + DPU_ERROR("failed to get hw_mdp: %d\n", rc); + dpu_kms->hw_mdp = NULL; + goto power_error; + } + + for (i = 0; i < dpu_kms->catalog->vbif_count; i++) { + u32 vbif_idx = dpu_kms->catalog->vbif[i].id; + + dpu_kms->hw_vbif[vbif_idx] = dpu_hw_vbif_init(vbif_idx, + dpu_kms->vbif[vbif_idx], dpu_kms->catalog); + if (IS_ERR_OR_NULL(dpu_kms->hw_vbif[vbif_idx])) { + rc = PTR_ERR(dpu_kms->hw_vbif[vbif_idx]); + if (!dpu_kms->hw_vbif[vbif_idx]) + rc = -EINVAL; + DPU_ERROR("failed to init vbif %d: %d\n", vbif_idx, rc); + dpu_kms->hw_vbif[vbif_idx] = NULL; + goto power_error; + } + } + + rc = dpu_core_perf_init(&dpu_kms->perf, dev, dpu_kms->catalog, + _dpu_kms_get_clk(dpu_kms, "core")); + if (rc) { + DPU_ERROR("failed to init perf %d\n", rc); + goto perf_err; + } + + dpu_kms->hw_intr = dpu_hw_intr_init(dpu_kms->mmio, dpu_kms->catalog); + if (IS_ERR_OR_NULL(dpu_kms->hw_intr)) { + rc = PTR_ERR(dpu_kms->hw_intr); + DPU_ERROR("hw_intr init failed: %d\n", rc); + dpu_kms->hw_intr = NULL; + goto hw_intr_init_err; + } + + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + + /* + * max crtc width is equal to the max mixer width * 2 and max height is + * is 4K + */ + dev->mode_config.max_width = + dpu_kms->catalog->caps->max_mixer_width * 2; + dev->mode_config.max_height = 4096; + + /* + * Support format modifiers for compression etc. + */ + dev->mode_config.allow_fb_modifiers = true; + + /* + * _dpu_kms_drm_obj_init should create the DRM related objects + * i.e. CRTCs, planes, encoders, connectors and so forth + */ + rc = _dpu_kms_drm_obj_init(dpu_kms); + if (rc) { + DPU_ERROR("modeset init failed: %d\n", rc); + goto drm_obj_init_err; + } + + dpu_vbif_init_memtypes(dpu_kms); + + pm_runtime_put_sync(&dpu_kms->pdev->dev); + + return 0; + +drm_obj_init_err: + dpu_core_perf_destroy(&dpu_kms->perf); +hw_intr_init_err: +perf_err: +power_error: + pm_runtime_put_sync(&dpu_kms->pdev->dev); +error: + _dpu_kms_hw_destroy(dpu_kms); + + return rc; +} + +struct msm_kms *dpu_kms_init(struct drm_device *dev) +{ + struct msm_drm_private *priv; + struct dpu_kms *dpu_kms; + int irq; + + if (!dev) { + DPU_ERROR("drm device node invalid\n"); + return ERR_PTR(-EINVAL); + } + + priv = dev->dev_private; + dpu_kms = to_dpu_kms(priv->kms); + + irq = irq_of_parse_and_map(dpu_kms->pdev->dev.of_node, 0); + if (irq < 0) { + DPU_ERROR("failed to get irq: %d\n", irq); + return ERR_PTR(irq); + } + dpu_kms->base.irq = irq; + + return &dpu_kms->base; +} + +static int dpu_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *ddev = dev_get_drvdata(master); + struct platform_device *pdev = to_platform_device(dev); + struct msm_drm_private *priv = ddev->dev_private; + struct dpu_kms *dpu_kms; + struct dss_module_power *mp; + int ret = 0; + + dpu_kms = devm_kzalloc(&pdev->dev, sizeof(*dpu_kms), GFP_KERNEL); + if (!dpu_kms) + return -ENOMEM; + + dpu_kms->opp_table = dev_pm_opp_set_clkname(dev, "core"); + if (IS_ERR(dpu_kms->opp_table)) + return PTR_ERR(dpu_kms->opp_table); + /* OPP table is optional */ + ret = dev_pm_opp_of_add_table(dev); + if (!ret) { + dpu_kms->has_opp_table = true; + } else if (ret != -ENODEV) { + dev_err(dev, "invalid OPP table in device tree\n"); + dev_pm_opp_put_clkname(dpu_kms->opp_table); + return ret; + } + + mp = &dpu_kms->mp; + ret = msm_dss_parse_clock(pdev, mp); + if (ret) { + DPU_ERROR("failed to parse clocks, ret=%d\n", ret); + goto err; + } + + platform_set_drvdata(pdev, dpu_kms); + + msm_kms_init(&dpu_kms->base, &kms_funcs); + dpu_kms->dev = ddev; + dpu_kms->pdev = pdev; + + pm_runtime_enable(&pdev->dev); + dpu_kms->rpm_enabled = true; + + priv->kms = &dpu_kms->base; + return ret; +err: + if (dpu_kms->has_opp_table) + dev_pm_opp_of_remove_table(dev); + dev_pm_opp_put_clkname(dpu_kms->opp_table); + return ret; +} + +static void dpu_unbind(struct device *dev, struct device *master, void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + struct dpu_kms *dpu_kms = platform_get_drvdata(pdev); + struct dss_module_power *mp = &dpu_kms->mp; + + msm_dss_put_clk(mp->clk_config, mp->num_clk); + devm_kfree(&pdev->dev, mp->clk_config); + mp->num_clk = 0; + + if (dpu_kms->rpm_enabled) + pm_runtime_disable(&pdev->dev); + + if (dpu_kms->has_opp_table) + dev_pm_opp_of_remove_table(dev); + dev_pm_opp_put_clkname(dpu_kms->opp_table); +} + +static const struct component_ops dpu_ops = { + .bind = dpu_bind, + .unbind = dpu_unbind, +}; + +static int dpu_dev_probe(struct platform_device *pdev) +{ + return component_add(&pdev->dev, &dpu_ops); +} + +static int dpu_dev_remove(struct platform_device *pdev) +{ + component_del(&pdev->dev, &dpu_ops); + return 0; +} + +static int __maybe_unused dpu_runtime_suspend(struct device *dev) +{ + int i, rc = -1; + struct platform_device *pdev = to_platform_device(dev); + struct dpu_kms *dpu_kms = platform_get_drvdata(pdev); + struct dss_module_power *mp = &dpu_kms->mp; + + /* Drop the performance state vote */ + dev_pm_opp_set_rate(dev, 0); + rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, false); + if (rc) + DPU_ERROR("clock disable failed rc:%d\n", rc); + + for (i = 0; i < dpu_kms->num_paths; i++) + icc_set_bw(dpu_kms->path[i], 0, 0); + + return rc; +} + +static int __maybe_unused dpu_runtime_resume(struct device *dev) +{ + int rc = -1; + struct platform_device *pdev = to_platform_device(dev); + struct dpu_kms *dpu_kms = platform_get_drvdata(pdev); + struct drm_encoder *encoder; + struct drm_device *ddev; + struct dss_module_power *mp = &dpu_kms->mp; + int i; + + ddev = dpu_kms->dev; + + WARN_ON(!(dpu_kms->num_paths)); + /* Min vote of BW is required before turning on AXI clk */ + for (i = 0; i < dpu_kms->num_paths; i++) + icc_set_bw(dpu_kms->path[i], 0, Bps_to_icc(MIN_IB_BW)); + + rc = msm_dss_enable_clk(mp->clk_config, mp->num_clk, true); + if (rc) { + DPU_ERROR("clock enable failed rc:%d\n", rc); + return rc; + } + + dpu_vbif_init_memtypes(dpu_kms); + + drm_for_each_encoder(encoder, ddev) + dpu_encoder_virt_runtime_resume(encoder); + + return rc; +} + +static const struct dev_pm_ops dpu_pm_ops = { + SET_RUNTIME_PM_OPS(dpu_runtime_suspend, dpu_runtime_resume, NULL) + SET_SYSTEM_SLEEP_PM_OPS(pm_runtime_force_suspend, + pm_runtime_force_resume) +}; + +static const struct of_device_id dpu_dt_match[] = { + { .compatible = "qcom,sdm845-dpu", }, + { .compatible = "qcom,sc7180-dpu", }, + {} +}; +MODULE_DEVICE_TABLE(of, dpu_dt_match); + +static struct platform_driver dpu_driver = { + .probe = dpu_dev_probe, + .remove = dpu_dev_remove, + .driver = { + .name = "msm_dpu", + .of_match_table = dpu_dt_match, + .pm = &dpu_pm_ops, + }, +}; + +void __init msm_dpu_register(void) +{ + platform_driver_register(&dpu_driver); +} + +void __exit msm_dpu_unregister(void) +{ + platform_driver_unregister(&dpu_driver); +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.h new file mode 100644 index 000000000..1c0e4c0c9 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_kms.h @@ -0,0 +1,269 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#ifndef __DPU_KMS_H__ +#define __DPU_KMS_H__ + +#include <linux/interconnect.h> + +#include <drm/drm_drv.h> + +#include "msm_drv.h" +#include "msm_kms.h" +#include "msm_mmu.h" +#include "msm_gem.h" +#include "dpu_hw_catalog.h" +#include "dpu_hw_ctl.h" +#include "dpu_hw_lm.h" +#include "dpu_hw_interrupts.h" +#include "dpu_hw_top.h" +#include "dpu_io_util.h" +#include "dpu_rm.h" +#include "dpu_core_perf.h" + +#define DRMID(x) ((x) ? (x)->base.id : -1) + +/** + * DPU_DEBUG - macro for kms/plane/crtc/encoder/connector logs + * @fmt: Pointer to format string + */ +#define DPU_DEBUG(fmt, ...) \ + do { \ + if (drm_debug_enabled(DRM_UT_KMS)) \ + DRM_DEBUG(fmt, ##__VA_ARGS__); \ + else \ + pr_debug(fmt, ##__VA_ARGS__); \ + } while (0) + +/** + * DPU_DEBUG_DRIVER - macro for hardware driver logging + * @fmt: Pointer to format string + */ +#define DPU_DEBUG_DRIVER(fmt, ...) \ + do { \ + if (drm_debug_enabled(DRM_UT_DRIVER)) \ + DRM_ERROR(fmt, ##__VA_ARGS__); \ + else \ + pr_debug(fmt, ##__VA_ARGS__); \ + } while (0) + +#define DPU_ERROR(fmt, ...) pr_err("[dpu error]" fmt, ##__VA_ARGS__) + +/** + * ktime_compare_safe - compare two ktime structures + * This macro is similar to the standard ktime_compare() function, but + * attempts to also handle ktime overflows. + * @A: First ktime value + * @B: Second ktime value + * Returns: -1 if A < B, 0 if A == B, 1 if A > B + */ +#define ktime_compare_safe(A, B) \ + ktime_compare(ktime_sub((A), (B)), ktime_set(0, 0)) + +#define DPU_NAME_SIZE 12 + +/* + * struct dpu_irq_callback - IRQ callback handlers + * @list: list to callback + * @func: intr handler + * @arg: argument for the handler + */ +struct dpu_irq_callback { + struct list_head list; + void (*func)(void *arg, int irq_idx); + void *arg; +}; + +/** + * struct dpu_irq: IRQ structure contains callback registration info + * @total_irq: total number of irq_idx obtained from HW interrupts mapping + * @irq_cb_tbl: array of IRQ callbacks setting + * @enable_counts array of IRQ enable counts + * @cb_lock: callback lock + * @debugfs_file: debugfs file for irq statistics + */ +struct dpu_irq { + u32 total_irqs; + struct list_head *irq_cb_tbl; + atomic_t *enable_counts; + atomic_t *irq_counts; + spinlock_t cb_lock; +}; + +struct dpu_kms { + struct msm_kms base; + struct drm_device *dev; + int core_rev; + struct dpu_mdss_cfg *catalog; + + /* io/register spaces: */ + void __iomem *mmio, *vbif[VBIF_MAX], *reg_dma; + + struct regulator *vdd; + struct regulator *mmagic; + struct regulator *venus; + + struct dpu_hw_intr *hw_intr; + struct dpu_irq irq_obj; + + struct dpu_core_perf perf; + + /* + * Global private object state, Do not access directly, use + * dpu_kms_global_get_state() + */ + struct drm_modeset_lock global_state_lock; + struct drm_private_obj global_state; + + struct dpu_rm rm; + bool rm_init; + + struct dpu_hw_vbif *hw_vbif[VBIF_MAX]; + struct dpu_hw_mdp *hw_mdp; + + bool has_danger_ctrl; + + struct platform_device *pdev; + bool rpm_enabled; + + struct opp_table *opp_table; + bool has_opp_table; + + struct dss_module_power mp; + + /* reference count bandwidth requests, so we know when we can + * release bandwidth. Each atomic update increments, and frame- + * done event decrements. Additionally, for video mode, the + * reference is incremented when crtc is enabled, and decremented + * when disabled. + */ + atomic_t bandwidth_ref; + struct icc_path *path[2]; + u32 num_paths; +}; + +struct vsync_info { + u32 frame_count; + u32 line_count; +}; + +#define to_dpu_kms(x) container_of(x, struct dpu_kms, base) + +#define to_dpu_global_state(x) container_of(x, struct dpu_global_state, base) + +/* Global private object state for tracking resources that are shared across + * multiple kms objects (planes/crtcs/etc). + */ +struct dpu_global_state { + struct drm_private_state base; + + uint32_t pingpong_to_enc_id[PINGPONG_MAX - PINGPONG_0]; + uint32_t mixer_to_enc_id[LM_MAX - LM_0]; + uint32_t ctl_to_enc_id[CTL_MAX - CTL_0]; + uint32_t intf_to_enc_id[INTF_MAX - INTF_0]; + uint32_t dspp_to_enc_id[DSPP_MAX - DSPP_0]; +}; + +struct dpu_global_state + *dpu_kms_get_existing_global_state(struct dpu_kms *dpu_kms); +struct dpu_global_state + *__must_check dpu_kms_get_global_state(struct drm_atomic_state *s); + +/** + * Debugfs functions - extra helper functions for debugfs support + * + * Main debugfs documentation is located at, + * + * Documentation/filesystems/debugfs.rst + * + * @dpu_debugfs_setup_regset32: Initialize data for dpu_debugfs_create_regset32 + * @dpu_debugfs_create_regset32: Create 32-bit register dump file + * @dpu_debugfs_get_root: Get root dentry for DPU_KMS's debugfs node + */ + +/** + * Companion structure for dpu_debugfs_create_regset32. Do not initialize the + * members of this structure explicitly; use dpu_debugfs_setup_regset32 instead. + */ +struct dpu_debugfs_regset32 { + uint32_t offset; + uint32_t blk_len; + struct dpu_kms *dpu_kms; +}; + +/** + * dpu_debugfs_setup_regset32 - Initialize register block definition for debugfs + * This function is meant to initialize dpu_debugfs_regset32 structures for use + * with dpu_debugfs_create_regset32. + * @regset: opaque register definition structure + * @offset: sub-block offset + * @length: sub-block length, in bytes + * @dpu_kms: pointer to dpu kms structure + */ +void dpu_debugfs_setup_regset32(struct dpu_debugfs_regset32 *regset, + uint32_t offset, uint32_t length, struct dpu_kms *dpu_kms); + +/** + * dpu_debugfs_create_regset32 - Create register read back file for debugfs + * + * This function is almost identical to the standard debugfs_create_regset32() + * function, with the main difference being that a list of register + * names/offsets do not need to be provided. The 'read' function simply outputs + * sequential register values over a specified range. + * + * Similar to the related debugfs_create_regset32 API, the structure pointed to + * by regset needs to persist for the lifetime of the created file. The calling + * code is responsible for initialization/management of this structure. + * + * The structure pointed to by regset is meant to be opaque. Please use + * dpu_debugfs_setup_regset32 to initialize it. + * + * @name: File name within debugfs + * @mode: File mode within debugfs + * @parent: Parent directory entry within debugfs, can be NULL + * @regset: Pointer to persistent register block definition + */ +void dpu_debugfs_create_regset32(const char *name, umode_t mode, + void *parent, struct dpu_debugfs_regset32 *regset); + +/** + * dpu_debugfs_get_root - Return root directory entry for KMS's debugfs + * + * The return value should be passed as the 'parent' argument to subsequent + * debugfs create calls. + * + * @dpu_kms: Pointer to DPU's KMS structure + * + * Return: dentry pointer for DPU's debugfs location + */ +void *dpu_debugfs_get_root(struct dpu_kms *dpu_kms); + +/** + * DPU info management functions + * These functions/definitions allow for building up a 'dpu_info' structure + * containing one or more "key=value\n" entries. + */ +#define DPU_KMS_INFO_MAX_SIZE 4096 + +/** + * Vblank enable/disable functions + */ +int dpu_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc); +void dpu_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc); + +void dpu_kms_encoder_enable(struct drm_encoder *encoder); + +/** + * dpu_kms_get_clk_rate() - get the clock rate + * @dpu_kms: poiner to dpu_kms structure + * @clock_name: clock name to get the rate + * + * Return: current clock rate + */ +u64 dpu_kms_get_clk_rate(struct dpu_kms *dpu_kms, char *clock_name); + +#endif /* __dpu_kms_H__ */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_mdss.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_mdss.c new file mode 100644 index 000000000..96f3908e4 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_mdss.c @@ -0,0 +1,277 @@ +/* + * SPDX-License-Identifier: GPL-2.0 + * Copyright (c) 2018, The Linux Foundation + */ + +#include <linux/irq.h> +#include <linux/irqchip.h> +#include <linux/irqdesc.h> +#include <linux/irqchip/chained_irq.h> +#include "dpu_kms.h" + +#define to_dpu_mdss(x) container_of(x, struct dpu_mdss, base) + +#define HW_REV 0x0 +#define HW_INTR_STATUS 0x0010 + +#define UBWC_STATIC 0x144 +#define UBWC_CTRL_2 0x150 +#define UBWC_PREDICTION_MODE 0x154 + +/* Max BW defined in KBps */ +#define MAX_BW 6800000 + +struct dpu_irq_controller { + unsigned long enabled_mask; + struct irq_domain *domain; +}; + +struct dpu_mdss { + struct msm_mdss base; + void __iomem *mmio; + struct dss_module_power mp; + struct dpu_irq_controller irq_controller; +}; + +static void dpu_mdss_irq(struct irq_desc *desc) +{ + struct dpu_mdss *dpu_mdss = irq_desc_get_handler_data(desc); + struct irq_chip *chip = irq_desc_get_chip(desc); + u32 interrupts; + + chained_irq_enter(chip, desc); + + interrupts = readl_relaxed(dpu_mdss->mmio + HW_INTR_STATUS); + + while (interrupts) { + irq_hw_number_t hwirq = fls(interrupts) - 1; + unsigned int mapping; + int rc; + + mapping = irq_find_mapping(dpu_mdss->irq_controller.domain, + hwirq); + if (mapping == 0) { + DRM_ERROR("couldn't find irq mapping for %lu\n", hwirq); + break; + } + + rc = generic_handle_irq(mapping); + if (rc < 0) { + DRM_ERROR("handle irq fail: irq=%lu mapping=%u rc=%d\n", + hwirq, mapping, rc); + break; + } + + interrupts &= ~(1 << hwirq); + } + + chained_irq_exit(chip, desc); +} + +static void dpu_mdss_irq_mask(struct irq_data *irqd) +{ + struct dpu_mdss *dpu_mdss = irq_data_get_irq_chip_data(irqd); + + /* memory barrier */ + smp_mb__before_atomic(); + clear_bit(irqd->hwirq, &dpu_mdss->irq_controller.enabled_mask); + /* memory barrier */ + smp_mb__after_atomic(); +} + +static void dpu_mdss_irq_unmask(struct irq_data *irqd) +{ + struct dpu_mdss *dpu_mdss = irq_data_get_irq_chip_data(irqd); + + /* memory barrier */ + smp_mb__before_atomic(); + set_bit(irqd->hwirq, &dpu_mdss->irq_controller.enabled_mask); + /* memory barrier */ + smp_mb__after_atomic(); +} + +static struct irq_chip dpu_mdss_irq_chip = { + .name = "dpu_mdss", + .irq_mask = dpu_mdss_irq_mask, + .irq_unmask = dpu_mdss_irq_unmask, +}; + +static struct lock_class_key dpu_mdss_lock_key, dpu_mdss_request_key; + +static int dpu_mdss_irqdomain_map(struct irq_domain *domain, + unsigned int irq, irq_hw_number_t hwirq) +{ + struct dpu_mdss *dpu_mdss = domain->host_data; + + irq_set_lockdep_class(irq, &dpu_mdss_lock_key, &dpu_mdss_request_key); + irq_set_chip_and_handler(irq, &dpu_mdss_irq_chip, handle_level_irq); + return irq_set_chip_data(irq, dpu_mdss); +} + +static const struct irq_domain_ops dpu_mdss_irqdomain_ops = { + .map = dpu_mdss_irqdomain_map, + .xlate = irq_domain_xlate_onecell, +}; + +static int _dpu_mdss_irq_domain_add(struct dpu_mdss *dpu_mdss) +{ + struct device *dev; + struct irq_domain *domain; + + dev = dpu_mdss->base.dev->dev; + + domain = irq_domain_add_linear(dev->of_node, 32, + &dpu_mdss_irqdomain_ops, dpu_mdss); + if (!domain) { + DPU_ERROR("failed to add irq_domain\n"); + return -EINVAL; + } + + dpu_mdss->irq_controller.enabled_mask = 0; + dpu_mdss->irq_controller.domain = domain; + + return 0; +} + +static void _dpu_mdss_irq_domain_fini(struct dpu_mdss *dpu_mdss) +{ + if (dpu_mdss->irq_controller.domain) { + irq_domain_remove(dpu_mdss->irq_controller.domain); + dpu_mdss->irq_controller.domain = NULL; + } +} +static int dpu_mdss_enable(struct msm_mdss *mdss) +{ + struct dpu_mdss *dpu_mdss = to_dpu_mdss(mdss); + struct dss_module_power *mp = &dpu_mdss->mp; + int ret; + + ret = msm_dss_enable_clk(mp->clk_config, mp->num_clk, true); + if (ret) { + DPU_ERROR("clock enable failed, ret:%d\n", ret); + return ret; + } + + /* + * ubwc config is part of the "mdss" region which is not accessible + * from the rest of the driver. hardcode known configurations here + */ + switch (readl_relaxed(dpu_mdss->mmio + HW_REV)) { + case DPU_HW_VER_500: + case DPU_HW_VER_501: + writel_relaxed(0x420, dpu_mdss->mmio + UBWC_STATIC); + break; + case DPU_HW_VER_600: + /* TODO: 0x102e for LP_DDR4 */ + writel_relaxed(0x103e, dpu_mdss->mmio + UBWC_STATIC); + writel_relaxed(2, dpu_mdss->mmio + UBWC_CTRL_2); + writel_relaxed(1, dpu_mdss->mmio + UBWC_PREDICTION_MODE); + break; + case DPU_HW_VER_620: + writel_relaxed(0x1e, dpu_mdss->mmio + UBWC_STATIC); + break; + } + + return ret; +} + +static int dpu_mdss_disable(struct msm_mdss *mdss) +{ + struct dpu_mdss *dpu_mdss = to_dpu_mdss(mdss); + struct dss_module_power *mp = &dpu_mdss->mp; + int ret; + + ret = msm_dss_enable_clk(mp->clk_config, mp->num_clk, false); + if (ret) + DPU_ERROR("clock disable failed, ret:%d\n", ret); + + return ret; +} + +static void dpu_mdss_destroy(struct drm_device *dev) +{ + struct platform_device *pdev = to_platform_device(dev->dev); + struct msm_drm_private *priv = dev->dev_private; + struct dpu_mdss *dpu_mdss = to_dpu_mdss(priv->mdss); + struct dss_module_power *mp = &dpu_mdss->mp; + int irq; + + pm_runtime_suspend(dev->dev); + pm_runtime_disable(dev->dev); + _dpu_mdss_irq_domain_fini(dpu_mdss); + irq = platform_get_irq(pdev, 0); + irq_set_chained_handler_and_data(irq, NULL, NULL); + msm_dss_put_clk(mp->clk_config, mp->num_clk); + devm_kfree(&pdev->dev, mp->clk_config); + + if (dpu_mdss->mmio) + devm_iounmap(&pdev->dev, dpu_mdss->mmio); + dpu_mdss->mmio = NULL; + priv->mdss = NULL; +} + +static const struct msm_mdss_funcs mdss_funcs = { + .enable = dpu_mdss_enable, + .disable = dpu_mdss_disable, + .destroy = dpu_mdss_destroy, +}; + +int dpu_mdss_init(struct drm_device *dev) +{ + struct platform_device *pdev = to_platform_device(dev->dev); + struct msm_drm_private *priv = dev->dev_private; + struct dpu_mdss *dpu_mdss; + struct dss_module_power *mp; + int ret; + int irq; + + dpu_mdss = devm_kzalloc(dev->dev, sizeof(*dpu_mdss), GFP_KERNEL); + if (!dpu_mdss) + return -ENOMEM; + + dpu_mdss->mmio = msm_ioremap(pdev, "mdss", "mdss"); + if (IS_ERR(dpu_mdss->mmio)) + return PTR_ERR(dpu_mdss->mmio); + + DRM_DEBUG("mapped mdss address space @%pK\n", dpu_mdss->mmio); + + mp = &dpu_mdss->mp; + ret = msm_dss_parse_clock(pdev, mp); + if (ret) { + DPU_ERROR("failed to parse clocks, ret=%d\n", ret); + goto clk_parse_err; + } + + dpu_mdss->base.dev = dev; + dpu_mdss->base.funcs = &mdss_funcs; + + ret = _dpu_mdss_irq_domain_add(dpu_mdss); + if (ret) + goto irq_domain_error; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + goto irq_error; + } + + irq_set_chained_handler_and_data(irq, dpu_mdss_irq, + dpu_mdss); + + priv->mdss = &dpu_mdss->base; + + pm_runtime_enable(dev->dev); + + return 0; + +irq_error: + _dpu_mdss_irq_domain_fini(dpu_mdss); +irq_domain_error: + msm_dss_put_clk(mp->clk_config, mp->num_clk); +clk_parse_err: + devm_kfree(&pdev->dev, mp->clk_config); + if (dpu_mdss->mmio) + devm_iounmap(&pdev->dev, dpu_mdss->mmio); + dpu_mdss->mmio = NULL; + return ret; +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c new file mode 100644 index 000000000..8aa9f2335 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.c @@ -0,0 +1,1660 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014-2018 The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ + +#include <linux/debugfs.h> +#include <linux/dma-buf.h> + +#include <drm/drm_atomic_uapi.h> +#include <drm/drm_damage_helper.h> +#include <drm/drm_file.h> +#include <drm/drm_gem_framebuffer_helper.h> + +#include "msm_drv.h" +#include "dpu_kms.h" +#include "dpu_formats.h" +#include "dpu_hw_sspp.h" +#include "dpu_hw_catalog_format.h" +#include "dpu_trace.h" +#include "dpu_crtc.h" +#include "dpu_vbif.h" +#include "dpu_plane.h" + +#define DPU_DEBUG_PLANE(pl, fmt, ...) DPU_DEBUG("plane%d " fmt,\ + (pl) ? (pl)->base.base.id : -1, ##__VA_ARGS__) + +#define DPU_ERROR_PLANE(pl, fmt, ...) DPU_ERROR("plane%d " fmt,\ + (pl) ? (pl)->base.base.id : -1, ##__VA_ARGS__) + +#define DECIMATED_DIMENSION(dim, deci) (((dim) + ((1 << (deci)) - 1)) >> (deci)) +#define PHASE_STEP_SHIFT 21 +#define PHASE_STEP_UNIT_SCALE ((int) (1 << PHASE_STEP_SHIFT)) +#define PHASE_RESIDUAL 15 + +#define SHARP_STRENGTH_DEFAULT 32 +#define SHARP_EDGE_THR_DEFAULT 112 +#define SHARP_SMOOTH_THR_DEFAULT 8 +#define SHARP_NOISE_THR_DEFAULT 2 + +#define DPU_NAME_SIZE 12 + +#define DPU_PLANE_COLOR_FILL_FLAG BIT(31) +#define DPU_ZPOS_MAX 255 + +/* multirect rect index */ +enum { + R0, + R1, + R_MAX +}; + +/* + * Default Preload Values + */ +#define DPU_QSEED3_DEFAULT_PRELOAD_H 0x4 +#define DPU_QSEED3_DEFAULT_PRELOAD_V 0x3 +#define DPU_QSEED4_DEFAULT_PRELOAD_V 0x2 +#define DPU_QSEED4_DEFAULT_PRELOAD_H 0x4 + +#define DEFAULT_REFRESH_RATE 60 + +/** + * enum dpu_plane_qos - Different qos configurations for each pipe + * + * @DPU_PLANE_QOS_VBLANK_CTRL: Setup VBLANK qos for the pipe. + * @DPU_PLANE_QOS_VBLANK_AMORTIZE: Enables Amortization within pipe. + * this configuration is mutually exclusive from VBLANK_CTRL. + * @DPU_PLANE_QOS_PANIC_CTRL: Setup panic for the pipe. + */ +enum dpu_plane_qos { + DPU_PLANE_QOS_VBLANK_CTRL = BIT(0), + DPU_PLANE_QOS_VBLANK_AMORTIZE = BIT(1), + DPU_PLANE_QOS_PANIC_CTRL = BIT(2), +}; + +/* + * struct dpu_plane - local dpu plane structure + * @aspace: address space pointer + * @csc_ptr: Points to dpu_csc_cfg structure to use for current + * @mplane_list: List of multirect planes of the same pipe + * @catalog: Points to dpu catalog structure + * @revalidate: force revalidation of all the plane properties + */ +struct dpu_plane { + struct drm_plane base; + + struct mutex lock; + + enum dpu_sspp pipe; + uint32_t features; /* capabilities from catalog */ + + struct dpu_hw_pipe *pipe_hw; + struct dpu_hw_pipe_cfg pipe_cfg; + struct dpu_hw_pipe_qos_cfg pipe_qos_cfg; + uint32_t color_fill; + bool is_error; + bool is_rt_pipe; + bool is_virtual; + struct list_head mplane_list; + struct dpu_mdss_cfg *catalog; + + struct dpu_csc_cfg *csc_ptr; + + const struct dpu_sspp_sub_blks *pipe_sblk; + char pipe_name[DPU_NAME_SIZE]; + + /* debugfs related stuff */ + struct dentry *debugfs_root; + struct dpu_debugfs_regset32 debugfs_src; + struct dpu_debugfs_regset32 debugfs_scaler; + struct dpu_debugfs_regset32 debugfs_csc; + bool debugfs_default_scale; +}; + +static const uint64_t supported_format_modifiers[] = { + DRM_FORMAT_MOD_QCOM_COMPRESSED, + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +#define to_dpu_plane(x) container_of(x, struct dpu_plane, base) + +static struct dpu_kms *_dpu_plane_get_kms(struct drm_plane *plane) +{ + struct msm_drm_private *priv = plane->dev->dev_private; + + return to_dpu_kms(priv->kms); +} + +/** + * _dpu_plane_calc_bw - calculate bandwidth required for a plane + * @Plane: Pointer to drm plane. + * Result: Updates calculated bandwidth in the plane state. + * BW Equation: src_w * src_h * bpp * fps * (v_total / v_dest) + * Prefill BW Equation: line src bytes * line_time + */ +static void _dpu_plane_calc_bw(struct drm_plane *plane, + struct drm_framebuffer *fb) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + struct dpu_plane_state *pstate; + struct drm_display_mode *mode; + const struct dpu_format *fmt = NULL; + struct dpu_kms *dpu_kms = _dpu_plane_get_kms(plane); + int src_width, src_height, dst_height, fps; + u64 plane_pixel_rate, plane_bit_rate; + u64 plane_prefill_bw; + u64 plane_bw; + u32 hw_latency_lines; + u64 scale_factor; + int vbp, vpw; + + pstate = to_dpu_plane_state(plane->state); + mode = &plane->state->crtc->mode; + + fmt = dpu_get_dpu_format_ext(fb->format->format, fb->modifier); + + src_width = drm_rect_width(&pdpu->pipe_cfg.src_rect); + src_height = drm_rect_height(&pdpu->pipe_cfg.src_rect); + dst_height = drm_rect_height(&pdpu->pipe_cfg.dst_rect); + fps = drm_mode_vrefresh(mode); + vbp = mode->vtotal - mode->vsync_end; + vpw = mode->vsync_end - mode->vsync_start; + hw_latency_lines = dpu_kms->catalog->perf.min_prefill_lines; + scale_factor = src_height > dst_height ? + mult_frac(src_height, 1, dst_height) : 1; + + plane_pixel_rate = src_width * mode->vtotal * fps; + plane_bit_rate = plane_pixel_rate * fmt->bpp; + + plane_bw = plane_bit_rate * scale_factor; + + plane_prefill_bw = plane_bw * hw_latency_lines; + + do_div(plane_prefill_bw, (vbp+vpw)); + + pstate->plane_fetch_bw = max(plane_bw, plane_prefill_bw); +} + +/** + * _dpu_plane_calc_clk - calculate clock required for a plane + * @Plane: Pointer to drm plane. + * Result: Updates calculated clock in the plane state. + * Clock equation: dst_w * v_total * fps * (src_h / dst_h) + */ +static void _dpu_plane_calc_clk(struct drm_plane *plane) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + struct dpu_plane_state *pstate; + struct drm_display_mode *mode; + int dst_width, src_height, dst_height, fps; + + pstate = to_dpu_plane_state(plane->state); + mode = &plane->state->crtc->mode; + + src_height = drm_rect_height(&pdpu->pipe_cfg.src_rect); + dst_width = drm_rect_width(&pdpu->pipe_cfg.dst_rect); + dst_height = drm_rect_height(&pdpu->pipe_cfg.dst_rect); + fps = drm_mode_vrefresh(mode); + + pstate->plane_clk = + dst_width * mode->vtotal * fps; + + if (src_height > dst_height) { + pstate->plane_clk *= src_height; + do_div(pstate->plane_clk, dst_height); + } +} + +/** + * _dpu_plane_calc_fill_level - calculate fill level of the given source format + * @plane: Pointer to drm plane + * @fmt: Pointer to source buffer format + * @src_wdith: width of source buffer + * Return: fill level corresponding to the source buffer/format or 0 if error + */ +static int _dpu_plane_calc_fill_level(struct drm_plane *plane, + const struct dpu_format *fmt, u32 src_width) +{ + struct dpu_plane *pdpu, *tmp; + struct dpu_plane_state *pstate; + u32 fixed_buff_size; + u32 total_fl; + + if (!fmt || !plane->state || !src_width || !fmt->bpp) { + DPU_ERROR("invalid arguments\n"); + return 0; + } + + pdpu = to_dpu_plane(plane); + pstate = to_dpu_plane_state(plane->state); + fixed_buff_size = pdpu->catalog->caps->pixel_ram_size; + + list_for_each_entry(tmp, &pdpu->mplane_list, mplane_list) { + if (!tmp->base.state->visible) + continue; + DPU_DEBUG("plane%d/%d src_width:%d/%d\n", + pdpu->base.base.id, tmp->base.base.id, + src_width, + drm_rect_width(&tmp->pipe_cfg.src_rect)); + src_width = max_t(u32, src_width, + drm_rect_width(&tmp->pipe_cfg.src_rect)); + } + + if (fmt->fetch_planes == DPU_PLANE_PSEUDO_PLANAR) { + if (fmt->chroma_sample == DPU_CHROMA_420) { + /* NV12 */ + total_fl = (fixed_buff_size / 2) / + ((src_width + 32) * fmt->bpp); + } else { + /* non NV12 */ + total_fl = (fixed_buff_size / 2) * 2 / + ((src_width + 32) * fmt->bpp); + } + } else { + if (pstate->multirect_mode == DPU_SSPP_MULTIRECT_PARALLEL) { + total_fl = (fixed_buff_size / 2) * 2 / + ((src_width + 32) * fmt->bpp); + } else { + total_fl = (fixed_buff_size) * 2 / + ((src_width + 32) * fmt->bpp); + } + } + + DPU_DEBUG("plane%u: pnum:%d fmt: %4.4s w:%u fl:%u\n", + plane->base.id, pdpu->pipe - SSPP_VIG0, + (char *)&fmt->base.pixel_format, + src_width, total_fl); + + return total_fl; +} + +/** + * _dpu_plane_get_qos_lut - get LUT mapping based on fill level + * @tbl: Pointer to LUT table + * @total_fl: fill level + * Return: LUT setting corresponding to the fill level + */ +static u64 _dpu_plane_get_qos_lut(const struct dpu_qos_lut_tbl *tbl, + u32 total_fl) +{ + int i; + + if (!tbl || !tbl->nentry || !tbl->entries) + return 0; + + for (i = 0; i < tbl->nentry; i++) + if (total_fl <= tbl->entries[i].fl) + return tbl->entries[i].lut; + + /* if last fl is zero, use as default */ + if (!tbl->entries[i-1].fl) + return tbl->entries[i-1].lut; + + return 0; +} + +/** + * _dpu_plane_set_qos_lut - set QoS LUT of the given plane + * @plane: Pointer to drm plane + * @fb: Pointer to framebuffer associated with the given plane + */ +static void _dpu_plane_set_qos_lut(struct drm_plane *plane, + struct drm_framebuffer *fb) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + const struct dpu_format *fmt = NULL; + u64 qos_lut; + u32 total_fl = 0, lut_usage; + + if (!pdpu->is_rt_pipe) { + lut_usage = DPU_QOS_LUT_USAGE_NRT; + } else { + fmt = dpu_get_dpu_format_ext( + fb->format->format, + fb->modifier); + total_fl = _dpu_plane_calc_fill_level(plane, fmt, + drm_rect_width(&pdpu->pipe_cfg.src_rect)); + + if (fmt && DPU_FORMAT_IS_LINEAR(fmt)) + lut_usage = DPU_QOS_LUT_USAGE_LINEAR; + else + lut_usage = DPU_QOS_LUT_USAGE_MACROTILE; + } + + qos_lut = _dpu_plane_get_qos_lut( + &pdpu->catalog->perf.qos_lut_tbl[lut_usage], total_fl); + + pdpu->pipe_qos_cfg.creq_lut = qos_lut; + + trace_dpu_perf_set_qos_luts(pdpu->pipe - SSPP_VIG0, + (fmt) ? fmt->base.pixel_format : 0, + pdpu->is_rt_pipe, total_fl, qos_lut, lut_usage); + + DPU_DEBUG("plane%u: pnum:%d fmt: %4.4s rt:%d fl:%u lut:0x%llx\n", + plane->base.id, + pdpu->pipe - SSPP_VIG0, + fmt ? (char *)&fmt->base.pixel_format : NULL, + pdpu->is_rt_pipe, total_fl, qos_lut); + + pdpu->pipe_hw->ops.setup_creq_lut(pdpu->pipe_hw, &pdpu->pipe_qos_cfg); +} + +/** + * _dpu_plane_set_panic_lut - set danger/safe LUT of the given plane + * @plane: Pointer to drm plane + * @fb: Pointer to framebuffer associated with the given plane + */ +static void _dpu_plane_set_danger_lut(struct drm_plane *plane, + struct drm_framebuffer *fb) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + const struct dpu_format *fmt = NULL; + u32 danger_lut, safe_lut; + + if (!pdpu->is_rt_pipe) { + danger_lut = pdpu->catalog->perf.danger_lut_tbl + [DPU_QOS_LUT_USAGE_NRT]; + safe_lut = pdpu->catalog->perf.safe_lut_tbl + [DPU_QOS_LUT_USAGE_NRT]; + } else { + fmt = dpu_get_dpu_format_ext( + fb->format->format, + fb->modifier); + + if (fmt && DPU_FORMAT_IS_LINEAR(fmt)) { + danger_lut = pdpu->catalog->perf.danger_lut_tbl + [DPU_QOS_LUT_USAGE_LINEAR]; + safe_lut = pdpu->catalog->perf.safe_lut_tbl + [DPU_QOS_LUT_USAGE_LINEAR]; + } else { + danger_lut = pdpu->catalog->perf.danger_lut_tbl + [DPU_QOS_LUT_USAGE_MACROTILE]; + safe_lut = pdpu->catalog->perf.safe_lut_tbl + [DPU_QOS_LUT_USAGE_MACROTILE]; + } + } + + pdpu->pipe_qos_cfg.danger_lut = danger_lut; + pdpu->pipe_qos_cfg.safe_lut = safe_lut; + + trace_dpu_perf_set_danger_luts(pdpu->pipe - SSPP_VIG0, + (fmt) ? fmt->base.pixel_format : 0, + (fmt) ? fmt->fetch_mode : 0, + pdpu->pipe_qos_cfg.danger_lut, + pdpu->pipe_qos_cfg.safe_lut); + + DPU_DEBUG("plane%u: pnum:%d fmt: %4.4s mode:%d luts[0x%x, 0x%x]\n", + plane->base.id, + pdpu->pipe - SSPP_VIG0, + fmt ? (char *)&fmt->base.pixel_format : NULL, + fmt ? fmt->fetch_mode : -1, + pdpu->pipe_qos_cfg.danger_lut, + pdpu->pipe_qos_cfg.safe_lut); + + pdpu->pipe_hw->ops.setup_danger_safe_lut(pdpu->pipe_hw, + &pdpu->pipe_qos_cfg); +} + +/** + * _dpu_plane_set_qos_ctrl - set QoS control of the given plane + * @plane: Pointer to drm plane + * @enable: true to enable QoS control + * @flags: QoS control mode (enum dpu_plane_qos) + */ +static void _dpu_plane_set_qos_ctrl(struct drm_plane *plane, + bool enable, u32 flags) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + + if (flags & DPU_PLANE_QOS_VBLANK_CTRL) { + pdpu->pipe_qos_cfg.creq_vblank = pdpu->pipe_sblk->creq_vblank; + pdpu->pipe_qos_cfg.danger_vblank = + pdpu->pipe_sblk->danger_vblank; + pdpu->pipe_qos_cfg.vblank_en = enable; + } + + if (flags & DPU_PLANE_QOS_VBLANK_AMORTIZE) { + /* this feature overrules previous VBLANK_CTRL */ + pdpu->pipe_qos_cfg.vblank_en = false; + pdpu->pipe_qos_cfg.creq_vblank = 0; /* clear vblank bits */ + } + + if (flags & DPU_PLANE_QOS_PANIC_CTRL) + pdpu->pipe_qos_cfg.danger_safe_en = enable; + + if (!pdpu->is_rt_pipe) { + pdpu->pipe_qos_cfg.vblank_en = false; + pdpu->pipe_qos_cfg.danger_safe_en = false; + } + + DPU_DEBUG("plane%u: pnum:%d ds:%d vb:%d pri[0x%x, 0x%x] is_rt:%d\n", + plane->base.id, + pdpu->pipe - SSPP_VIG0, + pdpu->pipe_qos_cfg.danger_safe_en, + pdpu->pipe_qos_cfg.vblank_en, + pdpu->pipe_qos_cfg.creq_vblank, + pdpu->pipe_qos_cfg.danger_vblank, + pdpu->is_rt_pipe); + + pdpu->pipe_hw->ops.setup_qos_ctrl(pdpu->pipe_hw, + &pdpu->pipe_qos_cfg); +} + +/** + * _dpu_plane_set_ot_limit - set OT limit for the given plane + * @plane: Pointer to drm plane + * @crtc: Pointer to drm crtc + */ +static void _dpu_plane_set_ot_limit(struct drm_plane *plane, + struct drm_crtc *crtc) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + struct dpu_vbif_set_ot_params ot_params; + struct dpu_kms *dpu_kms = _dpu_plane_get_kms(plane); + + memset(&ot_params, 0, sizeof(ot_params)); + ot_params.xin_id = pdpu->pipe_hw->cap->xin_id; + ot_params.num = pdpu->pipe_hw->idx - SSPP_NONE; + ot_params.width = drm_rect_width(&pdpu->pipe_cfg.src_rect); + ot_params.height = drm_rect_height(&pdpu->pipe_cfg.src_rect); + ot_params.is_wfd = !pdpu->is_rt_pipe; + ot_params.frame_rate = drm_mode_vrefresh(&crtc->mode); + ot_params.vbif_idx = VBIF_RT; + ot_params.clk_ctrl = pdpu->pipe_hw->cap->clk_ctrl; + ot_params.rd = true; + + dpu_vbif_set_ot_limit(dpu_kms, &ot_params); +} + +/** + * _dpu_plane_set_vbif_qos - set vbif QoS for the given plane + * @plane: Pointer to drm plane + */ +static void _dpu_plane_set_qos_remap(struct drm_plane *plane) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + struct dpu_vbif_set_qos_params qos_params; + struct dpu_kms *dpu_kms = _dpu_plane_get_kms(plane); + + memset(&qos_params, 0, sizeof(qos_params)); + qos_params.vbif_idx = VBIF_RT; + qos_params.clk_ctrl = pdpu->pipe_hw->cap->clk_ctrl; + qos_params.xin_id = pdpu->pipe_hw->cap->xin_id; + qos_params.num = pdpu->pipe_hw->idx - SSPP_VIG0; + qos_params.is_rt = pdpu->is_rt_pipe; + + DPU_DEBUG("plane%d pipe:%d vbif:%d xin:%d rt:%d, clk_ctrl:%d\n", + plane->base.id, qos_params.num, + qos_params.vbif_idx, + qos_params.xin_id, qos_params.is_rt, + qos_params.clk_ctrl); + + dpu_vbif_set_qos_remap(dpu_kms, &qos_params); +} + +static void _dpu_plane_set_scanout(struct drm_plane *plane, + struct dpu_plane_state *pstate, + struct dpu_hw_pipe_cfg *pipe_cfg, + struct drm_framebuffer *fb) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + struct dpu_kms *kms = _dpu_plane_get_kms(&pdpu->base); + struct msm_gem_address_space *aspace = kms->base.aspace; + int ret; + + ret = dpu_format_populate_layout(aspace, fb, &pipe_cfg->layout); + if (ret == -EAGAIN) + DPU_DEBUG_PLANE(pdpu, "not updating same src addrs\n"); + else if (ret) + DPU_ERROR_PLANE(pdpu, "failed to get format layout, %d\n", ret); + else if (pdpu->pipe_hw->ops.setup_sourceaddress) { + trace_dpu_plane_set_scanout(pdpu->pipe_hw->idx, + &pipe_cfg->layout, + pstate->multirect_index); + pdpu->pipe_hw->ops.setup_sourceaddress(pdpu->pipe_hw, pipe_cfg, + pstate->multirect_index); + } +} + +static void _dpu_plane_setup_scaler3(struct dpu_plane *pdpu, + struct dpu_plane_state *pstate, + uint32_t src_w, uint32_t src_h, uint32_t dst_w, uint32_t dst_h, + struct dpu_hw_scaler3_cfg *scale_cfg, + const struct dpu_format *fmt, + uint32_t chroma_subsmpl_h, uint32_t chroma_subsmpl_v) +{ + uint32_t i; + + memset(scale_cfg, 0, sizeof(*scale_cfg)); + memset(&pstate->pixel_ext, 0, sizeof(struct dpu_hw_pixel_ext)); + + scale_cfg->phase_step_x[DPU_SSPP_COMP_0] = + mult_frac((1 << PHASE_STEP_SHIFT), src_w, dst_w); + scale_cfg->phase_step_y[DPU_SSPP_COMP_0] = + mult_frac((1 << PHASE_STEP_SHIFT), src_h, dst_h); + + + scale_cfg->phase_step_y[DPU_SSPP_COMP_1_2] = + scale_cfg->phase_step_y[DPU_SSPP_COMP_0] / chroma_subsmpl_v; + scale_cfg->phase_step_x[DPU_SSPP_COMP_1_2] = + scale_cfg->phase_step_x[DPU_SSPP_COMP_0] / chroma_subsmpl_h; + + scale_cfg->phase_step_x[DPU_SSPP_COMP_2] = + scale_cfg->phase_step_x[DPU_SSPP_COMP_1_2]; + scale_cfg->phase_step_y[DPU_SSPP_COMP_2] = + scale_cfg->phase_step_y[DPU_SSPP_COMP_1_2]; + + scale_cfg->phase_step_x[DPU_SSPP_COMP_3] = + scale_cfg->phase_step_x[DPU_SSPP_COMP_0]; + scale_cfg->phase_step_y[DPU_SSPP_COMP_3] = + scale_cfg->phase_step_y[DPU_SSPP_COMP_0]; + + for (i = 0; i < DPU_MAX_PLANES; i++) { + scale_cfg->src_width[i] = src_w; + scale_cfg->src_height[i] = src_h; + if (i == DPU_SSPP_COMP_1_2 || i == DPU_SSPP_COMP_2) { + scale_cfg->src_width[i] /= chroma_subsmpl_h; + scale_cfg->src_height[i] /= chroma_subsmpl_v; + } + + if (pdpu->pipe_hw->cap->features & + BIT(DPU_SSPP_SCALER_QSEED4)) { + scale_cfg->preload_x[i] = DPU_QSEED4_DEFAULT_PRELOAD_H; + scale_cfg->preload_y[i] = DPU_QSEED4_DEFAULT_PRELOAD_V; + } else { + scale_cfg->preload_x[i] = DPU_QSEED3_DEFAULT_PRELOAD_H; + scale_cfg->preload_y[i] = DPU_QSEED3_DEFAULT_PRELOAD_V; + } + + pstate->pixel_ext.num_ext_pxls_top[i] = + scale_cfg->src_height[i]; + pstate->pixel_ext.num_ext_pxls_left[i] = + scale_cfg->src_width[i]; + } + if (!(DPU_FORMAT_IS_YUV(fmt)) && (src_h == dst_h) + && (src_w == dst_w)) + return; + + scale_cfg->dst_width = dst_w; + scale_cfg->dst_height = dst_h; + scale_cfg->y_rgb_filter_cfg = DPU_SCALE_BIL; + scale_cfg->uv_filter_cfg = DPU_SCALE_BIL; + scale_cfg->alpha_filter_cfg = DPU_SCALE_ALPHA_BIL; + scale_cfg->lut_flag = 0; + scale_cfg->blend_cfg = 1; + scale_cfg->enable = 1; +} + +static void _dpu_plane_setup_csc(struct dpu_plane *pdpu) +{ + static const struct dpu_csc_cfg dpu_csc_YUV2RGB_601L = { + { + /* S15.16 format */ + 0x00012A00, 0x00000000, 0x00019880, + 0x00012A00, 0xFFFF9B80, 0xFFFF3000, + 0x00012A00, 0x00020480, 0x00000000, + }, + /* signed bias */ + { 0xfff0, 0xff80, 0xff80,}, + { 0x0, 0x0, 0x0,}, + /* unsigned clamp */ + { 0x10, 0xeb, 0x10, 0xf0, 0x10, 0xf0,}, + { 0x00, 0xff, 0x00, 0xff, 0x00, 0xff,}, + }; + static const struct dpu_csc_cfg dpu_csc10_YUV2RGB_601L = { + { + /* S15.16 format */ + 0x00012A00, 0x00000000, 0x00019880, + 0x00012A00, 0xFFFF9B80, 0xFFFF3000, + 0x00012A00, 0x00020480, 0x00000000, + }, + /* signed bias */ + { 0xffc0, 0xfe00, 0xfe00,}, + { 0x0, 0x0, 0x0,}, + /* unsigned clamp */ + { 0x40, 0x3ac, 0x40, 0x3c0, 0x40, 0x3c0,}, + { 0x00, 0x3ff, 0x00, 0x3ff, 0x00, 0x3ff,}, + }; + + if (!pdpu) { + DPU_ERROR("invalid plane\n"); + return; + } + + if (BIT(DPU_SSPP_CSC_10BIT) & pdpu->features) + pdpu->csc_ptr = (struct dpu_csc_cfg *)&dpu_csc10_YUV2RGB_601L; + else + pdpu->csc_ptr = (struct dpu_csc_cfg *)&dpu_csc_YUV2RGB_601L; + + DPU_DEBUG_PLANE(pdpu, "using 0x%X 0x%X 0x%X...\n", + pdpu->csc_ptr->csc_mv[0], + pdpu->csc_ptr->csc_mv[1], + pdpu->csc_ptr->csc_mv[2]); +} + +static void _dpu_plane_setup_scaler(struct dpu_plane *pdpu, + struct dpu_plane_state *pstate, + const struct dpu_format *fmt, bool color_fill) +{ + const struct drm_format_info *info = drm_format_info(fmt->base.pixel_format); + + /* don't chroma subsample if decimating */ + /* update scaler. calculate default config for QSEED3 */ + _dpu_plane_setup_scaler3(pdpu, pstate, + drm_rect_width(&pdpu->pipe_cfg.src_rect), + drm_rect_height(&pdpu->pipe_cfg.src_rect), + drm_rect_width(&pdpu->pipe_cfg.dst_rect), + drm_rect_height(&pdpu->pipe_cfg.dst_rect), + &pstate->scaler3_cfg, fmt, + info->hsub, info->vsub); +} + +/** + * _dpu_plane_color_fill - enables color fill on plane + * @pdpu: Pointer to DPU plane object + * @color: RGB fill color value, [23..16] Blue, [15..8] Green, [7..0] Red + * @alpha: 8-bit fill alpha value, 255 selects 100% alpha + * Returns: 0 on success + */ +static int _dpu_plane_color_fill(struct dpu_plane *pdpu, + uint32_t color, uint32_t alpha) +{ + const struct dpu_format *fmt; + const struct drm_plane *plane = &pdpu->base; + struct dpu_plane_state *pstate = to_dpu_plane_state(plane->state); + + DPU_DEBUG_PLANE(pdpu, "\n"); + + /* + * select fill format to match user property expectation, + * h/w only supports RGB variants + */ + fmt = dpu_get_dpu_format(DRM_FORMAT_ABGR8888); + + /* update sspp */ + if (fmt && pdpu->pipe_hw->ops.setup_solidfill) { + pdpu->pipe_hw->ops.setup_solidfill(pdpu->pipe_hw, + (color & 0xFFFFFF) | ((alpha & 0xFF) << 24), + pstate->multirect_index); + + /* override scaler/decimation if solid fill */ + pdpu->pipe_cfg.src_rect.x1 = 0; + pdpu->pipe_cfg.src_rect.y1 = 0; + pdpu->pipe_cfg.src_rect.x2 = + drm_rect_width(&pdpu->pipe_cfg.dst_rect); + pdpu->pipe_cfg.src_rect.y2 = + drm_rect_height(&pdpu->pipe_cfg.dst_rect); + _dpu_plane_setup_scaler(pdpu, pstate, fmt, true); + + if (pdpu->pipe_hw->ops.setup_format) + pdpu->pipe_hw->ops.setup_format(pdpu->pipe_hw, + fmt, DPU_SSPP_SOLID_FILL, + pstate->multirect_index); + + if (pdpu->pipe_hw->ops.setup_rects) + pdpu->pipe_hw->ops.setup_rects(pdpu->pipe_hw, + &pdpu->pipe_cfg, + pstate->multirect_index); + + if (pdpu->pipe_hw->ops.setup_pe) + pdpu->pipe_hw->ops.setup_pe(pdpu->pipe_hw, + &pstate->pixel_ext); + + if (pdpu->pipe_hw->ops.setup_scaler && + pstate->multirect_index != DPU_SSPP_RECT_1) + pdpu->pipe_hw->ops.setup_scaler(pdpu->pipe_hw, + &pdpu->pipe_cfg, &pstate->pixel_ext, + &pstate->scaler3_cfg); + } + + return 0; +} + +void dpu_plane_clear_multirect(const struct drm_plane_state *drm_state) +{ + struct dpu_plane_state *pstate = to_dpu_plane_state(drm_state); + + pstate->multirect_index = DPU_SSPP_RECT_SOLO; + pstate->multirect_mode = DPU_SSPP_MULTIRECT_NONE; +} + +int dpu_plane_validate_multirect_v2(struct dpu_multirect_plane_states *plane) +{ + struct dpu_plane_state *pstate[R_MAX]; + const struct drm_plane_state *drm_state[R_MAX]; + struct drm_rect src[R_MAX], dst[R_MAX]; + struct dpu_plane *dpu_plane[R_MAX]; + const struct dpu_format *fmt[R_MAX]; + int i, buffer_lines; + unsigned int max_tile_height = 1; + bool parallel_fetch_qualified = true; + bool has_tiled_rect = false; + + for (i = 0; i < R_MAX; i++) { + const struct msm_format *msm_fmt; + + drm_state[i] = i ? plane->r1 : plane->r0; + msm_fmt = msm_framebuffer_format(drm_state[i]->fb); + fmt[i] = to_dpu_format(msm_fmt); + + if (DPU_FORMAT_IS_UBWC(fmt[i])) { + has_tiled_rect = true; + if (fmt[i]->tile_height > max_tile_height) + max_tile_height = fmt[i]->tile_height; + } + } + + for (i = 0; i < R_MAX; i++) { + int width_threshold; + + pstate[i] = to_dpu_plane_state(drm_state[i]); + dpu_plane[i] = to_dpu_plane(drm_state[i]->plane); + + if (pstate[i] == NULL) { + DPU_ERROR("DPU plane state of plane id %d is NULL\n", + drm_state[i]->plane->base.id); + return -EINVAL; + } + + src[i].x1 = drm_state[i]->src_x >> 16; + src[i].y1 = drm_state[i]->src_y >> 16; + src[i].x2 = src[i].x1 + (drm_state[i]->src_w >> 16); + src[i].y2 = src[i].y1 + (drm_state[i]->src_h >> 16); + + dst[i] = drm_plane_state_dest(drm_state[i]); + + if (drm_rect_calc_hscale(&src[i], &dst[i], 1, 1) != 1 || + drm_rect_calc_vscale(&src[i], &dst[i], 1, 1) != 1) { + DPU_ERROR_PLANE(dpu_plane[i], + "scaling is not supported in multirect mode\n"); + return -EINVAL; + } + + if (DPU_FORMAT_IS_YUV(fmt[i])) { + DPU_ERROR_PLANE(dpu_plane[i], + "Unsupported format for multirect mode\n"); + return -EINVAL; + } + + /** + * SSPP PD_MEM is split half - one for each RECT. + * Tiled formats need 5 lines of buffering while fetching + * whereas linear formats need only 2 lines. + * So we cannot support more than half of the supported SSPP + * width for tiled formats. + */ + width_threshold = dpu_plane[i]->catalog->caps->max_linewidth; + if (has_tiled_rect) + width_threshold /= 2; + + if (parallel_fetch_qualified && + drm_rect_width(&src[i]) > width_threshold) + parallel_fetch_qualified = false; + + } + + /* Validate RECT's and set the mode */ + + /* Prefer PARALLEL FETCH Mode over TIME_MX Mode */ + if (parallel_fetch_qualified) { + pstate[R0]->multirect_mode = DPU_SSPP_MULTIRECT_PARALLEL; + pstate[R1]->multirect_mode = DPU_SSPP_MULTIRECT_PARALLEL; + + goto done; + } + + /* TIME_MX Mode */ + buffer_lines = 2 * max_tile_height; + + if (dst[R1].y1 >= dst[R0].y2 + buffer_lines || + dst[R0].y1 >= dst[R1].y2 + buffer_lines) { + pstate[R0]->multirect_mode = DPU_SSPP_MULTIRECT_TIME_MX; + pstate[R1]->multirect_mode = DPU_SSPP_MULTIRECT_TIME_MX; + } else { + DPU_ERROR( + "No multirect mode possible for the planes (%d - %d)\n", + drm_state[R0]->plane->base.id, + drm_state[R1]->plane->base.id); + return -EINVAL; + } + +done: + if (dpu_plane[R0]->is_virtual) { + pstate[R0]->multirect_index = DPU_SSPP_RECT_1; + pstate[R1]->multirect_index = DPU_SSPP_RECT_0; + } else { + pstate[R0]->multirect_index = DPU_SSPP_RECT_0; + pstate[R1]->multirect_index = DPU_SSPP_RECT_1; + } + + DPU_DEBUG_PLANE(dpu_plane[R0], "R0: %d - %d\n", + pstate[R0]->multirect_mode, pstate[R0]->multirect_index); + DPU_DEBUG_PLANE(dpu_plane[R1], "R1: %d - %d\n", + pstate[R1]->multirect_mode, pstate[R1]->multirect_index); + return 0; +} + +/** + * dpu_plane_get_ctl_flush - get control flush for the given plane + * @plane: Pointer to drm plane structure + * @ctl: Pointer to hardware control driver + * @flush_sspp: Pointer to sspp flush control word + */ +void dpu_plane_get_ctl_flush(struct drm_plane *plane, struct dpu_hw_ctl *ctl, + u32 *flush_sspp) +{ + *flush_sspp = ctl->ops.get_bitmask_sspp(ctl, dpu_plane_pipe(plane)); +} + +static int dpu_plane_prepare_fb(struct drm_plane *plane, + struct drm_plane_state *new_state) +{ + struct drm_framebuffer *fb = new_state->fb; + struct dpu_plane *pdpu = to_dpu_plane(plane); + struct dpu_plane_state *pstate = to_dpu_plane_state(new_state); + struct dpu_hw_fmt_layout layout; + struct dpu_kms *kms = _dpu_plane_get_kms(&pdpu->base); + int ret; + + if (!new_state->fb) + return 0; + + DPU_DEBUG_PLANE(pdpu, "FB[%u]\n", fb->base.id); + + /* cache aspace */ + pstate->aspace = kms->base.aspace; + + /* + * TODO: Need to sort out the msm_framebuffer_prepare() call below so + * we can use msm_atomic_prepare_fb() instead of doing the + * implicit fence and fb prepare by hand here. + */ + drm_gem_fb_prepare_fb(plane, new_state); + + if (pstate->aspace) { + ret = msm_framebuffer_prepare(new_state->fb, + pstate->aspace); + if (ret) { + DPU_ERROR("failed to prepare framebuffer\n"); + return ret; + } + } + + /* validate framebuffer layout before commit */ + ret = dpu_format_populate_layout(pstate->aspace, + new_state->fb, &layout); + if (ret) { + DPU_ERROR_PLANE(pdpu, "failed to get format layout, %d\n", ret); + return ret; + } + + return 0; +} + +static void dpu_plane_cleanup_fb(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + struct dpu_plane_state *old_pstate; + + if (!old_state || !old_state->fb) + return; + + old_pstate = to_dpu_plane_state(old_state); + + DPU_DEBUG_PLANE(pdpu, "FB[%u]\n", old_state->fb->base.id); + + msm_framebuffer_cleanup(old_state->fb, old_pstate->aspace); +} + +static bool dpu_plane_validate_src(struct drm_rect *src, + struct drm_rect *fb_rect, + uint32_t min_src_size) +{ + /* Ensure fb size is supported */ + if (drm_rect_width(fb_rect) > MAX_IMG_WIDTH || + drm_rect_height(fb_rect) > MAX_IMG_HEIGHT) + return false; + + /* Ensure src rect is above the minimum size */ + if (drm_rect_width(src) < min_src_size || + drm_rect_height(src) < min_src_size) + return false; + + /* Ensure src is fully encapsulated in fb */ + return drm_rect_intersect(fb_rect, src) && + drm_rect_equals(fb_rect, src); +} + +static int dpu_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + int ret = 0, min_scale; + struct dpu_plane *pdpu = to_dpu_plane(plane); + const struct drm_crtc_state *crtc_state = NULL; + const struct dpu_format *fmt; + struct drm_rect src, dst, fb_rect = { 0 }; + uint32_t min_src_size, max_linewidth; + + if (state->crtc) + crtc_state = drm_atomic_get_new_crtc_state(state->state, + state->crtc); + + min_scale = FRAC_16_16(1, pdpu->pipe_sblk->maxupscale); + ret = drm_atomic_helper_check_plane_state(state, crtc_state, min_scale, + pdpu->pipe_sblk->maxdwnscale << 16, + true, true); + if (ret) { + DPU_DEBUG_PLANE(pdpu, "Check plane state failed (%d)\n", ret); + return ret; + } + if (!state->visible) + return 0; + + src.x1 = state->src_x >> 16; + src.y1 = state->src_y >> 16; + src.x2 = src.x1 + (state->src_w >> 16); + src.y2 = src.y1 + (state->src_h >> 16); + + dst = drm_plane_state_dest(state); + + fb_rect.x2 = state->fb->width; + fb_rect.y2 = state->fb->height; + + max_linewidth = pdpu->catalog->caps->max_linewidth; + + fmt = to_dpu_format(msm_framebuffer_format(state->fb)); + + min_src_size = DPU_FORMAT_IS_YUV(fmt) ? 2 : 1; + + if (DPU_FORMAT_IS_YUV(fmt) && + (!(pdpu->features & DPU_SSPP_SCALER) || + !(pdpu->features & (BIT(DPU_SSPP_CSC) + | BIT(DPU_SSPP_CSC_10BIT))))) { + DPU_DEBUG_PLANE(pdpu, + "plane doesn't have scaler/csc for yuv\n"); + return -EINVAL; + + /* check src bounds */ + } else if (!dpu_plane_validate_src(&src, &fb_rect, min_src_size)) { + DPU_DEBUG_PLANE(pdpu, "invalid source " DRM_RECT_FMT "\n", + DRM_RECT_ARG(&src)); + return -E2BIG; + + /* valid yuv image */ + } else if (DPU_FORMAT_IS_YUV(fmt) && + (src.x1 & 0x1 || src.y1 & 0x1 || + drm_rect_width(&src) & 0x1 || + drm_rect_height(&src) & 0x1)) { + DPU_DEBUG_PLANE(pdpu, "invalid yuv source " DRM_RECT_FMT "\n", + DRM_RECT_ARG(&src)); + return -EINVAL; + + /* min dst support */ + } else if (drm_rect_width(&dst) < 0x1 || drm_rect_height(&dst) < 0x1) { + DPU_DEBUG_PLANE(pdpu, "invalid dest rect " DRM_RECT_FMT "\n", + DRM_RECT_ARG(&dst)); + return -EINVAL; + + /* check decimated source width */ + } else if (drm_rect_width(&src) > max_linewidth) { + DPU_DEBUG_PLANE(pdpu, "invalid src " DRM_RECT_FMT " line:%u\n", + DRM_RECT_ARG(&src), max_linewidth); + return -E2BIG; + } + + return 0; +} + +void dpu_plane_flush(struct drm_plane *plane) +{ + struct dpu_plane *pdpu; + struct dpu_plane_state *pstate; + + if (!plane || !plane->state) { + DPU_ERROR("invalid plane\n"); + return; + } + + pdpu = to_dpu_plane(plane); + pstate = to_dpu_plane_state(plane->state); + + /* + * These updates have to be done immediately before the plane flush + * timing, and may not be moved to the atomic_update/mode_set functions. + */ + if (pdpu->is_error) + /* force white frame with 100% alpha pipe output on error */ + _dpu_plane_color_fill(pdpu, 0xFFFFFF, 0xFF); + else if (pdpu->color_fill & DPU_PLANE_COLOR_FILL_FLAG) + /* force 100% alpha */ + _dpu_plane_color_fill(pdpu, pdpu->color_fill, 0xFF); + else if (pdpu->pipe_hw && pdpu->csc_ptr && pdpu->pipe_hw->ops.setup_csc) + pdpu->pipe_hw->ops.setup_csc(pdpu->pipe_hw, pdpu->csc_ptr); + + /* flag h/w flush complete */ + if (plane->state) + pstate->pending = false; +} + +/** + * dpu_plane_set_error: enable/disable error condition + * @plane: pointer to drm_plane structure + */ +void dpu_plane_set_error(struct drm_plane *plane, bool error) +{ + struct dpu_plane *pdpu; + + if (!plane) + return; + + pdpu = to_dpu_plane(plane); + pdpu->is_error = error; +} + +static void dpu_plane_sspp_atomic_update(struct drm_plane *plane) +{ + uint32_t src_flags; + struct dpu_plane *pdpu = to_dpu_plane(plane); + struct drm_plane_state *state = plane->state; + struct dpu_plane_state *pstate = to_dpu_plane_state(state); + struct drm_crtc *crtc = state->crtc; + struct drm_framebuffer *fb = state->fb; + const struct dpu_format *fmt = + to_dpu_format(msm_framebuffer_format(fb)); + + memset(&(pdpu->pipe_cfg), 0, sizeof(struct dpu_hw_pipe_cfg)); + + _dpu_plane_set_scanout(plane, pstate, &pdpu->pipe_cfg, fb); + + pstate->pending = true; + + pdpu->is_rt_pipe = (dpu_crtc_get_client_type(crtc) != NRT_CLIENT); + _dpu_plane_set_qos_ctrl(plane, false, DPU_PLANE_QOS_PANIC_CTRL); + + DPU_DEBUG_PLANE(pdpu, "FB[%u] " DRM_RECT_FP_FMT "->crtc%u " DRM_RECT_FMT + ", %4.4s ubwc %d\n", fb->base.id, DRM_RECT_FP_ARG(&state->src), + crtc->base.id, DRM_RECT_ARG(&state->dst), + (char *)&fmt->base.pixel_format, DPU_FORMAT_IS_UBWC(fmt)); + + pdpu->pipe_cfg.src_rect = state->src; + + /* state->src is 16.16, src_rect is not */ + pdpu->pipe_cfg.src_rect.x1 >>= 16; + pdpu->pipe_cfg.src_rect.x2 >>= 16; + pdpu->pipe_cfg.src_rect.y1 >>= 16; + pdpu->pipe_cfg.src_rect.y2 >>= 16; + + pdpu->pipe_cfg.dst_rect = state->dst; + + _dpu_plane_setup_scaler(pdpu, pstate, fmt, false); + + /* override for color fill */ + if (pdpu->color_fill & DPU_PLANE_COLOR_FILL_FLAG) { + /* skip remaining processing on color fill */ + return; + } + + if (pdpu->pipe_hw->ops.setup_rects) { + pdpu->pipe_hw->ops.setup_rects(pdpu->pipe_hw, + &pdpu->pipe_cfg, + pstate->multirect_index); + } + + if (pdpu->pipe_hw->ops.setup_pe && + (pstate->multirect_index != DPU_SSPP_RECT_1)) + pdpu->pipe_hw->ops.setup_pe(pdpu->pipe_hw, + &pstate->pixel_ext); + + /** + * when programmed in multirect mode, scalar block will be + * bypassed. Still we need to update alpha and bitwidth + * ONLY for RECT0 + */ + if (pdpu->pipe_hw->ops.setup_scaler && + pstate->multirect_index != DPU_SSPP_RECT_1) + pdpu->pipe_hw->ops.setup_scaler(pdpu->pipe_hw, + &pdpu->pipe_cfg, &pstate->pixel_ext, + &pstate->scaler3_cfg); + + if (pdpu->pipe_hw->ops.setup_multirect) + pdpu->pipe_hw->ops.setup_multirect( + pdpu->pipe_hw, + pstate->multirect_index, + pstate->multirect_mode); + + if (pdpu->pipe_hw->ops.setup_format) { + unsigned int rotation; + + src_flags = 0x0; + + rotation = drm_rotation_simplify(state->rotation, + DRM_MODE_ROTATE_0 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y); + + if (rotation & DRM_MODE_REFLECT_X) + src_flags |= DPU_SSPP_FLIP_LR; + + if (rotation & DRM_MODE_REFLECT_Y) + src_flags |= DPU_SSPP_FLIP_UD; + + /* update format */ + pdpu->pipe_hw->ops.setup_format(pdpu->pipe_hw, fmt, src_flags, + pstate->multirect_index); + + if (pdpu->pipe_hw->ops.setup_cdp) { + struct dpu_hw_pipe_cdp_cfg *cdp_cfg = &pstate->cdp_cfg; + + memset(cdp_cfg, 0, sizeof(struct dpu_hw_pipe_cdp_cfg)); + + cdp_cfg->enable = pdpu->catalog->perf.cdp_cfg + [DPU_PERF_CDP_USAGE_RT].rd_enable; + cdp_cfg->ubwc_meta_enable = + DPU_FORMAT_IS_UBWC(fmt); + cdp_cfg->tile_amortize_enable = + DPU_FORMAT_IS_UBWC(fmt) || + DPU_FORMAT_IS_TILE(fmt); + cdp_cfg->preload_ahead = DPU_SSPP_CDP_PRELOAD_AHEAD_64; + + pdpu->pipe_hw->ops.setup_cdp(pdpu->pipe_hw, cdp_cfg); + } + + /* update csc */ + if (DPU_FORMAT_IS_YUV(fmt)) + _dpu_plane_setup_csc(pdpu); + else + pdpu->csc_ptr = 0; + } + + _dpu_plane_set_qos_lut(plane, fb); + _dpu_plane_set_danger_lut(plane, fb); + + if (plane->type != DRM_PLANE_TYPE_CURSOR) { + _dpu_plane_set_qos_ctrl(plane, true, DPU_PLANE_QOS_PANIC_CTRL); + _dpu_plane_set_ot_limit(plane, crtc); + } + + _dpu_plane_set_qos_remap(plane); + + _dpu_plane_calc_bw(plane, fb); + + _dpu_plane_calc_clk(plane); +} + +static void _dpu_plane_atomic_disable(struct drm_plane *plane) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + struct drm_plane_state *state = plane->state; + struct dpu_plane_state *pstate = to_dpu_plane_state(state); + + trace_dpu_plane_disable(DRMID(plane), is_dpu_plane_virtual(plane), + pstate->multirect_mode); + + pstate->pending = true; + + if (is_dpu_plane_virtual(plane) && + pdpu->pipe_hw && pdpu->pipe_hw->ops.setup_multirect) + pdpu->pipe_hw->ops.setup_multirect(pdpu->pipe_hw, + DPU_SSPP_RECT_SOLO, DPU_SSPP_MULTIRECT_NONE); +} + +static void dpu_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + struct drm_plane_state *state = plane->state; + + pdpu->is_error = false; + + DPU_DEBUG_PLANE(pdpu, "\n"); + + if (!state->visible) { + _dpu_plane_atomic_disable(plane); + } else { + dpu_plane_sspp_atomic_update(plane); + } +} + +void dpu_plane_restore(struct drm_plane *plane) +{ + struct dpu_plane *pdpu; + + if (!plane || !plane->state) { + DPU_ERROR("invalid plane\n"); + return; + } + + pdpu = to_dpu_plane(plane); + + DPU_DEBUG_PLANE(pdpu, "\n"); + + /* last plane state is same as current state */ + dpu_plane_atomic_update(plane, plane->state); +} + +static void dpu_plane_destroy(struct drm_plane *plane) +{ + struct dpu_plane *pdpu = plane ? to_dpu_plane(plane) : NULL; + + DPU_DEBUG_PLANE(pdpu, "\n"); + + if (pdpu) { + _dpu_plane_set_qos_ctrl(plane, false, DPU_PLANE_QOS_PANIC_CTRL); + + mutex_destroy(&pdpu->lock); + + /* this will destroy the states as well */ + drm_plane_cleanup(plane); + + dpu_hw_sspp_destroy(pdpu->pipe_hw); + + kfree(pdpu); + } +} + +static void dpu_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + __drm_atomic_helper_plane_destroy_state(state); + kfree(to_dpu_plane_state(state)); +} + +static struct drm_plane_state * +dpu_plane_duplicate_state(struct drm_plane *plane) +{ + struct dpu_plane *pdpu; + struct dpu_plane_state *pstate; + struct dpu_plane_state *old_state; + + if (!plane) { + DPU_ERROR("invalid plane\n"); + return NULL; + } else if (!plane->state) { + DPU_ERROR("invalid plane state\n"); + return NULL; + } + + old_state = to_dpu_plane_state(plane->state); + pdpu = to_dpu_plane(plane); + pstate = kmemdup(old_state, sizeof(*old_state), GFP_KERNEL); + if (!pstate) { + DPU_ERROR_PLANE(pdpu, "failed to allocate state\n"); + return NULL; + } + + DPU_DEBUG_PLANE(pdpu, "\n"); + + pstate->pending = false; + + __drm_atomic_helper_plane_duplicate_state(plane, &pstate->base); + + return &pstate->base; +} + +static void dpu_plane_reset(struct drm_plane *plane) +{ + struct dpu_plane *pdpu; + struct dpu_plane_state *pstate; + + if (!plane) { + DPU_ERROR("invalid plane\n"); + return; + } + + pdpu = to_dpu_plane(plane); + DPU_DEBUG_PLANE(pdpu, "\n"); + + /* remove previous state, if present */ + if (plane->state) { + dpu_plane_destroy_state(plane, plane->state); + plane->state = 0; + } + + pstate = kzalloc(sizeof(*pstate), GFP_KERNEL); + if (!pstate) { + DPU_ERROR_PLANE(pdpu, "failed to allocate state\n"); + return; + } + + pstate->base.plane = plane; + + plane->state = &pstate->base; +} + +#ifdef CONFIG_DEBUG_FS +static void dpu_plane_danger_signal_ctrl(struct drm_plane *plane, bool enable) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + struct dpu_kms *dpu_kms = _dpu_plane_get_kms(plane); + + if (!pdpu->is_rt_pipe) + return; + + pm_runtime_get_sync(&dpu_kms->pdev->dev); + _dpu_plane_set_qos_ctrl(plane, enable, DPU_PLANE_QOS_PANIC_CTRL); + pm_runtime_put_sync(&dpu_kms->pdev->dev); +} + +static ssize_t _dpu_plane_danger_read(struct file *file, + char __user *buff, size_t count, loff_t *ppos) +{ + struct dpu_kms *kms = file->private_data; + int len; + char buf[40]; + + len = scnprintf(buf, sizeof(buf), "%d\n", !kms->has_danger_ctrl); + + return simple_read_from_buffer(buff, count, ppos, buf, len); +} + +static void _dpu_plane_set_danger_state(struct dpu_kms *kms, bool enable) +{ + struct drm_plane *plane; + + drm_for_each_plane(plane, kms->dev) { + if (plane->fb && plane->state) { + dpu_plane_danger_signal_ctrl(plane, enable); + DPU_DEBUG("plane:%d img:%dx%d ", + plane->base.id, plane->fb->width, + plane->fb->height); + DPU_DEBUG("src[%d,%d,%d,%d] dst[%d,%d,%d,%d]\n", + plane->state->src_x >> 16, + plane->state->src_y >> 16, + plane->state->src_w >> 16, + plane->state->src_h >> 16, + plane->state->crtc_x, plane->state->crtc_y, + plane->state->crtc_w, plane->state->crtc_h); + } else { + DPU_DEBUG("Inactive plane:%d\n", plane->base.id); + } + } +} + +static ssize_t _dpu_plane_danger_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + struct dpu_kms *kms = file->private_data; + int disable_panic; + int ret; + + ret = kstrtouint_from_user(user_buf, count, 0, &disable_panic); + if (ret) + return ret; + + if (disable_panic) { + /* Disable panic signal for all active pipes */ + DPU_DEBUG("Disabling danger:\n"); + _dpu_plane_set_danger_state(kms, false); + kms->has_danger_ctrl = false; + } else { + /* Enable panic signal for all active pipes */ + DPU_DEBUG("Enabling danger:\n"); + kms->has_danger_ctrl = true; + _dpu_plane_set_danger_state(kms, true); + } + + return count; +} + +static const struct file_operations dpu_plane_danger_enable = { + .open = simple_open, + .read = _dpu_plane_danger_read, + .write = _dpu_plane_danger_write, +}; + +static int _dpu_plane_init_debugfs(struct drm_plane *plane) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + struct dpu_kms *kms = _dpu_plane_get_kms(plane); + const struct dpu_sspp_cfg *cfg = pdpu->pipe_hw->cap; + const struct dpu_sspp_sub_blks *sblk = cfg->sblk; + + /* create overall sub-directory for the pipe */ + pdpu->debugfs_root = + debugfs_create_dir(pdpu->pipe_name, + plane->dev->primary->debugfs_root); + + /* don't error check these */ + debugfs_create_x32("features", 0600, + pdpu->debugfs_root, &pdpu->features); + + /* add register dump support */ + dpu_debugfs_setup_regset32(&pdpu->debugfs_src, + sblk->src_blk.base + cfg->base, + sblk->src_blk.len, + kms); + dpu_debugfs_create_regset32("src_blk", 0400, + pdpu->debugfs_root, &pdpu->debugfs_src); + + if (cfg->features & BIT(DPU_SSPP_SCALER_QSEED3) || + cfg->features & BIT(DPU_SSPP_SCALER_QSEED2) || + cfg->features & BIT(DPU_SSPP_SCALER_QSEED4)) { + dpu_debugfs_setup_regset32(&pdpu->debugfs_scaler, + sblk->scaler_blk.base + cfg->base, + sblk->scaler_blk.len, + kms); + dpu_debugfs_create_regset32("scaler_blk", 0400, + pdpu->debugfs_root, + &pdpu->debugfs_scaler); + debugfs_create_bool("default_scaling", + 0600, + pdpu->debugfs_root, + &pdpu->debugfs_default_scale); + } + + if (cfg->features & BIT(DPU_SSPP_CSC) || + cfg->features & BIT(DPU_SSPP_CSC_10BIT)) { + dpu_debugfs_setup_regset32(&pdpu->debugfs_csc, + sblk->csc_blk.base + cfg->base, + sblk->csc_blk.len, + kms); + dpu_debugfs_create_regset32("csc_blk", 0400, + pdpu->debugfs_root, &pdpu->debugfs_csc); + } + + debugfs_create_u32("xin_id", + 0400, + pdpu->debugfs_root, + (u32 *) &cfg->xin_id); + debugfs_create_u32("clk_ctrl", + 0400, + pdpu->debugfs_root, + (u32 *) &cfg->clk_ctrl); + debugfs_create_x32("creq_vblank", + 0600, + pdpu->debugfs_root, + (u32 *) &sblk->creq_vblank); + debugfs_create_x32("danger_vblank", + 0600, + pdpu->debugfs_root, + (u32 *) &sblk->danger_vblank); + + debugfs_create_file("disable_danger", + 0600, + pdpu->debugfs_root, + kms, &dpu_plane_danger_enable); + + return 0; +} +#else +static int _dpu_plane_init_debugfs(struct drm_plane *plane) +{ + return 0; +} +#endif + +static int dpu_plane_late_register(struct drm_plane *plane) +{ + return _dpu_plane_init_debugfs(plane); +} + +static void dpu_plane_early_unregister(struct drm_plane *plane) +{ + struct dpu_plane *pdpu = to_dpu_plane(plane); + + debugfs_remove_recursive(pdpu->debugfs_root); +} + +static bool dpu_plane_format_mod_supported(struct drm_plane *plane, + uint32_t format, uint64_t modifier) +{ + if (modifier == DRM_FORMAT_MOD_LINEAR) + return true; + + if (modifier == DRM_FORMAT_MOD_QCOM_COMPRESSED) { + int i; + for (i = 0; i < ARRAY_SIZE(qcom_compressed_supported_formats); i++) { + if (format == qcom_compressed_supported_formats[i]) + return true; + } + } + + return false; +} + +static const struct drm_plane_funcs dpu_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = dpu_plane_destroy, + .reset = dpu_plane_reset, + .atomic_duplicate_state = dpu_plane_duplicate_state, + .atomic_destroy_state = dpu_plane_destroy_state, + .late_register = dpu_plane_late_register, + .early_unregister = dpu_plane_early_unregister, + .format_mod_supported = dpu_plane_format_mod_supported, +}; + +static const struct drm_plane_helper_funcs dpu_plane_helper_funcs = { + .prepare_fb = dpu_plane_prepare_fb, + .cleanup_fb = dpu_plane_cleanup_fb, + .atomic_check = dpu_plane_atomic_check, + .atomic_update = dpu_plane_atomic_update, +}; + +enum dpu_sspp dpu_plane_pipe(struct drm_plane *plane) +{ + return plane ? to_dpu_plane(plane)->pipe : SSPP_NONE; +} + +bool is_dpu_plane_virtual(struct drm_plane *plane) +{ + return plane ? to_dpu_plane(plane)->is_virtual : false; +} + +/* initialize plane */ +struct drm_plane *dpu_plane_init(struct drm_device *dev, + uint32_t pipe, enum drm_plane_type type, + unsigned long possible_crtcs, u32 master_plane_id) +{ + struct drm_plane *plane = NULL, *master_plane = NULL; + const uint32_t *format_list; + struct dpu_plane *pdpu; + struct msm_drm_private *priv = dev->dev_private; + struct dpu_kms *kms = to_dpu_kms(priv->kms); + int zpos_max = DPU_ZPOS_MAX; + uint32_t num_formats; + int ret = -EINVAL; + + /* create and zero local structure */ + pdpu = kzalloc(sizeof(*pdpu), GFP_KERNEL); + if (!pdpu) { + DPU_ERROR("[%u]failed to allocate local plane struct\n", pipe); + ret = -ENOMEM; + return ERR_PTR(ret); + } + + /* cache local stuff for later */ + plane = &pdpu->base; + pdpu->pipe = pipe; + pdpu->is_virtual = (master_plane_id != 0); + INIT_LIST_HEAD(&pdpu->mplane_list); + master_plane = drm_plane_find(dev, NULL, master_plane_id); + if (master_plane) { + struct dpu_plane *mpdpu = to_dpu_plane(master_plane); + + list_add_tail(&pdpu->mplane_list, &mpdpu->mplane_list); + } + + /* initialize underlying h/w driver */ + pdpu->pipe_hw = dpu_hw_sspp_init(pipe, kms->mmio, kms->catalog, + master_plane_id != 0); + if (IS_ERR(pdpu->pipe_hw)) { + DPU_ERROR("[%u]SSPP init failed\n", pipe); + ret = PTR_ERR(pdpu->pipe_hw); + goto clean_plane; + } else if (!pdpu->pipe_hw->cap || !pdpu->pipe_hw->cap->sblk) { + DPU_ERROR("[%u]SSPP init returned invalid cfg\n", pipe); + goto clean_sspp; + } + + /* cache features mask for later */ + pdpu->features = pdpu->pipe_hw->cap->features; + pdpu->pipe_sblk = pdpu->pipe_hw->cap->sblk; + if (!pdpu->pipe_sblk) { + DPU_ERROR("[%u]invalid sblk\n", pipe); + goto clean_sspp; + } + + if (pdpu->is_virtual) { + format_list = pdpu->pipe_sblk->virt_format_list; + num_formats = pdpu->pipe_sblk->virt_num_formats; + } + else { + format_list = pdpu->pipe_sblk->format_list; + num_formats = pdpu->pipe_sblk->num_formats; + } + + ret = drm_universal_plane_init(dev, plane, 0xff, &dpu_plane_funcs, + format_list, num_formats, + supported_format_modifiers, type, NULL); + if (ret) + goto clean_sspp; + + pdpu->catalog = kms->catalog; + + if (kms->catalog->mixer_count && + kms->catalog->mixer[0].sblk->maxblendstages) { + zpos_max = kms->catalog->mixer[0].sblk->maxblendstages - 1; + if (zpos_max > DPU_STAGE_MAX - DPU_STAGE_0 - 1) + zpos_max = DPU_STAGE_MAX - DPU_STAGE_0 - 1; + } + + ret = drm_plane_create_zpos_property(plane, 0, 0, zpos_max); + if (ret) + DPU_ERROR("failed to install zpos property, rc = %d\n", ret); + + drm_plane_create_rotation_property(plane, + DRM_MODE_ROTATE_0, + DRM_MODE_ROTATE_0 | + DRM_MODE_ROTATE_180 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y); + + drm_plane_enable_fb_damage_clips(plane); + + /* success! finalize initialization */ + drm_plane_helper_add(plane, &dpu_plane_helper_funcs); + + /* save user friendly pipe name for later */ + snprintf(pdpu->pipe_name, DPU_NAME_SIZE, "plane%u", plane->base.id); + + mutex_init(&pdpu->lock); + + DPU_DEBUG("%s created for pipe:%u id:%u virtual:%u\n", pdpu->pipe_name, + pipe, plane->base.id, master_plane_id); + return plane; + +clean_sspp: + if (pdpu && pdpu->pipe_hw) + dpu_hw_sspp_destroy(pdpu->pipe_hw); +clean_plane: + kfree(pdpu); + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.h new file mode 100644 index 000000000..ca83b8753 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_plane.h @@ -0,0 +1,141 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#ifndef _DPU_PLANE_H_ +#define _DPU_PLANE_H_ + +#include <drm/drm_crtc.h> + +#include "dpu_kms.h" +#include "dpu_hw_mdss.h" +#include "dpu_hw_sspp.h" + +/** + * struct dpu_plane_state: Define dpu extension of drm plane state object + * @base: base drm plane state object + * @aspace: pointer to address space for input/output buffers + * @stage: assigned by crtc blender + * @multirect_index: index of the rectangle of SSPP + * @multirect_mode: parallel or time multiplex multirect mode + * @pending: whether the current update is still pending + * @scaler3_cfg: configuration data for scaler3 + * @pixel_ext: configuration data for pixel extensions + * @cdp_cfg: CDP configuration + * @plane_fetch_bw: calculated BW per plane + * @plane_clk: calculated clk per plane + */ +struct dpu_plane_state { + struct drm_plane_state base; + struct msm_gem_address_space *aspace; + enum dpu_stage stage; + uint32_t multirect_index; + uint32_t multirect_mode; + bool pending; + + /* scaler configuration */ + struct dpu_hw_scaler3_cfg scaler3_cfg; + struct dpu_hw_pixel_ext pixel_ext; + + struct dpu_hw_pipe_cdp_cfg cdp_cfg; + u64 plane_fetch_bw; + u64 plane_clk; +}; + +/** + * struct dpu_multirect_plane_states: Defines multirect pair of drm plane states + * @r0: drm plane configured on rect 0 + * @r1: drm plane configured on rect 1 + */ +struct dpu_multirect_plane_states { + const struct drm_plane_state *r0; + const struct drm_plane_state *r1; +}; + +#define to_dpu_plane_state(x) \ + container_of(x, struct dpu_plane_state, base) + +/** + * dpu_plane_pipe - return sspp identifier for the given plane + * @plane: Pointer to DRM plane object + * Returns: sspp identifier of the given plane + */ +enum dpu_sspp dpu_plane_pipe(struct drm_plane *plane); + +/** + * is_dpu_plane_virtual - check for virtual plane + * @plane: Pointer to DRM plane object + * returns: true - if the plane is virtual + * false - if the plane is primary + */ +bool is_dpu_plane_virtual(struct drm_plane *plane); + +/** + * dpu_plane_get_ctl_flush - get control flush mask + * @plane: Pointer to DRM plane object + * @ctl: Pointer to control hardware + * @flush_sspp: Pointer to sspp flush control word + */ +void dpu_plane_get_ctl_flush(struct drm_plane *plane, struct dpu_hw_ctl *ctl, + u32 *flush_sspp); + +/** + * dpu_plane_restore - restore hw state if previously power collapsed + * @plane: Pointer to drm plane structure + */ +void dpu_plane_restore(struct drm_plane *plane); + +/** + * dpu_plane_flush - final plane operations before commit flush + * @plane: Pointer to drm plane structure + */ +void dpu_plane_flush(struct drm_plane *plane); + +/** + * dpu_plane_set_error: enable/disable error condition + * @plane: pointer to drm_plane structure + */ +void dpu_plane_set_error(struct drm_plane *plane, bool error); + +/** + * dpu_plane_init - create new dpu plane for the given pipe + * @dev: Pointer to DRM device + * @pipe: dpu hardware pipe identifier + * @type: Plane type - PRIMARY/OVERLAY/CURSOR + * @possible_crtcs: bitmask of crtc that can be attached to the given pipe + * @master_plane_id: primary plane id of a multirect pipe. 0 value passed for + * a regular plane initialization. A non-zero primary plane + * id will be passed for a virtual pipe initialization. + * + */ +struct drm_plane *dpu_plane_init(struct drm_device *dev, + uint32_t pipe, enum drm_plane_type type, + unsigned long possible_crtcs, u32 master_plane_id); + +/** + * dpu_plane_validate_multirecti_v2 - validate the multirect planes + * against hw limitations + * @plane: drm plate states of the multirect pair + */ +int dpu_plane_validate_multirect_v2(struct dpu_multirect_plane_states *plane); + +/** + * dpu_plane_clear_multirect - clear multirect bits for the given pipe + * @drm_state: Pointer to DRM plane state + */ +void dpu_plane_clear_multirect(const struct drm_plane_state *drm_state); + +/** + * dpu_plane_color_fill - enables color fill on plane + * @plane: Pointer to DRM plane object + * @color: RGB fill color value, [23..16] Blue, [15..8] Green, [7..0] Red + * @alpha: 8-bit fill alpha value, 255 selects 100% alpha + * Returns: 0 on success + */ +int dpu_plane_color_fill(struct drm_plane *plane, + uint32_t color, uint32_t alpha); + +#endif /* _DPU_PLANE_H_ */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c new file mode 100644 index 000000000..948300529 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.c @@ -0,0 +1,645 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm:%s] " fmt, __func__ +#include "dpu_kms.h" +#include "dpu_hw_lm.h" +#include "dpu_hw_ctl.h" +#include "dpu_hw_pingpong.h" +#include "dpu_hw_intf.h" +#include "dpu_hw_dspp.h" +#include "dpu_encoder.h" +#include "dpu_trace.h" + + +static inline bool reserved_by_other(uint32_t *res_map, int idx, + uint32_t enc_id) +{ + return res_map[idx] && res_map[idx] != enc_id; +} + +/** + * struct dpu_rm_requirements - Reservation requirements parameter bundle + * @topology: selected topology for the display + * @hw_res: Hardware resources required as reported by the encoders + */ +struct dpu_rm_requirements { + struct msm_display_topology topology; + struct dpu_encoder_hw_resources hw_res; +}; + +int dpu_rm_destroy(struct dpu_rm *rm) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rm->dspp_blks); i++) { + struct dpu_hw_dspp *hw; + + if (rm->dspp_blks[i]) { + hw = to_dpu_hw_dspp(rm->dspp_blks[i]); + dpu_hw_dspp_destroy(hw); + } + } + for (i = 0; i < ARRAY_SIZE(rm->pingpong_blks); i++) { + struct dpu_hw_pingpong *hw; + + if (rm->pingpong_blks[i]) { + hw = to_dpu_hw_pingpong(rm->pingpong_blks[i]); + dpu_hw_pingpong_destroy(hw); + } + } + for (i = 0; i < ARRAY_SIZE(rm->mixer_blks); i++) { + struct dpu_hw_mixer *hw; + + if (rm->mixer_blks[i]) { + hw = to_dpu_hw_mixer(rm->mixer_blks[i]); + dpu_hw_lm_destroy(hw); + } + } + for (i = 0; i < ARRAY_SIZE(rm->ctl_blks); i++) { + struct dpu_hw_ctl *hw; + + if (rm->ctl_blks[i]) { + hw = to_dpu_hw_ctl(rm->ctl_blks[i]); + dpu_hw_ctl_destroy(hw); + } + } + for (i = 0; i < ARRAY_SIZE(rm->intf_blks); i++) { + struct dpu_hw_intf *hw; + + if (rm->intf_blks[i]) { + hw = to_dpu_hw_intf(rm->intf_blks[i]); + dpu_hw_intf_destroy(hw); + } + } + + return 0; +} + +int dpu_rm_init(struct dpu_rm *rm, + struct dpu_mdss_cfg *cat, + void __iomem *mmio) +{ + int rc, i; + + if (!rm || !cat || !mmio) { + DPU_ERROR("invalid kms\n"); + return -EINVAL; + } + + /* Clear, setup lists */ + memset(rm, 0, sizeof(*rm)); + + /* Interrogate HW catalog and create tracking items for hw blocks */ + for (i = 0; i < cat->mixer_count; i++) { + struct dpu_hw_mixer *hw; + const struct dpu_lm_cfg *lm = &cat->mixer[i]; + + if (lm->pingpong == PINGPONG_MAX) { + DPU_DEBUG("skip mixer %d without pingpong\n", lm->id); + continue; + } + + if (lm->id < LM_0 || lm->id >= LM_MAX) { + DPU_ERROR("skip mixer %d with invalid id\n", lm->id); + continue; + } + hw = dpu_hw_lm_init(lm->id, mmio, cat); + if (IS_ERR_OR_NULL(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed lm object creation: err %d\n", rc); + goto fail; + } + rm->mixer_blks[lm->id - LM_0] = &hw->base; + + if (!rm->lm_max_width) { + rm->lm_max_width = lm->sblk->maxwidth; + } else if (rm->lm_max_width != lm->sblk->maxwidth) { + /* + * Don't expect to have hw where lm max widths differ. + * If found, take the min. + */ + DPU_ERROR("unsupported: lm maxwidth differs\n"); + if (rm->lm_max_width > lm->sblk->maxwidth) + rm->lm_max_width = lm->sblk->maxwidth; + } + } + + for (i = 0; i < cat->pingpong_count; i++) { + struct dpu_hw_pingpong *hw; + const struct dpu_pingpong_cfg *pp = &cat->pingpong[i]; + + if (pp->id < PINGPONG_0 || pp->id >= PINGPONG_MAX) { + DPU_ERROR("skip pingpong %d with invalid id\n", pp->id); + continue; + } + hw = dpu_hw_pingpong_init(pp->id, mmio, cat); + if (IS_ERR_OR_NULL(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed pingpong object creation: err %d\n", + rc); + goto fail; + } + rm->pingpong_blks[pp->id - PINGPONG_0] = &hw->base; + } + + for (i = 0; i < cat->intf_count; i++) { + struct dpu_hw_intf *hw; + const struct dpu_intf_cfg *intf = &cat->intf[i]; + + if (intf->type == INTF_NONE) { + DPU_DEBUG("skip intf %d with type none\n", i); + continue; + } + if (intf->id < INTF_0 || intf->id >= INTF_MAX) { + DPU_ERROR("skip intf %d with invalid id\n", intf->id); + continue; + } + hw = dpu_hw_intf_init(intf->id, mmio, cat); + if (IS_ERR_OR_NULL(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed intf object creation: err %d\n", rc); + goto fail; + } + rm->intf_blks[intf->id - INTF_0] = &hw->base; + } + + for (i = 0; i < cat->ctl_count; i++) { + struct dpu_hw_ctl *hw; + const struct dpu_ctl_cfg *ctl = &cat->ctl[i]; + + if (ctl->id < CTL_0 || ctl->id >= CTL_MAX) { + DPU_ERROR("skip ctl %d with invalid id\n", ctl->id); + continue; + } + hw = dpu_hw_ctl_init(ctl->id, mmio, cat); + if (IS_ERR_OR_NULL(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed ctl object creation: err %d\n", rc); + goto fail; + } + rm->ctl_blks[ctl->id - CTL_0] = &hw->base; + } + + for (i = 0; i < cat->dspp_count; i++) { + struct dpu_hw_dspp *hw; + const struct dpu_dspp_cfg *dspp = &cat->dspp[i]; + + if (dspp->id < DSPP_0 || dspp->id >= DSPP_MAX) { + DPU_ERROR("skip dspp %d with invalid id\n", dspp->id); + continue; + } + hw = dpu_hw_dspp_init(dspp->id, mmio, cat); + if (IS_ERR_OR_NULL(hw)) { + rc = PTR_ERR(hw); + DPU_ERROR("failed dspp object creation: err %d\n", rc); + goto fail; + } + rm->dspp_blks[dspp->id - DSPP_0] = &hw->base; + } + + return 0; + +fail: + dpu_rm_destroy(rm); + + return rc ? rc : -EFAULT; +} + +static bool _dpu_rm_needs_split_display(const struct msm_display_topology *top) +{ + return top->num_intf > 1; +} + +/** + * _dpu_rm_check_lm_peer - check if a mixer is a peer of the primary + * @rm: dpu resource manager handle + * @primary_idx: index of primary mixer in rm->mixer_blks[] + * @peer_idx: index of other mixer in rm->mixer_blks[] + * @Return: true if rm->mixer_blks[peer_idx] is a peer of + * rm->mixer_blks[primary_idx] + */ +static bool _dpu_rm_check_lm_peer(struct dpu_rm *rm, int primary_idx, + int peer_idx) +{ + const struct dpu_lm_cfg *prim_lm_cfg; + const struct dpu_lm_cfg *peer_cfg; + + prim_lm_cfg = to_dpu_hw_mixer(rm->mixer_blks[primary_idx])->cap; + peer_cfg = to_dpu_hw_mixer(rm->mixer_blks[peer_idx])->cap; + + if (!test_bit(peer_cfg->id, &prim_lm_cfg->lm_pair_mask)) { + DPU_DEBUG("lm %d not peer of lm %d\n", peer_cfg->id, + peer_cfg->id); + return false; + } + return true; +} + +/** + * _dpu_rm_check_lm_and_get_connected_blks - check if proposed layer mixer meets + * proposed use case requirements, incl. hardwired dependent blocks like + * pingpong + * @rm: dpu resource manager handle + * @enc_id: encoder id requesting for allocation + * @lm_idx: index of proposed layer mixer in rm->mixer_blks[], function checks + * if lm, and all other hardwired blocks connected to the lm (pp) is + * available and appropriate + * @pp_idx: output parameter, index of pingpong block attached to the layer + * mixer in rm->pingpong_blks[]. + * @dspp_idx: output parameter, index of dspp block attached to the layer + * mixer in rm->dspp_blks[]. + * @reqs: input parameter, rm requirements for HW blocks needed in the + * datapath. + * @Return: true if lm matches all requirements, false otherwise + */ +static bool _dpu_rm_check_lm_and_get_connected_blks(struct dpu_rm *rm, + struct dpu_global_state *global_state, + uint32_t enc_id, int lm_idx, int *pp_idx, int *dspp_idx, + struct dpu_rm_requirements *reqs) +{ + const struct dpu_lm_cfg *lm_cfg; + int idx; + + /* Already reserved? */ + if (reserved_by_other(global_state->mixer_to_enc_id, lm_idx, enc_id)) { + DPU_DEBUG("lm %d already reserved\n", lm_idx + LM_0); + return false; + } + + lm_cfg = to_dpu_hw_mixer(rm->mixer_blks[lm_idx])->cap; + idx = lm_cfg->pingpong - PINGPONG_0; + if (idx < 0 || idx >= ARRAY_SIZE(rm->pingpong_blks)) { + DPU_ERROR("failed to get pp on lm %d\n", lm_cfg->pingpong); + return false; + } + + if (reserved_by_other(global_state->pingpong_to_enc_id, idx, enc_id)) { + DPU_DEBUG("lm %d pp %d already reserved\n", lm_cfg->id, + lm_cfg->pingpong); + return false; + } + *pp_idx = idx; + + if (!reqs->topology.num_dspp) + return true; + + idx = lm_cfg->dspp - DSPP_0; + if (idx < 0 || idx >= ARRAY_SIZE(rm->dspp_blks)) { + DPU_ERROR("failed to get dspp on lm %d\n", lm_cfg->dspp); + return false; + } + + if (reserved_by_other(global_state->dspp_to_enc_id, idx, enc_id)) { + DPU_DEBUG("lm %d dspp %d already reserved\n", lm_cfg->id, + lm_cfg->dspp); + return false; + } + *dspp_idx = idx; + + return true; +} + +static int _dpu_rm_reserve_lms(struct dpu_rm *rm, + struct dpu_global_state *global_state, + uint32_t enc_id, + struct dpu_rm_requirements *reqs) + +{ + int lm_idx[MAX_BLOCKS]; + int pp_idx[MAX_BLOCKS]; + int dspp_idx[MAX_BLOCKS] = {0}; + int i, j, lm_count = 0; + + if (!reqs->topology.num_lm) { + DPU_ERROR("invalid number of lm: %d\n", reqs->topology.num_lm); + return -EINVAL; + } + + /* Find a primary mixer */ + for (i = 0; i < ARRAY_SIZE(rm->mixer_blks) && + lm_count < reqs->topology.num_lm; i++) { + if (!rm->mixer_blks[i]) + continue; + + lm_count = 0; + lm_idx[lm_count] = i; + + if (!_dpu_rm_check_lm_and_get_connected_blks(rm, global_state, + enc_id, i, &pp_idx[lm_count], + &dspp_idx[lm_count], reqs)) { + continue; + } + + ++lm_count; + + /* Valid primary mixer found, find matching peers */ + for (j = i + 1; j < ARRAY_SIZE(rm->mixer_blks) && + lm_count < reqs->topology.num_lm; j++) { + if (!rm->mixer_blks[j]) + continue; + + if (!_dpu_rm_check_lm_peer(rm, i, j)) { + DPU_DEBUG("lm %d not peer of lm %d\n", LM_0 + j, + LM_0 + i); + continue; + } + + if (!_dpu_rm_check_lm_and_get_connected_blks(rm, + global_state, enc_id, j, + &pp_idx[lm_count], &dspp_idx[lm_count], + reqs)) { + continue; + } + + lm_idx[lm_count] = j; + ++lm_count; + } + } + + if (lm_count != reqs->topology.num_lm) { + DPU_DEBUG("unable to find appropriate mixers\n"); + return -ENAVAIL; + } + + for (i = 0; i < lm_count; i++) { + global_state->mixer_to_enc_id[lm_idx[i]] = enc_id; + global_state->pingpong_to_enc_id[pp_idx[i]] = enc_id; + global_state->dspp_to_enc_id[dspp_idx[i]] = + reqs->topology.num_dspp ? enc_id : 0; + + trace_dpu_rm_reserve_lms(lm_idx[i] + LM_0, enc_id, + pp_idx[i] + PINGPONG_0); + } + + return 0; +} + +static int _dpu_rm_reserve_ctls( + struct dpu_rm *rm, + struct dpu_global_state *global_state, + uint32_t enc_id, + const struct msm_display_topology *top) +{ + int ctl_idx[MAX_BLOCKS]; + int i = 0, j, num_ctls; + bool needs_split_display; + + /* each hw_intf needs its own hw_ctrl to program its control path */ + num_ctls = top->num_intf; + + needs_split_display = _dpu_rm_needs_split_display(top); + + for (j = 0; j < ARRAY_SIZE(rm->ctl_blks); j++) { + const struct dpu_hw_ctl *ctl; + unsigned long features; + bool has_split_display; + + if (!rm->ctl_blks[j]) + continue; + if (reserved_by_other(global_state->ctl_to_enc_id, j, enc_id)) + continue; + + ctl = to_dpu_hw_ctl(rm->ctl_blks[j]); + features = ctl->caps->features; + has_split_display = BIT(DPU_CTL_SPLIT_DISPLAY) & features; + + DPU_DEBUG("ctl %d caps 0x%lX\n", rm->ctl_blks[j]->id, features); + + if (needs_split_display != has_split_display) + continue; + + ctl_idx[i] = j; + DPU_DEBUG("ctl %d match\n", j + CTL_0); + + if (++i == num_ctls) + break; + + } + + if (i != num_ctls) + return -ENAVAIL; + + for (i = 0; i < ARRAY_SIZE(ctl_idx) && i < num_ctls; i++) { + global_state->ctl_to_enc_id[ctl_idx[i]] = enc_id; + trace_dpu_rm_reserve_ctls(i + CTL_0, enc_id); + } + + return 0; +} + +static int _dpu_rm_reserve_intf( + struct dpu_rm *rm, + struct dpu_global_state *global_state, + uint32_t enc_id, + uint32_t id) +{ + int idx = id - INTF_0; + + if (idx < 0 || idx >= ARRAY_SIZE(rm->intf_blks)) { + DPU_ERROR("invalid intf id: %d", id); + return -EINVAL; + } + + if (!rm->intf_blks[idx]) { + DPU_ERROR("couldn't find intf id %d\n", id); + return -EINVAL; + } + + if (reserved_by_other(global_state->intf_to_enc_id, idx, enc_id)) { + DPU_ERROR("intf id %d already reserved\n", id); + return -ENAVAIL; + } + + global_state->intf_to_enc_id[idx] = enc_id; + return 0; +} + +static int _dpu_rm_reserve_intf_related_hw( + struct dpu_rm *rm, + struct dpu_global_state *global_state, + uint32_t enc_id, + struct dpu_encoder_hw_resources *hw_res) +{ + int i, ret = 0; + u32 id; + + for (i = 0; i < ARRAY_SIZE(hw_res->intfs); i++) { + if (hw_res->intfs[i] == INTF_MODE_NONE) + continue; + id = i + INTF_0; + ret = _dpu_rm_reserve_intf(rm, global_state, enc_id, id); + if (ret) + return ret; + } + + return ret; +} + +static int _dpu_rm_make_reservation( + struct dpu_rm *rm, + struct dpu_global_state *global_state, + struct drm_encoder *enc, + struct dpu_rm_requirements *reqs) +{ + int ret; + + ret = _dpu_rm_reserve_lms(rm, global_state, enc->base.id, reqs); + if (ret) { + DPU_ERROR("unable to find appropriate mixers\n"); + return ret; + } + + ret = _dpu_rm_reserve_ctls(rm, global_state, enc->base.id, + &reqs->topology); + if (ret) { + DPU_ERROR("unable to find appropriate CTL\n"); + return ret; + } + + ret = _dpu_rm_reserve_intf_related_hw(rm, global_state, enc->base.id, + &reqs->hw_res); + if (ret) + return ret; + + return ret; +} + +static int _dpu_rm_populate_requirements( + struct drm_encoder *enc, + struct dpu_rm_requirements *reqs, + struct msm_display_topology req_topology) +{ + dpu_encoder_get_hw_resources(enc, &reqs->hw_res); + + reqs->topology = req_topology; + + DRM_DEBUG_KMS("num_lm: %d num_enc: %d num_intf: %d\n", + reqs->topology.num_lm, reqs->topology.num_enc, + reqs->topology.num_intf); + + return 0; +} + +static void _dpu_rm_clear_mapping(uint32_t *res_mapping, int cnt, + uint32_t enc_id) +{ + int i; + + for (i = 0; i < cnt; i++) { + if (res_mapping[i] == enc_id) + res_mapping[i] = 0; + } +} + +void dpu_rm_release(struct dpu_global_state *global_state, + struct drm_encoder *enc) +{ + _dpu_rm_clear_mapping(global_state->pingpong_to_enc_id, + ARRAY_SIZE(global_state->pingpong_to_enc_id), enc->base.id); + _dpu_rm_clear_mapping(global_state->mixer_to_enc_id, + ARRAY_SIZE(global_state->mixer_to_enc_id), enc->base.id); + _dpu_rm_clear_mapping(global_state->ctl_to_enc_id, + ARRAY_SIZE(global_state->ctl_to_enc_id), enc->base.id); + _dpu_rm_clear_mapping(global_state->intf_to_enc_id, + ARRAY_SIZE(global_state->intf_to_enc_id), enc->base.id); +} + +int dpu_rm_reserve( + struct dpu_rm *rm, + struct dpu_global_state *global_state, + struct drm_encoder *enc, + struct drm_crtc_state *crtc_state, + struct msm_display_topology topology) +{ + struct dpu_rm_requirements reqs; + int ret; + + /* Check if this is just a page-flip */ + if (!drm_atomic_crtc_needs_modeset(crtc_state)) + return 0; + + if (IS_ERR(global_state)) { + DPU_ERROR("failed to global state\n"); + return PTR_ERR(global_state); + } + + DRM_DEBUG_KMS("reserving hw for enc %d crtc %d\n", + enc->base.id, crtc_state->crtc->base.id); + + ret = _dpu_rm_populate_requirements(enc, &reqs, topology); + if (ret) { + DPU_ERROR("failed to populate hw requirements\n"); + return ret; + } + + ret = _dpu_rm_make_reservation(rm, global_state, enc, &reqs); + if (ret) + DPU_ERROR("failed to reserve hw resources: %d\n", ret); + + + + return ret; +} + +int dpu_rm_get_assigned_resources(struct dpu_rm *rm, + struct dpu_global_state *global_state, uint32_t enc_id, + enum dpu_hw_blk_type type, struct dpu_hw_blk **blks, int blks_size) +{ + struct dpu_hw_blk **hw_blks; + uint32_t *hw_to_enc_id; + int i, num_blks, max_blks; + + switch (type) { + case DPU_HW_BLK_PINGPONG: + hw_blks = rm->pingpong_blks; + hw_to_enc_id = global_state->pingpong_to_enc_id; + max_blks = ARRAY_SIZE(rm->pingpong_blks); + break; + case DPU_HW_BLK_LM: + hw_blks = rm->mixer_blks; + hw_to_enc_id = global_state->mixer_to_enc_id; + max_blks = ARRAY_SIZE(rm->mixer_blks); + break; + case DPU_HW_BLK_CTL: + hw_blks = rm->ctl_blks; + hw_to_enc_id = global_state->ctl_to_enc_id; + max_blks = ARRAY_SIZE(rm->ctl_blks); + break; + case DPU_HW_BLK_INTF: + hw_blks = rm->intf_blks; + hw_to_enc_id = global_state->intf_to_enc_id; + max_blks = ARRAY_SIZE(rm->intf_blks); + break; + case DPU_HW_BLK_DSPP: + hw_blks = rm->dspp_blks; + hw_to_enc_id = global_state->dspp_to_enc_id; + max_blks = ARRAY_SIZE(rm->dspp_blks); + break; + default: + DPU_ERROR("blk type %d not managed by rm\n", type); + return 0; + } + + num_blks = 0; + for (i = 0; i < max_blks; i++) { + if (hw_to_enc_id[i] != enc_id) + continue; + + if (num_blks == blks_size) { + DPU_ERROR("More than %d resources assigned to enc %d\n", + blks_size, enc_id); + break; + } + if (!hw_blks[i]) { + DPU_ERROR("Allocated resource %d unavailable to assign to enc %d\n", + type, enc_id); + break; + } + blks[num_blks++] = hw_blks[i]; + } + + return num_blks; +} diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.h new file mode 100644 index 000000000..08726bb10 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_rm.h @@ -0,0 +1,91 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef __DPU_RM_H__ +#define __DPU_RM_H__ + +#include <linux/list.h> + +#include "msm_kms.h" +#include "dpu_hw_top.h" + +struct dpu_global_state; + +/** + * struct dpu_rm - DPU dynamic hardware resource manager + * @pingpong_blks: array of pingpong hardware resources + * @mixer_blks: array of layer mixer hardware resources + * @ctl_blks: array of ctl hardware resources + * @intf_blks: array of intf hardware resources + * @dspp_blks: array of dspp hardware resources + * @lm_max_width: cached layer mixer maximum width + * @rm_lock: resource manager mutex + */ +struct dpu_rm { + struct dpu_hw_blk *pingpong_blks[PINGPONG_MAX - PINGPONG_0]; + struct dpu_hw_blk *mixer_blks[LM_MAX - LM_0]; + struct dpu_hw_blk *ctl_blks[CTL_MAX - CTL_0]; + struct dpu_hw_blk *intf_blks[INTF_MAX - INTF_0]; + struct dpu_hw_blk *dspp_blks[DSPP_MAX - DSPP_0]; + + uint32_t lm_max_width; +}; + +/** + * dpu_rm_init - Read hardware catalog and create reservation tracking objects + * for all HW blocks. + * @rm: DPU Resource Manager handle + * @cat: Pointer to hardware catalog + * @mmio: mapped register io address of MDP + * @Return: 0 on Success otherwise -ERROR + */ +int dpu_rm_init(struct dpu_rm *rm, + struct dpu_mdss_cfg *cat, + void __iomem *mmio); + +/** + * dpu_rm_destroy - Free all memory allocated by dpu_rm_init + * @rm: DPU Resource Manager handle + * @Return: 0 on Success otherwise -ERROR + */ +int dpu_rm_destroy(struct dpu_rm *rm); + +/** + * dpu_rm_reserve - Given a CRTC->Encoder->Connector display chain, analyze + * the use connections and user requirements, specified through related + * topology control properties, and reserve hardware blocks to that + * display chain. + * HW blocks can then be accessed through dpu_rm_get_* functions. + * HW Reservations should be released via dpu_rm_release_hw. + * @rm: DPU Resource Manager handle + * @drm_enc: DRM Encoder handle + * @crtc_state: Proposed Atomic DRM CRTC State handle + * @topology: Pointer to topology info for the display + * @Return: 0 on Success otherwise -ERROR + */ +int dpu_rm_reserve(struct dpu_rm *rm, + struct dpu_global_state *global_state, + struct drm_encoder *drm_enc, + struct drm_crtc_state *crtc_state, + struct msm_display_topology topology); + +/** + * dpu_rm_reserve - Given the encoder for the display chain, release any + * HW blocks previously reserved for that use case. + * @rm: DPU Resource Manager handle + * @enc: DRM Encoder handle + * @Return: 0 on Success otherwise -ERROR + */ +void dpu_rm_release(struct dpu_global_state *global_state, + struct drm_encoder *enc); + +/** + * Get hw resources of the given type that are assigned to this encoder. + */ +int dpu_rm_get_assigned_resources(struct dpu_rm *rm, + struct dpu_global_state *global_state, uint32_t enc_id, + enum dpu_hw_blk_type type, struct dpu_hw_blk **blks, int blks_size); +#endif /* __DPU_RM_H__ */ + diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_trace.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_trace.h new file mode 100644 index 000000000..6714b0889 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_trace.h @@ -0,0 +1,1004 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2014-2018, The Linux Foundation. All rights reserved. + */ + +#if !defined(_DPU_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ) +#define _DPU_TRACE_H_ + +#include <linux/stringify.h> +#include <linux/types.h> +#include <linux/tracepoint.h> + +#include <drm/drm_rect.h> +#include "dpu_crtc.h" +#include "dpu_encoder_phys.h" +#include "dpu_hw_mdss.h" +#include "dpu_hw_vbif.h" +#include "dpu_plane.h" + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM dpu +#undef TRACE_INCLUDE_FILE +#define TRACE_INCLUDE_FILE dpu_trace + +TRACE_EVENT(dpu_perf_set_qos_luts, + TP_PROTO(u32 pnum, u32 fmt, bool rt, u32 fl, + u32 lut, u32 lut_usage), + TP_ARGS(pnum, fmt, rt, fl, lut, lut_usage), + TP_STRUCT__entry( + __field(u32, pnum) + __field(u32, fmt) + __field(bool, rt) + __field(u32, fl) + __field(u64, lut) + __field(u32, lut_usage) + ), + TP_fast_assign( + __entry->pnum = pnum; + __entry->fmt = fmt; + __entry->rt = rt; + __entry->fl = fl; + __entry->lut = lut; + __entry->lut_usage = lut_usage; + ), + TP_printk("pnum=%d fmt=%x rt=%d fl=%d lut=0x%llx lut_usage=%d", + __entry->pnum, __entry->fmt, + __entry->rt, __entry->fl, + __entry->lut, __entry->lut_usage) +); + +TRACE_EVENT(dpu_perf_set_danger_luts, + TP_PROTO(u32 pnum, u32 fmt, u32 mode, u32 danger_lut, + u32 safe_lut), + TP_ARGS(pnum, fmt, mode, danger_lut, safe_lut), + TP_STRUCT__entry( + __field(u32, pnum) + __field(u32, fmt) + __field(u32, mode) + __field(u32, danger_lut) + __field(u32, safe_lut) + ), + TP_fast_assign( + __entry->pnum = pnum; + __entry->fmt = fmt; + __entry->mode = mode; + __entry->danger_lut = danger_lut; + __entry->safe_lut = safe_lut; + ), + TP_printk("pnum=%d fmt=%x mode=%d luts[0x%x, 0x%x]", + __entry->pnum, __entry->fmt, + __entry->mode, __entry->danger_lut, + __entry->safe_lut) +); + +TRACE_EVENT(dpu_perf_set_ot, + TP_PROTO(u32 pnum, u32 xin_id, u32 rd_lim, u32 vbif_idx), + TP_ARGS(pnum, xin_id, rd_lim, vbif_idx), + TP_STRUCT__entry( + __field(u32, pnum) + __field(u32, xin_id) + __field(u32, rd_lim) + __field(u32, vbif_idx) + ), + TP_fast_assign( + __entry->pnum = pnum; + __entry->xin_id = xin_id; + __entry->rd_lim = rd_lim; + __entry->vbif_idx = vbif_idx; + ), + TP_printk("pnum:%d xin_id:%d ot:%d vbif:%d", + __entry->pnum, __entry->xin_id, __entry->rd_lim, + __entry->vbif_idx) +) + +TRACE_EVENT(dpu_cmd_release_bw, + TP_PROTO(u32 crtc_id), + TP_ARGS(crtc_id), + TP_STRUCT__entry( + __field(u32, crtc_id) + ), + TP_fast_assign( + __entry->crtc_id = crtc_id; + ), + TP_printk("crtc:%d", __entry->crtc_id) +); + +TRACE_EVENT(tracing_mark_write, + TP_PROTO(int pid, const char *name, bool trace_begin), + TP_ARGS(pid, name, trace_begin), + TP_STRUCT__entry( + __field(int, pid) + __string(trace_name, name) + __field(bool, trace_begin) + ), + TP_fast_assign( + __entry->pid = pid; + __assign_str(trace_name, name); + __entry->trace_begin = trace_begin; + ), + TP_printk("%s|%d|%s", __entry->trace_begin ? "B" : "E", + __entry->pid, __get_str(trace_name)) +) + +TRACE_EVENT(dpu_trace_counter, + TP_PROTO(int pid, char *name, int value), + TP_ARGS(pid, name, value), + TP_STRUCT__entry( + __field(int, pid) + __string(counter_name, name) + __field(int, value) + ), + TP_fast_assign( + __entry->pid = current->tgid; + __assign_str(counter_name, name); + __entry->value = value; + ), + TP_printk("%d|%s|%d", __entry->pid, + __get_str(counter_name), __entry->value) +) + +TRACE_EVENT(dpu_perf_crtc_update, + TP_PROTO(u32 crtc, u64 bw_ctl, u32 core_clk_rate, + bool stop_req, bool update_bus, bool update_clk), + TP_ARGS(crtc, bw_ctl, core_clk_rate, stop_req, update_bus, update_clk), + TP_STRUCT__entry( + __field(u32, crtc) + __field(u64, bw_ctl) + __field(u32, core_clk_rate) + __field(bool, stop_req) + __field(u32, update_bus) + __field(u32, update_clk) + ), + TP_fast_assign( + __entry->crtc = crtc; + __entry->bw_ctl = bw_ctl; + __entry->core_clk_rate = core_clk_rate; + __entry->stop_req = stop_req; + __entry->update_bus = update_bus; + __entry->update_clk = update_clk; + ), + TP_printk( + "crtc=%d bw_ctl=%llu clk_rate=%u stop_req=%d u_bus=%d u_clk=%d", + __entry->crtc, + __entry->bw_ctl, + __entry->core_clk_rate, + __entry->stop_req, + __entry->update_bus, + __entry->update_clk) +); + +DECLARE_EVENT_CLASS(dpu_enc_irq_template, + TP_PROTO(uint32_t drm_id, enum dpu_intr_idx intr_idx, int hw_idx, + int irq_idx), + TP_ARGS(drm_id, intr_idx, hw_idx, irq_idx), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( enum dpu_intr_idx, intr_idx ) + __field( int, hw_idx ) + __field( int, irq_idx ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->intr_idx = intr_idx; + __entry->hw_idx = hw_idx; + __entry->irq_idx = irq_idx; + ), + TP_printk("id=%u, intr=%d, hw=%d, irq=%d", + __entry->drm_id, __entry->intr_idx, __entry->hw_idx, + __entry->irq_idx) +); +DEFINE_EVENT(dpu_enc_irq_template, dpu_enc_irq_register_success, + TP_PROTO(uint32_t drm_id, enum dpu_intr_idx intr_idx, int hw_idx, + int irq_idx), + TP_ARGS(drm_id, intr_idx, hw_idx, irq_idx) +); +DEFINE_EVENT(dpu_enc_irq_template, dpu_enc_irq_unregister_success, + TP_PROTO(uint32_t drm_id, enum dpu_intr_idx intr_idx, int hw_idx, + int irq_idx), + TP_ARGS(drm_id, intr_idx, hw_idx, irq_idx) +); + +TRACE_EVENT(dpu_enc_irq_wait_success, + TP_PROTO(uint32_t drm_id, enum dpu_intr_idx intr_idx, int hw_idx, + int irq_idx, enum dpu_pingpong pp_idx, int atomic_cnt), + TP_ARGS(drm_id, intr_idx, hw_idx, irq_idx, pp_idx, atomic_cnt), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( enum dpu_intr_idx, intr_idx ) + __field( int, hw_idx ) + __field( int, irq_idx ) + __field( enum dpu_pingpong, pp_idx ) + __field( int, atomic_cnt ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->intr_idx = intr_idx; + __entry->hw_idx = hw_idx; + __entry->irq_idx = irq_idx; + __entry->pp_idx = pp_idx; + __entry->atomic_cnt = atomic_cnt; + ), + TP_printk("id=%u, intr=%d, hw=%d, irq=%d, pp=%d, atomic_cnt=%d", + __entry->drm_id, __entry->intr_idx, __entry->hw_idx, + __entry->irq_idx, __entry->pp_idx, __entry->atomic_cnt) +); + +DECLARE_EVENT_CLASS(dpu_drm_obj_template, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + ), + TP_printk("id=%u", __entry->drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_enc_atomic_check, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_enc_mode_set, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_enc_disable, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_enc_kickoff, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_enc_prepare_kickoff, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_enc_prepare_kickoff_reset, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_crtc_complete_flip, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_crtc_vblank_cb, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_crtc_complete_commit, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_kms_enc_enable, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_kms_commit, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_kms_wait_for_commit_done, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); +DEFINE_EVENT(dpu_drm_obj_template, dpu_crtc_runtime_resume, + TP_PROTO(uint32_t drm_id), + TP_ARGS(drm_id) +); + +TRACE_EVENT(dpu_enc_enable, + TP_PROTO(uint32_t drm_id, int hdisplay, int vdisplay), + TP_ARGS(drm_id, hdisplay, vdisplay), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( int, hdisplay ) + __field( int, vdisplay ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->hdisplay = hdisplay; + __entry->vdisplay = vdisplay; + ), + TP_printk("id=%u, mode=%dx%d", + __entry->drm_id, __entry->hdisplay, __entry->vdisplay) +); + +DECLARE_EVENT_CLASS(dpu_enc_keyval_template, + TP_PROTO(uint32_t drm_id, int val), + TP_ARGS(drm_id, val), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( int, val ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->val = val; + ), + TP_printk("id=%u, val=%d", __entry->drm_id, __entry->val) +); +DEFINE_EVENT(dpu_enc_keyval_template, dpu_enc_underrun_cb, + TP_PROTO(uint32_t drm_id, int count), + TP_ARGS(drm_id, count) +); +DEFINE_EVENT(dpu_enc_keyval_template, dpu_enc_trigger_start, + TP_PROTO(uint32_t drm_id, int ctl_idx), + TP_ARGS(drm_id, ctl_idx) +); + +TRACE_EVENT(dpu_enc_atomic_check_flags, + TP_PROTO(uint32_t drm_id, unsigned int flags), + TP_ARGS(drm_id, flags), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( unsigned int, flags ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->flags = flags; + ), + TP_printk("id=%u, flags=%u", + __entry->drm_id, __entry->flags) +); + +DECLARE_EVENT_CLASS(dpu_enc_id_enable_template, + TP_PROTO(uint32_t drm_id, bool enable), + TP_ARGS(drm_id, enable), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( bool, enable ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->enable = enable; + ), + TP_printk("id=%u, enable=%s", + __entry->drm_id, __entry->enable ? "true" : "false") +); +DEFINE_EVENT(dpu_enc_id_enable_template, dpu_enc_rc_helper, + TP_PROTO(uint32_t drm_id, bool enable), + TP_ARGS(drm_id, enable) +); +DEFINE_EVENT(dpu_enc_id_enable_template, dpu_enc_vblank_cb, + TP_PROTO(uint32_t drm_id, bool enable), + TP_ARGS(drm_id, enable) +); +DEFINE_EVENT(dpu_enc_id_enable_template, dpu_enc_frame_event_cb, + TP_PROTO(uint32_t drm_id, bool enable), + TP_ARGS(drm_id, enable) +); +DEFINE_EVENT(dpu_enc_id_enable_template, dpu_enc_phys_cmd_connect_te, + TP_PROTO(uint32_t drm_id, bool enable), + TP_ARGS(drm_id, enable) +); + +TRACE_EVENT(dpu_enc_rc, + TP_PROTO(uint32_t drm_id, u32 sw_event, bool idle_pc_supported, + int rc_state, const char *stage), + TP_ARGS(drm_id, sw_event, idle_pc_supported, rc_state, stage), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( u32, sw_event ) + __field( bool, idle_pc_supported ) + __field( int, rc_state ) + __string( stage_str, stage ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->sw_event = sw_event; + __entry->idle_pc_supported = idle_pc_supported; + __entry->rc_state = rc_state; + __assign_str(stage_str, stage); + ), + TP_printk("%s: id:%u, sw_event:%d, idle_pc_supported:%s, rc_state:%d", + __get_str(stage_str), __entry->drm_id, __entry->sw_event, + __entry->idle_pc_supported ? "true" : "false", + __entry->rc_state) +); + +TRACE_EVENT(dpu_enc_frame_done_cb_not_busy, + TP_PROTO(uint32_t drm_id, u32 event, enum dpu_intf intf_idx), + TP_ARGS(drm_id, event, intf_idx), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( u32, event ) + __field( enum dpu_intf, intf_idx ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->event = event; + __entry->intf_idx = intf_idx; + ), + TP_printk("id=%u, event=%u, intf=%d", __entry->drm_id, __entry->event, + __entry->intf_idx) +); + +TRACE_EVENT(dpu_enc_frame_done_cb, + TP_PROTO(uint32_t drm_id, unsigned int idx, + unsigned long frame_busy_mask), + TP_ARGS(drm_id, idx, frame_busy_mask), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( unsigned int, idx ) + __field( unsigned long, frame_busy_mask ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->idx = idx; + __entry->frame_busy_mask = frame_busy_mask; + ), + TP_printk("id=%u, idx=%u, frame_busy_mask=%lx", __entry->drm_id, + __entry->idx, __entry->frame_busy_mask) +); + +TRACE_EVENT(dpu_enc_trigger_flush, + TP_PROTO(uint32_t drm_id, enum dpu_intf intf_idx, + int pending_kickoff_cnt, int ctl_idx, u32 extra_flush_bits, + u32 pending_flush_ret), + TP_ARGS(drm_id, intf_idx, pending_kickoff_cnt, ctl_idx, + extra_flush_bits, pending_flush_ret), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( enum dpu_intf, intf_idx ) + __field( int, pending_kickoff_cnt ) + __field( int, ctl_idx ) + __field( u32, extra_flush_bits ) + __field( u32, pending_flush_ret ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->intf_idx = intf_idx; + __entry->pending_kickoff_cnt = pending_kickoff_cnt; + __entry->ctl_idx = ctl_idx; + __entry->extra_flush_bits = extra_flush_bits; + __entry->pending_flush_ret = pending_flush_ret; + ), + TP_printk("id=%u, intf_idx=%d, pending_kickoff_cnt=%d ctl_idx=%d " + "extra_flush_bits=0x%x pending_flush_ret=0x%x", + __entry->drm_id, __entry->intf_idx, + __entry->pending_kickoff_cnt, __entry->ctl_idx, + __entry->extra_flush_bits, __entry->pending_flush_ret) +); + +DECLARE_EVENT_CLASS(dpu_enc_ktime_template, + TP_PROTO(uint32_t drm_id, ktime_t time), + TP_ARGS(drm_id, time), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( ktime_t, time ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->time = time; + ), + TP_printk("id=%u, time=%lld", __entry->drm_id, + ktime_to_ms(__entry->time)) +); +DEFINE_EVENT(dpu_enc_ktime_template, dpu_enc_vsync_event_work, + TP_PROTO(uint32_t drm_id, ktime_t time), + TP_ARGS(drm_id, time) +); +DEFINE_EVENT(dpu_enc_ktime_template, dpu_enc_early_kickoff, + TP_PROTO(uint32_t drm_id, ktime_t time), + TP_ARGS(drm_id, time) +); + +DECLARE_EVENT_CLASS(dpu_id_event_template, + TP_PROTO(uint32_t drm_id, u32 event), + TP_ARGS(drm_id, event), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( u32, event ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->event = event; + ), + TP_printk("id=%u, event=%u", __entry->drm_id, __entry->event) +); +DEFINE_EVENT(dpu_id_event_template, dpu_enc_frame_done_timeout, + TP_PROTO(uint32_t drm_id, u32 event), + TP_ARGS(drm_id, event) +); +DEFINE_EVENT(dpu_id_event_template, dpu_crtc_frame_event_cb, + TP_PROTO(uint32_t drm_id, u32 event), + TP_ARGS(drm_id, event) +); +DEFINE_EVENT(dpu_id_event_template, dpu_crtc_frame_event_done, + TP_PROTO(uint32_t drm_id, u32 event), + TP_ARGS(drm_id, event) +); +DEFINE_EVENT(dpu_id_event_template, dpu_crtc_frame_event_more_pending, + TP_PROTO(uint32_t drm_id, u32 event), + TP_ARGS(drm_id, event) +); + +TRACE_EVENT(dpu_enc_wait_event_timeout, + TP_PROTO(uint32_t drm_id, int32_t hw_id, int rc, s64 time, + s64 expected_time, int atomic_cnt), + TP_ARGS(drm_id, hw_id, rc, time, expected_time, atomic_cnt), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( int32_t, hw_id ) + __field( int, rc ) + __field( s64, time ) + __field( s64, expected_time ) + __field( int, atomic_cnt ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->hw_id = hw_id; + __entry->rc = rc; + __entry->time = time; + __entry->expected_time = expected_time; + __entry->atomic_cnt = atomic_cnt; + ), + TP_printk("id=%u, hw_id=%d, rc=%d, time=%lld, expected=%lld cnt=%d", + __entry->drm_id, __entry->hw_id, __entry->rc, __entry->time, + __entry->expected_time, __entry->atomic_cnt) +); + +TRACE_EVENT(dpu_enc_phys_cmd_irq_ctrl, + TP_PROTO(uint32_t drm_id, enum dpu_pingpong pp, bool enable, + int refcnt), + TP_ARGS(drm_id, pp, enable, refcnt), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( enum dpu_pingpong, pp ) + __field( bool, enable ) + __field( int, refcnt ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->pp = pp; + __entry->enable = enable; + __entry->refcnt = refcnt; + ), + TP_printk("id=%u, pp=%d, enable=%s, refcnt=%d", __entry->drm_id, + __entry->pp, __entry->enable ? "true" : "false", + __entry->refcnt) +); + +TRACE_EVENT(dpu_enc_phys_cmd_pp_tx_done, + TP_PROTO(uint32_t drm_id, enum dpu_pingpong pp, int new_count, + u32 event), + TP_ARGS(drm_id, pp, new_count, event), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( enum dpu_pingpong, pp ) + __field( int, new_count ) + __field( u32, event ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->pp = pp; + __entry->new_count = new_count; + __entry->event = event; + ), + TP_printk("id=%u, pp=%d, new_count=%d, event=%u", __entry->drm_id, + __entry->pp, __entry->new_count, __entry->event) +); + +TRACE_EVENT(dpu_enc_phys_cmd_pdone_timeout, + TP_PROTO(uint32_t drm_id, enum dpu_pingpong pp, int timeout_count, + int kickoff_count, u32 event), + TP_ARGS(drm_id, pp, timeout_count, kickoff_count, event), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( enum dpu_pingpong, pp ) + __field( int, timeout_count ) + __field( int, kickoff_count ) + __field( u32, event ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->pp = pp; + __entry->timeout_count = timeout_count; + __entry->kickoff_count = kickoff_count; + __entry->event = event; + ), + TP_printk("id=%u, pp=%d, timeout_count=%d, kickoff_count=%d, event=%u", + __entry->drm_id, __entry->pp, __entry->timeout_count, + __entry->kickoff_count, __entry->event) +); + +TRACE_EVENT(dpu_enc_phys_vid_post_kickoff, + TP_PROTO(uint32_t drm_id, enum dpu_intf intf_idx), + TP_ARGS(drm_id, intf_idx), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( enum dpu_intf, intf_idx ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->intf_idx = intf_idx; + ), + TP_printk("id=%u, intf_idx=%d", __entry->drm_id, __entry->intf_idx) +); + +TRACE_EVENT(dpu_enc_phys_vid_irq_ctrl, + TP_PROTO(uint32_t drm_id, enum dpu_intf intf_idx, bool enable, + int refcnt), + TP_ARGS(drm_id, intf_idx, enable, refcnt), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( enum dpu_intf, intf_idx ) + __field( bool, enable ) + __field( int, refcnt ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->intf_idx = intf_idx; + __entry->enable = enable; + __entry->refcnt = refcnt; + ), + TP_printk("id=%u, intf_idx=%d enable=%s refcnt=%d", __entry->drm_id, + __entry->intf_idx, __entry->enable ? "true" : "false", + __entry->drm_id) +); + +TRACE_EVENT(dpu_crtc_setup_mixer, + TP_PROTO(uint32_t crtc_id, uint32_t plane_id, + struct drm_plane_state *state, struct dpu_plane_state *pstate, + uint32_t stage_idx, enum dpu_sspp sspp, uint32_t pixel_format, + uint64_t modifier), + TP_ARGS(crtc_id, plane_id, state, pstate, stage_idx, sspp, + pixel_format, modifier), + TP_STRUCT__entry( + __field( uint32_t, crtc_id ) + __field( uint32_t, plane_id ) + __field( uint32_t, fb_id ) + __field_struct( struct drm_rect, src_rect ) + __field_struct( struct drm_rect, dst_rect ) + __field( uint32_t, stage_idx ) + __field( enum dpu_stage, stage ) + __field( enum dpu_sspp, sspp ) + __field( uint32_t, multirect_idx ) + __field( uint32_t, multirect_mode ) + __field( uint32_t, pixel_format ) + __field( uint64_t, modifier ) + ), + TP_fast_assign( + __entry->crtc_id = crtc_id; + __entry->plane_id = plane_id; + __entry->fb_id = state ? state->fb->base.id : 0; + __entry->src_rect = drm_plane_state_src(state); + __entry->dst_rect = drm_plane_state_dest(state); + __entry->stage_idx = stage_idx; + __entry->stage = pstate->stage; + __entry->sspp = sspp; + __entry->multirect_idx = pstate->multirect_index; + __entry->multirect_mode = pstate->multirect_mode; + __entry->pixel_format = pixel_format; + __entry->modifier = modifier; + ), + TP_printk("crtc_id:%u plane_id:%u fb_id:%u src:" DRM_RECT_FP_FMT + " dst:" DRM_RECT_FMT " stage_idx:%u stage:%d, sspp:%d " + "multirect_index:%d multirect_mode:%u pix_format:%u " + "modifier:%llu", + __entry->crtc_id, __entry->plane_id, __entry->fb_id, + DRM_RECT_FP_ARG(&__entry->src_rect), + DRM_RECT_ARG(&__entry->dst_rect), + __entry->stage_idx, __entry->stage, __entry->sspp, + __entry->multirect_idx, __entry->multirect_mode, + __entry->pixel_format, __entry->modifier) +); + +TRACE_EVENT(dpu_crtc_setup_lm_bounds, + TP_PROTO(uint32_t drm_id, int mixer, struct drm_rect *bounds), + TP_ARGS(drm_id, mixer, bounds), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( int, mixer ) + __field_struct( struct drm_rect, bounds ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->mixer = mixer; + __entry->bounds = *bounds; + ), + TP_printk("id:%u mixer:%d bounds:" DRM_RECT_FMT, __entry->drm_id, + __entry->mixer, DRM_RECT_ARG(&__entry->bounds)) +); + +TRACE_EVENT(dpu_crtc_vblank_enable, + TP_PROTO(uint32_t drm_id, uint32_t enc_id, bool enable, + struct dpu_crtc *crtc), + TP_ARGS(drm_id, enc_id, enable, crtc), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( uint32_t, enc_id ) + __field( bool, enable ) + __field( bool, enabled ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->enc_id = enc_id; + __entry->enable = enable; + __entry->enabled = crtc->enabled; + ), + TP_printk("id:%u encoder:%u enable:%s state{enabled:%s}", + __entry->drm_id, __entry->enc_id, + __entry->enable ? "true" : "false", + __entry->enabled ? "true" : "false") +); + +DECLARE_EVENT_CLASS(dpu_crtc_enable_template, + TP_PROTO(uint32_t drm_id, bool enable, struct dpu_crtc *crtc), + TP_ARGS(drm_id, enable, crtc), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( bool, enable ) + __field( bool, enabled ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->enable = enable; + __entry->enabled = crtc->enabled; + ), + TP_printk("id:%u enable:%s state{enabled:%s}", + __entry->drm_id, __entry->enable ? "true" : "false", + __entry->enabled ? "true" : "false") +); +DEFINE_EVENT(dpu_crtc_enable_template, dpu_crtc_enable, + TP_PROTO(uint32_t drm_id, bool enable, struct dpu_crtc *crtc), + TP_ARGS(drm_id, enable, crtc) +); +DEFINE_EVENT(dpu_crtc_enable_template, dpu_crtc_disable, + TP_PROTO(uint32_t drm_id, bool enable, struct dpu_crtc *crtc), + TP_ARGS(drm_id, enable, crtc) +); +DEFINE_EVENT(dpu_crtc_enable_template, dpu_crtc_vblank, + TP_PROTO(uint32_t drm_id, bool enable, struct dpu_crtc *crtc), + TP_ARGS(drm_id, enable, crtc) +); + +TRACE_EVENT(dpu_crtc_disable_frame_pending, + TP_PROTO(uint32_t drm_id, int frame_pending), + TP_ARGS(drm_id, frame_pending), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( int, frame_pending ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->frame_pending = frame_pending; + ), + TP_printk("id:%u frame_pending:%d", __entry->drm_id, + __entry->frame_pending) +); + +TRACE_EVENT(dpu_plane_set_scanout, + TP_PROTO(enum dpu_sspp index, struct dpu_hw_fmt_layout *layout, + enum dpu_sspp_multirect_index multirect_index), + TP_ARGS(index, layout, multirect_index), + TP_STRUCT__entry( + __field( enum dpu_sspp, index ) + __field_struct( struct dpu_hw_fmt_layout, layout ) + __field( enum dpu_sspp_multirect_index, multirect_index) + ), + TP_fast_assign( + __entry->index = index; + __entry->layout = *layout; + __entry->multirect_index = multirect_index; + ), + TP_printk("index:%d layout:{%ux%u @ [%u/%u, %u/%u, %u/%u, %u/%u]} " + "multirect_index:%d", __entry->index, __entry->layout.width, + __entry->layout.height, __entry->layout.plane_addr[0], + __entry->layout.plane_size[0], + __entry->layout.plane_addr[1], + __entry->layout.plane_size[1], + __entry->layout.plane_addr[2], + __entry->layout.plane_size[2], + __entry->layout.plane_addr[3], + __entry->layout.plane_size[3], __entry->multirect_index) +); + +TRACE_EVENT(dpu_plane_disable, + TP_PROTO(uint32_t drm_id, bool is_virtual, uint32_t multirect_mode), + TP_ARGS(drm_id, is_virtual, multirect_mode), + TP_STRUCT__entry( + __field( uint32_t, drm_id ) + __field( bool, is_virtual ) + __field( uint32_t, multirect_mode ) + ), + TP_fast_assign( + __entry->drm_id = drm_id; + __entry->is_virtual = is_virtual; + __entry->multirect_mode = multirect_mode; + ), + TP_printk("id:%u is_virtual:%s multirect_mode:%u", __entry->drm_id, + __entry->is_virtual ? "true" : "false", + __entry->multirect_mode) +); + +DECLARE_EVENT_CLASS(dpu_rm_iter_template, + TP_PROTO(uint32_t id, uint32_t enc_id), + TP_ARGS(id, enc_id), + TP_STRUCT__entry( + __field( uint32_t, id ) + __field( uint32_t, enc_id ) + ), + TP_fast_assign( + __entry->id = id; + __entry->enc_id = enc_id; + ), + TP_printk("id:%d enc_id:%u", __entry->id, __entry->enc_id) +); +DEFINE_EVENT(dpu_rm_iter_template, dpu_rm_reserve_intf, + TP_PROTO(uint32_t id, uint32_t enc_id), + TP_ARGS(id, enc_id) +); +DEFINE_EVENT(dpu_rm_iter_template, dpu_rm_reserve_ctls, + TP_PROTO(uint32_t id, uint32_t enc_id), + TP_ARGS(id, enc_id) +); + +TRACE_EVENT(dpu_rm_reserve_lms, + TP_PROTO(uint32_t id, uint32_t enc_id, uint32_t pp_id), + TP_ARGS(id, enc_id, pp_id), + TP_STRUCT__entry( + __field( uint32_t, id ) + __field( uint32_t, enc_id ) + __field( uint32_t, pp_id ) + ), + TP_fast_assign( + __entry->id = id; + __entry->enc_id = enc_id; + __entry->pp_id = pp_id; + ), + TP_printk("id:%d enc_id:%u pp_id:%u", __entry->id, + __entry->enc_id, __entry->pp_id) +); + +TRACE_EVENT(dpu_vbif_wait_xin_halt_fail, + TP_PROTO(enum dpu_vbif index, u32 xin_id), + TP_ARGS(index, xin_id), + TP_STRUCT__entry( + __field( enum dpu_vbif, index ) + __field( u32, xin_id ) + ), + TP_fast_assign( + __entry->index = index; + __entry->xin_id = xin_id; + ), + TP_printk("index:%d xin_id:%u", __entry->index, __entry->xin_id) +); + +TRACE_EVENT(dpu_pp_connect_ext_te, + TP_PROTO(enum dpu_pingpong pp, u32 cfg), + TP_ARGS(pp, cfg), + TP_STRUCT__entry( + __field( enum dpu_pingpong, pp ) + __field( u32, cfg ) + ), + TP_fast_assign( + __entry->pp = pp; + __entry->cfg = cfg; + ), + TP_printk("pp:%d cfg:%u", __entry->pp, __entry->cfg) +); + +DECLARE_EVENT_CLASS(dpu_core_irq_idx_cnt_template, + TP_PROTO(int irq_idx, int enable_count), + TP_ARGS(irq_idx, enable_count), + TP_STRUCT__entry( + __field( int, irq_idx ) + __field( int, enable_count ) + ), + TP_fast_assign( + __entry->irq_idx = irq_idx; + __entry->enable_count = enable_count; + ), + TP_printk("irq_idx:%d enable_count:%u", __entry->irq_idx, + __entry->enable_count) +); +DEFINE_EVENT(dpu_core_irq_idx_cnt_template, dpu_core_irq_enable_idx, + TP_PROTO(int irq_idx, int enable_count), + TP_ARGS(irq_idx, enable_count) +); +DEFINE_EVENT(dpu_core_irq_idx_cnt_template, dpu_core_irq_disable_idx, + TP_PROTO(int irq_idx, int enable_count), + TP_ARGS(irq_idx, enable_count) +); + +DECLARE_EVENT_CLASS(dpu_core_irq_callback_template, + TP_PROTO(int irq_idx, struct dpu_irq_callback *callback), + TP_ARGS(irq_idx, callback), + TP_STRUCT__entry( + __field( int, irq_idx ) + __field( struct dpu_irq_callback *, callback) + ), + TP_fast_assign( + __entry->irq_idx = irq_idx; + __entry->callback = callback; + ), + TP_printk("irq_idx:%d callback:%pK", __entry->irq_idx, + __entry->callback) +); +DEFINE_EVENT(dpu_core_irq_callback_template, dpu_core_irq_register_callback, + TP_PROTO(int irq_idx, struct dpu_irq_callback *callback), + TP_ARGS(irq_idx, callback) +); +DEFINE_EVENT(dpu_core_irq_callback_template, dpu_core_irq_unregister_callback, + TP_PROTO(int irq_idx, struct dpu_irq_callback *callback), + TP_ARGS(irq_idx, callback) +); + +TRACE_EVENT(dpu_core_perf_update_clk, + TP_PROTO(struct drm_device *dev, bool stop_req, u64 clk_rate), + TP_ARGS(dev, stop_req, clk_rate), + TP_STRUCT__entry( + __string( dev_name, dev->unique ) + __field( bool, stop_req ) + __field( u64, clk_rate ) + ), + TP_fast_assign( + __assign_str(dev_name, dev->unique); + __entry->stop_req = stop_req; + __entry->clk_rate = clk_rate; + ), + TP_printk("dev:%s stop_req:%s clk_rate:%llu", __get_str(dev_name), + __entry->stop_req ? "true" : "false", __entry->clk_rate) +); + +TRACE_EVENT(dpu_hw_ctl_update_pending_flush, + TP_PROTO(u32 new_bits, u32 pending_mask), + TP_ARGS(new_bits, pending_mask), + TP_STRUCT__entry( + __field( u32, new_bits ) + __field( u32, pending_mask ) + ), + TP_fast_assign( + __entry->new_bits = new_bits; + __entry->pending_mask = pending_mask; + ), + TP_printk("new=%x existing=%x", __entry->new_bits, + __entry->pending_mask) +); + +DECLARE_EVENT_CLASS(dpu_hw_ctl_pending_flush_template, + TP_PROTO(u32 pending_mask, u32 ctl_flush), + TP_ARGS(pending_mask, ctl_flush), + TP_STRUCT__entry( + __field( u32, pending_mask ) + __field( u32, ctl_flush ) + ), + TP_fast_assign( + __entry->pending_mask = pending_mask; + __entry->ctl_flush = ctl_flush; + ), + TP_printk("pending_mask=%x CTL_FLUSH=%x", __entry->pending_mask, + __entry->ctl_flush) +); +DEFINE_EVENT(dpu_hw_ctl_pending_flush_template, dpu_hw_ctl_clear_pending_flush, + TP_PROTO(u32 pending_mask, u32 ctl_flush), + TP_ARGS(pending_mask, ctl_flush) +); +DEFINE_EVENT(dpu_hw_ctl_pending_flush_template, + dpu_hw_ctl_trigger_pending_flush, + TP_PROTO(u32 pending_mask, u32 ctl_flush), + TP_ARGS(pending_mask, ctl_flush) +); +DEFINE_EVENT(dpu_hw_ctl_pending_flush_template, dpu_hw_ctl_trigger_prepare, + TP_PROTO(u32 pending_mask, u32 ctl_flush), + TP_ARGS(pending_mask, ctl_flush) +); +DEFINE_EVENT(dpu_hw_ctl_pending_flush_template, dpu_hw_ctl_trigger_start, + TP_PROTO(u32 pending_mask, u32 ctl_flush), + TP_ARGS(pending_mask, ctl_flush) +); + +#define DPU_ATRACE_END(name) trace_tracing_mark_write(current->tgid, name, 0) +#define DPU_ATRACE_BEGIN(name) trace_tracing_mark_write(current->tgid, name, 1) +#define DPU_ATRACE_FUNC() DPU_ATRACE_BEGIN(__func__) + +#define DPU_ATRACE_INT(name, value) \ + trace_dpu_trace_counter(current->tgid, name, value) + +#endif /* _DPU_TRACE_H_ */ + +/* This part must be outside protection */ +#undef TRACE_INCLUDE_PATH +#define TRACE_INCLUDE_PATH . +#include <trace/define_trace.h> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_vbif.c b/drivers/gpu/drm/msm/disp/dpu1/dpu_vbif.c new file mode 100644 index 000000000..fc86d34ae --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_vbif.c @@ -0,0 +1,346 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__ + +#include <linux/debugfs.h> +#include <linux/delay.h> + +#include "dpu_vbif.h" +#include "dpu_hw_vbif.h" +#include "dpu_trace.h" + +static struct dpu_hw_vbif *dpu_get_vbif(struct dpu_kms *dpu_kms, enum dpu_vbif vbif_idx) +{ + if (vbif_idx < ARRAY_SIZE(dpu_kms->hw_vbif)) + return dpu_kms->hw_vbif[vbif_idx]; + + return NULL; +} + +/** + * _dpu_vbif_wait_for_xin_halt - wait for the xin to halt + * @vbif: Pointer to hardware vbif driver + * @xin_id: Client interface identifier + * @return: 0 if success; error code otherwise + */ +static int _dpu_vbif_wait_for_xin_halt(struct dpu_hw_vbif *vbif, u32 xin_id) +{ + ktime_t timeout; + bool status; + int rc; + + if (!vbif || !vbif->cap || !vbif->ops.get_halt_ctrl) { + DPU_ERROR("invalid arguments vbif %d\n", vbif != NULL); + return -EINVAL; + } + + timeout = ktime_add_us(ktime_get(), vbif->cap->xin_halt_timeout); + for (;;) { + status = vbif->ops.get_halt_ctrl(vbif, xin_id); + if (status) + break; + if (ktime_compare_safe(ktime_get(), timeout) > 0) { + status = vbif->ops.get_halt_ctrl(vbif, xin_id); + break; + } + usleep_range(501, 1000); + } + + if (!status) { + rc = -ETIMEDOUT; + DPU_ERROR("VBIF %d client %d not halting. TIMEDOUT.\n", + vbif->idx - VBIF_0, xin_id); + } else { + rc = 0; + DPU_DEBUG("VBIF %d client %d is halted\n", + vbif->idx - VBIF_0, xin_id); + } + + return rc; +} + +/** + * _dpu_vbif_apply_dynamic_ot_limit - determine OT based on usecase parameters + * @vbif: Pointer to hardware vbif driver + * @ot_lim: Pointer to OT limit to be modified + * @params: Pointer to usecase parameters + */ +static void _dpu_vbif_apply_dynamic_ot_limit(struct dpu_hw_vbif *vbif, + u32 *ot_lim, struct dpu_vbif_set_ot_params *params) +{ + u64 pps; + const struct dpu_vbif_dynamic_ot_tbl *tbl; + u32 i; + + if (!vbif || !(vbif->cap->features & BIT(DPU_VBIF_QOS_OTLIM))) + return; + + /* Dynamic OT setting done only for WFD */ + if (!params->is_wfd) + return; + + pps = params->frame_rate; + pps *= params->width; + pps *= params->height; + + tbl = params->rd ? &vbif->cap->dynamic_ot_rd_tbl : + &vbif->cap->dynamic_ot_wr_tbl; + + for (i = 0; i < tbl->count; i++) { + if (pps <= tbl->cfg[i].pps) { + *ot_lim = tbl->cfg[i].ot_limit; + break; + } + } + + DPU_DEBUG("vbif:%d xin:%d w:%d h:%d fps:%d pps:%llu ot:%u\n", + vbif->idx - VBIF_0, params->xin_id, + params->width, params->height, params->frame_rate, + pps, *ot_lim); +} + +/** + * _dpu_vbif_get_ot_limit - get OT based on usecase & configuration parameters + * @vbif: Pointer to hardware vbif driver + * @params: Pointer to usecase parameters + * @return: OT limit + */ +static u32 _dpu_vbif_get_ot_limit(struct dpu_hw_vbif *vbif, + struct dpu_vbif_set_ot_params *params) +{ + u32 ot_lim = 0; + u32 val; + + if (!vbif || !vbif->cap) { + DPU_ERROR("invalid arguments vbif %d\n", vbif != NULL); + return -EINVAL; + } + + if (vbif->cap->default_ot_wr_limit && !params->rd) + ot_lim = vbif->cap->default_ot_wr_limit; + else if (vbif->cap->default_ot_rd_limit && params->rd) + ot_lim = vbif->cap->default_ot_rd_limit; + + /* + * If default ot is not set from dt/catalog, + * then do not configure it. + */ + if (ot_lim == 0) + goto exit; + + /* Modify the limits if the target and the use case requires it */ + _dpu_vbif_apply_dynamic_ot_limit(vbif, &ot_lim, params); + + if (vbif && vbif->ops.get_limit_conf) { + val = vbif->ops.get_limit_conf(vbif, + params->xin_id, params->rd); + if (val == ot_lim) + ot_lim = 0; + } + +exit: + DPU_DEBUG("vbif:%d xin:%d ot_lim:%d\n", + vbif->idx - VBIF_0, params->xin_id, ot_lim); + return ot_lim; +} + +/** + * dpu_vbif_set_ot_limit - set OT based on usecase & configuration parameters + * @vbif: Pointer to hardware vbif driver + * @params: Pointer to usecase parameters + * + * Note this function would block waiting for bus halt. + */ +void dpu_vbif_set_ot_limit(struct dpu_kms *dpu_kms, + struct dpu_vbif_set_ot_params *params) +{ + struct dpu_hw_vbif *vbif; + struct dpu_hw_mdp *mdp; + bool forced_on = false; + u32 ot_lim; + int ret; + + mdp = dpu_kms->hw_mdp; + + vbif = dpu_get_vbif(dpu_kms, params->vbif_idx); + if (!vbif || !mdp) { + DPU_DEBUG("invalid arguments vbif %d mdp %d\n", + vbif != NULL, mdp != NULL); + return; + } + + if (!mdp->ops.setup_clk_force_ctrl || + !vbif->ops.set_limit_conf || + !vbif->ops.set_halt_ctrl) + return; + + /* set write_gather_en for all write clients */ + if (vbif->ops.set_write_gather_en && !params->rd) + vbif->ops.set_write_gather_en(vbif, params->xin_id); + + ot_lim = _dpu_vbif_get_ot_limit(vbif, params) & 0xFF; + + if (ot_lim == 0) + return; + + trace_dpu_perf_set_ot(params->num, params->xin_id, ot_lim, + params->vbif_idx); + + forced_on = mdp->ops.setup_clk_force_ctrl(mdp, params->clk_ctrl, true); + + vbif->ops.set_limit_conf(vbif, params->xin_id, params->rd, ot_lim); + + vbif->ops.set_halt_ctrl(vbif, params->xin_id, true); + + ret = _dpu_vbif_wait_for_xin_halt(vbif, params->xin_id); + if (ret) + trace_dpu_vbif_wait_xin_halt_fail(vbif->idx, params->xin_id); + + vbif->ops.set_halt_ctrl(vbif, params->xin_id, false); + + if (forced_on) + mdp->ops.setup_clk_force_ctrl(mdp, params->clk_ctrl, false); +} + +void dpu_vbif_set_qos_remap(struct dpu_kms *dpu_kms, + struct dpu_vbif_set_qos_params *params) +{ + struct dpu_hw_vbif *vbif; + struct dpu_hw_mdp *mdp; + bool forced_on = false; + const struct dpu_vbif_qos_tbl *qos_tbl; + int i; + + if (!params || !dpu_kms->hw_mdp) { + DPU_ERROR("invalid arguments\n"); + return; + } + mdp = dpu_kms->hw_mdp; + + vbif = dpu_get_vbif(dpu_kms, params->vbif_idx); + + if (!vbif || !vbif->cap) { + DPU_ERROR("invalid vbif %d\n", params->vbif_idx); + return; + } + + if (!vbif->ops.set_qos_remap || !mdp->ops.setup_clk_force_ctrl) { + DPU_DEBUG("qos remap not supported\n"); + return; + } + + qos_tbl = params->is_rt ? &vbif->cap->qos_rt_tbl : + &vbif->cap->qos_nrt_tbl; + + if (!qos_tbl->npriority_lvl || !qos_tbl->priority_lvl) { + DPU_DEBUG("qos tbl not defined\n"); + return; + } + + forced_on = mdp->ops.setup_clk_force_ctrl(mdp, params->clk_ctrl, true); + + for (i = 0; i < qos_tbl->npriority_lvl; i++) { + DPU_DEBUG("vbif:%d xin:%d lvl:%d/%d\n", + params->vbif_idx, params->xin_id, i, + qos_tbl->priority_lvl[i]); + vbif->ops.set_qos_remap(vbif, params->xin_id, i, + qos_tbl->priority_lvl[i]); + } + + if (forced_on) + mdp->ops.setup_clk_force_ctrl(mdp, params->clk_ctrl, false); +} + +void dpu_vbif_clear_errors(struct dpu_kms *dpu_kms) +{ + struct dpu_hw_vbif *vbif; + u32 i, pnd, src; + + for (i = 0; i < ARRAY_SIZE(dpu_kms->hw_vbif); i++) { + vbif = dpu_kms->hw_vbif[i]; + if (vbif && vbif->ops.clear_errors) { + vbif->ops.clear_errors(vbif, &pnd, &src); + if (pnd || src) { + DRM_DEBUG_KMS("VBIF %d: pnd 0x%X, src 0x%X\n", + vbif->idx - VBIF_0, pnd, src); + } + } + } +} + +void dpu_vbif_init_memtypes(struct dpu_kms *dpu_kms) +{ + struct dpu_hw_vbif *vbif; + int i, j; + + for (i = 0; i < ARRAY_SIZE(dpu_kms->hw_vbif); i++) { + vbif = dpu_kms->hw_vbif[i]; + if (vbif && vbif->cap && vbif->ops.set_mem_type) { + for (j = 0; j < vbif->cap->memtype_count; j++) + vbif->ops.set_mem_type( + vbif, j, vbif->cap->memtype[j]); + } + } +} + +#ifdef CONFIG_DEBUG_FS + +void dpu_debugfs_vbif_init(struct dpu_kms *dpu_kms, struct dentry *debugfs_root) +{ + char vbif_name[32]; + struct dentry *entry, *debugfs_vbif; + int i, j; + + entry = debugfs_create_dir("vbif", debugfs_root); + + for (i = 0; i < dpu_kms->catalog->vbif_count; i++) { + const struct dpu_vbif_cfg *vbif = &dpu_kms->catalog->vbif[i]; + + snprintf(vbif_name, sizeof(vbif_name), "%d", vbif->id); + + debugfs_vbif = debugfs_create_dir(vbif_name, entry); + + debugfs_create_u32("features", 0600, debugfs_vbif, + (u32 *)&vbif->features); + + debugfs_create_u32("xin_halt_timeout", 0400, debugfs_vbif, + (u32 *)&vbif->xin_halt_timeout); + + debugfs_create_u32("default_rd_ot_limit", 0400, debugfs_vbif, + (u32 *)&vbif->default_ot_rd_limit); + + debugfs_create_u32("default_wr_ot_limit", 0400, debugfs_vbif, + (u32 *)&vbif->default_ot_wr_limit); + + for (j = 0; j < vbif->dynamic_ot_rd_tbl.count; j++) { + const struct dpu_vbif_dynamic_ot_cfg *cfg = + &vbif->dynamic_ot_rd_tbl.cfg[j]; + + snprintf(vbif_name, sizeof(vbif_name), + "dynamic_ot_rd_%d_pps", j); + debugfs_create_u64(vbif_name, 0400, debugfs_vbif, + (u64 *)&cfg->pps); + snprintf(vbif_name, sizeof(vbif_name), + "dynamic_ot_rd_%d_ot_limit", j); + debugfs_create_u32(vbif_name, 0400, debugfs_vbif, + (u32 *)&cfg->ot_limit); + } + + for (j = 0; j < vbif->dynamic_ot_wr_tbl.count; j++) { + const struct dpu_vbif_dynamic_ot_cfg *cfg = + &vbif->dynamic_ot_wr_tbl.cfg[j]; + + snprintf(vbif_name, sizeof(vbif_name), + "dynamic_ot_wr_%d_pps", j); + debugfs_create_u64(vbif_name, 0400, debugfs_vbif, + (u64 *)&cfg->pps); + snprintf(vbif_name, sizeof(vbif_name), + "dynamic_ot_wr_%d_ot_limit", j); + debugfs_create_u32(vbif_name, 0400, debugfs_vbif, + (u32 *)&cfg->ot_limit); + } + } +} +#endif diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_vbif.h b/drivers/gpu/drm/msm/disp/dpu1/dpu_vbif.h new file mode 100644 index 000000000..ab490177d --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_vbif.h @@ -0,0 +1,75 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2016-2018, The Linux Foundation. All rights reserved. + */ + +#ifndef __DPU_VBIF_H__ +#define __DPU_VBIF_H__ + +#include "dpu_kms.h" + +struct dpu_vbif_set_ot_params { + u32 xin_id; + u32 num; + u32 width; + u32 height; + u32 frame_rate; + bool rd; + bool is_wfd; + u32 vbif_idx; + u32 clk_ctrl; +}; + +struct dpu_vbif_set_memtype_params { + u32 xin_id; + u32 vbif_idx; + u32 clk_ctrl; + bool is_cacheable; +}; + +/** + * struct dpu_vbif_set_qos_params - QoS remapper parameter + * @vbif_idx: vbif identifier + * @xin_id: client interface identifier + * @clk_ctrl: clock control identifier of the xin + * @num: pipe identifier (debug only) + * @is_rt: true if pipe is used in real-time use case + */ +struct dpu_vbif_set_qos_params { + u32 vbif_idx; + u32 xin_id; + u32 clk_ctrl; + u32 num; + bool is_rt; +}; + +/** + * dpu_vbif_set_ot_limit - set OT limit for vbif client + * @dpu_kms: DPU handler + * @params: Pointer to OT configuration parameters + */ +void dpu_vbif_set_ot_limit(struct dpu_kms *dpu_kms, + struct dpu_vbif_set_ot_params *params); + +/** + * dpu_vbif_set_qos_remap - set QoS priority level remap + * @dpu_kms: DPU handler + * @params: Pointer to QoS configuration parameters + */ +void dpu_vbif_set_qos_remap(struct dpu_kms *dpu_kms, + struct dpu_vbif_set_qos_params *params); + +/** + * dpu_vbif_clear_errors - clear any vbif errors + * @dpu_kms: DPU handler + */ +void dpu_vbif_clear_errors(struct dpu_kms *dpu_kms); + +/** + * dpu_vbif_init_memtypes - initialize xin memory types for vbif + * @dpu_kms: DPU handler + */ +void dpu_vbif_init_memtypes(struct dpu_kms *dpu_kms); + +void dpu_debugfs_vbif_init(struct dpu_kms *dpu_kms, struct dentry *debugfs_root); + +#endif /* __DPU_VBIF_H__ */ diff --git a/drivers/gpu/drm/msm/disp/dpu1/msm_media_info.h b/drivers/gpu/drm/msm/disp/dpu1/msm_media_info.h new file mode 100644 index 000000000..9fc9dbde8 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/dpu1/msm_media_info.h @@ -0,0 +1,1155 @@ +#ifndef __MEDIA_INFO_H__ +#define __MEDIA_INFO_H__ + +#ifndef MSM_MEDIA_ALIGN +#define MSM_MEDIA_ALIGN(__sz, __align) (((__align) & ((__align) - 1)) ?\ + ((((__sz) + (__align) - 1) / (__align)) * (__align)) :\ + (((__sz) + (__align) - 1) & (~((__align) - 1)))) +#endif + +#ifndef MSM_MEDIA_ROUNDUP +#define MSM_MEDIA_ROUNDUP(__sz, __r) (((__sz) + ((__r) - 1)) / (__r)) +#endif + +#ifndef MSM_MEDIA_MAX +#define MSM_MEDIA_MAX(__a, __b) ((__a) > (__b)?(__a):(__b)) +#endif + +enum color_fmts { + /* Venus NV12: + * YUV 4:2:0 image with a plane of 8 bit Y samples followed + * by an interleaved U/V plane containing 8 bit 2x2 subsampled + * colour difference samples. + * + * <-------- Y/UV_Stride --------> + * <------- Width -------> + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . ^ ^ + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . Height | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | Y_Scanlines + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * U V U V U V U V U V U V . . . . ^ + * U V U V U V U V U V U V . . . . | + * U V U V U V U V U V U V . . . . | + * U V U V U V U V U V U V . . . . UV_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . --> Buffer size alignment + * + * Y_Stride : Width aligned to 128 + * UV_Stride : Width aligned to 128 + * Y_Scanlines: Height aligned to 32 + * UV_Scanlines: Height/2 aligned to 16 + * Extradata: Arbitrary (software-imposed) padding + * Total size = align((Y_Stride * Y_Scanlines + * + UV_Stride * UV_Scanlines + * + max(Extradata, Y_Stride * 8), 4096) + */ + COLOR_FMT_NV12, + + /* Venus NV21: + * YUV 4:2:0 image with a plane of 8 bit Y samples followed + * by an interleaved V/U plane containing 8 bit 2x2 subsampled + * colour difference samples. + * + * <-------- Y/UV_Stride --------> + * <------- Width -------> + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . ^ ^ + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . Height | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | Y_Scanlines + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * V U V U V U V U V U V U . . . . ^ + * V U V U V U V U V U V U . . . . | + * V U V U V U V U V U V U . . . . | + * V U V U V U V U V U V U . . . . UV_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . --> Padding & Buffer size alignment + * + * Y_Stride : Width aligned to 128 + * UV_Stride : Width aligned to 128 + * Y_Scanlines: Height aligned to 32 + * UV_Scanlines: Height/2 aligned to 16 + * Extradata: Arbitrary (software-imposed) padding + * Total size = align((Y_Stride * Y_Scanlines + * + UV_Stride * UV_Scanlines + * + max(Extradata, Y_Stride * 8), 4096) + */ + COLOR_FMT_NV21, + /* Venus NV12_MVTB: + * Two YUV 4:2:0 images/views one after the other + * in a top-bottom layout, same as NV12 + * with a plane of 8 bit Y samples followed + * by an interleaved U/V plane containing 8 bit 2x2 subsampled + * colour difference samples. + * + * + * <-------- Y/UV_Stride --------> + * <------- Width -------> + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . ^ ^ ^ + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . Height | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | Y_Scanlines | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . V | | + * . . . . . . . . . . . . . . . . | View_1 + * . . . . . . . . . . . . . . . . | | + * . . . . . . . . . . . . . . . . | | + * . . . . . . . . . . . . . . . . V | + * U V U V U V U V U V U V . . . . ^ | + * U V U V U V U V U V U V . . . . | | + * U V U V U V U V U V U V . . . . | | + * U V U V U V U V U V U V . . . . UV_Scanlines | + * . . . . . . . . . . . . . . . . | | + * . . . . . . . . . . . . . . . . V V + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . ^ ^ ^ + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . Height | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | Y_Scanlines | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . V | | + * . . . . . . . . . . . . . . . . | View_2 + * . . . . . . . . . . . . . . . . | | + * . . . . . . . . . . . . . . . . | | + * . . . . . . . . . . . . . . . . V | + * U V U V U V U V U V U V . . . . ^ | + * U V U V U V U V U V U V . . . . | | + * U V U V U V U V U V U V . . . . | | + * U V U V U V U V U V U V . . . . UV_Scanlines | + * . . . . . . . . . . . . . . . . | | + * . . . . . . . . . . . . . . . . V V + * . . . . . . . . . . . . . . . . --> Buffer size alignment + * + * Y_Stride : Width aligned to 128 + * UV_Stride : Width aligned to 128 + * Y_Scanlines: Height aligned to 32 + * UV_Scanlines: Height/2 aligned to 16 + * View_1 begin at: 0 (zero) + * View_2 begin at: Y_Stride * Y_Scanlines + UV_Stride * UV_Scanlines + * Extradata: Arbitrary (software-imposed) padding + * Total size = align((2*(Y_Stride * Y_Scanlines) + * + 2*(UV_Stride * UV_Scanlines) + Extradata), 4096) + */ + COLOR_FMT_NV12_MVTB, + /* + * The buffer can be of 2 types: + * (1) Venus NV12 UBWC Progressive + * (2) Venus NV12 UBWC Interlaced + * + * (1) Venus NV12 UBWC Progressive Buffer Format: + * Compressed Macro-tile format for NV12. + * Contains 4 planes in the following order - + * (A) Y_Meta_Plane + * (B) Y_UBWC_Plane + * (C) UV_Meta_Plane + * (D) UV_UBWC_Plane + * + * Y_Meta_Plane consists of meta information to decode compressed + * tile data in Y_UBWC_Plane. + * Y_UBWC_Plane consists of Y data in compressed macro-tile format. + * UBWC decoder block will use the Y_Meta_Plane data together with + * Y_UBWC_Plane data to produce loss-less uncompressed 8 bit Y samples. + * + * UV_Meta_Plane consists of meta information to decode compressed + * tile data in UV_UBWC_Plane. + * UV_UBWC_Plane consists of UV data in compressed macro-tile format. + * UBWC decoder block will use UV_Meta_Plane data together with + * UV_UBWC_Plane data to produce loss-less uncompressed 8 bit 2x2 + * subsampled color difference samples. + * + * Each tile in Y_UBWC_Plane/UV_UBWC_Plane is independently decodable + * and randomly accessible. There is no dependency between tiles. + * + * <----- Y_Meta_Stride ----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Height | + * M M M M M M M M M M M M . . | Meta_Y_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <--Compressed tile Y Stride---> + * <------- Width -------> + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . ^ ^ + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . Height | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | Macro_tile_Y_Scanlines + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * <----- UV_Meta_Stride ----> + * M M M M M M M M M M M M . . ^ + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . M_UV_Scanlines + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <--Compressed tile UV Stride---> + * U* V* U* V* U* V* U* V* . . . . ^ + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . UV_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * + * Y_Stride = align(Width, 128) + * UV_Stride = align(Width, 128) + * Y_Scanlines = align(Height, 32) + * UV_Scanlines = align(Height/2, 16) + * Y_UBWC_Plane_size = align(Y_Stride * Y_Scanlines, 4096) + * UV_UBWC_Plane_size = align(UV_Stride * UV_Scanlines, 4096) + * Y_Meta_Stride = align(roundup(Width, Y_TileWidth), 64) + * Y_Meta_Scanlines = align(roundup(Height, Y_TileHeight), 16) + * Y_Meta_Plane_size = align(Y_Meta_Stride * Y_Meta_Scanlines, 4096) + * UV_Meta_Stride = align(roundup(Width, UV_TileWidth), 64) + * UV_Meta_Scanlines = align(roundup(Height, UV_TileHeight), 16) + * UV_Meta_Plane_size = align(UV_Meta_Stride * UV_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align( Y_UBWC_Plane_size + UV_UBWC_Plane_size + + * Y_Meta_Plane_size + UV_Meta_Plane_size + * + max(Extradata, Y_Stride * 48), 4096) + * + * + * (2) Venus NV12 UBWC Interlaced Buffer Format: + * Compressed Macro-tile format for NV12 interlaced. + * Contains 8 planes in the following order - + * (A) Y_Meta_Top_Field_Plane + * (B) Y_UBWC_Top_Field_Plane + * (C) UV_Meta_Top_Field_Plane + * (D) UV_UBWC_Top_Field_Plane + * (E) Y_Meta_Bottom_Field_Plane + * (F) Y_UBWC_Bottom_Field_Plane + * (G) UV_Meta_Bottom_Field_Plane + * (H) UV_UBWC_Bottom_Field_Plane + * Y_Meta_Top_Field_Plane consists of meta information to decode + * compressed tile data for Y_UBWC_Top_Field_Plane. + * Y_UBWC_Top_Field_Plane consists of Y data in compressed macro-tile + * format for top field of an interlaced frame. + * UBWC decoder block will use the Y_Meta_Top_Field_Plane data together + * with Y_UBWC_Top_Field_Plane data to produce loss-less uncompressed + * 8 bit Y samples for top field of an interlaced frame. + * + * UV_Meta_Top_Field_Plane consists of meta information to decode + * compressed tile data in UV_UBWC_Top_Field_Plane. + * UV_UBWC_Top_Field_Plane consists of UV data in compressed macro-tile + * format for top field of an interlaced frame. + * UBWC decoder block will use UV_Meta_Top_Field_Plane data together + * with UV_UBWC_Top_Field_Plane data to produce loss-less uncompressed + * 8 bit subsampled color difference samples for top field of an + * interlaced frame. + * + * Each tile in Y_UBWC_Top_Field_Plane/UV_UBWC_Top_Field_Plane is + * independently decodable and randomly accessible. There is no + * dependency between tiles. + * + * Y_Meta_Bottom_Field_Plane consists of meta information to decode + * compressed tile data for Y_UBWC_Bottom_Field_Plane. + * Y_UBWC_Bottom_Field_Plane consists of Y data in compressed macro-tile + * format for bottom field of an interlaced frame. + * UBWC decoder block will use the Y_Meta_Bottom_Field_Plane data + * together with Y_UBWC_Bottom_Field_Plane data to produce loss-less + * uncompressed 8 bit Y samples for bottom field of an interlaced frame. + * + * UV_Meta_Bottom_Field_Plane consists of meta information to decode + * compressed tile data in UV_UBWC_Bottom_Field_Plane. + * UV_UBWC_Bottom_Field_Plane consists of UV data in compressed + * macro-tile format for bottom field of an interlaced frame. + * UBWC decoder block will use UV_Meta_Bottom_Field_Plane data together + * with UV_UBWC_Bottom_Field_Plane data to produce loss-less + * uncompressed 8 bit subsampled color difference samples for bottom + * field of an interlaced frame. + * + * Each tile in Y_UBWC_Bottom_Field_Plane/UV_UBWC_Bottom_Field_Plane is + * independently decodable and randomly accessible. There is no + * dependency between tiles. + * + * <-----Y_TF_Meta_Stride----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Half_height | + * M M M M M M M M M M M M . . | Meta_Y_TF_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-Compressed tile Y_TF Stride-> + * <------- Width -------> + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . ^ ^ + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . Half_height | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | Macro_tile_Y_TF_Scanlines + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * <----UV_TF_Meta_Stride----> + * M M M M M M M M M M M M . . ^ + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . M_UV_TF_Scanlines + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <-Compressed tile UV_TF Stride-> + * U* V* U* V* U* V* U* V* . . . . ^ + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . UV_TF_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <-----Y_BF_Meta_Stride----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Half_height | + * M M M M M M M M M M M M . . | Meta_Y_BF_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-Compressed tile Y_BF Stride-> + * <------- Width -------> + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . ^ ^ + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . Half_height | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | Macro_tile_Y_BF_Scanlines + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * <----UV_BF_Meta_Stride----> + * M M M M M M M M M M M M . . ^ + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . M_UV_BF_Scanlines + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <-Compressed tile UV_BF Stride-> + * U* V* U* V* U* V* U* V* . . . . ^ + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . UV_BF_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * + * Half_height = (Height+1)>>1 + * Y_TF_Stride = align(Width, 128) + * UV_TF_Stride = align(Width, 128) + * Y_TF_Scanlines = align(Half_height, 32) + * UV_TF_Scanlines = align((Half_height+1)/2, 32) + * Y_UBWC_TF_Plane_size = align(Y_TF_Stride * Y_TF_Scanlines, 4096) + * UV_UBWC_TF_Plane_size = align(UV_TF_Stride * UV_TF_Scanlines, 4096) + * Y_TF_Meta_Stride = align(roundup(Width, Y_TileWidth), 64) + * Y_TF_Meta_Scanlines = align(roundup(Half_height, Y_TileHeight), 16) + * Y_TF_Meta_Plane_size = + * align(Y_TF_Meta_Stride * Y_TF_Meta_Scanlines, 4096) + * UV_TF_Meta_Stride = align(roundup(Width, UV_TileWidth), 64) + * UV_TF_Meta_Scanlines = align(roundup(Half_height, UV_TileHeight), 16) + * UV_TF_Meta_Plane_size = + * align(UV_TF_Meta_Stride * UV_TF_Meta_Scanlines, 4096) + * Y_BF_Stride = align(Width, 128) + * UV_BF_Stride = align(Width, 128) + * Y_BF_Scanlines = align(Half_height, 32) + * UV_BF_Scanlines = align((Half_height+1)/2, 32) + * Y_UBWC_BF_Plane_size = align(Y_BF_Stride * Y_BF_Scanlines, 4096) + * UV_UBWC_BF_Plane_size = align(UV_BF_Stride * UV_BF_Scanlines, 4096) + * Y_BF_Meta_Stride = align(roundup(Width, Y_TileWidth), 64) + * Y_BF_Meta_Scanlines = align(roundup(Half_height, Y_TileHeight), 16) + * Y_BF_Meta_Plane_size = + * align(Y_BF_Meta_Stride * Y_BF_Meta_Scanlines, 4096) + * UV_BF_Meta_Stride = align(roundup(Width, UV_TileWidth), 64) + * UV_BF_Meta_Scanlines = align(roundup(Half_height, UV_TileHeight), 16) + * UV_BF_Meta_Plane_size = + * align(UV_BF_Meta_Stride * UV_BF_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align( Y_UBWC_TF_Plane_size + UV_UBWC_TF_Plane_size + + * Y_TF_Meta_Plane_size + UV_TF_Meta_Plane_size + + * Y_UBWC_BF_Plane_size + UV_UBWC_BF_Plane_size + + * Y_BF_Meta_Plane_size + UV_BF_Meta_Plane_size + + * + max(Extradata, Y_TF_Stride * 48), 4096) + */ + COLOR_FMT_NV12_UBWC, + /* Venus NV12 10-bit UBWC: + * Compressed Macro-tile format for NV12. + * Contains 4 planes in the following order - + * (A) Y_Meta_Plane + * (B) Y_UBWC_Plane + * (C) UV_Meta_Plane + * (D) UV_UBWC_Plane + * + * Y_Meta_Plane consists of meta information to decode compressed + * tile data in Y_UBWC_Plane. + * Y_UBWC_Plane consists of Y data in compressed macro-tile format. + * UBWC decoder block will use the Y_Meta_Plane data together with + * Y_UBWC_Plane data to produce loss-less uncompressed 10 bit Y samples. + * + * UV_Meta_Plane consists of meta information to decode compressed + * tile data in UV_UBWC_Plane. + * UV_UBWC_Plane consists of UV data in compressed macro-tile format. + * UBWC decoder block will use UV_Meta_Plane data together with + * UV_UBWC_Plane data to produce loss-less uncompressed 10 bit 2x2 + * subsampled color difference samples. + * + * Each tile in Y_UBWC_Plane/UV_UBWC_Plane is independently decodable + * and randomly accessible. There is no dependency between tiles. + * + * <----- Y_Meta_Stride -----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Height | + * M M M M M M M M M M M M . . | Meta_Y_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <--Compressed tile Y Stride---> + * <------- Width -------> + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . ^ ^ + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . Height | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | Macro_tile_Y_Scanlines + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * <----- UV_Meta_Stride ----> + * M M M M M M M M M M M M . . ^ + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . M_UV_Scanlines + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <--Compressed tile UV Stride---> + * U* V* U* V* U* V* U* V* . . . . ^ + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . UV_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * + * + * Y_Stride = align(Width * 4/3, 128) + * UV_Stride = align(Width * 4/3, 128) + * Y_Scanlines = align(Height, 32) + * UV_Scanlines = align(Height/2, 16) + * Y_UBWC_Plane_Size = align(Y_Stride * Y_Scanlines, 4096) + * UV_UBWC_Plane_Size = align(UV_Stride * UV_Scanlines, 4096) + * Y_Meta_Stride = align(roundup(Width, Y_TileWidth), 64) + * Y_Meta_Scanlines = align(roundup(Height, Y_TileHeight), 16) + * Y_Meta_Plane_size = align(Y_Meta_Stride * Y_Meta_Scanlines, 4096) + * UV_Meta_Stride = align(roundup(Width, UV_TileWidth), 64) + * UV_Meta_Scanlines = align(roundup(Height, UV_TileHeight), 16) + * UV_Meta_Plane_size = align(UV_Meta_Stride * UV_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align(Y_UBWC_Plane_size + UV_UBWC_Plane_size + + * Y_Meta_Plane_size + UV_Meta_Plane_size + * + max(Extradata, Y_Stride * 48), 4096) + */ + COLOR_FMT_NV12_BPP10_UBWC, + /* Venus RGBA8888 format: + * Contains 1 plane in the following order - + * (A) RGBA plane + * + * <-------- RGB_Stride --------> + * <------- Width -------> + * R R R R R R R R R R R R . . . . ^ ^ + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . Height | + * R R R R R R R R R R R R . . . . | RGB_Scanlines + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * + * RGB_Stride = align(Width * 4, 128) + * RGB_Scanlines = align(Height, 32) + * RGB_Plane_size = align(RGB_Stride * RGB_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align(RGB_Plane_size + Extradata, 4096) + */ + COLOR_FMT_RGBA8888, + /* Venus RGBA8888 UBWC format: + * Contains 2 planes in the following order - + * (A) Meta plane + * (B) RGBA plane + * + * <--- RGB_Meta_Stride ----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Height | + * M M M M M M M M M M M M . . | Meta_RGB_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-------- RGB_Stride --------> + * <------- Width -------> + * R R R R R R R R R R R R . . . . ^ ^ + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . Height | + * R R R R R R R R R R R R . . . . | RGB_Scanlines + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * + * RGB_Stride = align(Width * 4, 128) + * RGB_Scanlines = align(Height, 32) + * RGB_Plane_size = align(RGB_Stride * RGB_Scanlines, 4096) + * RGB_Meta_Stride = align(roundup(Width, RGB_TileWidth), 64) + * RGB_Meta_Scanline = align(roundup(Height, RGB_TileHeight), 16) + * RGB_Meta_Plane_size = align(RGB_Meta_Stride * + * RGB_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align(RGB_Meta_Plane_size + RGB_Plane_size + + * Extradata, 4096) + */ + COLOR_FMT_RGBA8888_UBWC, + /* Venus RGBA1010102 UBWC format: + * Contains 2 planes in the following order - + * (A) Meta plane + * (B) RGBA plane + * + * <--- RGB_Meta_Stride ----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Height | + * M M M M M M M M M M M M . . | Meta_RGB_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-------- RGB_Stride --------> + * <------- Width -------> + * R R R R R R R R R R R R . . . . ^ ^ + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . Height | + * R R R R R R R R R R R R . . . . | RGB_Scanlines + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * + * RGB_Stride = align(Width * 4, 256) + * RGB_Scanlines = align(Height, 16) + * RGB_Plane_size = align(RGB_Stride * RGB_Scanlines, 4096) + * RGB_Meta_Stride = align(roundup(Width, RGB_TileWidth), 64) + * RGB_Meta_Scanline = align(roundup(Height, RGB_TileHeight), 16) + * RGB_Meta_Plane_size = align(RGB_Meta_Stride * + * RGB_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align(RGB_Meta_Plane_size + RGB_Plane_size + + * Extradata, 4096) + */ + COLOR_FMT_RGBA1010102_UBWC, + /* Venus RGB565 UBWC format: + * Contains 2 planes in the following order - + * (A) Meta plane + * (B) RGB plane + * + * <--- RGB_Meta_Stride ----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Height | + * M M M M M M M M M M M M . . | Meta_RGB_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <-------- RGB_Stride --------> + * <------- Width -------> + * R R R R R R R R R R R R . . . . ^ ^ + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . Height | + * R R R R R R R R R R R R . . . . | RGB_Scanlines + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . | | + * R R R R R R R R R R R R . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * + * RGB_Stride = align(Width * 2, 128) + * RGB_Scanlines = align(Height, 16) + * RGB_Plane_size = align(RGB_Stride * RGB_Scanlines, 4096) + * RGB_Meta_Stride = align(roundup(Width, RGB_TileWidth), 64) + * RGB_Meta_Scanline = align(roundup(Height, RGB_TileHeight), 16) + * RGB_Meta_Plane_size = align(RGB_Meta_Stride * + * RGB_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align(RGB_Meta_Plane_size + RGB_Plane_size + + * Extradata, 4096) + */ + COLOR_FMT_RGB565_UBWC, + /* P010 UBWC: + * Compressed Macro-tile format for NV12. + * Contains 4 planes in the following order - + * (A) Y_Meta_Plane + * (B) Y_UBWC_Plane + * (C) UV_Meta_Plane + * (D) UV_UBWC_Plane + * + * Y_Meta_Plane consists of meta information to decode compressed + * tile data in Y_UBWC_Plane. + * Y_UBWC_Plane consists of Y data in compressed macro-tile format. + * UBWC decoder block will use the Y_Meta_Plane data together with + * Y_UBWC_Plane data to produce loss-less uncompressed 10 bit Y samples. + * + * UV_Meta_Plane consists of meta information to decode compressed + * tile data in UV_UBWC_Plane. + * UV_UBWC_Plane consists of UV data in compressed macro-tile format. + * UBWC decoder block will use UV_Meta_Plane data together with + * UV_UBWC_Plane data to produce loss-less uncompressed 10 bit 2x2 + * subsampled color difference samples. + * + * Each tile in Y_UBWC_Plane/UV_UBWC_Plane is independently decodable + * and randomly accessible. There is no dependency between tiles. + * + * <----- Y_Meta_Stride -----> + * <-------- Width ------> + * M M M M M M M M M M M M . . ^ ^ + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . Height | + * M M M M M M M M M M M M . . | Meta_Y_Scanlines + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . | | + * M M M M M M M M M M M M . . V | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . V + * <--Compressed tile Y Stride---> + * <------- Width -------> + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . ^ ^ + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . Height | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | Macro_tile_Y_Scanlines + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . | | + * Y* Y* Y* Y* Y* Y* Y* Y* . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * . . . . . . . . . . . . . . . . V + * <----- UV_Meta_Stride ----> + * M M M M M M M M M M M M . . ^ + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . | + * M M M M M M M M M M M M . . M_UV_Scanlines + * . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * <--Compressed tile UV Stride---> + * U* V* U* V* U* V* U* V* . . . . ^ + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . | + * U* V* U* V* U* V* U* V* . . . . UV_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . -------> Buffer size aligned to 4k + * + * + * Y_Stride = align(Width * 2, 256) + * UV_Stride = align(Width * 2, 256) + * Y_Scanlines = align(Height, 16) + * UV_Scanlines = align(Height/2, 16) + * Y_UBWC_Plane_Size = align(Y_Stride * Y_Scanlines, 4096) + * UV_UBWC_Plane_Size = align(UV_Stride * UV_Scanlines, 4096) + * Y_Meta_Stride = align(roundup(Width, Y_TileWidth), 64) + * Y_Meta_Scanlines = align(roundup(Height, Y_TileHeight), 16) + * Y_Meta_Plane_size = align(Y_Meta_Stride * Y_Meta_Scanlines, 4096) + * UV_Meta_Stride = align(roundup(Width, UV_TileWidth), 64) + * UV_Meta_Scanlines = align(roundup(Height, UV_TileHeight), 16) + * UV_Meta_Plane_size = align(UV_Meta_Stride * UV_Meta_Scanlines, 4096) + * Extradata = 8k + * + * Total size = align(Y_UBWC_Plane_size + UV_UBWC_Plane_size + + * Y_Meta_Plane_size + UV_Meta_Plane_size + * + max(Extradata, Y_Stride * 48), 4096) + */ + COLOR_FMT_P010_UBWC, + /* Venus P010: + * YUV 4:2:0 image with a plane of 10 bit Y samples followed + * by an interleaved U/V plane containing 10 bit 2x2 subsampled + * colour difference samples. + * + * <-------- Y/UV_Stride --------> + * <------- Width -------> + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . ^ ^ + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . Height | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | Y_Scanlines + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . | | + * Y Y Y Y Y Y Y Y Y Y Y Y . . . . V | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * U V U V U V U V U V U V . . . . ^ + * U V U V U V U V U V U V . . . . | + * U V U V U V U V U V U V . . . . | + * U V U V U V U V U V U V . . . . UV_Scanlines + * . . . . . . . . . . . . . . . . | + * . . . . . . . . . . . . . . . . V + * . . . . . . . . . . . . . . . . --> Buffer size alignment + * + * Y_Stride : Width * 2 aligned to 128 + * UV_Stride : Width * 2 aligned to 128 + * Y_Scanlines: Height aligned to 32 + * UV_Scanlines: Height/2 aligned to 16 + * Extradata: Arbitrary (software-imposed) padding + * Total size = align((Y_Stride * Y_Scanlines + * + UV_Stride * UV_Scanlines + * + max(Extradata, Y_Stride * 8), 4096) + */ + COLOR_FMT_P010, +}; + +#define COLOR_FMT_RGBA1010102_UBWC COLOR_FMT_RGBA1010102_UBWC +#define COLOR_FMT_RGB565_UBWC COLOR_FMT_RGB565_UBWC +#define COLOR_FMT_P010_UBWC COLOR_FMT_P010_UBWC +#define COLOR_FMT_P010 COLOR_FMT_P010 + +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + */ +static unsigned int VENUS_Y_STRIDE(int color_fmt, int width) +{ + unsigned int stride = 0; + + if (!width) + return 0; + + switch (color_fmt) { + case COLOR_FMT_NV21: + case COLOR_FMT_NV12: + case COLOR_FMT_NV12_MVTB: + case COLOR_FMT_NV12_UBWC: + stride = MSM_MEDIA_ALIGN(width, 128); + break; + case COLOR_FMT_NV12_BPP10_UBWC: + stride = MSM_MEDIA_ALIGN(width, 192); + stride = MSM_MEDIA_ALIGN(stride * 4 / 3, 256); + break; + case COLOR_FMT_P010_UBWC: + stride = MSM_MEDIA_ALIGN(width * 2, 256); + break; + case COLOR_FMT_P010: + stride = MSM_MEDIA_ALIGN(width * 2, 128); + break; + } + + return stride; +} + +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + */ +static unsigned int VENUS_UV_STRIDE(int color_fmt, int width) +{ + unsigned int stride = 0; + + if (!width) + return 0; + + switch (color_fmt) { + case COLOR_FMT_NV21: + case COLOR_FMT_NV12: + case COLOR_FMT_NV12_MVTB: + case COLOR_FMT_NV12_UBWC: + stride = MSM_MEDIA_ALIGN(width, 128); + break; + case COLOR_FMT_NV12_BPP10_UBWC: + stride = MSM_MEDIA_ALIGN(width, 192); + stride = MSM_MEDIA_ALIGN(stride * 4 / 3, 256); + break; + case COLOR_FMT_P010_UBWC: + stride = MSM_MEDIA_ALIGN(width * 2, 256); + break; + case COLOR_FMT_P010: + stride = MSM_MEDIA_ALIGN(width * 2, 128); + break; + } + + return stride; +} + +/* + * Function arguments: + * @color_fmt + * @height + * Progressive: height + * Interlaced: (height+1)>>1 + */ +static unsigned int VENUS_Y_SCANLINES(int color_fmt, int height) +{ + unsigned int sclines = 0; + + if (!height) + return 0; + + switch (color_fmt) { + case COLOR_FMT_NV21: + case COLOR_FMT_NV12: + case COLOR_FMT_NV12_MVTB: + case COLOR_FMT_NV12_UBWC: + case COLOR_FMT_P010: + sclines = MSM_MEDIA_ALIGN(height, 32); + break; + case COLOR_FMT_NV12_BPP10_UBWC: + case COLOR_FMT_P010_UBWC: + sclines = MSM_MEDIA_ALIGN(height, 16); + break; + } + + return sclines; +} + +/* + * Function arguments: + * @color_fmt + * @height + * Progressive: height + * Interlaced: (height+1)>>1 + */ +static unsigned int VENUS_UV_SCANLINES(int color_fmt, int height) +{ + unsigned int sclines = 0; + + if (!height) + return 0; + + switch (color_fmt) { + case COLOR_FMT_NV21: + case COLOR_FMT_NV12: + case COLOR_FMT_NV12_MVTB: + case COLOR_FMT_NV12_BPP10_UBWC: + case COLOR_FMT_P010_UBWC: + case COLOR_FMT_P010: + sclines = MSM_MEDIA_ALIGN((height + 1) >> 1, 16); + break; + case COLOR_FMT_NV12_UBWC: + sclines = MSM_MEDIA_ALIGN((height + 1) >> 1, 32); + break; + } + + return sclines; +} + +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + */ +static unsigned int VENUS_Y_META_STRIDE(int color_fmt, int width) +{ + int y_tile_width = 0, y_meta_stride; + + if (!width) + return 0; + + switch (color_fmt) { + case COLOR_FMT_NV12_UBWC: + case COLOR_FMT_P010_UBWC: + y_tile_width = 32; + break; + case COLOR_FMT_NV12_BPP10_UBWC: + y_tile_width = 48; + break; + default: + return 0; + } + + y_meta_stride = MSM_MEDIA_ROUNDUP(width, y_tile_width); + return MSM_MEDIA_ALIGN(y_meta_stride, 64); +} + +/* + * Function arguments: + * @color_fmt + * @height + * Progressive: height + * Interlaced: (height+1)>>1 + */ +static unsigned int VENUS_Y_META_SCANLINES(int color_fmt, int height) +{ + int y_tile_height = 0, y_meta_scanlines; + + if (!height) + return 0; + + switch (color_fmt) { + case COLOR_FMT_NV12_UBWC: + y_tile_height = 8; + break; + case COLOR_FMT_NV12_BPP10_UBWC: + case COLOR_FMT_P010_UBWC: + y_tile_height = 4; + break; + default: + return 0; + } + + y_meta_scanlines = MSM_MEDIA_ROUNDUP(height, y_tile_height); + return MSM_MEDIA_ALIGN(y_meta_scanlines, 16); +} + +/* + * Function arguments: + * @color_fmt + * @width + * Progressive: width + * Interlaced: width + */ +static unsigned int VENUS_UV_META_STRIDE(int color_fmt, int width) +{ + int uv_tile_width = 0, uv_meta_stride; + + if (!width) + return 0; + + switch (color_fmt) { + case COLOR_FMT_NV12_UBWC: + case COLOR_FMT_P010_UBWC: + uv_tile_width = 16; + break; + case COLOR_FMT_NV12_BPP10_UBWC: + uv_tile_width = 24; + break; + default: + return 0; + } + + uv_meta_stride = MSM_MEDIA_ROUNDUP((width+1)>>1, uv_tile_width); + return MSM_MEDIA_ALIGN(uv_meta_stride, 64); +} + +/* + * Function arguments: + * @color_fmt + * @height + * Progressive: height + * Interlaced: (height+1)>>1 + */ +static unsigned int VENUS_UV_META_SCANLINES(int color_fmt, int height) +{ + int uv_tile_height = 0, uv_meta_scanlines; + + if (!height) + return 0; + + switch (color_fmt) { + case COLOR_FMT_NV12_UBWC: + uv_tile_height = 8; + break; + case COLOR_FMT_NV12_BPP10_UBWC: + case COLOR_FMT_P010_UBWC: + uv_tile_height = 4; + break; + default: + return 0; + } + + uv_meta_scanlines = MSM_MEDIA_ROUNDUP((height+1)>>1, uv_tile_height); + return MSM_MEDIA_ALIGN(uv_meta_scanlines, 16); +} + +static unsigned int VENUS_RGB_STRIDE(int color_fmt, int width) +{ + unsigned int alignment = 0, bpp = 4; + + if (!width) + return 0; + + switch (color_fmt) { + case COLOR_FMT_RGBA8888: + alignment = 128; + break; + case COLOR_FMT_RGB565_UBWC: + alignment = 256; + bpp = 2; + break; + case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: + alignment = 256; + break; + default: + return 0; + } + + return MSM_MEDIA_ALIGN(width * bpp, alignment); +} + +static unsigned int VENUS_RGB_SCANLINES(int color_fmt, int height) +{ + unsigned int alignment = 0; + + if (!height) + return 0; + + switch (color_fmt) { + case COLOR_FMT_RGBA8888: + alignment = 32; + break; + case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: + case COLOR_FMT_RGB565_UBWC: + alignment = 16; + break; + default: + return 0; + } + + return MSM_MEDIA_ALIGN(height, alignment); +} + +static unsigned int VENUS_RGB_META_STRIDE(int color_fmt, int width) +{ + int rgb_meta_stride; + + if (!width) + return 0; + + switch (color_fmt) { + case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: + case COLOR_FMT_RGB565_UBWC: + rgb_meta_stride = MSM_MEDIA_ROUNDUP(width, 16); + return MSM_MEDIA_ALIGN(rgb_meta_stride, 64); + } + + return 0; +} + +static unsigned int VENUS_RGB_META_SCANLINES(int color_fmt, int height) +{ + int rgb_meta_scanlines; + + if (!height) + return 0; + + switch (color_fmt) { + case COLOR_FMT_RGBA8888_UBWC: + case COLOR_FMT_RGBA1010102_UBWC: + case COLOR_FMT_RGB565_UBWC: + rgb_meta_scanlines = MSM_MEDIA_ROUNDUP(height, 4); + return MSM_MEDIA_ALIGN(rgb_meta_scanlines, 16); + } + + return 0; +} + +#endif diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4.xml.h b/drivers/gpu/drm/msm/disp/mdp4/mdp4.xml.h new file mode 100644 index 000000000..f1d1de578 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4.xml.h @@ -0,0 +1,1174 @@ +#ifndef MDP4_XML +#define MDP4_XML + +/* Autogenerated file, DO NOT EDIT manually! + +This file was generated by the rules-ng-ng headergen tool in this git repository: +http://github.com/freedreno/envytools/ +git clone https://github.com/freedreno/envytools.git + +The rules-ng-ng source files this header was generated from are: +- /home/robclark/src/envytools/rnndb/msm.xml ( 676 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/freedreno_copyright.xml ( 1572 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/mdp/mdp4.xml ( 20915 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/mdp/mdp_common.xml ( 2849 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/mdp/mdp5.xml ( 37411 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/dsi/dsi.xml ( 42301 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/dsi/sfpb.xml ( 602 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/dsi/mmss_cc.xml ( 1686 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/hdmi/qfprom.xml ( 600 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/hdmi/hdmi.xml ( 41874 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/edp/edp.xml ( 10416 bytes, from 2020-07-23 21:58:14) + +Copyright (C) 2013-2020 by the following authors: +- Rob Clark <robdclark@gmail.com> (robclark) +- Ilia Mirkin <imirkin@alum.mit.edu> (imirkin) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice (including the +next paragraph) shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + + +enum mdp4_pipe { + VG1 = 0, + VG2 = 1, + RGB1 = 2, + RGB2 = 3, + RGB3 = 4, + VG3 = 5, + VG4 = 6, +}; + +enum mdp4_mixer { + MIXER0 = 0, + MIXER1 = 1, + MIXER2 = 2, +}; + +enum mdp4_intf { + INTF_LCDC_DTV = 0, + INTF_DSI_VIDEO = 1, + INTF_DSI_CMD = 2, + INTF_EBI2_TV = 3, +}; + +enum mdp4_cursor_format { + CURSOR_ARGB = 1, + CURSOR_XRGB = 2, +}; + +enum mdp4_frame_format { + FRAME_LINEAR = 0, + FRAME_TILE_ARGB_4X4 = 1, + FRAME_TILE_YCBCR_420 = 2, +}; + +enum mdp4_scale_unit { + SCALE_FIR = 0, + SCALE_MN_PHASE = 1, + SCALE_PIXEL_RPT = 2, +}; + +enum mdp4_dma { + DMA_P = 0, + DMA_S = 1, + DMA_E = 2, +}; + +#define MDP4_IRQ_OVERLAY0_DONE 0x00000001 +#define MDP4_IRQ_OVERLAY1_DONE 0x00000002 +#define MDP4_IRQ_DMA_S_DONE 0x00000004 +#define MDP4_IRQ_DMA_E_DONE 0x00000008 +#define MDP4_IRQ_DMA_P_DONE 0x00000010 +#define MDP4_IRQ_VG1_HISTOGRAM 0x00000020 +#define MDP4_IRQ_VG2_HISTOGRAM 0x00000040 +#define MDP4_IRQ_PRIMARY_VSYNC 0x00000080 +#define MDP4_IRQ_PRIMARY_INTF_UDERRUN 0x00000100 +#define MDP4_IRQ_EXTERNAL_VSYNC 0x00000200 +#define MDP4_IRQ_EXTERNAL_INTF_UDERRUN 0x00000400 +#define MDP4_IRQ_PRIMARY_RDPTR 0x00000800 +#define MDP4_IRQ_DMA_P_HISTOGRAM 0x00020000 +#define MDP4_IRQ_DMA_S_HISTOGRAM 0x04000000 +#define MDP4_IRQ_OVERLAY2_DONE 0x40000000 +#define REG_MDP4_VERSION 0x00000000 +#define MDP4_VERSION_MINOR__MASK 0x00ff0000 +#define MDP4_VERSION_MINOR__SHIFT 16 +static inline uint32_t MDP4_VERSION_MINOR(uint32_t val) +{ + return ((val) << MDP4_VERSION_MINOR__SHIFT) & MDP4_VERSION_MINOR__MASK; +} +#define MDP4_VERSION_MAJOR__MASK 0xff000000 +#define MDP4_VERSION_MAJOR__SHIFT 24 +static inline uint32_t MDP4_VERSION_MAJOR(uint32_t val) +{ + return ((val) << MDP4_VERSION_MAJOR__SHIFT) & MDP4_VERSION_MAJOR__MASK; +} + +#define REG_MDP4_OVLP0_KICK 0x00000004 + +#define REG_MDP4_OVLP1_KICK 0x00000008 + +#define REG_MDP4_OVLP2_KICK 0x000000d0 + +#define REG_MDP4_DMA_P_KICK 0x0000000c + +#define REG_MDP4_DMA_S_KICK 0x00000010 + +#define REG_MDP4_DMA_E_KICK 0x00000014 + +#define REG_MDP4_DISP_STATUS 0x00000018 + +#define REG_MDP4_DISP_INTF_SEL 0x00000038 +#define MDP4_DISP_INTF_SEL_PRIM__MASK 0x00000003 +#define MDP4_DISP_INTF_SEL_PRIM__SHIFT 0 +static inline uint32_t MDP4_DISP_INTF_SEL_PRIM(enum mdp4_intf val) +{ + return ((val) << MDP4_DISP_INTF_SEL_PRIM__SHIFT) & MDP4_DISP_INTF_SEL_PRIM__MASK; +} +#define MDP4_DISP_INTF_SEL_SEC__MASK 0x0000000c +#define MDP4_DISP_INTF_SEL_SEC__SHIFT 2 +static inline uint32_t MDP4_DISP_INTF_SEL_SEC(enum mdp4_intf val) +{ + return ((val) << MDP4_DISP_INTF_SEL_SEC__SHIFT) & MDP4_DISP_INTF_SEL_SEC__MASK; +} +#define MDP4_DISP_INTF_SEL_EXT__MASK 0x00000030 +#define MDP4_DISP_INTF_SEL_EXT__SHIFT 4 +static inline uint32_t MDP4_DISP_INTF_SEL_EXT(enum mdp4_intf val) +{ + return ((val) << MDP4_DISP_INTF_SEL_EXT__SHIFT) & MDP4_DISP_INTF_SEL_EXT__MASK; +} +#define MDP4_DISP_INTF_SEL_DSI_VIDEO 0x00000040 +#define MDP4_DISP_INTF_SEL_DSI_CMD 0x00000080 + +#define REG_MDP4_RESET_STATUS 0x0000003c + +#define REG_MDP4_READ_CNFG 0x0000004c + +#define REG_MDP4_INTR_ENABLE 0x00000050 + +#define REG_MDP4_INTR_STATUS 0x00000054 + +#define REG_MDP4_INTR_CLEAR 0x00000058 + +#define REG_MDP4_EBI2_LCD0 0x00000060 + +#define REG_MDP4_EBI2_LCD1 0x00000064 + +#define REG_MDP4_PORTMAP_MODE 0x00000070 + +#define REG_MDP4_CS_CONTROLLER0 0x000000c0 + +#define REG_MDP4_CS_CONTROLLER1 0x000000c4 + +#define REG_MDP4_LAYERMIXER2_IN_CFG 0x000100f0 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE0__MASK 0x00000007 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE0__SHIFT 0 +static inline uint32_t MDP4_LAYERMIXER2_IN_CFG_PIPE0(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER2_IN_CFG_PIPE0__SHIFT) & MDP4_LAYERMIXER2_IN_CFG_PIPE0__MASK; +} +#define MDP4_LAYERMIXER2_IN_CFG_PIPE0_MIXER1 0x00000008 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE1__MASK 0x00000070 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE1__SHIFT 4 +static inline uint32_t MDP4_LAYERMIXER2_IN_CFG_PIPE1(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER2_IN_CFG_PIPE1__SHIFT) & MDP4_LAYERMIXER2_IN_CFG_PIPE1__MASK; +} +#define MDP4_LAYERMIXER2_IN_CFG_PIPE1_MIXER1 0x00000080 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE2__MASK 0x00000700 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE2__SHIFT 8 +static inline uint32_t MDP4_LAYERMIXER2_IN_CFG_PIPE2(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER2_IN_CFG_PIPE2__SHIFT) & MDP4_LAYERMIXER2_IN_CFG_PIPE2__MASK; +} +#define MDP4_LAYERMIXER2_IN_CFG_PIPE2_MIXER1 0x00000800 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE3__MASK 0x00007000 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE3__SHIFT 12 +static inline uint32_t MDP4_LAYERMIXER2_IN_CFG_PIPE3(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER2_IN_CFG_PIPE3__SHIFT) & MDP4_LAYERMIXER2_IN_CFG_PIPE3__MASK; +} +#define MDP4_LAYERMIXER2_IN_CFG_PIPE3_MIXER1 0x00008000 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE4__MASK 0x00070000 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE4__SHIFT 16 +static inline uint32_t MDP4_LAYERMIXER2_IN_CFG_PIPE4(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER2_IN_CFG_PIPE4__SHIFT) & MDP4_LAYERMIXER2_IN_CFG_PIPE4__MASK; +} +#define MDP4_LAYERMIXER2_IN_CFG_PIPE4_MIXER1 0x00080000 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE5__MASK 0x00700000 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE5__SHIFT 20 +static inline uint32_t MDP4_LAYERMIXER2_IN_CFG_PIPE5(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER2_IN_CFG_PIPE5__SHIFT) & MDP4_LAYERMIXER2_IN_CFG_PIPE5__MASK; +} +#define MDP4_LAYERMIXER2_IN_CFG_PIPE5_MIXER1 0x00800000 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE6__MASK 0x07000000 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE6__SHIFT 24 +static inline uint32_t MDP4_LAYERMIXER2_IN_CFG_PIPE6(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER2_IN_CFG_PIPE6__SHIFT) & MDP4_LAYERMIXER2_IN_CFG_PIPE6__MASK; +} +#define MDP4_LAYERMIXER2_IN_CFG_PIPE6_MIXER1 0x08000000 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE7__MASK 0x70000000 +#define MDP4_LAYERMIXER2_IN_CFG_PIPE7__SHIFT 28 +static inline uint32_t MDP4_LAYERMIXER2_IN_CFG_PIPE7(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER2_IN_CFG_PIPE7__SHIFT) & MDP4_LAYERMIXER2_IN_CFG_PIPE7__MASK; +} +#define MDP4_LAYERMIXER2_IN_CFG_PIPE7_MIXER1 0x80000000 + +#define REG_MDP4_LAYERMIXER_IN_CFG_UPDATE_METHOD 0x000100fc + +#define REG_MDP4_LAYERMIXER_IN_CFG 0x00010100 +#define MDP4_LAYERMIXER_IN_CFG_PIPE0__MASK 0x00000007 +#define MDP4_LAYERMIXER_IN_CFG_PIPE0__SHIFT 0 +static inline uint32_t MDP4_LAYERMIXER_IN_CFG_PIPE0(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER_IN_CFG_PIPE0__SHIFT) & MDP4_LAYERMIXER_IN_CFG_PIPE0__MASK; +} +#define MDP4_LAYERMIXER_IN_CFG_PIPE0_MIXER1 0x00000008 +#define MDP4_LAYERMIXER_IN_CFG_PIPE1__MASK 0x00000070 +#define MDP4_LAYERMIXER_IN_CFG_PIPE1__SHIFT 4 +static inline uint32_t MDP4_LAYERMIXER_IN_CFG_PIPE1(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER_IN_CFG_PIPE1__SHIFT) & MDP4_LAYERMIXER_IN_CFG_PIPE1__MASK; +} +#define MDP4_LAYERMIXER_IN_CFG_PIPE1_MIXER1 0x00000080 +#define MDP4_LAYERMIXER_IN_CFG_PIPE2__MASK 0x00000700 +#define MDP4_LAYERMIXER_IN_CFG_PIPE2__SHIFT 8 +static inline uint32_t MDP4_LAYERMIXER_IN_CFG_PIPE2(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER_IN_CFG_PIPE2__SHIFT) & MDP4_LAYERMIXER_IN_CFG_PIPE2__MASK; +} +#define MDP4_LAYERMIXER_IN_CFG_PIPE2_MIXER1 0x00000800 +#define MDP4_LAYERMIXER_IN_CFG_PIPE3__MASK 0x00007000 +#define MDP4_LAYERMIXER_IN_CFG_PIPE3__SHIFT 12 +static inline uint32_t MDP4_LAYERMIXER_IN_CFG_PIPE3(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER_IN_CFG_PIPE3__SHIFT) & MDP4_LAYERMIXER_IN_CFG_PIPE3__MASK; +} +#define MDP4_LAYERMIXER_IN_CFG_PIPE3_MIXER1 0x00008000 +#define MDP4_LAYERMIXER_IN_CFG_PIPE4__MASK 0x00070000 +#define MDP4_LAYERMIXER_IN_CFG_PIPE4__SHIFT 16 +static inline uint32_t MDP4_LAYERMIXER_IN_CFG_PIPE4(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER_IN_CFG_PIPE4__SHIFT) & MDP4_LAYERMIXER_IN_CFG_PIPE4__MASK; +} +#define MDP4_LAYERMIXER_IN_CFG_PIPE4_MIXER1 0x00080000 +#define MDP4_LAYERMIXER_IN_CFG_PIPE5__MASK 0x00700000 +#define MDP4_LAYERMIXER_IN_CFG_PIPE5__SHIFT 20 +static inline uint32_t MDP4_LAYERMIXER_IN_CFG_PIPE5(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER_IN_CFG_PIPE5__SHIFT) & MDP4_LAYERMIXER_IN_CFG_PIPE5__MASK; +} +#define MDP4_LAYERMIXER_IN_CFG_PIPE5_MIXER1 0x00800000 +#define MDP4_LAYERMIXER_IN_CFG_PIPE6__MASK 0x07000000 +#define MDP4_LAYERMIXER_IN_CFG_PIPE6__SHIFT 24 +static inline uint32_t MDP4_LAYERMIXER_IN_CFG_PIPE6(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER_IN_CFG_PIPE6__SHIFT) & MDP4_LAYERMIXER_IN_CFG_PIPE6__MASK; +} +#define MDP4_LAYERMIXER_IN_CFG_PIPE6_MIXER1 0x08000000 +#define MDP4_LAYERMIXER_IN_CFG_PIPE7__MASK 0x70000000 +#define MDP4_LAYERMIXER_IN_CFG_PIPE7__SHIFT 28 +static inline uint32_t MDP4_LAYERMIXER_IN_CFG_PIPE7(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP4_LAYERMIXER_IN_CFG_PIPE7__SHIFT) & MDP4_LAYERMIXER_IN_CFG_PIPE7__MASK; +} +#define MDP4_LAYERMIXER_IN_CFG_PIPE7_MIXER1 0x80000000 + +#define REG_MDP4_VG2_SRC_FORMAT 0x00030050 + +#define REG_MDP4_VG2_CONST_COLOR 0x00031008 + +#define REG_MDP4_OVERLAY_FLUSH 0x00018000 +#define MDP4_OVERLAY_FLUSH_OVLP0 0x00000001 +#define MDP4_OVERLAY_FLUSH_OVLP1 0x00000002 +#define MDP4_OVERLAY_FLUSH_VG1 0x00000004 +#define MDP4_OVERLAY_FLUSH_VG2 0x00000008 +#define MDP4_OVERLAY_FLUSH_RGB1 0x00000010 +#define MDP4_OVERLAY_FLUSH_RGB2 0x00000020 + +static inline uint32_t __offset_OVLP(uint32_t idx) +{ + switch (idx) { + case 0: return 0x00010000; + case 1: return 0x00018000; + case 2: return 0x00088000; + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP4_OVLP(uint32_t i0) { return 0x00000000 + __offset_OVLP(i0); } + +static inline uint32_t REG_MDP4_OVLP_CFG(uint32_t i0) { return 0x00000004 + __offset_OVLP(i0); } + +static inline uint32_t REG_MDP4_OVLP_SIZE(uint32_t i0) { return 0x00000008 + __offset_OVLP(i0); } +#define MDP4_OVLP_SIZE_HEIGHT__MASK 0xffff0000 +#define MDP4_OVLP_SIZE_HEIGHT__SHIFT 16 +static inline uint32_t MDP4_OVLP_SIZE_HEIGHT(uint32_t val) +{ + return ((val) << MDP4_OVLP_SIZE_HEIGHT__SHIFT) & MDP4_OVLP_SIZE_HEIGHT__MASK; +} +#define MDP4_OVLP_SIZE_WIDTH__MASK 0x0000ffff +#define MDP4_OVLP_SIZE_WIDTH__SHIFT 0 +static inline uint32_t MDP4_OVLP_SIZE_WIDTH(uint32_t val) +{ + return ((val) << MDP4_OVLP_SIZE_WIDTH__SHIFT) & MDP4_OVLP_SIZE_WIDTH__MASK; +} + +static inline uint32_t REG_MDP4_OVLP_BASE(uint32_t i0) { return 0x0000000c + __offset_OVLP(i0); } + +static inline uint32_t REG_MDP4_OVLP_STRIDE(uint32_t i0) { return 0x00000010 + __offset_OVLP(i0); } + +static inline uint32_t REG_MDP4_OVLP_OPMODE(uint32_t i0) { return 0x00000014 + __offset_OVLP(i0); } + +static inline uint32_t __offset_STAGE(uint32_t idx) +{ + switch (idx) { + case 0: return 0x00000104; + case 1: return 0x00000124; + case 2: return 0x00000144; + case 3: return 0x00000160; + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP4_OVLP_STAGE(uint32_t i0, uint32_t i1) { return 0x00000000 + __offset_OVLP(i0) + __offset_STAGE(i1); } + +static inline uint32_t REG_MDP4_OVLP_STAGE_OP(uint32_t i0, uint32_t i1) { return 0x00000000 + __offset_OVLP(i0) + __offset_STAGE(i1); } +#define MDP4_OVLP_STAGE_OP_FG_ALPHA__MASK 0x00000003 +#define MDP4_OVLP_STAGE_OP_FG_ALPHA__SHIFT 0 +static inline uint32_t MDP4_OVLP_STAGE_OP_FG_ALPHA(enum mdp_alpha_type val) +{ + return ((val) << MDP4_OVLP_STAGE_OP_FG_ALPHA__SHIFT) & MDP4_OVLP_STAGE_OP_FG_ALPHA__MASK; +} +#define MDP4_OVLP_STAGE_OP_FG_INV_ALPHA 0x00000004 +#define MDP4_OVLP_STAGE_OP_FG_MOD_ALPHA 0x00000008 +#define MDP4_OVLP_STAGE_OP_BG_ALPHA__MASK 0x00000030 +#define MDP4_OVLP_STAGE_OP_BG_ALPHA__SHIFT 4 +static inline uint32_t MDP4_OVLP_STAGE_OP_BG_ALPHA(enum mdp_alpha_type val) +{ + return ((val) << MDP4_OVLP_STAGE_OP_BG_ALPHA__SHIFT) & MDP4_OVLP_STAGE_OP_BG_ALPHA__MASK; +} +#define MDP4_OVLP_STAGE_OP_BG_INV_ALPHA 0x00000040 +#define MDP4_OVLP_STAGE_OP_BG_MOD_ALPHA 0x00000080 +#define MDP4_OVLP_STAGE_OP_FG_TRANSP 0x00000100 +#define MDP4_OVLP_STAGE_OP_BG_TRANSP 0x00000200 + +static inline uint32_t REG_MDP4_OVLP_STAGE_FG_ALPHA(uint32_t i0, uint32_t i1) { return 0x00000004 + __offset_OVLP(i0) + __offset_STAGE(i1); } + +static inline uint32_t REG_MDP4_OVLP_STAGE_BG_ALPHA(uint32_t i0, uint32_t i1) { return 0x00000008 + __offset_OVLP(i0) + __offset_STAGE(i1); } + +static inline uint32_t REG_MDP4_OVLP_STAGE_TRANSP_LOW0(uint32_t i0, uint32_t i1) { return 0x0000000c + __offset_OVLP(i0) + __offset_STAGE(i1); } + +static inline uint32_t REG_MDP4_OVLP_STAGE_TRANSP_LOW1(uint32_t i0, uint32_t i1) { return 0x00000010 + __offset_OVLP(i0) + __offset_STAGE(i1); } + +static inline uint32_t REG_MDP4_OVLP_STAGE_TRANSP_HIGH0(uint32_t i0, uint32_t i1) { return 0x00000014 + __offset_OVLP(i0) + __offset_STAGE(i1); } + +static inline uint32_t REG_MDP4_OVLP_STAGE_TRANSP_HIGH1(uint32_t i0, uint32_t i1) { return 0x00000018 + __offset_OVLP(i0) + __offset_STAGE(i1); } + +static inline uint32_t __offset_STAGE_CO3(uint32_t idx) +{ + switch (idx) { + case 0: return 0x00001004; + case 1: return 0x00001404; + case 2: return 0x00001804; + case 3: return 0x00001b84; + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP4_OVLP_STAGE_CO3(uint32_t i0, uint32_t i1) { return 0x00000000 + __offset_OVLP(i0) + __offset_STAGE_CO3(i1); } + +static inline uint32_t REG_MDP4_OVLP_STAGE_CO3_SEL(uint32_t i0, uint32_t i1) { return 0x00000000 + __offset_OVLP(i0) + __offset_STAGE_CO3(i1); } +#define MDP4_OVLP_STAGE_CO3_SEL_FG_ALPHA 0x00000001 + +static inline uint32_t REG_MDP4_OVLP_TRANSP_LOW0(uint32_t i0) { return 0x00000180 + __offset_OVLP(i0); } + +static inline uint32_t REG_MDP4_OVLP_TRANSP_LOW1(uint32_t i0) { return 0x00000184 + __offset_OVLP(i0); } + +static inline uint32_t REG_MDP4_OVLP_TRANSP_HIGH0(uint32_t i0) { return 0x00000188 + __offset_OVLP(i0); } + +static inline uint32_t REG_MDP4_OVLP_TRANSP_HIGH1(uint32_t i0) { return 0x0000018c + __offset_OVLP(i0); } + +static inline uint32_t REG_MDP4_OVLP_CSC_CONFIG(uint32_t i0) { return 0x00000200 + __offset_OVLP(i0); } + +static inline uint32_t REG_MDP4_OVLP_CSC(uint32_t i0) { return 0x00002000 + __offset_OVLP(i0); } + + +static inline uint32_t REG_MDP4_OVLP_CSC_MV(uint32_t i0, uint32_t i1) { return 0x00002400 + __offset_OVLP(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_OVLP_CSC_MV_VAL(uint32_t i0, uint32_t i1) { return 0x00002400 + __offset_OVLP(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_OVLP_CSC_PRE_BV(uint32_t i0, uint32_t i1) { return 0x00002500 + __offset_OVLP(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_OVLP_CSC_PRE_BV_VAL(uint32_t i0, uint32_t i1) { return 0x00002500 + __offset_OVLP(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_OVLP_CSC_POST_BV(uint32_t i0, uint32_t i1) { return 0x00002580 + __offset_OVLP(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_OVLP_CSC_POST_BV_VAL(uint32_t i0, uint32_t i1) { return 0x00002580 + __offset_OVLP(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_OVLP_CSC_PRE_LV(uint32_t i0, uint32_t i1) { return 0x00002600 + __offset_OVLP(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_OVLP_CSC_PRE_LV_VAL(uint32_t i0, uint32_t i1) { return 0x00002600 + __offset_OVLP(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_OVLP_CSC_POST_LV(uint32_t i0, uint32_t i1) { return 0x00002680 + __offset_OVLP(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_OVLP_CSC_POST_LV_VAL(uint32_t i0, uint32_t i1) { return 0x00002680 + __offset_OVLP(i0) + 0x4*i1; } + +#define REG_MDP4_DMA_P_OP_MODE 0x00090070 + +static inline uint32_t REG_MDP4_LUTN(uint32_t i0) { return 0x00094800 + 0x400*i0; } + +static inline uint32_t REG_MDP4_LUTN_LUT(uint32_t i0, uint32_t i1) { return 0x00094800 + 0x400*i0 + 0x4*i1; } + +static inline uint32_t REG_MDP4_LUTN_LUT_VAL(uint32_t i0, uint32_t i1) { return 0x00094800 + 0x400*i0 + 0x4*i1; } + +#define REG_MDP4_DMA_S_OP_MODE 0x000a0028 + +static inline uint32_t REG_MDP4_DMA_E_QUANT(uint32_t i0) { return 0x000b0070 + 0x4*i0; } + +static inline uint32_t __offset_DMA(enum mdp4_dma idx) +{ + switch (idx) { + case DMA_P: return 0x00090000; + case DMA_S: return 0x000a0000; + case DMA_E: return 0x000b0000; + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP4_DMA(enum mdp4_dma i0) { return 0x00000000 + __offset_DMA(i0); } + +static inline uint32_t REG_MDP4_DMA_CONFIG(enum mdp4_dma i0) { return 0x00000000 + __offset_DMA(i0); } +#define MDP4_DMA_CONFIG_G_BPC__MASK 0x00000003 +#define MDP4_DMA_CONFIG_G_BPC__SHIFT 0 +static inline uint32_t MDP4_DMA_CONFIG_G_BPC(enum mdp_bpc val) +{ + return ((val) << MDP4_DMA_CONFIG_G_BPC__SHIFT) & MDP4_DMA_CONFIG_G_BPC__MASK; +} +#define MDP4_DMA_CONFIG_B_BPC__MASK 0x0000000c +#define MDP4_DMA_CONFIG_B_BPC__SHIFT 2 +static inline uint32_t MDP4_DMA_CONFIG_B_BPC(enum mdp_bpc val) +{ + return ((val) << MDP4_DMA_CONFIG_B_BPC__SHIFT) & MDP4_DMA_CONFIG_B_BPC__MASK; +} +#define MDP4_DMA_CONFIG_R_BPC__MASK 0x00000030 +#define MDP4_DMA_CONFIG_R_BPC__SHIFT 4 +static inline uint32_t MDP4_DMA_CONFIG_R_BPC(enum mdp_bpc val) +{ + return ((val) << MDP4_DMA_CONFIG_R_BPC__SHIFT) & MDP4_DMA_CONFIG_R_BPC__MASK; +} +#define MDP4_DMA_CONFIG_PACK_ALIGN_MSB 0x00000080 +#define MDP4_DMA_CONFIG_PACK__MASK 0x0000ff00 +#define MDP4_DMA_CONFIG_PACK__SHIFT 8 +static inline uint32_t MDP4_DMA_CONFIG_PACK(uint32_t val) +{ + return ((val) << MDP4_DMA_CONFIG_PACK__SHIFT) & MDP4_DMA_CONFIG_PACK__MASK; +} +#define MDP4_DMA_CONFIG_DEFLKR_EN 0x01000000 +#define MDP4_DMA_CONFIG_DITHER_EN 0x01000000 + +static inline uint32_t REG_MDP4_DMA_SRC_SIZE(enum mdp4_dma i0) { return 0x00000004 + __offset_DMA(i0); } +#define MDP4_DMA_SRC_SIZE_HEIGHT__MASK 0xffff0000 +#define MDP4_DMA_SRC_SIZE_HEIGHT__SHIFT 16 +static inline uint32_t MDP4_DMA_SRC_SIZE_HEIGHT(uint32_t val) +{ + return ((val) << MDP4_DMA_SRC_SIZE_HEIGHT__SHIFT) & MDP4_DMA_SRC_SIZE_HEIGHT__MASK; +} +#define MDP4_DMA_SRC_SIZE_WIDTH__MASK 0x0000ffff +#define MDP4_DMA_SRC_SIZE_WIDTH__SHIFT 0 +static inline uint32_t MDP4_DMA_SRC_SIZE_WIDTH(uint32_t val) +{ + return ((val) << MDP4_DMA_SRC_SIZE_WIDTH__SHIFT) & MDP4_DMA_SRC_SIZE_WIDTH__MASK; +} + +static inline uint32_t REG_MDP4_DMA_SRC_BASE(enum mdp4_dma i0) { return 0x00000008 + __offset_DMA(i0); } + +static inline uint32_t REG_MDP4_DMA_SRC_STRIDE(enum mdp4_dma i0) { return 0x0000000c + __offset_DMA(i0); } + +static inline uint32_t REG_MDP4_DMA_DST_SIZE(enum mdp4_dma i0) { return 0x00000010 + __offset_DMA(i0); } +#define MDP4_DMA_DST_SIZE_HEIGHT__MASK 0xffff0000 +#define MDP4_DMA_DST_SIZE_HEIGHT__SHIFT 16 +static inline uint32_t MDP4_DMA_DST_SIZE_HEIGHT(uint32_t val) +{ + return ((val) << MDP4_DMA_DST_SIZE_HEIGHT__SHIFT) & MDP4_DMA_DST_SIZE_HEIGHT__MASK; +} +#define MDP4_DMA_DST_SIZE_WIDTH__MASK 0x0000ffff +#define MDP4_DMA_DST_SIZE_WIDTH__SHIFT 0 +static inline uint32_t MDP4_DMA_DST_SIZE_WIDTH(uint32_t val) +{ + return ((val) << MDP4_DMA_DST_SIZE_WIDTH__SHIFT) & MDP4_DMA_DST_SIZE_WIDTH__MASK; +} + +static inline uint32_t REG_MDP4_DMA_CURSOR_SIZE(enum mdp4_dma i0) { return 0x00000044 + __offset_DMA(i0); } +#define MDP4_DMA_CURSOR_SIZE_WIDTH__MASK 0x0000007f +#define MDP4_DMA_CURSOR_SIZE_WIDTH__SHIFT 0 +static inline uint32_t MDP4_DMA_CURSOR_SIZE_WIDTH(uint32_t val) +{ + return ((val) << MDP4_DMA_CURSOR_SIZE_WIDTH__SHIFT) & MDP4_DMA_CURSOR_SIZE_WIDTH__MASK; +} +#define MDP4_DMA_CURSOR_SIZE_HEIGHT__MASK 0x007f0000 +#define MDP4_DMA_CURSOR_SIZE_HEIGHT__SHIFT 16 +static inline uint32_t MDP4_DMA_CURSOR_SIZE_HEIGHT(uint32_t val) +{ + return ((val) << MDP4_DMA_CURSOR_SIZE_HEIGHT__SHIFT) & MDP4_DMA_CURSOR_SIZE_HEIGHT__MASK; +} + +static inline uint32_t REG_MDP4_DMA_CURSOR_BASE(enum mdp4_dma i0) { return 0x00000048 + __offset_DMA(i0); } + +static inline uint32_t REG_MDP4_DMA_CURSOR_POS(enum mdp4_dma i0) { return 0x0000004c + __offset_DMA(i0); } +#define MDP4_DMA_CURSOR_POS_X__MASK 0x0000ffff +#define MDP4_DMA_CURSOR_POS_X__SHIFT 0 +static inline uint32_t MDP4_DMA_CURSOR_POS_X(uint32_t val) +{ + return ((val) << MDP4_DMA_CURSOR_POS_X__SHIFT) & MDP4_DMA_CURSOR_POS_X__MASK; +} +#define MDP4_DMA_CURSOR_POS_Y__MASK 0xffff0000 +#define MDP4_DMA_CURSOR_POS_Y__SHIFT 16 +static inline uint32_t MDP4_DMA_CURSOR_POS_Y(uint32_t val) +{ + return ((val) << MDP4_DMA_CURSOR_POS_Y__SHIFT) & MDP4_DMA_CURSOR_POS_Y__MASK; +} + +static inline uint32_t REG_MDP4_DMA_CURSOR_BLEND_CONFIG(enum mdp4_dma i0) { return 0x00000060 + __offset_DMA(i0); } +#define MDP4_DMA_CURSOR_BLEND_CONFIG_CURSOR_EN 0x00000001 +#define MDP4_DMA_CURSOR_BLEND_CONFIG_FORMAT__MASK 0x00000006 +#define MDP4_DMA_CURSOR_BLEND_CONFIG_FORMAT__SHIFT 1 +static inline uint32_t MDP4_DMA_CURSOR_BLEND_CONFIG_FORMAT(enum mdp4_cursor_format val) +{ + return ((val) << MDP4_DMA_CURSOR_BLEND_CONFIG_FORMAT__SHIFT) & MDP4_DMA_CURSOR_BLEND_CONFIG_FORMAT__MASK; +} +#define MDP4_DMA_CURSOR_BLEND_CONFIG_TRANSP_EN 0x00000008 + +static inline uint32_t REG_MDP4_DMA_CURSOR_BLEND_PARAM(enum mdp4_dma i0) { return 0x00000064 + __offset_DMA(i0); } + +static inline uint32_t REG_MDP4_DMA_BLEND_TRANS_LOW(enum mdp4_dma i0) { return 0x00000068 + __offset_DMA(i0); } + +static inline uint32_t REG_MDP4_DMA_BLEND_TRANS_HIGH(enum mdp4_dma i0) { return 0x0000006c + __offset_DMA(i0); } + +static inline uint32_t REG_MDP4_DMA_FETCH_CONFIG(enum mdp4_dma i0) { return 0x00001004 + __offset_DMA(i0); } + +static inline uint32_t REG_MDP4_DMA_CSC(enum mdp4_dma i0) { return 0x00003000 + __offset_DMA(i0); } + + +static inline uint32_t REG_MDP4_DMA_CSC_MV(enum mdp4_dma i0, uint32_t i1) { return 0x00003400 + __offset_DMA(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_DMA_CSC_MV_VAL(enum mdp4_dma i0, uint32_t i1) { return 0x00003400 + __offset_DMA(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_DMA_CSC_PRE_BV(enum mdp4_dma i0, uint32_t i1) { return 0x00003500 + __offset_DMA(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_DMA_CSC_PRE_BV_VAL(enum mdp4_dma i0, uint32_t i1) { return 0x00003500 + __offset_DMA(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_DMA_CSC_POST_BV(enum mdp4_dma i0, uint32_t i1) { return 0x00003580 + __offset_DMA(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_DMA_CSC_POST_BV_VAL(enum mdp4_dma i0, uint32_t i1) { return 0x00003580 + __offset_DMA(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_DMA_CSC_PRE_LV(enum mdp4_dma i0, uint32_t i1) { return 0x00003600 + __offset_DMA(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_DMA_CSC_PRE_LV_VAL(enum mdp4_dma i0, uint32_t i1) { return 0x00003600 + __offset_DMA(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_DMA_CSC_POST_LV(enum mdp4_dma i0, uint32_t i1) { return 0x00003680 + __offset_DMA(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_DMA_CSC_POST_LV_VAL(enum mdp4_dma i0, uint32_t i1) { return 0x00003680 + __offset_DMA(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP4_PIPE(enum mdp4_pipe i0) { return 0x00020000 + 0x10000*i0; } + +static inline uint32_t REG_MDP4_PIPE_SRC_SIZE(enum mdp4_pipe i0) { return 0x00020000 + 0x10000*i0; } +#define MDP4_PIPE_SRC_SIZE_HEIGHT__MASK 0xffff0000 +#define MDP4_PIPE_SRC_SIZE_HEIGHT__SHIFT 16 +static inline uint32_t MDP4_PIPE_SRC_SIZE_HEIGHT(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_SIZE_HEIGHT__SHIFT) & MDP4_PIPE_SRC_SIZE_HEIGHT__MASK; +} +#define MDP4_PIPE_SRC_SIZE_WIDTH__MASK 0x0000ffff +#define MDP4_PIPE_SRC_SIZE_WIDTH__SHIFT 0 +static inline uint32_t MDP4_PIPE_SRC_SIZE_WIDTH(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_SIZE_WIDTH__SHIFT) & MDP4_PIPE_SRC_SIZE_WIDTH__MASK; +} + +static inline uint32_t REG_MDP4_PIPE_SRC_XY(enum mdp4_pipe i0) { return 0x00020004 + 0x10000*i0; } +#define MDP4_PIPE_SRC_XY_Y__MASK 0xffff0000 +#define MDP4_PIPE_SRC_XY_Y__SHIFT 16 +static inline uint32_t MDP4_PIPE_SRC_XY_Y(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_XY_Y__SHIFT) & MDP4_PIPE_SRC_XY_Y__MASK; +} +#define MDP4_PIPE_SRC_XY_X__MASK 0x0000ffff +#define MDP4_PIPE_SRC_XY_X__SHIFT 0 +static inline uint32_t MDP4_PIPE_SRC_XY_X(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_XY_X__SHIFT) & MDP4_PIPE_SRC_XY_X__MASK; +} + +static inline uint32_t REG_MDP4_PIPE_DST_SIZE(enum mdp4_pipe i0) { return 0x00020008 + 0x10000*i0; } +#define MDP4_PIPE_DST_SIZE_HEIGHT__MASK 0xffff0000 +#define MDP4_PIPE_DST_SIZE_HEIGHT__SHIFT 16 +static inline uint32_t MDP4_PIPE_DST_SIZE_HEIGHT(uint32_t val) +{ + return ((val) << MDP4_PIPE_DST_SIZE_HEIGHT__SHIFT) & MDP4_PIPE_DST_SIZE_HEIGHT__MASK; +} +#define MDP4_PIPE_DST_SIZE_WIDTH__MASK 0x0000ffff +#define MDP4_PIPE_DST_SIZE_WIDTH__SHIFT 0 +static inline uint32_t MDP4_PIPE_DST_SIZE_WIDTH(uint32_t val) +{ + return ((val) << MDP4_PIPE_DST_SIZE_WIDTH__SHIFT) & MDP4_PIPE_DST_SIZE_WIDTH__MASK; +} + +static inline uint32_t REG_MDP4_PIPE_DST_XY(enum mdp4_pipe i0) { return 0x0002000c + 0x10000*i0; } +#define MDP4_PIPE_DST_XY_Y__MASK 0xffff0000 +#define MDP4_PIPE_DST_XY_Y__SHIFT 16 +static inline uint32_t MDP4_PIPE_DST_XY_Y(uint32_t val) +{ + return ((val) << MDP4_PIPE_DST_XY_Y__SHIFT) & MDP4_PIPE_DST_XY_Y__MASK; +} +#define MDP4_PIPE_DST_XY_X__MASK 0x0000ffff +#define MDP4_PIPE_DST_XY_X__SHIFT 0 +static inline uint32_t MDP4_PIPE_DST_XY_X(uint32_t val) +{ + return ((val) << MDP4_PIPE_DST_XY_X__SHIFT) & MDP4_PIPE_DST_XY_X__MASK; +} + +static inline uint32_t REG_MDP4_PIPE_SRCP0_BASE(enum mdp4_pipe i0) { return 0x00020010 + 0x10000*i0; } + +static inline uint32_t REG_MDP4_PIPE_SRCP1_BASE(enum mdp4_pipe i0) { return 0x00020014 + 0x10000*i0; } + +static inline uint32_t REG_MDP4_PIPE_SRCP2_BASE(enum mdp4_pipe i0) { return 0x00020018 + 0x10000*i0; } + +static inline uint32_t REG_MDP4_PIPE_SRCP3_BASE(enum mdp4_pipe i0) { return 0x0002001c + 0x10000*i0; } + +static inline uint32_t REG_MDP4_PIPE_SRC_STRIDE_A(enum mdp4_pipe i0) { return 0x00020040 + 0x10000*i0; } +#define MDP4_PIPE_SRC_STRIDE_A_P0__MASK 0x0000ffff +#define MDP4_PIPE_SRC_STRIDE_A_P0__SHIFT 0 +static inline uint32_t MDP4_PIPE_SRC_STRIDE_A_P0(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_STRIDE_A_P0__SHIFT) & MDP4_PIPE_SRC_STRIDE_A_P0__MASK; +} +#define MDP4_PIPE_SRC_STRIDE_A_P1__MASK 0xffff0000 +#define MDP4_PIPE_SRC_STRIDE_A_P1__SHIFT 16 +static inline uint32_t MDP4_PIPE_SRC_STRIDE_A_P1(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_STRIDE_A_P1__SHIFT) & MDP4_PIPE_SRC_STRIDE_A_P1__MASK; +} + +static inline uint32_t REG_MDP4_PIPE_SRC_STRIDE_B(enum mdp4_pipe i0) { return 0x00020044 + 0x10000*i0; } +#define MDP4_PIPE_SRC_STRIDE_B_P2__MASK 0x0000ffff +#define MDP4_PIPE_SRC_STRIDE_B_P2__SHIFT 0 +static inline uint32_t MDP4_PIPE_SRC_STRIDE_B_P2(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_STRIDE_B_P2__SHIFT) & MDP4_PIPE_SRC_STRIDE_B_P2__MASK; +} +#define MDP4_PIPE_SRC_STRIDE_B_P3__MASK 0xffff0000 +#define MDP4_PIPE_SRC_STRIDE_B_P3__SHIFT 16 +static inline uint32_t MDP4_PIPE_SRC_STRIDE_B_P3(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_STRIDE_B_P3__SHIFT) & MDP4_PIPE_SRC_STRIDE_B_P3__MASK; +} + +static inline uint32_t REG_MDP4_PIPE_SSTILE_FRAME_SIZE(enum mdp4_pipe i0) { return 0x00020048 + 0x10000*i0; } +#define MDP4_PIPE_SSTILE_FRAME_SIZE_HEIGHT__MASK 0xffff0000 +#define MDP4_PIPE_SSTILE_FRAME_SIZE_HEIGHT__SHIFT 16 +static inline uint32_t MDP4_PIPE_SSTILE_FRAME_SIZE_HEIGHT(uint32_t val) +{ + return ((val) << MDP4_PIPE_SSTILE_FRAME_SIZE_HEIGHT__SHIFT) & MDP4_PIPE_SSTILE_FRAME_SIZE_HEIGHT__MASK; +} +#define MDP4_PIPE_SSTILE_FRAME_SIZE_WIDTH__MASK 0x0000ffff +#define MDP4_PIPE_SSTILE_FRAME_SIZE_WIDTH__SHIFT 0 +static inline uint32_t MDP4_PIPE_SSTILE_FRAME_SIZE_WIDTH(uint32_t val) +{ + return ((val) << MDP4_PIPE_SSTILE_FRAME_SIZE_WIDTH__SHIFT) & MDP4_PIPE_SSTILE_FRAME_SIZE_WIDTH__MASK; +} + +static inline uint32_t REG_MDP4_PIPE_SRC_FORMAT(enum mdp4_pipe i0) { return 0x00020050 + 0x10000*i0; } +#define MDP4_PIPE_SRC_FORMAT_G_BPC__MASK 0x00000003 +#define MDP4_PIPE_SRC_FORMAT_G_BPC__SHIFT 0 +static inline uint32_t MDP4_PIPE_SRC_FORMAT_G_BPC(enum mdp_bpc val) +{ + return ((val) << MDP4_PIPE_SRC_FORMAT_G_BPC__SHIFT) & MDP4_PIPE_SRC_FORMAT_G_BPC__MASK; +} +#define MDP4_PIPE_SRC_FORMAT_B_BPC__MASK 0x0000000c +#define MDP4_PIPE_SRC_FORMAT_B_BPC__SHIFT 2 +static inline uint32_t MDP4_PIPE_SRC_FORMAT_B_BPC(enum mdp_bpc val) +{ + return ((val) << MDP4_PIPE_SRC_FORMAT_B_BPC__SHIFT) & MDP4_PIPE_SRC_FORMAT_B_BPC__MASK; +} +#define MDP4_PIPE_SRC_FORMAT_R_BPC__MASK 0x00000030 +#define MDP4_PIPE_SRC_FORMAT_R_BPC__SHIFT 4 +static inline uint32_t MDP4_PIPE_SRC_FORMAT_R_BPC(enum mdp_bpc val) +{ + return ((val) << MDP4_PIPE_SRC_FORMAT_R_BPC__SHIFT) & MDP4_PIPE_SRC_FORMAT_R_BPC__MASK; +} +#define MDP4_PIPE_SRC_FORMAT_A_BPC__MASK 0x000000c0 +#define MDP4_PIPE_SRC_FORMAT_A_BPC__SHIFT 6 +static inline uint32_t MDP4_PIPE_SRC_FORMAT_A_BPC(enum mdp_bpc_alpha val) +{ + return ((val) << MDP4_PIPE_SRC_FORMAT_A_BPC__SHIFT) & MDP4_PIPE_SRC_FORMAT_A_BPC__MASK; +} +#define MDP4_PIPE_SRC_FORMAT_ALPHA_ENABLE 0x00000100 +#define MDP4_PIPE_SRC_FORMAT_CPP__MASK 0x00000600 +#define MDP4_PIPE_SRC_FORMAT_CPP__SHIFT 9 +static inline uint32_t MDP4_PIPE_SRC_FORMAT_CPP(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_FORMAT_CPP__SHIFT) & MDP4_PIPE_SRC_FORMAT_CPP__MASK; +} +#define MDP4_PIPE_SRC_FORMAT_ROTATED_90 0x00001000 +#define MDP4_PIPE_SRC_FORMAT_UNPACK_COUNT__MASK 0x00006000 +#define MDP4_PIPE_SRC_FORMAT_UNPACK_COUNT__SHIFT 13 +static inline uint32_t MDP4_PIPE_SRC_FORMAT_UNPACK_COUNT(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_FORMAT_UNPACK_COUNT__SHIFT) & MDP4_PIPE_SRC_FORMAT_UNPACK_COUNT__MASK; +} +#define MDP4_PIPE_SRC_FORMAT_UNPACK_TIGHT 0x00020000 +#define MDP4_PIPE_SRC_FORMAT_UNPACK_ALIGN_MSB 0x00040000 +#define MDP4_PIPE_SRC_FORMAT_FETCH_PLANES__MASK 0x00180000 +#define MDP4_PIPE_SRC_FORMAT_FETCH_PLANES__SHIFT 19 +static inline uint32_t MDP4_PIPE_SRC_FORMAT_FETCH_PLANES(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_FORMAT_FETCH_PLANES__SHIFT) & MDP4_PIPE_SRC_FORMAT_FETCH_PLANES__MASK; +} +#define MDP4_PIPE_SRC_FORMAT_SOLID_FILL 0x00400000 +#define MDP4_PIPE_SRC_FORMAT_CHROMA_SAMP__MASK 0x0c000000 +#define MDP4_PIPE_SRC_FORMAT_CHROMA_SAMP__SHIFT 26 +static inline uint32_t MDP4_PIPE_SRC_FORMAT_CHROMA_SAMP(enum mdp_chroma_samp_type val) +{ + return ((val) << MDP4_PIPE_SRC_FORMAT_CHROMA_SAMP__SHIFT) & MDP4_PIPE_SRC_FORMAT_CHROMA_SAMP__MASK; +} +#define MDP4_PIPE_SRC_FORMAT_FRAME_FORMAT__MASK 0x60000000 +#define MDP4_PIPE_SRC_FORMAT_FRAME_FORMAT__SHIFT 29 +static inline uint32_t MDP4_PIPE_SRC_FORMAT_FRAME_FORMAT(enum mdp4_frame_format val) +{ + return ((val) << MDP4_PIPE_SRC_FORMAT_FRAME_FORMAT__SHIFT) & MDP4_PIPE_SRC_FORMAT_FRAME_FORMAT__MASK; +} + +static inline uint32_t REG_MDP4_PIPE_SRC_UNPACK(enum mdp4_pipe i0) { return 0x00020054 + 0x10000*i0; } +#define MDP4_PIPE_SRC_UNPACK_ELEM0__MASK 0x000000ff +#define MDP4_PIPE_SRC_UNPACK_ELEM0__SHIFT 0 +static inline uint32_t MDP4_PIPE_SRC_UNPACK_ELEM0(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_UNPACK_ELEM0__SHIFT) & MDP4_PIPE_SRC_UNPACK_ELEM0__MASK; +} +#define MDP4_PIPE_SRC_UNPACK_ELEM1__MASK 0x0000ff00 +#define MDP4_PIPE_SRC_UNPACK_ELEM1__SHIFT 8 +static inline uint32_t MDP4_PIPE_SRC_UNPACK_ELEM1(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_UNPACK_ELEM1__SHIFT) & MDP4_PIPE_SRC_UNPACK_ELEM1__MASK; +} +#define MDP4_PIPE_SRC_UNPACK_ELEM2__MASK 0x00ff0000 +#define MDP4_PIPE_SRC_UNPACK_ELEM2__SHIFT 16 +static inline uint32_t MDP4_PIPE_SRC_UNPACK_ELEM2(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_UNPACK_ELEM2__SHIFT) & MDP4_PIPE_SRC_UNPACK_ELEM2__MASK; +} +#define MDP4_PIPE_SRC_UNPACK_ELEM3__MASK 0xff000000 +#define MDP4_PIPE_SRC_UNPACK_ELEM3__SHIFT 24 +static inline uint32_t MDP4_PIPE_SRC_UNPACK_ELEM3(uint32_t val) +{ + return ((val) << MDP4_PIPE_SRC_UNPACK_ELEM3__SHIFT) & MDP4_PIPE_SRC_UNPACK_ELEM3__MASK; +} + +static inline uint32_t REG_MDP4_PIPE_OP_MODE(enum mdp4_pipe i0) { return 0x00020058 + 0x10000*i0; } +#define MDP4_PIPE_OP_MODE_SCALEX_EN 0x00000001 +#define MDP4_PIPE_OP_MODE_SCALEY_EN 0x00000002 +#define MDP4_PIPE_OP_MODE_SCALEX_UNIT_SEL__MASK 0x0000000c +#define MDP4_PIPE_OP_MODE_SCALEX_UNIT_SEL__SHIFT 2 +static inline uint32_t MDP4_PIPE_OP_MODE_SCALEX_UNIT_SEL(enum mdp4_scale_unit val) +{ + return ((val) << MDP4_PIPE_OP_MODE_SCALEX_UNIT_SEL__SHIFT) & MDP4_PIPE_OP_MODE_SCALEX_UNIT_SEL__MASK; +} +#define MDP4_PIPE_OP_MODE_SCALEY_UNIT_SEL__MASK 0x00000030 +#define MDP4_PIPE_OP_MODE_SCALEY_UNIT_SEL__SHIFT 4 +static inline uint32_t MDP4_PIPE_OP_MODE_SCALEY_UNIT_SEL(enum mdp4_scale_unit val) +{ + return ((val) << MDP4_PIPE_OP_MODE_SCALEY_UNIT_SEL__SHIFT) & MDP4_PIPE_OP_MODE_SCALEY_UNIT_SEL__MASK; +} +#define MDP4_PIPE_OP_MODE_SRC_YCBCR 0x00000200 +#define MDP4_PIPE_OP_MODE_DST_YCBCR 0x00000400 +#define MDP4_PIPE_OP_MODE_CSC_EN 0x00000800 +#define MDP4_PIPE_OP_MODE_FLIP_LR 0x00002000 +#define MDP4_PIPE_OP_MODE_FLIP_UD 0x00004000 +#define MDP4_PIPE_OP_MODE_DITHER_EN 0x00008000 +#define MDP4_PIPE_OP_MODE_IGC_LUT_EN 0x00010000 +#define MDP4_PIPE_OP_MODE_DEINT_EN 0x00040000 +#define MDP4_PIPE_OP_MODE_DEINT_ODD_REF 0x00080000 + +static inline uint32_t REG_MDP4_PIPE_PHASEX_STEP(enum mdp4_pipe i0) { return 0x0002005c + 0x10000*i0; } + +static inline uint32_t REG_MDP4_PIPE_PHASEY_STEP(enum mdp4_pipe i0) { return 0x00020060 + 0x10000*i0; } + +static inline uint32_t REG_MDP4_PIPE_FETCH_CONFIG(enum mdp4_pipe i0) { return 0x00021004 + 0x10000*i0; } + +static inline uint32_t REG_MDP4_PIPE_SOLID_COLOR(enum mdp4_pipe i0) { return 0x00021008 + 0x10000*i0; } + +static inline uint32_t REG_MDP4_PIPE_CSC(enum mdp4_pipe i0) { return 0x00024000 + 0x10000*i0; } + + +static inline uint32_t REG_MDP4_PIPE_CSC_MV(enum mdp4_pipe i0, uint32_t i1) { return 0x00024400 + 0x10000*i0 + 0x4*i1; } + +static inline uint32_t REG_MDP4_PIPE_CSC_MV_VAL(enum mdp4_pipe i0, uint32_t i1) { return 0x00024400 + 0x10000*i0 + 0x4*i1; } + +static inline uint32_t REG_MDP4_PIPE_CSC_PRE_BV(enum mdp4_pipe i0, uint32_t i1) { return 0x00024500 + 0x10000*i0 + 0x4*i1; } + +static inline uint32_t REG_MDP4_PIPE_CSC_PRE_BV_VAL(enum mdp4_pipe i0, uint32_t i1) { return 0x00024500 + 0x10000*i0 + 0x4*i1; } + +static inline uint32_t REG_MDP4_PIPE_CSC_POST_BV(enum mdp4_pipe i0, uint32_t i1) { return 0x00024580 + 0x10000*i0 + 0x4*i1; } + +static inline uint32_t REG_MDP4_PIPE_CSC_POST_BV_VAL(enum mdp4_pipe i0, uint32_t i1) { return 0x00024580 + 0x10000*i0 + 0x4*i1; } + +static inline uint32_t REG_MDP4_PIPE_CSC_PRE_LV(enum mdp4_pipe i0, uint32_t i1) { return 0x00024600 + 0x10000*i0 + 0x4*i1; } + +static inline uint32_t REG_MDP4_PIPE_CSC_PRE_LV_VAL(enum mdp4_pipe i0, uint32_t i1) { return 0x00024600 + 0x10000*i0 + 0x4*i1; } + +static inline uint32_t REG_MDP4_PIPE_CSC_POST_LV(enum mdp4_pipe i0, uint32_t i1) { return 0x00024680 + 0x10000*i0 + 0x4*i1; } + +static inline uint32_t REG_MDP4_PIPE_CSC_POST_LV_VAL(enum mdp4_pipe i0, uint32_t i1) { return 0x00024680 + 0x10000*i0 + 0x4*i1; } + +#define REG_MDP4_LCDC 0x000c0000 + +#define REG_MDP4_LCDC_ENABLE 0x000c0000 + +#define REG_MDP4_LCDC_HSYNC_CTRL 0x000c0004 +#define MDP4_LCDC_HSYNC_CTRL_PULSEW__MASK 0x0000ffff +#define MDP4_LCDC_HSYNC_CTRL_PULSEW__SHIFT 0 +static inline uint32_t MDP4_LCDC_HSYNC_CTRL_PULSEW(uint32_t val) +{ + return ((val) << MDP4_LCDC_HSYNC_CTRL_PULSEW__SHIFT) & MDP4_LCDC_HSYNC_CTRL_PULSEW__MASK; +} +#define MDP4_LCDC_HSYNC_CTRL_PERIOD__MASK 0xffff0000 +#define MDP4_LCDC_HSYNC_CTRL_PERIOD__SHIFT 16 +static inline uint32_t MDP4_LCDC_HSYNC_CTRL_PERIOD(uint32_t val) +{ + return ((val) << MDP4_LCDC_HSYNC_CTRL_PERIOD__SHIFT) & MDP4_LCDC_HSYNC_CTRL_PERIOD__MASK; +} + +#define REG_MDP4_LCDC_VSYNC_PERIOD 0x000c0008 + +#define REG_MDP4_LCDC_VSYNC_LEN 0x000c000c + +#define REG_MDP4_LCDC_DISPLAY_HCTRL 0x000c0010 +#define MDP4_LCDC_DISPLAY_HCTRL_START__MASK 0x0000ffff +#define MDP4_LCDC_DISPLAY_HCTRL_START__SHIFT 0 +static inline uint32_t MDP4_LCDC_DISPLAY_HCTRL_START(uint32_t val) +{ + return ((val) << MDP4_LCDC_DISPLAY_HCTRL_START__SHIFT) & MDP4_LCDC_DISPLAY_HCTRL_START__MASK; +} +#define MDP4_LCDC_DISPLAY_HCTRL_END__MASK 0xffff0000 +#define MDP4_LCDC_DISPLAY_HCTRL_END__SHIFT 16 +static inline uint32_t MDP4_LCDC_DISPLAY_HCTRL_END(uint32_t val) +{ + return ((val) << MDP4_LCDC_DISPLAY_HCTRL_END__SHIFT) & MDP4_LCDC_DISPLAY_HCTRL_END__MASK; +} + +#define REG_MDP4_LCDC_DISPLAY_VSTART 0x000c0014 + +#define REG_MDP4_LCDC_DISPLAY_VEND 0x000c0018 + +#define REG_MDP4_LCDC_ACTIVE_HCTL 0x000c001c +#define MDP4_LCDC_ACTIVE_HCTL_START__MASK 0x00007fff +#define MDP4_LCDC_ACTIVE_HCTL_START__SHIFT 0 +static inline uint32_t MDP4_LCDC_ACTIVE_HCTL_START(uint32_t val) +{ + return ((val) << MDP4_LCDC_ACTIVE_HCTL_START__SHIFT) & MDP4_LCDC_ACTIVE_HCTL_START__MASK; +} +#define MDP4_LCDC_ACTIVE_HCTL_END__MASK 0x7fff0000 +#define MDP4_LCDC_ACTIVE_HCTL_END__SHIFT 16 +static inline uint32_t MDP4_LCDC_ACTIVE_HCTL_END(uint32_t val) +{ + return ((val) << MDP4_LCDC_ACTIVE_HCTL_END__SHIFT) & MDP4_LCDC_ACTIVE_HCTL_END__MASK; +} +#define MDP4_LCDC_ACTIVE_HCTL_ACTIVE_START_X 0x80000000 + +#define REG_MDP4_LCDC_ACTIVE_VSTART 0x000c0020 + +#define REG_MDP4_LCDC_ACTIVE_VEND 0x000c0024 + +#define REG_MDP4_LCDC_BORDER_CLR 0x000c0028 + +#define REG_MDP4_LCDC_UNDERFLOW_CLR 0x000c002c +#define MDP4_LCDC_UNDERFLOW_CLR_COLOR__MASK 0x00ffffff +#define MDP4_LCDC_UNDERFLOW_CLR_COLOR__SHIFT 0 +static inline uint32_t MDP4_LCDC_UNDERFLOW_CLR_COLOR(uint32_t val) +{ + return ((val) << MDP4_LCDC_UNDERFLOW_CLR_COLOR__SHIFT) & MDP4_LCDC_UNDERFLOW_CLR_COLOR__MASK; +} +#define MDP4_LCDC_UNDERFLOW_CLR_ENABLE_RECOVERY 0x80000000 + +#define REG_MDP4_LCDC_HSYNC_SKEW 0x000c0030 + +#define REG_MDP4_LCDC_TEST_CNTL 0x000c0034 + +#define REG_MDP4_LCDC_CTRL_POLARITY 0x000c0038 +#define MDP4_LCDC_CTRL_POLARITY_HSYNC_LOW 0x00000001 +#define MDP4_LCDC_CTRL_POLARITY_VSYNC_LOW 0x00000002 +#define MDP4_LCDC_CTRL_POLARITY_DATA_EN_LOW 0x00000004 + +#define REG_MDP4_LCDC_LVDS_INTF_CTL 0x000c2000 +#define MDP4_LCDC_LVDS_INTF_CTL_MODE_SEL 0x00000004 +#define MDP4_LCDC_LVDS_INTF_CTL_RGB_OUT 0x00000008 +#define MDP4_LCDC_LVDS_INTF_CTL_CH_SWAP 0x00000010 +#define MDP4_LCDC_LVDS_INTF_CTL_CH1_RES_BIT 0x00000020 +#define MDP4_LCDC_LVDS_INTF_CTL_CH2_RES_BIT 0x00000040 +#define MDP4_LCDC_LVDS_INTF_CTL_ENABLE 0x00000080 +#define MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN 0x00000100 +#define MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN 0x00000200 +#define MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN 0x00000400 +#define MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE3_EN 0x00000800 +#define MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE0_EN 0x00001000 +#define MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE1_EN 0x00002000 +#define MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE2_EN 0x00004000 +#define MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE3_EN 0x00008000 +#define MDP4_LCDC_LVDS_INTF_CTL_CH1_CLK_LANE_EN 0x00010000 +#define MDP4_LCDC_LVDS_INTF_CTL_CH2_CLK_LANE_EN 0x00020000 + +static inline uint32_t REG_MDP4_LCDC_LVDS_MUX_CTL(uint32_t i0) { return 0x000c2014 + 0x8*i0; } + +static inline uint32_t REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(uint32_t i0) { return 0x000c2014 + 0x8*i0; } +#define MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0__MASK 0x000000ff +#define MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0__SHIFT 0 +static inline uint32_t MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(uint32_t val) +{ + return ((val) << MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0__SHIFT) & MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0__MASK; +} +#define MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1__MASK 0x0000ff00 +#define MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1__SHIFT 8 +static inline uint32_t MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(uint32_t val) +{ + return ((val) << MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1__SHIFT) & MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1__MASK; +} +#define MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2__MASK 0x00ff0000 +#define MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2__SHIFT 16 +static inline uint32_t MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(uint32_t val) +{ + return ((val) << MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2__SHIFT) & MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2__MASK; +} +#define MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3__MASK 0xff000000 +#define MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3__SHIFT 24 +static inline uint32_t MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(uint32_t val) +{ + return ((val) << MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3__SHIFT) & MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3__MASK; +} + +static inline uint32_t REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(uint32_t i0) { return 0x000c2018 + 0x8*i0; } +#define MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4__MASK 0x000000ff +#define MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4__SHIFT 0 +static inline uint32_t MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(uint32_t val) +{ + return ((val) << MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4__SHIFT) & MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4__MASK; +} +#define MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5__MASK 0x0000ff00 +#define MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5__SHIFT 8 +static inline uint32_t MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(uint32_t val) +{ + return ((val) << MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5__SHIFT) & MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5__MASK; +} +#define MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6__MASK 0x00ff0000 +#define MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6__SHIFT 16 +static inline uint32_t MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(uint32_t val) +{ + return ((val) << MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6__SHIFT) & MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6__MASK; +} + +#define REG_MDP4_LCDC_LVDS_PHY_RESET 0x000c2034 + +#define REG_MDP4_LVDS_PHY_PLL_CTRL_0 0x000c3000 + +#define REG_MDP4_LVDS_PHY_PLL_CTRL_1 0x000c3004 + +#define REG_MDP4_LVDS_PHY_PLL_CTRL_2 0x000c3008 + +#define REG_MDP4_LVDS_PHY_PLL_CTRL_3 0x000c300c + +#define REG_MDP4_LVDS_PHY_PLL_CTRL_5 0x000c3014 + +#define REG_MDP4_LVDS_PHY_PLL_CTRL_6 0x000c3018 + +#define REG_MDP4_LVDS_PHY_PLL_CTRL_7 0x000c301c + +#define REG_MDP4_LVDS_PHY_PLL_CTRL_8 0x000c3020 + +#define REG_MDP4_LVDS_PHY_PLL_CTRL_9 0x000c3024 + +#define REG_MDP4_LVDS_PHY_PLL_LOCKED 0x000c3080 + +#define REG_MDP4_LVDS_PHY_CFG2 0x000c3108 + +#define REG_MDP4_LVDS_PHY_CFG0 0x000c3100 +#define MDP4_LVDS_PHY_CFG0_SERIALIZATION_ENBLE 0x00000010 +#define MDP4_LVDS_PHY_CFG0_CHANNEL0 0x00000040 +#define MDP4_LVDS_PHY_CFG0_CHANNEL1 0x00000080 + +#define REG_MDP4_DTV 0x000d0000 + +#define REG_MDP4_DTV_ENABLE 0x000d0000 + +#define REG_MDP4_DTV_HSYNC_CTRL 0x000d0004 +#define MDP4_DTV_HSYNC_CTRL_PULSEW__MASK 0x0000ffff +#define MDP4_DTV_HSYNC_CTRL_PULSEW__SHIFT 0 +static inline uint32_t MDP4_DTV_HSYNC_CTRL_PULSEW(uint32_t val) +{ + return ((val) << MDP4_DTV_HSYNC_CTRL_PULSEW__SHIFT) & MDP4_DTV_HSYNC_CTRL_PULSEW__MASK; +} +#define MDP4_DTV_HSYNC_CTRL_PERIOD__MASK 0xffff0000 +#define MDP4_DTV_HSYNC_CTRL_PERIOD__SHIFT 16 +static inline uint32_t MDP4_DTV_HSYNC_CTRL_PERIOD(uint32_t val) +{ + return ((val) << MDP4_DTV_HSYNC_CTRL_PERIOD__SHIFT) & MDP4_DTV_HSYNC_CTRL_PERIOD__MASK; +} + +#define REG_MDP4_DTV_VSYNC_PERIOD 0x000d0008 + +#define REG_MDP4_DTV_VSYNC_LEN 0x000d000c + +#define REG_MDP4_DTV_DISPLAY_HCTRL 0x000d0018 +#define MDP4_DTV_DISPLAY_HCTRL_START__MASK 0x0000ffff +#define MDP4_DTV_DISPLAY_HCTRL_START__SHIFT 0 +static inline uint32_t MDP4_DTV_DISPLAY_HCTRL_START(uint32_t val) +{ + return ((val) << MDP4_DTV_DISPLAY_HCTRL_START__SHIFT) & MDP4_DTV_DISPLAY_HCTRL_START__MASK; +} +#define MDP4_DTV_DISPLAY_HCTRL_END__MASK 0xffff0000 +#define MDP4_DTV_DISPLAY_HCTRL_END__SHIFT 16 +static inline uint32_t MDP4_DTV_DISPLAY_HCTRL_END(uint32_t val) +{ + return ((val) << MDP4_DTV_DISPLAY_HCTRL_END__SHIFT) & MDP4_DTV_DISPLAY_HCTRL_END__MASK; +} + +#define REG_MDP4_DTV_DISPLAY_VSTART 0x000d001c + +#define REG_MDP4_DTV_DISPLAY_VEND 0x000d0020 + +#define REG_MDP4_DTV_ACTIVE_HCTL 0x000d002c +#define MDP4_DTV_ACTIVE_HCTL_START__MASK 0x00007fff +#define MDP4_DTV_ACTIVE_HCTL_START__SHIFT 0 +static inline uint32_t MDP4_DTV_ACTIVE_HCTL_START(uint32_t val) +{ + return ((val) << MDP4_DTV_ACTIVE_HCTL_START__SHIFT) & MDP4_DTV_ACTIVE_HCTL_START__MASK; +} +#define MDP4_DTV_ACTIVE_HCTL_END__MASK 0x7fff0000 +#define MDP4_DTV_ACTIVE_HCTL_END__SHIFT 16 +static inline uint32_t MDP4_DTV_ACTIVE_HCTL_END(uint32_t val) +{ + return ((val) << MDP4_DTV_ACTIVE_HCTL_END__SHIFT) & MDP4_DTV_ACTIVE_HCTL_END__MASK; +} +#define MDP4_DTV_ACTIVE_HCTL_ACTIVE_START_X 0x80000000 + +#define REG_MDP4_DTV_ACTIVE_VSTART 0x000d0030 + +#define REG_MDP4_DTV_ACTIVE_VEND 0x000d0038 + +#define REG_MDP4_DTV_BORDER_CLR 0x000d0040 + +#define REG_MDP4_DTV_UNDERFLOW_CLR 0x000d0044 +#define MDP4_DTV_UNDERFLOW_CLR_COLOR__MASK 0x00ffffff +#define MDP4_DTV_UNDERFLOW_CLR_COLOR__SHIFT 0 +static inline uint32_t MDP4_DTV_UNDERFLOW_CLR_COLOR(uint32_t val) +{ + return ((val) << MDP4_DTV_UNDERFLOW_CLR_COLOR__SHIFT) & MDP4_DTV_UNDERFLOW_CLR_COLOR__MASK; +} +#define MDP4_DTV_UNDERFLOW_CLR_ENABLE_RECOVERY 0x80000000 + +#define REG_MDP4_DTV_HSYNC_SKEW 0x000d0048 + +#define REG_MDP4_DTV_TEST_CNTL 0x000d004c + +#define REG_MDP4_DTV_CTRL_POLARITY 0x000d0050 +#define MDP4_DTV_CTRL_POLARITY_HSYNC_LOW 0x00000001 +#define MDP4_DTV_CTRL_POLARITY_VSYNC_LOW 0x00000002 +#define MDP4_DTV_CTRL_POLARITY_DATA_EN_LOW 0x00000004 + +#define REG_MDP4_DSI 0x000e0000 + +#define REG_MDP4_DSI_ENABLE 0x000e0000 + +#define REG_MDP4_DSI_HSYNC_CTRL 0x000e0004 +#define MDP4_DSI_HSYNC_CTRL_PULSEW__MASK 0x0000ffff +#define MDP4_DSI_HSYNC_CTRL_PULSEW__SHIFT 0 +static inline uint32_t MDP4_DSI_HSYNC_CTRL_PULSEW(uint32_t val) +{ + return ((val) << MDP4_DSI_HSYNC_CTRL_PULSEW__SHIFT) & MDP4_DSI_HSYNC_CTRL_PULSEW__MASK; +} +#define MDP4_DSI_HSYNC_CTRL_PERIOD__MASK 0xffff0000 +#define MDP4_DSI_HSYNC_CTRL_PERIOD__SHIFT 16 +static inline uint32_t MDP4_DSI_HSYNC_CTRL_PERIOD(uint32_t val) +{ + return ((val) << MDP4_DSI_HSYNC_CTRL_PERIOD__SHIFT) & MDP4_DSI_HSYNC_CTRL_PERIOD__MASK; +} + +#define REG_MDP4_DSI_VSYNC_PERIOD 0x000e0008 + +#define REG_MDP4_DSI_VSYNC_LEN 0x000e000c + +#define REG_MDP4_DSI_DISPLAY_HCTRL 0x000e0010 +#define MDP4_DSI_DISPLAY_HCTRL_START__MASK 0x0000ffff +#define MDP4_DSI_DISPLAY_HCTRL_START__SHIFT 0 +static inline uint32_t MDP4_DSI_DISPLAY_HCTRL_START(uint32_t val) +{ + return ((val) << MDP4_DSI_DISPLAY_HCTRL_START__SHIFT) & MDP4_DSI_DISPLAY_HCTRL_START__MASK; +} +#define MDP4_DSI_DISPLAY_HCTRL_END__MASK 0xffff0000 +#define MDP4_DSI_DISPLAY_HCTRL_END__SHIFT 16 +static inline uint32_t MDP4_DSI_DISPLAY_HCTRL_END(uint32_t val) +{ + return ((val) << MDP4_DSI_DISPLAY_HCTRL_END__SHIFT) & MDP4_DSI_DISPLAY_HCTRL_END__MASK; +} + +#define REG_MDP4_DSI_DISPLAY_VSTART 0x000e0014 + +#define REG_MDP4_DSI_DISPLAY_VEND 0x000e0018 + +#define REG_MDP4_DSI_ACTIVE_HCTL 0x000e001c +#define MDP4_DSI_ACTIVE_HCTL_START__MASK 0x00007fff +#define MDP4_DSI_ACTIVE_HCTL_START__SHIFT 0 +static inline uint32_t MDP4_DSI_ACTIVE_HCTL_START(uint32_t val) +{ + return ((val) << MDP4_DSI_ACTIVE_HCTL_START__SHIFT) & MDP4_DSI_ACTIVE_HCTL_START__MASK; +} +#define MDP4_DSI_ACTIVE_HCTL_END__MASK 0x7fff0000 +#define MDP4_DSI_ACTIVE_HCTL_END__SHIFT 16 +static inline uint32_t MDP4_DSI_ACTIVE_HCTL_END(uint32_t val) +{ + return ((val) << MDP4_DSI_ACTIVE_HCTL_END__SHIFT) & MDP4_DSI_ACTIVE_HCTL_END__MASK; +} +#define MDP4_DSI_ACTIVE_HCTL_ACTIVE_START_X 0x80000000 + +#define REG_MDP4_DSI_ACTIVE_VSTART 0x000e0020 + +#define REG_MDP4_DSI_ACTIVE_VEND 0x000e0024 + +#define REG_MDP4_DSI_BORDER_CLR 0x000e0028 + +#define REG_MDP4_DSI_UNDERFLOW_CLR 0x000e002c +#define MDP4_DSI_UNDERFLOW_CLR_COLOR__MASK 0x00ffffff +#define MDP4_DSI_UNDERFLOW_CLR_COLOR__SHIFT 0 +static inline uint32_t MDP4_DSI_UNDERFLOW_CLR_COLOR(uint32_t val) +{ + return ((val) << MDP4_DSI_UNDERFLOW_CLR_COLOR__SHIFT) & MDP4_DSI_UNDERFLOW_CLR_COLOR__MASK; +} +#define MDP4_DSI_UNDERFLOW_CLR_ENABLE_RECOVERY 0x80000000 + +#define REG_MDP4_DSI_HSYNC_SKEW 0x000e0030 + +#define REG_MDP4_DSI_TEST_CNTL 0x000e0034 + +#define REG_MDP4_DSI_CTRL_POLARITY 0x000e0038 +#define MDP4_DSI_CTRL_POLARITY_HSYNC_LOW 0x00000001 +#define MDP4_DSI_CTRL_POLARITY_VSYNC_LOW 0x00000002 +#define MDP4_DSI_CTRL_POLARITY_DATA_EN_LOW 0x00000004 + + +#endif /* MDP4_XML */ diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c new file mode 100644 index 000000000..bf54b9e4f --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_crtc.c @@ -0,0 +1,665 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <drm/drm_crtc.h> +#include <drm/drm_flip_work.h> +#include <drm/drm_mode.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "mdp4_kms.h" + +struct mdp4_crtc { + struct drm_crtc base; + char name[8]; + int id; + int ovlp; + enum mdp4_dma dma; + bool enabled; + + /* which mixer/encoder we route output to: */ + int mixer; + + struct { + spinlock_t lock; + bool stale; + uint32_t width, height; + uint32_t x, y; + + /* next cursor to scan-out: */ + uint32_t next_iova; + struct drm_gem_object *next_bo; + + /* current cursor being scanned out: */ + struct drm_gem_object *scanout_bo; + } cursor; + + + /* if there is a pending flip, these will be non-null: */ + struct drm_pending_vblank_event *event; + + /* Bits have been flushed at the last commit, + * used to decide if a vsync has happened since last commit. + */ + u32 flushed_mask; + +#define PENDING_CURSOR 0x1 +#define PENDING_FLIP 0x2 + atomic_t pending; + + /* for unref'ing cursor bo's after scanout completes: */ + struct drm_flip_work unref_cursor_work; + + struct mdp_irq vblank; + struct mdp_irq err; +}; +#define to_mdp4_crtc(x) container_of(x, struct mdp4_crtc, base) + +static struct mdp4_kms *get_kms(struct drm_crtc *crtc) +{ + struct msm_drm_private *priv = crtc->dev->dev_private; + return to_mdp4_kms(to_mdp_kms(priv->kms)); +} + +static void request_pending(struct drm_crtc *crtc, uint32_t pending) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + + atomic_or(pending, &mdp4_crtc->pending); + mdp_irq_register(&get_kms(crtc)->base, &mdp4_crtc->vblank); +} + +static void crtc_flush(struct drm_crtc *crtc) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct mdp4_kms *mdp4_kms = get_kms(crtc); + struct drm_plane *plane; + uint32_t flush = 0; + + drm_atomic_crtc_for_each_plane(plane, crtc) { + enum mdp4_pipe pipe_id = mdp4_plane_pipe(plane); + flush |= pipe2flush(pipe_id); + } + + flush |= ovlp2flush(mdp4_crtc->ovlp); + + DBG("%s: flush=%08x", mdp4_crtc->name, flush); + + mdp4_crtc->flushed_mask = flush; + + mdp4_write(mdp4_kms, REG_MDP4_OVERLAY_FLUSH, flush); +} + +/* if file!=NULL, this is preclose potential cancel-flip path */ +static void complete_flip(struct drm_crtc *crtc, struct drm_file *file) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct drm_device *dev = crtc->dev; + struct drm_pending_vblank_event *event; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + event = mdp4_crtc->event; + if (event) { + mdp4_crtc->event = NULL; + DBG("%s: send event: %p", mdp4_crtc->name, event); + drm_crtc_send_vblank_event(crtc, event); + } + spin_unlock_irqrestore(&dev->event_lock, flags); +} + +static void unref_cursor_worker(struct drm_flip_work *work, void *val) +{ + struct mdp4_crtc *mdp4_crtc = + container_of(work, struct mdp4_crtc, unref_cursor_work); + struct mdp4_kms *mdp4_kms = get_kms(&mdp4_crtc->base); + struct msm_kms *kms = &mdp4_kms->base.base; + + msm_gem_unpin_iova(val, kms->aspace); + drm_gem_object_put(val); +} + +static void mdp4_crtc_destroy(struct drm_crtc *crtc) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + + drm_crtc_cleanup(crtc); + drm_flip_work_cleanup(&mdp4_crtc->unref_cursor_work); + + kfree(mdp4_crtc); +} + +/* statically (for now) map planes to mixer stage (z-order): */ +static const int idxs[] = { + [VG1] = 1, + [VG2] = 2, + [RGB1] = 0, + [RGB2] = 0, + [RGB3] = 0, + [VG3] = 3, + [VG4] = 4, + +}; + +/* setup mixer config, for which we need to consider all crtc's and + * the planes attached to them + * + * TODO may possibly need some extra locking here + */ +static void setup_mixer(struct mdp4_kms *mdp4_kms) +{ + struct drm_mode_config *config = &mdp4_kms->dev->mode_config; + struct drm_crtc *crtc; + uint32_t mixer_cfg = 0; + static const enum mdp_mixer_stage_id stages[] = { + STAGE_BASE, STAGE0, STAGE1, STAGE2, STAGE3, + }; + + list_for_each_entry(crtc, &config->crtc_list, head) { + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct drm_plane *plane; + + drm_atomic_crtc_for_each_plane(plane, crtc) { + enum mdp4_pipe pipe_id = mdp4_plane_pipe(plane); + int idx = idxs[pipe_id]; + mixer_cfg = mixercfg(mixer_cfg, mdp4_crtc->mixer, + pipe_id, stages[idx]); + } + } + + mdp4_write(mdp4_kms, REG_MDP4_LAYERMIXER_IN_CFG, mixer_cfg); +} + +static void blend_setup(struct drm_crtc *crtc) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct mdp4_kms *mdp4_kms = get_kms(crtc); + struct drm_plane *plane; + int i, ovlp = mdp4_crtc->ovlp; + bool alpha[4]= { false, false, false, false }; + + mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_LOW0(ovlp), 0); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_LOW1(ovlp), 0); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_HIGH0(ovlp), 0); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_TRANSP_HIGH1(ovlp), 0); + + drm_atomic_crtc_for_each_plane(plane, crtc) { + enum mdp4_pipe pipe_id = mdp4_plane_pipe(plane); + int idx = idxs[pipe_id]; + if (idx > 0) { + const struct mdp_format *format = + to_mdp_format(msm_framebuffer_format(plane->state->fb)); + alpha[idx-1] = format->alpha_enable; + } + } + + for (i = 0; i < 4; i++) { + uint32_t op; + + if (alpha[i]) { + op = MDP4_OVLP_STAGE_OP_FG_ALPHA(FG_PIXEL) | + MDP4_OVLP_STAGE_OP_BG_ALPHA(FG_PIXEL) | + MDP4_OVLP_STAGE_OP_BG_INV_ALPHA; + } else { + op = MDP4_OVLP_STAGE_OP_FG_ALPHA(FG_CONST) | + MDP4_OVLP_STAGE_OP_BG_ALPHA(BG_CONST); + } + + mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_FG_ALPHA(ovlp, i), 0xff); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_BG_ALPHA(ovlp, i), 0x00); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_OP(ovlp, i), op); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_CO3(ovlp, i), 1); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_LOW0(ovlp, i), 0); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_LOW1(ovlp, i), 0); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_HIGH0(ovlp, i), 0); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_STAGE_TRANSP_HIGH1(ovlp, i), 0); + } + + setup_mixer(mdp4_kms); +} + +static void mdp4_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct mdp4_kms *mdp4_kms = get_kms(crtc); + enum mdp4_dma dma = mdp4_crtc->dma; + int ovlp = mdp4_crtc->ovlp; + struct drm_display_mode *mode; + + if (WARN_ON(!crtc->state)) + return; + + mode = &crtc->state->adjusted_mode; + + DBG("%s: set mode: " DRM_MODE_FMT, + mdp4_crtc->name, DRM_MODE_ARG(mode)); + + mdp4_write(mdp4_kms, REG_MDP4_DMA_SRC_SIZE(dma), + MDP4_DMA_SRC_SIZE_WIDTH(mode->hdisplay) | + MDP4_DMA_SRC_SIZE_HEIGHT(mode->vdisplay)); + + /* take data from pipe: */ + mdp4_write(mdp4_kms, REG_MDP4_DMA_SRC_BASE(dma), 0); + mdp4_write(mdp4_kms, REG_MDP4_DMA_SRC_STRIDE(dma), 0); + mdp4_write(mdp4_kms, REG_MDP4_DMA_DST_SIZE(dma), + MDP4_DMA_DST_SIZE_WIDTH(0) | + MDP4_DMA_DST_SIZE_HEIGHT(0)); + + mdp4_write(mdp4_kms, REG_MDP4_OVLP_BASE(ovlp), 0); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_SIZE(ovlp), + MDP4_OVLP_SIZE_WIDTH(mode->hdisplay) | + MDP4_OVLP_SIZE_HEIGHT(mode->vdisplay)); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_STRIDE(ovlp), 0); + + mdp4_write(mdp4_kms, REG_MDP4_OVLP_CFG(ovlp), 1); + + if (dma == DMA_E) { + mdp4_write(mdp4_kms, REG_MDP4_DMA_E_QUANT(0), 0x00ff0000); + mdp4_write(mdp4_kms, REG_MDP4_DMA_E_QUANT(1), 0x00ff0000); + mdp4_write(mdp4_kms, REG_MDP4_DMA_E_QUANT(2), 0x00ff0000); + } +} + +static void mdp4_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct mdp4_kms *mdp4_kms = get_kms(crtc); + unsigned long flags; + + DBG("%s", mdp4_crtc->name); + + if (WARN_ON(!mdp4_crtc->enabled)) + return; + + /* Disable/save vblank irq handling before power is disabled */ + drm_crtc_vblank_off(crtc); + + mdp_irq_unregister(&mdp4_kms->base, &mdp4_crtc->err); + mdp4_disable(mdp4_kms); + + if (crtc->state->event && !crtc->state->active) { + WARN_ON(mdp4_crtc->event); + spin_lock_irqsave(&mdp4_kms->dev->event_lock, flags); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + spin_unlock_irqrestore(&mdp4_kms->dev->event_lock, flags); + } + + mdp4_crtc->enabled = false; +} + +static void mdp4_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct mdp4_kms *mdp4_kms = get_kms(crtc); + + DBG("%s", mdp4_crtc->name); + + if (WARN_ON(mdp4_crtc->enabled)) + return; + + mdp4_enable(mdp4_kms); + + /* Restore vblank irq handling after power is enabled */ + drm_crtc_vblank_on(crtc); + + mdp_irq_register(&mdp4_kms->base, &mdp4_crtc->err); + + crtc_flush(crtc); + + mdp4_crtc->enabled = true; +} + +static int mdp4_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + DBG("%s: check", mdp4_crtc->name); + // TODO anything else to check? + return 0; +} + +static void mdp4_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + DBG("%s: begin", mdp4_crtc->name); +} + +static void mdp4_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct drm_device *dev = crtc->dev; + unsigned long flags; + + DBG("%s: event: %p", mdp4_crtc->name, crtc->state->event); + + WARN_ON(mdp4_crtc->event); + + spin_lock_irqsave(&dev->event_lock, flags); + mdp4_crtc->event = crtc->state->event; + crtc->state->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + + blend_setup(crtc); + crtc_flush(crtc); + request_pending(crtc, PENDING_FLIP); +} + +#define CURSOR_WIDTH 64 +#define CURSOR_HEIGHT 64 + +/* called from IRQ to update cursor related registers (if needed). The + * cursor registers, other than x/y position, appear not to be double + * buffered, and changing them other than from vblank seems to trigger + * underflow. + */ +static void update_cursor(struct drm_crtc *crtc) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct mdp4_kms *mdp4_kms = get_kms(crtc); + struct msm_kms *kms = &mdp4_kms->base.base; + enum mdp4_dma dma = mdp4_crtc->dma; + unsigned long flags; + + spin_lock_irqsave(&mdp4_crtc->cursor.lock, flags); + if (mdp4_crtc->cursor.stale) { + struct drm_gem_object *next_bo = mdp4_crtc->cursor.next_bo; + struct drm_gem_object *prev_bo = mdp4_crtc->cursor.scanout_bo; + uint64_t iova = mdp4_crtc->cursor.next_iova; + + if (next_bo) { + /* take a obj ref + iova ref when we start scanning out: */ + drm_gem_object_get(next_bo); + msm_gem_get_and_pin_iova(next_bo, kms->aspace, &iova); + + /* enable cursor: */ + mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_SIZE(dma), + MDP4_DMA_CURSOR_SIZE_WIDTH(mdp4_crtc->cursor.width) | + MDP4_DMA_CURSOR_SIZE_HEIGHT(mdp4_crtc->cursor.height)); + mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BASE(dma), iova); + mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BLEND_CONFIG(dma), + MDP4_DMA_CURSOR_BLEND_CONFIG_FORMAT(CURSOR_ARGB) | + MDP4_DMA_CURSOR_BLEND_CONFIG_CURSOR_EN); + } else { + /* disable cursor: */ + mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_BASE(dma), + mdp4_kms->blank_cursor_iova); + } + + /* and drop the iova ref + obj rev when done scanning out: */ + if (prev_bo) + drm_flip_work_queue(&mdp4_crtc->unref_cursor_work, prev_bo); + + mdp4_crtc->cursor.scanout_bo = next_bo; + mdp4_crtc->cursor.stale = false; + } + + mdp4_write(mdp4_kms, REG_MDP4_DMA_CURSOR_POS(dma), + MDP4_DMA_CURSOR_POS_X(mdp4_crtc->cursor.x) | + MDP4_DMA_CURSOR_POS_Y(mdp4_crtc->cursor.y)); + + spin_unlock_irqrestore(&mdp4_crtc->cursor.lock, flags); +} + +static int mdp4_crtc_cursor_set(struct drm_crtc *crtc, + struct drm_file *file_priv, uint32_t handle, + uint32_t width, uint32_t height) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct mdp4_kms *mdp4_kms = get_kms(crtc); + struct msm_kms *kms = &mdp4_kms->base.base; + struct drm_device *dev = crtc->dev; + struct drm_gem_object *cursor_bo, *old_bo; + unsigned long flags; + uint64_t iova; + int ret; + + if ((width > CURSOR_WIDTH) || (height > CURSOR_HEIGHT)) { + DRM_DEV_ERROR(dev->dev, "bad cursor size: %dx%d\n", width, height); + return -EINVAL; + } + + if (handle) { + cursor_bo = drm_gem_object_lookup(file_priv, handle); + if (!cursor_bo) + return -ENOENT; + } else { + cursor_bo = NULL; + } + + if (cursor_bo) { + ret = msm_gem_get_and_pin_iova(cursor_bo, kms->aspace, &iova); + if (ret) + goto fail; + } else { + iova = 0; + } + + spin_lock_irqsave(&mdp4_crtc->cursor.lock, flags); + old_bo = mdp4_crtc->cursor.next_bo; + mdp4_crtc->cursor.next_bo = cursor_bo; + mdp4_crtc->cursor.next_iova = iova; + mdp4_crtc->cursor.width = width; + mdp4_crtc->cursor.height = height; + mdp4_crtc->cursor.stale = true; + spin_unlock_irqrestore(&mdp4_crtc->cursor.lock, flags); + + if (old_bo) { + /* drop our previous reference: */ + drm_flip_work_queue(&mdp4_crtc->unref_cursor_work, old_bo); + } + + request_pending(crtc, PENDING_CURSOR); + + return 0; + +fail: + drm_gem_object_put(cursor_bo); + return ret; +} + +static int mdp4_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + unsigned long flags; + + spin_lock_irqsave(&mdp4_crtc->cursor.lock, flags); + mdp4_crtc->cursor.x = x; + mdp4_crtc->cursor.y = y; + spin_unlock_irqrestore(&mdp4_crtc->cursor.lock, flags); + + crtc_flush(crtc); + request_pending(crtc, PENDING_CURSOR); + + return 0; +} + +static const struct drm_crtc_funcs mdp4_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .destroy = mdp4_crtc_destroy, + .page_flip = drm_atomic_helper_page_flip, + .cursor_set = mdp4_crtc_cursor_set, + .cursor_move = mdp4_crtc_cursor_move, + .reset = drm_atomic_helper_crtc_reset, + .atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_crtc_destroy_state, + .enable_vblank = msm_crtc_enable_vblank, + .disable_vblank = msm_crtc_disable_vblank, +}; + +static const struct drm_crtc_helper_funcs mdp4_crtc_helper_funcs = { + .mode_set_nofb = mdp4_crtc_mode_set_nofb, + .atomic_check = mdp4_crtc_atomic_check, + .atomic_begin = mdp4_crtc_atomic_begin, + .atomic_flush = mdp4_crtc_atomic_flush, + .atomic_enable = mdp4_crtc_atomic_enable, + .atomic_disable = mdp4_crtc_atomic_disable, +}; + +static void mdp4_crtc_vblank_irq(struct mdp_irq *irq, uint32_t irqstatus) +{ + struct mdp4_crtc *mdp4_crtc = container_of(irq, struct mdp4_crtc, vblank); + struct drm_crtc *crtc = &mdp4_crtc->base; + struct msm_drm_private *priv = crtc->dev->dev_private; + unsigned pending; + + mdp_irq_unregister(&get_kms(crtc)->base, &mdp4_crtc->vblank); + + pending = atomic_xchg(&mdp4_crtc->pending, 0); + + if (pending & PENDING_FLIP) { + complete_flip(crtc, NULL); + } + + if (pending & PENDING_CURSOR) { + update_cursor(crtc); + drm_flip_work_commit(&mdp4_crtc->unref_cursor_work, priv->wq); + } +} + +static void mdp4_crtc_err_irq(struct mdp_irq *irq, uint32_t irqstatus) +{ + struct mdp4_crtc *mdp4_crtc = container_of(irq, struct mdp4_crtc, err); + struct drm_crtc *crtc = &mdp4_crtc->base; + DBG("%s: error: %08x", mdp4_crtc->name, irqstatus); + crtc_flush(crtc); +} + +static void mdp4_crtc_wait_for_flush_done(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct mdp4_kms *mdp4_kms = get_kms(crtc); + int ret; + + ret = drm_crtc_vblank_get(crtc); + if (ret) + return; + + ret = wait_event_timeout(dev->vblank[drm_crtc_index(crtc)].queue, + !(mdp4_read(mdp4_kms, REG_MDP4_OVERLAY_FLUSH) & + mdp4_crtc->flushed_mask), + msecs_to_jiffies(50)); + if (ret <= 0) + dev_warn(dev->dev, "vblank time out, crtc=%d\n", mdp4_crtc->id); + + mdp4_crtc->flushed_mask = 0; + + drm_crtc_vblank_put(crtc); +} + +uint32_t mdp4_crtc_vblank(struct drm_crtc *crtc) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + return mdp4_crtc->vblank.irqmask; +} + +/* set dma config, ie. the format the encoder wants. */ +void mdp4_crtc_set_config(struct drm_crtc *crtc, uint32_t config) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct mdp4_kms *mdp4_kms = get_kms(crtc); + + mdp4_write(mdp4_kms, REG_MDP4_DMA_CONFIG(mdp4_crtc->dma), config); +} + +/* set interface for routing crtc->encoder: */ +void mdp4_crtc_set_intf(struct drm_crtc *crtc, enum mdp4_intf intf, int mixer) +{ + struct mdp4_crtc *mdp4_crtc = to_mdp4_crtc(crtc); + struct mdp4_kms *mdp4_kms = get_kms(crtc); + uint32_t intf_sel; + + intf_sel = mdp4_read(mdp4_kms, REG_MDP4_DISP_INTF_SEL); + + switch (mdp4_crtc->dma) { + case DMA_P: + intf_sel &= ~MDP4_DISP_INTF_SEL_PRIM__MASK; + intf_sel |= MDP4_DISP_INTF_SEL_PRIM(intf); + break; + case DMA_S: + intf_sel &= ~MDP4_DISP_INTF_SEL_SEC__MASK; + intf_sel |= MDP4_DISP_INTF_SEL_SEC(intf); + break; + case DMA_E: + intf_sel &= ~MDP4_DISP_INTF_SEL_EXT__MASK; + intf_sel |= MDP4_DISP_INTF_SEL_EXT(intf); + break; + } + + if (intf == INTF_DSI_VIDEO) { + intf_sel &= ~MDP4_DISP_INTF_SEL_DSI_CMD; + intf_sel |= MDP4_DISP_INTF_SEL_DSI_VIDEO; + } else if (intf == INTF_DSI_CMD) { + intf_sel &= ~MDP4_DISP_INTF_SEL_DSI_VIDEO; + intf_sel |= MDP4_DISP_INTF_SEL_DSI_CMD; + } + + mdp4_crtc->mixer = mixer; + + blend_setup(crtc); + + DBG("%s: intf_sel=%08x", mdp4_crtc->name, intf_sel); + + mdp4_write(mdp4_kms, REG_MDP4_DISP_INTF_SEL, intf_sel); +} + +void mdp4_crtc_wait_for_commit_done(struct drm_crtc *crtc) +{ + /* wait_for_flush_done is the only case for now. + * Later we will have command mode CRTC to wait for + * other event. + */ + mdp4_crtc_wait_for_flush_done(crtc); +} + +static const char *dma_names[] = { + "DMA_P", "DMA_S", "DMA_E", +}; + +/* initialize crtc */ +struct drm_crtc *mdp4_crtc_init(struct drm_device *dev, + struct drm_plane *plane, int id, int ovlp_id, + enum mdp4_dma dma_id) +{ + struct drm_crtc *crtc = NULL; + struct mdp4_crtc *mdp4_crtc; + + mdp4_crtc = kzalloc(sizeof(*mdp4_crtc), GFP_KERNEL); + if (!mdp4_crtc) + return ERR_PTR(-ENOMEM); + + crtc = &mdp4_crtc->base; + + mdp4_crtc->id = id; + + mdp4_crtc->ovlp = ovlp_id; + mdp4_crtc->dma = dma_id; + + mdp4_crtc->vblank.irqmask = dma2irq(mdp4_crtc->dma); + mdp4_crtc->vblank.irq = mdp4_crtc_vblank_irq; + + mdp4_crtc->err.irqmask = dma2err(mdp4_crtc->dma); + mdp4_crtc->err.irq = mdp4_crtc_err_irq; + + snprintf(mdp4_crtc->name, sizeof(mdp4_crtc->name), "%s:%d", + dma_names[dma_id], ovlp_id); + + spin_lock_init(&mdp4_crtc->cursor.lock); + + drm_flip_work_init(&mdp4_crtc->unref_cursor_work, + "unref cursor", unref_cursor_worker); + + drm_crtc_init_with_planes(dev, crtc, plane, NULL, &mdp4_crtc_funcs, + NULL); + drm_crtc_helper_add(crtc, &mdp4_crtc_helper_funcs); + + return crtc; +} diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_dsi_encoder.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_dsi_encoder.c new file mode 100644 index 000000000..aaf2f26f8 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_dsi_encoder.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + * Copyright (c) 2014, Inforce Computing. All rights reserved. + * + * Author: Vinay Simha <vinaysimha@inforcecomputing.com> + */ + +#include <drm/drm_crtc.h> +#include <drm/drm_probe_helper.h> + +#include "mdp4_kms.h" + +struct mdp4_dsi_encoder { + struct drm_encoder base; + struct drm_panel *panel; + bool enabled; +}; +#define to_mdp4_dsi_encoder(x) container_of(x, struct mdp4_dsi_encoder, base) + +static struct mdp4_kms *get_kms(struct drm_encoder *encoder) +{ + struct msm_drm_private *priv = encoder->dev->dev_private; + return to_mdp4_kms(to_mdp_kms(priv->kms)); +} + +static void mdp4_dsi_encoder_destroy(struct drm_encoder *encoder) +{ + struct mdp4_dsi_encoder *mdp4_dsi_encoder = to_mdp4_dsi_encoder(encoder); + + drm_encoder_cleanup(encoder); + kfree(mdp4_dsi_encoder); +} + +static const struct drm_encoder_funcs mdp4_dsi_encoder_funcs = { + .destroy = mdp4_dsi_encoder_destroy, +}; + +static void mdp4_dsi_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct mdp4_kms *mdp4_kms = get_kms(encoder); + uint32_t dsi_hsync_skew, vsync_period, vsync_len, ctrl_pol; + uint32_t display_v_start, display_v_end; + uint32_t hsync_start_x, hsync_end_x; + + mode = adjusted_mode; + + DBG("set mode: " DRM_MODE_FMT, DRM_MODE_ARG(mode)); + + ctrl_pol = 0; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + ctrl_pol |= MDP4_DSI_CTRL_POLARITY_HSYNC_LOW; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ctrl_pol |= MDP4_DSI_CTRL_POLARITY_VSYNC_LOW; + /* probably need to get DATA_EN polarity from panel.. */ + + dsi_hsync_skew = 0; /* get this from panel? */ + + hsync_start_x = (mode->htotal - mode->hsync_start); + hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1; + + vsync_period = mode->vtotal * mode->htotal; + vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal; + display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + dsi_hsync_skew; + display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + dsi_hsync_skew - 1; + + mdp4_write(mdp4_kms, REG_MDP4_DSI_HSYNC_CTRL, + MDP4_DSI_HSYNC_CTRL_PULSEW(mode->hsync_end - mode->hsync_start) | + MDP4_DSI_HSYNC_CTRL_PERIOD(mode->htotal)); + mdp4_write(mdp4_kms, REG_MDP4_DSI_VSYNC_PERIOD, vsync_period); + mdp4_write(mdp4_kms, REG_MDP4_DSI_VSYNC_LEN, vsync_len); + mdp4_write(mdp4_kms, REG_MDP4_DSI_DISPLAY_HCTRL, + MDP4_DSI_DISPLAY_HCTRL_START(hsync_start_x) | + MDP4_DSI_DISPLAY_HCTRL_END(hsync_end_x)); + mdp4_write(mdp4_kms, REG_MDP4_DSI_DISPLAY_VSTART, display_v_start); + mdp4_write(mdp4_kms, REG_MDP4_DSI_DISPLAY_VEND, display_v_end); + + mdp4_write(mdp4_kms, REG_MDP4_DSI_CTRL_POLARITY, ctrl_pol); + mdp4_write(mdp4_kms, REG_MDP4_DSI_UNDERFLOW_CLR, + MDP4_DSI_UNDERFLOW_CLR_ENABLE_RECOVERY | + MDP4_DSI_UNDERFLOW_CLR_COLOR(0xff)); + mdp4_write(mdp4_kms, REG_MDP4_DSI_ACTIVE_HCTL, + MDP4_DSI_ACTIVE_HCTL_START(0) | + MDP4_DSI_ACTIVE_HCTL_END(0)); + mdp4_write(mdp4_kms, REG_MDP4_DSI_HSYNC_SKEW, dsi_hsync_skew); + mdp4_write(mdp4_kms, REG_MDP4_DSI_BORDER_CLR, 0); + mdp4_write(mdp4_kms, REG_MDP4_DSI_ACTIVE_VSTART, 0); + mdp4_write(mdp4_kms, REG_MDP4_DSI_ACTIVE_VEND, 0); +} + +static void mdp4_dsi_encoder_disable(struct drm_encoder *encoder) +{ + struct mdp4_dsi_encoder *mdp4_dsi_encoder = to_mdp4_dsi_encoder(encoder); + struct mdp4_kms *mdp4_kms = get_kms(encoder); + + if (!mdp4_dsi_encoder->enabled) + return; + + mdp4_write(mdp4_kms, REG_MDP4_DSI_ENABLE, 0); + + /* + * Wait for a vsync so we know the ENABLE=0 latched before + * the (connector) source of the vsync's gets disabled, + * otherwise we end up in a funny state if we re-enable + * before the disable latches, which results that some of + * the settings changes for the new modeset (like new + * scanout buffer) don't latch properly.. + */ + mdp_irq_wait(&mdp4_kms->base, MDP4_IRQ_PRIMARY_VSYNC); + + mdp4_dsi_encoder->enabled = false; +} + +static void mdp4_dsi_encoder_enable(struct drm_encoder *encoder) +{ + struct mdp4_dsi_encoder *mdp4_dsi_encoder = to_mdp4_dsi_encoder(encoder); + struct mdp4_kms *mdp4_kms = get_kms(encoder); + + if (mdp4_dsi_encoder->enabled) + return; + + mdp4_crtc_set_config(encoder->crtc, + MDP4_DMA_CONFIG_PACK_ALIGN_MSB | + MDP4_DMA_CONFIG_DEFLKR_EN | + MDP4_DMA_CONFIG_DITHER_EN | + MDP4_DMA_CONFIG_R_BPC(BPC8) | + MDP4_DMA_CONFIG_G_BPC(BPC8) | + MDP4_DMA_CONFIG_B_BPC(BPC8) | + MDP4_DMA_CONFIG_PACK(0x21)); + + mdp4_crtc_set_intf(encoder->crtc, INTF_DSI_VIDEO, 0); + + mdp4_write(mdp4_kms, REG_MDP4_DSI_ENABLE, 1); + + mdp4_dsi_encoder->enabled = true; +} + +static const struct drm_encoder_helper_funcs mdp4_dsi_encoder_helper_funcs = { + .mode_set = mdp4_dsi_encoder_mode_set, + .disable = mdp4_dsi_encoder_disable, + .enable = mdp4_dsi_encoder_enable, +}; + +/* initialize encoder */ +struct drm_encoder *mdp4_dsi_encoder_init(struct drm_device *dev) +{ + struct drm_encoder *encoder = NULL; + struct mdp4_dsi_encoder *mdp4_dsi_encoder; + int ret; + + mdp4_dsi_encoder = kzalloc(sizeof(*mdp4_dsi_encoder), GFP_KERNEL); + if (!mdp4_dsi_encoder) { + ret = -ENOMEM; + goto fail; + } + + encoder = &mdp4_dsi_encoder->base; + + drm_encoder_init(dev, encoder, &mdp4_dsi_encoder_funcs, + DRM_MODE_ENCODER_DSI, NULL); + drm_encoder_helper_add(encoder, &mdp4_dsi_encoder_helper_funcs); + + return encoder; + +fail: + if (encoder) + mdp4_dsi_encoder_destroy(encoder); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_dtv_encoder.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_dtv_encoder.c new file mode 100644 index 000000000..88645dbc3 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_dtv_encoder.c @@ -0,0 +1,213 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <drm/drm_crtc.h> +#include <drm/drm_probe_helper.h> + +#include "mdp4_kms.h" + +struct mdp4_dtv_encoder { + struct drm_encoder base; + struct clk *hdmi_clk; + struct clk *mdp_clk; + unsigned long int pixclock; + bool enabled; + uint32_t bsc; +}; +#define to_mdp4_dtv_encoder(x) container_of(x, struct mdp4_dtv_encoder, base) + +static struct mdp4_kms *get_kms(struct drm_encoder *encoder) +{ + struct msm_drm_private *priv = encoder->dev->dev_private; + return to_mdp4_kms(to_mdp_kms(priv->kms)); +} + +static void mdp4_dtv_encoder_destroy(struct drm_encoder *encoder) +{ + struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder); + drm_encoder_cleanup(encoder); + kfree(mdp4_dtv_encoder); +} + +static const struct drm_encoder_funcs mdp4_dtv_encoder_funcs = { + .destroy = mdp4_dtv_encoder_destroy, +}; + +static void mdp4_dtv_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder); + struct mdp4_kms *mdp4_kms = get_kms(encoder); + uint32_t dtv_hsync_skew, vsync_period, vsync_len, ctrl_pol; + uint32_t display_v_start, display_v_end; + uint32_t hsync_start_x, hsync_end_x; + + mode = adjusted_mode; + + DBG("set mode: " DRM_MODE_FMT, DRM_MODE_ARG(mode)); + + mdp4_dtv_encoder->pixclock = mode->clock * 1000; + + DBG("pixclock=%lu", mdp4_dtv_encoder->pixclock); + + ctrl_pol = 0; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + ctrl_pol |= MDP4_DTV_CTRL_POLARITY_HSYNC_LOW; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ctrl_pol |= MDP4_DTV_CTRL_POLARITY_VSYNC_LOW; + /* probably need to get DATA_EN polarity from panel.. */ + + dtv_hsync_skew = 0; /* get this from panel? */ + + hsync_start_x = (mode->htotal - mode->hsync_start); + hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1; + + vsync_period = mode->vtotal * mode->htotal; + vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal; + display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + dtv_hsync_skew; + display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + dtv_hsync_skew - 1; + + mdp4_write(mdp4_kms, REG_MDP4_DTV_HSYNC_CTRL, + MDP4_DTV_HSYNC_CTRL_PULSEW(mode->hsync_end - mode->hsync_start) | + MDP4_DTV_HSYNC_CTRL_PERIOD(mode->htotal)); + mdp4_write(mdp4_kms, REG_MDP4_DTV_VSYNC_PERIOD, vsync_period); + mdp4_write(mdp4_kms, REG_MDP4_DTV_VSYNC_LEN, vsync_len); + mdp4_write(mdp4_kms, REG_MDP4_DTV_DISPLAY_HCTRL, + MDP4_DTV_DISPLAY_HCTRL_START(hsync_start_x) | + MDP4_DTV_DISPLAY_HCTRL_END(hsync_end_x)); + mdp4_write(mdp4_kms, REG_MDP4_DTV_DISPLAY_VSTART, display_v_start); + mdp4_write(mdp4_kms, REG_MDP4_DTV_DISPLAY_VEND, display_v_end); + mdp4_write(mdp4_kms, REG_MDP4_DTV_BORDER_CLR, 0); + mdp4_write(mdp4_kms, REG_MDP4_DTV_UNDERFLOW_CLR, + MDP4_DTV_UNDERFLOW_CLR_ENABLE_RECOVERY | + MDP4_DTV_UNDERFLOW_CLR_COLOR(0xff)); + mdp4_write(mdp4_kms, REG_MDP4_DTV_HSYNC_SKEW, dtv_hsync_skew); + mdp4_write(mdp4_kms, REG_MDP4_DTV_CTRL_POLARITY, ctrl_pol); + mdp4_write(mdp4_kms, REG_MDP4_DTV_ACTIVE_HCTL, + MDP4_DTV_ACTIVE_HCTL_START(0) | + MDP4_DTV_ACTIVE_HCTL_END(0)); + mdp4_write(mdp4_kms, REG_MDP4_DTV_ACTIVE_VSTART, 0); + mdp4_write(mdp4_kms, REG_MDP4_DTV_ACTIVE_VEND, 0); +} + +static void mdp4_dtv_encoder_disable(struct drm_encoder *encoder) +{ + struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder); + struct mdp4_kms *mdp4_kms = get_kms(encoder); + + if (WARN_ON(!mdp4_dtv_encoder->enabled)) + return; + + mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 0); + + /* + * Wait for a vsync so we know the ENABLE=0 latched before + * the (connector) source of the vsync's gets disabled, + * otherwise we end up in a funny state if we re-enable + * before the disable latches, which results that some of + * the settings changes for the new modeset (like new + * scanout buffer) don't latch properly.. + */ + mdp_irq_wait(&mdp4_kms->base, MDP4_IRQ_EXTERNAL_VSYNC); + + clk_disable_unprepare(mdp4_dtv_encoder->hdmi_clk); + clk_disable_unprepare(mdp4_dtv_encoder->mdp_clk); + + mdp4_dtv_encoder->enabled = false; +} + +static void mdp4_dtv_encoder_enable(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder); + struct mdp4_kms *mdp4_kms = get_kms(encoder); + unsigned long pc = mdp4_dtv_encoder->pixclock; + int ret; + + if (WARN_ON(mdp4_dtv_encoder->enabled)) + return; + + mdp4_crtc_set_config(encoder->crtc, + MDP4_DMA_CONFIG_R_BPC(BPC8) | + MDP4_DMA_CONFIG_G_BPC(BPC8) | + MDP4_DMA_CONFIG_B_BPC(BPC8) | + MDP4_DMA_CONFIG_PACK(0x21)); + mdp4_crtc_set_intf(encoder->crtc, INTF_LCDC_DTV, 1); + + DBG("setting mdp_clk=%lu", pc); + + ret = clk_set_rate(mdp4_dtv_encoder->mdp_clk, pc); + if (ret) + DRM_DEV_ERROR(dev->dev, "failed to set mdp_clk to %lu: %d\n", + pc, ret); + + ret = clk_prepare_enable(mdp4_dtv_encoder->mdp_clk); + if (ret) + DRM_DEV_ERROR(dev->dev, "failed to enabled mdp_clk: %d\n", ret); + + ret = clk_prepare_enable(mdp4_dtv_encoder->hdmi_clk); + if (ret) + DRM_DEV_ERROR(dev->dev, "failed to enable hdmi_clk: %d\n", ret); + + mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 1); + + mdp4_dtv_encoder->enabled = true; +} + +static const struct drm_encoder_helper_funcs mdp4_dtv_encoder_helper_funcs = { + .mode_set = mdp4_dtv_encoder_mode_set, + .enable = mdp4_dtv_encoder_enable, + .disable = mdp4_dtv_encoder_disable, +}; + +long mdp4_dtv_round_pixclk(struct drm_encoder *encoder, unsigned long rate) +{ + struct mdp4_dtv_encoder *mdp4_dtv_encoder = to_mdp4_dtv_encoder(encoder); + return clk_round_rate(mdp4_dtv_encoder->mdp_clk, rate); +} + +/* initialize encoder */ +struct drm_encoder *mdp4_dtv_encoder_init(struct drm_device *dev) +{ + struct drm_encoder *encoder = NULL; + struct mdp4_dtv_encoder *mdp4_dtv_encoder; + int ret; + + mdp4_dtv_encoder = kzalloc(sizeof(*mdp4_dtv_encoder), GFP_KERNEL); + if (!mdp4_dtv_encoder) { + ret = -ENOMEM; + goto fail; + } + + encoder = &mdp4_dtv_encoder->base; + + drm_encoder_init(dev, encoder, &mdp4_dtv_encoder_funcs, + DRM_MODE_ENCODER_TMDS, NULL); + drm_encoder_helper_add(encoder, &mdp4_dtv_encoder_helper_funcs); + + mdp4_dtv_encoder->hdmi_clk = devm_clk_get(dev->dev, "hdmi_clk"); + if (IS_ERR(mdp4_dtv_encoder->hdmi_clk)) { + DRM_DEV_ERROR(dev->dev, "failed to get hdmi_clk\n"); + ret = PTR_ERR(mdp4_dtv_encoder->hdmi_clk); + goto fail; + } + + mdp4_dtv_encoder->mdp_clk = devm_clk_get(dev->dev, "tv_clk"); + if (IS_ERR(mdp4_dtv_encoder->mdp_clk)) { + DRM_DEV_ERROR(dev->dev, "failed to get tv_clk\n"); + ret = PTR_ERR(mdp4_dtv_encoder->mdp_clk); + goto fail; + } + + return encoder; + +fail: + if (encoder) + mdp4_dtv_encoder_destroy(encoder); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_irq.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_irq.c new file mode 100644 index 000000000..4d49f3ba6 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_irq.c @@ -0,0 +1,111 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <drm/drm_print.h> +#include <drm/drm_vblank.h> + +#include "msm_drv.h" +#include "mdp4_kms.h" + +void mdp4_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask, + uint32_t old_irqmask) +{ + mdp4_write(to_mdp4_kms(mdp_kms), REG_MDP4_INTR_CLEAR, + irqmask ^ (irqmask & old_irqmask)); + mdp4_write(to_mdp4_kms(mdp_kms), REG_MDP4_INTR_ENABLE, irqmask); +} + +static void mdp4_irq_error_handler(struct mdp_irq *irq, uint32_t irqstatus) +{ + struct mdp4_kms *mdp4_kms = container_of(irq, struct mdp4_kms, error_handler); + static DEFINE_RATELIMIT_STATE(rs, 5*HZ, 1); + extern bool dumpstate; + + DRM_ERROR_RATELIMITED("errors: %08x\n", irqstatus); + + if (dumpstate && __ratelimit(&rs)) { + struct drm_printer p = drm_info_printer(mdp4_kms->dev->dev); + drm_state_dump(mdp4_kms->dev, &p); + } +} + +void mdp4_irq_preinstall(struct msm_kms *kms) +{ + struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms)); + mdp4_enable(mdp4_kms); + mdp4_write(mdp4_kms, REG_MDP4_INTR_CLEAR, 0xffffffff); + mdp4_write(mdp4_kms, REG_MDP4_INTR_ENABLE, 0x00000000); + mdp4_disable(mdp4_kms); +} + +int mdp4_irq_postinstall(struct msm_kms *kms) +{ + struct mdp_kms *mdp_kms = to_mdp_kms(kms); + struct mdp4_kms *mdp4_kms = to_mdp4_kms(mdp_kms); + struct mdp_irq *error_handler = &mdp4_kms->error_handler; + + error_handler->irq = mdp4_irq_error_handler; + error_handler->irqmask = MDP4_IRQ_PRIMARY_INTF_UDERRUN | + MDP4_IRQ_EXTERNAL_INTF_UDERRUN; + + mdp_irq_register(mdp_kms, error_handler); + + return 0; +} + +void mdp4_irq_uninstall(struct msm_kms *kms) +{ + struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms)); + mdp4_enable(mdp4_kms); + mdp4_write(mdp4_kms, REG_MDP4_INTR_ENABLE, 0x00000000); + mdp4_disable(mdp4_kms); +} + +irqreturn_t mdp4_irq(struct msm_kms *kms) +{ + struct mdp_kms *mdp_kms = to_mdp_kms(kms); + struct mdp4_kms *mdp4_kms = to_mdp4_kms(mdp_kms); + struct drm_device *dev = mdp4_kms->dev; + struct msm_drm_private *priv = dev->dev_private; + unsigned int id; + uint32_t status, enable; + + enable = mdp4_read(mdp4_kms, REG_MDP4_INTR_ENABLE); + status = mdp4_read(mdp4_kms, REG_MDP4_INTR_STATUS) & enable; + mdp4_write(mdp4_kms, REG_MDP4_INTR_CLEAR, status); + + VERB("status=%08x", status); + + mdp_dispatch_irqs(mdp_kms, status); + + for (id = 0; id < priv->num_crtcs; id++) + if (status & mdp4_crtc_vblank(priv->crtcs[id])) + drm_handle_vblank(dev, id); + + return IRQ_HANDLED; +} + +int mdp4_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc) +{ + struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms)); + + mdp4_enable(mdp4_kms); + mdp_update_vblank_mask(to_mdp_kms(kms), + mdp4_crtc_vblank(crtc), true); + mdp4_disable(mdp4_kms); + + return 0; +} + +void mdp4_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc) +{ + struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms)); + + mdp4_enable(mdp4_kms); + mdp_update_vblank_mask(to_mdp_kms(kms), + mdp4_crtc_vblank(crtc), false); + mdp4_disable(mdp4_kms); +} diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c new file mode 100644 index 000000000..b4d0bfc83 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.c @@ -0,0 +1,573 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <linux/delay.h> + +#include <drm/drm_vblank.h> + +#include "msm_drv.h" +#include "msm_gem.h" +#include "msm_mmu.h" +#include "mdp4_kms.h" + +static struct mdp4_platform_config *mdp4_get_config(struct platform_device *dev); + +static int mdp4_hw_init(struct msm_kms *kms) +{ + struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms)); + struct drm_device *dev = mdp4_kms->dev; + u32 dmap_cfg, vg_cfg; + unsigned long clk; + int ret = 0; + + pm_runtime_get_sync(dev->dev); + + if (mdp4_kms->rev > 1) { + mdp4_write(mdp4_kms, REG_MDP4_CS_CONTROLLER0, 0x0707ffff); + mdp4_write(mdp4_kms, REG_MDP4_CS_CONTROLLER1, 0x03073f3f); + } + + mdp4_write(mdp4_kms, REG_MDP4_PORTMAP_MODE, 0x3); + + /* max read pending cmd config, 3 pending requests: */ + mdp4_write(mdp4_kms, REG_MDP4_READ_CNFG, 0x02222); + + clk = clk_get_rate(mdp4_kms->clk); + + if ((mdp4_kms->rev >= 1) || (clk >= 90000000)) { + dmap_cfg = 0x47; /* 16 bytes-burst x 8 req */ + vg_cfg = 0x47; /* 16 bytes-burs x 8 req */ + } else { + dmap_cfg = 0x27; /* 8 bytes-burst x 8 req */ + vg_cfg = 0x43; /* 16 bytes-burst x 4 req */ + } + + DBG("fetch config: dmap=%02x, vg=%02x", dmap_cfg, vg_cfg); + + mdp4_write(mdp4_kms, REG_MDP4_DMA_FETCH_CONFIG(DMA_P), dmap_cfg); + mdp4_write(mdp4_kms, REG_MDP4_DMA_FETCH_CONFIG(DMA_E), dmap_cfg); + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(VG1), vg_cfg); + mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(VG2), vg_cfg); + mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(RGB1), vg_cfg); + mdp4_write(mdp4_kms, REG_MDP4_PIPE_FETCH_CONFIG(RGB2), vg_cfg); + + if (mdp4_kms->rev >= 2) + mdp4_write(mdp4_kms, REG_MDP4_LAYERMIXER_IN_CFG_UPDATE_METHOD, 1); + mdp4_write(mdp4_kms, REG_MDP4_LAYERMIXER_IN_CFG, 0); + + /* disable CSC matrix / YUV by default: */ + mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(VG1), 0); + mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(VG2), 0); + mdp4_write(mdp4_kms, REG_MDP4_DMA_P_OP_MODE, 0); + mdp4_write(mdp4_kms, REG_MDP4_DMA_S_OP_MODE, 0); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_CSC_CONFIG(1), 0); + mdp4_write(mdp4_kms, REG_MDP4_OVLP_CSC_CONFIG(2), 0); + + if (mdp4_kms->rev > 1) + mdp4_write(mdp4_kms, REG_MDP4_RESET_STATUS, 1); + + pm_runtime_put_sync(dev->dev); + + return ret; +} + +static void mdp4_enable_commit(struct msm_kms *kms) +{ + struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms)); + mdp4_enable(mdp4_kms); +} + +static void mdp4_disable_commit(struct msm_kms *kms) +{ + struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms)); + mdp4_disable(mdp4_kms); +} + +static void mdp4_prepare_commit(struct msm_kms *kms, struct drm_atomic_state *state) +{ +} + +static void mdp4_flush_commit(struct msm_kms *kms, unsigned crtc_mask) +{ + /* TODO */ +} + +static void mdp4_wait_flush(struct msm_kms *kms, unsigned crtc_mask) +{ + struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms)); + struct drm_crtc *crtc; + + for_each_crtc_mask(mdp4_kms->dev, crtc, crtc_mask) + mdp4_crtc_wait_for_commit_done(crtc); +} + +static void mdp4_complete_commit(struct msm_kms *kms, unsigned crtc_mask) +{ +} + +static long mdp4_round_pixclk(struct msm_kms *kms, unsigned long rate, + struct drm_encoder *encoder) +{ + /* if we had >1 encoder, we'd need something more clever: */ + switch (encoder->encoder_type) { + case DRM_MODE_ENCODER_TMDS: + return mdp4_dtv_round_pixclk(encoder, rate); + case DRM_MODE_ENCODER_LVDS: + case DRM_MODE_ENCODER_DSI: + default: + return rate; + } +} + +static void mdp4_destroy(struct msm_kms *kms) +{ + struct mdp4_kms *mdp4_kms = to_mdp4_kms(to_mdp_kms(kms)); + struct device *dev = mdp4_kms->dev->dev; + struct msm_gem_address_space *aspace = kms->aspace; + + if (mdp4_kms->blank_cursor_iova) + msm_gem_unpin_iova(mdp4_kms->blank_cursor_bo, kms->aspace); + drm_gem_object_put(mdp4_kms->blank_cursor_bo); + + if (aspace) { + aspace->mmu->funcs->detach(aspace->mmu); + msm_gem_address_space_put(aspace); + } + + if (mdp4_kms->rpm_enabled) + pm_runtime_disable(dev); + + kfree(mdp4_kms); +} + +static const struct mdp_kms_funcs kms_funcs = { + .base = { + .hw_init = mdp4_hw_init, + .irq_preinstall = mdp4_irq_preinstall, + .irq_postinstall = mdp4_irq_postinstall, + .irq_uninstall = mdp4_irq_uninstall, + .irq = mdp4_irq, + .enable_vblank = mdp4_enable_vblank, + .disable_vblank = mdp4_disable_vblank, + .enable_commit = mdp4_enable_commit, + .disable_commit = mdp4_disable_commit, + .prepare_commit = mdp4_prepare_commit, + .flush_commit = mdp4_flush_commit, + .wait_flush = mdp4_wait_flush, + .complete_commit = mdp4_complete_commit, + .get_format = mdp_get_format, + .round_pixclk = mdp4_round_pixclk, + .destroy = mdp4_destroy, + }, + .set_irqmask = mdp4_set_irqmask, +}; + +int mdp4_disable(struct mdp4_kms *mdp4_kms) +{ + DBG(""); + + clk_disable_unprepare(mdp4_kms->clk); + if (mdp4_kms->pclk) + clk_disable_unprepare(mdp4_kms->pclk); + if (mdp4_kms->lut_clk) + clk_disable_unprepare(mdp4_kms->lut_clk); + if (mdp4_kms->axi_clk) + clk_disable_unprepare(mdp4_kms->axi_clk); + + return 0; +} + +int mdp4_enable(struct mdp4_kms *mdp4_kms) +{ + DBG(""); + + clk_prepare_enable(mdp4_kms->clk); + if (mdp4_kms->pclk) + clk_prepare_enable(mdp4_kms->pclk); + if (mdp4_kms->lut_clk) + clk_prepare_enable(mdp4_kms->lut_clk); + if (mdp4_kms->axi_clk) + clk_prepare_enable(mdp4_kms->axi_clk); + + return 0; +} + + +static int mdp4_modeset_init_intf(struct mdp4_kms *mdp4_kms, + int intf_type) +{ + struct drm_device *dev = mdp4_kms->dev; + struct msm_drm_private *priv = dev->dev_private; + struct drm_encoder *encoder; + struct drm_connector *connector; + struct device_node *panel_node; + int dsi_id; + int ret; + + switch (intf_type) { + case DRM_MODE_ENCODER_LVDS: + /* + * bail out early if there is no panel node (no need to + * initialize LCDC encoder and LVDS connector) + */ + panel_node = of_graph_get_remote_node(dev->dev->of_node, 0, 0); + if (!panel_node) + return 0; + + encoder = mdp4_lcdc_encoder_init(dev, panel_node); + if (IS_ERR(encoder)) { + DRM_DEV_ERROR(dev->dev, "failed to construct LCDC encoder\n"); + of_node_put(panel_node); + return PTR_ERR(encoder); + } + + /* LCDC can be hooked to DMA_P (TODO: Add DMA_S later?) */ + encoder->possible_crtcs = 1 << DMA_P; + + connector = mdp4_lvds_connector_init(dev, panel_node, encoder); + if (IS_ERR(connector)) { + DRM_DEV_ERROR(dev->dev, "failed to initialize LVDS connector\n"); + of_node_put(panel_node); + return PTR_ERR(connector); + } + + priv->encoders[priv->num_encoders++] = encoder; + priv->connectors[priv->num_connectors++] = connector; + + break; + case DRM_MODE_ENCODER_TMDS: + encoder = mdp4_dtv_encoder_init(dev); + if (IS_ERR(encoder)) { + DRM_DEV_ERROR(dev->dev, "failed to construct DTV encoder\n"); + return PTR_ERR(encoder); + } + + /* DTV can be hooked to DMA_E: */ + encoder->possible_crtcs = 1 << 1; + + if (priv->hdmi) { + /* Construct bridge/connector for HDMI: */ + ret = msm_hdmi_modeset_init(priv->hdmi, dev, encoder); + if (ret) { + DRM_DEV_ERROR(dev->dev, "failed to initialize HDMI: %d\n", ret); + return ret; + } + } + + priv->encoders[priv->num_encoders++] = encoder; + + break; + case DRM_MODE_ENCODER_DSI: + /* only DSI1 supported for now */ + dsi_id = 0; + + if (!priv->dsi[dsi_id]) + break; + + encoder = mdp4_dsi_encoder_init(dev); + if (IS_ERR(encoder)) { + ret = PTR_ERR(encoder); + DRM_DEV_ERROR(dev->dev, + "failed to construct DSI encoder: %d\n", ret); + return ret; + } + + /* TODO: Add DMA_S later? */ + encoder->possible_crtcs = 1 << DMA_P; + priv->encoders[priv->num_encoders++] = encoder; + + ret = msm_dsi_modeset_init(priv->dsi[dsi_id], dev, encoder); + if (ret) { + DRM_DEV_ERROR(dev->dev, "failed to initialize DSI: %d\n", + ret); + return ret; + } + + break; + default: + DRM_DEV_ERROR(dev->dev, "Invalid or unsupported interface\n"); + return -EINVAL; + } + + return 0; +} + +static int modeset_init(struct mdp4_kms *mdp4_kms) +{ + struct drm_device *dev = mdp4_kms->dev; + struct msm_drm_private *priv = dev->dev_private; + struct drm_plane *plane; + struct drm_crtc *crtc; + int i, ret; + static const enum mdp4_pipe rgb_planes[] = { + RGB1, RGB2, + }; + static const enum mdp4_pipe vg_planes[] = { + VG1, VG2, + }; + static const enum mdp4_dma mdp4_crtcs[] = { + DMA_P, DMA_E, + }; + static const char * const mdp4_crtc_names[] = { + "DMA_P", "DMA_E", + }; + static const int mdp4_intfs[] = { + DRM_MODE_ENCODER_LVDS, + DRM_MODE_ENCODER_DSI, + DRM_MODE_ENCODER_TMDS, + }; + + /* construct non-private planes: */ + for (i = 0; i < ARRAY_SIZE(vg_planes); i++) { + plane = mdp4_plane_init(dev, vg_planes[i], false); + if (IS_ERR(plane)) { + DRM_DEV_ERROR(dev->dev, + "failed to construct plane for VG%d\n", i + 1); + ret = PTR_ERR(plane); + goto fail; + } + priv->planes[priv->num_planes++] = plane; + } + + for (i = 0; i < ARRAY_SIZE(mdp4_crtcs); i++) { + plane = mdp4_plane_init(dev, rgb_planes[i], true); + if (IS_ERR(plane)) { + DRM_DEV_ERROR(dev->dev, + "failed to construct plane for RGB%d\n", i + 1); + ret = PTR_ERR(plane); + goto fail; + } + + crtc = mdp4_crtc_init(dev, plane, priv->num_crtcs, i, + mdp4_crtcs[i]); + if (IS_ERR(crtc)) { + DRM_DEV_ERROR(dev->dev, "failed to construct crtc for %s\n", + mdp4_crtc_names[i]); + ret = PTR_ERR(crtc); + goto fail; + } + + priv->crtcs[priv->num_crtcs++] = crtc; + } + + /* + * we currently set up two relatively fixed paths: + * + * LCDC/LVDS path: RGB1 -> DMA_P -> LCDC -> LVDS + * or + * DSI path: RGB1 -> DMA_P -> DSI1 -> DSI Panel + * + * DTV/HDMI path: RGB2 -> DMA_E -> DTV -> HDMI + */ + + for (i = 0; i < ARRAY_SIZE(mdp4_intfs); i++) { + ret = mdp4_modeset_init_intf(mdp4_kms, mdp4_intfs[i]); + if (ret) { + DRM_DEV_ERROR(dev->dev, "failed to initialize intf: %d, %d\n", + i, ret); + goto fail; + } + } + + return 0; + +fail: + return ret; +} + +static void read_mdp_hw_revision(struct mdp4_kms *mdp4_kms, + u32 *major, u32 *minor) +{ + struct drm_device *dev = mdp4_kms->dev; + u32 version; + + mdp4_enable(mdp4_kms); + version = mdp4_read(mdp4_kms, REG_MDP4_VERSION); + mdp4_disable(mdp4_kms); + + *major = FIELD(version, MDP4_VERSION_MAJOR); + *minor = FIELD(version, MDP4_VERSION_MINOR); + + DRM_DEV_INFO(dev->dev, "MDP4 version v%d.%d", *major, *minor); +} + +struct msm_kms *mdp4_kms_init(struct drm_device *dev) +{ + struct platform_device *pdev = to_platform_device(dev->dev); + struct mdp4_platform_config *config = mdp4_get_config(pdev); + struct msm_drm_private *priv = dev->dev_private; + struct mdp4_kms *mdp4_kms; + struct msm_kms *kms = NULL; + struct msm_gem_address_space *aspace; + int irq, ret; + u32 major, minor; + + mdp4_kms = kzalloc(sizeof(*mdp4_kms), GFP_KERNEL); + if (!mdp4_kms) { + DRM_DEV_ERROR(dev->dev, "failed to allocate kms\n"); + ret = -ENOMEM; + goto fail; + } + + mdp_kms_init(&mdp4_kms->base, &kms_funcs); + + priv->kms = &mdp4_kms->base.base; + kms = priv->kms; + + mdp4_kms->dev = dev; + + mdp4_kms->mmio = msm_ioremap(pdev, NULL, "MDP4"); + if (IS_ERR(mdp4_kms->mmio)) { + ret = PTR_ERR(mdp4_kms->mmio); + goto fail; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = irq; + DRM_DEV_ERROR(dev->dev, "failed to get irq: %d\n", ret); + goto fail; + } + + kms->irq = irq; + + /* NOTE: driver for this regulator still missing upstream.. use + * _get_exclusive() and ignore the error if it does not exist + * (and hope that the bootloader left it on for us) + */ + mdp4_kms->vdd = devm_regulator_get_exclusive(&pdev->dev, "vdd"); + if (IS_ERR(mdp4_kms->vdd)) + mdp4_kms->vdd = NULL; + + if (mdp4_kms->vdd) { + ret = regulator_enable(mdp4_kms->vdd); + if (ret) { + DRM_DEV_ERROR(dev->dev, "failed to enable regulator vdd: %d\n", ret); + goto fail; + } + } + + mdp4_kms->clk = devm_clk_get(&pdev->dev, "core_clk"); + if (IS_ERR(mdp4_kms->clk)) { + DRM_DEV_ERROR(dev->dev, "failed to get core_clk\n"); + ret = PTR_ERR(mdp4_kms->clk); + goto fail; + } + + mdp4_kms->pclk = devm_clk_get(&pdev->dev, "iface_clk"); + if (IS_ERR(mdp4_kms->pclk)) + mdp4_kms->pclk = NULL; + + mdp4_kms->axi_clk = devm_clk_get(&pdev->dev, "bus_clk"); + if (IS_ERR(mdp4_kms->axi_clk)) { + DRM_DEV_ERROR(dev->dev, "failed to get axi_clk\n"); + ret = PTR_ERR(mdp4_kms->axi_clk); + goto fail; + } + + clk_set_rate(mdp4_kms->clk, config->max_clk); + + read_mdp_hw_revision(mdp4_kms, &major, &minor); + + if (major != 4) { + DRM_DEV_ERROR(dev->dev, "unexpected MDP version: v%d.%d\n", + major, minor); + ret = -ENXIO; + goto fail; + } + + mdp4_kms->rev = minor; + + if (mdp4_kms->rev >= 2) { + mdp4_kms->lut_clk = devm_clk_get(&pdev->dev, "lut_clk"); + if (IS_ERR(mdp4_kms->lut_clk)) { + DRM_DEV_ERROR(dev->dev, "failed to get lut_clk\n"); + ret = PTR_ERR(mdp4_kms->lut_clk); + goto fail; + } + clk_set_rate(mdp4_kms->lut_clk, config->max_clk); + } + + pm_runtime_enable(dev->dev); + mdp4_kms->rpm_enabled = true; + + /* make sure things are off before attaching iommu (bootloader could + * have left things on, in which case we'll start getting faults if + * we don't disable): + */ + mdp4_enable(mdp4_kms); + mdp4_write(mdp4_kms, REG_MDP4_DTV_ENABLE, 0); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 0); + mdp4_write(mdp4_kms, REG_MDP4_DSI_ENABLE, 0); + mdp4_disable(mdp4_kms); + mdelay(16); + + if (config->iommu) { + struct msm_mmu *mmu = msm_iommu_new(&pdev->dev, + config->iommu); + + aspace = msm_gem_address_space_create(mmu, + "mdp4", 0x1000, 0x100000000 - 0x1000); + + if (IS_ERR(aspace)) { + if (!IS_ERR(mmu)) + mmu->funcs->destroy(mmu); + ret = PTR_ERR(aspace); + goto fail; + } + + kms->aspace = aspace; + } else { + DRM_DEV_INFO(dev->dev, "no iommu, fallback to phys " + "contig buffers for scanout\n"); + aspace = NULL; + } + + ret = modeset_init(mdp4_kms); + if (ret) { + DRM_DEV_ERROR(dev->dev, "modeset_init failed: %d\n", ret); + goto fail; + } + + mdp4_kms->blank_cursor_bo = msm_gem_new(dev, SZ_16K, MSM_BO_WC | MSM_BO_SCANOUT); + if (IS_ERR(mdp4_kms->blank_cursor_bo)) { + ret = PTR_ERR(mdp4_kms->blank_cursor_bo); + DRM_DEV_ERROR(dev->dev, "could not allocate blank-cursor bo: %d\n", ret); + mdp4_kms->blank_cursor_bo = NULL; + goto fail; + } + + ret = msm_gem_get_and_pin_iova(mdp4_kms->blank_cursor_bo, kms->aspace, + &mdp4_kms->blank_cursor_iova); + if (ret) { + DRM_DEV_ERROR(dev->dev, "could not pin blank-cursor bo: %d\n", ret); + goto fail; + } + + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.max_width = 2048; + dev->mode_config.max_height = 2048; + + return kms; + +fail: + if (kms) + mdp4_destroy(kms); + return ERR_PTR(ret); +} + +static struct mdp4_platform_config *mdp4_get_config(struct platform_device *dev) +{ + static struct mdp4_platform_config config = {}; + + /* TODO: Chips that aren't apq8064 have a 200 Mhz max_clk */ + config.max_clk = 266667000; + config.iommu = iommu_domain_alloc(&platform_bus_type); + + return &config; +} diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.h b/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.h new file mode 100644 index 000000000..e8ee92ab7 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_kms.h @@ -0,0 +1,225 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#ifndef __MDP4_KMS_H__ +#define __MDP4_KMS_H__ + +#include <drm/drm_panel.h> + +#include "msm_drv.h" +#include "msm_kms.h" +#include "disp/mdp_kms.h" +#include "mdp4.xml.h" + +struct device_node; + +struct mdp4_kms { + struct mdp_kms base; + + struct drm_device *dev; + + int rev; + + void __iomem *mmio; + + struct regulator *vdd; + + struct clk *clk; + struct clk *pclk; + struct clk *lut_clk; + struct clk *axi_clk; + + struct mdp_irq error_handler; + + bool rpm_enabled; + + /* empty/blank cursor bo to use when cursor is "disabled" */ + struct drm_gem_object *blank_cursor_bo; + uint64_t blank_cursor_iova; +}; +#define to_mdp4_kms(x) container_of(x, struct mdp4_kms, base) + +/* platform config data (ie. from DT, or pdata) */ +struct mdp4_platform_config { + struct iommu_domain *iommu; + uint32_t max_clk; +}; + +static inline void mdp4_write(struct mdp4_kms *mdp4_kms, u32 reg, u32 data) +{ + msm_writel(data, mdp4_kms->mmio + reg); +} + +static inline u32 mdp4_read(struct mdp4_kms *mdp4_kms, u32 reg) +{ + return msm_readl(mdp4_kms->mmio + reg); +} + +static inline uint32_t pipe2flush(enum mdp4_pipe pipe) +{ + switch (pipe) { + case VG1: return MDP4_OVERLAY_FLUSH_VG1; + case VG2: return MDP4_OVERLAY_FLUSH_VG2; + case RGB1: return MDP4_OVERLAY_FLUSH_RGB1; + case RGB2: return MDP4_OVERLAY_FLUSH_RGB2; + default: return 0; + } +} + +static inline uint32_t ovlp2flush(int ovlp) +{ + switch (ovlp) { + case 0: return MDP4_OVERLAY_FLUSH_OVLP0; + case 1: return MDP4_OVERLAY_FLUSH_OVLP1; + default: return 0; + } +} + +static inline uint32_t dma2irq(enum mdp4_dma dma) +{ + switch (dma) { + case DMA_P: return MDP4_IRQ_DMA_P_DONE; + case DMA_S: return MDP4_IRQ_DMA_S_DONE; + case DMA_E: return MDP4_IRQ_DMA_E_DONE; + default: return 0; + } +} + +static inline uint32_t dma2err(enum mdp4_dma dma) +{ + switch (dma) { + case DMA_P: return MDP4_IRQ_PRIMARY_INTF_UDERRUN; + case DMA_S: return 0; // ??? + case DMA_E: return MDP4_IRQ_EXTERNAL_INTF_UDERRUN; + default: return 0; + } +} + +static inline uint32_t mixercfg(uint32_t mixer_cfg, int mixer, + enum mdp4_pipe pipe, enum mdp_mixer_stage_id stage) +{ + switch (pipe) { + case VG1: + mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE0__MASK | + MDP4_LAYERMIXER_IN_CFG_PIPE0_MIXER1); + mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE0(stage) | + COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE0_MIXER1); + break; + case VG2: + mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE1__MASK | + MDP4_LAYERMIXER_IN_CFG_PIPE1_MIXER1); + mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE1(stage) | + COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE1_MIXER1); + break; + case RGB1: + mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE2__MASK | + MDP4_LAYERMIXER_IN_CFG_PIPE2_MIXER1); + mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE2(stage) | + COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE2_MIXER1); + break; + case RGB2: + mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE3__MASK | + MDP4_LAYERMIXER_IN_CFG_PIPE3_MIXER1); + mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE3(stage) | + COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE3_MIXER1); + break; + case RGB3: + mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE4__MASK | + MDP4_LAYERMIXER_IN_CFG_PIPE4_MIXER1); + mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE4(stage) | + COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE4_MIXER1); + break; + case VG3: + mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE5__MASK | + MDP4_LAYERMIXER_IN_CFG_PIPE5_MIXER1); + mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE5(stage) | + COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE5_MIXER1); + break; + case VG4: + mixer_cfg &= ~(MDP4_LAYERMIXER_IN_CFG_PIPE6__MASK | + MDP4_LAYERMIXER_IN_CFG_PIPE6_MIXER1); + mixer_cfg |= MDP4_LAYERMIXER_IN_CFG_PIPE6(stage) | + COND(mixer == 1, MDP4_LAYERMIXER_IN_CFG_PIPE6_MIXER1); + break; + default: + WARN(1, "invalid pipe"); + break; + } + + return mixer_cfg; +} + +int mdp4_disable(struct mdp4_kms *mdp4_kms); +int mdp4_enable(struct mdp4_kms *mdp4_kms); + +void mdp4_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask, + uint32_t old_irqmask); +void mdp4_irq_preinstall(struct msm_kms *kms); +int mdp4_irq_postinstall(struct msm_kms *kms); +void mdp4_irq_uninstall(struct msm_kms *kms); +irqreturn_t mdp4_irq(struct msm_kms *kms); +int mdp4_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc); +void mdp4_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc); + +static inline uint32_t mdp4_pipe_caps(enum mdp4_pipe pipe) +{ + switch (pipe) { + case VG1: + case VG2: + case VG3: + case VG4: + return MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | MDP_PIPE_CAP_CSC; + case RGB1: + case RGB2: + case RGB3: + return MDP_PIPE_CAP_SCALE; + default: + return 0; + } +} + +enum mdp4_pipe mdp4_plane_pipe(struct drm_plane *plane); +struct drm_plane *mdp4_plane_init(struct drm_device *dev, + enum mdp4_pipe pipe_id, bool private_plane); + +uint32_t mdp4_crtc_vblank(struct drm_crtc *crtc); +void mdp4_crtc_set_config(struct drm_crtc *crtc, uint32_t config); +void mdp4_crtc_set_intf(struct drm_crtc *crtc, enum mdp4_intf intf, int mixer); +void mdp4_crtc_wait_for_commit_done(struct drm_crtc *crtc); +struct drm_crtc *mdp4_crtc_init(struct drm_device *dev, + struct drm_plane *plane, int id, int ovlp_id, + enum mdp4_dma dma_id); + +long mdp4_dtv_round_pixclk(struct drm_encoder *encoder, unsigned long rate); +struct drm_encoder *mdp4_dtv_encoder_init(struct drm_device *dev); + +long mdp4_lcdc_round_pixclk(struct drm_encoder *encoder, unsigned long rate); +struct drm_encoder *mdp4_lcdc_encoder_init(struct drm_device *dev, + struct device_node *panel_node); + +struct drm_connector *mdp4_lvds_connector_init(struct drm_device *dev, + struct device_node *panel_node, struct drm_encoder *encoder); + +#ifdef CONFIG_DRM_MSM_DSI +struct drm_encoder *mdp4_dsi_encoder_init(struct drm_device *dev); +#else +static inline struct drm_encoder *mdp4_dsi_encoder_init(struct drm_device *dev) +{ + return ERR_PTR(-ENODEV); +} +#endif + +#ifdef CONFIG_COMMON_CLK +struct clk *mpd4_lvds_pll_init(struct drm_device *dev); +#else +static inline struct clk *mpd4_lvds_pll_init(struct drm_device *dev) +{ + return ERR_PTR(-ENODEV); +} +#endif + +#endif /* __MDP4_KMS_H__ */ diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_lcdc_encoder.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_lcdc_encoder.c new file mode 100644 index 000000000..10eb3e5b2 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_lcdc_encoder.c @@ -0,0 +1,445 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + * Author: Vinay Simha <vinaysimha@inforcecomputing.com> + */ + +#include <linux/delay.h> + +#include <drm/drm_crtc.h> +#include <drm/drm_probe_helper.h> + +#include "mdp4_kms.h" + +struct mdp4_lcdc_encoder { + struct drm_encoder base; + struct device_node *panel_node; + struct drm_panel *panel; + struct clk *lcdc_clk; + unsigned long int pixclock; + struct regulator *regs[3]; + bool enabled; + uint32_t bsc; +}; +#define to_mdp4_lcdc_encoder(x) container_of(x, struct mdp4_lcdc_encoder, base) + +static struct mdp4_kms *get_kms(struct drm_encoder *encoder) +{ + struct msm_drm_private *priv = encoder->dev->dev_private; + return to_mdp4_kms(to_mdp_kms(priv->kms)); +} + +static void mdp4_lcdc_encoder_destroy(struct drm_encoder *encoder) +{ + struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = + to_mdp4_lcdc_encoder(encoder); + drm_encoder_cleanup(encoder); + kfree(mdp4_lcdc_encoder); +} + +static const struct drm_encoder_funcs mdp4_lcdc_encoder_funcs = { + .destroy = mdp4_lcdc_encoder_destroy, +}; + +/* this should probably be a helper: */ +static struct drm_connector *get_connector(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + + list_for_each_entry(connector, &dev->mode_config.connector_list, head) + if (connector->encoder == encoder) + return connector; + + return NULL; +} + +static void setup_phy(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct drm_connector *connector = get_connector(encoder); + struct mdp4_kms *mdp4_kms = get_kms(encoder); + uint32_t lvds_intf = 0, lvds_phy_cfg0 = 0; + int bpp, nchan, swap; + + if (!connector) + return; + + bpp = 3 * connector->display_info.bpc; + + if (!bpp) + bpp = 18; + + /* TODO, these should come from panel somehow: */ + nchan = 1; + swap = 0; + + switch (bpp) { + case 24: + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(0), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x08) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x05) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x04) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x03)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(0), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x02) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x01) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x00)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(1), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x11) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x10) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x0d) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0c)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(1), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0b) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x0a) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x09)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(2), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1a) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x19) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x18) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x15)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(2), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x14) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x13) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x12)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(3), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1b) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x17) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x16) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0f)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(3), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0e) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x07) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x06)); + if (nchan == 2) { + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE3_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE2_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE1_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE0_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE3_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN; + } else { + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE3_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN; + } + break; + + case 18: + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(0), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x0a) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x07) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x06) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x05)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(0), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x04) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x03) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x02)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(1), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x13) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x12) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x0f) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x0e)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(1), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x0d) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x0c) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x0b)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_3_TO_0(2), + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT0(0x1a) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT1(0x19) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT2(0x18) | + MDP4_LCDC_LVDS_MUX_CTL_3_TO_0_BIT3(0x17)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_MUX_CTL_6_TO_4(2), + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT4(0x16) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT5(0x15) | + MDP4_LCDC_LVDS_MUX_CTL_6_TO_4_BIT6(0x14)); + if (nchan == 2) { + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE2_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE1_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH2_DATA_LANE0_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN; + } else { + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE2_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE1_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_DATA_LANE0_EN; + } + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_RGB_OUT; + break; + + default: + DRM_DEV_ERROR(dev->dev, "unknown bpp: %d\n", bpp); + return; + } + + switch (nchan) { + case 1: + lvds_phy_cfg0 = MDP4_LVDS_PHY_CFG0_CHANNEL0; + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH1_CLK_LANE_EN | + MDP4_LCDC_LVDS_INTF_CTL_MODE_SEL; + break; + case 2: + lvds_phy_cfg0 = MDP4_LVDS_PHY_CFG0_CHANNEL0 | + MDP4_LVDS_PHY_CFG0_CHANNEL1; + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH2_CLK_LANE_EN | + MDP4_LCDC_LVDS_INTF_CTL_CH1_CLK_LANE_EN; + break; + default: + DRM_DEV_ERROR(dev->dev, "unknown # of channels: %d\n", nchan); + return; + } + + if (swap) + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_CH_SWAP; + + lvds_intf |= MDP4_LCDC_LVDS_INTF_CTL_ENABLE; + + mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, lvds_phy_cfg0); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_INTF_CTL, lvds_intf); + mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG2, 0x30); + + mb(); + udelay(1); + lvds_phy_cfg0 |= MDP4_LVDS_PHY_CFG0_SERIALIZATION_ENBLE; + mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, lvds_phy_cfg0); +} + +static void mdp4_lcdc_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = + to_mdp4_lcdc_encoder(encoder); + struct mdp4_kms *mdp4_kms = get_kms(encoder); + uint32_t lcdc_hsync_skew, vsync_period, vsync_len, ctrl_pol; + uint32_t display_v_start, display_v_end; + uint32_t hsync_start_x, hsync_end_x; + + mode = adjusted_mode; + + DBG("set mode: " DRM_MODE_FMT, DRM_MODE_ARG(mode)); + + mdp4_lcdc_encoder->pixclock = mode->clock * 1000; + + DBG("pixclock=%lu", mdp4_lcdc_encoder->pixclock); + + ctrl_pol = 0; + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + ctrl_pol |= MDP4_LCDC_CTRL_POLARITY_HSYNC_LOW; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ctrl_pol |= MDP4_LCDC_CTRL_POLARITY_VSYNC_LOW; + /* probably need to get DATA_EN polarity from panel.. */ + + lcdc_hsync_skew = 0; /* get this from panel? */ + + hsync_start_x = (mode->htotal - mode->hsync_start); + hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1; + + vsync_period = mode->vtotal * mode->htotal; + vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal; + display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + lcdc_hsync_skew; + display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + lcdc_hsync_skew - 1; + + mdp4_write(mdp4_kms, REG_MDP4_LCDC_HSYNC_CTRL, + MDP4_LCDC_HSYNC_CTRL_PULSEW(mode->hsync_end - mode->hsync_start) | + MDP4_LCDC_HSYNC_CTRL_PERIOD(mode->htotal)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_VSYNC_PERIOD, vsync_period); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_VSYNC_LEN, vsync_len); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_HCTRL, + MDP4_LCDC_DISPLAY_HCTRL_START(hsync_start_x) | + MDP4_LCDC_DISPLAY_HCTRL_END(hsync_end_x)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_VSTART, display_v_start); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_DISPLAY_VEND, display_v_end); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_BORDER_CLR, 0); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_UNDERFLOW_CLR, + MDP4_LCDC_UNDERFLOW_CLR_ENABLE_RECOVERY | + MDP4_LCDC_UNDERFLOW_CLR_COLOR(0xff)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_HSYNC_SKEW, lcdc_hsync_skew); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_CTRL_POLARITY, ctrl_pol); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_HCTL, + MDP4_LCDC_ACTIVE_HCTL_START(0) | + MDP4_LCDC_ACTIVE_HCTL_END(0)); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_VSTART, 0); + mdp4_write(mdp4_kms, REG_MDP4_LCDC_ACTIVE_VEND, 0); +} + +static void mdp4_lcdc_encoder_disable(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = + to_mdp4_lcdc_encoder(encoder); + struct mdp4_kms *mdp4_kms = get_kms(encoder); + struct drm_panel *panel; + int i, ret; + + if (WARN_ON(!mdp4_lcdc_encoder->enabled)) + return; + + mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 0); + + panel = of_drm_find_panel(mdp4_lcdc_encoder->panel_node); + if (!IS_ERR(panel)) { + drm_panel_disable(panel); + drm_panel_unprepare(panel); + } + + /* + * Wait for a vsync so we know the ENABLE=0 latched before + * the (connector) source of the vsync's gets disabled, + * otherwise we end up in a funny state if we re-enable + * before the disable latches, which results that some of + * the settings changes for the new modeset (like new + * scanout buffer) don't latch properly.. + */ + mdp_irq_wait(&mdp4_kms->base, MDP4_IRQ_PRIMARY_VSYNC); + + clk_disable_unprepare(mdp4_lcdc_encoder->lcdc_clk); + + for (i = 0; i < ARRAY_SIZE(mdp4_lcdc_encoder->regs); i++) { + ret = regulator_disable(mdp4_lcdc_encoder->regs[i]); + if (ret) + DRM_DEV_ERROR(dev->dev, "failed to disable regulator: %d\n", ret); + } + + mdp4_lcdc_encoder->enabled = false; +} + +static void mdp4_lcdc_encoder_enable(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = + to_mdp4_lcdc_encoder(encoder); + unsigned long pc = mdp4_lcdc_encoder->pixclock; + struct mdp4_kms *mdp4_kms = get_kms(encoder); + struct drm_panel *panel; + uint32_t config; + int i, ret; + + if (WARN_ON(mdp4_lcdc_encoder->enabled)) + return; + + /* TODO: hard-coded for 18bpp: */ + config = + MDP4_DMA_CONFIG_R_BPC(BPC6) | + MDP4_DMA_CONFIG_G_BPC(BPC6) | + MDP4_DMA_CONFIG_B_BPC(BPC6) | + MDP4_DMA_CONFIG_PACK(0x21) | + MDP4_DMA_CONFIG_DEFLKR_EN | + MDP4_DMA_CONFIG_DITHER_EN; + + if (!of_property_read_bool(dev->dev->of_node, "qcom,lcdc-align-lsb")) + config |= MDP4_DMA_CONFIG_PACK_ALIGN_MSB; + + mdp4_crtc_set_config(encoder->crtc, config); + mdp4_crtc_set_intf(encoder->crtc, INTF_LCDC_DTV, 0); + + for (i = 0; i < ARRAY_SIZE(mdp4_lcdc_encoder->regs); i++) { + ret = regulator_enable(mdp4_lcdc_encoder->regs[i]); + if (ret) + DRM_DEV_ERROR(dev->dev, "failed to enable regulator: %d\n", ret); + } + + DBG("setting lcdc_clk=%lu", pc); + ret = clk_set_rate(mdp4_lcdc_encoder->lcdc_clk, pc); + if (ret) + DRM_DEV_ERROR(dev->dev, "failed to configure lcdc_clk: %d\n", ret); + ret = clk_prepare_enable(mdp4_lcdc_encoder->lcdc_clk); + if (ret) + DRM_DEV_ERROR(dev->dev, "failed to enable lcdc_clk: %d\n", ret); + + panel = of_drm_find_panel(mdp4_lcdc_encoder->panel_node); + if (!IS_ERR(panel)) { + drm_panel_prepare(panel); + drm_panel_enable(panel); + } + + setup_phy(encoder); + + mdp4_write(mdp4_kms, REG_MDP4_LCDC_ENABLE, 1); + + mdp4_lcdc_encoder->enabled = true; +} + +static const struct drm_encoder_helper_funcs mdp4_lcdc_encoder_helper_funcs = { + .mode_set = mdp4_lcdc_encoder_mode_set, + .disable = mdp4_lcdc_encoder_disable, + .enable = mdp4_lcdc_encoder_enable, +}; + +long mdp4_lcdc_round_pixclk(struct drm_encoder *encoder, unsigned long rate) +{ + struct mdp4_lcdc_encoder *mdp4_lcdc_encoder = + to_mdp4_lcdc_encoder(encoder); + return clk_round_rate(mdp4_lcdc_encoder->lcdc_clk, rate); +} + +/* initialize encoder */ +struct drm_encoder *mdp4_lcdc_encoder_init(struct drm_device *dev, + struct device_node *panel_node) +{ + struct drm_encoder *encoder = NULL; + struct mdp4_lcdc_encoder *mdp4_lcdc_encoder; + struct regulator *reg; + int ret; + + mdp4_lcdc_encoder = kzalloc(sizeof(*mdp4_lcdc_encoder), GFP_KERNEL); + if (!mdp4_lcdc_encoder) { + ret = -ENOMEM; + goto fail; + } + + mdp4_lcdc_encoder->panel_node = panel_node; + + encoder = &mdp4_lcdc_encoder->base; + + drm_encoder_init(dev, encoder, &mdp4_lcdc_encoder_funcs, + DRM_MODE_ENCODER_LVDS, NULL); + drm_encoder_helper_add(encoder, &mdp4_lcdc_encoder_helper_funcs); + + /* TODO: do we need different pll in other cases? */ + mdp4_lcdc_encoder->lcdc_clk = mpd4_lvds_pll_init(dev); + if (IS_ERR(mdp4_lcdc_encoder->lcdc_clk)) { + DRM_DEV_ERROR(dev->dev, "failed to get lvds_clk\n"); + ret = PTR_ERR(mdp4_lcdc_encoder->lcdc_clk); + goto fail; + } + + /* TODO: different regulators in other cases? */ + reg = devm_regulator_get(dev->dev, "lvds-vccs-3p3v"); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + DRM_DEV_ERROR(dev->dev, "failed to get lvds-vccs-3p3v: %d\n", ret); + goto fail; + } + mdp4_lcdc_encoder->regs[0] = reg; + + reg = devm_regulator_get(dev->dev, "lvds-pll-vdda"); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + DRM_DEV_ERROR(dev->dev, "failed to get lvds-pll-vdda: %d\n", ret); + goto fail; + } + mdp4_lcdc_encoder->regs[1] = reg; + + reg = devm_regulator_get(dev->dev, "lvds-vdda"); + if (IS_ERR(reg)) { + ret = PTR_ERR(reg); + DRM_DEV_ERROR(dev->dev, "failed to get lvds-vdda: %d\n", ret); + goto fail; + } + mdp4_lcdc_encoder->regs[2] = reg; + + return encoder; + +fail: + if (encoder) + mdp4_lcdc_encoder_destroy(encoder); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_lvds_connector.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_lvds_connector.c new file mode 100644 index 000000000..7444b75c4 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_lvds_connector.c @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + * Author: Vinay Simha <vinaysimha@inforcecomputing.com> + */ + +#include "mdp4_kms.h" + +struct mdp4_lvds_connector { + struct drm_connector base; + struct drm_encoder *encoder; + struct device_node *panel_node; + struct drm_panel *panel; +}; +#define to_mdp4_lvds_connector(x) container_of(x, struct mdp4_lvds_connector, base) + +static enum drm_connector_status mdp4_lvds_connector_detect( + struct drm_connector *connector, bool force) +{ + struct mdp4_lvds_connector *mdp4_lvds_connector = + to_mdp4_lvds_connector(connector); + + if (!mdp4_lvds_connector->panel) { + mdp4_lvds_connector->panel = + of_drm_find_panel(mdp4_lvds_connector->panel_node); + if (IS_ERR(mdp4_lvds_connector->panel)) + mdp4_lvds_connector->panel = NULL; + } + + return mdp4_lvds_connector->panel ? + connector_status_connected : + connector_status_disconnected; +} + +static void mdp4_lvds_connector_destroy(struct drm_connector *connector) +{ + struct mdp4_lvds_connector *mdp4_lvds_connector = + to_mdp4_lvds_connector(connector); + + drm_connector_cleanup(connector); + + kfree(mdp4_lvds_connector); +} + +static int mdp4_lvds_connector_get_modes(struct drm_connector *connector) +{ + struct mdp4_lvds_connector *mdp4_lvds_connector = + to_mdp4_lvds_connector(connector); + struct drm_panel *panel = mdp4_lvds_connector->panel; + int ret = 0; + + if (panel) + ret = drm_panel_get_modes(panel, connector); + + return ret; +} + +static enum drm_mode_status +mdp4_lvds_connector_mode_valid(struct drm_connector *connector, + struct drm_display_mode *mode) +{ + struct mdp4_lvds_connector *mdp4_lvds_connector = + to_mdp4_lvds_connector(connector); + struct drm_encoder *encoder = mdp4_lvds_connector->encoder; + long actual, requested; + + requested = 1000 * mode->clock; + actual = mdp4_lcdc_round_pixclk(encoder, requested); + + DBG("requested=%ld, actual=%ld", requested, actual); + + if (actual != requested) + return MODE_CLOCK_RANGE; + + return MODE_OK; +} + +static const struct drm_connector_funcs mdp4_lvds_connector_funcs = { + .detect = mdp4_lvds_connector_detect, + .fill_modes = drm_helper_probe_single_connector_modes, + .destroy = mdp4_lvds_connector_destroy, + .reset = drm_atomic_helper_connector_reset, + .atomic_duplicate_state = drm_atomic_helper_connector_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_connector_destroy_state, +}; + +static const struct drm_connector_helper_funcs mdp4_lvds_connector_helper_funcs = { + .get_modes = mdp4_lvds_connector_get_modes, + .mode_valid = mdp4_lvds_connector_mode_valid, +}; + +/* initialize connector */ +struct drm_connector *mdp4_lvds_connector_init(struct drm_device *dev, + struct device_node *panel_node, struct drm_encoder *encoder) +{ + struct drm_connector *connector = NULL; + struct mdp4_lvds_connector *mdp4_lvds_connector; + + mdp4_lvds_connector = kzalloc(sizeof(*mdp4_lvds_connector), GFP_KERNEL); + if (!mdp4_lvds_connector) + return ERR_PTR(-ENOMEM); + + mdp4_lvds_connector->encoder = encoder; + mdp4_lvds_connector->panel_node = panel_node; + + connector = &mdp4_lvds_connector->base; + + drm_connector_init(dev, connector, &mdp4_lvds_connector_funcs, + DRM_MODE_CONNECTOR_LVDS); + drm_connector_helper_add(connector, &mdp4_lvds_connector_helper_funcs); + + connector->polled = 0; + + connector->interlace_allowed = 0; + connector->doublescan_allowed = 0; + + drm_connector_attach_encoder(connector, encoder); + + return connector; +} diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_lvds_pll.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_lvds_pll.c new file mode 100644 index 000000000..ab8c0c187 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_lvds_pll.c @@ -0,0 +1,161 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <linux/clk.h> +#include <linux/clk-provider.h> + +#include "mdp4_kms.h" + +struct mdp4_lvds_pll { + struct clk_hw pll_hw; + struct drm_device *dev; + unsigned long pixclk; +}; +#define to_mdp4_lvds_pll(x) container_of(x, struct mdp4_lvds_pll, pll_hw) + +static struct mdp4_kms *get_kms(struct mdp4_lvds_pll *lvds_pll) +{ + struct msm_drm_private *priv = lvds_pll->dev->dev_private; + return to_mdp4_kms(to_mdp_kms(priv->kms)); +} + +struct pll_rate { + unsigned long rate; + struct { + uint32_t val; + uint32_t reg; + } conf[32]; +}; + +/* NOTE: keep sorted highest freq to lowest: */ +static const struct pll_rate freqtbl[] = { + { 72000000, { + { 0x8f, REG_MDP4_LVDS_PHY_PLL_CTRL_1 }, + { 0x30, REG_MDP4_LVDS_PHY_PLL_CTRL_2 }, + { 0xc6, REG_MDP4_LVDS_PHY_PLL_CTRL_3 }, + { 0x10, REG_MDP4_LVDS_PHY_PLL_CTRL_5 }, + { 0x07, REG_MDP4_LVDS_PHY_PLL_CTRL_6 }, + { 0x62, REG_MDP4_LVDS_PHY_PLL_CTRL_7 }, + { 0x41, REG_MDP4_LVDS_PHY_PLL_CTRL_8 }, + { 0x0d, REG_MDP4_LVDS_PHY_PLL_CTRL_9 }, + { 0, 0 } } + }, +}; + +static const struct pll_rate *find_rate(unsigned long rate) +{ + int i; + for (i = 1; i < ARRAY_SIZE(freqtbl); i++) + if (rate > freqtbl[i].rate) + return &freqtbl[i-1]; + return &freqtbl[i-1]; +} + +static int mpd4_lvds_pll_enable(struct clk_hw *hw) +{ + struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); + struct mdp4_kms *mdp4_kms = get_kms(lvds_pll); + const struct pll_rate *pll_rate = find_rate(lvds_pll->pixclk); + int i; + + DBG("pixclk=%lu (%lu)", lvds_pll->pixclk, pll_rate->rate); + + if (WARN_ON(!pll_rate)) + return -EINVAL; + + mdp4_write(mdp4_kms, REG_MDP4_LCDC_LVDS_PHY_RESET, 0x33); + + for (i = 0; pll_rate->conf[i].reg; i++) + mdp4_write(mdp4_kms, pll_rate->conf[i].reg, pll_rate->conf[i].val); + + mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_CTRL_0, 0x01); + + /* Wait until LVDS PLL is locked and ready */ + while (!mdp4_read(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_LOCKED)) + cpu_relax(); + + return 0; +} + +static void mpd4_lvds_pll_disable(struct clk_hw *hw) +{ + struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); + struct mdp4_kms *mdp4_kms = get_kms(lvds_pll); + + DBG(""); + + mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_CFG0, 0x0); + mdp4_write(mdp4_kms, REG_MDP4_LVDS_PHY_PLL_CTRL_0, 0x0); +} + +static unsigned long mpd4_lvds_pll_recalc_rate(struct clk_hw *hw, + unsigned long parent_rate) +{ + struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); + return lvds_pll->pixclk; +} + +static long mpd4_lvds_pll_round_rate(struct clk_hw *hw, unsigned long rate, + unsigned long *parent_rate) +{ + const struct pll_rate *pll_rate = find_rate(rate); + return pll_rate->rate; +} + +static int mpd4_lvds_pll_set_rate(struct clk_hw *hw, unsigned long rate, + unsigned long parent_rate) +{ + struct mdp4_lvds_pll *lvds_pll = to_mdp4_lvds_pll(hw); + lvds_pll->pixclk = rate; + return 0; +} + + +static const struct clk_ops mpd4_lvds_pll_ops = { + .enable = mpd4_lvds_pll_enable, + .disable = mpd4_lvds_pll_disable, + .recalc_rate = mpd4_lvds_pll_recalc_rate, + .round_rate = mpd4_lvds_pll_round_rate, + .set_rate = mpd4_lvds_pll_set_rate, +}; + +static const char *mpd4_lvds_pll_parents[] = { + "pxo", +}; + +static struct clk_init_data pll_init = { + .name = "mpd4_lvds_pll", + .ops = &mpd4_lvds_pll_ops, + .parent_names = mpd4_lvds_pll_parents, + .num_parents = ARRAY_SIZE(mpd4_lvds_pll_parents), +}; + +struct clk *mpd4_lvds_pll_init(struct drm_device *dev) +{ + struct mdp4_lvds_pll *lvds_pll; + struct clk *clk; + int ret; + + lvds_pll = devm_kzalloc(dev->dev, sizeof(*lvds_pll), GFP_KERNEL); + if (!lvds_pll) { + ret = -ENOMEM; + goto fail; + } + + lvds_pll->dev = dev; + + lvds_pll->pll_hw.init = &pll_init; + clk = devm_clk_register(dev->dev, &lvds_pll->pll_hw); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + goto fail; + } + + return clk; + +fail: + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c b/drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c new file mode 100644 index 000000000..ee1dbb2b8 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp4/mdp4_plane.c @@ -0,0 +1,401 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <drm/drm_damage_helper.h> +#include <drm/drm_fourcc.h> + +#include "mdp4_kms.h" + +#define DOWN_SCALE_MAX 8 +#define UP_SCALE_MAX 8 + +struct mdp4_plane { + struct drm_plane base; + const char *name; + + enum mdp4_pipe pipe; + + uint32_t caps; + uint32_t nformats; + uint32_t formats[32]; + + bool enabled; +}; +#define to_mdp4_plane(x) container_of(x, struct mdp4_plane, base) + +/* MDP format helper functions */ +static inline +enum mdp4_frame_format mdp4_get_frame_format(struct drm_framebuffer *fb) +{ + bool is_tile = false; + + if (fb->modifier == DRM_FORMAT_MOD_SAMSUNG_64_32_TILE) + is_tile = true; + + if (fb->format->format == DRM_FORMAT_NV12 && is_tile) + return FRAME_TILE_YCBCR_420; + + return FRAME_LINEAR; +} + +static void mdp4_plane_set_scanout(struct drm_plane *plane, + struct drm_framebuffer *fb); +static int mdp4_plane_mode_set(struct drm_plane *plane, + struct drm_crtc *crtc, struct drm_framebuffer *fb, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h); + +static struct mdp4_kms *get_kms(struct drm_plane *plane) +{ + struct msm_drm_private *priv = plane->dev->dev_private; + return to_mdp4_kms(to_mdp_kms(priv->kms)); +} + +static void mdp4_plane_destroy(struct drm_plane *plane) +{ + struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane); + + drm_plane_cleanup(plane); + + kfree(mdp4_plane); +} + +/* helper to install properties which are common to planes and crtcs */ +static void mdp4_plane_install_properties(struct drm_plane *plane, + struct drm_mode_object *obj) +{ + // XXX +} + +static int mdp4_plane_set_property(struct drm_plane *plane, + struct drm_property *property, uint64_t val) +{ + // XXX + return -EINVAL; +} + +static const struct drm_plane_funcs mdp4_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = mdp4_plane_destroy, + .set_property = mdp4_plane_set_property, + .reset = drm_atomic_helper_plane_reset, + .atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state, + .atomic_destroy_state = drm_atomic_helper_plane_destroy_state, +}; + +static void mdp4_plane_cleanup_fb(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane); + struct mdp4_kms *mdp4_kms = get_kms(plane); + struct msm_kms *kms = &mdp4_kms->base.base; + struct drm_framebuffer *fb = old_state->fb; + + if (!fb) + return; + + DBG("%s: cleanup: FB[%u]", mdp4_plane->name, fb->base.id); + msm_framebuffer_cleanup(fb, kms->aspace); +} + + +static int mdp4_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + return 0; +} + +static void mdp4_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct drm_plane_state *state = plane->state; + int ret; + + ret = mdp4_plane_mode_set(plane, + state->crtc, state->fb, + state->crtc_x, state->crtc_y, + state->crtc_w, state->crtc_h, + state->src_x, state->src_y, + state->src_w, state->src_h); + /* atomic_check should have ensured that this doesn't fail */ + WARN_ON(ret < 0); +} + +static const struct drm_plane_helper_funcs mdp4_plane_helper_funcs = { + .prepare_fb = msm_atomic_prepare_fb, + .cleanup_fb = mdp4_plane_cleanup_fb, + .atomic_check = mdp4_plane_atomic_check, + .atomic_update = mdp4_plane_atomic_update, +}; + +static void mdp4_plane_set_scanout(struct drm_plane *plane, + struct drm_framebuffer *fb) +{ + struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane); + struct mdp4_kms *mdp4_kms = get_kms(plane); + struct msm_kms *kms = &mdp4_kms->base.base; + enum mdp4_pipe pipe = mdp4_plane->pipe; + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_STRIDE_A(pipe), + MDP4_PIPE_SRC_STRIDE_A_P0(fb->pitches[0]) | + MDP4_PIPE_SRC_STRIDE_A_P1(fb->pitches[1])); + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_STRIDE_B(pipe), + MDP4_PIPE_SRC_STRIDE_B_P2(fb->pitches[2]) | + MDP4_PIPE_SRC_STRIDE_B_P3(fb->pitches[3])); + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRCP0_BASE(pipe), + msm_framebuffer_iova(fb, kms->aspace, 0)); + mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRCP1_BASE(pipe), + msm_framebuffer_iova(fb, kms->aspace, 1)); + mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRCP2_BASE(pipe), + msm_framebuffer_iova(fb, kms->aspace, 2)); + mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRCP3_BASE(pipe), + msm_framebuffer_iova(fb, kms->aspace, 3)); +} + +static void mdp4_write_csc_config(struct mdp4_kms *mdp4_kms, + enum mdp4_pipe pipe, struct csc_cfg *csc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(csc->matrix); i++) { + mdp4_write(mdp4_kms, REG_MDP4_PIPE_CSC_MV(pipe, i), + csc->matrix[i]); + } + + for (i = 0; i < ARRAY_SIZE(csc->post_bias) ; i++) { + mdp4_write(mdp4_kms, REG_MDP4_PIPE_CSC_PRE_BV(pipe, i), + csc->pre_bias[i]); + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_CSC_POST_BV(pipe, i), + csc->post_bias[i]); + } + + for (i = 0; i < ARRAY_SIZE(csc->post_clamp) ; i++) { + mdp4_write(mdp4_kms, REG_MDP4_PIPE_CSC_PRE_LV(pipe, i), + csc->pre_clamp[i]); + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_CSC_POST_LV(pipe, i), + csc->post_clamp[i]); + } +} + +#define MDP4_VG_PHASE_STEP_DEFAULT 0x20000000 + +static int mdp4_plane_mode_set(struct drm_plane *plane, + struct drm_crtc *crtc, struct drm_framebuffer *fb, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + uint32_t src_x, uint32_t src_y, + uint32_t src_w, uint32_t src_h) +{ + struct drm_device *dev = plane->dev; + struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane); + struct mdp4_kms *mdp4_kms = get_kms(plane); + enum mdp4_pipe pipe = mdp4_plane->pipe; + const struct mdp_format *format; + uint32_t op_mode = 0; + uint32_t phasex_step = MDP4_VG_PHASE_STEP_DEFAULT; + uint32_t phasey_step = MDP4_VG_PHASE_STEP_DEFAULT; + enum mdp4_frame_format frame_type; + + if (!(crtc && fb)) { + DBG("%s: disabled!", mdp4_plane->name); + return 0; + } + + frame_type = mdp4_get_frame_format(fb); + + /* src values are in Q16 fixed point, convert to integer: */ + src_x = src_x >> 16; + src_y = src_y >> 16; + src_w = src_w >> 16; + src_h = src_h >> 16; + + DBG("%s: FB[%u] %u,%u,%u,%u -> CRTC[%u] %d,%d,%u,%u", mdp4_plane->name, + fb->base.id, src_x, src_y, src_w, src_h, + crtc->base.id, crtc_x, crtc_y, crtc_w, crtc_h); + + format = to_mdp_format(msm_framebuffer_format(fb)); + + if (src_w > (crtc_w * DOWN_SCALE_MAX)) { + DRM_DEV_ERROR(dev->dev, "Width down scaling exceeds limits!\n"); + return -ERANGE; + } + + if (src_h > (crtc_h * DOWN_SCALE_MAX)) { + DRM_DEV_ERROR(dev->dev, "Height down scaling exceeds limits!\n"); + return -ERANGE; + } + + if (crtc_w > (src_w * UP_SCALE_MAX)) { + DRM_DEV_ERROR(dev->dev, "Width up scaling exceeds limits!\n"); + return -ERANGE; + } + + if (crtc_h > (src_h * UP_SCALE_MAX)) { + DRM_DEV_ERROR(dev->dev, "Height up scaling exceeds limits!\n"); + return -ERANGE; + } + + if (src_w != crtc_w) { + uint32_t sel_unit = SCALE_FIR; + op_mode |= MDP4_PIPE_OP_MODE_SCALEX_EN; + + if (MDP_FORMAT_IS_YUV(format)) { + if (crtc_w > src_w) + sel_unit = SCALE_PIXEL_RPT; + else if (crtc_w <= (src_w / 4)) + sel_unit = SCALE_MN_PHASE; + + op_mode |= MDP4_PIPE_OP_MODE_SCALEX_UNIT_SEL(sel_unit); + phasex_step = mult_frac(MDP4_VG_PHASE_STEP_DEFAULT, + src_w, crtc_w); + } + } + + if (src_h != crtc_h) { + uint32_t sel_unit = SCALE_FIR; + op_mode |= MDP4_PIPE_OP_MODE_SCALEY_EN; + + if (MDP_FORMAT_IS_YUV(format)) { + + if (crtc_h > src_h) + sel_unit = SCALE_PIXEL_RPT; + else if (crtc_h <= (src_h / 4)) + sel_unit = SCALE_MN_PHASE; + + op_mode |= MDP4_PIPE_OP_MODE_SCALEY_UNIT_SEL(sel_unit); + phasey_step = mult_frac(MDP4_VG_PHASE_STEP_DEFAULT, + src_h, crtc_h); + } + } + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_SIZE(pipe), + MDP4_PIPE_SRC_SIZE_WIDTH(src_w) | + MDP4_PIPE_SRC_SIZE_HEIGHT(src_h)); + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_XY(pipe), + MDP4_PIPE_SRC_XY_X(src_x) | + MDP4_PIPE_SRC_XY_Y(src_y)); + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_DST_SIZE(pipe), + MDP4_PIPE_DST_SIZE_WIDTH(crtc_w) | + MDP4_PIPE_DST_SIZE_HEIGHT(crtc_h)); + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_DST_XY(pipe), + MDP4_PIPE_DST_XY_X(crtc_x) | + MDP4_PIPE_DST_XY_Y(crtc_y)); + + mdp4_plane_set_scanout(plane, fb); + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_FORMAT(pipe), + MDP4_PIPE_SRC_FORMAT_A_BPC(format->bpc_a) | + MDP4_PIPE_SRC_FORMAT_R_BPC(format->bpc_r) | + MDP4_PIPE_SRC_FORMAT_G_BPC(format->bpc_g) | + MDP4_PIPE_SRC_FORMAT_B_BPC(format->bpc_b) | + COND(format->alpha_enable, MDP4_PIPE_SRC_FORMAT_ALPHA_ENABLE) | + MDP4_PIPE_SRC_FORMAT_CPP(format->cpp - 1) | + MDP4_PIPE_SRC_FORMAT_UNPACK_COUNT(format->unpack_count - 1) | + MDP4_PIPE_SRC_FORMAT_FETCH_PLANES(format->fetch_type) | + MDP4_PIPE_SRC_FORMAT_CHROMA_SAMP(format->chroma_sample) | + MDP4_PIPE_SRC_FORMAT_FRAME_FORMAT(frame_type) | + COND(format->unpack_tight, MDP4_PIPE_SRC_FORMAT_UNPACK_TIGHT)); + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_SRC_UNPACK(pipe), + MDP4_PIPE_SRC_UNPACK_ELEM0(format->unpack[0]) | + MDP4_PIPE_SRC_UNPACK_ELEM1(format->unpack[1]) | + MDP4_PIPE_SRC_UNPACK_ELEM2(format->unpack[2]) | + MDP4_PIPE_SRC_UNPACK_ELEM3(format->unpack[3])); + + if (MDP_FORMAT_IS_YUV(format)) { + struct csc_cfg *csc = mdp_get_default_csc_cfg(CSC_YUV2RGB); + + op_mode |= MDP4_PIPE_OP_MODE_SRC_YCBCR; + op_mode |= MDP4_PIPE_OP_MODE_CSC_EN; + mdp4_write_csc_config(mdp4_kms, pipe, csc); + } + + mdp4_write(mdp4_kms, REG_MDP4_PIPE_OP_MODE(pipe), op_mode); + mdp4_write(mdp4_kms, REG_MDP4_PIPE_PHASEX_STEP(pipe), phasex_step); + mdp4_write(mdp4_kms, REG_MDP4_PIPE_PHASEY_STEP(pipe), phasey_step); + + if (frame_type != FRAME_LINEAR) + mdp4_write(mdp4_kms, REG_MDP4_PIPE_SSTILE_FRAME_SIZE(pipe), + MDP4_PIPE_SSTILE_FRAME_SIZE_WIDTH(src_w) | + MDP4_PIPE_SSTILE_FRAME_SIZE_HEIGHT(src_h)); + + return 0; +} + +static const char *pipe_names[] = { + "VG1", "VG2", + "RGB1", "RGB2", "RGB3", + "VG3", "VG4", +}; + +enum mdp4_pipe mdp4_plane_pipe(struct drm_plane *plane) +{ + struct mdp4_plane *mdp4_plane = to_mdp4_plane(plane); + return mdp4_plane->pipe; +} + +static const uint64_t supported_format_modifiers[] = { + DRM_FORMAT_MOD_SAMSUNG_64_32_TILE, + DRM_FORMAT_MOD_LINEAR, + DRM_FORMAT_MOD_INVALID +}; + +/* initialize plane */ +struct drm_plane *mdp4_plane_init(struct drm_device *dev, + enum mdp4_pipe pipe_id, bool private_plane) +{ + struct drm_plane *plane = NULL; + struct mdp4_plane *mdp4_plane; + int ret; + enum drm_plane_type type; + + mdp4_plane = kzalloc(sizeof(*mdp4_plane), GFP_KERNEL); + if (!mdp4_plane) { + ret = -ENOMEM; + goto fail; + } + + plane = &mdp4_plane->base; + + mdp4_plane->pipe = pipe_id; + mdp4_plane->name = pipe_names[pipe_id]; + mdp4_plane->caps = mdp4_pipe_caps(pipe_id); + + mdp4_plane->nformats = mdp_get_formats(mdp4_plane->formats, + ARRAY_SIZE(mdp4_plane->formats), + !pipe_supports_yuv(mdp4_plane->caps)); + + type = private_plane ? DRM_PLANE_TYPE_PRIMARY : DRM_PLANE_TYPE_OVERLAY; + ret = drm_universal_plane_init(dev, plane, 0xff, &mdp4_plane_funcs, + mdp4_plane->formats, mdp4_plane->nformats, + supported_format_modifiers, type, NULL); + if (ret) + goto fail; + + drm_plane_helper_add(plane, &mdp4_plane_helper_funcs); + + mdp4_plane_install_properties(plane, &plane->base); + + drm_plane_enable_fb_damage_clips(plane); + + return plane; + +fail: + if (plane) + mdp4_plane_destroy(plane); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h new file mode 100644 index 000000000..4cf095300 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5.xml.h @@ -0,0 +1,1968 @@ +#ifndef MDP5_XML +#define MDP5_XML + +/* Autogenerated file, DO NOT EDIT manually! + +This file was generated by the rules-ng-ng headergen tool in this git repository: +http://github.com/freedreno/envytools/ +git clone https://github.com/freedreno/envytools.git + +The rules-ng-ng source files this header was generated from are: +- /home/robclark/src/envytools/rnndb/msm.xml ( 676 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/freedreno_copyright.xml ( 1572 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/mdp/mdp4.xml ( 20915 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/mdp/mdp_common.xml ( 2849 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/mdp/mdp5.xml ( 37411 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/dsi/dsi.xml ( 42301 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/dsi/sfpb.xml ( 602 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/dsi/mmss_cc.xml ( 1686 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/hdmi/qfprom.xml ( 600 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/hdmi/hdmi.xml ( 41874 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/edp/edp.xml ( 10416 bytes, from 2020-07-23 21:58:14) + +Copyright (C) 2013-2020 by the following authors: +- Rob Clark <robdclark@gmail.com> (robclark) +- Ilia Mirkin <imirkin@alum.mit.edu> (imirkin) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice (including the +next paragraph) shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + + +enum mdp5_intf_type { + INTF_DISABLED = 0, + INTF_DSI = 1, + INTF_HDMI = 3, + INTF_LCDC = 5, + INTF_eDP = 9, + INTF_VIRTUAL = 100, + INTF_WB = 101, +}; + +enum mdp5_intfnum { + NO_INTF = 0, + INTF0 = 1, + INTF1 = 2, + INTF2 = 3, + INTF3 = 4, +}; + +enum mdp5_pipe { + SSPP_NONE = 0, + SSPP_VIG0 = 1, + SSPP_VIG1 = 2, + SSPP_VIG2 = 3, + SSPP_RGB0 = 4, + SSPP_RGB1 = 5, + SSPP_RGB2 = 6, + SSPP_DMA0 = 7, + SSPP_DMA1 = 8, + SSPP_VIG3 = 9, + SSPP_RGB3 = 10, + SSPP_CURSOR0 = 11, + SSPP_CURSOR1 = 12, +}; + +enum mdp5_ctl_mode { + MODE_NONE = 0, + MODE_WB_0_BLOCK = 1, + MODE_WB_1_BLOCK = 2, + MODE_WB_0_LINE = 3, + MODE_WB_1_LINE = 4, + MODE_WB_2_LINE = 5, +}; + +enum mdp5_pack_3d { + PACK_3D_FRAME_INT = 0, + PACK_3D_H_ROW_INT = 1, + PACK_3D_V_ROW_INT = 2, + PACK_3D_COL_INT = 3, +}; + +enum mdp5_scale_filter { + SCALE_FILTER_NEAREST = 0, + SCALE_FILTER_BIL = 1, + SCALE_FILTER_PCMN = 2, + SCALE_FILTER_CA = 3, +}; + +enum mdp5_pipe_bwc { + BWC_LOSSLESS = 0, + BWC_Q_HIGH = 1, + BWC_Q_MED = 2, +}; + +enum mdp5_cursor_format { + CURSOR_FMT_ARGB8888 = 0, + CURSOR_FMT_ARGB1555 = 2, + CURSOR_FMT_ARGB4444 = 4, +}; + +enum mdp5_cursor_alpha { + CURSOR_ALPHA_CONST = 0, + CURSOR_ALPHA_PER_PIXEL = 2, +}; + +enum mdp5_igc_type { + IGC_VIG = 0, + IGC_RGB = 1, + IGC_DMA = 2, + IGC_DSPP = 3, +}; + +enum mdp5_data_format { + DATA_FORMAT_RGB = 0, + DATA_FORMAT_YUV = 1, +}; + +enum mdp5_block_size { + BLOCK_SIZE_64 = 0, + BLOCK_SIZE_128 = 1, +}; + +enum mdp5_rotate_mode { + ROTATE_0 = 0, + ROTATE_90 = 1, +}; + +enum mdp5_chroma_downsample_method { + DS_MTHD_NO_PIXEL_DROP = 0, + DS_MTHD_PIXEL_DROP = 1, +}; + +#define MDP5_IRQ_WB_0_DONE 0x00000001 +#define MDP5_IRQ_WB_1_DONE 0x00000002 +#define MDP5_IRQ_WB_2_DONE 0x00000010 +#define MDP5_IRQ_PING_PONG_0_DONE 0x00000100 +#define MDP5_IRQ_PING_PONG_1_DONE 0x00000200 +#define MDP5_IRQ_PING_PONG_2_DONE 0x00000400 +#define MDP5_IRQ_PING_PONG_3_DONE 0x00000800 +#define MDP5_IRQ_PING_PONG_0_RD_PTR 0x00001000 +#define MDP5_IRQ_PING_PONG_1_RD_PTR 0x00002000 +#define MDP5_IRQ_PING_PONG_2_RD_PTR 0x00004000 +#define MDP5_IRQ_PING_PONG_3_RD_PTR 0x00008000 +#define MDP5_IRQ_PING_PONG_0_WR_PTR 0x00010000 +#define MDP5_IRQ_PING_PONG_1_WR_PTR 0x00020000 +#define MDP5_IRQ_PING_PONG_2_WR_PTR 0x00040000 +#define MDP5_IRQ_PING_PONG_3_WR_PTR 0x00080000 +#define MDP5_IRQ_PING_PONG_0_AUTO_REF 0x00100000 +#define MDP5_IRQ_PING_PONG_1_AUTO_REF 0x00200000 +#define MDP5_IRQ_PING_PONG_2_AUTO_REF 0x00400000 +#define MDP5_IRQ_PING_PONG_3_AUTO_REF 0x00800000 +#define MDP5_IRQ_INTF0_UNDER_RUN 0x01000000 +#define MDP5_IRQ_INTF0_VSYNC 0x02000000 +#define MDP5_IRQ_INTF1_UNDER_RUN 0x04000000 +#define MDP5_IRQ_INTF1_VSYNC 0x08000000 +#define MDP5_IRQ_INTF2_UNDER_RUN 0x10000000 +#define MDP5_IRQ_INTF2_VSYNC 0x20000000 +#define MDP5_IRQ_INTF3_UNDER_RUN 0x40000000 +#define MDP5_IRQ_INTF3_VSYNC 0x80000000 +#define REG_MDSS_HW_VERSION 0x00000000 +#define MDSS_HW_VERSION_STEP__MASK 0x0000ffff +#define MDSS_HW_VERSION_STEP__SHIFT 0 +static inline uint32_t MDSS_HW_VERSION_STEP(uint32_t val) +{ + return ((val) << MDSS_HW_VERSION_STEP__SHIFT) & MDSS_HW_VERSION_STEP__MASK; +} +#define MDSS_HW_VERSION_MINOR__MASK 0x0fff0000 +#define MDSS_HW_VERSION_MINOR__SHIFT 16 +static inline uint32_t MDSS_HW_VERSION_MINOR(uint32_t val) +{ + return ((val) << MDSS_HW_VERSION_MINOR__SHIFT) & MDSS_HW_VERSION_MINOR__MASK; +} +#define MDSS_HW_VERSION_MAJOR__MASK 0xf0000000 +#define MDSS_HW_VERSION_MAJOR__SHIFT 28 +static inline uint32_t MDSS_HW_VERSION_MAJOR(uint32_t val) +{ + return ((val) << MDSS_HW_VERSION_MAJOR__SHIFT) & MDSS_HW_VERSION_MAJOR__MASK; +} + +#define REG_MDSS_HW_INTR_STATUS 0x00000010 +#define MDSS_HW_INTR_STATUS_INTR_MDP 0x00000001 +#define MDSS_HW_INTR_STATUS_INTR_DSI0 0x00000010 +#define MDSS_HW_INTR_STATUS_INTR_DSI1 0x00000020 +#define MDSS_HW_INTR_STATUS_INTR_HDMI 0x00000100 +#define MDSS_HW_INTR_STATUS_INTR_EDP 0x00001000 + +#define REG_MDP5_HW_VERSION 0x00000000 +#define MDP5_HW_VERSION_STEP__MASK 0x0000ffff +#define MDP5_HW_VERSION_STEP__SHIFT 0 +static inline uint32_t MDP5_HW_VERSION_STEP(uint32_t val) +{ + return ((val) << MDP5_HW_VERSION_STEP__SHIFT) & MDP5_HW_VERSION_STEP__MASK; +} +#define MDP5_HW_VERSION_MINOR__MASK 0x0fff0000 +#define MDP5_HW_VERSION_MINOR__SHIFT 16 +static inline uint32_t MDP5_HW_VERSION_MINOR(uint32_t val) +{ + return ((val) << MDP5_HW_VERSION_MINOR__SHIFT) & MDP5_HW_VERSION_MINOR__MASK; +} +#define MDP5_HW_VERSION_MAJOR__MASK 0xf0000000 +#define MDP5_HW_VERSION_MAJOR__SHIFT 28 +static inline uint32_t MDP5_HW_VERSION_MAJOR(uint32_t val) +{ + return ((val) << MDP5_HW_VERSION_MAJOR__SHIFT) & MDP5_HW_VERSION_MAJOR__MASK; +} + +#define REG_MDP5_DISP_INTF_SEL 0x00000004 +#define MDP5_DISP_INTF_SEL_INTF0__MASK 0x000000ff +#define MDP5_DISP_INTF_SEL_INTF0__SHIFT 0 +static inline uint32_t MDP5_DISP_INTF_SEL_INTF0(enum mdp5_intf_type val) +{ + return ((val) << MDP5_DISP_INTF_SEL_INTF0__SHIFT) & MDP5_DISP_INTF_SEL_INTF0__MASK; +} +#define MDP5_DISP_INTF_SEL_INTF1__MASK 0x0000ff00 +#define MDP5_DISP_INTF_SEL_INTF1__SHIFT 8 +static inline uint32_t MDP5_DISP_INTF_SEL_INTF1(enum mdp5_intf_type val) +{ + return ((val) << MDP5_DISP_INTF_SEL_INTF1__SHIFT) & MDP5_DISP_INTF_SEL_INTF1__MASK; +} +#define MDP5_DISP_INTF_SEL_INTF2__MASK 0x00ff0000 +#define MDP5_DISP_INTF_SEL_INTF2__SHIFT 16 +static inline uint32_t MDP5_DISP_INTF_SEL_INTF2(enum mdp5_intf_type val) +{ + return ((val) << MDP5_DISP_INTF_SEL_INTF2__SHIFT) & MDP5_DISP_INTF_SEL_INTF2__MASK; +} +#define MDP5_DISP_INTF_SEL_INTF3__MASK 0xff000000 +#define MDP5_DISP_INTF_SEL_INTF3__SHIFT 24 +static inline uint32_t MDP5_DISP_INTF_SEL_INTF3(enum mdp5_intf_type val) +{ + return ((val) << MDP5_DISP_INTF_SEL_INTF3__SHIFT) & MDP5_DISP_INTF_SEL_INTF3__MASK; +} + +#define REG_MDP5_INTR_EN 0x00000010 + +#define REG_MDP5_INTR_STATUS 0x00000014 + +#define REG_MDP5_INTR_CLEAR 0x00000018 + +#define REG_MDP5_HIST_INTR_EN 0x0000001c + +#define REG_MDP5_HIST_INTR_STATUS 0x00000020 + +#define REG_MDP5_HIST_INTR_CLEAR 0x00000024 + +#define REG_MDP5_SPARE_0 0x00000028 +#define MDP5_SPARE_0_SPLIT_DPL_SINGLE_FLUSH_EN 0x00000001 + +static inline uint32_t REG_MDP5_SMP_ALLOC_W(uint32_t i0) { return 0x00000080 + 0x4*i0; } + +static inline uint32_t REG_MDP5_SMP_ALLOC_W_REG(uint32_t i0) { return 0x00000080 + 0x4*i0; } +#define MDP5_SMP_ALLOC_W_REG_CLIENT0__MASK 0x000000ff +#define MDP5_SMP_ALLOC_W_REG_CLIENT0__SHIFT 0 +static inline uint32_t MDP5_SMP_ALLOC_W_REG_CLIENT0(uint32_t val) +{ + return ((val) << MDP5_SMP_ALLOC_W_REG_CLIENT0__SHIFT) & MDP5_SMP_ALLOC_W_REG_CLIENT0__MASK; +} +#define MDP5_SMP_ALLOC_W_REG_CLIENT1__MASK 0x0000ff00 +#define MDP5_SMP_ALLOC_W_REG_CLIENT1__SHIFT 8 +static inline uint32_t MDP5_SMP_ALLOC_W_REG_CLIENT1(uint32_t val) +{ + return ((val) << MDP5_SMP_ALLOC_W_REG_CLIENT1__SHIFT) & MDP5_SMP_ALLOC_W_REG_CLIENT1__MASK; +} +#define MDP5_SMP_ALLOC_W_REG_CLIENT2__MASK 0x00ff0000 +#define MDP5_SMP_ALLOC_W_REG_CLIENT2__SHIFT 16 +static inline uint32_t MDP5_SMP_ALLOC_W_REG_CLIENT2(uint32_t val) +{ + return ((val) << MDP5_SMP_ALLOC_W_REG_CLIENT2__SHIFT) & MDP5_SMP_ALLOC_W_REG_CLIENT2__MASK; +} + +static inline uint32_t REG_MDP5_SMP_ALLOC_R(uint32_t i0) { return 0x00000130 + 0x4*i0; } + +static inline uint32_t REG_MDP5_SMP_ALLOC_R_REG(uint32_t i0) { return 0x00000130 + 0x4*i0; } +#define MDP5_SMP_ALLOC_R_REG_CLIENT0__MASK 0x000000ff +#define MDP5_SMP_ALLOC_R_REG_CLIENT0__SHIFT 0 +static inline uint32_t MDP5_SMP_ALLOC_R_REG_CLIENT0(uint32_t val) +{ + return ((val) << MDP5_SMP_ALLOC_R_REG_CLIENT0__SHIFT) & MDP5_SMP_ALLOC_R_REG_CLIENT0__MASK; +} +#define MDP5_SMP_ALLOC_R_REG_CLIENT1__MASK 0x0000ff00 +#define MDP5_SMP_ALLOC_R_REG_CLIENT1__SHIFT 8 +static inline uint32_t MDP5_SMP_ALLOC_R_REG_CLIENT1(uint32_t val) +{ + return ((val) << MDP5_SMP_ALLOC_R_REG_CLIENT1__SHIFT) & MDP5_SMP_ALLOC_R_REG_CLIENT1__MASK; +} +#define MDP5_SMP_ALLOC_R_REG_CLIENT2__MASK 0x00ff0000 +#define MDP5_SMP_ALLOC_R_REG_CLIENT2__SHIFT 16 +static inline uint32_t MDP5_SMP_ALLOC_R_REG_CLIENT2(uint32_t val) +{ + return ((val) << MDP5_SMP_ALLOC_R_REG_CLIENT2__SHIFT) & MDP5_SMP_ALLOC_R_REG_CLIENT2__MASK; +} + +static inline uint32_t __offset_IGC(enum mdp5_igc_type idx) +{ + switch (idx) { + case IGC_VIG: return 0x00000200; + case IGC_RGB: return 0x00000210; + case IGC_DMA: return 0x00000220; + case IGC_DSPP: return 0x00000300; + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_IGC(enum mdp5_igc_type i0) { return 0x00000000 + __offset_IGC(i0); } + +static inline uint32_t REG_MDP5_IGC_LUT(enum mdp5_igc_type i0, uint32_t i1) { return 0x00000000 + __offset_IGC(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP5_IGC_LUT_REG(enum mdp5_igc_type i0, uint32_t i1) { return 0x00000000 + __offset_IGC(i0) + 0x4*i1; } +#define MDP5_IGC_LUT_REG_VAL__MASK 0x00000fff +#define MDP5_IGC_LUT_REG_VAL__SHIFT 0 +static inline uint32_t MDP5_IGC_LUT_REG_VAL(uint32_t val) +{ + return ((val) << MDP5_IGC_LUT_REG_VAL__SHIFT) & MDP5_IGC_LUT_REG_VAL__MASK; +} +#define MDP5_IGC_LUT_REG_INDEX_UPDATE 0x02000000 +#define MDP5_IGC_LUT_REG_DISABLE_PIPE_0 0x10000000 +#define MDP5_IGC_LUT_REG_DISABLE_PIPE_1 0x20000000 +#define MDP5_IGC_LUT_REG_DISABLE_PIPE_2 0x40000000 + +#define REG_MDP5_SPLIT_DPL_EN 0x000002f4 + +#define REG_MDP5_SPLIT_DPL_UPPER 0x000002f8 +#define MDP5_SPLIT_DPL_UPPER_SMART_PANEL 0x00000002 +#define MDP5_SPLIT_DPL_UPPER_SMART_PANEL_FREE_RUN 0x00000004 +#define MDP5_SPLIT_DPL_UPPER_INTF1_SW_TRG_MUX 0x00000010 +#define MDP5_SPLIT_DPL_UPPER_INTF2_SW_TRG_MUX 0x00000100 + +#define REG_MDP5_SPLIT_DPL_LOWER 0x000003f0 +#define MDP5_SPLIT_DPL_LOWER_SMART_PANEL 0x00000002 +#define MDP5_SPLIT_DPL_LOWER_SMART_PANEL_FREE_RUN 0x00000004 +#define MDP5_SPLIT_DPL_LOWER_INTF1_TG_SYNC 0x00000010 +#define MDP5_SPLIT_DPL_LOWER_INTF2_TG_SYNC 0x00000100 + +static inline uint32_t __offset_CTL(uint32_t idx) +{ + switch (idx) { + case 0: return (mdp5_cfg->ctl.base[0]); + case 1: return (mdp5_cfg->ctl.base[1]); + case 2: return (mdp5_cfg->ctl.base[2]); + case 3: return (mdp5_cfg->ctl.base[3]); + case 4: return (mdp5_cfg->ctl.base[4]); + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_CTL(uint32_t i0) { return 0x00000000 + __offset_CTL(i0); } + +static inline uint32_t __offset_LAYER(uint32_t idx) +{ + switch (idx) { + case 0: return 0x00000000; + case 1: return 0x00000004; + case 2: return 0x00000008; + case 3: return 0x0000000c; + case 4: return 0x00000010; + case 5: return 0x00000024; + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_CTL_LAYER(uint32_t i0, uint32_t i1) { return 0x00000000 + __offset_CTL(i0) + __offset_LAYER(i1); } + +static inline uint32_t REG_MDP5_CTL_LAYER_REG(uint32_t i0, uint32_t i1) { return 0x00000000 + __offset_CTL(i0) + __offset_LAYER(i1); } +#define MDP5_CTL_LAYER_REG_VIG0__MASK 0x00000007 +#define MDP5_CTL_LAYER_REG_VIG0__SHIFT 0 +static inline uint32_t MDP5_CTL_LAYER_REG_VIG0(uint32_t val) +{ + return ((val) << MDP5_CTL_LAYER_REG_VIG0__SHIFT) & MDP5_CTL_LAYER_REG_VIG0__MASK; +} +#define MDP5_CTL_LAYER_REG_VIG1__MASK 0x00000038 +#define MDP5_CTL_LAYER_REG_VIG1__SHIFT 3 +static inline uint32_t MDP5_CTL_LAYER_REG_VIG1(uint32_t val) +{ + return ((val) << MDP5_CTL_LAYER_REG_VIG1__SHIFT) & MDP5_CTL_LAYER_REG_VIG1__MASK; +} +#define MDP5_CTL_LAYER_REG_VIG2__MASK 0x000001c0 +#define MDP5_CTL_LAYER_REG_VIG2__SHIFT 6 +static inline uint32_t MDP5_CTL_LAYER_REG_VIG2(uint32_t val) +{ + return ((val) << MDP5_CTL_LAYER_REG_VIG2__SHIFT) & MDP5_CTL_LAYER_REG_VIG2__MASK; +} +#define MDP5_CTL_LAYER_REG_RGB0__MASK 0x00000e00 +#define MDP5_CTL_LAYER_REG_RGB0__SHIFT 9 +static inline uint32_t MDP5_CTL_LAYER_REG_RGB0(uint32_t val) +{ + return ((val) << MDP5_CTL_LAYER_REG_RGB0__SHIFT) & MDP5_CTL_LAYER_REG_RGB0__MASK; +} +#define MDP5_CTL_LAYER_REG_RGB1__MASK 0x00007000 +#define MDP5_CTL_LAYER_REG_RGB1__SHIFT 12 +static inline uint32_t MDP5_CTL_LAYER_REG_RGB1(uint32_t val) +{ + return ((val) << MDP5_CTL_LAYER_REG_RGB1__SHIFT) & MDP5_CTL_LAYER_REG_RGB1__MASK; +} +#define MDP5_CTL_LAYER_REG_RGB2__MASK 0x00038000 +#define MDP5_CTL_LAYER_REG_RGB2__SHIFT 15 +static inline uint32_t MDP5_CTL_LAYER_REG_RGB2(uint32_t val) +{ + return ((val) << MDP5_CTL_LAYER_REG_RGB2__SHIFT) & MDP5_CTL_LAYER_REG_RGB2__MASK; +} +#define MDP5_CTL_LAYER_REG_DMA0__MASK 0x001c0000 +#define MDP5_CTL_LAYER_REG_DMA0__SHIFT 18 +static inline uint32_t MDP5_CTL_LAYER_REG_DMA0(uint32_t val) +{ + return ((val) << MDP5_CTL_LAYER_REG_DMA0__SHIFT) & MDP5_CTL_LAYER_REG_DMA0__MASK; +} +#define MDP5_CTL_LAYER_REG_DMA1__MASK 0x00e00000 +#define MDP5_CTL_LAYER_REG_DMA1__SHIFT 21 +static inline uint32_t MDP5_CTL_LAYER_REG_DMA1(uint32_t val) +{ + return ((val) << MDP5_CTL_LAYER_REG_DMA1__SHIFT) & MDP5_CTL_LAYER_REG_DMA1__MASK; +} +#define MDP5_CTL_LAYER_REG_BORDER_COLOR 0x01000000 +#define MDP5_CTL_LAYER_REG_CURSOR_OUT 0x02000000 +#define MDP5_CTL_LAYER_REG_VIG3__MASK 0x1c000000 +#define MDP5_CTL_LAYER_REG_VIG3__SHIFT 26 +static inline uint32_t MDP5_CTL_LAYER_REG_VIG3(uint32_t val) +{ + return ((val) << MDP5_CTL_LAYER_REG_VIG3__SHIFT) & MDP5_CTL_LAYER_REG_VIG3__MASK; +} +#define MDP5_CTL_LAYER_REG_RGB3__MASK 0xe0000000 +#define MDP5_CTL_LAYER_REG_RGB3__SHIFT 29 +static inline uint32_t MDP5_CTL_LAYER_REG_RGB3(uint32_t val) +{ + return ((val) << MDP5_CTL_LAYER_REG_RGB3__SHIFT) & MDP5_CTL_LAYER_REG_RGB3__MASK; +} + +static inline uint32_t REG_MDP5_CTL_OP(uint32_t i0) { return 0x00000014 + __offset_CTL(i0); } +#define MDP5_CTL_OP_MODE__MASK 0x0000000f +#define MDP5_CTL_OP_MODE__SHIFT 0 +static inline uint32_t MDP5_CTL_OP_MODE(enum mdp5_ctl_mode val) +{ + return ((val) << MDP5_CTL_OP_MODE__SHIFT) & MDP5_CTL_OP_MODE__MASK; +} +#define MDP5_CTL_OP_INTF_NUM__MASK 0x00000070 +#define MDP5_CTL_OP_INTF_NUM__SHIFT 4 +static inline uint32_t MDP5_CTL_OP_INTF_NUM(enum mdp5_intfnum val) +{ + return ((val) << MDP5_CTL_OP_INTF_NUM__SHIFT) & MDP5_CTL_OP_INTF_NUM__MASK; +} +#define MDP5_CTL_OP_CMD_MODE 0x00020000 +#define MDP5_CTL_OP_PACK_3D_ENABLE 0x00080000 +#define MDP5_CTL_OP_PACK_3D__MASK 0x00300000 +#define MDP5_CTL_OP_PACK_3D__SHIFT 20 +static inline uint32_t MDP5_CTL_OP_PACK_3D(enum mdp5_pack_3d val) +{ + return ((val) << MDP5_CTL_OP_PACK_3D__SHIFT) & MDP5_CTL_OP_PACK_3D__MASK; +} + +static inline uint32_t REG_MDP5_CTL_FLUSH(uint32_t i0) { return 0x00000018 + __offset_CTL(i0); } +#define MDP5_CTL_FLUSH_VIG0 0x00000001 +#define MDP5_CTL_FLUSH_VIG1 0x00000002 +#define MDP5_CTL_FLUSH_VIG2 0x00000004 +#define MDP5_CTL_FLUSH_RGB0 0x00000008 +#define MDP5_CTL_FLUSH_RGB1 0x00000010 +#define MDP5_CTL_FLUSH_RGB2 0x00000020 +#define MDP5_CTL_FLUSH_LM0 0x00000040 +#define MDP5_CTL_FLUSH_LM1 0x00000080 +#define MDP5_CTL_FLUSH_LM2 0x00000100 +#define MDP5_CTL_FLUSH_LM3 0x00000200 +#define MDP5_CTL_FLUSH_LM4 0x00000400 +#define MDP5_CTL_FLUSH_DMA0 0x00000800 +#define MDP5_CTL_FLUSH_DMA1 0x00001000 +#define MDP5_CTL_FLUSH_DSPP0 0x00002000 +#define MDP5_CTL_FLUSH_DSPP1 0x00004000 +#define MDP5_CTL_FLUSH_DSPP2 0x00008000 +#define MDP5_CTL_FLUSH_WB 0x00010000 +#define MDP5_CTL_FLUSH_CTL 0x00020000 +#define MDP5_CTL_FLUSH_VIG3 0x00040000 +#define MDP5_CTL_FLUSH_RGB3 0x00080000 +#define MDP5_CTL_FLUSH_LM5 0x00100000 +#define MDP5_CTL_FLUSH_DSPP3 0x00200000 +#define MDP5_CTL_FLUSH_CURSOR_0 0x00400000 +#define MDP5_CTL_FLUSH_CURSOR_1 0x00800000 +#define MDP5_CTL_FLUSH_CHROMADOWN_0 0x04000000 +#define MDP5_CTL_FLUSH_TIMING_3 0x10000000 +#define MDP5_CTL_FLUSH_TIMING_2 0x20000000 +#define MDP5_CTL_FLUSH_TIMING_1 0x40000000 +#define MDP5_CTL_FLUSH_TIMING_0 0x80000000 + +static inline uint32_t REG_MDP5_CTL_START(uint32_t i0) { return 0x0000001c + __offset_CTL(i0); } + +static inline uint32_t REG_MDP5_CTL_PACK_3D(uint32_t i0) { return 0x00000020 + __offset_CTL(i0); } + +static inline uint32_t __offset_LAYER_EXT(uint32_t idx) +{ + switch (idx) { + case 0: return 0x00000040; + case 1: return 0x00000044; + case 2: return 0x00000048; + case 3: return 0x0000004c; + case 4: return 0x00000050; + case 5: return 0x00000054; + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_CTL_LAYER_EXT(uint32_t i0, uint32_t i1) { return 0x00000000 + __offset_CTL(i0) + __offset_LAYER_EXT(i1); } + +static inline uint32_t REG_MDP5_CTL_LAYER_EXT_REG(uint32_t i0, uint32_t i1) { return 0x00000000 + __offset_CTL(i0) + __offset_LAYER_EXT(i1); } +#define MDP5_CTL_LAYER_EXT_REG_VIG0_BIT3 0x00000001 +#define MDP5_CTL_LAYER_EXT_REG_VIG1_BIT3 0x00000004 +#define MDP5_CTL_LAYER_EXT_REG_VIG2_BIT3 0x00000010 +#define MDP5_CTL_LAYER_EXT_REG_VIG3_BIT3 0x00000040 +#define MDP5_CTL_LAYER_EXT_REG_RGB0_BIT3 0x00000100 +#define MDP5_CTL_LAYER_EXT_REG_RGB1_BIT3 0x00000400 +#define MDP5_CTL_LAYER_EXT_REG_RGB2_BIT3 0x00001000 +#define MDP5_CTL_LAYER_EXT_REG_RGB3_BIT3 0x00004000 +#define MDP5_CTL_LAYER_EXT_REG_DMA0_BIT3 0x00010000 +#define MDP5_CTL_LAYER_EXT_REG_DMA1_BIT3 0x00040000 +#define MDP5_CTL_LAYER_EXT_REG_CURSOR0__MASK 0x00f00000 +#define MDP5_CTL_LAYER_EXT_REG_CURSOR0__SHIFT 20 +static inline uint32_t MDP5_CTL_LAYER_EXT_REG_CURSOR0(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP5_CTL_LAYER_EXT_REG_CURSOR0__SHIFT) & MDP5_CTL_LAYER_EXT_REG_CURSOR0__MASK; +} +#define MDP5_CTL_LAYER_EXT_REG_CURSOR1__MASK 0x3c000000 +#define MDP5_CTL_LAYER_EXT_REG_CURSOR1__SHIFT 26 +static inline uint32_t MDP5_CTL_LAYER_EXT_REG_CURSOR1(enum mdp_mixer_stage_id val) +{ + return ((val) << MDP5_CTL_LAYER_EXT_REG_CURSOR1__SHIFT) & MDP5_CTL_LAYER_EXT_REG_CURSOR1__MASK; +} + +static inline uint32_t __offset_PIPE(enum mdp5_pipe idx) +{ + switch (idx) { + case SSPP_NONE: return (INVALID_IDX(idx)); + case SSPP_VIG0: return (mdp5_cfg->pipe_vig.base[0]); + case SSPP_VIG1: return (mdp5_cfg->pipe_vig.base[1]); + case SSPP_VIG2: return (mdp5_cfg->pipe_vig.base[2]); + case SSPP_RGB0: return (mdp5_cfg->pipe_rgb.base[0]); + case SSPP_RGB1: return (mdp5_cfg->pipe_rgb.base[1]); + case SSPP_RGB2: return (mdp5_cfg->pipe_rgb.base[2]); + case SSPP_DMA0: return (mdp5_cfg->pipe_dma.base[0]); + case SSPP_DMA1: return (mdp5_cfg->pipe_dma.base[1]); + case SSPP_VIG3: return (mdp5_cfg->pipe_vig.base[3]); + case SSPP_RGB3: return (mdp5_cfg->pipe_rgb.base[3]); + case SSPP_CURSOR0: return (mdp5_cfg->pipe_cursor.base[0]); + case SSPP_CURSOR1: return (mdp5_cfg->pipe_cursor.base[1]); + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_PIPE(enum mdp5_pipe i0) { return 0x00000000 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_OP_MODE(enum mdp5_pipe i0) { return 0x00000200 + __offset_PIPE(i0); } +#define MDP5_PIPE_OP_MODE_CSC_DST_DATA_FORMAT__MASK 0x00080000 +#define MDP5_PIPE_OP_MODE_CSC_DST_DATA_FORMAT__SHIFT 19 +static inline uint32_t MDP5_PIPE_OP_MODE_CSC_DST_DATA_FORMAT(enum mdp5_data_format val) +{ + return ((val) << MDP5_PIPE_OP_MODE_CSC_DST_DATA_FORMAT__SHIFT) & MDP5_PIPE_OP_MODE_CSC_DST_DATA_FORMAT__MASK; +} +#define MDP5_PIPE_OP_MODE_CSC_SRC_DATA_FORMAT__MASK 0x00040000 +#define MDP5_PIPE_OP_MODE_CSC_SRC_DATA_FORMAT__SHIFT 18 +static inline uint32_t MDP5_PIPE_OP_MODE_CSC_SRC_DATA_FORMAT(enum mdp5_data_format val) +{ + return ((val) << MDP5_PIPE_OP_MODE_CSC_SRC_DATA_FORMAT__SHIFT) & MDP5_PIPE_OP_MODE_CSC_SRC_DATA_FORMAT__MASK; +} +#define MDP5_PIPE_OP_MODE_CSC_1_EN 0x00020000 + +static inline uint32_t REG_MDP5_PIPE_HIST_CTL_BASE(enum mdp5_pipe i0) { return 0x000002c4 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_HIST_LUT_BASE(enum mdp5_pipe i0) { return 0x000002f0 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_HIST_LUT_SWAP(enum mdp5_pipe i0) { return 0x00000300 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_CSC_1_MATRIX_COEFF_0(enum mdp5_pipe i0) { return 0x00000320 + __offset_PIPE(i0); } +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_0_COEFF_11__MASK 0x00001fff +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_0_COEFF_11__SHIFT 0 +static inline uint32_t MDP5_PIPE_CSC_1_MATRIX_COEFF_0_COEFF_11(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_MATRIX_COEFF_0_COEFF_11__SHIFT) & MDP5_PIPE_CSC_1_MATRIX_COEFF_0_COEFF_11__MASK; +} +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_0_COEFF_12__MASK 0x1fff0000 +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_0_COEFF_12__SHIFT 16 +static inline uint32_t MDP5_PIPE_CSC_1_MATRIX_COEFF_0_COEFF_12(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_MATRIX_COEFF_0_COEFF_12__SHIFT) & MDP5_PIPE_CSC_1_MATRIX_COEFF_0_COEFF_12__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_CSC_1_MATRIX_COEFF_1(enum mdp5_pipe i0) { return 0x00000324 + __offset_PIPE(i0); } +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_1_COEFF_13__MASK 0x00001fff +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_1_COEFF_13__SHIFT 0 +static inline uint32_t MDP5_PIPE_CSC_1_MATRIX_COEFF_1_COEFF_13(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_MATRIX_COEFF_1_COEFF_13__SHIFT) & MDP5_PIPE_CSC_1_MATRIX_COEFF_1_COEFF_13__MASK; +} +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_1_COEFF_21__MASK 0x1fff0000 +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_1_COEFF_21__SHIFT 16 +static inline uint32_t MDP5_PIPE_CSC_1_MATRIX_COEFF_1_COEFF_21(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_MATRIX_COEFF_1_COEFF_21__SHIFT) & MDP5_PIPE_CSC_1_MATRIX_COEFF_1_COEFF_21__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_CSC_1_MATRIX_COEFF_2(enum mdp5_pipe i0) { return 0x00000328 + __offset_PIPE(i0); } +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_2_COEFF_22__MASK 0x00001fff +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_2_COEFF_22__SHIFT 0 +static inline uint32_t MDP5_PIPE_CSC_1_MATRIX_COEFF_2_COEFF_22(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_MATRIX_COEFF_2_COEFF_22__SHIFT) & MDP5_PIPE_CSC_1_MATRIX_COEFF_2_COEFF_22__MASK; +} +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_2_COEFF_23__MASK 0x1fff0000 +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_2_COEFF_23__SHIFT 16 +static inline uint32_t MDP5_PIPE_CSC_1_MATRIX_COEFF_2_COEFF_23(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_MATRIX_COEFF_2_COEFF_23__SHIFT) & MDP5_PIPE_CSC_1_MATRIX_COEFF_2_COEFF_23__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_CSC_1_MATRIX_COEFF_3(enum mdp5_pipe i0) { return 0x0000032c + __offset_PIPE(i0); } +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_3_COEFF_31__MASK 0x00001fff +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_3_COEFF_31__SHIFT 0 +static inline uint32_t MDP5_PIPE_CSC_1_MATRIX_COEFF_3_COEFF_31(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_MATRIX_COEFF_3_COEFF_31__SHIFT) & MDP5_PIPE_CSC_1_MATRIX_COEFF_3_COEFF_31__MASK; +} +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_3_COEFF_32__MASK 0x1fff0000 +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_3_COEFF_32__SHIFT 16 +static inline uint32_t MDP5_PIPE_CSC_1_MATRIX_COEFF_3_COEFF_32(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_MATRIX_COEFF_3_COEFF_32__SHIFT) & MDP5_PIPE_CSC_1_MATRIX_COEFF_3_COEFF_32__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_CSC_1_MATRIX_COEFF_4(enum mdp5_pipe i0) { return 0x00000330 + __offset_PIPE(i0); } +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_4_COEFF_33__MASK 0x00001fff +#define MDP5_PIPE_CSC_1_MATRIX_COEFF_4_COEFF_33__SHIFT 0 +static inline uint32_t MDP5_PIPE_CSC_1_MATRIX_COEFF_4_COEFF_33(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_MATRIX_COEFF_4_COEFF_33__SHIFT) & MDP5_PIPE_CSC_1_MATRIX_COEFF_4_COEFF_33__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_CSC_1_PRE_CLAMP(enum mdp5_pipe i0, uint32_t i1) { return 0x00000334 + __offset_PIPE(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP5_PIPE_CSC_1_PRE_CLAMP_REG(enum mdp5_pipe i0, uint32_t i1) { return 0x00000334 + __offset_PIPE(i0) + 0x4*i1; } +#define MDP5_PIPE_CSC_1_PRE_CLAMP_REG_HIGH__MASK 0x000000ff +#define MDP5_PIPE_CSC_1_PRE_CLAMP_REG_HIGH__SHIFT 0 +static inline uint32_t MDP5_PIPE_CSC_1_PRE_CLAMP_REG_HIGH(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_PRE_CLAMP_REG_HIGH__SHIFT) & MDP5_PIPE_CSC_1_PRE_CLAMP_REG_HIGH__MASK; +} +#define MDP5_PIPE_CSC_1_PRE_CLAMP_REG_LOW__MASK 0x0000ff00 +#define MDP5_PIPE_CSC_1_PRE_CLAMP_REG_LOW__SHIFT 8 +static inline uint32_t MDP5_PIPE_CSC_1_PRE_CLAMP_REG_LOW(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_PRE_CLAMP_REG_LOW__SHIFT) & MDP5_PIPE_CSC_1_PRE_CLAMP_REG_LOW__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_CSC_1_POST_CLAMP(enum mdp5_pipe i0, uint32_t i1) { return 0x00000340 + __offset_PIPE(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP5_PIPE_CSC_1_POST_CLAMP_REG(enum mdp5_pipe i0, uint32_t i1) { return 0x00000340 + __offset_PIPE(i0) + 0x4*i1; } +#define MDP5_PIPE_CSC_1_POST_CLAMP_REG_HIGH__MASK 0x000000ff +#define MDP5_PIPE_CSC_1_POST_CLAMP_REG_HIGH__SHIFT 0 +static inline uint32_t MDP5_PIPE_CSC_1_POST_CLAMP_REG_HIGH(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_POST_CLAMP_REG_HIGH__SHIFT) & MDP5_PIPE_CSC_1_POST_CLAMP_REG_HIGH__MASK; +} +#define MDP5_PIPE_CSC_1_POST_CLAMP_REG_LOW__MASK 0x0000ff00 +#define MDP5_PIPE_CSC_1_POST_CLAMP_REG_LOW__SHIFT 8 +static inline uint32_t MDP5_PIPE_CSC_1_POST_CLAMP_REG_LOW(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_POST_CLAMP_REG_LOW__SHIFT) & MDP5_PIPE_CSC_1_POST_CLAMP_REG_LOW__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_CSC_1_PRE_BIAS(enum mdp5_pipe i0, uint32_t i1) { return 0x0000034c + __offset_PIPE(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP5_PIPE_CSC_1_PRE_BIAS_REG(enum mdp5_pipe i0, uint32_t i1) { return 0x0000034c + __offset_PIPE(i0) + 0x4*i1; } +#define MDP5_PIPE_CSC_1_PRE_BIAS_REG_VALUE__MASK 0x000001ff +#define MDP5_PIPE_CSC_1_PRE_BIAS_REG_VALUE__SHIFT 0 +static inline uint32_t MDP5_PIPE_CSC_1_PRE_BIAS_REG_VALUE(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_PRE_BIAS_REG_VALUE__SHIFT) & MDP5_PIPE_CSC_1_PRE_BIAS_REG_VALUE__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_CSC_1_POST_BIAS(enum mdp5_pipe i0, uint32_t i1) { return 0x00000358 + __offset_PIPE(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP5_PIPE_CSC_1_POST_BIAS_REG(enum mdp5_pipe i0, uint32_t i1) { return 0x00000358 + __offset_PIPE(i0) + 0x4*i1; } +#define MDP5_PIPE_CSC_1_POST_BIAS_REG_VALUE__MASK 0x000001ff +#define MDP5_PIPE_CSC_1_POST_BIAS_REG_VALUE__SHIFT 0 +static inline uint32_t MDP5_PIPE_CSC_1_POST_BIAS_REG_VALUE(uint32_t val) +{ + return ((val) << MDP5_PIPE_CSC_1_POST_BIAS_REG_VALUE__SHIFT) & MDP5_PIPE_CSC_1_POST_BIAS_REG_VALUE__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_SRC_SIZE(enum mdp5_pipe i0) { return 0x00000000 + __offset_PIPE(i0); } +#define MDP5_PIPE_SRC_SIZE_HEIGHT__MASK 0xffff0000 +#define MDP5_PIPE_SRC_SIZE_HEIGHT__SHIFT 16 +static inline uint32_t MDP5_PIPE_SRC_SIZE_HEIGHT(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_SIZE_HEIGHT__SHIFT) & MDP5_PIPE_SRC_SIZE_HEIGHT__MASK; +} +#define MDP5_PIPE_SRC_SIZE_WIDTH__MASK 0x0000ffff +#define MDP5_PIPE_SRC_SIZE_WIDTH__SHIFT 0 +static inline uint32_t MDP5_PIPE_SRC_SIZE_WIDTH(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_SIZE_WIDTH__SHIFT) & MDP5_PIPE_SRC_SIZE_WIDTH__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_SRC_IMG_SIZE(enum mdp5_pipe i0) { return 0x00000004 + __offset_PIPE(i0); } +#define MDP5_PIPE_SRC_IMG_SIZE_HEIGHT__MASK 0xffff0000 +#define MDP5_PIPE_SRC_IMG_SIZE_HEIGHT__SHIFT 16 +static inline uint32_t MDP5_PIPE_SRC_IMG_SIZE_HEIGHT(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_IMG_SIZE_HEIGHT__SHIFT) & MDP5_PIPE_SRC_IMG_SIZE_HEIGHT__MASK; +} +#define MDP5_PIPE_SRC_IMG_SIZE_WIDTH__MASK 0x0000ffff +#define MDP5_PIPE_SRC_IMG_SIZE_WIDTH__SHIFT 0 +static inline uint32_t MDP5_PIPE_SRC_IMG_SIZE_WIDTH(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_IMG_SIZE_WIDTH__SHIFT) & MDP5_PIPE_SRC_IMG_SIZE_WIDTH__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_SRC_XY(enum mdp5_pipe i0) { return 0x00000008 + __offset_PIPE(i0); } +#define MDP5_PIPE_SRC_XY_Y__MASK 0xffff0000 +#define MDP5_PIPE_SRC_XY_Y__SHIFT 16 +static inline uint32_t MDP5_PIPE_SRC_XY_Y(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_XY_Y__SHIFT) & MDP5_PIPE_SRC_XY_Y__MASK; +} +#define MDP5_PIPE_SRC_XY_X__MASK 0x0000ffff +#define MDP5_PIPE_SRC_XY_X__SHIFT 0 +static inline uint32_t MDP5_PIPE_SRC_XY_X(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_XY_X__SHIFT) & MDP5_PIPE_SRC_XY_X__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_OUT_SIZE(enum mdp5_pipe i0) { return 0x0000000c + __offset_PIPE(i0); } +#define MDP5_PIPE_OUT_SIZE_HEIGHT__MASK 0xffff0000 +#define MDP5_PIPE_OUT_SIZE_HEIGHT__SHIFT 16 +static inline uint32_t MDP5_PIPE_OUT_SIZE_HEIGHT(uint32_t val) +{ + return ((val) << MDP5_PIPE_OUT_SIZE_HEIGHT__SHIFT) & MDP5_PIPE_OUT_SIZE_HEIGHT__MASK; +} +#define MDP5_PIPE_OUT_SIZE_WIDTH__MASK 0x0000ffff +#define MDP5_PIPE_OUT_SIZE_WIDTH__SHIFT 0 +static inline uint32_t MDP5_PIPE_OUT_SIZE_WIDTH(uint32_t val) +{ + return ((val) << MDP5_PIPE_OUT_SIZE_WIDTH__SHIFT) & MDP5_PIPE_OUT_SIZE_WIDTH__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_OUT_XY(enum mdp5_pipe i0) { return 0x00000010 + __offset_PIPE(i0); } +#define MDP5_PIPE_OUT_XY_Y__MASK 0xffff0000 +#define MDP5_PIPE_OUT_XY_Y__SHIFT 16 +static inline uint32_t MDP5_PIPE_OUT_XY_Y(uint32_t val) +{ + return ((val) << MDP5_PIPE_OUT_XY_Y__SHIFT) & MDP5_PIPE_OUT_XY_Y__MASK; +} +#define MDP5_PIPE_OUT_XY_X__MASK 0x0000ffff +#define MDP5_PIPE_OUT_XY_X__SHIFT 0 +static inline uint32_t MDP5_PIPE_OUT_XY_X(uint32_t val) +{ + return ((val) << MDP5_PIPE_OUT_XY_X__SHIFT) & MDP5_PIPE_OUT_XY_X__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_SRC0_ADDR(enum mdp5_pipe i0) { return 0x00000014 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_SRC1_ADDR(enum mdp5_pipe i0) { return 0x00000018 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_SRC2_ADDR(enum mdp5_pipe i0) { return 0x0000001c + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_SRC3_ADDR(enum mdp5_pipe i0) { return 0x00000020 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_SRC_STRIDE_A(enum mdp5_pipe i0) { return 0x00000024 + __offset_PIPE(i0); } +#define MDP5_PIPE_SRC_STRIDE_A_P0__MASK 0x0000ffff +#define MDP5_PIPE_SRC_STRIDE_A_P0__SHIFT 0 +static inline uint32_t MDP5_PIPE_SRC_STRIDE_A_P0(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_STRIDE_A_P0__SHIFT) & MDP5_PIPE_SRC_STRIDE_A_P0__MASK; +} +#define MDP5_PIPE_SRC_STRIDE_A_P1__MASK 0xffff0000 +#define MDP5_PIPE_SRC_STRIDE_A_P1__SHIFT 16 +static inline uint32_t MDP5_PIPE_SRC_STRIDE_A_P1(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_STRIDE_A_P1__SHIFT) & MDP5_PIPE_SRC_STRIDE_A_P1__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_SRC_STRIDE_B(enum mdp5_pipe i0) { return 0x00000028 + __offset_PIPE(i0); } +#define MDP5_PIPE_SRC_STRIDE_B_P2__MASK 0x0000ffff +#define MDP5_PIPE_SRC_STRIDE_B_P2__SHIFT 0 +static inline uint32_t MDP5_PIPE_SRC_STRIDE_B_P2(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_STRIDE_B_P2__SHIFT) & MDP5_PIPE_SRC_STRIDE_B_P2__MASK; +} +#define MDP5_PIPE_SRC_STRIDE_B_P3__MASK 0xffff0000 +#define MDP5_PIPE_SRC_STRIDE_B_P3__SHIFT 16 +static inline uint32_t MDP5_PIPE_SRC_STRIDE_B_P3(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_STRIDE_B_P3__SHIFT) & MDP5_PIPE_SRC_STRIDE_B_P3__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_STILE_FRAME_SIZE(enum mdp5_pipe i0) { return 0x0000002c + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_SRC_FORMAT(enum mdp5_pipe i0) { return 0x00000030 + __offset_PIPE(i0); } +#define MDP5_PIPE_SRC_FORMAT_G_BPC__MASK 0x00000003 +#define MDP5_PIPE_SRC_FORMAT_G_BPC__SHIFT 0 +static inline uint32_t MDP5_PIPE_SRC_FORMAT_G_BPC(enum mdp_bpc val) +{ + return ((val) << MDP5_PIPE_SRC_FORMAT_G_BPC__SHIFT) & MDP5_PIPE_SRC_FORMAT_G_BPC__MASK; +} +#define MDP5_PIPE_SRC_FORMAT_B_BPC__MASK 0x0000000c +#define MDP5_PIPE_SRC_FORMAT_B_BPC__SHIFT 2 +static inline uint32_t MDP5_PIPE_SRC_FORMAT_B_BPC(enum mdp_bpc val) +{ + return ((val) << MDP5_PIPE_SRC_FORMAT_B_BPC__SHIFT) & MDP5_PIPE_SRC_FORMAT_B_BPC__MASK; +} +#define MDP5_PIPE_SRC_FORMAT_R_BPC__MASK 0x00000030 +#define MDP5_PIPE_SRC_FORMAT_R_BPC__SHIFT 4 +static inline uint32_t MDP5_PIPE_SRC_FORMAT_R_BPC(enum mdp_bpc val) +{ + return ((val) << MDP5_PIPE_SRC_FORMAT_R_BPC__SHIFT) & MDP5_PIPE_SRC_FORMAT_R_BPC__MASK; +} +#define MDP5_PIPE_SRC_FORMAT_A_BPC__MASK 0x000000c0 +#define MDP5_PIPE_SRC_FORMAT_A_BPC__SHIFT 6 +static inline uint32_t MDP5_PIPE_SRC_FORMAT_A_BPC(enum mdp_bpc_alpha val) +{ + return ((val) << MDP5_PIPE_SRC_FORMAT_A_BPC__SHIFT) & MDP5_PIPE_SRC_FORMAT_A_BPC__MASK; +} +#define MDP5_PIPE_SRC_FORMAT_ALPHA_ENABLE 0x00000100 +#define MDP5_PIPE_SRC_FORMAT_CPP__MASK 0x00000600 +#define MDP5_PIPE_SRC_FORMAT_CPP__SHIFT 9 +static inline uint32_t MDP5_PIPE_SRC_FORMAT_CPP(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_FORMAT_CPP__SHIFT) & MDP5_PIPE_SRC_FORMAT_CPP__MASK; +} +#define MDP5_PIPE_SRC_FORMAT_ROT90 0x00000800 +#define MDP5_PIPE_SRC_FORMAT_UNPACK_COUNT__MASK 0x00003000 +#define MDP5_PIPE_SRC_FORMAT_UNPACK_COUNT__SHIFT 12 +static inline uint32_t MDP5_PIPE_SRC_FORMAT_UNPACK_COUNT(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_FORMAT_UNPACK_COUNT__SHIFT) & MDP5_PIPE_SRC_FORMAT_UNPACK_COUNT__MASK; +} +#define MDP5_PIPE_SRC_FORMAT_UNPACK_TIGHT 0x00020000 +#define MDP5_PIPE_SRC_FORMAT_UNPACK_ALIGN_MSB 0x00040000 +#define MDP5_PIPE_SRC_FORMAT_FETCH_TYPE__MASK 0x00180000 +#define MDP5_PIPE_SRC_FORMAT_FETCH_TYPE__SHIFT 19 +static inline uint32_t MDP5_PIPE_SRC_FORMAT_FETCH_TYPE(enum mdp_fetch_type val) +{ + return ((val) << MDP5_PIPE_SRC_FORMAT_FETCH_TYPE__SHIFT) & MDP5_PIPE_SRC_FORMAT_FETCH_TYPE__MASK; +} +#define MDP5_PIPE_SRC_FORMAT_CHROMA_SAMP__MASK 0x01800000 +#define MDP5_PIPE_SRC_FORMAT_CHROMA_SAMP__SHIFT 23 +static inline uint32_t MDP5_PIPE_SRC_FORMAT_CHROMA_SAMP(enum mdp_chroma_samp_type val) +{ + return ((val) << MDP5_PIPE_SRC_FORMAT_CHROMA_SAMP__SHIFT) & MDP5_PIPE_SRC_FORMAT_CHROMA_SAMP__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_SRC_UNPACK(enum mdp5_pipe i0) { return 0x00000034 + __offset_PIPE(i0); } +#define MDP5_PIPE_SRC_UNPACK_ELEM0__MASK 0x000000ff +#define MDP5_PIPE_SRC_UNPACK_ELEM0__SHIFT 0 +static inline uint32_t MDP5_PIPE_SRC_UNPACK_ELEM0(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_UNPACK_ELEM0__SHIFT) & MDP5_PIPE_SRC_UNPACK_ELEM0__MASK; +} +#define MDP5_PIPE_SRC_UNPACK_ELEM1__MASK 0x0000ff00 +#define MDP5_PIPE_SRC_UNPACK_ELEM1__SHIFT 8 +static inline uint32_t MDP5_PIPE_SRC_UNPACK_ELEM1(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_UNPACK_ELEM1__SHIFT) & MDP5_PIPE_SRC_UNPACK_ELEM1__MASK; +} +#define MDP5_PIPE_SRC_UNPACK_ELEM2__MASK 0x00ff0000 +#define MDP5_PIPE_SRC_UNPACK_ELEM2__SHIFT 16 +static inline uint32_t MDP5_PIPE_SRC_UNPACK_ELEM2(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_UNPACK_ELEM2__SHIFT) & MDP5_PIPE_SRC_UNPACK_ELEM2__MASK; +} +#define MDP5_PIPE_SRC_UNPACK_ELEM3__MASK 0xff000000 +#define MDP5_PIPE_SRC_UNPACK_ELEM3__SHIFT 24 +static inline uint32_t MDP5_PIPE_SRC_UNPACK_ELEM3(uint32_t val) +{ + return ((val) << MDP5_PIPE_SRC_UNPACK_ELEM3__SHIFT) & MDP5_PIPE_SRC_UNPACK_ELEM3__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_SRC_OP_MODE(enum mdp5_pipe i0) { return 0x00000038 + __offset_PIPE(i0); } +#define MDP5_PIPE_SRC_OP_MODE_BWC_EN 0x00000001 +#define MDP5_PIPE_SRC_OP_MODE_BWC__MASK 0x00000006 +#define MDP5_PIPE_SRC_OP_MODE_BWC__SHIFT 1 +static inline uint32_t MDP5_PIPE_SRC_OP_MODE_BWC(enum mdp5_pipe_bwc val) +{ + return ((val) << MDP5_PIPE_SRC_OP_MODE_BWC__SHIFT) & MDP5_PIPE_SRC_OP_MODE_BWC__MASK; +} +#define MDP5_PIPE_SRC_OP_MODE_FLIP_LR 0x00002000 +#define MDP5_PIPE_SRC_OP_MODE_FLIP_UD 0x00004000 +#define MDP5_PIPE_SRC_OP_MODE_IGC_EN 0x00010000 +#define MDP5_PIPE_SRC_OP_MODE_IGC_ROM_0 0x00020000 +#define MDP5_PIPE_SRC_OP_MODE_IGC_ROM_1 0x00040000 +#define MDP5_PIPE_SRC_OP_MODE_DEINTERLACE 0x00400000 +#define MDP5_PIPE_SRC_OP_MODE_DEINTERLACE_ODD 0x00800000 +#define MDP5_PIPE_SRC_OP_MODE_SW_PIX_EXT_OVERRIDE 0x80000000 + +static inline uint32_t REG_MDP5_PIPE_SRC_CONSTANT_COLOR(enum mdp5_pipe i0) { return 0x0000003c + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_FETCH_CONFIG(enum mdp5_pipe i0) { return 0x00000048 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_VC1_RANGE(enum mdp5_pipe i0) { return 0x0000004c + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_REQPRIO_FIFO_WM_0(enum mdp5_pipe i0) { return 0x00000050 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_REQPRIO_FIFO_WM_1(enum mdp5_pipe i0) { return 0x00000054 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_REQPRIO_FIFO_WM_2(enum mdp5_pipe i0) { return 0x00000058 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_SRC_ADDR_SW_STATUS(enum mdp5_pipe i0) { return 0x00000070 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_CURRENT_SRC0_ADDR(enum mdp5_pipe i0) { return 0x000000a4 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_CURRENT_SRC1_ADDR(enum mdp5_pipe i0) { return 0x000000a8 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_CURRENT_SRC2_ADDR(enum mdp5_pipe i0) { return 0x000000ac + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_CURRENT_SRC3_ADDR(enum mdp5_pipe i0) { return 0x000000b0 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_DECIMATION(enum mdp5_pipe i0) { return 0x000000b4 + __offset_PIPE(i0); } +#define MDP5_PIPE_DECIMATION_VERT__MASK 0x000000ff +#define MDP5_PIPE_DECIMATION_VERT__SHIFT 0 +static inline uint32_t MDP5_PIPE_DECIMATION_VERT(uint32_t val) +{ + return ((val) << MDP5_PIPE_DECIMATION_VERT__SHIFT) & MDP5_PIPE_DECIMATION_VERT__MASK; +} +#define MDP5_PIPE_DECIMATION_HORZ__MASK 0x0000ff00 +#define MDP5_PIPE_DECIMATION_HORZ__SHIFT 8 +static inline uint32_t MDP5_PIPE_DECIMATION_HORZ(uint32_t val) +{ + return ((val) << MDP5_PIPE_DECIMATION_HORZ__SHIFT) & MDP5_PIPE_DECIMATION_HORZ__MASK; +} + +static inline uint32_t __offset_SW_PIX_EXT(enum mdp_component_type idx) +{ + switch (idx) { + case COMP_0: return 0x00000100; + case COMP_1_2: return 0x00000110; + case COMP_3: return 0x00000120; + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_PIPE_SW_PIX_EXT(enum mdp5_pipe i0, enum mdp_component_type i1) { return 0x00000000 + __offset_PIPE(i0) + __offset_SW_PIX_EXT(i1); } + +static inline uint32_t REG_MDP5_PIPE_SW_PIX_EXT_LR(enum mdp5_pipe i0, enum mdp_component_type i1) { return 0x00000000 + __offset_PIPE(i0) + __offset_SW_PIX_EXT(i1); } +#define MDP5_PIPE_SW_PIX_EXT_LR_LEFT_RPT__MASK 0x000000ff +#define MDP5_PIPE_SW_PIX_EXT_LR_LEFT_RPT__SHIFT 0 +static inline uint32_t MDP5_PIPE_SW_PIX_EXT_LR_LEFT_RPT(uint32_t val) +{ + return ((val) << MDP5_PIPE_SW_PIX_EXT_LR_LEFT_RPT__SHIFT) & MDP5_PIPE_SW_PIX_EXT_LR_LEFT_RPT__MASK; +} +#define MDP5_PIPE_SW_PIX_EXT_LR_LEFT_OVF__MASK 0x0000ff00 +#define MDP5_PIPE_SW_PIX_EXT_LR_LEFT_OVF__SHIFT 8 +static inline uint32_t MDP5_PIPE_SW_PIX_EXT_LR_LEFT_OVF(int32_t val) +{ + return ((val) << MDP5_PIPE_SW_PIX_EXT_LR_LEFT_OVF__SHIFT) & MDP5_PIPE_SW_PIX_EXT_LR_LEFT_OVF__MASK; +} +#define MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_RPT__MASK 0x00ff0000 +#define MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_RPT__SHIFT 16 +static inline uint32_t MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_RPT(uint32_t val) +{ + return ((val) << MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_RPT__SHIFT) & MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_RPT__MASK; +} +#define MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_OVF__MASK 0xff000000 +#define MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_OVF__SHIFT 24 +static inline uint32_t MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_OVF(int32_t val) +{ + return ((val) << MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_OVF__SHIFT) & MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_OVF__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_SW_PIX_EXT_TB(enum mdp5_pipe i0, enum mdp_component_type i1) { return 0x00000004 + __offset_PIPE(i0) + __offset_SW_PIX_EXT(i1); } +#define MDP5_PIPE_SW_PIX_EXT_TB_TOP_RPT__MASK 0x000000ff +#define MDP5_PIPE_SW_PIX_EXT_TB_TOP_RPT__SHIFT 0 +static inline uint32_t MDP5_PIPE_SW_PIX_EXT_TB_TOP_RPT(uint32_t val) +{ + return ((val) << MDP5_PIPE_SW_PIX_EXT_TB_TOP_RPT__SHIFT) & MDP5_PIPE_SW_PIX_EXT_TB_TOP_RPT__MASK; +} +#define MDP5_PIPE_SW_PIX_EXT_TB_TOP_OVF__MASK 0x0000ff00 +#define MDP5_PIPE_SW_PIX_EXT_TB_TOP_OVF__SHIFT 8 +static inline uint32_t MDP5_PIPE_SW_PIX_EXT_TB_TOP_OVF(int32_t val) +{ + return ((val) << MDP5_PIPE_SW_PIX_EXT_TB_TOP_OVF__SHIFT) & MDP5_PIPE_SW_PIX_EXT_TB_TOP_OVF__MASK; +} +#define MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_RPT__MASK 0x00ff0000 +#define MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_RPT__SHIFT 16 +static inline uint32_t MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_RPT(uint32_t val) +{ + return ((val) << MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_RPT__SHIFT) & MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_RPT__MASK; +} +#define MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_OVF__MASK 0xff000000 +#define MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_OVF__SHIFT 24 +static inline uint32_t MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_OVF(int32_t val) +{ + return ((val) << MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_OVF__SHIFT) & MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_OVF__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS(enum mdp5_pipe i0, enum mdp_component_type i1) { return 0x00000008 + __offset_PIPE(i0) + __offset_SW_PIX_EXT(i1); } +#define MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_LEFT_RIGHT__MASK 0x0000ffff +#define MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_LEFT_RIGHT__SHIFT 0 +static inline uint32_t MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_LEFT_RIGHT(uint32_t val) +{ + return ((val) << MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_LEFT_RIGHT__SHIFT) & MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_LEFT_RIGHT__MASK; +} +#define MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_TOP_BOTTOM__MASK 0xffff0000 +#define MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_TOP_BOTTOM__SHIFT 16 +static inline uint32_t MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_TOP_BOTTOM(uint32_t val) +{ + return ((val) << MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_TOP_BOTTOM__SHIFT) & MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_TOP_BOTTOM__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_SCALE_CONFIG(enum mdp5_pipe i0) { return 0x00000204 + __offset_PIPE(i0); } +#define MDP5_PIPE_SCALE_CONFIG_SCALEX_EN 0x00000001 +#define MDP5_PIPE_SCALE_CONFIG_SCALEY_EN 0x00000002 +#define MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_0__MASK 0x00000300 +#define MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_0__SHIFT 8 +static inline uint32_t MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_0(enum mdp5_scale_filter val) +{ + return ((val) << MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_0__SHIFT) & MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_0__MASK; +} +#define MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_0__MASK 0x00000c00 +#define MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_0__SHIFT 10 +static inline uint32_t MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_0(enum mdp5_scale_filter val) +{ + return ((val) << MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_0__SHIFT) & MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_0__MASK; +} +#define MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_1_2__MASK 0x00003000 +#define MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_1_2__SHIFT 12 +static inline uint32_t MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_1_2(enum mdp5_scale_filter val) +{ + return ((val) << MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_1_2__SHIFT) & MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_1_2__MASK; +} +#define MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_1_2__MASK 0x0000c000 +#define MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_1_2__SHIFT 14 +static inline uint32_t MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_1_2(enum mdp5_scale_filter val) +{ + return ((val) << MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_1_2__SHIFT) & MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_1_2__MASK; +} +#define MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_3__MASK 0x00030000 +#define MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_3__SHIFT 16 +static inline uint32_t MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_3(enum mdp5_scale_filter val) +{ + return ((val) << MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_3__SHIFT) & MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_3__MASK; +} +#define MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_3__MASK 0x000c0000 +#define MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_3__SHIFT 18 +static inline uint32_t MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_3(enum mdp5_scale_filter val) +{ + return ((val) << MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_3__SHIFT) & MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_3__MASK; +} + +static inline uint32_t REG_MDP5_PIPE_SCALE_PHASE_STEP_X(enum mdp5_pipe i0) { return 0x00000210 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_SCALE_PHASE_STEP_Y(enum mdp5_pipe i0) { return 0x00000214 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_SCALE_CR_PHASE_STEP_X(enum mdp5_pipe i0) { return 0x00000218 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_SCALE_CR_PHASE_STEP_Y(enum mdp5_pipe i0) { return 0x0000021c + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_SCALE_INIT_PHASE_X(enum mdp5_pipe i0) { return 0x00000220 + __offset_PIPE(i0); } + +static inline uint32_t REG_MDP5_PIPE_SCALE_INIT_PHASE_Y(enum mdp5_pipe i0) { return 0x00000224 + __offset_PIPE(i0); } + +static inline uint32_t __offset_LM(uint32_t idx) +{ + switch (idx) { + case 0: return (mdp5_cfg->lm.base[0]); + case 1: return (mdp5_cfg->lm.base[1]); + case 2: return (mdp5_cfg->lm.base[2]); + case 3: return (mdp5_cfg->lm.base[3]); + case 4: return (mdp5_cfg->lm.base[4]); + case 5: return (mdp5_cfg->lm.base[5]); + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_LM(uint32_t i0) { return 0x00000000 + __offset_LM(i0); } + +static inline uint32_t REG_MDP5_LM_BLEND_COLOR_OUT(uint32_t i0) { return 0x00000000 + __offset_LM(i0); } +#define MDP5_LM_BLEND_COLOR_OUT_STAGE0_FG_ALPHA 0x00000002 +#define MDP5_LM_BLEND_COLOR_OUT_STAGE1_FG_ALPHA 0x00000004 +#define MDP5_LM_BLEND_COLOR_OUT_STAGE2_FG_ALPHA 0x00000008 +#define MDP5_LM_BLEND_COLOR_OUT_STAGE3_FG_ALPHA 0x00000010 +#define MDP5_LM_BLEND_COLOR_OUT_STAGE4_FG_ALPHA 0x00000020 +#define MDP5_LM_BLEND_COLOR_OUT_STAGE5_FG_ALPHA 0x00000040 +#define MDP5_LM_BLEND_COLOR_OUT_STAGE6_FG_ALPHA 0x00000080 +#define MDP5_LM_BLEND_COLOR_OUT_SPLIT_LEFT_RIGHT 0x80000000 + +static inline uint32_t REG_MDP5_LM_OUT_SIZE(uint32_t i0) { return 0x00000004 + __offset_LM(i0); } +#define MDP5_LM_OUT_SIZE_HEIGHT__MASK 0xffff0000 +#define MDP5_LM_OUT_SIZE_HEIGHT__SHIFT 16 +static inline uint32_t MDP5_LM_OUT_SIZE_HEIGHT(uint32_t val) +{ + return ((val) << MDP5_LM_OUT_SIZE_HEIGHT__SHIFT) & MDP5_LM_OUT_SIZE_HEIGHT__MASK; +} +#define MDP5_LM_OUT_SIZE_WIDTH__MASK 0x0000ffff +#define MDP5_LM_OUT_SIZE_WIDTH__SHIFT 0 +static inline uint32_t MDP5_LM_OUT_SIZE_WIDTH(uint32_t val) +{ + return ((val) << MDP5_LM_OUT_SIZE_WIDTH__SHIFT) & MDP5_LM_OUT_SIZE_WIDTH__MASK; +} + +static inline uint32_t REG_MDP5_LM_BORDER_COLOR_0(uint32_t i0) { return 0x00000008 + __offset_LM(i0); } + +static inline uint32_t REG_MDP5_LM_BORDER_COLOR_1(uint32_t i0) { return 0x00000010 + __offset_LM(i0); } + +static inline uint32_t __offset_BLEND(uint32_t idx) +{ + switch (idx) { + case 0: return 0x00000020; + case 1: return 0x00000050; + case 2: return 0x00000080; + case 3: return 0x000000b0; + case 4: return 0x00000230; + case 5: return 0x00000260; + case 6: return 0x00000290; + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_LM_BLEND(uint32_t i0, uint32_t i1) { return 0x00000000 + __offset_LM(i0) + __offset_BLEND(i1); } + +static inline uint32_t REG_MDP5_LM_BLEND_OP_MODE(uint32_t i0, uint32_t i1) { return 0x00000000 + __offset_LM(i0) + __offset_BLEND(i1); } +#define MDP5_LM_BLEND_OP_MODE_FG_ALPHA__MASK 0x00000003 +#define MDP5_LM_BLEND_OP_MODE_FG_ALPHA__SHIFT 0 +static inline uint32_t MDP5_LM_BLEND_OP_MODE_FG_ALPHA(enum mdp_alpha_type val) +{ + return ((val) << MDP5_LM_BLEND_OP_MODE_FG_ALPHA__SHIFT) & MDP5_LM_BLEND_OP_MODE_FG_ALPHA__MASK; +} +#define MDP5_LM_BLEND_OP_MODE_FG_INV_ALPHA 0x00000004 +#define MDP5_LM_BLEND_OP_MODE_FG_MOD_ALPHA 0x00000008 +#define MDP5_LM_BLEND_OP_MODE_FG_INV_MOD_ALPHA 0x00000010 +#define MDP5_LM_BLEND_OP_MODE_FG_TRANSP_EN 0x00000020 +#define MDP5_LM_BLEND_OP_MODE_BG_ALPHA__MASK 0x00000300 +#define MDP5_LM_BLEND_OP_MODE_BG_ALPHA__SHIFT 8 +static inline uint32_t MDP5_LM_BLEND_OP_MODE_BG_ALPHA(enum mdp_alpha_type val) +{ + return ((val) << MDP5_LM_BLEND_OP_MODE_BG_ALPHA__SHIFT) & MDP5_LM_BLEND_OP_MODE_BG_ALPHA__MASK; +} +#define MDP5_LM_BLEND_OP_MODE_BG_INV_ALPHA 0x00000400 +#define MDP5_LM_BLEND_OP_MODE_BG_MOD_ALPHA 0x00000800 +#define MDP5_LM_BLEND_OP_MODE_BG_INV_MOD_ALPHA 0x00001000 +#define MDP5_LM_BLEND_OP_MODE_BG_TRANSP_EN 0x00002000 + +static inline uint32_t REG_MDP5_LM_BLEND_FG_ALPHA(uint32_t i0, uint32_t i1) { return 0x00000004 + __offset_LM(i0) + __offset_BLEND(i1); } + +static inline uint32_t REG_MDP5_LM_BLEND_BG_ALPHA(uint32_t i0, uint32_t i1) { return 0x00000008 + __offset_LM(i0) + __offset_BLEND(i1); } + +static inline uint32_t REG_MDP5_LM_BLEND_FG_TRANSP_LOW0(uint32_t i0, uint32_t i1) { return 0x0000000c + __offset_LM(i0) + __offset_BLEND(i1); } + +static inline uint32_t REG_MDP5_LM_BLEND_FG_TRANSP_LOW1(uint32_t i0, uint32_t i1) { return 0x00000010 + __offset_LM(i0) + __offset_BLEND(i1); } + +static inline uint32_t REG_MDP5_LM_BLEND_FG_TRANSP_HIGH0(uint32_t i0, uint32_t i1) { return 0x00000014 + __offset_LM(i0) + __offset_BLEND(i1); } + +static inline uint32_t REG_MDP5_LM_BLEND_FG_TRANSP_HIGH1(uint32_t i0, uint32_t i1) { return 0x00000018 + __offset_LM(i0) + __offset_BLEND(i1); } + +static inline uint32_t REG_MDP5_LM_BLEND_BG_TRANSP_LOW0(uint32_t i0, uint32_t i1) { return 0x0000001c + __offset_LM(i0) + __offset_BLEND(i1); } + +static inline uint32_t REG_MDP5_LM_BLEND_BG_TRANSP_LOW1(uint32_t i0, uint32_t i1) { return 0x00000020 + __offset_LM(i0) + __offset_BLEND(i1); } + +static inline uint32_t REG_MDP5_LM_BLEND_BG_TRANSP_HIGH0(uint32_t i0, uint32_t i1) { return 0x00000024 + __offset_LM(i0) + __offset_BLEND(i1); } + +static inline uint32_t REG_MDP5_LM_BLEND_BG_TRANSP_HIGH1(uint32_t i0, uint32_t i1) { return 0x00000028 + __offset_LM(i0) + __offset_BLEND(i1); } + +static inline uint32_t REG_MDP5_LM_CURSOR_IMG_SIZE(uint32_t i0) { return 0x000000e0 + __offset_LM(i0); } +#define MDP5_LM_CURSOR_IMG_SIZE_SRC_W__MASK 0x0000ffff +#define MDP5_LM_CURSOR_IMG_SIZE_SRC_W__SHIFT 0 +static inline uint32_t MDP5_LM_CURSOR_IMG_SIZE_SRC_W(uint32_t val) +{ + return ((val) << MDP5_LM_CURSOR_IMG_SIZE_SRC_W__SHIFT) & MDP5_LM_CURSOR_IMG_SIZE_SRC_W__MASK; +} +#define MDP5_LM_CURSOR_IMG_SIZE_SRC_H__MASK 0xffff0000 +#define MDP5_LM_CURSOR_IMG_SIZE_SRC_H__SHIFT 16 +static inline uint32_t MDP5_LM_CURSOR_IMG_SIZE_SRC_H(uint32_t val) +{ + return ((val) << MDP5_LM_CURSOR_IMG_SIZE_SRC_H__SHIFT) & MDP5_LM_CURSOR_IMG_SIZE_SRC_H__MASK; +} + +static inline uint32_t REG_MDP5_LM_CURSOR_SIZE(uint32_t i0) { return 0x000000e4 + __offset_LM(i0); } +#define MDP5_LM_CURSOR_SIZE_ROI_W__MASK 0x0000ffff +#define MDP5_LM_CURSOR_SIZE_ROI_W__SHIFT 0 +static inline uint32_t MDP5_LM_CURSOR_SIZE_ROI_W(uint32_t val) +{ + return ((val) << MDP5_LM_CURSOR_SIZE_ROI_W__SHIFT) & MDP5_LM_CURSOR_SIZE_ROI_W__MASK; +} +#define MDP5_LM_CURSOR_SIZE_ROI_H__MASK 0xffff0000 +#define MDP5_LM_CURSOR_SIZE_ROI_H__SHIFT 16 +static inline uint32_t MDP5_LM_CURSOR_SIZE_ROI_H(uint32_t val) +{ + return ((val) << MDP5_LM_CURSOR_SIZE_ROI_H__SHIFT) & MDP5_LM_CURSOR_SIZE_ROI_H__MASK; +} + +static inline uint32_t REG_MDP5_LM_CURSOR_XY(uint32_t i0) { return 0x000000e8 + __offset_LM(i0); } +#define MDP5_LM_CURSOR_XY_SRC_X__MASK 0x0000ffff +#define MDP5_LM_CURSOR_XY_SRC_X__SHIFT 0 +static inline uint32_t MDP5_LM_CURSOR_XY_SRC_X(uint32_t val) +{ + return ((val) << MDP5_LM_CURSOR_XY_SRC_X__SHIFT) & MDP5_LM_CURSOR_XY_SRC_X__MASK; +} +#define MDP5_LM_CURSOR_XY_SRC_Y__MASK 0xffff0000 +#define MDP5_LM_CURSOR_XY_SRC_Y__SHIFT 16 +static inline uint32_t MDP5_LM_CURSOR_XY_SRC_Y(uint32_t val) +{ + return ((val) << MDP5_LM_CURSOR_XY_SRC_Y__SHIFT) & MDP5_LM_CURSOR_XY_SRC_Y__MASK; +} + +static inline uint32_t REG_MDP5_LM_CURSOR_STRIDE(uint32_t i0) { return 0x000000dc + __offset_LM(i0); } +#define MDP5_LM_CURSOR_STRIDE_STRIDE__MASK 0x0000ffff +#define MDP5_LM_CURSOR_STRIDE_STRIDE__SHIFT 0 +static inline uint32_t MDP5_LM_CURSOR_STRIDE_STRIDE(uint32_t val) +{ + return ((val) << MDP5_LM_CURSOR_STRIDE_STRIDE__SHIFT) & MDP5_LM_CURSOR_STRIDE_STRIDE__MASK; +} + +static inline uint32_t REG_MDP5_LM_CURSOR_FORMAT(uint32_t i0) { return 0x000000ec + __offset_LM(i0); } +#define MDP5_LM_CURSOR_FORMAT_FORMAT__MASK 0x00000007 +#define MDP5_LM_CURSOR_FORMAT_FORMAT__SHIFT 0 +static inline uint32_t MDP5_LM_CURSOR_FORMAT_FORMAT(enum mdp5_cursor_format val) +{ + return ((val) << MDP5_LM_CURSOR_FORMAT_FORMAT__SHIFT) & MDP5_LM_CURSOR_FORMAT_FORMAT__MASK; +} + +static inline uint32_t REG_MDP5_LM_CURSOR_BASE_ADDR(uint32_t i0) { return 0x000000f0 + __offset_LM(i0); } + +static inline uint32_t REG_MDP5_LM_CURSOR_START_XY(uint32_t i0) { return 0x000000f4 + __offset_LM(i0); } +#define MDP5_LM_CURSOR_START_XY_X_START__MASK 0x0000ffff +#define MDP5_LM_CURSOR_START_XY_X_START__SHIFT 0 +static inline uint32_t MDP5_LM_CURSOR_START_XY_X_START(uint32_t val) +{ + return ((val) << MDP5_LM_CURSOR_START_XY_X_START__SHIFT) & MDP5_LM_CURSOR_START_XY_X_START__MASK; +} +#define MDP5_LM_CURSOR_START_XY_Y_START__MASK 0xffff0000 +#define MDP5_LM_CURSOR_START_XY_Y_START__SHIFT 16 +static inline uint32_t MDP5_LM_CURSOR_START_XY_Y_START(uint32_t val) +{ + return ((val) << MDP5_LM_CURSOR_START_XY_Y_START__SHIFT) & MDP5_LM_CURSOR_START_XY_Y_START__MASK; +} + +static inline uint32_t REG_MDP5_LM_CURSOR_BLEND_CONFIG(uint32_t i0) { return 0x000000f8 + __offset_LM(i0); } +#define MDP5_LM_CURSOR_BLEND_CONFIG_BLEND_EN 0x00000001 +#define MDP5_LM_CURSOR_BLEND_CONFIG_BLEND_ALPHA_SEL__MASK 0x00000006 +#define MDP5_LM_CURSOR_BLEND_CONFIG_BLEND_ALPHA_SEL__SHIFT 1 +static inline uint32_t MDP5_LM_CURSOR_BLEND_CONFIG_BLEND_ALPHA_SEL(enum mdp5_cursor_alpha val) +{ + return ((val) << MDP5_LM_CURSOR_BLEND_CONFIG_BLEND_ALPHA_SEL__SHIFT) & MDP5_LM_CURSOR_BLEND_CONFIG_BLEND_ALPHA_SEL__MASK; +} +#define MDP5_LM_CURSOR_BLEND_CONFIG_BLEND_TRANSP_EN 0x00000008 + +static inline uint32_t REG_MDP5_LM_CURSOR_BLEND_PARAM(uint32_t i0) { return 0x000000fc + __offset_LM(i0); } + +static inline uint32_t REG_MDP5_LM_CURSOR_BLEND_TRANSP_LOW0(uint32_t i0) { return 0x00000100 + __offset_LM(i0); } + +static inline uint32_t REG_MDP5_LM_CURSOR_BLEND_TRANSP_LOW1(uint32_t i0) { return 0x00000104 + __offset_LM(i0); } + +static inline uint32_t REG_MDP5_LM_CURSOR_BLEND_TRANSP_HIGH0(uint32_t i0) { return 0x00000108 + __offset_LM(i0); } + +static inline uint32_t REG_MDP5_LM_CURSOR_BLEND_TRANSP_HIGH1(uint32_t i0) { return 0x0000010c + __offset_LM(i0); } + +static inline uint32_t REG_MDP5_LM_GC_LUT_BASE(uint32_t i0) { return 0x00000110 + __offset_LM(i0); } + +static inline uint32_t __offset_DSPP(uint32_t idx) +{ + switch (idx) { + case 0: return (mdp5_cfg->dspp.base[0]); + case 1: return (mdp5_cfg->dspp.base[1]); + case 2: return (mdp5_cfg->dspp.base[2]); + case 3: return (mdp5_cfg->dspp.base[3]); + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_DSPP(uint32_t i0) { return 0x00000000 + __offset_DSPP(i0); } + +static inline uint32_t REG_MDP5_DSPP_OP_MODE(uint32_t i0) { return 0x00000000 + __offset_DSPP(i0); } +#define MDP5_DSPP_OP_MODE_IGC_LUT_EN 0x00000001 +#define MDP5_DSPP_OP_MODE_IGC_TBL_IDX__MASK 0x0000000e +#define MDP5_DSPP_OP_MODE_IGC_TBL_IDX__SHIFT 1 +static inline uint32_t MDP5_DSPP_OP_MODE_IGC_TBL_IDX(uint32_t val) +{ + return ((val) << MDP5_DSPP_OP_MODE_IGC_TBL_IDX__SHIFT) & MDP5_DSPP_OP_MODE_IGC_TBL_IDX__MASK; +} +#define MDP5_DSPP_OP_MODE_PCC_EN 0x00000010 +#define MDP5_DSPP_OP_MODE_DITHER_EN 0x00000100 +#define MDP5_DSPP_OP_MODE_HIST_EN 0x00010000 +#define MDP5_DSPP_OP_MODE_AUTO_CLEAR 0x00020000 +#define MDP5_DSPP_OP_MODE_HIST_LUT_EN 0x00080000 +#define MDP5_DSPP_OP_MODE_PA_EN 0x00100000 +#define MDP5_DSPP_OP_MODE_GAMUT_EN 0x00800000 +#define MDP5_DSPP_OP_MODE_GAMUT_ORDER 0x01000000 + +static inline uint32_t REG_MDP5_DSPP_PCC_BASE(uint32_t i0) { return 0x00000030 + __offset_DSPP(i0); } + +static inline uint32_t REG_MDP5_DSPP_DITHER_DEPTH(uint32_t i0) { return 0x00000150 + __offset_DSPP(i0); } + +static inline uint32_t REG_MDP5_DSPP_HIST_CTL_BASE(uint32_t i0) { return 0x00000210 + __offset_DSPP(i0); } + +static inline uint32_t REG_MDP5_DSPP_HIST_LUT_BASE(uint32_t i0) { return 0x00000230 + __offset_DSPP(i0); } + +static inline uint32_t REG_MDP5_DSPP_HIST_LUT_SWAP(uint32_t i0) { return 0x00000234 + __offset_DSPP(i0); } + +static inline uint32_t REG_MDP5_DSPP_PA_BASE(uint32_t i0) { return 0x00000238 + __offset_DSPP(i0); } + +static inline uint32_t REG_MDP5_DSPP_GAMUT_BASE(uint32_t i0) { return 0x000002dc + __offset_DSPP(i0); } + +static inline uint32_t REG_MDP5_DSPP_GC_BASE(uint32_t i0) { return 0x000002b0 + __offset_DSPP(i0); } + +static inline uint32_t __offset_PP(uint32_t idx) +{ + switch (idx) { + case 0: return (mdp5_cfg->pp.base[0]); + case 1: return (mdp5_cfg->pp.base[1]); + case 2: return (mdp5_cfg->pp.base[2]); + case 3: return (mdp5_cfg->pp.base[3]); + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_PP(uint32_t i0) { return 0x00000000 + __offset_PP(i0); } + +static inline uint32_t REG_MDP5_PP_TEAR_CHECK_EN(uint32_t i0) { return 0x00000000 + __offset_PP(i0); } + +static inline uint32_t REG_MDP5_PP_SYNC_CONFIG_VSYNC(uint32_t i0) { return 0x00000004 + __offset_PP(i0); } +#define MDP5_PP_SYNC_CONFIG_VSYNC_COUNT__MASK 0x0007ffff +#define MDP5_PP_SYNC_CONFIG_VSYNC_COUNT__SHIFT 0 +static inline uint32_t MDP5_PP_SYNC_CONFIG_VSYNC_COUNT(uint32_t val) +{ + return ((val) << MDP5_PP_SYNC_CONFIG_VSYNC_COUNT__SHIFT) & MDP5_PP_SYNC_CONFIG_VSYNC_COUNT__MASK; +} +#define MDP5_PP_SYNC_CONFIG_VSYNC_COUNTER_EN 0x00080000 +#define MDP5_PP_SYNC_CONFIG_VSYNC_IN_EN 0x00100000 + +static inline uint32_t REG_MDP5_PP_SYNC_CONFIG_HEIGHT(uint32_t i0) { return 0x00000008 + __offset_PP(i0); } + +static inline uint32_t REG_MDP5_PP_SYNC_WRCOUNT(uint32_t i0) { return 0x0000000c + __offset_PP(i0); } +#define MDP5_PP_SYNC_WRCOUNT_LINE_COUNT__MASK 0x0000ffff +#define MDP5_PP_SYNC_WRCOUNT_LINE_COUNT__SHIFT 0 +static inline uint32_t MDP5_PP_SYNC_WRCOUNT_LINE_COUNT(uint32_t val) +{ + return ((val) << MDP5_PP_SYNC_WRCOUNT_LINE_COUNT__SHIFT) & MDP5_PP_SYNC_WRCOUNT_LINE_COUNT__MASK; +} +#define MDP5_PP_SYNC_WRCOUNT_FRAME_COUNT__MASK 0xffff0000 +#define MDP5_PP_SYNC_WRCOUNT_FRAME_COUNT__SHIFT 16 +static inline uint32_t MDP5_PP_SYNC_WRCOUNT_FRAME_COUNT(uint32_t val) +{ + return ((val) << MDP5_PP_SYNC_WRCOUNT_FRAME_COUNT__SHIFT) & MDP5_PP_SYNC_WRCOUNT_FRAME_COUNT__MASK; +} + +static inline uint32_t REG_MDP5_PP_VSYNC_INIT_VAL(uint32_t i0) { return 0x00000010 + __offset_PP(i0); } + +static inline uint32_t REG_MDP5_PP_INT_COUNT_VAL(uint32_t i0) { return 0x00000014 + __offset_PP(i0); } +#define MDP5_PP_INT_COUNT_VAL_LINE_COUNT__MASK 0x0000ffff +#define MDP5_PP_INT_COUNT_VAL_LINE_COUNT__SHIFT 0 +static inline uint32_t MDP5_PP_INT_COUNT_VAL_LINE_COUNT(uint32_t val) +{ + return ((val) << MDP5_PP_INT_COUNT_VAL_LINE_COUNT__SHIFT) & MDP5_PP_INT_COUNT_VAL_LINE_COUNT__MASK; +} +#define MDP5_PP_INT_COUNT_VAL_FRAME_COUNT__MASK 0xffff0000 +#define MDP5_PP_INT_COUNT_VAL_FRAME_COUNT__SHIFT 16 +static inline uint32_t MDP5_PP_INT_COUNT_VAL_FRAME_COUNT(uint32_t val) +{ + return ((val) << MDP5_PP_INT_COUNT_VAL_FRAME_COUNT__SHIFT) & MDP5_PP_INT_COUNT_VAL_FRAME_COUNT__MASK; +} + +static inline uint32_t REG_MDP5_PP_SYNC_THRESH(uint32_t i0) { return 0x00000018 + __offset_PP(i0); } +#define MDP5_PP_SYNC_THRESH_START__MASK 0x0000ffff +#define MDP5_PP_SYNC_THRESH_START__SHIFT 0 +static inline uint32_t MDP5_PP_SYNC_THRESH_START(uint32_t val) +{ + return ((val) << MDP5_PP_SYNC_THRESH_START__SHIFT) & MDP5_PP_SYNC_THRESH_START__MASK; +} +#define MDP5_PP_SYNC_THRESH_CONTINUE__MASK 0xffff0000 +#define MDP5_PP_SYNC_THRESH_CONTINUE__SHIFT 16 +static inline uint32_t MDP5_PP_SYNC_THRESH_CONTINUE(uint32_t val) +{ + return ((val) << MDP5_PP_SYNC_THRESH_CONTINUE__SHIFT) & MDP5_PP_SYNC_THRESH_CONTINUE__MASK; +} + +static inline uint32_t REG_MDP5_PP_START_POS(uint32_t i0) { return 0x0000001c + __offset_PP(i0); } + +static inline uint32_t REG_MDP5_PP_RD_PTR_IRQ(uint32_t i0) { return 0x00000020 + __offset_PP(i0); } + +static inline uint32_t REG_MDP5_PP_WR_PTR_IRQ(uint32_t i0) { return 0x00000024 + __offset_PP(i0); } + +static inline uint32_t REG_MDP5_PP_OUT_LINE_COUNT(uint32_t i0) { return 0x00000028 + __offset_PP(i0); } + +static inline uint32_t REG_MDP5_PP_PP_LINE_COUNT(uint32_t i0) { return 0x0000002c + __offset_PP(i0); } + +static inline uint32_t REG_MDP5_PP_AUTOREFRESH_CONFIG(uint32_t i0) { return 0x00000030 + __offset_PP(i0); } + +static inline uint32_t REG_MDP5_PP_FBC_MODE(uint32_t i0) { return 0x00000034 + __offset_PP(i0); } + +static inline uint32_t REG_MDP5_PP_FBC_BUDGET_CTL(uint32_t i0) { return 0x00000038 + __offset_PP(i0); } + +static inline uint32_t REG_MDP5_PP_FBC_LOSSY_MODE(uint32_t i0) { return 0x0000003c + __offset_PP(i0); } + +static inline uint32_t __offset_WB(uint32_t idx) +{ + switch (idx) { +#if 0 /* TEMPORARY until patch that adds wb.base[] is merged */ + case 0: return (mdp5_cfg->wb.base[0]); + case 1: return (mdp5_cfg->wb.base[1]); + case 2: return (mdp5_cfg->wb.base[2]); + case 3: return (mdp5_cfg->wb.base[3]); + case 4: return (mdp5_cfg->wb.base[4]); +#endif + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_WB(uint32_t i0) { return 0x00000000 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_DST_FORMAT(uint32_t i0) { return 0x00000000 + __offset_WB(i0); } +#define MDP5_WB_DST_FORMAT_DSTC0_OUT__MASK 0x00000003 +#define MDP5_WB_DST_FORMAT_DSTC0_OUT__SHIFT 0 +static inline uint32_t MDP5_WB_DST_FORMAT_DSTC0_OUT(uint32_t val) +{ + return ((val) << MDP5_WB_DST_FORMAT_DSTC0_OUT__SHIFT) & MDP5_WB_DST_FORMAT_DSTC0_OUT__MASK; +} +#define MDP5_WB_DST_FORMAT_DSTC1_OUT__MASK 0x0000000c +#define MDP5_WB_DST_FORMAT_DSTC1_OUT__SHIFT 2 +static inline uint32_t MDP5_WB_DST_FORMAT_DSTC1_OUT(uint32_t val) +{ + return ((val) << MDP5_WB_DST_FORMAT_DSTC1_OUT__SHIFT) & MDP5_WB_DST_FORMAT_DSTC1_OUT__MASK; +} +#define MDP5_WB_DST_FORMAT_DSTC2_OUT__MASK 0x00000030 +#define MDP5_WB_DST_FORMAT_DSTC2_OUT__SHIFT 4 +static inline uint32_t MDP5_WB_DST_FORMAT_DSTC2_OUT(uint32_t val) +{ + return ((val) << MDP5_WB_DST_FORMAT_DSTC2_OUT__SHIFT) & MDP5_WB_DST_FORMAT_DSTC2_OUT__MASK; +} +#define MDP5_WB_DST_FORMAT_DSTC3_OUT__MASK 0x000000c0 +#define MDP5_WB_DST_FORMAT_DSTC3_OUT__SHIFT 6 +static inline uint32_t MDP5_WB_DST_FORMAT_DSTC3_OUT(uint32_t val) +{ + return ((val) << MDP5_WB_DST_FORMAT_DSTC3_OUT__SHIFT) & MDP5_WB_DST_FORMAT_DSTC3_OUT__MASK; +} +#define MDP5_WB_DST_FORMAT_DSTC3_EN 0x00000100 +#define MDP5_WB_DST_FORMAT_DST_BPP__MASK 0x00000600 +#define MDP5_WB_DST_FORMAT_DST_BPP__SHIFT 9 +static inline uint32_t MDP5_WB_DST_FORMAT_DST_BPP(uint32_t val) +{ + return ((val) << MDP5_WB_DST_FORMAT_DST_BPP__SHIFT) & MDP5_WB_DST_FORMAT_DST_BPP__MASK; +} +#define MDP5_WB_DST_FORMAT_PACK_COUNT__MASK 0x00003000 +#define MDP5_WB_DST_FORMAT_PACK_COUNT__SHIFT 12 +static inline uint32_t MDP5_WB_DST_FORMAT_PACK_COUNT(uint32_t val) +{ + return ((val) << MDP5_WB_DST_FORMAT_PACK_COUNT__SHIFT) & MDP5_WB_DST_FORMAT_PACK_COUNT__MASK; +} +#define MDP5_WB_DST_FORMAT_DST_ALPHA_X 0x00004000 +#define MDP5_WB_DST_FORMAT_PACK_TIGHT 0x00020000 +#define MDP5_WB_DST_FORMAT_PACK_ALIGN_MSB 0x00040000 +#define MDP5_WB_DST_FORMAT_WRITE_PLANES__MASK 0x00180000 +#define MDP5_WB_DST_FORMAT_WRITE_PLANES__SHIFT 19 +static inline uint32_t MDP5_WB_DST_FORMAT_WRITE_PLANES(uint32_t val) +{ + return ((val) << MDP5_WB_DST_FORMAT_WRITE_PLANES__SHIFT) & MDP5_WB_DST_FORMAT_WRITE_PLANES__MASK; +} +#define MDP5_WB_DST_FORMAT_DST_DITHER_EN 0x00400000 +#define MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP__MASK 0x03800000 +#define MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP__SHIFT 23 +static inline uint32_t MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP(uint32_t val) +{ + return ((val) << MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP__SHIFT) & MDP5_WB_DST_FORMAT_DST_CHROMA_SAMP__MASK; +} +#define MDP5_WB_DST_FORMAT_DST_CHROMA_SITE__MASK 0x3c000000 +#define MDP5_WB_DST_FORMAT_DST_CHROMA_SITE__SHIFT 26 +static inline uint32_t MDP5_WB_DST_FORMAT_DST_CHROMA_SITE(uint32_t val) +{ + return ((val) << MDP5_WB_DST_FORMAT_DST_CHROMA_SITE__SHIFT) & MDP5_WB_DST_FORMAT_DST_CHROMA_SITE__MASK; +} +#define MDP5_WB_DST_FORMAT_FRAME_FORMAT__MASK 0xc0000000 +#define MDP5_WB_DST_FORMAT_FRAME_FORMAT__SHIFT 30 +static inline uint32_t MDP5_WB_DST_FORMAT_FRAME_FORMAT(uint32_t val) +{ + return ((val) << MDP5_WB_DST_FORMAT_FRAME_FORMAT__SHIFT) & MDP5_WB_DST_FORMAT_FRAME_FORMAT__MASK; +} + +static inline uint32_t REG_MDP5_WB_DST_OP_MODE(uint32_t i0) { return 0x00000004 + __offset_WB(i0); } +#define MDP5_WB_DST_OP_MODE_BWC_ENC_EN 0x00000001 +#define MDP5_WB_DST_OP_MODE_BWC_ENC_OP__MASK 0x00000006 +#define MDP5_WB_DST_OP_MODE_BWC_ENC_OP__SHIFT 1 +static inline uint32_t MDP5_WB_DST_OP_MODE_BWC_ENC_OP(uint32_t val) +{ + return ((val) << MDP5_WB_DST_OP_MODE_BWC_ENC_OP__SHIFT) & MDP5_WB_DST_OP_MODE_BWC_ENC_OP__MASK; +} +#define MDP5_WB_DST_OP_MODE_BLOCK_SIZE__MASK 0x00000010 +#define MDP5_WB_DST_OP_MODE_BLOCK_SIZE__SHIFT 4 +static inline uint32_t MDP5_WB_DST_OP_MODE_BLOCK_SIZE(uint32_t val) +{ + return ((val) << MDP5_WB_DST_OP_MODE_BLOCK_SIZE__SHIFT) & MDP5_WB_DST_OP_MODE_BLOCK_SIZE__MASK; +} +#define MDP5_WB_DST_OP_MODE_ROT_MODE__MASK 0x00000020 +#define MDP5_WB_DST_OP_MODE_ROT_MODE__SHIFT 5 +static inline uint32_t MDP5_WB_DST_OP_MODE_ROT_MODE(uint32_t val) +{ + return ((val) << MDP5_WB_DST_OP_MODE_ROT_MODE__SHIFT) & MDP5_WB_DST_OP_MODE_ROT_MODE__MASK; +} +#define MDP5_WB_DST_OP_MODE_ROT_EN 0x00000040 +#define MDP5_WB_DST_OP_MODE_CSC_EN 0x00000100 +#define MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT__MASK 0x00000200 +#define MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT__SHIFT 9 +static inline uint32_t MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT(uint32_t val) +{ + return ((val) << MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT__SHIFT) & MDP5_WB_DST_OP_MODE_CSC_SRC_DATA_FORMAT__MASK; +} +#define MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT__MASK 0x00000400 +#define MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT__SHIFT 10 +static inline uint32_t MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT(uint32_t val) +{ + return ((val) << MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT__SHIFT) & MDP5_WB_DST_OP_MODE_CSC_DST_DATA_FORMAT__MASK; +} +#define MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_EN 0x00000800 +#define MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_FORMAT__MASK 0x00001000 +#define MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_FORMAT__SHIFT 12 +static inline uint32_t MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_FORMAT(uint32_t val) +{ + return ((val) << MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_FORMAT__SHIFT) & MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_FORMAT__MASK; +} +#define MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_H_MTHD__MASK 0x00002000 +#define MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_H_MTHD__SHIFT 13 +static inline uint32_t MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_H_MTHD(uint32_t val) +{ + return ((val) << MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_H_MTHD__SHIFT) & MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_H_MTHD__MASK; +} +#define MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_V_MTHD__MASK 0x00004000 +#define MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_V_MTHD__SHIFT 14 +static inline uint32_t MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_V_MTHD(uint32_t val) +{ + return ((val) << MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_V_MTHD__SHIFT) & MDP5_WB_DST_OP_MODE_CHROMA_DWN_SAMPLE_V_MTHD__MASK; +} + +static inline uint32_t REG_MDP5_WB_DST_PACK_PATTERN(uint32_t i0) { return 0x00000008 + __offset_WB(i0); } +#define MDP5_WB_DST_PACK_PATTERN_ELEMENT0__MASK 0x00000003 +#define MDP5_WB_DST_PACK_PATTERN_ELEMENT0__SHIFT 0 +static inline uint32_t MDP5_WB_DST_PACK_PATTERN_ELEMENT0(uint32_t val) +{ + return ((val) << MDP5_WB_DST_PACK_PATTERN_ELEMENT0__SHIFT) & MDP5_WB_DST_PACK_PATTERN_ELEMENT0__MASK; +} +#define MDP5_WB_DST_PACK_PATTERN_ELEMENT1__MASK 0x00000300 +#define MDP5_WB_DST_PACK_PATTERN_ELEMENT1__SHIFT 8 +static inline uint32_t MDP5_WB_DST_PACK_PATTERN_ELEMENT1(uint32_t val) +{ + return ((val) << MDP5_WB_DST_PACK_PATTERN_ELEMENT1__SHIFT) & MDP5_WB_DST_PACK_PATTERN_ELEMENT1__MASK; +} +#define MDP5_WB_DST_PACK_PATTERN_ELEMENT2__MASK 0x00030000 +#define MDP5_WB_DST_PACK_PATTERN_ELEMENT2__SHIFT 16 +static inline uint32_t MDP5_WB_DST_PACK_PATTERN_ELEMENT2(uint32_t val) +{ + return ((val) << MDP5_WB_DST_PACK_PATTERN_ELEMENT2__SHIFT) & MDP5_WB_DST_PACK_PATTERN_ELEMENT2__MASK; +} +#define MDP5_WB_DST_PACK_PATTERN_ELEMENT3__MASK 0x03000000 +#define MDP5_WB_DST_PACK_PATTERN_ELEMENT3__SHIFT 24 +static inline uint32_t MDP5_WB_DST_PACK_PATTERN_ELEMENT3(uint32_t val) +{ + return ((val) << MDP5_WB_DST_PACK_PATTERN_ELEMENT3__SHIFT) & MDP5_WB_DST_PACK_PATTERN_ELEMENT3__MASK; +} + +static inline uint32_t REG_MDP5_WB_DST0_ADDR(uint32_t i0) { return 0x0000000c + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_DST1_ADDR(uint32_t i0) { return 0x00000010 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_DST2_ADDR(uint32_t i0) { return 0x00000014 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_DST3_ADDR(uint32_t i0) { return 0x00000018 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_DST_YSTRIDE0(uint32_t i0) { return 0x0000001c + __offset_WB(i0); } +#define MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE__MASK 0x0000ffff +#define MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE__SHIFT 0 +static inline uint32_t MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE(uint32_t val) +{ + return ((val) << MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE__SHIFT) & MDP5_WB_DST_YSTRIDE0_DST0_YSTRIDE__MASK; +} +#define MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE__MASK 0xffff0000 +#define MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE__SHIFT 16 +static inline uint32_t MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE(uint32_t val) +{ + return ((val) << MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE__SHIFT) & MDP5_WB_DST_YSTRIDE0_DST1_YSTRIDE__MASK; +} + +static inline uint32_t REG_MDP5_WB_DST_YSTRIDE1(uint32_t i0) { return 0x00000020 + __offset_WB(i0); } +#define MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE__MASK 0x0000ffff +#define MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE__SHIFT 0 +static inline uint32_t MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE(uint32_t val) +{ + return ((val) << MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE__SHIFT) & MDP5_WB_DST_YSTRIDE1_DST2_YSTRIDE__MASK; +} +#define MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE__MASK 0xffff0000 +#define MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE__SHIFT 16 +static inline uint32_t MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE(uint32_t val) +{ + return ((val) << MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE__SHIFT) & MDP5_WB_DST_YSTRIDE1_DST3_YSTRIDE__MASK; +} + +static inline uint32_t REG_MDP5_WB_DST_DITHER_BITDEPTH(uint32_t i0) { return 0x00000024 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_DITHER_MATRIX_ROW0(uint32_t i0) { return 0x00000030 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_DITHER_MATRIX_ROW1(uint32_t i0) { return 0x00000034 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_DITHER_MATRIX_ROW2(uint32_t i0) { return 0x00000038 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_DITHER_MATRIX_ROW3(uint32_t i0) { return 0x0000003c + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_DST_WRITE_CONFIG(uint32_t i0) { return 0x00000048 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_ROTATION_DNSCALER(uint32_t i0) { return 0x00000050 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_N16_INIT_PHASE_X_0_3(uint32_t i0) { return 0x00000060 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_N16_INIT_PHASE_X_1_2(uint32_t i0) { return 0x00000064 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_N16_INIT_PHASE_Y_0_3(uint32_t i0) { return 0x00000068 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_N16_INIT_PHASE_Y_1_2(uint32_t i0) { return 0x0000006c + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_OUT_SIZE(uint32_t i0) { return 0x00000074 + __offset_WB(i0); } +#define MDP5_WB_OUT_SIZE_DST_W__MASK 0x0000ffff +#define MDP5_WB_OUT_SIZE_DST_W__SHIFT 0 +static inline uint32_t MDP5_WB_OUT_SIZE_DST_W(uint32_t val) +{ + return ((val) << MDP5_WB_OUT_SIZE_DST_W__SHIFT) & MDP5_WB_OUT_SIZE_DST_W__MASK; +} +#define MDP5_WB_OUT_SIZE_DST_H__MASK 0xffff0000 +#define MDP5_WB_OUT_SIZE_DST_H__SHIFT 16 +static inline uint32_t MDP5_WB_OUT_SIZE_DST_H(uint32_t val) +{ + return ((val) << MDP5_WB_OUT_SIZE_DST_H__SHIFT) & MDP5_WB_OUT_SIZE_DST_H__MASK; +} + +static inline uint32_t REG_MDP5_WB_ALPHA_X_VALUE(uint32_t i0) { return 0x00000078 + __offset_WB(i0); } + +static inline uint32_t REG_MDP5_WB_CSC_MATRIX_COEFF_0(uint32_t i0) { return 0x00000260 + __offset_WB(i0); } +#define MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11__MASK 0x00001fff +#define MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11__SHIFT 0 +static inline uint32_t MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11__SHIFT) & MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_11__MASK; +} +#define MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12__MASK 0x1fff0000 +#define MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12__SHIFT 16 +static inline uint32_t MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12__SHIFT) & MDP5_WB_CSC_MATRIX_COEFF_0_COEFF_12__MASK; +} + +static inline uint32_t REG_MDP5_WB_CSC_MATRIX_COEFF_1(uint32_t i0) { return 0x00000264 + __offset_WB(i0); } +#define MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13__MASK 0x00001fff +#define MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13__SHIFT 0 +static inline uint32_t MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13__SHIFT) & MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_13__MASK; +} +#define MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21__MASK 0x1fff0000 +#define MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21__SHIFT 16 +static inline uint32_t MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21__SHIFT) & MDP5_WB_CSC_MATRIX_COEFF_1_COEFF_21__MASK; +} + +static inline uint32_t REG_MDP5_WB_CSC_MATRIX_COEFF_2(uint32_t i0) { return 0x00000268 + __offset_WB(i0); } +#define MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22__MASK 0x00001fff +#define MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22__SHIFT 0 +static inline uint32_t MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22__SHIFT) & MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_22__MASK; +} +#define MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23__MASK 0x1fff0000 +#define MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23__SHIFT 16 +static inline uint32_t MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23__SHIFT) & MDP5_WB_CSC_MATRIX_COEFF_2_COEFF_23__MASK; +} + +static inline uint32_t REG_MDP5_WB_CSC_MATRIX_COEFF_3(uint32_t i0) { return 0x0000026c + __offset_WB(i0); } +#define MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31__MASK 0x00001fff +#define MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31__SHIFT 0 +static inline uint32_t MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31__SHIFT) & MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_31__MASK; +} +#define MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32__MASK 0x1fff0000 +#define MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32__SHIFT 16 +static inline uint32_t MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32__SHIFT) & MDP5_WB_CSC_MATRIX_COEFF_3_COEFF_32__MASK; +} + +static inline uint32_t REG_MDP5_WB_CSC_MATRIX_COEFF_4(uint32_t i0) { return 0x00000270 + __offset_WB(i0); } +#define MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33__MASK 0x00001fff +#define MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33__SHIFT 0 +static inline uint32_t MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33__SHIFT) & MDP5_WB_CSC_MATRIX_COEFF_4_COEFF_33__MASK; +} + +static inline uint32_t REG_MDP5_WB_CSC_COMP_PRECLAMP(uint32_t i0, uint32_t i1) { return 0x00000274 + __offset_WB(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP5_WB_CSC_COMP_PRECLAMP_REG(uint32_t i0, uint32_t i1) { return 0x00000274 + __offset_WB(i0) + 0x4*i1; } +#define MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH__MASK 0x000000ff +#define MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH__SHIFT 0 +static inline uint32_t MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH__SHIFT) & MDP5_WB_CSC_COMP_PRECLAMP_REG_HIGH__MASK; +} +#define MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW__MASK 0x0000ff00 +#define MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW__SHIFT 8 +static inline uint32_t MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW__SHIFT) & MDP5_WB_CSC_COMP_PRECLAMP_REG_LOW__MASK; +} + +static inline uint32_t REG_MDP5_WB_CSC_COMP_POSTCLAMP(uint32_t i0, uint32_t i1) { return 0x00000280 + __offset_WB(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP5_WB_CSC_COMP_POSTCLAMP_REG(uint32_t i0, uint32_t i1) { return 0x00000280 + __offset_WB(i0) + 0x4*i1; } +#define MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH__MASK 0x000000ff +#define MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH__SHIFT 0 +static inline uint32_t MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH__SHIFT) & MDP5_WB_CSC_COMP_POSTCLAMP_REG_HIGH__MASK; +} +#define MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW__MASK 0x0000ff00 +#define MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW__SHIFT 8 +static inline uint32_t MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW__SHIFT) & MDP5_WB_CSC_COMP_POSTCLAMP_REG_LOW__MASK; +} + +static inline uint32_t REG_MDP5_WB_CSC_COMP_PREBIAS(uint32_t i0, uint32_t i1) { return 0x0000028c + __offset_WB(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP5_WB_CSC_COMP_PREBIAS_REG(uint32_t i0, uint32_t i1) { return 0x0000028c + __offset_WB(i0) + 0x4*i1; } +#define MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE__MASK 0x000001ff +#define MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE__SHIFT 0 +static inline uint32_t MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE__SHIFT) & MDP5_WB_CSC_COMP_PREBIAS_REG_VALUE__MASK; +} + +static inline uint32_t REG_MDP5_WB_CSC_COMP_POSTBIAS(uint32_t i0, uint32_t i1) { return 0x00000298 + __offset_WB(i0) + 0x4*i1; } + +static inline uint32_t REG_MDP5_WB_CSC_COMP_POSTBIAS_REG(uint32_t i0, uint32_t i1) { return 0x00000298 + __offset_WB(i0) + 0x4*i1; } +#define MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE__MASK 0x000001ff +#define MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE__SHIFT 0 +static inline uint32_t MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE(uint32_t val) +{ + return ((val) << MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE__SHIFT) & MDP5_WB_CSC_COMP_POSTBIAS_REG_VALUE__MASK; +} + +static inline uint32_t __offset_INTF(uint32_t idx) +{ + switch (idx) { + case 0: return (mdp5_cfg->intf.base[0]); + case 1: return (mdp5_cfg->intf.base[1]); + case 2: return (mdp5_cfg->intf.base[2]); + case 3: return (mdp5_cfg->intf.base[3]); + case 4: return (mdp5_cfg->intf.base[4]); + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_INTF(uint32_t i0) { return 0x00000000 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_TIMING_ENGINE_EN(uint32_t i0) { return 0x00000000 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_CONFIG(uint32_t i0) { return 0x00000004 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_HSYNC_CTL(uint32_t i0) { return 0x00000008 + __offset_INTF(i0); } +#define MDP5_INTF_HSYNC_CTL_PULSEW__MASK 0x0000ffff +#define MDP5_INTF_HSYNC_CTL_PULSEW__SHIFT 0 +static inline uint32_t MDP5_INTF_HSYNC_CTL_PULSEW(uint32_t val) +{ + return ((val) << MDP5_INTF_HSYNC_CTL_PULSEW__SHIFT) & MDP5_INTF_HSYNC_CTL_PULSEW__MASK; +} +#define MDP5_INTF_HSYNC_CTL_PERIOD__MASK 0xffff0000 +#define MDP5_INTF_HSYNC_CTL_PERIOD__SHIFT 16 +static inline uint32_t MDP5_INTF_HSYNC_CTL_PERIOD(uint32_t val) +{ + return ((val) << MDP5_INTF_HSYNC_CTL_PERIOD__SHIFT) & MDP5_INTF_HSYNC_CTL_PERIOD__MASK; +} + +static inline uint32_t REG_MDP5_INTF_VSYNC_PERIOD_F0(uint32_t i0) { return 0x0000000c + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_VSYNC_PERIOD_F1(uint32_t i0) { return 0x00000010 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_VSYNC_LEN_F0(uint32_t i0) { return 0x00000014 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_VSYNC_LEN_F1(uint32_t i0) { return 0x00000018 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_DISPLAY_VSTART_F0(uint32_t i0) { return 0x0000001c + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_DISPLAY_VSTART_F1(uint32_t i0) { return 0x00000020 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_DISPLAY_VEND_F0(uint32_t i0) { return 0x00000024 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_DISPLAY_VEND_F1(uint32_t i0) { return 0x00000028 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_ACTIVE_VSTART_F0(uint32_t i0) { return 0x0000002c + __offset_INTF(i0); } +#define MDP5_INTF_ACTIVE_VSTART_F0_VAL__MASK 0x7fffffff +#define MDP5_INTF_ACTIVE_VSTART_F0_VAL__SHIFT 0 +static inline uint32_t MDP5_INTF_ACTIVE_VSTART_F0_VAL(uint32_t val) +{ + return ((val) << MDP5_INTF_ACTIVE_VSTART_F0_VAL__SHIFT) & MDP5_INTF_ACTIVE_VSTART_F0_VAL__MASK; +} +#define MDP5_INTF_ACTIVE_VSTART_F0_ACTIVE_V_ENABLE 0x80000000 + +static inline uint32_t REG_MDP5_INTF_ACTIVE_VSTART_F1(uint32_t i0) { return 0x00000030 + __offset_INTF(i0); } +#define MDP5_INTF_ACTIVE_VSTART_F1_VAL__MASK 0x7fffffff +#define MDP5_INTF_ACTIVE_VSTART_F1_VAL__SHIFT 0 +static inline uint32_t MDP5_INTF_ACTIVE_VSTART_F1_VAL(uint32_t val) +{ + return ((val) << MDP5_INTF_ACTIVE_VSTART_F1_VAL__SHIFT) & MDP5_INTF_ACTIVE_VSTART_F1_VAL__MASK; +} + +static inline uint32_t REG_MDP5_INTF_ACTIVE_VEND_F0(uint32_t i0) { return 0x00000034 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_ACTIVE_VEND_F1(uint32_t i0) { return 0x00000038 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_DISPLAY_HCTL(uint32_t i0) { return 0x0000003c + __offset_INTF(i0); } +#define MDP5_INTF_DISPLAY_HCTL_START__MASK 0x0000ffff +#define MDP5_INTF_DISPLAY_HCTL_START__SHIFT 0 +static inline uint32_t MDP5_INTF_DISPLAY_HCTL_START(uint32_t val) +{ + return ((val) << MDP5_INTF_DISPLAY_HCTL_START__SHIFT) & MDP5_INTF_DISPLAY_HCTL_START__MASK; +} +#define MDP5_INTF_DISPLAY_HCTL_END__MASK 0xffff0000 +#define MDP5_INTF_DISPLAY_HCTL_END__SHIFT 16 +static inline uint32_t MDP5_INTF_DISPLAY_HCTL_END(uint32_t val) +{ + return ((val) << MDP5_INTF_DISPLAY_HCTL_END__SHIFT) & MDP5_INTF_DISPLAY_HCTL_END__MASK; +} + +static inline uint32_t REG_MDP5_INTF_ACTIVE_HCTL(uint32_t i0) { return 0x00000040 + __offset_INTF(i0); } +#define MDP5_INTF_ACTIVE_HCTL_START__MASK 0x00007fff +#define MDP5_INTF_ACTIVE_HCTL_START__SHIFT 0 +static inline uint32_t MDP5_INTF_ACTIVE_HCTL_START(uint32_t val) +{ + return ((val) << MDP5_INTF_ACTIVE_HCTL_START__SHIFT) & MDP5_INTF_ACTIVE_HCTL_START__MASK; +} +#define MDP5_INTF_ACTIVE_HCTL_END__MASK 0x7fff0000 +#define MDP5_INTF_ACTIVE_HCTL_END__SHIFT 16 +static inline uint32_t MDP5_INTF_ACTIVE_HCTL_END(uint32_t val) +{ + return ((val) << MDP5_INTF_ACTIVE_HCTL_END__SHIFT) & MDP5_INTF_ACTIVE_HCTL_END__MASK; +} +#define MDP5_INTF_ACTIVE_HCTL_ACTIVE_H_ENABLE 0x80000000 + +static inline uint32_t REG_MDP5_INTF_BORDER_COLOR(uint32_t i0) { return 0x00000044 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_UNDERFLOW_COLOR(uint32_t i0) { return 0x00000048 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_HSYNC_SKEW(uint32_t i0) { return 0x0000004c + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_POLARITY_CTL(uint32_t i0) { return 0x00000050 + __offset_INTF(i0); } +#define MDP5_INTF_POLARITY_CTL_HSYNC_LOW 0x00000001 +#define MDP5_INTF_POLARITY_CTL_VSYNC_LOW 0x00000002 +#define MDP5_INTF_POLARITY_CTL_DATA_EN_LOW 0x00000004 + +static inline uint32_t REG_MDP5_INTF_TEST_CTL(uint32_t i0) { return 0x00000054 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_TP_COLOR0(uint32_t i0) { return 0x00000058 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_TP_COLOR1(uint32_t i0) { return 0x0000005c + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_DSI_CMD_MODE_TRIGGER_EN(uint32_t i0) { return 0x00000084 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_PANEL_FORMAT(uint32_t i0) { return 0x00000090 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_FRAME_LINE_COUNT_EN(uint32_t i0) { return 0x000000a8 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_FRAME_COUNT(uint32_t i0) { return 0x000000ac + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_LINE_COUNT(uint32_t i0) { return 0x000000b0 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_DEFLICKER_CONFIG(uint32_t i0) { return 0x000000f0 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_DEFLICKER_STRNG_COEFF(uint32_t i0) { return 0x000000f4 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_DEFLICKER_WEAK_COEFF(uint32_t i0) { return 0x000000f8 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_TPG_ENABLE(uint32_t i0) { return 0x00000100 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_TPG_MAIN_CONTROL(uint32_t i0) { return 0x00000104 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_TPG_VIDEO_CONFIG(uint32_t i0) { return 0x00000108 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_TPG_COMPONENT_LIMITS(uint32_t i0) { return 0x0000010c + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_TPG_RECTANGLE(uint32_t i0) { return 0x00000110 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_TPG_INITIAL_VALUE(uint32_t i0) { return 0x00000114 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_TPG_BLK_WHITE_PATTERN_FRAME(uint32_t i0) { return 0x00000118 + __offset_INTF(i0); } + +static inline uint32_t REG_MDP5_INTF_TPG_RGB_MAPPING(uint32_t i0) { return 0x0000011c + __offset_INTF(i0); } + +static inline uint32_t __offset_AD(uint32_t idx) +{ + switch (idx) { + case 0: return (mdp5_cfg->ad.base[0]); + case 1: return (mdp5_cfg->ad.base[1]); + default: return INVALID_IDX(idx); + } +} +static inline uint32_t REG_MDP5_AD(uint32_t i0) { return 0x00000000 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_BYPASS(uint32_t i0) { return 0x00000000 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_CTRL_0(uint32_t i0) { return 0x00000004 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_CTRL_1(uint32_t i0) { return 0x00000008 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_FRAME_SIZE(uint32_t i0) { return 0x0000000c + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_CON_CTRL_0(uint32_t i0) { return 0x00000010 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_CON_CTRL_1(uint32_t i0) { return 0x00000014 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_STR_MAN(uint32_t i0) { return 0x00000018 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_VAR(uint32_t i0) { return 0x0000001c + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_DITH(uint32_t i0) { return 0x00000020 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_DITH_CTRL(uint32_t i0) { return 0x00000024 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_AMP_LIM(uint32_t i0) { return 0x00000028 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_SLOPE(uint32_t i0) { return 0x0000002c + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_BW_LVL(uint32_t i0) { return 0x00000030 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_LOGO_POS(uint32_t i0) { return 0x00000034 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_LUT_FI(uint32_t i0) { return 0x00000038 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_LUT_CC(uint32_t i0) { return 0x0000007c + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_STR_LIM(uint32_t i0) { return 0x000000c8 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_CALIB_AB(uint32_t i0) { return 0x000000cc + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_CALIB_CD(uint32_t i0) { return 0x000000d0 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_MODE_SEL(uint32_t i0) { return 0x000000d4 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_TFILT_CTRL(uint32_t i0) { return 0x000000d8 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_BL_MINMAX(uint32_t i0) { return 0x000000dc + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_BL(uint32_t i0) { return 0x000000e0 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_BL_MAX(uint32_t i0) { return 0x000000e8 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_AL(uint32_t i0) { return 0x000000ec + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_AL_MIN(uint32_t i0) { return 0x000000f0 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_AL_FILT(uint32_t i0) { return 0x000000f4 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_CFG_BUF(uint32_t i0) { return 0x000000f8 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_LUT_AL(uint32_t i0) { return 0x00000100 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_TARG_STR(uint32_t i0) { return 0x00000144 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_START_CALC(uint32_t i0) { return 0x00000148 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_STR_OUT(uint32_t i0) { return 0x0000014c + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_BL_OUT(uint32_t i0) { return 0x00000154 + __offset_AD(i0); } + +static inline uint32_t REG_MDP5_AD_CALC_DONE(uint32_t i0) { return 0x00000158 + __offset_AD(i0); } + + +#endif /* MDP5_XML */ diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c new file mode 100644 index 000000000..df10c1ac7 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.c @@ -0,0 +1,1220 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2015 The Linux Foundation. All rights reserved. + */ + +#include "mdp5_kms.h" +#include "mdp5_cfg.h" + +struct mdp5_cfg_handler { + int revision; + struct mdp5_cfg config; +}; + +/* mdp5_cfg must be exposed (used in mdp5.xml.h) */ +const struct mdp5_cfg_hw *mdp5_cfg = NULL; + +static const struct mdp5_cfg_hw msm8x74v1_config = { + .name = "msm8x74v1", + .mdp = { + .count = 1, + .caps = MDP_CAP_SMP | + 0, + }, + .smp = { + .mmb_count = 22, + .mmb_size = 4096, + .clients = { + [SSPP_VIG0] = 1, [SSPP_VIG1] = 4, [SSPP_VIG2] = 7, + [SSPP_DMA0] = 10, [SSPP_DMA1] = 13, + [SSPP_RGB0] = 16, [SSPP_RGB1] = 17, [SSPP_RGB2] = 18, + }, + }, + .ctl = { + .count = 5, + .base = { 0x00500, 0x00600, 0x00700, 0x00800, 0x00900 }, + .flush_hw_mask = 0x0003ffff, + }, + .pipe_vig = { + .count = 3, + .base = { 0x01100, 0x01500, 0x01900 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | + MDP_PIPE_CAP_CSC | + 0, + }, + .pipe_rgb = { + .count = 3, + .base = { 0x01d00, 0x02100, 0x02500 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | + 0, + }, + .pipe_dma = { + .count = 2, + .base = { 0x02900, 0x02d00 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + 0, + }, + .lm = { + .count = 5, + .base = { 0x03100, 0x03500, 0x03900, 0x03d00, 0x04100 }, + .instances = { + { .id = 0, .pp = 0, .dspp = 0, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 1, .pp = 1, .dspp = 1, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 2, .pp = 2, .dspp = 2, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 3, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB }, + { .id = 4, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB }, + }, + .nb_stages = 5, + .max_width = 2048, + .max_height = 0xFFFF, + }, + .dspp = { + .count = 3, + .base = { 0x04500, 0x04900, 0x04d00 }, + }, + .pp = { + .count = 3, + .base = { 0x21a00, 0x21b00, 0x21c00 }, + }, + .intf = { + .base = { 0x21000, 0x21200, 0x21400, 0x21600 }, + .connect = { + [0] = INTF_eDP, + [1] = INTF_DSI, + [2] = INTF_DSI, + [3] = INTF_HDMI, + }, + }, + .max_clk = 200000000, +}; + +static const struct mdp5_cfg_hw msm8x74v2_config = { + .name = "msm8x74", + .mdp = { + .count = 1, + .caps = MDP_CAP_SMP | + 0, + }, + .smp = { + .mmb_count = 22, + .mmb_size = 4096, + .clients = { + [SSPP_VIG0] = 1, [SSPP_VIG1] = 4, [SSPP_VIG2] = 7, + [SSPP_DMA0] = 10, [SSPP_DMA1] = 13, + [SSPP_RGB0] = 16, [SSPP_RGB1] = 17, [SSPP_RGB2] = 18, + }, + }, + .ctl = { + .count = 5, + .base = { 0x00500, 0x00600, 0x00700, 0x00800, 0x00900 }, + .flush_hw_mask = 0x0003ffff, + }, + .pipe_vig = { + .count = 3, + .base = { 0x01100, 0x01500, 0x01900 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | MDP_PIPE_CAP_CSC | + MDP_PIPE_CAP_DECIMATION, + }, + .pipe_rgb = { + .count = 3, + .base = { 0x01d00, 0x02100, 0x02500 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | MDP_PIPE_CAP_DECIMATION, + }, + .pipe_dma = { + .count = 2, + .base = { 0x02900, 0x02d00 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP, + }, + .lm = { + .count = 5, + .base = { 0x03100, 0x03500, 0x03900, 0x03d00, 0x04100 }, + .instances = { + { .id = 0, .pp = 0, .dspp = 0, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 1, .pp = 1, .dspp = 1, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 2, .pp = 2, .dspp = 2, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 3, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + { .id = 4, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + }, + .nb_stages = 5, + .max_width = 2048, + .max_height = 0xFFFF, + }, + .dspp = { + .count = 3, + .base = { 0x04500, 0x04900, 0x04d00 }, + }, + .ad = { + .count = 2, + .base = { 0x13000, 0x13200 }, + }, + .pp = { + .count = 3, + .base = { 0x12c00, 0x12d00, 0x12e00 }, + }, + .intf = { + .base = { 0x12400, 0x12600, 0x12800, 0x12a00 }, + .connect = { + [0] = INTF_eDP, + [1] = INTF_DSI, + [2] = INTF_DSI, + [3] = INTF_HDMI, + }, + }, + .max_clk = 200000000, +}; + +static const struct mdp5_cfg_hw apq8084_config = { + .name = "apq8084", + .mdp = { + .count = 1, + .caps = MDP_CAP_SMP | + MDP_CAP_SRC_SPLIT | + 0, + }, + .smp = { + .mmb_count = 44, + .mmb_size = 8192, + .clients = { + [SSPP_VIG0] = 1, [SSPP_VIG1] = 4, + [SSPP_VIG2] = 7, [SSPP_VIG3] = 19, + [SSPP_DMA0] = 10, [SSPP_DMA1] = 13, + [SSPP_RGB0] = 16, [SSPP_RGB1] = 17, + [SSPP_RGB2] = 18, [SSPP_RGB3] = 22, + }, + .reserved_state[0] = GENMASK(7, 0), /* first 8 MMBs */ + .reserved = { + /* Two SMP blocks are statically tied to RGB pipes: */ + [16] = 2, [17] = 2, [18] = 2, [22] = 2, + }, + }, + .ctl = { + .count = 5, + .base = { 0x00500, 0x00600, 0x00700, 0x00800, 0x00900 }, + .flush_hw_mask = 0x003fffff, + }, + .pipe_vig = { + .count = 4, + .base = { 0x01100, 0x01500, 0x01900, 0x01d00 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | MDP_PIPE_CAP_CSC | + MDP_PIPE_CAP_DECIMATION, + }, + .pipe_rgb = { + .count = 4, + .base = { 0x02100, 0x02500, 0x02900, 0x02d00 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | MDP_PIPE_CAP_DECIMATION, + }, + .pipe_dma = { + .count = 2, + .base = { 0x03100, 0x03500 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP, + }, + .lm = { + .count = 6, + .base = { 0x03900, 0x03d00, 0x04100, 0x04500, 0x04900, 0x04d00 }, + .instances = { + { .id = 0, .pp = 0, .dspp = 0, + .caps = MDP_LM_CAP_DISPLAY | + MDP_LM_CAP_PAIR, }, + { .id = 1, .pp = 1, .dspp = 1, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 2, .pp = 2, .dspp = 2, + .caps = MDP_LM_CAP_DISPLAY | + MDP_LM_CAP_PAIR, }, + { .id = 3, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + { .id = 4, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + { .id = 5, .pp = 3, .dspp = 3, + .caps = MDP_LM_CAP_DISPLAY, }, + }, + .nb_stages = 5, + .max_width = 2048, + .max_height = 0xFFFF, + }, + .dspp = { + .count = 4, + .base = { 0x05100, 0x05500, 0x05900, 0x05d00 }, + + }, + .ad = { + .count = 3, + .base = { 0x13400, 0x13600, 0x13800 }, + }, + .pp = { + .count = 4, + .base = { 0x12e00, 0x12f00, 0x13000, 0x13100 }, + }, + .intf = { + .base = { 0x12400, 0x12600, 0x12800, 0x12a00, 0x12c00 }, + .connect = { + [0] = INTF_eDP, + [1] = INTF_DSI, + [2] = INTF_DSI, + [3] = INTF_HDMI, + }, + }, + .max_clk = 320000000, +}; + +static const struct mdp5_cfg_hw msm8x16_config = { + .name = "msm8x16", + .mdp = { + .count = 1, + .base = { 0x0 }, + .caps = MDP_CAP_SMP | + 0, + }, + .smp = { + .mmb_count = 8, + .mmb_size = 8192, + .clients = { + [SSPP_VIG0] = 1, [SSPP_DMA0] = 4, + [SSPP_RGB0] = 7, [SSPP_RGB1] = 8, + }, + }, + .ctl = { + .count = 5, + .base = { 0x01000, 0x01200, 0x01400, 0x01600, 0x01800 }, + .flush_hw_mask = 0x4003ffff, + }, + .pipe_vig = { + .count = 1, + .base = { 0x04000 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | MDP_PIPE_CAP_CSC | + MDP_PIPE_CAP_DECIMATION, + }, + .pipe_rgb = { + .count = 2, + .base = { 0x14000, 0x16000 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_DECIMATION, + }, + .pipe_dma = { + .count = 1, + .base = { 0x24000 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP, + }, + .lm = { + .count = 2, /* LM0 and LM3 */ + .base = { 0x44000, 0x47000 }, + .instances = { + { .id = 0, .pp = 0, .dspp = 0, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 3, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB }, + }, + .nb_stages = 8, + .max_width = 2048, + .max_height = 0xFFFF, + }, + .dspp = { + .count = 1, + .base = { 0x54000 }, + + }, + .intf = { + .base = { 0x00000, 0x6a800 }, + .connect = { + [0] = INTF_DISABLED, + [1] = INTF_DSI, + }, + }, + .max_clk = 320000000, +}; + +static const struct mdp5_cfg_hw msm8x36_config = { + .name = "msm8x36", + .mdp = { + .count = 1, + .base = { 0x0 }, + .caps = MDP_CAP_SMP | + 0, + }, + .smp = { + .mmb_count = 8, + .mmb_size = 10240, + .clients = { + [SSPP_VIG0] = 1, [SSPP_DMA0] = 4, + [SSPP_RGB0] = 7, [SSPP_RGB1] = 8, + }, + }, + .ctl = { + .count = 3, + .base = { 0x01000, 0x01200, 0x01400 }, + .flush_hw_mask = 0x4003ffff, + }, + .pipe_vig = { + .count = 1, + .base = { 0x04000 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | MDP_PIPE_CAP_CSC | + MDP_PIPE_CAP_DECIMATION, + }, + .pipe_rgb = { + .count = 2, + .base = { 0x14000, 0x16000 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_DECIMATION, + }, + .pipe_dma = { + .count = 1, + .base = { 0x24000 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP, + }, + .lm = { + .count = 2, + .base = { 0x44000, 0x47000 }, + .instances = { + { .id = 0, .pp = 0, .dspp = 0, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 1, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + }, + .nb_stages = 8, + .max_width = 2560, + .max_height = 0xFFFF, + }, + .pp = { + .count = 1, + .base = { 0x70000 }, + }, + .ad = { + .count = 1, + .base = { 0x78000 }, + }, + .dspp = { + .count = 1, + .base = { 0x54000 }, + }, + .intf = { + .base = { 0x00000, 0x6a800, 0x6b000 }, + .connect = { + [0] = INTF_DISABLED, + [1] = INTF_DSI, + [2] = INTF_DSI, + }, + }, + .max_clk = 366670000, +}; + +static const struct mdp5_cfg_hw msm8x94_config = { + .name = "msm8x94", + .mdp = { + .count = 1, + .caps = MDP_CAP_SMP | + MDP_CAP_SRC_SPLIT | + 0, + }, + .smp = { + .mmb_count = 44, + .mmb_size = 8192, + .clients = { + [SSPP_VIG0] = 1, [SSPP_VIG1] = 4, + [SSPP_VIG2] = 7, [SSPP_VIG3] = 19, + [SSPP_DMA0] = 10, [SSPP_DMA1] = 13, + [SSPP_RGB0] = 16, [SSPP_RGB1] = 17, + [SSPP_RGB2] = 18, [SSPP_RGB3] = 22, + }, + .reserved_state[0] = GENMASK(23, 0), /* first 24 MMBs */ + .reserved = { + [1] = 1, [4] = 1, [7] = 1, [19] = 1, + [16] = 5, [17] = 5, [18] = 5, [22] = 5, + }, + }, + .ctl = { + .count = 5, + .base = { 0x01000, 0x01200, 0x01400, 0x01600, 0x01800 }, + .flush_hw_mask = 0xf0ffffff, + }, + .pipe_vig = { + .count = 4, + .base = { 0x04000, 0x06000, 0x08000, 0x0a000 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | MDP_PIPE_CAP_CSC | + MDP_PIPE_CAP_DECIMATION, + }, + .pipe_rgb = { + .count = 4, + .base = { 0x14000, 0x16000, 0x18000, 0x1a000 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | MDP_PIPE_CAP_DECIMATION, + }, + .pipe_dma = { + .count = 2, + .base = { 0x24000, 0x26000 }, + .caps = MDP_PIPE_CAP_HFLIP | MDP_PIPE_CAP_VFLIP, + }, + .lm = { + .count = 6, + .base = { 0x44000, 0x45000, 0x46000, 0x47000, 0x48000, 0x49000 }, + .instances = { + { .id = 0, .pp = 0, .dspp = 0, + .caps = MDP_LM_CAP_DISPLAY | + MDP_LM_CAP_PAIR, }, + { .id = 1, .pp = 1, .dspp = 1, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 2, .pp = 2, .dspp = 2, + .caps = MDP_LM_CAP_DISPLAY | + MDP_LM_CAP_PAIR, }, + { .id = 3, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + { .id = 4, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + { .id = 5, .pp = 3, .dspp = 3, + .caps = MDP_LM_CAP_DISPLAY, }, + }, + .nb_stages = 8, + .max_width = 2048, + .max_height = 0xFFFF, + }, + .dspp = { + .count = 4, + .base = { 0x54000, 0x56000, 0x58000, 0x5a000 }, + + }, + .ad = { + .count = 3, + .base = { 0x78000, 0x78800, 0x79000 }, + }, + .pp = { + .count = 4, + .base = { 0x70000, 0x70800, 0x71000, 0x71800 }, + }, + .intf = { + .base = { 0x6a000, 0x6a800, 0x6b000, 0x6b800, 0x6c000 }, + .connect = { + [0] = INTF_DISABLED, + [1] = INTF_DSI, + [2] = INTF_DSI, + [3] = INTF_HDMI, + }, + }, + .max_clk = 400000000, +}; + +static const struct mdp5_cfg_hw msm8x96_config = { + .name = "msm8x96", + .mdp = { + .count = 1, + .caps = MDP_CAP_DSC | + MDP_CAP_CDM | + MDP_CAP_SRC_SPLIT | + 0, + }, + .ctl = { + .count = 5, + .base = { 0x01000, 0x01200, 0x01400, 0x01600, 0x01800 }, + .flush_hw_mask = 0xf4ffffff, + }, + .pipe_vig = { + .count = 4, + .base = { 0x04000, 0x06000, 0x08000, 0x0a000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | + MDP_PIPE_CAP_CSC | + MDP_PIPE_CAP_DECIMATION | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_rgb = { + .count = 4, + .base = { 0x14000, 0x16000, 0x18000, 0x1a000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | + MDP_PIPE_CAP_DECIMATION | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_dma = { + .count = 2, + .base = { 0x24000, 0x26000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_cursor = { + .count = 2, + .base = { 0x34000, 0x36000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SW_PIX_EXT | + MDP_PIPE_CAP_CURSOR | + 0, + }, + + .lm = { + .count = 6, + .base = { 0x44000, 0x45000, 0x46000, 0x47000, 0x48000, 0x49000 }, + .instances = { + { .id = 0, .pp = 0, .dspp = 0, + .caps = MDP_LM_CAP_DISPLAY | + MDP_LM_CAP_PAIR, }, + { .id = 1, .pp = 1, .dspp = 1, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 2, .pp = 2, .dspp = -1, + .caps = MDP_LM_CAP_DISPLAY | + MDP_LM_CAP_PAIR, }, + { .id = 3, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + { .id = 4, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + { .id = 5, .pp = 3, .dspp = -1, + .caps = MDP_LM_CAP_DISPLAY, }, + }, + .nb_stages = 8, + .max_width = 2560, + .max_height = 0xFFFF, + }, + .dspp = { + .count = 2, + .base = { 0x54000, 0x56000 }, + }, + .ad = { + .count = 3, + .base = { 0x78000, 0x78800, 0x79000 }, + }, + .pp = { + .count = 4, + .base = { 0x70000, 0x70800, 0x71000, 0x71800 }, + }, + .cdm = { + .count = 1, + .base = { 0x79200 }, + }, + .dsc = { + .count = 2, + .base = { 0x80000, 0x80400 }, + }, + .intf = { + .base = { 0x6a000, 0x6a800, 0x6b000, 0x6b800, 0x6c000 }, + .connect = { + [0] = INTF_DISABLED, + [1] = INTF_DSI, + [2] = INTF_DSI, + [3] = INTF_HDMI, + }, + }, + .max_clk = 412500000, +}; + +const struct mdp5_cfg_hw msm8x76_config = { + .name = "msm8x76", + .mdp = { + .count = 1, + .caps = MDP_CAP_SMP | + MDP_CAP_DSC | + MDP_CAP_SRC_SPLIT | + 0, + }, + .ctl = { + .count = 3, + .base = { 0x01000, 0x01200, 0x01400 }, + .flush_hw_mask = 0xffffffff, + }, + .smp = { + .mmb_count = 10, + .mmb_size = 10240, + .clients = { + [SSPP_VIG0] = 1, [SSPP_VIG1] = 9, + [SSPP_DMA0] = 4, + [SSPP_RGB0] = 7, [SSPP_RGB1] = 8, + }, + }, + .pipe_vig = { + .count = 2, + .base = { 0x04000, 0x06000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | + MDP_PIPE_CAP_CSC | + MDP_PIPE_CAP_DECIMATION | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_rgb = { + .count = 2, + .base = { 0x14000, 0x16000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_DECIMATION | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_dma = { + .count = 1, + .base = { 0x24000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_cursor = { + .count = 1, + .base = { 0x440DC }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SW_PIX_EXT | + MDP_PIPE_CAP_CURSOR | + 0, + }, + .lm = { + .count = 2, + .base = { 0x44000, 0x45000 }, + .instances = { + { .id = 0, .pp = 0, .dspp = 0, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 1, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB }, + }, + .nb_stages = 8, + .max_width = 2560, + .max_height = 0xFFFF, + }, + .dspp = { + .count = 1, + .base = { 0x54000 }, + + }, + .pp = { + .count = 3, + .base = { 0x70000, 0x70800, 0x72000 }, + }, + .dsc = { + .count = 2, + .base = { 0x80000, 0x80400 }, + }, + .intf = { + .base = { 0x6a000, 0x6a800, 0x6b000 }, + .connect = { + [0] = INTF_DISABLED, + [1] = INTF_DSI, + [2] = INTF_DSI, + }, + }, + .max_clk = 360000000, +}; + +static const struct mdp5_cfg_hw msm8917_config = { + .name = "msm8917", + .mdp = { + .count = 1, + .caps = MDP_CAP_CDM, + }, + .ctl = { + .count = 3, + .base = { 0x01000, 0x01200, 0x01400 }, + .flush_hw_mask = 0xffffffff, + }, + .pipe_vig = { + .count = 1, + .base = { 0x04000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | + MDP_PIPE_CAP_CSC | + MDP_PIPE_CAP_DECIMATION | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_rgb = { + .count = 2, + .base = { 0x14000, 0x16000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_DECIMATION | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_dma = { + .count = 1, + .base = { 0x24000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_cursor = { + .count = 1, + .base = { 0x34000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SW_PIX_EXT | + MDP_PIPE_CAP_CURSOR | + 0, + }, + + .lm = { + .count = 2, + .base = { 0x44000, 0x45000 }, + .instances = { + { .id = 0, .pp = 0, .dspp = 0, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 1, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB }, + }, + .nb_stages = 8, + .max_width = 2048, + .max_height = 0xFFFF, + }, + .dspp = { + .count = 1, + .base = { 0x54000 }, + + }, + .pp = { + .count = 1, + .base = { 0x70000 }, + }, + .cdm = { + .count = 1, + .base = { 0x79200 }, + }, + .intf = { + .base = { 0x6a000, 0x6a800 }, + .connect = { + [0] = INTF_DISABLED, + [1] = INTF_DSI, + }, + }, + .max_clk = 320000000, +}; + +static const struct mdp5_cfg_hw msm8998_config = { + .name = "msm8998", + .mdp = { + .count = 1, + .caps = MDP_CAP_DSC | + MDP_CAP_CDM | + MDP_CAP_SRC_SPLIT | + 0, + }, + .ctl = { + .count = 5, + .base = { 0x01000, 0x01200, 0x01400, 0x01600, 0x01800 }, + .flush_hw_mask = 0xf7ffffff, + }, + .pipe_vig = { + .count = 4, + .base = { 0x04000, 0x06000, 0x08000, 0x0a000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | + MDP_PIPE_CAP_CSC | + MDP_PIPE_CAP_DECIMATION | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_rgb = { + .count = 4, + .base = { 0x14000, 0x16000, 0x18000, 0x1a000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | + MDP_PIPE_CAP_DECIMATION | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_dma = { + .count = 2, /* driver supports max of 2 currently */ + .base = { 0x24000, 0x26000, 0x28000, 0x2a000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_cursor = { + .count = 2, + .base = { 0x34000, 0x36000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SW_PIX_EXT | + MDP_PIPE_CAP_CURSOR | + 0, + }, + + .lm = { + .count = 6, + .base = { 0x44000, 0x45000, 0x46000, 0x47000, 0x48000, 0x49000 }, + .instances = { + { .id = 0, .pp = 0, .dspp = 0, + .caps = MDP_LM_CAP_DISPLAY | + MDP_LM_CAP_PAIR, }, + { .id = 1, .pp = 1, .dspp = 1, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 2, .pp = 2, .dspp = -1, + .caps = MDP_LM_CAP_DISPLAY | + MDP_LM_CAP_PAIR, }, + { .id = 3, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + { .id = 4, .pp = -1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + { .id = 5, .pp = 3, .dspp = -1, + .caps = MDP_LM_CAP_DISPLAY, }, + }, + .nb_stages = 8, + .max_width = 2560, + .max_height = 0xFFFF, + }, + .dspp = { + .count = 2, + .base = { 0x54000, 0x56000 }, + }, + .ad = { + .count = 3, + .base = { 0x78000, 0x78800, 0x79000 }, + }, + .pp = { + .count = 4, + .base = { 0x70000, 0x70800, 0x71000, 0x71800 }, + }, + .cdm = { + .count = 1, + .base = { 0x79200 }, + }, + .dsc = { + .count = 2, + .base = { 0x80000, 0x80400 }, + }, + .intf = { + .base = { 0x6a000, 0x6a800, 0x6b000, 0x6b800, 0x6c000 }, + .connect = { + [0] = INTF_eDP, + [1] = INTF_DSI, + [2] = INTF_DSI, + [3] = INTF_HDMI, + }, + }, + .max_clk = 412500000, +}; + +static const struct mdp5_cfg_hw sdm630_config = { + .name = "sdm630", + .mdp = { + .count = 1, + .caps = MDP_CAP_CDM | + MDP_CAP_SRC_SPLIT | + 0, + }, + .ctl = { + .count = 5, + .base = { 0x01000, 0x01200, 0x01400, 0x01600, 0x01800 }, + .flush_hw_mask = 0xf4ffffff, + }, + .pipe_vig = { + .count = 1, + .base = { 0x04000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | + MDP_PIPE_CAP_CSC | + MDP_PIPE_CAP_DECIMATION | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_rgb = { + .count = 4, + .base = { 0x14000, 0x16000, 0x18000, 0x1a000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | + MDP_PIPE_CAP_DECIMATION | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_dma = { + .count = 2, /* driver supports max of 2 currently */ + .base = { 0x24000, 0x26000, 0x28000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_cursor = { + .count = 1, + .base = { 0x34000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SW_PIX_EXT | + MDP_PIPE_CAP_CURSOR | + 0, + }, + + .lm = { + .count = 2, + .base = { 0x44000, 0x46000 }, + .instances = { + { .id = 0, .pp = 0, .dspp = 0, + .caps = MDP_LM_CAP_DISPLAY | + MDP_LM_CAP_PAIR, }, + { .id = 1, .pp = 1, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + }, + .nb_stages = 8, + .max_width = 2048, + .max_height = 0xFFFF, + }, + .dspp = { + .count = 1, + .base = { 0x54000 }, + }, + .ad = { + .count = 2, + .base = { 0x78000, 0x78800 }, + }, + .pp = { + .count = 3, + .base = { 0x70000, 0x71000, 0x72000 }, + }, + .cdm = { + .count = 1, + .base = { 0x79200 }, + }, + .intf = { + .base = { 0x6a000, 0x6a800 }, + .connect = { + [0] = INTF_DISABLED, + [1] = INTF_DSI, + }, + }, + .max_clk = 412500000, +}; + +static const struct mdp5_cfg_hw sdm660_config = { + .name = "sdm660", + .mdp = { + .count = 1, + .caps = MDP_CAP_DSC | + MDP_CAP_CDM | + MDP_CAP_SRC_SPLIT | + 0, + }, + .ctl = { + .count = 5, + .base = { 0x01000, 0x01200, 0x01400, 0x01600, 0x01800 }, + .flush_hw_mask = 0xf4ffffff, + }, + .pipe_vig = { + .count = 2, + .base = { 0x04000, 0x6000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | + MDP_PIPE_CAP_CSC | + MDP_PIPE_CAP_DECIMATION | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_rgb = { + .count = 4, + .base = { 0x14000, 0x16000, 0x18000, 0x1a000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SCALE | + MDP_PIPE_CAP_DECIMATION | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_dma = { + .count = 2, /* driver supports max of 2 currently */ + .base = { 0x24000, 0x26000, 0x28000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SW_PIX_EXT | + 0, + }, + .pipe_cursor = { + .count = 1, + .base = { 0x34000 }, + .caps = MDP_PIPE_CAP_HFLIP | + MDP_PIPE_CAP_VFLIP | + MDP_PIPE_CAP_SW_PIX_EXT | + MDP_PIPE_CAP_CURSOR | + 0, + }, + + .lm = { + .count = 4, + .base = { 0x44000, 0x45000, 0x46000, 0x49000 }, + .instances = { + { .id = 0, .pp = 0, .dspp = 0, + .caps = MDP_LM_CAP_DISPLAY | + MDP_LM_CAP_PAIR, }, + { .id = 1, .pp = 1, .dspp = 1, + .caps = MDP_LM_CAP_DISPLAY, }, + { .id = 2, .pp = 2, .dspp = -1, + .caps = MDP_LM_CAP_DISPLAY | + MDP_LM_CAP_PAIR, }, + { .id = 3, .pp = 3, .dspp = -1, + .caps = MDP_LM_CAP_WB, }, + }, + .nb_stages = 8, + .max_width = 2560, + .max_height = 0xFFFF, + }, + .dspp = { + .count = 2, + .base = { 0x54000, 0x56000 }, + }, + .ad = { + .count = 2, + .base = { 0x78000, 0x78800 }, + }, + .pp = { + .count = 5, + .base = { 0x70000, 0x70800, 0x71000, 0x71800, 0x72000 }, + }, + .cdm = { + .count = 1, + .base = { 0x79200 }, + }, + .dsc = { + .count = 2, + .base = { 0x80000, 0x80400 }, + }, + .intf = { + .base = { 0x6a000, 0x6a800, 0x6b000, 0x6b800 }, + .connect = { + [0] = INTF_DISABLED, + [1] = INTF_DSI, + [2] = INTF_DSI, + [3] = INTF_HDMI, + }, + }, + .max_clk = 412500000, +}; + +static const struct mdp5_cfg_handler cfg_handlers_v1[] = { + { .revision = 0, .config = { .hw = &msm8x74v1_config } }, + { .revision = 2, .config = { .hw = &msm8x74v2_config } }, + { .revision = 3, .config = { .hw = &apq8084_config } }, + { .revision = 6, .config = { .hw = &msm8x16_config } }, + { .revision = 8, .config = { .hw = &msm8x36_config } }, + { .revision = 9, .config = { .hw = &msm8x94_config } }, + { .revision = 7, .config = { .hw = &msm8x96_config } }, + { .revision = 11, .config = { .hw = &msm8x76_config } }, + { .revision = 15, .config = { .hw = &msm8917_config } }, +}; + +static const struct mdp5_cfg_handler cfg_handlers_v3[] = { + { .revision = 0, .config = { .hw = &msm8998_config } }, + { .revision = 2, .config = { .hw = &sdm660_config } }, + { .revision = 3, .config = { .hw = &sdm630_config } }, +}; + +static struct mdp5_cfg_platform *mdp5_get_config(struct platform_device *dev); + +const struct mdp5_cfg_hw *mdp5_cfg_get_hw_config(struct mdp5_cfg_handler *cfg_handler) +{ + return cfg_handler->config.hw; +} + +struct mdp5_cfg *mdp5_cfg_get_config(struct mdp5_cfg_handler *cfg_handler) +{ + return &cfg_handler->config; +} + +int mdp5_cfg_get_hw_rev(struct mdp5_cfg_handler *cfg_handler) +{ + return cfg_handler->revision; +} + +void mdp5_cfg_destroy(struct mdp5_cfg_handler *cfg_handler) +{ + kfree(cfg_handler); +} + +struct mdp5_cfg_handler *mdp5_cfg_init(struct mdp5_kms *mdp5_kms, + uint32_t major, uint32_t minor) +{ + struct drm_device *dev = mdp5_kms->dev; + struct platform_device *pdev = to_platform_device(dev->dev); + struct mdp5_cfg_handler *cfg_handler; + const struct mdp5_cfg_handler *cfg_handlers; + struct mdp5_cfg_platform *pconfig; + int i, ret = 0, num_handlers; + + cfg_handler = kzalloc(sizeof(*cfg_handler), GFP_KERNEL); + if (unlikely(!cfg_handler)) { + ret = -ENOMEM; + goto fail; + } + + switch (major) { + case 1: + cfg_handlers = cfg_handlers_v1; + num_handlers = ARRAY_SIZE(cfg_handlers_v1); + break; + case 3: + cfg_handlers = cfg_handlers_v3; + num_handlers = ARRAY_SIZE(cfg_handlers_v3); + break; + default: + DRM_DEV_ERROR(dev->dev, "unexpected MDP major version: v%d.%d\n", + major, minor); + ret = -ENXIO; + goto fail; + } + + /* only after mdp5_cfg global pointer's init can we access the hw */ + for (i = 0; i < num_handlers; i++) { + if (cfg_handlers[i].revision != minor) + continue; + mdp5_cfg = cfg_handlers[i].config.hw; + + break; + } + if (unlikely(!mdp5_cfg)) { + DRM_DEV_ERROR(dev->dev, "unexpected MDP minor revision: v%d.%d\n", + major, minor); + ret = -ENXIO; + goto fail; + } + + cfg_handler->revision = minor; + cfg_handler->config.hw = mdp5_cfg; + + pconfig = mdp5_get_config(pdev); + memcpy(&cfg_handler->config.platform, pconfig, sizeof(*pconfig)); + + DBG("MDP5: %s hw config selected", mdp5_cfg->name); + + return cfg_handler; + +fail: + if (cfg_handler) + mdp5_cfg_destroy(cfg_handler); + + return ERR_PTR(ret); +} + +static struct mdp5_cfg_platform *mdp5_get_config(struct platform_device *dev) +{ + static struct mdp5_cfg_platform config = {}; + + config.iommu = iommu_domain_alloc(&platform_bus_type); + + return &config; +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h new file mode 100644 index 000000000..1c50d01f1 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cfg.h @@ -0,0 +1,125 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014 The Linux Foundation. All rights reserved. + */ + +#ifndef __MDP5_CFG_H__ +#define __MDP5_CFG_H__ + +#include "msm_drv.h" + +/* + * mdp5_cfg + * + * This module configures the dynamic offsets used by mdp5.xml.h + * (initialized in mdp5_cfg.c) + */ +extern const struct mdp5_cfg_hw *mdp5_cfg; + +#define MAX_CTL 8 +#define MAX_BASES 8 +#define MAX_SMP_BLOCKS 44 +#define MAX_CLIENTS 32 + +typedef DECLARE_BITMAP(mdp5_smp_state_t, MAX_SMP_BLOCKS); + +#define MDP5_SUB_BLOCK_DEFINITION \ + unsigned int count; \ + uint32_t base[MAX_BASES] + +struct mdp5_sub_block { + MDP5_SUB_BLOCK_DEFINITION; +}; + +struct mdp5_lm_instance { + int id; + int pp; + int dspp; + uint32_t caps; +}; + +struct mdp5_lm_block { + MDP5_SUB_BLOCK_DEFINITION; + struct mdp5_lm_instance instances[MAX_BASES]; + uint32_t nb_stages; /* number of stages per blender */ + uint32_t max_width; /* Maximum output resolution */ + uint32_t max_height; +}; + +struct mdp5_pipe_block { + MDP5_SUB_BLOCK_DEFINITION; + uint32_t caps; /* pipe capabilities */ +}; + +struct mdp5_ctl_block { + MDP5_SUB_BLOCK_DEFINITION; + uint32_t flush_hw_mask; /* FLUSH register's hardware mask */ +}; + +struct mdp5_smp_block { + int mmb_count; /* number of SMP MMBs */ + int mmb_size; /* MMB: size in bytes */ + uint32_t clients[MAX_CLIENTS]; /* SMP port allocation /pipe */ + mdp5_smp_state_t reserved_state;/* SMP MMBs statically allocated */ + uint8_t reserved[MAX_CLIENTS]; /* # of MMBs allocated per client */ +}; + +struct mdp5_mdp_block { + MDP5_SUB_BLOCK_DEFINITION; + uint32_t caps; /* MDP capabilities: MDP_CAP_xxx bits */ +}; + +#define MDP5_INTF_NUM_MAX 5 + +struct mdp5_intf_block { + uint32_t base[MAX_BASES]; + u32 connect[MDP5_INTF_NUM_MAX]; /* array of enum mdp5_intf_type */ +}; + +struct mdp5_cfg_hw { + char *name; + + struct mdp5_mdp_block mdp; + struct mdp5_smp_block smp; + struct mdp5_ctl_block ctl; + struct mdp5_pipe_block pipe_vig; + struct mdp5_pipe_block pipe_rgb; + struct mdp5_pipe_block pipe_dma; + struct mdp5_pipe_block pipe_cursor; + struct mdp5_lm_block lm; + struct mdp5_sub_block dspp; + struct mdp5_sub_block ad; + struct mdp5_sub_block pp; + struct mdp5_sub_block dsc; + struct mdp5_sub_block cdm; + struct mdp5_intf_block intf; + + uint32_t max_clk; +}; + +/* platform config data (ie. from DT, or pdata) */ +struct mdp5_cfg_platform { + struct iommu_domain *iommu; +}; + +struct mdp5_cfg { + const struct mdp5_cfg_hw *hw; + struct mdp5_cfg_platform platform; +}; + +struct mdp5_kms; +struct mdp5_cfg_handler; + +const struct mdp5_cfg_hw *mdp5_cfg_get_hw_config(struct mdp5_cfg_handler *cfg_hnd); +struct mdp5_cfg *mdp5_cfg_get_config(struct mdp5_cfg_handler *cfg_hnd); +int mdp5_cfg_get_hw_rev(struct mdp5_cfg_handler *cfg_hnd); + +#define mdp5_cfg_intf_is_virtual(intf_type) ({ \ + typeof(intf_type) __val = (intf_type); \ + (__val) >= INTF_VIRTUAL ? true : false; }) + +struct mdp5_cfg_handler *mdp5_cfg_init(struct mdp5_kms *mdp5_kms, + uint32_t major, uint32_t minor); +void mdp5_cfg_destroy(struct mdp5_cfg_handler *cfg_hnd); + +#endif /* __MDP5_CFG_H__ */ diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_cmd_encoder.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cmd_encoder.c new file mode 100644 index 000000000..0392d4dfe --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_cmd_encoder.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2015, The Linux Foundation. All rights reserved. + */ + +#include <drm/drm_crtc.h> +#include <drm/drm_probe_helper.h> + +#include "mdp5_kms.h" + +static struct mdp5_kms *get_kms(struct drm_encoder *encoder) +{ + struct msm_drm_private *priv = encoder->dev->dev_private; + return to_mdp5_kms(to_mdp_kms(priv->kms)); +} + +#define VSYNC_CLK_RATE 19200000 +static int pingpong_tearcheck_setup(struct drm_encoder *encoder, + struct drm_display_mode *mode) +{ + struct mdp5_kms *mdp5_kms = get_kms(encoder); + struct device *dev = encoder->dev->dev; + u32 total_lines, vclks_line, cfg; + long vsync_clk_speed; + struct mdp5_hw_mixer *mixer = mdp5_crtc_get_mixer(encoder->crtc); + int pp_id = mixer->pp; + + if (IS_ERR_OR_NULL(mdp5_kms->vsync_clk)) { + DRM_DEV_ERROR(dev, "vsync_clk is not initialized\n"); + return -EINVAL; + } + + total_lines = mode->vtotal * drm_mode_vrefresh(mode); + if (!total_lines) { + DRM_DEV_ERROR(dev, "%s: vtotal(%d) or vrefresh(%d) is 0\n", + __func__, mode->vtotal, drm_mode_vrefresh(mode)); + return -EINVAL; + } + + vsync_clk_speed = clk_round_rate(mdp5_kms->vsync_clk, VSYNC_CLK_RATE); + if (vsync_clk_speed <= 0) { + DRM_DEV_ERROR(dev, "vsync_clk round rate failed %ld\n", + vsync_clk_speed); + return -EINVAL; + } + vclks_line = vsync_clk_speed / total_lines; + + cfg = MDP5_PP_SYNC_CONFIG_VSYNC_COUNTER_EN + | MDP5_PP_SYNC_CONFIG_VSYNC_IN_EN; + cfg |= MDP5_PP_SYNC_CONFIG_VSYNC_COUNT(vclks_line); + + /* + * Tearcheck emits a blanking signal every vclks_line * vtotal * 2 ticks on + * the vsync_clk equating to roughly half the desired panel refresh rate. + * This is only necessary as stability fallback if interrupts from the + * panel arrive too late or not at all, but is currently used by default + * because these panel interrupts are not wired up yet. + */ + mdp5_write(mdp5_kms, REG_MDP5_PP_SYNC_CONFIG_VSYNC(pp_id), cfg); + mdp5_write(mdp5_kms, + REG_MDP5_PP_SYNC_CONFIG_HEIGHT(pp_id), (2 * mode->vtotal)); + + mdp5_write(mdp5_kms, + REG_MDP5_PP_VSYNC_INIT_VAL(pp_id), mode->vdisplay); + mdp5_write(mdp5_kms, REG_MDP5_PP_RD_PTR_IRQ(pp_id), mode->vdisplay + 1); + mdp5_write(mdp5_kms, REG_MDP5_PP_START_POS(pp_id), mode->vdisplay); + mdp5_write(mdp5_kms, REG_MDP5_PP_SYNC_THRESH(pp_id), + MDP5_PP_SYNC_THRESH_START(4) | + MDP5_PP_SYNC_THRESH_CONTINUE(4)); + + return 0; +} + +static int pingpong_tearcheck_enable(struct drm_encoder *encoder) +{ + struct mdp5_kms *mdp5_kms = get_kms(encoder); + struct mdp5_hw_mixer *mixer = mdp5_crtc_get_mixer(encoder->crtc); + int pp_id = mixer->pp; + int ret; + + ret = clk_set_rate(mdp5_kms->vsync_clk, + clk_round_rate(mdp5_kms->vsync_clk, VSYNC_CLK_RATE)); + if (ret) { + DRM_DEV_ERROR(encoder->dev->dev, + "vsync_clk clk_set_rate failed, %d\n", ret); + return ret; + } + ret = clk_prepare_enable(mdp5_kms->vsync_clk); + if (ret) { + DRM_DEV_ERROR(encoder->dev->dev, + "vsync_clk clk_prepare_enable failed, %d\n", ret); + return ret; + } + + mdp5_write(mdp5_kms, REG_MDP5_PP_TEAR_CHECK_EN(pp_id), 1); + + return 0; +} + +static void pingpong_tearcheck_disable(struct drm_encoder *encoder) +{ + struct mdp5_kms *mdp5_kms = get_kms(encoder); + struct mdp5_hw_mixer *mixer = mdp5_crtc_get_mixer(encoder->crtc); + int pp_id = mixer->pp; + + mdp5_write(mdp5_kms, REG_MDP5_PP_TEAR_CHECK_EN(pp_id), 0); + clk_disable_unprepare(mdp5_kms->vsync_clk); +} + +void mdp5_cmd_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + mode = adjusted_mode; + + DBG("set mode: " DRM_MODE_FMT, DRM_MODE_ARG(mode)); + pingpong_tearcheck_setup(encoder, mode); + mdp5_crtc_set_pipeline(encoder->crtc); +} + +void mdp5_cmd_encoder_disable(struct drm_encoder *encoder) +{ + struct mdp5_encoder *mdp5_cmd_enc = to_mdp5_encoder(encoder); + struct mdp5_ctl *ctl = mdp5_cmd_enc->ctl; + struct mdp5_interface *intf = mdp5_cmd_enc->intf; + struct mdp5_pipeline *pipeline = mdp5_crtc_get_pipeline(encoder->crtc); + + if (WARN_ON(!mdp5_cmd_enc->enabled)) + return; + + pingpong_tearcheck_disable(encoder); + + mdp5_ctl_set_encoder_state(ctl, pipeline, false); + mdp5_ctl_commit(ctl, pipeline, mdp_ctl_flush_mask_encoder(intf), true); + + mdp5_cmd_enc->enabled = false; +} + +void mdp5_cmd_encoder_enable(struct drm_encoder *encoder) +{ + struct mdp5_encoder *mdp5_cmd_enc = to_mdp5_encoder(encoder); + struct mdp5_ctl *ctl = mdp5_cmd_enc->ctl; + struct mdp5_interface *intf = mdp5_cmd_enc->intf; + struct mdp5_pipeline *pipeline = mdp5_crtc_get_pipeline(encoder->crtc); + + if (WARN_ON(mdp5_cmd_enc->enabled)) + return; + + if (pingpong_tearcheck_enable(encoder)) + return; + + mdp5_ctl_commit(ctl, pipeline, mdp_ctl_flush_mask_encoder(intf), true); + + mdp5_ctl_set_encoder_state(ctl, pipeline, true); + + mdp5_cmd_enc->enabled = true; +} + +int mdp5_cmd_encoder_set_split_display(struct drm_encoder *encoder, + struct drm_encoder *slave_encoder) +{ + struct mdp5_encoder *mdp5_cmd_enc = to_mdp5_encoder(encoder); + struct mdp5_kms *mdp5_kms; + struct device *dev; + int intf_num; + u32 data = 0; + + if (!encoder || !slave_encoder) + return -EINVAL; + + mdp5_kms = get_kms(encoder); + intf_num = mdp5_cmd_enc->intf->num; + + /* Switch slave encoder's trigger MUX, to use the master's + * start signal for the slave encoder + */ + if (intf_num == 1) + data |= MDP5_SPLIT_DPL_UPPER_INTF2_SW_TRG_MUX; + else if (intf_num == 2) + data |= MDP5_SPLIT_DPL_UPPER_INTF1_SW_TRG_MUX; + else + return -EINVAL; + + /* Smart Panel, Sync mode */ + data |= MDP5_SPLIT_DPL_UPPER_SMART_PANEL; + + dev = &mdp5_kms->pdev->dev; + + /* Make sure clocks are on when connectors calling this function. */ + pm_runtime_get_sync(dev); + mdp5_write(mdp5_kms, REG_MDP5_SPLIT_DPL_UPPER, data); + + mdp5_write(mdp5_kms, REG_MDP5_SPLIT_DPL_LOWER, + MDP5_SPLIT_DPL_LOWER_SMART_PANEL); + mdp5_write(mdp5_kms, REG_MDP5_SPLIT_DPL_EN, 1); + pm_runtime_put_sync(dev); + + return 0; +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c new file mode 100644 index 000000000..60e7371cd --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_crtc.c @@ -0,0 +1,1345 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2015 The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <linux/sort.h> + +#include <drm/drm_mode.h> +#include <drm/drm_crtc.h> +#include <drm/drm_flip_work.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_probe_helper.h> +#include <drm/drm_vblank.h> + +#include "mdp5_kms.h" + +#define CURSOR_WIDTH 64 +#define CURSOR_HEIGHT 64 + +struct mdp5_crtc { + struct drm_crtc base; + int id; + bool enabled; + + spinlock_t lm_lock; /* protect REG_MDP5_LM_* registers */ + + /* if there is a pending flip, these will be non-null: */ + struct drm_pending_vblank_event *event; + + /* Bits have been flushed at the last commit, + * used to decide if a vsync has happened since last commit. + */ + u32 flushed_mask; + +#define PENDING_CURSOR 0x1 +#define PENDING_FLIP 0x2 + atomic_t pending; + + /* for unref'ing cursor bo's after scanout completes: */ + struct drm_flip_work unref_cursor_work; + + struct mdp_irq vblank; + struct mdp_irq err; + struct mdp_irq pp_done; + + struct completion pp_completion; + + bool lm_cursor_enabled; + + struct { + /* protect REG_MDP5_LM_CURSOR* registers and cursor scanout_bo*/ + spinlock_t lock; + + /* current cursor being scanned out: */ + struct drm_gem_object *scanout_bo; + uint64_t iova; + uint32_t width, height; + int x, y; + } cursor; +}; +#define to_mdp5_crtc(x) container_of(x, struct mdp5_crtc, base) + +static void mdp5_crtc_restore_cursor(struct drm_crtc *crtc); + +static struct mdp5_kms *get_kms(struct drm_crtc *crtc) +{ + struct msm_drm_private *priv = crtc->dev->dev_private; + return to_mdp5_kms(to_mdp_kms(priv->kms)); +} + +static void request_pending(struct drm_crtc *crtc, uint32_t pending) +{ + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + + atomic_or(pending, &mdp5_crtc->pending); + mdp_irq_register(&get_kms(crtc)->base, &mdp5_crtc->vblank); +} + +static void request_pp_done_pending(struct drm_crtc *crtc) +{ + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + reinit_completion(&mdp5_crtc->pp_completion); +} + +static u32 crtc_flush(struct drm_crtc *crtc, u32 flush_mask) +{ + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct mdp5_ctl *ctl = mdp5_cstate->ctl; + struct mdp5_pipeline *pipeline = &mdp5_cstate->pipeline; + bool start = !mdp5_cstate->defer_start; + + mdp5_cstate->defer_start = false; + + DBG("%s: flush=%08x", crtc->name, flush_mask); + + return mdp5_ctl_commit(ctl, pipeline, flush_mask, start); +} + +/* + * flush updates, to make sure hw is updated to new scanout fb, + * so that we can safely queue unref to current fb (ie. next + * vblank we know hw is done w/ previous scanout_fb). + */ +static u32 crtc_flush_all(struct drm_crtc *crtc) +{ + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct mdp5_hw_mixer *mixer, *r_mixer; + struct drm_plane *plane; + uint32_t flush_mask = 0; + + /* this should not happen: */ + if (WARN_ON(!mdp5_cstate->ctl)) + return 0; + + drm_atomic_crtc_for_each_plane(plane, crtc) { + if (!plane->state->visible) + continue; + flush_mask |= mdp5_plane_get_flush(plane); + } + + mixer = mdp5_cstate->pipeline.mixer; + flush_mask |= mdp_ctl_flush_mask_lm(mixer->lm); + + r_mixer = mdp5_cstate->pipeline.r_mixer; + if (r_mixer) + flush_mask |= mdp_ctl_flush_mask_lm(r_mixer->lm); + + return crtc_flush(crtc, flush_mask); +} + +/* if file!=NULL, this is preclose potential cancel-flip path */ +static void complete_flip(struct drm_crtc *crtc, struct drm_file *file) +{ + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct mdp5_pipeline *pipeline = &mdp5_cstate->pipeline; + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + struct mdp5_ctl *ctl = mdp5_cstate->ctl; + struct drm_device *dev = crtc->dev; + struct drm_pending_vblank_event *event; + unsigned long flags; + + spin_lock_irqsave(&dev->event_lock, flags); + event = mdp5_crtc->event; + if (event) { + mdp5_crtc->event = NULL; + DBG("%s: send event: %p", crtc->name, event); + drm_crtc_send_vblank_event(crtc, event); + } + spin_unlock_irqrestore(&dev->event_lock, flags); + + if (ctl && !crtc->state->enable) { + /* set STAGE_UNUSED for all layers */ + mdp5_ctl_blend(ctl, pipeline, NULL, NULL, 0, 0); + /* XXX: What to do here? */ + /* mdp5_crtc->ctl = NULL; */ + } +} + +static void unref_cursor_worker(struct drm_flip_work *work, void *val) +{ + struct mdp5_crtc *mdp5_crtc = + container_of(work, struct mdp5_crtc, unref_cursor_work); + struct mdp5_kms *mdp5_kms = get_kms(&mdp5_crtc->base); + struct msm_kms *kms = &mdp5_kms->base.base; + + msm_gem_unpin_iova(val, kms->aspace); + drm_gem_object_put(val); +} + +static void mdp5_crtc_destroy(struct drm_crtc *crtc) +{ + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + + drm_crtc_cleanup(crtc); + drm_flip_work_cleanup(&mdp5_crtc->unref_cursor_work); + + kfree(mdp5_crtc); +} + +static inline u32 mdp5_lm_use_fg_alpha_mask(enum mdp_mixer_stage_id stage) +{ + switch (stage) { + case STAGE0: return MDP5_LM_BLEND_COLOR_OUT_STAGE0_FG_ALPHA; + case STAGE1: return MDP5_LM_BLEND_COLOR_OUT_STAGE1_FG_ALPHA; + case STAGE2: return MDP5_LM_BLEND_COLOR_OUT_STAGE2_FG_ALPHA; + case STAGE3: return MDP5_LM_BLEND_COLOR_OUT_STAGE3_FG_ALPHA; + case STAGE4: return MDP5_LM_BLEND_COLOR_OUT_STAGE4_FG_ALPHA; + case STAGE5: return MDP5_LM_BLEND_COLOR_OUT_STAGE5_FG_ALPHA; + case STAGE6: return MDP5_LM_BLEND_COLOR_OUT_STAGE6_FG_ALPHA; + default: + return 0; + } +} + +/* + * left/right pipe offsets for the stage array used in blend_setup() + */ +#define PIPE_LEFT 0 +#define PIPE_RIGHT 1 + +/* + * blend_setup() - blend all the planes of a CRTC + * + * If no base layer is available, border will be enabled as the base layer. + * Otherwise all layers will be blended based on their stage calculated + * in mdp5_crtc_atomic_check. + */ +static void blend_setup(struct drm_crtc *crtc) +{ + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct mdp5_pipeline *pipeline = &mdp5_cstate->pipeline; + struct mdp5_kms *mdp5_kms = get_kms(crtc); + struct drm_plane *plane; + struct mdp5_plane_state *pstate, *pstates[STAGE_MAX + 1] = {NULL}; + const struct mdp_format *format; + struct mdp5_hw_mixer *mixer = pipeline->mixer; + uint32_t lm = mixer->lm; + struct mdp5_hw_mixer *r_mixer = pipeline->r_mixer; + uint32_t r_lm = r_mixer ? r_mixer->lm : 0; + struct mdp5_ctl *ctl = mdp5_cstate->ctl; + uint32_t blend_op, fg_alpha, bg_alpha, ctl_blend_flags = 0; + unsigned long flags; + enum mdp5_pipe stage[STAGE_MAX + 1][MAX_PIPE_STAGE] = { { SSPP_NONE } }; + enum mdp5_pipe r_stage[STAGE_MAX + 1][MAX_PIPE_STAGE] = { { SSPP_NONE } }; + int i, plane_cnt = 0; + bool bg_alpha_enabled = false; + u32 mixer_op_mode = 0; + u32 val; +#define blender(stage) ((stage) - STAGE0) + + spin_lock_irqsave(&mdp5_crtc->lm_lock, flags); + + /* ctl could be released already when we are shutting down: */ + /* XXX: Can this happen now? */ + if (!ctl) + goto out; + + /* Collect all plane information */ + drm_atomic_crtc_for_each_plane(plane, crtc) { + enum mdp5_pipe right_pipe; + + if (!plane->state->visible) + continue; + + pstate = to_mdp5_plane_state(plane->state); + pstates[pstate->stage] = pstate; + stage[pstate->stage][PIPE_LEFT] = mdp5_plane_pipe(plane); + /* + * if we have a right mixer, stage the same pipe as we + * have on the left mixer + */ + if (r_mixer) + r_stage[pstate->stage][PIPE_LEFT] = + mdp5_plane_pipe(plane); + /* + * if we have a right pipe (i.e, the plane comprises of 2 + * hwpipes, then stage the right pipe on the right side of both + * the layer mixers + */ + right_pipe = mdp5_plane_right_pipe(plane); + if (right_pipe) { + stage[pstate->stage][PIPE_RIGHT] = right_pipe; + r_stage[pstate->stage][PIPE_RIGHT] = right_pipe; + } + + plane_cnt++; + } + + if (!pstates[STAGE_BASE]) { + ctl_blend_flags |= MDP5_CTL_BLEND_OP_FLAG_BORDER_OUT; + DBG("Border Color is enabled"); + } else if (plane_cnt) { + format = to_mdp_format(msm_framebuffer_format(pstates[STAGE_BASE]->base.fb)); + + if (format->alpha_enable) + bg_alpha_enabled = true; + } + + /* The reset for blending */ + for (i = STAGE0; i <= STAGE_MAX; i++) { + if (!pstates[i]) + continue; + + format = to_mdp_format( + msm_framebuffer_format(pstates[i]->base.fb)); + plane = pstates[i]->base.plane; + blend_op = MDP5_LM_BLEND_OP_MODE_FG_ALPHA(FG_CONST) | + MDP5_LM_BLEND_OP_MODE_BG_ALPHA(BG_CONST); + fg_alpha = pstates[i]->alpha; + bg_alpha = 0xFF - pstates[i]->alpha; + + if (!format->alpha_enable && bg_alpha_enabled) + mixer_op_mode = 0; + else + mixer_op_mode |= mdp5_lm_use_fg_alpha_mask(i); + + DBG("Stage %d fg_alpha %x bg_alpha %x", i, fg_alpha, bg_alpha); + + if (format->alpha_enable && pstates[i]->premultiplied) { + blend_op = MDP5_LM_BLEND_OP_MODE_FG_ALPHA(FG_CONST) | + MDP5_LM_BLEND_OP_MODE_BG_ALPHA(FG_PIXEL); + if (fg_alpha != 0xff) { + bg_alpha = fg_alpha; + blend_op |= + MDP5_LM_BLEND_OP_MODE_BG_MOD_ALPHA | + MDP5_LM_BLEND_OP_MODE_BG_INV_MOD_ALPHA; + } else { + blend_op |= MDP5_LM_BLEND_OP_MODE_BG_INV_ALPHA; + } + } else if (format->alpha_enable) { + blend_op = MDP5_LM_BLEND_OP_MODE_FG_ALPHA(FG_PIXEL) | + MDP5_LM_BLEND_OP_MODE_BG_ALPHA(FG_PIXEL); + if (fg_alpha != 0xff) { + bg_alpha = fg_alpha; + blend_op |= + MDP5_LM_BLEND_OP_MODE_FG_MOD_ALPHA | + MDP5_LM_BLEND_OP_MODE_FG_INV_MOD_ALPHA | + MDP5_LM_BLEND_OP_MODE_BG_MOD_ALPHA | + MDP5_LM_BLEND_OP_MODE_BG_INV_MOD_ALPHA; + } else { + blend_op |= MDP5_LM_BLEND_OP_MODE_BG_INV_ALPHA; + } + } + + mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_OP_MODE(lm, + blender(i)), blend_op); + mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_FG_ALPHA(lm, + blender(i)), fg_alpha); + mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_BG_ALPHA(lm, + blender(i)), bg_alpha); + if (r_mixer) { + mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_OP_MODE(r_lm, + blender(i)), blend_op); + mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_FG_ALPHA(r_lm, + blender(i)), fg_alpha); + mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_BG_ALPHA(r_lm, + blender(i)), bg_alpha); + } + } + + val = mdp5_read(mdp5_kms, REG_MDP5_LM_BLEND_COLOR_OUT(lm)); + mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_COLOR_OUT(lm), + val | mixer_op_mode); + if (r_mixer) { + val = mdp5_read(mdp5_kms, REG_MDP5_LM_BLEND_COLOR_OUT(r_lm)); + mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_COLOR_OUT(r_lm), + val | mixer_op_mode); + } + + mdp5_ctl_blend(ctl, pipeline, stage, r_stage, plane_cnt, + ctl_blend_flags); +out: + spin_unlock_irqrestore(&mdp5_crtc->lm_lock, flags); +} + +static void mdp5_crtc_mode_set_nofb(struct drm_crtc *crtc) +{ + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct mdp5_kms *mdp5_kms = get_kms(crtc); + struct mdp5_hw_mixer *mixer = mdp5_cstate->pipeline.mixer; + struct mdp5_hw_mixer *r_mixer = mdp5_cstate->pipeline.r_mixer; + uint32_t lm = mixer->lm; + u32 mixer_width, val; + unsigned long flags; + struct drm_display_mode *mode; + + if (WARN_ON(!crtc->state)) + return; + + mode = &crtc->state->adjusted_mode; + + DBG("%s: set mode: " DRM_MODE_FMT, crtc->name, DRM_MODE_ARG(mode)); + + mixer_width = mode->hdisplay; + if (r_mixer) + mixer_width /= 2; + + spin_lock_irqsave(&mdp5_crtc->lm_lock, flags); + mdp5_write(mdp5_kms, REG_MDP5_LM_OUT_SIZE(lm), + MDP5_LM_OUT_SIZE_WIDTH(mixer_width) | + MDP5_LM_OUT_SIZE_HEIGHT(mode->vdisplay)); + + /* Assign mixer to LEFT side in source split mode */ + val = mdp5_read(mdp5_kms, REG_MDP5_LM_BLEND_COLOR_OUT(lm)); + val &= ~MDP5_LM_BLEND_COLOR_OUT_SPLIT_LEFT_RIGHT; + mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_COLOR_OUT(lm), val); + + if (r_mixer) { + u32 r_lm = r_mixer->lm; + + mdp5_write(mdp5_kms, REG_MDP5_LM_OUT_SIZE(r_lm), + MDP5_LM_OUT_SIZE_WIDTH(mixer_width) | + MDP5_LM_OUT_SIZE_HEIGHT(mode->vdisplay)); + + /* Assign mixer to RIGHT side in source split mode */ + val = mdp5_read(mdp5_kms, REG_MDP5_LM_BLEND_COLOR_OUT(r_lm)); + val |= MDP5_LM_BLEND_COLOR_OUT_SPLIT_LEFT_RIGHT; + mdp5_write(mdp5_kms, REG_MDP5_LM_BLEND_COLOR_OUT(r_lm), val); + } + + spin_unlock_irqrestore(&mdp5_crtc->lm_lock, flags); +} + +static struct drm_encoder *get_encoder_from_crtc(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct drm_encoder *encoder; + + drm_for_each_encoder(encoder, dev) + if (encoder->crtc == crtc) + return encoder; + + return NULL; +} + +static bool mdp5_crtc_get_scanout_position(struct drm_crtc *crtc, + bool in_vblank_irq, + int *vpos, int *hpos, + ktime_t *stime, ktime_t *etime, + const struct drm_display_mode *mode) +{ + unsigned int pipe = crtc->index; + struct drm_encoder *encoder; + int line, vsw, vbp, vactive_start, vactive_end, vfp_end; + + + encoder = get_encoder_from_crtc(crtc); + if (!encoder) { + DRM_ERROR("no encoder found for crtc %d\n", pipe); + return false; + } + + vsw = mode->crtc_vsync_end - mode->crtc_vsync_start; + vbp = mode->crtc_vtotal - mode->crtc_vsync_end; + + /* + * the line counter is 1 at the start of the VSYNC pulse and VTOTAL at + * the end of VFP. Translate the porch values relative to the line + * counter positions. + */ + + vactive_start = vsw + vbp + 1; + + vactive_end = vactive_start + mode->crtc_vdisplay; + + /* last scan line before VSYNC */ + vfp_end = mode->crtc_vtotal; + + if (stime) + *stime = ktime_get(); + + line = mdp5_encoder_get_linecount(encoder); + + if (line < vactive_start) + line -= vactive_start; + else if (line > vactive_end) + line = line - vfp_end - vactive_start; + else + line -= vactive_start; + + *vpos = line; + *hpos = 0; + + if (etime) + *etime = ktime_get(); + + return true; +} + +static u32 mdp5_crtc_get_vblank_counter(struct drm_crtc *crtc) +{ + struct drm_encoder *encoder; + + encoder = get_encoder_from_crtc(crtc); + if (!encoder) + return 0; + + return mdp5_encoder_get_framecount(encoder); +} + +static void mdp5_crtc_atomic_disable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct mdp5_kms *mdp5_kms = get_kms(crtc); + struct device *dev = &mdp5_kms->pdev->dev; + unsigned long flags; + + DBG("%s", crtc->name); + + if (WARN_ON(!mdp5_crtc->enabled)) + return; + + /* Disable/save vblank irq handling before power is disabled */ + drm_crtc_vblank_off(crtc); + + if (mdp5_cstate->cmd_mode) + mdp_irq_unregister(&mdp5_kms->base, &mdp5_crtc->pp_done); + + mdp_irq_unregister(&mdp5_kms->base, &mdp5_crtc->err); + pm_runtime_put_sync(dev); + + if (crtc->state->event && !crtc->state->active) { + WARN_ON(mdp5_crtc->event); + spin_lock_irqsave(&mdp5_kms->dev->event_lock, flags); + drm_crtc_send_vblank_event(crtc, crtc->state->event); + crtc->state->event = NULL; + spin_unlock_irqrestore(&mdp5_kms->dev->event_lock, flags); + } + + mdp5_crtc->enabled = false; +} + +static void mdp5_crtc_vblank_on(struct drm_crtc *crtc) +{ + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct mdp5_interface *intf = mdp5_cstate->pipeline.intf; + u32 count; + + count = intf->mode == MDP5_INTF_DSI_MODE_COMMAND ? 0 : 0xffffffff; + drm_crtc_set_max_vblank_count(crtc, count); + + drm_crtc_vblank_on(crtc); +} + +static void mdp5_crtc_atomic_enable(struct drm_crtc *crtc, + struct drm_crtc_state *old_state) +{ + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct mdp5_kms *mdp5_kms = get_kms(crtc); + struct device *dev = &mdp5_kms->pdev->dev; + + DBG("%s", crtc->name); + + if (WARN_ON(mdp5_crtc->enabled)) + return; + + pm_runtime_get_sync(dev); + + if (mdp5_crtc->lm_cursor_enabled) { + /* + * Restore LM cursor state, as it might have been lost + * with suspend: + */ + if (mdp5_crtc->cursor.iova) { + unsigned long flags; + + spin_lock_irqsave(&mdp5_crtc->cursor.lock, flags); + mdp5_crtc_restore_cursor(crtc); + spin_unlock_irqrestore(&mdp5_crtc->cursor.lock, flags); + + mdp5_ctl_set_cursor(mdp5_cstate->ctl, + &mdp5_cstate->pipeline, 0, true); + } else { + mdp5_ctl_set_cursor(mdp5_cstate->ctl, + &mdp5_cstate->pipeline, 0, false); + } + } + + /* Restore vblank irq handling after power is enabled */ + mdp5_crtc_vblank_on(crtc); + + mdp5_crtc_mode_set_nofb(crtc); + + mdp_irq_register(&mdp5_kms->base, &mdp5_crtc->err); + + if (mdp5_cstate->cmd_mode) + mdp_irq_register(&mdp5_kms->base, &mdp5_crtc->pp_done); + + mdp5_crtc->enabled = true; +} + +int mdp5_crtc_setup_pipeline(struct drm_crtc *crtc, + struct drm_crtc_state *new_crtc_state, + bool need_right_mixer) +{ + struct mdp5_crtc_state *mdp5_cstate = + to_mdp5_crtc_state(new_crtc_state); + struct mdp5_pipeline *pipeline = &mdp5_cstate->pipeline; + struct mdp5_interface *intf; + bool new_mixer = false; + + new_mixer = !pipeline->mixer; + + if ((need_right_mixer && !pipeline->r_mixer) || + (!need_right_mixer && pipeline->r_mixer)) + new_mixer = true; + + if (new_mixer) { + struct mdp5_hw_mixer *old_mixer = pipeline->mixer; + struct mdp5_hw_mixer *old_r_mixer = pipeline->r_mixer; + u32 caps; + int ret; + + caps = MDP_LM_CAP_DISPLAY; + if (need_right_mixer) + caps |= MDP_LM_CAP_PAIR; + + ret = mdp5_mixer_assign(new_crtc_state->state, crtc, caps, + &pipeline->mixer, need_right_mixer ? + &pipeline->r_mixer : NULL); + if (ret) + return ret; + + ret = mdp5_mixer_release(new_crtc_state->state, old_mixer); + if (ret) + return ret; + + if (old_r_mixer) { + ret = mdp5_mixer_release(new_crtc_state->state, old_r_mixer); + if (ret) + return ret; + + if (!need_right_mixer) + pipeline->r_mixer = NULL; + } + } + + /* + * these should have been already set up in the encoder's atomic + * check (called by drm_atomic_helper_check_modeset) + */ + intf = pipeline->intf; + + mdp5_cstate->err_irqmask = intf2err(intf->num); + mdp5_cstate->vblank_irqmask = intf2vblank(pipeline->mixer, intf); + + if ((intf->type == INTF_DSI) && + (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)) { + mdp5_cstate->pp_done_irqmask = lm2ppdone(pipeline->mixer); + mdp5_cstate->cmd_mode = true; + } else { + mdp5_cstate->pp_done_irqmask = 0; + mdp5_cstate->cmd_mode = false; + } + + return 0; +} + +struct plane_state { + struct drm_plane *plane; + struct mdp5_plane_state *state; +}; + +static int pstate_cmp(const void *a, const void *b) +{ + struct plane_state *pa = (struct plane_state *)a; + struct plane_state *pb = (struct plane_state *)b; + return pa->state->zpos - pb->state->zpos; +} + +/* is there a helper for this? */ +static bool is_fullscreen(struct drm_crtc_state *cstate, + struct drm_plane_state *pstate) +{ + return (pstate->crtc_x <= 0) && (pstate->crtc_y <= 0) && + ((pstate->crtc_x + pstate->crtc_w) >= cstate->mode.hdisplay) && + ((pstate->crtc_y + pstate->crtc_h) >= cstate->mode.vdisplay); +} + +static enum mdp_mixer_stage_id get_start_stage(struct drm_crtc *crtc, + struct drm_crtc_state *new_crtc_state, + struct drm_plane_state *bpstate) +{ + struct mdp5_crtc_state *mdp5_cstate = + to_mdp5_crtc_state(new_crtc_state); + + /* + * if we're in source split mode, it's mandatory to have + * border out on the base stage + */ + if (mdp5_cstate->pipeline.r_mixer) + return STAGE0; + + /* if the bottom-most layer is not fullscreen, we need to use + * it for solid-color: + */ + if (!is_fullscreen(new_crtc_state, bpstate)) + return STAGE0; + + return STAGE_BASE; +} + +static int mdp5_crtc_atomic_check(struct drm_crtc *crtc, + struct drm_crtc_state *state) +{ + struct mdp5_kms *mdp5_kms = get_kms(crtc); + struct drm_plane *plane; + struct drm_device *dev = crtc->dev; + struct plane_state pstates[STAGE_MAX + 1]; + const struct mdp5_cfg_hw *hw_cfg; + const struct drm_plane_state *pstate; + const struct drm_display_mode *mode = &state->adjusted_mode; + bool cursor_plane = false; + bool need_right_mixer = false; + int cnt = 0, i; + int ret; + enum mdp_mixer_stage_id start; + + DBG("%s: check", crtc->name); + + drm_atomic_crtc_state_for_each_plane_state(plane, pstate, state) { + if (!pstate->visible) + continue; + + pstates[cnt].plane = plane; + pstates[cnt].state = to_mdp5_plane_state(pstate); + + /* + * if any plane on this crtc uses 2 hwpipes, then we need + * the crtc to have a right hwmixer. + */ + if (pstates[cnt].state->r_hwpipe) + need_right_mixer = true; + cnt++; + + if (plane->type == DRM_PLANE_TYPE_CURSOR) + cursor_plane = true; + } + + /* bail out early if there aren't any planes */ + if (!cnt) + return 0; + + hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg); + + /* + * we need a right hwmixer if the mode's width is greater than a single + * LM's max width + */ + if (mode->hdisplay > hw_cfg->lm.max_width) + need_right_mixer = true; + + ret = mdp5_crtc_setup_pipeline(crtc, state, need_right_mixer); + if (ret) { + DRM_DEV_ERROR(dev->dev, "couldn't assign mixers %d\n", ret); + return ret; + } + + /* assign a stage based on sorted zpos property */ + sort(pstates, cnt, sizeof(pstates[0]), pstate_cmp, NULL); + + /* trigger a warning if cursor isn't the highest zorder */ + WARN_ON(cursor_plane && + (pstates[cnt - 1].plane->type != DRM_PLANE_TYPE_CURSOR)); + + start = get_start_stage(crtc, state, &pstates[0].state->base); + + /* verify that there are not too many planes attached to crtc + * and that we don't have conflicting mixer stages: + */ + if ((cnt + start - 1) >= hw_cfg->lm.nb_stages) { + DRM_DEV_ERROR(dev->dev, "too many planes! cnt=%d, start stage=%d\n", + cnt, start); + return -EINVAL; + } + + for (i = 0; i < cnt; i++) { + if (cursor_plane && (i == (cnt - 1))) + pstates[i].state->stage = hw_cfg->lm.nb_stages; + else + pstates[i].state->stage = start + i; + DBG("%s: assign pipe %s on stage=%d", crtc->name, + pstates[i].plane->name, + pstates[i].state->stage); + } + + return 0; +} + +static void mdp5_crtc_atomic_begin(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + DBG("%s: begin", crtc->name); +} + +static void mdp5_crtc_atomic_flush(struct drm_crtc *crtc, + struct drm_crtc_state *old_crtc_state) +{ + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct drm_device *dev = crtc->dev; + unsigned long flags; + + DBG("%s: event: %p", crtc->name, crtc->state->event); + + WARN_ON(mdp5_crtc->event); + + spin_lock_irqsave(&dev->event_lock, flags); + mdp5_crtc->event = crtc->state->event; + crtc->state->event = NULL; + spin_unlock_irqrestore(&dev->event_lock, flags); + + /* + * If no CTL has been allocated in mdp5_crtc_atomic_check(), + * it means we are trying to flush a CRTC whose state is disabled: + * nothing else needs to be done. + */ + /* XXX: Can this happen now ? */ + if (unlikely(!mdp5_cstate->ctl)) + return; + + blend_setup(crtc); + + /* PP_DONE irq is only used by command mode for now. + * It is better to request pending before FLUSH and START trigger + * to make sure no pp_done irq missed. + * This is safe because no pp_done will happen before SW trigger + * in command mode. + */ + if (mdp5_cstate->cmd_mode) + request_pp_done_pending(crtc); + + mdp5_crtc->flushed_mask = crtc_flush_all(crtc); + + /* XXX are we leaking out state here? */ + mdp5_crtc->vblank.irqmask = mdp5_cstate->vblank_irqmask; + mdp5_crtc->err.irqmask = mdp5_cstate->err_irqmask; + mdp5_crtc->pp_done.irqmask = mdp5_cstate->pp_done_irqmask; + + request_pending(crtc, PENDING_FLIP); +} + +static void get_roi(struct drm_crtc *crtc, uint32_t *roi_w, uint32_t *roi_h) +{ + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + uint32_t xres = crtc->mode.hdisplay; + uint32_t yres = crtc->mode.vdisplay; + + /* + * Cursor Region Of Interest (ROI) is a plane read from cursor + * buffer to render. The ROI region is determined by the visibility of + * the cursor point. In the default Cursor image the cursor point will + * be at the top left of the cursor image. + * + * Without rotation: + * If the cursor point reaches the right (xres - x < cursor.width) or + * bottom (yres - y < cursor.height) boundary of the screen, then ROI + * width and ROI height need to be evaluated to crop the cursor image + * accordingly. + * (xres-x) will be new cursor width when x > (xres - cursor.width) + * (yres-y) will be new cursor height when y > (yres - cursor.height) + * + * With rotation: + * We get negative x and/or y coordinates. + * (cursor.width - abs(x)) will be new cursor width when x < 0 + * (cursor.height - abs(y)) will be new cursor width when y < 0 + */ + if (mdp5_crtc->cursor.x >= 0) + *roi_w = min(mdp5_crtc->cursor.width, xres - + mdp5_crtc->cursor.x); + else + *roi_w = mdp5_crtc->cursor.width - abs(mdp5_crtc->cursor.x); + if (mdp5_crtc->cursor.y >= 0) + *roi_h = min(mdp5_crtc->cursor.height, yres - + mdp5_crtc->cursor.y); + else + *roi_h = mdp5_crtc->cursor.height - abs(mdp5_crtc->cursor.y); +} + +static void mdp5_crtc_restore_cursor(struct drm_crtc *crtc) +{ + const struct drm_format_info *info = drm_format_info(DRM_FORMAT_ARGB8888); + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + struct mdp5_kms *mdp5_kms = get_kms(crtc); + const enum mdp5_cursor_alpha cur_alpha = CURSOR_ALPHA_PER_PIXEL; + uint32_t blendcfg, stride; + uint32_t x, y, src_x, src_y, width, height; + uint32_t roi_w, roi_h; + int lm; + + assert_spin_locked(&mdp5_crtc->cursor.lock); + + lm = mdp5_cstate->pipeline.mixer->lm; + + x = mdp5_crtc->cursor.x; + y = mdp5_crtc->cursor.y; + width = mdp5_crtc->cursor.width; + height = mdp5_crtc->cursor.height; + + stride = width * info->cpp[0]; + + get_roi(crtc, &roi_w, &roi_h); + + /* If cusror buffer overlaps due to rotation on the + * upper or left screen border the pixel offset inside + * the cursor buffer of the ROI is the positive overlap + * distance. + */ + if (mdp5_crtc->cursor.x < 0) { + src_x = abs(mdp5_crtc->cursor.x); + x = 0; + } else { + src_x = 0; + } + if (mdp5_crtc->cursor.y < 0) { + src_y = abs(mdp5_crtc->cursor.y); + y = 0; + } else { + src_y = 0; + } + DBG("%s: x=%d, y=%d roi_w=%d roi_h=%d src_x=%d src_y=%d", + crtc->name, x, y, roi_w, roi_h, src_x, src_y); + + mdp5_write(mdp5_kms, REG_MDP5_LM_CURSOR_STRIDE(lm), stride); + mdp5_write(mdp5_kms, REG_MDP5_LM_CURSOR_FORMAT(lm), + MDP5_LM_CURSOR_FORMAT_FORMAT(CURSOR_FMT_ARGB8888)); + mdp5_write(mdp5_kms, REG_MDP5_LM_CURSOR_IMG_SIZE(lm), + MDP5_LM_CURSOR_IMG_SIZE_SRC_H(height) | + MDP5_LM_CURSOR_IMG_SIZE_SRC_W(width)); + mdp5_write(mdp5_kms, REG_MDP5_LM_CURSOR_SIZE(lm), + MDP5_LM_CURSOR_SIZE_ROI_H(roi_h) | + MDP5_LM_CURSOR_SIZE_ROI_W(roi_w)); + mdp5_write(mdp5_kms, REG_MDP5_LM_CURSOR_START_XY(lm), + MDP5_LM_CURSOR_START_XY_Y_START(y) | + MDP5_LM_CURSOR_START_XY_X_START(x)); + mdp5_write(mdp5_kms, REG_MDP5_LM_CURSOR_XY(lm), + MDP5_LM_CURSOR_XY_SRC_Y(src_y) | + MDP5_LM_CURSOR_XY_SRC_X(src_x)); + mdp5_write(mdp5_kms, REG_MDP5_LM_CURSOR_BASE_ADDR(lm), + mdp5_crtc->cursor.iova); + + blendcfg = MDP5_LM_CURSOR_BLEND_CONFIG_BLEND_EN; + blendcfg |= MDP5_LM_CURSOR_BLEND_CONFIG_BLEND_ALPHA_SEL(cur_alpha); + mdp5_write(mdp5_kms, REG_MDP5_LM_CURSOR_BLEND_CONFIG(lm), blendcfg); +} + +static int mdp5_crtc_cursor_set(struct drm_crtc *crtc, + struct drm_file *file, uint32_t handle, + uint32_t width, uint32_t height) +{ + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct mdp5_pipeline *pipeline = &mdp5_cstate->pipeline; + struct drm_device *dev = crtc->dev; + struct mdp5_kms *mdp5_kms = get_kms(crtc); + struct platform_device *pdev = mdp5_kms->pdev; + struct msm_kms *kms = &mdp5_kms->base.base; + struct drm_gem_object *cursor_bo, *old_bo = NULL; + struct mdp5_ctl *ctl; + int ret; + uint32_t flush_mask = mdp_ctl_flush_mask_cursor(0); + bool cursor_enable = true; + unsigned long flags; + + if (!mdp5_crtc->lm_cursor_enabled) { + dev_warn(dev->dev, + "cursor_set is deprecated with cursor planes\n"); + return -EINVAL; + } + + if ((width > CURSOR_WIDTH) || (height > CURSOR_HEIGHT)) { + DRM_DEV_ERROR(dev->dev, "bad cursor size: %dx%d\n", width, height); + return -EINVAL; + } + + ctl = mdp5_cstate->ctl; + if (!ctl) + return -EINVAL; + + /* don't support LM cursors when we have source split enabled */ + if (mdp5_cstate->pipeline.r_mixer) + return -EINVAL; + + if (!handle) { + DBG("Cursor off"); + cursor_enable = false; + mdp5_crtc->cursor.iova = 0; + pm_runtime_get_sync(&pdev->dev); + goto set_cursor; + } + + cursor_bo = drm_gem_object_lookup(file, handle); + if (!cursor_bo) + return -ENOENT; + + ret = msm_gem_get_and_pin_iova(cursor_bo, kms->aspace, + &mdp5_crtc->cursor.iova); + if (ret) { + drm_gem_object_put(cursor_bo); + return -EINVAL; + } + + pm_runtime_get_sync(&pdev->dev); + + spin_lock_irqsave(&mdp5_crtc->cursor.lock, flags); + old_bo = mdp5_crtc->cursor.scanout_bo; + + mdp5_crtc->cursor.scanout_bo = cursor_bo; + mdp5_crtc->cursor.width = width; + mdp5_crtc->cursor.height = height; + + mdp5_crtc_restore_cursor(crtc); + + spin_unlock_irqrestore(&mdp5_crtc->cursor.lock, flags); + +set_cursor: + ret = mdp5_ctl_set_cursor(ctl, pipeline, 0, cursor_enable); + if (ret) { + DRM_DEV_ERROR(dev->dev, "failed to %sable cursor: %d\n", + cursor_enable ? "en" : "dis", ret); + goto end; + } + + crtc_flush(crtc, flush_mask); + +end: + pm_runtime_put_sync(&pdev->dev); + if (old_bo) { + drm_flip_work_queue(&mdp5_crtc->unref_cursor_work, old_bo); + /* enable vblank to complete cursor work: */ + request_pending(crtc, PENDING_CURSOR); + } + return ret; +} + +static int mdp5_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) +{ + struct mdp5_kms *mdp5_kms = get_kms(crtc); + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + uint32_t flush_mask = mdp_ctl_flush_mask_cursor(0); + struct drm_device *dev = crtc->dev; + uint32_t roi_w; + uint32_t roi_h; + unsigned long flags; + + if (!mdp5_crtc->lm_cursor_enabled) { + dev_warn(dev->dev, + "cursor_move is deprecated with cursor planes\n"); + return -EINVAL; + } + + /* don't support LM cursors when we have source split enabled */ + if (mdp5_cstate->pipeline.r_mixer) + return -EINVAL; + + /* In case the CRTC is disabled, just drop the cursor update */ + if (unlikely(!crtc->state->enable)) + return 0; + + /* accept negative x/y coordinates up to maximum cursor overlap */ + mdp5_crtc->cursor.x = x = max(x, -(int)mdp5_crtc->cursor.width); + mdp5_crtc->cursor.y = y = max(y, -(int)mdp5_crtc->cursor.height); + + get_roi(crtc, &roi_w, &roi_h); + + pm_runtime_get_sync(&mdp5_kms->pdev->dev); + + spin_lock_irqsave(&mdp5_crtc->cursor.lock, flags); + mdp5_crtc_restore_cursor(crtc); + spin_unlock_irqrestore(&mdp5_crtc->cursor.lock, flags); + + crtc_flush(crtc, flush_mask); + + pm_runtime_put_sync(&mdp5_kms->pdev->dev); + + return 0; +} + +static void +mdp5_crtc_atomic_print_state(struct drm_printer *p, + const struct drm_crtc_state *state) +{ + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(state); + struct mdp5_pipeline *pipeline = &mdp5_cstate->pipeline; + struct mdp5_kms *mdp5_kms = get_kms(state->crtc); + + if (WARN_ON(!pipeline)) + return; + + if (mdp5_cstate->ctl) + drm_printf(p, "\tctl=%d\n", mdp5_ctl_get_ctl_id(mdp5_cstate->ctl)); + + drm_printf(p, "\thwmixer=%s\n", pipeline->mixer ? + pipeline->mixer->name : "(null)"); + + if (mdp5_kms->caps & MDP_CAP_SRC_SPLIT) + drm_printf(p, "\tright hwmixer=%s\n", pipeline->r_mixer ? + pipeline->r_mixer->name : "(null)"); + + drm_printf(p, "\tcmd_mode=%d\n", mdp5_cstate->cmd_mode); +} + +static struct drm_crtc_state * +mdp5_crtc_duplicate_state(struct drm_crtc *crtc) +{ + struct mdp5_crtc_state *mdp5_cstate; + + if (WARN_ON(!crtc->state)) + return NULL; + + mdp5_cstate = kmemdup(to_mdp5_crtc_state(crtc->state), + sizeof(*mdp5_cstate), GFP_KERNEL); + if (!mdp5_cstate) + return NULL; + + __drm_atomic_helper_crtc_duplicate_state(crtc, &mdp5_cstate->base); + + return &mdp5_cstate->base; +} + +static void mdp5_crtc_destroy_state(struct drm_crtc *crtc, struct drm_crtc_state *state) +{ + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(state); + + __drm_atomic_helper_crtc_destroy_state(state); + + kfree(mdp5_cstate); +} + +static void mdp5_crtc_reset(struct drm_crtc *crtc) +{ + struct mdp5_crtc_state *mdp5_cstate = + kzalloc(sizeof(*mdp5_cstate), GFP_KERNEL); + + if (crtc->state) + mdp5_crtc_destroy_state(crtc, crtc->state); + + if (mdp5_cstate) + __drm_atomic_helper_crtc_reset(crtc, &mdp5_cstate->base); + else + __drm_atomic_helper_crtc_reset(crtc, NULL); +} + +static const struct drm_crtc_funcs mdp5_crtc_no_lm_cursor_funcs = { + .set_config = drm_atomic_helper_set_config, + .destroy = mdp5_crtc_destroy, + .page_flip = drm_atomic_helper_page_flip, + .reset = mdp5_crtc_reset, + .atomic_duplicate_state = mdp5_crtc_duplicate_state, + .atomic_destroy_state = mdp5_crtc_destroy_state, + .atomic_print_state = mdp5_crtc_atomic_print_state, + .get_vblank_counter = mdp5_crtc_get_vblank_counter, + .enable_vblank = msm_crtc_enable_vblank, + .disable_vblank = msm_crtc_disable_vblank, + .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, +}; + +static const struct drm_crtc_funcs mdp5_crtc_funcs = { + .set_config = drm_atomic_helper_set_config, + .destroy = mdp5_crtc_destroy, + .page_flip = drm_atomic_helper_page_flip, + .reset = mdp5_crtc_reset, + .atomic_duplicate_state = mdp5_crtc_duplicate_state, + .atomic_destroy_state = mdp5_crtc_destroy_state, + .cursor_set = mdp5_crtc_cursor_set, + .cursor_move = mdp5_crtc_cursor_move, + .atomic_print_state = mdp5_crtc_atomic_print_state, + .get_vblank_counter = mdp5_crtc_get_vblank_counter, + .enable_vblank = msm_crtc_enable_vblank, + .disable_vblank = msm_crtc_disable_vblank, + .get_vblank_timestamp = drm_crtc_vblank_helper_get_vblank_timestamp, +}; + +static const struct drm_crtc_helper_funcs mdp5_crtc_helper_funcs = { + .mode_set_nofb = mdp5_crtc_mode_set_nofb, + .atomic_check = mdp5_crtc_atomic_check, + .atomic_begin = mdp5_crtc_atomic_begin, + .atomic_flush = mdp5_crtc_atomic_flush, + .atomic_enable = mdp5_crtc_atomic_enable, + .atomic_disable = mdp5_crtc_atomic_disable, + .get_scanout_position = mdp5_crtc_get_scanout_position, +}; + +static void mdp5_crtc_vblank_irq(struct mdp_irq *irq, uint32_t irqstatus) +{ + struct mdp5_crtc *mdp5_crtc = container_of(irq, struct mdp5_crtc, vblank); + struct drm_crtc *crtc = &mdp5_crtc->base; + struct msm_drm_private *priv = crtc->dev->dev_private; + unsigned pending; + + mdp_irq_unregister(&get_kms(crtc)->base, &mdp5_crtc->vblank); + + pending = atomic_xchg(&mdp5_crtc->pending, 0); + + if (pending & PENDING_FLIP) { + complete_flip(crtc, NULL); + } + + if (pending & PENDING_CURSOR) + drm_flip_work_commit(&mdp5_crtc->unref_cursor_work, priv->wq); +} + +static void mdp5_crtc_err_irq(struct mdp_irq *irq, uint32_t irqstatus) +{ + struct mdp5_crtc *mdp5_crtc = container_of(irq, struct mdp5_crtc, err); + + DBG("%s: error: %08x", mdp5_crtc->base.name, irqstatus); +} + +static void mdp5_crtc_pp_done_irq(struct mdp_irq *irq, uint32_t irqstatus) +{ + struct mdp5_crtc *mdp5_crtc = container_of(irq, struct mdp5_crtc, + pp_done); + + complete_all(&mdp5_crtc->pp_completion); +} + +static void mdp5_crtc_wait_for_pp_done(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + int ret; + + ret = wait_for_completion_timeout(&mdp5_crtc->pp_completion, + msecs_to_jiffies(50)); + if (ret == 0) + dev_warn_ratelimited(dev->dev, "pp done time out, lm=%d\n", + mdp5_cstate->pipeline.mixer->lm); +} + +static void mdp5_crtc_wait_for_flush_done(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct mdp5_ctl *ctl = mdp5_cstate->ctl; + int ret; + + /* Should not call this function if crtc is disabled. */ + if (!ctl) + return; + + ret = drm_crtc_vblank_get(crtc); + if (ret) + return; + + ret = wait_event_timeout(dev->vblank[drm_crtc_index(crtc)].queue, + ((mdp5_ctl_get_commit_status(ctl) & + mdp5_crtc->flushed_mask) == 0), + msecs_to_jiffies(50)); + if (ret <= 0) + dev_warn(dev->dev, "vblank time out, crtc=%d\n", mdp5_crtc->id); + + mdp5_crtc->flushed_mask = 0; + + drm_crtc_vblank_put(crtc); +} + +uint32_t mdp5_crtc_vblank(struct drm_crtc *crtc) +{ + struct mdp5_crtc *mdp5_crtc = to_mdp5_crtc(crtc); + return mdp5_crtc->vblank.irqmask; +} + +void mdp5_crtc_set_pipeline(struct drm_crtc *crtc) +{ + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + struct mdp5_kms *mdp5_kms = get_kms(crtc); + + /* should this be done elsewhere ? */ + mdp_irq_update(&mdp5_kms->base); + + mdp5_ctl_set_pipeline(mdp5_cstate->ctl, &mdp5_cstate->pipeline); +} + +struct mdp5_ctl *mdp5_crtc_get_ctl(struct drm_crtc *crtc) +{ + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + + return mdp5_cstate->ctl; +} + +struct mdp5_hw_mixer *mdp5_crtc_get_mixer(struct drm_crtc *crtc) +{ + struct mdp5_crtc_state *mdp5_cstate; + + if (WARN_ON(!crtc)) + return ERR_PTR(-EINVAL); + + mdp5_cstate = to_mdp5_crtc_state(crtc->state); + + return WARN_ON(!mdp5_cstate->pipeline.mixer) ? + ERR_PTR(-EINVAL) : mdp5_cstate->pipeline.mixer; +} + +struct mdp5_pipeline *mdp5_crtc_get_pipeline(struct drm_crtc *crtc) +{ + struct mdp5_crtc_state *mdp5_cstate; + + if (WARN_ON(!crtc)) + return ERR_PTR(-EINVAL); + + mdp5_cstate = to_mdp5_crtc_state(crtc->state); + + return &mdp5_cstate->pipeline; +} + +void mdp5_crtc_wait_for_commit_done(struct drm_crtc *crtc) +{ + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc->state); + + if (mdp5_cstate->cmd_mode) + mdp5_crtc_wait_for_pp_done(crtc); + else + mdp5_crtc_wait_for_flush_done(crtc); +} + +/* initialize crtc */ +struct drm_crtc *mdp5_crtc_init(struct drm_device *dev, + struct drm_plane *plane, + struct drm_plane *cursor_plane, int id) +{ + struct drm_crtc *crtc = NULL; + struct mdp5_crtc *mdp5_crtc; + + mdp5_crtc = kzalloc(sizeof(*mdp5_crtc), GFP_KERNEL); + if (!mdp5_crtc) + return ERR_PTR(-ENOMEM); + + crtc = &mdp5_crtc->base; + + mdp5_crtc->id = id; + + spin_lock_init(&mdp5_crtc->lm_lock); + spin_lock_init(&mdp5_crtc->cursor.lock); + init_completion(&mdp5_crtc->pp_completion); + + mdp5_crtc->vblank.irq = mdp5_crtc_vblank_irq; + mdp5_crtc->err.irq = mdp5_crtc_err_irq; + mdp5_crtc->pp_done.irq = mdp5_crtc_pp_done_irq; + + mdp5_crtc->lm_cursor_enabled = cursor_plane ? false : true; + + drm_crtc_init_with_planes(dev, crtc, plane, cursor_plane, + cursor_plane ? + &mdp5_crtc_no_lm_cursor_funcs : + &mdp5_crtc_funcs, NULL); + + drm_flip_work_init(&mdp5_crtc->unref_cursor_work, + "unref cursor", unref_cursor_worker); + + drm_crtc_helper_add(crtc, &mdp5_crtc_helper_funcs); + + return crtc; +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_ctl.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_ctl.c new file mode 100644 index 000000000..030279d7b --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_ctl.c @@ -0,0 +1,755 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2015 The Linux Foundation. All rights reserved. + */ + +#include "mdp5_kms.h" +#include "mdp5_ctl.h" + +/* + * CTL - MDP Control Pool Manager + * + * Controls are shared between all display interfaces. + * + * They are intended to be used for data path configuration. + * The top level register programming describes the complete data path for + * a specific data path ID - REG_MDP5_CTL_*(<id>, ...) + * + * Hardware capabilities determine the number of concurrent data paths + * + * In certain use cases (high-resolution dual pipe), one single CTL can be + * shared across multiple CRTCs. + */ + +#define CTL_STAT_BUSY 0x1 +#define CTL_STAT_BOOKED 0x2 + +struct mdp5_ctl { + struct mdp5_ctl_manager *ctlm; + + u32 id; + + /* CTL status bitmask */ + u32 status; + + bool encoder_enabled; + + /* pending flush_mask bits */ + u32 flush_mask; + + /* REG_MDP5_CTL_*(<id>) registers access info + lock: */ + spinlock_t hw_lock; + u32 reg_offset; + + /* when do CTL registers need to be flushed? (mask of trigger bits) */ + u32 pending_ctl_trigger; + + bool cursor_on; + + /* True if the current CTL has FLUSH bits pending for single FLUSH. */ + bool flush_pending; + + struct mdp5_ctl *pair; /* Paired CTL to be flushed together */ +}; + +struct mdp5_ctl_manager { + struct drm_device *dev; + + /* number of CTL / Layer Mixers in this hw config: */ + u32 nlm; + u32 nctl; + + /* to filter out non-present bits in the current hardware config */ + u32 flush_hw_mask; + + /* status for single FLUSH */ + bool single_flush_supported; + u32 single_flush_pending_mask; + + /* pool of CTLs + lock to protect resource allocation (ctls[i].busy) */ + spinlock_t pool_lock; + struct mdp5_ctl ctls[MAX_CTL]; +}; + +static inline +struct mdp5_kms *get_kms(struct mdp5_ctl_manager *ctl_mgr) +{ + struct msm_drm_private *priv = ctl_mgr->dev->dev_private; + + return to_mdp5_kms(to_mdp_kms(priv->kms)); +} + +static inline +void ctl_write(struct mdp5_ctl *ctl, u32 reg, u32 data) +{ + struct mdp5_kms *mdp5_kms = get_kms(ctl->ctlm); + + (void)ctl->reg_offset; /* TODO use this instead of mdp5_write */ + mdp5_write(mdp5_kms, reg, data); +} + +static inline +u32 ctl_read(struct mdp5_ctl *ctl, u32 reg) +{ + struct mdp5_kms *mdp5_kms = get_kms(ctl->ctlm); + + (void)ctl->reg_offset; /* TODO use this instead of mdp5_write */ + return mdp5_read(mdp5_kms, reg); +} + +static void set_display_intf(struct mdp5_kms *mdp5_kms, + struct mdp5_interface *intf) +{ + unsigned long flags; + u32 intf_sel; + + spin_lock_irqsave(&mdp5_kms->resource_lock, flags); + intf_sel = mdp5_read(mdp5_kms, REG_MDP5_DISP_INTF_SEL); + + switch (intf->num) { + case 0: + intf_sel &= ~MDP5_DISP_INTF_SEL_INTF0__MASK; + intf_sel |= MDP5_DISP_INTF_SEL_INTF0(intf->type); + break; + case 1: + intf_sel &= ~MDP5_DISP_INTF_SEL_INTF1__MASK; + intf_sel |= MDP5_DISP_INTF_SEL_INTF1(intf->type); + break; + case 2: + intf_sel &= ~MDP5_DISP_INTF_SEL_INTF2__MASK; + intf_sel |= MDP5_DISP_INTF_SEL_INTF2(intf->type); + break; + case 3: + intf_sel &= ~MDP5_DISP_INTF_SEL_INTF3__MASK; + intf_sel |= MDP5_DISP_INTF_SEL_INTF3(intf->type); + break; + default: + BUG(); + break; + } + + mdp5_write(mdp5_kms, REG_MDP5_DISP_INTF_SEL, intf_sel); + spin_unlock_irqrestore(&mdp5_kms->resource_lock, flags); +} + +static void set_ctl_op(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline) +{ + unsigned long flags; + struct mdp5_interface *intf = pipeline->intf; + u32 ctl_op = 0; + + if (!mdp5_cfg_intf_is_virtual(intf->type)) + ctl_op |= MDP5_CTL_OP_INTF_NUM(INTF0 + intf->num); + + switch (intf->type) { + case INTF_DSI: + if (intf->mode == MDP5_INTF_DSI_MODE_COMMAND) + ctl_op |= MDP5_CTL_OP_CMD_MODE; + break; + + case INTF_WB: + if (intf->mode == MDP5_INTF_WB_MODE_LINE) + ctl_op |= MDP5_CTL_OP_MODE(MODE_WB_2_LINE); + break; + + default: + break; + } + + if (pipeline->r_mixer) + ctl_op |= MDP5_CTL_OP_PACK_3D_ENABLE | + MDP5_CTL_OP_PACK_3D(1); + + spin_lock_irqsave(&ctl->hw_lock, flags); + ctl_write(ctl, REG_MDP5_CTL_OP(ctl->id), ctl_op); + spin_unlock_irqrestore(&ctl->hw_lock, flags); +} + +int mdp5_ctl_set_pipeline(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline) +{ + struct mdp5_kms *mdp5_kms = get_kms(ctl->ctlm); + struct mdp5_interface *intf = pipeline->intf; + + /* Virtual interfaces need not set a display intf (e.g.: Writeback) */ + if (!mdp5_cfg_intf_is_virtual(intf->type)) + set_display_intf(mdp5_kms, intf); + + set_ctl_op(ctl, pipeline); + + return 0; +} + +static bool start_signal_needed(struct mdp5_ctl *ctl, + struct mdp5_pipeline *pipeline) +{ + struct mdp5_interface *intf = pipeline->intf; + + if (!ctl->encoder_enabled) + return false; + + switch (intf->type) { + case INTF_WB: + return true; + case INTF_DSI: + return intf->mode == MDP5_INTF_DSI_MODE_COMMAND; + default: + return false; + } +} + +/* + * send_start_signal() - Overlay Processor Start Signal + * + * For a given control operation (display pipeline), a START signal needs to be + * executed in order to kick off operation and activate all layers. + * e.g.: DSI command mode, Writeback + */ +static void send_start_signal(struct mdp5_ctl *ctl) +{ + unsigned long flags; + + spin_lock_irqsave(&ctl->hw_lock, flags); + ctl_write(ctl, REG_MDP5_CTL_START(ctl->id), 1); + spin_unlock_irqrestore(&ctl->hw_lock, flags); +} + +/** + * mdp5_ctl_set_encoder_state() - set the encoder state + * + * @enable: true, when encoder is ready for data streaming; false, otherwise. + * + * Note: + * This encoder state is needed to trigger START signal (data path kickoff). + */ +int mdp5_ctl_set_encoder_state(struct mdp5_ctl *ctl, + struct mdp5_pipeline *pipeline, + bool enabled) +{ + struct mdp5_interface *intf = pipeline->intf; + + if (WARN_ON(!ctl)) + return -EINVAL; + + ctl->encoder_enabled = enabled; + DBG("intf_%d: %s", intf->num, enabled ? "on" : "off"); + + if (start_signal_needed(ctl, pipeline)) { + send_start_signal(ctl); + } + + return 0; +} + +/* + * Note: + * CTL registers need to be flushed after calling this function + * (call mdp5_ctl_commit() with mdp_ctl_flush_mask_ctl() mask) + */ +int mdp5_ctl_set_cursor(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline, + int cursor_id, bool enable) +{ + struct mdp5_ctl_manager *ctl_mgr = ctl->ctlm; + unsigned long flags; + u32 blend_cfg; + struct mdp5_hw_mixer *mixer = pipeline->mixer; + + if (WARN_ON(!mixer)) { + DRM_DEV_ERROR(ctl_mgr->dev->dev, "CTL %d cannot find LM", + ctl->id); + return -EINVAL; + } + + if (pipeline->r_mixer) { + DRM_DEV_ERROR(ctl_mgr->dev->dev, "unsupported configuration"); + return -EINVAL; + } + + spin_lock_irqsave(&ctl->hw_lock, flags); + + blend_cfg = ctl_read(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, mixer->lm)); + + if (enable) + blend_cfg |= MDP5_CTL_LAYER_REG_CURSOR_OUT; + else + blend_cfg &= ~MDP5_CTL_LAYER_REG_CURSOR_OUT; + + ctl_write(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, mixer->lm), blend_cfg); + ctl->cursor_on = enable; + + spin_unlock_irqrestore(&ctl->hw_lock, flags); + + ctl->pending_ctl_trigger = mdp_ctl_flush_mask_cursor(cursor_id); + + return 0; +} + +static u32 mdp_ctl_blend_mask(enum mdp5_pipe pipe, + enum mdp_mixer_stage_id stage) +{ + switch (pipe) { + case SSPP_VIG0: return MDP5_CTL_LAYER_REG_VIG0(stage); + case SSPP_VIG1: return MDP5_CTL_LAYER_REG_VIG1(stage); + case SSPP_VIG2: return MDP5_CTL_LAYER_REG_VIG2(stage); + case SSPP_RGB0: return MDP5_CTL_LAYER_REG_RGB0(stage); + case SSPP_RGB1: return MDP5_CTL_LAYER_REG_RGB1(stage); + case SSPP_RGB2: return MDP5_CTL_LAYER_REG_RGB2(stage); + case SSPP_DMA0: return MDP5_CTL_LAYER_REG_DMA0(stage); + case SSPP_DMA1: return MDP5_CTL_LAYER_REG_DMA1(stage); + case SSPP_VIG3: return MDP5_CTL_LAYER_REG_VIG3(stage); + case SSPP_RGB3: return MDP5_CTL_LAYER_REG_RGB3(stage); + case SSPP_CURSOR0: + case SSPP_CURSOR1: + default: return 0; + } +} + +static u32 mdp_ctl_blend_ext_mask(enum mdp5_pipe pipe, + enum mdp_mixer_stage_id stage) +{ + if (stage < STAGE6 && (pipe != SSPP_CURSOR0 && pipe != SSPP_CURSOR1)) + return 0; + + switch (pipe) { + case SSPP_VIG0: return MDP5_CTL_LAYER_EXT_REG_VIG0_BIT3; + case SSPP_VIG1: return MDP5_CTL_LAYER_EXT_REG_VIG1_BIT3; + case SSPP_VIG2: return MDP5_CTL_LAYER_EXT_REG_VIG2_BIT3; + case SSPP_RGB0: return MDP5_CTL_LAYER_EXT_REG_RGB0_BIT3; + case SSPP_RGB1: return MDP5_CTL_LAYER_EXT_REG_RGB1_BIT3; + case SSPP_RGB2: return MDP5_CTL_LAYER_EXT_REG_RGB2_BIT3; + case SSPP_DMA0: return MDP5_CTL_LAYER_EXT_REG_DMA0_BIT3; + case SSPP_DMA1: return MDP5_CTL_LAYER_EXT_REG_DMA1_BIT3; + case SSPP_VIG3: return MDP5_CTL_LAYER_EXT_REG_VIG3_BIT3; + case SSPP_RGB3: return MDP5_CTL_LAYER_EXT_REG_RGB3_BIT3; + case SSPP_CURSOR0: return MDP5_CTL_LAYER_EXT_REG_CURSOR0(stage); + case SSPP_CURSOR1: return MDP5_CTL_LAYER_EXT_REG_CURSOR1(stage); + default: return 0; + } +} + +static void mdp5_ctl_reset_blend_regs(struct mdp5_ctl *ctl) +{ + unsigned long flags; + struct mdp5_ctl_manager *ctl_mgr = ctl->ctlm; + int i; + + spin_lock_irqsave(&ctl->hw_lock, flags); + + for (i = 0; i < ctl_mgr->nlm; i++) { + ctl_write(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, i), 0x0); + ctl_write(ctl, REG_MDP5_CTL_LAYER_EXT_REG(ctl->id, i), 0x0); + } + + spin_unlock_irqrestore(&ctl->hw_lock, flags); +} + +#define PIPE_LEFT 0 +#define PIPE_RIGHT 1 +int mdp5_ctl_blend(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline, + enum mdp5_pipe stage[][MAX_PIPE_STAGE], + enum mdp5_pipe r_stage[][MAX_PIPE_STAGE], + u32 stage_cnt, u32 ctl_blend_op_flags) +{ + struct mdp5_hw_mixer *mixer = pipeline->mixer; + struct mdp5_hw_mixer *r_mixer = pipeline->r_mixer; + unsigned long flags; + u32 blend_cfg = 0, blend_ext_cfg = 0; + u32 r_blend_cfg = 0, r_blend_ext_cfg = 0; + int i, start_stage; + + mdp5_ctl_reset_blend_regs(ctl); + + if (ctl_blend_op_flags & MDP5_CTL_BLEND_OP_FLAG_BORDER_OUT) { + start_stage = STAGE0; + blend_cfg |= MDP5_CTL_LAYER_REG_BORDER_COLOR; + if (r_mixer) + r_blend_cfg |= MDP5_CTL_LAYER_REG_BORDER_COLOR; + } else { + start_stage = STAGE_BASE; + } + + for (i = start_stage; stage_cnt && i <= STAGE_MAX; i++) { + blend_cfg |= + mdp_ctl_blend_mask(stage[i][PIPE_LEFT], i) | + mdp_ctl_blend_mask(stage[i][PIPE_RIGHT], i); + blend_ext_cfg |= + mdp_ctl_blend_ext_mask(stage[i][PIPE_LEFT], i) | + mdp_ctl_blend_ext_mask(stage[i][PIPE_RIGHT], i); + if (r_mixer) { + r_blend_cfg |= + mdp_ctl_blend_mask(r_stage[i][PIPE_LEFT], i) | + mdp_ctl_blend_mask(r_stage[i][PIPE_RIGHT], i); + r_blend_ext_cfg |= + mdp_ctl_blend_ext_mask(r_stage[i][PIPE_LEFT], i) | + mdp_ctl_blend_ext_mask(r_stage[i][PIPE_RIGHT], i); + } + } + + spin_lock_irqsave(&ctl->hw_lock, flags); + if (ctl->cursor_on) + blend_cfg |= MDP5_CTL_LAYER_REG_CURSOR_OUT; + + ctl_write(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, mixer->lm), blend_cfg); + ctl_write(ctl, REG_MDP5_CTL_LAYER_EXT_REG(ctl->id, mixer->lm), + blend_ext_cfg); + if (r_mixer) { + ctl_write(ctl, REG_MDP5_CTL_LAYER_REG(ctl->id, r_mixer->lm), + r_blend_cfg); + ctl_write(ctl, REG_MDP5_CTL_LAYER_EXT_REG(ctl->id, r_mixer->lm), + r_blend_ext_cfg); + } + spin_unlock_irqrestore(&ctl->hw_lock, flags); + + ctl->pending_ctl_trigger = mdp_ctl_flush_mask_lm(mixer->lm); + if (r_mixer) + ctl->pending_ctl_trigger |= mdp_ctl_flush_mask_lm(r_mixer->lm); + + DBG("lm%d: blend config = 0x%08x. ext_cfg = 0x%08x", mixer->lm, + blend_cfg, blend_ext_cfg); + if (r_mixer) + DBG("lm%d: blend config = 0x%08x. ext_cfg = 0x%08x", + r_mixer->lm, r_blend_cfg, r_blend_ext_cfg); + + return 0; +} + +u32 mdp_ctl_flush_mask_encoder(struct mdp5_interface *intf) +{ + if (intf->type == INTF_WB) + return MDP5_CTL_FLUSH_WB; + + switch (intf->num) { + case 0: return MDP5_CTL_FLUSH_TIMING_0; + case 1: return MDP5_CTL_FLUSH_TIMING_1; + case 2: return MDP5_CTL_FLUSH_TIMING_2; + case 3: return MDP5_CTL_FLUSH_TIMING_3; + default: return 0; + } +} + +u32 mdp_ctl_flush_mask_cursor(int cursor_id) +{ + switch (cursor_id) { + case 0: return MDP5_CTL_FLUSH_CURSOR_0; + case 1: return MDP5_CTL_FLUSH_CURSOR_1; + default: return 0; + } +} + +u32 mdp_ctl_flush_mask_pipe(enum mdp5_pipe pipe) +{ + switch (pipe) { + case SSPP_VIG0: return MDP5_CTL_FLUSH_VIG0; + case SSPP_VIG1: return MDP5_CTL_FLUSH_VIG1; + case SSPP_VIG2: return MDP5_CTL_FLUSH_VIG2; + case SSPP_RGB0: return MDP5_CTL_FLUSH_RGB0; + case SSPP_RGB1: return MDP5_CTL_FLUSH_RGB1; + case SSPP_RGB2: return MDP5_CTL_FLUSH_RGB2; + case SSPP_DMA0: return MDP5_CTL_FLUSH_DMA0; + case SSPP_DMA1: return MDP5_CTL_FLUSH_DMA1; + case SSPP_VIG3: return MDP5_CTL_FLUSH_VIG3; + case SSPP_RGB3: return MDP5_CTL_FLUSH_RGB3; + case SSPP_CURSOR0: return MDP5_CTL_FLUSH_CURSOR_0; + case SSPP_CURSOR1: return MDP5_CTL_FLUSH_CURSOR_1; + default: return 0; + } +} + +u32 mdp_ctl_flush_mask_lm(int lm) +{ + switch (lm) { + case 0: return MDP5_CTL_FLUSH_LM0; + case 1: return MDP5_CTL_FLUSH_LM1; + case 2: return MDP5_CTL_FLUSH_LM2; + case 3: return MDP5_CTL_FLUSH_LM3; + case 4: return MDP5_CTL_FLUSH_LM4; + case 5: return MDP5_CTL_FLUSH_LM5; + default: return 0; + } +} + +static u32 fix_sw_flush(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline, + u32 flush_mask) +{ + struct mdp5_ctl_manager *ctl_mgr = ctl->ctlm; + u32 sw_mask = 0; +#define BIT_NEEDS_SW_FIX(bit) \ + (!(ctl_mgr->flush_hw_mask & bit) && (flush_mask & bit)) + + /* for some targets, cursor bit is the same as LM bit */ + if (BIT_NEEDS_SW_FIX(MDP5_CTL_FLUSH_CURSOR_0)) + sw_mask |= mdp_ctl_flush_mask_lm(pipeline->mixer->lm); + + return sw_mask; +} + +static void fix_for_single_flush(struct mdp5_ctl *ctl, u32 *flush_mask, + u32 *flush_id) +{ + struct mdp5_ctl_manager *ctl_mgr = ctl->ctlm; + + if (ctl->pair) { + DBG("CTL %d FLUSH pending mask %x", ctl->id, *flush_mask); + ctl->flush_pending = true; + ctl_mgr->single_flush_pending_mask |= (*flush_mask); + *flush_mask = 0; + + if (ctl->pair->flush_pending) { + *flush_id = min_t(u32, ctl->id, ctl->pair->id); + *flush_mask = ctl_mgr->single_flush_pending_mask; + + ctl->flush_pending = false; + ctl->pair->flush_pending = false; + ctl_mgr->single_flush_pending_mask = 0; + + DBG("Single FLUSH mask %x,ID %d", *flush_mask, + *flush_id); + } + } +} + +/** + * mdp5_ctl_commit() - Register Flush + * + * The flush register is used to indicate several registers are all + * programmed, and are safe to update to the back copy of the double + * buffered registers. + * + * Some registers FLUSH bits are shared when the hardware does not have + * dedicated bits for them; handling these is the job of fix_sw_flush(). + * + * CTL registers need to be flushed in some circumstances; if that is the + * case, some trigger bits will be present in both flush mask and + * ctl->pending_ctl_trigger. + * + * Return H/W flushed bit mask. + */ +u32 mdp5_ctl_commit(struct mdp5_ctl *ctl, + struct mdp5_pipeline *pipeline, + u32 flush_mask, bool start) +{ + struct mdp5_ctl_manager *ctl_mgr = ctl->ctlm; + unsigned long flags; + u32 flush_id = ctl->id; + u32 curr_ctl_flush_mask; + + VERB("flush_mask=%x, trigger=%x", flush_mask, ctl->pending_ctl_trigger); + + if (ctl->pending_ctl_trigger & flush_mask) { + flush_mask |= MDP5_CTL_FLUSH_CTL; + ctl->pending_ctl_trigger = 0; + } + + flush_mask |= fix_sw_flush(ctl, pipeline, flush_mask); + + flush_mask &= ctl_mgr->flush_hw_mask; + + curr_ctl_flush_mask = flush_mask; + + fix_for_single_flush(ctl, &flush_mask, &flush_id); + + if (!start) { + ctl->flush_mask |= flush_mask; + return curr_ctl_flush_mask; + } else { + flush_mask |= ctl->flush_mask; + ctl->flush_mask = 0; + } + + if (flush_mask) { + spin_lock_irqsave(&ctl->hw_lock, flags); + ctl_write(ctl, REG_MDP5_CTL_FLUSH(flush_id), flush_mask); + spin_unlock_irqrestore(&ctl->hw_lock, flags); + } + + if (start_signal_needed(ctl, pipeline)) { + send_start_signal(ctl); + } + + return curr_ctl_flush_mask; +} + +u32 mdp5_ctl_get_commit_status(struct mdp5_ctl *ctl) +{ + return ctl_read(ctl, REG_MDP5_CTL_FLUSH(ctl->id)); +} + +int mdp5_ctl_get_ctl_id(struct mdp5_ctl *ctl) +{ + return WARN_ON(!ctl) ? -EINVAL : ctl->id; +} + +/* + * mdp5_ctl_pair() - Associate 2 booked CTLs for single FLUSH + */ +int mdp5_ctl_pair(struct mdp5_ctl *ctlx, struct mdp5_ctl *ctly, bool enable) +{ + struct mdp5_ctl_manager *ctl_mgr = ctlx->ctlm; + struct mdp5_kms *mdp5_kms = get_kms(ctl_mgr); + + /* do nothing silently if hw doesn't support */ + if (!ctl_mgr->single_flush_supported) + return 0; + + if (!enable) { + ctlx->pair = NULL; + ctly->pair = NULL; + mdp5_write(mdp5_kms, REG_MDP5_SPARE_0, 0); + return 0; + } else if ((ctlx->pair != NULL) || (ctly->pair != NULL)) { + DRM_DEV_ERROR(ctl_mgr->dev->dev, "CTLs already paired\n"); + return -EINVAL; + } else if (!(ctlx->status & ctly->status & CTL_STAT_BOOKED)) { + DRM_DEV_ERROR(ctl_mgr->dev->dev, "Only pair booked CTLs\n"); + return -EINVAL; + } + + ctlx->pair = ctly; + ctly->pair = ctlx; + + mdp5_write(mdp5_kms, REG_MDP5_SPARE_0, + MDP5_SPARE_0_SPLIT_DPL_SINGLE_FLUSH_EN); + + return 0; +} + +/* + * mdp5_ctl_request() - CTL allocation + * + * Try to return booked CTL for @intf_num is 1 or 2, unbooked for other INTFs. + * If no CTL is available in preferred category, allocate from the other one. + * + * @return fail if no CTL is available. + */ +struct mdp5_ctl *mdp5_ctlm_request(struct mdp5_ctl_manager *ctl_mgr, + int intf_num) +{ + struct mdp5_ctl *ctl = NULL; + const u32 checkm = CTL_STAT_BUSY | CTL_STAT_BOOKED; + u32 match = ((intf_num == 1) || (intf_num == 2)) ? CTL_STAT_BOOKED : 0; + unsigned long flags; + int c; + + spin_lock_irqsave(&ctl_mgr->pool_lock, flags); + + /* search the preferred */ + for (c = 0; c < ctl_mgr->nctl; c++) + if ((ctl_mgr->ctls[c].status & checkm) == match) + goto found; + + dev_warn(ctl_mgr->dev->dev, + "fall back to the other CTL category for INTF %d!\n", intf_num); + + match ^= CTL_STAT_BOOKED; + for (c = 0; c < ctl_mgr->nctl; c++) + if ((ctl_mgr->ctls[c].status & checkm) == match) + goto found; + + DRM_DEV_ERROR(ctl_mgr->dev->dev, "No more CTL available!"); + goto unlock; + +found: + ctl = &ctl_mgr->ctls[c]; + ctl->status |= CTL_STAT_BUSY; + ctl->pending_ctl_trigger = 0; + DBG("CTL %d allocated", ctl->id); + +unlock: + spin_unlock_irqrestore(&ctl_mgr->pool_lock, flags); + return ctl; +} + +void mdp5_ctlm_hw_reset(struct mdp5_ctl_manager *ctl_mgr) +{ + unsigned long flags; + int c; + + for (c = 0; c < ctl_mgr->nctl; c++) { + struct mdp5_ctl *ctl = &ctl_mgr->ctls[c]; + + spin_lock_irqsave(&ctl->hw_lock, flags); + ctl_write(ctl, REG_MDP5_CTL_OP(ctl->id), 0); + spin_unlock_irqrestore(&ctl->hw_lock, flags); + } +} + +void mdp5_ctlm_destroy(struct mdp5_ctl_manager *ctl_mgr) +{ + kfree(ctl_mgr); +} + +struct mdp5_ctl_manager *mdp5_ctlm_init(struct drm_device *dev, + void __iomem *mmio_base, struct mdp5_cfg_handler *cfg_hnd) +{ + struct mdp5_ctl_manager *ctl_mgr; + const struct mdp5_cfg_hw *hw_cfg = mdp5_cfg_get_hw_config(cfg_hnd); + int rev = mdp5_cfg_get_hw_rev(cfg_hnd); + unsigned dsi_cnt = 0; + const struct mdp5_ctl_block *ctl_cfg = &hw_cfg->ctl; + unsigned long flags; + int c, ret; + + ctl_mgr = kzalloc(sizeof(*ctl_mgr), GFP_KERNEL); + if (!ctl_mgr) { + DRM_DEV_ERROR(dev->dev, "failed to allocate CTL manager\n"); + ret = -ENOMEM; + goto fail; + } + + if (WARN_ON(ctl_cfg->count > MAX_CTL)) { + DRM_DEV_ERROR(dev->dev, "Increase static pool size to at least %d\n", + ctl_cfg->count); + ret = -ENOSPC; + goto fail; + } + + /* initialize the CTL manager: */ + ctl_mgr->dev = dev; + ctl_mgr->nlm = hw_cfg->lm.count; + ctl_mgr->nctl = ctl_cfg->count; + ctl_mgr->flush_hw_mask = ctl_cfg->flush_hw_mask; + spin_lock_init(&ctl_mgr->pool_lock); + + /* initialize each CTL of the pool: */ + spin_lock_irqsave(&ctl_mgr->pool_lock, flags); + for (c = 0; c < ctl_mgr->nctl; c++) { + struct mdp5_ctl *ctl = &ctl_mgr->ctls[c]; + + if (WARN_ON(!ctl_cfg->base[c])) { + DRM_DEV_ERROR(dev->dev, "CTL_%d: base is null!\n", c); + ret = -EINVAL; + spin_unlock_irqrestore(&ctl_mgr->pool_lock, flags); + goto fail; + } + ctl->ctlm = ctl_mgr; + ctl->id = c; + ctl->reg_offset = ctl_cfg->base[c]; + ctl->status = 0; + spin_lock_init(&ctl->hw_lock); + } + + /* + * In Dual DSI case, CTL0 and CTL1 are always assigned to two DSI + * interfaces to support single FLUSH feature (Flush CTL0 and CTL1 when + * only write into CTL0's FLUSH register) to keep two DSI pipes in sync. + * Single FLUSH is supported from hw rev v3.0. + */ + for (c = 0; c < ARRAY_SIZE(hw_cfg->intf.connect); c++) + if (hw_cfg->intf.connect[c] == INTF_DSI) + dsi_cnt++; + if ((rev >= 3) && (dsi_cnt > 1)) { + ctl_mgr->single_flush_supported = true; + /* Reserve CTL0/1 for INTF1/2 */ + ctl_mgr->ctls[0].status |= CTL_STAT_BOOKED; + ctl_mgr->ctls[1].status |= CTL_STAT_BOOKED; + } + spin_unlock_irqrestore(&ctl_mgr->pool_lock, flags); + DBG("Pool of %d CTLs created.", ctl_mgr->nctl); + + return ctl_mgr; + +fail: + if (ctl_mgr) + mdp5_ctlm_destroy(ctl_mgr); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_ctl.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_ctl.h new file mode 100644 index 000000000..c2af68aa7 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_ctl.h @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014 The Linux Foundation. All rights reserved. + */ + +#ifndef __MDP5_CTL_H__ +#define __MDP5_CTL_H__ + +#include "msm_drv.h" + +/* + * CTL Manager prototypes: + * mdp5_ctlm_init() returns a ctlm (CTL Manager) handler, + * which is then used to call the other mdp5_ctlm_*(ctlm, ...) functions. + */ +struct mdp5_ctl_manager; +struct mdp5_ctl_manager *mdp5_ctlm_init(struct drm_device *dev, + void __iomem *mmio_base, struct mdp5_cfg_handler *cfg_hnd); +void mdp5_ctlm_hw_reset(struct mdp5_ctl_manager *ctlm); +void mdp5_ctlm_destroy(struct mdp5_ctl_manager *ctlm); + +/* + * CTL prototypes: + * mdp5_ctl_request(ctlm, ...) returns a ctl (CTL resource) handler, + * which is then used to call the other mdp5_ctl_*(ctl, ...) functions. + */ +struct mdp5_ctl *mdp5_ctlm_request(struct mdp5_ctl_manager *ctlm, int intf_num); + +int mdp5_ctl_get_ctl_id(struct mdp5_ctl *ctl); + +struct mdp5_interface; +struct mdp5_pipeline; +int mdp5_ctl_set_pipeline(struct mdp5_ctl *ctl, struct mdp5_pipeline *p); +int mdp5_ctl_set_encoder_state(struct mdp5_ctl *ctl, struct mdp5_pipeline *p, + bool enabled); + +int mdp5_ctl_set_cursor(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline, + int cursor_id, bool enable); +int mdp5_ctl_pair(struct mdp5_ctl *ctlx, struct mdp5_ctl *ctly, bool enable); + +#define MAX_PIPE_STAGE 2 + +/* + * mdp5_ctl_blend() - Blend multiple layers on a Layer Mixer (LM) + * + * @stage: array to contain the pipe num for each stage + * @stage_cnt: valid stage number in stage array + * @ctl_blend_op_flags: blender operation mode flags + * + * Note: + * CTL registers need to be flushed after calling this function + * (call mdp5_ctl_commit() with mdp_ctl_flush_mask_ctl() mask) + */ +#define MDP5_CTL_BLEND_OP_FLAG_BORDER_OUT BIT(0) +int mdp5_ctl_blend(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline, + enum mdp5_pipe stage[][MAX_PIPE_STAGE], + enum mdp5_pipe r_stage[][MAX_PIPE_STAGE], + u32 stage_cnt, u32 ctl_blend_op_flags); + +/** + * mdp_ctl_flush_mask...() - Register FLUSH masks + * + * These masks are used to specify which block(s) need to be flushed + * through @flush_mask parameter in mdp5_ctl_commit(.., flush_mask). + */ +u32 mdp_ctl_flush_mask_lm(int lm); +u32 mdp_ctl_flush_mask_pipe(enum mdp5_pipe pipe); +u32 mdp_ctl_flush_mask_cursor(int cursor_id); +u32 mdp_ctl_flush_mask_encoder(struct mdp5_interface *intf); + +/* @flush_mask: see CTL flush masks definitions below */ +u32 mdp5_ctl_commit(struct mdp5_ctl *ctl, struct mdp5_pipeline *pipeline, + u32 flush_mask, bool start); +u32 mdp5_ctl_get_commit_status(struct mdp5_ctl *ctl); + + + +#endif /* __MDP5_CTL_H__ */ diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_encoder.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_encoder.c new file mode 100644 index 000000000..79d67c495 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_encoder.c @@ -0,0 +1,370 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <drm/drm_crtc.h> +#include <drm/drm_probe_helper.h> + +#include "mdp5_kms.h" + +static struct mdp5_kms *get_kms(struct drm_encoder *encoder) +{ + struct msm_drm_private *priv = encoder->dev->dev_private; + return to_mdp5_kms(to_mdp_kms(priv->kms)); +} + +static void mdp5_encoder_destroy(struct drm_encoder *encoder) +{ + struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder); + drm_encoder_cleanup(encoder); + kfree(mdp5_encoder); +} + +static const struct drm_encoder_funcs mdp5_encoder_funcs = { + .destroy = mdp5_encoder_destroy, +}; + +static void mdp5_vid_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder); + struct mdp5_kms *mdp5_kms = get_kms(encoder); + struct drm_device *dev = encoder->dev; + struct drm_connector *connector; + int intf = mdp5_encoder->intf->num; + uint32_t dtv_hsync_skew, vsync_period, vsync_len, ctrl_pol; + uint32_t display_v_start, display_v_end; + uint32_t hsync_start_x, hsync_end_x; + uint32_t format = 0x2100; + unsigned long flags; + + mode = adjusted_mode; + + DBG("set mode: " DRM_MODE_FMT, DRM_MODE_ARG(mode)); + + ctrl_pol = 0; + + /* DSI controller cannot handle active-low sync signals. */ + if (mdp5_encoder->intf->type != INTF_DSI) { + if (mode->flags & DRM_MODE_FLAG_NHSYNC) + ctrl_pol |= MDP5_INTF_POLARITY_CTL_HSYNC_LOW; + if (mode->flags & DRM_MODE_FLAG_NVSYNC) + ctrl_pol |= MDP5_INTF_POLARITY_CTL_VSYNC_LOW; + } + /* probably need to get DATA_EN polarity from panel.. */ + + dtv_hsync_skew = 0; /* get this from panel? */ + + /* Get color format from panel, default is 8bpc */ + list_for_each_entry(connector, &dev->mode_config.connector_list, head) { + if (connector->encoder == encoder) { + switch (connector->display_info.bpc) { + case 4: + format |= 0; + break; + case 5: + format |= 0x15; + break; + case 6: + format |= 0x2A; + break; + case 8: + default: + format |= 0x3F; + break; + } + break; + } + } + + hsync_start_x = (mode->htotal - mode->hsync_start); + hsync_end_x = mode->htotal - (mode->hsync_start - mode->hdisplay) - 1; + + vsync_period = mode->vtotal * mode->htotal; + vsync_len = (mode->vsync_end - mode->vsync_start) * mode->htotal; + display_v_start = (mode->vtotal - mode->vsync_start) * mode->htotal + dtv_hsync_skew; + display_v_end = vsync_period - ((mode->vsync_start - mode->vdisplay) * mode->htotal) + dtv_hsync_skew - 1; + + /* + * For edp only: + * DISPLAY_V_START = (VBP * HCYCLE) + HBP + * DISPLAY_V_END = (VBP + VACTIVE) * HCYCLE - 1 - HFP + */ + if (mdp5_encoder->intf->type == INTF_eDP) { + display_v_start += mode->htotal - mode->hsync_start; + display_v_end -= mode->hsync_start - mode->hdisplay; + } + + spin_lock_irqsave(&mdp5_encoder->intf_lock, flags); + + mdp5_write(mdp5_kms, REG_MDP5_INTF_HSYNC_CTL(intf), + MDP5_INTF_HSYNC_CTL_PULSEW(mode->hsync_end - mode->hsync_start) | + MDP5_INTF_HSYNC_CTL_PERIOD(mode->htotal)); + mdp5_write(mdp5_kms, REG_MDP5_INTF_VSYNC_PERIOD_F0(intf), vsync_period); + mdp5_write(mdp5_kms, REG_MDP5_INTF_VSYNC_LEN_F0(intf), vsync_len); + mdp5_write(mdp5_kms, REG_MDP5_INTF_DISPLAY_HCTL(intf), + MDP5_INTF_DISPLAY_HCTL_START(hsync_start_x) | + MDP5_INTF_DISPLAY_HCTL_END(hsync_end_x)); + mdp5_write(mdp5_kms, REG_MDP5_INTF_DISPLAY_VSTART_F0(intf), display_v_start); + mdp5_write(mdp5_kms, REG_MDP5_INTF_DISPLAY_VEND_F0(intf), display_v_end); + mdp5_write(mdp5_kms, REG_MDP5_INTF_BORDER_COLOR(intf), 0); + mdp5_write(mdp5_kms, REG_MDP5_INTF_UNDERFLOW_COLOR(intf), 0xff); + mdp5_write(mdp5_kms, REG_MDP5_INTF_HSYNC_SKEW(intf), dtv_hsync_skew); + mdp5_write(mdp5_kms, REG_MDP5_INTF_POLARITY_CTL(intf), ctrl_pol); + mdp5_write(mdp5_kms, REG_MDP5_INTF_ACTIVE_HCTL(intf), + MDP5_INTF_ACTIVE_HCTL_START(0) | + MDP5_INTF_ACTIVE_HCTL_END(0)); + mdp5_write(mdp5_kms, REG_MDP5_INTF_ACTIVE_VSTART_F0(intf), 0); + mdp5_write(mdp5_kms, REG_MDP5_INTF_ACTIVE_VEND_F0(intf), 0); + mdp5_write(mdp5_kms, REG_MDP5_INTF_PANEL_FORMAT(intf), format); + mdp5_write(mdp5_kms, REG_MDP5_INTF_FRAME_LINE_COUNT_EN(intf), 0x3); /* frame+line? */ + + spin_unlock_irqrestore(&mdp5_encoder->intf_lock, flags); + + mdp5_crtc_set_pipeline(encoder->crtc); +} + +static void mdp5_vid_encoder_disable(struct drm_encoder *encoder) +{ + struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder); + struct mdp5_kms *mdp5_kms = get_kms(encoder); + struct mdp5_ctl *ctl = mdp5_encoder->ctl; + struct mdp5_pipeline *pipeline = mdp5_crtc_get_pipeline(encoder->crtc); + struct mdp5_hw_mixer *mixer = mdp5_crtc_get_mixer(encoder->crtc); + struct mdp5_interface *intf = mdp5_encoder->intf; + int intfn = mdp5_encoder->intf->num; + unsigned long flags; + + if (WARN_ON(!mdp5_encoder->enabled)) + return; + + mdp5_ctl_set_encoder_state(ctl, pipeline, false); + + spin_lock_irqsave(&mdp5_encoder->intf_lock, flags); + mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(intfn), 0); + spin_unlock_irqrestore(&mdp5_encoder->intf_lock, flags); + mdp5_ctl_commit(ctl, pipeline, mdp_ctl_flush_mask_encoder(intf), true); + + /* + * Wait for a vsync so we know the ENABLE=0 latched before + * the (connector) source of the vsync's gets disabled, + * otherwise we end up in a funny state if we re-enable + * before the disable latches, which results that some of + * the settings changes for the new modeset (like new + * scanout buffer) don't latch properly.. + */ + mdp_irq_wait(&mdp5_kms->base, intf2vblank(mixer, intf)); + + mdp5_encoder->enabled = false; +} + +static void mdp5_vid_encoder_enable(struct drm_encoder *encoder) +{ + struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder); + struct mdp5_kms *mdp5_kms = get_kms(encoder); + struct mdp5_ctl *ctl = mdp5_encoder->ctl; + struct mdp5_interface *intf = mdp5_encoder->intf; + struct mdp5_pipeline *pipeline = mdp5_crtc_get_pipeline(encoder->crtc); + int intfn = intf->num; + unsigned long flags; + + if (WARN_ON(mdp5_encoder->enabled)) + return; + + spin_lock_irqsave(&mdp5_encoder->intf_lock, flags); + mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(intfn), 1); + spin_unlock_irqrestore(&mdp5_encoder->intf_lock, flags); + mdp5_ctl_commit(ctl, pipeline, mdp_ctl_flush_mask_encoder(intf), true); + + mdp5_ctl_set_encoder_state(ctl, pipeline, true); + + mdp5_encoder->enabled = true; +} + +static void mdp5_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder); + struct mdp5_interface *intf = mdp5_encoder->intf; + + if (intf->mode == MDP5_INTF_DSI_MODE_COMMAND) + mdp5_cmd_encoder_mode_set(encoder, mode, adjusted_mode); + else + mdp5_vid_encoder_mode_set(encoder, mode, adjusted_mode); +} + +static void mdp5_encoder_disable(struct drm_encoder *encoder) +{ + struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder); + struct mdp5_interface *intf = mdp5_encoder->intf; + + if (intf->mode == MDP5_INTF_DSI_MODE_COMMAND) + mdp5_cmd_encoder_disable(encoder); + else + mdp5_vid_encoder_disable(encoder); +} + +static void mdp5_encoder_enable(struct drm_encoder *encoder) +{ + struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder); + struct mdp5_interface *intf = mdp5_encoder->intf; + /* this isn't right I think */ + struct drm_crtc_state *cstate = encoder->crtc->state; + + mdp5_encoder_mode_set(encoder, &cstate->mode, &cstate->adjusted_mode); + + if (intf->mode == MDP5_INTF_DSI_MODE_COMMAND) + mdp5_cmd_encoder_enable(encoder); + else + mdp5_vid_encoder_enable(encoder); +} + +static int mdp5_encoder_atomic_check(struct drm_encoder *encoder, + struct drm_crtc_state *crtc_state, + struct drm_connector_state *conn_state) +{ + struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder); + struct mdp5_crtc_state *mdp5_cstate = to_mdp5_crtc_state(crtc_state); + struct mdp5_interface *intf = mdp5_encoder->intf; + struct mdp5_ctl *ctl = mdp5_encoder->ctl; + + mdp5_cstate->ctl = ctl; + mdp5_cstate->pipeline.intf = intf; + + /* + * This is a bit awkward, but we want to flush the CTL and hit the + * START bit at most once for an atomic update. In the non-full- + * modeset case, this is done from crtc->atomic_flush(), but that + * is too early in the case of full modeset, in which case we + * defer to encoder->enable(). But we need to *know* whether + * encoder->enable() will be called to do this: + */ + if (drm_atomic_crtc_needs_modeset(crtc_state)) + mdp5_cstate->defer_start = true; + + return 0; +} + +static const struct drm_encoder_helper_funcs mdp5_encoder_helper_funcs = { + .disable = mdp5_encoder_disable, + .enable = mdp5_encoder_enable, + .atomic_check = mdp5_encoder_atomic_check, +}; + +int mdp5_encoder_get_linecount(struct drm_encoder *encoder) +{ + struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder); + struct mdp5_kms *mdp5_kms = get_kms(encoder); + int intf = mdp5_encoder->intf->num; + + return mdp5_read(mdp5_kms, REG_MDP5_INTF_LINE_COUNT(intf)); +} + +u32 mdp5_encoder_get_framecount(struct drm_encoder *encoder) +{ + struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder); + struct mdp5_kms *mdp5_kms = get_kms(encoder); + int intf = mdp5_encoder->intf->num; + + return mdp5_read(mdp5_kms, REG_MDP5_INTF_FRAME_COUNT(intf)); +} + +int mdp5_vid_encoder_set_split_display(struct drm_encoder *encoder, + struct drm_encoder *slave_encoder) +{ + struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder); + struct mdp5_encoder *mdp5_slave_enc = to_mdp5_encoder(slave_encoder); + struct mdp5_kms *mdp5_kms; + struct device *dev; + int intf_num; + u32 data = 0; + + if (!encoder || !slave_encoder) + return -EINVAL; + + mdp5_kms = get_kms(encoder); + intf_num = mdp5_encoder->intf->num; + + /* Switch slave encoder's TimingGen Sync mode, + * to use the master's enable signal for the slave encoder. + */ + if (intf_num == 1) + data |= MDP5_SPLIT_DPL_LOWER_INTF2_TG_SYNC; + else if (intf_num == 2) + data |= MDP5_SPLIT_DPL_LOWER_INTF1_TG_SYNC; + else + return -EINVAL; + + dev = &mdp5_kms->pdev->dev; + /* Make sure clocks are on when connectors calling this function. */ + pm_runtime_get_sync(dev); + + /* Dumb Panel, Sync mode */ + mdp5_write(mdp5_kms, REG_MDP5_SPLIT_DPL_UPPER, 0); + mdp5_write(mdp5_kms, REG_MDP5_SPLIT_DPL_LOWER, data); + mdp5_write(mdp5_kms, REG_MDP5_SPLIT_DPL_EN, 1); + + mdp5_ctl_pair(mdp5_encoder->ctl, mdp5_slave_enc->ctl, true); + + pm_runtime_put_sync(dev); + + return 0; +} + +void mdp5_encoder_set_intf_mode(struct drm_encoder *encoder, bool cmd_mode) +{ + struct mdp5_encoder *mdp5_encoder = to_mdp5_encoder(encoder); + struct mdp5_interface *intf = mdp5_encoder->intf; + + /* TODO: Expand this to set writeback modes too */ + if (cmd_mode) { + WARN_ON(intf->type != INTF_DSI); + intf->mode = MDP5_INTF_DSI_MODE_COMMAND; + } else { + if (intf->type == INTF_DSI) + intf->mode = MDP5_INTF_DSI_MODE_VIDEO; + else + intf->mode = MDP5_INTF_MODE_NONE; + } +} + +/* initialize encoder */ +struct drm_encoder *mdp5_encoder_init(struct drm_device *dev, + struct mdp5_interface *intf, + struct mdp5_ctl *ctl) +{ + struct drm_encoder *encoder = NULL; + struct mdp5_encoder *mdp5_encoder; + int enc_type = (intf->type == INTF_DSI) ? + DRM_MODE_ENCODER_DSI : DRM_MODE_ENCODER_TMDS; + int ret; + + mdp5_encoder = kzalloc(sizeof(*mdp5_encoder), GFP_KERNEL); + if (!mdp5_encoder) { + ret = -ENOMEM; + goto fail; + } + + encoder = &mdp5_encoder->base; + mdp5_encoder->ctl = ctl; + mdp5_encoder->intf = intf; + + spin_lock_init(&mdp5_encoder->intf_lock); + + drm_encoder_init(dev, encoder, &mdp5_encoder_funcs, enc_type, NULL); + + drm_encoder_helper_add(encoder, &mdp5_encoder_helper_funcs); + + return encoder; + +fail: + if (encoder) + mdp5_encoder_destroy(encoder); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_irq.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_irq.c new file mode 100644 index 000000000..9b4c8d92f --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_irq.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <linux/irq.h> + +#include <drm/drm_print.h> +#include <drm/drm_vblank.h> + +#include "msm_drv.h" +#include "mdp5_kms.h" + +void mdp5_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask, + uint32_t old_irqmask) +{ + mdp5_write(to_mdp5_kms(mdp_kms), REG_MDP5_INTR_CLEAR, + irqmask ^ (irqmask & old_irqmask)); + mdp5_write(to_mdp5_kms(mdp_kms), REG_MDP5_INTR_EN, irqmask); +} + +static void mdp5_irq_error_handler(struct mdp_irq *irq, uint32_t irqstatus) +{ + struct mdp5_kms *mdp5_kms = container_of(irq, struct mdp5_kms, error_handler); + static DEFINE_RATELIMIT_STATE(rs, 5*HZ, 1); + extern bool dumpstate; + + DRM_ERROR_RATELIMITED("errors: %08x\n", irqstatus); + + if (dumpstate && __ratelimit(&rs)) { + struct drm_printer p = drm_info_printer(mdp5_kms->dev->dev); + drm_state_dump(mdp5_kms->dev, &p); + if (mdp5_kms->smp) + mdp5_smp_dump(mdp5_kms->smp, &p); + } +} + +void mdp5_irq_preinstall(struct msm_kms *kms) +{ + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms)); + struct device *dev = &mdp5_kms->pdev->dev; + + pm_runtime_get_sync(dev); + mdp5_write(mdp5_kms, REG_MDP5_INTR_CLEAR, 0xffffffff); + mdp5_write(mdp5_kms, REG_MDP5_INTR_EN, 0x00000000); + pm_runtime_put_sync(dev); +} + +int mdp5_irq_postinstall(struct msm_kms *kms) +{ + struct mdp_kms *mdp_kms = to_mdp_kms(kms); + struct mdp5_kms *mdp5_kms = to_mdp5_kms(mdp_kms); + struct device *dev = &mdp5_kms->pdev->dev; + struct mdp_irq *error_handler = &mdp5_kms->error_handler; + + error_handler->irq = mdp5_irq_error_handler; + error_handler->irqmask = MDP5_IRQ_INTF0_UNDER_RUN | + MDP5_IRQ_INTF1_UNDER_RUN | + MDP5_IRQ_INTF2_UNDER_RUN | + MDP5_IRQ_INTF3_UNDER_RUN; + + pm_runtime_get_sync(dev); + mdp_irq_register(mdp_kms, error_handler); + pm_runtime_put_sync(dev); + + return 0; +} + +void mdp5_irq_uninstall(struct msm_kms *kms) +{ + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms)); + struct device *dev = &mdp5_kms->pdev->dev; + + pm_runtime_get_sync(dev); + mdp5_write(mdp5_kms, REG_MDP5_INTR_EN, 0x00000000); + pm_runtime_put_sync(dev); +} + +irqreturn_t mdp5_irq(struct msm_kms *kms) +{ + struct mdp_kms *mdp_kms = to_mdp_kms(kms); + struct mdp5_kms *mdp5_kms = to_mdp5_kms(mdp_kms); + struct drm_device *dev = mdp5_kms->dev; + struct msm_drm_private *priv = dev->dev_private; + unsigned int id; + uint32_t status, enable; + + enable = mdp5_read(mdp5_kms, REG_MDP5_INTR_EN); + status = mdp5_read(mdp5_kms, REG_MDP5_INTR_STATUS) & enable; + mdp5_write(mdp5_kms, REG_MDP5_INTR_CLEAR, status); + + VERB("status=%08x", status); + + mdp_dispatch_irqs(mdp_kms, status); + + for (id = 0; id < priv->num_crtcs; id++) + if (status & mdp5_crtc_vblank(priv->crtcs[id])) + drm_handle_vblank(dev, id); + + return IRQ_HANDLED; +} + +int mdp5_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc) +{ + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms)); + struct device *dev = &mdp5_kms->pdev->dev; + + pm_runtime_get_sync(dev); + mdp_update_vblank_mask(to_mdp_kms(kms), + mdp5_crtc_vblank(crtc), true); + pm_runtime_put_sync(dev); + + return 0; +} + +void mdp5_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc) +{ + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms)); + struct device *dev = &mdp5_kms->pdev->dev; + + pm_runtime_get_sync(dev); + mdp_update_vblank_mask(to_mdp_kms(kms), + mdp5_crtc_vblank(crtc), false); + pm_runtime_put_sync(dev); +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c new file mode 100644 index 000000000..9baaaef70 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.c @@ -0,0 +1,1068 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <linux/delay.h> +#include <linux/interconnect.h> +#include <linux/of_irq.h> + +#include <drm/drm_debugfs.h> +#include <drm/drm_drv.h> +#include <drm/drm_file.h> +#include <drm/drm_vblank.h> + +#include "msm_drv.h" +#include "msm_gem.h" +#include "msm_mmu.h" +#include "mdp5_kms.h" + +static int mdp5_hw_init(struct msm_kms *kms) +{ + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms)); + struct device *dev = &mdp5_kms->pdev->dev; + unsigned long flags; + + pm_runtime_get_sync(dev); + + /* Magic unknown register writes: + * + * W VBIF:0x004 00000001 (mdss_mdp.c:839) + * W MDP5:0x2e0 0xe9 (mdss_mdp.c:839) + * W MDP5:0x2e4 0x55 (mdss_mdp.c:839) + * W MDP5:0x3ac 0xc0000ccc (mdss_mdp.c:839) + * W MDP5:0x3b4 0xc0000ccc (mdss_mdp.c:839) + * W MDP5:0x3bc 0xcccccc (mdss_mdp.c:839) + * W MDP5:0x4a8 0xcccc0c0 (mdss_mdp.c:839) + * W MDP5:0x4b0 0xccccc0c0 (mdss_mdp.c:839) + * W MDP5:0x4b8 0xccccc000 (mdss_mdp.c:839) + * + * Downstream fbdev driver gets these register offsets/values + * from DT.. not really sure what these registers are or if + * different values for different boards/SoC's, etc. I guess + * they are the golden registers. + * + * Not setting these does not seem to cause any problem. But + * we may be getting lucky with the bootloader initializing + * them for us. OTOH, if we can always count on the bootloader + * setting the golden registers, then perhaps we don't need to + * care. + */ + + spin_lock_irqsave(&mdp5_kms->resource_lock, flags); + mdp5_write(mdp5_kms, REG_MDP5_DISP_INTF_SEL, 0); + spin_unlock_irqrestore(&mdp5_kms->resource_lock, flags); + + mdp5_ctlm_hw_reset(mdp5_kms->ctlm); + + pm_runtime_put_sync(dev); + + return 0; +} + +/* Global/shared object state funcs */ + +/* + * This is a helper that returns the private state currently in operation. + * Note that this would return the "old_state" if called in the atomic check + * path, and the "new_state" after the atomic swap has been done. + */ +struct mdp5_global_state * +mdp5_get_existing_global_state(struct mdp5_kms *mdp5_kms) +{ + return to_mdp5_global_state(mdp5_kms->glob_state.state); +} + +/* + * This acquires the modeset lock set aside for global state, creates + * a new duplicated private object state. + */ +struct mdp5_global_state *mdp5_get_global_state(struct drm_atomic_state *s) +{ + struct msm_drm_private *priv = s->dev->dev_private; + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms)); + struct drm_private_state *priv_state; + int ret; + + ret = drm_modeset_lock(&mdp5_kms->glob_state_lock, s->acquire_ctx); + if (ret) + return ERR_PTR(ret); + + priv_state = drm_atomic_get_private_obj_state(s, &mdp5_kms->glob_state); + if (IS_ERR(priv_state)) + return ERR_CAST(priv_state); + + return to_mdp5_global_state(priv_state); +} + +static struct drm_private_state * +mdp5_global_duplicate_state(struct drm_private_obj *obj) +{ + struct mdp5_global_state *state; + + state = kmemdup(obj->state, sizeof(*state), GFP_KERNEL); + if (!state) + return NULL; + + __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base); + + return &state->base; +} + +static void mdp5_global_destroy_state(struct drm_private_obj *obj, + struct drm_private_state *state) +{ + struct mdp5_global_state *mdp5_state = to_mdp5_global_state(state); + + kfree(mdp5_state); +} + +static const struct drm_private_state_funcs mdp5_global_state_funcs = { + .atomic_duplicate_state = mdp5_global_duplicate_state, + .atomic_destroy_state = mdp5_global_destroy_state, +}; + +static int mdp5_global_obj_init(struct mdp5_kms *mdp5_kms) +{ + struct mdp5_global_state *state; + + drm_modeset_lock_init(&mdp5_kms->glob_state_lock); + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + state->mdp5_kms = mdp5_kms; + + drm_atomic_private_obj_init(mdp5_kms->dev, &mdp5_kms->glob_state, + &state->base, + &mdp5_global_state_funcs); + return 0; +} + +static void mdp5_enable_commit(struct msm_kms *kms) +{ + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms)); + pm_runtime_get_sync(&mdp5_kms->pdev->dev); +} + +static void mdp5_disable_commit(struct msm_kms *kms) +{ + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms)); + pm_runtime_put_sync(&mdp5_kms->pdev->dev); +} + +static void mdp5_prepare_commit(struct msm_kms *kms, struct drm_atomic_state *state) +{ + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms)); + struct mdp5_global_state *global_state; + + global_state = mdp5_get_existing_global_state(mdp5_kms); + + if (mdp5_kms->smp) + mdp5_smp_prepare_commit(mdp5_kms->smp, &global_state->smp); +} + +static void mdp5_flush_commit(struct msm_kms *kms, unsigned crtc_mask) +{ + /* TODO */ +} + +static void mdp5_wait_flush(struct msm_kms *kms, unsigned crtc_mask) +{ + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms)); + struct drm_crtc *crtc; + + for_each_crtc_mask(mdp5_kms->dev, crtc, crtc_mask) + mdp5_crtc_wait_for_commit_done(crtc); +} + +static void mdp5_complete_commit(struct msm_kms *kms, unsigned crtc_mask) +{ + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms)); + struct mdp5_global_state *global_state; + + global_state = mdp5_get_existing_global_state(mdp5_kms); + + if (mdp5_kms->smp) + mdp5_smp_complete_commit(mdp5_kms->smp, &global_state->smp); +} + +static long mdp5_round_pixclk(struct msm_kms *kms, unsigned long rate, + struct drm_encoder *encoder) +{ + return rate; +} + +static int mdp5_set_split_display(struct msm_kms *kms, + struct drm_encoder *encoder, + struct drm_encoder *slave_encoder, + bool is_cmd_mode) +{ + if (is_cmd_mode) + return mdp5_cmd_encoder_set_split_display(encoder, + slave_encoder); + else + return mdp5_vid_encoder_set_split_display(encoder, + slave_encoder); +} + +static void mdp5_set_encoder_mode(struct msm_kms *kms, + struct drm_encoder *encoder, + bool cmd_mode) +{ + mdp5_encoder_set_intf_mode(encoder, cmd_mode); +} + +static void mdp5_kms_destroy(struct msm_kms *kms) +{ + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(kms)); + struct msm_gem_address_space *aspace = kms->aspace; + int i; + + for (i = 0; i < mdp5_kms->num_hwmixers; i++) + mdp5_mixer_destroy(mdp5_kms->hwmixers[i]); + + for (i = 0; i < mdp5_kms->num_hwpipes; i++) + mdp5_pipe_destroy(mdp5_kms->hwpipes[i]); + + if (aspace) { + aspace->mmu->funcs->detach(aspace->mmu); + msm_gem_address_space_put(aspace); + } +} + +#ifdef CONFIG_DEBUG_FS +static int smp_show(struct seq_file *m, void *arg) +{ + struct drm_info_node *node = (struct drm_info_node *) m->private; + struct drm_device *dev = node->minor->dev; + struct msm_drm_private *priv = dev->dev_private; + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms)); + struct drm_printer p = drm_seq_file_printer(m); + + if (!mdp5_kms->smp) { + drm_printf(&p, "no SMP pool\n"); + return 0; + } + + mdp5_smp_dump(mdp5_kms->smp, &p); + + return 0; +} + +static struct drm_info_list mdp5_debugfs_list[] = { + {"smp", smp_show }, +}; + +static int mdp5_kms_debugfs_init(struct msm_kms *kms, struct drm_minor *minor) +{ + drm_debugfs_create_files(mdp5_debugfs_list, + ARRAY_SIZE(mdp5_debugfs_list), + minor->debugfs_root, minor); + + return 0; +} +#endif + +static const struct mdp_kms_funcs kms_funcs = { + .base = { + .hw_init = mdp5_hw_init, + .irq_preinstall = mdp5_irq_preinstall, + .irq_postinstall = mdp5_irq_postinstall, + .irq_uninstall = mdp5_irq_uninstall, + .irq = mdp5_irq, + .enable_vblank = mdp5_enable_vblank, + .disable_vblank = mdp5_disable_vblank, + .flush_commit = mdp5_flush_commit, + .enable_commit = mdp5_enable_commit, + .disable_commit = mdp5_disable_commit, + .prepare_commit = mdp5_prepare_commit, + .wait_flush = mdp5_wait_flush, + .complete_commit = mdp5_complete_commit, + .get_format = mdp_get_format, + .round_pixclk = mdp5_round_pixclk, + .set_split_display = mdp5_set_split_display, + .set_encoder_mode = mdp5_set_encoder_mode, + .destroy = mdp5_kms_destroy, +#ifdef CONFIG_DEBUG_FS + .debugfs_init = mdp5_kms_debugfs_init, +#endif + }, + .set_irqmask = mdp5_set_irqmask, +}; + +int mdp5_disable(struct mdp5_kms *mdp5_kms) +{ + DBG(""); + + mdp5_kms->enable_count--; + WARN_ON(mdp5_kms->enable_count < 0); + + if (mdp5_kms->tbu_rt_clk) + clk_disable_unprepare(mdp5_kms->tbu_rt_clk); + if (mdp5_kms->tbu_clk) + clk_disable_unprepare(mdp5_kms->tbu_clk); + clk_disable_unprepare(mdp5_kms->ahb_clk); + clk_disable_unprepare(mdp5_kms->axi_clk); + clk_disable_unprepare(mdp5_kms->core_clk); + if (mdp5_kms->lut_clk) + clk_disable_unprepare(mdp5_kms->lut_clk); + + return 0; +} + +int mdp5_enable(struct mdp5_kms *mdp5_kms) +{ + DBG(""); + + mdp5_kms->enable_count++; + + clk_prepare_enable(mdp5_kms->ahb_clk); + clk_prepare_enable(mdp5_kms->axi_clk); + clk_prepare_enable(mdp5_kms->core_clk); + if (mdp5_kms->lut_clk) + clk_prepare_enable(mdp5_kms->lut_clk); + if (mdp5_kms->tbu_clk) + clk_prepare_enable(mdp5_kms->tbu_clk); + if (mdp5_kms->tbu_rt_clk) + clk_prepare_enable(mdp5_kms->tbu_rt_clk); + + return 0; +} + +static struct drm_encoder *construct_encoder(struct mdp5_kms *mdp5_kms, + struct mdp5_interface *intf, + struct mdp5_ctl *ctl) +{ + struct drm_device *dev = mdp5_kms->dev; + struct msm_drm_private *priv = dev->dev_private; + struct drm_encoder *encoder; + + encoder = mdp5_encoder_init(dev, intf, ctl); + if (IS_ERR(encoder)) { + DRM_DEV_ERROR(dev->dev, "failed to construct encoder\n"); + return encoder; + } + + priv->encoders[priv->num_encoders++] = encoder; + + return encoder; +} + +static int get_dsi_id_from_intf(const struct mdp5_cfg_hw *hw_cfg, int intf_num) +{ + const enum mdp5_intf_type *intfs = hw_cfg->intf.connect; + const int intf_cnt = ARRAY_SIZE(hw_cfg->intf.connect); + int id = 0, i; + + for (i = 0; i < intf_cnt; i++) { + if (intfs[i] == INTF_DSI) { + if (intf_num == i) + return id; + + id++; + } + } + + return -EINVAL; +} + +static int modeset_init_intf(struct mdp5_kms *mdp5_kms, + struct mdp5_interface *intf) +{ + struct drm_device *dev = mdp5_kms->dev; + struct msm_drm_private *priv = dev->dev_private; + struct mdp5_ctl_manager *ctlm = mdp5_kms->ctlm; + struct mdp5_ctl *ctl; + struct drm_encoder *encoder; + int ret = 0; + + switch (intf->type) { + case INTF_eDP: + if (!priv->edp) + break; + + ctl = mdp5_ctlm_request(ctlm, intf->num); + if (!ctl) { + ret = -EINVAL; + break; + } + + encoder = construct_encoder(mdp5_kms, intf, ctl); + if (IS_ERR(encoder)) { + ret = PTR_ERR(encoder); + break; + } + + ret = msm_edp_modeset_init(priv->edp, dev, encoder); + break; + case INTF_HDMI: + if (!priv->hdmi) + break; + + ctl = mdp5_ctlm_request(ctlm, intf->num); + if (!ctl) { + ret = -EINVAL; + break; + } + + encoder = construct_encoder(mdp5_kms, intf, ctl); + if (IS_ERR(encoder)) { + ret = PTR_ERR(encoder); + break; + } + + ret = msm_hdmi_modeset_init(priv->hdmi, dev, encoder); + break; + case INTF_DSI: + { + const struct mdp5_cfg_hw *hw_cfg = + mdp5_cfg_get_hw_config(mdp5_kms->cfg); + int dsi_id = get_dsi_id_from_intf(hw_cfg, intf->num); + + if ((dsi_id >= ARRAY_SIZE(priv->dsi)) || (dsi_id < 0)) { + DRM_DEV_ERROR(dev->dev, "failed to find dsi from intf %d\n", + intf->num); + ret = -EINVAL; + break; + } + + if (!priv->dsi[dsi_id]) + break; + + ctl = mdp5_ctlm_request(ctlm, intf->num); + if (!ctl) { + ret = -EINVAL; + break; + } + + encoder = construct_encoder(mdp5_kms, intf, ctl); + if (IS_ERR(encoder)) { + ret = PTR_ERR(encoder); + break; + } + + ret = msm_dsi_modeset_init(priv->dsi[dsi_id], dev, encoder); + break; + } + default: + DRM_DEV_ERROR(dev->dev, "unknown intf: %d\n", intf->type); + ret = -EINVAL; + break; + } + + return ret; +} + +static int modeset_init(struct mdp5_kms *mdp5_kms) +{ + struct drm_device *dev = mdp5_kms->dev; + struct msm_drm_private *priv = dev->dev_private; + unsigned int num_crtcs; + int i, ret, pi = 0, ci = 0; + struct drm_plane *primary[MAX_BASES] = { NULL }; + struct drm_plane *cursor[MAX_BASES] = { NULL }; + + /* + * Construct encoders and modeset initialize connector devices + * for each external display interface. + */ + for (i = 0; i < mdp5_kms->num_intfs; i++) { + ret = modeset_init_intf(mdp5_kms, mdp5_kms->intfs[i]); + if (ret) + goto fail; + } + + /* + * We should ideally have less number of encoders (set up by parsing + * the MDP5 interfaces) than the number of layer mixers present in HW, + * but let's be safe here anyway + */ + num_crtcs = min(priv->num_encoders, mdp5_kms->num_hwmixers); + + /* + * Construct planes equaling the number of hw pipes, and CRTCs for the + * N encoders set up by the driver. The first N planes become primary + * planes for the CRTCs, with the remainder as overlay planes: + */ + for (i = 0; i < mdp5_kms->num_hwpipes; i++) { + struct mdp5_hw_pipe *hwpipe = mdp5_kms->hwpipes[i]; + struct drm_plane *plane; + enum drm_plane_type type; + + if (i < num_crtcs) + type = DRM_PLANE_TYPE_PRIMARY; + else if (hwpipe->caps & MDP_PIPE_CAP_CURSOR) + type = DRM_PLANE_TYPE_CURSOR; + else + type = DRM_PLANE_TYPE_OVERLAY; + + plane = mdp5_plane_init(dev, type); + if (IS_ERR(plane)) { + ret = PTR_ERR(plane); + DRM_DEV_ERROR(dev->dev, "failed to construct plane %d (%d)\n", i, ret); + goto fail; + } + priv->planes[priv->num_planes++] = plane; + + if (type == DRM_PLANE_TYPE_PRIMARY) + primary[pi++] = plane; + if (type == DRM_PLANE_TYPE_CURSOR) + cursor[ci++] = plane; + } + + for (i = 0; i < num_crtcs; i++) { + struct drm_crtc *crtc; + + crtc = mdp5_crtc_init(dev, primary[i], cursor[i], i); + if (IS_ERR(crtc)) { + ret = PTR_ERR(crtc); + DRM_DEV_ERROR(dev->dev, "failed to construct crtc %d (%d)\n", i, ret); + goto fail; + } + priv->crtcs[priv->num_crtcs++] = crtc; + } + + /* + * Now that we know the number of crtcs we've created, set the possible + * crtcs for the encoders + */ + for (i = 0; i < priv->num_encoders; i++) { + struct drm_encoder *encoder = priv->encoders[i]; + + encoder->possible_crtcs = (1 << priv->num_crtcs) - 1; + } + + return 0; + +fail: + return ret; +} + +static void read_mdp_hw_revision(struct mdp5_kms *mdp5_kms, + u32 *major, u32 *minor) +{ + struct device *dev = &mdp5_kms->pdev->dev; + u32 version; + + pm_runtime_get_sync(dev); + version = mdp5_read(mdp5_kms, REG_MDP5_HW_VERSION); + pm_runtime_put_sync(dev); + + *major = FIELD(version, MDP5_HW_VERSION_MAJOR); + *minor = FIELD(version, MDP5_HW_VERSION_MINOR); + + DRM_DEV_INFO(dev, "MDP5 version v%d.%d", *major, *minor); +} + +static int get_clk(struct platform_device *pdev, struct clk **clkp, + const char *name, bool mandatory) +{ + struct device *dev = &pdev->dev; + struct clk *clk = msm_clk_get(pdev, name); + if (IS_ERR(clk) && mandatory) { + DRM_DEV_ERROR(dev, "failed to get %s (%ld)\n", name, PTR_ERR(clk)); + return PTR_ERR(clk); + } + if (IS_ERR(clk)) + DBG("skipping %s", name); + else + *clkp = clk; + + return 0; +} + +struct msm_kms *mdp5_kms_init(struct drm_device *dev) +{ + struct msm_drm_private *priv = dev->dev_private; + struct platform_device *pdev; + struct mdp5_kms *mdp5_kms; + struct mdp5_cfg *config; + struct msm_kms *kms; + struct msm_gem_address_space *aspace; + int irq, i, ret; + struct device *iommu_dev; + + /* priv->kms would have been populated by the MDP5 driver */ + kms = priv->kms; + if (!kms) + return NULL; + + mdp5_kms = to_mdp5_kms(to_mdp_kms(kms)); + + mdp_kms_init(&mdp5_kms->base, &kms_funcs); + + pdev = mdp5_kms->pdev; + + irq = irq_of_parse_and_map(pdev->dev.of_node, 0); + if (!irq) { + ret = -EINVAL; + DRM_DEV_ERROR(&pdev->dev, "failed to get irq\n"); + goto fail; + } + + kms->irq = irq; + + config = mdp5_cfg_get_config(mdp5_kms->cfg); + + /* make sure things are off before attaching iommu (bootloader could + * have left things on, in which case we'll start getting faults if + * we don't disable): + */ + pm_runtime_get_sync(&pdev->dev); + for (i = 0; i < MDP5_INTF_NUM_MAX; i++) { + if (mdp5_cfg_intf_is_virtual(config->hw->intf.connect[i]) || + !config->hw->intf.base[i]) + continue; + mdp5_write(mdp5_kms, REG_MDP5_INTF_TIMING_ENGINE_EN(i), 0); + + mdp5_write(mdp5_kms, REG_MDP5_INTF_FRAME_LINE_COUNT_EN(i), 0x3); + } + mdelay(16); + + if (config->platform.iommu) { + struct msm_mmu *mmu; + + iommu_dev = &pdev->dev; + if (!dev_iommu_fwspec_get(iommu_dev)) + iommu_dev = iommu_dev->parent; + + mmu = msm_iommu_new(iommu_dev, config->platform.iommu); + + aspace = msm_gem_address_space_create(mmu, "mdp5", + 0x1000, 0x100000000 - 0x1000); + + if (IS_ERR(aspace)) { + if (!IS_ERR(mmu)) + mmu->funcs->destroy(mmu); + ret = PTR_ERR(aspace); + goto fail; + } + + kms->aspace = aspace; + } else { + DRM_DEV_INFO(&pdev->dev, + "no iommu, fallback to phys contig buffers for scanout\n"); + aspace = NULL; + } + + pm_runtime_put_sync(&pdev->dev); + + ret = modeset_init(mdp5_kms); + if (ret) { + DRM_DEV_ERROR(&pdev->dev, "modeset_init failed: %d\n", ret); + goto fail; + } + + dev->mode_config.min_width = 0; + dev->mode_config.min_height = 0; + dev->mode_config.max_width = 0xffff; + dev->mode_config.max_height = 0xffff; + + dev->max_vblank_count = 0; /* max_vblank_count is set on each CRTC */ + dev->vblank_disable_immediate = true; + + return kms; +fail: + if (kms) + mdp5_kms_destroy(kms); + return ERR_PTR(ret); +} + +static void mdp5_destroy(struct platform_device *pdev) +{ + struct mdp5_kms *mdp5_kms = platform_get_drvdata(pdev); + int i; + + if (mdp5_kms->ctlm) + mdp5_ctlm_destroy(mdp5_kms->ctlm); + if (mdp5_kms->smp) + mdp5_smp_destroy(mdp5_kms->smp); + if (mdp5_kms->cfg) + mdp5_cfg_destroy(mdp5_kms->cfg); + + for (i = 0; i < mdp5_kms->num_intfs; i++) + kfree(mdp5_kms->intfs[i]); + + if (mdp5_kms->rpm_enabled) + pm_runtime_disable(&pdev->dev); + + drm_atomic_private_obj_fini(&mdp5_kms->glob_state); + drm_modeset_lock_fini(&mdp5_kms->glob_state_lock); +} + +static int construct_pipes(struct mdp5_kms *mdp5_kms, int cnt, + const enum mdp5_pipe *pipes, const uint32_t *offsets, + uint32_t caps) +{ + struct drm_device *dev = mdp5_kms->dev; + int i, ret; + + for (i = 0; i < cnt; i++) { + struct mdp5_hw_pipe *hwpipe; + + hwpipe = mdp5_pipe_init(pipes[i], offsets[i], caps); + if (IS_ERR(hwpipe)) { + ret = PTR_ERR(hwpipe); + DRM_DEV_ERROR(dev->dev, "failed to construct pipe for %s (%d)\n", + pipe2name(pipes[i]), ret); + return ret; + } + hwpipe->idx = mdp5_kms->num_hwpipes; + mdp5_kms->hwpipes[mdp5_kms->num_hwpipes++] = hwpipe; + } + + return 0; +} + +static int hwpipe_init(struct mdp5_kms *mdp5_kms) +{ + static const enum mdp5_pipe rgb_planes[] = { + SSPP_RGB0, SSPP_RGB1, SSPP_RGB2, SSPP_RGB3, + }; + static const enum mdp5_pipe vig_planes[] = { + SSPP_VIG0, SSPP_VIG1, SSPP_VIG2, SSPP_VIG3, + }; + static const enum mdp5_pipe dma_planes[] = { + SSPP_DMA0, SSPP_DMA1, + }; + static const enum mdp5_pipe cursor_planes[] = { + SSPP_CURSOR0, SSPP_CURSOR1, + }; + const struct mdp5_cfg_hw *hw_cfg; + int ret; + + hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg); + + /* Construct RGB pipes: */ + ret = construct_pipes(mdp5_kms, hw_cfg->pipe_rgb.count, rgb_planes, + hw_cfg->pipe_rgb.base, hw_cfg->pipe_rgb.caps); + if (ret) + return ret; + + /* Construct video (VIG) pipes: */ + ret = construct_pipes(mdp5_kms, hw_cfg->pipe_vig.count, vig_planes, + hw_cfg->pipe_vig.base, hw_cfg->pipe_vig.caps); + if (ret) + return ret; + + /* Construct DMA pipes: */ + ret = construct_pipes(mdp5_kms, hw_cfg->pipe_dma.count, dma_planes, + hw_cfg->pipe_dma.base, hw_cfg->pipe_dma.caps); + if (ret) + return ret; + + /* Construct cursor pipes: */ + ret = construct_pipes(mdp5_kms, hw_cfg->pipe_cursor.count, + cursor_planes, hw_cfg->pipe_cursor.base, + hw_cfg->pipe_cursor.caps); + if (ret) + return ret; + + return 0; +} + +static int hwmixer_init(struct mdp5_kms *mdp5_kms) +{ + struct drm_device *dev = mdp5_kms->dev; + const struct mdp5_cfg_hw *hw_cfg; + int i, ret; + + hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg); + + for (i = 0; i < hw_cfg->lm.count; i++) { + struct mdp5_hw_mixer *mixer; + + mixer = mdp5_mixer_init(&hw_cfg->lm.instances[i]); + if (IS_ERR(mixer)) { + ret = PTR_ERR(mixer); + DRM_DEV_ERROR(dev->dev, "failed to construct LM%d (%d)\n", + i, ret); + return ret; + } + + mixer->idx = mdp5_kms->num_hwmixers; + mdp5_kms->hwmixers[mdp5_kms->num_hwmixers++] = mixer; + } + + return 0; +} + +static int interface_init(struct mdp5_kms *mdp5_kms) +{ + struct drm_device *dev = mdp5_kms->dev; + const struct mdp5_cfg_hw *hw_cfg; + const enum mdp5_intf_type *intf_types; + int i; + + hw_cfg = mdp5_cfg_get_hw_config(mdp5_kms->cfg); + intf_types = hw_cfg->intf.connect; + + for (i = 0; i < ARRAY_SIZE(hw_cfg->intf.connect); i++) { + struct mdp5_interface *intf; + + if (intf_types[i] == INTF_DISABLED) + continue; + + intf = kzalloc(sizeof(*intf), GFP_KERNEL); + if (!intf) { + DRM_DEV_ERROR(dev->dev, "failed to construct INTF%d\n", i); + return -ENOMEM; + } + + intf->num = i; + intf->type = intf_types[i]; + intf->mode = MDP5_INTF_MODE_NONE; + intf->idx = mdp5_kms->num_intfs; + mdp5_kms->intfs[mdp5_kms->num_intfs++] = intf; + } + + return 0; +} + +static int mdp5_init(struct platform_device *pdev, struct drm_device *dev) +{ + struct msm_drm_private *priv = dev->dev_private; + struct mdp5_kms *mdp5_kms; + struct mdp5_cfg *config; + u32 major, minor; + int ret; + + mdp5_kms = devm_kzalloc(&pdev->dev, sizeof(*mdp5_kms), GFP_KERNEL); + if (!mdp5_kms) { + ret = -ENOMEM; + goto fail; + } + + platform_set_drvdata(pdev, mdp5_kms); + + spin_lock_init(&mdp5_kms->resource_lock); + + mdp5_kms->dev = dev; + mdp5_kms->pdev = pdev; + + ret = mdp5_global_obj_init(mdp5_kms); + if (ret) + goto fail; + + mdp5_kms->mmio = msm_ioremap(pdev, "mdp_phys", "MDP5"); + if (IS_ERR(mdp5_kms->mmio)) { + ret = PTR_ERR(mdp5_kms->mmio); + goto fail; + } + + /* mandatory clocks: */ + ret = get_clk(pdev, &mdp5_kms->axi_clk, "bus", true); + if (ret) + goto fail; + ret = get_clk(pdev, &mdp5_kms->ahb_clk, "iface", true); + if (ret) + goto fail; + ret = get_clk(pdev, &mdp5_kms->core_clk, "core", true); + if (ret) + goto fail; + ret = get_clk(pdev, &mdp5_kms->vsync_clk, "vsync", true); + if (ret) + goto fail; + + /* optional clocks: */ + get_clk(pdev, &mdp5_kms->lut_clk, "lut", false); + get_clk(pdev, &mdp5_kms->tbu_clk, "tbu", false); + get_clk(pdev, &mdp5_kms->tbu_rt_clk, "tbu_rt", false); + + /* we need to set a default rate before enabling. Set a safe + * rate first, then figure out hw revision, and then set a + * more optimal rate: + */ + clk_set_rate(mdp5_kms->core_clk, 200000000); + + pm_runtime_enable(&pdev->dev); + mdp5_kms->rpm_enabled = true; + + read_mdp_hw_revision(mdp5_kms, &major, &minor); + + mdp5_kms->cfg = mdp5_cfg_init(mdp5_kms, major, minor); + if (IS_ERR(mdp5_kms->cfg)) { + ret = PTR_ERR(mdp5_kms->cfg); + mdp5_kms->cfg = NULL; + goto fail; + } + + config = mdp5_cfg_get_config(mdp5_kms->cfg); + mdp5_kms->caps = config->hw->mdp.caps; + + /* TODO: compute core clock rate at runtime */ + clk_set_rate(mdp5_kms->core_clk, config->hw->max_clk); + + /* + * Some chipsets have a Shared Memory Pool (SMP), while others + * have dedicated latency buffering per source pipe instead; + * this section initializes the SMP: + */ + if (mdp5_kms->caps & MDP_CAP_SMP) { + mdp5_kms->smp = mdp5_smp_init(mdp5_kms, &config->hw->smp); + if (IS_ERR(mdp5_kms->smp)) { + ret = PTR_ERR(mdp5_kms->smp); + mdp5_kms->smp = NULL; + goto fail; + } + } + + mdp5_kms->ctlm = mdp5_ctlm_init(dev, mdp5_kms->mmio, mdp5_kms->cfg); + if (IS_ERR(mdp5_kms->ctlm)) { + ret = PTR_ERR(mdp5_kms->ctlm); + mdp5_kms->ctlm = NULL; + goto fail; + } + + ret = hwpipe_init(mdp5_kms); + if (ret) + goto fail; + + ret = hwmixer_init(mdp5_kms); + if (ret) + goto fail; + + ret = interface_init(mdp5_kms); + if (ret) + goto fail; + + /* set uninit-ed kms */ + priv->kms = &mdp5_kms->base.base; + + return 0; +fail: + if (mdp5_kms) + mdp5_destroy(pdev); + return ret; +} + +static int mdp5_bind(struct device *dev, struct device *master, void *data) +{ + struct drm_device *ddev = dev_get_drvdata(master); + struct platform_device *pdev = to_platform_device(dev); + + DBG(""); + + return mdp5_init(pdev, ddev); +} + +static void mdp5_unbind(struct device *dev, struct device *master, + void *data) +{ + struct platform_device *pdev = to_platform_device(dev); + + mdp5_destroy(pdev); +} + +static const struct component_ops mdp5_ops = { + .bind = mdp5_bind, + .unbind = mdp5_unbind, +}; + +static int mdp5_setup_interconnect(struct platform_device *pdev) +{ + struct icc_path *path0 = of_icc_get(&pdev->dev, "mdp0-mem"); + struct icc_path *path1 = of_icc_get(&pdev->dev, "mdp1-mem"); + struct icc_path *path_rot = of_icc_get(&pdev->dev, "rotator-mem"); + + if (IS_ERR(path0)) + return PTR_ERR(path0); + + if (!path0) { + /* no interconnect support is not necessarily a fatal + * condition, the platform may simply not have an + * interconnect driver yet. But warn about it in case + * bootloader didn't setup bus clocks high enough for + * scanout. + */ + dev_warn(&pdev->dev, "No interconnect support may cause display underflows!\n"); + return 0; + } + + icc_set_bw(path0, 0, MBps_to_icc(6400)); + + if (!IS_ERR_OR_NULL(path1)) + icc_set_bw(path1, 0, MBps_to_icc(6400)); + if (!IS_ERR_OR_NULL(path_rot)) + icc_set_bw(path_rot, 0, MBps_to_icc(6400)); + + return 0; +} + +static int mdp5_dev_probe(struct platform_device *pdev) +{ + int ret; + + DBG(""); + + ret = mdp5_setup_interconnect(pdev); + if (ret) + return ret; + + return component_add(&pdev->dev, &mdp5_ops); +} + +static int mdp5_dev_remove(struct platform_device *pdev) +{ + DBG(""); + component_del(&pdev->dev, &mdp5_ops); + return 0; +} + +static __maybe_unused int mdp5_runtime_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mdp5_kms *mdp5_kms = platform_get_drvdata(pdev); + + DBG(""); + + return mdp5_disable(mdp5_kms); +} + +static __maybe_unused int mdp5_runtime_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct mdp5_kms *mdp5_kms = platform_get_drvdata(pdev); + + DBG(""); + + return mdp5_enable(mdp5_kms); +} + +static const struct dev_pm_ops mdp5_pm_ops = { + SET_RUNTIME_PM_OPS(mdp5_runtime_suspend, mdp5_runtime_resume, NULL) +}; + +static const struct of_device_id mdp5_dt_match[] = { + { .compatible = "qcom,mdp5", }, + /* to support downstream DT files */ + { .compatible = "qcom,mdss_mdp", }, + {} +}; +MODULE_DEVICE_TABLE(of, mdp5_dt_match); + +static struct platform_driver mdp5_driver = { + .probe = mdp5_dev_probe, + .remove = mdp5_dev_remove, + .driver = { + .name = "msm_mdp", + .of_match_table = mdp5_dt_match, + .pm = &mdp5_pm_ops, + }, +}; + +void __init msm_mdp_register(void) +{ + DBG(""); + platform_driver_register(&mdp5_driver); +} + +void __exit msm_mdp_unregister(void) +{ + DBG(""); + platform_driver_unregister(&mdp5_driver); +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h new file mode 100644 index 000000000..128866742 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_kms.h @@ -0,0 +1,327 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#ifndef __MDP5_KMS_H__ +#define __MDP5_KMS_H__ + +#include "msm_drv.h" +#include "msm_kms.h" +#include "disp/mdp_kms.h" +#include "mdp5_cfg.h" /* must be included before mdp5.xml.h */ +#include "mdp5.xml.h" +#include "mdp5_pipe.h" +#include "mdp5_mixer.h" +#include "mdp5_ctl.h" +#include "mdp5_smp.h" + +struct mdp5_kms { + struct mdp_kms base; + + struct drm_device *dev; + + struct platform_device *pdev; + + unsigned num_hwpipes; + struct mdp5_hw_pipe *hwpipes[SSPP_MAX]; + + unsigned num_hwmixers; + struct mdp5_hw_mixer *hwmixers[8]; + + unsigned num_intfs; + struct mdp5_interface *intfs[5]; + + struct mdp5_cfg_handler *cfg; + uint32_t caps; /* MDP capabilities (MDP_CAP_XXX bits) */ + + /* + * Global private object state, Do not access directly, use + * mdp5_global_get_state() + */ + struct drm_modeset_lock glob_state_lock; + struct drm_private_obj glob_state; + + struct mdp5_smp *smp; + struct mdp5_ctl_manager *ctlm; + + /* io/register spaces: */ + void __iomem *mmio; + + struct clk *axi_clk; + struct clk *ahb_clk; + struct clk *core_clk; + struct clk *lut_clk; + struct clk *tbu_clk; + struct clk *tbu_rt_clk; + struct clk *vsync_clk; + + /* + * lock to protect access to global resources: ie., following register: + * - REG_MDP5_DISP_INTF_SEL + */ + spinlock_t resource_lock; + + bool rpm_enabled; + + struct mdp_irq error_handler; + + int enable_count; +}; +#define to_mdp5_kms(x) container_of(x, struct mdp5_kms, base) + +/* Global private object state for tracking resources that are shared across + * multiple kms objects (planes/crtcs/etc). + */ +#define to_mdp5_global_state(x) container_of(x, struct mdp5_global_state, base) +struct mdp5_global_state { + struct drm_private_state base; + + struct drm_atomic_state *state; + struct mdp5_kms *mdp5_kms; + + struct mdp5_hw_pipe_state hwpipe; + struct mdp5_hw_mixer_state hwmixer; + struct mdp5_smp_state smp; +}; + +struct mdp5_global_state * mdp5_get_existing_global_state(struct mdp5_kms *mdp5_kms); +struct mdp5_global_state *__must_check mdp5_get_global_state(struct drm_atomic_state *s); + +/* Atomic plane state. Subclasses the base drm_plane_state in order to + * track assigned hwpipe and hw specific state. + */ +struct mdp5_plane_state { + struct drm_plane_state base; + + struct mdp5_hw_pipe *hwpipe; + struct mdp5_hw_pipe *r_hwpipe; /* right hwpipe */ + + /* aligned with property */ + uint8_t premultiplied; + uint8_t zpos; + uint8_t alpha; + + /* assigned by crtc blender */ + enum mdp_mixer_stage_id stage; +}; +#define to_mdp5_plane_state(x) \ + container_of(x, struct mdp5_plane_state, base) + +struct mdp5_pipeline { + struct mdp5_interface *intf; + struct mdp5_hw_mixer *mixer; + struct mdp5_hw_mixer *r_mixer; /* right mixer */ +}; + +struct mdp5_crtc_state { + struct drm_crtc_state base; + + struct mdp5_ctl *ctl; + struct mdp5_pipeline pipeline; + + /* these are derivatives of intf/mixer state in mdp5_pipeline */ + u32 vblank_irqmask; + u32 err_irqmask; + u32 pp_done_irqmask; + + bool cmd_mode; + + /* should we not write CTL[n].START register on flush? If the + * encoder has changed this is set to true, since encoder->enable() + * is called after crtc state is committed, but we only want to + * write the CTL[n].START register once. This lets us defer + * writing CTL[n].START until encoder->enable() + */ + bool defer_start; +}; +#define to_mdp5_crtc_state(x) \ + container_of(x, struct mdp5_crtc_state, base) + +enum mdp5_intf_mode { + MDP5_INTF_MODE_NONE = 0, + + /* Modes used for DSI interface (INTF_DSI type): */ + MDP5_INTF_DSI_MODE_VIDEO, + MDP5_INTF_DSI_MODE_COMMAND, + + /* Modes used for WB interface (INTF_WB type): */ + MDP5_INTF_WB_MODE_BLOCK, + MDP5_INTF_WB_MODE_LINE, +}; + +struct mdp5_interface { + int idx; + int num; /* display interface number */ + enum mdp5_intf_type type; + enum mdp5_intf_mode mode; +}; + +struct mdp5_encoder { + struct drm_encoder base; + spinlock_t intf_lock; /* protect REG_MDP5_INTF_* registers */ + bool enabled; + uint32_t bsc; + + struct mdp5_interface *intf; + struct mdp5_ctl *ctl; +}; +#define to_mdp5_encoder(x) container_of(x, struct mdp5_encoder, base) + +static inline void mdp5_write(struct mdp5_kms *mdp5_kms, u32 reg, u32 data) +{ + WARN_ON(mdp5_kms->enable_count <= 0); + msm_writel(data, mdp5_kms->mmio + reg); +} + +static inline u32 mdp5_read(struct mdp5_kms *mdp5_kms, u32 reg) +{ + WARN_ON(mdp5_kms->enable_count <= 0); + return msm_readl(mdp5_kms->mmio + reg); +} + +static inline const char *stage2name(enum mdp_mixer_stage_id stage) +{ + static const char *names[] = { +#define NAME(n) [n] = #n + NAME(STAGE_UNUSED), NAME(STAGE_BASE), + NAME(STAGE0), NAME(STAGE1), NAME(STAGE2), + NAME(STAGE3), NAME(STAGE4), NAME(STAGE6), +#undef NAME + }; + return names[stage]; +} + +static inline const char *pipe2name(enum mdp5_pipe pipe) +{ + static const char *names[] = { +#define NAME(n) [SSPP_ ## n] = #n + NAME(VIG0), NAME(VIG1), NAME(VIG2), + NAME(RGB0), NAME(RGB1), NAME(RGB2), + NAME(DMA0), NAME(DMA1), + NAME(VIG3), NAME(RGB3), + NAME(CURSOR0), NAME(CURSOR1), +#undef NAME + }; + return names[pipe]; +} + +static inline int pipe2nclients(enum mdp5_pipe pipe) +{ + switch (pipe) { + case SSPP_RGB0: + case SSPP_RGB1: + case SSPP_RGB2: + case SSPP_RGB3: + return 1; + default: + return 3; + } +} + +static inline uint32_t intf2err(int intf_num) +{ + switch (intf_num) { + case 0: return MDP5_IRQ_INTF0_UNDER_RUN; + case 1: return MDP5_IRQ_INTF1_UNDER_RUN; + case 2: return MDP5_IRQ_INTF2_UNDER_RUN; + case 3: return MDP5_IRQ_INTF3_UNDER_RUN; + default: return 0; + } +} + +static inline uint32_t intf2vblank(struct mdp5_hw_mixer *mixer, + struct mdp5_interface *intf) +{ + /* + * In case of DSI Command Mode, the Ping Pong's read pointer IRQ + * acts as a Vblank signal. The Ping Pong buffer used is bound to + * layer mixer. + */ + + if ((intf->type == INTF_DSI) && + (intf->mode == MDP5_INTF_DSI_MODE_COMMAND)) + return MDP5_IRQ_PING_PONG_0_RD_PTR << mixer->pp; + + if (intf->type == INTF_WB) + return MDP5_IRQ_WB_2_DONE; + + switch (intf->num) { + case 0: return MDP5_IRQ_INTF0_VSYNC; + case 1: return MDP5_IRQ_INTF1_VSYNC; + case 2: return MDP5_IRQ_INTF2_VSYNC; + case 3: return MDP5_IRQ_INTF3_VSYNC; + default: return 0; + } +} + +static inline uint32_t lm2ppdone(struct mdp5_hw_mixer *mixer) +{ + return MDP5_IRQ_PING_PONG_0_DONE << mixer->pp; +} + +void mdp5_set_irqmask(struct mdp_kms *mdp_kms, uint32_t irqmask, + uint32_t old_irqmask); +void mdp5_irq_preinstall(struct msm_kms *kms); +int mdp5_irq_postinstall(struct msm_kms *kms); +void mdp5_irq_uninstall(struct msm_kms *kms); +irqreturn_t mdp5_irq(struct msm_kms *kms); +int mdp5_enable_vblank(struct msm_kms *kms, struct drm_crtc *crtc); +void mdp5_disable_vblank(struct msm_kms *kms, struct drm_crtc *crtc); +int mdp5_irq_domain_init(struct mdp5_kms *mdp5_kms); +void mdp5_irq_domain_fini(struct mdp5_kms *mdp5_kms); + +uint32_t mdp5_plane_get_flush(struct drm_plane *plane); +enum mdp5_pipe mdp5_plane_pipe(struct drm_plane *plane); +enum mdp5_pipe mdp5_plane_right_pipe(struct drm_plane *plane); +struct drm_plane *mdp5_plane_init(struct drm_device *dev, + enum drm_plane_type type); + +struct mdp5_ctl *mdp5_crtc_get_ctl(struct drm_crtc *crtc); +uint32_t mdp5_crtc_vblank(struct drm_crtc *crtc); + +struct mdp5_hw_mixer *mdp5_crtc_get_mixer(struct drm_crtc *crtc); +struct mdp5_pipeline *mdp5_crtc_get_pipeline(struct drm_crtc *crtc); +void mdp5_crtc_set_pipeline(struct drm_crtc *crtc); +void mdp5_crtc_wait_for_commit_done(struct drm_crtc *crtc); +struct drm_crtc *mdp5_crtc_init(struct drm_device *dev, + struct drm_plane *plane, + struct drm_plane *cursor_plane, int id); + +struct drm_encoder *mdp5_encoder_init(struct drm_device *dev, + struct mdp5_interface *intf, struct mdp5_ctl *ctl); +int mdp5_vid_encoder_set_split_display(struct drm_encoder *encoder, + struct drm_encoder *slave_encoder); +void mdp5_encoder_set_intf_mode(struct drm_encoder *encoder, bool cmd_mode); +int mdp5_encoder_get_linecount(struct drm_encoder *encoder); +u32 mdp5_encoder_get_framecount(struct drm_encoder *encoder); + +#ifdef CONFIG_DRM_MSM_DSI +void mdp5_cmd_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); +void mdp5_cmd_encoder_disable(struct drm_encoder *encoder); +void mdp5_cmd_encoder_enable(struct drm_encoder *encoder); +int mdp5_cmd_encoder_set_split_display(struct drm_encoder *encoder, + struct drm_encoder *slave_encoder); +#else +static inline void mdp5_cmd_encoder_mode_set(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ +} +static inline void mdp5_cmd_encoder_disable(struct drm_encoder *encoder) +{ +} +static inline void mdp5_cmd_encoder_enable(struct drm_encoder *encoder) +{ +} +static inline int mdp5_cmd_encoder_set_split_display( + struct drm_encoder *encoder, struct drm_encoder *slave_encoder) +{ + return -EINVAL; +} +#endif + +#endif /* __MDP5_KMS_H__ */ diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_mdss.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_mdss.c new file mode 100644 index 000000000..09bd46ad8 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_mdss.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2016, The Linux Foundation. All rights reserved. + */ + +#include <linux/irqdomain.h> +#include <linux/irq.h> + +#include "msm_drv.h" +#include "mdp5_kms.h" + +#define to_mdp5_mdss(x) container_of(x, struct mdp5_mdss, base) + +struct mdp5_mdss { + struct msm_mdss base; + + void __iomem *mmio, *vbif; + + struct regulator *vdd; + + struct clk *ahb_clk; + struct clk *axi_clk; + struct clk *vsync_clk; + + struct { + volatile unsigned long enabled_mask; + struct irq_domain *domain; + } irqcontroller; +}; + +static inline void mdss_write(struct mdp5_mdss *mdp5_mdss, u32 reg, u32 data) +{ + msm_writel(data, mdp5_mdss->mmio + reg); +} + +static inline u32 mdss_read(struct mdp5_mdss *mdp5_mdss, u32 reg) +{ + return msm_readl(mdp5_mdss->mmio + reg); +} + +static irqreturn_t mdss_irq(int irq, void *arg) +{ + struct mdp5_mdss *mdp5_mdss = arg; + u32 intr; + + intr = mdss_read(mdp5_mdss, REG_MDSS_HW_INTR_STATUS); + + VERB("intr=%08x", intr); + + while (intr) { + irq_hw_number_t hwirq = fls(intr) - 1; + + generic_handle_irq(irq_find_mapping( + mdp5_mdss->irqcontroller.domain, hwirq)); + intr &= ~(1 << hwirq); + } + + return IRQ_HANDLED; +} + +/* + * interrupt-controller implementation, so sub-blocks (MDP/HDMI/eDP/DSI/etc) + * can register to get their irq's delivered + */ + +#define VALID_IRQS (MDSS_HW_INTR_STATUS_INTR_MDP | \ + MDSS_HW_INTR_STATUS_INTR_DSI0 | \ + MDSS_HW_INTR_STATUS_INTR_DSI1 | \ + MDSS_HW_INTR_STATUS_INTR_HDMI | \ + MDSS_HW_INTR_STATUS_INTR_EDP) + +static void mdss_hw_mask_irq(struct irq_data *irqd) +{ + struct mdp5_mdss *mdp5_mdss = irq_data_get_irq_chip_data(irqd); + + smp_mb__before_atomic(); + clear_bit(irqd->hwirq, &mdp5_mdss->irqcontroller.enabled_mask); + smp_mb__after_atomic(); +} + +static void mdss_hw_unmask_irq(struct irq_data *irqd) +{ + struct mdp5_mdss *mdp5_mdss = irq_data_get_irq_chip_data(irqd); + + smp_mb__before_atomic(); + set_bit(irqd->hwirq, &mdp5_mdss->irqcontroller.enabled_mask); + smp_mb__after_atomic(); +} + +static struct irq_chip mdss_hw_irq_chip = { + .name = "mdss", + .irq_mask = mdss_hw_mask_irq, + .irq_unmask = mdss_hw_unmask_irq, +}; + +static int mdss_hw_irqdomain_map(struct irq_domain *d, unsigned int irq, + irq_hw_number_t hwirq) +{ + struct mdp5_mdss *mdp5_mdss = d->host_data; + + if (!(VALID_IRQS & (1 << hwirq))) + return -EPERM; + + irq_set_chip_and_handler(irq, &mdss_hw_irq_chip, handle_level_irq); + irq_set_chip_data(irq, mdp5_mdss); + + return 0; +} + +static const struct irq_domain_ops mdss_hw_irqdomain_ops = { + .map = mdss_hw_irqdomain_map, + .xlate = irq_domain_xlate_onecell, +}; + + +static int mdss_irq_domain_init(struct mdp5_mdss *mdp5_mdss) +{ + struct device *dev = mdp5_mdss->base.dev->dev; + struct irq_domain *d; + + d = irq_domain_add_linear(dev->of_node, 32, &mdss_hw_irqdomain_ops, + mdp5_mdss); + if (!d) { + DRM_DEV_ERROR(dev, "mdss irq domain add failed\n"); + return -ENXIO; + } + + mdp5_mdss->irqcontroller.enabled_mask = 0; + mdp5_mdss->irqcontroller.domain = d; + + return 0; +} + +static int mdp5_mdss_enable(struct msm_mdss *mdss) +{ + struct mdp5_mdss *mdp5_mdss = to_mdp5_mdss(mdss); + DBG(""); + + clk_prepare_enable(mdp5_mdss->ahb_clk); + if (mdp5_mdss->axi_clk) + clk_prepare_enable(mdp5_mdss->axi_clk); + if (mdp5_mdss->vsync_clk) + clk_prepare_enable(mdp5_mdss->vsync_clk); + + return 0; +} + +static int mdp5_mdss_disable(struct msm_mdss *mdss) +{ + struct mdp5_mdss *mdp5_mdss = to_mdp5_mdss(mdss); + DBG(""); + + if (mdp5_mdss->vsync_clk) + clk_disable_unprepare(mdp5_mdss->vsync_clk); + if (mdp5_mdss->axi_clk) + clk_disable_unprepare(mdp5_mdss->axi_clk); + clk_disable_unprepare(mdp5_mdss->ahb_clk); + + return 0; +} + +static int msm_mdss_get_clocks(struct mdp5_mdss *mdp5_mdss) +{ + struct platform_device *pdev = + to_platform_device(mdp5_mdss->base.dev->dev); + + mdp5_mdss->ahb_clk = msm_clk_get(pdev, "iface"); + if (IS_ERR(mdp5_mdss->ahb_clk)) + mdp5_mdss->ahb_clk = NULL; + + mdp5_mdss->axi_clk = msm_clk_get(pdev, "bus"); + if (IS_ERR(mdp5_mdss->axi_clk)) + mdp5_mdss->axi_clk = NULL; + + mdp5_mdss->vsync_clk = msm_clk_get(pdev, "vsync"); + if (IS_ERR(mdp5_mdss->vsync_clk)) + mdp5_mdss->vsync_clk = NULL; + + return 0; +} + +static void mdp5_mdss_destroy(struct drm_device *dev) +{ + struct msm_drm_private *priv = dev->dev_private; + struct mdp5_mdss *mdp5_mdss = to_mdp5_mdss(priv->mdss); + + if (!mdp5_mdss) + return; + + irq_domain_remove(mdp5_mdss->irqcontroller.domain); + mdp5_mdss->irqcontroller.domain = NULL; + + regulator_disable(mdp5_mdss->vdd); + + pm_runtime_disable(dev->dev); +} + +static const struct msm_mdss_funcs mdss_funcs = { + .enable = mdp5_mdss_enable, + .disable = mdp5_mdss_disable, + .destroy = mdp5_mdss_destroy, +}; + +int mdp5_mdss_init(struct drm_device *dev) +{ + struct platform_device *pdev = to_platform_device(dev->dev); + struct msm_drm_private *priv = dev->dev_private; + struct mdp5_mdss *mdp5_mdss; + int ret; + + DBG(""); + + if (!of_device_is_compatible(dev->dev->of_node, "qcom,mdss")) + return 0; + + mdp5_mdss = devm_kzalloc(dev->dev, sizeof(*mdp5_mdss), GFP_KERNEL); + if (!mdp5_mdss) { + ret = -ENOMEM; + goto fail; + } + + mdp5_mdss->base.dev = dev; + + mdp5_mdss->mmio = msm_ioremap(pdev, "mdss_phys", "MDSS"); + if (IS_ERR(mdp5_mdss->mmio)) { + ret = PTR_ERR(mdp5_mdss->mmio); + goto fail; + } + + mdp5_mdss->vbif = msm_ioremap(pdev, "vbif_phys", "VBIF"); + if (IS_ERR(mdp5_mdss->vbif)) { + ret = PTR_ERR(mdp5_mdss->vbif); + goto fail; + } + + ret = msm_mdss_get_clocks(mdp5_mdss); + if (ret) { + DRM_DEV_ERROR(dev->dev, "failed to get clocks: %d\n", ret); + goto fail; + } + + /* Regulator to enable GDSCs in downstream kernels */ + mdp5_mdss->vdd = devm_regulator_get(dev->dev, "vdd"); + if (IS_ERR(mdp5_mdss->vdd)) { + ret = PTR_ERR(mdp5_mdss->vdd); + goto fail; + } + + ret = regulator_enable(mdp5_mdss->vdd); + if (ret) { + DRM_DEV_ERROR(dev->dev, "failed to enable regulator vdd: %d\n", + ret); + goto fail; + } + + ret = devm_request_irq(dev->dev, platform_get_irq(pdev, 0), + mdss_irq, 0, "mdss_isr", mdp5_mdss); + if (ret) { + DRM_DEV_ERROR(dev->dev, "failed to init irq: %d\n", ret); + goto fail_irq; + } + + ret = mdss_irq_domain_init(mdp5_mdss); + if (ret) { + DRM_DEV_ERROR(dev->dev, "failed to init sub-block irqs: %d\n", ret); + goto fail_irq; + } + + mdp5_mdss->base.funcs = &mdss_funcs; + priv->mdss = &mdp5_mdss->base; + + pm_runtime_enable(dev->dev); + + return 0; +fail_irq: + regulator_disable(mdp5_mdss->vdd); +fail: + return ret; +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_mixer.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_mixer.c new file mode 100644 index 000000000..2536def2a --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_mixer.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2017 The Linux Foundation. All rights reserved. + */ + +#include "mdp5_kms.h" + +/* + * As of now, there are only 2 combinations possible for source split: + * + * Left | Right + * -----|------ + * LM0 | LM1 + * LM2 | LM5 + * + */ +static int lm_right_pair[] = { 1, -1, 5, -1, -1, -1 }; + +static int get_right_pair_idx(struct mdp5_kms *mdp5_kms, int lm) +{ + int i; + int pair_lm; + + pair_lm = lm_right_pair[lm]; + if (pair_lm < 0) + return -EINVAL; + + for (i = 0; i < mdp5_kms->num_hwmixers; i++) { + struct mdp5_hw_mixer *mixer = mdp5_kms->hwmixers[i]; + + if (mixer->lm == pair_lm) + return mixer->idx; + } + + return -1; +} + +int mdp5_mixer_assign(struct drm_atomic_state *s, struct drm_crtc *crtc, + uint32_t caps, struct mdp5_hw_mixer **mixer, + struct mdp5_hw_mixer **r_mixer) +{ + struct msm_drm_private *priv = s->dev->dev_private; + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms)); + struct mdp5_global_state *global_state = mdp5_get_global_state(s); + struct mdp5_hw_mixer_state *new_state; + int i; + + if (IS_ERR(global_state)) + return PTR_ERR(global_state); + + new_state = &global_state->hwmixer; + + for (i = 0; i < mdp5_kms->num_hwmixers; i++) { + struct mdp5_hw_mixer *cur = mdp5_kms->hwmixers[i]; + + /* + * skip if already in-use by a different CRTC. If there is a + * mixer already assigned to this CRTC, it means this call is + * a request to get an additional right mixer. Assume that the + * existing mixer is the 'left' one, and try to see if we can + * get its corresponding 'right' pair. + */ + if (new_state->hwmixer_to_crtc[cur->idx] && + new_state->hwmixer_to_crtc[cur->idx] != crtc) + continue; + + /* skip if doesn't support some required caps: */ + if (caps & ~cur->caps) + continue; + + if (r_mixer) { + int pair_idx; + + pair_idx = get_right_pair_idx(mdp5_kms, cur->lm); + if (pair_idx < 0) + return -EINVAL; + + if (new_state->hwmixer_to_crtc[pair_idx]) + continue; + + *r_mixer = mdp5_kms->hwmixers[pair_idx]; + } + + /* + * prefer a pair-able LM over an unpairable one. We can + * switch the CRTC from Normal mode to Source Split mode + * without requiring a full modeset if we had already + * assigned this CRTC a pair-able LM. + * + * TODO: There will be assignment sequences which would + * result in the CRTC requiring a full modeset, even + * if we have the LM resources to prevent it. For a platform + * with a few displays, we don't run out of pair-able LMs + * so easily. For now, ignore the possibility of requiring + * a full modeset. + */ + if (!(*mixer) || cur->caps & MDP_LM_CAP_PAIR) + *mixer = cur; + } + + if (!(*mixer)) + return -ENOMEM; + + if (r_mixer && !(*r_mixer)) + return -ENOMEM; + + DBG("assigning Layer Mixer %d to crtc %s", (*mixer)->lm, crtc->name); + + new_state->hwmixer_to_crtc[(*mixer)->idx] = crtc; + if (r_mixer) { + DBG("assigning Right Layer Mixer %d to crtc %s", (*r_mixer)->lm, + crtc->name); + new_state->hwmixer_to_crtc[(*r_mixer)->idx] = crtc; + } + + return 0; +} + +int mdp5_mixer_release(struct drm_atomic_state *s, struct mdp5_hw_mixer *mixer) +{ + struct mdp5_global_state *global_state = mdp5_get_global_state(s); + struct mdp5_hw_mixer_state *new_state; + + if (!mixer) + return 0; + + if (IS_ERR(global_state)) + return PTR_ERR(global_state); + + new_state = &global_state->hwmixer; + + if (WARN_ON(!new_state->hwmixer_to_crtc[mixer->idx])) + return -EINVAL; + + DBG("%s: release from crtc %s", mixer->name, + new_state->hwmixer_to_crtc[mixer->idx]->name); + + new_state->hwmixer_to_crtc[mixer->idx] = NULL; + + return 0; +} + +void mdp5_mixer_destroy(struct mdp5_hw_mixer *mixer) +{ + kfree(mixer); +} + +static const char * const mixer_names[] = { + "LM0", "LM1", "LM2", "LM3", "LM4", "LM5", +}; + +struct mdp5_hw_mixer *mdp5_mixer_init(const struct mdp5_lm_instance *lm) +{ + struct mdp5_hw_mixer *mixer; + + mixer = kzalloc(sizeof(*mixer), GFP_KERNEL); + if (!mixer) + return ERR_PTR(-ENOMEM); + + mixer->name = mixer_names[lm->id]; + mixer->lm = lm->id; + mixer->caps = lm->caps; + mixer->pp = lm->pp; + mixer->dspp = lm->dspp; + mixer->flush_mask = mdp_ctl_flush_mask_lm(lm->id); + + return mixer; +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_mixer.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_mixer.h new file mode 100644 index 000000000..545ee223b --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_mixer.h @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2017 The Linux Foundation. All rights reserved. + */ + +#ifndef __MDP5_LM_H__ +#define __MDP5_LM_H__ + +/* represents a hw Layer Mixer, one (or more) is dynamically assigned to a crtc */ +struct mdp5_hw_mixer { + int idx; + + const char *name; + + int lm; /* the LM instance # */ + uint32_t caps; + int pp; + int dspp; + + uint32_t flush_mask; /* used to commit LM registers */ +}; + +/* global atomic state of assignment between CRTCs and Layer Mixers: */ +struct mdp5_hw_mixer_state { + struct drm_crtc *hwmixer_to_crtc[8]; +}; + +struct mdp5_hw_mixer *mdp5_mixer_init(const struct mdp5_lm_instance *lm); +void mdp5_mixer_destroy(struct mdp5_hw_mixer *lm); +int mdp5_mixer_assign(struct drm_atomic_state *s, struct drm_crtc *crtc, + uint32_t caps, struct mdp5_hw_mixer **mixer, + struct mdp5_hw_mixer **r_mixer); +int mdp5_mixer_release(struct drm_atomic_state *s, + struct mdp5_hw_mixer *mixer); + +#endif /* __MDP5_LM_H__ */ diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_pipe.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_pipe.c new file mode 100644 index 000000000..e4b8a7898 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_pipe.c @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2016 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include "mdp5_kms.h" + +int mdp5_pipe_assign(struct drm_atomic_state *s, struct drm_plane *plane, + uint32_t caps, uint32_t blkcfg, + struct mdp5_hw_pipe **hwpipe, + struct mdp5_hw_pipe **r_hwpipe) +{ + struct msm_drm_private *priv = s->dev->dev_private; + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms)); + struct mdp5_global_state *new_global_state, *old_global_state; + struct mdp5_hw_pipe_state *old_state, *new_state; + int i, j; + + new_global_state = mdp5_get_global_state(s); + if (IS_ERR(new_global_state)) + return PTR_ERR(new_global_state); + + /* grab old_state after mdp5_get_global_state(), since now we hold lock: */ + old_global_state = mdp5_get_existing_global_state(mdp5_kms); + + old_state = &old_global_state->hwpipe; + new_state = &new_global_state->hwpipe; + + for (i = 0; i < mdp5_kms->num_hwpipes; i++) { + struct mdp5_hw_pipe *cur = mdp5_kms->hwpipes[i]; + + /* skip if already in-use.. check both new and old state, + * since we cannot immediately re-use a pipe that is + * released in the current update in some cases: + * (1) mdp5 can have SMP (non-double-buffered) + * (2) hw pipe previously assigned to different CRTC + * (vblanks might not be aligned) + */ + if (new_state->hwpipe_to_plane[cur->idx] || + old_state->hwpipe_to_plane[cur->idx]) + continue; + + /* skip if doesn't support some required caps: */ + if (caps & ~cur->caps) + continue; + + /* + * don't assign a cursor pipe to a plane that isn't going to + * be used as a cursor + */ + if (cur->caps & MDP_PIPE_CAP_CURSOR && + plane->type != DRM_PLANE_TYPE_CURSOR) + continue; + + /* possible candidate, take the one with the + * fewest unneeded caps bits set: + */ + if (!(*hwpipe) || (hweight_long(cur->caps & ~caps) < + hweight_long((*hwpipe)->caps & ~caps))) { + bool r_found = false; + + if (r_hwpipe) { + for (j = i + 1; j < mdp5_kms->num_hwpipes; + j++) { + struct mdp5_hw_pipe *r_cur = + mdp5_kms->hwpipes[j]; + + /* reject different types of hwpipes */ + if (r_cur->caps != cur->caps) + continue; + + /* respect priority, eg. VIG0 > VIG1 */ + if (cur->pipe > r_cur->pipe) + continue; + + *r_hwpipe = r_cur; + r_found = true; + break; + } + } + + if (!r_hwpipe || r_found) + *hwpipe = cur; + } + } + + if (!(*hwpipe)) + return -ENOMEM; + + if (r_hwpipe && !(*r_hwpipe)) + return -ENOMEM; + + if (mdp5_kms->smp) { + int ret; + + /* We don't support SMP and 2 hwpipes/plane together */ + WARN_ON(r_hwpipe); + + DBG("%s: alloc SMP blocks", (*hwpipe)->name); + ret = mdp5_smp_assign(mdp5_kms->smp, &new_global_state->smp, + (*hwpipe)->pipe, blkcfg); + if (ret) + return -ENOMEM; + + (*hwpipe)->blkcfg = blkcfg; + } + + DBG("%s: assign to plane %s for caps %x", + (*hwpipe)->name, plane->name, caps); + new_state->hwpipe_to_plane[(*hwpipe)->idx] = plane; + + if (r_hwpipe) { + DBG("%s: assign to right of plane %s for caps %x", + (*r_hwpipe)->name, plane->name, caps); + new_state->hwpipe_to_plane[(*r_hwpipe)->idx] = plane; + } + + return 0; +} + +int mdp5_pipe_release(struct drm_atomic_state *s, struct mdp5_hw_pipe *hwpipe) +{ + struct msm_drm_private *priv = s->dev->dev_private; + struct mdp5_kms *mdp5_kms = to_mdp5_kms(to_mdp_kms(priv->kms)); + struct mdp5_global_state *state; + struct mdp5_hw_pipe_state *new_state; + + if (!hwpipe) + return 0; + + state = mdp5_get_global_state(s); + if (IS_ERR(state)) + return PTR_ERR(state); + + new_state = &state->hwpipe; + + if (WARN_ON(!new_state->hwpipe_to_plane[hwpipe->idx])) + return -EINVAL; + + DBG("%s: release from plane %s", hwpipe->name, + new_state->hwpipe_to_plane[hwpipe->idx]->name); + + if (mdp5_kms->smp) { + DBG("%s: free SMP blocks", hwpipe->name); + mdp5_smp_release(mdp5_kms->smp, &state->smp, hwpipe->pipe); + } + + new_state->hwpipe_to_plane[hwpipe->idx] = NULL; + + return 0; +} + +void mdp5_pipe_destroy(struct mdp5_hw_pipe *hwpipe) +{ + kfree(hwpipe); +} + +struct mdp5_hw_pipe *mdp5_pipe_init(enum mdp5_pipe pipe, + uint32_t reg_offset, uint32_t caps) +{ + struct mdp5_hw_pipe *hwpipe; + + hwpipe = kzalloc(sizeof(*hwpipe), GFP_KERNEL); + if (!hwpipe) + return ERR_PTR(-ENOMEM); + + hwpipe->name = pipe2name(pipe); + hwpipe->pipe = pipe; + hwpipe->reg_offset = reg_offset; + hwpipe->caps = caps; + hwpipe->flush_mask = mdp_ctl_flush_mask_pipe(pipe); + + return hwpipe; +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_pipe.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_pipe.h new file mode 100644 index 000000000..cca67938c --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_pipe.h @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2016 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#ifndef __MDP5_PIPE_H__ +#define __MDP5_PIPE_H__ + +/* TODO: Add SSPP_MAX in mdp5.xml.h */ +#define SSPP_MAX (SSPP_CURSOR1 + 1) + +/* represents a hw pipe, which is dynamically assigned to a plane */ +struct mdp5_hw_pipe { + int idx; + + const char *name; + enum mdp5_pipe pipe; + + uint32_t reg_offset; + uint32_t caps; + + uint32_t flush_mask; /* used to commit pipe registers */ + + /* number of smp blocks per plane, ie: + * nblks_y | (nblks_u << 8) | (nblks_v << 16) + */ + uint32_t blkcfg; +}; + +/* global atomic state of assignment between pipes and planes: */ +struct mdp5_hw_pipe_state { + struct drm_plane *hwpipe_to_plane[SSPP_MAX]; +}; + +int mdp5_pipe_assign(struct drm_atomic_state *s, struct drm_plane *plane, + uint32_t caps, uint32_t blkcfg, + struct mdp5_hw_pipe **hwpipe, + struct mdp5_hw_pipe **r_hwpipe); +int mdp5_pipe_release(struct drm_atomic_state *s, struct mdp5_hw_pipe *hwpipe); + +struct mdp5_hw_pipe *mdp5_pipe_init(enum mdp5_pipe pipe, + uint32_t reg_offset, uint32_t caps); +void mdp5_pipe_destroy(struct mdp5_hw_pipe *hwpipe); + +#endif /* __MDP5_PIPE_H__ */ diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c new file mode 100644 index 000000000..e1c1b4ad5 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_plane.c @@ -0,0 +1,1117 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2014-2015 The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <drm/drm_damage_helper.h> +#include <drm/drm_fourcc.h> +#include <drm/drm_print.h> + +#include "mdp5_kms.h" + +struct mdp5_plane { + struct drm_plane base; + + uint32_t nformats; + uint32_t formats[32]; +}; +#define to_mdp5_plane(x) container_of(x, struct mdp5_plane, base) + +static int mdp5_plane_mode_set(struct drm_plane *plane, + struct drm_crtc *crtc, struct drm_framebuffer *fb, + struct drm_rect *src, struct drm_rect *dest); + +static struct mdp5_kms *get_kms(struct drm_plane *plane) +{ + struct msm_drm_private *priv = plane->dev->dev_private; + return to_mdp5_kms(to_mdp_kms(priv->kms)); +} + +static bool plane_enabled(struct drm_plane_state *state) +{ + return state->visible; +} + +static void mdp5_plane_destroy(struct drm_plane *plane) +{ + struct mdp5_plane *mdp5_plane = to_mdp5_plane(plane); + + drm_plane_cleanup(plane); + + kfree(mdp5_plane); +} + +static void mdp5_plane_install_rotation_property(struct drm_device *dev, + struct drm_plane *plane) +{ + drm_plane_create_rotation_property(plane, + DRM_MODE_ROTATE_0, + DRM_MODE_ROTATE_0 | + DRM_MODE_ROTATE_180 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y); +} + +/* helper to install properties which are common to planes and crtcs */ +static void mdp5_plane_install_properties(struct drm_plane *plane, + struct drm_mode_object *obj) +{ + struct drm_device *dev = plane->dev; + struct msm_drm_private *dev_priv = dev->dev_private; + struct drm_property *prop; + +#define INSTALL_PROPERTY(name, NAME, init_val, fnc, ...) do { \ + prop = dev_priv->plane_property[PLANE_PROP_##NAME]; \ + if (!prop) { \ + prop = drm_property_##fnc(dev, 0, #name, \ + ##__VA_ARGS__); \ + if (!prop) { \ + dev_warn(dev->dev, \ + "Create property %s failed\n", \ + #name); \ + return; \ + } \ + dev_priv->plane_property[PLANE_PROP_##NAME] = prop; \ + } \ + drm_object_attach_property(&plane->base, prop, init_val); \ + } while (0) + +#define INSTALL_RANGE_PROPERTY(name, NAME, min, max, init_val) \ + INSTALL_PROPERTY(name, NAME, init_val, \ + create_range, min, max) + +#define INSTALL_ENUM_PROPERTY(name, NAME, init_val) \ + INSTALL_PROPERTY(name, NAME, init_val, \ + create_enum, name##_prop_enum_list, \ + ARRAY_SIZE(name##_prop_enum_list)) + + INSTALL_RANGE_PROPERTY(zpos, ZPOS, 1, 255, 1); + + mdp5_plane_install_rotation_property(dev, plane); + +#undef INSTALL_RANGE_PROPERTY +#undef INSTALL_ENUM_PROPERTY +#undef INSTALL_PROPERTY +} + +static int mdp5_plane_atomic_set_property(struct drm_plane *plane, + struct drm_plane_state *state, struct drm_property *property, + uint64_t val) +{ + struct drm_device *dev = plane->dev; + struct mdp5_plane_state *pstate; + struct msm_drm_private *dev_priv = dev->dev_private; + int ret = 0; + + pstate = to_mdp5_plane_state(state); + +#define SET_PROPERTY(name, NAME, type) do { \ + if (dev_priv->plane_property[PLANE_PROP_##NAME] == property) { \ + pstate->name = (type)val; \ + DBG("Set property %s %d", #name, (type)val); \ + goto done; \ + } \ + } while (0) + + SET_PROPERTY(zpos, ZPOS, uint8_t); + + DRM_DEV_ERROR(dev->dev, "Invalid property\n"); + ret = -EINVAL; +done: + return ret; +#undef SET_PROPERTY +} + +static int mdp5_plane_atomic_get_property(struct drm_plane *plane, + const struct drm_plane_state *state, + struct drm_property *property, uint64_t *val) +{ + struct drm_device *dev = plane->dev; + struct mdp5_plane_state *pstate; + struct msm_drm_private *dev_priv = dev->dev_private; + int ret = 0; + + pstate = to_mdp5_plane_state(state); + +#define GET_PROPERTY(name, NAME, type) do { \ + if (dev_priv->plane_property[PLANE_PROP_##NAME] == property) { \ + *val = pstate->name; \ + DBG("Get property %s %lld", #name, *val); \ + goto done; \ + } \ + } while (0) + + GET_PROPERTY(zpos, ZPOS, uint8_t); + + DRM_DEV_ERROR(dev->dev, "Invalid property\n"); + ret = -EINVAL; +done: + return ret; +#undef SET_PROPERTY +} + +static void +mdp5_plane_atomic_print_state(struct drm_printer *p, + const struct drm_plane_state *state) +{ + struct mdp5_plane_state *pstate = to_mdp5_plane_state(state); + struct mdp5_kms *mdp5_kms = get_kms(state->plane); + + drm_printf(p, "\thwpipe=%s\n", pstate->hwpipe ? + pstate->hwpipe->name : "(null)"); + if (mdp5_kms->caps & MDP_CAP_SRC_SPLIT) + drm_printf(p, "\tright-hwpipe=%s\n", + pstate->r_hwpipe ? pstate->r_hwpipe->name : + "(null)"); + drm_printf(p, "\tpremultiplied=%u\n", pstate->premultiplied); + drm_printf(p, "\tzpos=%u\n", pstate->zpos); + drm_printf(p, "\talpha=%u\n", pstate->alpha); + drm_printf(p, "\tstage=%s\n", stage2name(pstate->stage)); +} + +static void mdp5_plane_reset(struct drm_plane *plane) +{ + struct mdp5_plane_state *mdp5_state; + + if (plane->state && plane->state->fb) + drm_framebuffer_put(plane->state->fb); + + kfree(to_mdp5_plane_state(plane->state)); + plane->state = NULL; + mdp5_state = kzalloc(sizeof(*mdp5_state), GFP_KERNEL); + if (!mdp5_state) + return; + + /* assign default blend parameters */ + mdp5_state->alpha = 255; + mdp5_state->premultiplied = 0; + + if (plane->type == DRM_PLANE_TYPE_PRIMARY) + mdp5_state->zpos = STAGE_BASE; + else + mdp5_state->zpos = STAGE0 + drm_plane_index(plane); + + mdp5_state->base.plane = plane; + + plane->state = &mdp5_state->base; +} + +static struct drm_plane_state * +mdp5_plane_duplicate_state(struct drm_plane *plane) +{ + struct mdp5_plane_state *mdp5_state; + + if (WARN_ON(!plane->state)) + return NULL; + + mdp5_state = kmemdup(to_mdp5_plane_state(plane->state), + sizeof(*mdp5_state), GFP_KERNEL); + if (!mdp5_state) + return NULL; + + __drm_atomic_helper_plane_duplicate_state(plane, &mdp5_state->base); + + return &mdp5_state->base; +} + +static void mdp5_plane_destroy_state(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct mdp5_plane_state *pstate = to_mdp5_plane_state(state); + + __drm_atomic_helper_plane_destroy_state(state); + + kfree(pstate); +} + +static const struct drm_plane_funcs mdp5_plane_funcs = { + .update_plane = drm_atomic_helper_update_plane, + .disable_plane = drm_atomic_helper_disable_plane, + .destroy = mdp5_plane_destroy, + .atomic_set_property = mdp5_plane_atomic_set_property, + .atomic_get_property = mdp5_plane_atomic_get_property, + .reset = mdp5_plane_reset, + .atomic_duplicate_state = mdp5_plane_duplicate_state, + .atomic_destroy_state = mdp5_plane_destroy_state, + .atomic_print_state = mdp5_plane_atomic_print_state, +}; + +static void mdp5_plane_cleanup_fb(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct mdp5_kms *mdp5_kms = get_kms(plane); + struct msm_kms *kms = &mdp5_kms->base.base; + struct drm_framebuffer *fb = old_state->fb; + + if (!fb) + return; + + DBG("%s: cleanup: FB[%u]", plane->name, fb->base.id); + msm_framebuffer_cleanup(fb, kms->aspace); +} + +static int mdp5_plane_atomic_check_with_state(struct drm_crtc_state *crtc_state, + struct drm_plane_state *state) +{ + struct mdp5_plane_state *mdp5_state = to_mdp5_plane_state(state); + struct drm_plane *plane = state->plane; + struct drm_plane_state *old_state = plane->state; + struct mdp5_cfg *config = mdp5_cfg_get_config(get_kms(plane)->cfg); + bool new_hwpipe = false; + bool need_right_hwpipe = false; + uint32_t max_width, max_height; + bool out_of_bounds = false; + uint32_t caps = 0; + int min_scale, max_scale; + int ret; + + DBG("%s: check (%d -> %d)", plane->name, + plane_enabled(old_state), plane_enabled(state)); + + max_width = config->hw->lm.max_width << 16; + max_height = config->hw->lm.max_height << 16; + + /* Make sure source dimensions are within bounds. */ + if (state->src_h > max_height) + out_of_bounds = true; + + if (state->src_w > max_width) { + /* If source split is supported, we can go up to 2x + * the max LM width, but we'd need to stage another + * hwpipe to the right LM. So, the drm_plane would + * consist of 2 hwpipes. + */ + if (config->hw->mdp.caps & MDP_CAP_SRC_SPLIT && + (state->src_w <= 2 * max_width)) + need_right_hwpipe = true; + else + out_of_bounds = true; + } + + if (out_of_bounds) { + struct drm_rect src = drm_plane_state_src(state); + DBG("Invalid source size "DRM_RECT_FP_FMT, + DRM_RECT_FP_ARG(&src)); + return -ERANGE; + } + + min_scale = FRAC_16_16(1, 8); + max_scale = FRAC_16_16(8, 1); + + ret = drm_atomic_helper_check_plane_state(state, crtc_state, + min_scale, max_scale, + true, true); + if (ret) + return ret; + + if (plane_enabled(state)) { + unsigned int rotation; + const struct mdp_format *format; + struct mdp5_kms *mdp5_kms = get_kms(plane); + uint32_t blkcfg = 0; + + format = to_mdp_format(msm_framebuffer_format(state->fb)); + if (MDP_FORMAT_IS_YUV(format)) + caps |= MDP_PIPE_CAP_SCALE | MDP_PIPE_CAP_CSC; + + if (((state->src_w >> 16) != state->crtc_w) || + ((state->src_h >> 16) != state->crtc_h)) + caps |= MDP_PIPE_CAP_SCALE; + + rotation = drm_rotation_simplify(state->rotation, + DRM_MODE_ROTATE_0 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y); + + if (rotation & DRM_MODE_REFLECT_X) + caps |= MDP_PIPE_CAP_HFLIP; + + if (rotation & DRM_MODE_REFLECT_Y) + caps |= MDP_PIPE_CAP_VFLIP; + + if (plane->type == DRM_PLANE_TYPE_CURSOR) + caps |= MDP_PIPE_CAP_CURSOR; + + /* (re)allocate hw pipe if we don't have one or caps-mismatch: */ + if (!mdp5_state->hwpipe || (caps & ~mdp5_state->hwpipe->caps)) + new_hwpipe = true; + + /* + * (re)allocte hw pipe if we're either requesting for 2 hw pipes + * or we're switching from 2 hw pipes to 1 hw pipe because the + * new src_w can be supported by 1 hw pipe itself. + */ + if ((need_right_hwpipe && !mdp5_state->r_hwpipe) || + (!need_right_hwpipe && mdp5_state->r_hwpipe)) + new_hwpipe = true; + + if (mdp5_kms->smp) { + const struct mdp_format *format = + to_mdp_format(msm_framebuffer_format(state->fb)); + + blkcfg = mdp5_smp_calculate(mdp5_kms->smp, format, + state->src_w >> 16, false); + + if (mdp5_state->hwpipe && (mdp5_state->hwpipe->blkcfg != blkcfg)) + new_hwpipe = true; + } + + /* (re)assign hwpipe if needed, otherwise keep old one: */ + if (new_hwpipe) { + /* TODO maybe we want to re-assign hwpipe sometimes + * in cases when we no-longer need some caps to make + * it available for other planes? + */ + struct mdp5_hw_pipe *old_hwpipe = mdp5_state->hwpipe; + struct mdp5_hw_pipe *old_right_hwpipe = + mdp5_state->r_hwpipe; + struct mdp5_hw_pipe *new_hwpipe = NULL; + struct mdp5_hw_pipe *new_right_hwpipe = NULL; + + ret = mdp5_pipe_assign(state->state, plane, caps, + blkcfg, &new_hwpipe, + need_right_hwpipe ? + &new_right_hwpipe : NULL); + if (ret) { + DBG("%s: failed to assign hwpipe(s)!", + plane->name); + return ret; + } + + mdp5_state->hwpipe = new_hwpipe; + if (need_right_hwpipe) + mdp5_state->r_hwpipe = new_right_hwpipe; + else + /* + * set it to NULL so that the driver knows we + * don't have a right hwpipe when committing a + * new state + */ + mdp5_state->r_hwpipe = NULL; + + + ret = mdp5_pipe_release(state->state, old_hwpipe); + if (ret) + return ret; + + ret = mdp5_pipe_release(state->state, old_right_hwpipe); + if (ret) + return ret; + + } + } else { + ret = mdp5_pipe_release(state->state, mdp5_state->hwpipe); + if (ret) + return ret; + + ret = mdp5_pipe_release(state->state, mdp5_state->r_hwpipe); + if (ret) + return ret; + + mdp5_state->hwpipe = mdp5_state->r_hwpipe = NULL; + } + + return 0; +} + +static int mdp5_plane_atomic_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct drm_crtc *crtc; + struct drm_crtc_state *crtc_state; + + crtc = state->crtc ? state->crtc : plane->state->crtc; + if (!crtc) + return 0; + + crtc_state = drm_atomic_get_existing_crtc_state(state->state, crtc); + if (WARN_ON(!crtc_state)) + return -EINVAL; + + return mdp5_plane_atomic_check_with_state(crtc_state, state); +} + +static void mdp5_plane_atomic_update(struct drm_plane *plane, + struct drm_plane_state *old_state) +{ + struct drm_plane_state *state = plane->state; + + DBG("%s: update", plane->name); + + if (plane_enabled(state)) { + int ret; + + ret = mdp5_plane_mode_set(plane, + state->crtc, state->fb, + &state->src, &state->dst); + /* atomic_check should have ensured that this doesn't fail */ + WARN_ON(ret < 0); + } +} + +static int mdp5_plane_atomic_async_check(struct drm_plane *plane, + struct drm_plane_state *state) +{ + struct mdp5_plane_state *mdp5_state = to_mdp5_plane_state(state); + struct drm_crtc_state *crtc_state; + int min_scale, max_scale; + int ret; + + crtc_state = drm_atomic_get_existing_crtc_state(state->state, + state->crtc); + if (WARN_ON(!crtc_state)) + return -EINVAL; + + if (!crtc_state->active) + return -EINVAL; + + mdp5_state = to_mdp5_plane_state(state); + + /* don't use fast path if we don't have a hwpipe allocated yet */ + if (!mdp5_state->hwpipe) + return -EINVAL; + + /* only allow changing of position(crtc x/y or src x/y) in fast path */ + if (plane->state->crtc != state->crtc || + plane->state->src_w != state->src_w || + plane->state->src_h != state->src_h || + plane->state->crtc_w != state->crtc_w || + plane->state->crtc_h != state->crtc_h || + !plane->state->fb || + plane->state->fb != state->fb) + return -EINVAL; + + min_scale = FRAC_16_16(1, 8); + max_scale = FRAC_16_16(8, 1); + + ret = drm_atomic_helper_check_plane_state(state, crtc_state, + min_scale, max_scale, + true, true); + if (ret) + return ret; + + /* + * if the visibility of the plane changes (i.e, if the cursor is + * clipped out completely, we can't take the async path because + * we need to stage/unstage the plane from the Layer Mixer(s). We + * also assign/unassign the hwpipe(s) tied to the plane. We avoid + * taking the fast path for both these reasons. + */ + if (state->visible != plane->state->visible) + return -EINVAL; + + return 0; +} + +static void mdp5_plane_atomic_async_update(struct drm_plane *plane, + struct drm_plane_state *new_state) +{ + struct drm_framebuffer *old_fb = plane->state->fb; + + plane->state->src_x = new_state->src_x; + plane->state->src_y = new_state->src_y; + plane->state->crtc_x = new_state->crtc_x; + plane->state->crtc_y = new_state->crtc_y; + + if (plane_enabled(new_state)) { + struct mdp5_ctl *ctl; + struct mdp5_pipeline *pipeline = + mdp5_crtc_get_pipeline(new_state->crtc); + int ret; + + ret = mdp5_plane_mode_set(plane, new_state->crtc, new_state->fb, + &new_state->src, &new_state->dst); + WARN_ON(ret < 0); + + ctl = mdp5_crtc_get_ctl(new_state->crtc); + + mdp5_ctl_commit(ctl, pipeline, mdp5_plane_get_flush(plane), true); + } + + *to_mdp5_plane_state(plane->state) = + *to_mdp5_plane_state(new_state); + + new_state->fb = old_fb; +} + +static const struct drm_plane_helper_funcs mdp5_plane_helper_funcs = { + .prepare_fb = msm_atomic_prepare_fb, + .cleanup_fb = mdp5_plane_cleanup_fb, + .atomic_check = mdp5_plane_atomic_check, + .atomic_update = mdp5_plane_atomic_update, + .atomic_async_check = mdp5_plane_atomic_async_check, + .atomic_async_update = mdp5_plane_atomic_async_update, +}; + +static void set_scanout_locked(struct mdp5_kms *mdp5_kms, + enum mdp5_pipe pipe, + struct drm_framebuffer *fb) +{ + struct msm_kms *kms = &mdp5_kms->base.base; + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_STRIDE_A(pipe), + MDP5_PIPE_SRC_STRIDE_A_P0(fb->pitches[0]) | + MDP5_PIPE_SRC_STRIDE_A_P1(fb->pitches[1])); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_STRIDE_B(pipe), + MDP5_PIPE_SRC_STRIDE_B_P2(fb->pitches[2]) | + MDP5_PIPE_SRC_STRIDE_B_P3(fb->pitches[3])); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC0_ADDR(pipe), + msm_framebuffer_iova(fb, kms->aspace, 0)); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC1_ADDR(pipe), + msm_framebuffer_iova(fb, kms->aspace, 1)); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC2_ADDR(pipe), + msm_framebuffer_iova(fb, kms->aspace, 2)); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC3_ADDR(pipe), + msm_framebuffer_iova(fb, kms->aspace, 3)); +} + +/* Note: mdp5_plane->pipe_lock must be locked */ +static void csc_disable(struct mdp5_kms *mdp5_kms, enum mdp5_pipe pipe) +{ + uint32_t value = mdp5_read(mdp5_kms, REG_MDP5_PIPE_OP_MODE(pipe)) & + ~MDP5_PIPE_OP_MODE_CSC_1_EN; + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_OP_MODE(pipe), value); +} + +/* Note: mdp5_plane->pipe_lock must be locked */ +static void csc_enable(struct mdp5_kms *mdp5_kms, enum mdp5_pipe pipe, + struct csc_cfg *csc) +{ + uint32_t i, mode = 0; /* RGB, no CSC */ + uint32_t *matrix; + + if (unlikely(!csc)) + return; + + if ((csc->type == CSC_YUV2RGB) || (CSC_YUV2YUV == csc->type)) + mode |= MDP5_PIPE_OP_MODE_CSC_SRC_DATA_FORMAT(DATA_FORMAT_YUV); + if ((csc->type == CSC_RGB2YUV) || (CSC_YUV2YUV == csc->type)) + mode |= MDP5_PIPE_OP_MODE_CSC_DST_DATA_FORMAT(DATA_FORMAT_YUV); + mode |= MDP5_PIPE_OP_MODE_CSC_1_EN; + mdp5_write(mdp5_kms, REG_MDP5_PIPE_OP_MODE(pipe), mode); + + matrix = csc->matrix; + mdp5_write(mdp5_kms, REG_MDP5_PIPE_CSC_1_MATRIX_COEFF_0(pipe), + MDP5_PIPE_CSC_1_MATRIX_COEFF_0_COEFF_11(matrix[0]) | + MDP5_PIPE_CSC_1_MATRIX_COEFF_0_COEFF_12(matrix[1])); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_CSC_1_MATRIX_COEFF_1(pipe), + MDP5_PIPE_CSC_1_MATRIX_COEFF_1_COEFF_13(matrix[2]) | + MDP5_PIPE_CSC_1_MATRIX_COEFF_1_COEFF_21(matrix[3])); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_CSC_1_MATRIX_COEFF_2(pipe), + MDP5_PIPE_CSC_1_MATRIX_COEFF_2_COEFF_22(matrix[4]) | + MDP5_PIPE_CSC_1_MATRIX_COEFF_2_COEFF_23(matrix[5])); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_CSC_1_MATRIX_COEFF_3(pipe), + MDP5_PIPE_CSC_1_MATRIX_COEFF_3_COEFF_31(matrix[6]) | + MDP5_PIPE_CSC_1_MATRIX_COEFF_3_COEFF_32(matrix[7])); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_CSC_1_MATRIX_COEFF_4(pipe), + MDP5_PIPE_CSC_1_MATRIX_COEFF_4_COEFF_33(matrix[8])); + + for (i = 0; i < ARRAY_SIZE(csc->pre_bias); i++) { + uint32_t *pre_clamp = csc->pre_clamp; + uint32_t *post_clamp = csc->post_clamp; + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_CSC_1_PRE_CLAMP(pipe, i), + MDP5_PIPE_CSC_1_PRE_CLAMP_REG_HIGH(pre_clamp[2*i+1]) | + MDP5_PIPE_CSC_1_PRE_CLAMP_REG_LOW(pre_clamp[2*i])); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_CSC_1_POST_CLAMP(pipe, i), + MDP5_PIPE_CSC_1_POST_CLAMP_REG_HIGH(post_clamp[2*i+1]) | + MDP5_PIPE_CSC_1_POST_CLAMP_REG_LOW(post_clamp[2*i])); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_CSC_1_PRE_BIAS(pipe, i), + MDP5_PIPE_CSC_1_PRE_BIAS_REG_VALUE(csc->pre_bias[i])); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_CSC_1_POST_BIAS(pipe, i), + MDP5_PIPE_CSC_1_POST_BIAS_REG_VALUE(csc->post_bias[i])); + } +} + +#define PHASE_STEP_SHIFT 21 +#define DOWN_SCALE_RATIO_MAX 32 /* 2^(26-21) */ + +static int calc_phase_step(uint32_t src, uint32_t dst, uint32_t *out_phase) +{ + uint32_t unit; + + if (src == 0 || dst == 0) + return -EINVAL; + + /* + * PHASE_STEP_X/Y is coded on 26 bits (25:0), + * where 2^21 represents the unity "1" in fixed-point hardware design. + * This leaves 5 bits for the integer part (downscale case): + * -> maximum downscale ratio = 0b1_1111 = 31 + */ + if (src > (dst * DOWN_SCALE_RATIO_MAX)) + return -EOVERFLOW; + + unit = 1 << PHASE_STEP_SHIFT; + *out_phase = mult_frac(unit, src, dst); + + return 0; +} + +static int calc_scalex_steps(struct drm_plane *plane, + uint32_t pixel_format, uint32_t src, uint32_t dest, + uint32_t phasex_steps[COMP_MAX]) +{ + const struct drm_format_info *info = drm_format_info(pixel_format); + struct mdp5_kms *mdp5_kms = get_kms(plane); + struct device *dev = mdp5_kms->dev->dev; + uint32_t phasex_step; + int ret; + + ret = calc_phase_step(src, dest, &phasex_step); + if (ret) { + DRM_DEV_ERROR(dev, "X scaling (%d->%d) failed: %d\n", src, dest, ret); + return ret; + } + + phasex_steps[COMP_0] = phasex_step; + phasex_steps[COMP_3] = phasex_step; + phasex_steps[COMP_1_2] = phasex_step / info->hsub; + + return 0; +} + +static int calc_scaley_steps(struct drm_plane *plane, + uint32_t pixel_format, uint32_t src, uint32_t dest, + uint32_t phasey_steps[COMP_MAX]) +{ + const struct drm_format_info *info = drm_format_info(pixel_format); + struct mdp5_kms *mdp5_kms = get_kms(plane); + struct device *dev = mdp5_kms->dev->dev; + uint32_t phasey_step; + int ret; + + ret = calc_phase_step(src, dest, &phasey_step); + if (ret) { + DRM_DEV_ERROR(dev, "Y scaling (%d->%d) failed: %d\n", src, dest, ret); + return ret; + } + + phasey_steps[COMP_0] = phasey_step; + phasey_steps[COMP_3] = phasey_step; + phasey_steps[COMP_1_2] = phasey_step / info->vsub; + + return 0; +} + +static uint32_t get_scale_config(const struct mdp_format *format, + uint32_t src, uint32_t dst, bool horz) +{ + const struct drm_format_info *info = drm_format_info(format->base.pixel_format); + bool scaling = format->is_yuv ? true : (src != dst); + uint32_t sub; + uint32_t ya_filter, uv_filter; + bool yuv = format->is_yuv; + + if (!scaling) + return 0; + + if (yuv) { + sub = horz ? info->hsub : info->vsub; + uv_filter = ((src / sub) <= dst) ? + SCALE_FILTER_BIL : SCALE_FILTER_PCMN; + } + ya_filter = (src <= dst) ? SCALE_FILTER_BIL : SCALE_FILTER_PCMN; + + if (horz) + return MDP5_PIPE_SCALE_CONFIG_SCALEX_EN | + MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_0(ya_filter) | + MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_3(ya_filter) | + COND(yuv, MDP5_PIPE_SCALE_CONFIG_SCALEX_FILTER_COMP_1_2(uv_filter)); + else + return MDP5_PIPE_SCALE_CONFIG_SCALEY_EN | + MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_0(ya_filter) | + MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_3(ya_filter) | + COND(yuv, MDP5_PIPE_SCALE_CONFIG_SCALEY_FILTER_COMP_1_2(uv_filter)); +} + +static void calc_pixel_ext(const struct mdp_format *format, + uint32_t src, uint32_t dst, uint32_t phase_step[2], + int pix_ext_edge1[COMP_MAX], int pix_ext_edge2[COMP_MAX], + bool horz) +{ + bool scaling = format->is_yuv ? true : (src != dst); + int i; + + /* + * Note: + * We assume here that: + * 1. PCMN filter is used for downscale + * 2. bilinear filter is used for upscale + * 3. we are in a single pipe configuration + */ + + for (i = 0; i < COMP_MAX; i++) { + pix_ext_edge1[i] = 0; + pix_ext_edge2[i] = scaling ? 1 : 0; + } +} + +static void mdp5_write_pixel_ext(struct mdp5_kms *mdp5_kms, enum mdp5_pipe pipe, + const struct mdp_format *format, + uint32_t src_w, int pe_left[COMP_MAX], int pe_right[COMP_MAX], + uint32_t src_h, int pe_top[COMP_MAX], int pe_bottom[COMP_MAX]) +{ + const struct drm_format_info *info = drm_format_info(format->base.pixel_format); + uint32_t lr, tb, req; + int i; + + for (i = 0; i < COMP_MAX; i++) { + uint32_t roi_w = src_w; + uint32_t roi_h = src_h; + + if (format->is_yuv && i == COMP_1_2) { + roi_w /= info->hsub; + roi_h /= info->vsub; + } + + lr = (pe_left[i] >= 0) ? + MDP5_PIPE_SW_PIX_EXT_LR_LEFT_RPT(pe_left[i]) : + MDP5_PIPE_SW_PIX_EXT_LR_LEFT_OVF(pe_left[i]); + + lr |= (pe_right[i] >= 0) ? + MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_RPT(pe_right[i]) : + MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_OVF(pe_right[i]); + + tb = (pe_top[i] >= 0) ? + MDP5_PIPE_SW_PIX_EXT_TB_TOP_RPT(pe_top[i]) : + MDP5_PIPE_SW_PIX_EXT_TB_TOP_OVF(pe_top[i]); + + tb |= (pe_bottom[i] >= 0) ? + MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_RPT(pe_bottom[i]) : + MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_OVF(pe_bottom[i]); + + req = MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_LEFT_RIGHT(roi_w + + pe_left[i] + pe_right[i]); + + req |= MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_TOP_BOTTOM(roi_h + + pe_top[i] + pe_bottom[i]); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SW_PIX_EXT_LR(pipe, i), lr); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SW_PIX_EXT_TB(pipe, i), tb); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS(pipe, i), req); + + DBG("comp-%d (L/R): rpt=%d/%d, ovf=%d/%d, req=%d", i, + FIELD(lr, MDP5_PIPE_SW_PIX_EXT_LR_LEFT_RPT), + FIELD(lr, MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_RPT), + FIELD(lr, MDP5_PIPE_SW_PIX_EXT_LR_LEFT_OVF), + FIELD(lr, MDP5_PIPE_SW_PIX_EXT_LR_RIGHT_OVF), + FIELD(req, MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_LEFT_RIGHT)); + + DBG("comp-%d (T/B): rpt=%d/%d, ovf=%d/%d, req=%d", i, + FIELD(tb, MDP5_PIPE_SW_PIX_EXT_TB_TOP_RPT), + FIELD(tb, MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_RPT), + FIELD(tb, MDP5_PIPE_SW_PIX_EXT_TB_TOP_OVF), + FIELD(tb, MDP5_PIPE_SW_PIX_EXT_TB_BOTTOM_OVF), + FIELD(req, MDP5_PIPE_SW_PIX_EXT_REQ_PIXELS_TOP_BOTTOM)); + } +} + +struct pixel_ext { + int left[COMP_MAX]; + int right[COMP_MAX]; + int top[COMP_MAX]; + int bottom[COMP_MAX]; +}; + +struct phase_step { + u32 x[COMP_MAX]; + u32 y[COMP_MAX]; +}; + +static void mdp5_hwpipe_mode_set(struct mdp5_kms *mdp5_kms, + struct mdp5_hw_pipe *hwpipe, + struct drm_framebuffer *fb, + struct phase_step *step, + struct pixel_ext *pe, + u32 scale_config, u32 hdecm, u32 vdecm, + bool hflip, bool vflip, + int crtc_x, int crtc_y, + unsigned int crtc_w, unsigned int crtc_h, + u32 src_img_w, u32 src_img_h, + u32 src_x, u32 src_y, + u32 src_w, u32 src_h) +{ + enum mdp5_pipe pipe = hwpipe->pipe; + bool has_pe = hwpipe->caps & MDP_PIPE_CAP_SW_PIX_EXT; + const struct mdp_format *format = + to_mdp_format(msm_framebuffer_format(fb)); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_IMG_SIZE(pipe), + MDP5_PIPE_SRC_IMG_SIZE_WIDTH(src_img_w) | + MDP5_PIPE_SRC_IMG_SIZE_HEIGHT(src_img_h)); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_SIZE(pipe), + MDP5_PIPE_SRC_SIZE_WIDTH(src_w) | + MDP5_PIPE_SRC_SIZE_HEIGHT(src_h)); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_XY(pipe), + MDP5_PIPE_SRC_XY_X(src_x) | + MDP5_PIPE_SRC_XY_Y(src_y)); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_OUT_SIZE(pipe), + MDP5_PIPE_OUT_SIZE_WIDTH(crtc_w) | + MDP5_PIPE_OUT_SIZE_HEIGHT(crtc_h)); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_OUT_XY(pipe), + MDP5_PIPE_OUT_XY_X(crtc_x) | + MDP5_PIPE_OUT_XY_Y(crtc_y)); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_FORMAT(pipe), + MDP5_PIPE_SRC_FORMAT_A_BPC(format->bpc_a) | + MDP5_PIPE_SRC_FORMAT_R_BPC(format->bpc_r) | + MDP5_PIPE_SRC_FORMAT_G_BPC(format->bpc_g) | + MDP5_PIPE_SRC_FORMAT_B_BPC(format->bpc_b) | + COND(format->alpha_enable, MDP5_PIPE_SRC_FORMAT_ALPHA_ENABLE) | + MDP5_PIPE_SRC_FORMAT_CPP(format->cpp - 1) | + MDP5_PIPE_SRC_FORMAT_UNPACK_COUNT(format->unpack_count - 1) | + COND(format->unpack_tight, MDP5_PIPE_SRC_FORMAT_UNPACK_TIGHT) | + MDP5_PIPE_SRC_FORMAT_FETCH_TYPE(format->fetch_type) | + MDP5_PIPE_SRC_FORMAT_CHROMA_SAMP(format->chroma_sample)); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_UNPACK(pipe), + MDP5_PIPE_SRC_UNPACK_ELEM0(format->unpack[0]) | + MDP5_PIPE_SRC_UNPACK_ELEM1(format->unpack[1]) | + MDP5_PIPE_SRC_UNPACK_ELEM2(format->unpack[2]) | + MDP5_PIPE_SRC_UNPACK_ELEM3(format->unpack[3])); + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_OP_MODE(pipe), + (hflip ? MDP5_PIPE_SRC_OP_MODE_FLIP_LR : 0) | + (vflip ? MDP5_PIPE_SRC_OP_MODE_FLIP_UD : 0) | + COND(has_pe, MDP5_PIPE_SRC_OP_MODE_SW_PIX_EXT_OVERRIDE) | + MDP5_PIPE_SRC_OP_MODE_BWC(BWC_LOSSLESS)); + + /* not using secure mode: */ + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SRC_ADDR_SW_STATUS(pipe), 0); + + if (hwpipe->caps & MDP_PIPE_CAP_SW_PIX_EXT) + mdp5_write_pixel_ext(mdp5_kms, pipe, format, + src_w, pe->left, pe->right, + src_h, pe->top, pe->bottom); + + if (hwpipe->caps & MDP_PIPE_CAP_SCALE) { + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SCALE_PHASE_STEP_X(pipe), + step->x[COMP_0]); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SCALE_PHASE_STEP_Y(pipe), + step->y[COMP_0]); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SCALE_CR_PHASE_STEP_X(pipe), + step->x[COMP_1_2]); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SCALE_CR_PHASE_STEP_Y(pipe), + step->y[COMP_1_2]); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_DECIMATION(pipe), + MDP5_PIPE_DECIMATION_VERT(vdecm) | + MDP5_PIPE_DECIMATION_HORZ(hdecm)); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_SCALE_CONFIG(pipe), + scale_config); + } + + if (hwpipe->caps & MDP_PIPE_CAP_CSC) { + if (MDP_FORMAT_IS_YUV(format)) + csc_enable(mdp5_kms, pipe, + mdp_get_default_csc_cfg(CSC_YUV2RGB)); + else + csc_disable(mdp5_kms, pipe); + } + + set_scanout_locked(mdp5_kms, pipe, fb); +} + +static int mdp5_plane_mode_set(struct drm_plane *plane, + struct drm_crtc *crtc, struct drm_framebuffer *fb, + struct drm_rect *src, struct drm_rect *dest) +{ + struct drm_plane_state *pstate = plane->state; + struct mdp5_hw_pipe *hwpipe = to_mdp5_plane_state(pstate)->hwpipe; + struct mdp5_kms *mdp5_kms = get_kms(plane); + enum mdp5_pipe pipe = hwpipe->pipe; + struct mdp5_hw_pipe *right_hwpipe; + const struct mdp_format *format; + uint32_t nplanes, config = 0; + struct phase_step step = { { 0 } }; + struct pixel_ext pe = { { 0 } }; + uint32_t hdecm = 0, vdecm = 0; + uint32_t pix_format; + unsigned int rotation; + bool vflip, hflip; + int crtc_x, crtc_y; + unsigned int crtc_w, crtc_h; + uint32_t src_x, src_y; + uint32_t src_w, src_h; + uint32_t src_img_w, src_img_h; + int ret; + + nplanes = fb->format->num_planes; + + /* bad formats should already be rejected: */ + if (WARN_ON(nplanes > pipe2nclients(pipe))) + return -EINVAL; + + format = to_mdp_format(msm_framebuffer_format(fb)); + pix_format = format->base.pixel_format; + + src_x = src->x1; + src_y = src->y1; + src_w = drm_rect_width(src); + src_h = drm_rect_height(src); + + crtc_x = dest->x1; + crtc_y = dest->y1; + crtc_w = drm_rect_width(dest); + crtc_h = drm_rect_height(dest); + + /* src values are in Q16 fixed point, convert to integer: */ + src_x = src_x >> 16; + src_y = src_y >> 16; + src_w = src_w >> 16; + src_h = src_h >> 16; + + src_img_w = min(fb->width, src_w); + src_img_h = min(fb->height, src_h); + + DBG("%s: FB[%u] %u,%u,%u,%u -> CRTC[%u] %d,%d,%u,%u", plane->name, + fb->base.id, src_x, src_y, src_w, src_h, + crtc->base.id, crtc_x, crtc_y, crtc_w, crtc_h); + + right_hwpipe = to_mdp5_plane_state(pstate)->r_hwpipe; + if (right_hwpipe) { + /* + * if the plane comprises of 2 hw pipes, assume that the width + * is split equally across them. The only parameters that varies + * between the 2 pipes are src_x and crtc_x + */ + crtc_w /= 2; + src_w /= 2; + src_img_w /= 2; + } + + ret = calc_scalex_steps(plane, pix_format, src_w, crtc_w, step.x); + if (ret) + return ret; + + ret = calc_scaley_steps(plane, pix_format, src_h, crtc_h, step.y); + if (ret) + return ret; + + if (hwpipe->caps & MDP_PIPE_CAP_SW_PIX_EXT) { + calc_pixel_ext(format, src_w, crtc_w, step.x, + pe.left, pe.right, true); + calc_pixel_ext(format, src_h, crtc_h, step.y, + pe.top, pe.bottom, false); + } + + /* TODO calc hdecm, vdecm */ + + /* SCALE is used to both scale and up-sample chroma components */ + config |= get_scale_config(format, src_w, crtc_w, true); + config |= get_scale_config(format, src_h, crtc_h, false); + DBG("scale config = %x", config); + + rotation = drm_rotation_simplify(pstate->rotation, + DRM_MODE_ROTATE_0 | + DRM_MODE_REFLECT_X | + DRM_MODE_REFLECT_Y); + hflip = !!(rotation & DRM_MODE_REFLECT_X); + vflip = !!(rotation & DRM_MODE_REFLECT_Y); + + mdp5_hwpipe_mode_set(mdp5_kms, hwpipe, fb, &step, &pe, + config, hdecm, vdecm, hflip, vflip, + crtc_x, crtc_y, crtc_w, crtc_h, + src_img_w, src_img_h, + src_x, src_y, src_w, src_h); + if (right_hwpipe) + mdp5_hwpipe_mode_set(mdp5_kms, right_hwpipe, fb, &step, &pe, + config, hdecm, vdecm, hflip, vflip, + crtc_x + crtc_w, crtc_y, crtc_w, crtc_h, + src_img_w, src_img_h, + src_x + src_w, src_y, src_w, src_h); + + return ret; +} + +/* + * Use this func and the one below only after the atomic state has been + * successfully swapped + */ +enum mdp5_pipe mdp5_plane_pipe(struct drm_plane *plane) +{ + struct mdp5_plane_state *pstate = to_mdp5_plane_state(plane->state); + + if (WARN_ON(!pstate->hwpipe)) + return SSPP_NONE; + + return pstate->hwpipe->pipe; +} + +enum mdp5_pipe mdp5_plane_right_pipe(struct drm_plane *plane) +{ + struct mdp5_plane_state *pstate = to_mdp5_plane_state(plane->state); + + if (!pstate->r_hwpipe) + return SSPP_NONE; + + return pstate->r_hwpipe->pipe; +} + +uint32_t mdp5_plane_get_flush(struct drm_plane *plane) +{ + struct mdp5_plane_state *pstate = to_mdp5_plane_state(plane->state); + u32 mask; + + if (WARN_ON(!pstate->hwpipe)) + return 0; + + mask = pstate->hwpipe->flush_mask; + + if (pstate->r_hwpipe) + mask |= pstate->r_hwpipe->flush_mask; + + return mask; +} + +/* initialize plane */ +struct drm_plane *mdp5_plane_init(struct drm_device *dev, + enum drm_plane_type type) +{ + struct drm_plane *plane = NULL; + struct mdp5_plane *mdp5_plane; + int ret; + + mdp5_plane = kzalloc(sizeof(*mdp5_plane), GFP_KERNEL); + if (!mdp5_plane) { + ret = -ENOMEM; + goto fail; + } + + plane = &mdp5_plane->base; + + mdp5_plane->nformats = mdp_get_formats(mdp5_plane->formats, + ARRAY_SIZE(mdp5_plane->formats), false); + + ret = drm_universal_plane_init(dev, plane, 0xff, &mdp5_plane_funcs, + mdp5_plane->formats, mdp5_plane->nformats, + NULL, type, NULL); + if (ret) + goto fail; + + drm_plane_helper_add(plane, &mdp5_plane_helper_funcs); + + mdp5_plane_install_properties(plane, &plane->base); + + drm_plane_enable_fb_damage_clips(plane); + + return plane; + +fail: + if (plane) + mdp5_plane_destroy(plane); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_smp.c b/drivers/gpu/drm/msm/disp/mdp5/mdp5_smp.c new file mode 100644 index 000000000..d7fa2c49e --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_smp.c @@ -0,0 +1,408 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#include <drm/drm_fourcc.h> +#include <drm/drm_util.h> + +#include "mdp5_kms.h" +#include "mdp5_smp.h" + + +struct mdp5_smp { + struct drm_device *dev; + + uint8_t reserved[MAX_CLIENTS]; /* fixed MMBs allocation per client */ + + int blk_cnt; + int blk_size; + + /* register cache */ + u32 alloc_w[22]; + u32 alloc_r[22]; + u32 pipe_reqprio_fifo_wm0[SSPP_MAX]; + u32 pipe_reqprio_fifo_wm1[SSPP_MAX]; + u32 pipe_reqprio_fifo_wm2[SSPP_MAX]; +}; + +static inline +struct mdp5_kms *get_kms(struct mdp5_smp *smp) +{ + struct msm_drm_private *priv = smp->dev->dev_private; + + return to_mdp5_kms(to_mdp_kms(priv->kms)); +} + +static inline u32 pipe2client(enum mdp5_pipe pipe, int plane) +{ +#define CID_UNUSED 0 + + if (WARN_ON(plane >= pipe2nclients(pipe))) + return CID_UNUSED; + + /* + * Note on SMP clients: + * For ViG pipes, fetch Y/Cr/Cb-components clients are always + * consecutive, and in that order. + * + * e.g.: + * if mdp5_cfg->smp.clients[SSPP_VIG0] = N, + * Y plane's client ID is N + * Cr plane's client ID is N + 1 + * Cb plane's client ID is N + 2 + */ + + return mdp5_cfg->smp.clients[pipe] + plane; +} + +/* allocate blocks for the specified request: */ +static int smp_request_block(struct mdp5_smp *smp, + struct mdp5_smp_state *state, + u32 cid, int nblks) +{ + void *cs = state->client_state[cid]; + int i, avail, cnt = smp->blk_cnt; + uint8_t reserved; + + /* we shouldn't be requesting blocks for an in-use client: */ + WARN_ON(bitmap_weight(cs, cnt) > 0); + + reserved = smp->reserved[cid]; + + if (reserved) { + nblks = max(0, nblks - reserved); + DBG("%d MMBs allocated (%d reserved)", nblks, reserved); + } + + avail = cnt - bitmap_weight(state->state, cnt); + if (nblks > avail) { + DRM_DEV_ERROR(smp->dev->dev, "out of blks (req=%d > avail=%d)\n", + nblks, avail); + return -ENOSPC; + } + + for (i = 0; i < nblks; i++) { + int blk = find_first_zero_bit(state->state, cnt); + set_bit(blk, cs); + set_bit(blk, state->state); + } + + return 0; +} + +static void set_fifo_thresholds(struct mdp5_smp *smp, + enum mdp5_pipe pipe, int nblks) +{ + u32 smp_entries_per_blk = smp->blk_size / (128 / BITS_PER_BYTE); + u32 val; + + /* 1/4 of SMP pool that is being fetched */ + val = (nblks * smp_entries_per_blk) / 4; + + smp->pipe_reqprio_fifo_wm0[pipe] = val * 1; + smp->pipe_reqprio_fifo_wm1[pipe] = val * 2; + smp->pipe_reqprio_fifo_wm2[pipe] = val * 3; +} + +/* + * NOTE: looks like if horizontal decimation is used (if we supported that) + * then the width used to calculate SMP block requirements is the post- + * decimated width. Ie. SMP buffering sits downstream of decimation (which + * presumably happens during the dma from scanout buffer). + */ +uint32_t mdp5_smp_calculate(struct mdp5_smp *smp, + const struct mdp_format *format, + u32 width, bool hdecim) +{ + const struct drm_format_info *info = drm_format_info(format->base.pixel_format); + struct mdp5_kms *mdp5_kms = get_kms(smp); + int rev = mdp5_cfg_get_hw_rev(mdp5_kms->cfg); + int i, hsub, nplanes, nlines; + uint32_t blkcfg = 0; + + nplanes = info->num_planes; + hsub = info->hsub; + + /* different if BWC (compressed framebuffer?) enabled: */ + nlines = 2; + + /* Newer MDPs have split/packing logic, which fetches sub-sampled + * U and V components (splits them from Y if necessary) and packs + * them together, writes to SMP using a single client. + */ + if ((rev > 0) && (format->chroma_sample > CHROMA_FULL)) { + nplanes = 2; + + /* if decimation is enabled, HW decimates less on the + * sub sampled chroma components + */ + if (hdecim && (hsub > 1)) + hsub = 1; + } + + for (i = 0; i < nplanes; i++) { + int n, fetch_stride, cpp; + + cpp = info->cpp[i]; + fetch_stride = width * cpp / (i ? hsub : 1); + + n = DIV_ROUND_UP(fetch_stride * nlines, smp->blk_size); + + /* for hw rev v1.00 */ + if (rev == 0) + n = roundup_pow_of_two(n); + + blkcfg |= (n << (8 * i)); + } + + return blkcfg; +} + +int mdp5_smp_assign(struct mdp5_smp *smp, struct mdp5_smp_state *state, + enum mdp5_pipe pipe, uint32_t blkcfg) +{ + struct mdp5_kms *mdp5_kms = get_kms(smp); + struct drm_device *dev = mdp5_kms->dev; + int i, ret; + + for (i = 0; i < pipe2nclients(pipe); i++) { + u32 cid = pipe2client(pipe, i); + int n = blkcfg & 0xff; + + if (!n) + continue; + + DBG("%s[%d]: request %d SMP blocks", pipe2name(pipe), i, n); + ret = smp_request_block(smp, state, cid, n); + if (ret) { + DRM_DEV_ERROR(dev->dev, "Cannot allocate %d SMP blocks: %d\n", + n, ret); + return ret; + } + + blkcfg >>= 8; + } + + state->assigned |= (1 << pipe); + + return 0; +} + +/* Release SMP blocks for all clients of the pipe */ +void mdp5_smp_release(struct mdp5_smp *smp, struct mdp5_smp_state *state, + enum mdp5_pipe pipe) +{ + int i; + int cnt = smp->blk_cnt; + + for (i = 0; i < pipe2nclients(pipe); i++) { + u32 cid = pipe2client(pipe, i); + void *cs = state->client_state[cid]; + + /* update global state: */ + bitmap_andnot(state->state, state->state, cs, cnt); + + /* clear client's state */ + bitmap_zero(cs, cnt); + } + + state->released |= (1 << pipe); +} + +/* NOTE: SMP_ALLOC_* regs are *not* double buffered, so release has to + * happen after scanout completes. + */ +static unsigned update_smp_state(struct mdp5_smp *smp, + u32 cid, mdp5_smp_state_t *assigned) +{ + int cnt = smp->blk_cnt; + unsigned nblks = 0; + u32 blk, val; + + for_each_set_bit(blk, *assigned, cnt) { + int idx = blk / 3; + int fld = blk % 3; + + val = smp->alloc_w[idx]; + + switch (fld) { + case 0: + val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT0__MASK; + val |= MDP5_SMP_ALLOC_W_REG_CLIENT0(cid); + break; + case 1: + val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT1__MASK; + val |= MDP5_SMP_ALLOC_W_REG_CLIENT1(cid); + break; + case 2: + val &= ~MDP5_SMP_ALLOC_W_REG_CLIENT2__MASK; + val |= MDP5_SMP_ALLOC_W_REG_CLIENT2(cid); + break; + } + + smp->alloc_w[idx] = val; + smp->alloc_r[idx] = val; + + nblks++; + } + + return nblks; +} + +static void write_smp_alloc_regs(struct mdp5_smp *smp) +{ + struct mdp5_kms *mdp5_kms = get_kms(smp); + int i, num_regs; + + num_regs = smp->blk_cnt / 3 + 1; + + for (i = 0; i < num_regs; i++) { + mdp5_write(mdp5_kms, REG_MDP5_SMP_ALLOC_W_REG(i), + smp->alloc_w[i]); + mdp5_write(mdp5_kms, REG_MDP5_SMP_ALLOC_R_REG(i), + smp->alloc_r[i]); + } +} + +static void write_smp_fifo_regs(struct mdp5_smp *smp) +{ + struct mdp5_kms *mdp5_kms = get_kms(smp); + int i; + + for (i = 0; i < mdp5_kms->num_hwpipes; i++) { + struct mdp5_hw_pipe *hwpipe = mdp5_kms->hwpipes[i]; + enum mdp5_pipe pipe = hwpipe->pipe; + + mdp5_write(mdp5_kms, REG_MDP5_PIPE_REQPRIO_FIFO_WM_0(pipe), + smp->pipe_reqprio_fifo_wm0[pipe]); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_REQPRIO_FIFO_WM_1(pipe), + smp->pipe_reqprio_fifo_wm1[pipe]); + mdp5_write(mdp5_kms, REG_MDP5_PIPE_REQPRIO_FIFO_WM_2(pipe), + smp->pipe_reqprio_fifo_wm2[pipe]); + } +} + +void mdp5_smp_prepare_commit(struct mdp5_smp *smp, struct mdp5_smp_state *state) +{ + enum mdp5_pipe pipe; + + for_each_set_bit(pipe, &state->assigned, sizeof(state->assigned) * 8) { + unsigned i, nblks = 0; + + for (i = 0; i < pipe2nclients(pipe); i++) { + u32 cid = pipe2client(pipe, i); + void *cs = state->client_state[cid]; + + nblks += update_smp_state(smp, cid, cs); + + DBG("assign %s:%u, %u blks", + pipe2name(pipe), i, nblks); + } + + set_fifo_thresholds(smp, pipe, nblks); + } + + write_smp_alloc_regs(smp); + write_smp_fifo_regs(smp); + + state->assigned = 0; +} + +void mdp5_smp_complete_commit(struct mdp5_smp *smp, struct mdp5_smp_state *state) +{ + enum mdp5_pipe pipe; + + for_each_set_bit(pipe, &state->released, sizeof(state->released) * 8) { + DBG("release %s", pipe2name(pipe)); + set_fifo_thresholds(smp, pipe, 0); + } + + write_smp_fifo_regs(smp); + + state->released = 0; +} + +void mdp5_smp_dump(struct mdp5_smp *smp, struct drm_printer *p) +{ + struct mdp5_kms *mdp5_kms = get_kms(smp); + struct mdp5_hw_pipe_state *hwpstate; + struct mdp5_smp_state *state; + struct mdp5_global_state *global_state; + int total = 0, i, j; + + drm_printf(p, "name\tinuse\tplane\n"); + drm_printf(p, "----\t-----\t-----\n"); + + if (drm_can_sleep()) + drm_modeset_lock(&mdp5_kms->glob_state_lock, NULL); + + global_state = mdp5_get_existing_global_state(mdp5_kms); + + /* grab these *after* we hold the state_lock */ + hwpstate = &global_state->hwpipe; + state = &global_state->smp; + + for (i = 0; i < mdp5_kms->num_hwpipes; i++) { + struct mdp5_hw_pipe *hwpipe = mdp5_kms->hwpipes[i]; + struct drm_plane *plane = hwpstate->hwpipe_to_plane[hwpipe->idx]; + enum mdp5_pipe pipe = hwpipe->pipe; + for (j = 0; j < pipe2nclients(pipe); j++) { + u32 cid = pipe2client(pipe, j); + void *cs = state->client_state[cid]; + int inuse = bitmap_weight(cs, smp->blk_cnt); + + drm_printf(p, "%s:%d\t%d\t%s\n", + pipe2name(pipe), j, inuse, + plane ? plane->name : NULL); + + total += inuse; + } + } + + drm_printf(p, "TOTAL:\t%d\t(of %d)\n", total, smp->blk_cnt); + drm_printf(p, "AVAIL:\t%d\n", smp->blk_cnt - + bitmap_weight(state->state, smp->blk_cnt)); + + if (drm_can_sleep()) + drm_modeset_unlock(&mdp5_kms->glob_state_lock); +} + +void mdp5_smp_destroy(struct mdp5_smp *smp) +{ + kfree(smp); +} + +struct mdp5_smp *mdp5_smp_init(struct mdp5_kms *mdp5_kms, const struct mdp5_smp_block *cfg) +{ + struct mdp5_smp_state *state; + struct mdp5_global_state *global_state; + struct mdp5_smp *smp = NULL; + int ret; + + smp = kzalloc(sizeof(*smp), GFP_KERNEL); + if (unlikely(!smp)) { + ret = -ENOMEM; + goto fail; + } + + smp->dev = mdp5_kms->dev; + smp->blk_cnt = cfg->mmb_count; + smp->blk_size = cfg->mmb_size; + + global_state = mdp5_get_existing_global_state(mdp5_kms); + state = &global_state->smp; + + /* statically tied MMBs cannot be re-allocated: */ + bitmap_copy(state->state, cfg->reserved_state, smp->blk_cnt); + memcpy(smp->reserved, cfg->reserved, sizeof(smp->reserved)); + + return smp; +fail: + if (smp) + mdp5_smp_destroy(smp); + + return ERR_PTR(ret); +} diff --git a/drivers/gpu/drm/msm/disp/mdp5/mdp5_smp.h b/drivers/gpu/drm/msm/disp/mdp5/mdp5_smp.h new file mode 100644 index 000000000..ba5618e13 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp5/mdp5_smp.h @@ -0,0 +1,87 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2014, The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#ifndef __MDP5_SMP_H__ +#define __MDP5_SMP_H__ + +#include <drm/drm_print.h> + +#include "msm_drv.h" + +/* + * SMP - Shared Memory Pool: + * + * SMP blocks are shared between all the clients, where each plane in + * a scanout buffer is a SMP client. Ie. scanout of 3 plane I420 on + * pipe VIG0 => 3 clients: VIG0_Y, VIG0_CB, VIG0_CR. + * + * Based on the size of the attached scanout buffer, a certain # of + * blocks must be allocated to that client out of the shared pool. + * + * In some hw, some blocks are statically allocated for certain pipes + * and CANNOT be re-allocated (eg: MMB0 and MMB1 both tied to RGB0). + * + * + * Atomic SMP State: + * + * On atomic updates that modify SMP configuration, the state is cloned + * (copied) and modified. For test-only, or in cases where atomic + * update fails (or if we hit ww_mutex deadlock/backoff condition) the + * new state is simply thrown away. + * + * Because the SMP registers are not double buffered, updates are a + * two step process: + * + * 1) in _prepare_commit() we configure things (via read-modify-write) + * for the newly assigned pipes, so we don't take away blocks + * assigned to pipes that are still scanning out + * 2) in _complete_commit(), after vblank/etc, we clear things for the + * released clients, since at that point old pipes are no longer + * scanning out. + */ +struct mdp5_smp_state { + /* global state of what blocks are in use: */ + mdp5_smp_state_t state; + + /* per client state of what blocks they are using: */ + mdp5_smp_state_t client_state[MAX_CLIENTS]; + + /* assigned pipes (hw updated at _prepare_commit()): */ + unsigned long assigned; + + /* released pipes (hw updated at _complete_commit()): */ + unsigned long released; +}; + +struct mdp5_kms; +struct mdp5_smp; + +/* + * SMP module prototypes: + * mdp5_smp_init() returns a SMP @handler, + * which is then used to call the other mdp5_smp_*(handler, ...) functions. + */ + +struct mdp5_smp *mdp5_smp_init(struct mdp5_kms *mdp5_kms, + const struct mdp5_smp_block *cfg); +void mdp5_smp_destroy(struct mdp5_smp *smp); + +void mdp5_smp_dump(struct mdp5_smp *smp, struct drm_printer *p); + +uint32_t mdp5_smp_calculate(struct mdp5_smp *smp, + const struct mdp_format *format, + u32 width, bool hdecim); + +int mdp5_smp_assign(struct mdp5_smp *smp, struct mdp5_smp_state *state, + enum mdp5_pipe pipe, uint32_t blkcfg); +void mdp5_smp_release(struct mdp5_smp *smp, struct mdp5_smp_state *state, + enum mdp5_pipe pipe); + +void mdp5_smp_prepare_commit(struct mdp5_smp *smp, struct mdp5_smp_state *state); +void mdp5_smp_complete_commit(struct mdp5_smp *smp, struct mdp5_smp_state *state); + +#endif /* __MDP5_SMP_H__ */ diff --git a/drivers/gpu/drm/msm/disp/mdp_common.xml.h b/drivers/gpu/drm/msm/disp/mdp_common.xml.h new file mode 100644 index 000000000..4f51bea8f --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp_common.xml.h @@ -0,0 +1,104 @@ +#ifndef MDP_COMMON_XML +#define MDP_COMMON_XML + +/* Autogenerated file, DO NOT EDIT manually! + +This file was generated by the rules-ng-ng headergen tool in this git repository: +http://github.com/freedreno/envytools/ +git clone https://github.com/freedreno/envytools.git + +The rules-ng-ng source files this header was generated from are: +- /home/robclark/src/envytools/rnndb/msm.xml ( 676 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/freedreno_copyright.xml ( 1572 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/mdp/mdp4.xml ( 20915 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/mdp/mdp_common.xml ( 2849 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/mdp/mdp5.xml ( 37411 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/dsi/dsi.xml ( 42301 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/dsi/sfpb.xml ( 602 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/dsi/mmss_cc.xml ( 1686 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/hdmi/qfprom.xml ( 600 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/hdmi/hdmi.xml ( 41874 bytes, from 2020-07-23 21:58:14) +- /home/robclark/src/envytools/rnndb/edp/edp.xml ( 10416 bytes, from 2020-07-23 21:58:14) + +Copyright (C) 2013-2020 by the following authors: +- Rob Clark <robdclark@gmail.com> (robclark) +- Ilia Mirkin <imirkin@alum.mit.edu> (imirkin) + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice (including the +next paragraph) shall be included in all copies or substantial +portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + + +enum mdp_chroma_samp_type { + CHROMA_FULL = 0, + CHROMA_H2V1 = 1, + CHROMA_H1V2 = 2, + CHROMA_420 = 3, +}; + +enum mdp_fetch_type { + MDP_PLANE_INTERLEAVED = 0, + MDP_PLANE_PLANAR = 1, + MDP_PLANE_PSEUDO_PLANAR = 2, +}; + +enum mdp_mixer_stage_id { + STAGE_UNUSED = 0, + STAGE_BASE = 1, + STAGE0 = 2, + STAGE1 = 3, + STAGE2 = 4, + STAGE3 = 5, + STAGE4 = 6, + STAGE5 = 7, + STAGE6 = 8, + STAGE_MAX = 8, +}; + +enum mdp_alpha_type { + FG_CONST = 0, + BG_CONST = 1, + FG_PIXEL = 2, + BG_PIXEL = 3, +}; + +enum mdp_component_type { + COMP_0 = 0, + COMP_1_2 = 1, + COMP_3 = 2, + COMP_MAX = 3, +}; + +enum mdp_bpc { + BPC1 = 0, + BPC5 = 1, + BPC6 = 2, + BPC8 = 3, +}; + +enum mdp_bpc_alpha { + BPC1A = 0, + BPC4A = 1, + BPC6A = 2, + BPC8A = 3, +}; + + +#endif /* MDP_COMMON_XML */ diff --git a/drivers/gpu/drm/msm/disp/mdp_format.c b/drivers/gpu/drm/msm/disp/mdp_format.c new file mode 100644 index 000000000..5495d8b3f --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp_format.c @@ -0,0 +1,181 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014 The Linux Foundation. All rights reserved. + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + + +#include "msm_drv.h" +#include "mdp_kms.h" + +static struct csc_cfg csc_convert[CSC_MAX] = { + [CSC_RGB2RGB] = { + .type = CSC_RGB2RGB, + .matrix = { + 0x0200, 0x0000, 0x0000, + 0x0000, 0x0200, 0x0000, + 0x0000, 0x0000, 0x0200 + }, + .pre_bias = { 0x0, 0x0, 0x0 }, + .post_bias = { 0x0, 0x0, 0x0 }, + .pre_clamp = { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff }, + .post_clamp = { 0x0, 0xff, 0x0, 0xff, 0x0, 0xff }, + }, + [CSC_YUV2RGB] = { + .type = CSC_YUV2RGB, + .matrix = { + 0x0254, 0x0000, 0x0331, + 0x0254, 0xff37, 0xfe60, + 0x0254, 0x0409, 0x0000 + }, + .pre_bias = { 0xfff0, 0xff80, 0xff80 }, + .post_bias = { 0x00, 0x00, 0x00 }, + .pre_clamp = { 0x00, 0xff, 0x00, 0xff, 0x00, 0xff }, + .post_clamp = { 0x00, 0xff, 0x00, 0xff, 0x00, 0xff }, + }, + [CSC_RGB2YUV] = { + .type = CSC_RGB2YUV, + .matrix = { + 0x0083, 0x0102, 0x0032, + 0x1fb5, 0x1f6c, 0x00e1, + 0x00e1, 0x1f45, 0x1fdc + }, + .pre_bias = { 0x00, 0x00, 0x00 }, + .post_bias = { 0x10, 0x80, 0x80 }, + .pre_clamp = { 0x00, 0xff, 0x00, 0xff, 0x00, 0xff }, + .post_clamp = { 0x10, 0xeb, 0x10, 0xf0, 0x10, 0xf0 }, + }, + [CSC_YUV2YUV] = { + .type = CSC_YUV2YUV, + .matrix = { + 0x0200, 0x0000, 0x0000, + 0x0000, 0x0200, 0x0000, + 0x0000, 0x0000, 0x0200 + }, + .pre_bias = { 0x00, 0x00, 0x00 }, + .post_bias = { 0x00, 0x00, 0x00 }, + .pre_clamp = { 0x00, 0xff, 0x00, 0xff, 0x00, 0xff }, + .post_clamp = { 0x00, 0xff, 0x00, 0xff, 0x00, 0xff }, + }, +}; + +#define FMT(name, a, r, g, b, e0, e1, e2, e3, alpha, tight, c, cnt, fp, cs, yuv) { \ + .base = { .pixel_format = DRM_FORMAT_ ## name }, \ + .bpc_a = BPC ## a ## A, \ + .bpc_r = BPC ## r, \ + .bpc_g = BPC ## g, \ + .bpc_b = BPC ## b, \ + .unpack = { e0, e1, e2, e3 }, \ + .alpha_enable = alpha, \ + .unpack_tight = tight, \ + .cpp = c, \ + .unpack_count = cnt, \ + .fetch_type = fp, \ + .chroma_sample = cs, \ + .is_yuv = yuv, \ +} + +#define BPC0A 0 + +/* + * Note: Keep RGB formats 1st, followed by YUV formats to avoid breaking + * mdp_get_rgb_formats()'s implementation. + */ +static const struct mdp_format formats[] = { + /* name a r g b e0 e1 e2 e3 alpha tight cpp cnt ... */ + FMT(ARGB8888, 8, 8, 8, 8, 1, 0, 2, 3, true, true, 4, 4, + MDP_PLANE_INTERLEAVED, CHROMA_FULL, false), + FMT(ABGR8888, 8, 8, 8, 8, 2, 0, 1, 3, true, true, 4, 4, + MDP_PLANE_INTERLEAVED, CHROMA_FULL, false), + FMT(RGBA8888, 8, 8, 8, 8, 3, 1, 0, 2, true, true, 4, 4, + MDP_PLANE_INTERLEAVED, CHROMA_FULL, false), + FMT(BGRA8888, 8, 8, 8, 8, 3, 2, 0, 1, true, true, 4, 4, + MDP_PLANE_INTERLEAVED, CHROMA_FULL, false), + FMT(XRGB8888, 8, 8, 8, 8, 1, 0, 2, 3, false, true, 4, 4, + MDP_PLANE_INTERLEAVED, CHROMA_FULL, false), + FMT(XBGR8888, 8, 8, 8, 8, 2, 0, 1, 3, false, true, 4, 4, + MDP_PLANE_INTERLEAVED, CHROMA_FULL, false), + FMT(RGBX8888, 8, 8, 8, 8, 3, 1, 0, 2, false, true, 4, 4, + MDP_PLANE_INTERLEAVED, CHROMA_FULL, false), + FMT(BGRX8888, 8, 8, 8, 8, 3, 2, 0, 1, false, true, 4, 4, + MDP_PLANE_INTERLEAVED, CHROMA_FULL, false), + FMT(RGB888, 0, 8, 8, 8, 1, 0, 2, 0, false, true, 3, 3, + MDP_PLANE_INTERLEAVED, CHROMA_FULL, false), + FMT(BGR888, 0, 8, 8, 8, 2, 0, 1, 0, false, true, 3, 3, + MDP_PLANE_INTERLEAVED, CHROMA_FULL, false), + FMT(RGB565, 0, 5, 6, 5, 1, 0, 2, 0, false, true, 2, 3, + MDP_PLANE_INTERLEAVED, CHROMA_FULL, false), + FMT(BGR565, 0, 5, 6, 5, 2, 0, 1, 0, false, true, 2, 3, + MDP_PLANE_INTERLEAVED, CHROMA_FULL, false), + + /* --- RGB formats above / YUV formats below this line --- */ + + /* 2 plane YUV */ + FMT(NV12, 0, 8, 8, 8, 1, 2, 0, 0, false, true, 2, 2, + MDP_PLANE_PSEUDO_PLANAR, CHROMA_420, true), + FMT(NV21, 0, 8, 8, 8, 2, 1, 0, 0, false, true, 2, 2, + MDP_PLANE_PSEUDO_PLANAR, CHROMA_420, true), + FMT(NV16, 0, 8, 8, 8, 1, 2, 0, 0, false, true, 2, 2, + MDP_PLANE_PSEUDO_PLANAR, CHROMA_H2V1, true), + FMT(NV61, 0, 8, 8, 8, 2, 1, 0, 0, false, true, 2, 2, + MDP_PLANE_PSEUDO_PLANAR, CHROMA_H2V1, true), + /* 1 plane YUV */ + FMT(VYUY, 0, 8, 8, 8, 2, 0, 1, 0, false, true, 2, 4, + MDP_PLANE_INTERLEAVED, CHROMA_H2V1, true), + FMT(UYVY, 0, 8, 8, 8, 1, 0, 2, 0, false, true, 2, 4, + MDP_PLANE_INTERLEAVED, CHROMA_H2V1, true), + FMT(YUYV, 0, 8, 8, 8, 0, 1, 0, 2, false, true, 2, 4, + MDP_PLANE_INTERLEAVED, CHROMA_H2V1, true), + FMT(YVYU, 0, 8, 8, 8, 0, 2, 0, 1, false, true, 2, 4, + MDP_PLANE_INTERLEAVED, CHROMA_H2V1, true), + /* 3 plane YUV */ + FMT(YUV420, 0, 8, 8, 8, 2, 1, 0, 0, false, true, 1, 1, + MDP_PLANE_PLANAR, CHROMA_420, true), + FMT(YVU420, 0, 8, 8, 8, 1, 2, 0, 0, false, true, 1, 1, + MDP_PLANE_PLANAR, CHROMA_420, true), +}; + +/* + * Note: + * @rgb_only must be set to true, when requesting + * supported formats for RGB pipes. + */ +uint32_t mdp_get_formats(uint32_t *pixel_formats, uint32_t max_formats, + bool rgb_only) +{ + uint32_t i; + for (i = 0; i < ARRAY_SIZE(formats); i++) { + const struct mdp_format *f = &formats[i]; + + if (i == max_formats) + break; + + if (rgb_only && MDP_FORMAT_IS_YUV(f)) + break; + + pixel_formats[i] = f->base.pixel_format; + } + + return i; +} + +const struct msm_format *mdp_get_format(struct msm_kms *kms, uint32_t format, + uint64_t modifier) +{ + int i; + for (i = 0; i < ARRAY_SIZE(formats); i++) { + const struct mdp_format *f = &formats[i]; + if (f->base.pixel_format == format) + return &f->base; + } + return NULL; +} + +struct csc_cfg *mdp_get_default_csc_cfg(enum csc_type type) +{ + if (WARN_ON(type >= CSC_MAX)) + return NULL; + + return &csc_convert[type]; +} diff --git a/drivers/gpu/drm/msm/disp/mdp_kms.c b/drivers/gpu/drm/msm/disp/mdp_kms.c new file mode 100644 index 000000000..3c35ccfc7 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp_kms.c @@ -0,0 +1,138 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + + +#include "msm_drv.h" +#include "mdp_kms.h" + + +struct mdp_irq_wait { + struct mdp_irq irq; + int count; +}; + +static DECLARE_WAIT_QUEUE_HEAD(wait_event); + +static DEFINE_SPINLOCK(list_lock); + +static void update_irq(struct mdp_kms *mdp_kms) +{ + struct mdp_irq *irq; + uint32_t irqmask = mdp_kms->vblank_mask; + + assert_spin_locked(&list_lock); + + list_for_each_entry(irq, &mdp_kms->irq_list, node) + irqmask |= irq->irqmask; + + mdp_kms->funcs->set_irqmask(mdp_kms, irqmask, mdp_kms->cur_irq_mask); + mdp_kms->cur_irq_mask = irqmask; +} + +/* if an mdp_irq's irqmask has changed, such as when mdp5 crtc<->encoder + * link changes, this must be called to figure out the new global irqmask + */ +void mdp_irq_update(struct mdp_kms *mdp_kms) +{ + unsigned long flags; + spin_lock_irqsave(&list_lock, flags); + update_irq(mdp_kms); + spin_unlock_irqrestore(&list_lock, flags); +} + +void mdp_dispatch_irqs(struct mdp_kms *mdp_kms, uint32_t status) +{ + struct mdp_irq *handler, *n; + unsigned long flags; + + spin_lock_irqsave(&list_lock, flags); + mdp_kms->in_irq = true; + list_for_each_entry_safe(handler, n, &mdp_kms->irq_list, node) { + if (handler->irqmask & status) { + spin_unlock_irqrestore(&list_lock, flags); + handler->irq(handler, handler->irqmask & status); + spin_lock_irqsave(&list_lock, flags); + } + } + mdp_kms->in_irq = false; + update_irq(mdp_kms); + spin_unlock_irqrestore(&list_lock, flags); + +} + +void mdp_update_vblank_mask(struct mdp_kms *mdp_kms, uint32_t mask, bool enable) +{ + unsigned long flags; + + spin_lock_irqsave(&list_lock, flags); + if (enable) + mdp_kms->vblank_mask |= mask; + else + mdp_kms->vblank_mask &= ~mask; + update_irq(mdp_kms); + spin_unlock_irqrestore(&list_lock, flags); +} + +static void wait_irq(struct mdp_irq *irq, uint32_t irqstatus) +{ + struct mdp_irq_wait *wait = + container_of(irq, struct mdp_irq_wait, irq); + wait->count--; + wake_up_all(&wait_event); +} + +void mdp_irq_wait(struct mdp_kms *mdp_kms, uint32_t irqmask) +{ + struct mdp_irq_wait wait = { + .irq = { + .irq = wait_irq, + .irqmask = irqmask, + }, + .count = 1, + }; + mdp_irq_register(mdp_kms, &wait.irq); + wait_event_timeout(wait_event, (wait.count <= 0), + msecs_to_jiffies(100)); + mdp_irq_unregister(mdp_kms, &wait.irq); +} + +void mdp_irq_register(struct mdp_kms *mdp_kms, struct mdp_irq *irq) +{ + unsigned long flags; + bool needs_update = false; + + spin_lock_irqsave(&list_lock, flags); + + if (!irq->registered) { + irq->registered = true; + list_add(&irq->node, &mdp_kms->irq_list); + needs_update = !mdp_kms->in_irq; + } + + spin_unlock_irqrestore(&list_lock, flags); + + if (needs_update) + mdp_irq_update(mdp_kms); +} + +void mdp_irq_unregister(struct mdp_kms *mdp_kms, struct mdp_irq *irq) +{ + unsigned long flags; + bool needs_update = false; + + spin_lock_irqsave(&list_lock, flags); + + if (irq->registered) { + irq->registered = false; + list_del(&irq->node); + needs_update = !mdp_kms->in_irq; + } + + spin_unlock_irqrestore(&list_lock, flags); + + if (needs_update) + mdp_irq_update(mdp_kms); +} diff --git a/drivers/gpu/drm/msm/disp/mdp_kms.h b/drivers/gpu/drm/msm/disp/mdp_kms.h new file mode 100644 index 000000000..1535c5618 --- /dev/null +++ b/drivers/gpu/drm/msm/disp/mdp_kms.h @@ -0,0 +1,137 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (C) 2013 Red Hat + * Author: Rob Clark <robdclark@gmail.com> + */ + +#ifndef __MDP_KMS_H__ +#define __MDP_KMS_H__ + +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/regulator/consumer.h> + +#include "msm_drv.h" +#include "msm_kms.h" +#include "mdp_common.xml.h" + +struct mdp_kms; + +struct mdp_kms_funcs { + struct msm_kms_funcs base; + void (*set_irqmask)(struct mdp_kms *mdp_kms, uint32_t irqmask, + uint32_t old_irqmask); +}; + +struct mdp_kms { + struct msm_kms base; + + const struct mdp_kms_funcs *funcs; + + /* irq handling: */ + bool in_irq; + struct list_head irq_list; /* list of mdp4_irq */ + uint32_t vblank_mask; /* irq bits set for userspace vblank */ + uint32_t cur_irq_mask; /* current irq mask */ +}; +#define to_mdp_kms(x) container_of(x, struct mdp_kms, base) + +static inline void mdp_kms_init(struct mdp_kms *mdp_kms, + const struct mdp_kms_funcs *funcs) +{ + mdp_kms->funcs = funcs; + INIT_LIST_HEAD(&mdp_kms->irq_list); + msm_kms_init(&mdp_kms->base, &funcs->base); +} + +/* + * irq helpers: + */ + +/* For transiently registering for different MDP irqs that various parts + * of the KMS code need during setup/configuration. These are not + * necessarily the same as what drm_vblank_get/put() are requesting, and + * the hysteresis in drm_vblank_put() is not necessarily desirable for + * internal housekeeping related irq usage. + */ +struct mdp_irq { + struct list_head node; + uint32_t irqmask; + bool registered; + void (*irq)(struct mdp_irq *irq, uint32_t irqstatus); +}; + +void mdp_dispatch_irqs(struct mdp_kms *mdp_kms, uint32_t status); +void mdp_update_vblank_mask(struct mdp_kms *mdp_kms, uint32_t mask, bool enable); +void mdp_irq_wait(struct mdp_kms *mdp_kms, uint32_t irqmask); +void mdp_irq_register(struct mdp_kms *mdp_kms, struct mdp_irq *irq); +void mdp_irq_unregister(struct mdp_kms *mdp_kms, struct mdp_irq *irq); +void mdp_irq_update(struct mdp_kms *mdp_kms); + +/* + * pixel format helpers: + */ + +struct mdp_format { + struct msm_format base; + enum mdp_bpc bpc_r, bpc_g, bpc_b; + enum mdp_bpc_alpha bpc_a; + uint8_t unpack[4]; + bool alpha_enable, unpack_tight; + uint8_t cpp, unpack_count; + enum mdp_fetch_type fetch_type; + enum mdp_chroma_samp_type chroma_sample; + bool is_yuv; +}; +#define to_mdp_format(x) container_of(x, struct mdp_format, base) +#define MDP_FORMAT_IS_YUV(mdp_format) ((mdp_format)->is_yuv) + +uint32_t mdp_get_formats(uint32_t *formats, uint32_t max_formats, bool rgb_only); +const struct msm_format *mdp_get_format(struct msm_kms *kms, uint32_t format, uint64_t modifier); + +/* MDP capabilities */ +#define MDP_CAP_SMP BIT(0) /* Shared Memory Pool */ +#define MDP_CAP_DSC BIT(1) /* VESA Display Stream Compression */ +#define MDP_CAP_CDM BIT(2) /* Chroma Down Module (HDMI 2.0 YUV) */ +#define MDP_CAP_SRC_SPLIT BIT(3) /* Source Split of SSPPs */ + +/* MDP pipe capabilities */ +#define MDP_PIPE_CAP_HFLIP BIT(0) +#define MDP_PIPE_CAP_VFLIP BIT(1) +#define MDP_PIPE_CAP_SCALE BIT(2) +#define MDP_PIPE_CAP_CSC BIT(3) +#define MDP_PIPE_CAP_DECIMATION BIT(4) +#define MDP_PIPE_CAP_SW_PIX_EXT BIT(5) +#define MDP_PIPE_CAP_CURSOR BIT(6) + +/* MDP layer mixer caps */ +#define MDP_LM_CAP_DISPLAY BIT(0) +#define MDP_LM_CAP_WB BIT(1) +#define MDP_LM_CAP_PAIR BIT(2) + +static inline bool pipe_supports_yuv(uint32_t pipe_caps) +{ + return (pipe_caps & MDP_PIPE_CAP_SCALE) && + (pipe_caps & MDP_PIPE_CAP_CSC); +} + +enum csc_type { + CSC_RGB2RGB = 0, + CSC_YUV2RGB, + CSC_RGB2YUV, + CSC_YUV2YUV, + CSC_MAX +}; + +struct csc_cfg { + enum csc_type type; + uint32_t matrix[9]; + uint32_t pre_bias[3]; + uint32_t post_bias[3]; + uint32_t pre_clamp[6]; + uint32_t post_clamp[6]; +}; + +struct csc_cfg *mdp_get_default_csc_cfg(enum csc_type); + +#endif /* __MDP_KMS_H__ */ |