Merge tag 'drm-misc-next-2025-11-14-1' of https://gitlab.freedesktop.org/drm/misc/kernel into drm-next

drm-misc-next for v6.19:

UAPI Changes:
- Add sysfs entries, coredump support and uevents to QAIC.
- Add fdinfo memory statistics to ivpu.

Cross-subsystem Changes:
- Handle stub fence initialization during module init.
- Stop using system_wq in scheduler and drivers.

Core Changes:
- Documentation updates to ttm, vblank.
- Add EDID quirk for sharp panel.
- Use drm_crtc_vblank_(crtc,waitqueue) more in core and drivers.

Driver Changes:
- Small updates and fixes to panfrost, amdxdna, vmwgfx, ast, ivpu.
- Handle preemption in amdxdna.
- Add PM support to qaic.
- Huge refactor of sun4i's layer code to decouple plane code from output
  and improve support for DE33.
- Add larger page and compression support to nouveau.

Signed-off-by: Dave Airlie <airlied@redhat.com>
From: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Link: https://patch.msgid.link/1ad3ea69-d029-4a21-8b3d-6b264b1b2a30@linux.intel.com
This commit is contained in:
Dave Airlie 2025-11-17 14:21:48 +10:00
commit 490fd93366
72 changed files with 2323 additions and 713 deletions

View File

@ -0,0 +1,19 @@
What: /sys/bus/pci/drivers/qaic/XXXX:XX:XX.X/accel/accel<minor_nr>/dbc<N>_state
Date: October 2025
KernelVersion: 6.19
Contact: Jeff Hugo <jeff.hugo@oss.qualcomm.com>
Description: Represents the current state of DMA Bridge channel (DBC). Below are the possible
states:
=================== ==========================================================
IDLE (0) DBC is free and can be activated
ASSIGNED (1) DBC is activated and a workload is running on device
BEFORE_SHUTDOWN (2) Sub-system associated with this workload has crashed and
it will shutdown soon
AFTER_SHUTDOWN (3) Sub-system associated with this workload has crashed and
it has shutdown
BEFORE_POWER_UP (4) Sub-system associated with this workload is shutdown and
it will be powered up soon
AFTER_POWER_UP (5) Sub-system associated with this workload is now powered up
=================== ==========================================================
Users: Any userspace application or clients interested in DBC state.

View File

@ -487,8 +487,8 @@ one user crashes, the fallout of that should be limited to that workload and not
impact other workloads. SSR accomplishes this.
If a particular workload crashes, QSM notifies the host via the QAIC_SSR MHI
channel. This notification identifies the workload by it's assigned DBC. A
multi-stage recovery process is then used to cleanup both sides, and get the
channel. This notification identifies the workload by its assigned DBC. A
multi-stage recovery process is then used to cleanup both sides, and gets the
DBC/NSPs into a working state.
When SSR occurs, any state in the workload is lost. Any inputs that were in
@ -496,6 +496,27 @@ process, or queued by not yet serviced, are lost. The loaded artifacts will
remain in on-card DDR, but the host will need to re-activate the workload if
it desires to recover the workload.
When SSR occurs for a specific NSP, the assigned DBC goes through the
following state transactions in order:
DBC_STATE_BEFORE_SHUTDOWN
Indicates that the affected NSP was found in an unrecoverable error
condition.
DBC_STATE_AFTER_SHUTDOWN
Indicates that the NSP is under reset.
DBC_STATE_BEFORE_POWER_UP
Indicates that the NSP's debug information has been collected, and is
ready to be collected by the host (if desired). At that stage the NSP
is restarted by QSM.
DBC_STATE_AFTER_POWER_UP
Indicates that the NSP has been restarted, fully operational and is
in idle state.
SSR also has an optional crashdump collection feature. If enabled, the host can
collect the memory dump for the crashed NSP and dump it to the user space via
the dev_coredump subsystem. The host can also decline the crashdump collection
request from the device.
Reliability, Accessibility, Serviceability (RAS)
================================================

View File

@ -25,6 +25,9 @@ properties:
- enum:
- renesas,r9a07g054-du # RZ/V2L
- const: renesas,r9a07g044-du # RZ/G2L fallback
- items:
- const: renesas,r9a09g056-du # RZ/V2N
- const: renesas,r9a09g057-du # RZ/V2H(P) fallback
reg:
maxItems: 1

View File

@ -189,7 +189,6 @@ aie2_sched_notify(struct amdxdna_sched_job *job)
up(&job->hwctx->priv->job_sem);
job->job_done = true;
dma_fence_put(fence);
mmput_async(job->mm);
aie2_job_put(job);
}
@ -691,17 +690,19 @@ void aie2_hwctx_fini(struct amdxdna_hwctx *hwctx)
xdna = hwctx->client->xdna;
XDNA_DBG(xdna, "%s sequence number %lld", hwctx->name, hwctx->priv->seq);
drm_sched_entity_destroy(&hwctx->priv->entity);
aie2_hwctx_wait_for_idle(hwctx);
/* Request fw to destroy hwctx and cancel the rest pending requests */
aie2_release_resource(hwctx);
mutex_unlock(&xdna->dev_lock);
drm_sched_entity_destroy(&hwctx->priv->entity);
/* Wait for all submitted jobs to be completed or canceled */
wait_event(hwctx->priv->job_free_wq,
atomic64_read(&hwctx->job_submit_cnt) ==
atomic64_read(&hwctx->job_free_cnt));
mutex_lock(&xdna->dev_lock);
drm_sched_fini(&hwctx->priv->sched);
aie2_ctx_syncobj_destroy(hwctx);

View File

@ -210,6 +210,14 @@ int aie2_create_context(struct amdxdna_dev_hdl *ndev, struct amdxdna_hwctx *hwct
hwctx->fw_ctx_id = resp.context_id;
WARN_ONCE(hwctx->fw_ctx_id == -1, "Unexpected context id");
if (ndev->force_preempt_enabled) {
ret = aie2_runtime_cfg(ndev, AIE2_RT_CFG_FORCE_PREEMPT, &hwctx->fw_ctx_id);
if (ret) {
XDNA_ERR(xdna, "failed to enable force preempt %d", ret);
return ret;
}
}
cq_pair = &resp.cq_pair[0];
x2i.mb_head_ptr_reg = AIE2_MBOX_OFF(ndev, cq_pair->x2i_q.head_addr);
x2i.mb_tail_ptr_reg = AIE2_MBOX_OFF(ndev, cq_pair->x2i_q.tail_addr);
@ -601,6 +609,11 @@ aie2_cmdlist_fill_dpu(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size)
return 0;
}
static int aie2_cmdlist_unsupp(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size)
{
return -EOPNOTSUPP;
}
static u32 aie2_get_chain_msg_op(u32 cmd_op)
{
switch (cmd_op) {
@ -621,6 +634,8 @@ static struct aie2_exec_msg_ops legacy_exec_message_ops = {
.init_chain_req = aie2_init_exec_chain_req,
.fill_cf_slot = aie2_cmdlist_fill_cf,
.fill_dpu_slot = aie2_cmdlist_fill_dpu,
.fill_preempt_slot = aie2_cmdlist_unsupp,
.fill_elf_slot = aie2_cmdlist_unsupp,
.get_chain_msg_op = aie2_get_chain_msg_op,
};
@ -680,6 +695,74 @@ aie2_cmdlist_fill_npu_dpu(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *si
return 0;
}
static int
aie2_cmdlist_fill_npu_preempt(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size)
{
struct cmd_chain_slot_npu *npu_slot = slot;
struct amdxdna_cmd_preempt_data *pd;
u32 cmd_len;
u32 arg_sz;
pd = amdxdna_cmd_get_payload(cmd_bo, &cmd_len);
arg_sz = cmd_len - sizeof(*pd);
if (cmd_len < sizeof(*pd) || arg_sz > MAX_NPU_ARGS_SIZE)
return -EINVAL;
if (*size < sizeof(*npu_slot) + arg_sz)
return -EINVAL;
npu_slot->cu_idx = amdxdna_cmd_get_cu_idx(cmd_bo);
if (npu_slot->cu_idx == INVALID_CU_IDX)
return -EINVAL;
memset(npu_slot, 0, sizeof(*npu_slot));
npu_slot->type = EXEC_NPU_TYPE_PREEMPT;
npu_slot->inst_buf_addr = pd->inst_buf;
npu_slot->save_buf_addr = pd->save_buf;
npu_slot->restore_buf_addr = pd->restore_buf;
npu_slot->inst_size = pd->inst_size;
npu_slot->save_size = pd->save_size;
npu_slot->restore_size = pd->restore_size;
npu_slot->inst_prop_cnt = pd->inst_prop_cnt;
npu_slot->arg_cnt = arg_sz / sizeof(u32);
memcpy(npu_slot->args, pd->prop_args, arg_sz);
*size = sizeof(*npu_slot) + arg_sz;
return 0;
}
static int
aie2_cmdlist_fill_npu_elf(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size)
{
struct cmd_chain_slot_npu *npu_slot = slot;
struct amdxdna_cmd_preempt_data *pd;
u32 cmd_len;
u32 arg_sz;
pd = amdxdna_cmd_get_payload(cmd_bo, &cmd_len);
arg_sz = cmd_len - sizeof(*pd);
if (cmd_len < sizeof(*pd) || arg_sz > MAX_NPU_ARGS_SIZE)
return -EINVAL;
if (*size < sizeof(*npu_slot) + arg_sz)
return -EINVAL;
memset(npu_slot, 0, sizeof(*npu_slot));
npu_slot->type = EXEC_NPU_TYPE_ELF;
npu_slot->inst_buf_addr = pd->inst_buf;
npu_slot->save_buf_addr = pd->save_buf;
npu_slot->restore_buf_addr = pd->restore_buf;
npu_slot->inst_size = pd->inst_size;
npu_slot->save_size = pd->save_size;
npu_slot->restore_size = pd->restore_size;
npu_slot->inst_prop_cnt = pd->inst_prop_cnt;
npu_slot->arg_cnt = 1;
npu_slot->args[0] = AIE2_EXEC_BUFFER_KERNEL_OP_TXN;
*size = struct_size(npu_slot, args, npu_slot->arg_cnt);
return 0;
}
static u32 aie2_get_npu_chain_msg_op(u32 cmd_op)
{
return MSG_OP_CHAIN_EXEC_NPU;
@ -691,6 +774,8 @@ static struct aie2_exec_msg_ops npu_exec_message_ops = {
.init_chain_req = aie2_init_npu_chain_req,
.fill_cf_slot = aie2_cmdlist_fill_npu_cf,
.fill_dpu_slot = aie2_cmdlist_fill_npu_dpu,
.fill_preempt_slot = aie2_cmdlist_fill_npu_preempt,
.fill_elf_slot = aie2_cmdlist_fill_npu_elf,
.get_chain_msg_op = aie2_get_npu_chain_msg_op,
};
@ -749,6 +834,16 @@ aie2_cmdlist_fill_slot(void *slot, struct amdxdna_gem_obj *cmd_abo,
case ERT_START_NPU:
ret = EXEC_MSG_OPS(xdna)->fill_dpu_slot(cmd_abo, slot, size);
break;
case ERT_START_NPU_PREEMPT:
if (!AIE2_FEATURE_ON(xdna->dev_handle, AIE2_PREEMPT))
return -EOPNOTSUPP;
ret = EXEC_MSG_OPS(xdna)->fill_preempt_slot(cmd_abo, slot, size);
break;
case ERT_START_NPU_PREEMPT_ELF:
if (!AIE2_FEATURE_ON(xdna->dev_handle, AIE2_PREEMPT))
return -EOPNOTSUPP;
ret = EXEC_MSG_OPS(xdna)->fill_elf_slot(cmd_abo, slot, size);
break;
default:
XDNA_INFO(xdna, "Unsupported op %d", op);
ret = -EOPNOTSUPP;

View File

@ -176,6 +176,8 @@ struct exec_dpu_req {
enum exec_npu_type {
EXEC_NPU_TYPE_NON_ELF = 0x1,
EXEC_NPU_TYPE_PARTIAL_ELF = 0x2,
EXEC_NPU_TYPE_PREEMPT = 0x3,
EXEC_NPU_TYPE_ELF = 0x4,
};
union exec_req {
@ -372,6 +374,7 @@ struct cmd_chain_slot_dpu {
};
#define MAX_NPU_ARGS_SIZE (26 * sizeof(__u32))
#define AIE2_EXEC_BUFFER_KERNEL_OP_TXN 3
struct cmd_chain_slot_npu {
enum exec_npu_type type;
u64 inst_buf_addr;

View File

@ -183,6 +183,10 @@ int aie2_runtime_cfg(struct amdxdna_dev_hdl *ndev,
if (cfg->category != category)
continue;
if (cfg->feature_mask &&
bitmap_subset(&cfg->feature_mask, &ndev->feature_mask, AIE2_FEATURE_MAX))
continue;
value = val ? *val : cfg->value;
ret = aie2_set_runtime_cfg(ndev, cfg->type, value);
if (ret) {
@ -932,6 +936,25 @@ static int aie2_get_telemetry(struct amdxdna_client *client,
return 0;
}
static int aie2_get_preempt_state(struct amdxdna_client *client,
struct amdxdna_drm_get_info *args)
{
struct amdxdna_drm_attribute_state state = {};
struct amdxdna_dev *xdna = client->xdna;
struct amdxdna_dev_hdl *ndev;
ndev = xdna->dev_handle;
if (args->param == DRM_AMDXDNA_GET_FORCE_PREEMPT_STATE)
state.state = ndev->force_preempt_enabled;
else if (args->param == DRM_AMDXDNA_GET_FRAME_BOUNDARY_PREEMPT_STATE)
state.state = ndev->frame_boundary_preempt;
if (copy_to_user(u64_to_user_ptr(args->buffer), &state, sizeof(state)))
return -EFAULT;
return 0;
}
static int aie2_get_info(struct amdxdna_client *client, struct amdxdna_drm_get_info *args)
{
struct amdxdna_dev *xdna = client->xdna;
@ -972,6 +995,10 @@ static int aie2_get_info(struct amdxdna_client *client, struct amdxdna_drm_get_i
case DRM_AMDXDNA_QUERY_RESOURCE_INFO:
ret = aie2_query_resource_info(client, args);
break;
case DRM_AMDXDNA_GET_FORCE_PREEMPT_STATE:
case DRM_AMDXDNA_GET_FRAME_BOUNDARY_PREEMPT_STATE:
ret = aie2_get_preempt_state(client, args);
break;
default:
XDNA_ERR(xdna, "Not supported request parameter %u", args->param);
ret = -EOPNOTSUPP;
@ -1078,6 +1105,38 @@ static int aie2_set_power_mode(struct amdxdna_client *client,
return aie2_pm_set_mode(xdna->dev_handle, power_mode);
}
static int aie2_set_preempt_state(struct amdxdna_client *client,
struct amdxdna_drm_set_state *args)
{
struct amdxdna_dev_hdl *ndev = client->xdna->dev_handle;
struct amdxdna_drm_attribute_state state;
u32 val;
int ret;
if (copy_from_user(&state, u64_to_user_ptr(args->buffer), sizeof(state)))
return -EFAULT;
if (state.state > 1)
return -EINVAL;
if (XDNA_MBZ_DBG(client->xdna, state.pad, sizeof(state.pad)))
return -EINVAL;
if (args->param == DRM_AMDXDNA_SET_FORCE_PREEMPT) {
ndev->force_preempt_enabled = state.state;
} else if (args->param == DRM_AMDXDNA_SET_FRAME_BOUNDARY_PREEMPT) {
val = state.state;
ret = aie2_runtime_cfg(ndev, AIE2_RT_CFG_FRAME_BOUNDARY_PREEMPT,
&val);
if (ret)
return ret;
ndev->frame_boundary_preempt = state.state;
}
return 0;
}
static int aie2_set_state(struct amdxdna_client *client,
struct amdxdna_drm_set_state *args)
{
@ -1095,6 +1154,10 @@ static int aie2_set_state(struct amdxdna_client *client,
case DRM_AMDXDNA_SET_POWER_MODE:
ret = aie2_set_power_mode(client, args);
break;
case DRM_AMDXDNA_SET_FORCE_PREEMPT:
case DRM_AMDXDNA_SET_FRAME_BOUNDARY_PREEMPT:
ret = aie2_set_preempt_state(client, args);
break;
default:
XDNA_ERR(xdna, "Not supported request parameter %u", args->param);
ret = -EOPNOTSUPP;

View File

@ -110,12 +110,15 @@ struct aie_metadata {
enum rt_config_category {
AIE2_RT_CFG_INIT,
AIE2_RT_CFG_CLK_GATING,
AIE2_RT_CFG_FORCE_PREEMPT,
AIE2_RT_CFG_FRAME_BOUNDARY_PREEMPT,
};
struct rt_config {
u32 type;
u32 value;
u32 category;
unsigned long feature_mask;
};
struct dpm_clk_freq {
@ -164,6 +167,8 @@ struct aie2_exec_msg_ops {
void (*init_chain_req)(void *req, u64 slot_addr, size_t size, u32 cmd_cnt);
int (*fill_cf_slot)(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size);
int (*fill_dpu_slot)(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size);
int (*fill_preempt_slot)(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size);
int (*fill_elf_slot)(struct amdxdna_gem_obj *cmd_bo, void *slot, size_t *size);
u32 (*get_chain_msg_op)(u32 cmd_op);
};
@ -197,6 +202,8 @@ struct amdxdna_dev_hdl {
u32 hclk_freq;
u32 max_tops;
u32 curr_tops;
u32 force_preempt_enabled;
u32 frame_boundary_preempt;
/* Mailbox and the management channel */
struct mailbox *mbox;
@ -223,6 +230,7 @@ struct aie2_hw_ops {
enum aie2_fw_feature {
AIE2_NPU_COMMAND,
AIE2_PREEMPT,
AIE2_FEATURE_MAX
};

View File

@ -147,6 +147,16 @@ int aie2_smu_init(struct amdxdna_dev_hdl *ndev)
{
int ret;
/*
* Failing to set power off indicates an unrecoverable hardware or
* firmware error.
*/
ret = aie2_smu_exec(ndev, AIE2_SMU_POWER_OFF, 0, NULL);
if (ret) {
XDNA_ERR(ndev->xdna, "Access power failed, ret %d", ret);
return ret;
}
ret = aie2_smu_exec(ndev, AIE2_SMU_POWER_ON, 0, NULL);
if (ret) {
XDNA_ERR(ndev->xdna, "Power on failed, ret %d", ret);

View File

@ -422,6 +422,7 @@ void amdxdna_sched_job_cleanup(struct amdxdna_sched_job *job)
trace_amdxdna_debug_point(job->hwctx->name, job->seq, "job release");
amdxdna_arg_bos_put(job);
amdxdna_gem_put_obj(job->cmd_bo);
dma_fence_put(job->fence);
}
int amdxdna_cmd_submit(struct amdxdna_client *client,

View File

@ -16,6 +16,8 @@ enum ert_cmd_opcode {
ERT_START_CU = 0,
ERT_CMD_CHAIN = 19,
ERT_START_NPU = 20,
ERT_START_NPU_PREEMPT = 21,
ERT_START_NPU_PREEMPT_ELF = 22,
ERT_INVALID_CMD = ~0U,
};
@ -55,6 +57,21 @@ struct amdxdna_cmd_chain {
u64 data[] __counted_by(command_count);
};
/*
* Interpretation of the beginning of data payload for ERT_START_NPU_PREEMPT in
* amdxdna_cmd. The rest of the payload in amdxdna_cmd is regular kernel args.
*/
struct amdxdna_cmd_preempt_data {
u64 inst_buf; /* instruction buffer address */
u64 save_buf; /* save buffer address */
u64 restore_buf; /* restore buffer address */
u32 inst_size; /* size of instruction buffer in bytes */
u32 save_size; /* size of save buffer in bytes */
u32 restore_size; /* size of restore buffer in bytes */
u32 inst_prop_cnt; /* properties count */
u32 prop_args[]; /* properties and regular kernel arguments */
};
/* Exec buffer command header format */
#define AMDXDNA_CMD_STATE GENMASK(3, 0)
#define AMDXDNA_CMD_EXTRA_CU_MASK GENMASK(11, 10)

View File

@ -516,6 +516,7 @@ xdna_mailbox_create_channel(struct mailbox *mb,
}
mb_chann->bad_state = false;
mailbox_reg_write(mb_chann, mb_chann->iohub_int_addr, 0);
MB_DBG(mb_chann, "Mailbox channel created (irq: %d)", mb_chann->msix_irq);
return mb_chann;

View File

@ -31,9 +31,10 @@ MODULE_FIRMWARE("amdnpu/17f0_20/npu.sbin");
* 0.3: Support firmware debug buffer
* 0.4: Support getting resource information
* 0.5: Support getting telemetry data
* 0.6: Support preemption
*/
#define AMDXDNA_DRIVER_MAJOR 0
#define AMDXDNA_DRIVER_MINOR 5
#define AMDXDNA_DRIVER_MINOR 6
/*
* Bind the driver base on (vendor_id, device_id) pair and later use the

View File

@ -64,10 +64,13 @@
const struct rt_config npu4_default_rt_cfg[] = {
{ 5, 1, AIE2_RT_CFG_INIT }, /* PDI APP LOAD MODE */
{ 10, 1, AIE2_RT_CFG_INIT }, /* DEBUG BUF */
{ 14, 0, AIE2_RT_CFG_INIT, BIT_U64(AIE2_PREEMPT) }, /* Frame boundary preemption */
{ 1, 1, AIE2_RT_CFG_CLK_GATING }, /* Clock gating on */
{ 2, 1, AIE2_RT_CFG_CLK_GATING }, /* Clock gating on */
{ 3, 1, AIE2_RT_CFG_CLK_GATING }, /* Clock gating on */
{ 4, 1, AIE2_RT_CFG_CLK_GATING }, /* Clock gating on */
{ 13, 0, AIE2_RT_CFG_FORCE_PREEMPT },
{ 14, 0, AIE2_RT_CFG_FRAME_BOUNDARY_PREEMPT },
{ 0 },
};
@ -85,6 +88,7 @@ const struct dpm_clk_freq npu4_dpm_clk_table[] = {
const struct aie2_fw_feature_tbl npu4_fw_feature_table[] = {
{ .feature = AIE2_NPU_COMMAND, .min_minor = 15 },
{ .feature = AIE2_PREEMPT, .min_minor = 12 },
{ 0 }
};

View File

@ -455,6 +455,9 @@ int ivpu_shutdown(struct ivpu_device *vdev)
static const struct file_operations ivpu_fops = {
.owner = THIS_MODULE,
DRM_ACCEL_FOPS,
#ifdef CONFIG_PROC_FS
.show_fdinfo = drm_show_fdinfo,
#endif
};
static const struct drm_driver driver = {
@ -469,6 +472,9 @@ static const struct drm_driver driver = {
.ioctls = ivpu_drm_ioctls,
.num_ioctls = ARRAY_SIZE(ivpu_drm_ioctls),
.fops = &ivpu_fops,
#ifdef CONFIG_PROC_FS
.show_fdinfo = drm_show_memory_stats,
#endif
.name = DRIVER_NAME,
.desc = DRIVER_DESC,

View File

@ -333,6 +333,17 @@ static void ivpu_gem_bo_free(struct drm_gem_object *obj)
drm_gem_shmem_free(&bo->base);
}
static enum drm_gem_object_status ivpu_gem_status(struct drm_gem_object *obj)
{
struct ivpu_bo *bo = to_ivpu_bo(obj);
enum drm_gem_object_status status = 0;
if (ivpu_bo_is_resident(bo))
status |= DRM_GEM_OBJECT_RESIDENT;
return status;
}
static const struct drm_gem_object_funcs ivpu_gem_funcs = {
.free = ivpu_gem_bo_free,
.open = ivpu_gem_bo_open,
@ -343,6 +354,7 @@ static const struct drm_gem_object_funcs ivpu_gem_funcs = {
.vmap = drm_gem_shmem_object_vmap,
.vunmap = drm_gem_shmem_object_vunmap,
.mmap = drm_gem_shmem_object_mmap,
.status = ivpu_gem_status,
.vm_ops = &drm_gem_shmem_vm_ops,
};

View File

@ -82,6 +82,11 @@ static inline bool ivpu_bo_is_read_only(struct ivpu_bo *bo)
return bo->flags & DRM_IVPU_BO_READ_ONLY;
}
static inline bool ivpu_bo_is_resident(struct ivpu_bo *bo)
{
return !!bo->base.pages;
}
static inline void *ivpu_to_cpu_addr(struct ivpu_bo *bo, u32 vpu_addr)
{
if (vpu_addr < bo->vpu_addr)

View File

@ -63,7 +63,8 @@ npu_memory_utilization_show(struct device *dev, struct device_attribute *attr, c
mutex_lock(&vdev->bo_list_lock);
list_for_each_entry(bo, &vdev->bo_list, bo_list_node)
total_npu_memory += bo->base.base.size;
if (ivpu_bo_is_resident(bo))
total_npu_memory += ivpu_bo_size(bo);
mutex_unlock(&vdev->bo_list_lock);
return sysfs_emit(buf, "%lld\n", total_npu_memory);

View File

@ -9,6 +9,7 @@ config DRM_ACCEL_QAIC
depends on PCI && HAS_IOMEM
depends on MHI_BUS
select CRC32
select WANT_DEV_COREDUMP
help
Enables driver for Qualcomm's Cloud AI accelerator PCIe cards that are
designed to accelerate Deep Learning inference workloads.

View File

@ -11,6 +11,8 @@ qaic-y := \
qaic_data.o \
qaic_drv.o \
qaic_ras.o \
qaic_ssr.o \
qaic_sysfs.o \
qaic_timesync.o \
sahara.o

View File

@ -21,6 +21,7 @@
#define QAIC_DBC_BASE SZ_128K
#define QAIC_DBC_SIZE SZ_4K
#define QAIC_SSR_DBC_SENTINEL U32_MAX /* No ongoing SSR sentinel */
#define QAIC_NO_PARTITION -1
@ -47,6 +48,22 @@ enum __packed dev_states {
QAIC_ONLINE,
};
enum dbc_states {
/* DBC is free and can be activated */
DBC_STATE_IDLE,
/* DBC is activated and a workload is running on device */
DBC_STATE_ASSIGNED,
/* Sub-system associated with this workload has crashed and it will shutdown soon */
DBC_STATE_BEFORE_SHUTDOWN,
/* Sub-system associated with this workload has crashed and it has shutdown */
DBC_STATE_AFTER_SHUTDOWN,
/* Sub-system associated with this workload is shutdown and it will be powered up soon */
DBC_STATE_BEFORE_POWER_UP,
/* Sub-system associated with this workload is now powered up */
DBC_STATE_AFTER_POWER_UP,
DBC_STATE_MAX,
};
extern bool datapath_polling;
struct qaic_user {
@ -114,6 +131,8 @@ struct dma_bridge_chan {
unsigned int irq;
/* Polling work item to simulate interrupts */
struct work_struct poll_work;
/* Represents various states of this DBC from enum dbc_states */
unsigned int state;
};
struct qaic_device {
@ -161,6 +180,8 @@ struct qaic_device {
struct mhi_device *qts_ch;
/* Work queue for tasks related to MHI "QAIC_TIMESYNC" channel */
struct workqueue_struct *qts_wq;
/* MHI "QAIC_TIMESYNC_PERIODIC" channel device */
struct mhi_device *mqts_ch;
/* Head of list of page allocated by MHI bootlog device */
struct list_head bootlog;
/* MHI bootlog channel device */
@ -177,6 +198,14 @@ struct qaic_device {
unsigned int ue_count;
/* Un-correctable non-fatal error count */
unsigned int ue_nf_count;
/* MHI SSR channel device */
struct mhi_device *ssr_ch;
/* Work queue for tasks related to MHI SSR device */
struct workqueue_struct *ssr_wq;
/* Buffer to collect SSR crashdump via SSR MHI channel */
void *ssr_mhi_buf;
/* DBC which is under SSR. Sentinel U32_MAX would mean that no SSR in progress */
u32 ssr_dbc;
};
struct qaic_drm_device {
@ -195,6 +224,8 @@ struct qaic_drm_device {
struct list_head users;
/* Synchronizes access to users list */
struct mutex users_mutex;
/* Pointer to array of DBC sysfs attributes */
void *sysfs_attrs;
};
struct qaic_bo {
@ -317,6 +348,13 @@ int qaic_partial_execute_bo_ioctl(struct drm_device *dev, void *data, struct drm
int qaic_wait_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qaic_perf_stats_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qaic_detach_slice_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv);
void irq_polling_work(struct work_struct *work);
void qaic_irq_polling_work(struct work_struct *work);
void qaic_dbc_enter_ssr(struct qaic_device *qdev, u32 dbc_id);
void qaic_dbc_exit_ssr(struct qaic_device *qdev);
/* qaic_sysfs.c */
int qaic_sysfs_init(struct qaic_drm_device *qddev);
void qaic_sysfs_remove(struct qaic_drm_device *qddev);
void set_dbc_state(struct qaic_device *qdev, u32 dbc_id, unsigned int state);
#endif /* _QAIC_H_ */

View File

@ -310,6 +310,7 @@ static void save_dbc_buf(struct qaic_device *qdev, struct ioctl_resources *resou
enable_dbc(qdev, dbc_id, usr);
qdev->dbc[dbc_id].in_use = true;
resources->buf = NULL;
set_dbc_state(qdev, dbc_id, DBC_STATE_ASSIGNED);
}
}
@ -923,6 +924,7 @@ static int decode_deactivate(struct qaic_device *qdev, void *trans, u32 *msg_len
}
release_dbc(qdev, dbc_id);
set_dbc_state(qdev, dbc_id, DBC_STATE_IDLE);
*msg_len += sizeof(*in_trans);
return 0;

View File

@ -1047,6 +1047,11 @@ int qaic_attach_slice_bo_ioctl(struct drm_device *dev, void *data, struct drm_fi
goto unlock_ch_srcu;
}
if (dbc->id == qdev->ssr_dbc) {
ret = -EPIPE;
goto unlock_ch_srcu;
}
ret = qaic_prepare_bo(qdev, bo, &args->hdr);
if (ret)
goto unlock_ch_srcu;
@ -1370,6 +1375,11 @@ static int __qaic_execute_bo_ioctl(struct drm_device *dev, void *data, struct dr
goto release_ch_rcu;
}
if (dbc->id == qdev->ssr_dbc) {
ret = -EPIPE;
goto release_ch_rcu;
}
ret = mutex_lock_interruptible(&dbc->req_lock);
if (ret)
goto release_ch_rcu;
@ -1504,7 +1514,7 @@ irqreturn_t dbc_irq_handler(int irq, void *data)
return IRQ_WAKE_THREAD;
}
void irq_polling_work(struct work_struct *work)
void qaic_irq_polling_work(struct work_struct *work)
{
struct dma_bridge_chan *dbc = container_of(work, struct dma_bridge_chan, poll_work);
unsigned long flags;
@ -1722,6 +1732,11 @@ int qaic_wait_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file
goto unlock_ch_srcu;
}
if (dbc->id == qdev->ssr_dbc) {
ret = -EPIPE;
goto unlock_ch_srcu;
}
obj = drm_gem_object_lookup(file_priv, args->handle);
if (!obj) {
ret = -ENOENT;
@ -1742,6 +1757,9 @@ int qaic_wait_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file
if (!dbc->usr)
ret = -EPERM;
if (dbc->id == qdev->ssr_dbc)
ret = -EPIPE;
put_obj:
drm_gem_object_put(obj);
unlock_ch_srcu:
@ -1945,6 +1963,17 @@ static void empty_xfer_list(struct qaic_device *qdev, struct dma_bridge_chan *db
spin_unlock_irqrestore(&dbc->xfer_lock, flags);
}
static void sync_empty_xfer_list(struct qaic_device *qdev, struct dma_bridge_chan *dbc)
{
empty_xfer_list(qdev, dbc);
synchronize_srcu(&dbc->ch_lock);
/*
* Threads holding channel lock, may add more elements in the xfer_list.
* Flush out these elements from xfer_list.
*/
empty_xfer_list(qdev, dbc);
}
int disable_dbc(struct qaic_device *qdev, u32 dbc_id, struct qaic_user *usr)
{
if (!qdev->dbc[dbc_id].usr || qdev->dbc[dbc_id].usr->handle != usr->handle)
@ -1973,13 +2002,7 @@ void wakeup_dbc(struct qaic_device *qdev, u32 dbc_id)
struct dma_bridge_chan *dbc = &qdev->dbc[dbc_id];
dbc->usr = NULL;
empty_xfer_list(qdev, dbc);
synchronize_srcu(&dbc->ch_lock);
/*
* Threads holding channel lock, may add more elements in the xfer_list.
* Flush out these elements from xfer_list.
*/
empty_xfer_list(qdev, dbc);
sync_empty_xfer_list(qdev, dbc);
}
void release_dbc(struct qaic_device *qdev, u32 dbc_id)
@ -2020,3 +2043,30 @@ void qaic_data_get_fifo_info(struct dma_bridge_chan *dbc, u32 *head, u32 *tail)
*head = readl(dbc->dbc_base + REQHP_OFF);
*tail = readl(dbc->dbc_base + REQTP_OFF);
}
/*
* qaic_dbc_enter_ssr - Prepare to enter in sub system reset(SSR) for given DBC ID.
* @qdev: qaic device handle
* @dbc_id: ID of the DBC which will enter SSR
*
* The device will automatically deactivate the workload as not
* all errors can be silently recovered. The user will be
* notified and will need to decide the required recovery
* action to take.
*/
void qaic_dbc_enter_ssr(struct qaic_device *qdev, u32 dbc_id)
{
qdev->ssr_dbc = dbc_id;
release_dbc(qdev, dbc_id);
}
/*
* qaic_dbc_exit_ssr - Prepare to exit from sub system reset(SSR) for given DBC ID.
* @qdev: qaic device handle
*
* The DBC returns to an operational state and begins accepting work after exiting SSR.
*/
void qaic_dbc_exit_ssr(struct qaic_device *qdev)
{
qdev->ssr_dbc = QAIC_SSR_DBC_SENTINEL;
}

View File

@ -30,6 +30,7 @@
#include "qaic.h"
#include "qaic_debugfs.h"
#include "qaic_ras.h"
#include "qaic_ssr.h"
#include "qaic_timesync.h"
#include "sahara.h"
@ -270,6 +271,13 @@ static int qaic_create_drm_device(struct qaic_device *qdev, s32 partition_id)
return ret;
}
ret = qaic_sysfs_init(qddev);
if (ret) {
drm_dev_unregister(drm);
pci_dbg(qdev->pdev, "qaic_sysfs_init failed %d\n", ret);
return ret;
}
qaic_debugfs_init(qddev);
return ret;
@ -281,6 +289,7 @@ static void qaic_destroy_drm_device(struct qaic_device *qdev, s32 partition_id)
struct drm_device *drm = to_drm(qddev);
struct qaic_user *usr;
qaic_sysfs_remove(qddev);
drm_dev_unregister(drm);
qddev->partition_id = 0;
/*
@ -382,6 +391,7 @@ void qaic_dev_reset_clean_local_state(struct qaic_device *qdev)
qaic_notify_reset(qdev);
/* start tearing things down */
qaic_clean_up_ssr(qdev);
for (i = 0; i < qdev->num_dbc; ++i)
release_dbc(qdev, i);
}
@ -431,11 +441,18 @@ static struct qaic_device *create_qdev(struct pci_dev *pdev,
qdev->qts_wq = qaicm_wq_init(drm, "qaic_ts");
if (IS_ERR(qdev->qts_wq))
return NULL;
qdev->ssr_wq = qaicm_wq_init(drm, "qaic_ssr");
if (IS_ERR(qdev->ssr_wq))
return NULL;
ret = qaicm_srcu_init(drm, &qdev->dev_lock);
if (ret)
return NULL;
ret = qaic_ssr_init(qdev, drm);
if (ret)
pci_info(pdev, "QAIC SSR crashdump collection not supported.\n");
qdev->qddev = qddev;
qdev->pdev = pdev;
qddev->qdev = qdev;
@ -545,7 +562,7 @@ static int init_msi(struct qaic_device *qdev, struct pci_dev *pdev)
qdev->dbc[i].irq = pci_irq_vector(pdev, qdev->single_msi ? 0 : i + 1);
if (!qdev->single_msi)
disable_irq_nosync(qdev->dbc[i].irq);
INIT_WORK(&qdev->dbc[i].poll_work, irq_polling_work);
INIT_WORK(&qdev->dbc[i].poll_work, qaic_irq_polling_work);
}
}
@ -660,6 +677,92 @@ static const struct pci_error_handlers qaic_pci_err_handler = {
.reset_done = qaic_pci_reset_done,
};
static bool qaic_is_under_reset(struct qaic_device *qdev)
{
int rcu_id;
bool ret;
rcu_id = srcu_read_lock(&qdev->dev_lock);
ret = qdev->dev_state != QAIC_ONLINE;
srcu_read_unlock(&qdev->dev_lock, rcu_id);
return ret;
}
static bool qaic_data_path_busy(struct qaic_device *qdev)
{
bool ret = false;
int dev_rcu_id;
int i;
dev_rcu_id = srcu_read_lock(&qdev->dev_lock);
if (qdev->dev_state != QAIC_ONLINE) {
srcu_read_unlock(&qdev->dev_lock, dev_rcu_id);
return false;
}
for (i = 0; i < qdev->num_dbc; i++) {
struct dma_bridge_chan *dbc = &qdev->dbc[i];
unsigned long flags;
int ch_rcu_id;
ch_rcu_id = srcu_read_lock(&dbc->ch_lock);
if (!dbc->usr || !dbc->in_use) {
srcu_read_unlock(&dbc->ch_lock, ch_rcu_id);
continue;
}
spin_lock_irqsave(&dbc->xfer_lock, flags);
ret = !list_empty(&dbc->xfer_list);
spin_unlock_irqrestore(&dbc->xfer_lock, flags);
srcu_read_unlock(&dbc->ch_lock, ch_rcu_id);
if (ret)
break;
}
srcu_read_unlock(&qdev->dev_lock, dev_rcu_id);
return ret;
}
static int qaic_pm_suspend(struct device *dev)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(dev));
dev_dbg(dev, "Suspending..\n");
if (qaic_data_path_busy(qdev)) {
dev_dbg(dev, "Device's datapath is busy. Aborting suspend..\n");
return -EBUSY;
}
if (qaic_is_under_reset(qdev)) {
dev_dbg(dev, "Device is under reset. Aborting suspend..\n");
return -EBUSY;
}
qaic_mqts_ch_stop_timer(qdev->mqts_ch);
qaic_pci_reset_prepare(qdev->pdev);
pci_save_state(qdev->pdev);
pci_disable_device(qdev->pdev);
pci_set_power_state(qdev->pdev, PCI_D3hot);
return 0;
}
static int qaic_pm_resume(struct device *dev)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(dev));
int ret;
dev_dbg(dev, "Resuming..\n");
pci_set_power_state(qdev->pdev, PCI_D0);
pci_restore_state(qdev->pdev);
ret = pci_enable_device(qdev->pdev);
if (ret) {
dev_err(dev, "pci_enable_device failed on resume %d\n", ret);
return ret;
}
pci_set_master(qdev->pdev);
qaic_pci_reset_done(qdev->pdev);
return 0;
}
static const struct dev_pm_ops qaic_pm_ops = {
SYSTEM_SLEEP_PM_OPS(qaic_pm_suspend, qaic_pm_resume)
};
static struct pci_driver qaic_pci_driver = {
.name = QAIC_NAME,
.id_table = qaic_ids,
@ -667,6 +770,9 @@ static struct pci_driver qaic_pci_driver = {
.remove = qaic_pci_remove,
.shutdown = qaic_pci_shutdown,
.err_handler = &qaic_pci_err_handler,
.driver = {
.pm = pm_sleep_ptr(&qaic_pm_ops),
},
};
static int __init qaic_init(void)
@ -702,9 +808,16 @@ static int __init qaic_init(void)
ret = qaic_ras_register();
if (ret)
pr_debug("qaic: qaic_ras_register failed %d\n", ret);
ret = qaic_ssr_register();
if (ret) {
pr_debug("qaic: qaic_ssr_register failed %d\n", ret);
goto free_bootlog;
}
return 0;
free_bootlog:
qaic_bootlog_unregister();
free_mhi:
mhi_driver_unregister(&qaic_mhi_driver);
free_pci:
@ -730,6 +843,7 @@ static void __exit qaic_exit(void)
* reinitializing the link_up state after the cleanup is done.
*/
link_up = true;
qaic_ssr_unregister();
qaic_ras_unregister();
qaic_bootlog_unregister();
qaic_timesync_deinit();

View File

@ -0,0 +1,815 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */
/* Copyright (c) 2021-2024 Qualcomm Innovation Center, Inc. All rights reserved. */
#include <asm/byteorder.h>
#include <drm/drm_file.h>
#include <drm/drm_managed.h>
#include <linux/devcoredump.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mhi.h>
#include <linux/workqueue.h>
#include "qaic.h"
#include "qaic_ssr.h"
#define SSR_RESP_MSG_SZ 32
#define SSR_MHI_BUF_SIZE SZ_64K
#define SSR_MEM_READ_DATA_SIZE ((u64)SSR_MHI_BUF_SIZE - sizeof(struct ssr_crashdump))
#define SSR_MEM_READ_CHUNK_SIZE ((u64)SSR_MEM_READ_DATA_SIZE - sizeof(struct ssr_memory_read_rsp))
#define DEBUG_TRANSFER_INFO BIT(0)
#define DEBUG_TRANSFER_INFO_RSP BIT(1)
#define MEMORY_READ BIT(2)
#define MEMORY_READ_RSP BIT(3)
#define DEBUG_TRANSFER_DONE BIT(4)
#define DEBUG_TRANSFER_DONE_RSP BIT(5)
#define SSR_EVENT BIT(8)
#define SSR_EVENT_RSP BIT(9)
#define SSR_EVENT_NACK BIT(0)
#define BEFORE_SHUTDOWN BIT(1)
#define AFTER_SHUTDOWN BIT(2)
#define BEFORE_POWER_UP BIT(3)
#define AFTER_POWER_UP BIT(4)
struct debug_info_table {
/* Save preferences. Default is mandatory */
u64 save_perf;
/* Base address of the debug region */
u64 mem_base;
/* Size of debug region in bytes */
u64 len;
/* Description */
char desc[20];
/* Filename of debug region */
char filename[20];
};
struct _ssr_hdr {
__le32 cmd;
__le32 len;
__le32 dbc_id;
};
struct ssr_hdr {
u32 cmd;
u32 len;
u32 dbc_id;
};
struct ssr_debug_transfer_info {
struct ssr_hdr hdr;
u32 resv;
u64 tbl_addr;
u64 tbl_len;
} __packed;
struct ssr_debug_transfer_info_rsp {
struct _ssr_hdr hdr;
__le32 ret;
} __packed;
struct ssr_memory_read {
struct _ssr_hdr hdr;
__le32 resv;
__le64 addr;
__le64 len;
} __packed;
struct ssr_memory_read_rsp {
struct _ssr_hdr hdr;
__le32 resv;
u8 data[];
} __packed;
struct ssr_debug_transfer_done {
struct _ssr_hdr hdr;
__le32 resv;
} __packed;
struct ssr_debug_transfer_done_rsp {
struct _ssr_hdr hdr;
__le32 ret;
} __packed;
struct ssr_event {
struct ssr_hdr hdr;
u32 event;
} __packed;
struct ssr_event_rsp {
struct _ssr_hdr hdr;
__le32 event;
} __packed;
struct ssr_resp {
/* Work struct to schedule work coming on QAIC_SSR channel */
struct work_struct work;
/* Root struct of device, used to access device resources */
struct qaic_device *qdev;
/* Buffer used by MHI for transfer requests */
u8 data[] __aligned(8);
};
/* SSR crashdump book keeping structure */
struct ssr_dump_info {
/* DBC associated with this SSR crashdump */
struct dma_bridge_chan *dbc;
/*
* It will be used when we complete the crashdump download and switch
* to waiting on SSR events
*/
struct ssr_resp *resp;
/* MEMORY READ request MHI buffer.*/
struct ssr_memory_read *read_buf_req;
/* TRUE: ->read_buf_req is queued for MHI transaction. FALSE: Otherwise */
bool read_buf_req_queued;
/* Address of table in host */
void *tbl_addr;
/* Total size of table */
u64 tbl_len;
/* Offset of table(->tbl_addr) where the new chunk will be dumped */
u64 tbl_off;
/* Address of table in device/target */
u64 tbl_addr_dev;
/* Ptr to the entire dump */
void *dump_addr;
/* Entire crashdump size */
u64 dump_sz;
/* Offset of crashdump(->dump_addr) where the new chunk will be dumped */
u64 dump_off;
/* Points to the table entry we are currently downloading */
struct debug_info_table *tbl_ent;
/* Offset in the current table entry(->tbl_ent) for next chuck */
u64 tbl_ent_off;
};
struct ssr_crashdump {
/*
* Points to a book keeping struct maintained by MHI SSR device while
* downloading a SSR crashdump. It is NULL when crashdump downloading
* not in progress.
*/
struct ssr_dump_info *dump_info;
/* Work struct to schedule work coming on QAIC_SSR channel */
struct work_struct work;
/* Root struct of device, used to access device resources */
struct qaic_device *qdev;
/* Buffer used by MHI for transfer requests */
u8 data[];
};
#define QAIC_SSR_DUMP_V1_MAGIC 0x1234567890abcdef
#define QAIC_SSR_DUMP_V1_VER 1
struct dump_file_meta {
u64 magic;
u64 version;
u64 size; /* Total size of the entire dump */
u64 tbl_len; /* Length of the table in byte */
};
/*
* Layout of crashdump
* +------------------------------------------+
* | Crashdump Meta structure |
* | type: struct dump_file_meta |
* +------------------------------------------+
* | Crashdump Table |
* | type: array of struct debug_info_table |
* | |
* | |
* | |
* +------------------------------------------+
* | Crashdump |
* | |
* | |
* | |
* | |
* | |
* +------------------------------------------+
*/
static void free_ssr_dump_info(struct ssr_crashdump *ssr_crash)
{
struct ssr_dump_info *dump_info = ssr_crash->dump_info;
ssr_crash->dump_info = NULL;
if (!dump_info)
return;
if (!dump_info->read_buf_req_queued)
kfree(dump_info->read_buf_req);
vfree(dump_info->tbl_addr);
vfree(dump_info->dump_addr);
kfree(dump_info);
}
void qaic_clean_up_ssr(struct qaic_device *qdev)
{
struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf;
if (!ssr_crash)
return;
qaic_dbc_exit_ssr(qdev);
free_ssr_dump_info(ssr_crash);
}
static int alloc_dump(struct ssr_dump_info *dump_info)
{
struct debug_info_table *tbl_ent = dump_info->tbl_addr;
struct dump_file_meta *dump_meta;
u64 tbl_sz_lp = 0;
u64 dump_size = 0;
while (tbl_sz_lp < dump_info->tbl_len) {
le64_to_cpus(&tbl_ent->save_perf);
le64_to_cpus(&tbl_ent->mem_base);
le64_to_cpus(&tbl_ent->len);
if (tbl_ent->len == 0)
return -EINVAL;
dump_size += tbl_ent->len;
tbl_ent++;
tbl_sz_lp += sizeof(*tbl_ent);
}
dump_info->dump_sz = dump_size + dump_info->tbl_len + sizeof(*dump_meta);
dump_info->dump_addr = vzalloc(dump_info->dump_sz);
if (!dump_info->dump_addr)
return -ENOMEM;
/* Copy crashdump meta and table */
dump_meta = dump_info->dump_addr;
dump_meta->magic = QAIC_SSR_DUMP_V1_MAGIC;
dump_meta->version = QAIC_SSR_DUMP_V1_VER;
dump_meta->size = dump_info->dump_sz;
dump_meta->tbl_len = dump_info->tbl_len;
memcpy(dump_info->dump_addr + sizeof(*dump_meta), dump_info->tbl_addr, dump_info->tbl_len);
/* Offset by crashdump meta and table (copied above) */
dump_info->dump_off = dump_info->tbl_len + sizeof(*dump_meta);
return 0;
}
static int send_xfer_done(struct qaic_device *qdev, void *resp, u32 dbc_id)
{
struct ssr_debug_transfer_done *xfer_done;
int ret;
xfer_done = kmalloc(sizeof(*xfer_done), GFP_KERNEL);
if (!xfer_done) {
ret = -ENOMEM;
goto out;
}
ret = mhi_queue_buf(qdev->ssr_ch, DMA_FROM_DEVICE, resp, SSR_RESP_MSG_SZ, MHI_EOT);
if (ret)
goto free_xfer_done;
xfer_done->hdr.cmd = cpu_to_le32(DEBUG_TRANSFER_DONE);
xfer_done->hdr.len = cpu_to_le32(sizeof(*xfer_done));
xfer_done->hdr.dbc_id = cpu_to_le32(dbc_id);
ret = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, xfer_done, sizeof(*xfer_done), MHI_EOT);
if (ret)
goto free_xfer_done;
return 0;
free_xfer_done:
kfree(xfer_done);
out:
return ret;
}
static int mem_read_req(struct qaic_device *qdev, u64 dest_addr, u64 dest_len)
{
struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf;
struct ssr_memory_read *read_buf_req;
struct ssr_dump_info *dump_info;
int ret;
dump_info = ssr_crash->dump_info;
ret = mhi_queue_buf(qdev->ssr_ch, DMA_FROM_DEVICE, ssr_crash->data, SSR_MEM_READ_DATA_SIZE,
MHI_EOT);
if (ret)
goto out;
read_buf_req = dump_info->read_buf_req;
read_buf_req->hdr.cmd = cpu_to_le32(MEMORY_READ);
read_buf_req->hdr.len = cpu_to_le32(sizeof(*read_buf_req));
read_buf_req->hdr.dbc_id = cpu_to_le32(qdev->ssr_dbc);
read_buf_req->addr = cpu_to_le64(dest_addr);
read_buf_req->len = cpu_to_le64(dest_len);
ret = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, read_buf_req, sizeof(*read_buf_req),
MHI_EOT);
if (!ret)
dump_info->read_buf_req_queued = true;
out:
return ret;
}
static int ssr_copy_table(struct ssr_dump_info *dump_info, void *data, u64 len)
{
if (len > dump_info->tbl_len - dump_info->tbl_off)
return -EINVAL;
memcpy(dump_info->tbl_addr + dump_info->tbl_off, data, len);
dump_info->tbl_off += len;
/* Entire table has been downloaded, alloc dump memory */
if (dump_info->tbl_off == dump_info->tbl_len) {
dump_info->tbl_ent = dump_info->tbl_addr;
return alloc_dump(dump_info);
}
return 0;
}
static int ssr_copy_dump(struct ssr_dump_info *dump_info, void *data, u64 len)
{
struct debug_info_table *tbl_ent;
tbl_ent = dump_info->tbl_ent;
if (len > tbl_ent->len - dump_info->tbl_ent_off)
return -EINVAL;
memcpy(dump_info->dump_addr + dump_info->dump_off, data, len);
dump_info->dump_off += len;
dump_info->tbl_ent_off += len;
/*
* Current segment (a entry in table) of the crashdump is complete,
* move to next one
*/
if (tbl_ent->len == dump_info->tbl_ent_off) {
dump_info->tbl_ent++;
dump_info->tbl_ent_off = 0;
}
return 0;
}
static void ssr_dump_worker(struct work_struct *work)
{
struct ssr_crashdump *ssr_crash = container_of(work, struct ssr_crashdump, work);
struct qaic_device *qdev = ssr_crash->qdev;
struct ssr_memory_read_rsp *mem_rd_resp;
struct debug_info_table *tbl_ent;
struct ssr_dump_info *dump_info;
u64 dest_addr, dest_len;
struct _ssr_hdr *_hdr;
struct ssr_hdr hdr;
u64 data_len;
int ret;
mem_rd_resp = (struct ssr_memory_read_rsp *)ssr_crash->data;
_hdr = &mem_rd_resp->hdr;
hdr.cmd = le32_to_cpu(_hdr->cmd);
hdr.len = le32_to_cpu(_hdr->len);
hdr.dbc_id = le32_to_cpu(_hdr->dbc_id);
if (hdr.dbc_id != qdev->ssr_dbc)
goto reset_device;
dump_info = ssr_crash->dump_info;
if (!dump_info)
goto reset_device;
if (hdr.cmd != MEMORY_READ_RSP)
goto free_dump_info;
if (hdr.len > SSR_MEM_READ_DATA_SIZE)
goto free_dump_info;
data_len = hdr.len - sizeof(*mem_rd_resp);
if (dump_info->tbl_off < dump_info->tbl_len) /* Chunk belongs to table */
ret = ssr_copy_table(dump_info, mem_rd_resp->data, data_len);
else /* Chunk belongs to crashdump */
ret = ssr_copy_dump(dump_info, mem_rd_resp->data, data_len);
if (ret)
goto free_dump_info;
if (dump_info->tbl_off < dump_info->tbl_len) {
/* Continue downloading table */
dest_addr = dump_info->tbl_addr_dev + dump_info->tbl_off;
dest_len = min(SSR_MEM_READ_CHUNK_SIZE, dump_info->tbl_len - dump_info->tbl_off);
ret = mem_read_req(qdev, dest_addr, dest_len);
} else if (dump_info->dump_off < dump_info->dump_sz) {
/* Continue downloading crashdump */
tbl_ent = dump_info->tbl_ent;
dest_addr = tbl_ent->mem_base + dump_info->tbl_ent_off;
dest_len = min(SSR_MEM_READ_CHUNK_SIZE, tbl_ent->len - dump_info->tbl_ent_off);
ret = mem_read_req(qdev, dest_addr, dest_len);
} else {
/* Crashdump download complete */
ret = send_xfer_done(qdev, dump_info->resp->data, hdr.dbc_id);
}
/* Most likely a MHI xfer has failed */
if (ret)
goto free_dump_info;
return;
free_dump_info:
/* Free the allocated memory */
free_ssr_dump_info(ssr_crash);
reset_device:
/*
* After subsystem crashes in device crashdump collection begins but
* something went wrong while collecting crashdump, now instead of
* handling this error we just reset the device as the best effort has
* been made
*/
mhi_soc_reset(qdev->mhi_cntrl);
}
static struct ssr_dump_info *alloc_dump_info(struct qaic_device *qdev,
struct ssr_debug_transfer_info *debug_info)
{
struct ssr_dump_info *dump_info;
int ret;
le64_to_cpus(&debug_info->tbl_len);
le64_to_cpus(&debug_info->tbl_addr);
if (debug_info->tbl_len == 0 ||
debug_info->tbl_len % sizeof(struct debug_info_table) != 0) {
ret = -EINVAL;
goto out;
}
/* Allocate SSR crashdump book keeping structure */
dump_info = kzalloc(sizeof(*dump_info), GFP_KERNEL);
if (!dump_info) {
ret = -ENOMEM;
goto out;
}
/* Buffer used to send MEMORY READ request to device via MHI */
dump_info->read_buf_req = kzalloc(sizeof(*dump_info->read_buf_req), GFP_KERNEL);
if (!dump_info->read_buf_req) {
ret = -ENOMEM;
goto free_dump_info;
}
/* Crashdump meta table buffer */
dump_info->tbl_addr = vzalloc(debug_info->tbl_len);
if (!dump_info->tbl_addr) {
ret = -ENOMEM;
goto free_read_buf_req;
}
dump_info->tbl_addr_dev = debug_info->tbl_addr;
dump_info->tbl_len = debug_info->tbl_len;
return dump_info;
free_read_buf_req:
kfree(dump_info->read_buf_req);
free_dump_info:
kfree(dump_info);
out:
return ERR_PTR(ret);
}
static int dbg_xfer_info_rsp(struct qaic_device *qdev, struct dma_bridge_chan *dbc,
struct ssr_debug_transfer_info *debug_info)
{
struct ssr_debug_transfer_info_rsp *debug_rsp;
struct ssr_crashdump *ssr_crash = NULL;
int ret = 0, ret2;
debug_rsp = kmalloc(sizeof(*debug_rsp), GFP_KERNEL);
if (!debug_rsp)
return -ENOMEM;
if (!qdev->ssr_mhi_buf) {
ret = -ENOMEM;
goto send_rsp;
}
if (dbc->state != DBC_STATE_BEFORE_POWER_UP) {
ret = -EINVAL;
goto send_rsp;
}
ssr_crash = qdev->ssr_mhi_buf;
ssr_crash->dump_info = alloc_dump_info(qdev, debug_info);
if (IS_ERR(ssr_crash->dump_info)) {
ret = PTR_ERR(ssr_crash->dump_info);
ssr_crash->dump_info = NULL;
}
send_rsp:
debug_rsp->hdr.cmd = cpu_to_le32(DEBUG_TRANSFER_INFO_RSP);
debug_rsp->hdr.len = cpu_to_le32(sizeof(*debug_rsp));
debug_rsp->hdr.dbc_id = cpu_to_le32(dbc->id);
/*
* 0 = Return an ACK confirming the host is ready to download crashdump
* 1 = Return an NACK confirming the host is not ready to download crashdump
*/
debug_rsp->ret = cpu_to_le32(ret ? 1 : 0);
ret2 = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, debug_rsp, sizeof(*debug_rsp), MHI_EOT);
if (ret2) {
free_ssr_dump_info(ssr_crash);
kfree(debug_rsp);
return ret2;
}
return ret;
}
static void dbg_xfer_done_rsp(struct qaic_device *qdev, struct dma_bridge_chan *dbc,
struct ssr_debug_transfer_done_rsp *xfer_rsp)
{
struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf;
u32 status = le32_to_cpu(xfer_rsp->ret);
struct device *dev = &qdev->pdev->dev;
struct ssr_dump_info *dump_info;
dump_info = ssr_crash->dump_info;
if (!dump_info)
return;
if (status) {
free_ssr_dump_info(ssr_crash);
return;
}
dev_coredumpv(dev, dump_info->dump_addr, dump_info->dump_sz, GFP_KERNEL);
/* dev_coredumpv will free dump_info->dump_addr */
dump_info->dump_addr = NULL;
free_ssr_dump_info(ssr_crash);
}
static void ssr_worker(struct work_struct *work)
{
struct ssr_resp *resp = container_of(work, struct ssr_resp, work);
struct ssr_hdr *hdr = (struct ssr_hdr *)resp->data;
struct ssr_dump_info *dump_info = NULL;
struct qaic_device *qdev = resp->qdev;
struct ssr_crashdump *ssr_crash;
struct ssr_event_rsp *event_rsp;
struct dma_bridge_chan *dbc;
struct ssr_event *event;
u32 ssr_event_ack;
int ret;
le32_to_cpus(&hdr->cmd);
le32_to_cpus(&hdr->len);
le32_to_cpus(&hdr->dbc_id);
if (hdr->len > SSR_RESP_MSG_SZ)
goto out;
if (hdr->dbc_id >= qdev->num_dbc)
goto out;
dbc = &qdev->dbc[hdr->dbc_id];
switch (hdr->cmd) {
case DEBUG_TRANSFER_INFO:
ret = dbg_xfer_info_rsp(qdev, dbc, (struct ssr_debug_transfer_info *)resp->data);
if (ret)
break;
ssr_crash = qdev->ssr_mhi_buf;
dump_info = ssr_crash->dump_info;
dump_info->dbc = dbc;
dump_info->resp = resp;
/* Start by downloading debug table */
ret = mem_read_req(qdev, dump_info->tbl_addr_dev,
min(dump_info->tbl_len, SSR_MEM_READ_CHUNK_SIZE));
if (ret) {
free_ssr_dump_info(ssr_crash);
break;
}
/*
* Till now everything went fine, which means that we will be
* collecting crashdump chunk by chunk. Do not queue a response
* buffer for SSR cmds till the crashdump is complete.
*/
return;
case SSR_EVENT:
event = (struct ssr_event *)hdr;
le32_to_cpus(&event->event);
ssr_event_ack = event->event;
ssr_crash = qdev->ssr_mhi_buf;
switch (event->event) {
case BEFORE_SHUTDOWN:
set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_BEFORE_SHUTDOWN);
qaic_dbc_enter_ssr(qdev, hdr->dbc_id);
break;
case AFTER_SHUTDOWN:
set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_AFTER_SHUTDOWN);
break;
case BEFORE_POWER_UP:
set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_BEFORE_POWER_UP);
break;
case AFTER_POWER_UP:
/*
* If dump info is a non NULL value it means that we
* have received this SSR event while downloading a
* crashdump for this DBC is still in progress. NACK
* the SSR event
*/
if (ssr_crash && ssr_crash->dump_info) {
free_ssr_dump_info(ssr_crash);
ssr_event_ack = SSR_EVENT_NACK;
break;
}
set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_AFTER_POWER_UP);
break;
default:
break;
}
event_rsp = kmalloc(sizeof(*event_rsp), GFP_KERNEL);
if (!event_rsp)
break;
event_rsp->hdr.cmd = cpu_to_le32(SSR_EVENT_RSP);
event_rsp->hdr.len = cpu_to_le32(sizeof(*event_rsp));
event_rsp->hdr.dbc_id = cpu_to_le32(hdr->dbc_id);
event_rsp->event = cpu_to_le32(ssr_event_ack);
ret = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, event_rsp, sizeof(*event_rsp),
MHI_EOT);
if (ret)
kfree(event_rsp);
if (event->event == AFTER_POWER_UP && ssr_event_ack != SSR_EVENT_NACK) {
qaic_dbc_exit_ssr(qdev);
set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_IDLE);
}
break;
case DEBUG_TRANSFER_DONE_RSP:
dbg_xfer_done_rsp(qdev, dbc, (struct ssr_debug_transfer_done_rsp *)hdr);
break;
default:
break;
}
out:
ret = mhi_queue_buf(qdev->ssr_ch, DMA_FROM_DEVICE, resp->data, SSR_RESP_MSG_SZ, MHI_EOT);
if (ret)
kfree(resp);
}
static int qaic_ssr_mhi_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(mhi_dev->mhi_cntrl->cntrl_dev));
struct ssr_resp *resp;
int ret;
ret = mhi_prepare_for_transfer(mhi_dev);
if (ret)
return ret;
resp = kzalloc(sizeof(*resp) + SSR_RESP_MSG_SZ, GFP_KERNEL);
if (!resp) {
mhi_unprepare_from_transfer(mhi_dev);
return -ENOMEM;
}
resp->qdev = qdev;
INIT_WORK(&resp->work, ssr_worker);
ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, resp->data, SSR_RESP_MSG_SZ, MHI_EOT);
if (ret) {
kfree(resp);
mhi_unprepare_from_transfer(mhi_dev);
return ret;
}
dev_set_drvdata(&mhi_dev->dev, qdev);
qdev->ssr_ch = mhi_dev;
return 0;
}
static void qaic_ssr_mhi_remove(struct mhi_device *mhi_dev)
{
struct qaic_device *qdev;
qdev = dev_get_drvdata(&mhi_dev->dev);
mhi_unprepare_from_transfer(qdev->ssr_ch);
qdev->ssr_ch = NULL;
}
static void qaic_ssr_mhi_ul_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct qaic_device *qdev = dev_get_drvdata(&mhi_dev->dev);
struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf;
struct _ssr_hdr *hdr = mhi_result->buf_addr;
struct ssr_dump_info *dump_info;
if (mhi_result->transaction_status) {
kfree(mhi_result->buf_addr);
return;
}
/*
* MEMORY READ is used to download crashdump. And crashdump is
* downloaded chunk by chunk in a series of MEMORY READ SSR commands.
* Hence to avoid too many kmalloc() and kfree() of the same MEMORY READ
* request buffer, we allocate only one such buffer and free it only
* once.
*/
if (le32_to_cpu(hdr->cmd) == MEMORY_READ) {
dump_info = ssr_crash->dump_info;
if (dump_info) {
dump_info->read_buf_req_queued = false;
return;
}
}
kfree(mhi_result->buf_addr);
}
static void qaic_ssr_mhi_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct ssr_resp *resp = container_of(mhi_result->buf_addr, struct ssr_resp, data);
struct qaic_device *qdev = dev_get_drvdata(&mhi_dev->dev);
struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf;
bool memory_read_rsp = false;
if (ssr_crash && ssr_crash->data == mhi_result->buf_addr)
memory_read_rsp = true;
if (mhi_result->transaction_status) {
/* Do not free SSR crashdump buffer as it allocated via managed APIs */
if (!memory_read_rsp)
kfree(resp);
return;
}
if (memory_read_rsp)
queue_work(qdev->ssr_wq, &ssr_crash->work);
else
queue_work(qdev->ssr_wq, &resp->work);
}
static const struct mhi_device_id qaic_ssr_mhi_match_table[] = {
{ .chan = "QAIC_SSR", },
{},
};
static struct mhi_driver qaic_ssr_mhi_driver = {
.id_table = qaic_ssr_mhi_match_table,
.remove = qaic_ssr_mhi_remove,
.probe = qaic_ssr_mhi_probe,
.ul_xfer_cb = qaic_ssr_mhi_ul_xfer_cb,
.dl_xfer_cb = qaic_ssr_mhi_dl_xfer_cb,
.driver = {
.name = "qaic_ssr",
},
};
int qaic_ssr_init(struct qaic_device *qdev, struct drm_device *drm)
{
struct ssr_crashdump *ssr_crash;
qdev->ssr_dbc = QAIC_SSR_DBC_SENTINEL;
/*
* Device requests only one SSR at a time. So allocating only one
* buffer to download crashdump is good enough.
*/
ssr_crash = drmm_kzalloc(drm, SSR_MHI_BUF_SIZE, GFP_KERNEL);
if (!ssr_crash)
return -ENOMEM;
ssr_crash->qdev = qdev;
INIT_WORK(&ssr_crash->work, ssr_dump_worker);
qdev->ssr_mhi_buf = ssr_crash;
return 0;
}
int qaic_ssr_register(void)
{
return mhi_driver_register(&qaic_ssr_mhi_driver);
}
void qaic_ssr_unregister(void)
{
mhi_driver_unregister(&qaic_ssr_mhi_driver);
}

View File

@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0-only
*
* Copyright (c) 2020, The Linux Foundation. All rights reserved.
* Copyright (c) 2021, 2024 Qualcomm Innovation Center, Inc. All rights reserved.
*/
#ifndef __QAIC_SSR_H__
#define __QAIC_SSR_H__
struct drm_device;
struct qaic_device;
int qaic_ssr_register(void);
void qaic_ssr_unregister(void);
void qaic_clean_up_ssr(struct qaic_device *qdev);
int qaic_ssr_init(struct qaic_device *qdev, struct drm_device *drm);
#endif /* __QAIC_SSR_H__ */

View File

@ -0,0 +1,109 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020-2025, The Linux Foundation. All rights reserved. */
#include <drm/drm_file.h>
#include <drm/drm_managed.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#include "qaic.h"
#define NAME_LEN 14
struct dbc_attribute {
struct device_attribute dev_attr;
u32 dbc_id;
char name[NAME_LEN];
};
static ssize_t dbc_state_show(struct device *dev, struct device_attribute *a, char *buf)
{
struct dbc_attribute *dbc_attr = container_of(a, struct dbc_attribute, dev_attr);
struct drm_minor *minor = dev_get_drvdata(dev);
struct qaic_device *qdev;
qdev = to_qaic_device(minor->dev);
return sysfs_emit(buf, "%d\n", qdev->dbc[dbc_attr->dbc_id].state);
}
void set_dbc_state(struct qaic_device *qdev, u32 dbc_id, unsigned int state)
{
struct device *kdev = to_accel_kdev(qdev->qddev);
char *envp[3] = {};
char state_str[16];
char id_str[12];
envp[0] = id_str;
envp[1] = state_str;
if (state >= DBC_STATE_MAX)
return;
if (dbc_id >= qdev->num_dbc)
return;
if (state == qdev->dbc[dbc_id].state)
return;
scnprintf(id_str, ARRAY_SIZE(id_str), "DBC_ID=%d", dbc_id);
scnprintf(state_str, ARRAY_SIZE(state_str), "DBC_STATE=%d", state);
qdev->dbc[dbc_id].state = state;
kobject_uevent_env(&kdev->kobj, KOBJ_CHANGE, envp);
}
int qaic_sysfs_init(struct qaic_drm_device *qddev)
{
struct device *kdev = to_accel_kdev(qddev);
struct drm_device *drm = to_drm(qddev);
u32 num_dbc = qddev->qdev->num_dbc;
struct dbc_attribute *dbc_attrs;
int i, ret;
dbc_attrs = drmm_kcalloc(drm, num_dbc, sizeof(*dbc_attrs), GFP_KERNEL);
if (!dbc_attrs)
return -ENOMEM;
for (i = 0; i < num_dbc; ++i) {
struct dbc_attribute *dbc_attr = &dbc_attrs[i];
sysfs_attr_init(&dbc_attr->dev_attr.attr);
dbc_attr->dbc_id = i;
scnprintf(dbc_attr->name, NAME_LEN, "dbc%d_state", i);
dbc_attr->dev_attr.attr.name = dbc_attr->name;
dbc_attr->dev_attr.attr.mode = 0444;
dbc_attr->dev_attr.show = dbc_state_show;
ret = sysfs_create_file(&kdev->kobj, &dbc_attr->dev_attr.attr);
if (ret) {
int j;
for (j = 0; j < i; ++j) {
dbc_attr = &dbc_attrs[j];
sysfs_remove_file(&kdev->kobj, &dbc_attr->dev_attr.attr);
}
drmm_kfree(drm, dbc_attrs);
return ret;
}
}
qddev->sysfs_attrs = dbc_attrs;
return 0;
}
void qaic_sysfs_remove(struct qaic_drm_device *qddev)
{
struct dbc_attribute *dbc_attrs = qddev->sysfs_attrs;
struct device *kdev = to_accel_kdev(qddev);
u32 num_dbc = qddev->qdev->num_dbc;
int i;
if (!dbc_attrs)
return;
qddev->sysfs_attrs = NULL;
for (i = 0; i < num_dbc; ++i)
sysfs_remove_file(&kdev->kobj, &dbc_attrs[i].dev_attr.attr);
drmm_kfree(to_drm(qddev), dbc_attrs);
}

View File

@ -171,6 +171,13 @@ static void qaic_timesync_timer(struct timer_list *t)
dev_err(mqtsdev->dev, "%s mod_timer error:%d\n", __func__, ret);
}
void qaic_mqts_ch_stop_timer(struct mhi_device *mhi_dev)
{
struct mqts_dev *mqtsdev = dev_get_drvdata(&mhi_dev->dev);
timer_delete_sync(&mqtsdev->timer);
}
static int qaic_timesync_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(mhi_dev->mhi_cntrl->cntrl_dev));
@ -206,6 +213,7 @@ static int qaic_timesync_probe(struct mhi_device *mhi_dev, const struct mhi_devi
timer->expires = jiffies + msecs_to_jiffies(timesync_delay_ms);
add_timer(timer);
dev_set_drvdata(&mhi_dev->dev, mqtsdev);
qdev->mqts_ch = mhi_dev;
return 0;
@ -221,6 +229,7 @@ static void qaic_timesync_remove(struct mhi_device *mhi_dev)
{
struct mqts_dev *mqtsdev = dev_get_drvdata(&mhi_dev->dev);
mqtsdev->qdev->mqts_ch = NULL;
timer_delete_sync(&mqtsdev->timer);
mhi_unprepare_from_transfer(mqtsdev->mhi_dev);
kfree(mqtsdev->sync_msg);

View File

@ -6,6 +6,9 @@
#ifndef __QAIC_TIMESYNC_H__
#define __QAIC_TIMESYNC_H__
#include <linux/mhi.h>
int qaic_timesync_init(void);
void qaic_timesync_deinit(void);
void qaic_mqts_ch_stop_timer(struct mhi_device *mhi_dev);
#endif /* __QAIC_TIMESYNC_H__ */

View File

@ -121,29 +121,27 @@ static const struct dma_fence_ops dma_fence_stub_ops = {
.get_timeline_name = dma_fence_stub_get_name,
};
static int __init dma_fence_init_stub(void)
{
dma_fence_init(&dma_fence_stub, &dma_fence_stub_ops,
&dma_fence_stub_lock, 0, 0);
set_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT,
&dma_fence_stub.flags);
dma_fence_signal(&dma_fence_stub);
return 0;
}
subsys_initcall(dma_fence_init_stub);
/**
* dma_fence_get_stub - return a signaled fence
*
* Return a stub fence which is already signaled. The fence's
* timestamp corresponds to the first time after boot this
* function is called.
* Return a stub fence which is already signaled. The fence's timestamp
* corresponds to the initialisation time of the linux kernel.
*/
struct dma_fence *dma_fence_get_stub(void)
{
spin_lock(&dma_fence_stub_lock);
if (!dma_fence_stub.ops) {
dma_fence_init(&dma_fence_stub,
&dma_fence_stub_ops,
&dma_fence_stub_lock,
0, 0);
set_bit(DMA_FENCE_FLAG_ENABLE_SIGNAL_BIT,
&dma_fence_stub.flags);
dma_fence_signal_locked(&dma_fence_stub);
}
spin_unlock(&dma_fence_stub_lock);
return dma_fence_get(&dma_fence_stub);
}
EXPORT_SYMBOL(dma_fence_get_stub);

View File

@ -557,9 +557,14 @@ static void ast_primary_plane_helper_atomic_update(struct drm_plane *plane,
ast_set_vbios_color_reg(ast, fb->format, ast_crtc_state->vmode);
}
drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state);
drm_atomic_for_each_plane_damage(&iter, &damage) {
ast_handle_damage(ast_plane, shadow_plane_state->data, fb, &damage);
/* if the buffer comes from another device */
if (drm_gem_fb_begin_cpu_access(fb, DMA_FROM_DEVICE) == 0) {
drm_atomic_helper_damage_iter_init(&iter, old_plane_state, plane_state);
drm_atomic_for_each_plane_damage(&iter, &damage) {
ast_handle_damage(ast_plane, shadow_plane_state->data, fb, &damage);
}
drm_gem_fb_end_cpu_access(fb, DMA_FROM_DEVICE);
}
/*

View File

@ -1831,10 +1831,12 @@ drm_atomic_helper_wait_for_vblanks(struct drm_device *dev,
}
for_each_old_crtc_in_state(state, crtc, old_crtc_state, i) {
wait_queue_head_t *queue = drm_crtc_vblank_waitqueue(crtc);
if (!(crtc_mask & drm_crtc_mask(crtc)))
continue;
ret = wait_event_timeout(dev->vblank[i].queue,
ret = wait_event_timeout(*queue,
state->crtcs[i].last_vblank_count !=
drm_crtc_vblank_count(crtc),
msecs_to_jiffies(100));

View File

@ -250,6 +250,9 @@ static const struct edid_quirk {
EDID_QUIRK('S', 'V', 'R', 0x1019, BIT(EDID_QUIRK_NON_DESKTOP)),
EDID_QUIRK('A', 'U', 'O', 0x1111, BIT(EDID_QUIRK_NON_DESKTOP)),
/* LQ116M1JW10 displays noise when 8 bpc, but display fine as 6 bpc */
EDID_QUIRK('S', 'H', 'P', 0x154c, BIT(EDID_QUIRK_FORCE_6BPC)),
/*
* @drm_edid_internal_quirk entries end here, following with the
* @drm_edid_quirk entries.

View File

@ -1315,7 +1315,7 @@ void drm_wait_one_vblank(struct drm_device *dev, unsigned int pipe)
ret = wait_event_timeout(vblank->queue,
last != drm_vblank_count(dev, pipe),
msecs_to_jiffies(100));
msecs_to_jiffies(1000));
drm_WARN(dev, ret == 0, "vblank wait timed out on crtc %i\n", pipe);
@ -2258,7 +2258,7 @@ int drm_crtc_vblank_start_timer(struct drm_crtc *crtc)
EXPORT_SYMBOL(drm_crtc_vblank_start_timer);
/**
* drm_crtc_vblank_start_timer - Cancels the given CRTC's vblank timer
* drm_crtc_vblank_cancel_timer - Cancels the given CRTC's vblank timer
* @crtc: the CRTC
*
* Drivers should call this function from their CRTC's disable_vblank

View File

@ -244,7 +244,7 @@ EXPORT_SYMBOL(drm_vblank_work_flush);
void drm_vblank_work_flush_all(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
struct drm_vblank_crtc *vblank = &dev->vblank[drm_crtc_index(crtc)];
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
spin_lock_irq(&dev->event_lock);
wait_event_lock_irq(vblank->work_wait_queue,

View File

@ -250,6 +250,7 @@ static irqreturn_t gma_irq_handler(int irq, void *arg)
void gma_irq_preinstall(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
struct drm_crtc *crtc;
unsigned long irqflags;
spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags);
@ -260,10 +261,15 @@ void gma_irq_preinstall(struct drm_device *dev)
PSB_WSGX32(0x00000000, PSB_CR_EVENT_HOST_ENABLE);
PSB_RSGX32(PSB_CR_EVENT_HOST_ENABLE);
if (dev->vblank[0].enabled)
dev_priv->vdc_irq_mask |= _PSB_VSYNC_PIPEA_FLAG;
if (dev->vblank[1].enabled)
dev_priv->vdc_irq_mask |= _PSB_VSYNC_PIPEB_FLAG;
drm_for_each_crtc(crtc, dev) {
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
if (vblank->enabled) {
u32 mask = drm_crtc_index(crtc) ? _PSB_VSYNC_PIPEB_FLAG :
_PSB_VSYNC_PIPEA_FLAG;
dev_priv->vdc_irq_mask |= mask;
}
}
/* Revisit this area - want per device masks ? */
if (dev_priv->ops->hotplug)
@ -278,8 +284,8 @@ void gma_irq_preinstall(struct drm_device *dev)
void gma_irq_postinstall(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
struct drm_crtc *crtc;
unsigned long irqflags;
unsigned int i;
spin_lock_irqsave(&dev_priv->irqmask_lock, irqflags);
@ -292,11 +298,13 @@ void gma_irq_postinstall(struct drm_device *dev)
PSB_WVDC32(dev_priv->vdc_irq_mask, PSB_INT_ENABLE_R);
PSB_WVDC32(0xFFFFFFFF, PSB_HWSTAM);
for (i = 0; i < dev->num_crtcs; ++i) {
if (dev->vblank[i].enabled)
gma_enable_pipestat(dev_priv, i, PIPE_VBLANK_INTERRUPT_ENABLE);
drm_for_each_crtc(crtc, dev) {
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
if (vblank->enabled)
gma_enable_pipestat(dev_priv, drm_crtc_index(crtc), PIPE_VBLANK_INTERRUPT_ENABLE);
else
gma_disable_pipestat(dev_priv, i, PIPE_VBLANK_INTERRUPT_ENABLE);
gma_disable_pipestat(dev_priv, drm_crtc_index(crtc), PIPE_VBLANK_INTERRUPT_ENABLE);
}
if (dev_priv->ops->hotplug_enable)
@ -337,8 +345,8 @@ void gma_irq_uninstall(struct drm_device *dev)
{
struct drm_psb_private *dev_priv = to_drm_psb_private(dev);
struct pci_dev *pdev = to_pci_dev(dev->dev);
struct drm_crtc *crtc;
unsigned long irqflags;
unsigned int i;
if (!dev_priv->irq_enabled)
return;
@ -350,9 +358,11 @@ void gma_irq_uninstall(struct drm_device *dev)
PSB_WVDC32(0xFFFFFFFF, PSB_HWSTAM);
for (i = 0; i < dev->num_crtcs; ++i) {
if (dev->vblank[i].enabled)
gma_disable_pipestat(dev_priv, i, PIPE_VBLANK_INTERRUPT_ENABLE);
drm_for_each_crtc(crtc, dev) {
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
if (vblank->enabled)
gma_disable_pipestat(dev_priv, drm_crtc_index(crtc), PIPE_VBLANK_INTERRUPT_ENABLE);
}
dev_priv->vdc_irq_mask &= _PSB_IRQ_SGX_FLAG |

View File

@ -144,7 +144,6 @@ static int imx_drm_dumb_create(struct drm_file *file_priv,
struct drm_mode_create_dumb *args)
{
u32 fourcc;
const struct drm_format_info *info;
u64 pitch_align;
int ret;
@ -156,12 +155,15 @@ static int imx_drm_dumb_create(struct drm_file *file_priv,
* the allocated buffer.
*/
fourcc = drm_driver_color_mode_format(drm, args->bpp);
if (fourcc == DRM_FORMAT_INVALID)
return -EINVAL;
info = drm_format_info(fourcc);
if (!info)
return -EINVAL;
pitch_align = drm_format_info_min_pitch(info, 0, SZ_8);
if (fourcc != DRM_FORMAT_INVALID) {
const struct drm_format_info *info = drm_format_info(fourcc);
if (!info)
return -EINVAL;
pitch_align = drm_format_info_min_pitch(info, 0, 8);
} else {
pitch_align = DIV_ROUND_UP(args->bpp, SZ_8) * 8;
}
if (!pitch_align || pitch_align > U32_MAX)
return -EINVAL;
ret = drm_mode_size_dumb(drm, args, pitch_align, 0);

View File

@ -527,13 +527,14 @@ 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);
wait_queue_head_t *queue = drm_crtc_vblank_waitqueue(crtc);
int ret;
ret = drm_crtc_vblank_get(crtc);
if (ret)
return;
ret = wait_event_timeout(dev->vblank[drm_crtc_index(crtc)].queue,
ret = wait_event_timeout(*queue,
!(mdp4_read(mdp4_kms, REG_MDP4_OVERLAY_FLUSH) &
mdp4_crtc->flushed_mask),
msecs_to_jiffies(50));

View File

@ -1234,6 +1234,7 @@ static void mdp5_crtc_wait_for_flush_done(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_ctl *ctl = mdp5_cstate->ctl;
wait_queue_head_t *queue = drm_crtc_vblank_waitqueue(crtc);
int ret;
/* Should not call this function if crtc is disabled. */
@ -1244,7 +1245,7 @@ static void mdp5_crtc_wait_for_flush_done(struct drm_crtc *crtc)
if (ret)
return;
ret = wait_event_timeout(dev->vblank[drm_crtc_index(crtc)].queue,
ret = wait_event_timeout(*queue,
((mdp5_ctl_get_commit_status(ctl) &
mdp5_crtc->flushed_mask) == 0),
msecs_to_jiffies(50));

View File

@ -52,7 +52,9 @@ struct nvfw_hs_load_header_v2 {
struct {
u32 offset;
u32 size;
} app[];
u32 data_offset;
u32 data_size;
} app[] __counted_by(num_apps);
};
const struct nvfw_hs_load_header_v2 *nvfw_hs_load_header_v2(struct nvkm_subdev *, const void *);

View File

@ -10,7 +10,7 @@
#define DRIVER_MAJOR 1
#define DRIVER_MINOR 4
#define DRIVER_PATCHLEVEL 0
#define DRIVER_PATCHLEVEL 1
/*
* 1.1.1:
@ -35,6 +35,8 @@
* programs that get directly linked with NVKM.
* 1.3.1:
* - implemented limited ABI16/NVIF interop
* 1.4.1:
* - add variable page sizes and compression for Turing+
*/
#include <linux/notifier.h>

View File

@ -107,34 +107,34 @@ nouveau_uvmm_vmm_sparse_unref(struct nouveau_uvmm *uvmm,
static int
nouveau_uvmm_vmm_get(struct nouveau_uvmm *uvmm,
u64 addr, u64 range)
u64 addr, u64 range, u8 page_shift)
{
struct nvif_vmm *vmm = &uvmm->vmm.vmm;
return nvif_vmm_raw_get(vmm, addr, range, PAGE_SHIFT);
return nvif_vmm_raw_get(vmm, addr, range, page_shift);
}
static int
nouveau_uvmm_vmm_put(struct nouveau_uvmm *uvmm,
u64 addr, u64 range)
u64 addr, u64 range, u8 page_shift)
{
struct nvif_vmm *vmm = &uvmm->vmm.vmm;
return nvif_vmm_raw_put(vmm, addr, range, PAGE_SHIFT);
return nvif_vmm_raw_put(vmm, addr, range, page_shift);
}
static int
nouveau_uvmm_vmm_unmap(struct nouveau_uvmm *uvmm,
u64 addr, u64 range, bool sparse)
u64 addr, u64 range, u8 page_shift, bool sparse)
{
struct nvif_vmm *vmm = &uvmm->vmm.vmm;
return nvif_vmm_raw_unmap(vmm, addr, range, PAGE_SHIFT, sparse);
return nvif_vmm_raw_unmap(vmm, addr, range, page_shift, sparse);
}
static int
nouveau_uvmm_vmm_map(struct nouveau_uvmm *uvmm,
u64 addr, u64 range,
u64 addr, u64 range, u8 page_shift,
u64 bo_offset, u8 kind,
struct nouveau_mem *mem)
{
@ -163,7 +163,7 @@ nouveau_uvmm_vmm_map(struct nouveau_uvmm *uvmm,
return -ENOSYS;
}
return nvif_vmm_raw_map(vmm, addr, range, PAGE_SHIFT,
return nvif_vmm_raw_map(vmm, addr, range, page_shift,
&args, argc,
&mem->mem, bo_offset);
}
@ -182,8 +182,9 @@ nouveau_uvma_vmm_put(struct nouveau_uvma *uvma)
{
u64 addr = uvma->va.va.addr;
u64 range = uvma->va.va.range;
u8 page_shift = uvma->page_shift;
return nouveau_uvmm_vmm_put(to_uvmm(uvma), addr, range);
return nouveau_uvmm_vmm_put(to_uvmm(uvma), addr, range, page_shift);
}
static int
@ -193,9 +194,11 @@ nouveau_uvma_map(struct nouveau_uvma *uvma,
u64 addr = uvma->va.va.addr;
u64 offset = uvma->va.gem.offset;
u64 range = uvma->va.va.range;
u8 page_shift = uvma->page_shift;
return nouveau_uvmm_vmm_map(to_uvmm(uvma), addr, range,
offset, uvma->kind, mem);
page_shift, offset, uvma->kind,
mem);
}
static int
@ -203,12 +206,13 @@ nouveau_uvma_unmap(struct nouveau_uvma *uvma)
{
u64 addr = uvma->va.va.addr;
u64 range = uvma->va.va.range;
u8 page_shift = uvma->page_shift;
bool sparse = !!uvma->region;
if (drm_gpuva_invalidated(&uvma->va))
return 0;
return nouveau_uvmm_vmm_unmap(to_uvmm(uvma), addr, range, sparse);
return nouveau_uvmm_vmm_unmap(to_uvmm(uvma), addr, range, page_shift, sparse);
}
static int
@ -450,6 +454,62 @@ op_unmap_prepare_unwind(struct drm_gpuva *va)
drm_gpuva_insert(va->vm, va);
}
static bool
op_map_aligned_to_page_shift(const struct drm_gpuva_op_map *op, u8 page_shift)
{
u64 non_page_bits = (1ULL << page_shift) - 1;
return (op->va.addr & non_page_bits) == 0 &&
(op->va.range & non_page_bits) == 0 &&
(op->gem.offset & non_page_bits) == 0;
}
static u8
select_page_shift(struct nouveau_uvmm *uvmm, struct drm_gpuva_op_map *op)
{
struct nouveau_bo *nvbo = nouveau_gem_object(op->gem.obj);
/* nouveau_bo_fixup_align() guarantees that the page size will be aligned
* for most cases, but it can't handle cases where userspace allocates with
* a size and then binds with a smaller granularity. So in order to avoid
* breaking old userspace, we need to ensure that the VA is actually
* aligned before using it, and if it isn't, then we downgrade to the first
* granularity that will fit, which is optimal from a correctness and
* performance perspective.
*/
if (op_map_aligned_to_page_shift(op, nvbo->page))
return nvbo->page;
struct nouveau_mem *mem = nouveau_mem(nvbo->bo.resource);
struct nvif_vmm *vmm = &uvmm->vmm.vmm;
int i;
/* If the given granularity doesn't fit, let's find one that will fit. */
for (i = 0; i < vmm->page_nr; i++) {
/* Ignore anything that is bigger or identical to the BO preference. */
if (vmm->page[i].shift >= nvbo->page)
continue;
/* Skip incompatible domains. */
if ((mem->mem.type & NVIF_MEM_VRAM) && !vmm->page[i].vram)
continue;
if ((mem->mem.type & NVIF_MEM_HOST) &&
(!vmm->page[i].host || vmm->page[i].shift > PAGE_SHIFT))
continue;
/* If it fits, return the proposed shift. */
if (op_map_aligned_to_page_shift(op, vmm->page[i].shift))
return vmm->page[i].shift;
}
/* If we get here then nothing can reconcile the requirements. This should never
* happen.
*/
drm_WARN_ONCE(op->gem.obj->dev, 1, "Could not find an appropriate page size.\n");
return PAGE_SHIFT;
}
static void
nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm,
struct nouveau_uvma_prealloc *new,
@ -501,7 +561,8 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm,
if (vmm_get_range)
nouveau_uvmm_vmm_put(uvmm, vmm_get_start,
vmm_get_range);
vmm_get_range,
select_page_shift(uvmm, &op->map));
break;
}
case DRM_GPUVA_OP_REMAP: {
@ -528,6 +589,7 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm,
u64 ustart = va->va.addr;
u64 urange = va->va.range;
u64 uend = ustart + urange;
u8 page_shift = uvma_from_va(va)->page_shift;
/* Nothing to do for mappings we merge with. */
if (uend == vmm_get_start ||
@ -538,7 +600,8 @@ nouveau_uvmm_sm_prepare_unwind(struct nouveau_uvmm *uvmm,
u64 vmm_get_range = ustart - vmm_get_start;
nouveau_uvmm_vmm_put(uvmm, vmm_get_start,
vmm_get_range);
vmm_get_range,
page_shift);
}
vmm_get_start = uend;
break;
@ -592,6 +655,7 @@ op_map_prepare(struct nouveau_uvmm *uvmm,
uvma->region = args->region;
uvma->kind = args->kind;
uvma->page_shift = select_page_shift(uvmm, op);
drm_gpuva_map(&uvmm->base, &uvma->va, op);
@ -633,7 +697,8 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm *uvmm,
if (vmm_get_range) {
ret = nouveau_uvmm_vmm_get(uvmm, vmm_get_start,
vmm_get_range);
vmm_get_range,
new->map->page_shift);
if (ret) {
op_map_prepare_unwind(new->map);
goto unwind;
@ -689,6 +754,7 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm *uvmm,
u64 ustart = va->va.addr;
u64 urange = va->va.range;
u64 uend = ustart + urange;
u8 page_shift = uvma_from_va(va)->page_shift;
op_unmap_prepare(u);
@ -704,7 +770,7 @@ nouveau_uvmm_sm_prepare(struct nouveau_uvmm *uvmm,
u64 vmm_get_range = ustart - vmm_get_start;
ret = nouveau_uvmm_vmm_get(uvmm, vmm_get_start,
vmm_get_range);
vmm_get_range, page_shift);
if (ret) {
op_unmap_prepare_unwind(va);
goto unwind;
@ -799,10 +865,11 @@ op_unmap_range(struct drm_gpuva_op_unmap *u,
u64 addr, u64 range)
{
struct nouveau_uvma *uvma = uvma_from_va(u->va);
u8 page_shift = uvma->page_shift;
bool sparse = !!uvma->region;
if (!drm_gpuva_invalidated(u->va))
nouveau_uvmm_vmm_unmap(to_uvmm(uvma), addr, range, sparse);
nouveau_uvmm_vmm_unmap(to_uvmm(uvma), addr, range, page_shift, sparse);
}
static void
@ -882,6 +949,7 @@ nouveau_uvmm_sm_cleanup(struct nouveau_uvmm *uvmm,
struct drm_gpuva_op_map *n = r->next;
struct drm_gpuva *va = r->unmap->va;
struct nouveau_uvma *uvma = uvma_from_va(va);
u8 page_shift = uvma->page_shift;
if (unmap) {
u64 addr = va->va.addr;
@ -893,7 +961,7 @@ nouveau_uvmm_sm_cleanup(struct nouveau_uvmm *uvmm,
if (n)
end = n->va.addr;
nouveau_uvmm_vmm_put(uvmm, addr, end - addr);
nouveau_uvmm_vmm_put(uvmm, addr, end - addr, page_shift);
}
nouveau_uvma_gem_put(uvma);

View File

@ -33,6 +33,7 @@ struct nouveau_uvma {
struct nouveau_uvma_region *region;
u8 kind;
u8 page_shift;
};
#define uvmm_from_gpuvm(x) container_of((x), struct nouveau_uvmm, base)

View File

@ -21,9 +21,7 @@
*/
#include "vmm.h"
#include <core/client.h>
#include <subdev/fb.h>
#include <subdev/ltc.h>
#include <subdev/timer.h>
#include <engine/gr.h>
@ -111,13 +109,33 @@ gp100_vmm_pgt_pfn(struct nvkm_vmm *vmm, struct nvkm_mmu_pt *pt,
nvkm_done(pt->memory);
}
static inline u64
gp100_vmm_comptag_nr(u64 size)
{
return size >> 16; /* One comptag per 64KiB VRAM. */
}
static inline u64
gp100_vmm_pte_comptagline_base(u64 addr)
{
/* RM allocates enough comptags for all of VRAM, so use a 1:1 mapping. */
return (1 + gp100_vmm_comptag_nr(addr)) << 36; /* NV_MMU_VER2_PTE_COMPTAGLINE */
}
static inline u64
gp100_vmm_pte_comptagline_incr(u32 page_size)
{
return gp100_vmm_comptag_nr(page_size) << 36; /* NV_MMU_VER2_PTE_COMPTAGLINE */
}
static inline void
gp100_vmm_pgt_pte(struct nvkm_vmm *vmm, struct nvkm_mmu_pt *pt,
u32 ptei, u32 ptes, struct nvkm_vmm_map *map, u64 addr)
{
u64 data = (addr >> 4) | map->type;
map->type += ptes * map->ctag;
if (map->ctag)
data |= gp100_vmm_pte_comptagline_base(addr);
while (ptes--) {
VMM_WO064(pt, vmm, ptei++ * 8, data);
@ -142,7 +160,6 @@ gp100_vmm_pgt_dma(struct nvkm_vmm *vmm, struct nvkm_mmu_pt *pt,
while (ptes--) {
const u64 data = (*map->dma++ >> 4) | map->type;
VMM_WO064(pt, vmm, ptei++ * 8, data);
map->type += map->ctag;
}
nvkm_done(pt->memory);
return;
@ -200,7 +217,8 @@ gp100_vmm_pd0_pte(struct nvkm_vmm *vmm, struct nvkm_mmu_pt *pt,
{
u64 data = (addr >> 4) | map->type;
map->type += ptes * map->ctag;
if (map->ctag)
data |= gp100_vmm_pte_comptagline_base(addr);
while (ptes--) {
VMM_WO128(pt, vmm, ptei++ * 0x10, data, 0ULL);
@ -411,8 +429,6 @@ gp100_vmm_valid(struct nvkm_vmm *vmm, void *argv, u32 argc,
struct gp100_vmm_map_vn vn;
struct gp100_vmm_map_v0 v0;
} *args = argv;
struct nvkm_device *device = vmm->mmu->subdev.device;
struct nvkm_memory *memory = map->memory;
u8 kind, kind_inv, priv, ro, vol;
int kindn, aper, ret = -ENOSYS;
const u8 *kindm;
@ -449,29 +465,24 @@ gp100_vmm_valid(struct nvkm_vmm *vmm, void *argv, u32 argc,
return -EINVAL;
}
/* Handle compression. */
if (kindm[kind] != kind) {
u64 tags = nvkm_memory_size(memory) >> 16;
if (aper != 0 || !(page->type & NVKM_VMM_PAGE_COMP)) {
VMM_DEBUG(vmm, "comp %d %02x", aper, page->type);
return -EINVAL;
}
struct nvkm_device *device = vmm->mmu->subdev.device;
if (!map->no_comp) {
ret = nvkm_memory_tags_get(memory, device, tags,
nvkm_ltc_tags_clear,
&map->tags);
if (ret) {
VMM_DEBUG(vmm, "comp %d", ret);
return ret;
/* Compression is only supported when using GSP-RM, as
* PMU firmware is required in order to initialise the
* compbit backing store.
*/
if (nvkm_gsp_rm(device->gsp)) {
/* Turing GPUs require PTE_COMPTAGLINE to be filled,
* in addition to specifying a compressed kind.
*/
if (device->card_type < GA100) {
map->ctag = gp100_vmm_pte_comptagline_incr(1 << map->page->shift);
map->next |= map->ctag;
}
}
if (!map->no_comp && map->tags->mn) {
tags = map->tags->mn->offset + (map->offset >> 16);
map->ctag |= ((1ULL << page->shift) >> 16) << 36;
map->type |= tags << 36;
map->next |= map->ctag;
} else {
/* Revert to non-compressed kind. */
kind = kindm[kind];
}
}
@ -592,8 +603,8 @@ gp100_vmm = {
{ 47, &gp100_vmm_desc_16[4], NVKM_VMM_PAGE_Sxxx },
{ 38, &gp100_vmm_desc_16[3], NVKM_VMM_PAGE_Sxxx },
{ 29, &gp100_vmm_desc_16[2], NVKM_VMM_PAGE_Sxxx },
{ 21, &gp100_vmm_desc_16[1], NVKM_VMM_PAGE_SVxC },
{ 16, &gp100_vmm_desc_16[0], NVKM_VMM_PAGE_SVxC },
{ 21, &gp100_vmm_desc_16[1], NVKM_VMM_PAGE_SVxx },
{ 16, &gp100_vmm_desc_16[0], NVKM_VMM_PAGE_SVxx },
{ 12, &gp100_vmm_desc_12[0], NVKM_VMM_PAGE_SVHx },
{}
}

View File

@ -34,8 +34,8 @@ gp10b_vmm = {
{ 47, &gp100_vmm_desc_16[4], NVKM_VMM_PAGE_Sxxx },
{ 38, &gp100_vmm_desc_16[3], NVKM_VMM_PAGE_Sxxx },
{ 29, &gp100_vmm_desc_16[2], NVKM_VMM_PAGE_Sxxx },
{ 21, &gp100_vmm_desc_16[1], NVKM_VMM_PAGE_SxHC },
{ 16, &gp100_vmm_desc_16[0], NVKM_VMM_PAGE_SxHC },
{ 21, &gp100_vmm_desc_16[1], NVKM_VMM_PAGE_SxHx },
{ 16, &gp100_vmm_desc_16[0], NVKM_VMM_PAGE_SxHx },
{ 12, &gp100_vmm_desc_12[0], NVKM_VMM_PAGE_SxHx },
{}
}

View File

@ -1147,6 +1147,20 @@ static void panthor_vm_cleanup_op_ctx(struct panthor_vm_op_ctx *op_ctx,
}
}
static void
panthor_vm_op_ctx_return_vma(struct panthor_vm_op_ctx *op_ctx,
struct panthor_vma *vma)
{
for (u32 i = 0; i < ARRAY_SIZE(op_ctx->preallocated_vmas); i++) {
if (!op_ctx->preallocated_vmas[i]) {
op_ctx->preallocated_vmas[i] = vma;
return;
}
}
WARN_ON_ONCE(1);
}
static struct panthor_vma *
panthor_vm_op_ctx_get_vma(struct panthor_vm_op_ctx *op_ctx)
{
@ -2082,8 +2096,10 @@ static int panthor_gpuva_sm_step_map(struct drm_gpuva_op *op, void *priv)
ret = panthor_vm_map_pages(vm, op->map.va.addr, flags_to_prot(vma->flags),
op_ctx->map.sgt, op->map.gem.offset,
op->map.va.range);
if (ret)
if (ret) {
panthor_vm_op_ctx_return_vma(op_ctx, vma);
return ret;
}
/* Ref owned by the mapping now, clear the obj field so we don't release the
* pinning/obj ref behind GPUVA's back.

View File

@ -1320,7 +1320,7 @@ int drm_sched_init(struct drm_gpu_scheduler *sched, const struct drm_sched_init_
sched->name = args->name;
sched->timeout = args->timeout;
sched->hang_limit = args->hang_limit;
sched->timeout_wq = args->timeout_wq ? args->timeout_wq : system_wq;
sched->timeout_wq = args->timeout_wq ? args->timeout_wq : system_percpu_wq;
sched->score = args->score ? args->score : &sched->_score;
sched->dev = args->dev;

View File

@ -3,11 +3,20 @@
* Copyright (C) Jernej Skrabec <jernej.skrabec@siol.net>
*/
#include <drm/drm_fourcc.h>
#include <drm/drm_framebuffer.h>
#include <drm/drm_plane.h>
#include <drm/drm_print.h>
#include "sun8i_csc.h"
#include "sun8i_mixer.h"
enum sun8i_csc_mode {
SUN8I_CSC_MODE_OFF,
SUN8I_CSC_MODE_YUV2RGB,
SUN8I_CSC_MODE_YVU2RGB,
};
static const u32 ccsc_base[][2] = {
[CCSC_MIXER0_LAYOUT] = {CCSC00_OFFSET, CCSC01_OFFSET},
[CCSC_MIXER1_LAYOUT] = {CCSC10_OFFSET, CCSC11_OFFSET},
@ -107,23 +116,28 @@ static const u32 yuv2rgb_de3[2][3][12] = {
},
};
static void sun8i_csc_set_coefficients(struct regmap *map, u32 base,
enum sun8i_csc_mode mode,
enum drm_color_encoding encoding,
enum drm_color_range range)
static void sun8i_csc_setup(struct regmap *map, u32 base,
enum sun8i_csc_mode mode,
enum drm_color_encoding encoding,
enum drm_color_range range)
{
u32 base_reg, val;
const u32 *table;
u32 base_reg;
int i;
table = yuv2rgb[range][encoding];
switch (mode) {
case SUN8I_CSC_MODE_OFF:
val = 0;
break;
case SUN8I_CSC_MODE_YUV2RGB:
val = SUN8I_CSC_CTRL_EN;
base_reg = SUN8I_CSC_COEFF(base, 0);
regmap_bulk_write(map, base_reg, table, 12);
break;
case SUN8I_CSC_MODE_YVU2RGB:
val = SUN8I_CSC_CTRL_EN;
for (i = 0; i < 12; i++) {
if ((i & 3) == 1)
base_reg = SUN8I_CSC_COEFF(base, i + 1);
@ -135,28 +149,37 @@ static void sun8i_csc_set_coefficients(struct regmap *map, u32 base,
}
break;
default:
val = 0;
DRM_WARN("Wrong CSC mode specified.\n");
return;
}
regmap_write(map, SUN8I_CSC_CTRL(base), val);
}
static void sun8i_de3_ccsc_set_coefficients(struct regmap *map, int layer,
enum sun8i_csc_mode mode,
enum drm_color_encoding encoding,
enum drm_color_range range)
static void sun8i_de3_ccsc_setup(struct regmap *map, int layer,
enum sun8i_csc_mode mode,
enum drm_color_encoding encoding,
enum drm_color_range range)
{
u32 addr, val, mask;
const u32 *table;
u32 addr;
int i;
mask = SUN50I_MIXER_BLEND_CSC_CTL_EN(layer);
table = yuv2rgb_de3[range][encoding];
switch (mode) {
case SUN8I_CSC_MODE_OFF:
val = 0;
break;
case SUN8I_CSC_MODE_YUV2RGB:
val = mask;
addr = SUN50I_MIXER_BLEND_CSC_COEFF(DE3_BLD_BASE, layer, 0);
regmap_bulk_write(map, addr, table, 12);
break;
case SUN8I_CSC_MODE_YVU2RGB:
val = mask;
for (i = 0; i < 12; i++) {
if ((i & 3) == 1)
addr = SUN50I_MIXER_BLEND_CSC_COEFF(DE3_BLD_BASE,
@ -173,67 +196,53 @@ static void sun8i_de3_ccsc_set_coefficients(struct regmap *map, int layer,
}
break;
default:
val = 0;
DRM_WARN("Wrong CSC mode specified.\n");
return;
}
}
static void sun8i_csc_enable(struct regmap *map, u32 base, bool enable)
{
u32 val;
if (enable)
val = SUN8I_CSC_CTRL_EN;
else
val = 0;
regmap_update_bits(map, SUN8I_CSC_CTRL(base), SUN8I_CSC_CTRL_EN, val);
}
static void sun8i_de3_ccsc_enable(struct regmap *map, int layer, bool enable)
{
u32 val, mask;
mask = SUN50I_MIXER_BLEND_CSC_CTL_EN(layer);
if (enable)
val = mask;
else
val = 0;
regmap_update_bits(map, SUN50I_MIXER_BLEND_CSC_CTL(DE3_BLD_BASE),
mask, val);
}
void sun8i_csc_set_ccsc_coefficients(struct sun8i_mixer *mixer, int layer,
enum sun8i_csc_mode mode,
enum drm_color_encoding encoding,
enum drm_color_range range)
static u32 sun8i_csc_get_mode(struct drm_plane_state *state)
{
const struct drm_format_info *format;
if (!state->crtc || !state->visible)
return SUN8I_CSC_MODE_OFF;
format = state->fb->format;
if (!format->is_yuv)
return SUN8I_CSC_MODE_OFF;
switch (format->format) {
case DRM_FORMAT_YVU411:
case DRM_FORMAT_YVU420:
case DRM_FORMAT_YVU422:
case DRM_FORMAT_YVU444:
return SUN8I_CSC_MODE_YVU2RGB;
default:
return SUN8I_CSC_MODE_YUV2RGB;
}
}
void sun8i_csc_config(struct sun8i_layer *layer,
struct drm_plane_state *state)
{
u32 mode = sun8i_csc_get_mode(state);
u32 base;
if (mixer->cfg->de_type == SUN8I_MIXER_DE3) {
sun8i_de3_ccsc_set_coefficients(mixer->engine.regs, layer,
mode, encoding, range);
if (layer->cfg->de_type == SUN8I_MIXER_DE3) {
sun8i_de3_ccsc_setup(layer->regs, layer->channel,
mode, state->color_encoding,
state->color_range);
return;
}
base = ccsc_base[mixer->cfg->ccsc][layer];
base = ccsc_base[layer->cfg->ccsc][layer->channel];
sun8i_csc_set_coefficients(mixer->engine.regs, base,
mode, encoding, range);
}
void sun8i_csc_enable_ccsc(struct sun8i_mixer *mixer, int layer, bool enable)
{
u32 base;
if (mixer->cfg->de_type == SUN8I_MIXER_DE3) {
sun8i_de3_ccsc_enable(mixer->engine.regs, layer, enable);
return;
}
base = ccsc_base[mixer->cfg->ccsc][layer];
sun8i_csc_enable(mixer->engine.regs, base, enable);
sun8i_csc_setup(layer->regs, base,
mode, state->color_encoding,
state->color_range);
}

View File

@ -8,7 +8,8 @@
#include <drm/drm_color_mgmt.h>
struct sun8i_mixer;
struct drm_plane_state;
struct sun8i_layer;
/* VI channel CSC units offsets */
#define CCSC00_OFFSET 0xAA050
@ -22,16 +23,7 @@ struct sun8i_mixer;
#define SUN8I_CSC_CTRL_EN BIT(0)
enum sun8i_csc_mode {
SUN8I_CSC_MODE_OFF,
SUN8I_CSC_MODE_YUV2RGB,
SUN8I_CSC_MODE_YVU2RGB,
};
void sun8i_csc_set_ccsc_coefficients(struct sun8i_mixer *mixer, int layer,
enum sun8i_csc_mode mode,
enum drm_color_encoding encoding,
enum drm_color_range range);
void sun8i_csc_enable_ccsc(struct sun8i_mixer *mixer, int layer, bool enable);
void sun8i_csc_config(struct sun8i_layer *layer,
struct drm_plane_state *state);
#endif

View File

@ -251,24 +251,6 @@ int sun8i_mixer_drm_format_to_hw(u32 format, u32 *hw_format)
return -EINVAL;
}
static void sun8i_layer_enable(struct sun8i_layer *layer, bool enable)
{
u32 ch_base = sun8i_channel_base(layer->mixer, layer->channel);
u32 val, reg, mask;
if (layer->type == SUN8I_LAYER_TYPE_UI) {
val = enable ? SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN : 0;
mask = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN;
reg = SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, layer->overlay);
} else {
val = enable ? SUN8I_MIXER_CHAN_VI_LAYER_ATTR_EN : 0;
mask = SUN8I_MIXER_CHAN_VI_LAYER_ATTR_EN;
reg = SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay);
}
regmap_update_bits(layer->mixer->engine.regs, reg, mask, val);
}
static void sun8i_mixer_commit(struct sunxi_engine *engine,
struct drm_crtc *crtc,
struct drm_atomic_state *state)
@ -284,10 +266,10 @@ static void sun8i_mixer_commit(struct sunxi_engine *engine,
drm_for_each_plane(plane, state->dev) {
struct sun8i_layer *layer = plane_to_sun8i_layer(plane);
int w, h, x, y, zpos;
bool enable;
int zpos;
if (!(plane->possible_crtcs & drm_crtc_mask(crtc)) || layer->mixer != mixer)
if (!(plane->possible_crtcs & drm_crtc_mask(crtc)))
continue;
plane_state = drm_atomic_get_new_plane_state(state, plane);
@ -296,23 +278,28 @@ static void sun8i_mixer_commit(struct sunxi_engine *engine,
enable = plane_state->crtc && plane_state->visible;
zpos = plane_state->normalized_zpos;
x = plane_state->dst.x1;
y = plane_state->dst.y1;
w = drm_rect_width(&plane_state->dst);
h = drm_rect_height(&plane_state->dst);
DRM_DEBUG_DRIVER(" plane %d: chan=%d ovl=%d en=%d zpos=%d\n",
plane->base.id, layer->channel, layer->overlay,
enable, zpos);
/*
* We always update the layer enable bit, because it can clear
* spontaneously for unknown reasons.
*/
sun8i_layer_enable(layer, enable);
DRM_DEBUG_DRIVER(" plane %d: chan=%d ovl=%d en=%d zpos=%d x=%d y=%d w=%d h=%d\n",
plane->base.id, layer->index, layer->overlay,
enable, zpos, x, y, w, h);
if (!enable)
continue;
/* Route layer to pipe based on zpos */
route |= layer->channel << SUN8I_MIXER_BLEND_ROUTE_PIPE_SHIFT(zpos);
route |= layer->index << SUN8I_MIXER_BLEND_ROUTE_PIPE_SHIFT(zpos);
pipe_en |= SUN8I_MIXER_BLEND_PIPE_CTL_EN(zpos);
regmap_write(bld_regs,
SUN8I_MIXER_BLEND_ATTR_COORD(bld_base, zpos),
SUN8I_MIXER_COORD(x, y));
regmap_write(bld_regs,
SUN8I_MIXER_BLEND_ATTR_INSIZE(bld_base, zpos),
SUN8I_MIXER_SIZE(w, h));
}
regmap_write(bld_regs, SUN8I_MIXER_BLEND_ROUTE(bld_base), route);
@ -329,18 +316,30 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm,
{
struct drm_plane **planes;
struct sun8i_mixer *mixer = engine_to_sun8i_mixer(engine);
int plane_cnt = mixer->cfg->ui_num + mixer->cfg->vi_num;
enum drm_plane_type type;
unsigned int phy_index;
int i;
planes = devm_kcalloc(drm->dev,
mixer->cfg->vi_num + mixer->cfg->ui_num + 1,
sizeof(*planes), GFP_KERNEL);
planes = devm_kcalloc(drm->dev, plane_cnt, sizeof(*planes), GFP_KERNEL);
if (!planes)
return ERR_PTR(-ENOMEM);
for (i = 0; i < mixer->cfg->vi_num; i++) {
struct sun8i_layer *layer;
layer = sun8i_vi_layer_init_one(drm, mixer, i);
if (i == 0 && !mixer->cfg->ui_num)
type = DRM_PLANE_TYPE_PRIMARY;
else
type = DRM_PLANE_TYPE_OVERLAY;
phy_index = i;
if (mixer->cfg->de_type == SUN8I_MIXER_DE33)
phy_index = mixer->cfg->map[i];
layer = sun8i_vi_layer_init_one(drm, type, mixer->engine.regs,
i, phy_index, plane_cnt,
&mixer->cfg->lay_cfg);
if (IS_ERR(layer)) {
dev_err(drm->dev,
"Couldn't initialize overlay plane\n");
@ -351,16 +350,28 @@ static struct drm_plane **sun8i_layers_init(struct drm_device *drm,
}
for (i = 0; i < mixer->cfg->ui_num; i++) {
unsigned int index = mixer->cfg->vi_num + i;
struct sun8i_layer *layer;
layer = sun8i_ui_layer_init_one(drm, mixer, i);
if (i == 0)
type = DRM_PLANE_TYPE_PRIMARY;
else
type = DRM_PLANE_TYPE_OVERLAY;
phy_index = index;
if (mixer->cfg->de_type == SUN8I_MIXER_DE33)
phy_index = mixer->cfg->map[index];
layer = sun8i_ui_layer_init_one(drm, type, mixer->engine.regs,
index, phy_index, plane_cnt,
&mixer->cfg->lay_cfg);
if (IS_ERR(layer)) {
dev_err(drm->dev, "Couldn't initialize %s plane\n",
i ? "overlay" : "primary");
return ERR_CAST(layer);
}
planes[mixer->cfg->vi_num + i] = &layer->plane;
planes[index] = &layer->plane;
}
return planes;
@ -693,119 +704,173 @@ static void sun8i_mixer_remove(struct platform_device *pdev)
}
static const struct sun8i_mixer_cfg sun8i_a83t_mixer0_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.lay_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0xf,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.scaler_mask = 0xf,
.scanline_yuv = 2048,
.ui_num = 3,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_a83t_mixer1_cfg = {
.ccsc = CCSC_MIXER1_LAYOUT,
.lay_cfg = {
.ccsc = CCSC_MIXER1_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.ui_num = 1,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_h3_mixer0_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.lay_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0xf,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 432000000,
.scaler_mask = 0xf,
.scanline_yuv = 2048,
.ui_num = 3,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_r40_mixer0_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.lay_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0xf,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 297000000,
.scaler_mask = 0xf,
.scanline_yuv = 2048,
.ui_num = 3,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_r40_mixer1_cfg = {
.ccsc = CCSC_MIXER1_LAYOUT,
.lay_cfg = {
.ccsc = CCSC_MIXER1_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 297000000,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.ui_num = 1,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun8i_v3s_mixer_cfg = {
.de_type = SUN8I_MIXER_DE2,
.vi_num = 2,
.ui_num = 1,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.ccsc = CCSC_MIXER0_LAYOUT,
.mod_rate = 150000000,
.lay_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 2,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 150000000,
.vi_num = 2,
.ui_num = 1,
};
static const struct sun8i_mixer_cfg sun20i_d1_mixer0_cfg = {
.ccsc = CCSC_D1_MIXER0_LAYOUT,
.lay_cfg = {
.ccsc = CCSC_D1_MIXER0_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 297000000,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.ui_num = 1,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun20i_d1_mixer1_cfg = {
.ccsc = CCSC_MIXER1_LAYOUT,
.lay_cfg = {
.ccsc = CCSC_MIXER1_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0x1,
.scanline_yuv = 1024,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 297000000,
.scaler_mask = 0x1,
.scanline_yuv = 1024,
.ui_num = 0,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun50i_a64_mixer0_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.lay_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0xf,
.scanline_yuv = 4096,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 297000000,
.scaler_mask = 0xf,
.scanline_yuv = 4096,
.ui_num = 3,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun50i_a64_mixer1_cfg = {
.ccsc = CCSC_MIXER1_LAYOUT,
.lay_cfg = {
.ccsc = CCSC_MIXER1_LAYOUT,
.de_type = SUN8I_MIXER_DE2,
.vi_scaler_num = 1,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.de2_fcc_alpha = 1,
},
.de_type = SUN8I_MIXER_DE2,
.mod_rate = 297000000,
.scaler_mask = 0x3,
.scanline_yuv = 2048,
.ui_num = 1,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun50i_h6_mixer0_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.lay_cfg = {
.de_type = SUN8I_MIXER_DE3,
.vi_scaler_num = 1,
.scaler_mask = 0xf,
.scanline_yuv = 4096,
},
.de_type = SUN8I_MIXER_DE3,
.mod_rate = 600000000,
.scaler_mask = 0xf,
.scanline_yuv = 4096,
.ui_num = 3,
.vi_num = 1,
};
static const struct sun8i_mixer_cfg sun50i_h616_mixer0_cfg = {
.ccsc = CCSC_MIXER0_LAYOUT,
.lay_cfg = {
.de_type = SUN8I_MIXER_DE33,
.scaler_mask = 0xf,
.scanline_yuv = 4096,
},
.de_type = SUN8I_MIXER_DE33,
.mod_rate = 600000000,
.scaler_mask = 0xf,
.scanline_yuv = 4096,
.ui_num = 3,
.vi_num = 1,
.map = {0, 6, 7, 8},

View File

@ -39,6 +39,9 @@
#define DE3_CH_BASE 0x1000
#define DE3_CH_SIZE 0x0800
#define DE33_CH_BASE 0x1000
#define DE33_CH_SIZE 0x20000
#define SUN8I_MIXER_BLEND_PIPE_CTL(base) ((base) + 0)
#define SUN8I_MIXER_BLEND_ATTR_FCOLOR(base, x) ((base) + 0x4 + 0x10 * (x))
#define SUN8I_MIXER_BLEND_ATTR_INSIZE(base, x) ((base) + 0x8 + 0x10 * (x))
@ -161,29 +164,45 @@ enum sun8i_mixer_type {
};
/**
* struct sun8i_mixer_cfg - mixer HW configuration
* @vi_num: number of VI channels
* @ui_num: number of UI channels
* struct sun8i_layer_cfg - layer configuration
* @vi_scaler_num: Number of VI scalers. Used on DE2 and DE3.
* @scaler_mask: bitmask which tells which channel supports scaling
* First, scaler supports for VI channels is defined and after that, scaler
* support for UI channels. For example, if mixer has 2 VI channels without
* scaler and 2 UI channels with scaler, bitmask would be 0xC.
* @ccsc: select set of CCSC base addresses from the enumeration above.
* @mod_rate: module clock rate that needs to be set in order to have
* a functional block.
* @de_type: sun8i_mixer_type enum representing the display engine generation.
* @scaline_yuv: size of a scanline for VI scaler for YUV formats.
* @map: channel map for DE variants processing YUV separately (DE33)
* @de2_fcc_alpha: use FCC for missing DE2 VI alpha capability
* Most DE2 cores has FCC. If number of VI planes is one, enable this.
*/
struct sun8i_mixer_cfg {
int vi_num;
int ui_num;
struct sun8i_layer_cfg {
unsigned int vi_scaler_num;
int scaler_mask;
int ccsc;
unsigned long mod_rate;
unsigned int de_type;
unsigned int scanline_yuv;
unsigned int map[6];
unsigned int de2_fcc_alpha : 1;
};
/**
* struct sun8i_mixer_cfg - mixer HW configuration
* @lay_cfg: layer configuration
* @vi_num: number of VI channels
* @ui_num: number of UI channels
* @de_type: sun8i_mixer_type enum representing the display engine generation.
* @mod_rate: module clock rate that needs to be set in order to have
* a functional block.
* @map: channel map for DE variants processing YUV separately (DE33)
*/
struct sun8i_mixer_cfg {
struct sun8i_layer_cfg lay_cfg;
int vi_num;
int ui_num;
unsigned int de_type;
unsigned long mod_rate;
unsigned int map[6];
};
struct sun8i_mixer {
@ -206,11 +225,13 @@ enum {
};
struct sun8i_layer {
struct drm_plane plane;
struct sun8i_mixer *mixer;
int type;
int channel;
int overlay;
struct drm_plane plane;
int type;
int index;
int channel;
int overlay;
struct regmap *regs;
const struct sun8i_layer_cfg *cfg;
};
static inline struct sun8i_layer *
@ -239,14 +260,14 @@ sun8i_blender_regmap(struct sun8i_mixer *mixer)
}
static inline u32
sun8i_channel_base(struct sun8i_mixer *mixer, int channel)
sun8i_channel_base(struct sun8i_layer *layer)
{
if (mixer->cfg->de_type == SUN8I_MIXER_DE33)
return mixer->cfg->map[channel] * 0x20000 + DE2_CH_SIZE;
else if (mixer->cfg->de_type == SUN8I_MIXER_DE3)
return DE3_CH_BASE + channel * DE3_CH_SIZE;
if (layer->cfg->de_type == SUN8I_MIXER_DE33)
return DE33_CH_BASE + layer->channel * DE33_CH_SIZE;
else if (layer->cfg->de_type == SUN8I_MIXER_DE3)
return DE3_CH_BASE + layer->channel * DE3_CH_SIZE;
else
return DE2_CH_BASE + channel * DE2_CH_SIZE;
return DE2_CH_BASE + layer->channel * DE2_CH_SIZE;
}
int sun8i_mixer_drm_format_to_hw(u32 format, u32 *hw_format);

View File

@ -26,44 +26,49 @@
#include "sun8i_ui_scaler.h"
#include "sun8i_vi_scaler.h"
static void sun8i_ui_layer_update_alpha(struct sun8i_mixer *mixer, int channel,
int overlay, struct drm_plane *plane)
static void sun8i_ui_layer_disable(struct sun8i_layer *layer)
{
u32 mask, val, ch_base;
u32 ch_base = sun8i_channel_base(layer);
ch_base = sun8i_channel_base(mixer, channel);
mask = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_MASK |
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MASK;
val = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA(plane->state->alpha >> 8);
val |= (plane->state->alpha == DRM_BLEND_ALPHA_OPAQUE) ?
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_PIXEL :
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_COMBINED;
regmap_update_bits(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, overlay),
mask, val);
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, layer->overlay), 0);
}
static int sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel,
int overlay, struct drm_plane *plane,
unsigned int zpos)
static void sun8i_ui_layer_update_attributes(struct sun8i_layer *layer,
struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
const struct drm_format_info *fmt;
u32 val, ch_base, hw_fmt;
ch_base = sun8i_channel_base(layer);
fmt = state->fb->format;
sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt);
val = SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA(state->alpha >> 8);
val |= (state->alpha == DRM_BLEND_ALPHA_OPAQUE) ?
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_PIXEL :
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_ALPHA_MODE_COMBINED;
val |= hw_fmt << SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_OFFSET;
val |= SUN8I_MIXER_CHAN_UI_LAYER_ATTR_EN;
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, layer->overlay), val);
}
static void sun8i_ui_layer_update_coord(struct sun8i_layer *layer,
struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
u32 src_w, src_h, dst_w, dst_h;
struct regmap *bld_regs;
u32 bld_base, ch_base;
u32 outsize, insize;
u32 hphase, vphase;
u32 ch_base;
DRM_DEBUG_DRIVER("Updating UI channel %d overlay %d\n",
channel, overlay);
layer->channel, layer->overlay);
bld_base = sun8i_blender_base(mixer);
bld_regs = sun8i_blender_regmap(mixer);
ch_base = sun8i_channel_base(mixer, channel);
ch_base = sun8i_channel_base(layer);
src_w = drm_rect_width(&state->src) >> 16;
src_h = drm_rect_height(&state->src) >> 16;
@ -80,10 +85,10 @@ static int sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel,
DRM_DEBUG_DRIVER("Layer source offset X: %d Y: %d\n",
state->src.x1 >> 16, state->src.y1 >> 16);
DRM_DEBUG_DRIVER("Layer source size W: %d H: %d\n", src_w, src_h);
regmap_write(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_SIZE(ch_base, overlay),
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_UI_LAYER_SIZE(ch_base, layer->overlay),
insize);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_UI_OVL_SIZE(ch_base),
insize);
@ -95,67 +100,27 @@ static int sun8i_ui_layer_update_coord(struct sun8i_mixer *mixer, int channel,
hscale = state->src_w / state->crtc_w;
vscale = state->src_h / state->crtc_h;
if (mixer->cfg->de_type == SUN8I_MIXER_DE33) {
sun8i_vi_scaler_setup(mixer, channel, src_w, src_h,
dst_w, dst_h, hscale, vscale,
hphase, vphase,
if (layer->cfg->de_type == SUN8I_MIXER_DE33) {
sun8i_vi_scaler_setup(layer, src_w, src_h, dst_w, dst_h,
hscale, vscale, hphase, vphase,
state->fb->format);
sun8i_vi_scaler_enable(mixer, channel, true);
sun8i_vi_scaler_enable(layer, true);
} else {
sun8i_ui_scaler_setup(mixer, channel, src_w, src_h,
dst_w, dst_h, hscale, vscale,
hphase, vphase);
sun8i_ui_scaler_enable(mixer, channel, true);
sun8i_ui_scaler_setup(layer, src_w, src_h, dst_w, dst_h,
hscale, vscale, hphase, vphase);
sun8i_ui_scaler_enable(layer, true);
}
} else {
DRM_DEBUG_DRIVER("HW scaling is not needed\n");
if (mixer->cfg->de_type == SUN8I_MIXER_DE33)
sun8i_vi_scaler_enable(mixer, channel, false);
if (layer->cfg->de_type == SUN8I_MIXER_DE33)
sun8i_vi_scaler_enable(layer, false);
else
sun8i_ui_scaler_enable(mixer, channel, false);
sun8i_ui_scaler_enable(layer, false);
}
/* Set base coordinates */
DRM_DEBUG_DRIVER("Layer destination coordinates X: %d Y: %d\n",
state->dst.x1, state->dst.y1);
DRM_DEBUG_DRIVER("Layer destination size W: %d H: %d\n", dst_w, dst_h);
regmap_write(bld_regs,
SUN8I_MIXER_BLEND_ATTR_COORD(bld_base, zpos),
SUN8I_MIXER_COORD(state->dst.x1, state->dst.y1));
regmap_write(bld_regs,
SUN8I_MIXER_BLEND_ATTR_INSIZE(bld_base, zpos),
outsize);
return 0;
}
static int sun8i_ui_layer_update_formats(struct sun8i_mixer *mixer, int channel,
int overlay, struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
const struct drm_format_info *fmt;
u32 val, ch_base, hw_fmt;
int ret;
ch_base = sun8i_channel_base(mixer, channel);
fmt = state->fb->format;
ret = sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt);
if (ret || fmt->is_yuv) {
DRM_DEBUG_DRIVER("Invalid format\n");
return -EINVAL;
}
val = hw_fmt << SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_OFFSET;
regmap_update_bits(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_ATTR(ch_base, overlay),
SUN8I_MIXER_CHAN_UI_LAYER_ATTR_FBFMT_MASK, val);
return 0;
}
static int sun8i_ui_layer_update_buffer(struct sun8i_mixer *mixer, int channel,
int overlay, struct drm_plane *plane)
static void sun8i_ui_layer_update_buffer(struct sun8i_layer *layer,
struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
struct drm_framebuffer *fb = state->fb;
@ -164,7 +129,7 @@ static int sun8i_ui_layer_update_buffer(struct sun8i_mixer *mixer, int channel,
u32 ch_base;
int bpp;
ch_base = sun8i_channel_base(mixer, channel);
ch_base = sun8i_channel_base(layer);
/* Get the physical address of the buffer in memory */
gem = drm_fb_dma_get_gem_obj(fb, 0);
@ -181,17 +146,15 @@ static int sun8i_ui_layer_update_buffer(struct sun8i_mixer *mixer, int channel,
/* Set the line width */
DRM_DEBUG_DRIVER("Layer line width: %d bytes\n", fb->pitches[0]);
regmap_write(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_PITCH(ch_base, overlay),
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_UI_LAYER_PITCH(ch_base, layer->overlay),
fb->pitches[0]);
DRM_DEBUG_DRIVER("Setting buffer address to %pad\n", &dma_addr);
regmap_write(mixer->engine.regs,
SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(ch_base, overlay),
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_UI_LAYER_TOP_LADDR(ch_base, layer->overlay),
lower_32_bits(dma_addr));
return 0;
}
static int sun8i_ui_layer_atomic_check(struct drm_plane *plane,
@ -202,7 +165,9 @@ static int sun8i_ui_layer_atomic_check(struct drm_plane *plane,
struct sun8i_layer *layer = plane_to_sun8i_layer(plane);
struct drm_crtc *crtc = new_plane_state->crtc;
struct drm_crtc_state *crtc_state;
int min_scale, max_scale;
const struct drm_format_info *fmt;
int min_scale, max_scale, ret;
u32 hw_fmt;
if (!crtc)
return 0;
@ -211,10 +176,17 @@ static int sun8i_ui_layer_atomic_check(struct drm_plane *plane,
if (WARN_ON(!crtc_state))
return -EINVAL;
fmt = new_plane_state->fb->format;
ret = sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt);
if (ret || fmt->is_yuv) {
DRM_DEBUG_DRIVER("Invalid plane format\n");
return -EINVAL;
}
min_scale = DRM_PLANE_NO_SCALING;
max_scale = DRM_PLANE_NO_SCALING;
if (layer->mixer->cfg->scaler_mask & BIT(layer->channel)) {
if (layer->cfg->scaler_mask & BIT(layer->channel)) {
min_scale = SUN8I_UI_SCALER_SCALE_MIN;
max_scale = SUN8I_UI_SCALER_SCALE_MAX;
}
@ -232,20 +204,15 @@ static void sun8i_ui_layer_atomic_update(struct drm_plane *plane,
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
plane);
struct sun8i_layer *layer = plane_to_sun8i_layer(plane);
unsigned int zpos = new_state->normalized_zpos;
struct sun8i_mixer *mixer = layer->mixer;
if (!new_state->crtc || !new_state->visible)
if (!new_state->crtc || !new_state->visible) {
sun8i_ui_layer_disable(layer);
return;
}
sun8i_ui_layer_update_coord(mixer, layer->channel,
layer->overlay, plane, zpos);
sun8i_ui_layer_update_alpha(mixer, layer->channel,
layer->overlay, plane);
sun8i_ui_layer_update_formats(mixer, layer->channel,
layer->overlay, plane);
sun8i_ui_layer_update_buffer(mixer, layer->channel,
layer->overlay, plane);
sun8i_ui_layer_update_attributes(layer, plane);
sun8i_ui_layer_update_coord(layer, plane);
sun8i_ui_layer_update_buffer(layer, plane);
}
static const struct drm_plane_helper_funcs sun8i_ui_layer_helper_funcs = {
@ -291,21 +258,25 @@ static const uint64_t sun8i_layer_modifiers[] = {
};
struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm,
struct sun8i_mixer *mixer,
int index)
enum drm_plane_type type,
struct regmap *regs,
int index, int phy_index,
int plane_cnt,
const struct sun8i_layer_cfg *cfg)
{
enum drm_plane_type type = DRM_PLANE_TYPE_OVERLAY;
int channel = mixer->cfg->vi_num + index;
struct sun8i_layer *layer;
unsigned int plane_cnt;
int ret;
layer = devm_kzalloc(drm->dev, sizeof(*layer), GFP_KERNEL);
if (!layer)
return ERR_PTR(-ENOMEM);
if (index == 0)
type = DRM_PLANE_TYPE_PRIMARY;
layer->type = SUN8I_LAYER_TYPE_UI;
layer->index = index;
layer->channel = phy_index;
layer->overlay = 0;
layer->regs = regs;
layer->cfg = cfg;
/* possible crtcs are set later */
ret = drm_universal_plane_init(drm, &layer->plane, 0,
@ -318,15 +289,13 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm,
return ERR_PTR(ret);
}
plane_cnt = mixer->cfg->ui_num + mixer->cfg->vi_num;
ret = drm_plane_create_alpha_property(&layer->plane);
if (ret) {
dev_err(drm->dev, "Couldn't add alpha property\n");
return ERR_PTR(ret);
}
ret = drm_plane_create_zpos_property(&layer->plane, channel,
ret = drm_plane_create_zpos_property(&layer->plane, index,
0, plane_cnt - 1);
if (ret) {
dev_err(drm->dev, "Couldn't add zpos property\n");
@ -334,10 +303,6 @@ struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm,
}
drm_plane_helper_add(&layer->plane, &sun8i_ui_layer_helper_funcs);
layer->mixer = mixer;
layer->type = SUN8I_LAYER_TYPE_UI;
layer->channel = channel;
layer->overlay = 0;
return layer;
}

View File

@ -50,6 +50,9 @@ struct sun8i_mixer;
struct sun8i_layer;
struct sun8i_layer *sun8i_ui_layer_init_one(struct drm_device *drm,
struct sun8i_mixer *mixer,
int index);
enum drm_plane_type type,
struct regmap *regs,
int index, int phy_index,
int plane_cnt,
const struct sun8i_layer_cfg *cfg);
#endif /* _SUN8I_UI_LAYER_H_ */

View File

@ -89,18 +89,18 @@ static const u32 lan2coefftab16[240] = {
0x0b1c1603, 0x0d1c1502, 0x0e1d1401, 0x0f1d1301,
};
static u32 sun8i_ui_scaler_base(struct sun8i_mixer *mixer, int channel)
static u32 sun8i_ui_scaler_base(struct sun8i_layer *layer)
{
int vi_num = mixer->cfg->vi_num;
int offset = layer->cfg->vi_scaler_num;
if (mixer->cfg->de_type == SUN8I_MIXER_DE3)
if (layer->cfg->de_type == SUN8I_MIXER_DE3)
return DE3_VI_SCALER_UNIT_BASE +
DE3_VI_SCALER_UNIT_SIZE * vi_num +
DE3_UI_SCALER_UNIT_SIZE * (channel - vi_num);
DE3_VI_SCALER_UNIT_SIZE * offset +
DE3_UI_SCALER_UNIT_SIZE * (layer->channel - offset);
else
return DE2_VI_SCALER_UNIT_BASE +
DE2_VI_SCALER_UNIT_SIZE * vi_num +
DE2_UI_SCALER_UNIT_SIZE * (channel - vi_num);
DE2_VI_SCALER_UNIT_SIZE * offset +
DE2_UI_SCALER_UNIT_SIZE * (layer->channel - offset);
}
static int sun8i_ui_scaler_coef_index(unsigned int step)
@ -127,14 +127,11 @@ static int sun8i_ui_scaler_coef_index(unsigned int step)
}
}
void sun8i_ui_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable)
void sun8i_ui_scaler_enable(struct sun8i_layer *layer, bool enable)
{
u32 val, base;
if (WARN_ON(layer < mixer->cfg->vi_num))
return;
base = sun8i_ui_scaler_base(mixer, layer);
base = sun8i_ui_scaler_base(layer);
if (enable)
val = SUN8I_SCALER_GSU_CTRL_EN |
@ -142,10 +139,10 @@ void sun8i_ui_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable)
else
val = 0;
regmap_write(mixer->engine.regs, SUN8I_SCALER_GSU_CTRL(base), val);
regmap_write(layer->regs, SUN8I_SCALER_GSU_CTRL(base), val);
}
void sun8i_ui_scaler_setup(struct sun8i_mixer *mixer, int layer,
void sun8i_ui_scaler_setup(struct sun8i_layer *layer,
u32 src_w, u32 src_h, u32 dst_w, u32 dst_h,
u32 hscale, u32 vscale, u32 hphase, u32 vphase)
{
@ -153,10 +150,7 @@ void sun8i_ui_scaler_setup(struct sun8i_mixer *mixer, int layer,
int i, offset;
u32 base;
if (WARN_ON(layer < mixer->cfg->vi_num))
return;
base = sun8i_ui_scaler_base(mixer, layer);
base = sun8i_ui_scaler_base(layer);
hphase <<= SUN8I_UI_SCALER_PHASE_FRAC - 16;
vphase <<= SUN8I_UI_SCALER_PHASE_FRAC - 16;
@ -166,22 +160,22 @@ void sun8i_ui_scaler_setup(struct sun8i_mixer *mixer, int layer,
insize = SUN8I_UI_SCALER_SIZE(src_w, src_h);
outsize = SUN8I_UI_SCALER_SIZE(dst_w, dst_h);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_GSU_OUTSIZE(base), outsize);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_GSU_INSIZE(base), insize);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_GSU_HSTEP(base), hscale);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_GSU_VSTEP(base), vscale);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_GSU_HPHASE(base), hphase);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_GSU_VPHASE(base), vphase);
offset = sun8i_ui_scaler_coef_index(hscale) *
SUN8I_UI_SCALER_COEFF_COUNT;
for (i = 0; i < SUN8I_UI_SCALER_COEFF_COUNT; i++)
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_GSU_HCOEFF(base, i),
lan2coefftab16[offset + i]);
}

View File

@ -35,8 +35,8 @@
#define SUN8I_SCALER_GSU_CTRL_EN BIT(0)
#define SUN8I_SCALER_GSU_CTRL_COEFF_RDY BIT(4)
void sun8i_ui_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable);
void sun8i_ui_scaler_setup(struct sun8i_mixer *mixer, int layer,
void sun8i_ui_scaler_enable(struct sun8i_layer *layer, bool enable);
void sun8i_ui_scaler_setup(struct sun8i_layer *layer,
u32 src_w, u32 src_h, u32 dst_w, u32 dst_h,
u32 hscale, u32 vscale, u32 hphase, u32 vphase);

View File

@ -14,62 +14,71 @@
#include <drm/drm_print.h>
#include <drm/drm_probe_helper.h>
#include "sun4i_crtc.h"
#include "sun8i_csc.h"
#include "sun8i_mixer.h"
#include "sun8i_vi_layer.h"
#include "sun8i_vi_scaler.h"
static void sun8i_vi_layer_update_alpha(struct sun8i_mixer *mixer, int channel,
int overlay, struct drm_plane *plane)
static void sun8i_vi_layer_disable(struct sun8i_layer *layer)
{
u32 mask, val, ch_base;
u32 ch_base = sun8i_channel_base(layer);
ch_base = sun8i_channel_base(mixer, channel);
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay), 0);
}
if (mixer->cfg->de_type >= SUN8I_MIXER_DE3) {
mask = SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MASK |
SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_MASK;
val = SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA
(plane->state->alpha >> 8);
static void sun8i_vi_layer_update_attributes(struct sun8i_layer *layer,
struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
const struct drm_format_info *fmt;
u32 val, ch_base, hw_fmt;
val |= (plane->state->alpha == DRM_BLEND_ALPHA_OPAQUE) ?
ch_base = sun8i_channel_base(layer);
fmt = state->fb->format;
sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt);
val = hw_fmt << SUN8I_MIXER_CHAN_VI_LAYER_ATTR_FBFMT_OFFSET;
if (!fmt->is_yuv)
val |= SUN8I_MIXER_CHAN_VI_LAYER_ATTR_RGB_MODE;
val |= SUN8I_MIXER_CHAN_VI_LAYER_ATTR_EN;
if (layer->cfg->de_type >= SUN8I_MIXER_DE3) {
val |= SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA(state->alpha >> 8);
val |= (state->alpha == DRM_BLEND_ALPHA_OPAQUE) ?
SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_PIXEL :
SUN50I_MIXER_CHAN_VI_LAYER_ATTR_ALPHA_MODE_COMBINED;
}
regmap_update_bits(mixer->engine.regs,
SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base,
overlay),
mask, val);
} else if (mixer->cfg->vi_num == 1) {
regmap_update_bits(mixer->engine.regs,
SUN8I_MIXER_FCC_GLOBAL_ALPHA_REG,
SUN8I_MIXER_FCC_GLOBAL_ALPHA_MASK,
SUN8I_MIXER_FCC_GLOBAL_ALPHA
(plane->state->alpha >> 8));
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, layer->overlay), val);
if (layer->cfg->de2_fcc_alpha) {
regmap_write(layer->regs,
SUN8I_MIXER_FCC_GLOBAL_ALPHA_REG,
SUN8I_MIXER_FCC_GLOBAL_ALPHA(state->alpha >> 8));
}
}
static int sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel,
int overlay, struct drm_plane *plane,
unsigned int zpos)
static void sun8i_vi_layer_update_coord(struct sun8i_layer *layer,
struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
struct sun4i_crtc *scrtc = drm_crtc_to_sun4i_crtc(state->crtc);
struct sun8i_mixer *mixer = engine_to_sun8i_mixer(scrtc->engine);
const struct drm_format_info *format = state->fb->format;
u32 src_w, src_h, dst_w, dst_h;
struct regmap *bld_regs;
u32 bld_base, ch_base;
u32 outsize, insize;
u32 hphase, vphase;
u32 hn = 0, hm = 0;
u32 vn = 0, vm = 0;
bool subsampled;
u32 ch_base;
DRM_DEBUG_DRIVER("Updating VI channel %d overlay %d\n",
channel, overlay);
layer->channel, layer->overlay);
bld_base = sun8i_blender_base(mixer);
bld_regs = sun8i_blender_regmap(mixer);
ch_base = sun8i_channel_base(mixer, channel);
ch_base = sun8i_channel_base(layer);
src_w = drm_rect_width(&state->src) >> 16;
src_h = drm_rect_height(&state->src) >> 16;
@ -106,10 +115,10 @@ static int sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel,
(state->src.x1 >> 16) & ~(format->hsub - 1),
(state->src.y1 >> 16) & ~(format->vsub - 1));
DRM_DEBUG_DRIVER("Layer source size W: %d H: %d\n", src_w, src_h);
regmap_write(mixer->engine.regs,
SUN8I_MIXER_CHAN_VI_LAYER_SIZE(ch_base, overlay),
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_LAYER_SIZE(ch_base, layer->overlay),
insize);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_OVL_SIZE(ch_base),
insize);
@ -144,7 +153,7 @@ static int sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel,
}
/* it seems that every RGB scaler has buffer for 2048 pixels */
scanline = subsampled ? mixer->cfg->scanline_yuv : 2048;
scanline = subsampled ? layer->cfg->scanline_yuv : 2048;
if (src_w > scanline) {
DRM_DEBUG_DRIVER("Using horizontal coarse scaling\n");
@ -156,108 +165,34 @@ static int sun8i_vi_layer_update_coord(struct sun8i_mixer *mixer, int channel,
hscale = (src_w << 16) / dst_w;
vscale = (src_h << 16) / dst_h;
sun8i_vi_scaler_setup(mixer, channel, src_w, src_h, dst_w,
dst_h, hscale, vscale, hphase, vphase,
format);
sun8i_vi_scaler_enable(mixer, channel, true);
sun8i_vi_scaler_setup(layer, src_w, src_h, dst_w, dst_h,
hscale, vscale, hphase, vphase, format);
sun8i_vi_scaler_enable(layer, true);
} else {
DRM_DEBUG_DRIVER("HW scaling is not needed\n");
sun8i_vi_scaler_enable(mixer, channel, false);
sun8i_vi_scaler_enable(layer, false);
}
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_HDS_Y(ch_base),
SUN8I_MIXER_CHAN_VI_DS_N(hn) |
SUN8I_MIXER_CHAN_VI_DS_M(hm));
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_HDS_UV(ch_base),
SUN8I_MIXER_CHAN_VI_DS_N(hn) |
SUN8I_MIXER_CHAN_VI_DS_M(hm));
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_VDS_Y(ch_base),
SUN8I_MIXER_CHAN_VI_DS_N(vn) |
SUN8I_MIXER_CHAN_VI_DS_M(vm));
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_VDS_UV(ch_base),
SUN8I_MIXER_CHAN_VI_DS_N(vn) |
SUN8I_MIXER_CHAN_VI_DS_M(vm));
/* Set base coordinates */
DRM_DEBUG_DRIVER("Layer destination coordinates X: %d Y: %d\n",
state->dst.x1, state->dst.y1);
DRM_DEBUG_DRIVER("Layer destination size W: %d H: %d\n", dst_w, dst_h);
regmap_write(bld_regs,
SUN8I_MIXER_BLEND_ATTR_COORD(bld_base, zpos),
SUN8I_MIXER_COORD(state->dst.x1, state->dst.y1));
regmap_write(bld_regs,
SUN8I_MIXER_BLEND_ATTR_INSIZE(bld_base, zpos),
outsize);
return 0;
}
static u32 sun8i_vi_layer_get_csc_mode(const struct drm_format_info *format)
{
if (!format->is_yuv)
return SUN8I_CSC_MODE_OFF;
switch (format->format) {
case DRM_FORMAT_YVU411:
case DRM_FORMAT_YVU420:
case DRM_FORMAT_YVU422:
case DRM_FORMAT_YVU444:
return SUN8I_CSC_MODE_YVU2RGB;
default:
return SUN8I_CSC_MODE_YUV2RGB;
}
}
static int sun8i_vi_layer_update_formats(struct sun8i_mixer *mixer, int channel,
int overlay, struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
u32 val, ch_base, csc_mode, hw_fmt;
const struct drm_format_info *fmt;
int ret;
ch_base = sun8i_channel_base(mixer, channel);
fmt = state->fb->format;
ret = sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt);
if (ret) {
DRM_DEBUG_DRIVER("Invalid format\n");
return ret;
}
val = hw_fmt << SUN8I_MIXER_CHAN_VI_LAYER_ATTR_FBFMT_OFFSET;
regmap_update_bits(mixer->engine.regs,
SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, overlay),
SUN8I_MIXER_CHAN_VI_LAYER_ATTR_FBFMT_MASK, val);
csc_mode = sun8i_vi_layer_get_csc_mode(fmt);
if (csc_mode != SUN8I_CSC_MODE_OFF) {
sun8i_csc_set_ccsc_coefficients(mixer, channel, csc_mode,
state->color_encoding,
state->color_range);
sun8i_csc_enable_ccsc(mixer, channel, true);
} else {
sun8i_csc_enable_ccsc(mixer, channel, false);
}
if (!fmt->is_yuv)
val = SUN8I_MIXER_CHAN_VI_LAYER_ATTR_RGB_MODE;
else
val = 0;
regmap_update_bits(mixer->engine.regs,
SUN8I_MIXER_CHAN_VI_LAYER_ATTR(ch_base, overlay),
SUN8I_MIXER_CHAN_VI_LAYER_ATTR_RGB_MODE, val);
return 0;
}
static int sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel,
int overlay, struct drm_plane *plane)
static void sun8i_vi_layer_update_buffer(struct sun8i_layer *layer,
struct drm_plane *plane)
{
struct drm_plane_state *state = plane->state;
struct drm_framebuffer *fb = state->fb;
@ -268,7 +203,7 @@ static int sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel,
u32 ch_base;
int i;
ch_base = sun8i_channel_base(mixer, channel);
ch_base = sun8i_channel_base(layer);
/* Adjust x and y to be dividable by subsampling factor */
src_x = (state->src.x1 >> 16) & ~(format->hsub - 1);
@ -298,21 +233,19 @@ static int sun8i_vi_layer_update_buffer(struct sun8i_mixer *mixer, int channel,
/* Set the line width */
DRM_DEBUG_DRIVER("Layer %d. line width: %d bytes\n",
i + 1, fb->pitches[i]);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_LAYER_PITCH(ch_base,
overlay, i),
layer->overlay, i),
fb->pitches[i]);
DRM_DEBUG_DRIVER("Setting %d. buffer address to %pad\n",
i + 1, &dma_addr);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_MIXER_CHAN_VI_LAYER_TOP_LADDR(ch_base,
overlay, i),
layer->overlay, i),
lower_32_bits(dma_addr));
}
return 0;
}
static int sun8i_vi_layer_atomic_check(struct drm_plane *plane,
@ -323,7 +256,9 @@ static int sun8i_vi_layer_atomic_check(struct drm_plane *plane,
struct sun8i_layer *layer = plane_to_sun8i_layer(plane);
struct drm_crtc *crtc = new_plane_state->crtc;
struct drm_crtc_state *crtc_state;
int min_scale, max_scale;
const struct drm_format_info *fmt;
int min_scale, max_scale, ret;
u32 hw_fmt;
if (!crtc)
return 0;
@ -332,10 +267,17 @@ static int sun8i_vi_layer_atomic_check(struct drm_plane *plane,
if (WARN_ON(!crtc_state))
return -EINVAL;
fmt = new_plane_state->fb->format;
ret = sun8i_mixer_drm_format_to_hw(fmt->format, &hw_fmt);
if (ret) {
DRM_DEBUG_DRIVER("Invalid plane format\n");
return ret;
}
min_scale = DRM_PLANE_NO_SCALING;
max_scale = DRM_PLANE_NO_SCALING;
if (layer->mixer->cfg->scaler_mask & BIT(layer->channel)) {
if (layer->cfg->scaler_mask & BIT(layer->channel)) {
min_scale = SUN8I_VI_SCALER_SCALE_MIN;
max_scale = SUN8I_VI_SCALER_SCALE_MAX;
}
@ -352,20 +294,16 @@ static void sun8i_vi_layer_atomic_update(struct drm_plane *plane,
struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state,
plane);
struct sun8i_layer *layer = plane_to_sun8i_layer(plane);
unsigned int zpos = new_state->normalized_zpos;
struct sun8i_mixer *mixer = layer->mixer;
if (!new_state->crtc || !new_state->visible)
if (!new_state->crtc || !new_state->visible) {
sun8i_vi_layer_disable(layer);
return;
}
sun8i_vi_layer_update_coord(mixer, layer->channel,
layer->overlay, plane, zpos);
sun8i_vi_layer_update_alpha(mixer, layer->channel,
layer->overlay, plane);
sun8i_vi_layer_update_formats(mixer, layer->channel,
layer->overlay, plane);
sun8i_vi_layer_update_buffer(mixer, layer->channel,
layer->overlay, plane);
sun8i_vi_layer_update_attributes(layer, plane);
sun8i_vi_layer_update_coord(layer, plane);
sun8i_csc_config(layer, new_state);
sun8i_vi_layer_update_buffer(layer, plane);
}
static const struct drm_plane_helper_funcs sun8i_vi_layer_helper_funcs = {
@ -471,12 +409,14 @@ static const uint64_t sun8i_layer_modifiers[] = {
};
struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm,
struct sun8i_mixer *mixer,
int index)
enum drm_plane_type type,
struct regmap *regs,
int index, int phy_index,
int plane_cnt,
const struct sun8i_layer_cfg *cfg)
{
enum drm_plane_type type = DRM_PLANE_TYPE_OVERLAY;
u32 supported_encodings, supported_ranges;
unsigned int plane_cnt, format_count;
unsigned int format_count;
struct sun8i_layer *layer;
const u32 *formats;
int ret;
@ -485,7 +425,14 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm,
if (!layer)
return ERR_PTR(-ENOMEM);
if (mixer->cfg->de_type >= SUN8I_MIXER_DE3) {
layer->type = SUN8I_LAYER_TYPE_VI;
layer->index = index;
layer->channel = phy_index;
layer->overlay = 0;
layer->regs = regs;
layer->cfg = cfg;
if (layer->cfg->de_type >= SUN8I_MIXER_DE3) {
formats = sun8i_vi_layer_de3_formats;
format_count = ARRAY_SIZE(sun8i_vi_layer_de3_formats);
} else {
@ -493,9 +440,6 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm,
format_count = ARRAY_SIZE(sun8i_vi_layer_formats);
}
if (!mixer->cfg->ui_num && index == 0)
type = DRM_PLANE_TYPE_PRIMARY;
/* possible crtcs are set later */
ret = drm_universal_plane_init(drm, &layer->plane, 0,
&sun8i_vi_layer_funcs,
@ -507,9 +451,7 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm,
return ERR_PTR(ret);
}
plane_cnt = mixer->cfg->ui_num + mixer->cfg->vi_num;
if (mixer->cfg->vi_num == 1 || mixer->cfg->de_type >= SUN8I_MIXER_DE3) {
if (layer->cfg->de2_fcc_alpha || layer->cfg->de_type >= SUN8I_MIXER_DE3) {
ret = drm_plane_create_alpha_property(&layer->plane);
if (ret) {
dev_err(drm->dev, "Couldn't add alpha property\n");
@ -526,7 +468,7 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm,
supported_encodings = BIT(DRM_COLOR_YCBCR_BT601) |
BIT(DRM_COLOR_YCBCR_BT709);
if (mixer->cfg->de_type >= SUN8I_MIXER_DE3)
if (layer->cfg->de_type >= SUN8I_MIXER_DE3)
supported_encodings |= BIT(DRM_COLOR_YCBCR_BT2020);
supported_ranges = BIT(DRM_COLOR_YCBCR_LIMITED_RANGE) |
@ -543,10 +485,6 @@ struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm,
}
drm_plane_helper_add(&layer->plane, &sun8i_vi_layer_helper_funcs);
layer->mixer = mixer;
layer->type = SUN8I_LAYER_TYPE_VI;
layer->channel = index;
layer->overlay = 0;
return layer;
}

View File

@ -55,6 +55,9 @@ struct sun8i_mixer;
struct sun8i_layer;
struct sun8i_layer *sun8i_vi_layer_init_one(struct drm_device *drm,
struct sun8i_mixer *mixer,
int index);
enum drm_plane_type type,
struct regmap *regs,
int index, int phy_index,
int plane_cnt,
const struct sun8i_layer_cfg *cfg);
#endif /* _SUN8I_VI_LAYER_H_ */

View File

@ -833,16 +833,17 @@ static const u32 bicubic4coefftab32[480] = {
0x1012110d, 0x1012110d, 0x1013110c, 0x1013110c,
};
static u32 sun8i_vi_scaler_base(struct sun8i_mixer *mixer, int channel)
static u32 sun8i_vi_scaler_base(struct sun8i_layer *layer)
{
if (mixer->cfg->de_type == SUN8I_MIXER_DE33)
return sun8i_channel_base(mixer, channel) + 0x3000;
else if (mixer->cfg->de_type == SUN8I_MIXER_DE3)
if (layer->cfg->de_type == SUN8I_MIXER_DE33)
return DE33_VI_SCALER_UNIT_BASE +
DE33_CH_SIZE * layer->channel;
else if (layer->cfg->de_type == SUN8I_MIXER_DE3)
return DE3_VI_SCALER_UNIT_BASE +
DE3_VI_SCALER_UNIT_SIZE * channel;
DE3_VI_SCALER_UNIT_SIZE * layer->channel;
else
return DE2_VI_SCALER_UNIT_BASE +
DE2_VI_SCALER_UNIT_SIZE * channel;
DE2_VI_SCALER_UNIT_SIZE * layer->channel;
}
static int sun8i_vi_scaler_coef_index(unsigned int step)
@ -909,11 +910,11 @@ static void sun8i_vi_scaler_set_coeff(struct regmap *map, u32 base,
}
}
void sun8i_vi_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable)
void sun8i_vi_scaler_enable(struct sun8i_layer *layer, bool enable)
{
u32 val, base;
base = sun8i_vi_scaler_base(mixer, layer);
base = sun8i_vi_scaler_base(layer);
if (enable)
val = SUN8I_SCALER_VSU_CTRL_EN |
@ -921,11 +922,11 @@ void sun8i_vi_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable)
else
val = 0;
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_VSU_CTRL(base), val);
}
void sun8i_vi_scaler_setup(struct sun8i_mixer *mixer, int layer,
void sun8i_vi_scaler_setup(struct sun8i_layer *layer,
u32 src_w, u32 src_h, u32 dst_w, u32 dst_h,
u32 hscale, u32 vscale, u32 hphase, u32 vphase,
const struct drm_format_info *format)
@ -934,7 +935,7 @@ void sun8i_vi_scaler_setup(struct sun8i_mixer *mixer, int layer,
u32 insize, outsize;
u32 base;
base = sun8i_vi_scaler_base(mixer, layer);
base = sun8i_vi_scaler_base(layer);
hphase <<= SUN8I_VI_SCALER_PHASE_FRAC - 16;
vphase <<= SUN8I_VI_SCALER_PHASE_FRAC - 16;
@ -958,7 +959,7 @@ void sun8i_vi_scaler_setup(struct sun8i_mixer *mixer, int layer,
cvphase = vphase;
}
if (mixer->cfg->de_type >= SUN8I_MIXER_DE3) {
if (layer->cfg->de_type >= SUN8I_MIXER_DE3) {
u32 val;
if (format->hsub == 1 && format->vsub == 1)
@ -966,36 +967,36 @@ void sun8i_vi_scaler_setup(struct sun8i_mixer *mixer, int layer,
else
val = SUN50I_SCALER_VSU_SCALE_MODE_NORMAL;
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN50I_SCALER_VSU_SCALE_MODE(base), val);
}
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_VSU_OUTSIZE(base), outsize);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_VSU_YINSIZE(base), insize);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_VSU_YHSTEP(base), hscale);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_VSU_YVSTEP(base), vscale);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_VSU_YHPHASE(base), hphase);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_VSU_YVPHASE(base), vphase);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_VSU_CINSIZE(base),
SUN8I_VI_SCALER_SIZE(src_w / format->hsub,
src_h / format->vsub));
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_VSU_CHSTEP(base),
hscale / format->hsub);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_VSU_CVSTEP(base),
vscale / format->vsub);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_VSU_CHPHASE(base), chphase);
regmap_write(mixer->engine.regs,
regmap_write(layer->regs,
SUN8I_SCALER_VSU_CVPHASE(base), cvphase);
sun8i_vi_scaler_set_coeff(mixer->engine.regs, base,
sun8i_vi_scaler_set_coeff(layer->regs, base,
hscale, vscale, format);
}

View File

@ -18,6 +18,8 @@
#define DE3_VI_SCALER_UNIT_BASE 0x20000
#define DE3_VI_SCALER_UNIT_SIZE 0x08000
#define DE33_VI_SCALER_UNIT_BASE 0x4000
/* this two macros assumes 16 fractional bits which is standard in DRM */
#define SUN8I_VI_SCALER_SCALE_MIN 1
#define SUN8I_VI_SCALER_SCALE_MAX ((1UL << 20) - 1)
@ -69,8 +71,8 @@
#define SUN50I_SCALER_VSU_ANGLE_SHIFT(x) (((x) << 16) & 0xF)
#define SUN50I_SCALER_VSU_ANGLE_OFFSET(x) ((x) & 0xFF)
void sun8i_vi_scaler_enable(struct sun8i_mixer *mixer, int layer, bool enable);
void sun8i_vi_scaler_setup(struct sun8i_mixer *mixer, int layer,
void sun8i_vi_scaler_enable(struct sun8i_layer *layer, bool enable);
void sun8i_vi_scaler_setup(struct sun8i_layer *layer,
u32 src_w, u32 src_h, u32 dst_w, u32 dst_h,
u32 hscale, u32 vscale, u32 hphase, u32 vphase,
const struct drm_format_info *format);

View File

@ -248,8 +248,7 @@ static void tidss_crtc_atomic_enable(struct drm_crtc *crtc,
dispc_vp_enable(tidss->dispc, tcrtc->hw_videoport);
if (crtc->state->event) {
unsigned int pipe = drm_crtc_index(crtc);
struct drm_vblank_crtc *vblank = &ddev->vblank[pipe];
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
vblank->time = ktime_get();

View File

@ -58,12 +58,6 @@ static const u16 tidss_k2g_common_regs[DISPC_COMMON_REG_TABLE_LEN] = {
};
const struct dispc_features dispc_k2g_feats = {
.min_pclk_khz = 4375,
.max_pclk_khz = {
[DISPC_VP_DPI] = 150000,
},
/*
* XXX According TRM the RGB input buffer width up to 2560 should
* work on 3 taps, but in practice it only works up to 1280.
@ -146,11 +140,6 @@ static const u16 tidss_am65x_common_regs[DISPC_COMMON_REG_TABLE_LEN] = {
};
const struct dispc_features dispc_am65x_feats = {
.max_pclk_khz = {
[DISPC_VP_DPI] = 165000,
[DISPC_VP_OLDI_AM65X] = 165000,
},
.scaling = {
.in_width_max_5tap_rgb = 1280,
.in_width_max_3tap_rgb = 2560,
@ -246,11 +235,6 @@ static const u16 tidss_j721e_common_regs[DISPC_COMMON_REG_TABLE_LEN] = {
};
const struct dispc_features dispc_j721e_feats = {
.max_pclk_khz = {
[DISPC_VP_DPI] = 170000,
[DISPC_VP_INTERNAL] = 600000,
},
.scaling = {
.in_width_max_5tap_rgb = 2048,
.in_width_max_3tap_rgb = 4096,
@ -317,11 +301,6 @@ const struct dispc_features dispc_j721e_feats = {
};
const struct dispc_features dispc_am625_feats = {
.max_pclk_khz = {
[DISPC_VP_DPI] = 165000,
[DISPC_VP_INTERNAL] = 170000,
},
.scaling = {
.in_width_max_5tap_rgb = 1280,
.in_width_max_3tap_rgb = 2560,
@ -378,15 +357,6 @@ const struct dispc_features dispc_am625_feats = {
};
const struct dispc_features dispc_am62a7_feats = {
/*
* if the code reaches dispc_mode_valid with VP1,
* it should return MODE_BAD.
*/
.max_pclk_khz = {
[DISPC_VP_TIED_OFF] = 0,
[DISPC_VP_DPI] = 165000,
},
.scaling = {
.in_width_max_5tap_rgb = 1280,
.in_width_max_3tap_rgb = 2560,
@ -443,10 +413,6 @@ const struct dispc_features dispc_am62a7_feats = {
};
const struct dispc_features dispc_am62l_feats = {
.max_pclk_khz = {
[DISPC_VP_DPI] = 165000,
},
.subrev = DISPC_AM62L,
.common = "common",
@ -1324,33 +1290,61 @@ static void dispc_vp_set_default_color(struct dispc_device *dispc,
DISPC_OVR_DEFAULT_COLOR2, (v >> 32) & 0xffff);
}
/*
* Calculate the percentage difference between the requested pixel clock rate
* and the effective rate resulting from calculating the clock divider value.
*/
unsigned int dispc_pclk_diff(unsigned long rate, unsigned long real_rate)
{
int r = rate / 100, rr = real_rate / 100;
return (unsigned int)(abs(((rr - r) * 100) / r));
}
static int check_pixel_clock(struct dispc_device *dispc, u32 hw_videoport,
unsigned long clock)
{
unsigned long round_clock;
/*
* For VP's with external clocking, clock operations must be
* delegated to respective driver, so we skip the check here.
*/
if (dispc->tidss->is_ext_vp_clk[hw_videoport])
return 0;
round_clock = clk_round_rate(dispc->vp_clk[hw_videoport], clock);
/*
* To keep the check consistent with dispc_vp_set_clk_rate(), we
* use the same 5% check here.
*/
if (dispc_pclk_diff(clock, round_clock) > 5)
return -EINVAL;
return 0;
}
enum drm_mode_status dispc_vp_mode_valid(struct dispc_device *dispc,
u32 hw_videoport,
const struct drm_display_mode *mode)
{
u32 hsw, hfp, hbp, vsw, vfp, vbp;
enum dispc_vp_bus_type bus_type;
int max_pclk;
bus_type = dispc->feat->vp_bus_type[hw_videoport];
max_pclk = dispc->feat->max_pclk_khz[bus_type];
if (WARN_ON(max_pclk == 0))
if (WARN_ON(bus_type == DISPC_VP_TIED_OFF))
return MODE_BAD;
if (mode->clock < dispc->feat->min_pclk_khz)
return MODE_CLOCK_LOW;
if (mode->clock > max_pclk)
return MODE_CLOCK_HIGH;
if (mode->hdisplay > 4096)
return MODE_BAD;
if (mode->vdisplay > 4096)
return MODE_BAD;
if (check_pixel_clock(dispc, hw_videoport, mode->clock * 1000))
return MODE_CLOCK_RANGE;
/* TODO: add interlace support */
if (mode->flags & DRM_MODE_FLAG_INTERLACE)
return MODE_NO_INTERLACE;
@ -1414,17 +1408,6 @@ void dispc_vp_disable_clk(struct dispc_device *dispc, u32 hw_videoport)
clk_disable_unprepare(dispc->vp_clk[hw_videoport]);
}
/*
* Calculate the percentage difference between the requested pixel clock rate
* and the effective rate resulting from calculating the clock divider value.
*/
unsigned int dispc_pclk_diff(unsigned long rate, unsigned long real_rate)
{
int r = rate / 100, rr = real_rate / 100;
return (unsigned int)(abs(((rr - r) * 100) / r));
}
int dispc_vp_set_clk_rate(struct dispc_device *dispc, u32 hw_videoport,
unsigned long rate)
{

View File

@ -77,9 +77,6 @@ enum dispc_dss_subrevision {
};
struct dispc_features {
int min_pclk_khz;
int max_pclk_khz[DISPC_VP_MAX_BUS_TYPE];
struct dispc_features_scaling scaling;
enum dispc_dss_subrevision subrev;

View File

@ -24,6 +24,8 @@ struct tidss_device {
const struct dispc_features *feat;
struct dispc_device *dispc;
bool is_ext_vp_clk[TIDSS_MAX_PORTS];
unsigned int num_crtcs;
struct drm_crtc *crtcs[TIDSS_MAX_PORTS];

View File

@ -309,6 +309,25 @@ static u32 *tidss_oldi_atomic_get_input_bus_fmts(struct drm_bridge *bridge,
return input_fmts;
}
static enum drm_mode_status
tidss_oldi_mode_valid(struct drm_bridge *bridge,
const struct drm_display_info *info,
const struct drm_display_mode *mode)
{
struct tidss_oldi *oldi = drm_bridge_to_tidss_oldi(bridge);
unsigned long round_clock;
round_clock = clk_round_rate(oldi->serial, mode->clock * 7 * 1000);
/*
* To keep the check consistent with dispc_vp_set_clk_rate(),
* we use the same 5% check here.
*/
if (dispc_pclk_diff(mode->clock * 7 * 1000, round_clock) > 5)
return -EINVAL;
return 0;
}
static const struct drm_bridge_funcs tidss_oldi_bridge_funcs = {
.attach = tidss_oldi_bridge_attach,
.atomic_pre_enable = tidss_oldi_atomic_pre_enable,
@ -317,6 +336,7 @@ static const struct drm_bridge_funcs tidss_oldi_bridge_funcs = {
.atomic_duplicate_state = drm_atomic_helper_bridge_duplicate_state,
.atomic_destroy_state = drm_atomic_helper_bridge_destroy_state,
.atomic_reset = drm_atomic_helper_bridge_reset,
.mode_valid = tidss_oldi_mode_valid,
};
static int get_oldi_mode(struct device_node *oldi_tx, int *companion_instance)
@ -430,6 +450,7 @@ void tidss_oldi_deinit(struct tidss_device *tidss)
for (int i = 0; i < tidss->num_oldis; i++) {
if (tidss->oldis[i]) {
drm_bridge_remove(&tidss->oldis[i]->bridge);
tidss->is_ext_vp_clk[tidss->oldis[i]->parent_vp] = false;
tidss->oldis[i] = NULL;
}
}
@ -580,6 +601,7 @@ int tidss_oldi_init(struct tidss_device *tidss)
oldi->bridge.timings = &default_tidss_oldi_timings;
tidss->oldis[tidss->num_oldis++] = oldi;
tidss->is_ext_vp_clk[oldi->parent_vp] = true;
oldi->tidss = tidss;
drm_bridge_add(&oldi->bridge);

View File

@ -199,7 +199,7 @@ EXPORT_SYMBOL(ttm_device_swapout);
* @dev: The core kernel device pointer for DMA mappings and allocations.
* @mapping: The address space to use for this bo.
* @vma_manager: A pointer to a vma manager.
* @alloc_flags: TTM_ALLOCATION_ flags.
* @alloc_flags: TTM_ALLOCATION_* flags.
*
* Initializes a struct ttm_device:
* Returns:

View File

@ -1067,7 +1067,7 @@ long ttm_pool_backup(struct ttm_pool *pool, struct ttm_tt *tt,
* @pool: the pool to initialize
* @dev: device for DMA allocations and mappings
* @nid: NUMA node to use for allocations
* @alloc_flags: TTM_ALLOCATION_POOL_ flags
* @alloc_flags: TTM_ALLOCATION_POOL_* flags
*
* Initialize the pool and its pool types.
*/

View File

@ -553,6 +553,9 @@ static int vmw_kms_new_framebuffer_surface(struct vmw_private *dev_priv,
memcpy(&vfbs->uo, uo, sizeof(vfbs->uo));
vmw_user_object_ref(&vfbs->uo);
if (vfbs->uo.buffer)
vfbs->base.base.obj[0] = &vfbs->uo.buffer->tbo.base;
*out = &vfbs->base;
ret = drm_framebuffer_init(dev, &vfbs->base.base,

View File

@ -247,9 +247,8 @@ vmw_vkms_get_vblank_timestamp(struct drm_crtc *crtc,
{
struct drm_device *dev = crtc->dev;
struct vmw_private *vmw = vmw_priv(dev);
unsigned int pipe = crtc->index;
struct vmw_display_unit *du = vmw_crtc_to_du(crtc);
struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
if (!vmw->vkms_enabled)
return false;
@ -281,8 +280,7 @@ vmw_vkms_enable_vblank(struct drm_crtc *crtc)
{
struct drm_device *dev = crtc->dev;
struct vmw_private *vmw = vmw_priv(dev);
unsigned int pipe = drm_crtc_index(crtc);
struct drm_vblank_crtc *vblank = &dev->vblank[pipe];
struct drm_vblank_crtc *vblank = drm_crtc_vblank_crtc(crtc);
struct vmw_display_unit *du = vmw_crtc_to_du(crtc);
if (!vmw->vkms_enabled)

View File

@ -221,7 +221,7 @@ struct ttm_device {
struct list_head device_list;
/**
* @alloc_flags: TTM_ALLOCATION_ flags.
* @alloc_flags: TTM_ALLOCATION_* flags.
*/
unsigned int alloc_flags;

View File

@ -64,7 +64,7 @@ struct ttm_pool_type {
*
* @dev: the device we allocate pages for
* @nid: which numa node to use
* @alloc_flags: TTM_ALLOCATION_POOL_ flags
* @alloc_flags: TTM_ALLOCATION_POOL_* flags
* @caching: pools for each caching/order
*/
struct ttm_pool {

View File

@ -443,7 +443,9 @@ enum amdxdna_drm_get_param {
DRM_AMDXDNA_QUERY_FIRMWARE_VERSION = 8,
DRM_AMDXDNA_GET_POWER_MODE,
DRM_AMDXDNA_QUERY_TELEMETRY,
DRM_AMDXDNA_QUERY_RESOURCE_INFO = 12,
DRM_AMDXDNA_GET_FORCE_PREEMPT_STATE,
DRM_AMDXDNA_QUERY_RESOURCE_INFO,
DRM_AMDXDNA_GET_FRAME_BOUNDARY_PREEMPT_STATE,
};
/**
@ -462,6 +464,16 @@ struct amdxdna_drm_get_resource_info {
__u64 npu_task_curr;
};
/**
* struct amdxdna_drm_attribute_state - State of an attribute
*/
struct amdxdna_drm_attribute_state {
/** @state: enabled or disabled */
__u8 state;
/** @pad: MBZ */
__u8 pad[7];
};
/**
* struct amdxdna_drm_query_telemetry_header - Telemetry data header
*/
@ -613,6 +625,8 @@ enum amdxdna_drm_set_param {
DRM_AMDXDNA_SET_POWER_MODE,
DRM_AMDXDNA_WRITE_AIE_MEM,
DRM_AMDXDNA_WRITE_AIE_REG,
DRM_AMDXDNA_SET_FORCE_PREEMPT,
DRM_AMDXDNA_SET_FRAME_BOUNDARY_PREEMPT,
};
/**

View File

@ -54,32 +54,46 @@ extern "C" {
* This asks the kernel to have the GPU execute a render command list.
*/
struct drm_panfrost_submit {
/** Address to GPU mapping of job descriptor */
/**
* @jc: Address to GPU mapping of job descriptor
*/
__u64 jc;
/** An optional array of sync objects to wait on before starting this job. */
/**
* @in_syncs: An optional array of sync objects to wait on
* before starting this job.
*/
__u64 in_syncs;
/** Number of sync objects to wait on before starting this job. */
/**
* @in_sync_count: Number of sync objects to wait on before
* starting this job.
*/
__u32 in_sync_count;
/** An optional sync object to place the completion fence in. */
/**
* @out_sync: An optional sync object to place the completion fence in.
*/
__u32 out_sync;
/** Pointer to a u32 array of the BOs that are referenced by the job. */
/**
* @bo_handles: Pointer to a u32 array of the BOs that are
* referenced by the job.
*/
__u64 bo_handles;
/** Number of BO handles passed in (size is that times 4). */
/**
* @bo_handle_count: Number of BO handles passed in (size is
* that times 4).
*/
__u32 bo_handle_count;
/** A combination of PANFROST_JD_REQ_* */
/**
* @requirements: A combination of PANFROST_JD_REQ_*
*/
__u32 requirements;
/** JM context handle. Zero if you want to use the default context. */
/**
* @jm_ctx_handle: JM context handle. Zero if you want to use the
* default context.
*/
__u32 jm_ctx_handle;
/** Padding field. MBZ. */
/**
* @pad: Padding field. Must be zero.
*/
__u32 pad;
};
@ -92,9 +106,18 @@ struct drm_panfrost_submit {
* completed.
*/
struct drm_panfrost_wait_bo {
/**
* @handle: Handle for the object to wait for.
*/
__u32 handle;
/**
* @pad: Padding, must be zero-filled.
*/
__u32 pad;
__s64 timeout_ns; /* absolute */
/**
* @timeout_ns: absolute number of nanoseconds to wait.
*/
__s64 timeout_ns;
};
/* Valid flags to pass to drm_panfrost_create_bo */
@ -107,16 +130,26 @@ struct drm_panfrost_wait_bo {
* The flags argument is a bit mask of PANFROST_BO_* flags.
*/
struct drm_panfrost_create_bo {
/**
* @size: size of shmem/BO area to create (bytes)
*/
__u32 size;
/**
* @flags: see PANFROST_BO_* flags
*/
__u32 flags;
/** Returned GEM handle for the BO. */
/**
* @handle: Returned GEM handle for the BO.
*/
__u32 handle;
/* Pad, must be zero-filled. */
/**
* @pad: Padding, must be zero-filled.
*/
__u32 pad;
/**
* Returned offset for the BO in the GPU address space. This offset
* is private to the DRM fd and is valid for the lifetime of the GEM
* handle.
* @offset: Returned offset for the BO in the GPU address space.
* This offset is private to the DRM fd and is valid for the
* lifetime of the GEM handle.
*
* This offset value will always be nonzero, since various HW
* units treat 0 specially.
@ -136,10 +169,17 @@ struct drm_panfrost_create_bo {
* used in a future extension.
*/
struct drm_panfrost_mmap_bo {
/** Handle for the object being mapped. */
/**
* @handle: Handle for the object being mapped.
*/
__u32 handle;
/**
* @flags: currently not used (should be zero)
*/
__u32 flags;
/** offset into the drm node to use for subsequent mmap call. */
/**
* @offset: offset into the drm node to use for subsequent mmap call.
*/
__u64 offset;
};
@ -196,7 +236,7 @@ struct drm_panfrost_get_param {
__u64 value;
};
/**
/*
* Returns the offset for the BO in the GPU address space for this DRM fd.
* This is the same value returned by drm_panfrost_create_bo, if that was called
* from this DRM fd.
@ -244,12 +284,14 @@ struct drm_panfrost_madvise {
* struct drm_panfrost_set_label_bo - ioctl argument for labelling Panfrost BOs.
*/
struct drm_panfrost_set_label_bo {
/** @handle: Handle of the buffer object to label. */
/**
* @handle: Handle of the buffer object to label.
*/
__u32 handle;
/** @pad: MBZ. */
/**
* @pad: Must be zero.
*/
__u32 pad;
/**
* @label: User pointer to a NUL-terminated string
*
@ -330,10 +372,13 @@ enum drm_panfrost_jm_ctx_priority {
};
struct drm_panfrost_jm_ctx_create {
/** @handle: Handle of the created JM context */
/**
* @handle: Handle of the created JM context
*/
__u32 handle;
/** @priority: Context priority (see enum drm_panfrost_jm_ctx_priority). */
/**
* @priority: Context priority (see enum drm_panfrost_jm_ctx_priority).
*/
__u32 priority;
};
@ -344,8 +389,9 @@ struct drm_panfrost_jm_ctx_destroy {
* Must be a valid context handle returned by DRM_IOCTL_PANTHOR_JM_CTX_CREATE.
*/
__u32 handle;
/** @pad: Padding field, MBZ. */
/**
* @pad: Padding field, must be zero.
*/
__u32 pad;
};