[Freedreno] [PATCH] drm/msm/dpu: Add CRC support for DPU

Jessica Zhang jesszhan at codeaurora.org
Wed Oct 13 20:21:01 UTC 2021


On 10/11/2021 7:01 PM, Dmitry Baryshkov wrote:
> On 12/10/2021 02:41, Jessica Zhang wrote:
>> Add CRC support to DPU, which is currently not supported by
>> this driver. Only supports CRC for CRTC for now, but will extend support
>> to other blocks later on.
>>
>> Tested on Qualcomm RB3 (debian, sdm845)
>>
>> Signed-off-by: Jessica Zhang <jesszhan at codeaurora.org>
>> ---
>>   drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c    | 169 +++++++++++++++++++-
>>   drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.h    |  20 ++-
>>   drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.c   |  46 +++++-
>>   drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.h   |  13 +-
>>   drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h |   9 +-
>>   5 files changed, 251 insertions(+), 6 deletions(-)
>>
>> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c 
>> b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
>> index 768012243b44..6ebf989c4e67 100644
>> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
>> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.c
>> @@ -1,6 +1,6 @@
>>   // SPDX-License-Identifier: GPL-2.0-only
>>   /*
>> - * Copyright (c) 2014-2018 The Linux Foundation. All rights reserved.
>> + * Copyright (c) 2014-2021 The Linux Foundation. All rights reserved.
>>    * Copyright (C) 2013 Red Hat
>>    * Author: Rob Clark <robdclark at gmail.com>
>>    */
>> @@ -70,6 +70,121 @@ static struct drm_encoder 
>> *get_encoder_from_crtc(struct drm_crtc *crtc)
>>       return NULL;
>>   }
>>   +static enum dpu_crtc_crc_source dpu_crtc_parse_crc_source(const 
>> char *src_name)
>> +{
>> +    if (!src_name || !strcmp(src_name, "none"))
>> +        return DPU_CRTC_CRC_SOURCE_NONE;
>
> Newlines after || please. this would improve readability.
Noted.
>
>> +    if (!strcmp(src_name, "auto") || !strcmp(src_name, "lm"))
>> +        return DPU_CRTC_CRC_SOURCE_LAYER_MIXER;
>> +
>> +    return DPU_CRTC_CRC_SOURCE_INVALID;
>> +}
>> +
>> +static bool dpu_crtc_is_valid_crc_source(enum dpu_crtc_crc_source 
>> source)
>> +{
>> +    return (source > DPU_CRTC_CRC_SOURCE_NONE &&
>> +        source < DPU_CRTC_CRC_SOURCE_MAX);
>> +}
>> +
>> +int dpu_crtc_verify_crc_source(struct drm_crtc *crtc, const char 
>> *src_name, size_t *values_cnt)
>> +{
>> +    enum dpu_crtc_crc_source source = 
>> dpu_crtc_parse_crc_source(src_name);
>> +    struct dpu_crtc_state *crtc_state = to_dpu_crtc_state(crtc->state);
>> +
>> +    if (source < 0) {
>
> Just use dpu_crtc_is_valid_crtc_source() here.

dpu_crtc_is_valid_crc_source() is not exactly the same as checking if 
the source *name* is valid, as "none" is a valid source name (e.g. would 
pass the `source < 0` check), but 
dpu_crtc_is_valid_crc_source(DPU_CRTC_CRC_SOURCE_NONE) would return 
false as DPU_CRTC_CRC_SOURCE_NONE represents when the CRC source is set 
to nothing. The general purpose of dpu_crc_is_valid_crtc_source() is to 
check that the source specified is able to return a CRC value, so a 
source set to "none" would return false, even though "none" is a 
technically valid source name.

Seems like the root issue is that the name 
"dpu_crtc_is_valid_crc_source" is misleading and it would be better to 
rename the helper method to something clearer. Or replace the 
dpu_crtc_is_valid_crc_source() checks with a check against 
DPU_CRTC_CRC_SOURCE_NONE instead.

>
>> +        DRM_DEBUG_DRIVER("Invalid source %s for CRTC%d\n", src_name, 
>> crtc->index);
>> +        return -EINVAL;
>> +    }
>> +
>> +    if (source == DPU_CRTC_CRC_SOURCE_LAYER_MIXER)
>> +        *values_cnt = crtc_state->num_mixers;
>> +
>> +    return 0;
>> +}
>> +
>> +int dpu_crtc_set_crc_source(struct drm_crtc *crtc, const char 
>> *src_name)
>> +{
>> +    enum dpu_crtc_crc_source source = 
>> dpu_crtc_parse_crc_source(src_name);
>> +    enum dpu_crtc_crc_source current_source;
>> +    struct drm_crtc_commit *commit;
>> +    struct dpu_crtc_state *crtc_state;
>> +    struct drm_device *drm_dev = crtc->dev;
>> +    struct dpu_crtc *dpu_crtc = to_dpu_crtc(crtc);
>> +    struct dpu_crtc_mixer *m;
>> +
>> +    bool was_enabled;
>> +    bool enable = false;
>> +    int i, ret = 0;
>> +
>> +    if (source < 0) {
>> +        DRM_DEBUG_DRIVER("Invalid CRC source %s for CRTC%d\n", 
>> src_name, crtc->index);
>> +        return -EINVAL;
>> +    }
>> +
>> +    ret = drm_modeset_lock(&crtc->mutex, NULL);
>> +
>> +    if (ret)
>> +        return ret;
>> +
>> +    /* Wait for any pending commits to finish */
>> +    spin_lock(&crtc->commit_lock);
>> +    commit = list_first_entry_or_null(&crtc->commit_list, struct 
>> drm_crtc_commit, commit_entry);
>> +
>> +    if (commit)
>> +        drm_crtc_commit_get(commit);
>> +    spin_unlock(&crtc->commit_lock);
>> +
>> +    if (commit) {
>> +        ret = 
>> wait_for_completion_interruptible_timeout(&commit->hw_done, 10 * HZ);
>> +
>> +        if (ret)
>> +            goto cleanup;
>> +    }
>
> AMD drivers waits for the commit to finish, because it's commit tail 
> can modify CRC-related registers. It unique, no other drivers seem to 
> do this kind of wait. Why do we need to do it? And if we really need, 
> I'd prefer to have this code in some kind of DRM helper function.
>
Makes sense. I wanted to include it to be safe, but as far as I know 
nothing that happens during a commit will affect reading the CRC for 
this driver. I've also tested without the wait for commit and it doesn't 
seem to affect the CRC read, so I'll remove it.
>
>> +    enable = dpu_crtc_is_valid_crc_source(source);
>> +    crtc_state = to_dpu_crtc_state(crtc->state);
>> +
>> +    spin_lock_irq(&drm_dev->event_lock);
>> +    current_source = dpu_crtc->crc_source;
>> +    spin_unlock_irq(&drm_dev->event_lock);
>> +
>> +    was_enabled = !(current_source == DPU_CRTC_CRC_SOURCE_NONE);
>
> current_source != DPU_CRTC_CRC_SOURCE_NONE would be easier.
>
Noted.
>> +
>> +    if (!was_enabled && enable) {
>> +        ret = drm_crtc_vblank_get(crtc);
>> +
>> +        if (ret)
>> +            goto cleanup;
>> +
>> +    } else if (was_enabled && !enable) {
>> +        drm_crtc_vblank_put(crtc);
>> +    }
>> +
>> +    spin_lock_irq(&drm_dev->event_lock);
>> +    dpu_crtc->crc_source = source;
>> +    spin_unlock_irq(&drm_dev->event_lock);
>> +
>> +    crtc_state->skip_count = 0;
>> +
>> +    for (i = 0; i < crtc_state->num_mixers; ++i) {
>> +        m = &crtc_state->mixers[i];
>> +
>> +        if (!m->hw_lm || !m->hw_lm->ops.collect_misr || 
>> !m->hw_lm->ops.setup_misr)
>> +            continue;
>
> No need to check for collect_misr here, it is not used.
>
Noted.
>> +
>> +        /* Calculate MISR over 1 frame */
>> +        m->hw_lm->ops.setup_misr(m->hw_lm, true, 1);
>> +    }
>> +
>> +
>> +cleanup:
>> +    if (commit)
>> +        drm_crtc_commit_put(commit);
>> +    drm_modeset_unlock(&crtc->mutex);
>> +
>> +    return ret;
>> +}
>> +
>>   static u32 dpu_crtc_get_vblank_counter(struct drm_crtc *crtc)
>>   {
>>       struct drm_encoder *encoder;
>> @@ -83,6 +198,52 @@ static u32 dpu_crtc_get_vblank_counter(struct 
>> drm_crtc *crtc)
>>       return dpu_encoder_get_frame_count(encoder);
>>   }
>>   +
>> +static void dpu_crtc_get_crc(struct drm_crtc *crtc)
>> +{
>> +    struct dpu_crtc *dpu_crtc;
>> +    struct dpu_crtc_state *crtc_state;
>> +    struct dpu_crtc_mixer *m;
>> +    u32 *crcs;
>> +
>> +    int i = 0;
>> +    int rc = 0;
>> +
>> +    if (!crtc) {
>> +        DPU_ERROR("Invalid crtc\n");
>> +        return;
>> +    }
>> +
>> +    crtc_state = to_dpu_crtc_state(crtc->state);
>> +    dpu_crtc = to_dpu_crtc(crtc);
>> +    crcs = kcalloc(crtc_state->num_mixers, sizeof(*crcs), GFP_KERNEL);
>
> This would be constantly leaking the memory. You missed kfree in all 
> return paths (both error and non-error). Since you are limited by the 
> max amount of mixers in the crtc (which is 2 currently and can be 4 at 
> max IIRC), I'd use the on-stack array rather than calling into memory 
> allocator.
>
Noted.
>> +
>> +    /* Skip first 2 frames in case of "uncooked" CRCs */
>> +    if (crtc_state->skip_count < 2) {
>> +        crtc_state->skip_count++;
>
> You see, missing kfree() here.
>
Noted.
>> +        return;
>> +    }
>> +
>> +    for (i = 0; i < crtc_state->num_mixers; ++i) {
>> +
>> +        m = &crtc_state->mixers[i];
>> +
>> +        if (!m->hw_lm || !m->hw_lm->ops.collect_misr
>> +            || !m->hw_lm->ops.setup_misr)
>
> And here we do not use setup_misr, do we?
>
Noted, removed setup_misr check.
>> +            continue;
>> +
>> +        rc = m->hw_lm->ops.collect_misr(m->hw_lm, &crcs[i]);
>> +
>> +        if (rc) {
>> +            DRM_DEBUG_DRIVER("MISR read failed\n");
>
> And here
>
>> +            return;
>> +        }
>> +    }
>> +
>> +    drm_crtc_add_crc_entry(crtc, true,
>> +            drm_crtc_accurate_vblank_count(crtc), crcs);
>
> And even here.
>
> Also I'd propagate the erorr code here. We might not care later, but 
> let's not ignore it if we can.
>
Noted.
>> +}
>> +
>>   static bool dpu_crtc_get_scanout_position(struct drm_crtc *crtc,
>>                          bool in_vblank_irq,
>>                          int *vpos, int *hpos,
>> @@ -389,6 +550,10 @@ void dpu_crtc_vblank_callback(struct drm_crtc 
>> *crtc)
>>           dpu_crtc->vblank_cb_time = ktime_get();
>>       else
>>           dpu_crtc->vblank_cb_count++;
>> +
>> +    if (dpu_crtc_is_valid_crc_source(dpu_crtc->crc_source))
>> +        dpu_crtc_get_crc(crtc);
>> +
>>       drm_crtc_handle_vblank(crtc);
>>       trace_dpu_crtc_vblank_cb(DRMID(crtc));
>>   }
>> @@ -1332,6 +1497,8 @@ static const struct drm_crtc_funcs 
>> dpu_crtc_funcs = {
>>       .atomic_destroy_state = dpu_crtc_destroy_state,
>>       .late_register = dpu_crtc_late_register,
>>       .early_unregister = dpu_crtc_early_unregister,
>> +    .verify_crc_source = dpu_crtc_verify_crc_source,
>> +    .set_crc_source = dpu_crtc_set_crc_source,
>>       .enable_vblank  = msm_crtc_enable_vblank,
>>       .disable_vblank = msm_crtc_disable_vblank,
>>       .get_vblank_timestamp = 
>> drm_crtc_vblank_helper_get_vblank_timestamp,
>> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.h 
>> b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.h
>> index cec3474340e8..e9940f1d5d15 100644
>> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.h
>> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_crtc.h
>> @@ -1,6 +1,6 @@
>>   /* SPDX-License-Identifier: GPL-2.0-only */
>>   /*
>> - * Copyright (c) 2015-2018 The Linux Foundation. All rights reserved.
>> + * Copyright (c) 2015-2021 The Linux Foundation. All rights reserved.
>>    * Copyright (C) 2013 Red Hat
>>    * Author: Rob Clark <robdclark at gmail.com>
>>    */
>> @@ -69,6 +69,19 @@ struct dpu_crtc_smmu_state_data {
>>       uint32_t transition_error;
>>   };
>>   +/**
>> + * enum dpu_crtc_crc_source: CRC source
>> + * @DPU_CRTC_CRC_SOURCE_NONE: no source set
>> + * @DPU_CRTC_CRC_SOURCE_LAYER_MIXER: CRC in layer mixer
>> + * @DPU_CRTC_CRC_SOURCE_INVALID: Invalid source
>> + */
>> +enum dpu_crtc_crc_source {
>> +    DPU_CRTC_CRC_SOURCE_NONE = 0,
>> +    DPU_CRTC_CRC_SOURCE_LAYER_MIXER,
>> +    DPU_CRTC_CRC_SOURCE_MAX,
>> +    DPU_CRTC_CRC_SOURCE_INVALID = -1
>> +};
>> +
>>   /**
>>    * struct dpu_crtc_mixer: stores the map for each virtual pipeline 
>> in the CRTC
>>    * @hw_lm:    LM HW Driver context
>> @@ -139,6 +152,7 @@ struct dpu_crtc_frame_event {
>>    * @event_lock    : Spinlock around event handling code
>>    * @phandle: Pointer to power handler
>>    * @cur_perf      : current performance committed to 
>> clock/bandwidth driver
>> + * @crc_source    : CRC source
>>    */
>>   struct dpu_crtc {
>>       struct drm_crtc base;
>> @@ -171,8 +185,8 @@ struct dpu_crtc {
>>       spinlock_t event_lock;
>>         struct dpu_core_perf_params cur_perf;
>> -
>
> Unrelated
>
Noted.

>>       struct dpu_crtc_smmu_state_data smmu_state;
>> +    enum dpu_crtc_crc_source crc_source;
>
> I think it would find a better place at the dpu_crtc_state, wouldn't it?
>
Makes sense.
>>   };
>>     #define to_dpu_crtc(x) container_of(x, struct dpu_crtc, base)
>> @@ -210,6 +224,8 @@ struct dpu_crtc_state {
>>         u32 num_ctls;
>>       struct dpu_hw_ctl *hw_ctls[CRTC_DUAL_MIXERS];
>> +
>> +    int skip_count;
>
> What are we skipping? Could you please rename it so that usage is 
> clearer.
>
Noted.
>>   };
>>     #define to_dpu_crtc_state(x) \
>> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.c 
>> b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.c
>> index cb6bb7a22c15..679b3728e891 100644
>> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.c
>> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.c
>> @@ -1,5 +1,6 @@
>>   // SPDX-License-Identifier: GPL-2.0-only
>> -/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
>> +/*
>> + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved.
>>    */
>>     #include "dpu_kms.h"
>> @@ -24,6 +25,9 @@
>>   #define LM_BLEND0_FG_ALPHA               0x04
>>   #define LM_BLEND0_BG_ALPHA               0x08
>>   +#define LM_MISR_CTRL                     0x310
>> +#define LM_MISR_SIGNATURE                0x314
>> +
>>   static const struct dpu_lm_cfg *_lm_offset(enum dpu_lm mixer,
>>           const struct dpu_mdss_cfg *m,
>>           void __iomem *addr,
>> @@ -96,6 +100,44 @@ static void dpu_hw_lm_setup_border_color(struct 
>> dpu_hw_mixer *ctx,
>>       }
>>   }
>>   +static void dpu_hw_lm_setup_misr(struct dpu_hw_mixer *ctx, bool 
>> enable, u32 frame_count)
>> +{
>> +    struct dpu_hw_blk_reg_map *c = &ctx->hw;
>> +    u32 config = 0;
>> +
>> +    DPU_REG_WRITE(c, LM_MISR_CTRL, MISR_CTRL_STATUS_CLEAR);
>> +
>> +    /* Clear MISR data */
>> +    wmb();
>
> Do we need wmb here? Maybe it would be more logical to setup 
> LM_MISR_CTRL and clear the status afterwards?
>
We want to guarantee that MISR_SIGNATURE is cleared before it's read in 
case MISR_SIGNATURE is read before a new value is calculated.
>> +
>> +    if (enable)
>> +        config = (frame_count & MISR_FRAME_COUNT_MASK) |
>> +            MISR_CTRL_ENABLE | INTF_MISR_CTRL_FREE_RUN_MASK;
>> +
>> +    DPU_REG_WRITE(c, LM_MISR_CTRL, config);
>
> I think the following might be better:
>
> if (enable)
>    DPU_REG_WRITE(c, LM_MISR_CTRL, config);
> else
>    DPU_REG_WRITE(c, LM_MISR_CTRL, 0);
>
Hmm... agreed. I can see how this may increase readability.
>> +}
>> +
>> +static int dpu_hw_lm_collect_misr(struct dpu_hw_mixer *ctx, u32 
>> *misr_value)
>> +{
>> +    struct dpu_hw_blk_reg_map *c = &ctx->hw;
>> +    u32 ctrl = 0;
>> +
>> +    if (!misr_value)
>> +        return -EINVAL;
>> +
>> +    ctrl = DPU_REG_READ(c, LM_MISR_CTRL);
>> +
>> +    if (!(ctrl & MISR_CTRL_ENABLE))
>> +        return -EINVAL;
>> +
>> +    if (!(ctrl & MISR_CTRL_STATUS))
>> +        return -EINVAL;
>> +
>> +    *misr_value = DPU_REG_READ(c, LM_MISR_SIGNATURE);
>> +
>> +    return 0;
>> +}
>> +
>>   static void dpu_hw_lm_setup_blend_config_sdm845(struct dpu_hw_mixer 
>> *ctx,
>>       u32 stage, u32 fg_alpha, u32 bg_alpha, u32 blend_op)
>>   {
>> @@ -158,6 +200,8 @@ static void _setup_mixer_ops(const struct 
>> dpu_mdss_cfg *m,
>>           ops->setup_blend_config = dpu_hw_lm_setup_blend_config;
>>       ops->setup_alpha_out = dpu_hw_lm_setup_color3;
>>       ops->setup_border_color = dpu_hw_lm_setup_border_color;
>> +    ops->setup_misr = dpu_hw_lm_setup_misr;
>> +    ops->collect_misr = dpu_hw_lm_collect_misr;
>>   }
>>     struct dpu_hw_mixer *dpu_hw_lm_init(enum dpu_lm idx,
>> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.h 
>> b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.h
>> index 4a6b2de19ef6..d8052fb2d5da 100644
>> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.h
>> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_lm.h
>> @@ -1,5 +1,6 @@
>>   /* SPDX-License-Identifier: GPL-2.0-only */
>> -/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
>> +/*
>> + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved.
>>    */
>>     #ifndef _DPU_HW_LM_H
>> @@ -53,6 +54,16 @@ struct dpu_hw_lm_ops {
>>       void (*setup_border_color)(struct dpu_hw_mixer *ctx,
>>           struct dpu_mdss_color *color,
>>           u8 border_en);
>> +
>> +    /**
>> +     * setup_misr: Enable/disable MISR
>> +     */
>> +    void (*setup_misr)(struct dpu_hw_mixer *ctx, bool enable, u32 
>> frame_count);
>> +
>> +    /**
>> +     * collect_misr: Read MISR signature
>> +     */
>> +    int (*collect_misr)(struct dpu_hw_mixer *ctx, u32 *misr_value);
>>   };
>>     struct dpu_hw_mixer {
>> diff --git a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h 
>> b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h
>> index ff3cffde84cd..506d4af7d018 100644
>> --- a/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h
>> +++ b/drivers/gpu/drm/msm/disp/dpu1/dpu_hw_util.h
>> @@ -1,5 +1,6 @@
>>   /* SPDX-License-Identifier: GPL-2.0-only */
>> -/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
>> +/*
>> + * Copyright (c) 2015-2021, The Linux Foundation. All rights reserved.
>>    */
>>     #ifndef _DPU_HW_UTIL_H
>> @@ -309,6 +310,12 @@ int dpu_reg_read(struct dpu_hw_blk_reg_map *c, 
>> u32 reg_off);
>>   #define DPU_REG_WRITE(c, off, val) dpu_reg_write(c, off, val, #off)
>>   #define DPU_REG_READ(c, off) dpu_reg_read(c, off)
>>   +#define MISR_FRAME_COUNT_MASK           0xFF
>> +#define MISR_CTRL_ENABLE                BIT(8)
>> +#define MISR_CTRL_STATUS                BIT(9)
>> +#define MISR_CTRL_STATUS_CLEAR          BIT(10)
>> +#define INTF_MISR_CTRL_FREE_RUN_MASK    BIT(31)
>> +
>
> This seems totally unrelated to dpu_hw_util.h  Please move to 
> dpu_hw_lm.c where they belong. And please fix the prefixes.
>
Noted.
>>   void *dpu_hw_util_get_dir(void);
>>     void dpu_hw_setup_scaler3(struct dpu_hw_blk_reg_map *c,
>>
>
>
Thanks for the feedback!


Best,

Jessica Zhang



More information about the Freedreno mailing list