// 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;
}

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
};

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);
}