// SPDX-License-Identifier: MIT /* * Copyright © 2023 Intel Corporation */ #include "i915_drv.h" #include "i9xx_wm.h" #include "intel_display_types.h" #include "intel_wm.h" #include "skl_watermark.h" /** * intel_update_watermarks - update FIFO watermark values based on current modes * @i915: i915 device * * Calculate watermark values for the various WM regs based on current mode * and plane configuration. * * There are several cases to deal with here: * - normal (i.e. non-self-refresh) * - self-refresh (SR) mode * - lines are large relative to FIFO size (buffer can hold up to 2) * - lines are small relative to FIFO size (buffer can hold more than 2 * lines), so need to account for TLB latency * * The normal calculation is: * watermark = dotclock * bytes per pixel * latency * where latency is platform & configuration dependent (we assume pessimal * values here). * * The SR calculation is: * watermark = (trunc(latency/line time)+1) * surface width * * bytes per pixel * where * line time = htotal / dotclock * surface width = hdisplay for normal plane and 64 for cursor * and latency is assumed to be high, as above. * * The final value programmed to the register should always be rounded up, * and include an extra 2 entries to account for clock crossings. * * We don't use the sprite, so we can ignore that. And on Crestline we have * to set the non-SR watermarks to 8. */ void intel_update_watermarks(struct drm_i915_private *i915) { if (i915->display.funcs.wm->update_wm) i915->display.funcs.wm->update_wm(i915); } int intel_compute_pipe_wm(struct intel_atomic_state *state, struct intel_crtc *crtc) { struct drm_i915_private *i915 = to_i915(state->base.dev); if (i915->display.funcs.wm->compute_pipe_wm) return i915->display.funcs.wm->compute_pipe_wm(state, crtc); return 0; } int intel_compute_intermediate_wm(struct intel_atomic_state *state, struct intel_crtc *crtc) { struct drm_i915_private *i915 = to_i915(state->base.dev); if (!i915->display.funcs.wm->compute_intermediate_wm) return 0; if (drm_WARN_ON(&i915->drm, !i915->display.funcs.wm->compute_pipe_wm)) return 0; return i915->display.funcs.wm->compute_intermediate_wm(state, crtc); } bool intel_initial_watermarks(struct intel_atomic_state *state, struct intel_crtc *crtc) { struct drm_i915_private *i915 = to_i915(state->base.dev); if (i915->display.funcs.wm->initial_watermarks) { i915->display.funcs.wm->initial_watermarks(state, crtc); return true; } return false; } void intel_atomic_update_watermarks(struct intel_atomic_state *state, struct intel_crtc *crtc) { struct drm_i915_private *i915 = to_i915(state->base.dev); if (i915->display.funcs.wm->atomic_update_watermarks) i915->display.funcs.wm->atomic_update_watermarks(state, crtc); } void intel_optimize_watermarks(struct intel_atomic_state *state, struct intel_crtc *crtc) { struct drm_i915_private *i915 = to_i915(state->base.dev); if (i915->display.funcs.wm->optimize_watermarks) i915->display.funcs.wm->optimize_watermarks(state, crtc); } int intel_compute_global_watermarks(struct intel_atomic_state *state) { struct drm_i915_private *i915 = to_i915(state->base.dev); if (i915->display.funcs.wm->compute_global_watermarks) return i915->display.funcs.wm->compute_global_watermarks(state); return 0; } void intel_wm_get_hw_state(struct drm_i915_private *i915) { if (i915->display.funcs.wm->get_hw_state) return i915->display.funcs.wm->get_hw_state(i915); } bool intel_wm_plane_visible(const struct intel_crtc_state *crtc_state, const struct intel_plane_state *plane_state) { struct intel_plane *plane = to_intel_plane(plane_state->uapi.plane); /* FIXME check the 'enable' instead */ if (!crtc_state->hw.active) return false; /* * Treat cursor with fb as always visible since cursor updates * can happen faster than the vrefresh rate, and the current * watermark code doesn't handle that correctly. Cursor updates * which set/clear the fb or change the cursor size are going * to get throttled by intel_legacy_cursor_update() to work * around this problem with the watermark code. */ if (plane->id == PLANE_CURSOR) return plane_state->hw.fb != NULL; else return plane_state->uapi.visible; } void intel_print_wm_latency(struct drm_i915_private *dev_priv, const char *name, const u16 wm[]) { int level; for (level = 0; level < dev_priv->display.wm.num_levels; level++) { unsigned int latency = wm[level]; if (latency == 0) { drm_dbg_kms(&dev_priv->drm, "%s WM%d latency not provided\n", name, level); continue; } /* * - latencies are in us on gen9. * - before then, WM1+ latency values are in 0.5us units */ if (DISPLAY_VER(dev_priv) >= 9) latency *= 10; else if (level > 0) latency *= 5; drm_dbg_kms(&dev_priv->drm, "%s WM%d latency %u (%u.%u usec)\n", name, level, wm[level], latency / 10, latency % 10); } } void intel_wm_init(struct drm_i915_private *i915) { if (DISPLAY_VER(i915) >= 9) skl_wm_init(i915); else i9xx_wm_init(i915); } static void wm_latency_show(struct seq_file *m, const u16 wm[8]) { struct drm_i915_private *dev_priv = m->private; int level; drm_modeset_lock_all(&dev_priv->drm); for (level = 0; level < dev_priv->display.wm.num_levels; level++) { unsigned int latency = wm[level]; /* * - WM1+ latency values in 0.5us units * - latencies are in us on gen9/vlv/chv */ if (DISPLAY_VER(dev_priv) >= 9 || IS_VALLEYVIEW(dev_priv) || IS_CHERRYVIEW(dev_priv) || IS_G4X(dev_priv)) latency *= 10; else if (level > 0) latency *= 5; seq_printf(m, "WM%d %u (%u.%u usec)\n", level, wm[level], latency / 10, latency % 10); } drm_modeset_unlock_all(&dev_priv->drm); } static int pri_wm_latency_show(struct seq_file *m, void *data) { struct drm_i915_private *dev_priv = m->private; const u16 *latencies; if (DISPLAY_VER(dev_priv) >= 9) latencies = dev_priv->display.wm.skl_latency; else latencies = dev_priv->display.wm.pri_latency; wm_latency_show(m, latencies); return 0; } static int spr_wm_latency_show(struct seq_file *m, void *data) { struct drm_i915_private *dev_priv = m->private; const u16 *latencies; if (DISPLAY_VER(dev_priv) >= 9) latencies = dev_priv->display.wm.skl_latency; else latencies = dev_priv->display.wm.spr_latency; wm_latency_show(m, latencies); return 0; } static int cur_wm_latency_show(struct seq_file *m, void *data) { struct drm_i915_private *dev_priv = m->private; const u16 *latencies; if (DISPLAY_VER(dev_priv) >= 9) latencies = dev_priv->display.wm.skl_latency; else latencies = dev_priv->display.wm.cur_latency; wm_latency_show(m, latencies); return 0; } #ifdef notyet static int pri_wm_latency_open(struct inode *inode, struct file *file) { struct drm_i915_private *dev_priv = inode->i_private; if (DISPLAY_VER(dev_priv) < 5 && !IS_G4X(dev_priv)) return -ENODEV; return single_open(file, pri_wm_latency_show, dev_priv); } static int spr_wm_latency_open(struct inode *inode, struct file *file) { struct drm_i915_private *dev_priv = inode->i_private; if (HAS_GMCH(dev_priv)) return -ENODEV; return single_open(file, spr_wm_latency_show, dev_priv); } static int cur_wm_latency_open(struct inode *inode, struct file *file) { struct drm_i915_private *dev_priv = inode->i_private; if (HAS_GMCH(dev_priv)) return -ENODEV; return single_open(file, cur_wm_latency_show, dev_priv); } static ssize_t wm_latency_write(struct file *file, const char __user *ubuf, size_t len, loff_t *offp, u16 wm[8]) { struct seq_file *m = file->private_data; struct drm_i915_private *dev_priv = m->private; u16 new[8] = { 0 }; int level; int ret; char tmp[32]; if (len >= sizeof(tmp)) return -EINVAL; if (copy_from_user(tmp, ubuf, len)) return -EFAULT; tmp[len] = '\0'; ret = sscanf(tmp, "%hu %hu %hu %hu %hu %hu %hu %hu", &new[0], &new[1], &new[2], &new[3], &new[4], &new[5], &new[6], &new[7]); if (ret != dev_priv->display.wm.num_levels) return -EINVAL; drm_modeset_lock_all(&dev_priv->drm); for (level = 0; level < dev_priv->display.wm.num_levels; level++) wm[level] = new[level]; drm_modeset_unlock_all(&dev_priv->drm); return len; } static ssize_t pri_wm_latency_write(struct file *file, const char __user *ubuf, size_t len, loff_t *offp) { struct seq_file *m = file->private_data; struct drm_i915_private *dev_priv = m->private; u16 *latencies; if (DISPLAY_VER(dev_priv) >= 9) latencies = dev_priv->display.wm.skl_latency; else latencies = dev_priv->display.wm.pri_latency; return wm_latency_write(file, ubuf, len, offp, latencies); } static ssize_t spr_wm_latency_write(struct file *file, const char __user *ubuf, size_t len, loff_t *offp) { struct seq_file *m = file->private_data; struct drm_i915_private *dev_priv = m->private; u16 *latencies; if (DISPLAY_VER(dev_priv) >= 9) latencies = dev_priv->display.wm.skl_latency; else latencies = dev_priv->display.wm.spr_latency; return wm_latency_write(file, ubuf, len, offp, latencies); } static ssize_t cur_wm_latency_write(struct file *file, const char __user *ubuf, size_t len, loff_t *offp) { struct seq_file *m = file->private_data; struct drm_i915_private *dev_priv = m->private; u16 *latencies; if (DISPLAY_VER(dev_priv) >= 9) latencies = dev_priv->display.wm.skl_latency; else latencies = dev_priv->display.wm.cur_latency; return wm_latency_write(file, ubuf, len, offp, latencies); } static const struct file_operations i915_pri_wm_latency_fops = { .owner = THIS_MODULE, .open = pri_wm_latency_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = pri_wm_latency_write }; static const struct file_operations i915_spr_wm_latency_fops = { .owner = THIS_MODULE, .open = spr_wm_latency_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = spr_wm_latency_write }; static const struct file_operations i915_cur_wm_latency_fops = { .owner = THIS_MODULE, .open = cur_wm_latency_open, .read = seq_read, .llseek = seq_lseek, .release = single_release, .write = cur_wm_latency_write }; #endif /* notyet */ void intel_wm_debugfs_register(struct drm_i915_private *i915) { struct drm_minor *minor = i915->drm.primary; debugfs_create_file("i915_pri_wm_latency", 0644, minor->debugfs_root, i915, &i915_pri_wm_latency_fops); debugfs_create_file("i915_spr_wm_latency", 0644, minor->debugfs_root, i915, &i915_spr_wm_latency_fops); debugfs_create_file("i915_cur_wm_latency", 0644, minor->debugfs_root, i915, &i915_cur_wm_latency_fops); skl_watermark_debugfs_register(i915); }