[PATCH v16 1/2] drm/tegra: dc: Support memory bandwidth management
Michał Mirosław
mirq-linux at rere.qmqm.pl
Thu Mar 18 09:31:42 UTC 2021
On Wed, Mar 17, 2021 at 09:57:33PM +0300, Dmitry Osipenko wrote:
[...]
> --- a/drivers/gpu/drm/tegra/dc.c
> +++ b/drivers/gpu/drm/tegra/dc.c
> @@ -8,6 +8,7 @@
> #include <linux/debugfs.h>
> #include <linux/delay.h>
> #include <linux/iommu.h>
> +#include <linux/interconnect.h>
> #include <linux/module.h>
> #include <linux/of_device.h>
> #include <linux/pm_runtime.h>
> @@ -618,6 +619,9 @@ static int tegra_plane_atomic_check(struct drm_plane *plane,
> struct tegra_dc *dc = to_tegra_dc(new_plane_state->crtc);
> int err;
>
> + plane_state->peak_memory_bandwidth = 0;
> + plane_state->avg_memory_bandwidth = 0;
> +
> /* no need for further checks if the plane is being disabled */
> if (!new_plane_state->crtc)
> return 0;
> @@ -808,6 +812,12 @@ static struct drm_plane *tegra_primary_plane_create(struct drm_device *drm,
> formats = dc->soc->primary_formats;
> modifiers = dc->soc->modifiers;
>
> + err = tegra_plane_interconnect_init(plane);
> + if (err) {
> + kfree(plane);
> + return ERR_PTR(err);
> + }
> +
> err = drm_universal_plane_init(drm, &plane->base, possible_crtcs,
> &tegra_plane_funcs, formats,
> num_formats, modifiers, type, NULL);
> @@ -841,9 +851,13 @@ static int tegra_cursor_atomic_check(struct drm_plane *plane,
> {
> struct drm_plane_state *new_plane_state = drm_atomic_get_new_plane_state(state,
> plane);
> + struct tegra_plane_state *plane_state = to_tegra_plane_state(new_plane_state);
> struct tegra_plane *tegra = to_tegra_plane(plane);
> int err;
>
> + plane_state->peak_memory_bandwidth = 0;
> + plane_state->avg_memory_bandwidth = 0;
> +
> /* no need for further checks if the plane is being disabled */
> if (!new_plane_state->crtc)
> return 0;
> @@ -985,6 +999,12 @@ static struct drm_plane *tegra_dc_cursor_plane_create(struct drm_device *drm,
> num_formats = ARRAY_SIZE(tegra_cursor_plane_formats);
> formats = tegra_cursor_plane_formats;
>
> + err = tegra_plane_interconnect_init(plane);
> + if (err) {
> + kfree(plane);
> + return ERR_PTR(err);
> + }
> +
> err = drm_universal_plane_init(drm, &plane->base, possible_crtcs,
> &tegra_plane_funcs, formats,
> num_formats, NULL,
> @@ -1099,6 +1119,12 @@ static struct drm_plane *tegra_dc_overlay_plane_create(struct drm_device *drm,
> num_formats = dc->soc->num_overlay_formats;
> formats = dc->soc->overlay_formats;
>
> + err = tegra_plane_interconnect_init(plane);
> + if (err) {
> + kfree(plane);
> + return ERR_PTR(err);
> + }
> +
> if (!cursor)
> type = DRM_PLANE_TYPE_OVERLAY;
> else
> @@ -1216,6 +1242,7 @@ tegra_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
> {
> struct tegra_dc_state *state = to_dc_state(crtc->state);
> struct tegra_dc_state *copy;
> + unsigned int i;
>
> copy = kmalloc(sizeof(*copy), GFP_KERNEL);
> if (!copy)
> @@ -1227,6 +1254,9 @@ tegra_crtc_atomic_duplicate_state(struct drm_crtc *crtc)
> copy->div = state->div;
> copy->planes = state->planes;
>
> + for (i = 0; i < ARRAY_SIZE(state->plane_peak_bw); i++)
> + copy->plane_peak_bw[i] = state->plane_peak_bw[i];
> +
> return ©->base;
> }
>
> @@ -1753,6 +1783,106 @@ static int tegra_dc_wait_idle(struct tegra_dc *dc, unsigned long timeout)
> return -ETIMEDOUT;
> }
>
> +static void
> +tegra_crtc_update_memory_bandwidth(struct drm_crtc *crtc,
> + struct drm_atomic_state *state,
> + bool prepare_bandwidth_transition)
> +{
> + const struct tegra_plane_state *old_tegra_state, *new_tegra_state;
> + const struct tegra_dc_state *old_dc_state, *new_dc_state;
> + u32 i, new_avg_bw, old_avg_bw, new_peak_bw, old_peak_bw;
> + const struct drm_plane_state *old_plane_state;
> + const struct drm_crtc_state *old_crtc_state;
> + struct tegra_dc_window window, old_window;
> + struct tegra_dc *dc = to_tegra_dc(crtc);
> + struct tegra_plane *tegra;
> + struct drm_plane *plane;
> +
> + if (dc->soc->has_nvdisplay)
> + return;
> +
> + old_crtc_state = drm_atomic_get_old_crtc_state(state, crtc);
> + old_dc_state = to_const_dc_state(old_crtc_state);
> + new_dc_state = to_const_dc_state(crtc->state);
> +
> + if (!crtc->state->active) {
> + if (!old_crtc_state->active)
> + return;
> +
> + /*
> + * When CRTC is disabled on DPMS, the state of attached planes
> + * is kept unchanged. Hence we need to enforce removal of the
> + * bandwidths from the ICC paths.
> + */
> + drm_atomic_crtc_for_each_plane(plane, crtc) {
> + tegra = to_tegra_plane(plane);
> +
> + icc_set_bw(tegra->icc_mem, 0, 0);
> + icc_set_bw(tegra->icc_mem_vfilter, 0, 0);
> + }
> +
> + return;
> + }
> +
> + for_each_old_plane_in_state(old_crtc_state->state, plane,
> + old_plane_state, i) {
> + old_tegra_state = to_const_tegra_plane_state(old_plane_state);
> + new_tegra_state = to_const_tegra_plane_state(plane->state);
> + tegra = to_tegra_plane(plane);
> +
> + /*
> + * We're iterating over the global atomic state and it contains
> + * planes from another CRTC, hence we need to filter out the
> + * planes unrelated to this CRTC.
> + */
> + if (tegra->dc != dc)
> + continue;
> +
> + new_avg_bw = new_tegra_state->avg_memory_bandwidth;
> + old_avg_bw = old_tegra_state->avg_memory_bandwidth;
> +
> + new_peak_bw = new_dc_state->plane_peak_bw[tegra->index];
> + old_peak_bw = old_dc_state->plane_peak_bw[tegra->index];
> +
> + /*
> + * See the comment related to !crtc->state->active above,
> + * which explains why bandwidths need to be updated when
> + * CRTC is turning ON.
> + */
> + if (new_avg_bw == old_avg_bw && new_peak_bw == old_peak_bw &&
> + old_crtc_state->active)
> + continue;
> +
> + window.src.h = drm_rect_height(&plane->state->src) >> 16;
> + window.dst.h = drm_rect_height(&plane->state->dst);
> +
> + old_window.src.h = drm_rect_height(&old_plane_state->src) >> 16;
> + old_window.dst.h = drm_rect_height(&old_plane_state->dst);
> +
> + /*
> + * During the preparation phase (atomic_begin), the memory
> + * freq should go high before the DC changes are committed
> + * if bandwidth requirement goes up, otherwise memory freq
> + * should to stay high if BW requirement goes down. The
> + * opposite applies to the completion phase (post_commit).
> + */
> + if (prepare_bandwidth_transition) {
> + new_avg_bw = max(old_avg_bw, new_avg_bw);
> + new_peak_bw = max(old_peak_bw, new_peak_bw);
> +
> + if (tegra_plane_use_vertical_filtering(tegra, &old_window))
> + window = old_window;
> + }
> +
> + icc_set_bw(tegra->icc_mem, new_avg_bw, new_peak_bw);
> +
> + if (tegra_plane_use_vertical_filtering(tegra, &window))
> + icc_set_bw(tegra->icc_mem_vfilter, new_avg_bw, new_peak_bw);
> + else
> + icc_set_bw(tegra->icc_mem_vfilter, 0, 0);
> + }
> +}
> +
> static void tegra_crtc_atomic_disable(struct drm_crtc *crtc,
> struct drm_atomic_state *state)
> {
> @@ -1934,6 +2064,8 @@ static void tegra_crtc_atomic_begin(struct drm_crtc *crtc,
> {
> unsigned long flags;
>
> + tegra_crtc_update_memory_bandwidth(crtc, state, true);
> +
> if (crtc->state->event) {
> spin_lock_irqsave(&crtc->dev->event_lock, flags);
>
> @@ -1966,7 +2098,215 @@ static void tegra_crtc_atomic_flush(struct drm_crtc *crtc,
> value = tegra_dc_readl(dc, DC_CMD_STATE_CONTROL);
> }
>
> +static bool tegra_plane_is_cursor(const struct drm_plane_state *state)
> +{
> + const struct tegra_dc_soc_info *soc = to_tegra_dc(state->crtc)->soc;
> + const struct drm_format_info *fmt = state->fb->format;
> + unsigned int src_w = drm_rect_width(&state->src) >> 16;
> + unsigned int dst_w = drm_rect_width(&state->dst);
> +
> + if (state->plane->type != DRM_PLANE_TYPE_CURSOR)
> + return false;
> +
> + if (soc->supports_cursor)
> + return true;
> +
> + if (src_w != dst_w || fmt->num_planes != 1 || src_w * fmt->cpp[0] > 256)
> + return false;
> +
> + return true;
> +}
> +
> +static unsigned long
> +tegra_plane_overlap_mask(struct drm_crtc_state *state,
> + const struct drm_plane_state *plane_state)
> +{
> + const struct drm_plane_state *other_state;
> + const struct tegra_plane *tegra;
> + unsigned long overlap_mask = 0;
> + struct drm_plane *plane;
> + struct drm_rect rect;
> +
> + if (!plane_state->visible || !plane_state->fb)
> + return 0;
> +
> + /*
> + * Data-prefetch FIFO will easily help to overcome temporal memory
> + * pressure if other plane overlaps with the cursor plane.
> + */
> + if (tegra_plane_is_cursor(plane_state))
> + return 0;
> +
> + drm_atomic_crtc_state_for_each_plane_state(plane, other_state, state) {
> + rect = plane_state->dst;
> +
> + tegra = to_tegra_plane(other_state->plane);
> +
> + if (!other_state->visible || !other_state->fb)
> + continue;
> +
> + /*
> + * Ignore cursor plane overlaps because it's not practical to
> + * assume that it contributes to the bandwidth in overlapping
> + * area if window width is small.
> + */
> + if (tegra_plane_is_cursor(other_state))
> + continue;
> +
> + if (drm_rect_intersect(&rect, &other_state->dst))
> + overlap_mask |= BIT(tegra->index);
> + }
> +
> + return overlap_mask;
> +}
> +
> +static struct drm_plane *
> +tegra_crtc_get_plane_by_index(struct drm_crtc *crtc, unsigned int index)
> +{
> + struct drm_plane *plane;
> +
> + drm_atomic_crtc_for_each_plane(plane, crtc) {
> + if (to_tegra_plane(plane)->index == index)
> + return plane;
> + }
> +
> + return NULL;
> +}
> +
> +static int tegra_crtc_calculate_memory_bandwidth(struct drm_crtc *crtc,
> + struct drm_atomic_state *state)
> +{
> + ulong overlap_mask[TEGRA_DC_LEGACY_PLANES_NUM] = {}, mask;
> + u32 plane_peak_bw[TEGRA_DC_LEGACY_PLANES_NUM] = {};
> + bool all_planes_overlap_simultaneously = true;
> + const struct tegra_plane_state *tegra_state;
> + const struct drm_plane_state *plane_state;
> + const struct tegra_dc_state *old_dc_state;
> + struct tegra_dc *dc = to_tegra_dc(crtc);
> + const struct drm_crtc_state *old_state;
> + struct tegra_dc_state *new_dc_state;
> + struct drm_crtc_state *new_state;
> + struct tegra_plane *tegra;
> + struct drm_plane *plane;
> + u32 i, k, overlap_bw;
> +
> + /*
> + * The nv-display uses shared planes. The algorithm below assumes
> + * maximum 3 planes per-CRTC, this assumption isn't applicable to
> + * the nv-display. Note that T124 support has additional windows,
> + * but currently they aren't supported by the driver.
> + */
> + if (dc->soc->has_nvdisplay)
> + return 0;
> +
> + new_state = drm_atomic_get_new_crtc_state(state, crtc);
> + new_dc_state = to_dc_state(new_state);
> +
> + /*
> + * For overlapping planes pixel's data is fetched for each plane at
> + * the same time, hence bandwidths are accumulated in this case.
> + * This needs to be taken into account for calculating total bandwidth
> + * consumed by all planes.
> + *
> + * Here we get the overlapping state of each plane, which is a
> + * bitmask of plane indices telling with what planes there is an
> + * overlap. Note that bitmask[plane] includes BIT(plane) in order
> + * to make further code nicer and simpler.
> + */
> + drm_atomic_crtc_state_for_each_plane_state(plane, plane_state, new_state) {
> + tegra_state = to_const_tegra_plane_state(plane_state);
> + tegra = to_tegra_plane(plane);
> +
> + if (WARN_ON_ONCE(tegra->index >= TEGRA_DC_LEGACY_PLANES_NUM))
> + return -EINVAL;
> +
> + plane_peak_bw[tegra->index] = tegra_state->peak_memory_bandwidth;
> + mask = tegra_plane_overlap_mask(new_state, plane_state);
> + overlap_mask[tegra->index] = mask;
> +
> + if (hweight_long(mask) != 3)
> + all_planes_overlap_simultaneously = false;
> + }
> +
> + old_state = drm_atomic_get_old_crtc_state(state, crtc);
> + old_dc_state = to_const_dc_state(old_state);
> +
> + /*
> + * Then we calculate maximum bandwidth of each plane state.
> + * The bandwidth includes the plane BW + BW of the "simultaneously"
> + * overlapping planes, where "simultaneously" means areas where DC
> + * fetches from the planes simultaneously during of scan-out process.
> + *
> + * For example, if plane A overlaps with planes B and C, but B and C
> + * don't overlap, then the peak bandwidth will be either in area where
> + * A-and-B or A-and-C planes overlap.
> + *
> + * The plane_peak_bw[] contains peak memory bandwidth values of
> + * each plane, this information is needed by interconnect provider
> + * in order to set up latency allowness based on the peak BW, see
> + * tegra_crtc_update_memory_bandwidth().
> + */
> + for (i = 0; i < ARRAY_SIZE(plane_peak_bw); i++) {
> + overlap_bw = 0;
> +
> + for_each_set_bit(k, &overlap_mask[i], 3) {
> + if (k == i)
> + continue;
> +
> + if (all_planes_overlap_simultaneously)
> + overlap_bw += plane_peak_bw[k];
> + else
> + overlap_bw = max(overlap_bw, plane_peak_bw[k]);
> + }
> +
> + new_dc_state->plane_peak_bw[i] = plane_peak_bw[i] + overlap_bw;
> +
> + /*
> + * If plane's peak bandwidth changed (for example plane isn't
> + * overlapped anymore) and plane isn't in the atomic state,
> + * then add plane to the state in order to have the bandwidth
> + * updated.
> + */
> + if (old_dc_state->plane_peak_bw[i] !=
> + new_dc_state->plane_peak_bw[i]) {
> + plane = tegra_crtc_get_plane_by_index(crtc, i);
> + if (!plane)
> + continue;
> +
> + plane_state = drm_atomic_get_plane_state(state, plane);
> + if (IS_ERR(plane_state))
> + return PTR_ERR(plane_state);
> + }
> + }
> +
> + return 0;
> +}
> +
> +static int tegra_crtc_atomic_check(struct drm_crtc *crtc,
> + struct drm_atomic_state *state)
> +{
> + int err;
> +
> + err = tegra_crtc_calculate_memory_bandwidth(crtc, state);
> + if (err)
> + return err;
> +
> + return 0;
> +}
> +
> +void tegra_crtc_atomic_post_commit(struct drm_crtc *crtc,
> + struct drm_atomic_state *state)
> +{
> + /*
> + * Display bandwidth is allowed to go down only once hardware state
> + * is known to be armed, i.e. state was committed and VBLANK event
> + * received.
> + */
> + tegra_crtc_update_memory_bandwidth(crtc, state, false);
> +}
> +
> static const struct drm_crtc_helper_funcs tegra_crtc_helper_funcs = {
> + .atomic_check = tegra_crtc_atomic_check,
> .atomic_begin = tegra_crtc_atomic_begin,
> .atomic_flush = tegra_crtc_atomic_flush,
> .atomic_enable = tegra_crtc_atomic_enable,
> @@ -2257,7 +2597,9 @@ static const struct tegra_dc_soc_info tegra20_dc_soc_info = {
> .overlay_formats = tegra20_overlay_formats,
> .modifiers = tegra20_modifiers,
> .has_win_a_without_filters = true,
> + .has_win_b_vfilter_mem_client = true,
> .has_win_c_without_vert_filter = true,
> + .plane_tiled_memory_bandwidth_x2 = false,
> };
>
> static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
> @@ -2276,7 +2618,9 @@ static const struct tegra_dc_soc_info tegra30_dc_soc_info = {
> .overlay_formats = tegra20_overlay_formats,
> .modifiers = tegra20_modifiers,
> .has_win_a_without_filters = false,
> + .has_win_b_vfilter_mem_client = true,
> .has_win_c_without_vert_filter = false,
> + .plane_tiled_memory_bandwidth_x2 = true,
> };
>
> static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
> @@ -2295,7 +2639,9 @@ static const struct tegra_dc_soc_info tegra114_dc_soc_info = {
> .overlay_formats = tegra114_overlay_formats,
> .modifiers = tegra20_modifiers,
> .has_win_a_without_filters = false,
> + .has_win_b_vfilter_mem_client = false,
> .has_win_c_without_vert_filter = false,
> + .plane_tiled_memory_bandwidth_x2 = true,
> };
>
> static const struct tegra_dc_soc_info tegra124_dc_soc_info = {
> @@ -2314,7 +2660,9 @@ static const struct tegra_dc_soc_info tegra124_dc_soc_info = {
> .overlay_formats = tegra124_overlay_formats,
> .modifiers = tegra124_modifiers,
> .has_win_a_without_filters = false,
> + .has_win_b_vfilter_mem_client = false,
> .has_win_c_without_vert_filter = false,
> + .plane_tiled_memory_bandwidth_x2 = false,
> };
>
> static const struct tegra_dc_soc_info tegra210_dc_soc_info = {
> @@ -2333,7 +2681,9 @@ static const struct tegra_dc_soc_info tegra210_dc_soc_info = {
> .overlay_formats = tegra114_overlay_formats,
> .modifiers = tegra124_modifiers,
> .has_win_a_without_filters = false,
> + .has_win_b_vfilter_mem_client = false,
> .has_win_c_without_vert_filter = false,
> + .plane_tiled_memory_bandwidth_x2 = false,
> };
>
> static const struct tegra_windowgroup_soc tegra186_dc_wgrps[] = {
> @@ -2382,6 +2732,7 @@ static const struct tegra_dc_soc_info tegra186_dc_soc_info = {
> .has_nvdisplay = true,
> .wgrps = tegra186_dc_wgrps,
> .num_wgrps = ARRAY_SIZE(tegra186_dc_wgrps),
> + .plane_tiled_memory_bandwidth_x2 = false,
> };
>
> static const struct tegra_windowgroup_soc tegra194_dc_wgrps[] = {
> @@ -2430,6 +2781,7 @@ static const struct tegra_dc_soc_info tegra194_dc_soc_info = {
> .has_nvdisplay = true,
> .wgrps = tegra194_dc_wgrps,
> .num_wgrps = ARRAY_SIZE(tegra194_dc_wgrps),
> + .plane_tiled_memory_bandwidth_x2 = false,
> };
For globals you will have .x = false by default; I'm not sure those entries
add much value.
Reviewed-by: Michał Mirosław <mirq-linux at rere.qmqm.pl>
More information about the dri-devel
mailing list